diff --git a/.gitattributes b/.gitattributes index 0bf66f3ab51..aa3877d9167 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,10 +1,11 @@ hack/verify-flags/known-flags.txt merge=union test/test_owners.csv merge=union -**/zz_generated.*.go -diff -**/types.generated.go -diff -**/generated.pb.go -diff +**/zz_generated.*.go -diff linguist-generated=true +**/types.generated.go -diff linguist-generated=true +**/generated.pb.go -diff linguist-generated=true **/generated.proto -diff -**/types_swagger_doc_generated.go -diff -docs/api-reference/** -diff -federation/docs/api-reference/** -diff +**/types_swagger_doc_generated.go -diff linguist-generated=true +docs/api-reference/** -diff linguist-generated=true +api/swagger-spec/*.json -diff linguist-generated=true +api/openapi-spec/*.json -diff linguist-generated=true diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 8a562b52346..d397f16b1df 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -22,7 +22,7 @@ If you're looking for help check [Stack Overflow](https://stackoverflow.com/ques **Environment**: - Kubernetes version (use `kubectl version`): -- Cloud provider or hardware configuration**: +- Cloud provider or hardware configuration: - OS (e.g. from /etc/os-release): - Kernel (e.g. `uname -a`): - Install tools: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 756c905e023..2ac1d28d9ef 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,18 +2,21 @@ 1. If this is your first time, read our contributor guidelines https://git.k8s.io/community/contributors/devel/pull-requests.md#the-pr-submit-process and developer guide https://git.k8s.io/community/contributors/devel/development.md#development-guide 2. If you want *faster* PR reviews, read how: https://git.k8s.io/community/contributors/devel/pull-requests.md#best-practices-for-faster-reviews 3. Follow the instructions for writing a release note: https://git.k8s.io/community/contributors/devel/pull-requests.md#write-release-notes-if-needed +4. If the PR is unfinished, see how to mark it: https://github.com/kubernetes/community/blob/master/contributors/devel/pull-requests.md#marking-unfinished-pull-requests --> **What this PR does / why we need it**: -**Which issue this PR fixes** *(optional, in `fixes #(, fixes #, ...)` format, will close that issue when PR gets merged)*: fixes # +**Which issue(s) this PR fixes** *(optional, in `fixes #(, fixes #, ...)` format, will close the issue(s) when PR gets merged)*: +Fixes # **Special notes for your reviewer**: **Release note**: - ```release-note + ``` diff --git a/CHANGELOG-1.10.md b/CHANGELOG-1.10.md new file mode 100644 index 00000000000..cec21139e65 --- /dev/null +++ b/CHANGELOG-1.10.md @@ -0,0 +1,104 @@ + +- [v1.10.0-alpha.1](#v1100-alpha1) + - [Downloads for v1.10.0-alpha.1](#downloads-for-v1100-alpha1) + - [Client Binaries](#client-binaries) + - [Server Binaries](#server-binaries) + - [Node Binaries](#node-binaries) + - [Changelog since v1.9.0](#changelog-since-v190) + - [Action Required](#action-required) + - [Other notable changes](#other-notable-changes) + + + + + +# v1.10.0-alpha.1 + +[Documentation](https://docs.k8s.io) & [Examples](https://releases.k8s.io/master/examples) + +## Downloads for v1.10.0-alpha.1 + + +filename | sha256 hash +-------- | ----------- +[kubernetes.tar.gz](https://dl.k8s.io/v1.10.0-alpha.1/kubernetes.tar.gz) | `403b90bfa32f7669b326045a629bd15941c533addcaf0c49d3c3c561da0542f2` +[kubernetes-src.tar.gz](https://dl.k8s.io/v1.10.0-alpha.1/kubernetes-src.tar.gz) | `266da065e9eddf19d36df5ad325f2f854101a0e712766148e87d998e789b80cf` + +### Client Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-client-darwin-386.tar.gz](https://dl.k8s.io/v1.10.0-alpha.1/kubernetes-client-darwin-386.tar.gz) | `5aaa8e294ae4060d34828239e37f37b45fa5a69508374be668965102848626be` +[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.10.0-alpha.1/kubernetes-client-darwin-amd64.tar.gz) | `40a8e3bab11b88a2bb8e748f0b29da806d89b55775508039abe9c38c5f4ab97d` +[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.10.0-alpha.1/kubernetes-client-linux-386.tar.gz) | `e08dde0b561529f0b2bb39c141f4d7b1c943749ef7c1f9779facf5fb5b385d6a` +[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.10.0-alpha.1/kubernetes-client-linux-amd64.tar.gz) | `76a05d31acaab932ef45c67e1d6c9273933b8bc06dd5ce9bad3c7345d5267702` +[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.10.0-alpha.1/kubernetes-client-linux-arm64.tar.gz) | `4b833c9e80f3e4ac4958ea0ffb5ae564b31d2a524f6a14e58802937b2b936d73` +[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.10.0-alpha.1/kubernetes-client-linux-arm.tar.gz) | `f1484ab75010a2258ed7717b1284d0c139d17e194ac9e391b8f1c0999eec3c2d` +[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.10.0-alpha.1/kubernetes-client-linux-ppc64le.tar.gz) | `da884f09ec753925b2c1f27ea0a1f6c3da2056855fc88f47929bb3d6c2a09312` +[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.10.0-alpha.1/kubernetes-client-linux-s390x.tar.gz) | `c486f760c6707fc92d1659d3cbe33d68c03190760b73ac215957ee52f9c19195` +[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.10.0-alpha.1/kubernetes-client-windows-386.tar.gz) | `514c550b7ff85ac33e6ed333bcc06461651fe4004d8b7c12ca67f5dc1d2198bf` +[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.10.0-alpha.1/kubernetes-client-windows-amd64.tar.gz) | `ddad59222f6a8cb4e88c4330c2a967c4126cb22ac5e0d7126f9f65cca0fb9f45` + +### Server Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.10.0-alpha.1/kubernetes-server-linux-amd64.tar.gz) | `514efd798ce1d7fe4233127f3334a3238faad6c26372a2d457eff02cbe72d756` +[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.10.0-alpha.1/kubernetes-server-linux-arm64.tar.gz) | `f71f75fb96221f65891fc3e04fd52ae4e5628da8b7b4fbedece3fab4cb650afa` +[kubernetes-server-linux-arm.tar.gz](https://dl.k8s.io/v1.10.0-alpha.1/kubernetes-server-linux-arm.tar.gz) | `a9d8c2386813fd690e60623a6ee1968fe8f0a1a8e13bc5cc12b2caf8e8a862e1` +[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.10.0-alpha.1/kubernetes-server-linux-ppc64le.tar.gz) | `21336a5e40aead4e2ec7e744a99d72bf8cb552341f3141abf8f235beb250cd93` +[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.10.0-alpha.1/kubernetes-server-linux-s390x.tar.gz) | `257e44d38fef83f08990b6b9b5e985118e867c0c33f0e869f0900397b9d30498` + +### Node Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.10.0-alpha.1/kubernetes-node-linux-amd64.tar.gz) | `97bf1210f0595ebf496ca7b000c4367f8a459d97ef72459efc6d0e07a072398f` +[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.10.0-alpha.1/kubernetes-node-linux-arm64.tar.gz) | `eebcd3c14fb4faeb82ab047a2152db528adc2d9f7b20eef6f5dc58202ebe3124` +[kubernetes-node-linux-arm.tar.gz](https://dl.k8s.io/v1.10.0-alpha.1/kubernetes-node-linux-arm.tar.gz) | `3d4428416c775a0a6463f623286bd2ecdf9240ce901e1fbae180dfb564c53ea1` +[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.10.0-alpha.1/kubernetes-node-linux-ppc64le.tar.gz) | `5cc96b24fad0ac1779a66f9b136d90e975b07bf619fea905e6c26ac5a4c41168` +[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.10.0-alpha.1/kubernetes-node-linux-s390x.tar.gz) | `134c13338edf4efcd511f4161742fbaa6dc232965d3d926c3de435e8a080fcbb` +[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.10.0-alpha.1/kubernetes-node-windows-amd64.tar.gz) | `ae54bf2bbcb99cdcde959140460d0f83c0ecb187d060b594ae9c5349960ab055` + +## Changelog since v1.9.0 + +### Action Required + +* [action required] Remove the kubelet's `--cloud-provider=auto-detect` feature ([#56287](https://github.com/kubernetes/kubernetes/pull/56287), [@stewart-yu](https://github.com/stewart-yu)) + +### Other notable changes + +* Fix Heapster configuration and Metrics Server configuration to enable overriding default resource requirements. ([#56965](https://github.com/kubernetes/kubernetes/pull/56965), [@kawych](https://github.com/kawych)) +* YAMLDecoder Read now returns the number of bytes read ([#57000](https://github.com/kubernetes/kubernetes/pull/57000), [@sel](https://github.com/sel)) +* Retry 'connection refused' errors when setting up clusters on GCE. ([#57324](https://github.com/kubernetes/kubernetes/pull/57324), [@mborsz](https://github.com/mborsz)) +* Update kubeadm's minimum supported Kubernetes version in v1.10.x to v1.9.0 ([#57233](https://github.com/kubernetes/kubernetes/pull/57233), [@xiangpengzhao](https://github.com/xiangpengzhao)) +* Graduate CPU Manager feature from alpha to beta. ([#55977](https://github.com/kubernetes/kubernetes/pull/55977), [@ConnorDoyle](https://github.com/ConnorDoyle)) +* Drop hacks used for Mesos integration that was already removed from main kubernetes repository ([#56754](https://github.com/kubernetes/kubernetes/pull/56754), [@dims](https://github.com/dims)) +* Compare correct file names for volume detach operation ([#57053](https://github.com/kubernetes/kubernetes/pull/57053), [@prashima](https://github.com/prashima)) +* Improved event generation in volume mount, attach, and extend operations ([#56872](https://github.com/kubernetes/kubernetes/pull/56872), [@davidz627](https://github.com/davidz627)) +* GCE: bump COS image version to cos-stable-63-10032-71-0 ([#57204](https://github.com/kubernetes/kubernetes/pull/57204), [@yujuhong](https://github.com/yujuhong)) +* fluentd-gcp updated to version 2.0.11. ([#56927](https://github.com/kubernetes/kubernetes/pull/56927), [@x13n](https://github.com/x13n)) +* calico-node addon tolerates all NoExecute and NoSchedule taints by default. ([#57122](https://github.com/kubernetes/kubernetes/pull/57122), [@caseydavenport](https://github.com/caseydavenport)) +* Support LoadBalancer for Azure Virtual Machine Scale Sets ([#57131](https://github.com/kubernetes/kubernetes/pull/57131), [@feiskyer](https://github.com/feiskyer)) +* Makes the kube-dns addon optional so that users can deploy their own DNS solution. ([#57113](https://github.com/kubernetes/kubernetes/pull/57113), [@wwwtyro](https://github.com/wwwtyro)) +* Enabled log rotation for load balancer's api logs to prevent running out of disk space. ([#56979](https://github.com/kubernetes/kubernetes/pull/56979), [@hyperbolic2346](https://github.com/hyperbolic2346)) +* Remove ScrubDNS interface from cloudprovider. ([#56955](https://github.com/kubernetes/kubernetes/pull/56955), [@feiskyer](https://github.com/feiskyer)) +* Fix `etcd-version-monitor` to backward compatibly support etcd 3.1 [go-grpc-prometheus](https://github.com/grpc-ecosystem/go-grpc-prometheus) metrics format. ([#56871](https://github.com/kubernetes/kubernetes/pull/56871), [@jpbetz](https://github.com/jpbetz)) +* enable flexvolume on Windows node ([#56921](https://github.com/kubernetes/kubernetes/pull/56921), [@andyzhangx](https://github.com/andyzhangx)) +* When using Role-Based Access Control, the "admin", "edit", and "view" roles now have the expected permissions on NetworkPolicy resources. ([#56650](https://github.com/kubernetes/kubernetes/pull/56650), [@danwinship](https://github.com/danwinship)) +* Fix the PersistentVolumeLabel controller from initializing the PV labels when it's not the next pending initializer. ([#56831](https://github.com/kubernetes/kubernetes/pull/56831), [@jhorwit2](https://github.com/jhorwit2)) +* kube-apiserver: The external hostname no longer longer use the cloud provider API to select a default. It can be set explicitly using --external-hostname, if needed. ([#56812](https://github.com/kubernetes/kubernetes/pull/56812), [@dims](https://github.com/dims)) +* Use GiB unit for creating and resizing volumes for Glusterfs ([#56581](https://github.com/kubernetes/kubernetes/pull/56581), [@gnufied](https://github.com/gnufied)) +* PersistentVolume flexVolume sources can now reference secrets in a namespace other than the PersistentVolumeClaim's namespace. ([#56460](https://github.com/kubernetes/kubernetes/pull/56460), [@liggitt](https://github.com/liggitt)) +* Scheduler skips pods that use a PVC that either does not exist or is being deleted. ([#55957](https://github.com/kubernetes/kubernetes/pull/55957), [@jsafrane](https://github.com/jsafrane)) +* Fixed a garbage collection race condition where objects with ownerRefs pointing to cluster-scoped objects could be deleted incorrectly. ([#57211](https://github.com/kubernetes/kubernetes/pull/57211), [@liggitt](https://github.com/liggitt)) +* Kubectl explain now prints out the Kind and API version of the resource being explained ([#55689](https://github.com/kubernetes/kubernetes/pull/55689), [@luksa](https://github.com/luksa)) +* api-server provides specific events when unable to repair a service cluster ip or node port ([#54304](https://github.com/kubernetes/kubernetes/pull/54304), [@frodenas](https://github.com/frodenas)) +* Added docker-logins config to kubernetes-worker charm ([#56217](https://github.com/kubernetes/kubernetes/pull/56217), [@Cynerva](https://github.com/Cynerva)) +* delete useless params containerized ([#56146](https://github.com/kubernetes/kubernetes/pull/56146), [@jiulongzaitian](https://github.com/jiulongzaitian)) +* add mount options support for azure disk ([#56147](https://github.com/kubernetes/kubernetes/pull/56147), [@andyzhangx](https://github.com/andyzhangx)) +* Use structured generator for kubectl autoscale ([#55913](https://github.com/kubernetes/kubernetes/pull/55913), [@wackxu](https://github.com/wackxu)) +* K8s supports cephfs fuse mount. ([#55866](https://github.com/kubernetes/kubernetes/pull/55866), [@zhangxiaoyu-zidif](https://github.com/zhangxiaoyu-zidif)) +* COS: Keep the docker network checkpoint ([#54805](https://github.com/kubernetes/kubernetes/pull/54805), [@yujuhong](https://github.com/yujuhong)) +* Fixed documentation typo in IPVS README. ([#56578](https://github.com/kubernetes/kubernetes/pull/56578), [@shift](https://github.com/shift)) + diff --git a/CHANGELOG-1.4.md b/CHANGELOG-1.4.md index 11e70484464..8c229de490b 100644 --- a/CHANGELOG-1.4.md +++ b/CHANGELOG-1.4.md @@ -68,8 +68,8 @@ - [Known Issues](#known-issues) - [Notable Changes to Existing Behavior](#notable-changes-to-existing-behavior) - [Deployments](#deployments) - - [kubectl rolling-update: < v1.4.0 client vs >=v1.4.0 cluster](#kubectl-rolling-update:-<-v140-client-vs->=v140-cluster) - - [kubectl delete: < v1.4.0 client vs >=v1.4.0 cluster](#kubectl-delete:-<-v140-client-vs->=v140-cluster) + - [kubectl rolling-update: < v1.4.0 client vs >=v1.4.0 cluster](#kubectl-rolling-update--v140-client-vs-v140-cluster) + - [kubectl delete: < v1.4.0 client vs >=v1.4.0 cluster](#kubectl-delete--v140-client-vs-v140-cluster) - [DELETE operation in REST API](#delete-operation-in-rest-api) - [Action Required Before Upgrading](#action-required-before-upgrading) - [optionally, remove the old secret](#optionally-remove-the-old-secret) diff --git a/CHANGELOG-1.6.md b/CHANGELOG-1.6.md index 6f47be1eb22..ae2ce75e411 100644 --- a/CHANGELOG-1.6.md +++ b/CHANGELOG-1.6.md @@ -1,88 +1,102 @@ -- [v1.6.11](#v1611) - - [Downloads for v1.6.11](#downloads-for-v1611) +- [v1.6.13](#v1613) + - [Downloads for v1.6.13](#downloads-for-v1613) - [Client Binaries](#client-binaries) - [Server Binaries](#server-binaries) - [Node Binaries](#node-binaries) - - [Changelog since v1.6.10](#changelog-since-v1610) + - [Changelog since v1.6.12](#changelog-since-v1612) - [Other notable changes](#other-notable-changes) -- [v1.6.10](#v1610) - - [Downloads for v1.6.10](#downloads-for-v1610) +- [v1.6.12](#v1612) + - [Downloads for v1.6.12](#downloads-for-v1612) - [Client Binaries](#client-binaries-1) - [Server Binaries](#server-binaries-1) - [Node Binaries](#node-binaries-1) - - [Changelog since v1.6.9](#changelog-since-v169) + - [Changelog since v1.6.11](#changelog-since-v1611) - [Other notable changes](#other-notable-changes-1) -- [v1.6.9](#v169) - - [Downloads for v1.6.9](#downloads-for-v169) +- [v1.6.11](#v1611) + - [Downloads for v1.6.11](#downloads-for-v1611) - [Client Binaries](#client-binaries-2) - [Server Binaries](#server-binaries-2) - [Node Binaries](#node-binaries-2) - - [Changelog since v1.6.8](#changelog-since-v168) + - [Changelog since v1.6.10](#changelog-since-v1610) - [Other notable changes](#other-notable-changes-2) -- [v1.6.8](#v168) - - [Downloads for v1.6.8](#downloads-for-v168) +- [v1.6.10](#v1610) + - [Downloads for v1.6.10](#downloads-for-v1610) - [Client Binaries](#client-binaries-3) - [Server Binaries](#server-binaries-3) - [Node Binaries](#node-binaries-3) - - [Changelog since v1.6.7](#changelog-since-v167) + - [Changelog since v1.6.9](#changelog-since-v169) - [Other notable changes](#other-notable-changes-3) -- [v1.6.7](#v167) - - [Downloads for v1.6.7](#downloads-for-v167) +- [v1.6.9](#v169) + - [Downloads for v1.6.9](#downloads-for-v169) - [Client Binaries](#client-binaries-4) - [Server Binaries](#server-binaries-4) - [Node Binaries](#node-binaries-4) - - [Changelog since v1.6.6](#changelog-since-v166) + - [Changelog since v1.6.8](#changelog-since-v168) - [Other notable changes](#other-notable-changes-4) -- [v1.6.6](#v166) - - [Downloads for v1.6.6](#downloads-for-v166) +- [v1.6.8](#v168) + - [Downloads for v1.6.8](#downloads-for-v168) - [Client Binaries](#client-binaries-5) - [Server Binaries](#server-binaries-5) - [Node Binaries](#node-binaries-5) - - [Changelog since v1.6.5](#changelog-since-v165) - - [Action Required](#action-required) + - [Changelog since v1.6.7](#changelog-since-v167) - [Other notable changes](#other-notable-changes-5) -- [v1.6.5](#v165) - - [Known Issues for v1.6.5](#known-issues-for-v165) - - [Downloads for v1.6.5](#downloads-for-v165) +- [v1.6.7](#v167) + - [Downloads for v1.6.7](#downloads-for-v167) - [Client Binaries](#client-binaries-6) - [Server Binaries](#server-binaries-6) - [Node Binaries](#node-binaries-6) - - [Changelog since v1.6.4](#changelog-since-v164) + - [Changelog since v1.6.6](#changelog-since-v166) - [Other notable changes](#other-notable-changes-6) -- [v1.6.4](#v164) - - [Known Issues for v1.6.4](#known-issues-for-v164) - - [Downloads for v1.6.4](#downloads-for-v164) +- [v1.6.6](#v166) + - [Downloads for v1.6.6](#downloads-for-v166) - [Client Binaries](#client-binaries-7) - [Server Binaries](#server-binaries-7) - [Node Binaries](#node-binaries-7) - - [Changelog since v1.6.3](#changelog-since-v163) + - [Changelog since v1.6.5](#changelog-since-v165) + - [Action Required](#action-required) - [Other notable changes](#other-notable-changes-7) -- [v1.6.3](#v163) - - [Known Issues for v1.6.3](#known-issues-for-v163) - - [Downloads for v1.6.3](#downloads-for-v163) +- [v1.6.5](#v165) + - [Known Issues for v1.6.5](#known-issues-for-v165) + - [Downloads for v1.6.5](#downloads-for-v165) - [Client Binaries](#client-binaries-8) - [Server Binaries](#server-binaries-8) - [Node Binaries](#node-binaries-8) - - [Changelog since v1.6.2](#changelog-since-v162) + - [Changelog since v1.6.4](#changelog-since-v164) - [Other notable changes](#other-notable-changes-8) -- [v1.6.2](#v162) - - [Downloads for v1.6.2](#downloads-for-v162) +- [v1.6.4](#v164) + - [Known Issues for v1.6.4](#known-issues-for-v164) + - [Downloads for v1.6.4](#downloads-for-v164) - [Client Binaries](#client-binaries-9) - [Server Binaries](#server-binaries-9) - - [Changelog since v1.6.1](#changelog-since-v161) + - [Node Binaries](#node-binaries-9) + - [Changelog since v1.6.3](#changelog-since-v163) - [Other notable changes](#other-notable-changes-9) -- [v1.6.1](#v161) - - [Downloads for v1.6.1](#downloads-for-v161) +- [v1.6.3](#v163) + - [Known Issues for v1.6.3](#known-issues-for-v163) + - [Downloads for v1.6.3](#downloads-for-v163) - [Client Binaries](#client-binaries-10) - [Server Binaries](#server-binaries-10) - - [Changelog since v1.6.0](#changelog-since-v160) + - [Node Binaries](#node-binaries-10) + - [Changelog since v1.6.2](#changelog-since-v162) - [Other notable changes](#other-notable-changes-10) -- [v1.6.0](#v160) - - [Downloads for v1.6.0](#downloads-for-v160) +- [v1.6.2](#v162) + - [Downloads for v1.6.2](#downloads-for-v162) - [Client Binaries](#client-binaries-11) - [Server Binaries](#server-binaries-11) - - [WARNING: etcd backup strongly recommended](#warning:-etcd-backup-strongly-recommended) + - [Changelog since v1.6.1](#changelog-since-v161) + - [Other notable changes](#other-notable-changes-11) +- [v1.6.1](#v161) + - [Downloads for v1.6.1](#downloads-for-v161) + - [Client Binaries](#client-binaries-12) + - [Server Binaries](#server-binaries-12) + - [Changelog since v1.6.0](#changelog-since-v160) + - [Other notable changes](#other-notable-changes-12) +- [v1.6.0](#v160) + - [Downloads for v1.6.0](#downloads-for-v160) + - [Client Binaries](#client-binaries-13) + - [Server Binaries](#server-binaries-13) + - [WARNING: etcd backup strongly recommended](#warning-etcd-backup-strongly-recommended) - [Major updates and release themes](#major-updates-and-release-themes) - [Action Required](#action-required-1) - [Certificates API](#certificates-api) @@ -148,7 +162,7 @@ - [vSphere](#vsphere) - [Federation](#federation-2) - [kubefed](#kubefed) - - [Other Notable Changes](#other-notable-changes-11) + - [Other Notable Changes](#other-notable-changes-13) - [Garbage Collector](#garbage-collector) - [kubeadm](#kubeadm-2) - [kubectl](#kubectl-1) @@ -158,7 +172,7 @@ - [Updates to apply](#updates-to-apply) - [Updates to edit](#updates-to-edit) - [Bug fixes](#bug-fixes) - - [Other Notable Changes](#other-notable-changes-12) + - [Other Notable Changes](#other-notable-changes-14) - [Node Components](#node-components-2) - [Bug fixes](#bug-fixes-1) - [kube-controller-manager](#kube-controller-manager) @@ -171,7 +185,7 @@ - [Photon](#photon) - [rbd](#rbd) - [vSphere](#vsphere-1) - - [Other Notable Changes](#other-notable-changes-13) + - [Other Notable Changes](#other-notable-changes-15) - [Changes to Cluster Provisioning Scripts](#changes-to-cluster-provisioning-scripts) - [AWS](#aws-1) - [Juju](#juju) @@ -179,7 +193,7 @@ - [GCE](#gce-1) - [OpenStack](#openstack) - [Container Images](#container-images) - - [Other Notable Changes](#other-notable-changes-14) + - [Other Notable Changes](#other-notable-changes-16) - [Changes to Addons](#changes-to-addons) - [Dashboard](#dashboard) - [DNS](#dns) @@ -195,60 +209,176 @@ - [Previous Releases Included in v1.6.0](#previous-releases-included-in-v160) - [v1.6.0-rc.1](#v160-rc1) - [Downloads for v1.6.0-rc.1](#downloads-for-v160-rc1) - - [Client Binaries](#client-binaries-12) - - [Server Binaries](#server-binaries-12) - - [Changelog since v1.6.0-beta.4](#changelog-since-v160-beta4) - - [Other notable changes](#other-notable-changes-15) -- [v1.6.0-beta.4](#v160-beta4) - - [Downloads for v1.6.0-beta.4](#downloads-for-v160-beta4) - - [Client Binaries](#client-binaries-13) - - [Server Binaries](#server-binaries-13) - - [Changelog since v1.6.0-beta.3](#changelog-since-v160-beta3) - - [Other notable changes](#other-notable-changes-16) -- [v1.6.0-beta.3](#v160-beta3) - - [Downloads for v1.6.0-beta.3](#downloads-for-v160-beta3) - [Client Binaries](#client-binaries-14) - [Server Binaries](#server-binaries-14) - - [Changelog since v1.6.0-beta.2](#changelog-since-v160-beta2) + - [Changelog since v1.6.0-beta.4](#changelog-since-v160-beta4) - [Other notable changes](#other-notable-changes-17) -- [v1.6.0-beta.2](#v160-beta2) - - [Downloads for v1.6.0-beta.2](#downloads-for-v160-beta2) +- [v1.6.0-beta.4](#v160-beta4) + - [Downloads for v1.6.0-beta.4](#downloads-for-v160-beta4) - [Client Binaries](#client-binaries-15) - [Server Binaries](#server-binaries-15) - - [Changelog since v1.6.0-beta.1](#changelog-since-v160-beta1) - - [Action Required](#action-required-2) + - [Changelog since v1.6.0-beta.3](#changelog-since-v160-beta3) - [Other notable changes](#other-notable-changes-18) -- [v1.6.0-beta.1](#v160-beta1) - - [Downloads for v1.6.0-beta.1](#downloads-for-v160-beta1) +- [v1.6.0-beta.3](#v160-beta3) + - [Downloads for v1.6.0-beta.3](#downloads-for-v160-beta3) - [Client Binaries](#client-binaries-16) - [Server Binaries](#server-binaries-16) - - [Changelog since v1.6.0-alpha.3](#changelog-since-v160-alpha3) - - [Action Required](#action-required-3) + - [Changelog since v1.6.0-beta.2](#changelog-since-v160-beta2) - [Other notable changes](#other-notable-changes-19) -- [v1.6.0-alpha.3](#v160-alpha3) - - [Downloads for v1.6.0-alpha.3](#downloads-for-v160-alpha3) +- [v1.6.0-beta.2](#v160-beta2) + - [Downloads for v1.6.0-beta.2](#downloads-for-v160-beta2) - [Client Binaries](#client-binaries-17) - [Server Binaries](#server-binaries-17) - - [Changelog since v1.6.0-alpha.2](#changelog-since-v160-alpha2) + - [Changelog since v1.6.0-beta.1](#changelog-since-v160-beta1) + - [Action Required](#action-required-2) - [Other notable changes](#other-notable-changes-20) -- [v1.6.0-alpha.2](#v160-alpha2) - - [Downloads for v1.6.0-alpha.2](#downloads-for-v160-alpha2) +- [v1.6.0-beta.1](#v160-beta1) + - [Downloads for v1.6.0-beta.1](#downloads-for-v160-beta1) - [Client Binaries](#client-binaries-18) - [Server Binaries](#server-binaries-18) - - [Changelog since v1.6.0-alpha.1](#changelog-since-v160-alpha1) + - [Changelog since v1.6.0-alpha.3](#changelog-since-v160-alpha3) + - [Action Required](#action-required-3) - [Other notable changes](#other-notable-changes-21) -- [v1.6.0-alpha.1](#v160-alpha1) - - [Downloads for v1.6.0-alpha.1](#downloads-for-v160-alpha1) +- [v1.6.0-alpha.3](#v160-alpha3) + - [Downloads for v1.6.0-alpha.3](#downloads-for-v160-alpha3) - [Client Binaries](#client-binaries-19) - [Server Binaries](#server-binaries-19) + - [Changelog since v1.6.0-alpha.2](#changelog-since-v160-alpha2) + - [Other notable changes](#other-notable-changes-22) +- [v1.6.0-alpha.2](#v160-alpha2) + - [Downloads for v1.6.0-alpha.2](#downloads-for-v160-alpha2) + - [Client Binaries](#client-binaries-20) + - [Server Binaries](#server-binaries-20) + - [Changelog since v1.6.0-alpha.1](#changelog-since-v160-alpha1) + - [Other notable changes](#other-notable-changes-23) +- [v1.6.0-alpha.1](#v160-alpha1) + - [Downloads for v1.6.0-alpha.1](#downloads-for-v160-alpha1) + - [Client Binaries](#client-binaries-21) + - [Server Binaries](#server-binaries-21) - [Changelog since v1.5.0](#changelog-since-v150) - [Action Required](#action-required-4) - - [Other notable changes](#other-notable-changes-22) + - [Other notable changes](#other-notable-changes-24) +# v1.6.13 + +[Documentation](https://docs.k8s.io) & [Examples](https://releases.k8s.io/release-1.6/examples) + +## Downloads for v1.6.13 + + +filename | sha256 hash +-------- | ----------- +[kubernetes.tar.gz](https://dl.k8s.io/v1.6.13/kubernetes.tar.gz) | `effda17f63d149f1082e85cf5e5bc41b1f5f9bf64e082020c4455ce2e4525e26` +[kubernetes-src.tar.gz](https://dl.k8s.io/v1.6.13/kubernetes-src.tar.gz) | `0552eb170d276c8dc33a66629da9641fd333813c353a2e741593c9acf81d39fd` + +### Client Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-client-darwin-386.tar.gz](https://dl.k8s.io/v1.6.13/kubernetes-client-darwin-386.tar.gz) | `91cf455f8271e781de76517d2ba31740f8d8a2c9a9f900a59bae8ff639b1f2fc` +[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.6.13/kubernetes-client-darwin-amd64.tar.gz) | `5e1aa9357eb027d4c4ab21ee1bbc54a3c9bf3a60265a168ab7a02ae32f1800c1` +[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.6.13/kubernetes-client-linux-386.tar.gz) | `a2fb4f6a68581ef5aaf4dc2e0d4bc10c432007f88c82c97e236e27b482fc77fa` +[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.6.13/kubernetes-client-linux-amd64.tar.gz) | `d94f999c6a7bcda4e0901399d7613eba237c2f0632df9317a59d87097ba59cda` +[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.6.13/kubernetes-client-linux-arm64.tar.gz) | `b80f2ecb7c6e5f3315b129a69efef7d01317fada4a739a6a890225d24c50a35d` +[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.6.13/kubernetes-client-linux-arm.tar.gz) | `270396ec50d523706387d1270c717d1d3c00112c6e92399726620086643025f5` +[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.6.13/kubernetes-client-linux-ppc64le.tar.gz) | `117d5d73529306c2b43e7db7692fa651d7a715c61ca31866af87aee8c81bd8e2` +[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.6.13/kubernetes-client-linux-s390x.tar.gz) | `48e812aa64e484f10a5083c2198b48b75d203489084271a259ba55b6138f616c` +[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.6.13/kubernetes-client-windows-386.tar.gz) | `e3425bf16e66250f6cb358efb908b4922d82258772259829ac6fb43a6e8c5f5a` +[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.6.13/kubernetes-client-windows-amd64.tar.gz) | `04514ad6957011da3e26e5bcf23ffad4078a42a0286dfd66da8a46aee3132acb` + +### Server Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.6.13/kubernetes-server-linux-amd64.tar.gz) | `08854f22684fe3dcf00c90ed841ab0c40566175a7611fc6111943da0beb8aed8` +[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.6.13/kubernetes-server-linux-arm64.tar.gz) | `01cae48b032fab674aad7c350c8e04b4f5bcc77f98601817cb9177843bb24f4b` +[kubernetes-server-linux-arm.tar.gz](https://dl.k8s.io/v1.6.13/kubernetes-server-linux-arm.tar.gz) | `e0ce0f75442e48ea495c36071818345c9302204f933acb86c8ad357da8924fa2` +[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.6.13/kubernetes-server-linux-ppc64le.tar.gz) | `5bba326c1a95030ba559627589107875932ef9d41610888a4ee89785f022f9b1` +[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.6.13/kubernetes-server-linux-s390x.tar.gz) | `8e21336a027ad71c382d2d0ecac777b32441b90dad948b67a490782886460370` + +### Node Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.6.13/kubernetes-node-linux-amd64.tar.gz) | `c2062b4d2c00db35b5d159651d50fbdc826fc4f866d4add483650da58b4cdfa1` +[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.6.13/kubernetes-node-linux-arm64.tar.gz) | `9b865d9be4ecde441ed959e240b4e7df1718e1fd38861084ae6f4b14e3b2f8df` +[kubernetes-node-linux-arm.tar.gz](https://dl.k8s.io/v1.6.13/kubernetes-node-linux-arm.tar.gz) | `9d0433621021bca490e8a04eb96e47402d3f8e3d47762590b83be6a595d29b16` +[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.6.13/kubernetes-node-linux-ppc64le.tar.gz) | `438f0e05d29e371c028e73b28c31e3b13dc1decd0780557e0231d96de871b419` +[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.6.13/kubernetes-node-linux-s390x.tar.gz) | `aabf03fd85b75d57cc27548e74585e3926383a82b3f8201800b34ca483d31bcf` +[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.6.13/kubernetes-node-windows-amd64.tar.gz) | `b02148bfdc9c0937841cced51a436afcc81a70d10be2edcf3b55f045e4ee3b2f` + +## Changelog since v1.6.12 + +### Other notable changes + +* Bump cAdvisor to v0.25.1, which adds support for the Docker overlay2 storage driver. ([#55744](https://github.com/kubernetes/kubernetes/pull/55744), [@dashpole](https://github.com/dashpole)) +* GCE: provide an option to disable docker's live-restore on COS/ubuntu ([#55260](https://github.com/kubernetes/kubernetes/pull/55260), [@yujuhong](https://github.com/yujuhong)) +* Fix kube-proxy hang due to panic when requesting a closed healthcheck. ([#46450](https://github.com/kubernetes/kubernetes/pull/46450), [@MrHohn](https://github.com/MrHohn)) +* Fix Flexvolume/FC/ISCSI volumes not being mounted after Node reboot. ([#51985](https://github.com/kubernetes/kubernetes/pull/51985), [@chakri-nelluri](https://github.com/chakri-nelluri)) + + + +# v1.6.12 + +[Documentation](https://docs.k8s.io) & [Examples](https://releases.k8s.io/release-1.6/examples) + +## Downloads for v1.6.12 + + +filename | sha256 hash +-------- | ----------- +[kubernetes.tar.gz](https://dl.k8s.io/v1.6.12/kubernetes.tar.gz) | `d57a05942f581959bc31778def4df4b526df80adaa75942214e9439fdecbe74f` +[kubernetes-src.tar.gz](https://dl.k8s.io/v1.6.12/kubernetes-src.tar.gz) | `c1781161d1b0fa0c8dbd3a644ee42e65968a0a4bb5bec47f2c70796edbc1d396` + +### Client Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-client-darwin-386.tar.gz](https://dl.k8s.io/v1.6.12/kubernetes-client-darwin-386.tar.gz) | `85956c72c8fc1a1c4768e95fb403ddb1740db1f2d6dec8263d74a67cfce429b9` +[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.6.12/kubernetes-client-darwin-amd64.tar.gz) | `bcea37dc2852ee83bdcac505600e724ae391e3a5879232d47ddd763cb2253c14` +[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.6.12/kubernetes-client-linux-386.tar.gz) | `8e0f3d0a6807b050070717efacd25db9f96ad3f0ef59d9095aae71ab20734212` +[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.6.12/kubernetes-client-linux-amd64.tar.gz) | `5f324e91dce59e05acdc0d6c0a8a6a56d5a5d1a3144e58b6de5c22a9b9020f8b` +[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.6.12/kubernetes-client-linux-arm64.tar.gz) | `442fadf05af70826231e03b0146c0f9283ec474d6a1ba390eac7204888db5dc7` +[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.6.12/kubernetes-client-linux-arm.tar.gz) | `2e8073e626ba6a6f6148ccca3d5bbfd20f051f9992de676446d2ae259555a75a` +[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.6.12/kubernetes-client-linux-ppc64le.tar.gz) | `f6af1ea1762e9fe889e10da670f61ce0bd2c1bc8fd5c1bd9fc3d1eacabe820a1` +[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.6.12/kubernetes-client-linux-s390x.tar.gz) | `77aee845c290b5637d2560586e9065220aa433d6e15be03c6bbb6dfc75292564` +[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.6.12/kubernetes-client-windows-386.tar.gz) | `6a47f85d4e01595bb7e58469a4cab09053e5e11819440c630ae47008aa0b000d` +[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.6.12/kubernetes-client-windows-amd64.tar.gz) | `6b4f1ae4bed0523a0c5b2b1d6dc9cb1ac54399a2a78b548acd876b8029a2dd6c` + +### Server Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.6.12/kubernetes-server-linux-amd64.tar.gz) | `0970f6da85acd1bb61e92a77c7dbfa3f059da409db68c3d940781708795a0204` +[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.6.12/kubernetes-server-linux-arm64.tar.gz) | `d598d846882744e724e65156807a3b2fa5bb92e1c389ff2e08984455346a7305` +[kubernetes-server-linux-arm.tar.gz](https://dl.k8s.io/v1.6.12/kubernetes-server-linux-arm.tar.gz) | `6c02ade45ba8495917f0a7fe0542369ba482bc98f2dc008f7f69a21fd2e653d2` +[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.6.12/kubernetes-server-linux-ppc64le.tar.gz) | `5479030e4ff21afcd638f4dd22bd0f6fb221dc3b27731954c0a5e53877ed1131` +[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.6.12/kubernetes-server-linux-s390x.tar.gz) | `6407d6c580acc2fafbe2d87b309e60c6d16624403308e41a8a973d772bf26e3b` + +### Node Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.6.12/kubernetes-node-linux-amd64.tar.gz) | `9cb5a28417cbc34165eaa2eb412488738b0e190e9128bb68b16d68d2a942b4c9` +[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.6.12/kubernetes-node-linux-arm64.tar.gz) | `e4c1d1994b7595f2ac9c7ffebaaefc0737c1fbbcc4d2a9a9097d52d060236ad8` +[kubernetes-node-linux-arm.tar.gz](https://dl.k8s.io/v1.6.12/kubernetes-node-linux-arm.tar.gz) | `77879441f3b5e753959b32103ef6a49fbc9ccea16cac1b1fb83446b5d573901d` +[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.6.12/kubernetes-node-linux-ppc64le.tar.gz) | `c6866605adf86d5bc91acb670c5d8bba00c989a8758014a17322c041289e472d` +[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.6.12/kubernetes-node-linux-s390x.tar.gz) | `8a280b31c7118728660ac4b7010ed0af9bc3b058e98964c04b47f58c5fd84671` +[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.6.12/kubernetes-node-windows-amd64.tar.gz) | `bc3d39ed8f23e59692bcffc23eaae7f575bef01e3c7c1510528662f984283a9c` + +## Changelog since v1.6.11 + +### Other notable changes + +* Azure cloudprovider: Fix controller manager crash issue on a manually created k8s cluster. ([#53694](https://github.com/kubernetes/kubernetes/pull/53694), [@andyzhangx](https://github.com/andyzhangx)) +* Fix kubelet reset liveness probe failure count across pod restart boundaries. ([#46371](https://github.com/kubernetes/kubernetes/pull/46371), [@sjenning](https://github.com/sjenning)) + + + # v1.6.11 [Documentation](https://docs.k8s.io) & [Examples](https://releases.k8s.io/release-1.6/examples) @@ -1135,7 +1265,7 @@ Anyway, the cluster should get back to the proper size after 10 min. clusters post upgrade where kubelet hard eviction has been turned on for memory. To opt-out set `--experimental-allocatable-ignore-eviction=true`. * More details on these feature here: - https://kubernetes.io/docs/concepts/cluster-administration/node-allocatable/ + https://kubernetes.io/docs/tasks/administer-cluster/reserve-compute-resources/ * Drop the support for docker 1.9.x. Docker versions 1.10.3, 1.11.2, 1.12.6 have been validated. * The following deprecated kubelet flags are removed: `--config`, `--auth-path`, diff --git a/CHANGELOG-1.7.md b/CHANGELOG-1.7.md index 478466ed65b..dce11c3515d 100644 --- a/CHANGELOG-1.7.md +++ b/CHANGELOG-1.7.md @@ -1,65 +1,93 @@ -- [v1.7.8](#v178) - - [Downloads for v1.7.8](#downloads-for-v178) +- [v1.7.12](#v1712) + - [Downloads for v1.7.12](#downloads-for-v1712) - [Client Binaries](#client-binaries) - [Server Binaries](#server-binaries) - [Node Binaries](#node-binaries) - - [Changelog since v1.7.7](#changelog-since-v177) + - [Changelog since v1.7.11](#changelog-since-v1711) - [Other notable changes](#other-notable-changes) -- [v1.7.7](#v177) - - [Downloads for v1.7.7](#downloads-for-v177) +- [v1.7.11](#v1711) + - [Downloads for v1.7.11](#downloads-for-v1711) - [Client Binaries](#client-binaries-1) - [Server Binaries](#server-binaries-1) - [Node Binaries](#node-binaries-1) - - [Changelog since v1.7.6](#changelog-since-v176) + - [Changelog since v1.7.10](#changelog-since-v1710) - [Other notable changes](#other-notable-changes-1) -- [v1.7.6](#v176) - - [Downloads for v1.7.6](#downloads-for-v176) +- [v1.7.10](#v1710) + - [Downloads for v1.7.10](#downloads-for-v1710) - [Client Binaries](#client-binaries-2) - [Server Binaries](#server-binaries-2) - [Node Binaries](#node-binaries-2) - - [Changelog since v1.7.5](#changelog-since-v175) + - [Changelog since v1.7.9](#changelog-since-v179) - [Other notable changes](#other-notable-changes-2) -- [v1.7.5](#v175) - - [Downloads for v1.7.5](#downloads-for-v175) +- [v1.7.9](#v179) + - [Downloads for v1.7.9](#downloads-for-v179) - [Client Binaries](#client-binaries-3) - [Server Binaries](#server-binaries-3) - [Node Binaries](#node-binaries-3) - - [Changelog since v1.7.4](#changelog-since-v174) + - [Changelog since v1.7.8](#changelog-since-v178) - [Other notable changes](#other-notable-changes-3) -- [v1.7.4](#v174) - - [Downloads for v1.7.4](#downloads-for-v174) +- [v1.7.8](#v178) + - [Downloads for v1.7.8](#downloads-for-v178) - [Client Binaries](#client-binaries-4) - [Server Binaries](#server-binaries-4) - [Node Binaries](#node-binaries-4) - - [Changelog since v1.7.3](#changelog-since-v173) + - [Changelog since v1.7.7](#changelog-since-v177) - [Other notable changes](#other-notable-changes-4) -- [v1.7.3](#v173) - - [Downloads for v1.7.3](#downloads-for-v173) +- [v1.7.7](#v177) + - [Downloads for v1.7.7](#downloads-for-v177) - [Client Binaries](#client-binaries-5) - [Server Binaries](#server-binaries-5) - [Node Binaries](#node-binaries-5) - - [Changelog since v1.7.2](#changelog-since-v172) + - [Changelog since v1.7.6](#changelog-since-v176) - [Other notable changes](#other-notable-changes-5) -- [v1.7.2](#v172) - - [Downloads for v1.7.2](#downloads-for-v172) +- [v1.7.6](#v176) + - [Downloads for v1.7.6](#downloads-for-v176) - [Client Binaries](#client-binaries-6) - [Server Binaries](#server-binaries-6) - [Node Binaries](#node-binaries-6) - - [Changelog since v1.7.1](#changelog-since-v171) + - [Changelog since v1.7.5](#changelog-since-v175) - [Other notable changes](#other-notable-changes-6) -- [v1.7.1](#v171) - - [Downloads for v1.7.1](#downloads-for-v171) +- [v1.7.5](#v175) + - [Downloads for v1.7.5](#downloads-for-v175) - [Client Binaries](#client-binaries-7) - [Server Binaries](#server-binaries-7) - [Node Binaries](#node-binaries-7) - - [Changelog since v1.7.0](#changelog-since-v170) + - [Changelog since v1.7.4](#changelog-since-v174) - [Other notable changes](#other-notable-changes-7) -- [v1.7.0](#v170) - - [Downloads for v1.7.0](#downloads-for-v170) +- [v1.7.4](#v174) + - [Downloads for v1.7.4](#downloads-for-v174) - [Client Binaries](#client-binaries-8) - [Server Binaries](#server-binaries-8) - [Node Binaries](#node-binaries-8) + - [Changelog since v1.7.3](#changelog-since-v173) + - [Other notable changes](#other-notable-changes-8) +- [v1.7.3](#v173) + - [Downloads for v1.7.3](#downloads-for-v173) + - [Client Binaries](#client-binaries-9) + - [Server Binaries](#server-binaries-9) + - [Node Binaries](#node-binaries-9) + - [Changelog since v1.7.2](#changelog-since-v172) + - [Other notable changes](#other-notable-changes-9) +- [v1.7.2](#v172) + - [Downloads for v1.7.2](#downloads-for-v172) + - [Client Binaries](#client-binaries-10) + - [Server Binaries](#server-binaries-10) + - [Node Binaries](#node-binaries-10) + - [Changelog since v1.7.1](#changelog-since-v171) + - [Other notable changes](#other-notable-changes-10) +- [v1.7.1](#v171) + - [Downloads for v1.7.1](#downloads-for-v171) + - [Client Binaries](#client-binaries-11) + - [Server Binaries](#server-binaries-11) + - [Node Binaries](#node-binaries-11) + - [Changelog since v1.7.0](#changelog-since-v170) + - [Other notable changes](#other-notable-changes-11) +- [v1.7.0](#v170) + - [Downloads for v1.7.0](#downloads-for-v170) + - [Client Binaries](#client-binaries-12) + - [Server Binaries](#server-binaries-12) + - [Node Binaries](#node-binaries-12) - [**Major Themes**](#major-themes) - [**Action Required Before Upgrading**](#action-required-before-upgrading) - [Network](#network) @@ -115,7 +143,7 @@ - [Local Storage](#local-storage) - [Volume Plugins](#volume-plugins) - [Metrics](#metrics) - - [**Other notable changes**](#other-notable-changes-8) + - [**Other notable changes**](#other-notable-changes-12) - [Admission plugin](#admission-plugin) - [API Machinery](#api-machinery-1) - [Application autoscaling](#application-autoscaling-1) @@ -143,62 +171,325 @@ - [Previous Releases Included in v1.7.0](#previous-releases-included-in-v170) - [v1.7.0-rc.1](#v170-rc1) - [Downloads for v1.7.0-rc.1](#downloads-for-v170-rc1) - - [Client Binaries](#client-binaries-9) - - [Server Binaries](#server-binaries-9) - - [Node Binaries](#node-binaries-9) - - [Changelog since v1.7.0-beta.2](#changelog-since-v170-beta2) - - [Action Required](#action-required) - - [Other notable changes](#other-notable-changes-9) -- [v1.7.0-beta.2](#v170-beta2) - - [Downloads for v1.7.0-beta.2](#downloads-for-v170-beta2) - - [Client Binaries](#client-binaries-10) - - [Server Binaries](#server-binaries-10) - - [Node Binaries](#node-binaries-10) - - [Changelog since v1.7.0-beta.1](#changelog-since-v170-beta1) - - [Action Required](#action-required-1) - - [Other notable changes](#other-notable-changes-10) -- [v1.7.0-beta.1](#v170-beta1) - - [Downloads for v1.7.0-beta.1](#downloads-for-v170-beta1) - - [Client Binaries](#client-binaries-11) - - [Server Binaries](#server-binaries-11) - - [Node Binaries](#node-binaries-11) - - [Changelog since v1.7.0-alpha.4](#changelog-since-v170-alpha4) - - [Action Required](#action-required-2) - - [Other notable changes](#other-notable-changes-11) -- [v1.7.0-alpha.4](#v170-alpha4) - - [Downloads for v1.7.0-alpha.4](#downloads-for-v170-alpha4) - - [Client Binaries](#client-binaries-12) - - [Server Binaries](#server-binaries-12) - - [Node Binaries](#node-binaries-12) - - [Changelog since v1.7.0-alpha.3](#changelog-since-v170-alpha3) - - [Action Required](#action-required-3) - - [Other notable changes](#other-notable-changes-12) -- [v1.7.0-alpha.3](#v170-alpha3) - - [Downloads for v1.7.0-alpha.3](#downloads-for-v170-alpha3) - [Client Binaries](#client-binaries-13) - [Server Binaries](#server-binaries-13) - [Node Binaries](#node-binaries-13) - - [Changelog since v1.7.0-alpha.2](#changelog-since-v170-alpha2) - - [Action Required](#action-required-4) + - [Changelog since v1.7.0-beta.2](#changelog-since-v170-beta2) + - [Action Required](#action-required) - [Other notable changes](#other-notable-changes-13) -- [v1.7.0-alpha.2](#v170-alpha2) - - [Downloads for v1.7.0-alpha.2](#downloads-for-v170-alpha2) +- [v1.7.0-beta.2](#v170-beta2) + - [Downloads for v1.7.0-beta.2](#downloads-for-v170-beta2) - [Client Binaries](#client-binaries-14) - [Server Binaries](#server-binaries-14) - - [Changelog since v1.7.0-alpha.1](#changelog-since-v170-alpha1) - - [Action Required](#action-required-5) + - [Node Binaries](#node-binaries-14) + - [Changelog since v1.7.0-beta.1](#changelog-since-v170-beta1) + - [Action Required](#action-required-1) - [Other notable changes](#other-notable-changes-14) -- [v1.7.0-alpha.1](#v170-alpha1) - - [Downloads for v1.7.0-alpha.1](#downloads-for-v170-alpha1) +- [v1.7.0-beta.1](#v170-beta1) + - [Downloads for v1.7.0-beta.1](#downloads-for-v170-beta1) - [Client Binaries](#client-binaries-15) - [Server Binaries](#server-binaries-15) - - [Changelog since v1.6.0](#changelog-since-v160) + - [Node Binaries](#node-binaries-15) + - [Changelog since v1.7.0-alpha.4](#changelog-since-v170-alpha4) + - [Action Required](#action-required-2) - [Other notable changes](#other-notable-changes-15) +- [v1.7.0-alpha.4](#v170-alpha4) + - [Downloads for v1.7.0-alpha.4](#downloads-for-v170-alpha4) + - [Client Binaries](#client-binaries-16) + - [Server Binaries](#server-binaries-16) + - [Node Binaries](#node-binaries-16) + - [Changelog since v1.7.0-alpha.3](#changelog-since-v170-alpha3) + - [Action Required](#action-required-3) + - [Other notable changes](#other-notable-changes-16) +- [v1.7.0-alpha.3](#v170-alpha3) + - [Downloads for v1.7.0-alpha.3](#downloads-for-v170-alpha3) + - [Client Binaries](#client-binaries-17) + - [Server Binaries](#server-binaries-17) + - [Node Binaries](#node-binaries-17) + - [Changelog since v1.7.0-alpha.2](#changelog-since-v170-alpha2) + - [Action Required](#action-required-4) + - [Other notable changes](#other-notable-changes-17) +- [v1.7.0-alpha.2](#v170-alpha2) + - [Downloads for v1.7.0-alpha.2](#downloads-for-v170-alpha2) + - [Client Binaries](#client-binaries-18) + - [Server Binaries](#server-binaries-18) + - [Changelog since v1.7.0-alpha.1](#changelog-since-v170-alpha1) + - [Action Required](#action-required-5) + - [Other notable changes](#other-notable-changes-18) +- [v1.7.0-alpha.1](#v170-alpha1) + - [Downloads for v1.7.0-alpha.1](#downloads-for-v170-alpha1) + - [Client Binaries](#client-binaries-19) + - [Server Binaries](#server-binaries-19) + - [Changelog since v1.6.0](#changelog-since-v160) + - [Other notable changes](#other-notable-changes-19) +# v1.7.12 + +[Documentation](https://docs.k8s.io) & [Examples](https://releases.k8s.io/release-1.7/examples) + +## Downloads for v1.7.12 + + +filename | sha256 hash +-------- | ----------- +[kubernetes.tar.gz](https://dl.k8s.io/v1.7.12/kubernetes.tar.gz) | `749f811fb77daca197ecce2eacfea13f28e9fa69748d1b9fa7521850a5e77b93` +[kubernetes-src.tar.gz](https://dl.k8s.io/v1.7.12/kubernetes-src.tar.gz) | `86804d5a20a929429f1a8ed4aecba78d391a0dbaee7ffca914724b37e56eeebe` + +### Client Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-client-darwin-386.tar.gz](https://dl.k8s.io/v1.7.12/kubernetes-client-darwin-386.tar.gz) | `7fa3e25fa63a31955de12f1cfa67bb94bcc09ccd3e90e5c5ad090b2ea9d90f94` +[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.7.12/kubernetes-client-darwin-amd64.tar.gz) | `107fa0f038b3530f57a6b04512262cbde04c888b771a1b931c6ff0a98adc1bc9` +[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.7.12/kubernetes-client-linux-386.tar.gz) | `22827bee712441a57dfa2c6d87182128c82a0f0ded34970910d1aebdb968d4db` +[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.7.12/kubernetes-client-linux-amd64.tar.gz) | `01e87c03e4c928a105ac64618a8923d9d5afa321f9ce2c4d739dad5aa564da72` +[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.7.12/kubernetes-client-linux-arm64.tar.gz) | `5d44328b0f2070885102fd15e9bb142d53b8b0c431cc5bfc5018fe07642c0380` +[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.7.12/kubernetes-client-linux-arm.tar.gz) | `30986808b540706a88855e87bd997103b506635dcc62b02e34e6d6ac507301ef` +[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.7.12/kubernetes-client-linux-ppc64le.tar.gz) | `d577a244e0f09f47d926fbcbd097e149a53488406952089225545f591f2c1945` +[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.7.12/kubernetes-client-linux-s390x.tar.gz) | `2f5eab8cb47eb467727649ef2683abe72232f9b6f481384244c535507d15a3d7` +[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.7.12/kubernetes-client-windows-386.tar.gz) | `e0c060c5fa1fa61ff6477485fb40329d57e6dd20cc6a1bbc50a5f98f54f61d1a` +[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.7.12/kubernetes-client-windows-amd64.tar.gz) | `bc824cf320dc94a96998665fad5925fb1b6c66569aa9bb34b12e7dfa7d437c73` + +### Server Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.7.12/kubernetes-server-linux-amd64.tar.gz) | `2bf0fee82996eaae55547852c5082ecbc2389356b4c929294ed3bc198f80ec33` +[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.7.12/kubernetes-server-linux-arm64.tar.gz) | `b7b193a53650bac279fed535fa6e5a0cb4cff6376731ef4ca3a383af97b94486` +[kubernetes-server-linux-arm.tar.gz](https://dl.k8s.io/v1.7.12/kubernetes-server-linux-arm.tar.gz) | `ecee8f65c62f4a79c423b585bf0f78e3c64ed4bb1afc7a87f0ac6dfcfb262908` +[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.7.12/kubernetes-server-linux-ppc64le.tar.gz) | `eb9058d726fd48eb6797e99ba2d9353ab2bae4dec21836deaafb2ded0b412acc` +[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.7.12/kubernetes-server-linux-s390x.tar.gz) | `b6eb522fb1aac7ea82ae2d04b456e4e69740ce40dd48eb205c5d071f4aa49d76` + +### Node Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.7.12/kubernetes-node-linux-amd64.tar.gz) | `1ab49460eb34ebab60a9109479e2f43194c763ae24a1922889e301d8c1b0644e` +[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.7.12/kubernetes-node-linux-arm64.tar.gz) | `16bf9e50d74d8b66e791ee9d23498e7b4a6e49f499df02f84baaf277128da9c2` +[kubernetes-node-linux-arm.tar.gz](https://dl.k8s.io/v1.7.12/kubernetes-node-linux-arm.tar.gz) | `c64fe4901f94076f6df2d464e13799f6399f68bc439ad966357ea3790e73a22e` +[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.7.12/kubernetes-node-linux-ppc64le.tar.gz) | `4c641014245741fd0835e430c6cc61bae0c1f30526ec07313343d59eee462a01` +[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.7.12/kubernetes-node-linux-s390x.tar.gz) | `9262f3821d02ac6a6d3d5fe51fc56cb264e2bf1adaa4b63b8b87612f1e01411d` +[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.7.12/kubernetes-node-windows-amd64.tar.gz) | `266b57c417190621ee9583fa556336dfe447ce8847f8be64d383fa48a81b22e2` + +## Changelog since v1.7.11 + +### Other notable changes + +* fix azure disk storage account init issue ([#55927](https://github.com/kubernetes/kubernetes/pull/55927), [@andyzhangx](https://github.com/andyzhangx)) +* Fixes a bug where if an error was returned that was not an `autorest.DetailedError` we would return `"not found", nil` which caused nodes to go to `NotReady` state. ([#57484](https://github.com/kubernetes/kubernetes/pull/57484), [@brendandburns](https://github.com/brendandburns)) +* Retry 'connection refused' errors when setting up clusters on GCE. ([#57394](https://github.com/kubernetes/kubernetes/pull/57394), [@mborsz](https://github.com/mborsz)) +* Retry 'connection refused' errors when setting up clusters on GCE. ([#57394](https://github.com/kubernetes/kubernetes/pull/57394), [@mborsz](https://github.com/mborsz)) +* Retry 'connection refused' errors when setting up clusters on GCE. ([#57394](https://github.com/kubernetes/kubernetes/pull/57394), [@mborsz](https://github.com/mborsz)) +* Fix a problem of not respecting TerminationGracePeriodSeconds of the Pods created by DaemonSet controller. ([#51279](https://github.com/kubernetes/kubernetes/pull/51279), [@kow3ns](https://github.com/kow3ns)) +* BUG FIX: Check both name and ports for azure health probes ([#56918](https://github.com/kubernetes/kubernetes/pull/56918), [@feiskyer](https://github.com/feiskyer)) +* Provides compatibility of fields SizeLimit in types.EmptyDirVolumeSource since v1.7.8 ([#56505](https://github.com/kubernetes/kubernetes/pull/56505), [@yue9944882](https://github.com/yue9944882)) +* Fixes issue where masquerade rules are flushed in GCE k8s clusters. ([#56728](https://github.com/kubernetes/kubernetes/pull/56728), [@dnardo](https://github.com/dnardo)) +* kubelet: fix bug where `runAsUser: MustRunAsNonRoot` strategy didn't reject a pod with a non-numeric `USER`. ([#56711](https://github.com/kubernetes/kubernetes/pull/56711), [@php-coder](https://github.com/php-coder)) +* Fix a bug in GCE multizonal clusters where PersistentVolumes were sometimes created in zones without nodes. ([#52322](https://github.com/kubernetes/kubernetes/pull/52322), [@davidz627](https://github.com/davidz627)) +* Fix validation of NetworkPolicy ([#56223](https://github.com/kubernetes/kubernetes/pull/56223), [@deads2k](https://github.com/deads2k)) +* add GRS, RAGRS storage account type support for azure disk ([#55931](https://github.com/kubernetes/kubernetes/pull/55931), [@andyzhangx](https://github.com/andyzhangx)) +* Fixes server name verification of aggregated API servers and webhook admission endpoints ([#56415](https://github.com/kubernetes/kubernetes/pull/56415), [@liggitt](https://github.com/liggitt)) +* Fix a typo in prometheus-to-sd configuration, that drops some stackdriver metrics. ([#56473](https://github.com/kubernetes/kubernetes/pull/56473), [@loburm](https://github.com/loburm)) +* Update jquery and bootstrap dependencies ([#56447](https://github.com/kubernetes/kubernetes/pull/56447), [@dashpole](https://github.com/dashpole)) + + + +# v1.7.11 + +[Documentation](https://docs.k8s.io) & [Examples](https://releases.k8s.io/release-1.7/examples) + +## Downloads for v1.7.11 + + +filename | sha256 hash +-------- | ----------- +[kubernetes.tar.gz](https://dl.k8s.io/v1.7.11/kubernetes.tar.gz) | `0b4c9247784851a6681adf7ed068be75f346179035cdab840d11d4e6dc274aa1` +[kubernetes-src.tar.gz](https://dl.k8s.io/v1.7.11/kubernetes-src.tar.gz) | `380a7ca5b57dba2c45b64f48c48d1035f191b15687c724d1173a8367097c3451` + +### Client Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-client-darwin-386.tar.gz](https://dl.k8s.io/v1.7.11/kubernetes-client-darwin-386.tar.gz) | `6bd9ecc484da25e1d09b8de7fe2ec411e989e56c9456d20bb01ad10823b54dfa` +[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.7.11/kubernetes-client-darwin-amd64.tar.gz) | `bf723c41ae7599a5ba2dbf8fe62aa19dadb91d243ff248d56a41fa7de9db8699` +[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.7.11/kubernetes-client-linux-386.tar.gz) | `9ce79fe18a725e1d723c9bb4cefa95d90e8e05276bcc66fb9b809dc7767a939c` +[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.7.11/kubernetes-client-linux-amd64.tar.gz) | `c3c2af3ad16257853e8a2594779168002d20c7259b9ad6beb6f5f7a57585220e` +[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.7.11/kubernetes-client-linux-arm64.tar.gz) | `a525d204a4aa45481cd858cadee8d06bc969c81a902f51a6d31a1ab1ed8a9278` +[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.7.11/kubernetes-client-linux-arm.tar.gz) | `d910e54cdc5e9240a3f1c8f7cf32f28b442d740e8cc7db29742f40bb33e331b8` +[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.7.11/kubernetes-client-linux-ppc64le.tar.gz) | `eeeee6f6a763348226cc8ef112e83526b09834365fce02a6595b085f25289e9e` +[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.7.11/kubernetes-client-linux-s390x.tar.gz) | `a6d4581330dfd6f08603674e74016631687d86b9dcca2a8c9d4dacb668d4dc30` +[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.7.11/kubernetes-client-windows-386.tar.gz) | `6ccf7e4b0321d0dc7befd55d278966de921ea4303043cec6abf4ce13348184db` +[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.7.11/kubernetes-client-windows-amd64.tar.gz) | `233afcd0b0d4bfdc0190b00570aed7f1ed112da511c637fbd64565670f873829` + +### Server Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.7.11/kubernetes-server-linux-amd64.tar.gz) | `80a1ad525e92e5d8f9e835c88cfa3e3f5244c287de0cb4cbf98a2496e78fb83d` +[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.7.11/kubernetes-server-linux-arm64.tar.gz) | `3f884b85b60b10209b8b7a5f2f735dfdfeb0afa9380170a1de82a09f7e347603` +[kubernetes-server-linux-arm.tar.gz](https://dl.k8s.io/v1.7.11/kubernetes-server-linux-arm.tar.gz) | `3ae170d0ce2b781e7ed41941d568c457c7888b0b15a44b7853713e63f5ff9cc1` +[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.7.11/kubernetes-server-linux-ppc64le.tar.gz) | `f19ba4496dbbcb1fae00ce40164ae8de8b35aa871a4f6a7c24e20a9dd6ef7a1f` +[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.7.11/kubernetes-server-linux-s390x.tar.gz) | `3da441a0b7acd2f59fdb3232080d49df29c684aa2260b7010ec37a0730d3e82b` + +### Node Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.7.11/kubernetes-node-linux-amd64.tar.gz) | `8ab11a1b7c0ed298d89fe6f1ed9196046f8ac8d5eaecf3803890cefd92deb656` +[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.7.11/kubernetes-node-linux-arm64.tar.gz) | `435c81717e1bf4968db1ec916fe24bd5c4cfedaa339ae7890374f06ce49fa7e6` +[kubernetes-node-linux-arm.tar.gz](https://dl.k8s.io/v1.7.11/kubernetes-node-linux-arm.tar.gz) | `3a978350045c04bbeeb936cac2aabe6a92040d66ed14f0f30fd44ed03cf9fe0f` +[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.7.11/kubernetes-node-linux-ppc64le.tar.gz) | `82fc341fc4ee9213020894bcf1bd6d34c226f03554507915bdfd379fffd1b608` +[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.7.11/kubernetes-node-linux-s390x.tar.gz) | `e91e97533fab0b759ace3ad0fb7a3ff128cdc38221d55c8a9893bfe056a0ea8f` +[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.7.11/kubernetes-node-windows-amd64.tar.gz) | `0ebb1aafaa8af8581580d9425705a3535555d65f960eba2c16dfee90080d53e1` + +## Changelog since v1.7.10 + +### Other notable changes + +* Bugfix: master startup script on GCP no longer fails randomly due to concurrent iptables invocations. ([#55945](https://github.com/kubernetes/kubernetes/pull/55945), [@x13n](https://github.com/x13n)) +* Fix bug in mounting volumes with GlusterFS plugin ([#53292](https://github.com/kubernetes/kubernetes/pull/53292), [@humblec](https://github.com/humblec)) +* Add /bin/tee symlink to bazel build for busybox, so that CI builds have /bin/tee ([#55417](https://github.com/kubernetes/kubernetes/pull/55417), [@justinsb](https://github.com/justinsb)) +* Reduce log noise produced by prometheus-to-sd, by bumping it to version 0.2.2. ([#54635](https://github.com/kubernetes/kubernetes/pull/54635), [@loburm](https://github.com/loburm)) +* Fix session affinity issue with external load balancer traffic when ExternalTrafficPolicy=Local. ([#55519](https://github.com/kubernetes/kubernetes/pull/55519), [@MrHohn](https://github.com/MrHohn)) +* Add masquerading rules by default to GCE/GKE ([#55178](https://github.com/kubernetes/kubernetes/pull/55178), [@dnardo](https://github.com/dnardo)) +* Azure cloudprovider: Fix controller manager crash issue on a manually created k8s cluster. ([#53694](https://github.com/kubernetes/kubernetes/pull/53694), [@andyzhangx](https://github.com/andyzhangx)) +* Fix a bug where soft eviction would not trigger when the threshold was crossed ([#52046](https://github.com/kubernetes/kubernetes/pull/52046), [@dashpole](https://github.com/dashpole)) +* Addon manager supports HA masters. ([#55782](https://github.com/kubernetes/kubernetes/pull/55782), [@x13n](https://github.com/x13n)) +* Fixed 'Schedulercache is corrupted' error in kube-scheduler ([#55262](https://github.com/kubernetes/kubernetes/pull/55262), [@liggitt](https://github.com/liggitt)) +* Fix hyperkube kubelet --experimental-dockershim ([#55335](https://github.com/kubernetes/kubernetes/pull/55335), [@ivan4th](https://github.com/ivan4th)) +* fix azure pv crash due to volumeSource.ReadOnly value nil ([#54607](https://github.com/kubernetes/kubernetes/pull/54607), [@andyzhangx](https://github.com/andyzhangx)) +* GCE: provide an option to disable docker's live-restore on COS/ubuntu ([#55260](https://github.com/kubernetes/kubernetes/pull/55260), [@yujuhong](https://github.com/yujuhong)) +* Fix overlay2 container disk metrics for Docker ([#54958](https://github.com/kubernetes/kubernetes/pull/54958), [@dashpole](https://github.com/dashpole)) + + + +# v1.7.10 + +[Documentation](https://docs.k8s.io) & [Examples](https://releases.k8s.io/release-1.7/examples) + +## Downloads for v1.7.10 + + +filename | sha256 hash +-------- | ----------- +[kubernetes.tar.gz](https://dl.k8s.io/v1.7.10/kubernetes.tar.gz) | `a4a4e63576f25cfc3b1f5be2a74c992f42caca75adace41db1edf4d692c478c5` +[kubernetes-src.tar.gz](https://dl.k8s.io/v1.7.10/kubernetes-src.tar.gz) | `0592c01139e1d056fede2eaca1c957d2dfd8c907939e2e20846a819ede07c7ad` + +### Client Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-client-darwin-386.tar.gz](https://dl.k8s.io/v1.7.10/kubernetes-client-darwin-386.tar.gz) | `2b55ee1675ead0825a866653c609354eaedb3ef9254bc1625c1b9fada35070b5` +[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.7.10/kubernetes-client-darwin-amd64.tar.gz) | `b63a89d0ac4452c5f05a3836962809a80fb1a8a97a2d42d67fcbb587d608acca` +[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.7.10/kubernetes-client-linux-386.tar.gz) | `3d3d5721469adcf6eac9b581c2bed2c7d16b9994ae6f367a24f9b89380bfa864` +[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.7.10/kubernetes-client-linux-amd64.tar.gz) | `5c9bbfe045ecc4725a258d6b854ef799f841f62e02fbee5e8fdc6918c86f282e` +[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.7.10/kubernetes-client-linux-arm64.tar.gz) | `c835369962b05aa22b9304a49050986242a23a56a1b0aa5162fc77dd5202ad78` +[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.7.10/kubernetes-client-linux-arm.tar.gz) | `3c67b7088803bd2de4ecc933c70e29e1cf67aff98bc5bd6b6a8bc06df94248a1` +[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.7.10/kubernetes-client-linux-ppc64le.tar.gz) | `eecb107511eadd50f2757cac63ec09dd103ffcd5e03b6d6d9b8a91e45a8d6710` +[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.7.10/kubernetes-client-linux-s390x.tar.gz) | `711941d5eb230e483c1fba7337d4175fc9e390469f0870dc2cc9e7bafc39058e` +[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.7.10/kubernetes-client-windows-386.tar.gz) | `3ba41679dd6d7c3925e24036b583b061ae706dab65b540818aa533fc3a658aeb` +[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.7.10/kubernetes-client-windows-amd64.tar.gz) | `c1e10d051de9ad569d32f66b6df238c111fac153299a8bb3b76cf7bf6787ed68` + +### Server Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.7.10/kubernetes-server-linux-amd64.tar.gz) | `636cfbe99af340e7d3c067253698c1e531f22a37035f41c535e27f4cde9b74bf` +[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.7.10/kubernetes-server-linux-arm64.tar.gz) | `16a8a2b61b0e10da3950feb1226849f3c73990414f350121ccecccf625b943ff` +[kubernetes-server-linux-arm.tar.gz](https://dl.k8s.io/v1.7.10/kubernetes-server-linux-arm.tar.gz) | `5719656a79f1f28c9b100e2dc863144e8d1e9b8bcc11e2f42387544ca4d8c02f` +[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.7.10/kubernetes-server-linux-ppc64le.tar.gz) | `a5997050ce825e48aae5604b348f68d9ef2fec42db26ee1e8eebc48a795fcbfd` +[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.7.10/kubernetes-server-linux-s390x.tar.gz) | `6834c4a3c81484c835221bc17bb0df76c153bdabefc7b6fd6b26969df5f34d6f` + +### Node Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.7.10/kubernetes-node-linux-amd64.tar.gz) | `2946cd7f7b2f6be9bd5c416fe8048ea2371f2aad60c974d154a8a9078ccf6e2b` +[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.7.10/kubernetes-node-linux-arm64.tar.gz) | `fad120efc9474d18ea8e45addb2291c9a3a1649da05722889bc087fe1e0a8e06` +[kubernetes-node-linux-arm.tar.gz](https://dl.k8s.io/v1.7.10/kubernetes-node-linux-arm.tar.gz) | `a8c339b7308738c6b7dd021c294eeffee28c1fc7c3e4619779bc8f9f61af09ad` +[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.7.10/kubernetes-node-linux-ppc64le.tar.gz) | `360fc062e935313020b98cc8e04ce3cf26401c046ab96783d404da598e54baa8` +[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.7.10/kubernetes-node-linux-s390x.tar.gz) | `82808a27b89638ea77816b05629a2dbcb3a22da7b140f834c813980260f6cc7c` +[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.7.10/kubernetes-node-windows-amd64.tar.gz) | `420d525a25d28059aa4e938d8b8043242d7ebdf1e4ad9ae27e7260064fec4b51` + +## Changelog since v1.7.9 + +### Other notable changes + +* Fix for service controller so that it won't retry on doNotRetry service update failure. ([#54184](https://github.com/kubernetes/kubernetes/pull/54184), [@MrHohn](https://github.com/MrHohn)) +* [fluentd-gcp addon] Fluentd now runs in its own network, not in the host one. ([#54395](https://github.com/kubernetes/kubernetes/pull/54395), [@crassirostris](https://github.com/crassirostris)) +* fix azure disk mount failure on coreos and some other distros ([#54334](https://github.com/kubernetes/kubernetes/pull/54334), [@andyzhangx](https://github.com/andyzhangx)) +* Update busybox image link to gcr.io for kube-proxy. ([#53818](https://github.com/kubernetes/kubernetes/pull/53818), [@MrHohn](https://github.com/MrHohn)) +* Restores the ability to apply network policy objects against the networking.k8s.io/v1 API ([#54106](https://github.com/kubernetes/kubernetes/pull/54106), [@liggitt](https://github.com/liggitt)) +* Allow for configuring etcd hostname in the manifest ([#54403](https://github.com/kubernetes/kubernetes/pull/54403), [@wojtek-t](https://github.com/wojtek-t)) +* fix a bug where disk pressure could trigger prematurely when using overlay2 ([#53684](https://github.com/kubernetes/kubernetes/pull/53684), [@dashpole](https://github.com/dashpole)) + + + +# v1.7.9 + +[Documentation](https://docs.k8s.io) & [Examples](https://releases.k8s.io/release-1.7/examples) + +## Downloads for v1.7.9 + + +filename | sha256 hash +-------- | ----------- +[kubernetes.tar.gz](https://dl.k8s.io/v1.7.9/kubernetes.tar.gz) | `8c7c16c137c421cfe27311aba0fea49411ed725d3d41938706474c328647afcc` +[kubernetes-src.tar.gz](https://dl.k8s.io/v1.7.9/kubernetes-src.tar.gz) | `eb2d967731d20b2f42787400fd9114ebd40c2722f3afd7ebb232324d2e66815e` + +### Client Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-client-darwin-386.tar.gz](https://dl.k8s.io/v1.7.9/kubernetes-client-darwin-386.tar.gz) | `930e24595a8cf87f65d0cbee6f033f8c441a64da86cdc22ad9d31cd5e0496928` +[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.7.9/kubernetes-client-darwin-amd64.tar.gz) | `59c10f48351347821216d1cb9726db0b31868cd5e059814a5154dfdeb36548e1` +[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.7.9/kubernetes-client-linux-386.tar.gz) | `3a7e20a3d45eab69bd8a6c9572ecd98f50499b1880882c0e78c8cdd726046802` +[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.7.9/kubernetes-client-linux-amd64.tar.gz) | `ac530a89b701669df889c7d5e34c7c5ba0b1c231e45fd9a1ff441d807d0fba8f` +[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.7.9/kubernetes-client-linux-arm64.tar.gz) | `cdad0b14762b01aac8820e41cb89b850b1dc8d539ac892ca9f718d9e00e8505e` +[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.7.9/kubernetes-client-linux-arm.tar.gz) | `11c1bb76f2fc7fa9038d1d8687df857a231bd8a44b00d3f3bfef277b44e1c604` +[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.7.9/kubernetes-client-linux-ppc64le.tar.gz) | `e7ed462fb6d86b1205ca9c4701b521d80b9c614fb98ca3a75579d18835303f7f` +[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.7.9/kubernetes-client-linux-s390x.tar.gz) | `7aff3b2d0540c3efd53d383dc87d95b62b4203933bd154f66e167fffa5dd0d72` +[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.7.9/kubernetes-client-windows-386.tar.gz) | `45f64fae0368f80bff7f11fafcce4ccc5c79876cb496481fbcdb35fd5aa85a49` +[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.7.9/kubernetes-client-windows-amd64.tar.gz) | `f7c34d11b35424fe96e1477a9347618169b911d4ecc57f00945f63d5cef53968` + +### Server Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.7.9/kubernetes-server-linux-amd64.tar.gz) | `9b94e2b1c13dd3304aa36d0800f88a86d1c335a2b56de8a69d67f50c6f08d0ad` +[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.7.9/kubernetes-server-linux-arm64.tar.gz) | `2c5cb85515137f58ddc475963cd42cd69a881b2269724e0c5237b365644974db` +[kubernetes-server-linux-arm.tar.gz](https://dl.k8s.io/v1.7.9/kubernetes-server-linux-arm.tar.gz) | `e62d8e234bc31d8dd4c88b28261445f4bc00e9e19795c57f7e72da91c037b6cd` +[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.7.9/kubernetes-server-linux-ppc64le.tar.gz) | `b59c47ff865c4f21da816500d1013e5bab71bcb2ed214ceb022395eb6d729634` +[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.7.9/kubernetes-server-linux-s390x.tar.gz) | `2c057b4dcfd40457fb5ee7d692239b702b61c17a9cc095ecd2a65ac553e4d2d7` + +### Node Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.7.9/kubernetes-node-linux-amd64.tar.gz) | `e92e3deb34ce06b11b667027ddd9753f8c3149996320bb9dd3555d758775e60d` +[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.7.9/kubernetes-node-linux-arm64.tar.gz) | `96bf63c7ba4a322ec21b22c3fa3f37c713aa846bdde311bc7a52df8abc7ef291` +[kubernetes-node-linux-arm.tar.gz](https://dl.k8s.io/v1.7.9/kubernetes-node-linux-arm.tar.gz) | `4274d183d002c57cf6fff21ba71cdb69121f520ae77c913013adb92f7efee2a6` +[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.7.9/kubernetes-node-linux-ppc64le.tar.gz) | `8c9a7ef4141dc59be1b613b461ca8e16612c5d36ca9cd1b9fbb92f35f02e63f1` +[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.7.9/kubernetes-node-linux-s390x.tar.gz) | `14314c3c958bf4b966bc6960495271412019973834e9ca427bcedb1bd51c787f` +[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.7.9/kubernetes-node-windows-amd64.tar.gz) | `a20e882b046f95ebb6c94f87aee99b27d43422ea5e09b48fa67aa4926b2edbfe` + +## Changelog since v1.7.8 + +### Other notable changes + +* Support German cloud for azure disk mount feature ([#50673](https://github.com/kubernetes/kubernetes/pull/50673), [@clement-buchart](https://github.com/clement-buchart)) +* BugFix: Exited containers are not Garbage Collected by the kubelet while the pod is running ([#53167](https://github.com/kubernetes/kubernetes/pull/53167), [@dashpole](https://github.com/dashpole)) +* Address a bug which allowed the horizontal pod autoscaler to allocate `desiredReplicas` > `maxReplicas` in certain instances. ([#53690](https://github.com/kubernetes/kubernetes/pull/53690), [@mattjmcnaughton](https://github.com/mattjmcnaughton)) +* Use separate client for leader election in scheduler to avoid starving leader election by regular scheduler operations. ([#53793](https://github.com/kubernetes/kubernetes/pull/53793), [@wojtek-t](https://github.com/wojtek-t)) +* fix azure disk mounter issue ([#52260](https://github.com/kubernetes/kubernetes/pull/52260), [@andyzhangx](https://github.com/andyzhangx)) +* GCE: Fix issue deleting internal load balancers when the firewall resource may not exist. ([#53450](https://github.com/kubernetes/kubernetes/pull/53450), [@nicksardo](https://github.com/nicksardo)) +* Fix compilation of k8s.io/apiextensions-apiserver ([#48036](https://github.com/kubernetes/kubernetes/pull/48036), [@hongchaodeng](https://github.com/hongchaodeng)) + + # v1.7.8 [Documentation](https://docs.k8s.io) & [Examples](https://releases.k8s.io/release-1.7/examples) @@ -521,8 +812,8 @@ filename | sha256 hash * Fix an issue where if a CSR is not approved initially by the SAR approver is not retried. ([#49788](https://github.com/kubernetes/kubernetes/pull/49788), [@mikedanese](https://github.com/mikedanese)) * Cluster Autoscaler - fixes issues with taints and updates kube-proxy cpu request. ([#50514](https://github.com/kubernetes/kubernetes/pull/50514), [@mwielgus](https://github.com/mwielgus)) * Bumped Heapster version to 1.4.1: ([#50642](https://github.com/kubernetes/kubernetes/pull/50642), [@piosz](https://github.com/piosz)) - * - handle gracefully problem when kubelet reports duplicated stats for the same container (see [#47853](https://github.com/kubernetes/kubernetes/pull/47853)) on Heapster side - * - fixed bugs and improved performance in Stackdriver Sink + * handle gracefully problem when kubelet reports duplicated stats for the same container (see [#47853](https://github.com/kubernetes/kubernetes/pull/47853)) on Heapster side + * fixed bugs and improved performance in Stackdriver Sink * fluentd-gcp addon: Fix a bug in the event-exporter, when repeated events were not sent to Stackdriver. ([#50511](https://github.com/kubernetes/kubernetes/pull/50511), [@crassirostris](https://github.com/crassirostris)) * Collect metrics from Heapster in Stackdriver mode. ([#50517](https://github.com/kubernetes/kubernetes/pull/50517), [@piosz](https://github.com/piosz)) * fixes a bug around using the Global config ElbSecurityGroup where Kuberentes would modify the passed in Security Group. ([#49805](https://github.com/kubernetes/kubernetes/pull/49805), [@nbutton23](https://github.com/nbutton23)) @@ -2337,20 +2628,20 @@ filename | sha256 hash * Ensure that autoscaling/v1 is the preferred version for API discovery when autoscaling/v2alpha1 is enabled. ([#45741](https://github.com/kubernetes/kubernetes/pull/45741), [@DirectXMan12](https://github.com/DirectXMan12)) * Promotes Source IP preservation for Virtual IPs to GA. ([#41162](https://github.com/kubernetes/kubernetes/pull/41162), [@MrHohn](https://github.com/MrHohn)) * Two api fields are defined correspondingly: - * - Service.Spec.ExternalTrafficPolicy <- 'service.beta.kubernetes.io/external-traffic' annotation. - * - Service.Spec.HealthCheckNodePort <- 'service.beta.kubernetes.io/healthcheck-nodeport' annotation. + * Service.Spec.ExternalTrafficPolicy <- 'service.beta.kubernetes.io/external-traffic' annotation. + * Service.Spec.HealthCheckNodePort <- 'service.beta.kubernetes.io/healthcheck-nodeport' annotation. * Fix pods failing to start if they specify a file as a volume subPath to mount. ([#45623](https://github.com/kubernetes/kubernetes/pull/45623), [@wongma7](https://github.com/wongma7)) * the resource quota controller was not adding quota to be resynced at proper interval ([#45685](https://github.com/kubernetes/kubernetes/pull/45685), [@derekwaynecarr](https://github.com/derekwaynecarr)) * Marks the Kubelet's --master-service-namespace flag deprecated ([#44250](https://github.com/kubernetes/kubernetes/pull/44250), [@mtaufen](https://github.com/mtaufen)) * fluentd will tolerate all NoExecute Taints when run in gcp configuration. ([#45715](https://github.com/kubernetes/kubernetes/pull/45715), [@gmarek](https://github.com/gmarek)) * Added Group/Version/Kind and Action extension to OpenAPI Operations ([#44787](https://github.com/kubernetes/kubernetes/pull/44787), [@mbohlool](https://github.com/mbohlool)) * Updates kube-dns to 1.14.2 ([#45684](https://github.com/kubernetes/kubernetes/pull/45684), [@bowei](https://github.com/bowei)) - * - Support kube-master-url flag without kubeconfig - * - Fix concurrent R/Ws in dns.go - * - Fix confusing logging when initialize server - * - Fix printf in cmd/kube-dns/app/server.go - * - Fix version on startup and --version flag - * - Support specifying port number for nameserver in stubDomains + * Support kube-master-url flag without kubeconfig + * Fix concurrent R/Ws in dns.go + * Fix confusing logging when initialize server + * Fix printf in cmd/kube-dns/app/server.go + * Fix version on startup and --version flag + * Support specifying port number for nameserver in stubDomains * detach the volume when pod is terminated ([#45286](https://github.com/kubernetes/kubernetes/pull/45286), [@gnufied](https://github.com/gnufied)) * Don't append :443 to registry domain in the kubernetes-worker layer registry action ([#45550](https://github.com/kubernetes/kubernetes/pull/45550), [@jacekn](https://github.com/jacekn)) * vSphere cloud provider: Fix volume detach on node failure. ([#45569](https://github.com/kubernetes/kubernetes/pull/45569), [@divyenpatel](https://github.com/divyenpatel)) @@ -2472,9 +2763,9 @@ filename | sha256 hash * `zone` is the target zone (Will be "" if not applicable) * Note: this fixes some issues with the previous implementation of * metrics for disks: - * - Time duration tracked was of the initial API call, not the entire + * Time duration tracked was of the initial API call, not the entire * operation. - * - Metrics label tuple would have resulted in many independent + * Metrics label tuple would have resulted in many independent * histograms stored, one for each disk. (Did not aggregate well). * Update kubernetes-e2e charm to use snaps ([#45044](https://github.com/kubernetes/kubernetes/pull/45044), [@Cynerva](https://github.com/Cynerva)) * Log the error (if any) in e2e metrics gathering step ([#45039](https://github.com/kubernetes/kubernetes/pull/45039), [@shyamjvs](https://github.com/shyamjvs)) diff --git a/CHANGELOG-1.8.md b/CHANGELOG-1.8.md index 38dc2f57513..56d268d1178 100644 --- a/CHANGELOG-1.8.md +++ b/CHANGELOG-1.8.md @@ -1,17 +1,52 @@ -- [v1.8.1](#v181) - - [Downloads for v1.8.1](#downloads-for-v181) +- [v1.8.6](#v186) + - [Downloads for v1.8.6](#downloads-for-v186) - [Client Binaries](#client-binaries) - [Server Binaries](#server-binaries) - [Node Binaries](#node-binaries) - - [Changelog since v1.8.0](#changelog-since-v180) - - [Action Required](#action-required) + - [Changelog since v1.8.5](#changelog-since-v185) - [Other notable changes](#other-notable-changes) -- [v1.8.0](#v180) - - [Downloads for v1.8.0](#downloads-for-v180) +- [v1.8.5](#v185) + - [Downloads for v1.8.5](#downloads-for-v185) - [Client Binaries](#client-binaries-1) - [Server Binaries](#server-binaries-1) - [Node Binaries](#node-binaries-1) + - [Changelog since v1.8.4](#changelog-since-v184) + - [Other notable changes](#other-notable-changes-1) +- [v1.8.4](#v184) + - [Downloads for v1.8.4](#downloads-for-v184) + - [Client Binaries](#client-binaries-2) + - [Server Binaries](#server-binaries-2) + - [Node Binaries](#node-binaries-2) + - [Changelog since v1.8.3](#changelog-since-v183) + - [Other notable changes](#other-notable-changes-2) +- [v1.8.3](#v183) + - [Downloads for v1.8.3](#downloads-for-v183) + - [Client Binaries](#client-binaries-3) + - [Server Binaries](#server-binaries-3) + - [Node Binaries](#node-binaries-3) + - [Changelog since v1.8.2](#changelog-since-v182) + - [Other notable changes](#other-notable-changes-3) +- [v1.8.2](#v182) + - [Downloads for v1.8.2](#downloads-for-v182) + - [Client Binaries](#client-binaries-4) + - [Server Binaries](#server-binaries-4) + - [Node Binaries](#node-binaries-4) + - [Changelog since v1.8.1](#changelog-since-v181) + - [Other notable changes](#other-notable-changes-4) +- [v1.8.1](#v181) + - [Downloads for v1.8.1](#downloads-for-v181) + - [Client Binaries](#client-binaries-5) + - [Server Binaries](#server-binaries-5) + - [Node Binaries](#node-binaries-5) + - [Changelog since v1.8.0](#changelog-since-v180) + - [Action Required](#action-required) + - [Other notable changes](#other-notable-changes-5) +- [v1.8.0](#v180) + - [Downloads for v1.8.0](#downloads-for-v180) + - [Client Binaries](#client-binaries-6) + - [Server Binaries](#server-binaries-6) + - [Node Binaries](#node-binaries-6) - [Introduction to v1.8.0](#introduction-to-v180) - [Major Themes](#major-themes) - [SIG API Machinery](#sig-api-machinery) @@ -45,8 +80,8 @@ - [Scheduling](#scheduling-1) - [Storage](#storage) - [Cluster Federation](#cluster-federation) - - [[alpha] Federated Jobs](#[alpha]-federated-jobs) - - [[alpha] Federated Horizontal Pod Autoscaling (HPA)](#[alpha]-federated-horizontal-pod-autoscaling-hpa) + - [[alpha] Federated Jobs](#alpha-federated-jobs) + - [[alpha] Federated Horizontal Pod Autoscaling (HPA)](#alpha-federated-horizontal-pod-autoscaling-hpa) - [Node Components](#node-components) - [Autoscaling and Metrics](#autoscaling-and-metrics) - [Cluster Autoscaler](#cluster-autoscaler) @@ -72,49 +107,407 @@ - [External Dependencies](#external-dependencies) - [v1.8.0-rc.1](#v180-rc1) - [Downloads for v1.8.0-rc.1](#downloads-for-v180-rc1) - - [Client Binaries](#client-binaries-2) - - [Server Binaries](#server-binaries-2) - - [Node Binaries](#node-binaries-2) + - [Client Binaries](#client-binaries-7) + - [Server Binaries](#server-binaries-7) + - [Node Binaries](#node-binaries-7) - [Changelog since v1.8.0-beta.1](#changelog-since-v180-beta1) - [Action Required](#action-required-1) - - [Other notable changes](#other-notable-changes-1) + - [Other notable changes](#other-notable-changes-6) - [v1.8.0-beta.1](#v180-beta1) - [Downloads for v1.8.0-beta.1](#downloads-for-v180-beta1) - - [Client Binaries](#client-binaries-3) - - [Server Binaries](#server-binaries-3) - - [Node Binaries](#node-binaries-3) + - [Client Binaries](#client-binaries-8) + - [Server Binaries](#server-binaries-8) + - [Node Binaries](#node-binaries-8) - [Changelog since v1.8.0-alpha.3](#changelog-since-v180-alpha3) - [Action Required](#action-required-2) - - [Other notable changes](#other-notable-changes-2) + - [Other notable changes](#other-notable-changes-7) - [v1.8.0-alpha.3](#v180-alpha3) - [Downloads for v1.8.0-alpha.3](#downloads-for-v180-alpha3) - - [Client Binaries](#client-binaries-4) - - [Server Binaries](#server-binaries-4) - - [Node Binaries](#node-binaries-4) + - [Client Binaries](#client-binaries-9) + - [Server Binaries](#server-binaries-9) + - [Node Binaries](#node-binaries-9) - [Changelog since v1.8.0-alpha.2](#changelog-since-v180-alpha2) - [Action Required](#action-required-3) - - [Other notable changes](#other-notable-changes-3) + - [Other notable changes](#other-notable-changes-8) - [v1.8.0-alpha.2](#v180-alpha2) - [Downloads for v1.8.0-alpha.2](#downloads-for-v180-alpha2) - - [Client Binaries](#client-binaries-5) - - [Server Binaries](#server-binaries-5) - - [Node Binaries](#node-binaries-5) + - [Client Binaries](#client-binaries-10) + - [Server Binaries](#server-binaries-10) + - [Node Binaries](#node-binaries-10) - [Changelog since v1.7.0](#changelog-since-v170) - [Action Required](#action-required-4) - - [Other notable changes](#other-notable-changes-4) + - [Other notable changes](#other-notable-changes-9) - [v1.8.0-alpha.1](#v180-alpha1) - [Downloads for v1.8.0-alpha.1](#downloads-for-v180-alpha1) - - [Client Binaries](#client-binaries-6) - - [Server Binaries](#server-binaries-6) - - [Node Binaries](#node-binaries-6) + - [Client Binaries](#client-binaries-11) + - [Server Binaries](#server-binaries-11) + - [Node Binaries](#node-binaries-11) - [Changelog since v1.7.0-alpha.4](#changelog-since-v170-alpha4) - [Action Required](#action-required-5) - - [Other notable changes](#other-notable-changes-5) + - [Other notable changes](#other-notable-changes-10) +# v1.8.6 + +[Documentation](https://docs.k8s.io) & [Examples](https://releases.k8s.io/release-1.8/examples) + +## Downloads for v1.8.6 + + +filename | sha256 hash +-------- | ----------- +[kubernetes.tar.gz](https://dl.k8s.io/v1.8.6/kubernetes.tar.gz) | `8289c42b5d6da1dbf910585fca3a9d909195e540cc81bace61ec1d06b2366c1b` +[kubernetes-src.tar.gz](https://dl.k8s.io/v1.8.6/kubernetes-src.tar.gz) | `8a9d5d890c44137527fe3976d71d4f7cb18db21ba34262ce587cd979a88bb2fe` + +### Client Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-client-darwin-386.tar.gz](https://dl.k8s.io/v1.8.6/kubernetes-client-darwin-386.tar.gz) | `0e282477bfed6b534f2fbbd125e6e3e065bf72d15ac3532acef405e6717d8fb7` +[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.8.6/kubernetes-client-darwin-amd64.tar.gz) | `767c7bfbc6c1d01120e11726b9e33e184d32294e07c69a299b229329c5b98eba` +[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.8.6/kubernetes-client-linux-386.tar.gz) | `088b40c343fecb83b514bf9af0ad1c359c98ae7aa3b62d2a078c1363f50901c9` +[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.8.6/kubernetes-client-linux-amd64.tar.gz) | `47541706e4d27da55d32372344d7a4038ed389ba0be1e6fe15c651c574aac97a` +[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.8.6/kubernetes-client-linux-arm64.tar.gz) | `4be0b7a01c28c1f85d4f01f86def03dd3d49ef88cb43bf7be641d9d16b6aabc2` +[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.8.6/kubernetes-client-linux-arm.tar.gz) | `2d70384262cbdfb0958542bc5a71d926c49557fc8cc3000a2592571a945ad119` +[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.8.6/kubernetes-client-linux-ppc64le.tar.gz) | `c3be3a125ac77aa809da3495ad38456059a89cccfdfad0babaf95896fb958adc` +[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.8.6/kubernetes-client-linux-s390x.tar.gz) | `2b9831c2dd65c9669b335e3623e6a7001173b9ddf203f52f37b350659d9f1102` +[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.8.6/kubernetes-client-windows-386.tar.gz) | `9d14a96372cdcecbbb28717aff305fcd68beb540066a27f1b5e84e208a25405f` +[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.8.6/kubernetes-client-windows-amd64.tar.gz) | `0fbe358ff305188fe00793284e22c9c5b2ec0e0213882f0bfe0e4bf9685075f0` + +### Server Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.8.6/kubernetes-server-linux-amd64.tar.gz) | `9c8ff48343e5314638965407358d1e91d510c72a1c7dd7cde0c3be12790fdb98` +[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.8.6/kubernetes-server-linux-arm64.tar.gz) | `dd35c1b7572ab383eb2ff60f3b039053afa124836db6d044ab14afdafbe5ca74` +[kubernetes-server-linux-arm.tar.gz](https://dl.k8s.io/v1.8.6/kubernetes-server-linux-arm.tar.gz) | `5f4637d309eb47f4f97db8d2978b0b37b271339feb5952b216a9d09ad7e67c32` +[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.8.6/kubernetes-server-linux-ppc64le.tar.gz) | `6d3ea43edd53253e9e3b9ceb49e61b6d2c093e55be35f7b1a8f798cde842a562` +[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.8.6/kubernetes-server-linux-s390x.tar.gz) | `dfe89b91399977cee291d57b446625f01cf76ebecce696e2e889863bd3c8d3b1` + +### Node Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.8.6/kubernetes-node-linux-amd64.tar.gz) | `f8f3e7bb07db540f4b88fa5818c46efb918e795e5e89e389b9048f2f7f37674d` +[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.8.6/kubernetes-node-linux-arm64.tar.gz) | `1754b8a20d9176317fea3b77b5c48ad5565b922820adcbca4017bf210168dc6e` +[kubernetes-node-linux-arm.tar.gz](https://dl.k8s.io/v1.8.6/kubernetes-node-linux-arm.tar.gz) | `0a8255effff1d5b3ad7c84c3d6f6b8cfb5beb71606bfedaef0bb45f170b806d6` +[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.8.6/kubernetes-node-linux-ppc64le.tar.gz) | `fef465c9f66eda35479e152619b6c91e2432e92736646a898c5917098a10a1b4` +[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.8.6/kubernetes-node-linux-s390x.tar.gz) | `ff024e59d52afdee003f11c65f7de428915f7e28f9b8be4b3ebf117422ae5d67` +[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.8.6/kubernetes-node-windows-amd64.tar.gz) | `19a673b714c02322c544ec3a972e011410b69a7aed016ecf7ba09eccb175a1de` + +## Changelog since v1.8.5 + +### Other notable changes + +* change default azure file/dir mode to 0755 ([#56551](https://github.com/kubernetes/kubernetes/pull/56551), [@andyzhangx](https://github.com/andyzhangx)) +* Retry 'connection refused' errors when setting up clusters on GCE. ([#57394](https://github.com/kubernetes/kubernetes/pull/57394), [@mborsz](https://github.com/mborsz)) +* enable flexvolume on Windows node ([#56921](https://github.com/kubernetes/kubernetes/pull/56921), [@andyzhangx](https://github.com/andyzhangx)) +* Add prometheus metrics for the PodSecurityPolicy admission controller ([#57346](https://github.com/kubernetes/kubernetes/pull/57346), [@tallclair](https://github.com/tallclair)) +* fix CreateVolume func: use search mode instead ([#54687](https://github.com/kubernetes/kubernetes/pull/54687), [@andyzhangx](https://github.com/andyzhangx)) +* remove time waiting after create storage account (save 25s) ([#56679](https://github.com/kubernetes/kubernetes/pull/56679), [@andyzhangx](https://github.com/andyzhangx)) +* Add pvc as part of equivalence hash ([#56577](https://github.com/kubernetes/kubernetes/pull/56577), [@resouer](https://github.com/resouer)) +* fix azure disk storage account init issue ([#55927](https://github.com/kubernetes/kubernetes/pull/55927), [@andyzhangx](https://github.com/andyzhangx)) +* falls back to parse Docker runtime version as generic if not semver ([#54040](https://github.com/kubernetes/kubernetes/pull/54040), [@dixudx](https://github.com/dixudx)) +* BUG FIX: Check both name and ports for azure health probes ([#56918](https://github.com/kubernetes/kubernetes/pull/56918), [@feiskyer](https://github.com/feiskyer)) + + + +# v1.8.5 + +[Documentation](https://docs.k8s.io) & [Examples](https://releases.k8s.io/release-1.8/examples) + +## Downloads for v1.8.5 + + +filename | sha256 hash +-------- | ----------- +[kubernetes.tar.gz](https://dl.k8s.io/v1.8.5/kubernetes.tar.gz) | `7a7993e5dee72ede890e180112959a1fe179b592178ef24d04c48212c09345b8` +[kubernetes-src.tar.gz](https://dl.k8s.io/v1.8.5/kubernetes-src.tar.gz) | `358de791b2bfd85a9b76ee42629dd8d07ae46710ad2bd5a37a20136ec3c7cea8` + +### Client Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-client-darwin-386.tar.gz](https://dl.k8s.io/v1.8.5/kubernetes-client-darwin-386.tar.gz) | `89b57f6eccc02c95c4de4db189092756a9bf85033200a11db56ff30a38e2dda0` +[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.8.5/kubernetes-client-darwin-amd64.tar.gz) | `a02bbbfe403db81f7a6317e752d9fe7853b583e34077eebfa05c7f0ec4a89712` +[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.8.5/kubernetes-client-linux-386.tar.gz) | `a1c047cdfbcb753a8beabcf6358863c125d46e71c4d3cbe56f06237ce6f2fed6` +[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.8.5/kubernetes-client-linux-amd64.tar.gz) | `c32b6f90f1e8a15451f0d412d6d1f3db28948d2f7d76d4e28d83c11e1eb25f20` +[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.8.5/kubernetes-client-linux-arm64.tar.gz) | `a89a5f2889e0aae0caa673a2664c7af40e488a55ae26ab7a55599b0fbd87e281` +[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.8.5/kubernetes-client-linux-arm.tar.gz) | `5b485bbac15b8621be7ff936a5f02565511b9b00e56a5b67dfa1b273586d5af1` +[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.8.5/kubernetes-client-linux-ppc64le.tar.gz) | `ae4e8fcd230198bc3ad1294d61e04602a6bdd3c836997d48fd3262ab24e2885c` +[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.8.5/kubernetes-client-linux-s390x.tar.gz) | `c7803f0e3480dfdeedd8afd2d460ab6badf0e8879febafa30a4a3fbc87554507` +[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.8.5/kubernetes-client-windows-386.tar.gz) | `b78e04b0bc400f3f7a012cef630fd3757c12d54f16b180470d722c4d678867e1` +[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.8.5/kubernetes-client-windows-amd64.tar.gz) | `a0b32d3fcd5e692a452d2a38a6dd34a7f3e40e22e88e4cfba77ae224e07d8565` + +### Server Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.8.5/kubernetes-server-linux-amd64.tar.gz) | `523f747f68842000ca88c84e8db07243248f6064295701b2168c64d2b77adfcb` +[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.8.5/kubernetes-server-linux-arm64.tar.gz) | `3e43fccbe224ae7b20fd462f9c5932e5c5d58f0a3d6f67365a9e0d4e00fa796a` +[kubernetes-server-linux-arm.tar.gz](https://dl.k8s.io/v1.8.5/kubernetes-server-linux-arm.tar.gz) | `678c92b8b7b0616d102f9b74c9a11dd2763ba67bfa30075aca964aead2fe5370` +[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.8.5/kubernetes-server-linux-ppc64le.tar.gz) | `55993ca6301988412876b79216442968834847a571b6423235a0c7bffe65a56a` +[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.8.5/kubernetes-server-linux-s390x.tar.gz) | `32cb7484cdbeb4153fc672373055a4e8a05a61f83c722bef623f3c6922c01faa` + +### Node Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.8.5/kubernetes-node-linux-amd64.tar.gz) | `a3ae45d389001788401c07c5b3d14a9f0af842466080a3c31b6a03200b27231b` +[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.8.5/kubernetes-node-linux-arm64.tar.gz) | `642bd5c1c2728463667b1e0e6a110e2bf732972c16e8900701320a7fe85ead89` +[kubernetes-node-linux-arm.tar.gz](https://dl.k8s.io/v1.8.5/kubernetes-node-linux-arm.tar.gz) | `5b654c6fad642739f949be245eae94455fd9f2a25a388ca8effb01c49bd3451e` +[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.8.5/kubernetes-node-linux-ppc64le.tar.gz) | `3eeec484d7ea6caf1a3f8157d2fe504c411f27ee9930d744a017adefae191786` +[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.8.5/kubernetes-node-linux-s390x.tar.gz) | `5874957a48d103e9dd9c1bdbecced59d13bc3ac59d2dec44de989521f711c842` +[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.8.5/kubernetes-node-windows-amd64.tar.gz) | `46a57f13bc5a4b78cd58b9914257aff15163cee24f3e43bf6c3a0a87ae3ed030` + +## Changelog since v1.8.4 + +### Other notable changes + +* Fix scheduler cache panic when updating pod conditions. ([#56731](https://github.com/kubernetes/kubernetes/pull/56731), [@bsalamat](https://github.com/bsalamat)) +* Add new Prometheus metric that monitors the remaining lifetime of certificates used to authenticate requests to the API server. ([#50387](https://github.com/kubernetes/kubernetes/pull/50387), [@jcbsmpsn](https://github.com/jcbsmpsn)) +* scheduler: Fix issue were a new pod with affinity gets stuck at `creating` because the node had been deleted but the pod still exists. ([#56835](https://github.com/kubernetes/kubernetes/pull/56835), [@wenlxie](https://github.com/wenlxie)) +* Updated Dashboard add-on to version 1.8.0: The Dashboard add-on now deploys with https enabled. The Dashboard can be accessed via kubectl proxy at http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/. The /ui redirect is deprecated and will be removed in 1.10. ([#53046](https://github.com/kubernetes/kubernetes/pull/53046), [@maciaszczykm](https://github.com/maciaszczykm)) +* Fix issue where masquerade rules are flushed in GCE k8s clusters. ([#56729](https://github.com/kubernetes/kubernetes/pull/56729), [@dnardo](https://github.com/dnardo)) +* kubelet: Fix bug where `runAsUser: MustRunAsNonRoot` strategy didn't reject a pod with a non-numeric `USER`. ([#56708](https://github.com/kubernetes/kubernetes/pull/56708), [@php-coder](https://github.com/php-coder)) +* Add iptables rules to allow Pod traffic even when default iptables policy is to reject. ([#52569](https://github.com/kubernetes/kubernetes/pull/52569), [@tmjd](https://github.com/tmjd)) +* Fix a bug in GCE multizonal clusters where PersistentVolumes were sometimes created in zones without nodes. ([#52322](https://github.com/kubernetes/kubernetes/pull/52322), [@davidz627](https://github.com/davidz627)) +* If a non-absolute mountPath is passed to the kubelet, prefix it with the appropriate root path. ([#55665](https://github.com/kubernetes/kubernetes/pull/55665), [@brendandburns](https://github.com/brendandburns)) +* add GRS, RAGRS storage account type support for azure disk ([#55931](https://github.com/kubernetes/kubernetes/pull/55931), [@andyzhangx](https://github.com/andyzhangx)) +* Fix a typo in prometheus-to-sd configuration, that drops some stackdriver metrics. ([#56473](https://github.com/kubernetes/kubernetes/pull/56473), [@loburm](https://github.com/loburm)) +* Fixes server name verification of aggregated API servers and webhook admission endpoints ([#56415](https://github.com/kubernetes/kubernetes/pull/56415), [@liggitt](https://github.com/liggitt)) +* Update jquery and bootstrap dependencies ([#56445](https://github.com/kubernetes/kubernetes/pull/56445), [@dashpole](https://github.com/dashpole)) +* Fix CRI localhost seccomp path in format localhost//profileRoot/profileName. ([#55450](https://github.com/kubernetes/kubernetes/pull/55450), [@feiskyer](https://github.com/feiskyer)) +* support mount options in azure file ([#54674](https://github.com/kubernetes/kubernetes/pull/54674), [@andyzhangx](https://github.com/andyzhangx)) +* kube-apiserver: fixed --oidc-username-prefix and --oidc-group-prefix flags which previously weren't correctly enabled ([#56175](https://github.com/kubernetes/kubernetes/pull/56175), [@ericchiang](https://github.com/ericchiang)) +* fluentd-gcp addon: Fix fluentd deployment on GCP when custom resources are set. ([#55950](https://github.com/kubernetes/kubernetes/pull/55950), [@crassirostris](https://github.com/crassirostris)) +* API discovery failures no longer crash the kube controller manager via the garbage collector. ([#55259](https://github.com/kubernetes/kubernetes/pull/55259), [@ironcladlou](https://github.com/ironcladlou)) +* Fix bug where master startup script on GCP failed randomly due to concurrent iptables invocations. ([#55945](https://github.com/kubernetes/kubernetes/pull/55945), [@x13n](https://github.com/x13n)) + + + +# v1.8.4 + +[Documentation](https://docs.k8s.io) & [Examples](https://releases.k8s.io/release-1.8/examples) + +## Downloads for v1.8.4 + + +filename | sha256 hash +-------- | ----------- +[kubernetes.tar.gz](https://dl.k8s.io/v1.8.4/kubernetes.tar.gz) | `7f87cdafaf5959dfd60e4a89203a7e85cc139262b87c491e3ef46a1313fb9379` +[kubernetes-src.tar.gz](https://dl.k8s.io/v1.8.4/kubernetes-src.tar.gz) | `084a6d95c17c0c06123c146f04501eb8cbf23bfcbcfa23d511a0a2d2018c4a93` + +### Client Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-client-darwin-386.tar.gz](https://dl.k8s.io/v1.8.4/kubernetes-client-darwin-386.tar.gz) | `86b1ac96cd3bbaaa25806f8de34c26a9d6c9ba1daf70baa9df9d488db0da7054` +[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.8.4/kubernetes-client-darwin-amd64.tar.gz) | `f541a9b48ef115e2e4923f906daa9bc112f0b308d8d5559135e507d04fdc0424` +[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.8.4/kubernetes-client-linux-386.tar.gz) | `9d3ea12e58e2e6eef35641856a5fa116bd7301570868252c5525ff8a0719b5bc` +[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.8.4/kubernetes-client-linux-amd64.tar.gz) | `4d3c2a9e0d837e3607580d95bbc473ffb496fc47ba0ce7721e9180a9020f1f39` +[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.8.4/kubernetes-client-linux-arm64.tar.gz) | `02c95d433cc5ce4f2d1e162b13f74f82888cd6dbd91c031198fbb7ab55131093` +[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.8.4/kubernetes-client-linux-arm.tar.gz) | `8f3d6bf3a3e05a65c93e071ce6b5653be534aa358c01cc2de704db9bc45b040e` +[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.8.4/kubernetes-client-linux-ppc64le.tar.gz) | `775bcc7d66364f43794be96ab6b36992904f7ed0d56bb8e309216be23ff22862` +[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.8.4/kubernetes-client-linux-s390x.tar.gz) | `162584246b70c2a3c40571080c1cf0c73efbe6101d7c7f27059115336b901cb8` +[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.8.4/kubernetes-client-windows-386.tar.gz) | `fd88cc783cd73972b9175bebdb719dff697b5ff200ea6ef61152f3ce38b07f6f` +[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.8.4/kubernetes-client-windows-amd64.tar.gz) | `42ec653406de971f7a7e5b16c5ef0d6ebf3d17782d40b2a88a13ef128fe57d62` + +### Server Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.8.4/kubernetes-server-linux-amd64.tar.gz) | `08d64a59a5fe620488f05214844a910144d7fe16a783d351704c71f3843124dc` +[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.8.4/kubernetes-server-linux-arm64.tar.gz) | `75ef62ecd203088a0f5bb5f48d782fd91af4a7dc3348b265ddd13c5bd15d0d01` +[kubernetes-server-linux-arm.tar.gz](https://dl.k8s.io/v1.8.4/kubernetes-server-linux-arm.tar.gz) | `276120cdc40e7925c4c09e26a546d954a43d0599b573e26b76f62f816b5b256d` +[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.8.4/kubernetes-server-linux-ppc64le.tar.gz) | `2c9a213de651be74452116778dc47800f036d03cdbdf65a424a3fd566906933d` +[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.8.4/kubernetes-server-linux-s390x.tar.gz) | `7c073fe63198b793b7a63ebd5f8adb69b780cae128df70b2c964f2493487021f` + +### Node Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.8.4/kubernetes-node-linux-amd64.tar.gz) | `108e9cb2353aa64bbf5e11b938ee65a79abd879136b1f4ab123c897463d388fb` +[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.8.4/kubernetes-node-linux-arm64.tar.gz) | `b59029a6abbfb628bb14d1d2b633307ad1f22c6b758ffd11d7ba5b1a82e63f94` +[kubernetes-node-linux-arm.tar.gz](https://dl.k8s.io/v1.8.4/kubernetes-node-linux-arm.tar.gz) | `f31b08171d6a07ae4fca6b0153ce8da68df766f2334dc75c8b3206840c22424e` +[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.8.4/kubernetes-node-linux-ppc64le.tar.gz) | `0065e1b5cf385097b8da29cc2c91c5555e5f3cd8beed1874f1753b9b5c10e363` +[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.8.4/kubernetes-node-linux-s390x.tar.gz) | `dd08355d5350ef7f881f109bbe627071b494f3d86633a29ac2e4a834cd8d70b3` +[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.8.4/kubernetes-node-windows-amd64.tar.gz) | `1b11e3fbc0af816510660a624f38a68c8c1008c1d9045a4bad373be8af022f7a` + +## Changelog since v1.8.3 + +### Other notable changes + +* Cluster Autoscaler 1.0.3 ([#55947](https://github.com/kubernetes/kubernetes/pull/55947), [@aleksandra-malinowska](https://github.com/aleksandra-malinowska)) +* - Add PodSecurityPolicies for cluster addons ([#55509](https://github.com/kubernetes/kubernetes/pull/55509), [@tallclair](https://github.com/tallclair)) + * - Remove SSL cert HostPath volumes from heapster addons +* Fix session affinity issue with external load balancer traffic when ExternalTrafficPolicy=Local. ([#55519](https://github.com/kubernetes/kubernetes/pull/55519), [@MrHohn](https://github.com/MrHohn)) +* Addon manager supports HA masters. ([#55782](https://github.com/kubernetes/kubernetes/pull/55782), [@x13n](https://github.com/x13n)) +* ScaleIO persistent volumes now support referencing a secret in a namespace other than the bound persistent volume claim's namespace; this is controlled during provisioning with the `secretNamespace` storage class parameter; StoragePool and ProtectionDomain attributes no longer defaults to the value `default` ([#54013](https://github.com/kubernetes/kubernetes/pull/54013), [@vladimirvivien](https://github.com/vladimirvivien)) +* Allow HPA to read custom metrics. ([#54854](https://github.com/kubernetes/kubernetes/pull/54854), [@kawych](https://github.com/kawych)) +* Add masquerading rules by default to GCE/GKE ([#55178](https://github.com/kubernetes/kubernetes/pull/55178), [@dnardo](https://github.com/dnardo)) +* kubeadm now produces error during preflight checks if swap is enabled. Users, who can setup kubelet to run in unsupported environment with enabled swap, will be able to skip that preflight check. ([#55399](https://github.com/kubernetes/kubernetes/pull/55399), [@kad](https://github.com/kad)) +* GCE: provide an option to disable docker's live-restore on COS/ubuntu ([#55260](https://github.com/kubernetes/kubernetes/pull/55260), [@yujuhong](https://github.com/yujuhong)) +* Fix hyperkube kubelet --experimental-dockershim ([#55250](https://github.com/kubernetes/kubernetes/pull/55250), [@ivan4th](https://github.com/ivan4th)) +* ScaleIO driver completely removes dependency on drv_cfg binary so a Kubernetes cluster can easily run a containerized kubelet. ([#54956](https://github.com/kubernetes/kubernetes/pull/54956), [@vladimirvivien](https://github.com/vladimirvivien)) + + + +# v1.8.3 + +[Documentation](https://docs.k8s.io) & [Examples](https://releases.k8s.io/release-1.8/examples) + +## Downloads for v1.8.3 + + +filename | sha256 hash +-------- | ----------- +[kubernetes.tar.gz](https://dl.k8s.io/v1.8.3/kubernetes.tar.gz) | `86a565d47afb2b4440a3d706e24b9590225e576f1aee1d0117f6a82c13a7ca1a` +[kubernetes-src.tar.gz](https://dl.k8s.io/v1.8.3/kubernetes-src.tar.gz) | `3fa0d5f87f92004297f17ed9791a9c309c6ed6958bbd4df6e3de5da640d35c25` + +### Client Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-client-darwin-386.tar.gz](https://dl.k8s.io/v1.8.3/kubernetes-client-darwin-386.tar.gz) | `e85d9804e14c0acc3f9e71a03e0ea10fc4848c94bb0fed56776d8137b71f70d7` +[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.8.3/kubernetes-client-darwin-amd64.tar.gz) | `2095e610c6b838a51ef054175794a9fe2436b02c1c4f36dfe4ac7b6ea77a59e5` +[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.8.3/kubernetes-client-linux-386.tar.gz) | `970764b73734809daf11337ced1f71708a8be15573fa6c68dcf2a12b70820640` +[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.8.3/kubernetes-client-linux-amd64.tar.gz) | `8796ce36f2f59e34e9bd7e788bc23076ccc8371a79535443b6105a9aae544c2a` +[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.8.3/kubernetes-client-linux-arm64.tar.gz) | `94a5ce6fea8ce9d3e3b726f79820d3c85d87322687ff9b97f5bbc0d28f41816b` +[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.8.3/kubernetes-client-linux-arm.tar.gz) | `6ff7bdabf7a5ff01d9f0d03d991c9dcd11503cf5c7b1ead5a9103ebf6f6dc2a1` +[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.8.3/kubernetes-client-linux-ppc64le.tar.gz) | `000b8c1138e3074d6880bf3eb0b2ed5c6db8c6fba4792dba720c489cf2f82b58` +[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.8.3/kubernetes-client-linux-s390x.tar.gz) | `c77de362e41606c2fa7757cdf47c95e0dce6dc448017a8b9550f7bab9eb52cca` +[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.8.3/kubernetes-client-windows-386.tar.gz) | `3a7561fb0e90add10b286e738ec369987a1bc4750ccf05d00dc0e4fd735b86e1` +[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.8.3/kubernetes-client-windows-amd64.tar.gz) | `0e1bc781f607cf580696b929a9e40805701ebf25f8b166ec7687de46eb417011` + +### Server Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.8.3/kubernetes-server-linux-amd64.tar.gz) | `557c231a63f5975d08565dd690381bd63d9db14528da07c7e86305a82fbd9c8b` +[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.8.3/kubernetes-server-linux-arm64.tar.gz) | `b1c2cbe6a308df51815c98f93a1ec5e8e5be390ae1e4c31ab7c03c581e8442f2` +[kubernetes-server-linux-arm.tar.gz](https://dl.k8s.io/v1.8.3/kubernetes-server-linux-arm.tar.gz) | `db5cb69166b482bc705f56a4b50fbe1c553dcbcf83c569bef2828ec70c94fa36` +[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.8.3/kubernetes-server-linux-ppc64le.tar.gz) | `aca313f74aa682e9ced1b3f238fd6b03795d6a2f12a6604481fafe9756f88c82` +[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.8.3/kubernetes-server-linux-s390x.tar.gz) | `42e7cc141555ffa7a7653de5065715164817c7b096d13b58b7770a6b66283b39` + +### Node Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.8.3/kubernetes-node-linux-amd64.tar.gz) | `6035027a39fd8cac6de9f33efcb929300798a5601b0c2ca0569baaf18ce12559` +[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.8.3/kubernetes-node-linux-arm64.tar.gz) | `495ebf4885af7896cf28fbd6988bd954d576bee99ba815e6e741a0c407bae92a` +[kubernetes-node-linux-arm.tar.gz](https://dl.k8s.io/v1.8.3/kubernetes-node-linux-arm.tar.gz) | `d1c0595f086a1a2c9c73ee556750db3e7485c3e75f9496214313f935ad6d0350` +[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.8.3/kubernetes-node-linux-ppc64le.tar.gz) | `2b036ca22970d9dcb6b80da45f3ecaeb6b1e78b4474718a8581d2f987779c3fa` +[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.8.3/kubernetes-node-linux-s390x.tar.gz) | `f936cbfe0f2888a25620c8fe21b297459dd235044f1587f02456921be458d5ff` +[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.8.3/kubernetes-node-windows-amd64.tar.gz) | `b913fb8267c545db77c0c43234c773043031c7564cc745e61842f58277041c58` + +## Changelog since v1.8.2 + +### Other notable changes + +* Fixed 'Schedulercache is corrupted' error in kube-scheduler ([#55262](https://github.com/kubernetes/kubernetes/pull/55262), [@liggitt](https://github.com/liggitt)) +* Add support for PodSecurityPolicy on GCE: `ENABLE_POD_SECURITY_POLICY=true` enables the admission controller, and installs policies for default addons. ([#52367](https://github.com/kubernetes/kubernetes/pull/52367), [@tallclair](https://github.com/tallclair)) +* Azure cloudprovider: Fix controller manager crash issue on a manually created k8s cluster. ([#53694](https://github.com/kubernetes/kubernetes/pull/53694), [@andyzhangx](https://github.com/andyzhangx)) +* Cluster Autoscaler 1.0.2 ([#55161](https://github.com/kubernetes/kubernetes/pull/55161), [@mwielgus](https://github.com/mwielgus)) +* - fluentd-gcp runs with a dedicated fluentd-gcp service account ([#54175](https://github.com/kubernetes/kubernetes/pull/54175), [@tallclair](https://github.com/tallclair)) + * - Stop mounting the host certificates into fluentd's prometheus-to-sd container +* Fix a bug where pod address is not removed from endpoints object while pod is in graceful termination. ([#54828](https://github.com/kubernetes/kubernetes/pull/54828), [@freehan](https://github.com/freehan)) +* fix warning messages due to GetMountRefs func not implemented in windows ([#52401](https://github.com/kubernetes/kubernetes/pull/52401), [@andyzhangx](https://github.com/andyzhangx)) +* allow windows mount path ([#51240](https://github.com/kubernetes/kubernetes/pull/51240), [@andyzhangx](https://github.com/andyzhangx)) +* Reduce log noise produced by prometheus-to-sd, by bumping it to version 0.2.2. ([#54635](https://github.com/kubernetes/kubernetes/pull/54635), [@loburm](https://github.com/loburm)) +* Fix clustered datastore name to be absolute. ([#54438](https://github.com/kubernetes/kubernetes/pull/54438), [@pshahzeb](https://github.com/pshahzeb)) +* Fix `kubeadm upgrade plan` for offline operation: ignore errors when trying to fetch latest versions from dl.k8s.io ([#54016](https://github.com/kubernetes/kubernetes/pull/54016), [@praseodym](https://github.com/praseodym)) +* Add openssh-client back into the hyperkube image. This allows the gitRepo volume plugin to work properly. ([#54250](https://github.com/kubernetes/kubernetes/pull/54250), [@ixdy](https://github.com/ixdy)) +* Fix an issue where pods were briefly transitioned to a "Pending" state during the deletion process. ([#54593](https://github.com/kubernetes/kubernetes/pull/54593), [@dashpole](https://github.com/dashpole)) +* Add a label which prevents a node from being added to a cloud load balancer ([#53146](https://github.com/kubernetes/kubernetes/pull/53146), [@brendandburns](https://github.com/brendandburns)) +* Add a new feature gate for enabling an alpha annotation which, if present, excludes the annotated node from being added to a service load balancers. ([#54644](https://github.com/kubernetes/kubernetes/pull/54644), [@brendandburns](https://github.com/brendandburns)) +* Support German cloud for azure disk mount feature ([#50673](https://github.com/kubernetes/kubernetes/pull/50673), [@clement-buchart](https://github.com/clement-buchart)) +* Fix overlay2 container disk metrics for Docker and CRI-O ([#54827](https://github.com/kubernetes/kubernetes/pull/54827), [@dashpole](https://github.com/dashpole)) +* API machinery's httpstream/spdy calls now support CIDR notation for NO_PROXY ([#54413](https://github.com/kubernetes/kubernetes/pull/54413), [@kad](https://github.com/kad)) +* fix a bug where disk pressure could trigger prematurely when using overlay2 ([#53684](https://github.com/kubernetes/kubernetes/pull/53684), [@dashpole](https://github.com/dashpole)) +* PodSecurityPolicy: when multiple policies allow a submitted pod, priority is given to ones which do not require any fields in the pod spec to be defaulted. If the pod must be defaulted, the first policy (ordered by name) that allows the pod is used. ([#52849](https://github.com/kubernetes/kubernetes/pull/52849), [@liggitt](https://github.com/liggitt)) +* Fixes discovery information for scale subresources in the apps API group ([#54683](https://github.com/kubernetes/kubernetes/pull/54683), [@liggitt](https://github.com/liggitt)) +* BugFix: Exited containers are not Garbage Collected by the kubelet while the pod is running ([#53167](https://github.com/kubernetes/kubernetes/pull/53167), [@dashpole](https://github.com/dashpole)) +* fix azure pv crash due to volumeSource.ReadOnly value nil ([#54607](https://github.com/kubernetes/kubernetes/pull/54607), [@andyzhangx](https://github.com/andyzhangx)) +* kubeadm init: fix a bug that prevented the --token-ttl flag and tokenTTL configuration value from working as expected for infinite (0) values. ([#54640](https://github.com/kubernetes/kubernetes/pull/54640), [@mattmoyer](https://github.com/mattmoyer)) +* [fluentd-gcp addon] Fluentd now runs in its own network, not in the host one. ([#54395](https://github.com/kubernetes/kubernetes/pull/54395), [@crassirostris](https://github.com/crassirostris)) +* fix azure disk mount failure on coreos and some other distros ([#54334](https://github.com/kubernetes/kubernetes/pull/54334), [@andyzhangx](https://github.com/andyzhangx)) +* Fix for service controller so that it won't retry on doNotRetry service update failure. ([#54184](https://github.com/kubernetes/kubernetes/pull/54184), [@MrHohn](https://github.com/MrHohn)) +* BulkVerifyVolumes() implementation for vSphere ([#52131](https://github.com/kubernetes/kubernetes/pull/52131), [@BaluDontu](https://github.com/BaluDontu)) +* Added option lb-provider to OpenStack cloud provider config ([#54176](https://github.com/kubernetes/kubernetes/pull/54176), [@gonzolino](https://github.com/gonzolino)) + + + +# v1.8.2 + +[Documentation](https://docs.k8s.io) & [Examples](https://releases.k8s.io/release-1.8/examples) + +## Downloads for v1.8.2 + + +filename | sha256 hash +-------- | ----------- +[kubernetes.tar.gz](https://dl.k8s.io/v1.8.2/kubernetes.tar.gz) | `06a800c414e776640a7861baa4f0b6edbd898c13ad3ebcd33860fe5d949bbdee` +[kubernetes-src.tar.gz](https://dl.k8s.io/v1.8.2/kubernetes-src.tar.gz) | `fbfb65a4eb1ddff32e302a0821204fa780ebb5b27298e31699c43c19da48191e` + +### Client Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-client-darwin-386.tar.gz](https://dl.k8s.io/v1.8.2/kubernetes-client-darwin-386.tar.gz) | `3eb81f1178ff73ca683738606acea1d9537a33c6e3d15571795a24af6c53dbd7` +[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.8.2/kubernetes-client-darwin-amd64.tar.gz) | `15da279f018a73f93b857639931c4ba8a714c86e5c5738c33840c47df44ac2a4` +[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.8.2/kubernetes-client-linux-386.tar.gz) | `bd9f144e6ddfc715fa77d9cb0310763e49f8121e894ed33714658fb2d6eb2675` +[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.8.2/kubernetes-client-linux-amd64.tar.gz) | `7c20d4a3859c07aadf9a1676876bafdf56187478a69d3bfca5277fb275febb96` +[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.8.2/kubernetes-client-linux-arm64.tar.gz) | `395c3fb5992509191cacbaf6e7ed4fd0fbee5c0b9c890f496879784454f88aa3` +[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.8.2/kubernetes-client-linux-arm.tar.gz) | `a1cff2f8ab5f77f000e20f87b00a3723a8323fec82926afcc984722ab3f8d714` +[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.8.2/kubernetes-client-linux-ppc64le.tar.gz) | `832a1e399802bfd8871cd911f17dbb6b2264680e9477c2944d442a3f9e5fa6f2` +[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.8.2/kubernetes-client-linux-s390x.tar.gz) | `6afc2c4a331ee70e095a6d1e1f11bf69923afb1830840d110459e32b849b1b6c` +[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.8.2/kubernetes-client-windows-386.tar.gz) | `ecaadb5a4c08357685dbaee288d1220bd60ff0f86281ec88a5467da6eebf213b` +[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.8.2/kubernetes-client-windows-amd64.tar.gz) | `b8ff337615f740b1501cf7284d7f0a51a82880dcf23fff2464f8d37045c27f3f` + +### Server Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.8.2/kubernetes-server-linux-amd64.tar.gz) | `8ccd4912473e0d334694434936a5ca9547caddaa39d771a1fb94620c5d6002d4` +[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.8.2/kubernetes-server-linux-arm64.tar.gz) | `39b3c61927c905f142d74fe69391156e6bf61cc5e7a798cdf2c295a76e72161d` +[kubernetes-server-linux-arm.tar.gz](https://dl.k8s.io/v1.8.2/kubernetes-server-linux-arm.tar.gz) | `fc6b01b233f8d0c61dd485d8d571c9a2e1a5b085f0d0db734a84c42b71416537` +[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.8.2/kubernetes-server-linux-ppc64le.tar.gz) | `6f6d8dcef0334736021d9f6cc2bbfdb78500483f8961e7ff14b09f1c67d37056` +[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.8.2/kubernetes-server-linux-s390x.tar.gz) | `6f1b6b5fb818fdb787cdf65ff3da81235b5b4db5b4a9b5579920d11dc8a3fa73` + +### Node Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.8.2/kubernetes-node-linux-amd64.tar.gz) | `93c6b5d2a5e4aaf8776f56e5b8f40038c76d3d03709124fb8900f83acb49c782` +[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.8.2/kubernetes-node-linux-arm64.tar.gz) | `ab4535e19825e0e9b76987dbb11d9fd746281e45a90f90b453dbc7d6fecb2c69` +[kubernetes-node-linux-arm.tar.gz](https://dl.k8s.io/v1.8.2/kubernetes-node-linux-arm.tar.gz) | `96acd6ec41d4a3ec7ea6f95acecf116755340915e3d261de760d9ed84708e3f0` +[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.8.2/kubernetes-node-linux-ppc64le.tar.gz) | `4256a8c315de083435fcdfc8ee2ae370bd603fa976218edadbf7bfe11adcf223` +[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.8.2/kubernetes-node-linux-s390x.tar.gz) | `30f2254bf442fc36fc23bd962930eb48fd000c9ffce81c26c0d64d4a0fd0c193` +[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.8.2/kubernetes-node-windows-amd64.tar.gz) | `b92c2670ce8dd75f744537abb6e98df84ce5a18da7df6b70741e92d9e57098bf` + +## Changelog since v1.8.1 + +### Other notable changes + +* Allow for configuring etcd hostname in the manifest ([#54403](https://github.com/kubernetes/kubernetes/pull/54403), [@wojtek-t](https://github.com/wojtek-t)) +* Allow standard flags in client-gen. ([#53999](https://github.com/kubernetes/kubernetes/pull/53999), [@sttts](https://github.com/sttts)) +* Cluster Autoscaler 1.0.1 ([#54298](https://github.com/kubernetes/kubernetes/pull/54298), [@mwielgus](https://github.com/mwielgus)) +* Resolves forbidden error when accessing replicasets and daemonsets via the apps API group ([#54309](https://github.com/kubernetes/kubernetes/pull/54309), [@liggitt](https://github.com/liggitt)) +* kubelet: prevent removal of default labels from Node API objects on startup ([#54073](https://github.com/kubernetes/kubernetes/pull/54073), [@liggitt](https://github.com/liggitt)) +* Fix a bug that prevents client-go metrics from being registered in prometheus in multiple components. ([#53434](https://github.com/kubernetes/kubernetes/pull/53434), [@crassirostris](https://github.com/crassirostris)) +* Webhook always retries connection reset error. ([#53947](https://github.com/kubernetes/kubernetes/pull/53947), [@crassirostris](https://github.com/crassirostris)) +* Adjust batching audit webhook default parameters: increase queue size, batch size, and initial backoff. Add throttling to the batching audit webhook. Default rate limit is 10 QPS. ([#53417](https://github.com/kubernetes/kubernetes/pull/53417), [@crassirostris](https://github.com/crassirostris)) +* Address a bug which allowed the horizontal pod autoscaler to allocate `desiredReplicas` > `maxReplicas` in certain instances. ([#53690](https://github.com/kubernetes/kubernetes/pull/53690), [@mattjmcnaughton](https://github.com/mattjmcnaughton)) +* Fix metrics API group name in audit configuration ([#53493](https://github.com/kubernetes/kubernetes/pull/53493), [@piosz](https://github.com/piosz)) +* Use separate client for leader election in scheduler to avoid starving leader election by regular scheduler operations. ([#53793](https://github.com/kubernetes/kubernetes/pull/53793), [@wojtek-t](https://github.com/wojtek-t)) +* kubeadm: Strip bootstrap tokens from the `kubeadm-config` ConfigMap ([#53559](https://github.com/kubernetes/kubernetes/pull/53559), [@fabriziopandini](https://github.com/fabriziopandini)) + + + # v1.8.1 [Documentation](https://docs.k8s.io) & [Examples](https://releases.k8s.io/release-1.8/examples) @@ -416,7 +809,7 @@ Consider the following changes, limitations, and guidelines before you upgrade: * The `--audit-policy-file` option is required if the `AdvancedAudit` feature is not explicitly turned off (`--feature-gates=AdvancedAudit=false`) on the API server. * The audit log file defaults to JSON encoding when using the advanced auditing feature gate. - * The `--audit-policy-file` option requires `kind` and `apiVersion` fields specifying what format version the `Policy` is using. + * An audit policy file without either an `apiVersion` or a `kind` field may be treated as invalid. * The webhook and log file now output the `v1beta1` event format. For more details, see [Advanced audit](https://kubernetes.io/docs/tasks/debug-application-cluster/audit/#advanced-audit). @@ -526,7 +919,7 @@ This section provides an overview of deprecated API versions, options, flags, an - Enable an out-of-tree cloud provider with `--cloud-provider=external` in either version. - For more information on deprecating auto-detecting cloud providers in kubelet, see [PR [#51312](https://github.com/kubernetes/kubernetes/pull/51312) and [announcement](https://groups.google.com/forum/#!topic/kubernetes-dev/UAxwa2inbTA). + For more information on deprecating auto-detecting cloud providers in kubelet, see PR [#51312](https://github.com/kubernetes/kubernetes/pull/51312) and [announcement](https://groups.google.com/forum/#!topic/kubernetes-dev/UAxwa2inbTA). - The `PersistentVolumeLabel` admission controller in the API server is deprecated. @@ -539,7 +932,7 @@ This section provides an overview of deprecated API versions, options, flags, an ### OpenStack - The `openstack-heat` provider for `kube-up` is deprecated and will be removed - in a future release. Refer to [Issue [#49213](https://github.com/kubernetes/kubernetes/issues/49213) + in a future release. Refer to Issue [#49213](https://github.com/kubernetes/kubernetes/issues/49213) for background information. ### Scheduling @@ -1106,7 +1499,6 @@ filename | sha256 hash * kubeadm: Use the release-1.8 branch by default ([#52085](https://github.com/kubernetes/kubernetes/pull/52085), [@luxas](https://github.com/luxas)) * PersistentVolumeLabel admission controller is now deprecated. ([#52618](https://github.com/kubernetes/kubernetes/pull/52618), [@dims](https://github.com/dims)) * Mark the LBaaS v1 of OpenStack cloud provider deprecated. ([#52821](https://github.com/kubernetes/kubernetes/pull/52821), [@FengyunPan](https://github.com/FengyunPan)) -* NONE ([#52819](https://github.com/kubernetes/kubernetes/pull/52819), [@verult](https://github.com/verult)) * Mark image as deliberately optional in v1 Container struct. Many objects in the Kubernetes API inherit the container struct and only Pods require the field to be set. ([#48406](https://github.com/kubernetes/kubernetes/pull/48406), [@gyliu513](https://github.com/gyliu513)) * [fluentd-gcp addon] Update Stackdriver plugin to version 0.6.7 ([#52565](https://github.com/kubernetes/kubernetes/pull/52565), [@crassirostris](https://github.com/crassirostris)) * Remove duplicate proto errors in kubelet. ([#52132](https://github.com/kubernetes/kubernetes/pull/52132), [@adityadani](https://github.com/adityadani)) @@ -1145,7 +1537,6 @@ filename | sha256 hash * The `kube-cloud-controller-manager` flag `--service-account-private-key-file` was non-functional and is now deprecated. ([#50289](https://github.com/kubernetes/kubernetes/pull/50289), [@liggitt](https://github.com/liggitt)) * The `kube-cloud-controller-manager` flag `--use-service-account-credentials` is now honored consistently, regardless of whether `--service-account-private-key-file` was specified. * Fix credentials providers for docker sandbox image. ([#51870](https://github.com/kubernetes/kubernetes/pull/51870), [@feiskyer](https://github.com/feiskyer)) -* NONE ([#52120](https://github.com/kubernetes/kubernetes/pull/52120), [@abgworrall](https://github.com/abgworrall)) * Fixed an issue looking up cronjobs when they existed in more than one API version ([#52227](https://github.com/kubernetes/kubernetes/pull/52227), [@liggitt](https://github.com/liggitt)) * Add priority-based preemption to the scheduler. ([#50949](https://github.com/kubernetes/kubernetes/pull/50949), [@bsalamat](https://github.com/bsalamat)) * Add CLUSTER_SIGNING_DURATION environment variable to cluster configuration scripts ([#51844](https://github.com/kubernetes/kubernetes/pull/51844), [@jcbsmpsn](https://github.com/jcbsmpsn)) @@ -1492,7 +1883,6 @@ filename | sha256 hash * Make rolling update the default update strategy for v1beta2.DaemonSet and v1beta2.StatefulSet ([#50175](https://github.com/kubernetes/kubernetes/pull/50175), [@foxish](https://github.com/foxish)) * Deprecate Deployment .spec.rollbackTo field ([#49340](https://github.com/kubernetes/kubernetes/pull/49340), [@janetkuo](https://github.com/janetkuo)) * Collect metrics from Heapster in Stackdriver mode. ([#50290](https://github.com/kubernetes/kubernetes/pull/50290), [@piosz](https://github.com/piosz)) -* N/A ([#50179](https://github.com/kubernetes/kubernetes/pull/50179), [@k82cn](https://github.com/k82cn)) * [Federation] HPA controller ([#45993](https://github.com/kubernetes/kubernetes/pull/45993), [@irfanurrehman](https://github.com/irfanurrehman)) * Relax restrictions on environment variable names. ([#48986](https://github.com/kubernetes/kubernetes/pull/48986), [@timoreimann](https://github.com/timoreimann)) * The node condition 'NodeInodePressure' was removed, as kubelet did not report it. ([#50124](https://github.com/kubernetes/kubernetes/pull/50124), [@k82cn](https://github.com/k82cn)) diff --git a/CHANGELOG-1.9.md b/CHANGELOG-1.9.md index 28e2f2b5f69..0094d62b279 100644 --- a/CHANGELOG-1.9.md +++ b/CHANGELOG-1.9.md @@ -1,17 +1,1549 @@ -- [v1.9.0-alpha.1](#v190-alpha1) - - [Downloads for v1.9.0-alpha.1](#downloads-for-v190-alpha1) +- [v1.9.1](#v191) + - [Downloads for v1.9.1](#downloads-for-v191) - [Client Binaries](#client-binaries) - [Server Binaries](#server-binaries) - [Node Binaries](#node-binaries) - - [Changelog since v1.8.0-alpha.3](#changelog-since-v180-alpha3) - - [Action Required](#action-required) + - [Changelog since v1.9.0](#changelog-since-v190) - [Other notable changes](#other-notable-changes) +- [v1.9.0](#v190) + - [Downloads for v1.9.0](#downloads-for-v190) + - [Client Binaries](#client-binaries-1) + - [Server Binaries](#server-binaries-1) + - [Node Binaries](#node-binaries-1) + - [1.9 Release Notes](#19-release-notes) + - [WARNING: etcd backup strongly recommended](#warning-etcd-backup-strongly-recommended) + - [Introduction to 1.9.0](#introduction-to-190) + - [Major themes](#major-themes) + - [API Machinery](#api-machinery) + - [Apps](#apps) + - [Auth](#auth) + - [AWS](#aws) + - [Azure](#azure) + - [Cluster Lifecycle](#cluster-lifecycle) + - [Instrumentation](#instrumentation) + - [Network](#network) + - [Node](#node) + - [OpenStack](#openstack) + - [Storage](#storage) + - [Windows](#windows) + - [Before Upgrading](#before-upgrading) + - [**API Machinery**](#api-machinery-1) + - [**Auth**](#auth-1) + - [**CLI**](#cli) + - [**Cluster Lifecycle**](#cluster-lifecycle-1) + - [**Multicluster**](#multicluster) + - [**Node**](#node-1) + - [**Network**](#network-1) + - [**Scheduling**](#scheduling) + - [**Storage**](#storage-1) + - [**OpenStack**](#openstack-1) + - [Known Issues](#known-issues) + - [Deprecations](#deprecations) + - [**API Machinery**](#api-machinery-2) + - [**Auth**](#auth-2) + - [**Cluster Lifecycle**](#cluster-lifecycle-2) + - [**Network**](#network-2) + - [**Storage**](#storage-2) + - [**Scheduling**](#scheduling-1) + - [**Node**](#node-2) + - [Notable Changes](#notable-changes) + - [**Workloads API (apps/v1)**](#workloads-api-appsv1) + - [**API Machinery**](#api-machinery-3) + - [**Admission Control**](#admission-control) + - [**API & API server**](#api-&-api-server) + - [**Audit**](#audit) + - [**Custom Resources**](#custom-resources) + - [**Other**](#other) + - [**Apps**](#apps-1) + - [**Auth**](#auth-3) + - [**Audit**](#audit-1) + - [**RBAC**](#rbac) + - [**Other**](#other-1) + - [**GCE**](#gce) + - [**Autoscaling**](#autoscaling) + - [**AWS**](#aws-1) + - [**Azure**](#azure-1) + - [**CLI**](#cli-1) + - [**Kubectl**](#kubectl) + - [**Cluster Lifecycle**](#cluster-lifecycle-3) + - [**API Server**](#api-server) + - [**Cloud Provider Integration**](#cloud-provider-integration) + - [**Kubeadm**](#kubeadm) + - [**Juju**](#juju) + - [**Other**](#other-2) + - [**GCP**](#gcp) + - [**Instrumentation**](#instrumentation-1) + - [**Audit**](#audit-2) + - [**Other**](#other-3) + - [**Multicluster**](#multicluster-1) + - [**Federation**](#federation) + - [**Network**](#network-3) + - [**IPv6**](#ipv6) + - [**IPVS**](#ipvs) + - [**Kube-Proxy**](#kube-proxy) + - [**CoreDNS**](#coredns) + - [**Other**](#other-4) + - [**Node**](#node-3) + - [**Pod API**](#pod-api) + - [**Hardware Accelerators**](#hardware-accelerators) + - [**Container Runtime**](#container-runtime) + - [**Kubelet**](#kubelet) + - [**Other**](#other-5) + - [**OpenStack**](#openstack-2) + - [**Scheduling**](#scheduling-2) + - [**Hardware Accelerators**](#hardware-accelerators-1) + - [**Other**](#other-6) + - [**Storage**](#storage-3) + - [External Dependencies](#external-dependencies) +- [v1.9.0-beta.2](#v190-beta2) + - [Downloads for v1.9.0-beta.2](#downloads-for-v190-beta2) + - [Client Binaries](#client-binaries-2) + - [Server Binaries](#server-binaries-2) + - [Node Binaries](#node-binaries-2) + - [Changelog since v1.9.0-beta.1](#changelog-since-v190-beta1) + - [Other notable changes](#other-notable-changes-1) +- [v1.9.0-beta.1](#v190-beta1) + - [Downloads for v1.9.0-beta.1](#downloads-for-v190-beta1) + - [Client Binaries](#client-binaries-3) + - [Server Binaries](#server-binaries-3) + - [Node Binaries](#node-binaries-3) + - [Changelog since v1.9.0-alpha.3](#changelog-since-v190-alpha3) + - [Action Required](#action-required) + - [Other notable changes](#other-notable-changes-2) +- [v1.9.0-alpha.3](#v190-alpha3) + - [Downloads for v1.9.0-alpha.3](#downloads-for-v190-alpha3) + - [Client Binaries](#client-binaries-4) + - [Server Binaries](#server-binaries-4) + - [Node Binaries](#node-binaries-4) + - [Changelog since v1.9.0-alpha.2](#changelog-since-v190-alpha2) + - [Action Required](#action-required-1) + - [Other notable changes](#other-notable-changes-3) +- [v1.9.0-alpha.2](#v190-alpha2) + - [Downloads for v1.9.0-alpha.2](#downloads-for-v190-alpha2) + - [Client Binaries](#client-binaries-5) + - [Server Binaries](#server-binaries-5) + - [Node Binaries](#node-binaries-5) + - [Changelog since v1.8.0](#changelog-since-v180) + - [Action Required](#action-required-2) + - [Other notable changes](#other-notable-changes-4) +- [v1.9.0-alpha.1](#v190-alpha1) + - [Downloads for v1.9.0-alpha.1](#downloads-for-v190-alpha1) + - [Client Binaries](#client-binaries-6) + - [Server Binaries](#server-binaries-6) + - [Node Binaries](#node-binaries-6) + - [Changelog since v1.8.0-alpha.3](#changelog-since-v180-alpha3) + - [Action Required](#action-required-3) + - [Other notable changes](#other-notable-changes-5) +# v1.9.1 + +[Documentation](https://docs.k8s.io) & [Examples](https://releases.k8s.io/release-1.9/examples) + +## Downloads for v1.9.1 + + +filename | sha256 hash +-------- | ----------- +[kubernetes.tar.gz](https://storage.googleapis.com/kubernetes-release-mehdy/release/v1.9.1/kubernetes.tar.gz) | `0eece0e6c1f68535ea71b58b87e239019bb57fdd61118f3d7defa6bbf4fad5ee` +[kubernetes-src.tar.gz](https://storage.googleapis.com/kubernetes-release-mehdy/release/v1.9.1/kubernetes-src.tar.gz) | `625ebb79412bd12feccf12e8b6a15d9c71ea681b571f34deaa59fe6c9ba55935` + +### Client Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-client-darwin-386.tar.gz](https://storage.googleapis.com/kubernetes-release-mehdy/release/v1.9.1/kubernetes-client-darwin-386.tar.gz) | `909556ed9b8445703d0124f2d8c1901b00afaba63a9123a4296be8663c3a2b2d` +[kubernetes-client-darwin-amd64.tar.gz](https://storage.googleapis.com/kubernetes-release-mehdy/release/v1.9.1/kubernetes-client-darwin-amd64.tar.gz) | `71e191d99d3ac1426e23e087b8d0875e793e5615d3aa7ac1e175b250f9707c48` +[kubernetes-client-linux-386.tar.gz](https://storage.googleapis.com/kubernetes-release-mehdy/release/v1.9.1/kubernetes-client-linux-386.tar.gz) | `1c4e60c0c056a3300c7fcc9faccd1b1ea2b337e1360c20c5b1c25fdc47923cf0` +[kubernetes-client-linux-amd64.tar.gz](https://storage.googleapis.com/kubernetes-release-mehdy/release/v1.9.1/kubernetes-client-linux-amd64.tar.gz) | `fe8fe40148df404b33069931ea30937699758ed4611ef6baddb4c21b7b19db5e` +[kubernetes-client-linux-arm64.tar.gz](https://storage.googleapis.com/kubernetes-release-mehdy/release/v1.9.1/kubernetes-client-linux-arm64.tar.gz) | `921f5711b97f0b4de69784d9c79f95e80f75a550f28fc1f26597aa0ef6faa471` +[kubernetes-client-linux-arm.tar.gz](https://storage.googleapis.com/kubernetes-release-mehdy/release/v1.9.1/kubernetes-client-linux-arm.tar.gz) | `77b010cadef98dc832a2f560afe15e57a675ed9fbc59ffad5e19878510997874` +[kubernetes-client-linux-ppc64le.tar.gz](https://storage.googleapis.com/kubernetes-release-mehdy/release/v1.9.1/kubernetes-client-linux-ppc64le.tar.gz) | `02aa71ddcbe8b711814af7287aac79de5d99c1c143c0d3af5e14b1ff195b8bdc` +[kubernetes-client-linux-s390x.tar.gz](https://storage.googleapis.com/kubernetes-release-mehdy/release/v1.9.1/kubernetes-client-linux-s390x.tar.gz) | `7e315024267306a620045d003785ecc8d7f2e763a6108ae806d5d384aa7552cc` +[kubernetes-client-windows-386.tar.gz](https://storage.googleapis.com/kubernetes-release-mehdy/release/v1.9.1/kubernetes-client-windows-386.tar.gz) | `99b2a81b7876498e119db4cb34c434b3790bc41cd882384037c1c1b18cba9f99` +[kubernetes-client-windows-amd64.tar.gz](https://storage.googleapis.com/kubernetes-release-mehdy/release/v1.9.1/kubernetes-client-windows-amd64.tar.gz) | `d89d303cbbf9e57e5a540277158e4d83ad18ca7402b5b54665f1378bb4528599` + +### Server Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://storage.googleapis.com/kubernetes-release-mehdy/release/v1.9.1/kubernetes-server-linux-amd64.tar.gz) | `5acf2527461419ba883ac352f7c36c3fa0b86a618dbede187054ad90fa233b0e` +[kubernetes-server-linux-arm64.tar.gz](https://storage.googleapis.com/kubernetes-release-mehdy/release/v1.9.1/kubernetes-server-linux-arm64.tar.gz) | `e1f61b4dc6e0c9986e95ec25f876f9a89966215ee8cc7f4a3539ec391b217587` +[kubernetes-server-linux-arm.tar.gz](https://storage.googleapis.com/kubernetes-release-mehdy/release/v1.9.1/kubernetes-server-linux-arm.tar.gz) | `441c45e16e63e9bdf99887a896a99b3a376af778cb778cc1d0e6afc505237200` +[kubernetes-server-linux-ppc64le.tar.gz](https://storage.googleapis.com/kubernetes-release-mehdy/release/v1.9.1/kubernetes-server-linux-ppc64le.tar.gz) | `c0175f02180d9c88028ee5ad4e3ea04af8a6741a97f4900b02615f7f83c4d1c5` +[kubernetes-server-linux-s390x.tar.gz](https://storage.googleapis.com/kubernetes-release-mehdy/release/v1.9.1/kubernetes-server-linux-s390x.tar.gz) | `2178150d31197ad7f59d44ffea37d682c2675b3a4ea2fc3fa1eaa0e768b993f7` + +### Node Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://storage.googleapis.com/kubernetes-release-mehdy/release/v1.9.1/kubernetes-node-linux-amd64.tar.gz) | `b8ff0ae693ecca4d55669c66786d6c585f8c77b41a270d65f8175eba8729663a` +[kubernetes-node-linux-arm64.tar.gz](https://storage.googleapis.com/kubernetes-release-mehdy/release/v1.9.1/kubernetes-node-linux-arm64.tar.gz) | `f0f63baaace463dc663c98cbc9a41e52233d1ef33410571ce3f3e78bd485787e` +[kubernetes-node-linux-arm.tar.gz](https://storage.googleapis.com/kubernetes-release-mehdy/release/v1.9.1/kubernetes-node-linux-arm.tar.gz) | `554bdd11deaf390de85830c7c888dfd4d75d9de8ac147799df12993f27bde905` +[kubernetes-node-linux-ppc64le.tar.gz](https://storage.googleapis.com/kubernetes-release-mehdy/release/v1.9.1/kubernetes-node-linux-ppc64le.tar.gz) | `913af8ca8b258930e76fd3368acc83608e36e7e270638fa01a6e3be4f682d8bd` +[kubernetes-node-linux-s390x.tar.gz](https://storage.googleapis.com/kubernetes-release-mehdy/release/v1.9.1/kubernetes-node-linux-s390x.tar.gz) | `8192c1c80563230d727fab71514105571afa52cde8520b3d90af58e6daf0e19c` +[kubernetes-node-windows-amd64.tar.gz](https://storage.googleapis.com/kubernetes-release-mehdy/release/v1.9.1/kubernetes-node-windows-amd64.tar.gz) | `4408e6d741c6008044584c0d7235e608c596e836d51346ee773589d9b4589fdc` + +## Changelog since v1.9.0 + +### Other notable changes + +* Compare correct file names for volume detach operation ([#57053](https://github.com/kubernetes/kubernetes/pull/57053), [@prashima](https://github.com/prashima)) +* Fixed a garbage collection race condition where objects with ownerRefs pointing to cluster-scoped objects could be deleted incorrectly. ([#57211](https://github.com/kubernetes/kubernetes/pull/57211), [@liggitt](https://github.com/liggitt)) +* Free up CPU and memory requested but unused by Metrics Server Pod Nanny. ([#57252](https://github.com/kubernetes/kubernetes/pull/57252), [@kawych](https://github.com/kawych)) +* Configurable liveness probe initial delays for etcd and kube-apiserver in GCE ([#57749](https://github.com/kubernetes/kubernetes/pull/57749), [@wojtek-t](https://github.com/wojtek-t)) +* Fixed garbage collection hang ([#57503](https://github.com/kubernetes/kubernetes/pull/57503), [@liggitt](https://github.com/liggitt)) +* GCE: Fixes ILB creation on automatic networks with manually created subnetworks. ([#57351](https://github.com/kubernetes/kubernetes/pull/57351), [@nicksardo](https://github.com/nicksardo)) +* Check for known manifests during preflight instead of only checking for non-empty manifests directory. ([#57287](https://github.com/kubernetes/kubernetes/pull/57287), [@mattkelly](https://github.com/mattkelly)) +* enable flexvolume on Windows node ([#56921](https://github.com/kubernetes/kubernetes/pull/56921), [@andyzhangx](https://github.com/andyzhangx)) +* change default azure file/dir mode to 0755 ([#56551](https://github.com/kubernetes/kubernetes/pull/56551), [@andyzhangx](https://github.com/andyzhangx)) +* fix incorrect error info when creating an azure file PVC failed ([#56550](https://github.com/kubernetes/kubernetes/pull/56550), [@andyzhangx](https://github.com/andyzhangx)) +* Retry 'connection refused' errors when setting up clusters on GCE. ([#57394](https://github.com/kubernetes/kubernetes/pull/57394), [@mborsz](https://github.com/mborsz)) +* Fixes issue creating docker secrets with kubectl 1.9 for accessing docker private registries. ([#57463](https://github.com/kubernetes/kubernetes/pull/57463), [@dims](https://github.com/dims)) +* Fixes a bug where if an error was returned that was not an `autorest.DetailedError` we would return `"not found", nil` which caused nodes to go to `NotReady` state. ([#57484](https://github.com/kubernetes/kubernetes/pull/57484), [@brendandburns](https://github.com/brendandburns)) +* Fix Heapster configuration and Metrics Server configuration to enable overriding default resource requirements. ([#56965](https://github.com/kubernetes/kubernetes/pull/56965), [@kawych](https://github.com/kawych)) + + + +# v1.9.0 + +[Documentation](https://docs.k8s.io) & [Examples](https://releases.k8s.io/release-1.9/examples) + +## Downloads for v1.9.0 + + +filename | sha256 hash +-------- | ----------- +[kubernetes.tar.gz](https://dl.k8s.io/v1.9.0/kubernetes.tar.gz) | `d8a52a97382a418b69d46a8b3946bd95c404e03a2d50489d16b36517c9dbc7f4` +[kubernetes-src.tar.gz](https://dl.k8s.io/v1.9.0/kubernetes-src.tar.gz) | `95d35ad7d274e5ed207674983c3e8ec28d8190c17e635ee922e2af8349fb031b` + +### Client Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-client-darwin-386.tar.gz](https://dl.k8s.io/v1.9.0/kubernetes-client-darwin-386.tar.gz) | `2646aa4badf9281b42b921c1e9e2ed235e1305d331423f252a3380396e0c383f` +[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.9.0/kubernetes-client-darwin-amd64.tar.gz) | `e76e69cf58399c10908afce8bb8d1f12cb8811de7b24e657e5f9fc80e7b9b6fb` +[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.9.0/kubernetes-client-linux-386.tar.gz) | `bcd5ca428eb78fdaadbcf9ff78d9cbcbf70585a2d2582342a4460e55f3bbad13` +[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.9.0/kubernetes-client-linux-amd64.tar.gz) | `ba96c8e71dba68b1b3abcad769392fb4df53e402cb65ef25cd176346ee2c39e8` +[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.9.0/kubernetes-client-linux-arm64.tar.gz) | `80ceae744fbbfc7759c3d95999075f98e5d86d80e53ea83d16fa8e849da4073d` +[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.9.0/kubernetes-client-linux-arm.tar.gz) | `86b271e2518230f3502708cbe8f188a3a68b913c812247b8cc6fbb4c9f35f6c8` +[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.9.0/kubernetes-client-linux-ppc64le.tar.gz) | `8b7506ab64ceb2ff470120432d7a6a93adf14e14e612b3c53b3c238d334b55e2` +[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.9.0/kubernetes-client-linux-s390x.tar.gz) | `c066aa75a99c141410f9b9a78d230aff4a14dee472fe2b17729e902739798831` +[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.9.0/kubernetes-client-windows-386.tar.gz) | `a315535d6a64842a7c2efbf2bb876c0b73db7efd4c848812af07956c2446f526` +[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.9.0/kubernetes-client-windows-amd64.tar.gz) | `5d2ba1f008253da1a784c8bb5266d026fb6fdac5d22133b51e86d348dbaff49b` + +### Server Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.9.0/kubernetes-server-linux-amd64.tar.gz) | `a8d7be19e3b662681dc50dc0085ca12045979530a27d0200cf986ada3eff4d32` +[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.9.0/kubernetes-server-linux-arm64.tar.gz) | `8ef6ad23c60a50b4255ff41db044b2f5922e2a4b0332303065d9e66688a0b026` +[kubernetes-server-linux-arm.tar.gz](https://dl.k8s.io/v1.9.0/kubernetes-server-linux-arm.tar.gz) | `7cb99cf65553c9637ee6f55821ea3f778873a9912917ebbd6203e06d5effb055` +[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.9.0/kubernetes-server-linux-ppc64le.tar.gz) | `529b0f45a0fc688aa624aa2b850f28807ce2be3ac1660189f20cd3ae864ac064` +[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.9.0/kubernetes-server-linux-s390x.tar.gz) | `692f0c198da712f15ff93a4634c67f9105e3ec603240b50b51a84480ed63e987` + +### Node Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.9.0/kubernetes-node-linux-amd64.tar.gz) | `7ff3f526d1c4ec23516a65ecec3b947fd8f52d8c0605473b1a87159399dfeab1` +[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.9.0/kubernetes-node-linux-arm64.tar.gz) | `fada290471467c341734a3cfff63cd0f867aad95623b67096029d76c459bde06` +[kubernetes-node-linux-arm.tar.gz](https://dl.k8s.io/v1.9.0/kubernetes-node-linux-arm.tar.gz) | `ded3640bef5f9701f7f622de4ed162cd2e5a968e80a6a56b843ba84a0b146fac` +[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.9.0/kubernetes-node-linux-ppc64le.tar.gz) | `a83ebe3b360d33c2190bffd5bf0e2c68268ca2c85e3b5295c1a71ddb517a4f90` +[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.9.0/kubernetes-node-linux-s390x.tar.gz) | `1210efdf35ec5e0b2e96ff7e456e340684ff12dbea36aa255ac592ca7195e168` +[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.9.0/kubernetes-node-windows-amd64.tar.gz) | `9961ad142abc7e769bbe962aeb30a014065fae83291a2d65bc2da91f04fbf185` + +## 1.9 Release Notes + +## WARNING: etcd backup strongly recommended + +Before updating to 1.9, you are strongly recommended to back up your etcd data. Consult the installation procedure you are using (kargo, kops, kube-up, kube-aws, kubeadm etc) for specific advice. + +Some upgrade methods might upgrade etcd from 3.0 to 3.1 automatically when you upgrade from Kubernetes 1.8, unless you specify otherwise. Because [etcd does not support downgrading](https://coreos.com/etcd/docs/latest/upgrades/upgrade_3_1.html), you'll need to either remain on etcd 3.1 or restore from a backup if you want to downgrade back to Kubernetes 1.8. + +## Introduction to 1.9.0 + +Kubernetes version 1.9 includes new features and enhancements, as well as fixes to identified issues. The release notes contain a brief overview of the important changes introduced in this release. The content is organized by Special Interest Group ([SIG](https://github.com/kubernetes/community/blob/master/sig-list.md)). + +For initial installations, see the [Setup topics](https://kubernetes.io/docs/setup/pick-right-solution/) in the Kubernetes documentation. + +To upgrade to this release from a previous version, first take any actions required [Before Upgrading](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG-1.9.md#before-upgrading). + +For more information about this release and for the latest documentation, see the [Kubernetes documentation](https://kubernetes.io/docs/home/). + +## Major themes + +Kubernetes is developed by community members whose work is organized into +[Special Interest Groups](https://github.com/kubernetes/community/blob/master/sig-list.md), which provide the themes that guide their work. For the 1.9 release, these themes included: + +### API Machinery + +Extensibility. SIG API Machinery added a new class of admission control webhooks (mutating), and brought the admission control webhooks to beta. + +### Apps + +The core workloads API, which is composed of the DaemonSet, Deployment, ReplicaSet, and StatefulSet kinds, has been promoted to GA stability in the apps/v1 group version. As such, the apps/v1beta2 group version is deprecated, and all new code should use the kinds in the apps/v1 group version. + +### Auth + +SIG Auth focused on extension-related authorization improvements. Permissions can now be added to the built-in RBAC admin/edit/view roles using [cluster role aggregation](https://kubernetes.io/docs/admin/authorization/rbac/#aggregated-clusterroles). [Webhook authorizers](https://kubernetes.io/docs/admin/authorization/webhook/) can now deny requests and short-circuit checking subsequent authorizers. Performance and usability of the beta [PodSecurityPolicy](https://kubernetes.io/docs/concepts/policy/pod-security-policy/) feature was also improved. + +### AWS + +In v1.9 SIG AWS has improved stability of EBS support across the board. If a Volume is “stuck” in the attaching state to a node for too long a unschedulable taint will be applied to the node, so a Kubernetes admin can [take manual steps to correct the error](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-attaching-volume.html). Users are encouraged to ensure they are monitoring for the taint, and should consider automatically terminating instances in this state. + +In addition, support for NVMe disks has been added to Kubernetes, and a service of type LoadBalancer can now be backed with an NLB instead of an ELB (alpha). + +### Azure + +SIG Azure worked on improvements in the cloud provider, including significant work on the Azure Load Balancer implementation. + +### Cluster Lifecycle + +SIG Cluster Lifecycle has been focusing on improving kubeadm in order to bring it to GA in a future release, as well as developing the [Cluster API](https://github.com/kubernetes/kube-deploy/tree/master/cluster-api). For kubeadm, most new features, such as support for CoreDNS, IPv6 and Dynamic Kubelet Configuration, have gone in as alpha features. We expect to graduate these features to beta and beyond in the next release. The initial Cluster API spec and GCE sample implementation were developed from scratch during this cycle, and we look forward to stabilizing them into something production-grade during 2018. + +### Instrumentation + +In v1.9 we focused on improving stability of the components owned by the SIG, including Heapster, Custom Metrics API adapters for Prometheus, and Stackdriver. + +### Network + +In v1.9 SIG Network has implemented alpha support for IPv6, and alpha support for CoreDNS as a drop-in replacement for kube-dns. Additionally, SIG Network has begun the deprecation process for the extensions/v1beta1 NetworkPolicy API in favor of the networking.k8s.io/v1 equivalent. + +### Node + +SIG Node iterated on the ability to support more workloads with better performance and improved reliability. Alpha features were improved around hardware accelerator support, device plugins enablement, and cpu pinning policies to enable us to graduate these features to beta in a future release. In addition, a number of reliability and performance enhancements were made across the node to help operators in production. + +### OpenStack + +In this cycle, SIG OpenStack focused on configuration simplification through smarter defaults and the use of auto-detection wherever feasible (Block Storage API versions, Security Groups) as well as updating API support, including: + +* Block Storage (Cinder) V3 is now supported. +* Load Balancer (Octavia) V2 is now supported, in addition to Neutron LBaaS V2. +* Neutron LBaas V1 support has been removed. + +This work enables Kubernetes to take full advantage of the relevant services as exposed by OpenStack clouds. Refer to the [Cloud Providers](https://kubernetes.io/docs/concepts/cluster-administration/cloud-providers/#openstack) documentation for more information. + +### Storage + +[SIG Storage](https://github.com/kubernetes/community/tree/master/sig-storage) is responsible for storage and volume plugin components. + +For the 1.9 release, SIG Storage made Kubernetes more pluggable and modular by introducing an alpha implementation of the Container Storage Interface (CSI). CSI will make installing new volume plugins as easy as deploying a pod, and enable third-party storage providers to develop their plugins without the need to add code to the core Kubernetes codebase. + +The SIG also focused on adding functionality to the Kubernetes volume subsystem, such as alpha support for exposing volumes as block devices inside containers, extending the alpha volume-resizing support to more volume plugins, and topology-aware volume scheduling. + +### Windows + +We are advancing support for Windows Server and Windows Server Containers to beta along with continued feature and functional advancements on both the Kubernetes and Windows platforms. This opens the door for many Windows-specific applications and workloads to run on Kubernetes, significantly expanding the implementation scenarios and the enterprise reach of Kubernetes. + +## Before Upgrading + +Consider the following changes, limitations, and guidelines before you upgrade: + +### **API Machinery** + +* The admission API, which is used when the API server calls admission control webhooks, is moved from `admission.v1alpha1` to `admission.v1beta1`. You must **delete any existing webhooks before you upgrade** your cluster, and update them to use the latest API. This change is not backward compatible. +* The admission webhook configurations API, part of the admissionregistration API, is now at v1beta1. Delete any existing webhook configurations before you upgrade, and update your configuration files to use the latest API. For this and the previous change, see also [the documentation]([https://kubernetes.io/docs/admin/extensible-admission-controllers/#external-admission-webhooks](https://kubernetes.io/docs/admin/extensible-admission-controllers/#external-admission-webhooks)). +* A new `ValidatingAdmissionWebhook` is added (replacing `GenericAdmissionWebhook`) and is available in the generic API server. You must update your API server configuration file to pass the webhook to the `--admission-control` flag. ([#55988](https://github.com/kubernetes/kubernetes/pull/55988),[ @caesarxuchao](https://github.com/caesarxuchao)) ([#54513](https://github.com/kubernetes/kubernetes/pull/54513),[ @deads2k](https://github.com/deads2k)) +* The deprecated options `--portal-net` and `--service-node-ports` for the API server are removed. ([#52547](https://github.com/kubernetes/kubernetes/pull/52547),[ @xiangpengzhao](https://github.com/xiangpengzhao)) + +### **Auth** + +* PodSecurityPolicy: A compatibility issue with the allowPrivilegeEscalation field that caused policies to start denying pods they previously allowed was fixed. If you defined PodSecurityPolicy objects using a 1.8.0 client or server and set allowPrivilegeEscalation to false, these objects must be reapplied after you upgrade. ([#53443](https://github.com/kubernetes/kubernetes/pull/53443),[ @liggitt](https://github.com/liggitt)) +* KMS: Alpha integration with GCP KMS was removed in favor of a future out-of-process extension point. Discontinue use of the GCP KMS integration and ensure [data has been decrypted](https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/#decrypting-all-data) (or reencrypted with a different provider) before upgrading ([#54759](https://github.com/kubernetes/kubernetes/pull/54759),[ @sakshamsharma](https://github.com/sakshamsharma)) + +### **CLI** + +* Swagger 1.2 validation is removed for kubectl. The options `--use-openapi` and `--schema-cache-dir` are also removed because they are no longer needed. ([#53232](https://github.com/kubernetes/kubernetes/pull/53232),[ @apelisse](https://github.com/apelisse)) + +### **Cluster Lifecycle** + +* You must either specify the `--discovery-token-ca-cert-hash` flag to `kubeadm join`, or opt out of the CA pinning feature using `--discovery-token-unsafe-skip-ca-verification`. +* The default `auto-detect` behavior of the kubelet's `--cloud-provider` flag is removed. + * You can manually set `--cloud-provider=auto-detect`, but be aware that this behavior will be removed completely in a future version. + * Best practice for version 1.9 and future versions is to explicitly set a cloud-provider. See [the documentation](https://kubernetes.io/docs/getting-started-guides/scratch/#cloud-providers) +* The kubeadm `--skip-preflight-checks` flag is now deprecated and will be removed in a future release. +* If you are using the cloud provider API to determine the external host address of the apiserver, set `--external-hostname` explicitly instead. The cloud provider detection has been deprecated and will be removed in the future ([#54516](https://github.com/kubernetes/kubernetes/pull/54516),[ @dims](https://github.com/dims)) + +### **Multicluster** + +* Development of Kubernetes Federation has moved to [github.com/kubernetes/federation](github.com/kubernetes/federation). This move out of tree also means that Federation will begin releasing separately from Kubernetes. Impact: + * Federation-specific behavior will no longer be included in kubectl + * kubefed will no longer be released as part of Kubernetes + * The Federation servers will no longer be included in the hyperkube binary and image. ([#53816](https://github.com/kubernetes/kubernetes/pull/53816),[ @marun](https://github.com/marun)) + +### **Node** + +* The kubelet `--network-plugin-dir` flag is removed. This flag was deprecated in version 1.7, and is replaced with `--cni-bin-dir`. ([#53564](https://github.com/kubernetes/kubernetes/pull/53564),[ @supereagle](https://github.com/supereagle)) +* kubelet's `--cloud-provider` flag no longer defaults to "auto-detect". If you want cloud-provider support in kubelet, you must set a specific cloud-provider explicitly. ([#53573](https://github.com/kubernetes/kubernetes/pull/53573),[ @dims](https://github.com/dims)) + +### **Network** + +* NetworkPolicy objects are now stored in etcd in v1 format. After you upgrade to version 1.9, make sure that all NetworkPolicy objects are migrated to v1. ([#51955](https://github.com/kubernetes/kubernetes/pull/51955), [@danwinship](https://github.com/danwinship)) +* The API group/version for the kube-proxy configuration has changed from `componentconfig/v1alpha1` to `kubeproxy.config.k8s.io/v1alpha1`. If you are using a config file for kube-proxy instead of the command line flags, you must change its apiVersion to `kubeproxy.config.k8s.io/v1alpha1`. ([#53645](https://github.com/kubernetes/kubernetes/pull/53645), [@xiangpengzhao](https://github.com/xiangpengzhao)) +* The "ServiceNodeExclusion" feature gate must now be enabled for the `alpha.service-controller.kubernetes.io/exclude-balancer` annotation on nodes to be honored. ([#54644](https://github.com/kubernetes/kubernetes/pull/54644),[ @brendandburns](https://github.com/brendandburns)) + +### **Scheduling** + +* Taint key `unreachable` is now in GA. +* Taint key `notReady` is changed to `not-ready`, and is also now in GA. +* These changes are automatically updated for taints. Tolerations for these taints must be updated manually. Specifically, you must: + * Change `node.alpha.kubernetes.io/notReady` to `node.kubernetes.io/not-ready` + * Change `node.alpha.kubernetes.io/unreachable` to `node.kubernetes.io/unreachable` +* The `node.kubernetes.io/memory-pressure` taint now respects the configured whitelist. To use it, you must add it to the whitelist.([#55251](https://github.com/kubernetes/kubernetes/pull/55251),[ @deads2k](https://github.com/deads2k)) +* Refactor kube-scheduler configuration ([#52428](https://github.com/kubernetes/kubernetes/pull/52428)) + * The kube-scheduler command now supports a --config flag which is the location of a file containing a serialized scheduler configuration. Most other kube-scheduler flags are now deprecated. ([#52562](https://github.com/kubernetes/kubernetes/pull/52562),[ @ironcladlou](https://github.com/ironcladlou)) +* Opaque integer resources (OIR), which were (deprecated in v1.8.), have been removed. ([#55103](https://github.com/kubernetes/kubernetes/pull/55103),[ @ConnorDoyle](https://github.com/ConnorDoyle)) + +### **Storage** + +* [alpha] The LocalPersistentVolumes alpha feature now also requires the VolumeScheduling alpha feature. This is a breaking change, and the following changes are required: + * The VolumeScheduling feature gate must also be enabled on kube-scheduler and kube-controller-manager components. + * The NoVolumeNodeConflict predicate has been removed. For non-default schedulers, update your scheduler policy. + * The CheckVolumeBinding predicate must be enabled in non-default schedulers. ([#55039](https://github.com/kubernetes/kubernetes/pull/55039),[ @msau42](https://github.com/msau42)) + +### **OpenStack** + +* Remove the LbaasV1 of OpenStack cloud provider, currently only support LbaasV2. ([#52717](https://github.com/kubernetes/kubernetes/pull/52717),[ @FengyunPan](https://github.com/FengyunPan)) + +## Known Issues + +This section contains a list of known issues reported in Kubernetes 1.9 release. The content is populated from the [v1.9.x known issues and FAQ accumulator](https://github.com/kubernetes/kubernetes/issues/57159](https://github.com/kubernetes/kubernetes/issues/57159). + +* If you are adding Windows Server Virtual Machines as nodes to your Kubernetes environment, there is a compatibility issue with certain virtualization products. Specifically the Windows version of the kubelet.exe calls `GetPhysicallyInstalledSystemMemory` to get the physical memory installed on Windows machines and reports it as part of node metrics to heapster. This API call fails for VMware and VirtualBox virtualization environments. This issue is not present in bare metal Windows deployments, in Hyper-V, or on some of the popular public cloud providers. + +* If you run `kubectl get po` while the API server in unreachable, a misleading error is returned: `the server doesn't have a resource type "po"`. To work around this issue, specify the full resource name in the command instead of the abbreviation: `kubectl get pods`. This issue will be fixed in a future release. + + For more information, see [#57198](https://github.com/kubernetes/kubernetes/issues/57198). + +* Mutating and validating webhook configurations are continuously polled by the API server (once per second). This issue will be fixed in a future release. + + For more information, see [#56357](https://github.com/kubernetes/kubernetes/issues/56357). + +* Audit logging is slow because writes to the log are performed synchronously with requests to the log. This issue will be fixed in a future release. + + For more information, see [#53006](https://github.com/kubernetes/kubernetes/issues/53006). + +* Custom Resource Definitions (CRDs) are not properly deleted under certain conditions. This issue will be fixed in a future release. + + For more information, see [#56348](https://github.com/kubernetes/kubernetes/issues/56348). + +* API server times out after performing a rolling update of the etcd cluster. This issue will be fixed in a future release. + + For more information, see [#47131](https://github.com/kubernetes/kubernetes/issues/47131) + +* If a namespaced resource is owned by a cluster scoped resource, and the namespaced dependent is processed before the cluster scoped owner has ever been observed by the garbage collector, the dependent will be erroneously deleted. + + For more information, see [#54940](https://github.com/kubernetes/kubernetes/issues/54940) + +## Deprecations + +This section provides an overview of deprecated API versions, options, flags, and arguments. Deprecated means that we intend to remove the capability from a future release. After removal, the capability will no longer work. The sections are organized by SIGs. + +### **API Machinery** + +* The kube-apiserver `--etcd-quorum-read` flag is deprecated and the ability to switch off quorum read will be removed in a future release. ([#53795](https://github.com/kubernetes/kubernetes/pull/53795),[ @xiangpengzhao](https://github.com/xiangpengzhao)) +* The `/ui` redirect in kube-apiserver is deprecated and will be removed in Kubernetes 1.10. ([#53046](https://github.com/kubernetes/kubernetes/pull/53046), [@maciaszczykm](https://github.com/maciaszczykm)) +* `etcd2` as a backend is deprecated and support will be removed in Kubernetes 1.13 or 1.14. + +### **Auth** + +* Default controller-manager options for `--cluster-signing-cert-file` and `--cluster-signing-key-file` are deprecated and will be removed in a future release. ([#54495](https://github.com/kubernetes/kubernetes/pull/54495),[ @mikedanese](https://github.com/mikedanese)) +* RBAC objects are now stored in etcd in v1 format. After upgrading to 1.9, ensure all RBAC objects (Roles, RoleBindings, ClusterRoles, ClusterRoleBindings) are at v1. v1alpha1 support is deprecated and will be removed in a future release. ([#52950](https://github.com/kubernetes/kubernetes/pull/52950),[ @liggitt](https://github.com/liggitt)) + +### **Cluster Lifecycle** + +* kube-apiserver: `--ssh-user` and `--ssh-keyfile` are now deprecated and will be removed in a future release. Users of SSH tunnel functionality in Google Container Engine for the Master -> Cluster communication should plan alternate methods for bridging master and node networks. ([#54433](https://github.com/kubernetes/kubernetes/pull/54433),[ @dims](https://github.com/dims)) +* The kubeadm `--skip-preflight-checks` flag is now deprecated and will be removed in a future release. +* If you are using the cloud provider API to determine the external host address of the apiserver, set `--external-hostname` explicitly instead. The cloud provider detection has been deprecated and will be removed in the future ([#54516](https://github.com/kubernetes/kubernetes/pull/54516),[ @dims](https://github.com/dims)) + +### **Network** + +* The NetworkPolicy extensions/v1beta1 API is now deprecated and will be removed in a future release. This functionality has been migrated to a dedicated v1 API - networking.k8s.io/v1. v1beta1 Network Policies can be upgraded to the v1 API with the [cluster/update-storage-objects.sh script](https://github.com/danwinship/kubernetes/blob/master/cluster/update-storage-objects.sh). Documentation can be found [here](https://kubernetes.io/docs/concepts/services-networking/network-policies/). ([#56425](https://github.com/kubernetes/kubernetes/pull/56425), [@cmluciano](https://github.com/cmluciano)) + +### **Storage** + +* The `volume.beta.kubernetes.io/storage-class` annotation is deprecated. It will be removed in a future release. For the StorageClass API object, use v1, and in place of the annotation use `v1.PersistentVolumeClaim.Spec.StorageClassName` and `v1.PersistentVolume.Spec.StorageClassName` instead. ([#53580](https://github.com/kubernetes/kubernetes/pull/53580),[ @xiangpengzhao](https://github.com/xiangpengzhao)) + +### **Scheduling** + +* The kube-scheduler command now supports a `--config` flag, which is the location of a file containing a serialized scheduler configuration. Most other kube-scheduler flags are now deprecated. ([#52562](https://github.com/kubernetes/kubernetes/pull/52562),[ @ironcladlou](https://github.com/ironcladlou)) + +### **Node** + +* The kubelet's `--enable-custom-metrics` flag is now deprecated. ([#54154](https://github.com/kubernetes/kubernetes/pull/54154),[ @mtaufen](https://github.com/mtaufen)) + +## Notable Changes + +### **Workloads API (apps/v1)** + +As announced with the release of version 1.8, the Kubernetes Workloads API is at v1 in version 1.9. This API consists of the DaemonSet, Deployment, ReplicaSet and StatefulSet kinds. + +### **API Machinery** + +#### **Admission Control** + +* Admission webhooks are now in beta, and include the following: + * Mutation support for admission webhooks. ([#54892](https://github.com/kubernetes/kubernetes/pull/54892),[ @caesarxuchao](https://github.com/caesarxuchao)) + * Webhook admission now takes a config file that describes how to authenticate to webhook servers ([#54414](https://github.com/kubernetes/kubernetes/pull/54414),[ @deads2k](https://github.com/deads2k)) + * The dynamic admission webhook now supports a URL in addition to a service reference, to accommodate out-of-cluster webhooks. ([#54889](https://github.com/kubernetes/kubernetes/pull/54889),[ @lavalamp](https://github.com/lavalamp)) + * Added `namespaceSelector` to `externalAdmissionWebhook` configuration to allow applying webhooks only to objects in the namespaces that have matching labels. ([#54727](https://github.com/kubernetes/kubernetes/pull/54727),[ @caesarxuchao](https://github.com/caesarxuchao)) +* Metrics are added for monitoring admission plugins, including the new dynamic (webhook-based) ones. ([#55183](https://github.com/kubernetes/kubernetes/pull/55183),[ @jpbetz](https://github.com/jpbetz)) +* The PodSecurityPolicy annotation kubernetes.io/psp on pods is set only once on create. ([#55486](https://github.com/kubernetes/kubernetes/pull/55486),[ @sttts](https://github.com/sttts)) + +#### **API & API server** + +* Fixed a bug related to discovery information for scale subresources in the apps API group ([#54683](https://github.com/kubernetes/kubernetes/pull/54683),[ @liggitt](https://github.com/liggitt)) +* Fixed a bug that prevented client-go metrics from being registered in Prometheus. This bug affected multiple components. ([#53434](https://github.com/kubernetes/kubernetes/pull/53434),[ @crassirostris](https://github.com/crassirostris)) + +#### **Audit** + +* Fixed a bug so that `kube-apiserver` now waits for open connections to finish before exiting. This fix provides graceful shutdown and ensures that the audit backend no longer drops events on shutdown. ([#53695](https://github.com/kubernetes/kubernetes/pull/53695),[ @hzxuzhonghu](https://github.com/hzxuzhonghu)) +* Webhooks now always retry sending if a connection reset error is returned. ([#53947](https://github.com/kubernetes/kubernetes/pull/53947),[ @crassirostris](https://github.com/crassirostris)) + +#### **Custom Resources** + +* Validation of resources defined by a Custom Resource Definition (CRD) is now in beta ([#54647](https://github.com/kubernetes/kubernetes/pull/54647),[ @colemickens](https://github.com/colemickens)) +* An example CRD controller has been added, at [github.com/kubernetes/sample-controller](github.com/kubernetes/sample-controller). ([#52753](https://github.com/kubernetes/kubernetes/pull/52753),[ @munnerz](https://github.com/munnerz)) +* Custom resources served by CustomResourceDefinition objects now support field selectors for `metadata.name` and `metadata.namespace`. Also fixed an issue with watching a single object; earlier versions could watch only a collection, and so a watch on an instance would fail. ([#53345](https://github.com/kubernetes/kubernetes/pull/53345),[ @ncdc](https://github.com/ncdc)) + +#### **Other** + +* `kube-apiserver` now runs with the default value for `service-cluster-ip-range` ([#52870](https://github.com/kubernetes/kubernetes/pull/52870),[ @jennybuckley](https://github.com/jennybuckley)) +* Add `--etcd-compaction-interval` to apiserver for controlling request of compaction to etcd3 from apiserver. ([#51765](https://github.com/kubernetes/kubernetes/pull/51765),[ @mitake](https://github.com/mitake)) +* The httpstream/spdy calls now support CIDR notation for NO_PROXY ([#54413](https://github.com/kubernetes/kubernetes/pull/54413),[ @kad](https://github.com/kad)) +* Code generation for CRD and User API server types is improved with the addition of two new scripts to k8s.io/code-generator: `generate-groups.sh` and `generate-internal-groups.sh`. ([#52186](https://github.com/kubernetes/kubernetes/pull/52186),[ @sttts](https://github.com/sttts)) +* [beta] Flag `--chunk-size={SIZE}` is added to `kubectl get` to customize the number of results returned in large lists of resources. This reduces the perceived latency of managing large clusters because the server returns the first set of results to the client much more quickly. Pass 0 to disable this feature.([#53768](https://github.com/kubernetes/kubernetes/pull/53768),[ @smarterclayton](https://github.com/smarterclayton)) +* [beta] API chunking via the limit and continue request parameters is promoted to beta in this release. Client libraries using the Informer or ListWatch types will automatically opt in to chunking. ([#52949](https://github.com/kubernetes/kubernetes/pull/52949),[ @smarterclayton](https://github.com/smarterclayton)) +* The `--etcd-quorum-read` flag now defaults to true to ensure correct operation with HA etcd clusters. This flag is deprecated and the flag will be removed in future versions, as well as the ability to turn off this functionality. ([#53717](https://github.com/kubernetes/kubernetes/pull/53717),[ @liggitt](https://github.com/liggitt)) +* Add events.k8s.io api group with v1beta1 API containing redesigned event type. ([#49112](https://github.com/kubernetes/kubernetes/pull/49112),[ @gmarek](https://github.com/gmarek)) +* Fixed a bug where API discovery failures were crashing the kube controller manager via the garbage collector. ([#55259](https://github.com/kubernetes/kubernetes/pull/55259),[ @ironcladlou](https://github.com/ironcladlou)) +* `conversion-gen` is now usable in a context without a vendored k8s.io/kubernetes. The Kubernetes core API is removed from `default extra-peer-dirs`. ([#54394](https://github.com/kubernetes/kubernetes/pull/54394),[ @sttts](https://github.com/sttts)) +* Fixed a bug where the `client-gen` tag for code-generator required a newline between a comment block and a statement. tag shortcomings when newline is omitted ([#53893](https://github.com/kubernetes/kubernetes/pull/53893)) ([#55233](https://github.com/kubernetes/kubernetes/pull/55233),[ @sttts](https://github.com/sttts)) +* The Apiserver proxy now rewrites the URL when a service returns an absolute path with the request's host. ([#52556](https://github.com/kubernetes/kubernetes/pull/52556),[ @roycaihw](https://github.com/roycaihw)) +* The gRPC library is updated to pick up data race fix ([#53124](https://github.com/kubernetes/kubernetes/pull/53124)) ([#53128](https://github.com/kubernetes/kubernetes/pull/53128),[ @dixudx](https://github.com/dixudx)) +* Fixed server name verification of aggregated API servers and webhook admission endpoints ([#56415](https://github.com/kubernetes/kubernetes/pull/56415),[ @liggitt](https://github.com/liggitt)) + +### **Apps** + +* The `kubernetes.io/created-by` annotation is no longer added to controller-created objects. Use the `metadata.ownerReferences` item with controller set to `true` to determine which controller, if any, owns an object. ([#54445](https://github.com/kubernetes/kubernetes/pull/54445),[ @crimsonfaith91](https://github.com/crimsonfaith91)) +* StatefulSet controller now creates a label for each Pod in a StatefulSet. The label is `statefulset.kubernetes.io/pod-name`, where `pod-name` = the name of the Pod. This allows users to create a Service per Pod to expose a connection to individual Pods. ([#55329](https://github.com/kubernetes/kubernetes/pull/55329),[ @kow3ns](https://github.com/kow3ns)) +* DaemonSet status includes a new field named `conditions`, making it consistent with other workloads controllers. ([#55272](https://github.com/kubernetes/kubernetes/pull/55272),[ @janetkuo](https://github.com/janetkuo)) +* StatefulSet status now supports conditions, making it consistent with other core controllers in v1 ([#55268](https://github.com/kubernetes/kubernetes/pull/55268),[ @foxish](https://github.com/foxish)) +* The default garbage collection policy for Deployment, DaemonSet, StatefulSet, and ReplicaSet has changed from OrphanDependents to DeleteDependents when the deletion is requested through an `apps/v1` endpoint. ([#55148](https://github.com/kubernetes/kubernetes/pull/55148),[ @dixudx](https://github.com/dixudx)) + * Clients using older endpoints will be unaffected. This change is only at the REST API level and is independent of the default behavior of particular clients (e.g. this does not affect the default for the kubectl `--cascade` flag). + * If you upgrade your client-go libs and use the `AppsV1()` interface, please note that the default garbage collection behavior is changed. + +### **Auth** + +#### **Audit** + +* RequestReceivedTimestamp and StageTimestamp are added to audit events ([#52981](https://github.com/kubernetes/kubernetes/pull/52981),[ @CaoShuFeng](https://github.com/CaoShuFeng)) +* Advanced audit policy now supports a policy wide omitStage ([#54634](https://github.com/kubernetes/kubernetes/pull/54634),[ @CaoShuFeng](https://github.com/CaoShuFeng)) + +#### **RBAC** + +* New permissions have been added to default RBAC roles ([#52654](https://github.com/kubernetes/kubernetes/pull/52654),[ @liggitt](https://github.com/liggitt)): + * The default admin and edit roles now include read/write permissions + * The view role includes read permissions on poddisruptionbudget.policy resources. +* RBAC rules can now match the same subresource on any resource using the form `*/(subresource)`. For example, `*/scale` matches requests to `replicationcontroller/scale`. ([#53722](https://github.com/kubernetes/kubernetes/pull/53722),[ @deads2k](https://github.com/deads2k)) +* The RBAC bootstrapping policy now allows authenticated users to create selfsubjectrulesreviews. ([#56095](https://github.com/kubernetes/kubernetes/pull/56095),[ @ericchiang](https://github.com/ericchiang)) +* RBAC ClusterRoles can now select other roles to aggregate. ([#54005](https://github.com/kubernetes/kubernetes/pull/54005),[ @deads2k](https://github.com/deads2k)) +* Fixed an issue with RBAC reconciliation that caused duplicated subjects in some bootstrapped RoleBinding objects on each restart of the API server. ([#53239](https://github.com/kubernetes/kubernetes/pull/53239),[ @enj](https://github.com/enj)) + +#### **Other** + +* Pod Security Policy can now manage access to specific FlexVolume drivers ([#53179](https://github.com/kubernetes/kubernetes/pull/53179),[ @wanghaoran1988](https://github.com/wanghaoran1988)) +* Audit policy files without apiVersion and kind are treated as invalid. ([#54267](https://github.com/kubernetes/kubernetes/pull/54267),[ @ericchiang](https://github.com/ericchiang)) +* Fixed a bug that where forbidden errors were encountered when accessing ReplicaSet and DaemonSets objects via the apps API group. ([#54309](https://github.com/kubernetes/kubernetes/pull/54309),[ @liggitt](https://github.com/liggitt)) +* Improved PodSecurityPolicy admission latency. ([#55643](https://github.com/kubernetes/kubernetes/pull/55643),[ @tallclair](https://github.com/tallclair)) +* kube-apiserver: `--oidc-username-prefix` and `--oidc-group-prefix` flags are now correctly enabled. ([#56175](https://github.com/kubernetes/kubernetes/pull/56175),[ @ericchiang](https://github.com/ericchiang)) +* If multiple PodSecurityPolicy objects allow a submitted pod, priority is given to policies that do not require default values for any fields in the pod spec. If default values are required, the first policy ordered by name that allows the pod is used. ([#52849](https://github.com/kubernetes/kubernetes/pull/52849),[ @liggitt](https://github.com/liggitt)) +* A new controller automatically cleans up Certificate Signing Requests that are Approved and Issued, or Denied. ([#51840](https://github.com/kubernetes/kubernetes/pull/51840),[ @jcbsmpsn](https://github.com/jcbsmpsn)) +* PodSecurityPolicies have been added for all in-tree cluster addons ([#55509](https://github.com/kubernetes/kubernetes/pull/55509),[ @tallclair](https://github.com/tallclair)) + +#### **GCE** + +* Added support for PodSecurityPolicy on GCE: `ENABLE_POD_SECURITY_POLICY=true` enables the admission controller, and installs policies for default addons. ([#52367](https://github.com/kubernetes/kubernetes/pull/52367),[ @tallclair](https://github.com/tallclair)) + +### **Autoscaling** + +* HorizontalPodAutoscaler objects now properly functions on scalable resources in any API group. Fixed by adding a polymorphic scale client. ([#53743](https://github.com/kubernetes/kubernetes/pull/53743),[ @DirectXMan12](https://github.com/DirectXMan12)) +* Fixed a set of minor issues with Cluster Autoscaler 1.0.1 ([#54298](https://github.com/kubernetes/kubernetes/pull/54298),[ @mwielgus](https://github.com/mwielgus)) +* HPA tolerance is now configurable by setting the `horizontal-pod-autoscaler-tolerance` flag. ([#52275](https://github.com/kubernetes/kubernetes/pull/52275),[ @mattjmcnaughton](https://github.com/mattjmcnaughton)) +* Fixed a bug that allowed the horizontal pod autoscaler to allocate more `desiredReplica` objects than `maxReplica` objects in certain instances. ([#53690](https://github.com/kubernetes/kubernetes/pull/53690),[ @mattjmcnaughton](https://github.com/mattjmcnaughton)) + +### **AWS** + +* Nodes can now use instance types (such as C5) that use NVMe. ([#56607](https://github.com/kubernetes/kubernetes/pull/56607), [@justinsb](https://github.com/justinsb)) +* Nodes are now unreachable if volumes are stuck in the attaching state. Implemented by applying a taint to the node. ([#55558](https://github.com/kubernetes/kubernetes/pull/55558),[ @gnufied](https://github.com/gnufied)) +* Volumes are now checked for available state before attempting to attach or delete a volume in EBS. ([#55008](https://github.com/kubernetes/kubernetes/pull/55008),[ @gnufied](https://github.com/gnufied)) +* Fixed a bug where error log messages were breaking into two lines. ([#49826](https://github.com/kubernetes/kubernetes/pull/49826),[ @dixudx](https://github.com/dixudx)) +* Fixed a bug so that volumes are now detached from stopped nodes. ([#55893](https://github.com/kubernetes/kubernetes/pull/55893),[ @gnufied](https://github.com/gnufied)) +* You can now override the health check parameters for AWS ELBs by specifying annotations on the corresponding service. The new annotations are: `healthy-threshold`, `unhealthy-threshold`, `timeout`, `interval`. The prefix for all annotations is `service.beta.kubernetes.io/aws-load-balancer-healthcheck-`. ([#56024](https://github.com/kubernetes/kubernetes/pull/56024),[ @dimpavloff](https://github.com/dimpavloff)) +* Fixed a bug so that AWS ECR credentials are now supported in the China region. ([#50108](https://github.com/kubernetes/kubernetes/pull/50108),[ @zzq889](https://github.com/zzq889)) +* Added Amazon NLB support ([#53400](https://github.com/kubernetes/kubernetes/pull/53400),[ @micahhausler](https://github.com/micahhausler)) +* Additional annotations are now properly set or updated for AWS load balancers ([#55731](https://github.com/kubernetes/kubernetes/pull/55731),[ @georgebuckerfield](https://github.com/georgebuckerfield)) +* AWS SDK is updated to version 1.12.7 ([#53561](https://github.com/kubernetes/kubernetes/pull/53561),[ @justinsb](https://github.com/justinsb)) + +### **Azure** + +* Fixed several issues with properly provisioning Azure disk storage ([#55927](https://github.com/kubernetes/kubernetes/pull/55927),[ @andyzhangx](https://github.com/andyzhangx)) +* A new service annotation `service.beta.kubernetes.io/azure-dns-label-name` now sets the Azure DNS label for a public IP address. ([#47849](https://github.com/kubernetes/kubernetes/pull/47849),[ @tomerf](https://github.com/tomerf)) +* Support for GetMountRefs function added; warning messages no longer displayed. ([#54670](https://github.com/kubernetes/kubernetes/pull/54670), [#52401](https://github.com/kubernetes/kubernetes/pull/52401),[ @andyzhangx](https://github.com/andyzhangx)) +* Fixed an issue where an Azure PersistentVolume object would crash because the value of `volumeSource.ReadOnly` was set to nil. ([#54607](https://github.com/kubernetes/kubernetes/pull/54607),[ @andyzhangx](https://github.com/andyzhangx)) +* Fixed an issue with Azure disk mount failures on CoreOS and some other distros ([#54334](https://github.com/kubernetes/kubernetes/pull/54334),[ @andyzhangx](https://github.com/andyzhangx)) +* GRS, RAGRS storage account types are now supported for Azure disks. ([#55931](https://github.com/kubernetes/kubernetes/pull/55931),[ @andyzhangx](https://github.com/andyzhangx)) +* Azure NSG rules are now restricted so that external access is allowed only to the load balancer IP. ([#54177](https://github.com/kubernetes/kubernetes/pull/54177),[ @itowlson](https://github.com/itowlson)) +* Azure NSG rules can be consolidated to reduce the likelihood of hitting Azure resource limits (available only in regions where the Augmented Security Groups preview is available). ([#55740](https://github.com/kubernetes/kubernetes/pull/55740), [@itowlson](https://github.com/itowlson)) +* The Azure SDK is upgraded to v11.1.1. ([#54971](https://github.com/kubernetes/kubernetes/pull/54971),[ @itowlson](https://github.com/itowlson)) +* You can now create Windows mount paths ([#51240](https://github.com/kubernetes/kubernetes/pull/51240),[ @andyzhangx](https://github.com/andyzhangx)) +* Fixed a controller manager crash issue on a manually created k8s cluster. ([#53694](https://github.com/kubernetes/kubernetes/pull/53694),[ @andyzhangx](https://github.com/andyzhangx)) +* Azure-based clusters now support unlimited mount points. ([#54668](https://github.com/kubernetes/kubernetes/pull/54668)) ([#53629](https://github.com/kubernetes/kubernetes/pull/53629),[ @andyzhangx](https://github.com/andyzhangx)) +* Load balancer reconciliation now considers NSG rules based not only on Name, but also on Protocol, SourcePortRange, DestinationPortRange, SourceAddressPrefix, DestinationAddressPrefix, Access, and Direction. This change makes it possible to update NSG rules under more conditions. ([#55752](https://github.com/kubernetes/kubernetes/pull/55752),[ @kevinkim9264](https://github.com/kevinkim9264)) +* Custom mountOptions for the azurefile StorageClass object are now respected. Specifically, `dir_mode` and `file_mode` can now be customized. ([#54674](https://github.com/kubernetes/kubernetes/pull/54674),[ @andyzhangx](https://github.com/andyzhangx)) +* Azure Load Balancer Auto Mode: Services can be annotated to allow auto selection of available load balancers and to provide specific availability sets that host the load balancers (for example, `service.beta.kubernetes.io/azure-load-balancer-mode=auto|as1,as2...`) + +### **CLI** + +#### **Kubectl** + +* `kubectl cp` can now copy a remote file into a local directory. ([#46762](https://github.com/kubernetes/kubernetes/pull/46762),[ @bruceauyeung](https://github.com/bruceauyeung)) +* `kubectl cp` now honors destination names for directories. A complete directory is now copied; in previous versions only the file contents were copied. ([#51215](https://github.com/kubernetes/kubernetes/pull/51215),[ @juanvallejo](https://github.com/juanvallejo)) +* You can now use `kubectl get` with a fieldSelector. ([#50140](https://github.com/kubernetes/kubernetes/pull/50140),[ @dixudx](https://github.com/dixudx)) +* Secret data containing Docker registry auth objects is now generated using the config.json format ([#53916](https://github.com/kubernetes/kubernetes/pull/53916),[ @juanvallejo](https://github.com/juanvallejo)) +* `kubectl apply` now calculates the diff between the current and new configurations based on the OpenAPI spec. If the OpenAPI spec is not available, it falls back to baked-in types. ([#51321](https://github.com/kubernetes/kubernetes/pull/51321),[ @mengqiy](https://github.com/mengqiy)) +* `kubectl explain` now explains `apiservices` and `customresourcedefinition`. (Updated to use OpenAPI instead of Swagger 1.2.) ([#53228](https://github.com/kubernetes/kubernetes/pull/53228),[ @apelisse](https://github.com/apelisse)) +* `kubectl get` now uses OpenAPI schema extensions by default to select columns for custom types. ([#53483](https://github.com/kubernetes/kubernetes/pull/53483),[ @apelisse](https://github.com/apelisse)) +* kubectl `top node` now sorts by name and `top pod` sorts by namespace. Fixed a bug where results were inconsistently sorted. ([#53560](https://github.com/kubernetes/kubernetes/pull/53560),[ @dixudx](https://github.com/dixudx)) +* Added --dry-run option to kubectl drain. ([#52440](https://github.com/kubernetes/kubernetes/pull/52440),[ @juanvallejo](https://github.com/juanvallejo)) +* Kubectl now outputs for columns specified by -o custom-columns but not found in object, rather than "xxx is not found" ([#51750](https://github.com/kubernetes/kubernetes/pull/51750),[ @jianhuiz](https://github.com/jianhuiz)) +* `kubectl create pdb` no longer sets the min-available field by default. ([#53047](https://github.com/kubernetes/kubernetes/pull/53047),[ @yuexiao-wang](https://github.com/yuexiao-wang)) +* The canonical pronunciation of kubectl is "cube control". +* Added --raw to kubectl create to POST using the normal transport. ([#54245](https://github.com/kubernetes/kubernetes/pull/54245),[ @deads2k](https://github.com/deads2k)) +* Added kubectl `create priorityclass` subcommand ([#54858](https://github.com/kubernetes/kubernetes/pull/54858),[ @wackxu](https://github.com/wackxu)) +* Fixed an issue where `kubectl set` commands occasionally encountered conversion errors for ReplicaSet and DaemonSet objects ([#53158](https://github.com/kubernetes/kubernetes/pull/53158),[ @liggitt](https://github.com/liggitt)) + +### **Cluster Lifecycle** + +#### **API Server** + +* [alpha] Added an `--endpoint-reconciler-type` command-line argument to select the endpoint reconciler to use. The default is to use the 'master-count' reconciler which is the default for 1.9 and in use prior to 1.9. The 'lease' reconciler stores endpoints within the storage api for better cleanup of deleted (or removed) API servers. The 'none' reconciler is a no-op reconciler, which can be used in self-hosted environments. ([#51698](https://github.com/kubernetes/kubernetes/pull/51698), [@rphillips](https://github.com/rphillips)) + +#### **Cloud Provider Integration** + +* Added `cloud-controller-manager` to `hyperkube`. This is useful as a number of deployment tools run all of the kubernetes components from the `hyperkube `image/binary. It also makes testing easier as a single binary/image can be built and pushed quickly. ([#54197](https://github.com/kubernetes/kubernetes/pull/54197),[ @colemickens](https://github.com/colemickens)) +* Added the concurrent service sync flag to the Cloud Controller Manager to allow changing the number of workers. (`--concurrent-service-syncs`) ([#55561](https://github.com/kubernetes/kubernetes/pull/55561),[ @jhorwit2](https://github.com/jhorwit2)) +* kubelet's --cloud-provider flag no longer defaults to "auto-detect". If you want cloud-provider support in kubelet, you must set a specific cloud-provider explicitly. ([#53573](https://github.com/kubernetes/kubernetes/pull/53573),[ @dims](https://github.com/dims)) + +#### **Kubeadm** + +* kubeadm health checks can now be skipped with `--ignore-preflight-errors`; the `--skip-preflight-checks` flag is now deprecated and will be removed in a future release. ([#56130](https://github.com/kubernetes/kubernetes/pull/56130),[ @anguslees](https://github.com/anguslees)) ([#56072](https://github.com/kubernetes/kubernetes/pull/56072),[ @kad](https://github.com/kad)) +* You now have the option to use CoreDNS instead of KubeDNS. To install CoreDNS instead of kube-dns, set CLUSTER_DNS_CORE_DNS to 'true'. This support is experimental. ([#52501](https://github.com/kubernetes/kubernetes/pull/52501),[ @rajansandeep](https://github.com/rajansandeep)) ([#55728](https://github.com/kubernetes/kubernetes/pull/55728),[ @rajansandeep](https://github.com/rajansandeep)) +* Added --print-join-command flag for kubeadm token create. ([#56185](https://github.com/kubernetes/kubernetes/pull/56185),[ @mattmoyer](https://github.com/mattmoyer)) +* Added a new --etcd-upgrade keyword to kubeadm upgrade apply. When this keyword is specified, etcd's static pod gets upgraded to the etcd version officially recommended for a target kubernetes release. ([#55010](https://github.com/kubernetes/kubernetes/pull/55010),[ @sbezverk](https://github.com/sbezverk)) +* Kubeadm now supports Kubelet Dynamic Configuration on an alpha level. ([#55803](https://github.com/kubernetes/kubernetes/pull/55803),[ @xiangpengzhao](https://github.com/xiangpengzhao)) +* Added support for adding a Windows node ([#53553](https://github.com/kubernetes/kubernetes/pull/53553),[ @bsteciuk](https://github.com/bsteciuk)) + +#### **Juju** + +* Added support for SAN entries in the master node certificate. ([#54234](https://github.com/kubernetes/kubernetes/pull/54234),[ @hyperbolic2346](https://github.com/hyperbolic2346)) +* Add extra-args configs for scheduler and controller-manager to kubernetes-master charm ([#55185](https://github.com/kubernetes/kubernetes/pull/55185),[ @Cynerva](https://github.com/Cynerva)) +* Add support for RBAC ([#53820](https://github.com/kubernetes/kubernetes/pull/53820),[ @ktsakalozos](https://github.com/ktsakalozos)) +* Fixed iptables FORWARD policy for Docker 1.13 in kubernetes-worker charm ([#54796](https://github.com/kubernetes/kubernetes/pull/54796),[ @Cynerva](https://github.com/Cynerva)) +* Upgrading the kubernetes-master units now results in staged upgrades just like the kubernetes-worker nodes. Use the upgrade action in order to continue the upgrade process on each unit such as juju run-action kubernetes-master/0 upgrade ([#55990](https://github.com/kubernetes/kubernetes/pull/55990),[ @hyperbolic2346](https://github.com/hyperbolic2346)) +* Added extra_sans config option to kubeapi-load-balancer charm. This allows the user to specify extra SAN entries on the certificate generated for the load balancer. ([#54947](https://github.com/kubernetes/kubernetes/pull/54947),[ @hyperbolic2346](https://github.com/hyperbolic2346)) +* Added extra-args configs to kubernetes-worker charm ([#55334](https://github.com/kubernetes/kubernetes/pull/55334),[ @Cynerva](https://github.com/Cynerva)) + +#### **Other** + +* Base images have been bumped to Debian Stretch (9) ([#52744](https://github.com/kubernetes/kubernetes/pull/52744),[ @rphillips](https://github.com/rphillips)) +* Upgraded to go1.9. ([#51375](https://github.com/kubernetes/kubernetes/pull/51375),[ @cblecker](https://github.com/cblecker)) +* Add-on manager now supports HA masters. ([#55466](https://github.com/kubernetes/kubernetes/pull/55466),[ #55782](https://github.com/x13n),[ @x13n](https://github.com/x13n)) +* Hyperkube can now run from a non-standard path. ([#54570](https://github.com/kubernetes/kubernetes/pull/54570)) + +#### **GCP** + +* The service account made available on your nodes is now configurable. ([#52868](https://github.com/kubernetes/kubernetes/pull/52868),[ @ihmccreery](https://github.com/ihmccreery)) +* GCE nodes with NVIDIA GPUs attached now expose nvidia.com/gpu as a resource instead of alpha.kubernetes.io/nvidia-gpu. ([#54826](https://github.com/kubernetes/kubernetes/pull/54826),[ @mindprince](https://github.com/mindprince)) +* Docker's live-restore on COS/ubuntu can now be disabled ([#55260](https://github.com/kubernetes/kubernetes/pull/55260),[ @yujuhong](https://github.com/yujuhong)) +* Metadata concealment is now controlled by the ENABLE_METADATA_CONCEALMENT env var. See cluster/gce/config-default.sh for more info. ([#54150](https://github.com/kubernetes/kubernetes/pull/54150),[ @ihmccreery](https://github.com/ihmccreery)) +* Masquerading rules are now added by default to GCE/GKE ([#55178](https://github.com/kubernetes/kubernetes/pull/55178),[ @dnardo](https://github.com/dnardo)) +* Fixed master startup issues with concurrent iptables invocations. ([#55945](https://github.com/kubernetes/kubernetes/pull/55945),[ @x13n](https://github.com/x13n)) +* Fixed issue deleting internal load balancers when the firewall resource may not exist. ([#53450](https://github.com/kubernetes/kubernetes/pull/53450),[ @nicksardo](https://github.com/nicksardo)) + +### **Instrumentation** + +#### **Audit** + +* Adjust batching audit webhook default parameters: increase queue size, batch size, and initial backoff. Add throttling to the batching audit webhook. Default rate limit is 10 QPS. ([#53417](https://github.com/kubernetes/kubernetes/pull/53417),[ @crassirostris](https://github.com/crassirostris)) + * These parameters are also now configurable. ([#56638](https://github.com/kubernetes/kubernetes/pull/56638), [@crassirostris](https://github.com/crassirostris)) + +#### **Other** + +* Fix a typo in prometheus-to-sd configuration, that drops some stackdriver metrics. ([#56473](https://github.com/kubernetes/kubernetes/pull/56473),[ @loburm](https://github.com/loburm)) +* [fluentd-elasticsearch addon] Elasticsearch and Kibana are updated to version 5.6.4 ([#55400](https://github.com/kubernetes/kubernetes/pull/55400),[ @mrahbar](https://github.com/mrahbar)) +* fluentd now supports CRI log format. ([#54777](https://github.com/kubernetes/kubernetes/pull/54777),[ @Random-Liu](https://github.com/Random-Liu)) +* Bring all prom-to-sd container to the same image version ([#54583](https://github.com/kubernetes/kubernetes/pull/54583)) + * Reduce log noise produced by prometheus-to-sd, by bumping it to version 0.2.2. ([#54635](https://github.com/kubernetes/kubernetes/pull/54635),[ @loburm](https://github.com/loburm)) +* [fluentd-elasticsearch addon] Elasticsearch service name can be overridden via env variable ELASTICSEARCH_SERVICE_NAME ([#54215](https://github.com/kubernetes/kubernetes/pull/54215),[ @mrahbar](https://github.com/mrahbar)) + +### **Multicluster** + +#### **Federation** + +* Kubefed init now supports --imagePullSecrets and --imagePullPolicy, making it possible to use private registries. ([#50740](https://github.com/kubernetes/kubernetes/pull/50740),[ @dixudx](https://github.com/dixudx)) +* Updated cluster printer to enable --show-labels ([#53771](https://github.com/kubernetes/kubernetes/pull/53771),[ @dixudx](https://github.com/dixudx)) +* Kubefed init now supports --nodeSelector, enabling you to determine on what node the controller will be installed. ([#50749](https://github.com/kubernetes/kubernetes/pull/50749),[ @dixudx](https://github.com/dixudx)) + +### **Network** + +#### **IPv6** + +* [alpha] IPv6 support has been added. Notable IPv6 support details include: + * Support for IPv6-only Kubernetes cluster deployments. **Note:** This feature does not provide dual-stack support. + * Support for IPv6 Kubernetes control and data planes. + * Support for Kubernetes IPv6 cluster deployments using kubeadm. + * Support for the iptables kube-proxy backend using ip6tables. + * Relies on CNI 0.6.0 binaries for IPv6 pod networking. + * Adds IPv6 support for kube-dns using SRV records. + * Caveats + * Only the CNI bridge and local-ipam plugins have been tested for the alpha release, although other CNI plugins do support IPv6. + * HostPorts are not supported. +* An IPv6 network mask for pod or cluster cidr network must be /66 or longer. For example: 2001:db1::/66, 2001:dead:beef::/76, 2001:cafe::/118 are supported. 2001:db1::/64 is not supported +* For details, see [the complete list of merged pull requests for IPv6 support](https://github.com/kubernetes/kubernetes/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged+label%3Aarea%2Fipv6). + +#### **IPVS** + +* You can now use the --cleanup-ipvs flag to tell kube-proxy whether to flush all existing ipvs rules in on startup ([#56036](https://github.com/kubernetes/kubernetes/pull/56036),[ @m1093782566](https://github.com/m1093782566)) +* Graduate kube-proxy IPVS mode to beta. ([#56623](https://github.com/kubernetes/kubernetes/pull/56623), [@m1093782566](https://github.com/m1093782566)) + +#### **Kube-Proxy** + +* Added iptables rules to allow Pod traffic even when default iptables policy is to reject. ([#52569](https://github.com/kubernetes/kubernetes/pull/52569),[ @tmjd](https://github.com/tmjd)) +* You can once again use 0 values for conntrack min, max, max per core, tcp close wait timeout, and tcp established timeout; this functionality was broken in 1.8. ([#55261](https://github.com/kubernetes/kubernetes/pull/55261),[ @ncdc](https://github.com/ncdc)) + +#### **CoreDNS** + +* You now have the option to use CoreDNS instead of KubeDNS. To install CoreDNS instead of kube-dns, set CLUSTER_DNS_CORE_DNS to 'true'. This support is experimental. ([#52501](https://github.com/kubernetes/kubernetes/pull/52501),[ @rajansandeep](https://github.com/rajansandeep)) ([#55728](https://github.com/kubernetes/kubernetes/pull/55728),[ @rajansandeep](https://github.com/rajansandeep)) + +#### **Other** + +* Pod addresses will now be removed from the list of endpoints when the pod is in graceful termination. ([#54828](https://github.com/kubernetes/kubernetes/pull/54828),[ @freehan](https://github.com/freehan)) +* You can now use a new supported service annotation for AWS clusters, `service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy`, which lets you specify which [predefined AWS SSL policy](http://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-security-policy-table.html) you would like to use. ([#54507](https://github.com/kubernetes/kubernetes/pull/54507),[ @micahhausler](https://github.com/micahhausler)) +* Termination grace period for the calico/node add-on DaemonSet has been eliminated, reducing downtime during a rolling upgrade or deletion. ([#55015](https://github.com/kubernetes/kubernetes/pull/55015),[ @fasaxc](https://github.com/fasaxc)) +* Fixed bad conversion in host port chain name generating func which led to some unreachable host ports. ([#55153](https://github.com/kubernetes/kubernetes/pull/55153),[ @chenchun](https://github.com/chenchun)) +* Fixed IPVS availability check ([#51874](https://github.com/kubernetes/kubernetes/pull/51874),[ @vfreex](https://github.com/vfreex)) +* The output for kubectl describe networkpolicy * has been enhanced to be more useful. ([#46951](https://github.com/kubernetes/kubernetes/pull/46951),[ @aanm](https://github.com/aanm)) +* Kernel modules are now loaded automatically inside a kube-proxy pod ([#52003](https://github.com/kubernetes/kubernetes/pull/52003),[ @vfreex](https://github.com/vfreex)) +* Improve resilience by annotating kube-dns addon with podAntiAffinity to prefer scheduling on different nodes. ([#52193](https://github.com/kubernetes/kubernetes/pull/52193),[ @StevenACoffman](https://github.com/StevenACoffman)) +* [alpha] Added DNSConfig field to PodSpec. "None" mode for DNSPolicy is now supported. ([#55848](https://github.com/kubernetes/kubernetes/pull/55848),[ @MrHohn](https://github.com/MrHohn)) +* You can now add "options" to the host's /etc/resolv.conf (or --resolv-conf), and they will be copied into pod's resolv.conf when dnsPolicy is Default. Being able to customize options is important because it is common to leverage options to fine-tune the behavior of DNS client. ([#54773](https://github.com/kubernetes/kubernetes/pull/54773),[ @phsiao](https://github.com/phsiao)) +* Fixed a bug so that the service controller no longer retries if doNotRetry service update fails. ([#54184](https://github.com/kubernetes/kubernetes/pull/54184),[ @MrHohn](https://github.com/MrHohn)) +* Added --no-negcache flag to kube-dns to prevent caching of NXDOMAIN responses. ([#53604](https://github.com/kubernetes/kubernetes/pull/53604),[ @cblecker](https://github.com/cblecker)) + +### **Node** + +#### **Pod API** + +* A single value in metadata.annotations/metadata.labels can now be passed into the containers via the Downward API. ([#55902](https://github.com/kubernetes/kubernetes/pull/55902),[ @yguo0905](https://github.com/yguo0905)) +* Pods will no longer briefly transition to a "Pending" state during the deletion process. ([#54593](https://github.com/kubernetes/kubernetes/pull/54593),[ @dashpole](https://github.com/dashpole)) +* Added pod-level local ephemeral storage metric to the Summary API. Pod-level ephemeral storage reports the total filesystem usage for the containers and emptyDir volumes in the measured Pod. ([#55447](https://github.com/kubernetes/kubernetes/pull/55447),[ @jingxu97](https://github.com/jingxu97)) + +#### **Hardware Accelerators** + +* Kubelet now exposes metrics for NVIDIA GPUs attached to the containers. ([#55188](https://github.com/kubernetes/kubernetes/pull/55188),[ @mindprince](https://github.com/mindprince)) +* The device plugin Alpha API no longer supports returning artifacts per device as part of AllocateResponse. ([#53031](https://github.com/kubernetes/kubernetes/pull/53031),[ @vishh](https://github.com/vishh)) +* Fix to ignore extended resources that are not registered with kubelet during container resource allocation. ([#53547](https://github.com/kubernetes/kubernetes/pull/53547),[ @jiayingz](https://github.com/jiayingz)) + + +#### **Container Runtime** +* [alpha] [cri-tools](https://github.com/kubernetes-incubator/cri-tools): CLI and validation tools for CRI is now v1.0.0-alpha.0. This release mainly focuses on UX improvements. [[@feiskyer](https://github.com/feiskyer)] + * Make crictl command more user friendly and add more subcommands. + * Integrate with CRI verbose option to provide extra debug information. + * Update CRI to kubernetes v1.9. + * Bug fixes in validation test suites. +* [beta] [cri-containerd](https://github.com/kubernetes-incubator/cri-containerd): CRI implementation for containerd is now v1.0.0-beta.0, [[@Random-Liu](https://github.com/Random-Liu)] + * This release supports Kubernetes 1.9+ and containerd v1.0.0+. + * Pass all Kubernetes 1.9 e2e test, node e2e test and CRI validation tests. + * [Kube-up.sh integration](https://github.com/kubernetes-incubator/cri-containerd/blob/master/docs/kube-up.md). + * [Full crictl integration including CRI verbose option.](https://github.com/kubernetes-incubator/cri-containerd/blob/master/docs/crictl.md) + * Integration with cadvisor to provide better summary api support. +* [stable] [cri-o](https://github.com/kubernetes-incubator/cri-o): CRI implementation for OCI-based runtimes is now v1.9. [[@mrunalp](https://github.com/mrunalp)] + * Pass all the Kubernetes 1.9 end-to-end test suites and now gating PRs as well + * Pass all the CRI validation tests + * Release has been focused on bug fixes, stability and performance with runc and Clear Containers + * Minikube integration +* [stable] [frakti](https://github.com/kubernetes/frakti): CRI implementation for hypervisor-based runtimes is now v1.9. [[@resouer](https://github.com/resouer)] + * Added ARM64 release. Upgraded to CNI 0.6.0, added block device as Pod volume mode. Fixed CNI plugin compatibility. + * Passed all CRI validation conformance tests and node end-to-end conformance tests. +* [alpha] [rktlet](https://github.com/kubernetes-incubator/rktlet): CRI implementation for the rkt runtime is now v0.1.0. [[@iaguis](https://github.com/iaguis)] + * This is the first release of rktlet and it implements support for the CRI including fetching images, running pods, CNI networking, logging and exec. +This release passes 129/145 Kubernetes e2e conformance tests. +* Container Runtime Interface API change. [[@yujuhong](https://github.com/yujuhong)] + * A new field is added to CRI container log format to support splitting a long log line into multiple lines. ([#55922](https://github.com/kubernetes/kubernetes/pull/55922), [@Random-Liu](https://github.com/Random-Liu)) + * CRI now supports debugging via a verbose option for status functions. ([#53965](https://github.com/kubernetes/kubernetes/pull/53965), [@Random-Liu](https://github.com/Random-Liu)) + * Kubelet can now provide full summary api support for the CRI container runtime, with the exception of container log stats. ([#55810](https://github.com/kubernetes/kubernetes/pull/55810), [@abhi](https://github.com/abhi)) + * CRI now uses the correct localhost seccomp path when provided with input in the format of localhost//profileRoot/profileName. ([#55450](https://github.com/kubernetes/kubernetes/pull/55450), [@feiskyer](https://github.com/feiskyer)) + + +#### **Kubelet** + +* The EvictionHard, EvictionSoft, EvictionSoftGracePeriod, EvictionMinimumReclaim, SystemReserved, and KubeReserved fields in the KubeletConfiguration object (`kubeletconfig/v1alpha1`) are now of type map[string]string, which facilitates writing JSON and YAML files. ([#54823](https://github.com/kubernetes/kubernetes/pull/54823),[ @mtaufen](https://github.com/mtaufen)) +* Relative paths in the Kubelet's local config files (`--init-config-dir`) will now be resolved relative to the location of the containing files. ([#55648](https://github.com/kubernetes/kubernetes/pull/55648),[ @mtaufen](https://github.com/mtaufen)) +* It is now possible to set multiple manifest URL headers with the kubelet's `--manifest-url-header` flag. Multiple headers for the same key will be added in the order provided. The ManifestURLHeader field in KubeletConfiguration object (kubeletconfig/v1alpha1) is now a map[string][]string, which facilitates writing JSON and YAML files. ([#54643](https://github.com/kubernetes/kubernetes/pull/54643),[ @mtaufen](https://github.com/mtaufen)) +* The Kubelet's feature gates are now specified as a map when provided via a JSON or YAML KubeletConfiguration, rather than as a string of key-value pairs, making them less awkward for users. ([#53025](https://github.com/kubernetes/kubernetes/pull/53025),[ @mtaufen](https://github.com/mtaufen)) + +##### **Other** + +* Fixed a performance issue ([#51899](https://github.com/kubernetes/kubernetes/pull/51899)) identified in large-scale clusters when deleting thousands of pods simultaneously across hundreds of nodes, by actively removing containers of deleted pods, rather than waiting for periodic garbage collection and batching resulting pod API deletion requests. ([#53233](https://github.com/kubernetes/kubernetes/pull/53233),[ @dashpole](https://github.com/dashpole)) +* Problems deleting local static pods have been resolved. ([#48339](https://github.com/kubernetes/kubernetes/pull/48339),[ @dixudx](https://github.com/dixudx)) +* CRI now only calls UpdateContainerResources when cpuset is set. ([#53122](https://github.com/kubernetes/kubernetes/pull/53122),[ @resouer](https://github.com/resouer)) +* Containerd monitoring is now supported. ([#56109](https://github.com/kubernetes/kubernetes/pull/56109),[ @dashpole](https://github.com/dashpole)) +* deviceplugin has been extended to more gracefully handle the full device plugin lifecycle, including: ([#55088](https://github.com/kubernetes/kubernetes/pull/55088),[ @jiayingz](https://github.com/jiayingz)) + * Kubelet now uses an explicit cm.GetDevicePluginResourceCapacity() function that makes it possible to more accurately determine what resources are inactive and return a more accurate view of available resources. + * Extends the device plugin checkpoint data to record registered resources so that we can finish resource removing devices even upon kubelet restarts. + * Passes sourcesReady from kubelet to the device plugin to avoid removing inactive pods during the grace period of kubelet restart. + * Extends the gpu_device_plugin e2e_node test to verify that scheduled pods can continue to run even after a device plugin deletion and kubelet restart. +* The NodeController no longer supports kubelet 1.2. ([#48996](https://github.com/kubernetes/kubernetes/pull/48996),[ @k82cn](https://github.com/k82cn)) +* Kubelet now provides more specific events via FailedSync when unable to sync a pod. ([#53857](https://github.com/kubernetes/kubernetes/pull/53857),[ @derekwaynecarr](https://github.com/derekwaynecarr)) +* You can now disable AppArmor by setting the AppArmor profile to unconfined. ([#52395](https://github.com/kubernetes/kubernetes/pull/52395),[ @dixudx](https://github.com/dixudx)) +* ImageGCManage now consumes ImageFS stats from StatsProvider rather than cadvisor. ([#53094](https://github.com/kubernetes/kubernetes/pull/53094),[ @yguo0905](https://github.com/yguo0905)) +* Hyperkube now supports the support --experimental-dockershim kubelet flag. ([#54508](https://github.com/kubernetes/kubernetes/pull/54508),[ @ivan4th](https://github.com/ivan4th)) +* Kubelet no longer removes default labels from Node API objects on startup ([#54073](https://github.com/kubernetes/kubernetes/pull/54073),[ @liggitt](https://github.com/liggitt)) +* The overlay2 container disk metrics for Docker and CRI-O now work properly. ([#54827](https://github.com/kubernetes/kubernetes/pull/54827),[ @dashpole](https://github.com/dashpole)) +* Removed docker dependency during kubelet start up. ([#54405](https://github.com/kubernetes/kubernetes/pull/54405),[ @resouer](https://github.com/resouer)) +* Added Windows support to the system verification check. ([#53730](https://github.com/kubernetes/kubernetes/pull/53730),[ @bsteciuk](https://github.com/bsteciuk)) +* Kubelet no longer removes unregistered extended resource capacities from node status; cluster admins will have to manually remove extended resources exposed via device plugins when they the remove plugins themselves. ([#53353](https://github.com/kubernetes/kubernetes/pull/53353),[ @jiayingz](https://github.com/jiayingz)) +* The stats summary network value now takes into account multiple network interfaces, and not just eth0. ([#52144](https://github.com/kubernetes/kubernetes/pull/52144),[ @andyxning](https://github.com/andyxning)) +* Base images have been bumped to Debian Stretch (9). ([#52744](https://github.com/kubernetes/kubernetes/pull/52744),[ @rphillips](https://github.com/rphillips)) + +### **OpenStack** + +* OpenStack Cinder support has been improved: + * Cinder version detection now works properly. ([#53115](https://github.com/kubernetes/kubernetes/pull/53115),[ @FengyunPan](https://github.com/FengyunPan)) + * The OpenStack cloud provider now supports Cinder v3 API. ([#52910](https://github.com/kubernetes/kubernetes/pull/52910),[ @FengyunPan](https://github.com/FengyunPan)) +* Load balancing is now more flexible: + * The OpenStack LBaaS v2 Provider is now [configurable](https://kubernetes.io/docs/concepts/cluster-administration/cloud-providers/#openstack). ([#54176](https://github.com/kubernetes/kubernetes/pull/54176),[ @gonzolino](https://github.com/gonzolino)) + * OpenStack Octavia v2 is now supported as a load balancer provider in addition to the existing support for the Neutron LBaaS V2 implementation. Neutron LBaaS V1 support has been removed. ([#55393](https://github.com/kubernetes/kubernetes/pull/55393),[ @jamiehannaford](https://github.com/jamiehannaford)) +* OpenStack security group support has been beefed up ([#50836](https://github.com/kubernetes/kubernetes/pull/50836),[ @FengyunPan](https://github.com/FengyunPan)): + * Kubernetes will now automatically determine the security group for the node + * Nodes can now belong to multiple security groups + +### **Scheduling** + +#### **Hardware Accelerators** + +* Add ExtendedResourceToleration admission controller. This facilitates creation of dedicated nodes with extended resources. If operators want to create dedicated nodes with extended resources (such as GPUs, FPGAs, and so on), they are expected to taint the node with extended resource name as the key. This admission controller, if enabled, automatically adds tolerations for such taints to pods requesting extended resources, so users don't have to manually add these tolerations. ([#55839](https://github.com/kubernetes/kubernetes/pull/55839),[ @mindprince](https://github.com/mindprince)) + +#### **Other** + +* Scheduler cache ignores updates to an assumed pod if updates are limited to pod annotations. ([#54008](https://github.com/kubernetes/kubernetes/pull/54008),[ @yguo0905](https://github.com/yguo0905)) +* Issues with namespace deletion have been resolved. ([#53720](https://github.com/kubernetes/kubernetes/pull/53720),[ @shyamjvs](https://github.com/shyamjvs)) ([#53793](https://github.com/kubernetes/kubernetes/pull/53793),[ @wojtek-t](https://github.com/wojtek-t)) +* Pod preemption has been improved. + * Now takes PodDisruptionBudget into account. ([#56178](https://github.com/kubernetes/kubernetes/pull/56178),[ @bsalamat](https://github.com/bsalamat)) + * Nominated pods are taken into account during scheduling to avoid starvation of higher priority pods. ([#55933](https://github.com/kubernetes/kubernetes/pull/55933),[ @bsalamat](https://github.com/bsalamat)) +* Fixed 'Schedulercache is corrupted' error in kube-scheduler ([#55262](https://github.com/kubernetes/kubernetes/pull/55262),[ @liggitt](https://github.com/liggitt)) +* The kube-scheduler command now supports a --config flag which is the location of a file containing a serialized scheduler configuration. Most other kube-scheduler flags are now deprecated. ([#52562](https://github.com/kubernetes/kubernetes/pull/52562),[ @ironcladlou](https://github.com/ironcladlou)) +* A new scheduling queue helps schedule the highest priority pending pod first. ([#55109](https://github.com/kubernetes/kubernetes/pull/55109),[ @bsalamat](https://github.com/bsalamat)) +* A Pod can now listen to the same port on multiple IP addresses. ([#52421](https://github.com/kubernetes/kubernetes/pull/52421),[ @WIZARD-CXY](https://github.com/WIZARD-CXY)) +* Object count quotas supported on all standard resources using count/. syntax ([#54320](https://github.com/kubernetes/kubernetes/pull/54320),[ @derekwaynecarr](https://github.com/derekwaynecarr)) +* Apply algorithm in scheduler by feature gates. ([#52723](https://github.com/kubernetes/kubernetes/pull/52723),[ @k82cn](https://github.com/k82cn)) +* A new priority function ResourceLimitsPriorityMap (disabled by default and behind alpha feature gate and not part of the scheduler's default priority functions list) that assigns a lowest possible score of 1 to a node that satisfies one or both of input pod's cpu and memory limits, mainly to break ties between nodes with same scores. ([#55906](https://github.com/kubernetes/kubernetes/pull/55906),[ @aveshagarwal](https://github.com/aveshagarwal)) +* Kubelet evictions now take pod priority into account ([#53542](https://github.com/kubernetes/kubernetes/pull/53542),[ @dashpole](https://github.com/dashpole)) +* PodTolerationRestriction admisson plugin: if namespace level tolerations are empty, now they override cluster level tolerations. ([#54812](https://github.com/kubernetes/kubernetes/pull/54812),[ @aveshagarwal](https://github.com/aveshagarwal)) + +### **Storage** + +* [stable] `PersistentVolume` and `PersistentVolumeClaim` objects must now have a capacity greater than zero. +* [stable] Mutation of `PersistentVolumeSource` after creation is no longer allowed +* [alpha] Deletion of `PersistentVolumeClaim` objects that are in use by a pod no longer permitted (if alpha feature is enabled). +* [alpha] Container Storage Interface + * New CSIVolumeSource enables Kubernetes to use external CSI drivers to provision, attach, and mount volumes. +* [alpha] Raw block volumes + * Support for surfacing volumes as raw block devices added to Kubernetes storage system. + * Only Fibre Channel volume plugin supports exposes this functionality, in this release. +* [alpha] Volume resizing + * Added file system resizing for the following volume plugins: GCE PD, Ceph RBD, AWS EBS, OpenStack Cinder +* [alpha] Topology Aware Volume Scheduling + * Improved volume scheduling for Local PersistentVolumes, by allowing the scheduler to make PersistentVolume binding decisions while respecting the Pod's scheduling requirements. + * Dynamic provisioning is not supported with this feature yet. +* [alpha] Containerized mount utilities + * Allow mount utilities, used to mount volumes, to run inside a container instead of on the host. +* Bug Fixes + * ScaleIO volume plugin is no longer dependent on the drv_cfg binary, so a Kubernetes cluster can easily run a containerized kubelet. ([#54956](https://github.com/kubernetes/kubernetes/pull/54956),[ @vladimirvivien](https://github.com/vladimirvivien)) + * AWS EBS Volumes are detached from stopped AWS nodes. ([#55893](https://github.com/kubernetes/kubernetes/pull/55893),[ @gnufied](https://github.com/gnufied)) + * AWS EBS volumes are detached if attached to a different node than expected. ([#55491](https://github.com/kubernetes/kubernetes/pull/55491),[ @gnufied](https://github.com/gnufied)) + * PV Recycle now works in environments that use architectures other than x86. ([#53958](https://github.com/kubernetes/kubernetes/pull/53958),[ @dixudx](https://github.com/dixudx)) + * Pod Security Policy can now manage access to specific FlexVolume drivers.([#53179](https://github.com/kubernetes/kubernetes/pull/53179),[ @wanghaoran1988](https://github.com/wanghaoran1988)) + * To prevent unauthorized access to CHAP Secrets, you can now set the secretNamespace storage class parameters for the following volume types: + * ScaleIO; StoragePool and ProtectionDomain attributes no longer default to the value default. ([#54013](https://github.com/kubernetes/kubernetes/pull/54013),[ @vladimirvivien](https://github.com/vladimirvivien)) + * RBD Persistent Volume Sources ([#54302](https://github.com/kubernetes/kubernetes/pull/54302),[ @sbezverk](https://github.com/sbezverk)) + * iSCSI Persistent Volume Sources ([#51530](https://github.com/kubernetes/kubernetes/pull/51530),[ @rootfs](https://github.com/rootfs)) + * In GCE multizonal clusters, `PersistentVolume` objects will no longer be dynamically provisioned in zones without nodes. ([#52322](https://github.com/kubernetes/kubernetes/pull/52322),[ @davidz627](https://github.com/davidz627)) + * Multi Attach PVC errors and events are now more useful and less noisy. ([#53401](https://github.com/kubernetes/kubernetes/pull/53401),[ @gnufied](https://github.com/gnufied)) + * The compute-rw scope has been removed from GCE nodes ([#53266](https://github.com/kubernetes/kubernetes/pull/53266),[ @mikedanese](https://github.com/mikedanese)) + * Updated vSphere cloud provider to support k8s cluster spread across multiple vCenters ([#55845](https://github.com/kubernetes/kubernetes/pull/55845),[ @rohitjogvmw](https://github.com/rohitjogvmw)) + * vSphere: Fix disk is not getting detached when PV is provisioned on clustered datastore. ([#54438](https://github.com/kubernetes/kubernetes/pull/54438),[ @pshahzeb](https://github.com/pshahzeb)) + * If a non-absolute mountPath is passed to the kubelet, it must now be prefixed with the appropriate root path. ([#55665](https://github.com/kubernetes/kubernetes/pull/55665),[ @brendandburns](https://github.com/brendandburns)) + +## External Dependencies + +* The supported etcd server version is **3.1.10**, as compared to 3.0.17 in v1.8 ([#49393](https://github.com/kubernetes/kubernetes/pull/49393),[ @hongchaodeng](https://github.com/hongchaodeng)) +* The validated docker versions are the same as for v1.8: **1.11.2 to 1.13.1 and 17.03.x** +* The Go version was upgraded from go1.8.3 to **go1.9.2** ([#51375](https://github.com/kubernetes/kubernetes/pull/51375),[ @cblecker](https://github.com/cblecker)) + * The minimum supported go version bumps to 1.9.1. ([#55301](https://github.com/kubernetes/kubernetes/pull/55301),[ @xiangpengzhao](https://github.com/xiangpengzhao)) + * Kubernetes has been upgraded to go1.9.2 ([#55420](https://github.com/kubernetes/kubernetes/pull/55420),[ @cblecker](https://github.com/cblecker)) +* CNI was upgraded to **v0.6.0** ([#51250](https://github.com/kubernetes/kubernetes/pull/51250),[ @dixudx](https://github.com/dixudx)) +* The dashboard add-on has been updated to [v1.8.0](https://github.com/kubernetes/dashboard/releases/tag/v1.8.0). ([#53046](https://github.com/kubernetes/kubernetes/pull/53046), [@maciaszczykm](https://github.com/maciaszczykm)) +* Heapster has been updated to [v1.5.0](https://github.com/kubernetes/heapster/releases/tag/v1.5.0). ([#57046](https://github.com/kubernetes/kubernetes/pull/57046), [@piosz](https://github.com/piosz)) +* Cluster Autoscaler has been updated to [v1.1.0](https://github.com/kubernetes/autoscaler/releases/tag/cluster-autoscaler-1.1.0). ([#56969](https://github.com/kubernetes/kubernetes/pull/56969), [@mwielgus](https://github.com/mwielgus)) +* Update kube-dns 1.14.7 ([#54443](https://github.com/kubernetes/kubernetes/pull/54443),[ @bowei](https://github.com/bowei)) +* Update influxdb to v1.3.3 and grafana to v4.4.3 ([#53319](https://github.com/kubernetes/kubernetes/pull/53319),[ @kairen](https://github.com/kairen)) +- [v1.9.0-beta.2](#v190-beta2) +- [v1.9.0-beta.1](#v190-beta1) +- [v1.9.0-alpha.3](#v190-alpha3) +- [v1.9.0-alpha.2](#v190-alpha2) +- [v1.9.0-alpha.1](#v190-alpha1) + + + +# v1.9.0-beta.2 + +[Documentation](https://docs.k8s.io) & [Examples](https://releases.k8s.io/release-1.9/examples) + +## Downloads for v1.9.0-beta.2 + + +filename | sha256 hash +-------- | ----------- +[kubernetes.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-beta.2/kubernetes.tar.gz) | `e5c88addf6aca01635f283021a72e05be99daf3e87fd3cda92477d0ed63c2d11` +[kubernetes-src.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-beta.2/kubernetes-src.tar.gz) | `2419a0ef3681460b64eefc083d07377786b308f6cc62d0618a5c74dfb4729b03` + +### Client Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-client-darwin-386.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-beta.2/kubernetes-client-darwin-386.tar.gz) | `68d971576c3e9a16fb736f06c07ce53b8371fc67c2f37fb60e9f3a366cd37a80` +[kubernetes-client-darwin-amd64.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-beta.2/kubernetes-client-darwin-amd64.tar.gz) | `36251b7b6043adb79706ac115181aa7ecf365ced9198a4c192f1fbc2817d030c` +[kubernetes-client-linux-386.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-beta.2/kubernetes-client-linux-386.tar.gz) | `585a3dd6a3440988bce3f83ea14fb9a0a18011bc62e28959301861faa06d6da9` +[kubernetes-client-linux-amd64.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-beta.2/kubernetes-client-linux-amd64.tar.gz) | `169769d6030d8c1d9d9bc01408b62ea3275d4632a7de85392fc95a48feeba522` +[kubernetes-client-linux-arm64.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-beta.2/kubernetes-client-linux-arm64.tar.gz) | `7841c2af49be9ae04cda305165b172021c0e72d809c2271d05061330c220256b` +[kubernetes-client-linux-arm.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-beta.2/kubernetes-client-linux-arm.tar.gz) | `9ab32843cec68b036de83f54a68c2273a913be5180dc20b5cf1e084b314a9a2d` +[kubernetes-client-linux-ppc64le.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-beta.2/kubernetes-client-linux-ppc64le.tar.gz) | `5a2bb39b78ef381382f9b8aac17d5dbcbef08a80ad3518ff2cf6c65bd7a6d07d` +[kubernetes-client-linux-s390x.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-beta.2/kubernetes-client-linux-s390x.tar.gz) | `ddf4b3780f5879b9fb9115353cc26234cfc3a6db63a3cd39122340189a4bf0ca` +[kubernetes-client-windows-386.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-beta.2/kubernetes-client-windows-386.tar.gz) | `5960a0a50c92a788e90eca9d85a1d12ff1d41264816b55b3a1a28ffd3f6acf93` +[kubernetes-client-windows-amd64.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-beta.2/kubernetes-client-windows-amd64.tar.gz) | `d85778ace9bf25f5d3626aef3a9419a2c4aaa3847d5e0c2bf34d4dd8ae6b5205` + +### Server Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-beta.2/kubernetes-server-linux-amd64.tar.gz) | `43e16b3d79c2805d712fd61ed6fd110d9db09a60d39584ef78c24821eb32b77a` +[kubernetes-server-linux-arm64.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-beta.2/kubernetes-server-linux-arm64.tar.gz) | `8580e454e6c467a30687ff5c85248919b3c0d2d0114e28cb3bf64d2e8998ff00` +[kubernetes-server-linux-arm.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-beta.2/kubernetes-server-linux-arm.tar.gz) | `d2e767be85ebf7c6c537c8e796e8fe0ce8a3f2ca526984490646acd30bf5e6fc` +[kubernetes-server-linux-ppc64le.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-beta.2/kubernetes-server-linux-ppc64le.tar.gz) | `81dd9072e805c181b4db2dfd00fe2bdb43c00da9e07b50285bce703bfd0d75ba` +[kubernetes-server-linux-s390x.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-beta.2/kubernetes-server-linux-s390x.tar.gz) | `f432c816c755d05e62cb5d5e8ac08dcb60d0df6d5121e1adaf42a32de65d6174` + +### Node Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-beta.2/kubernetes-node-linux-amd64.tar.gz) | `2bf2268735ca4ecbdca1a692b25329d6d9d4805963cbe0cfcbb92fc725c42481` +[kubernetes-node-linux-arm64.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-beta.2/kubernetes-node-linux-arm64.tar.gz) | `3bb4a695fd2e4fca1c77283c1ad6c2914d12b33d9c5f64ac9c630a42d5e30ab2` +[kubernetes-node-linux-arm.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-beta.2/kubernetes-node-linux-arm.tar.gz) | `331c1efadf99dcb634c8da301349e3be63d27a5c5f06cc124b59fcc8b8a91cb0` +[kubernetes-node-linux-ppc64le.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-beta.2/kubernetes-node-linux-ppc64le.tar.gz) | `ab036fdb64ed4702d7dbbadddf77af90de35f73aa13854bb5accf82acc95c7e6` +[kubernetes-node-linux-s390x.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-beta.2/kubernetes-node-linux-s390x.tar.gz) | `8257af566f98325549de320d2167c1f56fd137b6225c70f6c1e34507ba124a1f` +[kubernetes-node-windows-amd64.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-beta.2/kubernetes-node-windows-amd64.tar.gz) | `4146fcb5bb6bf3e04641b27e4aa8501649178716fa16bd9bcb7f1fe3449db7f2` + +## Changelog since v1.9.0-beta.1 + +### Other notable changes + +* Add pvc as part of equivalence hash ([#56577](https://github.com/kubernetes/kubernetes/pull/56577), [@resouer](https://github.com/resouer)) +* Fix port number and default Stackdriver Metadata Agent in daemon set configuration. ([#56576](https://github.com/kubernetes/kubernetes/pull/56576), [@kawych](https://github.com/kawych)) +* Declare ipvs proxier beta ([#56623](https://github.com/kubernetes/kubernetes/pull/56623), [@m1093782566](https://github.com/m1093782566)) +* Enable admissionregistration.k8s.io/v1beta1 by default in kube-apiserver. ([#56687](https://github.com/kubernetes/kubernetes/pull/56687), [@sttts](https://github.com/sttts)) +* Support autoprobing floating-network-id for openstack cloud provider ([#52013](https://github.com/kubernetes/kubernetes/pull/52013), [@FengyunPan](https://github.com/FengyunPan)) +* Audit webhook batching parameters are now configurable via command-line flags in the apiserver. ([#56638](https://github.com/kubernetes/kubernetes/pull/56638), [@crassirostris](https://github.com/crassirostris)) +* Update kubectl to the stable version ([#54345](https://github.com/kubernetes/kubernetes/pull/54345), [@zouyee](https://github.com/zouyee)) +* [scheduler] Fix issue new pod with affinity stuck at `creating` because node had been deleted but its pod still exists. ([#53647](https://github.com/kubernetes/kubernetes/pull/53647), [@wenlxie](https://github.com/wenlxie)) +* Updated Dashboard add-on to version 1.8.0: The Dashboard add-on now deploys with https enabled. The Dashboard can be accessed via kubectl proxy at http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/. The /ui redirect is deprecated and will be removed in 1.10. ([#53046](https://github.com/kubernetes/kubernetes/pull/53046), [@maciaszczykm](https://github.com/maciaszczykm)) +* AWS: Detect EBS volumes mounted via NVME and mount them ([#56607](https://github.com/kubernetes/kubernetes/pull/56607), [@justinsb](https://github.com/justinsb)) +* fix CreateVolume func: use search mode instead ([#54687](https://github.com/kubernetes/kubernetes/pull/54687), [@andyzhangx](https://github.com/andyzhangx)) +* kubelet: fix bug where `runAsUser: MustRunAsNonRoot` strategy didn't reject a pod with a non-numeric `USER`. ([#56503](https://github.com/kubernetes/kubernetes/pull/56503), [@php-coder](https://github.com/php-coder)) +* kube-proxy addon tolerates all NoExecute and NoSchedule taints by default. ([#56589](https://github.com/kubernetes/kubernetes/pull/56589), [@mindprince](https://github.com/mindprince)) +* Do not do file system resize on read-only mounts ([#56587](https://github.com/kubernetes/kubernetes/pull/56587), [@gnufied](https://github.com/gnufied)) +* Mark v1beta1 NetworkPolicy types as deprecated ([#56425](https://github.com/kubernetes/kubernetes/pull/56425), [@cmluciano](https://github.com/cmluciano)) +* Fix problem with /bin/bash ending up linked to dash ([#55018](https://github.com/kubernetes/kubernetes/pull/55018), [@dims](https://github.com/dims)) +* Modifying etcd recovery steps for the case of failed upgrade ([#56500](https://github.com/kubernetes/kubernetes/pull/56500), [@sbezverk](https://github.com/sbezverk)) + + + +# v1.9.0-beta.1 + +[Documentation](https://docs.k8s.io) & [Examples](https://releases.k8s.io/release-1.9/examples) + +## Downloads for v1.9.0-beta.1 + + +filename | sha256 hash +-------- | ----------- +[kubernetes.tar.gz](https://dl.k8s.io/v1.9.0-beta.1/kubernetes.tar.gz) | `ffdcf0f7cd972340bc666395d759fc18573a32775d38ed3f4fd99d4369e856e4` +[kubernetes-src.tar.gz](https://dl.k8s.io/v1.9.0-beta.1/kubernetes-src.tar.gz) | `09bee9a955987d53c7a65d2f1a3129854ca3a34f9fb38218f0c58f5bd603494a` + +### Client Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-client-darwin-386.tar.gz](https://dl.k8s.io/v1.9.0-beta.1/kubernetes-client-darwin-386.tar.gz) | `9d54db976ca7a12e9208e5595b552b094e0cc532b49ba6e919d776e52e56f4a8` +[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.9.0-beta.1/kubernetes-client-darwin-amd64.tar.gz) | `0a22af2c6c84ff8b3022c0ecebf4ba3021048fceddf7375c87c13a83488ffe2c` +[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.9.0-beta.1/kubernetes-client-linux-386.tar.gz) | `84bb638c8e61d7a7b415d49d76d166f3924052338c454d1ae57ae36eb37445c6` +[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.9.0-beta.1/kubernetes-client-linux-amd64.tar.gz) | `08b56240288d17f147485e79c5f6594391c5b46e26450d64e7510f65db1f9a79` +[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.9.0-beta.1/kubernetes-client-linux-arm64.tar.gz) | `7206573b131a8915d3bc14aa660fb44890ed79fdbd498bc8f9951c221aa12ea5` +[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.9.0-beta.1/kubernetes-client-linux-arm.tar.gz) | `7ad21796b0e0a9d247beb41d6b3a3d0aaa822b85adae4c90533ba0ef94c05b2e` +[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.9.0-beta.1/kubernetes-client-linux-ppc64le.tar.gz) | `2076328ca0958a96c8f551b91a393aa2d6fc24bef92991a1a4d9fc8df52519a7` +[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.9.0-beta.1/kubernetes-client-linux-s390x.tar.gz) | `17ac0aba9a4e2003cb3d06bd631032b760d1a2d521c60a25dc26687aadb5ba14` +[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.9.0-beta.1/kubernetes-client-windows-386.tar.gz) | `3a2bebd4adb6e1bf2b30a8cedb7ec212fc43c4b02e26a0a60c3429e478a86073` +[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.9.0-beta.1/kubernetes-client-windows-amd64.tar.gz) | `fcc852e97f0e64d1025344aefd042ceff05227bfded80142bfa99927de1a5f0e` + +### Server Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.9.0-beta.1/kubernetes-server-linux-amd64.tar.gz) | `7ed2a789b86f258f1739cb165276150512a171a715da9372aeff000e946548fd` +[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.9.0-beta.1/kubernetes-server-linux-arm64.tar.gz) | `e4e04a33698ac665a3e61fd8d60d4010fec6b0e3b0627dee9a965c2c2a510e3a` +[kubernetes-server-linux-arm.tar.gz](https://dl.k8s.io/v1.9.0-beta.1/kubernetes-server-linux-arm.tar.gz) | `befce41457fc15c8fadf37ee5bf80b83405279c60665cfb9ecfc9f61fcd549c7` +[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.9.0-beta.1/kubernetes-server-linux-ppc64le.tar.gz) | `e59e4fb84d6b890e9c6cb216ebb20546212e6c14feb077d9d0761c88e2685f4c` +[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.9.0-beta.1/kubernetes-server-linux-s390x.tar.gz) | `0aa47d01907ea78b9a1a8001536d5091fca93409b81bac6eb3e90a4dff6c3faa` + +### Node Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.9.0-beta.1/kubernetes-node-linux-amd64.tar.gz) | `107bfaf72b8b6d3b5c163e61ed169c89288958750636c16bc3d781cf94bf5f4c` +[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.9.0-beta.1/kubernetes-node-linux-arm64.tar.gz) | `6bc58e913a2467548664ece743617a1e595f6223100a1bad27e9a90bdf2e2927` +[kubernetes-node-linux-arm.tar.gz](https://dl.k8s.io/v1.9.0-beta.1/kubernetes-node-linux-arm.tar.gz) | `d4ff8f37d7c95f7ca3aca30fa3c191f2cc5e48f0159ac6a5395ec09092574baa` +[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.9.0-beta.1/kubernetes-node-linux-ppc64le.tar.gz) | `a88d65343ccb515c4eaab11352e69afee4a19c7fa345b08aaffa854b225cf305` +[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.9.0-beta.1/kubernetes-node-linux-s390x.tar.gz) | `16d6a67d18273460cab4c293a5b130d4827f41ee4bf5b79b07c60ef517f580cd` +[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.9.0-beta.1/kubernetes-node-windows-amd64.tar.gz) | `f086659462b6dcdd78abdf13bed339dd67c1111931bae962044aa4ae2396921d` + +## Changelog since v1.9.0-alpha.3 + +### Action Required + +* Adds alpha support for volume scheduling, which allows the scheduler to make PersistentVolume binding decisions while respecting the Pod's scheduling requirements. Dynamic provisioning is not supported with this feature yet. ([#55039](https://github.com/kubernetes/kubernetes/pull/55039), [@msau42](https://github.com/msau42)) + * Action required for existing users of the LocalPersistentVolumes alpha feature: + * The VolumeScheduling feature gate also has to be enabled on kube-scheduler and kube-controller-manager. + * The NoVolumeNodeConflict predicate has been removed. For non-default schedulers, update your scheduler policy. + * The CheckVolumeBinding predicate has to be enabled in non-default schedulers. +* Action required: ([#56004](https://github.com/kubernetes/kubernetes/pull/56004), [@caesarxuchao](https://github.com/caesarxuchao)) + * The `admission/v1alpha1` API has graduated to `v1beta1`. Please delete your existing webhooks before upgrading the cluster, and update your admission webhooks to use the latest API, because the API has backwards incompatible changes. + * The webhook registration related part of the `admissionregistration` API has graduated to `v1beta1`. Please delete your existing configurations before upgrading the cluster, and update your configuration file to use the latest API. +* [action required] kubeadm join: Error out if CA pinning isn't used or opted out of ([#55468](https://github.com/kubernetes/kubernetes/pull/55468), [@yuexiao-wang](https://github.com/yuexiao-wang)) + * kubeadm now requires the user to specify either the `--discovery-token-ca-cert-hash` flag or the `--discovery-token-unsafe-skip-ca-verification` flag. + +### Other notable changes + +* A new priority function `ResourceLimitsPriorityMap` (disabled by default and behind alpha feature gate and not part of the scheduler's default priority functions list) that assigns a lowest possible score of 1 to a node that satisfies one or both of input pod's cpu and memory limits, mainly to break ties between nodes with same scores. ([#55906](https://github.com/kubernetes/kubernetes/pull/55906), [@aveshagarwal](https://github.com/aveshagarwal)) +* AWS: Fix detaching volume from stopped nodes. ([#55893](https://github.com/kubernetes/kubernetes/pull/55893), [@gnufied](https://github.com/gnufied)) +* Fix stats summary network value when multiple network interfaces are available. ([#52144](https://github.com/kubernetes/kubernetes/pull/52144), [@andyxning](https://github.com/andyxning)) +* Fix a typo in prometheus-to-sd configuration, that drops some stackdriver metrics. ([#56473](https://github.com/kubernetes/kubernetes/pull/56473), [@loburm](https://github.com/loburm)) +* Fixes server name verification of aggregated API servers and webhook admission endpoints ([#56415](https://github.com/kubernetes/kubernetes/pull/56415), [@liggitt](https://github.com/liggitt)) +* OpenStack cloud provider supports Cinder v3 API. ([#52910](https://github.com/kubernetes/kubernetes/pull/52910), [@FengyunPan](https://github.com/FengyunPan)) +* kube-up: Add optional addon CoreDNS. ([#55728](https://github.com/kubernetes/kubernetes/pull/55728), [@rajansandeep](https://github.com/rajansandeep)) + * Install CoreDNS instead of kube-dns by setting CLUSTER_DNS_CORE_DNS value to 'true'. +* kubeadm health checks can also be skipped with `--ignore-checks-errors` ([#56130](https://github.com/kubernetes/kubernetes/pull/56130), [@anguslees](https://github.com/anguslees)) +* Adds kubeadm support for using ComponentConfig for the kube-proxy ([#55972](https://github.com/kubernetes/kubernetes/pull/55972), [@rpothier](https://github.com/rpothier)) +* Pod Security Policy can now manage access to specific FlexVolume drivers ([#53179](https://github.com/kubernetes/kubernetes/pull/53179), [@wanghaoran1988](https://github.com/wanghaoran1988)) +* PVC Finalizing Controller is introduced in order to prevent deletion of a PVC that is being used by a pod. ([#55824](https://github.com/kubernetes/kubernetes/pull/55824), [@pospispa](https://github.com/pospispa)) +* Kubelet can provide full summary api support except container log stats for CRI container runtime now. ([#55810](https://github.com/kubernetes/kubernetes/pull/55810), [@abhi](https://github.com/abhi)) +* Add support for resizing EBS disks ([#56118](https://github.com/kubernetes/kubernetes/pull/56118), [@gnufied](https://github.com/gnufied)) +* Add PodDisruptionBudget support during pod preemption ([#56178](https://github.com/kubernetes/kubernetes/pull/56178), [@bsalamat](https://github.com/bsalamat)) +* Fix CRI localhost seccomp path in format localhost//profileRoot/profileName. ([#55450](https://github.com/kubernetes/kubernetes/pull/55450), [@feiskyer](https://github.com/feiskyer)) +* kubeadm: Add CoreDNS support for kubeadm "upgrade" and "alpha phases addons". ([#55952](https://github.com/kubernetes/kubernetes/pull/55952), [@rajansandeep](https://github.com/rajansandeep)) +* The default garbage collection policy for Deployment, DaemonSet, StatefulSet, and ReplicaSet has changed from OrphanDependents to DeleteDependents when the deletion is requested through an `apps/v1` endpoint. Clients using older endpoints will be unaffected. This change is only at the REST API level and is independent of the default behavior of particular clients (e.g. this does not affect the default for the kubectl `--cascade` flag). ([#55148](https://github.com/kubernetes/kubernetes/pull/55148), [@dixudx](https://github.com/dixudx)) + * If you upgrade your client-go libs and use the `AppsV1()` interface, please note that the default garbage collection behavior is changed. +* Add resize support for ceph RBD ([#52767](https://github.com/kubernetes/kubernetes/pull/52767), [@NickrenREN](https://github.com/NickrenREN)) +* Expose single annotation/label via downward API ([#55902](https://github.com/kubernetes/kubernetes/pull/55902), [@yguo0905](https://github.com/yguo0905)) +* kubeadm: added `--print-join-command` flag for `kubeadm token create`. ([#56185](https://github.com/kubernetes/kubernetes/pull/56185), [@mattmoyer](https://github.com/mattmoyer)) +* Implement kubelet side file system resizing. Also implement GCE PD resizing ([#55815](https://github.com/kubernetes/kubernetes/pull/55815), [@gnufied](https://github.com/gnufied)) +* Improved PodSecurityPolicy admission latency, but validation errors are no longer limited to only errors from authorized policies. ([#55643](https://github.com/kubernetes/kubernetes/pull/55643), [@tallclair](https://github.com/tallclair)) +* Add containerd monitoring support ([#56109](https://github.com/kubernetes/kubernetes/pull/56109), [@dashpole](https://github.com/dashpole)) +* Add pod-level CPU and memory stats from pod cgroup information ([#55969](https://github.com/kubernetes/kubernetes/pull/55969), [@jingxu97](https://github.com/jingxu97)) +* kubectl apply use openapi to calculate diff be default. It will fall back to use baked-in types when openapi is not available. ([#51321](https://github.com/kubernetes/kubernetes/pull/51321), [@mengqiy](https://github.com/mengqiy)) +* It is now possible to override the healthcheck parameters for AWS ELBs via annotations on the corresponding service. The new annotations are `healthy-threshold`, `unhealthy-threshold`, `timeout`, `interval` (all prefixed with `service.beta.kubernetes.io/aws-load-balancer-healthcheck-`) ([#56024](https://github.com/kubernetes/kubernetes/pull/56024), [@dimpavloff](https://github.com/dimpavloff)) +* Adding etcd version display to kubeadm upgrade plan subcommand ([#56156](https://github.com/kubernetes/kubernetes/pull/56156), [@sbezverk](https://github.com/sbezverk)) +* [fluentd-gcp addon] Fixes fluentd deployment on GCP when custom resources are set. ([#55950](https://github.com/kubernetes/kubernetes/pull/55950), [@crassirostris](https://github.com/crassirostris)) +* [fluentd-elasticsearch addon] Elasticsearch and Kibana are updated to version 5.6.4 ([#55400](https://github.com/kubernetes/kubernetes/pull/55400), [@mrahbar](https://github.com/mrahbar)) +* install ipset in debian-iptables docker image ([#56115](https://github.com/kubernetes/kubernetes/pull/56115), [@m1093782566](https://github.com/m1093782566)) +* Add cleanup-ipvs flag for kube-proxy ([#56036](https://github.com/kubernetes/kubernetes/pull/56036), [@m1093782566](https://github.com/m1093782566)) +* Remove opaque integer resources (OIR) support (deprecated in v1.8.) ([#55103](https://github.com/kubernetes/kubernetes/pull/55103), [@ConnorDoyle](https://github.com/ConnorDoyle)) +* Implement volume resize for cinder ([#51498](https://github.com/kubernetes/kubernetes/pull/51498), [@NickrenREN](https://github.com/NickrenREN)) +* Block volumes Support: FC plugin update ([#51493](https://github.com/kubernetes/kubernetes/pull/51493), [@mtanino](https://github.com/mtanino)) +* kube-apiserver: fixed --oidc-username-prefix and --oidc-group-prefix flags which previously weren't correctly enabled ([#56175](https://github.com/kubernetes/kubernetes/pull/56175), [@ericchiang](https://github.com/ericchiang)) +* New kubeadm flag `--ignore-preflight-errors` that enables to decrease severity of each individual error to warning. ([#56072](https://github.com/kubernetes/kubernetes/pull/56072), [@kad](https://github.com/kad)) + * Old flag `--skip-preflight-checks` is marked as deprecated and acts as `--ignore-preflight-errors=all` +* Block volumes Support: CRI, volumemanager and operationexecutor changes ([#51494](https://github.com/kubernetes/kubernetes/pull/51494), [@mtanino](https://github.com/mtanino)) +* StatefulSet controller will create a label for each Pod in a StatefulSet. The label is named statefulset.kubernetes.io/pod-name and it is equal to the name of the Pod. This allows users to create a Service per Pod to expose a connection to individual Pods. ([#55329](https://github.com/kubernetes/kubernetes/pull/55329), [@kow3ns](https://github.com/kow3ns)) +* Initial basic bootstrap-checkpoint support ([#50984](https://github.com/kubernetes/kubernetes/pull/50984), [@timothysc](https://github.com/timothysc)) +* Add DNSConfig field to PodSpec and support "None" mode for DNSPolicy (Alpha). ([#55848](https://github.com/kubernetes/kubernetes/pull/55848), [@MrHohn](https://github.com/MrHohn)) +* Add pod-level local ephemeral storage metric in Summary API. Pod-level ephemeral storage reports the total filesystem usage for the containers and emptyDir volumes in the measured Pod. ([#55447](https://github.com/kubernetes/kubernetes/pull/55447), [@jingxu97](https://github.com/jingxu97)) +* Kubernetes update Azure nsg rules based on not just difference in Name, but also in Protocol, SourcePortRange, DestinationPortRange, SourceAddressPrefix, DestinationAddressPrefix, Access, and Direction. ([#55752](https://github.com/kubernetes/kubernetes/pull/55752), [@kevinkim9264](https://github.com/kevinkim9264)) +* Add support to take nominated pods into account during scheduling to avoid starvation of higher priority pods. ([#55933](https://github.com/kubernetes/kubernetes/pull/55933), [@bsalamat](https://github.com/bsalamat)) +* Add Amazon NLB support - Fixes [#52173](https://github.com/kubernetes/kubernetes/pull/52173) ([#53400](https://github.com/kubernetes/kubernetes/pull/53400), [@micahhausler](https://github.com/micahhausler)) +* Extends deviceplugin to gracefully handle full device plugin lifecycle. ([#55088](https://github.com/kubernetes/kubernetes/pull/55088), [@jiayingz](https://github.com/jiayingz)) +* A new field is added to CRI container log format to support splitting a long log line into multiple lines. ([#55922](https://github.com/kubernetes/kubernetes/pull/55922), [@Random-Liu](https://github.com/Random-Liu)) +* [advanced audit]add a policy wide omitStage ([#54634](https://github.com/kubernetes/kubernetes/pull/54634), [@CaoShuFeng](https://github.com/CaoShuFeng)) +* Fix a bug in GCE multizonal clusters where PersistentVolumes were sometimes created in zones without nodes. ([#52322](https://github.com/kubernetes/kubernetes/pull/52322), [@davidz627](https://github.com/davidz627)) +* With this change ([#55845](https://github.com/kubernetes/kubernetes/pull/55845), [@rohitjogvmw](https://github.com/rohitjogvmw)) + * - User should be able to create k8s cluster which spans across multiple ESXi clusters, datacenters or even vCenters. + * - vSphere cloud provider (VCP) uses OS hostname and not vSphere Inventory VM Name. + * That means, now VCP can handle cases where user changes VM inventory name. + * - VCP can handle cases where VM migrates to other ESXi cluster or datacenter or vCenter. + * The only requirement is the shared storage. VCP needs shared storage on all Node VMs. +* The RBAC bootstrapping policy now allows authenticated users to create selfsubjectrulesreviews. ([#56095](https://github.com/kubernetes/kubernetes/pull/56095), [@ericchiang](https://github.com/ericchiang)) +* Defaulting of controller-manager options for --cluster-signing-cert-file and --cluster-signing-key-file is deprecated and will be removed in a later release. ([#54495](https://github.com/kubernetes/kubernetes/pull/54495), [@mikedanese](https://github.com/mikedanese)) +* Add ExtendedResourceToleration admission controller. This facilitates creation of dedicated nodes with extended resources. If operators want to create dedicated nodes with extended resources (like GPUs, FPGAs etc.), they are expected to taint the node with extended resource name as the key. This admission controller, if enabled, automatically adds tolerations for such taints to pods requesting extended resources, so users don't have to manually add these tolerations. ([#55839](https://github.com/kubernetes/kubernetes/pull/55839), [@mindprince](https://github.com/mindprince)) +* Move unreachable taint key out of alpha. ([#54208](https://github.com/kubernetes/kubernetes/pull/54208), [@resouer](https://github.com/resouer)) + * Please note the existing pods with the alpha toleration should be updated by user himself to tolerate the GA taint. +* add GRS, RAGRS storage account type support for azure disk ([#55931](https://github.com/kubernetes/kubernetes/pull/55931), [@andyzhangx](https://github.com/andyzhangx)) +* Upgrading the kubernetes-master units now results in staged upgrades just like the kubernetes-worker nodes. Use the upgrade action in order to continue the upgrade process on each unit such as `juju run-action kubernetes-master/0 upgrade` ([#55990](https://github.com/kubernetes/kubernetes/pull/55990), [@hyperbolic2346](https://github.com/hyperbolic2346)) +* Using ipset doing SNAT and packet filtering in IPVS kube-proxy ([#54219](https://github.com/kubernetes/kubernetes/pull/54219), [@m1093782566](https://github.com/m1093782566)) +* Add a new scheduling queue that helps schedule the highest priority pending pod first. ([#55109](https://github.com/kubernetes/kubernetes/pull/55109), [@bsalamat](https://github.com/bsalamat)) +* Adds to **kubeadm upgrade apply**, a new **--etcd-upgrade** keyword. When this keyword is specified, etcd's static pod gets upgraded to the etcd version officially recommended for a target kubernetes release. ([#55010](https://github.com/kubernetes/kubernetes/pull/55010), [@sbezverk](https://github.com/sbezverk)) +* Adding vishh as an reviewer/approver for hack directory ([#54007](https://github.com/kubernetes/kubernetes/pull/54007), [@vishh](https://github.com/vishh)) +* The `GenericAdmissionWebhook` is renamed as `ValidatingAdmissionWebhook`. Please update you apiserver configuration file to use the new name to pass to the apiserver's `--admission-control` flag. ([#55988](https://github.com/kubernetes/kubernetes/pull/55988), [@caesarxuchao](https://github.com/caesarxuchao)) +* iSCSI Persistent Volume Sources can now reference CHAP Secrets in namespaces other than the namespace of the bound Persistent Volume Claim ([#51530](https://github.com/kubernetes/kubernetes/pull/51530), [@rootfs](https://github.com/rootfs)) +* Bugfix: master startup script on GCP no longer fails randomly due to concurrent iptables invocations. ([#55945](https://github.com/kubernetes/kubernetes/pull/55945), [@x13n](https://github.com/x13n)) +* fix azure disk storage account init issue ([#55927](https://github.com/kubernetes/kubernetes/pull/55927), [@andyzhangx](https://github.com/andyzhangx)) +* Allow code-generator tags in the 2nd closest comment block and directly above a statement. ([#55233](https://github.com/kubernetes/kubernetes/pull/55233), [@sttts](https://github.com/sttts)) +* Ensure additional resource tags are set/updated AWS load balancers ([#55731](https://github.com/kubernetes/kubernetes/pull/55731), [@georgebuckerfield](https://github.com/georgebuckerfield)) +* `kubectl get` will now use OpenAPI schema extensions by default to select columns for custom types. ([#53483](https://github.com/kubernetes/kubernetes/pull/53483), [@apelisse](https://github.com/apelisse)) +* AWS: Apply taint to a node if volumes being attached to it are stuck in attaching state ([#55558](https://github.com/kubernetes/kubernetes/pull/55558), [@gnufied](https://github.com/gnufied)) +* Kubeadm now supports for Kubelet Dynamic Configuration. ([#55803](https://github.com/kubernetes/kubernetes/pull/55803), [@xiangpengzhao](https://github.com/xiangpengzhao)) +* Added mutation supports to admission webhooks. ([#54892](https://github.com/kubernetes/kubernetes/pull/54892), [@caesarxuchao](https://github.com/caesarxuchao)) +* Upgrade to go1.9.2 ([#55420](https://github.com/kubernetes/kubernetes/pull/55420), [@cblecker](https://github.com/cblecker)) +* If a non-absolute mountPath is passed to the kubelet, prefix it with the appropriate root path. ([#55665](https://github.com/kubernetes/kubernetes/pull/55665), [@brendandburns](https://github.com/brendandburns)) +* action-required: please update your admission webhook to use the latest [Admission API](https://github.com/kubernetes/api/tree/master/admission). ([#55829](https://github.com/kubernetes/kubernetes/pull/55829), [@cheftako](https://github.com/cheftako)) + * `admission/v1alpha1#AdmissionReview` now contains `AdmissionRequest` and `AdmissionResponse`. `AdmissionResponse` includes a `Patch` field to allow mutating webhooks to send json patch to the apiserver. +* support mount options in azure file ([#54674](https://github.com/kubernetes/kubernetes/pull/54674), [@andyzhangx](https://github.com/andyzhangx)) +* Support AWS ECR credentials in China ([#50108](https://github.com/kubernetes/kubernetes/pull/50108), [@zzq889](https://github.com/zzq889)) +* The EvictionHard, EvictionSoft, EvictionSoftGracePeriod, EvictionMinimumReclaim, SystemReserved, and KubeReserved fields in the KubeletConfiguration object (kubeletconfig/v1alpha1) are now of type map[string]string, which facilitates writing JSON and YAML files. ([#54823](https://github.com/kubernetes/kubernetes/pull/54823), [@mtaufen](https://github.com/mtaufen)) +* Added service annotation for AWS ELB SSL policy ([#54507](https://github.com/kubernetes/kubernetes/pull/54507), [@micahhausler](https://github.com/micahhausler)) +* Implement correction mechanism for dangling volumes attached for deleted pods ([#55491](https://github.com/kubernetes/kubernetes/pull/55491), [@gnufied](https://github.com/gnufied)) +* Promote validation for custom resources defined through CRD to beta ([#54647](https://github.com/kubernetes/kubernetes/pull/54647), [@colemickens](https://github.com/colemickens)) +* Octavia v2 now supported as a LB provider ([#55393](https://github.com/kubernetes/kubernetes/pull/55393), [@jamiehannaford](https://github.com/jamiehannaford)) +* Kubelet now exposes metrics for NVIDIA GPUs attached to the containers. ([#55188](https://github.com/kubernetes/kubernetes/pull/55188), [@mindprince](https://github.com/mindprince)) +* Addon manager supports HA masters. ([#55782](https://github.com/kubernetes/kubernetes/pull/55782), [@x13n](https://github.com/x13n)) +* Fix kubeadm reset crictl command ([#55717](https://github.com/kubernetes/kubernetes/pull/55717), [@runcom](https://github.com/runcom)) +* Fix code-generators to produce correct code when GroupName, PackageName and/or GoName differ. ([#55614](https://github.com/kubernetes/kubernetes/pull/55614), [@sttts](https://github.com/sttts)) +* Fixes bad conversion in host port chain name generating func which leads to some unreachable host ports. ([#55153](https://github.com/kubernetes/kubernetes/pull/55153), [@chenchun](https://github.com/chenchun)) +* Relative paths in the Kubelet's local config files (--init-config-dir) will be resolved relative to the location of the containing files. ([#55648](https://github.com/kubernetes/kubernetes/pull/55648), [@mtaufen](https://github.com/mtaufen)) +* kubeadm: Fix a bug on some OSes where the kubelet tried to mount a volume path that is non-existent and on a read-only filesystem ([#55320](https://github.com/kubernetes/kubernetes/pull/55320), [@andrewrynhard](https://github.com/andrewrynhard)) +* add hostIP and protocol to the original hostport predicates procedure in scheduler. ([#52421](https://github.com/kubernetes/kubernetes/pull/52421), [@WIZARD-CXY](https://github.com/WIZARD-CXY)) + + + +# v1.9.0-alpha.3 + +[Documentation](https://docs.k8s.io) & [Examples](https://releases.k8s.io/master/examples) + +## Downloads for v1.9.0-alpha.3 + + +filename | sha256 hash +-------- | ----------- +[kubernetes.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-alpha.3/kubernetes.tar.gz) | `dce2a70ca51fb4f8979645330f36c346b9c02be0501708380ae50956485a53a4` +[kubernetes-src.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-alpha.3/kubernetes-src.tar.gz) | `4a8c8eaf32c83968e18f75888ae0d432210262090893cad0a105eebab82b0302` + +### Client Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-client-darwin-386.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-alpha.3/kubernetes-client-darwin-386.tar.gz) | `354d6c8d65e4248c3393a3789e9394b8c31c63da4c42f3da60c7b8bc4713ad51` +[kubernetes-client-darwin-amd64.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-alpha.3/kubernetes-client-darwin-amd64.tar.gz) | `98c53e4108276535218f4c89c58974121cc28308cecf5bca676f68fa083a62c5` +[kubernetes-client-linux-386.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-alpha.3/kubernetes-client-linux-386.tar.gz) | `c0dc219073dcae6fb654f33ca6d83faf5f37a2dcba3cc86b32ea5f9e18054faa` +[kubernetes-client-linux-amd64.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-alpha.3/kubernetes-client-linux-amd64.tar.gz) | `df68fc512d173d1914f7863303cc0a4335439eb76000fa5a6134d5c454f4ef31` +[kubernetes-client-linux-arm64.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-alpha.3/kubernetes-client-linux-arm64.tar.gz) | `edbf086c5446a7b48bbf5ac0e65dacf472e7e2eb7ac434ffb4835b0c643363a4` +[kubernetes-client-linux-arm.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-alpha.3/kubernetes-client-linux-arm.tar.gz) | `138b02e0e96e9e30772e814d2650b40594e9f190442c9b31af5dcf4bd3c29fb2` +[kubernetes-client-linux-ppc64le.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-alpha.3/kubernetes-client-linux-ppc64le.tar.gz) | `8edb568048f64052e9ab3e2f0d9d9fee3a5c90667d00669d815c07cc1986eb03` +[kubernetes-client-linux-s390x.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-alpha.3/kubernetes-client-linux-s390x.tar.gz) | `9f0f0464041e85221cb65ab5908f7295d7237acdb6a39abff062e40be0a53b4c` +[kubernetes-client-windows-386.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-alpha.3/kubernetes-client-windows-386.tar.gz) | `a9d4b6014c2856b0602b7124dad41f2f932cccea7f48ba57583352f0fbf2710f` +[kubernetes-client-windows-amd64.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-alpha.3/kubernetes-client-windows-amd64.tar.gz) | `16827a05b0538ab8ef6e47b173dc5ad1c4398070324b0d2fc0510ad1efe66567` + +### Server Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-alpha.3/kubernetes-server-linux-amd64.tar.gz) | `e2aad29fff3cc3a98c642d8bc021a6caa42b4143696ca9d42a1ae3f7e803e777` +[kubernetes-server-linux-arm64.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-alpha.3/kubernetes-server-linux-arm64.tar.gz) | `a7e2370d29086dadcb59fc4c3f6e88610ef72ff168577cc1854b4e9c221cad8a` +[kubernetes-server-linux-arm.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-alpha.3/kubernetes-server-linux-arm.tar.gz) | `b8da04e06946b221b2ac4f6ebc8e0900cf8e750f0ca5d2e213984644048d1903` +[kubernetes-server-linux-ppc64le.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-alpha.3/kubernetes-server-linux-ppc64le.tar.gz) | `539db8044dcacc154fff92029d7c18ac9a68de426477cabcd52e01053e8de6e6` +[kubernetes-server-linux-s390x.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-alpha.3/kubernetes-server-linux-s390x.tar.gz) | `d793be99d39f1f7b55d381f656b059e4cd78418a6c6bcc77c2c026db82e98769` + +### Node Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-alpha.3/kubernetes-node-linux-amd64.tar.gz) | `22dae55dd97026eae31562fde6d8459f1594b050313ef294e009144aa8c27a8e` +[kubernetes-node-linux-arm64.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-alpha.3/kubernetes-node-linux-arm64.tar.gz) | `8d9bd9307cd5463b2e13717c862e171e20a1ba29a91d86fa3918a460006c823b` +[kubernetes-node-linux-arm.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-alpha.3/kubernetes-node-linux-arm.tar.gz) | `c696f882b4a95b13c8cf3c2e05695decb81407359911fba169a308165b06be55` +[kubernetes-node-linux-ppc64le.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-alpha.3/kubernetes-node-linux-ppc64le.tar.gz) | `611a0e04b1014263e66be91ef108a4a56291cae1438da562b157d04dfe84fd1a` +[kubernetes-node-linux-s390x.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-alpha.3/kubernetes-node-linux-s390x.tar.gz) | `61b619e3af7fcb836072c4b855978d7d76d6256aa99b9378488f063494518a0e` +[kubernetes-node-windows-amd64.tar.gz](https://storage.googleapis.com/kubernetes-release-dashpole/release/v1.9.0-alpha.3/kubernetes-node-windows-amd64.tar.gz) | `c274258e4379f0b50b2023f659bc982a82783c3de3ae07ef2759159300175a8a` + +## Changelog since v1.9.0-alpha.2 + +### Action Required + +* action required: The `storage.k8s.io/v1beta1` API and `volume.beta.kubernetes.io/storage-class` annotation are deprecated. They will be removed in a future release. Please use v1 API and field `v1.PersistentVolumeClaim.Spec.StorageClassName`/`v1.PersistentVolume.Spec.StorageClassName` instead. ([#53580](https://github.com/kubernetes/kubernetes/pull/53580), [@xiangpengzhao](https://github.com/xiangpengzhao)) +* action required: Deprecated flags `--portal-net` and `service-node-ports` of kube-apiserver are removed. ([#52547](https://github.com/kubernetes/kubernetes/pull/52547), [@xiangpengzhao](https://github.com/xiangpengzhao)) +* The `node.kubernetes.io/memory-pressure` taint now respects the configured whitelist. If you need to use it, you'll have to add it to the whitelist. ([#55251](https://github.com/kubernetes/kubernetes/pull/55251), [@deads2k](https://github.com/deads2k)) + +### Other notable changes + +* hyperkube: add cloud-controller-manager ([#54197](https://github.com/kubernetes/kubernetes/pull/54197), [@colemickens](https://github.com/colemickens)) +* Metrics have been added for monitoring admission plugins, including the new dynamic (webhook-based) ones. ([#55183](https://github.com/kubernetes/kubernetes/pull/55183), [@jpbetz](https://github.com/jpbetz)) +* Addon manager supports HA masters. ([#55466](https://github.com/kubernetes/kubernetes/pull/55466), [@x13n](https://github.com/x13n)) +* - Add PodSecurityPolicies for cluster addons ([#55509](https://github.com/kubernetes/kubernetes/pull/55509), [@tallclair](https://github.com/tallclair)) + * - Remove SSL cert HostPath volumes from heapster addons +* Add iptables rules to allow Pod traffic even when default iptables policy is to reject. ([#52569](https://github.com/kubernetes/kubernetes/pull/52569), [@tmjd](https://github.com/tmjd)) +* Validate positive capacity for PVs and PVCs. ([#55532](https://github.com/kubernetes/kubernetes/pull/55532), [@ianchakeres](https://github.com/ianchakeres)) +* Kubelet supports running mount utilities and final mount in a container instead running them on the host. ([#53440](https://github.com/kubernetes/kubernetes/pull/53440), [@jsafrane](https://github.com/jsafrane)) +* The PodSecurityPolicy annotation `kubernetes.io/psp` on pods is only set once on create. ([#55486](https://github.com/kubernetes/kubernetes/pull/55486), [@sttts](https://github.com/sttts)) +* The apiserver sends external versioned object to the admission webhooks now. Please update the webhooks to expect admissionReview.spec.object.raw to be serialized external versions of objects. ([#55127](https://github.com/kubernetes/kubernetes/pull/55127), [@caesarxuchao](https://github.com/caesarxuchao)) +* RBAC ClusterRoles can now select other roles to aggregate ([#54005](https://github.com/kubernetes/kubernetes/pull/54005), [@deads2k](https://github.com/deads2k)) +* GCE nodes with NVIDIA GPUs attached now expose `nvidia.com/gpu` as a resource instead of `alpha.kubernetes.io/nvidia-gpu`. ([#54826](https://github.com/kubernetes/kubernetes/pull/54826), [@mindprince](https://github.com/mindprince)) +* Remove docker dependency during kubelet start up ([#54405](https://github.com/kubernetes/kubernetes/pull/54405), [@resouer](https://github.com/resouer)) +* Fix session affinity issue with external load balancer traffic when ExternalTrafficPolicy=Local. ([#55519](https://github.com/kubernetes/kubernetes/pull/55519), [@MrHohn](https://github.com/MrHohn)) +* Add the concurrent service sync flag to the Cloud Controller Manager to allow changing the number of workers. (`--concurrent-service-syncs`) ([#55561](https://github.com/kubernetes/kubernetes/pull/55561), [@jhorwit2](https://github.com/jhorwit2)) +* move IsMissingVersion comments ([#55523](https://github.com/kubernetes/kubernetes/pull/55523), [@chenpengdev](https://github.com/chenpengdev)) +* The dynamic admission webhook now supports a URL in addition to a service reference, to accommodate out-of-cluster webhooks. ([#54889](https://github.com/kubernetes/kubernetes/pull/54889), [@lavalamp](https://github.com/lavalamp)) +* Correct wording of kubeadm upgrade response for missing ConfigMap. ([#53337](https://github.com/kubernetes/kubernetes/pull/53337), [@jmhardison](https://github.com/jmhardison)) +* add create priorityclass sub command ([#54858](https://github.com/kubernetes/kubernetes/pull/54858), [@wackxu](https://github.com/wackxu)) +* Added namespaceSelector to externalAdmissionWebhook configuration to allow applying webhooks only to objects in the namespaces that have matching labels. ([#54727](https://github.com/kubernetes/kubernetes/pull/54727), [@caesarxuchao](https://github.com/caesarxuchao)) +* Base images bumped to Debian Stretch (9) ([#52744](https://github.com/kubernetes/kubernetes/pull/52744), [@rphillips](https://github.com/rphillips)) +* [fluentd-elasticsearch addon] Elasticsearch service name can be overridden via env variable ELASTICSEARCH_SERVICE_NAME ([#54215](https://github.com/kubernetes/kubernetes/pull/54215), [@mrahbar](https://github.com/mrahbar)) +* Increase waiting time (120s) for docker startup in health-monitor.sh ([#54099](https://github.com/kubernetes/kubernetes/pull/54099), [@dchen1107](https://github.com/dchen1107)) +* not calculate new priority when user update other spec of a pod ([#55221](https://github.com/kubernetes/kubernetes/pull/55221), [@CaoShuFeng](https://github.com/CaoShuFeng)) +* kubectl create pdb will no longer set the min-available field by default. ([#53047](https://github.com/kubernetes/kubernetes/pull/53047), [@yuexiao-wang](https://github.com/yuexiao-wang)) +* StatefulSet status now has support for conditions, making it consistent with other core controllers in v1 ([#55268](https://github.com/kubernetes/kubernetes/pull/55268), [@foxish](https://github.com/foxish)) +* kubeadm: use the CRI for preflights checks ([#55055](https://github.com/kubernetes/kubernetes/pull/55055), [@runcom](https://github.com/runcom)) +* kubeadm now produces error during preflight checks if swap is enabled. Users, who can setup kubelet to run in unsupported environment with enabled swap, will be able to skip that preflight check. ([#55399](https://github.com/kubernetes/kubernetes/pull/55399), [@kad](https://github.com/kad)) +* - kubeadm will produce error if kubelet too new for control plane ([#54868](https://github.com/kubernetes/kubernetes/pull/54868), [@kad](https://github.com/kad)) +* validate if default and defaultRequest match when creating LimitRange for GPU and hugepages. ([#54919](https://github.com/kubernetes/kubernetes/pull/54919), [@tianshapjq](https://github.com/tianshapjq)) +* Add extra-args configs to kubernetes-worker charm ([#55334](https://github.com/kubernetes/kubernetes/pull/55334), [@Cynerva](https://github.com/Cynerva)) +* Restored kube-proxy's support for 0 values for conntrack min, max, max per core, tcp close wait timeout, and tcp established timeout. ([#55261](https://github.com/kubernetes/kubernetes/pull/55261), [@ncdc](https://github.com/ncdc)) +* Audit policy files without apiVersion and kind are treated as invalid. ([#54267](https://github.com/kubernetes/kubernetes/pull/54267), [@ericchiang](https://github.com/ericchiang)) +* ReplicationController now shares its underlying controller implementation with ReplicaSet to reduce the maintenance burden going forward. However, they are still separate resources and there should be no externally visible effects from this change. ([#49429](https://github.com/kubernetes/kubernetes/pull/49429), [@enisoc](https://github.com/enisoc)) +* Add limitrange/resourcequota/downward_api e2e tests for local ephemeral storage ([#52523](https://github.com/kubernetes/kubernetes/pull/52523), [@NickrenREN](https://github.com/NickrenREN)) +* Support copying "options" in resolv.conf into pod sandbox when dnsPolicy is Default ([#54773](https://github.com/kubernetes/kubernetes/pull/54773), [@phsiao](https://github.com/phsiao)) +* Fix support for configmap resource lock type in CCM ([#55125](https://github.com/kubernetes/kubernetes/pull/55125), [@jhorwit2](https://github.com/jhorwit2)) +* The minimum supported go version bumps to 1.9.1. ([#55301](https://github.com/kubernetes/kubernetes/pull/55301), [@xiangpengzhao](https://github.com/xiangpengzhao)) +* GCE: provide an option to disable docker's live-restore on COS/ubuntu ([#55260](https://github.com/kubernetes/kubernetes/pull/55260), [@yujuhong](https://github.com/yujuhong)) +* Azure NSG rules for services exposed via external load balancer ([#54177](https://github.com/kubernetes/kubernetes/pull/54177), [@itowlson](https://github.com/itowlson)) + * now limit the destination IP address to the relevant front end load + * balancer IP. +* DaemonSet status now has a new field named "conditions", making it consistent with other workloads controllers. ([#55272](https://github.com/kubernetes/kubernetes/pull/55272), [@janetkuo](https://github.com/janetkuo)) +* kubeadm: Add an experimental mode to deploy CoreDNS instead of KubeDNS ([#52501](https://github.com/kubernetes/kubernetes/pull/52501), [@rajansandeep](https://github.com/rajansandeep)) +* Allow HPA to read custom metrics. ([#54854](https://github.com/kubernetes/kubernetes/pull/54854), [@kawych](https://github.com/kawych)) +* Fixed 'Schedulercache is corrupted' error in kube-scheduler ([#55262](https://github.com/kubernetes/kubernetes/pull/55262), [@liggitt](https://github.com/liggitt)) +* API discovery failures no longer crash the kube controller manager via the garbage collector. ([#55259](https://github.com/kubernetes/kubernetes/pull/55259), [@ironcladlou](https://github.com/ironcladlou)) +* The kube-scheduler command now supports a `--config` flag which is the location of a file containing a serialized scheduler configuration. Most other kube-scheduler flags are now deprecated. ([#52562](https://github.com/kubernetes/kubernetes/pull/52562), [@ironcladlou](https://github.com/ironcladlou)) +* add field selector for kubectl get ([#50140](https://github.com/kubernetes/kubernetes/pull/50140), [@dixudx](https://github.com/dixudx)) +* Removes Priority Admission Controller from kubeadm since it's alpha. ([#55237](https://github.com/kubernetes/kubernetes/pull/55237), [@andrewsykim](https://github.com/andrewsykim)) +* Add support for the webhook authorizer to make a Deny decision that short-circuits the union authorizer and immediately returns Deny. ([#53273](https://github.com/kubernetes/kubernetes/pull/53273), [@mikedanese](https://github.com/mikedanese)) +* kubeadm init: fix a bug that prevented the --token-ttl flag and tokenTTL configuration value from working as expected for infinite (0) values. ([#54640](https://github.com/kubernetes/kubernetes/pull/54640), [@mattmoyer](https://github.com/mattmoyer)) +* Add CRI log parsing library at pkg/kubelet/apis/cri/logs ([#55140](https://github.com/kubernetes/kubernetes/pull/55140), [@feiskyer](https://github.com/feiskyer)) +* Add extra-args configs for scheduler and controller-manager to kubernetes-master charm ([#55185](https://github.com/kubernetes/kubernetes/pull/55185), [@Cynerva](https://github.com/Cynerva)) +* Add masquerading rules by default to GCE/GKE ([#55178](https://github.com/kubernetes/kubernetes/pull/55178), [@dnardo](https://github.com/dnardo)) +* Upgraded Azure SDK to v11.1.1. ([#54971](https://github.com/kubernetes/kubernetes/pull/54971), [@itowlson](https://github.com/itowlson)) +* Disable the termination grace period for the calico/node add-on DaemonSet to reduce downtime during a rolling upgrade or deletion. ([#55015](https://github.com/kubernetes/kubernetes/pull/55015), [@fasaxc](https://github.com/fasaxc)) +* Google KMS integration was removed from in-tree in favor of a out-of-process extension point that will be used for all KMS providers. ([#54759](https://github.com/kubernetes/kubernetes/pull/54759), [@sakshamsharma](https://github.com/sakshamsharma)) +* kubeadm: reset: use crictl to reset containers ([#54721](https://github.com/kubernetes/kubernetes/pull/54721), [@runcom](https://github.com/runcom)) +* Check for available volume before attach/delete operation in EBS ([#55008](https://github.com/kubernetes/kubernetes/pull/55008), [@gnufied](https://github.com/gnufied)) +* DaemonSet, Deployment, ReplicaSet, and StatefulSet have been promoted to GA and are available in the apps/v1 group version. ([#53679](https://github.com/kubernetes/kubernetes/pull/53679), [@kow3ns](https://github.com/kow3ns)) +* In conversion-gen removed Kubernetes core API from default extra-peer-dirs. ([#54394](https://github.com/kubernetes/kubernetes/pull/54394), [@sttts](https://github.com/sttts)) +* Fix IPVS availability check ([#51874](https://github.com/kubernetes/kubernetes/pull/51874), [@vfreex](https://github.com/vfreex)) +* ScaleIO driver completely removes dependency on drv_cfg binary so a Kubernetes cluster can easily run a containerized kubelet. ([#54956](https://github.com/kubernetes/kubernetes/pull/54956), [@vladimirvivien](https://github.com/vladimirvivien)) +* Avoid unnecessary spam in kube-controller-manager log if --cluster-cidr is not specified and --allocate-node-cidrs is false. ([#54934](https://github.com/kubernetes/kubernetes/pull/54934), [@akosiaris](https://github.com/akosiaris)) +* It is now possible to set multiple manifest url headers via the Kubelet's --manifest-url-header flag. Multiple headers for the same key will be added in the order provided. The ManifestURLHeader field in KubeletConfiguration object (kubeletconfig/v1alpha1) is now a map[string][]string, which facilitates writing JSON and YAML files. ([#54643](https://github.com/kubernetes/kubernetes/pull/54643), [@mtaufen](https://github.com/mtaufen)) +* Add support for PodSecurityPolicy on GCE: `ENABLE_POD_SECURITY_POLICY=true` enables the admission controller, and installs policies for default addons. ([#52367](https://github.com/kubernetes/kubernetes/pull/52367), [@tallclair](https://github.com/tallclair)) +* In PodTolerationRestriction admisson plugin, if namespace level tolerations are empty, now they override cluster level tolerations. ([#54812](https://github.com/kubernetes/kubernetes/pull/54812), [@aveshagarwal](https://github.com/aveshagarwal)) +* - Fix handling of IPv6 URLs in NO_PROXY. ([#53898](https://github.com/kubernetes/kubernetes/pull/53898), [@kad](https://github.com/kad)) +* Added extra_sans config option to kubeapi-load-balancer charm. This allows the user to specify extra SAN entries on the certificate generated for the load balancer. ([#54947](https://github.com/kubernetes/kubernetes/pull/54947), [@hyperbolic2346](https://github.com/hyperbolic2346)) +* set leveled logging (v=4) for 'updating container' message ([#54865](https://github.com/kubernetes/kubernetes/pull/54865), [@phsiao](https://github.com/phsiao)) +* Fix a bug where pod address is not removed from endpoints object while pod is in graceful termination. ([#54828](https://github.com/kubernetes/kubernetes/pull/54828), [@freehan](https://github.com/freehan)) +* kubeadm: Add support for adding a Windows node ([#53553](https://github.com/kubernetes/kubernetes/pull/53553), [@bsteciuk](https://github.com/bsteciuk)) + + + +# v1.9.0-alpha.2 + +[Documentation](https://docs.k8s.io) & [Examples](https://releases.k8s.io/master/examples) + +## Downloads for v1.9.0-alpha.2 + + +filename | sha256 hash +-------- | ----------- +[kubernetes.tar.gz](https://dl.k8s.io/v1.9.0-alpha.2/kubernetes.tar.gz) | `9d548271e8475171114b3b68323ab3c0e024cf54e25debe4702ffafe3f1d0952` +[kubernetes-src.tar.gz](https://dl.k8s.io/v1.9.0-alpha.2/kubernetes-src.tar.gz) | `99901fa7f996ddf75ecab7fcd1d33a3faca38e9d1398daa2ae30c9b3ac6a71ce` + +### Client Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-client-darwin-386.tar.gz](https://dl.k8s.io/v1.9.0-alpha.2/kubernetes-client-darwin-386.tar.gz) | `5a5e1ce20db98d7f7f0c88957440ab6c7d4b4a4dfefcb31dcd1d6546e9db01d6` +[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.9.0-alpha.2/kubernetes-client-darwin-amd64.tar.gz) | `094481f8f650321f39ba79cd6348de5052db2bb3820f55a74cf5ce33d5c98701` +[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.9.0-alpha.2/kubernetes-client-linux-386.tar.gz) | `9a7d8e682a35772ba24bd3fa7a06fb153067b9387daa4db285e15dda75de757d` +[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.9.0-alpha.2/kubernetes-client-linux-amd64.tar.gz) | `3bb742ffed1a6a51cac01c16614873fea2864c2a4432057a15db90a9d7e40aed` +[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.9.0-alpha.2/kubernetes-client-linux-arm64.tar.gz) | `928936f06161e8a6f40196381d3e0dc215ca7e7dbc5f7fe6ebccd8d8268b8177` +[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.9.0-alpha.2/kubernetes-client-linux-arm.tar.gz) | `0a0fa24107f490db0ad57f33638b1aa9ba2baccb5f250caa75405d6612a3e10a` +[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.9.0-alpha.2/kubernetes-client-linux-ppc64le.tar.gz) | `a92f790d1a480318ea206d84d24d2c1d7e43c3683e60f22e7735b63ee73ccbb4` +[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.9.0-alpha.2/kubernetes-client-linux-s390x.tar.gz) | `1bfb7f056ad91fcbc50657fb9760310a0920c15a5770eaa74cf1a17b1725a232` +[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.9.0-alpha.2/kubernetes-client-windows-386.tar.gz) | `d1b0abbc9cd0376fa0d56096e42094db8a40485082b301723d05c8e78d8f4717` +[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.9.0-alpha.2/kubernetes-client-windows-amd64.tar.gz) | `69799ea8741caadac8949a120a455e08aba4d2babba6b63fba2ee9aaeb10c84b` + +### Server Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.9.0-alpha.2/kubernetes-server-linux-amd64.tar.gz) | `f3d9f67e94176aa65cffcc6557a7a251ec2384a3f89a81d3daedd8f8dd4c51a7` +[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.9.0-alpha.2/kubernetes-server-linux-arm64.tar.gz) | `3747b7e26d8bfba59c53b3f20d547e7e90cbb9356e513183ac27f901d7317630` +[kubernetes-server-linux-arm.tar.gz](https://dl.k8s.io/v1.9.0-alpha.2/kubernetes-server-linux-arm.tar.gz) | `397b7a49adf90735ceea54720dbf012c8566b34dadde911599bceefb507bc29a` +[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.9.0-alpha.2/kubernetes-server-linux-ppc64le.tar.gz) | `56f76ebb0788c4e23fc3ede36b52eb34b50b456bb5ff0cf7d78c383c04837565` +[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.9.0-alpha.2/kubernetes-server-linux-s390x.tar.gz) | `83d961657a50513db82bf421854c567206ccd34240eb8a017167cb98bdb6d38f` + +### Node Binaries + +filename | sha256 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.9.0-alpha.2/kubernetes-node-linux-amd64.tar.gz) | `1bb0f5ac920e27b4e51260a80fbfaa013ed7d446d58cd1f9d5f73af4d9517edf` +[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.9.0-alpha.2/kubernetes-node-linux-arm64.tar.gz) | `47635b9097fc6e3d9b1f1f2c3bd1558d144b1a26d1bf03cfc2e97a3c6db4c439` +[kubernetes-node-linux-arm.tar.gz](https://dl.k8s.io/v1.9.0-alpha.2/kubernetes-node-linux-arm.tar.gz) | `212117f1d027c79d50e7c7388951da40b440943748691ba82a3f9f6af75b3ed0` +[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.9.0-alpha.2/kubernetes-node-linux-ppc64le.tar.gz) | `f2b1d086d07bf2f807dbf02e1f0cd7f6528e57c55be9dadfcecde73e73980068` +[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.9.0-alpha.2/kubernetes-node-linux-s390x.tar.gz) | `ba6803a5c065b06cf43d1db674319008f15d4bc45900299d0b90105002af245e` +[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.9.0-alpha.2/kubernetes-node-windows-amd64.tar.gz) | `6d928e3bdba87db3b9198e02f84696f7345b4b78d07ff4ea048d47691c67b212` + +## Changelog since v1.8.0 + +### Action Required + +* PodSecurityPolicy: Fixes a compatibility issue that caused policies that previously allowed privileged pods to start forbidding them, due to an incorrect default value for `allowPrivilegeEscalation`. PodSecurityPolicy objects defined using a 1.8.0 client or server that intended to set `allowPrivilegeEscalation` to `false` must be reapplied after upgrading to 1.8.1. ([#53443](https://github.com/kubernetes/kubernetes/pull/53443), [@liggitt](https://github.com/liggitt)) +* RBAC objects are now stored in etcd in v1 format. After completing an upgrade to 1.9, RBAC objects (Roles, RoleBindings, ClusterRoles, ClusterRoleBindings) should be migrated to ensure all persisted objects are written in `v1` format, prior to `v1alpha1` support being removed in a future release. ([#52950](https://github.com/kubernetes/kubernetes/pull/52950), [@liggitt](https://github.com/liggitt)) + +### Other notable changes + +* Log error of failed healthz check ([#53048](https://github.com/kubernetes/kubernetes/pull/53048), [@mrIncompetent](https://github.com/mrIncompetent)) +* fix azure file mount limit issue on windows due to using drive letter ([#53629](https://github.com/kubernetes/kubernetes/pull/53629), [@andyzhangx](https://github.com/andyzhangx)) +* Update AWS SDK to 1.12.7 ([#53561](https://github.com/kubernetes/kubernetes/pull/53561), [@justinsb](https://github.com/justinsb)) +* The `kubernetes.io/created-by` annotation is no longer added to controller-created objects. Use the `metadata.ownerReferences` item that has `controller` set to `true` to determine which controller, if any, owns an object. ([#54445](https://github.com/kubernetes/kubernetes/pull/54445), [@crimsonfaith91](https://github.com/crimsonfaith91)) +* Fix overlay2 container disk metrics for Docker and CRI-O ([#54827](https://github.com/kubernetes/kubernetes/pull/54827), [@dashpole](https://github.com/dashpole)) +* Fix iptables FORWARD policy for Docker 1.13 in kubernetes-worker charm ([#54796](https://github.com/kubernetes/kubernetes/pull/54796), [@Cynerva](https://github.com/Cynerva)) +* Fix `kubeadm upgrade plan` for offline operation: ignore errors when trying to fetch latest versions from dl.k8s.io ([#54016](https://github.com/kubernetes/kubernetes/pull/54016), [@praseodym](https://github.com/praseodym)) +* fluentd now supports CRI log format. ([#54777](https://github.com/kubernetes/kubernetes/pull/54777), [@Random-Liu](https://github.com/Random-Liu)) +* Validate that PersistentVolumeSource is not changed during PV Update ([#54761](https://github.com/kubernetes/kubernetes/pull/54761), [@ianchakeres](https://github.com/ianchakeres)) +* If you are using the cloud provider API to determine the external host address of the apiserver, set --external-hostname explicitly instead. The cloud provider detection has been deprecated and will be removed in the future ([#54516](https://github.com/kubernetes/kubernetes/pull/54516), [@dims](https://github.com/dims)) +* Fixes discovery information for scale subresources in the apps API group ([#54683](https://github.com/kubernetes/kubernetes/pull/54683), [@liggitt](https://github.com/liggitt)) +* Optimize Repeated registration of AlgorithmProvider when ApplyFeatureGates ([#54047](https://github.com/kubernetes/kubernetes/pull/54047), [@kuramal](https://github.com/kuramal)) +* `kubectl get` will by default fetch large lists of resources in chunks of up to 500 items rather than requesting all resources up front from the server. This reduces the perceived latency of managing large clusters since the server returns the first set of results to the client much more quickly. A new flag `--chunk-size=SIZE` may be used to alter the number of items or disable this feature when `0` is passed. This is a beta feature. ([#53768](https://github.com/kubernetes/kubernetes/pull/53768), [@smarterclayton](https://github.com/smarterclayton)) +* Add a new feature gate for enabling an alpha annotation which, if present, excludes the annotated node from being added to a service load balancers. ([#54644](https://github.com/kubernetes/kubernetes/pull/54644), [@brendandburns](https://github.com/brendandburns)) +* Implement graceful shutdown of the kube-apiserver by waiting for open connections to finish before exiting. Moreover, the audit backend will stop dropping events on shutdown. ([#53695](https://github.com/kubernetes/kubernetes/pull/53695), [@hzxuzhonghu](https://github.com/hzxuzhonghu)) +* fix warning messages due to GetMountRefs func not implemented in windows ([#52401](https://github.com/kubernetes/kubernetes/pull/52401), [@andyzhangx](https://github.com/andyzhangx)) +* Object count quotas supported on all standard resources using `count/.` syntax ([#54320](https://github.com/kubernetes/kubernetes/pull/54320), [@derekwaynecarr](https://github.com/derekwaynecarr)) +* Add openssh-client back into the hyperkube image. This allows the gitRepo volume plugin to work properly. ([#54250](https://github.com/kubernetes/kubernetes/pull/54250), [@ixdy](https://github.com/ixdy)) +* Bump version of all prometheus-to-sd images to v0.2.2. ([#54635](https://github.com/kubernetes/kubernetes/pull/54635), [@loburm](https://github.com/loburm)) +* [fluentd-gcp addon] Fluentd now runs in its own network, not in the host one. ([#54395](https://github.com/kubernetes/kubernetes/pull/54395), [@crassirostris](https://github.com/crassirostris)) +* fix azure storage account num exhausting issue ([#54459](https://github.com/kubernetes/kubernetes/pull/54459), [@andyzhangx](https://github.com/andyzhangx)) +* Add Windows support to the system verification check ([#53730](https://github.com/kubernetes/kubernetes/pull/53730), [@bsteciuk](https://github.com/bsteciuk)) +* allow windows mount path ([#51240](https://github.com/kubernetes/kubernetes/pull/51240), [@andyzhangx](https://github.com/andyzhangx)) +* Development of Kubernetes Federation has moved to github.com/kubernetes/federation. This move out of tree also means that Federation will begin releasing separately from Kubernetes. The impact of this is Federation-specific behavior will no longer be included in kubectl, kubefed will no longer be released as part of Kubernetes, and the Federation servers will no longer be included in the hyperkube binary and image. ([#53816](https://github.com/kubernetes/kubernetes/pull/53816), [@marun](https://github.com/marun)) +* Metadata concealment on GCE is now controlled by the `ENABLE_METADATA_CONCEALMENT` env var. See cluster/gce/config-default.sh for more info. ([#54150](https://github.com/kubernetes/kubernetes/pull/54150), [@ihmccreery](https://github.com/ihmccreery)) +* Fixed a bug which is causes kube-apiserver to not run without specifying service-cluster-ip-range ([#52870](https://github.com/kubernetes/kubernetes/pull/52870), [@jennybuckley](https://github.com/jennybuckley)) +* the generic admission webhook is now available in the generic apiserver ([#54513](https://github.com/kubernetes/kubernetes/pull/54513), [@deads2k](https://github.com/deads2k)) +* ScaleIO persistent volumes now support referencing a secret in a namespace other than the bound persistent volume claim's namespace; this is controlled during provisioning with the `secretNamespace` storage class parameter; StoragePool and ProtectionDomain attributes no longer defaults to the value `default` ([#54013](https://github.com/kubernetes/kubernetes/pull/54013), [@vladimirvivien](https://github.com/vladimirvivien)) +* Feature gates now check minimum versions ([#54539](https://github.com/kubernetes/kubernetes/pull/54539), [@jamiehannaford](https://github.com/jamiehannaford)) +* fix azure pv crash due to volumeSource.ReadOnly value nil ([#54607](https://github.com/kubernetes/kubernetes/pull/54607), [@andyzhangx](https://github.com/andyzhangx)) +* Fix an issue where pods were briefly transitioned to a "Pending" state during the deletion process. ([#54593](https://github.com/kubernetes/kubernetes/pull/54593), [@dashpole](https://github.com/dashpole)) +* move getMaxVols function to predicates.go and add some NewVolumeCountPredicate funcs ([#51783](https://github.com/kubernetes/kubernetes/pull/51783), [@jiulongzaitian](https://github.com/jiulongzaitian)) +* Remove the LbaasV1 of OpenStack cloud provider, currently only support LbaasV2. ([#52717](https://github.com/kubernetes/kubernetes/pull/52717), [@FengyunPan](https://github.com/FengyunPan)) +* generic webhook admission now takes a config file which describes how to authenticate to webhook servers ([#54414](https://github.com/kubernetes/kubernetes/pull/54414), [@deads2k](https://github.com/deads2k)) +* The NodeController will not support kubelet 1.2. ([#48996](https://github.com/kubernetes/kubernetes/pull/48996), [@k82cn](https://github.com/k82cn)) +* - fluentd-gcp runs with a dedicated fluentd-gcp service account ([#54175](https://github.com/kubernetes/kubernetes/pull/54175), [@tallclair](https://github.com/tallclair)) + * - Stop mounting the host certificates into fluentd's prometheus-to-sd container +* fix azure disk mount failure on coreos and some other distros ([#54334](https://github.com/kubernetes/kubernetes/pull/54334), [@andyzhangx](https://github.com/andyzhangx)) +* Allow GCE users to configure the service account made available on their nodes ([#52868](https://github.com/kubernetes/kubernetes/pull/52868), [@ihmccreery](https://github.com/ihmccreery)) +* Load kernel modules automatically inside a kube-proxy pod ([#52003](https://github.com/kubernetes/kubernetes/pull/52003), [@vfreex](https://github.com/vfreex)) +* kube-apiserver: `--ssh-user` and `--ssh-keyfile` are now deprecated and will be removed in a future release. Users of SSH tunnel functionality used in Google Container Engine for the Master -> Cluster communication should plan to transition to alternate methods for bridging master and node networks. ([#54433](https://github.com/kubernetes/kubernetes/pull/54433), [@dims](https://github.com/dims)) +* Fix hyperkube kubelet --experimental-dockershim ([#54508](https://github.com/kubernetes/kubernetes/pull/54508), [@ivan4th](https://github.com/ivan4th)) +* Fix clustered datastore name to be absolute. ([#54438](https://github.com/kubernetes/kubernetes/pull/54438), [@pshahzeb](https://github.com/pshahzeb)) +* Fix for service controller so that it won't retry on doNotRetry service update failure. ([#54184](https://github.com/kubernetes/kubernetes/pull/54184), [@MrHohn](https://github.com/MrHohn)) +* Add support for RBAC support to Kubernetes via Juju ([#53820](https://github.com/kubernetes/kubernetes/pull/53820), [@ktsakalozos](https://github.com/ktsakalozos)) +* RBD Persistent Volume Sources can now reference User's Secret in namespaces other than the namespace of the bound Persistent Volume Claim ([#54302](https://github.com/kubernetes/kubernetes/pull/54302), [@sbezverk](https://github.com/sbezverk)) +* Apiserver proxy rewrites URL when service returns absolute path with request's host. ([#52556](https://github.com/kubernetes/kubernetes/pull/52556), [@roycaihw](https://github.com/roycaihw)) +* Logging cleanups ([#54443](https://github.com/kubernetes/kubernetes/pull/54443), [@bowei](https://github.com/bowei)) + * Updates kube-dns to use client-go 3 + * Updates containers to use alpine as the base image on all platforms + * Adds support for IPv6 +* add `--raw` to `kubectl create` to POST using the normal transport ([#54245](https://github.com/kubernetes/kubernetes/pull/54245), [@deads2k](https://github.com/deads2k)) +* Remove the --network-plugin-dir flag. ([#53564](https://github.com/kubernetes/kubernetes/pull/53564), [@supereagle](https://github.com/supereagle)) +* Introduces a polymorphic scale client, allowing HorizontalPodAutoscalers to properly function on scalable resources in any API group. ([#53743](https://github.com/kubernetes/kubernetes/pull/53743), [@DirectXMan12](https://github.com/DirectXMan12)) +* Add PodDisruptionBudget to scheduler cache. ([#53914](https://github.com/kubernetes/kubernetes/pull/53914), [@bsalamat](https://github.com/bsalamat)) +* - API machinery's httpstream/spdy calls now support CIDR notation for NO_PROXY ([#54413](https://github.com/kubernetes/kubernetes/pull/54413), [@kad](https://github.com/kad)) +* Added option lb-provider to OpenStack cloud provider config ([#54176](https://github.com/kubernetes/kubernetes/pull/54176), [@gonzolino](https://github.com/gonzolino)) +* Allow for configuring etcd hostname in the manifest ([#54403](https://github.com/kubernetes/kubernetes/pull/54403), [@wojtek-t](https://github.com/wojtek-t)) +* - kubeadm will warn users if access to IP ranges for Pods or Services will be done via HTTP proxy. ([#52792](https://github.com/kubernetes/kubernetes/pull/52792), [@kad](https://github.com/kad)) +* Resolves forbidden error when accessing replicasets and daemonsets via the apps API group ([#54309](https://github.com/kubernetes/kubernetes/pull/54309), [@liggitt](https://github.com/liggitt)) +* Cluster Autoscaler 1.0.1 ([#54298](https://github.com/kubernetes/kubernetes/pull/54298), [@mwielgus](https://github.com/mwielgus)) +* secret data containing Docker registry auth objects is now generated using the config.json format ([#53916](https://github.com/kubernetes/kubernetes/pull/53916), [@juanvallejo](https://github.com/juanvallejo)) +* Added support for SAN entries in the master node certificate via juju kubernetes-master config. ([#54234](https://github.com/kubernetes/kubernetes/pull/54234), [@hyperbolic2346](https://github.com/hyperbolic2346)) +* support imagePullSecrets and imagePullPolicy in kubefed init ([#50740](https://github.com/kubernetes/kubernetes/pull/50740), [@dixudx](https://github.com/dixudx)) +* update gRPC to v1.6.0 to pick up data race fix grpc/grpc-go#1316 ([#53128](https://github.com/kubernetes/kubernetes/pull/53128), [@dixudx](https://github.com/dixudx)) +* admission webhook registrations without a specific failure policy default to failing closed. ([#54162](https://github.com/kubernetes/kubernetes/pull/54162), [@deads2k](https://github.com/deads2k)) +* Device plugin Alpha API no longer supports returning artifacts per device as part of AllocateResponse. ([#53031](https://github.com/kubernetes/kubernetes/pull/53031), [@vishh](https://github.com/vishh)) +* admission webhook registration now allows URL paths ([#54145](https://github.com/kubernetes/kubernetes/pull/54145), [@deads2k](https://github.com/deads2k)) +* The Kubelet's --enable-custom-metrics flag is now marked deprecated. ([#54154](https://github.com/kubernetes/kubernetes/pull/54154), [@mtaufen](https://github.com/mtaufen)) +* Use multi-arch busybox image for e2e ([#54034](https://github.com/kubernetes/kubernetes/pull/54034), [@dixudx](https://github.com/dixudx)) +* sample-controller: add example CRD controller ([#52753](https://github.com/kubernetes/kubernetes/pull/52753), [@munnerz](https://github.com/munnerz)) +* RBAC PolicyRules now allow resource=`*/` to cover `any-resource/`. For example, `*/scale` covers `replicationcontroller/scale`. ([#53722](https://github.com/kubernetes/kubernetes/pull/53722), [@deads2k](https://github.com/deads2k)) +* Upgrade to go1.9 ([#51375](https://github.com/kubernetes/kubernetes/pull/51375), [@cblecker](https://github.com/cblecker)) +* Webhook always retries connection reset error. ([#53947](https://github.com/kubernetes/kubernetes/pull/53947), [@crassirostris](https://github.com/crassirostris)) +* fix PV Recycle failed on non-amd64 platfrom ([#53958](https://github.com/kubernetes/kubernetes/pull/53958), [@dixudx](https://github.com/dixudx)) +* Verbose option is added to each status function in CRI. Container runtime could return extra information in status response for debugging. ([#53965](https://github.com/kubernetes/kubernetes/pull/53965), [@Random-Liu](https://github.com/Random-Liu)) +* Fixed log fallback termination messages when using docker with journald log driver ([#52503](https://github.com/kubernetes/kubernetes/pull/52503), [@joelsmith](https://github.com/joelsmith)) +* falls back to parse Docker runtime version as generic if not semver ([#54040](https://github.com/kubernetes/kubernetes/pull/54040), [@dixudx](https://github.com/dixudx)) +* kubelet: prevent removal of default labels from Node API objects on startup ([#54073](https://github.com/kubernetes/kubernetes/pull/54073), [@liggitt](https://github.com/liggitt)) +* Change scheduler to skip pod with updates only on pod annotations ([#54008](https://github.com/kubernetes/kubernetes/pull/54008), [@yguo0905](https://github.com/yguo0905)) +* PodSecurityPolicy: when multiple policies allow a submitted pod, priority is given to ones which do not require any fields in the pod spec to be defaulted. If the pod must be defaulted, the first policy (ordered by name) that allows the pod is used. ([#52849](https://github.com/kubernetes/kubernetes/pull/52849), [@liggitt](https://github.com/liggitt)) +* Control HPA tolerance through the `horizontal-pod-autoscaler-tolerance` flag. ([#52275](https://github.com/kubernetes/kubernetes/pull/52275), [@mattjmcnaughton](https://github.com/mattjmcnaughton)) +* bump CNI to v0.6.0 ([#51250](https://github.com/kubernetes/kubernetes/pull/51250), [@dixudx](https://github.com/dixudx)) +* Improve resilience by annotating kube-dns addon with podAntiAffinity to prefer scheduling on different nodes. ([#52193](https://github.com/kubernetes/kubernetes/pull/52193), [@StevenACoffman](https://github.com/StevenACoffman)) +* Azure cloudprovider: Fix controller manager crash issue on a manually created k8s cluster. ([#53694](https://github.com/kubernetes/kubernetes/pull/53694), [@andyzhangx](https://github.com/andyzhangx)) +* Enable Priority admission control in kubeadm. ([#53175](https://github.com/kubernetes/kubernetes/pull/53175), [@andrewsykim](https://github.com/andrewsykim)) +* Add --no-negcache flag to kube-dns to prevent caching of NXDOMAIN responses. ([#53604](https://github.com/kubernetes/kubernetes/pull/53604), [@cblecker](https://github.com/cblecker)) +* kubelet provides more specific events when unable to sync pod ([#53857](https://github.com/kubernetes/kubernetes/pull/53857), [@derekwaynecarr](https://github.com/derekwaynecarr)) +* Kubelet evictions take pod priority into account ([#53542](https://github.com/kubernetes/kubernetes/pull/53542), [@dashpole](https://github.com/dashpole)) +* Adds a new controller which automatically cleans up Certificate Signing Requests that are ([#51840](https://github.com/kubernetes/kubernetes/pull/51840), [@jcbsmpsn](https://github.com/jcbsmpsn)) + * Approved and Issued, or Denied. +* Optimize random string generator to avoid multiple locks & use bit-masking ([#53720](https://github.com/kubernetes/kubernetes/pull/53720), [@shyamjvs](https://github.com/shyamjvs)) +* update cluster printer to enable --show-labels ([#53771](https://github.com/kubernetes/kubernetes/pull/53771), [@dixudx](https://github.com/dixudx)) +* add RequestReceivedTimestamp and StageTimestamp to audit event ([#52981](https://github.com/kubernetes/kubernetes/pull/52981), [@CaoShuFeng](https://github.com/CaoShuFeng)) +* Deprecation: The flag `etcd-quorum-read ` of kube-apiserver is deprecated and the ability to switch off quorum read will be removed in a future release. ([#53795](https://github.com/kubernetes/kubernetes/pull/53795), [@xiangpengzhao](https://github.com/xiangpengzhao)) +* Use separate client for leader election in scheduler to avoid starving leader election by regular scheduler operations. ([#53793](https://github.com/kubernetes/kubernetes/pull/53793), [@wojtek-t](https://github.com/wojtek-t)) +* Support autoprobing node-security-group for openstack cloud provider, Support multiple Security Groups for cluster's nodes. ([#50836](https://github.com/kubernetes/kubernetes/pull/50836), [@FengyunPan](https://github.com/FengyunPan)) +* fix a bug where disk pressure could trigger prematurely when using overlay2 ([#53684](https://github.com/kubernetes/kubernetes/pull/53684), [@dashpole](https://github.com/dashpole)) +* "kubectl cp" updated to honor destination names ([#51215](https://github.com/kubernetes/kubernetes/pull/51215), [@juanvallejo](https://github.com/juanvallejo)) +* kubeadm: Strip bootstrap tokens from the `kubeadm-config` ConfigMap ([#53559](https://github.com/kubernetes/kubernetes/pull/53559), [@fabriziopandini](https://github.com/fabriziopandini)) +* Skip podpreset test if the alpha feature setttings/v1alpha1 is disabled ([#53080](https://github.com/kubernetes/kubernetes/pull/53080), [@jennybuckley](https://github.com/jennybuckley)) +* Log when node is successfully initialized by Cloud Controller Manager ([#53517](https://github.com/kubernetes/kubernetes/pull/53517), [@andrewsykim](https://github.com/andrewsykim)) +* apiserver: --etcd-quorum-read now defaults to true, to ensure correct operation with HA etcd clusters ([#53717](https://github.com/kubernetes/kubernetes/pull/53717), [@liggitt](https://github.com/liggitt)) +* The Kubelet's feature gates are now specified as a map when provided via a JSON or YAML KubeletConfiguration, rather than as a string of key-value pairs. ([#53025](https://github.com/kubernetes/kubernetes/pull/53025), [@mtaufen](https://github.com/mtaufen)) +* Address a bug which allowed the horizontal pod autoscaler to allocate `desiredReplicas` > `maxReplicas` in certain instances. ([#53690](https://github.com/kubernetes/kubernetes/pull/53690), [@mattjmcnaughton](https://github.com/mattjmcnaughton)) +* Horizontal pod autoscaler uses REST clients through the kube-aggregator instead of the legacy client through the API server proxy. ([#53205](https://github.com/kubernetes/kubernetes/pull/53205), [@kawych](https://github.com/kawych)) +* Fix to prevent downward api change break on older versions ([#53673](https://github.com/kubernetes/kubernetes/pull/53673), [@timothysc](https://github.com/timothysc)) +* API chunking via the `limit` and `continue` request parameters is promoted to beta in this release. Client libraries using the Informer or ListWatch types will automatically opt in to chunking. ([#52949](https://github.com/kubernetes/kubernetes/pull/52949), [@smarterclayton](https://github.com/smarterclayton)) +* GCE: Bump GLBC version to [0.9.7](https://github.com/kubernetes/ingress/releases/tag/0.9.7). ([#53625](https://github.com/kubernetes/kubernetes/pull/53625), [@nikhiljindal](https://github.com/nikhiljindal)) +* kubelet's `--cloud-provider` flag no longer defaults to "auto-detect". If you want cloud-provider support in kubelet, you must set a specific cloud-provider explicitly. ([#53573](https://github.com/kubernetes/kubernetes/pull/53573), [@dims](https://github.com/dims)) +* Ignore extended resources that are not registered with kubelet during container resource allocation. ([#53547](https://github.com/kubernetes/kubernetes/pull/53547), [@jiayingz](https://github.com/jiayingz)) +* kubectl top pod and node should sort by namespace / name so that results don't jump around. ([#53560](https://github.com/kubernetes/kubernetes/pull/53560), [@dixudx](https://github.com/dixudx)) +* Added --dry-run option to `kubectl drain` ([#52440](https://github.com/kubernetes/kubernetes/pull/52440), [@juanvallejo](https://github.com/juanvallejo)) +* Fix a bug that prevents client-go metrics from being registered in prometheus in multiple components. ([#53434](https://github.com/kubernetes/kubernetes/pull/53434), [@crassirostris](https://github.com/crassirostris)) +* Adjust batching audit webhook default parameters: increase queue size, batch size, and initial backoff. Add throttling to the batching audit webhook. Default rate limit is 10 QPS. ([#53417](https://github.com/kubernetes/kubernetes/pull/53417), [@crassirostris](https://github.com/crassirostris)) +* Added integration test for TaintNodeByCondition. ([#53184](https://github.com/kubernetes/kubernetes/pull/53184), [@k82cn](https://github.com/k82cn)) +* Add API version apps/v1, and bump DaemonSet to apps/v1 ([#53278](https://github.com/kubernetes/kubernetes/pull/53278), [@janetkuo](https://github.com/janetkuo)) +* Change `kubeadm create token` to default to the group that almost everyone will want to use. The group is system:bootstrappers:kubeadm:default-node-token and is the group that kubeadm sets up, via an RBAC binding, for auto-approval (system:certificates.k8s.io:certificatesigningrequests:nodeclient). ([#53512](https://github.com/kubernetes/kubernetes/pull/53512), [@jbeda](https://github.com/jbeda)) +* Using OpenStack service catalog to do version detection ([#53115](https://github.com/kubernetes/kubernetes/pull/53115), [@FengyunPan](https://github.com/FengyunPan)) +* Fix metrics API group name in audit configuration ([#53493](https://github.com/kubernetes/kubernetes/pull/53493), [@piosz](https://github.com/piosz)) +* GCE: Fixes ILB sync on legacy networks and auto networks with unique subnet names ([#53410](https://github.com/kubernetes/kubernetes/pull/53410), [@nicksardo](https://github.com/nicksardo)) +* outputs `` for columns specified by `-o custom-columns` but not found in object ([#51750](https://github.com/kubernetes/kubernetes/pull/51750), [@jianhuiz](https://github.com/jianhuiz)) +* Metrics were added to network plugin to report latency of CNI operations ([#53446](https://github.com/kubernetes/kubernetes/pull/53446), [@sjenning](https://github.com/sjenning)) +* GCE: Fix issue deleting internal load balancers when the firewall resource may not exist. ([#53450](https://github.com/kubernetes/kubernetes/pull/53450), [@nicksardo](https://github.com/nicksardo)) +* Custom resources served through CustomResourceDefinition now support field selectors for `metadata.name` and `metadata.namespace`. ([#53345](https://github.com/kubernetes/kubernetes/pull/53345), [@ncdc](https://github.com/ncdc)) +* Add generate-groups.sh and generate-internal-groups.sh to k8s.io/code-generator to easily run generators against CRD or User API Server types. ([#52186](https://github.com/kubernetes/kubernetes/pull/52186), [@sttts](https://github.com/sttts)) +* kubelet `--cert-dir` now defaults to `/var/lib/kubelet/pki`, in order to ensure bootstrapped and rotated certificates persist beyond a reboot. resolves an issue in kubeadm with false-positive `/var/lib/kubelet is not empty` message during pre-flight checks ([#53317](https://github.com/kubernetes/kubernetes/pull/53317), [@liggitt](https://github.com/liggitt)) +* Fix multi-attach error spam in logs and events ([#53401](https://github.com/kubernetes/kubernetes/pull/53401), [@gnufied](https://github.com/gnufied)) +* Use `not-ready` to replace `notReady` in node condition taint keys. ([#51266](https://github.com/kubernetes/kubernetes/pull/51266), [@resouer](https://github.com/resouer)) +* Support completion for --clusterrole of kubectl create clusterrolebinding ([#48267](https://github.com/kubernetes/kubernetes/pull/48267), [@superbrothers](https://github.com/superbrothers)) +* Don't remove extended resource capacities that are not registered with kubelet from node status. ([#53353](https://github.com/kubernetes/kubernetes/pull/53353), [@jiayingz](https://github.com/jiayingz)) +* Kubectl: Remove swagger 1.2 validation. Also removes options `--use-openapi` and `--schema-cache-dir` as these are no longer needed. ([#53232](https://github.com/kubernetes/kubernetes/pull/53232), [@apelisse](https://github.com/apelisse)) +* `kubectl explain` now uses openapi rather than swagger 1.2. ([#53228](https://github.com/kubernetes/kubernetes/pull/53228), [@apelisse](https://github.com/apelisse)) +* Fixes a performance issue ([#51899](https://github.com/kubernetes/kubernetes/pull/51899)) identified in large-scale clusters when deleting thousands of pods simultaneously across hundreds of nodes, by actively removing containers of deleted pods, rather than waiting for periodic garbage collection and batching resulting pod API deletion requests. ([#53233](https://github.com/kubernetes/kubernetes/pull/53233), [@dashpole](https://github.com/dashpole)) +* Improve explanation of ReplicaSet ([#53403](https://github.com/kubernetes/kubernetes/pull/53403), [@rcorre](https://github.com/rcorre)) +* avoid newline " +" in the error to break log msg to 2 lines ([#49826](https://github.com/kubernetes/kubernetes/pull/49826), [@dixudx](https://github.com/dixudx)) +* don't recreate a mirror pod for static pod when node gets deleted ([#48339](https://github.com/kubernetes/kubernetes/pull/48339), [@dixudx](https://github.com/dixudx)) +* Fix permissions for Metrics Server. ([#53330](https://github.com/kubernetes/kubernetes/pull/53330), [@kawych](https://github.com/kawych)) +* default fail-swap-on to false for kubelet on kubernetes-worker charm ([#53386](https://github.com/kubernetes/kubernetes/pull/53386), [@wwwtyro](https://github.com/wwwtyro)) +* Add --etcd-compaction-interval to apiserver for controlling request of compaction to etcd3 from apiserver. ([#51765](https://github.com/kubernetes/kubernetes/pull/51765), [@mitake](https://github.com/mitake)) +* Apply algorithm in scheduler by feature gates. ([#52723](https://github.com/kubernetes/kubernetes/pull/52723), [@k82cn](https://github.com/k82cn)) +* etcd: update version to 3.1.10 ([#49393](https://github.com/kubernetes/kubernetes/pull/49393), [@hongchaodeng](https://github.com/hongchaodeng)) +* support nodeSelector in kubefed init ([#50749](https://github.com/kubernetes/kubernetes/pull/50749), [@dixudx](https://github.com/dixudx)) +* Upgrade fluentd-elasticsearch addon to Elasticsearch/Kibana 5.6.2 ([#53307](https://github.com/kubernetes/kubernetes/pull/53307), [@aknuds1](https://github.com/aknuds1)) +* enable to specific unconfined AppArmor profile ([#52395](https://github.com/kubernetes/kubernetes/pull/52395), [@dixudx](https://github.com/dixudx)) +* Update Influxdb image to latest version. ([#53319](https://github.com/kubernetes/kubernetes/pull/53319), [@kairen](https://github.com/kairen)) + * Update Grafana image to latest version. + * Change influxdb-grafana-controller resource to Deployment. +* Only do UpdateContainerResources when cpuset is set ([#53122](https://github.com/kubernetes/kubernetes/pull/53122), [@resouer](https://github.com/resouer)) +* Fixes an issue with RBAC reconciliation that could cause duplicated subjects in some bootstrapped rolebindings on each restart of the API server. ([#53239](https://github.com/kubernetes/kubernetes/pull/53239), [@enj](https://github.com/enj)) +* gce: remove compute-rw, see what breaks ([#53266](https://github.com/kubernetes/kubernetes/pull/53266), [@mikedanese](https://github.com/mikedanese)) +* Fix the bug that query Kubelet's stats summary with CRI stats enabled results in error. ([#53107](https://github.com/kubernetes/kubernetes/pull/53107), [@Random-Liu](https://github.com/Random-Liu)) +* kubeadm allows the kubelets in the cluster to automatically renew their client certificates ([#53252](https://github.com/kubernetes/kubernetes/pull/53252), [@kad](https://github.com/kad)) +* Fixes an issue with `kubectl set` commands encountering conversion errors for ReplicaSet and DaemonSet objects ([#53158](https://github.com/kubernetes/kubernetes/pull/53158), [@liggitt](https://github.com/liggitt)) +* RBAC: The default `admin` and `edit` roles now include read/write permissions and the `view` role includes read permissions on `poddisruptionbudget.policy` resources. ([#52654](https://github.com/kubernetes/kubernetes/pull/52654), [@liggitt](https://github.com/liggitt)) +* Change ImageGCManage to consume ImageFS stats from StatsProvider ([#53094](https://github.com/kubernetes/kubernetes/pull/53094), [@yguo0905](https://github.com/yguo0905)) +* BugFix: Exited containers are not Garbage Collected by the kubelet while the pod is running ([#53167](https://github.com/kubernetes/kubernetes/pull/53167), [@dashpole](https://github.com/dashpole)) +* - Improved generation of deb and rpm packages in bazel build ([#53163](https://github.com/kubernetes/kubernetes/pull/53163), [@kad](https://github.com/kad)) +* Add a label which prevents a node from being added to a cloud load balancer ([#53146](https://github.com/kubernetes/kubernetes/pull/53146), [@brendandburns](https://github.com/brendandburns)) +* Fixes an issue pulling pod specs referencing unqualified images from docker.io on centos/fedora/rhel ([#53161](https://github.com/kubernetes/kubernetes/pull/53161), [@dims](https://github.com/dims)) +* Update kube-dns to 1.14.5 ([#53153](https://github.com/kubernetes/kubernetes/pull/53153), [@bowei](https://github.com/bowei)) +* - kubeadm init can now deploy exact build from CI area by specifying ID with "ci/" prefix. Example: "ci/v1.9.0-alpha.1.123+01234567889" ([#53043](https://github.com/kubernetes/kubernetes/pull/53043), [@kad](https://github.com/kad)) + * - kubeadm upgrade apply supports all standard ways of specifying version via labels. Examples: stable-1.8, latest-1.8, ci/latest-1.9 and similar. +* - kubeadm 1.9 will detect and fail init or join pre-flight checks if kubelet is lower than 1.8.0-alpha ([#52913](https://github.com/kubernetes/kubernetes/pull/52913), [@kad](https://github.com/kad)) +* s390x ingress controller support ([#52663](https://github.com/kubernetes/kubernetes/pull/52663), [@wwwtyro](https://github.com/wwwtyro)) +* NONE ([#50532](https://github.com/kubernetes/kubernetes/pull/50532), [@steveperry-53](https://github.com/steveperry-53)) +* CRI: Add stdout/stderr fields to Exec and Attach requests. ([#52686](https://github.com/kubernetes/kubernetes/pull/52686), [@yujuhong](https://github.com/yujuhong)) +* NONE ([#53001](https://github.com/kubernetes/kubernetes/pull/53001), [@ericchiang](https://github.com/ericchiang)) +* Cluster Autoscaler 1.0.0 ([#53005](https://github.com/kubernetes/kubernetes/pull/53005), [@mwielgus](https://github.com/mwielgus)) +* Remove the --docker-exec-handler flag. Only native exec handler is supported. ([#52287](https://github.com/kubernetes/kubernetes/pull/52287), [@yujuhong](https://github.com/yujuhong)) +* The Rackspace cloud provider has been removed after a long deprecation period. It was deprecated because it duplicates a lot of the OpenStack logic and can no longer be maintained. Please use the OpenStack cloud provider instead. ([#52855](https://github.com/kubernetes/kubernetes/pull/52855), [@NickrenREN](https://github.com/NickrenREN)) +* Fixes an initializer bug where update requests which had an empty pending initializers list were erroneously rejected. ([#52558](https://github.com/kubernetes/kubernetes/pull/52558), [@jennybuckley](https://github.com/jennybuckley)) +* BulkVerifyVolumes() implementation for vSphere ([#52131](https://github.com/kubernetes/kubernetes/pull/52131), [@BaluDontu](https://github.com/BaluDontu)) +* added --list option to the `kubectl label` command ([#51971](https://github.com/kubernetes/kubernetes/pull/51971), [@juanvallejo](https://github.com/juanvallejo)) +* Removing `--prom-push-gateway` flag from e2e tests ([#52485](https://github.com/kubernetes/kubernetes/pull/52485), [@nielsole](https://github.com/nielsole)) +* If a container does not create a file at the `terminationMessagePath`, no message should be output about being unable to find the file. ([#52567](https://github.com/kubernetes/kubernetes/pull/52567), [@smarterclayton](https://github.com/smarterclayton)) +* Support German cloud for azure disk mount feature ([#50673](https://github.com/kubernetes/kubernetes/pull/50673), [@clement-buchart](https://github.com/clement-buchart)) +* Add s390x to juju kubernetes ([#52537](https://github.com/kubernetes/kubernetes/pull/52537), [@ktsakalozos](https://github.com/ktsakalozos)) +* Fix kubernetes charms not restarting services properly after host reboot on LXD ([#52445](https://github.com/kubernetes/kubernetes/pull/52445), [@Cynerva](https://github.com/Cynerva)) +* Add monitoring of Windows Server containers metrics in the kubelet via the stats/summary endpoint. ([#50396](https://github.com/kubernetes/kubernetes/pull/50396), [@bobbypage](https://github.com/bobbypage)) +* Restores redirect behavior for proxy subresources ([#52933](https://github.com/kubernetes/kubernetes/pull/52933), [@liggitt](https://github.com/liggitt)) +* A new service annotation has been added for services of type LoadBalancer on Azure, ([#51757](https://github.com/kubernetes/kubernetes/pull/51757), [@itowlson](https://github.com/itowlson)) + * to specify the subnet on which the service's front end IP should be provisioned. The + * annotation is service.beta.kubernetes.io/azure-load-balancer-internal-subnet and its + * value is the subnet name (not the subnet ARM ID). If omitted, the default is the + * master subnet. It is ignored if the service is not on Azure, if the type is not + * LoadBalancer, or if the load balancer is not internal. +* Adds a command-line argument to kube-apiserver called ([#51698](https://github.com/kubernetes/kubernetes/pull/51698), [@rphillips](https://github.com/rphillips)) + * --alpha-endpoint-reconciler-type=(master-count, lease, none) (default + * "master-count"). The original reconciler is 'master-count'. The 'lease' + * reconciler uses the storageapi and a TTL to keep alive an endpoint within the + * `kube-apiserver-endpoint` storage namespace. The 'none' reconciler is a noop + * reconciler that does not do anything. This is useful for self-hosted + * environments. +* Improved Italian translation for kubectl ([#51463](https://github.com/kubernetes/kubernetes/pull/51463), [@lucab85](https://github.com/lucab85)) +* Add a metric to the kubelet to monitor remaining lifetime of the certificate that ([#51031](https://github.com/kubernetes/kubernetes/pull/51031), [@jcbsmpsn](https://github.com/jcbsmpsn)) + * authenticates the kubelet to the API server. +* change AddEventHandlerWithResyncPeriod to AddEventHandler in factory.go ([#51582](https://github.com/kubernetes/kubernetes/pull/51582), [@jiulongzaitian](https://github.com/jiulongzaitian)) +* Validate that cronjob names are 52 characters or less ([#52733](https://github.com/kubernetes/kubernetes/pull/52733), [@julia-stripe](https://github.com/julia-stripe)) +* add readme file of ipvs ([#51937](https://github.com/kubernetes/kubernetes/pull/51937), [@Lion-Wei](https://github.com/Lion-Wei)) + + + # v1.9.0-alpha.1 [Documentation](https://docs.k8s.io) & [Examples](https://releases.k8s.io/master/examples) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 470f556adfd..9974dc68577 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,9 +1,7 @@ # Contributing -Information about contributing to the -[kubernetes code repo](README.md) lives in the -[kubernetes community repo](https://github.com/kubernetes/community) -(it's a big topic). +Welcome to Kubernetes! If you are interested in contributing to the [Kubernetes code repo](README.md) then checkout the [Contributor's Guide](https://git.k8s.io/community/contributors/guide/) +The [Kubernetes community repo](https://github.com/kubernetes/community) contains information on how the community is organized and other information that is pertinent to contributing. [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/CONTRIBUTING.md?pixel)]() diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 9522823b0b9..2ddf4896e85 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,6 +1,6 @@ { "ImportPath": "k8s.io/kubernetes", - "GoVersion": "go1.8", + "GoVersion": "go1.9", "GodepVersion": "v79", "Packages": [ "github.com/onsi/ginkgo/ginkgo", @@ -11,7 +11,7 @@ "Deps": [ { "ImportPath": "bitbucket.org/bertimus9/systemstat", - "Rev": "1468fd0db20598383c9393cccaa547de6ad99e5e" + "Rev": "6edb7bbcb021f6510db33e604f7e18861293a14a" }, { "ImportPath": "bitbucket.org/ww/goautoneg", @@ -30,33 +30,33 @@ }, { "ImportPath": "github.com/Azure/azure-sdk-for-go/arm/compute", - "Comment": "v10.0.4-beta-1-g786cc84", - "Rev": "786cc84138518bf7fd6d60e92fad1ac9d1a117ad" + "Comment": "v12.1.0-beta", + "Rev": "934e2462aeb6e0c14186dcfeedd73a026d1b8eeb" }, { "ImportPath": "github.com/Azure/azure-sdk-for-go/arm/containerregistry", - "Comment": "v10.0.4-beta-1-g786cc84", - "Rev": "786cc84138518bf7fd6d60e92fad1ac9d1a117ad" + "Comment": "v12.1.0-beta", + "Rev": "934e2462aeb6e0c14186dcfeedd73a026d1b8eeb" }, { "ImportPath": "github.com/Azure/azure-sdk-for-go/arm/disk", - "Comment": "v10.0.4-beta-1-g786cc84", - "Rev": "786cc84138518bf7fd6d60e92fad1ac9d1a117ad" + "Comment": "v12.1.0-beta", + "Rev": "934e2462aeb6e0c14186dcfeedd73a026d1b8eeb" }, { "ImportPath": "github.com/Azure/azure-sdk-for-go/arm/network", - "Comment": "v10.0.4-beta-1-g786cc84", - "Rev": "786cc84138518bf7fd6d60e92fad1ac9d1a117ad" + "Comment": "v12.1.0-beta", + "Rev": "934e2462aeb6e0c14186dcfeedd73a026d1b8eeb" }, { "ImportPath": "github.com/Azure/azure-sdk-for-go/arm/storage", - "Comment": "v10.0.4-beta-1-g786cc84", - "Rev": "786cc84138518bf7fd6d60e92fad1ac9d1a117ad" + "Comment": "v12.1.0-beta", + "Rev": "934e2462aeb6e0c14186dcfeedd73a026d1b8eeb" }, { "ImportPath": "github.com/Azure/azure-sdk-for-go/storage", - "Comment": "v10.0.4-beta-1-g786cc84", - "Rev": "786cc84138518bf7fd6d60e92fad1ac9d1a117ad" + "Comment": "v12.1.0-beta", + "Rev": "934e2462aeb6e0c14186dcfeedd73a026d1b8eeb" }, { "ImportPath": "github.com/Azure/go-ansiterm", @@ -68,41 +68,45 @@ }, { "ImportPath": "github.com/Azure/go-autorest/autorest", - "Comment": "v8.0.0", - "Rev": "58f6f26e200fa5dfb40c9cd1c83f3e2c860d779d" + "Comment": "v9.1.0", + "Rev": "e14a70c556c8e0db173358d1a903dca345a8e75e" }, { "ImportPath": "github.com/Azure/go-autorest/autorest/adal", - "Comment": "v8.0.0", - "Rev": "58f6f26e200fa5dfb40c9cd1c83f3e2c860d779d" + "Comment": "v9.1.0", + "Rev": "e14a70c556c8e0db173358d1a903dca345a8e75e" }, { "ImportPath": "github.com/Azure/go-autorest/autorest/azure", - "Comment": "v8.0.0", - "Rev": "58f6f26e200fa5dfb40c9cd1c83f3e2c860d779d" + "Comment": "v9.1.0", + "Rev": "e14a70c556c8e0db173358d1a903dca345a8e75e" }, { "ImportPath": "github.com/Azure/go-autorest/autorest/date", - "Comment": "v8.0.0", - "Rev": "58f6f26e200fa5dfb40c9cd1c83f3e2c860d779d" + "Comment": "v9.1.0", + "Rev": "e14a70c556c8e0db173358d1a903dca345a8e75e" }, { "ImportPath": "github.com/Azure/go-autorest/autorest/to", - "Comment": "v8.0.0", - "Rev": "58f6f26e200fa5dfb40c9cd1c83f3e2c860d779d" + "Comment": "v9.1.0", + "Rev": "e14a70c556c8e0db173358d1a903dca345a8e75e" }, { "ImportPath": "github.com/Azure/go-autorest/autorest/validation", - "Comment": "v8.0.0", - "Rev": "58f6f26e200fa5dfb40c9cd1c83f3e2c860d779d" + "Comment": "v9.1.0", + "Rev": "e14a70c556c8e0db173358d1a903dca345a8e75e" + }, + { + "ImportPath": "github.com/JeffAshton/win_pdh", + "Rev": "76bb4ee9f0ab50f77826f2a2ee7fb9d3880d6ec2" }, { "ImportPath": "github.com/MakeNowJust/heredoc", - "Rev": "1d91351acdc1cb2f2c995864674b754134b86ca7" + "Rev": "bb23615498cded5e105af4ce27de75b089cbe851" }, { "ImportPath": "github.com/Microsoft/go-winio", - "Comment": "v0.4.4-7-g7843996", + "Comment": "v0.4.5", "Rev": "78439966b38d69bf38227fbf57ac8a6fee70f69a" }, { @@ -162,168 +166,163 @@ }, { "ImportPath": "github.com/aws/aws-sdk-go/aws", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/awserr", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/awsutil", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/client", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/client/metadata", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/corehandlers", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/credentials", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/credentials/endpointcreds", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/credentials/stscreds", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/defaults", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/ec2metadata", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/endpoints", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/request", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/session", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/signer/v4", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/internal/shareddefaults", + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/private/protocol", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/private/protocol/ec2query", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/private/protocol/json/jsonutil", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/private/protocol/jsonrpc", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/private/protocol/query", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/private/protocol/query/queryutil", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/private/protocol/rest", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" - }, - { - "ImportPath": "github.com/aws/aws-sdk-go/private/protocol/restxml", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" - }, - { - "ImportPath": "github.com/aws/aws-sdk-go/private/waiter", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/service/autoscaling", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/service/ec2", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/service/ecr", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/service/elb", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/service/elbv2", + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/service/kms", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" - }, - { - "ImportPath": "github.com/aws/aws-sdk-go/service/route53", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/aws/aws-sdk-go/service/sts", - "Comment": "v1.6.10", - "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" + "Comment": "v1.12.7", + "Rev": "760741802ad40f49ae9fc4a69ef6706d2527d62e" }, { "ImportPath": "github.com/beorn7/perks/quantile", @@ -437,6 +436,55 @@ "Comment": "v0.1.0-62-g8d75e11", "Rev": "8d75e11374a1928608c906fe745b538483e7aeb2" }, + { + "ImportPath": "github.com/container-storage-interface/spec/lib/go/csi", + "Rev": "ec298903f94e1d6d954de121b28044a2e1fdbf48" + }, + { + "ImportPath": "github.com/containerd/containerd/api/services/containers/v1", + "Comment": "v1.0.0-beta.2-159-g27d450a", + "Rev": "27d450a01bb533d7ebc5701eb52792565396b084" + }, + { + "ImportPath": "github.com/containerd/containerd/api/services/tasks/v1", + "Comment": "v1.0.0-beta.2-159-g27d450a", + "Rev": "27d450a01bb533d7ebc5701eb52792565396b084" + }, + { + "ImportPath": "github.com/containerd/containerd/api/services/version/v1", + "Comment": "v1.0.0-beta.2-159-g27d450a", + "Rev": "27d450a01bb533d7ebc5701eb52792565396b084" + }, + { + "ImportPath": "github.com/containerd/containerd/api/types", + "Comment": "v1.0.0-beta.2-159-g27d450a", + "Rev": "27d450a01bb533d7ebc5701eb52792565396b084" + }, + { + "ImportPath": "github.com/containerd/containerd/api/types/task", + "Comment": "v1.0.0-beta.2-159-g27d450a", + "Rev": "27d450a01bb533d7ebc5701eb52792565396b084" + }, + { + "ImportPath": "github.com/containerd/containerd/containers", + "Comment": "v1.0.0-beta.2-159-g27d450a", + "Rev": "27d450a01bb533d7ebc5701eb52792565396b084" + }, + { + "ImportPath": "github.com/containerd/containerd/dialer", + "Comment": "v1.0.0-beta.2-159-g27d450a", + "Rev": "27d450a01bb533d7ebc5701eb52792565396b084" + }, + { + "ImportPath": "github.com/containerd/containerd/errdefs", + "Comment": "v1.0.0-beta.2-159-g27d450a", + "Rev": "27d450a01bb533d7ebc5701eb52792565396b084" + }, + { + "ImportPath": "github.com/containerd/containerd/namespaces", + "Comment": "v1.0.0-beta.2-159-g27d450a", + "Rev": "27d450a01bb533d7ebc5701eb52792565396b084" + }, { "ImportPath": "github.com/containernetworking/cni/libcni", "Comment": "v0.6.0", @@ -848,6 +896,10 @@ "ImportPath": "github.com/daviddengcn/go-colortext", "Rev": "511bcaf42ccd42c38aba7427b6673277bf19e2a1" }, + { + "ImportPath": "github.com/dchest/safefile", + "Rev": "855e8d98f1852d48dde521e0522408d1fe7e836a" + }, { "ImportPath": "github.com/dgrijalva/jwt-go", "Comment": "v3.0.0-4-g01aeca5", @@ -1003,84 +1055,19 @@ "Comment": "docs-v1.12.0-rc4-2016-07-15-7401-g4f3616f", "Rev": "4f3616fb1c112e206b88cb7a9922bf49067a7756" }, - { - "ImportPath": "github.com/docker/engine-api/client", - "Comment": "v0.3.1-78-gdea108d", - "Rev": "dea108d3aa0c67d7162a3fd8aa65f38a430019fd" - }, - { - "ImportPath": "github.com/docker/engine-api/client/transport", - "Comment": "v0.3.1-78-gdea108d", - "Rev": "dea108d3aa0c67d7162a3fd8aa65f38a430019fd" - }, - { - "ImportPath": "github.com/docker/engine-api/client/transport/cancellable", - "Comment": "v0.3.1-78-gdea108d", - "Rev": "dea108d3aa0c67d7162a3fd8aa65f38a430019fd" - }, - { - "ImportPath": "github.com/docker/engine-api/types", - "Comment": "v0.3.1-78-gdea108d", - "Rev": "dea108d3aa0c67d7162a3fd8aa65f38a430019fd" - }, - { - "ImportPath": "github.com/docker/engine-api/types/blkiodev", - "Comment": "v0.3.1-78-gdea108d", - "Rev": "dea108d3aa0c67d7162a3fd8aa65f38a430019fd" - }, - { - "ImportPath": "github.com/docker/engine-api/types/container", - "Comment": "v0.3.1-78-gdea108d", - "Rev": "dea108d3aa0c67d7162a3fd8aa65f38a430019fd" - }, - { - "ImportPath": "github.com/docker/engine-api/types/filters", - "Comment": "v0.3.1-78-gdea108d", - "Rev": "dea108d3aa0c67d7162a3fd8aa65f38a430019fd" - }, - { - "ImportPath": "github.com/docker/engine-api/types/network", - "Comment": "v0.3.1-78-gdea108d", - "Rev": "dea108d3aa0c67d7162a3fd8aa65f38a430019fd" - }, - { - "ImportPath": "github.com/docker/engine-api/types/reference", - "Comment": "v0.3.1-78-gdea108d", - "Rev": "dea108d3aa0c67d7162a3fd8aa65f38a430019fd" - }, - { - "ImportPath": "github.com/docker/engine-api/types/registry", - "Comment": "v0.3.1-78-gdea108d", - "Rev": "dea108d3aa0c67d7162a3fd8aa65f38a430019fd" - }, - { - "ImportPath": "github.com/docker/engine-api/types/strslice", - "Comment": "v0.3.1-78-gdea108d", - "Rev": "dea108d3aa0c67d7162a3fd8aa65f38a430019fd" - }, - { - "ImportPath": "github.com/docker/engine-api/types/time", - "Comment": "v0.3.1-78-gdea108d", - "Rev": "dea108d3aa0c67d7162a3fd8aa65f38a430019fd" - }, - { - "ImportPath": "github.com/docker/engine-api/types/versions", - "Comment": "v0.3.1-78-gdea108d", - "Rev": "dea108d3aa0c67d7162a3fd8aa65f38a430019fd" - }, { "ImportPath": "github.com/docker/go-connections/nat", - "Comment": "v0.2.1-30-g3ede32e", + "Comment": "v0.3.0", "Rev": "3ede32e2033de7505e6500d6c868c2b9ed9f169d" }, { "ImportPath": "github.com/docker/go-connections/sockets", - "Comment": "v0.2.1-30-g3ede32e", + "Comment": "v0.3.0", "Rev": "3ede32e2033de7505e6500d6c868c2b9ed9f169d" }, { "ImportPath": "github.com/docker/go-connections/tlsconfig", - "Comment": "v0.2.1-30-g3ede32e", + "Comment": "v0.3.0", "Rev": "3ede32e2033de7505e6500d6c868c2b9ed9f169d" }, { @@ -1167,8 +1154,8 @@ }, { "ImportPath": "github.com/go-ini/ini", - "Comment": "v0-54-g2e44421", - "Rev": "2e44421e256d82ebbf3d4d4fcabe8930b905eff3" + "Comment": "v1.25.4", + "Rev": "300e940a926eb277d3901b20bdfcc54928ad3642" }, { "ImportPath": "github.com/go-openapi/analysis", @@ -1330,6 +1317,11 @@ "Comment": "v0.4-3-gc0656ed", "Rev": "c0656edd0d9eab7c66d1eb0c568f9039345796f7" }, + { + "ImportPath": "github.com/gogo/protobuf/types", + "Comment": "v0.4-3-gc0656ed", + "Rev": "c0656edd0d9eab7c66d1eb0c568f9039345796f7" + }, { "ImportPath": "github.com/gogo/protobuf/vanity", "Comment": "v0.4-3-gc0656ed", @@ -1354,236 +1346,254 @@ }, { "ImportPath": "github.com/golang/protobuf/jsonpb", - "Rev": "4bd1920723d7b7c925de087aa32e2187708897f7" + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" }, { "ImportPath": "github.com/golang/protobuf/proto", - "Rev": "4bd1920723d7b7c925de087aa32e2187708897f7" + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" }, { "ImportPath": "github.com/golang/protobuf/ptypes", - "Rev": "4bd1920723d7b7c925de087aa32e2187708897f7" + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" }, { "ImportPath": "github.com/golang/protobuf/ptypes/any", - "Rev": "4bd1920723d7b7c925de087aa32e2187708897f7" + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" }, { "ImportPath": "github.com/golang/protobuf/ptypes/duration", - "Rev": "4bd1920723d7b7c925de087aa32e2187708897f7" + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" + }, + { + "ImportPath": "github.com/golang/protobuf/ptypes/empty", + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" + }, + { + "ImportPath": "github.com/golang/protobuf/ptypes/struct", + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" }, { "ImportPath": "github.com/golang/protobuf/ptypes/timestamp", - "Rev": "4bd1920723d7b7c925de087aa32e2187708897f7" + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" }, { "ImportPath": "github.com/google/btree", "Rev": "7d79101e329e5a3adf994758c578dab82b90c017" }, + { + "ImportPath": "github.com/google/cadvisor/accelerators", + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" + }, { "ImportPath": "github.com/google/cadvisor/api", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/cache/memory", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/client/v2", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/collector", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/container", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/container/common", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" + }, + { + "ImportPath": "github.com/google/cadvisor/container/containerd", + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/container/crio", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/container/docker", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/container/libcontainer", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/container/raw", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/container/rkt", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/container/systemd", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/devicemapper", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/events", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/fs", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/healthz", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/http", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/http/mux", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/info/v1", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/info/v2", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/machine", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/manager", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/manager/watcher", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/manager/watcher/raw", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/manager/watcher/rkt", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/metrics", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/pages", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/pages/static", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/storage", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/summary", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/utils", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/utils/cloudinfo", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/utils/cpuload", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/utils/cpuload/netlink", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/utils/docker", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/utils/oomparser", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/utils/sysfs", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/utils/sysinfo", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/validate", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/version", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/cadvisor/zfs", - "Comment": "v0.27.1", - "Rev": "cda62a43857256fbc95dd31e7c810888f00f8ec7" + "Comment": "v0.28.3", + "Rev": "1e567c2ac359c3ed1303e0c80b6cf08edefc841d" }, { "ImportPath": "github.com/google/certificate-transparency/go", @@ -1623,123 +1633,123 @@ }, { "ImportPath": "github.com/gophercloud/gophercloud", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" + }, + { + "ImportPath": "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions", + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" + }, + { + "ImportPath": "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes", + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/common/extensions", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/images", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/servers", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" + }, + { + "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external", + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" - }, - { - "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" - }, - { - "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" - }, - { - "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" - }, - { - "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" + }, + { + "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/networks", + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/ports", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/utils", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/pagination", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gorilla/context", @@ -1856,6 +1866,7 @@ }, { "ImportPath": "github.com/inconshreveable/mousetrap", + "Comment": "v1.0", "Rev": "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" }, { @@ -1880,8 +1891,8 @@ }, { "ImportPath": "github.com/jmespath/go-jmespath", - "Comment": "0.2.2", - "Rev": "3433f3ea46d9f8019119e7dd41274e112a2359a9" + "Comment": "0.2.2-12-g0b12d6b", + "Rev": "0b12d6b521d83fc7f755e7cfc1b1fbdd35a01a74" }, { "ImportPath": "github.com/jonboulle/clockwork", @@ -1889,8 +1900,8 @@ }, { "ImportPath": "github.com/json-iterator/go", - "Comment": "1.0.0", - "Rev": "36b14963da70d11297d313183d7e6388c8510e1e" + "Comment": "1.0.4-7-g13f8643", + "Rev": "13f86432b882000a51c6e610c620974462691a97" }, { "ImportPath": "github.com/jteeuwen/go-bindata", @@ -1966,10 +1977,6 @@ "Comment": "v1.0-1-g9577782", "Rev": "9577782540c1398b710ddae1b86268ba03a19b0c" }, - { - "ImportPath": "github.com/lxn/win", - "Rev": "712da405e3eb60e148bf5086991cd1e9eb570001" - }, { "ImportPath": "github.com/magiconair/properties", "Comment": "v1.7.0-4-g61b492c", @@ -1991,15 +1998,14 @@ "ImportPath": "github.com/matttproud/golang_protobuf_extensions/pbutil", "Rev": "fc2b8d3a73c4867e51861bbdd5ae3c1f0869dd6a" }, - { - "ImportPath": "github.com/miekg/coredns/middleware/etcd/msg", - "Comment": "v003", - "Rev": "20e25559d5eada5a68a0720816a6e947b94860ce" - }, { "ImportPath": "github.com/miekg/dns", "Rev": "5d001d020961ae1c184f9f8152fdc73810481677" }, + { + "ImportPath": "github.com/mindprince/gonvml", + "Rev": "fee913ce8fb235edf54739d259ca0ecc226c7b8a" + }, { "ImportPath": "github.com/mistifyio/go-zfs", "Comment": "v2.1.1-5-g1b4ae6f", @@ -2354,8 +2360,8 @@ }, { "ImportPath": "github.com/pkg/errors", - "Comment": "v0.7.0-13-ga221380", - "Rev": "a22138067af1c4942683050411a841ade67fe1eb" + "Comment": "v0.8.0", + "Rev": "645ef00459ed84a119197bfb8d8205042c6df63d" }, { "ImportPath": "github.com/pkg/sftp", @@ -2429,9 +2435,9 @@ "Rev": "300106c228d52c8941d4b3de6054a6062a86dda3" }, { - "ImportPath": "github.com/satori/uuid", - "Comment": "v1.1.0-8-g5bf94b6", - "Rev": "5bf94b69c6b68ee1b541973bb8e1144db23a194b" + "ImportPath": "github.com/satori/go.uuid", + "Comment": "v1.1.0", + "Rev": "879c5887cd475cd7864858769793b2ceb0d44feb" }, { "ImportPath": "github.com/seccomp/libseccomp-golang", @@ -2482,18 +2488,6 @@ "ImportPath": "github.com/spf13/viper", "Rev": "7fb2782df3d83e0036cc89f461ed0422628776f4" }, - { - "ImportPath": "github.com/square/go-jose", - "Rev": "789a4c4bd4c118f7564954f441b29c153ccd6a96" - }, - { - "ImportPath": "github.com/square/go-jose/cipher", - "Rev": "789a4c4bd4c118f7564954f441b29c153ccd6a96" - }, - { - "ImportPath": "github.com/square/go-jose/json", - "Rev": "789a4c4bd4c118f7564954f441b29c153ccd6a96" - }, { "ImportPath": "github.com/storageos/go-api", "Rev": "74f9beb613cacf0cc282facc2e1550a3231e126f" @@ -2536,15 +2530,15 @@ }, { "ImportPath": "github.com/vishvananda/netlink", - "Rev": "f5a6f697a596c788d474984a38a0ac4ba0719e93" + "Rev": "f67b75edbf5e3bb7dfe70bb788610693a71be3d1" }, { "ImportPath": "github.com/vishvananda/netlink/nl", - "Rev": "f5a6f697a596c788d474984a38a0ac4ba0719e93" + "Rev": "f67b75edbf5e3bb7dfe70bb788610693a71be3d1" }, { "ImportPath": "github.com/vishvananda/netns", - "Rev": "86bef332bfc3b59b7624a600bd53009ce91a9829" + "Rev": "be1fbeda19366dea804f00efff2dd73a1642fdcc" }, { "ImportPath": "github.com/vmware/govmomi", @@ -2792,11 +2786,11 @@ }, { "ImportPath": "golang.org/x/sys/unix", - "Rev": "7ddbeae9ae08c6a06a59597f0c9edbc5ff2444ce" + "Rev": "95c6576299259db960f6c5b9b69ea52422860fce" }, { "ImportPath": "golang.org/x/sys/windows", - "Rev": "7ddbeae9ae08c6a06a59597f0c9edbc5ff2444ce" + "Rev": "95c6576299259db960f6c5b9b69ea52422860fce" }, { "ImportPath": "golang.org/x/text/cases", @@ -2870,61 +2864,61 @@ "ImportPath": "golang.org/x/tools/container/intsets", "Rev": "2382e3994d48b1d22acc2c86bcad0a2aff028e32" }, + { + "ImportPath": "golang.org/x/tools/go/ast/astutil", + "Rev": "2382e3994d48b1d22acc2c86bcad0a2aff028e32" + }, { "ImportPath": "golang.org/x/tools/go/vcs", "Rev": "2382e3994d48b1d22acc2c86bcad0a2aff028e32" }, { - "ImportPath": "google.golang.org/api/cloudkms/v1", - "Rev": "654f863362977d69086620b5f72f13e911da2410" + "ImportPath": "golang.org/x/tools/imports", + "Rev": "2382e3994d48b1d22acc2c86bcad0a2aff028e32" }, { "ImportPath": "google.golang.org/api/cloudmonitoring/v2beta2", - "Rev": "654f863362977d69086620b5f72f13e911da2410" + "Rev": "c0dae069ee96c9261a04c81efd9e0f1e55f565ac" }, { "ImportPath": "google.golang.org/api/compute/v0.alpha", - "Rev": "654f863362977d69086620b5f72f13e911da2410" + "Rev": "c0dae069ee96c9261a04c81efd9e0f1e55f565ac" }, { "ImportPath": "google.golang.org/api/compute/v0.beta", - "Rev": "654f863362977d69086620b5f72f13e911da2410" + "Rev": "c0dae069ee96c9261a04c81efd9e0f1e55f565ac" }, { "ImportPath": "google.golang.org/api/compute/v1", - "Rev": "654f863362977d69086620b5f72f13e911da2410" + "Rev": "c0dae069ee96c9261a04c81efd9e0f1e55f565ac" }, { "ImportPath": "google.golang.org/api/container/v1", - "Rev": "654f863362977d69086620b5f72f13e911da2410" - }, - { - "ImportPath": "google.golang.org/api/dns/v1", - "Rev": "654f863362977d69086620b5f72f13e911da2410" + "Rev": "c0dae069ee96c9261a04c81efd9e0f1e55f565ac" }, { "ImportPath": "google.golang.org/api/gensupport", - "Rev": "654f863362977d69086620b5f72f13e911da2410" + "Rev": "c0dae069ee96c9261a04c81efd9e0f1e55f565ac" }, { "ImportPath": "google.golang.org/api/googleapi", - "Rev": "654f863362977d69086620b5f72f13e911da2410" + "Rev": "c0dae069ee96c9261a04c81efd9e0f1e55f565ac" }, { "ImportPath": "google.golang.org/api/googleapi/internal/uritemplates", - "Rev": "654f863362977d69086620b5f72f13e911da2410" + "Rev": "c0dae069ee96c9261a04c81efd9e0f1e55f565ac" }, { "ImportPath": "google.golang.org/api/logging/v2beta1", - "Rev": "654f863362977d69086620b5f72f13e911da2410" + "Rev": "c0dae069ee96c9261a04c81efd9e0f1e55f565ac" }, { "ImportPath": "google.golang.org/api/monitoring/v3", - "Rev": "654f863362977d69086620b5f72f13e911da2410" + "Rev": "c0dae069ee96c9261a04c81efd9e0f1e55f565ac" }, { "ImportPath": "google.golang.org/api/pubsub/v1", - "Rev": "654f863362977d69086620b5f72f13e911da2410" + "Rev": "c0dae069ee96c9261a04c81efd9e0f1e55f565ac" }, { "ImportPath": "google.golang.org/genproto/googleapis/rpc/status", @@ -3030,6 +3024,21 @@ "Comment": "v1.0-16-g20b71e5", "Rev": "20b71e5b60d756d3d2f80def009790325acc2b23" }, + { + "ImportPath": "gopkg.in/square/go-jose.v2", + "Comment": "v2.1.3", + "Rev": "f8f38de21b4dcd69d0413faf231983f5fd6634b1" + }, + { + "ImportPath": "gopkg.in/square/go-jose.v2/cipher", + "Comment": "v2.1.3", + "Rev": "f8f38de21b4dcd69d0413faf231983f5fd6634b1" + }, + { + "ImportPath": "gopkg.in/square/go-jose.v2/json", + "Comment": "v2.1.3", + "Rev": "f8f38de21b4dcd69d0413faf231983f5fd6634b1" + }, { "ImportPath": "gopkg.in/warnings.v0", "Comment": "v0.1.1", @@ -3041,43 +3050,43 @@ }, { "ImportPath": "k8s.io/gengo/args", - "Rev": "70ad626ed2d7a483d89d2c4c56364d60b48ee8fc" + "Rev": "b6c426f7730e6d66e6e476a85d1c3eb7633880e0" }, { "ImportPath": "k8s.io/gengo/examples/deepcopy-gen/generators", - "Rev": "70ad626ed2d7a483d89d2c4c56364d60b48ee8fc" + "Rev": "b6c426f7730e6d66e6e476a85d1c3eb7633880e0" }, { "ImportPath": "k8s.io/gengo/examples/defaulter-gen/generators", - "Rev": "70ad626ed2d7a483d89d2c4c56364d60b48ee8fc" + "Rev": "b6c426f7730e6d66e6e476a85d1c3eb7633880e0" }, { "ImportPath": "k8s.io/gengo/examples/import-boss/generators", - "Rev": "70ad626ed2d7a483d89d2c4c56364d60b48ee8fc" + "Rev": "b6c426f7730e6d66e6e476a85d1c3eb7633880e0" }, { "ImportPath": "k8s.io/gengo/examples/set-gen/generators", - "Rev": "70ad626ed2d7a483d89d2c4c56364d60b48ee8fc" + "Rev": "b6c426f7730e6d66e6e476a85d1c3eb7633880e0" }, { "ImportPath": "k8s.io/gengo/examples/set-gen/sets", - "Rev": "70ad626ed2d7a483d89d2c4c56364d60b48ee8fc" + "Rev": "b6c426f7730e6d66e6e476a85d1c3eb7633880e0" }, { "ImportPath": "k8s.io/gengo/generator", - "Rev": "70ad626ed2d7a483d89d2c4c56364d60b48ee8fc" + "Rev": "b6c426f7730e6d66e6e476a85d1c3eb7633880e0" }, { "ImportPath": "k8s.io/gengo/namer", - "Rev": "70ad626ed2d7a483d89d2c4c56364d60b48ee8fc" + "Rev": "b6c426f7730e6d66e6e476a85d1c3eb7633880e0" }, { "ImportPath": "k8s.io/gengo/parser", - "Rev": "70ad626ed2d7a483d89d2c4c56364d60b48ee8fc" + "Rev": "b6c426f7730e6d66e6e476a85d1c3eb7633880e0" }, { "ImportPath": "k8s.io/gengo/types", - "Rev": "70ad626ed2d7a483d89d2c4c56364d60b48ee8fc" + "Rev": "b6c426f7730e6d66e6e476a85d1c3eb7633880e0" }, { "ImportPath": "k8s.io/heapster/metrics/api/v1/types", @@ -3086,35 +3095,47 @@ }, { "ImportPath": "k8s.io/kube-openapi/pkg/aggregator", - "Rev": "868f2f29720b192240e18284659231b440f9cda5" + "Rev": "39a7bf85c140f972372c2a0d1ee40adbf0c8bfe1" }, { "ImportPath": "k8s.io/kube-openapi/pkg/builder", - "Rev": "868f2f29720b192240e18284659231b440f9cda5" + "Rev": "39a7bf85c140f972372c2a0d1ee40adbf0c8bfe1" }, { "ImportPath": "k8s.io/kube-openapi/pkg/common", - "Rev": "868f2f29720b192240e18284659231b440f9cda5" + "Rev": "39a7bf85c140f972372c2a0d1ee40adbf0c8bfe1" }, { "ImportPath": "k8s.io/kube-openapi/pkg/generators", - "Rev": "868f2f29720b192240e18284659231b440f9cda5" + "Rev": "39a7bf85c140f972372c2a0d1ee40adbf0c8bfe1" }, { "ImportPath": "k8s.io/kube-openapi/pkg/handler", - "Rev": "868f2f29720b192240e18284659231b440f9cda5" + "Rev": "39a7bf85c140f972372c2a0d1ee40adbf0c8bfe1" }, { "ImportPath": "k8s.io/kube-openapi/pkg/util", - "Rev": "868f2f29720b192240e18284659231b440f9cda5" + "Rev": "39a7bf85c140f972372c2a0d1ee40adbf0c8bfe1" + }, + { + "ImportPath": "k8s.io/kube-openapi/pkg/util/proto", + "Rev": "39a7bf85c140f972372c2a0d1ee40adbf0c8bfe1" + }, + { + "ImportPath": "k8s.io/kube-openapi/pkg/util/proto/validation", + "Rev": "39a7bf85c140f972372c2a0d1ee40adbf0c8bfe1" + }, + { + "ImportPath": "k8s.io/utils/clock", + "Rev": "aedf551cdb8b0119df3a19c65fde413a13b34997" }, { "ImportPath": "k8s.io/utils/exec", - "Rev": "9fdc871a36f37980dd85f96d576b20d564cc0784" + "Rev": "aedf551cdb8b0119df3a19c65fde413a13b34997" }, { "ImportPath": "k8s.io/utils/exec/testing", - "Rev": "9fdc871a36f37980dd85f96d576b20d564cc0784" + "Rev": "aedf551cdb8b0119df3a19c65fde413a13b34997" }, { "ImportPath": "vbom.ml/util/sortorder", diff --git a/Godeps/LICENSES b/Godeps/LICENSES index 505e9b9aa67..587fa18e32d 100644 --- a/Godeps/LICENSES +++ b/Godeps/LICENSES @@ -5172,6 +5172,216 @@ SOFTWARE. ================================================================================ +================================================================================ += vendor/github.com/aws/aws-sdk-go/internal/shareddefaults 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/aws/aws-sdk-go/LICENSE.txt 3b83ef96387f14655fc854ddc3c6bd57 +================================================================================ + + ================================================================================ = vendor/github.com/aws/aws-sdk-go/private/protocol licensed under: = @@ -6642,216 +6852,6 @@ SOFTWARE. ================================================================================ -================================================================================ -= vendor/github.com/aws/aws-sdk-go/private/protocol/restxml 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/aws/aws-sdk-go/LICENSE.txt 3b83ef96387f14655fc854ddc3c6bd57 -================================================================================ - - ================================================================================ = vendor/github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil licensed under: = @@ -7062,216 +7062,6 @@ SOFTWARE. ================================================================================ -================================================================================ -= vendor/github.com/aws/aws-sdk-go/private/waiter 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/aws/aws-sdk-go/LICENSE.txt 3b83ef96387f14655fc854ddc3c6bd57 -================================================================================ - - ================================================================================ = vendor/github.com/aws/aws-sdk-go/service/autoscaling licensed under: = @@ -8113,7 +7903,7 @@ SOFTWARE. ================================================================================ -= vendor/github.com/aws/aws-sdk-go/service/kms licensed under: = += vendor/github.com/aws/aws-sdk-go/service/elbv2 licensed under: = Apache License @@ -8323,7 +8113,7 @@ SOFTWARE. ================================================================================ -= vendor/github.com/aws/aws-sdk-go/service/route53 licensed under: = += vendor/github.com/aws/aws-sdk-go/service/kms licensed under: = Apache License @@ -12543,6 +12333,2006 @@ SOFTWARE. ================================================================================ +================================================================================ += vendor/github.com/container-storage-interface/spec/lib/go/csi 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/container-storage-interface/spec/LICENSE e3fc50a88d0a364313df4b21ef20c29e +================================================================================ + + +================================================================================ += vendor/github.com/containerd/containerd/api/services/containers/v1 licensed under: = + + + Apache License + Version 2.0, January 2004 + https://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 2013-2016 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 + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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/containerd/containerd/LICENSE.code aadc30f9c14d876ded7bedc0afd2d3d7 +================================================================================ + + +================================================================================ += vendor/github.com/containerd/containerd/api/services/tasks/v1 licensed under: = + + + Apache License + Version 2.0, January 2004 + https://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 2013-2016 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 + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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/containerd/containerd/LICENSE.code aadc30f9c14d876ded7bedc0afd2d3d7 +================================================================================ + + +================================================================================ += vendor/github.com/containerd/containerd/api/services/version/v1 licensed under: = + + + Apache License + Version 2.0, January 2004 + https://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 2013-2016 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 + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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/containerd/containerd/LICENSE.code aadc30f9c14d876ded7bedc0afd2d3d7 +================================================================================ + + +================================================================================ += vendor/github.com/containerd/containerd/api/types licensed under: = + + + Apache License + Version 2.0, January 2004 + https://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 2013-2016 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 + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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/containerd/containerd/LICENSE.code aadc30f9c14d876ded7bedc0afd2d3d7 +================================================================================ + + +================================================================================ += vendor/github.com/containerd/containerd/api/types/task licensed under: = + + + Apache License + Version 2.0, January 2004 + https://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 2013-2016 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 + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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/containerd/containerd/LICENSE.code aadc30f9c14d876ded7bedc0afd2d3d7 +================================================================================ + + +================================================================================ += vendor/github.com/containerd/containerd/containers licensed under: = + + + Apache License + Version 2.0, January 2004 + https://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 2013-2016 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 + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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/containerd/containerd/LICENSE.code aadc30f9c14d876ded7bedc0afd2d3d7 +================================================================================ + + +================================================================================ += vendor/github.com/containerd/containerd/dialer licensed under: = + + + Apache License + Version 2.0, January 2004 + https://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 2013-2016 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 + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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/containerd/containerd/LICENSE.code aadc30f9c14d876ded7bedc0afd2d3d7 +================================================================================ + + +================================================================================ += vendor/github.com/containerd/containerd/errdefs licensed under: = + + + Apache License + Version 2.0, January 2004 + https://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 2013-2016 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 + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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/containerd/containerd/LICENSE.code aadc30f9c14d876ded7bedc0afd2d3d7 +================================================================================ + + +================================================================================ += vendor/github.com/containerd/containerd/namespaces licensed under: = + + + Apache License + Version 2.0, January 2004 + https://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 2013-2016 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 + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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/containerd/containerd/LICENSE.code aadc30f9c14d876ded7bedc0afd2d3d7 +================================================================================ + + ================================================================================ = vendor/github.com/containernetworking/cni/libcni licensed under: = @@ -29587,6 +31377,40 @@ SOFTWARE. ================================================================================ +================================================================================ += vendor/github.com/dchest/safefile licensed under: = + +Copyright (c) 2013 Dmitry Chestnykh +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 +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/github.com/dchest/safefile/LICENSE 499ffd499356286ac590c21f7b8bd677 +================================================================================ + + ================================================================================ = vendor/github.com/dgrijalva/jwt-go licensed under: = @@ -35595,2593 +37419,6 @@ Apache License ================================================================================ -================================================================================ -= vendor/github.com/docker/engine-api/client licensed under: = - - - Apache License - Version 2.0, January 2004 - https://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-2016 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 - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT 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/docker/engine-api/LICENSE 783e0c5d91f5e3fee320c5f474227ae0 -================================================================================ - - -================================================================================ -= vendor/github.com/docker/engine-api/client/transport licensed under: = - - - Apache License - Version 2.0, January 2004 - https://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-2016 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 - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT 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/docker/engine-api/LICENSE 783e0c5d91f5e3fee320c5f474227ae0 -================================================================================ - - -================================================================================ -= vendor/github.com/docker/engine-api/client/transport/cancellable licensed under: = - - - Apache License - Version 2.0, January 2004 - https://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-2016 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 - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT 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/docker/engine-api/LICENSE 783e0c5d91f5e3fee320c5f474227ae0 -================================================================================ - - -================================================================================ -= vendor/github.com/docker/engine-api/types licensed under: = - - - Apache License - Version 2.0, January 2004 - https://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-2016 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 - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT 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/docker/engine-api/LICENSE 783e0c5d91f5e3fee320c5f474227ae0 -================================================================================ - - -================================================================================ -= vendor/github.com/docker/engine-api/types/blkiodev licensed under: = - - - Apache License - Version 2.0, January 2004 - https://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-2016 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 - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT 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/docker/engine-api/LICENSE 783e0c5d91f5e3fee320c5f474227ae0 -================================================================================ - - -================================================================================ -= vendor/github.com/docker/engine-api/types/container licensed under: = - - - Apache License - Version 2.0, January 2004 - https://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-2016 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 - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT 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/docker/engine-api/LICENSE 783e0c5d91f5e3fee320c5f474227ae0 -================================================================================ - - -================================================================================ -= vendor/github.com/docker/engine-api/types/filters licensed under: = - - - Apache License - Version 2.0, January 2004 - https://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-2016 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 - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT 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/docker/engine-api/LICENSE 783e0c5d91f5e3fee320c5f474227ae0 -================================================================================ - - -================================================================================ -= vendor/github.com/docker/engine-api/types/network licensed under: = - - - Apache License - Version 2.0, January 2004 - https://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-2016 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 - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT 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/docker/engine-api/LICENSE 783e0c5d91f5e3fee320c5f474227ae0 -================================================================================ - - -================================================================================ -= vendor/github.com/docker/engine-api/types/reference licensed under: = - - - Apache License - Version 2.0, January 2004 - https://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-2016 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 - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT 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/docker/engine-api/LICENSE 783e0c5d91f5e3fee320c5f474227ae0 -================================================================================ - - -================================================================================ -= vendor/github.com/docker/engine-api/types/registry licensed under: = - - - Apache License - Version 2.0, January 2004 - https://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-2016 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 - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT 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/docker/engine-api/LICENSE 783e0c5d91f5e3fee320c5f474227ae0 -================================================================================ - - -================================================================================ -= vendor/github.com/docker/engine-api/types/strslice licensed under: = - - - Apache License - Version 2.0, January 2004 - https://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-2016 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 - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT 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/docker/engine-api/LICENSE 783e0c5d91f5e3fee320c5f474227ae0 -================================================================================ - - -================================================================================ -= vendor/github.com/docker/engine-api/types/time licensed under: = - - - Apache License - Version 2.0, January 2004 - https://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-2016 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 - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT 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/docker/engine-api/LICENSE 783e0c5d91f5e3fee320c5f474227ae0 -================================================================================ - - -================================================================================ -= vendor/github.com/docker/engine-api/types/versions licensed under: = - - - Apache License - Version 2.0, January 2004 - https://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-2016 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 - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT 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/docker/engine-api/LICENSE 783e0c5d91f5e3fee320c5f474227ae0 -================================================================================ - - ================================================================================ = vendor/github.com/docker/go-connections/nat licensed under: = @@ -44041,6 +43278,50 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================================================ +================================================================================ += vendor/github.com/gogo/protobuf/types licensed under: = + +Protocol Buffers for Go with Gadgets + +Copyright (c) 2013, The GoGo Authors. All rights reserved. +http://github.com/gogo/protobuf + +Go support for Protocol Buffers - Google's data interchange format + +Copyright 2010 The Go Authors. All rights reserved. +https://github.com/golang/protobuf + +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. + + += vendor/github.com/gogo/protobuf/LICENSE f76ab0572aa28f537b1508f2f2bc155e +================================================================================ + + ================================================================================ = vendor/github.com/gogo/protobuf/vanity licensed under: = @@ -44932,6 +44213,84 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================================================ +================================================================================ += vendor/github.com/golang/protobuf/ptypes/empty licensed under: = + +Go support for Protocol Buffers - Google's data interchange format + +Copyright 2010 The Go Authors. All rights reserved. +https://github.com/golang/protobuf + +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. + + += vendor/github.com/golang/protobuf/LICENSE 14db3a56c3796a940ba32948a15f97d0 +================================================================================ + + +================================================================================ += vendor/github.com/golang/protobuf/ptypes/struct licensed under: = + +Go support for Protocol Buffers - Google's data interchange format + +Copyright 2010 The Go Authors. All rights reserved. +https://github.com/golang/protobuf + +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. + + += vendor/github.com/golang/protobuf/LICENSE 14db3a56c3796a940ba32948a15f97d0 +================================================================================ + + ================================================================================ = vendor/github.com/golang/protobuf/ptypes/timestamp licensed under: = @@ -45181,6 +44540,204 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================================================ +================================================================================ += vendor/github.com/google/cadvisor/accelerators licensed under: = + + Copyright 2014 The cAdvisor Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + 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 + += vendor/github.com/google/cadvisor/LICENSE e7790b946bfacb700e8a8f2baedb3205 +================================================================================ + + ================================================================================ = vendor/github.com/google/cadvisor/api licensed under: = @@ -46369,6 +45926,204 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================================================ +================================================================================ += vendor/github.com/google/cadvisor/container/containerd licensed under: = + + Copyright 2014 The cAdvisor Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + 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 + += vendor/github.com/google/cadvisor/LICENSE e7790b946bfacb700e8a8f2baedb3205 +================================================================================ + + ================================================================================ = vendor/github.com/google/cadvisor/container/crio licensed under: = @@ -55590,6 +55345,205 @@ specific language governing permissions and limitations under the License. ================================================================================ +================================================================================ += vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions licensed under: = + +Copyright 2012-2013 Rackspace, 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. + +------ + + 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 + += vendor/github.com/gophercloud/gophercloud/LICENSE dd19699707373c2ca31531a659130416 +================================================================================ + + ================================================================================ = vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes licensed under: = @@ -55988,6 +55942,205 @@ specific language governing permissions and limitations under the License. ================================================================================ +================================================================================ += vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes licensed under: = + +Copyright 2012-2013 Rackspace, 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. + +------ + + 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 + += vendor/github.com/gophercloud/gophercloud/LICENSE dd19699707373c2ca31531a659130416 +================================================================================ + + ================================================================================ = vendor/github.com/gophercloud/gophercloud/openstack/common/extensions licensed under: = @@ -58177,6 +58330,205 @@ specific language governing permissions and limitations under the License. ================================================================================ +================================================================================ += vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external licensed under: = + +Copyright 2012-2013 Rackspace, 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. + +------ + + 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 + += vendor/github.com/gophercloud/gophercloud/LICENSE dd19699707373c2ca31531a659130416 +================================================================================ + + ================================================================================ = vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips licensed under: = @@ -58575,802 +58927,6 @@ specific language governing permissions and limitations under the License. ================================================================================ -================================================================================ -= vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members licensed under: = - -Copyright 2012-2013 Rackspace, 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. - ------- - - 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 - -= vendor/github.com/gophercloud/gophercloud/LICENSE dd19699707373c2ca31531a659130416 -================================================================================ - - -================================================================================ -= vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors licensed under: = - -Copyright 2012-2013 Rackspace, 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. - ------- - - 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 - -= vendor/github.com/gophercloud/gophercloud/LICENSE dd19699707373c2ca31531a659130416 -================================================================================ - - -================================================================================ -= vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools licensed under: = - -Copyright 2012-2013 Rackspace, 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. - ------- - - 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 - -= vendor/github.com/gophercloud/gophercloud/LICENSE dd19699707373c2ca31531a659130416 -================================================================================ - - -================================================================================ -= vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips licensed under: = - -Copyright 2012-2013 Rackspace, 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. - ------- - - 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 - -= vendor/github.com/gophercloud/gophercloud/LICENSE dd19699707373c2ca31531a659130416 -================================================================================ - - ================================================================================ = vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners licensed under: = @@ -60565,6 +60121,205 @@ specific language governing permissions and limitations under the License. ================================================================================ +================================================================================ += vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks licensed under: = + +Copyright 2012-2013 Rackspace, 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. + +------ + + 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 + += vendor/github.com/gophercloud/gophercloud/LICENSE dd19699707373c2ca31531a659130416 +================================================================================ + + ================================================================================ = vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports licensed under: = @@ -66048,6 +65803,37 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ +================================================================================ += vendor/github.com/JeffAshton/win_pdh licensed under: = + +Copyright (c) 2010 The win_pdh 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: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 AUTHORS 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/JeffAshton/win_pdh/LICENSE fadcabe0503181faf8d4a9579bed3b7f +================================================================================ + + ================================================================================ = vendor/github.com/jmespath/go-jmespath licensed under: = @@ -68313,37 +68099,6 @@ Apache License ================================================================================ -================================================================================ -= vendor/github.com/lxn/win licensed under: = - -Copyright (c) 2010 The win 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: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The names of the authors may not be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 AUTHORS 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/lxn/win/LICENSE 794c9d5b17cb1deeb131552549b6e7f2 -================================================================================ - - ================================================================================ = vendor/github.com/magiconair/properties licensed under: = @@ -68425,29 +68180,29 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI ================================================================================ = vendor/github.com/MakeNowJust/heredoc licensed under: = -The MIT License (MIT) - -Copyright (c) 2014 TSUYUSATO Kitsune - -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. +The MIT License (MIT) -= vendor/github.com/MakeNowJust/heredoc/LICENSE 15e1c8f1d3c204c05f71630afacbc92b +Copyright (c) 2014-2017 TSUYUSATO Kitsune + +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/MakeNowJust/heredoc/LICENSE 59c4411f6d7dfdaa85623e672d3d4438 ================================================================================ @@ -68719,7 +68474,48 @@ SOFTWARE. ================================================================================ -= vendor/github.com/miekg/coredns/middleware/etcd/msg licensed under: = += vendor/github.com/miekg/dns licensed under: = + +Extensions of the original work are copyright (c) 2011 Miek Gieben + +As this is fork of the official Go code the same license applies: + +Copyright (c) 2009 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. + + += vendor/github.com/miekg/dns/LICENSE 147353de6868a20caa562d26eab7b3c5 +================================================================================ + + +================================================================================ += vendor/github.com/mindprince/gonvml licensed under: = + Apache License Version 2.0, January 2004 @@ -68901,7 +68697,7 @@ SOFTWARE. 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 "{}" + 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 @@ -68909,7 +68705,7 @@ SOFTWARE. same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright {yyyy} {name of copyright owner} + 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. @@ -68923,47 +68719,7 @@ SOFTWARE. See the License for the specific language governing permissions and limitations under the License. -= vendor/github.com/miekg/coredns/LICENSE e3fc50a88d0a364313df4b21ef20c29e -================================================================================ - - -================================================================================ -= vendor/github.com/miekg/dns licensed under: = - -Extensions of the original work are copyright (c) 2011 Miek Gieben - -As this is fork of the official Go code the same license applies: - -Copyright (c) 2009 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. - - -= vendor/github.com/miekg/dns/LICENSE 147353de6868a20caa562d26eab7b3c5 += vendor/github.com/mindprince/gonvml/LICENSE 3b83ef96387f14655fc854ddc3c6bd57 ================================================================================ @@ -77227,7 +76983,7 @@ Blackfriday is distributed under the Simplified BSD License: ================================================================================ -= vendor/github.com/satori/uuid licensed under: = += vendor/github.com/satori/go.uuid licensed under: = Copyright (C) 2013-2016 by Maxim Bublis @@ -77250,7 +77006,7 @@ 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/satori/uuid/LICENSE 02d5d17de0c82a23a09863acccc026f6 += vendor/github.com/satori/go.uuid/LICENSE 02d5d17de0c82a23a09863acccc026f6 ================================================================================ @@ -78370,636 +78126,6 @@ SOFTWARE. ================================================================================ -================================================================================ -= vendor/github.com/square/go-jose 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/square/go-jose/LICENSE 3b83ef96387f14655fc854ddc3c6bd57 -================================================================================ - - -================================================================================ -= vendor/github.com/square/go-jose/cipher 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/square/go-jose/LICENSE 3b83ef96387f14655fc854ddc3c6bd57 -================================================================================ - - -================================================================================ -= vendor/github.com/square/go-jose/json 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/square/go-jose/LICENSE 3b83ef96387f14655fc854ddc3c6bd57 -================================================================================ - - ================================================================================ = vendor/github.com/storageos/go-api licensed under: = @@ -86179,6 +85305,41 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================================================ +================================================================================ += vendor/golang.org/x/tools/go/ast/astutil licensed under: = + +Copyright (c) 2009 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. + += vendor/golang.org/x/tools/LICENSE 5d4950ecb7b26d2c5e4e7b4e0dd74707 +================================================================================ + + ================================================================================ = vendor/golang.org/x/tools/go/vcs licensed under: = @@ -86215,9 +85376,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================================================ -= vendor/google.golang.org/api/cloudkms/v1 licensed under: = += vendor/golang.org/x/tools/imports licensed under: = -Copyright (c) 2011 Google Inc. All rights reserved. +Copyright (c) 2009 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 @@ -86245,7 +85406,7 @@ 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/google.golang.org/api/LICENSE a651bb3d8b1c412632e28823bb432b40 += vendor/golang.org/x/tools/LICENSE 5d4950ecb7b26d2c5e4e7b4e0dd74707 ================================================================================ @@ -86424,41 +85585,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================================================ -================================================================================ -= vendor/google.golang.org/api/dns/v1 licensed under: = - -Copyright (c) 2011 Google Inc. 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. - -= vendor/google.golang.org/api/LICENSE a651bb3d8b1c412632e28823bb432b40 -================================================================================ - - ================================================================================ = vendor/google.golang.org/api/gensupport licensed under: = @@ -87591,6 +86717,636 @@ SOFTWARE. ================================================================================ +================================================================================ += vendor/gopkg.in/square/go-jose.v2 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/gopkg.in/square/go-jose.v2/LICENSE 3b83ef96387f14655fc854ddc3c6bd57 +================================================================================ + + +================================================================================ += vendor/gopkg.in/square/go-jose.v2/cipher 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/gopkg.in/square/go-jose.v2/LICENSE 3b83ef96387f14655fc854ddc3c6bd57 +================================================================================ + + +================================================================================ += vendor/gopkg.in/square/go-jose.v2/json 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/gopkg.in/square/go-jose.v2/LICENSE 3b83ef96387f14655fc854ddc3c6bd57 +================================================================================ + + ================================================================================ = vendor/gopkg.in/warnings.v0 licensed under: = @@ -91388,6 +91144,636 @@ Apache License ================================================================================ +================================================================================ += vendor/k8s.io/kube-openapi/pkg/util/proto 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/k8s.io/kube-openapi/LICENSE 3b83ef96387f14655fc854ddc3c6bd57 +================================================================================ + + +================================================================================ += vendor/k8s.io/kube-openapi/pkg/util/proto/validation 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/k8s.io/kube-openapi/LICENSE 3b83ef96387f14655fc854ddc3c6bd57 +================================================================================ + + +================================================================================ += vendor/k8s.io/utils/clock 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/k8s.io/utils/LICENSE 3b83ef96387f14655fc854ddc3c6bd57 +================================================================================ + + ================================================================================ = vendor/k8s.io/utils/exec licensed under: = diff --git a/OWNERS_ALIASES b/OWNERS_ALIASES index 9662e4677df..8f898647ea1 100644 --- a/OWNERS_ALIASES +++ b/OWNERS_ALIASES @@ -17,7 +17,6 @@ aliases: - adohe - brendandburns - deads2k - - fabianofranz - janetkuo - liggitt - pwittrock @@ -30,7 +29,6 @@ aliases: - dshulyak - eparis - ericchiang - - fabianofranz - ghodss - mengqiy - rootfs @@ -101,6 +99,12 @@ aliases: sig-apps-api-approvers: - erictune - smarterclayton + sig-autoscaling-maintainers: + - aleksandra-malinowska + - bskiba + - DirectXMan12 + - MaciekPytel + - mwielgus milestone-maintainers: - lavalamp - deads2k @@ -121,7 +125,6 @@ aliases: - slack - colemickens - foxish - - fabianofranz - pwittrock - AdoHe - lukemarsden @@ -175,4 +178,48 @@ aliases: - radhikpac - jpbetz - cmluciano - + - bsalamat + api-approvers: + - erictune + - lavalamp + - smarterclayton + - thockin + - liggitt + # - bgrant0607 # manual escalations only + api-reviewers: + - erictune + - lavalamp + - smarterclayton + - thockin + - liggitt + - wojtek-t + - deads2k + - yujuhong + - brendandburns + - derekwaynecarr + - caesarxuchao + - vishh + - mikedanese + - nikhiljindal + - gmarek + - davidopp + - pmorie + - sttts + - dchen1107 + - saad-ali + - zmerlynn + - luxas + - janetkuo + - justinsb + - pwittrock + - roberthbailey + - ncdc + - tallclair + - yifan-gu + - eparis + - mwielgus + - timothysc + - soltysh + - piosz + - jsafrane + - jbeda diff --git a/README.md b/README.md index 87840067880..d0bc7178e61 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ $ cd kubernetes $ make quick-release ``` -If you are less impatient, head over to the [developer's documentation]. +For the full story, head over to the [developer's documentation]. ## Support @@ -71,7 +71,7 @@ That said, if you have questions, reach out to us [communication]: https://github.com/kubernetes/community/blob/master/communication.md [community repository]: https://github.com/kubernetes/community [containerized applications]: https://kubernetes.io/docs/concepts/overview/what-is-kubernetes/ -[developer's documentation]: https://github.com/kubernetes/community/tree/master/contributors/devel +[developer's documentation]: https://github.com/kubernetes/community/tree/master/contributors/devel#readme [Docker environment]: https://docs.docker.com/engine [Go environment]: https://golang.org/doc/install [GoDoc]: https://godoc.org/k8s.io/kubernetes @@ -81,6 +81,6 @@ That said, if you have questions, reach out to us [Scalable Microservices with Kubernetes]: https://www.udacity.com/course/scalable-microservices-with-kubernetes--ud615 [Submit Queue]: http://submit-queue.k8s.io/#/ci [Submit Queue Widget]: http://submit-queue.k8s.io/health.svg?v=1 -[troubleshooting guide]: https://kubernetes.io/docs/tasks/debug-application-cluster/troubleshooting/ +[troubleshooting guide]: https://kubernetes.io/docs/tasks/debug-application-cluster/troubleshooting/ [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/README.md?pixel)]() diff --git a/Vagrantfile b/Vagrantfile index 532211d0f5d..8743a6f34af 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -71,7 +71,7 @@ $kube_provider_boxes = { :libvirt => { 'fedora' => { :box_name => 'kube-fedora23', - :box_url => 'https://dl.fedoraproject.org/pub/fedora/linux/releases/23/Cloud/x86_64/Images/Fedora-Cloud-Base-Vagrant-23-20151030.x86_64.vagrant-libvirt.box' + :box_url => 'https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/23/Cloud/x86_64/Images/Fedora-Cloud-Base-Vagrant-23-20151030.x86_64.vagrant-libvirt.box' } }, :vmware_desktop => { diff --git a/api/OWNERS b/api/OWNERS index 4d61bb354ea..8f7783f9f0b 100644 --- a/api/OWNERS +++ b/api/OWNERS @@ -1,44 +1,4 @@ approvers: -- erictune -- lavalamp -- smarterclayton -- thockin -- liggitt -# - bgrant0607 # manual escalations only +- api-approvers reviewers: -- thockin -- lavalamp -- smarterclayton -- wojtek-t -- deads2k -- yujuhong -- brendandburns -- derekwaynecarr -- caesarxuchao -- vishh -- mikedanese -- liggitt -- nikhiljindal -- gmarek -- erictune -- davidopp -- pmorie -- sttts -- dchen1107 -- saad-ali -- zmerlynn -- luxas -- janetkuo -- justinsb -- pwittrock -- roberthbailey -- ncdc -- tallclair -- yifan-gu -- eparis -- mwielgus -- timothysc -- soltysh -- piosz -- jsafrane -- jbeda +- api-reviewers diff --git a/api/openapi-spec/README.md b/api/openapi-spec/README.md index 31e0ed85140..167f7cec938 100644 --- a/api/openapi-spec/README.md +++ b/api/openapi-spec/README.md @@ -4,7 +4,7 @@ This folder contains an [OpenAPI specification][openapi] for Kubernetes API. ## Vendor Extensions -Kuberntes extends OpenAPI using these extensions. Note the version that +Kubernetes extends OpenAPI using these extensions. Note the version that extensions has been added. ### `x-kubernetes-group-version-kind` @@ -56,5 +56,5 @@ For example: ### `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 -[strategic-merge-patch] (https://github.com/kubernetes/community/blob/3a1e6d22f812751ee88eccf7c59101852de63d5b/contributors/devel/strategic-merge-patch.md). +Some of the definitions may have these extensions. For more information about PatchStrategy and PatchMergeKey see +[strategic-merge-patch] (https://github.com/kubernetes/community/blob/master/contributors/devel/strategic-merge-patch.md). diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index 214870b7404..795486975c8 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -2,7 +2,7 @@ "swagger": "2.0", "info": { "title": "Kubernetes", - "version": "v1.9.0" + "version": "v1.10.0" }, "paths": { "/api/": { @@ -1294,7 +1294,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -1800,7 +1800,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -2306,7 +2306,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -2812,7 +2812,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -3318,7 +3318,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -3984,7 +3984,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -5796,7 +5796,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -6302,7 +6302,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -7128,7 +7128,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -7794,7 +7794,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -8300,7 +8300,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -9608,7 +9608,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -10320,7 +10320,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -11574,7 +11574,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -18835,496 +18835,6 @@ } } }, - "/apis/admissionregistration.k8s.io/v1alpha1/externaladmissionhookconfigurations": { - "get": { - "description": "list or watch objects of kind ExternalAdmissionHookConfiguration", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "admissionregistration_v1alpha1" - ], - "operationId": "listAdmissionregistrationV1alpha1ExternalAdmissionHookConfiguration", - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.ExternalAdmissionHookConfigurationList" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "admissionregistration.k8s.io", - "kind": "ExternalAdmissionHookConfiguration", - "version": "v1alpha1" - } - }, - "post": { - "description": "create an ExternalAdmissionHookConfiguration", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "admissionregistration_v1alpha1" - ], - "operationId": "createAdmissionregistrationV1alpha1ExternalAdmissionHookConfiguration", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.ExternalAdmissionHookConfiguration" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.ExternalAdmissionHookConfiguration" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.ExternalAdmissionHookConfiguration" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.ExternalAdmissionHookConfiguration" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "post", - "x-kubernetes-group-version-kind": { - "group": "admissionregistration.k8s.io", - "kind": "ExternalAdmissionHookConfiguration", - "version": "v1alpha1" - } - }, - "delete": { - "description": "delete collection of ExternalAdmissionHookConfiguration", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "admissionregistration_v1alpha1" - ], - "operationId": "deleteAdmissionregistrationV1alpha1CollectionExternalAdmissionHookConfiguration", - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "deletecollection", - "x-kubernetes-group-version-kind": { - "group": "admissionregistration.k8s.io", - "kind": "ExternalAdmissionHookConfiguration", - "version": "v1alpha1" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/apis/admissionregistration.k8s.io/v1alpha1/externaladmissionhookconfigurations/{name}": { - "get": { - "description": "read the specified ExternalAdmissionHookConfiguration", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "admissionregistration_v1alpha1" - ], - "operationId": "readAdmissionregistrationV1alpha1ExternalAdmissionHookConfiguration", - "parameters": [ - { - "uniqueItems": true, - "type": "boolean", - "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", - "name": "exact", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Should this value be exported. Export strips fields that a user can not specify.", - "name": "export", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.ExternalAdmissionHookConfiguration" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "admissionregistration.k8s.io", - "kind": "ExternalAdmissionHookConfiguration", - "version": "v1alpha1" - } - }, - "put": { - "description": "replace the specified ExternalAdmissionHookConfiguration", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "admissionregistration_v1alpha1" - ], - "operationId": "replaceAdmissionregistrationV1alpha1ExternalAdmissionHookConfiguration", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.ExternalAdmissionHookConfiguration" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.ExternalAdmissionHookConfiguration" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.ExternalAdmissionHookConfiguration" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "admissionregistration.k8s.io", - "kind": "ExternalAdmissionHookConfiguration", - "version": "v1alpha1" - } - }, - "delete": { - "description": "delete an ExternalAdmissionHookConfiguration", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "admissionregistration_v1alpha1" - ], - "operationId": "deleteAdmissionregistrationV1alpha1ExternalAdmissionHookConfiguration", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" - } - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "gracePeriodSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "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.", - "name": "orphanDependents", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "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.", - "name": "propagationPolicy", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "delete", - "x-kubernetes-group-version-kind": { - "group": "admissionregistration.k8s.io", - "kind": "ExternalAdmissionHookConfiguration", - "version": "v1alpha1" - } - }, - "patch": { - "description": "partially update the specified ExternalAdmissionHookConfiguration", - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "admissionregistration_v1alpha1" - ], - "operationId": "patchAdmissionregistrationV1alpha1ExternalAdmissionHookConfiguration", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.ExternalAdmissionHookConfiguration" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "admissionregistration.k8s.io", - "kind": "ExternalAdmissionHookConfiguration", - "version": "v1alpha1" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "name of the ExternalAdmissionHookConfiguration", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, "/apis/admissionregistration.k8s.io/v1alpha1/initializerconfigurations": { "get": { "description": "list or watch objects of kind InitializerConfiguration", @@ -19727,7 +19237,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -19815,222 +19325,6 @@ } ] }, - "/apis/admissionregistration.k8s.io/v1alpha1/watch/externaladmissionhookconfigurations": { - "get": { - "description": "watch individual changes to a list of ExternalAdmissionHookConfiguration", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "admissionregistration_v1alpha1" - ], - "operationId": "watchAdmissionregistrationV1alpha1ExternalAdmissionHookConfigurationList", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watchlist", - "x-kubernetes-group-version-kind": { - "group": "admissionregistration.k8s.io", - "kind": "ExternalAdmissionHookConfiguration", - "version": "v1alpha1" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/apis/admissionregistration.k8s.io/v1alpha1/watch/externaladmissionhookconfigurations/{name}": { - "get": { - "description": "watch changes to an object of kind ExternalAdmissionHookConfiguration", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "admissionregistration_v1alpha1" - ], - "operationId": "watchAdmissionregistrationV1alpha1ExternalAdmissionHookConfiguration", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watch", - "x-kubernetes-group-version-kind": { - "group": "admissionregistration.k8s.io", - "kind": "ExternalAdmissionHookConfiguration", - "version": "v1alpha1" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "name of the ExternalAdmissionHookConfiguration", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, "/apis/admissionregistration.k8s.io/v1alpha1/watch/initializerconfigurations": { "get": { "description": "watch individual changes to a list of InitializerConfiguration", @@ -20247,6 +19541,1451 @@ } ] }, + "/apis/admissionregistration.k8s.io/v1beta1/": { + "get": { + "description": "get available resources", + "consumes": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "admissionregistration_v1beta1" + ], + "operationId": "getAdmissionregistrationV1beta1APIResources", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + } + }, + "401": { + "description": "Unauthorized" + } + } + } + }, + "/apis/admissionregistration.k8s.io/v1beta1/mutatingwebhookconfigurations": { + "get": { + "description": "list or watch objects of kind MutatingWebhookConfiguration", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "admissionregistration_v1beta1" + ], + "operationId": "listAdmissionregistrationV1beta1MutatingWebhookConfiguration", + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingWebhookConfigurationList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "MutatingWebhookConfiguration", + "version": "v1beta1" + } + }, + "post": { + "description": "create a MutatingWebhookConfiguration", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "admissionregistration_v1beta1" + ], + "operationId": "createAdmissionregistrationV1beta1MutatingWebhookConfiguration", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingWebhookConfiguration" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingWebhookConfiguration" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingWebhookConfiguration" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingWebhookConfiguration" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "MutatingWebhookConfiguration", + "version": "v1beta1" + } + }, + "delete": { + "description": "delete collection of MutatingWebhookConfiguration", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "admissionregistration_v1beta1" + ], + "operationId": "deleteAdmissionregistrationV1beta1CollectionMutatingWebhookConfiguration", + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "deletecollection", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "MutatingWebhookConfiguration", + "version": "v1beta1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, + "/apis/admissionregistration.k8s.io/v1beta1/mutatingwebhookconfigurations/{name}": { + "get": { + "description": "read the specified MutatingWebhookConfiguration", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "admissionregistration_v1beta1" + ], + "operationId": "readAdmissionregistrationV1beta1MutatingWebhookConfiguration", + "parameters": [ + { + "uniqueItems": true, + "type": "boolean", + "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", + "name": "exact", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Should this value be exported. Export strips fields that a user can not specify.", + "name": "export", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingWebhookConfiguration" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "MutatingWebhookConfiguration", + "version": "v1beta1" + } + }, + "put": { + "description": "replace the specified MutatingWebhookConfiguration", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "admissionregistration_v1beta1" + ], + "operationId": "replaceAdmissionregistrationV1beta1MutatingWebhookConfiguration", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingWebhookConfiguration" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingWebhookConfiguration" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingWebhookConfiguration" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "MutatingWebhookConfiguration", + "version": "v1beta1" + } + }, + "delete": { + "description": "delete a MutatingWebhookConfiguration", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "admissionregistration_v1beta1" + ], + "operationId": "deleteAdmissionregistrationV1beta1MutatingWebhookConfiguration", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + } + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "gracePeriodSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "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.", + "name": "orphanDependents", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "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.", + "name": "propagationPolicy", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "delete", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "MutatingWebhookConfiguration", + "version": "v1beta1" + } + }, + "patch": { + "description": "partially update the specified MutatingWebhookConfiguration", + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "admissionregistration_v1beta1" + ], + "operationId": "patchAdmissionregistrationV1beta1MutatingWebhookConfiguration", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingWebhookConfiguration" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "MutatingWebhookConfiguration", + "version": "v1beta1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "name of the MutatingWebhookConfiguration", + "name": "name", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, + "/apis/admissionregistration.k8s.io/v1beta1/validatingwebhookconfigurations": { + "get": { + "description": "list or watch objects of kind ValidatingWebhookConfiguration", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "admissionregistration_v1beta1" + ], + "operationId": "listAdmissionregistrationV1beta1ValidatingWebhookConfiguration", + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.ValidatingWebhookConfigurationList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingWebhookConfiguration", + "version": "v1beta1" + } + }, + "post": { + "description": "create a ValidatingWebhookConfiguration", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "admissionregistration_v1beta1" + ], + "operationId": "createAdmissionregistrationV1beta1ValidatingWebhookConfiguration", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.ValidatingWebhookConfiguration" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.ValidatingWebhookConfiguration" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.ValidatingWebhookConfiguration" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.ValidatingWebhookConfiguration" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingWebhookConfiguration", + "version": "v1beta1" + } + }, + "delete": { + "description": "delete collection of ValidatingWebhookConfiguration", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "admissionregistration_v1beta1" + ], + "operationId": "deleteAdmissionregistrationV1beta1CollectionValidatingWebhookConfiguration", + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "deletecollection", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingWebhookConfiguration", + "version": "v1beta1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, + "/apis/admissionregistration.k8s.io/v1beta1/validatingwebhookconfigurations/{name}": { + "get": { + "description": "read the specified ValidatingWebhookConfiguration", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "admissionregistration_v1beta1" + ], + "operationId": "readAdmissionregistrationV1beta1ValidatingWebhookConfiguration", + "parameters": [ + { + "uniqueItems": true, + "type": "boolean", + "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", + "name": "exact", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Should this value be exported. Export strips fields that a user can not specify.", + "name": "export", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.ValidatingWebhookConfiguration" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingWebhookConfiguration", + "version": "v1beta1" + } + }, + "put": { + "description": "replace the specified ValidatingWebhookConfiguration", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "admissionregistration_v1beta1" + ], + "operationId": "replaceAdmissionregistrationV1beta1ValidatingWebhookConfiguration", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.ValidatingWebhookConfiguration" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.ValidatingWebhookConfiguration" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.ValidatingWebhookConfiguration" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingWebhookConfiguration", + "version": "v1beta1" + } + }, + "delete": { + "description": "delete a ValidatingWebhookConfiguration", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "admissionregistration_v1beta1" + ], + "operationId": "deleteAdmissionregistrationV1beta1ValidatingWebhookConfiguration", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + } + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "gracePeriodSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "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.", + "name": "orphanDependents", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "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.", + "name": "propagationPolicy", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "delete", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingWebhookConfiguration", + "version": "v1beta1" + } + }, + "patch": { + "description": "partially update the specified ValidatingWebhookConfiguration", + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "admissionregistration_v1beta1" + ], + "operationId": "patchAdmissionregistrationV1beta1ValidatingWebhookConfiguration", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.ValidatingWebhookConfiguration" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingWebhookConfiguration", + "version": "v1beta1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "name of the ValidatingWebhookConfiguration", + "name": "name", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, + "/apis/admissionregistration.k8s.io/v1beta1/watch/mutatingwebhookconfigurations": { + "get": { + "description": "watch individual changes to a list of MutatingWebhookConfiguration", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "admissionregistration_v1beta1" + ], + "operationId": "watchAdmissionregistrationV1beta1MutatingWebhookConfigurationList", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "MutatingWebhookConfiguration", + "version": "v1beta1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, + "/apis/admissionregistration.k8s.io/v1beta1/watch/mutatingwebhookconfigurations/{name}": { + "get": { + "description": "watch changes to an object of kind MutatingWebhookConfiguration", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "admissionregistration_v1beta1" + ], + "operationId": "watchAdmissionregistrationV1beta1MutatingWebhookConfiguration", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "watch", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "MutatingWebhookConfiguration", + "version": "v1beta1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "name of the MutatingWebhookConfiguration", + "name": "name", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, + "/apis/admissionregistration.k8s.io/v1beta1/watch/validatingwebhookconfigurations": { + "get": { + "description": "watch individual changes to a list of ValidatingWebhookConfiguration", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "admissionregistration_v1beta1" + ], + "operationId": "watchAdmissionregistrationV1beta1ValidatingWebhookConfigurationList", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingWebhookConfiguration", + "version": "v1beta1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, + "/apis/admissionregistration.k8s.io/v1beta1/watch/validatingwebhookconfigurations/{name}": { + "get": { + "description": "watch changes to an object of kind ValidatingWebhookConfiguration", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "admissionregistration_v1beta1" + ], + "operationId": "watchAdmissionregistrationV1beta1ValidatingWebhookConfiguration", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "watch", + "x-kubernetes-group-version-kind": { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingWebhookConfiguration", + "version": "v1beta1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "name of the ValidatingWebhookConfiguration", + "name": "name", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, "/apis/apiextensions.k8s.io/": { "get": { "description": "get information of a group", @@ -20715,7 +21454,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -21557,7 +22296,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -21997,6 +22736,110 @@ } } }, + "/apis/apps/v1/controllerrevisions": { + "get": { + "description": "list or watch objects of kind ControllerRevision", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "listAppsV1ControllerRevisionForAllNamespaces", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevisionList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "ControllerRevision", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, "/apis/apps/v1/daemonsets": { "get": { "description": "list or watch objects of kind DaemonSet", @@ -22101,6 +22944,616 @@ } ] }, + "/apis/apps/v1/deployments": { + "get": { + "description": "list or watch objects of kind Deployment", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "listAppsV1DeploymentForAllNamespaces", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.DeploymentList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "Deployment", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, + "/apis/apps/v1/namespaces/{namespace}/controllerrevisions": { + "get": { + "description": "list or watch objects of kind ControllerRevision", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "listAppsV1NamespacedControllerRevision", + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevisionList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "ControllerRevision", + "version": "v1" + } + }, + "post": { + "description": "create a ControllerRevision", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "createAppsV1NamespacedControllerRevision", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "ControllerRevision", + "version": "v1" + } + }, + "delete": { + "description": "delete collection of ControllerRevision", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "deleteAppsV1CollectionNamespacedControllerRevision", + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "deletecollection", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "ControllerRevision", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, + "/apis/apps/v1/namespaces/{namespace}/controllerrevisions/{name}": { + "get": { + "description": "read the specified ControllerRevision", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "readAppsV1NamespacedControllerRevision", + "parameters": [ + { + "uniqueItems": true, + "type": "boolean", + "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", + "name": "exact", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Should this value be exported. Export strips fields that a user can not specify.", + "name": "export", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "ControllerRevision", + "version": "v1" + } + }, + "put": { + "description": "replace the specified ControllerRevision", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "replaceAppsV1NamespacedControllerRevision", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "ControllerRevision", + "version": "v1" + } + }, + "delete": { + "description": "delete a ControllerRevision", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "deleteAppsV1NamespacedControllerRevision", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + } + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "gracePeriodSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "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.", + "name": "orphanDependents", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "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.", + "name": "propagationPolicy", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "delete", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "ControllerRevision", + "version": "v1" + } + }, + "patch": { + "description": "partially update the specified ControllerRevision", + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "patchAppsV1NamespacedControllerRevision", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "ControllerRevision", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "name of the ControllerRevision", + "name": "name", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, "/apis/apps/v1/namespaces/{namespace}/daemonsets": { "get": { "description": "list or watch objects of kind DaemonSet", @@ -22511,7 +23964,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -22767,6 +24220,2796 @@ } ] }, + "/apis/apps/v1/namespaces/{namespace}/deployments": { + "get": { + "description": "list or watch objects of kind Deployment", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "listAppsV1NamespacedDeployment", + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.DeploymentList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "Deployment", + "version": "v1" + } + }, + "post": { + "description": "create a Deployment", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "createAppsV1NamespacedDeployment", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "Deployment", + "version": "v1" + } + }, + "delete": { + "description": "delete collection of Deployment", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "deleteAppsV1CollectionNamespacedDeployment", + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "deletecollection", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "Deployment", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, + "/apis/apps/v1/namespaces/{namespace}/deployments/{name}": { + "get": { + "description": "read the specified Deployment", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "readAppsV1NamespacedDeployment", + "parameters": [ + { + "uniqueItems": true, + "type": "boolean", + "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", + "name": "exact", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Should this value be exported. Export strips fields that a user can not specify.", + "name": "export", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "Deployment", + "version": "v1" + } + }, + "put": { + "description": "replace the specified Deployment", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "replaceAppsV1NamespacedDeployment", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "Deployment", + "version": "v1" + } + }, + "delete": { + "description": "delete a Deployment", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "deleteAppsV1NamespacedDeployment", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + } + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "gracePeriodSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "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.", + "name": "orphanDependents", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "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.", + "name": "propagationPolicy", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "delete", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "Deployment", + "version": "v1" + } + }, + "patch": { + "description": "partially update the specified Deployment", + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "patchAppsV1NamespacedDeployment", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "Deployment", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "name of the Deployment", + "name": "name", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, + "/apis/apps/v1/namespaces/{namespace}/deployments/{name}/scale": { + "get": { + "description": "read scale of the specified Deployment", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "readAppsV1NamespacedDeploymentScale", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.autoscaling.v1.Scale" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "autoscaling", + "kind": "Scale", + "version": "v1" + } + }, + "put": { + "description": "replace scale of the specified Deployment", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "replaceAppsV1NamespacedDeploymentScale", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.autoscaling.v1.Scale" + } + } + ], + "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" + } + }, + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "autoscaling", + "kind": "Scale", + "version": "v1" + } + }, + "patch": { + "description": "partially update scale of the specified Deployment", + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "patchAppsV1NamespacedDeploymentScale", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.autoscaling.v1.Scale" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "autoscaling", + "kind": "Scale", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "name of the Scale", + "name": "name", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, + "/apis/apps/v1/namespaces/{namespace}/deployments/{name}/status": { + "get": { + "description": "read status of the specified Deployment", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "readAppsV1NamespacedDeploymentStatus", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "Deployment", + "version": "v1" + } + }, + "put": { + "description": "replace status of the specified Deployment", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "replaceAppsV1NamespacedDeploymentStatus", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "Deployment", + "version": "v1" + } + }, + "patch": { + "description": "partially update status of the specified Deployment", + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "patchAppsV1NamespacedDeploymentStatus", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "Deployment", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "name of the Deployment", + "name": "name", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, + "/apis/apps/v1/namespaces/{namespace}/replicasets": { + "get": { + "description": "list or watch objects of kind ReplicaSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "listAppsV1NamespacedReplicaSet", + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSetList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "ReplicaSet", + "version": "v1" + } + }, + "post": { + "description": "create a ReplicaSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "createAppsV1NamespacedReplicaSet", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "ReplicaSet", + "version": "v1" + } + }, + "delete": { + "description": "delete collection of ReplicaSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "deleteAppsV1CollectionNamespacedReplicaSet", + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "deletecollection", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "ReplicaSet", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, + "/apis/apps/v1/namespaces/{namespace}/replicasets/{name}": { + "get": { + "description": "read the specified ReplicaSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "readAppsV1NamespacedReplicaSet", + "parameters": [ + { + "uniqueItems": true, + "type": "boolean", + "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", + "name": "exact", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Should this value be exported. Export strips fields that a user can not specify.", + "name": "export", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "ReplicaSet", + "version": "v1" + } + }, + "put": { + "description": "replace the specified ReplicaSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "replaceAppsV1NamespacedReplicaSet", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "ReplicaSet", + "version": "v1" + } + }, + "delete": { + "description": "delete a ReplicaSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "deleteAppsV1NamespacedReplicaSet", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + } + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "gracePeriodSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "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.", + "name": "orphanDependents", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "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.", + "name": "propagationPolicy", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "delete", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "ReplicaSet", + "version": "v1" + } + }, + "patch": { + "description": "partially update the specified ReplicaSet", + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "patchAppsV1NamespacedReplicaSet", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "ReplicaSet", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "name of the ReplicaSet", + "name": "name", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, + "/apis/apps/v1/namespaces/{namespace}/replicasets/{name}/scale": { + "get": { + "description": "read scale of the specified ReplicaSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "readAppsV1NamespacedReplicaSetScale", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.autoscaling.v1.Scale" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "autoscaling", + "kind": "Scale", + "version": "v1" + } + }, + "put": { + "description": "replace scale of the specified ReplicaSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "replaceAppsV1NamespacedReplicaSetScale", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.autoscaling.v1.Scale" + } + } + ], + "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" + } + }, + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "autoscaling", + "kind": "Scale", + "version": "v1" + } + }, + "patch": { + "description": "partially update scale of the specified ReplicaSet", + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "patchAppsV1NamespacedReplicaSetScale", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.autoscaling.v1.Scale" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "autoscaling", + "kind": "Scale", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "name of the Scale", + "name": "name", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, + "/apis/apps/v1/namespaces/{namespace}/replicasets/{name}/status": { + "get": { + "description": "read status of the specified ReplicaSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "readAppsV1NamespacedReplicaSetStatus", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "ReplicaSet", + "version": "v1" + } + }, + "put": { + "description": "replace status of the specified ReplicaSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "replaceAppsV1NamespacedReplicaSetStatus", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "ReplicaSet", + "version": "v1" + } + }, + "patch": { + "description": "partially update status of the specified ReplicaSet", + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "patchAppsV1NamespacedReplicaSetStatus", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "ReplicaSet", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "name of the ReplicaSet", + "name": "name", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, + "/apis/apps/v1/namespaces/{namespace}/statefulsets": { + "get": { + "description": "list or watch objects of kind StatefulSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "listAppsV1NamespacedStatefulSet", + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSetList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "StatefulSet", + "version": "v1" + } + }, + "post": { + "description": "create a StatefulSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "createAppsV1NamespacedStatefulSet", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + } + } + ], + "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" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "StatefulSet", + "version": "v1" + } + }, + "delete": { + "description": "delete collection of StatefulSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "deleteAppsV1CollectionNamespacedStatefulSet", + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "deletecollection", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "StatefulSet", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, + "/apis/apps/v1/namespaces/{namespace}/statefulsets/{name}": { + "get": { + "description": "read the specified StatefulSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "readAppsV1NamespacedStatefulSet", + "parameters": [ + { + "uniqueItems": true, + "type": "boolean", + "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", + "name": "exact", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Should this value be exported. Export strips fields that a user can not specify.", + "name": "export", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "StatefulSet", + "version": "v1" + } + }, + "put": { + "description": "replace the specified StatefulSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "replaceAppsV1NamespacedStatefulSet", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + } + } + ], + "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" + } + }, + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "StatefulSet", + "version": "v1" + } + }, + "delete": { + "description": "delete a StatefulSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "deleteAppsV1NamespacedStatefulSet", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + } + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "gracePeriodSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "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.", + "name": "orphanDependents", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "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.", + "name": "propagationPolicy", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "delete", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "StatefulSet", + "version": "v1" + } + }, + "patch": { + "description": "partially update the specified StatefulSet", + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "patchAppsV1NamespacedStatefulSet", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "StatefulSet", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "name of the StatefulSet", + "name": "name", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, + "/apis/apps/v1/namespaces/{namespace}/statefulsets/{name}/scale": { + "get": { + "description": "read scale of the specified StatefulSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "readAppsV1NamespacedStatefulSetScale", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.autoscaling.v1.Scale" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "autoscaling", + "kind": "Scale", + "version": "v1" + } + }, + "put": { + "description": "replace scale of the specified StatefulSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "replaceAppsV1NamespacedStatefulSetScale", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.autoscaling.v1.Scale" + } + } + ], + "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" + } + }, + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "autoscaling", + "kind": "Scale", + "version": "v1" + } + }, + "patch": { + "description": "partially update scale of the specified StatefulSet", + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "patchAppsV1NamespacedStatefulSetScale", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.autoscaling.v1.Scale" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "autoscaling", + "kind": "Scale", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "name of the Scale", + "name": "name", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, + "/apis/apps/v1/namespaces/{namespace}/statefulsets/{name}/status": { + "get": { + "description": "read status of the specified StatefulSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "readAppsV1NamespacedStatefulSetStatus", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "StatefulSet", + "version": "v1" + } + }, + "put": { + "description": "replace status of the specified StatefulSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "replaceAppsV1NamespacedStatefulSetStatus", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + } + } + ], + "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" + } + }, + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "StatefulSet", + "version": "v1" + } + }, + "patch": { + "description": "partially update status of the specified StatefulSet", + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "patchAppsV1NamespacedStatefulSetStatus", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "StatefulSet", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "name of the StatefulSet", + "name": "name", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, + "/apis/apps/v1/replicasets": { + "get": { + "description": "list or watch objects of kind ReplicaSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "listAppsV1ReplicaSetForAllNamespaces", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSetList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "ReplicaSet", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, + "/apis/apps/v1/statefulsets": { + "get": { + "description": "list or watch objects of kind StatefulSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "listAppsV1StatefulSetForAllNamespaces", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSetList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "StatefulSet", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, + "/apis/apps/v1/watch/controllerrevisions": { + "get": { + "description": "watch individual changes to a list of ControllerRevision", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "watchAppsV1ControllerRevisionListForAllNamespaces", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "ControllerRevision", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, "/apis/apps/v1/watch/daemonsets": { "get": { "description": "watch individual changes to a list of DaemonSet", @@ -22871,6 +27114,342 @@ } ] }, + "/apis/apps/v1/watch/deployments": { + "get": { + "description": "watch individual changes to a list of Deployment", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "watchAppsV1DeploymentListForAllNamespaces", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "Deployment", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, + "/apis/apps/v1/watch/namespaces/{namespace}/controllerrevisions": { + "get": { + "description": "watch individual changes to a list of ControllerRevision", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "watchAppsV1NamespacedControllerRevisionList", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "ControllerRevision", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, + "/apis/apps/v1/watch/namespaces/{namespace}/controllerrevisions/{name}": { + "get": { + "description": "watch changes to an object of kind ControllerRevision", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "watchAppsV1NamespacedControllerRevision", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "watch", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "ControllerRevision", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "name of the ControllerRevision", + "name": "name", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, "/apis/apps/v1/watch/namespaces/{namespace}/daemonsets": { "get": { "description": "watch individual changes to a list of DaemonSet", @@ -23103,6 +27682,910 @@ } ] }, + "/apis/apps/v1/watch/namespaces/{namespace}/deployments": { + "get": { + "description": "watch individual changes to a list of Deployment", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "watchAppsV1NamespacedDeploymentList", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "Deployment", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, + "/apis/apps/v1/watch/namespaces/{namespace}/deployments/{name}": { + "get": { + "description": "watch changes to an object of kind Deployment", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "watchAppsV1NamespacedDeployment", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "watch", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "Deployment", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "name of the Deployment", + "name": "name", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, + "/apis/apps/v1/watch/namespaces/{namespace}/replicasets": { + "get": { + "description": "watch individual changes to a list of ReplicaSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "watchAppsV1NamespacedReplicaSetList", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "ReplicaSet", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, + "/apis/apps/v1/watch/namespaces/{namespace}/replicasets/{name}": { + "get": { + "description": "watch changes to an object of kind ReplicaSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "watchAppsV1NamespacedReplicaSet", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "watch", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "ReplicaSet", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "name of the ReplicaSet", + "name": "name", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, + "/apis/apps/v1/watch/namespaces/{namespace}/statefulsets": { + "get": { + "description": "watch individual changes to a list of StatefulSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "watchAppsV1NamespacedStatefulSetList", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "StatefulSet", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, + "/apis/apps/v1/watch/namespaces/{namespace}/statefulsets/{name}": { + "get": { + "description": "watch changes to an object of kind StatefulSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "watchAppsV1NamespacedStatefulSet", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "watch", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "StatefulSet", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "name of the StatefulSet", + "name": "name", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, + "/apis/apps/v1/watch/replicasets": { + "get": { + "description": "watch individual changes to a list of ReplicaSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "watchAppsV1ReplicaSetListForAllNamespaces", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "ReplicaSet", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, + "/apis/apps/v1/watch/statefulsets": { + "get": { + "description": "watch individual changes to a list of StatefulSet", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "apps_v1" + ], + "operationId": "watchAppsV1StatefulSetListForAllNamespaces", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "apps", + "kind": "StatefulSet", + "version": "v1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, "/apis/apps/v1beta1/": { "get": { "description": "get available resources", @@ -23754,7 +29237,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -24260,7 +29743,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -25170,7 +30653,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -27453,7 +32936,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -27959,7 +33442,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -28625,7 +34108,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -29451,7 +34934,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -30277,7 +35760,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -34055,7 +39538,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -35194,7 +40677,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -36366,7 +41849,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -37505,7 +42988,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -38644,7 +44127,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -39704,7 +45187,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -40148,6 +45631,1018 @@ } ] }, + "/apis/events.k8s.io/": { + "get": { + "description": "get information of a group", + "consumes": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "events" + ], + "operationId": "getEventsAPIGroup", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup" + } + }, + "401": { + "description": "Unauthorized" + } + } + } + }, + "/apis/events.k8s.io/v1beta1/": { + "get": { + "description": "get available resources", + "consumes": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "events_v1beta1" + ], + "operationId": "getEventsV1beta1APIResources", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + } + }, + "401": { + "description": "Unauthorized" + } + } + } + }, + "/apis/events.k8s.io/v1beta1/events": { + "get": { + "description": "list or watch objects of kind Event", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "events_v1beta1" + ], + "operationId": "listEventsV1beta1EventForAllNamespaces", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.events.v1beta1.EventList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "events.k8s.io", + "kind": "Event", + "version": "v1beta1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, + "/apis/events.k8s.io/v1beta1/namespaces/{namespace}/events": { + "get": { + "description": "list or watch objects of kind Event", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "events_v1beta1" + ], + "operationId": "listEventsV1beta1NamespacedEvent", + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.events.v1beta1.EventList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "events.k8s.io", + "kind": "Event", + "version": "v1beta1" + } + }, + "post": { + "description": "create an Event", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "events_v1beta1" + ], + "operationId": "createEventsV1beta1NamespacedEvent", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.events.v1beta1.Event" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.events.v1beta1.Event" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.events.v1beta1.Event" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.events.v1beta1.Event" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "events.k8s.io", + "kind": "Event", + "version": "v1beta1" + } + }, + "delete": { + "description": "delete collection of Event", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "events_v1beta1" + ], + "operationId": "deleteEventsV1beta1CollectionNamespacedEvent", + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "deletecollection", + "x-kubernetes-group-version-kind": { + "group": "events.k8s.io", + "kind": "Event", + "version": "v1beta1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, + "/apis/events.k8s.io/v1beta1/namespaces/{namespace}/events/{name}": { + "get": { + "description": "read the specified Event", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "events_v1beta1" + ], + "operationId": "readEventsV1beta1NamespacedEvent", + "parameters": [ + { + "uniqueItems": true, + "type": "boolean", + "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", + "name": "exact", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Should this value be exported. Export strips fields that a user can not specify.", + "name": "export", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.events.v1beta1.Event" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "events.k8s.io", + "kind": "Event", + "version": "v1beta1" + } + }, + "put": { + "description": "replace the specified Event", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "events_v1beta1" + ], + "operationId": "replaceEventsV1beta1NamespacedEvent", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.events.v1beta1.Event" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.events.v1beta1.Event" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.events.v1beta1.Event" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "events.k8s.io", + "kind": "Event", + "version": "v1beta1" + } + }, + "delete": { + "description": "delete an Event", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "events_v1beta1" + ], + "operationId": "deleteEventsV1beta1NamespacedEvent", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + } + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "gracePeriodSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "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.", + "name": "orphanDependents", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "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.", + "name": "propagationPolicy", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "delete", + "x-kubernetes-group-version-kind": { + "group": "events.k8s.io", + "kind": "Event", + "version": "v1beta1" + } + }, + "patch": { + "description": "partially update the specified Event", + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "events_v1beta1" + ], + "operationId": "patchEventsV1beta1NamespacedEvent", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.events.v1beta1.Event" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "events.k8s.io", + "kind": "Event", + "version": "v1beta1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "name of the Event", + "name": "name", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, + "/apis/events.k8s.io/v1beta1/watch/events": { + "get": { + "description": "watch individual changes to a list of Event", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "events_v1beta1" + ], + "operationId": "watchEventsV1beta1EventListForAllNamespaces", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "events.k8s.io", + "kind": "Event", + "version": "v1beta1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, + "/apis/events.k8s.io/v1beta1/watch/namespaces/{namespace}/events": { + "get": { + "description": "watch individual changes to a list of Event", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "events_v1beta1" + ], + "operationId": "watchEventsV1beta1NamespacedEventList", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "events.k8s.io", + "kind": "Event", + "version": "v1beta1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, + "/apis/events.k8s.io/v1beta1/watch/namespaces/{namespace}/events/{name}": { + "get": { + "description": "watch changes to an object of kind Event", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "events_v1beta1" + ], + "operationId": "watchEventsV1beta1NamespacedEvent", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "watch", + "x-kubernetes-group-version-kind": { + "group": "events.k8s.io", + "kind": "Event", + "version": "v1beta1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "name of the Event", + "name": "name", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "object name and auth scope, such as for teams and projects", + "name": "namespace", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, "/apis/extensions/": { "get": { "description": "get information of a group", @@ -40936,7 +47431,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -41602,7 +48097,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -42512,7 +49007,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -43178,7 +49673,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -43684,7 +50179,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -44766,7 +51261,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -47330,7 +53825,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -48342,7 +54837,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -49490,7 +55985,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -49964,7 +56459,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -50446,7 +56941,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -50936,7 +57431,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -52763,7 +59258,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -53237,7 +59732,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -53719,7 +60214,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -54209,7 +60704,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -56036,7 +62531,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -56510,7 +63005,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -56992,7 +63487,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -57482,7 +63977,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -59358,7 +65853,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -60138,7 +66633,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -61142,7 +67637,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -61446,6 +67941,745 @@ } ] }, + "/apis/storage.k8s.io/v1alpha1/": { + "get": { + "description": "get available resources", + "consumes": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "storage_v1alpha1" + ], + "operationId": "getStorageV1alpha1APIResources", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + } + }, + "401": { + "description": "Unauthorized" + } + } + } + }, + "/apis/storage.k8s.io/v1alpha1/volumeattachments": { + "get": { + "description": "list or watch objects of kind VolumeAttachment", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "storage_v1alpha1" + ], + "operationId": "listStorageV1alpha1VolumeAttachment", + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.storage.v1alpha1.VolumeAttachmentList" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "storage.k8s.io", + "kind": "VolumeAttachment", + "version": "v1alpha1" + } + }, + "post": { + "description": "create a VolumeAttachment", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "storage_v1alpha1" + ], + "operationId": "createStorageV1alpha1VolumeAttachment", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.storage.v1alpha1.VolumeAttachment" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.storage.v1alpha1.VolumeAttachment" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.storage.v1alpha1.VolumeAttachment" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.k8s.api.storage.v1alpha1.VolumeAttachment" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "storage.k8s.io", + "kind": "VolumeAttachment", + "version": "v1alpha1" + } + }, + "delete": { + "description": "delete collection of VolumeAttachment", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "storage_v1alpha1" + ], + "operationId": "deleteStorageV1alpha1CollectionVolumeAttachment", + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "deletecollection", + "x-kubernetes-group-version-kind": { + "group": "storage.k8s.io", + "kind": "VolumeAttachment", + "version": "v1alpha1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, + "/apis/storage.k8s.io/v1alpha1/volumeattachments/{name}": { + "get": { + "description": "read the specified VolumeAttachment", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "storage_v1alpha1" + ], + "operationId": "readStorageV1alpha1VolumeAttachment", + "parameters": [ + { + "uniqueItems": true, + "type": "boolean", + "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", + "name": "exact", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Should this value be exported. Export strips fields that a user can not specify.", + "name": "export", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.storage.v1alpha1.VolumeAttachment" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "storage.k8s.io", + "kind": "VolumeAttachment", + "version": "v1alpha1" + } + }, + "put": { + "description": "replace the specified VolumeAttachment", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "storage_v1alpha1" + ], + "operationId": "replaceStorageV1alpha1VolumeAttachment", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.api.storage.v1alpha1.VolumeAttachment" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.storage.v1alpha1.VolumeAttachment" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.k8s.api.storage.v1alpha1.VolumeAttachment" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "storage.k8s.io", + "kind": "VolumeAttachment", + "version": "v1alpha1" + } + }, + "delete": { + "description": "delete a VolumeAttachment", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "storage_v1alpha1" + ], + "operationId": "deleteStorageV1alpha1VolumeAttachment", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + } + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "gracePeriodSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "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.", + "name": "orphanDependents", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "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.", + "name": "propagationPolicy", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "delete", + "x-kubernetes-group-version-kind": { + "group": "storage.k8s.io", + "kind": "VolumeAttachment", + "version": "v1alpha1" + } + }, + "patch": { + "description": "partially update the specified VolumeAttachment", + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "storage_v1alpha1" + ], + "operationId": "patchStorageV1alpha1VolumeAttachment", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.api.storage.v1alpha1.VolumeAttachment" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "storage.k8s.io", + "kind": "VolumeAttachment", + "version": "v1alpha1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "name of the VolumeAttachment", + "name": "name", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, + "/apis/storage.k8s.io/v1alpha1/watch/volumeattachments": { + "get": { + "description": "watch individual changes to a list of VolumeAttachment", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "storage_v1alpha1" + ], + "operationId": "watchStorageV1alpha1VolumeAttachmentList", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "storage.k8s.io", + "kind": "VolumeAttachment", + "version": "v1alpha1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, + "/apis/storage.k8s.io/v1alpha1/watch/volumeattachments/{name}": { + "get": { + "description": "watch changes to an object of kind VolumeAttachment", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "storage_v1alpha1" + ], + "operationId": "watchStorageV1alpha1VolumeAttachment", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "x-kubernetes-action": "watch", + "x-kubernetes-group-version-kind": { + "group": "storage.k8s.io", + "kind": "VolumeAttachment", + "version": "v1alpha1" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "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 indicating the client must restart their list without the continue field. This 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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "If true, partially initialized resources are included in the response.", + "name": "includeUninitialized", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "name of the VolumeAttachment", + "name": "name", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, "/apis/storage.k8s.io/v1beta1/": { "get": { "description": "get available resources", @@ -61881,7 +69115,7 @@ { "uniqueItems": true, "type": "string", - "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.", + "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.", "name": "propagationPolicy", "in": "query" } @@ -62258,119 +69492,6 @@ } }, "definitions": { - "io.k8s.api.admissionregistration.v1alpha1.AdmissionHookClientConfig": { - "description": "AdmissionHookClientConfig contains the information to make a TLS connection with the webhook", - "required": [ - "service", - "caBundle" - ], - "properties": { - "caBundle": { - "description": "CABundle is a PEM encoded CA bundle which will be used to validate webhook's server certificate. Required", - "type": "string", - "format": "byte" - }, - "service": { - "description": "Service is a reference to the service for this webhook. If there is only one port open for the service, that port will be used. If there are multiple ports open, port 443 will be used if it is open, otherwise it is an error. Required", - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.ServiceReference" - } - } - }, - "io.k8s.api.admissionregistration.v1alpha1.ExternalAdmissionHook": { - "description": "ExternalAdmissionHook describes an external admission webhook and the resources and operations it applies to.", - "required": [ - "name", - "clientConfig" - ], - "properties": { - "clientConfig": { - "description": "ClientConfig defines how to communicate with the hook. Required", - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.AdmissionHookClientConfig" - }, - "failurePolicy": { - "description": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Ignore.", - "type": "string" - }, - "name": { - "description": "The name of the external 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" - }, - "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.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.RuleWithOperations" - } - } - } - }, - "io.k8s.api.admissionregistration.v1alpha1.ExternalAdmissionHookConfiguration": { - "description": "ExternalAdmissionHookConfiguration describes the configuration of initializers.", - "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/api-conventions.md#resources", - "type": "string" - }, - "externalAdmissionHooks": { - "description": "ExternalAdmissionHooks is a list of external admission webhooks and the affected resources and operations.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.ExternalAdmissionHook" - }, - "x-kubernetes-patch-merge-key": "name", - "x-kubernetes-patch-strategy": "merge" - }, - "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/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "admissionregistration.k8s.io", - "kind": "ExternalAdmissionHookConfiguration", - "version": "v1alpha1" - } - ] - }, - "io.k8s.api.admissionregistration.v1alpha1.ExternalAdmissionHookConfigurationList": { - "description": "ExternalAdmissionHookConfigurationList is a list of ExternalAdmissionHookConfiguration.", - "required": [ - "items" - ], - "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/api-conventions.md#resources", - "type": "string" - }, - "items": { - "description": "List of ExternalAdmissionHookConfiguration.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.ExternalAdmissionHookConfiguration" - } - }, - "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/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "admissionregistration.k8s.io", - "kind": "ExternalAdmissionHookConfigurationList", - "version": "v1alpha1" - } - ] - }, "io.k8s.api.admissionregistration.v1alpha1.Initializer": { "description": "Initializer describes the name and the failure policy of an initializer, and what resources it applies to.", "required": [ @@ -62483,7 +69604,74 @@ } } }, - "io.k8s.api.admissionregistration.v1alpha1.RuleWithOperations": { + "io.k8s.api.admissionregistration.v1beta1.MutatingWebhookConfiguration": { + "description": "MutatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and may change the 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/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/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" + }, + "webhooks": { + "description": "Webhooks is a list of webhooks and the affected resources and operations.", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.Webhook" + }, + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "admissionregistration.k8s.io", + "kind": "MutatingWebhookConfiguration", + "version": "v1beta1" + } + ] + }, + "io.k8s.api.admissionregistration.v1beta1.MutatingWebhookConfigurationList": { + "description": "MutatingWebhookConfigurationList is a list of MutatingWebhookConfiguration.", + "required": [ + "items" + ], + "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/api-conventions.md#resources", + "type": "string" + }, + "items": { + "description": "List of MutatingWebhookConfiguration.", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingWebhookConfiguration" + } + }, + "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/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "admissionregistration.k8s.io", + "kind": "MutatingWebhookConfigurationList", + "version": "v1beta1" + } + ] + }, + "io.k8s.api.admissionregistration.v1beta1.RuleWithOperations": { "description": "RuleWithOperations is a tuple of Operations and Resources. It is recommended to make sure that all the tuple expansions are valid.", "properties": { "apiGroups": { @@ -62516,7 +69704,7 @@ } } }, - "io.k8s.api.admissionregistration.v1alpha1.ServiceReference": { + "io.k8s.api.admissionregistration.v1beta1.ServiceReference": { "description": "ServiceReference holds a reference to Service.legacy.k8s.io", "required": [ "namespace", @@ -62524,15 +69712,209 @@ ], "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.", "type": "string" } } }, + "io.k8s.api.admissionregistration.v1beta1.ValidatingWebhookConfiguration": { + "description": "ValidatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and object without changing it.", + "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/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/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" + }, + "webhooks": { + "description": "Webhooks is a list of webhooks and the affected resources and operations.", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.Webhook" + }, + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingWebhookConfiguration", + "version": "v1beta1" + } + ] + }, + "io.k8s.api.admissionregistration.v1beta1.ValidatingWebhookConfigurationList": { + "description": "ValidatingWebhookConfigurationList is a list of ValidatingWebhookConfiguration.", + "required": [ + "items" + ], + "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/api-conventions.md#resources", + "type": "string" + }, + "items": { + "description": "List of ValidatingWebhookConfiguration.", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.ValidatingWebhookConfiguration" + } + }, + "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/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "admissionregistration.k8s.io", + "kind": "ValidatingWebhookConfigurationList", + "version": "v1beta1" + } + ] + }, + "io.k8s.api.admissionregistration.v1beta1.Webhook": { + "description": "Webhook describes an admission webhook and the resources and operations it applies to.", + "required": [ + "name", + "clientConfig" + ], + "properties": { + "clientConfig": { + "description": "ClientConfig defines how to communicate with the hook. Required", + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.WebhookClientConfig" + }, + "failurePolicy": { + "description": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Ignore.", + "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.", + "type": "string" + }, + "namespaceSelector": { + "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 other cluster scoped resource, it is not subjected to 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": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" + }, + "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.", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.RuleWithOperations" + } + } + } + }, + "io.k8s.api.admissionregistration.v1beta1.WebhookClientConfig": { + "description": "WebhookClientConfig contains the information to make a TLS connection with the webhook", + "required": [ + "caBundle" + ], + "properties": { + "caBundle": { + "description": "`caBundle` is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. Required.", + "type": "string", + "format": "byte" + }, + "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`.\n\nIf there is only one port open for the service, that port will be used. If there are multiple ports open, port 443 will be used if it is open, otherwise it is an error.", + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.ServiceReference" + }, + "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.", + "type": "string" + } + } + }, + "io.k8s.api.apps.v1.ControllerRevision": { + "description": "ControllerRevision implements an immutable snapshot of state data. Clients are responsible for serializing and deserializing the objects that contain their internal state. Once a ControllerRevision has been successfully created, it can not be updated. The API Server will fail validation of all requests that attempt to mutate the Data field. ControllerRevisions may, however, be deleted. Note that, due to its use by both the DaemonSet and StatefulSet controllers for update and rollback, this object is beta. However, it may be subject to name and representation changes in future releases, and clients should not depend on its stability. It is primarily for internal use by controllers.", + "required": [ + "revision" + ], + "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/api-conventions.md#resources", + "type": "string" + }, + "data": { + "description": "Data is the serialized representation of the state.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.runtime.RawExtension" + }, + "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/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" + }, + "revision": { + "description": "Revision indicates the revision of the state represented by Data.", + "type": "integer", + "format": "int64" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "apps", + "kind": "ControllerRevision", + "version": "v1" + } + ] + }, + "io.k8s.api.apps.v1.ControllerRevisionList": { + "description": "ControllerRevisionList is a resource containing a list of ControllerRevision objects.", + "required": [ + "items" + ], + "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/api-conventions.md#resources", + "type": "string" + }, + "items": { + "description": "Items is the list of ControllerRevisions", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ControllerRevision" + } + }, + "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/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "description": "More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "apps", + "kind": "ControllerRevisionList", + "version": "v1" + } + ] + }, "io.k8s.api.apps.v1.DaemonSet": { "description": "DaemonSet represents the configuration of a daemon set.", "properties": { @@ -62565,6 +69947,35 @@ } ] }, + "io.k8s.api.apps.v1.DaemonSetCondition": { + "description": "DaemonSetCondition describes the state of a DaemonSet at a certain point.", + "required": [ + "type", + "status" + ], + "properties": { + "lastTransitionTime": { + "description": "Last time the condition transitioned from one status to another.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + }, + "message": { + "description": "A human readable message indicating details about the transition.", + "type": "string" + }, + "reason": { + "description": "The reason for the condition's last transition.", + "type": "string" + }, + "status": { + "description": "Status of the condition, one of True, False, Unknown.", + "type": "string" + }, + "type": { + "description": "Type of DaemonSet condition.", + "type": "string" + } + } + }, "io.k8s.api.apps.v1.DaemonSetList": { "description": "DaemonSetList is a collection of daemon sets.", "required": [ @@ -62602,6 +70013,7 @@ "io.k8s.api.apps.v1.DaemonSetSpec": { "description": "DaemonSetSpec is the specification of a daemon set.", "required": [ + "selector", "template" ], "properties": { @@ -62616,7 +70028,7 @@ "format": "int32" }, "selector": { - "description": "A label query over pods that are managed by the daemon set. Must match in order to be controlled. If empty, defaulted to labels on Pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", + "description": "A label query over pods that are managed by the daemon set. Must match in order to be controlled. It must match the pod template's labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" }, "template": { @@ -62643,6 +70055,15 @@ "type": "integer", "format": "int32" }, + "conditions": { + "description": "Represents the latest available observations of a DaemonSet's current state.", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSetCondition" + }, + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge" + }, "currentNumberScheduled": { "description": "The number of nodes that are running at least 1 daemon pod and are supposed to run the daemon pod. More info: https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/", "type": "integer", @@ -62698,6 +70119,375 @@ } } }, + "io.k8s.api.apps.v1.Deployment": { + "description": "Deployment enables declarative updates for Pods and ReplicaSets.", + "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/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/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "description": "Standard object metadata.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" + }, + "spec": { + "description": "Specification of the desired behavior of the Deployment.", + "$ref": "#/definitions/io.k8s.api.apps.v1.DeploymentSpec" + }, + "status": { + "description": "Most recently observed status of the Deployment.", + "$ref": "#/definitions/io.k8s.api.apps.v1.DeploymentStatus" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "apps", + "kind": "Deployment", + "version": "v1" + } + ] + }, + "io.k8s.api.apps.v1.DeploymentCondition": { + "description": "DeploymentCondition describes the state of a deployment at a certain point.", + "required": [ + "type", + "status" + ], + "properties": { + "lastTransitionTime": { + "description": "Last time the condition transitioned from one status to another.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + }, + "lastUpdateTime": { + "description": "The last time this condition was updated.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + }, + "message": { + "description": "A human readable message indicating details about the transition.", + "type": "string" + }, + "reason": { + "description": "The reason for the condition's last transition.", + "type": "string" + }, + "status": { + "description": "Status of the condition, one of True, False, Unknown.", + "type": "string" + }, + "type": { + "description": "Type of deployment condition.", + "type": "string" + } + } + }, + "io.k8s.api.apps.v1.DeploymentList": { + "description": "DeploymentList is a list of Deployments.", + "required": [ + "items" + ], + "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/api-conventions.md#resources", + "type": "string" + }, + "items": { + "description": "Items is the list of Deployments.", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.api.apps.v1.Deployment" + } + }, + "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/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "description": "Standard list metadata.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "apps", + "kind": "DeploymentList", + "version": "v1" + } + ] + }, + "io.k8s.api.apps.v1.DeploymentSpec": { + "description": "DeploymentSpec is the specification of the desired behavior of the Deployment.", + "required": [ + "selector", + "template" + ], + "properties": { + "minReadySeconds": { + "description": "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)", + "type": "integer", + "format": "int32" + }, + "paused": { + "description": "Indicates that the deployment is paused.", + "type": "boolean" + }, + "progressDeadlineSeconds": { + "description": "The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that progress will not be estimated during the time a deployment is paused. Defaults to 600s.", + "type": "integer", + "format": "int32" + }, + "replicas": { + "description": "Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1.", + "type": "integer", + "format": "int32" + }, + "revisionHistoryLimit": { + "description": "The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.", + "type": "integer", + "format": "int32" + }, + "selector": { + "description": "Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment. It must match the pod template's labels.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" + }, + "strategy": { + "description": "The deployment strategy to use to replace existing pods with new ones.", + "$ref": "#/definitions/io.k8s.api.apps.v1.DeploymentStrategy" + }, + "template": { + "description": "Template describes the pods that will be created.", + "$ref": "#/definitions/io.k8s.api.core.v1.PodTemplateSpec" + } + } + }, + "io.k8s.api.apps.v1.DeploymentStatus": { + "description": "DeploymentStatus is the most recently observed status of the Deployment.", + "properties": { + "availableReplicas": { + "description": "Total number of available pods (ready for at least minReadySeconds) targeted by this deployment.", + "type": "integer", + "format": "int32" + }, + "collisionCount": { + "description": "Count of hash collisions for the Deployment. The Deployment controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ReplicaSet.", + "type": "integer", + "format": "int32" + }, + "conditions": { + "description": "Represents the latest available observations of a deployment's current state.", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.api.apps.v1.DeploymentCondition" + }, + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge" + }, + "observedGeneration": { + "description": "The generation observed by the deployment controller.", + "type": "integer", + "format": "int64" + }, + "readyReplicas": { + "description": "Total number of ready pods targeted by this deployment.", + "type": "integer", + "format": "int32" + }, + "replicas": { + "description": "Total number of non-terminated pods targeted by this deployment (their labels match the selector).", + "type": "integer", + "format": "int32" + }, + "unavailableReplicas": { + "description": "Total number of unavailable pods targeted by this deployment. This is the total number of pods that are still required for the deployment to have 100% available capacity. They may either be pods that are running but not yet available or pods that still have not been created.", + "type": "integer", + "format": "int32" + }, + "updatedReplicas": { + "description": "Total number of non-terminated pods targeted by this deployment that have the desired template spec.", + "type": "integer", + "format": "int32" + } + } + }, + "io.k8s.api.apps.v1.DeploymentStrategy": { + "description": "DeploymentStrategy describes how to replace existing pods with new ones.", + "properties": { + "rollingUpdate": { + "description": "Rolling update config params. Present only if DeploymentStrategyType = RollingUpdate.", + "$ref": "#/definitions/io.k8s.api.apps.v1.RollingUpdateDeployment" + }, + "type": { + "description": "Type of deployment. Can be \"Recreate\" or \"RollingUpdate\". Default is RollingUpdate.", + "type": "string" + } + } + }, + "io.k8s.api.apps.v1.ReplicaSet": { + "description": "ReplicaSet ensures that a specified number of pod replicas are running at any given time.", + "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/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/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "description": "If the Labels of a ReplicaSet are empty, they are defaulted to be the same as the Pod(s) that the ReplicaSet manages. Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" + }, + "spec": { + "description": "Spec defines the specification of the desired behavior of the ReplicaSet. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status", + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSetSpec" + }, + "status": { + "description": "Status is the most recently observed status of the ReplicaSet. This data may be out of date by some window of time. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status", + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSetStatus" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "apps", + "kind": "ReplicaSet", + "version": "v1" + } + ] + }, + "io.k8s.api.apps.v1.ReplicaSetCondition": { + "description": "ReplicaSetCondition describes the state of a replica set at a certain point.", + "required": [ + "type", + "status" + ], + "properties": { + "lastTransitionTime": { + "description": "The last time the condition transitioned from one status to another.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + }, + "message": { + "description": "A human readable message indicating details about the transition.", + "type": "string" + }, + "reason": { + "description": "The reason for the condition's last transition.", + "type": "string" + }, + "status": { + "description": "Status of the condition, one of True, False, Unknown.", + "type": "string" + }, + "type": { + "description": "Type of replica set condition.", + "type": "string" + } + } + }, + "io.k8s.api.apps.v1.ReplicaSetList": { + "description": "ReplicaSetList is a collection of ReplicaSets.", + "required": [ + "items" + ], + "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/api-conventions.md#resources", + "type": "string" + }, + "items": { + "description": "List of ReplicaSets. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSet" + } + }, + "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/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "apps", + "kind": "ReplicaSetList", + "version": "v1" + } + ] + }, + "io.k8s.api.apps.v1.ReplicaSetSpec": { + "description": "ReplicaSetSpec is the specification of a ReplicaSet.", + "required": [ + "selector" + ], + "properties": { + "minReadySeconds": { + "description": "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)", + "type": "integer", + "format": "int32" + }, + "replicas": { + "description": "Replicas is the number of desired replicas. This is a pointer to distinguish between explicit zero and unspecified. Defaults to 1. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller", + "type": "integer", + "format": "int32" + }, + "selector": { + "description": "Selector is a label query over pods that should match the replica count. Label keys and values that must match in order to be controlled by this replica set. It must match the pod template's labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" + }, + "template": { + "description": "Template is the object that describes the pod that will be created if insufficient replicas are detected. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template", + "$ref": "#/definitions/io.k8s.api.core.v1.PodTemplateSpec" + } + } + }, + "io.k8s.api.apps.v1.ReplicaSetStatus": { + "description": "ReplicaSetStatus represents the current status of a ReplicaSet.", + "required": [ + "replicas" + ], + "properties": { + "availableReplicas": { + "description": "The number of available replicas (ready for at least minReadySeconds) for this replica set.", + "type": "integer", + "format": "int32" + }, + "conditions": { + "description": "Represents the latest available observations of a replica set's current state.", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.api.apps.v1.ReplicaSetCondition" + }, + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge" + }, + "fullyLabeledReplicas": { + "description": "The number of pods that have labels matching the labels of the pod template of the replicaset.", + "type": "integer", + "format": "int32" + }, + "observedGeneration": { + "description": "ObservedGeneration reflects the generation of the most recently observed ReplicaSet.", + "type": "integer", + "format": "int64" + }, + "readyReplicas": { + "description": "The number of ready replicas for this replica set.", + "type": "integer", + "format": "int32" + }, + "replicas": { + "description": "Replicas is the most recently oberved number of replicas. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller", + "type": "integer", + "format": "int32" + } + } + }, "io.k8s.api.apps.v1.RollingUpdateDaemonSet": { "description": "Spec to control the desired behavior of daemon set rolling update.", "properties": { @@ -62707,6 +70497,236 @@ } } }, + "io.k8s.api.apps.v1.RollingUpdateDeployment": { + "description": "Spec to control the desired behavior of rolling update.", + "properties": { + "maxSurge": { + "description": "The maximum number of pods that can be scheduled above the desired number of pods. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up. Defaults to 25%. Example: when this is set to 30%, the new RC can be scaled up immediately when the rolling update starts, such that the total number of old and new pods do not exceed 130% of desired pods. Once old pods have been killed, new RC can be scaled up further, ensuring that total number of pods running at any time during the update is at most 130% of desired pods.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString" + }, + "maxUnavailable": { + "description": "The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding down. This can not be 0 if MaxSurge is 0. Defaults to 25%. Example: when this is set to 30%, the old RC can be scaled down to 70% of desired pods immediately when the rolling update starts. Once new pods are ready, old RC can be scaled down further, followed by scaling up the new RC, ensuring that the total number of pods available at all times during the update is at least 70% of desired pods.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString" + } + } + }, + "io.k8s.api.apps.v1.RollingUpdateStatefulSetStrategy": { + "description": "RollingUpdateStatefulSetStrategy is used to communicate parameter for RollingUpdateStatefulSetStrategyType.", + "properties": { + "partition": { + "description": "Partition indicates the ordinal at which the StatefulSet should be partitioned. Default value is 0.", + "type": "integer", + "format": "int32" + } + } + }, + "io.k8s.api.apps.v1.StatefulSet": { + "description": "StatefulSet represents a set of pods with consistent identities. Identities are defined as:\n - Network: A single stable DNS and hostname.\n - Storage: As many VolumeClaims as requested.\nThe StatefulSet guarantees that a given network identity will always map to the same storage identity.", + "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/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/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" + }, + "spec": { + "description": "Spec defines the desired identities of pods in this set.", + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSetSpec" + }, + "status": { + "description": "Status is the current status of Pods in this StatefulSet. This data may be out of date by some window of time.", + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSetStatus" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "apps", + "kind": "StatefulSet", + "version": "v1" + } + ] + }, + "io.k8s.api.apps.v1.StatefulSetCondition": { + "description": "StatefulSetCondition describes the state of a statefulset at a certain point.", + "required": [ + "type", + "status" + ], + "properties": { + "lastTransitionTime": { + "description": "Last time the condition transitioned from one status to another.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + }, + "message": { + "description": "A human readable message indicating details about the transition.", + "type": "string" + }, + "reason": { + "description": "The reason for the condition's last transition.", + "type": "string" + }, + "status": { + "description": "Status of the condition, one of True, False, Unknown.", + "type": "string" + }, + "type": { + "description": "Type of statefulset condition.", + "type": "string" + } + } + }, + "io.k8s.api.apps.v1.StatefulSetList": { + "description": "StatefulSetList is a collection of StatefulSets.", + "required": [ + "items" + ], + "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/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSet" + } + }, + "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/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "apps", + "kind": "StatefulSetList", + "version": "v1" + } + ] + }, + "io.k8s.api.apps.v1.StatefulSetSpec": { + "description": "A StatefulSetSpec is the specification of a StatefulSet.", + "required": [ + "selector", + "template", + "serviceName" + ], + "properties": { + "podManagementPolicy": { + "description": "podManagementPolicy controls how pods are created during initial scale up, when replacing pods on nodes, or when scaling down. The default policy is `OrderedReady`, where pods are created in increasing order (pod-0, then pod-1, etc) and the controller will wait until each pod is ready before continuing. When scaling down, the pods are removed in the opposite order. The alternative policy is `Parallel` which will create pods in parallel to match the desired scale without waiting, and on scale down will delete all pods at once.", + "type": "string" + }, + "replicas": { + "description": "replicas is the desired number of replicas of the given Template. These are replicas in the sense that they are instantiations of the same Template, but individual replicas also have a consistent identity. If unspecified, defaults to 1.", + "type": "integer", + "format": "int32" + }, + "revisionHistoryLimit": { + "description": "revisionHistoryLimit is the maximum number of revisions that will be maintained in the StatefulSet's revision history. The revision history consists of all revisions not represented by a currently applied StatefulSetSpec version. The default value is 10.", + "type": "integer", + "format": "int32" + }, + "selector": { + "description": "selector is a label query over pods that should match the replica count. It must match the pod template's labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" + }, + "serviceName": { + "description": "serviceName is the name of the service that governs this StatefulSet. This service must exist before the StatefulSet, and is responsible for the network identity of the set. Pods get DNS/hostnames that follow the pattern: pod-specific-string.serviceName.default.svc.cluster.local where \"pod-specific-string\" is managed by the StatefulSet controller.", + "type": "string" + }, + "template": { + "description": "template is the object that describes the pod that will be created if insufficient replicas are detected. Each pod stamped out by the StatefulSet will fulfill this Template, but have a unique identity from the rest of the StatefulSet.", + "$ref": "#/definitions/io.k8s.api.core.v1.PodTemplateSpec" + }, + "updateStrategy": { + "description": "updateStrategy indicates the StatefulSetUpdateStrategy that will be employed to update Pods in the StatefulSet when a revision is made to Template.", + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSetUpdateStrategy" + }, + "volumeClaimTemplates": { + "description": "volumeClaimTemplates is a list of claims that pods are allowed to reference. The StatefulSet controller is responsible for mapping network identities to claims in a way that maintains the identity of a pod. Every claim in this list must have at least one matching (by name) volumeMount in one container in the template. A claim in this list takes precedence over any volumes in the template, with the same name.", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.PersistentVolumeClaim" + } + } + } + }, + "io.k8s.api.apps.v1.StatefulSetStatus": { + "description": "StatefulSetStatus represents the current state of a StatefulSet.", + "required": [ + "replicas" + ], + "properties": { + "collisionCount": { + "description": "collisionCount is the count of hash collisions for the StatefulSet. The StatefulSet controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ControllerRevision.", + "type": "integer", + "format": "int32" + }, + "conditions": { + "description": "Represents the latest available observations of a statefulset's current state.", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.api.apps.v1.StatefulSetCondition" + }, + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge" + }, + "currentReplicas": { + "description": "currentReplicas is the number of Pods created by the StatefulSet controller from the StatefulSet version indicated by currentRevision.", + "type": "integer", + "format": "int32" + }, + "currentRevision": { + "description": "currentRevision, if not empty, indicates the version of the StatefulSet used to generate Pods in the sequence [0,currentReplicas).", + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration is the most recent generation observed for this StatefulSet. It corresponds to the StatefulSet's generation, which is updated on mutation by the API Server.", + "type": "integer", + "format": "int64" + }, + "readyReplicas": { + "description": "readyReplicas is the number of Pods created by the StatefulSet controller that have a Ready Condition.", + "type": "integer", + "format": "int32" + }, + "replicas": { + "description": "replicas is the number of Pods created by the StatefulSet controller.", + "type": "integer", + "format": "int32" + }, + "updateRevision": { + "description": "updateRevision, if not empty, indicates the version of the StatefulSet used to generate Pods in the sequence [replicas-updatedReplicas,replicas)", + "type": "string" + }, + "updatedReplicas": { + "description": "updatedReplicas is the number of Pods created by the StatefulSet controller from the StatefulSet version indicated by updateRevision.", + "type": "integer", + "format": "int32" + } + } + }, + "io.k8s.api.apps.v1.StatefulSetUpdateStrategy": { + "description": "StatefulSetUpdateStrategy indicates the strategy that the StatefulSet controller will use to perform updates. It includes any additional parameters necessary to perform the update for the indicated strategy.", + "properties": { + "rollingUpdate": { + "description": "RollingUpdate is used to communicate parameters when Type is RollingUpdateStatefulSetStrategyType.", + "$ref": "#/definitions/io.k8s.api.apps.v1.RollingUpdateStatefulSetStrategy" + }, + "type": { + "description": "Type indicates the type of the StatefulSetUpdateStrategy. Default is RollingUpdate.", + "type": "string" + } + } + }, "io.k8s.api.apps.v1beta1.ControllerRevision": { "description": "DEPRECATED - This group version of ControllerRevision is deprecated by apps/v1beta2/ControllerRevision. See the release notes for more information. ControllerRevision implements an immutable snapshot of state data. Clients are responsible for serializing and deserializing the objects that contain their internal state. Once a ControllerRevision has been successfully created, it can not be updated. The API Server will fail validation of all requests that attempt to mutate the Data field. ControllerRevisions may, however, be deleted. Note that, due to its use by both the DaemonSet and StatefulSet controllers for update and rollback, this object is beta. However, it may be subject to name and representation changes in future releases, and clients should not depend on its stability. It is primarily for internal use by controllers.", "required": [ @@ -63155,6 +71175,35 @@ } ] }, + "io.k8s.api.apps.v1beta1.StatefulSetCondition": { + "description": "StatefulSetCondition describes the state of a statefulset at a certain point.", + "required": [ + "type", + "status" + ], + "properties": { + "lastTransitionTime": { + "description": "Last time the condition transitioned from one status to another.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + }, + "message": { + "description": "A human readable message indicating details about the transition.", + "type": "string" + }, + "reason": { + "description": "The reason for the condition's last transition.", + "type": "string" + }, + "status": { + "description": "Status of the condition, one of True, False, Unknown.", + "type": "string" + }, + "type": { + "description": "Type of statefulset condition.", + "type": "string" + } + } + }, "io.k8s.api.apps.v1beta1.StatefulSetList": { "description": "StatefulSetList is a collection of StatefulSets.", "required": [ @@ -63244,6 +71293,15 @@ "type": "integer", "format": "int32" }, + "conditions": { + "description": "Represents the latest available observations of a statefulset's current state.", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.api.apps.v1beta1.StatefulSetCondition" + }, + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge" + }, "currentReplicas": { "description": "currentReplicas is the number of Pods created by the StatefulSet controller from the StatefulSet version indicated by currentRevision.", "type": "integer", @@ -63293,7 +71351,7 @@ } }, "io.k8s.api.apps.v1beta2.ControllerRevision": { - "description": "ControllerRevision implements an immutable snapshot of state data. Clients are responsible for serializing and deserializing the objects that contain their internal state. Once a ControllerRevision has been successfully created, it can not be updated. The API Server will fail validation of all requests that attempt to mutate the Data field. ControllerRevisions may, however, be deleted. Note that, due to its use by both the DaemonSet and StatefulSet controllers for update and rollback, this object is beta. However, it may be subject to name and representation changes in future releases, and clients should not depend on its stability. It is primarily for internal use by controllers.", + "description": "DEPRECATED - This group version of ControllerRevision is deprecated by apps/v1/ControllerRevision. See the release notes for more information. ControllerRevision implements an immutable snapshot of state data. Clients are responsible for serializing and deserializing the objects that contain their internal state. Once a ControllerRevision has been successfully created, it can not be updated. The API Server will fail validation of all requests that attempt to mutate the Data field. ControllerRevisions may, however, be deleted. Note that, due to its use by both the DaemonSet and StatefulSet controllers for update and rollback, this object is beta. However, it may be subject to name and representation changes in future releases, and clients should not depend on its stability. It is primarily for internal use by controllers.", "required": [ "revision" ], @@ -63363,7 +71421,7 @@ ] }, "io.k8s.api.apps.v1beta2.DaemonSet": { - "description": "DaemonSet represents the configuration of a daemon set.", + "description": "DEPRECATED - This group version of DaemonSet is deprecated by apps/v1/DaemonSet. See the release notes for more information. DaemonSet represents the configuration of a daemon set.", "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/api-conventions.md#resources", @@ -63394,6 +71452,35 @@ } ] }, + "io.k8s.api.apps.v1beta2.DaemonSetCondition": { + "description": "DaemonSetCondition describes the state of a DaemonSet at a certain point.", + "required": [ + "type", + "status" + ], + "properties": { + "lastTransitionTime": { + "description": "Last time the condition transitioned from one status to another.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + }, + "message": { + "description": "A human readable message indicating details about the transition.", + "type": "string" + }, + "reason": { + "description": "The reason for the condition's last transition.", + "type": "string" + }, + "status": { + "description": "Status of the condition, one of True, False, Unknown.", + "type": "string" + }, + "type": { + "description": "Type of DaemonSet condition.", + "type": "string" + } + } + }, "io.k8s.api.apps.v1beta2.DaemonSetList": { "description": "DaemonSetList is a collection of daemon sets.", "required": [ @@ -63431,6 +71518,7 @@ "io.k8s.api.apps.v1beta2.DaemonSetSpec": { "description": "DaemonSetSpec is the specification of a daemon set.", "required": [ + "selector", "template" ], "properties": { @@ -63445,7 +71533,7 @@ "format": "int32" }, "selector": { - "description": "A label query over pods that are managed by the daemon set. Must match in order to be controlled. If empty, defaulted to labels on Pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", + "description": "A label query over pods that are managed by the daemon set. Must match in order to be controlled. It must match the pod template's labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" }, "template": { @@ -63472,6 +71560,15 @@ "type": "integer", "format": "int32" }, + "conditions": { + "description": "Represents the latest available observations of a DaemonSet's current state.", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.api.apps.v1beta2.DaemonSetCondition" + }, + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge" + }, "currentNumberScheduled": { "description": "The number of nodes that are running at least 1 daemon pod and are supposed to run the daemon pod. More info: https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/", "type": "integer", @@ -63528,7 +71625,7 @@ } }, "io.k8s.api.apps.v1beta2.Deployment": { - "description": "Deployment enables declarative updates for Pods and ReplicaSets.", + "description": "DEPRECATED - This group version of Deployment is deprecated by apps/v1/Deployment. See the release notes for more information. Deployment enables declarative updates for Pods and ReplicaSets.", "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/api-conventions.md#resources", @@ -63629,6 +71726,7 @@ "io.k8s.api.apps.v1beta2.DeploymentSpec": { "description": "DeploymentSpec is the specification of the desired behavior of the Deployment.", "required": [ + "selector", "template" ], "properties": { @@ -63657,7 +71755,7 @@ "format": "int32" }, "selector": { - "description": "Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment.", + "description": "Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment. It must match the pod template's labels.", "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" }, "strategy": { @@ -63733,7 +71831,7 @@ } }, "io.k8s.api.apps.v1beta2.ReplicaSet": { - "description": "ReplicaSet ensures that a specified number of pod replicas are running at any given time.", + "description": "DEPRECATED - This group version of ReplicaSet is deprecated by apps/v1/ReplicaSet. See the release notes for more information. ReplicaSet ensures that a specified number of pod replicas are running at any given time.", "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/api-conventions.md#resources", @@ -63829,6 +71927,9 @@ }, "io.k8s.api.apps.v1beta2.ReplicaSetSpec": { "description": "ReplicaSetSpec is the specification of a ReplicaSet.", + "required": [ + "selector" + ], "properties": { "minReadySeconds": { "description": "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)", @@ -63841,7 +71942,7 @@ "format": "int32" }, "selector": { - "description": "Selector is a label query over pods that should match the replica count. If the selector is empty, it is defaulted to the labels present on the pod template. Label keys and values that must match in order to be controlled by this replica set. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", + "description": "Selector is a label query over pods that should match the replica count. Label keys and values that must match in order to be controlled by this replica set. It must match the pod template's labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" }, "template": { @@ -63991,7 +72092,7 @@ } }, "io.k8s.api.apps.v1beta2.StatefulSet": { - "description": "StatefulSet represents a set of pods with consistent identities. Identities are defined as:\n - Network: A single stable DNS and hostname.\n - Storage: As many VolumeClaims as requested.\nThe StatefulSet guarantees that a given network identity will always map to the same storage identity.", + "description": "DEPRECATED - This group version of StatefulSet is deprecated by apps/v1/StatefulSet. See the release notes for more information. StatefulSet represents a set of pods with consistent identities. Identities are defined as:\n - Network: A single stable DNS and hostname.\n - Storage: As many VolumeClaims as requested.\nThe StatefulSet guarantees that a given network identity will always map to the same storage identity.", "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/api-conventions.md#resources", @@ -64021,6 +72122,35 @@ } ] }, + "io.k8s.api.apps.v1beta2.StatefulSetCondition": { + "description": "StatefulSetCondition describes the state of a statefulset at a certain point.", + "required": [ + "type", + "status" + ], + "properties": { + "lastTransitionTime": { + "description": "Last time the condition transitioned from one status to another.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + }, + "message": { + "description": "A human readable message indicating details about the transition.", + "type": "string" + }, + "reason": { + "description": "The reason for the condition's last transition.", + "type": "string" + }, + "status": { + "description": "Status of the condition, one of True, False, Unknown.", + "type": "string" + }, + "type": { + "description": "Type of statefulset condition.", + "type": "string" + } + } + }, "io.k8s.api.apps.v1beta2.StatefulSetList": { "description": "StatefulSetList is a collection of StatefulSets.", "required": [ @@ -64056,6 +72186,7 @@ "io.k8s.api.apps.v1beta2.StatefulSetSpec": { "description": "A StatefulSetSpec is the specification of a StatefulSet.", "required": [ + "selector", "template", "serviceName" ], @@ -64075,7 +72206,7 @@ "format": "int32" }, "selector": { - "description": "selector is a label query over pods that should match the replica count. If empty, defaulted to labels on the pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", + "description": "selector is a label query over pods that should match the replica count. It must match the pod template's labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" }, "serviceName": { @@ -64110,6 +72241,15 @@ "type": "integer", "format": "int32" }, + "conditions": { + "description": "Represents the latest available observations of a statefulset's current state.", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.api.apps.v1beta2.StatefulSetCondition" + }, + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge" + }, "currentReplicas": { "description": "currentReplicas is the number of Pods created by the StatefulSet controller from the StatefulSet version indicated by currentRevision.", "type": "integer", @@ -64461,7 +72601,7 @@ } }, "resources": { - "description": "Resources is a list of resources this rule applies to. ResourceAll represents all resources. \"*\" means all.", + "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": "array", "items": { "type": "string" @@ -64644,7 +72784,11 @@ ], "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.", "type": "boolean" }, "evaluationError": { @@ -64812,7 +72956,7 @@ } }, "resources": { - "description": "Resources is a list of resources this rule applies to. ResourceAll represents all resources. \"*\" means all.", + "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": "array", "items": { "type": "string" @@ -64995,7 +73139,11 @@ ], "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.", "type": "boolean" }, "evaluationError": { @@ -65456,7 +73604,7 @@ "$ref": "#/definitions/io.k8s.api.autoscaling.v2beta1.ResourceMetricSource" }, "type": { - "description": "type is the type of metric source. It should match one of the fields below.", + "description": "type is the type of metric source. It should be one of \"Object\", \"Pods\" or \"Resource\", each mapping to a matching field in the object.", "type": "string" } } @@ -65480,7 +73628,7 @@ "$ref": "#/definitions/io.k8s.api.autoscaling.v2beta1.ResourceMetricStatus" }, "type": { - "description": "type is the type of metric source. It will match one of the fields below.", + "description": "type is the type of metric source. It will be one of \"Object\", \"Pods\" or \"Resource\", each corresponds to a matching field in the object.", "type": "string" } } @@ -65727,7 +73875,7 @@ "format": "int32" }, "manualSelector": { - "description": "manualSelector controls generation of pod labels and pod selectors. Leave `manualSelector` unset unless you are certain what you are doing. When false or unset, the system pick labels unique to this job and appends those labels to the pod template. When true, the user is responsible for picking unique labels and specifying the selector. Failure to pick a unique label may cause this and other jobs to not function correctly. However, You may see `manualSelector=true` in jobs that were created with the old `extensions/v1beta1` API. More info: https://git.k8s.io/community/contributors/design-proposals/selector-generation.md", + "description": "manualSelector controls generation of pod labels and pod selectors. Leave `manualSelector` unset unless you are certain what you are doing. When false or unset, the system pick labels unique to this job and appends those labels to the pod template. When true, the user is responsible for picking unique labels and specifying the selector. Failure to pick a unique label may cause this and other jobs to not function correctly. However, You may see `manualSelector=true` in jobs that were created with the old `extensions/v1beta1` API. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/#specifying-your-own-pod-selector", "type": "boolean" }, "parallelism": { @@ -65856,7 +74004,7 @@ ], "properties": { "concurrencyPolicy": { - "description": "Specifies how to treat concurrent executions of a Job. Defaults to Allow.", + "description": "Specifies how to treat concurrent executions of a Job. Valid values are: - \"Allow\" (default): allows CronJobs to run concurrently; - \"Forbid\": forbids concurrent runs, skipping next run if previous run hasn't finished yet; - \"Replace\": cancels currently running job and replaces it with a new one", "type": "string" }, "failedJobsHistoryLimit": { @@ -65991,7 +74139,7 @@ ], "properties": { "concurrencyPolicy": { - "description": "Specifies how to treat concurrent executions of a Job. Defaults to Allow.", + "description": "Specifies how to treat concurrent executions of a Job. Valid values are: - \"Allow\" (default): allows CronJobs to run concurrently; - \"Forbid\": forbids concurrent runs, skipping next run if previous run hasn't finished yet; - \"Replace\": cancels currently running job and replaces it with a new one", "type": "string" }, "failedJobsHistoryLimit": { @@ -66367,6 +74515,27 @@ } ] }, + "io.k8s.api.core.v1.CSIPersistentVolumeSource": { + "description": "Represents storage that is managed by an external CSI volume driver", + "required": [ + "driver", + "volumeHandle" + ], + "properties": { + "driver": { + "description": "Driver is the name of the driver to use for this volume. Required.", + "type": "string" + }, + "readOnly": { + "description": "Optional: The value to pass to ControllerPublishVolumeRequest. Defaults to false (read/write).", + "type": "boolean" + }, + "volumeHandle": { + "description": "VolumeHandle is the unique volume name returned by the CSI volume plugin’s CreateVolume to refer to the volume on all subsequent calls. Required.", + "type": "string" + } + } + }, "io.k8s.api.core.v1.Capabilities": { "description": "Adds and removes POSIX capabilities from running containers.", "properties": { @@ -66795,7 +74964,7 @@ "$ref": "#/definitions/io.k8s.api.core.v1.ResourceRequirements" }, "securityContext": { - "description": "Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md", + "description": "Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/", "$ref": "#/definitions/io.k8s.api.core.v1.SecurityContext" }, "stdin": { @@ -66818,6 +74987,15 @@ "description": "Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false.", "type": "boolean" }, + "volumeDevices": { + "description": "volumeDevices is the list of block devices to be used by the container. This is an alpha feature and may change in the future.", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.VolumeDevice" + }, + "x-kubernetes-patch-merge-key": "devicePath", + "x-kubernetes-patch-strategy": "merge" + }, "volumeMounts": { "description": "Pod volumes to mount into the container's filesystem. Cannot be updated.", "type": "array", @@ -67289,6 +75467,10 @@ "involvedObject" ], "properties": { + "action": { + "description": "What action was taken/failed regarding to the Regarding object.", + "type": "string" + }, "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/api-conventions.md#resources", "type": "string" @@ -67298,6 +75480,10 @@ "type": "integer", "format": "int32" }, + "eventTime": { + "description": "Time when this Event was first observed.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.MicroTime" + }, "firstTimestamp": { "description": "The time at which the event was first recorded. (Time of server receipt is in TypeMeta.)", "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" @@ -67326,6 +75512,22 @@ "description": "This should be a short, machine understandable string that gives the reason for the transition into the object's current status.", "type": "string" }, + "related": { + "description": "Optional secondary object for more complex actions.", + "$ref": "#/definitions/io.k8s.api.core.v1.ObjectReference" + }, + "reportingComponent": { + "description": "Name of the controller that emitted this Event, e.g. `kubernetes.io/kubelet`.", + "type": "string" + }, + "reportingInstance": { + "description": "ID of the controller instance, e.g. `kubelet-xyzf`.", + "type": "string" + }, + "series": { + "description": "Data about the Event series this event represents or nil if it's a singleton Event.", + "$ref": "#/definitions/io.k8s.api.core.v1.EventSeries" + }, "source": { "description": "The component reporting this event. Should be a short machine understandable string.", "$ref": "#/definitions/io.k8s.api.core.v1.EventSource" @@ -67377,6 +75579,24 @@ } ] }, + "io.k8s.api.core.v1.EventSeries": { + "description": "EventSeries contain information on series of events, i.e. thing that was/is happening continously for some time.", + "properties": { + "count": { + "description": "Number of occurrences in this series up to the last heartbeat time", + "type": "integer", + "format": "int32" + }, + "lastObservedTime": { + "description": "Time of the last occurence observed", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.MicroTime" + }, + "state": { + "description": "State of this Series: Ongoing or Finished", + "type": "string" + } + } + }, "io.k8s.api.core.v1.EventSource": { "description": "EventSource contains information for an event.", "properties": { @@ -67434,8 +75654,39 @@ } } }, + "io.k8s.api.core.v1.FlexPersistentVolumeSource": { + "description": "FlexPersistentVolumeSource represents a generic persistent volume resource that is provisioned/attached using an exec based plugin.", + "required": [ + "driver" + ], + "properties": { + "driver": { + "description": "Driver is the name of the driver to use for this volume.", + "type": "string" + }, + "fsType": { + "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". The default filesystem depends on FlexVolume script.", + "type": "string" + }, + "options": { + "description": "Optional: Extra command options if any.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "readOnly": { + "description": "Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", + "type": "boolean" + }, + "secretRef": { + "description": "Optional: SecretRef is reference to the secret object containing sensitive information to pass to the plugin scripts. This may be empty if no secret object is specified. If the secret object contains more than one secret, all secrets are passed to the plugin scripts.", + "$ref": "#/definitions/io.k8s.api.core.v1.SecretReference" + } + } + }, "io.k8s.api.core.v1.FlexVolumeSource": { - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.", + "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.", "required": [ "driver" ], @@ -67641,6 +75892,64 @@ } } }, + "io.k8s.api.core.v1.ISCSIPersistentVolumeSource": { + "description": "ISCSIPersistentVolumeSource represents an ISCSI disk. ISCSI volumes can only be mounted as read/write once. ISCSI volumes support ownership management and SELinux relabeling.", + "required": [ + "targetPortal", + "iqn", + "lun" + ], + "properties": { + "chapAuthDiscovery": { + "description": "whether support iSCSI Discovery CHAP authentication", + "type": "boolean" + }, + "chapAuthSession": { + "description": "whether support iSCSI Session CHAP authentication", + "type": "boolean" + }, + "fsType": { + "description": "Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi", + "type": "string" + }, + "initiatorName": { + "description": "Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface \u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection.", + "type": "string" + }, + "iqn": { + "description": "Target iSCSI Qualified Name.", + "type": "string" + }, + "iscsiInterface": { + "description": "iSCSI Interface Name that uses an iSCSI transport. Defaults to 'default' (tcp).", + "type": "string" + }, + "lun": { + "description": "iSCSI Target Lun number.", + "type": "integer", + "format": "int32" + }, + "portals": { + "description": "iSCSI Target Portal List. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).", + "type": "array", + "items": { + "type": "string" + } + }, + "readOnly": { + "description": "ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false.", + "type": "boolean" + }, + "secretRef": { + "description": "CHAP Secret for iSCSI target and initiator authentication", + "$ref": "#/definitions/io.k8s.api.core.v1.SecretReference" + }, + "targetPortal": { + "description": "iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).", + "type": "string" + } + } + }, "io.k8s.api.core.v1.ISCSIVolumeSource": { "description": "Represents an ISCSI disk. ISCSI volumes can only be mounted as read/write once. ISCSI volumes support ownership management and SELinux relabeling.", "required": [ @@ -67662,7 +75971,7 @@ "type": "string" }, "initiatorName": { - "description": "Custom iSCSI initiator name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface \u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection.", + "description": "Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface \u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection.", "type": "string" }, "iqn": { @@ -67670,16 +75979,16 @@ "type": "string" }, "iscsiInterface": { - "description": "Optional: Defaults to 'default' (tcp). iSCSI interface name that uses an iSCSI transport.", + "description": "iSCSI Interface Name that uses an iSCSI transport. Defaults to 'default' (tcp).", "type": "string" }, "lun": { - "description": "iSCSI target lun number.", + "description": "iSCSI Target Lun number.", "type": "integer", "format": "int32" }, "portals": { - "description": "iSCSI target portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).", + "description": "iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).", "type": "array", "items": { "type": "string" @@ -67690,11 +75999,11 @@ "type": "boolean" }, "secretRef": { - "description": "CHAP secret for iSCSI target and initiator authentication", + "description": "CHAP Secret for iSCSI target and initiator authentication", "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference" }, "targetPortal": { - "description": "iSCSI target portal. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).", + "description": "iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).", "type": "string" } } @@ -67817,7 +76126,7 @@ "type": "string" }, "items": { - "description": "Items is a list of LimitRange objects. More info: https://git.k8s.io/community/contributors/design-proposals/admission_control_limit_range.md", + "description": "Items is a list of LimitRange objects. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/", "type": "array", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.LimitRange" @@ -67992,7 +76301,7 @@ "description": "NamespaceSpec describes the attributes on a Namespace.", "properties": { "finalizers": { - "description": "Finalizers is an opaque list of values that must be empty to permanently remove object from storage. More info: https://git.k8s.io/community/contributors/design-proposals/namespaces.md#finalizers", + "description": "Finalizers is an opaque list of values that must be empty to permanently remove object from storage. More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/", "type": "array", "items": { "type": "string" @@ -68004,7 +76313,7 @@ "description": "NamespaceStatus is information about the current status of a Namespace.", "properties": { "phase": { - "description": "Phase is the current lifecycle phase of the namespace. More info: https://git.k8s.io/community/contributors/design-proposals/namespaces.md#phases", + "description": "Phase is the current lifecycle phase of the namespace. More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/", "type": "string" } } @@ -68586,6 +76895,10 @@ "description": "Name of the StorageClass required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1", "type": "string" }, + "volumeMode": { + "description": "volumeMode defines what type of volume is required by the claim. Value of Filesystem is implied when not included in claim spec. This is an alpha feature and may change in the future.", + "type": "string" + }, "volumeName": { "description": "VolumeName is the binding reference to the PersistentVolume backing this claim.", "type": "string" @@ -68715,13 +77028,17 @@ "description": "ClaimRef is part of a bi-directional binding between PersistentVolume and PersistentVolumeClaim. Expected to be non-nil when bound. claim.VolumeName is the authoritative bind between PV and PVC. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#binding", "$ref": "#/definitions/io.k8s.api.core.v1.ObjectReference" }, + "csi": { + "description": "CSI represents storage that handled by an external CSI driver", + "$ref": "#/definitions/io.k8s.api.core.v1.CSIPersistentVolumeSource" + }, "fc": { "description": "FC represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod.", "$ref": "#/definitions/io.k8s.api.core.v1.FCVolumeSource" }, "flexVolume": { - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.", - "$ref": "#/definitions/io.k8s.api.core.v1.FlexVolumeSource" + "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.", + "$ref": "#/definitions/io.k8s.api.core.v1.FlexPersistentVolumeSource" }, "flocker": { "description": "Flocker represents a Flocker volume attached to a kubelet's host machine and exposed to the pod for its usage. This depends on the Flocker control service being running", @@ -68741,7 +77058,7 @@ }, "iscsi": { "description": "ISCSI represents an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod. Provisioned by an admin.", - "$ref": "#/definitions/io.k8s.api.core.v1.ISCSIVolumeSource" + "$ref": "#/definitions/io.k8s.api.core.v1.ISCSIPersistentVolumeSource" }, "local": { "description": "Local represents directly-attached storage with node affinity", @@ -68776,11 +77093,11 @@ }, "rbd": { "description": "RBD represents a Rados Block Device mount on the host that shares a pod's lifetime. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md", - "$ref": "#/definitions/io.k8s.api.core.v1.RBDVolumeSource" + "$ref": "#/definitions/io.k8s.api.core.v1.RBDPersistentVolumeSource" }, "scaleIO": { "description": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.", - "$ref": "#/definitions/io.k8s.api.core.v1.ScaleIOVolumeSource" + "$ref": "#/definitions/io.k8s.api.core.v1.ScaleIOPersistentVolumeSource" }, "storageClassName": { "description": "Name of StorageClass to which this persistent volume belongs. Empty value means that this volume does not belong to any StorageClass.", @@ -68790,6 +77107,10 @@ "description": "StorageOS represents a StorageOS volume that is attached to the kubelet's host machine and mounted into the pod More info: https://releases.k8s.io/HEAD/examples/volumes/storageos/README.md", "$ref": "#/definitions/io.k8s.api.core.v1.StorageOSPersistentVolumeSource" }, + "volumeMode": { + "description": "volumeMode defines if a volume is intended to be used with a formatted filesystem or to remain in raw block state. Value of Filesystem is implied when not included in spec. This is an alpha feature and may change in the future.", + "type": "string" + }, "vsphereVolume": { "description": "VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine", "$ref": "#/definitions/io.k8s.api.core.v1.VsphereVirtualDiskVolumeSource" @@ -68881,7 +77202,10 @@ } }, "io.k8s.api.core.v1.PodAffinityTerm": { - "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key \u003ctopologyKey\u003e tches that of any node on which a pod of the set of pods is running", + "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key \u003ctopologyKey\u003e matches that of any node on which a pod of the set of pods is running", + "required": [ + "topologyKey" + ], "properties": { "labelSelector": { "description": "A label query over a set of resources, in this case pods.", @@ -68895,7 +77219,7 @@ } }, "topologyKey": { - "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. For PreferredDuringScheduling pod anti-affinity, empty topologyKey is interpreted as \"all topologies\" (\"all topologies\" here means all the topologyKeys indicated by scheduler command-line argument --failure-domains); for affinity and for RequiredDuringScheduling pod anti-affinity, empty topologyKey is not allowed.", + "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.", "type": "string" } } @@ -68952,6 +77276,44 @@ } } }, + "io.k8s.api.core.v1.PodDNSConfig": { + "description": "PodDNSConfig defines the DNS parameters of a pod in addition to those generated from DNSPolicy.", + "properties": { + "nameservers": { + "description": "A list of DNS name server IP addresses. This will be appended to the base nameservers generated from DNSPolicy. Duplicated nameservers will be removed.", + "type": "array", + "items": { + "type": "string" + } + }, + "options": { + "description": "A list of DNS resolver options. This will be merged with the base options generated from DNSPolicy. Duplicated entries will be removed. Resolution options given in Options will override those that appear in the base DNSPolicy.", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.PodDNSConfigOption" + } + }, + "searches": { + "description": "A list of DNS search domains for host-name lookup. This will be appended to the base search paths generated from DNSPolicy. Duplicated search paths will be removed.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "io.k8s.api.core.v1.PodDNSConfigOption": { + "description": "PodDNSConfigOption defines DNS resolver options of a pod.", + "properties": { + "name": { + "description": "Required.", + "type": "string" + }, + "value": { + "type": "string" + } + } + }, "io.k8s.api.core.v1.PodList": { "description": "PodList is a list of Pods.", "required": [ @@ -69045,8 +77407,12 @@ "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge" }, + "dnsConfig": { + "description": "Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy. This is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it.", + "$ref": "#/definitions/io.k8s.api.core.v1.PodDNSConfig" + }, "dnsPolicy": { - "description": "Set DNS policy for containers within the pod. One of 'ClusterFirstWithHostNet', 'ClusterFirst' or 'Default'. Defaults to \"ClusterFirst\". To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'.", + "description": "Set DNS policy for the pod. Defaults to \"ClusterFirst\". Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'. Note that 'None' policy is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it.", "type": "string" }, "hostAliases": { @@ -69419,6 +77785,50 @@ } } }, + "io.k8s.api.core.v1.RBDPersistentVolumeSource": { + "description": "Represents a Rados Block Device mount that lasts the lifetime of a pod. RBD volumes support ownership management and SELinux relabeling.", + "required": [ + "monitors", + "image" + ], + "properties": { + "fsType": { + "description": "Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd", + "type": "string" + }, + "image": { + "description": "The rados image name. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it", + "type": "string" + }, + "keyring": { + "description": "Keyring is the path to key ring for RBDUser. Default is /etc/ceph/keyring. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it", + "type": "string" + }, + "monitors": { + "description": "A collection of Ceph monitors. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it", + "type": "array", + "items": { + "type": "string" + } + }, + "pool": { + "description": "The rados pool name. Default is rbd. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it", + "type": "string" + }, + "readOnly": { + "description": "ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it", + "type": "boolean" + }, + "secretRef": { + "description": "SecretRef is name of the authentication secret for RBDUser. If provided overrides keyring. Default is nil. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it", + "$ref": "#/definitions/io.k8s.api.core.v1.SecretReference" + }, + "user": { + "description": "The rados user name. Default is admin. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it", + "type": "string" + } + } + }, "io.k8s.api.core.v1.RBDVolumeSource": { "description": "Represents a Rados Block Device mount that lasts the lifetime of a pod. RBD volumes support ownership management and SELinux relabeling.", "required": [ @@ -69689,7 +78099,7 @@ "type": "string" }, "items": { - "description": "Items is a list of ResourceQuota objects. More info: https://git.k8s.io/community/contributors/design-proposals/admission_control_resource_quota.md", + "description": "Items is a list of ResourceQuota objects. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/", "type": "array", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.ResourceQuota" @@ -69716,7 +78126,7 @@ "description": "ResourceQuotaSpec defines the desired hard limits to enforce for Quota.", "properties": { "hard": { - "description": "Hard is the set of desired hard limits for each named resource. More info: https://git.k8s.io/community/contributors/design-proposals/admission_control_resource_quota.md", + "description": "Hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/", "type": "object", "additionalProperties": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.api.resource.Quantity" @@ -69735,7 +78145,7 @@ "description": "ResourceQuotaStatus defines the enforced hard limits and observed use.", "properties": { "hard": { - "description": "Hard is the set of enforced hard limits for each named resource. More info: https://git.k8s.io/community/contributors/design-proposals/admission_control_resource_quota.md", + "description": "Hard is the set of enforced hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/", "type": "object", "additionalProperties": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.api.resource.Quantity" @@ -69790,6 +78200,56 @@ } } }, + "io.k8s.api.core.v1.ScaleIOPersistentVolumeSource": { + "description": "ScaleIOPersistentVolumeSource represents a persistent ScaleIO volume", + "required": [ + "gateway", + "system", + "secretRef" + ], + "properties": { + "fsType": { + "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.", + "type": "string" + }, + "gateway": { + "description": "The host address of the ScaleIO API Gateway.", + "type": "string" + }, + "protectionDomain": { + "description": "The name of the ScaleIO Protection Domain for the configured storage.", + "type": "string" + }, + "readOnly": { + "description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", + "type": "boolean" + }, + "secretRef": { + "description": "SecretRef references to the secret for ScaleIO user and other sensitive information. If this is not provided, Login operation will fail.", + "$ref": "#/definitions/io.k8s.api.core.v1.SecretReference" + }, + "sslEnabled": { + "description": "Flag to enable/disable SSL communication with Gateway, default false", + "type": "boolean" + }, + "storageMode": { + "description": "Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned.", + "type": "string" + }, + "storagePool": { + "description": "The ScaleIO Storage Pool associated with the protection domain.", + "type": "string" + }, + "system": { + "description": "The name of the storage system as configured in ScaleIO.", + "type": "string" + }, + "volumeName": { + "description": "The name of a volume already created in the ScaleIO system that is associated with this volume source.", + "type": "string" + } + } + }, "io.k8s.api.core.v1.ScaleIOVolumeSource": { "description": "ScaleIOVolumeSource represents a persistent ScaleIO volume", "required": [ @@ -69807,7 +78267,7 @@ "type": "string" }, "protectionDomain": { - "description": "The name of the Protection Domain for the configured storage (defaults to \"default\").", + "description": "The name of the ScaleIO Protection Domain for the configured storage.", "type": "string" }, "readOnly": { @@ -69823,11 +78283,11 @@ "type": "boolean" }, "storageMode": { - "description": "Indicates whether the storage for a volume should be thick or thin (defaults to \"thin\").", + "description": "Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned.", "type": "string" }, "storagePool": { - "description": "The Storage Pool associated with the protection domain (defaults to \"default\").", + "description": "The ScaleIO Storage Pool associated with the protection domain.", "type": "string" }, "system": { @@ -70231,7 +78691,7 @@ } }, "externalName": { - "description": "externalName is the external reference that kubedns or equivalent will return as a CNAME record for this service. No proxying will be involved. Must be a valid DNS name and requires Type to be ExternalName.", + "description": "externalName is the external reference that kubedns or equivalent will return as a CNAME record for this service. No proxying will be involved. Must be a valid RFC-1123 hostname (https://tools.ietf.org/html/rfc1123) and requires Type to be ExternalName.", "type": "string" }, "externalTrafficPolicy": { @@ -70466,7 +78926,7 @@ "$ref": "#/definitions/io.k8s.api.core.v1.FCVolumeSource" }, "flexVolume": { - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.", + "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.", "$ref": "#/definitions/io.k8s.api.core.v1.FlexVolumeSource" }, "flocker": { @@ -70543,6 +79003,23 @@ } } }, + "io.k8s.api.core.v1.VolumeDevice": { + "description": "volumeDevice describes a mapping of a raw block device within a container.", + "required": [ + "name", + "devicePath" + ], + "properties": { + "devicePath": { + "description": "devicePath is the path inside of the container that the device will be mapped to.", + "type": "string" + }, + "name": { + "description": "name must match the name of a persistentVolumeClaim in the pod", + "type": "string" + } + } + }, "io.k8s.api.core.v1.VolumeMount": { "description": "VolumeMount describes a mounting of a Volume within a container.", "required": [ @@ -70631,6 +79108,158 @@ } } }, + "io.k8s.api.events.v1beta1.Event": { + "description": "Event is a report of an event somewhere in the cluster. It generally denotes some state change in the system.", + "required": [ + "eventTime" + ], + "properties": { + "action": { + "description": "What action was taken/failed regarding to the regarding object.", + "type": "string" + }, + "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/api-conventions.md#resources", + "type": "string" + }, + "deprecatedCount": { + "description": "Deprecated field assuring backward compatibility with core.v1 Event type", + "type": "integer", + "format": "int32" + }, + "deprecatedFirstTimestamp": { + "description": "Deprecated field assuring backward compatibility with core.v1 Event type", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + }, + "deprecatedLastTimestamp": { + "description": "Deprecated field assuring backward compatibility with core.v1 Event type", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + }, + "deprecatedSource": { + "description": "Deprecated field assuring backward compatibility with core.v1 Event type", + "$ref": "#/definitions/io.k8s.api.core.v1.EventSource" + }, + "eventTime": { + "description": "Required. Time when this Event was first observed.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.MicroTime" + }, + "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/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" + }, + "note": { + "description": "Optional. A human-readable description of the status of this operation. Maximal length of the note is 1kB, but libraries should be prepared to handle values up to 64kB.", + "type": "string" + }, + "reason": { + "description": "Why the action was taken.", + "type": "string" + }, + "regarding": { + "description": "The object this Event is about. In most cases it's an Object reporting controller implements. E.g. ReplicaSetController implements ReplicaSets and this event is emitted because it acts on some changes in a ReplicaSet object.", + "$ref": "#/definitions/io.k8s.api.core.v1.ObjectReference" + }, + "related": { + "description": "Optional secondary object for more complex actions. E.g. when regarding object triggers a creation or deletion of related object.", + "$ref": "#/definitions/io.k8s.api.core.v1.ObjectReference" + }, + "reportingController": { + "description": "Name of the controller that emitted this Event, e.g. `kubernetes.io/kubelet`.", + "type": "string" + }, + "reportingInstance": { + "description": "ID of the controller instance, e.g. `kubelet-xyzf`.", + "type": "string" + }, + "series": { + "description": "Data about the Event series this event represents or nil if it's a singleton Event.", + "$ref": "#/definitions/io.k8s.api.events.v1beta1.EventSeries" + }, + "type": { + "description": "Type of this event (Normal, Warning), new types could be added in the future.", + "type": "string" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "events.k8s.io", + "kind": "Event", + "version": "v1beta1" + } + ] + }, + "io.k8s.api.events.v1beta1.EventList": { + "description": "EventList is a list of Event objects.", + "required": [ + "items" + ], + "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/api-conventions.md#resources", + "type": "string" + }, + "items": { + "description": "Items is a list of schema objects.", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.api.events.v1beta1.Event" + } + }, + "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/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "events.k8s.io", + "kind": "EventList", + "version": "v1beta1" + } + ] + }, + "io.k8s.api.events.v1beta1.EventSeries": { + "description": "EventSeries contain information on series of events, i.e. thing that was/is happening continously for some time.", + "required": [ + "count", + "lastObservedTime", + "state" + ], + "properties": { + "count": { + "description": "Number of occurrences in this series up to the last heartbeat time", + "type": "integer", + "format": "int32" + }, + "lastObservedTime": { + "description": "Time when last Event from the series was seen before last heartbeat.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.MicroTime" + }, + "state": { + "description": "Information whether this series is ongoing or finished.", + "type": "string" + } + } + }, + "io.k8s.api.extensions.v1beta1.AllowedFlexVolume": { + "description": "AllowedFlexVolume represents a single Flexvolume that is allowed to be used.", + "required": [ + "driver" + ], + "properties": { + "driver": { + "description": "Driver is the name of the Flexvolume driver.", + "type": "string" + } + } + }, "io.k8s.api.extensions.v1beta1.AllowedHostPath": { "description": "defines the host volume conditions that will be enabled by a policy for pods to use. It requires the path prefix to be defined.", "properties": { @@ -70672,6 +79301,35 @@ } ] }, + "io.k8s.api.extensions.v1beta1.DaemonSetCondition": { + "description": "DaemonSetCondition describes the state of a DaemonSet at a certain point.", + "required": [ + "type", + "status" + ], + "properties": { + "lastTransitionTime": { + "description": "Last time the condition transitioned from one status to another.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + }, + "message": { + "description": "A human readable message indicating details about the transition.", + "type": "string" + }, + "reason": { + "description": "The reason for the condition's last transition.", + "type": "string" + }, + "status": { + "description": "Status of the condition, one of True, False, Unknown.", + "type": "string" + }, + "type": { + "description": "Type of DaemonSet condition.", + "type": "string" + } + } + }, "io.k8s.api.extensions.v1beta1.DaemonSetList": { "description": "DaemonSetList is a collection of daemon sets.", "required": [ @@ -70755,6 +79413,15 @@ "type": "integer", "format": "int32" }, + "conditions": { + "description": "Represents the latest available observations of a DaemonSet's current state.", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DaemonSetCondition" + }, + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge" + }, "currentNumberScheduled": { "description": "The number of nodes that are running at least 1 daemon pod and are supposed to run the daemon pod. More info: https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/", "type": "integer", @@ -71144,7 +79811,7 @@ } }, "io.k8s.api.extensions.v1beta1.IPBlock": { - "description": "IPBlock describes a particular CIDR (Ex. \"192.168.1.1/24\") that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The except entry describes CIDRs that should not be included within this rule.", + "description": "DEPRECATED 1.9 - This group version of IPBlock is deprecated by networking/v1/IPBlock. IPBlock describes a particular CIDR (Ex. \"192.168.1.1/24\") that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The except entry describes CIDRs that should not be included within this rule.", "required": [ "cidr" ], @@ -71306,7 +79973,7 @@ } }, "io.k8s.api.extensions.v1beta1.NetworkPolicy": { - "description": "NetworkPolicy describes what network traffic is allowed for a set of Pods", + "description": "DEPRECATED 1.9 - This group version of NetworkPolicy is deprecated by networking/v1/NetworkPolicy. NetworkPolicy describes what network traffic is allowed for a set of Pods", "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/api-conventions.md#resources", @@ -71334,7 +80001,7 @@ ] }, "io.k8s.api.extensions.v1beta1.NetworkPolicyEgressRule": { - "description": "NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8", + "description": "DEPRECATED 1.9 - This group version of NetworkPolicyEgressRule is deprecated by networking/v1/NetworkPolicyEgressRule. NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8", "properties": { "ports": { "description": "List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.", @@ -71353,7 +80020,7 @@ } }, "io.k8s.api.extensions.v1beta1.NetworkPolicyIngressRule": { - "description": "This NetworkPolicyIngressRule matches traffic if and only if the traffic matches both ports AND from.", + "description": "DEPRECATED 1.9 - This group version of NetworkPolicyIngressRule is deprecated by networking/v1/NetworkPolicyIngressRule. This NetworkPolicyIngressRule matches traffic if and only if the traffic matches both ports AND from.", "properties": { "from": { "description": "List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least on item, this rule allows traffic only if the traffic matches at least one item in the from list.", @@ -71372,7 +80039,7 @@ } }, "io.k8s.api.extensions.v1beta1.NetworkPolicyList": { - "description": "Network Policy List is a list of NetworkPolicy objects.", + "description": "DEPRECATED 1.9 - This group version of NetworkPolicyList is deprecated by networking/v1/NetworkPolicyList. Network Policy List is a list of NetworkPolicy objects.", "required": [ "items" ], @@ -71406,6 +80073,7 @@ ] }, "io.k8s.api.extensions.v1beta1.NetworkPolicyPeer": { + "description": "DEPRECATED 1.9 - This group version of NetworkPolicyPeer is deprecated by networking/v1/NetworkPolicyPeer.", "properties": { "ipBlock": { "description": "IPBlock defines policy on a particular IPBlock", @@ -71422,6 +80090,7 @@ } }, "io.k8s.api.extensions.v1beta1.NetworkPolicyPort": { + "description": "DEPRECATED 1.9 - This group version of NetworkPolicyPort is deprecated by networking/v1/NetworkPolicyPort.", "properties": { "port": { "description": "If specified, the port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.", @@ -71434,6 +80103,7 @@ } }, "io.k8s.api.extensions.v1beta1.NetworkPolicySpec": { + "description": "DEPRECATED 1.9 - This group version of NetworkPolicySpec is deprecated by networking/v1/NetworkPolicySpec.", "required": [ "podSelector" ], @@ -71547,6 +80217,13 @@ "type": "string" } }, + "allowedFlexVolumes": { + "description": "AllowedFlexVolumes is a whitelist of allowed Flexvolumes. Empty or nil indicates that all Flexvolumes may be used. This parameter is effective only when the usage of the Flexvolumes is allowed in the \"Volumes\" field.", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.AllowedFlexVolume" + } + }, "allowedHostPaths": { "description": "is a white list of allowed host paths. Empty indicates that all host paths may be used.", "type": "array", @@ -71555,7 +80232,7 @@ } }, "defaultAddCapabilities": { - "description": "DefaultAddCapabilities is the default set of capabilities that will be added to the container unless the pod spec specifically drops the capability. You may not list a capabiility in both DefaultAddCapabilities and RequiredDropCapabilities.", + "description": "DefaultAddCapabilities is the default set of capabilities that will be added to the container unless the pod spec specifically drops the capability. You may not list a capability in both DefaultAddCapabilities and RequiredDropCapabilities. Capabilities added here are implicitly allowed, and need not be included in the AllowedCapabilities list.", "type": "array", "items": { "type": "string" @@ -71846,7 +80523,7 @@ "type": "string" }, "seLinuxOptions": { - "description": "seLinuxOptions required to run as; required for MustRunAs More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md", + "description": "seLinuxOptions required to run as; required for MustRunAs More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/", "$ref": "#/definitions/io.k8s.api.core.v1.SELinuxOptions" } } @@ -72267,12 +80944,28 @@ } } }, + "io.k8s.api.rbac.v1.AggregationRule": { + "description": "AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole", + "properties": { + "clusterRoleSelectors": { + "description": "ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. If any of the selectors match, then the ClusterRole's permissions will be added", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" + } + } + } + }, "io.k8s.api.rbac.v1.ClusterRole": { "description": "ClusterRole is a cluster level, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding or ClusterRoleBinding.", "required": [ "rules" ], "properties": { + "aggregationRule": { + "description": "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 stomped by the controller.", + "$ref": "#/definitions/io.k8s.api.rbac.v1.AggregationRule" + }, "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/api-conventions.md#resources", "type": "string" @@ -72639,12 +81332,28 @@ } } }, + "io.k8s.api.rbac.v1alpha1.AggregationRule": { + "description": "AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole", + "properties": { + "clusterRoleSelectors": { + "description": "ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. If any of the selectors match, then the ClusterRole's permissions will be added", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" + } + } + } + }, "io.k8s.api.rbac.v1alpha1.ClusterRole": { "description": "ClusterRole is a cluster level, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding or ClusterRoleBinding.", "required": [ "rules" ], "properties": { + "aggregationRule": { + "description": "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 stomped by the controller.", + "$ref": "#/definitions/io.k8s.api.rbac.v1alpha1.AggregationRule" + }, "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/api-conventions.md#resources", "type": "string" @@ -73011,12 +81720,28 @@ } } }, + "io.k8s.api.rbac.v1beta1.AggregationRule": { + "description": "AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole", + "properties": { + "clusterRoleSelectors": { + "description": "ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. If any of the selectors match, then the ClusterRole's permissions will be added", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" + } + } + } + }, "io.k8s.api.rbac.v1beta1.ClusterRole": { "description": "ClusterRole is a cluster level, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding or ClusterRoleBinding.", "required": [ "rules" ], "properties": { + "aggregationRule": { + "description": "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 stomped by the controller.", + "$ref": "#/definitions/io.k8s.api.rbac.v1beta1.AggregationRule" + }, "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/api-conventions.md#resources", "type": "string" @@ -73180,7 +81905,7 @@ } }, "resources": { - "description": "Resources is a list of resources this rule applies to. ResourceAll represents all resources.", + "description": "Resources is a list of resources this rule applies to. '*' represents all resources in the specified apiGroups. '*/foo' represents the subresource 'foo' for all resources in the specified apiGroups.", "type": "array", "items": { "type": "string" @@ -73597,6 +82322,10 @@ "reclaimPolicy": { "description": "Dynamically provisioned PersistentVolumes of this storage class are created with this reclaimPolicy. Defaults to Delete.", "type": "string" + }, + "volumeBindingMode": { + "description": "VolumeBindingMode indicates how PersistentVolumeClaims should be provisioned and bound. When unset, VolumeBindingImmediate is used. This field is alpha-level and is only honored by servers that enable the VolumeScheduling feature.", + "type": "string" } }, "x-kubernetes-group-version-kind": [ @@ -73641,6 +82370,146 @@ } ] }, + "io.k8s.api.storage.v1alpha1.VolumeAttachment": { + "description": "VolumeAttachment captures the intent to attach or detach the specified volume to/from the specified node.\n\nVolumeAttachment objects are non-namespaced.", + "required": [ + "spec" + ], + "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/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/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "description": "Standard object metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" + }, + "spec": { + "description": "Specification of the desired attach/detach volume behavior. Populated by the Kubernetes system.", + "$ref": "#/definitions/io.k8s.api.storage.v1alpha1.VolumeAttachmentSpec" + }, + "status": { + "description": "Status of the VolumeAttachment request. Populated by the entity completing the attach or detach operation, i.e. the external-attacher.", + "$ref": "#/definitions/io.k8s.api.storage.v1alpha1.VolumeAttachmentStatus" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "storage.k8s.io", + "kind": "VolumeAttachment", + "version": "v1alpha1" + } + ] + }, + "io.k8s.api.storage.v1alpha1.VolumeAttachmentList": { + "description": "VolumeAttachmentList is a collection of VolumeAttachment objects.", + "required": [ + "items" + ], + "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/api-conventions.md#resources", + "type": "string" + }, + "items": { + "description": "Items is the list of VolumeAttachments", + "type": "array", + "items": { + "$ref": "#/definitions/io.k8s.api.storage.v1alpha1.VolumeAttachment" + } + }, + "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/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "description": "Standard list metadata More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "storage.k8s.io", + "kind": "VolumeAttachmentList", + "version": "v1alpha1" + } + ] + }, + "io.k8s.api.storage.v1alpha1.VolumeAttachmentSource": { + "description": "VolumeAttachmentSource represents a volume that should be attached. Right now only PersistenVolumes can be attached via external attacher, in future we may allow also inline volumes in pods. Exactly one member can be set.", + "properties": { + "persistentVolumeName": { + "description": "Name of the persistent volume to attach.", + "type": "string" + } + } + }, + "io.k8s.api.storage.v1alpha1.VolumeAttachmentSpec": { + "description": "VolumeAttachmentSpec is the specification of a VolumeAttachment request.", + "required": [ + "attacher", + "source", + "nodeName" + ], + "properties": { + "attacher": { + "description": "Attacher indicates the name of the volume driver that MUST handle this request. This is the name returned by GetPluginName().", + "type": "string" + }, + "nodeName": { + "description": "The node that the volume should be attached to.", + "type": "string" + }, + "source": { + "description": "Source represents the volume that should be attached.", + "$ref": "#/definitions/io.k8s.api.storage.v1alpha1.VolumeAttachmentSource" + } + } + }, + "io.k8s.api.storage.v1alpha1.VolumeAttachmentStatus": { + "description": "VolumeAttachmentStatus is the status of a VolumeAttachment request.", + "required": [ + "attached" + ], + "properties": { + "attachError": { + "description": "The last error encountered during attach operation, if any. This field must only be set by the entity completing the attach operation, i.e. the external-attacher.", + "$ref": "#/definitions/io.k8s.api.storage.v1alpha1.VolumeError" + }, + "attached": { + "description": "Indicates the volume is successfully attached. This field must only be set by the entity completing the attach operation, i.e. the external-attacher.", + "type": "boolean" + }, + "attachmentMetadata": { + "description": "Upon successful attach, this field is populated with any information returned by the attach operation that must be passed into subsequent WaitForAttach or Mount calls. This field must only be set by the entity completing the attach operation, i.e. the external-attacher.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "detachError": { + "description": "The last error encountered during detach operation, if any. This field must only be set by the entity completing the detach operation, i.e. the external-attacher.", + "$ref": "#/definitions/io.k8s.api.storage.v1alpha1.VolumeError" + } + } + }, + "io.k8s.api.storage.v1alpha1.VolumeError": { + "description": "VolumeError captures an error encountered during a volume operation.", + "properties": { + "message": { + "description": "String detailing the error encountered during Attach or Detach operation. This string maybe logged, so it should not contain sensitive information.", + "type": "string" + }, + "time": { + "description": "Time the error was encountered.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + } + } + }, "io.k8s.api.storage.v1beta1.StorageClass": { "description": "StorageClass describes the parameters for a class of storage for which PersistentVolumes can be dynamically provisioned.\n\nStorageClasses are non-namespaced; the name of the storage class according to etcd is in ObjectMeta.Name.", "required": [ @@ -73684,6 +82553,10 @@ "reclaimPolicy": { "description": "Dynamically provisioned PersistentVolumes of this storage class are created with this reclaimPolicy. Defaults to Delete.", "type": "string" + }, + "volumeBindingMode": { + "description": "VolumeBindingMode indicates how PersistentVolumeClaims should be provisioned and bound. When unset, VolumeBindingImmediate is used. This field is alpha-level and is only honored by servers that enable the VolumeScheduling feature.", + "type": "string" } }, "x-kubernetes-group-version-kind": [ @@ -73861,7 +82734,7 @@ "type": "string" }, "validation": { - "description": "Validation describes the validation methods for CustomResources This field is alpha-level and should only be sent to servers that enable the CustomResourceValidation feature.", + "description": "Validation describes the validation methods for CustomResources", "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.CustomResourceValidation" }, "version": { @@ -74357,7 +83230,7 @@ "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Preconditions" }, "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.", + "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" } }, @@ -74370,13 +83243,18 @@ { "group": "admission.k8s.io", "kind": "DeleteOptions", - "version": "v1alpha1" + "version": "v1beta1" }, { "group": "admissionregistration.k8s.io", "kind": "DeleteOptions", "version": "v1alpha1" }, + { + "group": "admissionregistration.k8s.io", + "kind": "DeleteOptions", + "version": "v1beta1" + }, { "group": "apps", "kind": "DeleteOptions", @@ -74443,12 +83321,12 @@ "version": "v1beta1" }, { - "group": "extensions", + "group": "events.k8s.io", "kind": "DeleteOptions", "version": "v1beta1" }, { - "group": "federation", + "group": "extensions", "kind": "DeleteOptions", "version": "v1beta1" }, @@ -74497,6 +83375,11 @@ "kind": "DeleteOptions", "version": "v1" }, + { + "group": "storage.k8s.io", + "kind": "DeleteOptions", + "version": "v1alpha1" + }, { "group": "storage.k8s.io", "kind": "DeleteOptions", @@ -74616,6 +83499,10 @@ } } }, + "io.k8s.apimachinery.pkg.apis.meta.v1.MicroTime": { + "type": "string", + "format": "date-time" + }, "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": { @@ -74640,7 +83527,7 @@ "format": "int64" }, "deletionTimestamp": { - "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 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/api-conventions.md#metadata", + "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/api-conventions.md#metadata", "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" }, "finalizers": { @@ -74889,13 +83776,18 @@ { "group": "admission.k8s.io", "kind": "WatchEvent", - "version": "v1alpha1" + "version": "v1beta1" }, { "group": "admissionregistration.k8s.io", "kind": "WatchEvent", "version": "v1alpha1" }, + { + "group": "admissionregistration.k8s.io", + "kind": "WatchEvent", + "version": "v1beta1" + }, { "group": "apps", "kind": "WatchEvent", @@ -74962,12 +83854,12 @@ "version": "v1beta1" }, { - "group": "extensions", + "group": "events.k8s.io", "kind": "WatchEvent", "version": "v1beta1" }, { - "group": "federation", + "group": "extensions", "kind": "WatchEvent", "version": "v1beta1" }, @@ -75016,6 +83908,11 @@ "kind": "WatchEvent", "version": "v1" }, + { + "group": "storage.k8s.io", + "kind": "WatchEvent", + "version": "v1alpha1" + }, { "group": "storage.k8s.io", "kind": "WatchEvent", @@ -75825,22 +84722,6 @@ "description": "Deprecated. Please use io.k8s.api.core.v1.WeightedPodAffinityTerm instead.", "$ref": "#/definitions/io.k8s.api.core.v1.WeightedPodAffinityTerm" }, - "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.AdmissionHookClientConfig": { - "description": "Deprecated. Please use io.k8s.api.admissionregistration.v1alpha1.AdmissionHookClientConfig instead.", - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.AdmissionHookClientConfig" - }, - "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.ExternalAdmissionHook": { - "description": "Deprecated. Please use io.k8s.api.admissionregistration.v1alpha1.ExternalAdmissionHook instead.", - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.ExternalAdmissionHook" - }, - "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.ExternalAdmissionHookConfiguration": { - "description": "Deprecated. Please use io.k8s.api.admissionregistration.v1alpha1.ExternalAdmissionHookConfiguration instead.", - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.ExternalAdmissionHookConfiguration" - }, - "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.ExternalAdmissionHookConfigurationList": { - "description": "Deprecated. Please use io.k8s.api.admissionregistration.v1alpha1.ExternalAdmissionHookConfigurationList instead.", - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.ExternalAdmissionHookConfigurationList" - }, "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.Initializer": { "description": "Deprecated. Please use io.k8s.api.admissionregistration.v1alpha1.Initializer instead.", "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.Initializer" @@ -75857,14 +84738,6 @@ "description": "Deprecated. Please use io.k8s.api.admissionregistration.v1alpha1.Rule instead.", "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.Rule" }, - "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.RuleWithOperations": { - "description": "Deprecated. Please use io.k8s.api.admissionregistration.v1alpha1.RuleWithOperations instead.", - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.RuleWithOperations" - }, - "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.ServiceReference": { - "description": "Deprecated. Please use io.k8s.api.admissionregistration.v1alpha1.ServiceReference instead.", - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1alpha1.ServiceReference" - }, "io.k8s.kubernetes.pkg.apis.apps.v1beta1.ControllerRevision": { "description": "Deprecated. Please use io.k8s.api.apps.v1beta1.ControllerRevision instead.", "$ref": "#/definitions/io.k8s.api.apps.v1beta1.ControllerRevision" diff --git a/api/swagger-spec/admissionregistration.k8s.io_v1alpha1.json b/api/swagger-spec/admissionregistration.k8s.io_v1alpha1.json index c3218e0ffcf..e2f457cf2be 100644 --- a/api/swagger-spec/admissionregistration.k8s.io_v1alpha1.json +++ b/api/swagger-spec/admissionregistration.k8s.io_v1alpha1.json @@ -8,700 +8,6 @@ "description": "" }, "apis": [ - { - "path": "/apis/admissionregistration.k8s.io/v1alpha1/externaladmissionhookconfigurations", - "description": "API at /apis/admissionregistration.k8s.io/v1alpha1", - "operations": [ - { - "type": "v1alpha1.ExternalAdmissionHookConfigurationList", - "method": "GET", - "summary": "list or watch objects of kind ExternalAdmissionHookConfiguration", - "nickname": "listExternalAdmissionHookConfiguration", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1alpha1.ExternalAdmissionHookConfigurationList" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1alpha1.ExternalAdmissionHookConfiguration", - "method": "POST", - "summary": "create an ExternalAdmissionHookConfiguration", - "nickname": "createExternalAdmissionHookConfiguration", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1alpha1.ExternalAdmissionHookConfiguration", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1alpha1.ExternalAdmissionHookConfiguration" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1alpha1.ExternalAdmissionHookConfiguration" - }, - { - "code": 202, - "message": "Accepted", - "responseModel": "v1alpha1.ExternalAdmissionHookConfiguration" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Status", - "method": "DELETE", - "summary": "delete collection of ExternalAdmissionHookConfiguration", - "nickname": "deletecollectionExternalAdmissionHookConfiguration", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Status" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/admissionregistration.k8s.io/v1alpha1/watch/externaladmissionhookconfigurations", - "description": "API at /apis/admissionregistration.k8s.io/v1alpha1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch individual changes to a list of ExternalAdmissionHookConfiguration", - "nickname": "watchExternalAdmissionHookConfigurationList", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/admissionregistration.k8s.io/v1alpha1/externaladmissionhookconfigurations/{name}", - "description": "API at /apis/admissionregistration.k8s.io/v1alpha1", - "operations": [ - { - "type": "v1alpha1.ExternalAdmissionHookConfiguration", - "method": "GET", - "summary": "read the specified ExternalAdmissionHookConfiguration", - "nickname": "readExternalAdmissionHookConfiguration", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "export", - "description": "Should this value be exported. Export strips fields that a user can not specify.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "exact", - "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the ExternalAdmissionHookConfiguration", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1alpha1.ExternalAdmissionHookConfiguration" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1alpha1.ExternalAdmissionHookConfiguration", - "method": "PUT", - "summary": "replace the specified ExternalAdmissionHookConfiguration", - "nickname": "replaceExternalAdmissionHookConfiguration", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1alpha1.ExternalAdmissionHookConfiguration", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the ExternalAdmissionHookConfiguration", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1alpha1.ExternalAdmissionHookConfiguration" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1alpha1.ExternalAdmissionHookConfiguration" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1alpha1.ExternalAdmissionHookConfiguration", - "method": "PATCH", - "summary": "partially update the specified ExternalAdmissionHookConfiguration", - "nickname": "patchExternalAdmissionHookConfiguration", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Patch", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the ExternalAdmissionHookConfiguration", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1alpha1.ExternalAdmissionHookConfiguration" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ] - }, - { - "type": "v1.Status", - "method": "DELETE", - "summary": "delete an ExternalAdmissionHookConfiguration", - "nickname": "deleteExternalAdmissionHookConfiguration", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.DeleteOptions", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the ExternalAdmissionHookConfiguration", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Status" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/admissionregistration.k8s.io/v1alpha1/watch/externaladmissionhookconfigurations/{name}", - "description": "API at /apis/admissionregistration.k8s.io/v1alpha1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch changes to an object of kind ExternalAdmissionHookConfiguration", - "nickname": "watchExternalAdmissionHookConfiguration", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the ExternalAdmissionHookConfiguration", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, { "path": "/apis/admissionregistration.k8s.io/v1alpha1/initializerconfigurations", "description": "API at /apis/admissionregistration.k8s.io/v1alpha1", @@ -1254,7 +560,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -1421,9 +727,9 @@ } ], "models": { - "v1alpha1.ExternalAdmissionHookConfigurationList": { - "id": "v1alpha1.ExternalAdmissionHookConfigurationList", - "description": "ExternalAdmissionHookConfigurationList is a list of ExternalAdmissionHookConfiguration.", + "v1alpha1.InitializerConfigurationList": { + "id": "v1alpha1.InitializerConfigurationList", + "description": "InitializerConfigurationList is a list of InitializerConfiguration.", "required": [ "items" ], @@ -1443,9 +749,9 @@ "items": { "type": "array", "items": { - "$ref": "v1alpha1.ExternalAdmissionHookConfiguration" + "$ref": "v1alpha1.InitializerConfiguration" }, - "description": "List of ExternalAdmissionHookConfiguration." + "description": "List of InitializerConfiguration." } } }, @@ -1467,9 +773,9 @@ } } }, - "v1alpha1.ExternalAdmissionHookConfiguration": { - "id": "v1alpha1.ExternalAdmissionHookConfiguration", - "description": "ExternalAdmissionHookConfiguration describes the configuration of initializers.", + "v1alpha1.InitializerConfiguration": { + "id": "v1alpha1.InitializerConfiguration", + "description": "InitializerConfiguration describes the configuration of initializers.", "properties": { "kind": { "type": "string", @@ -1483,12 +789,12 @@ "$ref": "v1.ObjectMeta", "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata." }, - "externalAdmissionHooks": { + "initializers": { "type": "array", "items": { - "$ref": "v1alpha1.ExternalAdmissionHook" + "$ref": "v1alpha1.Initializer" }, - "description": "ExternalAdmissionHooks is a list of external admission webhooks and the affected resources and operations." + "description": "Initializers is a list of resources and their default initializers Order-sensitive. When merging multiple InitializerConfigurations, we sort the initializers from different InitializerConfigurations by the name of the InitializerConfigurations; the order of the initializers from the same InitializerConfiguration is preserved." } } }, @@ -1531,7 +837,7 @@ }, "deletionTimestamp": { "type": "string", - "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 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/api-conventions.md#metadata" + "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/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "type": "integer", @@ -1730,82 +1036,30 @@ } } }, - "v1alpha1.ExternalAdmissionHook": { - "id": "v1alpha1.ExternalAdmissionHook", - "description": "ExternalAdmissionHook describes an external admission webhook and the resources and operations it applies to.", + "v1alpha1.Initializer": { + "id": "v1alpha1.Initializer", + "description": "Initializer describes the name and the failure policy of an initializer, and what resources it applies to.", "required": [ - "name", - "clientConfig" + "name" ], "properties": { "name": { "type": "string", - "description": "The name of the external 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": { - "$ref": "v1alpha1.AdmissionHookClientConfig", - "description": "ClientConfig defines how to communicate with the hook. Required" + "description": "Name is the identifier of the initializer. It will be added to the object that needs to be initialized. Name should be fully qualified, e.g., alwayspullimages.kubernetes.io, where \"alwayspullimages\" is the name of the webhook, and kubernetes.io is the name of the organization. Required" }, "rules": { "type": "array", "items": { - "$ref": "v1alpha1.RuleWithOperations" + "$ref": "v1alpha1.Rule" }, - "description": "Rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule." - }, - "failurePolicy": { - "$ref": "v1alpha1.FailurePolicyType", - "description": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Ignore." + "description": "Rules describes what resources/subresources the initializer cares about. The initializer cares about an operation if it matches _any_ Rule. Rule.Resources must not include subresources." } } }, - "v1alpha1.AdmissionHookClientConfig": { - "id": "v1alpha1.AdmissionHookClientConfig", - "description": "AdmissionHookClientConfig contains the information to make a TLS connection with the webhook", - "required": [ - "service", - "caBundle" - ], + "v1alpha1.Rule": { + "id": "v1alpha1.Rule", + "description": "Rule is a tuple of APIGroups, APIVersion, and Resources.It is recommended to make sure that all the tuple expansions are valid.", "properties": { - "service": { - "$ref": "v1alpha1.ServiceReference", - "description": "Service is a reference to the service for this webhook. If there is only one port open for the service, that port will be used. If there are multiple ports open, port 443 will be used if it is open, otherwise it is an error. Required" - }, - "caBundle": { - "type": "string", - "description": "CABundle is a PEM encoded CA bundle which will be used to validate webhook's server certificate. Required" - } - } - }, - "v1alpha1.ServiceReference": { - "id": "v1alpha1.ServiceReference", - "description": "ServiceReference holds a reference to Service.legacy.k8s.io", - "required": [ - "namespace", - "name" - ], - "properties": { - "namespace": { - "type": "string", - "description": "Namespace is the namespace of the service Required" - }, - "name": { - "type": "string", - "description": "Name is the name of the service Required" - } - } - }, - "v1alpha1.RuleWithOperations": { - "id": "v1alpha1.RuleWithOperations", - "description": "RuleWithOperations is a tuple of Operations and Resources. It is recommended to make sure that all the tuple expansions are valid.", - "properties": { - "operations": { - "type": "array", - "items": { - "$ref": "v1alpha1.OperationType" - }, - "description": "Operations is the operations the admission hook cares about - CREATE, UPDATE, or * for all operations. If '*' is present, the length of the slice must be one. Required." - }, "apiGroups": { "type": "array", "items": { @@ -1829,14 +1083,6 @@ } } }, - "v1alpha1.OperationType": { - "id": "v1alpha1.OperationType", - "properties": {} - }, - "v1alpha1.FailurePolicyType": { - "id": "v1alpha1.FailurePolicyType", - "properties": {} - }, "v1.WatchEvent": { "id": "v1.WatchEvent", "required": [ @@ -1884,7 +1130,7 @@ }, "propagationPolicy": { "$ref": "v1.DeletionPropagation", - "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." + "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." } } }, @@ -1906,106 +1152,6 @@ "id": "v1.DeletionPropagation", "properties": {} }, - "v1alpha1.InitializerConfigurationList": { - "id": "v1alpha1.InitializerConfigurationList", - "description": "InitializerConfigurationList is a list of InitializerConfiguration.", - "required": [ - "items" - ], - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ListMeta", - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds" - }, - "items": { - "type": "array", - "items": { - "$ref": "v1alpha1.InitializerConfiguration" - }, - "description": "List of InitializerConfiguration." - } - } - }, - "v1alpha1.InitializerConfiguration": { - "id": "v1alpha1.InitializerConfiguration", - "description": "InitializerConfiguration describes the configuration of initializers.", - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ObjectMeta", - "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata." - }, - "initializers": { - "type": "array", - "items": { - "$ref": "v1alpha1.Initializer" - }, - "description": "Initializers is a list of resources and their default initializers Order-sensitive. When merging multiple InitializerConfigurations, we sort the initializers from different InitializerConfigurations by the name of the InitializerConfigurations; the order of the initializers from the same InitializerConfiguration is preserved." - } - } - }, - "v1alpha1.Initializer": { - "id": "v1alpha1.Initializer", - "description": "Initializer describes the name and the failure policy of an initializer, and what resources it applies to.", - "required": [ - "name" - ], - "properties": { - "name": { - "type": "string", - "description": "Name is the identifier of the initializer. It will be added to the object that needs to be initialized. Name should be fully qualified, e.g., alwayspullimages.kubernetes.io, where \"alwayspullimages\" is the name of the webhook, and kubernetes.io is the name of the organization. Required" - }, - "rules": { - "type": "array", - "items": { - "$ref": "v1alpha1.Rule" - }, - "description": "Rules describes what resources/subresources the initializer cares about. The initializer cares about an operation if it matches _any_ Rule. Rule.Resources must not include subresources." - } - } - }, - "v1alpha1.Rule": { - "id": "v1alpha1.Rule", - "description": "Rule is a tuple of APIGroups, APIVersion, and Resources.It is recommended to make sure that all the tuple expansions are valid.", - "properties": { - "apiGroups": { - "type": "array", - "items": { - "type": "string" - }, - "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." - }, - "apiVersions": { - "type": "array", - "items": { - "type": "string" - }, - "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." - }, - "resources": { - "type": "array", - "items": { - "type": "string" - }, - "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." - } - } - }, "v1.APIResourceList": { "id": "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.", diff --git a/api/swagger-spec/admissionregistration.k8s.io_v1beta1.json b/api/swagger-spec/admissionregistration.k8s.io_v1beta1.json new file mode 100644 index 00000000000..dd4e735bf89 --- /dev/null +++ b/api/swagger-spec/admissionregistration.k8s.io_v1beta1.json @@ -0,0 +1,2104 @@ +{ + "swaggerVersion": "1.2", + "apiVersion": "admissionregistration.k8s.io/v1beta1", + "basePath": "https://10.10.10.10:6443", + "resourcePath": "/apis/admissionregistration.k8s.io/v1beta1", + "info": { + "title": "", + "description": "" + }, + "apis": [ + { + "path": "/apis/admissionregistration.k8s.io/v1beta1/mutatingwebhookconfigurations", + "description": "API at /apis/admissionregistration.k8s.io/v1beta1", + "operations": [ + { + "type": "v1beta1.MutatingWebhookConfigurationList", + "method": "GET", + "summary": "list or watch objects of kind MutatingWebhookConfiguration", + "nickname": "listMutatingWebhookConfiguration", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta1.MutatingWebhookConfigurationList" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1beta1.MutatingWebhookConfiguration", + "method": "POST", + "summary": "create a MutatingWebhookConfiguration", + "nickname": "createMutatingWebhookConfiguration", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1beta1.MutatingWebhookConfiguration", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta1.MutatingWebhookConfiguration" + }, + { + "code": 201, + "message": "Created", + "responseModel": "v1beta1.MutatingWebhookConfiguration" + }, + { + "code": 202, + "message": "Accepted", + "responseModel": "v1beta1.MutatingWebhookConfiguration" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.Status", + "method": "DELETE", + "summary": "delete collection of MutatingWebhookConfiguration", + "nickname": "deletecollectionMutatingWebhookConfiguration", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Status" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/admissionregistration.k8s.io/v1beta1/watch/mutatingwebhookconfigurations", + "description": "API at /apis/admissionregistration.k8s.io/v1beta1", + "operations": [ + { + "type": "v1.WatchEvent", + "method": "GET", + "summary": "watch individual changes to a list of MutatingWebhookConfiguration", + "nickname": "watchMutatingWebhookConfigurationList", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.WatchEvent" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/admissionregistration.k8s.io/v1beta1/mutatingwebhookconfigurations/{name}", + "description": "API at /apis/admissionregistration.k8s.io/v1beta1", + "operations": [ + { + "type": "v1beta1.MutatingWebhookConfiguration", + "method": "GET", + "summary": "read the specified MutatingWebhookConfiguration", + "nickname": "readMutatingWebhookConfiguration", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "export", + "description": "Should this value be exported. Export strips fields that a user can not specify.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "exact", + "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the MutatingWebhookConfiguration", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta1.MutatingWebhookConfiguration" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1beta1.MutatingWebhookConfiguration", + "method": "PUT", + "summary": "replace the specified MutatingWebhookConfiguration", + "nickname": "replaceMutatingWebhookConfiguration", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1beta1.MutatingWebhookConfiguration", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the MutatingWebhookConfiguration", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta1.MutatingWebhookConfiguration" + }, + { + "code": 201, + "message": "Created", + "responseModel": "v1beta1.MutatingWebhookConfiguration" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1beta1.MutatingWebhookConfiguration", + "method": "PATCH", + "summary": "partially update the specified MutatingWebhookConfiguration", + "nickname": "patchMutatingWebhookConfiguration", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.Patch", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the MutatingWebhookConfiguration", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta1.MutatingWebhookConfiguration" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ] + }, + { + "type": "v1.Status", + "method": "DELETE", + "summary": "delete a MutatingWebhookConfiguration", + "nickname": "deleteMutatingWebhookConfiguration", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.DeleteOptions", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the MutatingWebhookConfiguration", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Status" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/admissionregistration.k8s.io/v1beta1/watch/mutatingwebhookconfigurations/{name}", + "description": "API at /apis/admissionregistration.k8s.io/v1beta1", + "operations": [ + { + "type": "v1.WatchEvent", + "method": "GET", + "summary": "watch changes to an object of kind MutatingWebhookConfiguration", + "nickname": "watchMutatingWebhookConfiguration", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the MutatingWebhookConfiguration", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.WatchEvent" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/admissionregistration.k8s.io/v1beta1/validatingwebhookconfigurations", + "description": "API at /apis/admissionregistration.k8s.io/v1beta1", + "operations": [ + { + "type": "v1beta1.ValidatingWebhookConfigurationList", + "method": "GET", + "summary": "list or watch objects of kind ValidatingWebhookConfiguration", + "nickname": "listValidatingWebhookConfiguration", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta1.ValidatingWebhookConfigurationList" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1beta1.ValidatingWebhookConfiguration", + "method": "POST", + "summary": "create a ValidatingWebhookConfiguration", + "nickname": "createValidatingWebhookConfiguration", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1beta1.ValidatingWebhookConfiguration", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta1.ValidatingWebhookConfiguration" + }, + { + "code": 201, + "message": "Created", + "responseModel": "v1beta1.ValidatingWebhookConfiguration" + }, + { + "code": 202, + "message": "Accepted", + "responseModel": "v1beta1.ValidatingWebhookConfiguration" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.Status", + "method": "DELETE", + "summary": "delete collection of ValidatingWebhookConfiguration", + "nickname": "deletecollectionValidatingWebhookConfiguration", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Status" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/admissionregistration.k8s.io/v1beta1/watch/validatingwebhookconfigurations", + "description": "API at /apis/admissionregistration.k8s.io/v1beta1", + "operations": [ + { + "type": "v1.WatchEvent", + "method": "GET", + "summary": "watch individual changes to a list of ValidatingWebhookConfiguration", + "nickname": "watchValidatingWebhookConfigurationList", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.WatchEvent" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/admissionregistration.k8s.io/v1beta1/validatingwebhookconfigurations/{name}", + "description": "API at /apis/admissionregistration.k8s.io/v1beta1", + "operations": [ + { + "type": "v1beta1.ValidatingWebhookConfiguration", + "method": "GET", + "summary": "read the specified ValidatingWebhookConfiguration", + "nickname": "readValidatingWebhookConfiguration", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "export", + "description": "Should this value be exported. Export strips fields that a user can not specify.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "exact", + "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the ValidatingWebhookConfiguration", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta1.ValidatingWebhookConfiguration" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1beta1.ValidatingWebhookConfiguration", + "method": "PUT", + "summary": "replace the specified ValidatingWebhookConfiguration", + "nickname": "replaceValidatingWebhookConfiguration", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1beta1.ValidatingWebhookConfiguration", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the ValidatingWebhookConfiguration", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta1.ValidatingWebhookConfiguration" + }, + { + "code": 201, + "message": "Created", + "responseModel": "v1beta1.ValidatingWebhookConfiguration" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1beta1.ValidatingWebhookConfiguration", + "method": "PATCH", + "summary": "partially update the specified ValidatingWebhookConfiguration", + "nickname": "patchValidatingWebhookConfiguration", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.Patch", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the ValidatingWebhookConfiguration", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta1.ValidatingWebhookConfiguration" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ] + }, + { + "type": "v1.Status", + "method": "DELETE", + "summary": "delete a ValidatingWebhookConfiguration", + "nickname": "deleteValidatingWebhookConfiguration", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.DeleteOptions", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the ValidatingWebhookConfiguration", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Status" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/admissionregistration.k8s.io/v1beta1/watch/validatingwebhookconfigurations/{name}", + "description": "API at /apis/admissionregistration.k8s.io/v1beta1", + "operations": [ + { + "type": "v1.WatchEvent", + "method": "GET", + "summary": "watch changes to an object of kind ValidatingWebhookConfiguration", + "nickname": "watchValidatingWebhookConfiguration", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the ValidatingWebhookConfiguration", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.WatchEvent" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/admissionregistration.k8s.io/v1beta1", + "description": "API at /apis/admissionregistration.k8s.io/v1beta1", + "operations": [ + { + "type": "v1.APIResourceList", + "method": "GET", + "summary": "get available resources", + "nickname": "getAPIResources", + "parameters": [], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ] + } + ] + } + ], + "models": { + "v1beta1.MutatingWebhookConfigurationList": { + "id": "v1beta1.MutatingWebhookConfigurationList", + "description": "MutatingWebhookConfigurationList is a list of MutatingWebhookConfiguration.", + "required": [ + "items" + ], + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "metadata": { + "$ref": "v1.ListMeta", + "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds" + }, + "items": { + "type": "array", + "items": { + "$ref": "v1beta1.MutatingWebhookConfiguration" + }, + "description": "List of MutatingWebhookConfiguration." + } + } + }, + "v1.ListMeta": { + "id": "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": { + "selfLink": { + "type": "string", + "description": "selfLink is a URL representing this object. Populated by the system. Read-only." + }, + "resourceVersion": { + "type": "string", + "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/api-conventions.md#concurrency-control-and-consistency" + }, + "continue": { + "type": "string", + "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 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." + } + } + }, + "v1beta1.MutatingWebhookConfiguration": { + "id": "v1beta1.MutatingWebhookConfiguration", + "description": "MutatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and may change the object.", + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "metadata": { + "$ref": "v1.ObjectMeta", + "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata." + }, + "webhooks": { + "type": "array", + "items": { + "$ref": "v1beta1.Webhook" + }, + "description": "Webhooks is a list of webhooks and the affected resources and operations." + } + } + }, + "v1.ObjectMeta": { + "id": "v1.ObjectMeta", + "description": "ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.", + "properties": { + "name": { + "type": "string", + "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: http://kubernetes.io/docs/user-guide/identifiers#names" + }, + "generateName": { + "type": "string", + "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 NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header).\n\nApplied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency" + }, + "namespace": { + "type": "string", + "description": "Namespace defines the space within 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: http://kubernetes.io/docs/user-guide/namespaces" + }, + "selfLink": { + "type": "string", + "description": "SelfLink is a URL representing this object. Populated by the system. Read-only." + }, + "uid": { + "type": "string", + "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: http://kubernetes.io/docs/user-guide/identifiers#uids" + }, + "resourceVersion": { + "type": "string", + "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/api-conventions.md#concurrency-control-and-consistency" + }, + "generation": { + "type": "integer", + "format": "int64", + "description": "A sequence number representing a specific generation of the desired state. Populated by the system. Read-only." + }, + "creationTimestamp": { + "type": "string", + "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/api-conventions.md#metadata" + }, + "deletionTimestamp": { + "type": "string", + "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/api-conventions.md#metadata" + }, + "deletionGracePeriodSeconds": { + "type": "integer", + "format": "int64", + "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." + }, + "labels": { + "type": "object", + "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: http://kubernetes.io/docs/user-guide/labels" + }, + "annotations": { + "type": "object", + "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: http://kubernetes.io/docs/user-guide/annotations" + }, + "ownerReferences": { + "type": "array", + "items": { + "$ref": "v1.OwnerReference" + }, + "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." + }, + "initializers": { + "$ref": "v1.Initializers", + "description": "An initializer is a controller which enforces some system invariant at object creation time. This field is a list of initializers that have not yet acted on this object. If nil or empty, this object has been completely initialized. Otherwise, the object is considered uninitialized and is hidden (in list/watch and get calls) from clients that haven't explicitly asked to observe uninitialized objects.\n\nWhen an object is created, the system will populate this list with the current set of initializers. Only privileged users may set or modify this list. Once it is empty, it may not be modified further by any user." + }, + "finalizers": { + "type": "array", + "items": { + "type": "string" + }, + "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." + }, + "clusterName": { + "type": "string", + "description": "The name of the cluster which the object belongs to. This is used to distinguish resources with same name and namespace in different clusters. This field is not set anywhere right now and apiserver is going to ignore it if set in create or update request." + } + } + }, + "v1.OwnerReference": { + "id": "v1.OwnerReference", + "description": "OwnerReference contains enough information to let you identify an owning object. Currently, an owning object must be in the same namespace, so there is no namespace field.", + "required": [ + "apiVersion", + "kind", + "name", + "uid" + ], + "properties": { + "apiVersion": { + "type": "string", + "description": "API version of the referent." + }, + "kind": { + "type": "string", + "description": "Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds" + }, + "name": { + "type": "string", + "description": "Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names" + }, + "uid": { + "type": "string", + "description": "UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids" + }, + "controller": { + "type": "boolean", + "description": "If true, this reference points to the managing controller." + }, + "blockOwnerDeletion": { + "type": "boolean", + "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. Defaults to false. To set this field, a user needs \"delete\" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned." + } + } + }, + "v1.Initializers": { + "id": "v1.Initializers", + "description": "Initializers tracks the progress of initialization.", + "required": [ + "pending" + ], + "properties": { + "pending": { + "type": "array", + "items": { + "$ref": "v1.Initializer" + }, + "description": "Pending is a list of initializers that must execute in order before this object is visible. When the last pending initializer is removed, and no failing result is set, the initializers struct will be set to nil and the object is considered as initialized and visible to all clients." + }, + "result": { + "$ref": "v1.Status", + "description": "If result is set with the Failure field, the object will be persisted to storage and then deleted, ensuring that other clients can observe the deletion." + } + } + }, + "v1.Initializer": { + "id": "v1.Initializer", + "description": "Initializer is information about an initializer that has not yet completed.", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "name of the process that is responsible for initializing this object." + } + } + }, + "v1.Status": { + "id": "v1.Status", + "description": "Status is a return value for calls that don't return other objects.", + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "metadata": { + "$ref": "v1.ListMeta", + "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds" + }, + "status": { + "type": "string", + "description": "Status of the operation. One of: \"Success\" or \"Failure\". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status" + }, + "message": { + "type": "string", + "description": "A human-readable description of the status of this operation." + }, + "reason": { + "type": "string", + "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." + }, + "details": { + "$ref": "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." + }, + "code": { + "type": "integer", + "format": "int32", + "description": "Suggested HTTP return code for this status, 0 if not set." + } + } + }, + "v1.StatusDetails": { + "id": "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": { + "name": { + "type": "string", + "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described)." + }, + "group": { + "type": "string", + "description": "The group attribute of the resource associated with the status StatusReason." + }, + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "uid": { + "type": "string", + "description": "UID of the resource. (when there is a single resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids" + }, + "causes": { + "type": "array", + "items": { + "$ref": "v1.StatusCause" + }, + "description": "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes." + }, + "retryAfterSeconds": { + "type": "integer", + "format": "int32", + "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." + } + } + }, + "v1.StatusCause": { + "id": "v1.StatusCause", + "description": "StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.", + "properties": { + "reason": { + "type": "string", + "description": "A machine-readable description of the cause of the error. If this value is empty there is no information available." + }, + "message": { + "type": "string", + "description": "A human-readable description of the cause of the error. This field may be presented as-is to a reader." + }, + "field": { + "type": "string", + "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\"" + } + } + }, + "v1beta1.Webhook": { + "id": "v1beta1.Webhook", + "description": "Webhook describes an admission webhook and the resources and operations it applies to.", + "required": [ + "name", + "clientConfig" + ], + "properties": { + "name": { + "type": "string", + "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." + }, + "clientConfig": { + "$ref": "v1beta1.WebhookClientConfig", + "description": "ClientConfig defines how to communicate with the hook. Required" + }, + "rules": { + "type": "array", + "items": { + "$ref": "v1beta1.RuleWithOperations" + }, + "description": "Rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule." + }, + "failurePolicy": { + "$ref": "v1beta1.FailurePolicyType", + "description": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Ignore." + }, + "namespaceSelector": { + "$ref": "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 other cluster scoped resource, it is not subjected to 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." + } + } + }, + "v1beta1.WebhookClientConfig": { + "id": "v1beta1.WebhookClientConfig", + "description": "WebhookClientConfig contains the information to make a TLS connection with the webhook", + "required": [ + "service", + "caBundle" + ], + "properties": { + "url": { + "type": "string", + "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." + }, + "service": { + "$ref": "v1beta1.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`.\n\nIf there is only one port open for the service, that port will be used. If there are multiple ports open, port 443 will be used if it is open, otherwise it is an error." + }, + "caBundle": { + "type": "string", + "description": "`caBundle` is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. Required." + } + } + }, + "v1beta1.ServiceReference": { + "id": "v1beta1.ServiceReference", + "description": "ServiceReference holds a reference to Service.legacy.k8s.io", + "required": [ + "namespace", + "name" + ], + "properties": { + "namespace": { + "type": "string", + "description": "`namespace` is the namespace of the service. Required" + }, + "name": { + "type": "string", + "description": "`name` is the name of the service. Required" + }, + "path": { + "type": "string", + "description": "`path` is an optional URL path which will be sent in any request to this service." + } + } + }, + "v1beta1.RuleWithOperations": { + "id": "v1beta1.RuleWithOperations", + "description": "RuleWithOperations is a tuple of Operations and Resources. It is recommended to make sure that all the tuple expansions are valid.", + "properties": { + "operations": { + "type": "array", + "items": { + "$ref": "v1beta1.OperationType" + }, + "description": "Operations is the operations the admission hook cares about - CREATE, UPDATE, or * for all operations. If '*' is present, the length of the slice must be one. Required." + }, + "apiGroups": { + "type": "array", + "items": { + "type": "string" + }, + "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." + }, + "apiVersions": { + "type": "array", + "items": { + "type": "string" + }, + "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." + }, + "resources": { + "type": "array", + "items": { + "type": "string" + }, + "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." + } + } + }, + "v1beta1.OperationType": { + "id": "v1beta1.OperationType", + "properties": {} + }, + "v1beta1.FailurePolicyType": { + "id": "v1beta1.FailurePolicyType", + "properties": {} + }, + "v1.LabelSelector": { + "id": "v1.LabelSelector", + "description": "A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.", + "properties": { + "matchLabels": { + "type": "object", + "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed." + }, + "matchExpressions": { + "type": "array", + "items": { + "$ref": "v1.LabelSelectorRequirement" + }, + "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed." + } + } + }, + "v1.LabelSelectorRequirement": { + "id": "v1.LabelSelectorRequirement", + "description": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + "required": [ + "key", + "operator" + ], + "properties": { + "key": { + "type": "string", + "description": "key is the label key that the selector applies to." + }, + "operator": { + "type": "string", + "description": "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist." + }, + "values": { + "type": "array", + "items": { + "type": "string" + }, + "description": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch." + } + } + }, + "v1.WatchEvent": { + "id": "v1.WatchEvent", + "required": [ + "type", + "object" + ], + "properties": { + "type": { + "type": "string" + }, + "object": { + "type": "string" + } + } + }, + "v1.Patch": { + "id": "v1.Patch", + "description": "Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.", + "properties": {} + }, + "v1.DeleteOptions": { + "id": "v1.DeleteOptions", + "description": "DeleteOptions may be provided when deleting an API object.", + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "gracePeriodSeconds": { + "type": "integer", + "format": "int64", + "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." + }, + "preconditions": { + "$ref": "v1.Preconditions", + "description": "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned." + }, + "orphanDependents": { + "type": "boolean", + "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." + }, + "propagationPolicy": { + "$ref": "v1.DeletionPropagation", + "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." + } + } + }, + "v1.Preconditions": { + "id": "v1.Preconditions", + "description": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", + "properties": { + "uid": { + "$ref": "types.UID", + "description": "Specifies the target UID." + } + } + }, + "types.UID": { + "id": "types.UID", + "properties": {} + }, + "v1.DeletionPropagation": { + "id": "v1.DeletionPropagation", + "properties": {} + }, + "v1beta1.ValidatingWebhookConfigurationList": { + "id": "v1beta1.ValidatingWebhookConfigurationList", + "description": "ValidatingWebhookConfigurationList is a list of ValidatingWebhookConfiguration.", + "required": [ + "items" + ], + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "metadata": { + "$ref": "v1.ListMeta", + "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds" + }, + "items": { + "type": "array", + "items": { + "$ref": "v1beta1.ValidatingWebhookConfiguration" + }, + "description": "List of ValidatingWebhookConfiguration." + } + } + }, + "v1beta1.ValidatingWebhookConfiguration": { + "id": "v1beta1.ValidatingWebhookConfiguration", + "description": "ValidatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and object without changing it.", + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "metadata": { + "$ref": "v1.ObjectMeta", + "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata." + }, + "webhooks": { + "type": "array", + "items": { + "$ref": "v1beta1.Webhook" + }, + "description": "Webhooks is a list of webhooks and the affected resources and operations." + } + } + }, + "v1.APIResourceList": { + "id": "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.", + "required": [ + "groupVersion", + "resources" + ], + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "groupVersion": { + "type": "string", + "description": "groupVersion is the group and version this APIResourceList is for." + }, + "resources": { + "type": "array", + "items": { + "$ref": "v1.APIResource" + }, + "description": "resources contains the name of the resources and if they are namespaced." + } + } + }, + "v1.APIResource": { + "id": "v1.APIResource", + "description": "APIResource specifies the name of a resource and whether it is namespaced.", + "required": [ + "name", + "singularName", + "namespaced", + "kind", + "verbs" + ], + "properties": { + "name": { + "type": "string", + "description": "name is the plural name of the resource." + }, + "singularName": { + "type": "string", + "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." + }, + "namespaced": { + "type": "boolean", + "description": "namespaced indicates if a resource is namespaced or not." + }, + "group": { + "type": "string", + "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\"." + }, + "version": { + "type": "string", + "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)\"." + }, + "kind": { + "type": "string", + "description": "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')" + }, + "verbs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy)" + }, + "shortNames": { + "type": "array", + "items": { + "type": "string" + }, + "description": "shortNames is a list of suggested short names of the resource." + }, + "categories": { + "type": "array", + "items": { + "type": "string" + }, + "description": "categories is a list of the grouped resources this resource belongs to (e.g. 'all')" + } + } + } + } + } diff --git a/api/swagger-spec/apps_v1.json b/api/swagger-spec/apps_v1.json index 62e90557202..ebebe36d148 100644 --- a/api/swagger-spec/apps_v1.json +++ b/api/swagger-spec/apps_v1.json @@ -8,6 +8,978 @@ "description": "" }, "apis": [ + { + "path": "/apis/apps/v1/namespaces/{namespace}/controllerrevisions", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.ControllerRevisionList", + "method": "GET", + "summary": "list or watch objects of kind ControllerRevision", + "nickname": "listNamespacedControllerRevision", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.ControllerRevisionList" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.ControllerRevision", + "method": "POST", + "summary": "create a ControllerRevision", + "nickname": "createNamespacedControllerRevision", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.ControllerRevision", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.ControllerRevision" + }, + { + "code": 201, + "message": "Created", + "responseModel": "v1.ControllerRevision" + }, + { + "code": 202, + "message": "Accepted", + "responseModel": "v1.ControllerRevision" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.Status", + "method": "DELETE", + "summary": "delete collection of ControllerRevision", + "nickname": "deletecollectionNamespacedControllerRevision", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Status" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/apps/v1/watch/namespaces/{namespace}/controllerrevisions", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.WatchEvent", + "method": "GET", + "summary": "watch individual changes to a list of ControllerRevision", + "nickname": "watchNamespacedControllerRevisionList", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.WatchEvent" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/apps/v1/namespaces/{namespace}/controllerrevisions/{name}", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.ControllerRevision", + "method": "GET", + "summary": "read the specified ControllerRevision", + "nickname": "readNamespacedControllerRevision", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "export", + "description": "Should this value be exported. Export strips fields that a user can not specify.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "exact", + "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the ControllerRevision", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.ControllerRevision" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.ControllerRevision", + "method": "PUT", + "summary": "replace the specified ControllerRevision", + "nickname": "replaceNamespacedControllerRevision", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.ControllerRevision", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the ControllerRevision", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.ControllerRevision" + }, + { + "code": 201, + "message": "Created", + "responseModel": "v1.ControllerRevision" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.ControllerRevision", + "method": "PATCH", + "summary": "partially update the specified ControllerRevision", + "nickname": "patchNamespacedControllerRevision", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.Patch", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the ControllerRevision", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.ControllerRevision" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ] + }, + { + "type": "v1.Status", + "method": "DELETE", + "summary": "delete a ControllerRevision", + "nickname": "deleteNamespacedControllerRevision", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.DeleteOptions", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the ControllerRevision", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Status" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/apps/v1/watch/namespaces/{namespace}/controllerrevisions/{name}", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.WatchEvent", + "method": "GET", + "summary": "watch changes to an object of kind ControllerRevision", + "nickname": "watchNamespacedControllerRevision", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the ControllerRevision", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.WatchEvent" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/apps/v1/controllerrevisions", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.ControllerRevisionList", + "method": "GET", + "summary": "list or watch objects of kind ControllerRevision", + "nickname": "listControllerRevisionForAllNamespaces", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.ControllerRevisionList" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/apps/v1/watch/controllerrevisions", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.WatchEvent", + "method": "GET", + "summary": "watch individual changes to a list of ControllerRevision", + "nickname": "watchControllerRevisionListForAllNamespaces", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.WatchEvent" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, { "path": "/apis/apps/v1/namespaces/{namespace}/daemonsets", "description": "API at /apis/apps/v1", @@ -616,7 +1588,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -1150,6 +2122,3942 @@ } ] }, + { + "path": "/apis/apps/v1/namespaces/{namespace}/deployments", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.DeploymentList", + "method": "GET", + "summary": "list or watch objects of kind Deployment", + "nickname": "listNamespacedDeployment", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.DeploymentList" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.Deployment", + "method": "POST", + "summary": "create a Deployment", + "nickname": "createNamespacedDeployment", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.Deployment", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Deployment" + }, + { + "code": 201, + "message": "Created", + "responseModel": "v1.Deployment" + }, + { + "code": 202, + "message": "Accepted", + "responseModel": "v1.Deployment" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.Status", + "method": "DELETE", + "summary": "delete collection of Deployment", + "nickname": "deletecollectionNamespacedDeployment", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Status" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/apps/v1/watch/namespaces/{namespace}/deployments", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.WatchEvent", + "method": "GET", + "summary": "watch individual changes to a list of Deployment", + "nickname": "watchNamespacedDeploymentList", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.WatchEvent" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/apps/v1/namespaces/{namespace}/deployments/{name}", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.Deployment", + "method": "GET", + "summary": "read the specified Deployment", + "nickname": "readNamespacedDeployment", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "export", + "description": "Should this value be exported. Export strips fields that a user can not specify.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "exact", + "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the Deployment", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Deployment" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.Deployment", + "method": "PUT", + "summary": "replace the specified Deployment", + "nickname": "replaceNamespacedDeployment", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.Deployment", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the Deployment", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Deployment" + }, + { + "code": 201, + "message": "Created", + "responseModel": "v1.Deployment" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.Deployment", + "method": "PATCH", + "summary": "partially update the specified Deployment", + "nickname": "patchNamespacedDeployment", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.Patch", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the Deployment", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Deployment" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ] + }, + { + "type": "v1.Status", + "method": "DELETE", + "summary": "delete a Deployment", + "nickname": "deleteNamespacedDeployment", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.DeleteOptions", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the Deployment", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Status" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/apps/v1/watch/namespaces/{namespace}/deployments/{name}", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.WatchEvent", + "method": "GET", + "summary": "watch changes to an object of kind Deployment", + "nickname": "watchNamespacedDeployment", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the Deployment", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.WatchEvent" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/apps/v1/deployments", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.DeploymentList", + "method": "GET", + "summary": "list or watch objects of kind Deployment", + "nickname": "listDeploymentForAllNamespaces", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.DeploymentList" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/apps/v1/watch/deployments", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.WatchEvent", + "method": "GET", + "summary": "watch individual changes to a list of Deployment", + "nickname": "watchDeploymentListForAllNamespaces", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.WatchEvent" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/apps/v1/namespaces/{namespace}/deployments/{name}/scale", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.Scale", + "method": "GET", + "summary": "read scale of the specified Deployment", + "nickname": "readNamespacedDeploymentScale", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the Scale", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Scale" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.Scale", + "method": "PUT", + "summary": "replace scale of the specified Deployment", + "nickname": "replaceNamespacedDeploymentScale", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.Scale", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the Scale", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Scale" + }, + { + "code": 201, + "message": "Created", + "responseModel": "v1.Scale" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.Scale", + "method": "PATCH", + "summary": "partially update scale of the specified Deployment", + "nickname": "patchNamespacedDeploymentScale", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.Patch", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the Scale", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Scale" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ] + } + ] + }, + { + "path": "/apis/apps/v1/namespaces/{namespace}/deployments/{name}/status", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.Deployment", + "method": "GET", + "summary": "read status of the specified Deployment", + "nickname": "readNamespacedDeploymentStatus", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the Deployment", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Deployment" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.Deployment", + "method": "PUT", + "summary": "replace status of the specified Deployment", + "nickname": "replaceNamespacedDeploymentStatus", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.Deployment", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the Deployment", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Deployment" + }, + { + "code": 201, + "message": "Created", + "responseModel": "v1.Deployment" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.Deployment", + "method": "PATCH", + "summary": "partially update status of the specified Deployment", + "nickname": "patchNamespacedDeploymentStatus", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.Patch", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the Deployment", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Deployment" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ] + } + ] + }, + { + "path": "/apis/apps/v1/namespaces/{namespace}/replicasets", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.ReplicaSetList", + "method": "GET", + "summary": "list or watch objects of kind ReplicaSet", + "nickname": "listNamespacedReplicaSet", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.ReplicaSetList" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.ReplicaSet", + "method": "POST", + "summary": "create a ReplicaSet", + "nickname": "createNamespacedReplicaSet", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.ReplicaSet", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.ReplicaSet" + }, + { + "code": 201, + "message": "Created", + "responseModel": "v1.ReplicaSet" + }, + { + "code": 202, + "message": "Accepted", + "responseModel": "v1.ReplicaSet" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.Status", + "method": "DELETE", + "summary": "delete collection of ReplicaSet", + "nickname": "deletecollectionNamespacedReplicaSet", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Status" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/apps/v1/watch/namespaces/{namespace}/replicasets", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.WatchEvent", + "method": "GET", + "summary": "watch individual changes to a list of ReplicaSet", + "nickname": "watchNamespacedReplicaSetList", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.WatchEvent" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/apps/v1/namespaces/{namespace}/replicasets/{name}", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.ReplicaSet", + "method": "GET", + "summary": "read the specified ReplicaSet", + "nickname": "readNamespacedReplicaSet", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "export", + "description": "Should this value be exported. Export strips fields that a user can not specify.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "exact", + "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the ReplicaSet", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.ReplicaSet" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.ReplicaSet", + "method": "PUT", + "summary": "replace the specified ReplicaSet", + "nickname": "replaceNamespacedReplicaSet", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.ReplicaSet", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the ReplicaSet", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.ReplicaSet" + }, + { + "code": 201, + "message": "Created", + "responseModel": "v1.ReplicaSet" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.ReplicaSet", + "method": "PATCH", + "summary": "partially update the specified ReplicaSet", + "nickname": "patchNamespacedReplicaSet", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.Patch", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the ReplicaSet", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.ReplicaSet" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ] + }, + { + "type": "v1.Status", + "method": "DELETE", + "summary": "delete a ReplicaSet", + "nickname": "deleteNamespacedReplicaSet", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.DeleteOptions", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the ReplicaSet", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Status" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/apps/v1/watch/namespaces/{namespace}/replicasets/{name}", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.WatchEvent", + "method": "GET", + "summary": "watch changes to an object of kind ReplicaSet", + "nickname": "watchNamespacedReplicaSet", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the ReplicaSet", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.WatchEvent" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/apps/v1/replicasets", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.ReplicaSetList", + "method": "GET", + "summary": "list or watch objects of kind ReplicaSet", + "nickname": "listReplicaSetForAllNamespaces", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.ReplicaSetList" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/apps/v1/watch/replicasets", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.WatchEvent", + "method": "GET", + "summary": "watch individual changes to a list of ReplicaSet", + "nickname": "watchReplicaSetListForAllNamespaces", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.WatchEvent" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/apps/v1/namespaces/{namespace}/replicasets/{name}/scale", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.Scale", + "method": "GET", + "summary": "read scale of the specified ReplicaSet", + "nickname": "readNamespacedReplicaSetScale", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the Scale", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Scale" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.Scale", + "method": "PUT", + "summary": "replace scale of the specified ReplicaSet", + "nickname": "replaceNamespacedReplicaSetScale", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.Scale", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the Scale", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Scale" + }, + { + "code": 201, + "message": "Created", + "responseModel": "v1.Scale" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.Scale", + "method": "PATCH", + "summary": "partially update scale of the specified ReplicaSet", + "nickname": "patchNamespacedReplicaSetScale", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.Patch", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the Scale", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Scale" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ] + } + ] + }, + { + "path": "/apis/apps/v1/namespaces/{namespace}/replicasets/{name}/status", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.ReplicaSet", + "method": "GET", + "summary": "read status of the specified ReplicaSet", + "nickname": "readNamespacedReplicaSetStatus", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the ReplicaSet", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.ReplicaSet" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.ReplicaSet", + "method": "PUT", + "summary": "replace status of the specified ReplicaSet", + "nickname": "replaceNamespacedReplicaSetStatus", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.ReplicaSet", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the ReplicaSet", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.ReplicaSet" + }, + { + "code": 201, + "message": "Created", + "responseModel": "v1.ReplicaSet" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.ReplicaSet", + "method": "PATCH", + "summary": "partially update status of the specified ReplicaSet", + "nickname": "patchNamespacedReplicaSetStatus", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.Patch", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the ReplicaSet", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.ReplicaSet" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ] + } + ] + }, + { + "path": "/apis/apps/v1/namespaces/{namespace}/statefulsets", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.StatefulSetList", + "method": "GET", + "summary": "list or watch objects of kind StatefulSet", + "nickname": "listNamespacedStatefulSet", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.StatefulSetList" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.StatefulSet", + "method": "POST", + "summary": "create a StatefulSet", + "nickname": "createNamespacedStatefulSet", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.StatefulSet", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.StatefulSet" + }, + { + "code": 201, + "message": "Created", + "responseModel": "v1.StatefulSet" + }, + { + "code": 202, + "message": "Accepted", + "responseModel": "v1.StatefulSet" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.Status", + "method": "DELETE", + "summary": "delete collection of StatefulSet", + "nickname": "deletecollectionNamespacedStatefulSet", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Status" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/apps/v1/watch/namespaces/{namespace}/statefulsets", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.WatchEvent", + "method": "GET", + "summary": "watch individual changes to a list of StatefulSet", + "nickname": "watchNamespacedStatefulSetList", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.WatchEvent" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/apps/v1/namespaces/{namespace}/statefulsets/{name}", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.StatefulSet", + "method": "GET", + "summary": "read the specified StatefulSet", + "nickname": "readNamespacedStatefulSet", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "export", + "description": "Should this value be exported. Export strips fields that a user can not specify.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "exact", + "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the StatefulSet", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.StatefulSet" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.StatefulSet", + "method": "PUT", + "summary": "replace the specified StatefulSet", + "nickname": "replaceNamespacedStatefulSet", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.StatefulSet", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the StatefulSet", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.StatefulSet" + }, + { + "code": 201, + "message": "Created", + "responseModel": "v1.StatefulSet" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.StatefulSet", + "method": "PATCH", + "summary": "partially update the specified StatefulSet", + "nickname": "patchNamespacedStatefulSet", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.Patch", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the StatefulSet", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.StatefulSet" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ] + }, + { + "type": "v1.Status", + "method": "DELETE", + "summary": "delete a StatefulSet", + "nickname": "deleteNamespacedStatefulSet", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.DeleteOptions", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the StatefulSet", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Status" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/apps/v1/watch/namespaces/{namespace}/statefulsets/{name}", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.WatchEvent", + "method": "GET", + "summary": "watch changes to an object of kind StatefulSet", + "nickname": "watchNamespacedStatefulSet", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the StatefulSet", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.WatchEvent" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/apps/v1/statefulsets", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.StatefulSetList", + "method": "GET", + "summary": "list or watch objects of kind StatefulSet", + "nickname": "listStatefulSetForAllNamespaces", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.StatefulSetList" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/apps/v1/watch/statefulsets", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.WatchEvent", + "method": "GET", + "summary": "watch individual changes to a list of StatefulSet", + "nickname": "watchStatefulSetListForAllNamespaces", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.WatchEvent" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/apps/v1/namespaces/{namespace}/statefulsets/{name}/scale", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.Scale", + "method": "GET", + "summary": "read scale of the specified StatefulSet", + "nickname": "readNamespacedStatefulSetScale", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the Scale", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Scale" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.Scale", + "method": "PUT", + "summary": "replace scale of the specified StatefulSet", + "nickname": "replaceNamespacedStatefulSetScale", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.Scale", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the Scale", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Scale" + }, + { + "code": 201, + "message": "Created", + "responseModel": "v1.Scale" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.Scale", + "method": "PATCH", + "summary": "partially update scale of the specified StatefulSet", + "nickname": "patchNamespacedStatefulSetScale", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.Patch", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the Scale", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Scale" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ] + } + ] + }, + { + "path": "/apis/apps/v1/namespaces/{namespace}/statefulsets/{name}/status", + "description": "API at /apis/apps/v1", + "operations": [ + { + "type": "v1.StatefulSet", + "method": "GET", + "summary": "read status of the specified StatefulSet", + "nickname": "readNamespacedStatefulSetStatus", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the StatefulSet", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.StatefulSet" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.StatefulSet", + "method": "PUT", + "summary": "replace status of the specified StatefulSet", + "nickname": "replaceNamespacedStatefulSetStatus", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.StatefulSet", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the StatefulSet", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.StatefulSet" + }, + { + "code": 201, + "message": "Created", + "responseModel": "v1.StatefulSet" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.StatefulSet", + "method": "PATCH", + "summary": "partially update status of the specified StatefulSet", + "nickname": "patchNamespacedStatefulSetStatus", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.Patch", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the StatefulSet", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.StatefulSet" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ] + } + ] + }, { "path": "/apis/apps/v1", "description": "API at /apis/apps/v1", @@ -1175,9 +6083,9 @@ } ], "models": { - "v1.DaemonSetList": { - "id": "v1.DaemonSetList", - "description": "DaemonSetList is a collection of daemon sets.", + "v1.ControllerRevisionList": { + "id": "v1.ControllerRevisionList", + "description": "ControllerRevisionList is a resource containing a list of ControllerRevision objects.", "required": [ "items" ], @@ -1192,14 +6100,14 @@ }, "metadata": { "$ref": "v1.ListMeta", - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" + "description": "More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" }, "items": { "type": "array", "items": { - "$ref": "v1.DaemonSet" + "$ref": "v1.ControllerRevision" }, - "description": "A list of daemon sets." + "description": "Items is the list of ControllerRevisions" } } }, @@ -1221,9 +6129,12 @@ } } }, - "v1.DaemonSet": { - "id": "v1.DaemonSet", - "description": "DaemonSet represents the configuration of a daemon set.", + "v1.ControllerRevision": { + "id": "v1.ControllerRevision", + "description": "ControllerRevision implements an immutable snapshot of state data. Clients are responsible for serializing and deserializing the objects that contain their internal state. Once a ControllerRevision has been successfully created, it can not be updated. The API Server will fail validation of all requests that attempt to mutate the Data field. ControllerRevisions may, however, be deleted. Note that, due to its use by both the DaemonSet and StatefulSet controllers for update and rollback, this object is beta. However, it may be subject to name and representation changes in future releases, and clients should not depend on its stability. It is primarily for internal use by controllers.", + "required": [ + "revision" + ], "properties": { "kind": { "type": "string", @@ -1237,13 +6148,14 @@ "$ref": "v1.ObjectMeta", "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" }, - "spec": { - "$ref": "v1.DaemonSetSpec", - "description": "The desired behavior of this daemon set. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status" + "data": { + "type": "string", + "description": "Data is the serialized representation of the state." }, - "status": { - "$ref": "v1.DaemonSetStatus", - "description": "The current status of this daemon set. This data may be out of date by some window of time. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status" + "revision": { + "type": "integer", + "format": "int64", + "description": "Revision indicates the revision of the state represented by Data." } } }, @@ -1286,7 +6198,7 @@ }, "deletionTimestamp": { "type": "string", - "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 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/api-conventions.md#metadata" + "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/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "type": "integer", @@ -1485,16 +6397,140 @@ } } }, + "v1.WatchEvent": { + "id": "v1.WatchEvent", + "required": [ + "type", + "object" + ], + "properties": { + "type": { + "type": "string" + }, + "object": { + "type": "string" + } + } + }, + "v1.Patch": { + "id": "v1.Patch", + "description": "Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.", + "properties": {} + }, + "v1.DeleteOptions": { + "id": "v1.DeleteOptions", + "description": "DeleteOptions may be provided when deleting an API object.", + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "gracePeriodSeconds": { + "type": "integer", + "format": "int64", + "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." + }, + "preconditions": { + "$ref": "v1.Preconditions", + "description": "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned." + }, + "orphanDependents": { + "type": "boolean", + "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." + }, + "propagationPolicy": { + "$ref": "v1.DeletionPropagation", + "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." + } + } + }, + "v1.Preconditions": { + "id": "v1.Preconditions", + "description": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", + "properties": { + "uid": { + "$ref": "types.UID", + "description": "Specifies the target UID." + } + } + }, + "types.UID": { + "id": "types.UID", + "properties": {} + }, + "v1.DeletionPropagation": { + "id": "v1.DeletionPropagation", + "properties": {} + }, + "v1.DaemonSetList": { + "id": "v1.DaemonSetList", + "description": "DaemonSetList is a collection of daemon sets.", + "required": [ + "items" + ], + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "metadata": { + "$ref": "v1.ListMeta", + "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" + }, + "items": { + "type": "array", + "items": { + "$ref": "v1.DaemonSet" + }, + "description": "A list of daemon sets." + } + } + }, + "v1.DaemonSet": { + "id": "v1.DaemonSet", + "description": "DaemonSet represents the configuration of a daemon set.", + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "metadata": { + "$ref": "v1.ObjectMeta", + "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" + }, + "spec": { + "$ref": "v1.DaemonSetSpec", + "description": "The desired behavior of this daemon set. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status" + }, + "status": { + "$ref": "v1.DaemonSetStatus", + "description": "The current status of this daemon set. This data may be out of date by some window of time. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status" + } + } + }, "v1.DaemonSetSpec": { "id": "v1.DaemonSetSpec", "description": "DaemonSetSpec is the specification of a daemon set.", "required": [ + "selector", "template" ], "properties": { "selector": { "$ref": "v1.LabelSelector", - "description": "A label query over pods that are managed by the daemon set. Must match in order to be controlled. If empty, defaulted to labels on Pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors" + "description": "A label query over pods that are managed by the daemon set. Must match in order to be controlled. It must match the pod template's labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors" }, "template": { "$ref": "v1.PodTemplateSpec", @@ -1616,7 +6652,7 @@ }, "dnsPolicy": { "type": "string", - "description": "Set DNS policy for containers within the pod. One of 'ClusterFirstWithHostNet', 'ClusterFirst' or 'Default'. Defaults to \"ClusterFirst\". To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'." + "description": "Set DNS policy for the pod. Defaults to \"ClusterFirst\". Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'. Note that 'None' policy is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it." }, "nodeSelector": { "type": "object", @@ -1699,6 +6735,10 @@ "type": "integer", "format": "int32", "description": "The priority value. Various system components use this field to find the priority of the pod. 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." + }, + "dnsConfig": { + "$ref": "v1.PodDNSConfig", + "description": "Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy. This is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it." } } }, @@ -1759,7 +6799,7 @@ }, "flexVolume": { "$ref": "v1.FlexVolumeSource", - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future." + "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin." }, "cinder": { "$ref": "v1.CinderVolumeSource", @@ -2013,7 +7053,7 @@ "properties": { "targetPortal": { "type": "string", - "description": "iSCSI target portal. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." + "description": "iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." }, "iqn": { "type": "string", @@ -2022,11 +7062,11 @@ "lun": { "type": "integer", "format": "int32", - "description": "iSCSI target lun number." + "description": "iSCSI Target Lun number." }, "iscsiInterface": { "type": "string", - "description": "Optional: Defaults to 'default' (tcp). iSCSI interface name that uses an iSCSI transport." + "description": "iSCSI Interface Name that uses an iSCSI transport. Defaults to 'default' (tcp)." }, "fsType": { "type": "string", @@ -2041,7 +7081,7 @@ "items": { "type": "string" }, - "description": "iSCSI target portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." + "description": "iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." }, "chapAuthDiscovery": { "type": "boolean", @@ -2053,11 +7093,11 @@ }, "secretRef": { "$ref": "v1.LocalObjectReference", - "description": "CHAP secret for iSCSI target and initiator authentication" + "description": "CHAP Secret for iSCSI target and initiator authentication" }, "initiatorName": { "type": "string", - "description": "Custom iSCSI initiator name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface \u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection." + "description": "Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface \u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection." } } }, @@ -2157,7 +7197,7 @@ }, "v1.FlexVolumeSource": { "id": "v1.FlexVolumeSource", - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.", + "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.", "required": [ "driver" ], @@ -2674,15 +7714,15 @@ }, "protectionDomain": { "type": "string", - "description": "The name of the Protection Domain for the configured storage (defaults to \"default\")." + "description": "The name of the ScaleIO Protection Domain for the configured storage." }, "storagePool": { "type": "string", - "description": "The Storage Pool associated with the protection domain (defaults to \"default\")." + "description": "The ScaleIO Storage Pool associated with the protection domain." }, "storageMode": { "type": "string", - "description": "Indicates whether the storage for a volume should be thick or thin (defaults to \"thin\")." + "description": "Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned." }, "volumeName": { "type": "string", @@ -2789,6 +7829,13 @@ }, "description": "Pod volumes to mount into the container's filesystem. Cannot be updated." }, + "volumeDevices": { + "type": "array", + "items": { + "$ref": "v1.VolumeDevice" + }, + "description": "volumeDevices is the list of block devices to be used by the container. This is an alpha feature and may change in the future." + }, "livenessProbe": { "$ref": "v1.Probe", "description": "Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes" @@ -2815,7 +7862,7 @@ }, "securityContext": { "$ref": "v1.SecurityContext", - "description": "Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md" + "description": "Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/" }, "stdin": { "type": "boolean", @@ -3041,6 +8088,24 @@ "id": "v1.MountPropagationMode", "properties": {} }, + "v1.VolumeDevice": { + "id": "v1.VolumeDevice", + "description": "volumeDevice describes a mapping of a raw block device within a container.", + "required": [ + "name", + "devicePath" + ], + "properties": { + "name": { + "type": "string", + "description": "name must match the name of a persistentVolumeClaim in the pod" + }, + "devicePath": { + "type": "string", + "description": "devicePath is the path inside of the container that the device will be mapped to." + } + } + }, "v1.Probe": { "id": "v1.Probe", "description": "Probe describes a health check to be performed against a container to determine whether it is alive or ready to receive traffic.", @@ -3441,7 +8506,10 @@ }, "v1.PodAffinityTerm": { "id": "v1.PodAffinityTerm", - "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key \u003ctopologyKey\u003e tches that of any node on which a pod of the set of pods is running", + "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key \u003ctopologyKey\u003e matches that of any node on which a pod of the set of pods is running", + "required": [ + "topologyKey" + ], "properties": { "labelSelector": { "$ref": "v1.LabelSelector", @@ -3456,7 +8524,7 @@ }, "topologyKey": { "type": "string", - "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. For PreferredDuringScheduling pod anti-affinity, empty topologyKey is interpreted as \"all topologies\" (\"all topologies\" here means all the topologyKeys indicated by scheduler command-line argument --failure-domains); for affinity and for RequiredDuringScheduling pod anti-affinity, empty topologyKey is not allowed." + "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed." } } }, @@ -3543,6 +8611,46 @@ } } }, + "v1.PodDNSConfig": { + "id": "v1.PodDNSConfig", + "description": "PodDNSConfig defines the DNS parameters of a pod in addition to those generated from DNSPolicy.", + "properties": { + "nameservers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of DNS name server IP addresses. This will be appended to the base nameservers generated from DNSPolicy. Duplicated nameservers will be removed." + }, + "searches": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of DNS search domains for host-name lookup. This will be appended to the base search paths generated from DNSPolicy. Duplicated search paths will be removed." + }, + "options": { + "type": "array", + "items": { + "$ref": "v1.PodDNSConfigOption" + }, + "description": "A list of DNS resolver options. This will be merged with the base options generated from DNSPolicy. Duplicated entries will be removed. Resolution options given in Options will override those that appear in the base DNSPolicy." + } + } + }, + "v1.PodDNSConfigOption": { + "id": "v1.PodDNSConfigOption", + "description": "PodDNSConfigOption defines DNS resolver options of a pod.", + "properties": { + "name": { + "type": "string", + "description": "Required." + }, + "value": { + "type": "string" + } + } + }, "v1.DaemonSetUpdateStrategy": { "id": "v1.DaemonSetUpdateStrategy", "description": "DaemonSetUpdateStrategy is a struct used to control the update strategy for a DaemonSet.", @@ -3621,32 +8729,52 @@ "type": "integer", "format": "int32", "description": "Count of hash collisions for the DaemonSet. The DaemonSet controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ControllerRevision." + }, + "conditions": { + "type": "array", + "items": { + "$ref": "v1.DaemonSetCondition" + }, + "description": "Represents the latest available observations of a DaemonSet's current state." } } }, - "v1.WatchEvent": { - "id": "v1.WatchEvent", + "v1.DaemonSetCondition": { + "id": "v1.DaemonSetCondition", + "description": "DaemonSetCondition describes the state of a DaemonSet at a certain point.", "required": [ "type", - "object" + "status" ], "properties": { "type": { - "type": "string" + "type": "string", + "description": "Type of DaemonSet condition." }, - "object": { - "type": "string" + "status": { + "type": "string", + "description": "Status of the condition, one of True, False, Unknown." + }, + "lastTransitionTime": { + "type": "string", + "description": "Last time the condition transitioned from one status to another." + }, + "reason": { + "type": "string", + "description": "The reason for the condition's last transition." + }, + "message": { + "type": "string", + "description": "A human readable message indicating details about the transition." } } }, - "v1.Patch": { - "id": "v1.Patch", - "description": "Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.", - "properties": {} - }, - "v1.DeleteOptions": { - "id": "v1.DeleteOptions", - "description": "DeleteOptions may be provided when deleting an API object.", + "v1.DeploymentList": { + "id": "v1.DeploymentList", + "description": "DeploymentList is a list of Deployments.", + "required": [ + "items" + ], "properties": { "kind": { "type": "string", @@ -3656,43 +8784,743 @@ "type": "string", "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/api-conventions.md#resources" }, - "gracePeriodSeconds": { + "metadata": { + "$ref": "v1.ListMeta", + "description": "Standard list metadata." + }, + "items": { + "type": "array", + "items": { + "$ref": "v1.Deployment" + }, + "description": "Items is the list of Deployments." + } + } + }, + "v1.Deployment": { + "id": "v1.Deployment", + "description": "Deployment enables declarative updates for Pods and ReplicaSets.", + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "metadata": { + "$ref": "v1.ObjectMeta", + "description": "Standard object metadata." + }, + "spec": { + "$ref": "v1.DeploymentSpec", + "description": "Specification of the desired behavior of the Deployment." + }, + "status": { + "$ref": "v1.DeploymentStatus", + "description": "Most recently observed status of the Deployment." + } + } + }, + "v1.DeploymentSpec": { + "id": "v1.DeploymentSpec", + "description": "DeploymentSpec is the specification of the desired behavior of the Deployment.", + "required": [ + "selector", + "template" + ], + "properties": { + "replicas": { + "type": "integer", + "format": "int32", + "description": "Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1." + }, + "selector": { + "$ref": "v1.LabelSelector", + "description": "Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment. It must match the pod template's labels." + }, + "template": { + "$ref": "v1.PodTemplateSpec", + "description": "Template describes the pods that will be created." + }, + "strategy": { + "$ref": "v1.DeploymentStrategy", + "description": "The deployment strategy to use to replace existing pods with new ones." + }, + "minReadySeconds": { + "type": "integer", + "format": "int32", + "description": "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)" + }, + "revisionHistoryLimit": { + "type": "integer", + "format": "int32", + "description": "The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10." + }, + "paused": { + "type": "boolean", + "description": "Indicates that the deployment is paused." + }, + "progressDeadlineSeconds": { + "type": "integer", + "format": "int32", + "description": "The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that progress will not be estimated during the time a deployment is paused. Defaults to 600s." + } + } + }, + "v1.DeploymentStrategy": { + "id": "v1.DeploymentStrategy", + "description": "DeploymentStrategy describes how to replace existing pods with new ones.", + "properties": { + "type": { + "type": "string", + "description": "Type of deployment. Can be \"Recreate\" or \"RollingUpdate\". Default is RollingUpdate." + }, + "rollingUpdate": { + "$ref": "v1.RollingUpdateDeployment", + "description": "Rolling update config params. Present only if DeploymentStrategyType = RollingUpdate." + } + } + }, + "v1.RollingUpdateDeployment": { + "id": "v1.RollingUpdateDeployment", + "description": "Spec to control the desired behavior of rolling update.", + "properties": { + "maxUnavailable": { + "type": "string", + "description": "The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding down. This can not be 0 if MaxSurge is 0. Defaults to 25%. Example: when this is set to 30%, the old RC can be scaled down to 70% of desired pods immediately when the rolling update starts. Once new pods are ready, old RC can be scaled down further, followed by scaling up the new RC, ensuring that the total number of pods available at all times during the update is at least 70% of desired pods." + }, + "maxSurge": { + "type": "string", + "description": "The maximum number of pods that can be scheduled above the desired number of pods. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up. Defaults to 25%. Example: when this is set to 30%, the new RC can be scaled up immediately when the rolling update starts, such that the total number of old and new pods do not exceed 130% of desired pods. Once old pods have been killed, new RC can be scaled up further, ensuring that total number of pods running at any time during the update is at most 130% of desired pods." + } + } + }, + "v1.DeploymentStatus": { + "id": "v1.DeploymentStatus", + "description": "DeploymentStatus is the most recently observed status of the Deployment.", + "properties": { + "observedGeneration": { "type": "integer", "format": "int64", - "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." + "description": "The generation observed by the deployment controller." }, - "preconditions": { - "$ref": "v1.Preconditions", - "description": "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned." + "replicas": { + "type": "integer", + "format": "int32", + "description": "Total number of non-terminated pods targeted by this deployment (their labels match the selector)." }, - "orphanDependents": { - "type": "boolean", - "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." + "updatedReplicas": { + "type": "integer", + "format": "int32", + "description": "Total number of non-terminated pods targeted by this deployment that have the desired template spec." }, - "propagationPolicy": { - "$ref": "v1.DeletionPropagation", - "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." + "readyReplicas": { + "type": "integer", + "format": "int32", + "description": "Total number of ready pods targeted by this deployment." + }, + "availableReplicas": { + "type": "integer", + "format": "int32", + "description": "Total number of available pods (ready for at least minReadySeconds) targeted by this deployment." + }, + "unavailableReplicas": { + "type": "integer", + "format": "int32", + "description": "Total number of unavailable pods targeted by this deployment. This is the total number of pods that are still required for the deployment to have 100% available capacity. They may either be pods that are running but not yet available or pods that still have not been created." + }, + "conditions": { + "type": "array", + "items": { + "$ref": "v1.DeploymentCondition" + }, + "description": "Represents the latest available observations of a deployment's current state." + }, + "collisionCount": { + "type": "integer", + "format": "int32", + "description": "Count of hash collisions for the Deployment. The Deployment controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ReplicaSet." } } }, - "v1.Preconditions": { - "id": "v1.Preconditions", - "description": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", + "v1.DeploymentCondition": { + "id": "v1.DeploymentCondition", + "description": "DeploymentCondition describes the state of a deployment at a certain point.", + "required": [ + "type", + "status" + ], "properties": { - "uid": { - "$ref": "types.UID", - "description": "Specifies the target UID." + "type": { + "type": "string", + "description": "Type of deployment condition." + }, + "status": { + "type": "string", + "description": "Status of the condition, one of True, False, Unknown." + }, + "lastUpdateTime": { + "type": "string", + "description": "The last time this condition was updated." + }, + "lastTransitionTime": { + "type": "string", + "description": "Last time the condition transitioned from one status to another." + }, + "reason": { + "type": "string", + "description": "The reason for the condition's last transition." + }, + "message": { + "type": "string", + "description": "A human readable message indicating details about the transition." } } }, - "types.UID": { - "id": "types.UID", + "v1.Scale": { + "id": "v1.Scale", + "description": "Scale represents a scaling request for a resource.", + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "metadata": { + "$ref": "v1.ObjectMeta", + "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata." + }, + "spec": { + "$ref": "v1.ScaleSpec", + "description": "defines the behavior of the scale. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status." + }, + "status": { + "$ref": "v1.ScaleStatus", + "description": "current status of the scale. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status. Read-only." + } + } + }, + "v1.ScaleSpec": { + "id": "v1.ScaleSpec", + "description": "ScaleSpec describes the attributes of a scale subresource.", + "properties": { + "replicas": { + "type": "integer", + "format": "int32", + "description": "desired number of instances for the scaled object." + } + } + }, + "v1.ScaleStatus": { + "id": "v1.ScaleStatus", + "description": "ScaleStatus represents the current status of a scale subresource.", + "required": [ + "replicas" + ], + "properties": { + "replicas": { + "type": "integer", + "format": "int32", + "description": "actual number of observed instances of the scaled object." + }, + "selector": { + "type": "string", + "description": "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: http://kubernetes.io/docs/user-guide/labels#label-selectors" + } + } + }, + "v1.ReplicaSetList": { + "id": "v1.ReplicaSetList", + "description": "ReplicaSetList is a collection of ReplicaSets.", + "required": [ + "items" + ], + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "metadata": { + "$ref": "v1.ListMeta", + "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds" + }, + "items": { + "type": "array", + "items": { + "$ref": "v1.ReplicaSet" + }, + "description": "List of ReplicaSets. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller" + } + } + }, + "v1.ReplicaSet": { + "id": "v1.ReplicaSet", + "description": "ReplicaSet ensures that a specified number of pod replicas are running at any given time.", + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "metadata": { + "$ref": "v1.ObjectMeta", + "description": "If the Labels of a ReplicaSet are empty, they are defaulted to be the same as the Pod(s) that the ReplicaSet manages. Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" + }, + "spec": { + "$ref": "v1.ReplicaSetSpec", + "description": "Spec defines the specification of the desired behavior of the ReplicaSet. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status" + }, + "status": { + "$ref": "v1.ReplicaSetStatus", + "description": "Status is the most recently observed status of the ReplicaSet. This data may be out of date by some window of time. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status" + } + } + }, + "v1.ReplicaSetSpec": { + "id": "v1.ReplicaSetSpec", + "description": "ReplicaSetSpec is the specification of a ReplicaSet.", + "required": [ + "selector" + ], + "properties": { + "replicas": { + "type": "integer", + "format": "int32", + "description": "Replicas is the number of desired replicas. This is a pointer to distinguish between explicit zero and unspecified. Defaults to 1. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller" + }, + "minReadySeconds": { + "type": "integer", + "format": "int32", + "description": "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)" + }, + "selector": { + "$ref": "v1.LabelSelector", + "description": "Selector is a label query over pods that should match the replica count. Label keys and values that must match in order to be controlled by this replica set. It must match the pod template's labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors" + }, + "template": { + "$ref": "v1.PodTemplateSpec", + "description": "Template is the object that describes the pod that will be created if insufficient replicas are detected. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template" + } + } + }, + "v1.ReplicaSetStatus": { + "id": "v1.ReplicaSetStatus", + "description": "ReplicaSetStatus represents the current status of a ReplicaSet.", + "required": [ + "replicas" + ], + "properties": { + "replicas": { + "type": "integer", + "format": "int32", + "description": "Replicas is the most recently oberved number of replicas. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller" + }, + "fullyLabeledReplicas": { + "type": "integer", + "format": "int32", + "description": "The number of pods that have labels matching the labels of the pod template of the replicaset." + }, + "readyReplicas": { + "type": "integer", + "format": "int32", + "description": "The number of ready replicas for this replica set." + }, + "availableReplicas": { + "type": "integer", + "format": "int32", + "description": "The number of available replicas (ready for at least minReadySeconds) for this replica set." + }, + "observedGeneration": { + "type": "integer", + "format": "int64", + "description": "ObservedGeneration reflects the generation of the most recently observed ReplicaSet." + }, + "conditions": { + "type": "array", + "items": { + "$ref": "v1.ReplicaSetCondition" + }, + "description": "Represents the latest available observations of a replica set's current state." + } + } + }, + "v1.ReplicaSetCondition": { + "id": "v1.ReplicaSetCondition", + "description": "ReplicaSetCondition describes the state of a replica set at a certain point.", + "required": [ + "type", + "status" + ], + "properties": { + "type": { + "type": "string", + "description": "Type of replica set condition." + }, + "status": { + "type": "string", + "description": "Status of the condition, one of True, False, Unknown." + }, + "lastTransitionTime": { + "type": "string", + "description": "The last time the condition transitioned from one status to another." + }, + "reason": { + "type": "string", + "description": "The reason for the condition's last transition." + }, + "message": { + "type": "string", + "description": "A human readable message indicating details about the transition." + } + } + }, + "v1.StatefulSetList": { + "id": "v1.StatefulSetList", + "description": "StatefulSetList is a collection of StatefulSets.", + "required": [ + "items" + ], + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "metadata": { + "$ref": "v1.ListMeta" + }, + "items": { + "type": "array", + "items": { + "$ref": "v1.StatefulSet" + } + } + } + }, + "v1.StatefulSet": { + "id": "v1.StatefulSet", + "description": "StatefulSet represents a set of pods with consistent identities. Identities are defined as:\n - Network: A single stable DNS and hostname.\n - Storage: As many VolumeClaims as requested.\nThe StatefulSet guarantees that a given network identity will always map to the same storage identity.", + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "metadata": { + "$ref": "v1.ObjectMeta" + }, + "spec": { + "$ref": "v1.StatefulSetSpec", + "description": "Spec defines the desired identities of pods in this set." + }, + "status": { + "$ref": "v1.StatefulSetStatus", + "description": "Status is the current status of Pods in this StatefulSet. This data may be out of date by some window of time." + } + } + }, + "v1.StatefulSetSpec": { + "id": "v1.StatefulSetSpec", + "description": "A StatefulSetSpec is the specification of a StatefulSet.", + "required": [ + "selector", + "template", + "serviceName" + ], + "properties": { + "replicas": { + "type": "integer", + "format": "int32", + "description": "replicas is the desired number of replicas of the given Template. These are replicas in the sense that they are instantiations of the same Template, but individual replicas also have a consistent identity. If unspecified, defaults to 1." + }, + "selector": { + "$ref": "v1.LabelSelector", + "description": "selector is a label query over pods that should match the replica count. It must match the pod template's labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors" + }, + "template": { + "$ref": "v1.PodTemplateSpec", + "description": "template is the object that describes the pod that will be created if insufficient replicas are detected. Each pod stamped out by the StatefulSet will fulfill this Template, but have a unique identity from the rest of the StatefulSet." + }, + "volumeClaimTemplates": { + "type": "array", + "items": { + "$ref": "v1.PersistentVolumeClaim" + }, + "description": "volumeClaimTemplates is a list of claims that pods are allowed to reference. The StatefulSet controller is responsible for mapping network identities to claims in a way that maintains the identity of a pod. Every claim in this list must have at least one matching (by name) volumeMount in one container in the template. A claim in this list takes precedence over any volumes in the template, with the same name." + }, + "serviceName": { + "type": "string", + "description": "serviceName is the name of the service that governs this StatefulSet. This service must exist before the StatefulSet, and is responsible for the network identity of the set. Pods get DNS/hostnames that follow the pattern: pod-specific-string.serviceName.default.svc.cluster.local where \"pod-specific-string\" is managed by the StatefulSet controller." + }, + "podManagementPolicy": { + "type": "string", + "description": "podManagementPolicy controls how pods are created during initial scale up, when replacing pods on nodes, or when scaling down. The default policy is `OrderedReady`, where pods are created in increasing order (pod-0, then pod-1, etc) and the controller will wait until each pod is ready before continuing. When scaling down, the pods are removed in the opposite order. The alternative policy is `Parallel` which will create pods in parallel to match the desired scale without waiting, and on scale down will delete all pods at once." + }, + "updateStrategy": { + "$ref": "v1.StatefulSetUpdateStrategy", + "description": "updateStrategy indicates the StatefulSetUpdateStrategy that will be employed to update Pods in the StatefulSet when a revision is made to Template." + }, + "revisionHistoryLimit": { + "type": "integer", + "format": "int32", + "description": "revisionHistoryLimit is the maximum number of revisions that will be maintained in the StatefulSet's revision history. The revision history consists of all revisions not represented by a currently applied StatefulSetSpec version. The default value is 10." + } + } + }, + "v1.PersistentVolumeClaim": { + "id": "v1.PersistentVolumeClaim", + "description": "PersistentVolumeClaim is a user's request for and claim to a persistent volume", + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "metadata": { + "$ref": "v1.ObjectMeta", + "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" + }, + "spec": { + "$ref": "v1.PersistentVolumeClaimSpec", + "description": "Spec defines the desired characteristics of a volume requested by a pod author. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims" + }, + "status": { + "$ref": "v1.PersistentVolumeClaimStatus", + "description": "Status represents the current information/status of a persistent volume claim. Read-only. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims" + } + } + }, + "v1.PersistentVolumeClaimSpec": { + "id": "v1.PersistentVolumeClaimSpec", + "description": "PersistentVolumeClaimSpec describes the common attributes of storage devices and allows a Source for provider-specific attributes", + "properties": { + "accessModes": { + "type": "array", + "items": { + "$ref": "v1.PersistentVolumeAccessMode" + }, + "description": "AccessModes contains the desired access modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1" + }, + "selector": { + "$ref": "v1.LabelSelector", + "description": "A label query over volumes to consider for binding." + }, + "resources": { + "$ref": "v1.ResourceRequirements", + "description": "Resources represents the minimum resources the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources" + }, + "volumeName": { + "type": "string", + "description": "VolumeName is the binding reference to the PersistentVolume backing this claim." + }, + "storageClassName": { + "type": "string", + "description": "Name of the StorageClass required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1" + }, + "volumeMode": { + "$ref": "v1.PersistentVolumeMode", + "description": "volumeMode defines what type of volume is required by the claim. Value of Filesystem is implied when not included in claim spec. This is an alpha feature and may change in the future." + } + } + }, + "v1.PersistentVolumeAccessMode": { + "id": "v1.PersistentVolumeAccessMode", "properties": {} }, - "v1.DeletionPropagation": { - "id": "v1.DeletionPropagation", + "v1.PersistentVolumeMode": { + "id": "v1.PersistentVolumeMode", "properties": {} }, + "v1.PersistentVolumeClaimStatus": { + "id": "v1.PersistentVolumeClaimStatus", + "description": "PersistentVolumeClaimStatus is the current status of a persistent volume claim.", + "properties": { + "phase": { + "type": "string", + "description": "Phase represents the current phase of PersistentVolumeClaim." + }, + "accessModes": { + "type": "array", + "items": { + "$ref": "v1.PersistentVolumeAccessMode" + }, + "description": "AccessModes contains the actual access modes the volume backing the PVC has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1" + }, + "capacity": { + "type": "object", + "description": "Represents the actual resources of the underlying volume." + }, + "conditions": { + "type": "array", + "items": { + "$ref": "v1.PersistentVolumeClaimCondition" + }, + "description": "Current Condition of persistent volume claim. If underlying persistent volume is being resized then the Condition will be set to 'ResizeStarted'." + } + } + }, + "v1.PersistentVolumeClaimCondition": { + "id": "v1.PersistentVolumeClaimCondition", + "description": "PersistentVolumeClaimCondition contails details about state of pvc", + "required": [ + "type", + "status" + ], + "properties": { + "type": { + "type": "string" + }, + "status": { + "type": "string" + }, + "lastProbeTime": { + "type": "string", + "description": "Last time we probed the condition." + }, + "lastTransitionTime": { + "type": "string", + "description": "Last time the condition transitioned from one status to another." + }, + "reason": { + "type": "string", + "description": "Unique, this should be a short, machine understandable string that gives the reason for condition's last transition. If it reports \"ResizeStarted\" that means the underlying persistent volume is being resized." + }, + "message": { + "type": "string", + "description": "Human-readable message indicating details about last transition." + } + } + }, + "v1.StatefulSetUpdateStrategy": { + "id": "v1.StatefulSetUpdateStrategy", + "description": "StatefulSetUpdateStrategy indicates the strategy that the StatefulSet controller will use to perform updates. It includes any additional parameters necessary to perform the update for the indicated strategy.", + "properties": { + "type": { + "type": "string", + "description": "Type indicates the type of the StatefulSetUpdateStrategy. Default is RollingUpdate." + }, + "rollingUpdate": { + "$ref": "v1.RollingUpdateStatefulSetStrategy", + "description": "RollingUpdate is used to communicate parameters when Type is RollingUpdateStatefulSetStrategyType." + } + } + }, + "v1.RollingUpdateStatefulSetStrategy": { + "id": "v1.RollingUpdateStatefulSetStrategy", + "description": "RollingUpdateStatefulSetStrategy is used to communicate parameter for RollingUpdateStatefulSetStrategyType.", + "properties": { + "partition": { + "type": "integer", + "format": "int32", + "description": "Partition indicates the ordinal at which the StatefulSet should be partitioned. Default value is 0." + } + } + }, + "v1.StatefulSetStatus": { + "id": "v1.StatefulSetStatus", + "description": "StatefulSetStatus represents the current state of a StatefulSet.", + "required": [ + "replicas" + ], + "properties": { + "observedGeneration": { + "type": "integer", + "format": "int64", + "description": "observedGeneration is the most recent generation observed for this StatefulSet. It corresponds to the StatefulSet's generation, which is updated on mutation by the API Server." + }, + "replicas": { + "type": "integer", + "format": "int32", + "description": "replicas is the number of Pods created by the StatefulSet controller." + }, + "readyReplicas": { + "type": "integer", + "format": "int32", + "description": "readyReplicas is the number of Pods created by the StatefulSet controller that have a Ready Condition." + }, + "currentReplicas": { + "type": "integer", + "format": "int32", + "description": "currentReplicas is the number of Pods created by the StatefulSet controller from the StatefulSet version indicated by currentRevision." + }, + "updatedReplicas": { + "type": "integer", + "format": "int32", + "description": "updatedReplicas is the number of Pods created by the StatefulSet controller from the StatefulSet version indicated by updateRevision." + }, + "currentRevision": { + "type": "string", + "description": "currentRevision, if not empty, indicates the version of the StatefulSet used to generate Pods in the sequence [0,currentReplicas)." + }, + "updateRevision": { + "type": "string", + "description": "updateRevision, if not empty, indicates the version of the StatefulSet used to generate Pods in the sequence [replicas-updatedReplicas,replicas)" + }, + "collisionCount": { + "type": "integer", + "format": "int32", + "description": "collisionCount is the count of hash collisions for the StatefulSet. The StatefulSet controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ControllerRevision." + }, + "conditions": { + "type": "array", + "items": { + "$ref": "v1.StatefulSetCondition" + }, + "description": "Represents the latest available observations of a statefulset's current state." + } + } + }, + "v1.StatefulSetCondition": { + "id": "v1.StatefulSetCondition", + "description": "StatefulSetCondition describes the state of a statefulset at a certain point.", + "required": [ + "type", + "status" + ], + "properties": { + "type": { + "type": "string", + "description": "Type of statefulset condition." + }, + "status": { + "type": "string", + "description": "Status of the condition, one of True, False, Unknown." + }, + "lastTransitionTime": { + "type": "string", + "description": "Last time the condition transitioned from one status to another." + }, + "reason": { + "type": "string", + "description": "The reason for the condition's last transition." + }, + "message": { + "type": "string", + "description": "A human readable message indicating details about the transition." + } + } + }, "v1.APIResourceList": { "id": "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.", diff --git a/api/swagger-spec/apps_v1beta1.json b/api/swagger-spec/apps_v1beta1.json index 3972b3f658a..e2533176f49 100644 --- a/api/swagger-spec/apps_v1beta1.json +++ b/api/swagger-spec/apps_v1beta1.json @@ -616,7 +616,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -1588,7 +1588,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -2971,7 +2971,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -3815,7 +3815,7 @@ }, "deletionTimestamp": { "type": "string", - "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 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/api-conventions.md#metadata" + "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/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "type": "integer", @@ -4061,7 +4061,7 @@ }, "propagationPolicy": { "$ref": "v1.DeletionPropagation", - "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." + "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." } } }, @@ -4286,7 +4286,7 @@ }, "dnsPolicy": { "type": "string", - "description": "Set DNS policy for containers within the pod. One of 'ClusterFirstWithHostNet', 'ClusterFirst' or 'Default'. Defaults to \"ClusterFirst\". To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'." + "description": "Set DNS policy for the pod. Defaults to \"ClusterFirst\". Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'. Note that 'None' policy is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it." }, "nodeSelector": { "type": "object", @@ -4369,6 +4369,10 @@ "type": "integer", "format": "int32", "description": "The priority value. Various system components use this field to find the priority of the pod. 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." + }, + "dnsConfig": { + "$ref": "v1.PodDNSConfig", + "description": "Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy. This is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it." } } }, @@ -4429,7 +4433,7 @@ }, "flexVolume": { "$ref": "v1.FlexVolumeSource", - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future." + "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin." }, "cinder": { "$ref": "v1.CinderVolumeSource", @@ -4683,7 +4687,7 @@ "properties": { "targetPortal": { "type": "string", - "description": "iSCSI target portal. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." + "description": "iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." }, "iqn": { "type": "string", @@ -4692,11 +4696,11 @@ "lun": { "type": "integer", "format": "int32", - "description": "iSCSI target lun number." + "description": "iSCSI Target Lun number." }, "iscsiInterface": { "type": "string", - "description": "Optional: Defaults to 'default' (tcp). iSCSI interface name that uses an iSCSI transport." + "description": "iSCSI Interface Name that uses an iSCSI transport. Defaults to 'default' (tcp)." }, "fsType": { "type": "string", @@ -4711,7 +4715,7 @@ "items": { "type": "string" }, - "description": "iSCSI target portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." + "description": "iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." }, "chapAuthDiscovery": { "type": "boolean", @@ -4723,11 +4727,11 @@ }, "secretRef": { "$ref": "v1.LocalObjectReference", - "description": "CHAP secret for iSCSI target and initiator authentication" + "description": "CHAP Secret for iSCSI target and initiator authentication" }, "initiatorName": { "type": "string", - "description": "Custom iSCSI initiator name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface \u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection." + "description": "Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface \u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection." } } }, @@ -4827,7 +4831,7 @@ }, "v1.FlexVolumeSource": { "id": "v1.FlexVolumeSource", - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.", + "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.", "required": [ "driver" ], @@ -5344,15 +5348,15 @@ }, "protectionDomain": { "type": "string", - "description": "The name of the Protection Domain for the configured storage (defaults to \"default\")." + "description": "The name of the ScaleIO Protection Domain for the configured storage." }, "storagePool": { "type": "string", - "description": "The Storage Pool associated with the protection domain (defaults to \"default\")." + "description": "The ScaleIO Storage Pool associated with the protection domain." }, "storageMode": { "type": "string", - "description": "Indicates whether the storage for a volume should be thick or thin (defaults to \"thin\")." + "description": "Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned." }, "volumeName": { "type": "string", @@ -5459,6 +5463,13 @@ }, "description": "Pod volumes to mount into the container's filesystem. Cannot be updated." }, + "volumeDevices": { + "type": "array", + "items": { + "$ref": "v1.VolumeDevice" + }, + "description": "volumeDevices is the list of block devices to be used by the container. This is an alpha feature and may change in the future." + }, "livenessProbe": { "$ref": "v1.Probe", "description": "Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes" @@ -5485,7 +5496,7 @@ }, "securityContext": { "$ref": "v1.SecurityContext", - "description": "Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md" + "description": "Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/" }, "stdin": { "type": "boolean", @@ -5711,6 +5722,24 @@ "id": "v1.MountPropagationMode", "properties": {} }, + "v1.VolumeDevice": { + "id": "v1.VolumeDevice", + "description": "volumeDevice describes a mapping of a raw block device within a container.", + "required": [ + "name", + "devicePath" + ], + "properties": { + "name": { + "type": "string", + "description": "name must match the name of a persistentVolumeClaim in the pod" + }, + "devicePath": { + "type": "string", + "description": "devicePath is the path inside of the container that the device will be mapped to." + } + } + }, "v1.Probe": { "id": "v1.Probe", "description": "Probe describes a health check to be performed against a container to determine whether it is alive or ready to receive traffic.", @@ -6111,7 +6140,10 @@ }, "v1.PodAffinityTerm": { "id": "v1.PodAffinityTerm", - "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key \u003ctopologyKey\u003e tches that of any node on which a pod of the set of pods is running", + "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key \u003ctopologyKey\u003e matches that of any node on which a pod of the set of pods is running", + "required": [ + "topologyKey" + ], "properties": { "labelSelector": { "$ref": "v1.LabelSelector", @@ -6126,7 +6158,7 @@ }, "topologyKey": { "type": "string", - "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. For PreferredDuringScheduling pod anti-affinity, empty topologyKey is interpreted as \"all topologies\" (\"all topologies\" here means all the topologyKeys indicated by scheduler command-line argument --failure-domains); for affinity and for RequiredDuringScheduling pod anti-affinity, empty topologyKey is not allowed." + "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed." } } }, @@ -6213,6 +6245,46 @@ } } }, + "v1.PodDNSConfig": { + "id": "v1.PodDNSConfig", + "description": "PodDNSConfig defines the DNS parameters of a pod in addition to those generated from DNSPolicy.", + "properties": { + "nameservers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of DNS name server IP addresses. This will be appended to the base nameservers generated from DNSPolicy. Duplicated nameservers will be removed." + }, + "searches": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of DNS search domains for host-name lookup. This will be appended to the base search paths generated from DNSPolicy. Duplicated search paths will be removed." + }, + "options": { + "type": "array", + "items": { + "$ref": "v1.PodDNSConfigOption" + }, + "description": "A list of DNS resolver options. This will be merged with the base options generated from DNSPolicy. Duplicated entries will be removed. Resolution options given in Options will override those that appear in the base DNSPolicy." + } + } + }, + "v1.PodDNSConfigOption": { + "id": "v1.PodDNSConfigOption", + "description": "PodDNSConfigOption defines DNS resolver options of a pod.", + "properties": { + "name": { + "type": "string", + "description": "Required." + }, + "value": { + "type": "string" + } + } + }, "v1beta1.DeploymentStrategy": { "id": "v1beta1.DeploymentStrategy", "description": "DeploymentStrategy describes how to replace existing pods with new ones.", @@ -6573,6 +6645,10 @@ "storageClassName": { "type": "string", "description": "Name of the StorageClass required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1" + }, + "volumeMode": { + "$ref": "v1.PersistentVolumeMode", + "description": "volumeMode defines what type of volume is required by the claim. Value of Filesystem is implied when not included in claim spec. This is an alpha feature and may change in the future." } } }, @@ -6580,6 +6656,10 @@ "id": "v1.PersistentVolumeAccessMode", "properties": {} }, + "v1.PersistentVolumeMode": { + "id": "v1.PersistentVolumeMode", + "properties": {} + }, "v1.PersistentVolumeClaimStatus": { "id": "v1.PersistentVolumeClaimStatus", "description": "PersistentVolumeClaimStatus is the current status of a persistent volume claim.", @@ -6709,6 +6789,43 @@ "type": "integer", "format": "int32", "description": "collisionCount is the count of hash collisions for the StatefulSet. The StatefulSet controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ControllerRevision." + }, + "conditions": { + "type": "array", + "items": { + "$ref": "v1beta1.StatefulSetCondition" + }, + "description": "Represents the latest available observations of a statefulset's current state." + } + } + }, + "v1beta1.StatefulSetCondition": { + "id": "v1beta1.StatefulSetCondition", + "description": "StatefulSetCondition describes the state of a statefulset at a certain point.", + "required": [ + "type", + "status" + ], + "properties": { + "type": { + "type": "string", + "description": "Type of statefulset condition." + }, + "status": { + "type": "string", + "description": "Status of the condition, one of True, False, Unknown." + }, + "lastTransitionTime": { + "type": "string", + "description": "Last time the condition transitioned from one status to another." + }, + "reason": { + "type": "string", + "description": "The reason for the condition's last transition." + }, + "message": { + "type": "string", + "description": "A human readable message indicating details about the transition." } } }, diff --git a/api/swagger-spec/apps_v1beta2.json b/api/swagger-spec/apps_v1beta2.json index e041cfbefac..be427887ae3 100644 --- a/api/swagger-spec/apps_v1beta2.json +++ b/api/swagger-spec/apps_v1beta2.json @@ -616,7 +616,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -1588,7 +1588,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -2730,7 +2730,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -4042,7 +4042,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -5354,7 +5354,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -6131,7 +6131,7 @@ }, "v1beta2.ControllerRevision": { "id": "v1beta2.ControllerRevision", - "description": "ControllerRevision implements an immutable snapshot of state data. Clients are responsible for serializing and deserializing the objects that contain their internal state. Once a ControllerRevision has been successfully created, it can not be updated. The API Server will fail validation of all requests that attempt to mutate the Data field. ControllerRevisions may, however, be deleted. Note that, due to its use by both the DaemonSet and StatefulSet controllers for update and rollback, this object is beta. However, it may be subject to name and representation changes in future releases, and clients should not depend on its stability. It is primarily for internal use by controllers.", + "description": "DEPRECATED - This group version of ControllerRevision is deprecated by apps/v1/ControllerRevision. See the release notes for more information. ControllerRevision implements an immutable snapshot of state data. Clients are responsible for serializing and deserializing the objects that contain their internal state. Once a ControllerRevision has been successfully created, it can not be updated. The API Server will fail validation of all requests that attempt to mutate the Data field. ControllerRevisions may, however, be deleted. Note that, due to its use by both the DaemonSet and StatefulSet controllers for update and rollback, this object is beta. However, it may be subject to name and representation changes in future releases, and clients should not depend on its stability. It is primarily for internal use by controllers.", "required": [ "revision" ], @@ -6198,7 +6198,7 @@ }, "deletionTimestamp": { "type": "string", - "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 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/api-conventions.md#metadata" + "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/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "type": "integer", @@ -6444,7 +6444,7 @@ }, "propagationPolicy": { "$ref": "v1.DeletionPropagation", - "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." + "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." } } }, @@ -6496,7 +6496,7 @@ }, "v1beta2.DaemonSet": { "id": "v1beta2.DaemonSet", - "description": "DaemonSet represents the configuration of a daemon set.", + "description": "DEPRECATED - This group version of DaemonSet is deprecated by apps/v1/DaemonSet. See the release notes for more information. DaemonSet represents the configuration of a daemon set.", "properties": { "kind": { "type": "string", @@ -6524,12 +6524,13 @@ "id": "v1beta2.DaemonSetSpec", "description": "DaemonSetSpec is the specification of a daemon set.", "required": [ + "selector", "template" ], "properties": { "selector": { "$ref": "v1.LabelSelector", - "description": "A label query over pods that are managed by the daemon set. Must match in order to be controlled. If empty, defaulted to labels on Pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors" + "description": "A label query over pods that are managed by the daemon set. Must match in order to be controlled. It must match the pod template's labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors" }, "template": { "$ref": "v1.PodTemplateSpec", @@ -6651,7 +6652,7 @@ }, "dnsPolicy": { "type": "string", - "description": "Set DNS policy for containers within the pod. One of 'ClusterFirstWithHostNet', 'ClusterFirst' or 'Default'. Defaults to \"ClusterFirst\". To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'." + "description": "Set DNS policy for the pod. Defaults to \"ClusterFirst\". Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'. Note that 'None' policy is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it." }, "nodeSelector": { "type": "object", @@ -6734,6 +6735,10 @@ "type": "integer", "format": "int32", "description": "The priority value. Various system components use this field to find the priority of the pod. 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." + }, + "dnsConfig": { + "$ref": "v1.PodDNSConfig", + "description": "Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy. This is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it." } } }, @@ -6794,7 +6799,7 @@ }, "flexVolume": { "$ref": "v1.FlexVolumeSource", - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future." + "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin." }, "cinder": { "$ref": "v1.CinderVolumeSource", @@ -7048,7 +7053,7 @@ "properties": { "targetPortal": { "type": "string", - "description": "iSCSI target portal. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." + "description": "iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." }, "iqn": { "type": "string", @@ -7057,11 +7062,11 @@ "lun": { "type": "integer", "format": "int32", - "description": "iSCSI target lun number." + "description": "iSCSI Target Lun number." }, "iscsiInterface": { "type": "string", - "description": "Optional: Defaults to 'default' (tcp). iSCSI interface name that uses an iSCSI transport." + "description": "iSCSI Interface Name that uses an iSCSI transport. Defaults to 'default' (tcp)." }, "fsType": { "type": "string", @@ -7076,7 +7081,7 @@ "items": { "type": "string" }, - "description": "iSCSI target portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." + "description": "iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." }, "chapAuthDiscovery": { "type": "boolean", @@ -7088,11 +7093,11 @@ }, "secretRef": { "$ref": "v1.LocalObjectReference", - "description": "CHAP secret for iSCSI target and initiator authentication" + "description": "CHAP Secret for iSCSI target and initiator authentication" }, "initiatorName": { "type": "string", - "description": "Custom iSCSI initiator name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface \u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection." + "description": "Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface \u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection." } } }, @@ -7192,7 +7197,7 @@ }, "v1.FlexVolumeSource": { "id": "v1.FlexVolumeSource", - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.", + "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.", "required": [ "driver" ], @@ -7709,15 +7714,15 @@ }, "protectionDomain": { "type": "string", - "description": "The name of the Protection Domain for the configured storage (defaults to \"default\")." + "description": "The name of the ScaleIO Protection Domain for the configured storage." }, "storagePool": { "type": "string", - "description": "The Storage Pool associated with the protection domain (defaults to \"default\")." + "description": "The ScaleIO Storage Pool associated with the protection domain." }, "storageMode": { "type": "string", - "description": "Indicates whether the storage for a volume should be thick or thin (defaults to \"thin\")." + "description": "Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned." }, "volumeName": { "type": "string", @@ -7824,6 +7829,13 @@ }, "description": "Pod volumes to mount into the container's filesystem. Cannot be updated." }, + "volumeDevices": { + "type": "array", + "items": { + "$ref": "v1.VolumeDevice" + }, + "description": "volumeDevices is the list of block devices to be used by the container. This is an alpha feature and may change in the future." + }, "livenessProbe": { "$ref": "v1.Probe", "description": "Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes" @@ -7850,7 +7862,7 @@ }, "securityContext": { "$ref": "v1.SecurityContext", - "description": "Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md" + "description": "Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/" }, "stdin": { "type": "boolean", @@ -8076,6 +8088,24 @@ "id": "v1.MountPropagationMode", "properties": {} }, + "v1.VolumeDevice": { + "id": "v1.VolumeDevice", + "description": "volumeDevice describes a mapping of a raw block device within a container.", + "required": [ + "name", + "devicePath" + ], + "properties": { + "name": { + "type": "string", + "description": "name must match the name of a persistentVolumeClaim in the pod" + }, + "devicePath": { + "type": "string", + "description": "devicePath is the path inside of the container that the device will be mapped to." + } + } + }, "v1.Probe": { "id": "v1.Probe", "description": "Probe describes a health check to be performed against a container to determine whether it is alive or ready to receive traffic.", @@ -8476,7 +8506,10 @@ }, "v1.PodAffinityTerm": { "id": "v1.PodAffinityTerm", - "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key \u003ctopologyKey\u003e tches that of any node on which a pod of the set of pods is running", + "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key \u003ctopologyKey\u003e matches that of any node on which a pod of the set of pods is running", + "required": [ + "topologyKey" + ], "properties": { "labelSelector": { "$ref": "v1.LabelSelector", @@ -8491,7 +8524,7 @@ }, "topologyKey": { "type": "string", - "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. For PreferredDuringScheduling pod anti-affinity, empty topologyKey is interpreted as \"all topologies\" (\"all topologies\" here means all the topologyKeys indicated by scheduler command-line argument --failure-domains); for affinity and for RequiredDuringScheduling pod anti-affinity, empty topologyKey is not allowed." + "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed." } } }, @@ -8578,6 +8611,46 @@ } } }, + "v1.PodDNSConfig": { + "id": "v1.PodDNSConfig", + "description": "PodDNSConfig defines the DNS parameters of a pod in addition to those generated from DNSPolicy.", + "properties": { + "nameservers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of DNS name server IP addresses. This will be appended to the base nameservers generated from DNSPolicy. Duplicated nameservers will be removed." + }, + "searches": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of DNS search domains for host-name lookup. This will be appended to the base search paths generated from DNSPolicy. Duplicated search paths will be removed." + }, + "options": { + "type": "array", + "items": { + "$ref": "v1.PodDNSConfigOption" + }, + "description": "A list of DNS resolver options. This will be merged with the base options generated from DNSPolicy. Duplicated entries will be removed. Resolution options given in Options will override those that appear in the base DNSPolicy." + } + } + }, + "v1.PodDNSConfigOption": { + "id": "v1.PodDNSConfigOption", + "description": "PodDNSConfigOption defines DNS resolver options of a pod.", + "properties": { + "name": { + "type": "string", + "description": "Required." + }, + "value": { + "type": "string" + } + } + }, "v1beta2.DaemonSetUpdateStrategy": { "id": "v1beta2.DaemonSetUpdateStrategy", "description": "DaemonSetUpdateStrategy is a struct used to control the update strategy for a DaemonSet.", @@ -8656,6 +8729,43 @@ "type": "integer", "format": "int32", "description": "Count of hash collisions for the DaemonSet. The DaemonSet controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ControllerRevision." + }, + "conditions": { + "type": "array", + "items": { + "$ref": "v1beta2.DaemonSetCondition" + }, + "description": "Represents the latest available observations of a DaemonSet's current state." + } + } + }, + "v1beta2.DaemonSetCondition": { + "id": "v1beta2.DaemonSetCondition", + "description": "DaemonSetCondition describes the state of a DaemonSet at a certain point.", + "required": [ + "type", + "status" + ], + "properties": { + "type": { + "type": "string", + "description": "Type of DaemonSet condition." + }, + "status": { + "type": "string", + "description": "Status of the condition, one of True, False, Unknown." + }, + "lastTransitionTime": { + "type": "string", + "description": "Last time the condition transitioned from one status to another." + }, + "reason": { + "type": "string", + "description": "The reason for the condition's last transition." + }, + "message": { + "type": "string", + "description": "A human readable message indicating details about the transition." } } }, @@ -8689,7 +8799,7 @@ }, "v1beta2.Deployment": { "id": "v1beta2.Deployment", - "description": "Deployment enables declarative updates for Pods and ReplicaSets.", + "description": "DEPRECATED - This group version of Deployment is deprecated by apps/v1/Deployment. See the release notes for more information. Deployment enables declarative updates for Pods and ReplicaSets.", "properties": { "kind": { "type": "string", @@ -8717,6 +8827,7 @@ "id": "v1beta2.DeploymentSpec", "description": "DeploymentSpec is the specification of the desired behavior of the Deployment.", "required": [ + "selector", "template" ], "properties": { @@ -8727,7 +8838,7 @@ }, "selector": { "$ref": "v1.LabelSelector", - "description": "Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment." + "description": "Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment. It must match the pod template's labels." }, "template": { "$ref": "v1.PodTemplateSpec", @@ -8957,7 +9068,7 @@ }, "v1beta2.ReplicaSet": { "id": "v1beta2.ReplicaSet", - "description": "ReplicaSet ensures that a specified number of pod replicas are running at any given time.", + "description": "DEPRECATED - This group version of ReplicaSet is deprecated by apps/v1/ReplicaSet. See the release notes for more information. ReplicaSet ensures that a specified number of pod replicas are running at any given time.", "properties": { "kind": { "type": "string", @@ -8984,6 +9095,9 @@ "v1beta2.ReplicaSetSpec": { "id": "v1beta2.ReplicaSetSpec", "description": "ReplicaSetSpec is the specification of a ReplicaSet.", + "required": [ + "selector" + ], "properties": { "replicas": { "type": "integer", @@ -8997,7 +9111,7 @@ }, "selector": { "$ref": "v1.LabelSelector", - "description": "Selector is a label query over pods that should match the replica count. If the selector is empty, it is defaulted to the labels present on the pod template. Label keys and values that must match in order to be controlled by this replica set. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors" + "description": "Selector is a label query over pods that should match the replica count. Label keys and values that must match in order to be controlled by this replica set. It must match the pod template's labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors" }, "template": { "$ref": "v1.PodTemplateSpec", @@ -9104,7 +9218,7 @@ }, "v1beta2.StatefulSet": { "id": "v1beta2.StatefulSet", - "description": "StatefulSet represents a set of pods with consistent identities. Identities are defined as:\n - Network: A single stable DNS and hostname.\n - Storage: As many VolumeClaims as requested.\nThe StatefulSet guarantees that a given network identity will always map to the same storage identity.", + "description": "DEPRECATED - This group version of StatefulSet is deprecated by apps/v1/StatefulSet. See the release notes for more information. StatefulSet represents a set of pods with consistent identities. Identities are defined as:\n - Network: A single stable DNS and hostname.\n - Storage: As many VolumeClaims as requested.\nThe StatefulSet guarantees that a given network identity will always map to the same storage identity.", "properties": { "kind": { "type": "string", @@ -9131,6 +9245,7 @@ "id": "v1beta2.StatefulSetSpec", "description": "A StatefulSetSpec is the specification of a StatefulSet.", "required": [ + "selector", "template", "serviceName" ], @@ -9142,7 +9257,7 @@ }, "selector": { "$ref": "v1.LabelSelector", - "description": "selector is a label query over pods that should match the replica count. If empty, defaulted to labels on the pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors" + "description": "selector is a label query over pods that should match the replica count. It must match the pod template's labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors" }, "template": { "$ref": "v1.PodTemplateSpec", @@ -9226,6 +9341,10 @@ "storageClassName": { "type": "string", "description": "Name of the StorageClass required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1" + }, + "volumeMode": { + "$ref": "v1.PersistentVolumeMode", + "description": "volumeMode defines what type of volume is required by the claim. Value of Filesystem is implied when not included in claim spec. This is an alpha feature and may change in the future." } } }, @@ -9233,6 +9352,10 @@ "id": "v1.PersistentVolumeAccessMode", "properties": {} }, + "v1.PersistentVolumeMode": { + "id": "v1.PersistentVolumeMode", + "properties": {} + }, "v1.PersistentVolumeClaimStatus": { "id": "v1.PersistentVolumeClaimStatus", "description": "PersistentVolumeClaimStatus is the current status of a persistent volume claim.", @@ -9362,6 +9485,43 @@ "type": "integer", "format": "int32", "description": "collisionCount is the count of hash collisions for the StatefulSet. The StatefulSet controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ControllerRevision." + }, + "conditions": { + "type": "array", + "items": { + "$ref": "v1beta2.StatefulSetCondition" + }, + "description": "Represents the latest available observations of a statefulset's current state." + } + } + }, + "v1beta2.StatefulSetCondition": { + "id": "v1beta2.StatefulSetCondition", + "description": "StatefulSetCondition describes the state of a statefulset at a certain point.", + "required": [ + "type", + "status" + ], + "properties": { + "type": { + "type": "string", + "description": "Type of statefulset condition." + }, + "status": { + "type": "string", + "description": "Status of the condition, one of True, False, Unknown." + }, + "lastTransitionTime": { + "type": "string", + "description": "Last time the condition transitioned from one status to another." + }, + "reason": { + "type": "string", + "description": "The reason for the condition's last transition." + }, + "message": { + "type": "string", + "description": "A human readable message indicating details about the transition." } } }, diff --git a/api/swagger-spec/authentication.k8s.io_v1.json b/api/swagger-spec/authentication.k8s.io_v1.json index 035bc3de871..99e1146a830 100644 --- a/api/swagger-spec/authentication.k8s.io_v1.json +++ b/api/swagger-spec/authentication.k8s.io_v1.json @@ -155,7 +155,7 @@ }, "deletionTimestamp": { "type": "string", - "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 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/api-conventions.md#metadata" + "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/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "type": "integer", diff --git a/api/swagger-spec/authentication.k8s.io_v1beta1.json b/api/swagger-spec/authentication.k8s.io_v1beta1.json index 290648a5113..fa507c2b85e 100644 --- a/api/swagger-spec/authentication.k8s.io_v1beta1.json +++ b/api/swagger-spec/authentication.k8s.io_v1beta1.json @@ -155,7 +155,7 @@ }, "deletionTimestamp": { "type": "string", - "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 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/api-conventions.md#metadata" + "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/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "type": "integer", diff --git a/api/swagger-spec/authorization.k8s.io_v1.json b/api/swagger-spec/authorization.k8s.io_v1.json index 46f957024cf..56f1720c4f2 100644 --- a/api/swagger-spec/authorization.k8s.io_v1.json +++ b/api/swagger-spec/authorization.k8s.io_v1.json @@ -328,7 +328,7 @@ }, "deletionTimestamp": { "type": "string", - "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 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/api-conventions.md#metadata" + "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/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "type": "integer", @@ -635,7 +635,11 @@ "properties": { "allowed": { "type": "boolean", - "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." + }, + "denied": { + "type": "boolean", + "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." }, "reason": { "type": "string", @@ -785,7 +789,7 @@ "items": { "type": "string" }, - "description": "Resources is a list of resources this rule applies to. ResourceAll represents all resources. \"*\" means all." + "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." }, "resourceNames": { "type": "array", diff --git a/api/swagger-spec/authorization.k8s.io_v1beta1.json b/api/swagger-spec/authorization.k8s.io_v1beta1.json index dcc02d2b092..e2c8f923a4c 100644 --- a/api/swagger-spec/authorization.k8s.io_v1beta1.json +++ b/api/swagger-spec/authorization.k8s.io_v1beta1.json @@ -328,7 +328,7 @@ }, "deletionTimestamp": { "type": "string", - "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 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/api-conventions.md#metadata" + "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/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "type": "integer", @@ -635,7 +635,11 @@ "properties": { "allowed": { "type": "boolean", - "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." + }, + "denied": { + "type": "boolean", + "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." }, "reason": { "type": "string", @@ -785,7 +789,7 @@ "items": { "type": "string" }, - "description": "Resources is a list of resources this rule applies to. ResourceAll represents all resources. \"*\" means all." + "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." }, "resourceNames": { "type": "array", diff --git a/api/swagger-spec/autoscaling_v1.json b/api/swagger-spec/autoscaling_v1.json index 36a6004735f..ab8f6e0119e 100644 --- a/api/swagger-spec/autoscaling_v1.json +++ b/api/swagger-spec/autoscaling_v1.json @@ -616,7 +616,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -1286,7 +1286,7 @@ }, "deletionTimestamp": { "type": "string", - "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 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/api-conventions.md#metadata" + "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/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "type": "integer", @@ -1617,7 +1617,7 @@ }, "propagationPolicy": { "$ref": "v1.DeletionPropagation", - "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." + "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." } } }, diff --git a/api/swagger-spec/autoscaling_v2beta1.json b/api/swagger-spec/autoscaling_v2beta1.json index 9b09f31b920..bf7d8a7d383 100644 --- a/api/swagger-spec/autoscaling_v2beta1.json +++ b/api/swagger-spec/autoscaling_v2beta1.json @@ -616,7 +616,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -1286,7 +1286,7 @@ }, "deletionTimestamp": { "type": "string", - "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 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/api-conventions.md#metadata" + "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/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "type": "integer", @@ -1547,7 +1547,7 @@ "properties": { "type": { "type": "string", - "description": "type is the type of metric source. It should match one of the fields below." + "description": "type is the type of metric source. It should be one of \"Object\", \"Pods\" or \"Resource\", each mapping to a matching field in the object." }, "object": { "$ref": "v2beta1.ObjectMetricSource", @@ -1680,7 +1680,7 @@ "properties": { "type": { "type": "string", - "description": "type is the type of metric source. It will match one of the fields below." + "description": "type is the type of metric source. It will be one of \"Object\", \"Pods\" or \"Resource\", each corresponds to a matching field in the object." }, "object": { "$ref": "v2beta1.ObjectMetricStatus", @@ -1837,7 +1837,7 @@ }, "propagationPolicy": { "$ref": "v1.DeletionPropagation", - "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." + "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." } } }, diff --git a/api/swagger-spec/batch_v1.json b/api/swagger-spec/batch_v1.json index e64f92ce491..28787d81889 100644 --- a/api/swagger-spec/batch_v1.json +++ b/api/swagger-spec/batch_v1.json @@ -616,7 +616,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -1286,7 +1286,7 @@ }, "deletionTimestamp": { "type": "string", - "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 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/api-conventions.md#metadata" + "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/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "type": "integer", @@ -1518,7 +1518,7 @@ }, "manualSelector": { "type": "boolean", - "description": "manualSelector controls generation of pod labels and pod selectors. Leave `manualSelector` unset unless you are certain what you are doing. When false or unset, the system pick labels unique to this job and appends those labels to the pod template. When true, the user is responsible for picking unique labels and specifying the selector. Failure to pick a unique label may cause this and other jobs to not function correctly. However, You may see `manualSelector=true` in jobs that were created with the old `extensions/v1beta1` API. More info: https://git.k8s.io/community/contributors/design-proposals/selector-generation.md" + "description": "manualSelector controls generation of pod labels and pod selectors. Leave `manualSelector` unset unless you are certain what you are doing. When false or unset, the system pick labels unique to this job and appends those labels to the pod template. When true, the user is responsible for picking unique labels and specifying the selector. Failure to pick a unique label may cause this and other jobs to not function correctly. However, You may see `manualSelector=true` in jobs that were created with the old `extensions/v1beta1` API. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/#specifying-your-own-pod-selector" }, "template": { "$ref": "v1.PodTemplateSpec", @@ -1626,7 +1626,7 @@ }, "dnsPolicy": { "type": "string", - "description": "Set DNS policy for containers within the pod. One of 'ClusterFirstWithHostNet', 'ClusterFirst' or 'Default'. Defaults to \"ClusterFirst\". To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'." + "description": "Set DNS policy for the pod. Defaults to \"ClusterFirst\". Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'. Note that 'None' policy is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it." }, "nodeSelector": { "type": "object", @@ -1709,6 +1709,10 @@ "type": "integer", "format": "int32", "description": "The priority value. Various system components use this field to find the priority of the pod. 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." + }, + "dnsConfig": { + "$ref": "v1.PodDNSConfig", + "description": "Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy. This is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it." } } }, @@ -1769,7 +1773,7 @@ }, "flexVolume": { "$ref": "v1.FlexVolumeSource", - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future." + "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin." }, "cinder": { "$ref": "v1.CinderVolumeSource", @@ -2023,7 +2027,7 @@ "properties": { "targetPortal": { "type": "string", - "description": "iSCSI target portal. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." + "description": "iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." }, "iqn": { "type": "string", @@ -2032,11 +2036,11 @@ "lun": { "type": "integer", "format": "int32", - "description": "iSCSI target lun number." + "description": "iSCSI Target Lun number." }, "iscsiInterface": { "type": "string", - "description": "Optional: Defaults to 'default' (tcp). iSCSI interface name that uses an iSCSI transport." + "description": "iSCSI Interface Name that uses an iSCSI transport. Defaults to 'default' (tcp)." }, "fsType": { "type": "string", @@ -2051,7 +2055,7 @@ "items": { "type": "string" }, - "description": "iSCSI target portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." + "description": "iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." }, "chapAuthDiscovery": { "type": "boolean", @@ -2063,11 +2067,11 @@ }, "secretRef": { "$ref": "v1.LocalObjectReference", - "description": "CHAP secret for iSCSI target and initiator authentication" + "description": "CHAP Secret for iSCSI target and initiator authentication" }, "initiatorName": { "type": "string", - "description": "Custom iSCSI initiator name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface \u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection." + "description": "Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface \u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection." } } }, @@ -2167,7 +2171,7 @@ }, "v1.FlexVolumeSource": { "id": "v1.FlexVolumeSource", - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.", + "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.", "required": [ "driver" ], @@ -2684,15 +2688,15 @@ }, "protectionDomain": { "type": "string", - "description": "The name of the Protection Domain for the configured storage (defaults to \"default\")." + "description": "The name of the ScaleIO Protection Domain for the configured storage." }, "storagePool": { "type": "string", - "description": "The Storage Pool associated with the protection domain (defaults to \"default\")." + "description": "The ScaleIO Storage Pool associated with the protection domain." }, "storageMode": { "type": "string", - "description": "Indicates whether the storage for a volume should be thick or thin (defaults to \"thin\")." + "description": "Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned." }, "volumeName": { "type": "string", @@ -2799,6 +2803,13 @@ }, "description": "Pod volumes to mount into the container's filesystem. Cannot be updated." }, + "volumeDevices": { + "type": "array", + "items": { + "$ref": "v1.VolumeDevice" + }, + "description": "volumeDevices is the list of block devices to be used by the container. This is an alpha feature and may change in the future." + }, "livenessProbe": { "$ref": "v1.Probe", "description": "Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes" @@ -2825,7 +2836,7 @@ }, "securityContext": { "$ref": "v1.SecurityContext", - "description": "Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md" + "description": "Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/" }, "stdin": { "type": "boolean", @@ -3051,6 +3062,24 @@ "id": "v1.MountPropagationMode", "properties": {} }, + "v1.VolumeDevice": { + "id": "v1.VolumeDevice", + "description": "volumeDevice describes a mapping of a raw block device within a container.", + "required": [ + "name", + "devicePath" + ], + "properties": { + "name": { + "type": "string", + "description": "name must match the name of a persistentVolumeClaim in the pod" + }, + "devicePath": { + "type": "string", + "description": "devicePath is the path inside of the container that the device will be mapped to." + } + } + }, "v1.Probe": { "id": "v1.Probe", "description": "Probe describes a health check to be performed against a container to determine whether it is alive or ready to receive traffic.", @@ -3451,7 +3480,10 @@ }, "v1.PodAffinityTerm": { "id": "v1.PodAffinityTerm", - "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key \u003ctopologyKey\u003e tches that of any node on which a pod of the set of pods is running", + "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key \u003ctopologyKey\u003e matches that of any node on which a pod of the set of pods is running", + "required": [ + "topologyKey" + ], "properties": { "labelSelector": { "$ref": "v1.LabelSelector", @@ -3466,7 +3498,7 @@ }, "topologyKey": { "type": "string", - "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. For PreferredDuringScheduling pod anti-affinity, empty topologyKey is interpreted as \"all topologies\" (\"all topologies\" here means all the topologyKeys indicated by scheduler command-line argument --failure-domains); for affinity and for RequiredDuringScheduling pod anti-affinity, empty topologyKey is not allowed." + "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed." } } }, @@ -3553,6 +3585,46 @@ } } }, + "v1.PodDNSConfig": { + "id": "v1.PodDNSConfig", + "description": "PodDNSConfig defines the DNS parameters of a pod in addition to those generated from DNSPolicy.", + "properties": { + "nameservers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of DNS name server IP addresses. This will be appended to the base nameservers generated from DNSPolicy. Duplicated nameservers will be removed." + }, + "searches": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of DNS search domains for host-name lookup. This will be appended to the base search paths generated from DNSPolicy. Duplicated search paths will be removed." + }, + "options": { + "type": "array", + "items": { + "$ref": "v1.PodDNSConfigOption" + }, + "description": "A list of DNS resolver options. This will be merged with the base options generated from DNSPolicy. Duplicated entries will be removed. Resolution options given in Options will override those that appear in the base DNSPolicy." + } + } + }, + "v1.PodDNSConfigOption": { + "id": "v1.PodDNSConfigOption", + "description": "PodDNSConfigOption defines DNS resolver options of a pod.", + "properties": { + "name": { + "type": "string", + "description": "Required." + }, + "value": { + "type": "string" + } + } + }, "v1.JobStatus": { "id": "v1.JobStatus", "description": "JobStatus represents the current state of a Job.", @@ -3670,7 +3742,7 @@ }, "propagationPolicy": { "$ref": "v1.DeletionPropagation", - "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." + "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." } } }, diff --git a/api/swagger-spec/batch_v1beta1.json b/api/swagger-spec/batch_v1beta1.json index 8dcb9ea0b82..bb9b870bda2 100644 --- a/api/swagger-spec/batch_v1beta1.json +++ b/api/swagger-spec/batch_v1beta1.json @@ -616,7 +616,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -1286,7 +1286,7 @@ }, "deletionTimestamp": { "type": "string", - "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 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/api-conventions.md#metadata" + "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/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "type": "integer", @@ -1504,7 +1504,7 @@ }, "concurrencyPolicy": { "type": "string", - "description": "Specifies how to treat concurrent executions of a Job. Defaults to Allow." + "description": "Specifies how to treat concurrent executions of a Job. Valid values are: - \"Allow\" (default): allows CronJobs to run concurrently; - \"Forbid\": forbids concurrent runs, skipping next run if previous run hasn't finished yet; - \"Replace\": cancels currently running job and replaces it with a new one" }, "suspend": { "type": "boolean", @@ -1573,7 +1573,7 @@ }, "manualSelector": { "type": "boolean", - "description": "manualSelector controls generation of pod labels and pod selectors. Leave `manualSelector` unset unless you are certain what you are doing. When false or unset, the system pick labels unique to this job and appends those labels to the pod template. When true, the user is responsible for picking unique labels and specifying the selector. Failure to pick a unique label may cause this and other jobs to not function correctly. However, You may see `manualSelector=true` in jobs that were created with the old `extensions/v1beta1` API. More info: https://git.k8s.io/community/contributors/design-proposals/selector-generation.md" + "description": "manualSelector controls generation of pod labels and pod selectors. Leave `manualSelector` unset unless you are certain what you are doing. When false or unset, the system pick labels unique to this job and appends those labels to the pod template. When true, the user is responsible for picking unique labels and specifying the selector. Failure to pick a unique label may cause this and other jobs to not function correctly. However, You may see `manualSelector=true` in jobs that were created with the old `extensions/v1beta1` API. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/#specifying-your-own-pod-selector" }, "template": { "$ref": "v1.PodTemplateSpec", @@ -1681,7 +1681,7 @@ }, "dnsPolicy": { "type": "string", - "description": "Set DNS policy for containers within the pod. One of 'ClusterFirstWithHostNet', 'ClusterFirst' or 'Default'. Defaults to \"ClusterFirst\". To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'." + "description": "Set DNS policy for the pod. Defaults to \"ClusterFirst\". Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'. Note that 'None' policy is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it." }, "nodeSelector": { "type": "object", @@ -1764,6 +1764,10 @@ "type": "integer", "format": "int32", "description": "The priority value. Various system components use this field to find the priority of the pod. 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." + }, + "dnsConfig": { + "$ref": "v1.PodDNSConfig", + "description": "Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy. This is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it." } } }, @@ -1824,7 +1828,7 @@ }, "flexVolume": { "$ref": "v1.FlexVolumeSource", - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future." + "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin." }, "cinder": { "$ref": "v1.CinderVolumeSource", @@ -2078,7 +2082,7 @@ "properties": { "targetPortal": { "type": "string", - "description": "iSCSI target portal. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." + "description": "iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." }, "iqn": { "type": "string", @@ -2087,11 +2091,11 @@ "lun": { "type": "integer", "format": "int32", - "description": "iSCSI target lun number." + "description": "iSCSI Target Lun number." }, "iscsiInterface": { "type": "string", - "description": "Optional: Defaults to 'default' (tcp). iSCSI interface name that uses an iSCSI transport." + "description": "iSCSI Interface Name that uses an iSCSI transport. Defaults to 'default' (tcp)." }, "fsType": { "type": "string", @@ -2106,7 +2110,7 @@ "items": { "type": "string" }, - "description": "iSCSI target portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." + "description": "iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." }, "chapAuthDiscovery": { "type": "boolean", @@ -2118,11 +2122,11 @@ }, "secretRef": { "$ref": "v1.LocalObjectReference", - "description": "CHAP secret for iSCSI target and initiator authentication" + "description": "CHAP Secret for iSCSI target and initiator authentication" }, "initiatorName": { "type": "string", - "description": "Custom iSCSI initiator name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface \u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection." + "description": "Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface \u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection." } } }, @@ -2222,7 +2226,7 @@ }, "v1.FlexVolumeSource": { "id": "v1.FlexVolumeSource", - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.", + "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.", "required": [ "driver" ], @@ -2739,15 +2743,15 @@ }, "protectionDomain": { "type": "string", - "description": "The name of the Protection Domain for the configured storage (defaults to \"default\")." + "description": "The name of the ScaleIO Protection Domain for the configured storage." }, "storagePool": { "type": "string", - "description": "The Storage Pool associated with the protection domain (defaults to \"default\")." + "description": "The ScaleIO Storage Pool associated with the protection domain." }, "storageMode": { "type": "string", - "description": "Indicates whether the storage for a volume should be thick or thin (defaults to \"thin\")." + "description": "Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned." }, "volumeName": { "type": "string", @@ -2854,6 +2858,13 @@ }, "description": "Pod volumes to mount into the container's filesystem. Cannot be updated." }, + "volumeDevices": { + "type": "array", + "items": { + "$ref": "v1.VolumeDevice" + }, + "description": "volumeDevices is the list of block devices to be used by the container. This is an alpha feature and may change in the future." + }, "livenessProbe": { "$ref": "v1.Probe", "description": "Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes" @@ -2880,7 +2891,7 @@ }, "securityContext": { "$ref": "v1.SecurityContext", - "description": "Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md" + "description": "Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/" }, "stdin": { "type": "boolean", @@ -3106,6 +3117,24 @@ "id": "v1.MountPropagationMode", "properties": {} }, + "v1.VolumeDevice": { + "id": "v1.VolumeDevice", + "description": "volumeDevice describes a mapping of a raw block device within a container.", + "required": [ + "name", + "devicePath" + ], + "properties": { + "name": { + "type": "string", + "description": "name must match the name of a persistentVolumeClaim in the pod" + }, + "devicePath": { + "type": "string", + "description": "devicePath is the path inside of the container that the device will be mapped to." + } + } + }, "v1.Probe": { "id": "v1.Probe", "description": "Probe describes a health check to be performed against a container to determine whether it is alive or ready to receive traffic.", @@ -3506,7 +3535,10 @@ }, "v1.PodAffinityTerm": { "id": "v1.PodAffinityTerm", - "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key \u003ctopologyKey\u003e tches that of any node on which a pod of the set of pods is running", + "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key \u003ctopologyKey\u003e matches that of any node on which a pod of the set of pods is running", + "required": [ + "topologyKey" + ], "properties": { "labelSelector": { "$ref": "v1.LabelSelector", @@ -3521,7 +3553,7 @@ }, "topologyKey": { "type": "string", - "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. For PreferredDuringScheduling pod anti-affinity, empty topologyKey is interpreted as \"all topologies\" (\"all topologies\" here means all the topologyKeys indicated by scheduler command-line argument --failure-domains); for affinity and for RequiredDuringScheduling pod anti-affinity, empty topologyKey is not allowed." + "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed." } } }, @@ -3608,6 +3640,46 @@ } } }, + "v1.PodDNSConfig": { + "id": "v1.PodDNSConfig", + "description": "PodDNSConfig defines the DNS parameters of a pod in addition to those generated from DNSPolicy.", + "properties": { + "nameservers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of DNS name server IP addresses. This will be appended to the base nameservers generated from DNSPolicy. Duplicated nameservers will be removed." + }, + "searches": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of DNS search domains for host-name lookup. This will be appended to the base search paths generated from DNSPolicy. Duplicated search paths will be removed." + }, + "options": { + "type": "array", + "items": { + "$ref": "v1.PodDNSConfigOption" + }, + "description": "A list of DNS resolver options. This will be merged with the base options generated from DNSPolicy. Duplicated entries will be removed. Resolution options given in Options will override those that appear in the base DNSPolicy." + } + } + }, + "v1.PodDNSConfigOption": { + "id": "v1.PodDNSConfigOption", + "description": "PodDNSConfigOption defines DNS resolver options of a pod.", + "properties": { + "name": { + "type": "string", + "description": "Required." + }, + "value": { + "type": "string" + } + } + }, "v1beta1.CronJobStatus": { "id": "v1beta1.CronJobStatus", "description": "CronJobStatus represents the current state of a cron job.", @@ -3706,7 +3778,7 @@ }, "propagationPolicy": { "$ref": "v1.DeletionPropagation", - "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." + "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." } } }, diff --git a/api/swagger-spec/batch_v2alpha1.json b/api/swagger-spec/batch_v2alpha1.json index 52bd44dbffd..cde661970dd 100644 --- a/api/swagger-spec/batch_v2alpha1.json +++ b/api/swagger-spec/batch_v2alpha1.json @@ -616,7 +616,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -1286,7 +1286,7 @@ }, "deletionTimestamp": { "type": "string", - "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 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/api-conventions.md#metadata" + "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/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "type": "integer", @@ -1504,7 +1504,7 @@ }, "concurrencyPolicy": { "type": "string", - "description": "Specifies how to treat concurrent executions of a Job. Defaults to Allow." + "description": "Specifies how to treat concurrent executions of a Job. Valid values are: - \"Allow\" (default): allows CronJobs to run concurrently; - \"Forbid\": forbids concurrent runs, skipping next run if previous run hasn't finished yet; - \"Replace\": cancels currently running job and replaces it with a new one" }, "suspend": { "type": "boolean", @@ -1573,7 +1573,7 @@ }, "manualSelector": { "type": "boolean", - "description": "manualSelector controls generation of pod labels and pod selectors. Leave `manualSelector` unset unless you are certain what you are doing. When false or unset, the system pick labels unique to this job and appends those labels to the pod template. When true, the user is responsible for picking unique labels and specifying the selector. Failure to pick a unique label may cause this and other jobs to not function correctly. However, You may see `manualSelector=true` in jobs that were created with the old `extensions/v1beta1` API. More info: https://git.k8s.io/community/contributors/design-proposals/selector-generation.md" + "description": "manualSelector controls generation of pod labels and pod selectors. Leave `manualSelector` unset unless you are certain what you are doing. When false or unset, the system pick labels unique to this job and appends those labels to the pod template. When true, the user is responsible for picking unique labels and specifying the selector. Failure to pick a unique label may cause this and other jobs to not function correctly. However, You may see `manualSelector=true` in jobs that were created with the old `extensions/v1beta1` API. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/#specifying-your-own-pod-selector" }, "template": { "$ref": "v1.PodTemplateSpec", @@ -1681,7 +1681,7 @@ }, "dnsPolicy": { "type": "string", - "description": "Set DNS policy for containers within the pod. One of 'ClusterFirstWithHostNet', 'ClusterFirst' or 'Default'. Defaults to \"ClusterFirst\". To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'." + "description": "Set DNS policy for the pod. Defaults to \"ClusterFirst\". Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'. Note that 'None' policy is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it." }, "nodeSelector": { "type": "object", @@ -1764,6 +1764,10 @@ "type": "integer", "format": "int32", "description": "The priority value. Various system components use this field to find the priority of the pod. 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." + }, + "dnsConfig": { + "$ref": "v1.PodDNSConfig", + "description": "Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy. This is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it." } } }, @@ -1824,7 +1828,7 @@ }, "flexVolume": { "$ref": "v1.FlexVolumeSource", - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future." + "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin." }, "cinder": { "$ref": "v1.CinderVolumeSource", @@ -2078,7 +2082,7 @@ "properties": { "targetPortal": { "type": "string", - "description": "iSCSI target portal. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." + "description": "iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." }, "iqn": { "type": "string", @@ -2087,11 +2091,11 @@ "lun": { "type": "integer", "format": "int32", - "description": "iSCSI target lun number." + "description": "iSCSI Target Lun number." }, "iscsiInterface": { "type": "string", - "description": "Optional: Defaults to 'default' (tcp). iSCSI interface name that uses an iSCSI transport." + "description": "iSCSI Interface Name that uses an iSCSI transport. Defaults to 'default' (tcp)." }, "fsType": { "type": "string", @@ -2106,7 +2110,7 @@ "items": { "type": "string" }, - "description": "iSCSI target portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." + "description": "iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." }, "chapAuthDiscovery": { "type": "boolean", @@ -2118,11 +2122,11 @@ }, "secretRef": { "$ref": "v1.LocalObjectReference", - "description": "CHAP secret for iSCSI target and initiator authentication" + "description": "CHAP Secret for iSCSI target and initiator authentication" }, "initiatorName": { "type": "string", - "description": "Custom iSCSI initiator name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface \u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection." + "description": "Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface \u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection." } } }, @@ -2222,7 +2226,7 @@ }, "v1.FlexVolumeSource": { "id": "v1.FlexVolumeSource", - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.", + "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.", "required": [ "driver" ], @@ -2739,15 +2743,15 @@ }, "protectionDomain": { "type": "string", - "description": "The name of the Protection Domain for the configured storage (defaults to \"default\")." + "description": "The name of the ScaleIO Protection Domain for the configured storage." }, "storagePool": { "type": "string", - "description": "The Storage Pool associated with the protection domain (defaults to \"default\")." + "description": "The ScaleIO Storage Pool associated with the protection domain." }, "storageMode": { "type": "string", - "description": "Indicates whether the storage for a volume should be thick or thin (defaults to \"thin\")." + "description": "Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned." }, "volumeName": { "type": "string", @@ -2854,6 +2858,13 @@ }, "description": "Pod volumes to mount into the container's filesystem. Cannot be updated." }, + "volumeDevices": { + "type": "array", + "items": { + "$ref": "v1.VolumeDevice" + }, + "description": "volumeDevices is the list of block devices to be used by the container. This is an alpha feature and may change in the future." + }, "livenessProbe": { "$ref": "v1.Probe", "description": "Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes" @@ -2880,7 +2891,7 @@ }, "securityContext": { "$ref": "v1.SecurityContext", - "description": "Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md" + "description": "Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/" }, "stdin": { "type": "boolean", @@ -3106,6 +3117,24 @@ "id": "v1.MountPropagationMode", "properties": {} }, + "v1.VolumeDevice": { + "id": "v1.VolumeDevice", + "description": "volumeDevice describes a mapping of a raw block device within a container.", + "required": [ + "name", + "devicePath" + ], + "properties": { + "name": { + "type": "string", + "description": "name must match the name of a persistentVolumeClaim in the pod" + }, + "devicePath": { + "type": "string", + "description": "devicePath is the path inside of the container that the device will be mapped to." + } + } + }, "v1.Probe": { "id": "v1.Probe", "description": "Probe describes a health check to be performed against a container to determine whether it is alive or ready to receive traffic.", @@ -3506,7 +3535,10 @@ }, "v1.PodAffinityTerm": { "id": "v1.PodAffinityTerm", - "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key \u003ctopologyKey\u003e tches that of any node on which a pod of the set of pods is running", + "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key \u003ctopologyKey\u003e matches that of any node on which a pod of the set of pods is running", + "required": [ + "topologyKey" + ], "properties": { "labelSelector": { "$ref": "v1.LabelSelector", @@ -3521,7 +3553,7 @@ }, "topologyKey": { "type": "string", - "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. For PreferredDuringScheduling pod anti-affinity, empty topologyKey is interpreted as \"all topologies\" (\"all topologies\" here means all the topologyKeys indicated by scheduler command-line argument --failure-domains); for affinity and for RequiredDuringScheduling pod anti-affinity, empty topologyKey is not allowed." + "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed." } } }, @@ -3608,6 +3640,46 @@ } } }, + "v1.PodDNSConfig": { + "id": "v1.PodDNSConfig", + "description": "PodDNSConfig defines the DNS parameters of a pod in addition to those generated from DNSPolicy.", + "properties": { + "nameservers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of DNS name server IP addresses. This will be appended to the base nameservers generated from DNSPolicy. Duplicated nameservers will be removed." + }, + "searches": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of DNS search domains for host-name lookup. This will be appended to the base search paths generated from DNSPolicy. Duplicated search paths will be removed." + }, + "options": { + "type": "array", + "items": { + "$ref": "v1.PodDNSConfigOption" + }, + "description": "A list of DNS resolver options. This will be merged with the base options generated from DNSPolicy. Duplicated entries will be removed. Resolution options given in Options will override those that appear in the base DNSPolicy." + } + } + }, + "v1.PodDNSConfigOption": { + "id": "v1.PodDNSConfigOption", + "description": "PodDNSConfigOption defines DNS resolver options of a pod.", + "properties": { + "name": { + "type": "string", + "description": "Required." + }, + "value": { + "type": "string" + } + } + }, "v2alpha1.CronJobStatus": { "id": "v2alpha1.CronJobStatus", "description": "CronJobStatus represents the current state of a cron job.", @@ -3706,7 +3778,7 @@ }, "propagationPolicy": { "$ref": "v1.DeletionPropagation", - "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." + "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." } } }, diff --git a/api/swagger-spec/certificates.k8s.io_v1beta1.json b/api/swagger-spec/certificates.k8s.io_v1beta1.json index 0c301503cbd..025b54ae532 100644 --- a/api/swagger-spec/certificates.k8s.io_v1beta1.json +++ b/api/swagger-spec/certificates.k8s.io_v1beta1.json @@ -560,7 +560,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -950,7 +950,7 @@ }, "deletionTimestamp": { "type": "string", - "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 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/api-conventions.md#metadata" + "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/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "type": "integer", @@ -1279,7 +1279,7 @@ }, "propagationPolicy": { "$ref": "v1.DeletionPropagation", - "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." + "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." } } }, diff --git a/api/swagger-spec/events.k8s.io.json b/api/swagger-spec/events.k8s.io.json new file mode 100644 index 00000000000..423933664fc --- /dev/null +++ b/api/swagger-spec/events.k8s.io.json @@ -0,0 +1,114 @@ +{ + "swaggerVersion": "1.2", + "apiVersion": "", + "basePath": "https://10.10.10.10:6443", + "resourcePath": "/apis/events.k8s.io", + "info": { + "title": "", + "description": "" + }, + "apis": [ + { + "path": "/apis/events.k8s.io", + "description": "get information of a group", + "operations": [ + { + "type": "v1.APIGroup", + "method": "GET", + "summary": "get information of a group", + "nickname": "getAPIGroup", + "parameters": [], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ] + } + ] + } + ], + "models": { + "v1.APIGroup": { + "id": "v1.APIGroup", + "description": "APIGroup contains the name, the supported versions, and the preferred version of a group.", + "required": [ + "name", + "versions", + "serverAddressByClientCIDRs" + ], + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "name": { + "type": "string", + "description": "name is the name of the group." + }, + "versions": { + "type": "array", + "items": { + "$ref": "v1.GroupVersionForDiscovery" + }, + "description": "versions are the versions supported in this group." + }, + "preferredVersion": { + "$ref": "v1.GroupVersionForDiscovery", + "description": "preferredVersion is the version preferred by the API server, which probably is the storage version." + }, + "serverAddressByClientCIDRs": { + "type": "array", + "items": { + "$ref": "v1.ServerAddressByClientCIDR" + }, + "description": "a map of client CIDR to server address that is serving this group. This is to help clients reach servers in the most network-efficient way possible. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR. The server returns only those CIDRs that it thinks that the client can match. For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP." + } + } + }, + "v1.GroupVersionForDiscovery": { + "id": "v1.GroupVersionForDiscovery", + "description": "GroupVersion contains the \"group/version\" and \"version\" string of a version. It is made a struct to keep extensibility.", + "required": [ + "groupVersion", + "version" + ], + "properties": { + "groupVersion": { + "type": "string", + "description": "groupVersion specifies the API group and version in the form \"group/version\"" + }, + "version": { + "type": "string", + "description": "version specifies the version in the form of \"version\". This is to save the clients the trouble of splitting the GroupVersion." + } + } + }, + "v1.ServerAddressByClientCIDR": { + "id": "v1.ServerAddressByClientCIDR", + "description": "ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match.", + "required": [ + "clientCIDR", + "serverAddress" + ], + "properties": { + "clientCIDR": { + "type": "string", + "description": "The CIDR with which clients can match their IP to figure out the server address that they should use." + }, + "serverAddress": { + "type": "string", + "description": "Address of this server, suitable for a client that matches the above CIDR. This can be a hostname, hostname:port, IP or IP:port." + } + } + } + } + } diff --git a/api/swagger-spec/events.k8s.io_v1beta1.json b/api/swagger-spec/events.k8s.io_v1beta1.json new file mode 100644 index 00000000000..0cc8bdd3299 --- /dev/null +++ b/api/swagger-spec/events.k8s.io_v1beta1.json @@ -0,0 +1,1600 @@ +{ + "swaggerVersion": "1.2", + "apiVersion": "events.k8s.io/v1beta1", + "basePath": "https://10.10.10.10:6443", + "resourcePath": "/apis/events.k8s.io/v1beta1", + "info": { + "title": "", + "description": "" + }, + "apis": [ + { + "path": "/apis/events.k8s.io/v1beta1/namespaces/{namespace}/events", + "description": "API at /apis/events.k8s.io/v1beta1", + "operations": [ + { + "type": "v1beta1.EventList", + "method": "GET", + "summary": "list or watch objects of kind Event", + "nickname": "listNamespacedEvent", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta1.EventList" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1beta1.Event", + "method": "POST", + "summary": "create an Event", + "nickname": "createNamespacedEvent", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1beta1.Event", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta1.Event" + }, + { + "code": 201, + "message": "Created", + "responseModel": "v1beta1.Event" + }, + { + "code": 202, + "message": "Accepted", + "responseModel": "v1beta1.Event" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.Status", + "method": "DELETE", + "summary": "delete collection of Event", + "nickname": "deletecollectionNamespacedEvent", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Status" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/events.k8s.io/v1beta1/watch/namespaces/{namespace}/events", + "description": "API at /apis/events.k8s.io/v1beta1", + "operations": [ + { + "type": "v1.WatchEvent", + "method": "GET", + "summary": "watch individual changes to a list of Event", + "nickname": "watchNamespacedEventList", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.WatchEvent" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/events.k8s.io/v1beta1/namespaces/{namespace}/events/{name}", + "description": "API at /apis/events.k8s.io/v1beta1", + "operations": [ + { + "type": "v1beta1.Event", + "method": "GET", + "summary": "read the specified Event", + "nickname": "readNamespacedEvent", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "export", + "description": "Should this value be exported. Export strips fields that a user can not specify.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "exact", + "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the Event", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta1.Event" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1beta1.Event", + "method": "PUT", + "summary": "replace the specified Event", + "nickname": "replaceNamespacedEvent", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1beta1.Event", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the Event", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta1.Event" + }, + { + "code": 201, + "message": "Created", + "responseModel": "v1beta1.Event" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1beta1.Event", + "method": "PATCH", + "summary": "partially update the specified Event", + "nickname": "patchNamespacedEvent", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.Patch", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the Event", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta1.Event" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ] + }, + { + "type": "v1.Status", + "method": "DELETE", + "summary": "delete an Event", + "nickname": "deleteNamespacedEvent", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.DeleteOptions", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the Event", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Status" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/events.k8s.io/v1beta1/watch/namespaces/{namespace}/events/{name}", + "description": "API at /apis/events.k8s.io/v1beta1", + "operations": [ + { + "type": "v1.WatchEvent", + "method": "GET", + "summary": "watch changes to an object of kind Event", + "nickname": "watchNamespacedEvent", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the Event", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.WatchEvent" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/events.k8s.io/v1beta1/events", + "description": "API at /apis/events.k8s.io/v1beta1", + "operations": [ + { + "type": "v1beta1.EventList", + "method": "GET", + "summary": "list or watch objects of kind Event", + "nickname": "listEventForAllNamespaces", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta1.EventList" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/events.k8s.io/v1beta1/watch/events", + "description": "API at /apis/events.k8s.io/v1beta1", + "operations": [ + { + "type": "v1.WatchEvent", + "method": "GET", + "summary": "watch individual changes to a list of Event", + "nickname": "watchEventListForAllNamespaces", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.WatchEvent" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/events.k8s.io/v1beta1", + "description": "API at /apis/events.k8s.io/v1beta1", + "operations": [ + { + "type": "v1.APIResourceList", + "method": "GET", + "summary": "get available resources", + "nickname": "getAPIResources", + "parameters": [], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ] + } + ] + } + ], + "models": { + "v1beta1.EventList": { + "id": "v1beta1.EventList", + "description": "EventList is a list of Event objects.", + "required": [ + "items" + ], + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "metadata": { + "$ref": "v1.ListMeta", + "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" + }, + "items": { + "type": "array", + "items": { + "$ref": "v1beta1.Event" + }, + "description": "Items is a list of schema objects." + } + } + }, + "v1.ListMeta": { + "id": "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": { + "selfLink": { + "type": "string", + "description": "selfLink is a URL representing this object. Populated by the system. Read-only." + }, + "resourceVersion": { + "type": "string", + "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/api-conventions.md#concurrency-control-and-consistency" + }, + "continue": { + "type": "string", + "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 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." + } + } + }, + "v1beta1.Event": { + "id": "v1beta1.Event", + "description": "Event is a report of an event somewhere in the cluster. It generally denotes some state change in the system.", + "required": [ + "eventTime", + "reportingInstance", + "action" + ], + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "metadata": { + "$ref": "v1.ObjectMeta" + }, + "eventTime": { + "type": "string", + "description": "Required. Time when this Event was first observed." + }, + "series": { + "$ref": "v1beta1.EventSeries", + "description": "Data about the Event series this event represents or nil if it's a singleton Event." + }, + "reportingController": { + "type": "string", + "description": "Name of the controller that emitted this Event, e.g. `kubernetes.io/kubelet`." + }, + "reportingInstance": { + "type": "string", + "description": "ID of the controller instance, e.g. `kubelet-xyzf`." + }, + "action": { + "type": "string", + "description": "What action was taken/failed regarding to the regarding object." + }, + "reason": { + "type": "string", + "description": "Why the action was taken." + }, + "regarding": { + "$ref": "v1.ObjectReference", + "description": "The object this Event is about. In most cases it's an Object reporting controller implements. E.g. ReplicaSetController implements ReplicaSets and this event is emitted because it acts on some changes in a ReplicaSet object." + }, + "related": { + "$ref": "v1.ObjectReference", + "description": "Optional secondary object for more complex actions. E.g. when regarding object triggers a creation or deletion of related object." + }, + "note": { + "type": "string", + "description": "Optional. A human-readable description of the status of this operation. Maximal length of the note is 1kB, but libraries should be prepared to handle values up to 64kB." + }, + "type": { + "type": "string", + "description": "Type of this event (Normal, Warning), new types could be added in the future." + }, + "deprecatedSource": { + "$ref": "v1.EventSource", + "description": "Deprecated field assuring backward compatibility with core.v1 Event type" + }, + "deprecatedFirstTimestamp": { + "type": "string", + "description": "Deprecated field assuring backward compatibility with core.v1 Event type" + }, + "deprecatedLastTimestamp": { + "type": "string", + "description": "Deprecated field assuring backward compatibility with core.v1 Event type" + }, + "deprecatedCount": { + "type": "integer", + "format": "int32", + "description": "Deprecated field assuring backward compatibility with core.v1 Event type" + } + } + }, + "v1.ObjectMeta": { + "id": "v1.ObjectMeta", + "description": "ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.", + "properties": { + "name": { + "type": "string", + "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: http://kubernetes.io/docs/user-guide/identifiers#names" + }, + "generateName": { + "type": "string", + "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 NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header).\n\nApplied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency" + }, + "namespace": { + "type": "string", + "description": "Namespace defines the space within 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: http://kubernetes.io/docs/user-guide/namespaces" + }, + "selfLink": { + "type": "string", + "description": "SelfLink is a URL representing this object. Populated by the system. Read-only." + }, + "uid": { + "type": "string", + "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: http://kubernetes.io/docs/user-guide/identifiers#uids" + }, + "resourceVersion": { + "type": "string", + "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/api-conventions.md#concurrency-control-and-consistency" + }, + "generation": { + "type": "integer", + "format": "int64", + "description": "A sequence number representing a specific generation of the desired state. Populated by the system. Read-only." + }, + "creationTimestamp": { + "type": "string", + "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/api-conventions.md#metadata" + }, + "deletionTimestamp": { + "type": "string", + "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/api-conventions.md#metadata" + }, + "deletionGracePeriodSeconds": { + "type": "integer", + "format": "int64", + "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." + }, + "labels": { + "type": "object", + "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: http://kubernetes.io/docs/user-guide/labels" + }, + "annotations": { + "type": "object", + "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: http://kubernetes.io/docs/user-guide/annotations" + }, + "ownerReferences": { + "type": "array", + "items": { + "$ref": "v1.OwnerReference" + }, + "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." + }, + "initializers": { + "$ref": "v1.Initializers", + "description": "An initializer is a controller which enforces some system invariant at object creation time. This field is a list of initializers that have not yet acted on this object. If nil or empty, this object has been completely initialized. Otherwise, the object is considered uninitialized and is hidden (in list/watch and get calls) from clients that haven't explicitly asked to observe uninitialized objects.\n\nWhen an object is created, the system will populate this list with the current set of initializers. Only privileged users may set or modify this list. Once it is empty, it may not be modified further by any user." + }, + "finalizers": { + "type": "array", + "items": { + "type": "string" + }, + "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." + }, + "clusterName": { + "type": "string", + "description": "The name of the cluster which the object belongs to. This is used to distinguish resources with same name and namespace in different clusters. This field is not set anywhere right now and apiserver is going to ignore it if set in create or update request." + } + } + }, + "v1.OwnerReference": { + "id": "v1.OwnerReference", + "description": "OwnerReference contains enough information to let you identify an owning object. Currently, an owning object must be in the same namespace, so there is no namespace field.", + "required": [ + "apiVersion", + "kind", + "name", + "uid" + ], + "properties": { + "apiVersion": { + "type": "string", + "description": "API version of the referent." + }, + "kind": { + "type": "string", + "description": "Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds" + }, + "name": { + "type": "string", + "description": "Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names" + }, + "uid": { + "type": "string", + "description": "UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids" + }, + "controller": { + "type": "boolean", + "description": "If true, this reference points to the managing controller." + }, + "blockOwnerDeletion": { + "type": "boolean", + "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. Defaults to false. To set this field, a user needs \"delete\" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned." + } + } + }, + "v1.Initializers": { + "id": "v1.Initializers", + "description": "Initializers tracks the progress of initialization.", + "required": [ + "pending" + ], + "properties": { + "pending": { + "type": "array", + "items": { + "$ref": "v1.Initializer" + }, + "description": "Pending is a list of initializers that must execute in order before this object is visible. When the last pending initializer is removed, and no failing result is set, the initializers struct will be set to nil and the object is considered as initialized and visible to all clients." + }, + "result": { + "$ref": "v1.Status", + "description": "If result is set with the Failure field, the object will be persisted to storage and then deleted, ensuring that other clients can observe the deletion." + } + } + }, + "v1.Initializer": { + "id": "v1.Initializer", + "description": "Initializer is information about an initializer that has not yet completed.", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "name of the process that is responsible for initializing this object." + } + } + }, + "v1.Status": { + "id": "v1.Status", + "description": "Status is a return value for calls that don't return other objects.", + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "metadata": { + "$ref": "v1.ListMeta", + "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds" + }, + "status": { + "type": "string", + "description": "Status of the operation. One of: \"Success\" or \"Failure\". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status" + }, + "message": { + "type": "string", + "description": "A human-readable description of the status of this operation." + }, + "reason": { + "type": "string", + "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." + }, + "details": { + "$ref": "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." + }, + "code": { + "type": "integer", + "format": "int32", + "description": "Suggested HTTP return code for this status, 0 if not set." + } + } + }, + "v1.StatusDetails": { + "id": "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": { + "name": { + "type": "string", + "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described)." + }, + "group": { + "type": "string", + "description": "The group attribute of the resource associated with the status StatusReason." + }, + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "uid": { + "type": "string", + "description": "UID of the resource. (when there is a single resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids" + }, + "causes": { + "type": "array", + "items": { + "$ref": "v1.StatusCause" + }, + "description": "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes." + }, + "retryAfterSeconds": { + "type": "integer", + "format": "int32", + "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." + } + } + }, + "v1.StatusCause": { + "id": "v1.StatusCause", + "description": "StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.", + "properties": { + "reason": { + "type": "string", + "description": "A machine-readable description of the cause of the error. If this value is empty there is no information available." + }, + "message": { + "type": "string", + "description": "A human-readable description of the cause of the error. This field may be presented as-is to a reader." + }, + "field": { + "type": "string", + "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\"" + } + } + }, + "v1beta1.EventSeries": { + "id": "v1beta1.EventSeries", + "description": "EventSeries contain information on series of events, i.e. thing that was/is happening continously for some time.", + "required": [ + "count", + "lastObservedTime", + "state" + ], + "properties": { + "count": { + "type": "integer", + "format": "int32", + "description": "Number of occurrences in this series up to the last heartbeat time" + }, + "lastObservedTime": { + "type": "string", + "description": "Time when last Event from the series was seen before last heartbeat." + }, + "state": { + "type": "string", + "description": "Information whether this series is ongoing or finished." + } + } + }, + "v1.ObjectReference": { + "id": "v1.ObjectReference", + "description": "ObjectReference contains enough information to let you inspect or modify the referred object.", + "properties": { + "kind": { + "type": "string", + "description": "Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds" + }, + "namespace": { + "type": "string", + "description": "Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/" + }, + "name": { + "type": "string", + "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names" + }, + "uid": { + "type": "string", + "description": "UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids" + }, + "apiVersion": { + "type": "string", + "description": "API version of the referent." + }, + "resourceVersion": { + "type": "string", + "description": "Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency" + }, + "fieldPath": { + "type": "string", + "description": "If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: \"spec.containers{name}\" (where \"name\" refers to the name of the container that triggered the event) or if no container name is specified \"spec.containers[2]\" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object." + } + } + }, + "v1.EventSource": { + "id": "v1.EventSource", + "description": "EventSource contains information for an event.", + "properties": { + "component": { + "type": "string", + "description": "Component from which the event is generated." + }, + "host": { + "type": "string", + "description": "Node name on which the event is generated." + } + } + }, + "v1.WatchEvent": { + "id": "v1.WatchEvent", + "required": [ + "type", + "object" + ], + "properties": { + "type": { + "type": "string" + }, + "object": { + "type": "string" + } + } + }, + "v1.Patch": { + "id": "v1.Patch", + "description": "Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.", + "properties": {} + }, + "v1.DeleteOptions": { + "id": "v1.DeleteOptions", + "description": "DeleteOptions may be provided when deleting an API object.", + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "gracePeriodSeconds": { + "type": "integer", + "format": "int64", + "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." + }, + "preconditions": { + "$ref": "v1.Preconditions", + "description": "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned." + }, + "orphanDependents": { + "type": "boolean", + "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." + }, + "propagationPolicy": { + "$ref": "v1.DeletionPropagation", + "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." + } + } + }, + "v1.Preconditions": { + "id": "v1.Preconditions", + "description": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", + "properties": { + "uid": { + "$ref": "types.UID", + "description": "Specifies the target UID." + } + } + }, + "types.UID": { + "id": "types.UID", + "properties": {} + }, + "v1.DeletionPropagation": { + "id": "v1.DeletionPropagation", + "properties": {} + }, + "v1.APIResourceList": { + "id": "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.", + "required": [ + "groupVersion", + "resources" + ], + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "groupVersion": { + "type": "string", + "description": "groupVersion is the group and version this APIResourceList is for." + }, + "resources": { + "type": "array", + "items": { + "$ref": "v1.APIResource" + }, + "description": "resources contains the name of the resources and if they are namespaced." + } + } + }, + "v1.APIResource": { + "id": "v1.APIResource", + "description": "APIResource specifies the name of a resource and whether it is namespaced.", + "required": [ + "name", + "singularName", + "namespaced", + "kind", + "verbs" + ], + "properties": { + "name": { + "type": "string", + "description": "name is the plural name of the resource." + }, + "singularName": { + "type": "string", + "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." + }, + "namespaced": { + "type": "boolean", + "description": "namespaced indicates if a resource is namespaced or not." + }, + "group": { + "type": "string", + "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\"." + }, + "version": { + "type": "string", + "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)\"." + }, + "kind": { + "type": "string", + "description": "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')" + }, + "verbs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy)" + }, + "shortNames": { + "type": "array", + "items": { + "type": "string" + }, + "description": "shortNames is a list of suggested short names of the resource." + }, + "categories": { + "type": "array", + "items": { + "type": "string" + }, + "description": "categories is a list of the grouped resources this resource belongs to (e.g. 'all')" + } + } + } + } + } diff --git a/api/swagger-spec/extensions_v1beta1.json b/api/swagger-spec/extensions_v1beta1.json index 6d86c3f52cd..d8b20a3fd98 100644 --- a/api/swagger-spec/extensions_v1beta1.json +++ b/api/swagger-spec/extensions_v1beta1.json @@ -616,7 +616,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -1758,7 +1758,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -3141,7 +3141,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -4283,7 +4283,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -5199,7 +5199,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -5949,7 +5949,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -6959,7 +6959,7 @@ }, "deletionTimestamp": { "type": "string", - "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 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/api-conventions.md#metadata" + "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/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "type": "integer", @@ -7294,7 +7294,7 @@ }, "dnsPolicy": { "type": "string", - "description": "Set DNS policy for containers within the pod. One of 'ClusterFirstWithHostNet', 'ClusterFirst' or 'Default'. Defaults to \"ClusterFirst\". To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'." + "description": "Set DNS policy for the pod. Defaults to \"ClusterFirst\". Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'. Note that 'None' policy is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it." }, "nodeSelector": { "type": "object", @@ -7377,6 +7377,10 @@ "type": "integer", "format": "int32", "description": "The priority value. Various system components use this field to find the priority of the pod. 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." + }, + "dnsConfig": { + "$ref": "v1.PodDNSConfig", + "description": "Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy. This is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it." } } }, @@ -7437,7 +7441,7 @@ }, "flexVolume": { "$ref": "v1.FlexVolumeSource", - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future." + "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin." }, "cinder": { "$ref": "v1.CinderVolumeSource", @@ -7691,7 +7695,7 @@ "properties": { "targetPortal": { "type": "string", - "description": "iSCSI target portal. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." + "description": "iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." }, "iqn": { "type": "string", @@ -7700,11 +7704,11 @@ "lun": { "type": "integer", "format": "int32", - "description": "iSCSI target lun number." + "description": "iSCSI Target Lun number." }, "iscsiInterface": { "type": "string", - "description": "Optional: Defaults to 'default' (tcp). iSCSI interface name that uses an iSCSI transport." + "description": "iSCSI Interface Name that uses an iSCSI transport. Defaults to 'default' (tcp)." }, "fsType": { "type": "string", @@ -7719,7 +7723,7 @@ "items": { "type": "string" }, - "description": "iSCSI target portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." + "description": "iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." }, "chapAuthDiscovery": { "type": "boolean", @@ -7731,11 +7735,11 @@ }, "secretRef": { "$ref": "v1.LocalObjectReference", - "description": "CHAP secret for iSCSI target and initiator authentication" + "description": "CHAP Secret for iSCSI target and initiator authentication" }, "initiatorName": { "type": "string", - "description": "Custom iSCSI initiator name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface \u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection." + "description": "Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface \u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection." } } }, @@ -7835,7 +7839,7 @@ }, "v1.FlexVolumeSource": { "id": "v1.FlexVolumeSource", - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.", + "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.", "required": [ "driver" ], @@ -8352,15 +8356,15 @@ }, "protectionDomain": { "type": "string", - "description": "The name of the Protection Domain for the configured storage (defaults to \"default\")." + "description": "The name of the ScaleIO Protection Domain for the configured storage." }, "storagePool": { "type": "string", - "description": "The Storage Pool associated with the protection domain (defaults to \"default\")." + "description": "The ScaleIO Storage Pool associated with the protection domain." }, "storageMode": { "type": "string", - "description": "Indicates whether the storage for a volume should be thick or thin (defaults to \"thin\")." + "description": "Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned." }, "volumeName": { "type": "string", @@ -8467,6 +8471,13 @@ }, "description": "Pod volumes to mount into the container's filesystem. Cannot be updated." }, + "volumeDevices": { + "type": "array", + "items": { + "$ref": "v1.VolumeDevice" + }, + "description": "volumeDevices is the list of block devices to be used by the container. This is an alpha feature and may change in the future." + }, "livenessProbe": { "$ref": "v1.Probe", "description": "Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes" @@ -8493,7 +8504,7 @@ }, "securityContext": { "$ref": "v1.SecurityContext", - "description": "Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md" + "description": "Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/" }, "stdin": { "type": "boolean", @@ -8719,6 +8730,24 @@ "id": "v1.MountPropagationMode", "properties": {} }, + "v1.VolumeDevice": { + "id": "v1.VolumeDevice", + "description": "volumeDevice describes a mapping of a raw block device within a container.", + "required": [ + "name", + "devicePath" + ], + "properties": { + "name": { + "type": "string", + "description": "name must match the name of a persistentVolumeClaim in the pod" + }, + "devicePath": { + "type": "string", + "description": "devicePath is the path inside of the container that the device will be mapped to." + } + } + }, "v1.Probe": { "id": "v1.Probe", "description": "Probe describes a health check to be performed against a container to determine whether it is alive or ready to receive traffic.", @@ -9119,7 +9148,10 @@ }, "v1.PodAffinityTerm": { "id": "v1.PodAffinityTerm", - "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key \u003ctopologyKey\u003e tches that of any node on which a pod of the set of pods is running", + "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key \u003ctopologyKey\u003e matches that of any node on which a pod of the set of pods is running", + "required": [ + "topologyKey" + ], "properties": { "labelSelector": { "$ref": "v1.LabelSelector", @@ -9134,7 +9166,7 @@ }, "topologyKey": { "type": "string", - "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. For PreferredDuringScheduling pod anti-affinity, empty topologyKey is interpreted as \"all topologies\" (\"all topologies\" here means all the topologyKeys indicated by scheduler command-line argument --failure-domains); for affinity and for RequiredDuringScheduling pod anti-affinity, empty topologyKey is not allowed." + "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed." } } }, @@ -9221,6 +9253,46 @@ } } }, + "v1.PodDNSConfig": { + "id": "v1.PodDNSConfig", + "description": "PodDNSConfig defines the DNS parameters of a pod in addition to those generated from DNSPolicy.", + "properties": { + "nameservers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of DNS name server IP addresses. This will be appended to the base nameservers generated from DNSPolicy. Duplicated nameservers will be removed." + }, + "searches": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of DNS search domains for host-name lookup. This will be appended to the base search paths generated from DNSPolicy. Duplicated search paths will be removed." + }, + "options": { + "type": "array", + "items": { + "$ref": "v1.PodDNSConfigOption" + }, + "description": "A list of DNS resolver options. This will be merged with the base options generated from DNSPolicy. Duplicated entries will be removed. Resolution options given in Options will override those that appear in the base DNSPolicy." + } + } + }, + "v1.PodDNSConfigOption": { + "id": "v1.PodDNSConfigOption", + "description": "PodDNSConfigOption defines DNS resolver options of a pod.", + "properties": { + "name": { + "type": "string", + "description": "Required." + }, + "value": { + "type": "string" + } + } + }, "v1beta1.DaemonSetUpdateStrategy": { "id": "v1beta1.DaemonSetUpdateStrategy", "properties": { @@ -9298,6 +9370,43 @@ "type": "integer", "format": "int32", "description": "Count of hash collisions for the DaemonSet. The DaemonSet controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ControllerRevision." + }, + "conditions": { + "type": "array", + "items": { + "$ref": "v1beta1.DaemonSetCondition" + }, + "description": "Represents the latest available observations of a DaemonSet's current state." + } + } + }, + "v1beta1.DaemonSetCondition": { + "id": "v1beta1.DaemonSetCondition", + "description": "DaemonSetCondition describes the state of a DaemonSet at a certain point.", + "required": [ + "type", + "status" + ], + "properties": { + "type": { + "type": "string", + "description": "Type of DaemonSet condition." + }, + "status": { + "type": "string", + "description": "Status of the condition, one of True, False, Unknown." + }, + "lastTransitionTime": { + "type": "string", + "description": "Last time the condition transitioned from one status to another." + }, + "reason": { + "type": "string", + "description": "The reason for the condition's last transition." + }, + "message": { + "type": "string", + "description": "A human readable message indicating details about the transition." } } }, @@ -9348,7 +9457,7 @@ }, "propagationPolicy": { "$ref": "v1.DeletionPropagation", - "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." + "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." } } }, @@ -9881,7 +9990,7 @@ }, "v1beta1.NetworkPolicyList": { "id": "v1beta1.NetworkPolicyList", - "description": "Network Policy List is a list of NetworkPolicy objects.", + "description": "DEPRECATED 1.9 - This group version of NetworkPolicyList is deprecated by networking/v1/NetworkPolicyList. Network Policy List is a list of NetworkPolicy objects.", "required": [ "items" ], @@ -9909,7 +10018,7 @@ }, "v1beta1.NetworkPolicy": { "id": "v1beta1.NetworkPolicy", - "description": "NetworkPolicy describes what network traffic is allowed for a set of Pods", + "description": "DEPRECATED 1.9 - This group version of NetworkPolicy is deprecated by networking/v1/NetworkPolicy. NetworkPolicy describes what network traffic is allowed for a set of Pods", "properties": { "kind": { "type": "string", @@ -9931,6 +10040,7 @@ }, "v1beta1.NetworkPolicySpec": { "id": "v1beta1.NetworkPolicySpec", + "description": "DEPRECATED 1.9 - This group version of NetworkPolicySpec is deprecated by networking/v1/NetworkPolicySpec.", "required": [ "podSelector" ], @@ -9964,7 +10074,7 @@ }, "v1beta1.NetworkPolicyIngressRule": { "id": "v1beta1.NetworkPolicyIngressRule", - "description": "This NetworkPolicyIngressRule matches traffic if and only if the traffic matches both ports AND from.", + "description": "DEPRECATED 1.9 - This group version of NetworkPolicyIngressRule is deprecated by networking/v1/NetworkPolicyIngressRule. This NetworkPolicyIngressRule matches traffic if and only if the traffic matches both ports AND from.", "properties": { "ports": { "type": "array", @@ -9984,6 +10094,7 @@ }, "v1beta1.NetworkPolicyPort": { "id": "v1beta1.NetworkPolicyPort", + "description": "DEPRECATED 1.9 - This group version of NetworkPolicyPort is deprecated by networking/v1/NetworkPolicyPort.", "properties": { "protocol": { "$ref": "v1.Protocol", @@ -10001,6 +10112,7 @@ }, "v1beta1.NetworkPolicyPeer": { "id": "v1beta1.NetworkPolicyPeer", + "description": "DEPRECATED 1.9 - This group version of NetworkPolicyPeer is deprecated by networking/v1/NetworkPolicyPeer.", "properties": { "podSelector": { "$ref": "v1.LabelSelector", @@ -10018,7 +10130,7 @@ }, "v1beta1.IPBlock": { "id": "v1beta1.IPBlock", - "description": "IPBlock describes a particular CIDR (Ex. \"192.168.1.1/24\") that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The except entry describes CIDRs that should not be included within this rule.", + "description": "DEPRECATED 1.9 - This group version of IPBlock is deprecated by networking/v1/IPBlock. IPBlock describes a particular CIDR (Ex. \"192.168.1.1/24\") that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The except entry describes CIDRs that should not be included within this rule.", "required": [ "cidr" ], @@ -10038,7 +10150,7 @@ }, "v1beta1.NetworkPolicyEgressRule": { "id": "v1beta1.NetworkPolicyEgressRule", - "description": "NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8", + "description": "DEPRECATED 1.9 - This group version of NetworkPolicyEgressRule is deprecated by networking/v1/NetworkPolicyEgressRule. NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8", "properties": { "ports": { "type": "array", @@ -10129,7 +10241,7 @@ "items": { "$ref": "v1.Capability" }, - "description": "DefaultAddCapabilities is the default set of capabilities that will be added to the container unless the pod spec specifically drops the capability. You may not list a capabiility in both DefaultAddCapabilities and RequiredDropCapabilities." + "description": "DefaultAddCapabilities is the default set of capabilities that will be added to the container unless the pod spec specifically drops the capability. You may not list a capability in both DefaultAddCapabilities and RequiredDropCapabilities. Capabilities added here are implicitly allowed, and need not be included in the AllowedCapabilities list." }, "requiredDropCapabilities": { "type": "array", @@ -10205,6 +10317,13 @@ "$ref": "v1beta1.AllowedHostPath" }, "description": "is a white list of allowed host paths. Empty indicates that all host paths may be used." + }, + "allowedFlexVolumes": { + "type": "array", + "items": { + "$ref": "v1beta1.AllowedFlexVolume" + }, + "description": "AllowedFlexVolumes is a whitelist of allowed Flexvolumes. Empty or nil indicates that all Flexvolumes may be used. This parameter is effective only when the usage of the Flexvolumes is allowed in the \"Volumes\" field." } } }, @@ -10245,7 +10364,7 @@ }, "seLinuxOptions": { "$ref": "v1.SELinuxOptions", - "description": "seLinuxOptions required to run as; required for MustRunAs More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md" + "description": "seLinuxOptions required to run as; required for MustRunAs More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/" } } }, @@ -10333,6 +10452,19 @@ } } }, + "v1beta1.AllowedFlexVolume": { + "id": "v1beta1.AllowedFlexVolume", + "description": "AllowedFlexVolume represents a single Flexvolume that is allowed to be used.", + "required": [ + "driver" + ], + "properties": { + "driver": { + "type": "string", + "description": "Driver is the name of the Flexvolume driver." + } + } + }, "v1beta1.ReplicaSetList": { "id": "v1beta1.ReplicaSetList", "description": "ReplicaSetList is a collection of ReplicaSets.", diff --git a/api/swagger-spec/networking.k8s.io_v1.json b/api/swagger-spec/networking.k8s.io_v1.json index d11ed82b376..fd4634e9e1f 100644 --- a/api/swagger-spec/networking.k8s.io_v1.json +++ b/api/swagger-spec/networking.k8s.io_v1.json @@ -616,7 +616,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -1112,7 +1112,7 @@ }, "deletionTimestamp": { "type": "string", - "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 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/api-conventions.md#metadata" + "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/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "type": "integer", @@ -1534,7 +1534,7 @@ }, "propagationPolicy": { "$ref": "v1.DeletionPropagation", - "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." + "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." } } }, diff --git a/api/swagger-spec/policy_v1beta1.json b/api/swagger-spec/policy_v1beta1.json index 884b287e27f..18b6119d90a 100644 --- a/api/swagger-spec/policy_v1beta1.json +++ b/api/swagger-spec/policy_v1beta1.json @@ -616,7 +616,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -1283,7 +1283,7 @@ }, "deletionTimestamp": { "type": "string", - "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 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/api-conventions.md#metadata" + "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/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "type": "integer", @@ -1631,7 +1631,7 @@ }, "propagationPolicy": { "$ref": "v1.DeletionPropagation", - "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." + "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." } } }, diff --git a/api/swagger-spec/rbac.authorization.k8s.io_v1.json b/api/swagger-spec/rbac.authorization.k8s.io_v1.json index cd46e7dfd63..722df344c70 100644 --- a/api/swagger-spec/rbac.authorization.k8s.io_v1.json +++ b/api/swagger-spec/rbac.authorization.k8s.io_v1.json @@ -544,7 +544,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -1222,7 +1222,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -1956,7 +1956,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -2912,7 +2912,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -3419,7 +3419,7 @@ }, "deletionTimestamp": { "type": "string", - "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 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/api-conventions.md#metadata" + "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/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "type": "integer", @@ -3714,7 +3714,7 @@ }, "propagationPolicy": { "$ref": "v1.DeletionPropagation", - "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." + "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." } } }, @@ -3789,6 +3789,10 @@ "$ref": "v1.PolicyRule" }, "description": "Rules holds all the PolicyRules for this ClusterRole" + }, + "aggregationRule": { + "$ref": "v1.AggregationRule", + "description": "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 stomped by the controller." } } }, @@ -3836,6 +3840,61 @@ } } }, + "v1.AggregationRule": { + "id": "v1.AggregationRule", + "description": "AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole", + "properties": { + "clusterRoleSelectors": { + "type": "array", + "items": { + "$ref": "v1.LabelSelector" + }, + "description": "ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. If any of the selectors match, then the ClusterRole's permissions will be added" + } + } + }, + "v1.LabelSelector": { + "id": "v1.LabelSelector", + "description": "A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.", + "properties": { + "matchLabels": { + "type": "object", + "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed." + }, + "matchExpressions": { + "type": "array", + "items": { + "$ref": "v1.LabelSelectorRequirement" + }, + "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed." + } + } + }, + "v1.LabelSelectorRequirement": { + "id": "v1.LabelSelectorRequirement", + "description": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + "required": [ + "key", + "operator" + ], + "properties": { + "key": { + "type": "string", + "description": "key is the label key that the selector applies to." + }, + "operator": { + "type": "string", + "description": "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist." + }, + "values": { + "type": "array", + "items": { + "type": "string" + }, + "description": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch." + } + } + }, "v1.RoleBindingList": { "id": "v1.RoleBindingList", "description": "RoleBindingList is a collection of RoleBindings", diff --git a/api/swagger-spec/rbac.authorization.k8s.io_v1alpha1.json b/api/swagger-spec/rbac.authorization.k8s.io_v1alpha1.json index e258ca81fd1..a12516c92a8 100644 --- a/api/swagger-spec/rbac.authorization.k8s.io_v1alpha1.json +++ b/api/swagger-spec/rbac.authorization.k8s.io_v1alpha1.json @@ -544,7 +544,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -1222,7 +1222,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -1956,7 +1956,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -2912,7 +2912,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -3419,7 +3419,7 @@ }, "deletionTimestamp": { "type": "string", - "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 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/api-conventions.md#metadata" + "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/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "type": "integer", @@ -3714,7 +3714,7 @@ }, "propagationPolicy": { "$ref": "v1.DeletionPropagation", - "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." + "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." } } }, @@ -3789,6 +3789,10 @@ "$ref": "v1alpha1.PolicyRule" }, "description": "Rules holds all the PolicyRules for this ClusterRole" + }, + "aggregationRule": { + "$ref": "v1alpha1.AggregationRule", + "description": "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 stomped by the controller." } } }, @@ -3836,6 +3840,61 @@ } } }, + "v1alpha1.AggregationRule": { + "id": "v1alpha1.AggregationRule", + "description": "AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole", + "properties": { + "clusterRoleSelectors": { + "type": "array", + "items": { + "$ref": "v1.LabelSelector" + }, + "description": "ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. If any of the selectors match, then the ClusterRole's permissions will be added" + } + } + }, + "v1.LabelSelector": { + "id": "v1.LabelSelector", + "description": "A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.", + "properties": { + "matchLabels": { + "type": "object", + "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed." + }, + "matchExpressions": { + "type": "array", + "items": { + "$ref": "v1.LabelSelectorRequirement" + }, + "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed." + } + } + }, + "v1.LabelSelectorRequirement": { + "id": "v1.LabelSelectorRequirement", + "description": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + "required": [ + "key", + "operator" + ], + "properties": { + "key": { + "type": "string", + "description": "key is the label key that the selector applies to." + }, + "operator": { + "type": "string", + "description": "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist." + }, + "values": { + "type": "array", + "items": { + "type": "string" + }, + "description": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch." + } + } + }, "v1alpha1.RoleBindingList": { "id": "v1alpha1.RoleBindingList", "description": "RoleBindingList is a collection of RoleBindings", diff --git a/api/swagger-spec/rbac.authorization.k8s.io_v1beta1.json b/api/swagger-spec/rbac.authorization.k8s.io_v1beta1.json index b52fcc084ed..880d4b1c73b 100644 --- a/api/swagger-spec/rbac.authorization.k8s.io_v1beta1.json +++ b/api/swagger-spec/rbac.authorization.k8s.io_v1beta1.json @@ -544,7 +544,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -1222,7 +1222,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -1956,7 +1956,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -2912,7 +2912,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -3419,7 +3419,7 @@ }, "deletionTimestamp": { "type": "string", - "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 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/api-conventions.md#metadata" + "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/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "type": "integer", @@ -3714,7 +3714,7 @@ }, "propagationPolicy": { "$ref": "v1.DeletionPropagation", - "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." + "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." } } }, @@ -3789,6 +3789,10 @@ "$ref": "v1beta1.PolicyRule" }, "description": "Rules holds all the PolicyRules for this ClusterRole" + }, + "aggregationRule": { + "$ref": "v1beta1.AggregationRule", + "description": "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 stomped by the controller." } } }, @@ -3818,7 +3822,7 @@ "items": { "type": "string" }, - "description": "Resources is a list of resources this rule applies to. ResourceAll represents all resources." + "description": "Resources is a list of resources this rule applies to. '*' represents all resources in the specified apiGroups. '*/foo' represents the subresource 'foo' for all resources in the specified apiGroups." }, "resourceNames": { "type": "array", @@ -3836,6 +3840,61 @@ } } }, + "v1beta1.AggregationRule": { + "id": "v1beta1.AggregationRule", + "description": "AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole", + "properties": { + "clusterRoleSelectors": { + "type": "array", + "items": { + "$ref": "v1.LabelSelector" + }, + "description": "ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. If any of the selectors match, then the ClusterRole's permissions will be added" + } + } + }, + "v1.LabelSelector": { + "id": "v1.LabelSelector", + "description": "A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.", + "properties": { + "matchLabels": { + "type": "object", + "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed." + }, + "matchExpressions": { + "type": "array", + "items": { + "$ref": "v1.LabelSelectorRequirement" + }, + "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed." + } + } + }, + "v1.LabelSelectorRequirement": { + "id": "v1.LabelSelectorRequirement", + "description": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + "required": [ + "key", + "operator" + ], + "properties": { + "key": { + "type": "string", + "description": "key is the label key that the selector applies to." + }, + "operator": { + "type": "string", + "description": "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist." + }, + "values": { + "type": "array", + "items": { + "type": "string" + }, + "description": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch." + } + } + }, "v1beta1.RoleBindingList": { "id": "v1beta1.RoleBindingList", "description": "RoleBindingList is a collection of RoleBindings", diff --git a/api/swagger-spec/resourceListing.json b/api/swagger-spec/resourceListing.json index cf0a4eb572a..3eadc0bae42 100644 --- a/api/swagger-spec/resourceListing.json +++ b/api/swagger-spec/resourceListing.json @@ -145,6 +145,10 @@ "path": "/apis/storage.k8s.io/v1beta1", "description": "API at /apis/storage.k8s.io/v1beta1" }, + { + "path": "/apis/storage.k8s.io/v1alpha1", + "description": "API at /apis/storage.k8s.io/v1alpha1" + }, { "path": "/apis/storage.k8s.io", "description": "get information of a group" @@ -165,6 +169,10 @@ "path": "/apis/apps", "description": "get information of a group" }, + { + "path": "/apis/admissionregistration.k8s.io/v1beta1", + "description": "API at /apis/admissionregistration.k8s.io/v1beta1" + }, { "path": "/apis/admissionregistration.k8s.io/v1alpha1", "description": "API at /apis/admissionregistration.k8s.io/v1alpha1" @@ -172,6 +180,14 @@ { "path": "/apis/admissionregistration.k8s.io", "description": "get information of a group" + }, + { + "path": "/apis/events.k8s.io/v1beta1", + "description": "API at /apis/events.k8s.io/v1beta1" + }, + { + "path": "/apis/events.k8s.io", + "description": "get information of a group" } ], "apiVersion": "", diff --git a/api/swagger-spec/scheduling.k8s.io_v1alpha1.json b/api/swagger-spec/scheduling.k8s.io_v1alpha1.json index ba86df62240..76bec8c02df 100644 --- a/api/swagger-spec/scheduling.k8s.io_v1alpha1.json +++ b/api/swagger-spec/scheduling.k8s.io_v1alpha1.json @@ -560,7 +560,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -846,7 +846,7 @@ }, "deletionTimestamp": { "type": "string", - "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 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/api-conventions.md#metadata" + "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/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "type": "integer", @@ -1092,7 +1092,7 @@ }, "propagationPolicy": { "$ref": "v1.DeletionPropagation", - "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." + "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." } } }, diff --git a/api/swagger-spec/settings.k8s.io_v1alpha1.json b/api/swagger-spec/settings.k8s.io_v1alpha1.json index c7290358a32..dc442a8ebbe 100644 --- a/api/swagger-spec/settings.k8s.io_v1alpha1.json +++ b/api/swagger-spec/settings.k8s.io_v1alpha1.json @@ -616,7 +616,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -1110,7 +1110,7 @@ }, "deletionTimestamp": { "type": "string", - "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 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/api-conventions.md#metadata" + "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/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "type": "integer", @@ -1615,7 +1615,7 @@ }, "flexVolume": { "$ref": "v1.FlexVolumeSource", - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future." + "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin." }, "cinder": { "$ref": "v1.CinderVolumeSource", @@ -1869,7 +1869,7 @@ "properties": { "targetPortal": { "type": "string", - "description": "iSCSI target portal. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." + "description": "iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." }, "iqn": { "type": "string", @@ -1878,11 +1878,11 @@ "lun": { "type": "integer", "format": "int32", - "description": "iSCSI target lun number." + "description": "iSCSI Target Lun number." }, "iscsiInterface": { "type": "string", - "description": "Optional: Defaults to 'default' (tcp). iSCSI interface name that uses an iSCSI transport." + "description": "iSCSI Interface Name that uses an iSCSI transport. Defaults to 'default' (tcp)." }, "fsType": { "type": "string", @@ -1897,7 +1897,7 @@ "items": { "type": "string" }, - "description": "iSCSI target portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." + "description": "iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." }, "chapAuthDiscovery": { "type": "boolean", @@ -1909,11 +1909,11 @@ }, "secretRef": { "$ref": "v1.LocalObjectReference", - "description": "CHAP secret for iSCSI target and initiator authentication" + "description": "CHAP Secret for iSCSI target and initiator authentication" }, "initiatorName": { "type": "string", - "description": "Custom iSCSI initiator name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface \u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection." + "description": "Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface \u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection." } } }, @@ -2013,7 +2013,7 @@ }, "v1.FlexVolumeSource": { "id": "v1.FlexVolumeSource", - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.", + "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.", "required": [ "driver" ], @@ -2492,15 +2492,15 @@ }, "protectionDomain": { "type": "string", - "description": "The name of the Protection Domain for the configured storage (defaults to \"default\")." + "description": "The name of the ScaleIO Protection Domain for the configured storage." }, "storagePool": { "type": "string", - "description": "The Storage Pool associated with the protection domain (defaults to \"default\")." + "description": "The ScaleIO Storage Pool associated with the protection domain." }, "storageMode": { "type": "string", - "description": "Indicates whether the storage for a volume should be thick or thin (defaults to \"thin\")." + "description": "Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned." }, "volumeName": { "type": "string", @@ -2623,7 +2623,7 @@ }, "propagationPolicy": { "$ref": "v1.DeletionPropagation", - "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." + "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." } } }, diff --git a/api/swagger-spec/storage.k8s.io_v1.json b/api/swagger-spec/storage.k8s.io_v1.json index 8e379de74e3..5757f49f5c9 100644 --- a/api/swagger-spec/storage.k8s.io_v1.json +++ b/api/swagger-spec/storage.k8s.io_v1.json @@ -560,7 +560,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -814,6 +814,10 @@ "allowVolumeExpansion": { "type": "boolean", "description": "AllowVolumeExpansion shows whether the storage class allow volume expand" + }, + "volumeBindingMode": { + "$ref": "v1.VolumeBindingMode", + "description": "VolumeBindingMode indicates how PersistentVolumeClaims should be provisioned and bound. When unset, VolumeBindingImmediate is used. This field is alpha-level and is only honored by servers that enable the VolumeScheduling feature." } } }, @@ -856,7 +860,7 @@ }, "deletionTimestamp": { "type": "string", - "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 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/api-conventions.md#metadata" + "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/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "type": "integer", @@ -1059,6 +1063,10 @@ "id": "v1.PersistentVolumeReclaimPolicy", "properties": {} }, + "v1.VolumeBindingMode": { + "id": "v1.VolumeBindingMode", + "properties": {} + }, "v1.WatchEvent": { "id": "v1.WatchEvent", "required": [ @@ -1106,7 +1114,7 @@ }, "propagationPolicy": { "$ref": "v1.DeletionPropagation", - "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." + "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." } } }, diff --git a/api/swagger-spec/storage.k8s.io_v1alpha1.json b/api/swagger-spec/storage.k8s.io_v1alpha1.json new file mode 100644 index 00000000000..2d7704b18e8 --- /dev/null +++ b/api/swagger-spec/storage.k8s.io_v1alpha1.json @@ -0,0 +1,1272 @@ +{ + "swaggerVersion": "1.2", + "apiVersion": "storage.k8s.io/v1alpha1", + "basePath": "https://10.10.10.10:6443", + "resourcePath": "/apis/storage.k8s.io/v1alpha1", + "info": { + "title": "", + "description": "" + }, + "apis": [ + { + "path": "/apis/storage.k8s.io/v1alpha1/volumeattachments", + "description": "API at /apis/storage.k8s.io/v1alpha1", + "operations": [ + { + "type": "v1alpha1.VolumeAttachmentList", + "method": "GET", + "summary": "list or watch objects of kind VolumeAttachment", + "nickname": "listVolumeAttachment", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1alpha1.VolumeAttachmentList" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1alpha1.VolumeAttachment", + "method": "POST", + "summary": "create a VolumeAttachment", + "nickname": "createVolumeAttachment", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1alpha1.VolumeAttachment", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1alpha1.VolumeAttachment" + }, + { + "code": 201, + "message": "Created", + "responseModel": "v1alpha1.VolumeAttachment" + }, + { + "code": 202, + "message": "Accepted", + "responseModel": "v1alpha1.VolumeAttachment" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1.Status", + "method": "DELETE", + "summary": "delete collection of VolumeAttachment", + "nickname": "deletecollectionVolumeAttachment", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Status" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/storage.k8s.io/v1alpha1/watch/volumeattachments", + "description": "API at /apis/storage.k8s.io/v1alpha1", + "operations": [ + { + "type": "v1.WatchEvent", + "method": "GET", + "summary": "watch individual changes to a list of VolumeAttachment", + "nickname": "watchVolumeAttachmentList", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.WatchEvent" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/storage.k8s.io/v1alpha1/volumeattachments/{name}", + "description": "API at /apis/storage.k8s.io/v1alpha1", + "operations": [ + { + "type": "v1alpha1.VolumeAttachment", + "method": "GET", + "summary": "read the specified VolumeAttachment", + "nickname": "readVolumeAttachment", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "export", + "description": "Should this value be exported. Export strips fields that a user can not specify.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "exact", + "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the VolumeAttachment", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1alpha1.VolumeAttachment" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1alpha1.VolumeAttachment", + "method": "PUT", + "summary": "replace the specified VolumeAttachment", + "nickname": "replaceVolumeAttachment", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1alpha1.VolumeAttachment", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the VolumeAttachment", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1alpha1.VolumeAttachment" + }, + { + "code": 201, + "message": "Created", + "responseModel": "v1alpha1.VolumeAttachment" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1alpha1.VolumeAttachment", + "method": "PATCH", + "summary": "partially update the specified VolumeAttachment", + "nickname": "patchVolumeAttachment", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.Patch", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the VolumeAttachment", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1alpha1.VolumeAttachment" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ] + }, + { + "type": "v1.Status", + "method": "DELETE", + "summary": "delete a VolumeAttachment", + "nickname": "deleteVolumeAttachment", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.DeleteOptions", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the VolumeAttachment", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.Status" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/storage.k8s.io/v1alpha1/watch/volumeattachments/{name}", + "description": "API at /apis/storage.k8s.io/v1alpha1", + "operations": [ + { + "type": "v1.WatchEvent", + "method": "GET", + "summary": "watch changes to an object of kind VolumeAttachment", + "nickname": "watchVolumeAttachment", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "includeUninitialized", + "description": "If true, partially initialized resources are included in the response.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "limit", + "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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "continue", + "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 indicating the client must restart their list without the continue field. This 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.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the VolumeAttachment", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.WatchEvent" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/storage.k8s.io/v1alpha1", + "description": "API at /apis/storage.k8s.io/v1alpha1", + "operations": [ + { + "type": "v1.APIResourceList", + "method": "GET", + "summary": "get available resources", + "nickname": "getAPIResources", + "parameters": [], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ] + } + ] + } + ], + "models": { + "v1alpha1.VolumeAttachmentList": { + "id": "v1alpha1.VolumeAttachmentList", + "description": "VolumeAttachmentList is a collection of VolumeAttachment objects.", + "required": [ + "items" + ], + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "metadata": { + "$ref": "v1.ListMeta", + "description": "Standard list metadata More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" + }, + "items": { + "type": "array", + "items": { + "$ref": "v1alpha1.VolumeAttachment" + }, + "description": "Items is the list of VolumeAttachments" + } + } + }, + "v1.ListMeta": { + "id": "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": { + "selfLink": { + "type": "string", + "description": "selfLink is a URL representing this object. Populated by the system. Read-only." + }, + "resourceVersion": { + "type": "string", + "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/api-conventions.md#concurrency-control-and-consistency" + }, + "continue": { + "type": "string", + "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 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." + } + } + }, + "v1alpha1.VolumeAttachment": { + "id": "v1alpha1.VolumeAttachment", + "description": "VolumeAttachment captures the intent to attach or detach the specified volume to/from the specified node.\n\nVolumeAttachment objects are non-namespaced.", + "required": [ + "spec" + ], + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "metadata": { + "$ref": "v1.ObjectMeta", + "description": "Standard object metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" + }, + "spec": { + "$ref": "v1alpha1.VolumeAttachmentSpec", + "description": "Specification of the desired attach/detach volume behavior. Populated by the Kubernetes system." + }, + "status": { + "$ref": "v1alpha1.VolumeAttachmentStatus", + "description": "Status of the VolumeAttachment request. Populated by the entity completing the attach or detach operation, i.e. the external-attacher." + } + } + }, + "v1.ObjectMeta": { + "id": "v1.ObjectMeta", + "description": "ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.", + "properties": { + "name": { + "type": "string", + "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: http://kubernetes.io/docs/user-guide/identifiers#names" + }, + "generateName": { + "type": "string", + "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 NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header).\n\nApplied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency" + }, + "namespace": { + "type": "string", + "description": "Namespace defines the space within 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: http://kubernetes.io/docs/user-guide/namespaces" + }, + "selfLink": { + "type": "string", + "description": "SelfLink is a URL representing this object. Populated by the system. Read-only." + }, + "uid": { + "type": "string", + "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: http://kubernetes.io/docs/user-guide/identifiers#uids" + }, + "resourceVersion": { + "type": "string", + "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/api-conventions.md#concurrency-control-and-consistency" + }, + "generation": { + "type": "integer", + "format": "int64", + "description": "A sequence number representing a specific generation of the desired state. Populated by the system. Read-only." + }, + "creationTimestamp": { + "type": "string", + "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/api-conventions.md#metadata" + }, + "deletionTimestamp": { + "type": "string", + "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/api-conventions.md#metadata" + }, + "deletionGracePeriodSeconds": { + "type": "integer", + "format": "int64", + "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." + }, + "labels": { + "type": "object", + "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: http://kubernetes.io/docs/user-guide/labels" + }, + "annotations": { + "type": "object", + "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: http://kubernetes.io/docs/user-guide/annotations" + }, + "ownerReferences": { + "type": "array", + "items": { + "$ref": "v1.OwnerReference" + }, + "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." + }, + "initializers": { + "$ref": "v1.Initializers", + "description": "An initializer is a controller which enforces some system invariant at object creation time. This field is a list of initializers that have not yet acted on this object. If nil or empty, this object has been completely initialized. Otherwise, the object is considered uninitialized and is hidden (in list/watch and get calls) from clients that haven't explicitly asked to observe uninitialized objects.\n\nWhen an object is created, the system will populate this list with the current set of initializers. Only privileged users may set or modify this list. Once it is empty, it may not be modified further by any user." + }, + "finalizers": { + "type": "array", + "items": { + "type": "string" + }, + "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." + }, + "clusterName": { + "type": "string", + "description": "The name of the cluster which the object belongs to. This is used to distinguish resources with same name and namespace in different clusters. This field is not set anywhere right now and apiserver is going to ignore it if set in create or update request." + } + } + }, + "v1.OwnerReference": { + "id": "v1.OwnerReference", + "description": "OwnerReference contains enough information to let you identify an owning object. Currently, an owning object must be in the same namespace, so there is no namespace field.", + "required": [ + "apiVersion", + "kind", + "name", + "uid" + ], + "properties": { + "apiVersion": { + "type": "string", + "description": "API version of the referent." + }, + "kind": { + "type": "string", + "description": "Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds" + }, + "name": { + "type": "string", + "description": "Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names" + }, + "uid": { + "type": "string", + "description": "UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids" + }, + "controller": { + "type": "boolean", + "description": "If true, this reference points to the managing controller." + }, + "blockOwnerDeletion": { + "type": "boolean", + "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. Defaults to false. To set this field, a user needs \"delete\" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned." + } + } + }, + "v1.Initializers": { + "id": "v1.Initializers", + "description": "Initializers tracks the progress of initialization.", + "required": [ + "pending" + ], + "properties": { + "pending": { + "type": "array", + "items": { + "$ref": "v1.Initializer" + }, + "description": "Pending is a list of initializers that must execute in order before this object is visible. When the last pending initializer is removed, and no failing result is set, the initializers struct will be set to nil and the object is considered as initialized and visible to all clients." + }, + "result": { + "$ref": "v1.Status", + "description": "If result is set with the Failure field, the object will be persisted to storage and then deleted, ensuring that other clients can observe the deletion." + } + } + }, + "v1.Initializer": { + "id": "v1.Initializer", + "description": "Initializer is information about an initializer that has not yet completed.", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "name of the process that is responsible for initializing this object." + } + } + }, + "v1.Status": { + "id": "v1.Status", + "description": "Status is a return value for calls that don't return other objects.", + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "metadata": { + "$ref": "v1.ListMeta", + "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds" + }, + "status": { + "type": "string", + "description": "Status of the operation. One of: \"Success\" or \"Failure\". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status" + }, + "message": { + "type": "string", + "description": "A human-readable description of the status of this operation." + }, + "reason": { + "type": "string", + "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." + }, + "details": { + "$ref": "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." + }, + "code": { + "type": "integer", + "format": "int32", + "description": "Suggested HTTP return code for this status, 0 if not set." + } + } + }, + "v1.StatusDetails": { + "id": "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": { + "name": { + "type": "string", + "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described)." + }, + "group": { + "type": "string", + "description": "The group attribute of the resource associated with the status StatusReason." + }, + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "uid": { + "type": "string", + "description": "UID of the resource. (when there is a single resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids" + }, + "causes": { + "type": "array", + "items": { + "$ref": "v1.StatusCause" + }, + "description": "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes." + }, + "retryAfterSeconds": { + "type": "integer", + "format": "int32", + "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." + } + } + }, + "v1.StatusCause": { + "id": "v1.StatusCause", + "description": "StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.", + "properties": { + "reason": { + "type": "string", + "description": "A machine-readable description of the cause of the error. If this value is empty there is no information available." + }, + "message": { + "type": "string", + "description": "A human-readable description of the cause of the error. This field may be presented as-is to a reader." + }, + "field": { + "type": "string", + "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\"" + } + } + }, + "v1alpha1.VolumeAttachmentSpec": { + "id": "v1alpha1.VolumeAttachmentSpec", + "description": "VolumeAttachmentSpec is the specification of a VolumeAttachment request.", + "required": [ + "attacher", + "source", + "nodeName" + ], + "properties": { + "attacher": { + "type": "string", + "description": "Attacher indicates the name of the volume driver that MUST handle this request. This is the name returned by GetPluginName()." + }, + "source": { + "$ref": "v1alpha1.VolumeAttachmentSource", + "description": "Source represents the volume that should be attached." + }, + "nodeName": { + "type": "string", + "description": "The node that the volume should be attached to." + } + } + }, + "v1alpha1.VolumeAttachmentSource": { + "id": "v1alpha1.VolumeAttachmentSource", + "description": "VolumeAttachmentSource represents a volume that should be attached. Right now only PersistenVolumes can be attached via external attacher, in future we may allow also inline volumes in pods. Exactly one member can be set.", + "properties": { + "persistentVolumeName": { + "type": "string", + "description": "Name of the persistent volume to attach." + } + } + }, + "v1alpha1.VolumeAttachmentStatus": { + "id": "v1alpha1.VolumeAttachmentStatus", + "description": "VolumeAttachmentStatus is the status of a VolumeAttachment request.", + "required": [ + "attached" + ], + "properties": { + "attached": { + "type": "boolean", + "description": "Indicates the volume is successfully attached. This field must only be set by the entity completing the attach operation, i.e. the external-attacher." + }, + "attachmentMetadata": { + "type": "object", + "description": "Upon successful attach, this field is populated with any information returned by the attach operation that must be passed into subsequent WaitForAttach or Mount calls. This field must only be set by the entity completing the attach operation, i.e. the external-attacher." + }, + "attachError": { + "$ref": "v1alpha1.VolumeError", + "description": "The last error encountered during attach operation, if any. This field must only be set by the entity completing the attach operation, i.e. the external-attacher." + }, + "detachError": { + "$ref": "v1alpha1.VolumeError", + "description": "The last error encountered during detach operation, if any. This field must only be set by the entity completing the detach operation, i.e. the external-attacher." + } + } + }, + "v1alpha1.VolumeError": { + "id": "v1alpha1.VolumeError", + "description": "VolumeError captures an error encountered during a volume operation.", + "properties": { + "time": { + "type": "string", + "description": "Time the error was encountered." + }, + "message": { + "type": "string", + "description": "String detailing the error encountered during Attach or Detach operation. This string maybe logged, so it should not contain sensitive information." + } + } + }, + "v1.WatchEvent": { + "id": "v1.WatchEvent", + "required": [ + "type", + "object" + ], + "properties": { + "type": { + "type": "string" + }, + "object": { + "type": "string" + } + } + }, + "v1.Patch": { + "id": "v1.Patch", + "description": "Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.", + "properties": {} + }, + "v1.DeleteOptions": { + "id": "v1.DeleteOptions", + "description": "DeleteOptions may be provided when deleting an API object.", + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "gracePeriodSeconds": { + "type": "integer", + "format": "int64", + "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." + }, + "preconditions": { + "$ref": "v1.Preconditions", + "description": "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned." + }, + "orphanDependents": { + "type": "boolean", + "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." + }, + "propagationPolicy": { + "$ref": "v1.DeletionPropagation", + "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." + } + } + }, + "v1.Preconditions": { + "id": "v1.Preconditions", + "description": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", + "properties": { + "uid": { + "$ref": "types.UID", + "description": "Specifies the target UID." + } + } + }, + "types.UID": { + "id": "types.UID", + "properties": {} + }, + "v1.DeletionPropagation": { + "id": "v1.DeletionPropagation", + "properties": {} + }, + "v1.APIResourceList": { + "id": "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.", + "required": [ + "groupVersion", + "resources" + ], + "properties": { + "kind": { + "type": "string", + "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/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "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/api-conventions.md#resources" + }, + "groupVersion": { + "type": "string", + "description": "groupVersion is the group and version this APIResourceList is for." + }, + "resources": { + "type": "array", + "items": { + "$ref": "v1.APIResource" + }, + "description": "resources contains the name of the resources and if they are namespaced." + } + } + }, + "v1.APIResource": { + "id": "v1.APIResource", + "description": "APIResource specifies the name of a resource and whether it is namespaced.", + "required": [ + "name", + "singularName", + "namespaced", + "kind", + "verbs" + ], + "properties": { + "name": { + "type": "string", + "description": "name is the plural name of the resource." + }, + "singularName": { + "type": "string", + "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." + }, + "namespaced": { + "type": "boolean", + "description": "namespaced indicates if a resource is namespaced or not." + }, + "group": { + "type": "string", + "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\"." + }, + "version": { + "type": "string", + "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)\"." + }, + "kind": { + "type": "string", + "description": "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')" + }, + "verbs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy)" + }, + "shortNames": { + "type": "array", + "items": { + "type": "string" + }, + "description": "shortNames is a list of suggested short names of the resource." + }, + "categories": { + "type": "array", + "items": { + "type": "string" + }, + "description": "categories is a list of the grouped resources this resource belongs to (e.g. 'all')" + } + } + } + } + } diff --git a/api/swagger-spec/storage.k8s.io_v1beta1.json b/api/swagger-spec/storage.k8s.io_v1beta1.json index 9692cb27582..21af56a87bb 100644 --- a/api/swagger-spec/storage.k8s.io_v1beta1.json +++ b/api/swagger-spec/storage.k8s.io_v1beta1.json @@ -560,7 +560,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -814,6 +814,10 @@ "allowVolumeExpansion": { "type": "boolean", "description": "AllowVolumeExpansion shows whether the storage class allow volume expand" + }, + "volumeBindingMode": { + "$ref": "v1beta1.VolumeBindingMode", + "description": "VolumeBindingMode indicates how PersistentVolumeClaims should be provisioned and bound. When unset, VolumeBindingImmediate is used. This field is alpha-level and is only honored by servers that enable the VolumeScheduling feature." } } }, @@ -856,7 +860,7 @@ }, "deletionTimestamp": { "type": "string", - "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 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/api-conventions.md#metadata" + "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/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "type": "integer", @@ -1059,6 +1063,10 @@ "id": "v1.PersistentVolumeReclaimPolicy", "properties": {} }, + "v1beta1.VolumeBindingMode": { + "id": "v1beta1.VolumeBindingMode", + "properties": {} + }, "v1.WatchEvent": { "id": "v1.WatchEvent", "required": [ @@ -1106,7 +1114,7 @@ }, "propagationPolicy": { "$ref": "v1.DeletionPropagation", - "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." + "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." } } }, diff --git a/api/swagger-spec/v1.json b/api/swagger-spec/v1.json index fe4bf3b7666..cc2cebe67cf 100644 --- a/api/swagger-spec/v1.json +++ b/api/swagger-spec/v1.json @@ -827,7 +827,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -1799,7 +1799,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -2771,7 +2771,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -3743,7 +3743,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -4564,7 +4564,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -5462,7 +5462,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -7222,7 +7222,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -8308,7 +8308,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -9204,7 +9204,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -12076,7 +12076,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -13048,7 +13048,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -14360,7 +14360,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -15502,7 +15502,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -16474,7 +16474,7 @@ "type": "string", "paramType": "query", "name": "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.", + "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.", "required": false, "allowMultiple": false }, @@ -19022,7 +19022,7 @@ }, "deletionTimestamp": { "type": "string", - "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 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/api-conventions.md#metadata" + "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/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "type": "integer", @@ -19449,7 +19449,7 @@ }, "propagationPolicy": { "$ref": "v1.DeletionPropagation", - "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." + "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." } } }, @@ -19634,7 +19634,9 @@ "description": "Event is a report of an event somewhere in the cluster.", "required": [ "metadata", - "involvedObject" + "involvedObject", + "reportingComponent", + "reportingInstance" ], "properties": { "kind": { @@ -19681,6 +19683,30 @@ "type": { "type": "string", "description": "Type of this event (Normal, Warning), new types could be added in the future" + }, + "eventTime": { + "type": "string", + "description": "Time when this Event was first observed." + }, + "series": { + "$ref": "v1.EventSeries", + "description": "Data about the Event series this event represents or nil if it's a singleton Event." + }, + "action": { + "type": "string", + "description": "What action was taken/failed regarding to the Regarding object." + }, + "related": { + "$ref": "v1.ObjectReference", + "description": "Optional secondary object for more complex actions." + }, + "reportingComponent": { + "type": "string", + "description": "Name of the controller that emitted this Event, e.g. `kubernetes.io/kubelet`." + }, + "reportingInstance": { + "type": "string", + "description": "ID of the controller instance, e.g. `kubelet-xyzf`." } } }, @@ -19698,6 +19724,25 @@ } } }, + "v1.EventSeries": { + "id": "v1.EventSeries", + "description": "EventSeries contain information on series of events, i.e. thing that was/is happening continously for some time.", + "properties": { + "count": { + "type": "integer", + "format": "int32", + "description": "Number of occurrences in this series up to the last heartbeat time" + }, + "lastObservedTime": { + "type": "string", + "description": "Time of the last occurence observed" + }, + "state": { + "type": "string", + "description": "State of this Series: Ongoing or Finished" + } + } + }, "v1.LimitRangeList": { "id": "v1.LimitRangeList", "description": "LimitRangeList is a list of LimitRange items.", @@ -19722,7 +19767,7 @@ "items": { "$ref": "v1.LimitRange" }, - "description": "Items is a list of LimitRange objects. More info: https://git.k8s.io/community/contributors/design-proposals/admission_control_limit_range.md" + "description": "Items is a list of LimitRange objects. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/" } } }, @@ -19857,7 +19902,7 @@ "items": { "$ref": "v1.FinalizerName" }, - "description": "Finalizers is an opaque list of values that must be empty to permanently remove object from storage. More info: https://git.k8s.io/community/contributors/design-proposals/namespaces.md#finalizers" + "description": "Finalizers is an opaque list of values that must be empty to permanently remove object from storage. More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/" } } }, @@ -19871,7 +19916,7 @@ "properties": { "phase": { "type": "string", - "description": "Phase is the current lifecycle phase of the namespace. More info: https://git.k8s.io/community/contributors/design-proposals/namespaces.md#phases" + "description": "Phase is the current lifecycle phase of the namespace. More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/" } } }, @@ -20323,6 +20368,10 @@ "storageClassName": { "type": "string", "description": "Name of the StorageClass required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1" + }, + "volumeMode": { + "$ref": "v1.PersistentVolumeMode", + "description": "volumeMode defines what type of volume is required by the claim. Value of Filesystem is implied when not included in claim spec. This is an alpha feature and may change in the future." } } }, @@ -20386,6 +20435,10 @@ } } }, + "v1.PersistentVolumeMode": { + "id": "v1.PersistentVolumeMode", + "properties": {} + }, "v1.PersistentVolumeClaimStatus": { "id": "v1.PersistentVolumeClaimStatus", "description": "PersistentVolumeClaimStatus is the current status of a persistent volume claim.", @@ -20529,11 +20582,11 @@ "description": "NFS represents an NFS mount on the host. Provisioned by an admin. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs" }, "rbd": { - "$ref": "v1.RBDVolumeSource", + "$ref": "v1.RBDPersistentVolumeSource", "description": "RBD represents a Rados Block Device mount on the host that shares a pod's lifetime. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md" }, "iscsi": { - "$ref": "v1.ISCSIVolumeSource", + "$ref": "v1.ISCSIPersistentVolumeSource", "description": "ISCSI represents an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod. Provisioned by an admin." }, "cinder": { @@ -20553,8 +20606,8 @@ "description": "Flocker represents a Flocker volume attached to a kubelet's host machine and exposed to the pod for its usage. This depends on the Flocker control service being running" }, "flexVolume": { - "$ref": "v1.FlexVolumeSource", - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future." + "$ref": "v1.FlexPersistentVolumeSource", + "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin." }, "azureFile": { "$ref": "v1.AzureFilePersistentVolumeSource", @@ -20581,7 +20634,7 @@ "description": "PortworxVolume represents a portworx volume attached and mounted on kubelets host machine" }, "scaleIO": { - "$ref": "v1.ScaleIOVolumeSource", + "$ref": "v1.ScaleIOPersistentVolumeSource", "description": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes." }, "local": { @@ -20592,6 +20645,10 @@ "$ref": "v1.StorageOSPersistentVolumeSource", "description": "StorageOS represents a StorageOS volume that is attached to the kubelet's host machine and mounted into the pod More info: https://releases.k8s.io/HEAD/examples/volumes/storageos/README.md" }, + "csi": { + "$ref": "v1.CSIPersistentVolumeSource", + "description": "CSI represents storage that handled by an external CSI driver" + }, "accessModes": { "type": "array", "items": { @@ -20617,6 +20674,10 @@ "type": "string" }, "description": "A list of mount options, e.g. [\"ro\", \"soft\"]. Not validated - mount will simply fail if one is invalid. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#mount-options" + }, + "volumeMode": { + "$ref": "v1.PersistentVolumeMode", + "description": "volumeMode defines if a volume is intended to be used with a formatted filesystem or to remain in raw block state. Value of Filesystem is implied when not included in spec. This is an alpha feature and may change in the future." } } }, @@ -20737,8 +20798,8 @@ } } }, - "v1.RBDVolumeSource": { - "id": "v1.RBDVolumeSource", + "v1.RBDPersistentVolumeSource": { + "id": "v1.RBDPersistentVolumeSource", "description": "Represents a Rados Block Device mount that lasts the lifetime of a pod. RBD volumes support ownership management and SELinux relabeling.", "required": [ "monitors", @@ -20773,7 +20834,7 @@ "description": "Keyring is the path to key ring for RBDUser. Default is /etc/ceph/keyring. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it" }, "secretRef": { - "$ref": "v1.LocalObjectReference", + "$ref": "v1.SecretReference", "description": "SecretRef is name of the authentication secret for RBDUser. If provided overrides keyring. Default is nil. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it" }, "readOnly": { @@ -20782,19 +20843,23 @@ } } }, - "v1.LocalObjectReference": { - "id": "v1.LocalObjectReference", - "description": "LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace.", + "v1.SecretReference": { + "id": "v1.SecretReference", + "description": "SecretReference represents a Secret Reference. It has enough information to retrieve secret in any namespace", "properties": { "name": { "type": "string", - "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names" + "description": "Name is unique within a namespace to reference a secret resource." + }, + "namespace": { + "type": "string", + "description": "Namespace defines the space within which the secret name must be unique." } } }, - "v1.ISCSIVolumeSource": { - "id": "v1.ISCSIVolumeSource", - "description": "Represents an ISCSI disk. ISCSI volumes can only be mounted as read/write once. ISCSI volumes support ownership management and SELinux relabeling.", + "v1.ISCSIPersistentVolumeSource": { + "id": "v1.ISCSIPersistentVolumeSource", + "description": "ISCSIPersistentVolumeSource represents an ISCSI disk. ISCSI volumes can only be mounted as read/write once. ISCSI volumes support ownership management and SELinux relabeling.", "required": [ "targetPortal", "iqn", @@ -20803,7 +20868,7 @@ "properties": { "targetPortal": { "type": "string", - "description": "iSCSI target portal. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." + "description": "iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." }, "iqn": { "type": "string", @@ -20812,11 +20877,11 @@ "lun": { "type": "integer", "format": "int32", - "description": "iSCSI target lun number." + "description": "iSCSI Target Lun number." }, "iscsiInterface": { "type": "string", - "description": "Optional: Defaults to 'default' (tcp). iSCSI interface name that uses an iSCSI transport." + "description": "iSCSI Interface Name that uses an iSCSI transport. Defaults to 'default' (tcp)." }, "fsType": { "type": "string", @@ -20831,7 +20896,7 @@ "items": { "type": "string" }, - "description": "iSCSI target portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." + "description": "iSCSI Target Portal List. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." }, "chapAuthDiscovery": { "type": "boolean", @@ -20842,12 +20907,12 @@ "description": "whether support iSCSI Session CHAP authentication" }, "secretRef": { - "$ref": "v1.LocalObjectReference", - "description": "CHAP secret for iSCSI target and initiator authentication" + "$ref": "v1.SecretReference", + "description": "CHAP Secret for iSCSI target and initiator authentication" }, "initiatorName": { "type": "string", - "description": "Custom iSCSI initiator name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface \u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection." + "description": "Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface \u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection." } } }, @@ -20908,20 +20973,6 @@ } } }, - "v1.SecretReference": { - "id": "v1.SecretReference", - "description": "SecretReference represents a Secret Reference. It has enough information to retrieve secret in any namespace", - "properties": { - "name": { - "type": "string", - "description": "Name is unique within a namespace to reference a secret resource." - }, - "namespace": { - "type": "string", - "description": "Namespace defines the space within which the secret name must be unique." - } - } - }, "v1.FCVolumeSource": { "id": "v1.FCVolumeSource", "description": "Represents a Fibre Channel volume. Fibre Channel volumes can only be mounted as read/write once. Fibre Channel volumes support ownership management and SELinux relabeling.", @@ -20969,9 +21020,9 @@ } } }, - "v1.FlexVolumeSource": { - "id": "v1.FlexVolumeSource", - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.", + "v1.FlexPersistentVolumeSource": { + "id": "v1.FlexPersistentVolumeSource", + "description": "FlexPersistentVolumeSource represents a generic persistent volume resource that is provisioned/attached using an exec based plugin.", "required": [ "driver" ], @@ -20985,7 +21036,7 @@ "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". The default filesystem depends on FlexVolume script." }, "secretRef": { - "$ref": "v1.LocalObjectReference", + "$ref": "v1.SecretReference", "description": "Optional: SecretRef is reference to the secret object containing sensitive information to pass to the plugin scripts. This may be empty if no secret object is specified. If the secret object contains more than one secret, all secrets are passed to the plugin scripts." }, "readOnly": { @@ -21160,9 +21211,9 @@ } } }, - "v1.ScaleIOVolumeSource": { - "id": "v1.ScaleIOVolumeSource", - "description": "ScaleIOVolumeSource represents a persistent ScaleIO volume", + "v1.ScaleIOPersistentVolumeSource": { + "id": "v1.ScaleIOPersistentVolumeSource", + "description": "ScaleIOPersistentVolumeSource represents a persistent ScaleIO volume", "required": [ "gateway", "system", @@ -21178,7 +21229,7 @@ "description": "The name of the storage system as configured in ScaleIO." }, "secretRef": { - "$ref": "v1.LocalObjectReference", + "$ref": "v1.SecretReference", "description": "SecretRef references to the secret for ScaleIO user and other sensitive information. If this is not provided, Login operation will fail." }, "sslEnabled": { @@ -21187,15 +21238,15 @@ }, "protectionDomain": { "type": "string", - "description": "The name of the Protection Domain for the configured storage (defaults to \"default\")." + "description": "The name of the ScaleIO Protection Domain for the configured storage." }, "storagePool": { "type": "string", - "description": "The Storage Pool associated with the protection domain (defaults to \"default\")." + "description": "The ScaleIO Storage Pool associated with the protection domain." }, "storageMode": { "type": "string", - "description": "Indicates whether the storage for a volume should be thick or thin (defaults to \"thin\")." + "description": "Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned." }, "volumeName": { "type": "string", @@ -21250,6 +21301,28 @@ } } }, + "v1.CSIPersistentVolumeSource": { + "id": "v1.CSIPersistentVolumeSource", + "description": "Represents storage that is managed by an external CSI volume driver", + "required": [ + "driver", + "volumeHandle" + ], + "properties": { + "driver": { + "type": "string", + "description": "Driver is the name of the driver to use for this volume. Required." + }, + "volumeHandle": { + "type": "string", + "description": "VolumeHandle is the unique volume name returned by the CSI volume plugin’s CreateVolume to refer to the volume on all subsequent calls. Required." + }, + "readOnly": { + "type": "boolean", + "description": "Optional: The value to pass to ControllerPublishVolumeRequest. Defaults to false (read/write)." + } + } + }, "v1.PersistentVolumeStatus": { "id": "v1.PersistentVolumeStatus", "description": "PersistentVolumeStatus is the current status of a persistent volume.", @@ -21366,7 +21439,7 @@ }, "dnsPolicy": { "type": "string", - "description": "Set DNS policy for containers within the pod. One of 'ClusterFirstWithHostNet', 'ClusterFirst' or 'Default'. Defaults to \"ClusterFirst\". To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'." + "description": "Set DNS policy for the pod. Defaults to \"ClusterFirst\". Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'. Note that 'None' policy is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it." }, "nodeSelector": { "type": "object", @@ -21449,6 +21522,10 @@ "type": "integer", "format": "int32", "description": "The priority value. Various system components use this field to find the priority of the pod. 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." + }, + "dnsConfig": { + "$ref": "v1.PodDNSConfig", + "description": "Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy. This is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it." } } }, @@ -21509,7 +21586,7 @@ }, "flexVolume": { "$ref": "v1.FlexVolumeSource", - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future." + "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin." }, "cinder": { "$ref": "v1.CinderVolumeSource", @@ -21657,6 +21734,75 @@ } } }, + "v1.ISCSIVolumeSource": { + "id": "v1.ISCSIVolumeSource", + "description": "Represents an ISCSI disk. ISCSI volumes can only be mounted as read/write once. ISCSI volumes support ownership management and SELinux relabeling.", + "required": [ + "targetPortal", + "iqn", + "lun" + ], + "properties": { + "targetPortal": { + "type": "string", + "description": "iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." + }, + "iqn": { + "type": "string", + "description": "Target iSCSI Qualified Name." + }, + "lun": { + "type": "integer", + "format": "int32", + "description": "iSCSI Target Lun number." + }, + "iscsiInterface": { + "type": "string", + "description": "iSCSI Interface Name that uses an iSCSI transport. Defaults to 'default' (tcp)." + }, + "fsType": { + "type": "string", + "description": "Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi" + }, + "readOnly": { + "type": "boolean", + "description": "ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false." + }, + "portals": { + "type": "array", + "items": { + "type": "string" + }, + "description": "iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." + }, + "chapAuthDiscovery": { + "type": "boolean", + "description": "whether support iSCSI Discovery CHAP authentication" + }, + "chapAuthSession": { + "type": "boolean", + "description": "whether support iSCSI Session CHAP authentication" + }, + "secretRef": { + "$ref": "v1.LocalObjectReference", + "description": "CHAP Secret for iSCSI target and initiator authentication" + }, + "initiatorName": { + "type": "string", + "description": "Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface \u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection." + } + } + }, + "v1.LocalObjectReference": { + "id": "v1.LocalObjectReference", + "description": "LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace.", + "properties": { + "name": { + "type": "string", + "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names" + } + } + }, "v1.PersistentVolumeClaimVolumeSource": { "id": "v1.PersistentVolumeClaimVolumeSource", "description": "PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace. This volume finds the bound PV and mounts that volume for the pod. A PersistentVolumeClaimVolumeSource is, essentially, a wrapper around another type of volume that is owned by someone else (the system).", @@ -21674,6 +21820,80 @@ } } }, + "v1.RBDVolumeSource": { + "id": "v1.RBDVolumeSource", + "description": "Represents a Rados Block Device mount that lasts the lifetime of a pod. RBD volumes support ownership management and SELinux relabeling.", + "required": [ + "monitors", + "image" + ], + "properties": { + "monitors": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A collection of Ceph monitors. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it" + }, + "image": { + "type": "string", + "description": "The rados image name. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it" + }, + "fsType": { + "type": "string", + "description": "Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd" + }, + "pool": { + "type": "string", + "description": "The rados pool name. Default is rbd. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it" + }, + "user": { + "type": "string", + "description": "The rados user name. Default is admin. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it" + }, + "keyring": { + "type": "string", + "description": "Keyring is the path to key ring for RBDUser. Default is /etc/ceph/keyring. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it" + }, + "secretRef": { + "$ref": "v1.LocalObjectReference", + "description": "SecretRef is name of the authentication secret for RBDUser. If provided overrides keyring. Default is nil. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it" + }, + "readOnly": { + "type": "boolean", + "description": "ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it" + } + } + }, + "v1.FlexVolumeSource": { + "id": "v1.FlexVolumeSource", + "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.", + "required": [ + "driver" + ], + "properties": { + "driver": { + "type": "string", + "description": "Driver is the name of the driver to use for this volume." + }, + "fsType": { + "type": "string", + "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". The default filesystem depends on FlexVolume script." + }, + "secretRef": { + "$ref": "v1.LocalObjectReference", + "description": "Optional: SecretRef is reference to the secret object containing sensitive information to pass to the plugin scripts. This may be empty if no secret object is specified. If the secret object contains more than one secret, all secrets are passed to the plugin scripts." + }, + "readOnly": { + "type": "boolean", + "description": "Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts." + }, + "options": { + "type": "object", + "description": "Optional: Extra command options if any." + } + } + }, "v1.CephFSVolumeSource": { "id": "v1.CephFSVolumeSource", "description": "Represents a Ceph Filesystem mount that lasts the lifetime of a pod Cephfs volumes do not support ownership management or SELinux relabeling.", @@ -21934,6 +22154,57 @@ } } }, + "v1.ScaleIOVolumeSource": { + "id": "v1.ScaleIOVolumeSource", + "description": "ScaleIOVolumeSource represents a persistent ScaleIO volume", + "required": [ + "gateway", + "system", + "secretRef" + ], + "properties": { + "gateway": { + "type": "string", + "description": "The host address of the ScaleIO API Gateway." + }, + "system": { + "type": "string", + "description": "The name of the storage system as configured in ScaleIO." + }, + "secretRef": { + "$ref": "v1.LocalObjectReference", + "description": "SecretRef references to the secret for ScaleIO user and other sensitive information. If this is not provided, Login operation will fail." + }, + "sslEnabled": { + "type": "boolean", + "description": "Flag to enable/disable SSL communication with Gateway, default false" + }, + "protectionDomain": { + "type": "string", + "description": "The name of the ScaleIO Protection Domain for the configured storage." + }, + "storagePool": { + "type": "string", + "description": "The ScaleIO Storage Pool associated with the protection domain." + }, + "storageMode": { + "type": "string", + "description": "Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned." + }, + "volumeName": { + "type": "string", + "description": "The name of a volume already created in the ScaleIO system that is associated with this volume source." + }, + "fsType": { + "type": "string", + "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified." + }, + "readOnly": { + "type": "boolean", + "description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts." + } + } + }, "v1.StorageOSVolumeSource": { "id": "v1.StorageOSVolumeSource", "description": "Represents a StorageOS persistent volume resource.", @@ -22025,6 +22296,13 @@ }, "description": "Pod volumes to mount into the container's filesystem. Cannot be updated." }, + "volumeDevices": { + "type": "array", + "items": { + "$ref": "v1.VolumeDevice" + }, + "description": "volumeDevices is the list of block devices to be used by the container. This is an alpha feature and may change in the future." + }, "livenessProbe": { "$ref": "v1.Probe", "description": "Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes" @@ -22051,7 +22329,7 @@ }, "securityContext": { "$ref": "v1.SecurityContext", - "description": "Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md" + "description": "Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/" }, "stdin": { "type": "boolean", @@ -22263,6 +22541,24 @@ "id": "v1.MountPropagationMode", "properties": {} }, + "v1.VolumeDevice": { + "id": "v1.VolumeDevice", + "description": "volumeDevice describes a mapping of a raw block device within a container.", + "required": [ + "name", + "devicePath" + ], + "properties": { + "name": { + "type": "string", + "description": "name must match the name of a persistentVolumeClaim in the pod" + }, + "devicePath": { + "type": "string", + "description": "devicePath is the path inside of the container that the device will be mapped to." + } + } + }, "v1.Probe": { "id": "v1.Probe", "description": "Probe describes a health check to be performed against a container to determine whether it is alive or ready to receive traffic.", @@ -22663,7 +22959,10 @@ }, "v1.PodAffinityTerm": { "id": "v1.PodAffinityTerm", - "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key \u003ctopologyKey\u003e tches that of any node on which a pod of the set of pods is running", + "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key \u003ctopologyKey\u003e matches that of any node on which a pod of the set of pods is running", + "required": [ + "topologyKey" + ], "properties": { "labelSelector": { "$ref": "v1.LabelSelector", @@ -22678,7 +22977,7 @@ }, "topologyKey": { "type": "string", - "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. For PreferredDuringScheduling pod anti-affinity, empty topologyKey is interpreted as \"all topologies\" (\"all topologies\" here means all the topologyKeys indicated by scheduler command-line argument --failure-domains); for affinity and for RequiredDuringScheduling pod anti-affinity, empty topologyKey is not allowed." + "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed." } } }, @@ -22765,6 +23064,46 @@ } } }, + "v1.PodDNSConfig": { + "id": "v1.PodDNSConfig", + "description": "PodDNSConfig defines the DNS parameters of a pod in addition to those generated from DNSPolicy.", + "properties": { + "nameservers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of DNS name server IP addresses. This will be appended to the base nameservers generated from DNSPolicy. Duplicated nameservers will be removed." + }, + "searches": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of DNS search domains for host-name lookup. This will be appended to the base search paths generated from DNSPolicy. Duplicated search paths will be removed." + }, + "options": { + "type": "array", + "items": { + "$ref": "v1.PodDNSConfigOption" + }, + "description": "A list of DNS resolver options. This will be merged with the base options generated from DNSPolicy. Duplicated entries will be removed. Resolution options given in Options will override those that appear in the base DNSPolicy." + } + } + }, + "v1.PodDNSConfigOption": { + "id": "v1.PodDNSConfigOption", + "description": "PodDNSConfigOption defines DNS resolver options of a pod.", + "properties": { + "name": { + "type": "string", + "description": "Required." + }, + "value": { + "type": "string" + } + } + }, "v1.PodStatus": { "id": "v1.PodStatus", "description": "PodStatus represents information about the status of a pod. Status may trail the actual state of a system.", @@ -23295,7 +23634,7 @@ "items": { "$ref": "v1.ResourceQuota" }, - "description": "Items is a list of ResourceQuota objects. More info: https://git.k8s.io/community/contributors/design-proposals/admission_control_resource_quota.md" + "description": "Items is a list of ResourceQuota objects. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/" } } }, @@ -23331,7 +23670,7 @@ "properties": { "hard": { "type": "object", - "description": "Hard is the set of desired hard limits for each named resource. More info: https://git.k8s.io/community/contributors/design-proposals/admission_control_resource_quota.md" + "description": "Hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/" }, "scopes": { "type": "array", @@ -23352,7 +23691,7 @@ "properties": { "hard": { "type": "object", - "description": "Hard is the set of enforced hard limits for each named resource. More info: https://git.k8s.io/community/contributors/design-proposals/admission_control_resource_quota.md" + "description": "Hard is the set of enforced hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/" }, "used": { "type": "object", @@ -23583,7 +23922,7 @@ }, "externalName": { "type": "string", - "description": "externalName is the external reference that kubedns or equivalent will return as a CNAME record for this service. No proxying will be involved. Must be a valid DNS name and requires Type to be ExternalName." + "description": "externalName is the external reference that kubedns or equivalent will return as a CNAME record for this service. No proxying will be involved. Must be a valid RFC-1123 hostname (https://tools.ietf.org/html/rfc1123) and requires Type to be ExternalName." }, "externalTrafficPolicy": { "type": "string", diff --git a/build/BUILD b/build/BUILD index 0864c9cdd04..7e7beb7d671 100644 --- a/build/BUILD +++ b/build/BUILD @@ -38,7 +38,7 @@ DOCKERIZED_BINARIES = { }, "kube-scheduler": { "base": "@official_busybox//image", - "target": "//plugin/cmd/kube-scheduler:kube-scheduler", + "target": "//cmd/kube-scheduler:kube-scheduler", }, "kube-proxy": { "base": "@debian-iptables-amd64//image", @@ -103,7 +103,6 @@ release_filegroup( name = "client-targets", srcs = [ "//cmd/kubectl", - "//federation/cmd/kubefed", ], ) @@ -112,6 +111,7 @@ release_filegroup( name = "node-targets", srcs = [ "//cmd/kube-proxy", + "//cmd/kubeadm", "//cmd/kubelet", ], ) @@ -122,12 +122,12 @@ release_filegroup( release_filegroup( name = "server-targets", srcs = [ + "//cluster/gce/gci/mounter", "//cmd/cloud-controller-manager", "//cmd/hyperkube", "//cmd/kube-apiserver", "//cmd/kube-controller-manager", - "//cmd/kubeadm", - "//plugin/cmd/kube-scheduler", + "//cmd/kube-scheduler", "//vendor/k8s.io/kube-aggregator", ], ) @@ -143,7 +143,6 @@ filegroup( "//cmd/genyaml", "//cmd/kubemark", # TODO: server platforms only "//cmd/linkcheck", - "//federation/cmd/genfeddocs", "//test/e2e:e2e.test", "//test/e2e_node:e2e_node.test", # TODO: server platforms only "//vendor/github.com/onsi/ginkgo/ginkgo", @@ -154,9 +153,7 @@ filegroup( filegroup( name = "test-portable-targets", srcs = [ - "//federation/develop:all-srcs", "//hack:e2e.go", - "//hack:federated-ginkgo-e2e.sh", "//hack:get-build.sh", "//hack:ginkgo-e2e.sh", "//hack/e2e-internal:all-srcs", diff --git a/build/build-image/cross/Dockerfile b/build/build-image/cross/Dockerfile index 15823fd1760..1b6170ba2af 100644 --- a/build/build-image/cross/Dockerfile +++ b/build/build-image/cross/Dockerfile @@ -15,7 +15,7 @@ # This file creates a standard build environment for building cross # platform go binary for the architecture kubernetes cares about. -FROM golang:1.8.3 +FROM golang:1.9.2 ENV GOARM 7 ENV KUBE_DYNAMIC_CROSSPLATFORMS \ diff --git a/build/build-image/cross/Makefile b/build/build-image/cross/Makefile index 83e2c862226..c8ed25375ca 100644 --- a/build/build-image/cross/Makefile +++ b/build/build-image/cross/Makefile @@ -24,4 +24,4 @@ build: docker build --pull -t gcr.io/google_containers/$(IMAGE):$(TAG) . push: build - gcloud docker --server=gcr.io -- push gcr.io/google_containers/$(IMAGE):$(TAG) + gcloud docker -- push gcr.io/google_containers/$(IMAGE):$(TAG) diff --git a/build/build-image/cross/VERSION b/build/build-image/cross/VERSION index e331872b28f..c67c6bbfd8c 100644 --- a/build/build-image/cross/VERSION +++ b/build/build-image/cross/VERSION @@ -1 +1 @@ -v1.8.3-3 +v1.9.2-1 diff --git a/build/common.sh b/build/common.sh index 750440dcd13..8f8254ca228 100755 --- a/build/common.sh +++ b/build/common.sh @@ -85,7 +85,7 @@ readonly KUBE_CONTAINER_RSYNC_PORT=8730 # # $1 - server architecture kube::build::get_docker_wrapped_binaries() { - debian_iptables_version=v8 + debian_iptables_version=v10 ### If you change any of these lists, please also update DOCKERIZED_BINARIES ### in build/BUILD. case $1 in @@ -720,7 +720,7 @@ function kube::build::sync_to_container() { # necessary. kube::build::rsync \ --delete \ - --filter='H /.git/' \ + --filter='H /.git' \ --filter='- /.make/' \ --filter='- /_tmp/' \ --filter='- /_output/' \ diff --git a/build/debian-base/Dockerfile.build b/build/debian-base/Dockerfile.build index 8992d6bea9d..7b7b99d3a6a 100644 --- a/build/debian-base/Dockerfile.build +++ b/build/debian-base/Dockerfile.build @@ -44,7 +44,6 @@ RUN echo "Yes, do as I say!" | apt-get purge \ debconf-i18n \ e2fslibs \ e2fsprogs \ - gcc-4.8-base \ init \ initscripts \ libcap2-bin \ @@ -54,7 +53,7 @@ RUN echo "Yes, do as I say!" | apt-get purge \ libudev1 \ libblkid1 \ libncursesw5 \ - libprocps3 \ + libprocps6 \ libslang2 \ libss2 \ libtext-charwidth-perl libtext-iconv-perl libtext-wrapi18n-perl \ diff --git a/build/debian-base/Makefile b/build/debian-base/Makefile index 6baa8d5dd3a..47eafa5c645 100755 --- a/build/debian-base/Makefile +++ b/build/debian-base/Makefile @@ -18,7 +18,7 @@ REGISTRY ?= gcr.io/google-containers IMAGE ?= debian-base BUILD_IMAGE ?= debian-build -TAG ?= 0.2 +TAG ?= 0.3 TAR_FILE ?= rootfs.tar ARCH?=amd64 @@ -26,22 +26,22 @@ TEMP_DIR:=$(shell mktemp -d) QEMUVERSION=v2.9.1 ifeq ($(ARCH),amd64) - BASEIMAGE?=debian:jessie + BASEIMAGE?=debian:stretch endif ifeq ($(ARCH),arm) - BASEIMAGE?=arm32v7/debian:jessie + BASEIMAGE?=arm32v7/debian:stretch QEMUARCH=arm endif ifeq ($(ARCH),arm64) - BASEIMAGE?=arm64v8/debian:jessie + BASEIMAGE?=arm64v8/debian:stretch QEMUARCH=aarch64 endif ifeq ($(ARCH),ppc64le) - BASEIMAGE?=ppc64le/debian:jessie + BASEIMAGE?=ppc64le/debian:stretch QEMUARCH=ppc64le endif ifeq ($(ARCH),s390x) - BASEIMAGE?=s390x/debian:jessie + BASEIMAGE?=s390x/debian:stretch QEMUARCH=s390x endif diff --git a/build/debian-hyperkube-base/Dockerfile b/build/debian-hyperkube-base/Dockerfile index a4882d87ba2..aeb62ce5683 100644 --- a/build/debian-hyperkube-base/Dockerfile +++ b/build/debian-hyperkube-base/Dockerfile @@ -14,26 +14,30 @@ FROM BASEIMAGE +RUN echo CACHEBUST>/dev/null && clean-install \ + bash + # The samba-common, cifs-utils, and nfs-common packages depend on -# ucf, which itself depends on /bin/bash existing. -# It doesn't seem to actually need bash, however. -RUN ln -s /bin/sh /bin/bash +# ucf, which itself depends on /bin/bash. +RUN echo "dash dash/sh boolean false" | debconf-set-selections +RUN DEBIAN_FRONTEND=noninteractive dpkg-reconfigure dash RUN echo CACHEBUST>/dev/null && clean-install \ - iptables \ + ca-certificates \ + ceph-common \ + cifs-utils \ + conntrack \ e2fsprogs \ ebtables \ ethtool \ - kmod \ - ca-certificates \ - conntrack \ - util-linux \ - socat \ git \ - jq \ - nfs-common \ glusterfs-client \ - cifs-utils \ - ceph-common + iptables \ + jq \ + kmod \ + openssh-client \ + nfs-common \ + socat \ + util-linux COPY cni-bin/bin /opt/cni/bin diff --git a/build/debian-hyperkube-base/Makefile b/build/debian-hyperkube-base/Makefile index b33a92c58e8..54ca29f7e9a 100644 --- a/build/debian-hyperkube-base/Makefile +++ b/build/debian-hyperkube-base/Makefile @@ -19,11 +19,11 @@ REGISTRY?=gcr.io/google-containers IMAGE?=debian-hyperkube-base -TAG=0.4 +TAG=0.8 ARCH?=amd64 CACHEBUST?=1 -BASEIMAGE=gcr.io/google-containers/debian-base-$(ARCH):0.2 +BASEIMAGE=gcr.io/google-containers/debian-base-$(ARCH):0.3 CNI_VERSION=v0.6.0 TEMP_DIR:=$(shell mktemp -d) diff --git a/build/debian-iptables/Dockerfile b/build/debian-iptables/Dockerfile index 8b1f7b5f76c..0ec3d56dc62 100644 --- a/build/debian-iptables/Dockerfile +++ b/build/debian-iptables/Dockerfile @@ -19,7 +19,8 @@ FROM BASEIMAGE CROSS_BUILD_COPY qemu-ARCH-static /usr/bin/ RUN clean-install \ - iptables \ - ebtables \ conntrack \ - module-init-tools + ebtables \ + ipset \ + iptables \ + kmod diff --git a/build/debian-iptables/Makefile b/build/debian-iptables/Makefile index bcf5d17b88d..1cc75edf17a 100644 --- a/build/debian-iptables/Makefile +++ b/build/debian-iptables/Makefile @@ -16,7 +16,7 @@ REGISTRY?="gcr.io/google-containers" IMAGE=debian-iptables -TAG=v8 +TAG=v10 ARCH?=amd64 TEMP_DIR:=$(shell mktemp -d) QEMUVERSION=v2.9.1 @@ -34,7 +34,7 @@ ifeq ($(ARCH),s390x) QEMUARCH=s390x endif -BASEIMAGE=gcr.io/google-containers/debian-base-$(ARCH):0.2 +BASEIMAGE=gcr.io/google-containers/debian-base-$(ARCH):0.3 build: cp ./* $(TEMP_DIR) diff --git a/build/debs/BUILD b/build/debs/BUILD index f091030f661..d8a195dbc74 100644 --- a/build/debs/BUILD +++ b/build/debs/BUILD @@ -41,7 +41,7 @@ deb_data( name = "kube-scheduler", data = [ { - "files": ["//plugin/cmd/kube-scheduler"], + "files": ["//cmd/kube-scheduler"], "mode": "0755", "dir": "/usr/bin", }, @@ -82,7 +82,7 @@ deb_data( pkg_tar( name = "kubernetes-cni-data", - package_dir = "/opt/cni", + package_dir = "/opt/cni/bin", deps = ["@kubernetes_cni//file"], ) diff --git a/build/lib/release.sh b/build/lib/release.sh index d0a0464ab91..870451601f6 100644 --- a/build/lib/release.sh +++ b/build/lib/release.sh @@ -28,11 +28,7 @@ readonly RELEASE_STAGE="${LOCAL_OUTPUT_ROOT}/release-stage" readonly RELEASE_TARS="${LOCAL_OUTPUT_ROOT}/release-tars" readonly RELEASE_IMAGES="${LOCAL_OUTPUT_ROOT}/release-images" -KUBE_BUILD_HYPERKUBE=${KUBE_BUILD_HYPERKUBE:-n} -if [[ -n "${KUBE_DOCKER_IMAGE_TAG-}" && -n "${KUBE_DOCKER_REGISTRY-}" ]]; then - # retain legacy behavior of automatically building hyperkube during releases - KUBE_BUILD_HYPERKUBE=y -fi +KUBE_BUILD_HYPERKUBE=${KUBE_BUILD_HYPERKUBE:-y} # Validate a ci version # @@ -98,18 +94,23 @@ function kube::release::package_tarballs() { # Package the source code we built, for compliance/licensing/audit/yadda. function kube::release::package_src_tarball() { + local -r src_tarball="${RELEASE_TARS}/kubernetes-src.tar.gz" kube::log::status "Building tarball: src" - local source_files=( - $(cd "${KUBE_ROOT}" && find . -mindepth 1 -maxdepth 1 \ - -not \( \ - \( -path ./_\* -o \ - -path ./.git\* -o \ - -path ./.config\* -o \ - -path ./.gsutil\* \ - \) -prune \ - \)) - ) - "${TAR}" czf "${RELEASE_TARS}/kubernetes-src.tar.gz" -C "${KUBE_ROOT}" "${source_files[@]}" + if [[ "${KUBE_GIT_TREE_STATE-}" == "clean" ]]; then + git archive -o "${src_tarball}" HEAD + else + local source_files=( + $(cd "${KUBE_ROOT}" && find . -mindepth 1 -maxdepth 1 \ + -not \( \ + \( -path ./_\* -o \ + -path ./.git\* -o \ + -path ./.config\* -o \ + -path ./.gsutil\* \ + \) -prune \ + \)) + ) + "${TAR}" czf "${src_tarball}" -C "${KUBE_ROOT}" "${source_files[@]}" + fi } # Package up all of the cross compiled clients. Over time this should grow into @@ -412,13 +413,18 @@ function kube::release::package_kube_manifests_tarball() { cp "${salt_dir}/rescheduler/rescheduler.manifest" "${gci_dst_dir}/" cp "${salt_dir}/e2e-image-puller/e2e-image-puller.manifest" "${gci_dst_dir}/" cp "${KUBE_ROOT}/cluster/gce/gci/configure-helper.sh" "${gci_dst_dir}/gci-configure-helper.sh" - cp "${KUBE_ROOT}/cluster/gce/gci/mounter/mounter" "${gci_dst_dir}/gci-mounter" cp "${KUBE_ROOT}/cluster/gce/gci/health-monitor.sh" "${gci_dst_dir}/health-monitor.sh" cp "${KUBE_ROOT}/cluster/gce/container-linux/configure-helper.sh" "${gci_dst_dir}/container-linux-configure-helper.sh" cp -r "${salt_dir}/kube-admission-controls/limit-range" "${gci_dst_dir}" local objects objects=$(cd "${KUBE_ROOT}/cluster/addons" && find . \( -name \*.yaml -or -name \*.yaml.in -or -name \*.json \) | grep -v demo) tar c -C "${KUBE_ROOT}/cluster/addons" ${objects} | tar x -C "${gci_dst_dir}" + # Merge GCE-specific addons with general purpose addons. + local gce_objects + gce_objects=$(cd "${KUBE_ROOT}/cluster/gce/addons" && find . \( -name \*.yaml -or -name \*.yaml.in -or -name \*.json \) \( -not -name \*demo\* \)) + if [[ -n "${gce_objects}" ]]; then + tar c -C "${KUBE_ROOT}/cluster/gce/addons" ${gce_objects} | tar x -C "${gci_dst_dir}" + fi kube::release::clean_cruft @@ -503,11 +509,6 @@ EOF mkdir -p "${release_stage}/third_party" cp -R "${KUBE_ROOT}/third_party/htpasswd" "${release_stage}/third_party/htpasswd" - # Include only federation/cluster and federation/deploy - mkdir "${release_stage}/federation" - cp -R "${KUBE_ROOT}/federation/cluster" "${release_stage}/federation/" - cp -R "${KUBE_ROOT}/federation/deploy" "${release_stage}/federation/" - # Include hack/lib as a dependency for the cluster/ scripts mkdir -p "${release_stage}/hack" cp -R "${KUBE_ROOT}/hack/lib" "${release_stage}/hack/" diff --git a/build/openapi.bzl b/build/openapi.bzl new file mode 100644 index 00000000000..4b673e28acf --- /dev/null +++ b/build/openapi.bzl @@ -0,0 +1,9 @@ +# A project wanting to generate openapi code for vendored +# k8s.io/kubernetes will need to set the following variables in +# //build/openapi.bzl in their project and customize the go prefix: +# +# openapi_go_prefix = "k8s.io/myproject/" +# openapi_vendor_prefix = "vendor/k8s.io/kubernetes/" + +openapi_go_prefix = "k8s.io/kubernetes/" +openapi_vendor_prefix = "" diff --git a/build/pause/CHANGELOG.md b/build/pause/CHANGELOG.md new file mode 100644 index 00000000000..8f58bcdac0a --- /dev/null +++ b/build/pause/CHANGELOG.md @@ -0,0 +1,8 @@ +# 3.1 + +* The pause container gains a signal handler to clean up orphaned zombie processes. ([#36853](https://prs.k8s.io/36853), [@verb](https://github.com/verb)) +* `pause -v` will return build information for the pause binary. ([#56762](https://prs.k8s.io/56762), [@verb](https://github.com/verb)) + +# 3.0 + +* The pause container was rewritten entirely in C. ([#23009](https://prs.k8s.io/23009), [@uluyol](https://github.com/uluyol)) diff --git a/build/pause/Makefile b/build/pause/Makefile index cad23f60857..93240e8fd33 100644 --- a/build/pause/Makefile +++ b/build/pause/Makefile @@ -18,14 +18,15 @@ REGISTRY ?= gcr.io/google_containers IMAGE = $(REGISTRY)/pause-$(ARCH) LEGACY_AMD64_IMAGE = $(REGISTRY)/pause -TAG = 3.0 +TAG = 3.1 +REV = $(shell git describe --contains --always --match='v*') # Architectures supported: amd64, arm, arm64, ppc64le and s390x ARCH ?= amd64 ALL_ARCH = amd64 arm arm64 ppc64le s390x -CFLAGS = -Os -Wall -Werror -static +CFLAGS = -Os -Wall -Werror -static -DVERSION=v$(TAG)-$(REV) KUBE_CROSS_IMAGE ?= gcr.io/google_containers/kube-cross KUBE_CROSS_VERSION ?= $(shell cat ../build-image/cross/VERSION) @@ -37,7 +38,7 @@ ifeq ($(ARCH),amd64) endif ifeq ($(ARCH),arm) - TRIPLE ?= arm-linux-gnueabi + TRIPLE ?= arm-linux-gnueabihf endif ifeq ($(ARCH),arm64) diff --git a/build/pause/pause.c b/build/pause/pause.c index f2e21b41d55..95966f4384d 100644 --- a/build/pause/pause.c +++ b/build/pause/pause.c @@ -17,20 +17,37 @@ limitations under the License. #include #include #include +#include #include #include #include +#define STRINGIFY(x) #x +#define VERSION_STRING(x) STRINGIFY(x) + +#ifndef VERSION +#define VERSION HEAD +#endif + static void sigdown(int signo) { psignal(signo, "Shutting down, got signal"); exit(0); } static void sigreap(int signo) { - while (waitpid(-1, NULL, WNOHANG) > 0); + while (waitpid(-1, NULL, WNOHANG) > 0) + ; } -int main() { +int main(int argc, char **argv) { + int i; + for (i = 1; i < argc; ++i) { + if (!strcasecmp(argv[i], "-v")) { + printf("pause.c %s\n", VERSION_STRING(VERSION)); + return 0; + } + } + if (getpid() != 1) /* Not an error because pause sees use outside of infra containers. */ fprintf(stderr, "Warning: pause should be the first process\n"); diff --git a/build/release-tars/BUILD b/build/release-tars/BUILD index 0d09ffcccfe..39f588e9518 100644 --- a/build/release-tars/BUILD +++ b/build/release-tars/BUILD @@ -38,6 +38,7 @@ grep ^STABLE_BUILD_GIT_COMMIT bazel-out/stable-status.txt | cut -d' ' -f2 >>$@ pkg_tar( name = "kubernetes-src", + build_tar = "@io_kubernetes_build//tools/build_tar", extension = "tar.gz", files = select({ ":package_src": ["//:all-srcs"], @@ -65,6 +66,7 @@ filegroup( pkg_tar( name = "_client-bin", + build_tar = "@io_kubernetes_build//tools/build_tar", files = ["//build:client-targets"], mode = "0755", package_dir = "client/bin", @@ -73,6 +75,7 @@ pkg_tar( pkg_tar( name = "kubernetes-client-%s" % PLATFORM_ARCH_STRING, + build_tar = "@io_kubernetes_build//tools/build_tar", extension = "tar.gz", package_dir = "kubernetes", deps = [ @@ -82,6 +85,7 @@ pkg_tar( pkg_tar( name = "_node-bin", + build_tar = "@io_kubernetes_build//tools/build_tar", files = [ "//build:client-targets", "//build:node-targets", @@ -93,6 +97,7 @@ pkg_tar( pkg_tar( name = "kubernetes-node-%s" % PLATFORM_ARCH_STRING, + build_tar = "@io_kubernetes_build//tools/build_tar", extension = "tar.gz", files = [":license-targets"], mode = "0644", @@ -104,6 +109,7 @@ pkg_tar( pkg_tar( name = "_server-bin", + build_tar = "@io_kubernetes_build//tools/build_tar", files = [ "//build:client-targets", "//build:docker-artifacts", @@ -125,6 +131,7 @@ genrule( # Some of the startup scripts fail if there isn't an addons/ directory in the server tarball. pkg_tar( name = "_server-addons", + build_tar = "@io_kubernetes_build//tools/build_tar", files = [ ":.dummy", ], @@ -134,6 +141,7 @@ pkg_tar( pkg_tar( name = "kubernetes-server-%s" % PLATFORM_ARCH_STRING, + build_tar = "@io_kubernetes_build//tools/build_tar", extension = "tar.gz", files = [":license-targets"], mode = "0644", @@ -146,6 +154,7 @@ pkg_tar( pkg_tar( name = "_test-bin", + build_tar = "@io_kubernetes_build//tools/build_tar", files = ["//build:test-targets"], mode = "0755", package_dir = "platforms/" + PLATFORM_ARCH_STRING.replace("-", "/"), @@ -155,6 +164,7 @@ pkg_tar( pkg_tar( name = "kubernetes-test", + build_tar = "@io_kubernetes_build//tools/build_tar", extension = "tar.gz", files = ["//build:test-portable-targets"], package_dir = "kubernetes", @@ -167,6 +177,7 @@ pkg_tar( pkg_tar( name = "_full_server", + build_tar = "@io_kubernetes_build//tools/build_tar", files = [ ":kubernetes-manifests.tar.gz", ":kubernetes-salt.tar.gz", @@ -177,6 +188,7 @@ pkg_tar( pkg_tar( name = "kubernetes", + build_tar = "@io_kubernetes_build//tools/build_tar", extension = "tar.gz", files = [ "//:Godeps/LICENSES", @@ -193,12 +205,12 @@ pkg_tar( strip_prefix = "//", deps = [ ":_full_server", - "//federation:release", ], ) pkg_tar( name = "kubernetes-manifests", + build_tar = "@io_kubernetes_build//tools/build_tar", extension = "tar.gz", deps = [ "//cluster:manifests", @@ -207,6 +219,7 @@ pkg_tar( pkg_tar( name = "kubernetes-salt", + build_tar = "@io_kubernetes_build//tools/build_tar", extension = "tar.gz", deps = [ "//cluster/saltbase:salt", diff --git a/build/root/.bazelrc b/build/root/.bazelrc index 9101ce7a7cd..fc36b294e2d 100644 --- a/build/root/.bazelrc +++ b/build/root/.bazelrc @@ -12,16 +12,5 @@ build --sandbox_tmpfs_path=/tmp # This flag requires Bazel 0.5.0+ build --sandbox_fake_username -# rules_go@82483596ec203eb9c1849937636f4cbed83733eb has a typo that -# inadvertently relies on comprehension variables leaking. -# TODO(ixdy): Remove these defaults once rules_go is bumped. -# Ref kubernetes/kubernetes#52677 -build --incompatible_comprehension_variables_do_not_leak=false -query --incompatible_comprehension_variables_do_not_leak=false - -# TODO(ixdy): remove the following once repo-infra is bumped. -build --incompatible_disallow_set_constructor=false -query --incompatible_disallow_set_constructor=false - # Enable go race detection. test --features=race diff --git a/build/root/BUILD.root b/build/root/BUILD.root index a56e731655a..5b162716fc5 100644 --- a/build/root/BUILD.root +++ b/build/root/BUILD.root @@ -63,7 +63,6 @@ filegroup( "//cmd:all-srcs", "//docs:all-srcs", "//examples:all-srcs", - "//federation:all-srcs", "//hack:all-srcs", "//pkg:all-srcs", "//plugin:all-srcs", diff --git a/build/root/Makefile b/build/root/Makefile index e39c5053216..23636241f9b 100644 --- a/build/root/Makefile +++ b/build/root/Makefile @@ -291,7 +291,6 @@ else test-cmd: generated_files hack/make-rules/test-kubeadm-cmd.sh hack/make-rules/test-cmd.sh - hack/make-rules/test-federation-cmd.sh endif define CLEAN_HELP_INFO @@ -390,7 +389,7 @@ define RELEASE_SKIP_TESTS_HELP_INFO # # Args: # KUBE_RELEASE_RUN_TESTS: Whether to run tests. Set to 'y' to run tests anyways. -# KUBE_FASTBUILD: Whether to cross-compile for other architectures. Set to 'true' to do so. +# KUBE_FASTBUILD: Whether to cross-compile for other architectures. Set to 'false' to do so. # # Example: # make release-skip-tests @@ -467,36 +466,6 @@ $(filter-out %$(EXCLUDE_TARGET),$(notdir $(abspath $(wildcard cmd/*/)))): genera hack/make-rules/build.sh cmd/$@ endif -define PLUGIN_CMD_HELP_INFO -# Add rules for all directories in plugin/cmd/ -# -# Example: -# make kube-scheduler -endef -.PHONY: $(notdir $(abspath $(wildcard plugin/cmd/*/))) -ifeq ($(PRINT_HELP),y) -$(notdir $(abspath $(wildcard plugin/cmd/*/))): - @echo "$$PLUGIN_CMD_HELP_INFO" -else -$(notdir $(abspath $(wildcard plugin/cmd/*/))): generated_files - hack/make-rules/build.sh plugin/cmd/$@ -endif - -define FED_CMD_HELP_INFO -# Add rules for all directories in federation/cmd/ -# -# Example: -# make federation-apiserver federation-controller-manager -endef -.PHONY: $(notdir $(abspath $(wildcard federation/cmd/*/))) -ifeq ($(PRINT_HELP),y) -$(notdir $(abspath $(wildcard federation/cmd/*/))): - @echo "$$FED_CMD_HELP_INFO" -else -$(notdir $(abspath $(wildcard federation/cmd/*/))): generated_files - hack/make-rules/build.sh federation/cmd/$@ -endif - define GENERATED_FILES_HELP_INFO # Produce auto-generated files needed for the build. # diff --git a/build/root/Makefile.generated_files b/build/root/Makefile.generated_files index 96caaf87a73..066da2daadc 100644 --- a/build/root/Makefile.generated_files +++ b/build/root/Makefile.generated_files @@ -628,6 +628,7 @@ CONVERSION_DIRS := $(shell \ ) CONVERSION_FILES := $(addsuffix /$(CONVERSION_FILENAME), $(CONVERSION_DIRS)) +CONVERSION_EXTRA_PEER_DIRS := k8s.io/kubernetes/pkg/apis/core,k8s.io/kubernetes/pkg/apis/core/v1,k8s.io/api/core/v1 # Shell function for reuse in rules. RUN_GEN_CONVERSION = \ @@ -638,6 +639,7 @@ RUN_GEN_CONVERSION = \ echo "DBG: running $(CONVERSION_GEN) for $$pkgs"; \ fi; \ ./hack/run-in-gopath.sh $(CONVERSION_GEN) \ + --extra-peer-dirs $(CONVERSION_EXTRA_PEER_DIRS) \ --v $(KUBE_VERBOSE) \ --logtostderr \ -i "$$pkgs" \ diff --git a/build/root/WORKSPACE b/build/root/WORKSPACE index 6ca56c53055..c6ba9c71059 100644 --- a/build/root/WORKSPACE +++ b/build/root/WORKSPACE @@ -1,15 +1,15 @@ http_archive( name = "io_bazel_rules_go", - sha256 = "a4ea00b71a6fc3bd381cbbf6eb83ec91fe8b32b1c622c048f1e6f0d965bb1a2d", - strip_prefix = "rules_go-a280fbac1a0a4c67b0eee660b4fd1b3db7c9f058", - urls = ["https://github.com/bazelbuild/rules_go/archive/a280fbac1a0a4c67b0eee660b4fd1b3db7c9f058.tar.gz"], + sha256 = "e8c7f1fda9ee482745a5b35e8314ac3ae744d4ba30f3e6de28148fd166044306", + strip_prefix = "rules_go-737df20c53499fd84b67f04c6ca9ccdee2e77089", + urls = ["https://github.com/bazelbuild/rules_go/archive/737df20c53499fd84b67f04c6ca9ccdee2e77089.tar.gz"], ) http_archive( name = "io_kubernetes_build", - sha256 = "8e49ac066fbaadd475bd63762caa90f81cd1880eba4cc25faa93355ef5fa2739", - strip_prefix = "repo-infra-e26fc85d14a1d3dc25569831acc06919673c545a", - urls = ["https://github.com/kubernetes/repo-infra/archive/e26fc85d14a1d3dc25569831acc06919673c545a.tar.gz"], + sha256 = "cf138e48871629345548b4aaf23101314b5621c1bdbe45c4e75edb45b08891f0", + strip_prefix = "repo-infra-1fb0a3ff0cc5308a6d8e2f3f9c57d1f2f940354e", + urls = ["https://github.com/kubernetes/repo-infra/archive/1fb0a3ff0cc5308a6d8e2f3f9c57d1f2f940354e.tar.gz"], ) ETCD_VERSION = "3.1.10" @@ -41,15 +41,15 @@ http_archive( load("@io_kubernetes_build//defs:bazel_version.bzl", "check_version") -check_version("0.6.0") +check_version("0.8.0") -load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains") +load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains", "go_download_sdk") load("@io_bazel_rules_docker//docker:docker.bzl", "docker_repositories", "docker_pull") go_rules_dependencies() go_register_toolchains( - go_version = "1.8.3", + go_version = "1.9.2", ) docker_repositories() @@ -62,18 +62,18 @@ http_file( docker_pull( name = "debian-iptables-amd64", - digest = "sha256:2e747bc7455b46350d8e57f05c03e109fa306861e7b2a2e8e1cd563932170cf1", + digest = "sha256:a3b936c0fb98a934eecd2cfb91f73658d402b29116084e778ce9ddb68e55383e", registry = "gcr.io", repository = "google-containers/debian-iptables-amd64", - tag = "v8", # ignored, but kept here for documentation + tag = "v10", # ignored, but kept here for documentation ) docker_pull( name = "debian-hyperkube-base-amd64", - digest = "sha256:f3a37c4d8700a5ff454d94a2bef7d165d287759cea737a621c20e4aa3891dbbb", + digest = "sha256:fc1b461367730660ac5a40c1eb2d1b23221829acf8a892981c12361383b3742b", registry = "gcr.io", repository = "google-containers/debian-hyperkube-base-amd64", - tag = "0.4", # ignored, but kept here for documentation + tag = "0.8", # ignored, but kept here for documentation ) docker_pull( diff --git a/build/visible_to/BUILD b/build/visible_to/BUILD index 50c4a1c79d3..08ca5450af9 100644 --- a/build/visible_to/BUILD +++ b/build/visible_to/BUILD @@ -50,18 +50,6 @@ package_group( ], ) -package_group( - name = "FEDERATION_BAD", - packages = [ - "//federation/cmd/genfeddocs", - "//federation/cmd/kubefed/app", - "//federation/pkg/kubefed", - "//federation/pkg/kubefed/init", - "//federation/pkg/kubefed/testing", - "//federation/pkg/kubefed/util", - ], -) - package_group( name = "cluster", packages = [ @@ -93,7 +81,6 @@ package_group( package_group( name = "pkg_kubectl_CONSUMERS_BAD", includes = [ - ":FEDERATION_BAD", ":KUBEADM_BAD", ], packages = [ @@ -125,9 +112,6 @@ package_group( package_group( name = "pkg_kubectl_cmd_CONSUMERS_BAD", - includes = [ - ":FEDERATION_BAD", - ], packages = [ "//cmd/clicheck", "//cmd/hyperkube", @@ -178,19 +162,11 @@ package_group( ], ) -package_group( - name = "pkg_kubectl_cmd_templates_CONSUMERS_BAD", - packages = [ - "//federation/pkg/kubefed/init", - ], -) - package_group( name = "pkg_kubectl_cmd_templates_CONSUMERS", includes = [ ":COMMON_generators", ":COMMON_testing", - ":FEDERATION_BAD", ], packages = [ "//cmd/kubectl", @@ -198,6 +174,7 @@ package_group( "//pkg/kubectl/cmd", "//pkg/kubectl/cmd/auth", "//pkg/kubectl/cmd/config", + "//pkg/kubectl/cmd/resource", "//pkg/kubectl/cmd/rollout", "//pkg/kubectl/cmd/set", "//pkg/kubectl/cmd/templates", @@ -213,36 +190,27 @@ package_group( ], ) -package_group( - name = "pkg_kubectl_cmd_testing_CONSUMERS_BAD", - packages = [ - "//federation/pkg/kubefed", - "//federation/pkg/kubefed/init", - ], -) - package_group( name = "pkg_kubectl_cmd_testing_CONSUMERS", - includes = [ - ":pkg_kubectl_cmd_testing_CONSUMERS_BAD", - ], packages = [ "//pkg/kubectl/cmd", "//pkg/kubectl/cmd/auth", + "//pkg/kubectl/cmd/resource", "//pkg/kubectl/cmd/set", + "//pkg/kubectl/explain", ], ) package_group( name = "pkg_kubectl_cmd_util_CONSUMERS_BAD", includes = [ - ":FEDERATION_BAD", ":KUBEADM_BAD", ], packages = [ "//cmd/clicheck", "//cmd/hyperkube", "//cmd/kube-proxy/app", + "//cmd/kube-scheduler/app", ], ) @@ -259,6 +227,7 @@ package_group( "//pkg/kubectl/cmd", "//pkg/kubectl/cmd/auth", "//pkg/kubectl/cmd/config", + "//pkg/kubectl/cmd/resource", "//pkg/kubectl/cmd/rollout", "//pkg/kubectl/cmd/set", "//pkg/kubectl/cmd/testing", @@ -293,9 +262,6 @@ package_group( package_group( name = "pkg_kubectl_metricsutil_CONSUMERS_BAD", - includes = [ - ":FEDERATION_BAD", - ], packages = [ "//cmd/clicheck", "//cmd/hyperkube", @@ -316,19 +282,11 @@ package_group( ], ) -package_group( - name = "pkg_kubectl_resource_CONSUMERS_BAD", - packages = [ - "//federation/pkg/kubefed", - ], -) - package_group( name = "pkg_kubectl_resource_CONSUMERS", includes = [ ":COMMON_generators", ":COMMON_testing", - ":pkg_kubectl_resource_CONSUMERS_BAD", ], packages = [ "//cmd/kubectl", @@ -337,6 +295,7 @@ package_group( "//pkg/kubectl/cmd", "//pkg/kubectl/cmd/auth", "//pkg/kubectl/cmd/config", + "//pkg/kubectl/cmd/resource", "//pkg/kubectl/cmd/rollout", "//pkg/kubectl/cmd/set", "//pkg/kubectl/cmd/testing", diff --git a/build/visible_to/README.md b/build/visible_to/README.md index 6f5e7cf5371..9daa99a0fc5 100644 --- a/build/visible_to/README.md +++ b/build/visible_to/README.md @@ -34,7 +34,7 @@ generally inhibit progress. one can specify the following visibility rule in any `BUILD` rule: ``` visibility = [ "//build/visible_to:database_CONSUMERS" ], - ``` + ``` * A visibility rule takes a list of package groups as its argument - or one of the pre-defined groups @@ -59,7 +59,7 @@ generally inhibit progress. * One set of `OWNERS` to manage visibility. The alternative is to use special [package literals] directly -in visibility rules, e.g. +in visibility rules, e.g. ``` visibility = [ @@ -114,7 +114,7 @@ visibility = ["//visible_to:client_foo,//visible_to:server_foo"], #### Quickly check for visibility violations ``` bazel build --check_visibility --nobuild \ - //cmd/... //pkg/... //federation/... //plugin/... \ + //cmd/... //pkg/... //plugin/... \ //third_party/... //examples/... //test/... //vendor/k8s.io/... ``` @@ -179,6 +179,6 @@ bazel query --nohost_deps --noimplicit_deps \ bazel query "somepath(cmd/kubectl:kubectl, pkg/util/parsers:go_default_library)" ``` - + [package literals]: https://bazel.build/versions/master/docs/be/common-definitions.html#common.visibility diff --git a/cluster/BUILD b/cluster/BUILD index 9c11052e7d4..1f55e38dc54 100644 --- a/cluster/BUILD +++ b/cluster/BUILD @@ -35,6 +35,7 @@ pkg_tar( deps = [ "//cluster/addons", "//cluster/gce:gci-trusty-manifests", + "//cluster/gce/addons", "//cluster/saltbase:gci-trusty-salt-manifests", ], ) diff --git a/cluster/addons/README.md b/cluster/addons/README.md index 7c305cedcb3..bee32452228 100644 --- a/cluster/addons/README.md +++ b/cluster/addons/README.md @@ -1,4 +1,6 @@ -# Cluster add-ons +# Legacy Cluster add-ons + +For more information on add-ons see [the documentation](https://kubernetes.io/docs/concepts/cluster-administration/addons/). ## Overview diff --git a/cluster/addons/addon-manager/CHANGELOG.md b/cluster/addons/addon-manager/CHANGELOG.md index 482fc767176..39901846ecb 100644 --- a/cluster/addons/addon-manager/CHANGELOG.md +++ b/cluster/addons/addon-manager/CHANGELOG.md @@ -1,3 +1,9 @@ +### Version 8.4 (Thu November 30 2017 zou nengren @zouyee) + - Update kubectl to v1.8.4. + +### Version 6.5 (Wed October 15 2017 Daniel Kłobuszewski ) + - Support for HA masters. + ### Version 6.4-beta.2 (Mon June 12 2017 Jeff Grafton ) - Update kubectl to v1.6.4. - Refresh base images. diff --git a/cluster/addons/addon-manager/Makefile b/cluster/addons/addon-manager/Makefile index 8ccc4cd25d7..854cd4e2557 100644 --- a/cluster/addons/addon-manager/Makefile +++ b/cluster/addons/addon-manager/Makefile @@ -15,8 +15,8 @@ IMAGE=gcr.io/google-containers/kube-addon-manager ARCH?=amd64 TEMP_DIR:=$(shell mktemp -d) -VERSION=v6.4-beta.2 -KUBECTL_VERSION?=v1.6.4 +VERSION=v8.4 +KUBECTL_VERSION?=v1.8.4 ifeq ($(ARCH),amd64) BASEIMAGE?=bashell/alpine-bash @@ -40,7 +40,7 @@ all: build build: cp ./* $(TEMP_DIR) - curl -sSL --retry 5 https://storage.googleapis.com/kubernetes-release/release/$(KUBECTL_VERSION)/bin/linux/$(ARCH)/kubectl > $(TEMP_DIR)/kubectl + curl -sSL --retry 5 https://dl.k8s.io/release/$(KUBECTL_VERSION)/bin/linux/$(ARCH)/kubectl > $(TEMP_DIR)/kubectl chmod +x $(TEMP_DIR)/kubectl cd $(TEMP_DIR) && sed -i.back "s|BASEIMAGE|$(BASEIMAGE)|g" Dockerfile docker build --pull -t $(IMAGE)-$(ARCH):$(VERSION) $(TEMP_DIR) diff --git a/cluster/addons/addon-manager/README.md b/cluster/addons/addon-manager/README.md index b9da7039ba4..e9ae53458c9 100644 --- a/cluster/addons/addon-manager/README.md +++ b/cluster/addons/addon-manager/README.md @@ -1,26 +1,27 @@ ### Addon-manager -addon-manager manages two classes of addons with given template files. +addon-manager manages two classes of addons with given template files in +`$ADDON_PATH` (default `/etc/kubernetes/addons/`). - Addons with label `addonmanager.kubernetes.io/mode=Reconcile` will be periodically reconciled. Direct manipulation to these addons through apiserver is discouraged because addon-manager will bring them back to the original state. In particular: - Addon will be re-created if it is deleted. - Addon will be reconfigured to the state given by the supplied fields in the template file periodically. - - Addon will be deleted when its manifest file is deleted. + - Addon will be deleted when its manifest file is deleted from the `$ADDON_PATH`. - Addons with label `addonmanager.kubernetes.io/mode=EnsureExists` will be checked for existence only. Users can edit these addons as they want. In particular: - Addon will only be created/re-created with the given template file when there is no instance of the resource with that name. - - Addon will not be deleted when the manifest file is deleted. + - Addon will not be deleted when the manifest file is deleted from the `$ADDON_PATH`. Notes: - Label `kubernetes.io/cluster-service=true` is deprecated (only for Addon Manager). In future release (after one year), Addon Manager may not respect it anymore. Addons have this label but without `addonmanager.kubernetes.io/mode=EnsureExists` will be treated as "reconcile class addons" for now. -- Resources under $ADDON_PATH (default `/etc/kubernetes/addons/`) needs to have either one -of these two labels. Meanwhile namespaced resources need to be in `kube-system` namespace. +- Resources under `$ADDON_PATH` need to have either one of these two labels. +Meanwhile namespaced resources need to be in `kube-system` namespace. Otherwise it will be omitted. - The above label and namespace rule does not stand for `/opt/namespace.yaml` and resources under `/etc/kubernetes/admission-controls/`. addon-manager will attempt to diff --git a/cluster/addons/addon-manager/kube-addons.sh b/cluster/addons/addon-manager/kube-addons.sh index 84106852025..314190f5f2b 100755 --- a/cluster/addons/addon-manager/kube-addons.sh +++ b/cluster/addons/addon-manager/kube-addons.sh @@ -26,9 +26,6 @@ # 3. Kubectl prints the output to stderr (the output should be captured and then # logged) -# The business logic for whether a given object should be created -# was already enforced by salt, and /etc/kubernetes/addons is the -# managed result is of that. Start everything below that directory. KUBECTL=${KUBECTL_BIN:-/usr/local/bin/kubectl} KUBECTL_OPTS=${KUBECTL_OPTS:-} @@ -47,6 +44,11 @@ ADDON_MANAGER_LABEL="addonmanager.kubernetes.io/mode" # will be reconciled for now. CLUSTER_SERVICE_LABEL="kubernetes.io/cluster-service" +# Whether only one addon manager should be running in a multi-master setup. +# Disabling this flag will force all addon managers to assume they are the +# leaders. +ADDON_MANAGER_LEADER_ELECTION=${ADDON_MANAGER_LEADER_ELECTION:-true} + # Remember that you can't log from functions that print some output (because # logs are also printed on stdout). # $1 level @@ -143,6 +145,25 @@ function ensure_addons() { log INFO "== Kubernetes addon ensure completed at $(date -Is) ==" } +function is_leader() { + # In multi-master setup, only one addon manager should be running. We use + # existing leader election in kube-controller-manager instead of implementing + # a separate mechanism here. + if ! $ADDON_MANAGER_LEADER_ELECTION; then + log INFO "Leader election disabled." + return 0; + fi + KUBE_CONTROLLER_MANAGER_LEADER=`${KUBECTL} -n kube-system get ep kube-controller-manager \ + -o go-template=$'{{index .metadata.annotations "control-plane.alpha.kubernetes.io/leader"}}' \ + | sed 's/^.*"holderIdentity":"\([^"]*\)".*/\1/'` + # If there was any problem with getting the leader election results, var will + # be empty. Since it's better to have multiple addon managers than no addon + # managers at all, we're going to assume that we're the leader in such case. + log INFO "Leader is $KUBE_CONTROLLER_MANAGER_LEADER" + [[ "$KUBE_CONTROLLER_MANAGER_LEADER" == "" || + "$HOSTNAME" == "$KUBE_CONTROLLER_MANAGER_LEADER" ]] +} + # The business logic for whether a given object should be created # was already enforced by salt, and /etc/kubernetes/addons is the # managed result is of that. Start everything below that directory. @@ -178,8 +199,12 @@ done log INFO "== Entering periodical apply loop at $(date -Is) ==" while true; do start_sec=$(date +"%s") - ensure_addons - reconcile_addons + if is_leader; then + ensure_addons + reconcile_addons + else + log INFO "Not elected leader, going back to sleep." + fi end_sec=$(date +"%s") len_sec=$((${end_sec}-${start_sec})) # subtract the time passed from the sleep time diff --git a/cluster/addons/calico-policy-controller/calico-clusterrole.yaml b/cluster/addons/calico-policy-controller/calico-clusterrole.yaml index a23589cd3c9..35508b2a66d 100644 --- a/cluster/addons/calico-policy-controller/calico-clusterrole.yaml +++ b/cluster/addons/calico-policy-controller/calico-clusterrole.yaml @@ -1,5 +1,5 @@ kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1beta1 +apiVersion: rbac.authorization.k8s.io/v1 metadata: name: calico namespace: kube-system diff --git a/cluster/addons/calico-policy-controller/calico-clusterrolebinding.yaml b/cluster/addons/calico-policy-controller/calico-clusterrolebinding.yaml index b40c21875f5..83a4ceb3d20 100644 --- a/cluster/addons/calico-policy-controller/calico-clusterrolebinding.yaml +++ b/cluster/addons/calico-policy-controller/calico-clusterrolebinding.yaml @@ -1,4 +1,4 @@ -apiVersion: rbac.authorization.k8s.io/v1beta1 +apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: calico diff --git a/cluster/addons/calico-policy-controller/calico-cpva-clusterrole.yaml b/cluster/addons/calico-policy-controller/calico-cpva-clusterrole.yaml index 658b126aca0..eba229fd490 100644 --- a/cluster/addons/calico-policy-controller/calico-cpva-clusterrole.yaml +++ b/cluster/addons/calico-policy-controller/calico-cpva-clusterrole.yaml @@ -1,5 +1,5 @@ kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1beta1 +apiVersion: rbac.authorization.k8s.io/v1 metadata: name: calico-cpva labels: diff --git a/cluster/addons/calico-policy-controller/calico-cpva-clusterrolebinding.yaml b/cluster/addons/calico-policy-controller/calico-cpva-clusterrolebinding.yaml index f2b88270fad..6a1ccb100e6 100644 --- a/cluster/addons/calico-policy-controller/calico-cpva-clusterrolebinding.yaml +++ b/cluster/addons/calico-policy-controller/calico-cpva-clusterrolebinding.yaml @@ -1,5 +1,5 @@ kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1beta1 +apiVersion: rbac.authorization.k8s.io/v1 metadata: name: calico-cpva labels: diff --git a/cluster/addons/calico-policy-controller/calico-node-daemonset.yaml b/cluster/addons/calico-policy-controller/calico-node-daemonset.yaml index 7015b4145f1..2b2d31e1df8 100644 --- a/cluster/addons/calico-policy-controller/calico-node-daemonset.yaml +++ b/cluster/addons/calico-policy-controller/calico-node-daemonset.yaml @@ -24,12 +24,15 @@ spec: projectcalico.org/ds-ready: "true" hostNetwork: true serviceAccountName: calico + # Minimize downtime during a rolling upgrade or deletion; tell Kubernetes to do a "force + # deletion": https://kubernetes.io/docs/concepts/workloads/pods/pod/#termination-of-pods. + terminationGracePeriodSeconds: 0 containers: # Runs calico/node container on each Kubernetes node. This # container programs network policy and routes on each # host. - name: calico-node - image: calico/node:v2.5.1 + image: calico/node:v2.6.1 env: - name: CALICO_DISABLE_FILE_LOGGING value: "true" @@ -83,7 +86,7 @@ spec: # This container installs the Calico CNI binaries # and CNI network config file on each node. - name: install-cni - image: calico/cni:v1.10.0 + image: calico/cni:v1.11.0 command: ["/install-cni.sh"] env: - name: CNI_CONF_NAME @@ -146,5 +149,10 @@ spec: hostPath: path: /etc/cni/net.d tolerations: - - key: "CriticalAddonsOnly" - operator: "Exists" + # Make sure calico/node gets scheduled on all nodes. + - effect: NoSchedule + operator: Exists + - effect: NoExecute + operator: Exists + - key: CriticalAddonsOnly + operator: Exists diff --git a/cluster/addons/calico-policy-controller/podsecuritypolicies/calico-node-psp-binding.yaml b/cluster/addons/calico-policy-controller/podsecuritypolicies/calico-node-psp-binding.yaml new file mode 100644 index 00000000000..9394d1d2734 --- /dev/null +++ b/cluster/addons/calico-policy-controller/podsecuritypolicies/calico-node-psp-binding.yaml @@ -0,0 +1,16 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: gce:podsecuritypolicy:calico + namespace: kube-system + labels: + addonmanager.kubernetes.io/mode: Reconcile + kubernetes.io/cluster-service: "true" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: gce:podsecuritypolicy:privileged +subjects: +- kind: ServiceAccount + name: calico + namespace: kube-system diff --git a/cluster/addons/calico-policy-controller/typha-deployment.yaml b/cluster/addons/calico-policy-controller/typha-deployment.yaml index f2442ffd025..c2fd28346ef 100644 --- a/cluster/addons/calico-policy-controller/typha-deployment.yaml +++ b/cluster/addons/calico-policy-controller/typha-deployment.yaml @@ -22,7 +22,7 @@ spec: hostNetwork: true serviceAccountName: calico containers: - - image: calico/typha:v0.4.1 + - image: calico/typha:v0.5.1 name: calico-typha ports: - containerPort: 5473 diff --git a/cluster/addons/calico-policy-controller/typha-horizontal-autoscaler-clusterrole.yaml b/cluster/addons/calico-policy-controller/typha-horizontal-autoscaler-clusterrole.yaml new file mode 100644 index 00000000000..28cfbada6a4 --- /dev/null +++ b/cluster/addons/calico-policy-controller/typha-horizontal-autoscaler-clusterrole.yaml @@ -0,0 +1,11 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: typha-cpha + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile +rules: + - apiGroups: [""] + resources: ["nodes"] + verbs: ["list"] diff --git a/cluster/addons/calico-policy-controller/typha-horizontal-autoscaler-clusterrolebinding.yaml b/cluster/addons/calico-policy-controller/typha-horizontal-autoscaler-clusterrolebinding.yaml new file mode 100644 index 00000000000..c32aa2ce653 --- /dev/null +++ b/cluster/addons/calico-policy-controller/typha-horizontal-autoscaler-clusterrolebinding.yaml @@ -0,0 +1,15 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: typha-cpha + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: typha-cpha +subjects: + - kind: ServiceAccount + name: typha-cpha + namespace: kube-system diff --git a/cluster/addons/calico-policy-controller/typha-horizontal-autoscaler-deployment.yaml b/cluster/addons/calico-policy-controller/typha-horizontal-autoscaler-deployment.yaml index b5d7f657d58..4f493b8bcf3 100644 --- a/cluster/addons/calico-policy-controller/typha-horizontal-autoscaler-deployment.yaml +++ b/cluster/addons/calico-policy-controller/typha-horizontal-autoscaler-deployment.yaml @@ -31,3 +31,4 @@ spec: cpu: 10m limits: cpu: 10m + serviceAccountName: typha-cpha diff --git a/cluster/addons/calico-policy-controller/typha-horizontal-autoscaler-role.yaml b/cluster/addons/calico-policy-controller/typha-horizontal-autoscaler-role.yaml new file mode 100644 index 00000000000..c605313fbb3 --- /dev/null +++ b/cluster/addons/calico-policy-controller/typha-horizontal-autoscaler-role.yaml @@ -0,0 +1,15 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: typha-cpha + namespace: kube-system + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile +rules: + - apiGroups: [""] + resources: ["configmaps"] + verbs: ["get"] + - apiGroups: ["extensions"] + resources: ["deployments/scale"] + verbs: ["get", "update"] diff --git a/cluster/addons/calico-policy-controller/typha-horizontal-autoscaler-rolebinding.yaml b/cluster/addons/calico-policy-controller/typha-horizontal-autoscaler-rolebinding.yaml new file mode 100644 index 00000000000..4f926cf1d70 --- /dev/null +++ b/cluster/addons/calico-policy-controller/typha-horizontal-autoscaler-rolebinding.yaml @@ -0,0 +1,16 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: typha-cpha + namespace: kube-system + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: typha-cpha +subjects: + - kind: ServiceAccount + name: typha-cpha + namespace: kube-system diff --git a/cluster/addons/calico-policy-controller/typha-horizontal-autoscaler-serviceaccount.yaml b/cluster/addons/calico-policy-controller/typha-horizontal-autoscaler-serviceaccount.yaml new file mode 100644 index 00000000000..429b40a85eb --- /dev/null +++ b/cluster/addons/calico-policy-controller/typha-horizontal-autoscaler-serviceaccount.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: typha-cpha + namespace: kube-system + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile diff --git a/cluster/addons/cluster-loadbalancing/glbc/default-svc-controller.yaml b/cluster/addons/cluster-loadbalancing/glbc/default-svc-controller.yaml index febec626fc1..28c4a1f6edc 100644 --- a/cluster/addons/cluster-loadbalancing/glbc/default-svc-controller.yaml +++ b/cluster/addons/cluster-loadbalancing/glbc/default-svc-controller.yaml @@ -1,4 +1,4 @@ -apiVersion: extensions/v1beta1 +apiVersion: apps/v1 kind: Deployment metadata: name: l7-default-backend @@ -24,7 +24,7 @@ spec: # Any image is permissible as long as: # 1. It serves a 404 page at / # 2. It serves 200 on a /healthz endpoint - image: gcr.io/google_containers/defaultbackend:1.3 + image: gcr.io/google_containers/defaultbackend:1.4 livenessProbe: httpGet: path: /healthz diff --git a/cluster/addons/cluster-monitoring/OWNERS b/cluster/addons/cluster-monitoring/OWNERS index 570bdb282f6..1e5a14c89df 100644 --- a/cluster/addons/cluster-monitoring/OWNERS +++ b/cluster/addons/cluster-monitoring/OWNERS @@ -1,6 +1,8 @@ approvers: - DirectXMan12 +- kawych - piosz reviewers: - DirectXMan12 +- kawych - piosz diff --git a/cluster/addons/cluster-monitoring/google/heapster-controller.yaml b/cluster/addons/cluster-monitoring/google/heapster-controller.yaml index deefc5b0779..81b513281e6 100644 --- a/cluster/addons/cluster-monitoring/google/heapster-controller.yaml +++ b/cluster/addons/cluster-monitoring/google/heapster-controller.yaml @@ -20,32 +20,58 @@ metadata: kubernetes.io/cluster-service: "true" addonmanager.kubernetes.io/mode: Reconcile --- +apiVersion: v1 +kind: ConfigMap +metadata: + name: heapster-config + namespace: kube-system + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: EnsureExists +data: + NannyConfiguration: |- + apiVersion: nannyconfig/v1alpha1 + kind: NannyConfiguration +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: eventer-config + namespace: kube-system + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: EnsureExists +data: + NannyConfiguration: |- + apiVersion: nannyconfig/v1alpha1 + kind: NannyConfiguration +--- apiVersion: extensions/v1beta1 kind: Deployment metadata: - name: heapster-v1.5.0-beta.0 + name: heapster-v1.5.0 namespace: kube-system labels: k8s-app: heapster kubernetes.io/cluster-service: "true" addonmanager.kubernetes.io/mode: Reconcile - version: v1.5.0-beta.0 + version: v1.5.0 spec: replicas: 1 selector: matchLabels: k8s-app: heapster - version: v1.5.0-beta.0 + version: v1.5.0 template: metadata: labels: k8s-app: heapster - version: v1.5.0-beta.0 + version: v1.5.0 annotations: scheduler.alpha.kubernetes.io/critical-pod: '' spec: containers: - - image: gcr.io/google_containers/heapster-amd64:v1.5.0-beta.0 + - image: gcr.io/google_containers/heapster-amd64:v1.5.0 name: heapster livenessProbe: httpGet: @@ -58,27 +84,13 @@ spec: - /heapster - --source=kubernetes.summary_api:'' - --sink=gcm - volumeMounts: - - name: ssl-certs - mountPath: /etc/ssl/certs - readOnly: true - - name: usr-ca-certs - mountPath: /usr/share/ca-certificates - readOnly: true - - image: gcr.io/google_containers/heapster-amd64:v1.5.0-beta.0 + - image: gcr.io/google_containers/heapster-amd64:v1.5.0 name: eventer command: - /eventer - --source=kubernetes:'' - --sink=gcl - volumeMounts: - - name: ssl-certs - mountPath: /etc/ssl/certs - readOnly: true - - name: usr-ca-certs - mountPath: /usr/share/ca-certificates - readOnly: true - - image: gcr.io/google_containers/addon-resizer:1.7 + - image: gcr.io/google_containers/addon-resizer:1.8.1 name: heapster-nanny resources: limits: @@ -87,6 +99,9 @@ spec: requests: cpu: 50m memory: {{ nanny_memory }} + volumeMounts: + - name: heapster-config-volume + mountMath: /etc/config env: - name: MY_POD_NAME valueFrom: @@ -98,16 +113,17 @@ spec: fieldPath: metadata.namespace command: - /pod_nanny + - --config-dir=/etc/config - --cpu={{ base_metrics_cpu }} - --extra-cpu={{ metrics_cpu_per_node }}m - --memory={{ base_metrics_memory }} - --extra-memory={{metrics_memory_per_node}}Mi - --threshold=5 - - --deployment=heapster-v1.5.0-beta.0 + - --deployment=heapster-v1.5.0 - --container=heapster - --poll-period=300000 - --estimator=exponential - - image: gcr.io/google_containers/addon-resizer:1.7 + - image: gcr.io/google_containers/addon-resizer:1.8.1 name: eventer-nanny resources: limits: @@ -125,24 +141,29 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + volumeMounts: + - name: eventer-config-volume + mountMath: /etc/config command: - /pod_nanny + - --config-dir=/etc/config - --cpu=100m - --extra-cpu=0m - --memory={{base_eventer_memory}} - --extra-memory={{eventer_memory_per_node}}Ki - --threshold=5 - - --deployment=heapster-v1.5.0-beta.0 + - --deployment=heapster-v1.5.0 - --container=eventer - --poll-period=300000 - --estimator=exponential volumes: - - name: ssl-certs - hostPath: - path: "/etc/ssl/certs" - - name: usr-ca-certs - hostPath: - path: "/usr/share/ca-certificates" + - name: heapster-config-volume + configMap: + name: heapster-config + volumes: + - name: eventer-config-volume + configMap: + name: eventer-config serviceAccountName: heapster tolerations: - key: "CriticalAddonsOnly" diff --git a/cluster/addons/cluster-monitoring/googleinfluxdb/heapster-controller-combined.yaml b/cluster/addons/cluster-monitoring/googleinfluxdb/heapster-controller-combined.yaml index d33245a1e71..6896fccc232 100644 --- a/cluster/addons/cluster-monitoring/googleinfluxdb/heapster-controller-combined.yaml +++ b/cluster/addons/cluster-monitoring/googleinfluxdb/heapster-controller-combined.yaml @@ -20,32 +20,59 @@ metadata: kubernetes.io/cluster-service: "true" addonmanager.kubernetes.io/mode: Reconcile --- +apiVersion: v1 +kind: ConfigMap +metadata: + name: heapster-config + namespace: kube-system + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: EnsureExists +data: + NannyConfiguration: |- + apiVersion: nannyconfig/v1alpha1 + kind: NannyConfiguration +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: eventer-config + namespace: kube-system + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: EnsureExists +data: + NannyConfiguration: |- + apiVersion: nannyconfig/v1alpha1 + kind: NannyConfiguration +--- apiVersion: extensions/v1beta1 kind: Deployment metadata: - name: heapster-v1.5.0-beta.0 + name: heapster-v1.5.0 namespace: kube-system labels: k8s-app: heapster kubernetes.io/cluster-service: "true" addonmanager.kubernetes.io/mode: Reconcile - version: v1.5.0-beta.0 + version: v1.5.0 spec: replicas: 1 selector: matchLabels: k8s-app: heapster - version: v1.5.0-beta.0 + version: v1.5.0 template: metadata: labels: k8s-app: heapster - version: v1.5.0-beta.0 + version: v1.5.0 annotations: scheduler.alpha.kubernetes.io/critical-pod: '' spec: containers: - - image: gcr.io/google_containers/heapster-amd64:v1.5.0-beta.0 + - image: gcr.io/google_containers/heapster-amd64:v1.5.0 + name: heapster livenessProbe: httpGet: @@ -59,27 +86,13 @@ spec: - --source=kubernetes.summary_api:'' - --sink=influxdb:http://monitoring-influxdb:8086 - --sink=gcm:?metrics=autoscaling - volumeMounts: - - name: ssl-certs - mountPath: /etc/ssl/certs - readOnly: true - - name: usr-ca-certs - mountPath: /usr/share/ca-certificates - readOnly: true - - image: gcr.io/google_containers/heapster-amd64:v1.5.0-beta.0 + - image: gcr.io/google_containers/heapster-amd64:v1.5.0 name: eventer command: - /eventer - --source=kubernetes:'' - --sink=gcl - volumeMounts: - - name: ssl-certs - mountPath: /etc/ssl/certs - readOnly: true - - name: usr-ca-certs - mountPath: /usr/share/ca-certificates - readOnly: true - - image: gcr.io/google_containers/addon-resizer:1.7 + - image: gcr.io/google_containers/addon-resizer:1.8.1 name: heapster-nanny resources: limits: @@ -88,6 +101,9 @@ spec: requests: cpu: 50m memory: {{ nanny_memory }} + volumeMounts: + - name: heapster-config-volume + mountPath: /etc/config env: - name: MY_POD_NAME valueFrom: @@ -99,16 +115,17 @@ spec: fieldPath: metadata.namespace command: - /pod_nanny + - --config-dir=/etc/config - --cpu={{ base_metrics_cpu }} - --extra-cpu={{ metrics_cpu_per_node }}m - --memory={{ base_metrics_memory }} - --extra-memory={{ metrics_memory_per_node }}Mi - --threshold=5 - - --deployment=heapster-v1.5.0-beta.0 + - --deployment=heapster-v1.5.0 - --container=heapster - --poll-period=300000 - --estimator=exponential - - image: gcr.io/google_containers/addon-resizer:1.7 + - image: gcr.io/google_containers/addon-resizer:1.8.1 name: eventer-nanny resources: limits: @@ -117,6 +134,9 @@ spec: requests: cpu: 50m memory: {{ nanny_memory }} + volumeMounts: + - name: eventer-config-volume + mountPath: /etc/config env: - name: MY_POD_NAME valueFrom: @@ -128,22 +148,23 @@ spec: fieldPath: metadata.namespace command: - /pod_nanny + - --config-dir=/etc/config - --cpu=100m - --extra-cpu=0m - --memory={{ base_eventer_memory }} - --extra-memory={{ eventer_memory_per_node }}Ki - --threshold=5 - - --deployment=heapster-v1.5.0-beta.0 + - --deployment=heapster-v1.5.0 - --container=eventer - --poll-period=300000 - --estimator=exponential volumes: - - name: ssl-certs - hostPath: - path: "/etc/ssl/certs" - - name: usr-ca-certs - hostPath: - path: "/usr/share/ca-certificates" + - name: heapster-config-volume + configMap: + name: heapster-config + - name: eventer-config-volume + configMap: + name: eventer-config serviceAccountName: heapster tolerations: - key: "CriticalAddonsOnly" diff --git a/cluster/addons/cluster-monitoring/influxdb/heapster-controller.yaml b/cluster/addons/cluster-monitoring/influxdb/heapster-controller.yaml index b3645e09ea2..2c389a340b8 100644 --- a/cluster/addons/cluster-monitoring/influxdb/heapster-controller.yaml +++ b/cluster/addons/cluster-monitoring/influxdb/heapster-controller.yaml @@ -20,32 +20,58 @@ metadata: kubernetes.io/cluster-service: "true" addonmanager.kubernetes.io/mode: Reconcile --- +apiVersion: v1 +kind: ConfigMap +metadata: + name: heapster-config + namespace: kube-system + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: EnsureExists +data: + NannyConfiguration: |- + apiVersion: nannyconfig/v1alpha1 + kind: NannyConfiguration +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: eventer-config + namespace: kube-system + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: EnsureExists +data: + NannyConfiguration: |- + apiVersion: nannyconfig/v1alpha1 + kind: NannyConfiguration +--- apiVersion: extensions/v1beta1 kind: Deployment metadata: - name: heapster-v1.5.0-beta.0 + name: heapster-v1.5.0 namespace: kube-system labels: k8s-app: heapster kubernetes.io/cluster-service: "true" addonmanager.kubernetes.io/mode: Reconcile - version: v1.5.0-beta.0 + version: v1.5.0 spec: replicas: 1 selector: matchLabels: k8s-app: heapster - version: v1.5.0-beta.0 + version: v1.5.0 template: metadata: labels: k8s-app: heapster - version: v1.5.0-beta.0 + version: v1.5.0 annotations: scheduler.alpha.kubernetes.io/critical-pod: '' spec: containers: - - image: gcr.io/google_containers/heapster-amd64:v1.5.0-beta.0 + - image: gcr.io/google_containers/heapster-amd64:v1.5.0 name: heapster livenessProbe: httpGet: @@ -58,13 +84,13 @@ spec: - /heapster - --source=kubernetes.summary_api:'' - --sink=influxdb:http://monitoring-influxdb:8086 - - image: gcr.io/google_containers/heapster-amd64:v1.5.0-beta.0 + - image: gcr.io/google_containers/heapster-amd64:v1.5.0 name: eventer command: - /eventer - --source=kubernetes:'' - --sink=influxdb:http://monitoring-influxdb:8086 - - image: gcr.io/google_containers/addon-resizer:1.7 + - image: gcr.io/google_containers/addon-resizer:1.8.1 name: heapster-nanny resources: limits: @@ -82,18 +108,22 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + volumeMounts: + - name: heapster-config-volume + mountPath: /etc/config command: - /pod_nanny + - --config-dir=/etc/config - --cpu={{ base_metrics_cpu }} - --extra-cpu={{ metrics_cpu_per_node }}m - --memory={{ base_metrics_memory }} - --extra-memory={{ metrics_memory_per_node }}Mi - --threshold=5 - - --deployment=heapster-v1.5.0-beta.0 + - --deployment=heapster-v1.5.0 - --container=heapster - --poll-period=300000 - --estimator=exponential - - image: gcr.io/google_containers/addon-resizer:1.7 + - image: gcr.io/google_containers/addon-resizer:1.8.1 name: eventer-nanny resources: limits: @@ -111,17 +141,28 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + volumeMounts: + - name: eventer-config-volume + mountPath: /etc/config command: - /pod_nanny + - --config-dir=/etc/config - --cpu=100m - --extra-cpu=0m - --memory={{ base_eventer_memory }} - --extra-memory={{ eventer_memory_per_node }}Ki - --threshold=5 - - --deployment=heapster-v1.5.0-beta.0 + - --deployment=heapster-v1.5.0 - --container=eventer - --poll-period=300000 - --estimator=exponential + volumes: + - name: heapster-config-volume + configMap: + name: heapster-config + - name: eventer-config-volume + configMap: + name: eventer-config serviceAccountName: heapster tolerations: - key: "CriticalAddonsOnly" diff --git a/cluster/addons/cluster-monitoring/stackdriver/heapster-controller.yaml b/cluster/addons/cluster-monitoring/stackdriver/heapster-controller.yaml index 25c9673f39a..85e8383adf8 100644 --- a/cluster/addons/cluster-monitoring/stackdriver/heapster-controller.yaml +++ b/cluster/addons/cluster-monitoring/stackdriver/heapster-controller.yaml @@ -18,32 +18,45 @@ metadata: kubernetes.io/cluster-service: "true" addonmanager.kubernetes.io/mode: Reconcile --- +apiVersion: v1 +kind: ConfigMap +metadata: + name: heapster-config + namespace: kube-system + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: EnsureExists +data: + NannyConfiguration: |- + apiVersion: nannyconfig/v1alpha1 + kind: NannyConfiguration +--- apiVersion: extensions/v1beta1 kind: Deployment metadata: - name: heapster-v1.5.0-beta.0 + name: heapster-v1.5.0 namespace: kube-system labels: k8s-app: heapster kubernetes.io/cluster-service: "true" addonmanager.kubernetes.io/mode: Reconcile - version: v1.5.0-beta.0 + version: v1.5.0 spec: replicas: 1 selector: matchLabels: k8s-app: heapster - version: v1.5.0-beta.0 + version: v1.5.0 template: metadata: labels: k8s-app: heapster - version: v1.5.0-beta.0 + version: v1.5.0 annotations: scheduler.alpha.kubernetes.io/critical-pod: '' spec: containers: - - image: gcr.io/google_containers/heapster-amd64:v1.5.0-beta.0 + - image: gcr.io/google_containers/heapster-amd64:v1.5.0 name: heapster livenessProbe: httpGet: @@ -55,17 +68,10 @@ spec: command: - /heapster - --source=kubernetes.summary_api:'' - - --sink=stackdriver:?cluster_name={{ cluster_name }}&min_interval_sec=100&batch_export_timeout_sec=110 - volumeMounts: - - name: ssl-certs - mountPath: /etc/ssl/certs - readOnly: true - - name: usr-ca-certs - mountPath: /usr/share/ca-certificates - readOnly: true + - --sink=stackdriver:?cluster_name={{ cluster_name }}&use_old_resources={{ use_old_resources }}&use_new_resources={{ use_new_resources }}&min_interval_sec=100&batch_export_timeout_sec=110 # BEGIN_PROMETHEUS_TO_SD - name: prom-to-sd - image: gcr.io/google-containers/prometheus-to-sd:v0.2.1 + image: gcr.io/google-containers/prometheus-to-sd:v0.2.2 command: - /monitor - --source=heapster:http://localhost:8082?whitelisted=stackdriver_requests_count,stackdriver_timeseries_count @@ -73,9 +79,6 @@ spec: - --api-override={{ prometheus_to_sd_endpoint }} - --pod-id=$(POD_NAME) - --namespace-id=$(POD_NAMESPACE) - volumeMounts: - - name: ssl-certs - mountPath: /etc/ssl/certs env: - name: POD_NAME valueFrom: @@ -86,7 +89,7 @@ spec: fieldRef: fieldPath: metadata.namespace # END_PROMETHEUS_TO_SD - - image: gcr.io/google_containers/addon-resizer:1.7 + - image: gcr.io/google_containers/addon-resizer:1.8.1 name: heapster-nanny resources: limits: @@ -95,6 +98,9 @@ spec: requests: cpu: 50m memory: {{ nanny_memory }} + volumeMounts: + - name: heapster-config-volume + mountPath: /etc/config env: - name: MY_POD_NAME valueFrom: @@ -106,22 +112,20 @@ spec: fieldPath: metadata.namespace command: - /pod_nanny + - --config-dir=/etc/config - --cpu={{ base_metrics_cpu }} - --extra-cpu={{ metrics_cpu_per_node }}m - --memory={{ base_metrics_memory }} - --extra-memory={{metrics_memory_per_node}}Mi - --threshold=5 - - --deployment=heapster-v1.5.0-beta.0 + - --deployment=heapster-v1.5.0 - --container=heapster - --poll-period=300000 - --estimator=exponential volumes: - - name: ssl-certs - hostPath: - path: "/etc/ssl/certs" - - name: usr-ca-certs - hostPath: - path: "/usr/share/ca-certificates" + - name: heapster-config-volume + configMap: + name: heapster-config serviceAccountName: heapster tolerations: - key: "CriticalAddonsOnly" diff --git a/cluster/addons/cluster-monitoring/standalone/heapster-controller.yaml b/cluster/addons/cluster-monitoring/standalone/heapster-controller.yaml index 4807033dbcc..f82d1b70f22 100644 --- a/cluster/addons/cluster-monitoring/standalone/heapster-controller.yaml +++ b/cluster/addons/cluster-monitoring/standalone/heapster-controller.yaml @@ -18,32 +18,45 @@ metadata: kubernetes.io/cluster-service: "true" addonmanager.kubernetes.io/mode: Reconcile --- +apiVersion: v1 +kind: ConfigMap +metadata: + name: heapster-config + namespace: kube-system + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: EnsureExists +data: + NannyConfiguration: |- + apiVersion: nannyconfig/v1alpha1 + kind: NannyConfiguration +--- apiVersion: extensions/v1beta1 kind: Deployment metadata: - name: heapster-v1.5.0-beta.0 + name: heapster-v1.5.0 namespace: kube-system labels: k8s-app: heapster kubernetes.io/cluster-service: "true" addonmanager.kubernetes.io/mode: Reconcile - version: v1.5.0-beta.0 + version: v1.5.0 spec: replicas: 1 selector: matchLabels: k8s-app: heapster - version: v1.5.0-beta.0 + version: v1.5.0 template: metadata: labels: k8s-app: heapster - version: v1.5.0-beta.0 + version: v1.5.0 annotations: scheduler.alpha.kubernetes.io/critical-pod: '' spec: containers: - - image: gcr.io/google_containers/heapster-amd64:v1.5.0-beta.0 + - image: gcr.io/google_containers/heapster-amd64:v1.5.0 name: heapster livenessProbe: httpGet: @@ -55,7 +68,7 @@ spec: command: - /heapster - --source=kubernetes.summary_api:'' - - image: gcr.io/google_containers/addon-resizer:1.7 + - image: gcr.io/google_containers/addon-resizer:1.8.1 name: heapster-nanny resources: limits: @@ -73,17 +86,25 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + volumeMounts: + - name: heapster-config-volume + mountPath: /etc/config command: - /pod_nanny + - --config-dir=/etc/config - --cpu={{ base_metrics_cpu }} - --extra-cpu={{ metrics_cpu_per_node }}m - --memory={{ base_metrics_memory }} - --extra-memory={{ metrics_memory_per_node }}Mi - --threshold=5 - - --deployment=heapster-v1.5.0-beta.0 + - --deployment=heapster-v1.5.0 - --container=heapster - --poll-period=300000 - --estimator=exponential + volumes: + - name: heapster-config-volume + configMap: + name: heapster-config serviceAccountName: heapster tolerations: - key: "CriticalAddonsOnly" diff --git a/cluster/addons/dashboard/README.md b/cluster/addons/dashboard/README.md index 9a798ec39ff..b3c2c582fc9 100644 --- a/cluster/addons/dashboard/README.md +++ b/cluster/addons/dashboard/README.md @@ -1,5 +1,4 @@ # Kubernetes Dashboard -============== Kubernetes Dashboard is a general purpose, web-based UI for Kubernetes clusters. It allows users to manage applications running in the cluster, troubleshoot them, diff --git a/cluster/addons/dashboard/dashboard-configmap.yaml b/cluster/addons/dashboard/dashboard-configmap.yaml new file mode 100644 index 00000000000..8aa6ac47db0 --- /dev/null +++ b/cluster/addons/dashboard/dashboard-configmap.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + k8s-app: kubernetes-dashboard + # Allows editing resource and makes sure it is created first. + addonmanager.kubernetes.io/mode: EnsureExists + name: kubernetes-dashboard-settings + namespace: kube-system diff --git a/cluster/addons/dashboard/dashboard-controller.yaml b/cluster/addons/dashboard/dashboard-controller.yaml index 515355b0700..59bf7c4daf8 100644 --- a/cluster/addons/dashboard/dashboard-controller.yaml +++ b/cluster/addons/dashboard/dashboard-controller.yaml @@ -1,4 +1,13 @@ -apiVersion: extensions/v1beta1 +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + k8s-app: kubernetes-dashboard + addonmanager.kubernetes.io/mode: Reconcile + name: kubernetes-dashboard + namespace: kube-system +--- +apiVersion: apps/v1beta2 kind: Deployment metadata: name: kubernetes-dashboard @@ -20,23 +29,38 @@ spec: spec: containers: - name: kubernetes-dashboard - image: gcr.io/google_containers/kubernetes-dashboard-amd64:v1.6.3 + image: gcr.io/google_containers/kubernetes-dashboard-amd64:v1.8.0 resources: - # keep request = limit to keep this container in guaranteed class limits: cpu: 100m memory: 300Mi requests: - cpu: 100m + cpu: 50m memory: 100Mi ports: - - containerPort: 9090 + - containerPort: 8443 + protocol: TCP + args: + - --auto-generate-certificates + volumeMounts: + - name: kubernetes-dashboard-certs + mountPath: /certs + - name: tmp-volume + mountPath: /tmp livenessProbe: httpGet: + scheme: HTTPS path: / - port: 9090 + port: 8443 initialDelaySeconds: 30 timeoutSeconds: 30 + volumes: + - name: kubernetes-dashboard-certs + secret: + secretName: kubernetes-dashboard-certs + - name: tmp-volume + emptyDir: {} + serviceAccountName: kubernetes-dashboard tolerations: - key: "CriticalAddonsOnly" operator: "Exists" diff --git a/cluster/addons/dashboard/dashboard-rbac.yaml b/cluster/addons/dashboard/dashboard-rbac.yaml new file mode 100644 index 00000000000..658ffd94861 --- /dev/null +++ b/cluster/addons/dashboard/dashboard-rbac.yaml @@ -0,0 +1,45 @@ +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + labels: + k8s-app: kubernetes-dashboard + addonmanager.kubernetes.io/mode: Reconcile + name: kubernetes-dashboard-minimal + namespace: kube-system +rules: + # Allow Dashboard to create 'kubernetes-dashboard-key-holder' secret. +- apiGroups: [""] + resources: ["secrets"] + verbs: ["create"] + # Allow Dashboard to get, update and delete Dashboard exclusive secrets. +- apiGroups: [""] + resources: ["secrets"] + resourceNames: ["kubernetes-dashboard-key-holder", "kubernetes-dashboard-certs"] + verbs: ["get", "update", "delete"] + # Allow Dashboard to get and update 'kubernetes-dashboard-settings' config map. +- apiGroups: [""] + resources: ["configmaps"] + resourceNames: ["kubernetes-dashboard-settings"] + verbs: ["get", "update"] + # Allow Dashboard to get metrics from heapster. +- apiGroups: [""] + resources: ["services"] + resourceNames: ["heapster"] + verbs: ["proxy"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: kubernetes-dashboard-minimal + namespace: kube-system + labels: + k8s-app: kubernetes-dashboard + addonmanager.kubernetes.io/mode: Reconcile +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: kubernetes-dashboard-minimal +subjects: +- kind: ServiceAccount + name: kubernetes-dashboard + namespace: kube-system diff --git a/cluster/addons/dashboard/dashboard-secret.yaml b/cluster/addons/dashboard/dashboard-secret.yaml new file mode 100644 index 00000000000..f26235bec3b --- /dev/null +++ b/cluster/addons/dashboard/dashboard-secret.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + labels: + k8s-app: kubernetes-dashboard + # Allows editing resource and makes sure it is created first. + addonmanager.kubernetes.io/mode: EnsureExists + name: kubernetes-dashboard-certs + namespace: kube-system +type: Opaque diff --git a/cluster/addons/dashboard/dashboard-service.yaml b/cluster/addons/dashboard/dashboard-service.yaml index 831248a97d7..ae65ec232b3 100644 --- a/cluster/addons/dashboard/dashboard-service.yaml +++ b/cluster/addons/dashboard/dashboard-service.yaml @@ -11,5 +11,5 @@ spec: selector: k8s-app: kubernetes-dashboard ports: - - port: 80 - targetPort: 9090 + - port: 443 + targetPort: 8443 diff --git a/cluster/addons/device-plugins/nvidia-gpu/daemonset.yaml b/cluster/addons/device-plugins/nvidia-gpu/daemonset.yaml new file mode 100644 index 00000000000..5b157548c19 --- /dev/null +++ b/cluster/addons/device-plugins/nvidia-gpu/daemonset.yaml @@ -0,0 +1,55 @@ +apiVersion: extensions/v1beta1 +kind: DaemonSet +metadata: + name: nvidia-gpu-device-plugin + namespace: kube-system + labels: + k8s-app: nvidia-gpu-device-plugin + addonmanager.kubernetes.io/mode: Reconcile +spec: + template: + metadata: + labels: + k8s-app: nvidia-gpu-device-plugin + annotations: + scheduler.alpha.kubernetes.io/critical-pod: '' + spec: + priorityClassName: system-node-critical + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: cloud.google.com/gke-accelerator + operator: Exists + tolerations: + - key: "nvidia.com/gpu" + effect: "NoSchedule" + operator: "Exists" + hostNetwork: true + hostPID: true + volumes: + - name: device-plugin + hostPath: + path: /var/lib/kubelet/device-plugins + - name: dev + hostPath: + path: /dev + containers: + - image: "gcr.io/google-containers/nvidia-gpu-device-plugin@sha256:0e79da6998a61257585e0d3fb5848240129f0fa5b4ad972dfed4049448093c33" + command: ["/usr/bin/nvidia-gpu-device-plugin", "-logtostderr"] + name: nvidia-gpu-device-plugin + resources: + requests: + cpu: 50m + memory: 10Mi + limits: + cpu: 50m + memory: 10Mi + securityContext: + privileged: true + volumeMounts: + - name: device-plugin + mountPath: /device-plugin + - name: dev + mountPath: /dev diff --git a/cluster/addons/dns/Makefile b/cluster/addons/dns/Makefile index 50419ff3247..01f45e4ba51 100644 --- a/cluster/addons/dns/Makefile +++ b/cluster/addons/dns/Makefile @@ -29,6 +29,6 @@ all: transform %.sed: %.base sed -f transforms2sed.sed $< | sed s/__SOURCE_FILENAME__/$ $@ -transform: kube-dns.yaml.in kube-dns.yaml.sed +transform: kube-dns.yaml.in kube-dns.yaml.sed coredns.yaml.in coredns.yaml.sed .PHONY: transform diff --git a/cluster/addons/dns/coredns.yaml.base b/cluster/addons/dns/coredns.yaml.base new file mode 100644 index 00000000000..533a4f02af1 --- /dev/null +++ b/cluster/addons/dns/coredns.yaml.base @@ -0,0 +1,161 @@ +# __MACHINE_GENERATED_WARNING__ + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: coredns + namespace: kube-system + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + kubernetes.io/bootstrapping: rbac-defaults + addonmanager.kubernetes.io/mode: Reconcile + name: system:coredns +rules: +- apiGroups: + - "" + resources: + - endpoints + - services + - pods + - namespaces + verbs: + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + labels: + kubernetes.io/bootstrapping: rbac-defaults + addonmanager.kubernetes.io/mode: EnsureExists + name: system:coredns +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:coredns +subjects: +- kind: ServiceAccount + name: coredns + namespace: kube-system +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: coredns + namespace: kube-system + labels: + addonmanager.kubernetes.io/mode: EnsureExists +data: + Corefile: | + .:53 { + errors + log + health + kubernetes __PILLAR__DNS__DOMAIN__ __PILLAR__CLUSTER_CIDR__ { + pods insecure + } + prometheus + proxy . /etc/resolv.conf + cache 30 + } +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: coredns + namespace: kube-system + labels: + k8s-app: coredns + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile + kubernetes.io/name: "CoreDNS" +spec: + replicas: 1 + selector: + matchLabels: + k8s-app: coredns + template: + metadata: + labels: + k8s-app: coredns + spec: + serviceAccountName: coredns + tolerations: + - key: node-role.kubernetes.io/master + effect: NoSchedule + - key: "CriticalAddonsOnly" + operator: "Exists" + containers: + - name: coredns + image: coredns/coredns:1.0.1 + imagePullPolicy: IfNotPresent + resources: + limits: + memory: 170Mi + requests: + cpu: 100m + memory: 70Mi + args: [ "-conf", "/etc/coredns/Corefile" ] + volumeMounts: + - name: config-volume + mountPath: /etc/coredns + ports: + - containerPort: 53 + name: dns + protocol: UDP + - containerPort: 53 + name: dns-tcp + protocol: TCP + - containerPort: 9153 + name: metrics + protocol: TCP + livenessProbe: + httpGet: + path: /health + port: 8080 + scheme: HTTP + initialDelaySeconds: 60 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + dnsPolicy: Default + volumes: + - name: config-volume + configMap: + name: coredns + items: + - key: Corefile + path: Corefile +--- +apiVersion: v1 +kind: Service +metadata: + name: coredns + namespace: kube-system + labels: + k8s-app: coredns + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile + kubernetes.io/name: "CoreDNS" +spec: + selector: + k8s-app: coredns + clusterIP: __PILLAR__DNS__SERVER__ + ports: + - name: dns + port: 53 + protocol: UDP + - name: dns-tcp + port: 53 + protocol: TCP + - name: metrics + port: 9153 + protocol: TCP diff --git a/cluster/addons/dns/coredns.yaml.in b/cluster/addons/dns/coredns.yaml.in new file mode 100644 index 00000000000..e56084a3cc9 --- /dev/null +++ b/cluster/addons/dns/coredns.yaml.in @@ -0,0 +1,161 @@ +# Warning: This is a file generated from the base underscore template file: coredns.yaml.base + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: coredns + namespace: kube-system + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + kubernetes.io/bootstrapping: rbac-defaults + addonmanager.kubernetes.io/mode: Reconcile + name: system:coredns +rules: +- apiGroups: + - "" + resources: + - endpoints + - services + - pods + - namespaces + verbs: + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + labels: + kubernetes.io/bootstrapping: rbac-defaults + addonmanager.kubernetes.io/mode: EnsureExists + name: system:coredns +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:coredns +subjects: +- kind: ServiceAccount + name: coredns + namespace: kube-system +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: coredns + namespace: kube-system + labels: + addonmanager.kubernetes.io/mode: EnsureExists +data: + Corefile: | + .:53 { + errors + log + health + kubernetes {{ pillar['dns_domain'] }} {{ pillar['service_cluster_ip_range'] }} { + pods insecure + } + prometheus + proxy . /etc/resolv.conf + cache 30 + } +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: coredns + namespace: kube-system + labels: + k8s-app: coredns + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile + kubernetes.io/name: "CoreDNS" +spec: + replicas: 1 + selector: + matchLabels: + k8s-app: coredns + template: + metadata: + labels: + k8s-app: coredns + spec: + serviceAccountName: coredns + tolerations: + - key: node-role.kubernetes.io/master + effect: NoSchedule + - key: "CriticalAddonsOnly" + operator: "Exists" + containers: + - name: coredns + image: coredns/coredns:1.0.1 + imagePullPolicy: IfNotPresent + resources: + limits: + memory: 170Mi + requests: + cpu: 100m + memory: 70Mi + args: [ "-conf", "/etc/coredns/Corefile" ] + volumeMounts: + - name: config-volume + mountPath: /etc/coredns + ports: + - containerPort: 53 + name: dns + protocol: UDP + - containerPort: 53 + name: dns-tcp + protocol: TCP + - containerPort: 9153 + name: metrics + protocol: TCP + livenessProbe: + httpGet: + path: /health + port: 8080 + scheme: HTTP + initialDelaySeconds: 60 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + dnsPolicy: Default + volumes: + - name: config-volume + configMap: + name: coredns + items: + - key: Corefile + path: Corefile +--- +apiVersion: v1 +kind: Service +metadata: + name: coredns + namespace: kube-system + labels: + k8s-app: coredns + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile + kubernetes.io/name: "CoreDNS" +spec: + selector: + k8s-app: coredns + clusterIP: {{ pillar['dns_server'] }} + ports: + - name: dns + port: 53 + protocol: UDP + - name: dns-tcp + port: 53 + protocol: TCP + - name: metrics + port: 9153 + protocol: TCP diff --git a/cluster/addons/dns/coredns.yaml.sed b/cluster/addons/dns/coredns.yaml.sed new file mode 100644 index 00000000000..4ec582f2edd --- /dev/null +++ b/cluster/addons/dns/coredns.yaml.sed @@ -0,0 +1,161 @@ +# Warning: This is a file generated from the base underscore template file: coredns.yaml.base + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: coredns + namespace: kube-system + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + kubernetes.io/bootstrapping: rbac-defaults + addonmanager.kubernetes.io/mode: Reconcile + name: system:coredns +rules: +- apiGroups: + - "" + resources: + - endpoints + - services + - pods + - namespaces + verbs: + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + labels: + kubernetes.io/bootstrapping: rbac-defaults + addonmanager.kubernetes.io/mode: EnsureExists + name: system:coredns +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:coredns +subjects: +- kind: ServiceAccount + name: coredns + namespace: kube-system +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: coredns + namespace: kube-system + labels: + addonmanager.kubernetes.io/mode: EnsureExists +data: + Corefile: | + .:53 { + errors + log + health + kubernetes $DNS_DOMAIN $SERVICE_CLUSTER_IP_RANGE { + pods insecure + } + prometheus + proxy . /etc/resolv.conf + cache 30 + } +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: coredns + namespace: kube-system + labels: + k8s-app: coredns + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile + kubernetes.io/name: "CoreDNS" +spec: + replicas: 1 + selector: + matchLabels: + k8s-app: coredns + template: + metadata: + labels: + k8s-app: coredns + spec: + serviceAccountName: coredns + tolerations: + - key: node-role.kubernetes.io/master + effect: NoSchedule + - key: "CriticalAddonsOnly" + operator: "Exists" + containers: + - name: coredns + image: coredns/coredns:1.0.1 + imagePullPolicy: IfNotPresent + resources: + limits: + memory: 170Mi + requests: + cpu: 100m + memory: 70Mi + args: [ "-conf", "/etc/coredns/Corefile" ] + volumeMounts: + - name: config-volume + mountPath: /etc/coredns + ports: + - containerPort: 53 + name: dns + protocol: UDP + - containerPort: 53 + name: dns-tcp + protocol: TCP + - containerPort: 9153 + name: metrics + protocol: TCP + livenessProbe: + httpGet: + path: /health + port: 8080 + scheme: HTTP + initialDelaySeconds: 60 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + dnsPolicy: Default + volumes: + - name: config-volume + configMap: + name: coredns + items: + - key: Corefile + path: Corefile +--- +apiVersion: v1 +kind: Service +metadata: + name: coredns + namespace: kube-system + labels: + k8s-app: coredns + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile + kubernetes.io/name: "CoreDNS" +spec: + selector: + k8s-app: coredns + clusterIP: $DNS_SERVER_IP + ports: + - name: dns + port: 53 + protocol: UDP + - name: dns-tcp + port: 53 + protocol: TCP + - name: metrics + port: 9153 + protocol: TCP diff --git a/cluster/addons/dns/kube-dns.yaml.base b/cluster/addons/dns/kube-dns.yaml.base index 0fbda4f447f..e93884df0ca 100644 --- a/cluster/addons/dns/kube-dns.yaml.base +++ b/cluster/addons/dns/kube-dns.yaml.base @@ -84,17 +84,6 @@ spec: annotations: scheduler.alpha.kubernetes.io/critical-pod: '' spec: - affinity: - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 100 - podAffinityTerm: - labelSelector: - matchExpressions: - - key: k8s-app - operator: In - values: ["kube-dns"] - topologyKey: kubernetes.io/hostname tolerations: - key: "CriticalAddonsOnly" operator: "Exists" @@ -105,7 +94,7 @@ spec: optional: true containers: - name: kubedns - image: gcr.io/google_containers/k8s-dns-kube-dns-amd64:1.14.5 + image: gcr.io/google_containers/k8s-dns-kube-dns-amd64:1.14.7 resources: # TODO: Set memory limits when we've profiled the container for large # clusters, then set request = limit to keep this container in @@ -156,7 +145,7 @@ spec: - name: kube-dns-config mountPath: /kube-dns-config - name: dnsmasq - image: gcr.io/google_containers/k8s-dns-dnsmasq-nanny-amd64:1.14.5 + image: gcr.io/google_containers/k8s-dns-dnsmasq-nanny-amd64:1.14.7 livenessProbe: httpGet: path: /healthcheck/dnsmasq @@ -195,7 +184,7 @@ spec: - name: kube-dns-config mountPath: /etc/k8s/dns/dnsmasq-nanny - name: sidecar - image: gcr.io/google_containers/k8s-dns-sidecar-amd64:1.14.5 + image: gcr.io/google_containers/k8s-dns-sidecar-amd64:1.14.7 livenessProbe: httpGet: path: /metrics @@ -208,8 +197,8 @@ spec: args: - --v=2 - --logtostderr - - --probe=kubedns,127.0.0.1:10053,kubernetes.default.svc.__PILLAR__DNS__DOMAIN__,5,A - - --probe=dnsmasq,127.0.0.1:53,kubernetes.default.svc.__PILLAR__DNS__DOMAIN__,5,A + - --probe=kubedns,127.0.0.1:10053,kubernetes.default.svc.__PILLAR__DNS__DOMAIN__,5,SRV + - --probe=dnsmasq,127.0.0.1:53,kubernetes.default.svc.__PILLAR__DNS__DOMAIN__,5,SRV ports: - containerPort: 10054 name: metrics diff --git a/cluster/addons/dns/kube-dns.yaml.in b/cluster/addons/dns/kube-dns.yaml.in index b8a431ab307..12b09236723 100644 --- a/cluster/addons/dns/kube-dns.yaml.in +++ b/cluster/addons/dns/kube-dns.yaml.in @@ -84,17 +84,6 @@ spec: annotations: scheduler.alpha.kubernetes.io/critical-pod: '' spec: - affinity: - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 100 - podAffinityTerm: - labelSelector: - matchExpressions: - - key: k8s-app - operator: In - values: ["kube-dns"] - topologyKey: kubernetes.io/hostname tolerations: - key: "CriticalAddonsOnly" operator: "Exists" @@ -105,7 +94,7 @@ spec: optional: true containers: - name: kubedns - image: gcr.io/google_containers/k8s-dns-kube-dns-amd64:1.14.5 + image: gcr.io/google_containers/k8s-dns-kube-dns-amd64:1.14.7 resources: # TODO: Set memory limits when we've profiled the container for large # clusters, then set request = limit to keep this container in @@ -156,7 +145,7 @@ spec: - name: kube-dns-config mountPath: /kube-dns-config - name: dnsmasq - image: gcr.io/google_containers/k8s-dns-dnsmasq-nanny-amd64:1.14.5 + image: gcr.io/google_containers/k8s-dns-dnsmasq-nanny-amd64:1.14.7 livenessProbe: httpGet: path: /healthcheck/dnsmasq @@ -195,7 +184,7 @@ spec: - name: kube-dns-config mountPath: /etc/k8s/dns/dnsmasq-nanny - name: sidecar - image: gcr.io/google_containers/k8s-dns-sidecar-amd64:1.14.5 + image: gcr.io/google_containers/k8s-dns-sidecar-amd64:1.14.7 livenessProbe: httpGet: path: /metrics @@ -208,8 +197,8 @@ spec: args: - --v=2 - --logtostderr - - --probe=kubedns,127.0.0.1:10053,kubernetes.default.svc.{{ pillar['dns_domain'] }},5,A - - --probe=dnsmasq,127.0.0.1:53,kubernetes.default.svc.{{ pillar['dns_domain'] }},5,A + - --probe=kubedns,127.0.0.1:10053,kubernetes.default.svc.{{ pillar['dns_domain'] }},5,SRV + - --probe=dnsmasq,127.0.0.1:53,kubernetes.default.svc.{{ pillar['dns_domain'] }},5,SRV ports: - containerPort: 10054 name: metrics diff --git a/cluster/addons/dns/kube-dns.yaml.sed b/cluster/addons/dns/kube-dns.yaml.sed index 92a29c94cb3..101cf588e2d 100644 --- a/cluster/addons/dns/kube-dns.yaml.sed +++ b/cluster/addons/dns/kube-dns.yaml.sed @@ -84,17 +84,6 @@ spec: annotations: scheduler.alpha.kubernetes.io/critical-pod: '' spec: - affinity: - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 100 - podAffinityTerm: - labelSelector: - matchExpressions: - - key: k8s-app - operator: In - values: ["kube-dns"] - topologyKey: kubernetes.io/hostname tolerations: - key: "CriticalAddonsOnly" operator: "Exists" @@ -105,7 +94,7 @@ spec: optional: true containers: - name: kubedns - image: gcr.io/google_containers/k8s-dns-kube-dns-amd64:1.14.5 + image: gcr.io/google_containers/k8s-dns-kube-dns-amd64:1.14.7 resources: # TODO: Set memory limits when we've profiled the container for large # clusters, then set request = limit to keep this container in @@ -156,7 +145,7 @@ spec: - name: kube-dns-config mountPath: /kube-dns-config - name: dnsmasq - image: gcr.io/google_containers/k8s-dns-dnsmasq-nanny-amd64:1.14.5 + image: gcr.io/google_containers/k8s-dns-dnsmasq-nanny-amd64:1.14.7 livenessProbe: httpGet: path: /healthcheck/dnsmasq @@ -195,7 +184,7 @@ spec: - name: kube-dns-config mountPath: /etc/k8s/dns/dnsmasq-nanny - name: sidecar - image: gcr.io/google_containers/k8s-dns-sidecar-amd64:1.14.5 + image: gcr.io/google_containers/k8s-dns-sidecar-amd64:1.14.7 livenessProbe: httpGet: path: /metrics @@ -208,8 +197,8 @@ spec: args: - --v=2 - --logtostderr - - --probe=kubedns,127.0.0.1:10053,kubernetes.default.svc.$DNS_DOMAIN,5,A - - --probe=dnsmasq,127.0.0.1:53,kubernetes.default.svc.$DNS_DOMAIN,5,A + - --probe=kubedns,127.0.0.1:10053,kubernetes.default.svc.$DNS_DOMAIN,5,SRV + - --probe=dnsmasq,127.0.0.1:53,kubernetes.default.svc.$DNS_DOMAIN,5,SRV ports: - containerPort: 10054 name: metrics diff --git a/cluster/addons/dns/transforms2salt.sed b/cluster/addons/dns/transforms2salt.sed index fea818b771d..0a0778b9292 100644 --- a/cluster/addons/dns/transforms2salt.sed +++ b/cluster/addons/dns/transforms2salt.sed @@ -1,3 +1,4 @@ s/__PILLAR__DNS__SERVER__/{{ pillar['dns_server'] }}/g s/__PILLAR__DNS__DOMAIN__/{{ pillar['dns_domain'] }}/g +s/__PILLAR__CLUSTER_CIDR__/{{ pillar['service_cluster_ip_range'] }}/g s/__MACHINE_GENERATED_WARNING__/Warning: This is a file generated from the base underscore template file: __SOURCE_FILENAME__/g diff --git a/cluster/addons/dns/transforms2sed.sed b/cluster/addons/dns/transforms2sed.sed index d5415654f36..7d64f8e0b51 100644 --- a/cluster/addons/dns/transforms2sed.sed +++ b/cluster/addons/dns/transforms2sed.sed @@ -1,3 +1,4 @@ s/__PILLAR__DNS__SERVER__/$DNS_SERVER_IP/g s/__PILLAR__DNS__DOMAIN__/$DNS_DOMAIN/g +s/__PILLAR__CLUSTER_CIDR__/$SERVICE_CLUSTER_IP_RANGE/g s/__MACHINE_GENERATED_WARNING__/Warning: This is a file generated from the base underscore template file: __SOURCE_FILENAME__/g diff --git a/cluster/addons/etcd-empty-dir-cleanup/etcd-empty-dir-cleanup.yaml b/cluster/addons/etcd-empty-dir-cleanup/etcd-empty-dir-cleanup.yaml index cf7e5b5f515..05943c8c416 100644 --- a/cluster/addons/etcd-empty-dir-cleanup/etcd-empty-dir-cleanup.yaml +++ b/cluster/addons/etcd-empty-dir-cleanup/etcd-empty-dir-cleanup.yaml @@ -1,4 +1,14 @@ apiVersion: v1 +kind: ServiceAccount +metadata: + name: etcd-empty-dir-cleanup + namespace: kube-system + labels: + k8s-app: etcd-empty-dir-cleanup + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile +--- +apiVersion: v1 kind: Pod metadata: name: etcd-empty-dir-cleanup @@ -8,6 +18,7 @@ metadata: labels: k8s-app: etcd-empty-dir-cleanup spec: + serviceAccountName: etcd-empty-dir-cleanup hostNetwork: true dnsPolicy: Default containers: diff --git a/cluster/addons/etcd-empty-dir-cleanup/podsecuritypolicies/etcd-empty-dir-cleanup-psp-binding.yaml b/cluster/addons/etcd-empty-dir-cleanup/podsecuritypolicies/etcd-empty-dir-cleanup-psp-binding.yaml new file mode 100644 index 00000000000..77003f69c5f --- /dev/null +++ b/cluster/addons/etcd-empty-dir-cleanup/podsecuritypolicies/etcd-empty-dir-cleanup-psp-binding.yaml @@ -0,0 +1,16 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: gce:podsecuritypolicy:etcd-empty-dir-cleanup + namespace: kube-system + labels: + addonmanager.kubernetes.io/mode: Reconcile + kubernetes.io/cluster-service: "true" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: gce:podsecuritypolicy:etcd-empty-dir-cleanup +subjects: +- kind: ServiceAccount + name: etcd-empty-dir-cleanup + namespace: kube-system diff --git a/cluster/addons/etcd-empty-dir-cleanup/podsecuritypolicies/etcd-empty-dir-cleanup-psp-role.yaml b/cluster/addons/etcd-empty-dir-cleanup/podsecuritypolicies/etcd-empty-dir-cleanup-psp-role.yaml new file mode 100644 index 00000000000..6b577479d59 --- /dev/null +++ b/cluster/addons/etcd-empty-dir-cleanup/podsecuritypolicies/etcd-empty-dir-cleanup-psp-role.yaml @@ -0,0 +1,17 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: gce:podsecuritypolicy:etcd-empty-dir-cleanup + namespace: kube-system + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile +rules: +- apiGroups: + - extensions + resourceNames: + - gce.etcd-empty-dir-cleanup + resources: + - podsecuritypolicies + verbs: + - use diff --git a/cluster/addons/etcd-empty-dir-cleanup/podsecuritypolicies/etcd-empty-dir-cleanup-psp.yaml b/cluster/addons/etcd-empty-dir-cleanup/podsecuritypolicies/etcd-empty-dir-cleanup-psp.yaml new file mode 100644 index 00000000000..d51c4781148 --- /dev/null +++ b/cluster/addons/etcd-empty-dir-cleanup/podsecuritypolicies/etcd-empty-dir-cleanup-psp.yaml @@ -0,0 +1,31 @@ +apiVersion: extensions/v1beta1 +kind: PodSecurityPolicy +metadata: + name: gce.etcd-empty-dir-cleanup + annotations: + kubernetes.io/description: 'Policy used by the etcd-empty-dir-cleanup addon.' + # TODO: etcd-empty-dir-cleanup should run with the default seccomp profile + seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*' + # 'runtime/default' is already the default, but must be filled in on the + # pod to pass admission. + apparmor.security.beta.kubernetes.io/defaultProfileName: 'runtime/default' + apparmor.security.beta.kubernetes.io/allowedProfileNames: 'runtime/default' + labels: + kubernetes.io/cluster-service: 'true' + addonmanager.kubernetes.io/mode: Reconcile +spec: + privileged: false + volumes: + - 'secret' + hostNetwork: true + hostIPC: false + hostPID: false + runAsUser: + rule: 'RunAsAny' + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'RunAsAny' + fsGroup: + rule: 'RunAsAny' + readOnlyRootFilesystem: false diff --git a/cluster/addons/fluentd-elasticsearch/README.md b/cluster/addons/fluentd-elasticsearch/README.md index 59cc5ddfe99..d51b3b142d2 100644 --- a/cluster/addons/fluentd-elasticsearch/README.md +++ b/cluster/addons/fluentd-elasticsearch/README.md @@ -8,7 +8,7 @@ is a graphical interface for viewing and querying the logs stored in Elasticsearch. **Note:** this addon should **not** be used as-is in production. This is -an example and you should treat is as such. Please see at least the +an example and you should treat it as such. Please see at least the [Security](#security) and the [Storage](#storage) sections for more information. @@ -19,9 +19,9 @@ a Deployment, but allows for maintaining state on storage volumes. ### Security -Elasticsearch has capabilities to enable authorization using +Elasticsearch has capabilities to enable authorization using the [X-Pack plugin][xPack]. See configuration parameter `xpack.security.enabled` -in Elasticsearch and Kibana configurations. It can also be set via +in Elasticsearch and Kibana configurations. It can also be set via the `XPACK_SECURITY_ENABLED` env variable. After enabling the feature, follow [official documentation][setupCreds] to set up credentials in Elasticsearch and Kibana. Don't forget to propagate those credentials also to @@ -31,7 +31,7 @@ and [Secrets][secret] to store credentials in the Kubernetes apiserver. ### Initialization -The Elasticsearch Statefulset manifest specifies that there shall be an +The Elasticsearch StatefulSet manifest specifies that there shall be an [init container][initContainer] executing before Elasticsearch containers themselves, in order to ensure that the kernel state variable `vm.max_map_count` is at least 262144, since this is a requirement of @@ -61,7 +61,7 @@ Learn more in the [official Kubernetes documentation][k8sElasticsearchDocs]. Since Fluentd talks to the Elasticsearch service inside the cluster, instances on masters won't work, because masters have no kube-proxy. Don't mark masters -with a label mentioned in the previous paragraph or add a taint on them to +with the label mentioned in the previous paragraph or add a taint on them to avoid Fluentd pods scheduling there. [fluentd]: http://www.fluentd.org/ @@ -71,7 +71,7 @@ avoid Fluentd pods scheduling there. [setupCreds]: https://www.elastic.co/guide/en/x-pack/current/setting-up-authentication.html#reset-built-in-user-passwords [fluentdCreds]: https://github.com/uken/fluent-plugin-elasticsearch#user-password-path-scheme-ssl_verify [fluentdEnvVar]: https://docs.fluentd.org/v0.12/articles/faq#how-can-i-use-environment-variables-to-configure-parameters-dynamically -[configMap]: https://kubernetes.io/docs/tasks/configure-pod-container/configmap/ +[configMap]: https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/ [secret]: https://kubernetes.io/docs/concepts/configuration/secret/ [statefulSet]: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset [initContainer]: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ diff --git a/cluster/addons/fluentd-elasticsearch/es-image/BUILD b/cluster/addons/fluentd-elasticsearch/es-image/BUILD index 99680582cff..6ac051f91cf 100644 --- a/cluster/addons/fluentd-elasticsearch/es-image/BUILD +++ b/cluster/addons/fluentd-elasticsearch/es-image/BUILD @@ -8,8 +8,8 @@ load( go_binary( name = "es-image", + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cluster/addons/fluentd-elasticsearch/es-image", - library = ":go_default_library", ) go_library( @@ -17,7 +17,7 @@ go_library( srcs = ["elasticsearch_logging_discovery.go"], importpath = "k8s.io/kubernetes/cluster/addons/fluentd-elasticsearch/es-image", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/cluster/addons/fluentd-elasticsearch/es-image/Dockerfile b/cluster/addons/fluentd-elasticsearch/es-image/Dockerfile index 2e9d5b3f07e..8e971d78349 100644 --- a/cluster/addons/fluentd-elasticsearch/es-image/Dockerfile +++ b/cluster/addons/fluentd-elasticsearch/es-image/Dockerfile @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM docker.elastic.co/elasticsearch/elasticsearch:5.6.2 +FROM docker.elastic.co/elasticsearch/elasticsearch:5.6.4 VOLUME ["/data"] EXPOSE 9200 9300 diff --git a/cluster/addons/fluentd-elasticsearch/es-image/Makefile b/cluster/addons/fluentd-elasticsearch/es-image/Makefile index c5a4ba69dbd..51222ad3800 100755 --- a/cluster/addons/fluentd-elasticsearch/es-image/Makefile +++ b/cluster/addons/fluentd-elasticsearch/es-image/Makefile @@ -16,7 +16,7 @@ PREFIX = gcr.io/google-containers IMAGE = elasticsearch -TAG = v5.6.2 +TAG = v5.6.4 build: docker build --pull -t $(PREFIX)/$(IMAGE):$(TAG) . diff --git a/cluster/addons/fluentd-elasticsearch/es-image/elasticsearch_logging_discovery.go b/cluster/addons/fluentd-elasticsearch/es-image/elasticsearch_logging_discovery.go index a896faf80bd..ed3a623a6e4 100644 --- a/cluster/addons/fluentd-elasticsearch/es-image/elasticsearch_logging_discovery.go +++ b/cluster/addons/fluentd-elasticsearch/es-image/elasticsearch_logging_discovery.go @@ -28,7 +28,7 @@ import ( restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" clientapi "k8s.io/client-go/tools/clientcmd/api" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" ) @@ -81,10 +81,15 @@ func main() { } var elasticsearch *api.Service + serviceName := os.Getenv("ELASTICSEARCH_SERVICE_NAME") + if serviceName == "" { + serviceName = "elasticsearch-logging" + } + // Look for endpoints associated with the Elasticsearch loggging service. // First wait for the service to become available. for t := time.Now(); time.Since(t) < 5*time.Minute; time.Sleep(10 * time.Second) { - elasticsearch, err = client.Core().Services(namespace).Get("elasticsearch-logging", metav1.GetOptions{}) + elasticsearch, err = client.Core().Services(namespace).Get(serviceName, metav1.GetOptions{}) if err == nil { break } @@ -101,7 +106,7 @@ func main() { // Wait for some endpoints. count := 0 for t := time.Now(); time.Since(t) < 5*time.Minute; time.Sleep(10 * time.Second) { - endpoints, err = client.Core().Endpoints(namespace).Get("elasticsearch-logging", metav1.GetOptions{}) + endpoints, err = client.Core().Endpoints(namespace).Get(serviceName, metav1.GetOptions{}) if err != nil { continue } diff --git a/cluster/addons/fluentd-elasticsearch/es-statefulset.yaml b/cluster/addons/fluentd-elasticsearch/es-statefulset.yaml index fb00c9d0fc7..b6357f47ef3 100644 --- a/cluster/addons/fluentd-elasticsearch/es-statefulset.yaml +++ b/cluster/addons/fluentd-elasticsearch/es-statefulset.yaml @@ -54,7 +54,7 @@ metadata: namespace: kube-system labels: k8s-app: elasticsearch-logging - version: v5.6.2 + version: v5.6.4 kubernetes.io/cluster-service: "true" addonmanager.kubernetes.io/mode: Reconcile spec: @@ -63,17 +63,17 @@ spec: selector: matchLabels: k8s-app: elasticsearch-logging - version: v5.6.2 + version: v5.6.4 template: metadata: labels: k8s-app: elasticsearch-logging - version: v5.6.2 + version: v5.6.4 kubernetes.io/cluster-service: "true" spec: serviceAccountName: elasticsearch-logging containers: - - image: gcr.io/google-containers/elasticsearch:v5.6.2 + - image: gcr.io/google-containers/elasticsearch:v5.6.4 name: elasticsearch-logging resources: # need more cpu upon initialization, therefore burstable class diff --git a/cluster/addons/fluentd-elasticsearch/fluentd-es-configmap.yaml b/cluster/addons/fluentd-elasticsearch/fluentd-es-configmap.yaml index 3fe62d8ae59..09fbad0ebf4 100644 --- a/cluster/addons/fluentd-elasticsearch/fluentd-es-configmap.yaml +++ b/cluster/addons/fluentd-elasticsearch/fluentd-es-configmap.yaml @@ -96,16 +96,27 @@ data: # the name of the Kubernetes container regardless of how many times the # Kubernetes pod has been restarted (resulting in a several Docker container IDs). - # Example: + # Json Log Example: # {"log":"[info:2016-02-16T16:04:05.930-08:00] Some log text here\n","stream":"stdout","time":"2016-02-17T00:04:05.931087621Z"} + # CRI Log Example: + # 2016-02-17T00:04:05.931087621Z stdout F [info:2016-02-16T16:04:05.930-08:00] Some log text here type tail path /var/log/containers/*.log pos_file /var/log/es-containers.log.pos time_format %Y-%m-%dT%H:%M:%S.%NZ tag kubernetes.* - format json read_from_head true + format multi_format + + format json + time_key time + time_format %Y-%m-%dT%H:%M:%S.%NZ + + + format /^(? system.input.conf: |- # Example: @@ -356,7 +367,7 @@ data: num_threads 2 metadata: - name: fluentd-es-config-v0.1.0 + name: fluentd-es-config-v0.1.1 namespace: kube-system labels: addonmanager.kubernetes.io/mode: Reconcile diff --git a/cluster/addons/fluentd-elasticsearch/fluentd-es-ds.yaml b/cluster/addons/fluentd-elasticsearch/fluentd-es-ds.yaml index cb14b33e32c..74242adce74 100644 --- a/cluster/addons/fluentd-elasticsearch/fluentd-es-ds.yaml +++ b/cluster/addons/fluentd-elasticsearch/fluentd-es-ds.yaml @@ -48,20 +48,24 @@ roleRef: apiVersion: apps/v1beta2 kind: DaemonSet metadata: - name: fluentd-es-v2.0.1 + name: fluentd-es-v2.0.2 namespace: kube-system labels: k8s-app: fluentd-es - version: v2.0.1 + version: v2.0.2 kubernetes.io/cluster-service: "true" addonmanager.kubernetes.io/mode: Reconcile spec: + selector: + matchLabels: + k8s-app: fluentd-es + version: v2.0.2 template: metadata: labels: k8s-app: fluentd-es kubernetes.io/cluster-service: "true" - version: v2.0.1 + version: v2.0.2 # This annotation ensures that fluentd does not get evicted if the node # supports critical pod annotation based priority scheme. # Note that this does not guarantee admission on the nodes (#40573). @@ -71,7 +75,7 @@ spec: serviceAccountName: fluentd-es containers: - name: fluentd-es - image: gcr.io/google-containers/fluentd-elasticsearch:v2.0.1 + image: gcr.io/google-containers/fluentd-elasticsearch:v2.0.2 env: - name: FLUENTD_ARGS value: --no-supervisor -q @@ -108,4 +112,4 @@ spec: path: /usr/lib64 - name: config-volume configMap: - name: fluentd-es-config-v0.1.0 + name: fluentd-es-config-v0.1.1 diff --git a/cluster/addons/fluentd-elasticsearch/fluentd-es-image/Gemfile b/cluster/addons/fluentd-elasticsearch/fluentd-es-image/Gemfile index 5ef6d20e2c3..c936b40f3c4 100644 --- a/cluster/addons/fluentd-elasticsearch/fluentd-es-image/Gemfile +++ b/cluster/addons/fluentd-elasticsearch/fluentd-es-image/Gemfile @@ -6,4 +6,5 @@ gem 'fluent-plugin-kubernetes_metadata_filter', '~>0.27.0' gem 'fluent-plugin-elasticsearch', '~>1.9.5' gem 'fluent-plugin-systemd', '~>0.0.8' gem 'fluent-plugin-prometheus', '~>0.3.0' +gem 'fluent-plugin-multi-format-parser', '~>0.1.1' gem 'oj', '~>2.18.1' diff --git a/cluster/addons/fluentd-elasticsearch/fluentd-es-image/Makefile b/cluster/addons/fluentd-elasticsearch/fluentd-es-image/Makefile index 23f35c886ca..0b5fa8a487c 100644 --- a/cluster/addons/fluentd-elasticsearch/fluentd-es-image/Makefile +++ b/cluster/addons/fluentd-elasticsearch/fluentd-es-image/Makefile @@ -16,7 +16,7 @@ PREFIX = gcr.io/google-containers IMAGE = fluentd-elasticsearch -TAG = v2.0.1 +TAG = v2.0.2 build: docker build --pull -t $(PREFIX)/$(IMAGE):$(TAG) . diff --git a/cluster/addons/fluentd-elasticsearch/kibana-deployment.yaml b/cluster/addons/fluentd-elasticsearch/kibana-deployment.yaml index 525fd9f4e5f..5034fef4273 100644 --- a/cluster/addons/fluentd-elasticsearch/kibana-deployment.yaml +++ b/cluster/addons/fluentd-elasticsearch/kibana-deployment.yaml @@ -19,7 +19,7 @@ spec: spec: containers: - name: kibana-logging - image: docker.elastic.co/kibana/kibana:5.6.2 + image: docker.elastic.co/kibana/kibana:5.6.4 resources: # need more cpu upon initialization, therefore burstable class limits: diff --git a/cluster/addons/fluentd-elasticsearch/podsecuritypolicies/es-psp-binding.yaml b/cluster/addons/fluentd-elasticsearch/podsecuritypolicies/es-psp-binding.yaml new file mode 100644 index 00000000000..fbe06861ce7 --- /dev/null +++ b/cluster/addons/fluentd-elasticsearch/podsecuritypolicies/es-psp-binding.yaml @@ -0,0 +1,16 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: gce:podsecuritypolicy:elasticsearch-logging + namespace: kube-system + labels: + addonmanager.kubernetes.io/mode: Reconcile + kubernetes.io/cluster-service: "true" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: gce:podsecuritypolicy:privileged +subjects: +- kind: ServiceAccount + name: elasticsearch-logging + namespace: kube-system diff --git a/cluster/addons/fluentd-gcp/event-exporter.yaml b/cluster/addons/fluentd-gcp/event-exporter.yaml index 3c661bbb560..246fa8c42bc 100644 --- a/cluster/addons/fluentd-gcp/event-exporter.yaml +++ b/cluster/addons/fluentd-gcp/event-exporter.yaml @@ -52,16 +52,23 @@ spec: - '/event-exporter' # BEGIN_PROMETHEUS_TO_SD - name: prometheus-to-sd-exporter - image: gcr.io/google-containers/prometheus-to-sd:v0.2.1 + image: gcr.io/google-containers/prometheus-to-sd:v0.2.2 command: - /monitor - - --component=event_exporter - --stackdriver-prefix={{ prometheus_to_sd_prefix }}/addons - --api-override={{ prometheus_to_sd_endpoint }} - - --whitelisted-metrics=stackdriver_sink_received_entry_count,stackdriver_sink_request_count,stackdriver_sink_successfully_sent_entry_count - volumeMounts: - - name: ssl-certs - mountPath: /etc/ssl/certs + - --source=event_exporter:http://localhost:80?whitelisted=stackdriver_sink_received_entry_count,stackdriver_sink_request_count,stackdriver_sink_successfully_sent_entry_count + - --pod-id=$(POD_NAME) + - --namespace-id=$(POD_NAMESPACE) + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace # END_PROMETHEUS_TO_SD terminationGracePeriodSeconds: 30 volumes: diff --git a/cluster/addons/fluentd-gcp/fluentd-gcp-configmap.yaml b/cluster/addons/fluentd-gcp/fluentd-gcp-configmap.yaml index a1049e22eb3..e248aa4e3a8 100644 --- a/cluster/addons/fluentd-gcp/fluentd-gcp-configmap.yaml +++ b/cluster/addons/fluentd-gcp/fluentd-gcp-configmap.yaml @@ -41,17 +41,26 @@ data: # Tag is then parsed by google_cloud plugin and translated to the metadata, # visible in the log viewer - # Example: + # Json Log Example: # {"log":"[info:2016-02-16T16:04:05.930-08:00] Some log text here\n","stream":"stdout","time":"2016-02-17T00:04:05.931087621Z"} + # CRI Log Example: + # 2016-02-17T00:04:05.931087621Z stdout F [info:2016-02-16T16:04:05.930-08:00] Some log text here type tail - format json - time_key time path /var/log/containers/*.log pos_file /var/log/gcp-containers.log.pos - time_format %Y-%m-%dT%H:%M:%S.%N%Z tag reform.* read_from_head true + format multi_format + + format json + time_key time + time_format %Y-%m-%dT%H:%M:%S.%NZ + + + format /^(? @@ -389,7 +398,7 @@ data: num_threads 2 metadata: - name: fluentd-gcp-config-v1.2.2 + name: fluentd-gcp-config-v1.2.3 namespace: kube-system labels: addonmanager.kubernetes.io/mode: Reconcile diff --git a/cluster/addons/fluentd-gcp/fluentd-gcp-ds-sa.yaml b/cluster/addons/fluentd-gcp/fluentd-gcp-ds-sa.yaml new file mode 100644 index 00000000000..45cdeb7de6f --- /dev/null +++ b/cluster/addons/fluentd-gcp/fluentd-gcp-ds-sa.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: fluentd-gcp + namespace: kube-system + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile diff --git a/cluster/addons/fluentd-gcp/fluentd-gcp-ds.yaml b/cluster/addons/fluentd-gcp/fluentd-gcp-ds.yaml index c9cc6bd558b..dd516db77e0 100644 --- a/cluster/addons/fluentd-gcp/fluentd-gcp-ds.yaml +++ b/cluster/addons/fluentd-gcp/fluentd-gcp-ds.yaml @@ -1,13 +1,13 @@ apiVersion: extensions/v1beta1 kind: DaemonSet metadata: - name: fluentd-gcp-v2.0.9 + name: fluentd-gcp-v2.0.11 namespace: kube-system labels: k8s-app: fluentd-gcp kubernetes.io/cluster-service: "true" addonmanager.kubernetes.io/mode: Reconcile - version: v2.0.9 + version: v2.0.11 spec: updateStrategy: type: RollingUpdate @@ -16,18 +16,18 @@ spec: labels: k8s-app: fluentd-gcp kubernetes.io/cluster-service: "true" - version: v2.0.9 + version: v2.0.11 # This annotation ensures that fluentd does not get evicted if the node # supports critical pod annotation based priority scheme. # Note that this does not guarantee admission on the nodes (#40573). annotations: scheduler.alpha.kubernetes.io/critical-pod: '' spec: + serviceAccountName: fluentd-gcp dnsPolicy: Default - hostNetwork: true containers: - name: fluentd-gcp - image: gcr.io/google-containers/fluentd-gcp:2.0.9 + image: gcr.io/google-containers/fluentd-gcp:2.0.11 env: - name: FLUENTD_ARGS value: --no-supervisor -q @@ -82,17 +82,23 @@ spec: fi; # BEGIN_PROMETHEUS_TO_SD - name: prometheus-to-sd-exporter - image: gcr.io/google-containers/prometheus-to-sd:v0.1.3 + image: gcr.io/google-containers/prometheus-to-sd:v0.2.2 command: - /monitor - - --component=fluentd - - --target-port=31337 - --stackdriver-prefix={{ prometheus_to_sd_prefix }}/addons - --api-override={{ prometheus_to_sd_endpoint }} - - --whitelisted-metrics=stackdriver_successful_requests_count,stackdriver_failed_requests_count,stackdriver_ingested_entries_count,stackdriver_dropped_entries_count - volumeMounts: - - name: ssl-certs - mountPath: /etc/ssl/certs + - --source=fluentd:http://localhost:31337?whitelisted=stackdriver_successful_requests_count,stackdriver_failed_requests_count,stackdriver_ingested_entries_count,stackdriver_dropped_entries_count + - --pod-id=$(POD_NAME) + - --namespace-id=$(POD_NAMESPACE) + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace # END_PROMETHEUS_TO_SD nodeSelector: beta.kubernetes.io/fluentd-ds-ready: "true" @@ -101,7 +107,6 @@ spec: effect: "NoSchedule" - operator: "Exists" effect: "NoExecute" - #TODO: remove this toleration once #44445 is properly fixed. - operator: "Exists" effect: "NoSchedule" terminationGracePeriodSeconds: 30 @@ -117,7 +122,4 @@ spec: path: /usr/lib64 - name: config-volume configMap: - name: fluentd-gcp-config-v1.2.2 - - name: ssl-certs - hostPath: - path: /etc/ssl/certs + name: fluentd-gcp-config-v1.2.3 diff --git a/cluster/addons/fluentd-gcp/podsecuritypolicies/event-exporter-psp-binding.yaml b/cluster/addons/fluentd-gcp/podsecuritypolicies/event-exporter-psp-binding.yaml new file mode 100644 index 00000000000..5ef3b67340c --- /dev/null +++ b/cluster/addons/fluentd-gcp/podsecuritypolicies/event-exporter-psp-binding.yaml @@ -0,0 +1,16 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: gce:podsecuritypolicy:event-exporter + namespace: kube-system + labels: + addonmanager.kubernetes.io/mode: Reconcile + kubernetes.io/cluster-service: "true" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: gce:podsecuritypolicy:event-exporter +subjects: +- kind: ServiceAccount + name: event-exporter-sa + namespace: kube-system diff --git a/cluster/addons/fluentd-gcp/podsecuritypolicies/event-exporter-psp-role.yaml b/cluster/addons/fluentd-gcp/podsecuritypolicies/event-exporter-psp-role.yaml new file mode 100644 index 00000000000..f360a87f4c8 --- /dev/null +++ b/cluster/addons/fluentd-gcp/podsecuritypolicies/event-exporter-psp-role.yaml @@ -0,0 +1,17 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: gce:podsecuritypolicy:event-exporter + namespace: kube-system + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile +rules: +- apiGroups: + - extensions + resourceNames: + - gce.event-exporter + resources: + - podsecuritypolicies + verbs: + - use diff --git a/cluster/addons/fluentd-gcp/podsecuritypolicies/event-exporter-psp.yaml b/cluster/addons/fluentd-gcp/podsecuritypolicies/event-exporter-psp.yaml new file mode 100644 index 00000000000..48e57f7f0a6 --- /dev/null +++ b/cluster/addons/fluentd-gcp/podsecuritypolicies/event-exporter-psp.yaml @@ -0,0 +1,38 @@ +apiVersion: extensions/v1beta1 +kind: PodSecurityPolicy +metadata: + name: gce.event-exporter + annotations: + kubernetes.io/description: 'Policy used by the event-exporter addon.' + # TODO: event-exporter should run with the default seccomp profile + seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*' + # 'runtime/default' is already the default, but must be filled in on the + # pod to pass admission. + apparmor.security.beta.kubernetes.io/defaultProfileName: 'runtime/default' + apparmor.security.beta.kubernetes.io/allowedProfileNames: 'runtime/default' + labels: + kubernetes.io/cluster-service: 'true' + addonmanager.kubernetes.io/mode: Reconcile +spec: + privileged: false + allowPrivilegeEscalation: false + volumes: + - 'hostPath' + - 'secret' + # TODO: This only needs a hostPath to read /etc/ssl/certs, + # but it should be able to just include these in the image. + allowedHostPaths: + - pathPrefix: /etc/ssl/certs + hostNetwork: false + hostIPC: false + hostPID: false + # TODO: This doesn't need to run as root. + runAsUser: + rule: 'RunAsAny' + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'RunAsAny' + fsGroup: + rule: 'RunAsAny' + readOnlyRootFilesystem: false diff --git a/cluster/addons/fluentd-gcp/podsecuritypolicies/fluentd-gcp-psp-binding.yaml b/cluster/addons/fluentd-gcp/podsecuritypolicies/fluentd-gcp-psp-binding.yaml new file mode 100644 index 00000000000..b34f524c98d --- /dev/null +++ b/cluster/addons/fluentd-gcp/podsecuritypolicies/fluentd-gcp-psp-binding.yaml @@ -0,0 +1,16 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: gce:podsecuritypolicy:fluentd-gcp + namespace: kube-system + labels: + addonmanager.kubernetes.io/mode: Reconcile + kubernetes.io/cluster-service: "true" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: gce:podsecuritypolicy:fluentd-gcp +subjects: +- kind: ServiceAccount + name: fluentd-gcp + namespace: kube-system diff --git a/cluster/addons/fluentd-gcp/podsecuritypolicies/fluentd-gcp-psp-role.yaml b/cluster/addons/fluentd-gcp/podsecuritypolicies/fluentd-gcp-psp-role.yaml new file mode 100644 index 00000000000..478257bf2a4 --- /dev/null +++ b/cluster/addons/fluentd-gcp/podsecuritypolicies/fluentd-gcp-psp-role.yaml @@ -0,0 +1,17 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: gce:podsecuritypolicy:fluentd-gcp + namespace: kube-system + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile +rules: +- apiGroups: + - extensions + resourceNames: + - gce.fluentd-gcp + resources: + - podsecuritypolicies + verbs: + - use diff --git a/cluster/addons/fluentd-gcp/podsecuritypolicies/fluentd-gcp-psp.yaml b/cluster/addons/fluentd-gcp/podsecuritypolicies/fluentd-gcp-psp.yaml new file mode 100644 index 00000000000..36b3168f83d --- /dev/null +++ b/cluster/addons/fluentd-gcp/podsecuritypolicies/fluentd-gcp-psp.yaml @@ -0,0 +1,38 @@ +apiVersion: extensions/v1beta1 +kind: PodSecurityPolicy +metadata: + name: gce.fluentd-gcp + annotations: + kubernetes.io/description: 'Policy used by the fluentd-gcp addon.' + # TODO: fluentd-gcp should run with the default seccomp profile + seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*' + # 'runtime/default' is already the default, but must be filled in on the + # pod to pass admission. + apparmor.security.beta.kubernetes.io/defaultProfileName: 'runtime/default' + apparmor.security.beta.kubernetes.io/allowedProfileNames: 'runtime/default' + labels: + kubernetes.io/cluster-service: 'true' + addonmanager.kubernetes.io/mode: Reconcile +spec: + privileged: false + allowPrivilegeEscalation: false + volumes: + - 'configMap' + - 'hostPath' + - 'secret' + allowedHostPaths: + - pathPrefix: /var/log + - pathPrefix: /var/lib/docker/containers + - pathPrefix: /usr/lib64 + hostNetwork: true + hostIPC: false + hostPID: false + runAsUser: + rule: 'RunAsAny' + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'RunAsAny' + fsGroup: + rule: 'RunAsAny' + readOnlyRootFilesystem: false diff --git a/cluster/addons/ip-masq-agent/ip-masq-agent.yaml b/cluster/addons/ip-masq-agent/ip-masq-agent.yaml index 02152357fb4..f6bb21c01b9 100644 --- a/cluster/addons/ip-masq-agent/ip-masq-agent.yaml +++ b/cluster/addons/ip-masq-agent/ip-masq-agent.yaml @@ -1,3 +1,13 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: ip-masq-agent + namespace: kube-system + labels: + k8s-app: ip-masq-agent + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile +--- # https://github.com/kubernetes-incubator/ip-masq-agent/blob/v2.0.0/README.md apiVersion: extensions/v1beta1 kind: DaemonSet @@ -14,6 +24,7 @@ spec: annotations: scheduler.alpha.kubernetes.io/critical-pod: '' spec: + serviceAccountName: ip-masq-agent hostNetwork: true containers: - name: ip-masq-agent diff --git a/cluster/addons/ip-masq-agent/podsecuritypolicies/ip-masq-agent-psp-binding.yaml b/cluster/addons/ip-masq-agent/podsecuritypolicies/ip-masq-agent-psp-binding.yaml new file mode 100644 index 00000000000..95f056ef755 --- /dev/null +++ b/cluster/addons/ip-masq-agent/podsecuritypolicies/ip-masq-agent-psp-binding.yaml @@ -0,0 +1,16 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: gce:podsecuritypolicy:ip-masq-agent + namespace: kube-system + labels: + addonmanager.kubernetes.io/mode: Reconcile + kubernetes.io/cluster-service: "true" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: gce:podsecuritypolicy:privileged +subjects: +- kind: ServiceAccount + name: ip-masq-agent + namespace: kube-system diff --git a/cluster/addons/kube-proxy/kube-proxy-ds.yaml b/cluster/addons/kube-proxy/kube-proxy-ds.yaml index a2b41e7db34..2134e875fba 100644 --- a/cluster/addons/kube-proxy/kube-proxy-ds.yaml +++ b/cluster/addons/kube-proxy/kube-proxy-ds.yaml @@ -28,6 +28,11 @@ spec: hostNetwork: true nodeSelector: beta.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['kube-proxy_docker_tag']}} @@ -52,6 +57,9 @@ spec: - mountPath: /run/xtables.lock name: xtables-lock readOnly: false + - mountPath: /lib/modules + name: lib-modules + readOnly: true volumes: - name: varlog hostPath: @@ -60,4 +68,7 @@ spec: hostPath: path: /run/xtables.lock type: FileOrCreate + - name: lib-modules + hostPath: + path: /lib/modules serviceAccountName: kube-proxy diff --git a/cluster/addons/metadata-agent/OWNERS b/cluster/addons/metadata-agent/OWNERS new file mode 100644 index 00000000000..6fda00e6c98 --- /dev/null +++ b/cluster/addons/metadata-agent/OWNERS @@ -0,0 +1,6 @@ +approvers: +- kawych +- piosz +reviewers: +- kawych +- piosz diff --git a/cluster/addons/metadata-agent/README.md b/cluster/addons/metadata-agent/README.md new file mode 100644 index 00000000000..2e4dbc98490 --- /dev/null +++ b/cluster/addons/metadata-agent/README.md @@ -0,0 +1,4 @@ +# Kubernetes Metadata Agent + +Metadata Agent is a source of metadata required by logging and monitoring agents +running on a cluster. diff --git a/cluster/addons/metadata-agent/stackdriver/metadata-agent.yaml b/cluster/addons/metadata-agent/stackdriver/metadata-agent.yaml new file mode 100644 index 00000000000..a7f2b1c55bd --- /dev/null +++ b/cluster/addons/metadata-agent/stackdriver/metadata-agent.yaml @@ -0,0 +1,38 @@ +kind: DaemonSet +apiVersion: extensions/v1beta1 +metadata: + labels: + app: stackdriver-agents + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile + name: stackdriver-agents + namespace: kube-system +spec: + selector: + matchLabels: + app: stackdriver-agents + template: + metadata: + labels: + app: stackdriver-agents + spec: + containers: + - image: us.gcr.io/container-monitoring-storage/stackdriver-metadata-agent:{{ metadata_agent_version }} + imagePullPolicy: IfNotPresent + name: metadata-agent + ports: + - containerPort: 8000 + hostPort: 8799 + protocol: TCP + resources: + requests: + cpu: {{ metadata_agent_cpu_request }} + memory: {{ metadata_agent_memory_request }} + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + terminationGracePeriodSeconds: 30 + updateStrategy: + rollingUpdate: + maxUnavailable: 1 + type: RollingUpdate diff --git a/cluster/addons/metadata-proxy/gce/metadata-proxy-configmap.yaml b/cluster/addons/metadata-proxy/gce/metadata-proxy-configmap.yaml deleted file mode 100644 index 2d23f42ad63..00000000000 --- a/cluster/addons/metadata-proxy/gce/metadata-proxy-configmap.yaml +++ /dev/null @@ -1,88 +0,0 @@ -kind: ConfigMap -apiVersion: v1 -metadata: - name: metadata-proxy-config - namespace: kube-system - labels: - addonmanager.kubernetes.io/mode: EnsureExists -data: - nginx.conf: |- - user www-data; - worker_processes 4; - pid /run/nginx.pid; - error_log /dev/stdout; - - events { - worker_connections 20; - } - - http { - access_log /dev/stdout; - server { - listen 127.0.0.1:988; - # When serving 301s, don't redirect to port 988. - port_in_redirect off; - - # By default, return 403. This protects us from new API versions. - location / { - return 403 "This metadata API is not allowed by the metadata proxy."; - } - - # Allow for REST discovery. - location = / { - if ($args ~* "^(.+&)?recursive=") { - return 403 "?recursive calls are not allowed by the metadata proxy."; - } - proxy_pass http://169.254.169.254; - } - location = /computeMetadata/ { - if ($args ~* "^(.+&)?recursive=") { - return 403 "?recursive calls are not allowed by the metadata proxy."; - } - proxy_pass http://169.254.169.254; - } - - # By default, allow the v0.1, v1beta1, and v1 APIs. - location /0.1/ { - if ($args ~* "^(.+&)?recursive=") { - return 403 "?recursive calls are not allowed by the metadata proxy."; - } - proxy_pass http://169.254.169.254; - } - location /computeMetadata/v1beta1/ { - if ($args ~* "^(.+&)?recursive=") { - return 403 "?recursive calls are not allowed by the metadata proxy."; - } - proxy_pass http://169.254.169.254; - } - location /computeMetadata/v1/ { - if ($args ~* "^(.+&)?recursive=") { - return 403 "?recursive calls are not allowed by the metadata proxy."; - } - proxy_pass http://169.254.169.254; - } - - # Return a 403 for the kube-env attribute in all allowed API versions. - location /0.1/meta-data/attributes/kube-env { - return 403 "This metadata endpoint is concealed."; - } - location /computeMetadata/v1beta1/instance/attributes/kube-env { - return 403 "This metadata endpoint is concealed."; - } - location /computeMetadata/v1/instance/attributes/kube-env { - return 403 "This metadata endpoint is concealed."; - } - - # Return a 403 for instance identity in all allowed API versions. - location ~ /0.1/meta-data/service-accounts/.+/identity { - return 403 "This metadata endpoint is concealed."; - } - location ~ /computeMetadata/v1beta1/instance/service-accounts/.+/identity { - return 403 "This metadata endpoint is concealed."; - } - location ~ /computeMetadata/v1/instance/service-accounts/.+/identity { - return 403 "This metadata endpoint is concealed."; - } - } - } - diff --git a/cluster/addons/metadata-proxy/gce/metadata-proxy.yaml b/cluster/addons/metadata-proxy/gce/metadata-proxy.yaml index 2095345098d..ced6e43f9db 100644 --- a/cluster/addons/metadata-proxy/gce/metadata-proxy.yaml +++ b/cluster/addons/metadata-proxy/gce/metadata-proxy.yaml @@ -1,3 +1,13 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: metadata-proxy + namespace: kube-system + labels: + k8s-app: metadata-proxy + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile +--- apiVersion: extensions/v1beta1 kind: DaemonSet metadata: @@ -23,30 +33,50 @@ spec: annotations: scheduler.alpha.kubernetes.io/critical-pod: '' spec: + serviceAccountName: metadata-proxy hostNetwork: true dnsPolicy: Default containers: - name: metadata-proxy - image: gcr.io/google-containers/metadata-proxy:0.1.3 - imagePullPolicy: Always + image: gcr.io/google_containers/metadata-proxy:v0.1.7 securityContext: privileged: true - command: - - '/start-proxy.sh' + # Request and limit resources to get guaranteed QoS. resources: requests: - memory: "32Mi" - cpu: "50m" + memory: "25Mi" + cpu: "30m" limits: - memory: "32Mi" - cpu: "50m" - volumeMounts: - - name: config-volume - mountPath: /etc/nginx/ + memory: "25Mi" + cpu: "30m" + # BEGIN_PROMETHEUS_TO_SD + - name: prometheus-to-sd-exporter + image: gcr.io/google_containers/prometheus-to-sd:v0.2.2 + # Request and limit resources to get guaranteed QoS. + resources: + requests: + memory: "20Mi" + cpu: "2m" + limits: + memory: "20Mi" + cpu: "2m" + command: + - /monitor + - --stackdriver-prefix={{ prometheus_to_sd_prefix }}/addons + - --api-override={{ prometheus_to_sd_endpoint }} + - --source=metadata_proxy:http://127.0.0.1:989?whitelisted=request_count + - --pod-id=$(POD_NAME) + - --namespace-id=$(POD_NAMESPACE) + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + # END_PROMETHEUS_TO_SD nodeSelector: beta.kubernetes.io/metadata-proxy-ready: "true" terminationGracePeriodSeconds: 30 - volumes: - - name: config-volume - configMap: - name: metadata-proxy-config diff --git a/cluster/addons/metadata-proxy/gce/podsecuritypolicies/metadata-proxy-psp-binding.yaml b/cluster/addons/metadata-proxy/gce/podsecuritypolicies/metadata-proxy-psp-binding.yaml new file mode 100644 index 00000000000..86c5d06dbc9 --- /dev/null +++ b/cluster/addons/metadata-proxy/gce/podsecuritypolicies/metadata-proxy-psp-binding.yaml @@ -0,0 +1,16 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: gce:podsecuritypolicy:metadata-proxy + namespace: kube-system + labels: + addonmanager.kubernetes.io/mode: Reconcile + kubernetes.io/cluster-service: "true" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: gce:podsecuritypolicy:privileged +subjects: +- kind: ServiceAccount + name: metadata-proxy + namespace: kube-system diff --git a/cluster/addons/metrics-server/metrics-server-deployment.yaml b/cluster/addons/metrics-server/metrics-server-deployment.yaml index d018f8a34f0..73375b2202b 100644 --- a/cluster/addons/metrics-server/metrics-server-deployment.yaml +++ b/cluster/addons/metrics-server/metrics-server-deployment.yaml @@ -7,6 +7,19 @@ metadata: kubernetes.io/cluster-service: "true" addonmanager.kubernetes.io/mode: Reconcile --- +apiVersion: v1 +kind: ConfigMap +metadata: + name: metrics-server-config + namespace: kube-system + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: EnsureExists +data: + NannyConfiguration: |- + apiVersion: nannyconfig/v1alpha1 + kind: NannyConfiguration +--- apiVersion: extensions/v1beta1 kind: Deployment metadata: @@ -43,14 +56,14 @@ spec: name: https protocol: TCP - name: metrics-server-nanny - image: gcr.io/google_containers/addon-resizer:1.7 + image: gcr.io/google_containers/addon-resizer:1.8.1 resources: limits: cpu: 100m memory: 300Mi requests: - cpu: 50m - memory: 100Mi + cpu: 5m + memory: 50Mi env: - name: MY_POD_NAME valueFrom: @@ -60,9 +73,13 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + volumeMounts: + - name: metrics-server-config-volume + mountPath: /etc/config command: - /pod_nanny - - --cpu=80m + - --config-dir=/etc/config + - --cpu=40m - --extra-cpu=0.5m - --memory=140Mi - --extra-memory=4Mi @@ -71,6 +88,10 @@ spec: - --container=metrics-server - --poll-period=300000 - --estimator=exponential + volumes: + - name: metrics-server-config-volume + configMap: + name: metrics-server-config tolerations: - key: "CriticalAddonsOnly" operator: "Exists" diff --git a/cluster/addons/node-problem-detector/podsecuritypolicies/npd-psp-binding.yaml b/cluster/addons/node-problem-detector/podsecuritypolicies/npd-psp-binding.yaml new file mode 100644 index 00000000000..e55a285032f --- /dev/null +++ b/cluster/addons/node-problem-detector/podsecuritypolicies/npd-psp-binding.yaml @@ -0,0 +1,16 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: gce:podsecuritypolicy:npd + namespace: kube-system + labels: + addonmanager.kubernetes.io/mode: Reconcile + kubernetes.io/cluster-service: "true" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: gce:podsecuritypolicy:privileged +subjects: +- kind: ServiceAccount + name: node-problem-detector + namespace: kube-system diff --git a/cluster/addons/podsecuritypolicies/privileged.yaml b/cluster/addons/podsecuritypolicies/privileged.yaml deleted file mode 100644 index 345c0797cc6..00000000000 --- a/cluster/addons/podsecuritypolicies/privileged.yaml +++ /dev/null @@ -1,32 +0,0 @@ -apiVersion: extensions/v1beta1 -kind: PodSecurityPolicy -metadata: - annotations: - kubernetes.io/description: 'privileged allows access to all privileged and host - features and the ability to run as any user, any group, any fsGroup, and with - any SELinux context.' - creationTimestamp: 2016-05-06T19:28:58Z - name: privileged -spec: - privileged: true - defaultAddCapabilities: null - requiredDropCapabilities: null - allowedCapabilities: null - volumes: - - '*' - hostNetwork: true - hostPorts: - - - min: 0 - max: 65535 - hostIPC: true - hostPID: true - runAsUser: - rule: 'RunAsAny' - seLinux: - rule: 'RunAsAny' - supplementalGroups: - rule: 'RunAsAny' - fsGroup: - rule: 'RunAsAny' - readOnlyRootFilesystem: false diff --git a/cluster/addons/rbac/kube-apiserver-kubelet-api-admin-binding.yaml b/cluster/addons/rbac/kubelet-api-auth/kube-apiserver-kubelet-api-admin-binding.yaml similarity index 100% rename from cluster/addons/rbac/kube-apiserver-kubelet-api-admin-binding.yaml rename to cluster/addons/rbac/kubelet-api-auth/kube-apiserver-kubelet-api-admin-binding.yaml diff --git a/cluster/addons/rbac/kubelet-api-admin-role.yaml b/cluster/addons/rbac/kubelet-api-auth/kubelet-api-admin-role.yaml similarity index 100% rename from cluster/addons/rbac/kubelet-api-admin-role.yaml rename to cluster/addons/rbac/kubelet-api-auth/kubelet-api-admin-role.yaml diff --git a/cluster/addons/rbac/kubelet-certificate-management.yaml b/cluster/addons/rbac/kubelet-cert-rotation/kubelet-certificate-management.yaml similarity index 100% rename from cluster/addons/rbac/kubelet-certificate-management.yaml rename to cluster/addons/rbac/kubelet-cert-rotation/kubelet-certificate-management.yaml diff --git a/cluster/addons/rbac/legacy-kubelet-user-disable/kubelet-binding.yaml b/cluster/addons/rbac/legacy-kubelet-user-disable/kubelet-binding.yaml new file mode 100644 index 00000000000..4cd7174eafc --- /dev/null +++ b/cluster/addons/rbac/legacy-kubelet-user-disable/kubelet-binding.yaml @@ -0,0 +1,31 @@ +# This is required so that old clusters don't remove required bindings for 1.5 +# kubelets to function. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: kubelet-cluster-admin + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: EnsureExists +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:node +subjects: [] +--- +# This is required so that new clusters still have bootstrap permissions +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: kubelet-bootstrap + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:node-bootstrapper +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: User + name: kubelet diff --git a/cluster/addons/rbac/kubelet-binding.yaml b/cluster/addons/rbac/legacy-kubelet-user/kubelet-binding.yaml similarity index 100% rename from cluster/addons/rbac/kubelet-binding.yaml rename to cluster/addons/rbac/legacy-kubelet-user/kubelet-binding.yaml diff --git a/cluster/addons/storage-class/aws/default.yaml b/cluster/addons/storage-class/aws/default.yaml index defd7cd73bb..85fc316163b 100644 --- a/cluster/addons/storage-class/aws/default.yaml +++ b/cluster/addons/storage-class/aws/default.yaml @@ -1,4 +1,4 @@ -apiVersion: storage.k8s.io/v1beta1 +apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: gp2 diff --git a/cluster/addons/storage-class/azure/default.yaml b/cluster/addons/storage-class/azure/default.yaml index fb24e0e009e..04541645123 100644 --- a/cluster/addons/storage-class/azure/default.yaml +++ b/cluster/addons/storage-class/azure/default.yaml @@ -1,4 +1,4 @@ -apiVersion: storage.k8s.io/v1beta1 +apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: standard diff --git a/cluster/addons/storage-class/gce/default.yaml b/cluster/addons/storage-class/gce/default.yaml index 7a00fd03957..cf332090844 100644 --- a/cluster/addons/storage-class/gce/default.yaml +++ b/cluster/addons/storage-class/gce/default.yaml @@ -1,4 +1,4 @@ -apiVersion: storage.k8s.io/v1beta1 +apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: standard diff --git a/cluster/addons/storage-class/local/default.yaml b/cluster/addons/storage-class/local/default.yaml index 7e7d58b579c..a90a74b16f8 100644 --- a/cluster/addons/storage-class/local/default.yaml +++ b/cluster/addons/storage-class/local/default.yaml @@ -1,4 +1,4 @@ -apiVersion: storage.k8s.io/v1beta1 +apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: namespace: kube-system diff --git a/cluster/addons/storage-class/openstack/default.yaml b/cluster/addons/storage-class/openstack/default.yaml index 2fe85cd50f4..01687637ef5 100644 --- a/cluster/addons/storage-class/openstack/default.yaml +++ b/cluster/addons/storage-class/openstack/default.yaml @@ -1,4 +1,4 @@ -apiVersion: storage.k8s.io/v1beta1 +apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: standard diff --git a/cluster/addons/storage-class/vsphere/default.yaml b/cluster/addons/storage-class/vsphere/default.yaml index 288c541165f..5678b8f08e3 100644 --- a/cluster/addons/storage-class/vsphere/default.yaml +++ b/cluster/addons/storage-class/vsphere/default.yaml @@ -1,4 +1,4 @@ -apiVersion: storage.k8s.io/v1beta1 +apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: thin diff --git a/cluster/centos/config-build.sh b/cluster/centos/config-build.sh index 4887bc13b93..4854f7e1062 100755 --- a/cluster/centos/config-build.sh +++ b/cluster/centos/config-build.sh @@ -40,8 +40,7 @@ FLANNEL_DOWNLOAD_URL=\ ETCD_DOWNLOAD_URL=\ "https://github.com/coreos/etcd/releases/download/v${ETCD_VERSION}/etcd-v${ETCD_VERSION}-linux-amd64.tar.gz" -# TODO(#33726): switch to dl.k8s.io K8S_CLIENT_DOWNLOAD_URL=\ -"https://storage.googleapis.com/kubernetes-release/release/v${K8S_VERSION}/kubernetes-client-linux-amd64.tar.gz" +"https://dl.k8s.io/v${K8S_VERSION}/kubernetes-client-linux-amd64.tar.gz" K8S_SERVER_DOWNLOAD_URL=\ -"https://storage.googleapis.com/kubernetes-release/release/v${K8S_VERSION}/kubernetes-server-linux-amd64.tar.gz" +"https://dl.k8s.io/v${K8S_VERSION}/kubernetes-server-linux-amd64.tar.gz" diff --git a/cluster/centos/config-default.sh b/cluster/centos/config-default.sh index dff6b5f387c..eca05cb3cc9 100755 --- a/cluster/centos/config-default.sh +++ b/cluster/centos/config-default.sh @@ -120,7 +120,7 @@ export FLANNEL_NET=${FLANNEL_NET:-"172.16.0.0/16"} # Admission Controllers to invoke prior to persisting objects in cluster # If we included ResourceQuota, we should keep it at the end of the list to prevent incrementing quota usage prematurely. -export ADMISSION_CONTROL=${ADMISSION_CONTROL:-"Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultTolerationSeconds,Priority,ResourceQuota"} +export ADMISSION_CONTROL=${ADMISSION_CONTROL:-"Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeClaimResize,DefaultTolerationSeconds,Priority,PVCProtection,ResourceQuota"} # Extra options to set on the Docker command line. # This is useful for setting --insecure-registry for local registries. diff --git a/cluster/centos/deployAddons.sh b/cluster/centos/deployAddons.sh index cc96b44248e..cefbc7c250d 100755 --- a/cluster/centos/deployAddons.sh +++ b/cluster/centos/deployAddons.sh @@ -45,19 +45,13 @@ function deploy_dns { } function deploy_dashboard { - if ${KUBECTL} get rc -l k8s-app=kubernetes-dashboard --namespace=kube-system | grep kubernetes-dashboard-v &> /dev/null; then - echo "Kubernetes Dashboard replicationController already exists" - else - echo "Creating Kubernetes Dashboard replicationController" - ${KUBECTL} create -f ${KUBE_ROOT}/cluster/addons/dashboard/dashboard-controller.yaml - fi + echo "Deploying Kubernetes Dashboard" - if ${KUBECTL} get service/kubernetes-dashboard --namespace=kube-system &> /dev/null; then - echo "Kubernetes Dashboard service already exists" - else - echo "Creating Kubernetes Dashboard service" - ${KUBECTL} create -f ${KUBE_ROOT}/cluster/addons/dashboard/dashboard-service.yaml - fi + ${KUBECTL} apply -f ${KUBE_ROOT}/cluster/addons/dashboard/dashboard-secret.yaml + ${KUBECTL} apply -f ${KUBE_ROOT}/cluster/addons/dashboard/dashboard-configmap.yaml + ${KUBECTL} apply -f ${KUBE_ROOT}/cluster/addons/dashboard/dashboard-rbac.yaml + ${KUBECTL} apply -f ${KUBE_ROOT}/cluster/addons/dashboard/dashboard-controller.yaml + ${KUBECTL} apply -f ${KUBE_ROOT}/cluster/addons/dashboard/dashboard-service.yaml echo } diff --git a/cluster/centos/master/scripts/apiserver.sh b/cluster/centos/master/scripts/apiserver.sh index 6b7b1c2b940..686e95d68b6 100755 --- a/cluster/centos/master/scripts/apiserver.sh +++ b/cluster/centos/master/scripts/apiserver.sh @@ -16,7 +16,7 @@ MASTER_ADDRESS=${1:-"8.8.8.18"} -ETCD_SERVERS=${2:-"http://8.8.8.18:2379"} +ETCD_SERVERS=${2:-"https://8.8.8.18:2379"} SERVICE_CLUSTER_IP_RANGE=${3:-"10.10.10.0/24"} ADMISSION_CONTROL=${4:-""} diff --git a/cluster/centos/node/scripts/flannel.sh b/cluster/centos/node/scripts/flannel.sh index 2830daefd70..58783c5e3b1 100755 --- a/cluster/centos/node/scripts/flannel.sh +++ b/cluster/centos/node/scripts/flannel.sh @@ -15,7 +15,7 @@ # limitations under the License. -ETCD_SERVERS=${1:-"http://8.8.8.18:2379"} +ETCD_SERVERS=${1:-"https://8.8.8.18:2379"} FLANNEL_NET=${2:-"172.16.0.0/16"} CA_FILE="/srv/kubernetes/etcd/ca.pem" diff --git a/cluster/clientbin.sh b/cluster/clientbin.sh index b34a6e65866..a03a2ca3e36 100755 --- a/cluster/clientbin.sh +++ b/cluster/clientbin.sh @@ -84,9 +84,14 @@ function get_bin() { "${KUBE_ROOT}/_output/bin/${bin}" "${KUBE_ROOT}/_output/dockerized/bin/${host_os}/${host_arch}/${bin}" "${KUBE_ROOT}/_output/local/bin/${host_os}/${host_arch}/${bin}" - "${KUBE_ROOT}/bazel-bin/${srcdir}/${bin}" "${KUBE_ROOT}/platforms/${host_os}/${host_arch}/${bin}" ) + # Also search for binary in bazel build tree. + # The bazel go rules place binaries in subtrees like + # "bazel-bin/source/path/linux_amd64_pure_stripped/binaryname", so make sure + # the platform name is matched in the path. + locations+=($(find "${KUBE_ROOT}/bazel-bin/${srcdir}" -type f -executable \ + -path "*/${host_os}_${host_arch}*/${bin}" 2>/dev/null || true) ) echo $( (ls -t "${locations[@]}" 2>/dev/null || true) | head -1 ) } diff --git a/cluster/common.sh b/cluster/common.sh index 23208b453be..a2b947f1748 100755 --- a/cluster/common.sh +++ b/cluster/common.sh @@ -160,20 +160,6 @@ function clear-kubeconfig() { echo "Cleared config for ${CONTEXT} from ${KUBECONFIG}" } -# Creates a kubeconfig file with the credentials for only the current-context -# cluster. This is used by federation to create secrets in test setup. -function create-kubeconfig-for-federation() { - if [[ "${FEDERATION:-}" == "true" ]]; then - echo "creating kubeconfig for federation secret" - local kubectl="${KUBE_ROOT}/cluster/kubectl.sh" - local cc=$("${kubectl}" config view -o jsonpath='{.current-context}') - KUBECONFIG_DIR=$(dirname ${KUBECONFIG:-$DEFAULT_KUBECONFIG}) - KUBECONFIG_PATH="${KUBECONFIG_DIR}/federation/kubernetes-apiserver/${cc}" - mkdir -p "${KUBECONFIG_PATH}" - "${kubectl}" config view --minify --flatten > "${KUBECONFIG_PATH}/kubeconfig" - fi -} - function tear_down_alive_resources() { local kubectl="${KUBE_ROOT}/cluster/kubectl.sh" "${kubectl}" delete deployments --all || true @@ -624,6 +610,8 @@ KUBERNETES_MASTER_NAME: $(yaml-quote ${KUBERNETES_MASTER_NAME}) ALLOCATE_NODE_CIDRS: $(yaml-quote ${ALLOCATE_NODE_CIDRS:-false}) ENABLE_CLUSTER_MONITORING: $(yaml-quote ${ENABLE_CLUSTER_MONITORING:-none}) ENABLE_METRICS_SERVER: $(yaml-quote ${ENABLE_METRICS_SERVER:-false}) +ENABLE_METADATA_AGENT: $(yaml-quote ${ENABLE_METADATA_AGENT:-none}) +METADATA_AGENT_VERSION: $(yaml-quote ${METADATA_AGENT_VERSION:-}) DOCKER_REGISTRY_MIRROR_URL: $(yaml-quote ${DOCKER_REGISTRY_MIRROR_URL:-}) ENABLE_L7_LOADBALANCING: $(yaml-quote ${ENABLE_L7_LOADBALANCING:-none}) ENABLE_CLUSTER_LOGGING: $(yaml-quote ${ENABLE_CLUSTER_LOGGING:-false}) @@ -636,6 +624,7 @@ ENABLE_RESCHEDULER: $(yaml-quote ${ENABLE_RESCHEDULER:-false}) LOGGING_DESTINATION: $(yaml-quote ${LOGGING_DESTINATION:-}) ELASTICSEARCH_LOGGING_REPLICAS: $(yaml-quote ${ELASTICSEARCH_LOGGING_REPLICAS:-}) ENABLE_CLUSTER_DNS: $(yaml-quote ${ENABLE_CLUSTER_DNS:-false}) +CLUSTER_DNS_CORE_DNS: $(yaml-quote ${CLUSTER_DNS_CORE_DNS:-false}) ENABLE_CLUSTER_REGISTRY: $(yaml-quote ${ENABLE_CLUSTER_REGISTRY:-false}) CLUSTER_REGISTRY_DISK: $(yaml-quote ${CLUSTER_REGISTRY_DISK:-}) CLUSTER_REGISTRY_DISK_SIZE: $(yaml-quote ${CLUSTER_REGISTRY_DISK_SIZE:-}) @@ -647,6 +636,7 @@ KUBE_PROXY_DAEMONSET: $(yaml-quote ${KUBE_PROXY_DAEMONSET:-false}) KUBE_PROXY_TOKEN: $(yaml-quote ${KUBE_PROXY_TOKEN:-}) NODE_PROBLEM_DETECTOR_TOKEN: $(yaml-quote ${NODE_PROBLEM_DETECTOR_TOKEN:-}) ADMISSION_CONTROL: $(yaml-quote ${ADMISSION_CONTROL:-}) +ENABLE_POD_SECURITY_POLICY: $(yaml-quote ${ENABLE_POD_SECURITY_POLICY:-}) MASTER_IP_RANGE: $(yaml-quote ${MASTER_IP_RANGE}) RUNTIME_CONFIG: $(yaml-quote ${RUNTIME_CONFIG}) CA_CERT: $(yaml-quote ${CA_CERT_BASE64:-}) @@ -674,11 +664,22 @@ ENABLE_CACHE_MUTATION_DETECTOR: $(yaml-quote ${ENABLE_CACHE_MUTATION_DETECTOR:-f ENABLE_PATCH_CONVERSION_DETECTOR: $(yaml-quote ${ENABLE_PATCH_CONVERSION_DETECTOR:-false}) ADVANCED_AUDIT_POLICY: $(yaml-quote ${ADVANCED_AUDIT_POLICY:-}) ADVANCED_AUDIT_BACKEND: $(yaml-quote ${ADVANCED_AUDIT_BACKEND:-log}) +ADVANCED_AUDIT_WEBHOOK_BUFFER_SIZE: $(yaml-quote ${ADVANCED_AUDIT_WEBHOOK_BUFFER_SIZE:-}) +ADVANCED_AUDIT_WEBHOOK_MAX_BATCH_SIZE: $(yaml-quote ${ADVANCED_AUDIT_WEBHOOK_MAX_BATCH_SIZE:-}) +ADVANCED_AUDIT_WEBHOOK_MAX_BATCH_WAIT: $(yaml-quote ${ADVANCED_AUDIT_WEBHOOK_MAX_BATCH_WAIT:-}) +ADVANCED_AUDIT_WEBHOOK_THROTTLE_QPS: $(yaml-quote ${ADVANCED_AUDIT_WEBHOOK_THROTTLE_QPS:-}) +ADVANCED_AUDIT_WEBHOOK_THROTTLE_BURST: $(yaml-quote ${ADVANCED_AUDIT_WEBHOOK_THROTTLE_BURST:-}) +ADVANCED_AUDIT_WEBHOOK_INITIAL_BACKOFF: $(yaml-quote ${ADVANCED_AUDIT_WEBHOOK_INITIAL_BACKOFF:-}) GCE_API_ENDPOINT: $(yaml-quote ${GCE_API_ENDPOINT:-}) +GCE_GLBC_IMAGE: $(yaml-quote ${GCE_GLBC_IMAGE:-}) PROMETHEUS_TO_SD_ENDPOINT: $(yaml-quote ${PROMETHEUS_TO_SD_ENDPOINT:-}) PROMETHEUS_TO_SD_PREFIX: $(yaml-quote ${PROMETHEUS_TO_SD_PREFIX:-}) ENABLE_PROMETHEUS_TO_SD: $(yaml-quote ${ENABLE_PROMETHEUS_TO_SD:-false}) ENABLE_POD_PRIORITY: $(yaml-quote ${ENABLE_POD_PRIORITY:-}) +CONTAINER_RUNTIME: $(yaml-quote ${CONTAINER_RUNTIME:-}) +CONTAINER_RUNTIME_ENDPOINT: $(yaml-quote ${CONTAINER_RUNTIME_ENDPOINT:-}) +NODE_LOCAL_SSDS_EXT: $(yaml-quote ${NODE_LOCAL_SSDS_EXT:-}) +LOAD_IMAGE_COMMAND: $(yaml-quote ${LOAD_IMAGE_COMMAND:-}) EOF if [ -n "${KUBELET_PORT:-}" ]; then cat >>$file <>$file <>$file <>$file <>$file <>$file <>$file <>$file <>$file <>$file <>$file <>$file <>$file <>$file <>$file <>$file <>$file <${cert_create_debug_output} || { - # If there was an error in the subshell, just die. - # TODO(roberthbailey): add better error handling here + cp -r easy-rsa-master/easyrsa3/* easy-rsa-master/aggregator) &>${cert_create_debug_output} || true + CERT_DIR="${KUBE_TEMP}/easy-rsa-master/easyrsa3" + AGGREGATOR_CERT_DIR="${KUBE_TEMP}/easy-rsa-master/aggregator" + if [ ! -x "${CERT_DIR}/easyrsa" -o ! -x "${AGGREGATOR_CERT_DIR}/easyrsa" ]; then + # TODO(roberthbailey,porridge): add better error handling here, + # see https://github.com/kubernetes/kubernetes/issues/55229 cat "${cert_create_debug_output}" >&2 echo "=== Failed to setup easy-rsa: Aborting ===" >&2 exit 2 - } + fi } # Runs the easy RSA commands to generate certificate files. -# The generated files are at ${KUBE_TEMP}/easy-rsa-master/easyrsa3 +# The generated files are IN ${CERT_DIR} # # Assumed vars # KUBE_TEMP # MASTER_NAME +# CERT_DIR # PRIMARY_CN: Primary canonical name # SANS: Subject alternate names # @@ -1080,7 +1100,7 @@ function generate-certs { local -r cert_create_debug_output=$(mktemp "${KUBE_TEMP}/cert_create_debug_output.XXX") # Note: This was heavily cribbed from make-ca-cert.sh (set -x - cd "${KUBE_TEMP}/easy-rsa-master/easyrsa3" + cd "${CERT_DIR}" ./easyrsa init-pki # this puts the cert into pki/ca.crt and the key into pki/private/ca.key ./easyrsa --batch "--req-cn=${PRIMARY_CN}@$(date +%s)" build-ca nopass @@ -1101,21 +1121,42 @@ function generate-certs { ./easyrsa --dn-mode=org \ --req-cn=kubecfg --req-org=system:masters \ --req-c= --req-st= --req-city= --req-email= --req-ou= \ - build-client-full kubecfg nopass) &>${cert_create_debug_output} || { - # If there was an error in the subshell, just die. - # TODO(roberthbailey): add better error handling here + build-client-full kubecfg nopass) &>${cert_create_debug_output} || true + local output_file_missing=0 + local output_file + for output_file in \ + "${CERT_DIR}/pki/private/ca.key" \ + "${CERT_DIR}/pki/ca.crt" \ + "${CERT_DIR}/pki/issued/${MASTER_NAME}.crt" \ + "${CERT_DIR}/pki/private/${MASTER_NAME}.key" \ + "${CERT_DIR}/pki/issued/kubelet.crt" \ + "${CERT_DIR}/pki/private/kubelet.key" \ + "${CERT_DIR}/pki/issued/kubecfg.crt" \ + "${CERT_DIR}/pki/private/kubecfg.key" \ + "${CERT_DIR}/pki/issued/kube-apiserver.crt" \ + "${CERT_DIR}/pki/private/kube-apiserver.key" + do + if [[ ! -s "${output_file}" ]]; then + echo "Expected file ${output_file} not created" >&2 + output_file_missing=1 + fi + done + if (( $output_file_missing )); then + # TODO(roberthbailey,porridge): add better error handling here, + # see https://github.com/kubernetes/kubernetes/issues/55229 cat "${cert_create_debug_output}" >&2 echo "=== Failed to generate master certificates: Aborting ===" >&2 exit 2 - } + fi } # Runs the easy RSA commands to generate aggregator certificate files. -# The generated files are at ${KUBE_TEMP}/easy-rsa-master/aggregator +# The generated files are in ${AGGREGATOR_CERT_DIR} # # Assumed vars # KUBE_TEMP # AGGREGATOR_MASTER_NAME +# AGGREGATOR_CERT_DIR # AGGREGATOR_PRIMARY_CN: Primary canonical name # AGGREGATOR_SANS: Subject alternate names # @@ -1145,13 +1186,27 @@ function generate-aggregator-certs { ./easyrsa --dn-mode=org \ --req-cn=proxy-clientcfg --req-org=system:aggregator \ --req-c= --req-st= --req-city= --req-email= --req-ou= \ - build-client-full proxy-clientcfg nopass) &>${cert_create_debug_output} || { - # If there was an error in the subshell, just die. - # TODO(roberthbailey): add better error handling here + build-client-full proxy-clientcfg nopass) &>${cert_create_debug_output} || true + local output_file_missing=0 + local output_file + for output_file in \ + "${AGGREGATOR_CERT_DIR}/pki/private/ca.key" \ + "${AGGREGATOR_CERT_DIR}/pki/ca.crt" \ + "${AGGREGATOR_CERT_DIR}/pki/issued/proxy-client.crt" \ + "${AGGREGATOR_CERT_DIR}/pki/private/proxy-client.key" + do + if [[ ! -s "${output_file}" ]]; then + echo "Expected file ${output_file} not created" >&2 + output_file_missing=1 + fi + done + if (( $output_file_missing )); then + # TODO(roberthbailey,porridge): add better error handling here, + # see https://github.com/kubernetes/kubernetes/issues/55229 cat "${cert_create_debug_output}" >&2 echo "=== Failed to generate aggregator certificates: Aborting ===" >&2 exit 2 - } + fi } # Run the cfssl command to generates certificate files for etcd service, the diff --git a/cluster/gce/BUILD b/cluster/gce/BUILD index 35e6e600ce7..8be2252d98e 100644 --- a/cluster/gce/BUILD +++ b/cluster/gce/BUILD @@ -34,6 +34,7 @@ filegroup( name = "all-srcs", srcs = [ ":package-srcs", + "//cluster/gce/addons:all-srcs", "//cluster/gce/gci/mounter:all-srcs", ], tags = ["automanaged"], diff --git a/cluster/gce/addons/BUILD b/cluster/gce/addons/BUILD new file mode 100644 index 00000000000..c3e5620e25b --- /dev/null +++ b/cluster/gce/addons/BUILD @@ -0,0 +1,38 @@ +package(default_visibility = ["//visibility:public"]) + +load("@io_bazel//tools/build_defs/pkg:pkg.bzl", "pkg_tar") + +filegroup( + name = "addon-srcs", + srcs = glob( + [ + "**/*.json", + "**/*.yaml", + "**/*.yaml.in", + ], + exclude = ["**/*demo*/**"], + ), +) + +pkg_tar( + name = "addons", + extension = "tar.gz", + files = [ + ":addon-srcs", + ], + mode = "0644", + strip_prefix = ".", +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/cluster/gce/addons/README.md b/cluster/gce/addons/README.md new file mode 100644 index 00000000000..de55c8e9a01 --- /dev/null +++ b/cluster/gce/addons/README.md @@ -0,0 +1,7 @@ +# GCE Cluster addons + +These cluster add-ons are specific to GCE and GKE clusters. The GCE-specific addon directory is +merged with the general cluster addon directory at release, so addon paths (relative to the addon +directory) must be unique across the 2 directory structures. + +More details on addons in general can be found [here](../../addons/README.md). diff --git a/cluster/gce/addons/podsecuritypolicies/kube-proxy-binding.yaml b/cluster/gce/addons/podsecuritypolicies/kube-proxy-binding.yaml new file mode 100644 index 00000000000..49e8650352e --- /dev/null +++ b/cluster/gce/addons/podsecuritypolicies/kube-proxy-binding.yaml @@ -0,0 +1,15 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: gce:podsecuritypolicy:kube-proxy + labels: + addonmanager.kubernetes.io/mode: Reconcile + kubernetes.io/cluster-service: "true" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: gce:podsecuritypolicy:privileged +subjects: + - kind: ServiceAccount + name: kube-proxy + namespace: kube-system diff --git a/cluster/gce/addons/podsecuritypolicies/kube-system-binding.yaml b/cluster/gce/addons/podsecuritypolicies/kube-system-binding.yaml new file mode 100644 index 00000000000..0c3c97eca64 --- /dev/null +++ b/cluster/gce/addons/podsecuritypolicies/kube-system-binding.yaml @@ -0,0 +1,17 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: gce:podsecuritypolicy:unprivileged-addon + namespace: kube-system + labels: + addonmanager.kubernetes.io/mode: Reconcile + kubernetes.io/cluster-service: "true" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: gce:podsecuritypolicy:unprivileged-addon +subjects: +- kind: Group + # All service accounts in the kube-system namespace are allowed to use this. + name: system:serviceaccounts:kube-system + apiGroup: rbac.authorization.k8s.io diff --git a/cluster/gce/addons/podsecuritypolicies/node-binding.yaml b/cluster/gce/addons/podsecuritypolicies/node-binding.yaml new file mode 100644 index 00000000000..332358240b7 --- /dev/null +++ b/cluster/gce/addons/podsecuritypolicies/node-binding.yaml @@ -0,0 +1,24 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: gce:podsecuritypolicy:nodes + namespace: kube-system + annotations: + kubernetes.io/description: 'Allow nodes to create privileged pods. Should + be used in combination with the NodeRestriction admission plugin to limit + nodes to mirror pods bound to themselves.' + labels: + addonmanager.kubernetes.io/mode: Reconcile + kubernetes.io/cluster-service: 'true' +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: gce:podsecuritypolicy:privileged +subjects: + - kind: Group + apiGroup: rbac.authorization.k8s.io + name: system:nodes + - kind: User + apiGroup: rbac.authorization.k8s.io + # Legacy node ID + name: kubelet diff --git a/cluster/gce/addons/podsecuritypolicies/persistent-volume-binder-binding.yaml b/cluster/gce/addons/podsecuritypolicies/persistent-volume-binder-binding.yaml new file mode 100644 index 00000000000..40d202b8362 --- /dev/null +++ b/cluster/gce/addons/podsecuritypolicies/persistent-volume-binder-binding.yaml @@ -0,0 +1,18 @@ +apiVersion: rbac.authorization.k8s.io/v1 +# The persistent volume binder creates recycler pods in the default namespace, +# but the addon manager only creates namespaced objects in the kube-system +# namespace, so this is a ClusterRoleBinding. +kind: ClusterRoleBinding +metadata: + name: gce:podsecuritypolicy:persistent-volume-binder + labels: + addonmanager.kubernetes.io/mode: Reconcile + kubernetes.io/cluster-service: "true" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: gce:podsecuritypolicy:persistent-volume-binder +subjects: +- kind: ServiceAccount + name: persistent-volume-binder + namespace: kube-system diff --git a/cluster/gce/addons/podsecuritypolicies/persistent-volume-binder-role.yaml b/cluster/gce/addons/podsecuritypolicies/persistent-volume-binder-role.yaml new file mode 100644 index 00000000000..27770701991 --- /dev/null +++ b/cluster/gce/addons/podsecuritypolicies/persistent-volume-binder-role.yaml @@ -0,0 +1,20 @@ +apiVersion: rbac.authorization.k8s.io/v1 +# The persistent volume binder creates recycler pods in the default namespace, +# but the addon manager only creates namespaced objects in the kube-system +# namespace, so this is a ClusterRole. +kind: ClusterRole +metadata: + name: gce:podsecuritypolicy:persistent-volume-binder + namespace: default + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile +rules: +- apiGroups: + - extensions + resourceNames: + - gce.persistent-volume-binder + resources: + - podsecuritypolicies + verbs: + - use diff --git a/cluster/gce/addons/podsecuritypolicies/persistent-volume-binder.yaml b/cluster/gce/addons/podsecuritypolicies/persistent-volume-binder.yaml new file mode 100644 index 00000000000..f1546203757 --- /dev/null +++ b/cluster/gce/addons/podsecuritypolicies/persistent-volume-binder.yaml @@ -0,0 +1,29 @@ +apiVersion: extensions/v1beta1 +kind: PodSecurityPolicy +metadata: + name: gce.persistent-volume-binder + annotations: + kubernetes.io/description: 'Policy used by the persistent-volume-binder + (a.k.a. persistentvolume-controller) to run recycler pods.' + # TODO: This should use the default seccomp profile. + seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*' + labels: + kubernetes.io/cluster-service: 'true' + addonmanager.kubernetes.io/mode: Reconcile +spec: + privileged: false + volumes: + - 'nfs' + - 'secret' # Required for service account credentials. + hostNetwork: false + hostIPC: false + hostPID: false + runAsUser: + rule: 'RunAsAny' + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'RunAsAny' + fsGroup: + rule: 'RunAsAny' + readOnlyRootFilesystem: false diff --git a/cluster/gce/addons/podsecuritypolicies/privileged-role.yaml b/cluster/gce/addons/podsecuritypolicies/privileged-role.yaml new file mode 100644 index 00000000000..84bc91898d3 --- /dev/null +++ b/cluster/gce/addons/podsecuritypolicies/privileged-role.yaml @@ -0,0 +1,16 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: gce:podsecuritypolicy:privileged + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile +rules: +- apiGroups: + - extensions + resourceNames: + - gce.privileged + resources: + - podsecuritypolicies + verbs: + - use diff --git a/cluster/gce/addons/podsecuritypolicies/privileged.yaml b/cluster/gce/addons/podsecuritypolicies/privileged.yaml new file mode 100644 index 00000000000..0fb96e1644d --- /dev/null +++ b/cluster/gce/addons/podsecuritypolicies/privileged.yaml @@ -0,0 +1,33 @@ +apiVersion: extensions/v1beta1 +kind: PodSecurityPolicy +metadata: + name: gce.privileged + annotations: + kubernetes.io/description: 'privileged allows full unrestricted access to + pod features, as if the PodSecurityPolicy controller was not enabled.' + seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*' + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile +spec: + privileged: true + allowPrivilegeEscalation: true + allowedCapabilities: + - '*' + volumes: + - '*' + hostNetwork: true + hostPorts: + - min: 0 + max: 65535 + hostIPC: true + hostPID: true + runAsUser: + rule: 'RunAsAny' + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'RunAsAny' + fsGroup: + rule: 'RunAsAny' + readOnlyRootFilesystem: false diff --git a/cluster/gce/addons/podsecuritypolicies/unprivileged-addon-role.yaml b/cluster/gce/addons/podsecuritypolicies/unprivileged-addon-role.yaml new file mode 100644 index 00000000000..580d67dec42 --- /dev/null +++ b/cluster/gce/addons/podsecuritypolicies/unprivileged-addon-role.yaml @@ -0,0 +1,17 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: gce:podsecuritypolicy:unprivileged-addon + namespace: kube-system + labels: + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile +rules: +- apiGroups: + - extensions + resourceNames: + - gce.unprivileged-addon + resources: + - podsecuritypolicies + verbs: + - use diff --git a/cluster/gce/addons/podsecuritypolicies/unprivileged-addon.yaml b/cluster/gce/addons/podsecuritypolicies/unprivileged-addon.yaml new file mode 100644 index 00000000000..334e8b4c5c5 --- /dev/null +++ b/cluster/gce/addons/podsecuritypolicies/unprivileged-addon.yaml @@ -0,0 +1,38 @@ +apiVersion: extensions/v1beta1 +kind: PodSecurityPolicy +metadata: + name: gce.unprivileged-addon + annotations: + kubernetes.io/description: 'This policy grants the minimum ammount of + privilege necessary to run non-privileged kube-system pods. This policy is + not intended for use outside of kube-system, and may include further + restrictions in the future.' + # TODO: Addons should use the default seccomp profile. + seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*' + # 'runtime/default' is already the default, but must be filled in on the + # pod to pass admission. + apparmor.security.beta.kubernetes.io/defaultProfileName: 'runtime/default' + apparmor.security.beta.kubernetes.io/allowedProfileNames: 'runtime/default' + labels: + kubernetes.io/cluster-service: 'true' + addonmanager.kubernetes.io/mode: Reconcile +spec: + privileged: false + allowPrivilegeEscalation: false + volumes: + - 'emptyDir' + - 'configMap' + - 'secret' + hostNetwork: false + hostIPC: false + hostPID: false + # TODO: The addons using this profile should not run as root. + runAsUser: + rule: 'RunAsAny' + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'RunAsAny' + fsGroup: + rule: 'RunAsAny' + readOnlyRootFilesystem: false diff --git a/cluster/gce/config-common.sh b/cluster/gce/config-common.sh index 0b252825d9a..1515d3e0799 100644 --- a/cluster/gce/config-common.sh +++ b/cluster/gce/config-common.sh @@ -95,11 +95,9 @@ function get-cluster-ip-range { if [[ "${NUM_NODES}" -gt 4000 ]]; then suggested_range="10.64.0.0/11" fi - echo "${suggested_range}" + echo "${suggested_range}" } -if [[ "${FEDERATION:-}" == true ]]; then - NODE_SCOPES="${NODE_SCOPES:-monitoring,logging-write,storage-ro,https://www.googleapis.com/auth/ndev.clouddns.readwrite}" -else - NODE_SCOPES="${NODE_SCOPES:-monitoring,logging-write,storage-ro}" -fi +# NOTE: Avoid giving nodes empty scopes, because kubelet needs a service account +# in order to initialize properly. +NODE_SCOPES="${NODE_SCOPES:-monitoring,logging-write,storage-ro}" diff --git a/cluster/gce/config-default.sh b/cluster/gce/config-default.sh index 370939b7c98..80c73415dd9 100755 --- a/cluster/gce/config-default.sh +++ b/cluster/gce/config-default.sh @@ -30,12 +30,18 @@ REGIONAL_KUBE_ADDONS=${REGIONAL_KUBE_ADDONS:-true} NODE_SIZE=${NODE_SIZE:-n1-standard-2} NUM_NODES=${NUM_NODES:-3} MASTER_SIZE=${MASTER_SIZE:-n1-standard-$(get-master-size)} +MASTER_MIN_CPU_ARCHITECTURE=${MASTER_MIN_CPU_ARCHITECTURE:-} # To allow choosing better architectures. MASTER_DISK_TYPE=pd-ssd MASTER_DISK_SIZE=${MASTER_DISK_SIZE:-$(get-master-disk-size)} MASTER_ROOT_DISK_SIZE=${MASTER_ROOT_DISK_SIZE:-$(get-master-root-disk-size)} NODE_DISK_TYPE=${NODE_DISK_TYPE:-pd-standard} NODE_DISK_SIZE=${NODE_DISK_SIZE:-100GB} NODE_LOCAL_SSDS=${NODE_LOCAL_SSDS:-0} +# An extension to local SSDs allowing users to specify block/fs and SCSI/NVMe devices +# Format of this variable will be "#,scsi/nvme,block/fs" you can specify multiple +# configurations by seperating them by a semi-colon ex. "2,scsi,fs;1,nvme,block" +# is a request for 2 SCSI formatted and mounted SSDs and 1 NVMe block device SSD. +NODE_LOCAL_SSDS_EXT=${NODE_LOCAL_SSDS_EXT:-} # Accelerators to be attached to each node. Format "type=,count=" # More information on available GPUs here - https://cloud.google.com/compute/docs/gpus/ NODE_ACCELERATORS=${NODE_ACCELERATORS:-""} @@ -43,7 +49,7 @@ REGISTER_MASTER_KUBELET=${REGISTER_MASTER:-true} PREEMPTIBLE_NODE=${PREEMPTIBLE_NODE:-false} PREEMPTIBLE_MASTER=${PREEMPTIBLE_MASTER:-false} KUBE_DELETE_NODES=${KUBE_DELETE_NODES:-true} -KUBE_DELETE_NETWORK=${KUBE_DELETE_NETWORK:-false} +KUBE_DELETE_NETWORK=${KUBE_DELETE_NETWORK:-} # default value calculated below CREATE_CUSTOM_NETWORK=${CREATE_CUSTOM_NETWORK:-false} MASTER_OS_DISTRIBUTION=${KUBE_MASTER_OS_DISTRIBUTION:-${KUBE_OS_DISTRIBUTION:-gci}} @@ -74,16 +80,29 @@ fi # Also please update corresponding image for node e2e at: # https://github.com/kubernetes/kubernetes/blob/master/test/e2e_node/jenkins/image-config.yaml CVM_VERSION=${CVM_VERSION:-container-vm-v20170627} -GCI_VERSION=${KUBE_GCI_VERSION:-cos-stable-60-9592-90-0} +GCI_VERSION=${KUBE_GCI_VERSION:-cos-stable-63-10032-71-0} MASTER_IMAGE=${KUBE_GCE_MASTER_IMAGE:-} MASTER_IMAGE_PROJECT=${KUBE_GCE_MASTER_PROJECT:-cos-cloud} NODE_IMAGE=${KUBE_GCE_NODE_IMAGE:-${GCI_VERSION}} NODE_IMAGE_PROJECT=${KUBE_GCE_NODE_PROJECT:-cos-cloud} +NODE_SERVICE_ACCOUNT=${KUBE_GCE_NODE_SERVICE_ACCOUNT:-default} CONTAINER_RUNTIME=${KUBE_CONTAINER_RUNTIME:-docker} +CONTAINER_RUNTIME_ENDPOINT=${KUBE_CONTAINER_RUNTIME_ENDPOINT:-} +LOAD_IMAGE_COMMAND=${KUBE_LOAD_IMAGE_COMMAND:-docker load -i} RKT_VERSION=${KUBE_RKT_VERSION:-1.23.0} RKT_STAGE1_IMAGE=${KUBE_RKT_STAGE1_IMAGE:-coreos.com/rkt/stage1-coreos} +# MASTER_EXTRA_METADATA is the extra instance metadata on master instance separated by commas. +MASTER_EXTRA_METADATA=${KUBE_MASTER_EXTRA_METADATA:-${KUBE_EXTRA_METADATA:-}} +# MASTER_EXTRA_METADATA is the extra instance metadata on node instance separated by commas. +NODE_EXTRA_METADATA=${KUBE_NODE_EXTRA_METADATA:-${KUBE_EXTRA_METADATA:-}} NETWORK=${KUBE_GCE_NETWORK:-default} +# Enable network deletion by default (for kube-down), unless we're using 'default' network. +if [[ "${NETWORK}" == "default" ]]; then + KUBE_DELETE_NETWORK=${KUBE_DELETE_NETWORK:-false} +else + KUBE_DELETE_NETWORK=${KUBE_DELETE_NETWORK:-true} +fi if [[ "${CREATE_CUSTOM_NETWORK}" == true ]]; then SUBNETWORK="${SUBNETWORK:-${NETWORK}-custom-subnet}" fi @@ -102,11 +121,9 @@ MASTER_IP_RANGE="${MASTER_IP_RANGE:-10.246.0.0/24}" # It is the primary range in the subnet and is the range used for node instance IPs. NODE_IP_RANGE="$(get-node-ip-range)" -if [[ "${FEDERATION:-}" == true ]]; then - NODE_SCOPES="${NODE_SCOPES:-monitoring,logging-write,storage-ro,https://www.googleapis.com/auth/ndev.clouddns.readwrite}" -else - NODE_SCOPES="${NODE_SCOPES:-monitoring,logging-write,storage-ro}" -fi +# NOTE: Avoid giving nodes empty scopes, because kubelet needs a service account +# in order to initialize properly. +NODE_SCOPES="${NODE_SCOPES:-monitoring,logging-write,storage-ro}" # Extra docker options for nodes. EXTRA_DOCKER_OPTS="${EXTRA_DOCKER_OPTS:-}" @@ -136,6 +153,16 @@ ENABLE_CLUSTER_MONITORING="${KUBE_ENABLE_CLUSTER_MONITORING:-influxdb}" # TODO(piosz) remove this option once Metrics Server became a stable thing. ENABLE_METRICS_SERVER="${KUBE_ENABLE_METRICS_SERVER:-true}" +# Optional: Metadata agent to setup as part of the cluster bring up: +# none - No metadata agent +# stackdriver - Stackdriver metadata agent +# Metadata agent is a daemon set that provides metadata of kubernetes objects +# running on the same node for exporting metrics and logs. +ENABLE_METADATA_AGENT="${KUBE_ENABLE_METADATA_AGENT:-none}" + +# Version tag of metadata agent +METADATA_AGENT_VERSION="${KUBE_METADATA_AGENT_VERSION:-0.2-0.0.13-5-watch}" + # One special node out of NUM_NODES would be created of this type if specified. # Useful for scheduling heapster in large clusters with nodes of small size. HEAPSTER_MACHINE_TYPE="${HEAPSTER_MACHINE_TYPE:-}" @@ -147,18 +174,25 @@ HEAPSTER_MACHINE_TYPE="${HEAPSTER_MACHINE_TYPE:-}" # TODO(piosz): remove this in 1.8 NODE_LABELS="${KUBE_NODE_LABELS:-beta.kubernetes.io/fluentd-ds-ready=true}" +# NON_MASTER_NODE_LABELS are labels will only be applied on non-master nodes. +NON_MASTER_NODE_LABELS="${KUBE_NON_MASTER_NODE_LABELS:-}" + # To avoid running Calico on a node that is not configured appropriately, # label each Node so that the DaemonSet can run the Pods only on ready Nodes. if [[ ${NETWORK_POLICY_PROVIDER:-} == "calico" ]]; then - NODE_LABELS="${NODE_LABELS},projectcalico.org/ds-ready=true" + NON_MASTER_NODE_LABELS="${NON_MASTER_NODE_LABELS:+${NON_MASTER_NODE_LABELS},}projectcalico.org/ds-ready=true" fi -# Currently, ENABLE_METADATA_PROXY supports only "simple". In the future, we -# may add other options. -ENABLE_METADATA_PROXY="${ENABLE_METADATA_PROXY:-}" -# Apply the right node label if metadata proxy is on. -if [[ ${ENABLE_METADATA_PROXY:-} == "simple" ]]; then - NODE_LABELS="${NODE_LABELS},beta.kubernetes.io/metadata-proxy-ready=true" +# Enable metadata concealment by firewalling pod traffic to the metadata server +# and run a proxy daemonset on nodes. +# +# TODO(#8867) Enable by default. +ENABLE_METADATA_CONCEALMENT="${ENABLE_METADATA_CONCEALMENT:-false}" # true, false +if [[ ${ENABLE_METADATA_CONCEALMENT:-} == "true" ]]; then + # Put the necessary label on the node so the daemonset gets scheduled. + NODE_LABELS="${NODE_LABELS},beta.kubernetes.io/metadata-proxy-ready=true" + # Add to the provider custom variables. + PROVIDER_VARS="${PROVIDER_VARS:-} ENABLE_METADATA_CONCEALMENT" fi # Optional: Enable node logging. @@ -181,10 +215,15 @@ RUNTIME_CONFIG="${KUBE_RUNTIME_CONFIG:-}" FEATURE_GATES="${KUBE_FEATURE_GATES:-ExperimentalCriticalPodAnnotation=true}" if [[ ! -z "${NODE_ACCELERATORS}" ]]; then - FEATURE_GATES="${FEATURE_GATES},Accelerators=true" + FEATURE_GATES="${FEATURE_GATES},DevicePlugins=true" + if [[ "${NODE_ACCELERATORS}" =~ .*type=([a-zA-Z0-9-]+).* ]]; then + NODE_LABELS="${NODE_LABELS},cloud.google.com/gke-accelerator=${BASH_REMATCH[1]}" + fi fi # Optional: Install cluster DNS. +# Set CLUSTER_DNS_CORE_DNS to 'true' to install CoreDNS instead of kube-dns. +CLUSTER_DNS_CORE_DNS="${CLUSTER_DNS_CORE_DNS:-false}" ENABLE_CLUSTER_DNS="${KUBE_ENABLE_CLUSTER_DNS:-true}" DNS_SERVER_IP="${KUBE_DNS_SERVER_IP:-10.0.0.10}" DNS_DOMAIN="${KUBE_DNS_DOMAIN:-cluster.local}" @@ -244,15 +283,30 @@ if [ ${ENABLE_IP_ALIASES} = true ]; then PROVIDER_VARS="${PROVIDER_VARS:-} ENABLE_IP_ALIASES" fi - # Enable GCE Alpha features. if [[ -n "${GCE_ALPHA_FEATURES:-}" ]]; then PROVIDER_VARS="${PROVIDER_VARS:-} GCE_ALPHA_FEATURES" fi +# Disable Docker live-restore. +if [[ -n "${DISABLE_DOCKER_LIVE_RESTORE:-}" ]]; then + PROVIDER_VARS="${PROVIDER_VARS:-} DISABLE_DOCKER_LIVE_RESTORE" +fi + +# Override default GLBC image +if [[ -n "${GCE_GLBC_IMAGE:-}" ]]; then + PROVIDER_VARS="${PROVIDER_VARS:-} GCE_GLBC_IMAGE" +fi + # Admission Controllers to invoke prior to persisting objects in cluster -# If we included ResourceQuota, we should keep it at the end of the list to prevent incrementing quota usage prematurely. -ADMISSION_CONTROL=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,Priority,ResourceQuota +ADMISSION_CONTROL=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,PersistentVolumeClaimResize,DefaultTolerationSeconds,NodeRestriction,Priority,PVCProtection + +if [[ "${ENABLE_POD_SECURITY_POLICY:-}" == "true" ]]; then + ADMISSION_CONTROL="${ADMISSION_CONTROL},PodSecurityPolicy" +fi + +# ResourceQuota must come last, or a creation is recorded, but the pod was forbidden. +ADMISSION_CONTROL="${ADMISSION_CONTROL},ResourceQuota" # Optional: if set to true kube-up will automatically check for existing resources and clean them up. KUBE_UP_AUTOMATIC_CLEANUP=${KUBE_UP_AUTOMATIC_CLEANUP:-false} @@ -269,6 +323,8 @@ OPENCONTRAIL_PUBLIC_SUBNET="${OPENCONTRAIL_PUBLIC_SUBNET:-10.1.0.0/16}" # Network Policy plugin specific settings. NETWORK_POLICY_PROVIDER="${NETWORK_POLICY_PROVIDER:-none}" # calico +NON_MASQUERADE_CIDR="0.0.0.0/0" + # How should the kubelet configure hairpin mode? HAIRPIN_MODE="${HAIRPIN_MODE:-promiscuous-bridge}" # promiscuous-bridge, hairpin-veth, none # Optional: if set to true, kube-up will configure the cluster to run e2e tests. diff --git a/cluster/gce/config-test.sh b/cluster/gce/config-test.sh index f736b7147ed..124ea4b7a83 100755 --- a/cluster/gce/config-test.sh +++ b/cluster/gce/config-test.sh @@ -30,12 +30,18 @@ REGIONAL_KUBE_ADDONS=${REGIONAL_KUBE_ADDONS:-true} NODE_SIZE=${NODE_SIZE:-n1-standard-2} NUM_NODES=${NUM_NODES:-3} MASTER_SIZE=${MASTER_SIZE:-n1-standard-$(get-master-size)} +MASTER_MIN_CPU_ARCHITECTURE=${MASTER_MIN_CPU_ARCHITECTURE:-} # To allow choosing better architectures. MASTER_DISK_TYPE=pd-ssd MASTER_DISK_SIZE=${MASTER_DISK_SIZE:-$(get-master-disk-size)} MASTER_ROOT_DISK_SIZE=${MASTER_ROOT_DISK_SIZE:-$(get-master-root-disk-size)} NODE_DISK_TYPE=${NODE_DISK_TYPE:-pd-standard} NODE_DISK_SIZE=${NODE_DISK_SIZE:-100GB} NODE_LOCAL_SSDS=${NODE_LOCAL_SSDS:-0} +# An extension to local SSDs allowing users to specify block/fs and SCSI/NVMe devices +# Format of this variable will be "#,scsi/nvme,block/fs" you can specify multiple +# configurations by seperating them by a semi-colon ex. "2,scsi,fs;1,nvme,block" +# is a request for 2 SCSI formatted and mounted SSDs and 1 NVMe block device SSD. +NODE_LOCAL_SSDS_EXT=${NODE_LOCAL_SSDS_EXT:-} NODE_ACCELERATORS=${NODE_ACCELERATORS:-""} REGISTER_MASTER_KUBELET=${REGISTER_MASTER:-true} KUBE_APISERVER_REQUEST_TIMEOUT=300 @@ -73,15 +79,22 @@ fi # Also please update corresponding image for node e2e at: # https://github.com/kubernetes/kubernetes/blob/master/test/e2e_node/jenkins/image-config.yaml CVM_VERSION=${CVM_VERSION:-container-vm-v20170627} -GCI_VERSION=${KUBE_GCI_VERSION:-cos-stable-60-9592-90-0} +GCI_VERSION=${KUBE_GCI_VERSION:-cos-stable-63-10032-71-0} MASTER_IMAGE=${KUBE_GCE_MASTER_IMAGE:-} MASTER_IMAGE_PROJECT=${KUBE_GCE_MASTER_PROJECT:-cos-cloud} NODE_IMAGE=${KUBE_GCE_NODE_IMAGE:-${GCI_VERSION}} NODE_IMAGE_PROJECT=${KUBE_GCE_NODE_PROJECT:-cos-cloud} +NODE_SERVICE_ACCOUNT=${KUBE_GCE_NODE_SERVICE_ACCOUNT:-default} CONTAINER_RUNTIME=${KUBE_CONTAINER_RUNTIME:-docker} +CONTAINER_RUNTIME_ENDPOINT=${KUBE_CONTAINER_RUNTIME_ENDPOINT:-} +LOAD_IMAGE_COMMAND=${KUBE_LOAD_IMAGE_COMMAND:-docker load -i} GCI_DOCKER_VERSION=${KUBE_GCI_DOCKER_VERSION:-} RKT_VERSION=${KUBE_RKT_VERSION:-1.23.0} RKT_STAGE1_IMAGE=${KUBE_RKT_STAGE1_IMAGE:-coreos.com/rkt/stage1-coreos} +# MASTER_EXTRA_METADATA is the extra instance metadata on master instance separated by commas. +MASTER_EXTRA_METADATA=${KUBE_MASTER_EXTRA_METADATA:-${KUBE_EXTRA_METADATA:-}} +# MASTER_EXTRA_METADATA is the extra instance metadata on node instance separated by commas. +NODE_EXTRA_METADATA=${KUBE_NODE_EXTRA_METADATA:-${KUBE_EXTRA_METADATA:-}} NETWORK=${KUBE_GCE_NETWORK:-e2e-test-${USER}} if [[ "${CREATE_CUSTOM_NETWORK}" == true ]]; then @@ -107,10 +120,6 @@ RUNTIME_CONFIG="${KUBE_RUNTIME_CONFIG:-}" # Optional: set feature gates FEATURE_GATES="${KUBE_FEATURE_GATES:-ExperimentalCriticalPodAnnotation=true}" -if [[ ! -z "${NODE_ACCELERATORS}" ]]; then - FEATURE_GATES="${FEATURE_GATES},Accelerators=true" -fi - TERMINATED_POD_GC_THRESHOLD=${TERMINATED_POD_GC_THRESHOLD:-100} # Extra docker options for nodes. @@ -198,15 +207,23 @@ KUBEPROXY_TEST_ARGS="${KUBEPROXY_TEST_ARGS:-} ${TEST_CLUSTER_API_CONTENT_TYPE}" # TODO(piosz): remove this in 1.8 NODE_LABELS="${KUBE_NODE_LABELS:-beta.kubernetes.io/fluentd-ds-ready=true}" +# NON_MASTER_NODE_LABELS are labels will only be applied on non-master nodes. +NON_MASTER_NODE_LABELS="${KUBE_NON_MASTER_NODE_LABELS:-}" + # To avoid running Calico on a node that is not configured appropriately, # label each Node so that the DaemonSet can run the Pods only on ready Nodes. if [[ ${NETWORK_POLICY_PROVIDER:-} == "calico" ]]; then - NODE_LABELS="$NODE_LABELS,projectcalico.org/ds-ready=true" + NON_MASTER_NODE_LABELS="${NON_MASTER_NODE_LABELS:+${NON_MASTER_NODE_LABELS},}projectcalico.org/ds-ready=true" fi -# Apply the right node label if metadata proxy is on. -if [[ ${ENABLE_METADATA_PROXY:-} == "simple" ]]; then - NODE_LABELS="${NODE_LABELS},beta.kubernetes.io/metadata-proxy-ready=true" +# Enable metadata concealment by firewalling pod traffic to the metadata server +# and run a proxy daemonset on nodes. +ENABLE_METADATA_CONCEALMENT="${ENABLE_METADATA_CONCEALMENT:-true}" # true, false +if [[ ${ENABLE_METADATA_CONCEALMENT:-} == "true" ]]; then + # Put the necessary label on the node so the daemonset gets scheduled. + NODE_LABELS="${NODE_LABELS},beta.kubernetes.io/metadata-proxy-ready=true" + # Add to the provider custom variables. + PROVIDER_VARS="${PROVIDER_VARS:-} ENABLE_METADATA_CONCEALMENT" fi # Optional: Enable node logging. @@ -222,7 +239,16 @@ if [[ ${KUBE_ENABLE_INSECURE_REGISTRY:-false} == "true" ]]; then EXTRA_DOCKER_OPTS="${EXTRA_DOCKER_OPTS} --insecure-registry 10.0.0.0/8" fi +if [[ ! -z "${NODE_ACCELERATORS}" ]]; then + FEATURE_GATES="${FEATURE_GATES},DevicePlugins=true" + if [[ "${NODE_ACCELERATORS}" =~ .*type=([a-zA-Z0-9-]+).* ]]; then + NODE_LABELS="${NODE_LABELS},cloud.google.com/gke-accelerator=${BASH_REMATCH[1]}" + fi +fi + # Optional: Install cluster DNS. +# Set CLUSTER_DNS_CORE_DNS to 'true' to install CoreDNS instead of kube-dns. +CLUSTER_DNS_CORE_DNS="${CLUSTER_DNS_CORE_DNS:-false}" ENABLE_CLUSTER_DNS="${KUBE_ENABLE_CLUSTER_DNS:-true}" DNS_SERVER_IP="10.0.0.10" DNS_DOMAIN="cluster.local" @@ -287,8 +313,26 @@ if [[ -n "${GCE_ALPHA_FEATURES:-}" ]]; then PROVIDER_VARS="${PROVIDER_VARS:-} GCE_ALPHA_FEATURES" fi -# If we included ResourceQuota, we should keep it at the end of the list to prevent incrementing quota usage prematurely. -ADMISSION_CONTROL="${KUBE_ADMISSION_CONTROL:-Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,PodPreset,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,Priority,ResourceQuota}" +# Disable Docker live-restore. +if [[ -n "${DISABLE_DOCKER_LIVE_RESTORE:-}" ]]; then + PROVIDER_VARS="${PROVIDER_VARS:-} DISABLE_DOCKER_LIVE_RESTORE" +fi + +# Override default GLBC image +if [[ -n "${GCE_GLBC_IMAGE:-}" ]]; then + PROVIDER_VARS="${PROVIDER_VARS:-} GCE_GLBC_IMAGE" +fi + +if [[ -z "${KUBE_ADMISSION_CONTROL:-}" ]]; then + ADMISSION_CONTROL="Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,PodPreset,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,Priority" + if [[ "${ENABLE_POD_SECURITY_POLICY:-}" == "true" ]]; then + ADMISSION_CONTROL="${ADMISSION_CONTROL},PodSecurityPolicy" + fi + # ResourceQuota must come last, or a creation is recorded, but the pod may be forbidden. + ADMISSION_CONTROL="${ADMISSION_CONTROL},MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota" +else + ADMISSION_CONTROL=${KUBE_ADMISSION_CONTROL} +fi # Optional: if set to true kube-up will automatically check for existing resources and clean them up. KUBE_UP_AUTOMATIC_CLEANUP=${KUBE_UP_AUTOMATIC_CLEANUP:-false} @@ -313,6 +357,8 @@ OPENCONTRAIL_PUBLIC_SUBNET="${OPENCONTRAIL_PUBLIC_SUBNET:-10.1.0.0/16}" # Network Policy plugin specific settings. NETWORK_POLICY_PROVIDER="${NETWORK_POLICY_PROVIDER:-none}" # calico +NON_MASQUERADE_CIDR="0.0.0.0/0" + # How should the kubelet configure hairpin mode? HAIRPIN_MODE="${HAIRPIN_MODE:-promiscuous-bridge}" # promiscuous-bridge, hairpin-veth, none diff --git a/cluster/gce/configure-vm.sh b/cluster/gce/configure-vm.sh index 6d96a5d9554..c1b66bab0e3 100755 --- a/cluster/gce/configure-vm.sh +++ b/cluster/gce/configure-vm.sh @@ -66,10 +66,6 @@ function create-node-pki { KUBELET_KEY_PATH="${pki_dir}/kubelet.key" echo "${KUBELET_KEY}" | base64 --decode > "${KUBELET_KEY_PATH}" fi - - # TODO(mikedanese): remove this when we don't support downgrading to versions - # < 1.6. - ln -sf "${CA_CERT_BUNDLE_PATH}" /etc/kubernetes/ca.crt } # A hookpoint for setting up local devices @@ -90,11 +86,41 @@ ensure-local-disks() { function config-ip-firewall { echo "Configuring IP firewall rules" - iptables -N KUBE-METADATA-SERVER - iptables -I FORWARD -p tcp -d 169.254.169.254 --dport 80 -j KUBE-METADATA-SERVER + # Do not consider loopback addresses as martian source or destination while + # routing. This enables the use of 127/8 for local routing purposes. + sysctl -w net.ipv4.conf.all.route_localnet=1 - if [[ -n "${KUBE_FIREWALL_METADATA_SERVER:-}" ]]; then - iptables -A KUBE-METADATA-SERVER -j DROP + # We need to add rules to accept all TCP/UDP/ICMP packets. + if iptables -L INPUT | grep "Chain INPUT (policy DROP)" > /dev/null; then + echo "Add rules to accept all inbound TCP/UDP/ICMP packets" + iptables -A INPUT -p TCP -j ACCEPT + iptables -A INPUT -p UDP -j ACCEPT + iptables -A INPUT -p ICMP -j ACCEPT + fi + if iptables -L FORWARD | grep "Chain FORWARD (policy DROP)" > /dev/null; then + echo "Add rules to accept all forwarded TCP/UDP/ICMP packets" + iptables -A FORWARD -p TCP -j ACCEPT + iptables -A FORWARD -p UDP -j ACCEPT + iptables -A FORWARD -p ICMP -j ACCEPT + fi + + # Flush iptables nat table + iptables -t nat -F || true + + if [[ "${NON_MASQUERADE_CIDR:-}" == "0.0.0.0/0" ]]; then + echo "Add rules for ip masquerade" + iptables -t nat -N IP-MASQ + iptables -t nat -A POSTROUTING -m comment --comment "ip-masq: ensure nat POSTROUTING directs all non-LOCAL destination traffic to our custom IP-MASQ chain" -m addrtype ! --dst-type LOCAL -j IP-MASQ + iptables -t nat -A IP-MASQ -d 169.254.0.0/16 -m comment --comment "ip-masq: local traffic is not subject to MASQUERADE" -j RETURN + iptables -t nat -A IP-MASQ -d 10.0.0.0/8 -m comment --comment "ip-masq: local traffic is not subject to MASQUERADE" -j RETURN + iptables -t nat -A IP-MASQ -d 172.16.0.0/12 -m comment --comment "ip-masq: local traffic is not subject to MASQUERADE" -j RETURN + iptables -t nat -A IP-MASQ -d 192.168.0.0/16 -m comment --comment "ip-masq: local traffic is not subject to MASQUERADE" -j RETURN + iptables -t nat -A IP-MASQ -m comment --comment "ip-masq: outbound traffic is subject to MASQUERADE (must be last in chain)" -j MASQUERADE + fi + + if [[ "${ENABLE_METADATA_CONCEALMENT:-}" == "true" ]]; then + echo "Add rule for metadata concealment" + iptables -t nat -I PREROUTING -p tcp -d 169.254.169.254 --dport 80 -m comment --comment "metadata-concealment: bridge traffic to metadata server goes to metadata proxy" -j DNAT --to-destination 127.0.0.1:988 fi } @@ -155,6 +181,7 @@ function curl-metadata() { } function set-kube-env() { + (umask 700; local kube_env_yaml="${INSTALL_DIR}/kube_env.yaml" until curl-metadata kube-env > "${kube_env_yaml}"; do @@ -170,6 +197,7 @@ for k,v in yaml.load(sys.stdin).iteritems(): print("""readonly {var}={value}""".format(var = k, value = pipes.quote(str(v)))) print("""export {var}""".format(var = k)) ' < """${kube_env_yaml}""")" + ) } function remove-docker-artifacts() { @@ -177,7 +205,6 @@ function remove-docker-artifacts() { apt-get-install bridge-utils # Remove docker artifacts on minion nodes, if present - iptables -t nat -F || true ifconfig docker0 down || true brctl delbr docker0 || true echo "== Finished deleting docker0 ==" @@ -419,12 +446,14 @@ enable_cluster_ui: '$(echo "$ENABLE_CLUSTER_UI" | sed -e "s/'/''/g")' enable_node_problem_detector: '$(echo "$ENABLE_NODE_PROBLEM_DETECTOR" | sed -e "s/'/''/g")' enable_l7_loadbalancing: '$(echo "$ENABLE_L7_LOADBALANCING" | sed -e "s/'/''/g")' enable_node_logging: '$(echo "$ENABLE_NODE_LOGGING" | sed -e "s/'/''/g")' -enable_metadata_proxy: '$(echo "$ENABLE_METADATA_PROXY" | sed -e "s/'/''/g")' +enable_metadata_proxy: '$(echo "$ENABLE_METADATA_CONCEALMENT" | sed -e "s/'/''/g")' enable_metrics_server: '$(echo "$ENABLE_METRICS_SERVER" | sed -e "s/'/''/g")' +enable_pod_security_policy: '$(echo "$ENABLE_POD_SECURITY_POLICY" | sed -e "s/'/''/g")' enable_rescheduler: '$(echo "$ENABLE_RESCHEDULER" | sed -e "s/'/''/g")' logging_destination: '$(echo "$LOGGING_DESTINATION" | sed -e "s/'/''/g")' elasticsearch_replicas: '$(echo "$ELASTICSEARCH_LOGGING_REPLICAS" | sed -e "s/'/''/g")' enable_cluster_dns: '$(echo "$ENABLE_CLUSTER_DNS" | sed -e "s/'/''/g")' +cluster_dns_core_dns: '$(echo "$CLUSTER_DNS_CORE_DNS" | sed -e "s/'/''/g")' enable_cluster_registry: '$(echo "$ENABLE_CLUSTER_REGISTRY" | sed -e "s/'/''/g")' dns_server: '$(echo "$DNS_SERVER_IP" | sed -e "s/'/''/g")' dns_domain: '$(echo "$DNS_DOMAIN" | sed -e "s/'/''/g")' @@ -447,7 +476,7 @@ kube_uid: '$(echo "${KUBE_UID}" | sed -e "s/'/''/g")' initial_etcd_cluster: '$(echo "${INITIAL_ETCD_CLUSTER:-}" | sed -e "s/'/''/g")' initial_etcd_cluster_state: '$(echo "${INITIAL_ETCD_CLUSTER_STATE:-}" | sed -e "s/'/''/g")' ca_cert_bundle_path: '$(echo "${CA_CERT_BUNDLE_PATH:-}" | sed -e "s/'/''/g")' -hostname: $(hostname -s) +hostname: '$(echo "${ETCD_HOSTNAME:-$(hostname -s)}" | sed -e "s/'/''/g")' enable_pod_priority: '$(echo "${ENABLE_POD_PRIORITY:-}" | sed -e "s/'/''/g")' enable_default_storage_class: '$(echo "$ENABLE_DEFAULT_STORAGE_CLASS" | sed -e "s/'/''/g")' kube_proxy_daemonset: '$(echo "$KUBE_PROXY_DAEMONSET" | sed -e "s/'/''/g")' @@ -465,6 +494,16 @@ EOF if [ -n "${KUBE_APISERVER_REQUEST_TIMEOUT_SEC:-}" ]; then cat <>/srv/salt-overlay/pillar/cluster-params.sls kube_apiserver_request_timeout_sec: '$(echo "$KUBE_APISERVER_REQUEST_TIMEOUT_SEC" | sed -e "s/'/''/g")' +EOF + fi + if [ -n "${ETCD_LIVENESS_PROBE_INITIAL_DELAY_SEC:-}" ]; then + cat <>/srv/salt-overlay/pillar/cluster-params.sls +etcd_liveness_probe_initial_delay: '$(echo "$ETCD_LIVENESS_PROBE_INITIAL_DELAY_SEC" | sed -e "s/'/''/g")' +EOF + fi + if [ -n "${KUBE_APISERVER_LIVENESS_PROBE_INITIAL_DELAY_SEC:-}" ]; then + cat <>/srv/salt-overlay/pillar/cluster-params.sls +kube_apiserver_liveness_probe_initial_delay: '$(echo "$KUBE_APISERVER_LIVENESS_PROBE_INITIAL_DELAY_SEC" | sed -e "s/'/''/g")' EOF fi if [ -n "${ADMISSION_CONTROL:-}" ] && [ ${ADMISSION_CONTROL} == *"ImagePolicyWebhook"* ]; then @@ -583,6 +622,11 @@ EOF if [ -n "${NODE_LABELS:-}" ]; then cat <>/srv/salt-overlay/pillar/cluster-params.sls node_labels: '$(echo "${NODE_LABELS}" | sed -e "s/'/''/g")' +EOF + fi + if [ -n "${NON_MASTER_NODE_LABELS:-}" ]; then + cat <>/srv/salt-overlay/pillar/cluster-params.sls +non_master_node_labels: '$(echo "${NON_MASTER_NODE_LABELS}" | sed -e "s/'/''/g")' EOF fi if [ -n "${NODE_TAINTS:-}" ]; then @@ -853,7 +897,6 @@ fi if [[ -z "${is_push}" ]]; then echo "== kube-up node config starting ==" set-broken-motd - config-ip-firewall ensure-basic-networking fix-apt-sources ensure-install-dir @@ -870,6 +913,7 @@ if [[ -z "${is_push}" ]]; then download-release configure-salt remove-docker-artifacts + config-ip-firewall run-salt reset-motd diff --git a/cluster/gce/container-linux/configure-helper.sh b/cluster/gce/container-linux/configure-helper.sh index 1d4b5403374..90f677847e8 100755 --- a/cluster/gce/container-linux/configure-helper.sh +++ b/cluster/gce/container-linux/configure-helper.sh @@ -25,6 +25,12 @@ set -o errexit set -o nounset set -o pipefail +# Use --retry-connrefused opt only if it's supported by curl. +CURL_RETRY_CONNREFUSED="" +if curl --help | grep -q -- '--retry-connrefused'; then + CURL_RETRY_CONNREFUSED='--retry-connrefused' +fi + function create-dirs { echo "Creating required directories" mkdir -p /var/lib/kubelet @@ -215,14 +221,19 @@ EOF if [[ -n "${NODE_INSTANCE_PREFIX:-}" ]]; then use_cloud_config="true" if [[ -n "${NODE_TAGS:-}" ]]; then - local -r node_tags="${NODE_TAGS}" + # split NODE_TAGS into an array by comma. + IFS=',' read -r -a node_tags <<< ${NODE_TAGS} else local -r node_tags="${NODE_INSTANCE_PREFIX}" fi cat <>/etc/gce.conf -node-tags = ${node_tags} node-instance-prefix = ${NODE_INSTANCE_PREFIX} EOF + for tag in ${node_tags[@]}; do + cat <>/etc/gce.conf +node-tags = ${tag} +EOF + done fi if [[ -n "${MULTIZONE:-}" ]]; then use_cloud_config="true" @@ -232,14 +243,18 @@ EOF fi if [[ -n "${GCE_ALPHA_FEATURES:-}" ]]; then use_cloud_config="true" - cat <>/etc/gce.conf -alpha-features = ${GCE_ALPHA_FEATURES} + # split GCE_ALPHA_FEATURES into an array by comma. + IFS=',' read -r -a alpha_features <<< ${GCE_ALPHA_FEATURES} + for feature in ${alpha_features[@]}; do + cat <>/etc/gce.conf +alpha-features = ${feature} EOF + done fi if [[ -n "${SECONDARY_RANGE_NAME:-}" ]]; then use_cloud_config="true" cat <> /etc/gce.conf -secondary-range-name = ${SECONDARY-RANGE-NAME} +secondary-range-name = ${SECONDARY_RANGE_NAME} EOF fi if [[ "${use_cloud_config}" != "true" ]]; then @@ -598,6 +613,9 @@ function start-kubelet { if [[ -n "${NODE_LABELS:-}" ]]; then node_labels="${node_labels:+${node_labels},}${NODE_LABELS}" fi + if [[ -n "${NON_MASTER_NODE_LABELS:-}" && "${KUBERNETES_MASTER:-}" != "true" ]]; then + node_labels="${node_labels:+${node_labels},}${NON_MASTER_NODE_LABELS}" + fi if [[ -n "${node_labels:-}" ]]; then flags+=" --node-labels=${node_labels}" fi @@ -730,7 +748,7 @@ function start-kube-proxy { # $4: value for variable 'cpulimit' # $5: pod name, which should be either etcd or etcd-events function prepare-etcd-manifest { - local host_name=$(hostname -s) + local host_name=${ETCD_HOSTNAME:-$(hostname -s)} local etcd_cluster="" local cluster_state="new" local etcd_protocol="http" @@ -760,6 +778,7 @@ function prepare-etcd-manifest { sed -i -e "s@{{ *hostname *}}@$host_name@g" "${temp_file}" sed -i -e "s@{{ *srv_kube_path *}}@/etc/srv/kubernetes@g" "${temp_file}" sed -i -e "s@{{ *etcd_cluster *}}@$etcd_cluster@g" "${temp_file}" + sed -i -e "s@{{ *liveness_probe_initial_delay *}}@${ETCD_LIVENESS_PROBE_INITIAL_DELAY_SEC:-15}@g" "${temp_file}" # Get default storage backend from manifest file. local -r default_storage_backend=$(cat "${temp_file}" | \ grep -o "{{ *pillar\.get('storage_backend', '\(.*\)') *}}" | \ @@ -963,10 +982,14 @@ function start-kube-apiserver { params+=" --feature-gates=${FEATURE_GATES}" fi if [[ -n "${PROJECT_ID:-}" && -n "${TOKEN_URL:-}" && -n "${TOKEN_BODY:-}" && -n "${NODE_NETWORK:-}" ]]; then - local -r vm_external_ip=$(curl --retry 5 --retry-delay 3 --fail --silent -H 'Metadata-Flavor: Google' "http://metadata/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip") - params+=" --advertise-address=${vm_external_ip}" - params+=" --ssh-user=${PROXY_SSH_USER}" - params+=" --ssh-keyfile=/etc/srv/sshproxy/.sshkeyfile" + local -r vm_external_ip=$(curl --retry 5 --retry-delay 3 ${CURL_RETRY_CONNREFUSED} --fail --silent -H 'Metadata-Flavor: Google' "http://metadata/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip") + if [[ -n "${PROXY_SSH_USER:-}" ]]; then + params+=" --advertise-address=${vm_external_ip}" + params+=" --ssh-user=${PROXY_SSH_USER}" + params+=" --ssh-keyfile=/etc/srv/sshproxy/.sshkeyfile" + else + params+=" --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname", + fi elif [ -n "${MASTER_ADVERTISE_ADDRESS:-}" ]; then params="${params} --advertise-address=${MASTER_ADVERTISE_ADDRESS}" fi @@ -1038,6 +1061,7 @@ function start-kube-apiserver { sed -i -e "s@{{pillar\['kube_docker_registry'\]}}@${DOCKER_REGISTRY}@g" "${src_file}" sed -i -e "s@{{pillar\['kube-apiserver_docker_tag'\]}}@${kube_apiserver_docker_tag}@g" "${src_file}" sed -i -e "s@{{pillar\['allow_privileged'\]}}@true@g" "${src_file}" + sed -i -e "s@{{liveness_probe_initial_delay}}@${KUBE_APISERVER_LIVENESS_PROBE_INITIAL_DELAY_SEC:-15}@g" "${src_file}" sed -i -e "s@{{secure_port}}@443@g" "${src_file}" sed -i -e "s@{{secure_port}}@8080@g" "${src_file}" sed -i -e "s@{{additional_cloud_config_mount}}@@g" "${src_file}" @@ -1221,6 +1245,36 @@ function update-prometheus-to-sd-parameters { fi } +# Sets up the manifests of coreDNS for k8s addons. +function setup-coredns-manifest { + local -r coredns_file="${dst_dir}/dns/coredns.yaml" + mv "${dst_dir}/dns/coredns.yaml.in" "${coredns_file}" + # Replace the salt configurations with variable values. + sed -i -e "s@{{ *pillar\['dns_domain'\] *}}@${DNS_DOMAIN}@g" "${coredns_file}" + sed -i -e "s@{{ *pillar\['dns_server'\] *}}@${DNS_SERVER_IP}@g" "${coredns_file}" + sed -i -e "s@{{ *pillar\['service_cluster_ip_range'\] *}}@${SERVICE_CLUSTER_IP_RANGE}@g" "${coredns_file}" +} + +# Sets up the manifests of kube-dns for k8s addons. +function setup-kube-dns-manifest { + local -r kubedns_file="${dst_dir}/dns/kube-dns.yaml" + mv "${dst_dir}/dns/kube-dns.yaml.in" "${kubedns_file}" + if [ -n "${CUSTOM_KUBE_DNS_YAML:-}" ]; then + # Replace with custom GKE kube-dns deployment. + cat > "${kubedns_file}" < "$src_dir/kube-proxy/kube-proxy-ds.yaml" <&1); then echo "${result}" >&2 diff --git a/cluster/gce/container-linux/master.yaml b/cluster/gce/container-linux/master.yaml index 4dec695c9d7..444d3042739 100644 --- a/cluster/gce/container-linux/master.yaml +++ b/cluster/gce/container-linux/master.yaml @@ -17,7 +17,8 @@ coreos: Type=oneshot RemainAfterExit=yes ExecStartPre=/bin/mkdir -p /opt/kubernetes/bin - ExecStartPre=/usr/bin/curl --fail --retry 5 --retry-delay 3 --silent --show-error -H "X-Google-Metadata-Request: True" -o /opt/kubernetes/bin/configure.sh http://metadata.google.internal/computeMetadata/v1/instance/attributes/configure-sh + # Use --retry-connrefused opt only if it's supported by curl. + ExecStartPre=/bin/bash -c 'OPT=""; if curl --help | grep -q -- "--retry-connrefused"; then OPT="--retry-connrefused"; fi; /usr/bin/curl --fail --retry 5 --retry-delay 3 $OPT --silent --show-error -H "X-Google-Metadata-Request: True" -o /opt/kubernetes/bin/configure.sh http://metadata.google.internal/computeMetadata/v1/instance/attributes/configure-sh' ExecStartPre=/bin/chmod 544 /opt/kubernetes/bin/configure.sh ExecStart=/opt/kubernetes/bin/configure.sh diff --git a/cluster/gce/container-linux/node.yaml b/cluster/gce/container-linux/node.yaml index b203c4fded3..9886679cd78 100644 --- a/cluster/gce/container-linux/node.yaml +++ b/cluster/gce/container-linux/node.yaml @@ -17,7 +17,8 @@ coreos: Type=oneshot RemainAfterExit=yes ExecStartPre=/bin/mkdir -p /opt/kubernetes/bin - ExecStartPre=/usr/bin/curl --fail --retry 5 --retry-delay 3 --silent --show-error -H "X-Google-Metadata-Request: True" -o /opt/kubernetes/bin/configure.sh http://metadata.google.internal/computeMetadata/v1/instance/attributes/configure-sh + # Use --retry-connrefused opt only if it's supported by curl. + ExecStartPre=/bin/bash -c 'OPT=""; if curl --help | grep -q -- "--retry-connrefused"; then OPT="--retry-connrefused"; fi; /usr/bin/curl --fail --retry 5 --retry-delay 3 $OPT --silent --show-error -H "X-Google-Metadata-Request: True" -o /opt/kubernetes/bin/configure.sh http://metadata.google.internal/computeMetadata/v1/instance/attributes/configure-sh' ExecStartPre=/bin/chmod 544 /opt/kubernetes/bin/configure.sh ExecStart=/opt/kubernetes/bin/configure.sh diff --git a/cluster/gce/gci/configure-helper.sh b/cluster/gce/gci/configure-helper.sh index e0132d3145e..a10ffe2f312 100644 --- a/cluster/gce/gci/configure-helper.sh +++ b/cluster/gce/gci/configure-helper.sh @@ -25,6 +25,15 @@ set -o errexit set -o nounset set -o pipefail +readonly UUID_MNT_PREFIX="/mnt/disks/by-uuid/google-local-ssds" +readonly UUID_BLOCK_PREFIX="/dev/disk/by-uuid/google-local-ssds" + +# Use --retry-connrefused opt only if it's supported by curl. +CURL_RETRY_CONNREFUSED="" +if curl --help | grep -q -- '--retry-connrefused'; then + CURL_RETRY_CONNREFUSED='--retry-connrefused' +fi + function setup-os-params { # Reset core_pattern. On GCI, the default core_pattern pipes the core dumps to # /sbin/crash_reporter which is more restrictive in saving crash dumps. So for @@ -34,26 +43,43 @@ function setup-os-params { function config-ip-firewall { echo "Configuring IP firewall rules" + + # Do not consider loopback addresses as martian source or destination while + # routing. This enables the use of 127/8 for local routing purposes. + sysctl -w net.ipv4.conf.all.route_localnet=1 + # The GCI image has host firewall which drop most inbound/forwarded packets. # We need to add rules to accept all TCP/UDP/ICMP packets. - if iptables -L INPUT | grep "Chain INPUT (policy DROP)" > /dev/null; then + if iptables -w -L INPUT | grep "Chain INPUT (policy DROP)" > /dev/null; then echo "Add rules to accept all inbound TCP/UDP/ICMP packets" iptables -A INPUT -w -p TCP -j ACCEPT iptables -A INPUT -w -p UDP -j ACCEPT iptables -A INPUT -w -p ICMP -j ACCEPT fi - if iptables -L FORWARD | grep "Chain FORWARD (policy DROP)" > /dev/null; then + if iptables -w -L FORWARD | grep "Chain FORWARD (policy DROP)" > /dev/null; then echo "Add rules to accept all forwarded TCP/UDP/ICMP packets" iptables -A FORWARD -w -p TCP -j ACCEPT iptables -A FORWARD -w -p UDP -j ACCEPT iptables -A FORWARD -w -p ICMP -j ACCEPT fi - iptables -N KUBE-METADATA-SERVER - iptables -I FORWARD -p tcp -d 169.254.169.254 --dport 80 -j KUBE-METADATA-SERVER + # Flush iptables nat table + iptables -w -t nat -F || true - if [[ -n "${KUBE_FIREWALL_METADATA_SERVER:-}" ]]; then - iptables -A KUBE-METADATA-SERVER -j DROP + if [[ "${NON_MASQUERADE_CIDR:-}" == "0.0.0.0/0" ]]; then + echo "Add rules for ip masquerade" + iptables -w -t nat -N IP-MASQ + iptables -w -t nat -A POSTROUTING -m comment --comment "ip-masq: ensure nat POSTROUTING directs all non-LOCAL destination traffic to our custom IP-MASQ chain" -m addrtype ! --dst-type LOCAL -j IP-MASQ + iptables -w -t nat -A IP-MASQ -d 169.254.0.0/16 -m comment --comment "ip-masq: local traffic is not subject to MASQUERADE" -j RETURN + iptables -w -t nat -A IP-MASQ -d 10.0.0.0/8 -m comment --comment "ip-masq: local traffic is not subject to MASQUERADE" -j RETURN + iptables -w -t nat -A IP-MASQ -d 172.16.0.0/12 -m comment --comment "ip-masq: local traffic is not subject to MASQUERADE" -j RETURN + iptables -w -t nat -A IP-MASQ -d 192.168.0.0/16 -m comment --comment "ip-masq: local traffic is not subject to MASQUERADE" -j RETURN + iptables -w -t nat -A IP-MASQ -m comment --comment "ip-masq: outbound traffic is subject to MASQUERADE (must be last in chain)" -j MASQUERADE + fi + + if [[ "${ENABLE_METADATA_CONCEALMENT:-}" == "true" ]]; then + echo "Add rule for metadata concealment" + iptables -w -t nat -I PREROUTING -p tcp -d 169.254.169.254 --dport 80 -m comment --comment "metadata-concealment: bridge traffic to metadata server goes to metadata proxy" -j DNAT --to-destination 127.0.0.1:988 fi } @@ -66,11 +92,85 @@ function create-dirs { fi } -# Formats the given device ($1) if needed and mounts it at given mount point +# Gets the total number of $(1) and $(2) type disks specified +# by the user in ${NODE_LOCAL_SSDS_EXT} +function get-local-disk-num() { + local interface="${1}" + local format="${2}" + + localdisknum=0 + if [[ ! -z "${NODE_LOCAL_SSDS_EXT:-}" ]]; then + IFS=";" read -r -a ssdgroups <<< "${NODE_LOCAL_SSDS_EXT:-}" + for ssdgroup in "${ssdgroups[@]}"; do + IFS="," read -r -a ssdopts <<< "${ssdgroup}" + local opnum="${ssdopts[0]}" + local opinterface="${ssdopts[1]}" + local opformat="${ssdopts[2]}" + + if [[ "${opformat,,}" == "${format,,}" && "${opinterface,,}" == "${interface,,}" ]]; then + localdisknum=$((localdisknum+opnum)) + fi + done + fi +} + +# Creates a symlink for a ($1) so that it may be used as block storage +function safe-block-symlink(){ + local device="${1}" + local symdir="${2}" + + mkdir -p "${symdir}" + + get-or-generate-uuid "${device}" + local myuuid="${retuuid}" + + local sym="${symdir}/local-ssd-${myuuid}" + # Do not "mkdir -p ${sym}" as that will cause unintended symlink behavior + ln -s "${device}" "${sym}" + echo "Created a symlink for SSD $ssd at ${sym}" + chmod a+w "${sym}" +} + +# Gets a pregenerated UUID from ${ssdmap} if it exists, otherwise generates a new +# UUID and places it inside ${ssdmap} +function get-or-generate-uuid(){ + local device="${1}" + + local ssdmap="/home/kubernetes/localssdmap.txt" + echo "Generating or getting UUID from ${ssdmap}" + + if [[ ! -e "${ssdmap}" ]]; then + touch "${ssdmap}" + chmod +w "${ssdmap}" + fi + + # each line of the ssdmap looks like "${device} persistent-uuid" + if [[ ! -z $(grep ${device} ${ssdmap}) ]]; then + #create symlink based on saved uuid + local myuuid=$(grep ${device} ${ssdmap} | cut -d ' ' -f 2) + else + # generate new uuid and add it to the map + local myuuid=$(uuidgen) + if [[ ! ${?} -eq 0 ]]; then + echo "Failed to generate valid UUID with uuidgen" >&2 + exit 2 + fi + echo "${device} ${myuuid}" >> "${ssdmap}" + fi + + if [[ -z "${myuuid}" ]]; then + echo "Failed to get a uuid for device ${device} when symlinking." >&2 + exit 2 + fi + + retuuid="${myuuid}" +} + +#Formats the given device ($1) if needed and mounts it at given mount point # ($2). function safe-format-and-mount() { - device=$1 - mountpoint=$2 + local device="${1}" + local mountpoint="${2}" # Format only if the disk is not already formatted. if ! tune2fs -l "${device}" ; then @@ -83,18 +183,135 @@ function safe-format-and-mount() { mount -o discard,defaults "${device}" "${mountpoint}" } -# Local ssds, if present, are mounted at /mnt/disks/ssdN. +# Gets a devices UUID and bind mounts the device to mount location in +# /mnt/disks/by-id/ +function unique-uuid-bind-mount(){ + local mountpoint="${1}" + local actual_device="${2}" + + # Trigger udev refresh so that newly formatted devices are propagated in by-uuid + udevadm control --reload-rules + udevadm trigger + udevadm settle + + # grep the exact match of actual device, prevents substring matching + local myuuid=$(ls -l /dev/disk/by-uuid/ | grep "/${actual_device}$" | tr -s ' ' | cut -d ' ' -f 9) + # myuuid should be the uuid of the device as found in /dev/disk/by-uuid/ + if [[ -z "${myuuid}" ]]; then + echo "Failed to get a uuid for device ${actual_device} when mounting." >&2 + exit 2 + fi + + # bindpoint should be the full path of the to-be-bound device + local bindpoint="${UUID_MNT_PREFIX}-${interface}-fs/local-ssd-${myuuid}" + + safe-bind-mount "${mountpoint}" "${bindpoint}" +} + +# Bind mounts device at mountpoint to bindpoint +function safe-bind-mount(){ + local mountpoint="${1}" + local bindpoint="${2}" + + # Mount device to the mountpoint + mkdir -p "${bindpoint}" + echo "Binding '${mountpoint}' at '${bindpoint}'" + mount --bind "${mountpoint}" "${bindpoint}" + chmod a+w "${bindpoint}" +} + + +# Mounts, bindmounts, or symlinks depending on the interface and format +# of the incoming device +function mount-ext(){ + local ssd="${1}" + local devicenum="${2}" + local interface="${3}" + local format="${4}" + + + if [[ -z "${devicenum}" ]]; then + echo "Failed to get the local disk number for device ${ssd}" >&2 + exit 2 + fi + + # TODO: Handle partitioned disks. Right now this code just ignores partitions + if [[ "${format}" == "fs" ]]; then + if [[ "${interface}" == "scsi" ]]; then + local actual_device=$(readlink -f "${ssd}" | cut -d '/' -f 3) + # Error checking + if [[ "${actual_device}" != sd* ]]; then + echo "'actual_device' is not of the correct format. It must be the kernel name of the device, got ${actual_device} instead" >&2 + exit 1 + fi + local mountpoint="/mnt/disks/ssd${devicenum}" + else + # This path is required because the existing Google images do not + # expose NVMe devices in /dev/disk/by-id so we are using the /dev/nvme instead + local actual_device=$(echo ${ssd} | cut -d '/' -f 3) + # Error checking + if [[ "${actual_device}" != nvme* ]]; then + echo "'actual_device' is not of the correct format. It must be the kernel name of the device, got ${actual_device} instead" >&2 + exit 1 + fi + local mountpoint="/mnt/disks/ssd-nvme${devicenum}" + fi + + safe-format-and-mount "${ssd}" "${mountpoint}" + # We only do the bindmount if users are using the new local ssd request method + # see https://github.com/kubernetes/kubernetes/pull/53466#discussion_r146431894 + if [[ ! -z "${NODE_LOCAL_SSDS_EXT:-}" ]]; then + unique-uuid-bind-mount "${mountpoint}" "${actual_device}" + fi + elif [[ "${format}" == "block" ]]; then + local symdir="${UUID_BLOCK_PREFIX}-${interface}-block" + safe-block-symlink "${ssd}" "${symdir}" + else + echo "Disk format must be either fs or block, got ${format}" + fi +} + +# Local ssds, if present, are mounted or symlinked to their appropriate +# locations function ensure-local-ssds() { + get-local-disk-num "scsi" "block" + local scsiblocknum="${localdisknum}" + local i=0 for ssd in /dev/disk/by-id/google-local-ssd-*; do if [ -e "${ssd}" ]; then - ssdnum=`echo ${ssd} | sed -e 's/\/dev\/disk\/by-id\/google-local-ssd-\([0-9]*\)/\1/'` - ssdmount="/mnt/disks/ssd${ssdnum}/" - mkdir -p ${ssdmount} - safe-format-and-mount "${ssd}" ${ssdmount} - echo "Mounted local SSD $ssd at ${ssdmount}" - chmod a+w ${ssdmount} + local devicenum=`echo ${ssd} | sed -e 's/\/dev\/disk\/by-id\/google-local-ssd-\([0-9]*\)/\1/'` + if [[ "${i}" -lt "${scsiblocknum}" ]]; then + mount-ext "${ssd}" "${devicenum}" "scsi" "block" + else + # GKE does not set NODE_LOCAL_SSDS so all non-block devices + # are assumed to be filesystem devices + mount-ext "${ssd}" "${devicenum}" "scsi" "fs" + fi + i=$((i+1)) else - echo "No local SSD disks found." + echo "No local SCSI SSD disks found." + fi + done + + # The following mounts or symlinks NVMe devices + get-local-disk-num "nvme" "block" + local nvmeblocknum="${localdisknum}" + local i=0 + for ssd in /dev/nvme*; do + if [ -e "${ssd}" ]; then + # This workaround to find if the NVMe device is a disk is required because + # the existing Google images does not expose NVMe devices in /dev/disk/by-id + if [[ `udevadm info --query=property --name=${ssd} | grep DEVTYPE | sed "s/DEVTYPE=//"` == "disk" ]]; then + local devicenum=`echo ${ssd} | sed -e 's/\/dev\/nvme0n\([0-9]*\)/\1/'` + if [[ "${i}" -lt "${nvmeblocknum}" ]]; then + mount-ext "${ssd}" "${devicenum}" "nvme" "block" + else + mount-ext "${ssd}" "${devicenum}" "nvme" "fs" + fi + i=$((i+1)) + fi + else + echo "No local NVMe SSD disks found." fi done } @@ -225,10 +442,6 @@ function create-node-pki { KUBELET_KEY_PATH="${pki_dir}/kubelet.key" write-pki-data "${KUBELET_KEY}" "${KUBELET_KEY_PATH}" fi - - # TODO(mikedanese): remove this when we don't support downgrading to versions - # < 1.6. - ln -sf "${CA_CERT_BUNDLE_PATH}" /etc/srv/kubernetes/ca.crt } function create-master-pki { @@ -279,11 +492,6 @@ function create-master-pki { SERVICEACCOUNT_KEY_PATH="${pki_dir}/serviceaccount.key" write-pki-data "${SERVICEACCOUNT_KEY}" "${SERVICEACCOUNT_KEY_PATH}" - # TODO(mikedanese): remove this when we don't support downgrading to versions - # < 1.6. - ln -sf "${APISERVER_SERVER_KEY_PATH}" /etc/srv/kubernetes/server.key - ln -sf "${APISERVER_SERVER_CERT_PATH}" /etc/srv/kubernetes/server.cert - if [[ ! -z "${REQUESTHEADER_CA_CERT:-}" ]]; then AGGREGATOR_CA_KEY_PATH="${pki_dir}/aggr_ca.key" write-pki-data "${AGGREGATOR_CA_KEY}" "${AGGREGATOR_CA_KEY_PATH}" @@ -381,14 +589,19 @@ EOF if [[ -n "${NODE_INSTANCE_PREFIX:-}" ]]; then use_cloud_config="true" if [[ -n "${NODE_TAGS:-}" ]]; then - local -r node_tags="${NODE_TAGS}" + # split NODE_TAGS into an array by comma. + IFS=',' read -r -a node_tags <<< ${NODE_TAGS} else local -r node_tags="${NODE_INSTANCE_PREFIX}" fi cat <>/etc/gce.conf -node-tags = ${node_tags} node-instance-prefix = ${NODE_INSTANCE_PREFIX} EOF + for tag in ${node_tags[@]}; do + cat <>/etc/gce.conf +node-tags = ${tag} +EOF + done fi if [[ -n "${MULTIZONE:-}" ]]; then use_cloud_config="true" @@ -398,14 +611,18 @@ EOF fi if [[ -n "${GCE_ALPHA_FEATURES:-}" ]]; then use_cloud_config="true" - cat <>/etc/gce.conf -alpha-features = ${GCE_ALPHA_FEATURES} + # split GCE_ALPHA_FEATURES into an array by comma. + IFS=',' read -r -a alpha_features <<< ${GCE_ALPHA_FEATURES} + for feature in ${alpha_features[@]}; do + cat <>/etc/gce.conf +alpha-features = ${feature} EOF + done fi if [[ -n "${SECONDARY_RANGE_NAME:-}" ]]; then use_cloud_config="true" cat <> /etc/gce.conf -secondary-range-name = ${SECONDARY-RANGE-NAME} +secondary-range-name = ${SECONDARY_RANGE_NAME} EOF fi if [[ "${use_cloud_config}" != "true" ]]; then @@ -775,6 +992,14 @@ current-context: kube-scheduler EOF } +function create-kubescheduler-policy-config { + echo "Creating kube-scheduler policy config file" + mkdir -p /etc/srv/kubernetes/kube-scheduler + cat </etc/srv/kubernetes/kube-scheduler/policy-config +${SCHEDULER_POLICY_CONFIG} +EOF +} + function create-node-problem-detector-kubeconfig { echo "Creating node-problem-detector kubeconfig file" mkdir -p /var/lib/node-problem-detector @@ -836,25 +1061,19 @@ function assemble-docker-flags { docker_opts+=" --log-opt=max-size=${DOCKER_LOG_MAX_SIZE:-10m}" docker_opts+=" --log-opt=max-file=${DOCKER_LOG_MAX_FILE:-5}" - echo "DOCKER_OPTS=\"${docker_opts} ${EXTRA_DOCKER_OPTS:-}\"" > /etc/default/docker + # Disable live-restore if the environment variable is set. - if [[ "${use_net_plugin}" == "true" ]]; then - # If using a network plugin, extend the docker configuration to always remove - # the network checkpoint to avoid corrupt checkpoints. - # (https://github.com/docker/docker/issues/18283). - echo "Extend the docker.service configuration to remove the network checkpiont" - mkdir -p /etc/systemd/system/docker.service.d - cat </etc/systemd/system/docker.service.d/01network.conf -[Service] -ExecStartPre=/bin/sh -x -c "rm -rf /var/lib/docker/network" -EOF + if [[ "${DISABLE_DOCKER_LIVE_RESTORE:-false}" == "true" ]]; then + docker_opts+=" --live-restore=false" fi + echo "DOCKER_OPTS=\"${docker_opts} ${EXTRA_DOCKER_OPTS:-}\"" > /etc/default/docker + # Ensure TasksMax is sufficient for docker. # (https://github.com/kubernetes/kubernetes/issues/51977) echo "Extend the docker.service configuration to set a higher pids limit" mkdir -p /etc/systemd/system/docker.service.d - cat </etc/systemd/system/docker.service.d/02tasksmax.conf + cat </etc/systemd/system/docker.service.d/01tasksmax.conf [Service] TasksMax=infinity EOF @@ -933,7 +1152,9 @@ function start-kubelet { flags+=" --cni-bin-dir=/home/kubernetes/bin" if [[ "${NETWORK_POLICY_PROVIDER:-}" == "calico" ]]; then # Calico uses CNI always. - if [[ "${KUBERNETES_PRIVATE_MASTER:-}" == "true" ]]; then + # Keep KUBERNETES_PRIVATE_MASTER for backward compatibility. + # Note that network policy won't work for master node. + if [[ "${KUBERNETES_PRIVATE_MASTER:-}" == "true" || "${KUBERNETES_MASTER:-}" == "true" ]]; then flags+=" --network-plugin=${NETWORK_PROVIDER}" else flags+=" --network-plugin=cni" @@ -966,6 +1187,9 @@ function start-kubelet { if [[ -n "${NODE_LABELS:-}" ]]; then node_labels="${node_labels:+${node_labels},}${NODE_LABELS}" fi + if [[ -n "${NON_MASTER_NODE_LABELS:-}" && "${KUBERNETES_MASTER:-}" != "true" ]]; then + node_labels="${node_labels:+${node_labels},}${NON_MASTER_NODE_LABELS}" + fi if [[ -n "${node_labels:-}" ]]; then flags+=" --node-labels=${node_labels}" fi @@ -981,6 +1205,13 @@ function start-kubelet { if [[ -n "${ROTATE_CERTIFICATES:-}" ]]; then flags+=" --rotate-certificates=true" fi + if [[ -n "${CONTAINER_RUNTIME:-}" ]]; then + flags+=" --container-runtime=${CONTAINER_RUNTIME}" + fi + if [[ -n "${CONTAINER_RUNTIME_ENDPOINT:-}" ]]; then + flags+=" --container-runtime-endpoint=${CONTAINER_RUNTIME_ENDPOINT}" + fi + local -r kubelet_env_file="/etc/default/kubelet" echo "KUBELET_OPTS=\"${flags}\"" > "${kubelet_env_file}" @@ -1002,9 +1233,6 @@ ExecStart=${kubelet_bin} \$KUBELET_OPTS WantedBy=multi-user.target EOF - # Flush iptables nat table - iptables -t nat -F || true - systemctl start kubelet.service } @@ -1014,6 +1242,7 @@ function start-node-problem-detector { echo "Start node problem detector" local -r npd_bin="${KUBE_HOME}/bin/node-problem-detector" local -r km_config="${KUBE_HOME}/node-problem-detector/config/kernel-monitor.json" + # TODO(random-liu): Handle this for alternative container runtime. local -r dm_config="${KUBE_HOME}/node-problem-detector/config/docker-monitor.json" echo "Using node problem detector binary at ${npd_bin}" local flags="${NPD_TEST_LOG_LEVEL:-"--v=2"} ${NPD_TEST_ARGS:-}" @@ -1119,7 +1348,7 @@ function start-kube-proxy { # $4: value for variable 'cpulimit' # $5: pod name, which should be either etcd or etcd-events function prepare-etcd-manifest { - local host_name=$(hostname) + local host_name=${ETCD_HOSTNAME:-$(hostname -s)} local etcd_cluster="" local cluster_state="new" local etcd_protocol="http" @@ -1151,6 +1380,7 @@ function prepare-etcd-manifest { sed -i -e "s@{{ *hostname *}}@$host_name@g" "${temp_file}" sed -i -e "s@{{ *srv_kube_path *}}@/etc/srv/kubernetes@g" "${temp_file}" sed -i -e "s@{{ *etcd_cluster *}}@$etcd_cluster@g" "${temp_file}" + sed -i -e "s@{{ *liveness_probe_initial_delay *}}@${ETCD_LIVENESS_PROBE_INITIAL_DELAY_SEC:-15}@g" "${temp_file}" # Get default storage backend from manifest file. local -r default_storage_backend=$(cat "${temp_file}" | \ grep -o "{{ *pillar\.get('storage_backend', '\(.*\)') *}}" | \ @@ -1385,6 +1615,24 @@ function start-kube-apiserver { # Create the audit webhook config file, and mount it into the apiserver pod. local -r audit_webhook_config_file="/etc/audit_webhook.config" params+=" --audit-webhook-config-file=${audit_webhook_config_file}" + if [[ -n "${ADVANCED_AUDIT_WEBHOOK_BUFFER_SIZE:-}" ]]; then + params+=" --audit-webhook-batch-buffer-size=${ADVANCED_AUDIT_WEBHOOK_BUFFER_SIZE}" + fi + if [[ -n "${ADVANCED_AUDIT_WEBHOOK_MAX_BATCH_SIZE:-}" ]]; then + params+=" --audit-webhook-batch-max-size=${ADVANCED_AUDIT_WEBHOOK_MAX_BATCH_SIZE}" + fi + if [[ -n "${ADVANCED_AUDIT_WEBHOOK_MAX_BATCH_WAIT:-}" ]]; then + params+=" --audit-webhook-batch-max-wait=${ADVANCED_AUDIT_WEBHOOK_MAX_BATCH_WAIT}" + fi + if [[ -n "${ADVANCED_AUDIT_WEBHOOK_THROTTLE_QPS:-}" ]]; then + params+=" --audit-webhook-batch-throttle-qps=${ADVANCED_AUDIT_WEBHOOK_THROTTLE_QPS}" + fi + if [[ -n "${ADVANCED_AUDIT_WEBHOOK_THROTTLE_BURST:-}" ]]; then + params+=" --audit-webhook-batch-throttle-burst=${ADVANCED_AUDIT_WEBHOOK_THROTTLE_BURST}" + fi + if [[ -n "${ADVANCED_AUDIT_WEBHOOK_INITIAL_BACKOFF:-}" ]]; then + params+=" --audit-webhook-batch-initial-backoff=${ADVANCED_AUDIT_WEBHOOK_INITIAL_BACKOFF}" + fi create-master-audit-webhook-config "${audit_webhook_config_file}" audit_webhook_config_mount="{\"name\": \"auditwebhookconfigmount\",\"mountPath\": \"${audit_webhook_config_file}\", \"readOnly\": true}," audit_webhook_config_volume="{\"name\": \"auditwebhookconfigmount\",\"hostPath\": {\"path\": \"${audit_webhook_config_file}\", \"type\": \"FileOrCreate\"}}," @@ -1422,10 +1670,14 @@ function start-kube-apiserver { params+=" --feature-gates=${FEATURE_GATES}" fi if [[ -n "${PROJECT_ID:-}" && -n "${TOKEN_URL:-}" && -n "${TOKEN_BODY:-}" && -n "${NODE_NETWORK:-}" ]]; then - local -r vm_external_ip=$(curl --retry 5 --retry-delay 3 --fail --silent -H 'Metadata-Flavor: Google' "http://metadata/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip") - params+=" --advertise-address=${vm_external_ip}" - params+=" --ssh-user=${PROXY_SSH_USER}" - params+=" --ssh-keyfile=/etc/srv/sshproxy/.sshkeyfile" + local -r vm_external_ip=$(curl --retry 5 --retry-delay 3 ${CURL_RETRY_CONNREFUSED} --fail --silent -H 'Metadata-Flavor: Google' "http://metadata/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip") + if [[ -n "${PROXY_SSH_USER:-}" ]]; then + params+=" --advertise-address=${vm_external_ip}" + params+=" --ssh-user=${PROXY_SSH_USER}" + params+=" --ssh-keyfile=/etc/srv/sshproxy/.sshkeyfile" + else + params+=" --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname", + fi elif [ -n "${MASTER_ADVERTISE_ADDRESS:-}" ]; then params="${params} --advertise-address=${MASTER_ADVERTISE_ADDRESS}" fi @@ -1487,10 +1739,6 @@ function start-kube-apiserver { if [[ -n "${ENCRYPTION_PROVIDER_CONFIG:-}" ]]; then local encryption_provider_config_path="/etc/srv/kubernetes/encryption-provider-config.yml" - if [[ -n "${GOOGLE_CLOUD_KMS_CONFIG_FILE_NAME:-}" && -n "${GOOGLE_CLOUD_KMS_CONFIG:-}" ]]; then - echo "${GOOGLE_CLOUD_KMS_CONFIG}" | base64 --decode > "${GOOGLE_CLOUD_KMS_CONFIG_FILE_NAME}" - fi - echo "${ENCRYPTION_PROVIDER_CONFIG}" | base64 --decode > "${encryption_provider_config_path}" params+=" --experimental-encryption-provider-config=${encryption_provider_config_path}" fi @@ -1508,6 +1756,7 @@ function start-kube-apiserver { sed -i -e "s@{{pillar\['kube_docker_registry'\]}}@${DOCKER_REGISTRY}@g" "${src_file}" sed -i -e "s@{{pillar\['kube-apiserver_docker_tag'\]}}@${kube_apiserver_docker_tag}@g" "${src_file}" sed -i -e "s@{{pillar\['allow_privileged'\]}}@true@g" "${src_file}" + sed -i -e "s@{{liveness_probe_initial_delay}}@${KUBE_APISERVER_LIVENESS_PROBE_INITIAL_DELAY_SEC:-15}@g" "${src_file}" sed -i -e "s@{{secure_port}}@443@g" "${src_file}" sed -i -e "s@{{secure_port}}@8080@g" "${src_file}" sed -i -e "s@{{additional_cloud_config_mount}}@@g" "${src_file}" @@ -1587,10 +1836,16 @@ function start-kube-controller-manager { if [[ -n "${CLUSTER_SIGNING_DURATION:-}" ]]; then params+=" --experimental-cluster-signing-duration=$CLUSTER_SIGNING_DURATION" fi - # disable using HPA metrics REST clients if metrics-server isn't enabled - if [[ "${ENABLE_METRICS_SERVER:-}" != "true" ]]; then + # Disable using HPA metrics REST clients if metrics-server isn't enabled, + # or if we want to explicitly disable it by setting HPA_USE_REST_CLIENT. + if [[ "${ENABLE_METRICS_SERVER:-}" != "true" ]] || + [[ "${HPA_USE_REST_CLIENTS:-}" == "false" ]]; then params+=" --horizontal-pod-autoscaler-use-rest-clients=false" fi + if [[ -n "${PV_RECYCLER_OVERRIDE_TEMPLATE:-}" ]]; then + params+=" --pv-recycler-pod-template-filepath-nfs=$PV_RECYCLER_OVERRIDE_TEMPLATE" + params+=" --pv-recycler-pod-template-filepath-hostpath=$PV_RECYCLER_OVERRIDE_TEMPLATE" + fi local -r kube_rc_docker_tag=$(cat /home/kubernetes/kube-docker-files/kube-controller-manager.docker_tag) local container_env="" @@ -1610,6 +1865,8 @@ function start-kube-controller-manager { sed -i -e "s@{{cloud_config_volume}}@${CLOUD_CONFIG_VOLUME}@g" "${src_file}" sed -i -e "s@{{additional_cloud_config_mount}}@@g" "${src_file}" sed -i -e "s@{{additional_cloud_config_volume}}@@g" "${src_file}" + sed -i -e "s@{{pv_recycler_mount}}@${PV_RECYCLER_MOUNT}@g" "${src_file}" + sed -i -e "s@{{pv_recycler_volume}}@${PV_RECYCLER_VOLUME}@g" "${src_file}" cp "${src_file}" /etc/kubernetes/manifests } @@ -1633,6 +1890,11 @@ function start-kube-scheduler { if [[ -n "${SCHEDULING_ALGORITHM_PROVIDER:-}" ]]; then params+=" --algorithm-provider=${SCHEDULING_ALGORITHM_PROVIDER}" fi + if [[ -n "${SCHEDULER_POLICY_CONFIG:-}" ]]; then + create-kubescheduler-policy-config + params+=" --use-legacy-policy-config" + params+=" --policy-config-file=/etc/srv/kubernetes/kube-scheduler/policy-config" + fi local -r kube_scheduler_docker_tag=$(cat "${KUBE_HOME}/kube-docker-files/kube-scheduler.docker_tag") # Remove salt comments and replace variables with values. @@ -1670,14 +1932,35 @@ function start-cluster-autoscaler { fi } -# A helper function for copying addon manifests and set dir/files -# permissions. +# A helper function for setting up addon manifests. # # $1: addon category under /etc/kubernetes # $2: manifest source dir +# $3: (optional) auxilary manifest source dir function setup-addon-manifests { - local -r src_dir="${KUBE_HOME}/kube-manifests/kubernetes/gci-trusty/$2" + local -r src_dir="${KUBE_HOME}/kube-manifests/kubernetes/gci-trusty" local -r dst_dir="/etc/kubernetes/$1/$2" + + copy-manifests "${src_dir}/$2" "${dst_dir}" + + # If the PodSecurityPolicy admission controller is enabled, + # set up the corresponding addon policies. + if [[ "${ENABLE_POD_SECURITY_POLICY:-}" == "true" ]]; then + local -r psp_dir="${src_dir}/${3:-$2}/podsecuritypolicies" + if [[ -d "${psp_dir}" ]]; then + copy-manifests "${psp_dir}" "${dst_dir}" + fi + fi +} + +# A helper function for copying manifests and setting dir/files +# permissions. +# +# $1: absolute source dir +# $2: absolute destination dir +function copy-manifests { + local -r src_dir="$1" + local -r dst_dir="$2" if [[ ! -d "${dst_dir}" ]]; then mkdir -p "${dst_dir}" fi @@ -1701,21 +1984,47 @@ function setup-addon-manifests { # Fluentd manifest is modified using kubectl, which may not be available at # this point. Run this as a background process. function wait-for-apiserver-and-update-fluentd { + local -r fluentd_gcp_yaml="${1}" + + local modifying_flags="" + if [[ -n "${FLUENTD_GCP_MEMORY_LIMIT:-}" ]]; then + modifying_flags="${modifying_flags} --limits=memory=${FLUENTD_GCP_MEMORY_LIMIT}" + fi + local request_resources="" + if [[ -n "${FLUENTD_GCP_CPU_REQUEST:-}" ]]; then + request_resources="cpu=${FLUENTD_GCP_CPU_REQUEST}" + fi + if [[ -n "${FLUENTD_GCP_MEMORY_REQUEST:-}" ]]; then + if [[ -n "${request_resources}" ]]; then + request_resources="${request_resources}," + fi + request_resources="memory=${FLUENTD_GCP_MEMORY_REQUEST}" + fi + if [[ -n "${request_resources}" ]]; then + modifying_flags="${modifying_flags} --requests=${request_resources}" + fi + until kubectl get nodes do sleep 10 done - kubectl set resources --dry-run --local -f ${fluentd_gcp_yaml} \ - --limits=memory=${FLUENTD_GCP_MEMORY_LIMIT} \ - --requests=cpu=${FLUENTD_GCP_CPU_REQUEST},memory=${FLUENTD_GCP_MEMORY_REQUEST} \ - --containers=fluentd-gcp -o yaml > ${fluentd_gcp_yaml}.tmp - mv ${fluentd_gcp_yaml}.tmp ${fluentd_gcp_yaml} + + local -r temp_fluentd_gcp_yaml="${fluentd_gcp_yaml}.tmp" + if kubectl set resources --dry-run --local -f ${fluentd_gcp_yaml} ${modifying_flags} \ + --containers=fluentd-gcp -o yaml > ${temp_fluentd_gcp_yaml}; then + mv ${temp_fluentd_gcp_yaml} ${fluentd_gcp_yaml} + else + (echo "Failed to update fluentd resources. Used manifest:" && cat ${temp_fluentd_gcp_yaml}) >&2 + rm ${temp_fluentd_gcp_yaml} + fi } # Trigger background process that will ultimately update fluentd resource # requirements. function start-fluentd-resource-update { - wait-for-apiserver-and-update-fluentd & + local -r fluentd_gcp_yaml="${1}" + + wait-for-apiserver-and-update-fluentd ${fluentd_gcp_yaml} & } # Updates parameters in yaml file for prometheus-to-sd configuration, or @@ -1730,6 +2039,36 @@ function update-prometheus-to-sd-parameters { fi } +# Sets up the manifests of coreDNS for k8s addons. +function setup-coredns-manifest { + local -r coredns_file="${dst_dir}/dns/coredns.yaml" + mv "${dst_dir}/dns/coredns.yaml.in" "${coredns_file}" + # Replace the salt configurations with variable values. + sed -i -e "s@{{ *pillar\['dns_domain'\] *}}@${DNS_DOMAIN}@g" "${coredns_file}" + sed -i -e "s@{{ *pillar\['dns_server'\] *}}@${DNS_SERVER_IP}@g" "${coredns_file}" + sed -i -e "s@{{ *pillar\['service_cluster_ip_range'\] *}}@${SERVICE_CLUSTER_IP_RANGE}@g" "${coredns_file}" +} + +# Sets up the manifests of kube-dns for k8s addons. +function setup-kube-dns-manifest { + local -r kubedns_file="${dst_dir}/dns/kube-dns.yaml" + mv "${dst_dir}/dns/kube-dns.yaml.in" "${kubedns_file}" + if [ -n "${CUSTOM_KUBE_DNS_YAML:-}" ]; then + # Replace with custom GKE kube-dns deployment. + cat > "${kubedns_file}" < "$src_dir/kube-proxy/kube-proxy-ds.yaml" < /etc/profile.d/kube_env.sh } +function override-pv-recycler { + if [[ -z "${PV_RECYCLER_OVERRIDE_TEMPLATE:-}" ]]; then + echo "PV_RECYCLER_OVERRIDE_TEMPLATE is not set" + exit 1 + fi + + PV_RECYCLER_VOLUME="{\"name\": \"pv-recycler-mount\",\"hostPath\": {\"path\": \"${PV_RECYCLER_OVERRIDE_TEMPLATE}\", \"type\": \"FileOrCreate\"}}," + PV_RECYCLER_MOUNT="{\"name\": \"pv-recycler-mount\",\"mountPath\": \"${PV_RECYCLER_OVERRIDE_TEMPLATE}\", \"readOnly\": true}," + + cat > ${PV_RECYCLER_OVERRIDE_TEMPLATE} < /etc/motd < "${KUBE_HOME}/kube-env") rm -f "${tmp_kube_env}" + ) } function download-kube-master-certs { # Fetch kube-env from GCE metadata server. + (umask 700; local -r tmp_kube_master_certs="/tmp/kube-master-certs.yaml" - curl --fail --retry 5 --retry-delay 3 --silent --show-error \ + curl --fail --retry 5 --retry-delay 3 ${CURL_RETRY_CONNREFUSED} --silent --show-error \ -H "X-Google-Metadata-Request: True" \ -o "${tmp_kube_master_certs}" \ http://metadata.google.internal/computeMetadata/v1/instance/attributes/kube-master-certs @@ -68,6 +85,7 @@ for k,v in yaml.load(sys.stdin).iteritems(): print("readonly {var}={value}".format(var = k, value = pipes.quote(str(v)))) ''' < "${tmp_kube_master_certs}" > "${KUBE_HOME}/kube-master-certs") rm -f "${tmp_kube_master_certs}" + ) } function validate-hash { @@ -94,7 +112,7 @@ function download-or-bust { for url in "${urls[@]}"; do local file="${url##*/}" rm -f "${file}" - if ! curl -f --ipv4 -Lo "${file}" --connect-timeout 20 --max-time 300 --retry 6 --retry-delay 10 "${url}"; then + if ! curl -f --ipv4 -Lo "${file}" --connect-timeout 20 --max-time 300 --retry 6 --retry-delay 10 ${CURL_RETRY_CONNREFUSED} "${url}"; then echo "== Failed to download ${url}. Retrying. ==" elif [[ -n "${hash}" ]] && ! validate-hash "${file}" "${hash}"; then echo "== Hash validation of ${url} failed. Retrying. ==" @@ -122,7 +140,7 @@ function split-commas { function install-gci-mounter-tools { CONTAINERIZED_MOUNTER_HOME="${KUBE_HOME}/containerized_mounter" - local -r mounter_tar_sha="8003b798cf33c7f91320cd6ee5cec4fa22244571" + local -r mounter_tar_sha="${DEFAULT_MOUNTER_TAR_SHA}" if is-preloaded "mounter" "${mounter_tar_sha}"; then echo "mounter is preloaded." return @@ -133,7 +151,7 @@ function install-gci-mounter-tools { chmod a+x "${CONTAINERIZED_MOUNTER_HOME}" mkdir -p "${CONTAINERIZED_MOUNTER_HOME}/rootfs" download-or-bust "${mounter_tar_sha}" "https://storage.googleapis.com/kubernetes-release/gci-mounter/mounter.tar" - cp "${KUBE_HOME}/kube-manifests/kubernetes/gci-trusty/gci-mounter" "${CONTAINERIZED_MOUNTER_HOME}/mounter" + cp "${KUBE_HOME}/kubernetes/server/bin/mounter" "${CONTAINERIZED_MOUNTER_HOME}/mounter" chmod a+x "${CONTAINERIZED_MOUNTER_HOME}/mounter" mv "${KUBE_HOME}/mounter.tar" /tmp/mounter.tar tar xf /tmp/mounter.tar -C "${CONTAINERIZED_MOUNTER_HOME}/rootfs" @@ -147,8 +165,8 @@ function install-node-problem-detector { local -r npd_version="${NODE_PROBLEM_DETECTOR_VERSION}" local -r npd_sha1="${NODE_PROBLEM_DETECTOR_TAR_HASH}" else - local -r npd_version="v0.4.1" - local -r npd_sha1="a57a3fe64cab8a18ec654f5cef0aec59dae62568" + local -r npd_version="${DEFAULT_NPD_VERSION}" + local -r npd_sha1="${DEFAULT_NPD_SHA1}" fi if is-preloaded "node-problem-detector" "${npd_sha1}"; then @@ -170,9 +188,8 @@ function install-node-problem-detector { } function install-cni-binaries { - local -r cni_version="v0.6.0" - local -r cni_tar="cni-plugins-amd64-${cni_version}.tgz" - local -r cni_sha1="d595d3ded6499a64e8dac02466e2f5f2ce257c9f" + local -r cni_tar="cni-plugins-amd64-${DEFAULT_CNI_VERSION}.tgz" + local -r cni_sha1="${DEFAULT_CNI_SHA1}" if is-preloaded "${cni_tar}" "${cni_sha1}"; then echo "${cni_tar} is preloaded." return @@ -234,7 +251,7 @@ function try-load-docker-image { set +e local -r max_attempts=5 local -i attempt_num=1 - until timeout 30 docker load -i "${img}"; do + until timeout 30 ${LOAD_IMAGE_COMMAND:-docker load -i} "${img}"; do if [[ "${attempt_num}" == "${max_attempts}" ]]; then echo "Fail to load docker image file ${img} after ${max_attempts} retries. Exit!!" exit 1 diff --git a/cluster/gce/gci/health-monitor.sh b/cluster/gce/gci/health-monitor.sh index c66d4173317..00510b4d8de 100644 --- a/cluster/gce/gci/health-monitor.sh +++ b/cluster/gce/gci/health-monitor.sh @@ -30,7 +30,7 @@ function docker_monitoring { echo "Docker daemon failed!" pkill docker # Wait for a while, as we don't want to kill it again before it is really up. - sleep 30 + sleep 120 else sleep "${SLEEP_SECONDS}" fi diff --git a/cluster/gce/gci/master-helper.sh b/cluster/gce/gci/master-helper.sh index c34ca113e5e..e6fac224cf9 100755 --- a/cluster/gce/gci/master-helper.sh +++ b/cluster/gce/gci/master-helper.sh @@ -76,6 +76,12 @@ function replicate-master-instance() { function create-master-instance-internal() { local gcloud="gcloud" local retries=5 + local sleep_sec=10 + if [[ "${MASTER_SIZE##*-}" -ge 64 ]]; then # remove everything up to last dash (inclusive) + # Workaround for #55777 + retries=30 + sleep_sec=60 + fi if [[ "${ENABLE_IP_ALIASES:-}" == 'true' ]]; then gcloud="gcloud beta" fi @@ -100,6 +106,7 @@ function create-master-instance-internal() { metadata="${metadata},gci-ensure-gke-docker=${KUBE_TEMP}/gci-ensure-gke-docker.txt" metadata="${metadata},gci-docker-version=${KUBE_TEMP}/gci-docker-version.txt" metadata="${metadata},kube-master-certs=${KUBE_TEMP}/kube-master-certs.yaml" + metadata="${metadata},${MASTER_EXTRA_METADATA}" local disk="name=${master_name}-pd" disk="${disk},device-name=master-pd" @@ -119,6 +126,7 @@ function create-master-instance-internal() { --metadata-from-file "${metadata}" \ --disk "${disk}" \ --boot-disk-size "${MASTER_ROOT_DISK_SIZE}" \ + ${MASTER_MIN_CPU_ARCHITECTURE:+"--min-cpu-platform=${MASTER_MIN_CPU_ARCHITECTURE}"} \ ${preemptible_master} \ ${network} 2>&1); then echo "${result}" >&2 @@ -129,7 +137,7 @@ function create-master-instance-internal() { echo "Failed to create master instance due to non-retryable error" >&2 return 1 fi - sleep 10 + sleep $sleep_sec fi done diff --git a/cluster/gce/gci/master.yaml b/cluster/gce/gci/master.yaml index 7854ab4fa93..6d7d8e8e1a4 100644 --- a/cluster/gce/gci/master.yaml +++ b/cluster/gce/gci/master.yaml @@ -15,7 +15,8 @@ write_files: ExecStartPre=/bin/mkdir -p /home/kubernetes/bin ExecStartPre=/bin/mount --bind /home/kubernetes/bin /home/kubernetes/bin ExecStartPre=/bin/mount -o remount,exec /home/kubernetes/bin - ExecStartPre=/usr/bin/curl --fail --retry 5 --retry-delay 3 --silent --show-error -H "X-Google-Metadata-Request: True" -o /home/kubernetes/bin/configure.sh http://metadata.google.internal/computeMetadata/v1/instance/attributes/configure-sh + # Use --retry-connrefused opt only if it's supported by curl. + ExecStartPre=/bin/bash -c 'OPT=""; if curl --help | grep -q -- "--retry-connrefused"; then OPT="--retry-connrefused"; fi; /usr/bin/curl --fail --retry 5 --retry-delay 3 $OPT --silent --show-error -H "X-Google-Metadata-Request: True" -o /home/kubernetes/bin/configure.sh http://metadata.google.internal/computeMetadata/v1/instance/attributes/configure-sh' ExecStartPre=/bin/chmod 544 /home/kubernetes/bin/configure.sh ExecStart=/home/kubernetes/bin/configure.sh diff --git a/cluster/gce/gci/mounter/.gitignore b/cluster/gce/gci/mounter/.gitignore new file mode 100644 index 00000000000..7742e145294 --- /dev/null +++ b/cluster/gce/gci/mounter/.gitignore @@ -0,0 +1 @@ +mounter diff --git a/cluster/gce/gci/mounter/BUILD b/cluster/gce/gci/mounter/BUILD index 94653e86b05..9600d6e441d 100644 --- a/cluster/gce/gci/mounter/BUILD +++ b/cluster/gce/gci/mounter/BUILD @@ -8,8 +8,8 @@ load( go_binary( name = "mounter", + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cluster/gce/gci/mounter", - library = ":go_default_library", ) go_library( diff --git a/cluster/gce/gci/mounter/mounter b/cluster/gce/gci/mounter/mounter deleted file mode 100755 index 963296e03d2..00000000000 Binary files a/cluster/gce/gci/mounter/mounter and /dev/null differ diff --git a/cluster/gce/gci/node-helper.sh b/cluster/gce/gci/node-helper.sh index 45cdfe6cf1d..fa7c035fb0f 100755 --- a/cluster/gce/gci/node-helper.sh +++ b/cluster/gce/gci/node-helper.sh @@ -25,7 +25,8 @@ function get-node-instance-metadata { metadata+="cluster-name=${KUBE_TEMP}/cluster-name.txt," metadata+="gci-update-strategy=${KUBE_TEMP}/gci-update.txt," metadata+="gci-ensure-gke-docker=${KUBE_TEMP}/gci-ensure-gke-docker.txt," - metadata+="gci-docker-version=${KUBE_TEMP}/gci-docker-version.txt" + metadata+="gci-docker-version=${KUBE_TEMP}/gci-docker-version.txt," + metadata+="${NODE_EXTRA_METADATA}" echo "${metadata}" } diff --git a/cluster/gce/gci/node.yaml b/cluster/gce/gci/node.yaml index 52971e2a076..e04018e3453 100644 --- a/cluster/gce/gci/node.yaml +++ b/cluster/gce/gci/node.yaml @@ -15,7 +15,8 @@ write_files: ExecStartPre=/bin/mkdir -p /home/kubernetes/bin ExecStartPre=/bin/mount --bind /home/kubernetes/bin /home/kubernetes/bin ExecStartPre=/bin/mount -o remount,exec /home/kubernetes/bin - ExecStartPre=/usr/bin/curl --fail --retry 5 --retry-delay 3 --silent --show-error -H "X-Google-Metadata-Request: True" -o /home/kubernetes/bin/configure.sh http://metadata.google.internal/computeMetadata/v1/instance/attributes/configure-sh + # Use --retry-connrefused opt only if it's supported by curl. + ExecStartPre=/bin/bash -c 'OPT=""; if curl --help | grep -q -- "--retry-connrefused"; then OPT="--retry-connrefused"; fi; /usr/bin/curl --fail --retry 5 --retry-delay 3 $OPT --silent --show-error -H "X-Google-Metadata-Request: True" -o /home/kubernetes/bin/configure.sh http://metadata.google.internal/computeMetadata/v1/instance/attributes/configure-sh' ExecStartPre=/bin/chmod 544 /home/kubernetes/bin/configure.sh ExecStart=/home/kubernetes/bin/configure.sh diff --git a/cluster/gce/upgrade.sh b/cluster/gce/upgrade.sh index 1345eeb10e7..82ea8f9cb4b 100755 --- a/cluster/gce/upgrade.sh +++ b/cluster/gce/upgrade.sh @@ -99,8 +99,6 @@ function upgrade-master() { parse-master-env upgrade-master-env - backfile-kubeletauth-certs - # Delete the master instance. Note that the master-pd is created # with auto-delete=no, so it should not be deleted. gcloud compute instances delete \ @@ -122,51 +120,6 @@ function upgrade-master-env() { fi } -# TODO(mikedanese): delete when we don't support < 1.6 -function backfile-kubeletauth-certs() { - if [[ ! -z "${KUBEAPISERVER_CERT_BASE64:-}" && ! -z "${KUBEAPISERVER_CERT_BASE64:-}" ]]; then - return 0 - fi - - mkdir -p "${KUBE_TEMP}/pki" - echo "${CA_KEY_BASE64}" | base64 -d > "${KUBE_TEMP}/pki/ca.key" - echo "${CA_CERT_BASE64}" | base64 -d > "${KUBE_TEMP}/pki/ca.crt" - (cd "${KUBE_TEMP}/pki" - kube::util::ensure-cfssl "${KUBE_TEMP}/cfssl" - cat < ca-config.json -{ - "signing": { - "client": { - "expiry": "43800h", - "usages": [ - "signing", - "key encipherment", - "client auth" - ] - } - } -} -EOF - # the name kube-apiserver is bound to the node proxy - # subpaths required for the apiserver to hit proxy - # endpoints on the kubelet's handler. - cat <&2 + exit 1 + fi + fi +fi + print-node-version-info "Pre-Upgrade" if [[ "${local_binaries}" == "false" ]]; then diff --git a/cluster/gce/util.sh b/cluster/gce/util.sh index 8ab5f796ee1..46178dba693 100755 --- a/cluster/gce/util.sh +++ b/cluster/gce/util.sh @@ -18,6 +18,8 @@ # Use the config file specified in $KUBE_CONFIG_FILE, or default to # config-default.sh. +readonly GCE_MAX_LOCAL_SSD=8 + KUBE_ROOT=$(dirname "${BASH_SOURCE}")/../.. source "${KUBE_ROOT}/cluster/gce/${KUBE_CONFIG_FILE-"config-default.sh"}" source "${KUBE_ROOT}/cluster/common.sh" @@ -37,6 +39,11 @@ else exit 1 fi +if [[ ${NODE_LOCAL_SSDS:-} -ge 1 ]] && [[ ! -z ${NODE_LOCAL_SSDS_EXT:-} ]] ; then + echo -e "${color_red}Local SSD: Only one of NODE_LOCAL_SSDS and NODE_LOCAL_SSDS_EXT can be specified at once${color_norm}" >&2 + exit 2 +fi + if [[ "${MASTER_OS_DISTRIBUTION}" == "gci" ]]; then DEFAULT_GCI_PROJECT=google-containers if [[ "${GCI_VERSION}" == "cos"* ]]; then @@ -84,7 +91,8 @@ if [[ "${ENABLE_CLUSTER_AUTOSCALER}" == "true" ]]; then fi fi -NODE_INSTANCE_PREFIX="${INSTANCE_PREFIX}-minion" +NODE_INSTANCE_PREFIX=${NODE_INSTANCE_PREFIX:-"${INSTANCE_PREFIX}-minion"} + NODE_TAGS="${NODE_TAG}" ALLOCATE_NODE_CIDRS=true @@ -235,12 +243,12 @@ function set-preferred-region() { else KUBE_ADDON_REGISTRY="gcr.io/google_containers" fi - - if [[ "${ENABLE_DOCKER_REGISTRY_CACHE:-}" == "true" ]]; then - DOCKER_REGISTRY_MIRROR_URL="https://${preferred}-mirror.gcr.io" - fi } +if [[ "${ENABLE_DOCKER_REGISTRY_CACHE:-}" == "true" ]]; then + DOCKER_REGISTRY_MIRROR_URL="https://mirror.gcr.io" +fi + # Take the local tar files and upload them to Google Storage. They will then be # downloaded by the master as part of the start up script for the master. # @@ -401,12 +409,12 @@ function detect-master() { if [[ -z "${KUBE_MASTER_IP-}" ]]; then local master_address_name="${MASTER_NAME}-ip" echo "Looking for address '${master_address_name}'" >&2 - KUBE_MASTER_IP=$(gcloud compute addresses describe "${master_address_name}" \ - --project "${PROJECT}" --region "${REGION}" -q --format='value(address)') - fi - if [[ -z "${KUBE_MASTER_IP-}" ]]; then - echo "Could not detect Kubernetes master node. Make sure you've launched a cluster with 'kube-up.sh'" >&2 - exit 1 + if ! KUBE_MASTER_IP=$(gcloud compute addresses describe "${master_address_name}" \ + --project "${PROJECT}" --region "${REGION}" -q --format='value(address)') || \ + [[ -z "${KUBE_MASTER_IP-}" ]]; then + echo "Could not detect Kubernetes master node. Make sure you've launched a cluster with 'kube-up.sh'" >&2 + exit 1 + fi fi echo "Using master: $KUBE_MASTER (external IP: $KUBE_MASTER_IP)" >&2 } @@ -545,6 +553,29 @@ function get-template-name-from-version() { echo "${NODE_INSTANCE_PREFIX}-template-${1}" | cut -c 1-63 | sed 's/[\.\+]/-/g;s/-*$//g' } +# validates the NODE_LOCAL_SSDS_EXT variable +function validate-node-local-ssds-ext(){ + ssdopts="${1}" + + if [[ -z "${ssdopts[0]}" || -z "${ssdopts[1]}" || -z "${ssdopts[2]}" ]]; then + echo -e "${color_red}Local SSD: NODE_LOCAL_SSDS_EXT is malformed, found ${ssdopts[0]-_},${ssdopts[1]-_},${ssdopts[2]-_} ${color_norm}" >&2 + exit 2 + fi + if [[ "${ssdopts[1]}" != "scsi" && "${ssdopts[1]}" != "nvme" ]]; then + echo -e "${color_red}Local SSD: Interface must be scsi or nvme, found: ${ssdopts[1]} ${color_norm}" >&2 + exit 2 + fi + if [[ "${ssdopts[2]}" != "fs" && "${ssdopts[2]}" != "block" ]]; then + echo -e "${color_red}Local SSD: Filesystem type must be fs or block, found: ${ssdopts[2]} ${color_norm}" >&2 + exit 2 + fi + local_ssd_ext_count=$((local_ssd_ext_count+ssdopts[0])) + if [[ "${local_ssd_ext_count}" -gt "${GCE_MAX_LOCAL_SSD}" || "${local_ssd_ext_count}" -lt 1 ]]; then + echo -e "${color_red}Local SSD: Total number of local ssds must range from 1 to 8, found: ${local_ssd_ext_count} ${color_norm}" >&2 + exit 2 + fi +} + # Robustly try to create an instance template. # $1: The name of the instance template. # $2: The scopes flag. @@ -586,6 +617,19 @@ function create-node-template() { fi local local_ssds="" + local_ssd_ext_count=0 + if [[ ! -z ${NODE_LOCAL_SSDS_EXT:-} ]]; then + IFS=";" read -r -a ssdgroups <<< "${NODE_LOCAL_SSDS_EXT:-}" + for ssdgroup in "${ssdgroups[@]}" + do + IFS="," read -r -a ssdopts <<< "${ssdgroup}" + validate-node-local-ssds-ext "${ssdopts}" + for i in $(seq ${ssdopts[0]}); do + local_ssds="$local_ssds--local-ssd=interface=${ssdopts[1]} " + done + done + fi + if [[ ! -z ${NODE_LOCAL_SSDS+x} ]]; then # The NODE_LOCAL_SSDS check below fixes issue #49171 # Some versions of seq will count down from 1 if "seq 0" is specified @@ -595,6 +639,7 @@ function create-node-template() { done fi fi + local network=$(make-gcloud-network-argument \ "${NETWORK_PROJECT}" \ @@ -616,6 +661,7 @@ function create-node-template() { --boot-disk-size "${NODE_DISK_SIZE}" \ --image-project="${NODE_IMAGE_PROJECT}" \ --image "${NODE_IMAGE}" \ + --service-account "${NODE_SERVICE_ACCOUNT}" \ --tags "${NODE_TAG}" \ ${accelerator_args} \ ${local_ssds} \ @@ -771,6 +817,18 @@ function check-existing() { fi } +# TODO(#54017): Remove below logics for handling deprecated network mode field. +# `x_gcloud_mode` was replaced by `x_gcloud_subnet_mode` in gcloud 175.0.0 and +# the content changed as well. Keeping such logic to make the transition eaiser. +function check-network-mode() { + local mode="$(gcloud compute networks list --filter="name=('${NETWORK}')" --project ${NETWORK_PROJECT} --format='value(x_gcloud_subnet_mode)' || true)" + if [[ -z "${mode}" ]]; then + mode="$(gcloud compute networks list --filter="name=('${NETWORK}')" --project ${NETWORK_PROJECT} --format='value(x_gcloud_mode)' || true)" + fi + # The deprecated field uses lower case. Convert to upper case for consistency. + echo "$(echo $mode | tr [a-z] [A-Z])" +} + function create-network() { if ! gcloud compute networks --project "${NETWORK_PROJECT}" describe "${NETWORK}" &>/dev/null; then # The network needs to be created synchronously or we have a race. The @@ -783,7 +841,7 @@ function create-network() { gcloud compute networks create --project "${NETWORK_PROJECT}" "${NETWORK}" --mode="${network_mode}" else PREEXISTING_NETWORK=true - PREEXISTING_NETWORK_MODE="$(gcloud compute networks list --filter="name=('${NETWORK}')" --project ${NETWORK_PROJECT} --format='value(x_gcloud_subnet_mode)' || true)" + PREEXISTING_NETWORK_MODE="$(check-network-mode)" echo "Found existing network ${NETWORK} in ${PREEXISTING_NETWORK_MODE} mode." fi @@ -946,7 +1004,7 @@ function delete-network() { function delete-subnetworks() { if [[ ${ENABLE_IP_ALIASES:-} != "true" ]]; then # If running in custom mode network we need to delete subnets - mode="$(gcloud compute networks list --filter="name=('${NETWORK}')" --project ${NETWORK_PROJECT} --format='value(x_gcloud_subnet_mode)' || true)" + mode="$(check-network-mode)" if [[ "${mode}" == "CUSTOM" ]]; then if [[ "${ENABLE_BIG_CLUSTER_SUBNETS}" = "true" ]]; then echo "Deleting default subnets..." @@ -1343,6 +1401,7 @@ function create-nodes() { # - NODE_DISK_SIZE # - NODE_IMAGE_PROJECT # - NODE_IMAGE +# - NODE_SERVICE_ACCOUNT # - NODE_TAG # - NETWORK # - ENABLE_IP_ALIASES @@ -1373,6 +1432,7 @@ function create-heapster-node() { --boot-disk-size "${NODE_DISK_SIZE}" \ --image-project="${NODE_IMAGE_PROJECT}" \ --image "${NODE_IMAGE}" \ + --service-account "${NODE_SERVICE_ACCOUNT}" \ --tags "${NODE_TAG}" \ ${network} \ $(get-scope-flags) \ @@ -1466,14 +1526,20 @@ function check-cluster() { fi local start_time=$(date +%s) + local curl_out=$(mktemp) + kube::util::trap_add "rm -f ${curl_out}" EXIT until curl --cacert "${CERT_DIR}/pki/ca.crt" \ -H "Authorization: Bearer ${KUBE_BEARER_TOKEN}" \ ${secure} \ - --max-time 5 --fail --output /dev/null --silent \ - "https://${KUBE_MASTER_IP}/api/v1/pods"; do + --max-time 5 --fail \ + "https://${KUBE_MASTER_IP}/api/v1/pods?limit=100" > "${curl_out}" 2>&1; do local elapsed=$(($(date +%s) - ${start_time})) if [[ ${elapsed} -gt ${KUBE_CLUSTER_INITIALIZATION_TIMEOUT} ]]; then echo -e "${color_red}Cluster failed to initialize within ${KUBE_CLUSTER_INITIALIZATION_TIMEOUT} seconds.${color_norm}" >&2 + echo "Last output from querying API server follows:" >&2 + echo "-----------------------------------------------------" >&2 + cat "${curl_out}" >&2 + echo "-----------------------------------------------------" >&2 exit 2 fi printf "." @@ -1491,8 +1557,6 @@ function check-cluster() { # Update the user's kubeconfig to include credentials for this apiserver. create-kubeconfig - - create-kubeconfig-for-federation ) # ensures KUBECONFIG is set @@ -2175,13 +2239,6 @@ function prepare-e2e() { # easiest way to buy us a little more room. function prepare-startup-script() { # Find a standard sed instance (and ensure that the command works as expected on a Mac). - SED=sed - if which gsed &>/dev/null; then - SED=gsed - fi - if ! ($SED --version 2>&1 | grep -q GNU); then - echo "!!! GNU sed is required. If on OS X, use 'brew install gnu-sed'." - exit 1 - fi - $SED '/^\s*#\([^!].*\)*$/ d' ${KUBE_ROOT}/cluster/gce/configure-vm.sh > ${KUBE_TEMP}/configure-vm.sh + kube::util::ensure-gnu-sed + ${SED} '/^\s*#\([^!].*\)*$/ d' ${KUBE_ROOT}/cluster/gce/configure-vm.sh > ${KUBE_TEMP}/configure-vm.sh } diff --git a/cluster/get-kube.sh b/cluster/get-kube.sh index 39770b6d543..f0492d45ac6 100755 --- a/cluster/get-kube.sh +++ b/cluster/get-kube.sh @@ -24,14 +24,8 @@ # Set KUBERNETES_PROVIDER to choose between different providers: # Google Compute Engine [default] # * export KUBERNETES_PROVIDER=gce; wget -q -O - https://get.k8s.io | bash -# Google Container Engine -# * export KUBERNETES_PROVIDER=gke; wget -q -O - https://get.k8s.io | bash -# Amazon EC2 -# * export KUBERNETES_PROVIDER=aws; wget -q -O - https://get.k8s.io | bash # Libvirt (with CoreOS as a guest operating system) # * export KUBERNETES_PROVIDER=libvirt-coreos; wget -q -O - https://get.k8s.io | bash -# Microsoft Azure -# * export KUBERNETES_PROVIDER=azure-legacy; wget -q -O - https://get.k8s.io | bash # Vagrant (local virtual machines) # * export KUBERNETES_PROVIDER=vagrant; wget -q -O - https://get.k8s.io | bash # VMWare Photon Controller diff --git a/cluster/images/etcd-version-monitor/BUILD b/cluster/images/etcd-version-monitor/BUILD index bbc1137eb0a..c946b1194d9 100644 --- a/cluster/images/etcd-version-monitor/BUILD +++ b/cluster/images/etcd-version-monitor/BUILD @@ -8,8 +8,8 @@ load( go_binary( name = "etcd-version-monitor", + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cluster/images/etcd-version-monitor", - library = ":go_default_library", ) go_library( @@ -19,6 +19,8 @@ go_library( deps = [ "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", + "//vendor/github.com/prometheus/client_golang/prometheus/promhttp:go_default_library", + "//vendor/github.com/prometheus/client_model/go:go_default_library", "//vendor/github.com/prometheus/common/expfmt:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", ], diff --git a/cluster/images/etcd-version-monitor/Makefile b/cluster/images/etcd-version-monitor/Makefile index 09fb29607dc..e061b900151 100644 --- a/cluster/images/etcd-version-monitor/Makefile +++ b/cluster/images/etcd-version-monitor/Makefile @@ -20,7 +20,7 @@ ARCH:=amd64 GOLANG_VERSION?=1.8.3 REGISTRY?=gcr.io/google-containers -TAG?=0.1.0 +TAG?=0.1.1 IMAGE:=$(REGISTRY)/etcd-version-monitor:$(TAG) CURRENT_DIR:=$(pwd) TEMP_DIR:=$(shell mktemp -d) diff --git a/cluster/images/etcd-version-monitor/README.md b/cluster/images/etcd-version-monitor/README.md index bd000219fa7..3cfb675837d 100644 --- a/cluster/images/etcd-version-monitor/README.md +++ b/cluster/images/etcd-version-monitor/README.md @@ -1,11 +1,19 @@ # etcd-version-monitor -This is a tool for exporting metrics related to etcd version, like etcd -server's binary version, cluster version, and counts of different kinds of -gRPC calls (which is a characteristic of v3), etc. These metrics are in +This is a tool for exporting etcd metrics and supplementing them with etcd +server binary version and cluster version. These metrics are in prometheus format and can be scraped by a prometheus server. The metrics are exposed at the http://localhost:9101/metrics endpoint. +For etcd 3.1+, the +[go-grpc-prometheus](https://github.com/grpc-ecosystem/go-grpc-prometheus) +metrics format, which backward incompatibly replaces the 3.0 legacy grpc metric +format, is exposed in both the 3.1 format and in the 3.0. This preserves +backward compatiblity. + +For etcd 3.1+, the `--metrics=extensive` must be set on etcd for grpc request +latency metrics (`etcd_grpc_unary_requests_duration_seconds`) to be exposed. + **RUNNING THE TOOL** To run this tool as a docker container: diff --git a/cluster/images/etcd-version-monitor/etcd-version-monitor.go b/cluster/images/etcd-version-monitor/etcd-version-monitor.go index f455cde3d37..d87f895730a 100644 --- a/cluster/images/etcd-version-monitor/etcd-version-monitor.go +++ b/cluster/images/etcd-version-monitor/etcd-version-monitor.go @@ -25,6 +25,8 @@ import ( "github.com/golang/glog" "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + dto "github.com/prometheus/client_model/go" "github.com/prometheus/common/expfmt" "github.com/spf13/pflag" ) @@ -52,6 +54,11 @@ const ( // Initialize prometheus metrics to be exported. var ( + // Register all custom metrics with a dedicated registry to keep them separate. + customMetricRegistry = prometheus.NewRegistry() + + // Custom etcd version metric since etcd 3.2- does not export one. + // This will be replaced by https://github.com/coreos/etcd/pull/8960 in etcd 3.3. etcdVersion = prometheus.NewGaugeVec( prometheus.GaugeOpts{ Namespace: namespace, @@ -59,15 +66,122 @@ var ( Help: "Etcd server's binary version", }, []string{"binary_version"}) - etcdGRPCRequestsTotal = prometheus.NewCounterVec( - prometheus.CounterOpts{ - Namespace: namespace, - Name: "grpc_requests_total", - Help: "Counter of received grpc requests, labeled by the grpc method and service names", + + gatherer = &monitorGatherer{ + // Rewrite rules for etcd metrics that are exported by default. + exported: map[string]*exportedMetric{ + // etcd 3.0 metric format for total grpc requests with renamed method and service labels. + "etcd_grpc_requests_total": { + rewriters: []rewriteFunc{ + func(mf *dto.MetricFamily) (*dto.MetricFamily, error) { + mf = deepCopyMetricFamily(mf) + renameLabels(mf, map[string]string{ + "grpc_method": "method", + "grpc_service": "service", + }) + return mf, nil + }, + }, + }, + // etcd 3.1+ metric format for total grpc requests. + "grpc_server_handled_total": { + rewriters: []rewriteFunc{ + // Export the metric exactly as-is. For 3.1+ metrics, we will + // pass all metrics directly through. + identity, + // Write to the etcd 3.0 metric format for backward compatibility. + func(mf *dto.MetricFamily) (*dto.MetricFamily, error) { + mf = deepCopyMetricFamily(mf) + renameMetric(mf, "etcd_grpc_requests_total") + renameLabels(mf, map[string]string{ + "grpc_method": "method", + "grpc_service": "service", + }) + return mf, nil + }, + }, + }, + + // etcd 3.0 metric format for grpc request latencies, + // rewritten to the etcd 3.1+ format. + "etcd_grpc_unary_requests_duration_seconds": { + rewriters: []rewriteFunc{ + func(mf *dto.MetricFamily) (*dto.MetricFamily, error) { + mf = deepCopyMetricFamily(mf) + renameMetric(mf, "grpc_server_handling_seconds") + tpeName := "grpc_type" + tpeVal := "unary" + for _, m := range mf.Metric { + m.Label = append(m.Label, &dto.LabelPair{Name: &tpeName, Value: &tpeVal}) + } + return mf, nil + }, + }, + }, + // etcd 3.1+ metric format for total grpc requests. + "grpc_server_handling_seconds": {}, }, - []string{"method", "service"}) + } ) +// monitorGatherer is a custom metric gatherer for prometheus that exports custom metrics +// defined by this monitor as well as rewritten etcd metrics. +type monitorGatherer struct { + exported map[string]*exportedMetric +} + +// exportedMetric identifies a metric that is exported and defines how it is rewritten before +// it is exported. +type exportedMetric struct { + rewriters []rewriteFunc +} + +// rewriteFunc rewrites metrics before they are exported. +type rewriteFunc func(mf *dto.MetricFamily) (*dto.MetricFamily, error) + +func (m *monitorGatherer) Gather() ([]*dto.MetricFamily, error) { + etcdMetrics, err := scrapeMetrics() + if err != nil { + return nil, err + } + exported, err := m.rewriteExportedMetrics(etcdMetrics) + if err != nil { + return nil, err + } + custom, err := customMetricRegistry.Gather() + if err != nil { + return nil, err + } + result := make([]*dto.MetricFamily, 0, len(exported)+len(custom)) + result = append(result, exported...) + result = append(result, custom...) + return result, nil +} + +func (m *monitorGatherer) rewriteExportedMetrics(metrics map[string]*dto.MetricFamily) ([]*dto.MetricFamily, error) { + results := make([]*dto.MetricFamily, 0, len(metrics)) + for n, mf := range metrics { + if e, ok := m.exported[n]; ok { + // Apply rewrite rules for metrics that have them. + if e.rewriters == nil { + results = append(results, mf) + } else { + for _, rewriter := range e.rewriters { + new, err := rewriter(mf) + if err != nil { + return nil, err + } + results = append(results, new) + } + } + } else { + // Proxy all metrics without any rewrite rules directly. + results = append(results, mf) + } + } + return results, nil +} + // Struct for unmarshalling the json response from etcd's /version endpoint. type EtcdVersion struct { BinaryVersion string `json:"etcdserver"` @@ -132,83 +246,78 @@ func getVersionPeriodically(stopCh <-chan struct{}) { } } -// Struct for storing labels for gRPC request types. -type GRPCRequestLabels struct { - Method string - Service string -} - -// Function for fetching etcd grpc request counts and feeding it to the prometheus metric. -func getGRPCRequestCount(lastRecordedCount *map[GRPCRequestLabels]float64) error { - // Create the get request for the etcd metrics endpoint. +// scrapeMetrics scrapes the prometheus metrics from the etcd metrics URI. +func scrapeMetrics() (map[string]*dto.MetricFamily, error) { req, err := http.NewRequest("GET", etcdMetricsScrapeURI, nil) if err != nil { - return fmt.Errorf("Failed to create GET request for etcd metrics: %v", err) + return nil, fmt.Errorf("Failed to create GET request for etcd metrics: %v", err) } // Send the get request and receive a response. client := &http.Client{} resp, err := client.Do(req) if err != nil { - return fmt.Errorf("Failed to receive GET response for etcd metrics: %v", err) + return nil, fmt.Errorf("Failed to receive GET response for etcd metrics: %v", err) } defer resp.Body.Close() // Parse the metrics in text format to a MetricFamily struct. var textParser expfmt.TextParser - metricFamilies, err := textParser.TextToMetricFamilies(resp.Body) - if err != nil { - return fmt.Errorf("Failed to parse etcd metrics: %v", err) - } - - // Look through the grpc requests metric family and update our promotheus metric. - for _, metric := range metricFamilies["etcd_grpc_requests_total"].GetMetric() { - var grpcRequestLabels GRPCRequestLabels - for _, label := range metric.GetLabel() { - if label.GetName() == "grpc_method" { - grpcRequestLabels.Method = label.GetValue() - } - if label.GetName() == "grpc_service" { - grpcRequestLabels.Service = label.GetValue() - } - } - if grpcRequestLabels.Method == "" || grpcRequestLabels.Service == "" { - return fmt.Errorf("Could not get value for grpc_method and/or grpc_service label") - } - - // Get last recorded value and new value of the metric and update it suitably. - previousMetricValue := 0.0 - if value, ok := (*lastRecordedCount)[grpcRequestLabels]; ok { - previousMetricValue = value - } - newMetricValue := metric.GetCounter().GetValue() - (*lastRecordedCount)[grpcRequestLabels] = newMetricValue - if newMetricValue >= previousMetricValue { - etcdGRPCRequestsTotal.With(prometheus.Labels{ - "method": grpcRequestLabels.Method, - "service": grpcRequestLabels.Service, - }).Add(newMetricValue - previousMetricValue) - } - } - return nil + return textParser.TextToMetricFamilies(resp.Body) } -// Function for periodically fetching etcd GRPC request counts. -func getGRPCRequestCountPeriodically(stopCh <-chan struct{}) { - // This map stores last recorded count for a given grpc request type. - lastRecordedCount := make(map[GRPCRequestLabels]float64) - for { - if err := getGRPCRequestCount(&lastRecordedCount); err != nil { - glog.Errorf("Failed to fetch etcd grpc request counts: %v", err) - } - select { - case <-stopCh: - break - case <-time.After(scrapeTimeout): +func renameMetric(mf *dto.MetricFamily, name string) { + mf.Name = &name +} + +func renameLabels(mf *dto.MetricFamily, nameMapping map[string]string) { + for _, m := range mf.Metric { + for _, lbl := range m.Label { + if alias, ok := nameMapping[*lbl.Name]; ok { + lbl.Name = &alias + } } } } +func identity(mf *dto.MetricFamily) (*dto.MetricFamily, error) { + return mf, nil +} + +func deepCopyMetricFamily(mf *dto.MetricFamily) *dto.MetricFamily { + r := &dto.MetricFamily{} + r.Name = mf.Name + r.Help = mf.Help + r.Type = mf.Type + r.Metric = make([]*dto.Metric, len(mf.Metric)) + for i, m := range mf.Metric { + r.Metric[i] = deepCopyMetric(m) + } + return r +} + +func deepCopyMetric(m *dto.Metric) *dto.Metric { + r := &dto.Metric{} + r.Label = make([]*dto.LabelPair, len(m.Label)) + for i, lp := range m.Label { + r.Label[i] = deepCopyLabelPair(lp) + } + r.Gauge = m.Gauge + r.Counter = m.Counter + r.Summary = m.Summary + r.Untyped = m.Untyped + r.Histogram = m.Histogram + r.TimestampMs = m.TimestampMs + return r +} + +func deepCopyLabelPair(lp *dto.LabelPair) *dto.LabelPair { + r := &dto.LabelPair{} + r.Name = lp.Name + r.Value = lp.Value + return r +} + func main() { // Register the commandline flags passed to the tool. registerFlags(pflag.CommandLine) @@ -216,18 +325,16 @@ func main() { pflag.Parse() // Register the metrics we defined above with prometheus. - prometheus.MustRegister(etcdVersion) - prometheus.MustRegister(etcdGRPCRequestsTotal) - prometheus.Unregister(prometheus.NewGoCollector()) + customMetricRegistry.MustRegister(etcdVersion) + customMetricRegistry.Unregister(prometheus.NewGoCollector()) // Spawn threads for periodically scraping etcd version metrics. stopCh := make(chan struct{}) defer close(stopCh) go getVersionPeriodically(stopCh) - go getGRPCRequestCountPeriodically(stopCh) // Serve our metrics on listenAddress/metricsPath. glog.Infof("Listening on: %v", listenAddress) - http.Handle(metricsPath, prometheus.UninstrumentedHandler()) + http.Handle(metricsPath, promhttp.HandlerFor(gatherer, promhttp.HandlerOpts{})) glog.Errorf("Stopped listening/serving metrics: %v", http.ListenAndServe(listenAddress, nil)) } diff --git a/cluster/images/etcd/Makefile b/cluster/images/etcd/Makefile index b32a266f764..f972fbb757f 100644 --- a/cluster/images/etcd/Makefile +++ b/cluster/images/etcd/Makefile @@ -15,7 +15,7 @@ # Build the etcd image # # Usage: -# [TAGS=2.2.1 2.3.7 3.0.17 3.1.10] [REGISTRY=gcr.io/google_containers] [ARCH=amd64] [BASEIMAGE=busybox] make (build|push) +# [TAGS=2.2.1 2.3.7 3.0.17 3.1.11] [REGISTRY=gcr.io/google_containers] [ARCH=amd64] [BASEIMAGE=busybox] make (build|push) # The image contains different etcd versions to simplify # upgrades. Thus be careful when removing any tag from here. @@ -26,8 +26,8 @@ # Except from etcd-$(tag) and etcdctl-$(tag) binaries, we also # need etcd and etcdctl binaries for backward compatibility reasons. # That binary will be set to the last tag from $(TAGS). -TAGS?=2.2.1 2.3.7 3.0.17 3.1.10 -REGISTRY_TAG?=3.1.10 +TAGS?=2.2.1 2.3.7 3.0.17 3.1.11 +REGISTRY_TAG?=3.1.11 ARCH?=amd64 REGISTRY?=gcr.io/google_containers GOLANG_VERSION?=1.7.6 @@ -53,7 +53,7 @@ endif build: # Copy the content in this dir to the temp dir, # without copying the subdirectories. - find ./ -maxdepth 1 -type f | xargs cp -t $(TEMP_DIR) + find ./ -maxdepth 1 -type f | xargs -I {} cp {} $(TEMP_DIR) # Compile attachlease docker run -i -v $(shell pwd)/../../../:/go/src/k8s.io/kubernetes -v $(TEMP_DIR):/build -e GOARCH=$(ARCH) golang:$(GOLANG_VERSION) \ diff --git a/cluster/images/etcd/attachlease/BUILD b/cluster/images/etcd/attachlease/BUILD index 0e2cb9efbaf..abb4a3c831e 100644 --- a/cluster/images/etcd/attachlease/BUILD +++ b/cluster/images/etcd/attachlease/BUILD @@ -8,8 +8,8 @@ load( go_binary( name = "attachlease", + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cluster/images/etcd/attachlease", - library = ":go_default_library", ) go_library( diff --git a/cluster/images/etcd/rollback/BUILD b/cluster/images/etcd/rollback/BUILD index bdf4514b563..252e974829e 100644 --- a/cluster/images/etcd/rollback/BUILD +++ b/cluster/images/etcd/rollback/BUILD @@ -8,8 +8,8 @@ load( go_binary( name = "rollback", + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cluster/images/etcd/rollback", - library = ":go_default_library", ) go_library( diff --git a/cluster/images/hyperkube/BUILD b/cluster/images/hyperkube/BUILD index 20a1873c20e..6ca2ff6d61e 100644 --- a/cluster/images/hyperkube/BUILD +++ b/cluster/images/hyperkube/BUILD @@ -11,16 +11,12 @@ docker_build( for path in [ "/apiserver", "/controller-manager", - "/federation-apiserver", - "/federation-controller-manager", "/kubectl", "/kubelet", "/proxy", "/scheduler", "/usr/local/bin/kube-apiserver", "/usr/local/bin/kube-controller-manager", - "/usr/local/bin/federation-apiserver", - "/usr/local/bin/federation-controller-manager", "/usr/local/bin/kubectl", "/usr/local/bin/kubelet", "/usr/local/bin/kube-proxy", diff --git a/cluster/images/hyperkube/Dockerfile b/cluster/images/hyperkube/Dockerfile index bde5343c147..71d2300a04b 100644 --- a/cluster/images/hyperkube/Dockerfile +++ b/cluster/images/hyperkube/Dockerfile @@ -15,23 +15,20 @@ FROM BASEIMAGE # Create symlinks for each hyperkube server -# Also create symlinks to /usr/local/bin/ where the server image binaries live, so the hyperkube image may be +# Also create symlinks to /usr/local/bin/ where the server image binaries live, so the hyperkube image may be # used instead of gcr.io/google_containers/kube-* without any modifications. # TODO: replace manual symlink creation with --make-symlink command once # cross-building with qemu supports go binaries. See #28702 # RUN /hyperkube --make-symlinks RUN ln -s /hyperkube /apiserver \ && ln -s /hyperkube /controller-manager \ - && ln -s /hyperkube /federation-apiserver \ - && ln -s /hyperkube /federation-controller-manager \ && ln -s /hyperkube /kubectl \ && ln -s /hyperkube /kubelet \ && ln -s /hyperkube /proxy \ && ln -s /hyperkube /scheduler \ + && ln -s /hyperkube /aggerator \ && ln -s /hyperkube /usr/local/bin/kube-apiserver \ && ln -s /hyperkube /usr/local/bin/kube-controller-manager \ - && ln -s /hyperkube /usr/local/bin/federation-apiserver \ - && ln -s /hyperkube /usr/local/bin/federation-controller-manager \ && ln -s /hyperkube /usr/local/bin/kubectl \ && ln -s /hyperkube /usr/local/bin/kubelet \ && ln -s /hyperkube /usr/local/bin/kube-proxy \ diff --git a/cluster/images/hyperkube/Makefile b/cluster/images/hyperkube/Makefile index d84e854752b..ccecb9b80e0 100644 --- a/cluster/images/hyperkube/Makefile +++ b/cluster/images/hyperkube/Makefile @@ -19,9 +19,10 @@ REGISTRY?=gcr.io/google-containers ARCH?=amd64 -HYPERKUBE_BIN?=_output/dockerized/bin/linux/$(ARCH)/hyperkube +OUT_DIR?=_output +HYPERKUBE_BIN?=$(OUT_DIR)/dockerized/bin/linux/$(ARCH)/hyperkube -BASEIMAGE=gcr.io/google-containers/debian-hyperkube-base-$(ARCH):0.4 +BASEIMAGE=gcr.io/google-containers/debian-hyperkube-base-$(ARCH):0.8 TEMP_DIR:=$(shell mktemp -d -t hyperkubeXXXXXX) all: build diff --git a/cluster/juju/layers/kubeapi-load-balancer/config.yaml b/cluster/juju/layers/kubeapi-load-balancer/config.yaml index ad33ef60adc..a4678ae02f3 100644 --- a/cluster/juju/layers/kubeapi-load-balancer/config.yaml +++ b/cluster/juju/layers/kubeapi-load-balancer/config.yaml @@ -3,3 +3,9 @@ options: type: int default: 443 description: The port to run the loadbalancer + extra_sans: + type: string + default: "" + description: | + Space-separated list of extra SAN entries to add to the x509 certificate + created for the load balancers. diff --git a/cluster/juju/layers/kubeapi-load-balancer/reactive/load_balancer.py b/cluster/juju/layers/kubeapi-load-balancer/reactive/load_balancer.py index 8ed601e3cd3..ddb3845a10a 100644 --- a/cluster/juju/layers/kubeapi-load-balancer/reactive/load_balancer.py +++ b/cluster/juju/layers/kubeapi-load-balancer/reactive/load_balancer.py @@ -21,10 +21,14 @@ import subprocess from charms import layer from charms.reactive import when, when_any, when_not from charms.reactive import set_state, remove_state +from charms.reactive import hook from charmhelpers.core import hookenv +from charmhelpers.core import host from charmhelpers.contrib.charmsupport import nrpe +from charms.reactive.helpers import data_changed from charms.layer import nginx +from charms.layer import tls_client from subprocess import Popen from subprocess import PIPE @@ -32,6 +36,26 @@ from subprocess import STDOUT from subprocess import CalledProcessError +apilb_nginx = """/var/log/nginx.*.log { + daily + missingok + rotate 14 + compress + delaycompress + notifempty + create 0640 www-data adm + sharedscripts + prerotate + if [ -d /etc/logrotate.d/httpd-prerotate ]; then \\ + run-parts /etc/logrotate.d/httpd-prerotate; \\ + fi \\ + endscript + postrotate + invoke-rc.d nginx rotate >/dev/null 2>&1 + endscript +}""" + + @when('certificates.available') def request_server_certificates(tls): '''Send the data that is required to create a server certificate for @@ -44,12 +68,36 @@ def request_server_certificates(tls): hookenv.unit_private_ip(), socket.gethostname(), ] + # maybe they have extra names they want as SANs + extra_sans = hookenv.config('extra_sans') + if extra_sans and not extra_sans == "": + sans.extend(extra_sans.split()) # Create a path safe name by removing path characters from the unit name. certificate_name = hookenv.local_unit().replace('/', '_') # Request a server cert with this information. tls.request_server_cert(common_name, sans, certificate_name) +@when('config.changed.extra_sans', 'certificates.available') +def update_certificate(tls): + # Using the config.changed.extra_sans flag to catch changes. + # IP changes will take ~5 minutes or so to propagate, but + # it will update. + request_server_certificates(tls) + + +@when('certificates.server.cert.available', + 'nginx.available', 'tls_client.server.certificate.written') +def kick_nginx(tls): + # we are just going to sighup it, but still want to avoid kicking it + # without need + if data_changed('cert', tls.get_server_cert()): + # certificate changed, so sighup nginx + hookenv.log("Certificate information changed, sending SIGHUP to nginx") + host.service_restart('nginx') + tls_client.reset_certificate_write_flag('server') + + @when('config.changed.port') def close_old_port(): config = hookenv.config() @@ -62,6 +110,14 @@ def close_old_port(): hookenv.log('Port %d already closed, skipping.' % old_port) +def maybe_write_apilb_logrotate_config(): + filename = '/etc/logrotate.d/apilb_nginx' + if not os.path.exists(filename): + # Set log rotation for apilb log file + with open(filename, 'w+') as fp: + fp.write(apilb_nginx) + + @when('nginx.available', 'apiserver.available', 'certificates.server.cert.available') def install_load_balancer(apiserver, tls): @@ -96,9 +152,16 @@ def install_load_balancer(apiserver, tls): server_certificate=server_cert_path, server_key=server_key_path, ) + + maybe_write_apilb_logrotate_config() hookenv.status_set('active', 'Loadbalancer ready.') +@hook('upgrade-charm') +def upgrade_charm(): + maybe_write_apilb_logrotate_config() + + @when('nginx.available') def set_nginx_version(): ''' Surface the currently deployed version of nginx to Juju ''' diff --git a/cluster/juju/layers/kubernetes-e2e/config.yaml b/cluster/juju/layers/kubernetes-e2e/config.yaml index bf1ba66a77c..d765c028713 100644 --- a/cluster/juju/layers/kubernetes-e2e/config.yaml +++ b/cluster/juju/layers/kubernetes-e2e/config.yaml @@ -1,6 +1,6 @@ options: channel: type: string - default: "1.8/stable" + default: "1.9/stable" description: | Snap channel to install Kubernetes snaps from diff --git a/cluster/juju/layers/kubernetes-e2e/reactive/kubernetes_e2e.py b/cluster/juju/layers/kubernetes-e2e/reactive/kubernetes_e2e.py index 76a97aa0d63..0cede674399 100644 --- a/cluster/juju/layers/kubernetes-e2e/reactive/kubernetes_e2e.py +++ b/cluster/juju/layers/kubernetes-e2e/reactive/kubernetes_e2e.py @@ -22,14 +22,18 @@ from charms.reactive import is_state from charms.reactive import set_state from charms.reactive import when from charms.reactive import when_not +from charms.reactive.helpers import data_changed -from charmhelpers.core import hookenv +from charmhelpers.core import hookenv, unitdata from shlex import split from subprocess import check_call from subprocess import check_output +db = unitdata.kv() +USER = 'system:e2e' + @hook('upgrade-charm') def reset_delivery_states(): @@ -87,15 +91,16 @@ def install_snaps(): @when('tls_client.ca.saved', 'tls_client.client.certificate.saved', 'tls_client.client.key.saved', 'kubernetes-master.available', - 'kubernetes-e2e.installed', 'kube-control.auth.available') + 'kubernetes-e2e.installed', 'e2e.auth.bootstrapped') @when_not('kubeconfig.ready') -def prepare_kubeconfig_certificates(master, kube_control): +def prepare_kubeconfig_certificates(master): ''' Prepare the data to feed to create the kubeconfig file. ''' layer_options = layer.options('tls-client') # Get all the paths to the tls information required for kubeconfig. ca = layer_options.get('ca_certificate_path') - creds = kube_control.get_auth_credentials() + creds = db.get('credentials') + data_changed('kube-control.creds', creds) servers = get_kube_api_servers(master) @@ -118,11 +123,22 @@ def prepare_kubeconfig_certificates(master, kube_control): def request_credentials(kube_control): """ Request authorization creds.""" - # The kube-cotrol interface is created to support RBAC. - # At this point we might as well do the right thing and return the hostname - # even if it will only be used when we enable RBAC - user = 'system:masters' - kube_control.set_auth_request(user) + # Ask for a user, although we will be using the 'client_token' + kube_control.set_auth_request(USER) + + +@when('kube-control.auth.available') +def catch_change_in_creds(kube_control): + """Request a service restart in case credential updates were detected.""" + creds = kube_control.get_auth_credentials(USER) + if creds \ + and data_changed('kube-control.creds', creds) \ + and creds['user'] == USER: + # We need to cache the credentials here because if the + # master changes (master leader dies and replaced by a new one) + # the new master will have no recollection of our certs. + db.set('credentials', creds) + set_state('e2e.auth.bootstrapped') @when('kubernetes-e2e.installed', 'kubeconfig.ready') diff --git a/cluster/juju/layers/kubernetes-master/README.md b/cluster/juju/layers/kubernetes-master/README.md index c1738869a8d..c1cc84a6cf0 100644 --- a/cluster/juju/layers/kubernetes-master/README.md +++ b/cluster/juju/layers/kubernetes-master/README.md @@ -54,6 +54,10 @@ The domain name to use for the Kubernetes cluster for DNS. Enables the installation of Kubernetes dashboard, Heapster, Grafana, and InfluxDB. +#### enable-rbac + +Enable RBAC and Node authorisation. + # DNS for the cluster The DNS add-on allows the pods to have a DNS names in addition to IP addresses. diff --git a/cluster/juju/layers/kubernetes-master/actions.yaml b/cluster/juju/layers/kubernetes-master/actions.yaml index d2f6f495dd6..cfb74c248fc 100644 --- a/cluster/juju/layers/kubernetes-master/actions.yaml +++ b/cluster/juju/layers/kubernetes-master/actions.yaml @@ -46,3 +46,5 @@ namespace-delete: minLength: 2 required: - name +upgrade: + description: Upgrade the kubernetes snaps \ No newline at end of file diff --git a/cluster/juju/layers/kubernetes-master/actions/upgrade b/cluster/juju/layers/kubernetes-master/actions/upgrade new file mode 100755 index 00000000000..7a115293b59 --- /dev/null +++ b/cluster/juju/layers/kubernetes-master/actions/upgrade @@ -0,0 +1,5 @@ +#!/bin/sh +set -eux + +charms.reactive set_state kubernetes-master.upgrade-specified +exec hooks/config-changed diff --git a/cluster/juju/layers/kubernetes-master/config.yaml b/cluster/juju/layers/kubernetes-master/config.yaml index c328a43751c..208032002cc 100644 --- a/cluster/juju/layers/kubernetes-master/config.yaml +++ b/cluster/juju/layers/kubernetes-master/config.yaml @@ -3,10 +3,20 @@ options: type: boolean default: True description: Deploy the Kubernetes Dashboard and Heapster addons + enable-kube-dns: + type: boolean + default: True + description: Deploy kube-dns addon dns_domain: type: string default: cluster.local description: The local domain for cluster dns + extra_sans: + type: string + default: "" + description: | + Space-separated list of extra SAN entries to add to the x509 certificate + created for the master nodes. service-cidr: type: string default: 10.152.183.0/24 @@ -23,7 +33,7 @@ options: detected on a worker node. channel: type: string - default: "1.8/stable" + default: "1.9/stable" description: | Snap channel to install Kubernetes master services from client_password: @@ -40,3 +50,33 @@ options: runtime-config=batch/v2alpha1=true profiling=true will result in kube-apiserver being run with the following options: --runtime-config=batch/v2alpha1=true --profiling=true + controller-manager-extra-args: + type: string + default: "" + description: | + Space separated list of flags and key=value pairs that will be passed as arguments to + kube-controller-manager. For example a value like this: + runtime-config=batch/v2alpha1=true profiling=true + will result in kube-controller-manager being run with the following options: + --runtime-config=batch/v2alpha1=true --profiling=true + scheduler-extra-args: + type: string + default: "" + description: | + Space separated list of flags and key=value pairs that will be passed as arguments to + kube-scheduler. For example a value like this: + runtime-config=batch/v2alpha1=true profiling=true + will result in kube-scheduler being run with the following options: + --runtime-config=batch/v2alpha1=true --profiling=true + authorization-mode: + type: string + default: "AlwaysAllow" + description: | + Comma separated authorization modes. Allowed values are + "RBAC", "Node", "Webhook", "ABAC", "AlwaysDeny" and "AlwaysAllow". + require-manual-upgrade: + type: boolean + default: true + description: | + When true, master nodes will not be upgraded until the user triggers + it manually by running the upgrade action. diff --git a/cluster/juju/layers/kubernetes-master/lib/charms/kubernetes/flagmanager.py b/cluster/juju/layers/kubernetes-master/lib/charms/kubernetes/flagmanager.py deleted file mode 100644 index 7fe5737a6ef..00000000000 --- a/cluster/juju/layers/kubernetes-master/lib/charms/kubernetes/flagmanager.py +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env python - -# 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. - -from charmhelpers.core import unitdata - - -class FlagManager: - ''' - FlagManager - A Python class for managing the flags to pass to an - application without remembering what's been set previously. - - This is a blind class assuming the operator knows what they are doing. - Each instance of this class should be initialized with the intended - application to manage flags. Flags are then appended to a data-structure - and cached in unitdata for later recall. - - THe underlying data-provider is backed by a SQLITE database on each unit, - tracking the dictionary, provided from the 'charmhelpers' python package. - Summary: - opts = FlagManager('docker') - opts.add('bip', '192.168.22.2') - opts.to_s() - ''' - - def __init__(self, daemon, opts_path=None): - self.db = unitdata.kv() - self.daemon = daemon - if not self.db.get(daemon): - self.data = {} - else: - self.data = self.db.get(daemon) - - def __save(self): - self.db.set(self.daemon, self.data) - - def add(self, key, value, strict=False): - ''' - Adds data to the map of values for the DockerOpts file. - Supports single values, or "multiopt variables". If you - have a flag only option, like --tlsverify, set the value - to None. To preserve the exact value, pass strict - eg: - opts.add('label', 'foo') - opts.add('label', 'foo, bar, baz') - opts.add('flagonly', None) - opts.add('cluster-store', 'consul://a:4001,b:4001,c:4001/swarm', - strict=True) - ''' - if strict: - self.data['{}-strict'.format(key)] = value - self.__save() - return - - if value: - values = [x.strip() for x in value.split(',')] - # handle updates - if key in self.data and self.data[key] is not None: - item_data = self.data[key] - for c in values: - c = c.strip() - if c not in item_data: - item_data.append(c) - self.data[key] = item_data - else: - # handle new - self.data[key] = values - else: - # handle flagonly - self.data[key] = None - self.__save() - - def remove(self, key, value): - ''' - Remove a flag value from the DockerOpts manager - Assuming the data is currently {'foo': ['bar', 'baz']} - d.remove('foo', 'bar') - > {'foo': ['baz']} - :params key: - :params value: - ''' - self.data[key].remove(value) - self.__save() - - def destroy(self, key, strict=False): - ''' - Destructively remove all values and key from the FlagManager - Assuming the data is currently {'foo': ['bar', 'baz']} - d.wipe('foo') - >{} - :params key: - :params strict: - ''' - try: - if strict: - self.data.pop('{}-strict'.format(key)) - else: - self.data.pop(key) - self.__save() - except KeyError: - pass - - def get(self, key, default=None): - """Return the value for ``key``, or the default if ``key`` doesn't exist. - - """ - return self.data.get(key, default) - - def destroy_all(self): - ''' - Destructively removes all data from the FlagManager. - ''' - self.data.clear() - self.__save() - - def to_s(self): - ''' - Render the flags to a single string, prepared for the Docker - Defaults file. Typically in /etc/default/docker - d.to_s() - > "--foo=bar --foo=baz" - ''' - flags = [] - for key in self.data: - if self.data[key] is None: - # handle flagonly - flags.append("{}".format(key)) - elif '-strict' in key: - # handle strict values, and do it in 2 steps. - # If we rstrip -strict it strips a tailing s - proper_key = key.rstrip('strict').rstrip('-') - flags.append("{}={}".format(proper_key, self.data[key])) - else: - # handle multiopt and typical flags - for item in self.data[key]: - flags.append("{}={}".format(key, item)) - return ' '.join(flags) diff --git a/cluster/juju/layers/kubernetes-master/reactive/kubernetes_master.py b/cluster/juju/layers/kubernetes-master/reactive/kubernetes_master.py index b6430637d47..d27caf59f57 100644 --- a/cluster/juju/layers/kubernetes-master/reactive/kubernetes_master.py +++ b/cluster/juju/layers/kubernetes-master/reactive/kubernetes_master.py @@ -26,6 +26,8 @@ import ipaddress import charms.leadership +from shutil import move + from shlex import split from subprocess import check_call from subprocess import check_output @@ -37,11 +39,12 @@ from charms.reactive import hook from charms.reactive import remove_state from charms.reactive import set_state from charms.reactive import is_state -from charms.reactive import when, when_any, when_not, when_all +from charms.reactive import when, when_any, when_not from charms.reactive.helpers import data_changed, any_file_changed from charms.kubernetes.common import get_version from charms.kubernetes.common import retry -from charms.kubernetes.flagmanager import FlagManager + +from charms.layer import tls_client from charmhelpers.core import hookenv from charmhelpers.core import host @@ -60,6 +63,22 @@ nrpe.Check.shortname_re = '[\.A-Za-z0-9-_]+$' os.environ['PATH'] += os.pathsep + os.path.join(os.sep, 'snap', 'bin') +def set_upgrade_needed(forced=False): + set_state('kubernetes-master.upgrade-needed') + config = hookenv.config() + previous_channel = config.previous('channel') + require_manual = config.get('require-manual-upgrade') + hookenv.log('set upgrade needed') + if previous_channel is None or not require_manual or forced: + hookenv.log('forcing upgrade') + set_state('kubernetes-master.upgrade-specified') + + +@when('config.changed.channel') +def channel_changed(): + set_upgrade_needed() + + def service_cidr(): ''' Return the charm's service-cidr config ''' db = unitdata.kv() @@ -75,12 +94,77 @@ def freeze_service_cidr(): @hook('upgrade-charm') -def reset_states_for_delivery(): +def check_for_upgrade_needed(): '''An upgrade charm event was triggered by Juju, react to that here.''' + hookenv.status_set('maintenance', 'Checking resources') + migrate_from_pre_snaps() - install_snaps() + add_rbac_roles() set_state('reconfigure.authentication.setup') remove_state('authentication.setup') + changed = snap_resources_changed() + if changed == 'yes': + set_upgrade_needed() + elif changed == 'unknown': + # We are here on an upgrade from non-rolling master + # Since this upgrade might also include resource updates eg + # juju upgrade-charm kubernetes-master --resource kube-any=my.snap + # we take no risk and forcibly upgrade the snaps. + # Forcibly means we do not prompt the user to call the upgrade action. + set_upgrade_needed(forced=True) + + +def snap_resources_changed(): + ''' + Check if the snapped resources have changed. The first time this method is + called will report "unknown". + + Returns: "yes" in case a snap resource file has changed, + "no" in case a snap resources are the same as last call, + "unknown" if it is the first time this method is called + + ''' + db = unitdata.kv() + resources = ['kubectl', 'kube-apiserver', 'kube-controller-manager', + 'kube-scheduler', 'cdk-addons'] + paths = [hookenv.resource_get(resource) for resource in resources] + if db.get('snap.resources.fingerprint.initialised'): + result = 'yes' if any_file_changed(paths) else 'no' + return result + else: + db.set('snap.resources.fingerprint.initialised', True) + any_file_changed(paths) + return 'unknown' + + +def add_rbac_roles(): + '''Update the known_tokens file with proper groups.''' + + tokens_fname = '/root/cdk/known_tokens.csv' + tokens_backup_fname = '/root/cdk/known_tokens.csv.backup' + move(tokens_fname, tokens_backup_fname) + with open(tokens_fname, 'w') as ftokens: + with open(tokens_backup_fname, 'r') as stream: + for line in stream: + record = line.strip().split(',') + # token, username, user, groups + if record[2] == 'admin' and len(record) == 3: + towrite = '{0},{1},{2},"{3}"\n'.format(record[0], + record[1], + record[2], + 'system:masters') + ftokens.write(towrite) + continue + if record[2] == 'kube_proxy': + towrite = '{0},{1},{2}\n'.format(record[0], + 'system:kube-proxy', + 'kube-proxy') + ftokens.write(towrite) + continue + if record[2] == 'kubelet' and record[1] == 'kubelet': + continue + + ftokens.write('{}'.format(line)) def rename_file_idempotent(source, destination): @@ -137,10 +221,19 @@ def migrate_from_pre_snaps(): hookenv.log("Removing file: " + file) os.remove(file) - # clear the flag managers - FlagManager('kube-apiserver').destroy_all() - FlagManager('kube-controller-manager').destroy_all() - FlagManager('kube-scheduler').destroy_all() + +@when('kubernetes-master.upgrade-needed') +@when_not('kubernetes-master.upgrade-specified') +def upgrade_needed_status(): + msg = 'Needs manual upgrade, run the upgrade action' + hookenv.status_set('blocked', msg) + + +@when('kubernetes-master.upgrade-specified') +def do_upgrade(): + install_snaps() + remove_state('kubernetes-master.upgrade-needed') + remove_state('kubernetes-master.upgrade-specified') def install_snaps(): @@ -156,15 +249,11 @@ def install_snaps(): snap.install('kube-scheduler', channel=channel) hookenv.status_set('maintenance', 'Installing cdk-addons snap') snap.install('cdk-addons', channel=channel) + snap_resources_changed() set_state('kubernetes-master.snaps.installed') remove_state('kubernetes-master.components.started') -@when('config.changed.channel') -def channel_changed(): - install_snaps() - - @when('config.changed.client_password', 'leadership.is_leader') def password_changed(): """Handle password change via the charms config.""" @@ -193,15 +282,10 @@ def configure_cni(cni): @when_not('authentication.setup') def setup_leader_authentication(): '''Setup basic authentication and token access for the cluster.''' - api_opts = FlagManager('kube-apiserver') - controller_opts = FlagManager('kube-controller-manager') - service_key = '/root/cdk/serviceaccount.key' basic_auth = '/root/cdk/basic_auth.csv' known_tokens = '/root/cdk/known_tokens.csv' - api_opts.add('basic-auth-file', basic_auth) - api_opts.add('token-auth-file', known_tokens) hookenv.status_set('maintenance', 'Rendering authentication templates.') keys = [service_key, basic_auth, known_tokens] @@ -209,12 +293,10 @@ def setup_leader_authentication(): if not get_keys_from_leader(keys) \ or is_state('reconfigure.authentication.setup'): last_pass = get_password('basic_auth.csv', 'admin') - setup_basic_auth(last_pass, 'admin', 'admin') + setup_basic_auth(last_pass, 'admin', 'admin', 'system:masters') if not os.path.isfile(known_tokens): - setup_tokens(None, 'admin', 'admin') - setup_tokens(None, 'kubelet', 'kubelet') - setup_tokens(None, 'kube_proxy', 'kube_proxy') + touch(known_tokens) # Generate the default service account token key os.makedirs('/root/cdk', exist_ok=True) @@ -224,9 +306,6 @@ def setup_leader_authentication(): check_call(cmd) remove_state('reconfigure.authentication.setup') - api_opts.add('service-account-key-file', service_key) - controller_opts.add('service-account-private-key-file', service_key) - # read service account key for syndication leader_data = {} for f in [known_tokens, basic_auth, service_key]: @@ -261,13 +340,6 @@ def setup_non_leader_authentication(): return hookenv.status_set('maintenance', 'Rendering authentication templates.') - api_opts = FlagManager('kube-apiserver') - api_opts.add('basic-auth-file', basic_auth) - api_opts.add('token-auth-file', known_tokens) - api_opts.add('service-account-key-file', service_key) - - controller_opts = FlagManager('kube-controller-manager') - controller_opts.add('service-account-private-key-file', service_key) remove_state('kubernetes-master.components.started') set_state('authentication.setup') @@ -302,6 +374,7 @@ def get_keys_from_leader(keys, overwrite_local=False): # Write out the file and move on to the next item with open(k, 'w+') as fp: fp.write(contents) + fp.write('\n') return True @@ -315,6 +388,7 @@ def set_app_version(): @when('cdk-addons.configured', 'kube-api-endpoint.available', 'kube-control.connected') +@when_not('kubernetes-master.upgrade-needed') def idle_status(kube_api, kube_control): ''' Signal at the end of the run that we are running. ''' if not all_kube_system_pods_running(): @@ -356,7 +430,7 @@ def start_master(etcd): 'Configuring the Kubernetes master services.') freeze_service_cidr() if not etcd.get_connection_string(): - # etcd is not returning a connection string. This hapens when + # etcd is not returning a connection string. This happens when # the master unit disconnects from etcd and is ready to terminate. # No point in trying to start master services and fail. Just return. return @@ -366,10 +440,10 @@ def start_master(etcd): handle_etcd_relation(etcd) # Add CLI options to all components - configure_apiserver() + configure_apiserver(etcd) configure_controller_manager() configure_scheduler() - + set_state('kubernetes-master.components.started') hookenv.open_port(6443) @@ -393,26 +467,55 @@ def etcd_data_change(etcd): @when('cdk-addons.configured') def send_cluster_dns_detail(kube_control): ''' Send cluster DNS info ''' - # Note that the DNS server doesn't necessarily exist at this point. We know - # where we're going to put it, though, so let's send the info anyway. - dns_ip = get_dns_ip() - kube_control.set_dns(53, hookenv.config('dns_domain'), dns_ip) + enableKubeDNS = hookenv.config('enable-kube-dns') + dnsDomain = hookenv.config('dns_domain') + dns_ip = None + if enableKubeDNS: + try: + dns_ip = get_dns_ip() + except CalledProcessError: + hookenv.log("kubedns not ready yet") + return + kube_control.set_dns(53, dnsDomain, dns_ip, enableKubeDNS) -@when('kube-control.auth.requested') -@when('authentication.setup') +@when('kube-control.connected') +@when('snap.installed.kubectl') @when('leadership.is_leader') -def send_tokens(kube_control): - """Send the tokens to the workers.""" - kubelet_token = get_token('kubelet') - proxy_token = get_token('kube_proxy') - admin_token = get_token('admin') +def create_service_configs(kube_control): + """Create the users for kubelet""" + should_restart = False + # generate the username/pass for the requesting unit + proxy_token = get_token('system:kube-proxy') + if not proxy_token: + setup_tokens(None, 'system:kube-proxy', 'kube-proxy') + proxy_token = get_token('system:kube-proxy') + should_restart = True + + client_token = get_token('admin') + if not client_token: + setup_tokens(None, 'admin', 'admin', "system:masters") + client_token = get_token('admin') + should_restart = True - # Send the data requests = kube_control.auth_user() for request in requests: - kube_control.sign_auth_request(request[0], kubelet_token, - proxy_token, admin_token) + username = request[1]['user'] + group = request[1]['group'] + kubelet_token = get_token(username) + if not kubelet_token and username and group: + # Usernames have to be in the form of system:node: + userid = "kubelet-{}".format(request[0].split('/')[1]) + setup_tokens(None, username, userid, group) + kubelet_token = get_token(username) + kube_control.sign_auth_request(request[0], username, + kubelet_token, proxy_token, + client_token) + should_restart = True + + if should_restart: + host.service_restart('snap.kube-apiserver.daemon') + remove_state('authentication.setup') @when_not('kube-control.connected') @@ -457,22 +560,51 @@ def send_data(tls): 'kubernetes.default.svc', 'kubernetes.default.svc.{0}'.format(domain) ] + + # maybe they have extra names they want as SANs + extra_sans = hookenv.config('extra_sans') + if extra_sans and not extra_sans == "": + sans.extend(extra_sans.split()) + # Create a path safe name by removing path characters from the unit name. certificate_name = hookenv.local_unit().replace('/', '_') # Request a server cert with this information. tls.request_server_cert(common_name, sans, certificate_name) +@when('config.changed.extra_sans', 'certificates.available') +def update_certificate(tls): + # Using the config.changed.extra_sans flag to catch changes. + # IP changes will take ~5 minutes or so to propagate, but + # it will update. + send_data(tls) + + +@when('certificates.server.cert.available', + 'kubernetes-master.components.started', + 'tls_client.server.certificate.written') +def kick_api_server(tls): + # need to be idempotent and don't want to kick the api server + # without need + if data_changed('cert', tls.get_server_cert()): + # certificate changed, so restart the api server + hookenv.log("Certificate information changed, restarting api server") + restart_apiserver() + tls_client.reset_certificate_write_flag('server') + + @when('kubernetes-master.components.started') def configure_cdk_addons(): ''' Configure CDK addons ''' remove_state('cdk-addons.configured') dbEnabled = str(hookenv.config('enable-dashboard-addons')).lower() + dnsEnabled = str(hookenv.config('enable-kube-dns')).lower() args = [ 'arch=' + arch(), - 'dns-ip=' + get_dns_ip(), + 'dns-ip=' + get_deprecated_dns_ip(), 'dns-domain=' + hookenv.config('dns_domain'), - 'enable-dashboard=' + dbEnabled + 'enable-dashboard=' + dbEnabled, + 'enable-kube-dns=' + dnsEnabled ] check_call(['snap', 'set', 'cdk-addons'] + args) if not addons_ready(): @@ -596,7 +728,7 @@ def ceph_storage(ceph_admin): cmd = ['kubectl', 'apply', '-f', '/tmp/ceph-secret.yaml'] check_call(cmd) os.remove('/tmp/ceph-secret.yaml') - except: + except: # NOQA # the enlistment in kubernetes failed, return and prepare for re-exec return @@ -614,6 +746,15 @@ def initial_nrpe_config(nagios=None): update_nrpe_config(nagios) +@when('config.changed.authorization-mode', + 'kubernetes-master.components.started') +def switch_auth_mode(): + config = hookenv.config() + mode = config.get('authorization-mode') + if data_changed('auth-mode', mode): + remove_state('kubernetes-master.components.started') + + @when('kubernetes-master.components.started') @when('nrpe-external-master.available') @when_any('config.changed.nagios_context', @@ -675,8 +816,21 @@ def on_config_allow_privileged_change(): @when('config.changed.api-extra-args') @when('kubernetes-master.components.started') -def on_config_api_extra_args_change(): - configure_apiserver() +@when('etcd.available') +def on_config_api_extra_args_change(etcd): + configure_apiserver(etcd) + + +@when('config.changed.controller-manager-extra-args') +@when('kubernetes-master.components.started') +def on_config_controller_manager_extra_args_change(): + configure_controller_manager() + + +@when('config.changed.scheduler-extra-args') +@when('kubernetes-master.components.started') +def on_config_scheduler_extra_args_change(): + configure_scheduler() @when('kube-control.gpu.available') @@ -720,42 +874,25 @@ def shutdown(): service_stop('snap.kube-scheduler.daemon') -@when('kube-apiserver.do-restart') def restart_apiserver(): prev_state, prev_msg = hookenv.status_get() hookenv.status_set('maintenance', 'Restarting kube-apiserver') host.service_restart('snap.kube-apiserver.daemon') hookenv.status_set(prev_state, prev_msg) - remove_state('kube-apiserver.do-restart') - set_state('kube-apiserver.started') -@when('kube-controller-manager.do-restart') def restart_controller_manager(): prev_state, prev_msg = hookenv.status_get() hookenv.status_set('maintenance', 'Restarting kube-controller-manager') host.service_restart('snap.kube-controller-manager.daemon') hookenv.status_set(prev_state, prev_msg) - remove_state('kube-controller-manager.do-restart') - set_state('kube-controller-manager.started') -@when('kube-scheduler.do-restart') def restart_scheduler(): prev_state, prev_msg = hookenv.status_get() hookenv.status_set('maintenance', 'Restarting kube-scheduler') host.service_restart('snap.kube-scheduler.daemon') hookenv.status_set(prev_state, prev_msg) - remove_state('kube-scheduler.do-restart') - set_state('kube-scheduler.started') - - -@when_all('kube-apiserver.started', - 'kube-controller-manager.started', - 'kube-scheduler.started') -@when_not('kubernetes-master.components.started') -def componenets_started(): - set_state('kubernetes-master.components.started') def arch(): @@ -834,9 +971,16 @@ def create_kubeconfig(kubeconfig, server, ca, key=None, certificate=None, def get_dns_ip(): - '''Get an IP address for the DNS server on the provided cidr.''' + cmd = "kubectl get service --namespace kube-system kube-dns --output json" + output = check_output(cmd, shell=True).decode() + svc = json.loads(output) + return svc['spec']['clusterIP'] + + +def get_deprecated_dns_ip(): + '''We previously hardcoded the dns ip. This function returns the old + hardcoded value for use with older versions of cdk_addons.''' interface = ipaddress.IPv4Interface(service_cidr()) - # Add .10 at the end of the network ip = interface.network.network_address + 10 return ip.exploded @@ -852,9 +996,9 @@ def get_kubernetes_service_ip(): def handle_etcd_relation(reldata): ''' Save the client credentials and set appropriate daemon flags when etcd declares itself as available''' - connection_string = reldata.get_connection_string() # Define where the etcd tls files will be kept. etcd_dir = '/root/cdk/etcd' + # Create paths to the etcd client ca, key, and cert file locations. ca = os.path.join(etcd_dir, 'client-ca.pem') key = os.path.join(etcd_dir, 'client-key.pem') @@ -863,69 +1007,46 @@ def handle_etcd_relation(reldata): # Save the client credentials (in relation data) to the paths provided. reldata.save_client_credentials(key, cert, ca) - api_opts = FlagManager('kube-apiserver') - # Never use stale data, always prefer whats coming in during context - # building. if its stale, its because whats in unitdata is stale - data = api_opts.data - if data.get('etcd-servers-strict') or data.get('etcd-servers'): - api_opts.destroy('etcd-cafile') - api_opts.destroy('etcd-keyfile') - api_opts.destroy('etcd-certfile') - api_opts.destroy('etcd-servers', strict=True) - api_opts.destroy('etcd-servers') +def parse_extra_args(config_key): + elements = hookenv.config().get(config_key, '').split() + args = {} - # Set the apiserver flags in the options manager - api_opts.add('etcd-cafile', ca) - api_opts.add('etcd-keyfile', key) - api_opts.add('etcd-certfile', cert) - api_opts.add('etcd-servers', connection_string, strict=True) - - -def get_config_args(): - db = unitdata.kv() - old_config_args = db.get('api-extra-args', []) - # We have to convert them to tuples becuase we use sets - old_config_args = [tuple(i) for i in old_config_args] - new_config_args = [] - new_config_arg_names = [] - for arg in hookenv.config().get('api-extra-args', '').split(): - new_config_arg_names.append(arg.split('=', 1)[0]) - if len(arg.split('=', 1)) == 1: # handle flags ie. --profiling - new_config_args.append(tuple([arg, 'true'])) + for element in elements: + if '=' in element: + key, _, value = element.partition('=') + args[key] = value else: - new_config_args.append(tuple(arg.split('=', 1))) + args[element] = 'true' - hookenv.log('Handling "api-extra-args" option.') - hookenv.log('Old arguments: {}'.format(old_config_args)) - hookenv.log('New arguments: {}'.format(new_config_args)) - if set(new_config_args) == set(old_config_args): - return (new_config_args, []) - # Store new args - db.set('api-extra-args', new_config_args) - to_add = set(new_config_args) - to_remove = set(old_config_args) - set(new_config_args) - # Extract option names only - to_remove = [i[0] for i in to_remove if i[0] not in new_config_arg_names] - return (to_add, to_remove) + return args -def configure_apiserver(): - # TODO: investigate if it's possible to use config file to store args - # https://github.com/juju-solutions/bundle-canonical-kubernetes/issues/315 - # Handle api-extra-args config option - to_add, to_remove = get_config_args() +def configure_kubernetes_service(service, base_args, extra_args_key): + db = unitdata.kv() - api_opts = FlagManager('kube-apiserver') + prev_args_key = 'kubernetes-master.prev_args.' + service + prev_args = db.get(prev_args_key) or {} - # Remove arguments that are no longer provided as config option - # this allows them to be reverted to charm defaults - for arg in to_remove: - hookenv.log('Removing option: {}'.format(arg)) - api_opts.destroy(arg) - # We need to "unset" options by settig their value to "null" string - cmd = ['snap', 'set', 'kube-apiserver', '{}=null'.format(arg)] - check_call(cmd) + extra_args = parse_extra_args(extra_args_key) + + args = {} + for arg in prev_args: + # remove previous args by setting to null + args[arg] = 'null' + for k, v in base_args.items(): + args[k] = v + for k, v in extra_args.items(): + args[k] = v + + cmd = ['snap', 'set', service] + ['%s=%s' % item for item in args.items()] + check_call(cmd) + + db.set(prev_args_key, args) + + +def configure_apiserver(etcd): + api_opts = {} # Get the tls paths from the layer data. layer_options = layer.options('tls-client') @@ -936,25 +1057,39 @@ def configure_apiserver(): server_key_path = layer_options.get('server_key_path') if is_privileged(): - api_opts.add('allow-privileged', 'true', strict=True) + api_opts['allow-privileged'] = 'true' set_state('kubernetes-master.privileged') else: - api_opts.add('allow-privileged', 'false', strict=True) + api_opts['allow-privileged'] = 'false' remove_state('kubernetes-master.privileged') # Handle static options for now - api_opts.add('service-cluster-ip-range', service_cidr()) - api_opts.add('min-request-timeout', '300') - api_opts.add('v', '4') - api_opts.add('tls-cert-file', server_cert_path) - api_opts.add('tls-private-key-file', server_key_path) - api_opts.add('kubelet-certificate-authority', ca_cert_path) - api_opts.add('kubelet-client-certificate', client_cert_path) - api_opts.add('kubelet-client-key', client_key_path) - api_opts.add('logtostderr', 'true') - api_opts.add('insecure-bind-address', '127.0.0.1') - api_opts.add('insecure-port', '8080') - api_opts.add('storage-backend', 'etcd2') # FIXME: add etcd3 support + api_opts['service-cluster-ip-range'] = service_cidr() + api_opts['min-request-timeout'] = '300' + api_opts['v'] = '4' + api_opts['tls-cert-file'] = server_cert_path + api_opts['tls-private-key-file'] = server_key_path + api_opts['kubelet-certificate-authority'] = ca_cert_path + api_opts['kubelet-client-certificate'] = client_cert_path + api_opts['kubelet-client-key'] = client_key_path + api_opts['logtostderr'] = 'true' + api_opts['insecure-bind-address'] = '127.0.0.1' + api_opts['insecure-port'] = '8080' + api_opts['storage-backend'] = 'etcd2' # FIXME: add etcd3 support + + api_opts['basic-auth-file'] = '/root/cdk/basic_auth.csv' + api_opts['token-auth-file'] = '/root/cdk/known_tokens.csv' + api_opts['service-account-key-file'] = '/root/cdk/serviceaccount.key' + + etcd_dir = '/root/cdk/etcd' + etcd_ca = os.path.join(etcd_dir, 'client-ca.pem') + etcd_key = os.path.join(etcd_dir, 'client-key.pem') + etcd_cert = os.path.join(etcd_dir, 'client-cert.pem') + + api_opts['etcd-cafile'] = etcd_ca + api_opts['etcd-keyfile'] = etcd_key + api_opts['etcd-certfile'] = etcd_cert + api_opts['etcd-servers'] = etcd.get_connection_string() admission_control = [ 'Initializers', @@ -965,62 +1100,61 @@ def configure_apiserver(): 'DefaultTolerationSeconds' ] + auth_mode = hookenv.config('authorization-mode') + if 'Node' in auth_mode: + admission_control.append('NodeRestriction') + + api_opts['authorization-mode'] = auth_mode + if get_version('kube-apiserver') < (1, 6): hookenv.log('Removing DefaultTolerationSeconds from admission-control') admission_control.remove('DefaultTolerationSeconds') if get_version('kube-apiserver') < (1, 7): hookenv.log('Removing Initializers from admission-control') admission_control.remove('Initializers') - api_opts.add('admission-control', ','.join(admission_control), strict=True) + api_opts['admission-control'] = ','.join(admission_control) - # Add operator-provided arguments, this allows operators - # to override defaults - for arg in to_add: - hookenv.log('Adding option: {} {}'.format(arg[0], arg[1])) - # Make sure old value is gone - api_opts.destroy(arg[0]) - api_opts.add(arg[0], arg[1]) - - cmd = ['snap', 'set', 'kube-apiserver'] + api_opts.to_s().split(' ') - check_call(cmd) - set_state('kube-apiserver.do-restart') + configure_kubernetes_service('kube-apiserver', api_opts, 'api-extra-args') + restart_apiserver() def configure_controller_manager(): - controller_opts = FlagManager('kube-controller-manager') + controller_opts = {} # Get the tls paths from the layer data. layer_options = layer.options('tls-client') ca_cert_path = layer_options.get('ca_certificate_path') # Default to 3 minute resync. TODO: Make this configureable? - controller_opts.add('min-resync-period', '3m') - controller_opts.add('v', '2') - controller_opts.add('root-ca-file', ca_cert_path) - controller_opts.add('logtostderr', 'true') - controller_opts.add('master', 'http://127.0.0.1:8080') + controller_opts['min-resync-period'] = '3m' + controller_opts['v'] = '2' + controller_opts['root-ca-file'] = ca_cert_path + controller_opts['logtostderr'] = 'true' + controller_opts['master'] = 'http://127.0.0.1:8080' - cmd = ( - ['snap', 'set', 'kube-controller-manager'] + - controller_opts.to_s().split(' ') - ) - check_call(cmd) - set_state('kube-controller-manager.do-restart') + controller_opts['service-account-private-key-file'] = \ + '/root/cdk/serviceaccount.key' + + configure_kubernetes_service('kube-controller-manager', controller_opts, + 'controller-manager-extra-args') + restart_controller_manager() def configure_scheduler(): - scheduler_opts = FlagManager('kube-scheduler') + scheduler_opts = {} - scheduler_opts.add('v', '2') - scheduler_opts.add('logtostderr', 'true') - scheduler_opts.add('master', 'http://127.0.0.1:8080') + scheduler_opts['v'] = '2' + scheduler_opts['logtostderr'] = 'true' + scheduler_opts['master'] = 'http://127.0.0.1:8080' - cmd = ['snap', 'set', 'kube-scheduler'] + scheduler_opts.to_s().split(' ') - check_call(cmd) - set_state('kube-scheduler.do-restart') + configure_kubernetes_service('kube-scheduler', scheduler_opts, + 'scheduler-extra-args') + + restart_scheduler() -def setup_basic_auth(password=None, username='admin', uid='admin'): +def setup_basic_auth(password=None, username='admin', uid='admin', + groups=None): '''Create the htacces file and the tokens.''' root_cdk = '/root/cdk' if not os.path.isdir(root_cdk): @@ -1029,10 +1163,14 @@ def setup_basic_auth(password=None, username='admin', uid='admin'): if not password: password = token_generator() with open(htaccess, 'w') as stream: - stream.write('{0},{1},{2}'.format(password, username, uid)) + if groups: + stream.write('{0},{1},{2},"{3}"'.format(password, + username, uid, groups)) + else: + stream.write('{0},{1},{2}'.format(password, username, uid)) -def setup_tokens(token, username, user): +def setup_tokens(token, username, user, groups=None): '''Create a token file for kubernetes authentication.''' root_cdk = '/root/cdk' if not os.path.isdir(root_cdk): @@ -1041,7 +1179,13 @@ def setup_tokens(token, username, user): if not token: token = token_generator() with open(known_tokens, 'a') as stream: - stream.write('{0},{1},{2}\n'.format(token, username, user)) + if groups: + stream.write('{0},{1},{2},"{3}"\n'.format(token, + username, + user, + groups)) + else: + stream.write('{0},{1},{2}\n'.format(token, username, user)) def get_password(csv_fname, user): @@ -1097,7 +1241,9 @@ def all_kube_system_pods_running(): result = json.loads(output) for pod in result['items']: status = pod['status']['phase'] - if status != 'Running': + # Evicted nodes should re-spawn + if status != 'Running' and \ + pod['status'].get('reason', '') != 'Evicted': return False return True @@ -1107,3 +1253,10 @@ def apiserverVersion(): cmd = 'kube-apiserver --version'.split() version_string = check_output(cmd).decode('utf-8') return tuple(int(q) for q in re.findall("[0-9]+", version_string)[:3]) + + +def touch(fname): + try: + os.utime(fname, None) + except OSError: + open(fname, 'a').close() diff --git a/cluster/juju/layers/kubernetes-master/templates/rbd-persistent-volume.yaml b/cluster/juju/layers/kubernetes-master/templates/rbd-persistent-volume.yaml index f82a7543b45..84248e54326 100644 --- a/cluster/juju/layers/kubernetes-master/templates/rbd-persistent-volume.yaml +++ b/cluster/juju/layers/kubernetes-master/templates/rbd-persistent-volume.yaml @@ -4,13 +4,12 @@ apiVersion: v1 kind: PersistentVolume metadata: name: {{ RBD_NAME }} - annotations: - volume.beta.kubernetes.io/storage-class: "rbd" spec: capacity: storage: {{ RBD_SIZE }}M accessModes: - {{ PV_MODE }} + storageClassName: "rbd" rbd: monitors: {% for host in monitors %} diff --git a/cluster/juju/layers/kubernetes-worker/actions/microbot b/cluster/juju/layers/kubernetes-worker/actions/microbot index 0306747061f..41663f253bc 100755 --- a/cluster/juju/layers/kubernetes-worker/actions/microbot +++ b/cluster/juju/layers/kubernetes-worker/actions/microbot @@ -34,7 +34,7 @@ if not context['replicas']: context['replicas'] = 3 # Declare a kubectl template when invoking kubectl -kubectl = ['kubectl', '--kubeconfig=/root/cdk/kubeconfig'] +kubectl = ['kubectl', '--kubeconfig=/root/.kube/config'] # Remove deployment if requested if context['delete']: diff --git a/cluster/juju/layers/kubernetes-worker/actions/pause b/cluster/juju/layers/kubernetes-worker/actions/pause index 7f1c66e8b73..82b3d3838dd 100755 --- a/cluster/juju/layers/kubernetes-worker/actions/pause +++ b/cluster/juju/layers/kubernetes-worker/actions/pause @@ -21,8 +21,8 @@ fi # Cordon and drain the unit -kubectl --kubeconfig=/root/cdk/kubeconfig cordon $(hostname) -kubectl --kubeconfig=/root/cdk/kubeconfig drain $(hostname) ${EXTRA_FLAGS} +kubectl --kubeconfig=/root/.kube/config cordon $(hostname) +kubectl --kubeconfig=/root/.kube/config drain $(hostname) ${EXTRA_FLAGS} # Set status to indicate the unit is paused and under maintenance. status-set 'waiting' 'Kubernetes unit paused' diff --git a/cluster/juju/layers/kubernetes-worker/actions/registry b/cluster/juju/layers/kubernetes-worker/actions/registry index a99a0d5732e..11d57ce8835 100755 --- a/cluster/juju/layers/kubernetes-worker/actions/registry +++ b/cluster/juju/layers/kubernetes-worker/actions/registry @@ -57,7 +57,7 @@ if param_error: context['ingress'] = action_get('ingress') # Declare a kubectl template when invoking kubectl -kubectl = ['kubectl', '--kubeconfig=/root/cdk/kubeconfig'] +kubectl = ['kubectl', '--kubeconfig=/root/.kube/config'] # Remove deployment if requested if deletion: diff --git a/cluster/juju/layers/kubernetes-worker/actions/resume b/cluster/juju/layers/kubernetes-worker/actions/resume index 6131e8e037b..f7ef0a17f99 100755 --- a/cluster/juju/layers/kubernetes-worker/actions/resume +++ b/cluster/juju/layers/kubernetes-worker/actions/resume @@ -4,5 +4,5 @@ set -ex export PATH=$PATH:/snap/bin -kubectl --kubeconfig=/root/cdk/kubeconfig uncordon $(hostname) +kubectl --kubeconfig=/root/.kube/config uncordon $(hostname) status-set 'active' 'Kubernetes unit resumed' diff --git a/cluster/juju/layers/kubernetes-worker/config.yaml b/cluster/juju/layers/kubernetes-worker/config.yaml index 9c91f7fc826..b16b1814d77 100644 --- a/cluster/juju/layers/kubernetes-worker/config.yaml +++ b/cluster/juju/layers/kubernetes-worker/config.yaml @@ -22,7 +22,7 @@ options: switch to privileged mode if gpu hardware is detected. channel: type: string - default: "1.8/stable" + default: "1.9/stable" description: | Snap channel to install Kubernetes worker services from require-manual-upgrade: @@ -31,3 +31,30 @@ options: description: | When true, worker services will not be upgraded until the user triggers it manually by running the upgrade action. + kubelet-extra-args: + type: string + default: "" + description: | + Space separated list of flags and key=value pairs that will be passed as arguments to + kubelet. For example a value like this: + runtime-config=batch/v2alpha1=true profiling=true + will result in kube-apiserver being run with the following options: + --runtime-config=batch/v2alpha1=true --profiling=true + proxy-extra-args: + type: string + default: "" + description: | + Space separated list of flags and key=value pairs that will be passed as arguments to + kube-proxy. For example a value like this: + runtime-config=batch/v2alpha1=true profiling=true + will result in kube-apiserver being run with the following options: + --runtime-config=batch/v2alpha1=true --profiling=true + docker-logins: + type: string + default: "[]" + description: | + Docker login credentials. Setting this config allows Kubelet to pull images from + registries where auth is required. + + The value for this config must be a JSON array of credential objects, like this: + [{"server": "my.registry", "username": "myUser", "password": "myPass"}] diff --git a/cluster/juju/layers/kubernetes-worker/lib/charms/kubernetes/flagmanager.py b/cluster/juju/layers/kubernetes-worker/lib/charms/kubernetes/flagmanager.py deleted file mode 100644 index 7fe5737a6ef..00000000000 --- a/cluster/juju/layers/kubernetes-worker/lib/charms/kubernetes/flagmanager.py +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env python - -# 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. - -from charmhelpers.core import unitdata - - -class FlagManager: - ''' - FlagManager - A Python class for managing the flags to pass to an - application without remembering what's been set previously. - - This is a blind class assuming the operator knows what they are doing. - Each instance of this class should be initialized with the intended - application to manage flags. Flags are then appended to a data-structure - and cached in unitdata for later recall. - - THe underlying data-provider is backed by a SQLITE database on each unit, - tracking the dictionary, provided from the 'charmhelpers' python package. - Summary: - opts = FlagManager('docker') - opts.add('bip', '192.168.22.2') - opts.to_s() - ''' - - def __init__(self, daemon, opts_path=None): - self.db = unitdata.kv() - self.daemon = daemon - if not self.db.get(daemon): - self.data = {} - else: - self.data = self.db.get(daemon) - - def __save(self): - self.db.set(self.daemon, self.data) - - def add(self, key, value, strict=False): - ''' - Adds data to the map of values for the DockerOpts file. - Supports single values, or "multiopt variables". If you - have a flag only option, like --tlsverify, set the value - to None. To preserve the exact value, pass strict - eg: - opts.add('label', 'foo') - opts.add('label', 'foo, bar, baz') - opts.add('flagonly', None) - opts.add('cluster-store', 'consul://a:4001,b:4001,c:4001/swarm', - strict=True) - ''' - if strict: - self.data['{}-strict'.format(key)] = value - self.__save() - return - - if value: - values = [x.strip() for x in value.split(',')] - # handle updates - if key in self.data and self.data[key] is not None: - item_data = self.data[key] - for c in values: - c = c.strip() - if c not in item_data: - item_data.append(c) - self.data[key] = item_data - else: - # handle new - self.data[key] = values - else: - # handle flagonly - self.data[key] = None - self.__save() - - def remove(self, key, value): - ''' - Remove a flag value from the DockerOpts manager - Assuming the data is currently {'foo': ['bar', 'baz']} - d.remove('foo', 'bar') - > {'foo': ['baz']} - :params key: - :params value: - ''' - self.data[key].remove(value) - self.__save() - - def destroy(self, key, strict=False): - ''' - Destructively remove all values and key from the FlagManager - Assuming the data is currently {'foo': ['bar', 'baz']} - d.wipe('foo') - >{} - :params key: - :params strict: - ''' - try: - if strict: - self.data.pop('{}-strict'.format(key)) - else: - self.data.pop(key) - self.__save() - except KeyError: - pass - - def get(self, key, default=None): - """Return the value for ``key``, or the default if ``key`` doesn't exist. - - """ - return self.data.get(key, default) - - def destroy_all(self): - ''' - Destructively removes all data from the FlagManager. - ''' - self.data.clear() - self.__save() - - def to_s(self): - ''' - Render the flags to a single string, prepared for the Docker - Defaults file. Typically in /etc/default/docker - d.to_s() - > "--foo=bar --foo=baz" - ''' - flags = [] - for key in self.data: - if self.data[key] is None: - # handle flagonly - flags.append("{}".format(key)) - elif '-strict' in key: - # handle strict values, and do it in 2 steps. - # If we rstrip -strict it strips a tailing s - proper_key = key.rstrip('strict').rstrip('-') - flags.append("{}={}".format(proper_key, self.data[key])) - else: - # handle multiopt and typical flags - for item in self.data[key]: - flags.append("{}={}".format(key, item)) - return ' '.join(flags) diff --git a/cluster/juju/layers/kubernetes-worker/reactive/kubernetes_worker.py b/cluster/juju/layers/kubernetes-worker/reactive/kubernetes_worker.py index 2f5707790b6..d0ed9908cfc 100644 --- a/cluster/juju/layers/kubernetes-worker/reactive/kubernetes_worker.py +++ b/cluster/juju/layers/kubernetes-worker/reactive/kubernetes_worker.py @@ -14,6 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json import os import random import shutil @@ -32,7 +33,6 @@ from charms.reactive import set_state, remove_state, is_state from charms.reactive import when, when_any, when_not from charms.kubernetes.common import get_version -from charms.kubernetes.flagmanager import FlagManager from charms.reactive.helpers import data_changed, any_file_changed from charms.templating.jinja2 import render @@ -47,9 +47,10 @@ from charmhelpers.contrib.charmsupport import nrpe nrpe.Check.shortname_re = '[\.A-Za-z0-9-_]+$' kubeconfig_path = '/root/cdk/kubeconfig' +kubeproxyconfig_path = '/root/cdk/kubeproxyconfig' +kubeclientconfig_path = '/root/.kube/config' os.environ['PATH'] += os.pathsep + os.path.join(os.sep, 'snap', 'bin') - db = unitdata.kv() @@ -62,12 +63,13 @@ def upgrade_charm(): cleanup_pre_snap_services() check_resources_for_upgrade_needed() + # Remove the RC for nginx ingress if it exists + if hookenv.config().get('ingress'): + kubectl_success('delete', 'rc', 'nginx-ingress-controller') + # Remove gpu.enabled state so we can reconfigure gpu-related kubelet flags, # since they can differ between k8s versions remove_state('kubernetes-worker.gpu.enabled') - kubelet_opts = FlagManager('kubelet') - kubelet_opts.destroy('feature-gates') - kubelet_opts.destroy('experimental-nvidia-gpus') remove_state('kubernetes-worker.cni-plugins.installed') remove_state('kubernetes-worker.config.created') @@ -123,10 +125,6 @@ def cleanup_pre_snap_services(): hookenv.log("Removing file: " + file) os.remove(file) - # cleanup old flagmanagers - FlagManager('kubelet').destroy_all() - FlagManager('kube-proxy').destroy_all() - @when('config.changed.channel') def channel_changed(): @@ -164,7 +162,7 @@ def shutdown(): ''' try: if os.path.isfile(kubeconfig_path): - kubectl('delete', 'node', gethostname()) + kubectl('delete', 'node', gethostname().lower()) except CalledProcessError: hookenv.log('Failed to unregister node.') service_stop('snap.kubelet.daemon') @@ -319,7 +317,8 @@ def watch_for_changes(kube_api, kube_control, cni): 'tls_client.client.key.saved', 'tls_client.server.certificate.saved', 'tls_client.server.key.saved', 'kube-control.dns.available', 'kube-control.auth.available', - 'cni.available', 'kubernetes-worker.restart-needed') + 'cni.available', 'kubernetes-worker.restart-needed', + 'worker.auth.bootstrapped') def start_worker(kube_api, kube_control, auth_control, cni): ''' Start kubelet using the provided API and DNS info.''' servers = get_kube_api_servers(kube_api) @@ -335,14 +334,15 @@ def start_worker(kube_api, kube_control, auth_control, cni): hookenv.log('Waiting for cluster cidr.') return - creds = kube_control.get_auth_credentials() + creds = db.get('credentials') data_changed('kube-control.creds', creds) # set --allow-privileged flag for kubelet set_privileged() create_config(random.choice(servers), creds) - configure_worker_services(servers, dns, cluster_cidr) + configure_kubelet(dns) + configure_kube_proxy(servers, cluster_cidr) set_state('kubernetes-worker.config.created') restart_unit_services() update_kubelet_status() @@ -377,7 +377,7 @@ def sdn_changed(): @when('kubernetes-worker.config.created') @when_not('kubernetes-worker.ingress.available') def render_and_launch_ingress(): - ''' If configuration has ingress RC enabled, launch the ingress load + ''' If configuration has ingress daemon set enabled, launch the ingress load balancer and default http backend. Otherwise attempt deletion. ''' config = hookenv.config() # If ingress is enabled, launch the ingress controller @@ -388,23 +388,11 @@ def render_and_launch_ingress(): kubectl_manifest('delete', '/root/cdk/addons/default-http-backend.yaml') kubectl_manifest('delete', - '/root/cdk/addons/ingress-replication-controller.yaml') # noqa + '/root/cdk/addons/ingress-daemon-set.yaml') # noqa hookenv.close_port(80) hookenv.close_port(443) -@when('kubernetes-worker.ingress.available') -def scale_ingress_controller(): - ''' Scale the number of ingress controller replicas to match the number of - nodes. ''' - try: - output = kubectl('get', 'nodes', '-o', 'name') - count = len(output.splitlines()) - kubectl('scale', '--replicas=%d' % count, 'rc/nginx-ingress-controller') # noqa - except CalledProcessError: - hookenv.log('Failed to scale ingress controllers. Will attempt again next update.') # noqa - - @when('config.changed.labels', 'kubernetes-worker.config.created') def apply_node_labels(): ''' Parse the labels configuration option and apply the labels to the node. @@ -433,6 +421,42 @@ def apply_node_labels(): for label in user_labels: _apply_node_label(label, overwrite=True) + # Set label for application name + _apply_node_label('juju-application={}'.format(hookenv.service_name()), + overwrite=True) + + +@when_any('config.changed.kubelet-extra-args', + 'config.changed.proxy-extra-args') +def extra_args_changed(): + set_state('kubernetes-worker.restart-needed') + + +@when('config.changed.docker-logins') +def docker_logins_changed(): + config = hookenv.config() + previous_logins = config.previous('docker-logins') + logins = config['docker-logins'] + logins = json.loads(logins) + + if previous_logins: + previous_logins = json.loads(previous_logins) + next_servers = {login['server'] for login in logins} + previous_servers = {login['server'] for login in previous_logins} + servers_to_logout = previous_servers - next_servers + for server in servers_to_logout: + cmd = ['docker', 'logout', server] + subprocess.check_call(cmd) + + for login in logins: + server = login['server'] + username = login['username'] + password = login['password'] + cmd = ['docker', 'login', server, '-u', username, '-p', password] + subprocess.check_call(cmd) + + set_state('kubernetes-worker.restart-needed') + def arch(): '''Return the package architecture as a string. Raise an exception if the @@ -458,51 +482,103 @@ def create_config(server, creds): cmd = ['chown', '-R', 'ubuntu:ubuntu', '/home/ubuntu/.kube'] check_call(cmd) # Create kubernetes configuration in the default location for root. - create_kubeconfig('/root/.kube/config', server, ca, + create_kubeconfig(kubeclientconfig_path, server, ca, token=creds['client_token'], user='root') # Create kubernetes configuration for kubelet, and kube-proxy services. create_kubeconfig(kubeconfig_path, server, ca, token=creds['kubelet_token'], user='kubelet') + create_kubeconfig(kubeproxyconfig_path, server, ca, + token=creds['proxy_token'], user='kube-proxy') -def configure_worker_services(api_servers, dns, cluster_cidr): - ''' Add remaining flags for the worker services and configure snaps to use - them ''' +def parse_extra_args(config_key): + elements = hookenv.config().get(config_key, '').split() + args = {} + + for element in elements: + if '=' in element: + key, _, value = element.partition('=') + args[key] = value + else: + args[element] = 'true' + + return args + + +def configure_kubernetes_service(service, base_args, extra_args_key): + db = unitdata.kv() + + prev_args_key = 'kubernetes-worker.prev_args.' + service + prev_args = db.get(prev_args_key) or {} + + extra_args = parse_extra_args(extra_args_key) + + args = {} + for arg in prev_args: + # remove previous args by setting to null + args[arg] = 'null' + for k, v in base_args.items(): + args[k] = v + for k, v in extra_args.items(): + args[k] = v + + cmd = ['snap', 'set', service] + ['%s=%s' % item for item in args.items()] + check_call(cmd) + + db.set(prev_args_key, args) + + +def configure_kubelet(dns): layer_options = layer.options('tls-client') ca_cert_path = layer_options.get('ca_certificate_path') server_cert_path = layer_options.get('server_certificate_path') server_key_path = layer_options.get('server_key_path') - kubelet_opts = FlagManager('kubelet') - kubelet_opts.add('require-kubeconfig', 'true') - kubelet_opts.add('kubeconfig', kubeconfig_path) - kubelet_opts.add('network-plugin', 'cni') - kubelet_opts.add('v', '0') - kubelet_opts.add('address', '0.0.0.0') - kubelet_opts.add('port', '10250') - kubelet_opts.add('cluster-dns', dns['sdn-ip']) - kubelet_opts.add('cluster-domain', dns['domain']) - kubelet_opts.add('anonymous-auth', 'false') - kubelet_opts.add('client-ca-file', ca_cert_path) - kubelet_opts.add('tls-cert-file', server_cert_path) - kubelet_opts.add('tls-private-key-file', server_key_path) - kubelet_opts.add('logtostderr', 'true') - kubelet_opts.add('fail-swap-on', 'false') + kubelet_opts = {} + kubelet_opts['require-kubeconfig'] = 'true' + kubelet_opts['kubeconfig'] = kubeconfig_path + kubelet_opts['network-plugin'] = 'cni' + kubelet_opts['v'] = '0' + kubelet_opts['address'] = '0.0.0.0' + kubelet_opts['port'] = '10250' + kubelet_opts['cluster-domain'] = dns['domain'] + kubelet_opts['anonymous-auth'] = 'false' + kubelet_opts['client-ca-file'] = ca_cert_path + kubelet_opts['tls-cert-file'] = server_cert_path + kubelet_opts['tls-private-key-file'] = server_key_path + kubelet_opts['logtostderr'] = 'true' + kubelet_opts['fail-swap-on'] = 'false' - kube_proxy_opts = FlagManager('kube-proxy') - kube_proxy_opts.add('cluster-cidr', cluster_cidr) - kube_proxy_opts.add('kubeconfig', kubeconfig_path) - kube_proxy_opts.add('logtostderr', 'true') - kube_proxy_opts.add('v', '0') - kube_proxy_opts.add('master', random.choice(api_servers), strict=True) + if (dns['enable-kube-dns']): + kubelet_opts['cluster-dns'] = dns['sdn-ip'] + + privileged = is_state('kubernetes-worker.privileged') + kubelet_opts['allow-privileged'] = 'true' if privileged else 'false' + + if is_state('kubernetes-worker.gpu.enabled'): + if get_version('kubelet') < (1, 6): + hookenv.log('Adding --experimental-nvidia-gpus=1 to kubelet') + kubelet_opts['experimental-nvidia-gpus'] = '1' + else: + hookenv.log('Adding --feature-gates=Accelerators=true to kubelet') + kubelet_opts['feature-gates'] = 'Accelerators=true' + + configure_kubernetes_service('kubelet', kubelet_opts, 'kubelet-extra-args') + + +def configure_kube_proxy(api_servers, cluster_cidr): + kube_proxy_opts = {} + kube_proxy_opts['cluster-cidr'] = cluster_cidr + kube_proxy_opts['kubeconfig'] = kubeproxyconfig_path + kube_proxy_opts['logtostderr'] = 'true' + kube_proxy_opts['v'] = '0' + kube_proxy_opts['master'] = random.choice(api_servers) if b'lxc' in check_output('virt-what', shell=True): - kube_proxy_opts.add('conntrack-max-per-core', '0') + kube_proxy_opts['conntrack-max-per-core'] = '0' - cmd = ['snap', 'set', 'kubelet'] + kubelet_opts.to_s().split(' ') - check_call(cmd) - cmd = ['snap', 'set', 'kube-proxy'] + kube_proxy_opts.to_s().split(' ') - check_call(cmd) + configure_kubernetes_service('kube-proxy', kube_proxy_opts, + 'proxy-extra-args') def create_kubeconfig(kubeconfig, server, ca, key=None, certificate=None, @@ -554,6 +630,12 @@ def launch_default_ingress_controller(): context['arch'] = arch() addon_path = '/root/cdk/addons/{}' + context['defaultbackend_image'] = \ + "gcr.io/google_containers/defaultbackend:1.4" + if arch() == 's390x': + context['defaultbackend_image'] = \ + "gcr.io/google_containers/defaultbackend-s390x:1.4" + # Render the default http backend (404) replicationcontroller manifest manifest = addon_path.format('default-http-backend.yaml') render('default-http-backend.yaml', manifest, context) @@ -567,15 +649,16 @@ def launch_default_ingress_controller(): hookenv.close_port(443) return - # Render the ingress replication controller manifest + # Render the ingress daemon set controller manifest context['ingress_image'] = \ "gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.13" if arch() == 's390x': context['ingress_image'] = \ "docker.io/cdkbot/nginx-ingress-controller-s390x:0.9.0-beta.13" - manifest = addon_path.format('ingress-replication-controller.yaml') - render('ingress-replication-controller.yaml', manifest, context) - hookenv.log('Creating the ingress replication controller.') + context['juju_application'] = hookenv.service_name() + manifest = addon_path.format('ingress-daemon-set.yaml') + render('ingress-daemon-set.yaml', manifest, context) + hookenv.log('Creating the ingress daemon set.') try: kubectl('apply', '-f', manifest) except CalledProcessError as e: @@ -613,7 +696,7 @@ def get_kube_api_servers(kube_api): def kubectl(*args): ''' Run a kubectl cli command with a config file. Returns stdout and throws an error if the command fails. ''' - command = ['kubectl', '--kubeconfig=' + kubeconfig_path] + list(args) + command = ['kubectl', '--kubeconfig=' + kubeclientconfig_path] + list(args) hookenv.log('Executing {}'.format(command)) return check_output(command) @@ -696,12 +779,6 @@ def set_privileged(): gpu_enabled = is_state('kubernetes-worker.gpu.enabled') privileged = 'true' if gpu_enabled else 'false' - flag = 'allow-privileged' - hookenv.log('Setting {}={}'.format(flag, privileged)) - - kubelet_opts = FlagManager('kubelet') - kubelet_opts.add(flag, privileged) - if privileged == 'true': set_state('kubernetes-worker.privileged') else: @@ -744,14 +821,6 @@ def enable_gpu(): hookenv.log(cpe) return - kubelet_opts = FlagManager('kubelet') - if get_version('kubelet') < (1, 6): - hookenv.log('Adding --experimental-nvidia-gpus=1 to kubelet') - kubelet_opts.add('experimental-nvidia-gpus', '1') - else: - hookenv.log('Adding --feature-gates=Accelerators=true to kubelet') - kubelet_opts.add('feature-gates', 'Accelerators=true') - # Apply node labels _apply_node_label('gpu=true', overwrite=True) _apply_node_label('cuda=true', overwrite=True) @@ -773,12 +842,6 @@ def disable_gpu(): """ hookenv.log('Disabling gpu mode') - kubelet_opts = FlagManager('kubelet') - if get_version('kubelet') < (1, 6): - kubelet_opts.destroy('experimental-nvidia-gpus') - else: - kubelet_opts.remove('feature-gates', 'Accelerators=true') - # Remove node labels _apply_node_label('gpu', delete=True) _apply_node_label('cuda', delete=True) @@ -813,15 +876,23 @@ def request_kubelet_and_proxy_credentials(kube_control): # The kube-cotrol interface is created to support RBAC. # At this point we might as well do the right thing and return the hostname # even if it will only be used when we enable RBAC - nodeuser = 'system:node:{}'.format(gethostname()) + nodeuser = 'system:node:{}'.format(gethostname().lower()) kube_control.set_auth_request(nodeuser) -@when('kube-control.auth.available') +@when('kube-control.connected') def catch_change_in_creds(kube_control): """Request a service restart in case credential updates were detected.""" - creds = kube_control.get_auth_credentials() - if data_changed('kube-control.creds', creds): + nodeuser = 'system:node:{}'.format(gethostname().lower()) + creds = kube_control.get_auth_credentials(nodeuser) + if creds \ + and data_changed('kube-control.creds', creds) \ + and creds['user'] == nodeuser: + # We need to cache the credentials here because if the + # master changes (master leader dies and replaced by a new one) + # the new master will have no recollection of our certs. + db.set('credentials', creds) + set_state('worker.auth.bootstrapped') set_state('kubernetes-worker.restart-needed') @@ -840,6 +911,16 @@ def missing_kube_control(): hookenv.service_name())) +@when('docker.ready') +def fix_iptables_for_docker_1_13(): + """ Fix iptables FORWARD policy for Docker >=1.13 + https://github.com/kubernetes/kubernetes/issues/40182 + https://github.com/kubernetes/kubernetes/issues/39823 + """ + cmd = ['iptables', '-w', '300', '-P', 'FORWARD', 'ACCEPT'] + check_call(cmd) + + def _systemctl_is_active(application): ''' Poll systemctl to determine if the application is running ''' cmd = ['systemctl', 'is-active', application] @@ -850,23 +931,64 @@ def _systemctl_is_active(application): return False +class GetNodeNameFailed(Exception): + pass + + +def get_node_name(): + # Get all the nodes in the cluster + cmd = 'kubectl --kubeconfig={} get no -o=json'.format(kubeconfig_path) + cmd = cmd.split() + deadline = time.time() + 60 + while time.time() < deadline: + try: + raw = check_output(cmd) + break + except CalledProcessError: + hookenv.log('Failed to get node name for node %s.' + ' Will retry.' % (gethostname())) + time.sleep(1) + else: + msg = 'Failed to get node name for node %s' % gethostname() + raise GetNodeNameFailed(msg) + + result = json.loads(raw.decode('utf-8')) + if 'items' in result: + for node in result['items']: + if 'status' not in node: + continue + if 'addresses' not in node['status']: + continue + + # find the hostname + for address in node['status']['addresses']: + if address['type'] == 'Hostname': + if address['address'] == gethostname(): + return node['metadata']['name'] + + # if we didn't match, just bail to the next node + break + msg = 'Failed to get node name for node %s' % gethostname() + raise GetNodeNameFailed(msg) + + class ApplyNodeLabelFailed(Exception): pass def _apply_node_label(label, delete=False, overwrite=False): ''' Invoke kubectl to apply node label changes ''' + nodename = get_node_name() - hostname = gethostname() # TODO: Make this part of the kubectl calls instead of a special string cmd_base = 'kubectl --kubeconfig={0} label node {1} {2}' if delete is True: label_key = label.split('=')[0] - cmd = cmd_base.format(kubeconfig_path, hostname, label_key) + cmd = cmd_base.format(kubeconfig_path, nodename, label_key) cmd = cmd + '-' else: - cmd = cmd_base.format(kubeconfig_path, hostname, label) + cmd = cmd_base.format(kubeconfig_path, nodename, label) if overwrite: cmd = '{} --overwrite'.format(cmd) cmd = cmd.split() diff --git a/cluster/juju/layers/kubernetes-worker/templates/default-http-backend.yaml b/cluster/juju/layers/kubernetes-worker/templates/default-http-backend.yaml index 02500dc679d..91b800ab753 100644 --- a/cluster/juju/layers/kubernetes-worker/templates/default-http-backend.yaml +++ b/cluster/juju/layers/kubernetes-worker/templates/default-http-backend.yaml @@ -17,7 +17,7 @@ spec: # Any image is permissable as long as: # 1. It serves a 404 page at / # 2. It serves 200 on a /healthz endpoint - image: gcr.io/google_containers/defaultbackend:1.0 + image: {{ defaultbackend_image }} livenessProbe: httpGet: path: /healthz @@ -32,12 +32,13 @@ apiVersion: v1 kind: Service metadata: name: default-http-backend +# namespace: kube-system labels: - app: default-http-backend + k8s-app: default-http-backend spec: ports: - - port: 80 - protocol: TCP - targetPort: 80 + - port: 80 + protocol: TCP + targetPort: 80 selector: app: default-http-backend diff --git a/cluster/juju/layers/kubernetes-worker/templates/ingress-daemon-set.yaml b/cluster/juju/layers/kubernetes-worker/templates/ingress-daemon-set.yaml new file mode 100644 index 00000000000..1254f6d41db --- /dev/null +++ b/cluster/juju/layers/kubernetes-worker/templates/ingress-daemon-set.yaml @@ -0,0 +1,179 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: nginx-ingress-{{ juju_application }}-serviceaccount +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRole +metadata: + name: nginx-ingress-{{ juju_application }}-clusterrole +rules: + - apiGroups: + - "" + resources: + - configmaps + - endpoints + - nodes + - pods + - secrets + verbs: + - list + - watch + - apiGroups: + - "" + resources: + - nodes + verbs: + - get + - apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch + - apiGroups: + - "extensions" + resources: + - ingresses + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - apiGroups: + - "extensions" + resources: + - ingresses/status + verbs: + - update +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: Role +metadata: + name: nginx-ingress-{{ juju_application }}-role +rules: + - apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - namespaces + verbs: + - get + - apiGroups: + - "" + resources: + - configmaps + resourceNames: + # Defaults to "-" + # Here: "-" + # This has to be adapted if you change either parameter + # when launching the nginx-ingress-controller. + - "ingress-controller-leader-nginx" + verbs: + - get + - update + - apiGroups: + - "" + resources: + - configmaps + verbs: + - create + - apiGroups: + - "" + resources: + - endpoints + verbs: + - get + - create + - update +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: RoleBinding +metadata: + name: nginx-ingress-role-nisa-{{ juju_application }}-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: nginx-ingress-{{ juju_application }}-role +subjects: + - kind: ServiceAccount + name: nginx-ingress-{{ juju_application }}-serviceaccount +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRoleBinding +metadata: + name: nginx-ingress-clusterrole-nisa-{{ juju_application }}-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: nginx-ingress-{{ juju_application }}-clusterrole +subjects: + - kind: ServiceAccount + name: nginx-ingress-{{ juju_application }}-serviceaccount + namespace: default +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-load-balancer-{{ juju_application }}-conf +--- +apiVersion: apps/v1beta2 +kind: DaemonSet +metadata: + name: nginx-ingress-{{ juju_application }}-controller + labels: + juju-application: nginx-ingress-{{ juju_application }} +spec: + selector: + matchLabels: + name: nginx-ingress-{{ juju_application }} + template: + metadata: + labels: + name: nginx-ingress-{{ juju_application }} + spec: + nodeSelector: + juju-application: {{ juju_application }} + terminationGracePeriodSeconds: 60 + # hostPort doesn't work with CNI, so we have to use hostNetwork instead + # see https://github.com/kubernetes/kubernetes/issues/23920 + hostNetwork: true + serviceAccountName: nginx-ingress-{{ juju_application }}-serviceaccount + containers: + - image: {{ ingress_image }} + name: nginx-ingress-{{ juju_application }} + imagePullPolicy: Always + livenessProbe: + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 30 + timeoutSeconds: 5 + # use downward API + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + ports: + - containerPort: 80 + - containerPort: 443 + args: + - /nginx-ingress-controller + - --default-backend-service=$(POD_NAMESPACE)/default-http-backend + - --configmap=$(POD_NAMESPACE)/nginx-load-balancer-conf diff --git a/cluster/juju/layers/kubernetes-worker/templates/ingress-replication-controller.yaml b/cluster/juju/layers/kubernetes-worker/templates/ingress-replication-controller.yaml deleted file mode 100644 index aa7173ce025..00000000000 --- a/cluster/juju/layers/kubernetes-worker/templates/ingress-replication-controller.yaml +++ /dev/null @@ -1,53 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: nginx-load-balancer-conf ---- -apiVersion: v1 -kind: ReplicationController -metadata: - name: nginx-ingress-controller - labels: - k8s-app: nginx-ingress-lb -spec: - replicas: 1 - selector: - k8s-app: nginx-ingress-lb - template: - metadata: - labels: - k8s-app: nginx-ingress-lb - name: nginx-ingress-lb - spec: - terminationGracePeriodSeconds: 60 - # hostPort doesn't work with CNI, so we have to use hostNetwork instead - # see https://github.com/kubernetes/kubernetes/issues/23920 - hostNetwork: true - containers: - - image: {{ ingress_image }} - name: nginx-ingress-lb - imagePullPolicy: Always - livenessProbe: - httpGet: - path: /healthz - port: 10254 - scheme: HTTP - initialDelaySeconds: 30 - timeoutSeconds: 5 - # use downward API - env: - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - ports: - - containerPort: 80 - - containerPort: 443 - args: - - /nginx-ingress-controller - - --default-backend-service=$(POD_NAMESPACE)/default-http-backend - - --configmap=$(POD_NAMESPACE)/nginx-load-balancer-conf diff --git a/cluster/kube-up.sh b/cluster/kube-up.sh index 8a51f8a4555..a2813f99e2f 100755 --- a/cluster/kube-up.sh +++ b/cluster/kube-up.sh @@ -34,7 +34,7 @@ source "${KUBE_ROOT}/cluster/kube-util.sh" DEPRECATED_PROVIDERS=( "centos" - "libvert-coreos" + "libvirt-coreos" "local" "openstack-heat" "photon-controller" diff --git a/cluster/kube-util.sh b/cluster/kube-util.sh index ae9c686b21a..b35fddf4f6e 100755 --- a/cluster/kube-util.sh +++ b/cluster/kube-util.sh @@ -22,10 +22,12 @@ KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. source "${KUBE_ROOT}/cluster/skeleton/util.sh" -if [[ -n "${KUBERNETES_CONFORMANCE_TEST:-}" ]]; then - KUBERNETES_PROVIDER="" -else - KUBERNETES_PROVIDER="${KUBERNETES_PROVIDER:-gce}" +if [[ "${KUBERNETES_PROVIDER:-}" != "kubernetes-anywhere" ]]; then + if [[ -n "${KUBERNETES_CONFORMANCE_TEST:-}" ]]; then + KUBERNETES_PROVIDER="" + else + KUBERNETES_PROVIDER="${KUBERNETES_PROVIDER:-gce}" + fi fi # PROVIDER_VARS is a list of cloud provider specific variables. Note: @@ -38,59 +40,3 @@ PROVIDER_UTILS="${KUBE_ROOT}/cluster/${KUBERNETES_PROVIDER}/util.sh" if [ -f ${PROVIDER_UTILS} ]; then source "${PROVIDER_UTILS}" fi - -# Federation utils - -# Sets the kubeconfig context value for the current cluster. -# Args: -# $1: zone (required) -# -# Vars set: -# CLUSTER_CONTEXT -function kubeconfig-federation-context() { - if [[ -z "${1:-}" ]]; then - echo "zone parameter is required" - exit 1 - fi - CLUSTER_CONTEXT="federation-e2e-${KUBERNETES_PROVIDER}-${1}" -} - -# Should NOT be called within the global scope, unless setting the desired global zone vars -# This function is currently NOT USED in the global scope -function set-federation-zone-vars { - zone="$1" - kubeconfig-federation-context "${zone}" - export OVERRIDE_CONTEXT="${CLUSTER_CONTEXT}" - echo "Setting zone vars to: $OVERRIDE_CONTEXT" - if [[ "$KUBERNETES_PROVIDER" == "gce" ]];then - # This needs a revamp, but for now e2e zone name is used as the unique - # cluster identifier in our e2e tests and we will continue to use that - # pattern. - export CLUSTER_NAME="${zone}" - - export KUBE_GCE_ZONE="${zone}" - # gcloud has a 61 character limit, and for firewall rules this - # prefix gets appended to itself, with some extra information - # need tot keep it short - export KUBE_GCE_INSTANCE_PREFIX="${USER}-${zone}" - - elif [[ "$KUBERNETES_PROVIDER" == "gke" ]];then - - export CLUSTER_NAME="${USER}-${zone}" - - elif [[ "$KUBERNETES_PROVIDER" == "aws" ]];then - - export KUBE_AWS_ZONE="$zone" - export KUBE_AWS_INSTANCE_PREFIX="${USER}-${zone}" - - # WARNING: This is hack - # After KUBE_AWS_INSTANCE_PREFIX is changed, - # we need to make sure the config-xxx.sh file is - # re-sourced so the change propogates to dependent computed values - # (eg: MASTER_SG_NAME, NODE_SG_NAME, etc) - source "${KUBE_ROOT}/cluster/aws/util.sh" - else - echo "Provider \"${KUBERNETES_PROVIDER}\" is not supported" - exit 1 - fi -} diff --git a/cluster/kubemark/gce/config-default.sh b/cluster/kubemark/gce/config-default.sh index 94b7dfb3464..9d159603a94 100644 --- a/cluster/kubemark/gce/config-default.sh +++ b/cluster/kubemark/gce/config-default.sh @@ -37,7 +37,7 @@ EVENT_PD=${EVENT_PD:-false} MASTER_OS_DISTRIBUTION=${KUBE_MASTER_OS_DISTRIBUTION:-gci} NODE_OS_DISTRIBUTION=${KUBE_NODE_OS_DISTRIBUTION:-gci} -MASTER_IMAGE=${KUBE_GCE_MASTER_IMAGE:-cos-stable-60-9592-90-0} +MASTER_IMAGE=${KUBE_GCE_MASTER_IMAGE:-cos-stable-63-10032-71-0} MASTER_IMAGE_PROJECT=${KUBE_GCE_MASTER_PROJECT:-cos-cloud} # GPUs supported in GCE do not have compatible drivers in Debian 7. diff --git a/cluster/kubernetes-anywhere/util.sh b/cluster/kubernetes-anywhere/util.sh new file mode 100755 index 00000000000..8d80b103dab --- /dev/null +++ b/cluster/kubernetes-anywhere/util.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# Copyright 2017 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -x + +NODE_INSTANCE_PREFIX=${NODE_INSTANCE_PREFIX:-"${INSTANCE_PREFIX}-node"} + +source "${KUBE_ROOT}/cluster/gce/util.sh" diff --git a/cluster/libvirt-coreos/util.sh b/cluster/libvirt-coreos/util.sh index d63a247902a..545d9850453 100644 --- a/cluster/libvirt-coreos/util.sh +++ b/cluster/libvirt-coreos/util.sh @@ -27,7 +27,7 @@ source "$KUBE_ROOT/cluster/common.sh" export LIBVIRT_DEFAULT_URI=qemu:///system export SERVICE_ACCOUNT_LOOKUP=${SERVICE_ACCOUNT_LOOKUP:-true} -export ADMISSION_CONTROL=${ADMISSION_CONTROL:-Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,ResourceQuota} +export ADMISSION_CONTROL=${ADMISSION_CONTROL:-Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,PersistentVolumeClaimResize,DefaultTolerationSeconds,PVCProtection,ResourceQuota} readonly POOL=kubernetes readonly POOL_PATH=/var/lib/libvirt/images/kubernetes diff --git a/cluster/log-dump/log-dump.sh b/cluster/log-dump/log-dump.sh index bb13ef3838a..f61c6de34dc 100755 --- a/cluster/log-dump/log-dump.sh +++ b/cluster/log-dump/log-dump.sh @@ -37,9 +37,9 @@ else readonly use_custom_instance_list= fi -readonly master_ssh_supported_providers="gce aws" -readonly node_ssh_supported_providers="gce gke aws" -readonly gcloud_supported_providers="gce gke" +readonly master_ssh_supported_providers="gce aws kubernetes-anywhere" +readonly node_ssh_supported_providers="gce gke aws kubernetes-anywhere" +readonly gcloud_supported_providers="gce gke kubernetes-anywhere" readonly master_logfiles="kube-apiserver kube-apiserver-audit kube-scheduler rescheduler kube-controller-manager etcd etcd-events glbc cluster-autoscaler kube-addon-manager fluentd" readonly node_logfiles="kube-proxy fluentd node-problem-detector" @@ -50,7 +50,7 @@ readonly gce_logfiles="startupscript" readonly kern_logfile="kern" readonly initd_logfiles="docker" readonly supervisord_logfiles="kubelet supervisor/supervisord supervisor/kubelet-stdout supervisor/kubelet-stderr supervisor/docker-stdout supervisor/docker-stderr" -readonly systemd_services="kubelet docker" +readonly systemd_services="kubelet ${LOG_DUMP_SYSTEMD_SERVICES:-docker}" # Limit the number of concurrent node connections so that we don't run out of # file descriptors for large clusters. @@ -141,7 +141,7 @@ function save-logs() { fi else case "${KUBERNETES_PROVIDER}" in - gce|gke) + gce|gke|kubernetes-anywhere) files="${files} ${gce_logfiles}" ;; aws) @@ -219,7 +219,7 @@ function dump_masters() { } function dump_nodes() { - local node_names + local node_names=() if [[ -n "${1:-}" ]]; then echo "Dumping logs for nodes provided as args to dump_nodes() function" node_names=( "$@" ) @@ -232,7 +232,9 @@ function dump_nodes() { else echo "Detecting nodes in the cluster" detect-node-names &> /dev/null - node_names=( "${NODE_NAMES[@]}" ) + if [[ -n "${NODE_NAMES:-}" ]]; then + node_names=( "${NODE_NAMES[@]}" ) + fi fi if [[ "${#node_names[@]}" == 0 ]]; then @@ -283,7 +285,7 @@ function dump_nodes_with_logexporter() { echo "Detecting nodes in the cluster" detect-node-names &> /dev/null - if [[ "${#NODE_NAMES[@]}" == 0 ]]; then + if [[ -z "${NODE_NAMES:-}" ]]; then echo "No nodes found!" return fi diff --git a/cluster/log-dump/logexporter-daemonset.yaml b/cluster/log-dump/logexporter-daemonset.yaml index e1ac568030a..8099da1b9de 100644 --- a/cluster/log-dump/logexporter-daemonset.yaml +++ b/cluster/log-dump/logexporter-daemonset.yaml @@ -20,12 +20,15 @@ type: Opaque data: service-account.json: {{.ServiceAccountCredentials}} --- -apiVersion: extensions/v1beta1 +apiVersion: apps/v1beta2 kind: DaemonSet metadata: name: logexporter namespace: {{.LogexporterNamespace}} spec: + selector: + matchLabels: + app: logexporter template: metadata: labels: diff --git a/cluster/restore-from-backup.sh b/cluster/restore-from-backup.sh index 41865ae0119..912e877de98 100755 --- a/cluster/restore-from-backup.sh +++ b/cluster/restore-from-backup.sh @@ -62,6 +62,9 @@ ETCD_API="$(echo $VERSION_CONTENTS | cut -d '/' -f 2)" # NOTE: NAME HAS TO BE EQUAL TO WHAT WE USE IN --name flag when starting etcd. NAME="${NAME:-etcd-$(hostname)}" +INITIAL_CLUSTER="${INITIAL_CLUSTER:-${NAME}=http://localhost:2380}" +INITIAL_ADVERTISE_PEER_URLS="${INITIAL_ADVERTISE_PEER_URLS:-http://localhost:2380}" + # Port on which etcd is exposed. etcd_port=2379 event_etcd_port=4002 @@ -101,7 +104,7 @@ wait_for_cluster_healthy() { # Wait until etcd and apiserver pods are down. wait_for_etcd_and_apiserver_down() { for i in $(seq 120); do - etcd=$(docker ps | grep etcd | grep -v etcd-empty-dir | grep -v etcd-monitor | wc -l) + etcd=$(docker ps | grep etcd-server | wc -l) apiserver=$(docker ps | grep apiserver | wc -l) # TODO: Theoretically it is possible, that apiserver and or etcd # are currently down, but Kubelet is now restarting them and they @@ -134,6 +137,8 @@ if ! wait_for_etcd_and_apiserver_down; then exit 1 fi +read -rsp $'Press enter when all etcd instances are down...\n' + # Create the sort of directory structure that etcd expects. # If this directory already exists, remove it. BACKUP_DIR="/var/tmp/backup" @@ -185,15 +190,13 @@ elif [ "${ETCD_API}" == "etcd3" ]; then # Run etcdctl snapshot restore command and wait until it is finished. # setting with --name in the etcd manifest file and then it seems to work. - # TODO(jsz): This command may not work in case of HA. - image=$(docker run -d -v ${BACKUP_DIR}:/var/tmp/backup --env ETCDCTL_API=3 \ + docker run -v ${BACKUP_DIR}:/var/tmp/backup --env ETCDCTL_API=3 \ "gcr.io/google_containers/etcd:${ETCD_VERSION}" /bin/sh -c \ - "/usr/local/bin/etcdctl snapshot restore ${BACKUP_DIR}/${snapshot} --name ${NAME} --initial-cluster ${NAME}=http://localhost:2380; mv /${NAME}.etcd/member /var/tmp/backup/") + "/usr/local/bin/etcdctl snapshot restore ${BACKUP_DIR}/${snapshot} --name ${NAME} --initial-cluster ${INITIAL_CLUSTER} --initial-advertise-peer-urls ${INITIAL_ADVERTISE_PEER_URLS}; mv /${NAME}.etcd/member /var/tmp/backup/" if [ "$?" -ne "0" ]; then echo "Docker container didn't started correctly" exit 1 fi - echo "Prepare container exit code: $(docker wait ${image})" rm -f "${BACKUP_DIR}/${snapshot}" fi diff --git a/cluster/saltbase/salt/cluster-autoscaler/cluster-autoscaler.manifest b/cluster/saltbase/salt/cluster-autoscaler/cluster-autoscaler.manifest index 618f6504160..6e6ed2c2508 100644 --- a/cluster/saltbase/salt/cluster-autoscaler/cluster-autoscaler.manifest +++ b/cluster/saltbase/salt/cluster-autoscaler/cluster-autoscaler.manifest @@ -25,7 +25,7 @@ "containers": [ { "name": "cluster-autoscaler", - "image": "gcr.io/google_containers/cluster-autoscaler:v1.0.0", + "image": "gcr.io/google_containers/cluster-autoscaler:v1.1.0", "livenessProbe": { "httpGet": { "path": "/health-check", diff --git a/cluster/saltbase/salt/e2e-image-puller/e2e-image-puller.manifest b/cluster/saltbase/salt/e2e-image-puller/e2e-image-puller.manifest index 9422f1977ea..d7d5a430642 100644 --- a/cluster/saltbase/salt/e2e-image-puller/e2e-image-puller.manifest +++ b/cluster/saltbase/salt/e2e-image-puller/e2e-image-puller.manifest @@ -27,7 +27,56 @@ spec: command: - /bin/sh - -c - - "for i in gcr.io/google_containers/alpine-with-bash:1.0 gcr.io/google_containers/apparmor-loader:0.1 gcr.io/google_containers/busybox:1.24 gcr.io/google_containers/dnsutils:e2e gcr.io/google_containers/e2e-net-amd64:1.0 gcr.io/google_containers/echoserver:1.6 gcr.io/google_containers/eptest:0.1 gcr.io/google_containers/fakegitserver:0.1 gcr.io/google_containers/galera-install:0.1 gcr.io/google_containers/hostexec:1.2 gcr.io/google_containers/invalid-image:invalid-tag gcr.io/google_containers/iperf:e2e gcr.io/google_containers/jessie-dnsutils:e2e gcr.io/google_containers/k8s-dns-dnsmasq-amd64:1.14.5 gcr.io/google_containers/liveness:e2e gcr.io/google_containers/logs-generator:v0.1.0 gcr.io/google_containers/mounttest:0.8 gcr.io/google_containers/mounttest-user:0.5 gcr.io/google_containers/mysql-galera:e2e gcr.io/google_containers/mysql-healthz:1.0 gcr.io/google_containers/netexec:1.4 gcr.io/google_containers/netexec:1.5 gcr.io/google_containers/netexec:1.7 gcr.io/google_containers/nettest:1.7 gcr.io/google_containers/nginx:1.7.9 gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.1 gcr.io/google_containers/nginx-slim:0.7 gcr.io/google_containers/nginx-slim:0.8 gcr.io/google_containers/node-problem-detector:v0.3.0 gcr.io/google_containers/pause gcr.io/google_containers/porter:4524579c0eb935c056c8e75563b4e1eda31587e0 gcr.io/google_containers/portforwardtester:1.2 gcr.io/google_containers/redis-install-3.2.0:e2e gcr.io/google_containers/resource_consumer:beta4 gcr.io/google_containers/resource_consumer/controller:beta4 gcr.io/google_containers/serve_hostname:v1.4 gcr.io/google_containers/servicelb:0.1 gcr.io/google_containers/test-webserver:e2e gcr.io/google_containers/update-demo:kitten gcr.io/google_containers/update-demo:nautilus gcr.io/google_containers/volume-ceph:0.1 gcr.io/google_containers/volume-gluster:0.2 gcr.io/google_containers/volume-iscsi:0.1 gcr.io/google_containers/volume-nfs:0.8 gcr.io/google_containers/volume-rbd:0.1 gcr.io/google_containers/zookeeper-install-3.5.0-alpha:e2e gcr.io/google_samples/gb-redisslave:nonexistent; do echo $(date '+%X') pulling $i; docker pull $i 1>/dev/null; done; exit 0;" + - > + for i in + gcr.io/google_containers/alpine-with-bash:1.0 + gcr.io/google_containers/apparmor-loader:0.1 + gcr.io/google_containers/busybox:1.24 + gcr.io/google_containers/dnsutils:e2e + gcr.io/google_containers/e2e-net-amd64:1.0 + gcr.io/google_containers/echoserver:1.6 + gcr.io/google_containers/eptest:0.1 + gcr.io/google_containers/fakegitserver:0.1 + gcr.io/google_containers/galera-install:0.1 + gcr.io/google_containers/hostexec:1.2 + gcr.io/google_containers/invalid-image:invalid-tag + gcr.io/google_containers/iperf:e2e + gcr.io/google_containers/jessie-dnsutils:e2e + gcr.io/google_containers/k8s-dns-dnsmasq-amd64:1.14.5 + gcr.io/google_containers/liveness:e2e + gcr.io/google_containers/logs-generator:v0.1.0 + gcr.io/google_containers/mounttest:0.8 + gcr.io/google_containers/mounttest-user:0.5 + gcr.io/google_containers/mysql-galera:e2e + gcr.io/google_containers/mysql-healthz:1.0 + gcr.io/google_containers/netexec:1.4 + gcr.io/google_containers/netexec:1.5 + gcr.io/google_containers/netexec:1.7 + gcr.io/google_containers/nettest:1.7 + gcr.io/google_containers/nginx:1.7.9 + gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.1 + gcr.io/google_containers/nginx-slim:0.7 + gcr.io/google_containers/nginx-slim:0.8 + gcr.io/google_containers/node-problem-detector:v0.3.0 + gcr.io/google_containers/pause + gcr.io/google_containers/porter:4524579c0eb935c056c8e75563b4e1eda31587e0 + gcr.io/google_containers/portforwardtester:1.2 + gcr.io/google_containers/redis-install-3.2.0:e2e + gcr.io/google_containers/resource_consumer:beta4 + gcr.io/google_containers/resource_consumer/controller:beta4 + gcr.io/kubernetes-e2e-test-images/serve-hostname-amd64:1.1 + gcr.io/google_containers/servicelb:0.1 + gcr.io/google_containers/test-webserver:e2e + gcr.io/google_containers/update-demo:kitten + gcr.io/google_containers/update-demo:nautilus + gcr.io/google_containers/volume-ceph:0.1 + gcr.io/google_containers/volume-gluster:0.2 + gcr.io/google_containers/volume-iscsi:0.1 + gcr.io/google_containers/volume-nfs:0.8 + gcr.io/google_containers/volume-rbd:0.1 + gcr.io/google_containers/zookeeper-install-3.5.0-alpha:e2e + gcr.io/google_samples/gb-redisslave:nonexistent + ; do echo $(date '+%X') pulling $i; docker pull $i 1>/dev/null; done; exit 0; securityContext: privileged: true volumeMounts: diff --git a/cluster/saltbase/salt/etcd/etcd.manifest b/cluster/saltbase/salt/etcd/etcd.manifest index 44419aa744f..8c16bd15cee 100644 --- a/cluster/saltbase/salt/etcd/etcd.manifest +++ b/cluster/saltbase/salt/etcd/etcd.manifest @@ -22,6 +22,7 @@ {% if pillar.get('storage_backend', 'etcd3') == 'etcd3' -%} {% set quota_bytes = '--quota-backend-bytes=4294967296' -%} {% endif -%} +{% set liveness_probe_initial_delay = pillar.get('etcd_liveness_probe_initial_delay', 15) -%} {% set srv_kube_path = "/srv/kubernetes" -%} { @@ -48,7 +49,7 @@ "command": [ "/bin/sh", "-c", - "if [ -e /usr/local/bin/migrate-if-needed.sh ]; then /usr/local/bin/migrate-if-needed.sh 1>>/var/log/etcd{{ suffix }}.log 2>&1; fi; /usr/local/bin/etcd --name etcd-{{ hostname }} --listen-peer-urls {{ etcd_protocol }}://{{ hostname }}:{{ server_port }} --initial-advertise-peer-urls {{ etcd_protocol }}://{{ hostname }}:{{ server_port }} --advertise-client-urls http://127.0.0.1:{{ port }} --listen-client-urls http://127.0.0.1:{{ port }} {{ quota_bytes }} --data-dir /var/etcd/data{{ suffix }} --initial-cluster-state {{ cluster_state }} --initial-cluster {{ etcd_cluster }} {{ etcd_creds }} 1>>/var/log/etcd{{ suffix }}.log 2>&1" + "if [ -e /usr/local/bin/migrate-if-needed.sh ]; then /usr/local/bin/migrate-if-needed.sh 1>>/var/log/etcd{{ suffix }}.log 2>&1; fi; exec /usr/local/bin/etcd --name etcd-{{ hostname }} --listen-peer-urls {{ etcd_protocol }}://{{ hostname }}:{{ server_port }} --initial-advertise-peer-urls {{ etcd_protocol }}://{{ hostname }}:{{ server_port }} --advertise-client-urls http://127.0.0.1:{{ port }} --listen-client-urls http://127.0.0.1:{{ port }} {{ quota_bytes }} --data-dir /var/etcd/data{{ suffix }} --initial-cluster-state {{ cluster_state }} --initial-cluster {{ etcd_cluster }} {{ etcd_creds }} 1>>/var/log/etcd{{ suffix }}.log 2>&1" ], "env": [ { "name": "TARGET_STORAGE", @@ -67,7 +68,7 @@ "port": {{ port }}, "path": "/health" }, - "initialDelaySeconds": 15, + "initialDelaySeconds": {{ liveness_probe_initial_delay }}, "timeoutSeconds": 15 }, "ports": [ diff --git a/cluster/saltbase/salt/kube-addons/init.sls b/cluster/saltbase/salt/kube-addons/init.sls index d3b86dc4c32..3171cb6ca61 100644 --- a/cluster/saltbase/salt/kube-addons/init.sls +++ b/cluster/saltbase/salt/kube-addons/init.sls @@ -165,6 +165,17 @@ addon-dir-create: - file_mode: 644 {% endif %} +{% if pillar.get('enable_pod_security_policy', '').lower() == 'true' %} +/etc/kubernetes/addons/podsecuritypolicies: + file.recurse: + - source: salt://kube-addons/podsecuritypolicies + - include_pat: E@^.+\.yaml$ + - user: root + - group: root + - dir_mode: 755 + - file_mode: 644 +{% endif %} + {% if pillar.get('enable_cluster_ui', '').lower() == 'true' %} /etc/kubernetes/addons/dashboard: file.recurse: diff --git a/cluster/saltbase/salt/kube-addons/kube-addon-manager.yaml b/cluster/saltbase/salt/kube-addons/kube-addon-manager.yaml index f85baa96545..d345a366aa9 100644 --- a/cluster/saltbase/salt/kube-addons/kube-addon-manager.yaml +++ b/cluster/saltbase/salt/kube-addons/kube-addon-manager.yaml @@ -13,11 +13,11 @@ spec: - name: kube-addon-manager # When updating version also bump it in: # - test/kubemark/resources/manifests/kube-addon-manager.yaml - image: gcr.io/google-containers/kube-addon-manager:v6.4-beta.2 + image: gcr.io/google-containers/kube-addon-manager:v6.5 command: - /bin/bash - -c - - /opt/kube-addons.sh 1>>/var/log/kube-addon-manager.log 2>&1 + - exec /opt/kube-addons.sh 1>>/var/log/kube-addon-manager.log 2>&1 resources: requests: cpu: 5m diff --git a/cluster/saltbase/salt/kube-apiserver/kube-apiserver.manifest b/cluster/saltbase/salt/kube-apiserver/kube-apiserver.manifest index 8b1fdc630c3..878b10f43bf 100644 --- a/cluster/saltbase/salt/kube-apiserver/kube-apiserver.manifest +++ b/cluster/saltbase/salt/kube-apiserver/kube-apiserver.manifest @@ -66,6 +66,8 @@ {% set storage_media_type = "--storage-media-type=" + pillar['storage_media_type'] -%} {% endif -%} +{% set liveness_probe_initial_delay = pillar.get('kube_apiserver_liveness_probe_initial_delay', 15) -%} + {% set request_timeout = "" -%} {% if pillar['kube_apiserver_request_timeout_sec'] is defined -%} {% set request_timeout = "--request-timeout=" + pillar['kube_apiserver_request_timeout_sec'] + "s" -%} @@ -239,7 +241,7 @@ "command": [ "/bin/sh", "-c", - "/usr/local/bin/kube-apiserver {{params}} --allow-privileged={{pillar['allow_privileged']}} 1>>/var/log/kube-apiserver.log 2>&1" + "exec /usr/local/bin/kube-apiserver {{params}} --allow-privileged={{pillar['allow_privileged']}} 1>>/var/log/kube-apiserver.log 2>&1" ], {{container_env}} "livenessProbe": { @@ -248,7 +250,7 @@ "port": 8080, "path": "/healthz" }, - "initialDelaySeconds": 15, + "initialDelaySeconds": {{liveness_probe_initial_delay}}, "timeoutSeconds": 15 }, "ports":[ diff --git a/cluster/saltbase/salt/kube-controller-manager/kube-controller-manager.manifest b/cluster/saltbase/salt/kube-controller-manager/kube-controller-manager.manifest index 65359afda7b..e037d880177 100644 --- a/cluster/saltbase/salt/kube-controller-manager/kube-controller-manager.manifest +++ b/cluster/saltbase/salt/kube-controller-manager/kube-controller-manager.manifest @@ -34,6 +34,8 @@ {% set cloud_config_volume = "" -%} {% set additional_cloud_config_mount = "{\"name\": \"usrsharessl\",\"mountPath\": \"/usr/share/ssl\", \"readOnly\": true}, {\"name\": \"usrssl\",\"mountPath\": \"/usr/ssl\", \"readOnly\": true}, {\"name\": \"usrlibssl\",\"mountPath\": \"/usr/lib/ssl\", \"readOnly\": true}, {\"name\": \"usrlocalopenssl\",\"mountPath\": \"/usr/local/openssl\", \"readOnly\": true}," -%} {% set additional_cloud_config_volume = "{\"name\": \"usrsharessl\",\"hostPath\": {\"path\": \"/usr/share/ssl\"}}, {\"name\": \"usrssl\",\"hostPath\": {\"path\": \"/usr/ssl\"}}, {\"name\": \"usrlibssl\",\"hostPath\": {\"path\": \"/usr/lib/ssl\"}}, {\"name\": \"usrlocalopenssl\",\"hostPath\": {\"path\": \"/usr/local/openssl\"}}," -%} +{% set pv_recycler_mount = "" -%} +{% set pv_recycler_volume = "" -%} {% set srv_kube_path = "/srv/kubernetes" -%} {% if grains.cloud is defined -%} @@ -116,7 +118,7 @@ "command": [ "/bin/sh", "-c", - "/usr/local/bin/kube-controller-manager {{params}} 1>>/var/log/kube-controller-manager.log 2>&1" + "exec /usr/local/bin/kube-controller-manager {{params}} 1>>/var/log/kube-controller-manager.log 2>&1" ], {{container_env}} "livenessProbe": { @@ -131,6 +133,7 @@ "volumeMounts": [ {{cloud_config_mount}} {{additional_cloud_config_mount}} + {{pv_recycler_mount}} { "name": "srvkube", "mountPath": "{{srv_kube_path}}", "readOnly": true}, @@ -158,6 +161,7 @@ "volumes":[ {{cloud_config_volume}} {{additional_cloud_config_volume}} + {{pv_recycler_volume}} { "name": "srvkube", "hostPath": { "path": "{{srv_kube_path}}"} diff --git a/cluster/saltbase/salt/kube-proxy/kube-proxy.manifest b/cluster/saltbase/salt/kube-proxy/kube-proxy.manifest index 8550ae5fd13..d35692a3fd4 100644 --- a/cluster/saltbase/salt/kube-proxy/kube-proxy.manifest +++ b/cluster/saltbase/salt/kube-proxy/kube-proxy.manifest @@ -65,6 +65,11 @@ metadata: spec: {{pod_priority}} hostNetwork: true + tolerations: + - operator: "Exists" + effect: "NoExecute" + - operator: "Exists" + effect: "NoSchedule" containers: - name: kube-proxy image: {{pillar['kube_docker_registry']}}/kube-proxy:{{pillar['kube-proxy_docker_tag']}} @@ -74,7 +79,7 @@ spec: command: - /bin/sh - -c - - kube-proxy {{api_servers_with_port}} {{kubeconfig}} {{cluster_cidr}} --resource-container="" --oom-score-adj=-998 {{params}} 1>>/var/log/kube-proxy.log 2>&1 + - exec kube-proxy {{api_servers_with_port}} {{kubeconfig}} {{cluster_cidr}} --resource-container="" --oom-score-adj=-998 {{params}} 1>>/var/log/kube-proxy.log 2>&1 {{container_env}} {{kube_cache_mutation_detector_env_name}} {{kube_cache_mutation_detector_env_value}} @@ -96,6 +101,9 @@ spec: - mountPath: /run/xtables.lock name: iptableslock readOnly: false + - mountPath: /lib/modules + name: lib-modules + readOnly: true volumes: - hostPath: path: /usr/share/ca-certificates @@ -114,3 +122,6 @@ spec: path: /run/xtables.lock type: FileOrCreate name: iptableslock + - name: lib-modules + hostPath: + path: /lib/modules diff --git a/cluster/saltbase/salt/kube-scheduler/kube-scheduler.manifest b/cluster/saltbase/salt/kube-scheduler/kube-scheduler.manifest index 6f946bd8c86..26436657ede 100644 --- a/cluster/saltbase/salt/kube-scheduler/kube-scheduler.manifest +++ b/cluster/saltbase/salt/kube-scheduler/kube-scheduler.manifest @@ -51,7 +51,7 @@ "command": [ "/bin/sh", "-c", - "/usr/local/bin/kube-scheduler {{params}} 1>>/var/log/kube-scheduler.log 2>&1" + "exec /usr/local/bin/kube-scheduler {{params}} 1>>/var/log/kube-scheduler.log 2>&1" ], "livenessProbe": { "httpGet": { diff --git a/cluster/saltbase/salt/kubelet/default b/cluster/saltbase/salt/kubelet/default index 846935c3d84..27511061e84 100644 --- a/cluster/saltbase/salt/kubelet/default +++ b/cluster/saltbase/salt/kubelet/default @@ -133,7 +133,7 @@ {% elif pillar.get('network_policy_provider', '').lower() == 'calico' and grains['roles'][0] != 'kubernetes-master' %} {% set network_plugin = "--network-plugin=cni --cni-conf-dir=/etc/cni/net.d/ --cni-bin-dir=/home/kubernetes/bin/" %} {% elif pillar.get('network_provider', '').lower() == 'kubenet' %} - {% set network_plugin = "--network-plugin=kubenet --network-plugin-dir=/home/kubernetes/bin/" -%} + {% set network_plugin = "--network-plugin=kubenet --cni-bin-dir=/home/kubernetes/bin/" -%} {% endif -%} # Don't pipe the --hairpin-mode flag by default. This allows the kubelet to pick @@ -171,6 +171,9 @@ {% set kube_proxy_ds_label = "beta.kubernetes.io/kube-proxy-ds-ready=true," %} {% endif %} {% set node_labels = kube_proxy_ds_label + pillar['node_labels'] %} +{% if grains['roles'][0] != 'kubernetes-master' and pillar['non_master_node_labels'] is defined -%} + {% set node_labels = pillar['non_master_node_labels'] + "," + node_labels %} +{% endif %} {% if node_labels != "" %} {% set node_labels="--node-labels=" + node_labels %} {% endif %} diff --git a/cluster/saltbase/salt/l7-gcp/glbc.manifest b/cluster/saltbase/salt/l7-gcp/glbc.manifest index c808e5ee0e6..0ad0dc3de07 100644 --- a/cluster/saltbase/salt/l7-gcp/glbc.manifest +++ b/cluster/saltbase/salt/l7-gcp/glbc.manifest @@ -44,7 +44,7 @@ spec: # TODO: split this out into args when we no longer need to pipe stdout to a file #6428 - sh - -c - - '/glbc --verbose=true --apiserver-host=http://localhost:8080 --default-backend-service=kube-system/default-http-backend --sync-period=600s --running-in-cluster=false --use-real-cloud=true --config-file-path=/etc/gce.conf --healthz-port=8086 1>>/var/log/glbc.log 2>&1' + - 'exec /glbc --verbose=true --apiserver-host=http://localhost:8080 --default-backend-service=kube-system/default-http-backend --sync-period=600s --running-in-cluster=false --use-real-cloud=true --config-file-path=/etc/gce.conf --healthz-port=8086 1>>/var/log/glbc.log 2>&1' volumes: - hostPath: path: /etc/gce.conf diff --git a/cluster/saltbase/salt/rescheduler/rescheduler.manifest b/cluster/saltbase/salt/rescheduler/rescheduler.manifest index ef9af1f5f7f..584d35ca797 100644 --- a/cluster/saltbase/salt/rescheduler/rescheduler.manifest +++ b/cluster/saltbase/salt/rescheduler/rescheduler.manifest @@ -28,7 +28,7 @@ spec: # TODO: split this out into args when we no longer need to pipe stdout to a file #6428 - sh - -c - - '/rescheduler --running-in-cluster=false 1>>/var/log/rescheduler.log 2>&1' + - 'exec /rescheduler --running-in-cluster=false 1>>/var/log/rescheduler.log 2>&1' volumes: - hostPath: path: /var/log/rescheduler.log diff --git a/cluster/update-storage-objects.sh b/cluster/update-storage-objects.sh index fc01bad82d9..364c1d7e37a 100755 --- a/cluster/update-storage-objects.sh +++ b/cluster/update-storage-objects.sh @@ -53,6 +53,7 @@ declare -a resources=( "rolebindings.rbac.authorization.k8s.io" "clusterroles.rbac.authorization.k8s.io" "clusterrolebindings.rbac.authorization.k8s.io" + "networkpolicies.networking.k8s.io" ) # Find all the namespaces. diff --git a/cluster/vagrant/config-default.sh b/cluster/vagrant/config-default.sh index 7eea6e8e77e..63b49146db2 100755 --- a/cluster/vagrant/config-default.sh +++ b/cluster/vagrant/config-default.sh @@ -56,7 +56,7 @@ MASTER_PASSWD="${MASTER_PASSWD:-vagrant}" # Admission Controllers to invoke prior to persisting objects in cluster # If we included ResourceQuota, we should keep it at the end of the list to prevent incrementing quota usage prematurely. -ADMISSION_CONTROL=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,ResourceQuota +ADMISSION_CONTROL=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,PVCProtection,ResourceQuota # Optional: Enable node logging. ENABLE_NODE_LOGGING=false @@ -120,4 +120,3 @@ E2E_STORAGE_TEST_ENVIRONMENT=${KUBE_E2E_STORAGE_TEST_ENVIRONMENT:-false} # Default fallback NETWORK_IF_NAME, will be used in case when no 'VAGRANT-BEGIN' comments were defined in network-script export DEFAULT_NETWORK_IF_NAME="eth0" - diff --git a/cluster/vagrant/util.sh b/cluster/vagrant/util.sh index 4137de1eeab..3d022576d00 100755 --- a/cluster/vagrant/util.sh +++ b/cluster/vagrant/util.sh @@ -296,8 +296,6 @@ function kube-up { # Update the user's kubeconfig to include credentials for this apiserver. create-kubeconfig - - create-kubeconfig-for-federation ) verify-cluster diff --git a/cmd/BUILD b/cmd/BUILD index 75dcc63d834..1d4c2545ec8 100644 --- a/cmd/BUILD +++ b/cmd/BUILD @@ -13,6 +13,7 @@ filegroup( ":package-srcs", "//cmd/clicheck:all-srcs", "//cmd/cloud-controller-manager:all-srcs", + "//cmd/controller-manager/app/options:all-srcs", "//cmd/gendocs:all-srcs", "//cmd/genkubedocs:all-srcs", "//cmd/genman:all-srcs", @@ -25,6 +26,7 @@ filegroup( "//cmd/kube-apiserver:all-srcs", "//cmd/kube-controller-manager:all-srcs", "//cmd/kube-proxy:all-srcs", + "//cmd/kube-scheduler:all-srcs", "//cmd/kubeadm:all-srcs", "//cmd/kubectl:all-srcs", "//cmd/kubelet:all-srcs", diff --git a/cmd/clicheck/BUILD b/cmd/clicheck/BUILD index 7c0565e03bc..2d1df07eab8 100644 --- a/cmd/clicheck/BUILD +++ b/cmd/clicheck/BUILD @@ -8,8 +8,8 @@ load( go_binary( name = "clicheck", + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/clicheck", - library = ":go_default_library", ) go_library( diff --git a/cmd/cloud-controller-manager/BUILD b/cmd/cloud-controller-manager/BUILD index d637599ba00..7ab8666cab0 100644 --- a/cmd/cloud-controller-manager/BUILD +++ b/cmd/cloud-controller-manager/BUILD @@ -9,14 +9,9 @@ load("//pkg/version:def.bzl", "version_x_defs") go_binary( name = "cloud-controller-manager", - gc_linkopts = [ - "-linkmode", - "external", - "-extldflags", - "-static", - ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/cloud-controller-manager", - library = ":go_default_library", + pure = "on", x_defs = version_x_defs(), ) @@ -28,11 +23,9 @@ go_library( "//cmd/cloud-controller-manager/app:go_default_library", "//cmd/cloud-controller-manager/app/options:go_default_library", "//pkg/client/metrics/prometheus:go_default_library", - "//pkg/cloudprovider:go_default_library", "//pkg/cloudprovider/providers:go_default_library", "//pkg/version/prometheus:go_default_library", "//pkg/version/verflag:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/logs:go_default_library", diff --git a/cmd/cloud-controller-manager/app/BUILD b/cmd/cloud-controller-manager/app/BUILD index ecb4d36151a..f378d945812 100644 --- a/cmd/cloud-controller-manager/app/BUILD +++ b/cmd/cloud-controller-manager/app/BUILD @@ -11,7 +11,7 @@ go_library( importpath = "k8s.io/kubernetes/cmd/cloud-controller-manager/app", deps = [ "//cmd/cloud-controller-manager/app/options:go_default_library", - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/cloudprovider:go_default_library", "//pkg/controller:go_default_library", "//pkg/controller/cloud:go_default_library", @@ -23,7 +23,6 @@ go_library( "//vendor/github.com/spf13/cobra:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/apiserver/pkg/server/healthz:go_default_library", "//vendor/k8s.io/client-go/informers:go_default_library", diff --git a/cmd/cloud-controller-manager/app/controllermanager.go b/cmd/cloud-controller-manager/app/controllermanager.go index eb0c84c2ba5..364607b2840 100644 --- a/cmd/cloud-controller-manager/app/controllermanager.go +++ b/cmd/cloud-controller-manager/app/controllermanager.go @@ -28,7 +28,6 @@ import ( "time" "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apiserver/pkg/server/healthz" "k8s.io/client-go/informers" @@ -41,7 +40,7 @@ import ( "k8s.io/client-go/tools/leaderelection/resourcelock" "k8s.io/client-go/tools/record" "k8s.io/kubernetes/cmd/cloud-controller-manager/app/options" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/controller" cloudcontrollers "k8s.io/kubernetes/pkg/controller/cloud" @@ -84,7 +83,28 @@ func resyncPeriod(s *options.CloudControllerManagerServer) func() time.Duration } // Run runs the ExternalCMServer. This should never exit. -func Run(s *options.CloudControllerManagerServer, cloud cloudprovider.Interface) error { +func Run(s *options.CloudControllerManagerServer) error { + if s.CloudProvider == "" { + glog.Fatalf("--cloud-provider cannot be empty") + } + + cloud, err := cloudprovider.InitCloudProvider(s.CloudProvider, s.CloudConfigFile) + if err != nil { + glog.Fatalf("Cloud provider could not be initialized: %v", err) + } + + if cloud == nil { + glog.Fatalf("cloud provider is nil") + } + + if cloud.HasClusterID() == false { + if s.AllowUntaggedCloud == true { + glog.Warning("detected a cluster without a ClusterID. A ClusterID will be required in the future. Please tag your cluster to avoid any future issues") + } else { + glog.Fatalf("no ClusterID found. A ClusterID is required for the cloud provider to function properly. This check can be bypassed by setting the allow-untagged-cloud option") + } + } + if c, err := configz.New("componentconfig"); err == nil { c.Set(s.KubeControllerManagerConfiguration) } else { @@ -120,16 +140,16 @@ func Run(s *options.CloudControllerManagerServer, cloud cloudprovider.Interface) clientBuilder = controller.SAControllerClientBuilder{ ClientConfig: restclient.AnonymousClientConfig(kubeconfig), CoreClient: kubeClient.CoreV1(), - AuthenticationClient: kubeClient.Authentication(), + AuthenticationClient: kubeClient.AuthenticationV1(), Namespace: "kube-system", } } else { clientBuilder = rootClientBuilder } - err := StartControllers(s, kubeconfig, clientBuilder, stop, recorder, cloud) - glog.Fatalf("error running controllers: %v", err) - panic("unreachable") + if err := StartControllers(s, kubeconfig, rootClientBuilder, clientBuilder, stop, recorder, cloud); err != nil { + glog.Fatalf("error running controllers: %v", err) + } } if !s.LeaderElection.LeaderElect { @@ -144,21 +164,21 @@ func Run(s *options.CloudControllerManagerServer, cloud cloudprovider.Interface) } // Lock required for leader election - rl := resourcelock.EndpointsLock{ - EndpointsMeta: metav1.ObjectMeta{ - Namespace: "kube-system", - Name: "cloud-controller-manager", - }, - Client: leaderElectionClient.CoreV1(), - LockConfig: resourcelock.ResourceLockConfig{ - Identity: id + "-external-cloud-controller", + rl, err := resourcelock.New(s.LeaderElection.ResourceLock, + "kube-system", + "cloud-controller-manager", + leaderElectionClient.CoreV1(), + resourcelock.ResourceLockConfig{ + Identity: id, EventRecorder: recorder, - }, + }) + if err != nil { + glog.Fatalf("error creating lock: %v", err) } // Try and become the leader and start cloud controller manager loops leaderelection.RunOrDie(leaderelection.LeaderElectionConfig{ - Lock: &rl, + Lock: rl, LeaseDuration: s.LeaderElection.LeaseDuration.Duration, RenewDeadline: s.LeaderElection.RenewDeadline.Duration, RetryPeriod: s.LeaderElection.RetryPeriod.Duration, @@ -173,7 +193,7 @@ func Run(s *options.CloudControllerManagerServer, cloud cloudprovider.Interface) } // StartControllers starts the cloud specific controller loops. -func StartControllers(s *options.CloudControllerManagerServer, kubeconfig *restclient.Config, clientBuilder controller.ControllerClientBuilder, stop <-chan struct{}, recorder record.EventRecorder, cloud cloudprovider.Interface) error { +func StartControllers(s *options.CloudControllerManagerServer, kubeconfig *restclient.Config, rootClientBuilder, clientBuilder controller.ControllerClientBuilder, stop <-chan struct{}, recorder record.EventRecorder, cloud cloudprovider.Interface) error { // Function to build the kube client object client := func(serviceAccountName string) clientset.Interface { return clientBuilder.ClientOrDie(serviceAccountName) @@ -184,7 +204,7 @@ func StartControllers(s *options.CloudControllerManagerServer, kubeconfig *restc cloud.Initialize(clientBuilder) } - versionedClient := client("shared-informers") + versionedClient := rootClientBuilder.ClientOrDie("shared-informers") sharedInformers := informers.NewSharedInformerFactory(versionedClient, resyncPeriod(s)()) // Start the CloudNodeController @@ -283,5 +303,5 @@ func createRecorder(kubeClient *clientset.Clientset) record.EventRecorder { eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(glog.Infof) eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.CoreV1().RESTClient()).Events("")}) - return eventBroadcaster.NewRecorder(api.Scheme, v1.EventSource{Component: "cloud-controller-manager"}) + return eventBroadcaster.NewRecorder(legacyscheme.Scheme, v1.EventSource{Component: "cloud-controller-manager"}) } diff --git a/cmd/cloud-controller-manager/app/options/BUILD b/cmd/cloud-controller-manager/app/options/BUILD index 62c8e0d01da..9d9c0bb19cd 100644 --- a/cmd/cloud-controller-manager/app/options/BUILD +++ b/cmd/cloud-controller-manager/app/options/BUILD @@ -11,7 +11,7 @@ go_library( srcs = ["options.go"], importpath = "k8s.io/kubernetes/cmd/cloud-controller-manager/app/options", deps = [ - "//pkg/apis/componentconfig:go_default_library", + "//cmd/controller-manager/app/options:go_default_library", "//pkg/client/leaderelectionconfig:go_default_library", "//pkg/features:go_default_library", "//pkg/master/ports:go_default_library", @@ -37,9 +37,10 @@ filegroup( go_test( name = "go_default_test", srcs = ["options_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/cloud-controller-manager/app/options", - library = ":go_default_library", deps = [ + "//cmd/controller-manager/app/options:go_default_library", "//pkg/apis/componentconfig:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/cmd/cloud-controller-manager/app/options/options.go b/cmd/cloud-controller-manager/app/options/options.go index 8d2e4859bc3..5d606b16c14 100644 --- a/cmd/cloud-controller-manager/app/options/options.go +++ b/cmd/cloud-controller-manager/app/options/options.go @@ -21,7 +21,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/kubernetes/pkg/apis/componentconfig" + cmoptions "k8s.io/kubernetes/cmd/controller-manager/app/options" "k8s.io/kubernetes/pkg/client/leaderelectionconfig" "k8s.io/kubernetes/pkg/master/ports" @@ -33,34 +33,19 @@ import ( // CloudControllerManagerServer is the main context object for the controller manager. type CloudControllerManagerServer struct { - componentconfig.KubeControllerManagerConfiguration + cmoptions.ControllerManagerServer - Master string - Kubeconfig string - - // NodeStatusUpdateFrequency is the freuency at which the controller updates nodes' status + // NodeStatusUpdateFrequency is the frequency at which the controller updates nodes' status NodeStatusUpdateFrequency metav1.Duration } // NewCloudControllerManagerServer creates a new ExternalCMServer with a default config. func NewCloudControllerManagerServer() *CloudControllerManagerServer { s := CloudControllerManagerServer{ - // Part of these default values also present in 'cmd/kube-controller-manager/app/options/options.go'. - // Please keep them in sync when doing update. - KubeControllerManagerConfiguration: componentconfig.KubeControllerManagerConfiguration{ - Port: ports.CloudControllerManagerPort, - Address: "0.0.0.0", - ConcurrentServiceSyncs: 1, - MinResyncPeriod: metav1.Duration{Duration: 12 * time.Hour}, - NodeMonitorPeriod: metav1.Duration{Duration: 5 * time.Second}, - ClusterName: "kubernetes", - ConfigureCloudRoutes: true, - ContentType: "application/vnd.kubernetes.protobuf", - KubeAPIQPS: 20.0, - KubeAPIBurst: 30, - LeaderElection: leaderelectionconfig.DefaultLeaderElectionConfiguration(), - ControllerStartInterval: metav1.Duration{Duration: 0 * time.Second}, - RouteReconciliationPeriod: metav1.Duration{Duration: 10 * time.Second}, + // The common/default are kept in 'cmd/kube-controller-manager/app/options/util.go'. + // Please make common changes there and put anything cloud specific here. + ControllerManagerServer: cmoptions.ControllerManagerServer{ + KubeControllerManagerConfiguration: cmoptions.GetDefaultControllerOptions(ports.CloudControllerManagerPort), }, NodeStatusUpdateFrequency: metav1.Duration{Duration: 5 * time.Minute}, } @@ -70,33 +55,13 @@ func NewCloudControllerManagerServer() *CloudControllerManagerServer { // AddFlags adds flags for a specific ExternalCMServer to the specified FlagSet func (s *CloudControllerManagerServer) AddFlags(fs *pflag.FlagSet) { - fs.Int32Var(&s.Port, "port", s.Port, "The port that the cloud-controller-manager's http service runs on.") - fs.Var(componentconfig.IPVar{Val: &s.Address}, "address", "The IP address to serve on (set to 0.0.0.0 for all interfaces).") + cmoptions.AddDefaultControllerFlags(&s.ControllerManagerServer, fs) fs.StringVar(&s.CloudProvider, "cloud-provider", s.CloudProvider, "The provider of cloud services. Cannot be empty.") - fs.StringVar(&s.CloudConfigFile, "cloud-config", s.CloudConfigFile, "The path to the cloud provider configuration file. Empty string for no configuration file.") - fs.BoolVar(&s.AllowUntaggedCloud, "allow-untagged-cloud", false, "Allow the cluster to run without the cluster-id on cloud instances. This is a legacy mode of operation and a cluster-id will be required in the future.") - fs.MarkDeprecated("allow-untagged-cloud", "This flag is deprecated and will be removed in a future release. A cluster-id will be required on cloud instances.") - fs.DurationVar(&s.MinResyncPeriod.Duration, "min-resync-period", s.MinResyncPeriod.Duration, "The resync period in reflectors will be random between MinResyncPeriod and 2*MinResyncPeriod.") - fs.DurationVar(&s.NodeMonitorPeriod.Duration, "node-monitor-period", s.NodeMonitorPeriod.Duration, - "The period for syncing NodeStatus in NodeController.") fs.DurationVar(&s.NodeStatusUpdateFrequency.Duration, "node-status-update-frequency", s.NodeStatusUpdateFrequency.Duration, "Specifies how often the controller updates nodes' status.") // TODO: remove --service-account-private-key-file 6 months after 1.8 is released (~1.10) fs.StringVar(&s.ServiceAccountKeyFile, "service-account-private-key-file", s.ServiceAccountKeyFile, "Filename containing a PEM-encoded private RSA or ECDSA key used to sign service account tokens.") fs.MarkDeprecated("service-account-private-key-file", "This flag is currently no-op and will be deleted.") - fs.BoolVar(&s.UseServiceAccountCredentials, "use-service-account-credentials", s.UseServiceAccountCredentials, "If true, use individual service account credentials for each controller.") - fs.DurationVar(&s.RouteReconciliationPeriod.Duration, "route-reconciliation-period", s.RouteReconciliationPeriod.Duration, "The period for reconciling routes created for Nodes by cloud provider.") - fs.BoolVar(&s.ConfigureCloudRoutes, "configure-cloud-routes", true, "Should CIDRs allocated by allocate-node-cidrs be configured on the cloud provider.") - fs.BoolVar(&s.EnableProfiling, "profiling", true, "Enable profiling via web interface host:port/debug/pprof/.") - fs.BoolVar(&s.EnableContentionProfiling, "contention-profiling", false, "Enable lock contention profiling, if profiling is enabled.") - fs.StringVar(&s.ClusterCIDR, "cluster-cidr", s.ClusterCIDR, "CIDR Range for Pods in cluster.") - fs.StringVar(&s.ClusterName, "cluster-name", s.ClusterName, "The instance prefix for the cluster.") - fs.BoolVar(&s.AllocateNodeCIDRs, "allocate-node-cidrs", false, "Should CIDRs for Pods be allocated and set on the cloud provider.") - fs.StringVar(&s.Master, "master", s.Master, "The address of the Kubernetes API server (overrides any value in kubeconfig).") - fs.StringVar(&s.Kubeconfig, "kubeconfig", s.Kubeconfig, "Path to kubeconfig file with authorization and master location information.") - fs.StringVar(&s.ContentType, "kube-api-content-type", s.ContentType, "Content type of requests sent to apiserver.") - fs.Float32Var(&s.KubeAPIQPS, "kube-api-qps", s.KubeAPIQPS, "QPS to use while talking with kubernetes apiserver.") - fs.Int32Var(&s.KubeAPIBurst, "kube-api-burst", s.KubeAPIBurst, "Burst to use while talking with kubernetes apiserver.") - fs.DurationVar(&s.ControllerStartInterval.Duration, "controller-start-interval", s.ControllerStartInterval.Duration, "Interval between starting controller managers.") + fs.Int32Var(&s.ConcurrentServiceSyncs, "concurrent-service-syncs", s.ConcurrentServiceSyncs, "The number of services that are allowed to sync concurrently. Larger number = more responsive service management, but more CPU (and network) load") leaderelectionconfig.BindFlags(&s.LeaderElection, fs) diff --git a/cmd/cloud-controller-manager/app/options/options_test.go b/cmd/cloud-controller-manager/app/options/options_test.go index ece3bf09d22..7538169b427 100644 --- a/cmd/cloud-controller-manager/app/options/options_test.go +++ b/cmd/cloud-controller-manager/app/options/options_test.go @@ -25,6 +25,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/diff" + cmoptions "k8s.io/kubernetes/cmd/controller-manager/app/options" "k8s.io/kubernetes/pkg/apis/componentconfig" ) @@ -65,34 +66,82 @@ func TestAddFlags(t *testing.T) { f.Parse(args) expected := &CloudControllerManagerServer{ - KubeControllerManagerConfiguration: componentconfig.KubeControllerManagerConfiguration{ - CloudProvider: "gce", - CloudConfigFile: "/cloud-config", - Port: 10000, - Address: "192.168.4.10", - ConcurrentServiceSyncs: 1, - MinResyncPeriod: metav1.Duration{Duration: 100 * time.Minute}, - NodeMonitorPeriod: metav1.Duration{Duration: 5 * time.Second}, - ClusterName: "k8s", - ConfigureCloudRoutes: false, - AllocateNodeCIDRs: true, - ContentType: "application/vnd.kubernetes.protobuf", - EnableContentionProfiling: true, - KubeAPIQPS: 50.0, - KubeAPIBurst: 100, - LeaderElection: componentconfig.LeaderElectionConfiguration{ - ResourceLock: "configmap", - LeaderElect: false, - LeaseDuration: metav1.Duration{Duration: 30 * time.Second}, - RenewDeadline: metav1.Duration{Duration: 15 * time.Second}, - RetryPeriod: metav1.Duration{Duration: 5 * time.Second}, + ControllerManagerServer: cmoptions.ControllerManagerServer{ + KubeControllerManagerConfiguration: componentconfig.KubeControllerManagerConfiguration{ + CloudProvider: "gce", + CloudConfigFile: "/cloud-config", + Port: 10000, + Address: "192.168.4.10", + ConcurrentEndpointSyncs: 5, + ConcurrentRSSyncs: 5, + ConcurrentResourceQuotaSyncs: 5, + ConcurrentDeploymentSyncs: 5, + ConcurrentDaemonSetSyncs: 2, + ConcurrentJobSyncs: 5, + ConcurrentNamespaceSyncs: 10, + ConcurrentSATokenSyncs: 5, + ConcurrentServiceSyncs: 1, + ConcurrentGCSyncs: 20, + ConcurrentRCSyncs: 5, + MinResyncPeriod: metav1.Duration{Duration: 100 * time.Minute}, + NodeMonitorPeriod: metav1.Duration{Duration: 5 * time.Second}, + ServiceSyncPeriod: metav1.Duration{Duration: 5 * time.Minute}, + ResourceQuotaSyncPeriod: metav1.Duration{Duration: 5 * time.Minute}, + NamespaceSyncPeriod: metav1.Duration{Duration: 5 * time.Minute}, + PVClaimBinderSyncPeriod: metav1.Duration{Duration: 15 * time.Second}, + HorizontalPodAutoscalerSyncPeriod: metav1.Duration{Duration: 30 * time.Second}, + HorizontalPodAutoscalerUpscaleForbiddenWindow: metav1.Duration{Duration: 3 * time.Minute}, + HorizontalPodAutoscalerDownscaleForbiddenWindow: metav1.Duration{Duration: 5 * time.Minute}, + HorizontalPodAutoscalerTolerance: 0.1, + DeploymentControllerSyncPeriod: metav1.Duration{Duration: 30 * time.Second}, + PodEvictionTimeout: metav1.Duration{Duration: 5 * time.Minute}, + NodeMonitorGracePeriod: metav1.Duration{Duration: 40 * time.Second}, + NodeStartupGracePeriod: metav1.Duration{Duration: 1 * time.Minute}, + ClusterSigningDuration: metav1.Duration{Duration: 8760 * time.Hour}, + ReconcilerSyncLoopPeriod: metav1.Duration{Duration: 1 * time.Minute}, + TerminatedPodGCThreshold: 12500, + RegisterRetryCount: 10, + ClusterName: "k8s", + ConfigureCloudRoutes: false, + AllocateNodeCIDRs: true, + EnableGarbageCollector: true, + EnableTaintManager: true, + HorizontalPodAutoscalerUseRESTClients: true, + VolumeConfiguration: componentconfig.VolumeConfiguration{ + EnableDynamicProvisioning: true, + EnableHostPathProvisioning: false, + FlexVolumePluginDir: "/usr/libexec/kubernetes/kubelet-plugins/volume/exec/", + PersistentVolumeRecyclerConfiguration: componentconfig.PersistentVolumeRecyclerConfiguration{ + MaximumRetry: 3, + MinimumTimeoutNFS: 300, + IncrementTimeoutNFS: 30, + MinimumTimeoutHostPath: 60, + IncrementTimeoutHostPath: 30, + }, + }, + ContentType: "application/vnd.kubernetes.protobuf", + ClusterSigningCertFile: "/etc/kubernetes/ca/ca.pem", + ClusterSigningKeyFile: "/etc/kubernetes/ca/ca.key", + EnableContentionProfiling: true, + KubeAPIQPS: 50.0, + KubeAPIBurst: 100, + LeaderElection: componentconfig.LeaderElectionConfiguration{ + ResourceLock: "configmap", + LeaderElect: false, + LeaseDuration: metav1.Duration{Duration: 30 * time.Second}, + RenewDeadline: metav1.Duration{Duration: 15 * time.Second}, + RetryPeriod: metav1.Duration{Duration: 5 * time.Second}, + }, + ControllerStartInterval: metav1.Duration{Duration: 2 * time.Minute}, + RouteReconciliationPeriod: metav1.Duration{Duration: 30 * time.Second}, + ClusterCIDR: "1.2.3.4/24", + NodeCIDRMaskSize: 24, + CIDRAllocatorType: "RangeAllocator", + Controllers: []string{"*"}, }, - ControllerStartInterval: metav1.Duration{Duration: 2 * time.Minute}, - RouteReconciliationPeriod: metav1.Duration{Duration: 30 * time.Second}, - ClusterCIDR: "1.2.3.4/24", + Kubeconfig: "/kubeconfig", + Master: "192.168.4.20", }, - Kubeconfig: "/kubeconfig", - Master: "192.168.4.20", NodeStatusUpdateFrequency: metav1.Duration{Duration: 10 * time.Minute}, } if !reflect.DeepEqual(expected, s) { diff --git a/cmd/cloud-controller-manager/controller-manager.go b/cmd/cloud-controller-manager/controller-manager.go index 60bac4ac729..1d8b498fbf1 100644 --- a/cmd/cloud-controller-manager/controller-manager.go +++ b/cmd/cloud-controller-manager/controller-manager.go @@ -28,14 +28,12 @@ import ( "k8s.io/kubernetes/cmd/cloud-controller-manager/app" "k8s.io/kubernetes/cmd/cloud-controller-manager/app/options" _ "k8s.io/kubernetes/pkg/client/metrics/prometheus" // for client metric registration - "k8s.io/kubernetes/pkg/cloudprovider" // NOTE: Importing all in-tree cloud-providers is not required when // implementing an out-of-tree cloud-provider. _ "k8s.io/kubernetes/pkg/cloudprovider/providers" _ "k8s.io/kubernetes/pkg/version/prometheus" // for version metric registration "k8s.io/kubernetes/pkg/version/verflag" - "github.com/golang/glog" "github.com/spf13/pflag" ) @@ -49,24 +47,7 @@ func main() { verflag.PrintAndExitIfRequested() - if s.CloudProvider == "" { - glog.Errorf("--cloud-provider cannot be empty") - } - - cloud, err := cloudprovider.InitCloudProvider(s.CloudProvider, s.CloudConfigFile) - if err != nil { - glog.Fatalf("Cloud provider could not be initialized: %v", err) - } - - if cloud.HasClusterID() == false { - if s.AllowUntaggedCloud == true { - glog.Warning("detected a cluster without a ClusterID. A ClusterID will be required in the future. Please tag your cluster to avoid any future issues") - } else { - glog.Fatalf("no ClusterID found. A ClusterID is required for the cloud provider to function properly. This check can be bypassed by setting the allow-untagged-cloud option") - } - } - - if err := app.Run(s, cloud); err != nil { + if err := app.Run(s); err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) os.Exit(1) } diff --git a/cmd/controller-manager/app/options/BUILD b/cmd/controller-manager/app/options/BUILD new file mode 100644 index 00000000000..2ad13dea877 --- /dev/null +++ b/cmd/controller-manager/app/options/BUILD @@ -0,0 +1,29 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["utils.go"], + importpath = "k8s.io/kubernetes/cmd/controller-manager/app/options", + visibility = ["//visibility:public"], + deps = [ + "//pkg/apis/componentconfig:go_default_library", + "//pkg/client/leaderelectionconfig:go_default_library", + "//vendor/github.com/cloudflare/cfssl/helpers:go_default_library", + "//vendor/github.com/spf13/pflag:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/cmd/controller-manager/app/options/utils.go b/cmd/controller-manager/app/options/utils.go new file mode 100644 index 00000000000..903158f2eb4 --- /dev/null +++ b/cmd/controller-manager/app/options/utils.go @@ -0,0 +1,140 @@ +/* +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 options + +import ( + "github.com/cloudflare/cfssl/helpers" + "time" + + "github.com/spf13/pflag" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/pkg/apis/componentconfig" + "k8s.io/kubernetes/pkg/client/leaderelectionconfig" +) + +// ControllerManagerServer is the common structure for a controller manager. It works with GetDefaultControllerOptions +// and AddDefaultControllerFlags to create the common components of kube-controller-manager and cloud-controller-manager. +type ControllerManagerServer struct { + componentconfig.KubeControllerManagerConfiguration + + Master string + Kubeconfig string +} + +const ( + // These defaults are deprecated and exported so that we can warn if + // they are being used. + + // DefaultClusterSigningCertFile is deprecated. Do not use. + DefaultClusterSigningCertFile = "/etc/kubernetes/ca/ca.pem" + // DefaultClusterSigningKeyFile is deprecated. Do not use. + DefaultClusterSigningKeyFile = "/etc/kubernetes/ca/ca.key" +) + +// GetDefaultControllerOptions returns common/default configuration values for both +// the kube-controller-manager and the cloud-contoller-manager. Any common changes should +// be made here. Any individual changes should be made in that controller. +func GetDefaultControllerOptions(port int32) componentconfig.KubeControllerManagerConfiguration { + return componentconfig.KubeControllerManagerConfiguration{ + Controllers: []string{"*"}, + Port: port, + Address: "0.0.0.0", + ConcurrentEndpointSyncs: 5, + ConcurrentServiceSyncs: 1, + ConcurrentRCSyncs: 5, + ConcurrentRSSyncs: 5, + ConcurrentDaemonSetSyncs: 2, + ConcurrentJobSyncs: 5, + ConcurrentResourceQuotaSyncs: 5, + ConcurrentDeploymentSyncs: 5, + ConcurrentNamespaceSyncs: 10, + ConcurrentSATokenSyncs: 5, + ServiceSyncPeriod: metav1.Duration{Duration: 5 * time.Minute}, + RouteReconciliationPeriod: metav1.Duration{Duration: 10 * time.Second}, + ResourceQuotaSyncPeriod: metav1.Duration{Duration: 5 * time.Minute}, + NamespaceSyncPeriod: metav1.Duration{Duration: 5 * time.Minute}, + PVClaimBinderSyncPeriod: metav1.Duration{Duration: 15 * time.Second}, + HorizontalPodAutoscalerSyncPeriod: metav1.Duration{Duration: 30 * time.Second}, + HorizontalPodAutoscalerUpscaleForbiddenWindow: metav1.Duration{Duration: 3 * time.Minute}, + HorizontalPodAutoscalerDownscaleForbiddenWindow: metav1.Duration{Duration: 5 * time.Minute}, + HorizontalPodAutoscalerTolerance: 0.1, + DeploymentControllerSyncPeriod: metav1.Duration{Duration: 30 * time.Second}, + MinResyncPeriod: metav1.Duration{Duration: 12 * time.Hour}, + RegisterRetryCount: 10, + PodEvictionTimeout: metav1.Duration{Duration: 5 * time.Minute}, + NodeMonitorGracePeriod: metav1.Duration{Duration: 40 * time.Second}, + NodeStartupGracePeriod: metav1.Duration{Duration: 60 * time.Second}, + NodeMonitorPeriod: metav1.Duration{Duration: 5 * time.Second}, + ClusterName: "kubernetes", + NodeCIDRMaskSize: 24, + ConfigureCloudRoutes: true, + TerminatedPodGCThreshold: 12500, + VolumeConfiguration: componentconfig.VolumeConfiguration{ + EnableHostPathProvisioning: false, + EnableDynamicProvisioning: true, + PersistentVolumeRecyclerConfiguration: componentconfig.PersistentVolumeRecyclerConfiguration{ + MaximumRetry: 3, + MinimumTimeoutNFS: 300, + IncrementTimeoutNFS: 30, + MinimumTimeoutHostPath: 60, + IncrementTimeoutHostPath: 30, + }, + FlexVolumePluginDir: "/usr/libexec/kubernetes/kubelet-plugins/volume/exec/", + }, + ContentType: "application/vnd.kubernetes.protobuf", + KubeAPIQPS: 20.0, + KubeAPIBurst: 30, + LeaderElection: leaderelectionconfig.DefaultLeaderElectionConfiguration(), + ControllerStartInterval: metav1.Duration{Duration: 0 * time.Second}, + EnableGarbageCollector: true, + ConcurrentGCSyncs: 20, + ClusterSigningCertFile: DefaultClusterSigningCertFile, + ClusterSigningKeyFile: DefaultClusterSigningKeyFile, + ClusterSigningDuration: metav1.Duration{Duration: helpers.OneYear}, + ReconcilerSyncLoopPeriod: metav1.Duration{Duration: 60 * time.Second}, + EnableTaintManager: true, + HorizontalPodAutoscalerUseRESTClients: true, + } +} + +// AddDefaultControllerFlags adds common/default flags for both the kube and cloud Controller Manager Server to the +// specified FlagSet. Any common changes should be made here. Any individual changes should be made in that controller. +func AddDefaultControllerFlags(s *ControllerManagerServer, fs *pflag.FlagSet) { + fs.Int32Var(&s.Port, "port", s.Port, "The port that the controller-manager's http service runs on.") + fs.Var(componentconfig.IPVar{Val: &s.Address}, "address", "The IP address to serve on (set to 0.0.0.0 for all interfaces).") + fs.BoolVar(&s.UseServiceAccountCredentials, "use-service-account-credentials", s.UseServiceAccountCredentials, "If true, use individual service account credentials for each controller.") + fs.StringVar(&s.CloudConfigFile, "cloud-config", s.CloudConfigFile, "The path to the cloud provider configuration file. Empty string for no configuration file.") + fs.BoolVar(&s.AllowUntaggedCloud, "allow-untagged-cloud", false, "Allow the cluster to run without the cluster-id on cloud instances. This is a legacy mode of operation and a cluster-id will be required in the future.") + fs.MarkDeprecated("allow-untagged-cloud", "This flag is deprecated and will be removed in a future release. A cluster-id will be required on cloud instances.") + fs.DurationVar(&s.RouteReconciliationPeriod.Duration, "route-reconciliation-period", s.RouteReconciliationPeriod.Duration, "The period for reconciling routes created for Nodes by cloud provider.") + fs.DurationVar(&s.MinResyncPeriod.Duration, "min-resync-period", s.MinResyncPeriod.Duration, "The resync period in reflectors will be random between MinResyncPeriod and 2*MinResyncPeriod.") + fs.DurationVar(&s.NodeMonitorPeriod.Duration, "node-monitor-period", s.NodeMonitorPeriod.Duration, + "The period for syncing NodeStatus in NodeController.") + fs.BoolVar(&s.EnableProfiling, "profiling", true, "Enable profiling via web interface host:port/debug/pprof/") + fs.BoolVar(&s.EnableContentionProfiling, "contention-profiling", false, "Enable lock contention profiling, if profiling is enabled.") + fs.StringVar(&s.ClusterName, "cluster-name", s.ClusterName, "The instance prefix for the cluster.") + fs.StringVar(&s.ClusterCIDR, "cluster-cidr", s.ClusterCIDR, "CIDR Range for Pods in cluster. Requires --allocate-node-cidrs to be true") + fs.BoolVar(&s.AllocateNodeCIDRs, "allocate-node-cidrs", false, "Should CIDRs for Pods be allocated and set on the cloud provider.") + fs.StringVar(&s.CIDRAllocatorType, "cidr-allocator-type", "RangeAllocator", "Type of CIDR allocator to use") + fs.BoolVar(&s.ConfigureCloudRoutes, "configure-cloud-routes", true, "Should CIDRs allocated by allocate-node-cidrs be configured on the cloud provider.") + fs.StringVar(&s.Master, "master", s.Master, "The address of the Kubernetes API server (overrides any value in kubeconfig).") + fs.StringVar(&s.Kubeconfig, "kubeconfig", s.Kubeconfig, "Path to kubeconfig file with authorization and master location information.") + fs.StringVar(&s.ContentType, "kube-api-content-type", s.ContentType, "Content type of requests sent to apiserver.") + fs.Float32Var(&s.KubeAPIQPS, "kube-api-qps", s.KubeAPIQPS, "QPS to use while talking with kubernetes apiserver.") + fs.Int32Var(&s.KubeAPIBurst, "kube-api-burst", s.KubeAPIBurst, "Burst to use while talking with kubernetes apiserver.") + fs.DurationVar(&s.ControllerStartInterval.Duration, "controller-start-interval", s.ControllerStartInterval.Duration, "Interval between starting controller managers.") +} diff --git a/cmd/gendocs/BUILD b/cmd/gendocs/BUILD index 372300d965f..aa36f4f8cb4 100644 --- a/cmd/gendocs/BUILD +++ b/cmd/gendocs/BUILD @@ -8,8 +8,8 @@ load( go_binary( name = "gendocs", + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/gendocs", - library = ":go_default_library", ) go_library( diff --git a/cmd/genkubedocs/BUILD b/cmd/genkubedocs/BUILD index c3c418c7b5d..4041333ae0d 100644 --- a/cmd/genkubedocs/BUILD +++ b/cmd/genkubedocs/BUILD @@ -4,17 +4,21 @@ load( "@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", + "go_test", ) go_binary( name = "genkubedocs", + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/genkubedocs", - library = ":go_default_library", ) go_library( name = "go_default_library", - srcs = ["gen_kube_docs.go"], + srcs = [ + "gen_kube_docs.go", + "postprocessing.go", + ], importpath = "k8s.io/kubernetes/cmd/genkubedocs", deps = [ "//cmd/cloud-controller-manager/app:go_default_library", @@ -22,9 +26,12 @@ go_library( "//cmd/kube-apiserver/app:go_default_library", "//cmd/kube-controller-manager/app:go_default_library", "//cmd/kube-proxy/app:go_default_library", + "//cmd/kube-scheduler/app:go_default_library", + "//cmd/kubeadm/app/cmd:go_default_library", "//cmd/kubelet/app:go_default_library", - "//plugin/cmd/kube-scheduler/app:go_default_library", + "//vendor/github.com/spf13/cobra:go_default_library", "//vendor/github.com/spf13/cobra/doc:go_default_library", + "//vendor/github.com/spf13/pflag:go_default_library", ], ) @@ -40,3 +47,10 @@ filegroup( srcs = [":package-srcs"], tags = ["automanaged"], ) + +go_test( + name = "go_default_test", + srcs = ["postprocessing_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/cmd/genkubedocs", +) diff --git a/cmd/genkubedocs/gen_kube_docs.go b/cmd/genkubedocs/gen_kube_docs.go index faebb60e83b..975466f4418 100644 --- a/cmd/genkubedocs/gen_kube_docs.go +++ b/cmd/genkubedocs/gen_kube_docs.go @@ -21,13 +21,15 @@ import ( "os" "github.com/spf13/cobra/doc" + "github.com/spf13/pflag" ccmapp "k8s.io/kubernetes/cmd/cloud-controller-manager/app" "k8s.io/kubernetes/cmd/genutils" apiservapp "k8s.io/kubernetes/cmd/kube-apiserver/app" cmapp "k8s.io/kubernetes/cmd/kube-controller-manager/app" proxyapp "k8s.io/kubernetes/cmd/kube-proxy/app" + schapp "k8s.io/kubernetes/cmd/kube-scheduler/app" + kubeadmapp "k8s.io/kubernetes/cmd/kubeadm/app/cmd" kubeletapp "k8s.io/kubernetes/cmd/kubelet/app" - schapp "k8s.io/kubernetes/plugin/cmd/kube-scheduler/app" ) func main() { @@ -73,6 +75,19 @@ func main() { // generate docs for kubelet kubelet := kubeletapp.NewKubeletCommand() doc.GenMarkdownTree(kubelet, outDir) + case "kubeadm": + // resets global flags created by kubelet or other commands e.g. + // --azure-container-registry-config from pkg/credentialprovider/azure + // --google-json-key from pkg/credentialprovider/gcp + // --version pkg/version/verflag + pflag.CommandLine = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError) + + // generate docs for kubeadm + kubeadm := kubeadmapp.NewKubeadmCommand(os.Stdin, os.Stdout, os.Stderr) + doc.GenMarkdownTree(kubeadm, outDir) + + // cleanup generated code for usage as include in the website + MarkdownPostProcessing(kubeadm, outDir, cleanupForInclude) default: fmt.Fprintf(os.Stderr, "Module %s is not supported", module) os.Exit(1) diff --git a/cmd/genkubedocs/postprocessing.go b/cmd/genkubedocs/postprocessing.go new file mode 100644 index 00000000000..57b6d95e058 --- /dev/null +++ b/cmd/genkubedocs/postprocessing.go @@ -0,0 +1,73 @@ +/* +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 main + +import ( + "io/ioutil" + "path/filepath" + "strings" + + "github.com/spf13/cobra" +) + +// MarkdownPostProcessing goes though the generated files +func MarkdownPostProcessing(cmd *cobra.Command, dir string, processor func(string) string) error { + for _, c := range cmd.Commands() { + if !c.IsAvailableCommand() || c.IsHelpCommand() { + continue + } + if err := MarkdownPostProcessing(c, dir, processor); err != nil { + return err + } + } + + basename := strings.Replace(cmd.CommandPath(), " ", "_", -1) + ".md" + filename := filepath.Join(dir, basename) + + markdownBytes, err := ioutil.ReadFile(filename) + if err != nil { + return err + } + + processedMarkDown := processor(string(markdownBytes)) + + return ioutil.WriteFile(filename, []byte(processedMarkDown), 0644) +} + +// cleanupForInclude parts of markdown that will make difficult to use it as include in the website: +// - The title of the document (this allow more flexibility for include, e.g. include in tabs) +// - The sections see also, that assumes file will be used as a main page +func cleanupForInclude(md string) string { + lines := strings.Split(md, "\n") + + cleanMd := "" + for i, line := range lines { + if i == 0 { + continue + } + if line == "### SEE ALSO" { + break + } + + cleanMd += line + if i < len(lines)-1 { + cleanMd += "\n" + } + } + + return cleanMd +} diff --git a/cmd/genkubedocs/postprocessing_test.go b/cmd/genkubedocs/postprocessing_test.go new file mode 100644 index 00000000000..57afedf05a2 --- /dev/null +++ b/cmd/genkubedocs/postprocessing_test.go @@ -0,0 +1,57 @@ +/* +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 main + +import ( + "testing" +) + +func TestCleanupForInclude(t *testing.T) { + + var tests = []struct { + markdown, expectedMarkdown string + }{ + { // first line is removed + // Nb. fist line is the title of the document, and by removing it you get + // more flexibility for include, e.g. include in tabs + markdown: "line 1\n" + + "line 2\n" + + "line 3", + expectedMarkdown: "line 2\n" + + "line 3", + }, + { // evething after ###SEE ALSO is removed + // Nb. see also, that assumes file will be used as a main page (does not apply to includes) + markdown: "line 1\n" + + "line 2\n" + + "### SEE ALSO\n" + + "line 3", + expectedMarkdown: "line 2\n", + }, + } + for _, rt := range tests { + actual := cleanupForInclude(rt.markdown) + if actual != rt.expectedMarkdown { + t.Errorf( + "failed cleanupForInclude:\n\texpected: %s\n\t actual: %s", + rt.expectedMarkdown, + actual, + ) + } + } + +} diff --git a/cmd/genman/BUILD b/cmd/genman/BUILD index 68e73720156..c3fcedb8074 100644 --- a/cmd/genman/BUILD +++ b/cmd/genman/BUILD @@ -8,8 +8,8 @@ load( go_binary( name = "genman", + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/genman", - library = ":go_default_library", ) go_library( @@ -22,10 +22,11 @@ go_library( "//cmd/kube-apiserver/app:go_default_library", "//cmd/kube-controller-manager/app:go_default_library", "//cmd/kube-proxy/app:go_default_library", + "//cmd/kube-scheduler/app:go_default_library", + "//cmd/kubeadm/app/cmd:go_default_library", "//cmd/kubelet/app:go_default_library", "//pkg/kubectl/cmd:go_default_library", "//pkg/kubectl/cmd/util:go_default_library", - "//plugin/cmd/kube-scheduler/app:go_default_library", "//vendor/github.com/cpuguy83/go-md2man/md2man:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", diff --git a/cmd/genman/gen_kube_man.go b/cmd/genman/gen_kube_man.go index f8974951252..416d4a9f9fa 100644 --- a/cmd/genman/gen_kube_man.go +++ b/cmd/genman/gen_kube_man.go @@ -31,10 +31,11 @@ import ( apiservapp "k8s.io/kubernetes/cmd/kube-apiserver/app" cmapp "k8s.io/kubernetes/cmd/kube-controller-manager/app" proxyapp "k8s.io/kubernetes/cmd/kube-proxy/app" + schapp "k8s.io/kubernetes/cmd/kube-scheduler/app" + kubeadmapp "k8s.io/kubernetes/cmd/kubeadm/app/cmd" kubeletapp "k8s.io/kubernetes/cmd/kubelet/app" kubectlcmd "k8s.io/kubernetes/pkg/kubectl/cmd" kubectlcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" - schapp "k8s.io/kubernetes/plugin/cmd/kube-scheduler/app" ) func main() { @@ -110,6 +111,13 @@ func main() { for _, c := range kubectl.Commands() { genMarkdown(c, "kubectl", outDir) } + case "kubeadm": + // generate manpage for kubelet + kubeadm := kubeadmapp.NewKubeadmCommand(os.Stdin, os.Stdout, os.Stderr) + genMarkdown(kubeadm, "", outDir) + for _, c := range kubeadm.Commands() { + genMarkdown(c, "kubeadm", outDir) + } default: fmt.Fprintf(os.Stderr, "Module %s is not supported", module) os.Exit(1) diff --git a/cmd/genswaggertypedocs/BUILD b/cmd/genswaggertypedocs/BUILD index 135dd03e678..8f493a171d6 100644 --- a/cmd/genswaggertypedocs/BUILD +++ b/cmd/genswaggertypedocs/BUILD @@ -8,8 +8,8 @@ load( go_binary( name = "genswaggertypedocs", + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/genswaggertypedocs", - library = ":go_default_library", ) go_library( diff --git a/cmd/genutils/BUILD b/cmd/genutils/BUILD index 343b221f72c..47c67d9cb16 100644 --- a/cmd/genutils/BUILD +++ b/cmd/genutils/BUILD @@ -15,8 +15,8 @@ go_library( go_test( name = "go_default_test", srcs = ["genutils_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/genutils", - library = ":go_default_library", ) filegroup( diff --git a/cmd/genutils/genutils.go b/cmd/genutils/genutils.go index 309d6ec48fb..c94b25db547 100644 --- a/cmd/genutils/genutils.go +++ b/cmd/genutils/genutils.go @@ -22,6 +22,8 @@ import ( "path/filepath" ) +// OutDir creates the absolute path name from path and checks path exists. +// Returns absolute path including trailing '/' or error if path does not exist. func OutDir(path string) (string, error) { outDir, err := filepath.Abs(path) if err != nil { @@ -34,7 +36,7 @@ func OutDir(path string) (string, error) { } if !stat.IsDir() { - return "", fmt.Errorf("output directory %s is not a directory\n", outDir) + return "", fmt.Errorf("output directory %s is not a directory", outDir) } outDir = outDir + "/" return outDir, nil diff --git a/cmd/genyaml/BUILD b/cmd/genyaml/BUILD index 46855dfe317..527d060e3a2 100644 --- a/cmd/genyaml/BUILD +++ b/cmd/genyaml/BUILD @@ -8,8 +8,8 @@ load( go_binary( name = "genyaml", + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/genyaml", - library = ":go_default_library", ) go_library( diff --git a/cmd/gke-certificates-controller/BUILD b/cmd/gke-certificates-controller/BUILD index e77d8b0cb2a..2799b64369e 100644 --- a/cmd/gke-certificates-controller/BUILD +++ b/cmd/gke-certificates-controller/BUILD @@ -37,6 +37,6 @@ filegroup( go_binary( name = "gke-certificates-controller", + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/gke-certificates-controller", - library = ":go_default_library", ) diff --git a/cmd/gke-certificates-controller/app/BUILD b/cmd/gke-certificates-controller/app/BUILD index 402f22024ed..f7f1d24af68 100644 --- a/cmd/gke-certificates-controller/app/BUILD +++ b/cmd/gke-certificates-controller/app/BUILD @@ -15,7 +15,7 @@ go_library( ], importpath = "k8s.io/kubernetes/cmd/gke-certificates-controller/app", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/certificates/install:go_default_library", "//pkg/controller:go_default_library", "//pkg/controller/certificates:go_default_library", @@ -53,8 +53,8 @@ filegroup( go_test( name = "go_default_test", srcs = ["gke_signer_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/gke-certificates-controller/app", - library = ":go_default_library", deps = [ "//vendor/k8s.io/api/certificates/v1beta1:go_default_library", "//vendor/k8s.io/client-go/tools/record:go_default_library", diff --git a/cmd/gke-certificates-controller/app/gke_certificates_controller.go b/cmd/gke-certificates-controller/app/gke_certificates_controller.go index 7043c75cfd4..9e9db774c91 100644 --- a/cmd/gke-certificates-controller/app/gke_certificates_controller.go +++ b/cmd/gke-certificates-controller/app/gke_certificates_controller.go @@ -28,7 +28,7 @@ import ( restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/record" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller/certificates" @@ -65,7 +65,7 @@ func Run(s *GKECertificatesController) error { eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(glog.Infof) eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.CoreV1().RESTClient()).Events("")}) - recorder := eventBroadcaster.NewRecorder(api.Scheme, v1.EventSource{Component: "gke-certificates-controller"}) + recorder := eventBroadcaster.NewRecorder(legacyscheme.Scheme, v1.EventSource{Component: "gke-certificates-controller"}) clientBuilder := controller.SimpleControllerClientBuilder{ClientConfig: kubeconfig} client := clientBuilder.ClientOrDie("certificate-controller") @@ -77,14 +77,11 @@ func Run(s *GKECertificatesController) error { return err } - controller, err := certificates.NewCertificateController( + controller := certificates.NewCertificateController( client, sharedInformers.Certificates().V1beta1().CertificateSigningRequests(), signer.handle, ) - if err != nil { - return err - } sharedInformers.Start(nil) controller.Run(5, nil) // runs forever diff --git a/cmd/gke-certificates-controller/app/gke_signer.go b/cmd/gke-certificates-controller/app/gke_signer.go index 01beada20ac..69552a417d3 100644 --- a/cmd/gke-certificates-controller/app/gke_signer.go +++ b/cmd/gke-certificates-controller/app/gke_signer.go @@ -29,7 +29,7 @@ import ( clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/record" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" _ "k8s.io/kubernetes/pkg/apis/certificates/install" "k8s.io/kubernetes/pkg/controller/certificates" ) @@ -50,7 +50,7 @@ type GKESigner struct { // NewGKESigner will create a new instance of a GKESigner. func NewGKESigner(kubeConfigFile string, retryBackoff time.Duration, recorder record.EventRecorder, client clientset.Interface) (*GKESigner, error) { - webhook, err := webhook.NewGenericWebhook(api.Registry, api.Codecs, kubeConfigFile, groupVersions, retryBackoff) + webhook, err := webhook.NewGenericWebhook(legacyscheme.Registry, legacyscheme.Codecs, kubeConfigFile, groupVersions, retryBackoff) if err != nil { return nil, err } @@ -72,7 +72,7 @@ func (s *GKESigner) handle(csr *capi.CertificateSigningRequest) error { if err != nil { return fmt.Errorf("error auto signing csr: %v", err) } - _, err = s.client.Certificates().CertificateSigningRequests().UpdateStatus(csr) + _, err = s.client.CertificatesV1beta1().CertificateSigningRequests().UpdateStatus(csr) if err != nil { return fmt.Errorf("error updating signature for csr: %v", err) } diff --git a/cmd/hyperkube/BUILD b/cmd/hyperkube/BUILD index 4cc602ad88d..09ce16626f5 100644 --- a/cmd/hyperkube/BUILD +++ b/cmd/hyperkube/BUILD @@ -10,16 +10,16 @@ load("//pkg/version:def.bzl", "version_x_defs") go_binary( name = "hyperkube", + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/hyperkube", - library = ":go_default_library", x_defs = version_x_defs(), ) go_test( name = "go_default_test", srcs = ["hyperkube_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/hyperkube", - library = ":go_default_library", deps = [ "//vendor/github.com/spf13/cobra:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", @@ -30,8 +30,7 @@ go_test( go_library( name = "go_default_library", srcs = [ - "federation-apiserver.go", - "federation-controller-manager.go", + "cloud-controller-manager.go", "hyperkube.go", "kube-aggregator.go", "kube-apiserver.go", @@ -45,25 +44,22 @@ go_library( ], importpath = "k8s.io/kubernetes/cmd/hyperkube", deps = [ + "//cmd/cloud-controller-manager/app:go_default_library", + "//cmd/cloud-controller-manager/app/options:go_default_library", "//cmd/kube-apiserver/app:go_default_library", "//cmd/kube-apiserver/app/options:go_default_library", "//cmd/kube-controller-manager/app:go_default_library", "//cmd/kube-controller-manager/app/options:go_default_library", "//cmd/kube-proxy/app:go_default_library", + "//cmd/kube-scheduler/app:go_default_library", "//cmd/kubelet/app:go_default_library", "//cmd/kubelet/app/options:go_default_library", - "//federation/cmd/federation-apiserver/app:go_default_library", - "//federation/cmd/federation-apiserver/app/options:go_default_library", - "//federation/cmd/federation-controller-manager/app:go_default_library", - "//federation/cmd/federation-controller-manager/app/options:go_default_library", "//pkg/client/metrics/prometheus:go_default_library", "//pkg/kubectl/cmd:go_default_library", "//pkg/kubectl/cmd/util:go_default_library", "//pkg/util/template:go_default_library", "//pkg/version/prometheus:go_default_library", "//pkg/version/verflag:go_default_library", - "//plugin/cmd/kube-scheduler/app:go_default_library", - "//plugin/cmd/kube-scheduler/app/options:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/apiserver/pkg/server:go_default_library", diff --git a/cmd/hyperkube/cloud-controller-manager.go b/cmd/hyperkube/cloud-controller-manager.go new file mode 100644 index 00000000000..fd3266beeaa --- /dev/null +++ b/cmd/hyperkube/cloud-controller-manager.go @@ -0,0 +1,39 @@ +/* +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 main + +import ( + "k8s.io/kubernetes/cmd/cloud-controller-manager/app" + "k8s.io/kubernetes/cmd/cloud-controller-manager/app/options" +) + +// NewCloudControllerManager creates a new hyperkube Server object that includes the +// description and flags. +func NewCloudControllerManager() *Server { + s := options.NewCloudControllerManagerServer() + + hks := Server{ + name: "cloud-controller-manager", + SimpleUsage: "cloud-controller-manager", + Long: "A server that acts as an external cloud provider.", + Run: func(_ *Server, args []string, stopCh <-chan struct{}) error { + return app.Run(s) + }, + } + s.AddFlags(hks.Flags()) + return &hks +} diff --git a/cmd/hyperkube/federation-apiserver.go b/cmd/hyperkube/federation-apiserver.go deleted file mode 100644 index 97dff041396..00000000000 --- a/cmd/hyperkube/federation-apiserver.go +++ /dev/null @@ -1,39 +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 main - -import ( - "k8s.io/kubernetes/federation/cmd/federation-apiserver/app" - "k8s.io/kubernetes/federation/cmd/federation-apiserver/app/options" -) - -// NewFederationAPIServer creates a new hyperkube Server object that includes the -// description and flags. -func NewFederationAPIServer() *Server { - s := options.NewServerRunOptions() - - hks := Server{ - SimpleUsage: "federation-apiserver", - Long: "The API entrypoint for the federation control plane", - Run: func(_ *Server, args []string, stopCh <-chan struct{}) error { - return app.Run(s, stopCh) - }, - RespectsStopCh: true, - } - s.AddFlags(hks.Flags()) - return &hks -} diff --git a/cmd/hyperkube/federation-controller-manager.go b/cmd/hyperkube/federation-controller-manager.go deleted file mode 100644 index b702a1e34cb..00000000000 --- a/cmd/hyperkube/federation-controller-manager.go +++ /dev/null @@ -1,38 +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 main - -import ( - "k8s.io/kubernetes/federation/cmd/federation-controller-manager/app" - "k8s.io/kubernetes/federation/cmd/federation-controller-manager/app/options" -) - -// NewFederationCMServer creates a new hyperkube Server object that includes the -// description and flags. -func NewFederationCMServer() *Server { - s := options.NewCMServer() - - hks := Server{ - SimpleUsage: "federation-controller-manager", - Long: "Controller manager for federation control plane. Manages federation service endpoints and controllers", - Run: func(_ *Server, args []string, stopCh <-chan struct{}) error { - return app.Run(s) - }, - } - s.AddFlags(hks.Flags()) - return &hks -} diff --git a/cmd/hyperkube/hyperkube.go b/cmd/hyperkube/hyperkube.go index 556711d5d8a..a4de5c7af24 100644 --- a/cmd/hyperkube/hyperkube.go +++ b/cmd/hyperkube/hyperkube.go @@ -42,6 +42,7 @@ type HyperKube struct { Long string // A long description of the binary. It will be world wrapped before output. servers []Server + alphaServers []Server baseFlags *pflag.FlagSet out io.Writer helpFlagVal bool @@ -54,9 +55,24 @@ func (hk *HyperKube) AddServer(s *Server) { hk.servers[len(hk.servers)-1].hk = hk } +// AddServer adds an alpha server to the HyperKube object. +func (hk *HyperKube) AddAlphaServer(s *Server) { + hk.alphaServers = append(hk.alphaServers, *s) + hk.alphaServers[len(hk.alphaServers)-1].hk = hk +} + // FindServer will find a specific server named name. func (hk *HyperKube) FindServer(name string) (*Server, error) { - for _, s := range hk.servers { + return findServer(name, hk.servers) +} + +// FindServer will find a specific alpha server named name. +func (hk *HyperKube) FindAlphaServer(name string) (*Server, error) { + return findServer(name, hk.alphaServers) +} + +func findServer(name string, servers []Server) (*Server, error) { + for _, s := range servers { if s.Name() == name || s.AlternativeName == name { return &s, nil } @@ -154,7 +170,23 @@ func (hk *HyperKube) Run(args []string, stopCh <-chan struct{}) error { } } - s, err := hk.FindServer(serverName) + var s *Server + var err error + if serverName == "alpha" { + if len(args) > 0 && len(args[0]) > 0 { + serverName = args[0] + args = args[1:] + hk.Printf("Warning: alpha command syntax is unstable!\n\n") + } else { + err = errors.New("no alpha server specified") + hk.Printf("Error: %v\n\n", err) + hk.Usage() + return err + } + s, err = hk.FindAlphaServer(serverName) + } else { + s, err = hk.FindServer(serverName) + } if err != nil { hk.Printf("Error: %v\n\n", err) hk.Usage() diff --git a/cmd/hyperkube/hyperkube_test.go b/cmd/hyperkube/hyperkube_test.go index 5a4459b2a96..56761812010 100644 --- a/cmd/hyperkube/hyperkube_test.go +++ b/cmd/hyperkube/hyperkube_test.go @@ -151,6 +151,7 @@ func runFull(t *testing.T, args string, stopCh <-chan struct{}) *result { hk.AddServer(testServer("test1")) hk.AddServer(testServer("test2")) hk.AddServer(testServer("test3")) + hk.AddAlphaServer(testServer("testAlpha1")) hk.AddServer(testServerError("test-error")) hk.AddServer(testStopChIgnoringServer("test-stop-ch-ignoring")) hk.AddServer(testStopChRespectingServer("test-stop-ch-respecting")) @@ -236,6 +237,22 @@ func TestServerError(t *testing.T) { assert.EqualError(t, x.err, "server returning error") } +func TestAlphaRun(t *testing.T) { + x := runFull(t, "hyperkube alpha testAlpha1", wait.NeverStop) + assert.NoError(t, x.err) +} + +func TestAlphaNoArgs(t *testing.T) { + x := runFull(t, "hyperkube alpha", wait.NeverStop) + assert.EqualError(t, x.err, "no alpha server specified") +} + +func TestAlphaBadServer(t *testing.T) { + x := runFull(t, "hyperkube alpha bad-server", wait.NeverStop) + assert.EqualError(t, x.err, "Server not found: bad-server") + assert.Contains(t, x.output, "Usage") +} + func TestStopChIgnoringServer(t *testing.T) { stopCh := make(chan struct{}) returnedCh := make(chan struct{}) diff --git a/cmd/hyperkube/kube-scheduler.go b/cmd/hyperkube/kube-scheduler.go index 5e5abb2c630..ba48aebb2b7 100644 --- a/cmd/hyperkube/kube-scheduler.go +++ b/cmd/hyperkube/kube-scheduler.go @@ -17,24 +17,38 @@ limitations under the License. package main import ( - "k8s.io/kubernetes/plugin/cmd/kube-scheduler/app" - "k8s.io/kubernetes/plugin/cmd/kube-scheduler/app/options" + "flag" + + "k8s.io/apiserver/pkg/server/healthz" + "k8s.io/kubernetes/cmd/kube-scheduler/app" ) // NewScheduler creates a new hyperkube Server object that includes the // description and flags. func NewScheduler() *Server { - s := options.NewSchedulerServer() + healthz.DefaultHealthz() + + command := app.NewSchedulerCommand() hks := Server{ name: "scheduler", AlternativeName: "kube-scheduler", SimpleUsage: "scheduler", - Long: "Implements a Kubernetes scheduler. This will assign pods to kubelets based on capacity and constraints.", - Run: func(_ *Server, _ []string, stopCh <-chan struct{}) error { - return app.Run(s) - }, + Long: command.Long, } - s.AddFlags(hks.Flags()) + + serverFlags := hks.Flags() + serverFlags.AddFlagSet(command.Flags()) + + // FIXME this is here because hyperkube does its own flag parsing, and we need + // the command to know about the go flag set. Remove this once hyperkube is + // refactored to use cobra throughout. + command.Flags().AddGoFlagSet(flag.CommandLine) + + hks.Run = func(_ *Server, args []string, stopCh <-chan struct{}) error { + command.SetArgs(args) + return command.Execute() + } + return &hks } diff --git a/cmd/hyperkube/kubelet.go b/cmd/hyperkube/kubelet.go index 36c3286a12e..c58363a6c9b 100644 --- a/cmd/hyperkube/kubelet.go +++ b/cmd/hyperkube/kubelet.go @@ -39,6 +39,9 @@ func NewKubelet() (*Server, error) { configuration data, with the running set of containers by starting or stopping Docker containers.`, Run: func(_ *Server, _ []string, stopCh <-chan struct{}) error { + if s.ExperimentalDockershim { + return app.RunDockershim(&s.KubeletFlags, &s.KubeletConfiguration) + } return app.Run(s, nil) }, } diff --git a/cmd/hyperkube/main.go b/cmd/hyperkube/main.go index ef49d067508..123f040a87b 100644 --- a/cmd/hyperkube/main.go +++ b/cmd/hyperkube/main.go @@ -46,9 +46,8 @@ func main() { hk.AddServer(NewKubeProxy()) hk.AddServer(NewKubeAggregator()) - //Federation servers - hk.AddServer(NewFederationAPIServer()) - hk.AddServer(NewFederationCMServer()) + // Alpha servers + hk.AddAlphaServer(NewCloudControllerManager()) hk.RunToExit(os.Args) } diff --git a/cmd/importverifier/BUILD b/cmd/importverifier/BUILD index f0bd155478b..e5e1fe26d09 100644 --- a/cmd/importverifier/BUILD +++ b/cmd/importverifier/BUILD @@ -8,14 +8,15 @@ load( go_binary( name = "importverifier", + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/importverifier", - library = ":go_default_library", ) go_library( name = "go_default_library", srcs = ["importverifier.go"], importpath = "k8s.io/kubernetes/cmd/importverifier", + deps = ["//vendor/gopkg.in/yaml.v2:go_default_library"], ) filegroup( diff --git a/cmd/importverifier/importverifier.go b/cmd/importverifier/importverifier.go index d6b9efca4bf..584a74bdb33 100644 --- a/cmd/importverifier/importverifier.go +++ b/cmd/importverifier/importverifier.go @@ -27,15 +27,17 @@ import ( "os/exec" "path/filepath" "strings" + + "gopkg.in/yaml.v2" ) // Package is a subset of cmd/go.Package type Package struct { - Dir string `json:",omitempty"` // directory containing package sources - ImportPath string `json:",omitempty"` // import path of package in dir - Imports []string `json:",omitempty"` // import paths used by this package - TestImports []string `json:",omitempty"` // imports from TestGoFiles - XTestImports []string `json:",omitempty"` // imports from XTestGoFiles + Dir string `yaml:",omitempty"` // directory containing package sources + ImportPath string `yaml:",omitempty"` // import path of package in dir + Imports []string `yaml:",omitempty"` // import paths used by this package + TestImports []string `yaml:",omitempty"` // imports from TestGoFiles + XTestImports []string `yaml:",omitempty"` // imports from XTestGoFiles } // ImportRestriction describes a set of allowable import @@ -44,17 +46,17 @@ type ImportRestriction struct { // BaseDir is the root of the package tree that is // restricted by this configuration, given as a // relative path from the root of the repository - BaseDir string `json:"baseImportPath"` + BaseDir string `yaml:"baseImportPath"` // IgnoredSubTrees are roots of sub-trees of the // BaseDir for which we do not want to enforce // any import restrictions whatsoever, given as // relative paths from the root of the repository - IgnoredSubTrees []string `json:"ignoredSubTrees,omitempty"` + IgnoredSubTrees []string `yaml:"ignoredSubTrees,omitempty"` // AllowedImports are roots of package trees that // are allowed to be imported from the BaseDir, // given as paths that would be used in a Go // import statement - AllowedImports []string `json:"allowedImports"` + AllowedImports []string `yaml:"allowedImports"` } // ForbiddenImportsFor determines all of the forbidden @@ -110,7 +112,7 @@ func isPathUnder(base, path string) (bool, error) { // if path is below base, the relative path // from base to path will not start with `../` - return !strings.HasPrefix(relPath, "."), nil + return !strings.HasPrefix(relPath, ".."), nil } // forbiddenImportsFor determines all of the forbidden @@ -164,7 +166,7 @@ var rootPackage string func main() { if len(os.Args) != 3 { - log.Fatalf("Usage: %s ROOT RESTRICTIONS.json", os.Args[0]) + log.Fatalf("Usage: %s ROOT RESTRICTIONS.yaml", os.Args[0]) } rootPackage = os.Args[1] @@ -214,7 +216,7 @@ func loadImportRestrictions(configFile string) ([]ImportRestriction, error) { } var importRestrictions []ImportRestriction - if err := json.Unmarshal(config, &importRestrictions); err != nil { + if err := yaml.Unmarshal(config, &importRestrictions); err != nil { return nil, fmt.Errorf("failed to unmarshal from %s: %v", configFile, err) } diff --git a/cmd/kube-apiserver/BUILD b/cmd/kube-apiserver/BUILD index 81e46d79d24..29a454f06e0 100644 --- a/cmd/kube-apiserver/BUILD +++ b/cmd/kube-apiserver/BUILD @@ -9,14 +9,9 @@ load("//pkg/version:def.bzl", "version_x_defs") go_binary( name = "kube-apiserver", - gc_linkopts = [ - "-linkmode", - "external", - "-extldflags", - "-static", - ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kube-apiserver", - library = ":go_default_library", + pure = "on", x_defs = version_x_defs(), ) diff --git a/cmd/kube-apiserver/app/BUILD b/cmd/kube-apiserver/app/BUILD index 1e2865f178d..6f2497a161a 100644 --- a/cmd/kube-apiserver/app/BUILD +++ b/cmd/kube-apiserver/app/BUILD @@ -15,11 +15,15 @@ go_library( importpath = "k8s.io/kubernetes/cmd/kube-apiserver/app", deps = [ "//cmd/kube-apiserver/app/options:go_default_library", - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/admissionregistration:go_default_library", "//pkg/apis/apps:go_default_library", "//pkg/apis/batch:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/events:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/apis/networking:go_default_library", + "//pkg/apis/storage:go_default_library", "//pkg/capabilities:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", @@ -58,6 +62,8 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/initializer:go_default_library", "//vendor/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library", "//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", "//vendor/k8s.io/apiserver/pkg/server:go_default_library", @@ -69,6 +75,7 @@ go_library( "//vendor/k8s.io/apiserver/pkg/storage/etcd3/preflight:go_default_library", "//vendor/k8s.io/client-go/informers:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", "//vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration:go_default_library", "//vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1:go_default_library", diff --git a/cmd/kube-apiserver/app/aggregator.go b/cmd/kube-apiserver/app/aggregator.go index c6412c52f97..6d6e469434b 100644 --- a/cmd/kube-apiserver/app/aggregator.go +++ b/cmd/kube-apiserver/app/aggregator.go @@ -203,8 +203,9 @@ var apiVersionPriorities = map[schema.GroupVersion]priority{ {Group: "extensions", Version: "v1beta1"}: {group: 17900, version: 1}, // to my knowledge, nothing below here collides {Group: "apps", Version: "v1beta1"}: {group: 17800, version: 1}, - {Group: "apps", Version: "v1beta2"}: {group: 17800, version: 1}, + {Group: "apps", Version: "v1beta2"}: {group: 17800, version: 9}, {Group: "apps", Version: "v1"}: {group: 17800, version: 15}, + {Group: "events.k8s.io", Version: "v1beta1"}: {group: 17750, version: 5}, {Group: "authentication.k8s.io", Version: "v1"}: {group: 17700, version: 15}, {Group: "authentication.k8s.io", Version: "v1beta1"}: {group: 17700, version: 9}, {Group: "authorization.k8s.io", Version: "v1"}: {group: 17600, version: 15}, @@ -223,8 +224,11 @@ var apiVersionPriorities = map[schema.GroupVersion]priority{ {Group: "settings.k8s.io", Version: "v1alpha1"}: {group: 16900, version: 9}, {Group: "storage.k8s.io", Version: "v1"}: {group: 16800, version: 15}, {Group: "storage.k8s.io", Version: "v1beta1"}: {group: 16800, version: 9}, + {Group: "storage.k8s.io", Version: "v1alpha1"}: {group: 16800, version: 1}, {Group: "apiextensions.k8s.io", Version: "v1beta1"}: {group: 16700, version: 9}, + {Group: "admissionregistration.k8s.io", Version: "v1beta1"}: {group: 16700, version: 12}, {Group: "admissionregistration.k8s.io", Version: "v1alpha1"}: {group: 16700, version: 9}, + {Group: "scheduling.k8s.io", Version: "v1alpha1"}: {group: 16600, version: 9}, } func apiServicesToRegister(delegateAPIServer genericapiserver.DelegationTarget, registration autoregister.AutoAPIServiceRegistration) []*apiregistration.APIService { diff --git a/cmd/kube-apiserver/app/options/BUILD b/cmd/kube-apiserver/app/options/BUILD index a2676d66223..ef12f9230ab 100644 --- a/cmd/kube-apiserver/app/options/BUILD +++ b/cmd/kube-apiserver/app/options/BUILD @@ -15,8 +15,8 @@ go_library( ], importpath = "k8s.io/kubernetes/cmd/kube-apiserver/app/options", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//pkg/cloudprovider/providers:go_default_library", "//pkg/features:go_default_library", "//pkg/kubeapiserver/options:go_default_library", @@ -30,6 +30,7 @@ go_library( "//plugin/pkg/admission/deny:go_default_library", "//plugin/pkg/admission/eventratelimit:go_default_library", "//plugin/pkg/admission/exec:go_default_library", + "//plugin/pkg/admission/extendedresourcetoleration:go_default_library", "//plugin/pkg/admission/gc:go_default_library", "//plugin/pkg/admission/imagepolicy:go_default_library", "//plugin/pkg/admission/initialresources:go_default_library", @@ -39,6 +40,7 @@ go_library( "//plugin/pkg/admission/noderestriction:go_default_library", "//plugin/pkg/admission/persistentvolume/label:go_default_library", "//plugin/pkg/admission/persistentvolume/resize:go_default_library", + "//plugin/pkg/admission/persistentvolumeclaim/pvcprotection:go_default_library", "//plugin/pkg/admission/podnodeselector:go_default_library", "//plugin/pkg/admission/podpreset:go_default_library", "//plugin/pkg/admission/podtolerationrestriction:go_default_library", @@ -48,7 +50,6 @@ go_library( "//plugin/pkg/admission/securitycontext/scdeny:go_default_library", "//plugin/pkg/admission/serviceaccount:go_default_library", "//plugin/pkg/admission/storageclass/setdefault:go_default_library", - "//plugin/pkg/admission/webhook:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", @@ -60,10 +61,11 @@ go_library( go_test( name = "go_default_test", srcs = ["options_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kube-apiserver/app/options", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/kubeapiserver/options:go_default_library", "//pkg/kubelet/client:go_default_library", "//pkg/master/reconcilers:go_default_library", @@ -72,6 +74,7 @@ go_test( "//vendor/k8s.io/apiserver/pkg/server/options:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library", + "//vendor/k8s.io/apiserver/plugin/pkg/audit/webhook:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", ], ) diff --git a/cmd/kube-apiserver/app/options/options.go b/cmd/kube-apiserver/app/options/options.go index 727edd15edf..8b95fb66678 100644 --- a/cmd/kube-apiserver/app/options/options.go +++ b/cmd/kube-apiserver/app/options/options.go @@ -25,8 +25,8 @@ import ( utilnet "k8s.io/apimachinery/pkg/util/net" genericoptions "k8s.io/apiserver/pkg/server/options" "k8s.io/apiserver/pkg/storage/storagebackend" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/validation" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/validation" kubeoptions "k8s.io/kubernetes/pkg/kubeapiserver/options" kubeletclient "k8s.io/kubernetes/pkg/kubelet/client" "k8s.io/kubernetes/pkg/master/ports" @@ -153,11 +153,15 @@ func (s *ServerRunOptions) AddFlags(fs *pflag.FlagSet) { fs.BoolVar(&s.EnableLogsHandler, "enable-logs-handler", s.EnableLogsHandler, "If true, install a /logs handler for the apiserver logs.") + // Deprecated in release 1.9 fs.StringVar(&s.SSHUser, "ssh-user", s.SSHUser, "If non-empty, use secure SSH proxy to the nodes, using this user name") + fs.MarkDeprecated("ssh-user", "This flag will be removed in a future version.") + // Deprecated in release 1.9 fs.StringVar(&s.SSHKeyfile, "ssh-keyfile", s.SSHKeyfile, "If non-empty, use secure SSH proxy to the nodes, using this user keyfile") + fs.MarkDeprecated("ssh-keyfile", "This flag will be removed in a future version.") fs.Int64Var(&s.MaxConnectionBytesPerSec, "max-connection-bytes-per-sec", s.MaxConnectionBytesPerSec, ""+ "If non-zero, throttle each user connection to this number of bytes/sec. "+ @@ -166,7 +170,7 @@ func (s *ServerRunOptions) AddFlags(fs *pflag.FlagSet) { fs.IntVar(&s.MasterCount, "apiserver-count", s.MasterCount, "The number of apiservers running in the cluster, must be a positive number.") - fs.StringVar(&s.EndpointReconcilerType, "alpha-endpoint-reconciler-type", string(s.EndpointReconcilerType), + fs.StringVar(&s.EndpointReconcilerType, "endpoint-reconciler-type", string(s.EndpointReconcilerType), "Use an endpoint reconciler ("+strings.Join(reconcilers.AllTypes.Names(), ", ")+")") // See #14282 for details on how to test/try this option out. @@ -180,15 +184,9 @@ func (s *ServerRunOptions) AddFlags(fs *pflag.FlagSet) { "A CIDR notation IP range from which to assign service cluster IPs. This must not "+ "overlap with any IP ranges assigned to nodes for pods.") - fs.IPNetVar(&s.ServiceClusterIPRange, "portal-net", s.ServiceClusterIPRange, - "DEPRECATED: see --service-cluster-ip-range instead.") - fs.MarkDeprecated("portal-net", "see --service-cluster-ip-range instead") - fs.Var(&s.ServiceNodePortRange, "service-node-port-range", ""+ "A port range to reserve for services with NodePort visibility. "+ "Example: '30000-32767'. Inclusive at both ends of the range.") - fs.Var(&s.ServiceNodePortRange, "service-node-ports", "DEPRECATED: see --service-node-port-range instead") - fs.MarkDeprecated("service-node-ports", "see --service-node-port-range instead") // Kubelet related flags: fs.BoolVar(&s.KubeletConfig.EnableHttps, "kubelet-https", s.KubeletConfig.EnableHttps, diff --git a/cmd/kube-apiserver/app/options/options_test.go b/cmd/kube-apiserver/app/options/options_test.go index 59320114c2d..30979d10524 100644 --- a/cmd/kube-apiserver/app/options/options_test.go +++ b/cmd/kube-apiserver/app/options/options_test.go @@ -28,8 +28,10 @@ import ( apiserveroptions "k8s.io/apiserver/pkg/server/options" "k8s.io/apiserver/pkg/storage/storagebackend" utilconfig "k8s.io/apiserver/pkg/util/flag" + auditwebhook "k8s.io/apiserver/plugin/pkg/audit/webhook" restclient "k8s.io/client-go/rest" - kapi "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + kapi "k8s.io/kubernetes/pkg/apis/core" kubeoptions "k8s.io/kubernetes/pkg/kubeapiserver/options" kubeletclient "k8s.io/kubernetes/pkg/kubelet/client" "k8s.io/kubernetes/pkg/master/reconcilers" @@ -45,7 +47,6 @@ func TestAddFlags(t *testing.T) { "--admission-control-config-file=/admission-control-config", "--advertise-address=192.168.10.10", "--allow-privileged=false", - "--alpha-endpoint-reconciler-type=" + string(reconcilers.MasterCountReconcilerType), "--anonymous-auth=false", "--apiserver-count=5", "--audit-log-maxage=11", @@ -55,6 +56,12 @@ func TestAddFlags(t *testing.T) { "--audit-policy-file=/policy", "--audit-webhook-config-file=/webhook-config", "--audit-webhook-mode=blocking", + "--audit-webhook-batch-buffer-size=42", + "--audit-webhook-batch-max-size=43", + "--audit-webhook-batch-max-wait=1s", + "--audit-webhook-batch-throttle-qps=43.5", + "--audit-webhook-batch-throttle-burst=44", + "--audit-webhook-batch-initial-backoff=2s", "--authentication-token-webhook-cache-ttl=3m", "--authentication-token-webhook-config-file=/token-webhook-config", "--authorization-mode=AlwaysDeny", @@ -71,6 +78,7 @@ func TestAddFlags(t *testing.T) { "--enable-aggregator-routing=true", "--enable-logs-handler=false", "--enable-swagger-ui=true", + "--endpoint-reconciler-type=" + string(reconcilers.MasterCountReconcilerType), "--etcd-quorum-read=false", "--etcd-keyfile=/var/run/kubernetes/etcd.key", "--etcd-certfile=/var/run/kubernetes/etcdce.crt", @@ -103,8 +111,8 @@ func TestAddFlags(t *testing.T) { MinRequestTimeout: 1800, }, Admission: &apiserveroptions.AdmissionOptions{ - RecommendedPluginOrder: []string{"NamespaceLifecycle", "Initializers"}, - DefaultOffPlugins: []string{"Initializers"}, + RecommendedPluginOrder: []string{"NamespaceLifecycle", "Initializers", "MutatingAdmissionWebhook", "ValidatingAdmissionWebhook"}, + DefaultOffPlugins: []string{"Initializers", "MutatingAdmissionWebhook", "ValidatingAdmissionWebhook"}, PluginNames: []string{"AlwaysDeny"}, ConfigFile: "/admission-control-config", Plugins: s.Admission.Plugins, @@ -169,6 +177,14 @@ func TestAddFlags(t *testing.T) { WebhookOptions: apiserveroptions.AuditWebhookOptions{ Mode: "blocking", ConfigFile: "/webhook-config", + BatchConfig: auditwebhook.BatchBackendConfig{ + BufferSize: 42, + MaxBatchSize: 43, + MaxBatchWait: 1 * time.Second, + ThrottleQPS: 43.5, + ThrottleBurst: 44, + InitialBackoff: 2 * time.Second, + }, }, PolicyFile: "/policy", }, @@ -214,8 +230,8 @@ func TestAddFlags(t *testing.T) { CloudProvider: "azure", }, StorageSerialization: &kubeoptions.StorageSerializationOptions{ - StorageVersions: kapi.Registry.AllPreferredGroupVersions(), - DefaultStorageVersions: kapi.Registry.AllPreferredGroupVersions(), + StorageVersions: legacyscheme.Registry.AllPreferredGroupVersions(), + DefaultStorageVersions: legacyscheme.Registry.AllPreferredGroupVersions(), }, APIEnablement: &kubeoptions.APIEnablementOptions{ RuntimeConfig: utilconfig.ConfigurationMap{}, diff --git a/cmd/kube-apiserver/app/options/plugins.go b/cmd/kube-apiserver/app/options/plugins.go index 8656eb157ce..a0d2502e7f5 100644 --- a/cmd/kube-apiserver/app/options/plugins.go +++ b/cmd/kube-apiserver/app/options/plugins.go @@ -32,6 +32,7 @@ import ( "k8s.io/kubernetes/plugin/pkg/admission/deny" "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit" "k8s.io/kubernetes/plugin/pkg/admission/exec" + "k8s.io/kubernetes/plugin/pkg/admission/extendedresourcetoleration" "k8s.io/kubernetes/plugin/pkg/admission/gc" "k8s.io/kubernetes/plugin/pkg/admission/imagepolicy" "k8s.io/kubernetes/plugin/pkg/admission/initialresources" @@ -41,6 +42,7 @@ import ( "k8s.io/kubernetes/plugin/pkg/admission/noderestriction" "k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label" "k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/resize" + "k8s.io/kubernetes/plugin/pkg/admission/persistentvolumeclaim/pvcprotection" "k8s.io/kubernetes/plugin/pkg/admission/podnodeselector" "k8s.io/kubernetes/plugin/pkg/admission/podpreset" "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction" @@ -50,7 +52,6 @@ import ( "k8s.io/kubernetes/plugin/pkg/admission/securitycontext/scdeny" "k8s.io/kubernetes/plugin/pkg/admission/serviceaccount" "k8s.io/kubernetes/plugin/pkg/admission/storageclass/setdefault" - "k8s.io/kubernetes/plugin/pkg/admission/webhook" ) // RegisterAllAdmissionPlugins registers all admission plugins @@ -62,6 +63,7 @@ func RegisterAllAdmissionPlugins(plugins *admission.Plugins) { deny.Register(plugins) eventratelimit.Register(plugins) exec.Register(plugins) + extendedresourcetoleration.Register(plugins) gc.Register(plugins) imagepolicy.Register(plugins) initialresources.Register(plugins) @@ -79,6 +81,6 @@ func RegisterAllAdmissionPlugins(plugins *admission.Plugins) { scdeny.Register(plugins) serviceaccount.Register(plugins) setdefault.Register(plugins) - webhook.Register(plugins) resize.Register(plugins) + pvcprotection.Register(plugins) } diff --git a/cmd/kube-apiserver/app/options/validation.go b/cmd/kube-apiserver/app/options/validation.go index fb937bdf7db..481552d3b29 100644 --- a/cmd/kube-apiserver/app/options/validation.go +++ b/cmd/kube-apiserver/app/options/validation.go @@ -66,6 +66,9 @@ func (options *ServerRunOptions) Validate() []error { if errs := options.Audit.Validate(); len(errs) > 0 { errors = append(errors, errs...) } + if errs := options.Admission.Validate(); len(errs) > 0 { + errors = append(errors, errs...) + } if errs := options.InsecureServing.Validate("insecure-port"); len(errs) > 0 { errors = append(errors, errs...) } diff --git a/cmd/kube-apiserver/app/server.go b/cmd/kube-apiserver/app/server.go index 386e7e7eccf..51f4bfd2162 100644 --- a/cmd/kube-apiserver/app/server.go +++ b/cmd/kube-apiserver/app/server.go @@ -44,6 +44,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" utilwait "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apiserver/pkg/admission" + webhookconfig "k8s.io/apiserver/pkg/admission/plugin/webhook/config" "k8s.io/apiserver/pkg/authentication/authenticator" "k8s.io/apiserver/pkg/authorization/authorizer" genericapiserver "k8s.io/apiserver/pkg/server" @@ -54,15 +55,21 @@ import ( aggregatorapiserver "k8s.io/kube-aggregator/pkg/apiserver" openapi "k8s.io/kube-openapi/pkg/common" + webhookinit "k8s.io/apiserver/pkg/admission/plugin/webhook/initializer" "k8s.io/apiserver/pkg/storage/etcd3/preflight" clientgoinformers "k8s.io/client-go/informers" clientgoclientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "k8s.io/kubernetes/cmd/kube-apiserver/app/options" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/apis/admissionregistration" "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/batch" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/events" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/networking" + "k8s.io/kubernetes/pkg/apis/storage" "k8s.io/kubernetes/pkg/capabilities" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" @@ -153,7 +160,7 @@ func CreateServerChain(runOptions *options.ServerRunOptions, stopCh <-chan struc if len(os.Getenv("KUBE_API_VERSIONS")) > 0 { if insecureServingOptions != nil { insecureHandlerChain := kubeserver.BuildInsecureHandlerChain(kubeAPIServer.GenericAPIServer.UnprotectedHandler(), kubeAPIServerConfig.GenericConfig) - if err := kubeserver.NonBlockingRun(insecureServingOptions, insecureHandlerChain, stopCh); err != nil { + if err := kubeserver.NonBlockingRun(insecureServingOptions, insecureHandlerChain, kubeAPIServerConfig.GenericConfig.RequestTimeout, stopCh); err != nil { return nil, err } } @@ -183,7 +190,7 @@ func CreateServerChain(runOptions *options.ServerRunOptions, stopCh <-chan struc if insecureServingOptions != nil { insecureHandlerChain := kubeserver.BuildInsecureHandlerChain(aggregatorServer.GenericAPIServer.UnprotectedHandler(), kubeAPIServerConfig.GenericConfig) - if err := kubeserver.NonBlockingRun(insecureServingOptions, insecureHandlerChain, stopCh); err != nil { + if err := kubeserver.NonBlockingRun(insecureServingOptions, insecureHandlerChain, kubeAPIServerConfig.GenericConfig.RequestTimeout, stopCh); err != nil { return nil, err } } @@ -262,13 +269,6 @@ func CreateKubeAPIServerConfig(s *options.ServerRunOptions, nodeTunneler tunnele return nil, nil, nil, nil, nil, utilerrors.NewAggregate(errs) } - if s.CloudProvider != nil { - // Initialize the cloudprovider once, to give it a chance to register KMS plugins, if any. - _, err := cloudprovider.InitCloudProvider(s.CloudProvider.CloudProvider, s.CloudProvider.CloudConfigFile) - if err != nil { - return nil, nil, nil, nil, nil, err - } - } genericConfig, sharedInformers, versionedInformers, insecureServingOptions, serviceResolver, err := BuildGenericConfig(s, proxyTransport) if err != nil { return nil, nil, nil, nil, nil, err @@ -355,10 +355,11 @@ func CreateKubeAPIServerConfig(s *options.ServerRunOptions, nodeTunneler tunnele // BuildGenericConfig takes the master server options and produces the genericapiserver.Config associated with it func BuildGenericConfig(s *options.ServerRunOptions, proxyTransport *http.Transport) (*genericapiserver.Config, informers.SharedInformerFactory, clientgoinformers.SharedInformerFactory, *kubeserver.InsecureServingInfo, aggregatorapiserver.ServiceResolver, error) { - genericConfig := genericapiserver.NewConfig(api.Codecs) + genericConfig := genericapiserver.NewConfig(legacyscheme.Codecs) if err := s.GenericServerRunOptions.ApplyTo(genericConfig); err != nil { return nil, nil, nil, nil, nil, err } + insecureServingOptions, err := s.InsecureServing.ApplyTo(genericConfig) if err != nil { return nil, nil, nil, nil, nil, err @@ -376,7 +377,7 @@ func BuildGenericConfig(s *options.ServerRunOptions, proxyTransport *http.Transp return nil, nil, nil, nil, nil, err } - genericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(generatedopenapi.GetOpenAPIDefinitions, api.Scheme) + genericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(generatedopenapi.GetOpenAPIDefinitions, legacyscheme.Scheme) genericConfig.OpenAPIConfig.PostProcessSpec = postProcessOpenAPISpecForBackwardCompatibility genericConfig.OpenAPIConfig.Info.Title = "Kubernetes" genericConfig.SwaggerConfig = genericapiserver.DefaultSwaggerConfig() @@ -450,50 +451,47 @@ func BuildGenericConfig(s *options.ServerRunOptions, proxyTransport *http.Transp genericConfig.DisabledPostStartHooks.Insert(rbacrest.PostStartHookName) } - pluginInitializer, err := BuildAdmissionPluginInitializer( + webhookAuthResolver := func(delegate webhookconfig.AuthenticationInfoResolver) webhookconfig.AuthenticationInfoResolver { + return webhookconfig.AuthenticationInfoResolverFunc(func(server string) (*rest.Config, error) { + if server == "kubernetes.default.svc" { + return genericConfig.LoopbackClientConfig, nil + } + ret, err := delegate.ClientConfigFor(server) + if err != nil { + return nil, err + } + if proxyTransport != nil && proxyTransport.Dial != nil { + ret.Dial = proxyTransport.Dial + } + return ret, err + }) + } + pluginInitializers, err := BuildAdmissionPluginInitializers( s, client, sharedInformers, serviceResolver, - proxyTransport, + webhookAuthResolver, ) if err != nil { return nil, nil, nil, nil, nil, fmt.Errorf("failed to create admission plugin initializer: %v", err) } - // TODO: this is the wrong cert/key pair. - // Given the generic case of webhook admission from a generic apiserver, - // this key pair should be signed by the the API server's client CA. - // Read client cert/key for plugins that need to make calls out - certBytes, keyBytes := []byte{}, []byte{} - if len(s.ProxyClientCertFile) > 0 && len(s.ProxyClientKeyFile) > 0 { - var err error - certBytes, err = ioutil.ReadFile(s.ProxyClientCertFile) - if err != nil { - return nil, nil, nil, nil, nil, fmt.Errorf("failed to read proxy client cert file from: %s, err: %v", s.ProxyClientCertFile, err) - } - keyBytes, err = ioutil.ReadFile(s.ProxyClientKeyFile) - if err != nil { - return nil, nil, nil, nil, nil, fmt.Errorf("failed to read proxy client key file from: %s, err: %v", s.ProxyClientKeyFile, err) - } - } - err = s.Admission.ApplyTo( genericConfig, versionedInformers, - certBytes, - keyBytes, kubeClientConfig, - api.Scheme, - pluginInitializer) + legacyscheme.Scheme, + pluginInitializers...) if err != nil { return nil, nil, nil, nil, nil, fmt.Errorf("failed to initialize admission: %v", err) } + return genericConfig, sharedInformers, versionedInformers, insecureServingOptions, serviceResolver, nil } -// BuildAdmissionPluginInitializer constructs the admission plugin initializer -func BuildAdmissionPluginInitializer(s *options.ServerRunOptions, client internalclientset.Interface, sharedInformers informers.SharedInformerFactory, serviceResolver aggregatorapiserver.ServiceResolver, proxyTransport *http.Transport) (admission.PluginInitializer, error) { +// BuildAdmissionPluginInitializers constructs the admission plugin initializer +func BuildAdmissionPluginInitializers(s *options.ServerRunOptions, client internalclientset.Interface, sharedInformers informers.SharedInformerFactory, serviceResolver aggregatorapiserver.ServiceResolver, webhookAuthWrapper webhookconfig.AuthenticationInfoResolverWrapper) ([]admission.PluginInitializer, error) { var cloudConfig []byte if s.CloudProvider.CloudConfigFile != "" { @@ -505,18 +503,14 @@ func BuildAdmissionPluginInitializer(s *options.ServerRunOptions, client interna } // TODO: use a dynamic restmapper. See https://github.com/kubernetes/kubernetes/pull/42615. - restMapper := api.Registry.RESTMapper() + restMapper := legacyscheme.Registry.RESTMapper() - // NOTE: we do not provide informers to the quota registry because admission level decisions - // do not require us to open watches for all items tracked by quota. - quotaRegistry := quotainstall.NewRegistry(nil, nil) + quotaConfiguration := quotainstall.NewQuotaConfigurationForAdmission() - pluginInitializer := kubeapiserveradmission.NewPluginInitializer(client, sharedInformers, cloudConfig, restMapper, quotaRegistry) + kubePluginInitializer := kubeapiserveradmission.NewPluginInitializer(client, sharedInformers, cloudConfig, restMapper, quotaConfiguration) + webhookPluginInitializer := webhookinit.NewPluginInitializer(webhookAuthWrapper, serviceResolver) - pluginInitializer = pluginInitializer.SetServiceResolver(serviceResolver) - pluginInitializer = pluginInitializer.SetProxyTransport(proxyTransport) - - return pluginInitializer, nil + return []admission.PluginInitializer{webhookPluginInitializer, kubePluginInitializer}, nil } // BuildAuthenticator constructs the authenticator @@ -565,20 +559,28 @@ func BuildStorageFactory(s *options.ServerRunOptions) (*serverstorage.DefaultSto return nil, fmt.Errorf("error generating storage version map: %s", err) } storageFactory, err := kubeapiserver.NewStorageFactory( - s.Etcd.StorageConfig, s.Etcd.DefaultStorageMediaType, api.Codecs, - serverstorage.NewDefaultResourceEncodingConfig(api.Registry), storageGroupsToEncodingVersion, + s.Etcd.StorageConfig, s.Etcd.DefaultStorageMediaType, legacyscheme.Codecs, + serverstorage.NewDefaultResourceEncodingConfig(legacyscheme.Registry), storageGroupsToEncodingVersion, + // The list includes resources that need to be stored in a different + // group version than other resources in the groups. // FIXME (soltysh): this GroupVersionResource override should be configurable - []schema.GroupVersionResource{batch.Resource("cronjobs").WithVersion("v1beta1")}, + []schema.GroupVersionResource{ + batch.Resource("cronjobs").WithVersion("v1beta1"), + storage.Resource("volumeattachments").WithVersion("v1alpha1"), + admissionregistration.Resource("initializerconfigurations").WithVersion("v1alpha1"), + }, master.DefaultAPIResourceConfigSource(), s.APIEnablement.RuntimeConfig) if err != nil { return nil, fmt.Errorf("error in initializing storage factory: %s", err) } - // keep Deployments, NetworkPolicies, Daemonsets and ReplicaSets in extensions for backwards compatibility, we'll have to migrate at some point, eventually + storageFactory.AddCohabitatingResources(networking.Resource("networkpolicies"), extensions.Resource("networkpolicies")) + + // keep Deployments, Daemonsets and ReplicaSets in extensions for backwards compatibility, we'll have to migrate at some point, eventually storageFactory.AddCohabitatingResources(extensions.Resource("deployments"), apps.Resource("deployments")) storageFactory.AddCohabitatingResources(extensions.Resource("daemonsets"), apps.Resource("daemonsets")) storageFactory.AddCohabitatingResources(extensions.Resource("replicasets"), apps.Resource("replicasets")) - storageFactory.AddCohabitatingResources(extensions.Resource("networkpolicies"), networking.Resource("networkpolicies")) + storageFactory.AddCohabitatingResources(api.Resource("events"), events.Resource("events")) for _, override := range s.Etcd.EtcdServersOverrides { tokens := strings.Split(override, "#") if len(tokens) != 2 { @@ -620,15 +622,26 @@ func defaultOptions(s *options.ServerRunOptions) error { if err := kubeoptions.DefaultAdvertiseAddress(s.GenericServerRunOptions, s.InsecureServing); err != nil { return err } - _, apiServerServiceIP, err := master.DefaultServiceIPRange(s.ServiceClusterIPRange) + serviceIPRange, apiServerServiceIP, err := master.DefaultServiceIPRange(s.ServiceClusterIPRange) if err != nil { return fmt.Errorf("error determining service IP ranges: %v", err) } + s.ServiceClusterIPRange = serviceIPRange if err := s.SecureServing.MaybeDefaultWithSelfSignedCerts(s.GenericServerRunOptions.AdvertiseAddress.String(), []string{"kubernetes.default.svc", "kubernetes.default", "kubernetes"}, []net.IP{apiServerServiceIP}); err != nil { return fmt.Errorf("error creating self-signed certificates: %v", err) } - if err := s.CloudProvider.DefaultExternalHost(s.GenericServerRunOptions); err != nil { - return fmt.Errorf("error setting the external host value: %v", err) + + if len(s.GenericServerRunOptions.ExternalHost) == 0 { + if len(s.GenericServerRunOptions.AdvertiseAddress) > 0 { + s.GenericServerRunOptions.ExternalHost = s.GenericServerRunOptions.AdvertiseAddress.String() + } else { + if hostname, err := os.Hostname(); err == nil { + s.GenericServerRunOptions.ExternalHost = hostname + } else { + return fmt.Errorf("error finding host name: %v", err) + } + } + glog.Infof("external host was not specified, using %v", s.GenericServerRunOptions.ExternalHost) } s.Authentication.ApplyAuthorization(s.Authorization) @@ -690,333 +703,333 @@ func readCAorNil(file string) ([]byte, error) { // PostProcessSpec adds removed definitions for backward compatibility func postProcessOpenAPISpecForBackwardCompatibility(s *spec.Swagger) (*spec.Swagger, error) { compatibilityMap := map[string]string{ - "io.k8s.kubernetes.pkg.apis.authorization.v1beta1.SelfSubjectAccessReview": "io.k8s.api.authorization.v1beta1.SelfSubjectAccessReview", - "io.k8s.kubernetes.pkg.api.v1.GitRepoVolumeSource": "io.k8s.api.core.v1.GitRepoVolumeSource", - "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.ExternalAdmissionHookConfigurationList": "io.k8s.api.admissionregistration.v1alpha1.ExternalAdmissionHookConfigurationList", - "io.k8s.kubernetes.pkg.api.v1.EndpointPort": "io.k8s.api.core.v1.EndpointPort", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.SupplementalGroupsStrategyOptions": "io.k8s.api.extensions.v1beta1.SupplementalGroupsStrategyOptions", - "io.k8s.kubernetes.pkg.api.v1.PodStatus": "io.k8s.api.core.v1.PodStatus", - "io.k8s.kubernetes.pkg.apis.rbac.v1beta1.RoleBindingList": "io.k8s.api.rbac.v1beta1.RoleBindingList", - "io.k8s.kubernetes.pkg.apis.policy.v1beta1.PodDisruptionBudgetSpec": "io.k8s.api.policy.v1beta1.PodDisruptionBudgetSpec", - "io.k8s.kubernetes.pkg.api.v1.HTTPGetAction": "io.k8s.api.core.v1.HTTPGetAction", - "io.k8s.kubernetes.pkg.apis.authorization.v1.ResourceAttributes": "io.k8s.api.authorization.v1.ResourceAttributes", - "io.k8s.kubernetes.pkg.api.v1.PersistentVolumeList": "io.k8s.api.core.v1.PersistentVolumeList", - "io.k8s.kubernetes.pkg.apis.batch.v2alpha1.CronJobSpec": "io.k8s.api.batch.v2alpha1.CronJobSpec", - "io.k8s.kubernetes.pkg.api.v1.CephFSVolumeSource": "io.k8s.api.core.v1.CephFSVolumeSource", - "io.k8s.kubernetes.pkg.api.v1.Affinity": "io.k8s.api.core.v1.Affinity", - "io.k8s.kubernetes.pkg.apis.rbac.v1beta1.PolicyRule": "io.k8s.api.rbac.v1beta1.PolicyRule", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DaemonSetSpec": "io.k8s.api.extensions.v1beta1.DaemonSetSpec", - "io.k8s.kubernetes.pkg.api.v1.ProjectedVolumeSource": "io.k8s.api.core.v1.ProjectedVolumeSource", - "io.k8s.kubernetes.pkg.api.v1.TCPSocketAction": "io.k8s.api.core.v1.TCPSocketAction", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DaemonSet": "io.k8s.api.extensions.v1beta1.DaemonSet", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IngressList": "io.k8s.api.extensions.v1beta1.IngressList", - "io.k8s.kubernetes.pkg.api.v1.PodSpec": "io.k8s.api.core.v1.PodSpec", - "io.k8s.kubernetes.pkg.apis.authentication.v1.TokenReview": "io.k8s.api.authentication.v1.TokenReview", - "io.k8s.kubernetes.pkg.apis.authorization.v1beta1.SubjectAccessReview": "io.k8s.api.authorization.v1beta1.SubjectAccessReview", - "io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.ClusterRoleBinding": "io.k8s.api.rbac.v1alpha1.ClusterRoleBinding", - "io.k8s.kubernetes.pkg.api.v1.Node": "io.k8s.api.core.v1.Node", - "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.ServiceReference": "io.k8s.api.admissionregistration.v1alpha1.ServiceReference", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DeploymentStatus": "io.k8s.api.extensions.v1beta1.DeploymentStatus", - "io.k8s.kubernetes.pkg.apis.rbac.v1beta1.RoleRef": "io.k8s.api.rbac.v1beta1.RoleRef", - "io.k8s.kubernetes.pkg.apis.apps.v1beta1.Scale": "io.k8s.api.apps.v1beta1.Scale", - "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.InitializerConfiguration": "io.k8s.api.admissionregistration.v1alpha1.InitializerConfiguration", - "io.k8s.kubernetes.pkg.api.v1.PhotonPersistentDiskVolumeSource": "io.k8s.api.core.v1.PhotonPersistentDiskVolumeSource", - "io.k8s.kubernetes.pkg.api.v1.PreferredSchedulingTerm": "io.k8s.api.core.v1.PreferredSchedulingTerm", - "io.k8s.kubernetes.pkg.apis.batch.v1.JobSpec": "io.k8s.api.batch.v1.JobSpec", - "io.k8s.kubernetes.pkg.api.v1.EventSource": "io.k8s.api.core.v1.EventSource", - "io.k8s.kubernetes.pkg.api.v1.Container": "io.k8s.api.core.v1.Container", - "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.AdmissionHookClientConfig": "io.k8s.api.admissionregistration.v1alpha1.AdmissionHookClientConfig", - "io.k8s.kubernetes.pkg.api.v1.ResourceQuota": "io.k8s.api.core.v1.ResourceQuota", - "io.k8s.kubernetes.pkg.api.v1.SecretList": "io.k8s.api.core.v1.SecretList", - "io.k8s.kubernetes.pkg.api.v1.NodeSystemInfo": "io.k8s.api.core.v1.NodeSystemInfo", - "io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.PolicyRule": "io.k8s.api.rbac.v1alpha1.PolicyRule", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ReplicaSetSpec": "io.k8s.api.extensions.v1beta1.ReplicaSetSpec", - "io.k8s.kubernetes.pkg.api.v1.NodeStatus": "io.k8s.api.core.v1.NodeStatus", - "io.k8s.kubernetes.pkg.api.v1.ResourceQuotaList": "io.k8s.api.core.v1.ResourceQuotaList", - "io.k8s.kubernetes.pkg.api.v1.HostPathVolumeSource": "io.k8s.api.core.v1.HostPathVolumeSource", - "io.k8s.kubernetes.pkg.apis.certificates.v1beta1.CertificateSigningRequest": "io.k8s.api.certificates.v1beta1.CertificateSigningRequest", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IngressRule": "io.k8s.api.extensions.v1beta1.IngressRule", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.NetworkPolicyPeer": "io.k8s.api.extensions.v1beta1.NetworkPolicyPeer", - "io.k8s.kubernetes.pkg.apis.storage.v1.StorageClass": "io.k8s.api.storage.v1.StorageClass", - "io.k8s.kubernetes.pkg.apis.networking.v1.NetworkPolicyPeer": "io.k8s.api.networking.v1.NetworkPolicyPeer", - "io.k8s.kubernetes.pkg.apis.networking.v1.NetworkPolicyIngressRule": "io.k8s.api.networking.v1.NetworkPolicyIngressRule", - "io.k8s.kubernetes.pkg.api.v1.StorageOSPersistentVolumeSource": "io.k8s.api.core.v1.StorageOSPersistentVolumeSource", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.NetworkPolicyIngressRule": "io.k8s.api.extensions.v1beta1.NetworkPolicyIngressRule", - "io.k8s.kubernetes.pkg.api.v1.PodAffinity": "io.k8s.api.core.v1.PodAffinity", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.RollbackConfig": "io.k8s.api.extensions.v1beta1.RollbackConfig", - "io.k8s.kubernetes.pkg.api.v1.PodList": "io.k8s.api.core.v1.PodList", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ScaleStatus": "io.k8s.api.extensions.v1beta1.ScaleStatus", - "io.k8s.kubernetes.pkg.api.v1.ComponentCondition": "io.k8s.api.core.v1.ComponentCondition", - "io.k8s.kubernetes.pkg.apis.certificates.v1beta1.CertificateSigningRequestList": "io.k8s.api.certificates.v1beta1.CertificateSigningRequestList", - "io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.ClusterRoleBindingList": "io.k8s.api.rbac.v1alpha1.ClusterRoleBindingList", - "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.HorizontalPodAutoscalerCondition": "io.k8s.api.autoscaling.v2alpha1.HorizontalPodAutoscalerCondition", - "io.k8s.kubernetes.pkg.api.v1.ServiceList": "io.k8s.api.core.v1.ServiceList", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.PodSecurityPolicy": "io.k8s.api.extensions.v1beta1.PodSecurityPolicy", - "io.k8s.kubernetes.pkg.apis.batch.v1.JobCondition": "io.k8s.api.batch.v1.JobCondition", - "io.k8s.kubernetes.pkg.apis.apps.v1beta1.DeploymentStatus": "io.k8s.api.apps.v1beta1.DeploymentStatus", - "io.k8s.kubernetes.pkg.api.v1.Volume": "io.k8s.api.core.v1.Volume", - "io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.RoleBindingList": "io.k8s.api.rbac.v1alpha1.RoleBindingList", - "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.Rule": "io.k8s.api.admissionregistration.v1alpha1.Rule", - "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.InitializerConfigurationList": "io.k8s.api.admissionregistration.v1alpha1.InitializerConfigurationList", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.NetworkPolicy": "io.k8s.api.extensions.v1beta1.NetworkPolicy", - "io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.ClusterRoleList": "io.k8s.api.rbac.v1alpha1.ClusterRoleList", - "io.k8s.kubernetes.pkg.api.v1.ObjectFieldSelector": "io.k8s.api.core.v1.ObjectFieldSelector", - "io.k8s.kubernetes.pkg.api.v1.EventList": "io.k8s.api.core.v1.EventList", - "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.MetricStatus": "io.k8s.api.autoscaling.v2alpha1.MetricStatus", - "io.k8s.kubernetes.pkg.apis.networking.v1.NetworkPolicyPort": "io.k8s.api.networking.v1.NetworkPolicyPort", - "io.k8s.kubernetes.pkg.apis.rbac.v1beta1.RoleList": "io.k8s.api.rbac.v1beta1.RoleList", - "io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.RoleList": "io.k8s.api.rbac.v1alpha1.RoleList", - "io.k8s.kubernetes.pkg.apis.apps.v1beta1.DeploymentStrategy": "io.k8s.api.apps.v1beta1.DeploymentStrategy", - "io.k8s.kubernetes.pkg.apis.autoscaling.v1.CrossVersionObjectReference": "io.k8s.api.autoscaling.v1.CrossVersionObjectReference", - "io.k8s.kubernetes.pkg.api.v1.ConfigMapProjection": "io.k8s.api.core.v1.ConfigMapProjection", - "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.CrossVersionObjectReference": "io.k8s.api.autoscaling.v2alpha1.CrossVersionObjectReference", - "io.k8s.kubernetes.pkg.api.v1.LoadBalancerStatus": "io.k8s.api.core.v1.LoadBalancerStatus", - "io.k8s.kubernetes.pkg.api.v1.ISCSIVolumeSource": "io.k8s.api.core.v1.ISCSIVolumeSource", - "io.k8s.kubernetes.pkg.apis.apps.v1beta1.ControllerRevisionList": "io.k8s.api.apps.v1beta1.ControllerRevisionList", - "io.k8s.kubernetes.pkg.api.v1.EndpointSubset": "io.k8s.api.core.v1.EndpointSubset", - "io.k8s.kubernetes.pkg.api.v1.SELinuxOptions": "io.k8s.api.core.v1.SELinuxOptions", - "io.k8s.kubernetes.pkg.api.v1.PersistentVolumeClaimVolumeSource": "io.k8s.api.core.v1.PersistentVolumeClaimVolumeSource", - "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.MetricSpec": "io.k8s.api.autoscaling.v2alpha1.MetricSpec", - "io.k8s.kubernetes.pkg.apis.apps.v1beta1.StatefulSetList": "io.k8s.api.apps.v1beta1.StatefulSetList", - "io.k8s.kubernetes.pkg.apis.authorization.v1beta1.ResourceAttributes": "io.k8s.api.authorization.v1beta1.ResourceAttributes", - "io.k8s.kubernetes.pkg.api.v1.Capabilities": "io.k8s.api.core.v1.Capabilities", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.Deployment": "io.k8s.api.extensions.v1beta1.Deployment", - "io.k8s.kubernetes.pkg.api.v1.Binding": "io.k8s.api.core.v1.Binding", - "io.k8s.kubernetes.pkg.api.v1.ReplicationControllerList": "io.k8s.api.core.v1.ReplicationControllerList", - "io.k8s.kubernetes.pkg.apis.authorization.v1.SelfSubjectAccessReview": "io.k8s.api.authorization.v1.SelfSubjectAccessReview", - "io.k8s.kubernetes.pkg.apis.authentication.v1beta1.UserInfo": "io.k8s.api.authentication.v1beta1.UserInfo", - "io.k8s.kubernetes.pkg.api.v1.HostAlias": "io.k8s.api.core.v1.HostAlias", - "io.k8s.kubernetes.pkg.apis.apps.v1beta1.StatefulSetUpdateStrategy": "io.k8s.api.apps.v1beta1.StatefulSetUpdateStrategy", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IngressSpec": "io.k8s.api.extensions.v1beta1.IngressSpec", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DeploymentCondition": "io.k8s.api.extensions.v1beta1.DeploymentCondition", - "io.k8s.kubernetes.pkg.api.v1.GCEPersistentDiskVolumeSource": "io.k8s.api.core.v1.GCEPersistentDiskVolumeSource", - "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.ExternalAdmissionHook": "io.k8s.api.admissionregistration.v1alpha1.ExternalAdmissionHook", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.Scale": "io.k8s.api.extensions.v1beta1.Scale", - "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.HorizontalPodAutoscalerStatus": "io.k8s.api.autoscaling.v2alpha1.HorizontalPodAutoscalerStatus", - "io.k8s.kubernetes.pkg.api.v1.FlexVolumeSource": "io.k8s.api.core.v1.FlexVolumeSource", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.RollingUpdateDeployment": "io.k8s.api.extensions.v1beta1.RollingUpdateDeployment", - "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.ObjectMetricStatus": "io.k8s.api.autoscaling.v2alpha1.ObjectMetricStatus", - "io.k8s.kubernetes.pkg.api.v1.Event": "io.k8s.api.core.v1.Event", - "io.k8s.kubernetes.pkg.api.v1.ResourceQuotaSpec": "io.k8s.api.core.v1.ResourceQuotaSpec", - "io.k8s.kubernetes.pkg.api.v1.Handler": "io.k8s.api.core.v1.Handler", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IngressBackend": "io.k8s.api.extensions.v1beta1.IngressBackend", - "io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.Role": "io.k8s.api.rbac.v1alpha1.Role", - "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.ObjectMetricSource": "io.k8s.api.autoscaling.v2alpha1.ObjectMetricSource", - "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.ResourceMetricStatus": "io.k8s.api.autoscaling.v2alpha1.ResourceMetricStatus", - "io.k8s.kubernetes.pkg.apis.autoscaling.v1.HorizontalPodAutoscalerSpec": "io.k8s.api.autoscaling.v1.HorizontalPodAutoscalerSpec", - "io.k8s.kubernetes.pkg.api.v1.Lifecycle": "io.k8s.api.core.v1.Lifecycle", - "io.k8s.kubernetes.pkg.apis.certificates.v1beta1.CertificateSigningRequestStatus": "io.k8s.api.certificates.v1beta1.CertificateSigningRequestStatus", - "io.k8s.kubernetes.pkg.api.v1.ContainerStateRunning": "io.k8s.api.core.v1.ContainerStateRunning", - "io.k8s.kubernetes.pkg.api.v1.ServiceAccountList": "io.k8s.api.core.v1.ServiceAccountList", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.HostPortRange": "io.k8s.api.extensions.v1beta1.HostPortRange", - "io.k8s.kubernetes.pkg.apis.apps.v1beta1.ControllerRevision": "io.k8s.api.apps.v1beta1.ControllerRevision", - "io.k8s.kubernetes.pkg.api.v1.ReplicationControllerSpec": "io.k8s.api.core.v1.ReplicationControllerSpec", - "io.k8s.kubernetes.pkg.api.v1.ContainerStateTerminated": "io.k8s.api.core.v1.ContainerStateTerminated", - "io.k8s.kubernetes.pkg.api.v1.ReplicationControllerStatus": "io.k8s.api.core.v1.ReplicationControllerStatus", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DaemonSetList": "io.k8s.api.extensions.v1beta1.DaemonSetList", - "io.k8s.kubernetes.pkg.apis.authorization.v1.SelfSubjectAccessReviewSpec": "io.k8s.api.authorization.v1.SelfSubjectAccessReviewSpec", - "io.k8s.kubernetes.pkg.api.v1.ComponentStatusList": "io.k8s.api.core.v1.ComponentStatusList", - "io.k8s.kubernetes.pkg.api.v1.ContainerStateWaiting": "io.k8s.api.core.v1.ContainerStateWaiting", - "io.k8s.kubernetes.pkg.api.v1.VolumeMount": "io.k8s.api.core.v1.VolumeMount", - "io.k8s.kubernetes.pkg.api.v1.Secret": "io.k8s.api.core.v1.Secret", - "io.k8s.kubernetes.pkg.apis.rbac.v1beta1.ClusterRoleList": "io.k8s.api.rbac.v1beta1.ClusterRoleList", - "io.k8s.kubernetes.pkg.api.v1.ConfigMapList": "io.k8s.api.core.v1.ConfigMapList", - "io.k8s.kubernetes.pkg.apis.storage.v1beta1.StorageClassList": "io.k8s.api.storage.v1beta1.StorageClassList", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.HTTPIngressPath": "io.k8s.api.extensions.v1beta1.HTTPIngressPath", - "io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.ClusterRole": "io.k8s.api.rbac.v1alpha1.ClusterRole", - "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.ResourceMetricSource": "io.k8s.api.autoscaling.v2alpha1.ResourceMetricSource", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DeploymentRollback": "io.k8s.api.extensions.v1beta1.DeploymentRollback", - "io.k8s.kubernetes.pkg.api.v1.PersistentVolumeClaimSpec": "io.k8s.api.core.v1.PersistentVolumeClaimSpec", - "io.k8s.kubernetes.pkg.api.v1.ReplicationController": "io.k8s.api.core.v1.ReplicationController", - "io.k8s.kubernetes.pkg.apis.apps.v1beta1.StatefulSetSpec": "io.k8s.api.apps.v1beta1.StatefulSetSpec", - "io.k8s.kubernetes.pkg.api.v1.SecurityContext": "io.k8s.api.core.v1.SecurityContext", - "io.k8s.kubernetes.pkg.apis.networking.v1.NetworkPolicySpec": "io.k8s.api.networking.v1.NetworkPolicySpec", - "io.k8s.kubernetes.pkg.api.v1.LocalObjectReference": "io.k8s.api.core.v1.LocalObjectReference", - "io.k8s.kubernetes.pkg.api.v1.RBDVolumeSource": "io.k8s.api.core.v1.RBDVolumeSource", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.NetworkPolicySpec": "io.k8s.api.extensions.v1beta1.NetworkPolicySpec", - "io.k8s.kubernetes.pkg.api.v1.KeyToPath": "io.k8s.api.core.v1.KeyToPath", - "io.k8s.kubernetes.pkg.api.v1.WeightedPodAffinityTerm": "io.k8s.api.core.v1.WeightedPodAffinityTerm", - "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.PodsMetricStatus": "io.k8s.api.autoscaling.v2alpha1.PodsMetricStatus", - "io.k8s.kubernetes.pkg.api.v1.NodeAddress": "io.k8s.api.core.v1.NodeAddress", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.Ingress": "io.k8s.api.extensions.v1beta1.Ingress", - "io.k8s.kubernetes.pkg.apis.policy.v1beta1.PodDisruptionBudget": "io.k8s.api.policy.v1beta1.PodDisruptionBudget", - "io.k8s.kubernetes.pkg.api.v1.ServicePort": "io.k8s.api.core.v1.ServicePort", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IDRange": "io.k8s.api.extensions.v1beta1.IDRange", - "io.k8s.kubernetes.pkg.api.v1.SecretEnvSource": "io.k8s.api.core.v1.SecretEnvSource", - "io.k8s.kubernetes.pkg.api.v1.NodeSelector": "io.k8s.api.core.v1.NodeSelector", - "io.k8s.kubernetes.pkg.api.v1.PersistentVolumeClaimStatus": "io.k8s.api.core.v1.PersistentVolumeClaimStatus", - "io.k8s.kubernetes.pkg.apis.apps.v1beta1.DeploymentSpec": "io.k8s.api.apps.v1beta1.DeploymentSpec", - "io.k8s.kubernetes.pkg.apis.authorization.v1.NonResourceAttributes": "io.k8s.api.authorization.v1.NonResourceAttributes", - "io.k8s.kubernetes.pkg.apis.autoscaling.v1.ScaleStatus": "io.k8s.api.autoscaling.v1.ScaleStatus", - "io.k8s.kubernetes.pkg.api.v1.PodCondition": "io.k8s.api.core.v1.PodCondition", - "io.k8s.kubernetes.pkg.api.v1.PodTemplateSpec": "io.k8s.api.core.v1.PodTemplateSpec", - "io.k8s.kubernetes.pkg.apis.apps.v1beta1.StatefulSet": "io.k8s.api.apps.v1beta1.StatefulSet", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.NetworkPolicyPort": "io.k8s.api.extensions.v1beta1.NetworkPolicyPort", - "io.k8s.kubernetes.pkg.apis.authentication.v1beta1.TokenReview": "io.k8s.api.authentication.v1beta1.TokenReview", - "io.k8s.kubernetes.pkg.api.v1.LimitRangeSpec": "io.k8s.api.core.v1.LimitRangeSpec", - "io.k8s.kubernetes.pkg.api.v1.FlockerVolumeSource": "io.k8s.api.core.v1.FlockerVolumeSource", - "io.k8s.kubernetes.pkg.apis.policy.v1beta1.Eviction": "io.k8s.api.policy.v1beta1.Eviction", - "io.k8s.kubernetes.pkg.api.v1.PersistentVolumeClaimList": "io.k8s.api.core.v1.PersistentVolumeClaimList", - "io.k8s.kubernetes.pkg.apis.certificates.v1beta1.CertificateSigningRequestCondition": "io.k8s.api.certificates.v1beta1.CertificateSigningRequestCondition", - "io.k8s.kubernetes.pkg.api.v1.DownwardAPIVolumeFile": "io.k8s.api.core.v1.DownwardAPIVolumeFile", - "io.k8s.kubernetes.pkg.apis.authorization.v1beta1.LocalSubjectAccessReview": "io.k8s.api.authorization.v1beta1.LocalSubjectAccessReview", - "io.k8s.kubernetes.pkg.apis.apps.v1beta1.ScaleStatus": "io.k8s.api.apps.v1beta1.ScaleStatus", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.HTTPIngressRuleValue": "io.k8s.api.extensions.v1beta1.HTTPIngressRuleValue", - "io.k8s.kubernetes.pkg.apis.batch.v1.Job": "io.k8s.api.batch.v1.Job", - "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.ExternalAdmissionHookConfiguration": "io.k8s.api.admissionregistration.v1alpha1.ExternalAdmissionHookConfiguration", - "io.k8s.kubernetes.pkg.apis.rbac.v1beta1.RoleBinding": "io.k8s.api.rbac.v1beta1.RoleBinding", - "io.k8s.kubernetes.pkg.api.v1.FCVolumeSource": "io.k8s.api.core.v1.FCVolumeSource", - "io.k8s.kubernetes.pkg.api.v1.EndpointAddress": "io.k8s.api.core.v1.EndpointAddress", - "io.k8s.kubernetes.pkg.api.v1.ContainerPort": "io.k8s.api.core.v1.ContainerPort", - "io.k8s.kubernetes.pkg.apis.rbac.v1beta1.ClusterRoleBinding": "io.k8s.api.rbac.v1beta1.ClusterRoleBinding", - "io.k8s.kubernetes.pkg.api.v1.GlusterfsVolumeSource": "io.k8s.api.core.v1.GlusterfsVolumeSource", - "io.k8s.kubernetes.pkg.api.v1.ResourceRequirements": "io.k8s.api.core.v1.ResourceRequirements", - "io.k8s.kubernetes.pkg.apis.apps.v1beta1.RollingUpdateDeployment": "io.k8s.api.apps.v1beta1.RollingUpdateDeployment", - "io.k8s.kubernetes.pkg.api.v1.NamespaceStatus": "io.k8s.api.core.v1.NamespaceStatus", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.RunAsUserStrategyOptions": "io.k8s.api.extensions.v1beta1.RunAsUserStrategyOptions", - "io.k8s.kubernetes.pkg.api.v1.Namespace": "io.k8s.api.core.v1.Namespace", - "io.k8s.kubernetes.pkg.apis.authorization.v1.SubjectAccessReviewSpec": "io.k8s.api.authorization.v1.SubjectAccessReviewSpec", - "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.HorizontalPodAutoscaler": "io.k8s.api.autoscaling.v2alpha1.HorizontalPodAutoscaler", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ReplicaSetCondition": "io.k8s.api.extensions.v1beta1.ReplicaSetCondition", - "io.k8s.kubernetes.pkg.apis.autoscaling.v1.HorizontalPodAutoscalerStatus": "io.k8s.api.autoscaling.v1.HorizontalPodAutoscalerStatus", - "io.k8s.kubernetes.pkg.apis.authentication.v1.TokenReviewStatus": "io.k8s.api.authentication.v1.TokenReviewStatus", - "io.k8s.kubernetes.pkg.api.v1.PersistentVolume": "io.k8s.api.core.v1.PersistentVolume", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.FSGroupStrategyOptions": "io.k8s.api.extensions.v1beta1.FSGroupStrategyOptions", - "io.k8s.kubernetes.pkg.api.v1.PodSecurityContext": "io.k8s.api.core.v1.PodSecurityContext", - "io.k8s.kubernetes.pkg.api.v1.PodTemplate": "io.k8s.api.core.v1.PodTemplate", - "io.k8s.kubernetes.pkg.apis.authorization.v1.LocalSubjectAccessReview": "io.k8s.api.authorization.v1.LocalSubjectAccessReview", - "io.k8s.kubernetes.pkg.api.v1.StorageOSVolumeSource": "io.k8s.api.core.v1.StorageOSVolumeSource", - "io.k8s.kubernetes.pkg.api.v1.NodeSelectorTerm": "io.k8s.api.core.v1.NodeSelectorTerm", - "io.k8s.kubernetes.pkg.apis.rbac.v1beta1.Role": "io.k8s.api.rbac.v1beta1.Role", - "io.k8s.kubernetes.pkg.api.v1.ContainerStatus": "io.k8s.api.core.v1.ContainerStatus", - "io.k8s.kubernetes.pkg.apis.authorization.v1.SubjectAccessReviewStatus": "io.k8s.api.authorization.v1.SubjectAccessReviewStatus", - "io.k8s.kubernetes.pkg.apis.authentication.v1.TokenReviewSpec": "io.k8s.api.authentication.v1.TokenReviewSpec", - "io.k8s.kubernetes.pkg.api.v1.ConfigMap": "io.k8s.api.core.v1.ConfigMap", - "io.k8s.kubernetes.pkg.api.v1.ServiceStatus": "io.k8s.api.core.v1.ServiceStatus", - "io.k8s.kubernetes.pkg.apis.authorization.v1beta1.SelfSubjectAccessReviewSpec": "io.k8s.api.authorization.v1beta1.SelfSubjectAccessReviewSpec", - "io.k8s.kubernetes.pkg.api.v1.CinderVolumeSource": "io.k8s.api.core.v1.CinderVolumeSource", - "io.k8s.kubernetes.pkg.apis.settings.v1alpha1.PodPresetSpec": "io.k8s.api.settings.v1alpha1.PodPresetSpec", - "io.k8s.kubernetes.pkg.apis.authorization.v1beta1.NonResourceAttributes": "io.k8s.api.authorization.v1beta1.NonResourceAttributes", - "io.k8s.kubernetes.pkg.api.v1.ContainerImage": "io.k8s.api.core.v1.ContainerImage", - "io.k8s.kubernetes.pkg.api.v1.ReplicationControllerCondition": "io.k8s.api.core.v1.ReplicationControllerCondition", - "io.k8s.kubernetes.pkg.api.v1.EmptyDirVolumeSource": "io.k8s.api.core.v1.EmptyDirVolumeSource", - "io.k8s.kubernetes.pkg.apis.autoscaling.v1.HorizontalPodAutoscalerList": "io.k8s.api.autoscaling.v1.HorizontalPodAutoscalerList", - "io.k8s.kubernetes.pkg.apis.batch.v1.JobList": "io.k8s.api.batch.v1.JobList", - "io.k8s.kubernetes.pkg.api.v1.NFSVolumeSource": "io.k8s.api.core.v1.NFSVolumeSource", - "io.k8s.kubernetes.pkg.api.v1.Pod": "io.k8s.api.core.v1.Pod", - "io.k8s.kubernetes.pkg.api.v1.ObjectReference": "io.k8s.api.core.v1.ObjectReference", - "io.k8s.kubernetes.pkg.apis.apps.v1beta1.Deployment": "io.k8s.api.apps.v1beta1.Deployment", - "io.k8s.kubernetes.pkg.apis.storage.v1.StorageClassList": "io.k8s.api.storage.v1.StorageClassList", - "io.k8s.kubernetes.pkg.api.v1.AttachedVolume": "io.k8s.api.core.v1.AttachedVolume", - "io.k8s.kubernetes.pkg.api.v1.AWSElasticBlockStoreVolumeSource": "io.k8s.api.core.v1.AWSElasticBlockStoreVolumeSource", - "io.k8s.kubernetes.pkg.apis.batch.v2alpha1.CronJobList": "io.k8s.api.batch.v2alpha1.CronJobList", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DeploymentSpec": "io.k8s.api.extensions.v1beta1.DeploymentSpec", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.PodSecurityPolicyList": "io.k8s.api.extensions.v1beta1.PodSecurityPolicyList", - "io.k8s.kubernetes.pkg.api.v1.PodAffinityTerm": "io.k8s.api.core.v1.PodAffinityTerm", - "io.k8s.kubernetes.pkg.api.v1.HTTPHeader": "io.k8s.api.core.v1.HTTPHeader", - "io.k8s.kubernetes.pkg.api.v1.ConfigMapKeySelector": "io.k8s.api.core.v1.ConfigMapKeySelector", - "io.k8s.kubernetes.pkg.api.v1.SecretKeySelector": "io.k8s.api.core.v1.SecretKeySelector", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DeploymentList": "io.k8s.api.extensions.v1beta1.DeploymentList", - "io.k8s.kubernetes.pkg.apis.authentication.v1.UserInfo": "io.k8s.api.authentication.v1.UserInfo", - "io.k8s.kubernetes.pkg.api.v1.LoadBalancerIngress": "io.k8s.api.core.v1.LoadBalancerIngress", - "io.k8s.kubernetes.pkg.api.v1.DaemonEndpoint": "io.k8s.api.core.v1.DaemonEndpoint", - "io.k8s.kubernetes.pkg.api.v1.NodeSelectorRequirement": "io.k8s.api.core.v1.NodeSelectorRequirement", - "io.k8s.kubernetes.pkg.apis.batch.v2alpha1.CronJobStatus": "io.k8s.api.batch.v2alpha1.CronJobStatus", - "io.k8s.kubernetes.pkg.apis.autoscaling.v1.Scale": "io.k8s.api.autoscaling.v1.Scale", - "io.k8s.kubernetes.pkg.api.v1.ScaleIOVolumeSource": "io.k8s.api.core.v1.ScaleIOVolumeSource", - "io.k8s.kubernetes.pkg.api.v1.PodAntiAffinity": "io.k8s.api.core.v1.PodAntiAffinity", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.PodSecurityPolicySpec": "io.k8s.api.extensions.v1beta1.PodSecurityPolicySpec", - "io.k8s.kubernetes.pkg.apis.settings.v1alpha1.PodPresetList": "io.k8s.api.settings.v1alpha1.PodPresetList", - "io.k8s.kubernetes.pkg.api.v1.NodeAffinity": "io.k8s.api.core.v1.NodeAffinity", - "io.k8s.kubernetes.pkg.apis.apps.v1beta1.DeploymentCondition": "io.k8s.api.apps.v1beta1.DeploymentCondition", - "io.k8s.kubernetes.pkg.api.v1.NodeSpec": "io.k8s.api.core.v1.NodeSpec", - "io.k8s.kubernetes.pkg.apis.apps.v1beta1.StatefulSetStatus": "io.k8s.api.apps.v1beta1.StatefulSetStatus", - "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.RuleWithOperations": "io.k8s.api.admissionregistration.v1alpha1.RuleWithOperations", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IngressStatus": "io.k8s.api.extensions.v1beta1.IngressStatus", - "io.k8s.kubernetes.pkg.api.v1.LimitRangeList": "io.k8s.api.core.v1.LimitRangeList", - "io.k8s.kubernetes.pkg.api.v1.AzureDiskVolumeSource": "io.k8s.api.core.v1.AzureDiskVolumeSource", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ReplicaSetStatus": "io.k8s.api.extensions.v1beta1.ReplicaSetStatus", - "io.k8s.kubernetes.pkg.api.v1.ComponentStatus": "io.k8s.api.core.v1.ComponentStatus", - "io.k8s.kubernetes.pkg.apis.autoscaling.v1.HorizontalPodAutoscaler": "io.k8s.api.autoscaling.v1.HorizontalPodAutoscaler", - "io.k8s.kubernetes.pkg.apis.networking.v1.NetworkPolicy": "io.k8s.api.networking.v1.NetworkPolicy", - "io.k8s.kubernetes.pkg.apis.apps.v1beta1.RollbackConfig": "io.k8s.api.apps.v1beta1.RollbackConfig", - "io.k8s.kubernetes.pkg.api.v1.NodeCondition": "io.k8s.api.core.v1.NodeCondition", - "io.k8s.kubernetes.pkg.api.v1.DownwardAPIProjection": "io.k8s.api.core.v1.DownwardAPIProjection", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.SELinuxStrategyOptions": "io.k8s.api.extensions.v1beta1.SELinuxStrategyOptions", - "io.k8s.kubernetes.pkg.api.v1.NamespaceSpec": "io.k8s.api.core.v1.NamespaceSpec", - "io.k8s.kubernetes.pkg.apis.certificates.v1beta1.CertificateSigningRequestSpec": "io.k8s.api.certificates.v1beta1.CertificateSigningRequestSpec", - "io.k8s.kubernetes.pkg.api.v1.ServiceSpec": "io.k8s.api.core.v1.ServiceSpec", - "io.k8s.kubernetes.pkg.apis.authorization.v1.SubjectAccessReview": "io.k8s.api.authorization.v1.SubjectAccessReview", - "io.k8s.kubernetes.pkg.apis.apps.v1beta1.DeploymentList": "io.k8s.api.apps.v1beta1.DeploymentList", - "io.k8s.kubernetes.pkg.api.v1.Toleration": "io.k8s.api.core.v1.Toleration", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.NetworkPolicyList": "io.k8s.api.extensions.v1beta1.NetworkPolicyList", - "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.PodsMetricSource": "io.k8s.api.autoscaling.v2alpha1.PodsMetricSource", - "io.k8s.kubernetes.pkg.api.v1.EnvFromSource": "io.k8s.api.core.v1.EnvFromSource", - "io.k8s.kubernetes.pkg.apis.autoscaling.v1.ScaleSpec": "io.k8s.api.autoscaling.v1.ScaleSpec", - "io.k8s.kubernetes.pkg.api.v1.PodTemplateList": "io.k8s.api.core.v1.PodTemplateList", - "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.HorizontalPodAutoscalerSpec": "io.k8s.api.autoscaling.v2alpha1.HorizontalPodAutoscalerSpec", - "io.k8s.kubernetes.pkg.api.v1.SecretProjection": "io.k8s.api.core.v1.SecretProjection", - "io.k8s.kubernetes.pkg.api.v1.ResourceFieldSelector": "io.k8s.api.core.v1.ResourceFieldSelector", - "io.k8s.kubernetes.pkg.api.v1.PersistentVolumeSpec": "io.k8s.api.core.v1.PersistentVolumeSpec", - "io.k8s.kubernetes.pkg.api.v1.ConfigMapVolumeSource": "io.k8s.api.core.v1.ConfigMapVolumeSource", - "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.HorizontalPodAutoscalerList": "io.k8s.api.autoscaling.v2alpha1.HorizontalPodAutoscalerList", - "io.k8s.kubernetes.pkg.apis.authentication.v1beta1.TokenReviewStatus": "io.k8s.api.authentication.v1beta1.TokenReviewStatus", - "io.k8s.kubernetes.pkg.apis.networking.v1.NetworkPolicyList": "io.k8s.api.networking.v1.NetworkPolicyList", - "io.k8s.kubernetes.pkg.api.v1.Endpoints": "io.k8s.api.core.v1.Endpoints", - "io.k8s.kubernetes.pkg.api.v1.LimitRangeItem": "io.k8s.api.core.v1.LimitRangeItem", - "io.k8s.kubernetes.pkg.api.v1.ServiceAccount": "io.k8s.api.core.v1.ServiceAccount", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ScaleSpec": "io.k8s.api.extensions.v1beta1.ScaleSpec", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IngressTLS": "io.k8s.api.extensions.v1beta1.IngressTLS", - "io.k8s.kubernetes.pkg.apis.batch.v2alpha1.CronJob": "io.k8s.api.batch.v2alpha1.CronJob", - "io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.Subject": "io.k8s.api.rbac.v1alpha1.Subject", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DaemonSetStatus": "io.k8s.api.extensions.v1beta1.DaemonSetStatus", - "io.k8s.kubernetes.pkg.apis.policy.v1beta1.PodDisruptionBudgetList": "io.k8s.api.policy.v1beta1.PodDisruptionBudgetList", - "io.k8s.kubernetes.pkg.api.v1.VsphereVirtualDiskVolumeSource": "io.k8s.api.core.v1.VsphereVirtualDiskVolumeSource", - "io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.RoleRef": "io.k8s.api.rbac.v1alpha1.RoleRef", - "io.k8s.kubernetes.pkg.api.v1.PortworxVolumeSource": "io.k8s.api.core.v1.PortworxVolumeSource", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ReplicaSetList": "io.k8s.api.extensions.v1beta1.ReplicaSetList", - "io.k8s.kubernetes.pkg.api.v1.VolumeProjection": "io.k8s.api.core.v1.VolumeProjection", - "io.k8s.kubernetes.pkg.apis.storage.v1beta1.StorageClass": "io.k8s.api.storage.v1beta1.StorageClass", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ReplicaSet": "io.k8s.api.extensions.v1beta1.ReplicaSet", - "io.k8s.kubernetes.pkg.apis.apps.v1beta1.DeploymentRollback": "io.k8s.api.apps.v1beta1.DeploymentRollback", - "io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.RoleBinding": "io.k8s.api.rbac.v1alpha1.RoleBinding", - "io.k8s.kubernetes.pkg.api.v1.AzureFileVolumeSource": "io.k8s.api.core.v1.AzureFileVolumeSource", - "io.k8s.kubernetes.pkg.apis.policy.v1beta1.PodDisruptionBudgetStatus": "io.k8s.api.policy.v1beta1.PodDisruptionBudgetStatus", - "io.k8s.kubernetes.pkg.apis.authentication.v1beta1.TokenReviewSpec": "io.k8s.api.authentication.v1beta1.TokenReviewSpec", - "io.k8s.kubernetes.pkg.api.v1.EndpointsList": "io.k8s.api.core.v1.EndpointsList", - "io.k8s.kubernetes.pkg.api.v1.ConfigMapEnvSource": "io.k8s.api.core.v1.ConfigMapEnvSource", - "io.k8s.kubernetes.pkg.apis.batch.v2alpha1.JobTemplateSpec": "io.k8s.api.batch.v2alpha1.JobTemplateSpec", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DaemonSetUpdateStrategy": "io.k8s.api.extensions.v1beta1.DaemonSetUpdateStrategy", - "io.k8s.kubernetes.pkg.apis.authorization.v1beta1.SubjectAccessReviewSpec": "io.k8s.api.authorization.v1beta1.SubjectAccessReviewSpec", - "io.k8s.kubernetes.pkg.api.v1.LocalVolumeSource": "io.k8s.api.core.v1.LocalVolumeSource", - "io.k8s.kubernetes.pkg.api.v1.ContainerState": "io.k8s.api.core.v1.ContainerState", - "io.k8s.kubernetes.pkg.api.v1.Service": "io.k8s.api.core.v1.Service", - "io.k8s.kubernetes.pkg.api.v1.ExecAction": "io.k8s.api.core.v1.ExecAction", - "io.k8s.kubernetes.pkg.api.v1.Taint": "io.k8s.api.core.v1.Taint", - "io.k8s.kubernetes.pkg.apis.rbac.v1beta1.Subject": "io.k8s.api.rbac.v1beta1.Subject", - "io.k8s.kubernetes.pkg.apis.authorization.v1beta1.SubjectAccessReviewStatus": "io.k8s.api.authorization.v1beta1.SubjectAccessReviewStatus", - "io.k8s.kubernetes.pkg.apis.rbac.v1beta1.ClusterRoleBindingList": "io.k8s.api.rbac.v1beta1.ClusterRoleBindingList", - "io.k8s.kubernetes.pkg.api.v1.DownwardAPIVolumeSource": "io.k8s.api.core.v1.DownwardAPIVolumeSource", - "io.k8s.kubernetes.pkg.apis.batch.v1.JobStatus": "io.k8s.api.batch.v1.JobStatus", - "io.k8s.kubernetes.pkg.api.v1.ResourceQuotaStatus": "io.k8s.api.core.v1.ResourceQuotaStatus", - "io.k8s.kubernetes.pkg.api.v1.PersistentVolumeStatus": "io.k8s.api.core.v1.PersistentVolumeStatus", - "io.k8s.kubernetes.pkg.api.v1.PersistentVolumeClaim": "io.k8s.api.core.v1.PersistentVolumeClaim", - "io.k8s.kubernetes.pkg.api.v1.NodeDaemonEndpoints": "io.k8s.api.core.v1.NodeDaemonEndpoints", - "io.k8s.kubernetes.pkg.api.v1.EnvVar": "io.k8s.api.core.v1.EnvVar", - "io.k8s.kubernetes.pkg.api.v1.SecretVolumeSource": "io.k8s.api.core.v1.SecretVolumeSource", - "io.k8s.kubernetes.pkg.api.v1.EnvVarSource": "io.k8s.api.core.v1.EnvVarSource", - "io.k8s.kubernetes.pkg.apis.apps.v1beta1.RollingUpdateStatefulSetStrategy": "io.k8s.api.apps.v1beta1.RollingUpdateStatefulSetStrategy", - "io.k8s.kubernetes.pkg.apis.rbac.v1beta1.ClusterRole": "io.k8s.api.rbac.v1beta1.ClusterRole", - "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.Initializer": "io.k8s.api.admissionregistration.v1alpha1.Initializer", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DeploymentStrategy": "io.k8s.api.extensions.v1beta1.DeploymentStrategy", - "io.k8s.kubernetes.pkg.apis.apps.v1beta1.ScaleSpec": "io.k8s.api.apps.v1beta1.ScaleSpec", - "io.k8s.kubernetes.pkg.apis.settings.v1alpha1.PodPreset": "io.k8s.api.settings.v1alpha1.PodPreset", - "io.k8s.kubernetes.pkg.api.v1.Probe": "io.k8s.api.core.v1.Probe", - "io.k8s.kubernetes.pkg.api.v1.NamespaceList": "io.k8s.api.core.v1.NamespaceList", - "io.k8s.kubernetes.pkg.api.v1.QuobyteVolumeSource": "io.k8s.api.core.v1.QuobyteVolumeSource", - "io.k8s.kubernetes.pkg.api.v1.NodeList": "io.k8s.api.core.v1.NodeList", - "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.RollingUpdateDaemonSet": "io.k8s.api.extensions.v1beta1.RollingUpdateDaemonSet", - "io.k8s.kubernetes.pkg.api.v1.LimitRange": "io.k8s.api.core.v1.LimitRange", + "io.k8s.kubernetes.pkg.apis.authorization.v1beta1.SelfSubjectAccessReview": "io.k8s.api.authorization.v1beta1.SelfSubjectAccessReview", + "io.k8s.kubernetes.pkg.api.v1.GitRepoVolumeSource": "io.k8s.api.core.v1.GitRepoVolumeSource", + "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.ValidatingWebhookConfigurationList": "io.k8s.api.admissionregistration.v1alpha1.ValidatingWebhookConfigurationList", + "io.k8s.kubernetes.pkg.api.v1.EndpointPort": "io.k8s.api.core.v1.EndpointPort", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.SupplementalGroupsStrategyOptions": "io.k8s.api.extensions.v1beta1.SupplementalGroupsStrategyOptions", + "io.k8s.kubernetes.pkg.api.v1.PodStatus": "io.k8s.api.core.v1.PodStatus", + "io.k8s.kubernetes.pkg.apis.rbac.v1beta1.RoleBindingList": "io.k8s.api.rbac.v1beta1.RoleBindingList", + "io.k8s.kubernetes.pkg.apis.policy.v1beta1.PodDisruptionBudgetSpec": "io.k8s.api.policy.v1beta1.PodDisruptionBudgetSpec", + "io.k8s.kubernetes.pkg.api.v1.HTTPGetAction": "io.k8s.api.core.v1.HTTPGetAction", + "io.k8s.kubernetes.pkg.apis.authorization.v1.ResourceAttributes": "io.k8s.api.authorization.v1.ResourceAttributes", + "io.k8s.kubernetes.pkg.api.v1.PersistentVolumeList": "io.k8s.api.core.v1.PersistentVolumeList", + "io.k8s.kubernetes.pkg.apis.batch.v2alpha1.CronJobSpec": "io.k8s.api.batch.v2alpha1.CronJobSpec", + "io.k8s.kubernetes.pkg.api.v1.CephFSVolumeSource": "io.k8s.api.core.v1.CephFSVolumeSource", + "io.k8s.kubernetes.pkg.api.v1.Affinity": "io.k8s.api.core.v1.Affinity", + "io.k8s.kubernetes.pkg.apis.rbac.v1beta1.PolicyRule": "io.k8s.api.rbac.v1beta1.PolicyRule", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DaemonSetSpec": "io.k8s.api.extensions.v1beta1.DaemonSetSpec", + "io.k8s.kubernetes.pkg.api.v1.ProjectedVolumeSource": "io.k8s.api.core.v1.ProjectedVolumeSource", + "io.k8s.kubernetes.pkg.api.v1.TCPSocketAction": "io.k8s.api.core.v1.TCPSocketAction", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DaemonSet": "io.k8s.api.extensions.v1beta1.DaemonSet", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IngressList": "io.k8s.api.extensions.v1beta1.IngressList", + "io.k8s.kubernetes.pkg.api.v1.PodSpec": "io.k8s.api.core.v1.PodSpec", + "io.k8s.kubernetes.pkg.apis.authentication.v1.TokenReview": "io.k8s.api.authentication.v1.TokenReview", + "io.k8s.kubernetes.pkg.apis.authorization.v1beta1.SubjectAccessReview": "io.k8s.api.authorization.v1beta1.SubjectAccessReview", + "io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.ClusterRoleBinding": "io.k8s.api.rbac.v1alpha1.ClusterRoleBinding", + "io.k8s.kubernetes.pkg.api.v1.Node": "io.k8s.api.core.v1.Node", + "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.ServiceReference": "io.k8s.api.admissionregistration.v1alpha1.ServiceReference", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DeploymentStatus": "io.k8s.api.extensions.v1beta1.DeploymentStatus", + "io.k8s.kubernetes.pkg.apis.rbac.v1beta1.RoleRef": "io.k8s.api.rbac.v1beta1.RoleRef", + "io.k8s.kubernetes.pkg.apis.apps.v1beta1.Scale": "io.k8s.api.apps.v1beta1.Scale", + "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.InitializerConfiguration": "io.k8s.api.admissionregistration.v1alpha1.InitializerConfiguration", + "io.k8s.kubernetes.pkg.api.v1.PhotonPersistentDiskVolumeSource": "io.k8s.api.core.v1.PhotonPersistentDiskVolumeSource", + "io.k8s.kubernetes.pkg.api.v1.PreferredSchedulingTerm": "io.k8s.api.core.v1.PreferredSchedulingTerm", + "io.k8s.kubernetes.pkg.apis.batch.v1.JobSpec": "io.k8s.api.batch.v1.JobSpec", + "io.k8s.kubernetes.pkg.api.v1.EventSource": "io.k8s.api.core.v1.EventSource", + "io.k8s.kubernetes.pkg.api.v1.Container": "io.k8s.api.core.v1.Container", + "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.AdmissionHookClientConfig": "io.k8s.api.admissionregistration.v1alpha1.AdmissionHookClientConfig", + "io.k8s.kubernetes.pkg.api.v1.ResourceQuota": "io.k8s.api.core.v1.ResourceQuota", + "io.k8s.kubernetes.pkg.api.v1.SecretList": "io.k8s.api.core.v1.SecretList", + "io.k8s.kubernetes.pkg.api.v1.NodeSystemInfo": "io.k8s.api.core.v1.NodeSystemInfo", + "io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.PolicyRule": "io.k8s.api.rbac.v1alpha1.PolicyRule", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ReplicaSetSpec": "io.k8s.api.extensions.v1beta1.ReplicaSetSpec", + "io.k8s.kubernetes.pkg.api.v1.NodeStatus": "io.k8s.api.core.v1.NodeStatus", + "io.k8s.kubernetes.pkg.api.v1.ResourceQuotaList": "io.k8s.api.core.v1.ResourceQuotaList", + "io.k8s.kubernetes.pkg.api.v1.HostPathVolumeSource": "io.k8s.api.core.v1.HostPathVolumeSource", + "io.k8s.kubernetes.pkg.apis.certificates.v1beta1.CertificateSigningRequest": "io.k8s.api.certificates.v1beta1.CertificateSigningRequest", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IngressRule": "io.k8s.api.extensions.v1beta1.IngressRule", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.NetworkPolicyPeer": "io.k8s.api.extensions.v1beta1.NetworkPolicyPeer", + "io.k8s.kubernetes.pkg.apis.storage.v1.StorageClass": "io.k8s.api.storage.v1.StorageClass", + "io.k8s.kubernetes.pkg.apis.networking.v1.NetworkPolicyPeer": "io.k8s.api.networking.v1.NetworkPolicyPeer", + "io.k8s.kubernetes.pkg.apis.networking.v1.NetworkPolicyIngressRule": "io.k8s.api.networking.v1.NetworkPolicyIngressRule", + "io.k8s.kubernetes.pkg.api.v1.StorageOSPersistentVolumeSource": "io.k8s.api.core.v1.StorageOSPersistentVolumeSource", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.NetworkPolicyIngressRule": "io.k8s.api.extensions.v1beta1.NetworkPolicyIngressRule", + "io.k8s.kubernetes.pkg.api.v1.PodAffinity": "io.k8s.api.core.v1.PodAffinity", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.RollbackConfig": "io.k8s.api.extensions.v1beta1.RollbackConfig", + "io.k8s.kubernetes.pkg.api.v1.PodList": "io.k8s.api.core.v1.PodList", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ScaleStatus": "io.k8s.api.extensions.v1beta1.ScaleStatus", + "io.k8s.kubernetes.pkg.api.v1.ComponentCondition": "io.k8s.api.core.v1.ComponentCondition", + "io.k8s.kubernetes.pkg.apis.certificates.v1beta1.CertificateSigningRequestList": "io.k8s.api.certificates.v1beta1.CertificateSigningRequestList", + "io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.ClusterRoleBindingList": "io.k8s.api.rbac.v1alpha1.ClusterRoleBindingList", + "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.HorizontalPodAutoscalerCondition": "io.k8s.api.autoscaling.v2alpha1.HorizontalPodAutoscalerCondition", + "io.k8s.kubernetes.pkg.api.v1.ServiceList": "io.k8s.api.core.v1.ServiceList", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.PodSecurityPolicy": "io.k8s.api.extensions.v1beta1.PodSecurityPolicy", + "io.k8s.kubernetes.pkg.apis.batch.v1.JobCondition": "io.k8s.api.batch.v1.JobCondition", + "io.k8s.kubernetes.pkg.apis.apps.v1beta1.DeploymentStatus": "io.k8s.api.apps.v1beta1.DeploymentStatus", + "io.k8s.kubernetes.pkg.api.v1.Volume": "io.k8s.api.core.v1.Volume", + "io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.RoleBindingList": "io.k8s.api.rbac.v1alpha1.RoleBindingList", + "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.Rule": "io.k8s.api.admissionregistration.v1alpha1.Rule", + "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.InitializerConfigurationList": "io.k8s.api.admissionregistration.v1alpha1.InitializerConfigurationList", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.NetworkPolicy": "io.k8s.api.extensions.v1beta1.NetworkPolicy", + "io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.ClusterRoleList": "io.k8s.api.rbac.v1alpha1.ClusterRoleList", + "io.k8s.kubernetes.pkg.api.v1.ObjectFieldSelector": "io.k8s.api.core.v1.ObjectFieldSelector", + "io.k8s.kubernetes.pkg.api.v1.EventList": "io.k8s.api.core.v1.EventList", + "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.MetricStatus": "io.k8s.api.autoscaling.v2alpha1.MetricStatus", + "io.k8s.kubernetes.pkg.apis.networking.v1.NetworkPolicyPort": "io.k8s.api.networking.v1.NetworkPolicyPort", + "io.k8s.kubernetes.pkg.apis.rbac.v1beta1.RoleList": "io.k8s.api.rbac.v1beta1.RoleList", + "io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.RoleList": "io.k8s.api.rbac.v1alpha1.RoleList", + "io.k8s.kubernetes.pkg.apis.apps.v1beta1.DeploymentStrategy": "io.k8s.api.apps.v1beta1.DeploymentStrategy", + "io.k8s.kubernetes.pkg.apis.autoscaling.v1.CrossVersionObjectReference": "io.k8s.api.autoscaling.v1.CrossVersionObjectReference", + "io.k8s.kubernetes.pkg.api.v1.ConfigMapProjection": "io.k8s.api.core.v1.ConfigMapProjection", + "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.CrossVersionObjectReference": "io.k8s.api.autoscaling.v2alpha1.CrossVersionObjectReference", + "io.k8s.kubernetes.pkg.api.v1.LoadBalancerStatus": "io.k8s.api.core.v1.LoadBalancerStatus", + "io.k8s.kubernetes.pkg.api.v1.ISCSIVolumeSource": "io.k8s.api.core.v1.ISCSIVolumeSource", + "io.k8s.kubernetes.pkg.apis.apps.v1beta1.ControllerRevisionList": "io.k8s.api.apps.v1beta1.ControllerRevisionList", + "io.k8s.kubernetes.pkg.api.v1.EndpointSubset": "io.k8s.api.core.v1.EndpointSubset", + "io.k8s.kubernetes.pkg.api.v1.SELinuxOptions": "io.k8s.api.core.v1.SELinuxOptions", + "io.k8s.kubernetes.pkg.api.v1.PersistentVolumeClaimVolumeSource": "io.k8s.api.core.v1.PersistentVolumeClaimVolumeSource", + "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.MetricSpec": "io.k8s.api.autoscaling.v2alpha1.MetricSpec", + "io.k8s.kubernetes.pkg.apis.apps.v1beta1.StatefulSetList": "io.k8s.api.apps.v1beta1.StatefulSetList", + "io.k8s.kubernetes.pkg.apis.authorization.v1beta1.ResourceAttributes": "io.k8s.api.authorization.v1beta1.ResourceAttributes", + "io.k8s.kubernetes.pkg.api.v1.Capabilities": "io.k8s.api.core.v1.Capabilities", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.Deployment": "io.k8s.api.extensions.v1beta1.Deployment", + "io.k8s.kubernetes.pkg.api.v1.Binding": "io.k8s.api.core.v1.Binding", + "io.k8s.kubernetes.pkg.api.v1.ReplicationControllerList": "io.k8s.api.core.v1.ReplicationControllerList", + "io.k8s.kubernetes.pkg.apis.authorization.v1.SelfSubjectAccessReview": "io.k8s.api.authorization.v1.SelfSubjectAccessReview", + "io.k8s.kubernetes.pkg.apis.authentication.v1beta1.UserInfo": "io.k8s.api.authentication.v1beta1.UserInfo", + "io.k8s.kubernetes.pkg.api.v1.HostAlias": "io.k8s.api.core.v1.HostAlias", + "io.k8s.kubernetes.pkg.apis.apps.v1beta1.StatefulSetUpdateStrategy": "io.k8s.api.apps.v1beta1.StatefulSetUpdateStrategy", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IngressSpec": "io.k8s.api.extensions.v1beta1.IngressSpec", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DeploymentCondition": "io.k8s.api.extensions.v1beta1.DeploymentCondition", + "io.k8s.kubernetes.pkg.api.v1.GCEPersistentDiskVolumeSource": "io.k8s.api.core.v1.GCEPersistentDiskVolumeSource", + "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.Webhook": "io.k8s.api.admissionregistration.v1alpha1.Webhook", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.Scale": "io.k8s.api.extensions.v1beta1.Scale", + "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.HorizontalPodAutoscalerStatus": "io.k8s.api.autoscaling.v2alpha1.HorizontalPodAutoscalerStatus", + "io.k8s.kubernetes.pkg.api.v1.FlexVolumeSource": "io.k8s.api.core.v1.FlexVolumeSource", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.RollingUpdateDeployment": "io.k8s.api.extensions.v1beta1.RollingUpdateDeployment", + "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.ObjectMetricStatus": "io.k8s.api.autoscaling.v2alpha1.ObjectMetricStatus", + "io.k8s.kubernetes.pkg.api.v1.Event": "io.k8s.api.core.v1.Event", + "io.k8s.kubernetes.pkg.api.v1.ResourceQuotaSpec": "io.k8s.api.core.v1.ResourceQuotaSpec", + "io.k8s.kubernetes.pkg.api.v1.Handler": "io.k8s.api.core.v1.Handler", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IngressBackend": "io.k8s.api.extensions.v1beta1.IngressBackend", + "io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.Role": "io.k8s.api.rbac.v1alpha1.Role", + "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.ObjectMetricSource": "io.k8s.api.autoscaling.v2alpha1.ObjectMetricSource", + "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.ResourceMetricStatus": "io.k8s.api.autoscaling.v2alpha1.ResourceMetricStatus", + "io.k8s.kubernetes.pkg.apis.autoscaling.v1.HorizontalPodAutoscalerSpec": "io.k8s.api.autoscaling.v1.HorizontalPodAutoscalerSpec", + "io.k8s.kubernetes.pkg.api.v1.Lifecycle": "io.k8s.api.core.v1.Lifecycle", + "io.k8s.kubernetes.pkg.apis.certificates.v1beta1.CertificateSigningRequestStatus": "io.k8s.api.certificates.v1beta1.CertificateSigningRequestStatus", + "io.k8s.kubernetes.pkg.api.v1.ContainerStateRunning": "io.k8s.api.core.v1.ContainerStateRunning", + "io.k8s.kubernetes.pkg.api.v1.ServiceAccountList": "io.k8s.api.core.v1.ServiceAccountList", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.HostPortRange": "io.k8s.api.extensions.v1beta1.HostPortRange", + "io.k8s.kubernetes.pkg.apis.apps.v1beta1.ControllerRevision": "io.k8s.api.apps.v1beta1.ControllerRevision", + "io.k8s.kubernetes.pkg.api.v1.ReplicationControllerSpec": "io.k8s.api.core.v1.ReplicationControllerSpec", + "io.k8s.kubernetes.pkg.api.v1.ContainerStateTerminated": "io.k8s.api.core.v1.ContainerStateTerminated", + "io.k8s.kubernetes.pkg.api.v1.ReplicationControllerStatus": "io.k8s.api.core.v1.ReplicationControllerStatus", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DaemonSetList": "io.k8s.api.extensions.v1beta1.DaemonSetList", + "io.k8s.kubernetes.pkg.apis.authorization.v1.SelfSubjectAccessReviewSpec": "io.k8s.api.authorization.v1.SelfSubjectAccessReviewSpec", + "io.k8s.kubernetes.pkg.api.v1.ComponentStatusList": "io.k8s.api.core.v1.ComponentStatusList", + "io.k8s.kubernetes.pkg.api.v1.ContainerStateWaiting": "io.k8s.api.core.v1.ContainerStateWaiting", + "io.k8s.kubernetes.pkg.api.v1.VolumeMount": "io.k8s.api.core.v1.VolumeMount", + "io.k8s.kubernetes.pkg.api.v1.Secret": "io.k8s.api.core.v1.Secret", + "io.k8s.kubernetes.pkg.apis.rbac.v1beta1.ClusterRoleList": "io.k8s.api.rbac.v1beta1.ClusterRoleList", + "io.k8s.kubernetes.pkg.api.v1.ConfigMapList": "io.k8s.api.core.v1.ConfigMapList", + "io.k8s.kubernetes.pkg.apis.storage.v1beta1.StorageClassList": "io.k8s.api.storage.v1beta1.StorageClassList", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.HTTPIngressPath": "io.k8s.api.extensions.v1beta1.HTTPIngressPath", + "io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.ClusterRole": "io.k8s.api.rbac.v1alpha1.ClusterRole", + "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.ResourceMetricSource": "io.k8s.api.autoscaling.v2alpha1.ResourceMetricSource", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DeploymentRollback": "io.k8s.api.extensions.v1beta1.DeploymentRollback", + "io.k8s.kubernetes.pkg.api.v1.PersistentVolumeClaimSpec": "io.k8s.api.core.v1.PersistentVolumeClaimSpec", + "io.k8s.kubernetes.pkg.api.v1.ReplicationController": "io.k8s.api.core.v1.ReplicationController", + "io.k8s.kubernetes.pkg.apis.apps.v1beta1.StatefulSetSpec": "io.k8s.api.apps.v1beta1.StatefulSetSpec", + "io.k8s.kubernetes.pkg.api.v1.SecurityContext": "io.k8s.api.core.v1.SecurityContext", + "io.k8s.kubernetes.pkg.apis.networking.v1.NetworkPolicySpec": "io.k8s.api.networking.v1.NetworkPolicySpec", + "io.k8s.kubernetes.pkg.api.v1.LocalObjectReference": "io.k8s.api.core.v1.LocalObjectReference", + "io.k8s.kubernetes.pkg.api.v1.RBDVolumeSource": "io.k8s.api.core.v1.RBDVolumeSource", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.NetworkPolicySpec": "io.k8s.api.extensions.v1beta1.NetworkPolicySpec", + "io.k8s.kubernetes.pkg.api.v1.KeyToPath": "io.k8s.api.core.v1.KeyToPath", + "io.k8s.kubernetes.pkg.api.v1.WeightedPodAffinityTerm": "io.k8s.api.core.v1.WeightedPodAffinityTerm", + "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.PodsMetricStatus": "io.k8s.api.autoscaling.v2alpha1.PodsMetricStatus", + "io.k8s.kubernetes.pkg.api.v1.NodeAddress": "io.k8s.api.core.v1.NodeAddress", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.Ingress": "io.k8s.api.extensions.v1beta1.Ingress", + "io.k8s.kubernetes.pkg.apis.policy.v1beta1.PodDisruptionBudget": "io.k8s.api.policy.v1beta1.PodDisruptionBudget", + "io.k8s.kubernetes.pkg.api.v1.ServicePort": "io.k8s.api.core.v1.ServicePort", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IDRange": "io.k8s.api.extensions.v1beta1.IDRange", + "io.k8s.kubernetes.pkg.api.v1.SecretEnvSource": "io.k8s.api.core.v1.SecretEnvSource", + "io.k8s.kubernetes.pkg.api.v1.NodeSelector": "io.k8s.api.core.v1.NodeSelector", + "io.k8s.kubernetes.pkg.api.v1.PersistentVolumeClaimStatus": "io.k8s.api.core.v1.PersistentVolumeClaimStatus", + "io.k8s.kubernetes.pkg.apis.apps.v1beta1.DeploymentSpec": "io.k8s.api.apps.v1beta1.DeploymentSpec", + "io.k8s.kubernetes.pkg.apis.authorization.v1.NonResourceAttributes": "io.k8s.api.authorization.v1.NonResourceAttributes", + "io.k8s.kubernetes.pkg.apis.autoscaling.v1.ScaleStatus": "io.k8s.api.autoscaling.v1.ScaleStatus", + "io.k8s.kubernetes.pkg.api.v1.PodCondition": "io.k8s.api.core.v1.PodCondition", + "io.k8s.kubernetes.pkg.api.v1.PodTemplateSpec": "io.k8s.api.core.v1.PodTemplateSpec", + "io.k8s.kubernetes.pkg.apis.apps.v1beta1.StatefulSet": "io.k8s.api.apps.v1beta1.StatefulSet", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.NetworkPolicyPort": "io.k8s.api.extensions.v1beta1.NetworkPolicyPort", + "io.k8s.kubernetes.pkg.apis.authentication.v1beta1.TokenReview": "io.k8s.api.authentication.v1beta1.TokenReview", + "io.k8s.kubernetes.pkg.api.v1.LimitRangeSpec": "io.k8s.api.core.v1.LimitRangeSpec", + "io.k8s.kubernetes.pkg.api.v1.FlockerVolumeSource": "io.k8s.api.core.v1.FlockerVolumeSource", + "io.k8s.kubernetes.pkg.apis.policy.v1beta1.Eviction": "io.k8s.api.policy.v1beta1.Eviction", + "io.k8s.kubernetes.pkg.api.v1.PersistentVolumeClaimList": "io.k8s.api.core.v1.PersistentVolumeClaimList", + "io.k8s.kubernetes.pkg.apis.certificates.v1beta1.CertificateSigningRequestCondition": "io.k8s.api.certificates.v1beta1.CertificateSigningRequestCondition", + "io.k8s.kubernetes.pkg.api.v1.DownwardAPIVolumeFile": "io.k8s.api.core.v1.DownwardAPIVolumeFile", + "io.k8s.kubernetes.pkg.apis.authorization.v1beta1.LocalSubjectAccessReview": "io.k8s.api.authorization.v1beta1.LocalSubjectAccessReview", + "io.k8s.kubernetes.pkg.apis.apps.v1beta1.ScaleStatus": "io.k8s.api.apps.v1beta1.ScaleStatus", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.HTTPIngressRuleValue": "io.k8s.api.extensions.v1beta1.HTTPIngressRuleValue", + "io.k8s.kubernetes.pkg.apis.batch.v1.Job": "io.k8s.api.batch.v1.Job", + "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.ValidatingWebhookConfiguration": "io.k8s.api.admissionregistration.v1alpha1.ValidatingWebhookConfiguration", + "io.k8s.kubernetes.pkg.apis.rbac.v1beta1.RoleBinding": "io.k8s.api.rbac.v1beta1.RoleBinding", + "io.k8s.kubernetes.pkg.api.v1.FCVolumeSource": "io.k8s.api.core.v1.FCVolumeSource", + "io.k8s.kubernetes.pkg.api.v1.EndpointAddress": "io.k8s.api.core.v1.EndpointAddress", + "io.k8s.kubernetes.pkg.api.v1.ContainerPort": "io.k8s.api.core.v1.ContainerPort", + "io.k8s.kubernetes.pkg.apis.rbac.v1beta1.ClusterRoleBinding": "io.k8s.api.rbac.v1beta1.ClusterRoleBinding", + "io.k8s.kubernetes.pkg.api.v1.GlusterfsVolumeSource": "io.k8s.api.core.v1.GlusterfsVolumeSource", + "io.k8s.kubernetes.pkg.api.v1.ResourceRequirements": "io.k8s.api.core.v1.ResourceRequirements", + "io.k8s.kubernetes.pkg.apis.apps.v1beta1.RollingUpdateDeployment": "io.k8s.api.apps.v1beta1.RollingUpdateDeployment", + "io.k8s.kubernetes.pkg.api.v1.NamespaceStatus": "io.k8s.api.core.v1.NamespaceStatus", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.RunAsUserStrategyOptions": "io.k8s.api.extensions.v1beta1.RunAsUserStrategyOptions", + "io.k8s.kubernetes.pkg.api.v1.Namespace": "io.k8s.api.core.v1.Namespace", + "io.k8s.kubernetes.pkg.apis.authorization.v1.SubjectAccessReviewSpec": "io.k8s.api.authorization.v1.SubjectAccessReviewSpec", + "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.HorizontalPodAutoscaler": "io.k8s.api.autoscaling.v2alpha1.HorizontalPodAutoscaler", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ReplicaSetCondition": "io.k8s.api.extensions.v1beta1.ReplicaSetCondition", + "io.k8s.kubernetes.pkg.apis.autoscaling.v1.HorizontalPodAutoscalerStatus": "io.k8s.api.autoscaling.v1.HorizontalPodAutoscalerStatus", + "io.k8s.kubernetes.pkg.apis.authentication.v1.TokenReviewStatus": "io.k8s.api.authentication.v1.TokenReviewStatus", + "io.k8s.kubernetes.pkg.api.v1.PersistentVolume": "io.k8s.api.core.v1.PersistentVolume", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.FSGroupStrategyOptions": "io.k8s.api.extensions.v1beta1.FSGroupStrategyOptions", + "io.k8s.kubernetes.pkg.api.v1.PodSecurityContext": "io.k8s.api.core.v1.PodSecurityContext", + "io.k8s.kubernetes.pkg.api.v1.PodTemplate": "io.k8s.api.core.v1.PodTemplate", + "io.k8s.kubernetes.pkg.apis.authorization.v1.LocalSubjectAccessReview": "io.k8s.api.authorization.v1.LocalSubjectAccessReview", + "io.k8s.kubernetes.pkg.api.v1.StorageOSVolumeSource": "io.k8s.api.core.v1.StorageOSVolumeSource", + "io.k8s.kubernetes.pkg.api.v1.NodeSelectorTerm": "io.k8s.api.core.v1.NodeSelectorTerm", + "io.k8s.kubernetes.pkg.apis.rbac.v1beta1.Role": "io.k8s.api.rbac.v1beta1.Role", + "io.k8s.kubernetes.pkg.api.v1.ContainerStatus": "io.k8s.api.core.v1.ContainerStatus", + "io.k8s.kubernetes.pkg.apis.authorization.v1.SubjectAccessReviewStatus": "io.k8s.api.authorization.v1.SubjectAccessReviewStatus", + "io.k8s.kubernetes.pkg.apis.authentication.v1.TokenReviewSpec": "io.k8s.api.authentication.v1.TokenReviewSpec", + "io.k8s.kubernetes.pkg.api.v1.ConfigMap": "io.k8s.api.core.v1.ConfigMap", + "io.k8s.kubernetes.pkg.api.v1.ServiceStatus": "io.k8s.api.core.v1.ServiceStatus", + "io.k8s.kubernetes.pkg.apis.authorization.v1beta1.SelfSubjectAccessReviewSpec": "io.k8s.api.authorization.v1beta1.SelfSubjectAccessReviewSpec", + "io.k8s.kubernetes.pkg.api.v1.CinderVolumeSource": "io.k8s.api.core.v1.CinderVolumeSource", + "io.k8s.kubernetes.pkg.apis.settings.v1alpha1.PodPresetSpec": "io.k8s.api.settings.v1alpha1.PodPresetSpec", + "io.k8s.kubernetes.pkg.apis.authorization.v1beta1.NonResourceAttributes": "io.k8s.api.authorization.v1beta1.NonResourceAttributes", + "io.k8s.kubernetes.pkg.api.v1.ContainerImage": "io.k8s.api.core.v1.ContainerImage", + "io.k8s.kubernetes.pkg.api.v1.ReplicationControllerCondition": "io.k8s.api.core.v1.ReplicationControllerCondition", + "io.k8s.kubernetes.pkg.api.v1.EmptyDirVolumeSource": "io.k8s.api.core.v1.EmptyDirVolumeSource", + "io.k8s.kubernetes.pkg.apis.autoscaling.v1.HorizontalPodAutoscalerList": "io.k8s.api.autoscaling.v1.HorizontalPodAutoscalerList", + "io.k8s.kubernetes.pkg.apis.batch.v1.JobList": "io.k8s.api.batch.v1.JobList", + "io.k8s.kubernetes.pkg.api.v1.NFSVolumeSource": "io.k8s.api.core.v1.NFSVolumeSource", + "io.k8s.kubernetes.pkg.api.v1.Pod": "io.k8s.api.core.v1.Pod", + "io.k8s.kubernetes.pkg.api.v1.ObjectReference": "io.k8s.api.core.v1.ObjectReference", + "io.k8s.kubernetes.pkg.apis.apps.v1beta1.Deployment": "io.k8s.api.apps.v1beta1.Deployment", + "io.k8s.kubernetes.pkg.apis.storage.v1.StorageClassList": "io.k8s.api.storage.v1.StorageClassList", + "io.k8s.kubernetes.pkg.api.v1.AttachedVolume": "io.k8s.api.core.v1.AttachedVolume", + "io.k8s.kubernetes.pkg.api.v1.AWSElasticBlockStoreVolumeSource": "io.k8s.api.core.v1.AWSElasticBlockStoreVolumeSource", + "io.k8s.kubernetes.pkg.apis.batch.v2alpha1.CronJobList": "io.k8s.api.batch.v2alpha1.CronJobList", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DeploymentSpec": "io.k8s.api.extensions.v1beta1.DeploymentSpec", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.PodSecurityPolicyList": "io.k8s.api.extensions.v1beta1.PodSecurityPolicyList", + "io.k8s.kubernetes.pkg.api.v1.PodAffinityTerm": "io.k8s.api.core.v1.PodAffinityTerm", + "io.k8s.kubernetes.pkg.api.v1.HTTPHeader": "io.k8s.api.core.v1.HTTPHeader", + "io.k8s.kubernetes.pkg.api.v1.ConfigMapKeySelector": "io.k8s.api.core.v1.ConfigMapKeySelector", + "io.k8s.kubernetes.pkg.api.v1.SecretKeySelector": "io.k8s.api.core.v1.SecretKeySelector", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DeploymentList": "io.k8s.api.extensions.v1beta1.DeploymentList", + "io.k8s.kubernetes.pkg.apis.authentication.v1.UserInfo": "io.k8s.api.authentication.v1.UserInfo", + "io.k8s.kubernetes.pkg.api.v1.LoadBalancerIngress": "io.k8s.api.core.v1.LoadBalancerIngress", + "io.k8s.kubernetes.pkg.api.v1.DaemonEndpoint": "io.k8s.api.core.v1.DaemonEndpoint", + "io.k8s.kubernetes.pkg.api.v1.NodeSelectorRequirement": "io.k8s.api.core.v1.NodeSelectorRequirement", + "io.k8s.kubernetes.pkg.apis.batch.v2alpha1.CronJobStatus": "io.k8s.api.batch.v2alpha1.CronJobStatus", + "io.k8s.kubernetes.pkg.apis.autoscaling.v1.Scale": "io.k8s.api.autoscaling.v1.Scale", + "io.k8s.kubernetes.pkg.api.v1.ScaleIOVolumeSource": "io.k8s.api.core.v1.ScaleIOVolumeSource", + "io.k8s.kubernetes.pkg.api.v1.PodAntiAffinity": "io.k8s.api.core.v1.PodAntiAffinity", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.PodSecurityPolicySpec": "io.k8s.api.extensions.v1beta1.PodSecurityPolicySpec", + "io.k8s.kubernetes.pkg.apis.settings.v1alpha1.PodPresetList": "io.k8s.api.settings.v1alpha1.PodPresetList", + "io.k8s.kubernetes.pkg.api.v1.NodeAffinity": "io.k8s.api.core.v1.NodeAffinity", + "io.k8s.kubernetes.pkg.apis.apps.v1beta1.DeploymentCondition": "io.k8s.api.apps.v1beta1.DeploymentCondition", + "io.k8s.kubernetes.pkg.api.v1.NodeSpec": "io.k8s.api.core.v1.NodeSpec", + "io.k8s.kubernetes.pkg.apis.apps.v1beta1.StatefulSetStatus": "io.k8s.api.apps.v1beta1.StatefulSetStatus", + "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.RuleWithOperations": "io.k8s.api.admissionregistration.v1alpha1.RuleWithOperations", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IngressStatus": "io.k8s.api.extensions.v1beta1.IngressStatus", + "io.k8s.kubernetes.pkg.api.v1.LimitRangeList": "io.k8s.api.core.v1.LimitRangeList", + "io.k8s.kubernetes.pkg.api.v1.AzureDiskVolumeSource": "io.k8s.api.core.v1.AzureDiskVolumeSource", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ReplicaSetStatus": "io.k8s.api.extensions.v1beta1.ReplicaSetStatus", + "io.k8s.kubernetes.pkg.api.v1.ComponentStatus": "io.k8s.api.core.v1.ComponentStatus", + "io.k8s.kubernetes.pkg.apis.autoscaling.v1.HorizontalPodAutoscaler": "io.k8s.api.autoscaling.v1.HorizontalPodAutoscaler", + "io.k8s.kubernetes.pkg.apis.networking.v1.NetworkPolicy": "io.k8s.api.networking.v1.NetworkPolicy", + "io.k8s.kubernetes.pkg.apis.apps.v1beta1.RollbackConfig": "io.k8s.api.apps.v1beta1.RollbackConfig", + "io.k8s.kubernetes.pkg.api.v1.NodeCondition": "io.k8s.api.core.v1.NodeCondition", + "io.k8s.kubernetes.pkg.api.v1.DownwardAPIProjection": "io.k8s.api.core.v1.DownwardAPIProjection", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.SELinuxStrategyOptions": "io.k8s.api.extensions.v1beta1.SELinuxStrategyOptions", + "io.k8s.kubernetes.pkg.api.v1.NamespaceSpec": "io.k8s.api.core.v1.NamespaceSpec", + "io.k8s.kubernetes.pkg.apis.certificates.v1beta1.CertificateSigningRequestSpec": "io.k8s.api.certificates.v1beta1.CertificateSigningRequestSpec", + "io.k8s.kubernetes.pkg.api.v1.ServiceSpec": "io.k8s.api.core.v1.ServiceSpec", + "io.k8s.kubernetes.pkg.apis.authorization.v1.SubjectAccessReview": "io.k8s.api.authorization.v1.SubjectAccessReview", + "io.k8s.kubernetes.pkg.apis.apps.v1beta1.DeploymentList": "io.k8s.api.apps.v1beta1.DeploymentList", + "io.k8s.kubernetes.pkg.api.v1.Toleration": "io.k8s.api.core.v1.Toleration", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.NetworkPolicyList": "io.k8s.api.extensions.v1beta1.NetworkPolicyList", + "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.PodsMetricSource": "io.k8s.api.autoscaling.v2alpha1.PodsMetricSource", + "io.k8s.kubernetes.pkg.api.v1.EnvFromSource": "io.k8s.api.core.v1.EnvFromSource", + "io.k8s.kubernetes.pkg.apis.autoscaling.v1.ScaleSpec": "io.k8s.api.autoscaling.v1.ScaleSpec", + "io.k8s.kubernetes.pkg.api.v1.PodTemplateList": "io.k8s.api.core.v1.PodTemplateList", + "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.HorizontalPodAutoscalerSpec": "io.k8s.api.autoscaling.v2alpha1.HorizontalPodAutoscalerSpec", + "io.k8s.kubernetes.pkg.api.v1.SecretProjection": "io.k8s.api.core.v1.SecretProjection", + "io.k8s.kubernetes.pkg.api.v1.ResourceFieldSelector": "io.k8s.api.core.v1.ResourceFieldSelector", + "io.k8s.kubernetes.pkg.api.v1.PersistentVolumeSpec": "io.k8s.api.core.v1.PersistentVolumeSpec", + "io.k8s.kubernetes.pkg.api.v1.ConfigMapVolumeSource": "io.k8s.api.core.v1.ConfigMapVolumeSource", + "io.k8s.kubernetes.pkg.apis.autoscaling.v2alpha1.HorizontalPodAutoscalerList": "io.k8s.api.autoscaling.v2alpha1.HorizontalPodAutoscalerList", + "io.k8s.kubernetes.pkg.apis.authentication.v1beta1.TokenReviewStatus": "io.k8s.api.authentication.v1beta1.TokenReviewStatus", + "io.k8s.kubernetes.pkg.apis.networking.v1.NetworkPolicyList": "io.k8s.api.networking.v1.NetworkPolicyList", + "io.k8s.kubernetes.pkg.api.v1.Endpoints": "io.k8s.api.core.v1.Endpoints", + "io.k8s.kubernetes.pkg.api.v1.LimitRangeItem": "io.k8s.api.core.v1.LimitRangeItem", + "io.k8s.kubernetes.pkg.api.v1.ServiceAccount": "io.k8s.api.core.v1.ServiceAccount", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ScaleSpec": "io.k8s.api.extensions.v1beta1.ScaleSpec", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IngressTLS": "io.k8s.api.extensions.v1beta1.IngressTLS", + "io.k8s.kubernetes.pkg.apis.batch.v2alpha1.CronJob": "io.k8s.api.batch.v2alpha1.CronJob", + "io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.Subject": "io.k8s.api.rbac.v1alpha1.Subject", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DaemonSetStatus": "io.k8s.api.extensions.v1beta1.DaemonSetStatus", + "io.k8s.kubernetes.pkg.apis.policy.v1beta1.PodDisruptionBudgetList": "io.k8s.api.policy.v1beta1.PodDisruptionBudgetList", + "io.k8s.kubernetes.pkg.api.v1.VsphereVirtualDiskVolumeSource": "io.k8s.api.core.v1.VsphereVirtualDiskVolumeSource", + "io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.RoleRef": "io.k8s.api.rbac.v1alpha1.RoleRef", + "io.k8s.kubernetes.pkg.api.v1.PortworxVolumeSource": "io.k8s.api.core.v1.PortworxVolumeSource", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ReplicaSetList": "io.k8s.api.extensions.v1beta1.ReplicaSetList", + "io.k8s.kubernetes.pkg.api.v1.VolumeProjection": "io.k8s.api.core.v1.VolumeProjection", + "io.k8s.kubernetes.pkg.apis.storage.v1beta1.StorageClass": "io.k8s.api.storage.v1beta1.StorageClass", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ReplicaSet": "io.k8s.api.extensions.v1beta1.ReplicaSet", + "io.k8s.kubernetes.pkg.apis.apps.v1beta1.DeploymentRollback": "io.k8s.api.apps.v1beta1.DeploymentRollback", + "io.k8s.kubernetes.pkg.apis.rbac.v1alpha1.RoleBinding": "io.k8s.api.rbac.v1alpha1.RoleBinding", + "io.k8s.kubernetes.pkg.api.v1.AzureFileVolumeSource": "io.k8s.api.core.v1.AzureFileVolumeSource", + "io.k8s.kubernetes.pkg.apis.policy.v1beta1.PodDisruptionBudgetStatus": "io.k8s.api.policy.v1beta1.PodDisruptionBudgetStatus", + "io.k8s.kubernetes.pkg.apis.authentication.v1beta1.TokenReviewSpec": "io.k8s.api.authentication.v1beta1.TokenReviewSpec", + "io.k8s.kubernetes.pkg.api.v1.EndpointsList": "io.k8s.api.core.v1.EndpointsList", + "io.k8s.kubernetes.pkg.api.v1.ConfigMapEnvSource": "io.k8s.api.core.v1.ConfigMapEnvSource", + "io.k8s.kubernetes.pkg.apis.batch.v2alpha1.JobTemplateSpec": "io.k8s.api.batch.v2alpha1.JobTemplateSpec", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DaemonSetUpdateStrategy": "io.k8s.api.extensions.v1beta1.DaemonSetUpdateStrategy", + "io.k8s.kubernetes.pkg.apis.authorization.v1beta1.SubjectAccessReviewSpec": "io.k8s.api.authorization.v1beta1.SubjectAccessReviewSpec", + "io.k8s.kubernetes.pkg.api.v1.LocalVolumeSource": "io.k8s.api.core.v1.LocalVolumeSource", + "io.k8s.kubernetes.pkg.api.v1.ContainerState": "io.k8s.api.core.v1.ContainerState", + "io.k8s.kubernetes.pkg.api.v1.Service": "io.k8s.api.core.v1.Service", + "io.k8s.kubernetes.pkg.api.v1.ExecAction": "io.k8s.api.core.v1.ExecAction", + "io.k8s.kubernetes.pkg.api.v1.Taint": "io.k8s.api.core.v1.Taint", + "io.k8s.kubernetes.pkg.apis.rbac.v1beta1.Subject": "io.k8s.api.rbac.v1beta1.Subject", + "io.k8s.kubernetes.pkg.apis.authorization.v1beta1.SubjectAccessReviewStatus": "io.k8s.api.authorization.v1beta1.SubjectAccessReviewStatus", + "io.k8s.kubernetes.pkg.apis.rbac.v1beta1.ClusterRoleBindingList": "io.k8s.api.rbac.v1beta1.ClusterRoleBindingList", + "io.k8s.kubernetes.pkg.api.v1.DownwardAPIVolumeSource": "io.k8s.api.core.v1.DownwardAPIVolumeSource", + "io.k8s.kubernetes.pkg.apis.batch.v1.JobStatus": "io.k8s.api.batch.v1.JobStatus", + "io.k8s.kubernetes.pkg.api.v1.ResourceQuotaStatus": "io.k8s.api.core.v1.ResourceQuotaStatus", + "io.k8s.kubernetes.pkg.api.v1.PersistentVolumeStatus": "io.k8s.api.core.v1.PersistentVolumeStatus", + "io.k8s.kubernetes.pkg.api.v1.PersistentVolumeClaim": "io.k8s.api.core.v1.PersistentVolumeClaim", + "io.k8s.kubernetes.pkg.api.v1.NodeDaemonEndpoints": "io.k8s.api.core.v1.NodeDaemonEndpoints", + "io.k8s.kubernetes.pkg.api.v1.EnvVar": "io.k8s.api.core.v1.EnvVar", + "io.k8s.kubernetes.pkg.api.v1.SecretVolumeSource": "io.k8s.api.core.v1.SecretVolumeSource", + "io.k8s.kubernetes.pkg.api.v1.EnvVarSource": "io.k8s.api.core.v1.EnvVarSource", + "io.k8s.kubernetes.pkg.apis.apps.v1beta1.RollingUpdateStatefulSetStrategy": "io.k8s.api.apps.v1beta1.RollingUpdateStatefulSetStrategy", + "io.k8s.kubernetes.pkg.apis.rbac.v1beta1.ClusterRole": "io.k8s.api.rbac.v1beta1.ClusterRole", + "io.k8s.kubernetes.pkg.apis.admissionregistration.v1alpha1.Initializer": "io.k8s.api.admissionregistration.v1alpha1.Initializer", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DeploymentStrategy": "io.k8s.api.extensions.v1beta1.DeploymentStrategy", + "io.k8s.kubernetes.pkg.apis.apps.v1beta1.ScaleSpec": "io.k8s.api.apps.v1beta1.ScaleSpec", + "io.k8s.kubernetes.pkg.apis.settings.v1alpha1.PodPreset": "io.k8s.api.settings.v1alpha1.PodPreset", + "io.k8s.kubernetes.pkg.api.v1.Probe": "io.k8s.api.core.v1.Probe", + "io.k8s.kubernetes.pkg.api.v1.NamespaceList": "io.k8s.api.core.v1.NamespaceList", + "io.k8s.kubernetes.pkg.api.v1.QuobyteVolumeSource": "io.k8s.api.core.v1.QuobyteVolumeSource", + "io.k8s.kubernetes.pkg.api.v1.NodeList": "io.k8s.api.core.v1.NodeList", + "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.RollingUpdateDaemonSet": "io.k8s.api.extensions.v1beta1.RollingUpdateDaemonSet", + "io.k8s.kubernetes.pkg.api.v1.LimitRange": "io.k8s.api.core.v1.LimitRange", } for k, v := range compatibilityMap { diff --git a/cmd/kube-apiserver/app/testing/BUILD b/cmd/kube-apiserver/app/testing/BUILD index 0ff84b4697c..81763ecbaaf 100644 --- a/cmd/kube-apiserver/app/testing/BUILD +++ b/cmd/kube-apiserver/app/testing/BUILD @@ -3,32 +3,6 @@ package(default_visibility = ["//visibility:public"]) load( "@io_bazel_rules_go//go:def.bzl", "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = ["server_test.go"], - importpath = "k8s.io/kubernetes/cmd/kube-apiserver/app/testing", - library = ":go_default_library", - deps = [ - "//vendor/k8s.io/api/admissionregistration/v1alpha1:go_default_library", - "//vendor/k8s.io/api/apps/v1beta1:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/api/networking/v1:go_default_library", - "//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library", - "//vendor/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/apiserver/pkg/features:go_default_library", - "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", - "//vendor/k8s.io/apiserver/pkg/util/feature/testing:go_default_library", - "//vendor/k8s.io/client-go/dynamic:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - ], ) go_library( @@ -38,9 +12,10 @@ go_library( deps = [ "//cmd/kube-apiserver/app:go_default_library", "//cmd/kube-apiserver/app/options:go_default_library", - "//pkg/api:go_default_library", + "//vendor/github.com/spf13/pflag:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library", + "//vendor/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", ], diff --git a/cmd/kube-apiserver/app/testing/server_test.go b/cmd/kube-apiserver/app/testing/server_test.go deleted file mode 100644 index 575126426a6..00000000000 --- a/cmd/kube-apiserver/app/testing/server_test.go +++ /dev/null @@ -1,391 +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 testing - -import ( - "encoding/json" - "fmt" - "testing" - "time" - - admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1" - appsv1beta1 "k8s.io/api/apps/v1beta1" - corev1 "k8s.io/api/core/v1" - networkingv1 "k8s.io/api/networking/v1" - apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/apiserver/pkg/features" - utilfeature "k8s.io/apiserver/pkg/util/feature" - utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/kubernetes" -) - -func TestRun(t *testing.T) { - config, tearDown := StartTestServerOrDie(t) - defer tearDown() - - client, err := kubernetes.NewForConfig(config) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - - // test whether the server is really healthy after /healthz told us so - t.Logf("Creating Deployment directly after being healthy") - var replicas int32 = 1 - _, err = client.AppsV1beta1().Deployments("default").Create(&appsv1beta1.Deployment{ - TypeMeta: metav1.TypeMeta{ - Kind: "Deployment", - APIVersion: "apps/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "test", - }, - Spec: appsv1beta1.DeploymentSpec{ - Replicas: &replicas, - Strategy: appsv1beta1.DeploymentStrategy{ - Type: appsv1beta1.RollingUpdateDeploymentStrategyType, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{"foo": "bar"}, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "foo", - Image: "foo", - }, - }, - }, - }, - }, - }) - if err != nil { - t.Fatalf("Failed to create deployment: %v", err) - } -} - -func TestCRDShadowGroup(t *testing.T) { - config, tearDown := StartTestServerOrDie(t) - defer tearDown() - - kubeclient, err := kubernetes.NewForConfig(config) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - apiextensionsclient, err := apiextensionsclientset.NewForConfig(config) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - t.Logf("Creating a NetworkPolicy") - nwPolicy, err := kubeclient.NetworkingV1().NetworkPolicies("default").Create(&networkingv1.NetworkPolicy{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: networkingv1.NetworkPolicySpec{ - PodSelector: metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, - Ingress: []networkingv1.NetworkPolicyIngressRule{}, - }, - }) - if err != nil { - t.Fatalf("Failed to create NetworkPolicy: %v", err) - } - - t.Logf("Trying to shadow networking group") - crd := &apiextensionsv1beta1.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foos." + networkingv1.GroupName, - }, - Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{ - Group: networkingv1.GroupName, - Version: networkingv1.SchemeGroupVersion.Version, - Scope: apiextensionsv1beta1.ClusterScoped, - Names: apiextensionsv1beta1.CustomResourceDefinitionNames{ - Plural: "foos", - Kind: "Foo", - }, - }, - } - if _, err = apiextensionsclient.ApiextensionsV1beta1().CustomResourceDefinitions().Create(crd); err != nil { - t.Fatalf("Failed to create networking group CRD: %v", err) - } - if err := waitForEstablishedCRD(apiextensionsclient, crd.Name); err != nil { - t.Fatalf("Failed to establish networking group CRD: %v", err) - } - // wait to give aggregator time to update - time.Sleep(2 * time.Second) - - t.Logf("Checking that we still see the NetworkPolicy") - _, err = kubeclient.NetworkingV1().NetworkPolicies(nwPolicy.Namespace).Get(nwPolicy.Name, metav1.GetOptions{}) - if err != nil { - t.Errorf("Failed to get NetworkPolocy: %v", err) - } - - t.Logf("Checking that crd resource does not show up in networking group") - found, err := crdExistsInDiscovery(apiextensionsclient, crd) - if err != nil { - t.Fatalf("unexpected discovery error: %v", err) - } - if found { - t.Errorf("CRD resource shows up in discovery, but shouldn't.") - } -} - -func TestCRD(t *testing.T) { - defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.Initializers, true)() - - config, tearDown := StartTestServerOrDie(t) - defer tearDown() - - kubeclient, err := kubernetes.NewForConfig(config) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - apiextensionsclient, err := apiextensionsclientset.NewForConfig(config) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - t.Logf("Trying to create a custom resource without conflict") - crd := &apiextensionsv1beta1.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foos.cr.bar.com", - }, - Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{ - Group: "cr.bar.com", - Version: "v1", - Scope: apiextensionsv1beta1.NamespaceScoped, - Names: apiextensionsv1beta1.CustomResourceDefinitionNames{ - Plural: "foos", - Kind: "Foo", - }, - }, - } - if _, err = apiextensionsclient.ApiextensionsV1beta1().CustomResourceDefinitions().Create(crd); err != nil { - t.Fatalf("Failed to create foos.cr.bar.com CRD; %v", err) - } - if err := waitForEstablishedCRD(apiextensionsclient, crd.Name); err != nil { - t.Fatalf("Failed to establish foos.cr.bar.com CRD: %v", err) - } - if err := wait.PollImmediate(500*time.Millisecond, 30*time.Second, func() (bool, error) { - return crdExistsInDiscovery(apiextensionsclient, crd) - }); err != nil { - t.Fatalf("Failed to see foos.cr.bar.com in discovery: %v", err) - } - - t.Logf("Trying to access foos.cr.bar.com with dynamic client") - barComConfig := *config - barComConfig.GroupVersion = &schema.GroupVersion{Group: "cr.bar.com", Version: "v1"} - barComConfig.APIPath = "/apis" - barComClient, err := dynamic.NewClient(&barComConfig) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - _, err = barComClient.Resource(&metav1.APIResource{Name: "foos", Namespaced: true}, "default").List(metav1.ListOptions{}) - if err != nil { - t.Errorf("Failed to list foos.cr.bar.com instances: %v", err) - } - - t.Logf("Creating InitializerConfiguration") - _, err = kubeclient.AdmissionregistrationV1alpha1().InitializerConfigurations().Create(&admissionregistrationv1alpha1.InitializerConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foos.cr.bar.com", - }, - Initializers: []admissionregistrationv1alpha1.Initializer{ - { - Name: "cr.bar.com", - Rules: []admissionregistrationv1alpha1.Rule{ - { - APIGroups: []string{"cr.bar.com"}, - APIVersions: []string{"*"}, - Resources: []string{"*"}, - }, - }, - }, - }, - }) - if err != nil { - t.Fatalf("Failed to create InitializerConfiguration: %v", err) - } - - // TODO DO NOT MERGE THIS - time.Sleep(5 * time.Second) - - t.Logf("Creating Foo instance") - foo := &Foo{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "cr.bar.com/v1", - Kind: "Foo", - }, - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - } - unstructuredFoo, err := unstructuredFoo(foo) - if err != nil { - t.Fatalf("Unable to create Foo: %v", err) - } - createErr := make(chan error, 1) - go func() { - _, err = barComClient.Resource(&metav1.APIResource{Name: "foos", Namespaced: true}, "default").Create(unstructuredFoo) - t.Logf("Foo instance create returned: %v", err) - if err != nil { - createErr <- err - } - }() - - err = wait.PollImmediate(100*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) { - select { - case createErr := <-createErr: - return true, createErr - default: - } - - t.Logf("Checking that Foo instance is visible with IncludeUninitialized=true") - _, err := barComClient.Resource(&metav1.APIResource{Name: "foos", Namespaced: true}, "default").Get(foo.ObjectMeta.Name, metav1.GetOptions{ - IncludeUninitialized: true, - }) - switch { - case err == nil: - return true, nil - case errors.IsNotFound(err): - return false, nil - default: - return false, err - } - }) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - t.Logf("Removing initializer from Foo instance") - success := false - for i := 0; i < 10; i++ { - // would love to replace the following with a patch, but removing strings from the intitializer array - // is not what JSON (Merge) patch authors had in mind. - fooUnstructured, err := barComClient.Resource(&metav1.APIResource{Name: "foos", Namespaced: true}, "default").Get(foo.ObjectMeta.Name, metav1.GetOptions{ - IncludeUninitialized: true, - }) - if err != nil { - t.Fatalf("Error getting Foo instance: %v", err) - } - bs, _ := fooUnstructured.MarshalJSON() - t.Logf("Got Foo instance: %v", string(bs)) - foo := Foo{} - if err := json.Unmarshal(bs, &foo); err != nil { - t.Fatalf("Error parsing Foo instance: %v", err) - } - - // remove initialize - if foo.ObjectMeta.Initializers == nil { - t.Fatalf("Expected initializers to be set in Foo instance") - } - found := false - for i := range foo.ObjectMeta.Initializers.Pending { - if foo.ObjectMeta.Initializers.Pending[i].Name == "cr.bar.com" { - foo.ObjectMeta.Initializers.Pending = append(foo.ObjectMeta.Initializers.Pending[:i], foo.ObjectMeta.Initializers.Pending[i+1:]...) - found = true - break - } - } - if !found { - t.Fatalf("Expected cr.bar.com as initializer on Foo instance") - } - if len(foo.ObjectMeta.Initializers.Pending) == 0 && foo.ObjectMeta.Initializers.Result == nil { - foo.ObjectMeta.Initializers = nil - } - bs, err = json.Marshal(&foo) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - fooUnstructured.UnmarshalJSON(bs) - - _, err = barComClient.Resource(&metav1.APIResource{Name: "foos", Namespaced: true}, "default").Update(fooUnstructured) - if err != nil && !errors.IsConflict(err) { - t.Fatalf("Failed to update Foo instance: %v", err) - } else if err == nil { - success = true - break - } - } - if !success { - t.Fatalf("Failed to remove initializer from Foo object") - } - - t.Logf("Checking that Foo instance is visible after removing the initializer") - if _, err := barComClient.Resource(&metav1.APIResource{Name: "foos", Namespaced: true}, "default").Get(foo.ObjectMeta.Name, metav1.GetOptions{}); err != nil { - t.Errorf("Unexpected error: %v", err) - } -} - -type Foo struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` -} - -func unstructuredFoo(foo *Foo) (*unstructured.Unstructured, error) { - bs, err := json.Marshal(foo) - if err != nil { - return nil, err - } - ret := &unstructured.Unstructured{} - if err = ret.UnmarshalJSON(bs); err != nil { - return nil, err - } - return ret, nil -} - -func waitForEstablishedCRD(client apiextensionsclientset.Interface, name string) error { - return wait.PollImmediate(500*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) { - crd, err := client.ApiextensionsV1beta1().CustomResourceDefinitions().Get(name, metav1.GetOptions{}) - if err != nil { - return false, err - } - for _, cond := range crd.Status.Conditions { - switch cond.Type { - case apiextensionsv1beta1.Established: - if cond.Status == apiextensionsv1beta1.ConditionTrue { - return true, err - } - case apiextensionsv1beta1.NamesAccepted: - if cond.Status == apiextensionsv1beta1.ConditionFalse { - fmt.Printf("Name conflict: %v\n", cond.Reason) - } - } - } - return false, nil - }) -} - -func crdExistsInDiscovery(client apiextensionsclientset.Interface, crd *apiextensionsv1beta1.CustomResourceDefinition) (bool, error) { - resourceList, err := client.Discovery().ServerResourcesForGroupVersion(crd.Spec.Group + "/" + crd.Spec.Version) - if err != nil { - return false, nil - } - for _, resource := range resourceList.APIResources { - if resource.Name == crd.Spec.Names.Plural { - return true, nil - } - } - return false, nil -} diff --git a/cmd/kube-apiserver/app/testing/testserver.go b/cmd/kube-apiserver/app/testing/testserver.go index abbbb979435..469111185e5 100644 --- a/cmd/kube-apiserver/app/testing/testserver.go +++ b/cmd/kube-apiserver/app/testing/testserver.go @@ -21,91 +21,101 @@ import ( "io/ioutil" "net" "os" - "strings" "testing" "time" + pflag "github.com/spf13/pflag" + "k8s.io/apimachinery/pkg/util/wait" - etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" + "k8s.io/apiserver/pkg/registry/generic/registry" + "k8s.io/apiserver/pkg/storage/storagebackend" "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" "k8s.io/kubernetes/cmd/kube-apiserver/app" "k8s.io/kubernetes/cmd/kube-apiserver/app/options" - "k8s.io/kubernetes/pkg/api" ) // TearDownFunc is to be called to tear down a test server. type TearDownFunc func() -// StartTestServer starts a etcd server and kube-apiserver. A rest client config and a tear-down func -// are returned. +// TestServer return values supplied by kube-test-ApiServer +type TestServer struct { + ClientConfig *restclient.Config // Rest client config + ServerOpts *options.ServerRunOptions // ServerOpts + TearDownFn TearDownFunc // TearDown function + TmpDir string // Temp Dir used, by the apiserver +} + +// StartTestServer starts a etcd server and kube-apiserver. A rest client config and a tear-down func, +// and location of the tmpdir are returned. // // Note: we return a tear-down func instead of a stop channel because the later will leak temporariy // files that becaues Golang testing's call to os.Exit will not give a stop channel go routine // enough time to remove temporariy files. -func StartTestServer(t *testing.T) (result *restclient.Config, tearDownForCaller TearDownFunc, err error) { - var tmpDir string - var etcdServer *etcdtesting.EtcdTestServer +func StartTestServer(t *testing.T, customFlags []string, storageConfig *storagebackend.Config) (result TestServer, err error) { + + // TODO : Remove TrackStorageCleanup below when PR + // https://github.com/kubernetes/kubernetes/pull/50690 + // merges as that shuts down storage properly + registry.TrackStorageCleanup() + stopCh := make(chan struct{}) tearDown := func() { + registry.CleanupStorage() close(stopCh) - if etcdServer != nil { - etcdServer.Terminate(t) - } - if len(tmpDir) != 0 { - os.RemoveAll(tmpDir) + if len(result.TmpDir) != 0 { + os.RemoveAll(result.TmpDir) } } defer func() { - if tearDownForCaller == nil { + if result.TearDownFn == nil { tearDown() } }() - t.Logf("Starting etcd...") - etcdServer, storageConfig := etcdtesting.NewUnsecuredEtcd3TestClientServer(t, api.Scheme) - - tmpDir, err = ioutil.TempDir("", "kubernetes-kube-apiserver") + result.TmpDir, err = ioutil.TempDir("", "kubernetes-kube-apiserver") if err != nil { - return nil, nil, fmt.Errorf("Failed to create temp dir: %v", err) + return result, fmt.Errorf("failed to create temp dir: %v", err) } + fs := pflag.NewFlagSet("test", pflag.PanicOnError) + s := options.NewServerRunOptions() + s.AddFlags(fs) + s.InsecureServing.BindPort = 0 - s.SecureServing.BindPort = freePort() - s.SecureServing.ServerCert.CertDirectory = tmpDir + + s.SecureServing.Listener, s.SecureServing.BindPort, err = createListenerOnFreePort() + if err != nil { + return result, fmt.Errorf("failed to create listener: %v", err) + } + s.SecureServing.ServerCert.CertDirectory = result.TmpDir s.ServiceClusterIPRange.IP = net.IPv4(10, 0, 0, 0) s.ServiceClusterIPRange.Mask = net.CIDRMask(16, 32) s.Etcd.StorageConfig = *storageConfig - s.Etcd.DefaultStorageMediaType = "application/json" - s.Admission.PluginNames = strings.Split("Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds", ",") s.APIEnablement.RuntimeConfig.Set("api/all=true") - t.Logf("Starting kube-apiserver...") - runErrCh := make(chan error, 1) + fs.Parse(customFlags) + + t.Logf("Starting kube-apiserver on port %d...", s.SecureServing.BindPort) server, err := app.CreateServerChain(s, stopCh) if err != nil { - return nil, nil, fmt.Errorf("Failed to create server chain: %v", err) + return result, fmt.Errorf("failed to create server chain: %v", err) + } go func(stopCh <-chan struct{}) { if err := server.PrepareRun().Run(stopCh); err != nil { - t.Logf("kube-apiserver exited uncleanly: %v", err) - runErrCh <- err + t.Errorf("kube-apiserver failed run: %v", err) } }(stopCh) t.Logf("Waiting for /healthz to be ok...") + client, err := kubernetes.NewForConfig(server.LoopbackClientConfig) if err != nil { - return nil, nil, fmt.Errorf("Failed to create a client: %v", err) + return result, fmt.Errorf("failed to create a client: %v", err) } err = wait.Poll(100*time.Millisecond, 30*time.Second, func() (bool, error) { - select { - case err := <-runErrCh: - return false, err - default: - } - result := client.CoreV1().RESTClient().Get().AbsPath("/healthz").Do() status := 0 result.StatusCode(&status) @@ -115,50 +125,41 @@ func StartTestServer(t *testing.T) (result *restclient.Config, tearDownForCaller return false, nil }) if err != nil { - return nil, nil, fmt.Errorf("Failed to wait for /healthz to return ok: %v", err) + return result, fmt.Errorf("failed to wait for /healthz to return ok: %v", err) } // from here the caller must call tearDown - return server.LoopbackClientConfig, tearDown, nil + result.ClientConfig = server.LoopbackClientConfig + result.ServerOpts = s + result.TearDownFn = tearDown + + return result, nil } -// StartTestServerOrDie calls StartTestServer with up to 5 retries on bind error and dies with -// t.Fatal if it does not succeed. -func StartTestServerOrDie(t *testing.T) (*restclient.Config, TearDownFunc) { - // retry test because the bind might fail due to a race with another process - // binding to the port. We cannot listen to :0 (then the kernel would give us - // a port which is free for sure), so we need this workaround. +// StartTestServerOrDie calls StartTestServer t.Fatal if it does not succeed. +func StartTestServerOrDie(t *testing.T, flags []string, storageConfig *storagebackend.Config) *TestServer { - var err error - - for retry := 0; retry < 5 && !t.Failed(); retry++ { - var config *restclient.Config - var td TearDownFunc - - config, td, err = StartTestServer(t) - if err == nil { - return config, td - } - if err != nil && !strings.Contains(err.Error(), "bind") { - break - } - t.Logf("Bind error, retrying...") + result, err := StartTestServer(t, flags, storageConfig) + if err == nil { + return &result } - t.Fatalf("Failed to launch server: %v", err) - return nil, nil + t.Fatalf("failed to launch server: %v", err) + return nil } -func freePort() int { - addr, err := net.ResolveTCPAddr("tcp", "localhost:0") +func createListenerOnFreePort() (net.Listener, int, error) { + ln, err := net.Listen("tcp", ":0") if err != nil { - panic(err) + return nil, 0, err } - l, err := net.ListenTCP("tcp", addr) - if err != nil { - panic(err) + // get port + tcpAddr, ok := ln.Addr().(*net.TCPAddr) + if !ok { + ln.Close() + return nil, 0, fmt.Errorf("invalid listen address: %q", ln.Addr().String()) } - defer l.Close() - return l.Addr().(*net.TCPAddr).Port + + return ln, tcpAddr.Port, nil } diff --git a/cmd/kube-controller-manager/BUILD b/cmd/kube-controller-manager/BUILD index 68bf1d3b858..d5152c0e1cf 100644 --- a/cmd/kube-controller-manager/BUILD +++ b/cmd/kube-controller-manager/BUILD @@ -9,14 +9,9 @@ load("//pkg/version:def.bzl", "version_x_defs") go_binary( name = "kube-controller-manager", - gc_linkopts = [ - "-linkmode", - "external", - "-extldflags", - "-static", - ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kube-controller-manager", - library = ":go_default_library", + pure = "on", x_defs = version_x_defs(), ) @@ -33,7 +28,6 @@ go_library( "//pkg/version/prometheus:go_default_library", "//pkg/version/verflag:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", - "//vendor/k8s.io/apiserver/pkg/server/healthz:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/logs:go_default_library", ], diff --git a/cmd/kube-controller-manager/app/BUILD b/cmd/kube-controller-manager/app/BUILD index 97579128b2f..d6940c30d3b 100644 --- a/cmd/kube-controller-manager/app/BUILD +++ b/cmd/kube-controller-manager/app/BUILD @@ -20,12 +20,13 @@ go_library( "import_known_versions.go", "plugins.go", "policy.go", + "rbac.go", ], importpath = "k8s.io/kubernetes/cmd/kube-controller-manager/app", deps = [ + "//cmd/controller-manager/app/options:go_default_library", "//cmd/kube-controller-manager/app/options:go_default_library", - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/apps/install:go_default_library", "//pkg/apis/authentication/install:go_default_library", "//pkg/apis/authorization/install:go_default_library", @@ -33,6 +34,8 @@ go_library( "//pkg/apis/batch/install:go_default_library", "//pkg/apis/certificates/install:go_default_library", "//pkg/apis/componentconfig:go_default_library", + "//pkg/apis/core/install:go_default_library", + "//pkg/apis/events/install:go_default_library", "//pkg/apis/extensions/install:go_default_library", "//pkg/apis/policy/install:go_default_library", "//pkg/apis/rbac/install:go_default_library", @@ -41,17 +44,12 @@ go_library( "//pkg/apis/storage/install:go_default_library", "//pkg/cloudprovider:go_default_library", "//pkg/cloudprovider/providers:go_default_library", - "//pkg/cloudprovider/providers/aws:go_default_library", - "//pkg/cloudprovider/providers/azure:go_default_library", - "//pkg/cloudprovider/providers/gce:go_default_library", - "//pkg/cloudprovider/providers/openstack:go_default_library", - "//pkg/cloudprovider/providers/photon:go_default_library", - "//pkg/cloudprovider/providers/vsphere:go_default_library", "//pkg/controller:go_default_library", "//pkg/controller/bootstrap:go_default_library", "//pkg/controller/certificates/approver:go_default_library", "//pkg/controller/certificates/cleaner:go_default_library", "//pkg/controller/certificates/signer:go_default_library", + "//pkg/controller/clusterroleaggregation:go_default_library", "//pkg/controller/cronjob:go_default_library", "//pkg/controller/daemon:go_default_library", "//pkg/controller/deployment:go_default_library", @@ -60,8 +58,9 @@ go_library( "//pkg/controller/garbagecollector:go_default_library", "//pkg/controller/job:go_default_library", "//pkg/controller/namespace:go_default_library", - "//pkg/controller/node:go_default_library", - "//pkg/controller/node/ipam:go_default_library", + "//pkg/controller/nodeipam:go_default_library", + "//pkg/controller/nodeipam/ipam:go_default_library", + "//pkg/controller/nodelifecycle:go_default_library", "//pkg/controller/podautoscaler:go_default_library", "//pkg/controller/podautoscaler/metrics:go_default_library", "//pkg/controller/podgc:go_default_library", @@ -76,7 +75,9 @@ go_library( "//pkg/controller/volume/attachdetach:go_default_library", "//pkg/controller/volume/expand:go_default_library", "//pkg/controller/volume/persistentvolume:go_default_library", + "//pkg/controller/volume/pvcprotection:go_default_library", "//pkg/features:go_default_library", + "//pkg/quota/generic:go_default_library", "//pkg/quota/install:go_default_library", "//pkg/serviceaccount:go_default_library", "//pkg/util/configz:go_default_library", @@ -87,6 +88,7 @@ go_library( "//pkg/volume/azure_dd:go_default_library", "//pkg/volume/azure_file:go_default_library", "//pkg/volume/cinder:go_default_library", + "//pkg/volume/csi:go_default_library", "//pkg/volume/fc:go_default_library", "//pkg/volume/flexvolume:go_default_library", "//pkg/volume/flocker:go_default_library", @@ -123,6 +125,7 @@ go_library( "//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", + "//vendor/k8s.io/client-go/scale:go_default_library", "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", "//vendor/k8s.io/client-go/tools/leaderelection:go_default_library", "//vendor/k8s.io/client-go/tools/leaderelection/resourcelock:go_default_library", @@ -152,8 +155,8 @@ filegroup( go_test( name = "go_default_test", srcs = ["controller_manager_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kube-controller-manager/app", - library = ":go_default_library", deps = [ "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", diff --git a/cmd/kube-controller-manager/app/autoscaling.go b/cmd/kube-controller-manager/app/autoscaling.go index 5af911d7adc..43c1cd1ab00 100644 --- a/cmd/kube-controller-manager/app/autoscaling.go +++ b/cmd/kube-controller-manager/app/autoscaling.go @@ -21,7 +21,12 @@ limitations under the License. package app import ( + apimeta "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/discovery" + discocache "k8s.io/client-go/discovery/cached" // Saturday Night Fever + "k8s.io/client-go/dynamic" + "k8s.io/client-go/scale" "k8s.io/kubernetes/pkg/controller/podautoscaler" "k8s.io/kubernetes/pkg/controller/podautoscaler/metrics" resourceclient "k8s.io/metrics/pkg/client/clientset_generated/clientset/typed/metrics/v1beta1" @@ -63,16 +68,33 @@ func startHPAControllerWithLegacyClient(ctx ControllerContext) (bool, error) { } func startHPAControllerWithMetricsClient(ctx ControllerContext, metricsClient metrics.MetricsClient) (bool, error) { + hpaClientGoClient := ctx.ClientBuilder.ClientGoClientOrDie("horizontal-pod-autoscaler") hpaClient := ctx.ClientBuilder.ClientOrDie("horizontal-pod-autoscaler") + hpaClientConfig := ctx.ClientBuilder.ConfigOrDie("horizontal-pod-autoscaler") + + // TODO: we need something like deferred discovery REST mapper that calls invalidate + // on cache misses. + cachedDiscovery := discocache.NewMemCacheClient(hpaClientGoClient.Discovery()) + restMapper := discovery.NewDeferredDiscoveryRESTMapper(cachedDiscovery, apimeta.InterfacesForUnstructured) + restMapper.Reset() + // we don't use cached discovery because DiscoveryScaleKindResolver does its own caching, + // so we want to re-fetch every time when we actually ask for it + scaleKindResolver := scale.NewDiscoveryScaleKindResolver(hpaClientGoClient.Discovery()) + scaleClient, err := scale.NewForConfig(hpaClientConfig, restMapper, dynamic.LegacyAPIPathResolverFunc, scaleKindResolver) + if err != nil { + return false, err + } + replicaCalc := podautoscaler.NewReplicaCalculator( metricsClient, - hpaClient.Core(), + hpaClient.CoreV1(), ctx.Options.HorizontalPodAutoscalerTolerance, ) go podautoscaler.NewHorizontalController( - ctx.ClientBuilder.ClientGoClientOrDie("horizontal-pod-autoscaler").Core(), - hpaClient.Extensions(), - hpaClient.Autoscaling(), + hpaClientGoClient.CoreV1(), + scaleClient, + hpaClient.AutoscalingV1(), + restMapper, replicaCalc, ctx.InformerFactory.Autoscaling().V1().HorizontalPodAutoscalers(), ctx.Options.HorizontalPodAutoscalerSyncPeriod.Duration, diff --git a/cmd/kube-controller-manager/app/batch.go b/cmd/kube-controller-manager/app/batch.go index 0333206144d..b60d7c149e5 100644 --- a/cmd/kube-controller-manager/app/batch.go +++ b/cmd/kube-controller-manager/app/batch.go @@ -21,6 +21,8 @@ limitations under the License. package app import ( + "fmt" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/kubernetes/pkg/controller/cronjob" "k8s.io/kubernetes/pkg/controller/job" @@ -42,8 +44,12 @@ func startCronJobController(ctx ControllerContext) (bool, error) { if !ctx.AvailableResources[schema.GroupVersionResource{Group: "batch", Version: "v1beta1", Resource: "cronjobs"}] { return false, nil } - go cronjob.NewCronJobController( + cjc, err := cronjob.NewCronJobController( ctx.ClientBuilder.ClientOrDie("cronjob-controller"), - ).Run(ctx.Stop) + ) + if err != nil { + return true, fmt.Errorf("error creating CronJob controller: %v", err) + } + go cjc.Run(ctx.Stop) return true, nil } diff --git a/cmd/kube-controller-manager/app/bootstrap.go b/cmd/kube-controller-manager/app/bootstrap.go index 05ff566fd88..046070ecb27 100644 --- a/cmd/kube-controller-manager/app/bootstrap.go +++ b/cmd/kube-controller-manager/app/bootstrap.go @@ -16,20 +16,32 @@ limitations under the License. package app -import "k8s.io/kubernetes/pkg/controller/bootstrap" +import ( + "fmt" + + "k8s.io/kubernetes/pkg/controller/bootstrap" +) func startBootstrapSignerController(ctx ControllerContext) (bool, error) { - go bootstrap.NewBootstrapSigner( + bsc, err := bootstrap.NewBootstrapSigner( ctx.ClientBuilder.ClientGoClientOrDie("bootstrap-signer"), bootstrap.DefaultBootstrapSignerOptions(), - ).Run(ctx.Stop) + ) + if err != nil { + return true, fmt.Errorf("error creating BootstrapSigner controller: %v", err) + } + go bsc.Run(ctx.Stop) return true, nil } func startTokenCleanerController(ctx ControllerContext) (bool, error) { - go bootstrap.NewTokenCleaner( + tcc, err := bootstrap.NewTokenCleaner( ctx.ClientBuilder.ClientGoClientOrDie("token-cleaner"), bootstrap.DefaultTokenCleanerOptions(), - ).Run(ctx.Stop) + ) + if err != nil { + return true, fmt.Errorf("error creating TokenCleaner controller: %v", err) + } + go tcc.Run(ctx.Stop) return true, nil } diff --git a/cmd/kube-controller-manager/app/certificates.go b/cmd/kube-controller-manager/app/certificates.go index b3e4e8499d2..6c1531cae5b 100644 --- a/cmd/kube-controller-manager/app/certificates.go +++ b/cmd/kube-controller-manager/app/certificates.go @@ -21,9 +21,13 @@ limitations under the License. package app import ( + "fmt" + "os" + "github.com/golang/glog" "k8s.io/apimachinery/pkg/runtime/schema" + cmoptions "k8s.io/kubernetes/cmd/controller-manager/app/options" "k8s.io/kubernetes/pkg/controller/certificates/approver" "k8s.io/kubernetes/pkg/controller/certificates/cleaner" "k8s.io/kubernetes/pkg/controller/certificates/signer" @@ -36,6 +40,45 @@ func startCSRSigningController(ctx ControllerContext) (bool, error) { if ctx.Options.ClusterSigningCertFile == "" || ctx.Options.ClusterSigningKeyFile == "" { return false, nil } + + // Deprecation warning for old defaults. + // + // * If the signing cert and key are the default paths but the files + // exist, warn that the paths need to be specified explicitly in a + // later release and the defaults will be removed. We don't expect this + // to be the case. + // + // * If the signing cert and key are default paths but the files don't exist, + // bail out of startController without logging. + var keyFileExists, keyUsesDefault, certFileExists, certUsesDefault bool + + _, err := os.Stat(ctx.Options.ClusterSigningCertFile) + certFileExists = !os.IsNotExist(err) + + certUsesDefault = (ctx.Options.ClusterSigningCertFile == cmoptions.DefaultClusterSigningCertFile) + + _, err = os.Stat(ctx.Options.ClusterSigningKeyFile) + keyFileExists = !os.IsNotExist(err) + + keyUsesDefault = (ctx.Options.ClusterSigningKeyFile == cmoptions.DefaultClusterSigningKeyFile) + + switch { + case (keyFileExists && keyUsesDefault) || (certFileExists && certUsesDefault): + glog.Warningf("You might be using flag defaulting for --cluster-signing-cert-file and" + + " --cluster-signing-key-file. These defaults are deprecated and will be removed" + + " in a subsequent release. Please pass these options explicitly.") + case (!keyFileExists && keyUsesDefault) && (!certFileExists && certUsesDefault): + // This is what we expect right now if people aren't + // setting up the signing controller. This isn't + // actually a problem since the signer is not a + // required controller. + return false, nil + default: + // Note that '!filesExist && !usesDefaults' is obviously + // operator error. We don't handle this case here and instead + // allow it to be handled by NewCSR... below. + } + c := ctx.ClientBuilder.ClientOrDie("certificate-controller") signer, err := signer.NewCSRSigningController( @@ -46,8 +89,7 @@ func startCSRSigningController(ctx ControllerContext) (bool, error) { ctx.Options.ClusterSigningDuration.Duration, ) if err != nil { - glog.Errorf("Failed to start certificate controller: %v", err) - return false, nil + return false, fmt.Errorf("failed to start certificate controller: %v", err) } go signer.Run(1, ctx.Stop) @@ -59,16 +101,10 @@ func startCSRApprovingController(ctx ControllerContext) (bool, error) { return false, nil } - approver, err := approver.NewCSRApprovingController( + approver := approver.NewCSRApprovingController( ctx.ClientBuilder.ClientOrDie("certificate-controller"), ctx.InformerFactory.Certificates().V1beta1().CertificateSigningRequests(), ) - if err != nil { - // TODO this is failing consistently in test-cmd and local-up-cluster.sh. Fix them and make it consistent with all others which - // cause a crash loop - glog.Errorf("Failed to start certificate controller: %v", err) - return false, nil - } go approver.Run(1, ctx.Stop) return true, nil diff --git a/cmd/kube-controller-manager/app/controllermanager.go b/cmd/kube-controller-manager/app/controllermanager.go index 6cd1e6925d3..85dc51de5c8 100644 --- a/cmd/kube-controller-manager/app/controllermanager.go +++ b/cmd/kube-controller-manager/app/controllermanager.go @@ -52,7 +52,7 @@ import ( "k8s.io/client-go/tools/leaderelection" "k8s.io/client-go/tools/leaderelection/resourcelock" "k8s.io/kubernetes/cmd/kube-controller-manager/app/options" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/controller" serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount" @@ -71,6 +71,13 @@ const ( ControllerStartJitter = 1.0 ) +type ControllerLoopMode int + +const ( + IncludeCloudLoops ControllerLoopMode = iota + ExternalLoops +) + // NewControllerManagerCommand creates a *cobra.Command object with default parameters func NewControllerManagerCommand() *cobra.Command { s := options.NewCMServer() @@ -139,7 +146,7 @@ func Run(s *options.CMServer) error { clientBuilder = controller.SAControllerClientBuilder{ ClientConfig: restclient.AnonymousClientConfig(kubeconfig), CoreClient: kubeClient.CoreV1(), - AuthenticationClient: kubeClient.Authentication(), + AuthenticationClient: kubeClient.AuthenticationV1(), Namespace: "kube-system", } } else { @@ -151,7 +158,7 @@ func Run(s *options.CMServer) error { } saTokenControllerInitFunc := serviceAccountTokenControllerStarter{rootClientBuilder: rootClientBuilder}.startServiceAccountTokenController - if err := StartControllers(ctx, saTokenControllerInitFunc, NewControllerInitializers()); err != nil { + if err := StartControllers(ctx, saTokenControllerInitFunc, NewControllerInitializers(ctx.LoopMode)); err != nil { glog.Fatalf("error starting controllers: %v", err) } @@ -162,7 +169,9 @@ func Run(s *options.CMServer) error { } if !s.LeaderElection.LeaderElect { - run(nil) + stopCh := make(chan struct{}) + defer close(stopCh) + run(stopCh) panic("unreachable") } @@ -224,7 +233,7 @@ func createRecorder(kubeClient *clientset.Clientset) record.EventRecorder { eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(glog.Infof) eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.CoreV1().RESTClient()).Events("")}) - return eventBroadcaster.NewRecorder(api.Scheme, v1.EventSource{Component: "controller-manager"}) + return eventBroadcaster.NewRecorder(legacyscheme.Scheme, v1.EventSource{Component: "controller-manager"}) } func createClients(s *options.CMServer) (*clientset.Clientset, *clientset.Clientset, *restclient.Config, error) { @@ -262,6 +271,11 @@ type ControllerContext struct { // It must be initialized and ready to use. Cloud cloudprovider.Interface + // Control for which control loops to be run + // IncludeCloudLoops is for a kube-controller-manager running all loops + // ExternalLoops is for a kube-controller-manager running with a cloud-controller-manager + LoopMode ControllerLoopMode + // Stop is the stop channel Stop <-chan struct{} @@ -305,7 +319,7 @@ func IsControllerEnabled(name string, disabledByDefaultControllers sets.String, type InitFunc func(ctx ControllerContext) (bool, error) func KnownControllers() []string { - ret := sets.StringKeySet(NewControllerInitializers()) + ret := sets.StringKeySet(NewControllerInitializers(IncludeCloudLoops)) // add "special" controllers that aren't initialized normally. These controllers cannot be initialized // using a normal function. The only known special case is the SA token controller which *must* be started @@ -329,7 +343,7 @@ const ( // NewControllerInitializers is a public map of named controller groups (you can start more than one in an init func) // paired to their InitFunc. This allows for structured downstream composition and subdivision. -func NewControllerInitializers() map[string]InitFunc { +func NewControllerInitializers(loopMode ControllerLoopMode) map[string]InitFunc { controllers := map[string]InitFunc{} controllers["endpoint"] = startEndpointController controllers["replicationcontroller"] = startReplicationController @@ -352,12 +366,19 @@ func NewControllerInitializers() map[string]InitFunc { controllers["ttl"] = startTTLController controllers["bootstrapsigner"] = startBootstrapSignerController controllers["tokencleaner"] = startTokenCleanerController - controllers["service"] = startServiceController - controllers["node"] = startNodeController - controllers["route"] = startRouteController + if loopMode == IncludeCloudLoops { + controllers["service"] = startServiceController + controllers["nodeipam"] = startNodeIpamController + controllers["route"] = startRouteController + // TODO: volume controller into the IncludeCloudLoops only set. + // TODO: Separate cluster in cloud check from node lifecycle controller. + } + controllers["nodelifecycle"] = startNodeLifecycleController controllers["persistentvolume-binder"] = startPersistentVolumeBinderController controllers["attachdetach"] = startAttachDetachController controllers["persistentvolume-expander"] = startVolumeExpandController + controllers["clusterrole-aggregation"] = startClusterRoleAggregrationController + controllers["pvc-protection"] = startPVCProtectionController return controllers } @@ -428,7 +449,17 @@ func CreateControllerContext(s *options.CMServer, rootClientBuilder, clientBuild return ControllerContext{}, err } - cloud, err := cloudprovider.InitCloudProvider(s.CloudProvider, s.CloudConfigFile) + var cloud cloudprovider.Interface + var loopMode ControllerLoopMode + if cloudprovider.IsExternal(s.CloudProvider) { + loopMode = ExternalLoops + if s.ExternalCloudVolumePlugin != "" { + cloud, err = cloudprovider.InitCloudProvider(s.ExternalCloudVolumePlugin, s.CloudConfigFile) + } + } else { + loopMode = IncludeCloudLoops + cloud, err = cloudprovider.InitCloudProvider(s.CloudProvider, s.CloudConfigFile) + } if err != nil { return ControllerContext{}, fmt.Errorf("cloud provider could not be initialized: %v", err) } @@ -441,12 +472,17 @@ func CreateControllerContext(s *options.CMServer, rootClientBuilder, clientBuild } } + if informerUserCloud, ok := cloud.(cloudprovider.InformerUser); ok { + informerUserCloud.SetInformers(sharedInformers) + } + ctx := ControllerContext{ ClientBuilder: clientBuilder, InformerFactory: sharedInformers, Options: *s, AvailableResources: availableResources, Cloud: cloud, + LoopMode: loopMode, Stop: stop, InformersStarted: make(chan struct{}), } @@ -525,7 +561,7 @@ func (c serviceAccountTokenControllerStarter) startServiceAccountTokenController rootCA = c.rootClientBuilder.ConfigOrDie("tokens-controller").CAData } - controller := serviceaccountcontroller.NewTokensController( + controller, err := serviceaccountcontroller.NewTokensController( ctx.InformerFactory.Core().V1().ServiceAccounts(), ctx.InformerFactory.Core().V1().Secrets(), c.rootClientBuilder.ClientOrDie("tokens-controller"), @@ -534,6 +570,9 @@ func (c serviceAccountTokenControllerStarter) startServiceAccountTokenController RootCA: rootCA, }, ) + if err != nil { + return true, fmt.Errorf("error creating Tokens controller: %v", err) + } go controller.Run(int(ctx.Options.ConcurrentSATokenSyncs), ctx.Stop) // start the first set of informers now so that other controllers can start diff --git a/cmd/kube-controller-manager/app/core.go b/cmd/kube-controller-manager/app/core.go index 15443874360..2fceb2370bc 100644 --- a/cmd/kube-controller-manager/app/core.go +++ b/cmd/kube-controller-manager/app/core.go @@ -36,13 +36,14 @@ import ( cacheddiscovery "k8s.io/client-go/discovery/cached" "k8s.io/client-go/dynamic" clientset "k8s.io/client-go/kubernetes" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/controller" endpointcontroller "k8s.io/kubernetes/pkg/controller/endpoint" "k8s.io/kubernetes/pkg/controller/garbagecollector" namespacecontroller "k8s.io/kubernetes/pkg/controller/namespace" - nodecontroller "k8s.io/kubernetes/pkg/controller/node" - "k8s.io/kubernetes/pkg/controller/node/ipam" + nodeipamcontroller "k8s.io/kubernetes/pkg/controller/nodeipam" + "k8s.io/kubernetes/pkg/controller/nodeipam/ipam" + lifecyclecontroller "k8s.io/kubernetes/pkg/controller/nodelifecycle" "k8s.io/kubernetes/pkg/controller/podgc" replicationcontroller "k8s.io/kubernetes/pkg/controller/replication" resourcequotacontroller "k8s.io/kubernetes/pkg/controller/resourcequota" @@ -53,7 +54,9 @@ import ( "k8s.io/kubernetes/pkg/controller/volume/attachdetach" "k8s.io/kubernetes/pkg/controller/volume/expand" persistentvolumecontroller "k8s.io/kubernetes/pkg/controller/volume/persistentvolume" + "k8s.io/kubernetes/pkg/controller/volume/pvcprotection" "k8s.io/kubernetes/pkg/features" + "k8s.io/kubernetes/pkg/quota/generic" quotainstall "k8s.io/kubernetes/pkg/quota/install" "k8s.io/kubernetes/pkg/util/metrics" ) @@ -67,6 +70,7 @@ func startServiceController(ctx ControllerContext) (bool, error) { ctx.Options.ClusterName, ) if err != nil { + // This error shouldn't fail. It lives like this as a legacy. glog.Errorf("Failed to start service controller: %v", err) return false, nil } @@ -74,43 +78,58 @@ func startServiceController(ctx ControllerContext) (bool, error) { return true, nil } -func startNodeController(ctx ControllerContext) (bool, error) { - var clusterCIDR *net.IPNet - var err error - if len(strings.TrimSpace(ctx.Options.ClusterCIDR)) != 0 { - _, clusterCIDR, err = net.ParseCIDR(ctx.Options.ClusterCIDR) - if err != nil { - glog.Warningf("Unsuccessful parsing of cluster CIDR %v: %v", ctx.Options.ClusterCIDR, err) +func startNodeIpamController(ctx ControllerContext) (bool, error) { + var clusterCIDR *net.IPNet = nil + var serviceCIDR *net.IPNet = nil + if ctx.Options.AllocateNodeCIDRs { + var err error + if len(strings.TrimSpace(ctx.Options.ClusterCIDR)) != 0 { + _, clusterCIDR, err = net.ParseCIDR(ctx.Options.ClusterCIDR) + if err != nil { + glog.Warningf("Unsuccessful parsing of cluster CIDR %v: %v", ctx.Options.ClusterCIDR, err) + } + } + + if len(strings.TrimSpace(ctx.Options.ServiceCIDR)) != 0 { + _, serviceCIDR, err = net.ParseCIDR(ctx.Options.ServiceCIDR) + if err != nil { + glog.Warningf("Unsuccessful parsing of service CIDR %v: %v", ctx.Options.ServiceCIDR, err) + } } } - var serviceCIDR *net.IPNet - if len(strings.TrimSpace(ctx.Options.ServiceCIDR)) != 0 { - _, serviceCIDR, err = net.ParseCIDR(ctx.Options.ServiceCIDR) - if err != nil { - glog.Warningf("Unsuccessful parsing of service CIDR %v: %v", ctx.Options.ServiceCIDR, err) - } - } - - nodeController, err := nodecontroller.NewNodeController( - ctx.InformerFactory.Core().V1().Pods(), + nodeIpamController, err := nodeipamcontroller.NewNodeIpamController( ctx.InformerFactory.Core().V1().Nodes(), - ctx.InformerFactory.Extensions().V1beta1().DaemonSets(), ctx.Cloud, ctx.ClientBuilder.ClientOrDie("node-controller"), - ctx.Options.PodEvictionTimeout.Duration, - ctx.Options.NodeEvictionRate, - ctx.Options.SecondaryNodeEvictionRate, - ctx.Options.LargeClusterSizeThreshold, - ctx.Options.UnhealthyZoneThreshold, - ctx.Options.NodeMonitorGracePeriod.Duration, - ctx.Options.NodeStartupGracePeriod.Duration, - ctx.Options.NodeMonitorPeriod.Duration, clusterCIDR, serviceCIDR, int(ctx.Options.NodeCIDRMaskSize), ctx.Options.AllocateNodeCIDRs, ipam.CIDRAllocatorType(ctx.Options.CIDRAllocatorType), + ) + if err != nil { + return true, err + } + go nodeIpamController.Run(ctx.Stop) + return true, nil +} + +func startNodeLifecycleController(ctx ControllerContext) (bool, error) { + lifecycleController, err := lifecyclecontroller.NewNodeLifecycleController( + ctx.InformerFactory.Core().V1().Pods(), + ctx.InformerFactory.Core().V1().Nodes(), + ctx.InformerFactory.Extensions().V1beta1().DaemonSets(), + ctx.Cloud, + ctx.ClientBuilder.ClientOrDie("node-controller"), + ctx.Options.NodeMonitorPeriod.Duration, + ctx.Options.NodeStartupGracePeriod.Duration, + ctx.Options.NodeMonitorGracePeriod.Duration, + ctx.Options.PodEvictionTimeout.Duration, + ctx.Options.NodeEvictionRate, + ctx.Options.SecondaryNodeEvictionRate, + ctx.Options.LargeClusterSizeThreshold, + ctx.Options.UnhealthyZoneThreshold, ctx.Options.EnableTaintManager, utilfeature.DefaultFeatureGate.Enabled(features.TaintBasedEvictions), utilfeature.DefaultFeatureGate.Enabled(features.TaintNodesByCondition), @@ -118,15 +137,11 @@ func startNodeController(ctx ControllerContext) (bool, error) { if err != nil { return true, err } - go nodeController.Run(ctx.Stop) + go lifecycleController.Run(ctx.Stop) return true, nil } func startRouteController(ctx ControllerContext) (bool, error) { - _, clusterCIDR, err := net.ParseCIDR(ctx.Options.ClusterCIDR) - if err != nil { - glog.Warningf("Unsuccessful parsing of cluster CIDR %v: %v", ctx.Options.ClusterCIDR, err) - } if !ctx.Options.AllocateNodeCIDRs || !ctx.Options.ConfigureCloudRoutes { glog.Infof("Will not configure cloud provider routes for allocate-node-cidrs: %v, configure-cloud-routes: %v.", ctx.Options.AllocateNodeCIDRs, ctx.Options.ConfigureCloudRoutes) return false, nil @@ -140,6 +155,10 @@ func startRouteController(ctx ControllerContext) (bool, error) { glog.Warning("configure-cloud-routes is set, but cloud provider does not support routes. Will not configure cloud provider routes.") return false, nil } + _, clusterCIDR, err := net.ParseCIDR(ctx.Options.ClusterCIDR) + if err != nil { + glog.Warningf("Unsuccessful parsing of cluster CIDR %v: %v", ctx.Options.ClusterCIDR, err) + } routeController := routecontroller.New(routes, ctx.ClientBuilder.ClientOrDie("route-controller"), ctx.InformerFactory.Core().V1().Nodes(), ctx.Options.ClusterName, clusterCIDR) go routeController.Run(ctx.Stop, ctx.Options.RouteReconciliationPeriod.Duration) return true, nil @@ -239,37 +258,42 @@ func startPodGCController(ctx ControllerContext) (bool, error) { func startResourceQuotaController(ctx ControllerContext) (bool, error) { resourceQuotaControllerClient := ctx.ClientBuilder.ClientOrDie("resourcequota-controller") - resourceQuotaRegistry := quotainstall.NewRegistry(resourceQuotaControllerClient, ctx.InformerFactory) - groupKindsToReplenish := []schema.GroupKind{ - api.Kind("Pod"), - api.Kind("Service"), - api.Kind("ReplicationController"), - api.Kind("PersistentVolumeClaim"), - api.Kind("Secret"), - api.Kind("ConfigMap"), - } + discoveryFunc := resourceQuotaControllerClient.Discovery().ServerPreferredNamespacedResources + listerFuncForResource := generic.ListerFuncForResourceFunc(ctx.InformerFactory.ForResource) + quotaConfiguration := quotainstall.NewQuotaConfigurationForControllers(listerFuncForResource) + resourceQuotaControllerOptions := &resourcequotacontroller.ResourceQuotaControllerOptions{ - QuotaClient: resourceQuotaControllerClient.Core(), + QuotaClient: resourceQuotaControllerClient.CoreV1(), ResourceQuotaInformer: ctx.InformerFactory.Core().V1().ResourceQuotas(), ResyncPeriod: controller.StaticResyncPeriodFunc(ctx.Options.ResourceQuotaSyncPeriod.Duration), - Registry: resourceQuotaRegistry, - ControllerFactory: resourcequotacontroller.NewReplenishmentControllerFactory(ctx.InformerFactory), + InformerFactory: ctx.InformerFactory, ReplenishmentResyncPeriod: ResyncPeriod(&ctx.Options), - GroupKindsToReplenish: groupKindsToReplenish, + DiscoveryFunc: discoveryFunc, + IgnoredResourcesFunc: quotaConfiguration.IgnoredResources, + InformersStarted: ctx.InformersStarted, + Registry: generic.NewRegistry(quotaConfiguration.Evaluators()), } - if resourceQuotaControllerClient.Core().RESTClient().GetRateLimiter() != nil { - metrics.RegisterMetricAndTrackRateLimiterUsage("resource_quota_controller", resourceQuotaControllerClient.Core().RESTClient().GetRateLimiter()) + if resourceQuotaControllerClient.CoreV1().RESTClient().GetRateLimiter() != nil { + if err := metrics.RegisterMetricAndTrackRateLimiterUsage("resource_quota_controller", resourceQuotaControllerClient.CoreV1().RESTClient().GetRateLimiter()); err != nil { + return true, err + } } - go resourcequotacontroller.NewResourceQuotaController( - resourceQuotaControllerOptions, - ).Run(int(ctx.Options.ConcurrentResourceQuotaSyncs), ctx.Stop) + resourceQuotaController, err := resourcequotacontroller.NewResourceQuotaController(resourceQuotaControllerOptions) + if err != nil { + return false, err + } + go resourceQuotaController.Run(int(ctx.Options.ConcurrentResourceQuotaSyncs), ctx.Stop) + + // Periodically the quota controller to detect new resource types + go resourceQuotaController.Sync(discoveryFunc, 30*time.Second, ctx.Stop) + return true, nil } func startNamespaceController(ctx ControllerContext) (bool, error) { // TODO: should use a dynamic RESTMapper built from the discovery results. - restMapper := api.Registry.RESTMapper() + restMapper := legacyscheme.Registry.RESTMapper() // the namespace cleanup controller is very chatty. It makes lots of discovery calls and then it makes lots of delete calls // the ratelimiter negatively affects its speed. Deleting 100 total items in a namespace (that's only a few of each resource @@ -296,12 +320,16 @@ func startNamespaceController(ctx ControllerContext) (bool, error) { } func startServiceAccountController(ctx ControllerContext) (bool, error) { - go serviceaccountcontroller.NewServiceAccountsController( + sac, err := serviceaccountcontroller.NewServiceAccountsController( ctx.InformerFactory.Core().V1().ServiceAccounts(), ctx.InformerFactory.Core().V1().Namespaces(), ctx.ClientBuilder.ClientOrDie("service-account-controller"), serviceaccountcontroller.DefaultServiceAccountsControllerOptions(), - ).Run(1, ctx.Stop) + ) + if err != nil { + return true, fmt.Errorf("error creating ServiceAccount controller: %v", err) + } + go sac.Run(1, ctx.Stop) return true, nil } @@ -335,10 +363,7 @@ func startGarbageCollectorController(ctx ControllerContext) (bool, error) { clientPool := dynamic.NewClientPool(config, restMapper, dynamic.LegacyAPIPathResolverFunc) // Get an initial set of deletable resources to prime the garbage collector. - deletableResources, err := garbagecollector.GetDeletableResources(discoveryClient) - if err != nil { - return true, err - } + deletableResources := garbagecollector.GetDeletableResources(discoveryClient) ignoredResources := make(map[schema.GroupResource]struct{}) for _, r := range ctx.Options.GCIgnoredResources { ignoredResources[schema.GroupResource{Group: r.Group, Resource: r.Resource}] = struct{}{} @@ -366,3 +391,15 @@ func startGarbageCollectorController(ctx ControllerContext) (bool, error) { return true, nil } + +func startPVCProtectionController(ctx ControllerContext) (bool, error) { + if utilfeature.DefaultFeatureGate.Enabled(features.PVCProtection) { + go pvcprotection.NewPVCProtectionController( + ctx.InformerFactory.Core().V1().PersistentVolumeClaims(), + ctx.InformerFactory.Core().V1().Pods(), + ctx.ClientBuilder.ClientOrDie("pvc-protection-controller"), + ).Run(1, ctx.Stop) + return true, nil + } + return false, nil +} diff --git a/cmd/kube-controller-manager/app/extensions.go b/cmd/kube-controller-manager/app/extensions.go index 7e9be6a87e3..060704bbf53 100644 --- a/cmd/kube-controller-manager/app/extensions.go +++ b/cmd/kube-controller-manager/app/extensions.go @@ -21,6 +21,8 @@ limitations under the License. package app import ( + "fmt" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/kubernetes/pkg/controller/daemon" "k8s.io/kubernetes/pkg/controller/deployment" @@ -31,13 +33,17 @@ func startDaemonSetController(ctx ControllerContext) (bool, error) { if !ctx.AvailableResources[schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "daemonsets"}] { return false, nil } - go daemon.NewDaemonSetsController( + dsc, err := daemon.NewDaemonSetsController( ctx.InformerFactory.Extensions().V1beta1().DaemonSets(), ctx.InformerFactory.Apps().V1beta1().ControllerRevisions(), ctx.InformerFactory.Core().V1().Pods(), ctx.InformerFactory.Core().V1().Nodes(), ctx.ClientBuilder.ClientOrDie("daemon-set-controller"), - ).Run(int(ctx.Options.ConcurrentDaemonSetSyncs), ctx.Stop) + ) + if err != nil { + return true, fmt.Errorf("error creating DaemonSets controller: %v", err) + } + go dsc.Run(int(ctx.Options.ConcurrentDaemonSetSyncs), ctx.Stop) return true, nil } @@ -45,12 +51,16 @@ func startDeploymentController(ctx ControllerContext) (bool, error) { if !ctx.AvailableResources[schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"}] { return false, nil } - go deployment.NewDeploymentController( + dc, err := deployment.NewDeploymentController( ctx.InformerFactory.Extensions().V1beta1().Deployments(), ctx.InformerFactory.Extensions().V1beta1().ReplicaSets(), ctx.InformerFactory.Core().V1().Pods(), ctx.ClientBuilder.ClientOrDie("deployment-controller"), - ).Run(int(ctx.Options.ConcurrentDeploymentSyncs), ctx.Stop) + ) + if err != nil { + return true, fmt.Errorf("error creating Deployment controller: %v", err) + } + go dc.Run(int(ctx.Options.ConcurrentDeploymentSyncs), ctx.Stop) return true, nil } diff --git a/cmd/kube-controller-manager/app/import_known_versions.go b/cmd/kube-controller-manager/app/import_known_versions.go index 0f2436ac082..f63e298a240 100644 --- a/cmd/kube-controller-manager/app/import_known_versions.go +++ b/cmd/kube-controller-manager/app/import_known_versions.go @@ -15,21 +15,22 @@ limitations under the License. */ // TODO: Remove this file when namespace controller and garbage collector -// stops using api.Registry.RESTMapper() +// stops using legacyscheme.Registry.RESTMapper() package app // These imports are the API groups the client will support. import ( "fmt" - "k8s.io/kubernetes/pkg/api" - _ "k8s.io/kubernetes/pkg/api/install" + "k8s.io/kubernetes/pkg/api/legacyscheme" _ "k8s.io/kubernetes/pkg/apis/apps/install" _ "k8s.io/kubernetes/pkg/apis/authentication/install" _ "k8s.io/kubernetes/pkg/apis/authorization/install" _ "k8s.io/kubernetes/pkg/apis/autoscaling/install" _ "k8s.io/kubernetes/pkg/apis/batch/install" _ "k8s.io/kubernetes/pkg/apis/certificates/install" + _ "k8s.io/kubernetes/pkg/apis/core/install" + _ "k8s.io/kubernetes/pkg/apis/events/install" _ "k8s.io/kubernetes/pkg/apis/extensions/install" _ "k8s.io/kubernetes/pkg/apis/policy/install" _ "k8s.io/kubernetes/pkg/apis/rbac/install" @@ -39,7 +40,7 @@ import ( ) func init() { - if missingVersions := api.Registry.ValidateEnvRequestedVersions(); len(missingVersions) != 0 { + if missingVersions := legacyscheme.Registry.ValidateEnvRequestedVersions(); len(missingVersions) != 0 { panic(fmt.Sprintf("KUBE_API_VERSIONS contains versions that are not installed: %q.", missingVersions)) } } diff --git a/cmd/kube-controller-manager/app/options/BUILD b/cmd/kube-controller-manager/app/options/BUILD index 588b64c6866..cd814a31116 100644 --- a/cmd/kube-controller-manager/app/options/BUILD +++ b/cmd/kube-controller-manager/app/options/BUILD @@ -11,14 +11,13 @@ go_library( srcs = ["options.go"], importpath = "k8s.io/kubernetes/cmd/kube-controller-manager/app/options", deps = [ + "//cmd/controller-manager/app/options:go_default_library", "//pkg/apis/componentconfig:go_default_library", "//pkg/client/leaderelectionconfig:go_default_library", "//pkg/controller/garbagecollector:go_default_library", "//pkg/features:go_default_library", "//pkg/master/ports:go_default_library", - "//vendor/github.com/cloudflare/cfssl/helpers:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", @@ -41,10 +40,11 @@ filegroup( go_test( name = "go_default_test", srcs = ["options_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kube-controller-manager/app/options", - library = ":go_default_library", tags = ["automanaged"], deps = [ + "//cmd/controller-manager/app/options:go_default_library", "//pkg/apis/componentconfig:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/cmd/kube-controller-manager/app/options/options.go b/cmd/kube-controller-manager/app/options/options.go index a87afb20d8b..94548812aaa 100644 --- a/cmd/kube-controller-manager/app/options/options.go +++ b/cmd/kube-controller-manager/app/options/options.go @@ -21,12 +21,11 @@ package options import ( "fmt" "strings" - "time" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" utilfeature "k8s.io/apiserver/pkg/util/feature" + cmoptions "k8s.io/kubernetes/cmd/controller-manager/app/options" "k8s.io/kubernetes/pkg/apis/componentconfig" "k8s.io/kubernetes/pkg/client/leaderelectionconfig" "k8s.io/kubernetes/pkg/controller/garbagecollector" @@ -35,16 +34,12 @@ import ( // add the kubernetes feature gates _ "k8s.io/kubernetes/pkg/features" - "github.com/cloudflare/cfssl/helpers" "github.com/spf13/pflag" ) // CMServer is the main context object for the controller manager. type CMServer struct { - componentconfig.KubeControllerManagerConfiguration - - Master string - Kubeconfig string + cmoptions.ControllerManagerServer } // NewCMServer creates a new CMServer with a default config. @@ -55,91 +50,32 @@ func NewCMServer() *CMServer { } s := CMServer{ - // Part of these default values also present in 'cmd/cloud-controller-manager/app/options/options.go'. - // Please keep them in sync when doing update. - KubeControllerManagerConfiguration: componentconfig.KubeControllerManagerConfiguration{ - Controllers: []string{"*"}, - Port: ports.ControllerManagerPort, - Address: "0.0.0.0", - ConcurrentEndpointSyncs: 5, - ConcurrentServiceSyncs: 1, - ConcurrentRCSyncs: 5, - ConcurrentRSSyncs: 5, - ConcurrentDaemonSetSyncs: 2, - ConcurrentJobSyncs: 5, - ConcurrentResourceQuotaSyncs: 5, - ConcurrentDeploymentSyncs: 5, - ConcurrentNamespaceSyncs: 10, - ConcurrentSATokenSyncs: 5, - ServiceSyncPeriod: metav1.Duration{Duration: 5 * time.Minute}, - RouteReconciliationPeriod: metav1.Duration{Duration: 10 * time.Second}, - ResourceQuotaSyncPeriod: metav1.Duration{Duration: 5 * time.Minute}, - NamespaceSyncPeriod: metav1.Duration{Duration: 5 * time.Minute}, - PVClaimBinderSyncPeriod: metav1.Duration{Duration: 15 * time.Second}, - HorizontalPodAutoscalerSyncPeriod: metav1.Duration{Duration: 30 * time.Second}, - HorizontalPodAutoscalerUpscaleForbiddenWindow: metav1.Duration{Duration: 3 * time.Minute}, - HorizontalPodAutoscalerDownscaleForbiddenWindow: metav1.Duration{Duration: 5 * time.Minute}, - HorizontalPodAutoscalerTolerance: 0.1, - DeploymentControllerSyncPeriod: metav1.Duration{Duration: 30 * time.Second}, - MinResyncPeriod: metav1.Duration{Duration: 12 * time.Hour}, - RegisterRetryCount: 10, - PodEvictionTimeout: metav1.Duration{Duration: 5 * time.Minute}, - NodeMonitorGracePeriod: metav1.Duration{Duration: 40 * time.Second}, - NodeStartupGracePeriod: metav1.Duration{Duration: 60 * time.Second}, - NodeMonitorPeriod: metav1.Duration{Duration: 5 * time.Second}, - ClusterName: "kubernetes", - NodeCIDRMaskSize: 24, - ConfigureCloudRoutes: true, - TerminatedPodGCThreshold: 12500, - VolumeConfiguration: componentconfig.VolumeConfiguration{ - EnableHostPathProvisioning: false, - EnableDynamicProvisioning: true, - PersistentVolumeRecyclerConfiguration: componentconfig.PersistentVolumeRecyclerConfiguration{ - MaximumRetry: 3, - MinimumTimeoutNFS: 300, - IncrementTimeoutNFS: 30, - MinimumTimeoutHostPath: 60, - IncrementTimeoutHostPath: 30, - }, - FlexVolumePluginDir: "/usr/libexec/kubernetes/kubelet-plugins/volume/exec/", - }, - ContentType: "application/vnd.kubernetes.protobuf", - KubeAPIQPS: 20.0, - KubeAPIBurst: 30, - LeaderElection: leaderelectionconfig.DefaultLeaderElectionConfiguration(), - ControllerStartInterval: metav1.Duration{Duration: 0 * time.Second}, - EnableGarbageCollector: true, - ConcurrentGCSyncs: 20, - GCIgnoredResources: gcIgnoredResources, - ClusterSigningCertFile: "/etc/kubernetes/ca/ca.pem", - ClusterSigningKeyFile: "/etc/kubernetes/ca/ca.key", - ClusterSigningDuration: metav1.Duration{Duration: helpers.OneYear}, - ReconcilerSyncLoopPeriod: metav1.Duration{Duration: 60 * time.Second}, - EnableTaintManager: true, - HorizontalPodAutoscalerUseRESTClients: true, + // The common/default are kept in 'cmd/kube-controller-manager/app/options/util.go'. + // Please make common changes there but put anything kube-controller specific here. + ControllerManagerServer: cmoptions.ControllerManagerServer{ + KubeControllerManagerConfiguration: cmoptions.GetDefaultControllerOptions(ports.ControllerManagerPort), }, } + s.KubeControllerManagerConfiguration.GCIgnoredResources = gcIgnoredResources s.LeaderElection.LeaderElect = true return &s } // AddFlags adds flags for a specific CMServer to the specified FlagSet func (s *CMServer) AddFlags(fs *pflag.FlagSet, allControllers []string, disabledByDefaultControllers []string) { + cmoptions.AddDefaultControllerFlags(&s.ControllerManagerServer, fs) + fs.StringSliceVar(&s.Controllers, "controllers", s.Controllers, fmt.Sprintf(""+ "A list of controllers to enable. '*' enables all on-by-default controllers, 'foo' enables the controller "+ "named 'foo', '-foo' disables the controller named 'foo'.\nAll controllers: %s\nDisabled-by-default controllers: %s", strings.Join(allControllers, ", "), strings.Join(disabledByDefaultControllers, ", "))) - fs.Int32Var(&s.Port, "port", s.Port, "The port that the controller-manager's http service runs on") - fs.Var(componentconfig.IPVar{Val: &s.Address}, "address", "The IP address to serve on (set to 0.0.0.0 for all interfaces)") - fs.BoolVar(&s.UseServiceAccountCredentials, "use-service-account-credentials", s.UseServiceAccountCredentials, "If true, use individual service account credentials for each controller.") fs.StringVar(&s.CloudProvider, "cloud-provider", s.CloudProvider, "The provider for cloud services. Empty string for no provider.") - fs.StringVar(&s.CloudConfigFile, "cloud-config", s.CloudConfigFile, "The path to the cloud provider configuration file. Empty string for no configuration file.") - fs.BoolVar(&s.AllowUntaggedCloud, "allow-untagged-cloud", false, "Allow the cluster to run without the cluster-id on cloud instances. This is a legacy mode of operation and a cluster-id will be required in the future.") - fs.MarkDeprecated("allow-untagged-cloud", "This flag is deprecated and will be removed in a future release. A cluster-id will be required on cloud instances") + fs.StringVar(&s.ExternalCloudVolumePlugin, "external-cloud-volume-plugin", s.ExternalCloudVolumePlugin, "The plugin to use when cloud provider is set to external. Can be empty, should only be set when cloud-provider is external. Currently used to allow node and volume controllers to work for in tree cloud providers.") fs.Int32Var(&s.ConcurrentEndpointSyncs, "concurrent-endpoint-syncs", s.ConcurrentEndpointSyncs, "The number of endpoint syncing operations that will be done concurrently. Larger number = faster endpoint updating, but more CPU (and network) load") fs.Int32Var(&s.ConcurrentServiceSyncs, "concurrent-service-syncs", s.ConcurrentServiceSyncs, "The number of services that are allowed to sync concurrently. Larger number = more responsive service management, but more CPU (and network) load") fs.Int32Var(&s.ConcurrentRCSyncs, "concurrent_rc_syncs", s.ConcurrentRCSyncs, "The number of replication controllers that are allowed to sync concurrently. Larger number = more responsive replica management, but more CPU (and network) load") fs.Int32Var(&s.ConcurrentRSSyncs, "concurrent-replicaset-syncs", s.ConcurrentRSSyncs, "The number of replica sets that are allowed to sync concurrently. Larger number = more responsive replica management, but more CPU (and network) load") + fs.Int32Var(&s.ConcurrentResourceQuotaSyncs, "concurrent-resource-quota-syncs", s.ConcurrentResourceQuotaSyncs, "The number of resource quotas that are allowed to sync concurrently. Larger number = more responsive quota management, but more CPU (and network) load") fs.Int32Var(&s.ConcurrentDeploymentSyncs, "concurrent-deployment-syncs", s.ConcurrentDeploymentSyncs, "The number of deployment objects that are allowed to sync concurrently. Larger number = more responsive deployments, but more CPU (and network) load") fs.Int32Var(&s.ConcurrentNamespaceSyncs, "concurrent-namespace-syncs", s.ConcurrentNamespaceSyncs, "The number of namespace objects that are allowed to sync concurrently. Larger number = more responsive namespace termination, but more CPU (and network) load") @@ -149,11 +85,9 @@ func (s *CMServer) AddFlags(fs *pflag.FlagSet, allControllers []string, disabled "This flag is deprecated and will be removed in future releases. See node-monitor-period for Node health checking or "+ "route-reconciliation-period for cloud provider's route configuration settings.") fs.MarkDeprecated("node-sync-period", "This flag is currently no-op and will be deleted.") - fs.DurationVar(&s.RouteReconciliationPeriod.Duration, "route-reconciliation-period", s.RouteReconciliationPeriod.Duration, "The period for reconciling routes created for Nodes by cloud provider.") fs.DurationVar(&s.ResourceQuotaSyncPeriod.Duration, "resource-quota-sync-period", s.ResourceQuotaSyncPeriod.Duration, "The period for syncing quota usage status in the system") fs.DurationVar(&s.NamespaceSyncPeriod.Duration, "namespace-sync-period", s.NamespaceSyncPeriod.Duration, "The period for syncing namespace life-cycle updates") fs.DurationVar(&s.PVClaimBinderSyncPeriod.Duration, "pvclaimbinder-sync-period", s.PVClaimBinderSyncPeriod.Duration, "The period for syncing persistent volumes and persistent volume claims") - fs.DurationVar(&s.MinResyncPeriod.Duration, "min-resync-period", s.MinResyncPeriod.Duration, "The resync period in reflectors will be random between MinResyncPeriod and 2*MinResyncPeriod") fs.StringVar(&s.VolumeConfiguration.PersistentVolumeRecyclerConfiguration.PodTemplateFilePathNFS, "pv-recycler-pod-template-filepath-nfs", s.VolumeConfiguration.PersistentVolumeRecyclerConfiguration.PodTemplateFilePathNFS, "The file path to a pod definition used as a template for NFS persistent volume recycling") fs.Int32Var(&s.VolumeConfiguration.PersistentVolumeRecyclerConfiguration.MinimumTimeoutNFS, "pv-recycler-minimum-timeout-nfs", s.VolumeConfiguration.PersistentVolumeRecyclerConfiguration.MinimumTimeoutNFS, "The minimum ActiveDeadlineSeconds to use for an NFS Recycler pod") fs.Int32Var(&s.VolumeConfiguration.PersistentVolumeRecyclerConfiguration.IncrementTimeoutNFS, "pv-recycler-increment-timeout-nfs", s.VolumeConfiguration.PersistentVolumeRecyclerConfiguration.IncrementTimeoutNFS, "the increment of time added per Gi to ActiveDeadlineSeconds for an NFS scrubber pod") @@ -183,8 +117,6 @@ func (s *CMServer) AddFlags(fs *pflag.FlagSet, allControllers []string, disabled "where N means number of retries allowed for kubelet to post node status.") fs.DurationVar(&s.NodeStartupGracePeriod.Duration, "node-startup-grace-period", s.NodeStartupGracePeriod.Duration, "Amount of time which we allow starting Node to be unresponsive before marking it unhealthy.") - fs.DurationVar(&s.NodeMonitorPeriod.Duration, "node-monitor-period", s.NodeMonitorPeriod.Duration, - "The period for syncing NodeStatus in NodeController.") fs.StringVar(&s.ServiceAccountKeyFile, "service-account-private-key-file", s.ServiceAccountKeyFile, "Filename containing a PEM-encoded private RSA or ECDSA key used to sign service account tokens.") fs.StringVar(&s.ClusterSigningCertFile, "cluster-signing-cert-file", s.ClusterSigningCertFile, "Filename containing a PEM-encoded X509 CA certificate used to issue cluster-scoped certificates") fs.StringVar(&s.ClusterSigningKeyFile, "cluster-signing-key-file", s.ClusterSigningKeyFile, "Filename containing a PEM-encoded RSA or ECDSA private key used to sign cluster-scoped certificates") @@ -192,34 +124,19 @@ func (s *CMServer) AddFlags(fs *pflag.FlagSet, allControllers []string, disabled var dummy string fs.MarkDeprecated("insecure-experimental-approve-all-kubelet-csrs-for-group", "This flag does nothing.") fs.StringVar(&dummy, "insecure-experimental-approve-all-kubelet-csrs-for-group", "", "This flag does nothing.") - fs.BoolVar(&s.EnableProfiling, "profiling", true, "Enable profiling via web interface host:port/debug/pprof/") - fs.BoolVar(&s.EnableContentionProfiling, "contention-profiling", false, "Enable lock contention profiling, if profiling is enabled") - fs.StringVar(&s.ClusterName, "cluster-name", s.ClusterName, "The instance prefix for the cluster") - fs.StringVar(&s.ClusterCIDR, "cluster-cidr", s.ClusterCIDR, "CIDR Range for Pods in cluster.") - fs.StringVar(&s.ServiceCIDR, "service-cluster-ip-range", s.ServiceCIDR, "CIDR Range for Services in cluster.") + fs.StringVar(&s.ServiceCIDR, "service-cluster-ip-range", s.ServiceCIDR, "CIDR Range for Services in cluster. Requires --allocate-node-cidrs to be true") fs.Int32Var(&s.NodeCIDRMaskSize, "node-cidr-mask-size", s.NodeCIDRMaskSize, "Mask size for node cidr in cluster.") - fs.BoolVar(&s.AllocateNodeCIDRs, "allocate-node-cidrs", false, - "Should CIDRs for Pods be allocated and set on the cloud provider.") - fs.StringVar(&s.CIDRAllocatorType, "cidr-allocator-type", "RangeAllocator", - "Type of CIDR allocator to use") - fs.BoolVar(&s.ConfigureCloudRoutes, "configure-cloud-routes", true, "Should CIDRs allocated by allocate-node-cidrs be configured on the cloud provider.") - fs.StringVar(&s.Master, "master", s.Master, "The address of the Kubernetes API server (overrides any value in kubeconfig)") - fs.StringVar(&s.Kubeconfig, "kubeconfig", s.Kubeconfig, "Path to kubeconfig file with authorization and master location information.") fs.StringVar(&s.RootCAFile, "root-ca-file", s.RootCAFile, "If set, this root certificate authority will be included in service account's token secret. This must be a valid PEM-encoded CA bundle.") - fs.StringVar(&s.ContentType, "kube-api-content-type", s.ContentType, "Content type of requests sent to apiserver.") - fs.Float32Var(&s.KubeAPIQPS, "kube-api-qps", s.KubeAPIQPS, "QPS to use while talking with kubernetes apiserver") - fs.Int32Var(&s.KubeAPIBurst, "kube-api-burst", s.KubeAPIBurst, "Burst to use while talking with kubernetes apiserver") - fs.DurationVar(&s.ControllerStartInterval.Duration, "controller-start-interval", s.ControllerStartInterval.Duration, "Interval between starting controller managers.") fs.BoolVar(&s.EnableGarbageCollector, "enable-garbage-collector", s.EnableGarbageCollector, "Enables the generic garbage collector. MUST be synced with the corresponding flag of the kube-apiserver.") fs.Int32Var(&s.ConcurrentGCSyncs, "concurrent-gc-syncs", s.ConcurrentGCSyncs, "The number of garbage collector workers that are allowed to sync concurrently.") - fs.Float32Var(&s.NodeEvictionRate, "node-eviction-rate", 0.1, "Number of nodes per second on which pods are deleted in case of node failure when a zone is healthy (see --unhealthy-zone-threshold for definition of healthy/unhealthy). Zone refers to entire cluster in non-multizone clusters.") - fs.Float32Var(&s.SecondaryNodeEvictionRate, "secondary-node-eviction-rate", 0.01, "Number of nodes per second on which pods are deleted in case of node failure when a zone is unhealthy (see --unhealthy-zone-threshold for definition of healthy/unhealthy). Zone refers to entire cluster in non-multizone clusters. This value is implicitly overridden to 0 if the cluster size is smaller than --large-cluster-size-threshold.") fs.Int32Var(&s.LargeClusterSizeThreshold, "large-cluster-size-threshold", 50, "Number of nodes from which NodeController treats the cluster as large for the eviction logic purposes. --secondary-node-eviction-rate is implicitly overridden to 0 for clusters this size or smaller.") fs.Float32Var(&s.UnhealthyZoneThreshold, "unhealthy-zone-threshold", 0.55, "Fraction of Nodes in a zone which needs to be not Ready (minimum 3) for zone to be treated as unhealthy. ") fs.BoolVar(&s.DisableAttachDetachReconcilerSync, "disable-attach-detach-reconcile-sync", false, "Disable volume attach detach reconciler sync. Disabling this may cause volumes to be mismatched with pods. Use wisely.") fs.DurationVar(&s.ReconcilerSyncLoopPeriod.Duration, "attach-detach-reconcile-sync-period", s.ReconcilerSyncLoopPeriod.Duration, "The reconciler sync wait time between volume attach detach. This duration must be larger than one second, and increasing this value from the default may allow for volumes to be mismatched with pods.") fs.BoolVar(&s.EnableTaintManager, "enable-taint-manager", s.EnableTaintManager, "WARNING: Beta feature. If set to true enables NoExecute Taints and will evict all not-tolerating Pod running on Nodes tainted with this kind of Taints.") fs.BoolVar(&s.HorizontalPodAutoscalerUseRESTClients, "horizontal-pod-autoscaler-use-rest-clients", s.HorizontalPodAutoscalerUseRESTClients, "WARNING: alpha feature. If set to true, causes the horizontal pod autoscaler controller to use REST clients through the kube-aggregator, instead of using the legacy metrics client through the API server proxy. This is required for custom metrics support in the horizontal pod autoscaler.") + fs.Float32Var(&s.NodeEvictionRate, "node-eviction-rate", 0.1, "Number of nodes per second on which pods are deleted in case of node failure when a zone is healthy (see --unhealthy-zone-threshold for definition of healthy/unhealthy). Zone refers to entire cluster in non-multizone clusters.") + fs.Float32Var(&s.SecondaryNodeEvictionRate, "secondary-node-eviction-rate", 0.01, "Number of nodes per second on which pods are deleted in case of node failure when a zone is unhealthy (see --unhealthy-zone-threshold for definition of healthy/unhealthy). Zone refers to entire cluster in non-multizone clusters. This value is implicitly overridden to 0 if the cluster size is smaller than --large-cluster-size-threshold.") leaderelectionconfig.BindFlags(&s.LeaderElection, fs) diff --git a/cmd/kube-controller-manager/app/options/options_test.go b/cmd/kube-controller-manager/app/options/options_test.go index 87f92d5a9c0..904535121f7 100644 --- a/cmd/kube-controller-manager/app/options/options_test.go +++ b/cmd/kube-controller-manager/app/options/options_test.go @@ -26,6 +26,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/diff" + cmoptions "k8s.io/kubernetes/cmd/controller-manager/app/options" "k8s.io/kubernetes/pkg/apis/componentconfig" ) @@ -110,102 +111,104 @@ func TestAddFlags(t *testing.T) { sort.Sort(sortedGCIgnoredResources(s.GCIgnoredResources)) expected := &CMServer{ - KubeControllerManagerConfiguration: componentconfig.KubeControllerManagerConfiguration{ - Port: 10000, - Address: "192.168.4.10", - AllocateNodeCIDRs: true, - CloudConfigFile: "/cloud-config", - CloudProvider: "gce", - ClusterCIDR: "1.2.3.4/24", - ClusterName: "k8s", - ConcurrentDeploymentSyncs: 10, - ConcurrentEndpointSyncs: 10, - ConcurrentGCSyncs: 30, - ConcurrentNamespaceSyncs: 20, - ConcurrentRSSyncs: 10, - ConcurrentResourceQuotaSyncs: 10, - ConcurrentServiceSyncs: 2, - ConcurrentSATokenSyncs: 10, - ConcurrentRCSyncs: 10, - ConfigureCloudRoutes: false, - EnableContentionProfiling: true, - ControllerStartInterval: metav1.Duration{Duration: 2 * time.Minute}, - ConcurrentDaemonSetSyncs: 2, - ConcurrentJobSyncs: 5, - DeletingPodsQps: 0.1, - EnableProfiling: false, - CIDRAllocatorType: "CloudAllocator", - NodeCIDRMaskSize: 48, - ServiceSyncPeriod: metav1.Duration{Duration: 2 * time.Minute}, - ResourceQuotaSyncPeriod: metav1.Duration{Duration: 10 * time.Minute}, - NamespaceSyncPeriod: metav1.Duration{Duration: 10 * time.Minute}, - PVClaimBinderSyncPeriod: metav1.Duration{Duration: 30 * time.Second}, - HorizontalPodAutoscalerSyncPeriod: metav1.Duration{Duration: 45 * time.Second}, - DeploymentControllerSyncPeriod: metav1.Duration{Duration: 45 * time.Second}, - MinResyncPeriod: metav1.Duration{Duration: 8 * time.Hour}, - RegisterRetryCount: 10, - RouteReconciliationPeriod: metav1.Duration{Duration: 30 * time.Second}, - PodEvictionTimeout: metav1.Duration{Duration: 2 * time.Minute}, - NodeMonitorGracePeriod: metav1.Duration{Duration: 30 * time.Second}, - NodeStartupGracePeriod: metav1.Duration{Duration: 30 * time.Second}, - NodeMonitorPeriod: metav1.Duration{Duration: 10 * time.Second}, - HorizontalPodAutoscalerUpscaleForbiddenWindow: metav1.Duration{Duration: 1 * time.Minute}, - HorizontalPodAutoscalerDownscaleForbiddenWindow: metav1.Duration{Duration: 2 * time.Minute}, - HorizontalPodAutoscalerTolerance: 0.1, - TerminatedPodGCThreshold: 12000, - VolumeConfiguration: componentconfig.VolumeConfiguration{ - EnableDynamicProvisioning: false, - EnableHostPathProvisioning: true, - FlexVolumePluginDir: "/flex-volume-plugin", - PersistentVolumeRecyclerConfiguration: componentconfig.PersistentVolumeRecyclerConfiguration{ - MaximumRetry: 3, - MinimumTimeoutNFS: 200, - IncrementTimeoutNFS: 45, - MinimumTimeoutHostPath: 45, - IncrementTimeoutHostPath: 45, + ControllerManagerServer: cmoptions.ControllerManagerServer{ + KubeControllerManagerConfiguration: componentconfig.KubeControllerManagerConfiguration{ + Port: 10000, + Address: "192.168.4.10", + AllocateNodeCIDRs: true, + CloudConfigFile: "/cloud-config", + CloudProvider: "gce", + ClusterCIDR: "1.2.3.4/24", + ClusterName: "k8s", + ConcurrentDeploymentSyncs: 10, + ConcurrentEndpointSyncs: 10, + ConcurrentGCSyncs: 30, + ConcurrentNamespaceSyncs: 20, + ConcurrentRSSyncs: 10, + ConcurrentResourceQuotaSyncs: 10, + ConcurrentServiceSyncs: 2, + ConcurrentSATokenSyncs: 10, + ConcurrentRCSyncs: 10, + ConfigureCloudRoutes: false, + EnableContentionProfiling: true, + ControllerStartInterval: metav1.Duration{Duration: 2 * time.Minute}, + ConcurrentDaemonSetSyncs: 2, + ConcurrentJobSyncs: 5, + DeletingPodsQps: 0.1, + EnableProfiling: false, + CIDRAllocatorType: "CloudAllocator", + NodeCIDRMaskSize: 48, + ServiceSyncPeriod: metav1.Duration{Duration: 2 * time.Minute}, + ResourceQuotaSyncPeriod: metav1.Duration{Duration: 10 * time.Minute}, + NamespaceSyncPeriod: metav1.Duration{Duration: 10 * time.Minute}, + PVClaimBinderSyncPeriod: metav1.Duration{Duration: 30 * time.Second}, + HorizontalPodAutoscalerSyncPeriod: metav1.Duration{Duration: 45 * time.Second}, + DeploymentControllerSyncPeriod: metav1.Duration{Duration: 45 * time.Second}, + MinResyncPeriod: metav1.Duration{Duration: 8 * time.Hour}, + RegisterRetryCount: 10, + RouteReconciliationPeriod: metav1.Duration{Duration: 30 * time.Second}, + PodEvictionTimeout: metav1.Duration{Duration: 2 * time.Minute}, + NodeMonitorGracePeriod: metav1.Duration{Duration: 30 * time.Second}, + NodeStartupGracePeriod: metav1.Duration{Duration: 30 * time.Second}, + NodeMonitorPeriod: metav1.Duration{Duration: 10 * time.Second}, + HorizontalPodAutoscalerUpscaleForbiddenWindow: metav1.Duration{Duration: 1 * time.Minute}, + HorizontalPodAutoscalerDownscaleForbiddenWindow: metav1.Duration{Duration: 2 * time.Minute}, + HorizontalPodAutoscalerTolerance: 0.1, + TerminatedPodGCThreshold: 12000, + VolumeConfiguration: componentconfig.VolumeConfiguration{ + EnableDynamicProvisioning: false, + EnableHostPathProvisioning: true, + FlexVolumePluginDir: "/flex-volume-plugin", + PersistentVolumeRecyclerConfiguration: componentconfig.PersistentVolumeRecyclerConfiguration{ + MaximumRetry: 3, + MinimumTimeoutNFS: 200, + IncrementTimeoutNFS: 45, + MinimumTimeoutHostPath: 45, + IncrementTimeoutHostPath: 45, + }, }, + ContentType: "application/json", + KubeAPIQPS: 50.0, + KubeAPIBurst: 100, + LeaderElection: componentconfig.LeaderElectionConfiguration{ + ResourceLock: "configmap", + LeaderElect: false, + LeaseDuration: metav1.Duration{Duration: 30 * time.Second}, + RenewDeadline: metav1.Duration{Duration: 15 * time.Second}, + RetryPeriod: metav1.Duration{Duration: 5 * time.Second}, + }, + ClusterSigningCertFile: "/cluster-signing-cert", + ClusterSigningKeyFile: "/cluster-signing-key", + ServiceAccountKeyFile: "/service-account-private-key", + ClusterSigningDuration: metav1.Duration{Duration: 10 * time.Hour}, + EnableGarbageCollector: false, + GCIgnoredResources: []componentconfig.GroupResource{ + {Group: "extensions", Resource: "replicationcontrollers"}, + {Group: "", Resource: "bindings"}, + {Group: "", Resource: "componentstatuses"}, + {Group: "", Resource: "events"}, + {Group: "authentication.k8s.io", Resource: "tokenreviews"}, + {Group: "authorization.k8s.io", Resource: "subjectaccessreviews"}, + {Group: "authorization.k8s.io", Resource: "selfsubjectaccessreviews"}, + {Group: "authorization.k8s.io", Resource: "localsubjectaccessreviews"}, + {Group: "authorization.k8s.io", Resource: "selfsubjectrulesreviews"}, + {Group: "apiregistration.k8s.io", Resource: "apiservices"}, + {Group: "apiextensions.k8s.io", Resource: "customresourcedefinitions"}, + }, + NodeEvictionRate: 0.2, + SecondaryNodeEvictionRate: 0.05, + LargeClusterSizeThreshold: 100, + UnhealthyZoneThreshold: 0.6, + DisableAttachDetachReconcilerSync: true, + ReconcilerSyncLoopPeriod: metav1.Duration{Duration: 30 * time.Second}, + Controllers: []string{"foo", "bar"}, + EnableTaintManager: false, + HorizontalPodAutoscalerUseRESTClients: true, + UseServiceAccountCredentials: true, }, - ContentType: "application/json", - KubeAPIQPS: 50.0, - KubeAPIBurst: 100, - LeaderElection: componentconfig.LeaderElectionConfiguration{ - ResourceLock: "configmap", - LeaderElect: false, - LeaseDuration: metav1.Duration{Duration: 30 * time.Second}, - RenewDeadline: metav1.Duration{Duration: 15 * time.Second}, - RetryPeriod: metav1.Duration{Duration: 5 * time.Second}, - }, - ClusterSigningCertFile: "/cluster-signing-cert", - ClusterSigningKeyFile: "/cluster-signing-key", - ServiceAccountKeyFile: "/service-account-private-key", - ClusterSigningDuration: metav1.Duration{Duration: 10 * time.Hour}, - EnableGarbageCollector: false, - GCIgnoredResources: []componentconfig.GroupResource{ - {Group: "extensions", Resource: "replicationcontrollers"}, - {Group: "", Resource: "bindings"}, - {Group: "", Resource: "componentstatuses"}, - {Group: "", Resource: "events"}, - {Group: "authentication.k8s.io", Resource: "tokenreviews"}, - {Group: "authorization.k8s.io", Resource: "subjectaccessreviews"}, - {Group: "authorization.k8s.io", Resource: "selfsubjectaccessreviews"}, - {Group: "authorization.k8s.io", Resource: "localsubjectaccessreviews"}, - {Group: "authorization.k8s.io", Resource: "selfsubjectrulesreviews"}, - {Group: "apiregistration.k8s.io", Resource: "apiservices"}, - {Group: "apiextensions.k8s.io", Resource: "customresourcedefinitions"}, - }, - NodeEvictionRate: 0.2, - SecondaryNodeEvictionRate: 0.05, - LargeClusterSizeThreshold: 100, - UnhealthyZoneThreshold: 0.6, - DisableAttachDetachReconcilerSync: true, - ReconcilerSyncLoopPeriod: metav1.Duration{Duration: 30 * time.Second}, - Controllers: []string{"foo", "bar"}, - EnableTaintManager: false, - HorizontalPodAutoscalerUseRESTClients: true, - UseServiceAccountCredentials: true, + Kubeconfig: "/kubeconfig", + Master: "192.168.4.20", }, - Kubeconfig: "/kubeconfig", - Master: "192.168.4.20", } // Sort GCIgnoredResources because it's built from a map, which means the diff --git a/cmd/kube-controller-manager/app/plugins.go b/cmd/kube-controller-manager/app/plugins.go index bf9c9fe1299..170c366c90a 100644 --- a/cmd/kube-controller-manager/app/plugins.go +++ b/cmd/kube-controller-manager/app/plugins.go @@ -30,17 +30,12 @@ import ( // Volume plugins "github.com/golang/glog" "k8s.io/kubernetes/pkg/cloudprovider" - "k8s.io/kubernetes/pkg/cloudprovider/providers/aws" - "k8s.io/kubernetes/pkg/cloudprovider/providers/azure" - "k8s.io/kubernetes/pkg/cloudprovider/providers/gce" - "k8s.io/kubernetes/pkg/cloudprovider/providers/openstack" - "k8s.io/kubernetes/pkg/cloudprovider/providers/photon" - "k8s.io/kubernetes/pkg/cloudprovider/providers/vsphere" "k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume/aws_ebs" "k8s.io/kubernetes/pkg/volume/azure_dd" "k8s.io/kubernetes/pkg/volume/azure_file" "k8s.io/kubernetes/pkg/volume/cinder" + "k8s.io/kubernetes/pkg/volume/csi" "k8s.io/kubernetes/pkg/volume/fc" "k8s.io/kubernetes/pkg/volume/flexvolume" "k8s.io/kubernetes/pkg/volume/flocker" @@ -58,6 +53,9 @@ import ( "k8s.io/kubernetes/pkg/volume/storageos" volumeutil "k8s.io/kubernetes/pkg/volume/util" "k8s.io/kubernetes/pkg/volume/vsphere_volume" + + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/features" ) // ProbeAttachableVolumePlugins collects all volume plugins for the attach/ @@ -78,6 +76,10 @@ func ProbeAttachableVolumePlugins() []volume.VolumePlugin { allPlugins = append(allPlugins, storageos.ProbeVolumePlugins()...) allPlugins = append(allPlugins, fc.ProbeVolumePlugins()...) allPlugins = append(allPlugins, iscsi.ProbeVolumePlugins()...) + allPlugins = append(allPlugins, rbd.ProbeVolumePlugins()...) + if utilfeature.DefaultFeatureGate.Enabled(features.CSIPersistentVolume) { + allPlugins = append(allPlugins, csi.ProbeVolumePlugins()...) + } return allPlugins } @@ -104,6 +106,9 @@ func ProbeExpandableVolumePlugins(config componentconfig.VolumeConfiguration) [] allPlugins = append(allPlugins, scaleio.ProbeVolumePlugins()...) allPlugins = append(allPlugins, storageos.ProbeVolumePlugins()...) allPlugins = append(allPlugins, fc.ProbeVolumePlugins()...) + if !utilfeature.DefaultFeatureGate.Enabled(features.CSIPersistentVolume) { + allPlugins = append(allPlugins, csi.ProbeVolumePlugins()...) + } return allPlugins } @@ -154,22 +159,12 @@ func ProbeControllerVolumePlugins(cloud cloudprovider.Interface, config componen allPlugins = append(allPlugins, local.ProbeVolumePlugins()...) allPlugins = append(allPlugins, storageos.ProbeVolumePlugins()...) - if cloud != nil { - switch { - case aws.ProviderName == cloud.ProviderName(): - allPlugins = append(allPlugins, aws_ebs.ProbeVolumePlugins()...) - case gce.ProviderName == cloud.ProviderName(): - allPlugins = append(allPlugins, gce_pd.ProbeVolumePlugins()...) - case openstack.ProviderName == cloud.ProviderName(): - allPlugins = append(allPlugins, cinder.ProbeVolumePlugins()...) - case vsphere.ProviderName == cloud.ProviderName(): - allPlugins = append(allPlugins, vsphere_volume.ProbeVolumePlugins()...) - case azure.CloudProviderName == cloud.ProviderName(): - allPlugins = append(allPlugins, azure_dd.ProbeVolumePlugins()...) - case photon.ProviderName == cloud.ProviderName(): - allPlugins = append(allPlugins, photon_pd.ProbeVolumePlugins()...) - } - } + allPlugins = append(allPlugins, aws_ebs.ProbeVolumePlugins()...) + allPlugins = append(allPlugins, gce_pd.ProbeVolumePlugins()...) + allPlugins = append(allPlugins, cinder.ProbeVolumePlugins()...) + allPlugins = append(allPlugins, vsphere_volume.ProbeVolumePlugins()...) + allPlugins = append(allPlugins, azure_dd.ProbeVolumePlugins()...) + allPlugins = append(allPlugins, photon_pd.ProbeVolumePlugins()...) return allPlugins } diff --git a/cmd/kube-controller-manager/app/rbac.go b/cmd/kube-controller-manager/app/rbac.go new file mode 100644 index 00000000000..b49d3403fe0 --- /dev/null +++ b/cmd/kube-controller-manager/app/rbac.go @@ -0,0 +1,33 @@ +/* +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 app + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kubernetes/pkg/controller/clusterroleaggregation" +) + +func startClusterRoleAggregrationController(ctx ControllerContext) (bool, error) { + if !ctx.AvailableResources[schema.GroupVersionResource{Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "clusterroles"}] { + return false, nil + } + go clusterroleaggregation.NewClusterRoleAggregation( + ctx.InformerFactory.Rbac().V1().ClusterRoles(), + ctx.ClientBuilder.ClientOrDie("clusterrole-aggregation-controller").RbacV1(), + ).Run(5, ctx.Stop) + return true, nil +} diff --git a/cmd/kube-controller-manager/controller-manager.go b/cmd/kube-controller-manager/controller-manager.go index d4a827cdd17..495a5734f45 100644 --- a/cmd/kube-controller-manager/controller-manager.go +++ b/cmd/kube-controller-manager/controller-manager.go @@ -24,7 +24,6 @@ import ( "fmt" "os" - "k8s.io/apiserver/pkg/server/healthz" "k8s.io/apiserver/pkg/util/flag" "k8s.io/apiserver/pkg/util/logs" "k8s.io/kubernetes/cmd/kube-controller-manager/app" @@ -38,10 +37,6 @@ import ( "github.com/spf13/pflag" ) -func init() { - healthz.DefaultHealthz() -} - func main() { s := options.NewCMServer() s.AddFlags(pflag.CommandLine, app.KnownControllers(), app.ControllersDisabledByDefault.List()) diff --git a/cmd/kube-proxy/BUILD b/cmd/kube-proxy/BUILD index fbc765cd357..92f618488e7 100644 --- a/cmd/kube-proxy/BUILD +++ b/cmd/kube-proxy/BUILD @@ -9,14 +9,9 @@ load("//pkg/version:def.bzl", "version_x_defs") go_binary( name = "kube-proxy", - gc_linkopts = [ - "-linkmode", - "external", - "-extldflags", - "-static", - ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kube-proxy", - library = ":go_default_library", + pure = "on", x_defs = version_x_defs(), ) diff --git a/cmd/kube-proxy/app/BUILD b/cmd/kube-proxy/app/BUILD index db69094dae1..6a785fa8114 100644 --- a/cmd/kube-proxy/app/BUILD +++ b/cmd/kube-proxy/app/BUILD @@ -11,34 +11,63 @@ go_library( srcs = [ "conntrack.go", "server.go", - "server_others.go", - "validation.go", ] + select({ - "@io_bazel_rules_go//go/platform:windows_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "server_others.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "server_others.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "server_others.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "server_others.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "server_others.go", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "server_others.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "server_others.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "server_others.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "server_others.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "server_others.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ "server_windows.go", ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/cmd/kube-proxy/app", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/validation:go_default_library", "//pkg/apis/componentconfig:go_default_library", - "//pkg/apis/componentconfig/v1alpha1:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", - "//pkg/features:go_default_library", "//pkg/kubectl/cmd/util:go_default_library", "//pkg/kubelet/qos:go_default_library", "//pkg/master/ports:go_default_library", "//pkg/proxy:go_default_library", + "//pkg/proxy/apis/kubeproxyconfig:go_default_library", + "//pkg/proxy/apis/kubeproxyconfig/scheme:go_default_library", + "//pkg/proxy/apis/kubeproxyconfig/v1alpha1:go_default_library", + "//pkg/proxy/apis/kubeproxyconfig/validation:go_default_library", "//pkg/proxy/config:go_default_library", "//pkg/proxy/healthcheck:go_default_library", "//pkg/proxy/iptables:go_default_library", "//pkg/proxy/ipvs:go_default_library", "//pkg/proxy/userspace:go_default_library", "//pkg/util/configz:go_default_library", - "//pkg/util/dbus:go_default_library", + "//pkg/util/ipset:go_default_library", "//pkg/util/iptables:go_default_library", "//pkg/util/ipvs:go_default_library", "//pkg/util/mount:go_default_library", @@ -58,10 +87,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer/json:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/apiserver/pkg/server/healthz:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", @@ -73,10 +99,82 @@ go_library( "//vendor/k8s.io/client-go/tools/record:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library", ] + select({ - "@io_bazel_rules_go//go/platform:windows_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "//pkg/features:go_default_library", + "//pkg/proxy/metrics:go_default_library", + "//pkg/util/dbus:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "//pkg/features:go_default_library", + "//pkg/proxy/metrics:go_default_library", + "//pkg/util/dbus:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "//pkg/features:go_default_library", + "//pkg/proxy/metrics:go_default_library", + "//pkg/util/dbus:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "//pkg/features:go_default_library", + "//pkg/proxy/metrics:go_default_library", + "//pkg/util/dbus:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "//pkg/features:go_default_library", + "//pkg/proxy/metrics:go_default_library", + "//pkg/util/dbus:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "//pkg/features:go_default_library", + "//pkg/proxy/metrics:go_default_library", + "//pkg/util/dbus:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "//pkg/features:go_default_library", + "//pkg/proxy/metrics:go_default_library", + "//pkg/util/dbus:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "//pkg/features:go_default_library", + "//pkg/proxy/metrics:go_default_library", + "//pkg/util/dbus:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "//pkg/features:go_default_library", + "//pkg/proxy/metrics:go_default_library", + "//pkg/util/dbus:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "//pkg/features:go_default_library", + "//pkg/proxy/metrics:go_default_library", + "//pkg/util/dbus:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", + ], + "@io_bazel_rules_go//go/platform:windows": [ "//pkg/proxy/winkernel:go_default_library", "//pkg/proxy/winuserspace:go_default_library", "//pkg/util/netsh:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", ], "//conditions:default": [], }), @@ -84,24 +182,20 @@ go_library( go_test( name = "go_default_test", - srcs = [ - "server_test.go", - "validation_test.go", - ], + srcs = ["server_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kube-proxy/app", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", - "//pkg/apis/componentconfig:go_default_library", - "//pkg/apis/componentconfig/v1alpha1:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/proxy/apis/kubeproxyconfig:go_default_library", + "//pkg/proxy/ipvs:go_default_library", "//pkg/util/configz:go_default_library", "//pkg/util/iptables:go_default_library", "//pkg/util/pointer:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", ], ) diff --git a/cmd/kube-proxy/app/server.go b/cmd/kube-proxy/app/server.go index e5414dbb576..2f9ea2eac3e 100644 --- a/cmd/kube-proxy/app/server.go +++ b/cmd/kube-proxy/app/server.go @@ -45,21 +45,25 @@ import ( "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "k8s.io/client-go/tools/record" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/componentconfig" - "k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1" + api "k8s.io/kubernetes/pkg/apis/core" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubelet/qos" "k8s.io/kubernetes/pkg/master/ports" "k8s.io/kubernetes/pkg/proxy" - proxyconfig "k8s.io/kubernetes/pkg/proxy/config" + "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig" + "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/scheme" + "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1" + "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/validation" + "k8s.io/kubernetes/pkg/proxy/config" "k8s.io/kubernetes/pkg/proxy/healthcheck" "k8s.io/kubernetes/pkg/proxy/iptables" "k8s.io/kubernetes/pkg/proxy/ipvs" "k8s.io/kubernetes/pkg/proxy/userspace" "k8s.io/kubernetes/pkg/util/configz" + utilipset "k8s.io/kubernetes/pkg/util/ipset" utiliptables "k8s.io/kubernetes/pkg/util/iptables" utilipvs "k8s.io/kubernetes/pkg/util/ipvs" utilnode "k8s.io/kubernetes/pkg/util/node" @@ -91,12 +95,13 @@ type Options struct { WriteConfigTo string // CleanupAndExit, when true, makes the proxy server clean up iptables rules, then exit. CleanupAndExit bool - + // CleanupIPVS, when true, makes the proxy server clean up ipvs rules before running. + CleanupIPVS bool // config is the proxy server's configuration object. - config *componentconfig.KubeProxyConfiguration + config *kubeproxyconfig.KubeProxyConfiguration // The fields below here are placeholders for flags that can't be directly mapped into - // componentconfig.KubeProxyConfiguration. + // kubeproxyconfig.KubeProxyConfiguration. // // TODO remove these fields once the deprecated flags are removed. @@ -116,14 +121,15 @@ func AddFlags(options *Options, fs *pflag.FlagSet) { fs.BoolVar(&options.CleanupAndExit, "cleanup-iptables", options.CleanupAndExit, "If true cleanup iptables and ipvs rules and exit.") fs.MarkDeprecated("cleanup-iptables", "This flag is replaced by --cleanup.") fs.BoolVar(&options.CleanupAndExit, "cleanup", options.CleanupAndExit, "If true cleanup iptables and ipvs rules and exit.") + fs.BoolVar(&options.CleanupIPVS, "cleanup-ipvs", options.CleanupIPVS, "If true make kube-proxy cleanup ipvs rules before running. Default is true") // All flags below here are deprecated and will eventually be removed. - fs.Var(componentconfig.IPVar{Val: &options.config.BindAddress}, "bind-address", "The IP address for the proxy server to serve on (set to 0.0.0.0 for all interfaces)") + fs.Var(componentconfig.IPVar{Val: &options.config.BindAddress}, "bind-address", "The IP address for the proxy server to serve on (set to `0.0.0.0` for all IPv4 interfaces and `::` for all IPv6 interfaces)") fs.StringVar(&options.master, "master", options.master, "The address of the Kubernetes API server (overrides any value in kubeconfig)") fs.Int32Var(&options.healthzPort, "healthz-port", options.healthzPort, "The port to bind the health check server. Use 0 to disable.") - fs.Var(componentconfig.IPVar{Val: &options.config.HealthzBindAddress}, "healthz-bind-address", "The IP address and port for the health check server to serve on (set to 0.0.0.0 for all interfaces)") - fs.Var(componentconfig.IPVar{Val: &options.config.MetricsBindAddress}, "metrics-bind-address", "The IP address and port for the metrics server to serve on (set to 0.0.0.0 for all interfaces)") + fs.Var(componentconfig.IPVar{Val: &options.config.HealthzBindAddress}, "healthz-bind-address", "The IP address and port for the health check server to serve on (set to `0.0.0.0` for all IPv4 interfaces and `::` for all IPv6 interfaces)") + fs.Var(componentconfig.IPVar{Val: &options.config.MetricsBindAddress}, "metrics-bind-address", "The IP address and port for the metrics server to serve on (set to `0.0.0.0` for all IPv4 interfaces and `::` for all IPv6 interfaces)") fs.Int32Var(options.config.OOMScoreAdj, "oom-score-adj", utilpointer.Int32PtrDerefOr(options.config.OOMScoreAdj, int32(qos.KubeProxyOOMScoreAdj)), "The oom-score-adj value for kube-proxy process. Values must be within the range [-1000, 1000]") fs.StringVar(&options.config.ResourceContainer, "resource-container", options.config.ResourceContainer, "Absolute name of the resource-only container to create and run the Kube-proxy in (Default: /kube-proxy).") fs.MarkDeprecated("resource-container", "This feature will be removed in a later release.") @@ -141,14 +147,17 @@ func AddFlags(options *Options, fs *pflag.FlagSet) { fs.StringVar(&options.config.ClusterCIDR, "cluster-cidr", options.config.ClusterCIDR, "The CIDR range of pods in the cluster. When configured, traffic sent to a Service cluster IP from outside this range will be masqueraded and traffic sent from pods to an external LoadBalancer IP will be directed to the respective cluster IP instead") fs.StringVar(&options.config.ClientConnection.ContentType, "kube-api-content-type", options.config.ClientConnection.ContentType, "Content type of requests sent to apiserver.") fs.Float32Var(&options.config.ClientConnection.QPS, "kube-api-qps", options.config.ClientConnection.QPS, "QPS to use while talking with kubernetes apiserver") - fs.IntVar(&options.config.ClientConnection.Burst, "kube-api-burst", options.config.ClientConnection.Burst, "Burst to use while talking with kubernetes apiserver") + fs.Int32Var(&options.config.ClientConnection.Burst, "kube-api-burst", options.config.ClientConnection.Burst, "Burst to use while talking with kubernetes apiserver") fs.DurationVar(&options.config.UDPIdleTimeout.Duration, "udp-timeout", options.config.UDPIdleTimeout.Duration, "How long an idle UDP connection will be kept open (e.g. '250ms', '2s'). Must be greater than 0. Only applicable for proxy-mode=userspace") - fs.Int32Var(&options.config.Conntrack.Max, "conntrack-max", options.config.Conntrack.Max, + if options.config.Conntrack.Max == nil { + options.config.Conntrack.Max = utilpointer.Int32Ptr(0) + } + fs.Int32Var(options.config.Conntrack.Max, "conntrack-max", *options.config.Conntrack.Max, "Maximum number of NAT connections to track (0 to leave as-is). This overrides conntrack-max-per-core and conntrack-min.") fs.MarkDeprecated("conntrack-max", "This feature will be removed in a later release.") - fs.Int32Var(&options.config.Conntrack.MaxPerCore, "conntrack-max-per-core", options.config.Conntrack.MaxPerCore, + fs.Int32Var(options.config.Conntrack.MaxPerCore, "conntrack-max-per-core", *options.config.Conntrack.MaxPerCore, "Maximum number of NAT connections to track per CPU core (0 to leave the limit as-is and ignore conntrack-min).") - fs.Int32Var(&options.config.Conntrack.Min, "conntrack-min", options.config.Conntrack.Min, + fs.Int32Var(options.config.Conntrack.Min, "conntrack-min", *options.config.Conntrack.Min, "Minimum number of conntrack entries to allocate, regardless of conntrack-max-per-core (set conntrack-max-per-core=0 to leave the limit as-is).") fs.DurationVar(&options.config.Conntrack.TCPEstablishedTimeout.Duration, "conntrack-tcp-timeout-established", options.config.Conntrack.TCPEstablishedTimeout.Duration, "Idle timeout for established TCP connections (0 to leave as-is)") fs.DurationVar( @@ -160,23 +169,14 @@ func AddFlags(options *Options, fs *pflag.FlagSet) { utilfeature.DefaultFeatureGate.AddFlag(fs) } -func NewOptions() (*Options, error) { - o := &Options{ - config: new(componentconfig.KubeProxyConfiguration), +func NewOptions() *Options { + return &Options{ + config: new(kubeproxyconfig.KubeProxyConfiguration), healthzPort: ports.ProxyHealthzPort, + scheme: scheme.Scheme, + codecs: scheme.Codecs, + CleanupIPVS: true, } - - o.scheme = runtime.NewScheme() - o.codecs = serializer.NewCodecFactory(o.scheme) - - if err := componentconfig.AddToScheme(o.scheme); err != nil { - return nil, err - } - if err := v1alpha1.AddToScheme(o.scheme); err != nil { - return nil, err - } - - return o, nil } // Complete completes all the required options. @@ -186,6 +186,17 @@ func (o *Options) Complete() error { o.applyDeprecatedHealthzPortToConfig() } + // Load the config file here in Complete, so that Validate validates the fully-resolved config. + if len(o.ConfigFile) > 0 { + if c, err := o.loadConfigFromFile(o.ConfigFile); err != nil { + return err + } else { + o.config = c + // Make sure we apply the feature gate settings in the config file. + utilfeature.DefaultFeatureGate.Set(o.config.FeatureGates) + } + } + return nil } @@ -195,7 +206,7 @@ func (o *Options) Validate(args []string) error { return errors.New("no arguments are supported") } - if errs := Validate(o.config); len(errs) != 0 { + if errs := validation.Validate(o.config); len(errs) != 0 { return errs.ToAggregate() } @@ -203,23 +214,11 @@ func (o *Options) Validate(args []string) error { } func (o *Options) Run() error { - config := o.config - if len(o.WriteConfigTo) > 0 { return o.writeConfigFile() } - if len(o.ConfigFile) > 0 { - if c, err := o.loadConfigFromFile(o.ConfigFile); err != nil { - return err - } else { - config = c - // Make sure we apply the feature gate settings in the config file. - utilfeature.DefaultFeatureGate.Set(config.FeatureGates) - } - } - - proxyServer, err := NewProxyServer(config, o.CleanupAndExit, o.scheme, o.master) + proxyServer, err := NewProxyServer(o) if err != nil { return err } @@ -279,7 +278,7 @@ func (o *Options) applyDeprecatedHealthzPortToConfig() { // loadConfigFromFile loads the contents of file and decodes it as a // KubeProxyConfiguration object. -func (o *Options) loadConfigFromFile(file string) (*componentconfig.KubeProxyConfiguration, error) { +func (o *Options) loadConfigFromFile(file string) (*kubeproxyconfig.KubeProxyConfiguration, error) { data, err := ioutil.ReadFile(file) if err != nil { return nil, err @@ -289,19 +288,19 @@ func (o *Options) loadConfigFromFile(file string) (*componentconfig.KubeProxyCon } // loadConfig decodes data as a KubeProxyConfiguration object. -func (o *Options) loadConfig(data []byte) (*componentconfig.KubeProxyConfiguration, error) { +func (o *Options) loadConfig(data []byte) (*kubeproxyconfig.KubeProxyConfiguration, error) { configObj, gvk, err := o.codecs.UniversalDecoder().Decode(data, nil, nil) if err != nil { return nil, err } - config, ok := configObj.(*componentconfig.KubeProxyConfiguration) + config, ok := configObj.(*kubeproxyconfig.KubeProxyConfiguration) if !ok { return nil, fmt.Errorf("got unexpected config type: %v", gvk) } return config, nil } -func (o *Options) ApplyDefaults(in *componentconfig.KubeProxyConfiguration) (*componentconfig.KubeProxyConfiguration, error) { +func (o *Options) ApplyDefaults(in *kubeproxyconfig.KubeProxyConfiguration) (*kubeproxyconfig.KubeProxyConfiguration, error) { external, err := o.scheme.ConvertToVersion(in, v1alpha1.SchemeGroupVersion) if err != nil { return nil, err @@ -309,22 +308,19 @@ func (o *Options) ApplyDefaults(in *componentconfig.KubeProxyConfiguration) (*co o.scheme.Default(external) - internal, err := o.scheme.ConvertToVersion(external, componentconfig.SchemeGroupVersion) + internal, err := o.scheme.ConvertToVersion(external, kubeproxyconfig.SchemeGroupVersion) if err != nil { return nil, err } - out := internal.(*componentconfig.KubeProxyConfiguration) + out := internal.(*kubeproxyconfig.KubeProxyConfiguration) return out, nil } // NewProxyCommand creates a *cobra.Command object with default parameters func NewProxyCommand() *cobra.Command { - opts, err := NewOptions() - if err != nil { - glog.Fatalf("Unable to initialize command options: %v", err) - } + opts := NewOptions() cmd := &cobra.Command{ Use: "kube-proxy", @@ -343,6 +339,7 @@ with the apiserver API to configure the proxy.`, }, } + var err error opts.config, err = opts.ApplyDefaults(opts.config) if err != nil { glog.Fatalf("unable to create flag defaults: %v", err) @@ -363,28 +360,30 @@ type ProxyServer struct { EventClient v1core.EventsGetter IptInterface utiliptables.Interface IpvsInterface utilipvs.Interface + IpsetInterface utilipset.Interface execer exec.Interface Proxier proxy.ProxyProvider Broadcaster record.EventBroadcaster Recorder record.EventRecorder - ConntrackConfiguration componentconfig.KubeProxyConntrackConfiguration + ConntrackConfiguration kubeproxyconfig.KubeProxyConntrackConfiguration Conntracker Conntracker // if nil, ignored ProxyMode string NodeRef *v1.ObjectReference CleanupAndExit bool + CleanupIPVS bool MetricsBindAddress string EnableProfiling bool OOMScoreAdj *int32 ResourceContainer string ConfigSyncPeriod time.Duration - ServiceEventHandler proxyconfig.ServiceHandler - EndpointsEventHandler proxyconfig.EndpointsHandler + ServiceEventHandler config.ServiceHandler + EndpointsEventHandler config.EndpointsHandler HealthzServer *healthcheck.HealthzServer } // createClients creates a kube client and an event client from the given config and masterOverride. // TODO remove masterOverride when CLI flags are removed. -func createClients(config componentconfig.ClientConnectionConfiguration, masterOverride string) (clientset.Interface, v1core.EventsGetter, error) { +func createClients(config kubeproxyconfig.ClientConnectionConfiguration, masterOverride string) (clientset.Interface, v1core.EventsGetter, error) { var kubeConfig *rest.Config var err error @@ -429,7 +428,7 @@ func (s *ProxyServer) Run() error { if s.CleanupAndExit { encounteredError := userspace.CleanupLeftovers(s.IptInterface) encounteredError = iptables.CleanupLeftovers(s.IptInterface) || encounteredError - encounteredError = ipvs.CleanupLeftovers(s.execer, s.IpvsInterface, s.IptInterface) || encounteredError + encounteredError = ipvs.CleanupLeftovers(s.IpvsInterface, s.IptInterface, s.IpsetInterface, s.CleanupIPVS) || encounteredError if encounteredError { return errors.New("encountered an error while tearing down rules.") } @@ -511,14 +510,14 @@ func (s *ProxyServer) Run() error { } } - if s.ConntrackConfiguration.TCPEstablishedTimeout.Duration > 0 { + if s.ConntrackConfiguration.TCPEstablishedTimeout != nil && s.ConntrackConfiguration.TCPEstablishedTimeout.Duration > 0 { timeout := int(s.ConntrackConfiguration.TCPEstablishedTimeout.Duration / time.Second) if err := s.Conntracker.SetTCPEstablishedTimeout(timeout); err != nil { return err } } - if s.ConntrackConfiguration.TCPCloseWaitTimeout.Duration > 0 { + if s.ConntrackConfiguration.TCPCloseWaitTimeout != nil && s.ConntrackConfiguration.TCPCloseWaitTimeout.Duration > 0 { timeout := int(s.ConntrackConfiguration.TCPCloseWaitTimeout.Duration / time.Second) if err := s.Conntracker.SetTCPCloseWaitTimeout(timeout); err != nil { return err @@ -532,11 +531,11 @@ func (s *ProxyServer) Run() error { // Note: RegisterHandler() calls need to happen before creation of Sources because sources // only notify on changes, and the initial update (on process start) may be lost if no handlers // are registered yet. - serviceConfig := proxyconfig.NewServiceConfig(informerFactory.Core().InternalVersion().Services(), s.ConfigSyncPeriod) + serviceConfig := config.NewServiceConfig(informerFactory.Core().InternalVersion().Services(), s.ConfigSyncPeriod) serviceConfig.RegisterEventHandler(s.ServiceEventHandler) go serviceConfig.Run(wait.NeverStop) - endpointsConfig := proxyconfig.NewEndpointsConfig(informerFactory.Core().InternalVersion().Endpoints(), s.ConfigSyncPeriod) + endpointsConfig := config.NewEndpointsConfig(informerFactory.Core().InternalVersion().Endpoints(), s.ConfigSyncPeriod) endpointsConfig.RegisterEventHandler(s.EndpointsEventHandler) go endpointsConfig.Run(wait.NeverStop) @@ -556,17 +555,20 @@ func (s *ProxyServer) birthCry() { s.Recorder.Eventf(s.NodeRef, api.EventTypeNormal, "Starting", "Starting kube-proxy.") } -func getConntrackMax(config componentconfig.KubeProxyConntrackConfiguration) (int, error) { - if config.Max > 0 { - if config.MaxPerCore > 0 { +func getConntrackMax(config kubeproxyconfig.KubeProxyConntrackConfiguration) (int, error) { + if config.Max != nil && *config.Max > 0 { + if config.MaxPerCore != nil && *config.MaxPerCore > 0 { return -1, fmt.Errorf("invalid config: Conntrack Max and Conntrack MaxPerCore are mutually exclusive") } glog.V(3).Infof("getConntrackMax: using absolute conntrack-max (deprecated)") - return int(config.Max), nil + return int(*config.Max), nil } - if config.MaxPerCore > 0 { - floor := int(config.Min) - scaled := int(config.MaxPerCore) * goruntime.NumCPU() + if config.MaxPerCore != nil && *config.MaxPerCore > 0 { + floor := 0 + if config.Min != nil { + floor = int(*config.Min) + } + scaled := int(*config.MaxPerCore) * goruntime.NumCPU() if scaled > floor { glog.V(3).Infof("getConntrackMax: using scaled conntrack-max-per-core") return scaled, nil diff --git a/cmd/kube-proxy/app/server_others.go b/cmd/kube-proxy/app/server_others.go index 479d2adb4ca..30a8ac9bbf9 100644 --- a/cmd/kube-proxy/app/server_others.go +++ b/cmd/kube-proxy/app/server_others.go @@ -32,16 +32,18 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/tools/record" - "k8s.io/kubernetes/pkg/apis/componentconfig" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/proxy" + proxyconfigapi "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig" proxyconfig "k8s.io/kubernetes/pkg/proxy/config" "k8s.io/kubernetes/pkg/proxy/healthcheck" "k8s.io/kubernetes/pkg/proxy/iptables" "k8s.io/kubernetes/pkg/proxy/ipvs" + "k8s.io/kubernetes/pkg/proxy/metrics" "k8s.io/kubernetes/pkg/proxy/userspace" "k8s.io/kubernetes/pkg/util/configz" utildbus "k8s.io/kubernetes/pkg/util/dbus" + utilipset "k8s.io/kubernetes/pkg/util/ipset" utiliptables "k8s.io/kubernetes/pkg/util/iptables" utilipvs "k8s.io/kubernetes/pkg/util/ipvs" utilnode "k8s.io/kubernetes/pkg/util/node" @@ -52,12 +54,22 @@ import ( ) // NewProxyServer returns a new ProxyServer. -func NewProxyServer(config *componentconfig.KubeProxyConfiguration, cleanupAndExit bool, scheme *runtime.Scheme, master string) (*ProxyServer, error) { +func NewProxyServer(o *Options) (*ProxyServer, error) { + return newProxyServer(o.config, o.CleanupAndExit, o.CleanupIPVS, o.scheme, o.master) +} + +func newProxyServer( + config *proxyconfigapi.KubeProxyConfiguration, + cleanupAndExit bool, + cleanupIPVS bool, + scheme *runtime.Scheme, + master string) (*ProxyServer, error) { + if config == nil { return nil, errors.New("config is required") } - if c, err := configz.New("componentconfig"); err == nil { + if c, err := configz.New(proxyconfigapi.GroupName); err == nil { c.Set(config) } else { return nil, fmt.Errorf("unable to register configz: %s", err) @@ -71,6 +83,8 @@ func NewProxyServer(config *componentconfig.KubeProxyConfiguration, cleanupAndEx var iptInterface utiliptables.Interface var ipvsInterface utilipvs.Interface + var kernelHandler ipvs.KernelHandler + var ipsetInterface utilipset.Interface var dbus utildbus.Interface // Create a iptables utils. @@ -79,10 +93,18 @@ func NewProxyServer(config *componentconfig.KubeProxyConfiguration, cleanupAndEx dbus = utildbus.New() iptInterface = utiliptables.New(execer, dbus, protocol) ipvsInterface = utilipvs.New(execer) + kernelHandler = ipvs.NewLinuxKernelHandler() + ipsetInterface = utilipset.New(execer) // We omit creation of pretty much everything if we run in cleanup mode if cleanupAndExit { - return &ProxyServer{IptInterface: iptInterface, IpvsInterface: ipvsInterface, CleanupAndExit: cleanupAndExit}, nil + return &ProxyServer{ + execer: execer, + IptInterface: iptInterface, + IpvsInterface: ipvsInterface, + IpsetInterface: ipsetInterface, + CleanupAndExit: cleanupAndExit, + }, nil } client, eventClient, err := createClients(config.ClientConnection, master) @@ -113,7 +135,7 @@ func NewProxyServer(config *componentconfig.KubeProxyConfiguration, cleanupAndEx var serviceEventHandler proxyconfig.ServiceHandler var endpointsEventHandler proxyconfig.EndpointsHandler - proxyMode := getProxyMode(string(config.Mode), iptInterface, iptables.LinuxKernelCompatTester{}) + proxyMode := getProxyMode(string(config.Mode), iptInterface, kernelHandler, ipsetInterface, iptables.LinuxKernelCompatTester{}) if proxyMode == proxyModeIPTables { glog.V(0).Info("Using iptables Proxier.") nodeIP := net.ParseIP(config.BindAddress) @@ -143,7 +165,7 @@ func NewProxyServer(config *componentconfig.KubeProxyConfiguration, cleanupAndEx if err != nil { return nil, fmt.Errorf("unable to create proxier: %v", err) } - iptables.RegisterMetrics() + metrics.RegisterMetrics() proxier = proxierIPTables serviceEventHandler = proxierIPTables endpointsEventHandler = proxierIPTables @@ -151,14 +173,17 @@ func NewProxyServer(config *componentconfig.KubeProxyConfiguration, cleanupAndEx glog.V(0).Info("Tearing down inactive rules.") // TODO this has side effects that should only happen when Run() is invoked. userspace.CleanupLeftovers(iptInterface) - // IPVS Proxier will generate some iptables rules, - // need to clean them before switching to other proxy mode. - ipvs.CleanupLeftovers(execer, ipvsInterface, iptInterface) + // IPVS Proxier will generate some iptables rules, need to clean them before switching to other proxy mode. + // Besides, ipvs proxier will create some ipvs rules as well. Because there is no way to tell if a given + // ipvs rule is created by IPVS proxier or not. Users should explicitly specify `--clean-ipvs=true` to flush + // all ipvs rules when kube-proxy start up. Users do this operation should be with caution. + ipvs.CleanupLeftovers(ipvsInterface, iptInterface, ipsetInterface, cleanupIPVS) } else if proxyMode == proxyModeIPVS { glog.V(0).Info("Using ipvs Proxier.") proxierIPVS, err := ipvs.NewProxier( iptInterface, ipvsInterface, + ipsetInterface, utilsysctl.New(), execer, config.IPVS.SyncPeriod.Duration, @@ -175,6 +200,7 @@ func NewProxyServer(config *componentconfig.KubeProxyConfiguration, cleanupAndEx if err != nil { return nil, fmt.Errorf("unable to create proxier: %v", err) } + metrics.RegisterMetrics() proxier = proxierIPVS serviceEventHandler = proxierIPVS endpointsEventHandler = proxierIPVS @@ -211,9 +237,11 @@ func NewProxyServer(config *componentconfig.KubeProxyConfiguration, cleanupAndEx glog.V(0).Info("Tearing down inactive rules.") // TODO this has side effects that should only happen when Run() is invoked. iptables.CleanupLeftovers(iptInterface) - // IPVS Proxier will generate some iptables rules, - // need to clean them before switching to other proxy mode. - ipvs.CleanupLeftovers(execer, ipvsInterface, iptInterface) + // IPVS Proxier will generate some iptables rules, need to clean them before switching to other proxy mode. + // Besides, ipvs proxier will create some ipvs rules as well. Because there is no way to tell if a given + // ipvs rule is created by IPVS proxier or not. Users should explicitly specify `--clean-ipvs=true` to flush + // all ipvs rules when kube-proxy start up. Users do this operation should be with caution. + ipvs.CleanupLeftovers(ipvsInterface, iptInterface, ipsetInterface, cleanupIPVS) } iptInterface.AddReloadFunc(proxier.Sync) @@ -223,6 +251,7 @@ func NewProxyServer(config *componentconfig.KubeProxyConfiguration, cleanupAndEx EventClient: eventClient, IptInterface: iptInterface, IpvsInterface: ipvsInterface, + IpsetInterface: ipsetInterface, execer: execer, Proxier: proxier, Broadcaster: eventBroadcaster, @@ -242,7 +271,7 @@ func NewProxyServer(config *componentconfig.KubeProxyConfiguration, cleanupAndEx }, nil } -func getProxyMode(proxyMode string, iptver iptables.IPTablesVersioner, kcompat iptables.KernelCompatTester) string { +func getProxyMode(proxyMode string, iptver iptables.IPTablesVersioner, khandle ipvs.KernelHandler, ipsetver ipvs.IPSetVersioner, kcompat iptables.KernelCompatTester) string { if proxyMode == proxyModeUserspace { return proxyModeUserspace } @@ -253,7 +282,7 @@ func getProxyMode(proxyMode string, iptver iptables.IPTablesVersioner, kcompat i if utilfeature.DefaultFeatureGate.Enabled(features.SupportIPVSProxyMode) { if proxyMode == proxyModeIPVS { - return tryIPVSProxy(iptver, kcompat) + return tryIPVSProxy(iptver, khandle, ipsetver, kcompat) } else { glog.Warningf("Can't use ipvs proxier, trying iptables proxier") return tryIPTablesProxy(iptver, kcompat) @@ -263,20 +292,18 @@ func getProxyMode(proxyMode string, iptver iptables.IPTablesVersioner, kcompat i return tryIPTablesProxy(iptver, kcompat) } -func tryIPVSProxy(iptver iptables.IPTablesVersioner, kcompat iptables.KernelCompatTester) string { +func tryIPVSProxy(iptver iptables.IPTablesVersioner, khandle ipvs.KernelHandler, ipsetver ipvs.IPSetVersioner, kcompat iptables.KernelCompatTester) string { // guaranteed false on error, error only necessary for debugging - // IPVS Proxier relies on iptables - useIPVSProxy, err := ipvs.CanUseIPVSProxier() + // IPVS Proxier relies on ip_vs_* kernel modules and ipset + useIPVSProxy, err := ipvs.CanUseIPVSProxier(khandle, ipsetver) if err != nil { - utilruntime.HandleError(fmt.Errorf("can't determine whether to use ipvs proxy, using userspace proxier: %v", err)) - return proxyModeUserspace + // Try to fallback to iptables before falling back to userspace + utilruntime.HandleError(fmt.Errorf("can't determine whether to use ipvs proxy, error: %v", err)) } if useIPVSProxy { return proxyModeIPVS } - // TODO: Check ipvs version - // Try to fallback to iptables before falling back to userspace glog.V(1).Infof("Can't use ipvs proxier, trying iptables proxier") return tryIPTablesProxy(iptver, kcompat) diff --git a/cmd/kube-proxy/app/server_test.go b/cmd/kube-proxy/app/server_test.go index e6e0708064f..ba470950556 100644 --- a/cmd/kube-proxy/app/server_test.go +++ b/cmd/kube-proxy/app/server_test.go @@ -17,7 +17,6 @@ limitations under the License. package app import ( - "errors" "fmt" "reflect" "runtime" @@ -28,11 +27,11 @@ import ( "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - k8sRuntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/diff" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/componentconfig" - "k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1" + utilfeature "k8s.io/apiserver/pkg/util/feature" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig" + "k8s.io/kubernetes/pkg/proxy/ipvs" "k8s.io/kubernetes/pkg/util/configz" "k8s.io/kubernetes/pkg/util/iptables" utilpointer "k8s.io/kubernetes/pkg/util/pointer" @@ -55,6 +54,15 @@ func (fake *fakeIPTablesVersioner) GetVersion() (string, error) { return fake.version, fake.err } +type fakeIPSetVersioner struct { + version string // what to return + err error // what to return +} + +func (fake *fakeIPSetVersioner) GetVersion() (string, error) { + return fake.version, fake.err +} + type fakeKernelCompatTester struct { ok bool } @@ -66,6 +74,15 @@ func (fake *fakeKernelCompatTester) IsCompatible() error { return nil } +// fakeKernelHandler implements KernelHandler. +type fakeKernelHandler struct { + modules []string +} + +func (fake *fakeKernelHandler) GetModules() ([]string, error) { + return fake.modules, nil +} + func Test_getProxyMode(t *testing.T) { if runtime.GOOS != "linux" { t.Skip("skipping on non-Linux") @@ -75,8 +92,11 @@ func Test_getProxyMode(t *testing.T) { annotationKey string annotationVal string iptablesVersion string + ipsetVersion string + kmods []string kernelCompat bool iptablesError error + ipsetError error expected string }{ { // flag says userspace @@ -127,48 +147,165 @@ func Test_getProxyMode(t *testing.T) { kernelCompat: true, expected: proxyModeIPTables, }, + { // specify ipvs, feature gateway disabled, iptables version ok, kernel is compatible + flag: "ipvs", + iptablesVersion: iptables.MinCheckVersion, + kernelCompat: true, + expected: proxyModeIPTables, + }, + { // specify ipvs, feature gateway disabled, iptables version too low + flag: "ipvs", + iptablesVersion: "0.0.0", + expected: proxyModeUserspace, + }, + { // specify ipvs, feature gateway disabled, iptables version ok, kernel is not compatible + flag: "ipvs", + iptablesVersion: iptables.MinCheckVersion, + kernelCompat: false, + expected: proxyModeUserspace, + }, } for i, c := range cases { versioner := &fakeIPTablesVersioner{c.iptablesVersion, c.iptablesError} kcompater := &fakeKernelCompatTester{c.kernelCompat} - r := getProxyMode(c.flag, versioner, kcompater) + ipsetver := &fakeIPSetVersioner{c.ipsetVersion, c.ipsetError} + khandler := &fakeKernelHandler{c.kmods} + r := getProxyMode(c.flag, versioner, khandler, ipsetver, kcompater) if r != c.expected { t.Errorf("Case[%d] Expected %q, got %q", i, c.expected, r) } } } -// TestNewOptionsFailures tests failure modes for NewOptions() -func TestNewOptionsFailures(t *testing.T) { - - // Create a fake scheme builder that generates an error - errString := fmt.Sprintf("Simulated error") - genError := func(scheme *k8sRuntime.Scheme) error { - return errors.New(errString) - } - fakeSchemeBuilder := k8sRuntime.NewSchemeBuilder(genError) - - simulatedErrorTest := func(target string) { - var addToScheme *func(s *k8sRuntime.Scheme) error - if target == "componentconfig" { - addToScheme = &componentconfig.AddToScheme - } else { - addToScheme = &v1alpha1.AddToScheme - } - restoreValue := *addToScheme - restore := func() { - *addToScheme = restoreValue - } - defer restore() - *addToScheme = fakeSchemeBuilder.AddToScheme - _, err := NewOptions() - assert.Error(t, err, fmt.Sprintf("Simulated error in component %s", target)) +// This is a coarse test, but it offers some modicum of confidence as the code is evolved. +func Test_getProxyModeEnableFeatureGateway(t *testing.T) { + if runtime.GOOS != "linux" { + t.Skip("skipping on non-Linux") } - // Simulate errors in calls to AddToScheme() - faultTargets := []string{"componentconfig", "v1alpha1"} - for _, target := range faultTargets { - simulatedErrorTest(target) + // enable IPVS feature gateway + utilfeature.DefaultFeatureGate.Set("SupportIPVSProxyMode=true") + + var cases = []struct { + flag string + iptablesVersion string + ipsetVersion string + kernelCompat bool + iptablesError error + ipsetError error + mods []string + expected string + }{ + { // flag says userspace + flag: "userspace", + expected: proxyModeUserspace, + }, + { // flag says iptables, error detecting version + flag: "iptables", + iptablesError: fmt.Errorf("oops!"), + expected: proxyModeUserspace, + }, + { // flag says iptables, version too low + flag: "iptables", + iptablesVersion: "0.0.0", + expected: proxyModeUserspace, + }, + { // flag says iptables, version ok, kernel not compatible + flag: "iptables", + iptablesVersion: iptables.MinCheckVersion, + kernelCompat: false, + expected: proxyModeUserspace, + }, + { // flag says iptables, version ok, kernel is compatible + flag: "iptables", + iptablesVersion: iptables.MinCheckVersion, + kernelCompat: true, + expected: proxyModeIPTables, + }, + { // detect, error + flag: "", + iptablesError: fmt.Errorf("oops!"), + expected: proxyModeUserspace, + }, + { // detect, version too low + flag: "", + iptablesVersion: "0.0.0", + expected: proxyModeUserspace, + }, + { // detect, version ok, kernel not compatible + flag: "", + iptablesVersion: iptables.MinCheckVersion, + kernelCompat: false, + expected: proxyModeUserspace, + }, + { // detect, version ok, kernel is compatible + flag: "", + iptablesVersion: iptables.MinCheckVersion, + kernelCompat: true, + expected: proxyModeIPTables, + }, + { // detect, version ok, kernel is compatible + flag: "", + iptablesVersion: iptables.MinCheckVersion, + kernelCompat: true, + expected: proxyModeIPTables, + }, + { // flag says ipvs, ipset version ok, kernel modules installed + flag: "ipvs", + mods: []string{"ip_vs", "ip_vs_rr", "ip_vs_wrr", "ip_vs_sh", "nf_conntrack_ipv4"}, + ipsetVersion: ipvs.MinIPSetCheckVersion, + expected: proxyModeIPVS, + }, + { // flag says ipvs, ipset version too low, fallback on iptables mode + flag: "ipvs", + mods: []string{"ip_vs", "ip_vs_rr", "ip_vs_wrr", "ip_vs_sh", "nf_conntrack_ipv4"}, + ipsetVersion: "0.0", + iptablesVersion: iptables.MinCheckVersion, + kernelCompat: true, + expected: proxyModeIPTables, + }, + { // flag says ipvs, bad ipset version, fallback on iptables mode + flag: "ipvs", + mods: []string{"ip_vs", "ip_vs_rr", "ip_vs_wrr", "ip_vs_sh", "nf_conntrack_ipv4"}, + ipsetVersion: "a.b.c", + iptablesVersion: iptables.MinCheckVersion, + kernelCompat: true, + expected: proxyModeIPTables, + }, + { // flag says ipvs, required kernel modules are not installed, fallback on iptables mode + flag: "ipvs", + mods: []string{"foo", "bar", "baz"}, + ipsetVersion: ipvs.MinIPSetCheckVersion, + iptablesVersion: iptables.MinCheckVersion, + kernelCompat: true, + expected: proxyModeIPTables, + }, + { // flag says ipvs, required kernel modules are not installed, iptables version too old, fallback on userspace mode + flag: "ipvs", + mods: []string{"foo", "bar", "baz"}, + ipsetVersion: ipvs.MinIPSetCheckVersion, + iptablesVersion: "0.0.0", + kernelCompat: true, + expected: proxyModeUserspace, + }, + { // flag says ipvs, ipset version too low, iptables version too old, kernel not compatible, fallback on userspace mode + flag: "ipvs", + mods: []string{"ip_vs", "ip_vs_rr", "ip_vs_wrr", "ip_vs_sh", "nf_conntrack_ipv4"}, + ipsetVersion: "0.0", + iptablesVersion: iptables.MinCheckVersion, + kernelCompat: false, + expected: proxyModeUserspace, + }, + } + for i, c := range cases { + versioner := &fakeIPTablesVersioner{c.iptablesVersion, c.iptablesError} + kcompater := &fakeKernelCompatTester{c.kernelCompat} + ipsetver := &fakeIPSetVersioner{c.ipsetVersion, c.ipsetError} + khandle := &fakeKernelHandler{c.mods} + r := getProxyMode(c.flag, versioner, khandle, ipsetver, kcompater) + if r != c.expected { + t.Errorf("Case[%d] Expected %q, got %q", i, c.expected, r) + } } } @@ -180,17 +317,14 @@ func TestProxyServerWithCleanupAndExit(t *testing.T) { "::", } for _, addr := range bindAddresses { - options, err := NewOptions() - if err != nil { - t.Fatalf("Unexpected error with address %s: %v", addr, err) - } + options := NewOptions() - options.config = &componentconfig.KubeProxyConfiguration{ + options.config = &kubeproxyconfig.KubeProxyConfiguration{ BindAddress: addr, } options.CleanupAndExit = true - proxyserver, err := NewProxyServer(options.config, options.CleanupAndExit, options.scheme, options.master) + proxyserver, err := NewProxyServer(options) assert.Nil(t, err, "unexpected error in NewProxyServer, addr: %s", addr) assert.NotNil(t, proxyserver, "nil proxy server obj, addr: %s", addr) @@ -198,7 +332,7 @@ func TestProxyServerWithCleanupAndExit(t *testing.T) { assert.True(t, proxyserver.CleanupAndExit, "false CleanupAndExit, addr: %s", addr) // Clean up config for next test case - configz.Delete("componentconfig") + configz.Delete(kubeproxyconfig.GroupName) } } @@ -242,10 +376,10 @@ func TestGetConntrackMax(t *testing.T) { } for i, tc := range testCases { - cfg := componentconfig.KubeProxyConntrackConfiguration{ - Min: tc.min, - Max: tc.max, - MaxPerCore: tc.maxPerCore, + cfg := kubeproxyconfig.KubeProxyConntrackConfiguration{ + Min: utilpointer.Int32Ptr(tc.min), + Max: utilpointer.Int32Ptr(tc.max), + MaxPerCore: utilpointer.Int32Ptr(tc.maxPerCore), } x, e := getConntrackMax(cfg) if e != nil { @@ -263,7 +397,7 @@ func TestGetConntrackMax(t *testing.T) { // TestLoadConfig tests proper operation of loadConfig() func TestLoadConfig(t *testing.T) { - yamlTemplate := `apiVersion: componentconfig/v1alpha1 + yamlTemplate := `apiVersion: kubeproxy.config.k8s.io/v1alpha1 bindAddress: %s clientConnection: acceptContentTypes: "abc" @@ -372,9 +506,9 @@ udpTimeoutMilliseconds: 123ms // Surrounding double quotes will get stripped by the yaml parser. expBindAddr = expBindAddr[1 : len(tc.bindAddress)-1] } - expected := &componentconfig.KubeProxyConfiguration{ + expected := &kubeproxyconfig.KubeProxyConfiguration{ BindAddress: expBindAddr, - ClientConnection: componentconfig.ClientConnectionConfiguration{ + ClientConnection: kubeproxyconfig.ClientConnectionConfiguration{ AcceptContentTypes: "abc", Burst: 100, ContentType: "content-type", @@ -383,36 +517,35 @@ udpTimeoutMilliseconds: 123ms }, ClusterCIDR: tc.clusterCIDR, ConfigSyncPeriod: metav1.Duration{Duration: 15 * time.Second}, - Conntrack: componentconfig.KubeProxyConntrackConfiguration{ - Max: 4, - MaxPerCore: 2, - Min: 1, - TCPCloseWaitTimeout: metav1.Duration{Duration: 10 * time.Second}, - TCPEstablishedTimeout: metav1.Duration{Duration: 20 * time.Second}, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + Max: utilpointer.Int32Ptr(4), + MaxPerCore: utilpointer.Int32Ptr(2), + Min: utilpointer.Int32Ptr(1), + TCPCloseWaitTimeout: &metav1.Duration{Duration: 10 * time.Second}, + TCPEstablishedTimeout: &metav1.Duration{Duration: 20 * time.Second}, }, FeatureGates: "all", HealthzBindAddress: tc.healthzBindAddress, HostnameOverride: "foo", - IPTables: componentconfig.KubeProxyIPTablesConfiguration{ + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ MasqueradeAll: true, MasqueradeBit: utilpointer.Int32Ptr(17), MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second}, SyncPeriod: metav1.Duration{Duration: 60 * time.Second}, }, - IPVS: componentconfig.KubeProxyIPVSConfiguration{ + IPVS: kubeproxyconfig.KubeProxyIPVSConfiguration{ MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second}, SyncPeriod: metav1.Duration{Duration: 60 * time.Second}, }, MetricsBindAddress: tc.metricsBindAddress, - Mode: componentconfig.ProxyMode(tc.mode), + Mode: kubeproxyconfig.ProxyMode(tc.mode), OOMScoreAdj: utilpointer.Int32Ptr(17), PortRange: "2-7", ResourceContainer: "/foo", UDPIdleTimeout: metav1.Duration{Duration: 123 * time.Millisecond}, } - options, err := NewOptions() - assert.NoError(t, err, "unexpected error for %s: %v", tc.name, err) + options := NewOptions() yaml := fmt.Sprintf( yamlTemplate, tc.bindAddress, tc.clusterCIDR, @@ -440,7 +573,7 @@ func TestLoadConfigFailures(t *testing.T) { { name: "Bad config type test", config: "kind: KubeSchedulerConfiguration", - expErr: "unexpected config type", + expErr: "no kind", }, { name: "Missing quotes around :: bindAddress", @@ -448,9 +581,9 @@ func TestLoadConfigFailures(t *testing.T) { expErr: "mapping values are not allowed in this context", }, } - version := "apiVersion: componentconfig/v1alpha1" + version := "apiVersion: kubeproxy.config.k8s.io/v1alpha1" for _, tc := range testCases { - options, _ := NewOptions() + options := NewOptions() config := fmt.Sprintf("%s\n%s", version, tc.config) _, err := options.loadConfig([]byte(config)) if assert.Error(t, err, tc.name) { diff --git a/cmd/kube-proxy/app/server_windows.go b/cmd/kube-proxy/app/server_windows.go index f4f46386a74..1fb4f4ce7ec 100644 --- a/cmd/kube-proxy/app/server_windows.go +++ b/cmd/kube-proxy/app/server_windows.go @@ -31,8 +31,8 @@ import ( "k8s.io/apimachinery/pkg/types" utilnet "k8s.io/apimachinery/pkg/util/net" "k8s.io/client-go/tools/record" - "k8s.io/kubernetes/pkg/apis/componentconfig" "k8s.io/kubernetes/pkg/proxy" + proxyconfigapi "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig" proxyconfig "k8s.io/kubernetes/pkg/proxy/config" "k8s.io/kubernetes/pkg/proxy/healthcheck" "k8s.io/kubernetes/pkg/proxy/winkernel" @@ -46,12 +46,16 @@ import ( ) // NewProxyServer returns a new ProxyServer. -func NewProxyServer(config *componentconfig.KubeProxyConfiguration, cleanupAndExit bool, scheme *runtime.Scheme, master string) (*ProxyServer, error) { +func NewProxyServer(o *Options) (*ProxyServer, error) { + return newProxyServer(o.config, o.CleanupAndExit, o.scheme, o.master) +} + +func newProxyServer(config *proxyconfigapi.KubeProxyConfiguration, cleanupAndExit bool, scheme *runtime.Scheme, master string) (*ProxyServer, error) { if config == nil { return nil, errors.New("config is required") } - if c, err := configz.New("componentconfig"); err == nil { + if c, err := configz.New(proxyconfigapi.GroupName); err == nil { c.Set(config) } else { return nil, fmt.Errorf("unable to register configz: %s", err) diff --git a/cmd/kube-proxy/app/validation.go b/cmd/kube-proxy/app/validation.go deleted file mode 100644 index f899bd35c71..00000000000 --- a/cmd/kube-proxy/app/validation.go +++ /dev/null @@ -1,158 +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 app - -import ( - "fmt" - "net" - "strconv" - "strings" - - utilnet "k8s.io/apimachinery/pkg/util/net" - "k8s.io/apimachinery/pkg/util/validation/field" - apivalidation "k8s.io/kubernetes/pkg/api/validation" - "k8s.io/kubernetes/pkg/apis/componentconfig" -) - -// Validate validates the configuration of kube-proxy -func Validate(config *componentconfig.KubeProxyConfiguration) field.ErrorList { - allErrs := field.ErrorList{} - - newPath := field.NewPath("KubeProxyConfiguration") - - allErrs = append(allErrs, validateKubeProxyIPTablesConfiguration(config.IPTables, newPath.Child("KubeProxyIPTablesConfiguration"))...) - allErrs = append(allErrs, validateKubeProxyConntrackConfiguration(config.Conntrack, newPath.Child("KubeProxyConntrackConfiguration"))...) - allErrs = append(allErrs, validateProxyMode(config.Mode, newPath.Child("Mode"))...) - allErrs = append(allErrs, validateClientConnectionConfiguration(config.ClientConnection, newPath.Child("ClientConnection"))...) - - if config.OOMScoreAdj != nil && (*config.OOMScoreAdj < -1000 || *config.OOMScoreAdj > 1000) { - allErrs = append(allErrs, field.Invalid(newPath.Child("OOMScoreAdj"), *config.OOMScoreAdj, "must be within the range [-1000, 1000]")) - } - - if config.UDPIdleTimeout.Duration <= 0 { - allErrs = append(allErrs, field.Invalid(newPath.Child("UDPIdleTimeout"), config.UDPIdleTimeout, "must be greater than 0")) - } - - if config.ConfigSyncPeriod.Duration <= 0 { - allErrs = append(allErrs, field.Invalid(newPath.Child("ConfigSyncPeriod"), config.ConfigSyncPeriod, "must be greater than 0")) - } - - if net.ParseIP(config.BindAddress) == nil { - allErrs = append(allErrs, field.Invalid(newPath.Child("BindAddress"), config.BindAddress, "not a valid textual representation of an IP address")) - } - - allErrs = append(allErrs, validateHostPort(config.HealthzBindAddress, newPath.Child("HealthzBindAddress"))...) - allErrs = append(allErrs, validateHostPort(config.MetricsBindAddress, newPath.Child("MetricsBindAddress"))...) - - if config.ClusterCIDR != "" { - if _, _, err := net.ParseCIDR(config.ClusterCIDR); err != nil { - allErrs = append(allErrs, field.Invalid(newPath.Child("ClusterCIDR"), config.ClusterCIDR, "must be a valid CIDR block (e.g. 10.100.0.0/16)")) - } - } - - if _, err := utilnet.ParsePortRange(config.PortRange); err != nil { - allErrs = append(allErrs, field.Invalid(newPath.Child("PortRange"), config.PortRange, "must be a valid port range (e.g. 300-2000)")) - } - - return allErrs -} - -func validateKubeProxyIPTablesConfiguration(config componentconfig.KubeProxyIPTablesConfiguration, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - if config.MasqueradeBit != nil && (*config.MasqueradeBit < 0 || *config.MasqueradeBit > 31) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("MasqueradeBit"), config.MasqueradeBit, "must be within the range [0, 31]")) - } - - if config.SyncPeriod.Duration <= 0 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("SyncPeriod"), config.SyncPeriod, "must be greater than 0")) - } - - if config.MinSyncPeriod.Duration < 0 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("MinSyncPeriod"), config.MinSyncPeriod, "must be greater than or equal to 0")) - } - - return allErrs -} - -func validateKubeProxyConntrackConfiguration(config componentconfig.KubeProxyConntrackConfiguration, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - if config.Max < 0 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("Max"), config.Max, "must be greater than or equal to 0")) - } - - if config.MaxPerCore < 0 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("MaxPerCore"), config.MaxPerCore, "must be greater than or equal to 0")) - } - - if config.Min < 0 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("Min"), config.Min, "must be greater than or equal to 0")) - } - - if config.TCPEstablishedTimeout.Duration <= 0 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("TCPEstablishedTimeout"), config.TCPEstablishedTimeout, "must be greater than 0")) - } - - if config.TCPCloseWaitTimeout.Duration <= 0 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("TCPCloseWaitTimeout"), config.TCPCloseWaitTimeout, "must be greater than 0")) - } - - return allErrs -} - -func validateProxyMode(mode componentconfig.ProxyMode, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - switch mode { - case componentconfig.ProxyModeUserspace: - case componentconfig.ProxyModeIPTables: - case "": - default: - modes := []string{string(componentconfig.ProxyModeUserspace), string(componentconfig.ProxyModeIPTables)} - errMsg := fmt.Sprintf("must be %s or blank (blank means the best-available proxy (currently iptables)", strings.Join(modes, ",")) - allErrs = append(allErrs, field.Invalid(fldPath.Child("ProxyMode"), string(mode), errMsg)) - } - return allErrs -} - -func validateClientConnectionConfiguration(config componentconfig.ClientConnectionConfiguration, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(config.Burst), fldPath.Child("Burst"))...) - return allErrs -} - -func validateHostPort(input string, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - hostIP, port, err := net.SplitHostPort(input) - if err != nil { - allErrs = append(allErrs, field.Invalid(fldPath, input, "must be IP:port")) - return allErrs - } - - if ip := net.ParseIP(hostIP); ip == nil { - allErrs = append(allErrs, field.Invalid(fldPath, hostIP, "must be a valid IP")) - } - - if p, err := strconv.Atoi(port); err != nil { - allErrs = append(allErrs, field.Invalid(fldPath, port, "must be a valid port")) - } else if p < 1 || p > 65535 { - allErrs = append(allErrs, field.Invalid(fldPath, port, "must be a valid port")) - } - - return allErrs -} diff --git a/cmd/kube-proxy/app/validation_test.go b/cmd/kube-proxy/app/validation_test.go deleted file mode 100644 index cd30adcdfa8..00000000000 --- a/cmd/kube-proxy/app/validation_test.go +++ /dev/null @@ -1,403 +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 app - -import ( - "strings" - "testing" - "time" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/apis/componentconfig" -) - -func TestValidateKubeProxyConfiguration(t *testing.T) { - successCases := []componentconfig.KubeProxyConfiguration{ - { - BindAddress: "192.168.59.103", - HealthzBindAddress: "0.0.0.0:10256", - MetricsBindAddress: "127.0.0.1:10249", - ClusterCIDR: "192.168.59.0/24", - UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, - ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, - IPTables: componentconfig.KubeProxyIPTablesConfiguration{ - MasqueradeAll: true, - SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, - }, - Conntrack: componentconfig.KubeProxyConntrackConfiguration{ - Max: int32(2), - MaxPerCore: int32(1), - Min: int32(1), - TCPEstablishedTimeout: metav1.Duration{Duration: 5 * time.Second}, - TCPCloseWaitTimeout: metav1.Duration{Duration: 5 * time.Second}, - }, - }, - } - - for _, successCase := range successCases { - if errs := Validate(&successCase); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - } - - errorCases := []struct { - config componentconfig.KubeProxyConfiguration - msg string - }{ - { - config: componentconfig.KubeProxyConfiguration{ - // only BindAddress is invalid - BindAddress: "10.10.12.11:2000", - HealthzBindAddress: "0.0.0.0:10256", - MetricsBindAddress: "127.0.0.1:10249", - ClusterCIDR: "192.168.59.0/24", - UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, - ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, - IPTables: componentconfig.KubeProxyIPTablesConfiguration{ - MasqueradeAll: true, - SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, - }, - Conntrack: componentconfig.KubeProxyConntrackConfiguration{ - Max: int32(2), - MaxPerCore: int32(1), - Min: int32(1), - TCPEstablishedTimeout: metav1.Duration{Duration: 5 * time.Second}, - TCPCloseWaitTimeout: metav1.Duration{Duration: 5 * time.Second}, - }, - }, - msg: "not a valid textual representation of an IP address", - }, - { - config: componentconfig.KubeProxyConfiguration{ - BindAddress: "10.10.12.11", - // only HealthzBindAddress is invalid - HealthzBindAddress: "0.0.0.0", - MetricsBindAddress: "127.0.0.1:10249", - ClusterCIDR: "192.168.59.0/24", - UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, - ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, - IPTables: componentconfig.KubeProxyIPTablesConfiguration{ - MasqueradeAll: true, - SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, - }, - Conntrack: componentconfig.KubeProxyConntrackConfiguration{ - Max: int32(2), - MaxPerCore: int32(1), - Min: int32(1), - TCPEstablishedTimeout: metav1.Duration{Duration: 5 * time.Second}, - TCPCloseWaitTimeout: metav1.Duration{Duration: 5 * time.Second}, - }, - }, - msg: "must be IP:port", - }, - { - config: componentconfig.KubeProxyConfiguration{ - BindAddress: "10.10.12.11", - HealthzBindAddress: "0.0.0.0:12345", - // only HealthzBindAddress is invalid - MetricsBindAddress: "127.0.0.1", - ClusterCIDR: "192.168.59.0/24", - UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, - ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, - IPTables: componentconfig.KubeProxyIPTablesConfiguration{ - MasqueradeAll: true, - SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, - }, - Conntrack: componentconfig.KubeProxyConntrackConfiguration{ - Max: int32(2), - MaxPerCore: int32(1), - Min: int32(1), - TCPEstablishedTimeout: metav1.Duration{Duration: 5 * time.Second}, - TCPCloseWaitTimeout: metav1.Duration{Duration: 5 * time.Second}, - }, - }, - msg: "must be IP:port", - }, - { - config: componentconfig.KubeProxyConfiguration{ - BindAddress: "10.10.12.11", - HealthzBindAddress: "0.0.0.0:12345", - MetricsBindAddress: "127.0.0.1:10249", - // only ClusterCIDR is invalid - ClusterCIDR: "192.168.59.0", - UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, - ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, - IPTables: componentconfig.KubeProxyIPTablesConfiguration{ - MasqueradeAll: true, - SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, - }, - Conntrack: componentconfig.KubeProxyConntrackConfiguration{ - Max: int32(2), - MaxPerCore: int32(1), - Min: int32(1), - TCPEstablishedTimeout: metav1.Duration{Duration: 5 * time.Second}, - TCPCloseWaitTimeout: metav1.Duration{Duration: 5 * time.Second}, - }, - }, - msg: "must be a valid CIDR block (e.g. 10.100.0.0/16)", - }, - { - config: componentconfig.KubeProxyConfiguration{ - BindAddress: "10.10.12.11", - HealthzBindAddress: "0.0.0.0:12345", - MetricsBindAddress: "127.0.0.1:10249", - ClusterCIDR: "192.168.59.0/24", - // only UDPIdleTimeout is invalid - UDPIdleTimeout: metav1.Duration{Duration: -1 * time.Second}, - ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, - IPTables: componentconfig.KubeProxyIPTablesConfiguration{ - MasqueradeAll: true, - SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, - }, - Conntrack: componentconfig.KubeProxyConntrackConfiguration{ - Max: int32(2), - MaxPerCore: int32(1), - Min: int32(1), - TCPEstablishedTimeout: metav1.Duration{Duration: 5 * time.Second}, - TCPCloseWaitTimeout: metav1.Duration{Duration: 5 * time.Second}, - }, - }, - msg: "must be greater than 0", - }, - { - config: componentconfig.KubeProxyConfiguration{ - BindAddress: "10.10.12.11", - HealthzBindAddress: "0.0.0.0:12345", - MetricsBindAddress: "127.0.0.1:10249", - ClusterCIDR: "192.168.59.0/24", - UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, - // only ConfigSyncPeriod is invalid - ConfigSyncPeriod: metav1.Duration{Duration: -1 * time.Second}, - IPTables: componentconfig.KubeProxyIPTablesConfiguration{ - MasqueradeAll: true, - SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, - }, - Conntrack: componentconfig.KubeProxyConntrackConfiguration{ - Max: int32(2), - MaxPerCore: int32(1), - Min: int32(1), - TCPEstablishedTimeout: metav1.Duration{Duration: 5 * time.Second}, - TCPCloseWaitTimeout: metav1.Duration{Duration: 5 * time.Second}, - }, - }, - msg: "must be greater than 0", - }, - } - - for _, errorCase := range errorCases { - if errs := Validate(&errorCase.config); len(errs) == 0 { - t.Errorf("expected failure for %s", errorCase.msg) - } else if !strings.Contains(errs[0].Error(), errorCase.msg) { - t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg) - } - } -} - -func TestValidateKubeProxyIPTablesConfiguration(t *testing.T) { - valid := int32(5) - successCases := []componentconfig.KubeProxyIPTablesConfiguration{ - { - MasqueradeAll: true, - SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, - }, - { - MasqueradeBit: &valid, - MasqueradeAll: true, - SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, - }, - } - newPath := field.NewPath("KubeProxyConfiguration") - for _, successCase := range successCases { - if errs := validateKubeProxyIPTablesConfiguration(successCase, newPath.Child("KubeProxyIPTablesConfiguration")); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - } - - invalid := int32(-10) - errorCases := []struct { - config componentconfig.KubeProxyIPTablesConfiguration - msg string - }{ - { - config: componentconfig.KubeProxyIPTablesConfiguration{ - MasqueradeAll: true, - SyncPeriod: metav1.Duration{Duration: -5 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, - }, - msg: "must be greater than 0", - }, - { - config: componentconfig.KubeProxyIPTablesConfiguration{ - MasqueradeBit: &valid, - MasqueradeAll: true, - SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: -1 * time.Second}, - }, - msg: "must be greater than or equal to 0", - }, - { - config: componentconfig.KubeProxyIPTablesConfiguration{ - MasqueradeBit: &invalid, - MasqueradeAll: true, - SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, - }, - msg: "must be within the range [0, 31]", - }, - } - - for _, errorCase := range errorCases { - if errs := validateKubeProxyIPTablesConfiguration(errorCase.config, newPath.Child("KubeProxyIPTablesConfiguration")); len(errs) == 0 { - t.Errorf("expected failure for %s", errorCase.msg) - } else if !strings.Contains(errs[0].Error(), errorCase.msg) { - t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg) - } - } -} - -func TestValidateKubeProxyConntrackConfiguration(t *testing.T) { - successCases := []componentconfig.KubeProxyConntrackConfiguration{ - { - Max: int32(2), - MaxPerCore: int32(1), - Min: int32(1), - TCPEstablishedTimeout: metav1.Duration{Duration: 5 * time.Second}, - TCPCloseWaitTimeout: metav1.Duration{Duration: 5 * time.Second}, - }, - { - Max: 0, - MaxPerCore: 0, - Min: 0, - TCPEstablishedTimeout: metav1.Duration{Duration: 5 * time.Second}, - TCPCloseWaitTimeout: metav1.Duration{Duration: 60 * time.Second}, - }, - } - newPath := field.NewPath("KubeProxyConfiguration") - for _, successCase := range successCases { - if errs := validateKubeProxyConntrackConfiguration(successCase, newPath.Child("KubeProxyConntrackConfiguration")); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - } - - errorCases := []struct { - config componentconfig.KubeProxyConntrackConfiguration - msg string - }{ - { - config: componentconfig.KubeProxyConntrackConfiguration{ - Max: int32(-1), - MaxPerCore: int32(1), - Min: int32(1), - TCPEstablishedTimeout: metav1.Duration{Duration: 5 * time.Second}, - TCPCloseWaitTimeout: metav1.Duration{Duration: 5 * time.Second}, - }, - msg: "must be greater than or equal to 0", - }, - { - config: componentconfig.KubeProxyConntrackConfiguration{ - Max: int32(2), - MaxPerCore: int32(-1), - Min: int32(1), - TCPEstablishedTimeout: metav1.Duration{Duration: 5 * time.Second}, - TCPCloseWaitTimeout: metav1.Duration{Duration: 5 * time.Second}, - }, - msg: "must be greater than or equal to 0", - }, - { - config: componentconfig.KubeProxyConntrackConfiguration{ - Max: int32(2), - MaxPerCore: int32(1), - Min: int32(-1), - TCPEstablishedTimeout: metav1.Duration{Duration: 5 * time.Second}, - TCPCloseWaitTimeout: metav1.Duration{Duration: 5 * time.Second}, - }, - msg: "must be greater than or equal to 0", - }, - { - config: componentconfig.KubeProxyConntrackConfiguration{ - Max: int32(4), - MaxPerCore: int32(1), - Min: int32(3), - TCPEstablishedTimeout: metav1.Duration{Duration: -5 * time.Second}, - TCPCloseWaitTimeout: metav1.Duration{Duration: 5 * time.Second}, - }, - msg: "must be greater than 0", - }, - { - config: componentconfig.KubeProxyConntrackConfiguration{ - Max: int32(4), - MaxPerCore: int32(1), - Min: int32(3), - TCPEstablishedTimeout: metav1.Duration{Duration: 5 * time.Second}, - TCPCloseWaitTimeout: metav1.Duration{Duration: -5 * time.Second}, - }, - msg: "must be greater than 0", - }, - } - - for _, errorCase := range errorCases { - if errs := validateKubeProxyConntrackConfiguration(errorCase.config, newPath.Child("KubeProxyConntrackConfiguration")); len(errs) == 0 { - t.Errorf("expected failure for %s", errorCase.msg) - } else if !strings.Contains(errs[0].Error(), errorCase.msg) { - t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg) - } - } -} - -func TestValidateProxyMode(t *testing.T) { - newPath := field.NewPath("KubeProxyConfiguration") - - successCases := []componentconfig.ProxyMode{ - componentconfig.ProxyModeUserspace, - componentconfig.ProxyModeIPTables, - componentconfig.ProxyMode(""), - } - - for _, successCase := range successCases { - if errs := validateProxyMode(successCase, newPath.Child("ProxyMode")); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - } - - errorCases := []struct { - mode componentconfig.ProxyMode - msg string - }{ - { - mode: componentconfig.ProxyMode("non-existing"), - msg: "or blank (blank means the best-available proxy (currently iptables)", - }, - } - - for _, errorCase := range errorCases { - if errs := validateProxyMode(errorCase.mode, newPath.Child("ProxyMode")); len(errs) == 0 { - t.Errorf("expected failure for %s", errorCase.msg) - } else if !strings.Contains(errs[0].Error(), errorCase.msg) { - t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg) - } - } -} diff --git a/cmd/kube-proxy/proxy.go b/cmd/kube-proxy/proxy.go index 9ec68a51fed..a9c958d6b2d 100644 --- a/cmd/kube-proxy/proxy.go +++ b/cmd/kube-proxy/proxy.go @@ -18,6 +18,7 @@ package main import ( goflag "flag" + "fmt" "os" "github.com/spf13/pflag" @@ -42,6 +43,7 @@ func main() { defer logs.FlushLogs() if err := command.Execute(); err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } } diff --git a/cmd/kube-scheduler/BUILD b/cmd/kube-scheduler/BUILD new file mode 100644 index 00000000000..88e75f4e819 --- /dev/null +++ b/cmd/kube-scheduler/BUILD @@ -0,0 +1,45 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_binary", + "go_library", +) +load("//pkg/version:def.bzl", "version_x_defs") + +go_binary( + name = "kube-scheduler", + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/cmd/kube-scheduler", + pure = "on", + x_defs = version_x_defs(), +) + +go_library( + name = "go_default_library", + srcs = ["scheduler.go"], + importpath = "k8s.io/kubernetes/cmd/kube-scheduler", + deps = [ + "//cmd/kube-scheduler/app:go_default_library", + "//pkg/client/metrics/prometheus:go_default_library", + "//pkg/version/prometheus:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/logs:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//cmd/kube-scheduler/app:all-srcs", + ], + tags = ["automanaged"], +) diff --git a/plugin/cmd/kube-scheduler/OWNERS b/cmd/kube-scheduler/OWNERS similarity index 100% rename from plugin/cmd/kube-scheduler/OWNERS rename to cmd/kube-scheduler/OWNERS diff --git a/cmd/kube-scheduler/app/BUILD b/cmd/kube-scheduler/app/BUILD new file mode 100644 index 00000000000..990d72a0bcc --- /dev/null +++ b/cmd/kube-scheduler/app/BUILD @@ -0,0 +1,66 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = ["server.go"], + importpath = "k8s.io/kubernetes/cmd/kube-scheduler/app", + deps = [ + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/componentconfig:go_default_library", + "//pkg/apis/componentconfig/v1alpha1:go_default_library", + "//pkg/client/leaderelectionconfig:go_default_library", + "//pkg/controller:go_default_library", + "//pkg/features:go_default_library", + "//pkg/kubectl/cmd/util:go_default_library", + "//pkg/master/ports:go_default_library", + "//pkg/scheduler:go_default_library", + "//pkg/scheduler/algorithmprovider:go_default_library", + "//pkg/scheduler/api:go_default_library", + "//pkg/scheduler/api/latest:go_default_library", + "//pkg/scheduler/factory:go_default_library", + "//pkg/util/configz:go_default_library", + "//pkg/version:go_default_library", + "//pkg/version/verflag:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", + "//vendor/github.com/spf13/cobra:go_default_library", + "//vendor/github.com/spf13/pflag:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/apiserver/pkg/server/healthz:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + "//vendor/k8s.io/client-go/informers:go_default_library", + "//vendor/k8s.io/client-go/informers/core/v1:go_default_library", + "//vendor/k8s.io/client-go/informers/storage/v1:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", + "//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library", + "//vendor/k8s.io/client-go/tools/leaderelection:go_default_library", + "//vendor/k8s.io/client-go/tools/leaderelection/resourcelock:go_default_library", + "//vendor/k8s.io/client-go/tools/record:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/cmd/kube-scheduler/app/server.go b/cmd/kube-scheduler/app/server.go new file mode 100644 index 00000000000..a80a376f9f3 --- /dev/null +++ b/cmd/kube-scheduler/app/server.go @@ -0,0 +1,706 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES 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 implements a Server object for running the scheduler. +package app + +import ( + "errors" + "fmt" + "io/ioutil" + "net" + "net/http" + "net/http/pprof" + "os" + "reflect" + goruntime "runtime" + "strconv" + "time" + + "k8s.io/api/core/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" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apiserver/pkg/server/healthz" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/client-go/informers" + coreinformers "k8s.io/client-go/informers/core/v1" + storageinformers "k8s.io/client-go/informers/storage/v1" + clientset "k8s.io/client-go/kubernetes" + v1core "k8s.io/client-go/kubernetes/typed/core/v1" + restclient "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + "k8s.io/client-go/tools/leaderelection" + "k8s.io/client-go/tools/leaderelection/resourcelock" + "k8s.io/client-go/tools/record" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/apis/componentconfig" + componentconfigv1alpha1 "k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1" + "k8s.io/kubernetes/pkg/client/leaderelectionconfig" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/features" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/master/ports" + "k8s.io/kubernetes/pkg/scheduler" + "k8s.io/kubernetes/pkg/scheduler/algorithmprovider" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + latestschedulerapi "k8s.io/kubernetes/pkg/scheduler/api/latest" + "k8s.io/kubernetes/pkg/util/configz" + "k8s.io/kubernetes/pkg/version" + "k8s.io/kubernetes/pkg/version/verflag" + + "k8s.io/kubernetes/pkg/scheduler/factory" + + "github.com/golang/glog" + "github.com/prometheus/client_golang/prometheus" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +// SchedulerServer has all the context and params needed to run a Scheduler +type Options struct { + // ConfigFile is the location of the scheduler server's configuration file. + ConfigFile string + + // config is the scheduler server's configuration object. + config *componentconfig.KubeSchedulerConfiguration + + scheme *runtime.Scheme + codecs serializer.CodecFactory + + // The fields below here are placeholders for flags that can't be directly + // mapped into componentconfig.KubeSchedulerConfiguration. + // + // TODO remove these fields once the deprecated flags are removed. + + // master is the address of the Kubernetes API server (overrides any + // value in kubeconfig). + master string + healthzAddress string + healthzPort int32 + policyConfigFile string + policyConfigMapName string + policyConfigMapNamespace string + useLegacyPolicyConfig bool + algorithmProvider string +} + +// AddFlags adds flags for a specific SchedulerServer to the specified FlagSet +func (o *Options) AddFlags(fs *pflag.FlagSet) { + fs.StringVar(&o.ConfigFile, "config", o.ConfigFile, "The path to the configuration file.") + + // All flags below here are deprecated and will eventually be removed. + + fs.Int32Var(&o.healthzPort, "port", ports.SchedulerPort, "The port that the scheduler's http service runs on") + fs.StringVar(&o.healthzAddress, "address", o.healthzAddress, "The IP address to serve on (set to 0.0.0.0 for all interfaces)") + fs.StringVar(&o.algorithmProvider, "algorithm-provider", o.algorithmProvider, "The scheduling algorithm provider to use, one of: "+factory.ListAlgorithmProviders()) + fs.StringVar(&o.policyConfigFile, "policy-config-file", o.policyConfigFile, "File with scheduler policy configuration. This file is used if policy ConfigMap is not provided or --use-legacy-policy-config==true") + usage := fmt.Sprintf("Name of the ConfigMap object that contains scheduler's policy configuration. It must exist in the system namespace before scheduler initialization if --use-legacy-policy-config==false. The config must be provided as the value of an element in 'Data' map with the key='%v'", componentconfig.SchedulerPolicyConfigMapKey) + fs.StringVar(&o.policyConfigMapName, "policy-configmap", o.policyConfigMapName, usage) + fs.StringVar(&o.policyConfigMapNamespace, "policy-configmap-namespace", o.policyConfigMapNamespace, "The namespace where policy ConfigMap is located. The system namespace will be used if this is not provided or is empty.") + fs.BoolVar(&o.useLegacyPolicyConfig, "use-legacy-policy-config", false, "When set to true, scheduler will ignore policy ConfigMap and uses policy config file") + fs.BoolVar(&o.config.EnableProfiling, "profiling", o.config.EnableProfiling, "Enable profiling via web interface host:port/debug/pprof/") + fs.BoolVar(&o.config.EnableContentionProfiling, "contention-profiling", o.config.EnableContentionProfiling, "Enable lock contention profiling, if profiling is enabled") + fs.StringVar(&o.master, "master", o.master, "The address of the Kubernetes API server (overrides any value in kubeconfig)") + fs.StringVar(&o.config.ClientConnection.KubeConfigFile, "kubeconfig", o.config.ClientConnection.KubeConfigFile, "Path to kubeconfig file with authorization and master location information.") + fs.StringVar(&o.config.ClientConnection.ContentType, "kube-api-content-type", o.config.ClientConnection.ContentType, "Content type of requests sent to apiserver.") + fs.Float32Var(&o.config.ClientConnection.QPS, "kube-api-qps", o.config.ClientConnection.QPS, "QPS to use while talking with kubernetes apiserver") + fs.Int32Var(&o.config.ClientConnection.Burst, "kube-api-burst", o.config.ClientConnection.Burst, "Burst to use while talking with kubernetes apiserver") + fs.StringVar(&o.config.SchedulerName, "scheduler-name", o.config.SchedulerName, "Name of the scheduler, used to select which pods will be processed by this scheduler, based on pod's \"spec.SchedulerName\".") + fs.StringVar(&o.config.LeaderElection.LockObjectNamespace, "lock-object-namespace", o.config.LeaderElection.LockObjectNamespace, "Define the namespace of the lock object.") + fs.StringVar(&o.config.LeaderElection.LockObjectName, "lock-object-name", o.config.LeaderElection.LockObjectName, "Define the name of the lock object.") + fs.Int32Var(&o.config.HardPodAffinitySymmetricWeight, "hard-pod-affinity-symmetric-weight", o.config.HardPodAffinitySymmetricWeight, + "RequiredDuringScheduling affinity is not symmetric, but there is an implicit PreferredDuringScheduling affinity rule corresponding "+ + "to every RequiredDuringScheduling affinity rule. --hard-pod-affinity-symmetric-weight represents the weight of implicit PreferredDuringScheduling affinity rule.") + fs.MarkDeprecated("hard-pod-affinity-symmetric-weight", "This option was moved to the policy configuration file") + fs.StringVar(&o.config.FailureDomains, "failure-domains", o.config.FailureDomains, "Indicate the \"all topologies\" set for an empty topologyKey when it's used for PreferredDuringScheduling pod anti-affinity.") + fs.MarkDeprecated("failure-domains", "Doesn't have any effect. Will be removed in future version.") + leaderelectionconfig.BindFlags(&o.config.LeaderElection.LeaderElectionConfiguration, fs) + utilfeature.DefaultFeatureGate.AddFlag(fs) +} + +func NewOptions() (*Options, error) { + o := &Options{ + config: new(componentconfig.KubeSchedulerConfiguration), + } + + o.scheme = runtime.NewScheme() + o.codecs = serializer.NewCodecFactory(o.scheme) + + if err := componentconfig.AddToScheme(o.scheme); err != nil { + return nil, err + } + if err := componentconfigv1alpha1.AddToScheme(o.scheme); err != nil { + return nil, err + } + + return o, nil +} + +func (o *Options) Complete() error { + if len(o.ConfigFile) == 0 { + glog.Warning("WARNING: all flags other than --config are deprecated. Please begin using a config file ASAP.") + o.applyDeprecatedHealthzAddressToConfig() + o.applyDeprecatedHealthzPortToConfig() + o.applyDeprecatedAlgorithmSourceOptionsToConfig() + } + + return nil +} + +// applyDeprecatedHealthzAddressToConfig sets o.config.HealthzBindAddress and +// o.config.MetricsBindAddress from flags passed on the command line based on +// the following rules: +// +// 1. If --address is empty, leave the config as-is. +// 2. Otherwise, use the value of --address for the address portion of +// o.config.HealthzBindAddress +func (o *Options) applyDeprecatedHealthzAddressToConfig() { + if len(o.healthzAddress) == 0 { + return + } + + _, port, err := net.SplitHostPort(o.config.HealthzBindAddress) + if err != nil { + glog.Fatalf("invalid healthz bind address %q: %v", o.config.HealthzBindAddress, err) + } + o.config.HealthzBindAddress = net.JoinHostPort(o.healthzAddress, port) + o.config.MetricsBindAddress = net.JoinHostPort(o.healthzAddress, port) +} + +// applyDeprecatedHealthzPortToConfig sets o.config.HealthzBindAddress and +// o.config.MetricsBindAddress from flags passed on the command line based on +// the following rules: +// +// 1. If --port is -1, disable the healthz server. +// 2. Otherwise, use the value of --port for the port portion of +// o.config.HealthzBindAddress +func (o *Options) applyDeprecatedHealthzPortToConfig() { + if o.healthzPort == -1 { + o.config.HealthzBindAddress = "" + return + } + + host, _, err := net.SplitHostPort(o.config.HealthzBindAddress) + if err != nil { + glog.Fatalf("invalid healthz bind address %q: %v", o.config.HealthzBindAddress, err) + } + o.config.HealthzBindAddress = net.JoinHostPort(host, strconv.Itoa(int(o.healthzPort))) + o.config.MetricsBindAddress = net.JoinHostPort(host, strconv.Itoa(int(o.healthzPort))) +} + +// applyDeprecatedAlgorithmSourceOptionsToConfig sets o.config.AlgorithmSource from +// flags passed on the command line in the following precedence order: +// +// 1. --use-legacy-policy-config to use a policy file. +// 2. --policy-configmap to use a policy config map value. +// 3. --algorithm-provider to use a named algorithm provider. +func (o *Options) applyDeprecatedAlgorithmSourceOptionsToConfig() { + switch { + case o.useLegacyPolicyConfig: + o.config.AlgorithmSource = componentconfig.SchedulerAlgorithmSource{ + Policy: &componentconfig.SchedulerPolicySource{ + File: &componentconfig.SchedulerPolicyFileSource{ + Path: o.policyConfigFile, + }, + }, + } + case len(o.policyConfigMapName) > 0: + o.config.AlgorithmSource = componentconfig.SchedulerAlgorithmSource{ + Policy: &componentconfig.SchedulerPolicySource{ + ConfigMap: &componentconfig.SchedulerPolicyConfigMapSource{ + Name: o.policyConfigMapName, + Namespace: o.policyConfigMapNamespace, + }, + }, + } + case len(o.algorithmProvider) > 0: + o.config.AlgorithmSource = componentconfig.SchedulerAlgorithmSource{ + Provider: &o.algorithmProvider, + } + } +} + +// Validate validates all the required options. +func (o *Options) Validate(args []string) error { + if len(args) != 0 { + return errors.New("no arguments are supported") + } + + return nil +} + +// loadConfigFromFile loads the contents of file and decodes it as a +// KubeSchedulerConfiguration object. +func (o *Options) loadConfigFromFile(file string) (*componentconfig.KubeSchedulerConfiguration, error) { + data, err := ioutil.ReadFile(file) + if err != nil { + return nil, err + } + + return o.loadConfig(data) +} + +// loadConfig decodes data as a KubeSchedulerConfiguration object. +func (o *Options) loadConfig(data []byte) (*componentconfig.KubeSchedulerConfiguration, error) { + configObj, gvk, err := o.codecs.UniversalDecoder().Decode(data, nil, nil) + if err != nil { + return nil, err + } + config, ok := configObj.(*componentconfig.KubeSchedulerConfiguration) + if !ok { + return nil, fmt.Errorf("got unexpected config type: %v", gvk) + } + return config, nil +} + +func (o *Options) ApplyDefaults(in *componentconfig.KubeSchedulerConfiguration) (*componentconfig.KubeSchedulerConfiguration, error) { + external, err := o.scheme.ConvertToVersion(in, componentconfigv1alpha1.SchemeGroupVersion) + if err != nil { + return nil, err + } + + o.scheme.Default(external) + + internal, err := o.scheme.ConvertToVersion(external, componentconfig.SchemeGroupVersion) + if err != nil { + return nil, err + } + + out := internal.(*componentconfig.KubeSchedulerConfiguration) + + return out, nil +} + +func (o *Options) Run() error { + config := o.config + + if len(o.ConfigFile) > 0 { + if c, err := o.loadConfigFromFile(o.ConfigFile); err != nil { + return err + } else { + config = c + } + } + + // Apply algorithms based on feature gates. + // TODO: make configurable? + algorithmprovider.ApplyFeatureGates() + + server, err := NewSchedulerServer(config, o.master) + if err != nil { + return err + } + + stop := make(chan struct{}) + return server.Run(stop) +} + +// NewSchedulerCommand creates a *cobra.Command object with default parameters +func NewSchedulerCommand() *cobra.Command { + opts, err := NewOptions() + if err != nil { + glog.Fatalf("unable to initialize command options: %v", err) + } + + cmd := &cobra.Command{ + Use: "kube-scheduler", + Long: `The Kubernetes scheduler is a policy-rich, topology-aware, +workload-specific function that significantly impacts availability, performance, +and capacity. The scheduler needs to take into account individual and collective +resource requirements, quality of service requirements, hardware/software/policy +constraints, affinity and anti-affinity specifications, data locality, inter-workload +interference, deadlines, and so on. Workload-specific requirements will be exposed +through the API as necessary.`, + Run: func(cmd *cobra.Command, args []string) { + verflag.PrintAndExitIfRequested() + cmdutil.CheckErr(opts.Complete()) + cmdutil.CheckErr(opts.Validate(args)) + cmdutil.CheckErr(opts.Run()) + }, + } + + opts.config, err = opts.ApplyDefaults(opts.config) + if err != nil { + glog.Fatalf("unable to apply config defaults: %v", err) + } + + opts.AddFlags(pflag.CommandLine) + + cmd.MarkFlagFilename("config", "yaml", "yml", "json") + + return cmd +} + +// SchedulerServer represents all the parameters required to start the +// Kubernetes scheduler server. +type SchedulerServer struct { + SchedulerName string + Client clientset.Interface + InformerFactory informers.SharedInformerFactory + PodInformer coreinformers.PodInformer + AlgorithmSource componentconfig.SchedulerAlgorithmSource + HardPodAffinitySymmetricWeight int32 + EventClient v1core.EventsGetter + Recorder record.EventRecorder + Broadcaster record.EventBroadcaster + // LeaderElection is optional. + LeaderElection *leaderelection.LeaderElectionConfig + // HealthzServer is optional. + HealthzServer *http.Server + // MetricsServer is optional. + MetricsServer *http.Server +} + +// NewSchedulerServer creates a runnable SchedulerServer from configuration. +func NewSchedulerServer(config *componentconfig.KubeSchedulerConfiguration, master string) (*SchedulerServer, error) { + if config == nil { + return nil, errors.New("config is required") + } + + // Configz registration. + if c, err := configz.New("componentconfig"); err == nil { + c.Set(config) + } else { + return nil, fmt.Errorf("unable to register configz: %s", err) + } + + // Prepare some Kube clients. + client, leaderElectionClient, eventClient, err := createClients(config.ClientConnection, master) + if err != nil { + return nil, err + } + + // Prepare event clients. + eventBroadcaster := record.NewBroadcaster() + recorder := eventBroadcaster.NewRecorder(legacyscheme.Scheme, v1.EventSource{Component: config.SchedulerName}) + + // Set up leader election if enabled. + var leaderElectionConfig *leaderelection.LeaderElectionConfig + if config.LeaderElection.LeaderElect { + leaderElectionConfig, err = makeLeaderElectionConfig(config.LeaderElection, leaderElectionClient, recorder) + if err != nil { + return nil, err + } + } + + // Prepare a healthz server. If the metrics bind address is the same as the + // healthz bind address, consolidate the servers into one. + var healthzServer *http.Server + if len(config.HealthzBindAddress) != 0 { + healthzServer = makeHealthzServer(config) + } + + // Prepare a separate metrics server only if the bind address differs from the + // healthz bind address. + var metricsServer *http.Server + if len(config.MetricsBindAddress) > 0 && config.HealthzBindAddress != config.MetricsBindAddress { + metricsServer = makeMetricsServer(config) + } + + return &SchedulerServer{ + SchedulerName: config.SchedulerName, + Client: client, + InformerFactory: informers.NewSharedInformerFactory(client, 0), + PodInformer: factory.NewPodInformer(client, 0, config.SchedulerName), + AlgorithmSource: config.AlgorithmSource, + HardPodAffinitySymmetricWeight: config.HardPodAffinitySymmetricWeight, + EventClient: eventClient, + Recorder: recorder, + Broadcaster: eventBroadcaster, + LeaderElection: leaderElectionConfig, + HealthzServer: healthzServer, + MetricsServer: metricsServer, + }, nil +} + +// makeLeaderElectionConfig builds a leader election configuration. It will +// create a new resource lock associated with the configuration. +func makeLeaderElectionConfig(config componentconfig.KubeSchedulerLeaderElectionConfiguration, client clientset.Interface, recorder record.EventRecorder) (*leaderelection.LeaderElectionConfig, error) { + hostname, err := os.Hostname() + if err != nil { + return nil, fmt.Errorf("unable to get hostname: %v", err) + } + + rl, err := resourcelock.New(config.ResourceLock, + config.LockObjectNamespace, + config.LockObjectName, + client.CoreV1(), + resourcelock.ResourceLockConfig{ + Identity: hostname, + EventRecorder: recorder, + }) + if err != nil { + return nil, fmt.Errorf("couldn't create resource lock: %v", err) + } + + return &leaderelection.LeaderElectionConfig{ + Lock: rl, + LeaseDuration: config.LeaseDuration.Duration, + RenewDeadline: config.RenewDeadline.Duration, + RetryPeriod: config.RetryPeriod.Duration, + }, nil +} + +// makeHealthzServer creates a healthz server from the config, and will also +// embed the metrics handler if the healthz and metrics address configurations +// are the same. +func makeHealthzServer(config *componentconfig.KubeSchedulerConfiguration) *http.Server { + mux := http.NewServeMux() + healthz.InstallHandler(mux) + if config.HealthzBindAddress == config.MetricsBindAddress { + configz.InstallHandler(mux) + mux.Handle("/metrics", prometheus.Handler()) + } + if config.EnableProfiling { + mux.HandleFunc("/debug/pprof/", pprof.Index) + mux.HandleFunc("/debug/pprof/profile", pprof.Profile) + mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + mux.HandleFunc("/debug/pprof/trace", pprof.Trace) + if config.EnableContentionProfiling { + goruntime.SetBlockProfileRate(1) + } + } + return &http.Server{ + Addr: config.HealthzBindAddress, + Handler: mux, + } +} + +// makeMetricsServer builds a metrics server from the config. +func makeMetricsServer(config *componentconfig.KubeSchedulerConfiguration) *http.Server { + mux := http.NewServeMux() + configz.InstallHandler(mux) + mux.Handle("/metrics", prometheus.Handler()) + if config.EnableProfiling { + mux.HandleFunc("/debug/pprof/", pprof.Index) + mux.HandleFunc("/debug/pprof/profile", pprof.Profile) + mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + mux.HandleFunc("/debug/pprof/trace", pprof.Trace) + if config.EnableContentionProfiling { + goruntime.SetBlockProfileRate(1) + } + } + return &http.Server{ + Addr: config.MetricsBindAddress, + Handler: mux, + } +} + +// createClients creates a kube client and an event client from the given config and masterOverride. +// TODO remove masterOverride when CLI flags are removed. +func createClients(config componentconfig.ClientConnectionConfiguration, masterOverride string) (clientset.Interface, clientset.Interface, v1core.EventsGetter, error) { + if len(config.KubeConfigFile) == 0 && len(masterOverride) == 0 { + glog.Warningf("Neither --kubeconfig nor --master was specified. Using default API client. This might not work.") + } + + // This creates a client, first loading any specified kubeconfig + // file, and then overriding the Master flag, if non-empty. + kubeConfig, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + &clientcmd.ClientConfigLoadingRules{ExplicitPath: config.KubeConfigFile}, + &clientcmd.ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterOverride}}).ClientConfig() + if err != nil { + return nil, nil, nil, err + } + + kubeConfig.AcceptContentTypes = config.AcceptContentTypes + kubeConfig.ContentType = config.ContentType + kubeConfig.QPS = config.QPS + //TODO make config struct use int instead of int32? + kubeConfig.Burst = int(config.Burst) + + client, err := clientset.NewForConfig(restclient.AddUserAgent(kubeConfig, "scheduler")) + if err != nil { + return nil, nil, nil, err + } + + leaderElectionClient, err := clientset.NewForConfig(restclient.AddUserAgent(kubeConfig, "leader-election")) + if err != nil { + return nil, nil, nil, err + } + + eventClient, err := clientset.NewForConfig(kubeConfig) + if err != nil { + return nil, nil, nil, err + } + + return client, leaderElectionClient, eventClient.CoreV1(), nil +} + +// Run runs the SchedulerServer. This should never exit. +func (s *SchedulerServer) Run(stop chan struct{}) error { + // To help debugging, immediately log version + glog.Infof("Version: %+v", version.Get()) + + // Build a scheduler config from the provided algorithm source. + schedulerConfig, err := s.SchedulerConfig() + if err != nil { + return err + } + + // Create the scheduler. + sched := scheduler.NewFromConfig(schedulerConfig) + + // Prepare the event broadcaster. + if !reflect.ValueOf(s.Broadcaster).IsNil() && !reflect.ValueOf(s.EventClient).IsNil() { + s.Broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: s.EventClient.Events("")}) + } + + // Start up the healthz server. + if s.HealthzServer != nil { + go wait.Until(func() { + glog.Infof("starting healthz server on %v", s.HealthzServer.Addr) + err := s.HealthzServer.ListenAndServe() + if err != nil { + utilruntime.HandleError(fmt.Errorf("failed to start healthz server: %v", err)) + } + }, 5*time.Second, stop) + } + + // Start up the metrics server. + if s.MetricsServer != nil { + go wait.Until(func() { + glog.Infof("starting metrics server on %v", s.MetricsServer.Addr) + err := s.MetricsServer.ListenAndServe() + if err != nil { + utilruntime.HandleError(fmt.Errorf("failed to start metrics server: %v", err)) + } + }, 5*time.Second, stop) + } + + // Start all informers. + go s.PodInformer.Informer().Run(stop) + s.InformerFactory.Start(stop) + + // Wait for all caches to sync before scheduling. + s.InformerFactory.WaitForCacheSync(stop) + controller.WaitForCacheSync("scheduler", stop, s.PodInformer.Informer().HasSynced) + + // Prepare a reusable run function. + run := func(stopCh <-chan struct{}) { + sched.Run() + <-stopCh + } + + // If leader election is enabled, run via LeaderElector until done and exit. + if s.LeaderElection != nil { + s.LeaderElection.Callbacks = leaderelection.LeaderCallbacks{ + OnStartedLeading: run, + OnStoppedLeading: func() { + utilruntime.HandleError(fmt.Errorf("lost master")) + }, + } + leaderElector, err := leaderelection.NewLeaderElector(*s.LeaderElection) + if err != nil { + return fmt.Errorf("couldn't create leader elector: %v", err) + } + + leaderElector.Run() + + return fmt.Errorf("lost lease") + } + + // Leader election is disabled, so run inline until done. + run(stop) + return fmt.Errorf("finished without leader elect") +} + +// SchedulerConfig creates the scheduler configuration. This is exposed for use +// by tests. +func (s *SchedulerServer) SchedulerConfig() (*scheduler.Config, error) { + var storageClassInformer storageinformers.StorageClassInformer + if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) { + storageClassInformer = s.InformerFactory.Storage().V1().StorageClasses() + } + + // Set up the configurator which can create schedulers from configs. + configurator := factory.NewConfigFactory( + s.SchedulerName, + s.Client, + s.InformerFactory.Core().V1().Nodes(), + s.PodInformer, + s.InformerFactory.Core().V1().PersistentVolumes(), + s.InformerFactory.Core().V1().PersistentVolumeClaims(), + s.InformerFactory.Core().V1().ReplicationControllers(), + s.InformerFactory.Extensions().V1beta1().ReplicaSets(), + s.InformerFactory.Apps().V1beta1().StatefulSets(), + s.InformerFactory.Core().V1().Services(), + s.InformerFactory.Policy().V1beta1().PodDisruptionBudgets(), + storageClassInformer, + s.HardPodAffinitySymmetricWeight, + utilfeature.DefaultFeatureGate.Enabled(features.EnableEquivalenceClassCache), + ) + + source := s.AlgorithmSource + var config *scheduler.Config + switch { + case source.Provider != nil: + // Create the config from a named algorithm provider. + sc, err := configurator.CreateFromProvider(*source.Provider) + if err != nil { + return nil, fmt.Errorf("couldn't create scheduler using provider %q: %v", *source.Provider, err) + } + config = sc + case source.Policy != nil: + // Create the config from a user specified policy source. + policy := &schedulerapi.Policy{} + switch { + case source.Policy.File != nil: + // Use a policy serialized in a file. + policyFile := source.Policy.File.Path + _, err := os.Stat(policyFile) + if err != nil { + return nil, fmt.Errorf("missing policy config file %s", policyFile) + } + data, err := ioutil.ReadFile(policyFile) + if err != nil { + return nil, fmt.Errorf("couldn't read policy config: %v", err) + } + err = runtime.DecodeInto(latestschedulerapi.Codec, []byte(data), policy) + if err != nil { + return nil, fmt.Errorf("invalid policy: %v", err) + } + case source.Policy.ConfigMap != nil: + // Use a policy serialized in a config map value. + policyRef := source.Policy.ConfigMap + policyConfigMap, err := s.Client.CoreV1().ConfigMaps(policyRef.Namespace).Get(policyRef.Name, metav1.GetOptions{}) + if err != nil { + return nil, fmt.Errorf("couldn't get policy config map %s/%s: %v", policyRef.Namespace, policyRef.Name, err) + } + data, found := policyConfigMap.Data[componentconfig.SchedulerPolicyConfigMapKey] + if !found { + return nil, fmt.Errorf("missing policy config map value at key %q", componentconfig.SchedulerPolicyConfigMapKey) + } + err = runtime.DecodeInto(latestschedulerapi.Codec, []byte(data), policy) + if err != nil { + return nil, fmt.Errorf("invalid policy: %v", err) + } + } + sc, err := configurator.CreateFromConfig(*policy) + if err != nil { + return nil, fmt.Errorf("couldn't create scheduler from policy: %v", err) + } + config = sc + default: + return nil, fmt.Errorf("unsupported algorithm source: %v", source) + } + // Additional tweaks to the config produced by the configurator. + config.Recorder = s.Recorder + return config, nil +} diff --git a/cmd/kube-scheduler/scheduler.go b/cmd/kube-scheduler/scheduler.go new file mode 100644 index 00000000000..1f6fcf7d608 --- /dev/null +++ b/cmd/kube-scheduler/scheduler.go @@ -0,0 +1,39 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES 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" + + utilflag "k8s.io/apiserver/pkg/util/flag" + "k8s.io/apiserver/pkg/util/logs" + "k8s.io/kubernetes/cmd/kube-scheduler/app" + _ "k8s.io/kubernetes/pkg/client/metrics/prometheus" // for client metric registration + _ "k8s.io/kubernetes/pkg/version/prometheus" // for version metric registration +) + +func main() { + command := app.NewSchedulerCommand() + + utilflag.InitFlags() + logs.InitLogs() + defer logs.FlushLogs() + + if err := command.Execute(); err != nil { + os.Exit(1) + } +} diff --git a/cmd/kubeadm/BUILD b/cmd/kubeadm/BUILD index c04f62145f5..fad320975ef 100644 --- a/cmd/kubeadm/BUILD +++ b/cmd/kubeadm/BUILD @@ -9,14 +9,9 @@ load("//pkg/version:def.bzl", "version_x_defs") go_binary( name = "kubeadm", - gc_linkopts = [ - "-linkmode", - "external", - "-extldflags", - "-static", - ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm", - library = ":go_default_library", + pure = "on", x_defs = version_x_defs(), ) diff --git a/cmd/kubeadm/OWNERS b/cmd/kubeadm/OWNERS index 77bb47edbcf..8df7e7cd3ee 100644 --- a/cmd/kubeadm/OWNERS +++ b/cmd/kubeadm/OWNERS @@ -1,15 +1,18 @@ approvers: -- errordeveloper - jbeda - luxas - mikedanese - krousey +- timothysc reviewers: - mikedanese - luxas -- justinsb -- errordeveloper -- lukemarsden - dmmcquay - krousey - timothysc +- fabriziopandini +- jamiehannaford +- kad +- xiangpengzhao +- mattmoyer +- kargakis diff --git a/cmd/kubeadm/app/BUILD b/cmd/kubeadm/app/BUILD index e7eb023527c..24502fd9478 100644 --- a/cmd/kubeadm/app/BUILD +++ b/cmd/kubeadm/app/BUILD @@ -41,9 +41,9 @@ filegroup( "//cmd/kubeadm/app/phases/controlplane:all-srcs", "//cmd/kubeadm/app/phases/etcd:all-srcs", "//cmd/kubeadm/app/phases/kubeconfig:all-srcs", + "//cmd/kubeadm/app/phases/kubelet:all-srcs", "//cmd/kubeadm/app/phases/markmaster:all-srcs", "//cmd/kubeadm/app/phases/selfhosting:all-srcs", - "//cmd/kubeadm/app/phases/token:all-srcs", "//cmd/kubeadm/app/phases/upgrade:all-srcs", "//cmd/kubeadm/app/phases/uploadconfig:all-srcs", "//cmd/kubeadm/app/preflight:all-srcs", diff --git a/cmd/kubeadm/app/apis/kubeadm/BUILD b/cmd/kubeadm/app/apis/kubeadm/BUILD index e015f2ef53c..9d6eb70ee96 100644 --- a/cmd/kubeadm/app/apis/kubeadm/BUILD +++ b/cmd/kubeadm/app/apis/kubeadm/BUILD @@ -11,13 +11,13 @@ go_library( "doc.go", "register.go", "types.go", - "well_known_labels.go", "zz_generated.deepcopy.go", ], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm", deps = [ + "//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library", + "//pkg/proxy/apis/kubeproxyconfig/v1alpha1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/cmd/kubeadm/app/apis/kubeadm/doc.go b/cmd/kubeadm/app/apis/kubeadm/doc.go index 39d93b5d1a3..21b45eda4ad 100644 --- a/cmd/kubeadm/app/apis/kubeadm/doc.go +++ b/cmd/kubeadm/app/apis/kubeadm/doc.go @@ -17,7 +17,7 @@ limitations under the License. // Package kubeadm is the package that contains the libraries that drive the kubeadm binary. // kubeadm is responsible for handling a Kubernetes cluster's lifecycle. -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +groupName=kubeadm.k8s.io package kubeadm // import "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" diff --git a/cmd/kubeadm/app/apis/kubeadm/fuzzer/BUILD b/cmd/kubeadm/app/apis/kubeadm/fuzzer/BUILD index b87eee90077..9adebea330c 100644 --- a/cmd/kubeadm/app/apis/kubeadm/fuzzer/BUILD +++ b/cmd/kubeadm/app/apis/kubeadm/fuzzer/BUILD @@ -11,7 +11,11 @@ go_library( importpath = "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/fuzzer", deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", + "//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library", + "//pkg/proxy/apis/kubeproxyconfig/v1alpha1:go_default_library", + "//pkg/util/pointer:go_default_library", "//vendor/github.com/google/gofuzz:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", ], ) diff --git a/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go b/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go index bee5c7d2374..c107fae10ed 100644 --- a/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go +++ b/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go @@ -17,10 +17,16 @@ limitations under the License. package fuzzer import ( + "time" + "github.com/google/gofuzz" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" + kubeproxyconfigv1alpha1 "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1" + utilpointer "k8s.io/kubernetes/pkg/util/pointer" ) // Funcs returns the fuzzer functions for the kubeadm apis. @@ -30,24 +36,86 @@ func Funcs(codecs runtimeserializer.CodecFactory) []interface{} { c.FuzzNoCustom(obj) obj.KubernetesVersion = "v10" obj.API.BindPort = 20 + obj.TokenTTL = &metav1.Duration{Duration: 1 * time.Hour} obj.API.AdvertiseAddress = "foo" obj.Networking.ServiceSubnet = "foo" obj.Networking.DNSDomain = "foo" obj.AuthorizationModes = []string{"foo"} obj.CertificatesDir = "foo" - obj.APIServerCertSANs = []string{} + obj.APIServerCertSANs = []string{"foo"} obj.Token = "foo" obj.Etcd.Image = "foo" obj.Etcd.DataDir = "foo" obj.ImageRepository = "foo" obj.CIImageRepository = "" obj.UnifiedControlPlaneImage = "foo" - obj.FeatureGates = map[string]bool{} + obj.FeatureGates = map[string]bool{"foo": true} + obj.APIServerExtraArgs = map[string]string{"foo": "foo"} + obj.APIServerExtraVolumes = []kubeadm.HostPathMount{{ + Name: "foo", + HostPath: "foo", + MountPath: "foo", + }} + obj.Etcd.ExtraArgs = map[string]string{"foo": "foo"} + obj.Etcd.SelfHosted = &kubeadm.SelfHostedEtcd{ + CertificatesDir: "/etc/kubernetes/pki/etcd", + ClusterServiceName: "etcd-cluster", + EtcdVersion: "v0.1.0", + OperatorVersion: "v0.1.0", + } + obj.KubeletConfiguration = kubeadm.KubeletConfiguration{ + BaseConfig: &kubeletconfigv1alpha1.KubeletConfiguration{ + PodManifestPath: "foo", + AllowPrivileged: utilpointer.BoolPtr(true), + ClusterDNS: []string{"foo"}, + ClusterDomain: "foo", + Authorization: kubeletconfigv1alpha1.KubeletAuthorization{Mode: "foo"}, + Authentication: kubeletconfigv1alpha1.KubeletAuthentication{ + X509: kubeletconfigv1alpha1.KubeletX509Authentication{ClientCAFile: "foo"}, + }, + CAdvisorPort: utilpointer.Int32Ptr(0), + }, + } + kubeletconfigv1alpha1.SetDefaults_KubeletConfiguration(obj.KubeletConfiguration.BaseConfig) + obj.KubeProxy = kubeadm.KubeProxy{ + Config: &kubeproxyconfigv1alpha1.KubeProxyConfiguration{ + FeatureGates: "foo", + BindAddress: "foo", + HealthzBindAddress: "foo:10256", + MetricsBindAddress: "foo:", + EnableProfiling: bool(true), + ClusterCIDR: "foo", + HostnameOverride: "foo", + ClientConnection: kubeproxyconfigv1alpha1.ClientConnectionConfiguration{ + KubeConfigFile: "foo", + AcceptContentTypes: "foo", + ContentType: "foo", + QPS: float32(5), + Burst: 10, + }, + IPVS: kubeproxyconfigv1alpha1.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: 1}, + }, + IPTables: kubeproxyconfigv1alpha1.KubeProxyIPTablesConfiguration{ + MasqueradeBit: utilpointer.Int32Ptr(0), + SyncPeriod: metav1.Duration{Duration: 1}, + }, + OOMScoreAdj: utilpointer.Int32Ptr(0), + ResourceContainer: "foo", + UDPIdleTimeout: metav1.Duration{Duration: 1}, + Conntrack: kubeproxyconfigv1alpha1.KubeProxyConntrackConfiguration{ + MaxPerCore: utilpointer.Int32Ptr(2), + Min: utilpointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5}, + }, + ConfigSyncPeriod: metav1.Duration{Duration: 1}, + }, + } }, func(obj *kubeadm.NodeConfiguration, c fuzz.Continue) { c.FuzzNoCustom(obj) obj.CACertPath = "foo" - obj.CACertPath = "foo" obj.DiscoveryFile = "foo" obj.DiscoveryToken = "foo" obj.DiscoveryTokenAPIServers = []string{"foo"} diff --git a/cmd/kubeadm/app/apis/kubeadm/install/BUILD b/cmd/kubeadm/app/apis/kubeadm/install/BUILD index 4a78dc2acef..89505db81c8 100644 --- a/cmd/kubeadm/app/apis/kubeadm/install/BUILD +++ b/cmd/kubeadm/app/apis/kubeadm/install/BUILD @@ -16,7 +16,7 @@ go_library( deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library", - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", @@ -39,8 +39,8 @@ filegroup( go_test( name = "go_default_test", srcs = ["install_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/install", - library = ":go_default_library", deps = [ "//cmd/kubeadm/app/apis/kubeadm/fuzzer:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/testing/roundtrip:go_default_library", diff --git a/cmd/kubeadm/app/apis/kubeadm/install/install.go b/cmd/kubeadm/app/apis/kubeadm/install/install.go index 81f0174a703..0e1ebfca4d0 100644 --- a/cmd/kubeadm/app/apis/kubeadm/install/install.go +++ b/cmd/kubeadm/app/apis/kubeadm/install/install.go @@ -22,11 +22,11 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" ) func init() { - Install(api.GroupFactoryRegistry, api.Registry, api.Scheme) + Install(legacyscheme.GroupFactoryRegistry, legacyscheme.Registry, legacyscheme.Scheme) } // Install registers the API group and adds types to a scheme diff --git a/cmd/kubeadm/app/apis/kubeadm/types.go b/cmd/kubeadm/app/apis/kubeadm/types.go index 1d2deaf73bf..659a8a5442b 100644 --- a/cmd/kubeadm/app/apis/kubeadm/types.go +++ b/cmd/kubeadm/app/apis/kubeadm/types.go @@ -18,6 +18,8 @@ package kubeadm import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" + kubeproxyconfigv1alpha1 "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -27,21 +29,27 @@ import ( type MasterConfiguration struct { metav1.TypeMeta - API API - Etcd Etcd - Networking Networking - KubernetesVersion string - CloudProvider string - NodeName string - AuthorizationModes []string + API API + KubeProxy KubeProxy + Etcd Etcd + KubeletConfiguration KubeletConfiguration + Networking Networking + KubernetesVersion string + CloudProvider string + NodeName string + AuthorizationModes []string Token string - TokenTTL metav1.Duration + TokenTTL *metav1.Duration APIServerExtraArgs map[string]string ControllerManagerExtraArgs map[string]string SchedulerExtraArgs map[string]string + APIServerExtraVolumes []HostPathMount + ControllerManagerExtraVolumes []HostPathMount + SchedulerExtraVolumes []HostPathMount + // APIServerCertSANs sets extra Subject Alternative Names for the API Server signing cert APIServerCertSANs []string // CertificatesDir specifies where to store or look for all required certificates @@ -92,7 +100,21 @@ type Etcd struct { DataDir string ExtraArgs map[string]string // Image specifies which container image to use for running etcd. If empty, automatically populated by kubeadm using the image repository and default etcd version - Image string + Image string + SelfHosted *SelfHostedEtcd +} + +// SelfHostedEtcd describes options required to configure self-hosted etcd +type SelfHostedEtcd struct { + // CertificatesDir represents the directory where all etcd TLS assets are stored. By default this is + // a dir names "etcd" in the main CertificatesDir value. + CertificatesDir string + // ClusterServiceName is the name of the service that load balances the etcd cluster + ClusterServiceName string + // EtcdVersion is the version of etcd running in the cluster. + EtcdVersion string + // OperatorVersion is the version of the etcd-operator to use. + OperatorVersion string } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -124,6 +146,14 @@ type NodeConfiguration struct { // without CA verification via DiscoveryTokenCACertHashes. This can weaken // the security of kubeadm since other nodes can impersonate the master. DiscoveryTokenUnsafeSkipCAVerification bool + + // FeatureGates enabled by the user + FeatureGates map[string]bool +} + +// KubeletConfiguration contains elements describing initial remote configuration of kubelet +type KubeletConfiguration struct { + BaseConfig *kubeletconfigv1alpha1.KubeletConfiguration } // GetControlPlaneImageRepository returns name of image repository @@ -137,3 +167,16 @@ func (cfg *MasterConfiguration) GetControlPlaneImageRepository() string { } return cfg.ImageRepository } + +// HostPathMount contains elements describing volumes that are mounted from the +// host +type HostPathMount struct { + Name string + HostPath string + MountPath string +} + +// KubeProxy contains elements describing the proxy configuration +type KubeProxy struct { + Config *kubeproxyconfigv1alpha1.KubeProxyConfiguration +} diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/BUILD b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/BUILD index 427c31061cd..edeb99e0b06 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/BUILD +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/BUILD @@ -1,9 +1,4 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) +load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", @@ -15,11 +10,53 @@ go_library( "zz_generated.conversion.go", "zz_generated.deepcopy.go", "zz_generated.defaults.go", - ], + ] + select({ + "@io_bazel_rules_go//go/platform:android": [ + "defaults_unix.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "defaults_unix.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "defaults_unix.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "defaults_unix.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "defaults_unix.go", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "defaults_unix.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "defaults_unix.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "defaults_unix.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "defaults_unix.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "defaults_unix.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "defaults_windows.go", + ], + "//conditions:default": [], + }), importpath = "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1", + visibility = ["//visibility:public"], deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", + "//cmd/kubeadm/app/features:go_default_library", + "//pkg/kubelet/apis/kubeletconfig/scheme:go_default_library", + "//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library", + "//pkg/proxy/apis/kubeproxyconfig/scheme:go_default_library", + "//pkg/proxy/apis/kubeproxyconfig/v1alpha1:go_default_library", + "//pkg/util/pointer:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", @@ -38,4 +75,5 @@ filegroup( name = "all-srcs", srcs = [":package-srcs"], tags = ["automanaged"], + visibility = ["//visibility:public"], ) diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go index 5ed7eccc05a..ea28af88e5f 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go @@ -23,6 +23,12 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/kubernetes/cmd/kubeadm/app/constants" + "k8s.io/kubernetes/cmd/kubeadm/app/features" + kubeletscheme "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme" + kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" + kubeproxyscheme "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/scheme" + kubeproxyconfigv1alpha1 "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1" + utilpointer "k8s.io/kubernetes/pkg/util/pointer" ) const ( @@ -30,20 +36,37 @@ const ( DefaultServiceDNSDomain = "cluster.local" // DefaultServicesSubnet defines default service subnet range DefaultServicesSubnet = "10.96.0.0/12" + // DefaultClusterDNSIP defines default DNS IP + DefaultClusterDNSIP = "10.96.0.10" // DefaultKubernetesVersion defines default kubernetes version - DefaultKubernetesVersion = "stable-1.8" + DefaultKubernetesVersion = "stable-1.9" // DefaultAPIBindPort defines default API port DefaultAPIBindPort = 6443 // DefaultAuthorizationModes defines default authorization modes DefaultAuthorizationModes = "Node,RBAC" - // DefaultCACertPath defines default location of CA certificate - DefaultCACertPath = "/etc/kubernetes/pki/ca.crt" // DefaultCertificatesDir defines default certificate directory DefaultCertificatesDir = "/etc/kubernetes/pki" - // DefaultEtcdDataDir defines default location of etcd - DefaultEtcdDataDir = "/var/lib/etcd" // DefaultImageRepository defines default image registry DefaultImageRepository = "gcr.io/google_containers" + // DefaultManifestsDir defines default manifests directory + DefaultManifestsDir = "/etc/kubernetes/manifests" + + // DefaultEtcdDataDir defines default location of etcd where static pods will save data to + DefaultEtcdDataDir = "/var/lib/etcd" + // DefaultEtcdClusterSize defines the default cluster size when using the etcd-operator + DefaultEtcdClusterSize = 3 + // DefaultEtcdOperatorVersion defines the default version of the etcd-operator to use + DefaultEtcdOperatorVersion = "v0.6.0" + // DefaultEtcdCertDir represents the directory where PKI assets are stored for self-hosted etcd + DefaultEtcdCertDir = "/etc/kubernetes/pki/etcd" + // DefaultEtcdClusterServiceName is the default name of the service backing the etcd cluster + DefaultEtcdClusterServiceName = "etcd-cluster" + // DefaultProxyBindAddressv4 is the default bind address when the advertise address is v4 + DefaultProxyBindAddressv4 = "0.0.0.0" + // DefaultProxyBindAddressv6 is the default bind address when the advertise address is v6 + DefaultProxyBindAddressv6 = "::" + // KubeproxyKubeConfigFileName defines the file name for the kube-proxy's KubeConfig file + KubeproxyKubeConfigFileName = "/var/lib/kube-proxy/kubeconfig.conf" ) func addDefaultingFuncs(scheme *runtime.Scheme) error { @@ -76,8 +99,8 @@ func SetDefaults_MasterConfiguration(obj *MasterConfiguration) { obj.CertificatesDir = DefaultCertificatesDir } - if obj.TokenTTL.Duration == 0 { - obj.TokenTTL = metav1.Duration{ + if obj.TokenTTL == nil { + obj.TokenTTL = &metav1.Duration{ Duration: constants.DefaultTokenDuration, } } @@ -89,6 +112,28 @@ func SetDefaults_MasterConfiguration(obj *MasterConfiguration) { if obj.Etcd.DataDir == "" { obj.Etcd.DataDir = DefaultEtcdDataDir } + + SetDefaultsEtcdSelfHosted(obj) + if features.Enabled(obj.FeatureGates, features.DynamicKubeletConfig) { + SetDefaults_KubeletConfiguration(obj) + } + SetDefaults_ProxyConfiguration(obj) +} + +// SetDefaults_ProxyConfiguration assigns default values for the Proxy +func SetDefaults_ProxyConfiguration(obj *MasterConfiguration) { + if obj.KubeProxy.Config == nil { + obj.KubeProxy.Config = &kubeproxyconfigv1alpha1.KubeProxyConfiguration{} + } + if obj.KubeProxy.Config.ClusterCIDR == "" && obj.Networking.PodSubnet != "" { + obj.KubeProxy.Config.ClusterCIDR = obj.Networking.PodSubnet + } + + if obj.KubeProxy.Config.ClientConnection.KubeConfigFile == "" { + obj.KubeProxy.Config.ClientConnection.KubeConfigFile = KubeproxyKubeConfigFileName + } + + kubeproxyscheme.Scheme.Default(obj.KubeProxy.Config) } // SetDefaults_NodeConfiguration assigns default values to a regular node @@ -110,3 +155,62 @@ func SetDefaults_NodeConfiguration(obj *NodeConfiguration) { } } } + +// SetDefaultsEtcdSelfHosted sets defaults for self-hosted etcd if used +func SetDefaultsEtcdSelfHosted(obj *MasterConfiguration) { + if obj.Etcd.SelfHosted != nil { + if obj.Etcd.SelfHosted.ClusterServiceName == "" { + obj.Etcd.SelfHosted.ClusterServiceName = DefaultEtcdClusterServiceName + } + + if obj.Etcd.SelfHosted.EtcdVersion == "" { + obj.Etcd.SelfHosted.EtcdVersion = constants.DefaultEtcdVersion + } + + if obj.Etcd.SelfHosted.OperatorVersion == "" { + obj.Etcd.SelfHosted.OperatorVersion = DefaultEtcdOperatorVersion + } + + if obj.Etcd.SelfHosted.CertificatesDir == "" { + obj.Etcd.SelfHosted.CertificatesDir = DefaultEtcdCertDir + } + } +} + +// SetDefaults_KubeletConfiguration assigns default values to kubelet +func SetDefaults_KubeletConfiguration(obj *MasterConfiguration) { + if obj.KubeletConfiguration.BaseConfig == nil { + obj.KubeletConfiguration.BaseConfig = &kubeletconfigv1alpha1.KubeletConfiguration{} + } + if obj.KubeletConfiguration.BaseConfig.PodManifestPath == "" { + obj.KubeletConfiguration.BaseConfig.PodManifestPath = DefaultManifestsDir + } + if obj.KubeletConfiguration.BaseConfig.AllowPrivileged == nil { + obj.KubeletConfiguration.BaseConfig.AllowPrivileged = utilpointer.BoolPtr(true) + } + if obj.KubeletConfiguration.BaseConfig.ClusterDNS == nil { + dnsIP, err := constants.GetDNSIP(obj.Networking.ServiceSubnet) + if err != nil { + obj.KubeletConfiguration.BaseConfig.ClusterDNS = []string{DefaultClusterDNSIP} + } else { + obj.KubeletConfiguration.BaseConfig.ClusterDNS = []string{dnsIP.String()} + } + } + if obj.KubeletConfiguration.BaseConfig.ClusterDomain == "" { + obj.KubeletConfiguration.BaseConfig.ClusterDomain = DefaultServiceDNSDomain + } + if obj.KubeletConfiguration.BaseConfig.Authorization.Mode == "" { + obj.KubeletConfiguration.BaseConfig.Authorization.Mode = kubeletconfigv1alpha1.KubeletAuthorizationModeWebhook + } + if obj.KubeletConfiguration.BaseConfig.Authentication.X509.ClientCAFile == "" { + obj.KubeletConfiguration.BaseConfig.Authentication.X509.ClientCAFile = DefaultCACertPath + } + if obj.KubeletConfiguration.BaseConfig.CAdvisorPort == nil { + obj.KubeletConfiguration.BaseConfig.CAdvisorPort = utilpointer.Int32Ptr(0) + } + + scheme, _, _ := kubeletscheme.NewSchemeAndCodecs() + if scheme != nil { + scheme.Default(obj.KubeletConfiguration.BaseConfig) + } +} diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults_unix.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults_unix.go new file mode 100644 index 00000000000..5de42850626 --- /dev/null +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults_unix.go @@ -0,0 +1,22 @@ +// +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 v1alpha1 + +// DefaultCACertPath defines default location of CA certificate on Linux +const DefaultCACertPath = "/etc/kubernetes/pki/ca.crt" diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults_windows.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults_windows.go new file mode 100644 index 00000000000..d073613930d --- /dev/null +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults_windows.go @@ -0,0 +1,22 @@ +// +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 v1alpha1 + +// DefaultCACertPath defines default location of CA certificate on Windows +const DefaultCACertPath = "C:/etc/kubernetes/pki/ca.crt" diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go index 9631171d24a..ee7b84f1b1e 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go @@ -18,6 +18,8 @@ package v1alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" + kubeproxyconfigv1alpha1 "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -27,21 +29,27 @@ import ( type MasterConfiguration struct { metav1.TypeMeta `json:",inline"` - API API `json:"api"` - Etcd Etcd `json:"etcd"` - Networking Networking `json:"networking"` - KubernetesVersion string `json:"kubernetesVersion"` - CloudProvider string `json:"cloudProvider"` - NodeName string `json:"nodeName"` - AuthorizationModes []string `json:"authorizationModes,omitempty"` + API API `json:"api"` + KubeProxy KubeProxy `json:"kubeProxy"` + Etcd Etcd `json:"etcd"` + KubeletConfiguration KubeletConfiguration `json:"kubeletConfiguration"` + Networking Networking `json:"networking"` + KubernetesVersion string `json:"kubernetesVersion"` + CloudProvider string `json:"cloudProvider"` + NodeName string `json:"nodeName"` + AuthorizationModes []string `json:"authorizationModes,omitempty"` - Token string `json:"token"` - TokenTTL metav1.Duration `json:"tokenTTL"` + Token string `json:"token"` + TokenTTL *metav1.Duration `json:"tokenTTL,omitempty"` APIServerExtraArgs map[string]string `json:"apiServerExtraArgs,omitempty"` ControllerManagerExtraArgs map[string]string `json:"controllerManagerExtraArgs,omitempty"` SchedulerExtraArgs map[string]string `json:"schedulerExtraArgs,omitempty"` + APIServerExtraVolumes []HostPathMount `json:"apiServerExtraVolumes,omitempty"` + ControllerManagerExtraVolumes []HostPathMount `json:"controllerManagerExtraVolumes,omitempty"` + SchedulerExtraVolumes []HostPathMount `json:"schedulerExtraVolumes,omitempty"` + // APIServerCertSANs sets extra Subject Alternative Names for the API Server signing cert APIServerCertSANs []string `json:"apiServerCertSANs,omitempty"` // CertificatesDir specifies where to store or look for all required certificates @@ -87,7 +95,21 @@ type Etcd struct { DataDir string `json:"dataDir"` ExtraArgs map[string]string `json:"extraArgs,omitempty"` // Image specifies which container image to use for running etcd. If empty, automatically populated by kubeadm using the image repository and default etcd version - Image string `json:"image"` + Image string `json:"image"` + SelfHosted *SelfHostedEtcd `json:"selfHosted,omitempty"` +} + +// SelfHostedEtcd describes options required to configure self-hosted etcd +type SelfHostedEtcd struct { + // CertificatesDir represents the directory where all etcd TLS assets are stored. By default this is + // a dir names "etcd" in the main CertificatesDir value. + CertificatesDir string `json:"certificatesDir"` + // ClusterServiceName is the name of the service that load balances the etcd cluster + ClusterServiceName string `json:"clusterServiceName"` + // EtcdVersion is the version of etcd running in the cluster. + EtcdVersion string `json:"etcdVersion"` + // OperatorVersion is the version of the etcd-operator to use. + OperatorVersion string `json:"operatorVersion"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -118,4 +140,25 @@ type NodeConfiguration struct { // without CA verification via DiscoveryTokenCACertHashes. This can weaken // the security of kubeadm since other nodes can impersonate the master. DiscoveryTokenUnsafeSkipCAVerification bool `json:"discoveryTokenUnsafeSkipCAVerification"` + + // FeatureGates enabled by the user + FeatureGates map[string]bool `json:"featureGates,omitempty"` +} + +// KubeletConfiguration contains elements describing initial remote configuration of kubelet +type KubeletConfiguration struct { + BaseConfig *kubeletconfigv1alpha1.KubeletConfiguration `json:"baseConfig,omitempty"` +} + +// HostPathMount contains elements describing volumes that are mounted from the +// host +type HostPathMount struct { + Name string `json:"name"` + HostPath string `json:"hostPath"` + MountPath string `json:"mountPath"` +} + +// KubeProxy contains elements describing the proxy configuration +type KubeProxy struct { + Config *kubeproxyconfigv1alpha1.KubeProxyConfiguration `json:"config,omitempty"` } diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.conversion.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.conversion.go index d47deab93c1..79e487a0802 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.conversion.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,10 +21,14 @@ limitations under the License. package v1alpha1 import ( + unsafe "unsafe" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" kubeadm "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" - unsafe "unsafe" + kubeletconfig_v1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" + kubeproxyconfig_v1alpha1 "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1" ) func init() { @@ -39,12 +43,20 @@ func RegisterConversions(scheme *runtime.Scheme) error { Convert_kubeadm_API_To_v1alpha1_API, Convert_v1alpha1_Etcd_To_kubeadm_Etcd, Convert_kubeadm_Etcd_To_v1alpha1_Etcd, + Convert_v1alpha1_HostPathMount_To_kubeadm_HostPathMount, + Convert_kubeadm_HostPathMount_To_v1alpha1_HostPathMount, + Convert_v1alpha1_KubeProxy_To_kubeadm_KubeProxy, + Convert_kubeadm_KubeProxy_To_v1alpha1_KubeProxy, + Convert_v1alpha1_KubeletConfiguration_To_kubeadm_KubeletConfiguration, + Convert_kubeadm_KubeletConfiguration_To_v1alpha1_KubeletConfiguration, Convert_v1alpha1_MasterConfiguration_To_kubeadm_MasterConfiguration, Convert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration, Convert_v1alpha1_Networking_To_kubeadm_Networking, Convert_kubeadm_Networking_To_v1alpha1_Networking, Convert_v1alpha1_NodeConfiguration_To_kubeadm_NodeConfiguration, Convert_kubeadm_NodeConfiguration_To_v1alpha1_NodeConfiguration, + Convert_v1alpha1_SelfHostedEtcd_To_kubeadm_SelfHostedEtcd, + Convert_kubeadm_SelfHostedEtcd_To_v1alpha1_SelfHostedEtcd, Convert_v1alpha1_TokenDiscovery_To_kubeadm_TokenDiscovery, Convert_kubeadm_TokenDiscovery_To_v1alpha1_TokenDiscovery, ) @@ -80,6 +92,7 @@ func autoConvert_v1alpha1_Etcd_To_kubeadm_Etcd(in *Etcd, out *kubeadm.Etcd, s co out.DataDir = in.DataDir out.ExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ExtraArgs)) out.Image = in.Image + out.SelfHosted = (*kubeadm.SelfHostedEtcd)(unsafe.Pointer(in.SelfHosted)) return nil } @@ -96,6 +109,7 @@ func autoConvert_kubeadm_Etcd_To_v1alpha1_Etcd(in *kubeadm.Etcd, out *Etcd, s co out.DataDir = in.DataDir out.ExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ExtraArgs)) out.Image = in.Image + out.SelfHosted = (*SelfHostedEtcd)(unsafe.Pointer(in.SelfHosted)) return nil } @@ -104,13 +118,83 @@ func Convert_kubeadm_Etcd_To_v1alpha1_Etcd(in *kubeadm.Etcd, out *Etcd, s conver return autoConvert_kubeadm_Etcd_To_v1alpha1_Etcd(in, out, s) } +func autoConvert_v1alpha1_HostPathMount_To_kubeadm_HostPathMount(in *HostPathMount, out *kubeadm.HostPathMount, s conversion.Scope) error { + out.Name = in.Name + out.HostPath = in.HostPath + out.MountPath = in.MountPath + return nil +} + +// Convert_v1alpha1_HostPathMount_To_kubeadm_HostPathMount is an autogenerated conversion function. +func Convert_v1alpha1_HostPathMount_To_kubeadm_HostPathMount(in *HostPathMount, out *kubeadm.HostPathMount, s conversion.Scope) error { + return autoConvert_v1alpha1_HostPathMount_To_kubeadm_HostPathMount(in, out, s) +} + +func autoConvert_kubeadm_HostPathMount_To_v1alpha1_HostPathMount(in *kubeadm.HostPathMount, out *HostPathMount, s conversion.Scope) error { + out.Name = in.Name + out.HostPath = in.HostPath + out.MountPath = in.MountPath + return nil +} + +// Convert_kubeadm_HostPathMount_To_v1alpha1_HostPathMount is an autogenerated conversion function. +func Convert_kubeadm_HostPathMount_To_v1alpha1_HostPathMount(in *kubeadm.HostPathMount, out *HostPathMount, s conversion.Scope) error { + return autoConvert_kubeadm_HostPathMount_To_v1alpha1_HostPathMount(in, out, s) +} + +func autoConvert_v1alpha1_KubeProxy_To_kubeadm_KubeProxy(in *KubeProxy, out *kubeadm.KubeProxy, s conversion.Scope) error { + out.Config = (*kubeproxyconfig_v1alpha1.KubeProxyConfiguration)(unsafe.Pointer(in.Config)) + return nil +} + +// Convert_v1alpha1_KubeProxy_To_kubeadm_KubeProxy is an autogenerated conversion function. +func Convert_v1alpha1_KubeProxy_To_kubeadm_KubeProxy(in *KubeProxy, out *kubeadm.KubeProxy, s conversion.Scope) error { + return autoConvert_v1alpha1_KubeProxy_To_kubeadm_KubeProxy(in, out, s) +} + +func autoConvert_kubeadm_KubeProxy_To_v1alpha1_KubeProxy(in *kubeadm.KubeProxy, out *KubeProxy, s conversion.Scope) error { + out.Config = (*kubeproxyconfig_v1alpha1.KubeProxyConfiguration)(unsafe.Pointer(in.Config)) + return nil +} + +// Convert_kubeadm_KubeProxy_To_v1alpha1_KubeProxy is an autogenerated conversion function. +func Convert_kubeadm_KubeProxy_To_v1alpha1_KubeProxy(in *kubeadm.KubeProxy, out *KubeProxy, s conversion.Scope) error { + return autoConvert_kubeadm_KubeProxy_To_v1alpha1_KubeProxy(in, out, s) +} + +func autoConvert_v1alpha1_KubeletConfiguration_To_kubeadm_KubeletConfiguration(in *KubeletConfiguration, out *kubeadm.KubeletConfiguration, s conversion.Scope) error { + out.BaseConfig = (*kubeletconfig_v1alpha1.KubeletConfiguration)(unsafe.Pointer(in.BaseConfig)) + return nil +} + +// Convert_v1alpha1_KubeletConfiguration_To_kubeadm_KubeletConfiguration is an autogenerated conversion function. +func Convert_v1alpha1_KubeletConfiguration_To_kubeadm_KubeletConfiguration(in *KubeletConfiguration, out *kubeadm.KubeletConfiguration, s conversion.Scope) error { + return autoConvert_v1alpha1_KubeletConfiguration_To_kubeadm_KubeletConfiguration(in, out, s) +} + +func autoConvert_kubeadm_KubeletConfiguration_To_v1alpha1_KubeletConfiguration(in *kubeadm.KubeletConfiguration, out *KubeletConfiguration, s conversion.Scope) error { + out.BaseConfig = (*kubeletconfig_v1alpha1.KubeletConfiguration)(unsafe.Pointer(in.BaseConfig)) + return nil +} + +// Convert_kubeadm_KubeletConfiguration_To_v1alpha1_KubeletConfiguration is an autogenerated conversion function. +func Convert_kubeadm_KubeletConfiguration_To_v1alpha1_KubeletConfiguration(in *kubeadm.KubeletConfiguration, out *KubeletConfiguration, s conversion.Scope) error { + return autoConvert_kubeadm_KubeletConfiguration_To_v1alpha1_KubeletConfiguration(in, out, s) +} + func autoConvert_v1alpha1_MasterConfiguration_To_kubeadm_MasterConfiguration(in *MasterConfiguration, out *kubeadm.MasterConfiguration, s conversion.Scope) error { if err := Convert_v1alpha1_API_To_kubeadm_API(&in.API, &out.API, s); err != nil { return err } + if err := Convert_v1alpha1_KubeProxy_To_kubeadm_KubeProxy(&in.KubeProxy, &out.KubeProxy, s); err != nil { + return err + } if err := Convert_v1alpha1_Etcd_To_kubeadm_Etcd(&in.Etcd, &out.Etcd, s); err != nil { return err } + if err := Convert_v1alpha1_KubeletConfiguration_To_kubeadm_KubeletConfiguration(&in.KubeletConfiguration, &out.KubeletConfiguration, s); err != nil { + return err + } if err := Convert_v1alpha1_Networking_To_kubeadm_Networking(&in.Networking, &out.Networking, s); err != nil { return err } @@ -119,10 +203,13 @@ func autoConvert_v1alpha1_MasterConfiguration_To_kubeadm_MasterConfiguration(in out.NodeName = in.NodeName out.AuthorizationModes = *(*[]string)(unsafe.Pointer(&in.AuthorizationModes)) out.Token = in.Token - out.TokenTTL = in.TokenTTL + out.TokenTTL = (*v1.Duration)(unsafe.Pointer(in.TokenTTL)) out.APIServerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.APIServerExtraArgs)) out.ControllerManagerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ControllerManagerExtraArgs)) out.SchedulerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.SchedulerExtraArgs)) + out.APIServerExtraVolumes = *(*[]kubeadm.HostPathMount)(unsafe.Pointer(&in.APIServerExtraVolumes)) + out.ControllerManagerExtraVolumes = *(*[]kubeadm.HostPathMount)(unsafe.Pointer(&in.ControllerManagerExtraVolumes)) + out.SchedulerExtraVolumes = *(*[]kubeadm.HostPathMount)(unsafe.Pointer(&in.SchedulerExtraVolumes)) out.APIServerCertSANs = *(*[]string)(unsafe.Pointer(&in.APIServerCertSANs)) out.CertificatesDir = in.CertificatesDir out.ImageRepository = in.ImageRepository @@ -140,9 +227,15 @@ func autoConvert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration(in if err := Convert_kubeadm_API_To_v1alpha1_API(&in.API, &out.API, s); err != nil { return err } + if err := Convert_kubeadm_KubeProxy_To_v1alpha1_KubeProxy(&in.KubeProxy, &out.KubeProxy, s); err != nil { + return err + } if err := Convert_kubeadm_Etcd_To_v1alpha1_Etcd(&in.Etcd, &out.Etcd, s); err != nil { return err } + if err := Convert_kubeadm_KubeletConfiguration_To_v1alpha1_KubeletConfiguration(&in.KubeletConfiguration, &out.KubeletConfiguration, s); err != nil { + return err + } if err := Convert_kubeadm_Networking_To_v1alpha1_Networking(&in.Networking, &out.Networking, s); err != nil { return err } @@ -151,10 +244,13 @@ func autoConvert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration(in out.NodeName = in.NodeName out.AuthorizationModes = *(*[]string)(unsafe.Pointer(&in.AuthorizationModes)) out.Token = in.Token - out.TokenTTL = in.TokenTTL + out.TokenTTL = (*v1.Duration)(unsafe.Pointer(in.TokenTTL)) out.APIServerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.APIServerExtraArgs)) out.ControllerManagerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ControllerManagerExtraArgs)) out.SchedulerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.SchedulerExtraArgs)) + out.APIServerExtraVolumes = *(*[]HostPathMount)(unsafe.Pointer(&in.APIServerExtraVolumes)) + out.ControllerManagerExtraVolumes = *(*[]HostPathMount)(unsafe.Pointer(&in.ControllerManagerExtraVolumes)) + out.SchedulerExtraVolumes = *(*[]HostPathMount)(unsafe.Pointer(&in.SchedulerExtraVolumes)) out.APIServerCertSANs = *(*[]string)(unsafe.Pointer(&in.APIServerCertSANs)) out.CertificatesDir = in.CertificatesDir out.ImageRepository = in.ImageRepository @@ -203,6 +299,7 @@ func autoConvert_v1alpha1_NodeConfiguration_To_kubeadm_NodeConfiguration(in *Nod out.Token = in.Token out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes)) out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification + out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates)) return nil } @@ -221,6 +318,7 @@ func autoConvert_kubeadm_NodeConfiguration_To_v1alpha1_NodeConfiguration(in *kub out.Token = in.Token out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes)) out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification + out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates)) return nil } @@ -229,6 +327,32 @@ func Convert_kubeadm_NodeConfiguration_To_v1alpha1_NodeConfiguration(in *kubeadm return autoConvert_kubeadm_NodeConfiguration_To_v1alpha1_NodeConfiguration(in, out, s) } +func autoConvert_v1alpha1_SelfHostedEtcd_To_kubeadm_SelfHostedEtcd(in *SelfHostedEtcd, out *kubeadm.SelfHostedEtcd, s conversion.Scope) error { + out.CertificatesDir = in.CertificatesDir + out.ClusterServiceName = in.ClusterServiceName + out.EtcdVersion = in.EtcdVersion + out.OperatorVersion = in.OperatorVersion + return nil +} + +// Convert_v1alpha1_SelfHostedEtcd_To_kubeadm_SelfHostedEtcd is an autogenerated conversion function. +func Convert_v1alpha1_SelfHostedEtcd_To_kubeadm_SelfHostedEtcd(in *SelfHostedEtcd, out *kubeadm.SelfHostedEtcd, s conversion.Scope) error { + return autoConvert_v1alpha1_SelfHostedEtcd_To_kubeadm_SelfHostedEtcd(in, out, s) +} + +func autoConvert_kubeadm_SelfHostedEtcd_To_v1alpha1_SelfHostedEtcd(in *kubeadm.SelfHostedEtcd, out *SelfHostedEtcd, s conversion.Scope) error { + out.CertificatesDir = in.CertificatesDir + out.ClusterServiceName = in.ClusterServiceName + out.EtcdVersion = in.EtcdVersion + out.OperatorVersion = in.OperatorVersion + return nil +} + +// Convert_kubeadm_SelfHostedEtcd_To_v1alpha1_SelfHostedEtcd is an autogenerated conversion function. +func Convert_kubeadm_SelfHostedEtcd_To_v1alpha1_SelfHostedEtcd(in *kubeadm.SelfHostedEtcd, out *SelfHostedEtcd, s conversion.Scope) error { + return autoConvert_kubeadm_SelfHostedEtcd_To_v1alpha1_SelfHostedEtcd(in, out, s) +} + func autoConvert_v1alpha1_TokenDiscovery_To_kubeadm_TokenDiscovery(in *TokenDiscovery, out *kubeadm.TokenDiscovery, s conversion.Scope) error { out.ID = in.ID out.Secret = in.Secret diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.deepcopy.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.deepcopy.go index 79473a06973..720496b407e 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.deepcopy.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,43 +21,12 @@ limitations under the License. package v1alpha1 import ( - conversion "k8s.io/apimachinery/pkg/conversion" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" + kubeletconfig_v1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" + kubeproxyconfig_v1alpha1 "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1" ) -// GetGeneratedDeepCopyFuncs returns the generated funcs, since we aren't registering them. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func GetGeneratedDeepCopyFuncs() []conversion.GeneratedDeepCopyFunc { - return []conversion.GeneratedDeepCopyFunc{ - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*API).DeepCopyInto(out.(*API)) - return nil - }, InType: reflect.TypeOf(&API{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Etcd).DeepCopyInto(out.(*Etcd)) - return nil - }, InType: reflect.TypeOf(&Etcd{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*MasterConfiguration).DeepCopyInto(out.(*MasterConfiguration)) - return nil - }, InType: reflect.TypeOf(&MasterConfiguration{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Networking).DeepCopyInto(out.(*Networking)) - return nil - }, InType: reflect.TypeOf(&Networking{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeConfiguration).DeepCopyInto(out.(*NodeConfiguration)) - return nil - }, InType: reflect.TypeOf(&NodeConfiguration{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*TokenDiscovery).DeepCopyInto(out.(*TokenDiscovery)) - return nil - }, InType: reflect.TypeOf(&TokenDiscovery{})}, - } -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *API) DeepCopyInto(out *API) { *out = *in @@ -89,6 +58,15 @@ func (in *Etcd) DeepCopyInto(out *Etcd) { (*out)[key] = val } } + if in.SelfHosted != nil { + in, out := &in.SelfHosted, &out.SelfHosted + if *in == nil { + *out = nil + } else { + *out = new(SelfHostedEtcd) + **out = **in + } + } return } @@ -102,19 +80,95 @@ func (in *Etcd) DeepCopy() *Etcd { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HostPathMount) DeepCopyInto(out *HostPathMount) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostPathMount. +func (in *HostPathMount) DeepCopy() *HostPathMount { + if in == nil { + return nil + } + out := new(HostPathMount) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeProxy) DeepCopyInto(out *KubeProxy) { + *out = *in + if in.Config != nil { + in, out := &in.Config, &out.Config + if *in == nil { + *out = nil + } else { + *out = new(kubeproxyconfig_v1alpha1.KubeProxyConfiguration) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeProxy. +func (in *KubeProxy) DeepCopy() *KubeProxy { + if in == nil { + return nil + } + out := new(KubeProxy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) { + *out = *in + if in.BaseConfig != nil { + in, out := &in.BaseConfig, &out.BaseConfig + if *in == nil { + *out = nil + } else { + *out = new(kubeletconfig_v1alpha1.KubeletConfiguration) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeletConfiguration. +func (in *KubeletConfiguration) DeepCopy() *KubeletConfiguration { + if in == nil { + return nil + } + out := new(KubeletConfiguration) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MasterConfiguration) DeepCopyInto(out *MasterConfiguration) { *out = *in out.TypeMeta = in.TypeMeta out.API = in.API + in.KubeProxy.DeepCopyInto(&out.KubeProxy) in.Etcd.DeepCopyInto(&out.Etcd) + in.KubeletConfiguration.DeepCopyInto(&out.KubeletConfiguration) out.Networking = in.Networking if in.AuthorizationModes != nil { in, out := &in.AuthorizationModes, &out.AuthorizationModes *out = make([]string, len(*in)) copy(*out, *in) } - out.TokenTTL = in.TokenTTL + if in.TokenTTL != nil { + in, out := &in.TokenTTL, &out.TokenTTL + if *in == nil { + *out = nil + } else { + *out = new(v1.Duration) + **out = **in + } + } if in.APIServerExtraArgs != nil { in, out := &in.APIServerExtraArgs, &out.APIServerExtraArgs *out = make(map[string]string, len(*in)) @@ -136,6 +190,21 @@ func (in *MasterConfiguration) DeepCopyInto(out *MasterConfiguration) { (*out)[key] = val } } + if in.APIServerExtraVolumes != nil { + in, out := &in.APIServerExtraVolumes, &out.APIServerExtraVolumes + *out = make([]HostPathMount, len(*in)) + copy(*out, *in) + } + if in.ControllerManagerExtraVolumes != nil { + in, out := &in.ControllerManagerExtraVolumes, &out.ControllerManagerExtraVolumes + *out = make([]HostPathMount, len(*in)) + copy(*out, *in) + } + if in.SchedulerExtraVolumes != nil { + in, out := &in.SchedulerExtraVolumes, &out.SchedulerExtraVolumes + *out = make([]HostPathMount, len(*in)) + copy(*out, *in) + } if in.APIServerCertSANs != nil { in, out := &in.APIServerCertSANs, &out.APIServerCertSANs *out = make([]string, len(*in)) @@ -200,6 +269,13 @@ func (in *NodeConfiguration) DeepCopyInto(out *NodeConfiguration) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.FeatureGates != nil { + in, out := &in.FeatureGates, &out.FeatureGates + *out = make(map[string]bool, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } return } @@ -222,6 +298,22 @@ func (in *NodeConfiguration) DeepCopyObject() runtime.Object { } } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SelfHostedEtcd) DeepCopyInto(out *SelfHostedEtcd) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SelfHostedEtcd. +func (in *SelfHostedEtcd) DeepCopy() *SelfHostedEtcd { + if in == nil { + return nil + } + out := new(SelfHostedEtcd) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TokenDiscovery) DeepCopyInto(out *TokenDiscovery) { *out = *in diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.defaults.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.defaults.go index 47f1f439d4b..cf5182a6cf9 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.defaults.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,6 +22,8 @@ package v1alpha1 import ( runtime "k8s.io/apimachinery/pkg/runtime" + kubeletconfig_v1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" + kubeproxyconfig_v1alpha1 "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1" ) // RegisterDefaults adds defaulters functions to the given scheme. @@ -35,6 +37,12 @@ func RegisterDefaults(scheme *runtime.Scheme) error { func SetObjectDefaults_MasterConfiguration(in *MasterConfiguration) { SetDefaults_MasterConfiguration(in) + if in.KubeProxy.Config != nil { + kubeproxyconfig_v1alpha1.SetDefaults_KubeProxyConfiguration(in.KubeProxy.Config) + } + if in.KubeletConfiguration.BaseConfig != nil { + kubeletconfig_v1alpha1.SetDefaults_KubeletConfiguration(in.KubeletConfiguration.BaseConfig) + } } func SetObjectDefaults_NodeConfiguration(in *NodeConfiguration) { diff --git a/cmd/kubeadm/app/apis/kubeadm/validation/BUILD b/cmd/kubeadm/app/apis/kubeadm/validation/BUILD index 263abbb7a9d..01f8f8a4fc4 100644 --- a/cmd/kubeadm/app/apis/kubeadm/validation/BUILD +++ b/cmd/kubeadm/app/apis/kubeadm/validation/BUILD @@ -1,43 +1,49 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = ["validation_test.go"], - importpath = "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation", - library = ":go_default_library", - deps = [ - "//cmd/kubeadm/app/apis/kubeadm:go_default_library", - "//vendor/github.com/spf13/pflag:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", - ], -) +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = ["validation.go"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation", + visibility = ["//visibility:public"], deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/features:go_default_library", "//cmd/kubeadm/app/util:go_default_library", "//cmd/kubeadm/app/util/token:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//pkg/kubeapiserver/authorizer/modes:go_default_library", + "//pkg/kubelet/apis/kubeletconfig:go_default_library", + "//pkg/kubelet/apis/kubeletconfig/scheme:go_default_library", + "//pkg/kubelet/apis/kubeletconfig/validation:go_default_library", + "//pkg/proxy/apis/kubeproxyconfig:go_default_library", + "//pkg/proxy/apis/kubeproxyconfig/scheme:go_default_library", + "//pkg/proxy/apis/kubeproxyconfig/validation:go_default_library", "//pkg/registry/core/service/ipallocator:go_default_library", "//pkg/util/node:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", ], ) +go_test( + name = "go_default_test", + srcs = ["validation_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation", + deps = [ + "//cmd/kubeadm/app/apis/kubeadm:go_default_library", + "//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library", + "//pkg/proxy/apis/kubeproxyconfig/v1alpha1:go_default_library", + "//pkg/util/pointer:go_default_library", + "//vendor/github.com/spf13/pflag:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + ], +) + filegroup( name = "package-srcs", srcs = glob(["**"]), @@ -49,4 +55,5 @@ filegroup( name = "all-srcs", srcs = [":package-srcs"], tags = ["automanaged"], + visibility = ["//visibility:public"], ) diff --git a/cmd/kubeadm/app/apis/kubeadm/validation/validation.go b/cmd/kubeadm/app/apis/kubeadm/validation/validation.go index a42ce210304..3220b41aab4 100644 --- a/cmd/kubeadm/app/apis/kubeadm/validation/validation.go +++ b/cmd/kubeadm/app/apis/kubeadm/validation/validation.go @@ -26,6 +26,7 @@ import ( "github.com/spf13/pflag" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" @@ -33,8 +34,14 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/features" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token" - apivalidation "k8s.io/kubernetes/pkg/api/validation" + apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes" + "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" + kubeletscheme "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme" + kubeletvalidation "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/validation" + "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig" + kubeproxyscheme "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/scheme" + proxyvalidation "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/validation" "k8s.io/kubernetes/pkg/registry/core/service/ipallocator" "k8s.io/kubernetes/pkg/util/node" ) @@ -70,9 +77,27 @@ func ValidateMasterConfiguration(c *kubeadm.MasterConfiguration) field.ErrorList allErrs = append(allErrs, ValidateToken(c.Token, field.NewPath("token"))...) allErrs = append(allErrs, ValidateFeatureGates(c.FeatureGates, field.NewPath("feature-gates"))...) allErrs = append(allErrs, ValidateAPIEndpoint(c, field.NewPath("api-endpoint"))...) + allErrs = append(allErrs, ValidateProxy(c, field.NewPath("kube-proxy"))...) + if features.Enabled(c.FeatureGates, features.DynamicKubeletConfig) { + allErrs = append(allErrs, ValidateKubeletConfiguration(&c.KubeletConfiguration, field.NewPath("kubeletConfiguration"))...) + } return allErrs } +// ValidateProxy validates proxy configuration and collects all encountered errors +func ValidateProxy(c *kubeadm.MasterConfiguration, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + // Convert to the internal version + internalcfg := &kubeproxyconfig.KubeProxyConfiguration{} + err := kubeproxyscheme.Scheme.Convert(c.KubeProxy.Config, internalcfg, nil) + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath, "KubeProxy.Config", err.Error())) + return allErrs + } + return proxyvalidation.Validate(internalcfg) +} + // ValidateNodeConfiguration validates node configuration and collects all encountered errors func ValidateNodeConfiguration(c *kubeadm.NodeConfiguration) field.ErrorList { allErrs := field.ErrorList{} @@ -120,12 +145,6 @@ func ValidateDiscovery(c *kubeadm.NodeConfiguration, fldPath *field.Path) field. allErrs = append(allErrs, ValidateToken(c.TLSBootstrapToken, fldPath)...) allErrs = append(allErrs, ValidateJoinDiscoveryTokenAPIServer(c, fldPath)...) - if len(c.DiscoveryToken) != 0 { - allErrs = append(allErrs, ValidateToken(c.DiscoveryToken, fldPath)...) - } - if len(c.DiscoveryFile) != 0 { - allErrs = append(allErrs, ValidateDiscoveryFile(c.DiscoveryFile, fldPath)...) - } return allErrs } @@ -146,10 +165,9 @@ func ValidateArgSelection(cfg *kubeadm.NodeConfiguration, fldPath *field.Path) f allErrs = append(allErrs, field.Invalid(fldPath, "", "DiscoveryTokenCACertHashes cannot be used with DiscoveryFile")) } - // TODO: convert this warning to an error after v1.8 - if len(cfg.DiscoveryFile) == 0 && len(cfg.DiscoveryTokenCACertHashes) == 0 && !cfg.DiscoveryTokenUnsafeSkipCAVerification { - fmt.Println("[validation] WARNING: using token-based discovery without DiscoveryTokenCACertHashes can be unsafe (see https://kubernetes.io/docs/admin/kubeadm/#kubeadm-join).") - fmt.Println("[validation] WARNING: Pass --discovery-token-unsafe-skip-ca-verification to disable this warning. This warning will become an error in Kubernetes 1.9.") + if len(cfg.DiscoveryFile) == 0 && len(cfg.DiscoveryToken) != 0 && + len(cfg.DiscoveryTokenCACertHashes) == 0 && !cfg.DiscoveryTokenUnsafeSkipCAVerification { + allErrs = append(allErrs, field.Invalid(fldPath, "", "using token-based discovery without DiscoveryTokenCACertHashes can be unsafe. set --discovery-token-unsafe-skip-ca-verification to continue")) } // TODO remove once we support multiple api servers @@ -297,7 +315,7 @@ func ValidateMixedArguments(flag *pflag.FlagSet) error { mixedInvalidFlags := []string{} flag.Visit(func(f *pflag.Flag) { - if f.Name == "config" || strings.HasPrefix(f.Name, "skip-") || f.Name == "dry-run" || f.Name == "kubeconfig" { + if f.Name == "config" || f.Name == "ignore-preflight-errors" || strings.HasPrefix(f.Name, "skip-") || f.Name == "dry-run" || f.Name == "kubeconfig" { // "--skip-*" flags or other whitelisted flags can be set with --config return } @@ -336,3 +354,50 @@ func ValidateAPIEndpoint(c *kubeadm.MasterConfiguration, fldPath *field.Path) fi } return allErrs } + +// ValidateIgnorePreflightErrors validates duplicates in ignore-preflight-errors flag. +func ValidateIgnorePreflightErrors(ignorePreflightErrors []string, skipPreflightChecks bool) (sets.String, error) { + ignoreErrors := sets.NewString() + allErrs := field.ErrorList{} + + for _, item := range ignorePreflightErrors { + ignoreErrors.Insert(strings.ToLower(item)) // parameters are case insensitive + } + + // TODO: remove once deprecated flag --skip-preflight-checks is removed. + if skipPreflightChecks { + ignoreErrors.Insert("all") + } + + if ignoreErrors.Has("all") && ignoreErrors.Len() > 1 { + allErrs = append(allErrs, field.Invalid(field.NewPath("ignore-preflight-errors"), strings.Join(ignoreErrors.List(), ","), "don't specify individual checks if 'all' is used")) + } + + return ignoreErrors, allErrs.ToAggregate() +} + +// ValidateKubeletConfiguration validates kubelet configuration and collects all encountered errors +func ValidateKubeletConfiguration(c *kubeadm.KubeletConfiguration, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + scheme, _, err := kubeletscheme.NewSchemeAndCodecs() + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath, "kubeletConfiguration", err.Error())) + return allErrs + } + + // Convert versioned config to internal config + internalcfg := &kubeletconfig.KubeletConfiguration{} + err = scheme.Convert(c.BaseConfig, internalcfg, nil) + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath, "kubeletConfiguration", err.Error())) + return allErrs + } + + err = kubeletvalidation.ValidateKubeletConfiguration(internalcfg) + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath, "kubeletConfiguration", err.Error())) + } + + return allErrs +} diff --git a/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go b/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go index 2344c9a97ff..d23215d8590 100644 --- a/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go +++ b/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go @@ -17,12 +17,20 @@ limitations under the License. package validation import ( + "io/ioutil" + "os" + "strings" "testing" + "time" "github.com/spf13/pflag" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" + kubeproxyconfigv1alpha1 "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1" + utilpointer "k8s.io/kubernetes/pkg/util/pointer" ) func TestValidateTokenDiscovery(t *testing.T) { @@ -31,12 +39,13 @@ func TestValidateTokenDiscovery(t *testing.T) { f *field.Path expected bool }{ - {&kubeadm.NodeConfiguration{Token: "772ef5.6b6baab1d4a0a171", DiscoveryTokenAPIServers: []string{"192.168.122.100:9898"}}, nil, true}, - {&kubeadm.NodeConfiguration{Token: ".6b6baab1d4a0a171", DiscoveryTokenAPIServers: []string{"192.168.122.100:9898"}}, nil, false}, - {&kubeadm.NodeConfiguration{Token: "772ef5.", DiscoveryTokenAPIServers: []string{"192.168.122.100:9898"}}, nil, false}, - {&kubeadm.NodeConfiguration{Token: "772ef5.6b6baab1d4a0a171", DiscoveryTokenAPIServers: []string{"2001:db8::100:9898"}}, nil, true}, - {&kubeadm.NodeConfiguration{Token: ".6b6baab1d4a0a171", DiscoveryTokenAPIServers: []string{"2001:db8::100:9898"}}, nil, false}, - {&kubeadm.NodeConfiguration{Token: "772ef5.", DiscoveryTokenAPIServers: []string{"2001:db8::100:9898"}}, nil, false}, + {&kubeadm.NodeConfiguration{Token: "772ef5.6b6baab1d4a0a171", DiscoveryTokenAPIServers: []string{"192.168.122.100:6443"}}, nil, true}, + {&kubeadm.NodeConfiguration{Token: ".6b6baab1d4a0a171", DiscoveryTokenAPIServers: []string{"192.168.122.100:6443"}}, nil, false}, + {&kubeadm.NodeConfiguration{Token: "772ef5.", DiscoveryTokenAPIServers: []string{"192.168.122.100:6443"}}, nil, false}, + {&kubeadm.NodeConfiguration{Token: "772ef5.6b6baab1d4a0a171", DiscoveryTokenAPIServers: []string{"2001:db8::100:6443"}}, nil, true}, + {&kubeadm.NodeConfiguration{Token: ".6b6baab1d4a0a171", DiscoveryTokenAPIServers: []string{"2001:db8::100:6443"}}, nil, false}, + {&kubeadm.NodeConfiguration{Token: "772ef5.", DiscoveryTokenAPIServers: []string{"2001:db8::100:6443"}}, nil, false}, + {&kubeadm.NodeConfiguration{Token: "abcdef.1234567890123456@foobar", DiscoveryTokenAPIServers: []string{"192.168.122.100:6443"}}, nil, false}, } for _, rt := range tests { err := ValidateToken(rt.c.Token, rt.f).ToAggregate() @@ -325,7 +334,7 @@ func TestValidateMasterConfiguration(t *testing.T) { CertificatesDir: "/some/other/cert/dir", Token: "abcdef.0123456789abcdef", }, false}, - {"valid master configuration with IPv4 service subnet", + {"valid master configuration with incorrect IPv4 pod subnet", &kubeadm.MasterConfiguration{ API: kubeadm.API{ AdvertiseAddress: "1.2.3.4", @@ -335,6 +344,49 @@ func TestValidateMasterConfiguration(t *testing.T) { Networking: kubeadm.Networking{ ServiceSubnet: "10.96.0.1/12", DNSDomain: "cluster.local", + PodSubnet: "10.0.1.15", + }, + CertificatesDir: "/some/other/cert/dir", + Token: "abcdef.0123456789abcdef", + NodeName: nodename, + }, false}, + {"valid master configuration with IPv4 service subnet", + &kubeadm.MasterConfiguration{ + API: kubeadm.API{ + AdvertiseAddress: "1.2.3.4", + BindPort: 6443, + }, + KubeProxy: kubeadm.KubeProxy{ + Config: &kubeproxyconfigv1alpha1.KubeProxyConfiguration{ + BindAddress: "192.168.59.103", + HealthzBindAddress: "0.0.0.0:10256", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24", + UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfigv1alpha1.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + IPVS: kubeproxyconfigv1alpha1.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + }, + Conntrack: kubeproxyconfigv1alpha1.KubeProxyConntrackConfiguration{ + Max: utilpointer.Int32Ptr(2), + MaxPerCore: utilpointer.Int32Ptr(1), + Min: utilpointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + }, + AuthorizationModes: []string{"Node", "RBAC"}, + Networking: kubeadm.Networking{ + ServiceSubnet: "10.96.0.1/12", + DNSDomain: "cluster.local", + PodSubnet: "10.0.1.15/16", }, CertificatesDir: "/some/other/cert/dir", Token: "abcdef.0123456789abcdef", @@ -346,6 +398,32 @@ func TestValidateMasterConfiguration(t *testing.T) { AdvertiseAddress: "1:2:3::4", BindPort: 3446, }, + KubeProxy: kubeadm.KubeProxy{ + Config: &kubeproxyconfigv1alpha1.KubeProxyConfiguration{ + BindAddress: "192.168.59.103", + HealthzBindAddress: "0.0.0.0:10256", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24", + UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfigv1alpha1.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + IPVS: kubeproxyconfigv1alpha1.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + }, + Conntrack: kubeproxyconfigv1alpha1.KubeProxyConntrackConfiguration{ + Max: utilpointer.Int32Ptr(2), + MaxPerCore: utilpointer.Int32Ptr(1), + Min: utilpointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + }, AuthorizationModes: []string{"Node", "RBAC"}, Networking: kubeadm.Networking{ ServiceSubnet: "2001:db8::1/98", @@ -458,3 +536,506 @@ func TestValidateFeatureGates(t *testing.T) { } } } + +func TestValidateIgnorePreflightErrors(t *testing.T) { + var tests = []struct { + ignorePreflightErrors []string + skipPreflightChecks bool + expectedLen int + expectedError bool + }{ + {[]string{}, false, 0, false}, // empty list, no old skip-preflight-checks + {[]string{}, true, 1, false}, // empty list, old skip-preflight-checks + {[]string{"check1", "check2"}, false, 2, false}, // non-duplicate + {[]string{"check1", "check2"}, true, 3, true}, // non-duplicate, but skip-preflight-checks + {[]string{"check1", "check2", "check1"}, false, 2, false}, // duplicates + {[]string{"check1", "check2", "all"}, false, 3, true}, // non-duplicate, but 'all' present together wth individual checks + {[]string{"all"}, false, 1, false}, // skip all checks by using new flag + {[]string{"all"}, true, 1, false}, // skip all checks by using both old and new flags at the same time + } + for _, rt := range tests { + result, err := ValidateIgnorePreflightErrors(rt.ignorePreflightErrors, rt.skipPreflightChecks) + switch { + case err != nil && !rt.expectedError: + t.Errorf("ValidateIgnorePreflightErrors: unexpected error for input (%s, %v), error: %v", rt.ignorePreflightErrors, rt.skipPreflightChecks, err) + case err == nil && rt.expectedError: + t.Errorf("ValidateIgnorePreflightErrors: expected error for input (%s, %v) but got: %v", rt.ignorePreflightErrors, rt.skipPreflightChecks, result) + case result.Len() != rt.expectedLen: + t.Errorf("ValidateIgnorePreflightErrors: expected Len = %d for input (%s, %v) but got: %v, %v", rt.expectedLen, rt.ignorePreflightErrors, rt.skipPreflightChecks, result.Len(), result) + } + } +} + +func TestValidateKubeletConfiguration(t *testing.T) { + successCase := &kubeadm.KubeletConfiguration{ + BaseConfig: &kubeletconfigv1alpha1.KubeletConfiguration{ + CgroupsPerQOS: utilpointer.BoolPtr(true), + EnforceNodeAllocatable: []string{"pods", "system-reserved", "kube-reserved"}, + SystemCgroups: "", + CgroupRoot: "", + CAdvisorPort: utilpointer.Int32Ptr(0), + EventBurst: 10, + EventRecordQPS: utilpointer.Int32Ptr(5), + HealthzPort: utilpointer.Int32Ptr(10248), + ImageGCHighThresholdPercent: utilpointer.Int32Ptr(85), + ImageGCLowThresholdPercent: utilpointer.Int32Ptr(80), + IPTablesDropBit: utilpointer.Int32Ptr(15), + IPTablesMasqueradeBit: utilpointer.Int32Ptr(14), + KubeAPIBurst: 10, + KubeAPIQPS: utilpointer.Int32Ptr(5), + MaxOpenFiles: 1000000, + MaxPods: 110, + OOMScoreAdj: utilpointer.Int32Ptr(-999), + PodsPerCore: 100, + Port: 65535, + ReadOnlyPort: utilpointer.Int32Ptr(0), + RegistryBurst: 10, + RegistryPullQPS: utilpointer.Int32Ptr(5), + HairpinMode: "promiscuous-bridge", + }, + } + if allErrors := ValidateKubeletConfiguration(successCase, nil); len(allErrors) != 0 { + t.Errorf("failed ValidateKubeletConfiguration: expect no errors but got %v", allErrors) + } + + errorCase := &kubeadm.KubeletConfiguration{ + BaseConfig: &kubeletconfigv1alpha1.KubeletConfiguration{ + CgroupsPerQOS: utilpointer.BoolPtr(false), + EnforceNodeAllocatable: []string{"pods", "system-reserved", "kube-reserved", "illegal-key"}, + SystemCgroups: "/", + CgroupRoot: "", + CAdvisorPort: utilpointer.Int32Ptr(-10), + EventBurst: -10, + EventRecordQPS: utilpointer.Int32Ptr(-10), + HealthzPort: utilpointer.Int32Ptr(-10), + ImageGCHighThresholdPercent: utilpointer.Int32Ptr(101), + ImageGCLowThresholdPercent: utilpointer.Int32Ptr(101), + IPTablesDropBit: utilpointer.Int32Ptr(-10), + IPTablesMasqueradeBit: utilpointer.Int32Ptr(-10), + KubeAPIBurst: -10, + KubeAPIQPS: utilpointer.Int32Ptr(-10), + MaxOpenFiles: -10, + MaxPods: -10, + OOMScoreAdj: utilpointer.Int32Ptr(-1001), + PodsPerCore: -10, + Port: 0, + ReadOnlyPort: utilpointer.Int32Ptr(-10), + RegistryBurst: -10, + RegistryPullQPS: utilpointer.Int32Ptr(-10), + }, + } + if allErrors := ValidateKubeletConfiguration(errorCase, nil); len(allErrors) == 0 { + t.Errorf("failed ValidateKubeletConfiguration: expect errors but got no error") + } +} + +func TestValidateKubeProxyConfiguration(t *testing.T) { + successCases := []kubeadm.MasterConfiguration{ + { + KubeProxy: kubeadm.KubeProxy{ + Config: &kubeproxyconfigv1alpha1.KubeProxyConfiguration{ + BindAddress: "192.168.59.103", + HealthzBindAddress: "0.0.0.0:10256", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24", + UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfigv1alpha1.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + IPVS: kubeproxyconfigv1alpha1.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + }, + Conntrack: kubeproxyconfigv1alpha1.KubeProxyConntrackConfiguration{ + Max: utilpointer.Int32Ptr(2), + MaxPerCore: utilpointer.Int32Ptr(1), + Min: utilpointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + }, + }, + } + + for _, successCase := range successCases { + if errs := ValidateProxy(&successCase, nil); len(errs) != 0 { + t.Errorf("failed ValidateProxy: expect no errors but got %v", errs) + } + } + + errorCases := []struct { + masterConfig kubeadm.MasterConfiguration + msg string + }{ + { + masterConfig: kubeadm.MasterConfiguration{ + KubeProxy: kubeadm.KubeProxy{ + Config: &kubeproxyconfigv1alpha1.KubeProxyConfiguration{ + // only BindAddress is invalid + BindAddress: "10.10.12.11:2000", + HealthzBindAddress: "0.0.0.0:10256", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24", + UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfigv1alpha1.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + IPVS: kubeproxyconfigv1alpha1.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + }, + Conntrack: kubeproxyconfigv1alpha1.KubeProxyConntrackConfiguration{ + Max: utilpointer.Int32Ptr(2), + MaxPerCore: utilpointer.Int32Ptr(1), + Min: utilpointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + }, + }, + msg: "not a valid textual representation of an IP address", + }, + { + masterConfig: kubeadm.MasterConfiguration{ + KubeProxy: kubeadm.KubeProxy{ + Config: &kubeproxyconfigv1alpha1.KubeProxyConfiguration{ + BindAddress: "10.10.12.11", + // only HealthzBindAddress is invalid + HealthzBindAddress: "0.0.0.0", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24", + UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfigv1alpha1.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + IPVS: kubeproxyconfigv1alpha1.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + }, + Conntrack: kubeproxyconfigv1alpha1.KubeProxyConntrackConfiguration{ + Max: utilpointer.Int32Ptr(2), + MaxPerCore: utilpointer.Int32Ptr(1), + Min: utilpointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + }, + }, + msg: "must be IP:port", + }, + { + masterConfig: kubeadm.MasterConfiguration{ + KubeProxy: kubeadm.KubeProxy{ + Config: &kubeproxyconfigv1alpha1.KubeProxyConfiguration{ + BindAddress: "10.10.12.11", + HealthzBindAddress: "0.0.0.0:12345", + // only MetricsBindAddress is invalid + MetricsBindAddress: "127.0.0.1", + ClusterCIDR: "192.168.59.0/24", + UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfigv1alpha1.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + IPVS: kubeproxyconfigv1alpha1.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + }, + Conntrack: kubeproxyconfigv1alpha1.KubeProxyConntrackConfiguration{ + Max: utilpointer.Int32Ptr(2), + MaxPerCore: utilpointer.Int32Ptr(1), + Min: utilpointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + }, + }, + msg: "must be IP:port", + }, + { + masterConfig: kubeadm.MasterConfiguration{ + KubeProxy: kubeadm.KubeProxy{ + Config: &kubeproxyconfigv1alpha1.KubeProxyConfiguration{ + BindAddress: "10.10.12.11", + HealthzBindAddress: "0.0.0.0:12345", + MetricsBindAddress: "127.0.0.1:10249", + // only ClusterCIDR is invalid + ClusterCIDR: "192.168.59.0", + UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfigv1alpha1.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + IPVS: kubeproxyconfigv1alpha1.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + }, + Conntrack: kubeproxyconfigv1alpha1.KubeProxyConntrackConfiguration{ + Max: utilpointer.Int32Ptr(2), + MaxPerCore: utilpointer.Int32Ptr(1), + Min: utilpointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + }, + }, + msg: "must be a valid CIDR block (e.g. 10.100.0.0/16)", + }, + { + masterConfig: kubeadm.MasterConfiguration{ + KubeProxy: kubeadm.KubeProxy{ + Config: &kubeproxyconfigv1alpha1.KubeProxyConfiguration{ + BindAddress: "10.10.12.11", + HealthzBindAddress: "0.0.0.0:12345", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24", + // only UDPIdleTimeout is invalid + UDPIdleTimeout: metav1.Duration{Duration: -1 * time.Second}, + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfigv1alpha1.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + IPVS: kubeproxyconfigv1alpha1.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + }, + Conntrack: kubeproxyconfigv1alpha1.KubeProxyConntrackConfiguration{ + Max: utilpointer.Int32Ptr(2), + MaxPerCore: utilpointer.Int32Ptr(1), + Min: utilpointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + }, + }, + msg: "must be greater than 0", + }, + { + masterConfig: kubeadm.MasterConfiguration{ + KubeProxy: kubeadm.KubeProxy{ + Config: &kubeproxyconfigv1alpha1.KubeProxyConfiguration{ + BindAddress: "10.10.12.11", + HealthzBindAddress: "0.0.0.0:12345", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24", + UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, + // only ConfigSyncPeriod is invalid + ConfigSyncPeriod: metav1.Duration{Duration: -1 * time.Second}, + IPTables: kubeproxyconfigv1alpha1.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + IPVS: kubeproxyconfigv1alpha1.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + }, + Conntrack: kubeproxyconfigv1alpha1.KubeProxyConntrackConfiguration{ + Max: utilpointer.Int32Ptr(2), + MaxPerCore: utilpointer.Int32Ptr(1), + Min: utilpointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + }, + }, + msg: "must be greater than 0", + }, + } + + for i, errorCase := range errorCases { + if errs := ValidateProxy(&errorCase.masterConfig, nil); len(errs) == 0 { + t.Errorf("%d failed ValidateProxy: expected error for %s, but got no error", i, errorCase.msg) + } else if !strings.Contains(errs[0].Error(), errorCase.msg) { + t.Errorf("%d failed ValidateProxy: unexpected error: %v, expected: %s", i, errs[0], errorCase.msg) + } + } +} + +func TestValidateArgSelection(t *testing.T) { + var tests = []struct { + name string + c *kubeadm.NodeConfiguration + expected bool + }{ + { + "invalid: DiscoveryToken and DiscoveryFile cannot both be set", + &kubeadm.NodeConfiguration{ + DiscoveryFile: "https://url/file.conf", + DiscoveryToken: "abcdef.1234567890123456", + }, + false, + }, + { + "invalid: DiscoveryToken or DiscoveryFile must be set", + &kubeadm.NodeConfiguration{ + DiscoveryFile: "", + DiscoveryToken: "", + }, + false, + }, + { + "invalid: DiscoveryTokenAPIServers not set", + &kubeadm.NodeConfiguration{ + DiscoveryToken: "abcdef.1234567890123456", + }, + false, + }, + { + "invalid: DiscoveryTokenCACertHashes cannot be used with DiscoveryFile", + &kubeadm.NodeConfiguration{ + DiscoveryFile: "https://url/file.conf", + DiscoveryTokenCACertHashes: []string{"sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc"}, + }, + false, + }, + { + "invalid: using token-based discovery without DiscoveryTokenCACertHashes and DiscoveryTokenUnsafeSkipCAVerification", + &kubeadm.NodeConfiguration{ + DiscoveryToken: "abcdef.1234567890123456", + DiscoveryTokenUnsafeSkipCAVerification: false, + DiscoveryTokenAPIServers: []string{"192.168.122.100:6443"}, + }, + false, + }, + { + "WARNING: kubeadm doesn't fully support multiple API Servers yet", + &kubeadm.NodeConfiguration{ + DiscoveryToken: "abcdef.1234567890123456", + DiscoveryTokenUnsafeSkipCAVerification: true, + DiscoveryTokenAPIServers: []string{"192.168.122.100:6443", "192.168.122.88:6443"}, + }, + true, + }, + { + "valid: DiscoveryFile with DiscoveryTokenAPIServers", + &kubeadm.NodeConfiguration{ + DiscoveryFile: "https://url/file.conf", + DiscoveryTokenAPIServers: []string{"192.168.122.100:6443"}, + }, + true, + }, + { + "valid: DiscoveryFile without DiscoveryTokenAPIServers", + &kubeadm.NodeConfiguration{ + DiscoveryFile: "https://url/file.conf", + }, + true, + }, + { + "valid: using token-based discovery with DiscoveryTokenCACertHashes", + &kubeadm.NodeConfiguration{ + DiscoveryToken: "abcdef.1234567890123456", + DiscoveryTokenAPIServers: []string{"192.168.122.100:6443"}, + DiscoveryTokenCACertHashes: []string{"sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc"}, + DiscoveryTokenUnsafeSkipCAVerification: false, + }, + true, + }, + { + "valid: using token-based discovery with DiscoveryTokenCACertHashe but skip ca verification", + &kubeadm.NodeConfiguration{ + DiscoveryToken: "abcdef.1234567890123456", + DiscoveryTokenAPIServers: []string{"192.168.122.100:6443"}, + DiscoveryTokenCACertHashes: []string{"sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc"}, + DiscoveryTokenUnsafeSkipCAVerification: true, + }, + true, + }, + } + for _, rt := range tests { + err := ValidateArgSelection(rt.c, nil).ToAggregate() + if (err == nil) != rt.expected { + t.Errorf( + "%s test case failed: ValidateArgSelection:\n\texpected: %t\n\t actual: %t", + rt.name, + rt.expected, + (err == nil), + ) + } + } +} + +func TestValidateJoinDiscoveryTokenAPIServer(t *testing.T) { + var tests = []struct { + s *kubeadm.NodeConfiguration + expected bool + }{ + { + &kubeadm.NodeConfiguration{ + DiscoveryTokenAPIServers: []string{"192.168.122.100"}, + }, + false, + }, + { + &kubeadm.NodeConfiguration{ + DiscoveryTokenAPIServers: []string{"192.168.122.100:6443"}, + }, + true, + }, + } + for _, rt := range tests { + actual := ValidateJoinDiscoveryTokenAPIServer(rt.s, nil) + if (len(actual) == 0) != rt.expected { + t.Errorf( + "failed ValidateJoinDiscoveryTokenAPIServer:\n\texpected: %t\n\t actual: %t", + rt.expected, + (len(actual) == 0), + ) + } + } +} + +func TestValidateDiscoveryFile(t *testing.T) { + tmpfile, err := ioutil.TempFile("/tmp", "test_discovery_file") + if err != nil { + t.Errorf("Error creating temporary file: %v", err) + } + defer os.Remove(tmpfile.Name()) + + var tests = []struct { + s string + expected bool + }{ + {"foo", false}, + {"/foo/bar/file_which_i_believe_not_existing.conf", false}, + {tmpfile.Name(), true}, + {"http://[::1]a", false}, + {"http://url/file.conf", false}, + {"https://u r l/file.conf", false}, + {"https://url/file.conf", true}, + } + for i, rt := range tests { + actual := ValidateDiscoveryFile(rt.s, nil) + if (len(actual) == 0) != rt.expected { + t.Errorf( + "%d: failed ValidateDiscoveryFile:\n\texpected: %t\n\t actual: %t", + i, + rt.expected, + (len(actual) == 0), + ) + } + } +} diff --git a/cmd/kubeadm/app/apis/kubeadm/well_known_labels.go b/cmd/kubeadm/app/apis/kubeadm/well_known_labels.go deleted file mode 100644 index f2fb530fdc0..00000000000 --- a/cmd/kubeadm/app/apis/kubeadm/well_known_labels.go +++ /dev/null @@ -1,43 +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 kubeadm - -// Role labels are applied to Nodes to mark their purpose. In particular, we -// usually want to distinguish the master, so that we can isolate privileged -// pods and operations. -// -// Originally we relied on not registering the master, on the fact that the -// master was Unschedulable, and on static manifests for master components. -// But we now do register masters in many environments, are generally moving -// away from static manifests (for better manageability), and working towards -// deprecating the unschedulable field (replacing it with taints & tolerations -// instead). -// -// Even with tainting, a label remains the easiest way of making a positive -// selection, so that pods can schedule only to master nodes for example, and -// thus installations will likely define a label for their master nodes. -// -// So that we can recognize master nodes in consequent places though (such as -// kubectl get nodes), we encourage installations to use the well-known labels. -// We define NodeLabelRole, which is the preferred form, but we will also recognize -// other forms that are known to be in widespread use (NodeLabelKubeadmAlphaRole). - -const ( - // NodeLabelKubeadmAlphaRole is a label that kubeadm applies to a Node as a hint that it has a particular purpose. - // Use of NodeLabelRole is preferred. - NodeLabelKubeadmAlphaRole = "kubeadm.alpha.kubernetes.io/role" -) diff --git a/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go b/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go index f9dd8993845..aa8c30e4b78 100644 --- a/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go +++ b/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,48 +21,12 @@ limitations under the License. package kubeadm import ( - conversion "k8s.io/apimachinery/pkg/conversion" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" + kubeletconfig_v1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" + v1alpha1 "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*API).DeepCopyInto(out.(*API)) - return nil - }, InType: reflect.TypeOf(&API{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Etcd).DeepCopyInto(out.(*Etcd)) - return nil - }, InType: reflect.TypeOf(&Etcd{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*MasterConfiguration).DeepCopyInto(out.(*MasterConfiguration)) - return nil - }, InType: reflect.TypeOf(&MasterConfiguration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Networking).DeepCopyInto(out.(*Networking)) - return nil - }, InType: reflect.TypeOf(&Networking{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeConfiguration).DeepCopyInto(out.(*NodeConfiguration)) - return nil - }, InType: reflect.TypeOf(&NodeConfiguration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*TokenDiscovery).DeepCopyInto(out.(*TokenDiscovery)) - return nil - }, InType: reflect.TypeOf(&TokenDiscovery{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *API) DeepCopyInto(out *API) { *out = *in @@ -94,6 +58,15 @@ func (in *Etcd) DeepCopyInto(out *Etcd) { (*out)[key] = val } } + if in.SelfHosted != nil { + in, out := &in.SelfHosted, &out.SelfHosted + if *in == nil { + *out = nil + } else { + *out = new(SelfHostedEtcd) + **out = **in + } + } return } @@ -107,19 +80,95 @@ func (in *Etcd) DeepCopy() *Etcd { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HostPathMount) DeepCopyInto(out *HostPathMount) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostPathMount. +func (in *HostPathMount) DeepCopy() *HostPathMount { + if in == nil { + return nil + } + out := new(HostPathMount) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeProxy) DeepCopyInto(out *KubeProxy) { + *out = *in + if in.Config != nil { + in, out := &in.Config, &out.Config + if *in == nil { + *out = nil + } else { + *out = new(v1alpha1.KubeProxyConfiguration) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeProxy. +func (in *KubeProxy) DeepCopy() *KubeProxy { + if in == nil { + return nil + } + out := new(KubeProxy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) { + *out = *in + if in.BaseConfig != nil { + in, out := &in.BaseConfig, &out.BaseConfig + if *in == nil { + *out = nil + } else { + *out = new(kubeletconfig_v1alpha1.KubeletConfiguration) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeletConfiguration. +func (in *KubeletConfiguration) DeepCopy() *KubeletConfiguration { + if in == nil { + return nil + } + out := new(KubeletConfiguration) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MasterConfiguration) DeepCopyInto(out *MasterConfiguration) { *out = *in out.TypeMeta = in.TypeMeta out.API = in.API + in.KubeProxy.DeepCopyInto(&out.KubeProxy) in.Etcd.DeepCopyInto(&out.Etcd) + in.KubeletConfiguration.DeepCopyInto(&out.KubeletConfiguration) out.Networking = in.Networking if in.AuthorizationModes != nil { in, out := &in.AuthorizationModes, &out.AuthorizationModes *out = make([]string, len(*in)) copy(*out, *in) } - out.TokenTTL = in.TokenTTL + if in.TokenTTL != nil { + in, out := &in.TokenTTL, &out.TokenTTL + if *in == nil { + *out = nil + } else { + *out = new(v1.Duration) + **out = **in + } + } if in.APIServerExtraArgs != nil { in, out := &in.APIServerExtraArgs, &out.APIServerExtraArgs *out = make(map[string]string, len(*in)) @@ -141,6 +190,21 @@ func (in *MasterConfiguration) DeepCopyInto(out *MasterConfiguration) { (*out)[key] = val } } + if in.APIServerExtraVolumes != nil { + in, out := &in.APIServerExtraVolumes, &out.APIServerExtraVolumes + *out = make([]HostPathMount, len(*in)) + copy(*out, *in) + } + if in.ControllerManagerExtraVolumes != nil { + in, out := &in.ControllerManagerExtraVolumes, &out.ControllerManagerExtraVolumes + *out = make([]HostPathMount, len(*in)) + copy(*out, *in) + } + if in.SchedulerExtraVolumes != nil { + in, out := &in.SchedulerExtraVolumes, &out.SchedulerExtraVolumes + *out = make([]HostPathMount, len(*in)) + copy(*out, *in) + } if in.APIServerCertSANs != nil { in, out := &in.APIServerCertSANs, &out.APIServerCertSANs *out = make([]string, len(*in)) @@ -205,6 +269,13 @@ func (in *NodeConfiguration) DeepCopyInto(out *NodeConfiguration) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.FeatureGates != nil { + in, out := &in.FeatureGates, &out.FeatureGates + *out = make(map[string]bool, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } return } @@ -227,6 +298,22 @@ func (in *NodeConfiguration) DeepCopyObject() runtime.Object { } } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SelfHostedEtcd) DeepCopyInto(out *SelfHostedEtcd) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SelfHostedEtcd. +func (in *SelfHostedEtcd) DeepCopy() *SelfHostedEtcd { + if in == nil { + return nil + } + out := new(SelfHostedEtcd) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TokenDiscovery) DeepCopyInto(out *TokenDiscovery) { *out = *in diff --git a/cmd/kubeadm/app/cmd/BUILD b/cmd/kubeadm/app/cmd/BUILD index 8978a499313..f15c577dee5 100644 --- a/cmd/kubeadm/app/cmd/BUILD +++ b/cmd/kubeadm/app/cmd/BUILD @@ -21,6 +21,7 @@ go_library( importpath = "k8s.io/kubernetes/cmd/kubeadm/app/cmd", deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", + "//cmd/kubeadm/app/apis/kubeadm/install:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library", "//cmd/kubeadm/app/cmd/phases:go_default_library", @@ -39,6 +40,7 @@ go_library( "//cmd/kubeadm/app/phases/controlplane:go_default_library", "//cmd/kubeadm/app/phases/etcd:go_default_library", "//cmd/kubeadm/app/phases/kubeconfig:go_default_library", + "//cmd/kubeadm/app/phases/kubelet:go_default_library", "//cmd/kubeadm/app/phases/markmaster:go_default_library", "//cmd/kubeadm/app/phases/selfhosting:go_default_library", "//cmd/kubeadm/app/phases/uploadconfig:go_default_library", @@ -50,14 +52,14 @@ go_library( "//cmd/kubeadm/app/util/kubeconfig:go_default_library", "//cmd/kubeadm/app/util/pubkeypin:go_default_library", "//cmd/kubeadm/app/util/token:go_default_library", - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/bootstrap/api:go_default_library", "//pkg/kubectl/cmd/util:go_default_library", "//pkg/kubectl/util/i18n:go_default_library", "//pkg/printers:go_default_library", "//pkg/util/initsystem:go_default_library", "//pkg/util/node:go_default_library", - "//pkg/util/version:go_default_library", "//pkg/version:go_default_library", "//vendor/github.com/ghodss/yaml:go_default_library", "//vendor/github.com/renstrom/dedent:go_default_library", @@ -71,7 +73,9 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/version:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", "//vendor/k8s.io/client-go/util/cert:go_default_library", + "//vendor/k8s.io/utils/exec:go_default_library", ], ) @@ -81,11 +85,18 @@ go_test( "reset_test.go", "token_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/cmd", - library = ":go_default_library", deps = [ "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/preflight:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", + "//vendor/k8s.io/client-go/testing:go_default_library", + "//vendor/k8s.io/utils/exec:go_default_library", + "//vendor/k8s.io/utils/exec/testing:go_default_library", ], ) diff --git a/cmd/kubeadm/app/cmd/cmd.go b/cmd/kubeadm/app/cmd/cmd.go index aa51b2bfce2..a3124b31be2 100644 --- a/cmd/kubeadm/app/cmd/cmd.go +++ b/cmd/kubeadm/app/cmd/cmd.go @@ -25,6 +25,10 @@ import ( "k8s.io/apiserver/pkg/util/flag" "k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases" "k8s.io/kubernetes/cmd/kubeadm/app/cmd/upgrade" + + // Register the kubeadm configuration types because CLI flag generation + // depends on the generated defaults. + _ "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/install" ) // NewKubeadmCommand return cobra.Command to run kubeadm command @@ -36,9 +40,9 @@ func NewKubeadmCommand(_ io.Reader, out, err io.Writer) *cobra.Command { kubeadm: easily bootstrap a secure Kubernetes cluster. ┌──────────────────────────────────────────────────────────┐ - │ KUBEADM IS BETA, DO NOT USE IT FOR PRODUCTION CLUSTERS! │ + │ KUBEADM IS CURRENTLY IN BETA │ │ │ - │ But, please try it out! Give us feedback at: │ + │ But please, try it out and give us feedback at: │ │ https://github.com/kubernetes/kubeadm/issues │ │ and at-mention @kubernetes/sig-cluster-lifecycle-bugs │ │ or @kubernetes/sig-cluster-lifecycle-feature-requests │ @@ -50,13 +54,13 @@ func NewKubeadmCommand(_ io.Reader, out, err io.Writer) *cobra.Command { and one node (where your workloads, like Pods and Deployments run). ┌──────────────────────────────────────────────────────────┐ - │ On the first machine │ + │ On the first machine: │ ├──────────────────────────────────────────────────────────┤ │ master# kubeadm init │ └──────────────────────────────────────────────────────────┘ ┌──────────────────────────────────────────────────────────┐ - │ On the second machine │ + │ On the second machine: │ ├──────────────────────────────────────────────────────────┤ │ node# kubeadm join │ └──────────────────────────────────────────────────────────┘ diff --git a/cmd/kubeadm/app/cmd/completion.go b/cmd/kubeadm/app/cmd/completion.go index 55a96906e37..136de4f1125 100644 --- a/cmd/kubeadm/app/cmd/completion.go +++ b/cmd/kubeadm/app/cmd/completion.go @@ -97,7 +97,7 @@ func NewCmdCompletion(out io.Writer, boilerPlate string) *cobra.Command { cmd := &cobra.Command{ Use: "completion SHELL", - Short: i18n.T("Output shell completion code for the specified shell (bash or zsh)"), + Short: i18n.T("Output shell completion code for the specified shell (bash or zsh)."), Long: completionLong, Example: completionExample, Run: func(cmd *cobra.Command, args []string) { diff --git a/cmd/kubeadm/app/cmd/config.go b/cmd/kubeadm/app/cmd/config.go index dd531a84e01..00684a884a2 100644 --- a/cmd/kubeadm/app/cmd/config.go +++ b/cmd/kubeadm/app/cmd/config.go @@ -33,7 +33,7 @@ import ( kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" ) // NewCmdConfig returns cobra.Command for "kubeadm config" command @@ -45,9 +45,9 @@ func NewCmdConfig(out io.Writer) *cobra.Command { Short: "Manage configuration for a kubeadm cluster persisted in a ConfigMap in the cluster.", Long: fmt.Sprintf(dedent.Dedent(` There is a ConfigMap in the %s namespace called %q that kubeadm uses to store internal configuration about the - cluster. kubeadm CLI v1.8.0+ automatically creates this ConfigMap with used config on 'kubeadm init', but if you + cluster. kubeadm CLI v1.8.0+ automatically creates this ConfigMap with the config used with 'kubeadm init', but if you initialized your cluster using kubeadm v1.7.x or lower, you must use the 'config upload' command to create this - ConfigMap in order for 'kubeadm upgrade' to be able to configure your upgraded cluster correctly. + ConfigMap. This is required so that 'kubeadm upgrade' can configure your upgraded cluster correctly. `), metav1.NamespaceSystem, constants.MasterConfigurationConfigMap), // Without this callback, if a user runs just the "upload" // command without a subcommand, or with an invalid subcommand, @@ -57,7 +57,7 @@ func NewCmdConfig(out io.Writer) *cobra.Command { RunE: cmdutil.SubCmdRunE("config"), } - cmd.PersistentFlags().StringVar(&kubeConfigFile, "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use for talking to the cluster.") + cmd.PersistentFlags().StringVar(&kubeConfigFile, "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use when talking to the cluster.") cmd.AddCommand(NewCmdConfigUpload(out, &kubeConfigFile)) cmd.AddCommand(NewCmdConfigView(out, &kubeConfigFile)) @@ -65,11 +65,11 @@ func NewCmdConfig(out io.Writer) *cobra.Command { return cmd } -// NewCmdConfigUpload returs cobra.Command for "kubeadm config upload" command +// NewCmdConfigUpload returns cobra.Command for "kubeadm config upload" command func NewCmdConfigUpload(out io.Writer, kubeConfigFile *string) *cobra.Command { cmd := &cobra.Command{ Use: "upload", - Short: "Upload configuration about the current state so 'kubeadm upgrade' later can know how to configure the upgraded cluster.", + Short: "Upload configuration about the current state, so that 'kubeadm upgrade' can later know how to configure the upgraded cluster.", RunE: cmdutil.SubCmdRunE("upload"), } @@ -78,7 +78,7 @@ func NewCmdConfigUpload(out io.Writer, kubeConfigFile *string) *cobra.Command { return cmd } -// NewCmdConfigView returs cobra.Command for "kubeadm config view" command +// NewCmdConfigView returns cobra.Command for "kubeadm config view" command func NewCmdConfigView(out io.Writer, kubeConfigFile *string) *cobra.Command { return &cobra.Command{ Use: "view", @@ -98,7 +98,7 @@ func NewCmdConfigView(out io.Writer, kubeConfigFile *string) *cobra.Command { } } -// NewCmdConfigUploadFromFile verifies given kubernetes config file and returs cobra.Command for +// NewCmdConfigUploadFromFile verifies given kubernetes config file and returns cobra.Command for // "kubeadm config upload from-file" command func NewCmdConfigUploadFromFile(out io.Writer, kubeConfigFile *string) *cobra.Command { var cfgPath string @@ -106,8 +106,8 @@ func NewCmdConfigUploadFromFile(out io.Writer, kubeConfigFile *string) *cobra.Co Use: "from-file", Short: "Upload a configuration file to the in-cluster ConfigMap for kubeadm configuration.", Long: fmt.Sprintf(dedent.Dedent(` - Using from-file, you can upload configuration to the ConfigMap in the cluster using the same config file you gave to kubeadm init. - If you initialized your cluster using a v1.7.x or lower kubeadm client and used the --config option; you need to run this command with the + Using this command, you can upload configuration to the ConfigMap in the cluster using the same config file you gave to 'kubeadm init'. + If you initialized your cluster using a v1.7.x or lower kubeadm client and used the --config option, you need to run this command with the same config file before upgrading to v1.8 using 'kubeadm upgrade'. The configuration is located in the %q namespace in the %q ConfigMap. @@ -131,10 +131,10 @@ func NewCmdConfigUploadFromFile(out io.Writer, kubeConfigFile *string) *cobra.Co return cmd } -// NewCmdConfigUploadFromFlags returs cobra.Command for "kubeadm config upload from-flags" command +// NewCmdConfigUploadFromFlags returns cobra.Command for "kubeadm config upload from-flags" command func NewCmdConfigUploadFromFlags(out io.Writer, kubeConfigFile *string) *cobra.Command { cfg := &kubeadmapiext.MasterConfiguration{} - api.Scheme.Default(cfg) + legacyscheme.Scheme.Default(cfg) var featureGatesString string @@ -142,8 +142,8 @@ func NewCmdConfigUploadFromFlags(out io.Writer, kubeConfigFile *string) *cobra.C Use: "from-flags", Short: "Create the in-cluster configuration file for the first time from using flags.", Long: fmt.Sprintf(dedent.Dedent(` - Using from-flags, you can upload configuration to the ConfigMap in the cluster using the same flags you'd give to kubeadm init. - If you initialized your cluster using a v1.7.x or lower kubeadm client and set some flag; you need to run this command with the + Using this command, you can upload configuration to the ConfigMap in the cluster using the same flags you gave to 'kubeadm init'. + If you initialized your cluster using a v1.7.x or lower kubeadm client and set certain flags, you need to run this command with the same flags before upgrading to v1.8 using 'kubeadm upgrade'. The configuration is located in the %q namespace in the %q ConfigMap. diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index 353cbf49fd1..bcf98c0f508 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -31,6 +31,7 @@ import ( flag "github.com/spf13/pflag" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" clientset "k8s.io/client-go/kubernetes" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" @@ -47,6 +48,7 @@ import ( controlplanephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane" etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd" kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig" + kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet" markmasterphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/markmaster" selfhostingphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting" uploadconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig" @@ -57,15 +59,15 @@ import ( dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" "k8s.io/kubernetes/cmd/kubeadm/app/util/pubkeypin" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/util/version" + "k8s.io/kubernetes/pkg/api/legacyscheme" + utilsexec "k8s.io/utils/exec" ) var ( initDoneTempl = template.Must(template.New("init").Parse(dedent.Dedent(` Your Kubernetes master has initialized successfully! - To start using your cluster, you need to run (as a regular user): + To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i {{.KubeConfigPath}} $HOME/.kube/config @@ -86,15 +88,15 @@ var ( Unfortunately, an error has occurred: {{ .Error }} - This error is likely caused by that: + This error is likely caused by: - The kubelet is not running - The kubelet is unhealthy due to a misconfiguration of the node in some way (required cgroups disabled) - - There is no internet connection; so the kubelet can't pull the following control plane images: + - There is no internet connection, so the kubelet cannot pull the following control plane images: - {{ .APIServerImage }} - {{ .ControllerManagerImage }} - {{ .SchedulerImage }} - You can troubleshoot this for example with the following commands if you're on a systemd-powered system: + If you are on a systemd-powered system, you can try to troubleshoot the error with the following commands: - 'systemctl status kubelet' - 'journalctl -xeu kubelet' `))) @@ -103,28 +105,33 @@ var ( // NewCmdInit returns "kubeadm init" command. func NewCmdInit(out io.Writer) *cobra.Command { cfg := &kubeadmapiext.MasterConfiguration{} - api.Scheme.Default(cfg) + legacyscheme.Scheme.Default(cfg) var cfgPath string var skipPreFlight bool var skipTokenPrint bool var dryRun bool var featureGatesString string + var criSocket string + var ignorePreflightErrors []string cmd := &cobra.Command{ Use: "init", - Short: "Run this in order to set up the Kubernetes master", + Short: "Run this command in order to set up the Kubernetes master.", Run: func(cmd *cobra.Command, args []string) { var err error if cfg.FeatureGates, err = features.NewFeatureGate(&features.InitFeatureGates, featureGatesString); err != nil { kubeadmutil.CheckErr(err) } - api.Scheme.Default(cfg) + legacyscheme.Scheme.Default(cfg) internalcfg := &kubeadmapi.MasterConfiguration{} - api.Scheme.Convert(cfg, internalcfg, nil) + legacyscheme.Scheme.Convert(cfg, internalcfg, nil) - i, err := NewInit(cfgPath, internalcfg, skipPreFlight, skipTokenPrint, dryRun) + ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(ignorePreflightErrors, skipPreFlight) + kubeadmutil.CheckErr(err) + + i, err := NewInit(cfgPath, internalcfg, ignorePreflightErrorsSet, skipTokenPrint, dryRun, criSocket) kubeadmutil.CheckErr(err) kubeadmutil.CheckErr(i.Validate(cmd)) kubeadmutil.CheckErr(i.Run(out)) @@ -132,7 +139,7 @@ func NewCmdInit(out io.Writer) *cobra.Command { } AddInitConfigFlags(cmd.PersistentFlags(), cfg, &featureGatesString) - AddInitOtherFlags(cmd.PersistentFlags(), &cfgPath, &skipPreFlight, &skipTokenPrint, &dryRun) + AddInitOtherFlags(cmd.PersistentFlags(), &cfgPath, &skipPreFlight, &skipTokenPrint, &dryRun, &criSocket, &ignorePreflightErrors) return cmd } @@ -141,7 +148,7 @@ func NewCmdInit(out io.Writer) *cobra.Command { func AddInitConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiext.MasterConfiguration, featureGatesString *string) { flagSet.StringVar( &cfg.API.AdvertiseAddress, "apiserver-advertise-address", cfg.API.AdvertiseAddress, - "The IP address the API Server will advertise it's listening on. 0.0.0.0 means the default network interface's address.", + "The IP address the API Server will advertise it's listening on. Specify '0.0.0.0' to use the address of the default network interface.", ) flagSet.Int32Var( &cfg.API.BindPort, "apiserver-bind-port", cfg.API.BindPort, @@ -153,7 +160,7 @@ func AddInitConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiext.MasterConfigur ) flagSet.StringVar( &cfg.Networking.PodSubnet, "pod-network-cidr", cfg.Networking.PodSubnet, - "Specify range of IP addresses for the pod network; if set, the control plane will automatically allocate CIDRs for every node.", + "Specify range of IP addresses for the pod network. If set, the control plane will automatically allocate CIDRs for every node.", ) flagSet.StringVar( &cfg.Networking.DNSDomain, "service-dns-domain", cfg.Networking.DNSDomain, @@ -169,7 +176,7 @@ func AddInitConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiext.MasterConfigur ) flagSet.StringSliceVar( &cfg.APIServerCertSANs, "apiserver-cert-extra-sans", cfg.APIServerCertSANs, - `Optional extra altnames to use for the API Server serving cert. Can be both IP addresses and dns names.`, + `Optional extra Subject Alternative Names (SANs) to use for the API Server serving certificate. Can be both IP addresses and DNS names.`, ) flagSet.StringVar( &cfg.NodeName, "node-name", cfg.NodeName, @@ -181,23 +188,28 @@ func AddInitConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiext.MasterConfigur ) flagSet.DurationVar( &cfg.TokenTTL.Duration, "token-ttl", cfg.TokenTTL.Duration, - "The duration before the bootstrap token is automatically deleted. 0 means 'never expires'.", + "The duration before the bootstrap token is automatically deleted. If set to '0', the token will never expire.", ) flagSet.StringVar(featureGatesString, "feature-gates", *featureGatesString, "A set of key=value pairs that describe feature gates for various features. "+ "Options are:\n"+strings.Join(features.KnownFeatures(&features.InitFeatureGates), "\n")) } // AddInitOtherFlags adds init flags that are not bound to a configuration file to the given flagset -func AddInitOtherFlags(flagSet *flag.FlagSet, cfgPath *string, skipPreFlight, skipTokenPrint, dryRun *bool) { +func AddInitOtherFlags(flagSet *flag.FlagSet, cfgPath *string, skipPreFlight, skipTokenPrint, dryRun *bool, criSocket *string, ignorePreflightErrors *[]string) { flagSet.StringVar( cfgPath, "config", *cfgPath, "Path to kubeadm config file. WARNING: Usage of a configuration file is experimental.", ) + flagSet.StringSliceVar( + ignorePreflightErrors, "ignore-preflight-errors", *ignorePreflightErrors, + "A list of checks whose errors will be shown as warnings. Example: 'IsPrivilegedUser,Swap'. Value 'all' ignores errors from all checks.", + ) // Note: All flags that are not bound to the cfg object should be whitelisted in cmd/kubeadm/app/apis/kubeadm/validation/validation.go flagSet.BoolVar( skipPreFlight, "skip-preflight-checks", *skipPreFlight, - "Skip preflight checks normally run before modifying the system.", + "Skip preflight checks which normally run before modifying the system.", ) + flagSet.MarkDeprecated("skip-preflight-checks", "it is now equivalent to --ignore-preflight-errors=all") // Note: All flags that are not bound to the cfg object should be whitelisted in cmd/kubeadm/app/apis/kubeadm/validation/validation.go flagSet.BoolVar( skipTokenPrint, "skip-token-print", *skipTokenPrint, @@ -208,19 +220,21 @@ func AddInitOtherFlags(flagSet *flag.FlagSet, cfgPath *string, skipPreFlight, sk dryRun, "dry-run", *dryRun, "Don't apply any changes; just output what would be done.", ) + flagSet.StringVar( + criSocket, "cri-socket", "/var/run/dockershim.sock", + `Specify the CRI socket to connect to.`, + ) } // NewInit validates given arguments and instantiates Init struct with provided information. -func NewInit(cfgPath string, cfg *kubeadmapi.MasterConfiguration, skipPreFlight, skipTokenPrint, dryRun bool) (*Init, error) { - - fmt.Println("[kubeadm] WARNING: kubeadm is in beta, please do not use it for production clusters.") +func NewInit(cfgPath string, cfg *kubeadmapi.MasterConfiguration, ignorePreflightErrors sets.String, skipTokenPrint, dryRun bool, criSocket string) (*Init, error) { if cfgPath != "" { b, err := ioutil.ReadFile(cfgPath) if err != nil { return nil, fmt.Errorf("unable to read config from %q [%v]", cfgPath, err) } - if err := runtime.DecodeInto(api.Codecs.UniversalDecoder(), b, cfg); err != nil { + if err := runtime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), b, cfg); err != nil { return nil, fmt.Errorf("unable to decode config from %q [%v]", cfgPath, err) } } @@ -231,6 +245,10 @@ func NewInit(cfgPath string, cfg *kubeadmapi.MasterConfiguration, skipPreFlight, return nil, err } + if err := features.ValidateVersion(features.InitFeatureGates, cfg.FeatureGates, cfg.KubernetesVersion); err != nil { + return nil, err + } + fmt.Printf("[init] Using Kubernetes version: %s\n", cfg.KubernetesVersion) fmt.Printf("[init] Using Authorization modes: %v\n", cfg.AuthorizationModes) @@ -240,19 +258,15 @@ func NewInit(cfgPath string, cfg *kubeadmapi.MasterConfiguration, skipPreFlight, fmt.Println("\t(/etc/systemd/system/kubelet.service.d/10-kubeadm.conf should be edited for this purpose)") } - if !skipPreFlight { - fmt.Println("[preflight] Running pre-flight checks.") + fmt.Println("[preflight] Running pre-flight checks.") - if err := preflight.RunInitMasterChecks(cfg); err != nil { - return nil, err - } - - // Try to start the kubelet service in case it's inactive - preflight.TryStartKubelet() - } else { - fmt.Println("[preflight] Skipping pre-flight checks.") + if err := preflight.RunInitMasterChecks(utilsexec.New(), cfg, criSocket, ignorePreflightErrors); err != nil { + return nil, err } + // Try to start the kubelet service in case it's inactive + preflight.TryStartKubelet(ignorePreflightErrors) + return &Init{cfg: cfg, skipTokenPrint: skipTokenPrint, dryRun: dryRun}, nil } @@ -273,17 +287,11 @@ func (i *Init) Validate(cmd *cobra.Command) error { // Run executes master node provisioning, including certificates, needed static pod manifests, etc. func (i *Init) Run(out io.Writer) error { - - k8sVersion, err := version.ParseSemantic(i.cfg.KubernetesVersion) - if err != nil { - return fmt.Errorf("could not parse Kubernetes version %q: %v", i.cfg.KubernetesVersion, err) - } - // Get directories to write files to; can be faked if we're dry-running realCertsDir := i.cfg.CertificatesDir certsDirToWriteTo, kubeConfigDir, manifestDir, err := getDirectoriesToUse(i.dryRun, i.cfg.CertificatesDir) if err != nil { - return err + return fmt.Errorf("error getting directories to use: %v", err) } // certsDirToWriteTo is gonna equal cfg.CertificatesDir in the normal case, but gonna be a temp directory if dryrunning i.cfg.CertificatesDir = certsDirToWriteTo @@ -312,12 +320,12 @@ func (i *Init) Run(out io.Writer) error { // PHASE 3: Bootstrap the control plane if err := controlplanephase.CreateInitStaticPodManifestFiles(manifestDir, i.cfg); err != nil { - return err + return fmt.Errorf("error creating init static pod manifest files: %v", err) } // Add etcd static pod spec only if external etcd is not configured if len(i.cfg.Etcd.Endpoints) == 0 { if err := etcdphase.CreateLocalEtcdStaticPodManifestFile(manifestDir, i.cfg); err != nil { - return err + return fmt.Errorf("error creating local etcd static pod manifest file: %v", err) } } @@ -326,13 +334,21 @@ func (i *Init) Run(out io.Writer) error { // If we're dry-running, print the generated manifests if err := printFilesIfDryRunning(i.dryRun, manifestDir); err != nil { - return err + return fmt.Errorf("error printing files on dryrun: %v", err) + } + + // NOTE: flag "--dynamic-config-dir" should be specified in /etc/systemd/system/kubelet.service.d/10-kubeadm.conf + if features.Enabled(i.cfg.FeatureGates, features.DynamicKubeletConfig) { + // Write base kubelet configuration for dynamic kubelet configuration feature. + if err := kubeletphase.WriteInitKubeletConfigToDiskOnMaster(i.cfg); err != nil { + return fmt.Errorf("error writing base kubelet configuration to disk: %v", err) + } } // Create a kubernetes client and wait for the API server to be healthy (if not dryrunning) client, err := createClient(i.cfg, i.dryRun) if err != nil { - return err + return fmt.Errorf("error creating client: %v", err) } // waiter holds the apiclient.Waiter implementation of choice, responsible for querying the API server in various ways and waiting for conditions to be fulfilled @@ -351,16 +367,24 @@ func (i *Init) Run(out io.Writer) error { return fmt.Errorf("couldn't initialize a Kubernetes cluster") } + // NOTE: flag "--dynamic-config-dir" should be specified in /etc/systemd/system/kubelet.service.d/10-kubeadm.conf + if features.Enabled(i.cfg.FeatureGates, features.DynamicKubeletConfig) { + // Create base kubelet configuration for dynamic kubelet configuration feature. + if err := kubeletphase.CreateBaseKubeletConfiguration(i.cfg, client); err != nil { + return fmt.Errorf("error creating base kubelet configuration: %v", err) + } + } + // Upload currently used configuration to the cluster // Note: This is done right in the beginning of cluster initialization; as we might want to make other phases // depend on centralized information from this source in the future if err := uploadconfigphase.UploadConfiguration(i.cfg, client); err != nil { - return err + return fmt.Errorf("error uploading configuration: %v", err) } // PHASE 4: Mark the master with the right label/taint if err := markmasterphase.MarkMaster(client, i.cfg.NodeName); err != nil { - return err + return fmt.Errorf("error marking master: %v", err) } // PHASE 5: Set up the node bootstrap tokens @@ -371,63 +395,64 @@ func (i *Init) Run(out io.Writer) error { // Create the default node bootstrap token tokenDescription := "The default bootstrap token generated by 'kubeadm init'." if err := nodebootstraptokenphase.UpdateOrCreateToken(client, i.cfg.Token, false, i.cfg.TokenTTL.Duration, kubeadmconstants.DefaultTokenUsages, []string{kubeadmconstants.NodeBootstrapTokenAuthGroup}, tokenDescription); err != nil { - return err + return fmt.Errorf("error updating or creating token: %v", err) } // Create RBAC rules that makes the bootstrap tokens able to post CSRs - if err := nodebootstraptokenphase.AllowBootstrapTokensToPostCSRs(client, k8sVersion); err != nil { - return err + if err := nodebootstraptokenphase.AllowBootstrapTokensToPostCSRs(client); err != nil { + return fmt.Errorf("error allowing bootstrap tokens to post CSRs: %v", err) } // Create RBAC rules that makes the bootstrap tokens able to get their CSRs approved automatically - if err := nodebootstraptokenphase.AutoApproveNodeBootstrapTokens(client, k8sVersion); err != nil { - return err + if err := nodebootstraptokenphase.AutoApproveNodeBootstrapTokens(client); err != nil { + return fmt.Errorf("error auto-approving node bootstrap tokens: %v", err) } // Create/update RBAC rules that makes the nodes to rotate certificates and get their CSRs approved automatically - if err := nodebootstraptokenphase.AutoApproveNodeCertificateRotation(client, k8sVersion); err != nil { + if err := nodebootstraptokenphase.AutoApproveNodeCertificateRotation(client); err != nil { return err } // Create the cluster-info ConfigMap with the associated RBAC rules if err := clusterinfophase.CreateBootstrapConfigMapIfNotExists(client, adminKubeConfigPath); err != nil { - return err + return fmt.Errorf("error creating bootstrap configmap: %v", err) } if err := clusterinfophase.CreateClusterInfoRBACRules(client); err != nil { - return err + return fmt.Errorf("error creating clusterinfo RBAC rules: %v", err) } - // PHASE 6: Install and deploy all addons, and configure things as necessary - if err := dnsaddonphase.EnsureDNSAddon(i.cfg, client); err != nil { - return err + return fmt.Errorf("error ensuring dns addon: %v", err) } if err := proxyaddonphase.EnsureProxyAddon(i.cfg, client); err != nil { - return err + return fmt.Errorf("error ensuring proxy addon: %v", err) } // PHASE 7: Make the control plane self-hosted if feature gate is enabled if features.Enabled(i.cfg.FeatureGates, features.SelfHosting) { // Temporary control plane is up, now we create our self hosted control // plane components and remove the static manifests: - fmt.Println("[self-hosted] Creating self-hosted control plane...") - if err := selfhostingphase.CreateSelfHostedControlPlane(manifestDir, kubeConfigDir, i.cfg, client, waiter); err != nil { - return err + fmt.Println("[self-hosted] Creating self-hosted control plane.") + if err := selfhostingphase.CreateSelfHostedControlPlane(manifestDir, kubeConfigDir, i.cfg, client, waiter, i.dryRun); err != nil { + return fmt.Errorf("error creating self hosted control plane: %v", err) } } // Exit earlier if we're dryrunning if i.dryRun { - fmt.Println("[dryrun] Finished dry-running successfully; above are the resources that would be created.") + fmt.Println("[dryrun] Finished dry-running successfully. Above are the resources that would be created.") return nil } // Load the CA certificate from so we can pin its public key caCert, err := pkiutil.TryLoadCertFromDisk(i.cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName) + if err != nil { + return fmt.Errorf("error loading ca cert from disk: %v", err) + } // Generate the Master host/port pair used by initDoneTempl masterHostPort, err := kubeadmutil.GetMasterHostPort(i.cfg) if err != nil { - return err + return fmt.Errorf("error getting master host port: %v", err) } ctx := map[string]string{ @@ -476,9 +501,9 @@ func printFilesIfDryRunning(dryRun bool, manifestDir string) error { return nil } - fmt.Printf("[dryrun] Wrote certificates, kubeconfig files and control plane manifests to %q\n", manifestDir) - fmt.Println("[dryrun] Won't print certificates or kubeconfig files due to the sensitive nature of them") - fmt.Printf("[dryrun] Please go and examine the %q directory for details about what would be written\n", manifestDir) + fmt.Printf("[dryrun] Wrote certificates, kubeconfig files and control plane manifests to the %q directory.\n", manifestDir) + fmt.Println("[dryrun] The certificates or kubeconfig files would not be printed due to their sensitive nature.") + fmt.Printf("[dryrun] Please examine the %q directory for details about what would be written.\n", manifestDir) // Print the contents of the upgraded manifests and pretend like they were in /etc/kubernetes/manifests files := []dryrunutil.FileToPrint{} @@ -506,7 +531,7 @@ func waitForAPIAndKubelet(waiter apiclient.Waiter) error { errorChan := make(chan error) fmt.Printf("[init] Waiting for the kubelet to boot up the control plane as Static Pods from directory %q.\n", kubeadmconstants.GetStaticPodDirectory()) - fmt.Println("[init] This often takes around a minute; or longer if the control plane images have to be pulled.") + fmt.Println("[init] This might take a minute or longer if the control plane images have to be pulled.") go func(errC chan error, waiter apiclient.Waiter) { // This goroutine can only make kubeadm init fail. If this check succeeds, it won't do anything special diff --git a/cmd/kubeadm/app/cmd/join.go b/cmd/kubeadm/app/cmd/join.go index d5192275655..2bc32fe4d72 100644 --- a/cmd/kubeadm/app/cmd/join.go +++ b/cmd/kubeadm/app/cmd/join.go @@ -21,33 +21,38 @@ import ( "io" "io/ioutil" "path/filepath" + "strings" "github.com/renstrom/dedent" "github.com/spf13/cobra" flag "github.com/spf13/pflag" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" certutil "k8s.io/client-go/util/cert" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/discovery" + "k8s.io/kubernetes/cmd/kubeadm/app/features" + kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet" "k8s.io/kubernetes/cmd/kubeadm/app/preflight" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" nodeutil "k8s.io/kubernetes/pkg/util/node" + utilsexec "k8s.io/utils/exec" ) var ( joinDoneMsgf = dedent.Dedent(` - Node join complete: - * Certificate signing request sent to master and response - received. - * Kubelet informed of new secure connection details. + This node has joined the cluster: + * Certificate signing request was sent to master and a response + was received. + * The Kubelet was informed of the new secure connection details. - Run 'kubectl get nodes' on the master to see this machine join. + Run 'kubectl get nodes' on the master to see this node join the cluster. `) joinLongDescription = dedent.Dedent(` @@ -58,13 +63,14 @@ var ( There are 2 main schemes for discovery. The first is to use a shared token along with the IP address of the API server. The second is to - provide a file (a subset of the standard kubeconfig file). This file + provide a file - a subset of the standard kubeconfig file. This file can be a local file or downloaded via an HTTPS URL. The forms are kubeadm join --discovery-token abcdef.1234567890abcdef 1.2.3.4:6443, kubeadm join --discovery-file path/to/file.conf, or kubeadm join --discovery-file https://url/file.conf. Only one form can be used. If - the discovery information is loaded from a URL, HTTPS must be used and - the host installed CA bundle is used to verify the connection. + the discovery information is loaded from a URL, HTTPS must be used. + Also, in that case the host installed CA bundle is used to verify + the connection. If you use a shared token for discovery, you should also pass the --discovery-token-ca-cert-hash flag to validate the public key of the @@ -84,7 +90,7 @@ var ( The TLS bootstrap mechanism is also driven via a shared token. This is used to temporarily authenticate with the Kubernetes Master to submit a certificate signing request (CSR) for a locally created key pair. By - default kubeadm will set up the Kubernetes Master to automatically + default, kubeadm will set up the Kubernetes Master to automatically approve these signing requests. This token is passed in with the --tls-bootstrap-token abcdef.1234567890abcdef flag. @@ -96,10 +102,13 @@ var ( // NewCmdJoin returns "kubeadm join" command. func NewCmdJoin(out io.Writer) *cobra.Command { cfg := &kubeadmapiext.NodeConfiguration{} - api.Scheme.Default(cfg) + legacyscheme.Scheme.Default(cfg) var skipPreFlight bool var cfgPath string + var criSocket string + var featureGatesString string + var ignorePreflightErrors []string cmd := &cobra.Command{ Use: "join [flags]", @@ -108,25 +117,33 @@ func NewCmdJoin(out io.Writer) *cobra.Command { Run: func(cmd *cobra.Command, args []string) { cfg.DiscoveryTokenAPIServers = args - api.Scheme.Default(cfg) - internalcfg := &kubeadmapi.NodeConfiguration{} - api.Scheme.Convert(cfg, internalcfg, nil) + var err error + if cfg.FeatureGates, err = features.NewFeatureGate(&features.InitFeatureGates, featureGatesString); err != nil { + kubeadmutil.CheckErr(err) + } - j, err := NewJoin(cfgPath, args, internalcfg, skipPreFlight) + legacyscheme.Scheme.Default(cfg) + internalcfg := &kubeadmapi.NodeConfiguration{} + legacyscheme.Scheme.Convert(cfg, internalcfg, nil) + + ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(ignorePreflightErrors, skipPreFlight) + kubeadmutil.CheckErr(err) + + j, err := NewJoin(cfgPath, args, internalcfg, ignorePreflightErrorsSet, criSocket) kubeadmutil.CheckErr(err) kubeadmutil.CheckErr(j.Validate(cmd)) kubeadmutil.CheckErr(j.Run(out)) }, } - AddJoinConfigFlags(cmd.PersistentFlags(), cfg) - AddJoinOtherFlags(cmd.PersistentFlags(), &cfgPath, &skipPreFlight) + AddJoinConfigFlags(cmd.PersistentFlags(), cfg, &featureGatesString) + AddJoinOtherFlags(cmd.PersistentFlags(), &cfgPath, &skipPreFlight, &criSocket, &ignorePreflightErrors) return cmd } // AddJoinConfigFlags adds join flags bound to the config to the specified flagset -func AddJoinConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiext.NodeConfiguration) { +func AddJoinConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiext.NodeConfiguration, featureGatesString *string) { flagSet.StringVar( &cfg.DiscoveryFile, "discovery-file", "", "A file or url from which to load cluster information.") @@ -148,17 +165,30 @@ func AddJoinConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiext.NodeConfigurat flagSet.StringVar( &cfg.Token, "token", "", "Use this token for both discovery-token and tls-bootstrap-token.") + flagSet.StringVar( + featureGatesString, "feature-gates", *featureGatesString, + "A set of key=value pairs that describe feature gates for various features. "+ + "Options are:\n"+strings.Join(features.KnownFeatures(&features.InitFeatureGates), "\n")) } // AddJoinOtherFlags adds join flags that are not bound to a configuration file to the given flagset -func AddJoinOtherFlags(flagSet *flag.FlagSet, cfgPath *string, skipPreFlight *bool) { +func AddJoinOtherFlags(flagSet *flag.FlagSet, cfgPath *string, skipPreFlight *bool, criSocket *string, ignorePreflightErrors *[]string) { flagSet.StringVar( cfgPath, "config", *cfgPath, "Path to kubeadm config file.") + flagSet.StringSliceVar( + ignorePreflightErrors, "ignore-preflight-errors", *ignorePreflightErrors, + "A list of checks whose errors will be shown as warnings. Example: 'IsPrivilegedUser,Swap'. Value 'all' ignores errors from all checks.", + ) flagSet.BoolVar( skipPreFlight, "skip-preflight-checks", false, - "Skip preflight checks normally run before modifying the system.", + "Skip preflight checks which normally run before modifying the system.", + ) + flagSet.MarkDeprecated("skip-preflight-checks", "it is now equivalent to --ignore-preflight-errors=all") + flagSet.StringVar( + criSocket, "cri-socket", "/var/run/dockershim.sock", + `Specify the CRI socket to connect to.`, ) } @@ -168,8 +198,7 @@ type Join struct { } // NewJoin instantiates Join struct with given arguments -func NewJoin(cfgPath string, args []string, cfg *kubeadmapi.NodeConfiguration, skipPreFlight bool) (*Join, error) { - fmt.Println("[kubeadm] WARNING: kubeadm is in beta, please do not use it for production clusters.") +func NewJoin(cfgPath string, args []string, cfg *kubeadmapi.NodeConfiguration, ignorePreflightErrors sets.String, criSocket string) (*Join, error) { if cfg.NodeName == "" { cfg.NodeName = nodeutil.GetHostname("") @@ -180,25 +209,21 @@ func NewJoin(cfgPath string, args []string, cfg *kubeadmapi.NodeConfiguration, s if err != nil { return nil, fmt.Errorf("unable to read config from %q [%v]", cfgPath, err) } - if err := runtime.DecodeInto(api.Codecs.UniversalDecoder(), b, cfg); err != nil { + if err := runtime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), b, cfg); err != nil { return nil, fmt.Errorf("unable to decode config from %q [%v]", cfgPath, err) } } - if !skipPreFlight { - fmt.Println("[preflight] Running pre-flight checks.") + fmt.Println("[preflight] Running pre-flight checks.") - // Then continue with the others... - if err := preflight.RunJoinNodeChecks(cfg); err != nil { - return nil, err - } - - // Try to start the kubelet service in case it's inactive - preflight.TryStartKubelet() - } else { - fmt.Println("[preflight] Skipping pre-flight checks.") + // Then continue with the others... + if err := preflight.RunJoinNodeChecks(utilsexec.New(), cfg, criSocket, ignorePreflightErrors); err != nil { + return nil, err } + // Try to start the kubelet service in case it's inactive + preflight.TryStartKubelet(ignorePreflightErrors) + return &Join{cfg: cfg}, nil } @@ -221,7 +246,7 @@ func (j *Join) Run(out io.Writer) error { // Write the bootstrap kubelet config file or the TLS-Boostrapped kubelet config file down to disk if err := kubeconfigutil.WriteToDisk(kubeconfigFile, cfg); err != nil { - return err + return fmt.Errorf("couldn't save bootstrap-kubelet.conf to disk: %v", err) } // Write the ca certificate to disk so kubelet can use it for authentication @@ -231,6 +256,13 @@ func (j *Join) Run(out io.Writer) error { return fmt.Errorf("couldn't save the CA certificate to disk: %v", err) } + // NOTE: flag "--dynamic-config-dir" should be specified in /etc/systemd/system/kubelet.service.d/10-kubeadm.conf + if features.Enabled(j.cfg.FeatureGates, features.DynamicKubeletConfig) { + if err := kubeletphase.ConsumeBaseKubeletConfiguration(j.cfg.NodeName); err != nil { + return fmt.Errorf("error consuming base kubelet configuration: %v", err) + } + } + fmt.Fprintf(out, joinDoneMsgf) return nil } diff --git a/cmd/kubeadm/app/cmd/phases/BUILD b/cmd/kubeadm/app/cmd/phases/BUILD index bb350fbe395..ec3ae74f96d 100644 --- a/cmd/kubeadm/app/cmd/phases/BUILD +++ b/cmd/kubeadm/app/cmd/phases/BUILD @@ -35,6 +35,7 @@ go_library( "//cmd/kubeadm/app/phases/bootstraptoken/clusterinfo:go_default_library", "//cmd/kubeadm/app/phases/bootstraptoken/node:go_default_library", "//cmd/kubeadm/app/phases/certs:go_default_library", + "//cmd/kubeadm/app/phases/certs/pkiutil:go_default_library", "//cmd/kubeadm/app/phases/controlplane:go_default_library", "//cmd/kubeadm/app/phases/etcd:go_default_library", "//cmd/kubeadm/app/phases/kubeconfig:go_default_library", @@ -46,10 +47,16 @@ go_library( "//cmd/kubeadm/app/util/apiclient:go_default_library", "//cmd/kubeadm/app/util/config:go_default_library", "//cmd/kubeadm/app/util/kubeconfig:go_default_library", - "//pkg/api:go_default_library", - "//pkg/util/version:go_default_library", + "//cmd/kubeadm/app/util/pubkeypin:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/bootstrap/api:go_default_library", + "//pkg/util/normalizer:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", + "//vendor/github.com/spf13/pflag:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/utils/exec:go_default_library", ], ) @@ -62,8 +69,8 @@ go_test( "etcd_test.go", "kubeconfig_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases", - library = ":go_default_library", deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/install:go_default_library", diff --git a/cmd/kubeadm/app/cmd/phases/addons.go b/cmd/kubeadm/app/cmd/phases/addons.go index 4fc3fe950fb..c837113f9b5 100644 --- a/cmd/kubeadm/app/cmd/phases/addons.go +++ b/cmd/kubeadm/app/cmd/phases/addons.go @@ -17,6 +17,8 @@ limitations under the License. package phases import ( + "strings" + "github.com/spf13/cobra" clientset "k8s.io/client-go/kubernetes" @@ -24,21 +26,46 @@ import ( kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" + "k8s.io/kubernetes/cmd/kubeadm/app/features" dnsaddon "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns" proxyaddon "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/proxy" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/util/normalizer" +) + +var ( + allAddonsLongDesc = normalizer.LongDesc(` + Installs the kube-dns and the kube-proxys addons components via the API server. + Please note that although the DNS server is deployed, it will not be scheduled until CNI is installed. + ` + cmdutil.AlphaDisclaimer) + + allAddonsExample = normalizer.Examples(` + # Installs the kube-dns and the kube-proxys addons components via the API server, + # functionally equivalent to what installed by kubeadm init. + + kubeadm alpha phase selfhosting from-staticpods + `) + + kubednsAddonsLongDesc = normalizer.LongDesc(` + Installs the kube-dns addon components via the API server. + Please note that although the DNS server is deployed, it will not be scheduled until CNI is installed. + ` + cmdutil.AlphaDisclaimer) + + kubeproxyAddonsLongDesc = normalizer.LongDesc(` + Installs the kube-proxy addon components via the API server. + ` + cmdutil.AlphaDisclaimer) ) // NewCmdAddon returns the addon Cobra command func NewCmdAddon() *cobra.Command { cmd := &cobra.Command{ - Use: "addon ", + Use: "addon", Aliases: []string{"addons"}, - Short: "Install an addon to a Kubernetes cluster.", - RunE: cmdutil.SubCmdRunE("addon"), + Short: "Installs required addons for passing Conformance tests", + Long: cmdutil.MacroCommandLongDescription, } cmd.AddCommand(getAddonsSubCommands()...) @@ -67,29 +94,35 @@ func EnsureAllAddons(cfg *kubeadmapi.MasterConfiguration, client clientset.Inter func getAddonsSubCommands() []*cobra.Command { cfg := &kubeadmapiext.MasterConfiguration{} // Default values for the cobra help text - api.Scheme.Default(cfg) + legacyscheme.Scheme.Default(cfg) - var cfgPath, kubeConfigFile string + var cfgPath, kubeConfigFile, featureGatesString string var subCmds []*cobra.Command subCmdProperties := []struct { - use string - short string - cmdFunc func(cfg *kubeadmapi.MasterConfiguration, client clientset.Interface) error + use string + short string + long string + examples string + cmdFunc func(cfg *kubeadmapi.MasterConfiguration, client clientset.Interface) error }{ { - use: "all", - short: "Install all addons to a Kubernetes cluster", - cmdFunc: EnsureAllAddons, + use: "all", + short: "Installs all addons to a Kubernetes cluster", + long: allAddonsLongDesc, + examples: allAddonsExample, + cmdFunc: EnsureAllAddons, }, { use: "kube-dns", - short: "Install the kube-dns addon to a Kubernetes cluster.", + short: "Installs the kube-dns addon to a Kubernetes cluster", + long: kubednsAddonsLongDesc, cmdFunc: dnsaddon.EnsureDNSAddon, }, { use: "kube-proxy", - short: "Install the kube-proxy addon to a Kubernetes cluster.", + short: "Installs the kube-proxy addon to a Kubernetes cluster", + long: kubeproxyAddonsLongDesc, cmdFunc: proxyaddon.EnsureProxyAddon, }, } @@ -97,25 +130,30 @@ func getAddonsSubCommands() []*cobra.Command { for _, properties := range subCmdProperties { // Creates the UX Command cmd := &cobra.Command{ - Use: properties.use, - Short: properties.short, - Run: runAddonsCmdFunc(properties.cmdFunc, cfg, &kubeConfigFile, &cfgPath), + Use: properties.use, + Short: properties.short, + Long: properties.long, + Example: properties.examples, + Run: runAddonsCmdFunc(properties.cmdFunc, cfg, &kubeConfigFile, &cfgPath, &featureGatesString), } // Add flags to the command - cmd.Flags().StringVar(&kubeConfigFile, "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use for talking to the cluster") - cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)") - cmd.Flags().StringVar(&cfg.KubernetesVersion, "kubernetes-version", cfg.KubernetesVersion, `Choose a specific Kubernetes version for the control plane.`) - cmd.Flags().StringVar(&cfg.ImageRepository, "image-repository", cfg.ImageRepository, `Choose a container registry to pull control plane images from.`) + cmd.Flags().StringVar(&kubeConfigFile, "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use when talking to the cluster") + cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to a kubeadm config file. WARNING: Usage of a configuration file is experimental!") + cmd.Flags().StringVar(&cfg.KubernetesVersion, "kubernetes-version", cfg.KubernetesVersion, `Choose a specific Kubernetes version for the control plane`) + cmd.Flags().StringVar(&cfg.ImageRepository, "image-repository", cfg.ImageRepository, `Choose a container registry to pull control plane images from`) if properties.use == "all" || properties.use == "kube-proxy" { - cmd.Flags().StringVar(&cfg.API.AdvertiseAddress, "apiserver-advertise-address", cfg.API.AdvertiseAddress, `The IP address the API Server will advertise it's listening on. 0.0.0.0 means the default network interface's address.`) - cmd.Flags().Int32Var(&cfg.API.BindPort, "apiserver-bind-port", cfg.API.BindPort, `Port for the API Server to bind to.`) - cmd.Flags().StringVar(&cfg.Networking.PodSubnet, "pod-network-cidr", cfg.Networking.PodSubnet, `Specify range of IP addresses for the pod network; if set, the control plane will automatically allocate CIDRs for every node.`) + cmd.Flags().StringVar(&cfg.API.AdvertiseAddress, "apiserver-advertise-address", cfg.API.AdvertiseAddress, `The IP address or DNS name the API server is accessible on`) + cmd.Flags().Int32Var(&cfg.API.BindPort, "apiserver-bind-port", cfg.API.BindPort, `The port the API server is accessible on`) + cmd.Flags().StringVar(&cfg.Networking.PodSubnet, "pod-network-cidr", cfg.Networking.PodSubnet, `The range of IP addresses used for the Pod network`) } if properties.use == "all" || properties.use == "kube-dns" { - cmd.Flags().StringVar(&cfg.Networking.DNSDomain, "service-dns-domain", cfg.Networking.DNSDomain, `Use alternative domain for services, e.g. "myorg.internal.`) + cmd.Flags().StringVar(&cfg.Networking.DNSDomain, "service-dns-domain", cfg.Networking.DNSDomain, `Alternative domain for services`) + cmd.Flags().StringVar(&cfg.Networking.ServiceSubnet, "service-cidr", cfg.Networking.ServiceSubnet, `The range of IP address used for service VIPs`) + cmd.Flags().StringVar(&featureGatesString, "feature-gates", featureGatesString, "A set of key=value pairs that describe feature gates for various features."+ + "Options are:\n"+strings.Join(features.KnownFeatures(&features.InitFeatureGates), "\n")) } subCmds = append(subCmds, cmd) } @@ -124,7 +162,7 @@ func getAddonsSubCommands() []*cobra.Command { } // runAddonsCmdFunc creates a cobra.Command Run function, by composing the call to the given cmdFunc with necessary additional steps (e.g preparation of input parameters) -func runAddonsCmdFunc(cmdFunc func(cfg *kubeadmapi.MasterConfiguration, client clientset.Interface) error, cfg *kubeadmapiext.MasterConfiguration, kubeConfigFile *string, cfgPath *string) func(cmd *cobra.Command, args []string) { +func runAddonsCmdFunc(cmdFunc func(cfg *kubeadmapi.MasterConfiguration, client clientset.Interface) error, cfg *kubeadmapiext.MasterConfiguration, kubeConfigFile *string, cfgPath *string, featureGatesString *string) func(cmd *cobra.Command, args []string) { // the following statement build a clousure that wraps a call to a cmdFunc, binding // the function itself with the specific parameters of each sub command. @@ -132,16 +170,24 @@ func runAddonsCmdFunc(cmdFunc func(cfg *kubeadmapi.MasterConfiguration, client c // are shared between sub commands and gets access to current value e.g. flags value. return func(cmd *cobra.Command, args []string) { + var err error if err := validation.ValidateMixedArguments(cmd.Flags()); err != nil { kubeadmutil.CheckErr(err) } + if cfg.FeatureGates, err = features.NewFeatureGate(&features.InitFeatureGates, *featureGatesString); err != nil { + kubeadmutil.CheckErr(err) + } + internalcfg := &kubeadmapi.MasterConfiguration{} - api.Scheme.Convert(cfg, internalcfg, nil) + legacyscheme.Scheme.Convert(cfg, internalcfg, nil) client, err := kubeconfigutil.ClientSetFromFile(*kubeConfigFile) kubeadmutil.CheckErr(err) internalcfg, err = configutil.ConfigFileAndDefaultsToInternalConfig(*cfgPath, cfg) kubeadmutil.CheckErr(err) + if err := features.ValidateVersion(features.InitFeatureGates, internalcfg.FeatureGates, internalcfg.KubernetesVersion); err != nil { + kubeadmutil.CheckErr(err) + } // Execute the cmdFunc err = cmdFunc(internalcfg, client) diff --git a/cmd/kubeadm/app/cmd/phases/addons_test.go b/cmd/kubeadm/app/cmd/phases/addons_test.go index ca2e614d113..424e319081e 100644 --- a/cmd/kubeadm/app/cmd/phases/addons_test.go +++ b/cmd/kubeadm/app/cmd/phases/addons_test.go @@ -47,6 +47,7 @@ func TestAddonsSubCommandsHasFlags(t *testing.T) { "apiserver-bind-port", "pod-network-cidr", "service-dns-domain", + "service-cidr", }, }, { @@ -61,6 +62,7 @@ func TestAddonsSubCommandsHasFlags(t *testing.T) { command: "kube-dns", additionalFlags: []string{ "service-dns-domain", + "service-cidr", }, }, } diff --git a/cmd/kubeadm/app/cmd/phases/bootstraptoken.go b/cmd/kubeadm/app/cmd/phases/bootstraptoken.go index 9da5f54993c..fb216125636 100644 --- a/cmd/kubeadm/app/cmd/phases/bootstraptoken.go +++ b/cmd/kubeadm/app/cmd/phases/bootstraptoken.go @@ -18,16 +18,73 @@ package phases import ( "fmt" + "strings" "github.com/spf13/cobra" + "github.com/spf13/pflag" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" clientset "k8s.io/client-go/kubernetes" + kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" + "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" + kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo" "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node" + "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" + configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" - versionutil "k8s.io/kubernetes/pkg/util/version" + "k8s.io/kubernetes/cmd/kubeadm/app/util/pubkeypin" + "k8s.io/kubernetes/pkg/api/legacyscheme" + bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api" + "k8s.io/kubernetes/pkg/util/normalizer" +) + +var ( + allTokenLongDesc = normalizer.LongDesc(` + Bootstrap tokens are used for establishing bidirectional trust between a node joining + the cluster and a the master node. + + This command makes all the configurations required to make bootstrap tokens works + and then creates an initial token. + ` + cmdutil.AlphaDisclaimer) + + allTokenExamples = normalizer.Examples(` + # Makes all the bootstrap token configurations and creates an initial token, functionally + # equivalent to what generated by kubeadm init. + kubeadm alpha phase bootstrap-token all + `) + + createTokenLongDesc = normalizer.LongDesc(` + Creates a bootstrap token. If no token value is given, kubeadm will generate a random token instead. + + Alternatively, you can use kubeadm token. + ` + cmdutil.AlphaDisclaimer) + + clusterInfoLongDesc = fmt.Sprintf(normalizer.LongDesc(` + Uploads the %q ConfigMap in the %q namespace, populating it with cluster information extracted from the + given kubeconfig file. The ConfigMap is used for the node bootstrap process in its initial phases, + before the client trusts the API server. + + See online documentation about Authenticating with Bootstrap Tokens for more details. + `+cmdutil.AlphaDisclaimer), bootstrapapi.ConfigMapClusterInfo, metav1.NamespacePublic) + + nodePostCSRsLongDesc = normalizer.LongDesc(` + Configures RBAC rules to allow node bootstrap tokens to post a certificate signing request, + thus enabling nodes joining the cluster to request long term certificate credentials. + + See online documentation about TLS bootstrapping for more details. + ` + cmdutil.AlphaDisclaimer) + + nodeAutoApproveLongDesc = normalizer.LongDesc(` + Configures RBAC rules to allow the csrapprover controller to automatically approve + certificate signing requests generated by nodes joining the cluster. + It configures also RBAC rules for certificates rotation (with auto approval of new certificates). + + See online documentation about TLS bootstrapping for more details. + ` + cmdutil.AlphaDisclaimer) ) // NewCmdBootstrapToken returns the Cobra command for running the mark-master phase @@ -35,37 +92,131 @@ func NewCmdBootstrapToken() *cobra.Command { var kubeConfigFile string cmd := &cobra.Command{ Use: "bootstrap-token", - Short: "Manage kubeadm-specific Bootstrap Token functions.", + Short: "Manage kubeadm-specific bootstrap token functions", + Long: cmdutil.MacroCommandLongDescription, Aliases: []string{"bootstraptoken"}, - RunE: cmdutil.SubCmdRunE("bootstrap-token"), } - cmd.PersistentFlags().StringVar(&kubeConfigFile, "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use for talking to the cluster") + cmd.PersistentFlags().StringVar(&kubeConfigFile, "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use when talking to the cluster") // Add subcommands + cmd.AddCommand(NewSubCmdBootstrapTokenAll(&kubeConfigFile)) + cmd.AddCommand(NewSubCmdBootstrapToken(&kubeConfigFile)) cmd.AddCommand(NewSubCmdClusterInfo(&kubeConfigFile)) cmd.AddCommand(NewSubCmdNodeBootstrapToken(&kubeConfigFile)) return cmd } +// NewSubCmdBootstrapTokenAll returns the Cobra command for running the token all sub-phase +func NewSubCmdBootstrapTokenAll(kubeConfigFile *string) *cobra.Command { + cfg := &kubeadmapiext.MasterConfiguration{ + // KubernetesVersion is not used by bootstrap-token, but we set this explicitly to avoid + // the lookup of the version from the internet when executing ConfigFileAndDefaultsToInternalConfig + KubernetesVersion: "v1.9.0", + } + + // Default values for the cobra help text + legacyscheme.Scheme.Default(cfg) + + var cfgPath, description string + var usages, extraGroups []string + var skipTokenPrint bool + + cmd := &cobra.Command{ + Use: "all", + Short: "Makes all the bootstrap token configurations and creates an initial token", + Long: allTokenLongDesc, + Example: allTokenExamples, + Run: func(cmd *cobra.Command, args []string) { + err := validation.ValidateMixedArguments(cmd.Flags()) + kubeadmutil.CheckErr(err) + + client, err := kubeconfigutil.ClientSetFromFile(*kubeConfigFile) + kubeadmutil.CheckErr(err) + + // Creates the bootstap token + err = createBootstrapToken(client, cfgPath, cfg, description, usages, extraGroups, skipTokenPrint) + kubeadmutil.CheckErr(err) + + // Create the cluster-info ConfigMap or update if it already exists + err = clusterinfo.CreateBootstrapConfigMapIfNotExists(client, *kubeConfigFile) + kubeadmutil.CheckErr(err) + + // Create the RBAC rules that expose the cluster-info ConfigMap properly + err = clusterinfo.CreateClusterInfoRBACRules(client) + kubeadmutil.CheckErr(err) + + // Create RBAC rules that makes the bootstrap tokens able to post CSRs + err = node.AllowBootstrapTokensToPostCSRs(client) + kubeadmutil.CheckErr(err) + + // Create RBAC rules that makes the bootstrap tokens able to get their CSRs approved automatically + err = node.AutoApproveNodeBootstrapTokens(client) + kubeadmutil.CheckErr(err) + + // Create/update RBAC rules that makes the nodes to rotate certificates and get their CSRs approved automatically + err = node.AutoApproveNodeCertificateRotation(client) + kubeadmutil.CheckErr(err) + }, + } + + // Adds flags to the command + addBootstrapTokenFlags(cmd.Flags(), cfg, &cfgPath, &description, &usages, &extraGroups, &skipTokenPrint) + + return cmd +} + +// NewSubCmdBootstrapToken returns the Cobra command for running the create token phase +func NewSubCmdBootstrapToken(kubeConfigFile *string) *cobra.Command { + cfg := &kubeadmapiext.MasterConfiguration{ + // KubernetesVersion is not used by bootstrap-token, but we set this explicitly to avoid + // the lookup of the version from the internet when executing ConfigFileAndDefaultsToInternalConfig + KubernetesVersion: "v1.9.0", + } + + // Default values for the cobra help text + legacyscheme.Scheme.Default(cfg) + + var cfgPath, description string + var usages, extraGroups []string + var skipTokenPrint bool + + cmd := &cobra.Command{ + Use: "create", + Short: "Creates a bootstrap token to be used for node joining", + Long: createTokenLongDesc, + Run: func(cmd *cobra.Command, args []string) { + err := validation.ValidateMixedArguments(cmd.Flags()) + kubeadmutil.CheckErr(err) + + client, err := kubeconfigutil.ClientSetFromFile(*kubeConfigFile) + kubeadmutil.CheckErr(err) + + err = createBootstrapToken(client, cfgPath, cfg, description, usages, extraGroups, skipTokenPrint) + kubeadmutil.CheckErr(err) + }, + } + + // Adds flags to the command + addBootstrapTokenFlags(cmd.Flags(), cfg, &cfgPath, &description, &usages, &extraGroups, &skipTokenPrint) + + return cmd +} + // NewSubCmdClusterInfo returns the Cobra command for running the cluster-info sub-phase func NewSubCmdClusterInfo(kubeConfigFile *string) *cobra.Command { cmd := &cobra.Command{ - Use: "cluster-info ", - Short: "Uploads and exposes the cluster-info ConfigMap publicly from the given cluster-info file", + Use: "cluster-info", + Short: "Uploads the cluster-info ConfigMap from the given kubeconfig file", + Long: clusterInfoLongDesc, Aliases: []string{"clusterinfo"}, Run: func(cmd *cobra.Command, args []string) { - err := cmdutil.ValidateExactArgNumber(args, []string{"clusterinfo-file"}) - kubeadmutil.CheckErr(err) - client, err := kubeconfigutil.ClientSetFromFile(*kubeConfigFile) kubeadmutil.CheckErr(err) - // Here it's safe to get args[0], since we've validated that the argument exists above in validateExactArgNumber - clusterInfoFile := args[0] // Create the cluster-info ConfigMap or update if it already exists - err = clusterinfo.CreateBootstrapConfigMapIfNotExists(client, clusterInfoFile) + err = clusterinfo.CreateBootstrapConfigMapIfNotExists(client, *kubeConfigFile) kubeadmutil.CheckErr(err) // Create the RBAC rules that expose the cluster-info ConfigMap properly @@ -80,9 +231,9 @@ func NewSubCmdClusterInfo(kubeConfigFile *string) *cobra.Command { func NewSubCmdNodeBootstrapToken(kubeConfigFile *string) *cobra.Command { cmd := &cobra.Command{ Use: "node", - Short: "Manages Node Bootstrap Tokens", + Short: "Configures the node bootstrap process", Aliases: []string{"clusterinfo"}, - RunE: cmdutil.SubCmdRunE("node"), + Long: cmdutil.MacroCommandLongDescription, } cmd.AddCommand(NewSubCmdNodeBootstrapTokenPostCSRs(kubeConfigFile)) @@ -95,15 +246,14 @@ func NewSubCmdNodeBootstrapToken(kubeConfigFile *string) *cobra.Command { func NewSubCmdNodeBootstrapTokenPostCSRs(kubeConfigFile *string) *cobra.Command { cmd := &cobra.Command{ Use: "allow-post-csrs", - Short: "Configure RBAC to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials", + Short: "Configures RBAC to allow node bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials", + Long: nodePostCSRsLongDesc, Run: func(cmd *cobra.Command, args []string) { client, err := kubeconfigutil.ClientSetFromFile(*kubeConfigFile) kubeadmutil.CheckErr(err) - clusterVersion, err := getClusterVersion(client) - kubeadmutil.CheckErr(err) - - err = node.AllowBootstrapTokensToPostCSRs(client, clusterVersion) + // Create RBAC rules that makes the bootstrap tokens able to post CSRs + err = node.AllowBootstrapTokensToPostCSRs(client) kubeadmutil.CheckErr(err) }, } @@ -114,30 +264,95 @@ func NewSubCmdNodeBootstrapTokenPostCSRs(kubeConfigFile *string) *cobra.Command func NewSubCmdNodeBootstrapTokenAutoApprove(kubeConfigFile *string) *cobra.Command { cmd := &cobra.Command{ Use: "allow-auto-approve", - Short: "Configure RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token", + Short: "Configures RBAC rules to allow the csrapprover controller automatically approve CSRs from a node bootstrap token", + Long: nodeAutoApproveLongDesc, Run: func(cmd *cobra.Command, args []string) { client, err := kubeconfigutil.ClientSetFromFile(*kubeConfigFile) kubeadmutil.CheckErr(err) - clusterVersion, err := getClusterVersion(client) + // Create RBAC rules that makes the bootstrap tokens able to get their CSRs approved automatically + err = node.AutoApproveNodeBootstrapTokens(client) kubeadmutil.CheckErr(err) - err = node.AutoApproveNodeBootstrapTokens(client, clusterVersion) + // Create/update RBAC rules that makes the nodes to rotate certificates and get their CSRs approved automatically + err = node.AutoApproveNodeCertificateRotation(client) kubeadmutil.CheckErr(err) }, } return cmd } -// getClusterVersion fetches the API server version and parses it -func getClusterVersion(client clientset.Interface) (*versionutil.Version, error) { - clusterVersionInfo, err := client.Discovery().ServerVersion() - if err != nil { - return nil, fmt.Errorf("failed to check server version: %v", err) - } - clusterVersion, err := versionutil.ParseSemantic(clusterVersionInfo.String()) - if err != nil { - return nil, fmt.Errorf("failed to parse server version: %v", err) - } - return clusterVersion, nil +func addBootstrapTokenFlags(flagSet *pflag.FlagSet, cfg *kubeadmapiext.MasterConfiguration, cfgPath, description *string, usages, extraGroups *[]string, skipTokenPrint *bool) { + flagSet.StringVar( + cfgPath, "config", *cfgPath, + "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)", + ) + flagSet.StringVar( + &cfg.CertificatesDir, "cert-dir", cfg.CertificatesDir, + "The path where certificates are stored", + ) + flagSet.StringVar( + &cfg.Token, "token", cfg.Token, + "The token to use for establishing bidirectional trust between nodes and masters", + ) + flagSet.DurationVar( + &cfg.TokenTTL.Duration, "ttl", kubeadmconstants.DefaultTokenDuration, + "The duration before the token is automatically deleted (e.g. 1s, 2m, 3h). If set to '0', the token will never expire", + ) + flagSet.StringSliceVar( + usages, "usages", kubeadmconstants.DefaultTokenUsages, + fmt.Sprintf("Describes the ways in which this token can be used. You can pass --usages multiple times or provide a comma separated list of options. Valid options: [%s]", strings.Join(kubeadmconstants.DefaultTokenUsages, ",")), + ) + flagSet.StringSliceVar( + extraGroups, "groups", []string{kubeadmconstants.NodeBootstrapTokenAuthGroup}, + fmt.Sprintf("Extra groups that this token will authenticate as when used for authentication. Must match %q", bootstrapapi.BootstrapGroupPattern), + ) + flagSet.StringVar( + description, "description", "The default bootstrap token generated by 'kubeadm init'.", + "A human friendly description of how this token is used.", + ) + flagSet.BoolVar( + skipTokenPrint, "skip-token-print", *skipTokenPrint, + "Skip printing of the bootstrap token", + ) +} + +func createBootstrapToken(client clientset.Interface, cfgPath string, cfg *kubeadmapiext.MasterConfiguration, description string, usages, extraGroups []string, skipTokenPrint bool) error { + // adding groups only makes sense for authentication + usagesSet := sets.NewString(usages...) + usageAuthentication := strings.TrimPrefix(bootstrapapi.BootstrapTokenUsageAuthentication, bootstrapapi.BootstrapTokenUsagePrefix) + if len(extraGroups) > 0 && !usagesSet.Has(usageAuthentication) { + return fmt.Errorf("--groups cannot be specified unless --usages includes %q", usageAuthentication) + } + + // validate any extra group names + for _, group := range extraGroups { + if err := bootstrapapi.ValidateBootstrapGroupName(group); err != nil { + return err + } + } + + // This call returns the ready-to-use configuration based on the configuration file that might or might not exist and the default cfg populated by flags + internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, cfg) + kubeadmutil.CheckErr(err) + + // Load the CA certificate from so we can pin its public key + caCert, err := pkiutil.TryLoadCertFromDisk(internalcfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName) + if err != nil { + return fmt.Errorf("error loading ca cert from disk: %v", err) + } + + // Creates or updates the token + if err := node.UpdateOrCreateToken(client, internalcfg.Token, false, internalcfg.TokenTTL.Duration, usages, extraGroups, description); err != nil { + return err + } + + fmt.Println("[bootstraptoken] Bootstrap token Created") + if skipTokenPrint { + internalcfg.Token = "{token}" + } + fmt.Println("[bootstraptoken] You can now join any number of machines by running:") + fmt.Printf("[bootstraptoken] kubeadm join {master} --token %s --discovery-token-ca-cert-hash %s \n", internalcfg.Token, pubkeypin.Hash(caCert)) + + return nil } diff --git a/cmd/kubeadm/app/cmd/phases/certs.go b/cmd/kubeadm/app/cmd/phases/certs.go index 064e60e11bc..3c87983ad2b 100644 --- a/cmd/kubeadm/app/cmd/phases/certs.go +++ b/cmd/kubeadm/app/cmd/phases/certs.go @@ -17,25 +17,90 @@ limitations under the License. package phases import ( + "fmt" + "github.com/spf13/cobra" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" + kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/util/normalizer" ) -// NewCmdCerts return main command for certs phase +var ( + allCertsLongDesc = normalizer.LongDesc(` + Generates a self-signed CA to provision identities for each component in the cluster (including nodes) + and client certificates to be used by various components. + + If a given certificate and private key pair both exist, kubeadm skips the generation step and + existing files will be used. + ` + cmdutil.AlphaDisclaimer) + + allCertsExample = normalizer.Examples(` + # Creates all PKI assets necessary to establish the control plane, + # functionally equivalent to what generated by kubeadm init. + kubeadm alpha phase certs all + + # Creates all PKI assets using options read from a configuration file. + kubeadm alpha phase certs all --config masterconfiguration.yaml + `) + + caCertLongDesc = fmt.Sprintf(normalizer.LongDesc(` + Generates the self-signed certificate authority and related key, and saves them into %s and %s files. + + If both files already exist, kubeadm skips the generation step and existing files will be used. + `+cmdutil.AlphaDisclaimer), kubeadmconstants.CACertName, kubeadmconstants.CAKeyName) + + apiServerCertLongDesc = fmt.Sprintf(normalizer.LongDesc(` + Generates the API server serving certificate and key and saves them into %s and %s files. + + The certificate includes default subject alternative names and additional sans eventually provided by the user; + default sans are: , , kubernetes, kubernetes.default, kubernetes.default.svc, + kubernetes.default.svc., (that is the .10 address in address space). + + If both files already exist, kubeadm skips the generation step and existing files will be used. + `+cmdutil.AlphaDisclaimer), kubeadmconstants.APIServerCertName, kubeadmconstants.APIServerKeyName) + + apiServerKubeletCertLongDesc = fmt.Sprintf(normalizer.LongDesc(` + Generates the client certificate for the API server to connect to the kubelet securely and the respective key, + and saves them into %s and %s files. + + If both files already exist, kubeadm skips the generation step and existing files will be used. + `+cmdutil.AlphaDisclaimer), kubeadmconstants.APIServerKubeletClientCertName, kubeadmconstants.APIServerKubeletClientKeyName) + + saKeyLongDesc = fmt.Sprintf(normalizer.LongDesc(` + Generates the private key for signing service account tokens along with its public key, and saves them into + %s and %s files. + + If both files already exist, kubeadm skips the generation step and existing files will be used. + `+cmdutil.AlphaDisclaimer), kubeadmconstants.ServiceAccountPrivateKeyName, kubeadmconstants.ServiceAccountPublicKeyName) + + frontProxyCaCertLongDesc = fmt.Sprintf(normalizer.LongDesc(` + Generates the front proxy CA certificate and key and saves them into %s and %s files. + + If both files already exist, kubeadm skips the generation step and existing files will be used. + `+cmdutil.AlphaDisclaimer), kubeadmconstants.FrontProxyCACertName, kubeadmconstants.FrontProxyCAKeyName) + + frontProxyClientCertLongDesc = fmt.Sprintf(normalizer.LongDesc(` + Generates the front proxy client certificate and key and saves them into %s and %s files. + + If both files already exist, kubeadm skips the generation step and existing files will be used. + `+cmdutil.AlphaDisclaimer), kubeadmconstants.FrontProxyClientCertName, kubeadmconstants.FrontProxyClientKeyName) +) + +// NewCmdCerts returns main command for certs phase func NewCmdCerts() *cobra.Command { cmd := &cobra.Command{ Use: "certs", Aliases: []string{"certificates"}, - Short: "Generate certificates for a Kubernetes cluster.", - RunE: cmdutil.SubCmdRunE("certs"), + Short: "Generates certificates for a Kubernetes cluster", + Long: cmdutil.MacroCommandLongDescription, } cmd.AddCommand(getCertsSubCommands("")...) @@ -55,49 +120,59 @@ func getCertsSubCommands(defaultKubernetesVersion string) []*cobra.Command { } // Default values for the cobra help text - api.Scheme.Default(cfg) + legacyscheme.Scheme.Default(cfg) var cfgPath string var subCmds []*cobra.Command subCmdProperties := []struct { - use string - short string - cmdFunc func(cfg *kubeadmapi.MasterConfiguration) error + use string + short string + long string + examples string + cmdFunc func(cfg *kubeadmapi.MasterConfiguration) error }{ { - use: "all", - short: "Generate all PKI assets necessary to establish the control plane", - cmdFunc: certsphase.CreatePKIAssets, + use: "all", + short: "Generates all PKI assets necessary to establish the control plane", + long: allCertsLongDesc, + examples: allCertsExample, + cmdFunc: certsphase.CreatePKIAssets, }, { use: "ca", - short: "Generate CA certificate and key for a Kubernetes cluster.", + short: "Generates self-signed CA to provision identities for each component in the cluster", + long: caCertLongDesc, cmdFunc: certsphase.CreateCACertAndKeyfiles, }, { use: "apiserver", - short: "Generate API Server serving certificate and key.", + short: "Generates API server serving certificate and key", + long: apiServerCertLongDesc, cmdFunc: certsphase.CreateAPIServerCertAndKeyFiles, }, { use: "apiserver-kubelet-client", - short: "Generate a client certificate for the API Server to connect to the kubelets securely.", + short: "Generates client certificate for the API server to connect to the kubelets securely", + long: apiServerKubeletCertLongDesc, cmdFunc: certsphase.CreateAPIServerKubeletClientCertAndKeyFiles, }, { use: "sa", - short: "Generate a private key for signing service account tokens along with its public key.", + short: "Generates a private key for signing service account tokens along with its public key", + long: saKeyLongDesc, cmdFunc: certsphase.CreateServiceAccountKeyAndPublicKeyFiles, }, { use: "front-proxy-ca", - short: "Generate front proxy CA certificate and key for a Kubernetes cluster.", + short: "Generates front proxy CA certificate and key for a Kubernetes cluster", + long: frontProxyCaCertLongDesc, cmdFunc: certsphase.CreateFrontProxyCACertAndKeyFiles, }, { use: "front-proxy-client", - short: "Generate front proxy CA client certificate and key for a Kubernetes cluster.", + short: "Generates front proxy CA client certificate and key for a Kubernetes cluster", + long: frontProxyClientCertLongDesc, cmdFunc: certsphase.CreateFrontProxyClientCertAndKeyFiles, }, } @@ -105,19 +180,21 @@ func getCertsSubCommands(defaultKubernetesVersion string) []*cobra.Command { for _, properties := range subCmdProperties { // Creates the UX Command cmd := &cobra.Command{ - Use: properties.use, - Short: properties.short, - Run: runCmdFunc(properties.cmdFunc, &cfgPath, cfg), + Use: properties.use, + Short: properties.short, + Long: properties.long, + Example: properties.examples, + Run: runCmdFunc(properties.cmdFunc, &cfgPath, cfg), } // Add flags to the command cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)") - cmd.Flags().StringVar(&cfg.CertificatesDir, "cert-dir", cfg.CertificatesDir, "The path where to save and store the certificates") + cmd.Flags().StringVar(&cfg.CertificatesDir, "cert-dir", cfg.CertificatesDir, "The path where to save the certificates") if properties.use == "all" || properties.use == "apiserver" { - cmd.Flags().StringVar(&cfg.Networking.DNSDomain, "service-dns-domain", cfg.Networking.DNSDomain, "Use alternative domain for services, e.g. \"myorg.internal\"") - cmd.Flags().StringVar(&cfg.Networking.ServiceSubnet, "service-cidr", cfg.Networking.ServiceSubnet, "Use alternative range of IP address for service VIPs") - cmd.Flags().StringSliceVar(&cfg.APIServerCertSANs, "apiserver-cert-extra-sans", []string{}, "Optional extra altnames to use for the API Server serving cert. Can be both IP addresses and dns names.") - cmd.Flags().StringVar(&cfg.API.AdvertiseAddress, "apiserver-advertise-address", cfg.API.AdvertiseAddress, "The IP address the API Server will advertise it's listening on. 0.0.0.0 means the default network interface's address.") + cmd.Flags().StringVar(&cfg.Networking.DNSDomain, "service-dns-domain", cfg.Networking.DNSDomain, "Alternative domain for services, to use for the API server serving cert") + cmd.Flags().StringVar(&cfg.Networking.ServiceSubnet, "service-cidr", cfg.Networking.ServiceSubnet, "Alternative range of IP address for service VIPs, from which derives the internal API server VIP that will be added to the API Server serving cert") + cmd.Flags().StringSliceVar(&cfg.APIServerCertSANs, "apiserver-cert-extra-sans", []string{}, "Optional extra altnames to use for the API server serving cert. Can be both IP addresses and dns names") + cmd.Flags().StringVar(&cfg.API.AdvertiseAddress, "apiserver-advertise-address", cfg.API.AdvertiseAddress, "The IP address the API server is accessible on, to use for the API server serving cert") } subCmds = append(subCmds, cmd) @@ -129,7 +206,7 @@ func getCertsSubCommands(defaultKubernetesVersion string) []*cobra.Command { // runCmdFunc creates a cobra.Command Run function, by composing the call to the given cmdFunc with necessary additional steps (e.g preparation of input parameters) func runCmdFunc(cmdFunc func(cfg *kubeadmapi.MasterConfiguration) error, cfgPath *string, cfg *kubeadmapiext.MasterConfiguration) func(cmd *cobra.Command, args []string) { - // the following statement build a clousure that wraps a call to a cmdFunc, binding + // the following statement build a closure that wraps a call to a cmdFunc, binding // the function itself with the specific parameters of each sub command. // Please note that specific parameter should be passed as value, while other parameters - passed as reference - // are shared between sub commands and gets access to current value e.g. flags value. diff --git a/cmd/kubeadm/app/cmd/phases/certs_test.go b/cmd/kubeadm/app/cmd/phases/certs_test.go index 211eea5db8f..415e5d13890 100644 --- a/cmd/kubeadm/app/cmd/phases/certs_test.go +++ b/cmd/kubeadm/app/cmd/phases/certs_test.go @@ -34,7 +34,7 @@ import ( ) // phaseTestK8sVersion is a fake kubernetes version to use when testing -const phaseTestK8sVersion = "v1.8.0" +const phaseTestK8sVersion = "v1.9.0" func TestCertsSubCommandsHasFlags(t *testing.T) { diff --git a/cmd/kubeadm/app/cmd/phases/controlplane.go b/cmd/kubeadm/app/cmd/phases/controlplane.go index 97bc51c4dc7..726294990a5 100644 --- a/cmd/kubeadm/app/cmd/phases/controlplane.go +++ b/cmd/kubeadm/app/cmd/phases/controlplane.go @@ -17,22 +17,57 @@ limitations under the License. package phases import ( + "fmt" + "strings" + "github.com/spf13/cobra" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" + "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" + "k8s.io/kubernetes/cmd/kubeadm/app/features" controlplanephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane" - "k8s.io/kubernetes/pkg/api" + kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" + configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/util/normalizer" +) + +var ( + allControlplaneLongDesc = normalizer.LongDesc(` + Generates all static Pod manifest files necessary to establish the control plane. + ` + cmdutil.AlphaDisclaimer) + + allControlplaneExample = normalizer.Examples(` + # Generates all static Pod manifest files for control plane components, + # functionally equivalent to what generated by kubeadm init. + kubeadm alpha phase controlplane all + + # Generates all static Pod manifest files using options read from a configuration file. + kubeadm alpha phase controlplane --config masterconfiguration.yaml + `) + + apiServerControlplaneLongDesc = fmt.Sprintf(normalizer.LongDesc(` + Generates the static Pod manifest file for the API server and saves it into %s file. + `+cmdutil.AlphaDisclaimer), kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.KubeAPIServer, kubeadmconstants.GetStaticPodDirectory())) + + controllerManagerControlplaneLongDesc = fmt.Sprintf(normalizer.LongDesc(` + Generates the static Pod manifest file for the controller-manager and saves it into %s file. + `+cmdutil.AlphaDisclaimer), kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.KubeControllerManager, kubeadmconstants.GetStaticPodDirectory())) + + schedulerControlplaneLongDesc = fmt.Sprintf(normalizer.LongDesc(` + Generates the static Pod manifest file for the scheduler and saves it into %s file. + `+cmdutil.AlphaDisclaimer), kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.KubeScheduler, kubeadmconstants.GetStaticPodDirectory())) ) // NewCmdControlplane return main command for Controlplane phase func NewCmdControlplane() *cobra.Command { cmd := &cobra.Command{ Use: "controlplane", - Short: "Generate all static pod manifest files necessary to establish the control plane.", - RunE: cmdutil.SubCmdRunE("controlplane"), + Short: "Generates all static Pod manifest files necessary to establish the control plane", + Long: cmdutil.MacroCommandLongDescription, } manifestPath := kubeadmconstants.GetStaticPodDirectory() @@ -47,40 +82,47 @@ func getControlPlaneSubCommands(outDir, defaultKubernetesVersion string) []*cobr // This is used for unit testing only... // If we wouldn't set this to something, the code would dynamically look up the version from the internet - // By setting this explicitely for tests workarounds that + // By setting this explicitly for tests workarounds that if defaultKubernetesVersion != "" { cfg.KubernetesVersion = defaultKubernetesVersion } // Default values for the cobra help text - api.Scheme.Default(cfg) + legacyscheme.Scheme.Default(cfg) - var cfgPath string + var cfgPath, featureGatesString string var subCmds []*cobra.Command subCmdProperties := []struct { - use string - short string - cmdFunc func(outDir string, cfg *kubeadmapi.MasterConfiguration) error + use string + short string + long string + examples string + cmdFunc func(outDir string, cfg *kubeadmapi.MasterConfiguration) error }{ { - use: "all", - short: "Generate all static pod manifest files necessary to establish the control plane.", - cmdFunc: controlplanephase.CreateInitStaticPodManifestFiles, + use: "all", + short: "Generates all static Pod manifest files necessary to establish the control plane", + long: allControlplaneLongDesc, + examples: allControlplaneExample, + cmdFunc: controlplanephase.CreateInitStaticPodManifestFiles, }, { use: "apiserver", - short: "Generate apiserver static pod manifest.", + short: "Generates the API server static Pod manifest.", + long: apiServerControlplaneLongDesc, cmdFunc: controlplanephase.CreateAPIServerStaticPodManifestFile, }, { use: "controller-manager", - short: "Generate controller-manager static pod manifest.", + short: "Generates the controller-manager static Pod manifest.", + long: controllerManagerControlplaneLongDesc, cmdFunc: controlplanephase.CreateControllerManagerStaticPodManifestFile, }, { use: "scheduler", - short: "Generate scheduler static pod manifest.", + short: "Generates the scheduler static Pod manifest.", + long: schedulerControlplaneLongDesc, cmdFunc: controlplanephase.CreateSchedulerStaticPodManifestFile, }, } @@ -88,23 +130,27 @@ func getControlPlaneSubCommands(outDir, defaultKubernetesVersion string) []*cobr for _, properties := range subCmdProperties { // Creates the UX Command cmd := &cobra.Command{ - Use: properties.use, - Short: properties.short, - Run: runCmdPhase(properties.cmdFunc, &outDir, &cfgPath, cfg), + Use: properties.use, + Short: properties.short, + Long: properties.long, + Example: properties.examples, + Run: runCmdControlPlane(properties.cmdFunc, &outDir, &cfgPath, &featureGatesString, cfg), } // Add flags to the command - cmd.Flags().StringVar(&cfg.CertificatesDir, "cert-dir", cfg.CertificatesDir, `The path where certificates are stored.`) - cmd.Flags().StringVar(&cfg.KubernetesVersion, "kubernetes-version", cfg.KubernetesVersion, `Choose a specific Kubernetes version for the control plane.`) + cmd.Flags().StringVar(&cfg.CertificatesDir, "cert-dir", cfg.CertificatesDir, `The path where certificates are stored`) + cmd.Flags().StringVar(&cfg.KubernetesVersion, "kubernetes-version", cfg.KubernetesVersion, `Choose a specific Kubernetes version for the control plane`) if properties.use == "all" || properties.use == "apiserver" { - cmd.Flags().StringVar(&cfg.API.AdvertiseAddress, "apiserver-advertise-address", cfg.API.AdvertiseAddress, "The IP address or DNS name the API Server is accessible on.") - cmd.Flags().Int32Var(&cfg.API.BindPort, "apiserver-bind-port", cfg.API.BindPort, "The port the API Server is accessible on.") - cmd.Flags().StringVar(&cfg.Networking.ServiceSubnet, "service-cidr", cfg.Networking.ServiceSubnet, "The range of IP address used for service VIPs.") + cmd.Flags().StringVar(&cfg.API.AdvertiseAddress, "apiserver-advertise-address", cfg.API.AdvertiseAddress, "The IP address or DNS name the API server is accessible on") + cmd.Flags().Int32Var(&cfg.API.BindPort, "apiserver-bind-port", cfg.API.BindPort, "The port the API server is accessible on") + cmd.Flags().StringVar(&cfg.Networking.ServiceSubnet, "service-cidr", cfg.Networking.ServiceSubnet, "The range of IP address used for service VIPs") + cmd.Flags().StringVar(&featureGatesString, "feature-gates", featureGatesString, "A set of key=value pairs that describe feature gates for various features. "+ + "Options are:\n"+strings.Join(features.KnownFeatures(&features.InitFeatureGates), "\n")) } if properties.use == "all" || properties.use == "controller-manager" { - cmd.Flags().StringVar(&cfg.Networking.PodSubnet, "pod-network-cidr", cfg.Networking.PodSubnet, "The range of IP addresses used for the pod network.") + cmd.Flags().StringVar(&cfg.Networking.PodSubnet, "pod-network-cidr", cfg.Networking.PodSubnet, "The range of IP addresses used for the Pod network") } cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)") @@ -114,3 +160,34 @@ func getControlPlaneSubCommands(outDir, defaultKubernetesVersion string) []*cobr return subCmds } + +// runCmdControlPlane creates a cobra.Command Run function, by composing the call to the given cmdFunc with necessary additional steps (e.g preparation of input parameters) +func runCmdControlPlane(cmdFunc func(outDir string, cfg *kubeadmapi.MasterConfiguration) error, outDir, cfgPath *string, featureGatesString *string, cfg *kubeadmapiext.MasterConfiguration) func(cmd *cobra.Command, args []string) { + + // the following statement build a closure that wraps a call to a cmdFunc, binding + // the function itself with the specific parameters of each sub command. + // Please note that specific parameter should be passed as value, while other parameters - passed as reference - + // are shared between sub commands and gets access to current value e.g. flags value. + return func(cmd *cobra.Command, args []string) { + var err error + if err = validation.ValidateMixedArguments(cmd.Flags()); err != nil { + kubeadmutil.CheckErr(err) + } + + if cfg.FeatureGates, err = features.NewFeatureGate(&features.InitFeatureGates, *featureGatesString); err != nil { + kubeadmutil.CheckErr(err) + } + + // This call returns the ready-to-use configuration based on the configuration file that might or might not exist and the default cfg populated by flags + internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(*cfgPath, cfg) + kubeadmutil.CheckErr(err) + + if err := features.ValidateVersion(features.InitFeatureGates, internalcfg.FeatureGates, internalcfg.KubernetesVersion); err != nil { + kubeadmutil.CheckErr(err) + } + + // Execute the cmdFunc + err = cmdFunc(*outDir, internalcfg) + kubeadmutil.CheckErr(err) + } +} diff --git a/cmd/kubeadm/app/cmd/phases/controlplane_test.go b/cmd/kubeadm/app/cmd/phases/controlplane_test.go index f64fa053fb5..6333263dfb1 100644 --- a/cmd/kubeadm/app/cmd/phases/controlplane_test.go +++ b/cmd/kubeadm/app/cmd/phases/controlplane_test.go @@ -49,6 +49,7 @@ func TestControlPlaneSubCommandsHasFlags(t *testing.T) { "apiserver-bind-port", "service-cidr", "pod-network-cidr", + "feature-gates", }, }, { @@ -58,6 +59,7 @@ func TestControlPlaneSubCommandsHasFlags(t *testing.T) { "apiserver-advertise-address", "apiserver-bind-port", "service-cidr", + "feature-gates", }, }, { @@ -91,7 +93,7 @@ func TestControlPlaneCreateFilesWithFlags(t *testing.T) { { command: "all", additionalFlags: []string{ - "--kubernetes-version=v1.8.0", + "--kubernetes-version=v1.9.0", "--apiserver-advertise-address=1.2.3.4", "--apiserver-bind-port=6443", "--service-cidr=1.2.3.4/16", @@ -106,7 +108,7 @@ func TestControlPlaneCreateFilesWithFlags(t *testing.T) { { command: "apiserver", additionalFlags: []string{ - "--kubernetes-version=v1.8.0", + "--kubernetes-version=v1.9.0", "--apiserver-advertise-address=1.2.3.4", "--apiserver-bind-port=6443", "--service-cidr=1.2.3.4/16", @@ -116,7 +118,7 @@ func TestControlPlaneCreateFilesWithFlags(t *testing.T) { { command: "controller-manager", additionalFlags: []string{ - "--kubernetes-version=v1.8.0", + "--kubernetes-version=v1.9.0", "--pod-network-cidr=1.2.3.4/16", }, expectedFiles: []string{"kube-controller-manager.yaml"}, @@ -124,7 +126,7 @@ func TestControlPlaneCreateFilesWithFlags(t *testing.T) { { command: "scheduler", additionalFlags: []string{ - "--kubernetes-version=v1.8.0", + "--kubernetes-version=v1.9.0", }, expectedFiles: []string{"kube-scheduler.yaml"}, }, diff --git a/cmd/kubeadm/app/cmd/phases/etcd.go b/cmd/kubeadm/app/cmd/phases/etcd.go index 02dd8470d4b..86c9d8dc234 100644 --- a/cmd/kubeadm/app/cmd/phases/etcd.go +++ b/cmd/kubeadm/app/cmd/phases/etcd.go @@ -17,6 +17,8 @@ limitations under the License. package phases import ( + "fmt" + "github.com/spf13/cobra" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" @@ -24,15 +26,31 @@ import ( cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/util/normalizer" +) + +var ( + etcdLocalLongDesc = fmt.Sprintf(normalizer.LongDesc(` + Generates the static Pod manifest file for a local, single-node etcd instance and saves it to %s file. + `+cmdutil.AlphaDisclaimer), kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.Etcd, kubeadmconstants.GetStaticPodDirectory())) + + etcdLocalExample = normalizer.Examples(` + # Generates the static Pod manifest file for etcd, functionally + # equivalent to what generated by kubeadm init. + kubeadm alpha phase etcd local + + # Generates the static Pod manifest file for etcd. + kubeadm alpha phase etcd local --config masterconfiguration.yaml + `) ) // NewCmdEtcd return main command for Etcd phase func NewCmdEtcd() *cobra.Command { cmd := &cobra.Command{ Use: "etcd", - Short: "Generate static pod manifest file for etcd.", - RunE: cmdutil.SubCmdRunE("etcd"), + Short: "Generates static Pod manifest file for etcd.", + Long: cmdutil.MacroCommandLongDescription, } manifestPath := kubeadmconstants.GetStaticPodDirectory() @@ -53,26 +71,32 @@ func getEtcdSubCommands(outDir, defaultKubernetesVersion string) []*cobra.Comman } // Default values for the cobra help text - api.Scheme.Default(cfg) + legacyscheme.Scheme.Default(cfg) var cfgPath string var subCmds []*cobra.Command properties := struct { - use string - short string - cmdFunc func(outDir string, cfg *kubeadmapi.MasterConfiguration) error + use string + short string + long string + examples string + cmdFunc func(outDir string, cfg *kubeadmapi.MasterConfiguration) error }{ - use: "local", - short: "Generate static pod manifest file for a local, single-node etcd instance.", - cmdFunc: etcdphase.CreateLocalEtcdStaticPodManifestFile, + use: "local", + short: "Generates the static Pod manifest file for a local, single-node etcd instance", + long: etcdLocalLongDesc, + examples: etcdLocalExample, + cmdFunc: etcdphase.CreateLocalEtcdStaticPodManifestFile, } // Creates the UX Command cmd := &cobra.Command{ - Use: properties.use, - Short: properties.short, - Run: runCmdPhase(properties.cmdFunc, &outDir, &cfgPath, cfg), + Use: properties.use, + Short: properties.short, + Long: properties.long, + Example: properties.examples, + Run: runCmdPhase(properties.cmdFunc, &outDir, &cfgPath, cfg), } // Add flags to the command diff --git a/cmd/kubeadm/app/cmd/phases/kubeconfig.go b/cmd/kubeadm/app/cmd/phases/kubeconfig.go index 1ed1a8d7458..2d707a42014 100644 --- a/cmd/kubeadm/app/cmd/phases/kubeconfig.go +++ b/cmd/kubeadm/app/cmd/phases/kubeconfig.go @@ -19,6 +19,7 @@ package phases import ( "fmt" "io" + "path/filepath" "github.com/spf13/cobra" @@ -27,15 +28,59 @@ import ( cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/util/normalizer" +) + +var ( + allKubeconfigLongDesc = normalizer.LongDesc(` + Generates all kubeconfig files necessary to establish the control plane and the admin kubeconfig file. + ` + cmdutil.AlphaDisclaimer) + + allKubeconfigExample = normalizer.Examples(` + # Generates all kubeconfig files, functionally equivalent to what generated + # by kubeadm init. + kubeadm alpha phase kubeconfig all + + # Generates all kubeconfig files using options read from a configuration file. + kubeadm alpha phase kubeconfig all --config masterconfiguration.yaml + `) + + adminKubeconfigLongDesc = fmt.Sprintf(normalizer.LongDesc(` + Generates the kubeconfig file for the admin and for kubeadm itself, and saves it to %s file. + `+cmdutil.AlphaDisclaimer), kubeadmconstants.AdminKubeConfigFileName) + + kubeletKubeconfigLongDesc = fmt.Sprintf(normalizer.LongDesc(` + Generates the kubeconfig file for the kubelet to use and saves it to %s file. + + Please note that this should *only* be used for bootstrapping purposes. After your control plane is up, + you should request all kubelet credentials from the CSR API. + `+cmdutil.AlphaDisclaimer), filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.KubeletKubeConfigFileName)) + + controllerManagerKubeconfigLongDesc = fmt.Sprintf(normalizer.LongDesc(` + Generates the kubeconfig file for the controller manager to use and saves it to %s file. + `+cmdutil.AlphaDisclaimer), filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ControllerManagerKubeConfigFileName)) + + schedulerKubeconfigLongDesc = fmt.Sprintf(normalizer.LongDesc(` + Generates the kubeconfig file for the scheduler to use and saves it to %s file. + `+cmdutil.AlphaDisclaimer), filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.SchedulerKubeConfigFileName)) + + userKubeconfigLongDesc = normalizer.LongDesc(` + Outputs a kubeconfig file for an additional user. + ` + cmdutil.AlphaDisclaimer) + + userKubeconfigExample = normalizer.Examples(` + # Outputs a kubeconfig file for an additional user named foo + kubeadm alpha phase kubeconfig user --client-name=foo + `) ) // NewCmdKubeConfig return main command for kubeconfig phase func NewCmdKubeConfig(out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "kubeconfig", - Short: "Generate all kubeconfig files necessary to establish the control plane and the admin kubeconfig file.", - RunE: cmdutil.SubCmdRunE("kubeconfig"), + Short: "Generates all kubeconfig files necessary to establish the control plane and the admin kubeconfig file", + Long: cmdutil.MacroCommandLongDescription, } cmd.AddCommand(getKubeConfigSubCommands(out, kubeadmconstants.KubernetesDir, "")...) @@ -55,47 +100,57 @@ func getKubeConfigSubCommands(out io.Writer, outDir, defaultKubernetesVersion st } // Default values for the cobra help text - api.Scheme.Default(cfg) + legacyscheme.Scheme.Default(cfg) var cfgPath, token, clientName string var subCmds []*cobra.Command subCmdProperties := []struct { - use string - short string - cmdFunc func(outDir string, cfg *kubeadmapi.MasterConfiguration) error + use string + short string + long string + examples string + cmdFunc func(outDir string, cfg *kubeadmapi.MasterConfiguration) error }{ { - use: "all", - short: "Generate all kubeconfig files necessary to establish the control plane and the admin kubeconfig file.", - cmdFunc: kubeconfigphase.CreateInitKubeConfigFiles, + use: "all", + short: "Generates all kubeconfig files necessary to establish the control plane and the admin kubeconfig file", + long: allKubeconfigLongDesc, + examples: allKubeconfigExample, + cmdFunc: kubeconfigphase.CreateInitKubeConfigFiles, }, { use: "admin", - short: "Generate a kubeconfig file for the admin to use and for kubeadm itself.", + short: "Generates a kubeconfig file for the admin to use and for kubeadm itself", + long: adminKubeconfigLongDesc, cmdFunc: kubeconfigphase.CreateAdminKubeConfigFile, }, { use: "kubelet", - short: "Generate a kubeconfig file for the Kubelet to use. Please note that this should *only* be used for bootstrapping purposes. After your control plane is up, you should request all kubelet credentials from the CSR API.", + short: "Generates a kubeconfig file for the kubelet to use. Please note that this should be used *only* for bootstrapping purposes.", + long: kubeletKubeconfigLongDesc, cmdFunc: kubeconfigphase.CreateKubeletKubeConfigFile, }, { use: "controller-manager", - short: "Generate a kubeconfig file for the Controller Manager to use.", + short: "Generates a kubeconfig file for the controller manager to use", + long: controllerManagerKubeconfigLongDesc, cmdFunc: kubeconfigphase.CreateControllerManagerKubeConfigFile, }, { use: "scheduler", - short: "Generate a kubeconfig file for the Scheduler to use.", + short: "Generates a kubeconfig file for the scheduler to use", + long: schedulerKubeconfigLongDesc, cmdFunc: kubeconfigphase.CreateSchedulerKubeConfigFile, }, { - use: "user", - short: "Outputs a kubeconfig file for an additional user.", + use: "user", + short: "Outputs a kubeconfig file for an additional user", + long: userKubeconfigLongDesc, + examples: userKubeconfigExample, cmdFunc: func(outDir string, cfg *kubeadmapi.MasterConfiguration) error { if clientName == "" { - return fmt.Errorf("missing required argument client-name") + return fmt.Errorf("missing required argument --client-name") } // if the kubeconfig file for an additional user has to use a token, use it @@ -112,24 +167,27 @@ func getKubeConfigSubCommands(out io.Writer, outDir, defaultKubernetesVersion st for _, properties := range subCmdProperties { // Creates the UX Command cmd := &cobra.Command{ - Use: properties.use, - Short: properties.short, - Run: runCmdPhase(properties.cmdFunc, &outDir, &cfgPath, cfg), + Use: properties.use, + Short: properties.short, + Long: properties.long, + Example: properties.examples, + Run: runCmdPhase(properties.cmdFunc, &outDir, &cfgPath, cfg), } // Add flags to the command if properties.use != "user" { cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)") } - cmd.Flags().StringVar(&cfg.CertificatesDir, "cert-dir", cfg.CertificatesDir, "The path where certificates are stored.") - cmd.Flags().StringVar(&cfg.API.AdvertiseAddress, "apiserver-advertise-address", cfg.API.AdvertiseAddress, "The IP address or DNS name the API Server is accessible on.") - cmd.Flags().Int32Var(&cfg.API.BindPort, "apiserver-bind-port", cfg.API.BindPort, "The port the API Server is accessible on.") + cmd.Flags().StringVar(&cfg.CertificatesDir, "cert-dir", cfg.CertificatesDir, "The path where certificates are stored") + cmd.Flags().StringVar(&cfg.API.AdvertiseAddress, "apiserver-advertise-address", cfg.API.AdvertiseAddress, "The IP address the API server is accessible on") + cmd.Flags().Int32Var(&cfg.API.BindPort, "apiserver-bind-port", cfg.API.BindPort, "The port the API server is accessible on") + cmd.Flags().StringVar(&outDir, "kubeconfig-dir", outDir, "The port where to save the kubeconfig file") if properties.use == "all" || properties.use == "kubelet" { - cmd.Flags().StringVar(&cfg.NodeName, "node-name", cfg.NodeName, `The node name that the kubelet client cert should use.`) + cmd.Flags().StringVar(&cfg.NodeName, "node-name", cfg.NodeName, `The node name that should be used for the kubelet client certificate`) } if properties.use == "user" { - cmd.Flags().StringVar(&token, "token", token, "The token that should be used as the authentication mechanism for this kubeconfig.") - cmd.Flags().StringVar(&clientName, "client-name", clientName, "The name of the KubeConfig user that will be created. Will also be used as the CN if client certs are created.") + cmd.Flags().StringVar(&token, "token", token, "The token that should be used as the authentication mechanism for this kubeconfig (instead of client certificates)") + cmd.Flags().StringVar(&clientName, "client-name", clientName, "The name of user. It will be used as the CN if client certificates are created") } subCmds = append(subCmds, cmd) diff --git a/cmd/kubeadm/app/cmd/phases/kubeconfig_test.go b/cmd/kubeadm/app/cmd/phases/kubeconfig_test.go index f3d86eff9bb..089147ada70 100644 --- a/cmd/kubeadm/app/cmd/phases/kubeconfig_test.go +++ b/cmd/kubeadm/app/cmd/phases/kubeconfig_test.go @@ -44,6 +44,7 @@ func TestKubeConfigCSubCommandsHasFlags(t *testing.T) { "cert-dir", "apiserver-advertise-address", "apiserver-bind-port", + "kubeconfig-dir", } var tests = []struct { @@ -167,10 +168,12 @@ func TestKubeConfigSubCommandsThatCreateFilesWithFlags(t *testing.T) { // Adds a pki folder with a ca certs to the temp folder pkidir := testutil.SetupPkiDirWithCertificateAuthorithy(t, tmpdir) - // Retrives ca cert for assertions + outputdir := tmpdir + + // Retrieves ca cert for assertions caCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(pkidir, kubeadmconstants.CACertAndKeyBaseName) if err != nil { - t.Fatalf("couldn't retrive ca cert: %v", err) + t.Fatalf("couldn't retrieve ca cert: %v", err) } // Get subcommands working in the temporary directory @@ -178,7 +181,9 @@ func TestKubeConfigSubCommandsThatCreateFilesWithFlags(t *testing.T) { // Execute the subcommand certDirFlag := fmt.Sprintf("--cert-dir=%s", pkidir) + outputDirFlag := fmt.Sprintf("--kubeconfig-dir=%s", outputdir) allFlags := append(commonFlags, certDirFlag) + allFlags = append(allFlags, outputDirFlag) allFlags = append(allFlags, test.additionalFlags...) cmdtestutil.RunSubCommand(t, subCmds, test.command, allFlags...) @@ -267,10 +272,10 @@ func TestKubeConfigSubCommandsThatCreateFilesWithConfigFile(t *testing.T) { // Adds a pki folder with a ca certs to the temp folder pkidir := testutil.SetupPkiDirWithCertificateAuthorithy(t, tmpdir) - // Retrives ca cert for assertions + // Retrieves ca cert for assertions caCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(pkidir, kubeadmconstants.CACertAndKeyBaseName) if err != nil { - t.Fatalf("couldn't retrive ca cert: %v", err) + t.Fatalf("couldn't retrieve ca cert: %v", err) } // Adds a master configuration file @@ -320,10 +325,12 @@ func TestKubeConfigSubCommandsThatWritesToOut(t *testing.T) { // Adds a pki folder with a ca cert to the temp folder pkidir := testutil.SetupPkiDirWithCertificateAuthorithy(t, tmpdir) - // Retrives ca cert for assertions + outputdir := tmpdir + + // Retrieves ca cert for assertions caCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(pkidir, kubeadmconstants.CACertAndKeyBaseName) if err != nil { - t.Fatalf("couldn't retrive ca cert: %v", err) + t.Fatalf("couldn't retrieve ca cert: %v", err) } commonFlags := []string{ @@ -331,6 +338,7 @@ func TestKubeConfigSubCommandsThatWritesToOut(t *testing.T) { "--apiserver-bind-port=1234", "--client-name=myUser", fmt.Sprintf("--cert-dir=%s", pkidir), + fmt.Sprintf("--kubeconfig-dir=%s", outputdir), } var tests = []struct { diff --git a/cmd/kubeadm/app/cmd/phases/markmaster.go b/cmd/kubeadm/app/cmd/phases/markmaster.go index 89457fdfc76..3b9f4ad4388 100644 --- a/cmd/kubeadm/app/cmd/phases/markmaster.go +++ b/cmd/kubeadm/app/cmd/phases/markmaster.go @@ -19,31 +19,70 @@ package phases import ( "github.com/spf13/cobra" + kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" + "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" markmasterphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/markmaster" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" + configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/util/normalizer" +) + +var ( + markMasterLongDesc = normalizer.LongDesc(` + Applies a label that specifies that a node is a master and a taint that forces workloads to be deployed accordingly. + ` + cmdutil.AlphaDisclaimer) + + markMasterExample = normalizer.Examples(` + # Applies master label and taint to the current node, functionally equivalent to what executed by kubeadm init. + kubeadm alpha phase mark-master + + # Applies master label and taint to a specific node + kubeadm alpha phase mark-master --node-name myNode + `) ) // NewCmdMarkMaster returns the Cobra command for running the mark-master phase func NewCmdMarkMaster() *cobra.Command { - var kubeConfigFile string + + cfg := &kubeadmapiext.MasterConfiguration{ + // KubernetesVersion is not used by mark master, but we set this explicitly to avoid + // the lookup of the version from the internet when executing ConfigFileAndDefaultsToInternalConfig + KubernetesVersion: "v1.9.0", + } + + // Default values for the cobra help text + legacyscheme.Scheme.Default(cfg) + + var cfgPath, kubeConfigFile string cmd := &cobra.Command{ - Use: "mark-master ", - Short: "Mark a node as master.", + Use: "mark-master", + Short: "Mark a node as master", + Long: markMasterLongDesc, + Example: markMasterExample, Aliases: []string{"markmaster"}, - RunE: func(_ *cobra.Command, args []string) error { - err := cmdutil.ValidateExactArgNumber(args, []string{"node-name"}) + Run: func(cmd *cobra.Command, args []string) { + if err := validation.ValidateMixedArguments(cmd.Flags()); err != nil { + kubeadmutil.CheckErr(err) + } + + // This call returns the ready-to-use configuration based on the configuration file that might or might not exist and the default cfg populated by flags + internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, cfg) kubeadmutil.CheckErr(err) client, err := kubeconfigutil.ClientSetFromFile(kubeConfigFile) kubeadmutil.CheckErr(err) - nodeName := args[0] - return markmasterphase.MarkMaster(client, nodeName) + err = markmasterphase.MarkMaster(client, internalcfg.NodeName) + kubeadmutil.CheckErr(err) }, } - cmd.Flags().StringVar(&kubeConfigFile, "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use for talking to the cluster") + cmd.Flags().StringVar(&kubeConfigFile, "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use when talking to the cluster") + cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)") + cmd.Flags().StringVar(&cfg.NodeName, "node-name", cfg.NodeName, `The node name to which label and taints should apply`) + return cmd } diff --git a/cmd/kubeadm/app/cmd/phases/phase.go b/cmd/kubeadm/app/cmd/phases/phase.go index 276e37ba6d4..2f5a42588fb 100644 --- a/cmd/kubeadm/app/cmd/phases/phase.go +++ b/cmd/kubeadm/app/cmd/phases/phase.go @@ -28,7 +28,7 @@ func NewCmdPhase(out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "phase", Short: "Invoke subsets of kubeadm functions separately for a manual install.", - RunE: cmdutil.SubCmdRunE("phase"), + Long: cmdutil.MacroCommandLongDescription, } cmd.AddCommand(NewCmdAddon()) diff --git a/cmd/kubeadm/app/cmd/phases/preflight.go b/cmd/kubeadm/app/cmd/phases/preflight.go index 764328acc0c..54b51343aff 100644 --- a/cmd/kubeadm/app/cmd/phases/preflight.go +++ b/cmd/kubeadm/app/cmd/phases/preflight.go @@ -19,9 +19,33 @@ package phases import ( "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/util/sets" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" "k8s.io/kubernetes/cmd/kubeadm/app/preflight" + kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" + "k8s.io/kubernetes/pkg/util/normalizer" + utilsexec "k8s.io/utils/exec" +) + +var ( + masterPreflightLongDesc = normalizer.LongDesc(` + Run master pre-flight checks, functionally equivalent to what implemented by kubeadm init. + ` + cmdutil.AlphaDisclaimer) + + masterPreflightExample = normalizer.Examples(` + # Run master pre-flight checks. + kubeadm alpha phase preflight master + `) + + nodePreflightLongDesc = normalizer.LongDesc(` + Run node pre-flight checks, functionally equivalent to what implemented by kubeadm join. + ` + cmdutil.AlphaDisclaimer) + + nodePreflightExample = normalizer.Examples(` + # Run node pre-flight checks. + kubeadm alpha phase preflight node + `) ) // NewCmdPreFlight calls cobra.Command for preflight checks @@ -29,7 +53,7 @@ func NewCmdPreFlight() *cobra.Command { cmd := &cobra.Command{ Use: "preflight", Short: "Run pre-flight checks", - RunE: cmdutil.SubCmdRunE("preflight"), + Long: cmdutil.MacroCommandLongDescription, } cmd.AddCommand(NewCmdPreFlightMaster()) @@ -40,11 +64,15 @@ func NewCmdPreFlight() *cobra.Command { // NewCmdPreFlightMaster calls cobra.Command for master preflight checks func NewCmdPreFlightMaster() *cobra.Command { cmd := &cobra.Command{ - Use: "master", - Short: "Run master pre-flight checks", - RunE: func(cmd *cobra.Command, args []string) error { + Use: "master", + Short: "Run master pre-flight checks", + Long: masterPreflightLongDesc, + Example: masterPreflightExample, + Run: func(cmd *cobra.Command, args []string) { cfg := &kubeadmapi.MasterConfiguration{} - return preflight.RunInitMasterChecks(cfg) + criSocket := "" + err := preflight.RunInitMasterChecks(utilsexec.New(), cfg, criSocket, sets.NewString()) + kubeadmutil.CheckErr(err) }, } @@ -54,11 +82,15 @@ func NewCmdPreFlightMaster() *cobra.Command { // NewCmdPreFlightNode calls cobra.Command for node preflight checks func NewCmdPreFlightNode() *cobra.Command { cmd := &cobra.Command{ - Use: "node", - Short: "Run node pre-flight checks", - RunE: func(cmd *cobra.Command, args []string) error { + Use: "node", + Short: "Run node pre-flight checks", + Long: nodePreflightLongDesc, + Example: nodePreflightExample, + Run: func(cmd *cobra.Command, args []string) { cfg := &kubeadmapi.NodeConfiguration{} - return preflight.RunJoinNodeChecks(cfg) + criSocket := "" + err := preflight.RunJoinNodeChecks(utilsexec.New(), cfg, criSocket, sets.NewString()) + kubeadmutil.CheckErr(err) }, } diff --git a/cmd/kubeadm/app/cmd/phases/selfhosting.go b/cmd/kubeadm/app/cmd/phases/selfhosting.go index 7011a9b7761..9bf6530ee1f 100644 --- a/cmd/kubeadm/app/cmd/phases/selfhosting.go +++ b/cmd/kubeadm/app/cmd/phases/selfhosting.go @@ -33,16 +33,34 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/util/normalizer" +) + +var ( + selfhostingLongDesc = normalizer.LongDesc(` + Converts static Pod files for control plane components into self-hosted DaemonSets configured via the Kubernetes API. + + See the documentation for self-hosting limitations. + + ` + cmdutil.AlphaDisclaimer) + + selfhostingExample = normalizer.Examples(` + # Converts a static Pod-hosted control plane into a self-hosted one, + # functionally equivalent to what generated by kubeadm init executed + # with --feature-gates=SelfHosting=true. + + kubeadm alpha phase selfhosting convert-from-staticpods + `) ) // NewCmdSelfhosting returns the self-hosting Cobra command func NewCmdSelfhosting() *cobra.Command { cmd := &cobra.Command{ Use: "selfhosting", - Aliases: []string{"selfhosted"}, - Short: "Make a kubeadm cluster self-hosted.", - RunE: cmdutil.SubCmdRunE("selfhosting"), + Aliases: []string{"selfhosted", "self-hosting"}, + Short: "Makes a kubeadm cluster self-hosted", + Long: cmdutil.MacroCommandLongDescription, } cmd.AddCommand(getSelfhostingSubCommand()) @@ -54,7 +72,7 @@ func getSelfhostingSubCommand() *cobra.Command { cfg := &kubeadmapiext.MasterConfiguration{} // Default values for the cobra help text - api.Scheme.Default(cfg) + legacyscheme.Scheme.Default(cfg) var cfgPath, kubeConfigFile, featureGatesString string @@ -62,7 +80,9 @@ func getSelfhostingSubCommand() *cobra.Command { cmd := &cobra.Command{ Use: "convert-from-staticpods", Aliases: []string{"from-staticpods"}, - Short: "Converts a Static Pod-hosted control plane into a self-hosted one.", + Short: "Converts a static Pod-hosted control plane into a self-hosted one", + Long: selfhostingLongDesc, + Example: selfhostingExample, Run: func(cmd *cobra.Command, args []string) { var err error if cfg.FeatureGates, err = features.NewFeatureGate(&features.InitFeatureGates, featureGatesString); err != nil { @@ -83,7 +103,7 @@ func getSelfhostingSubCommand() *cobra.Command { // Converts the Static Pod-hosted control plane into a self-hosted one waiter := apiclient.NewKubeWaiter(client, 2*time.Minute, os.Stdout) - err = selfhosting.CreateSelfHostedControlPlane(constants.GetStaticPodDirectory(), constants.KubernetesDir, internalcfg, client, waiter) + err = selfhosting.CreateSelfHostedControlPlane(constants.GetStaticPodDirectory(), constants.KubernetesDir, internalcfg, client, waiter, false) kubeadmutil.CheckErr(err) }, } @@ -91,13 +111,13 @@ func getSelfhostingSubCommand() *cobra.Command { // Add flags to the command // flags bound to the configuration object cmd.Flags().StringVar(&cfg.CertificatesDir, "cert-dir", cfg.CertificatesDir, `The path where certificates are stored`) - cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)") - cmd.Flags().StringVar(&featureGatesString, "feature-gates", featureGatesString, "A set of key=value pairs that describe feature gates for various features."+ + cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to a kubeadm config file. WARNING: Usage of a configuration file is experimental!") + cmd.Flags().StringVar(&featureGatesString, "feature-gates", featureGatesString, "A set of key=value pairs that describe feature gates for various features. "+ "Options are:\n"+strings.Join(features.KnownFeatures(&features.InitFeatureGates), "\n")) // flags that are not bound to the configuration object // Note: All flags that are not bound to the cfg object should be whitelisted in cmd/kubeadm/app/apis/kubeadm/validation/validation.go - cmd.Flags().StringVar(&kubeConfigFile, "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use for talking to the cluster") + cmd.Flags().StringVar(&kubeConfigFile, "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use when talking to the cluster") return cmd } diff --git a/cmd/kubeadm/app/cmd/phases/uploadconfig.go b/cmd/kubeadm/app/cmd/phases/uploadconfig.go index bd2573f4f09..ec11c2f3380 100644 --- a/cmd/kubeadm/app/cmd/phases/uploadconfig.go +++ b/cmd/kubeadm/app/cmd/phases/uploadconfig.go @@ -21,11 +21,29 @@ import ( "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" + cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" + kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" + "k8s.io/kubernetes/pkg/util/normalizer" +) + +var ( + uploadConfigLongDesc = fmt.Sprintf(normalizer.LongDesc(` + Uploads the kubeadm init configuration of your cluster to a ConfigMap called %s in the %s namespace. + This enables correct configuration of system components and a seamless user experience when upgrading. + + Alternatively, you can use kubeadm config. + `+cmdutil.AlphaDisclaimer), kubeadmconstants.MasterConfigurationConfigMap, metav1.NamespaceSystem) + + uploadConfigExample = normalizer.Examples(` + # uploads the configuration of your cluster + kubeadm alpha phase upload-config --config=myConfig.yaml + `) ) // NewCmdUploadConfig returns the Cobra command for running the uploadconfig phase @@ -33,7 +51,9 @@ func NewCmdUploadConfig() *cobra.Command { var cfgPath, kubeConfigFile string cmd := &cobra.Command{ Use: "upload-config", - Short: "Upload the currently used configuration for kubeadm to a ConfigMap in the cluster for future use in reconfiguration and upgrades of the cluster.", + Short: "Uploads the currently used configuration for kubeadm to a ConfigMap", + Long: uploadConfigLongDesc, + Example: uploadConfigExample, Aliases: []string{"uploadconfig"}, Run: func(_ *cobra.Command, args []string) { if len(cfgPath) == 0 { @@ -51,8 +71,8 @@ func NewCmdUploadConfig() *cobra.Command { }, } - cmd.Flags().StringVar(&kubeConfigFile, "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use for talking to the cluster") - cmd.Flags().StringVar(&cfgPath, "config", "", "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)") + cmd.Flags().StringVar(&kubeConfigFile, "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use when talking to the cluster") + cmd.Flags().StringVar(&cfgPath, "config", "", "Path to a kubeadm config file. WARNING: Usage of a configuration file is experimental!") return cmd } diff --git a/cmd/kubeadm/app/cmd/phases/util.go b/cmd/kubeadm/app/cmd/phases/util.go index 32b7f3fb1d4..92be3a187c3 100644 --- a/cmd/kubeadm/app/cmd/phases/util.go +++ b/cmd/kubeadm/app/cmd/phases/util.go @@ -29,7 +29,7 @@ import ( // runCmdPhase creates a cobra.Command Run function, by composing the call to the given cmdFunc with necessary additional steps (e.g preparation of input parameters) func runCmdPhase(cmdFunc func(outDir string, cfg *kubeadmapi.MasterConfiguration) error, outDir, cfgPath *string, cfg *kubeadmapiext.MasterConfiguration) func(cmd *cobra.Command, args []string) { - // the following statement build a clousure that wraps a call to a cmdFunc, binding + // the following statement build a closure that wraps a call to a cmdFunc, binding // the function itself with the specific parameters of each sub command. // Please note that specific parameter should be passed as value, while other parameters - passed as reference - // are shared between sub commands and gets access to current value e.g. flags value. diff --git a/cmd/kubeadm/app/cmd/reset.go b/cmd/kubeadm/app/cmd/reset.go index 97213254feb..feaa945add9 100644 --- a/cmd/kubeadm/app/cmd/reset.go +++ b/cmd/kubeadm/app/cmd/reset.go @@ -22,62 +22,86 @@ import ( "os" "os/exec" "path/filepath" + "strings" "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/util/sets" kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" + "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/preflight" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/pkg/util/initsystem" + utilsexec "k8s.io/utils/exec" +) + +var ( + crictlSandboxesParamsFormat = "%s -r %s sandboxes --quiet | xargs -r" + crictlStopParamsFormat = "%s -r %s stops %s" + crictlRemoveParamsFormat = "%s -r %s rms %s" ) // NewCmdReset returns the "kubeadm reset" command func NewCmdReset(out io.Writer) *cobra.Command { var skipPreFlight bool var certsDir string + var criSocketPath string + var ignorePreflightErrors []string + cmd := &cobra.Command{ Use: "reset", Short: "Run this to revert any changes made to this host by 'kubeadm init' or 'kubeadm join'.", Run: func(cmd *cobra.Command, args []string) { - r, err := NewReset(skipPreFlight, certsDir) + ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(ignorePreflightErrors, skipPreFlight) + kubeadmutil.CheckErr(err) + + r, err := NewReset(ignorePreflightErrorsSet, certsDir, criSocketPath) kubeadmutil.CheckErr(err) kubeadmutil.CheckErr(r.Run(out)) }, } + cmd.PersistentFlags().StringSliceVar( + &ignorePreflightErrors, "ignore-preflight-errors", ignorePreflightErrors, + "A list of checks whose errors will be shown as warnings. Example: 'IsPrivilegedUser,Swap'. Value 'all' ignores errors from all checks.", + ) cmd.PersistentFlags().BoolVar( &skipPreFlight, "skip-preflight-checks", false, - "Skip preflight checks normally run before modifying the system.", + "Skip preflight checks which normally run before modifying the system.", ) + cmd.PersistentFlags().MarkDeprecated("skip-preflight-checks", "it is now equivalent to --ignore-preflight-errors=all") cmd.PersistentFlags().StringVar( &certsDir, "cert-dir", kubeadmapiext.DefaultCertificatesDir, "The path to the directory where the certificates are stored. If specified, clean this directory.", ) + cmd.PersistentFlags().StringVar( + &criSocketPath, "cri-socket", "/var/run/dockershim.sock", + "The path to the CRI socket to use with crictl when cleaning up containers.", + ) + return cmd } // Reset defines struct used for kubeadm reset command type Reset struct { - certsDir string + certsDir string + criSocketPath string } // NewReset instantiate Reset struct -func NewReset(skipPreFlight bool, certsDir string) (*Reset, error) { - if !skipPreFlight { - fmt.Println("[preflight] Running pre-flight checks.") +func NewReset(ignorePreflightErrors sets.String, certsDir, criSocketPath string) (*Reset, error) { + fmt.Println("[preflight] Running pre-flight checks.") - if err := preflight.RunRootCheckOnly(); err != nil { - return nil, err - } - } else { - fmt.Println("[preflight] Skipping pre-flight checks.") + if err := preflight.RunRootCheckOnly(ignorePreflightErrors); err != nil { + return nil, err } return &Reset{ - certsDir: certsDir, + certsDir: certsDir, + criSocketPath: criSocketPath, }, nil } @@ -87,12 +111,12 @@ func (r *Reset) Run(out io.Writer) error { // Try to stop the kubelet service initSystem, err := initsystem.GetInitSystem() if err != nil { - fmt.Println("[reset] WARNING: The kubelet service couldn't be stopped by kubeadm because no supported init system was detected.") + fmt.Println("[reset] WARNING: The kubelet service could not be stopped by kubeadm. Unable to detect a supported init system!") fmt.Println("[reset] WARNING: Please ensure kubelet is stopped manually.") } else { fmt.Println("[reset] Stopping the kubelet service.") if err := initSystem.ServiceStop("kubelet"); err != nil { - fmt.Printf("[reset] WARNING: The kubelet service couldn't be stopped by kubeadm: [%v]\n", err) + fmt.Printf("[reset] WARNING: The kubelet service could not be stopped by kubeadm: [%v]\n", err) fmt.Println("[reset] WARNING: Please ensure kubelet is stopped manually.") } } @@ -105,15 +129,11 @@ func (r *Reset) Run(out io.Writer) error { fmt.Printf("[reset] Failed to unmount mounted directories in /var/lib/kubelet: %s\n", string(umountOutputBytes)) } + fmt.Println("[reset] Removing kubernetes-managed containers.") dockerCheck := preflight.ServiceCheck{Service: "docker", CheckIfActive: true} - if _, errors := dockerCheck.Check(); len(errors) == 0 { - fmt.Println("[reset] Removing kubernetes-managed containers.") - if err := exec.Command("sh", "-c", "docker ps -a --filter name=k8s_ -q | xargs -r docker rm --force --volumes").Run(); err != nil { - fmt.Println("[reset] Failed to stop the running containers.") - } - } else { - fmt.Println("[reset] Docker doesn't seem to be running. Skipping the removal of running Kubernetes containers.") - } + execer := utilsexec.New() + + reset(execer, dockerCheck, r.criSocketPath) dirsToClean := []string{"/var/lib/kubelet", "/etc/cni/net.d", "/var/lib/dockershim", "/var/run/kubernetes"} @@ -123,7 +143,7 @@ func (r *Reset) Run(out io.Writer) error { if _, err := os.Stat(etcdManifestPath); err == nil { dirsToClean = append(dirsToClean, "/var/lib/etcd") } else { - fmt.Printf("[reset] No etcd manifest found in %q, assuming external etcd.\n", etcdManifestPath) + fmt.Printf("[reset] No etcd manifest found in %q. Assuming external etcd.\n", etcdManifestPath) } // Then clean contents from the stateful kubelet, etcd and cni directories @@ -133,11 +153,64 @@ func (r *Reset) Run(out io.Writer) error { } // Remove contents from the config and pki directories + if r.certsDir != kubeadmapiext.DefaultCertificatesDir { + fmt.Printf("[reset] WARNING: Cleaning a non-default certificates directory: %q\n", r.certsDir) + } resetConfigDir(kubeadmconstants.KubernetesDir, r.certsDir) return nil } +func reset(execer utilsexec.Interface, dockerCheck preflight.Checker, criSocketPath string) { + crictlPath, err := execer.LookPath("crictl") + if err == nil { + resetWithCrictl(execer, dockerCheck, criSocketPath, crictlPath) + } else { + resetWithDocker(execer, dockerCheck) + } +} + +func resetWithDocker(execer utilsexec.Interface, dockerCheck preflight.Checker) { + if _, errors := dockerCheck.Check(); len(errors) == 0 { + if err := execer.Command("sh", "-c", "docker ps -a --filter name=k8s_ -q | xargs -r docker rm --force --volumes").Run(); err != nil { + fmt.Println("[reset] Failed to stop the running containers.") + } + } else { + fmt.Println("[reset] Docker doesn't seem to be running. Skipping the removal of running Kubernetes containers.") + } +} + +func resetWithCrictl(execer utilsexec.Interface, dockerCheck preflight.Checker, criSocketPath, crictlPath string) { + if criSocketPath != "" { + fmt.Printf("[reset] Cleaning up running containers using crictl with socket %s\n", criSocketPath) + listcmd := fmt.Sprintf(crictlSandboxesParamsFormat, crictlPath, criSocketPath) + output, err := execer.Command("sh", "-c", listcmd).CombinedOutput() + if err != nil { + fmt.Println("[reset] Failed to list running pods using crictl. Trying using docker instead.") + resetWithDocker(execer, dockerCheck) + return + } + sandboxes := strings.Split(string(output), " ") + for _, s := range sandboxes { + stopcmd := fmt.Sprintf(crictlStopParamsFormat, crictlPath, criSocketPath, s) + if err := execer.Command("sh", "-c", stopcmd).Run(); err != nil { + fmt.Println("[reset] Failed to stop the running containers using crictl. Trying using docker instead.") + resetWithDocker(execer, dockerCheck) + return + } + removecmd := fmt.Sprintf(crictlRemoveParamsFormat, crictlPath, criSocketPath, s) + if err := execer.Command("sh", "-c", removecmd).Run(); err != nil { + fmt.Println("[reset] Failed to remove the running containers using crictl. Trying using docker instead.") + resetWithDocker(execer, dockerCheck) + return + } + } + } else { + fmt.Println("[reset] CRI socket path not provided for crictl. Trying docker instead.") + resetWithDocker(execer, dockerCheck) + } +} + // cleanDir removes everything in a directory, but not the directory itself func cleanDir(filePath string) error { // If the directory doesn't even exist there's nothing to do, and we do diff --git a/cmd/kubeadm/app/cmd/reset_test.go b/cmd/kubeadm/app/cmd/reset_test.go index 1eeb0076dc1..59780e0a5a6 100644 --- a/cmd/kubeadm/app/cmd/reset_test.go +++ b/cmd/kubeadm/app/cmd/reset_test.go @@ -17,13 +17,17 @@ limitations under the License. package cmd import ( + "errors" "io/ioutil" "os" "path/filepath" + "strings" "testing" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/preflight" + "k8s.io/utils/exec" + fakeexec "k8s.io/utils/exec/testing" ) func assertExists(t *testing.T, path string) { @@ -181,3 +185,161 @@ func TestConfigDirCleaner(t *testing.T) { } } } + +type fakeDockerChecker struct { + warnings []error + errors []error +} + +func (c *fakeDockerChecker) Check() (warnings, errors []error) { + return c.warnings, c.errors +} + +func (c *fakeDockerChecker) Name() string { + return "FakeDocker" +} + +func newFakeDockerChecker(warnings, errors []error) preflight.Checker { + return &fakeDockerChecker{warnings: warnings, errors: errors} +} + +func TestResetWithDocker(t *testing.T) { + fcmd := fakeexec.FakeCmd{ + RunScript: []fakeexec.FakeRunAction{ + func() ([]byte, []byte, error) { return nil, nil, nil }, + func() ([]byte, []byte, error) { return nil, nil, errors.New("docker error") }, + func() ([]byte, []byte, error) { return nil, nil, nil }, + }, + } + fexec := fakeexec.FakeExec{ + CommandScript: []fakeexec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + }, + } + resetWithDocker(&fexec, newFakeDockerChecker(nil, nil)) + if fcmd.RunCalls != 1 { + t.Errorf("expected 1 call to Run, got %d", fcmd.RunCalls) + } + resetWithDocker(&fexec, newFakeDockerChecker(nil, nil)) + if fcmd.RunCalls != 2 { + t.Errorf("expected 2 calls to Run, got %d", fcmd.RunCalls) + } + resetWithDocker(&fexec, newFakeDockerChecker(nil, []error{errors.New("test error")})) + if fcmd.RunCalls != 2 { + t.Errorf("expected 2 calls to Run, got %d", fcmd.RunCalls) + } +} + +func TestResetWithCrictl(t *testing.T) { + fcmd := fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ + // 2: socket path provided, not runnning with crictl (1x CombinedOutput, 2x Run) + func() ([]byte, error) { return []byte("1"), nil }, + // 3: socket path provided, crictl fails, reset with docker (1x CombinedOuput fail, 1x Run) + func() ([]byte, error) { return nil, errors.New("crictl list err") }, + }, + RunScript: []fakeexec.FakeRunAction{ + // 1: socket path not provided, running with docker + func() ([]byte, []byte, error) { return nil, nil, nil }, + // 2: socket path provided, now runnning with crictl (1x CombinedOutput, 2x Run) + func() ([]byte, []byte, error) { return nil, nil, nil }, + func() ([]byte, []byte, error) { return nil, nil, nil }, + // 3: socket path provided, crictl fails, reset with docker (1x CombinedOuput, 1x Run) + func() ([]byte, []byte, error) { return nil, nil, nil }, + // 4: running with no socket and docker fails (1x Run) + func() ([]byte, []byte, error) { return nil, nil, nil }, + }, + } + fexec := fakeexec.FakeExec{ + CommandScript: []fakeexec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + }, + } + + // 1: socket path not provided, running with docker + resetWithCrictl(&fexec, newFakeDockerChecker(nil, nil), "", "crictl") + if fcmd.RunCalls != 1 { + t.Errorf("expected 1 call to Run, got %d", fcmd.RunCalls) + } + if !strings.Contains(fcmd.RunLog[0][2], "docker") { + t.Errorf("expected a call to docker, got %v", fcmd.RunLog[0]) + } + + // 2: socket path provided, now runnning with crictl (1x CombinedOutput, 2x Run) + resetWithCrictl(&fexec, newFakeDockerChecker(nil, nil), "/test.sock", "crictl") + if fcmd.RunCalls != 3 { + t.Errorf("expected 3 calls to Run, got %d", fcmd.RunCalls) + } + if !strings.Contains(fcmd.RunLog[1][2], "crictl") { + t.Errorf("expected a call to crictl, got %v", fcmd.RunLog[0]) + } + if !strings.Contains(fcmd.RunLog[2][2], "crictl") { + t.Errorf("expected a call to crictl, got %v", fcmd.RunLog[0]) + } + + // 3: socket path provided, crictl fails, reset with docker + resetWithCrictl(&fexec, newFakeDockerChecker(nil, nil), "/test.sock", "crictl") + if fcmd.RunCalls != 4 { + t.Errorf("expected 4 calls to Run, got %d", fcmd.RunCalls) + } + if !strings.Contains(fcmd.RunLog[3][2], "docker") { + t.Errorf("expected a call to docker, got %v", fcmd.RunLog[0]) + } + + // 4: running with no socket and docker fails (1x Run) + resetWithCrictl(&fexec, newFakeDockerChecker(nil, []error{errors.New("test error")}), "", "crictl") + if fcmd.RunCalls != 4 { + t.Errorf("expected 4 calls to Run, got %d", fcmd.RunCalls) + } +} + +func TestReset(t *testing.T) { + fcmd := fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ + func() ([]byte, error) { return []byte("1"), nil }, + func() ([]byte, error) { return []byte("1"), nil }, + func() ([]byte, error) { return []byte("1"), nil }, + }, + RunScript: []fakeexec.FakeRunAction{ + func() ([]byte, []byte, error) { return nil, nil, nil }, + func() ([]byte, []byte, error) { return nil, nil, nil }, + func() ([]byte, []byte, error) { return nil, nil, nil }, + }, + } + fexec := fakeexec.FakeExec{ + CommandScript: []fakeexec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + }, + LookPathFunc: func(cmd string) (string, error) { return cmd, nil }, + } + + reset(&fexec, newFakeDockerChecker(nil, nil), "/test.sock") + if fcmd.RunCalls != 2 { + t.Errorf("expected 2 call to Run, got %d", fcmd.RunCalls) + } + if !strings.Contains(fcmd.RunLog[0][2], "crictl") { + t.Errorf("expected a call to crictl, got %v", fcmd.RunLog[0]) + } + + fexec.LookPathFunc = func(cmd string) (string, error) { return "", errors.New("no crictl") } + reset(&fexec, newFakeDockerChecker(nil, nil), "/test.sock") + if fcmd.RunCalls != 3 { + t.Errorf("expected 3 calls to Run, got %d", fcmd.RunCalls) + } + if !strings.Contains(fcmd.RunLog[2][2], "docker") { + t.Errorf("expected a call to docker, got %v", fcmd.RunLog[0]) + } +} diff --git a/cmd/kubeadm/app/cmd/token.go b/cmd/kubeadm/app/cmd/token.go index 2c7928b7d4f..87b8d077750 100644 --- a/cmd/kubeadm/app/cmd/token.go +++ b/cmd/kubeadm/app/cmd/token.go @@ -17,12 +17,15 @@ limitations under the License. package cmd import ( + "bytes" + "crypto/x509" "fmt" "io" "os" "sort" "strings" "text/tabwriter" + "text/template" "time" "github.com/renstrom/dedent" @@ -33,6 +36,8 @@ import ( "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/util/sets" clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" + clientcertutil "k8s.io/client-go/util/cert" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" @@ -40,38 +45,40 @@ import ( kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" + "k8s.io/kubernetes/cmd/kubeadm/app/util/pubkeypin" tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api" "k8s.io/kubernetes/pkg/printers" ) +var joinCommandTemplate = template.Must(template.New("join").Parse(`` + + `kubeadm join --token {{.Token}} {{.MasterHostPort}}{{range $h := .CAPubKeyPins}} --discovery-token-ca-cert-hash {{$h}}{{end}}`, +)) + // NewCmdToken returns cobra.Command for token management func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command { - var kubeConfigFile string var dryRun bool tokenCmd := &cobra.Command{ Use: "token", Short: "Manage bootstrap tokens.", Long: dedent.Dedent(` - This command will manage Bootstrap Token for you. - Please note this usage of this command is optional, and mostly for advanced users. + This command manages bootstrap tokens. It is optional and needed only for advanced use cases. - In short, Bootstrap Tokens are used for establishing bidirectional trust between a client and a server. - A Bootstrap Token can be used when a client (for example a node that's about to join the cluster) needs - to trust the server it is talking to. Then a Bootstrap Token with the "signing" usage can be used. - Bootstrap Tokens can also function as a way to allow short-lived authentication to the API Server + In short, bootstrap tokens are used for establishing bidirectional trust between a client and a server. + A bootstrap token can be used when a client (for example a node that is about to join the cluster) needs + to trust the server it is talking to. Then a bootstrap token with the "signing" usage can be used. + bootstrap tokens can also function as a way to allow short-lived authentication to the API Server (the token serves as a way for the API Server to trust the client), for example for doing the TLS Bootstrap. - What is a Bootstrap Token more exactly? + What is a bootstrap token more exactly? - It is a Secret in the kube-system namespace of type "bootstrap.kubernetes.io/token". - - A Bootstrap Token must be of the form "[a-z0-9]{6}.[a-z0-9]{16}"; the former part is the public Token ID, - and the latter is the Token Secret, which must be kept private at all circumstances. + - A bootstrap token must be of the form "[a-z0-9]{6}.[a-z0-9]{16}". The former part is the public token ID, + while the latter is the Token Secret and it must be kept private at all circumstances! - The name of the Secret must be named "bootstrap-token-(token-id)". - You can read more about Bootstrap Tokens here: - + You can read more about bootstrap tokens here: https://kubernetes.io/docs/admin/bootstrap-tokens/ `), @@ -84,7 +91,7 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command { } tokenCmd.PersistentFlags().StringVar(&kubeConfigFile, - "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use for talking to the cluster") + "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use when talking to the cluster") tokenCmd.PersistentFlags().BoolVar(&dryRun, "dry-run", dryRun, "Whether to enable dry-run mode or not") @@ -92,12 +99,13 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command { var extraGroups []string var tokenDuration time.Duration var description string + var printJoinCommand bool createCmd := &cobra.Command{ Use: "create [token]", Short: "Create bootstrap tokens on the server.", Long: dedent.Dedent(` - This command will create a Bootstrap Token for you. - You can specify the usages for this token, the time to live and an optional human friendly description. + This command will create a bootstrap token for you. + You can specify the usages for this token, the "time to live" and an optional human friendly description. The [token] is the actual token to write. This should be a securely generated random token of the form "[a-z0-9]{6}.[a-z0-9]{16}". @@ -111,19 +119,21 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command { client, err := getClientset(kubeConfigFile, dryRun) kubeadmutil.CheckErr(err) - err = RunCreateToken(out, client, token, tokenDuration, usages, extraGroups, description) + err = RunCreateToken(out, client, token, tokenDuration, usages, extraGroups, description, printJoinCommand, kubeConfigFile) kubeadmutil.CheckErr(err) }, } createCmd.Flags().DurationVar(&tokenDuration, - "ttl", kubeadmconstants.DefaultTokenDuration, "The duration before the token is automatically deleted (e.g. 1s, 2m, 3h). 0 means 'never expires'.") + "ttl", kubeadmconstants.DefaultTokenDuration, "The duration before the token is automatically deleted (e.g. 1s, 2m, 3h). If set to '0', the token will never expire.") createCmd.Flags().StringSliceVar(&usages, - "usages", kubeadmconstants.DefaultTokenUsages, "The ways in which this token can be used. Valid options: [signing,authentication].") + "usages", kubeadmconstants.DefaultTokenUsages, fmt.Sprintf("Describes the ways in which this token can be used. You can pass --usages multiple times or provide a comma separated list of options. Valid options: [%s].", strings.Join(kubeadmconstants.DefaultTokenUsages, ","))) createCmd.Flags().StringSliceVar(&extraGroups, "groups", []string{kubeadmconstants.NodeBootstrapTokenAuthGroup}, fmt.Sprintf("Extra groups that this token will authenticate as when used for authentication. Must match %q.", bootstrapapi.BootstrapGroupPattern)) createCmd.Flags().StringVar(&description, "description", "", "A human friendly description of how this token is used.") + createCmd.Flags().BoolVar(&printJoinCommand, + "print-join-command", false, "Instead of printing only the token, print the full 'kubeadm join' flag needed to join the cluster using the token.") tokenCmd.AddCommand(createCmd) tokenCmd.AddCommand(NewCmdTokenGenerate(out)) @@ -132,7 +142,7 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command { Use: "list", Short: "List bootstrap tokens on the server.", Long: dedent.Dedent(` - This command will list all Bootstrap Tokens for you. + This command will list all bootstrap tokens for you. `), Run: func(tokenCmd *cobra.Command, args []string) { client, err := getClientset(kubeConfigFile, dryRun) @@ -148,7 +158,7 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command { Use: "delete [token-value]", Short: "Delete bootstrap tokens on the server.", Long: dedent.Dedent(` - This command will delete a given Bootstrap Token for you. + This command will delete a given bootstrap token for you. The [token-value] is the full Token of the form "[a-z0-9]{6}.[a-z0-9]{16}" or the Token ID of the form "[a-z0-9]{6}" to delete. @@ -179,10 +189,10 @@ func NewCmdTokenGenerate(out io.Writer) *cobra.Command { the "init" and "join" commands. You don't have to use this command in order to generate a token. You can do so - yourself as long as it's in the format "[a-z0-9]{6}.[a-z0-9]{16}". This - command is provided for convenience to generate tokens in that format. + yourself as long as it is in the format "[a-z0-9]{6}.[a-z0-9]{16}". This + command is provided for convenience to generate tokens in the given format. - You can also use "kubeadm init" without specifying a token, and it will + You can also use "kubeadm init" without specifying a token and it will generate and print one for you. `), Run: func(cmd *cobra.Command, args []string) { @@ -193,8 +203,7 @@ func NewCmdTokenGenerate(out io.Writer) *cobra.Command { } // RunCreateToken generates a new bootstrap token and stores it as a secret on the server. -func RunCreateToken(out io.Writer, client clientset.Interface, token string, tokenDuration time.Duration, usages []string, extraGroups []string, description string) error { - +func RunCreateToken(out io.Writer, client clientset.Interface, token string, tokenDuration time.Duration, usages []string, extraGroups []string, description string, printJoinCommand bool, kubeConfigFile string) error { if len(token) == 0 { var err error token, err = tokenutil.GenerateToken() @@ -210,8 +219,9 @@ func RunCreateToken(out io.Writer, client clientset.Interface, token string, tok // adding groups only makes sense for authentication usagesSet := sets.NewString(usages...) - if len(extraGroups) > 0 && !usagesSet.Has("authentication") { - return fmt.Errorf("--groups cannot be specified unless --usages includes \"authentication\"") + usageAuthentication := strings.TrimPrefix(bootstrapapi.BootstrapTokenUsageAuthentication, bootstrapapi.BootstrapTokenUsagePrefix) + if len(extraGroups) > 0 && !usagesSet.Has(usageAuthentication) { + return fmt.Errorf("--groups cannot be specified unless --usages includes %q", usageAuthentication) } // validate any extra group names @@ -221,13 +231,28 @@ func RunCreateToken(out io.Writer, client clientset.Interface, token string, tok } } - // TODO: Validate usages here so we don't allow something unsupported + // validate usages + if err := bootstrapapi.ValidateUsages(usages); err != nil { + return err + } + err := tokenphase.CreateNewToken(client, token, tokenDuration, usages, extraGroups, description) if err != nil { return err } - fmt.Fprintln(out, token) + // if --print-join-command was specified, print the full `kubeadm join` command + // otherwise, just print the token + if printJoinCommand { + joinCommand, err := getJoinCommand(token, kubeConfigFile) + if err != nil { + return fmt.Errorf("failed to get join command: %v", err) + } + fmt.Fprintln(out, joinCommand) + } else { + fmt.Fprintln(out, token) + } + return nil } @@ -299,7 +324,7 @@ func RunListTokens(out io.Writer, errW io.Writer, client clientset.Interface) er usages := []string{} for k, v := range secret.Data { // Skip all fields that don't include this prefix - if !strings.Contains(k, bootstrapapi.BootstrapTokenUsagePrefix) { + if !strings.HasPrefix(k, bootstrapapi.BootstrapTokenUsagePrefix) { continue } // Skip those that don't have this usage set to true @@ -365,6 +390,54 @@ func getClientset(file string, dryRun bool) (clientset.Interface, error) { } return apiclient.NewDryRunClient(dryRunGetter, os.Stdout), nil } - client, err := kubeconfigutil.ClientSetFromFile(file) - return client, err + return kubeconfigutil.ClientSetFromFile(file) +} + +func getJoinCommand(token string, kubeConfigFile string) (string, error) { + // load the kubeconfig file to get the CA certificate and endpoint + config, err := clientcmd.LoadFromFile(kubeConfigFile) + if err != nil { + return "", fmt.Errorf("failed to load kubeconfig: %v", err) + } + + // load the default cluster config + clusterConfig := kubeconfigutil.GetClusterFromKubeConfig(config) + if clusterConfig == nil { + return "", fmt.Errorf("failed to get default cluster config") + } + + // load CA certificates from the kubeconfig (either from PEM data or by file path) + var caCerts []*x509.Certificate + if clusterConfig.CertificateAuthorityData != nil { + caCerts, err = clientcertutil.ParseCertsPEM(clusterConfig.CertificateAuthorityData) + if err != nil { + return "", fmt.Errorf("failed to parse CA certificate from kubeconfig: %v", err) + } + } else if clusterConfig.CertificateAuthority != "" { + caCerts, err = clientcertutil.CertsFromFile(clusterConfig.CertificateAuthority) + if err != nil { + return "", fmt.Errorf("failed to load CA certificate referenced by kubeconfig: %v", err) + } + } else { + return "", fmt.Errorf("no CA certificates found in kubeconfig") + } + + // hash all the CA certs and include their public key pins as trusted values + publicKeyPins := make([]string, 0, len(caCerts)) + for _, caCert := range caCerts { + publicKeyPins = append(publicKeyPins, pubkeypin.Hash(caCert)) + } + + ctx := map[string]interface{}{ + "Token": token, + "CAPubKeyPins": publicKeyPins, + "MasterHostPort": strings.Replace(clusterConfig.Server, "https://", "", -1), + } + + var out bytes.Buffer + err = joinCommandTemplate.Execute(&out, ctx) + if err != nil { + return "", fmt.Errorf("failed to render join command template: %v", err) + } + return out.String(), nil } diff --git a/cmd/kubeadm/app/cmd/token_test.go b/cmd/kubeadm/app/cmd/token_test.go index 2db9cd9e0b6..c3a344d1691 100644 --- a/cmd/kubeadm/app/cmd/token_test.go +++ b/cmd/kubeadm/app/cmd/token_test.go @@ -20,6 +20,12 @@ import ( "bytes" "regexp" "testing" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/fake" + core "k8s.io/client-go/testing" ) const ( @@ -44,3 +50,82 @@ func TestRunGenerateToken(t *testing.T) { t.Errorf("RunGenerateToken's output did not match expected regex; wanted: [%s], got: [%s]", TokenExpectedRegex, output) } } + +func TestRunCreateToken(t *testing.T) { + var buf bytes.Buffer + fakeClient := &fake.Clientset{} + fakeClient.AddReactor("get", "secrets", func(action core.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, errors.NewNotFound(v1.Resource("secrets"), "foo") + }) + + testCases := []struct { + name string + token string + usages []string + extraGroups []string + expectedError bool + }{ + { + name: "valid: empty token", + token: "", + usages: []string{"signing", "authentication"}, + extraGroups: []string{"system:bootstrappers:foo"}, + expectedError: false, + }, + { + name: "valid: non-empty token", + token: "abcdef.1234567890123456", + usages: []string{"signing", "authentication"}, + extraGroups: []string{"system:bootstrappers:foo"}, + expectedError: false, + }, + { + name: "valid: no extraGroups", + token: "abcdef.1234567890123456", + usages: []string{"signing", "authentication"}, + extraGroups: []string{}, + expectedError: false, + }, + { + name: "invalid: incorrect token", + token: "123456.AABBCCDDEEFFGGHH", + usages: []string{"signing", "authentication"}, + extraGroups: []string{}, + expectedError: true, + }, + { + name: "invalid: incorrect extraGroups", + token: "abcdef.1234567890123456", + usages: []string{"signing", "authentication"}, + extraGroups: []string{"foo"}, + expectedError: true, + }, + { + name: "invalid: specifying --groups when --usages doesn't include authentication", + token: "abcdef.1234567890123456", + usages: []string{"signing"}, + extraGroups: []string{"foo"}, + expectedError: true, + }, + { + name: "invalid: partially incorrect usages", + token: "abcdef.1234567890123456", + usages: []string{"foo", "authentication"}, + extraGroups: []string{"system:bootstrappers:foo"}, + expectedError: true, + }, + { + name: "invalid: all incorrect usages", + token: "abcdef.1234567890123456", + usages: []string{"foo", "bar"}, + extraGroups: []string{"system:bootstrappers:foo"}, + expectedError: true, + }, + } + for _, tc := range testCases { + err := RunCreateToken(&buf, fakeClient, tc.token, 0, tc.usages, tc.extraGroups, "", false, "") + if (err != nil) != tc.expectedError { + t.Errorf("Test case %s: RunCreateToken expected error: %v, saw: %v", tc.name, tc.expectedError, (err != nil)) + } + } +} diff --git a/cmd/kubeadm/app/cmd/upgrade/BUILD b/cmd/kubeadm/app/cmd/upgrade/BUILD index eb5958281b8..709a1e7d5fa 100644 --- a/cmd/kubeadm/app/cmd/upgrade/BUILD +++ b/cmd/kubeadm/app/cmd/upgrade/BUILD @@ -13,8 +13,10 @@ go_library( deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library", + "//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library", "//cmd/kubeadm/app/cmd/util:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", + "//cmd/kubeadm/app/features:go_default_library", "//cmd/kubeadm/app/phases/controlplane:go_default_library", "//cmd/kubeadm/app/phases/upgrade:go_default_library", "//cmd/kubeadm/app/preflight:go_default_library", @@ -23,10 +25,11 @@ go_library( "//cmd/kubeadm/app/util/config:go_default_library", "//cmd/kubeadm/app/util/dryrun:go_default_library", "//cmd/kubeadm/app/util/kubeconfig:go_default_library", - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/util/version:go_default_library", "//vendor/github.com/ghodss/yaml:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/client-go/discovery/fake:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", ], @@ -39,8 +42,8 @@ go_test( "common_test.go", "plan_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/cmd/upgrade", - library = ":go_default_library", deps = [ "//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library", "//cmd/kubeadm/app/phases/upgrade:go_default_library", diff --git a/cmd/kubeadm/app/cmd/upgrade/apply.go b/cmd/kubeadm/app/cmd/upgrade/apply.go index 09f82e1e041..08629ef2b78 100644 --- a/cmd/kubeadm/app/cmd/upgrade/apply.go +++ b/cmd/kubeadm/app/cmd/upgrade/apply.go @@ -25,15 +25,17 @@ import ( clientset "k8s.io/client-go/kubernetes" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" "k8s.io/kubernetes/cmd/kubeadm/app/constants" + "k8s.io/kubernetes/cmd/kubeadm/app/features" "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane" "k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/util/version" ) @@ -46,6 +48,7 @@ type applyFlags struct { nonInteractiveMode bool force bool dryRun bool + etcdUpgrade bool newK8sVersionStr string newK8sVersion *version.Version imagePullTimeout time.Duration @@ -62,14 +65,19 @@ func NewCmdApply(parentFlags *cmdUpgradeFlags) *cobra.Command { flags := &applyFlags{ parent: parentFlags, imagePullTimeout: 15 * time.Minute, + etcdUpgrade: true, } cmd := &cobra.Command{ Use: "apply [version]", - Short: "Upgrade your Kubernetes cluster to the specified version", + Short: "Upgrade your Kubernetes cluster to the specified version.", Run: func(cmd *cobra.Command, args []string) { + var err error + flags.parent.ignorePreflightErrorsSet, err = validation.ValidateIgnorePreflightErrors(flags.parent.ignorePreflightErrors, flags.parent.skipPreFlight) + kubeadmutil.CheckErr(err) + // Ensure the user is root - err := runPreflightChecks(flags.parent.skipPreFlight) + err = runPreflightChecks(flags.parent.ignorePreflightErrorsSet) kubeadmutil.CheckErr(err) err = cmdutil.ValidateExactArgNumber(args, []string{"version"}) @@ -90,7 +98,8 @@ func NewCmdApply(parentFlags *cmdUpgradeFlags) *cobra.Command { // Specify the valid flags specific for apply cmd.Flags().BoolVarP(&flags.nonInteractiveMode, "yes", "y", flags.nonInteractiveMode, "Perform the upgrade and do not prompt for confirmation (non-interactive mode).") cmd.Flags().BoolVarP(&flags.force, "force", "f", flags.force, "Force upgrading although some requirements might not be met. This also implies non-interactive mode.") - cmd.Flags().BoolVar(&flags.dryRun, "dry-run", flags.dryRun, "Do not change any state, just output what actions would be applied.") + cmd.Flags().BoolVar(&flags.dryRun, "dry-run", flags.dryRun, "Do not change any state, just output what actions would be performed.") + cmd.Flags().BoolVar(&flags.etcdUpgrade, "etcd-upgrade", flags.etcdUpgrade, "Perform the upgrade of etcd.") cmd.Flags().DurationVar(&flags.imagePullTimeout, "image-pull-timeout", flags.imagePullTimeout, "The maximum amount of time to wait for the control plane pods to be downloaded.") return cmd @@ -105,23 +114,20 @@ func NewCmdApply(parentFlags *cmdUpgradeFlags) *cobra.Command { // - Makes sure the control plane images are available locally on the master(s) // - Upgrades the control plane components // - Applies the other resources that'd be created with kubeadm init as well, like -// - Creating the RBAC rules for the Bootstrap Tokens and the cluster-info ConfigMap +// - Creating the RBAC rules for the bootstrap tokens and the cluster-info ConfigMap // - Applying new kube-dns and kube-proxy manifests // - Uploads the newly used configuration to the cluster ConfigMap func RunApply(flags *applyFlags) error { // Start with the basics, verify that the cluster is healthy and get the configuration from the cluster (using the ConfigMap) - upgradeVars, err := enforceRequirements(flags.parent.kubeConfigPath, flags.parent.cfgPath, flags.parent.printConfig, flags.dryRun) + upgradeVars, err := enforceRequirements(flags.parent, flags.dryRun, flags.newK8sVersionStr) if err != nil { return err } - // Set the upgraded version on the external config object now - upgradeVars.cfg.KubernetesVersion = flags.newK8sVersionStr - // Grab the external, versioned configuration and convert it to the internal type for usage here later internalcfg := &kubeadmapi.MasterConfiguration{} - api.Scheme.Convert(upgradeVars.cfg, internalcfg, nil) + legacyscheme.Scheme.Convert(upgradeVars.cfg, internalcfg, nil) // Validate requested and validate actual version if err := configutil.NormalizeKubernetesVersion(internalcfg); err != nil { @@ -136,6 +142,10 @@ func RunApply(flags *applyFlags) error { } flags.newK8sVersion = k8sVer + if err := features.ValidateVersion(features.InitFeatureGates, internalcfg.FeatureGates, internalcfg.KubernetesVersion); err != nil { + return err + } + // Enforce the version skew policies if err := EnforceVersionPolicies(flags, upgradeVars.versionGetter); err != nil { return fmt.Errorf("[upgrade/version] FATAL: %v", err) @@ -158,8 +168,8 @@ func RunApply(flags *applyFlags) error { return fmt.Errorf("[upgrade/apply] FATAL: %v", err) } - // Upgrade RBAC rules and addons. Optionally, if needed, perform some extra task for a specific version - if err := upgrade.PerformPostUpgradeTasks(upgradeVars.client, internalcfg, flags.newK8sVersion); err != nil { + // Upgrade RBAC rules and addons. + if err := upgrade.PerformPostUpgradeTasks(upgradeVars.client, internalcfg, flags.newK8sVersion, flags.dryRun); err != nil { return fmt.Errorf("[upgrade/postupgrade] FATAL post-upgrade error: %v", err) } @@ -193,22 +203,22 @@ func SetImplicitFlags(flags *applyFlags) error { // EnforceVersionPolicies makes sure that the version the user specified is valid to upgrade to // There are both fatal and skippable (with --force) errors func EnforceVersionPolicies(flags *applyFlags, versionGetter upgrade.VersionGetter) error { - fmt.Printf("[upgrade/version] You have chosen to upgrade to version %q\n", flags.newK8sVersionStr) + fmt.Printf("[upgrade/version] You have chosen to change the cluster version to %q\n", flags.newK8sVersionStr) versionSkewErrs := upgrade.EnforceVersionPolicies(versionGetter, flags.newK8sVersionStr, flags.newK8sVersion, flags.parent.allowExperimentalUpgrades, flags.parent.allowRCUpgrades) if versionSkewErrs != nil { if len(versionSkewErrs.Mandatory) > 0 { - return fmt.Errorf("The --version argument is invalid due to these fatal errors: %v", versionSkewErrs.Mandatory) + return fmt.Errorf("The --version argument is invalid due to these fatal errors:\n\n%v\nPlease fix the misalignments highlighted above and try upgrading again", kubeadmutil.FormatErrMsg(versionSkewErrs.Mandatory)) } if len(versionSkewErrs.Skippable) > 0 { // Return the error if the user hasn't specified the --force flag if !flags.force { - return fmt.Errorf("The --version argument is invalid due to these errors: %v. Can be bypassed if you pass the --force flag", versionSkewErrs.Skippable) + return fmt.Errorf("The --version argument is invalid due to these errors:\n\n%v\nCan be bypassed if you pass the --force flag", kubeadmutil.FormatErrMsg(versionSkewErrs.Skippable)) } // Soft errors found, but --force was specified - fmt.Printf("[upgrade/version] Found %d potential version compatibility errors but skipping since the --force flag is set: %v\n", len(versionSkewErrs.Skippable), versionSkewErrs.Skippable) + fmt.Printf("[upgrade/version] Found %d potential version compatibility errors but skipping since the --force flag is set: \n\n%v", len(versionSkewErrs.Skippable), kubeadmutil.FormatErrMsg(versionSkewErrs.Skippable)) } } return nil @@ -231,20 +241,18 @@ func PerformControlPlaneUpgrade(flags *applyFlags, client clientset.Interface, w if flags.dryRun { return DryRunStaticPodUpgrade(internalcfg) } - return PerformStaticPodUpgrade(client, waiter, internalcfg) + + return PerformStaticPodUpgrade(client, waiter, internalcfg, flags.etcdUpgrade) } // PerformStaticPodUpgrade performs the upgrade of the control plane components for a static pod hosted cluster -func PerformStaticPodUpgrade(client clientset.Interface, waiter apiclient.Waiter, internalcfg *kubeadmapi.MasterConfiguration) error { +func PerformStaticPodUpgrade(client clientset.Interface, waiter apiclient.Waiter, internalcfg *kubeadmapi.MasterConfiguration, etcdUpgrade bool) error { pathManager, err := upgrade.NewKubeStaticPodPathManagerUsingTempDirs(constants.GetStaticPodDirectory()) if err != nil { return err } - if err := upgrade.StaticPodControlPlane(waiter, pathManager, internalcfg); err != nil { - return err - } - return nil + return upgrade.StaticPodControlPlane(waiter, pathManager, internalcfg, etcdUpgrade) } // DryRunStaticPodUpgrade fakes an upgrade of the control plane @@ -268,8 +276,5 @@ func DryRunStaticPodUpgrade(internalcfg *kubeadmapi.MasterConfiguration) error { files = append(files, dryrunutil.NewFileToPrint(realPath, outputPath)) } - if err := dryrunutil.PrintDryRunFiles(files, os.Stdout); err != nil { - return err - } - return nil + return dryrunutil.PrintDryRunFiles(files, os.Stdout) } diff --git a/cmd/kubeadm/app/cmd/upgrade/common.go b/cmd/kubeadm/app/cmd/upgrade/common.go index 4755cc1140a..254c9afbce9 100644 --- a/cmd/kubeadm/app/cmd/upgrade/common.go +++ b/cmd/kubeadm/app/cmd/upgrade/common.go @@ -26,9 +26,11 @@ import ( "github.com/ghodss/yaml" + "k8s.io/apimachinery/pkg/util/sets" fakediscovery "k8s.io/client-go/discovery/fake" clientset "k8s.io/client-go/kubernetes" kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" + "k8s.io/kubernetes/cmd/kubeadm/app/features" "k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade" "k8s.io/kubernetes/cmd/kubeadm/app/preflight" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" @@ -46,28 +48,38 @@ type upgradeVariables struct { } // enforceRequirements verifies that it's okay to upgrade and then returns the variables needed for the rest of the procedure -func enforceRequirements(kubeConfigPath, cfgPath string, printConfig, dryRun bool) (*upgradeVariables, error) { - client, err := getClient(kubeConfigPath, dryRun) +func enforceRequirements(flags *cmdUpgradeFlags, dryRun bool, newK8sVersion string) (*upgradeVariables, error) { + client, err := getClient(flags.kubeConfigPath, dryRun) if err != nil { - return nil, fmt.Errorf("couldn't create a Kubernetes client from file %q: %v", kubeConfigPath, err) + return nil, fmt.Errorf("couldn't create a Kubernetes client from file %q: %v", flags.kubeConfigPath, err) } // Run healthchecks against the cluster - if err := upgrade.CheckClusterHealth(client); err != nil { + if err := upgrade.CheckClusterHealth(client, flags.ignorePreflightErrorsSet); err != nil { return nil, fmt.Errorf("[upgrade/health] FATAL: %v", err) } // Fetch the configuration from a file or ConfigMap and validate it - cfg, err := upgrade.FetchConfiguration(client, os.Stdout, cfgPath) + cfg, err := upgrade.FetchConfiguration(client, os.Stdout, flags.cfgPath) if err != nil { return nil, fmt.Errorf("[upgrade/config] FATAL: %v", err) } + // If a new k8s version should be set, apply the change before printing the config + if len(newK8sVersion) != 0 { + cfg.KubernetesVersion = newK8sVersion + } + // If the user told us to print this information out; do it! - if printConfig { + if flags.printConfig { printConfiguration(cfg, os.Stdout) } + cfg.FeatureGates, err = features.NewFeatureGate(&features.InitFeatureGates, flags.featureGatesString) + if err != nil { + return nil, fmt.Errorf("[upgrade/config] FATAL: %v", err) + } + return &upgradeVariables{ client: client, cfg: cfg, @@ -97,14 +109,9 @@ func printConfiguration(cfg *kubeadmapiext.MasterConfiguration, w io.Writer) { } // runPreflightChecks runs the root preflight check -func runPreflightChecks(skipPreFlight bool) error { - if skipPreFlight { - fmt.Println("[preflight] Skipping pre-flight checks") - return nil - } - - fmt.Println("[preflight] Running pre-flight checks") - return preflight.RunRootCheckOnly() +func runPreflightChecks(ignorePreflightErrors sets.String) error { + fmt.Println("[preflight] Running pre-flight checks.") + return preflight.RunRootCheckOnly(ignorePreflightErrors) } // getClient gets a real or fake client depending on whether the user is dry-running or not diff --git a/cmd/kubeadm/app/cmd/upgrade/common_test.go b/cmd/kubeadm/app/cmd/upgrade/common_test.go index 8e1bcfdf2dd..5ebff084e0a 100644 --- a/cmd/kubeadm/app/cmd/upgrade/common_test.go +++ b/cmd/kubeadm/app/cmd/upgrade/common_test.go @@ -51,6 +51,8 @@ func TestPrintConfiguration(t *testing.T) { image: "" keyFile: "" imageRepository: "" + kubeProxy: {} + kubeletConfiguration: {} kubernetesVersion: v1.7.1 networking: dnsDomain: "" @@ -58,7 +60,6 @@ func TestPrintConfiguration(t *testing.T) { serviceSubnet: "" nodeName: "" token: "" - tokenTTL: 0s unifiedControlPlaneImage: "" `), }, @@ -83,6 +84,8 @@ func TestPrintConfiguration(t *testing.T) { image: "" keyFile: "" imageRepository: "" + kubeProxy: {} + kubeletConfiguration: {} kubernetesVersion: v1.7.1 networking: dnsDomain: "" @@ -90,7 +93,49 @@ func TestPrintConfiguration(t *testing.T) { serviceSubnet: 10.96.0.1/12 nodeName: "" token: "" - tokenTTL: 0s + unifiedControlPlaneImage: "" +`), + }, + { + cfg: &kubeadmapiext.MasterConfiguration{ + KubernetesVersion: "v1.7.1", + Etcd: kubeadmapiext.Etcd{ + SelfHosted: &kubeadmapiext.SelfHostedEtcd{ + CertificatesDir: "/var/foo", + ClusterServiceName: "foo", + EtcdVersion: "v0.1.0", + OperatorVersion: "v0.1.0", + }, + }, + }, + expectedBytes: []byte(`[upgrade/config] Configuration used: + api: + advertiseAddress: "" + bindPort: 0 + certificatesDir: "" + cloudProvider: "" + etcd: + caFile: "" + certFile: "" + dataDir: "" + endpoints: null + image: "" + keyFile: "" + selfHosted: + certificatesDir: /var/foo + clusterServiceName: foo + etcdVersion: v0.1.0 + operatorVersion: v0.1.0 + imageRepository: "" + kubeProxy: {} + kubeletConfiguration: {} + kubernetesVersion: v1.7.1 + networking: + dnsDomain: "" + podSubnet: "" + serviceSubnet: "" + nodeName: "" + token: "" unifiedControlPlaneImage: "" `), }, diff --git a/cmd/kubeadm/app/cmd/upgrade/plan.go b/cmd/kubeadm/app/cmd/upgrade/plan.go index e695565ccf4..09031f81654 100644 --- a/cmd/kubeadm/app/cmd/upgrade/plan.go +++ b/cmd/kubeadm/app/cmd/upgrade/plan.go @@ -25,6 +25,8 @@ import ( "github.com/spf13/cobra" + "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" + "k8s.io/kubernetes/cmd/kubeadm/app/features" "k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" ) @@ -33,10 +35,13 @@ import ( func NewCmdPlan(parentFlags *cmdUpgradeFlags) *cobra.Command { cmd := &cobra.Command{ Use: "plan", - Short: "Check which versions are available to upgrade to and validate whether your current cluster is upgradeable", + Short: "Check which versions are available to upgrade to and validate whether your current cluster is upgradeable.", Run: func(_ *cobra.Command, _ []string) { + var err error + parentFlags.ignorePreflightErrorsSet, err = validation.ValidateIgnorePreflightErrors(parentFlags.ignorePreflightErrors, parentFlags.skipPreFlight) + kubeadmutil.CheckErr(err) // Ensure the user is root - err := runPreflightChecks(parentFlags.skipPreFlight) + err = runPreflightChecks(parentFlags.ignorePreflightErrorsSet) kubeadmutil.CheckErr(err) err = RunPlan(parentFlags) @@ -49,27 +54,29 @@ func NewCmdPlan(parentFlags *cmdUpgradeFlags) *cobra.Command { // RunPlan takes care of outputting available versions to upgrade to for the user func RunPlan(parentFlags *cmdUpgradeFlags) error { - - // Start with the basics, verify that the cluster is healthy, build a client and a versionGetter. Never set dry-run for plan. - upgradeVars, err := enforceRequirements(parentFlags.kubeConfigPath, parentFlags.cfgPath, parentFlags.printConfig, false) + // Start with the basics, verify that the cluster is healthy, build a client and a versionGetter. Never dry-run when planning. + upgradeVars, err := enforceRequirements(parentFlags, false, "") if err != nil { return err } + // Define Local Etcd cluster to be able to retrieve information + etcdCluster := kubeadmutil.LocalEtcdCluster{} + // Compute which upgrade possibilities there are - availUpgrades, err := upgrade.GetAvailableUpgrades(upgradeVars.versionGetter, parentFlags.allowExperimentalUpgrades, parentFlags.allowRCUpgrades) + availUpgrades, err := upgrade.GetAvailableUpgrades(upgradeVars.versionGetter, parentFlags.allowExperimentalUpgrades, parentFlags.allowRCUpgrades, etcdCluster, upgradeVars.cfg.FeatureGates) if err != nil { return fmt.Errorf("[upgrade/versions] FATAL: %v", err) } // Tell the user which upgrades are available - printAvailableUpgrades(availUpgrades, os.Stdout) + printAvailableUpgrades(availUpgrades, os.Stdout, upgradeVars.cfg.FeatureGates) return nil } // printAvailableUpgrades prints a UX-friendly overview of what versions are available to upgrade to // TODO look into columnize or some other formatter when time permits instead of using the tabwriter -func printAvailableUpgrades(upgrades []upgrade.Upgrade, w io.Writer) { +func printAvailableUpgrades(upgrades []upgrade.Upgrade, w io.Writer, featureGates map[string]bool) { // Return quickly if no upgrades can be made if len(upgrades) == 0 { @@ -83,7 +90,7 @@ func printAvailableUpgrades(upgrades []upgrade.Upgrade, w io.Writer) { for _, upgrade := range upgrades { if upgrade.CanUpgradeKubelets() { - fmt.Fprintln(w, "Components that must be upgraded manually after you've upgraded the control plane with 'kubeadm upgrade apply':") + fmt.Fprintln(w, "Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply':") fmt.Fprintln(tabw, "COMPONENT\tCURRENT\tAVAILABLE") firstPrinted := false @@ -111,7 +118,12 @@ func printAvailableUpgrades(upgrades []upgrade.Upgrade, w io.Writer) { fmt.Fprintf(tabw, "Controller Manager\t%s\t%s\n", upgrade.Before.KubeVersion, upgrade.After.KubeVersion) fmt.Fprintf(tabw, "Scheduler\t%s\t%s\n", upgrade.Before.KubeVersion, upgrade.After.KubeVersion) fmt.Fprintf(tabw, "Kube Proxy\t%s\t%s\n", upgrade.Before.KubeVersion, upgrade.After.KubeVersion) - fmt.Fprintf(tabw, "Kube DNS\t%s\t%s\n", upgrade.Before.DNSVersion, upgrade.After.DNSVersion) + if features.Enabled(featureGates, features.CoreDNS) { + fmt.Fprintf(tabw, "CoreDNS\t%s\t%s\n", upgrade.Before.DNSVersion, upgrade.After.DNSVersion) + } else { + fmt.Fprintf(tabw, "Kube DNS\t%s\t%s\n", upgrade.Before.DNSVersion, upgrade.After.DNSVersion) + } + fmt.Fprintf(tabw, "Etcd\t%s\t%s\n", upgrade.Before.EtcdVersion, upgrade.After.EtcdVersion) // The tabwriter should be flushed at this stage as we have now put in all the required content for this time. This is required for the tabs' size to be correct. tabw.Flush() @@ -122,7 +134,7 @@ func printAvailableUpgrades(upgrades []upgrade.Upgrade, w io.Writer) { fmt.Fprintln(w, "") if upgrade.Before.KubeadmVersion != upgrade.After.KubeadmVersion { - fmt.Fprintf(w, "Note: Before you can perform this upgrade, you have to update kubeadm to %s\n", upgrade.After.KubeadmVersion) + fmt.Fprintf(w, "Note: Before you can perform this upgrade, you have to update kubeadm to %s.\n", upgrade.After.KubeadmVersion) fmt.Fprintln(w, "") } diff --git a/cmd/kubeadm/app/cmd/upgrade/plan_test.go b/cmd/kubeadm/app/cmd/upgrade/plan_test.go index 3e0dc30b9f7..75eb8f212aa 100644 --- a/cmd/kubeadm/app/cmd/upgrade/plan_test.go +++ b/cmd/kubeadm/app/cmd/upgrade/plan_test.go @@ -60,6 +60,7 @@ func TestSortedSliceFromStringIntMap(t *testing.T) { // TODO Think about modifying this test to be less verbose checking b/c it can be brittle. func TestPrintAvailableUpgrades(t *testing.T) { + featureGates := make(map[string]bool) var tests = []struct { upgrades []upgrade.Upgrade buf *bytes.Buffer @@ -73,157 +74,169 @@ func TestPrintAvailableUpgrades(t *testing.T) { { upgrades: []upgrade.Upgrade{ { - Description: "version in the v1.7 series", + Description: "version in the v1.8 series", Before: upgrade.ClusterState{ - KubeVersion: "v1.7.1", + KubeVersion: "v1.8.1", KubeletVersions: map[string]uint16{ - "v1.7.1": 1, + "v1.8.1": 1, }, - KubeadmVersion: "v1.7.2", - DNSVersion: "1.14.5", - }, - After: upgrade.ClusterState{ - KubeVersion: "v1.7.3", - KubeadmVersion: "v1.7.3", - DNSVersion: "1.14.5", - }, - }, - }, - expectedBytes: []byte(`Components that must be upgraded manually after you've upgraded the control plane with 'kubeadm upgrade apply': -COMPONENT CURRENT AVAILABLE -Kubelet 1 x v1.7.1 v1.7.3 - -Upgrade to the latest version in the v1.7 series: - -COMPONENT CURRENT AVAILABLE -API Server v1.7.1 v1.7.3 -Controller Manager v1.7.1 v1.7.3 -Scheduler v1.7.1 v1.7.3 -Kube Proxy v1.7.1 v1.7.3 -Kube DNS 1.14.5 1.14.5 - -You can now apply the upgrade by executing the following command: - - kubeadm upgrade apply v1.7.3 - -Note: Before you can perform this upgrade, you have to update kubeadm to v1.7.3 - -_____________________________________________________________________ - -`), - }, - { - upgrades: []upgrade.Upgrade{ - { - Description: "stable version", - Before: upgrade.ClusterState{ - KubeVersion: "v1.7.3", - KubeletVersions: map[string]uint16{ - "v1.7.3": 1, - }, - KubeadmVersion: "v1.8.0", - DNSVersion: "1.14.5", - }, - After: upgrade.ClusterState{ - KubeVersion: "v1.8.0", - KubeadmVersion: "v1.8.0", - DNSVersion: "1.14.5", - }, - }, - }, - expectedBytes: []byte(`Components that must be upgraded manually after you've upgraded the control plane with 'kubeadm upgrade apply': -COMPONENT CURRENT AVAILABLE -Kubelet 1 x v1.7.3 v1.8.0 - -Upgrade to the latest stable version: - -COMPONENT CURRENT AVAILABLE -API Server v1.7.3 v1.8.0 -Controller Manager v1.7.3 v1.8.0 -Scheduler v1.7.3 v1.8.0 -Kube Proxy v1.7.3 v1.8.0 -Kube DNS 1.14.5 1.14.5 - -You can now apply the upgrade by executing the following command: - - kubeadm upgrade apply v1.8.0 - -_____________________________________________________________________ - -`), - }, - { - upgrades: []upgrade.Upgrade{ - { - Description: "version in the v1.7 series", - Before: upgrade.ClusterState{ - KubeVersion: "v1.7.3", - KubeletVersions: map[string]uint16{ - "v1.7.3": 1, - }, - KubeadmVersion: "v1.8.1", - DNSVersion: "1.14.5", - }, - After: upgrade.ClusterState{ - KubeVersion: "v1.7.5", - KubeadmVersion: "v1.8.1", - DNSVersion: "1.14.5", - }, - }, - { - Description: "stable version", - Before: upgrade.ClusterState{ - KubeVersion: "v1.7.3", - KubeletVersions: map[string]uint16{ - "v1.7.3": 1, - }, - KubeadmVersion: "v1.8.1", - DNSVersion: "1.14.5", - }, - After: upgrade.ClusterState{ - KubeVersion: "v1.8.2", KubeadmVersion: "v1.8.2", DNSVersion: "1.14.5", + EtcdVersion: "3.0.17", + }, + After: upgrade.ClusterState{ + KubeVersion: "v1.8.3", + KubeadmVersion: "v1.8.3", + DNSVersion: "1.14.5", + EtcdVersion: "3.0.17", }, }, }, - expectedBytes: []byte(`Components that must be upgraded manually after you've upgraded the control plane with 'kubeadm upgrade apply': + expectedBytes: []byte(`Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply': COMPONENT CURRENT AVAILABLE -Kubelet 1 x v1.7.3 v1.7.5 +Kubelet 1 x v1.8.1 v1.8.3 -Upgrade to the latest version in the v1.7 series: +Upgrade to the latest version in the v1.8 series: COMPONENT CURRENT AVAILABLE -API Server v1.7.3 v1.7.5 -Controller Manager v1.7.3 v1.7.5 -Scheduler v1.7.3 v1.7.5 -Kube Proxy v1.7.3 v1.7.5 +API Server v1.8.1 v1.8.3 +Controller Manager v1.8.1 v1.8.3 +Scheduler v1.8.1 v1.8.3 +Kube Proxy v1.8.1 v1.8.3 Kube DNS 1.14.5 1.14.5 +Etcd 3.0.17 3.0.17 You can now apply the upgrade by executing the following command: - kubeadm upgrade apply v1.7.5 + kubeadm upgrade apply v1.8.3 + +Note: Before you can perform this upgrade, you have to update kubeadm to v1.8.3. _____________________________________________________________________ -Components that must be upgraded manually after you've upgraded the control plane with 'kubeadm upgrade apply': +`), + }, + { + upgrades: []upgrade.Upgrade{ + { + Description: "stable version", + Before: upgrade.ClusterState{ + KubeVersion: "v1.8.3", + KubeletVersions: map[string]uint16{ + "v1.8.3": 1, + }, + KubeadmVersion: "v1.9.0", + DNSVersion: "1.14.5", + EtcdVersion: "3.0.17", + }, + After: upgrade.ClusterState{ + KubeVersion: "v1.9.0", + KubeadmVersion: "v1.9.0", + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", + }, + }, + }, + expectedBytes: []byte(`Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply': COMPONENT CURRENT AVAILABLE -Kubelet 1 x v1.7.3 v1.8.2 +Kubelet 1 x v1.8.3 v1.9.0 Upgrade to the latest stable version: COMPONENT CURRENT AVAILABLE -API Server v1.7.3 v1.8.2 -Controller Manager v1.7.3 v1.8.2 -Scheduler v1.7.3 v1.8.2 -Kube Proxy v1.7.3 v1.8.2 -Kube DNS 1.14.5 1.14.5 +API Server v1.8.3 v1.9.0 +Controller Manager v1.8.3 v1.9.0 +Scheduler v1.8.3 v1.9.0 +Kube Proxy v1.8.3 v1.9.0 +Kube DNS 1.14.5 1.14.7 +Etcd 3.0.17 3.1.10 You can now apply the upgrade by executing the following command: - kubeadm upgrade apply v1.8.2 + kubeadm upgrade apply v1.9.0 -Note: Before you can perform this upgrade, you have to update kubeadm to v1.8.2 +_____________________________________________________________________ + +`), + }, + { + upgrades: []upgrade.Upgrade{ + { + Description: "version in the v1.8 series", + Before: upgrade.ClusterState{ + KubeVersion: "v1.8.3", + KubeletVersions: map[string]uint16{ + "v1.8.3": 1, + }, + KubeadmVersion: "v1.8.3", + DNSVersion: "1.14.5", + EtcdVersion: "3.0.17", + }, + After: upgrade.ClusterState{ + KubeVersion: "v1.8.5", + KubeadmVersion: "v1.8.3", + DNSVersion: "1.14.5", + EtcdVersion: "3.0.17", + }, + }, + { + Description: "stable version", + Before: upgrade.ClusterState{ + KubeVersion: "v1.8.3", + KubeletVersions: map[string]uint16{ + "v1.8.3": 1, + }, + KubeadmVersion: "v1.8.3", + DNSVersion: "1.14.5", + EtcdVersion: "3.0.17", + }, + After: upgrade.ClusterState{ + KubeVersion: "v1.9.0", + KubeadmVersion: "v1.9.0", + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", + }, + }, + }, + expectedBytes: []byte(`Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply': +COMPONENT CURRENT AVAILABLE +Kubelet 1 x v1.8.3 v1.8.5 + +Upgrade to the latest version in the v1.8 series: + +COMPONENT CURRENT AVAILABLE +API Server v1.8.3 v1.8.5 +Controller Manager v1.8.3 v1.8.5 +Scheduler v1.8.3 v1.8.5 +Kube Proxy v1.8.3 v1.8.5 +Kube DNS 1.14.5 1.14.5 +Etcd 3.0.17 3.0.17 + +You can now apply the upgrade by executing the following command: + + kubeadm upgrade apply v1.8.5 + +_____________________________________________________________________ + +Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply': +COMPONENT CURRENT AVAILABLE +Kubelet 1 x v1.8.3 v1.9.0 + +Upgrade to the latest stable version: + +COMPONENT CURRENT AVAILABLE +API Server v1.8.3 v1.9.0 +Controller Manager v1.8.3 v1.9.0 +Scheduler v1.8.3 v1.9.0 +Kube Proxy v1.8.3 v1.9.0 +Kube DNS 1.14.5 1.14.7 +Etcd 3.0.17 3.1.10 + +You can now apply the upgrade by executing the following command: + + kubeadm upgrade apply v1.9.0 + +Note: Before you can perform this upgrade, you have to update kubeadm to v1.9.0. _____________________________________________________________________ @@ -234,38 +247,41 @@ _____________________________________________________________________ { Description: "experimental version", Before: upgrade.ClusterState{ - KubeVersion: "v1.7.5", + KubeVersion: "v1.8.5", KubeletVersions: map[string]uint16{ - "v1.7.5": 1, + "v1.8.5": 1, }, - KubeadmVersion: "v1.7.5", + KubeadmVersion: "v1.8.5", DNSVersion: "1.14.5", + EtcdVersion: "3.0.17", }, After: upgrade.ClusterState{ - KubeVersion: "v1.8.0-beta.1", - KubeadmVersion: "v1.8.0-beta.1", - DNSVersion: "1.14.5", + KubeVersion: "v1.9.0-beta.1", + KubeadmVersion: "v1.9.0-beta.1", + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", }, }, }, - expectedBytes: []byte(`Components that must be upgraded manually after you've upgraded the control plane with 'kubeadm upgrade apply': + expectedBytes: []byte(`Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply': COMPONENT CURRENT AVAILABLE -Kubelet 1 x v1.7.5 v1.8.0-beta.1 +Kubelet 1 x v1.8.5 v1.9.0-beta.1 Upgrade to the latest experimental version: COMPONENT CURRENT AVAILABLE -API Server v1.7.5 v1.8.0-beta.1 -Controller Manager v1.7.5 v1.8.0-beta.1 -Scheduler v1.7.5 v1.8.0-beta.1 -Kube Proxy v1.7.5 v1.8.0-beta.1 -Kube DNS 1.14.5 1.14.5 +API Server v1.8.5 v1.9.0-beta.1 +Controller Manager v1.8.5 v1.9.0-beta.1 +Scheduler v1.8.5 v1.9.0-beta.1 +Kube Proxy v1.8.5 v1.9.0-beta.1 +Kube DNS 1.14.5 1.14.7 +Etcd 3.0.17 3.1.10 You can now apply the upgrade by executing the following command: - kubeadm upgrade apply v1.8.0-beta.1 + kubeadm upgrade apply v1.9.0-beta.1 -Note: Before you can perform this upgrade, you have to update kubeadm to v1.8.0-beta.1 +Note: Before you can perform this upgrade, you have to update kubeadm to v1.9.0-beta.1. _____________________________________________________________________ @@ -276,38 +292,41 @@ _____________________________________________________________________ { Description: "release candidate version", Before: upgrade.ClusterState{ - KubeVersion: "v1.7.5", + KubeVersion: "v1.8.5", KubeletVersions: map[string]uint16{ - "v1.7.5": 1, + "v1.8.5": 1, }, - KubeadmVersion: "v1.7.5", + KubeadmVersion: "v1.8.5", DNSVersion: "1.14.5", + EtcdVersion: "3.0.17", }, After: upgrade.ClusterState{ - KubeVersion: "v1.8.0-rc.1", - KubeadmVersion: "v1.8.0-rc.1", - DNSVersion: "1.14.5", + KubeVersion: "v1.9.0-rc.1", + KubeadmVersion: "v1.9.0-rc.1", + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", }, }, }, - expectedBytes: []byte(`Components that must be upgraded manually after you've upgraded the control plane with 'kubeadm upgrade apply': + expectedBytes: []byte(`Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply': COMPONENT CURRENT AVAILABLE -Kubelet 1 x v1.7.5 v1.8.0-rc.1 +Kubelet 1 x v1.8.5 v1.9.0-rc.1 Upgrade to the latest release candidate version: COMPONENT CURRENT AVAILABLE -API Server v1.7.5 v1.8.0-rc.1 -Controller Manager v1.7.5 v1.8.0-rc.1 -Scheduler v1.7.5 v1.8.0-rc.1 -Kube Proxy v1.7.5 v1.8.0-rc.1 -Kube DNS 1.14.5 1.14.5 +API Server v1.8.5 v1.9.0-rc.1 +Controller Manager v1.8.5 v1.9.0-rc.1 +Scheduler v1.8.5 v1.9.0-rc.1 +Kube Proxy v1.8.5 v1.9.0-rc.1 +Kube DNS 1.14.5 1.14.7 +Etcd 3.0.17 3.1.10 You can now apply the upgrade by executing the following command: - kubeadm upgrade apply v1.8.0-rc.1 + kubeadm upgrade apply v1.9.0-rc.1 -Note: Before you can perform this upgrade, you have to update kubeadm to v1.8.0-rc.1 +Note: Before you can perform this upgrade, you have to update kubeadm to v1.9.0-rc.1. _____________________________________________________________________ @@ -316,7 +335,7 @@ _____________________________________________________________________ } for _, rt := range tests { rt.buf = bytes.NewBufferString("") - printAvailableUpgrades(rt.upgrades, rt.buf) + printAvailableUpgrades(rt.upgrades, rt.buf, featureGates) actualBytes := rt.buf.Bytes() if !bytes.Equal(actualBytes, rt.expectedBytes) { t.Errorf( diff --git a/cmd/kubeadm/app/cmd/upgrade/upgrade.go b/cmd/kubeadm/app/cmd/upgrade/upgrade.go index 9f9b14f08ff..a78a3dad01b 100644 --- a/cmd/kubeadm/app/cmd/upgrade/upgrade.go +++ b/cmd/kubeadm/app/cmd/upgrade/upgrade.go @@ -18,19 +18,25 @@ package upgrade import ( "io" + "strings" "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/util/sets" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" + "k8s.io/kubernetes/cmd/kubeadm/app/features" ) // cmdUpgradeFlags holds the values for the common flags in `kubeadm upgrade` type cmdUpgradeFlags struct { kubeConfigPath string cfgPath string + featureGatesString string allowExperimentalUpgrades bool allowRCUpgrades bool printConfig bool skipPreFlight bool + ignorePreflightErrors []string + ignorePreflightErrorsSet sets.String } // NewCmdUpgrade returns the cobra command for `kubeadm upgrade` @@ -38,10 +44,12 @@ func NewCmdUpgrade(out io.Writer) *cobra.Command { flags := &cmdUpgradeFlags{ kubeConfigPath: "/etc/kubernetes/admin.conf", cfgPath: "", + featureGatesString: "", allowExperimentalUpgrades: false, allowRCUpgrades: false, printConfig: false, skipPreFlight: false, + ignorePreflightErrorsSet: sets.NewString(), } cmd := &cobra.Command{ @@ -50,12 +58,16 @@ func NewCmdUpgrade(out io.Writer) *cobra.Command { RunE: cmdutil.SubCmdRunE("upgrade"), } - cmd.PersistentFlags().StringVar(&flags.kubeConfigPath, "kubeconfig", flags.kubeConfigPath, "The KubeConfig file to use for talking to the cluster.") - cmd.PersistentFlags().StringVar(&flags.cfgPath, "config", flags.cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental).") + cmd.PersistentFlags().StringVar(&flags.kubeConfigPath, "kubeconfig", flags.kubeConfigPath, "The KubeConfig file to use when talking to the cluster.") + cmd.PersistentFlags().StringVar(&flags.cfgPath, "config", flags.cfgPath, "Path to kubeadm config file. WARNING: Usage of a configuration file is experimental!") cmd.PersistentFlags().BoolVar(&flags.allowExperimentalUpgrades, "allow-experimental-upgrades", flags.allowExperimentalUpgrades, "Show unstable versions of Kubernetes as an upgrade alternative and allow upgrading to an alpha/beta/release candidate versions of Kubernetes.") cmd.PersistentFlags().BoolVar(&flags.allowRCUpgrades, "allow-release-candidate-upgrades", flags.allowRCUpgrades, "Show release candidate versions of Kubernetes as an upgrade alternative and allow upgrading to a release candidate versions of Kubernetes.") - cmd.PersistentFlags().BoolVar(&flags.printConfig, "print-config", flags.printConfig, "Whether the configuration file that will be used in the upgrade should be printed or not.") - cmd.PersistentFlags().BoolVar(&flags.skipPreFlight, "skip-preflight-checks", flags.skipPreFlight, "Skip preflight checks normally run before modifying the system") + cmd.PersistentFlags().BoolVar(&flags.printConfig, "print-config", flags.printConfig, "Specifies whether the configuration file that will be used in the upgrade should be printed or not.") + cmd.PersistentFlags().StringSliceVar(&flags.ignorePreflightErrors, "ignore-preflight-errors", flags.ignorePreflightErrors, "A list of checks whose errors will be shown as warnings. Example: 'IsPrivilegedUser,Swap'. Value 'all' ignores errors from all checks.") + cmd.PersistentFlags().BoolVar(&flags.skipPreFlight, "skip-preflight-checks", flags.skipPreFlight, "Skip preflight checks that normally run before modifying the system.") + cmd.PersistentFlags().MarkDeprecated("skip-preflight-checks", "it is now equivalent to --ignore-preflight-errors=all") + cmd.PersistentFlags().StringVar(&flags.featureGatesString, "feature-gates", flags.featureGatesString, "A set of key=value pairs that describe feature gates for various features."+ + "Options are:\n"+strings.Join(features.KnownFeatures(&features.InitFeatureGates), "\n")) cmd.AddCommand(NewCmdApply(flags)) cmd.AddCommand(NewCmdPlan(flags)) diff --git a/cmd/kubeadm/app/cmd/util/BUILD b/cmd/kubeadm/app/cmd/util/BUILD index 8478951086f..951960a273a 100644 --- a/cmd/kubeadm/app/cmd/util/BUILD +++ b/cmd/kubeadm/app/cmd/util/BUILD @@ -2,17 +2,23 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", - srcs = ["cmdutil.go"], + srcs = [ + "cmdutil.go", + "documentation.go", + ], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util", visibility = ["//visibility:public"], - deps = ["//vendor/github.com/spf13/cobra:go_default_library"], + deps = [ + "//pkg/util/normalizer:go_default_library", + "//vendor/github.com/spf13/cobra:go_default_library", + ], ) go_test( name = "go_default_test", srcs = ["cmdutil_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util", - library = ":go_default_library", ) filegroup( diff --git a/cmd/kubeadm/app/cmd/util/cmdutil.go b/cmd/kubeadm/app/cmd/util/cmdutil.go index 3c0e7d65b10..87dcdff67f0 100644 --- a/cmd/kubeadm/app/cmd/util/cmdutil.go +++ b/cmd/kubeadm/app/cmd/util/cmdutil.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package phases +package util import ( "fmt" @@ -39,19 +39,21 @@ func SubCmdRunE(name string) func(*cobra.Command, []string) error { // ValidateExactArgNumber validates that the required top-level arguments are specified func ValidateExactArgNumber(args []string, supportedArgs []string) error { + lenSupported := len(supportedArgs) validArgs := 0 // Disregard possible "" arguments; they are invalid for _, arg := range args { if len(arg) > 0 { validArgs++ } + // break early for too many arguments + if validArgs > lenSupported { + return fmt.Errorf("too many arguments. Required arguments: %v", supportedArgs) + } } - if validArgs < len(supportedArgs) { + if validArgs < lenSupported { return fmt.Errorf("missing one or more required arguments. Required arguments: %v", supportedArgs) } - if validArgs > len(supportedArgs) { - return fmt.Errorf("too many arguments, only %d argument(s) supported: %v", validArgs, supportedArgs) - } return nil } diff --git a/cmd/kubeadm/app/cmd/util/cmdutil_test.go b/cmd/kubeadm/app/cmd/util/cmdutil_test.go index ef4d81ce009..40fcf87dfbd 100644 --- a/cmd/kubeadm/app/cmd/util/cmdutil_test.go +++ b/cmd/kubeadm/app/cmd/util/cmdutil_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package phases +package util import ( "testing" diff --git a/cmd/kubeadm/app/cmd/util/documentation.go b/cmd/kubeadm/app/cmd/util/documentation.go new file mode 100644 index 00000000000..4867bb4aee8 --- /dev/null +++ b/cmd/kubeadm/app/cmd/util/documentation.go @@ -0,0 +1,33 @@ +/* +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 util + +import ( + "k8s.io/kubernetes/pkg/util/normalizer" +) + +var ( + // AlphaDisclaimer to be places at the end of description of commands in alpha release + AlphaDisclaimer = ` + Alpha Disclaimer: this command is currently alpha. + ` + + // MacroCommandLongDescription provide a standard description for "macro" commands + MacroCommandLongDescription = normalizer.LongDesc(` + This command is not meant to be run on its own. See list of available subcommands. + `) +) diff --git a/cmd/kubeadm/app/constants/BUILD b/cmd/kubeadm/app/constants/BUILD index 89204d405e8..d0f284c5ebb 100644 --- a/cmd/kubeadm/app/constants/BUILD +++ b/cmd/kubeadm/app/constants/BUILD @@ -11,6 +11,7 @@ go_library( srcs = ["constants.go"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/constants", deps = [ + "//pkg/registry/core/service/ipallocator:go_default_library", "//pkg/util/version:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", ], @@ -32,6 +33,7 @@ filegroup( go_test( name = "go_default_test", srcs = ["constants_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/constants", - library = ":go_default_library", + deps = ["//pkg/util/version:go_default_library"], ) diff --git a/cmd/kubeadm/app/constants/constants.go b/cmd/kubeadm/app/constants/constants.go index 4bdf31e974f..36584da4add 100644 --- a/cmd/kubeadm/app/constants/constants.go +++ b/cmd/kubeadm/app/constants/constants.go @@ -19,11 +19,13 @@ package constants import ( "fmt" "io/ioutil" + "net" "os" "path/filepath" "time" "k8s.io/api/core/v1" + "k8s.io/kubernetes/pkg/registry/core/service/ipallocator" "k8s.io/kubernetes/pkg/util/version" ) @@ -73,7 +75,7 @@ const ( FrontProxyCACertAndKeyBaseName = "front-proxy-ca" // FrontProxyCACertName defines front proxy CA certificate name FrontProxyCACertName = "front-proxy-ca.crt" - // FrontProxyCAKeyName defaines front proxy CA key name + // FrontProxyCAKeyName defines front proxy CA key name FrontProxyCAKeyName = "front-proxy-ca.key" // FrontProxyClientCertAndKeyBaseName defines front proxy certificate and key base name @@ -115,12 +117,17 @@ const ( // system:nodes group subject is removed if present. NodesClusterRoleBinding = "system:node" + // KubeletBaseConfigMapRoleName defines the base kubelet configuration ConfigMap. + KubeletBaseConfigMapRoleName = "kubeadm:kubelet-base-configmap" + // APICallRetryInterval defines how long kubeadm should wait before retrying a failed API operation APICallRetryInterval = 500 * time.Millisecond // DiscoveryRetryInterval specifies how long kubeadm should wait before retrying to connect to the master when doing discovery DiscoveryRetryInterval = 5 * time.Second // MarkMasterTimeout specifies how long kubeadm should wait for applying the label and taint on the master before timing out MarkMasterTimeout = 2 * time.Minute + // UpdateNodeTimeout specifies how long kubeadm should wait for updating node with the initial remote configuration of kubelet before timing out + UpdateNodeTimeout = 2 * time.Minute // MinimumAddressesInServiceSubnet defines minimum amount of nodes the Service subnet should allow. // We need at least ten, because the DNS service is always at the tenth cluster clusterIP @@ -140,6 +147,22 @@ const ( // MasterConfigurationConfigMapKey specifies in what ConfigMap key the master configuration should be stored MasterConfigurationConfigMapKey = "MasterConfiguration" + // KubeletBaseConfigurationConfigMap specifies in what ConfigMap in the kube-system namespace the initial remote configuration of kubelet should be stored + KubeletBaseConfigurationConfigMap = "kubelet-base-config-1.9" + + // KubeletBaseConfigurationConfigMapKey specifies in what ConfigMap key the initial remote configuration of kubelet should be stored + // TODO: Use the constant ("kubelet.config.k8s.io") defined in pkg/kubelet/kubeletconfig/util/keys/keys.go + // after https://github.com/kubernetes/kubernetes/pull/53833 being merged. + KubeletBaseConfigurationConfigMapKey = "kubelet" + + // KubeletBaseConfigurationDir specifies the directory on the node where stores the initial remote configuration of kubelet + KubeletBaseConfigurationDir = "/var/lib/kubelet/config/init" + + // KubeletBaseConfigurationFile specifies the file name on the node which stores initial remote configuration of kubelet + // TODO: Use the constant ("kubelet.config.k8s.io") defined in pkg/kubelet/kubeletconfig/util/keys/keys.go + // after https://github.com/kubernetes/kubernetes/pull/53833 being merged. + KubeletBaseConfigurationFile = "kubelet" + // MinExternalEtcdVersion indicates minimum external etcd version which kubeadm supports MinExternalEtcdVersion = "3.0.14" @@ -171,10 +194,14 @@ const ( // DefaultCIImageRepository points to image registry where CI uploads images from ci-cross build job DefaultCIImageRepository = "gcr.io/kubernetes-ci-images" + + // CoreDNS defines a variable used internally when referring to the CoreDNS addon for a cluster + CoreDNS = "coredns" + // KubeDNS defines a variable used internally when referring to the kube-dns addon for a cluster + KubeDNS = "kube-dns" ) var ( - // MasterTaint is the taint to apply on the PodSpec for being able to run that Pod on the master MasterTaint = v1.Taint{ Key: LabelNodeRoleMaster, @@ -199,16 +226,38 @@ var ( MasterComponents = []string{KubeAPIServer, KubeControllerManager, KubeScheduler} // MinimumControlPlaneVersion specifies the minimum control plane version kubeadm can deploy - MinimumControlPlaneVersion = version.MustParseSemantic("v1.8.0") - - // MinimumCSRAutoApprovalClusterRolesVersion defines whether kubeadm can rely on the built-in CSR approval ClusterRole or not (note, the binding is always created by kubeadm!) - // TODO: Remove this when the v1.9 cycle starts and we bump the minimum supported version to v1.8.0 - MinimumCSRAutoApprovalClusterRolesVersion = version.MustParseSemantic("v1.8.0-alpha.3") + MinimumControlPlaneVersion = version.MustParseSemantic("v1.9.0") // MinimumKubeletVersion specifies the minimum version of kubelet which kubeadm supports - MinimumKubeletVersion = version.MustParseSemantic("v1.8.0") + MinimumKubeletVersion = version.MustParseSemantic("v1.9.0") + + // SupportedEtcdVersion lists officially supported etcd versions with corresponding kubernetes releases + SupportedEtcdVersion = map[uint8]string{ + 8: "3.0.17", + 9: "3.1.10", + 10: "3.1.10", + 11: "3.1.10", + } ) +// EtcdSupportedVersion returns officially supported version of etcd for a specific kubernetes release +// if passed version is not listed, the function returns nil and an error +func EtcdSupportedVersion(versionString string) (*version.Version, error) { + kubernetesVersion, err := version.ParseSemantic(versionString) + if err != nil { + return nil, err + } + + if etcdStringVersion, ok := SupportedEtcdVersion[uint8(kubernetesVersion.Minor())]; ok { + etcdVersion, err := version.ParseSemantic(etcdStringVersion) + if err != nil { + return nil, err + } + return etcdVersion, nil + } + return nil, fmt.Errorf("Unsupported or unknown kubernetes version") +} + // GetStaticPodDirectory returns the location on the disk where the Static Pod should be present func GetStaticPodDirectory() string { return filepath.Join(KubernetesDir, ManifestsSubDirName) @@ -242,3 +291,20 @@ func CreateTempDirForKubeadm(dirName string) (string, error) { } return tempDir, nil } + +// GetDNSIP returns a dnsIP, which is 10th IP in svcSubnet CIDR range +func GetDNSIP(svcSubnet string) (net.IP, error) { + // Get the service subnet CIDR + _, svcSubnetCIDR, err := net.ParseCIDR(svcSubnet) + if err != nil { + return nil, fmt.Errorf("couldn't parse service subnet CIDR %q: %v", svcSubnet, err) + } + + // Selects the 10th IP in service subnet CIDR range as dnsIP + dnsIP, err := ipallocator.GetIndexedIP(svcSubnetCIDR, 10) + if err != nil { + return nil, fmt.Errorf("unable to get tenth IP address from service subnet CIDR %s: %v", svcSubnetCIDR.String(), err) + } + + return dnsIP, nil +} diff --git a/cmd/kubeadm/app/constants/constants_test.go b/cmd/kubeadm/app/constants/constants_test.go index 29cffa2abbd..8ab5daf7095 100644 --- a/cmd/kubeadm/app/constants/constants_test.go +++ b/cmd/kubeadm/app/constants/constants_test.go @@ -17,6 +17,9 @@ limitations under the License. package constants import ( + "fmt" + "k8s.io/kubernetes/pkg/util/version" + "strings" "testing" ) @@ -110,3 +113,58 @@ func TestAddSelfHostedPrefix(t *testing.T) { } } } + +func TestEtcdSupportedVersion(t *testing.T) { + var tests = []struct { + kubernetesVersion string + expectedVersion *version.Version + expectedError error + }{ + { + kubernetesVersion: "1.8.0", + expectedVersion: version.MustParseSemantic("3.0.17"), + expectedError: nil, + }, + { + kubernetesVersion: "1.80.0", + expectedVersion: nil, + expectedError: fmt.Errorf("Unsupported or unknown kubernetes version"), + }, + { + kubernetesVersion: "1.9.0", + expectedVersion: version.MustParseSemantic("3.1.10"), + expectedError: nil, + }, + { + kubernetesVersion: "1.10.0", + expectedVersion: version.MustParseSemantic("3.1.10"), + expectedError: nil, + }, + { + kubernetesVersion: "1.8.6", + expectedVersion: version.MustParseSemantic("3.0.17"), + expectedError: nil, + }, + } + for _, rt := range tests { + actualVersion, actualError := EtcdSupportedVersion(rt.kubernetesVersion) + if actualError != nil { + if actualError.Error() != rt.expectedError.Error() { + t.Errorf( + "failed EtcdSupportedVersion:\n\texpected error: %v\n\t actual error: %v", + rt.expectedError, + actualError, + ) + } + + } else { + if strings.Compare(actualVersion.String(), rt.expectedVersion.String()) != 0 { + t.Errorf( + "failed EtcdSupportedVersion:\n\texpected version: %s\n\t actual version: %s", + rt.expectedVersion.String(), + actualVersion.String(), + ) + } + } + } +} diff --git a/cmd/kubeadm/app/discovery/BUILD b/cmd/kubeadm/app/discovery/BUILD index 3c3f591ba14..14fd0f3f4f6 100644 --- a/cmd/kubeadm/app/discovery/BUILD +++ b/cmd/kubeadm/app/discovery/BUILD @@ -23,8 +23,8 @@ go_library( go_test( name = "go_default_test", srcs = ["discovery_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/discovery", - library = ":go_default_library", deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"], ) diff --git a/cmd/kubeadm/app/discovery/file/file.go b/cmd/kubeadm/app/discovery/file/file.go index 5dc0188e37c..60d3d48d08b 100644 --- a/cmd/kubeadm/app/discovery/file/file.go +++ b/cmd/kubeadm/app/discovery/file/file.go @@ -75,7 +75,7 @@ func ValidateClusterInfo(clusterinfo *clientcmdapi.Config) (*clientcmdapi.Cluste clusterinfoCM, err = client.CoreV1().ConfigMaps(metav1.NamespacePublic).Get(bootstrapapi.ConfigMapClusterInfo, metav1.GetOptions{}) if err != nil { if apierrors.IsForbidden(err) { - // If the request is unauthorized, the cluster admin has not granted access to the cluster info configmap for unauthenicated users + // If the request is unauthorized, the cluster admin has not granted access to the cluster info configmap for unauthenticated users // In that case, trust the cluster admin and do not refresh the cluster-info credentials fmt.Printf("[discovery] Could not access the %s ConfigMap for refreshing the cluster-info information, but the TLS cert is valid so proceeding...\n", bootstrapapi.ConfigMapClusterInfo) return true, nil diff --git a/cmd/kubeadm/app/discovery/token/BUILD b/cmd/kubeadm/app/discovery/token/BUILD index fe59263de60..cb4de3f600f 100644 --- a/cmd/kubeadm/app/discovery/token/BUILD +++ b/cmd/kubeadm/app/discovery/token/BUILD @@ -41,8 +41,8 @@ filegroup( go_test( name = "go_default_test", srcs = ["token_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/discovery/token", - library = ":go_default_library", deps = [ "//cmd/kubeadm/app/util/kubeconfig:go_default_library", "//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library", diff --git a/cmd/kubeadm/app/features/BUILD b/cmd/kubeadm/app/features/BUILD index 147dac3bc69..8808be9d42b 100644 --- a/cmd/kubeadm/app/features/BUILD +++ b/cmd/kubeadm/app/features/BUILD @@ -10,7 +10,10 @@ go_library( name = "go_default_library", srcs = ["features.go"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/features", - deps = ["//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library"], + deps = [ + "//pkg/util/version:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + ], ) filegroup( @@ -29,7 +32,7 @@ filegroup( go_test( name = "go_default_test", srcs = ["features_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/features", - library = ":go_default_library", deps = ["//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library"], ) diff --git a/cmd/kubeadm/app/features/features.go b/cmd/kubeadm/app/features/features.go index faab302eb6a..57627cb367b 100644 --- a/cmd/kubeadm/app/features/features.go +++ b/cmd/kubeadm/app/features/features.go @@ -23,21 +23,71 @@ import ( "strings" utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/util/version" ) const ( - // SelfHosting is beta in v1.8 - SelfHosting utilfeature.Feature = "SelfHosting" + // HighAvailability is alpha in v1.9 + HighAvailability = "HighAvailability" - // StoreCertsInSecrets is alpha in v1.8 - StoreCertsInSecrets utilfeature.Feature = "StoreCertsInSecrets" + // CoreDNS is alpha in v1.9 + CoreDNS = "CoreDNS" + + // SelfHosting is alpha in v1.8 and v1.9 + SelfHosting = "SelfHosting" + + // StoreCertsInSecrets is alpha in v1.8 and v1.9 + StoreCertsInSecrets = "StoreCertsInSecrets" + + // DynamicKubeletConfig is alpha in v1.9 + DynamicKubeletConfig = "DynamicKubeletConfig" ) +var v190 = version.MustParseSemantic("v1.9.0-alpha.1") + +// InitFeatureGates are the default feature gates for the init command +var InitFeatureGates = FeatureList{ + SelfHosting: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}}, + StoreCertsInSecrets: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}}, + // We don't want to advertise this feature gate exists in v1.9 to avoid confusion as it is not yet working + HighAvailability: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}, MinimumVersion: v190, HiddenInHelpText: true}, + CoreDNS: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}, MinimumVersion: v190}, + DynamicKubeletConfig: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}, MinimumVersion: v190}, +} + +// Feature represents a feature being gated +type Feature struct { + utilfeature.FeatureSpec + MinimumVersion *version.Version + HiddenInHelpText bool +} + // FeatureList represents a list of feature gates -type FeatureList map[utilfeature.Feature]utilfeature.FeatureSpec +type FeatureList map[string]Feature + +// ValidateVersion ensures that a feature gate list is compatible with the chosen kubernetes version +func ValidateVersion(allFeatures FeatureList, requestedFeatures map[string]bool, requestedVersion string) error { + if requestedVersion == "" { + return nil + } + parsedExpVersion, err := version.ParseSemantic(requestedVersion) + if err != nil { + return fmt.Errorf("Error parsing version %s: %v", requestedVersion, err) + } + for k := range requestedFeatures { + if minVersion := allFeatures[k].MinimumVersion; minVersion != nil { + if !parsedExpVersion.AtLeast(minVersion) { + return fmt.Errorf( + "the requested kubernetes version (%s) is incompatible with the %s feature gate, which needs %s as a minimum", + requestedVersion, k, minVersion) + } + } + } + return nil +} // Enabled indicates whether a feature name has been enabled -func Enabled(featureList map[string]bool, featureName utilfeature.Feature) bool { +func Enabled(featureList map[string]bool, featureName string) bool { return featureList[string(featureName)] } @@ -61,16 +111,14 @@ func Keys(featureList FeatureList) []string { return list } -// InitFeatureGates are the default feature gates for the init command -var InitFeatureGates = FeatureList{ - SelfHosting: {Default: false, PreRelease: utilfeature.Alpha}, - StoreCertsInSecrets: {Default: false, PreRelease: utilfeature.Alpha}, -} - // KnownFeatures returns a slice of strings describing the FeatureList features. func KnownFeatures(f *FeatureList) []string { var known []string for k, v := range *f { + if v.HiddenInHelpText { + continue + } + pre := "" if v.PreRelease != utilfeature.GA { pre = fmt.Sprintf("%s - ", v.PreRelease) @@ -81,7 +129,7 @@ func KnownFeatures(f *FeatureList) []string { return known } -// NewFeatureGate parse a string of the form "key1=value1,key2=value2,..." into a +// NewFeatureGate parses a string of the form "key1=value1,key2=value2,..." into a // map[string]bool of known keys or returns an error. func NewFeatureGate(f *FeatureList, value string) (map[string]bool, error) { featureGate := map[string]bool{} @@ -109,5 +157,22 @@ func NewFeatureGate(f *FeatureList, value string) (map[string]bool, error) { featureGate[k] = boolValue } + ResolveFeatureGateDependencies(featureGate) + return featureGate, nil } + +// ResolveFeatureGateDependencies resolve dependencies between feature gates +func ResolveFeatureGateDependencies(featureGate map[string]bool) { + + // if StoreCertsInSecrets enabled, SelfHosting should enabled + if Enabled(featureGate, StoreCertsInSecrets) { + featureGate[SelfHosting] = true + } + + // if HighAvailability enabled, both StoreCertsInSecrets and SelfHosting should enabled + if Enabled(featureGate, HighAvailability) && !Enabled(featureGate, StoreCertsInSecrets) { + featureGate[SelfHosting] = true + featureGate[StoreCertsInSecrets] = true + } +} diff --git a/cmd/kubeadm/app/features/features_test.go b/cmd/kubeadm/app/features/features_test.go index 486f10f84fc..1d64da4046e 100644 --- a/cmd/kubeadm/app/features/features_test.go +++ b/cmd/kubeadm/app/features/features_test.go @@ -25,9 +25,9 @@ import ( func TestKnownFeatures(t *testing.T) { var someFeatures = FeatureList{ - "feature2": {Default: true, PreRelease: utilfeature.Alpha}, - "feature1": {Default: false, PreRelease: utilfeature.Beta}, - "feature3": {Default: false, PreRelease: utilfeature.GA}, + "feature2": {FeatureSpec: utilfeature.FeatureSpec{Default: true, PreRelease: utilfeature.Alpha}}, + "feature1": {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Beta}}, + "feature3": {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.GA}}, } r := KnownFeatures(&someFeatures) @@ -46,7 +46,7 @@ func TestKnownFeatures(t *testing.T) { if r[1] != f2 { t.Errorf("KnownFeatures returned %s values, expected %s", r[1], f2) } - // check the second value is feature3; prerelease should not shown fo GA features; default should be present + // check the second value is feature3; prerelease should not be shown for GA features; default should be present f3 := "feature3=true|false (default=false)" if r[2] != f3 { t.Errorf("KnownFeatures returned %s values, expected %s", r[2], f3) @@ -55,8 +55,8 @@ func TestKnownFeatures(t *testing.T) { func TestNewFeatureGate(t *testing.T) { var someFeatures = FeatureList{ - "feature1": {Default: false, PreRelease: utilfeature.Beta}, - "feature2": {Default: true, PreRelease: utilfeature.Alpha}, + "feature1": {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Beta}}, + "feature2": {FeatureSpec: utilfeature.FeatureSpec{Default: true, PreRelease: utilfeature.Alpha}}, } var tests = []struct { @@ -117,3 +117,75 @@ func TestNewFeatureGate(t *testing.T) { } } } + +func TestValidateVersion(t *testing.T) { + var someFeatures = FeatureList{ + "feature1": {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Beta}}, + "feature2": {FeatureSpec: utilfeature.FeatureSpec{Default: true, PreRelease: utilfeature.Alpha}, MinimumVersion: v190}, + } + + var tests = []struct { + requestedVersion string + requestedFeatures map[string]bool + expectedError bool + }{ + { //no min version + requestedFeatures: map[string]bool{"feature1": true}, + expectedError: false, + }, + { //min version but correct value given + requestedFeatures: map[string]bool{"feature2": true}, + requestedVersion: "v1.9.0", + expectedError: false, + }, + { //min version and incorrect value given + requestedFeatures: map[string]bool{"feature2": true}, + requestedVersion: "v1.8.2", + expectedError: true, + }, + } + + for _, test := range tests { + err := ValidateVersion(someFeatures, test.requestedFeatures, test.requestedVersion) + if !test.expectedError && err != nil { + t.Errorf("ValidateVersion failed when not expected: %v", err) + continue + } else if test.expectedError && err == nil { + t.Error("ValidateVersion didn't failed when expected") + continue + } + } +} + +func TestResolveFeatureGateDependencies(t *testing.T) { + + var tests = []struct { + inputFeatures map[string]bool + expectedFeatures map[string]bool + }{ + { // no flags + inputFeatures: map[string]bool{}, + expectedFeatures: map[string]bool{}, + }, + { // others flags + inputFeatures: map[string]bool{CoreDNS: true}, + expectedFeatures: map[string]bool{CoreDNS: true}, + }, + { // just StoreCertsInSecrets flags + inputFeatures: map[string]bool{StoreCertsInSecrets: true}, + expectedFeatures: map[string]bool{StoreCertsInSecrets: true, SelfHosting: true}, + }, + { // just HighAvailability flags + inputFeatures: map[string]bool{HighAvailability: true}, + expectedFeatures: map[string]bool{HighAvailability: true, StoreCertsInSecrets: true, SelfHosting: true}, + }, + } + + for _, test := range tests { + ResolveFeatureGateDependencies(test.inputFeatures) + if !reflect.DeepEqual(test.inputFeatures, test.expectedFeatures) { + t.Errorf("ResolveFeatureGateDependencies failed, expected: %v, got: %v", test.inputFeatures, test.expectedFeatures) + + } + } +} diff --git a/cmd/kubeadm/app/images/BUILD b/cmd/kubeadm/app/images/BUILD index 05c83ab87fa..51d7334530d 100644 --- a/cmd/kubeadm/app/images/BUILD +++ b/cmd/kubeadm/app/images/BUILD @@ -19,8 +19,8 @@ go_library( go_test( name = "go_default_test", srcs = ["images_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/images", - library = ":go_default_library", deps = ["//cmd/kubeadm/app/constants:go_default_library"], ) diff --git a/cmd/kubeadm/app/images/images.go b/cmd/kubeadm/app/images/images.go index 61a9c473817..ef6ceb7eed5 100644 --- a/cmd/kubeadm/app/images/images.go +++ b/cmd/kubeadm/app/images/images.go @@ -30,8 +30,13 @@ func GetCoreImage(image, repoPrefix, k8sVersion, overrideImage string) string { return overrideImage } kubernetesImageTag := kubeadmutil.KubernetesVersionToImageTag(k8sVersion) + etcdImageTag := constants.DefaultEtcdVersion + etcdImageVersion, err := constants.EtcdSupportedVersion(k8sVersion) + if err == nil { + etcdImageTag = etcdImageVersion.String() + } return map[string]string{ - constants.Etcd: fmt.Sprintf("%s/%s-%s:%s", repoPrefix, "etcd", runtime.GOARCH, constants.DefaultEtcdVersion), + constants.Etcd: fmt.Sprintf("%s/%s-%s:%s", repoPrefix, "etcd", runtime.GOARCH, etcdImageTag), constants.KubeAPIServer: fmt.Sprintf("%s/%s-%s:%s", repoPrefix, "kube-apiserver", runtime.GOARCH, kubernetesImageTag), constants.KubeControllerManager: fmt.Sprintf("%s/%s-%s:%s", repoPrefix, "kube-controller-manager", runtime.GOARCH, kubernetesImageTag), constants.KubeScheduler: fmt.Sprintf("%s/%s-%s:%s", repoPrefix, "kube-scheduler", runtime.GOARCH, kubernetesImageTag), diff --git a/cmd/kubeadm/app/kubeadm.go b/cmd/kubeadm/app/kubeadm.go index 68ea1973b36..ccf69ee8d92 100644 --- a/cmd/kubeadm/app/kubeadm.go +++ b/cmd/kubeadm/app/kubeadm.go @@ -28,6 +28,7 @@ import ( // Run creates and executes new kubeadm command func Run() error { // We do not want these flags to show up in --help + pflag.CommandLine.MarkHidden("version") pflag.CommandLine.MarkHidden("google-json-key") pflag.CommandLine.MarkHidden("log-flush-frequency") diff --git a/cmd/kubeadm/app/phases/addons/dns/BUILD b/cmd/kubeadm/app/phases/addons/dns/BUILD index 0d11f5f72a1..ef8c19b2b8d 100644 --- a/cmd/kubeadm/app/phases/addons/dns/BUILD +++ b/cmd/kubeadm/app/phases/addons/dns/BUILD @@ -12,11 +12,12 @@ go_test( "dns_test.go", "versions_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns", - library = ":go_default_library", deps = [ + "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/util:go_default_library", - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/util/version:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", @@ -36,12 +37,14 @@ go_library( deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", + "//cmd/kubeadm/app/features:go_default_library", "//cmd/kubeadm/app/util:go_default_library", "//cmd/kubeadm/app/util/apiclient:go_default_library", - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/util/version:go_default_library", - "//vendor/k8s.io/api/apps/v1beta2:go_default_library", + "//vendor/k8s.io/api/apps/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/rbac/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/cmd/kubeadm/app/phases/addons/dns/dns.go b/cmd/kubeadm/app/phases/addons/dns/dns.go index 4ca3f70d5ec..4f1db3d045e 100644 --- a/cmd/kubeadm/app/phases/addons/dns/dns.go +++ b/cmd/kubeadm/app/phases/addons/dns/dns.go @@ -18,20 +18,21 @@ package dns import ( "fmt" - "net" "runtime" - apps "k8s.io/api/apps/v1beta2" + apps "k8s.io/api/apps/v1" "k8s.io/api/core/v1" + rbac "k8s.io/api/rbac/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kuberuntime "k8s.io/apimachinery/pkg/runtime" clientset "k8s.io/client-go/kubernetes" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" + "k8s.io/kubernetes/cmd/kubeadm/app/features" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/util/version" ) @@ -40,36 +41,54 @@ const ( KubeDNSServiceAccountName = "kube-dns" ) -// EnsureDNSAddon creates the kube-dns addon +// EnsureDNSAddon creates the kube-dns or CoreDNS addon func EnsureDNSAddon(cfg *kubeadmapi.MasterConfiguration, client clientset.Interface) error { k8sVersion, err := version.ParseSemantic(cfg.KubernetesVersion) if err != nil { return fmt.Errorf("couldn't parse kubernetes version %q: %v", cfg.KubernetesVersion, err) } + if features.Enabled(cfg.FeatureGates, features.CoreDNS) { + return coreDNSAddon(cfg, client, k8sVersion) + } + return kubeDNSAddon(cfg, client, k8sVersion) +} +func kubeDNSAddon(cfg *kubeadmapi.MasterConfiguration, client clientset.Interface, k8sVersion *version.Version) error { if err := CreateServiceAccount(client); err != nil { return err } - // Get the YAML manifest conditionally based on the k8s version - kubeDNSDeploymentBytes := GetKubeDNSManifest(k8sVersion) - dnsDeploymentBytes, err := kubeadmutil.ParseTemplate(kubeDNSDeploymentBytes, struct{ ImageRepository, Arch, Version, DNSDomain, MasterTaintKey string }{ - ImageRepository: cfg.ImageRepository, - Arch: runtime.GOARCH, - // Get the kube-dns version conditionally based on the k8s version - Version: GetKubeDNSVersion(k8sVersion), - DNSDomain: cfg.Networking.DNSDomain, - MasterTaintKey: kubeadmconstants.LabelNodeRoleMaster, - }) - if err != nil { - return fmt.Errorf("error when parsing kube-dns deployment template: %v", err) - } - - dnsip, err := getDNSIP(client) + dnsip, err := kubeadmconstants.GetDNSIP(cfg.Networking.ServiceSubnet) if err != nil { return err } + var dnsBindAddr, dnsProbeAddr string + if dnsip.To4() == nil { + dnsBindAddr = "::1" + dnsProbeAddr = "[" + dnsBindAddr + "]" + } else { + dnsBindAddr = "127.0.0.1" + dnsProbeAddr = dnsBindAddr + } + + // Get the YAML manifest conditionally based on the k8s version + kubeDNSDeploymentBytes := GetKubeDNSManifest(k8sVersion) + dnsDeploymentBytes, err := kubeadmutil.ParseTemplate(kubeDNSDeploymentBytes, + struct{ ImageRepository, Arch, Version, DNSBindAddr, DNSProbeAddr, DNSDomain, MasterTaintKey string }{ + ImageRepository: cfg.ImageRepository, + Arch: runtime.GOARCH, + // Get the kube-dns version conditionally based on the k8s version + Version: GetDNSVersion(k8sVersion, kubeadmconstants.KubeDNS), + DNSBindAddr: dnsBindAddr, + DNSProbeAddr: dnsProbeAddr, + DNSDomain: cfg.Networking.DNSDomain, + MasterTaintKey: kubeadmconstants.LabelNodeRoleMaster, + }) + if err != nil { + return fmt.Errorf("error when parsing kube-dns deployment template: %v", err) + } + dnsServiceBytes, err := kubeadmutil.ParseTemplate(KubeDNSService, struct{ DNSIP string }{ DNSIP: dnsip.String(), }) @@ -97,7 +116,7 @@ func CreateServiceAccount(client clientset.Interface) error { func createKubeDNSAddon(deploymentBytes, serviceBytes []byte, client clientset.Interface) error { kubednsDeployment := &apps.Deployment{} - if err := kuberuntime.DecodeInto(api.Codecs.UniversalDecoder(), deploymentBytes, kubednsDeployment); err != nil { + if err := kuberuntime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), deploymentBytes, kubednsDeployment); err != nil { return fmt.Errorf("unable to decode kube-dns deployment %v", err) } @@ -107,41 +126,121 @@ func createKubeDNSAddon(deploymentBytes, serviceBytes []byte, client clientset.I } kubednsService := &v1.Service{} - if err := kuberuntime.DecodeInto(api.Codecs.UniversalDecoder(), serviceBytes, kubednsService); err != nil { - return fmt.Errorf("unable to decode kube-dns service %v", err) + return createDNSService(kubednsService, serviceBytes, client) +} + +func coreDNSAddon(cfg *kubeadmapi.MasterConfiguration, client clientset.Interface, k8sVersion *version.Version) error { + // Get the YAML manifest conditionally based on the k8s version + dnsDeploymentBytes := GetCoreDNSManifest(k8sVersion) + coreDNSDeploymentBytes, err := kubeadmutil.ParseTemplate(dnsDeploymentBytes, struct{ MasterTaintKey, Version string }{ + MasterTaintKey: kubeadmconstants.LabelNodeRoleMaster, + Version: GetDNSVersion(k8sVersion, kubeadmconstants.CoreDNS), + }) + if err != nil { + return fmt.Errorf("error when parsing CoreDNS deployment template: %v", err) + } + + // Get the config file for CoreDNS + coreDNSConfigMapBytes, err := kubeadmutil.ParseTemplate(CoreDNSConfigMap, struct{ DNSDomain, ServiceCIDR string }{ + ServiceCIDR: cfg.Networking.ServiceSubnet, + DNSDomain: cfg.Networking.DNSDomain, + }) + if err != nil { + return fmt.Errorf("error when parsing CoreDNS configMap template: %v", err) + } + + dnsip, err := kubeadmconstants.GetDNSIP(cfg.Networking.ServiceSubnet) + if err != nil { + return err + } + + coreDNSServiceBytes, err := kubeadmutil.ParseTemplate(KubeDNSService, struct{ DNSIP string }{ + DNSIP: dnsip.String(), + }) + + if err != nil { + return fmt.Errorf("error when parsing CoreDNS service template: %v", err) + } + + if err := createCoreDNSAddon(coreDNSDeploymentBytes, coreDNSServiceBytes, coreDNSConfigMapBytes, client); err != nil { + return err + } + fmt.Println("[addons] Applied essential addon: CoreDNS") + return nil +} + +func createCoreDNSAddon(deploymentBytes, serviceBytes, configBytes []byte, client clientset.Interface) error { + coreDNSConfigMap := &v1.ConfigMap{} + if err := kuberuntime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), configBytes, coreDNSConfigMap); err != nil { + return fmt.Errorf("unable to decode CoreDNS configmap %v", err) + } + + // Create the ConfigMap for CoreDNS or update it in case it already exists + if err := apiclient.CreateOrUpdateConfigMap(client, coreDNSConfigMap); err != nil { + return err + } + + coreDNSClusterRoles := &rbac.ClusterRole{} + if err := kuberuntime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), []byte(CoreDNSClusterRole), coreDNSClusterRoles); err != nil { + return fmt.Errorf("unable to decode CoreDNS clusterroles %v", err) + } + + // Create the Clusterroles for CoreDNS or update it in case it already exists + if err := apiclient.CreateOrUpdateClusterRole(client, coreDNSClusterRoles); err != nil { + return err + } + + coreDNSClusterRolesBinding := &rbac.ClusterRoleBinding{} + if err := kuberuntime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), []byte(CoreDNSClusterRoleBinding), coreDNSClusterRolesBinding); err != nil { + return fmt.Errorf("unable to decode CoreDNS clusterrolebindings %v", err) + } + + // Create the Clusterrolebindings for CoreDNS or update it in case it already exists + if err := apiclient.CreateOrUpdateClusterRoleBinding(client, coreDNSClusterRolesBinding); err != nil { + return err + } + + coreDNSServiceAccount := &v1.ServiceAccount{} + if err := kuberuntime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), []byte(CoreDNSServiceAccount), coreDNSServiceAccount); err != nil { + return fmt.Errorf("unable to decode CoreDNS serviceaccount %v", err) + } + + // Create the ConfigMap for CoreDNS or update it in case it already exists + if err := apiclient.CreateOrUpdateServiceAccount(client, coreDNSServiceAccount); err != nil { + return err + } + + coreDNSDeployment := &apps.Deployment{} + if err := kuberuntime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), deploymentBytes, coreDNSDeployment); err != nil { + return fmt.Errorf("unable to decode CoreDNS deployment %v", err) + } + + // Create the Deployment for CoreDNS or update it in case it already exists + if err := apiclient.CreateOrUpdateDeployment(client, coreDNSDeployment); err != nil { + return err + } + + coreDNSService := &v1.Service{} + return createDNSService(coreDNSService, serviceBytes, client) +} + +func createDNSService(dnsService *v1.Service, serviceBytes []byte, client clientset.Interface) error { + if err := kuberuntime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), serviceBytes, dnsService); err != nil { + return fmt.Errorf("unable to decode the DNS service %v", err) } // Can't use a generic apiclient helper func here as we have to tolerate more than AlreadyExists. - if _, err := client.CoreV1().Services(metav1.NamespaceSystem).Create(kubednsService); err != nil { + if _, err := client.CoreV1().Services(metav1.NamespaceSystem).Create(dnsService); err != nil { // Ignore if the Service is invalid with this error message: // Service "kube-dns" is invalid: spec.clusterIP: Invalid value: "10.96.0.10": provided IP is already allocated if !apierrors.IsAlreadyExists(err) && !apierrors.IsInvalid(err) { - return fmt.Errorf("unable to create a new kube-dns service: %v", err) + return fmt.Errorf("unable to create a new DNS service: %v", err) } - if _, err := client.CoreV1().Services(metav1.NamespaceSystem).Update(kubednsService); err != nil { - return fmt.Errorf("unable to create/update the kube-dns service: %v", err) + if _, err := client.CoreV1().Services(metav1.NamespaceSystem).Update(dnsService); err != nil { + return fmt.Errorf("unable to create/update the DNS service: %v", err) } } return nil } - -// getDNSIP fetches the kubernetes service's ClusterIP and appends a "0" to it in order to get the DNS IP -func getDNSIP(client clientset.Interface) (net.IP, error) { - k8ssvc, err := client.CoreV1().Services(metav1.NamespaceDefault).Get("kubernetes", metav1.GetOptions{}) - if err != nil { - return nil, fmt.Errorf("couldn't fetch information about the kubernetes service: %v", err) - } - - if len(k8ssvc.Spec.ClusterIP) == 0 { - return nil, fmt.Errorf("couldn't fetch a valid clusterIP from the kubernetes service") - } - - // Build an IP by taking the kubernetes service's clusterIP and appending a "0" and checking that it's valid - dnsIP := net.ParseIP(fmt.Sprintf("%s0", k8ssvc.Spec.ClusterIP)) - if dnsIP == nil { - return nil, fmt.Errorf("could not parse dns ip %q: %v", dnsIP, err) - } - return dnsIP, nil -} diff --git a/cmd/kubeadm/app/phases/addons/dns/dns_test.go b/cmd/kubeadm/app/phases/addons/dns/dns_test.go index 22b1d444ded..c144dc49e04 100644 --- a/cmd/kubeadm/app/phases/addons/dns/dns_test.go +++ b/cmd/kubeadm/app/phases/addons/dns/dns_test.go @@ -23,8 +23,9 @@ import ( "k8s.io/apimachinery/pkg/runtime" clientsetfake "k8s.io/client-go/kubernetes/fake" core "k8s.io/client-go/testing" + kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestCreateServiceAccount(t *testing.T) { @@ -90,11 +91,13 @@ func TestCompileManifests(t *testing.T) { expected bool }{ { - manifest: v170AndAboveKubeDNSDeployment, - data: struct{ ImageRepository, Arch, Version, DNSDomain, MasterTaintKey string }{ + manifest: v180AndAboveKubeDNSDeployment, + data: struct{ ImageRepository, Arch, Version, DNSBindAddr, DNSProbeAddr, DNSDomain, MasterTaintKey string }{ ImageRepository: "foo", Arch: "foo", Version: "foo", + DNSBindAddr: "foo", + DNSProbeAddr: "foo", DNSDomain: "foo", MasterTaintKey: "foo", }, @@ -107,6 +110,29 @@ func TestCompileManifests(t *testing.T) { }, expected: true, }, + { + manifest: CoreDNSDeployment, + data: struct{ MasterTaintKey, Version string }{ + MasterTaintKey: "foo", + Version: "foo", + }, + expected: true, + }, + { + manifest: KubeDNSService, + data: struct{ DNSIP string }{ + DNSIP: "foo", + }, + expected: true, + }, + { + manifest: CoreDNSConfigMap, + data: struct{ DNSDomain, ServiceCIDR string }{ + DNSDomain: "foo", + ServiceCIDR: "foo", + }, + expected: true, + }, } for _, rt := range tests { _, actual := kubeadmutil.ParseTemplate(rt.manifest, rt.data) @@ -119,3 +145,33 @@ func TestCompileManifests(t *testing.T) { } } } + +func TestGetDNSIP(t *testing.T) { + var tests = []struct { + svcSubnet, expectedDNSIP string + }{ + { + svcSubnet: "10.96.0.0/12", + expectedDNSIP: "10.96.0.10", + }, + { + svcSubnet: "10.87.116.64/26", + expectedDNSIP: "10.87.116.74", + }, + } + for _, rt := range tests { + dnsIP, err := kubeadmconstants.GetDNSIP(rt.svcSubnet) + if err != nil { + t.Fatalf("couldn't get dnsIP : %v", err) + } + + actualDNSIP := dnsIP.String() + if actualDNSIP != rt.expectedDNSIP { + t.Errorf( + "failed GetDNSIP\n\texpected: %s\n\t actual: %s", + rt.expectedDNSIP, + actualDNSIP, + ) + } + } +} diff --git a/cmd/kubeadm/app/phases/addons/dns/manifests.go b/cmd/kubeadm/app/phases/addons/dns/manifests.go index 9b5801c78b1..e2d0f60a3d1 100644 --- a/cmd/kubeadm/app/phases/addons/dns/manifests.go +++ b/cmd/kubeadm/app/phases/addons/dns/manifests.go @@ -17,9 +17,9 @@ limitations under the License. package dns const ( - // v170AndAboveKubeDNSDeployment is the kube-dns Deployment manifest for the kube-dns manifest for v1.7+ - v170AndAboveKubeDNSDeployment = ` -apiVersion: apps/v1beta2 + // v180AndAboveKubeDNSDeployment is the kube-dns Deployment manifest for the kube-dns manifest for v1.7+ + v180AndAboveKubeDNSDeployment = ` +apiVersion: apps/v1 kind: Deployment metadata: name: kube-dns @@ -123,9 +123,9 @@ spec: - --cache-size=1000 - --no-negcache - --log-facility=- - - --server=/{{ .DNSDomain }}/127.0.0.1#10053 - - --server=/in-addr.arpa/127.0.0.1#10053 - - --server=/ip6.arpa/127.0.0.1#10053 + - --server=/{{ .DNSDomain }}/{{ .DNSBindAddr }}#10053 + - --server=/in-addr.arpa/{{ .DNSBindAddr }}#10053 + - --server=/ip6.arpa/{{ .DNSBindAddr }}#10053 ports: - containerPort: 53 name: dns @@ -156,8 +156,8 @@ spec: args: - --v=2 - --logtostderr - - --probe=kubedns,127.0.0.1:10053,kubernetes.default.svc.{{ .DNSDomain }},5,A - - --probe=dnsmasq,127.0.0.1:53,kubernetes.default.svc.{{ .DNSDomain }},5,A + - --probe=kubedns,{{ .DNSProbeAddr }}:10053,kubernetes.default.svc.{{ .DNSDomain }},5,SRV + - --probe=dnsmasq,{{ .DNSProbeAddr }}:53,kubernetes.default.svc.{{ .DNSDomain }},5,SRV ports: - containerPort: 10054 name: metrics @@ -213,4 +213,135 @@ spec: selector: k8s-app: kube-dns ` + + // CoreDNSDeployment is the CoreDNS Deployment manifest + CoreDNSDeployment = ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: coredns + namespace: kube-system + labels: + k8s-app: kube-dns +spec: + replicas: 1 + selector: + matchLabels: + k8s-app: kube-dns + template: + metadata: + labels: + k8s-app: kube-dns + spec: + serviceAccountName: coredns + tolerations: + - key: CriticalAddonsOnly + operator: Exists + - key: {{ .MasterTaintKey }} + effect: NoSchedule + containers: + - name: coredns + image: coredns/coredns:{{ .Version }} + imagePullPolicy: IfNotPresent + resources: + limits: + memory: 170Mi + requests: + cpu: 100m + memory: 70Mi + args: [ "-conf", "/etc/coredns/Corefile" ] + volumeMounts: + - name: config-volume + mountPath: /etc/coredns + ports: + - containerPort: 53 + name: dns + protocol: UDP + - containerPort: 53 + name: dns-tcp + protocol: TCP + - containerPort: 9153 + name: metrics + protocol: TCP + livenessProbe: + httpGet: + path: /health + port: 8080 + scheme: HTTP + initialDelaySeconds: 60 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + dnsPolicy: Default + volumes: + - name: config-volume + configMap: + name: coredns + items: + - key: Corefile + path: Corefile +` + + // CoreDNSConfigMap is the CoreDNS ConfigMap manifest + CoreDNSConfigMap = ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: coredns + namespace: kube-system +data: + Corefile: | + .:53 { + errors + log + health + kubernetes {{ .DNSDomain }} {{ .ServiceCIDR }} { + pods insecure + } + prometheus + proxy . /etc/resolv.conf + cache 30 + } +` + // CoreDNSClusterRole is the CoreDNS ClusterRole manifest + CoreDNSClusterRole = ` +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: system:coredns +rules: +- apiGroups: + - "" + resources: + - endpoints + - services + - pods + - namespaces + verbs: + - list + - watch +` + // CoreDNSClusterRoleBinding is the CoreDNS Clusterrolebinding manifest + CoreDNSClusterRoleBinding = ` +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: system:coredns +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:coredns +subjects: +- kind: ServiceAccount + name: coredns + namespace: kube-system +` + // CoreDNSServiceAccount is the CoreDNS ServiceAccount manifest + CoreDNSServiceAccount = ` +apiVersion: v1 +kind: ServiceAccount +metadata: + name: coredns + namespace: kube-system +` ) diff --git a/cmd/kubeadm/app/phases/addons/dns/versions.go b/cmd/kubeadm/app/phases/addons/dns/versions.go index f0fe6b939a6..c728f55f09c 100644 --- a/cmd/kubeadm/app/phases/addons/dns/versions.go +++ b/cmd/kubeadm/app/phases/addons/dns/versions.go @@ -17,24 +17,41 @@ limitations under the License. package dns import ( + kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/pkg/util/version" ) const ( - kubeDNSv170AndAboveVersion = "1.14.5" + kubeDNSv190AndAboveVersion = "1.14.7" + coreDNSVersion = "1.0.1" ) -// GetKubeDNSVersion returns the right kube-dns version for a specific k8s version -func GetKubeDNSVersion(kubeVersion *version.Version) string { - // v1.7.0+ uses 1.14.5, just return that here - // In the future when the kube-dns version is bumped at HEAD; add conditional logic to return the right versions +// GetDNSVersion returns the right kube-dns version for a specific k8s version +func GetDNSVersion(kubeVersion *version.Version, dns string) string { + // v1.9.0+ uses kube-dns 1.14.7 + // v1.9.0+ uses CoreDNS 1.0.1 if feature gate "CoreDNS" is enabled. + + // In the future when the version is bumped at HEAD; add conditional logic to return the right versions // Also, the version might be bumped for different k8s releases on the same branch - return kubeDNSv170AndAboveVersion + switch dns { + case kubeadmconstants.CoreDNS: + // return the CoreDNS version + return coreDNSVersion + default: + return kubeDNSv190AndAboveVersion + } } // GetKubeDNSManifest returns the right kube-dns YAML manifest for a specific k8s version func GetKubeDNSManifest(kubeVersion *version.Version) string { - // v1.7.0+ has only one known YAML manifest spec, just return that here + // v1.8.0+ has only one known YAML manifest spec, just return that here // In the future when the kube-dns version is bumped at HEAD; add conditional logic to return the right manifest - return v170AndAboveKubeDNSDeployment + return v180AndAboveKubeDNSDeployment +} + +// GetCoreDNSManifest returns the right CoreDNS YAML manifest for a specific k8s version +func GetCoreDNSManifest(kubeVersion *version.Version) string { + // v1.9.0+ has only one known YAML manifest spec, just return that here + // In the future when the CoreDNS version is bumped at HEAD; add conditional logic to return the right manifest + return CoreDNSDeployment } diff --git a/cmd/kubeadm/app/phases/addons/dns/versions_test.go b/cmd/kubeadm/app/phases/addons/dns/versions_test.go index 96d0f72dd60..c4ca3e78f89 100644 --- a/cmd/kubeadm/app/phases/addons/dns/versions_test.go +++ b/cmd/kubeadm/app/phases/addons/dns/versions_test.go @@ -19,49 +19,47 @@ package dns import ( "testing" + kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/pkg/util/version" ) func TestGetKubeDNSVersion(t *testing.T) { var tests = []struct { - k8sVersion, expected string + k8sVersion string + dns string + expected string }{ { - k8sVersion: "v1.7.0", - expected: "1.14.5", + k8sVersion: "v1.9.0", + dns: kubeadmconstants.KubeDNS, + expected: kubeDNSv190AndAboveVersion, }, { - k8sVersion: "v1.7.1", - expected: "1.14.5", + k8sVersion: "v1.10.0", + dns: kubeadmconstants.KubeDNS, + expected: kubeDNSv190AndAboveVersion, }, { - k8sVersion: "v1.7.2", - expected: "1.14.5", + k8sVersion: "v1.9.0", + dns: kubeadmconstants.CoreDNS, + expected: coreDNSVersion, }, { - k8sVersion: "v1.7.3", - expected: "1.14.5", - }, - { - k8sVersion: "v1.8.0-alpha.2", - expected: "1.14.5", - }, - { - k8sVersion: "v1.8.0", - expected: "1.14.5", + k8sVersion: "v1.10.0", + dns: kubeadmconstants.CoreDNS, + expected: coreDNSVersion, }, } for _, rt := range tests { - k8sVersion, err := version.ParseSemantic(rt.k8sVersion) if err != nil { t.Fatalf("couldn't parse kubernetes version %q: %v", rt.k8sVersion, err) } - actualDNSVersion := GetKubeDNSVersion(k8sVersion) + actualDNSVersion := GetDNSVersion(k8sVersion, rt.dns) if actualDNSVersion != rt.expected { t.Errorf( - "failed GetKubeDNSVersion:\n\texpected: %s\n\t actual: %s", + "failed GetDNSVersion:\n\texpected: %s\n\t actual: %s", rt.expected, actualDNSVersion, ) diff --git a/cmd/kubeadm/app/phases/addons/proxy/BUILD b/cmd/kubeadm/app/phases/addons/proxy/BUILD index 018e27f454c..c08b0dd6a48 100644 --- a/cmd/kubeadm/app/phases/addons/proxy/BUILD +++ b/cmd/kubeadm/app/phases/addons/proxy/BUILD @@ -9,12 +9,17 @@ load( go_test( name = "go_default_test", srcs = ["proxy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/proxy", - library = ":go_default_library", deps = [ + "//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library", "//cmd/kubeadm/app/util:go_default_library", - "//pkg/api:go_default_library", + "//cmd/kubeadm/app/util/config:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/proxy/apis/kubeproxyconfig/v1alpha1:go_default_library", + "//pkg/util/pointer:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", "//vendor/k8s.io/client-go/testing:go_default_library", @@ -33,9 +38,11 @@ go_library( "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/util:go_default_library", "//cmd/kubeadm/app/util/apiclient:go_default_library", - "//pkg/api:go_default_library", - "//plugin/pkg/scheduler/algorithm:go_default_library", - "//vendor/k8s.io/api/apps/v1beta2:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/proxy/apis/kubeproxyconfig/scheme:go_default_library", + "//pkg/proxy/apis/kubeproxyconfig/v1alpha1:go_default_library", + "//pkg/scheduler/algorithm:go_default_library", + "//vendor/k8s.io/api/apps/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/rbac/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/cmd/kubeadm/app/phases/addons/proxy/manifests.go b/cmd/kubeadm/app/phases/addons/proxy/manifests.go index df645cdf29d..f6e82319bf0 100644 --- a/cmd/kubeadm/app/phases/addons/proxy/manifests.go +++ b/cmd/kubeadm/app/phases/addons/proxy/manifests.go @@ -17,8 +17,8 @@ limitations under the License. package proxy const ( - // KubeProxyConfigMap is the proxy ConfigMap manifest - KubeProxyConfigMap = ` + // KubeProxyConfigMap19 is the proxy ConfigMap manifest for Kubernetes 1.9 and above + KubeProxyConfigMap19 = ` kind: ConfigMap apiVersion: v1 metadata: @@ -27,7 +27,7 @@ metadata: labels: app: kube-proxy data: - kubeconfig.conf: | + kubeconfig.conf: |- apiVersion: v1 kind: Config clusters: @@ -46,11 +46,13 @@ data: - name: default user: tokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token + config.conf: |- +{{ .ProxyConfig}} ` - // KubeProxyDaemonSet is the proxy DaemonSet manifest - KubeProxyDaemonSet = ` -apiVersion: apps/v1beta2 + // KubeProxyDaemonSet19 is the proxy DaemonSet manifest for Kubernetes 1.9 and above + KubeProxyDaemonSet19 = ` +apiVersion: apps/v1 kind: DaemonSet metadata: labels: @@ -74,8 +76,7 @@ spec: imagePullPolicy: IfNotPresent command: - /usr/local/bin/kube-proxy - - --kubeconfig=/var/lib/kube-proxy/kubeconfig.conf - {{ .ClusterCIDR }} + - --config=/var/lib/kube-proxy/config.conf securityContext: privileged: true volumeMounts: @@ -84,6 +85,9 @@ spec: - mountPath: /run/xtables.lock name: xtables-lock readOnly: false + - mountPath: /lib/modules + name: lib-modules + readOnly: true hostNetwork: true serviceAccountName: kube-proxy tolerations: @@ -100,5 +104,8 @@ spec: hostPath: path: /run/xtables.lock type: FileOrCreate + - name: lib-modules + hostPath: + path: /lib/modules ` ) diff --git a/cmd/kubeadm/app/phases/addons/proxy/proxy.go b/cmd/kubeadm/app/phases/addons/proxy/proxy.go index 086de21cde9..2f8f27e6d9f 100644 --- a/cmd/kubeadm/app/phases/addons/proxy/proxy.go +++ b/cmd/kubeadm/app/phases/addons/proxy/proxy.go @@ -20,7 +20,7 @@ import ( "fmt" "runtime" - apps "k8s.io/api/apps/v1beta2" + apps "k8s.io/api/apps/v1" "k8s.io/api/core/v1" rbac "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -30,8 +30,10 @@ import ( kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" + "k8s.io/kubernetes/pkg/api/legacyscheme" + kubeproxyconfigscheme "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/scheme" + kubeproxyconfigv1alpha1 "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1" + "k8s.io/kubernetes/pkg/scheduler/algorithm" ) const ( @@ -55,26 +57,34 @@ func EnsureProxyAddon(cfg *kubeadmapi.MasterConfiguration, client clientset.Inte return err } - proxyConfigMapBytes, err := kubeadmutil.ParseTemplate(KubeProxyConfigMap, struct{ MasterEndpoint string }{ - // Fetch this value from the kubeconfig file - MasterEndpoint: masterEndpoint}) + proxyBytes, err := kubeadmutil.MarshalToYamlForCodecsWithShift(cfg.KubeProxy.Config, kubeproxyconfigv1alpha1.SchemeGroupVersion, + kubeproxyconfigscheme.Codecs) + if err != nil { + return fmt.Errorf("error when marshaling: %v", err) + } + var proxyConfigMapBytes, proxyDaemonSetBytes []byte + proxyConfigMapBytes, err = kubeadmutil.ParseTemplate(KubeProxyConfigMap19, + struct { + MasterEndpoint string + ProxyConfig string + }{ + MasterEndpoint: masterEndpoint, + ProxyConfig: proxyBytes, + }) if err != nil { return fmt.Errorf("error when parsing kube-proxy configmap template: %v", err) } - - proxyDaemonSetBytes, err := kubeadmutil.ParseTemplate(KubeProxyDaemonSet, struct{ ImageRepository, Arch, Version, ImageOverride, ClusterCIDR, MasterTaintKey, CloudTaintKey string }{ + proxyDaemonSetBytes, err = kubeadmutil.ParseTemplate(KubeProxyDaemonSet19, struct{ ImageRepository, Arch, Version, ImageOverride, ClusterCIDR, MasterTaintKey, CloudTaintKey string }{ ImageRepository: cfg.GetControlPlaneImageRepository(), Arch: runtime.GOARCH, Version: kubeadmutil.KubernetesVersionToImageTag(cfg.KubernetesVersion), ImageOverride: cfg.UnifiedControlPlaneImage, - ClusterCIDR: getClusterCIDR(cfg.Networking.PodSubnet), MasterTaintKey: kubeadmconstants.LabelNodeRoleMaster, CloudTaintKey: algorithm.TaintExternalCloudProvider, }) if err != nil { return fmt.Errorf("error when parsing kube-proxy daemonset template: %v", err) } - if err := createKubeProxyAddon(proxyConfigMapBytes, proxyDaemonSetBytes, client); err != nil { return err } @@ -99,15 +109,12 @@ func CreateServiceAccount(client clientset.Interface) error { // CreateRBACRules creates the essential RBAC rules for a minimally set-up cluster func CreateRBACRules(client clientset.Interface) error { - if err := createClusterRoleBindings(client); err != nil { - return err - } - return nil + return createClusterRoleBindings(client) } func createKubeProxyAddon(configMapBytes, daemonSetbytes []byte, client clientset.Interface) error { kubeproxyConfigMap := &v1.ConfigMap{} - if err := kuberuntime.DecodeInto(api.Codecs.UniversalDecoder(), configMapBytes, kubeproxyConfigMap); err != nil { + if err := kuberuntime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), configMapBytes, kubeproxyConfigMap); err != nil { return fmt.Errorf("unable to decode kube-proxy configmap %v", err) } @@ -117,15 +124,12 @@ func createKubeProxyAddon(configMapBytes, daemonSetbytes []byte, client clientse } kubeproxyDaemonSet := &apps.DaemonSet{} - if err := kuberuntime.DecodeInto(api.Codecs.UniversalDecoder(), daemonSetbytes, kubeproxyDaemonSet); err != nil { + if err := kuberuntime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), daemonSetbytes, kubeproxyDaemonSet); err != nil { return fmt.Errorf("unable to decode kube-proxy daemonset %v", err) } // Create the DaemonSet for kube-proxy or update it in case it already exists - if err := apiclient.CreateOrUpdateDaemonSet(client, kubeproxyDaemonSet); err != nil { - return err - } - return nil + return apiclient.CreateOrUpdateDaemonSet(client, kubeproxyDaemonSet) } func createClusterRoleBindings(client clientset.Interface) error { diff --git a/cmd/kubeadm/app/phases/addons/proxy/proxy_test.go b/cmd/kubeadm/app/phases/addons/proxy/proxy_test.go index f14b7cf4f1a..21893d065da 100644 --- a/cmd/kubeadm/app/phases/addons/proxy/proxy_test.go +++ b/cmd/kubeadm/app/phases/addons/proxy/proxy_test.go @@ -17,14 +17,21 @@ limitations under the License. package proxy import ( + "strings" "testing" + "time" 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" core "k8s.io/client-go/testing" + kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" - "k8s.io/kubernetes/pkg/api" + cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" + api "k8s.io/kubernetes/pkg/apis/core" + kubeproxyconfigv1alpha1 "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1" + "k8s.io/kubernetes/pkg/util/pointer" ) func TestCreateServiceAccount(t *testing.T) { @@ -107,20 +114,22 @@ func TestCompileManifests(t *testing.T) { expected bool }{ { - manifest: KubeProxyConfigMap, - data: struct{ MasterEndpoint string }{ + manifest: KubeProxyConfigMap19, + data: struct { + MasterEndpoint, ProxyConfig string + }{ MasterEndpoint: "foo", + ProxyConfig: " bindAddress: 0.0.0.0\n clusterCIDR: 192.168.1.1\n enableProfiling: false", }, expected: true, }, { - manifest: KubeProxyDaemonSet, - data: struct{ ImageRepository, Arch, Version, ImageOverride, ClusterCIDR, MasterTaintKey, CloudTaintKey string }{ + manifest: KubeProxyDaemonSet19, + data: struct{ ImageRepository, Arch, Version, ImageOverride, MasterTaintKey, CloudTaintKey string }{ ImageRepository: "foo", Arch: "foo", Version: "foo", ImageOverride: "foo", - ClusterCIDR: "foo", MasterTaintKey: "foo", CloudTaintKey: "foo", }, @@ -131,10 +140,132 @@ func TestCompileManifests(t *testing.T) { _, actual := kubeadmutil.ParseTemplate(rt.manifest, rt.data) if (actual == nil) != rt.expected { t.Errorf( - "failed CompileManifests:\n\texpected: %t\n\t actual: %t", + "failed to compile %s manifest:\n\texpected: %t\n\t actual: %t", + rt.manifest, rt.expected, (actual == nil), ) } } } + +func TestEnsureProxyAddon(t *testing.T) { + type SimulatedError int + const ( + NoError SimulatedError = iota + ServiceAccountError + InvalidMasterEndpoint + IPv6SetBindAddress + ) + + var testCases = []struct { + name string + simError SimulatedError + expErrString string + expBindAddr string + expClusterCIDR string + }{ + { + name: "Successful proxy addon", + simError: NoError, + expErrString: "", + expBindAddr: "0.0.0.0", + expClusterCIDR: "5.6.7.8/24", + }, { + name: "Simulated service account error", + simError: ServiceAccountError, + expErrString: "error when creating kube-proxy service account", + expBindAddr: "0.0.0.0", + expClusterCIDR: "5.6.7.8/24", + }, { + name: "IPv6 AdvertiseAddress address", + simError: IPv6SetBindAddress, + expErrString: "", + expBindAddr: "::", + expClusterCIDR: "2001:101::/96", + }, + } + + for _, tc := range testCases { + + // Create a fake client and set up default test configuration + client := clientsetfake.NewSimpleClientset() + + masterConfig := &kubeadmapiext.MasterConfiguration{ + API: kubeadmapiext.API{ + AdvertiseAddress: "1.2.3.4", + BindPort: 1234, + }, + KubeProxy: kubeadmapiext.KubeProxy{ + Config: &kubeproxyconfigv1alpha1.KubeProxyConfiguration{ + BindAddress: "", + HealthzBindAddress: "0.0.0.0:10256", + MetricsBindAddress: "127.0.0.1:10249", + Conntrack: kubeproxyconfigv1alpha1.KubeProxyConntrackConfiguration{ + Max: pointer.Int32Ptr(2), + MaxPerCore: pointer.Int32Ptr(1), + Min: pointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + }, + Networking: kubeadmapiext.Networking{ + PodSubnet: "5.6.7.8/24", + }, + ImageRepository: "someRepo", + KubernetesVersion: "v1.9.0", + UnifiedControlPlaneImage: "someImage", + } + + // Simulate an error if neccessary + switch tc.simError { + case ServiceAccountError: + client.PrependReactor("create", "serviceaccounts", func(action core.Action) (bool, runtime.Object, error) { + return true, nil, apierrors.NewUnauthorized("") + }) + case InvalidMasterEndpoint: + masterConfig.API.AdvertiseAddress = "1.2.3" + case IPv6SetBindAddress: + masterConfig.API.AdvertiseAddress = "1:2::3:4" + masterConfig.Networking.PodSubnet = "2001:101::/96" + } + + kubeadmapiext.SetDefaults_MasterConfiguration(masterConfig) + intMaster, err := cmdutil.ConfigFileAndDefaultsToInternalConfig("", masterConfig) + if err != nil { + t.Errorf(" test failed to convert v1alpha1 to internal version") + break + } + err = EnsureProxyAddon(intMaster, client) + + // Compare actual to expected errors + actErr := "No error" + if err != nil { + actErr = err.Error() + } + expErr := "No error" + if tc.expErrString != "" { + expErr = tc.expErrString + } + if !strings.Contains(actErr, expErr) { + t.Errorf( + "%s test failed, expected: %s, got: %s", + tc.name, + expErr, + actErr) + } + if intMaster.KubeProxy.Config.BindAddress != tc.expBindAddr { + t.Errorf("%s test failed, expected: %s, got: %s", + tc.name, + tc.expBindAddr, + intMaster.KubeProxy.Config.BindAddress) + } + if intMaster.KubeProxy.Config.ClusterCIDR != tc.expClusterCIDR { + t.Errorf("%s test failed, expected: %s, got: %s", + tc.name, + tc.expClusterCIDR, + intMaster.KubeProxy.Config.ClusterCIDR) + } + } +} diff --git a/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo/BUILD b/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo/BUILD index 5fe4fe49cfd..df3f70f55f9 100644 --- a/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo/BUILD +++ b/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo/BUILD @@ -9,10 +9,10 @@ load( go_test( name = "go_default_test", srcs = ["clusterinfo_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", diff --git a/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo/clusterinfo_test.go b/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo/clusterinfo_test.go index 59aa8870c5a..1447147e9a5 100644 --- a/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo/clusterinfo_test.go +++ b/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo/clusterinfo_test.go @@ -26,7 +26,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" clientsetfake "k8s.io/client-go/kubernetes/fake" core "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) var testConfigTempl = template.Must(template.New("test").Parse(`apiVersion: v1 diff --git a/cmd/kubeadm/app/phases/bootstraptoken/node/BUILD b/cmd/kubeadm/app/phases/bootstraptoken/node/BUILD index ff94da31f30..e20d68b5d57 100644 --- a/cmd/kubeadm/app/phases/bootstraptoken/node/BUILD +++ b/cmd/kubeadm/app/phases/bootstraptoken/node/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["token_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node", - library = ":go_default_library", deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"], ) @@ -26,7 +26,6 @@ go_library( "//cmd/kubeadm/app/util/apiclient:go_default_library", "//cmd/kubeadm/app/util/token:go_default_library", "//pkg/bootstrap/api:go_default_library", - "//pkg/util/version:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/rbac/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", diff --git a/cmd/kubeadm/app/phases/bootstraptoken/node/tlsbootstrap.go b/cmd/kubeadm/app/phases/bootstraptoken/node/tlsbootstrap.go index d8a63b60595..5f3943339f9 100644 --- a/cmd/kubeadm/app/phases/bootstraptoken/node/tlsbootstrap.go +++ b/cmd/kubeadm/app/phases/bootstraptoken/node/tlsbootstrap.go @@ -24,7 +24,6 @@ import ( clientset "k8s.io/client-go/kubernetes" "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" - "k8s.io/kubernetes/pkg/util/version" ) const ( @@ -47,8 +46,7 @@ const ( ) // AllowBootstrapTokensToPostCSRs creates RBAC rules in a way the makes Node Bootstrap Tokens able to post CSRs -func AllowBootstrapTokensToPostCSRs(client clientset.Interface, k8sVersion *version.Version) error { - +func AllowBootstrapTokensToPostCSRs(client clientset.Interface) error { fmt.Println("[bootstraptoken] Configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials") return apiclient.CreateOrUpdateClusterRoleBinding(client, &rbac.ClusterRoleBinding{ @@ -70,8 +68,7 @@ func AllowBootstrapTokensToPostCSRs(client clientset.Interface, k8sVersion *vers } // AutoApproveNodeBootstrapTokens creates RBAC rules in a way that makes Node Bootstrap Tokens' CSR auto-approved by the csrapprover controller -func AutoApproveNodeBootstrapTokens(client clientset.Interface, k8sVersion *version.Version) error { - +func AutoApproveNodeBootstrapTokens(client clientset.Interface) error { fmt.Println("[bootstraptoken] Configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token") // Always create this kubeadm-specific binding though @@ -94,28 +91,23 @@ func AutoApproveNodeBootstrapTokens(client clientset.Interface, k8sVersion *vers } // AutoApproveNodeCertificateRotation creates RBAC rules in a way that makes Node certificate rotation CSR auto-approved by the csrapprover controller -func AutoApproveNodeCertificateRotation(client clientset.Interface, k8sVersion *version.Version) error { +func AutoApproveNodeCertificateRotation(client clientset.Interface) error { + fmt.Println("[bootstraptoken] Configured RBAC rules to allow certificate rotation for all node client certificates in the cluster") - // Create autorotation cluster role binding only if we deploying or upgrading to version that supports it. - if k8sVersion.AtLeast(constants.MinimumCSRAutoApprovalClusterRolesVersion) { - fmt.Println("[bootstraptoken] Configured RBAC rules to allow certificate rotation for all node client certificates in the cluster") - - return apiclient.CreateOrUpdateClusterRoleBinding(client, &rbac.ClusterRoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: NodeAutoApproveCertificateRotationClusterRoleBinding, + return apiclient.CreateOrUpdateClusterRoleBinding(client, &rbac.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: NodeAutoApproveCertificateRotationClusterRoleBinding, + }, + RoleRef: rbac.RoleRef{ + APIGroup: rbac.GroupName, + Kind: "ClusterRole", + Name: NodeSelfCSRAutoApprovalClusterRoleName, + }, + Subjects: []rbac.Subject{ + { + Kind: "Group", + Name: constants.NodesGroup, }, - RoleRef: rbac.RoleRef{ - APIGroup: rbac.GroupName, - Kind: "ClusterRole", - Name: NodeSelfCSRAutoApprovalClusterRoleName, - }, - Subjects: []rbac.Subject{ - { - Kind: "Group", - Name: constants.NodesGroup, - }, - }, - }) - } - return nil + }, + }) } diff --git a/cmd/kubeadm/app/phases/bootstraptoken/node/token.go b/cmd/kubeadm/app/phases/bootstraptoken/node/token.go index 4b59d81fcc0..b033702c468 100644 --- a/cmd/kubeadm/app/phases/bootstraptoken/node/token.go +++ b/cmd/kubeadm/app/phases/bootstraptoken/node/token.go @@ -53,7 +53,11 @@ func UpdateOrCreateToken(client clientset.Interface, token string, failIfExists return fmt.Errorf("a token with id %q already exists", tokenID) } // Secret with this ID already exists, update it: - secret.Data = encodeTokenSecretData(tokenID, tokenSecret, tokenDuration, usages, extraGroups, description) + tokenSecretData, err := encodeTokenSecretData(tokenID, tokenSecret, tokenDuration, usages, extraGroups, description) + if err != nil { + return err + } + secret.Data = tokenSecretData if _, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Update(secret); err == nil { return nil } @@ -63,12 +67,17 @@ func UpdateOrCreateToken(client clientset.Interface, token string, failIfExists // Secret does not already exist: if apierrors.IsNotFound(err) { + tokenSecretData, err := encodeTokenSecretData(tokenID, tokenSecret, tokenDuration, usages, extraGroups, description) + if err != nil { + return err + } + secret = &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: secretName, }, Type: v1.SecretType(bootstrapapi.SecretTypeBootstrapToken), - Data: encodeTokenSecretData(tokenID, tokenSecret, tokenDuration, usages, extraGroups, description), + Data: tokenSecretData, } if _, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Create(secret); err == nil { return nil @@ -86,7 +95,7 @@ func UpdateOrCreateToken(client clientset.Interface, token string, failIfExists } // encodeTokenSecretData takes the token discovery object and an optional duration and returns the .Data for the Secret -func encodeTokenSecretData(tokenID, tokenSecret string, duration time.Duration, usages []string, extraGroups []string, description string) map[string][]byte { +func encodeTokenSecretData(tokenID, tokenSecret string, duration time.Duration, usages []string, extraGroups []string, description string) (map[string][]byte, error) { data := map[string][]byte{ bootstrapapi.BootstrapTokenIDKey: []byte(tokenID), bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret), @@ -104,9 +113,13 @@ func encodeTokenSecretData(tokenID, tokenSecret string, duration time.Duration, if len(description) > 0 { data[bootstrapapi.BootstrapTokenDescriptionKey] = []byte(description) } + + // validate usages + if err := bootstrapapi.ValidateUsages(usages); err != nil { + return nil, err + } for _, usage := range usages { - // TODO: Validate the usage string here before data[bootstrapapi.BootstrapTokenUsagePrefix+usage] = []byte("true") } - return data + return data, nil } diff --git a/cmd/kubeadm/app/phases/bootstraptoken/node/token_test.go b/cmd/kubeadm/app/phases/bootstraptoken/node/token_test.go index 7af575e01d1..48bc8c1f168 100644 --- a/cmd/kubeadm/app/phases/bootstraptoken/node/token_test.go +++ b/cmd/kubeadm/app/phases/bootstraptoken/node/token_test.go @@ -33,7 +33,7 @@ func TestEncodeTokenSecretData(t *testing.T) { {token: &kubeadmapi.TokenDiscovery{ID: "foo", Secret: "bar"}, t: time.Second}, // should use default } for _, rt := range tests { - actual := encodeTokenSecretData(rt.token.ID, rt.token.Secret, rt.t, []string{}, []string{}, "") + actual, _ := encodeTokenSecretData(rt.token.ID, rt.token.Secret, rt.t, []string{}, []string{}, "") if !bytes.Equal(actual["token-id"], []byte(rt.token.ID)) { t.Errorf( "failed EncodeTokenSecretData:\n\texpected: %s\n\t actual: %s", diff --git a/cmd/kubeadm/app/phases/certs/BUILD b/cmd/kubeadm/app/phases/certs/BUILD index a1009be44dd..9e50fc2c73d 100644 --- a/cmd/kubeadm/app/phases/certs/BUILD +++ b/cmd/kubeadm/app/phases/certs/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["certs_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs", - library = ":go_default_library", deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", diff --git a/cmd/kubeadm/app/phases/certs/certs_test.go b/cmd/kubeadm/app/phases/certs/certs_test.go index a22f2e4ae77..8b135100ba1 100644 --- a/cmd/kubeadm/app/phases/certs/certs_test.go +++ b/cmd/kubeadm/app/phases/certs/certs_test.go @@ -262,9 +262,10 @@ func TestGetAltNames(t *testing.T) { hostname := "valid-hostname" advertiseIP := "1.2.3.4" cfg := &kubeadmapi.MasterConfiguration{ - API: kubeadmapi.API{AdvertiseAddress: advertiseIP}, - Networking: kubeadmapi.Networking{ServiceSubnet: "10.96.0.0/12", DNSDomain: "cluster.local"}, - NodeName: hostname, + API: kubeadmapi.API{AdvertiseAddress: advertiseIP}, + Networking: kubeadmapi.Networking{ServiceSubnet: "10.96.0.0/12", DNSDomain: "cluster.local"}, + NodeName: hostname, + APIServerCertSANs: []string{"10.1.245.94", "10.1.245.95"}, } altNames, err := getAltNames(cfg) @@ -287,7 +288,7 @@ func TestGetAltNames(t *testing.T) { } } - expectedIPAddresses := []string{"10.96.0.1", advertiseIP} + expectedIPAddresses := []string{"10.96.0.1", advertiseIP, "10.1.245.94", "10.1.245.95"} for _, IPAddress := range expectedIPAddresses { found := false for _, val := range altNames.IPs { @@ -323,6 +324,9 @@ func TestNewAPIServerCertAndKey(t *testing.T) { NodeName: "valid-hostname", } caCert, caKey, err := NewCACertAndKey() + if err != nil { + t.Fatalf("failed creation of ca cert and key: %v", err) + } apiServerCert, _, err := NewAPIServerCertAndKey(cfg, caCert, caKey) if err != nil { @@ -338,6 +342,9 @@ func TestNewAPIServerCertAndKey(t *testing.T) { func TestNewAPIServerKubeletClientCertAndKey(t *testing.T) { caCert, caKey, err := NewCACertAndKey() + if err != nil { + t.Fatalf("failed creation of ca cert and key: %v", err) + } apiClientCert, _, err := NewAPIServerKubeletClientCertAndKey(caCert, caKey) if err != nil { @@ -372,6 +379,9 @@ func TestNewFrontProxyCACertAndKey(t *testing.T) { func TestNewFrontProxyClientCertAndKey(t *testing.T) { frontProxyCACert, frontProxyCAKey, err := NewFrontProxyCACertAndKey() + if err != nil { + t.Fatalf("failed creation of ca cert and key: %v", err) + } frontProxyClientCert, _, err := NewFrontProxyClientCertAndKey(frontProxyCACert, frontProxyCAKey) if err != nil { diff --git a/cmd/kubeadm/app/phases/certs/pkiutil/BUILD b/cmd/kubeadm/app/phases/certs/pkiutil/BUILD index 6f0798ab08d..56f7a9383a5 100644 --- a/cmd/kubeadm/app/phases/certs/pkiutil/BUILD +++ b/cmd/kubeadm/app/phases/certs/pkiutil/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["pki_helpers_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil", - library = ":go_default_library", deps = ["//vendor/k8s.io/client-go/util/cert:go_default_library"], ) diff --git a/cmd/kubeadm/app/phases/certs/pkiutil/pki_helpers.go b/cmd/kubeadm/app/phases/certs/pkiutil/pki_helpers.go index 073d84c2c66..170973e4f18 100644 --- a/cmd/kubeadm/app/phases/certs/pkiutil/pki_helpers.go +++ b/cmd/kubeadm/app/phases/certs/pkiutil/pki_helpers.go @@ -76,11 +76,7 @@ func WriteCertAndKey(pkiPath string, name string, cert *x509.Certificate, key *r return err } - if err := WriteCert(pkiPath, name, cert); err != nil { - return err - } - - return nil + return WriteCert(pkiPath, name, cert) } // WriteCert stores the given certificate at the given location diff --git a/cmd/kubeadm/app/phases/controlplane/BUILD b/cmd/kubeadm/app/phases/controlplane/BUILD index 3a1bed492f4..830d6ae90bd 100644 --- a/cmd/kubeadm/app/phases/controlplane/BUILD +++ b/cmd/kubeadm/app/phases/controlplane/BUILD @@ -12,13 +12,15 @@ go_test( "manifests_test.go", "volumes_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane", - library = ":go_default_library", deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", + "//cmd/kubeadm/app/features:go_default_library", "//cmd/kubeadm/app/phases/certs:go_default_library", "//cmd/kubeadm/test:go_default_library", + "//pkg/master/reconcilers:go_default_library", "//pkg/util/version:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", ], @@ -35,11 +37,13 @@ go_library( "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", + "//cmd/kubeadm/app/features:go_default_library", "//cmd/kubeadm/app/images:go_default_library", "//cmd/kubeadm/app/phases/certs:go_default_library", "//cmd/kubeadm/app/util:go_default_library", "//cmd/kubeadm/app/util/staticpod:go_default_library", "//pkg/kubeapiserver/authorizer/modes:go_default_library", + "//pkg/master/reconcilers:go_default_library", "//pkg/util/version:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", diff --git a/cmd/kubeadm/app/phases/controlplane/manifests.go b/cmd/kubeadm/app/phases/controlplane/manifests.go index fe5433cba20..f1ae775f864 100644 --- a/cmd/kubeadm/app/phases/controlplane/manifests.go +++ b/cmd/kubeadm/app/phases/controlplane/manifests.go @@ -28,11 +28,13 @@ import ( kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" + "k8s.io/kubernetes/cmd/kubeadm/app/features" "k8s.io/kubernetes/cmd/kubeadm/app/images" certphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" staticpodutil "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod" authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes" + "k8s.io/kubernetes/pkg/master/reconcilers" "k8s.io/kubernetes/pkg/util/version" ) @@ -40,8 +42,9 @@ import ( const ( DefaultCloudConfigPath = "/etc/kubernetes/cloud-config" - defaultV18AdmissionControl = "Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,ResourceQuota" - defaultV19AdmissionControl = "Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,Priority,ResourceQuota" + defaultV18AdmissionControl = "Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,ResourceQuota" + deprecatedV19AdmissionControl = "Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,ResourceQuota" + defaultV19AdmissionControl = "Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,ResourceQuota" ) // CreateInitStaticPodManifestFiles will write all static pod manifest files needed to bring up the control plane. @@ -67,7 +70,6 @@ func CreateSchedulerStaticPodManifestFile(manifestDir string, cfg *kubeadmapi.Ma // GetStaticPodSpecs returns all staticPodSpecs actualized to the context of the current MasterConfiguration // NB. this methods holds the information about how kubeadm creates static pod mainfests. func GetStaticPodSpecs(cfg *kubeadmapi.MasterConfiguration, k8sVersion *version.Version) map[string]v1.Pod { - // Get the required hostpath mounts mounts := getHostPathVolumesForTheControlPlane(cfg) @@ -77,8 +79,8 @@ func GetStaticPodSpecs(cfg *kubeadmapi.MasterConfiguration, k8sVersion *version. Name: kubeadmconstants.KubeAPIServer, Image: images.GetCoreImage(kubeadmconstants.KubeAPIServer, cfg.GetControlPlaneImageRepository(), cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage), Command: getAPIServerCommand(cfg, k8sVersion), - VolumeMounts: mounts.GetVolumeMounts(kubeadmconstants.KubeAPIServer), - LivenessProbe: staticpodutil.ComponentProbe(int(cfg.API.BindPort), "/healthz", v1.URISchemeHTTPS), + VolumeMounts: staticpodutil.VolumeMountMapToSlice(mounts.GetVolumeMounts(kubeadmconstants.KubeAPIServer)), + LivenessProbe: staticpodutil.ComponentProbe(cfg, kubeadmconstants.KubeAPIServer, int(cfg.API.BindPort), "/healthz", v1.URISchemeHTTPS), Resources: staticpodutil.ComponentResources("250m"), Env: getProxyEnvVars(), }, mounts.GetVolumes(kubeadmconstants.KubeAPIServer)), @@ -86,8 +88,8 @@ func GetStaticPodSpecs(cfg *kubeadmapi.MasterConfiguration, k8sVersion *version. Name: kubeadmconstants.KubeControllerManager, Image: images.GetCoreImage(kubeadmconstants.KubeControllerManager, cfg.GetControlPlaneImageRepository(), cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage), Command: getControllerManagerCommand(cfg, k8sVersion), - VolumeMounts: mounts.GetVolumeMounts(kubeadmconstants.KubeControllerManager), - LivenessProbe: staticpodutil.ComponentProbe(10252, "/healthz", v1.URISchemeHTTP), + VolumeMounts: staticpodutil.VolumeMountMapToSlice(mounts.GetVolumeMounts(kubeadmconstants.KubeControllerManager)), + LivenessProbe: staticpodutil.ComponentProbe(cfg, kubeadmconstants.KubeControllerManager, 10252, "/healthz", v1.URISchemeHTTP), Resources: staticpodutil.ComponentResources("200m"), Env: getProxyEnvVars(), }, mounts.GetVolumes(kubeadmconstants.KubeControllerManager)), @@ -95,8 +97,8 @@ func GetStaticPodSpecs(cfg *kubeadmapi.MasterConfiguration, k8sVersion *version. Name: kubeadmconstants.KubeScheduler, Image: images.GetCoreImage(kubeadmconstants.KubeScheduler, cfg.GetControlPlaneImageRepository(), cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage), Command: getSchedulerCommand(cfg), - VolumeMounts: mounts.GetVolumeMounts(kubeadmconstants.KubeScheduler), - LivenessProbe: staticpodutil.ComponentProbe(10251, "/healthz", v1.URISchemeHTTP), + VolumeMounts: staticpodutil.VolumeMountMapToSlice(mounts.GetVolumeMounts(kubeadmconstants.KubeScheduler)), + LivenessProbe: staticpodutil.ComponentProbe(cfg, kubeadmconstants.KubeScheduler, 10251, "/healthz", v1.URISchemeHTTP), Resources: staticpodutil.ComponentResources("100m"), Env: getProxyEnvVars(), }, mounts.GetVolumes(kubeadmconstants.KubeScheduler)), @@ -107,7 +109,6 @@ func GetStaticPodSpecs(cfg *kubeadmapi.MasterConfiguration, k8sVersion *version. // createStaticPodFiles creates all the requested static pod files. func createStaticPodFiles(manifestDir string, cfg *kubeadmapi.MasterConfiguration, componentNames ...string) error { - // TODO: Move the "pkg/util/version".Version object into the internal API instead of always parsing the string k8sVersion, err := version.ParseSemantic(cfg.KubernetesVersion) if err != nil { @@ -170,6 +171,10 @@ func getAPIServerCommand(cfg *kubeadmapi.MasterConfiguration, k8sVersion *versio defaultArguments["admission-control"] = defaultV18AdmissionControl } + if cfg.CloudProvider == "aws" || cfg.CloudProvider == "gce" { + defaultArguments["admission-control"] = deprecatedV19AdmissionControl + } + command = append(command, kubeadmutil.BuildArgumentListFromMap(defaultArguments, cfg.APIServerExtraArgs)...) command = append(command, getAuthzParameters(cfg.AuthorizationModes)...) @@ -199,12 +204,19 @@ func getAPIServerCommand(cfg *kubeadmapi.MasterConfiguration, k8sVersion *versio } } + if features.Enabled(cfg.FeatureGates, features.HighAvailability) { + command = append(command, "--endpoint-reconciler-type="+reconcilers.LeaseEndpointReconcilerType) + } + + if features.Enabled(cfg.FeatureGates, features.DynamicKubeletConfig) { + command = append(command, "--feature-gates=DynamicKubeletConfig=true") + } + return command } // getControllerManagerCommand builds the right controller manager command from the given config object and version func getControllerManagerCommand(cfg *kubeadmapi.MasterConfiguration, k8sVersion *version.Version) []string { - defaultArguments := map[string]string{ "address": "127.0.0.1", "leader-elect": "true", diff --git a/cmd/kubeadm/app/phases/controlplane/manifests_test.go b/cmd/kubeadm/app/phases/controlplane/manifests_test.go index e59dcddcbd1..4764bf534f5 100644 --- a/cmd/kubeadm/app/phases/controlplane/manifests_test.go +++ b/cmd/kubeadm/app/phases/controlplane/manifests_test.go @@ -26,7 +26,9 @@ import ( kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" + "k8s.io/kubernetes/cmd/kubeadm/app/features" "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs" + "k8s.io/kubernetes/pkg/master/reconcilers" "k8s.io/kubernetes/pkg/util/version" testutil "k8s.io/kubernetes/cmd/kubeadm/test" @@ -362,7 +364,7 @@ func TestGetAPIServerCommand(t *testing.T) { expected: []string{ "kube-apiserver", "--insecure-port=0", - "--admission-control=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,Priority,ResourceQuota", + "--admission-control=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,ResourceQuota", "--service-cluster-ip-range=bar", "--service-account-key-file=" + testCertsDir + "/sa.pub", "--client-ca-file=" + testCertsDir + "/ca.crt", @@ -388,6 +390,114 @@ func TestGetAPIServerCommand(t *testing.T) { "--etcd-keyfile=faz", }, }, + { + cfg: &kubeadmapi.MasterConfiguration{ + API: kubeadmapi.API{BindPort: 123, AdvertiseAddress: "2001:db8::1"}, + Networking: kubeadmapi.Networking{ServiceSubnet: "bar"}, + FeatureGates: map[string]bool{features.HighAvailability: true}, + CertificatesDir: testCertsDir, + KubernetesVersion: "v1.9.0-beta.0", + }, + expected: []string{ + "kube-apiserver", + "--insecure-port=0", + "--admission-control=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,ResourceQuota", + "--service-cluster-ip-range=bar", + "--service-account-key-file=" + testCertsDir + "/sa.pub", + "--client-ca-file=" + testCertsDir + "/ca.crt", + "--tls-cert-file=" + testCertsDir + "/apiserver.crt", + "--tls-private-key-file=" + testCertsDir + "/apiserver.key", + "--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt", + "--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key", + fmt.Sprintf("--secure-port=%d", 123), + "--allow-privileged=true", + "--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname", + "--enable-bootstrap-token-auth=true", + "--proxy-client-cert-file=/var/lib/certs/front-proxy-client.crt", + "--proxy-client-key-file=/var/lib/certs/front-proxy-client.key", + "--requestheader-username-headers=X-Remote-User", + "--requestheader-group-headers=X-Remote-Group", + "--requestheader-extra-headers-prefix=X-Remote-Extra-", + "--requestheader-client-ca-file=" + testCertsDir + "/front-proxy-ca.crt", + "--requestheader-allowed-names=front-proxy-client", + "--authorization-mode=Node,RBAC", + "--advertise-address=2001:db8::1", + "--etcd-servers=http://127.0.0.1:2379", + fmt.Sprintf("--endpoint-reconciler-type=%s", reconcilers.LeaseEndpointReconcilerType), + }, + }, + { + cfg: &kubeadmapi.MasterConfiguration{ + API: kubeadmapi.API{BindPort: 123, AdvertiseAddress: "1.2.3.4"}, + Networking: kubeadmapi.Networking{ServiceSubnet: "bar"}, + CertificatesDir: testCertsDir, + KubernetesVersion: "v1.9.0-beta.0", + CloudProvider: "gce", + }, + expected: []string{ + "kube-apiserver", + "--insecure-port=0", + "--admission-control=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,ResourceQuota", + "--service-cluster-ip-range=bar", + "--service-account-key-file=" + testCertsDir + "/sa.pub", + "--client-ca-file=" + testCertsDir + "/ca.crt", + "--tls-cert-file=" + testCertsDir + "/apiserver.crt", + "--tls-private-key-file=" + testCertsDir + "/apiserver.key", + "--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt", + "--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key", + "--enable-bootstrap-token-auth=true", + "--secure-port=123", + "--allow-privileged=true", + "--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname", + "--proxy-client-cert-file=/var/lib/certs/front-proxy-client.crt", + "--proxy-client-key-file=/var/lib/certs/front-proxy-client.key", + "--requestheader-username-headers=X-Remote-User", + "--requestheader-group-headers=X-Remote-Group", + "--requestheader-extra-headers-prefix=X-Remote-Extra-", + "--requestheader-client-ca-file=" + testCertsDir + "/front-proxy-ca.crt", + "--requestheader-allowed-names=front-proxy-client", + "--authorization-mode=Node,RBAC", + "--advertise-address=1.2.3.4", + "--etcd-servers=http://127.0.0.1:2379", + "--cloud-provider=gce", + }, + }, + { + cfg: &kubeadmapi.MasterConfiguration{ + API: kubeadmapi.API{BindPort: 123, AdvertiseAddress: "1.2.3.4"}, + Networking: kubeadmapi.Networking{ServiceSubnet: "bar"}, + CertificatesDir: testCertsDir, + KubernetesVersion: "v1.9.0-beta.0", + CloudProvider: "aws", + }, + expected: []string{ + "kube-apiserver", + "--insecure-port=0", + "--admission-control=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,ResourceQuota", + "--service-cluster-ip-range=bar", + "--service-account-key-file=" + testCertsDir + "/sa.pub", + "--client-ca-file=" + testCertsDir + "/ca.crt", + "--tls-cert-file=" + testCertsDir + "/apiserver.crt", + "--tls-private-key-file=" + testCertsDir + "/apiserver.key", + "--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt", + "--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key", + "--enable-bootstrap-token-auth=true", + "--secure-port=123", + "--allow-privileged=true", + "--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname", + "--proxy-client-cert-file=/var/lib/certs/front-proxy-client.crt", + "--proxy-client-key-file=/var/lib/certs/front-proxy-client.key", + "--requestheader-username-headers=X-Remote-User", + "--requestheader-group-headers=X-Remote-Group", + "--requestheader-extra-headers-prefix=X-Remote-Extra-", + "--requestheader-client-ca-file=" + testCertsDir + "/front-proxy-ca.crt", + "--requestheader-allowed-names=front-proxy-client", + "--authorization-mode=Node,RBAC", + "--advertise-address=1.2.3.4", + "--etcd-servers=http://127.0.0.1:2379", + "--cloud-provider=aws", + }, + }, } for _, rt := range tests { diff --git a/cmd/kubeadm/app/phases/controlplane/volumes.go b/cmd/kubeadm/app/phases/controlplane/volumes.go index a9c69d01b91..a3b5ce7fefc 100644 --- a/cmd/kubeadm/app/phases/controlplane/volumes.go +++ b/cmd/kubeadm/app/phases/controlplane/volumes.go @@ -34,6 +34,7 @@ const ( caCertsVolumePath = "/etc/ssl/certs" caCertsPkiVolumeName = "ca-certs-etc-pki" flexvolumeDirVolumeName = "flexvolume-dir" + cloudConfigVolumeName = "cloud-config" flexvolumeDirVolumePath = "/usr/libexec/kubernetes/kubelet-plugins/volume/exec" ) @@ -57,7 +58,7 @@ func getHostPathVolumesForTheControlPlane(cfg *kubeadmapi.MasterConfiguration) c // If external etcd is specified, mount the directories needed for accessing the CA/serving certs and the private key if len(cfg.Etcd.Endpoints) != 0 { - etcdVols, etcdVolMounts := getEtcdCertVolumes(cfg.Etcd) + etcdVols, etcdVolMounts := getEtcdCertVolumes(cfg.Etcd, cfg.CertificatesDir) mounts.AddHostPathMounts(kubeadmconstants.KubeAPIServer, etcdVols, etcdVolMounts) } @@ -70,9 +71,18 @@ func getHostPathVolumesForTheControlPlane(cfg *kubeadmapi.MasterConfiguration) c // 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) + // Read-only mount of the cloud config file if present + if cfg.CloudProvider != "" { + if _, err := os.Stat(DefaultCloudConfigPath); err == nil { + mounts.NewHostPathMount(kubeadmconstants.KubeAPIServer, cloudConfigVolumeName, DefaultCloudConfigPath, DefaultCloudConfigPath, true, &hostPathFileOrCreate) + mounts.NewHostPathMount(kubeadmconstants.KubeControllerManager, cloudConfigVolumeName, DefaultCloudConfigPath, DefaultCloudConfigPath, true, &hostPathFileOrCreate) + } + } // Mount for the flexvolume directory (/usr/libexec/kubernetes/kubelet-plugins/volume/exec) directory // Flexvolume dir must NOT be readonly as it is used for third-party plugins to integrate with their storage backends via unix domain socket. - mounts.NewHostPathMount(kubeadmconstants.KubeControllerManager, flexvolumeDirVolumeName, flexvolumeDirVolumePath, flexvolumeDirVolumePath, false, &hostPathDirectoryOrCreate) + if stat, err := os.Stat(flexvolumeDirVolumePath); err == nil && stat.IsDir() { + mounts.NewHostPathMount(kubeadmconstants.KubeControllerManager, flexvolumeDirVolumeName, flexvolumeDirVolumePath, flexvolumeDirVolumePath, false, &hostPathDirectoryOrCreate) + } // HostPath volumes for the scheduler // Read-only mount for the scheduler kubeconfig file @@ -86,49 +96,95 @@ func getHostPathVolumesForTheControlPlane(cfg *kubeadmapi.MasterConfiguration) c mounts.NewHostPathMount(kubeadmconstants.KubeControllerManager, caCertsPkiVolumeName, caCertsPkiVolumePath, caCertsPkiVolumePath, true, &hostPathDirectoryOrCreate) } + // Merge user defined mounts and ensure unique volume and volume mount + // names + mounts.AddExtraHostPathMounts(kubeadmconstants.KubeAPIServer, cfg.APIServerExtraVolumes, true, &hostPathDirectoryOrCreate) + mounts.AddExtraHostPathMounts(kubeadmconstants.KubeControllerManager, cfg.ControllerManagerExtraVolumes, true, &hostPathDirectoryOrCreate) + mounts.AddExtraHostPathMounts(kubeadmconstants.KubeScheduler, cfg.SchedulerExtraVolumes, true, &hostPathDirectoryOrCreate) + return mounts } // controlPlaneHostPathMounts is a helper struct for handling all the control plane's hostPath mounts in an easy way type controlPlaneHostPathMounts struct { - volumes map[string][]v1.Volume - volumeMounts map[string][]v1.VolumeMount + // volumes is a nested map that forces a unique volumes. The outer map's + // keys are a string that should specify the target component to add the + // volume to. The values (inner map) of the outer map are maps with string + // keys and v1.Volume values. The inner map's key should specify the volume + // name. + volumes map[string]map[string]v1.Volume + // volumeMounts is a nested map that forces a unique volume mounts. The + // outer map's keys are a string that should specify the target component + // to add the volume mount to. The values (inner map) of the outer map are + // maps with string keys and v1.VolumeMount values. The inner map's key + // should specify the volume mount name. + volumeMounts map[string]map[string]v1.VolumeMount } func newControlPlaneHostPathMounts() controlPlaneHostPathMounts { return controlPlaneHostPathMounts{ - volumes: map[string][]v1.Volume{}, - volumeMounts: map[string][]v1.VolumeMount{}, + volumes: map[string]map[string]v1.Volume{}, + volumeMounts: map[string]map[string]v1.VolumeMount{}, } } func (c *controlPlaneHostPathMounts) NewHostPathMount(component, mountName, hostPath, containerPath string, readOnly bool, hostPathType *v1.HostPathType) { - c.volumes[component] = append(c.volumes[component], staticpodutil.NewVolume(mountName, hostPath, hostPathType)) - c.volumeMounts[component] = append(c.volumeMounts[component], staticpodutil.NewVolumeMount(mountName, containerPath, readOnly)) + vol := staticpodutil.NewVolume(mountName, hostPath, hostPathType) + c.addComponentVolume(component, vol) + volMount := staticpodutil.NewVolumeMount(mountName, containerPath, readOnly) + c.addComponentVolumeMount(component, volMount) } func (c *controlPlaneHostPathMounts) AddHostPathMounts(component string, vols []v1.Volume, volMounts []v1.VolumeMount) { - c.volumes[component] = append(c.volumes[component], vols...) - c.volumeMounts[component] = append(c.volumeMounts[component], volMounts...) + for _, v := range vols { + c.addComponentVolume(component, v) + } + for _, v := range volMounts { + c.addComponentVolumeMount(component, v) + } } -func (c *controlPlaneHostPathMounts) GetVolumes(component string) []v1.Volume { +// AddExtraHostPathMounts adds host path mounts and overwrites the default +// paths in the case that a user specifies the same volume/volume mount name. +func (c *controlPlaneHostPathMounts) AddExtraHostPathMounts(component string, extraVols []kubeadmapi.HostPathMount, readOnly bool, hostPathType *v1.HostPathType) { + for _, extraVol := range extraVols { + fmt.Printf("[controlplane] Adding extra host path mount %q to %q\n", extraVol.Name, component) + c.NewHostPathMount(component, extraVol.Name, extraVol.HostPath, extraVol.MountPath, readOnly, hostPathType) + } +} + +func (c *controlPlaneHostPathMounts) GetVolumes(component string) map[string]v1.Volume { return c.volumes[component] } -func (c *controlPlaneHostPathMounts) GetVolumeMounts(component string) []v1.VolumeMount { +func (c *controlPlaneHostPathMounts) GetVolumeMounts(component string) map[string]v1.VolumeMount { return c.volumeMounts[component] } +func (c *controlPlaneHostPathMounts) addComponentVolume(component string, vol v1.Volume) { + if _, ok := c.volumes[component]; !ok { + c.volumes[component] = map[string]v1.Volume{} + } + c.volumes[component][vol.Name] = vol +} + +func (c *controlPlaneHostPathMounts) addComponentVolumeMount(component string, volMount v1.VolumeMount) { + if _, ok := c.volumeMounts[component]; !ok { + c.volumeMounts[component] = map[string]v1.VolumeMount{} + } + c.volumeMounts[component][volMount.Name] = volMount +} + // getEtcdCertVolumes returns the volumes/volumemounts needed for talking to an external etcd cluster -func getEtcdCertVolumes(etcdCfg kubeadmapi.Etcd) ([]v1.Volume, []v1.VolumeMount) { +func getEtcdCertVolumes(etcdCfg kubeadmapi.Etcd, k8sCertificatesDir string) ([]v1.Volume, []v1.VolumeMount) { certPaths := []string{etcdCfg.CAFile, etcdCfg.CertFile, etcdCfg.KeyFile} certDirs := sets.NewString() for _, certPath := range certPaths { certDir := filepath.Dir(certPath) // Ignore ".", which is the result of passing an empty path. - // Also ignore the cert directories that already may be mounted; /etc/ssl/certs and /etc/pki. If the etcd certs are in there, it's okay, we don't have to do anything - if certDir == "." || strings.HasPrefix(certDir, caCertsVolumePath) || strings.HasPrefix(certDir, caCertsPkiVolumePath) { + // Also ignore the cert directories that already may be mounted; /etc/ssl/certs, /etc/pki or Kubernetes CertificatesDir + // If the etcd certs are in there, it's okay, we don't have to do anything + if certDir == "." || strings.HasPrefix(certDir, caCertsVolumePath) || strings.HasPrefix(certDir, caCertsPkiVolumePath) || strings.HasPrefix(certDir, k8sCertificatesDir) { continue } // Filter out any existing hostpath mounts in the list that contains a subset of the path diff --git a/cmd/kubeadm/app/phases/controlplane/volumes_test.go b/cmd/kubeadm/app/phases/controlplane/volumes_test.go index af784c0e64b..a5fde3cf035 100644 --- a/cmd/kubeadm/app/phases/controlplane/volumes_test.go +++ b/cmd/kubeadm/app/phases/controlplane/volumes_test.go @@ -30,6 +30,7 @@ import ( func TestGetEtcdCertVolumes(t *testing.T) { hostPathDirectoryOrCreate := v1.HostPathDirectoryOrCreate + k8sCertifcatesDir := "/etc/kubernetes/pki" var tests = []struct { ca, cert, key string vol []v1.Volume @@ -59,6 +60,14 @@ func TestGetEtcdCertVolumes(t *testing.T) { vol: []v1.Volume{}, volMount: []v1.VolumeMount{}, }, + { + // Should ignore files in Kubernetes PKI directory (and subdirs) + ca: k8sCertifcatesDir + "/ca/my-etcd-ca.crt", + cert: k8sCertifcatesDir + "/my-etcd.crt", + key: k8sCertifcatesDir + "/my-etcd.key", + vol: []v1.Volume{}, + volMount: []v1.VolumeMount{}, + }, { // All in the same dir ca: "/var/lib/certs/etcd/my-etcd-ca.crt", @@ -228,7 +237,7 @@ func TestGetEtcdCertVolumes(t *testing.T) { CAFile: rt.ca, CertFile: rt.cert, KeyFile: rt.key, - }) + }, k8sCertifcatesDir) if !reflect.DeepEqual(actualVol, rt.vol) { t.Errorf( "failed getEtcdCertVolumes:\n\texpected: %v\n\t actual: %v", @@ -249,10 +258,223 @@ func TestGetEtcdCertVolumes(t *testing.T) { func TestGetHostPathVolumesForTheControlPlane(t *testing.T) { hostPathDirectoryOrCreate := v1.HostPathDirectoryOrCreate hostPathFileOrCreate := v1.HostPathFileOrCreate + volMap := make(map[string]map[string]v1.Volume) + volMap[kubeadmconstants.KubeAPIServer] = map[string]v1.Volume{} + volMap[kubeadmconstants.KubeAPIServer]["k8s-certs"] = v1.Volume{ + Name: "k8s-certs", + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{ + Path: testCertsDir, + Type: &hostPathDirectoryOrCreate, + }, + }, + } + volMap[kubeadmconstants.KubeAPIServer]["ca-certs"] = v1.Volume{ + Name: "ca-certs", + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{ + Path: "/etc/ssl/certs", + Type: &hostPathDirectoryOrCreate, + }, + }, + } + volMap[kubeadmconstants.KubeControllerManager] = map[string]v1.Volume{} + volMap[kubeadmconstants.KubeControllerManager]["k8s-certs"] = v1.Volume{ + Name: "k8s-certs", + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{ + Path: testCertsDir, + Type: &hostPathDirectoryOrCreate, + }, + }, + } + volMap[kubeadmconstants.KubeControllerManager]["ca-certs"] = v1.Volume{ + Name: "ca-certs", + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{ + Path: "/etc/ssl/certs", + Type: &hostPathDirectoryOrCreate, + }, + }, + } + volMap[kubeadmconstants.KubeControllerManager]["kubeconfig"] = v1.Volume{ + Name: "kubeconfig", + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{ + Path: "/etc/kubernetes/controller-manager.conf", + Type: &hostPathFileOrCreate, + }, + }, + } + volMap[kubeadmconstants.KubeScheduler] = map[string]v1.Volume{} + volMap[kubeadmconstants.KubeScheduler]["kubeconfig"] = v1.Volume{ + Name: "kubeconfig", + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{ + Path: "/etc/kubernetes/scheduler.conf", + Type: &hostPathFileOrCreate, + }, + }, + } + volMountMap := make(map[string]map[string]v1.VolumeMount) + volMountMap[kubeadmconstants.KubeAPIServer] = map[string]v1.VolumeMount{} + volMountMap[kubeadmconstants.KubeAPIServer]["k8s-certs"] = v1.VolumeMount{ + Name: "k8s-certs", + MountPath: testCertsDir, + ReadOnly: true, + } + volMountMap[kubeadmconstants.KubeAPIServer]["ca-certs"] = v1.VolumeMount{ + Name: "ca-certs", + MountPath: "/etc/ssl/certs", + ReadOnly: true, + } + volMountMap[kubeadmconstants.KubeControllerManager] = map[string]v1.VolumeMount{} + volMountMap[kubeadmconstants.KubeControllerManager]["k8s-certs"] = v1.VolumeMount{ + Name: "k8s-certs", + MountPath: testCertsDir, + ReadOnly: true, + } + volMountMap[kubeadmconstants.KubeControllerManager]["ca-certs"] = v1.VolumeMount{ + Name: "ca-certs", + MountPath: "/etc/ssl/certs", + ReadOnly: true, + } + volMountMap[kubeadmconstants.KubeControllerManager]["kubeconfig"] = v1.VolumeMount{ + Name: "kubeconfig", + MountPath: "/etc/kubernetes/controller-manager.conf", + ReadOnly: true, + } + volMountMap[kubeadmconstants.KubeScheduler] = map[string]v1.VolumeMount{} + volMountMap[kubeadmconstants.KubeScheduler]["kubeconfig"] = v1.VolumeMount{ + Name: "kubeconfig", + MountPath: "/etc/kubernetes/scheduler.conf", + ReadOnly: true, + } + + volMap2 := make(map[string]map[string]v1.Volume) + volMap2[kubeadmconstants.KubeAPIServer] = map[string]v1.Volume{} + volMap2[kubeadmconstants.KubeAPIServer]["k8s-certs"] = v1.Volume{ + Name: "k8s-certs", + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{ + Path: testCertsDir, + Type: &hostPathDirectoryOrCreate, + }, + }, + } + volMap2[kubeadmconstants.KubeAPIServer]["ca-certs"] = v1.Volume{ + Name: "ca-certs", + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{ + Path: "/etc/ssl/certs", + Type: &hostPathDirectoryOrCreate, + }, + }, + } + volMap2[kubeadmconstants.KubeAPIServer]["etcd-certs-0"] = v1.Volume{ + Name: "etcd-certs-0", + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{ + Path: "/etc/certs/etcd", + Type: &hostPathDirectoryOrCreate, + }, + }, + } + volMap2[kubeadmconstants.KubeAPIServer]["etcd-certs-1"] = v1.Volume{ + Name: "etcd-certs-1", + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{ + Path: "/var/lib/etcd/certs", + Type: &hostPathDirectoryOrCreate, + }, + }, + } + volMap2[kubeadmconstants.KubeControllerManager] = map[string]v1.Volume{} + volMap2[kubeadmconstants.KubeControllerManager]["k8s-certs"] = v1.Volume{ + Name: "k8s-certs", + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{ + Path: testCertsDir, + Type: &hostPathDirectoryOrCreate, + }, + }, + } + volMap2[kubeadmconstants.KubeControllerManager]["ca-certs"] = v1.Volume{ + Name: "ca-certs", + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{ + Path: "/etc/ssl/certs", + Type: &hostPathDirectoryOrCreate, + }, + }, + } + volMap2[kubeadmconstants.KubeControllerManager]["kubeconfig"] = v1.Volume{ + Name: "kubeconfig", + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{ + Path: "/etc/kubernetes/controller-manager.conf", + Type: &hostPathFileOrCreate, + }, + }, + } + volMap2[kubeadmconstants.KubeScheduler] = map[string]v1.Volume{} + volMap2[kubeadmconstants.KubeScheduler]["kubeconfig"] = v1.Volume{ + Name: "kubeconfig", + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{ + Path: "/etc/kubernetes/scheduler.conf", + Type: &hostPathFileOrCreate, + }, + }, + } + volMountMap2 := make(map[string]map[string]v1.VolumeMount) + volMountMap2[kubeadmconstants.KubeAPIServer] = map[string]v1.VolumeMount{} + volMountMap2[kubeadmconstants.KubeAPIServer]["k8s-certs"] = v1.VolumeMount{ + Name: "k8s-certs", + MountPath: testCertsDir, + ReadOnly: true, + } + volMountMap2[kubeadmconstants.KubeAPIServer]["ca-certs"] = v1.VolumeMount{ + Name: "ca-certs", + MountPath: "/etc/ssl/certs", + ReadOnly: true, + } + volMountMap2[kubeadmconstants.KubeAPIServer]["etcd-certs-0"] = v1.VolumeMount{ + Name: "etcd-certs-0", + MountPath: "/etc/certs/etcd", + ReadOnly: true, + } + volMountMap2[kubeadmconstants.KubeAPIServer]["etcd-certs-1"] = v1.VolumeMount{ + Name: "etcd-certs-1", + MountPath: "/var/lib/etcd/certs", + ReadOnly: true, + } + volMountMap2[kubeadmconstants.KubeControllerManager] = map[string]v1.VolumeMount{} + volMountMap2[kubeadmconstants.KubeControllerManager]["k8s-certs"] = v1.VolumeMount{ + Name: "k8s-certs", + MountPath: testCertsDir, + ReadOnly: true, + } + volMountMap2[kubeadmconstants.KubeControllerManager]["ca-certs"] = v1.VolumeMount{ + Name: "ca-certs", + MountPath: "/etc/ssl/certs", + ReadOnly: true, + } + volMountMap2[kubeadmconstants.KubeControllerManager]["kubeconfig"] = v1.VolumeMount{ + Name: "kubeconfig", + MountPath: "/etc/kubernetes/controller-manager.conf", + ReadOnly: true, + } + volMountMap2[kubeadmconstants.KubeScheduler] = map[string]v1.VolumeMount{} + volMountMap2[kubeadmconstants.KubeScheduler]["kubeconfig"] = v1.VolumeMount{ + Name: "kubeconfig", + MountPath: "/etc/kubernetes/scheduler.conf", + ReadOnly: true, + } var tests = []struct { cfg *kubeadmapi.MasterConfiguration - vol map[string][]v1.Volume - volMount map[string][]v1.VolumeMount + vol map[string]map[string]v1.Volume + volMount map[string]map[string]v1.VolumeMount }{ { // Should ignore files in /etc/ssl/certs @@ -260,274 +482,22 @@ func TestGetHostPathVolumesForTheControlPlane(t *testing.T) { CertificatesDir: testCertsDir, Etcd: kubeadmapi.Etcd{}, }, - vol: map[string][]v1.Volume{ - kubeadmconstants.KubeAPIServer: { - { - Name: "k8s-certs", - VolumeSource: v1.VolumeSource{ - HostPath: &v1.HostPathVolumeSource{ - Path: testCertsDir, - Type: &hostPathDirectoryOrCreate, - }, - }, - }, - { - Name: "ca-certs", - VolumeSource: v1.VolumeSource{ - HostPath: &v1.HostPathVolumeSource{ - Path: "/etc/ssl/certs", - Type: &hostPathDirectoryOrCreate, - }, - }, - }, - }, - kubeadmconstants.KubeControllerManager: { - { - Name: "k8s-certs", - VolumeSource: v1.VolumeSource{ - HostPath: &v1.HostPathVolumeSource{ - Path: testCertsDir, - Type: &hostPathDirectoryOrCreate, - }, - }, - }, - { - Name: "ca-certs", - VolumeSource: v1.VolumeSource{ - HostPath: &v1.HostPathVolumeSource{ - Path: "/etc/ssl/certs", - Type: &hostPathDirectoryOrCreate, - }, - }, - }, - { - Name: "kubeconfig", - VolumeSource: v1.VolumeSource{ - HostPath: &v1.HostPathVolumeSource{ - Path: "/etc/kubernetes/controller-manager.conf", - Type: &hostPathFileOrCreate, - }, - }, - }, - { - Name: "flexvolume-dir", - VolumeSource: v1.VolumeSource{ - HostPath: &v1.HostPathVolumeSource{ - Path: "/usr/libexec/kubernetes/kubelet-plugins/volume/exec", - Type: &hostPathDirectoryOrCreate, - }, - }, - }, - }, - kubeadmconstants.KubeScheduler: { - { - Name: "kubeconfig", - VolumeSource: v1.VolumeSource{ - HostPath: &v1.HostPathVolumeSource{ - Path: "/etc/kubernetes/scheduler.conf", - Type: &hostPathFileOrCreate, - }, - }, - }, - }, - }, - volMount: map[string][]v1.VolumeMount{ - kubeadmconstants.KubeAPIServer: { - { - Name: "k8s-certs", - MountPath: testCertsDir, - ReadOnly: true, - }, - { - Name: "ca-certs", - MountPath: "/etc/ssl/certs", - ReadOnly: true, - }, - }, - kubeadmconstants.KubeControllerManager: { - { - Name: "k8s-certs", - MountPath: testCertsDir, - ReadOnly: true, - }, - { - Name: "ca-certs", - MountPath: "/etc/ssl/certs", - ReadOnly: true, - }, - { - Name: "kubeconfig", - MountPath: "/etc/kubernetes/controller-manager.conf", - ReadOnly: true, - }, - { - Name: "flexvolume-dir", - MountPath: "/usr/libexec/kubernetes/kubelet-plugins/volume/exec", - ReadOnly: false, - }, - }, - kubeadmconstants.KubeScheduler: { - { - Name: "kubeconfig", - MountPath: "/etc/kubernetes/scheduler.conf", - ReadOnly: true, - }, - }, - }, + vol: volMap, + volMount: volMountMap, }, { - // Should ignore files in /etc/ssl/certs + // Should ignore files in /etc/ssl/certs and in CertificatesDir cfg: &kubeadmapi.MasterConfiguration{ CertificatesDir: testCertsDir, Etcd: kubeadmapi.Etcd{ Endpoints: []string{"foo"}, CAFile: "/etc/certs/etcd/my-etcd-ca.crt", - CertFile: "/var/lib/certs/etcd/my-etcd.crt", - KeyFile: "/var/lib/certs/etcd/my-etcd.key", - }, - }, - vol: map[string][]v1.Volume{ - kubeadmconstants.KubeAPIServer: { - { - Name: "k8s-certs", - VolumeSource: v1.VolumeSource{ - HostPath: &v1.HostPathVolumeSource{ - Path: testCertsDir, - Type: &hostPathDirectoryOrCreate, - }, - }, - }, - { - Name: "ca-certs", - VolumeSource: v1.VolumeSource{ - HostPath: &v1.HostPathVolumeSource{ - Path: "/etc/ssl/certs", - Type: &hostPathDirectoryOrCreate, - }, - }, - }, - { - Name: "etcd-certs-0", - VolumeSource: v1.VolumeSource{ - HostPath: &v1.HostPathVolumeSource{ - Path: "/etc/certs/etcd", - Type: &hostPathDirectoryOrCreate, - }, - }, - }, - { - Name: "etcd-certs-1", - VolumeSource: v1.VolumeSource{ - HostPath: &v1.HostPathVolumeSource{ - Path: "/var/lib/certs/etcd", - Type: &hostPathDirectoryOrCreate, - }, - }, - }, - }, - kubeadmconstants.KubeControllerManager: { - { - Name: "k8s-certs", - VolumeSource: v1.VolumeSource{ - HostPath: &v1.HostPathVolumeSource{ - Path: testCertsDir, - Type: &hostPathDirectoryOrCreate, - }, - }, - }, - { - Name: "ca-certs", - VolumeSource: v1.VolumeSource{ - HostPath: &v1.HostPathVolumeSource{ - Path: "/etc/ssl/certs", - Type: &hostPathDirectoryOrCreate, - }, - }, - }, - { - Name: "kubeconfig", - VolumeSource: v1.VolumeSource{ - HostPath: &v1.HostPathVolumeSource{ - Path: "/etc/kubernetes/controller-manager.conf", - Type: &hostPathFileOrCreate, - }, - }, - }, - { - Name: "flexvolume-dir", - VolumeSource: v1.VolumeSource{ - HostPath: &v1.HostPathVolumeSource{ - Path: "/usr/libexec/kubernetes/kubelet-plugins/volume/exec", - Type: &hostPathDirectoryOrCreate, - }, - }, - }, - }, - kubeadmconstants.KubeScheduler: { - { - Name: "kubeconfig", - VolumeSource: v1.VolumeSource{ - HostPath: &v1.HostPathVolumeSource{ - Path: "/etc/kubernetes/scheduler.conf", - Type: &hostPathFileOrCreate, - }, - }, - }, - }, - }, - volMount: map[string][]v1.VolumeMount{ - kubeadmconstants.KubeAPIServer: { - { - Name: "k8s-certs", - MountPath: testCertsDir, - ReadOnly: true, - }, - { - Name: "ca-certs", - MountPath: "/etc/ssl/certs", - ReadOnly: true, - }, - { - Name: "etcd-certs-0", - MountPath: "/etc/certs/etcd", - ReadOnly: true, - }, - { - Name: "etcd-certs-1", - MountPath: "/var/lib/certs/etcd", - ReadOnly: true, - }, - }, - kubeadmconstants.KubeControllerManager: { - { - Name: "k8s-certs", - MountPath: testCertsDir, - ReadOnly: true, - }, - { - Name: "ca-certs", - MountPath: "/etc/ssl/certs", - ReadOnly: true, - }, - { - Name: "kubeconfig", - MountPath: "/etc/kubernetes/controller-manager.conf", - ReadOnly: true, - }, - { - Name: "flexvolume-dir", - MountPath: "/usr/libexec/kubernetes/kubelet-plugins/volume/exec", - ReadOnly: false, - }, - }, - kubeadmconstants.KubeScheduler: { - { - Name: "kubeconfig", - MountPath: "/etc/kubernetes/scheduler.conf", - ReadOnly: true, - }, + CertFile: testCertsDir + "/etcd/my-etcd.crt", + KeyFile: "/var/lib/etcd/certs/my-etcd.key", }, }, + vol: volMap2, + volMount: volMountMap2, }, } @@ -543,6 +513,17 @@ func TestGetHostPathVolumesForTheControlPlane(t *testing.T) { for _, rt := range tests { mounts := getHostPathVolumesForTheControlPlane(rt.cfg) + + // Avoid unit test errors when the flexvolume is mounted + if _, ok := mounts.volumes[kubeadmconstants.KubeControllerManager][flexvolumeDirVolumeName]; ok { + delete(mounts.volumes[kubeadmconstants.KubeControllerManager], flexvolumeDirVolumeName) + } + if _, ok := mounts.volumeMounts[kubeadmconstants.KubeControllerManager][flexvolumeDirVolumeName]; ok { + delete(mounts.volumeMounts[kubeadmconstants.KubeControllerManager], flexvolumeDirVolumeName) + } + if _, ok := mounts.volumeMounts[kubeadmconstants.KubeControllerManager][cloudConfigVolumeName]; ok { + delete(mounts.volumeMounts[kubeadmconstants.KubeControllerManager], cloudConfigVolumeName) + } if !reflect.DeepEqual(mounts.volumes, rt.vol) { t.Errorf( "failed getHostPathVolumesForTheControlPlane:\n\texpected: %v\n\t actual: %v", @@ -559,3 +540,70 @@ func TestGetHostPathVolumesForTheControlPlane(t *testing.T) { } } } + +func TestAddExtraHostPathMounts(t *testing.T) { + mounts := newControlPlaneHostPathMounts() + hostPathDirectoryOrCreate := v1.HostPathDirectoryOrCreate + hostPathFileOrCreate := v1.HostPathFileOrCreate + vols := []v1.Volume{ + { + Name: "foo", + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{ + Path: "/tmp/foo", + Type: &hostPathDirectoryOrCreate, + }, + }, + }, + { + Name: "bar", + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{ + Path: "/tmp/bar", + Type: &hostPathFileOrCreate, + }, + }, + }, + } + volMounts := []v1.VolumeMount{ + { + Name: "foo", + MountPath: "/tmp/foo", + ReadOnly: true, + }, + { + Name: "bar", + MountPath: "/tmp/bar", + ReadOnly: true, + }, + } + mounts.AddHostPathMounts("component", vols, volMounts) + hostPathMounts := []kubeadmapi.HostPathMount{ + { + Name: "foo", + HostPath: "/tmp/qux", + MountPath: "/tmp/qux", + }, + } + mounts.AddExtraHostPathMounts("component", hostPathMounts, true, &hostPathDirectoryOrCreate) + if _, ok := mounts.volumes["component"]["foo"]; !ok { + t.Errorf("Expected to find volume %q", "foo") + } + vol, _ := mounts.volumes["component"]["foo"] + if vol.Name != "foo" { + t.Errorf("Expected volume name %q", "foo") + } + if vol.HostPath.Path != "/tmp/qux" { + t.Errorf("Expected host path %q", "/tmp/qux") + } + if _, ok := mounts.volumeMounts["component"]["foo"]; !ok { + t.Errorf("Expected to find volume mount %q", "foo") + } + volMount, _ := mounts.volumeMounts["component"]["foo"] + if volMount.Name != "foo" { + t.Errorf("Expected volume mount name %q", "foo") + } + if volMount.MountPath != "/tmp/qux" { + t.Errorf("Expected container path %q", "/tmp/qux") + } +} diff --git a/cmd/kubeadm/app/phases/etcd/BUILD b/cmd/kubeadm/app/phases/etcd/BUILD index 8c0bef291c1..918598a6d0c 100644 --- a/cmd/kubeadm/app/phases/etcd/BUILD +++ b/cmd/kubeadm/app/phases/etcd/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["local_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd", - library = ":go_default_library", deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", @@ -41,6 +41,9 @@ filegroup( filegroup( name = "all-srcs", - srcs = [":package-srcs"], + srcs = [ + ":package-srcs", + "//cmd/kubeadm/app/phases/etcd/spec:all-srcs", + ], tags = ["automanaged"], ) diff --git a/cmd/kubeadm/app/phases/etcd/local.go b/cmd/kubeadm/app/phases/etcd/local.go index e947794fccb..a56ce49e13e 100644 --- a/cmd/kubeadm/app/phases/etcd/local.go +++ b/cmd/kubeadm/app/phases/etcd/local.go @@ -36,7 +36,6 @@ func CreateLocalEtcdStaticPodManifestFile(manifestDir string, cfg *kubeadmapi.Ma // gets etcd StaticPodSpec, actualized for the current MasterConfiguration spec := GetEtcdPodSpec(cfg) - // writes etcd StaticPod to disk if err := staticpodutil.WriteStaticPodToDisk(kubeadmconstants.Etcd, manifestDir, spec); err != nil { return err @@ -47,17 +46,20 @@ func CreateLocalEtcdStaticPodManifestFile(manifestDir string, cfg *kubeadmapi.Ma } // GetEtcdPodSpec returns the etcd static Pod actualized to the context of the current MasterConfiguration -// NB. GetEtcdPodSpec methods holds the information about how kubeadm creates etcd static pod mainfests. +// NB. GetEtcdPodSpec methods holds the information about how kubeadm creates etcd static pod manifests. func GetEtcdPodSpec(cfg *kubeadmapi.MasterConfiguration) v1.Pod { pathType := v1.HostPathDirectoryOrCreate + etcdMounts := map[string]v1.Volume{ + etcdVolumeName: staticpodutil.NewVolume(etcdVolumeName, cfg.Etcd.DataDir, &pathType), + } return staticpodutil.ComponentPod(v1.Container{ Name: kubeadmconstants.Etcd, Command: getEtcdCommand(cfg), - Image: images.GetCoreImage(kubeadmconstants.Etcd, cfg.ImageRepository, "", cfg.Etcd.Image), + Image: images.GetCoreImage(kubeadmconstants.Etcd, cfg.ImageRepository, cfg.KubernetesVersion, cfg.Etcd.Image), // Mount the etcd datadir path read-write so etcd can store data in a more persistent manner VolumeMounts: []v1.VolumeMount{staticpodutil.NewVolumeMount(etcdVolumeName, cfg.Etcd.DataDir, false)}, - LivenessProbe: staticpodutil.ComponentProbe(2379, "/health", v1.URISchemeHTTP), - }, []v1.Volume{staticpodutil.NewVolume(etcdVolumeName, cfg.Etcd.DataDir, &pathType)}) + LivenessProbe: staticpodutil.ComponentProbe(cfg, kubeadmconstants.Etcd, 2379, "/health", v1.URISchemeHTTP), + }, etcdMounts) } // getEtcdCommand builds the right etcd command from the given config object diff --git a/cmd/kubeadm/app/phases/etcd/spec/BUILD b/cmd/kubeadm/app/phases/etcd/spec/BUILD new file mode 100644 index 00000000000..8c65b8b7108 --- /dev/null +++ b/cmd/kubeadm/app/phases/etcd/spec/BUILD @@ -0,0 +1,32 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "spec.go", + "zz_generated.deepcopy.go", + ], + importpath = "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd/spec", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/cmd/kubeadm/app/phases/etcd/spec/doc.go b/cmd/kubeadm/app/phases/etcd/spec/doc.go new file mode 100644 index 00000000000..a91c65d7dda --- /dev/null +++ b/cmd/kubeadm/app/phases/etcd/spec/doc.go @@ -0,0 +1,19 @@ +/* +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 + +package spec diff --git a/cmd/kubeadm/app/phases/etcd/spec/spec.go b/cmd/kubeadm/app/phases/etcd/spec/spec.go new file mode 100644 index 00000000000..4cc587a5b6c --- /dev/null +++ b/cmd/kubeadm/app/phases/etcd/spec/spec.go @@ -0,0 +1,205 @@ +/* +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. +*/ + +// This file was collated from types used in: +// https://github.com/coreos/etcd-operator/tree/e7f18696bbdc127fa028a99ca8166a8519749328/pkg/apis/etcd/v1beta2. +// When kubeadm moves to its own repo and controls its own dependencies, +// this file will be no longer be needed. + +package spec + +import ( + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +const ( + // CRDResourceKind is the CRD resource kind + CRDResourceKind = "EtcdCluster" + // CRDResourcePlural is the CRD resource plural + CRDResourcePlural = "etcdclusters" + groupName = "etcd.database.coreos.com" +) + +var ( + // SchemeBuilder is a scheme builder + SchemeBuilder = runtime.NewSchemeBuilder(AddKnownTypes) + // AddToScheme adds to the scheme + AddToScheme = SchemeBuilder.AddToScheme + // SchemeGroupVersion is the scheme version + SchemeGroupVersion = schema.GroupVersion{Group: groupName, Version: "v1beta2"} + // CRDName is the name of the CRD + CRDName = CRDResourcePlural + "." + groupName +) + +// Resource gets an EtcdCluster GroupResource for a specified resource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +// AddKnownTypes adds the set of types defined in this package to the supplied scheme. +func AddKnownTypes(s *runtime.Scheme) error { + s.AddKnownTypes(SchemeGroupVersion, + &EtcdCluster{}, + &EtcdClusterList{}, + ) + metav1.AddToGroupVersion(s, SchemeGroupVersion) + return nil +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// EtcdClusterList is a list of etcd clusters. +type EtcdClusterList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata + // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata + metav1.ListMeta `json:"metadata,omitempty"` + Items []EtcdCluster `json:"items"` +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// EtcdCluster represents an etcd cluster +type EtcdCluster struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec ClusterSpec `json:"spec"` +} + +// ClusterSpec represents a cluster spec +type ClusterSpec struct { + // Size is the expected size of the etcd cluster. + // The etcd-operator will eventually make the size of the running + // cluster equal to the expected size. + // The vaild range of the size is from 1 to 7. + Size int `json:"size"` + + // BaseImage is the base etcd image name that will be used to launch + // etcd clusters. This is useful for private registries, etc. + // + // If image is not set, default is quay.io/coreos/etcd + BaseImage string `json:"baseImage"` + + // Version is the expected version of the etcd cluster. + // The etcd-operator will eventually make the etcd cluster version + // equal to the expected version. + // + // The version must follow the [semver]( http://semver.org) format, for example "3.1.8". + // Only etcd released versions are supported: https://github.com/coreos/etcd/releases + // + // If version is not set, default is "3.1.8". + Version string `json:"version,omitempty"` + + // Paused is to pause the control of the operator for the etcd cluster. + Paused bool `json:"paused,omitempty"` + + // Pod defines the policy to create pod for the etcd pod. + // + // Updating Pod does not take effect on any existing etcd pods. + Pod *PodPolicy `json:"pod,omitempty"` + + // SelfHosted determines if the etcd cluster is used for a self-hosted + // Kubernetes cluster. + // + // SelfHosted is a cluster initialization configuration. It cannot be updated. + SelfHosted *SelfHostedPolicy `json:"selfHosted,omitempty"` + + // etcd cluster TLS configuration + TLS *TLSPolicy `json:"TLS,omitempty"` +} + +// PodPolicy defines the policy to create pod for the etcd container. +type PodPolicy struct { + // Labels specifies the labels to attach to pods the operator creates for the + // etcd cluster. + // "app" and "etcd_*" labels are reserved for the internal use of the etcd operator. + // Do not overwrite them. + Labels map[string]string `json:"labels,omitempty"` + + // NodeSelector specifies a map of key-value pairs. For the pod to be eligible + // to run on a node, the node must have each of the indicated key-value pairs as + // labels. + NodeSelector map[string]string `json:"nodeSelector,omitempty"` + + // AntiAffinity determines if the etcd-operator tries to avoid putting + // the etcd members in the same cluster onto the same node. + AntiAffinity bool `json:"antiAffinity,omitempty"` + + // Resources is the resource requirements for the etcd container. + // This field cannot be updated once the cluster is created. + Resources v1.ResourceRequirements `json:"resources,omitempty"` + + // Tolerations specifies the pod's tolerations. + Tolerations []v1.Toleration `json:"tolerations,omitempty"` + + // List of environment variables to set in the etcd container. + // This is used to configure etcd process. etcd cluster cannot be created, when + // bad environement variables are provided. Do not overwrite any flags used to + // bootstrap the cluster (for example `--initial-cluster` flag). + // This field cannot be updated. + EtcdEnv []v1.EnvVar `json:"etcdEnv,omitempty"` + + // By default, kubernetes will mount a service account token into the etcd pods. + // AutomountServiceAccountToken indicates whether pods running with the service account should have an API token automatically mounted. + AutomountServiceAccountToken *bool `json:"automountServiceAccountToken,omitempty"` +} + +// TLSPolicy defines the TLS policy of an etcd cluster +type TLSPolicy struct { + // StaticTLS enables user to generate static x509 certificates and keys, + // put them into Kubernetes secrets, and specify them into here. + Static *StaticTLS `json:"static,omitempty"` +} + +// StaticTLS represents static TLS +type StaticTLS struct { + // Member contains secrets containing TLS certs used by each etcd member pod. + Member *MemberSecret `json:"member,omitempty"` + // OperatorSecret is the secret containing TLS certs used by operator to + // talk securely to this cluster. + OperatorSecret string `json:"operatorSecret,omitempty"` +} + +// MemberSecret represents a member secret +type MemberSecret struct { + // PeerSecret is the secret containing TLS certs used by each etcd member pod + // for the communication between etcd peers. + PeerSecret string `json:"peerSecret,omitempty"` + // ServerSecret is the secret containing TLS certs used by each etcd member pod + // for the communication between etcd server and its clients. + ServerSecret string `json:"serverSecret,omitempty"` +} + +// SelfHostedPolicy represents a self-hosted policy +type SelfHostedPolicy struct { + // BootMemberClientEndpoint specifies a bootstrap member for the cluster. + // If there is no bootstrap member, a completely new cluster will be created. + // The boot member will be removed from the cluster once the self-hosted cluster + // setup successfully. + BootMemberClientEndpoint string `json:"bootMemberClientEndpoint,omitempty"` + + // SkipBootMemberRemoval specifies whether the removal of the bootstrap member + // should be skipped. By default the operator will automatically remove the + // bootstrap member from the new cluster - this happens during the pivot + // procedure and is the first step of decommissioning the bootstrap member. + // If unspecified, the default is `false`. If set to `true`, you are + // expected to remove the boot member yourself from the etcd cluster. + SkipBootMemberRemoval bool `json:"skipBootMemberRemoval,omitempty"` +} diff --git a/cmd/kubeadm/app/phases/etcd/spec/zz_generated.deepcopy.go b/cmd/kubeadm/app/phases/etcd/spec/zz_generated.deepcopy.go new file mode 100644 index 00000000000..79217085e3f --- /dev/null +++ b/cmd/kubeadm/app/phases/etcd/spec/zz_generated.deepcopy.go @@ -0,0 +1,267 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +package spec + +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 *ClusterSpec) DeepCopyInto(out *ClusterSpec) { + *out = *in + if in.Pod != nil { + in, out := &in.Pod, &out.Pod + if *in == nil { + *out = nil + } else { + *out = new(PodPolicy) + (*in).DeepCopyInto(*out) + } + } + if in.SelfHosted != nil { + in, out := &in.SelfHosted, &out.SelfHosted + if *in == nil { + *out = nil + } else { + *out = new(SelfHostedPolicy) + **out = **in + } + } + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + if *in == nil { + *out = nil + } else { + *out = new(TLSPolicy) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterSpec. +func (in *ClusterSpec) DeepCopy() *ClusterSpec { + if in == nil { + return nil + } + out := new(ClusterSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EtcdCluster) DeepCopyInto(out *EtcdCluster) { + *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 EtcdCluster. +func (in *EtcdCluster) DeepCopy() *EtcdCluster { + if in == nil { + return nil + } + out := new(EtcdCluster) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *EtcdCluster) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EtcdClusterList) DeepCopyInto(out *EtcdClusterList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]EtcdCluster, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EtcdClusterList. +func (in *EtcdClusterList) DeepCopy() *EtcdClusterList { + if in == nil { + return nil + } + out := new(EtcdClusterList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *EtcdClusterList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MemberSecret) DeepCopyInto(out *MemberSecret) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemberSecret. +func (in *MemberSecret) DeepCopy() *MemberSecret { + if in == nil { + return nil + } + out := new(MemberSecret) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodPolicy) DeepCopyInto(out *PodPolicy) { + *out = *in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.Resources.DeepCopyInto(&out.Resources) + if in.Tolerations != nil { + in, out := &in.Tolerations, &out.Tolerations + *out = make([]v1.Toleration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.EtcdEnv != nil { + in, out := &in.EtcdEnv, &out.EtcdEnv + *out = make([]v1.EnvVar, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.AutomountServiceAccountToken != nil { + in, out := &in.AutomountServiceAccountToken, &out.AutomountServiceAccountToken + if *in == nil { + *out = nil + } else { + *out = new(bool) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodPolicy. +func (in *PodPolicy) DeepCopy() *PodPolicy { + if in == nil { + return nil + } + out := new(PodPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SelfHostedPolicy) DeepCopyInto(out *SelfHostedPolicy) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SelfHostedPolicy. +func (in *SelfHostedPolicy) DeepCopy() *SelfHostedPolicy { + if in == nil { + return nil + } + out := new(SelfHostedPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StaticTLS) DeepCopyInto(out *StaticTLS) { + *out = *in + if in.Member != nil { + in, out := &in.Member, &out.Member + if *in == nil { + *out = nil + } else { + *out = new(MemberSecret) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StaticTLS. +func (in *StaticTLS) DeepCopy() *StaticTLS { + if in == nil { + return nil + } + out := new(StaticTLS) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSPolicy) DeepCopyInto(out *TLSPolicy) { + *out = *in + if in.Static != nil { + in, out := &in.Static, &out.Static + if *in == nil { + *out = nil + } else { + *out = new(StaticTLS) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSPolicy. +func (in *TLSPolicy) DeepCopy() *TLSPolicy { + if in == nil { + return nil + } + out := new(TLSPolicy) + in.DeepCopyInto(out) + return out +} diff --git a/cmd/kubeadm/app/phases/kubeconfig/BUILD b/cmd/kubeadm/app/phases/kubeconfig/BUILD index b044091a5bc..a402634a580 100644 --- a/cmd/kubeadm/app/phases/kubeconfig/BUILD +++ b/cmd/kubeadm/app/phases/kubeconfig/BUILD @@ -41,8 +41,8 @@ filegroup( go_test( name = "go_default_test", srcs = ["kubeconfig_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig", - library = ":go_default_library", deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", diff --git a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go index 5290b2fb598..947bc75ab2f 100644 --- a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go +++ b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go @@ -143,7 +143,7 @@ func TestBuildKubeConfigFromSpecWithClientAuth(t *testing.T) { // Creates a CA caCert, caKey := certstestutil.SetupCertificateAuthorithy(t) - // Executes buildKubeConfigFromSpec passing a KubeConfigSpec wiht a ClientAuth + // Executes buildKubeConfigFromSpec passing a KubeConfigSpec with a ClientAuth config := setupdKubeConfigWithClientAuth(t, caCert, caKey, "https://1.2.3.4:1234", "myClientName", "myOrg1", "myOrg2") // Asserts spec data are propagated to the kubeconfig @@ -155,7 +155,7 @@ func TestBuildKubeConfigFromSpecWithTokenAuth(t *testing.T) { // Creates a CA caCert, _ := certstestutil.SetupCertificateAuthorithy(t) - // Executes buildKubeConfigFromSpec passing a KubeConfigSpec wiht a Token + // Executes buildKubeConfigFromSpec passing a KubeConfigSpec with a Token config := setupdKubeConfigWithTokenAuth(t, caCert, "https://1.2.3.4:1234", "myClientName", "123456") // Asserts spec data are propagated to the kubeconfig @@ -219,7 +219,7 @@ func TestCreateKubeConfigFileIfNotExists(t *testing.T) { t.Errorf("createKubeConfigFileIfNotExists failed") } - // Assert creted files is there + // Assert that the created file is there testutil.AssertFileExists(t, tmpdir, "test.conf") } } @@ -338,10 +338,10 @@ func TestWriteKubeConfig(t *testing.T) { // Adds a pki folder with a ca cert to the temp folder pkidir := testutil.SetupPkiDirWithCertificateAuthorithy(t, tmpdir) - // Retrives ca cert for assertions + // Retrieves ca cert for assertions caCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(pkidir, kubeadmconstants.CACertAndKeyBaseName) if err != nil { - t.Fatalf("couldn't retrive ca cert: %v", err) + t.Fatalf("couldn't retrieve ca cert: %v", err) } // Creates a Master Configuration pointing to the pkidir folder diff --git a/cmd/kubeadm/app/phases/kubelet/BUILD b/cmd/kubeadm/app/phases/kubelet/BUILD new file mode 100644 index 00000000000..8da2b8243e1 --- /dev/null +++ b/cmd/kubeadm/app/phases/kubelet/BUILD @@ -0,0 +1,57 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["kubelet.go"], + importpath = "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet", + visibility = ["//visibility:public"], + deps = [ + "//cmd/kubeadm/app/apis/kubeadm:go_default_library", + "//cmd/kubeadm/app/constants:go_default_library", + "//cmd/kubeadm/app/util:go_default_library", + "//cmd/kubeadm/app/util/apiclient:go_default_library", + "//cmd/kubeadm/app/util/kubeconfig:go_default_library", + "//pkg/apis/rbac/v1:go_default_library", + "//pkg/kubelet/apis/kubeletconfig/scheme:go_default_library", + "//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/rbac/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["kubelet_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet", + deps = [ + "//cmd/kubeadm/app/apis/kubeadm:go_default_library", + "//cmd/kubeadm/app/constants:go_default_library", + "//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", + "//vendor/k8s.io/client-go/testing:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/cmd/kubeadm/app/phases/kubelet/kubelet.go b/cmd/kubeadm/app/phases/kubelet/kubelet.go new file mode 100644 index 00000000000..aec20293b62 --- /dev/null +++ b/cmd/kubeadm/app/phases/kubelet/kubelet.go @@ -0,0 +1,234 @@ +/* +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 kubelet + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "k8s.io/api/core/v1" + rbac "k8s.io/api/rbac/v1" + apierrs "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/strategicpatch" + "k8s.io/apimachinery/pkg/util/wait" + clientset "k8s.io/client-go/kubernetes" + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" + kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" + "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" + kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" + rbachelper "k8s.io/kubernetes/pkg/apis/rbac/v1" + kubeletconfigscheme "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme" + kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" +) + +// CreateBaseKubeletConfiguration creates base kubelet configuration for dynamic kubelet configuration feature. +func CreateBaseKubeletConfiguration(cfg *kubeadmapi.MasterConfiguration, client clientset.Interface) error { + fmt.Printf("[kubelet] Uploading a ConfigMap %q in namespace %s with base configuration for the kubelets in the cluster\n", + kubeadmconstants.KubeletBaseConfigurationConfigMap, metav1.NamespaceSystem) + + _, kubeletCodecs, err := kubeletconfigscheme.NewSchemeAndCodecs() + if err != nil { + return err + } + kubeletBytes, err := kubeadmutil.MarshalToYamlForCodecs(cfg.KubeletConfiguration.BaseConfig, kubeletconfigv1alpha1.SchemeGroupVersion, *kubeletCodecs) + if err != nil { + return err + } + + if err = apiclient.CreateOrUpdateConfigMap(client, &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: kubeadmconstants.KubeletBaseConfigurationConfigMap, + Namespace: metav1.NamespaceSystem, + }, + Data: map[string]string{ + kubeadmconstants.KubeletBaseConfigurationConfigMapKey: string(kubeletBytes), + }, + }); err != nil { + return err + } + + if err := createKubeletBaseConfigMapRBACRules(client); err != nil { + return fmt.Errorf("error creating base kubelet configmap RBAC rules: %v", err) + } + + return updateNodeWithConfigMap(client, cfg.NodeName) +} + +// ConsumeBaseKubeletConfiguration consumes base kubelet configuration for dynamic kubelet configuration feature. +func ConsumeBaseKubeletConfiguration(nodeName string) error { + client, err := getLocalNodeTLSBootstrappedClient() + if err != nil { + return err + } + + kubeletCfg, err := client.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(kubeadmconstants.KubeletBaseConfigurationConfigMap, metav1.GetOptions{}) + if err != nil { + return err + } + + if err := writeInitKubeletConfigToDisk([]byte(kubeletCfg.Data[kubeadmconstants.KubeletBaseConfigurationConfigMapKey])); err != nil { + return fmt.Errorf("failed to write initial remote configuration of kubelet to disk for node %s: %v", nodeName, err) + } + + return updateNodeWithConfigMap(client, nodeName) +} + +// updateNodeWithConfigMap updates node ConfigSource with KubeletBaseConfigurationConfigMap +func updateNodeWithConfigMap(client clientset.Interface, nodeName string) error { + fmt.Printf("[kubelet] Using Dynamic Kubelet Config for node %q; config sourced from ConfigMap %q in namespace %s\n", + nodeName, kubeadmconstants.KubeletBaseConfigurationConfigMap, metav1.NamespaceSystem) + + // Loop on every falsy return. Return with an error if raised. Exit successfully if true is returned. + return wait.Poll(kubeadmconstants.APICallRetryInterval, kubeadmconstants.UpdateNodeTimeout, func() (bool, error) { + node, err := client.CoreV1().Nodes().Get(nodeName, metav1.GetOptions{}) + if err != nil { + return false, nil + } + + oldData, err := json.Marshal(node) + if err != nil { + return false, err + } + + kubeletCfg, err := client.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(kubeadmconstants.KubeletBaseConfigurationConfigMap, metav1.GetOptions{}) + if err != nil { + return false, nil + } + + node.Spec.ConfigSource = &v1.NodeConfigSource{ + ConfigMapRef: &v1.ObjectReference{ + Name: kubeadmconstants.KubeletBaseConfigurationConfigMap, + Namespace: metav1.NamespaceSystem, + UID: kubeletCfg.UID, + }, + } + + newData, err := json.Marshal(node) + if err != nil { + return false, err + } + + patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, v1.Node{}) + if err != nil { + return false, err + } + + if _, err := client.CoreV1().Nodes().Patch(node.Name, types.StrategicMergePatchType, patchBytes); err != nil { + if apierrs.IsConflict(err) { + fmt.Println("Temporarily unable to update node metadata due to conflict (will retry)") + return false, nil + } + return false, err + } + + return true, nil + }) +} + +// createKubeletBaseConfigMapRBACRules creates the RBAC rules for exposing the base kubelet ConfigMap in the kube-system namespace to unauthenticated users +func createKubeletBaseConfigMapRBACRules(client clientset.Interface) error { + if err := apiclient.CreateOrUpdateRole(client, &rbac.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: kubeadmconstants.KubeletBaseConfigMapRoleName, + Namespace: metav1.NamespaceSystem, + }, + Rules: []rbac.PolicyRule{ + rbachelper.NewRule("get").Groups("").Resources("configmaps").Names(kubeadmconstants.KubeletBaseConfigurationConfigMap).RuleOrDie(), + }, + }); err != nil { + return err + } + + return apiclient.CreateOrUpdateRoleBinding(client, &rbac.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: kubeadmconstants.KubeletBaseConfigMapRoleName, + Namespace: metav1.NamespaceSystem, + }, + RoleRef: rbac.RoleRef{ + APIGroup: rbac.GroupName, + Kind: "Role", + Name: kubeadmconstants.KubeletBaseConfigMapRoleName, + }, + Subjects: []rbac.Subject{ + { + Kind: rbac.GroupKind, + Name: kubeadmconstants.NodesGroup, + }, + { + Kind: rbac.GroupKind, + Name: kubeadmconstants.NodeBootstrapTokenAuthGroup, + }, + }, + }) +} + +// getLocalNodeTLSBootstrappedClient waits for the kubelet to perform the TLS bootstrap +// and then creates a client from config file /etc/kubernetes/kubelet.conf +func getLocalNodeTLSBootstrappedClient() (clientset.Interface, error) { + fmt.Println("[tlsbootstrap] Waiting for the kubelet to perform the TLS Bootstrap...") + + kubeletKubeConfig := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.KubeletKubeConfigFileName) + + // Loop on every falsy return. Return with an error if raised. Exit successfully if true is returned. + err := wait.PollImmediateInfinite(kubeadmconstants.APICallRetryInterval, func() (bool, error) { + _, err := os.Stat(kubeletKubeConfig) + return (err == nil), nil + }) + if err != nil { + return nil, err + } + + return kubeconfigutil.ClientSetFromFile(kubeletKubeConfig) +} + +// WriteInitKubeletConfigToDiskOnMaster writes base kubelet configuration to disk on master. +func WriteInitKubeletConfigToDiskOnMaster(cfg *kubeadmapi.MasterConfiguration) error { + fmt.Printf("[kubelet] Writing base configuration of kubelets to disk on master node %s\n", cfg.NodeName) + + _, kubeletCodecs, err := kubeletconfigscheme.NewSchemeAndCodecs() + if err != nil { + return err + } + + kubeletBytes, err := kubeadmutil.MarshalToYamlForCodecs(cfg.KubeletConfiguration.BaseConfig, kubeletconfigv1alpha1.SchemeGroupVersion, *kubeletCodecs) + if err != nil { + return err + } + + if err := writeInitKubeletConfigToDisk(kubeletBytes); err != nil { + return fmt.Errorf("failed to write base configuration of kubelet to disk on master node %s: %v", cfg.NodeName, err) + } + + return nil +} + +func writeInitKubeletConfigToDisk(kubeletConfig []byte) error { + if err := os.MkdirAll(kubeadmconstants.KubeletBaseConfigurationDir, 0644); err != nil { + return fmt.Errorf("failed to create directory %q: %v", kubeadmconstants.KubeletBaseConfigurationDir, err) + } + baseConfigFile := filepath.Join(kubeadmconstants.KubeletBaseConfigurationDir, kubeadmconstants.KubeletBaseConfigurationFile) + if err := ioutil.WriteFile(baseConfigFile, kubeletConfig, 0644); err != nil { + return fmt.Errorf("failed to write initial remote configuration of kubelet into file %q: %v", baseConfigFile, err) + } + return nil +} diff --git a/cmd/kubeadm/app/phases/kubelet/kubelet_test.go b/cmd/kubeadm/app/phases/kubelet/kubelet_test.go new file mode 100644 index 00000000000..0b38320b715 --- /dev/null +++ b/cmd/kubeadm/app/phases/kubelet/kubelet_test.go @@ -0,0 +1,134 @@ +/* +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 kubelet + +import ( + "testing" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/fake" + core "k8s.io/client-go/testing" + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" + kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" +) + +func TestCreateBaseKubeletConfiguration(t *testing.T) { + nodeName := "fake-node" + client := fake.NewSimpleClientset() + cfg := &kubeadmapi.MasterConfiguration{ + NodeName: nodeName, + KubeletConfiguration: kubeadmapi.KubeletConfiguration{ + BaseConfig: &kubeletconfigv1alpha1.KubeletConfiguration{ + TypeMeta: metav1.TypeMeta{ + Kind: "KubeletConfiguration", + }, + }, + }, + } + + client.PrependReactor("get", "nodes", func(action core.Action) (bool, runtime.Object, error) { + return true, &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: nodeName, + }, + Spec: v1.NodeSpec{ + ConfigSource: &v1.NodeConfigSource{ + ConfigMapRef: &v1.ObjectReference{ + UID: "", + }, + }, + }, + }, nil + }) + client.PrependReactor("get", "configmaps", func(action core.Action) (bool, runtime.Object, error) { + return true, &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: kubeadmconstants.KubeletBaseConfigurationConfigMap, + Namespace: metav1.NamespaceSystem, + UID: "fake-uid", + }, + }, nil + }) + client.PrependReactor("patch", "nodes", func(action core.Action) (bool, runtime.Object, error) { + return true, nil, nil + }) + client.PrependReactor("create", "roles", func(action core.Action) (bool, runtime.Object, error) { + return true, nil, nil + }) + client.PrependReactor("create", "rolebindings", func(action core.Action) (bool, runtime.Object, error) { + return true, nil, nil + }) + client.PrependReactor("create", "configmaps", func(action core.Action) (bool, runtime.Object, error) { + return true, nil, nil + }) + + if err := CreateBaseKubeletConfiguration(cfg, client); err != nil { + t.Errorf("CreateBaseKubeletConfiguration: unexepected error %v", err) + } +} + +func TestUpdateNodeWithConfigMap(t *testing.T) { + nodeName := "fake-node" + client := fake.NewSimpleClientset() + client.PrependReactor("get", "nodes", func(action core.Action) (bool, runtime.Object, error) { + return true, &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: nodeName, + }, + Spec: v1.NodeSpec{ + ConfigSource: &v1.NodeConfigSource{ + ConfigMapRef: &v1.ObjectReference{ + UID: "", + }, + }, + }, + }, nil + }) + client.PrependReactor("get", "configmaps", func(action core.Action) (bool, runtime.Object, error) { + return true, &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: kubeadmconstants.KubeletBaseConfigurationConfigMap, + Namespace: metav1.NamespaceSystem, + UID: "fake-uid", + }, + }, nil + }) + client.PrependReactor("patch", "nodes", func(action core.Action) (bool, runtime.Object, error) { + return true, nil, nil + }) + + if err := updateNodeWithConfigMap(client, nodeName); err != nil { + t.Errorf("UpdateNodeWithConfigMap: unexepected error %v", err) + } +} + +func TestCreateKubeletBaseConfigMapRBACRules(t *testing.T) { + client := fake.NewSimpleClientset() + client.PrependReactor("create", "roles", func(action core.Action) (bool, runtime.Object, error) { + return true, nil, nil + }) + client.PrependReactor("create", "rolebindings", func(action core.Action) (bool, runtime.Object, error) { + return true, nil, nil + }) + + if err := createKubeletBaseConfigMapRBACRules(client); err != nil { + t.Errorf("createKubeletBaseConfigMapRBACRules: unexepected error %v", err) + } +} diff --git a/cmd/kubeadm/app/phases/markmaster/BUILD b/cmd/kubeadm/app/phases/markmaster/BUILD index 6a1d55d9474..8dbc4b44457 100644 --- a/cmd/kubeadm/app/phases/markmaster/BUILD +++ b/cmd/kubeadm/app/phases/markmaster/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["markmaster_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/phases/markmaster", - library = ":go_default_library", deps = [ "//cmd/kubeadm/app/constants:go_default_library", "//pkg/kubelet/apis:go_default_library", diff --git a/cmd/kubeadm/app/phases/selfhosting/BUILD b/cmd/kubeadm/app/phases/selfhosting/BUILD index a81436e68c3..ef1ad56d0f2 100644 --- a/cmd/kubeadm/app/phases/selfhosting/BUILD +++ b/cmd/kubeadm/app/phases/selfhosting/BUILD @@ -13,12 +13,13 @@ go_test( "selfhosting_test.go", "selfhosting_volumes_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting", - library = ":go_default_library", deps = [ "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/util:go_default_library", - "//vendor/k8s.io/api/apps/v1beta2:go_default_library", + "//pkg/volume/util:go_default_library", + "//vendor/k8s.io/api/apps/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", ], ) @@ -37,11 +38,10 @@ go_library( "//cmd/kubeadm/app/features:go_default_library", "//cmd/kubeadm/app/util:go_default_library", "//cmd/kubeadm/app/util/apiclient:go_default_library", - "//pkg/api:go_default_library", - "//vendor/k8s.io/api/apps/v1beta2:go_default_library", + "//pkg/volume/util:go_default_library", + "//vendor/k8s.io/api/apps/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", ], ) diff --git a/cmd/kubeadm/app/phases/selfhosting/podspec_mutation.go b/cmd/kubeadm/app/phases/selfhosting/podspec_mutation.go index 78c54fd51b3..fd01d8c391a 100644 --- a/cmd/kubeadm/app/phases/selfhosting/podspec_mutation.go +++ b/cmd/kubeadm/app/phases/selfhosting/podspec_mutation.go @@ -18,6 +18,7 @@ package selfhosting import ( "path/filepath" + "strings" "k8s.io/api/core/v1" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" @@ -42,6 +43,7 @@ func GetDefaultMutators() map[string][]PodSpecMutatorFunc { addNodeSelectorToPodSpec, setMasterTolerationOnPodSpec, setRightDNSPolicyOnPodSpec, + setHostIPOnPodSpec, }, kubeadmconstants.KubeControllerManager: { addNodeSelectorToPodSpec, @@ -101,6 +103,26 @@ func setMasterTolerationOnPodSpec(podSpec *v1.PodSpec) { podSpec.Tolerations = append(podSpec.Tolerations, kubeadmconstants.MasterToleration) } +// setHostIPOnPodSpec sets the environment variable HOST_IP using downward API +func setHostIPOnPodSpec(podSpec *v1.PodSpec) { + envVar := v1.EnvVar{ + Name: "HOST_IP", + ValueFrom: &v1.EnvVarSource{ + FieldRef: &v1.ObjectFieldSelector{ + FieldPath: "status.hostIP", + }, + }, + } + + podSpec.Containers[0].Env = append(podSpec.Containers[0].Env, envVar) + + for i := range podSpec.Containers[0].Command { + if strings.Contains(podSpec.Containers[0].Command[i], "advertise-address") { + podSpec.Containers[0].Command[i] = "--advertise-address=$(HOST_IP)" + } + } +} + // setRightDNSPolicyOnPodSpec makes sure the self-hosted components can look up things via kube-dns if necessary func setRightDNSPolicyOnPodSpec(podSpec *v1.PodSpec) { podSpec.DNSPolicy = v1.DNSClusterFirstWithHostNet diff --git a/cmd/kubeadm/app/phases/selfhosting/podspec_mutation_test.go b/cmd/kubeadm/app/phases/selfhosting/podspec_mutation_test.go index 391ea4bad0e..6a5f1da8a8b 100644 --- a/cmd/kubeadm/app/phases/selfhosting/podspec_mutation_test.go +++ b/cmd/kubeadm/app/phases/selfhosting/podspec_mutation_test.go @@ -33,8 +33,36 @@ func TestMutatePodSpec(t *testing.T) { }{ { component: kubeadmconstants.KubeAPIServer, - podSpec: &v1.PodSpec{}, + podSpec: &v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "kube-apiserver", + Command: []string{ + "--advertise-address=10.0.0.1", + }, + }, + }, + }, expected: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "kube-apiserver", + Command: []string{ + "--advertise-address=$(HOST_IP)", + }, + Env: []v1.EnvVar{ + { + Name: "HOST_IP", + ValueFrom: &v1.EnvVarSource{ + FieldRef: &v1.ObjectFieldSelector{ + FieldPath: "status.hostIP", + }, + }, + }, + }, + }, + }, + NodeSelector: map[string]string{ kubeadmconstants.LabelNodeRoleMaster: "", }, @@ -185,6 +213,55 @@ func TestSetRightDNSPolicyOnPodSpec(t *testing.T) { } } +func TestSetHostIPOnPodSpec(t *testing.T) { + var tests = []struct { + podSpec *v1.PodSpec + expected v1.PodSpec + }{ + { + podSpec: &v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "kube-apiserver", + Command: []string{ + "--advertise-address=10.0.0.1", + }, + Env: []v1.EnvVar{}, + }, + }, + }, + expected: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "kube-apiserver", + Command: []string{ + "--advertise-address=$(HOST_IP)", + }, + Env: []v1.EnvVar{ + { + Name: "HOST_IP", + ValueFrom: &v1.EnvVarSource{ + FieldRef: &v1.ObjectFieldSelector{ + FieldPath: "status.hostIP", + }, + }, + }, + }, + }, + }, + }, + }, + } + + for _, rt := range tests { + setHostIPOnPodSpec(rt.podSpec) + + if !reflect.DeepEqual(*rt.podSpec, rt.expected) { + t.Errorf("failed setHostIPOnPodSpec:\nexpected:\n%v\nsaw:\n%v", rt.expected, *rt.podSpec) + } + } +} + func TestSetSelfHostedVolumesForAPIServer(t *testing.T) { hostPathDirectoryOrCreate := v1.HostPathDirectoryOrCreate var tests = []struct { diff --git a/cmd/kubeadm/app/phases/selfhosting/selfhosting.go b/cmd/kubeadm/app/phases/selfhosting/selfhosting.go index f310a4353ec..8456313a66d 100644 --- a/cmd/kubeadm/app/phases/selfhosting/selfhosting.go +++ b/cmd/kubeadm/app/phases/selfhosting/selfhosting.go @@ -18,20 +18,18 @@ package selfhosting import ( "fmt" - "io/ioutil" "os" "time" - apps "k8s.io/api/apps/v1beta2" + apps "k8s.io/api/apps/v1" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - kuberuntime "k8s.io/apimachinery/pkg/runtime" clientset "k8s.io/client-go/kubernetes" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/features" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" - "k8s.io/kubernetes/pkg/api" + volumeutil "k8s.io/kubernetes/pkg/volume/util" ) const ( @@ -54,12 +52,12 @@ const ( // 8. In order to avoid race conditions, we have to make sure that static pod is deleted correctly before we continue // Otherwise, there is a race condition when we proceed without kubelet having restarted the API server correctly and the next .Create call flakes // 9. Do that for the kube-apiserver, kube-controller-manager and kube-scheduler in a loop -func CreateSelfHostedControlPlane(manifestsDir, kubeConfigDir string, cfg *kubeadmapi.MasterConfiguration, client clientset.Interface, waiter apiclient.Waiter) error { +func CreateSelfHostedControlPlane(manifestsDir, kubeConfigDir string, cfg *kubeadmapi.MasterConfiguration, client clientset.Interface, waiter apiclient.Waiter, dryRun bool) error { // Adjust the timeout slightly to something self-hosting specific waiter.SetTimeout(selfHostingWaitTimeout) - // Here the map of different mutators to use for the control plane's podspec is stored + // Here the map of different mutators to use for the control plane's PodSpec is stored mutators := GetMutatorsFromFeatureGates(cfg.FeatureGates) // Some extra work to be done if we should store the control plane certificates in Secrets @@ -85,10 +83,11 @@ func CreateSelfHostedControlPlane(manifestsDir, kubeConfigDir string, cfg *kubea } // Load the Static Pod file in order to be able to create a self-hosted variant of that file - podSpec, err := loadPodSpecFromFile(manifestPath) + pod, err := volumeutil.LoadPodFromFile(manifestPath) if err != nil { return err } + podSpec := &pod.Spec // Build a DaemonSet object from the loaded PodSpec ds := BuildDaemonSet(componentName, podSpec, mutators) @@ -105,13 +104,15 @@ func CreateSelfHostedControlPlane(manifestsDir, kubeConfigDir string, cfg *kubea return err } - // Remove the old Static Pod manifest - if err := os.RemoveAll(manifestPath); err != nil { - return fmt.Errorf("unable to delete static pod manifest for %s [%v]", componentName, err) + // Remove the old Static Pod manifest if not dryrunning + if !dryRun { + if err := os.RemoveAll(manifestPath); err != nil { + return fmt.Errorf("unable to delete static pod manifest for %s [%v]", componentName, err) + } } // Wait for the mirror Pod hash to be removed; otherwise we'll run into race conditions here when the kubelet hasn't had time to - // remove the Static Pod (or the mirror Pod respectively). This implicitely also tests that the API server endpoint is healthy, + // remove the Static Pod (or the mirror Pod respectively). This implicitly also tests that the API server endpoint is healthy, // because this blocks until the API server returns a 404 Not Found when getting the Static Pod staticPodName := fmt.Sprintf("%s-%s", componentName, cfg.NodeName) if err := waiter.WaitForPodToDisappear(staticPodName); err != nil { @@ -128,7 +129,7 @@ func CreateSelfHostedControlPlane(manifestsDir, kubeConfigDir string, cfg *kubea return nil } -// BuildDaemonSet is responsible for mutating the PodSpec and return a DaemonSet which is suitable for the self-hosting purporse +// BuildDaemonSet is responsible for mutating the PodSpec and returns a DaemonSet which is suitable for self-hosting func BuildDaemonSet(name string, podSpec *v1.PodSpec, mutators map[string][]PodSpecMutatorFunc) *apps.DaemonSet { // Mutate the PodSpec so it's suitable for self-hosting @@ -142,6 +143,9 @@ func BuildDaemonSet(name string, podSpec *v1.PodSpec, mutators map[string][]PodS Labels: BuildSelfhostedComponentLabels(name), }, Spec: apps.DaemonSetSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: BuildSelfhostedComponentLabels(name), + }, Template: v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: BuildSelfhostedComponentLabels(name), @@ -156,22 +160,6 @@ func BuildDaemonSet(name string, podSpec *v1.PodSpec, mutators map[string][]PodS } } -// loadPodSpecFromFile reads and decodes a file containing a specification of a Pod -// TODO: Consider using "k8s.io/kubernetes/pkg/volume/util".LoadPodFromFile(filename string) in the future instead. -func loadPodSpecFromFile(manifestPath string) (*v1.PodSpec, error) { - podBytes, err := ioutil.ReadFile(manifestPath) - if err != nil { - return nil, err - } - - staticPod := &v1.Pod{} - if err := kuberuntime.DecodeInto(api.Codecs.UniversalDecoder(), podBytes, staticPod); err != nil { - return nil, fmt.Errorf("unable to decode static pod %v", err) - } - - return &staticPod.Spec, nil -} - // BuildSelfhostedComponentLabels returns the labels for a self-hosted component func BuildSelfhostedComponentLabels(component string) map[string]string { return map[string]string{ diff --git a/cmd/kubeadm/app/phases/selfhosting/selfhosting_test.go b/cmd/kubeadm/app/phases/selfhosting/selfhosting_test.go index 40a5a0b07a2..9b1b3306b56 100644 --- a/cmd/kubeadm/app/phases/selfhosting/selfhosting_test.go +++ b/cmd/kubeadm/app/phases/selfhosting/selfhosting_test.go @@ -23,9 +23,10 @@ import ( "os" "testing" - apps "k8s.io/api/apps/v1beta2" + apps "k8s.io/api/apps/v1" "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/util" + volumeutil "k8s.io/kubernetes/pkg/volume/util" ) const ( @@ -103,7 +104,7 @@ spec: status: {} ` - testAPIServerDaemonSet = `apiVersion: apps/v1beta2 + testAPIServerDaemonSet = `apiVersion: apps/v1 kind: DaemonSet metadata: creationTimestamp: null @@ -112,6 +113,9 @@ metadata: name: self-hosted-kube-apiserver namespace: kube-system spec: + selector: + matchLabels: + k8s-app: self-hosted-kube-apiserver template: metadata: creationTimestamp: null @@ -130,7 +134,7 @@ spec: - --service-cluster-ip-range=10.96.0.0/12 - --tls-cert-file=/etc/kubernetes/pki/apiserver.crt - --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt - - --advertise-address=192.168.1.115 + - --advertise-address=$(HOST_IP) - --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt - --insecure-port=0 - --experimental-bootstrap-token-auth=true @@ -144,6 +148,11 @@ spec: - --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key - --authorization-mode=Node,RBAC - --etcd-servers=http://127.0.0.1:2379 + env: + - name: HOST_IP + valueFrom: + fieldRef: + fieldPath: status.hostIP image: gcr.io/google_containers/kube-apiserver-amd64:v1.7.4 livenessProbe: failureThreshold: 8 @@ -261,7 +270,7 @@ spec: status: {} ` - testControllerManagerDaemonSet = `apiVersion: apps/v1beta2 + testControllerManagerDaemonSet = `apiVersion: apps/v1 kind: DaemonSet metadata: creationTimestamp: null @@ -270,6 +279,9 @@ metadata: name: self-hosted-kube-controller-manager namespace: kube-system spec: + selector: + matchLabels: + k8s-app: self-hosted-kube-controller-manager template: metadata: creationTimestamp: null @@ -388,7 +400,7 @@ spec: status: {} ` - testSchedulerDaemonSet = `apiVersion: apps/v1beta2 + testSchedulerDaemonSet = `apiVersion: apps/v1 kind: DaemonSet metadata: creationTimestamp: null @@ -397,6 +409,9 @@ metadata: name: self-hosted-kube-scheduler namespace: kube-system spec: + selector: + matchLabels: + k8s-app: self-hosted-kube-scheduler template: metadata: creationTimestamp: null @@ -474,12 +489,16 @@ func TestBuildDaemonSet(t *testing.T) { for _, rt := range tests { tempFile, err := createTempFileWithContent(rt.podBytes) + if err != nil { + t.Errorf("error creating tempfile with content:%v", err) + } defer os.Remove(tempFile) - podSpec, err := loadPodSpecFromFile(tempFile) + pod, err := volumeutil.LoadPodFromFile(tempFile) if err != nil { t.Fatalf("couldn't load the specified Pod") } + podSpec := &pod.Spec ds := BuildDaemonSet(rt.component, podSpec, GetDefaultMutators()) dsBytes, err := util.MarshalToYaml(ds, apps.SchemeGroupVersion) @@ -546,9 +565,12 @@ spec: for _, rt := range tests { tempFile, err := createTempFileWithContent([]byte(rt.content)) + if err != nil { + t.Errorf("error creating tempfile with content:%v", err) + } defer os.Remove(tempFile) - _, err = loadPodSpecFromFile(tempFile) + _, err = volumeutil.LoadPodFromFile(tempFile) if (err != nil) != rt.expectError { t.Errorf("failed TestLoadPodSpecFromFile:\nexpected error:\n%t\nsaw:\n%v", rt.expectError, err) } diff --git a/cmd/kubeadm/app/phases/token/BUILD b/cmd/kubeadm/app/phases/token/BUILD deleted file mode 100644 index 7e76248ad95..00000000000 --- a/cmd/kubeadm/app/phases/token/BUILD +++ /dev/null @@ -1,14 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/cmd/kubeadm/app/phases/upgrade/BUILD b/cmd/kubeadm/app/phases/upgrade/BUILD index 08f470828d6..d1c6135b93e 100644 --- a/cmd/kubeadm/app/phases/upgrade/BUILD +++ b/cmd/kubeadm/app/phases/upgrade/BUILD @@ -8,6 +8,7 @@ go_library( "health.go", "policy.go", "postupgrade.go", + "postupgrade_v18_19.go", "prepull.go", "selfhosted.go", "staticpods.go", @@ -20,26 +21,33 @@ go_library( "//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", + "//cmd/kubeadm/app/features:go_default_library", "//cmd/kubeadm/app/images:go_default_library", "//cmd/kubeadm/app/phases/addons/dns:go_default_library", "//cmd/kubeadm/app/phases/addons/proxy:go_default_library", "//cmd/kubeadm/app/phases/bootstraptoken/clusterinfo:go_default_library", "//cmd/kubeadm/app/phases/bootstraptoken/node:go_default_library", + "//cmd/kubeadm/app/phases/certs:go_default_library", "//cmd/kubeadm/app/phases/controlplane:go_default_library", + "//cmd/kubeadm/app/phases/etcd:go_default_library", "//cmd/kubeadm/app/phases/selfhosting:go_default_library", "//cmd/kubeadm/app/phases/uploadconfig:go_default_library", + "//cmd/kubeadm/app/preflight:go_default_library", "//cmd/kubeadm/app/util:go_default_library", "//cmd/kubeadm/app/util/apiclient:go_default_library", "//cmd/kubeadm/app/util/config:go_default_library", - "//pkg/api:go_default_library", + "//cmd/kubeadm/app/util/dryrun:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/util/version:go_default_library", "//pkg/version:go_default_library", - "//vendor/k8s.io/api/apps/v1beta2:go_default_library", + "//vendor/k8s.io/api/apps/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", ], ) @@ -63,19 +71,25 @@ go_test( srcs = [ "compute_test.go", "policy_test.go", + "postupgrade_v18_19_test.go", "prepull_test.go", "staticpods_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade", - library = ":go_default_library", deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", + "//cmd/kubeadm/app/phases/certs:go_default_library", + "//cmd/kubeadm/app/phases/certs/pkiutil:go_default_library", "//cmd/kubeadm/app/phases/controlplane:go_default_library", + "//cmd/kubeadm/app/phases/etcd:go_default_library", "//cmd/kubeadm/app/util/apiclient:go_default_library", - "//pkg/api:go_default_library", + "//cmd/kubeadm/test:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/util/version:go_default_library", + "//vendor/github.com/coreos/etcd/clientv3:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", ], ) diff --git a/cmd/kubeadm/app/phases/upgrade/compute.go b/cmd/kubeadm/app/phases/upgrade/compute.go index 2b78af9f699..220c6ffa613 100644 --- a/cmd/kubeadm/app/phases/upgrade/compute.go +++ b/cmd/kubeadm/app/phases/upgrade/compute.go @@ -20,7 +20,10 @@ import ( "fmt" "strings" + kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" + "k8s.io/kubernetes/cmd/kubeadm/app/features" "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns" + "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/pkg/util/version" ) @@ -47,6 +50,14 @@ func (u *Upgrade) CanUpgradeKubelets() bool { return !sameVersionFound } +// ActiveDNSAddon returns the version of CoreDNS or kube-dns +func ActiveDNSAddon(featureGates map[string]bool) string { + if features.Enabled(featureGates, features.CoreDNS) { + return kubeadmconstants.CoreDNS + } + return kubeadmconstants.KubeDNS +} + // ClusterState describes the state of certain versions for a cluster type ClusterState struct { // KubeVersion describes the version of the Kubernetes API Server, Controller Manager, Scheduler and Proxy. @@ -57,12 +68,14 @@ type ClusterState struct { KubeadmVersion string // KubeletVersions is a map with a version number linked to the amount of kubelets running that version in the cluster KubeletVersions map[string]uint16 + // EtcdVersion represents the version of etcd used in the cluster + EtcdVersion string } // GetAvailableUpgrades fetches all versions from the specified VersionGetter and computes which // kinds of upgrades can be performed -func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesAllowed, rcUpgradesAllowed bool) ([]Upgrade, error) { - fmt.Println("[upgrade] Fetching available versions to upgrade to:") +func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesAllowed, rcUpgradesAllowed bool, cluster util.EtcdCluster, featureGates map[string]bool) ([]Upgrade, error) { + fmt.Println("[upgrade] Fetching available versions to upgrade to") // Collect the upgrades kubeadm can do in this list upgrades := []Upgrade{} @@ -82,7 +95,9 @@ func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesA // Get and output the current latest stable version stableVersionStr, stableVersion, err := versionGetterImpl.VersionFromCILabel("stable", "stable version") if err != nil { - return nil, err + fmt.Printf("[upgrade/versions] WARNING: %v\n", err) + fmt.Println("[upgrade/versions] WARNING: Falling back to current kubeadm version as latest stable version") + stableVersionStr, stableVersion = kubeadmVersionStr, kubeadmVersion } // Get the kubelet versions in the cluster @@ -91,12 +106,19 @@ func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesA return nil, err } + // Get current etcd version + etcdStatus, err := cluster.GetEtcdClusterStatus() + if err != nil { + return nil, err + } + // Construct a descriptor for the current state of the world beforeState := ClusterState{ KubeVersion: clusterVersionStr, - DNSVersion: dns.GetKubeDNSVersion(clusterVersion), + DNSVersion: dns.GetDNSVersion(clusterVersion, ActiveDNSAddon(featureGates)), KubeadmVersion: kubeadmVersionStr, KubeletVersions: kubeletVersions, + EtcdVersion: etcdStatus.Version, } // Do a "dumb guess" that a new minor upgrade is available just because the latest stable version is higher than the cluster version @@ -107,7 +129,6 @@ func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesA // in the case that a user is trying to upgrade from, let's say, v1.8.0-beta.2 to v1.8.0-rc.1 (given we support such upgrades experimentally) // a stable-1.8 branch doesn't exist yet. Hence this check. if patchVersionBranchExists(clusterVersion, stableVersion) { - currentBranch := getBranchFromVersion(clusterVersionStr) versionLabel := fmt.Sprintf("stable-%s", currentBranch) description := fmt.Sprintf("version in the v%s series", currentBranch) @@ -115,34 +136,35 @@ func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesA // Get and output the latest patch version for the cluster branch patchVersionStr, patchVersion, err := versionGetterImpl.VersionFromCILabel(versionLabel, description) if err != nil { - return nil, err - } + fmt.Printf("[upgrade/versions] WARNING: %v\n", err) + } else { + // Check if a minor version upgrade is possible when a patch release exists + // It's only possible if the latest patch version is higher than the current patch version + // If that's the case, they must be on different branches => a newer minor version can be upgraded to + canDoMinorUpgrade = minorUpgradePossibleWithPatchRelease(stableVersion, patchVersion) - // Check if a minor version upgrade is possible when a patch release exists - // It's only possible if the latest patch version is higher than the current patch version - // If that's the case, they must be on different branches => a newer minor version can be upgraded to - canDoMinorUpgrade = minorUpgradePossibleWithPatchRelease(stableVersion, patchVersion) + // If the cluster version is lower than the newest patch version, we should inform about the possible upgrade + if patchUpgradePossible(clusterVersion, patchVersion) { - // If the cluster version is lower than the newest patch version, we should inform about the possible upgrade - if patchUpgradePossible(clusterVersion, patchVersion) { + // The kubeadm version has to be upgraded to the latest patch version + newKubeadmVer := patchVersionStr + if kubeadmVersion.AtLeast(patchVersion) { + // In this case, the kubeadm CLI version is new enough. Don't display an update suggestion for kubeadm by making .NewKubeadmVersion equal .CurrentKubeadmVersion + newKubeadmVer = kubeadmVersionStr + } - // The kubeadm version has to be upgraded to the latest patch version - newKubeadmVer := patchVersionStr - if kubeadmVersion.AtLeast(patchVersion) { - // In this case, the kubeadm CLI version is new enough. Don't display an update suggestion for kubeadm by making .NewKubeadmVersion equal .CurrentKubeadmVersion - newKubeadmVer = kubeadmVersionStr + upgrades = append(upgrades, Upgrade{ + Description: description, + Before: beforeState, + After: ClusterState{ + KubeVersion: patchVersionStr, + DNSVersion: dns.GetDNSVersion(patchVersion, ActiveDNSAddon(featureGates)), + KubeadmVersion: newKubeadmVer, + EtcdVersion: getSuggestedEtcdVersion(patchVersionStr), + // KubeletVersions is unset here as it is not used anywhere in .After + }, + }) } - - upgrades = append(upgrades, Upgrade{ - Description: description, - Before: beforeState, - After: ClusterState{ - KubeVersion: patchVersionStr, - DNSVersion: dns.GetKubeDNSVersion(patchVersion), - KubeadmVersion: newKubeadmVer, - // KubeletVersions is unset here as it is not used anywhere in .After - }, - }) } } @@ -152,8 +174,9 @@ func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesA Before: beforeState, After: ClusterState{ KubeVersion: stableVersionStr, - DNSVersion: dns.GetKubeDNSVersion(stableVersion), + DNSVersion: dns.GetDNSVersion(stableVersion, ActiveDNSAddon(featureGates)), KubeadmVersion: stableVersionStr, + EtcdVersion: getSuggestedEtcdVersion(stableVersionStr), // KubeletVersions is unset here as it is not used anywhere in .After }, }) @@ -196,8 +219,9 @@ func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesA Before: beforeState, After: ClusterState{ KubeVersion: previousBranchLatestVersionStr, - DNSVersion: dns.GetKubeDNSVersion(previousBranchLatestVersion), + DNSVersion: dns.GetDNSVersion(previousBranchLatestVersion, ActiveDNSAddon(featureGates)), KubeadmVersion: previousBranchLatestVersionStr, + EtcdVersion: getSuggestedEtcdVersion(previousBranchLatestVersionStr), // KubeletVersions is unset here as it is not used anywhere in .After }, }) @@ -208,12 +232,12 @@ func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesA // Default to assume that the experimental version to show is the unstable one unstableKubeVersion := latestVersionStr - unstableKubeDNSVersion := dns.GetKubeDNSVersion(latestVersion) + unstableKubeDNSVersion := dns.GetDNSVersion(latestVersion, ActiveDNSAddon(featureGates)) // Ẃe should not display alpha.0. The previous branch's beta/rc versions are more relevant due how the kube branching process works. if latestVersion.PreRelease() == "alpha.0" { unstableKubeVersion = previousBranchLatestVersionStr - unstableKubeDNSVersion = dns.GetKubeDNSVersion(previousBranchLatestVersion) + unstableKubeDNSVersion = dns.GetDNSVersion(previousBranchLatestVersion, ActiveDNSAddon(featureGates)) } upgrades = append(upgrades, Upgrade{ @@ -223,6 +247,7 @@ func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesA KubeVersion: unstableKubeVersion, DNSVersion: unstableKubeDNSVersion, KubeadmVersion: unstableKubeVersion, + EtcdVersion: getSuggestedEtcdVersion(unstableKubeVersion), // KubeletVersions is unset here as it is not used anywhere in .After }, }) @@ -254,3 +279,12 @@ func rcUpgradePossible(clusterVersion, previousBranchLatestVersion *version.Vers func minorUpgradePossibleWithPatchRelease(stableVersion, patchVersion *version.Version) bool { return patchVersion.LessThan(stableVersion) } + +func getSuggestedEtcdVersion(kubernetesVersion string) string { + etcdVersion, err := kubeadmconstants.EtcdSupportedVersion(kubernetesVersion) + if err != nil { + fmt.Printf("[upgrade/versions] WARNING: No recommended etcd for requested kubernetes version (%s)\n", kubernetesVersion) + return "N/A" + } + return etcdVersion.String() +} diff --git a/cmd/kubeadm/app/phases/upgrade/compute_test.go b/cmd/kubeadm/app/phases/upgrade/compute_test.go index 3c632ad21d2..7835493f79a 100644 --- a/cmd/kubeadm/app/phases/upgrade/compute_test.go +++ b/cmd/kubeadm/app/phases/upgrade/compute_test.go @@ -17,10 +17,10 @@ limitations under the License. package upgrade import ( + "github.com/coreos/etcd/clientv3" + versionutil "k8s.io/kubernetes/pkg/util/version" "reflect" "testing" - - versionutil "k8s.io/kubernetes/pkg/util/version" ) type fakeVersionGetter struct { @@ -47,7 +47,7 @@ func (f *fakeVersionGetter) VersionFromCILabel(ciVersionLabel, _ string) (string if ciVersionLabel == "latest" { return f.latestVersion, versionutil.MustParseSemantic(f.latestVersion), nil } - if ciVersionLabel == "latest-1.8" { + if ciVersionLabel == "latest-1.10" { return f.latestDevBranchVersion, versionutil.MustParseSemantic(f.latestDevBranchVersion), nil } return f.stablePatchVersion, versionutil.MustParseSemantic(f.stablePatchVersion), nil @@ -60,7 +60,16 @@ func (f *fakeVersionGetter) KubeletVersions() (map[string]uint16, error) { }, nil } +type fakeEtcdCluster struct{} + +func (f fakeEtcdCluster) GetEtcdClusterStatus() (*clientv3.StatusResponse, error) { + client := &clientv3.StatusResponse{} + client.Version = "3.1.10" + return client, nil +} + func TestGetAvailableUpgrades(t *testing.T) { + featureGates := make(map[string]bool) tests := []struct { vg *fakeVersionGetter expectedUpgrades []Upgrade @@ -69,12 +78,12 @@ func TestGetAvailableUpgrades(t *testing.T) { }{ { // no action needed, already up-to-date vg: &fakeVersionGetter{ - clusterVersion: "v1.7.3", - kubeletVersion: "v1.7.3", - kubeadmVersion: "v1.7.3", + clusterVersion: "v1.9.3", + kubeletVersion: "v1.9.3", + kubeadmVersion: "v1.9.3", - stablePatchVersion: "v1.7.3", - stableVersion: "v1.7.3", + stablePatchVersion: "v1.9.3", + stableVersion: "v1.9.3", }, expectedUpgrades: []Upgrade{}, allowExperimental: false, @@ -82,28 +91,30 @@ func TestGetAvailableUpgrades(t *testing.T) { }, { // simple patch version upgrade vg: &fakeVersionGetter{ - clusterVersion: "v1.7.1", - kubeletVersion: "v1.7.1", // the kubelet are on the same version as the control plane - kubeadmVersion: "v1.7.2", + clusterVersion: "v1.9.1", + kubeletVersion: "v1.9.1", // the kubelet are on the same version as the control plane + kubeadmVersion: "v1.9.2", - stablePatchVersion: "v1.7.3", - stableVersion: "v1.7.3", + stablePatchVersion: "v1.9.3", + stableVersion: "v1.9.3", }, expectedUpgrades: []Upgrade{ { - Description: "version in the v1.7 series", + Description: "version in the v1.9 series", Before: ClusterState{ - KubeVersion: "v1.7.1", + KubeVersion: "v1.9.1", KubeletVersions: map[string]uint16{ - "v1.7.1": 1, + "v1.9.1": 1, }, - KubeadmVersion: "v1.7.2", - DNSVersion: "1.14.5", + KubeadmVersion: "v1.9.2", + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", }, After: ClusterState{ - KubeVersion: "v1.7.3", - KubeadmVersion: "v1.7.3", - DNSVersion: "1.14.5", + KubeVersion: "v1.9.3", + KubeadmVersion: "v1.9.3", + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", }, }, }, @@ -112,28 +123,30 @@ func TestGetAvailableUpgrades(t *testing.T) { }, { // minor version upgrade only vg: &fakeVersionGetter{ - clusterVersion: "v1.7.3", - kubeletVersion: "v1.7.3", // the kubelet are on the same version as the control plane - kubeadmVersion: "v1.8.0", + clusterVersion: "v1.9.1", + kubeletVersion: "v1.9.1", // the kubelet are on the same version as the control plane + kubeadmVersion: "v1.10.0", - stablePatchVersion: "v1.7.3", - stableVersion: "v1.8.0", + stablePatchVersion: "v1.9.1", + stableVersion: "v1.10.0", }, expectedUpgrades: []Upgrade{ { Description: "stable version", Before: ClusterState{ - KubeVersion: "v1.7.3", + KubeVersion: "v1.9.1", KubeletVersions: map[string]uint16{ - "v1.7.3": 1, + "v1.9.1": 1, }, - KubeadmVersion: "v1.8.0", - DNSVersion: "1.14.5", + KubeadmVersion: "v1.10.0", + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", }, After: ClusterState{ - KubeVersion: "v1.8.0", - KubeadmVersion: "v1.8.0", - DNSVersion: "1.14.5", + KubeVersion: "v1.10.0", + KubeadmVersion: "v1.10.0", + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", }, }, }, @@ -142,44 +155,48 @@ func TestGetAvailableUpgrades(t *testing.T) { }, { // both minor version upgrade and patch version upgrade available vg: &fakeVersionGetter{ - clusterVersion: "v1.7.3", - kubeletVersion: "v1.7.3", // the kubelet are on the same version as the control plane - kubeadmVersion: "v1.8.1", + clusterVersion: "v1.9.3", + kubeletVersion: "v1.9.3", // the kubelet are on the same version as the control plane + kubeadmVersion: "v1.9.5", - stablePatchVersion: "v1.7.5", - stableVersion: "v1.8.2", + stablePatchVersion: "v1.9.5", + stableVersion: "v1.10.1", }, expectedUpgrades: []Upgrade{ { - Description: "version in the v1.7 series", + Description: "version in the v1.9 series", Before: ClusterState{ - KubeVersion: "v1.7.3", + KubeVersion: "v1.9.3", KubeletVersions: map[string]uint16{ - "v1.7.3": 1, + "v1.9.3": 1, }, - KubeadmVersion: "v1.8.1", - DNSVersion: "1.14.5", + KubeadmVersion: "v1.9.5", + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", }, After: ClusterState{ - KubeVersion: "v1.7.5", - KubeadmVersion: "v1.8.1", // Note: The kubeadm version mustn't be "downgraded" here - DNSVersion: "1.14.5", + KubeVersion: "v1.9.5", + KubeadmVersion: "v1.9.5", // Note: The kubeadm version mustn't be "downgraded" here + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", }, }, { Description: "stable version", Before: ClusterState{ - KubeVersion: "v1.7.3", + KubeVersion: "v1.9.3", KubeletVersions: map[string]uint16{ - "v1.7.3": 1, + "v1.9.3": 1, }, - KubeadmVersion: "v1.8.1", - DNSVersion: "1.14.5", + KubeadmVersion: "v1.9.5", + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", }, After: ClusterState{ - KubeVersion: "v1.8.2", - KubeadmVersion: "v1.8.2", - DNSVersion: "1.14.5", + KubeVersion: "v1.10.1", + KubeadmVersion: "v1.10.1", + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", }, }, }, @@ -188,13 +205,13 @@ func TestGetAvailableUpgrades(t *testing.T) { }, { // allow experimental upgrades, but no upgrade available vg: &fakeVersionGetter{ - clusterVersion: "v1.8.0-alpha.2", - kubeletVersion: "v1.7.5", - kubeadmVersion: "v1.7.5", + clusterVersion: "v1.10.0-alpha.2", + kubeletVersion: "v1.9.5", + kubeadmVersion: "v1.9.5", - stablePatchVersion: "v1.7.5", - stableVersion: "v1.7.5", - latestVersion: "v1.8.0-alpha.2", + stablePatchVersion: "v1.9.5", + stableVersion: "v1.9.5", + latestVersion: "v1.10.0-alpha.2", }, expectedUpgrades: []Upgrade{}, allowExperimental: true, @@ -202,29 +219,31 @@ func TestGetAvailableUpgrades(t *testing.T) { }, { // upgrade to an unstable version should be supported vg: &fakeVersionGetter{ - clusterVersion: "v1.7.5", - kubeletVersion: "v1.7.5", - kubeadmVersion: "v1.7.5", + clusterVersion: "v1.9.5", + kubeletVersion: "v1.9.5", + kubeadmVersion: "v1.9.5", - stablePatchVersion: "v1.7.5", - stableVersion: "v1.7.5", - latestVersion: "v1.8.0-alpha.2", + stablePatchVersion: "v1.9.5", + stableVersion: "v1.9.5", + latestVersion: "v1.10.0-alpha.2", }, expectedUpgrades: []Upgrade{ { Description: "experimental version", Before: ClusterState{ - KubeVersion: "v1.7.5", + KubeVersion: "v1.9.5", KubeletVersions: map[string]uint16{ - "v1.7.5": 1, + "v1.9.5": 1, }, - KubeadmVersion: "v1.7.5", - DNSVersion: "1.14.5", + KubeadmVersion: "v1.9.5", + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", }, After: ClusterState{ - KubeVersion: "v1.8.0-alpha.2", - KubeadmVersion: "v1.8.0-alpha.2", - DNSVersion: "1.14.5", + KubeVersion: "v1.10.0-alpha.2", + KubeadmVersion: "v1.10.0-alpha.2", + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", }, }, }, @@ -233,29 +252,31 @@ func TestGetAvailableUpgrades(t *testing.T) { }, { // upgrade from an unstable version to an unstable version should be supported vg: &fakeVersionGetter{ - clusterVersion: "v1.8.0-alpha.1", - kubeletVersion: "v1.7.5", - kubeadmVersion: "v1.7.5", + clusterVersion: "v1.10.0-alpha.1", + kubeletVersion: "v1.9.5", + kubeadmVersion: "v1.9.5", - stablePatchVersion: "v1.7.5", - stableVersion: "v1.7.5", - latestVersion: "v1.8.0-alpha.2", + stablePatchVersion: "v1.9.5", + stableVersion: "v1.9.5", + latestVersion: "v1.10.0-alpha.2", }, expectedUpgrades: []Upgrade{ { Description: "experimental version", Before: ClusterState{ - KubeVersion: "v1.8.0-alpha.1", + KubeVersion: "v1.10.0-alpha.1", KubeletVersions: map[string]uint16{ - "v1.7.5": 1, + "v1.9.5": 1, }, - KubeadmVersion: "v1.7.5", - DNSVersion: "1.14.5", + KubeadmVersion: "v1.9.5", + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", }, After: ClusterState{ - KubeVersion: "v1.8.0-alpha.2", - KubeadmVersion: "v1.8.0-alpha.2", - DNSVersion: "1.14.5", + KubeVersion: "v1.10.0-alpha.2", + KubeadmVersion: "v1.10.0-alpha.2", + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", }, }, }, @@ -264,30 +285,32 @@ func TestGetAvailableUpgrades(t *testing.T) { }, { // v1.X.0-alpha.0 should be ignored vg: &fakeVersionGetter{ - clusterVersion: "v1.7.5", - kubeletVersion: "v1.7.5", - kubeadmVersion: "v1.7.5", + clusterVersion: "v1.9.5", + kubeletVersion: "v1.9.5", + kubeadmVersion: "v1.9.5", - stablePatchVersion: "v1.7.5", - stableVersion: "v1.7.5", - latestDevBranchVersion: "v1.8.0-beta.1", - latestVersion: "v1.9.0-alpha.0", + stablePatchVersion: "v1.9.5", + stableVersion: "v1.9.5", + latestDevBranchVersion: "v1.10.0-beta.1", + latestVersion: "v1.11.0-alpha.0", }, expectedUpgrades: []Upgrade{ { Description: "experimental version", Before: ClusterState{ - KubeVersion: "v1.7.5", + KubeVersion: "v1.9.5", KubeletVersions: map[string]uint16{ - "v1.7.5": 1, + "v1.9.5": 1, }, - KubeadmVersion: "v1.7.5", - DNSVersion: "1.14.5", + KubeadmVersion: "v1.9.5", + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", }, After: ClusterState{ - KubeVersion: "v1.8.0-beta.1", - KubeadmVersion: "v1.8.0-beta.1", - DNSVersion: "1.14.5", + KubeVersion: "v1.10.0-beta.1", + KubeadmVersion: "v1.10.0-beta.1", + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", }, }, }, @@ -296,30 +319,32 @@ func TestGetAvailableUpgrades(t *testing.T) { }, { // upgrade to an RC version should be supported vg: &fakeVersionGetter{ - clusterVersion: "v1.7.5", - kubeletVersion: "v1.7.5", - kubeadmVersion: "v1.7.5", + clusterVersion: "v1.9.5", + kubeletVersion: "v1.9.5", + kubeadmVersion: "v1.9.5", - stablePatchVersion: "v1.7.5", - stableVersion: "v1.7.5", - latestDevBranchVersion: "v1.8.0-rc.1", - latestVersion: "v1.9.0-alpha.1", + stablePatchVersion: "v1.9.5", + stableVersion: "v1.9.5", + latestDevBranchVersion: "v1.10.0-rc.1", + latestVersion: "v1.11.0-alpha.1", }, expectedUpgrades: []Upgrade{ { Description: "release candidate version", Before: ClusterState{ - KubeVersion: "v1.7.5", + KubeVersion: "v1.9.5", KubeletVersions: map[string]uint16{ - "v1.7.5": 1, + "v1.9.5": 1, }, - KubeadmVersion: "v1.7.5", - DNSVersion: "1.14.5", + KubeadmVersion: "v1.9.5", + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", }, After: ClusterState{ - KubeVersion: "v1.8.0-rc.1", - KubeadmVersion: "v1.8.0-rc.1", - DNSVersion: "1.14.5", + KubeVersion: "v1.10.0-rc.1", + KubeadmVersion: "v1.10.0-rc.1", + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", }, }, }, @@ -328,30 +353,32 @@ func TestGetAvailableUpgrades(t *testing.T) { }, { // it is possible (but very uncommon) that the latest version from the previous branch is an rc and the current latest version is alpha.0. In that case, show the RC vg: &fakeVersionGetter{ - clusterVersion: "v1.7.5", - kubeletVersion: "v1.7.5", - kubeadmVersion: "v1.7.5", + clusterVersion: "v1.9.5", + kubeletVersion: "v1.9.5", + kubeadmVersion: "v1.9.5", - stablePatchVersion: "v1.7.5", - stableVersion: "v1.7.5", - latestDevBranchVersion: "v1.8.0-rc.1", - latestVersion: "v1.9.0-alpha.0", + stablePatchVersion: "v1.9.5", + stableVersion: "v1.9.5", + latestDevBranchVersion: "v1.10.6-rc.1", + latestVersion: "v1.11.1-alpha.0", }, expectedUpgrades: []Upgrade{ { Description: "experimental version", // Note that this is considered an experimental version in this uncommon scenario Before: ClusterState{ - KubeVersion: "v1.7.5", + KubeVersion: "v1.9.5", KubeletVersions: map[string]uint16{ - "v1.7.5": 1, + "v1.9.5": 1, }, - KubeadmVersion: "v1.7.5", - DNSVersion: "1.14.5", + KubeadmVersion: "v1.9.5", + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", }, After: ClusterState{ - KubeVersion: "v1.8.0-rc.1", - KubeadmVersion: "v1.8.0-rc.1", - DNSVersion: "1.14.5", + KubeVersion: "v1.10.6-rc.1", + KubeadmVersion: "v1.10.6-rc.1", + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", }, }, }, @@ -360,46 +387,50 @@ func TestGetAvailableUpgrades(t *testing.T) { }, { // upgrade to an RC version should be supported. There may also be an even newer unstable version. vg: &fakeVersionGetter{ - clusterVersion: "v1.7.5", - kubeletVersion: "v1.7.5", - kubeadmVersion: "v1.7.5", + clusterVersion: "v1.9.5", + kubeletVersion: "v1.9.5", + kubeadmVersion: "v1.9.5", - stablePatchVersion: "v1.7.5", - stableVersion: "v1.7.5", - latestDevBranchVersion: "v1.8.0-rc.1", - latestVersion: "v1.9.0-alpha.1", + stablePatchVersion: "v1.9.5", + stableVersion: "v1.9.5", + latestDevBranchVersion: "v1.10.0-rc.1", + latestVersion: "v1.11.0-alpha.2", }, expectedUpgrades: []Upgrade{ { Description: "release candidate version", Before: ClusterState{ - KubeVersion: "v1.7.5", + KubeVersion: "v1.9.5", KubeletVersions: map[string]uint16{ - "v1.7.5": 1, + "v1.9.5": 1, }, - KubeadmVersion: "v1.7.5", - DNSVersion: "1.14.5", + KubeadmVersion: "v1.9.5", + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", }, After: ClusterState{ - KubeVersion: "v1.8.0-rc.1", - KubeadmVersion: "v1.8.0-rc.1", - DNSVersion: "1.14.5", + KubeVersion: "v1.10.0-rc.1", + KubeadmVersion: "v1.10.0-rc.1", + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", }, }, { Description: "experimental version", Before: ClusterState{ - KubeVersion: "v1.7.5", + KubeVersion: "v1.9.5", KubeletVersions: map[string]uint16{ - "v1.7.5": 1, + "v1.9.5": 1, }, - KubeadmVersion: "v1.7.5", - DNSVersion: "1.14.5", + KubeadmVersion: "v1.9.5", + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", }, After: ClusterState{ - KubeVersion: "v1.9.0-alpha.1", - KubeadmVersion: "v1.9.0-alpha.1", - DNSVersion: "1.14.5", + KubeVersion: "v1.11.0-alpha.2", + KubeadmVersion: "v1.11.0-alpha.2", + DNSVersion: "1.14.7", + EtcdVersion: "3.1.10", }, }, }, @@ -409,9 +440,12 @@ func TestGetAvailableUpgrades(t *testing.T) { }, } + // Instantiating a fake etcd cluster for being able to get etcd version for a corresponding + // kubernetes release. + testCluster := fakeEtcdCluster{} for _, rt := range tests { - actualUpgrades, actualErr := GetAvailableUpgrades(rt.vg, rt.allowExperimental, rt.allowRCs) + actualUpgrades, actualErr := GetAvailableUpgrades(rt.vg, rt.allowExperimental, rt.allowRCs, testCluster, featureGates) if !reflect.DeepEqual(actualUpgrades, rt.expectedUpgrades) { t.Errorf("failed TestGetAvailableUpgrades\n\texpected upgrades: %v\n\tgot: %v", rt.expectedUpgrades, actualUpgrades) } @@ -429,36 +463,36 @@ func TestKubeletUpgrade(t *testing.T) { }{ { // upgrade available before: map[string]uint16{ - "v1.7.1": 1, + "v1.9.1": 1, }, - after: "v1.7.3", + after: "v1.9.3", expected: true, }, { // upgrade available before: map[string]uint16{ - "v1.7.1": 1, - "v1.7.3": 100, + "v1.9.1": 1, + "v1.9.3": 100, }, - after: "v1.7.3", + after: "v1.9.3", expected: true, }, { // upgrade not available before: map[string]uint16{ - "v1.7.3": 1, + "v1.9.3": 1, }, - after: "v1.7.3", + after: "v1.9.3", expected: false, }, { // upgrade not available before: map[string]uint16{ - "v1.7.3": 100, + "v1.9.3": 100, }, - after: "v1.7.3", + after: "v1.9.3", expected: false, }, { // upgrade not available if we don't know anything about the earlier state before: map[string]uint16{}, - after: "v1.7.3", + after: "v1.9.3", expected: false, }, } diff --git a/cmd/kubeadm/app/phases/upgrade/configuration.go b/cmd/kubeadm/app/phases/upgrade/configuration.go index a00dafd1a91..589578e9739 100644 --- a/cmd/kubeadm/app/phases/upgrade/configuration.go +++ b/cmd/kubeadm/app/phases/upgrade/configuration.go @@ -30,7 +30,7 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" "k8s.io/kubernetes/cmd/kubeadm/app/constants" configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" ) // FetchConfiguration fetches configuration required for upgrading your cluster from a file (which has precedence) or a ConfigMap in the cluster @@ -64,7 +64,7 @@ func loadConfigurationBytes(client clientset.Interface, w io.Writer, cfgPath str configMap, err := client.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(constants.MasterConfigurationConfigMap, metav1.GetOptions{}) if apierrors.IsNotFound(err) { fmt.Printf("[upgrade/config] In order to upgrade, a ConfigMap called %q in the %s namespace must exist.\n", constants.MasterConfigurationConfigMap, metav1.NamespaceSystem) - fmt.Println("[upgrade/config] Without this information, 'kubeadm upgrade' don't how to configure your upgraded cluster.") + fmt.Println("[upgrade/config] Without this information, 'kubeadm upgrade' won't know how to configure your upgraded cluster.") fmt.Println("") fmt.Println("[upgrade/config] Next steps:") fmt.Printf("\t- OPTION 1: Run 'kubeadm config upload from-flags' and specify the same CLI arguments you passed to 'kubeadm init' when you created your master.\n") @@ -86,12 +86,12 @@ func bytesToValidatedMasterConfig(b []byte) (*kubeadmapiext.MasterConfiguration, finalCfg := &kubeadmapiext.MasterConfiguration{} internalcfg := &kubeadmapi.MasterConfiguration{} - if err := runtime.DecodeInto(api.Codecs.UniversalDecoder(), b, cfg); err != nil { + if err := runtime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), b, cfg); err != nil { return nil, fmt.Errorf("unable to decode config from bytes: %v", err) } // Default and convert to the internal version - api.Scheme.Default(cfg) - api.Scheme.Convert(cfg, internalcfg, nil) + legacyscheme.Scheme.Default(cfg) + legacyscheme.Scheme.Convert(cfg, internalcfg, nil) // Applies dynamic defaults to settings not provided with flags if err := configutil.SetInitDynamicDefaults(internalcfg); err != nil { @@ -102,6 +102,6 @@ func bytesToValidatedMasterConfig(b []byte) (*kubeadmapiext.MasterConfiguration, return nil, err } // Finally converts back to the external version - api.Scheme.Convert(internalcfg, finalCfg, nil) + legacyscheme.Scheme.Convert(internalcfg, finalCfg, nil) return finalCfg, nil } diff --git a/cmd/kubeadm/app/phases/upgrade/health.go b/cmd/kubeadm/app/phases/upgrade/health.go index 719eced4f30..50b014dee6e 100644 --- a/cmd/kubeadm/app/phases/upgrade/health.go +++ b/cmd/kubeadm/app/phases/upgrade/health.go @@ -21,76 +21,75 @@ import ( "net/http" "os" - apps "k8s.io/api/apps/v1beta2" + apps "k8s.io/api/apps/v1" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/sets" clientset "k8s.io/client-go/kubernetes" "k8s.io/kubernetes/cmd/kubeadm/app/constants" + "k8s.io/kubernetes/cmd/kubeadm/app/preflight" ) // healthCheck is a helper struct for easily performing healthchecks against the cluster and printing the output type healthCheck struct { - description, okMessage, failMessage string - // f is invoked with a k8s client passed to it. Should return an optional warning and/or an error + name string + client clientset.Interface + // f is invoked with a k8s client passed to it. Should return an optional error f func(clientset.Interface) error } +// Check is part of the preflight.Checker interface +func (c *healthCheck) Check() (warnings, errors []error) { + if err := c.f(c.client); err != nil { + return nil, []error{err} + } + return nil, nil +} + +// Name is part of the preflight.Checker interface +func (c *healthCheck) Name() string { + return c.name +} + // CheckClusterHealth makes sure: // - the API /healthz endpoint is healthy -// - all Nodes are Ready +// - all master Nodes are Ready // - (if self-hosted) that there are DaemonSets with at least one Pod for all control plane components // - (if static pod-hosted) that all required Static Pod manifests exist on disk -func CheckClusterHealth(client clientset.Interface) error { +func CheckClusterHealth(client clientset.Interface, ignoreChecksErrors sets.String) error { fmt.Println("[upgrade] Making sure the cluster is healthy:") - healthChecks := []healthCheck{ - { - description: "API Server health", - okMessage: "Healthy", - failMessage: "Unhealthy", - f: apiServerHealthy, + healthChecks := []preflight.Checker{ + &healthCheck{ + name: "APIServerHealth", + client: client, + f: apiServerHealthy, }, - { - description: "Node health", - okMessage: "All Nodes are healthy", - failMessage: "More than one Node unhealthy", - f: nodesHealthy, + &healthCheck{ + name: "MasterNodesReady", + client: client, + f: masterNodesReady, }, // TODO: Add a check for ComponentStatuses here? } // Run slightly different health checks depending on control plane hosting type if IsControlPlaneSelfHosted(client) { - healthChecks = append(healthChecks, healthCheck{ - description: "Control plane DaemonSet health", - okMessage: "All control plane DaemonSets are healthy", - failMessage: "More than one control plane DaemonSet unhealthy", - f: controlPlaneHealth, + healthChecks = append(healthChecks, &healthCheck{ + name: "ControlPlaneHealth", + client: client, + f: controlPlaneHealth, }) } else { - healthChecks = append(healthChecks, healthCheck{ - description: "Static Pod manifests exists on disk", - okMessage: "All manifests exist on disk", - failMessage: "Some manifests don't exist on disk", - f: staticPodManifestHealth, + healthChecks = append(healthChecks, &healthCheck{ + name: "StaticPodManifest", + client: client, + f: staticPodManifestHealth, }) } - return runHealthChecks(client, healthChecks) -} - -// runHealthChecks runs a set of health checks against the cluster -func runHealthChecks(client clientset.Interface, healthChecks []healthCheck) error { - for _, check := range healthChecks { - - err := check.f(client) - if err != nil { - fmt.Printf("[upgrade/health] Checking %s: %s\n", check.description, check.failMessage) - return fmt.Errorf("The cluster is not in an upgradeable state due to: %v", err) - } - fmt.Printf("[upgrade/health] Checking %s: %s\n", check.description, check.okMessage) - } - return nil + return preflight.RunChecks(healthChecks, os.Stderr, ignoreChecksErrors) } // apiServerHealthy checks whether the API server's /healthz endpoint is healthy @@ -108,16 +107,25 @@ func apiServerHealthy(client clientset.Interface) error { return nil } -// nodesHealthy checks whether all Nodes in the cluster are in the Running state -func nodesHealthy(client clientset.Interface) error { - nodes, err := client.CoreV1().Nodes().List(metav1.ListOptions{}) +// masterNodesReady checks whether all master Nodes in the cluster are in the Running state +func masterNodesReady(client clientset.Interface) error { + selector := labels.SelectorFromSet(labels.Set(map[string]string{ + constants.LabelNodeRoleMaster: "", + })) + masters, err := client.CoreV1().Nodes().List(metav1.ListOptions{ + LabelSelector: selector.String(), + }) if err != nil { - return fmt.Errorf("couldn't list all nodes in cluster: %v", err) + return fmt.Errorf("couldn't list masters in cluster: %v", err) } - notReadyNodes := getNotReadyNodes(nodes.Items) - if len(notReadyNodes) != 0 { - return fmt.Errorf("there are NotReady Nodes in the cluster: %v", notReadyNodes) + if len(masters.Items) == 0 { + return fmt.Errorf("failed to find any nodes with master role") + } + + notReadyMasters := getNotReadyNodes(masters.Items) + if len(notReadyMasters) != 0 { + return fmt.Errorf("there are NotReady masters in the cluster: %v", notReadyMasters) } return nil } @@ -166,7 +174,7 @@ func getNotReadyDaemonSets(client clientset.Interface) ([]error, error) { notReadyDaemonSets := []error{} for _, component := range constants.MasterComponents { dsName := constants.AddSelfHostedPrefix(component) - ds, err := client.AppsV1beta2().DaemonSets(metav1.NamespaceSystem).Get(dsName, metav1.GetOptions{}) + ds, err := client.AppsV1().DaemonSets(metav1.NamespaceSystem).Get(dsName, metav1.GetOptions{}) if err != nil { return nil, fmt.Errorf("couldn't get daemonset %q in the %s namespace", dsName, metav1.NamespaceSystem) } diff --git a/cmd/kubeadm/app/phases/upgrade/policy.go b/cmd/kubeadm/app/phases/upgrade/policy.go index 0e3b13223c5..185aa6675ad 100644 --- a/cmd/kubeadm/app/phases/upgrade/policy.go +++ b/cmd/kubeadm/app/phases/upgrade/policy.go @@ -28,6 +28,9 @@ const ( // MaximumAllowedMinorVersionUpgradeSkew describes how many minor versions kubeadm can upgrade the control plane version in one go MaximumAllowedMinorVersionUpgradeSkew = 1 + // MaximumAllowedMinorVersionDowngradeSkew describes how many minor versions kubeadm can upgrade the control plane version in one go + MaximumAllowedMinorVersionDowngradeSkew = 1 + // MaximumAllowedMinorVersionKubeletSkew describes how many minor versions the control plane version and the kubelet can skew in a kubeadm cluster MaximumAllowedMinorVersionKubeletSkew = 1 ) @@ -63,7 +66,7 @@ func EnforceVersionPolicies(versionGetter VersionGetter, newK8sVersionStr string kubeletVersions, err := versionGetter.KubeletVersions() if err != nil { // This is a non-critical error; continue although kubeadm couldn't look this up - skewErrors.Skippable = append(skewErrors.Skippable, fmt.Errorf("Unable to fetch kubeadm version: %v", err)) + skewErrors.Skippable = append(skewErrors.Skippable, fmt.Errorf("Unable to fetch kubelet version: %v", err)) } // Make sure the new version is a supported version (higher than the minimum one supported) @@ -72,23 +75,41 @@ func EnforceVersionPolicies(versionGetter VersionGetter, newK8sVersionStr string skewErrors.Mandatory = append(skewErrors.Mandatory, fmt.Errorf("Specified version to upgrade to %q is equal to or lower than the minimum supported version %q. Please specify a higher version to upgrade to", newK8sVersionStr, clusterVersionStr)) } - // Make sure new version is higher than the current Kubernetes version - if clusterVersion.AtLeast(newK8sVersion) { - // Even though we don't officially support downgrades, it "should work", and if user(s) need it and are willing to try; they can do so with --force - skewErrors.Skippable = append(skewErrors.Skippable, fmt.Errorf("Specified version to upgrade to %q is equal to or lower than the cluster version %q. Downgrades are not supported yet", newK8sVersionStr, clusterVersionStr)) - } else { - // If this code path runs, it's an upgrade (this code will run most of the time) - // kubeadm doesn't support upgrades between two minor versions; e.g. a v1.7 -> v1.9 upgrade is not supported. Enforce that here - if newK8sVersion.Minor() > clusterVersion.Minor()+MaximumAllowedMinorVersionUpgradeSkew { - skewErrors.Mandatory = append(skewErrors.Mandatory, fmt.Errorf("Specified version to upgrade to %q is too high; kubeadm can upgrade only %d minor version at a time", newK8sVersionStr, MaximumAllowedMinorVersionUpgradeSkew)) + // kubeadm doesn't support upgrades between two minor versions; e.g. a v1.7 -> v1.9 upgrade is not supported right away + if newK8sVersion.Minor() > clusterVersion.Minor()+MaximumAllowedMinorVersionUpgradeSkew { + tooLargeUpgradeSkewErr := fmt.Errorf("Specified version to upgrade to %q is too high; kubeadm can upgrade only %d minor version at a time", newK8sVersionStr, MaximumAllowedMinorVersionUpgradeSkew) + // If the version that we're about to upgrade to is a released version, we should fully enforce this policy + // If the version is a CI/dev/experimental version, it's okay to jump two minor version steps, but then require the -f flag + if len(newK8sVersion.PreRelease()) == 0 { + skewErrors.Mandatory = append(skewErrors.Mandatory, tooLargeUpgradeSkewErr) + } else { + skewErrors.Skippable = append(skewErrors.Skippable, tooLargeUpgradeSkewErr) + } + } + + // kubeadm doesn't support downgrades between two minor versions; e.g. a v1.9 -> v1.7 downgrade is not supported right away + if newK8sVersion.Minor() < clusterVersion.Minor()-MaximumAllowedMinorVersionDowngradeSkew { + tooLargeDowngradeSkewErr := fmt.Errorf("Specified version to downgrade to %q is too low; kubeadm can downgrade only %d minor version at a time", newK8sVersionStr, MaximumAllowedMinorVersionDowngradeSkew) + // If the version that we're about to downgrade to is a released version, we should fully enforce this policy + // If the version is a CI/dev/experimental version, it's okay to jump two minor version steps, but then require the -f flag + if len(newK8sVersion.PreRelease()) == 0 { + skewErrors.Mandatory = append(skewErrors.Mandatory, tooLargeDowngradeSkewErr) + } else { + skewErrors.Skippable = append(skewErrors.Skippable, tooLargeDowngradeSkewErr) } } // If the kubeadm version is lower than what we want to upgrade to; error if kubeadmVersion.LessThan(newK8sVersion) { if newK8sVersion.Minor() > kubeadmVersion.Minor() { - // This is totally unsupported; kubeadm has no idea how it should handle a newer minor release than itself - skewErrors.Mandatory = append(skewErrors.Mandatory, fmt.Errorf("Specified version to upgrade to %q is one minor release higher than the kubeadm minor release (%d > %d). Such an upgrade is not supported", newK8sVersionStr, newK8sVersion.Minor(), kubeadmVersion.Minor())) + tooLargeKubeadmSkew := fmt.Errorf("Specified version to upgrade to %q is at least one minor release higher than the kubeadm minor release (%d > %d). Such an upgrade is not supported", newK8sVersionStr, newK8sVersion.Minor(), kubeadmVersion.Minor()) + // This is unsupported; kubeadm has no idea how it should handle a newer minor release than itself + // If the version is a CI/dev/experimental version though, lower the severity of this check, but then require the -f flag + if len(newK8sVersion.PreRelease()) == 0 { + skewErrors.Mandatory = append(skewErrors.Mandatory, tooLargeKubeadmSkew) + } else { + skewErrors.Skippable = append(skewErrors.Skippable, tooLargeKubeadmSkew) + } } else { // Upgrading to a higher patch version than kubeadm is ok if the user specifies --force. Not recommended, but possible. skewErrors.Skippable = append(skewErrors.Skippable, fmt.Errorf("Specified version to upgrade to %q is higher than the kubeadm version %q. Upgrade kubeadm first using the tool you used to install kubeadm", newK8sVersionStr, kubeadmVersionStr)) diff --git a/cmd/kubeadm/app/phases/upgrade/policy_test.go b/cmd/kubeadm/app/phases/upgrade/policy_test.go index 9b81609000b..e4b1cdc08e6 100644 --- a/cmd/kubeadm/app/phases/upgrade/policy_test.go +++ b/cmd/kubeadm/app/phases/upgrade/policy_test.go @@ -32,129 +32,135 @@ func TestEnforceVersionPolicies(t *testing.T) { }{ { // everything ok vg: &fakeVersionGetter{ - clusterVersion: "v1.8.3", - kubeletVersion: "v1.8.3", - kubeadmVersion: "v1.8.5", + clusterVersion: "v1.9.3", + kubeletVersion: "v1.9.3", + kubeadmVersion: "v1.9.5", }, - newK8sVersion: "v1.8.5", + newK8sVersion: "v1.9.5", }, { // everything ok vg: &fakeVersionGetter{ - clusterVersion: "v1.8.3", - kubeletVersion: "v1.8.2", - kubeadmVersion: "v1.9.1", + clusterVersion: "v1.9.3", + kubeletVersion: "v1.9.2", + kubeadmVersion: "v1.10.1", }, - newK8sVersion: "v1.9.0", + newK8sVersion: "v1.10.0", }, - { // downgrades not supported + { // downgrades ok vg: &fakeVersionGetter{ - clusterVersion: "v1.8.3", - kubeletVersion: "v1.8.3", - kubeadmVersion: "v1.8.3", + clusterVersion: "v1.9.3", + kubeletVersion: "v1.9.3", + kubeadmVersion: "v1.9.3", }, - newK8sVersion: "v1.8.2", - expectedSkippableErrs: 1, + newK8sVersion: "v1.9.2", }, - { // upgrades without bumping the version number not supported yet. TODO: Change this? + { // upgrades without bumping the version number ok vg: &fakeVersionGetter{ - clusterVersion: "v1.8.3", - kubeletVersion: "v1.8.3", - kubeadmVersion: "v1.8.3", + clusterVersion: "v1.9.3", + kubeletVersion: "v1.9.3", + kubeadmVersion: "v1.9.3", }, - newK8sVersion: "v1.8.3", - expectedSkippableErrs: 1, + newK8sVersion: "v1.9.3", }, - { // new version must be higher than v1.8.0 + { // new version must be higher than v1.9.0 vg: &fakeVersionGetter{ - clusterVersion: "v1.8.3", - kubeletVersion: "v1.8.3", - kubeadmVersion: "v1.8.3", + clusterVersion: "v1.9.3", + kubeletVersion: "v1.9.3", + kubeadmVersion: "v1.9.3", }, - newK8sVersion: "v1.7.10", - expectedMandatoryErrs: 1, // version must be higher than v1.8.0 - expectedSkippableErrs: 1, // version shouldn't be downgraded + newK8sVersion: "v1.8.10", + expectedMandatoryErrs: 1, // version must be higher than v1.9.0 }, { // upgrading two minor versions in one go is not supported vg: &fakeVersionGetter{ - clusterVersion: "v1.8.3", - kubeletVersion: "v1.8.3", - kubeadmVersion: "v1.10.0", + clusterVersion: "v1.9.3", + kubeletVersion: "v1.9.3", + kubeadmVersion: "v1.11.0", }, - newK8sVersion: "v1.10.0", + newK8sVersion: "v1.11.0", expectedMandatoryErrs: 1, // can't upgrade two minor versions expectedSkippableErrs: 1, // kubelet <-> apiserver skew too large }, + { // downgrading two minor versions in one go is not supported + vg: &fakeVersionGetter{ + clusterVersion: "v1.11.3", + kubeletVersion: "v1.11.3", + kubeadmVersion: "v1.11.0", + }, + newK8sVersion: "v1.9.3", + expectedMandatoryErrs: 1, // can't downgrade two minor versions + }, { // kubeadm version must be higher than the new kube version. However, patch version skews may be forced vg: &fakeVersionGetter{ - clusterVersion: "v1.8.3", - kubeletVersion: "v1.8.3", - kubeadmVersion: "v1.8.3", + clusterVersion: "v1.9.3", + kubeletVersion: "v1.9.3", + kubeadmVersion: "v1.9.3", }, - newK8sVersion: "v1.8.5", + newK8sVersion: "v1.9.5", expectedSkippableErrs: 1, }, { // kubeadm version must be higher than the new kube version. Trying to upgrade k8s to a higher minor version than kubeadm itself should never be supported vg: &fakeVersionGetter{ - clusterVersion: "v1.8.3", - kubeletVersion: "v1.8.3", - kubeadmVersion: "v1.8.3", + clusterVersion: "v1.9.3", + kubeletVersion: "v1.9.3", + kubeadmVersion: "v1.9.3", }, - newK8sVersion: "v1.9.0", + newK8sVersion: "v1.10.0", expectedMandatoryErrs: 1, }, { // the maximum skew between the cluster version and the kubelet versions should be one minor version. This may be forced through though. vg: &fakeVersionGetter{ - clusterVersion: "v1.8.3", - kubeletVersion: "v1.7.8", - kubeadmVersion: "v1.9.0", + clusterVersion: "v1.9.3", + kubeletVersion: "v1.8.8", + kubeadmVersion: "v1.10.0", }, - newK8sVersion: "v1.9.0", + newK8sVersion: "v1.10.0", expectedSkippableErrs: 1, }, { // experimental upgrades supported if the flag is set vg: &fakeVersionGetter{ - clusterVersion: "v1.8.3", - kubeletVersion: "v1.8.3", - kubeadmVersion: "v1.9.0-beta.1", + clusterVersion: "v1.9.3", + kubeletVersion: "v1.9.3", + kubeadmVersion: "v1.10.0-beta.1", }, - newK8sVersion: "v1.9.0-beta.1", + newK8sVersion: "v1.10.0-beta.1", allowExperimental: true, }, { // release candidate upgrades supported if the flag is set vg: &fakeVersionGetter{ - clusterVersion: "v1.8.3", - kubeletVersion: "v1.8.3", - kubeadmVersion: "v1.9.0-rc.1", + clusterVersion: "v1.9.3", + kubeletVersion: "v1.9.3", + kubeadmVersion: "v1.10.0-rc.1", }, - newK8sVersion: "v1.9.0-rc.1", + newK8sVersion: "v1.10.0-rc.1", allowRCs: true, }, { // release candidate upgrades supported if the flag is set vg: &fakeVersionGetter{ - clusterVersion: "v1.8.3", - kubeletVersion: "v1.8.3", - kubeadmVersion: "v1.9.0-rc.1", + clusterVersion: "v1.9.3", + kubeletVersion: "v1.9.3", + kubeadmVersion: "v1.10.0-rc.1", }, - newK8sVersion: "v1.9.0-rc.1", + newK8sVersion: "v1.10.0-rc.1", allowExperimental: true, }, { // the user should not be able to upgrade to an experimental version if they haven't opted into that vg: &fakeVersionGetter{ - clusterVersion: "v1.8.3", - kubeletVersion: "v1.8.3", - kubeadmVersion: "v1.9.0-beta.1", + clusterVersion: "v1.9.3", + kubeletVersion: "v1.9.3", + kubeadmVersion: "v1.10.0-beta.1", }, - newK8sVersion: "v1.9.0-beta.1", + newK8sVersion: "v1.10.0-beta.1", allowRCs: true, expectedSkippableErrs: 1, }, { // the user should not be able to upgrade to an release candidate version if they haven't opted into that vg: &fakeVersionGetter{ - clusterVersion: "v1.8.3", - kubeletVersion: "v1.8.3", - kubeadmVersion: "v1.9.0-rc.1", + clusterVersion: "v1.9.3", + kubeletVersion: "v1.9.3", + kubeadmVersion: "v1.10.0-rc.1", }, - newK8sVersion: "v1.9.0-rc.1", + newK8sVersion: "v1.10.0-rc.1", expectedSkippableErrs: 1, }, } diff --git a/cmd/kubeadm/app/phases/upgrade/postupgrade.go b/cmd/kubeadm/app/phases/upgrade/postupgrade.go index b2c18df2d88..f18c2e23f79 100644 --- a/cmd/kubeadm/app/phases/upgrade/postupgrade.go +++ b/cmd/kubeadm/app/phases/upgrade/postupgrade.go @@ -17,20 +17,32 @@ limitations under the License. package upgrade import ( + "fmt" + "os" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/errors" clientset "k8s.io/client-go/kubernetes" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" + kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" + "k8s.io/kubernetes/cmd/kubeadm/app/features" "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns" "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/proxy" "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo" nodebootstraptoken "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node" + certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs" + "k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting" "k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig" + "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" + dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun" "k8s.io/kubernetes/pkg/util/version" ) // PerformPostUpgradeTasks runs nearly the same functions as 'kubeadm init' would do // Note that the markmaster phase is left out, not needed, and no token is created as that doesn't belong to the upgrade -func PerformPostUpgradeTasks(client clientset.Interface, cfg *kubeadmapi.MasterConfiguration, k8sVersion *version.Version) error { +func PerformPostUpgradeTasks(client clientset.Interface, cfg *kubeadmapi.MasterConfiguration, newK8sVer *version.Version, dryRun bool) error { errs := []error{} // Upload currently used configuration to the cluster @@ -41,17 +53,22 @@ func PerformPostUpgradeTasks(client clientset.Interface, cfg *kubeadmapi.MasterC } // Create/update RBAC rules that makes the bootstrap tokens able to post CSRs - if err := nodebootstraptoken.AllowBootstrapTokensToPostCSRs(client, k8sVersion); err != nil { + if err := nodebootstraptoken.AllowBootstrapTokensToPostCSRs(client); err != nil { errs = append(errs, err) } // Create/update RBAC rules that makes the bootstrap tokens able to get their CSRs approved automatically - if err := nodebootstraptoken.AutoApproveNodeBootstrapTokens(client, k8sVersion); err != nil { + if err := nodebootstraptoken.AutoApproveNodeBootstrapTokens(client); err != nil { errs = append(errs, err) } - // Create/update RBAC rules that makes the 1.8.0+ nodes to rotate certificates and get their CSRs approved automatically - if err := nodebootstraptoken.AutoApproveNodeCertificateRotation(client, k8sVersion); err != nil { + // Create/update RBAC rules that makes the nodes to rotate certificates and get their CSRs approved automatically + if err := nodebootstraptoken.AutoApproveNodeCertificateRotation(client); err != nil { + errs = append(errs, err) + } + + // Upgrade to a self-hosted control plane if possible + if err := upgradeToSelfHosting(client, cfg, newK8sVer, dryRun); err != nil { errs = append(errs, err) } @@ -65,12 +82,73 @@ func PerformPostUpgradeTasks(client clientset.Interface, cfg *kubeadmapi.MasterC errs = append(errs, err) } + certAndKeyDir := kubeadmapiext.DefaultCertificatesDir + shouldBackup, err := shouldBackupAPIServerCertAndKey(certAndKeyDir, newK8sVer) + // Don't fail the upgrade phase if failing to determine to backup kube-apiserver cert and key. + if err != nil { + fmt.Printf("[postupgrade] WARNING: failed to determine to backup kube-apiserver cert and key: %v", err) + } else if shouldBackup { + // Don't fail the upgrade phase if failing to backup kube-apiserver cert and key. + if err := backupAPIServerCertAndKey(certAndKeyDir); err != nil { + fmt.Printf("[postupgrade] WARNING: failed to backup kube-apiserver cert and key: %v", err) + } + if err := certsphase.CreateAPIServerCertAndKeyFiles(cfg); err != nil { + errs = append(errs, err) + } + } + // Upgrade kube-dns and kube-proxy if err := dns.EnsureDNSAddon(cfg, client); err != nil { errs = append(errs, err) } + // Remove the old kube-dns deployment if coredns is now used + if !dryRun { + if err := removeOldKubeDNSDeploymentIfCoreDNSIsUsed(cfg, client); err != nil { + errs = append(errs, err) + } + } + if err := proxy.EnsureProxyAddon(cfg, client); err != nil { errs = append(errs, err) } return errors.NewAggregate(errs) } + +func removeOldKubeDNSDeploymentIfCoreDNSIsUsed(cfg *kubeadmapi.MasterConfiguration, client clientset.Interface) error { + if features.Enabled(cfg.FeatureGates, features.CoreDNS) { + return apiclient.TryRunCommand(func() error { + coreDNSDeployment, err := client.AppsV1().Deployments(metav1.NamespaceSystem).Get(kubeadmconstants.CoreDNS, metav1.GetOptions{}) + if err != nil { + return err + } + if coreDNSDeployment.Status.ReadyReplicas == 0 { + return fmt.Errorf("the CodeDNS deployment isn't ready yet") + } + return apiclient.DeleteDeploymentForeground(client, metav1.NamespaceSystem, kubeadmconstants.KubeDNS) + }, 10) + } + return nil +} + +func upgradeToSelfHosting(client clientset.Interface, cfg *kubeadmapi.MasterConfiguration, newK8sVer *version.Version, dryRun bool) error { + if features.Enabled(cfg.FeatureGates, features.SelfHosting) && !IsControlPlaneSelfHosted(client) && newK8sVer.AtLeast(v190alpha3) { + + waiter := getWaiter(dryRun, client) + + // kubeadm will now convert the static Pod-hosted control plane into a self-hosted one + fmt.Println("[self-hosted] Creating self-hosted control plane.") + if err := selfhosting.CreateSelfHostedControlPlane(kubeadmconstants.GetStaticPodDirectory(), kubeadmconstants.KubernetesDir, cfg, client, waiter, dryRun); err != nil { + return fmt.Errorf("error creating self hosted control plane: %v", err) + } + } + return nil +} + +// getWaiter gets the right waiter implementation for the right occasion +// TODO: Consolidate this with what's in init.go? +func getWaiter(dryRun bool, client clientset.Interface) apiclient.Waiter { + if dryRun { + return dryrunutil.NewWaiter() + } + return apiclient.NewKubeWaiter(client, 30*time.Minute, os.Stdout) +} diff --git a/cmd/kubeadm/app/phases/upgrade/postupgrade_v18_19.go b/cmd/kubeadm/app/phases/upgrade/postupgrade_v18_19.go new file mode 100644 index 00000000000..ef8f5d305d5 --- /dev/null +++ b/cmd/kubeadm/app/phases/upgrade/postupgrade_v18_19.go @@ -0,0 +1,106 @@ +/* +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 upgrade + +import ( + "crypto/x509" + "encoding/pem" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "time" + + "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/kubernetes/cmd/kubeadm/app/constants" + "k8s.io/kubernetes/pkg/util/version" +) + +// TODO: Maybe move these constants elsewhere in future releases +var v190 = version.MustParseSemantic("v1.9.0") +var v190alpha3 = version.MustParseSemantic("v1.9.0-alpha.3") +var expiry = 180 * 24 * time.Hour + +// backupAPIServerCertAndKey backups the old cert and key of kube-apiserver to a specified directory. +func backupAPIServerCertAndKey(certAndKeyDir string) error { + subDir := filepath.Join(certAndKeyDir, "expired") + if err := os.Mkdir(subDir, 0766); err != nil { + return fmt.Errorf("failed to created backup directory %s: %v", subDir, err) + } + + filesToMove := map[string]string{ + filepath.Join(certAndKeyDir, constants.APIServerCertName): filepath.Join(subDir, constants.APIServerCertName), + filepath.Join(certAndKeyDir, constants.APIServerKeyName): filepath.Join(subDir, constants.APIServerKeyName), + } + return moveFiles(filesToMove) +} + +// moveFiles moves files from one directory to another. +func moveFiles(files map[string]string) error { + filesToRecover := map[string]string{} + for from, to := range files { + if err := os.Rename(from, to); err != nil { + return rollbackFiles(filesToRecover, err) + } + filesToRecover[to] = from + } + return nil +} + +// rollbackFiles moves the files back to the original directory. +func rollbackFiles(files map[string]string, originalErr error) error { + errs := []error{originalErr} + for from, to := range files { + if err := os.Rename(from, to); err != nil { + errs = append(errs, err) + } + } + return fmt.Errorf("couldn't move these files: %v. Got errors: %v", files, errors.NewAggregate(errs)) +} + +// shouldBackupAPIServerCertAndKey check if the new k8s version is at least 1.9.0 +// and kube-apiserver will be expired in 60 days. +func shouldBackupAPIServerCertAndKey(certAndKeyDir string, newK8sVer *version.Version) (bool, error) { + if newK8sVer.LessThan(v190) { + return false, nil + } + + apiServerCert := filepath.Join(certAndKeyDir, constants.APIServerCertName) + data, err := ioutil.ReadFile(apiServerCert) + if err != nil { + return false, fmt.Errorf("failed to read kube-apiserver certificate from disk: %v", err) + } + + block, _ := pem.Decode(data) + if block == nil { + return false, fmt.Errorf("expected the kube-apiserver certificate to be PEM encoded") + } + + certs, err := x509.ParseCertificates(block.Bytes) + if err != nil { + return false, fmt.Errorf("unable to parse certificate data: %v", err) + } + if len(certs) == 0 { + return false, fmt.Errorf("no certificate data found") + } + + if time.Now().Sub(certs[0].NotBefore) > expiry { + return true, nil + } + + return false, nil +} diff --git a/cmd/kubeadm/app/phases/upgrade/postupgrade_v18_19_test.go b/cmd/kubeadm/app/phases/upgrade/postupgrade_v18_19_test.go new file mode 100644 index 00000000000..6720202f64b --- /dev/null +++ b/cmd/kubeadm/app/phases/upgrade/postupgrade_v18_19_test.go @@ -0,0 +1,192 @@ +/* +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 upgrade + +import ( + "errors" + "os" + "path/filepath" + "strings" + "testing" + "time" + + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + "k8s.io/kubernetes/cmd/kubeadm/app/constants" + certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs" + "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil" + testutil "k8s.io/kubernetes/cmd/kubeadm/test" + "k8s.io/kubernetes/pkg/util/version" +) + +func TestBackupAPIServerCertAndKey(t *testing.T) { + tmpdir := testutil.SetupTempDir(t) + defer os.RemoveAll(tmpdir) + os.Chmod(tmpdir, 0766) + + certPath := filepath.Join(tmpdir, constants.APIServerCertName) + certFile, err := os.OpenFile(certPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666) + if err != nil { + t.Fatalf("Failed to create cert file %s: %v", certPath, err) + } + defer certFile.Close() + + keyPath := filepath.Join(tmpdir, constants.APIServerKeyName) + keyFile, err := os.OpenFile(keyPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666) + if err != nil { + t.Fatalf("Failed to create key file %s: %v", keyPath, err) + } + defer keyFile.Close() + + if err := backupAPIServerCertAndKey(tmpdir); err != nil { + t.Fatalf("Failed to backup cert and key in dir %s: %v", tmpdir, err) + } +} + +func TestMoveFiles(t *testing.T) { + tmpdir := testutil.SetupTempDir(t) + defer os.RemoveAll(tmpdir) + os.Chmod(tmpdir, 0766) + + certPath := filepath.Join(tmpdir, constants.APIServerCertName) + certFile, err := os.OpenFile(certPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666) + if err != nil { + t.Fatalf("Failed to create cert file %s: %v", certPath, err) + } + defer certFile.Close() + + keyPath := filepath.Join(tmpdir, constants.APIServerKeyName) + keyFile, err := os.OpenFile(keyPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666) + if err != nil { + t.Fatalf("Failed to create key file %s: %v", keyPath, err) + } + defer keyFile.Close() + + subDir := filepath.Join(tmpdir, "expired") + if err := os.Mkdir(subDir, 0766); err != nil { + t.Fatalf("Failed to create backup directory %s: %v", subDir, err) + } + + filesToMove := map[string]string{ + filepath.Join(tmpdir, constants.APIServerCertName): filepath.Join(subDir, constants.APIServerCertName), + filepath.Join(tmpdir, constants.APIServerKeyName): filepath.Join(subDir, constants.APIServerKeyName), + } + + if err := moveFiles(filesToMove); err != nil { + t.Fatalf("Failed to move files %v: %v", filesToMove, err) + } +} + +func TestRollbackFiles(t *testing.T) { + tmpdir := testutil.SetupTempDir(t) + defer os.RemoveAll(tmpdir) + os.Chmod(tmpdir, 0766) + + subDir := filepath.Join(tmpdir, "expired") + if err := os.Mkdir(subDir, 0766); err != nil { + t.Fatalf("Failed to create backup directory %s: %v", subDir, err) + } + + certPath := filepath.Join(subDir, constants.APIServerCertName) + certFile, err := os.OpenFile(certPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666) + if err != nil { + t.Fatalf("Failed to create cert file %s: %v", certPath, err) + } + defer certFile.Close() + + keyPath := filepath.Join(subDir, constants.APIServerKeyName) + keyFile, err := os.OpenFile(keyPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666) + if err != nil { + t.Fatalf("Failed to create key file %s: %v", keyPath, err) + } + defer keyFile.Close() + + filesToRollBack := map[string]string{ + filepath.Join(subDir, constants.APIServerCertName): filepath.Join(tmpdir, constants.APIServerCertName), + filepath.Join(subDir, constants.APIServerKeyName): filepath.Join(tmpdir, constants.APIServerKeyName), + } + + errString := "there are files need roll back" + originalErr := errors.New(errString) + err = rollbackFiles(filesToRollBack, originalErr) + if err == nil { + t.Fatalf("Expected error contains %q, got nil", errString) + } + if !strings.Contains(err.Error(), errString) { + t.Fatalf("Expected error contains %q, got %v", errString, err) + } +} + +func TestShouldBackupAPIServerCertAndKey(t *testing.T) { + cfg := &kubeadmapi.MasterConfiguration{ + API: kubeadmapi.API{AdvertiseAddress: "1.2.3.4"}, + Networking: kubeadmapi.Networking{ServiceSubnet: "10.96.0.0/12", DNSDomain: "cluster.local"}, + NodeName: "test-node", + } + + for desc, test := range map[string]struct { + adjustedExpiry time.Duration + k8sVersion *version.Version + expected bool + }{ + "1.8 version doesn't need to backup": { + k8sVersion: version.MustParseSemantic("v1.8.0"), + expected: false, + }, + "1.9 version with cert not older than 180 days doesn't needs to backup": { + k8sVersion: version.MustParseSemantic("v1.9.0"), + expected: false, + }, + "1.9 version with cert older than 180 days need to backup": { + adjustedExpiry: expiry + 100*time.Hour, + k8sVersion: version.MustParseSemantic("v1.9.0"), + expected: true, + }, + } { + caCert, caKey, err := certsphase.NewCACertAndKey() + if err != nil { + t.Fatalf("failed creation of ca cert and key: %v", err) + } + caCert.NotBefore = caCert.NotBefore.Add(-test.adjustedExpiry).UTC() + apiCert, apiKey, err := certsphase.NewAPIServerCertAndKey(cfg, caCert, caKey) + if err != nil { + t.Fatalf("Test %s: failed creation of cert and key: %v", desc, err) + } + + tmpdir := testutil.SetupTempDir(t) + defer os.RemoveAll(tmpdir) + + if err := pkiutil.WriteCertAndKey(tmpdir, constants.APIServerCertAndKeyBaseName, apiCert, apiKey); err != nil { + t.Fatalf("Test %s: failure while saving %s certificate and key: %v", desc, constants.APIServerCertAndKeyBaseName, err) + } + + certAndKey := []string{filepath.Join(tmpdir, constants.APIServerCertName), filepath.Join(tmpdir, constants.APIServerKeyName)} + for _, path := range certAndKey { + if _, err := os.Stat(path); os.IsNotExist(err) { + t.Fatalf("Test %s: %s not exist: %v", desc, path, err) + } + } + + shouldBackup, err := shouldBackupAPIServerCertAndKey(tmpdir, test.k8sVersion) + if err != nil { + t.Fatalf("Test %s: failed to check shouldBackupAPIServerCertAndKey: %v", desc, err) + } + + if shouldBackup != test.expected { + t.Fatalf("Test %s: shouldBackupAPIServerCertAndKey expected %v, got %v", desc, test.expected, shouldBackup) + } + } +} diff --git a/cmd/kubeadm/app/phases/upgrade/prepull.go b/cmd/kubeadm/app/phases/upgrade/prepull.go index 5d0b2940234..c3ec257a743 100644 --- a/cmd/kubeadm/app/phases/upgrade/prepull.go +++ b/cmd/kubeadm/app/phases/upgrade/prepull.go @@ -20,7 +20,7 @@ import ( "fmt" "time" - apps "k8s.io/api/apps/v1beta2" + apps "k8s.io/api/apps/v1" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" clientset "k8s.io/client-go/kubernetes" @@ -41,7 +41,7 @@ type Prepuller interface { DeleteFunc(string) error } -// DaemonSetPrepuller makes sure the control plane images are availble on all masters +// DaemonSetPrepuller makes sure the control plane images are available on all masters type DaemonSetPrepuller struct { client clientset.Interface cfg *kubeadmapi.MasterConfiguration @@ -99,11 +99,11 @@ func PrepullImagesInParallel(kubePrepuller Prepuller, timeout time.Duration) err } } - // Create a channel for streaming data from goroutines that run in parallell to a blocking for loop that cleans up + // Create a channel for streaming data from goroutines that run in parallel to a blocking for loop that cleans up prePulledChan := make(chan string, len(componentsToPrepull)) for _, component := range componentsToPrepull { go func(c string) { - // Wait as long as needed. This WaitFunc call should be blocking until completetion + // Wait as long as needed. This WaitFunc call should be blocking until completion kubePrepuller.WaitFunc(c) // When the task is done, go ahead and cleanup by sending the name to the channel prePulledChan <- c diff --git a/cmd/kubeadm/app/phases/upgrade/selfhosted.go b/cmd/kubeadm/app/phases/upgrade/selfhosted.go index cef6420a899..385a2a30a47 100644 --- a/cmd/kubeadm/app/phases/upgrade/selfhosted.go +++ b/cmd/kubeadm/app/phases/upgrade/selfhosted.go @@ -20,7 +20,7 @@ import ( "fmt" "time" - apps "k8s.io/api/apps/v1beta2" + apps "k8s.io/api/apps/v1" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" clientset "k8s.io/client-go/kubernetes" @@ -119,7 +119,7 @@ func SelfHostedControlPlane(client clientset.Interface, waiter apiclient.Waiter, // During this upgrade; the temporary/backup component will take over if err := apiclient.TryRunCommand(func() error { - if _, err := client.AppsV1beta2().DaemonSets(newDS.ObjectMeta.Namespace).Update(newDS); err != nil { + if _, err := client.AppsV1().DaemonSets(newDS.ObjectMeta.Namespace).Update(newDS); err != nil { return fmt.Errorf("couldn't update self-hosted component's DaemonSet: %v", err) } return nil @@ -256,7 +256,7 @@ func getCurrentControlPlaneComponentResources(client clientset.Interface) (map[s if err := apiclient.TryRunCommand(func() error { var tryrunerr error // Try to get the current self-hosted component - currentDS, tryrunerr = client.AppsV1beta2().DaemonSets(metav1.NamespaceSystem).Get(dsName, metav1.GetOptions{}) + currentDS, tryrunerr = client.AppsV1().DaemonSets(metav1.NamespaceSystem).Get(dsName, metav1.GetOptions{}) return tryrunerr // note that tryrunerr is most likely nil here (in successful cases) }, selfHostingFailureThreshold); err != nil { return nil, err diff --git a/cmd/kubeadm/app/phases/upgrade/staticpods.go b/cmd/kubeadm/app/phases/upgrade/staticpods.go index f368a66950d..8ea3b2559f5 100644 --- a/cmd/kubeadm/app/phases/upgrade/staticpods.go +++ b/cmd/kubeadm/app/phases/upgrade/staticpods.go @@ -19,11 +19,15 @@ package upgrade import ( "fmt" "os" + "strings" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane" + etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd" + "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" + "k8s.io/kubernetes/pkg/util/version" ) // StaticPodPathManager is responsible for tracking the directories used in the static pod upgrade transition @@ -42,6 +46,8 @@ type StaticPodPathManager interface { BackupManifestPath(component string) string // BackupManifestDir should point to the backup directory used for backuping manifests during the transition BackupManifestDir() string + // BackupEtcdDir should point to the backup directory used for backuping manifests during the transition + BackupEtcdDir() string } // KubeStaticPodPathManager is a real implementation of StaticPodPathManager that is used when upgrading a static pod cluster @@ -49,14 +55,16 @@ type KubeStaticPodPathManager struct { realManifestDir string tempManifestDir string backupManifestDir string + backupEtcdDir string } // NewKubeStaticPodPathManager creates a new instance of KubeStaticPodPathManager -func NewKubeStaticPodPathManager(realDir, tempDir, backupDir string) StaticPodPathManager { +func NewKubeStaticPodPathManager(realDir, tempDir, backupDir, backupEtcdDir string) StaticPodPathManager { return &KubeStaticPodPathManager{ realManifestDir: realDir, tempManifestDir: tempDir, backupManifestDir: backupDir, + backupEtcdDir: backupEtcdDir, } } @@ -70,8 +78,12 @@ func NewKubeStaticPodPathManagerUsingTempDirs(realManifestDir string) (StaticPod if err != nil { return nil, err } + backupEtcdDir, err := constants.CreateTempDirForKubeadm("kubeadm-backup-etcd") + if err != nil { + return nil, err + } - return NewKubeStaticPodPathManager(realManifestDir, upgradedManifestsDir, backupManifestsDir), nil + return NewKubeStaticPodPathManager(realManifestDir, upgradedManifestsDir, backupManifestsDir, backupEtcdDir), nil } // MoveFile should move a file from oldPath to newPath @@ -109,75 +121,213 @@ func (spm *KubeStaticPodPathManager) BackupManifestDir() string { return spm.backupManifestDir } -// StaticPodControlPlane upgrades a static pod-hosted control plane -func StaticPodControlPlane(waiter apiclient.Waiter, pathMgr StaticPodPathManager, cfg *kubeadmapi.MasterConfiguration) error { +// BackupEtcdDir should point to the backup directory used for backuping manifests during the transition +func (spm *KubeStaticPodPathManager) BackupEtcdDir() string { + return spm.backupEtcdDir +} - // This string-string map stores the component name and backup filepath (if a rollback is needed). - // If a rollback is needed, +func upgradeComponent(component string, waiter apiclient.Waiter, pathMgr StaticPodPathManager, cfg *kubeadmapi.MasterConfiguration, beforePodHash string, recoverManifests map[string]string) error { + // Special treatment is required for etcd case, when rollbackOldManifests should roll back etcd + // manifests only for the case when component is Etcd + recoverEtcd := false + if component == constants.Etcd { + recoverEtcd = true + } + // The old manifest is here; in the /etc/kubernetes/manifests/ + currentManifestPath := pathMgr.RealManifestPath(component) + // The new, upgraded manifest will be written here + newManifestPath := pathMgr.TempManifestPath(component) + // The old manifest will be moved here; into a subfolder of the temporary directory + // If a rollback is needed, these manifests will be put back to where they where initially + backupManifestPath := pathMgr.BackupManifestPath(component) + + // Store the backup path in the recover list. If something goes wrong now, this component will be rolled back. + recoverManifests[component] = backupManifestPath + + // Move the old manifest into the old-manifests directory + if err := pathMgr.MoveFile(currentManifestPath, backupManifestPath); err != nil { + return rollbackOldManifests(recoverManifests, err, pathMgr, recoverEtcd) + } + + // Move the new manifest into the manifests directory + if err := pathMgr.MoveFile(newManifestPath, currentManifestPath); err != nil { + return rollbackOldManifests(recoverManifests, err, pathMgr, recoverEtcd) + } + + fmt.Printf("[upgrade/staticpods] Moved new manifest to %q and backed up old manifest to %q\n", currentManifestPath, backupManifestPath) + fmt.Println("[upgrade/staticpods] Waiting for the kubelet to restart the component") + + // Wait for the mirror Pod hash to change; otherwise we'll run into race conditions here when the kubelet hasn't had time to + // notice the removal of the Static Pod, leading to a false positive below where we check that the API endpoint is healthy + // If we don't do this, there is a case where we remove the Static Pod manifest, kubelet is slow to react, kubeadm checks the + // API endpoint below of the OLD Static Pod component and proceeds quickly enough, which might lead to unexpected results. + if err := waiter.WaitForStaticPodControlPlaneHashChange(cfg.NodeName, component, beforePodHash); err != nil { + return rollbackOldManifests(recoverManifests, err, pathMgr, recoverEtcd) + } + + // Wait for the static pod component to come up and register itself as a mirror pod + if err := waiter.WaitForPodsWithLabel("component=" + component); err != nil { + return rollbackOldManifests(recoverManifests, err, pathMgr, recoverEtcd) + } + + fmt.Printf("[upgrade/staticpods] Component %q upgraded successfully!\n", component) + return nil +} + +// performEtcdStaticPodUpgrade performs upgrade of etcd, it returns bool which indicates fatal error or not and the actual error. +func performEtcdStaticPodUpgrade(waiter apiclient.Waiter, pathMgr StaticPodPathManager, cfg *kubeadmapi.MasterConfiguration, recoverManifests map[string]string) (bool, error) { + // Add etcd static pod spec only if external etcd is not configured + if len(cfg.Etcd.Endpoints) != 0 { + return false, fmt.Errorf("external etcd detected, won't try to change any etcd state") + } + // Checking health state of etcd before proceeding with the upgrtade + etcdCluster := util.LocalEtcdCluster{} + etcdStatus, err := etcdCluster.GetEtcdClusterStatus() + if err != nil { + return true, fmt.Errorf("etcd cluster is not healthy: %v", err) + } + + // Backing up etcd data store + backupEtcdDir := pathMgr.BackupEtcdDir() + runningEtcdDir := cfg.Etcd.DataDir + if err := util.CopyDir(runningEtcdDir, backupEtcdDir); err != nil { + return true, fmt.Errorf("fail to back up etcd data: %v", err) + } + + // Need to check currently used version and version from constants, if differs then upgrade + desiredEtcdVersion, err := constants.EtcdSupportedVersion(cfg.KubernetesVersion) + if err != nil { + return true, fmt.Errorf("failed to parse the desired etcd version(%s): %v", desiredEtcdVersion.String(), err) + } + currentEtcdVersion, err := version.ParseSemantic(etcdStatus.Version) + if err != nil { + return true, fmt.Errorf("failed to parse the current etcd version(%s): %v", currentEtcdVersion.String(), err) + } + + // Comparing current etcd version with desired to catch the same version or downgrade condition and fail on them. + if desiredEtcdVersion.LessThan(currentEtcdVersion) { + return false, fmt.Errorf("the desired etcd version for this Kubernetes version %q is %q, but the current etcd version is %q. Won't downgrade etcd, instead just continue", cfg.KubernetesVersion, desiredEtcdVersion.String(), currentEtcdVersion.String()) + } + // For the case when desired etcd version is the same as current etcd version + if strings.Compare(desiredEtcdVersion.String(), currentEtcdVersion.String()) == 0 { + return false, nil + } + + beforeEtcdPodHash, err := waiter.WaitForStaticPodSingleHash(cfg.NodeName, constants.Etcd) + if err != nil { + return true, fmt.Errorf("fail to get etcd pod's hash: %v", err) + } + + // Write the updated etcd static Pod manifest into the temporary directory, at this point no etcd change + // has occured in any aspects. + if err := etcdphase.CreateLocalEtcdStaticPodManifestFile(pathMgr.TempManifestDir(), cfg); err != nil { + return true, fmt.Errorf("error creating local etcd static pod manifest file: %v", err) + } + + // Perform etcd upgrade using common to all control plane components function + if err := upgradeComponent(constants.Etcd, waiter, pathMgr, cfg, beforeEtcdPodHash, recoverManifests); err != nil { + // Since etcd upgrade component failed, the old manifest has been restored + // now we need to check the heatlth of etcd cluster if it came back up with old manifest + if _, err := etcdCluster.GetEtcdClusterStatus(); err != nil { + // At this point we know that etcd cluster is dead and it is safe to copy backup datastore and to rollback old etcd manifest + if err := rollbackEtcdData(cfg, fmt.Errorf("etcd cluster is not healthy after upgrade: %v rolling back", err), pathMgr); err != nil { + // Even copying back datastore failed, no options for recovery left, bailing out + return true, fmt.Errorf("fatal error upgrading local etcd cluster: %v, the backup of etcd database is stored here:(%s)", err, backupEtcdDir) + } + // Old datastore has been copied, rolling back old manifests + if err := rollbackOldManifests(recoverManifests, err, pathMgr, true); err != nil { + // Rolling back to old manifests failed, no options for recovery left, bailing out + return true, fmt.Errorf("fatal error upgrading local etcd cluster: %v, the backup of etcd database is stored here:(%s)", err, backupEtcdDir) + } + // Since rollback of the old etcd manifest was successful, checking again the status of etcd cluster + if _, err := etcdCluster.GetEtcdClusterStatus(); err != nil { + // Nothing else left to try to recover etcd cluster + return true, fmt.Errorf("fatal error upgrading local etcd cluster: %v, the backup of etcd database is stored here:(%s)", err, backupEtcdDir) + } + + return true, fmt.Errorf("fatal error upgrading local etcd cluster: %v, rolled the state back to pre-upgrade state", err) + } + // Since etcd cluster came back up with the old manifest + return true, fmt.Errorf("fatal error when trying to upgrade the etcd cluster: %v, rolled the state back to pre-upgrade state", err) + } + + // Checking health state of etcd after the upgrade + if _, err = etcdCluster.GetEtcdClusterStatus(); err != nil { + // Despite the fact that upgradeComponent was sucessfull, there is something wrong with etcd cluster + // First step is to restore back up of datastore + if err := rollbackEtcdData(cfg, fmt.Errorf("etcd cluster is not healthy after upgrade: %v rolling back", err), pathMgr); err != nil { + // Even copying back datastore failed, no options for recovery left, bailing out + return true, fmt.Errorf("fatal error upgrading local etcd cluster: %v, the backup of etcd database is stored here:(%s)", err, backupEtcdDir) + } + // Old datastore has been copied, rolling back old manifests + if err := rollbackOldManifests(recoverManifests, err, pathMgr, true); err != nil { + // Rolling back to old manifests failed, no options for recovery left, bailing out + return true, fmt.Errorf("fatal error upgrading local etcd cluster: %v, the backup of etcd database is stored here:(%s)", err, backupEtcdDir) + } + // Since rollback of the old etcd manifest was successful, checking again the status of etcd cluster + if _, err := etcdCluster.GetEtcdClusterStatus(); err != nil { + // Nothing else left to try to recover etcd cluster + return true, fmt.Errorf("fatal error upgrading local etcd cluster: %v, the backup of etcd database is stored here:(%s)", err, backupEtcdDir) + } + + return true, fmt.Errorf("fatal error upgrading local etcd cluster: %v, rolled the state back to pre-upgrade state", err) + } + + return false, nil +} + +// StaticPodControlPlane upgrades a static pod-hosted control plane +func StaticPodControlPlane(waiter apiclient.Waiter, pathMgr StaticPodPathManager, cfg *kubeadmapi.MasterConfiguration, etcdUpgrade bool) error { recoverManifests := map[string]string{} + // etcd upgrade is done prior to other control plane components + if etcdUpgrade { + // Perform etcd upgrade using common to all control plane components function + fatal, err := performEtcdStaticPodUpgrade(waiter, pathMgr, cfg, recoverManifests) + if err != nil { + if fatal { + return err + } + fmt.Printf("[upgrade/etcd] non fatal issue encountered during upgrade: %v\n", err) + } + } + beforePodHashMap, err := waiter.WaitForStaticPodControlPlaneHashes(cfg.NodeName) if err != nil { return err } // Write the updated static Pod manifests into the temporary directory - fmt.Printf("[upgrade/staticpods] Writing upgraded Static Pod manifests to %q\n", pathMgr.TempManifestDir()) + fmt.Printf("[upgrade/staticpods] Writing new Static Pod manifests to %q\n", pathMgr.TempManifestDir()) err = controlplane.CreateInitStaticPodManifestFiles(pathMgr.TempManifestDir(), cfg) + if err != nil { + return fmt.Errorf("error creating init static pod manifest files: %v", err) + } for _, component := range constants.MasterComponents { - // The old manifest is here; in the /etc/kubernetes/manifests/ - currentManifestPath := pathMgr.RealManifestPath(component) - // The new, upgraded manifest will be written here - newManifestPath := pathMgr.TempManifestPath(component) - // The old manifest will be moved here; into a subfolder of the temporary directory - // If a rollback is needed, these manifests will be put back to where they where initially - backupManifestPath := pathMgr.BackupManifestPath(component) - - // Store the backup path in the recover list. If something goes wrong now, this component will be rolled back. - recoverManifests[component] = backupManifestPath - - // Move the old manifest into the old-manifests directory - if err := pathMgr.MoveFile(currentManifestPath, backupManifestPath); err != nil { - return rollbackOldManifests(recoverManifests, err, pathMgr) + if err = upgradeComponent(component, waiter, pathMgr, cfg, beforePodHashMap[component], recoverManifests); err != nil { + return err } - - // Move the new manifest into the manifests directory - if err := pathMgr.MoveFile(newManifestPath, currentManifestPath); err != nil { - return rollbackOldManifests(recoverManifests, err, pathMgr) - } - - fmt.Printf("[upgrade/staticpods] Moved upgraded manifest to %q and backed up old manifest to %q\n", currentManifestPath, backupManifestPath) - fmt.Println("[upgrade/staticpods] Waiting for the kubelet to restart the component") - - // Wait for the mirror Pod hash to change; otherwise we'll run into race conditions here when the kubelet hasn't had time to - // notice the removal of the Static Pod, leading to a false positive below where we check that the API endpoint is healthy - // If we don't do this, there is a case where we remove the Static Pod manifest, kubelet is slow to react, kubeadm checks the - // API endpoint below of the OLD Static Pod component and proceeds quickly enough, which might lead to unexpected results. - if err := waiter.WaitForStaticPodControlPlaneHashChange(cfg.NodeName, component, beforePodHashMap[component]); err != nil { - return rollbackOldManifests(recoverManifests, err, pathMgr) - } - - // Wait for the static pod component to come up and register itself as a mirror pod - if err := waiter.WaitForPodsWithLabel("component=" + component); err != nil { - return rollbackOldManifests(recoverManifests, err, pathMgr) - } - - fmt.Printf("[upgrade/staticpods] Component %q upgraded successfully!\n", component) } + // Remove the temporary directories used on a best-effort (don't fail if the calls error out) // The calls are set here by design; we should _not_ use "defer" above as that would remove the directories // even in the "fail and rollback" case, where we want the directories preserved for the user. os.RemoveAll(pathMgr.TempManifestDir()) os.RemoveAll(pathMgr.BackupManifestDir()) + os.RemoveAll(pathMgr.BackupEtcdDir()) return nil } // rollbackOldManifests rolls back the backuped manifests if something went wrong -func rollbackOldManifests(oldManifests map[string]string, origErr error, pathMgr StaticPodPathManager) error { +func rollbackOldManifests(oldManifests map[string]string, origErr error, pathMgr StaticPodPathManager, restoreEtcd bool) error { errs := []error{origErr} for component, backupPath := range oldManifests { + // Will restore etcd manifest only if it was explicitely requested by setting restoreEtcd to True + if component == constants.Etcd && !restoreEtcd { + continue + } // Where we should put back the backed up manifest realManifestPath := pathMgr.RealManifestPath(component) @@ -187,6 +337,21 @@ func rollbackOldManifests(oldManifests map[string]string, origErr error, pathMgr errs = append(errs, err) } } - // Let the user know there we're problems, but we tried to reçover + // Let the user know there were problems, but we tried to recover return fmt.Errorf("couldn't upgrade control plane. kubeadm has tried to recover everything into the earlier state. Errors faced: %v", errs) } + +// rollbackEtcdData rolls back the the content of etcd folder if something went wrong +func rollbackEtcdData(cfg *kubeadmapi.MasterConfiguration, origErr error, pathMgr StaticPodPathManager) error { + errs := []error{origErr} + backupEtcdDir := pathMgr.BackupEtcdDir() + runningEtcdDir := cfg.Etcd.DataDir + err := util.CopyDir(backupEtcdDir, runningEtcdDir) + + if err != nil { + errs = append(errs, err) + } + + // Let the user know there we're problems, but we tried to reçover + return fmt.Errorf("couldn't recover etcd database with error: %v, the location of etcd backup: %s ", errs, backupEtcdDir) +} diff --git a/cmd/kubeadm/app/phases/upgrade/staticpods_test.go b/cmd/kubeadm/app/phases/upgrade/staticpods_test.go index accde5510db..e51a1295792 100644 --- a/cmd/kubeadm/app/phases/upgrade/staticpods_test.go +++ b/cmd/kubeadm/app/phases/upgrade/staticpods_test.go @@ -30,8 +30,9 @@ import ( kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane" + etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" ) const ( @@ -108,6 +109,11 @@ func (w *fakeWaiter) WaitForStaticPodControlPlaneHashes(_ string) (map[string]st return map[string]string{}, w.errsToReturn[waitForHashes] } +// WaitForStaticPodSingleHash returns an error if set from errsToReturn +func (w *fakeWaiter) WaitForStaticPodSingleHash(_ string, _ string) (string, error) { + return "", w.errsToReturn[waitForHashes] +} + // WaitForStaticPodControlPlaneHashChange returns an error if set from errsToReturn func (w *fakeWaiter) WaitForStaticPodControlPlaneHashChange(_, _, _ string) error { return w.errsToReturn[waitForHashChange] @@ -122,6 +128,7 @@ type fakeStaticPodPathManager struct { realManifestDir string tempManifestDir string backupManifestDir string + backupEtcdDir string MoveFileFunc func(string, string) error } @@ -140,11 +147,16 @@ func NewFakeStaticPodPathManager(moveFileFunc func(string, string) error) (Stati if err != nil { return nil, fmt.Errorf("couldn't create a temporary directory for the upgrade: %v", err) } + backupEtcdDir, err := ioutil.TempDir("", "kubeadm-backup-etcd") + if err != nil { + return nil, err + } return &fakeStaticPodPathManager{ realManifestDir: realManifestsDir, tempManifestDir: upgradedManifestsDir, backupManifestDir: backupManifestsDir, + backupEtcdDir: backupEtcdDir, MoveFileFunc: moveFileFunc, }, nil } @@ -174,6 +186,10 @@ func (spm *fakeStaticPodPathManager) BackupManifestDir() string { return spm.backupManifestDir } +func (spm *fakeStaticPodPathManager) BackupEtcdDir() string { + return spm.backupEtcdDir +} + func TestStaticPodControlPlane(t *testing.T) { tests := []struct { waitErrsToReturn map[string]error @@ -280,7 +296,6 @@ func TestStaticPodControlPlane(t *testing.T) { } for _, rt := range tests { - waiter := NewFakeStaticPodWaiter(rt.waitErrsToReturn) pathMgr, err := NewFakeStaticPodPathManager(rt.moveFileFunc) if err != nil { @@ -299,6 +314,10 @@ func TestStaticPodControlPlane(t *testing.T) { if err != nil { t.Fatalf("couldn't run CreateInitStaticPodManifestFiles: %v", err) } + err = etcdphase.CreateLocalEtcdStaticPodManifestFile(pathMgr.RealManifestDir(), oldcfg) + if err != nil { + t.Fatalf("couldn't run CreateLocalEtcdStaticPodManifestFile: %v", err) + } // Get a hash of the v1.7 API server manifest to compare later (was the file re-written) oldHash, err := getAPIServerHash(pathMgr.RealManifestDir()) if err != nil { @@ -310,7 +329,7 @@ func TestStaticPodControlPlane(t *testing.T) { t.Fatalf("couldn't create config: %v", err) } - actualErr := StaticPodControlPlane(waiter, pathMgr, newcfg) + actualErr := StaticPodControlPlane(waiter, pathMgr, newcfg, false) if (actualErr != nil) != rt.expectedErr { t.Errorf( "failed UpgradeStaticPodControlPlane\n\texpected error: %t\n\tgot: %t", @@ -349,9 +368,9 @@ func getAPIServerHash(dir string) (string, error) { func getConfig(version string) (*kubeadmapi.MasterConfiguration, error) { externalcfg := &kubeadmapiext.MasterConfiguration{} internalcfg := &kubeadmapi.MasterConfiguration{} - if err := runtime.DecodeInto(api.Codecs.UniversalDecoder(), []byte(fmt.Sprintf(testConfiguration, version)), externalcfg); err != nil { + if err := runtime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), []byte(fmt.Sprintf(testConfiguration, version)), externalcfg); err != nil { return nil, fmt.Errorf("unable to decode config: %v", err) } - api.Scheme.Convert(externalcfg, internalcfg, nil) + legacyscheme.Scheme.Convert(externalcfg, internalcfg, nil) return internalcfg, nil } diff --git a/cmd/kubeadm/app/phases/upgrade/versiongetter.go b/cmd/kubeadm/app/phases/upgrade/versiongetter.go index e0289176c50..13c65294e50 100644 --- a/cmd/kubeadm/app/phases/upgrade/versiongetter.go +++ b/cmd/kubeadm/app/phases/upgrade/versiongetter.go @@ -86,7 +86,7 @@ func (g *KubeVersionGetter) KubeadmVersion() (string, *versionutil.Version, erro func (g *KubeVersionGetter) VersionFromCILabel(ciVersionLabel, description string) (string, *versionutil.Version, error) { versionStr, err := kubeadmutil.KubernetesReleaseVersion(ciVersionLabel) if err != nil { - return "", nil, fmt.Errorf("Couldn't fetch latest %s version from the internet: %v", description, err) + return "", nil, fmt.Errorf("Couldn't fetch latest %s from the internet: %v", description, err) } if description != "" { @@ -95,7 +95,7 @@ func (g *KubeVersionGetter) VersionFromCILabel(ciVersionLabel, description strin ver, err := versionutil.ParseSemantic(versionStr) if err != nil { - return "", nil, fmt.Errorf("Couldn't parse latest %s version: %v", description, err) + return "", nil, fmt.Errorf("Couldn't parse latest %s: %v", description, err) } return versionStr, ver, nil } diff --git a/cmd/kubeadm/app/phases/uploadconfig/BUILD b/cmd/kubeadm/app/phases/uploadconfig/BUILD index bdb27d4528b..880fd4b4c37 100644 --- a/cmd/kubeadm/app/phases/uploadconfig/BUILD +++ b/cmd/kubeadm/app/phases/uploadconfig/BUILD @@ -15,7 +15,7 @@ go_library( "//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/util/apiclient:go_default_library", - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//vendor/github.com/ghodss/yaml:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -39,13 +39,13 @@ filegroup( go_test( name = "go_default_test", srcs = ["uploadconfig_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig", - library = ":go_default_library", deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/cmd/kubeadm/app/phases/uploadconfig/uploadconfig.go b/cmd/kubeadm/app/phases/uploadconfig/uploadconfig.go index 6208d568b35..42ed20d471c 100644 --- a/cmd/kubeadm/app/phases/uploadconfig/uploadconfig.go +++ b/cmd/kubeadm/app/phases/uploadconfig/uploadconfig.go @@ -28,7 +28,7 @@ import ( kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" ) // UploadConfiguration saves the MasterConfiguration used for later reference (when upgrading for instance) @@ -38,7 +38,7 @@ func UploadConfiguration(cfg *kubeadmapi.MasterConfiguration, client clientset.I // Convert cfg to the external version as that's the only version of the API that can be deserialized later externalcfg := &kubeadmapiext.MasterConfiguration{} - api.Scheme.Convert(cfg, externalcfg, nil) + legacyscheme.Scheme.Convert(cfg, externalcfg, nil) // Removes sensitive info from the data that will be stored in the config map externalcfg.Token = "" diff --git a/cmd/kubeadm/app/phases/uploadconfig/uploadconfig_test.go b/cmd/kubeadm/app/phases/uploadconfig/uploadconfig_test.go index dfc834ed545..69b6fd783b2 100644 --- a/cmd/kubeadm/app/phases/uploadconfig/uploadconfig_test.go +++ b/cmd/kubeadm/app/phases/uploadconfig/uploadconfig_test.go @@ -27,7 +27,7 @@ import ( kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" ) func TestUploadConfiguration(t *testing.T) { @@ -99,12 +99,12 @@ func TestUploadConfiguration(t *testing.T) { decodedExtCfg := &kubeadmapiext.MasterConfiguration{} decodedCfg := &kubeadmapi.MasterConfiguration{} - if err := runtime.DecodeInto(api.Codecs.UniversalDecoder(), []byte(configData), decodedExtCfg); err != nil { + if err := runtime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), []byte(configData), decodedExtCfg); err != nil { t.Errorf("unable to decode config from bytes: %v", err) } // Default and convert to the internal version - api.Scheme.Default(decodedExtCfg) - api.Scheme.Convert(decodedExtCfg, decodedCfg, nil) + legacyscheme.Scheme.Default(decodedExtCfg) + legacyscheme.Scheme.Convert(decodedExtCfg, decodedCfg, nil) if decodedCfg.KubernetesVersion != cfg.KubernetesVersion { t.Errorf("Decoded value doesn't match, decoded = %#v, expected = %#v", decodedCfg.KubernetesVersion, cfg.KubernetesVersion) diff --git a/cmd/kubeadm/app/preflight/BUILD b/cmd/kubeadm/app/preflight/BUILD index 84c515835e8..23f8484317a 100644 --- a/cmd/kubeadm/app/preflight/BUILD +++ b/cmd/kubeadm/app/preflight/BUILD @@ -11,23 +11,62 @@ go_library( srcs = [ "checks.go", "utils.go", - ], + ] + select({ + "@io_bazel_rules_go//go/platform:android": [ + "checks_unix.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "checks_unix.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "checks_unix.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "checks_unix.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "checks_unix.go", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "checks_unix.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "checks_unix.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "checks_unix.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "checks_unix.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "checks_unix.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "checks_windows.go", + ], + "//conditions:default": [], + }), importpath = "k8s.io/kubernetes/cmd/kubeadm/app/preflight", deps = [ "//cmd/kube-apiserver/app/options:go_default_library", "//cmd/kube-controller-manager/app/options:go_default_library", + "//cmd/kube-scheduler/app:go_default_library", "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//pkg/kubeapiserver/authorizer/modes:go_default_library", + "//pkg/registry/core/service/ipallocator:go_default_library", "//pkg/util/initsystem:go_default_library", "//pkg/util/version:go_default_library", "//pkg/version:go_default_library", - "//plugin/cmd/kube-scheduler/app/options:go_default_library", "//test/e2e_node/system:go_default_library", "//vendor/github.com/PuerkitoBio/purell:go_default_library", "//vendor/github.com/blang/semver:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/utils/exec:go_default_library", ], ) @@ -37,11 +76,13 @@ go_test( "checks_test.go", "utils_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/preflight", - library = ":go_default_library", deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//vendor/github.com/renstrom/dedent:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/utils/exec:go_default_library", ], ) diff --git a/cmd/kubeadm/app/preflight/checks.go b/cmd/kubeadm/app/preflight/checks.go index 0925e11328f..48462b80679 100644 --- a/cmd/kubeadm/app/preflight/checks.go +++ b/cmd/kubeadm/app/preflight/checks.go @@ -26,8 +26,8 @@ import ( "net" "net/http" "os" - "os/exec" "path/filepath" + "runtime" "strings" "time" @@ -40,21 +40,26 @@ import ( "net/url" + netutil "k8s.io/apimachinery/pkg/util/net" + "k8s.io/apimachinery/pkg/util/sets" apiservoptions "k8s.io/kubernetes/cmd/kube-apiserver/app/options" cmoptions "k8s.io/kubernetes/cmd/kube-controller-manager/app/options" + schedulerapp "k8s.io/kubernetes/cmd/kube-scheduler/app" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" - "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/apis/core/validation" authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes" + "k8s.io/kubernetes/pkg/registry/core/service/ipallocator" "k8s.io/kubernetes/pkg/util/initsystem" versionutil "k8s.io/kubernetes/pkg/util/version" kubeadmversion "k8s.io/kubernetes/pkg/version" - schoptions "k8s.io/kubernetes/plugin/cmd/kube-scheduler/app/options" "k8s.io/kubernetes/test/e2e_node/system" + utilsexec "k8s.io/utils/exec" ) const ( bridgenf = "/proc/sys/net/bridge/bridge-nf-call-iptables" + bridgenf6 = "/proc/sys/net/bridge/bridge-nf-call-ip6tables" externalEtcdRequestTimeout = time.Duration(10 * time.Second) externalEtcdRequestRetries = 3 externalEtcdRequestInterval = time.Duration(5 * time.Second) @@ -70,13 +75,34 @@ type Error struct { } func (e *Error) Error() string { - return fmt.Sprintf("[preflight] Some fatal errors occurred:\n%s%s", e.Msg, "[preflight] If you know what you are doing, you can skip pre-flight checks with `--skip-preflight-checks`") + return fmt.Sprintf("[preflight] Some fatal errors occurred:\n%s%s", e.Msg, "[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`") } // Checker validates the state of the system to ensure kubeadm will be -// successful as often as possilble. +// successful as often as possible. type Checker interface { Check() (warnings, errors []error) + Name() string +} + +// CRICheck verifies the container runtime through the CRI. +type CRICheck struct { + socket string + exec utilsexec.Interface +} + +// Name returns label for CRICheck. +func (CRICheck) Name() string { + return "CRI" +} + +// Check validates the container runtime through the CRI. +func (criCheck CRICheck) Check() (warnings, errors []error) { + if err := criCheck.exec.Command("sh", "-c", fmt.Sprintf("crictl -r %s info", criCheck.socket)).Run(); err != nil { + errors = append(errors, fmt.Errorf("unable to check if the container runtime at %q is running: %s", criCheck.socket, err)) + return warnings, errors + } + return warnings, errors } // ServiceCheck verifies that the given service is enabled and active. If we do not @@ -85,6 +111,15 @@ type Checker interface { type ServiceCheck struct { Service string CheckIfActive bool + Label string +} + +// Name returns label for ServiceCheck. If not provided, will return based on the service parameter +func (sc ServiceCheck) Name() string { + if sc.Label != "" { + return sc.Label + } + return fmt.Sprintf("Service-%s", strings.Title(sc.Service)) } // Check validates if the service is enabled and active. @@ -122,6 +157,11 @@ type FirewalldCheck struct { ports []int } +// Name returns label for FirewalldCheck. +func (FirewalldCheck) Name() string { + return "Firewalld" +} + // Check validates if the firewall is enabled and active. func (fc FirewalldCheck) Check() (warnings, errors []error) { initSystem, err := initsystem.GetInitSystem() @@ -146,7 +186,16 @@ func (fc FirewalldCheck) Check() (warnings, errors []error) { // PortOpenCheck ensures the given port is available for use. type PortOpenCheck struct { - port int + port int + label string +} + +// Name returns name for PortOpenCheck. If not known, will return "PortXXXX" based on port number +func (poc PortOpenCheck) Name() string { + if poc.label != "" { + return poc.label + } + return fmt.Sprintf("Port-%d", poc.port) } // Check validates if the particular port is available. @@ -163,22 +212,26 @@ func (poc PortOpenCheck) Check() (warnings, errors []error) { return nil, errors } -// IsRootCheck verifies user is root -type IsRootCheck struct{} +// IsPrivilegedUserCheck verifies user is privileged (linux - root, windows - Administrator) +type IsPrivilegedUserCheck struct{} -// Check validates if an user has root privileges. -func (irc IsRootCheck) Check() (warnings, errors []error) { - errors = []error{} - if os.Getuid() != 0 { - errors = append(errors, fmt.Errorf("user is not running as root")) - } - - return nil, errors +// Name returns name for IsPrivilegedUserCheck +func (IsPrivilegedUserCheck) Name() string { + return "IsPrivilegedUser" } // DirAvailableCheck checks if the given directory either does not exist, or is empty. type DirAvailableCheck struct { - Path string + Path string + Label string +} + +// Name returns label for individual DirAvailableChecks. If not known, will return based on path. +func (dac DirAvailableCheck) Name() string { + if dac.Label != "" { + return dac.Label + } + return fmt.Sprintf("DirAvailable-%s", strings.Replace(dac.Path, "/", "-", -1)) } // Check validates if a directory does not exist or empty. @@ -206,7 +259,16 @@ func (dac DirAvailableCheck) Check() (warnings, errors []error) { // FileAvailableCheck checks that the given file does not already exist. type FileAvailableCheck struct { - Path string + Path string + Label string +} + +// Name returns label for individual FileAvailableChecks. If not known, will return based on path. +func (fac FileAvailableCheck) Name() string { + if fac.Label != "" { + return fac.Label + } + return fmt.Sprintf("FileAvailable-%s", strings.Replace(fac.Path, "/", "-", -1)) } // Check validates if the given file does not already exist. @@ -220,7 +282,16 @@ func (fac FileAvailableCheck) Check() (warnings, errors []error) { // FileExistingCheck checks that the given file does not already exist. type FileExistingCheck struct { - Path string + Path string + Label string +} + +// Name returns label for individual FileExistingChecks. If not known, will return based on path. +func (fac FileExistingCheck) Name() string { + if fac.Label != "" { + return fac.Label + } + return fmt.Sprintf("FileExisting-%s", strings.Replace(fac.Path, "/", "-", -1)) } // Check validates if the given file already exists. @@ -236,6 +307,15 @@ func (fac FileExistingCheck) Check() (warnings, errors []error) { type FileContentCheck struct { Path string Content []byte + Label string +} + +// Name returns label for individual FileContentChecks. If not known, will return based on path. +func (fcc FileContentCheck) Name() string { + if fcc.Label != "" { + return fcc.Label + } + return fmt.Sprintf("FileContent-%s", strings.Replace(fcc.Path, "/", "-", -1)) } // Check validates if the given file contains the given content. @@ -265,11 +345,21 @@ func (fcc FileContentCheck) Check() (warnings, errors []error) { type InPathCheck struct { executable string mandatory bool + exec utilsexec.Interface + label string +} + +// Name returns label for individual InPathCheck. If not known, will return based on path. +func (ipc InPathCheck) Name() string { + if ipc.label != "" { + return ipc.label + } + return fmt.Sprintf("FileExisting-%s", strings.Replace(ipc.executable, "/", "-", -1)) } // Check validates if the given executable is present in the path. func (ipc InPathCheck) Check() (warnings, errors []error) { - _, err := exec.LookPath(ipc.executable) + _, err := ipc.exec.LookPath(ipc.executable) if err != nil { if ipc.mandatory { // Return as an error: @@ -287,6 +377,11 @@ type HostnameCheck struct { nodeName string } +// Name will return Hostname as name for HostnameCheck +func (HostnameCheck) Name() string { + return "Hostname" +} + // Check validates if hostname match dns sub domain regex. func (hc HostnameCheck) Check() (warnings, errors []error) { errors = []error{} @@ -312,6 +407,11 @@ type HTTPProxyCheck struct { Port int } +// Name returns HTTPProxy as name for HTTPProxyCheck +func (hst HTTPProxyCheck) Name() string { + return "HTTPProxy" +} + // Check validates http connectivity type, direct or via proxy. func (hst HTTPProxyCheck) Check() (warnings, errors []error) { @@ -332,6 +432,61 @@ func (hst HTTPProxyCheck) Check() (warnings, errors []error) { return nil, nil } +// HTTPProxyCIDRCheck checks if https connection to specific subnet is going +// to be done directly or over proxy. If proxy detected, it will return warning. +// Similar to HTTPProxyCheck above, but operates with subnets and uses API +// machinery transport defaults to simulate kube-apiserver accessing cluster +// services and pods. +type HTTPProxyCIDRCheck struct { + Proto string + CIDR string +} + +// Name will return HTTPProxyCIDR as name for HTTPProxyCIDRCheck +func (HTTPProxyCIDRCheck) Name() string { + return "HTTPProxyCIDR" +} + +// Check validates http connectivity to first IP address in the CIDR. +// If it is not directly connected and goes via proxy it will produce warning. +func (subnet HTTPProxyCIDRCheck) Check() (warnings, errors []error) { + + if len(subnet.CIDR) == 0 { + return nil, nil + } + + _, cidr, err := net.ParseCIDR(subnet.CIDR) + if err != nil { + return nil, []error{fmt.Errorf("error parsing CIDR %q: %v", subnet.CIDR, err)} + } + + testIP, err := ipallocator.GetIndexedIP(cidr, 1) + if err != nil { + return nil, []error{fmt.Errorf("unable to get first IP address from the given CIDR (%s): %v", cidr.String(), err)} + } + + testIPstring := testIP.String() + if len(testIP) == net.IPv6len { + testIPstring = fmt.Sprintf("[%s]:1234", testIP) + } + url := fmt.Sprintf("%s://%s/", subnet.Proto, testIPstring) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, []error{err} + } + + // Utilize same transport defaults as it will be used by API server + proxy, err := netutil.SetOldTransportDefaults(&http.Transport{}).Proxy(req) + if err != nil { + return nil, []error{err} + } + if proxy != nil { + return []error{fmt.Errorf("connection to %q uses proxy %q. This may lead to malfunctional cluster setup. Make sure that Pod and Services IP ranges specified correctly as exceptions in proxy configuration", subnet.CIDR, proxy)}, nil + } + return nil, nil +} + // ExtraArgsCheck checks if arguments are valid. type ExtraArgsCheck struct { APIServerExtraArgs map[string]string @@ -339,6 +494,11 @@ type ExtraArgsCheck struct { SchedulerExtraArgs map[string]string } +// Name will return ExtraArgs as name for ExtraArgsCheck +func (ExtraArgsCheck) Name() string { + return "ExtraArgs" +} + // Check validates additional arguments of the control plane components. func (eac ExtraArgsCheck) Check() (warnings, errors []error) { argsCheck := func(name string, args map[string]string, f *pflag.FlagSet) []error { @@ -365,16 +525,26 @@ func (eac ExtraArgsCheck) Check() (warnings, errors []error) { warnings = append(warnings, argsCheck("kube-controller-manager", eac.ControllerManagerExtraArgs, flags)...) } if len(eac.SchedulerExtraArgs) > 0 { + opts, err := schedulerapp.NewOptions() + if err != nil { + warnings = append(warnings, err) + } flags := pflag.NewFlagSet("", pflag.ContinueOnError) - s := schoptions.NewSchedulerServer() - s.AddFlags(flags) + opts.AddFlags(flags) warnings = append(warnings, argsCheck("kube-scheduler", eac.SchedulerExtraArgs, flags)...) } return warnings, nil } // SystemVerificationCheck defines struct used for for running the system verification node check in test/e2e_node/system -type SystemVerificationCheck struct{} +type SystemVerificationCheck struct { + CRISocket string +} + +// Name will return SystemVerification as name for SystemVerificationCheck +func (SystemVerificationCheck) Name() string { + return "SystemVerification" +} // Check runs all individual checks func (sysver SystemVerificationCheck) Check() (warnings, errors []error) { @@ -385,12 +555,21 @@ func (sysver SystemVerificationCheck) Check() (warnings, errors []error) { var errs []error var warns []error - // All the validators we'd like to run: + // All the common validators we'd like to run: var validators = []system.Validator{ - &system.OSValidator{Reporter: reporter}, - &system.KernelValidator{Reporter: reporter}, - &system.CgroupsValidator{Reporter: reporter}, - &system.DockerValidator{Reporter: reporter}, + &system.KernelValidator{Reporter: reporter}} + + // run the docker validator only with dockershim + if sysver.CRISocket == "/var/run/dockershim.sock" { + // https://github.com/kubernetes/kubeadm/issues/533 + validators = append(validators, &system.DockerValidator{Reporter: reporter}) + } + + if runtime.GOOS == "linux" { + //add linux validators + validators = append(validators, + &system.OSValidator{Reporter: reporter}, + &system.CgroupsValidator{Reporter: reporter}) } // Run all validators @@ -419,6 +598,11 @@ type KubernetesVersionCheck struct { KubernetesVersion string } +// Name will return KubernetesVersion as name for KubernetesVersionCheck +func (KubernetesVersionCheck) Name() string { + return "KubernetesVersion" +} + // Check validates kubernetes and kubeadm versions func (kubever KubernetesVersionCheck) Check() (warnings, errors []error) { @@ -450,7 +634,14 @@ func (kubever KubernetesVersionCheck) Check() (warnings, errors []error) { } // KubeletVersionCheck validates installed kubelet version -type KubeletVersionCheck struct{} +type KubeletVersionCheck struct { + KubernetesVersion string +} + +// Name will return KubeletVersion as name for KubeletVersionCheck +func (KubeletVersionCheck) Name() string { + return "KubeletVersion" +} // Check validates kubelet version. It should be not less than minimal supported version func (kubever KubeletVersionCheck) Check() (warnings, errors []error) { @@ -461,12 +652,27 @@ func (kubever KubeletVersionCheck) Check() (warnings, errors []error) { if kubeletVersion.LessThan(kubeadmconstants.MinimumKubeletVersion) { return nil, []error{fmt.Errorf("Kubelet version %q is lower than kubadm can support. Please upgrade kubelet", kubeletVersion)} } - return nil, []error{} + + if kubever.KubernetesVersion != "" { + k8sVersion, err := versionutil.ParseSemantic(kubever.KubernetesVersion) + if err != nil { + return nil, []error{fmt.Errorf("couldn't parse kubernetes version %q: %v", kubever.KubernetesVersion, err)} + } + if kubeletVersion.Major() > k8sVersion.Major() || kubeletVersion.Minor() > k8sVersion.Minor() { + return nil, []error{fmt.Errorf("the kubelet version is higher than the control plane version. This is not a supported version skew and may lead to a malfunctional cluster. Kubelet version: %q Control plane version: %q", kubeletVersion, k8sVersion)} + } + } + return nil, nil } // SwapCheck warns if swap is enabled type SwapCheck struct{} +// Name will return Swap as name for SwapCheck +func (SwapCheck) Name() string { + return "Swap" +} + // Check validates whether swap is enabled or not func (swc SwapCheck) Check() (warnings, errors []error) { f, err := os.Open("/proc/swaps") @@ -485,7 +691,7 @@ func (swc SwapCheck) Check() (warnings, errors []error) { } if len(buf) > 1 { - return []error{fmt.Errorf("running with swap on is not supported. Please disable swap or set kubelet's --fail-swap-on flag to false")}, nil + return nil, []error{fmt.Errorf("running with swap on is not supported. Please disable swap")} } return nil, nil @@ -501,6 +707,11 @@ type ExternalEtcdVersionCheck struct { Etcd kubeadmapi.Etcd } +// Name will return ExternalEtcdVersion as name for ExternalEtcdVersionCheck +func (ExternalEtcdVersionCheck) Name() string { + return "ExternalEtcdVersion" +} + // Check validates external etcd version func (evc ExternalEtcdVersionCheck) Check() (warnings, errors []error) { var config *tls.Config @@ -629,43 +840,62 @@ func getEtcdVersionResponse(client *http.Client, url string, target interface{}) } // RunInitMasterChecks executes all individual, applicable to Master node checks. -func RunInitMasterChecks(cfg *kubeadmapi.MasterConfiguration) error { +func RunInitMasterChecks(execer utilsexec.Interface, cfg *kubeadmapi.MasterConfiguration, criSocket string, ignorePreflightErrors sets.String) error { // First, check if we're root separately from the other preflight checks and fail fast - if err := RunRootCheckOnly(); err != nil { + if err := RunRootCheckOnly(ignorePreflightErrors); err != nil { return err } + // check if we can use crictl to perform checks via the CRI + criCtlChecker := InPathCheck{executable: "crictl", mandatory: false, exec: execer} + warns, _ := criCtlChecker.Check() + useCRI := len(warns) == 0 + + manifestsDir := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ManifestsSubDirName) + checks := []Checker{ KubernetesVersionCheck{KubernetesVersion: cfg.KubernetesVersion, KubeadmVersion: kubeadmversion.Get().GitVersion}, - SystemVerificationCheck{}, - IsRootCheck{}, + SystemVerificationCheck{CRISocket: criSocket}, + IsPrivilegedUserCheck{}, HostnameCheck{nodeName: cfg.NodeName}, - KubeletVersionCheck{}, + KubeletVersionCheck{KubernetesVersion: cfg.KubernetesVersion}, ServiceCheck{Service: "kubelet", CheckIfActive: false}, - ServiceCheck{Service: "docker", CheckIfActive: true}, FirewalldCheck{ports: []int{int(cfg.API.BindPort), 10250}}, PortOpenCheck{port: int(cfg.API.BindPort)}, PortOpenCheck{port: 10250}, PortOpenCheck{port: 10251}, PortOpenCheck{port: 10252}, - HTTPProxyCheck{Proto: "https", Host: cfg.API.AdvertiseAddress, Port: int(cfg.API.BindPort)}, - DirAvailableCheck{Path: filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ManifestsSubDirName)}, + FileAvailableCheck{Path: kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.KubeAPIServer, manifestsDir)}, + FileAvailableCheck{Path: kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.KubeControllerManager, manifestsDir)}, + FileAvailableCheck{Path: kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.KubeScheduler, manifestsDir)}, + FileAvailableCheck{Path: kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.Etcd, manifestsDir)}, FileContentCheck{Path: bridgenf, Content: []byte{'1'}}, SwapCheck{}, - InPathCheck{executable: "ip", mandatory: true}, - InPathCheck{executable: "iptables", mandatory: true}, - InPathCheck{executable: "mount", mandatory: true}, - InPathCheck{executable: "nsenter", mandatory: true}, - InPathCheck{executable: "ebtables", mandatory: false}, - InPathCheck{executable: "ethtool", mandatory: false}, - InPathCheck{executable: "socat", mandatory: false}, - InPathCheck{executable: "tc", mandatory: false}, - InPathCheck{executable: "touch", mandatory: false}, + InPathCheck{executable: "ip", mandatory: true, exec: execer}, + InPathCheck{executable: "iptables", mandatory: true, exec: execer}, + InPathCheck{executable: "mount", mandatory: true, exec: execer}, + InPathCheck{executable: "nsenter", mandatory: true, exec: execer}, + InPathCheck{executable: "ebtables", mandatory: false, exec: execer}, + InPathCheck{executable: "ethtool", mandatory: false, exec: execer}, + InPathCheck{executable: "socat", mandatory: false, exec: execer}, + InPathCheck{executable: "tc", mandatory: false, exec: execer}, + InPathCheck{executable: "touch", mandatory: false, exec: execer}, + criCtlChecker, ExtraArgsCheck{ APIServerExtraArgs: cfg.APIServerExtraArgs, ControllerManagerExtraArgs: cfg.ControllerManagerExtraArgs, SchedulerExtraArgs: cfg.SchedulerExtraArgs, }, + HTTPProxyCheck{Proto: "https", Host: cfg.API.AdvertiseAddress, Port: int(cfg.API.BindPort)}, + HTTPProxyCIDRCheck{Proto: "https", CIDR: cfg.Networking.ServiceSubnet}, + HTTPProxyCIDRCheck{Proto: "https", CIDR: cfg.Networking.PodSubnet}, + } + + if useCRI { + checks = append(checks, CRICheck{socket: criSocket, exec: execer}) + } else { + // assume docker + checks = append(checks, ServiceCheck{Service: "docker", CheckIfActive: true}) } if len(cfg.Etcd.Endpoints) == 0 { @@ -700,67 +930,115 @@ func RunInitMasterChecks(cfg *kubeadmapi.MasterConfiguration) error { } } - return RunChecks(checks, os.Stderr) + if ip := net.ParseIP(cfg.API.AdvertiseAddress); ip != nil { + if ip.To4() == nil && ip.To16() != nil { + checks = append(checks, + FileContentCheck{Path: bridgenf6, Content: []byte{'1'}}, + ) + } + } + return RunChecks(checks, os.Stderr, ignorePreflightErrors) } // RunJoinNodeChecks executes all individual, applicable to node checks. -func RunJoinNodeChecks(cfg *kubeadmapi.NodeConfiguration) error { +func RunJoinNodeChecks(execer utilsexec.Interface, cfg *kubeadmapi.NodeConfiguration, criSocket string, ignorePreflightErrors sets.String) error { // First, check if we're root separately from the other preflight checks and fail fast - if err := RunRootCheckOnly(); err != nil { + if err := RunRootCheckOnly(ignorePreflightErrors); err != nil { return err } + // check if we can use crictl to perform checks via the CRI + criCtlChecker := InPathCheck{executable: "crictl", mandatory: false, exec: execer} + warns, _ := criCtlChecker.Check() + useCRI := len(warns) == 0 + checks := []Checker{ - SystemVerificationCheck{}, - IsRootCheck{}, + SystemVerificationCheck{CRISocket: criSocket}, + IsPrivilegedUserCheck{}, HostnameCheck{cfg.NodeName}, KubeletVersionCheck{}, ServiceCheck{Service: "kubelet", CheckIfActive: false}, - ServiceCheck{Service: "docker", CheckIfActive: true}, PortOpenCheck{port: 10250}, DirAvailableCheck{Path: filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ManifestsSubDirName)}, FileAvailableCheck{Path: cfg.CACertPath}, FileAvailableCheck{Path: filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.KubeletKubeConfigFileName)}, - FileContentCheck{Path: bridgenf, Content: []byte{'1'}}, - SwapCheck{}, - InPathCheck{executable: "ip", mandatory: true}, - InPathCheck{executable: "iptables", mandatory: true}, - InPathCheck{executable: "mount", mandatory: true}, - InPathCheck{executable: "nsenter", mandatory: true}, - InPathCheck{executable: "ebtables", mandatory: false}, - InPathCheck{executable: "ethtool", mandatory: false}, - InPathCheck{executable: "socat", mandatory: false}, - InPathCheck{executable: "tc", mandatory: false}, - InPathCheck{executable: "touch", mandatory: false}, + } + if useCRI { + checks = append(checks, CRICheck{socket: criSocket, exec: execer}) + } else { + // assume docker + checks = append(checks, ServiceCheck{Service: "docker", CheckIfActive: true}) + } + //non-windows checks + if runtime.GOOS == "linux" { + checks = append(checks, + FileContentCheck{Path: bridgenf, Content: []byte{'1'}}, + SwapCheck{}, + InPathCheck{executable: "ip", mandatory: true, exec: execer}, + InPathCheck{executable: "iptables", mandatory: true, exec: execer}, + InPathCheck{executable: "mount", mandatory: true, exec: execer}, + InPathCheck{executable: "nsenter", mandatory: true, exec: execer}, + InPathCheck{executable: "ebtables", mandatory: false, exec: execer}, + InPathCheck{executable: "ethtool", mandatory: false, exec: execer}, + InPathCheck{executable: "socat", mandatory: false, exec: execer}, + InPathCheck{executable: "tc", mandatory: false, exec: execer}, + InPathCheck{executable: "touch", mandatory: false, exec: execer}, + criCtlChecker) } - return RunChecks(checks, os.Stderr) + if len(cfg.DiscoveryTokenAPIServers) > 0 { + if ip := net.ParseIP(cfg.DiscoveryTokenAPIServers[0]); ip != nil { + if ip.To4() == nil && ip.To16() != nil { + checks = append(checks, + FileContentCheck{Path: bridgenf6, Content: []byte{'1'}}, + ) + } + } + } + return RunChecks(checks, os.Stderr, ignorePreflightErrors) } -// RunRootCheckOnly initializes cheks slice of structs and call RunChecks -func RunRootCheckOnly() error { +// RunRootCheckOnly initializes checks slice of structs and call RunChecks +func RunRootCheckOnly(ignorePreflightErrors sets.String) error { checks := []Checker{ - IsRootCheck{}, + IsPrivilegedUserCheck{}, } - return RunChecks(checks, os.Stderr) + return RunChecks(checks, os.Stderr, ignorePreflightErrors) } // RunChecks runs each check, displays it's warnings/errors, and once all // are processed will exit if any errors occurred. -func RunChecks(checks []Checker, ww io.Writer) error { - found := []error{} +func RunChecks(checks []Checker, ww io.Writer, ignorePreflightErrors sets.String) error { + type checkErrors struct { + Name string + Errors []error + } + found := []checkErrors{} + for _, c := range checks { + name := c.Name() warnings, errs := c.Check() - for _, w := range warnings { - io.WriteString(ww, fmt.Sprintf("[preflight] WARNING: %v\n", w)) + + if setHasItemOrAll(ignorePreflightErrors, name) { + // Decrease severity of errors to warnings for this check + warnings = append(warnings, errs...) + errs = []error{} + } + + for _, w := range warnings { + io.WriteString(ww, fmt.Sprintf("\t[WARNING %s]: %v\n", name, w)) + } + if len(errs) > 0 { + found = append(found, checkErrors{Name: name, Errors: errs}) } - found = append(found, errs...) } if len(found) > 0 { var errs bytes.Buffer - for _, i := range found { - errs.WriteString("\t" + i.Error() + "\n") + for _, c := range found { + for _, i := range c.Errors { + errs.WriteString(fmt.Sprintf("\t[ERROR %s]: %v\n", c.Name, i.Error())) + } } return &Error{Msg: errs.String()} } @@ -768,7 +1046,10 @@ func RunChecks(checks []Checker, ww io.Writer) error { } // TryStartKubelet attempts to bring up kubelet service -func TryStartKubelet() { +func TryStartKubelet(ignorePreflightErrors sets.String) { + if setHasItemOrAll(ignorePreflightErrors, "StartKubelet") { + return + } // If we notice that the kubelet service is inactive, try to start it initSystem, err := initsystem.GetInitSystem() if err != nil { @@ -782,3 +1063,11 @@ func TryStartKubelet() { } } } + +// setHasItemOrAll is helper function that return true if item is present in the set (case insensitive) or special key 'all' is present +func setHasItemOrAll(s sets.String, item string) bool { + if s.Has("all") || s.Has(strings.ToLower(item)) { + return true + } + return false +} diff --git a/cmd/kubeadm/app/preflight/checks_test.go b/cmd/kubeadm/app/preflight/checks_test.go index 18efdfd1a60..7fe7c425caa 100644 --- a/cmd/kubeadm/app/preflight/checks_test.go +++ b/cmd/kubeadm/app/preflight/checks_test.go @@ -20,13 +20,18 @@ import ( "bytes" "fmt" "io/ioutil" + "path/filepath" + "strings" "testing" "github.com/renstrom/dedent" + "net/http" "os" + "k8s.io/apimachinery/pkg/util/sets" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + "k8s.io/utils/exec" ) var ( @@ -162,6 +167,10 @@ type preflightCheckTest struct { msg string } +func (pfct preflightCheckTest) Name() string { + return "preflightCheckTest" +} + func (pfct preflightCheckTest) Check() (warning, errors []error) { if pfct.msg == "warning" { return []error{fmt.Errorf("warning")}, nil @@ -205,10 +214,16 @@ func TestRunInitMasterChecks(t *testing.T) { }, expected: false, }, + { + cfg: &kubeadmapi.MasterConfiguration{ + API: kubeadmapi.API{AdvertiseAddress: "2001:1234::1:15"}, + }, + expected: false, + }, } for _, rt := range tests { - actual := RunInitMasterChecks(rt.cfg) + actual := RunInitMasterChecks(exec.New(), rt.cfg, "", sets.NewString()) if (actual == nil) != rt.expected { t.Errorf( "failed RunInitMasterChecks:\n\texpected: %t\n\t actual: %t\n\t error: %v", @@ -229,10 +244,22 @@ func TestRunJoinNodeChecks(t *testing.T) { cfg: &kubeadmapi.NodeConfiguration{}, expected: false, }, + { + cfg: &kubeadmapi.NodeConfiguration{ + DiscoveryTokenAPIServers: []string{"192.168.1.15"}, + }, + expected: false, + }, + { + cfg: &kubeadmapi.NodeConfiguration{ + DiscoveryTokenAPIServers: []string{"2001:1234::1:15"}, + }, + expected: false, + }, } for _, rt := range tests { - actual := RunJoinNodeChecks(rt.cfg) + actual := RunJoinNodeChecks(exec.New(), rt.cfg, "", sets.NewString()) if (actual == nil) != rt.expected { t.Errorf( "failed RunJoinNodeChecks:\n\texpected: %t\n\t actual: %t", @@ -250,7 +277,7 @@ func TestRunChecks(t *testing.T) { output string }{ {[]Checker{}, true, ""}, - {[]Checker{preflightCheckTest{"warning"}}, true, "[preflight] WARNING: warning\n"}, // should just print warning + {[]Checker{preflightCheckTest{"warning"}}, true, "\t[WARNING preflightCheckTest]: warning\n"}, // should just print warning {[]Checker{preflightCheckTest{"error"}}, false, ""}, {[]Checker{preflightCheckTest{"test"}}, false, ""}, {[]Checker{DirAvailableCheck{Path: "/does/not/exist"}}, true, ""}, @@ -259,8 +286,8 @@ func TestRunChecks(t *testing.T) { {[]Checker{FileContentCheck{Path: "/does/not/exist"}}, false, ""}, {[]Checker{FileContentCheck{Path: "/"}}, true, ""}, {[]Checker{FileContentCheck{Path: "/", Content: []byte("does not exist")}}, false, ""}, - {[]Checker{InPathCheck{executable: "foobarbaz"}}, true, "[preflight] WARNING: foobarbaz not found in system path\n"}, - {[]Checker{InPathCheck{executable: "foobarbaz", mandatory: true}}, false, ""}, + {[]Checker{InPathCheck{executable: "foobarbaz", exec: exec.New()}}, true, "\t[WARNING FileExisting-foobarbaz]: foobarbaz not found in system path\n"}, + {[]Checker{InPathCheck{executable: "foobarbaz", mandatory: true, exec: exec.New()}}, false, ""}, {[]Checker{ExtraArgsCheck{ APIServerExtraArgs: map[string]string{"secure-port": "1234"}, ControllerManagerExtraArgs: map[string]string{"use-service-account-credentials": "true"}, @@ -268,14 +295,14 @@ func TestRunChecks(t *testing.T) { }}, true, ""}, {[]Checker{ExtraArgsCheck{ APIServerExtraArgs: map[string]string{"secure-port": "foo"}, - }}, true, "[preflight] WARNING: kube-apiserver: failed to parse extra argument --secure-port=foo\n"}, + }}, true, "\t[WARNING ExtraArgs]: kube-apiserver: failed to parse extra argument --secure-port=foo\n"}, {[]Checker{ExtraArgsCheck{ APIServerExtraArgs: map[string]string{"invalid-argument": "foo"}, - }}, true, "[preflight] WARNING: kube-apiserver: failed to parse extra argument --invalid-argument=foo\n"}, + }}, true, "\t[WARNING ExtraArgs]: kube-apiserver: failed to parse extra argument --invalid-argument=foo\n"}, } for _, rt := range tokenTest { buf := new(bytes.Buffer) - actual := RunChecks(rt.p, buf) + actual := RunChecks(rt.p, buf, sets.NewString()) if (actual == nil) != rt.expected { t.Errorf( "failed RunChecks:\n\texpected: %t\n\t actual: %t", @@ -432,3 +459,190 @@ func TestKubernetesVersionCheck(t *testing.T) { } } } + +func TestHTTPProxyCIDRCheck(t *testing.T) { + var tests = []struct { + check HTTPProxyCIDRCheck + expectWarnings bool + }{ + { + check: HTTPProxyCIDRCheck{ + Proto: "https", + CIDR: "127.0.0.0/8", + }, // Loopback addresses never should produce proxy warnings + expectWarnings: false, + }, + { + check: HTTPProxyCIDRCheck{ + Proto: "https", + CIDR: "10.96.0.0/12", + }, // Expected to be accessed directly, we set NO_PROXY to 10.0.0.0/8 + expectWarnings: false, + }, + { + check: HTTPProxyCIDRCheck{ + Proto: "https", + CIDR: "192.168.0.0/16", + }, // Expected to go via proxy as this range is not listed in NO_PROXY + expectWarnings: true, + }, + { + check: HTTPProxyCIDRCheck{ + Proto: "https", + CIDR: "2001:db8::/56", + }, // Expected to be accessed directly, part of 2001:db8::/48 in NO_PROXY + expectWarnings: false, + }, + { + check: HTTPProxyCIDRCheck{ + Proto: "https", + CIDR: "2001:db8:1::/56", + }, // Expected to go via proxy, range is not in 2001:db8::/48 + expectWarnings: true, + }, + } + + // Save current content of *_proxy and *_PROXY variables. + savedEnv := resetProxyEnv() + defer restoreEnv(savedEnv) + t.Log("Saved environment: ", savedEnv) + + os.Setenv("HTTP_PROXY", "http://proxy.example.com:3128") + os.Setenv("NO_PROXY", "example.com,10.0.0.0/8,2001:db8::/48") + + // Check if we can reliably execute tests: + // ProxyFromEnvironment caches the *_proxy environment variables and + // if ProxyFromEnvironment already executed before our test with empty + // HTTP_PROXY it will make these tests return false positive failures + req, err := http.NewRequest("GET", "http://host.fake.tld/", nil) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + proxy, err := http.ProxyFromEnvironment(req) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + if proxy == nil { + t.Skip("test skipped as ProxyFromEnvironment already initialized in environment without defined HTTP proxy") + } + t.Log("http.ProxyFromEnvironment is usable, continue executing test") + + for _, rt := range tests { + warning, _ := rt.check.Check() + if (warning != nil) != rt.expectWarnings { + t.Errorf( + "failed HTTPProxyCIDRCheck:\n\texpected: %t\n\t actual: %t (CIDR:%s). Warnings: %v", + rt.expectWarnings, + (warning != nil), + rt.check.CIDR, + warning, + ) + } + } +} + +// resetProxyEnv is helper function that unsets all *_proxy variables +// and return previously set values as map. This can be used to restore +// original state of the environment. +func resetProxyEnv() map[string]string { + savedEnv := make(map[string]string) + for _, e := range os.Environ() { + pair := strings.Split(e, "=") + if strings.HasSuffix(strings.ToLower(pair[0]), "_proxy") { + savedEnv[pair[0]] = pair[1] + os.Unsetenv(pair[0]) + } + } + return savedEnv +} + +// restoreEnv is helper function to restores values +// of environment variables from saved state in the map +func restoreEnv(e map[string]string) { + for k, v := range e { + os.Setenv(k, v) + } +} + +func TestKubeletVersionCheck(t *testing.T) { + type T struct { + kubeletVersion string + k8sVersion string + expectErrors bool + expectWarnings bool + } + + cases := []T{ + {"v1.10.2", "", false, false}, // check minimally supported version when there is no information about control plane + {"v1.7.3", "v1.7.8", true, false}, // too old kubelet (older than kubeadmconstants.MinimumKubeletVersion), should fail. + {"v1.9.0", "v1.9.5", false, false}, // kubelet within same major.minor as control plane + {"v1.9.5", "v1.9.1", false, false}, // kubelet is newer, but still within same major.minor as control plane + {"v1.9.0", "v1.10.1", false, false}, // kubelet is lower than control plane, but newer than minimally supported + {"v1.10.0-alpha.1", "v1.9.1", true, false}, // kubelet is newer (development build) than control plane, should fail. + {"v1.10.0", "v1.9.5", true, false}, // kubelet is newer (release) than control plane, should fail. + } + + dir, err := ioutil.TempDir("", "test-kubelet-version-check") + if err != nil { + t.Errorf("Failed to create directory for testing GetKubeletVersion: %v", err) + } + defer os.RemoveAll(dir) + + // We don't want to call real kubelet or something else in $PATH + oldPATH := os.Getenv("PATH") + defer os.Setenv("PATH", oldPATH) + + os.Setenv("PATH", dir) + + kubeletFn := filepath.Join(dir, "kubelet") + for _, tc := range cases { + + content := []byte(fmt.Sprintf("#!/bin/sh\necho 'Kubernetes %s'", tc.kubeletVersion)) + if err := ioutil.WriteFile(kubeletFn, content, 0755); err != nil { + t.Errorf("Error creating test stub file %s: %v", kubeletFn, err) + } + + check := KubeletVersionCheck{KubernetesVersion: tc.k8sVersion} + warnings, errors := check.Check() + + switch { + case warnings != nil && !tc.expectWarnings: + t.Errorf("KubeletVersionCheck: unexpected warnings for kubelet version %q and kubernetes version %q. Warnings: %v", tc.kubeletVersion, tc.k8sVersion, warnings) + case warnings == nil && tc.expectWarnings: + t.Errorf("KubeletVersionCheck: expected warnings for kubelet version %q and kubernetes version %q but got nothing", tc.kubeletVersion, tc.k8sVersion) + case errors != nil && !tc.expectErrors: + t.Errorf("KubeletVersionCheck: unexpected errors for kubelet version %q and kubernetes version %q. errors: %v", tc.kubeletVersion, tc.k8sVersion, errors) + case errors == nil && tc.expectErrors: + t.Errorf("KubeletVersionCheck: expected errors for kubelet version %q and kubernetes version %q but got nothing", tc.kubeletVersion, tc.k8sVersion) + } + + } + +} + +func TestSetHasItemOrAll(t *testing.T) { + var tests = []struct { + ignoreSet sets.String + testString string + expectedResult bool + }{ + {sets.NewString(), "foo", false}, + {sets.NewString("all"), "foo", true}, + {sets.NewString("all", "bar"), "foo", true}, + {sets.NewString("bar"), "foo", false}, + {sets.NewString("baz", "foo", "bar"), "foo", true}, + {sets.NewString("baz", "bar", "foo"), "Foo", true}, + } + + for _, rt := range tests { + result := setHasItemOrAll(rt.ignoreSet, rt.testString) + if result != rt.expectedResult { + t.Errorf( + "setHasItemOrAll: expected: %v actual: %v (arguments: %q, %q)", + rt.expectedResult, result, + rt.ignoreSet, + rt.testString, + ) + } + } +} diff --git a/cmd/kubeadm/app/preflight/checks_unix.go b/cmd/kubeadm/app/preflight/checks_unix.go new file mode 100644 index 00000000000..1f2fb754e01 --- /dev/null +++ b/cmd/kubeadm/app/preflight/checks_unix.go @@ -0,0 +1,34 @@ +// +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 preflight + +import ( + "fmt" + "os" +) + +// Check validates if an user has elevated (root) privileges. +func (ipuc IsPrivilegedUserCheck) Check() (warnings, errors []error) { + errors = []error{} + if os.Getuid() != 0 { + errors = append(errors, fmt.Errorf("user is not running as root")) + } + + return nil, errors +} diff --git a/cmd/kubeadm/app/preflight/checks_windows.go b/cmd/kubeadm/app/preflight/checks_windows.go new file mode 100644 index 00000000000..1cc9bb3e59e --- /dev/null +++ b/cmd/kubeadm/app/preflight/checks_windows.go @@ -0,0 +1,44 @@ +// +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 preflight + +import ( + "fmt" + "os/exec" + "strings" +) + +// Check validates if an user has elevated (administrator) privileges. +func (ipuc IsPrivilegedUserCheck) Check() (warnings, errors []error) { + errors = []error{} + + // The "Well-known SID" of Administrator group is S-1-5-32-544 + // The following powershell will return "True" if run as an administrator, "False" otherwise + // See https://msdn.microsoft.com/en-us/library/cc980032.aspx + args := []string{"[bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).groups -match \"S-1-5-32-544\")"} + isAdmin, err := exec.Command("powershell", args...).Output() + + if err != nil { + errors = append(errors, fmt.Errorf("unable to determine if user is running as administrator: %s", err)) + } else if strings.EqualFold(strings.TrimSpace(string(isAdmin)), "false") { + errors = append(errors, fmt.Errorf("user is not running as administrator")) + } + + return nil, errors +} diff --git a/cmd/kubeadm/app/util/BUILD b/cmd/kubeadm/app/util/BUILD index 5f6665961e2..5c3a8173b60 100644 --- a/cmd/kubeadm/app/util/BUILD +++ b/cmd/kubeadm/app/util/BUILD @@ -10,8 +10,10 @@ go_library( name = "go_default_library", srcs = [ "arguments.go", + "copy.go", "endpoint.go", "error.go", + "etcd.go", "marshal.go", "template.go", "version.go", @@ -20,8 +22,10 @@ go_library( deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/preflight:go_default_library", + "//vendor/github.com/coreos/etcd/clientv3:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library", ], @@ -36,8 +40,8 @@ go_test( "template_test.go", "version_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util", - library = ":go_default_library", deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/preflight:go_default_library", diff --git a/cmd/kubeadm/app/util/apiclient/BUILD b/cmd/kubeadm/app/util/apiclient/BUILD index 1b79bbe519b..598c1eec29f 100644 --- a/cmd/kubeadm/app/util/apiclient/BUILD +++ b/cmd/kubeadm/app/util/apiclient/BUILD @@ -20,7 +20,7 @@ go_library( "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/util:go_default_library", "//pkg/registry/core/service/ipallocator:go_default_library", - "//vendor/k8s.io/api/apps/v1beta2:go_default_library", + "//vendor/k8s.io/api/apps/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/rbac/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", @@ -58,8 +58,8 @@ go_test( "dryrunclient_test.go", "init_dryrun_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient", - library = ":go_default_library", deps = [ "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/rbac/v1:go_default_library", diff --git a/cmd/kubeadm/app/util/apiclient/clientbacked_dryrun.go b/cmd/kubeadm/app/util/apiclient/clientbacked_dryrun.go index 2fbf95bdbb0..e531045fdf1 100644 --- a/cmd/kubeadm/app/util/apiclient/clientbacked_dryrun.go +++ b/cmd/kubeadm/app/util/apiclient/clientbacked_dryrun.go @@ -75,6 +75,9 @@ func (clg *ClientBackedDryRunGetter) HandleGetAction(action core.GetAction) (boo } unversionedObj, err := rc.Get(action.GetName(), metav1.GetOptions{}) + if err != nil { + return true, nil, err + } // If the unversioned object does not have .apiVersion; the inner object is probably nil if len(unversionedObj.GetAPIVersion()) == 0 { return true, nil, apierrors.NewNotFound(action.GetResource().GroupResource(), action.GetName()) @@ -100,6 +103,9 @@ func (clg *ClientBackedDryRunGetter) HandleListAction(action core.ListAction) (b } unversionedList, err := rc.List(listOpts) + if err != nil { + return true, nil, err + } // If the runtime.Object here is nil, we should return successfully with no result if unversionedList == nil { return true, unversionedList, nil diff --git a/cmd/kubeadm/app/util/apiclient/dryrunclient.go b/cmd/kubeadm/app/util/apiclient/dryrunclient.go index 030187987d4..6c0d796e6fa 100644 --- a/cmd/kubeadm/app/util/apiclient/dryrunclient.go +++ b/cmd/kubeadm/app/util/apiclient/dryrunclient.go @@ -85,10 +85,10 @@ func NewDryRunClient(drg DryRunGetter, w io.Writer) clientset.Interface { // This client doesn't apply changes to the backend. The client gets GET/LIST values from the DryRunGetter implementation. // This client logs all I/O to the writer w in YAML format func NewDryRunClientWithOpts(opts DryRunClientOptions) clientset.Interface { - // Build a chain of reactors to act like a normal clientset; but log everything's that happening and don't change any state + // Build a chain of reactors to act like a normal clientset; but log everything that is happening and don't change any state client := fakeclientset.NewSimpleClientset() - // Build the chain of reactors. Order matters; first item here will be invoked first on match, then the second one will be evaluted, etc. + // Build the chain of reactors. Order matters; first item here will be invoked first on match, then the second one will be evaluated, etc. defaultReactorChain := []core.Reactor{ // Log everything that happens. Default the object if it's about to be created/updated so that the logged object is representative. &core.SimpleReactor{ @@ -223,7 +223,7 @@ func logDryRunAction(action core.Action, w io.Writer, marshalFunc MarshalFunc) { patchAction, ok := action.(core.PatchAction) if ok { - // Replace all occurences of \" with a simple " when printing + // Replace all occurrences of \" with a simple " when printing fmt.Fprintf(w, "[dryrun] Attached patch:\n\t%s\n", strings.Replace(string(patchAction.GetPatch()), `\"`, `"`, -1)) } } diff --git a/cmd/kubeadm/app/util/apiclient/idempotency.go b/cmd/kubeadm/app/util/apiclient/idempotency.go index a2f11a74459..03f60be77cd 100644 --- a/cmd/kubeadm/app/util/apiclient/idempotency.go +++ b/cmd/kubeadm/app/util/apiclient/idempotency.go @@ -19,7 +19,7 @@ package apiclient import ( "fmt" - apps "k8s.io/api/apps/v1beta2" + apps "k8s.io/api/apps/v1" "k8s.io/api/core/v1" rbac "k8s.io/api/rbac/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -72,12 +72,12 @@ func CreateOrUpdateServiceAccount(client clientset.Interface, sa *v1.ServiceAcco // CreateOrUpdateDeployment creates a Deployment if the target resource doesn't exist. If the resource exists already, this function will update the resource instead. func CreateOrUpdateDeployment(client clientset.Interface, deploy *apps.Deployment) error { - if _, err := client.AppsV1beta2().Deployments(deploy.ObjectMeta.Namespace).Create(deploy); err != nil { + if _, err := client.AppsV1().Deployments(deploy.ObjectMeta.Namespace).Create(deploy); err != nil { if !apierrors.IsAlreadyExists(err) { return fmt.Errorf("unable to create deployment: %v", err) } - if _, err := client.AppsV1beta2().Deployments(deploy.ObjectMeta.Namespace).Update(deploy); err != nil { + if _, err := client.AppsV1().Deployments(deploy.ObjectMeta.Namespace).Update(deploy); err != nil { return fmt.Errorf("unable to update deployment: %v", err) } } @@ -86,12 +86,12 @@ func CreateOrUpdateDeployment(client clientset.Interface, deploy *apps.Deploymen // CreateOrUpdateDaemonSet creates a DaemonSet if the target resource doesn't exist. If the resource exists already, this function will update the resource instead. func CreateOrUpdateDaemonSet(client clientset.Interface, ds *apps.DaemonSet) error { - if _, err := client.AppsV1beta2().DaemonSets(ds.ObjectMeta.Namespace).Create(ds); err != nil { + if _, err := client.AppsV1().DaemonSets(ds.ObjectMeta.Namespace).Create(ds); err != nil { if !apierrors.IsAlreadyExists(err) { return fmt.Errorf("unable to create daemonset: %v", err) } - if _, err := client.AppsV1beta2().DaemonSets(ds.ObjectMeta.Namespace).Update(ds); err != nil { + if _, err := client.AppsV1().DaemonSets(ds.ObjectMeta.Namespace).Update(ds); err != nil { return fmt.Errorf("unable to update daemonset: %v", err) } } @@ -104,7 +104,16 @@ func DeleteDaemonSetForeground(client clientset.Interface, namespace, name strin deleteOptions := &metav1.DeleteOptions{ PropagationPolicy: &foregroundDelete, } - return client.AppsV1beta2().DaemonSets(namespace).Delete(name, deleteOptions) + return client.AppsV1().DaemonSets(namespace).Delete(name, deleteOptions) +} + +// DeleteDeploymentForeground deletes the specified Deployment in foreground mode; i.e. it blocks until/makes sure all the managed Pods are deleted +func DeleteDeploymentForeground(client clientset.Interface, namespace, name string) error { + foregroundDelete := metav1.DeletePropagationForeground + deleteOptions := &metav1.DeleteOptions{ + PropagationPolicy: &foregroundDelete, + } + return client.AppsV1().Deployments(namespace).Delete(name, deleteOptions) } // CreateOrUpdateRole creates a Role if the target resource doesn't exist. If the resource exists already, this function will update the resource instead. diff --git a/cmd/kubeadm/app/util/apiclient/wait.go b/cmd/kubeadm/app/util/apiclient/wait.go index 0ac79dd7ef4..2ab645d9a64 100644 --- a/cmd/kubeadm/app/util/apiclient/wait.go +++ b/cmd/kubeadm/app/util/apiclient/wait.go @@ -40,6 +40,8 @@ type Waiter interface { WaitForPodsWithLabel(kvLabel string) error // WaitForPodToDisappear waits for the given Pod in the kube-system namespace to be deleted WaitForPodToDisappear(staticPodName string) error + // WaitForStaticPodSingleHash fetches sha256 hash for the control plane static pod + WaitForStaticPodSingleHash(nodeName string, component string) (string, error) // WaitForStaticPodControlPlaneHashes fetches sha256 hashes for the control plane static pods WaitForStaticPodControlPlaneHashes(nodeName string) (map[string]string, error) // WaitForStaticPodControlPlaneHashChange waits for the given static pod component's static pod hash to get updated. @@ -154,17 +156,40 @@ func (w *KubeWaiter) SetTimeout(timeout time.Duration) { // WaitForStaticPodControlPlaneHashes blocks until it timeouts or gets a hash map for all components and their Static Pods func (w *KubeWaiter) WaitForStaticPodControlPlaneHashes(nodeName string) (map[string]string, error) { - var mirrorPodHashes map[string]string - err := wait.PollImmediate(constants.APICallRetryInterval, w.timeout, func() (bool, error) { + componentHash := "" + var err error + mirrorPodHashes := map[string]string{} + for _, component := range constants.MasterComponents { + err = wait.PollImmediate(constants.APICallRetryInterval, w.timeout, func() (bool, error) { + componentHash, err = getStaticPodSingleHash(w.client, nodeName, component) + if err != nil { + return false, nil + } + return true, nil + }) + if err != nil { + return nil, err + } + mirrorPodHashes[component] = componentHash + } - hashes, err := getStaticPodControlPlaneHashes(w.client, nodeName) + return mirrorPodHashes, nil +} + +// WaitForStaticPodSingleHash blocks until it timeouts or gets a hash for a single component and its Static Pod +func (w *KubeWaiter) WaitForStaticPodSingleHash(nodeName string, component string) (string, error) { + + componentPodHash := "" + var err error + err = wait.PollImmediate(constants.APICallRetryInterval, w.timeout, func() (bool, error) { + componentPodHash, err = getStaticPodSingleHash(w.client, nodeName, component) if err != nil { return false, nil } - mirrorPodHashes = hashes return true, nil }) - return mirrorPodHashes, err + + return componentPodHash, err } // WaitForStaticPodControlPlaneHashChange blocks until it timeouts or notices that the Mirror Pod (for the Static Pod, respectively) has changed @@ -190,22 +215,32 @@ func getStaticPodControlPlaneHashes(client clientset.Interface, nodeName string) mirrorPodHashes := map[string]string{} for _, component := range constants.MasterComponents { - staticPodName := fmt.Sprintf("%s-%s", component, nodeName) - staticPod, err := client.CoreV1().Pods(metav1.NamespaceSystem).Get(staticPodName, metav1.GetOptions{}) + hash, err := getStaticPodSingleHash(client, nodeName, component) if err != nil { return nil, err } - - podBytes, err := json.Marshal(staticPod) - if err != nil { - return nil, err - } - - mirrorPodHashes[component] = fmt.Sprintf("%x", sha256.Sum256(podBytes)) + mirrorPodHashes[component] = hash } return mirrorPodHashes, nil } +// getStaticSinglePodHash computes hashes for a single Static Pod resource +func getStaticPodSingleHash(client clientset.Interface, nodeName string, component string) (string, error) { + + staticPodName := fmt.Sprintf("%s-%s", component, nodeName) + staticPod, err := client.CoreV1().Pods(metav1.NamespaceSystem).Get(staticPodName, metav1.GetOptions{}) + if err != nil { + return "", err + } + + podBytes, err := json.Marshal(staticPod) + if err != nil { + return "", err + } + + return fmt.Sprintf("%x", sha256.Sum256(podBytes)), nil +} + // TryRunCommand runs a function a maximum of failureThreshold times, and retries on error. If failureThreshold is hit; the last error is returned func TryRunCommand(f func() error, failureThreshold int) error { backoff := wait.Backoff{ diff --git a/cmd/kubeadm/app/util/config/BUILD b/cmd/kubeadm/app/util/config/BUILD index c3a1e5c7909..90805eba5a0 100644 --- a/cmd/kubeadm/app/util/config/BUILD +++ b/cmd/kubeadm/app/util/config/BUILD @@ -17,7 +17,7 @@ go_library( "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/util:go_default_library", "//cmd/kubeadm/app/util/token:go_default_library", - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/util/node:go_default_library", "//pkg/util/version:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", @@ -28,8 +28,8 @@ go_library( go_test( name = "go_default_test", srcs = ["masterconfig_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/config", - library = ":go_default_library", ) filegroup( diff --git a/cmd/kubeadm/app/util/config/masterconfig.go b/cmd/kubeadm/app/util/config/masterconfig.go index f96d3d112c0..e4bec1d6dcc 100644 --- a/cmd/kubeadm/app/util/config/masterconfig.go +++ b/cmd/kubeadm/app/util/config/masterconfig.go @@ -29,7 +29,7 @@ import ( kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/util/node" "k8s.io/kubernetes/pkg/util/version" ) @@ -44,7 +44,12 @@ func SetInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error { return err } cfg.API.AdvertiseAddress = ip.String() - + ip = net.ParseIP(cfg.API.AdvertiseAddress) + if ip.To4() != nil { + cfg.KubeProxy.Config.BindAddress = kubeadmapiext.DefaultProxyBindAddressv4 + } else { + cfg.KubeProxy.Config.BindAddress = kubeadmapiext.DefaultProxyBindAddressv6 + } // Resolve possible version labels and validate version string err = NormalizeKubernetesVersion(cfg) if err != nil { @@ -72,7 +77,7 @@ func TryLoadMasterConfiguration(cfgPath string, cfg *kubeadmapiext.MasterConfigu if err != nil { return fmt.Errorf("unable to read config from %q [%v]", cfgPath, err) } - if err := runtime.DecodeInto(api.Codecs.UniversalDecoder(), b, cfg); err != nil { + if err := runtime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), b, cfg); err != nil { return fmt.Errorf("unable to decode config from %q [%v]", cfgPath, err) } } @@ -96,8 +101,8 @@ func ConfigFileAndDefaultsToInternalConfig(cfgPath string, defaultversionedcfg * // Takes passed flags into account; the defaulting is executed once again enforcing assignement of // static default values to cfg only for values not provided with flags - api.Scheme.Default(defaultversionedcfg) - api.Scheme.Convert(defaultversionedcfg, internalcfg, nil) + legacyscheme.Scheme.Default(defaultversionedcfg) + legacyscheme.Scheme.Convert(defaultversionedcfg, internalcfg, nil) // Applies dynamic defaults to settings not provided with flags if err := SetInitDynamicDefaults(internalcfg); err != nil { return nil, err diff --git a/cmd/kubeadm/app/util/copy.go b/cmd/kubeadm/app/util/copy.go new file mode 100644 index 00000000000..6465547d2c2 --- /dev/null +++ b/cmd/kubeadm/app/util/copy.go @@ -0,0 +1,31 @@ +/* +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 util + +import ( + "os/exec" +) + +// CopyDir copies the content of a folder +func CopyDir(src string, dst string) error { + cmd := exec.Command("cp", "-r", src, dst) + err := cmd.Run() + if err != nil { + return err + } + return nil +} diff --git a/cmd/kubeadm/app/util/dryrun/dryrun.go b/cmd/kubeadm/app/util/dryrun/dryrun.go index cad1eba2fba..cf3b391b1a0 100644 --- a/cmd/kubeadm/app/util/dryrun/dryrun.go +++ b/cmd/kubeadm/app/util/dryrun/dryrun.go @@ -116,6 +116,12 @@ func (w *Waiter) WaitForStaticPodControlPlaneHashes(_ string) (map[string]string }, nil } +// WaitForStaticPodSingleHash returns an empty hash +// but the empty strings there are needed +func (w *Waiter) WaitForStaticPodSingleHash(_ string, _ string) (string, error) { + return "", nil +} + // WaitForStaticPodControlPlaneHashChange returns a dummy nil error in order for the flow to just continue as we're dryrunning func (w *Waiter) WaitForStaticPodControlPlaneHashChange(_, _, _ string) error { return nil diff --git a/cmd/kubeadm/app/util/error.go b/cmd/kubeadm/app/util/error.go index 32cb6150197..61327cf5608 100644 --- a/cmd/kubeadm/app/util/error.go +++ b/cmd/kubeadm/app/util/error.go @@ -75,3 +75,12 @@ func checkErr(prefix string, err error, handleErr func(string, int)) { handleErr(err.Error(), DefaultErrorExitCode) } } + +// FormatErrMsg returns a human-readable string describing the slice of errors passed to the function +func FormatErrMsg(errs []error) string { + var errMsg string + for _, err := range errs { + errMsg = fmt.Sprintf("%s\t- %s\n", errMsg, err.Error()) + } + return errMsg +} diff --git a/cmd/kubeadm/app/util/error_test.go b/cmd/kubeadm/app/util/error_test.go index 07aae6c3357..c28a6cc0566 100644 --- a/cmd/kubeadm/app/util/error_test.go +++ b/cmd/kubeadm/app/util/error_test.go @@ -50,3 +50,34 @@ func TestCheckErr(t *testing.T) { } } } + +func TestFormatErrMsg(t *testing.T) { + errMsg1 := "specified version to upgrade to v1.9.0-alpha.3 is equal to or lower than the cluster version v1.10.0-alpha.0.69+638add6ddfb6d2. Downgrades are not supported yet" + errMsg2 := "specified version to upgrade to v1.9.0-alpha.3 is higher than the kubeadm version v1.9.0-alpha.1.3121+84178212527295-dirty. Upgrade kubeadm first using the tool you used to install kubeadm" + + testCases := []struct { + errs []error + expect string + }{ + { + errs: []error{ + fmt.Errorf(errMsg1), + fmt.Errorf(errMsg2), + }, + expect: "\t- " + errMsg1 + "\n" + "\t- " + errMsg2 + "\n", + }, + { + errs: []error{ + fmt.Errorf(errMsg1), + }, + expect: "\t- " + errMsg1 + "\n", + }, + } + + for _, testCase := range testCases { + got := FormatErrMsg(testCase.errs) + if got != testCase.expect { + t.Errorf("FormatErrMsg error, expect: %v, got: %v", testCase.expect, got) + } + } +} diff --git a/cmd/kubeadm/app/util/etcd.go b/cmd/kubeadm/app/util/etcd.go new file mode 100644 index 00000000000..45d80339072 --- /dev/null +++ b/cmd/kubeadm/app/util/etcd.go @@ -0,0 +1,51 @@ +/* +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 util + +import ( + "context" + "github.com/coreos/etcd/clientv3" + "time" +) + +// EtcdCluster is an interface to get etcd cluster related information +type EtcdCluster interface { + GetEtcdClusterStatus() (*clientv3.StatusResponse, error) +} + +// LocalEtcdCluster represents an instance of a local etcd cluster +type LocalEtcdCluster struct{} + +// GetEtcdClusterStatus returns nil for status Up or error for status Down +func (cluster LocalEtcdCluster) GetEtcdClusterStatus() (*clientv3.StatusResponse, error) { + ep := []string{"localhost:2379"} + cli, err := clientv3.New(clientv3.Config{ + Endpoints: ep, + DialTimeout: 5 * time.Second, + }) + if err != nil { + return nil, err + } + defer cli.Close() + + resp, err := cli.Status(context.Background(), ep[0]) + if err != nil { + return nil, err + } + + return resp, nil +} diff --git a/cmd/kubeadm/app/util/kubeconfig/BUILD b/cmd/kubeadm/app/util/kubeconfig/BUILD index 23e626ea97d..a6a93b95b14 100644 --- a/cmd/kubeadm/app/util/kubeconfig/BUILD +++ b/cmd/kubeadm/app/util/kubeconfig/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["kubeconfig_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig", - library = ":go_default_library", ) go_library( diff --git a/cmd/kubeadm/app/util/marshal.go b/cmd/kubeadm/app/util/marshal.go index f44180da3ac..907a0f17794 100644 --- a/cmd/kubeadm/app/util/marshal.go +++ b/cmd/kubeadm/app/util/marshal.go @@ -18,20 +18,42 @@ package util import ( "fmt" + "strings" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" clientsetscheme "k8s.io/client-go/kubernetes/scheme" ) // MarshalToYaml marshals an object into yaml. func MarshalToYaml(obj runtime.Object, gv schema.GroupVersion) ([]byte, error) { + return MarshalToYamlForCodecs(obj, gv, clientsetscheme.Codecs) +} + +// MarshalToYamlForCodecs marshals an object into yaml using the specified codec +func MarshalToYamlForCodecs(obj runtime.Object, gv schema.GroupVersion, codecs serializer.CodecFactory) ([]byte, error) { mediaType := "application/yaml" - info, ok := runtime.SerializerInfoForMediaType(clientsetscheme.Codecs.SupportedMediaTypes(), mediaType) + info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType) if !ok { return []byte{}, fmt.Errorf("unsupported media type %q", mediaType) } - encoder := clientsetscheme.Codecs.EncoderForVersion(info.Serializer, gv) + encoder := codecs.EncoderForVersion(info.Serializer, gv) return runtime.Encode(encoder, obj) } + +// MarshalToYamlForCodecsWithShift adds spaces in front of each line so the indents line up +// correctly in the manifest +func MarshalToYamlForCodecsWithShift(obj runtime.Object, gv schema.GroupVersion, codecs serializer.CodecFactory) (string, error) { + serial, err := MarshalToYamlForCodecs(obj, gv, codecs) + if err != nil { + return "", err + } + lines := strings.Split(string(serial), "\n") + var newSerial string + for _, line := range lines { + newSerial = newSerial + " " + line + "\n" + } + return newSerial, err +} diff --git a/cmd/kubeadm/app/util/pubkeypin/BUILD b/cmd/kubeadm/app/util/pubkeypin/BUILD index 017088f47a1..046cd8acd79 100644 --- a/cmd/kubeadm/app/util/pubkeypin/BUILD +++ b/cmd/kubeadm/app/util/pubkeypin/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["pubkeypin_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/pubkeypin", - library = ":go_default_library", ) go_library( diff --git a/cmd/kubeadm/app/util/staticpod/BUILD b/cmd/kubeadm/app/util/staticpod/BUILD index 6400a61d1e2..afa35d2712e 100644 --- a/cmd/kubeadm/app/util/staticpod/BUILD +++ b/cmd/kubeadm/app/util/staticpod/BUILD @@ -9,9 +9,11 @@ load( go_test( name = "go_default_test", srcs = ["utils_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod", - library = ":go_default_library", deps = [ + "//cmd/kubeadm/app/apis/kubeadm:go_default_library", + "//cmd/kubeadm/app/constants:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", @@ -23,6 +25,7 @@ go_library( srcs = ["utils.go"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod", deps = [ + "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/util:go_default_library", "//pkg/kubelet/types:go_default_library", diff --git a/cmd/kubeadm/app/util/staticpod/utils.go b/cmd/kubeadm/app/util/staticpod/utils.go index 505ab1b8e77..43c92e09890 100644 --- a/cmd/kubeadm/app/util/staticpod/utils.go +++ b/cmd/kubeadm/app/util/staticpod/utils.go @@ -19,19 +19,36 @@ package staticpod import ( "fmt" "io/ioutil" + "net" + "net/url" "os" + "strings" "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" + + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/util" kubetypes "k8s.io/kubernetes/pkg/kubelet/types" ) +const ( + // kubeControllerManagerAddressArg represents the address argument of the kube-controller-manager configuration. + kubeControllerManagerAddressArg = "address" + + // kubeSchedulerAddressArg represents the address argument of the kube-scheduler configuration. + kubeSchedulerAddressArg = "address" + + // etcdListenClientURLsArg represents the listen-client-urls argument of the etcd configuration. + etcdListenClientURLsArg = "listen-client-urls" +) + // ComponentPod returns a Pod object from the container and volume specifications -func ComponentPod(container v1.Container, volumes []v1.Volume) v1.Pod { +func ComponentPod(container v1.Container, volumes map[string]v1.Volume) v1.Pod { return v1.Pod{ TypeMeta: metav1.TypeMeta{ APIVersion: "v1", @@ -48,7 +65,7 @@ func ComponentPod(container v1.Container, volumes []v1.Volume) v1.Pod { Spec: v1.PodSpec{ Containers: []v1.Container{container}, HostNetwork: true, - Volumes: volumes, + Volumes: VolumeMapToSlice(volumes), }, } } @@ -63,12 +80,11 @@ func ComponentResources(cpu string) v1.ResourceRequirements { } // ComponentProbe is a helper function building a ready v1.Probe object from some simple parameters -func ComponentProbe(port int, path string, scheme v1.URIScheme) *v1.Probe { +func ComponentProbe(cfg *kubeadmapi.MasterConfiguration, componentName string, port int, path string, scheme v1.URIScheme) *v1.Probe { return &v1.Probe{ Handler: v1.Handler{ HTTPGet: &v1.HTTPGetAction{ - // Host has to be set to "127.0.0.1" here due to that our static Pods are on the host's network - Host: "127.0.0.1", + Host: GetProbeAddress(cfg, componentName), Path: path, Port: intstr.FromInt(port), Scheme: scheme, @@ -102,6 +118,28 @@ func NewVolumeMount(name, path string, readOnly bool) v1.VolumeMount { } } +// VolumeMapToSlice returns a slice of volumes from a map's values +func VolumeMapToSlice(volumes map[string]v1.Volume) []v1.Volume { + v := make([]v1.Volume, 0, len(volumes)) + + for _, vol := range volumes { + v = append(v, vol) + } + + return v +} + +// VolumeMountMapToSlice returns a slice of volumes from a map's values +func VolumeMountMapToSlice(volumeMounts map[string]v1.VolumeMount) []v1.VolumeMount { + v := make([]v1.VolumeMount, 0, len(volumeMounts)) + + for _, volMount := range volumeMounts { + v = append(v, volMount) + } + + return v +} + // GetExtraParameters builds a list of flag arguments two string-string maps, one with default, base commands and one with overrides func GetExtraParameters(overrides map[string]string, defaults map[string]string) []string { var command []string @@ -140,3 +178,59 @@ func WriteStaticPodToDisk(componentName, manifestDir string, pod v1.Pod) error { return nil } + +// GetProbeAddress returns an IP address or 127.0.0.1 to use for liveness probes +// in static pod manifests. +func GetProbeAddress(cfg *kubeadmapi.MasterConfiguration, componentName string) string { + switch { + case componentName == kubeadmconstants.KubeAPIServer: + if cfg.API.AdvertiseAddress != "" { + return cfg.API.AdvertiseAddress + } + case componentName == kubeadmconstants.KubeControllerManager: + if addr, exists := cfg.ControllerManagerExtraArgs[kubeControllerManagerAddressArg]; exists { + return addr + } + case componentName == kubeadmconstants.KubeScheduler: + if addr, exists := cfg.SchedulerExtraArgs[kubeSchedulerAddressArg]; exists { + return addr + } + case componentName == kubeadmconstants.Etcd: + if cfg.Etcd.ExtraArgs != nil { + if arg, exists := cfg.Etcd.ExtraArgs[etcdListenClientURLsArg]; exists { + // Use the first url in the listen-client-urls if multiple url's are specified. + if strings.ContainsAny(arg, ",") { + arg = strings.Split(arg, ",")[0] + } + parsedURL, err := url.Parse(arg) + if err != nil || parsedURL.Hostname() == "" { + break + } + // Return the IP if the URL contains an address instead of a name. + if ip := net.ParseIP(parsedURL.Hostname()); ip != nil { + return ip.String() + } + // Use the local resolver to try resolving the name within the URL. + // If the name can not be resolved, return an IPv4 loopback address. + // Otherwise, select the first valid IPv4 address. + // If the name does not resolve to an IPv4 address, select the first valid IPv6 address. + addrs, err := net.LookupIP(parsedURL.Hostname()) + if err != nil { + break + } + var ip net.IP + for _, addr := range addrs { + if addr.To4() != nil { + ip = addr + break + } + if addr.To16() != nil && ip == nil { + ip = addr + } + } + return ip.String() + } + } + } + return "127.0.0.1" +} diff --git a/cmd/kubeadm/app/util/staticpod/utils_test.go b/cmd/kubeadm/app/util/staticpod/utils_test.go index 9d7707f961e..5b681335841 100644 --- a/cmd/kubeadm/app/util/staticpod/utils_test.go +++ b/cmd/kubeadm/app/util/staticpod/utils_test.go @@ -24,6 +24,9 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" + + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" ) func TestComponentResources(t *testing.T) { @@ -37,43 +40,174 @@ func TestComponentResources(t *testing.T) { func TestComponentProbe(t *testing.T) { var tests = []struct { - port int - path string - scheme v1.URIScheme + name string + cfg *kubeadmapi.MasterConfiguration + component string + port int + path string + scheme v1.URIScheme + expected string }{ { - port: 1, - path: "foo", - scheme: v1.URISchemeHTTP, + name: "default apiserver advertise address with http", + cfg: &kubeadmapi.MasterConfiguration{ + API: kubeadmapi.API{ + AdvertiseAddress: "", + }, + }, + component: kubeadmconstants.KubeAPIServer, + port: 1, + path: "foo", + scheme: v1.URISchemeHTTP, + expected: "127.0.0.1", }, { - port: 2, - path: "bar", - scheme: v1.URISchemeHTTPS, + name: "default apiserver advertise address with https", + cfg: &kubeadmapi.MasterConfiguration{ + API: kubeadmapi.API{ + AdvertiseAddress: "", + }, + }, + component: kubeadmconstants.KubeAPIServer, + port: 2, + path: "bar", + scheme: v1.URISchemeHTTPS, + expected: "127.0.0.1", + }, + { + name: "valid ipv4 apiserver advertise address with http", + cfg: &kubeadmapi.MasterConfiguration{ + API: kubeadmapi.API{ + AdvertiseAddress: "1.2.3.4", + }, + }, + component: kubeadmconstants.KubeAPIServer, + port: 1, + path: "foo", + scheme: v1.URISchemeHTTP, + expected: "1.2.3.4", + }, + { + name: "valid ipv6 apiserver advertise address with http", + cfg: &kubeadmapi.MasterConfiguration{ + API: kubeadmapi.API{ + AdvertiseAddress: "2001:db8::1", + }, + }, + component: kubeadmconstants.KubeAPIServer, + port: 1, + path: "foo", + scheme: v1.URISchemeHTTP, + expected: "2001:db8::1", + }, + { + name: "valid IPv4 controller-manager probe", + cfg: &kubeadmapi.MasterConfiguration{ + ControllerManagerExtraArgs: map[string]string{"address": "1.2.3.4"}, + }, + component: kubeadmconstants.KubeControllerManager, + port: 1, + path: "foo", + scheme: v1.URISchemeHTTP, + expected: "1.2.3.4", + }, + { + name: "valid IPv6 controller-manager probe", + cfg: &kubeadmapi.MasterConfiguration{ + ControllerManagerExtraArgs: map[string]string{"address": "2001:db8::1"}, + }, + component: kubeadmconstants.KubeControllerManager, + port: 1, + path: "foo", + scheme: v1.URISchemeHTTP, + expected: "2001:db8::1", + }, + { + name: "valid IPv4 scheduler probe", + cfg: &kubeadmapi.MasterConfiguration{ + SchedulerExtraArgs: map[string]string{"address": "1.2.3.4"}, + }, + component: kubeadmconstants.KubeScheduler, + port: 1, + path: "foo", + scheme: v1.URISchemeHTTP, + expected: "1.2.3.4", + }, + { + name: "valid IPv6 scheduler probe", + cfg: &kubeadmapi.MasterConfiguration{ + SchedulerExtraArgs: map[string]string{"address": "2001:db8::1"}, + }, + component: kubeadmconstants.KubeScheduler, + port: 1, + path: "foo", + scheme: v1.URISchemeHTTP, + expected: "2001:db8::1", + }, + { + name: "valid etcd probe using listen-client-urls IPv4 addresses", + cfg: &kubeadmapi.MasterConfiguration{ + Etcd: kubeadmapi.Etcd{ + ExtraArgs: map[string]string{ + "listen-client-urls": "http://1.2.3.4:2379,http://4.3.2.1:2379"}, + }, + }, + component: kubeadmconstants.Etcd, + port: 1, + path: "foo", + scheme: v1.URISchemeHTTP, + expected: "1.2.3.4", + }, + { + name: "valid etcd probe using listen-client-urls IPv6 addresses", + cfg: &kubeadmapi.MasterConfiguration{ + Etcd: kubeadmapi.Etcd{ + ExtraArgs: map[string]string{ + "listen-client-urls": "http://[2001:db8::1]:2379,http://[2001:db8::2]:2379"}, + }, + }, + component: kubeadmconstants.Etcd, + port: 1, + path: "foo", + scheme: v1.URISchemeHTTP, + expected: "2001:db8::1", + }, + { + name: "valid IPv4 etcd probe using hostname for listen-client-urls", + cfg: &kubeadmapi.MasterConfiguration{ + Etcd: kubeadmapi.Etcd{ + ExtraArgs: map[string]string{ + "listen-client-urls": "http://localhost:2379"}, + }, + }, + component: kubeadmconstants.Etcd, + port: 1, + path: "foo", + scheme: v1.URISchemeHTTP, + expected: "127.0.0.1", }, } for _, rt := range tests { - actual := ComponentProbe(rt.port, rt.path, rt.scheme) + actual := ComponentProbe(rt.cfg, rt.component, rt.port, rt.path, rt.scheme) + if actual.Handler.HTTPGet.Host != rt.expected { + t.Errorf("%s test case failed:\n\texpected: %s\n\t actual: %s", + rt.name, rt.expected, + actual.Handler.HTTPGet.Host) + } if actual.Handler.HTTPGet.Port != intstr.FromInt(rt.port) { - t.Errorf( - "failed componentProbe:\n\texpected: %v\n\t actual: %v", - rt.port, - actual.Handler.HTTPGet.Port, - ) + t.Errorf("%s test case failed:\n\texpected: %v\n\t actual: %v", + rt.name, rt.port, + actual.Handler.HTTPGet.Port) } if actual.Handler.HTTPGet.Path != rt.path { - t.Errorf( - "failed componentProbe:\n\texpected: %s\n\t actual: %s", - rt.path, - actual.Handler.HTTPGet.Path, - ) + t.Errorf("%s test case failed:\n\texpected: %s\n\t actual: %s", + rt.name, rt.path, + actual.Handler.HTTPGet.Path) } if actual.Handler.HTTPGet.Scheme != rt.scheme { - t.Errorf( - "failed componentProbe:\n\texpected: %v\n\t actual: %v", - rt.scheme, - actual.Handler.HTTPGet.Scheme, - ) + t.Errorf("%s test case failed:\n\texpected: %v\n\t actual: %v", + rt.name, rt.scheme, + actual.Handler.HTTPGet.Scheme) } } } @@ -111,7 +245,7 @@ func TestComponentPod(t *testing.T) { for _, rt := range tests { c := v1.Container{Name: rt.name} - actual := ComponentPod(c, []v1.Volume{}) + actual := ComponentPod(c, map[string]v1.Volume{}) if !reflect.DeepEqual(rt.expected, actual) { t.Errorf( "failed componentPod:\n\texpected: %v\n\t actual: %v", @@ -198,6 +332,35 @@ func TestNewVolumeMount(t *testing.T) { } } } +func TestVolumeMapToSlice(t *testing.T) { + testVolumes := map[string]v1.Volume{ + "foo": { + Name: "foo", + }, + } + volumeSlice := VolumeMapToSlice(testVolumes) + if len(volumeSlice) != 1 { + t.Errorf("Expected slice length of 1, got %d", len(volumeSlice)) + } + if volumeSlice[0].Name != "foo" { + t.Errorf("Expected volume name \"foo\", got %s", volumeSlice[0].Name) + } +} + +func TestVolumeMountMapToSlice(t *testing.T) { + testVolumeMounts := map[string]v1.VolumeMount{ + "foo": { + Name: "foo", + }, + } + volumeMountSlice := VolumeMountMapToSlice(testVolumeMounts) + if len(volumeMountSlice) != 1 { + t.Errorf("Expected slice length of 1, got %d", len(volumeMountSlice)) + } + if volumeMountSlice[0].Name != "foo" { + t.Errorf("Expected volume mount name \"foo\", got %s", volumeMountSlice[0].Name) + } +} func TestGetExtraParameters(t *testing.T) { var tests = []struct { diff --git a/cmd/kubeadm/app/util/template_test.go b/cmd/kubeadm/app/util/template_test.go index 3a00e05e601..ed5fee9c330 100644 --- a/cmd/kubeadm/app/util/template_test.go +++ b/cmd/kubeadm/app/util/template_test.go @@ -21,9 +21,9 @@ import ( ) const ( - validTmpl = "image: {{ .ImageRepository }}/pause-{{ .Arch }}:3.0" - validTmplOut = "image: gcr.io/google_containers/pause-amd64:3.0" - doNothing = "image: gcr.io/google_containers/pause-amd64:3.0" + validTmpl = "image: {{ .ImageRepository }}/pause-{{ .Arch }}:3.1" + validTmplOut = "image: gcr.io/google_containers/pause-amd64:3.1" + doNothing = "image: gcr.io/google_containers/pause-amd64:3.1" invalidTmpl1 = "{{ .baz }/d}" invalidTmpl2 = "{{ !foobar }}" ) diff --git a/cmd/kubeadm/app/util/token/BUILD b/cmd/kubeadm/app/util/token/BUILD index 6ec7aad0c6e..e5268563799 100644 --- a/cmd/kubeadm/app/util/token/BUILD +++ b/cmd/kubeadm/app/util/token/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["tokens_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/token", - library = ":go_default_library", deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"], ) diff --git a/cmd/kubeadm/kubeadm.go b/cmd/kubeadm/kubeadm.go index 6aca5a18f3d..233b13f5cbc 100644 --- a/cmd/kubeadm/kubeadm.go +++ b/cmd/kubeadm/kubeadm.go @@ -17,6 +17,7 @@ limitations under the License. package main import ( + "fmt" "os" "k8s.io/kubernetes/cmd/kubeadm/app" @@ -24,6 +25,7 @@ import ( func main() { if err := app.Run(); err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } os.Exit(0) diff --git a/cmd/kubeadm/test/cmd/BUILD b/cmd/kubeadm/test/cmd/BUILD index ce5920d53e3..e9334489056 100644 --- a/cmd/kubeadm/test/cmd/BUILD +++ b/cmd/kubeadm/test/cmd/BUILD @@ -24,8 +24,8 @@ go_test( ], args = ["--kubeadm-path=../../kubeadm"], data = ["//cmd/kubeadm"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubeadm/test/cmd", - library = ":go_default_library", tags = [ "integration", "skip", diff --git a/cmd/kubectl/BUILD b/cmd/kubectl/BUILD index 1e26c979678..74501eed97b 100644 --- a/cmd/kubectl/BUILD +++ b/cmd/kubectl/BUILD @@ -7,19 +7,9 @@ load("//pkg/version:def.bzl", "version_x_defs") go_binary( name = "kubectl", - gc_linkopts = select({ - # Mac OS X doesn't support static binaries: - # https://developer.apple.com/library/content/qa/qa1118/_index.html - "@io_bazel_rules_go//go/platform:darwin_amd64": [], - "//conditions:default": [ - "-linkmode", - "external", - "-extldflags", - "-static", - ], - }), + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubectl", - library = ":go_default_library", + pure = "on", visibility = ["//visibility:public"], x_defs = version_x_defs(), ) diff --git a/cmd/kubectl/kubectl.go b/cmd/kubectl/kubectl.go index 3fce17f4086..5864a5ae68a 100644 --- a/cmd/kubectl/kubectl.go +++ b/cmd/kubectl/kubectl.go @@ -17,6 +17,7 @@ limitations under the License. package main import ( + "fmt" "os" "k8s.io/kubernetes/cmd/kubectl/app" @@ -24,6 +25,7 @@ import ( func main() { if err := app.Run(); err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } os.Exit(0) diff --git a/cmd/kubelet/BUILD b/cmd/kubelet/BUILD index fbcb8a7f6d3..a5b72a1e9ec 100644 --- a/cmd/kubelet/BUILD +++ b/cmd/kubelet/BUILD @@ -9,8 +9,8 @@ load("//pkg/version:def.bzl", "version_x_defs") go_binary( name = "kubelet", + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubelet", - library = ":go_default_library", x_defs = version_x_defs(), ) diff --git a/cmd/kubelet/app/BUILD b/cmd/kubelet/app/BUILD index 53bf54f285d..8bb2d3f58bc 100644 --- a/cmd/kubelet/app/BUILD +++ b/cmd/kubelet/app/BUILD @@ -9,9 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["server_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubelet/app", - library = ":go_default_library", - deps = ["//pkg/kubelet/apis/kubeletconfig:go_default_library"], ) go_library( @@ -20,17 +19,47 @@ go_library( "auth.go", "plugins.go", "server.go", - "server_unsupported.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "server_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "server_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "server_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "server_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ "server_linux.go", ], + "@io_bazel_rules_go//go/platform:nacl": [ + "server_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "server_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "server_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "server_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "server_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "server_unsupported.go", + ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/cmd/kubelet/app", deps = [ "//cmd/kubelet/app/options:go_default_library", - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/capabilities:go_default_library", "//pkg/client/chaosclient:go_default_library", "//pkg/cloudprovider:go_default_library", @@ -52,7 +81,6 @@ go_library( "//pkg/kubelet/config:go_default_library", "//pkg/kubelet/container:go_default_library", "//pkg/kubelet/dockershim:go_default_library", - "//pkg/kubelet/dockershim/libdocker:go_default_library", "//pkg/kubelet/dockershim/remote:go_default_library", "//pkg/kubelet/eviction:go_default_library", "//pkg/kubelet/eviction/api:go_default_library", @@ -78,6 +106,7 @@ go_library( "//pkg/volume/cephfs:go_default_library", "//pkg/volume/cinder:go_default_library", "//pkg/volume/configmap:go_default_library", + "//pkg/volume/csi:go_default_library", "//pkg/volume/downwardapi:go_default_library", "//pkg/volume/empty_dir:go_default_library", "//pkg/volume/fc:go_default_library", @@ -126,7 +155,7 @@ go_library( "//vendor/k8s.io/client-go/util/cert:go_default_library", "//vendor/k8s.io/client-go/util/certificate:go_default_library", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//vendor/golang.org/x/exp/inotify:go_default_library", ], "//conditions:default": [], diff --git a/cmd/kubelet/app/options/BUILD b/cmd/kubelet/app/options/BUILD index 91300fda06f..aca4a34882f 100644 --- a/cmd/kubelet/app/options/BUILD +++ b/cmd/kubelet/app/options/BUILD @@ -15,12 +15,14 @@ go_library( importpath = "k8s.io/kubernetes/cmd/kubelet/app/options", deps = [ "//pkg/apis/componentconfig:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/features:go_default_library", "//pkg/kubelet/apis/kubeletconfig:go_default_library", "//pkg/kubelet/apis/kubeletconfig/scheme:go_default_library", "//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library", "//pkg/kubelet/apis/kubeletconfig/validation:go_default_library", "//pkg/kubelet/config:go_default_library", + "//pkg/kubelet/types:go_default_library", "//pkg/util/taints:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -45,8 +47,8 @@ filegroup( go_test( name = "go_default_test", srcs = ["options_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubelet/app/options", - library = ":go_default_library", deps = [ "//vendor/github.com/spf13/pflag:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", diff --git a/cmd/kubelet/app/options/container_runtime.go b/cmd/kubelet/app/options/container_runtime.go index 00c46fe57b5..b57a4e20bca 100644 --- a/cmd/kubelet/app/options/container_runtime.go +++ b/cmd/kubelet/app/options/container_runtime.go @@ -22,12 +22,13 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/kubernetes/pkg/kubelet/config" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" ) const ( - // When these values are updated, also update test/e2e/framework/util.go + // When these values are updated, also update test/utils/image/manifest.go defaultPodSandboxImageName = "gcr.io/google_containers/pause" - defaultPodSandboxImageVersion = "3.0" + defaultPodSandboxImageVersion = "3.1" // From pkg/kubelet/rkt/rkt.go to avoid circular import defaultRktAPIServiceEndpoint = "localhost:15441" ) @@ -47,6 +48,7 @@ func NewContainerRuntimeOptions() *config.ContainerRuntimeOptions { } return &config.ContainerRuntimeOptions{ + ContainerRuntime: kubetypes.DockerContainerRuntime, DockerEndpoint: dockerEndpoint, DockershimRootDirectory: "/var/lib/dockershim", DockerDisableSharedPID: true, diff --git a/cmd/kubelet/app/options/options.go b/cmd/kubelet/app/options/options.go index 539e3785b16..a0562b20c33 100644 --- a/cmd/kubelet/app/options/options.go +++ b/cmd/kubelet/app/options/options.go @@ -20,6 +20,7 @@ package options import ( "fmt" _ "net/http/pprof" + "path/filepath" "runtime" "strings" @@ -29,6 +30,7 @@ import ( utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/apiserver/pkg/util/flag" "k8s.io/kubernetes/pkg/apis/componentconfig" + "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" kubeletscheme "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme" @@ -99,11 +101,19 @@ type KubeletFlags struct { // To use this flag, the DynamicKubeletConfig feature gate must be enabled. DynamicConfigDir flag.StringFlag - // The Kubelet will look in this directory for an init configuration. + // The Kubelet will load its initial configuration from this file. // The path may be absolute or relative; relative paths are under the Kubelet's current working directory. // Omit this flag to use the combination of built-in default configuration values and flags. - // To use this flag, the DynamicKubeletConfig feature gate must be enabled. - InitConfigDir flag.StringFlag + // To use this flag, the KubeletConfigFile feature gate must be enabled. + KubeletConfigFile flag.StringFlag + + // registerNode enables automatic registration with the apiserver. + RegisterNode bool + + // 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. + RegisterWithTaints []core.Taint // EXPERIMENTAL FLAGS // Whitelist of unsafe sysctls or sysctl patterns (ending in *). @@ -130,7 +140,26 @@ type KubeletFlags struct { // A set of ResourceName=Percentage (e.g. memory=50%) pairs that describe // how pod resource requests are reserved at the QoS level. // Currently only memory is supported. [default=none]" - ExperimentalQOSReserved kubeletconfig.ConfigurationMap + ExperimentalQOSReserved map[string]string + // Node Labels are the node labels to add when registering the node in the cluster + NodeLabels map[string]string + // volumePluginDir is the full path of the directory in which to search + // for additional third party volume plugins + VolumePluginDir string + // lockFilePath is the path that kubelet will use to as a lock file. + // It uses this file as a lock to synchronize with other kubelet processes + // that may be running. + LockFilePath string + // ExitOnLockContention is a flag that signifies to the kubelet that it is running + // in "bootstrap" mode. This requires that 'LockFilePath' has been set. + // This will cause the kubelet to listen to inotify events on the lock file, + // releasing it and exiting when another process tries to open that file. + ExitOnLockContention bool + // seccompProfileRoot is the directory path for seccomp profiles. + SeccompProfileRoot string + // bootstrapCheckpointPath is the path to the directory containing pod checkpoints to + // run on restore + BootstrapCheckpointPath string // DEPRECATED FLAGS // minimumGCAge is the minimum age for a finished container before it is @@ -156,6 +185,8 @@ type KubeletFlags struct { // This flag, if set, instructs the kubelet to keep volumes from terminated pods mounted to the node. // This can be useful for debugging volume related issues. KeepTerminatedPodVolumes bool + // enable gathering custom metrics. + EnableCustomMetrics bool } // NewKubeletFlags will create a new KubeletFlags with default values @@ -184,6 +215,12 @@ func NewKubeletFlags() *KubeletFlags { ExperimentalQOSReserved: make(map[string]string), RemoteRuntimeEndpoint: remoteRuntimeEndpoint, RotateCertificates: false, + // TODO(#54161:v1.11.0): Remove --enable-custom-metrics flag, it is deprecated. + EnableCustomMetrics: false, + NodeLabels: make(map[string]string), + VolumePluginDir: "/usr/libexec/kubernetes/kubelet-plugins/volume/exec/", + RegisterNode: true, + SeccompProfileRoot: filepath.Join(v1alpha1.DefaultRootDir, "seccomp"), } } @@ -192,9 +229,9 @@ func ValidateKubeletFlags(f *KubeletFlags) error { if f.DynamicConfigDir.Provided() && !utilfeature.DefaultFeatureGate.Enabled(features.DynamicKubeletConfig) { return fmt.Errorf("the DynamicKubeletConfig feature gate must be enabled in order to use the --dynamic-config-dir flag") } - // ensure that nobody sets InitConfigDir if the dynamic config feature gate is turned off - if f.InitConfigDir.Provided() && !utilfeature.DefaultFeatureGate.Enabled(features.KubeletConfigFile) { - return fmt.Errorf("the KubeletConfigFile feature gate must be enabled in order to use the --init-config-dir flag") + // ensure that nobody sets KubeletConfigFile if the KubeletConfigFile feature gate is turned off + if f.KubeletConfigFile.Provided() && !utilfeature.DefaultFeatureGate.Enabled(features.KubeletConfigFile) { + return fmt.Errorf("the KubeletConfigFile feature gate must be enabled in order to use the --config flag") } return nil } @@ -288,7 +325,10 @@ func (f *KubeletFlags) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&f.RootDirectory, "root-dir", f.RootDirectory, "Directory path for managing kubelet files (volume mounts,etc).") fs.Var(&f.DynamicConfigDir, "dynamic-config-dir", "The Kubelet will use this directory for checkpointing downloaded configurations and tracking configuration health. The Kubelet will create this directory if it does not already exist. The path may be absolute or relative; relative paths start at the Kubelet's current working directory. Providing this flag enables dynamic Kubelet configuration. Presently, you must also enable the DynamicKubeletConfig feature gate to pass this flag.") - fs.Var(&f.InitConfigDir, "init-config-dir", "The Kubelet will look in this directory for the init configuration. The path may be absolute or relative; relative paths start at the Kubelet's current working directory. Omit this argument to use the built-in default configuration values. Presently, you must also enable the DynamicKubeletConfig feature gate to pass this flag.") + fs.Var(&f.KubeletConfigFile, "config", "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. You must also enable the KubeletConfigFile feature gate to pass this flag.") + + fs.BoolVar(&f.RegisterNode, "register-node", f.RegisterNode, "Register the node with the apiserver. If --kubeconfig is not provided, this flag is irrelevant, as the Kubelet won't have an apiserver to register with. Default=true.") + fs.Var(utiltaints.NewTaintsVar(&f.RegisterWithTaints), "register-with-taints", "Register the node with the given list of taints (comma separated \"=:\"). No-op if register-node is false.") // EXPERIMENTAL FLAGS fs.StringVar(&f.ExperimentalMounterPath, "experimental-mounter-path", f.ExperimentalMounterPath, "[Experimental] Path of mounter binary. Leave empty to use the default mount.") @@ -299,7 +339,14 @@ func (f *KubeletFlags) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&f.RemoteImageEndpoint, "image-service-endpoint", f.RemoteImageEndpoint, "[Experimental] The endpoint of remote image service. If not specified, it will be the same with container-runtime-endpoint by default. Currently unix socket is supported on Linux, and tcp is supported on windows. Examples:'unix:///var/run/dockershim.sock', 'tcp://localhost:3735'") fs.BoolVar(&f.ExperimentalCheckNodeCapabilitiesBeforeMount, "experimental-check-node-capabilities-before-mount", f.ExperimentalCheckNodeCapabilitiesBeforeMount, "[Experimental] if set true, the kubelet will check the underlying node for required componenets (binaries, etc.) before performing the mount") fs.BoolVar(&f.ExperimentalNodeAllocatableIgnoreEvictionThreshold, "experimental-allocatable-ignore-eviction", f.ExperimentalNodeAllocatableIgnoreEvictionThreshold, "When set to 'true', Hard Eviction Thresholds will be ignored while calculating Node Allocatable. See https://kubernetes.io/docs/tasks/administer-cluster/reserve-compute-resources/ for more details. [default=false]") - fs.Var(&f.ExperimentalQOSReserved, "experimental-qos-reserved", "A set of ResourceName=Percentage (e.g. memory=50%) pairs that describe how pod resource requests are reserved at the QoS level. Currently only memory is supported. [default=none]") + fs.Var(flag.NewMapStringString(&f.ExperimentalQOSReserved), "experimental-qos-reserved", "A set of ResourceName=Percentage (e.g. memory=50%) pairs that describe how pod resource requests are reserved at the QoS level. Currently only memory is supported. [default=none]") + bindableNodeLabels := flag.ConfigurationMap(f.NodeLabels) + fs.Var(&bindableNodeLabels, "node-labels", " Labels to add when registering the node in the cluster. Labels must be key=value pairs separated by ','.") + fs.StringVar(&f.VolumePluginDir, "volume-plugin-dir", f.VolumePluginDir, " The full path of the directory in which to search for additional third party volume plugins") + fs.StringVar(&f.LockFilePath, "lock-file", f.LockFilePath, " The path to file for kubelet to use as a lock file.") + fs.BoolVar(&f.ExitOnLockContention, "exit-on-lock-contention", f.ExitOnLockContention, "Whether kubelet should exit upon lock-file contention.") + fs.StringVar(&f.SeccompProfileRoot, "seccomp-profile-root", f.SeccompProfileRoot, " Directory path for seccomp profiles.") + fs.StringVar(&f.BootstrapCheckpointPath, "bootstrap-checkpoint-path", f.BootstrapCheckpointPath, " Path to to the directory where the checkpoints are stored") // DEPRECATED FLAGS fs.DurationVar(&f.MinimumGCAge.Duration, "minimum-container-ttl-duration", f.MinimumGCAge.Duration, "Minimum age for a finished container before it is garbage collected. Examples: '300ms', '10s' or '2h45m'") @@ -316,13 +363,16 @@ func (f *KubeletFlags) AddFlags(fs *pflag.FlagSet) { fs.MarkDeprecated("non-masquerade-cidr", "will be removed in a future version") fs.BoolVar(&f.KeepTerminatedPodVolumes, "keep-terminated-pod-volumes", f.KeepTerminatedPodVolumes, "Keep terminated pod volumes mounted to the node after the pod terminates. Can be useful for debugging volume related issues.") fs.MarkDeprecated("keep-terminated-pod-volumes", "will be removed in a future version") + // TODO(#54161:v1.11.0): Remove --enable-custom-metrics flag, it is deprecated. + fs.BoolVar(&f.EnableCustomMetrics, "enable-custom-metrics", f.EnableCustomMetrics, "Support for gathering custom metrics.") + fs.MarkDeprecated("enable-custom-metrics", "will be removed in a future version") } // AddKubeletConfigFlags adds flags for a specific kubeletconfig.KubeletConfiguration to the specified FlagSet func AddKubeletConfigFlags(fs *pflag.FlagSet, c *kubeletconfig.KubeletConfiguration) { - fs.BoolVar(&c.FailSwapOn, "fail-swap-on", true, "Makes the Kubelet fail to start if swap is enabled on the node. ") - fs.BoolVar(&c.FailSwapOn, "experimental-fail-swap-on", true, "DEPRECATED: please use --fail-swap-on instead.") + fs.BoolVar(&c.FailSwapOn, "fail-swap-on", c.FailSwapOn, "Makes the Kubelet fail to start if swap is enabled on the node. ") + fs.BoolVar(&c.FailSwapOn, "experimental-fail-swap-on", c.FailSwapOn, "DEPRECATED: please use --fail-swap-on instead.") fs.MarkDeprecated("experimental-fail-swap-on", "This flag is deprecated and will be removed in future releases. please use --fail-swap-on instead.") fs.StringVar(&c.PodManifestPath, "pod-manifest-path", c.PodManifestPath, "Path to the directory containing pod manifest files to run, or the path to a single pod manifest file. Files starting with dots will be ignored.") @@ -330,9 +380,9 @@ func AddKubeletConfigFlags(fs *pflag.FlagSet, c *kubeletconfig.KubeletConfigurat fs.DurationVar(&c.FileCheckFrequency.Duration, "file-check-frequency", c.FileCheckFrequency.Duration, "Duration between checking config files for new data") fs.DurationVar(&c.HTTPCheckFrequency.Duration, "http-check-frequency", c.HTTPCheckFrequency.Duration, "Duration between checking http for new data") fs.StringVar(&c.ManifestURL, "manifest-url", c.ManifestURL, "URL for accessing the container manifest") - fs.StringVar(&c.ManifestURLHeader, "manifest-url-header", c.ManifestURLHeader, "HTTP header to use when accessing the manifest URL, with the key separated from the value with a ':', as in 'key:value'") + fs.Var(flag.NewColonSeparatedMultimapStringString(&c.ManifestURLHeader), "manifest-url-header", "Comma-separated list of HTTP headers to use when accessing the manifest URL. Multiple headers with the same name will be added in the same order provided. This flag can be repeatedly invoked. For example: `--manifest-url-header 'a:hello,b:again,c:world' --manifest-url-header 'b:beautiful'`") fs.BoolVar(&c.EnableServer, "enable-server", c.EnableServer, "Enable the Kubelet's server") - fs.Var(componentconfig.IPVar{Val: &c.Address}, "address", "The IP address for the Kubelet to serve on (set to 0.0.0.0 for all interfaces)") + fs.Var(componentconfig.IPVar{Val: &c.Address}, "address", "The IP address for the Kubelet to serve on (set to `0.0.0.0` for all IPv4 interfaces and `::` for all IPv6 interfaces)") fs.Int32Var(&c.Port, "port", c.Port, "The port for the Kubelet to serve on.") fs.Int32Var(&c.ReadOnlyPort, "read-only-port", c.ReadOnlyPort, "The read-only port for the Kubelet to serve on with no authentication/authorization (set to 0 to disable)") @@ -364,7 +414,6 @@ func AddKubeletConfigFlags(fs *pflag.FlagSet, c *kubeletconfig.KubeletConfigurat "are generated for the public address and saved to the directory passed to --cert-dir.") fs.StringVar(&c.TLSPrivateKeyFile, "tls-private-key-file", c.TLSPrivateKeyFile, "File containing x509 private key matching --tls-cert-file.") - fs.StringVar(&c.SeccompProfileRoot, "seccomp-profile-root", c.SeccompProfileRoot, "Directory path for seccomp profiles.") fs.BoolVar(&c.AllowPrivileged, "allow-privileged", c.AllowPrivileged, "If true, allow containers to request privileged mode.") fs.StringSliceVar(&c.HostNetworkSources, "host-network-sources", c.HostNetworkSources, "Comma-separated list of sources from which the Kubelet allows pods to use of host network.") fs.StringSliceVar(&c.HostPIDSources, "host-pid-sources", c.HostPIDSources, "Comma-separated list of sources from which the Kubelet allows pods to use the host pid namespace.") @@ -375,26 +424,21 @@ func AddKubeletConfigFlags(fs *pflag.FlagSet, c *kubeletconfig.KubeletConfigurat fs.Int32Var(&c.EventBurst, "event-burst", c.EventBurst, "Maximum size of a bursty event records, temporarily allows event records to burst to this number, while still not exceeding event-qps. Only used if --event-qps > 0") fs.BoolVar(&c.EnableDebuggingHandlers, "enable-debugging-handlers", c.EnableDebuggingHandlers, "Enables server endpoints for log collection and local running of containers and commands") - fs.BoolVar(&c.EnableContentionProfiling, "contention-profiling", false, "Enable lock contention profiling, if profiling is enabled") + fs.BoolVar(&c.EnableContentionProfiling, "contention-profiling", c.EnableContentionProfiling, "Enable lock contention profiling, if profiling is enabled") fs.Int32Var(&c.CAdvisorPort, "cadvisor-port", c.CAdvisorPort, "The port of the localhost cAdvisor endpoint (set to 0 to disable)") fs.Int32Var(&c.HealthzPort, "healthz-port", c.HealthzPort, "The port of the localhost healthz endpoint (set to 0 to disable)") - fs.Var(componentconfig.IPVar{Val: &c.HealthzBindAddress}, "healthz-bind-address", "The IP address for the healthz server to serve on. (set to 0.0.0.0 for all interfaces)") + fs.Var(componentconfig.IPVar{Val: &c.HealthzBindAddress}, "healthz-bind-address", "The IP address for the healthz server to serve on (set to `0.0.0.0` for all IPv4 interfaces and `::` for all IPv6 interfaces)") fs.Int32Var(&c.OOMScoreAdj, "oom-score-adj", c.OOMScoreAdj, "The oom-score-adj value for kubelet process. Values must be within the range [-1000, 1000]") - fs.BoolVar(&c.RegisterNode, "register-node", c.RegisterNode, "Register the node with the apiserver. If --kubeconfig is not provided, this flag is irrelevant, as the Kubelet won't have an apiserver to register with. Default=true.") fs.StringVar(&c.ClusterDomain, "cluster-domain", c.ClusterDomain, "Domain for this cluster. If set, kubelet will configure all containers to search this domain in addition to the host's search domains") fs.StringSliceVar(&c.ClusterDNS, "cluster-dns", c.ClusterDNS, "Comma-separated list of DNS server IP address. This value is used for containers DNS server in case of Pods with \"dnsPolicy=ClusterFirst\". Note: all DNS servers appearing in the list MUST serve the same set of records otherwise name resolution within the cluster may not work correctly. There is no guarantee as to which DNS server may be contacted for name resolution.") fs.DurationVar(&c.StreamingConnectionIdleTimeout.Duration, "streaming-connection-idle-timeout", c.StreamingConnectionIdleTimeout.Duration, "Maximum time a streaming connection can be idle before the connection is automatically closed. 0 indicates no timeout. Example: '5m'") fs.DurationVar(&c.NodeStatusUpdateFrequency.Duration, "node-status-update-frequency", c.NodeStatusUpdateFrequency.Duration, "Specifies how often kubelet posts node status to master. Note: be cautious when changing the constant, it must work with nodeMonitorGracePeriod in nodecontroller.") - c.NodeLabels = make(map[string]string) - bindableNodeLabels := flag.ConfigurationMap(c.NodeLabels) - fs.Var(&bindableNodeLabels, "node-labels", " Labels to add when registering the node in the cluster. Labels must be key=value pairs separated by ','.") fs.DurationVar(&c.ImageMinimumGCAge.Duration, "minimum-image-ttl-duration", c.ImageMinimumGCAge.Duration, "Minimum age for an unused image before it is garbage collected. Examples: '300ms', '10s' or '2h45m'.") fs.Int32Var(&c.ImageGCHighThresholdPercent, "image-gc-high-threshold", c.ImageGCHighThresholdPercent, "The percent of disk usage after which image garbage collection is always run.") fs.Int32Var(&c.ImageGCLowThresholdPercent, "image-gc-low-threshold", c.ImageGCLowThresholdPercent, "The percent of disk usage before which image garbage collection is never run. Lowest disk usage to garbage collect to.") fs.DurationVar(&c.VolumeStatsAggPeriod.Duration, "volume-stats-agg-period", c.VolumeStatsAggPeriod.Duration, "Specifies interval for kubelet to calculate and cache the volume disk usage for all pods and volumes. To disable volume calculations, set to 0.") - fs.StringVar(&c.VolumePluginDir, "volume-plugin-dir", c.VolumePluginDir, " The full path of the directory in which to search for additional third party volume plugins") - fs.Var(flag.MapStringBool(c.FeatureGates), "feature-gates", "A set of key=value pairs that describe feature gates for alpha/experimental features. "+ + fs.Var(flag.NewMapStringBool(&c.FeatureGates), "feature-gates", "A set of key=value pairs that describe feature gates for alpha/experimental features. "+ "Options are:\n"+strings.Join(utilfeature.DefaultFeatureGate.KnownFeatures(), "\n")) fs.StringVar(&c.KubeletCgroups, "kubelet-cgroups", c.KubeletCgroups, "Optional absolute name of cgroups to create and run the Kubelet in.") fs.StringVar(&c.SystemCgroups, "system-cgroups", c.SystemCgroups, "Optional absolute name of cgroups in which to place all non-kernel processes that are not already inside a cgroup under `/`. Empty for no container. Rolling back the flag requires a reboot.") @@ -404,14 +448,11 @@ func AddKubeletConfigFlags(fs *pflag.FlagSet, c *kubeletconfig.KubeletConfigurat fs.StringVar(&c.CgroupRoot, "cgroup-root", c.CgroupRoot, "Optional root cgroup to use for pods. This is handled by the container runtime on a best effort basis. Default: '', which means use the container runtime default.") fs.StringVar(&c.CPUManagerPolicy, "cpu-manager-policy", c.CPUManagerPolicy, " CPU Manager policy to use. Possible values: 'none', 'static'. Default: 'none'") fs.DurationVar(&c.CPUManagerReconcilePeriod.Duration, "cpu-manager-reconcile-period", c.CPUManagerReconcilePeriod.Duration, " CPU Manager reconciliation period. Examples: '10s', or '1m'. If not supplied, defaults to `NodeStatusUpdateFrequency`") - fs.StringVar(&c.ContainerRuntime, "container-runtime", c.ContainerRuntime, "The container runtime to use. Possible values: 'docker', 'rkt'.") fs.DurationVar(&c.RuntimeRequestTimeout.Duration, "runtime-request-timeout", c.RuntimeRequestTimeout.Duration, "Timeout of all runtime requests except long running request - pull, logs, exec and attach. When timeout exceeded, kubelet will cancel the request, throw out an error and retry later.") - fs.StringVar(&c.LockFilePath, "lock-file", c.LockFilePath, " The path to file for kubelet to use as a lock file.") - fs.BoolVar(&c.ExitOnLockContention, "exit-on-lock-contention", c.ExitOnLockContention, "Whether kubelet should exit upon lock-file contention.") fs.StringVar(&c.HairpinMode, "hairpin-mode", c.HairpinMode, "How should the kubelet setup hairpin NAT. This allows endpoints of a Service to loadbalance back to themselves if they should try to access their own Service. Valid values are \"promiscuous-bridge\", \"hairpin-veth\" and \"none\".") fs.Int32Var(&c.MaxPods, "max-pods", c.MaxPods, "Number of Pods that can run on this Kubelet.") - fs.StringVar(&c.PodCIDR, "pod-cidr", "", "The CIDR to use for pod IP addresses, only used in standalone mode. In cluster mode, this is obtained from the master.") + fs.StringVar(&c.PodCIDR, "pod-cidr", c.PodCIDR, "The CIDR to use for pod IP addresses, only used in standalone mode. In cluster mode, this is obtained from the master.") fs.StringVar(&c.ResolverConfig, "resolv-conf", c.ResolverConfig, "Resolver configuration file used as the basis for the container DNS resolution configuration.") fs.BoolVar(&c.CPUCFSQuota, "cpu-cfs-quota", c.CPUCFSQuota, "Enable CPU CFS quota enforcement for containers that specify CPU limits") 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") @@ -422,26 +463,23 @@ func AddKubeletConfigFlags(fs *pflag.FlagSet, c *kubeletconfig.KubeletConfigurat // Flags intended for testing, not recommended used in production environments. fs.Int64Var(&c.MaxOpenFiles, "max-open-files", c.MaxOpenFiles, "Number of files that can be opened by Kubelet process.") - fs.Var(utiltaints.NewTaintsVar(&c.RegisterWithTaints), "register-with-taints", "Register the node with the given list of taints (comma separated \"=:\"). No-op if register-node is false.") fs.StringVar(&c.ContentType, "kube-api-content-type", c.ContentType, "Content type of requests sent to apiserver.") fs.Int32Var(&c.KubeAPIQPS, "kube-api-qps", c.KubeAPIQPS, "QPS to use while talking with kubernetes apiserver") fs.Int32Var(&c.KubeAPIBurst, "kube-api-burst", c.KubeAPIBurst, "Burst to use while talking with kubernetes apiserver") fs.BoolVar(&c.SerializeImagePulls, "serialize-image-pulls", c.SerializeImagePulls, "Pull images one at a time. We recommend *not* changing the default value on nodes that run docker daemon with version < 1.9 or an Aufs storage backend. Issue #10959 has more details.") - fs.BoolVar(&c.EnableCustomMetrics, "enable-custom-metrics", c.EnableCustomMetrics, "Support for gathering custom metrics.") - fs.StringVar(&c.RuntimeCgroups, "runtime-cgroups", c.RuntimeCgroups, "Optional absolute name of cgroups to create and run the runtime in.") - fs.StringVar(&c.EvictionHard, "eviction-hard", c.EvictionHard, "A set of eviction thresholds (e.g. memory.available<1Gi) that if met would trigger a pod eviction.") - fs.StringVar(&c.EvictionSoft, "eviction-soft", c.EvictionSoft, "A set of eviction thresholds (e.g. memory.available<1.5Gi) that if met over a corresponding grace period would trigger a pod eviction.") - fs.StringVar(&c.EvictionSoftGracePeriod, "eviction-soft-grace-period", c.EvictionSoftGracePeriod, "A set of eviction grace periods (e.g. memory.available=1m30s) that correspond to how long a soft eviction threshold must hold before triggering a pod eviction.") + fs.Var(flag.NewLangleSeparatedMapStringString(&c.EvictionHard), "eviction-hard", "A set of eviction thresholds (e.g. memory.available<1Gi) that if met would trigger a pod eviction.") + fs.Var(flag.NewLangleSeparatedMapStringString(&c.EvictionSoft), "eviction-soft", "A set of eviction thresholds (e.g. memory.available<1.5Gi) that if met over a corresponding grace period would trigger a pod eviction.") + fs.Var(flag.NewMapStringString(&c.EvictionSoftGracePeriod), "eviction-soft-grace-period", "A set of eviction grace periods (e.g. memory.available=1m30s) that correspond to how long a soft eviction threshold must hold before triggering a pod eviction.") fs.DurationVar(&c.EvictionPressureTransitionPeriod.Duration, "eviction-pressure-transition-period", c.EvictionPressureTransitionPeriod.Duration, "Duration for which the kubelet has to wait before transitioning out of an eviction pressure condition.") fs.Int32Var(&c.EvictionMaxPodGracePeriod, "eviction-max-pod-grace-period", c.EvictionMaxPodGracePeriod, "Maximum allowed grace period (in seconds) to use when terminating pods in response to a soft eviction threshold being met. If negative, defer to pod specified value.") - fs.StringVar(&c.EvictionMinimumReclaim, "eviction-minimum-reclaim", c.EvictionMinimumReclaim, "A set of minimum reclaims (e.g. imagefs.available=2Gi) that describes the minimum amount of resource the kubelet will reclaim when performing a pod eviction if that resource is under pressure.") + fs.Var(flag.NewMapStringString(&c.EvictionMinimumReclaim), "eviction-minimum-reclaim", "A set of minimum reclaims (e.g. imagefs.available=2Gi) that describes the minimum amount of resource the kubelet will reclaim when performing a pod eviction if that resource is under pressure.") fs.Int32Var(&c.PodsPerCore, "pods-per-core", c.PodsPerCore, "Number of Pods per core that can run on this Kubelet. The total number of Pods on this Kubelet cannot exceed max-pods, so max-pods will be used if this calculation results in a larger number of Pods allowed on the Kubelet. A value of 0 disables this limit.") fs.BoolVar(&c.ProtectKernelDefaults, "protect-kernel-defaults", c.ProtectKernelDefaults, "Default kubelet behaviour for kernel tuning. If set, kubelet errors if any of kernel tunables is different than kubelet defaults.") // Node Allocatable Flags - fs.Var(&c.SystemReserved, "system-reserved", "A set of ResourceName=ResourceQuantity (e.g. cpu=200m,memory=500Mi) pairs that describe resources reserved for non-kubernetes components. Currently only cpu and memory are supported. See http://kubernetes.io/docs/user-guide/compute-resources for more detail. [default=none]") - fs.Var(&c.KubeReserved, "kube-reserved", "A set of ResourceName=ResourceQuantity (e.g. cpu=200m,memory=500Mi, ephemeral-storage=1Gi) pairs that describe resources reserved for kubernetes system components. Currently cpu, memory and local ephemeral storage for root file system are supported. See http://kubernetes.io/docs/user-guide/compute-resources for more detail. [default=none]") + fs.Var(flag.NewMapStringString(&c.SystemReserved), "system-reserved", "A set of ResourceName=ResourceQuantity (e.g. cpu=200m,memory=500Mi,ephemeral-storage=1Gi) pairs that describe resources reserved for non-kubernetes components. Currently only cpu and memory are supported. See http://kubernetes.io/docs/user-guide/compute-resources for more detail. [default=none]") + fs.Var(flag.NewMapStringString(&c.KubeReserved), "kube-reserved", "A set of ResourceName=ResourceQuantity (e.g. cpu=200m,memory=500Mi,ephemeral-storage=1Gi) pairs that describe resources reserved for kubernetes system components. Currently cpu, memory and local ephemeral storage for root file system are supported. See http://kubernetes.io/docs/user-guide/compute-resources for more detail. [default=none]") fs.StringSliceVar(&c.EnforceNodeAllocatable, "enforce-node-allocatable", c.EnforceNodeAllocatable, "A comma separated list of levels of node allocatable enforcement to be enforced by kubelet. Acceptible options are 'pods', 'system-reserved' & 'kube-reserved'. If the latter two options are specified, '--system-reserved-cgroup' & '--kube-reserved-cgroup' must also be set respectively. See https://kubernetes.io/docs/tasks/administer-cluster/reserve-compute-resources/ for more details.") fs.StringVar(&c.SystemReservedCgroup, "system-reserved-cgroup", c.SystemReservedCgroup, "Absolute name of the top level cgroup that is used to manage non-kubernetes components for which compute resources were reserved via '--system-reserved' flag. Ex. '/system-reserved'. [default='']") fs.StringVar(&c.KubeReservedCgroup, "kube-reserved-cgroup", c.KubeReservedCgroup, "Absolute name of the top level cgroup that is used to manage kubernetes components for which compute resources were reserved via '--kube-reserved' flag. Ex. '/kube-reserved'. [default='']") diff --git a/cmd/kubelet/app/options/options_test.go b/cmd/kubelet/app/options/options_test.go index a52d290620f..33fd10b9d9f 100644 --- a/cmd/kubelet/app/options/options_test.go +++ b/cmd/kubelet/app/options/options_test.go @@ -38,7 +38,7 @@ func newKubeletServerOrDie() *KubeletServer { func cleanFlags(s *KubeletServer) { s.KubeConfig = utilflag.NewStringFlag(s.KubeConfig.Value()) s.DynamicConfigDir = utilflag.NewStringFlag(s.DynamicConfigDir.Value()) - s.InitConfigDir = utilflag.NewStringFlag(s.InitConfigDir.Value()) + s.KubeletConfigFile = utilflag.NewStringFlag(s.KubeletConfigFile.Value()) } // TestRoundTrip ensures that flag values from the Kubelet can be serialized @@ -120,7 +120,12 @@ func asArgs(fn, defaultFn func(*pflag.FlagSet)) []string { defaultFn(defaults) var args []string fs.VisitAll(func(flag *pflag.Flag) { + // if the flag implements utilflag.OmitEmpty and the value is Empty, then just omit it from the command line + if omit, ok := flag.Value.(utilflag.OmitEmpty); ok && omit.Empty() { + return + } s := flag.Value.String() + // if the flag has the same value as the default, we can omit it without changing the meaning of the command line var defaultValue string if defaultFlag := defaults.Lookup(flag.Name); defaultFlag != nil { defaultValue = defaultFlag.Value.String() @@ -128,6 +133,7 @@ func asArgs(fn, defaultFn func(*pflag.FlagSet)) []string { return } } + // if the flag is a string slice, each element is specified with an independent flag invocation if values, err := fs.GetStringSlice(flag.Name); err == nil { for _, s := range values { args = append(args, fmt.Sprintf("--%s=%s", flag.Name, s)) diff --git a/cmd/kubelet/app/plugins.go b/cmd/kubelet/app/plugins.go index 13abcd9ff73..ef41bb8e909 100644 --- a/cmd/kubelet/app/plugins.go +++ b/cmd/kubelet/app/plugins.go @@ -35,6 +35,7 @@ import ( "k8s.io/kubernetes/pkg/volume/cephfs" "k8s.io/kubernetes/pkg/volume/cinder" "k8s.io/kubernetes/pkg/volume/configmap" + "k8s.io/kubernetes/pkg/volume/csi" "k8s.io/kubernetes/pkg/volume/downwardapi" "k8s.io/kubernetes/pkg/volume/empty_dir" "k8s.io/kubernetes/pkg/volume/fc" @@ -58,6 +59,9 @@ import ( "k8s.io/kubernetes/pkg/volume/vsphere_volume" // Cloud providers _ "k8s.io/kubernetes/pkg/cloudprovider/providers" + // features check + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/features" ) // ProbeVolumePlugins collects all volume plugins into an easy to use list. @@ -96,6 +100,9 @@ func ProbeVolumePlugins() []volume.VolumePlugin { allPlugins = append(allPlugins, scaleio.ProbeVolumePlugins()...) allPlugins = append(allPlugins, local.ProbeVolumePlugins()...) allPlugins = append(allPlugins, storageos.ProbeVolumePlugins()...) + if utilfeature.DefaultFeatureGate.Enabled(features.CSIPersistentVolume) { + allPlugins = append(allPlugins, csi.ProbeVolumePlugins()...) + } return allPlugins } @@ -107,21 +114,12 @@ func GetDynamicPluginProber(pluginDir string) volume.DynamicPluginProber { } // ProbeNetworkPlugins collects all compiled-in plugins -func ProbeNetworkPlugins(pluginDir, cniConfDir, cniBinDir string) []network.NetworkPlugin { +func ProbeNetworkPlugins(cniConfDir, cniBinDir string) []network.NetworkPlugin { allPlugins := []network.NetworkPlugin{} - // for backwards-compat, allow pluginDir as a source of CNI config files - if cniConfDir == "" { - cniConfDir = pluginDir - } - - binDir := cniBinDir - if binDir == "" { - binDir = pluginDir - } // for each existing plugin, add to the list - allPlugins = append(allPlugins, cni.ProbeNetworkPlugins(cniConfDir, binDir)...) - allPlugins = append(allPlugins, kubenet.NewPlugin(binDir)) + allPlugins = append(allPlugins, cni.ProbeNetworkPlugins(cniConfDir, cniBinDir)...) + allPlugins = append(allPlugins, kubenet.NewPlugin(cniBinDir)) return allPlugins } diff --git a/cmd/kubelet/app/server.go b/cmd/kubelet/app/server.go index 66badd8f2d6..00156112825 100644 --- a/cmd/kubelet/app/server.go +++ b/cmd/kubelet/app/server.go @@ -46,7 +46,6 @@ import ( "k8s.io/apiserver/pkg/server/healthz" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/apiserver/pkg/util/flag" - clientgoclientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes" v1core "k8s.io/client-go/kubernetes/typed/core/v1" restclient "k8s.io/client-go/rest" @@ -55,7 +54,8 @@ import ( certutil "k8s.io/client-go/util/cert" "k8s.io/client-go/util/certificate" "k8s.io/kubernetes/cmd/kubelet/app/options" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/capabilities" "k8s.io/kubernetes/pkg/client/chaosclient" "k8s.io/kubernetes/pkg/cloudprovider" @@ -72,7 +72,6 @@ import ( "k8s.io/kubernetes/pkg/kubelet/config" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/kubelet/dockershim" - "k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker" dockerremote "k8s.io/kubernetes/pkg/kubelet/dockershim/remote" "k8s.io/kubernetes/pkg/kubelet/eviction" evictionapi "k8s.io/kubernetes/pkg/kubelet/eviction/api" @@ -145,12 +144,13 @@ func UnsecuredDependencies(s *options.KubeletServer) (*kubelet.Dependencies, err writer = &kubeio.NsenterWriter{} } - var dockerClient libdocker.Interface + var dockerClientConfig *dockershim.ClientConfig if s.ContainerRuntime == kubetypes.DockerContainerRuntime { - dockerClient = libdocker.ConnectToDockerOrDie(s.DockerEndpoint, s.RuntimeRequestTimeout.Duration, - s.ImagePullProgressDeadline.Duration) - } else { - dockerClient = nil + dockerClientConfig = &dockershim.ClientConfig{ + DockerEndpoint: s.DockerEndpoint, + RuntimeRequestTimeout: s.RuntimeRequestTimeout.Duration, + ImagePullProgressDeadline: s.ImagePullProgressDeadline.Duration, + } } return &kubelet.Dependencies{ @@ -158,13 +158,13 @@ func UnsecuredDependencies(s *options.KubeletServer) (*kubelet.Dependencies, err CAdvisorInterface: nil, // cadvisor.New launches background processes (bg http.ListenAndServe, and some bg cleaners), not set here Cloud: nil, // cloud provider might start background processes ContainerManager: nil, - DockerClient: dockerClient, + DockerClientConfig: dockerClientConfig, KubeClient: nil, HeartbeatClient: nil, ExternalKubeClient: nil, EventClient: nil, Mounter: mounter, - NetworkPlugins: ProbeNetworkPlugins(s.NetworkPluginDir, s.CNIConfDir, s.CNIBinDir), + NetworkPlugins: ProbeNetworkPlugins(s.CNIConfDir, s.CNIBinDir), OOMAdjuster: oom.NewOOMAdjuster(), OSInterface: kubecontainer.RealOS{}, Writer: writer, @@ -221,13 +221,13 @@ func initConfigz(kc *kubeletconfiginternal.KubeletConfiguration) error { return nil } -// makeEventRecorder sets up kubeDeps.Recorder if its nil. Its a no-op otherwise. +// makeEventRecorder sets up kubeDeps.Recorder if it's nil. It's a no-op otherwise. func makeEventRecorder(kubeDeps *kubelet.Dependencies, nodeName types.NodeName) { if kubeDeps.Recorder != nil { return } eventBroadcaster := record.NewBroadcaster() - kubeDeps.Recorder = eventBroadcaster.NewRecorder(api.Scheme, v1.EventSource{Component: componentKubelet, Host: string(nodeName)}) + kubeDeps.Recorder = eventBroadcaster.NewRecorder(legacyscheme.Scheme, v1.EventSource{Component: componentKubelet, Host: string(nodeName)}) eventBroadcaster.StartLogging(glog.V(3).Infof) if kubeDeps.EventClient != nil { glog.V(4).Infof("Sending events to api server.") @@ -289,12 +289,8 @@ func run(s *options.KubeletServer, kubeDeps *kubelet.Dependencies) (err error) { } } - if s.CloudProvider == kubeletconfigv1alpha1.AutoDetectCloudProvider { - glog.Warning("--cloud-provider=auto-detect is deprecated. The desired cloud provider should be set explicitly") - } - if kubeDeps.Cloud == nil { - if !cloudprovider.IsExternal(s.CloudProvider) && s.CloudProvider != kubeletconfigv1alpha1.AutoDetectCloudProvider { + if !cloudprovider.IsExternal(s.CloudProvider) { cloud, err := cloudprovider.InitCloudProvider(s.CloudProvider, s.CloudConfigFile) if err != nil { return err @@ -331,9 +327,9 @@ func run(s *options.KubeletServer, kubeDeps *kubelet.Dependencies) (err error) { var kubeClient clientset.Interface var eventClient v1core.EventsGetter var heartbeatClient v1core.CoreV1Interface - var externalKubeClient clientgoclientset.Interface + var externalKubeClient clientset.Interface - clientConfig, err := CreateAPIServerClientConfig(s) + clientConfig, err := createAPIServerClientConfig(s) var clientCertificateManager certificate.Manager if err == nil { @@ -352,12 +348,12 @@ func run(s *options.KubeletServer, kubeDeps *kubelet.Dependencies) (err error) { kubeClient, err = clientset.NewForConfig(clientConfig) if err != nil { glog.Warningf("New kubeClient from clientConfig error: %v", err) - } else if kubeClient.Certificates() != nil && clientCertificateManager != nil { + } else if kubeClient.CertificatesV1beta1() != nil && clientCertificateManager != nil { glog.V(2).Info("Starting client certificate rotation.") - clientCertificateManager.SetCertificateSigningRequestClient(kubeClient.Certificates().CertificateSigningRequests()) + clientCertificateManager.SetCertificateSigningRequestClient(kubeClient.CertificatesV1beta1().CertificateSigningRequests()) clientCertificateManager.Start() } - externalKubeClient, err = clientgoclientset.NewForConfig(clientConfig) + externalKubeClient, err = clientset.NewForConfig(clientConfig) if err != nil { glog.Warningf("New kubeClient from clientConfig error: %v", err) } @@ -401,7 +397,7 @@ func run(s *options.KubeletServer, kubeDeps *kubelet.Dependencies) (err error) { // Alpha Dynamic Configuration Implementation; // if the kubelet config controller is available, inject the latest to start the config and status sync loops if utilfeature.DefaultFeatureGate.Enabled(features.DynamicKubeletConfig) && kubeDeps.KubeletConfigController != nil && !standaloneMode && !s.RunOnce { - kubeDeps.KubeletConfigController.StartSync(kubeDeps.KubeClient, string(nodeName)) + kubeDeps.KubeletConfigController.StartSync(kubeDeps.KubeClient, kubeDeps.EventClient, string(nodeName)) } if kubeDeps.Auth == nil { @@ -414,7 +410,7 @@ func run(s *options.KubeletServer, kubeDeps *kubelet.Dependencies) (err error) { if kubeDeps.CAdvisorInterface == nil { imageFsInfoProvider := cadvisor.NewImageFsInfoProvider(s.ContainerRuntime, s.RemoteRuntimeEndpoint) - kubeDeps.CAdvisorInterface, err = cadvisor.New(s.Address, uint(s.CAdvisorPort), imageFsInfoProvider, s.RootDirectory) + kubeDeps.CAdvisorInterface, err = cadvisor.New(s.Address, uint(s.CAdvisorPort), imageFsInfoProvider, s.RootDirectory, cadvisor.UsingLegacyCadvisorStats(s.ContainerRuntime, s.RemoteRuntimeEndpoint)) if err != nil { return err } @@ -439,7 +435,7 @@ func run(s *options.KubeletServer, kubeDeps *kubelet.Dependencies) (err error) { var hardEvictionThresholds []evictionapi.Threshold // If the user requested to ignore eviction thresholds, then do not set valid values for hardEvictionThresholds here. if !s.ExperimentalNodeAllocatableIgnoreEvictionThreshold { - hardEvictionThresholds, err = eviction.ParseThresholdConfig([]string{}, s.EvictionHard, "", "", "") + hardEvictionThresholds, err = eviction.ParseThresholdConfig([]string{}, s.EvictionHard, nil, nil, nil) if err != nil { return err } @@ -462,6 +458,7 @@ func run(s *options.KubeletServer, kubeDeps *kubelet.Dependencies) (err error) { CgroupsPerQOS: s.CgroupsPerQOS, CgroupRoot: s.CgroupRoot, CgroupDriver: s.CgroupDriver, + KubeletRootDir: s.RootDirectory, ProtectKernelDefaults: s.ProtectKernelDefaults, NodeAllocatableConfig: cm.NodeAllocatableConfig{ KubeReservedCgroupName: s.KubeReservedCgroup, @@ -616,10 +613,9 @@ func createClientConfig(s *options.KubeletServer) (*restclient.Config, error) { } } -// CreateAPIServerClientConfig generates a client.Config from command line flags +// createAPIServerClientConfig generates a client.Config from command line flags // via createClientConfig and then injects chaos into the configuration via addChaosToClientConfig. -// This func is exported to support integration with third party kubelet extensions (e.g. kubernetes-mesos). -func CreateAPIServerClientConfig(s *options.KubeletServer) (*restclient.Config, error) { +func createAPIServerClientConfig(s *options.KubeletServer) (*restclient.Config, error) { clientConfig, err := createClientConfig(s) if err != nil { return nil, err @@ -653,7 +649,7 @@ func addChaosToClientConfig(s *options.KubeletServer, config *restclient.Config) // Eventually, #2 will be replaced with instances of #3 func RunKubelet(kubeFlags *options.KubeletFlags, kubeCfg *kubeletconfiginternal.KubeletConfiguration, kubeDeps *kubelet.Dependencies, runOnce bool) error { hostname := nodeutil.GetHostname(kubeFlags.HostnameOverride) - // Query the cloud provider for our node name, default to hostname if kcfg.Cloud == nil + // Query the cloud provider for our node name, default to hostname if kubeDeps.Cloud == nil nodeName, err := getNodeName(kubeDeps.Cloud, hostname) if err != nil { return err @@ -691,25 +687,24 @@ func RunKubelet(kubeFlags *options.KubeletFlags, kubeCfg *kubeletconfiginternal. credentialprovider.SetPreferredDockercfgPath(kubeFlags.RootDirectory) glog.V(2).Infof("Using root directory: %v", kubeFlags.RootDirectory) - builder := kubeDeps.Builder - if builder == nil { - builder = CreateAndInitKubelet - } if kubeDeps.OSInterface == nil { kubeDeps.OSInterface = kubecontainer.RealOS{} } - k, err := builder(kubeCfg, + k, err := CreateAndInitKubelet(kubeCfg, kubeDeps, &kubeFlags.ContainerRuntimeOptions, + kubeFlags.ContainerRuntime, + kubeFlags.RuntimeCgroups, kubeFlags.HostnameOverride, kubeFlags.NodeIP, kubeFlags.ProviderID, kubeFlags.CloudProvider, kubeFlags.CertDirectory, kubeFlags.RootDirectory, + kubeFlags.RegisterNode, + kubeFlags.RegisterWithTaints, kubeFlags.AllowedUnsafeSysctls, - kubeFlags.Containerized, kubeFlags.RemoteRuntimeEndpoint, kubeFlags.RemoteImageEndpoint, kubeFlags.ExperimentalMounterPath, @@ -722,7 +717,10 @@ func RunKubelet(kubeFlags *options.KubeletFlags, kubeCfg *kubeletconfiginternal. kubeFlags.MasterServiceNamespace, kubeFlags.RegisterSchedulable, kubeFlags.NonMasqueradeCIDR, - kubeFlags.KeepTerminatedPodVolumes) + kubeFlags.KeepTerminatedPodVolumes, + kubeFlags.NodeLabels, + kubeFlags.SeccompProfileRoot, + kubeFlags.BootstrapCheckpointPath) if err != nil { return fmt.Errorf("failed to create kubelet: %v", err) } @@ -769,14 +767,17 @@ func startKubelet(k kubelet.Bootstrap, podCfg *config.PodConfig, kubeCfg *kubele func CreateAndInitKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, kubeDeps *kubelet.Dependencies, crOptions *config.ContainerRuntimeOptions, + containerRuntime string, + runtimeCgroups string, hostnameOverride string, nodeIP string, providerID string, cloudProvider string, certDirectory string, rootDirectory string, + registerNode bool, + registerWithTaints []api.Taint, allowedUnsafeSysctls []string, - containerized bool, remoteRuntimeEndpoint string, remoteImageEndpoint string, experimentalMounterPath string, @@ -789,21 +790,27 @@ func CreateAndInitKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, masterServiceNamespace string, registerSchedulable bool, nonMasqueradeCIDR string, - keepTerminatedPodVolumes bool) (k kubelet.Bootstrap, err error) { + keepTerminatedPodVolumes bool, + nodeLabels map[string]string, + seccompProfileRoot string, + bootstrapCheckpointPath string) (k kubelet.Bootstrap, err error) { // TODO: block until all sources have delivered at least one update to the channel, or break the sync loop // up into "per source" synchronizations k, err = kubelet.NewMainKubelet(kubeCfg, kubeDeps, crOptions, + containerRuntime, + runtimeCgroups, hostnameOverride, nodeIP, providerID, cloudProvider, certDirectory, rootDirectory, + registerNode, + registerWithTaints, allowedUnsafeSysctls, - containerized, remoteRuntimeEndpoint, remoteImageEndpoint, experimentalMounterPath, @@ -816,7 +823,10 @@ func CreateAndInitKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, masterServiceNamespace, registerSchedulable, nonMasqueradeCIDR, - keepTerminatedPodVolumes) + keepTerminatedPodVolumes, + nodeLabels, + seccompProfileRoot, + bootstrapCheckpointPath) if err != nil { return nil, err } @@ -830,7 +840,7 @@ func CreateAndInitKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, // parseResourceList parses the given configuration map into an API // ResourceList or returns an error. -func parseResourceList(m kubeletconfiginternal.ConfigurationMap) (v1.ResourceList, error) { +func parseResourceList(m map[string]string) (v1.ResourceList, error) { if len(m) == 0 { return nil, nil } @@ -856,16 +866,16 @@ func parseResourceList(m kubeletconfiginternal.ConfigurationMap) (v1.ResourceLis // BootstrapKubeletConfigController constructs and bootstrap a configuration controller func BootstrapKubeletConfigController(defaultConfig *kubeletconfiginternal.KubeletConfiguration, - initConfigDirFlag flag.StringFlag, + kubeletConfigFileFlag flag.StringFlag, dynamicConfigDirFlag flag.StringFlag) (*kubeletconfiginternal.KubeletConfiguration, *kubeletconfig.Controller, error) { var err error // Alpha Dynamic Configuration Implementation; this section only loads config from disk, it does not contact the API server // compute absolute paths based on current working dir - initConfigDir := "" - if utilfeature.DefaultFeatureGate.Enabled(features.KubeletConfigFile) && initConfigDirFlag.Provided() { - initConfigDir, err = filepath.Abs(initConfigDirFlag.Value()) + kubeletConfigFile := "" + if utilfeature.DefaultFeatureGate.Enabled(features.KubeletConfigFile) && kubeletConfigFileFlag.Provided() { + kubeletConfigFile, err = filepath.Abs(kubeletConfigFileFlag.Value()) if err != nil { - return nil, nil, fmt.Errorf("failed to get absolute path for --init-config-dir") + return nil, nil, fmt.Errorf("failed to get absolute path for --config") } } dynamicConfigDir := "" @@ -876,8 +886,8 @@ func BootstrapKubeletConfigController(defaultConfig *kubeletconfiginternal.Kubel } } - // get the latest KubeletConfiguration checkpoint from disk, or load the init or default config if no valid checkpoints exist - kubeletConfigController, err := kubeletconfig.NewController(defaultConfig, initConfigDir, dynamicConfigDir) + // get the latest KubeletConfiguration checkpoint from disk, or load the kubelet config file or default config if no valid checkpoints exist + kubeletConfigController, err := kubeletconfig.NewController(defaultConfig, kubeletConfigFile, dynamicConfigDir) if err != nil { return nil, nil, fmt.Errorf("failed to construct controller, error: %v", err) } @@ -892,22 +902,22 @@ func BootstrapKubeletConfigController(defaultConfig *kubeletconfiginternal.Kubel // TODO(random-liu): Move this to a separate binary. func RunDockershim(f *options.KubeletFlags, c *kubeletconfiginternal.KubeletConfiguration) error { r := &f.ContainerRuntimeOptions - // Create docker client. - dockerClient := libdocker.ConnectToDockerOrDie(r.DockerEndpoint, c.RuntimeRequestTimeout.Duration, - r.ImagePullProgressDeadline.Duration) + + // Initialize docker client configuration. + dockerClientConfig := &dockershim.ClientConfig{ + DockerEndpoint: r.DockerEndpoint, + RuntimeRequestTimeout: c.RuntimeRequestTimeout.Duration, + ImagePullProgressDeadline: r.ImagePullProgressDeadline.Duration, + } // Initialize network plugin settings. - binDir := r.CNIBinDir - if binDir == "" { - binDir = r.NetworkPluginDir - } nh := &kubelet.NoOpLegacyHost{} pluginSettings := dockershim.NetworkPluginSettings{ HairpinMode: kubeletconfiginternal.HairpinMode(c.HairpinMode), NonMasqueradeCIDR: f.NonMasqueradeCIDR, PluginName: r.NetworkPluginName, PluginConfDir: r.CNIConfDir, - PluginBinDir: binDir, + PluginBinDir: r.CNIBinDir, MTU: int(r.NetworkPluginMTU), LegacyRuntimeHost: nh, } @@ -922,8 +932,8 @@ func RunDockershim(f *options.KubeletFlags, c *kubeletconfiginternal.KubeletConf SupportedPortForwardProtocols: streaming.DefaultConfig.SupportedPortForwardProtocols, } - ds, err := dockershim.NewDockerService(dockerClient, r.PodSandboxImage, streamingConfig, &pluginSettings, - c.RuntimeCgroups, c.CgroupDriver, r.DockershimRootDirectory, r.DockerDisableSharedPID) + ds, err := dockershim.NewDockerService(dockerClientConfig, r.PodSandboxImage, streamingConfig, &pluginSettings, + f.RuntimeCgroups, c.CgroupDriver, r.DockershimRootDirectory, r.DockerDisableSharedPID) if err != nil { return err } diff --git a/cmd/kubelet/app/server_test.go b/cmd/kubelet/app/server_test.go index bf573168b2b..1db214ab954 100644 --- a/cmd/kubelet/app/server_test.go +++ b/cmd/kubelet/app/server_test.go @@ -18,46 +18,38 @@ package app import ( "testing" - - "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" ) func TestValueOfAllocatableResources(t *testing.T) { testCases := []struct { - kubeReserved string - systemReserved string + kubeReserved map[string]string + systemReserved map[string]string errorExpected bool name string }{ { - kubeReserved: "cpu=200m,memory=-150G,ephemeral-storage=10Gi", - systemReserved: "cpu=200m,memory=15Ki", + kubeReserved: map[string]string{"cpu": "200m", "memory": "-150G", "ephemeral-storage": "10Gi"}, + systemReserved: map[string]string{"cpu": "200m", "memory": "15Ki"}, errorExpected: true, name: "negative quantity value", }, { - kubeReserved: "cpu=200m,memory=150Gi,ephemeral-storage=10Gi", - systemReserved: "cpu=200m,memory=15Ky", + kubeReserved: map[string]string{"cpu": "200m", "memory": "150Gi", "ephemeral-storage": "10Gi"}, + systemReserved: map[string]string{"cpu": "200m", "memory": "15Ky"}, errorExpected: true, name: "invalid quantity unit", }, { - kubeReserved: "cpu=200m,memory=15G,ephemeral-storage=10Gi", - systemReserved: "cpu=200m,memory=15Ki", + kubeReserved: map[string]string{"cpu": "200m", "memory": "15G", "ephemeral-storage": "10Gi"}, + systemReserved: map[string]string{"cpu": "200m", "memory": "15Ki"}, errorExpected: false, name: "Valid resource quantity", }, } for _, test := range testCases { - kubeReservedCM := make(kubeletconfig.ConfigurationMap) - systemReservedCM := make(kubeletconfig.ConfigurationMap) - - kubeReservedCM.Set(test.kubeReserved) - systemReservedCM.Set(test.systemReserved) - - _, err1 := parseResourceList(kubeReservedCM) - _, err2 := parseResourceList(systemReservedCM) + _, err1 := parseResourceList(test.kubeReserved) + _, err2 := parseResourceList(test.systemReserved) if test.errorExpected { if err1 == nil && err2 == nil { t.Errorf("%s: error expected", test.name) diff --git a/cmd/kubelet/kubelet.go b/cmd/kubelet/kubelet.go index 33ed06f3f81..891aace197c 100644 --- a/cmd/kubelet/kubelet.go +++ b/cmd/kubelet/kubelet.go @@ -75,7 +75,7 @@ func main() { // bootstrap the kubelet config controller, app.BootstrapKubeletConfigController will check // feature gates and only turn on relevant parts of the controller kubeletConfig, kubeletConfigController, err := app.BootstrapKubeletConfigController( - defaultConfig, kubeletFlags.InitConfigDir, kubeletFlags.DynamicConfigDir) + defaultConfig, kubeletFlags.KubeletConfigFile, kubeletFlags.DynamicConfigDir) if err != nil { die(err) } diff --git a/cmd/kubemark/BUILD b/cmd/kubemark/BUILD index 740d20976e6..1948949aa07 100644 --- a/cmd/kubemark/BUILD +++ b/cmd/kubemark/BUILD @@ -8,8 +8,8 @@ load( go_binary( name = "kubemark", + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/kubemark", - library = ":go_default_library", ) go_library( @@ -17,11 +17,12 @@ go_library( srcs = ["hollow-node.go"], importpath = "k8s.io/kubernetes/cmd/kubemark", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/metrics/prometheus:go_default_library", "//pkg/kubelet/cadvisor/testing:go_default_library", "//pkg/kubelet/cm:go_default_library", + "//pkg/kubelet/dockershim:go_default_library", "//pkg/kubelet/dockershim/libdocker:go_default_library", "//pkg/kubemark:go_default_library", "//pkg/util/iptables/testing:go_default_library", diff --git a/cmd/kubemark/hollow-node.go b/cmd/kubemark/hollow-node.go index a6f7d307475..ffcbac533db 100644 --- a/cmd/kubemark/hollow-node.go +++ b/cmd/kubemark/hollow-node.go @@ -22,16 +22,16 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apiserver/pkg/util/flag" - clientgoclientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/record" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" _ "k8s.io/kubernetes/pkg/client/metrics/prometheus" // for client metric registration cadvisortest "k8s.io/kubernetes/pkg/kubelet/cadvisor/testing" "k8s.io/kubernetes/pkg/kubelet/cm" + "k8s.io/kubernetes/pkg/kubelet/dockershim" "k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker" "k8s.io/kubernetes/pkg/kubemark" fakeiptables "k8s.io/kubernetes/pkg/util/iptables/testing" @@ -102,7 +102,7 @@ func main() { glog.Fatalf("Failed to create a ClientConfig: %v. Exiting.", err) } - clientset, err := clientset.NewForConfig(clientConfig) + client, err := clientset.NewForConfig(clientConfig) if err != nil { glog.Fatalf("Failed to create a ClientSet: %v. Exiting.", err) } @@ -116,14 +116,18 @@ func main() { NodeName: config.NodeName, } containerManager := cm.NewStubContainerManager() - fakeDockerClient := libdocker.NewFakeDockerClient().WithTraceDisabled() - fakeDockerClient.EnableSleep = true + + fakeDockerClientConfig := &dockershim.ClientConfig{ + DockerEndpoint: libdocker.FakeDockerEndpoint, + EnableSleep: true, + WithTraceDisabled: true, + } hollowKubelet := kubemark.NewHollowKubelet( config.NodeName, - clientset, + client, cadvisorInterface, - fakeDockerClient, + fakeDockerClientConfig, config.KubeletPort, config.KubeletReadOnlyPort, containerManager, @@ -134,7 +138,7 @@ func main() { } if config.Morph == "proxy" { - client, err := clientgoclientset.NewForConfig(clientConfig) + client, err := clientset.NewForConfig(clientConfig) if err != nil { glog.Fatalf("Failed to create API Server client: %v", err) } @@ -142,7 +146,7 @@ func main() { sysctl := fakesysctl.NewFake() execer := &fakeexec.FakeExec{} eventBroadcaster := record.NewBroadcaster() - recorder := eventBroadcaster.NewRecorder(api.Scheme, v1.EventSource{Component: "kube-proxy", Host: config.NodeName}) + recorder := eventBroadcaster.NewRecorder(legacyscheme.Scheme, v1.EventSource{Component: "kube-proxy", Host: config.NodeName}) hollowProxy, err := kubemark.NewHollowProxyOrDie( config.NodeName, diff --git a/cmd/linkcheck/BUILD b/cmd/linkcheck/BUILD index 14e4b3fd6d1..16f3d84b518 100644 --- a/cmd/linkcheck/BUILD +++ b/cmd/linkcheck/BUILD @@ -8,8 +8,8 @@ load( go_binary( name = "linkcheck", + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/cmd/linkcheck", - library = ":go_default_library", ) go_library( diff --git a/code-of-conduct.md b/code-of-conduct.md index 622d4c1d1c2..0d15c00cf32 100644 --- a/code-of-conduct.md +++ b/code-of-conduct.md @@ -1,5 +1,3 @@ -## Kubernetes Community Code of Conduct +# Kubernetes Community Code of Conduct -Kubernetes follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). - -[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/code-of-conduct.md?pixel)]() +Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) diff --git a/docs/.generated_docs b/docs/.generated_docs index 70d87281940..4886eea60ba 100644 --- a/docs/.generated_docs +++ b/docs/.generated_docs @@ -1,23 +1,138 @@ docs/.generated_docs docs/admin/cloud-controller-manager.md -docs/admin/federation-apiserver.md -docs/admin/federation-controller-manager.md docs/admin/kube-apiserver.md docs/admin/kube-controller-manager.md docs/admin/kube-proxy.md docs/admin/kube-scheduler.md -docs/admin/kubefed.md -docs/admin/kubefed_init.md -docs/admin/kubefed_join.md -docs/admin/kubefed_options.md -docs/admin/kubefed_unjoin.md -docs/admin/kubefed_version.md +docs/admin/kubeadm.md +docs/admin/kubeadm_alpha.md +docs/admin/kubeadm_alpha_phase.md +docs/admin/kubeadm_alpha_phase_addon.md +docs/admin/kubeadm_alpha_phase_addon_all.md +docs/admin/kubeadm_alpha_phase_addon_kube-dns.md +docs/admin/kubeadm_alpha_phase_addon_kube-proxy.md +docs/admin/kubeadm_alpha_phase_bootstrap-token.md +docs/admin/kubeadm_alpha_phase_bootstrap-token_all.md +docs/admin/kubeadm_alpha_phase_bootstrap-token_cluster-info.md +docs/admin/kubeadm_alpha_phase_bootstrap-token_create.md +docs/admin/kubeadm_alpha_phase_bootstrap-token_node.md +docs/admin/kubeadm_alpha_phase_bootstrap-token_node_allow-auto-approve.md +docs/admin/kubeadm_alpha_phase_bootstrap-token_node_allow-post-csrs.md +docs/admin/kubeadm_alpha_phase_certs.md +docs/admin/kubeadm_alpha_phase_certs_all.md +docs/admin/kubeadm_alpha_phase_certs_apiserver-kubelet-client.md +docs/admin/kubeadm_alpha_phase_certs_apiserver.md +docs/admin/kubeadm_alpha_phase_certs_ca.md +docs/admin/kubeadm_alpha_phase_certs_front-proxy-ca.md +docs/admin/kubeadm_alpha_phase_certs_front-proxy-client.md +docs/admin/kubeadm_alpha_phase_certs_sa.md +docs/admin/kubeadm_alpha_phase_controlplane.md +docs/admin/kubeadm_alpha_phase_controlplane_all.md +docs/admin/kubeadm_alpha_phase_controlplane_apiserver.md +docs/admin/kubeadm_alpha_phase_controlplane_controller-manager.md +docs/admin/kubeadm_alpha_phase_controlplane_scheduler.md +docs/admin/kubeadm_alpha_phase_etcd.md +docs/admin/kubeadm_alpha_phase_etcd_local.md +docs/admin/kubeadm_alpha_phase_kubeconfig.md +docs/admin/kubeadm_alpha_phase_kubeconfig_admin.md +docs/admin/kubeadm_alpha_phase_kubeconfig_all.md +docs/admin/kubeadm_alpha_phase_kubeconfig_controller-manager.md +docs/admin/kubeadm_alpha_phase_kubeconfig_kubelet.md +docs/admin/kubeadm_alpha_phase_kubeconfig_scheduler.md +docs/admin/kubeadm_alpha_phase_kubeconfig_user.md +docs/admin/kubeadm_alpha_phase_mark-master.md +docs/admin/kubeadm_alpha_phase_preflight.md +docs/admin/kubeadm_alpha_phase_preflight_master.md +docs/admin/kubeadm_alpha_phase_preflight_node.md +docs/admin/kubeadm_alpha_phase_selfhosting.md +docs/admin/kubeadm_alpha_phase_selfhosting_convert-from-staticpods.md +docs/admin/kubeadm_alpha_phase_upload-config.md +docs/admin/kubeadm_completion.md +docs/admin/kubeadm_config.md +docs/admin/kubeadm_config_upload.md +docs/admin/kubeadm_config_upload_from-file.md +docs/admin/kubeadm_config_upload_from-flags.md +docs/admin/kubeadm_config_view.md +docs/admin/kubeadm_init.md +docs/admin/kubeadm_join.md +docs/admin/kubeadm_reset.md +docs/admin/kubeadm_token.md +docs/admin/kubeadm_token_create.md +docs/admin/kubeadm_token_delete.md +docs/admin/kubeadm_token_generate.md +docs/admin/kubeadm_token_list.md +docs/admin/kubeadm_upgrade.md +docs/admin/kubeadm_upgrade_apply.md +docs/admin/kubeadm_upgrade_plan.md +docs/admin/kubeadm_version.md docs/admin/kubelet.md docs/man/man1/cloud-controller-manager.1 docs/man/man1/kube-apiserver.1 docs/man/man1/kube-controller-manager.1 docs/man/man1/kube-proxy.1 docs/man/man1/kube-scheduler.1 +docs/man/man1/kubeadm-alpha-phase-addon-all.1 +docs/man/man1/kubeadm-alpha-phase-addon-kube-dns.1 +docs/man/man1/kubeadm-alpha-phase-addon-kube-proxy.1 +docs/man/man1/kubeadm-alpha-phase-addon.1 +docs/man/man1/kubeadm-alpha-phase-bootstrap-token-all.1 +docs/man/man1/kubeadm-alpha-phase-bootstrap-token-cluster-info.1 +docs/man/man1/kubeadm-alpha-phase-bootstrap-token-create.1 +docs/man/man1/kubeadm-alpha-phase-bootstrap-token-node-allow-auto-approve.1 +docs/man/man1/kubeadm-alpha-phase-bootstrap-token-node-allow-post-csrs.1 +docs/man/man1/kubeadm-alpha-phase-bootstrap-token-node.1 +docs/man/man1/kubeadm-alpha-phase-bootstrap-token.1 +docs/man/man1/kubeadm-alpha-phase-certs-all.1 +docs/man/man1/kubeadm-alpha-phase-certs-apiserver-kubelet-client.1 +docs/man/man1/kubeadm-alpha-phase-certs-apiserver.1 +docs/man/man1/kubeadm-alpha-phase-certs-ca.1 +docs/man/man1/kubeadm-alpha-phase-certs-front-proxy-ca.1 +docs/man/man1/kubeadm-alpha-phase-certs-front-proxy-client.1 +docs/man/man1/kubeadm-alpha-phase-certs-sa.1 +docs/man/man1/kubeadm-alpha-phase-certs.1 +docs/man/man1/kubeadm-alpha-phase-controlplane-all.1 +docs/man/man1/kubeadm-alpha-phase-controlplane-apiserver.1 +docs/man/man1/kubeadm-alpha-phase-controlplane-controller-manager.1 +docs/man/man1/kubeadm-alpha-phase-controlplane-scheduler.1 +docs/man/man1/kubeadm-alpha-phase-controlplane.1 +docs/man/man1/kubeadm-alpha-phase-etcd-local.1 +docs/man/man1/kubeadm-alpha-phase-etcd.1 +docs/man/man1/kubeadm-alpha-phase-kubeconfig-admin.1 +docs/man/man1/kubeadm-alpha-phase-kubeconfig-all.1 +docs/man/man1/kubeadm-alpha-phase-kubeconfig-controller-manager.1 +docs/man/man1/kubeadm-alpha-phase-kubeconfig-kubelet.1 +docs/man/man1/kubeadm-alpha-phase-kubeconfig-scheduler.1 +docs/man/man1/kubeadm-alpha-phase-kubeconfig-user.1 +docs/man/man1/kubeadm-alpha-phase-kubeconfig.1 +docs/man/man1/kubeadm-alpha-phase-mark-master.1 +docs/man/man1/kubeadm-alpha-phase-preflight-master.1 +docs/man/man1/kubeadm-alpha-phase-preflight-node.1 +docs/man/man1/kubeadm-alpha-phase-preflight.1 +docs/man/man1/kubeadm-alpha-phase-selfhosting-convert-from-staticpods.1 +docs/man/man1/kubeadm-alpha-phase-selfhosting.1 +docs/man/man1/kubeadm-alpha-phase-upload-config.1 +docs/man/man1/kubeadm-alpha-phase.1 +docs/man/man1/kubeadm-alpha.1 +docs/man/man1/kubeadm-completion.1 +docs/man/man1/kubeadm-config-upload-from-file.1 +docs/man/man1/kubeadm-config-upload-from-flags.1 +docs/man/man1/kubeadm-config-upload.1 +docs/man/man1/kubeadm-config-view.1 +docs/man/man1/kubeadm-config.1 +docs/man/man1/kubeadm-init.1 +docs/man/man1/kubeadm-join.1 +docs/man/man1/kubeadm-reset.1 +docs/man/man1/kubeadm-token-create.1 +docs/man/man1/kubeadm-token-delete.1 +docs/man/man1/kubeadm-token-generate.1 +docs/man/man1/kubeadm-token-list.1 +docs/man/man1/kubeadm-token.1 +docs/man/man1/kubeadm-upgrade-apply.1 +docs/man/man1/kubeadm-upgrade-plan.1 +docs/man/man1/kubeadm-upgrade.1 +docs/man/man1/kubeadm-version.1 +docs/man/man1/kubeadm.1 +docs/man/man1/kubectl-alpha-diff.1 docs/man/man1/kubectl-alpha.1 docs/man/man1/kubectl-annotate.1 docs/man/man1/kubectl-api-versions.1 @@ -59,6 +174,7 @@ docs/man/man1/kubectl-create-configmap.1 docs/man/man1/kubectl-create-deployment.1 docs/man/man1/kubectl-create-namespace.1 docs/man/man1/kubectl-create-poddisruptionbudget.1 +docs/man/man1/kubectl-create-priorityclass.1 docs/man/man1/kubectl-create-quota.1 docs/man/man1/kubectl-create-role.1 docs/man/man1/kubectl-create-rolebinding.1 @@ -115,6 +231,8 @@ docs/man/man1/kubectl-version.1 docs/man/man1/kubectl.1 docs/man/man1/kubelet.1 docs/user-guide/kubectl/kubectl.md +docs/user-guide/kubectl/kubectl_alpha.md +docs/user-guide/kubectl/kubectl_alpha_diff.md docs/user-guide/kubectl/kubectl_annotate.md docs/user-guide/kubectl/kubectl_api-versions.md docs/user-guide/kubectl/kubectl_apply.md @@ -156,6 +274,7 @@ docs/user-guide/kubectl/kubectl_create_configmap.md docs/user-guide/kubectl/kubectl_create_deployment.md docs/user-guide/kubectl/kubectl_create_namespace.md docs/user-guide/kubectl/kubectl_create_poddisruptionbudget.md +docs/user-guide/kubectl/kubectl_create_priorityclass.md docs/user-guide/kubectl/kubectl_create_quota.md docs/user-guide/kubectl/kubectl_create_role.md docs/user-guide/kubectl/kubectl_create_rolebinding.md diff --git a/docs/admin/federation-apiserver.md b/docs/admin/kubeadm.md similarity index 100% rename from docs/admin/federation-apiserver.md rename to docs/admin/kubeadm.md diff --git a/docs/admin/federation-controller-manager.md b/docs/admin/kubeadm_alpha.md similarity index 100% rename from docs/admin/federation-controller-manager.md rename to docs/admin/kubeadm_alpha.md diff --git a/docs/admin/kubefed.md b/docs/admin/kubeadm_alpha_phase.md similarity index 100% rename from docs/admin/kubefed.md rename to docs/admin/kubeadm_alpha_phase.md diff --git a/docs/admin/kubefed_init.md b/docs/admin/kubeadm_alpha_phase_addon.md similarity index 100% rename from docs/admin/kubefed_init.md rename to docs/admin/kubeadm_alpha_phase_addon.md diff --git a/docs/admin/kubefed_join.md b/docs/admin/kubeadm_alpha_phase_addon_all.md similarity index 100% rename from docs/admin/kubefed_join.md rename to docs/admin/kubeadm_alpha_phase_addon_all.md diff --git a/docs/admin/kubefed_options.md b/docs/admin/kubeadm_alpha_phase_addon_kube-dns.md similarity index 100% rename from docs/admin/kubefed_options.md rename to docs/admin/kubeadm_alpha_phase_addon_kube-dns.md diff --git a/docs/admin/kubefed_unjoin.md b/docs/admin/kubeadm_alpha_phase_addon_kube-proxy.md similarity index 100% rename from docs/admin/kubefed_unjoin.md rename to docs/admin/kubeadm_alpha_phase_addon_kube-proxy.md diff --git a/docs/admin/kubefed_version.md b/docs/admin/kubeadm_alpha_phase_bootstrap-token.md similarity index 100% rename from docs/admin/kubefed_version.md rename to docs/admin/kubeadm_alpha_phase_bootstrap-token.md diff --git a/docs/admin/kubeadm_alpha_phase_bootstrap-token_all.md b/docs/admin/kubeadm_alpha_phase_bootstrap-token_all.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_bootstrap-token_all.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_bootstrap-token_cluster-info.md b/docs/admin/kubeadm_alpha_phase_bootstrap-token_cluster-info.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_bootstrap-token_cluster-info.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_bootstrap-token_create.md b/docs/admin/kubeadm_alpha_phase_bootstrap-token_create.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_bootstrap-token_create.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_bootstrap-token_node.md b/docs/admin/kubeadm_alpha_phase_bootstrap-token_node.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_bootstrap-token_node.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_bootstrap-token_node_allow-auto-approve.md b/docs/admin/kubeadm_alpha_phase_bootstrap-token_node_allow-auto-approve.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_bootstrap-token_node_allow-auto-approve.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_bootstrap-token_node_allow-post-csrs.md b/docs/admin/kubeadm_alpha_phase_bootstrap-token_node_allow-post-csrs.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_bootstrap-token_node_allow-post-csrs.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_certs.md b/docs/admin/kubeadm_alpha_phase_certs.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_certs.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_certs_all.md b/docs/admin/kubeadm_alpha_phase_certs_all.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_certs_all.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_certs_apiserver-kubelet-client.md b/docs/admin/kubeadm_alpha_phase_certs_apiserver-kubelet-client.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_certs_apiserver-kubelet-client.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_certs_apiserver.md b/docs/admin/kubeadm_alpha_phase_certs_apiserver.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_certs_apiserver.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_certs_ca.md b/docs/admin/kubeadm_alpha_phase_certs_ca.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_certs_ca.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_certs_front-proxy-ca.md b/docs/admin/kubeadm_alpha_phase_certs_front-proxy-ca.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_certs_front-proxy-ca.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_certs_front-proxy-client.md b/docs/admin/kubeadm_alpha_phase_certs_front-proxy-client.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_certs_front-proxy-client.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_certs_sa.md b/docs/admin/kubeadm_alpha_phase_certs_sa.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_certs_sa.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_controlplane.md b/docs/admin/kubeadm_alpha_phase_controlplane.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_controlplane.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_controlplane_all.md b/docs/admin/kubeadm_alpha_phase_controlplane_all.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_controlplane_all.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_controlplane_apiserver.md b/docs/admin/kubeadm_alpha_phase_controlplane_apiserver.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_controlplane_apiserver.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_controlplane_controller-manager.md b/docs/admin/kubeadm_alpha_phase_controlplane_controller-manager.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_controlplane_controller-manager.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_controlplane_scheduler.md b/docs/admin/kubeadm_alpha_phase_controlplane_scheduler.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_controlplane_scheduler.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_etcd.md b/docs/admin/kubeadm_alpha_phase_etcd.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_etcd.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_etcd_local.md b/docs/admin/kubeadm_alpha_phase_etcd_local.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_etcd_local.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_kubeconfig.md b/docs/admin/kubeadm_alpha_phase_kubeconfig.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_kubeconfig.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_kubeconfig_admin.md b/docs/admin/kubeadm_alpha_phase_kubeconfig_admin.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_kubeconfig_admin.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_kubeconfig_all.md b/docs/admin/kubeadm_alpha_phase_kubeconfig_all.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_kubeconfig_all.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_kubeconfig_controller-manager.md b/docs/admin/kubeadm_alpha_phase_kubeconfig_controller-manager.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_kubeconfig_controller-manager.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_kubeconfig_kubelet.md b/docs/admin/kubeadm_alpha_phase_kubeconfig_kubelet.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_kubeconfig_kubelet.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_kubeconfig_scheduler.md b/docs/admin/kubeadm_alpha_phase_kubeconfig_scheduler.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_kubeconfig_scheduler.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_kubeconfig_user.md b/docs/admin/kubeadm_alpha_phase_kubeconfig_user.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_kubeconfig_user.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_mark-master.md b/docs/admin/kubeadm_alpha_phase_mark-master.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_mark-master.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_preflight.md b/docs/admin/kubeadm_alpha_phase_preflight.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_preflight.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_preflight_master.md b/docs/admin/kubeadm_alpha_phase_preflight_master.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_preflight_master.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_preflight_node.md b/docs/admin/kubeadm_alpha_phase_preflight_node.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_preflight_node.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_selfhosting.md b/docs/admin/kubeadm_alpha_phase_selfhosting.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_selfhosting.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_selfhosting_convert-from-staticpods.md b/docs/admin/kubeadm_alpha_phase_selfhosting_convert-from-staticpods.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_selfhosting_convert-from-staticpods.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_alpha_phase_upload-config.md b/docs/admin/kubeadm_alpha_phase_upload-config.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_alpha_phase_upload-config.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_completion.md b/docs/admin/kubeadm_completion.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_completion.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_config.md b/docs/admin/kubeadm_config.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_config.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_config_upload.md b/docs/admin/kubeadm_config_upload.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_config_upload.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_config_upload_from-file.md b/docs/admin/kubeadm_config_upload_from-file.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_config_upload_from-file.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_config_upload_from-flags.md b/docs/admin/kubeadm_config_upload_from-flags.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_config_upload_from-flags.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_config_view.md b/docs/admin/kubeadm_config_view.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_config_view.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_init.md b/docs/admin/kubeadm_init.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_init.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_join.md b/docs/admin/kubeadm_join.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_join.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_reset.md b/docs/admin/kubeadm_reset.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_reset.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_token.md b/docs/admin/kubeadm_token.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_token.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_token_create.md b/docs/admin/kubeadm_token_create.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_token_create.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_token_delete.md b/docs/admin/kubeadm_token_delete.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_token_delete.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_token_generate.md b/docs/admin/kubeadm_token_generate.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_token_generate.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_token_list.md b/docs/admin/kubeadm_token_list.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_token_list.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_upgrade.md b/docs/admin/kubeadm_upgrade.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_upgrade.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_upgrade_apply.md b/docs/admin/kubeadm_upgrade_apply.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_upgrade_apply.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_upgrade_plan.md b/docs/admin/kubeadm_upgrade_plan.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_upgrade_plan.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/admin/kubeadm_version.md b/docs/admin/kubeadm_version.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_version.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/api-reference/OWNERS b/docs/api-reference/OWNERS new file mode 100644 index 00000000000..8f7783f9f0b --- /dev/null +++ b/docs/api-reference/OWNERS @@ -0,0 +1,4 @@ +approvers: +- api-approvers +reviewers: +- api-reviewers diff --git a/docs/api-reference/admissionregistration.k8s.io/v1alpha1/definitions.html b/docs/api-reference/admissionregistration.k8s.io/v1alpha1/definitions.html index ffd1dc03756..9bd19cb602f 100755 --- a/docs/api-reference/admissionregistration.k8s.io/v1alpha1/definitions.html +++ b/docs/api-reference/admissionregistration.k8s.io/v1alpha1/definitions.html @@ -375,12 +375,6 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
  • v1alpha1.InitializerConfigurationList

  • -
  • -

    v1alpha1.ExternalAdmissionHookConfiguration

    -
  • -
  • -

    v1alpha1.ExternalAdmissionHookConfigurationList

    -
  • @@ -444,11 +438,78 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    v1alpha1.OperationType

    - +

    v1.Patch

    +
    +

    Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.

    +
    -

    v1alpha1.FailurePolicyType

    +

    v1.DeleteOptions

    +
    +

    DeleteOptions may be provided when deleting an API object.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    kind

    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/api-conventions.md#types-kinds

    false

    string

    apiVersion

    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/api-conventions.md#resources

    false

    string

    gracePeriodSeconds

    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.

    false

    integer (int64)

    preconditions

    Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.

    false

    v1.Preconditions

    orphanDependents

    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.

    false

    boolean

    false

    propagationPolicy

    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.

    false

    v1.DeletionPropagation

    @@ -504,67 +565,6 @@ Depending on the enclosing object, subresources might not be allowed. Required.< -
    -
    -

    v1alpha1.RuleWithOperations

    -
    -

    RuleWithOperations is a tuple of Operations and Resources. It is recommended to make sure that all the tuple expansions are valid.

    -
    - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameDescriptionRequiredSchemaDefault

    operations

    Operations is the operations the admission hook cares about - CREATE, UPDATE, or * for all operations. If * is present, the length of the slice must be one. Required.

    false

    v1alpha1.OperationType array

    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.

    false

    string array

    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.

    false

    string array

    resources

    Resources is a list of resources this rule applies to.
    -
    -For 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.
    -
    -If wildcard is present, the validation rule will ensure resources do not overlap with each other.
    -
    -Depending on the enclosing object, subresources might not be allowed. Required.

    false

    string array

    -

    v1.ListMeta

    @@ -615,9 +615,9 @@ Depending on the enclosing object, subresources might not be allowed. Required.<
    -

    v1.Initializers

    +

    v1.StatusDetails

    -

    Initializers tracks the progress of initialization.

    +

    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.

    @@ -638,17 +638,45 @@ Depending on the enclosing object, subresources might not be allowed. Required.< - - - - + + + + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -690,9 +718,9 @@ Depending on the enclosing object, subresources might not be allowed. Required.<
    -

    v1alpha1.AdmissionHookClientConfig

    +

    v1.Initializers

    -

    AdmissionHookClientConfig contains the information to make a TLS connection with the webhook

    +

    Initializers tracks the progress of initialization.

    pending

    Pending is a list of initializers that must execute in order before this object is visible. When the last pending initializer is removed, and no failing result is set, the initializers struct will be set to nil and the object is considered as initialized and visible to all clients.

    true

    v1.Initializer array

    name

    The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).

    false

    string

    result

    If result is set with the Failure field, the object will be persisted to storage and then deleted, ensuring that other clients can observe the deletion.

    group

    The group attribute of the resource associated with the status StatusReason.

    false

    v1.Status

    string

    kind

    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/api-conventions.md#types-kinds

    false

    string

    uid

    UID of the resource. (when there is a single resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids

    false

    string

    causes

    The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.

    false

    v1.StatusCause array

    retryAfterSeconds

    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.

    false

    integer (int32)

    @@ -713,15 +741,49 @@ Depending on the enclosing object, subresources might not be allowed. Required.< - - + + - + - - + + + + + + + +

    service

    Service is a reference to the service for this webhook. If there is only one port open for the service, that port will be used. If there are multiple ports open, port 443 will be used if it is open, otherwise it is an error. Required

    pending

    Pending is a list of initializers that must execute in order before this object is visible. When the last pending initializer is removed, and no failing result is set, the initializers struct will be set to nil and the object is considered as initialized and visible to all clients.

    true

    v1alpha1.ServiceReference

    v1.Initializer array

    caBundle

    CABundle is a PEM encoded CA bundle which will be used to validate webhook’s server certificate. Required

    result

    If result is set with the Failure field, the object will be persisted to storage and then deleted, ensuring that other clients can observe the deletion.

    false

    v1.Status

    + +
    +
    +

    v1.Initializer

    +
    +

    Initializer is information about an initializer that has not yet completed.

    +
    + +++++++ + + + + + + + + + + + + + @@ -850,449 +912,6 @@ Depending on the enclosing object, subresources might not be allowed. Required.<
    NameDescriptionRequiredSchemaDefault

    name

    name of the process that is responsible for initializing this object.

    true

    string

    -
    -
    -

    v1alpha1.ExternalAdmissionHook

    -
    -

    ExternalAdmissionHook describes an external admission webhook and the resources and operations it applies to.

    -
    - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameDescriptionRequiredSchemaDefault

    name

    The name of the external 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.

    true

    string

    clientConfig

    ClientConfig defines how to communicate with the hook. Required

    true

    v1alpha1.AdmissionHookClientConfig

    rules

    Rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches any Rule.

    false

    v1alpha1.RuleWithOperations array

    failurePolicy

    FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Ignore.

    false

    v1alpha1.FailurePolicyType

    - -
    -
    -

    v1alpha1.ServiceReference

    -
    -

    ServiceReference holds a reference to Service.legacy.k8s.io

    -
    - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameDescriptionRequiredSchemaDefault

    namespace

    Namespace is the namespace of the service Required

    true

    string

    name

    Name is the name of the service Required

    true

    string

    - -
    -
    -

    v1alpha1.Initializer

    -
    -

    Initializer describes the name and the failure policy of an initializer, and what resources it applies to.

    -
    - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameDescriptionRequiredSchemaDefault

    name

    Name is the identifier of the initializer. It will be added to the object that needs to be initialized. Name should be fully qualified, e.g., alwayspullimages.kubernetes.io, where "alwayspullimages" is the name of the webhook, and kubernetes.io is the name of the organization. Required

    true

    string

    rules

    Rules describes what resources/subresources the initializer cares about. The initializer cares about an operation if it matches any Rule. Rule.Resources must not include subresources.

    false

    v1alpha1.Rule array

    - -
    -
    -

    v1alpha1.ExternalAdmissionHookConfiguration

    -
    -

    ExternalAdmissionHookConfiguration describes the configuration of initializers.

    -
    - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameDescriptionRequiredSchemaDefault

    kind

    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/api-conventions.md#types-kinds

    false

    string

    apiVersion

    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/api-conventions.md#resources

    false

    string

    metadata

    Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata.

    false

    v1.ObjectMeta

    externalAdmissionHooks

    ExternalAdmissionHooks is a list of external admission webhooks and the affected resources and operations.

    false

    v1alpha1.ExternalAdmissionHook array

    - -
    -
    -

    v1.DeletionPropagation

    - -
    -
    -

    v1.Patch

    -
    -

    Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.

    -
    -
    -
    -

    v1.DeleteOptions

    -
    -

    DeleteOptions may be provided when deleting an API object.

    -
    - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameDescriptionRequiredSchemaDefault

    kind

    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/api-conventions.md#types-kinds

    false

    string

    apiVersion

    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/api-conventions.md#resources

    false

    string

    gracePeriodSeconds

    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.

    false

    integer (int64)

    preconditions

    Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.

    false

    v1.Preconditions

    orphanDependents

    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.

    false

    boolean

    false

    propagationPolicy

    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.

    false

    v1.DeletionPropagation

    - -
    -
    -

    v1.StatusDetails

    -
    -

    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.

    -
    - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameDescriptionRequiredSchemaDefault

    name

    The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).

    false

    string

    group

    The group attribute of the resource associated with the status StatusReason.

    false

    string

    kind

    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/api-conventions.md#types-kinds

    false

    string

    uid

    UID of the resource. (when there is a single resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids

    false

    string

    causes

    The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.

    false

    v1.StatusCause array

    retryAfterSeconds

    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.

    false

    integer (int32)

    - -
    -
    -

    v1.Initializer

    -
    -

    Initializer is information about an initializer that has not yet completed.

    -
    - ------- - - - - - - - - - - - - - - - - - - -
    NameDescriptionRequiredSchemaDefault

    name

    name of the process that is responsible for initializing this object.

    true

    string

    - -
    -
    -

    v1.OwnerReference

    -
    -

    OwnerReference contains enough information to let you identify an owning object. Currently, an owning object must be in the same namespace, so there is no namespace field.

    -
    - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameDescriptionRequiredSchemaDefault

    apiVersion

    API version of the referent.

    true

    string

    kind

    Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

    true

    string

    name

    Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names

    true

    string

    uid

    UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids

    true

    string

    controller

    If true, this reference points to the managing controller.

    false

    boolean

    false

    blockOwnerDeletion

    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. Defaults to false. To set this field, a user needs "delete" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.

    false

    boolean

    false

    -

    v1.ObjectMeta

    @@ -1387,7 +1006,7 @@ Populated by the system. Read-only. Null for lists. More info:

    deletionTimestamp

    -

    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 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.
    +

    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.

    Populated by the system when a graceful deletion is requested. Read-only. More info:
    https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

    false

    @@ -1448,6 +1067,75 @@ When an object is created, the system will populate this list with the current s +
    +
    +

    v1.OwnerReference

    +
    +

    OwnerReference contains enough information to let you identify an owning object. Currently, an owning object must be in the same namespace, so there is no namespace field.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    apiVersion

    API version of the referent.

    true

    string

    kind

    Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

    true

    string

    name

    Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names

    true

    string

    uid

    UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids

    true

    string

    controller

    If true, this reference points to the managing controller.

    false

    boolean

    false

    blockOwnerDeletion

    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. Defaults to false. To set this field, a user needs "delete" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.

    false

    boolean

    false

    +

    v1alpha1.InitializerConfiguration

    @@ -1597,6 +1285,47 @@ When an object is created, the system will populate this list with the current s

    types.UID

    +
    +
    +

    v1alpha1.Initializer

    +
    +

    Initializer describes the name and the failure policy of an initializer, and what resources it applies to.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    name

    Name is the identifier of the initializer. It will be added to the object that needs to be initialized. Name should be fully qualified, e.g., alwayspullimages.kubernetes.io, where "alwayspullimages" is the name of the webhook, and kubernetes.io is the name of the organization. Required

    true

    string

    rules

    Rules describes what resources/subresources the initializer cares about. The initializer cares about an operation if it matches any Rule. Rule.Resources must not include subresources.

    false

    v1alpha1.Rule array

    +

    v1.StatusCause

    @@ -1651,58 +1380,7 @@ Examples:
    -

    v1alpha1.ExternalAdmissionHookConfigurationList

    -
    -

    ExternalAdmissionHookConfigurationList is a list of ExternalAdmissionHookConfiguration.

    -
    - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameDescriptionRequiredSchemaDefault

    kind

    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/api-conventions.md#types-kinds

    false

    string

    apiVersion

    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/api-conventions.md#resources

    false

    string

    metadata

    Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

    false

    v1.ListMeta

    items

    List of ExternalAdmissionHookConfiguration.

    true

    v1alpha1.ExternalAdmissionHookConfiguration array

    +

    v1.DeletionPropagation

    diff --git a/docs/api-reference/admissionregistration.k8s.io/v1alpha1/operations.html b/docs/api-reference/admissionregistration.k8s.io/v1alpha1/operations.html index 36f8146dee6..4db173974f5 100755 --- a/docs/api-reference/admissionregistration.k8s.io/v1alpha1/operations.html +++ b/docs/api-reference/admissionregistration.k8s.io/v1alpha1/operations.html @@ -443,10 +443,10 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    list or watch objects of kind ExternalAdmissionHookConfiguration

    +

    list or watch objects of kind InitializerConfiguration

    -
    GET /apis/admissionregistration.k8s.io/v1alpha1/externaladmissionhookconfigurations
    +
    GET /apis/admissionregistration.k8s.io/v1alpha1/initializerconfigurations
    @@ -567,7 +567,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

    200

    success

    -

    v1alpha1.ExternalAdmissionHookConfigurationList

    +

    v1alpha1.InitializerConfigurationList

    @@ -617,10 +617,10 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    delete collection of ExternalAdmissionHookConfiguration

    +

    delete collection of InitializerConfiguration

    -
    DELETE /apis/admissionregistration.k8s.io/v1alpha1/externaladmissionhookconfigurations
    +
    DELETE /apis/admissionregistration.k8s.io/v1alpha1/initializerconfigurations
    @@ -785,10 +785,10 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    create an ExternalAdmissionHookConfiguration

    +

    create an InitializerConfiguration

    -
    POST /apis/admissionregistration.k8s.io/v1alpha1/externaladmissionhookconfigurations
    +
    POST /apis/admissionregistration.k8s.io/v1alpha1/initializerconfigurations
    @@ -826,7 +826,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

    body

    true

    -

    v1alpha1.ExternalAdmissionHookConfiguration

    +

    v1alpha1.InitializerConfiguration

    @@ -852,17 +852,17 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

    202

    Accepted

    -

    v1alpha1.ExternalAdmissionHookConfiguration

    +

    v1alpha1.InitializerConfiguration

    200

    success

    -

    v1alpha1.ExternalAdmissionHookConfiguration

    +

    v1alpha1.InitializerConfiguration

    201

    Created

    -

    v1alpha1.ExternalAdmissionHookConfiguration

    +

    v1alpha1.InitializerConfiguration

    @@ -906,10 +906,10 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    read the specified ExternalAdmissionHookConfiguration

    +

    read the specified InitializerConfiguration

    -
    GET /apis/admissionregistration.k8s.io/v1alpha1/externaladmissionhookconfigurations/{name}
    +
    GET /apis/admissionregistration.k8s.io/v1alpha1/initializerconfigurations/{name}
    @@ -961,7 +961,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

    PathParameter

    name

    -

    name of the ExternalAdmissionHookConfiguration

    +

    name of the InitializerConfiguration

    true

    string

    @@ -989,7 +989,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

    200

    success

    -

    v1alpha1.ExternalAdmissionHookConfiguration

    +

    v1alpha1.InitializerConfiguration

    @@ -1033,10 +1033,10 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    replace the specified ExternalAdmissionHookConfiguration

    +

    replace the specified InitializerConfiguration

    -
    PUT /apis/admissionregistration.k8s.io/v1alpha1/externaladmissionhookconfigurations/{name}
    +
    PUT /apis/admissionregistration.k8s.io/v1alpha1/initializerconfigurations/{name}
    @@ -1074,13 +1074,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

    body

    true

    -

    v1alpha1.ExternalAdmissionHookConfiguration

    +

    v1alpha1.InitializerConfiguration

    PathParameter

    name

    -

    name of the ExternalAdmissionHookConfiguration

    +

    name of the InitializerConfiguration

    true

    string

    @@ -1108,12 +1108,12 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

    200

    success

    -

    v1alpha1.ExternalAdmissionHookConfiguration

    +

    v1alpha1.InitializerConfiguration

    201

    Created

    -

    v1alpha1.ExternalAdmissionHookConfiguration

    +

    v1alpha1.InitializerConfiguration

    @@ -1157,10 +1157,10 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    delete an ExternalAdmissionHookConfiguration

    +

    delete an InitializerConfiguration

    -
    DELETE /apis/admissionregistration.k8s.io/v1alpha1/externaladmissionhookconfigurations/{name}
    +
    DELETE /apis/admissionregistration.k8s.io/v1alpha1/initializerconfigurations/{name}
    @@ -1220,7 +1220,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

    QueryParameter

    propagationPolicy

    -

    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.

    +

    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.

    false

    string

    @@ -1228,7 +1228,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

    PathParameter

    name

    -

    name of the ExternalAdmissionHookConfiguration

    +

    name of the InitializerConfiguration

    true

    string

    @@ -1300,10 +1300,10 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    partially update the specified ExternalAdmissionHookConfiguration

    +

    partially update the specified InitializerConfiguration

    -
    PATCH /apis/admissionregistration.k8s.io/v1alpha1/externaladmissionhookconfigurations/{name}
    +
    PATCH /apis/admissionregistration.k8s.io/v1alpha1/initializerconfigurations/{name}
    @@ -1347,7 +1347,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

    PathParameter

    name

    -

    name of the ExternalAdmissionHookConfiguration

    +

    name of the InitializerConfiguration

    true

    string

    @@ -1375,7 +1375,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

    200

    success

    -

    v1alpha1.ExternalAdmissionHookConfiguration

    +

    v1alpha1.InitializerConfiguration

    @@ -1425,10 +1425,10 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    list or watch objects of kind InitializerConfiguration

    +

    watch individual changes to a list of InitializerConfiguration

    -
    GET /apis/admissionregistration.k8s.io/v1alpha1/initializerconfigurations
    +
    GET /apis/admissionregistration.k8s.io/v1alpha1/watch/initializerconfigurations
    @@ -1549,7 +1549,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

    200

    success

    -

    v1alpha1.InitializerConfigurationList

    +

    v1.WatchEvent

    @@ -1599,10 +1599,10 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    delete collection of InitializerConfiguration

    +

    watch changes to an object of kind InitializerConfiguration

    -
    DELETE /apis/admissionregistration.k8s.io/v1alpha1/initializerconfigurations
    +
    GET /apis/admissionregistration.k8s.io/v1alpha1/watch/initializerconfigurations/{name}
    @@ -1700,6 +1700,14 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

    string

    + +

    PathParameter

    +

    name

    +

    name of the InitializerConfiguration

    +

    true

    +

    string

    + + @@ -1723,7 +1731,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

    200

    success

    -

    v1.Status

    +

    v1.WatchEvent

    @@ -1752,6 +1760,12 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
  • application/vnd.kubernetes.protobuf

  • +
  • +

    application/json;stream=watch

    +
  • +
  • +

    application/vnd.kubernetes.protobuf;stream=watch

    +
  • @@ -1766,1358 +1780,6 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } -
    -

    create an InitializerConfiguration

    -
    -
    -
    POST /apis/admissionregistration.k8s.io/v1alpha1/initializerconfigurations
    -
    -
    -
    -

    Parameters

    - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1alpha1.InitializerConfiguration

    - -
    -
    -

    Responses

    - ----- - - - - - - - - - - - - - - - - - - - - - - - - -
    HTTP CodeDescriptionSchema

    202

    Accepted

    v1alpha1.InitializerConfiguration

    200

    success

    v1alpha1.InitializerConfiguration

    201

    Created

    v1alpha1.InitializerConfiguration

    - -
    -
    -

    Consumes

    -
    -
      -
    • -

      /

      -
    • -
    -
    -
    -
    -

    Produces

    -
    -
      -
    • -

      application/json

      -
    • -
    • -

      application/yaml

      -
    • -
    • -

      application/vnd.kubernetes.protobuf

      -
    • -
    -
    -
    -
    -

    Tags

    -
    -
      -
    • -

      apisadmissionregistration.k8s.iov1alpha1

      -
    • -
    -
    -
    -
    -
    -

    read the specified InitializerConfiguration

    -
    -
    -
    GET /apis/admissionregistration.k8s.io/v1alpha1/initializerconfigurations/{name}
    -
    -
    -
    -

    Parameters

    - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    export

    Should this value be exported. Export strips fields that a user can not specify.

    false

    boolean

    QueryParameter

    exact

    Should the export be exact. Exact export maintains cluster-specific fields like Namespace.

    false

    boolean

    PathParameter

    name

    name of the InitializerConfiguration

    true

    string

    - -
    -
    -

    Responses

    - ----- - - - - - - - - - - - - - - -
    HTTP CodeDescriptionSchema

    200

    success

    v1alpha1.InitializerConfiguration

    - -
    -
    -

    Consumes

    -
    -
      -
    • -

      /

      -
    • -
    -
    -
    -
    -

    Produces

    -
    -
      -
    • -

      application/json

      -
    • -
    • -

      application/yaml

      -
    • -
    • -

      application/vnd.kubernetes.protobuf

      -
    • -
    -
    -
    -
    -

    Tags

    -
    -
      -
    • -

      apisadmissionregistration.k8s.iov1alpha1

      -
    • -
    -
    -
    -
    -
    -

    replace the specified InitializerConfiguration

    -
    -
    -
    PUT /apis/admissionregistration.k8s.io/v1alpha1/initializerconfigurations/{name}
    -
    -
    -
    -

    Parameters

    - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1alpha1.InitializerConfiguration

    PathParameter

    name

    name of the InitializerConfiguration

    true

    string

    - -
    -
    -

    Responses

    - ----- - - - - - - - - - - - - - - - - - - - -
    HTTP CodeDescriptionSchema

    200

    success

    v1alpha1.InitializerConfiguration

    201

    Created

    v1alpha1.InitializerConfiguration

    - -
    -
    -

    Consumes

    -
    -
      -
    • -

      /

      -
    • -
    -
    -
    -
    -

    Produces

    -
    -
      -
    • -

      application/json

      -
    • -
    • -

      application/yaml

      -
    • -
    • -

      application/vnd.kubernetes.protobuf

      -
    • -
    -
    -
    -
    -

    Tags

    -
    -
      -
    • -

      apisadmissionregistration.k8s.iov1alpha1

      -
    • -
    -
    -
    -
    -
    -

    delete an InitializerConfiguration

    -
    -
    -
    DELETE /apis/admissionregistration.k8s.io/v1alpha1/initializerconfigurations/{name}
    -
    -
    -
    -

    Parameters

    - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.DeleteOptions

    QueryParameter

    gracePeriodSeconds

    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.

    false

    integer (int32)

    QueryParameter

    orphanDependents

    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.

    false

    boolean

    QueryParameter

    propagationPolicy

    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.

    false

    string

    PathParameter

    name

    name of the InitializerConfiguration

    true

    string

    - -
    -
    -

    Responses

    - ----- - - - - - - - - - - - - - - -
    HTTP CodeDescriptionSchema

    200

    success

    v1.Status

    - -
    -
    -

    Consumes

    -
    -
      -
    • -

      /

      -
    • -
    -
    -
    -
    -

    Produces

    -
    -
      -
    • -

      application/json

      -
    • -
    • -

      application/yaml

      -
    • -
    • -

      application/vnd.kubernetes.protobuf

      -
    • -
    -
    -
    -
    -

    Tags

    -
    -
      -
    • -

      apisadmissionregistration.k8s.iov1alpha1

      -
    • -
    -
    -
    -
    -
    -

    partially update the specified InitializerConfiguration

    -
    -
    -
    PATCH /apis/admissionregistration.k8s.io/v1alpha1/initializerconfigurations/{name}
    -
    -
    -
    -

    Parameters

    - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.Patch

    PathParameter

    name

    name of the InitializerConfiguration

    true

    string

    - -
    -
    -

    Responses

    - ----- - - - - - - - - - - - - - - -
    HTTP CodeDescriptionSchema

    200

    success

    v1alpha1.InitializerConfiguration

    - -
    -
    -

    Consumes

    -
    -
      -
    • -

      application/json-patch+json

      -
    • -
    • -

      application/merge-patch+json

      -
    • -
    • -

      application/strategic-merge-patch+json

      -
    • -
    -
    -
    -
    -

    Produces

    -
    -
      -
    • -

      application/json

      -
    • -
    • -

      application/yaml

      -
    • -
    • -

      application/vnd.kubernetes.protobuf

      -
    • -
    -
    -
    -
    -

    Tags

    -
    -
      -
    • -

      apisadmissionregistration.k8s.iov1alpha1

      -
    • -
    -
    -
    -
    -
    -

    watch individual changes to a list of ExternalAdmissionHookConfiguration

    -
    -
    -
    GET /apis/admissionregistration.k8s.io/v1alpha1/watch/externaladmissionhookconfigurations
    -
    -
    -
    -

    Parameters

    - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. -

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    - -
    -
    -

    Responses

    - ----- - - - - - - - - - - - - - - -
    HTTP CodeDescriptionSchema

    200

    success

    v1.WatchEvent

    - -
    -
    -

    Consumes

    -
    -
      -
    • -

      /

      -
    • -
    -
    -
    -
    -

    Produces

    -
    -
      -
    • -

      application/json

      -
    • -
    • -

      application/yaml

      -
    • -
    • -

      application/vnd.kubernetes.protobuf

      -
    • -
    • -

      application/json;stream=watch

      -
    • -
    • -

      application/vnd.kubernetes.protobuf;stream=watch

      -
    • -
    -
    -
    -
    -

    Tags

    -
    -
      -
    • -

      apisadmissionregistration.k8s.iov1alpha1

      -
    • -
    -
    -
    -
    -
    -

    watch changes to an object of kind ExternalAdmissionHookConfiguration

    -
    -
    -
    GET /apis/admissionregistration.k8s.io/v1alpha1/watch/externaladmissionhookconfigurations/{name}
    -
    -
    -
    -

    Parameters

    - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. -

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    PathParameter

    name

    name of the ExternalAdmissionHookConfiguration

    true

    string

    - -
    -
    -

    Responses

    - ----- - - - - - - - - - - - - - - -
    HTTP CodeDescriptionSchema

    200

    success

    v1.WatchEvent

    - -
    -
    -

    Consumes

    -
    -
      -
    • -

      /

      -
    • -
    -
    -
    -
    -

    Produces

    -
    -
      -
    • -

      application/json

      -
    • -
    • -

      application/yaml

      -
    • -
    • -

      application/vnd.kubernetes.protobuf

      -
    • -
    • -

      application/json;stream=watch

      -
    • -
    • -

      application/vnd.kubernetes.protobuf;stream=watch

      -
    • -
    -
    -
    -
    -

    Tags

    -
    -
      -
    • -

      apisadmissionregistration.k8s.iov1alpha1

      -
    • -
    -
    -
    -
    -
    -

    watch individual changes to a list of InitializerConfiguration

    -
    -
    -
    GET /apis/admissionregistration.k8s.io/v1alpha1/watch/initializerconfigurations
    -
    -
    -
    -

    Parameters

    - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. -

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    - -
    -
    -

    Responses

    - ----- - - - - - - - - - - - - - - -
    HTTP CodeDescriptionSchema

    200

    success

    v1.WatchEvent

    - -
    -
    -

    Consumes

    -
    -
      -
    • -

      /

      -
    • -
    -
    -
    -
    -

    Produces

    -
    -
      -
    • -

      application/json

      -
    • -
    • -

      application/yaml

      -
    • -
    • -

      application/vnd.kubernetes.protobuf

      -
    • -
    • -

      application/json;stream=watch

      -
    • -
    • -

      application/vnd.kubernetes.protobuf;stream=watch

      -
    • -
    -
    -
    -
    -

    Tags

    -
    -
      -
    • -

      apisadmissionregistration.k8s.iov1alpha1

      -
    • -
    -
    -
    -
    -
    -

    watch changes to an object of kind InitializerConfiguration

    -
    -
    -
    GET /apis/admissionregistration.k8s.io/v1alpha1/watch/initializerconfigurations/{name}
    -
    -
    -
    -

    Parameters

    - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. -

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    PathParameter

    name

    name of the InitializerConfiguration

    true

    string

    - -
    -
    -

    Responses

    - ----- - - - - - - - - - - - - - - -
    HTTP CodeDescriptionSchema

    200

    success

    v1.WatchEvent

    - -
    -
    -

    Consumes

    -
    -
      -
    • -

      /

      -
    • -
    -
    -
    -
    -

    Produces

    -
    -
      -
    • -

      application/json

      -
    • -
    • -

      application/yaml

      -
    • -
    • -

      application/vnd.kubernetes.protobuf

      -
    • -
    • -

      application/json;stream=watch

      -
    • -
    • -

      application/vnd.kubernetes.protobuf;stream=watch

      -
    • -
    -
    -
    -
    -

    Tags

    -
    -
      -
    • -

      apisadmissionregistration.k8s.iov1alpha1

      -
    • -
    -
    -
    -
    diff --git a/docs/api-reference/admissionregistration.k8s.io/v1beta1/definitions.html b/docs/api-reference/admissionregistration.k8s.io/v1beta1/definitions.html new file mode 100755 index 00000000000..eb74c3e4d2f --- /dev/null +++ b/docs/api-reference/admissionregistration.k8s.io/v1beta1/definitions.html @@ -0,0 +1,1837 @@ + + + + + + +Top Level API Objects + + + + +
    + +
    +

    Definitions

    +
    +
    +

    v1.APIResourceList

    +
    +

    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.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    kind

    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/api-conventions.md#types-kinds

    false

    string

    apiVersion

    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/api-conventions.md#resources

    false

    string

    groupVersion

    groupVersion is the group and version this APIResourceList is for.

    true

    string

    resources

    resources contains the name of the resources and if they are namespaced.

    true

    v1.APIResource array

    + +
    +
    +

    v1beta1.RuleWithOperations

    +
    +

    RuleWithOperations is a tuple of Operations and Resources. It is recommended to make sure that all the tuple expansions are valid.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    operations

    Operations is the operations the admission hook cares about - CREATE, UPDATE, or * for all operations. If * is present, the length of the slice must be one. Required.

    false

    v1beta1.OperationType array

    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.

    false

    string array

    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.

    false

    string array

    resources

    Resources is a list of resources this rule applies to.
    +
    +For 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.
    +
    +If wildcard is present, the validation rule will ensure resources do not overlap with each other.
    +
    +Depending on the enclosing object, subresources might not be allowed. Required.

    false

    string array

    + +
    +
    +

    v1.ListMeta

    +
    +

    ListMeta describes metadata that synthetic resources must have, including lists and various status objects. A resource may have only one of {ObjectMeta, ListMeta}.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    selfLink

    selfLink is a URL representing this object. Populated by the system. Read-only.

    false

    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/api-conventions.md#concurrency-control-and-consistency

    false

    string

    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 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.

    false

    string

    + +
    +
    +

    v1.Initializers

    +
    +

    Initializers tracks the progress of initialization.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    pending

    Pending is a list of initializers that must execute in order before this object is visible. When the last pending initializer is removed, and no failing result is set, the initializers struct will be set to nil and the object is considered as initialized and visible to all clients.

    true

    v1.Initializer array

    result

    If result is set with the Failure field, the object will be persisted to storage and then deleted, ensuring that other clients can observe the deletion.

    false

    v1.Status

    + +
    +
    +

    v1.Preconditions

    +
    +

    Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    uid

    Specifies the target UID.

    false

    types.UID

    + +
    +
    +

    v1.Status

    +
    +

    Status is a return value for calls that don’t return other objects.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    kind

    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/api-conventions.md#types-kinds

    false

    string

    apiVersion

    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/api-conventions.md#resources

    false

    string

    metadata

    Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

    false

    v1.ListMeta

    status

    Status of the operation. One of: "Success" or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

    false

    string

    message

    A human-readable description of the status of this operation.

    false

    string

    reason

    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.

    false

    string

    details

    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.

    false

    v1.StatusDetails

    code

    Suggested HTTP return code for this status, 0 if not set.

    false

    integer (int32)

    + +
    +
    +

    v1beta1.ValidatingWebhookConfiguration

    +
    +

    ValidatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and object without changing it.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    kind

    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/api-conventions.md#types-kinds

    false

    string

    apiVersion

    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/api-conventions.md#resources

    false

    string

    metadata

    Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata.

    false

    v1.ObjectMeta

    webhooks

    Webhooks is a list of webhooks and the affected resources and operations.

    false

    v1beta1.Webhook array

    + +
    +
    +

    v1.WatchEvent

    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    type

    true

    string

    object

    true

    string

    + +
    +
    +

    v1.LabelSelector

    +
    +

    A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    matchLabels

    matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.

    false

    object

    matchExpressions

    matchExpressions is a list of label selector requirements. The requirements are ANDed.

    false

    v1.LabelSelectorRequirement array

    + +
    +
    +

    v1beta1.MutatingWebhookConfigurationList

    +
    +

    MutatingWebhookConfigurationList is a list of MutatingWebhookConfiguration.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    kind

    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/api-conventions.md#types-kinds

    false

    string

    apiVersion

    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/api-conventions.md#resources

    false

    string

    metadata

    Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

    false

    v1.ListMeta

    items

    List of MutatingWebhookConfiguration.

    true

    v1beta1.MutatingWebhookConfiguration array

    + +
    +
    +

    v1beta1.MutatingWebhookConfiguration

    +
    +

    MutatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and may change the object.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    kind

    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/api-conventions.md#types-kinds

    false

    string

    apiVersion

    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/api-conventions.md#resources

    false

    string

    metadata

    Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata.

    false

    v1.ObjectMeta

    webhooks

    Webhooks is a list of webhooks and the affected resources and operations.

    false

    v1beta1.Webhook array

    + +
    +
    +

    v1.LabelSelectorRequirement

    +
    +

    A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    key

    key is the label key that the selector applies to.

    true

    string

    operator

    operator represents a key’s relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.

    true

    string

    values

    values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.

    false

    string array

    + +
    +
    +

    v1beta1.ServiceReference

    +
    +

    ServiceReference holds a reference to Service.legacy.k8s.io

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    namespace

    namespace is the namespace of the service. Required

    true

    string

    name

    name is the name of the service. Required

    true

    string

    path

    path is an optional URL path which will be sent in any request to this service.

    false

    string

    + +
    +
    +

    v1beta1.Webhook

    +
    +

    Webhook describes an admission webhook and the resources and operations it applies to.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    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.

    true

    string

    clientConfig

    ClientConfig defines how to communicate with the hook. Required

    true

    v1beta1.WebhookClientConfig

    rules

    Rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches any Rule.

    false

    v1beta1.RuleWithOperations array

    failurePolicy

    FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Ignore.

    false

    v1beta1.FailurePolicyType

    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 other cluster scoped resource, it is not subjected to the webhook.
    +
    +For 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": {
    + "matchExpressions": [
    + {
    + "key": "runlevel",
    + "operator": "NotIn",
    + "values": [
    + "0",
    + "1"
    + ]
    + }
    + ]
    +}
    +
    +If 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": {
    + "matchExpressions": [
    + {
    + "key": "environment",
    + "operator": "In",
    + "values": [
    + "prod",
    + "staging"
    + ]
    + }
    + ]
    +}
    +
    +See https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.
    +
    +Default to the empty LabelSelector, which matches everything.

    false

    v1.LabelSelector

    + +
    +
    +

    v1.DeletionPropagation

    + +
    +
    +

    v1beta1.OperationType

    + +
    +
    +

    v1.Patch

    +
    +

    Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.

    +
    +
    +
    +

    v1.DeleteOptions

    +
    +

    DeleteOptions may be provided when deleting an API object.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    kind

    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/api-conventions.md#types-kinds

    false

    string

    apiVersion

    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/api-conventions.md#resources

    false

    string

    gracePeriodSeconds

    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.

    false

    integer (int64)

    preconditions

    Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.

    false

    v1.Preconditions

    orphanDependents

    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.

    false

    boolean

    false

    propagationPolicy

    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.

    false

    v1.DeletionPropagation

    + +
    +
    +

    v1.StatusDetails

    +
    +

    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.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    name

    The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).

    false

    string

    group

    The group attribute of the resource associated with the status StatusReason.

    false

    string

    kind

    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/api-conventions.md#types-kinds

    false

    string

    uid

    UID of the resource. (when there is a single resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids

    false

    string

    causes

    The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.

    false

    v1.StatusCause array

    retryAfterSeconds

    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.

    false

    integer (int32)

    + +
    +
    +

    v1.Initializer

    +
    +

    Initializer is information about an initializer that has not yet completed.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    name

    name of the process that is responsible for initializing this object.

    true

    string

    + +
    +
    +

    v1.OwnerReference

    +
    +

    OwnerReference contains enough information to let you identify an owning object. Currently, an owning object must be in the same namespace, so there is no namespace field.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    apiVersion

    API version of the referent.

    true

    string

    kind

    Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

    true

    string

    name

    Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names

    true

    string

    uid

    UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids

    true

    string

    controller

    If true, this reference points to the managing controller.

    false

    boolean

    false

    blockOwnerDeletion

    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. Defaults to false. To set this field, a user needs "delete" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.

    false

    boolean

    false

    + +
    +
    +

    v1.ObjectMeta

    +
    +

    ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    name

    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: http://kubernetes.io/docs/user-guide/identifiers#names

    false

    string

    generateName

    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.
    +
    +If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header).
    +
    +Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency

    false

    string

    namespace

    Namespace defines the space within 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.
    +
    +Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces

    false

    string

    selfLink

    SelfLink is a URL representing this object. Populated by the system. Read-only.

    false

    string

    uid

    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.
    +
    +Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids

    false

    string

    resourceVersion

    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.
    +
    +Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency

    false

    string

    generation

    A sequence number representing a specific generation of the desired state. Populated by the system. Read-only.

    false

    integer (int64)

    creationTimestamp

    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.
    +
    +Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

    false

    string

    deletionTimestamp

    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.
    +
    +Populated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

    false

    string

    deletionGracePeriodSeconds

    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.

    false

    integer (int64)

    labels

    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: http://kubernetes.io/docs/user-guide/labels

    false

    object

    annotations

    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: http://kubernetes.io/docs/user-guide/annotations

    false

    object

    ownerReferences

    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.

    false

    v1.OwnerReference array

    initializers

    An initializer is a controller which enforces some system invariant at object creation time. This field is a list of initializers that have not yet acted on this object. If nil or empty, this object has been completely initialized. Otherwise, the object is considered uninitialized and is hidden (in list/watch and get calls) from clients that haven’t explicitly asked to observe uninitialized objects.
    +
    +When an object is created, the system will populate this list with the current set of initializers. Only privileged users may set or modify this list. Once it is empty, it may not be modified further by any user.

    false

    v1.Initializers

    finalizers

    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.

    false

    string array

    clusterName

    The name of the cluster which the object belongs to. This is used to distinguish resources with same name and namespace in different clusters. This field is not set anywhere right now and apiserver is going to ignore it if set in create or update request.

    false

    string

    + +
    +
    +

    v1beta1.WebhookClientConfig

    +
    +

    WebhookClientConfig contains the information to make a TLS connection with the webhook

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    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.
    +
    +The 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.
    +
    +Please 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.
    +
    +The scheme must be "https"; the URL must begin with "https://".
    +
    +A 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.
    +
    +Attempting to use a user or basic auth e.g. "user:password@" is not allowed. Fragments ("#…") and query parameters ("?…") are not allowed, either.

    false

    string

    service

    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.
    +
    +If there is only one port open for the service, that port will be used. If there are multiple ports open, port 443 will be used if it is open, otherwise it is an error.

    true

    v1beta1.ServiceReference

    caBundle

    caBundle is a PEM encoded CA bundle which will be used to validate the webhook’s server certificate. Required.

    true

    string

    + +
    +
    +

    v1beta1.ValidatingWebhookConfigurationList

    +
    +

    ValidatingWebhookConfigurationList is a list of ValidatingWebhookConfiguration.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    kind

    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/api-conventions.md#types-kinds

    false

    string

    apiVersion

    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/api-conventions.md#resources

    false

    string

    metadata

    Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

    false

    v1.ListMeta

    items

    List of ValidatingWebhookConfiguration.

    true

    v1beta1.ValidatingWebhookConfiguration array

    + +
    +
    +

    v1.APIResource

    +
    +

    APIResource specifies the name of a resource and whether it is namespaced.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    name

    name is the plural name of the resource.

    true

    string

    singularName

    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.

    true

    string

    namespaced

    namespaced indicates if a resource is namespaced or not.

    true

    boolean

    false

    group

    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".

    false

    string

    version

    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)".

    false

    string

    kind

    kind is the kind for the resource (e.g. Foo is the kind for a resource foo)

    true

    string

    verbs

    verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy)

    true

    string array

    shortNames

    shortNames is a list of suggested short names of the resource.

    false

    string array

    categories

    categories is a list of the grouped resources this resource belongs to (e.g. all)

    false

    string array

    + +
    +
    +

    types.UID

    + +
    +
    +

    v1.StatusCause

    +
    +

    StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    reason

    A machine-readable description of the cause of the error. If this value is empty there is no information available.

    false

    string

    message

    A human-readable description of the cause of the error. This field may be presented as-is to a reader.

    false

    string

    field

    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.
    +
    +Examples:
    + "name" - the field "name" on the current resource
    + "items[0].name" - the field "name" on the first array entry in "items"

    false

    string

    + +
    +
    +

    v1beta1.FailurePolicyType

    + +
    +
    +

    any

    +
    +

    Represents an untyped JSON map - see the description of the field for more info about the structure of this object.

    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/docs/api-reference/admissionregistration.k8s.io/v1beta1/operations.html b/docs/api-reference/admissionregistration.k8s.io/v1beta1/operations.html new file mode 100755 index 00000000000..8a7d7385a9d --- /dev/null +++ b/docs/api-reference/admissionregistration.k8s.io/v1beta1/operations.html @@ -0,0 +1,3130 @@ + + + + + + +Operations + + + + +
    +
    +

    Operations

    +
    +
    +

    get available resources

    +
    +
    +
    GET /apis/admissionregistration.k8s.io/v1beta1
    +
    +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    default

    success

    v1.APIResourceList

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisadmissionregistration.k8s.iov1beta1

      +
    • +
    +
    +
    +
    +
    +

    list or watch objects of kind MutatingWebhookConfiguration

    +
    +
    +
    GET /apis/admissionregistration.k8s.io/v1beta1/mutatingwebhookconfigurations
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1beta1.MutatingWebhookConfigurationList

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    • +

      application/json;stream=watch

      +
    • +
    • +

      application/vnd.kubernetes.protobuf;stream=watch

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisadmissionregistration.k8s.iov1beta1

      +
    • +
    +
    +
    +
    +
    +

    delete collection of MutatingWebhookConfiguration

    +
    +
    +
    DELETE /apis/admissionregistration.k8s.io/v1beta1/mutatingwebhookconfigurations
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Status

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisadmissionregistration.k8s.iov1beta1

      +
    • +
    +
    +
    +
    +
    +

    create a MutatingWebhookConfiguration

    +
    +
    +
    POST /apis/admissionregistration.k8s.io/v1beta1/mutatingwebhookconfigurations
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1beta1.MutatingWebhookConfiguration

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    202

    Accepted

    v1beta1.MutatingWebhookConfiguration

    200

    success

    v1beta1.MutatingWebhookConfiguration

    201

    Created

    v1beta1.MutatingWebhookConfiguration

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisadmissionregistration.k8s.iov1beta1

      +
    • +
    +
    +
    +
    +
    +

    read the specified MutatingWebhookConfiguration

    +
    +
    +
    GET /apis/admissionregistration.k8s.io/v1beta1/mutatingwebhookconfigurations/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    export

    Should this value be exported. Export strips fields that a user can not specify.

    false

    boolean

    QueryParameter

    exact

    Should the export be exact. Exact export maintains cluster-specific fields like Namespace.

    false

    boolean

    PathParameter

    name

    name of the MutatingWebhookConfiguration

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1beta1.MutatingWebhookConfiguration

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisadmissionregistration.k8s.iov1beta1

      +
    • +
    +
    +
    +
    +
    +

    replace the specified MutatingWebhookConfiguration

    +
    +
    +
    PUT /apis/admissionregistration.k8s.io/v1beta1/mutatingwebhookconfigurations/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1beta1.MutatingWebhookConfiguration

    PathParameter

    name

    name of the MutatingWebhookConfiguration

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1beta1.MutatingWebhookConfiguration

    201

    Created

    v1beta1.MutatingWebhookConfiguration

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisadmissionregistration.k8s.iov1beta1

      +
    • +
    +
    +
    +
    +
    +

    delete a MutatingWebhookConfiguration

    +
    +
    +
    DELETE /apis/admissionregistration.k8s.io/v1beta1/mutatingwebhookconfigurations/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.DeleteOptions

    QueryParameter

    gracePeriodSeconds

    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.

    false

    integer (int32)

    QueryParameter

    orphanDependents

    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.

    false

    boolean

    QueryParameter

    propagationPolicy

    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.

    false

    string

    PathParameter

    name

    name of the MutatingWebhookConfiguration

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Status

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisadmissionregistration.k8s.iov1beta1

      +
    • +
    +
    +
    +
    +
    +

    partially update the specified MutatingWebhookConfiguration

    +
    +
    +
    PATCH /apis/admissionregistration.k8s.io/v1beta1/mutatingwebhookconfigurations/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.Patch

    PathParameter

    name

    name of the MutatingWebhookConfiguration

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1beta1.MutatingWebhookConfiguration

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      application/json-patch+json

      +
    • +
    • +

      application/merge-patch+json

      +
    • +
    • +

      application/strategic-merge-patch+json

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisadmissionregistration.k8s.iov1beta1

      +
    • +
    +
    +
    +
    +
    +

    list or watch objects of kind ValidatingWebhookConfiguration

    +
    +
    +
    GET /apis/admissionregistration.k8s.io/v1beta1/validatingwebhookconfigurations
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1beta1.ValidatingWebhookConfigurationList

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    • +

      application/json;stream=watch

      +
    • +
    • +

      application/vnd.kubernetes.protobuf;stream=watch

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisadmissionregistration.k8s.iov1beta1

      +
    • +
    +
    +
    +
    +
    +

    delete collection of ValidatingWebhookConfiguration

    +
    +
    +
    DELETE /apis/admissionregistration.k8s.io/v1beta1/validatingwebhookconfigurations
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Status

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisadmissionregistration.k8s.iov1beta1

      +
    • +
    +
    +
    +
    +
    +

    create a ValidatingWebhookConfiguration

    +
    +
    +
    POST /apis/admissionregistration.k8s.io/v1beta1/validatingwebhookconfigurations
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1beta1.ValidatingWebhookConfiguration

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    202

    Accepted

    v1beta1.ValidatingWebhookConfiguration

    200

    success

    v1beta1.ValidatingWebhookConfiguration

    201

    Created

    v1beta1.ValidatingWebhookConfiguration

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisadmissionregistration.k8s.iov1beta1

      +
    • +
    +
    +
    +
    +
    +

    read the specified ValidatingWebhookConfiguration

    +
    +
    +
    GET /apis/admissionregistration.k8s.io/v1beta1/validatingwebhookconfigurations/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    export

    Should this value be exported. Export strips fields that a user can not specify.

    false

    boolean

    QueryParameter

    exact

    Should the export be exact. Exact export maintains cluster-specific fields like Namespace.

    false

    boolean

    PathParameter

    name

    name of the ValidatingWebhookConfiguration

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1beta1.ValidatingWebhookConfiguration

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisadmissionregistration.k8s.iov1beta1

      +
    • +
    +
    +
    +
    +
    +

    replace the specified ValidatingWebhookConfiguration

    +
    +
    +
    PUT /apis/admissionregistration.k8s.io/v1beta1/validatingwebhookconfigurations/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1beta1.ValidatingWebhookConfiguration

    PathParameter

    name

    name of the ValidatingWebhookConfiguration

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1beta1.ValidatingWebhookConfiguration

    201

    Created

    v1beta1.ValidatingWebhookConfiguration

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisadmissionregistration.k8s.iov1beta1

      +
    • +
    +
    +
    +
    +
    +

    delete a ValidatingWebhookConfiguration

    +
    +
    +
    DELETE /apis/admissionregistration.k8s.io/v1beta1/validatingwebhookconfigurations/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.DeleteOptions

    QueryParameter

    gracePeriodSeconds

    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.

    false

    integer (int32)

    QueryParameter

    orphanDependents

    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.

    false

    boolean

    QueryParameter

    propagationPolicy

    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.

    false

    string

    PathParameter

    name

    name of the ValidatingWebhookConfiguration

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Status

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisadmissionregistration.k8s.iov1beta1

      +
    • +
    +
    +
    +
    +
    +

    partially update the specified ValidatingWebhookConfiguration

    +
    +
    +
    PATCH /apis/admissionregistration.k8s.io/v1beta1/validatingwebhookconfigurations/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.Patch

    PathParameter

    name

    name of the ValidatingWebhookConfiguration

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1beta1.ValidatingWebhookConfiguration

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      application/json-patch+json

      +
    • +
    • +

      application/merge-patch+json

      +
    • +
    • +

      application/strategic-merge-patch+json

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisadmissionregistration.k8s.iov1beta1

      +
    • +
    +
    +
    +
    +
    +

    watch individual changes to a list of MutatingWebhookConfiguration

    +
    +
    +
    GET /apis/admissionregistration.k8s.io/v1beta1/watch/mutatingwebhookconfigurations
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.WatchEvent

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    • +

      application/json;stream=watch

      +
    • +
    • +

      application/vnd.kubernetes.protobuf;stream=watch

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisadmissionregistration.k8s.iov1beta1

      +
    • +
    +
    +
    +
    +
    +

    watch changes to an object of kind MutatingWebhookConfiguration

    +
    +
    +
    GET /apis/admissionregistration.k8s.io/v1beta1/watch/mutatingwebhookconfigurations/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    PathParameter

    name

    name of the MutatingWebhookConfiguration

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.WatchEvent

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    • +

      application/json;stream=watch

      +
    • +
    • +

      application/vnd.kubernetes.protobuf;stream=watch

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisadmissionregistration.k8s.iov1beta1

      +
    • +
    +
    +
    +
    +
    +

    watch individual changes to a list of ValidatingWebhookConfiguration

    +
    +
    +
    GET /apis/admissionregistration.k8s.io/v1beta1/watch/validatingwebhookconfigurations
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.WatchEvent

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    • +

      application/json;stream=watch

      +
    • +
    • +

      application/vnd.kubernetes.protobuf;stream=watch

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisadmissionregistration.k8s.iov1beta1

      +
    • +
    +
    +
    +
    +
    +

    watch changes to an object of kind ValidatingWebhookConfiguration

    +
    +
    +
    GET /apis/admissionregistration.k8s.io/v1beta1/watch/validatingwebhookconfigurations/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    PathParameter

    name

    name of the ValidatingWebhookConfiguration

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.WatchEvent

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    • +

      application/json;stream=watch

      +
    • +
    • +

      application/vnd.kubernetes.protobuf;stream=watch

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisadmissionregistration.k8s.iov1beta1

      +
    • +
    +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/docs/api-reference/apps/v1/definitions.html b/docs/api-reference/apps/v1/definitions.html index 5dab7704b99..f4f24b8d229 100755 --- a/docs/api-reference/apps/v1/definitions.html +++ b/docs/api-reference/apps/v1/definitions.html @@ -370,11 +370,35 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } @@ -518,6 +542,89 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } + +
    +

    v1.StatefulSetSpec

    +
    +

    A StatefulSetSpec is the specification of a StatefulSet.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    replicas

    replicas is the desired number of replicas of the given Template. These are replicas in the sense that they are instantiations of the same Template, but individual replicas also have a consistent identity. If unspecified, defaults to 1.

    false

    integer (int32)

    selector

    selector is a label query over pods that should match the replica count. It must match the pod template’s labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors

    true

    v1.LabelSelector

    template

    template is the object that describes the pod that will be created if insufficient replicas are detected. Each pod stamped out by the StatefulSet will fulfill this Template, but have a unique identity from the rest of the StatefulSet.

    true

    v1.PodTemplateSpec

    volumeClaimTemplates

    volumeClaimTemplates is a list of claims that pods are allowed to reference. The StatefulSet controller is responsible for mapping network identities to claims in a way that maintains the identity of a pod. Every claim in this list must have at least one matching (by name) volumeMount in one container in the template. A claim in this list takes precedence over any volumes in the template, with the same name.

    false

    v1.PersistentVolumeClaim array

    serviceName

    serviceName is the name of the service that governs this StatefulSet. This service must exist before the StatefulSet, and is responsible for the network identity of the set. Pods get DNS/hostnames that follow the pattern: pod-specific-string.serviceName.default.svc.cluster.local where "pod-specific-string" is managed by the StatefulSet controller.

    true

    string

    podManagementPolicy

    podManagementPolicy controls how pods are created during initial scale up, when replacing pods on nodes, or when scaling down. The default policy is OrderedReady, where pods are created in increasing order (pod-0, then pod-1, etc) and the controller will wait until each pod is ready before continuing. When scaling down, the pods are removed in the opposite order. The alternative policy is Parallel which will create pods in parallel to match the desired scale without waiting, and on scale down will delete all pods at once.

    false

    string

    updateStrategy

    updateStrategy indicates the StatefulSetUpdateStrategy that will be employed to update Pods in the StatefulSet when a revision is made to Template.

    false

    v1.StatefulSetUpdateStrategy

    revisionHistoryLimit

    revisionHistoryLimit is the maximum number of revisions that will be maintained in the StatefulSet’s revision history. The revision history consists of all revisions not represented by a currently applied StatefulSetSpec version. The default value is 10.

    false

    integer (int32)

    +

    v1.Preconditions

    @@ -789,6 +896,75 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } +
    +
    +

    v1.PersistentVolumeClaimSpec

    +
    +

    PersistentVolumeClaimSpec describes the common attributes of storage devices and allows a Source for provider-specific attributes

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    accessModes

    AccessModes contains the desired access modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1

    false

    v1.PersistentVolumeAccessMode array

    selector

    A label query over volumes to consider for binding.

    false

    v1.LabelSelector

    resources

    Resources represents the minimum resources the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources

    false

    v1.ResourceRequirements

    volumeName

    VolumeName is the binding reference to the PersistentVolume backing this claim.

    false

    string

    storageClassName

    Name of the StorageClass required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1

    false

    string

    volumeMode

    volumeMode defines what type of volume is required by the claim. Value of Filesystem is implied when not included in claim spec. This is an alpha feature and may change in the future.

    false

    v1.PersistentVolumeMode

    +

    v1.CephFSVolumeSource

    @@ -1053,6 +1229,47 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } +
    +
    +

    v1.DeploymentStrategy

    +
    +

    DeploymentStrategy describes how to replace existing pods with new ones.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    type

    Type of deployment. Can be "Recreate" or "RollingUpdate". Default is RollingUpdate.

    false

    string

    rollingUpdate

    Rolling update config params. Present only if DeploymentStrategyType = RollingUpdate.

    false

    v1.RollingUpdateDeployment

    +

    v1.ConfigMapVolumeSource

    @@ -1203,6 +1420,47 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } +
    +
    +

    v1.ScaleStatus

    +
    +

    ScaleStatus represents the current status of a scale subresource.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    replicas

    actual number of observed instances of the scaled object.

    true

    integer (int32)

    selector

    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: http://kubernetes.io/docs/user-guide/labels#label-selectors

    false

    string

    +

    v1.PortworxVolumeSource

    @@ -1529,7 +1787,7 @@ Populated by the system. Read-only. Null for lists. More info:

    deletionTimestamp

    -

    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 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.
    +

    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.

    Populated by the system when a graceful deletion is requested. Read-only. More info:
    https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

    false

    @@ -1592,7 +1850,58 @@ When an object is created, the system will populate this list with the current s
    -

    types.UID

    +

    v1.ControllerRevisionList

    +
    +

    ControllerRevisionList is a resource containing a list of ControllerRevision objects.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    kind

    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/api-conventions.md#types-kinds

    false

    string

    apiVersion

    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/api-conventions.md#resources

    false

    string

    metadata

    More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

    false

    v1.ListMeta

    items

    Items is the list of ControllerRevisions

    true

    v1.ControllerRevision array

    @@ -1642,6 +1951,10 @@ When an object is created, the system will populate this list with the current s +
    +
    +

    types.UID

    +

    v1.ISCSIVolumeSource

    @@ -1668,7 +1981,7 @@ When an object is created, the system will populate this list with the current s

    targetPortal

    -

    iSCSI target portal. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

    +

    iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

    true

    string

    @@ -1682,14 +1995,14 @@ When an object is created, the system will populate this list with the current s

    lun

    -

    iSCSI target lun number.

    +

    iSCSI Target Lun number.

    true

    integer (int32)

    iscsiInterface

    -

    Optional: Defaults to default (tcp). iSCSI interface name that uses an iSCSI transport.

    +

    iSCSI Interface Name that uses an iSCSI transport. Defaults to default (tcp).

    false

    string

    @@ -1710,7 +2023,7 @@ When an object is created, the system will populate this list with the current s

    portals

    -

    iSCSI target portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

    +

    iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

    false

    string array

    @@ -1731,14 +2044,14 @@ When an object is created, the system will populate this list with the current s

    secretRef

    -

    CHAP secret for iSCSI target and initiator authentication

    +

    CHAP Secret for iSCSI target and initiator authentication

    false

    v1.LocalObjectReference

    initiatorName

    -

    Custom iSCSI initiator name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface <target portal>:<volume name> will be created for the connection.

    +

    Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface <target portal>:<volume name> will be created for the connection.

    false

    string

    @@ -1791,7 +2104,7 @@ When an object is created, the system will populate this list with the current s

    v1.PodAffinityTerm

    -

    Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key <topologyKey> tches that of any node on which a pod of the set of pods is running

    +

    Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key <topologyKey> matches that of any node on which a pod of the set of pods is running

    @@ -1827,8 +2140,8 @@ When an object is created, the system will populate this list with the current s - - + + @@ -1883,6 +2196,68 @@ When an object is created, the system will populate this list with the current s

    topologyKey

    This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. For PreferredDuringScheduling pod anti-affinity, empty topologyKey is interpreted as "all topologies" ("all topologies" here means all the topologyKeys indicated by scheduler command-line argument --failure-domains); for affinity and for RequiredDuringScheduling pod anti-affinity, empty topologyKey is not allowed.

    false

    This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.

    true

    string

    +
    +
    +

    v1.PersistentVolumeClaim

    +
    +

    PersistentVolumeClaim is a user’s request for and claim to a persistent volume

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    kind

    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/api-conventions.md#types-kinds

    false

    string

    apiVersion

    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/api-conventions.md#resources

    false

    string

    metadata

    Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

    false

    v1.ObjectMeta

    spec

    Spec defines the desired characteristics of a volume requested by a pod author. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims

    false

    v1.PersistentVolumeClaimSpec

    status

    Status represents the current information/status of a persistent volume claim. Read-only. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims

    false

    v1.PersistentVolumeClaimStatus

    +

    v1.PodAffinity

    @@ -2054,6 +2429,130 @@ When an object is created, the system will populate this list with the current s +
    +
    +

    v1.PersistentVolumeClaimStatus

    +
    +

    PersistentVolumeClaimStatus is the current status of a persistent volume claim.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    phase

    Phase represents the current phase of PersistentVolumeClaim.

    false

    string

    accessModes

    AccessModes contains the actual access modes the volume backing the PVC has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1

    false

    v1.PersistentVolumeAccessMode array

    capacity

    Represents the actual resources of the underlying volume.

    false

    object

    conditions

    Current Condition of persistent volume claim. If underlying persistent volume is being resized then the Condition will be set to ResizeStarted.

    false

    v1.PersistentVolumeClaimCondition array

    + +
    +
    +

    v1.PersistentVolumeClaimCondition

    +
    +

    PersistentVolumeClaimCondition contails details about state of pvc

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    type

    true

    string

    status

    true

    string

    lastProbeTime

    Last time we probed the condition.

    false

    string

    lastTransitionTime

    Last time the condition transitioned from one status to another.

    false

    string

    reason

    Unique, this should be a short, machine understandable string that gives the reason for condition’s last transition. If it reports "ResizeStarted" that means the underlying persistent volume is being resized.

    false

    string

    message

    Human-readable message indicating details about last transition.

    false

    string

    +

    v1.SecretVolumeSource

    @@ -2116,7 +2615,7 @@ When an object is created, the system will populate this list with the current s

    v1.FlexVolumeSource

    -

    FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.

    +

    FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.

    @@ -2229,6 +2728,116 @@ When an object is created, the system will populate this list with the current s
    +
    +
    +

    v1.Scale

    +
    +

    Scale represents a scaling request for a resource.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    kind

    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/api-conventions.md#types-kinds

    false

    string

    apiVersion

    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/api-conventions.md#resources

    false

    string

    metadata

    Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata.

    false

    v1.ObjectMeta

    spec

    defines the behavior of the scale. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status.

    false

    v1.ScaleSpec

    status

    current status of the scale. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status. Read-only.

    false

    v1.ScaleStatus

    + +
    +
    +

    v1.KeyToPath

    +
    +

    Maps a string key to a path within a volume.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    key

    The key to project.

    true

    string

    path

    The relative path of the file to map the key to. May not be an absolute path. May not contain the path element ... May not start with the string ...

    true

    string

    mode

    Optional: mode bits to use on this file, must be a value between 0 and 0777. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.

    false

    integer (int32)

    +

    v1.AzureDiskVolumeSource

    @@ -2300,9 +2909,9 @@ When an object is created, the system will populate this list with the current s
    -

    v1.KeyToPath

    +

    v1.ReplicaSetList

    -

    Maps a string key to a path within a volume.

    +

    ReplicaSetList is a collection of ReplicaSets.

    @@ -2323,24 +2932,31 @@ When an object is created, the system will populate this list with the current s - - - - - - - - - - - - - - - - + + - + + + + + + + + + + + + + + + + + + + + + + @@ -2427,8 +3043,8 @@ When an object is created, the system will populate this list with the current s - - + + @@ -2463,6 +3079,10 @@ When an object is created, the system will populate this list with the current s

    key

    The key to project.

    true

    string

    path

    The relative path of the file to map the key to. May not be an absolute path. May not contain the path element ... May not start with the string ...

    true

    string

    mode

    Optional: mode bits to use on this file, must be a value between 0 and 0777. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.

    kind

    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/api-conventions.md#types-kinds

    false

    integer (int32)

    string

    apiVersion

    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/api-conventions.md#resources

    false

    string

    metadata

    Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

    false

    v1.ListMeta

    items

    List of ReplicaSets. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller

    true

    v1.ReplicaSet array

    selector

    A label query over pods that are managed by the daemon set. Must match in order to be controlled. If empty, defaulted to labels on Pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors

    false

    A label query over pods that are managed by the daemon set. Must match in order to be controlled. It must match the pod template’s labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors

    true

    v1.LabelSelector

    +
    +
    +

    v1.PersistentVolumeMode

    +

    v1.DeleteOptions

    @@ -2524,7 +3144,7 @@ When an object is created, the system will populate this list with the current s

    propagationPolicy

    -

    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.

    +

    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.

    false

    v1.DeletionPropagation

    @@ -2642,7 +3262,7 @@ When an object is created, the system will populate this list with the current s

    flexVolume

    -

    FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.

    +

    FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.

    false

    v1.FlexVolumeSource

    @@ -2851,6 +3471,47 @@ When an object is created, the system will populate this list with the current s +
    +
    +

    v1.WeightedPodAffinityTerm

    +
    +

    The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s)

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    weight

    weight associated with matching the corresponding podAffinityTerm, in the range 1-100.

    true

    integer (int32)

    podAffinityTerm

    Required. A pod affinity term, associated with the corresponding weight.

    true

    v1.PodAffinityTerm

    +

    v1.Probe

    @@ -2936,9 +3597,9 @@ When an object is created, the system will populate this list with the current s
    -

    v1.WeightedPodAffinityTerm

    +

    v1.RollingUpdateStatefulSetStrategy

    -

    The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s)

    +

    RollingUpdateStatefulSetStrategy is used to communicate parameter for RollingUpdateStatefulSetStrategyType.

    @@ -2959,19 +3620,12 @@ When an object is created, the system will populate this list with the current s - - - + + + - - - - - - -

    weight

    weight associated with matching the corresponding podAffinityTerm, in the range 1-100.

    true

    partition

    Partition indicates the ordinal at which the StatefulSet should be partitioned. Default value is 0.

    false

    integer (int32)

    podAffinityTerm

    Required. A pod affinity term, associated with the corresponding weight.

    true

    v1.PodAffinityTerm

    @@ -3150,7 +3804,7 @@ When an object is created, the system will populate this list with the current s

    dnsPolicy

    -

    Set DNS policy for containers within the pod. One of ClusterFirstWithHostNet, ClusterFirst or Default. Defaults to "ClusterFirst". To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to ClusterFirstWithHostNet.

    +

    Set DNS policy for the pod. Defaults to "ClusterFirst". Valid values are ClusterFirstWithHostNet, ClusterFirst, Default or None. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to ClusterFirstWithHostNet. Note that None policy is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it.

    false

    string

    @@ -3281,6 +3935,13 @@ When an object is created, the system will populate this list with the current s

    integer (int32)

    + +

    dnsConfig

    +

    Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy. This is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it.

    +

    false

    +

    v1.PodDNSConfig

    + + @@ -3597,6 +4258,75 @@ Examples:
    +
    +
    +

    v1.DeploymentCondition

    +
    +

    DeploymentCondition describes the state of a deployment at a certain point.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    type

    Type of deployment condition.

    true

    string

    status

    Status of the condition, one of True, False, Unknown.

    true

    string

    lastUpdateTime

    The last time this condition was updated.

    false

    string

    lastTransitionTime

    Last time the condition transitioned from one status to another.

    false

    string

    reason

    The reason for the condition’s last transition.

    false

    string

    message

    A human readable message indicating details about the transition.

    false

    string

    +

    v1.RBDVolumeSource

    @@ -3826,21 +4556,21 @@ Examples:

    protectionDomain

    -

    The name of the Protection Domain for the configured storage (defaults to "default").

    +

    The name of the ScaleIO Protection Domain for the configured storage.

    false

    string

    storagePool

    -

    The Storage Pool associated with the protection domain (defaults to "default").

    +

    The ScaleIO Storage Pool associated with the protection domain.

    false

    string

    storageMode

    -

    Indicates whether the storage for a volume should be thick or thin (defaults to "thin").

    +

    Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned.

    false

    string

    @@ -3914,6 +4644,54 @@ Examples:
    +
    +
    +

    v1.PodDNSConfig

    +
    +

    PodDNSConfig defines the DNS parameters of a pod in addition to those generated from DNSPolicy.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    nameservers

    A list of DNS name server IP addresses. This will be appended to the base nameservers generated from DNSPolicy. Duplicated nameservers will be removed.

    false

    string array

    searches

    A list of DNS search domains for host-name lookup. This will be appended to the base search paths generated from DNSPolicy. Duplicated search paths will be removed.

    false

    string array

    options

    A list of DNS resolver options. This will be merged with the base options generated from DNSPolicy. Duplicated entries will be removed. Resolution options given in Options will override those that appear in the base DNSPolicy.

    false

    v1.PodDNSConfigOption array

    +

    v1.Status

    @@ -4275,6 +5053,289 @@ Examples:
    +
    +
    +

    v1.StatefulSetStatus

    +
    +

    StatefulSetStatus represents the current state of a StatefulSet.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    observedGeneration

    observedGeneration is the most recent generation observed for this StatefulSet. It corresponds to the StatefulSet’s generation, which is updated on mutation by the API Server.

    false

    integer (int64)

    replicas

    replicas is the number of Pods created by the StatefulSet controller.

    true

    integer (int32)

    readyReplicas

    readyReplicas is the number of Pods created by the StatefulSet controller that have a Ready Condition.

    false

    integer (int32)

    currentReplicas

    currentReplicas is the number of Pods created by the StatefulSet controller from the StatefulSet version indicated by currentRevision.

    false

    integer (int32)

    updatedReplicas

    updatedReplicas is the number of Pods created by the StatefulSet controller from the StatefulSet version indicated by updateRevision.

    false

    integer (int32)

    currentRevision

    currentRevision, if not empty, indicates the version of the StatefulSet used to generate Pods in the sequence [0,currentReplicas).

    false

    string

    updateRevision

    updateRevision, if not empty, indicates the version of the StatefulSet used to generate Pods in the sequence [replicas-updatedReplicas,replicas)

    false

    string

    collisionCount

    collisionCount is the count of hash collisions for the StatefulSet. The StatefulSet controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ControllerRevision.

    false

    integer (int32)

    conditions

    Represents the latest available observations of a statefulset’s current state.

    false

    v1.StatefulSetCondition array

    + +
    +
    +

    v1.StatefulSetCondition

    +
    +

    StatefulSetCondition describes the state of a statefulset at a certain point.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    type

    Type of statefulset condition.

    true

    string

    status

    Status of the condition, one of True, False, Unknown.

    true

    string

    lastTransitionTime

    Last time the condition transitioned from one status to another.

    false

    string

    reason

    The reason for the condition’s last transition.

    false

    string

    message

    A human readable message indicating details about the transition.

    false

    string

    + +
    +
    +

    v1.ReplicaSetStatus

    +
    +

    ReplicaSetStatus represents the current status of a ReplicaSet.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    replicas

    Replicas is the most recently oberved number of replicas. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller

    true

    integer (int32)

    fullyLabeledReplicas

    The number of pods that have labels matching the labels of the pod template of the replicaset.

    false

    integer (int32)

    readyReplicas

    The number of ready replicas for this replica set.

    false

    integer (int32)

    availableReplicas

    The number of available replicas (ready for at least minReadySeconds) for this replica set.

    false

    integer (int32)

    observedGeneration

    ObservedGeneration reflects the generation of the most recently observed ReplicaSet.

    false

    integer (int64)

    conditions

    Represents the latest available observations of a replica set’s current state.

    false

    v1.ReplicaSetCondition array

    + +
    +
    +

    v1.ReplicaSetCondition

    +
    +

    ReplicaSetCondition describes the state of a replica set at a certain point.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    type

    Type of replica set condition.

    true

    string

    status

    Status of the condition, one of True, False, Unknown.

    true

    string

    lastTransitionTime

    The last time the condition transitioned from one status to another.

    false

    string

    reason

    The reason for the condition’s last transition.

    false

    string

    message

    A human readable message indicating details about the transition.

    false

    string

    +

    v1.HTTPGetAction

    @@ -4501,6 +5562,13 @@ Examples:
    +

    volumeDevices

    +

    volumeDevices is the list of block devices to be used by the container. This is an alpha feature and may change in the future.

    +

    false

    +

    v1.VolumeDevice array

    + + +

    livenessProbe

    Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes

    false

    @@ -4544,7 +5612,7 @@ Examples:

    securityContext

    -

    Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md

    +

    Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/

    false

    v1.SecurityContext

    @@ -4637,6 +5705,68 @@ Examples:
    +
    +
    +

    v1.ReplicaSet

    +
    +

    ReplicaSet ensures that a specified number of pod replicas are running at any given time.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    kind

    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/api-conventions.md#types-kinds

    false

    string

    apiVersion

    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/api-conventions.md#resources

    false

    string

    metadata

    If the Labels of a ReplicaSet are empty, they are defaulted to be the same as the Pod(s) that the ReplicaSet manages. Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

    false

    v1.ObjectMeta

    spec

    Spec defines the specification of the desired behavior of the ReplicaSet. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

    false

    v1.ReplicaSetSpec

    status

    Status is the most recently observed status of the ReplicaSet. This data may be out of date by some window of time. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

    false

    v1.ReplicaSetStatus

    +

    v1.OwnerReference

    @@ -4706,6 +5836,81 @@ Examples:
    +
    +
    +

    v1.ScaleSpec

    +
    +

    ScaleSpec describes the attributes of a scale subresource.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    replicas

    desired number of instances for the scaled object.

    false

    integer (int32)

    + +
    +
    +

    v1.RollingUpdateDeployment

    +
    +

    Spec to control the desired behavior of rolling update.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    maxUnavailable

    The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding down. This can not be 0 if MaxSurge is 0. Defaults to 25%. Example: when this is set to 30%, the old RC can be scaled down to 70% of desired pods immediately when the rolling update starts. Once new pods are ready, old RC can be scaled down further, followed by scaling up the new RC, ensuring that the total number of pods available at all times during the update is at least 70% of desired pods.

    false

    string

    maxSurge

    The maximum number of pods that can be scheduled above the desired number of pods. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up. Defaults to 25%. Example: when this is set to 30%, the new RC can be scaled up immediately when the rolling update starts, such that the total number of old and new pods do not exceed 130% of desired pods. Once old pods have been killed, new RC can be scaled up further, ensuring that total number of pods running at any time during the update is at most 130% of desired pods.

    false

    string

    +

    v1.APIResource

    @@ -4796,6 +6001,102 @@ Examples:
    +
    +
    +

    v1.ReplicaSetSpec

    +
    +

    ReplicaSetSpec is the specification of a ReplicaSet.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    replicas

    Replicas is the number of desired replicas. This is a pointer to distinguish between explicit zero and unspecified. Defaults to 1. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller

    false

    integer (int32)

    minReadySeconds

    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)

    false

    integer (int32)

    selector

    Selector is a label query over pods that should match the replica count. Label keys and values that must match in order to be controlled by this replica set. It must match the pod template’s labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors

    true

    v1.LabelSelector

    template

    Template is the object that describes the pod that will be created if insufficient replicas are detected. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template

    false

    v1.PodTemplateSpec

    + +
    +
    +

    v1.VolumeDevice

    +
    +

    volumeDevice describes a mapping of a raw block device within a container.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    name

    name must match the name of a persistentVolumeClaim in the pod

    true

    string

    devicePath

    devicePath is the path inside of the container that the device will be mapped to.

    true

    string

    +

    v1.NodeSelectorRequirement

    @@ -4885,6 +6186,47 @@ Examples:
    +
    +
    +

    v1.PodDNSConfigOption

    +
    +

    PodDNSConfigOption defines DNS resolver options of a pod.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    name

    Required.

    false

    string

    value

    false

    string

    +

    v1.SecretProjection

    @@ -4936,6 +6278,89 @@ Examples:
    +
    +
    +

    v1.DeploymentStatus

    +
    +

    DeploymentStatus is the most recently observed status of the Deployment.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    observedGeneration

    The generation observed by the deployment controller.

    false

    integer (int64)

    replicas

    Total number of non-terminated pods targeted by this deployment (their labels match the selector).

    false

    integer (int32)

    updatedReplicas

    Total number of non-terminated pods targeted by this deployment that have the desired template spec.

    false

    integer (int32)

    readyReplicas

    Total number of ready pods targeted by this deployment.

    false

    integer (int32)

    availableReplicas

    Total number of available pods (ready for at least minReadySeconds) targeted by this deployment.

    false

    integer (int32)

    unavailableReplicas

    Total number of unavailable pods targeted by this deployment. This is the total number of pods that are still required for the deployment to have 100% available capacity. They may either be pods that are running but not yet available or pods that still have not been created.

    false

    integer (int32)

    conditions

    Represents the latest available observations of a deployment’s current state.

    false

    v1.DeploymentCondition array

    collisionCount

    Count of hash collisions for the Deployment. The Deployment controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ReplicaSet.

    false

    integer (int32)

    +

    v1.CinderVolumeSource

    @@ -4984,6 +6409,61 @@ Examples:
    +
    +
    +

    v1.DeploymentList

    +
    +

    DeploymentList is a list of Deployments.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    kind

    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/api-conventions.md#types-kinds

    false

    string

    apiVersion

    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/api-conventions.md#resources

    false

    string

    metadata

    Standard list metadata.

    false

    v1.ListMeta

    items

    Items is the list of Deployments.

    true

    v1.Deployment array

    +

    v1.SecurityContext

    @@ -5180,6 +6660,329 @@ Examples:
    +
    +
    +

    v1.StatefulSet

    +
    +

    StatefulSet represents a set of pods with consistent identities. Identities are defined as:
    + - Network: A single stable DNS and hostname.
    + - Storage: As many VolumeClaims as requested.
    +The StatefulSet guarantees that a given network identity will always map to the same storage identity.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    kind

    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/api-conventions.md#types-kinds

    false

    string

    apiVersion

    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/api-conventions.md#resources

    false

    string

    metadata

    false

    v1.ObjectMeta

    spec

    Spec defines the desired identities of pods in this set.

    false

    v1.StatefulSetSpec

    status

    Status is the current status of Pods in this StatefulSet. This data may be out of date by some window of time.

    false

    v1.StatefulSetStatus

    + +
    +
    +

    v1.Deployment

    +
    +

    Deployment enables declarative updates for Pods and ReplicaSets.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    kind

    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/api-conventions.md#types-kinds

    false

    string

    apiVersion

    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/api-conventions.md#resources

    false

    string

    metadata

    Standard object metadata.

    false

    v1.ObjectMeta

    spec

    Specification of the desired behavior of the Deployment.

    false

    v1.DeploymentSpec

    status

    Most recently observed status of the Deployment.

    false

    v1.DeploymentStatus

    + +
    +
    +

    v1.StatefulSetUpdateStrategy

    +
    +

    StatefulSetUpdateStrategy indicates the strategy that the StatefulSet controller will use to perform updates. It includes any additional parameters necessary to perform the update for the indicated strategy.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    type

    Type indicates the type of the StatefulSetUpdateStrategy. Default is RollingUpdate.

    false

    string

    rollingUpdate

    RollingUpdate is used to communicate parameters when Type is RollingUpdateStatefulSetStrategyType.

    false

    v1.RollingUpdateStatefulSetStrategy

    + +
    +
    +

    v1.ControllerRevision

    +
    +

    ControllerRevision implements an immutable snapshot of state data. Clients are responsible for serializing and deserializing the objects that contain their internal state. Once a ControllerRevision has been successfully created, it can not be updated. The API Server will fail validation of all requests that attempt to mutate the Data field. ControllerRevisions may, however, be deleted. Note that, due to its use by both the DaemonSet and StatefulSet controllers for update and rollback, this object is beta. However, it may be subject to name and representation changes in future releases, and clients should not depend on its stability. It is primarily for internal use by controllers.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    kind

    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/api-conventions.md#types-kinds

    false

    string

    apiVersion

    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/api-conventions.md#resources

    false

    string

    metadata

    Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

    false

    v1.ObjectMeta

    data

    Data is the serialized representation of the state.

    false

    string

    revision

    Revision indicates the revision of the state represented by Data.

    true

    integer (int64)

    + +
    +
    +

    v1.StatefulSetList

    +
    +

    StatefulSetList is a collection of StatefulSets.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    kind

    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/api-conventions.md#types-kinds

    false

    string

    apiVersion

    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/api-conventions.md#resources

    false

    string

    metadata

    false

    v1.ListMeta

    items

    true

    v1.StatefulSet array

    + +
    +
    +

    v1.WatchEvent

    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    type

    true

    string

    object

    true

    string

    +

    v1.DaemonSetList

    @@ -5235,44 +7038,6 @@ Examples:
    -
    -
    -

    v1.WatchEvent

    - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameDescriptionRequiredSchemaDefault

    type

    true

    string

    object

    true

    string

    -

    v1.LabelSelectorRequirement

    @@ -5369,6 +7134,10 @@ Examples:
    +
    +
    +

    v1.PersistentVolumeAccessMode

    +

    v1.ResourceRequirements

    @@ -5410,6 +7179,68 @@ Examples:
    +
    +
    +

    v1.DaemonSetCondition

    +
    +

    DaemonSetCondition describes the state of a DaemonSet at a certain point.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    type

    Type of DaemonSet condition.

    true

    string

    status

    Status of the condition, one of True, False, Unknown.

    true

    string

    lastTransitionTime

    Last time the condition transitioned from one status to another.

    false

    string

    reason

    The reason for the condition’s last transition.

    false

    string

    message

    A human readable message indicating details about the transition.

    false

    string

    +

    v1.HostAlias

    @@ -5526,6 +7357,89 @@ Examples:
    +
    +
    +

    v1.DeploymentSpec

    +
    +

    DeploymentSpec is the specification of the desired behavior of the Deployment.

    +
    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredSchemaDefault

    replicas

    Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1.

    false

    integer (int32)

    selector

    Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment. It must match the pod template’s labels.

    true

    v1.LabelSelector

    template

    Template describes the pods that will be created.

    true

    v1.PodTemplateSpec

    strategy

    The deployment strategy to use to replace existing pods with new ones.

    false

    v1.DeploymentStrategy

    minReadySeconds

    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)

    false

    integer (int32)

    revisionHistoryLimit

    The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.

    false

    integer (int32)

    paused

    Indicates that the deployment is paused.

    false

    boolean

    false

    progressDeadlineSeconds

    The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that progress will not be estimated during the time a deployment is paused. Defaults to 600s.

    false

    integer (int32)

    +

    v1.Patch

    @@ -5725,6 +7639,13 @@ Examples:

    integer (int32)

    + +

    conditions

    +

    Represents the latest available observations of a DaemonSet’s current state.

    +

    false

    +

    v1.DaemonSetCondition array

    + + diff --git a/docs/api-reference/apps/v1/operations.html b/docs/api-reference/apps/v1/operations.html index 341fb125534..c0b38dd26bd 100755 --- a/docs/api-reference/apps/v1/operations.html +++ b/docs/api-reference/apps/v1/operations.html @@ -443,10 +443,10 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    list or watch objects of kind DaemonSet

    +

    list or watch objects of kind ControllerRevision

    -
    GET /apis/apps/v1/daemonsets
    +
    GET /apis/apps/v1/controllerrevisions
    @@ -567,7 +567,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

    200

    success

    -

    v1.DaemonSetList

    +

    v1.ControllerRevisionList

    @@ -617,10 +617,10 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    list or watch objects of kind DaemonSet

    +

    list or watch objects of kind DaemonSet

    -
    GET /apis/apps/v1/namespaces/{namespace}/daemonsets
    +
    GET /apis/apps/v1/daemonsets
    @@ -718,14 +718,6 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

    string

    - -

    PathParameter

    -

    namespace

    -

    object name and auth scope, such as for teams and projects

    -

    true

    -

    string

    - - @@ -799,10 +791,10 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    delete collection of DaemonSet

    +

    list or watch objects of kind Deployment

    -
    DELETE /apis/apps/v1/namespaces/{namespace}/daemonsets
    +
    GET /apis/apps/v1/deployments
    @@ -900,14 +892,6 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

    string

    - -

    PathParameter

    -

    namespace

    -

    object name and auth scope, such as for teams and projects

    -

    true

    -

    string

    - - @@ -931,7 +915,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

    200

    success

    -

    v1.Status

    +

    v1.DeploymentList

    @@ -960,6 +944,12 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
  • application/vnd.kubernetes.protobuf

  • +
  • +

    application/json;stream=watch

    +
  • +
  • +

    application/vnd.kubernetes.protobuf;stream=watch

    +
  • @@ -975,10 +965,10 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    create a DaemonSet

    +

    list or watch objects of kind ControllerRevision

    -
    POST /apis/apps/v1/namespaces/{namespace}/daemonsets
    +
    GET /apis/apps/v1/namespaces/{namespace}/controllerrevisions
    @@ -1012,11 +1002,68 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } -

    BodyParameter

    -

    body

    +

    QueryParameter

    +

    labelSelector

    +

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    +

    false

    +

    string

    -

    true

    -

    v1.DaemonSet

    + + +

    QueryParameter

    +

    fieldSelector

    +

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    +

    false

    +

    string

    + + + +

    QueryParameter

    +

    includeUninitialized

    +

    If true, partially initialized resources are included in the response.

    +

    false

    +

    boolean

    + + + +

    QueryParameter

    +

    watch

    +

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    +

    false

    +

    boolean

    + + + +

    QueryParameter

    +

    resourceVersion

    +

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    +

    false

    +

    string

    + + + +

    QueryParameter

    +

    timeoutSeconds

    +

    Timeout for the list/watch call.

    +

    false

    +

    integer (int32)

    + + + +

    QueryParameter

    +

    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. +

    The 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.

    +

    false

    +

    integer (int32)

    + + + +

    QueryParameter

    +

    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 indicating the client must restart their list without the continue field. This 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.

    +

    false

    +

    string

    @@ -1048,19 +1095,9 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } -

    202

    -

    Accepted

    -

    v1.DaemonSet

    - -

    200

    success

    -

    v1.DaemonSet

    - - -

    201

    -

    Created

    -

    v1.DaemonSet

    +

    v1.ControllerRevisionList

    @@ -1089,6 +1126,12 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
  • application/vnd.kubernetes.protobuf

  • +
  • +

    application/json;stream=watch

    +
  • +
  • +

    application/vnd.kubernetes.protobuf;stream=watch

    +
  • @@ -1104,10 +1147,10 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    read the specified DaemonSet

    +

    delete collection of ControllerRevision

    -
    GET /apis/apps/v1/namespaces/{namespace}/daemonsets/{name}
    +
    DELETE /apis/apps/v1/namespaces/{namespace}/controllerrevisions
    @@ -1142,6 +1185,311 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

    QueryParameter

    +

    labelSelector

    +

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    +

    false

    +

    string

    + + + +

    QueryParameter

    +

    fieldSelector

    +

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    +

    false

    +

    string

    + + + +

    QueryParameter

    +

    includeUninitialized

    +

    If true, partially initialized resources are included in the response.

    +

    false

    +

    boolean

    + + + +

    QueryParameter

    +

    watch

    +

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    +

    false

    +

    boolean

    + + + +

    QueryParameter

    +

    resourceVersion

    +

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    +

    false

    +

    string

    + + + +

    QueryParameter

    +

    timeoutSeconds

    +

    Timeout for the list/watch call.

    +

    false

    +

    integer (int32)

    + + + +

    QueryParameter

    +

    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. +

    The 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.

    +

    false

    +

    integer (int32)

    + + + +

    QueryParameter

    +

    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 indicating the client must restart their list without the continue field. This 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.

    +

    false

    +

    string

    + + + +

    PathParameter

    +

    namespace

    +

    object name and auth scope, such as for teams and projects

    +

    true

    +

    string

    + + + + + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Status

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    create a ControllerRevision

    +
    +
    +
    POST /apis/apps/v1/namespaces/{namespace}/controllerrevisions
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.ControllerRevision

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    202

    Accepted

    v1.ControllerRevision

    200

    success

    v1.ControllerRevision

    201

    Created

    v1.ControllerRevision

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    read the specified ControllerRevision

    +
    +
    +
    GET /apis/apps/v1/namespaces/{namespace}/controllerrevisions/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + @@ -1167,7 +1515,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - + @@ -1177,7 +1525,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    Responses

    +

    Responses

    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    export

    Should this value be exported. Export strips fields that a user can not specify.

    false

    PathParameter

    name

    name of the DaemonSet

    name of the ControllerRevision

    true

    string

    @@ -1195,14 +1543,14 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - +

    200

    success

    v1.DaemonSet

    v1.ControllerRevision

    -

    Consumes

    +

    Consumes

    • @@ -1212,7 +1560,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    Produces

    +

    Produces

    • @@ -1228,7 +1576,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    Tags

    +

    Tags

    • @@ -1239,14 +1587,14 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    replace the specified DaemonSet

    +

    replace the specified ControllerRevision

    -
    PUT /apis/apps/v1/namespaces/{namespace}/daemonsets/{name}
    +
    PUT /apis/apps/v1/namespaces/{namespace}/controllerrevisions/{name}
    -

    Parameters

    +

    Parameters

    @@ -1280,7 +1628,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - + @@ -1294,7 +1642,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - + @@ -1304,7 +1652,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    Responses

    +

    Responses

    body

    true

    v1.DaemonSet

    v1.ControllerRevision

    PathParameter

    name

    name of the DaemonSet

    name of the ControllerRevision

    true

    string

    @@ -1322,19 +1670,19 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - + - +

    200

    success

    v1.DaemonSet

    v1.ControllerRevision

    201

    Created

    v1.DaemonSet

    v1.ControllerRevision

    -

    Consumes

    +

    Consumes

    • @@ -1344,7 +1692,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    Produces

    +

    Produces

    • @@ -1360,7 +1708,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    Tags

    +

    Tags

    • @@ -1371,14 +1719,14 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    delete a DaemonSet

    +

    delete a ControllerRevision

    -
    DELETE /apis/apps/v1/namespaces/{namespace}/daemonsets/{name}
    +
    DELETE /apis/apps/v1/namespaces/{namespace}/controllerrevisions/{name}
    -

    Parameters

    +

    Parameters

    @@ -1434,7 +1782,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - + @@ -1450,259 +1798,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - - - - - - -

    QueryParameter

    propagationPolicy

    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.

    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.

    false

    string

    PathParameter

    name

    name of the DaemonSet

    true

    string

    - -
    -
    -

    Responses

    - ----- - - - - - - - - - - - - - - -
    HTTP CodeDescriptionSchema

    200

    success

    v1.Status

    - -
    -
    -

    Consumes

    -
    -
      -
    • -

      /

      -
    • -
    -
    -
    -
    -

    Produces

    -
    -
      -
    • -

      application/json

      -
    • -
    • -

      application/yaml

      -
    • -
    • -

      application/vnd.kubernetes.protobuf

      -
    • -
    -
    -
    -
    -

    Tags

    -
    -
      -
    • -

      apisappsv1

      -
    • -
    -
    -
    -
    -
    -

    partially update the specified DaemonSet

    -
    -
    -
    PATCH /apis/apps/v1/namespaces/{namespace}/daemonsets/{name}
    -
    -
    -
    -

    Parameters

    - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.Patch

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the DaemonSet

    true

    string

    - -
    -
    -

    Responses

    - ----- - - - - - - - - - - - - - - -
    HTTP CodeDescriptionSchema

    200

    success

    v1.DaemonSet

    - -
    -
    -

    Consumes

    -
    -
      -
    • -

      application/json-patch+json

      -
    • -
    • -

      application/merge-patch+json

      -
    • -
    • -

      application/strategic-merge-patch+json

      -
    • -
    -
    -
    -
    -

    Produces

    -
    -
      -
    • -

      application/json

      -
    • -
    • -

      application/yaml

      -
    • -
    • -

      application/vnd.kubernetes.protobuf

      -
    • -
    -
    -
    -
    -

    Tags

    -
    -
      -
    • -

      apisappsv1

      -
    • -
    -
    -
    -
    -
    -

    read status of the specified DaemonSet

    -
    -
    -
    GET /apis/apps/v1/namespaces/{namespace}/daemonsets/{name}/status
    -
    -
    -
    -

    Parameters

    - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -1730,7 +1826,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the DaemonSet

    name of the ControllerRevision

    true

    string

    200

    success

    v1.DaemonSet

    v1.Status

    @@ -1774,10 +1870,10 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    replace status of the specified DaemonSet

    +

    partially update the specified ControllerRevision

    -
    PUT /apis/apps/v1/namespaces/{namespace}/daemonsets/{name}/status
    +
    PATCH /apis/apps/v1/namespaces/{namespace}/controllerrevisions/{name}
    @@ -1815,138 +1911,6 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

    body

    true

    -

    v1.DaemonSet

    - - - -

    PathParameter

    -

    namespace

    -

    object name and auth scope, such as for teams and projects

    -

    true

    -

    string

    - - - -

    PathParameter

    -

    name

    -

    name of the DaemonSet

    -

    true

    -

    string

    - - - - - -
    -
    -

    Responses

    - ----- - - - - - - - - - - - - - - - - - - - -
    HTTP CodeDescriptionSchema

    200

    success

    v1.DaemonSet

    201

    Created

    v1.DaemonSet

    - -
    -
    -

    Consumes

    -
    -
      -
    • -

      /

      -
    • -
    -
    -
    -
    -

    Produces

    -
    -
      -
    • -

      application/json

      -
    • -
    • -

      application/yaml

      -
    • -
    • -

      application/vnd.kubernetes.protobuf

      -
    • -
    -
    -
    -
    -

    Tags

    -
    -
      -
    • -

      apisappsv1

      -
    • -
    -
    -
    -
    -
    -

    partially update status of the specified DaemonSet

    -
    -
    -
    PATCH /apis/apps/v1/namespaces/{namespace}/daemonsets/{name}/status
    -
    -
    -
    -

    Parameters

    - -------- - - - - - - - - - - - - - - - - - - - - - - - - @@ -1961,7 +1925,189 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.Patch

    PathParameter

    name

    name of the DaemonSet

    name of the ControllerRevision

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.ControllerRevision

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      application/json-patch+json

      +
    • +
    • +

      application/merge-patch+json

      +
    • +
    • +

      application/strategic-merge-patch+json

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    list or watch objects of kind DaemonSet

    +
    +
    +
    GET /apis/apps/v1/namespaces/{namespace}/daemonsets
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1989,7 +2135,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    200

    success

    v1.DaemonSet

    v1.DaemonSetList

    @@ -2000,13 +2146,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    • -

      application/json-patch+json

      -
    • -
    • -

      application/merge-patch+json

      -
    • -
    • -

      application/strategic-merge-patch+json

      +

      /

    @@ -2024,6 +2164,12 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
  • application/vnd.kubernetes.protobuf

  • +
  • +

    application/json;stream=watch

    +
  • +
  • +

    application/vnd.kubernetes.protobuf;stream=watch

    +
  • @@ -2039,10 +2185,10 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    watch individual changes to a list of DaemonSet

    +

    delete collection of DaemonSet

    -
    GET /apis/apps/v1/watch/daemonsets
    +
    DELETE /apis/apps/v1/namespaces/{namespace}/daemonsets
    @@ -2140,6 +2286,14 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

    string

    + +

    PathParameter

    +

    namespace

    +

    object name and auth scope, such as for teams and projects

    +

    true

    +

    string

    + + @@ -2163,7 +2317,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

    200

    success

    -

    v1.WatchEvent

    +

    v1.Status

    @@ -2192,12 +2346,6 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
  • application/vnd.kubernetes.protobuf

  • -
  • -

    application/json;stream=watch

    -
  • -
  • -

    application/vnd.kubernetes.protobuf;stream=watch

    -
  • @@ -2213,10 +2361,10 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    watch individual changes to a list of DaemonSet

    +

    create a DaemonSet

    -
    GET /apis/apps/v1/watch/namespaces/{namespace}/daemonsets
    +
    POST /apis/apps/v1/namespaces/{namespace}/daemonsets
    @@ -2250,6 +2398,1070 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } +

    BodyParameter

    +

    body

    + +

    true

    +

    v1.DaemonSet

    + + + +

    PathParameter

    +

    namespace

    +

    object name and auth scope, such as for teams and projects

    +

    true

    +

    string

    + + + + + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    202

    Accepted

    v1.DaemonSet

    200

    success

    v1.DaemonSet

    201

    Created

    v1.DaemonSet

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    read the specified DaemonSet

    +
    +
    +
    GET /apis/apps/v1/namespaces/{namespace}/daemonsets/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    export

    Should this value be exported. Export strips fields that a user can not specify.

    false

    boolean

    QueryParameter

    exact

    Should the export be exact. Exact export maintains cluster-specific fields like Namespace.

    false

    boolean

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the DaemonSet

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.DaemonSet

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    replace the specified DaemonSet

    +
    +
    +
    PUT /apis/apps/v1/namespaces/{namespace}/daemonsets/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.DaemonSet

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the DaemonSet

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.DaemonSet

    201

    Created

    v1.DaemonSet

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    delete a DaemonSet

    +
    +
    +
    DELETE /apis/apps/v1/namespaces/{namespace}/daemonsets/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.DeleteOptions

    QueryParameter

    gracePeriodSeconds

    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.

    false

    integer (int32)

    QueryParameter

    orphanDependents

    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.

    false

    boolean

    QueryParameter

    propagationPolicy

    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.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the DaemonSet

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Status

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    partially update the specified DaemonSet

    +
    +
    +
    PATCH /apis/apps/v1/namespaces/{namespace}/daemonsets/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.Patch

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the DaemonSet

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.DaemonSet

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      application/json-patch+json

      +
    • +
    • +

      application/merge-patch+json

      +
    • +
    • +

      application/strategic-merge-patch+json

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    read status of the specified DaemonSet

    +
    +
    +
    GET /apis/apps/v1/namespaces/{namespace}/daemonsets/{name}/status
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the DaemonSet

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.DaemonSet

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    replace status of the specified DaemonSet

    +
    +
    +
    PUT /apis/apps/v1/namespaces/{namespace}/daemonsets/{name}/status
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.DaemonSet

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the DaemonSet

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.DaemonSet

    201

    Created

    v1.DaemonSet

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    partially update status of the specified DaemonSet

    +
    +
    +
    PATCH /apis/apps/v1/namespaces/{namespace}/daemonsets/{name}/status
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.Patch

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the DaemonSet

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.DaemonSet

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      application/json-patch+json

      +
    • +
    • +

      application/merge-patch+json

      +
    • +
    • +

      application/strategic-merge-patch+json

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    list or watch objects of kind Deployment

    +
    +
    +
    GET /apis/apps/v1/namespaces/{namespace}/deployments
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + @@ -2327,7 +3539,5765 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    Responses

    +

    Responses

    +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.DeploymentList

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    • +

      application/json;stream=watch

      +
    • +
    • +

      application/vnd.kubernetes.protobuf;stream=watch

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    delete collection of Deployment

    +
    +
    +
    DELETE /apis/apps/v1/namespaces/{namespace}/deployments
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Status

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    create a Deployment

    +
    +
    +
    POST /apis/apps/v1/namespaces/{namespace}/deployments
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.Deployment

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    202

    Accepted

    v1.Deployment

    200

    success

    v1.Deployment

    201

    Created

    v1.Deployment

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    read the specified Deployment

    +
    +
    +
    GET /apis/apps/v1/namespaces/{namespace}/deployments/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    export

    Should this value be exported. Export strips fields that a user can not specify.

    false

    boolean

    QueryParameter

    exact

    Should the export be exact. Exact export maintains cluster-specific fields like Namespace.

    false

    boolean

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the Deployment

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Deployment

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    replace the specified Deployment

    +
    +
    +
    PUT /apis/apps/v1/namespaces/{namespace}/deployments/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.Deployment

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the Deployment

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Deployment

    201

    Created

    v1.Deployment

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    delete a Deployment

    +
    +
    +
    DELETE /apis/apps/v1/namespaces/{namespace}/deployments/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.DeleteOptions

    QueryParameter

    gracePeriodSeconds

    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.

    false

    integer (int32)

    QueryParameter

    orphanDependents

    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.

    false

    boolean

    QueryParameter

    propagationPolicy

    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.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the Deployment

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Status

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    partially update the specified Deployment

    +
    +
    +
    PATCH /apis/apps/v1/namespaces/{namespace}/deployments/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.Patch

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the Deployment

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Deployment

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      application/json-patch+json

      +
    • +
    • +

      application/merge-patch+json

      +
    • +
    • +

      application/strategic-merge-patch+json

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    read scale of the specified Deployment

    +
    +
    +
    GET /apis/apps/v1/namespaces/{namespace}/deployments/{name}/scale
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the Scale

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Scale

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    replace scale of the specified Deployment

    +
    +
    +
    PUT /apis/apps/v1/namespaces/{namespace}/deployments/{name}/scale
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.Scale

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the Scale

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Scale

    201

    Created

    v1.Scale

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    partially update scale of the specified Deployment

    +
    +
    +
    PATCH /apis/apps/v1/namespaces/{namespace}/deployments/{name}/scale
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.Patch

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the Scale

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Scale

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      application/json-patch+json

      +
    • +
    • +

      application/merge-patch+json

      +
    • +
    • +

      application/strategic-merge-patch+json

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    read status of the specified Deployment

    +
    +
    +
    GET /apis/apps/v1/namespaces/{namespace}/deployments/{name}/status
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the Deployment

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Deployment

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    replace status of the specified Deployment

    +
    +
    +
    PUT /apis/apps/v1/namespaces/{namespace}/deployments/{name}/status
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.Deployment

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the Deployment

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Deployment

    201

    Created

    v1.Deployment

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    partially update status of the specified Deployment

    +
    +
    +
    PATCH /apis/apps/v1/namespaces/{namespace}/deployments/{name}/status
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.Patch

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the Deployment

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Deployment

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      application/json-patch+json

      +
    • +
    • +

      application/merge-patch+json

      +
    • +
    • +

      application/strategic-merge-patch+json

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    list or watch objects of kind ReplicaSet

    +
    +
    +
    GET /apis/apps/v1/namespaces/{namespace}/replicasets
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.ReplicaSetList

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    • +

      application/json;stream=watch

      +
    • +
    • +

      application/vnd.kubernetes.protobuf;stream=watch

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    delete collection of ReplicaSet

    +
    +
    +
    DELETE /apis/apps/v1/namespaces/{namespace}/replicasets
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Status

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    create a ReplicaSet

    +
    +
    +
    POST /apis/apps/v1/namespaces/{namespace}/replicasets
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.ReplicaSet

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    202

    Accepted

    v1.ReplicaSet

    200

    success

    v1.ReplicaSet

    201

    Created

    v1.ReplicaSet

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    read the specified ReplicaSet

    +
    +
    +
    GET /apis/apps/v1/namespaces/{namespace}/replicasets/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    export

    Should this value be exported. Export strips fields that a user can not specify.

    false

    boolean

    QueryParameter

    exact

    Should the export be exact. Exact export maintains cluster-specific fields like Namespace.

    false

    boolean

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the ReplicaSet

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.ReplicaSet

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    replace the specified ReplicaSet

    +
    +
    +
    PUT /apis/apps/v1/namespaces/{namespace}/replicasets/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.ReplicaSet

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the ReplicaSet

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.ReplicaSet

    201

    Created

    v1.ReplicaSet

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    delete a ReplicaSet

    +
    +
    +
    DELETE /apis/apps/v1/namespaces/{namespace}/replicasets/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.DeleteOptions

    QueryParameter

    gracePeriodSeconds

    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.

    false

    integer (int32)

    QueryParameter

    orphanDependents

    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.

    false

    boolean

    QueryParameter

    propagationPolicy

    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.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the ReplicaSet

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Status

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    partially update the specified ReplicaSet

    +
    +
    +
    PATCH /apis/apps/v1/namespaces/{namespace}/replicasets/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.Patch

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the ReplicaSet

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.ReplicaSet

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      application/json-patch+json

      +
    • +
    • +

      application/merge-patch+json

      +
    • +
    • +

      application/strategic-merge-patch+json

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    read scale of the specified ReplicaSet

    +
    +
    +
    GET /apis/apps/v1/namespaces/{namespace}/replicasets/{name}/scale
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the Scale

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Scale

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    replace scale of the specified ReplicaSet

    +
    +
    +
    PUT /apis/apps/v1/namespaces/{namespace}/replicasets/{name}/scale
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.Scale

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the Scale

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Scale

    201

    Created

    v1.Scale

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    partially update scale of the specified ReplicaSet

    +
    +
    +
    PATCH /apis/apps/v1/namespaces/{namespace}/replicasets/{name}/scale
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.Patch

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the Scale

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Scale

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      application/json-patch+json

      +
    • +
    • +

      application/merge-patch+json

      +
    • +
    • +

      application/strategic-merge-patch+json

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    read status of the specified ReplicaSet

    +
    +
    +
    GET /apis/apps/v1/namespaces/{namespace}/replicasets/{name}/status
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the ReplicaSet

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.ReplicaSet

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    replace status of the specified ReplicaSet

    +
    +
    +
    PUT /apis/apps/v1/namespaces/{namespace}/replicasets/{name}/status
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.ReplicaSet

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the ReplicaSet

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.ReplicaSet

    201

    Created

    v1.ReplicaSet

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    partially update status of the specified ReplicaSet

    +
    +
    +
    PATCH /apis/apps/v1/namespaces/{namespace}/replicasets/{name}/status
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.Patch

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the ReplicaSet

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.ReplicaSet

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      application/json-patch+json

      +
    • +
    • +

      application/merge-patch+json

      +
    • +
    • +

      application/strategic-merge-patch+json

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    list or watch objects of kind StatefulSet

    +
    +
    +
    GET /apis/apps/v1/namespaces/{namespace}/statefulsets
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.StatefulSetList

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    • +

      application/json;stream=watch

      +
    • +
    • +

      application/vnd.kubernetes.protobuf;stream=watch

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    delete collection of StatefulSet

    +
    +
    +
    DELETE /apis/apps/v1/namespaces/{namespace}/statefulsets
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Status

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    create a StatefulSet

    +
    +
    +
    POST /apis/apps/v1/namespaces/{namespace}/statefulsets
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.StatefulSet

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    202

    Accepted

    v1.StatefulSet

    200

    success

    v1.StatefulSet

    201

    Created

    v1.StatefulSet

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    read the specified StatefulSet

    +
    +
    +
    GET /apis/apps/v1/namespaces/{namespace}/statefulsets/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    export

    Should this value be exported. Export strips fields that a user can not specify.

    false

    boolean

    QueryParameter

    exact

    Should the export be exact. Exact export maintains cluster-specific fields like Namespace.

    false

    boolean

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the StatefulSet

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.StatefulSet

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    replace the specified StatefulSet

    +
    +
    +
    PUT /apis/apps/v1/namespaces/{namespace}/statefulsets/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.StatefulSet

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the StatefulSet

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.StatefulSet

    201

    Created

    v1.StatefulSet

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    delete a StatefulSet

    +
    +
    +
    DELETE /apis/apps/v1/namespaces/{namespace}/statefulsets/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.DeleteOptions

    QueryParameter

    gracePeriodSeconds

    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.

    false

    integer (int32)

    QueryParameter

    orphanDependents

    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.

    false

    boolean

    QueryParameter

    propagationPolicy

    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.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the StatefulSet

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Status

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    partially update the specified StatefulSet

    +
    +
    +
    PATCH /apis/apps/v1/namespaces/{namespace}/statefulsets/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.Patch

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the StatefulSet

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.StatefulSet

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      application/json-patch+json

      +
    • +
    • +

      application/merge-patch+json

      +
    • +
    • +

      application/strategic-merge-patch+json

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    read scale of the specified StatefulSet

    +
    +
    +
    GET /apis/apps/v1/namespaces/{namespace}/statefulsets/{name}/scale
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the Scale

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Scale

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    replace scale of the specified StatefulSet

    +
    +
    +
    PUT /apis/apps/v1/namespaces/{namespace}/statefulsets/{name}/scale
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.Scale

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the Scale

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Scale

    201

    Created

    v1.Scale

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    partially update scale of the specified StatefulSet

    +
    +
    +
    PATCH /apis/apps/v1/namespaces/{namespace}/statefulsets/{name}/scale
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.Patch

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the Scale

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.Scale

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      application/json-patch+json

      +
    • +
    • +

      application/merge-patch+json

      +
    • +
    • +

      application/strategic-merge-patch+json

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    read status of the specified StatefulSet

    +
    +
    +
    GET /apis/apps/v1/namespaces/{namespace}/statefulsets/{name}/status
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the StatefulSet

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.StatefulSet

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    replace status of the specified StatefulSet

    +
    +
    +
    PUT /apis/apps/v1/namespaces/{namespace}/statefulsets/{name}/status
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.StatefulSet

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the StatefulSet

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.StatefulSet

    201

    Created

    v1.StatefulSet

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    partially update status of the specified StatefulSet

    +
    +
    +
    PATCH /apis/apps/v1/namespaces/{namespace}/statefulsets/{name}/status
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    BodyParameter

    body

    true

    v1.Patch

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the StatefulSet

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.StatefulSet

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      application/json-patch+json

      +
    • +
    • +

      application/merge-patch+json

      +
    • +
    • +

      application/strategic-merge-patch+json

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    list or watch objects of kind ReplicaSet

    +
    +
    +
    GET /apis/apps/v1/replicasets
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.ReplicaSetList

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    • +

      application/json;stream=watch

      +
    • +
    • +

      application/vnd.kubernetes.protobuf;stream=watch

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    list or watch objects of kind StatefulSet

    +
    +
    +
    GET /apis/apps/v1/statefulsets
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.StatefulSetList

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    • +

      application/json;stream=watch

      +
    • +
    • +

      application/vnd.kubernetes.protobuf;stream=watch

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    watch individual changes to a list of ControllerRevision

    +
    +
    +
    GET /apis/apps/v1/watch/controllerrevisions
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    + +
    +
    +

    Responses

    @@ -2352,7 +9322,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    Consumes

    +

    Consumes

    • @@ -2362,7 +9332,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    Produces

    +

    Produces

    • @@ -2384,7 +9354,909 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    Tags

    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    + +
    +

    watch individual changes to a list of DaemonSet

    +
    +
    +
    GET /apis/apps/v1/watch/daemonsets
    +
    +
    +
    +

    Parameters

    +
    ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.WatchEvent

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    • +

      application/json;stream=watch

      +
    • +
    • +

      application/vnd.kubernetes.protobuf;stream=watch

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    watch individual changes to a list of Deployment

    +
    +
    +
    GET /apis/apps/v1/watch/deployments
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.WatchEvent

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    • +

      application/json;stream=watch

      +
    • +
    • +

      application/vnd.kubernetes.protobuf;stream=watch

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    watch individual changes to a list of ControllerRevision

    +
    +
    +
    GET /apis/apps/v1/watch/namespaces/{namespace}/controllerrevisions
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.WatchEvent

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    • +

      application/json;stream=watch

      +
    • +
    • +

      application/vnd.kubernetes.protobuf;stream=watch

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    watch changes to an object of kind ControllerRevision

    +
    +
    +
    GET /apis/apps/v1/watch/namespaces/{namespace}/controllerrevisions/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the ControllerRevision

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.WatchEvent

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    • +

      application/json;stream=watch

      +
    • +
    • +

      application/vnd.kubernetes.protobuf;stream=watch

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    watch individual changes to a list of DaemonSet

    +
    +
    +
    GET /apis/apps/v1/watch/namespaces/{namespace}/daemonsets
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.WatchEvent

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    • +

      application/json;stream=watch

      +
    • +
    • +

      application/vnd.kubernetes.protobuf;stream=watch

      +
    • +
    +
    +
    +
    +

    Tags

    • @@ -2402,7 +10274,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    Parameters

    +

    Parameters

    @@ -2517,7 +10389,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    Responses

    +

    Responses

    @@ -2542,7 +10414,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    Consumes

    +

    Consumes

    • @@ -2552,7 +10424,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    Produces

    +

    Produces

    • @@ -2574,7 +10446,1471 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
    -

    Tags

    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    + +
    +

    watch individual changes to a list of Deployment

    +
    +
    +
    GET /apis/apps/v1/watch/namespaces/{namespace}/deployments
    +
    +
    +
    +

    Parameters

    +
    ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.WatchEvent

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    • +

      application/json;stream=watch

      +
    • +
    • +

      application/vnd.kubernetes.protobuf;stream=watch

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    watch changes to an object of kind Deployment

    +
    +
    +
    GET /apis/apps/v1/watch/namespaces/{namespace}/deployments/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the Deployment

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.WatchEvent

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    • +

      application/json;stream=watch

      +
    • +
    • +

      application/vnd.kubernetes.protobuf;stream=watch

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    watch individual changes to a list of ReplicaSet

    +
    +
    +
    GET /apis/apps/v1/watch/namespaces/{namespace}/replicasets
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.WatchEvent

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    • +

      application/json;stream=watch

      +
    • +
    • +

      application/vnd.kubernetes.protobuf;stream=watch

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    watch changes to an object of kind ReplicaSet

    +
    +
    +
    GET /apis/apps/v1/watch/namespaces/{namespace}/replicasets/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the ReplicaSet

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.WatchEvent

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    • +

      application/json;stream=watch

      +
    • +
    • +

      application/vnd.kubernetes.protobuf;stream=watch

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    watch individual changes to a list of StatefulSet

    +
    +
    +
    GET /apis/apps/v1/watch/namespaces/{namespace}/statefulsets
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.WatchEvent

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    • +

      application/json;stream=watch

      +
    • +
    • +

      application/vnd.kubernetes.protobuf;stream=watch

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    watch changes to an object of kind StatefulSet

    +
    +
    +
    GET /apis/apps/v1/watch/namespaces/{namespace}/statefulsets/{name}
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    PathParameter

    namespace

    object name and auth scope, such as for teams and projects

    true

    string

    PathParameter

    name

    name of the StatefulSet

    true

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.WatchEvent

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    • +

      application/json;stream=watch

      +
    • +
    • +

      application/vnd.kubernetes.protobuf;stream=watch

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    watch individual changes to a list of ReplicaSet

    +
    +
    +
    GET /apis/apps/v1/watch/replicasets
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.WatchEvent

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    • +

      application/json;stream=watch

      +
    • +
    • +

      application/vnd.kubernetes.protobuf;stream=watch

      +
    • +
    +
    +
    +
    +

    Tags

    +
    +
      +
    • +

      apisappsv1

      +
    • +
    +
    +
    +
    +
    +

    watch individual changes to a list of StatefulSet

    +
    +
    +
    GET /apis/apps/v1/watch/statefulsets
    +
    +
    +
    +

    Parameters

    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeNameDescriptionRequiredSchemaDefault

    QueryParameter

    pretty

    If true, then the output is pretty printed.

    false

    string

    QueryParameter

    labelSelector

    A selector to restrict the list of returned objects by their labels. Defaults to everything.

    false

    string

    QueryParameter

    fieldSelector

    A selector to restrict the list of returned objects by their fields. Defaults to everything.

    false

    string

    QueryParameter

    includeUninitialized

    If true, partially initialized resources are included in the response.

    false

    boolean

    QueryParameter

    watch

    Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

    false

    boolean

    QueryParameter

    resourceVersion

    When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

    false

    string

    QueryParameter

    timeoutSeconds

    Timeout for the list/watch call.

    false

    integer (int32)

    QueryParameter

    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. +

    The 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.

    false

    integer (int32)

    QueryParameter

    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 indicating the client must restart their list without the continue field. This 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.

    false

    string

    + +
    +
    +

    Responses

    + +++++ + + + + + + + + + + + + + + +
    HTTP CodeDescriptionSchema

    200

    success

    v1.WatchEvent

    + +
    +
    +

    Consumes

    +
    +
      +
    • +

      /

      +
    • +
    +
    +
    +
    +

    Produces

    +
    +
      +
    • +

      application/json

      +
    • +
    • +

      application/yaml

      +
    • +
    • +

      application/vnd.kubernetes.protobuf

      +
    • +
    • +

      application/json;stream=watch

      +
    • +
    • +

      application/vnd.kubernetes.protobuf;stream=watch

      +
    • +
    +
    +
    +
    +

    Tags

    • diff --git a/docs/api-reference/apps/v1beta1/definitions.html b/docs/api-reference/apps/v1beta1/definitions.html index b55f627fa6e..8bf4a106ec9 100755 --- a/docs/api-reference/apps/v1beta1/definitions.html +++ b/docs/api-reference/apps/v1beta1/definitions.html @@ -983,6 +983,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      string

      + +

      volumeMode

      +

      volumeMode defines what type of volume is required by the claim. Value of Filesystem is implied when not included in claim spec. This is an alpha feature and may change in the future.

      +

      false

      +

      v1.PersistentVolumeMode

      + + @@ -1754,7 +1761,7 @@ Populated by the system. Read-only. Null for lists. More info:

      deletionTimestamp

      -

      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 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.
      +

      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.

      Populated by the system when a graceful deletion is requested. Read-only. More info:
      https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      @@ -1955,7 +1962,7 @@ When an object is created, the system will populate this list with the current s

      targetPortal

      -

      iSCSI target portal. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      +

      iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      true

      string

      @@ -1969,14 +1976,14 @@ When an object is created, the system will populate this list with the current s

      lun

      -

      iSCSI target lun number.

      +

      iSCSI Target Lun number.

      true

      integer (int32)

      iscsiInterface

      -

      Optional: Defaults to default (tcp). iSCSI interface name that uses an iSCSI transport.

      +

      iSCSI Interface Name that uses an iSCSI transport. Defaults to default (tcp).

      false

      string

      @@ -1997,7 +2004,7 @@ When an object is created, the system will populate this list with the current s

      portals

      -

      iSCSI target portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      +

      iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      false

      string array

      @@ -2018,14 +2025,14 @@ When an object is created, the system will populate this list with the current s

      secretRef

      -

      CHAP secret for iSCSI target and initiator authentication

      +

      CHAP Secret for iSCSI target and initiator authentication

      false

      v1.LocalObjectReference

      initiatorName

      -

      Custom iSCSI initiator name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface <target portal>:<volume name> will be created for the connection.

      +

      Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface <target portal>:<volume name> will be created for the connection.

      false

      string

      @@ -2112,7 +2119,7 @@ When an object is created, the system will populate this list with the current s

      v1.PodAffinityTerm

      -

      Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key <topologyKey> tches that of any node on which a pod of the set of pods is running

      +

      Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key <topologyKey> matches that of any node on which a pod of the set of pods is running

      @@ -2148,8 +2155,8 @@ When an object is created, the system will populate this list with the current s - - + + @@ -2204,6 +2211,68 @@ When an object is created, the system will populate this list with the current s

      topologyKey

      This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. For PreferredDuringScheduling pod anti-affinity, empty topologyKey is interpreted as "all topologies" ("all topologies" here means all the topologyKeys indicated by scheduler command-line argument --failure-domains); for affinity and for RequiredDuringScheduling pod anti-affinity, empty topologyKey is not allowed.

      false

      This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.

      true

      string

      +
      +
      +

      v1beta1.StatefulSetCondition

      +
      +

      StatefulSetCondition describes the state of a statefulset at a certain point.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      type

      Type of statefulset condition.

      true

      string

      status

      Status of the condition, one of True, False, Unknown.

      true

      string

      lastTransitionTime

      Last time the condition transitioned from one status to another.

      false

      string

      reason

      The reason for the condition’s last transition.

      false

      string

      message

      A human readable message indicating details about the transition.

      false

      string

      +

      v1.PersistentVolumeClaim

      @@ -2657,7 +2726,7 @@ When an object is created, the system will populate this list with the current s

      v1.FlexVolumeSource

      -

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.

      +

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.

      @@ -2942,6 +3011,10 @@ When an object is created, the system will populate this list with the current s
      +
      +
      +

      v1.PersistentVolumeMode

      +

      v1.DeleteOptions

      @@ -3003,7 +3076,7 @@ When an object is created, the system will populate this list with the current s

      propagationPolicy

      -

      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.

      +

      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.

      false

      v1.DeletionPropagation

      @@ -3186,7 +3259,7 @@ The StatefulSet guarantees that a given network identity will always map to the

      flexVolume

      -

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.

      +

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.

      false

      v1.FlexVolumeSource

      @@ -3395,6 +3468,47 @@ The StatefulSet guarantees that a given network identity will always map to the +
      +
      +

      v1.WeightedPodAffinityTerm

      +
      +

      The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s)

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      weight

      weight associated with matching the corresponding podAffinityTerm, in the range 1-100.

      true

      integer (int32)

      podAffinityTerm

      Required. A pod affinity term, associated with the corresponding weight.

      true

      v1.PodAffinityTerm

      +

      v1.Probe

      @@ -3478,47 +3592,6 @@ The StatefulSet guarantees that a given network identity will always map to the -
      -
      -

      v1.WeightedPodAffinityTerm

      -
      -

      The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s)

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      weight

      weight associated with matching the corresponding podAffinityTerm, in the range 1-100.

      true

      integer (int32)

      podAffinityTerm

      Required. A pod affinity term, associated with the corresponding weight.

      true

      v1.PodAffinityTerm

      -

      v1beta1.DeploymentSpec

      @@ -3784,7 +3857,7 @@ The StatefulSet guarantees that a given network identity will always map to the

      dnsPolicy

      -

      Set DNS policy for containers within the pod. One of ClusterFirstWithHostNet, ClusterFirst or Default. Defaults to "ClusterFirst". To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to ClusterFirstWithHostNet.

      +

      Set DNS policy for the pod. Defaults to "ClusterFirst". Valid values are ClusterFirstWithHostNet, ClusterFirst, Default or None. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to ClusterFirstWithHostNet. Note that None policy is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it.

      false

      string

      @@ -3915,6 +3988,13 @@ The StatefulSet guarantees that a given network identity will always map to the

      integer (int32)

      + +

      dnsConfig

      +

      Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy. This is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it.

      +

      false

      +

      v1.PodDNSConfig

      + + @@ -4639,21 +4719,21 @@ Examples:

      protectionDomain

      -

      The name of the Protection Domain for the configured storage (defaults to "default").

      +

      The name of the ScaleIO Protection Domain for the configured storage.

      false

      string

      storagePool

      -

      The Storage Pool associated with the protection domain (defaults to "default").

      +

      The ScaleIO Storage Pool associated with the protection domain.

      false

      string

      storageMode

      -

      Indicates whether the storage for a volume should be thick or thin (defaults to "thin").

      +

      Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned.

      false

      string

      @@ -4727,6 +4807,54 @@ Examples:
      +
      +
      +

      v1.PodDNSConfig

      +
      +

      PodDNSConfig defines the DNS parameters of a pod in addition to those generated from DNSPolicy.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      nameservers

      A list of DNS name server IP addresses. This will be appended to the base nameservers generated from DNSPolicy. Duplicated nameservers will be removed.

      false

      string array

      searches

      A list of DNS search domains for host-name lookup. This will be appended to the base search paths generated from DNSPolicy. Duplicated search paths will be removed.

      false

      string array

      options

      A list of DNS resolver options. This will be merged with the base options generated from DNSPolicy. Duplicated entries will be removed. Resolution options given in Options will override those that appear in the base DNSPolicy.

      false

      v1.PodDNSConfigOption array

      +

      v1.Status

      @@ -5247,6 +5375,13 @@ Examples:

      integer (int32)

      + +

      conditions

      +

      Represents the latest available observations of a statefulset’s current state.

      +

      false

      +

      v1beta1.StatefulSetCondition array

      + + @@ -5562,6 +5697,13 @@ Examples:
      +

      volumeDevices

      +

      volumeDevices is the list of block devices to be used by the container. This is an alpha feature and may change in the future.

      +

      false

      +

      v1.VolumeDevice array

      + + +

      livenessProbe

      Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes

      false

      @@ -5605,7 +5747,7 @@ Examples:

      securityContext

      -

      Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md

      +

      Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/

      false

      v1.SecurityContext

      @@ -5912,6 +6054,47 @@ Examples:
      +
      +
      +

      v1.VolumeDevice

      +
      +

      volumeDevice describes a mapping of a raw block device within a container.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      name must match the name of a persistentVolumeClaim in the pod

      true

      string

      devicePath

      devicePath is the path inside of the container that the device will be mapped to.

      true

      string

      +

      v1.NodeSelectorRequirement

      @@ -6001,6 +6184,47 @@ Examples:
      +
      +
      +

      v1.PodDNSConfigOption

      +
      +

      PodDNSConfigOption defines DNS resolver options of a pod.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      Required.

      false

      string

      value

      false

      string

      +

      v1.SecretProjection

      diff --git a/docs/api-reference/apps/v1beta1/operations.html b/docs/api-reference/apps/v1beta1/operations.html index 97a6fbf32be..f04166f4f79 100755 --- a/docs/api-reference/apps/v1beta1/operations.html +++ b/docs/api-reference/apps/v1beta1/operations.html @@ -1608,7 +1608,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -2646,7 +2646,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -4589,7 +4589,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      diff --git a/docs/api-reference/apps/v1beta2/definitions.html b/docs/api-reference/apps/v1beta2/definitions.html index 7f90d8d178f..0eb7eae50ba 100755 --- a/docs/api-reference/apps/v1beta2/definitions.html +++ b/docs/api-reference/apps/v1beta2/definitions.html @@ -930,6 +930,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      string

      + +

      volumeMode

      +

      volumeMode defines what type of volume is required by the claim. Value of Filesystem is implied when not included in claim spec. This is an alpha feature and may change in the future.

      +

      false

      +

      v1.PersistentVolumeMode

      + + @@ -1006,7 +1013,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      v1beta2.ControllerRevision

      -

      ControllerRevision implements an immutable snapshot of state data. Clients are responsible for serializing and deserializing the objects that contain their internal state. Once a ControllerRevision has been successfully created, it can not be updated. The API Server will fail validation of all requests that attempt to mutate the Data field. ControllerRevisions may, however, be deleted. Note that, due to its use by both the DaemonSet and StatefulSet controllers for update and rollback, this object is beta. However, it may be subject to name and representation changes in future releases, and clients should not depend on its stability. It is primarily for internal use by controllers.

      +

      DEPRECATED - This group version of ControllerRevision is deprecated by apps/v1/ControllerRevision. See the release notes for more information. ControllerRevision implements an immutable snapshot of state data. Clients are responsible for serializing and deserializing the objects that contain their internal state. Once a ControllerRevision has been successfully created, it can not be updated. The API Server will fail validation of all requests that attempt to mutate the Data field. ControllerRevisions may, however, be deleted. Note that, due to its use by both the DaemonSet and StatefulSet controllers for update and rollback, this object is beta. However, it may be subject to name and representation changes in future releases, and clients should not depend on its stability. It is primarily for internal use by controllers.

      @@ -1064,6 +1071,68 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
      +
      +
      +

      v1beta2.StatefulSetCondition

      +
      +

      StatefulSetCondition describes the state of a statefulset at a certain point.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      type

      Type of statefulset condition.

      true

      string

      status

      Status of the condition, one of True, False, Unknown.

      true

      string

      lastTransitionTime

      Last time the condition transitioned from one status to another.

      false

      string

      reason

      The reason for the condition’s last transition.

      false

      string

      message

      A human readable message indicating details about the transition.

      false

      string

      +

      v1.DownwardAPIVolumeSource

      @@ -1667,6 +1736,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      integer (int32)

      + +

      conditions

      +

      Represents the latest available observations of a statefulset’s current state.

      +

      false

      +

      v1beta2.StatefulSetCondition array

      + + @@ -1873,7 +1949,7 @@ Populated by the system. Read-only. Null for lists. More info:

      deletionTimestamp

      -

      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 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.
      +

      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.

      Populated by the system when a graceful deletion is requested. Read-only. More info:
      https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      @@ -2053,7 +2129,7 @@ When an object is created, the system will populate this list with the current s

      targetPortal

      -

      iSCSI target portal. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      +

      iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      true

      string

      @@ -2067,14 +2143,14 @@ When an object is created, the system will populate this list with the current s

      lun

      -

      iSCSI target lun number.

      +

      iSCSI Target Lun number.

      true

      integer (int32)

      iscsiInterface

      -

      Optional: Defaults to default (tcp). iSCSI interface name that uses an iSCSI transport.

      +

      iSCSI Interface Name that uses an iSCSI transport. Defaults to default (tcp).

      false

      string

      @@ -2095,7 +2171,7 @@ When an object is created, the system will populate this list with the current s

      portals

      -

      iSCSI target portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      +

      iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      false

      string array

      @@ -2116,14 +2192,14 @@ When an object is created, the system will populate this list with the current s

      secretRef

      -

      CHAP secret for iSCSI target and initiator authentication

      +

      CHAP Secret for iSCSI target and initiator authentication

      false

      v1.LocalObjectReference

      initiatorName

      -

      Custom iSCSI initiator name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface <target portal>:<volume name> will be created for the connection.

      +

      Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface <target portal>:<volume name> will be created for the connection.

      false

      string

      @@ -2431,6 +2507,13 @@ When an object is created, the system will populate this list with the current s

      integer (int32)

      + +

      conditions

      +

      Represents the latest available observations of a DaemonSet’s current state.

      +

      false

      +

      v1beta2.DaemonSetCondition array

      + + @@ -2438,7 +2521,7 @@ When an object is created, the system will populate this list with the current s

      v1.PodAffinityTerm

      -

      Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key <topologyKey> tches that of any node on which a pod of the set of pods is running

      +

      Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key <topologyKey> matches that of any node on which a pod of the set of pods is running

      @@ -2474,8 +2557,8 @@ When an object is created, the system will populate this list with the current s - - + + @@ -2920,8 +3003,8 @@ When an object is created, the system will populate this list with the current s - - + + @@ -3039,11 +3122,73 @@ When an object is created, the system will populate this list with the current s

      topologyKey

      This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. For PreferredDuringScheduling pod anti-affinity, empty topologyKey is interpreted as "all topologies" ("all topologies" here means all the topologyKeys indicated by scheduler command-line argument --failure-domains); for affinity and for RequiredDuringScheduling pod anti-affinity, empty topologyKey is not allowed.

      false

      This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.

      true

      string

      selector

      selector is a label query over pods that should match the replica count. If empty, defaulted to labels on the pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors

      false

      selector is a label query over pods that should match the replica count. It must match the pod template’s labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors

      true

      v1.LabelSelector

      +
      +
      +

      v1beta2.DaemonSetCondition

      +
      +

      DaemonSetCondition describes the state of a DaemonSet at a certain point.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      type

      Type of DaemonSet condition.

      true

      string

      status

      Status of the condition, one of True, False, Unknown.

      true

      string

      lastTransitionTime

      Last time the condition transitioned from one status to another.

      false

      string

      reason

      The reason for the condition’s last transition.

      false

      string

      message

      A human readable message indicating details about the transition.

      false

      string

      +

      v1beta2.DaemonSet

      -

      DaemonSet represents the configuration of a daemon set.

      +

      DEPRECATED - This group version of DaemonSet is deprecated by apps/v1/DaemonSet. See the release notes for more information. DaemonSet represents the configuration of a daemon set.

      @@ -3232,7 +3377,7 @@ When an object is created, the system will populate this list with the current s

      v1.FlexVolumeSource

      -

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.

      +

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.

      @@ -3572,11 +3717,15 @@ When an object is created, the system will populate this list with the current s
      +
      +
      +

      v1.PersistentVolumeMode

      +

      v1beta2.StatefulSet

      -

      StatefulSet represents a set of pods with consistent identities. Identities are defined as:
      +

      DEPRECATED - This group version of StatefulSet is deprecated by apps/v1/StatefulSet. See the release notes for more information. StatefulSet represents a set of pods with consistent identities. Identities are defined as:
      - Network: A single stable DNS and hostname.
      - Storage: As many VolumeClaims as requested.
      The StatefulSet guarantees that a given network identity will always map to the same storage identity.

      @@ -3698,7 +3847,7 @@ The StatefulSet guarantees that a given network identity will always map to the

      propagationPolicy

      -

      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.

      +

      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.

      false

      v1.DeletionPropagation

      @@ -3816,7 +3965,7 @@ The StatefulSet guarantees that a given network identity will always map to the

      flexVolume

      -

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.

      +

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.

      false

      v1.FlexVolumeSource

      @@ -4025,6 +4174,47 @@ The StatefulSet guarantees that a given network identity will always map to the +
      +
      +

      v1.WeightedPodAffinityTerm

      +
      +

      The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s)

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      weight

      weight associated with matching the corresponding podAffinityTerm, in the range 1-100.

      true

      integer (int32)

      podAffinityTerm

      Required. A pod affinity term, associated with the corresponding weight.

      true

      v1.PodAffinityTerm

      +

      v1.Probe

      @@ -4108,47 +4298,6 @@ The StatefulSet guarantees that a given network identity will always map to the -
      -
      -

      v1.WeightedPodAffinityTerm

      -
      -

      The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s)

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      weight

      weight associated with matching the corresponding podAffinityTerm, in the range 1-100.

      true

      integer (int32)

      podAffinityTerm

      Required. A pod affinity term, associated with the corresponding weight.

      true

      v1.PodAffinityTerm

      -

      v1.SecretKeySelector

      @@ -4324,7 +4473,7 @@ The StatefulSet guarantees that a given network identity will always map to the

      dnsPolicy

      -

      Set DNS policy for containers within the pod. One of ClusterFirstWithHostNet, ClusterFirst or Default. Defaults to "ClusterFirst". To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to ClusterFirstWithHostNet.

      +

      Set DNS policy for the pod. Defaults to "ClusterFirst". Valid values are ClusterFirstWithHostNet, ClusterFirst, Default or None. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to ClusterFirstWithHostNet. Note that None policy is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it.

      false

      string

      @@ -4455,6 +4604,13 @@ The StatefulSet guarantees that a given network identity will always map to the

      integer (int32)

      + +

      dnsConfig

      +

      Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy. This is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it.

      +

      false

      +

      v1.PodDNSConfig

      + + @@ -5000,21 +5156,21 @@ Examples:

      protectionDomain

      -

      The name of the Protection Domain for the configured storage (defaults to "default").

      +

      The name of the ScaleIO Protection Domain for the configured storage.

      false

      string

      storagePool

      -

      The Storage Pool associated with the protection domain (defaults to "default").

      +

      The ScaleIO Storage Pool associated with the protection domain.

      false

      string

      storageMode

      -

      Indicates whether the storage for a volume should be thick or thin (defaults to "thin").

      +

      Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned.

      false

      string

      @@ -5051,7 +5207,7 @@ Examples:

      v1beta2.ReplicaSet

      -

      ReplicaSet ensures that a specified number of pod replicas are running at any given time.

      +

      DEPRECATED - This group version of ReplicaSet is deprecated by apps/v1/ReplicaSet. See the release notes for more information. ReplicaSet ensures that a specified number of pod replicas are running at any given time.

      @@ -5198,6 +5354,54 @@ Examples:
      +
      +
      +

      v1.PodDNSConfig

      +
      +

      PodDNSConfig defines the DNS parameters of a pod in addition to those generated from DNSPolicy.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      nameservers

      A list of DNS name server IP addresses. This will be appended to the base nameservers generated from DNSPolicy. Duplicated nameservers will be removed.

      false

      string array

      searches

      A list of DNS search domains for host-name lookup. This will be appended to the base search paths generated from DNSPolicy. Duplicated search paths will be removed.

      false

      string array

      options

      A list of DNS resolver options. This will be merged with the base options generated from DNSPolicy. Duplicated entries will be removed. Resolution options given in Options will override those that appear in the base DNSPolicy.

      false

      v1.PodDNSConfigOption array

      +

      v1.Status

      @@ -5362,8 +5566,8 @@ Examples:

      selector

      -

      Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment.

      -

      false

      +

      Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment. It must match the pod template’s labels.

      +

      true

      v1.LabelSelector

      @@ -5827,6 +6031,13 @@ Examples:
      +

      volumeDevices

      +

      volumeDevices is the list of block devices to be used by the container. This is an alpha feature and may change in the future.

      +

      false

      +

      v1.VolumeDevice array

      + + +

      livenessProbe

      Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes

      false

      @@ -5870,7 +6081,7 @@ Examples:

      securityContext

      -

      Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md

      +

      Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/

      false

      v1.SecurityContext

      @@ -6122,6 +6333,47 @@ Examples:
      +
      +
      +

      v1.VolumeDevice

      +
      +

      volumeDevice describes a mapping of a raw block device within a container.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      name must match the name of a persistentVolumeClaim in the pod

      true

      string

      devicePath

      devicePath is the path inside of the container that the device will be mapped to.

      true

      string

      +

      v1.NodeSelectorRequirement

      @@ -6211,6 +6463,47 @@ Examples:
      +
      +
      +

      v1.PodDNSConfigOption

      +
      +

      PodDNSConfigOption defines DNS resolver options of a pod.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      Required.

      false

      string

      value

      false

      string

      +

      v1.SecretProjection

      @@ -6390,7 +6683,7 @@ Examples:

      v1beta2.Deployment

      -

      Deployment enables declarative updates for Pods and ReplicaSets.

      +

      DEPRECATED - This group version of Deployment is deprecated by apps/v1/Deployment. See the release notes for more information. Deployment enables declarative updates for Pods and ReplicaSets.

      @@ -7019,8 +7312,8 @@ Examples:
      - - + + @@ -7310,8 +7603,8 @@ Examples:
      - - + + diff --git a/docs/api-reference/apps/v1beta2/operations.html b/docs/api-reference/apps/v1beta2/operations.html index 10f541d2393..2e512471ccf 100755 --- a/docs/api-reference/apps/v1beta2/operations.html +++ b/docs/api-reference/apps/v1beta2/operations.html @@ -1782,7 +1782,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - + @@ -2820,7 +2820,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - + @@ -4242,7 +4242,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - + @@ -6048,7 +6048,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - + @@ -7854,7 +7854,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - + diff --git a/docs/api-reference/authentication.k8s.io/v1/definitions.html b/docs/api-reference/authentication.k8s.io/v1/definitions.html index 817a165b53e..d3561b68cc2 100755 --- a/docs/api-reference/authentication.k8s.io/v1/definitions.html +++ b/docs/api-reference/authentication.k8s.io/v1/definitions.html @@ -802,7 +802,7 @@ Populated by the system. Read-only. Null for lists. More info:

      deletionTimestamp

      -
      diff --git a/docs/api-reference/authentication.k8s.io/v1beta1/definitions.html b/docs/api-reference/authentication.k8s.io/v1beta1/definitions.html index 125eaa42e3a..17ed06d7fad 100755 --- a/docs/api-reference/authentication.k8s.io/v1beta1/definitions.html +++ b/docs/api-reference/authentication.k8s.io/v1beta1/definitions.html @@ -891,7 +891,7 @@ Populated by the system. Read-only. Null for lists. More info:

      deletionTimestamp

      -
      diff --git a/docs/api-reference/authorization.k8s.io/v1/definitions.html b/docs/api-reference/authorization.k8s.io/v1/definitions.html index 2c79a973817..a71abf874ab 100755 --- a/docs/api-reference/authorization.k8s.io/v1/definitions.html +++ b/docs/api-reference/authorization.k8s.io/v1/definitions.html @@ -850,12 +850,19 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - + + + + + + + + @@ -1007,7 +1014,7 @@ Populated by the system. Read-only. Null for lists. More info:

      deletionTimestamp

      -
      @@ -1432,7 +1439,8 @@ When an object is created, the system will populate this list with the current s - + diff --git a/docs/api-reference/authorization.k8s.io/v1beta1/definitions.html b/docs/api-reference/authorization.k8s.io/v1beta1/definitions.html index 9d10ac19a3f..6113f09dfdc 100755 --- a/docs/api-reference/authorization.k8s.io/v1beta1/definitions.html +++ b/docs/api-reference/authorization.k8s.io/v1beta1/definitions.html @@ -1094,12 +1094,19 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - + + + + + + + + @@ -1210,7 +1217,7 @@ Populated by the system. Read-only. Null for lists. More info:

      deletionTimestamp

      -
      @@ -1452,7 +1459,8 @@ When an object is created, the system will populate this list with the current s - + diff --git a/docs/api-reference/autoscaling/v1/definitions.html b/docs/api-reference/autoscaling/v1/definitions.html index b643b166dea..7db2cf23828 100755 --- a/docs/api-reference/autoscaling/v1/definitions.html +++ b/docs/api-reference/autoscaling/v1/definitions.html @@ -503,7 +503,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - + @@ -1234,7 +1234,7 @@ Populated by the system. Read-only. Null for lists. More info:

      deletionTimestamp

      -
      diff --git a/docs/api-reference/autoscaling/v1/operations.html b/docs/api-reference/autoscaling/v1/operations.html index 23acb52fd9e..ec2b119fa5d 100755 --- a/docs/api-reference/autoscaling/v1/operations.html +++ b/docs/api-reference/autoscaling/v1/operations.html @@ -1434,7 +1434,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - + diff --git a/docs/api-reference/autoscaling/v2beta1/definitions.html b/docs/api-reference/autoscaling/v2beta1/definitions.html index 76ca8a3845c..f8be7d0c377 100755 --- a/docs/api-reference/autoscaling/v2beta1/definitions.html +++ b/docs/api-reference/autoscaling/v2beta1/definitions.html @@ -871,7 +871,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - + @@ -1170,7 +1170,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - + @@ -1444,7 +1444,7 @@ Populated by the system. Read-only. Null for lists. More info:

      deletionTimestamp

      -
      @@ -1828,7 +1828,7 @@ Examples:
      - + diff --git a/docs/api-reference/autoscaling/v2beta1/operations.html b/docs/api-reference/autoscaling/v2beta1/operations.html index 2a91683e0b0..29cb376a652 100755 --- a/docs/api-reference/autoscaling/v2beta1/operations.html +++ b/docs/api-reference/autoscaling/v2beta1/operations.html @@ -1434,7 +1434,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - + diff --git a/docs/api-reference/batch/v1/definitions.html b/docs/api-reference/batch/v1/definitions.html index b8092c23dc8..291cf172cda 100755 --- a/docs/api-reference/batch/v1/definitions.html +++ b/docs/api-reference/batch/v1/definitions.html @@ -1433,7 +1433,7 @@ Populated by the system. Read-only. Null for lists. More info:

      deletionTimestamp

      -
      @@ -1572,7 +1572,7 @@ When an object is created, the system will populate this list with the current s - + @@ -1586,14 +1586,14 @@ When an object is created, the system will populate this list with the current s - + - + @@ -1614,7 +1614,7 @@ When an object is created, the system will populate this list with the current s - + @@ -1635,14 +1635,14 @@ When an object is created, the system will populate this list with the current s - + - + @@ -1695,7 +1695,7 @@ When an object is created, the system will populate this list with the current s

      v1.PodAffinityTerm

      -

      Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key <topologyKey> tches that of any node on which a pod of the set of pods is running

      +

      Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key <topologyKey> matches that of any node on which a pod of the set of pods is running

      selector

      A label query over pods that are managed by the daemon set. Must match in order to be controlled. If empty, defaulted to labels on Pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors

      false

      A label query over pods that are managed by the daemon set. Must match in order to be controlled. It must match the pod template’s labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors

      true

      v1.LabelSelector

      selector

      Selector is a label query over pods that should match the replica count. If the selector is empty, it is defaulted to the labels present on the pod template. Label keys and values that must match in order to be controlled by this replica set. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors

      false

      Selector is a label query over pods that should match the replica count. Label keys and values that must match in order to be controlled by this replica set. It must match the pod template’s labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors

      true

      v1.LabelSelector

      QueryParameter

      propagationPolicy

      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.

      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.

      false

      string

      QueryParameter

      propagationPolicy

      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.

      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.

      false

      string

      QueryParameter

      propagationPolicy

      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.

      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.

      false

      string

      QueryParameter

      propagationPolicy

      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.

      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.

      false

      string

      QueryParameter

      propagationPolicy

      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.

      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.

      false

      string

      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 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.
      +

      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.

      Populated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      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 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.
      +

      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.

      Populated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      allowed

      Allowed is required. True if the action would be allowed, false otherwise.

      Allowed is required. True if the action would be allowed, false otherwise.

      true

      boolean

      false

      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.

      false

      boolean

      false

      reason

      Reason is optional. It indicates why a request was allowed or denied.

      false

      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 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.
      +

      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.

      Populated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      resources

      Resources is a list of resources this rule applies to. ResourceAll represents all resources. "*" means all.

      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.

      false

      string array

      allowed

      Allowed is required. True if the action would be allowed, false otherwise.

      Allowed is required. True if the action would be allowed, false otherwise.

      true

      boolean

      false

      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.

      false

      boolean

      false

      reason

      Reason is optional. It indicates why a request was allowed or denied.

      false

      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 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.
      +

      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.

      Populated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      resources

      Resources is a list of resources this rule applies to. ResourceAll represents all resources. "*" means all.

      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.

      false

      string array

      propagationPolicy

      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.

      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.

      false

      v1.DeletionPropagation

      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 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.
      +

      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.

      Populated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      QueryParameter

      propagationPolicy

      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.

      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.

      false

      string

      type

      type is the type of metric source. It will match one of the fields below.

      type is the type of metric source. It will be one of "Object", "Pods" or "Resource", each corresponds to a matching field in the object.

      true

      string

      propagationPolicy

      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.

      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.

      false

      v1.DeletionPropagation

      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 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.
      +

      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.

      Populated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      type

      type is the type of metric source. It should match one of the fields below.

      type is the type of metric source. It should be one of "Object", "Pods" or "Resource", each mapping to a matching field in the object.

      true

      string

      QueryParameter

      propagationPolicy

      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.

      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.

      false

      string

      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 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.
      +

      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.

      Populated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      targetPortal

      iSCSI target portal. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      true

      string

      lun

      iSCSI target lun number.

      iSCSI Target Lun number.

      true

      integer (int32)

      iscsiInterface

      Optional: Defaults to default (tcp). iSCSI interface name that uses an iSCSI transport.

      iSCSI Interface Name that uses an iSCSI transport. Defaults to default (tcp).

      false

      string

      portals

      iSCSI target portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      false

      string array

      secretRef

      CHAP secret for iSCSI target and initiator authentication

      CHAP Secret for iSCSI target and initiator authentication

      false

      v1.LocalObjectReference

      initiatorName

      Custom iSCSI initiator name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface <target portal>:<volume name> will be created for the connection.

      Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface <target portal>:<volume name> will be created for the connection.

      false

      string

      @@ -1731,8 +1731,8 @@ When an object is created, the system will populate this list with the current s - - + + @@ -2089,7 +2089,7 @@ When an object is created, the system will populate this list with the current s

      v1.FlexVolumeSource

      -

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.

      +

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.

      topologyKey

      This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. For PreferredDuringScheduling pod anti-affinity, empty topologyKey is interpreted as "all topologies" ("all topologies" here means all the topologyKeys indicated by scheduler command-line argument --failure-domains); for affinity and for RequiredDuringScheduling pod anti-affinity, empty topologyKey is not allowed.

      false

      This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.

      true

      string

      @@ -2435,7 +2435,7 @@ When an object is created, the system will populate this list with the current s - + @@ -2511,7 +2511,7 @@ When an object is created, the system will populate this list with the current s - + @@ -2629,7 +2629,7 @@ When an object is created, the system will populate this list with the current s - + @@ -3137,7 +3137,7 @@ When an object is created, the system will populate this list with the current s - + @@ -3268,6 +3268,13 @@ When an object is created, the system will populate this list with the current s + + + + + + +

      manualSelector

      manualSelector controls generation of pod labels and pod selectors. Leave manualSelector unset unless you are certain what you are doing. When false or unset, the system pick labels unique to this job and appends those labels to the pod template. When true, the user is responsible for picking unique labels and specifying the selector. Failure to pick a unique label may cause this and other jobs to not function correctly. However, You may see manualSelector=true in jobs that were created with the old extensions/v1beta1 API. More info: https://git.k8s.io/community/contributors/design-proposals/selector-generation.md

      manualSelector controls generation of pod labels and pod selectors. Leave manualSelector unset unless you are certain what you are doing. When false or unset, the system pick labels unique to this job and appends those labels to the pod template. When true, the user is responsible for picking unique labels and specifying the selector. Failure to pick a unique label may cause this and other jobs to not function correctly. However, You may see manualSelector=true in jobs that were created with the old extensions/v1beta1 API. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/#specifying-your-own-pod-selector

      false

      boolean

      false

      propagationPolicy

      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.

      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.

      false

      v1.DeletionPropagation

      flexVolume

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.

      false

      v1.FlexVolumeSource

      dnsPolicy

      Set DNS policy for containers within the pod. One of ClusterFirstWithHostNet, ClusterFirst or Default. Defaults to "ClusterFirst". To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to ClusterFirstWithHostNet.

      Set DNS policy for the pod. Defaults to "ClusterFirst". Valid values are ClusterFirstWithHostNet, ClusterFirst, Default or None. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to ClusterFirstWithHostNet. Note that None policy is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it.

      false

      string

      integer (int32)

      dnsConfig

      Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy. This is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it.

      false

      v1.PodDNSConfig

      @@ -3813,21 +3820,21 @@ Examples:

      protectionDomain

      -

      The name of the Protection Domain for the configured storage (defaults to "default").

      +

      The name of the ScaleIO Protection Domain for the configured storage.

      false

      string

      storagePool

      -

      The Storage Pool associated with the protection domain (defaults to "default").

      +

      The ScaleIO Storage Pool associated with the protection domain.

      false

      string

      storageMode

      -

      Indicates whether the storage for a volume should be thick or thin (defaults to "thin").

      +

      Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned.

      false

      string

      @@ -3984,6 +3991,54 @@ Examples:
      +
      +
      +

      v1.PodDNSConfig

      +
      +

      PodDNSConfig defines the DNS parameters of a pod in addition to those generated from DNSPolicy.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      nameservers

      A list of DNS name server IP addresses. This will be appended to the base nameservers generated from DNSPolicy. Duplicated nameservers will be removed.

      false

      string array

      searches

      A list of DNS search domains for host-name lookup. This will be appended to the base search paths generated from DNSPolicy. Duplicated search paths will be removed.

      false

      string array

      options

      A list of DNS resolver options. This will be merged with the base options generated from DNSPolicy. Duplicated entries will be removed. Resolution options given in Options will override those that appear in the base DNSPolicy.

      false

      v1.PodDNSConfigOption array

      +
      +
      +

      v1.VolumeDevice

      +
      +

      volumeDevice describes a mapping of a raw block device within a container.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      name must match the name of a persistentVolumeClaim in the pod

      true

      string

      devicePath

      devicePath is the path inside of the container that the device will be mapped to.

      true

      string

      +

      v1.NodeSelectorRequirement

      @@ -4951,6 +5054,47 @@ Examples:
      +
      +
      +

      v1.PodDNSConfigOption

      +
      +

      PodDNSConfigOption defines DNS resolver options of a pod.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      Required.

      false

      string

      value

      false

      string

      +

      v1.CinderVolumeSource

      diff --git a/docs/api-reference/batch/v1/operations.html b/docs/api-reference/batch/v1/operations.html index 88ab3247576..ae8064088dc 100755 --- a/docs/api-reference/batch/v1/operations.html +++ b/docs/api-reference/batch/v1/operations.html @@ -1434,7 +1434,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      diff --git a/docs/api-reference/batch/v1beta1/definitions.html b/docs/api-reference/batch/v1beta1/definitions.html index 6f1b1dbd7b6..1e84af1e5ec 100755 --- a/docs/api-reference/batch/v1beta1/definitions.html +++ b/docs/api-reference/batch/v1beta1/definitions.html @@ -1474,7 +1474,7 @@ Populated by the system. Read-only. Null for lists. More info:

      deletionTimestamp

      -

      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 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.
      +

      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.

      Populated by the system when a graceful deletion is requested. Read-only. More info:
      https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      @@ -1613,7 +1613,7 @@ When an object is created, the system will populate this list with the current s

      targetPortal

      -

      iSCSI target portal. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      +

      iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      true

      string

      @@ -1627,14 +1627,14 @@ When an object is created, the system will populate this list with the current s

      lun

      -

      iSCSI target lun number.

      +

      iSCSI Target Lun number.

      true

      integer (int32)

      iscsiInterface

      -

      Optional: Defaults to default (tcp). iSCSI interface name that uses an iSCSI transport.

      +

      iSCSI Interface Name that uses an iSCSI transport. Defaults to default (tcp).

      false

      string

      @@ -1655,7 +1655,7 @@ When an object is created, the system will populate this list with the current s

      portals

      -

      iSCSI target portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      +

      iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      false

      string array

      @@ -1676,14 +1676,14 @@ When an object is created, the system will populate this list with the current s

      secretRef

      -

      CHAP secret for iSCSI target and initiator authentication

      +

      CHAP Secret for iSCSI target and initiator authentication

      false

      v1.LocalObjectReference

      initiatorName

      -

      Custom iSCSI initiator name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface <target portal>:<volume name> will be created for the connection.

      +

      Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface <target portal>:<volume name> will be created for the connection.

      false

      string

      @@ -1736,7 +1736,7 @@ When an object is created, the system will populate this list with the current s

      v1.PodAffinityTerm

      -

      Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key <topologyKey> tches that of any node on which a pod of the set of pods is running

      +

      Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key <topologyKey> matches that of any node on which a pod of the set of pods is running

      @@ -1772,8 +1772,8 @@ When an object is created, the system will populate this list with the current s - - + + @@ -2061,7 +2061,7 @@ When an object is created, the system will populate this list with the current s

      v1.FlexVolumeSource

      -

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.

      +

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.

      topologyKey

      This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. For PreferredDuringScheduling pod anti-affinity, empty topologyKey is interpreted as "all topologies" ("all topologies" here means all the topologyKeys indicated by scheduler command-line argument --failure-domains); for affinity and for RequiredDuringScheduling pod anti-affinity, empty topologyKey is not allowed.

      false

      This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.

      true

      string

      @@ -2469,7 +2469,7 @@ When an object is created, the system will populate this list with the current s - + @@ -2545,7 +2545,7 @@ When an object is created, the system will populate this list with the current s - + @@ -2663,7 +2663,7 @@ When an object is created, the system will populate this list with the current s - + @@ -3171,7 +3171,7 @@ When an object is created, the system will populate this list with the current s - + @@ -3302,6 +3302,13 @@ When an object is created, the system will populate this list with the current s + + + + + + +

      manualSelector

      manualSelector controls generation of pod labels and pod selectors. Leave manualSelector unset unless you are certain what you are doing. When false or unset, the system pick labels unique to this job and appends those labels to the pod template. When true, the user is responsible for picking unique labels and specifying the selector. Failure to pick a unique label may cause this and other jobs to not function correctly. However, You may see manualSelector=true in jobs that were created with the old extensions/v1beta1 API. More info: https://git.k8s.io/community/contributors/design-proposals/selector-generation.md

      manualSelector controls generation of pod labels and pod selectors. Leave manualSelector unset unless you are certain what you are doing. When false or unset, the system pick labels unique to this job and appends those labels to the pod template. When true, the user is responsible for picking unique labels and specifying the selector. Failure to pick a unique label may cause this and other jobs to not function correctly. However, You may see manualSelector=true in jobs that were created with the old extensions/v1beta1 API. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/#specifying-your-own-pod-selector

      false

      boolean

      false

      propagationPolicy

      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.

      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.

      false

      v1.DeletionPropagation

      flexVolume

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.

      false

      v1.FlexVolumeSource

      dnsPolicy

      Set DNS policy for containers within the pod. One of ClusterFirstWithHostNet, ClusterFirst or Default. Defaults to "ClusterFirst". To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to ClusterFirstWithHostNet.

      Set DNS policy for the pod. Defaults to "ClusterFirst". Valid values are ClusterFirstWithHostNet, ClusterFirst, Default or None. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to ClusterFirstWithHostNet. Note that None policy is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it.

      false

      string

      integer (int32)

      dnsConfig

      Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy. This is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it.

      false

      v1.PodDNSConfig

      @@ -3407,7 +3414,7 @@ When an object is created, the system will populate this list with the current s

      concurrencyPolicy

      -

      Specifies how to treat concurrent executions of a Job. Defaults to Allow.

      +

      Specifies how to treat concurrent executions of a Job. Valid values are: - "Allow" (default): allows CronJobs to run concurrently; - "Forbid": forbids concurrent runs, skipping next run if previous run hasn’t finished yet; - "Replace": cancels currently running job and replaces it with a new one

      false

      string

      @@ -3923,21 +3930,21 @@ Examples:

      protectionDomain

      -

      The name of the Protection Domain for the configured storage (defaults to "default").

      +

      The name of the ScaleIO Protection Domain for the configured storage.

      false

      string

      storagePool

      -

      The Storage Pool associated with the protection domain (defaults to "default").

      +

      The ScaleIO Storage Pool associated with the protection domain.

      false

      string

      storageMode

      -

      Indicates whether the storage for a volume should be thick or thin (defaults to "thin").

      +

      Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned.

      false

      string

      @@ -4094,6 +4101,54 @@ Examples:
      +
      +
      +

      v1.PodDNSConfig

      +
      +

      PodDNSConfig defines the DNS parameters of a pod in addition to those generated from DNSPolicy.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      nameservers

      A list of DNS name server IP addresses. This will be appended to the base nameservers generated from DNSPolicy. Duplicated nameservers will be removed.

      false

      string array

      searches

      A list of DNS search domains for host-name lookup. This will be appended to the base search paths generated from DNSPolicy. Duplicated search paths will be removed.

      false

      string array

      options

      A list of DNS resolver options. This will be merged with the base options generated from DNSPolicy. Duplicated entries will be removed. Resolution options given in Options will override those that appear in the base DNSPolicy.

      false

      v1.PodDNSConfigOption array

      +

      v1.NFSVolumeSource

      @@ -4183,6 +4238,47 @@ Examples:
      +
      +
      +

      v1beta1.CronJobStatus

      +
      +

      CronJobStatus represents the current state of a cron job.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      active

      A list of pointers to currently running jobs.

      false

      v1.ObjectReference array

      lastScheduleTime

      Information when was the last time the job was successfully scheduled.

      false

      string

      +

      v1.FCVolumeSource

      @@ -4245,47 +4341,6 @@ Examples:
      -
      -
      -

      v1beta1.CronJobStatus

      -
      -

      CronJobStatus represents the current state of a cron job.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      active

      A list of pointers to currently running jobs.

      false

      v1.ObjectReference array

      lastScheduleTime

      Information when was the last time the job was successfully scheduled.

      false

      string

      -

      v1.PodAntiAffinity

      @@ -4598,6 +4653,13 @@ Examples:
      +

      volumeDevices

      +

      volumeDevices is the list of block devices to be used by the container. This is an alpha feature and may change in the future.

      +

      false

      +

      v1.VolumeDevice array

      + + +

      livenessProbe

      Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes

      false

      @@ -4641,7 +4703,7 @@ Examples:

      securityContext

      -

      Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md

      +

      Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/

      false

      v1.SecurityContext

      @@ -4893,6 +4955,47 @@ Examples:
      +
      +
      +

      v1.VolumeDevice

      +
      +

      volumeDevice describes a mapping of a raw block device within a container.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      name must match the name of a persistentVolumeClaim in the pod

      true

      string

      devicePath

      devicePath is the path inside of the container that the device will be mapped to.

      true

      string

      +

      v1.NodeSelectorRequirement

      @@ -5033,6 +5136,47 @@ Examples:
      +
      +
      +

      v1.PodDNSConfigOption

      +
      +

      PodDNSConfigOption defines DNS resolver options of a pod.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      Required.

      false

      string

      value

      false

      string

      +

      v1beta1.CronJobList

      diff --git a/docs/api-reference/batch/v1beta1/operations.html b/docs/api-reference/batch/v1beta1/operations.html index 427be1e6ee0..b91b8d16503 100755 --- a/docs/api-reference/batch/v1beta1/operations.html +++ b/docs/api-reference/batch/v1beta1/operations.html @@ -1434,7 +1434,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      diff --git a/docs/api-reference/batch/v2alpha1/definitions.html b/docs/api-reference/batch/v2alpha1/definitions.html index 949c1d6f871..12662acce97 100755 --- a/docs/api-reference/batch/v2alpha1/definitions.html +++ b/docs/api-reference/batch/v2alpha1/definitions.html @@ -1433,7 +1433,7 @@ Populated by the system. Read-only. Null for lists. More info:

      deletionTimestamp

      -

      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 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.
      +

      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.

      Populated by the system when a graceful deletion is requested. Read-only. More info:
      https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      @@ -1572,7 +1572,7 @@ When an object is created, the system will populate this list with the current s

      targetPortal

      -

      iSCSI target portal. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      +

      iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      true

      string

      @@ -1586,14 +1586,14 @@ When an object is created, the system will populate this list with the current s

      lun

      -

      iSCSI target lun number.

      +

      iSCSI Target Lun number.

      true

      integer (int32)

      iscsiInterface

      -

      Optional: Defaults to default (tcp). iSCSI interface name that uses an iSCSI transport.

      +

      iSCSI Interface Name that uses an iSCSI transport. Defaults to default (tcp).

      false

      string

      @@ -1614,7 +1614,7 @@ When an object is created, the system will populate this list with the current s

      portals

      -

      iSCSI target portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      +

      iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      false

      string array

      @@ -1635,14 +1635,14 @@ When an object is created, the system will populate this list with the current s

      secretRef

      -

      CHAP secret for iSCSI target and initiator authentication

      +

      CHAP Secret for iSCSI target and initiator authentication

      false

      v1.LocalObjectReference

      initiatorName

      -

      Custom iSCSI initiator name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface <target portal>:<volume name> will be created for the connection.

      +

      Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface <target portal>:<volume name> will be created for the connection.

      false

      string

      @@ -1695,7 +1695,7 @@ When an object is created, the system will populate this list with the current s

      v1.PodAffinityTerm

      -

      Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key <topologyKey> tches that of any node on which a pod of the set of pods is running

      +

      Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key <topologyKey> matches that of any node on which a pod of the set of pods is running

      @@ -1731,8 +1731,8 @@ When an object is created, the system will populate this list with the current s - - + + @@ -2020,7 +2020,7 @@ When an object is created, the system will populate this list with the current s

      v1.FlexVolumeSource

      -

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.

      +

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.

      topologyKey

      This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. For PreferredDuringScheduling pod anti-affinity, empty topologyKey is interpreted as "all topologies" ("all topologies" here means all the topologyKeys indicated by scheduler command-line argument --failure-domains); for affinity and for RequiredDuringScheduling pod anti-affinity, empty topologyKey is not allowed.

      false

      This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.

      true

      string

      @@ -2173,7 +2173,7 @@ When an object is created, the system will populate this list with the current s - + @@ -2442,7 +2442,7 @@ When an object is created, the system will populate this list with the current s - + @@ -2518,7 +2518,7 @@ When an object is created, the system will populate this list with the current s - + @@ -2636,7 +2636,7 @@ When an object is created, the system will populate this list with the current s - + @@ -3144,7 +3144,7 @@ When an object is created, the system will populate this list with the current s - + @@ -3275,6 +3275,13 @@ When an object is created, the system will populate this list with the current s + + + + + + +

      concurrencyPolicy

      Specifies how to treat concurrent executions of a Job. Defaults to Allow.

      Specifies how to treat concurrent executions of a Job. Valid values are: - "Allow" (default): allows CronJobs to run concurrently; - "Forbid": forbids concurrent runs, skipping next run if previous run hasn’t finished yet; - "Replace": cancels currently running job and replaces it with a new one

      false

      string

      manualSelector

      manualSelector controls generation of pod labels and pod selectors. Leave manualSelector unset unless you are certain what you are doing. When false or unset, the system pick labels unique to this job and appends those labels to the pod template. When true, the user is responsible for picking unique labels and specifying the selector. Failure to pick a unique label may cause this and other jobs to not function correctly. However, You may see manualSelector=true in jobs that were created with the old extensions/v1beta1 API. More info: https://git.k8s.io/community/contributors/design-proposals/selector-generation.md

      manualSelector controls generation of pod labels and pod selectors. Leave manualSelector unset unless you are certain what you are doing. When false or unset, the system pick labels unique to this job and appends those labels to the pod template. When true, the user is responsible for picking unique labels and specifying the selector. Failure to pick a unique label may cause this and other jobs to not function correctly. However, You may see manualSelector=true in jobs that were created with the old extensions/v1beta1 API. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/#specifying-your-own-pod-selector

      false

      boolean

      false

      propagationPolicy

      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.

      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.

      false

      v1.DeletionPropagation

      flexVolume

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.

      false

      v1.FlexVolumeSource

      dnsPolicy

      Set DNS policy for containers within the pod. One of ClusterFirstWithHostNet, ClusterFirst or Default. Defaults to "ClusterFirst". To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to ClusterFirstWithHostNet.

      Set DNS policy for the pod. Defaults to "ClusterFirst". Valid values are ClusterFirstWithHostNet, ClusterFirst, Default or None. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to ClusterFirstWithHostNet. Note that None policy is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it.

      false

      string

      integer (int32)

      dnsConfig

      Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy. This is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it.

      false

      v1.PodDNSConfig

      @@ -3820,21 +3827,21 @@ Examples:

      protectionDomain

      -

      The name of the Protection Domain for the configured storage (defaults to "default").

      +

      The name of the ScaleIO Protection Domain for the configured storage.

      false

      string

      storagePool

      -

      The Storage Pool associated with the protection domain (defaults to "default").

      +

      The ScaleIO Storage Pool associated with the protection domain.

      false

      string

      storageMode

      -

      Indicates whether the storage for a volume should be thick or thin (defaults to "thin").

      +

      Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned.

      false

      string

      @@ -3991,6 +3998,54 @@ Examples:
      +
      +
      +

      v1.PodDNSConfig

      +
      +

      PodDNSConfig defines the DNS parameters of a pod in addition to those generated from DNSPolicy.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      nameservers

      A list of DNS name server IP addresses. This will be appended to the base nameservers generated from DNSPolicy. Duplicated nameservers will be removed.

      false

      string array

      searches

      A list of DNS search domains for host-name lookup. This will be appended to the base search paths generated from DNSPolicy. Duplicated search paths will be removed.

      false

      string array

      options

      A list of DNS resolver options. This will be merged with the base options generated from DNSPolicy. Duplicated entries will be removed. Resolution options given in Options will override those that appear in the base DNSPolicy.

      false

      v1.PodDNSConfigOption array

      +

      v1.NFSVolumeSource

      @@ -4454,6 +4509,13 @@ Examples:
      +

      volumeDevices

      +

      volumeDevices is the list of block devices to be used by the container. This is an alpha feature and may change in the future.

      +

      false

      +

      v1.VolumeDevice array

      + + +

      livenessProbe

      Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes

      false

      @@ -4497,7 +4559,7 @@ Examples:

      securityContext

      -

      Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md

      +

      Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/

      false

      v1.SecurityContext

      @@ -4749,6 +4811,47 @@ Examples:
      +
      +
      +

      v1.VolumeDevice

      +
      +

      volumeDevice describes a mapping of a raw block device within a container.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      name must match the name of a persistentVolumeClaim in the pod

      true

      string

      devicePath

      devicePath is the path inside of the container that the device will be mapped to.

      true

      string

      +

      v1.NodeSelectorRequirement

      @@ -4889,6 +4992,47 @@ Examples:
      +
      +
      +

      v1.PodDNSConfigOption

      +
      +

      PodDNSConfigOption defines DNS resolver options of a pod.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      Required.

      false

      string

      value

      false

      string

      +

      v1.CinderVolumeSource

      diff --git a/docs/api-reference/batch/v2alpha1/operations.html b/docs/api-reference/batch/v2alpha1/operations.html index 61e2c3bd707..6fbcf1d6cc2 100755 --- a/docs/api-reference/batch/v2alpha1/operations.html +++ b/docs/api-reference/batch/v2alpha1/operations.html @@ -1434,7 +1434,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      diff --git a/docs/api-reference/certificates.k8s.io/v1beta1/definitions.html b/docs/api-reference/certificates.k8s.io/v1beta1/definitions.html index 31363cb898d..22ceab74cc5 100755 --- a/docs/api-reference/certificates.k8s.io/v1beta1/definitions.html +++ b/docs/api-reference/certificates.k8s.io/v1beta1/definitions.html @@ -503,7 +503,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      propagationPolicy

      -

      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.

      +

      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.

      false

      v1.DeletionPropagation

      @@ -1136,7 +1136,7 @@ Populated by the system. Read-only. Null for lists. More info:

      deletionTimestamp

      -

      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 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.
      +

      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.

      Populated by the system when a graceful deletion is requested. Read-only. More info:
      https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      diff --git a/docs/api-reference/certificates.k8s.io/v1beta1/operations.html b/docs/api-reference/certificates.k8s.io/v1beta1/operations.html index 90231eba7ab..545af0f89d2 100755 --- a/docs/api-reference/certificates.k8s.io/v1beta1/operations.html +++ b/docs/api-reference/certificates.k8s.io/v1beta1/operations.html @@ -1220,7 +1220,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      diff --git a/docs/api-reference/events.k8s.io/v1beta1/definitions.html b/docs/api-reference/events.k8s.io/v1beta1/definitions.html new file mode 100755 index 00000000000..b3fd61ea23d --- /dev/null +++ b/docs/api-reference/events.k8s.io/v1beta1/definitions.html @@ -0,0 +1,1617 @@ + + + + + + +Top Level API Objects + + + + +
      +
      +

      Top Level API Objects

      +
      + +
      +
      +
      +

      Definitions

      +
      +
      +

      v1.APIResourceList

      +
      +

      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.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      groupVersion

      groupVersion is the group and version this APIResourceList is for.

      true

      string

      resources

      resources contains the name of the resources and if they are namespaced.

      true

      v1.APIResource array

      + +
      +
      +

      v1.Patch

      +
      +

      Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.

      +
      +
      +
      +

      v1.DeleteOptions

      +
      +

      DeleteOptions may be provided when deleting an API object.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      gracePeriodSeconds

      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.

      false

      integer (int64)

      preconditions

      Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.

      false

      v1.Preconditions

      orphanDependents

      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.

      false

      boolean

      false

      propagationPolicy

      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.

      false

      v1.DeletionPropagation

      + +
      +
      +

      v1beta1.Event

      +
      +

      Event is a report of an event somewhere in the cluster. It generally denotes some state change in the system.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      false

      v1.ObjectMeta

      eventTime

      Required. Time when this Event was first observed.

      true

      string

      series

      Data about the Event series this event represents or nil if it’s a singleton Event.

      false

      v1beta1.EventSeries

      reportingController

      Name of the controller that emitted this Event, e.g. kubernetes.io/kubelet.

      false

      string

      reportingInstance

      ID of the controller instance, e.g. kubelet-xyzf.

      true

      string

      action

      What action was taken/failed regarding to the regarding object.

      true

      string

      reason

      Why the action was taken.

      false

      string

      regarding

      The object this Event is about. In most cases it’s an Object reporting controller implements. E.g. ReplicaSetController implements ReplicaSets and this event is emitted because it acts on some changes in a ReplicaSet object.

      false

      v1.ObjectReference

      related

      Optional secondary object for more complex actions. E.g. when regarding object triggers a creation or deletion of related object.

      false

      v1.ObjectReference

      note

      Optional. A human-readable description of the status of this operation. Maximal length of the note is 1kB, but libraries should be prepared to handle values up to 64kB.

      false

      string

      type

      Type of this event (Normal, Warning), new types could be added in the future.

      false

      string

      deprecatedSource

      Deprecated field assuring backward compatibility with core.v1 Event type

      false

      v1.EventSource

      deprecatedFirstTimestamp

      Deprecated field assuring backward compatibility with core.v1 Event type

      false

      string

      deprecatedLastTimestamp

      Deprecated field assuring backward compatibility with core.v1 Event type

      false

      string

      deprecatedCount

      Deprecated field assuring backward compatibility with core.v1 Event type

      false

      integer (int32)

      + +
      +
      +

      v1.ListMeta

      +
      +

      ListMeta describes metadata that synthetic resources must have, including lists and various status objects. A resource may have only one of {ObjectMeta, ListMeta}.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      selfLink

      selfLink is a URL representing this object. Populated by the system. Read-only.

      false

      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/api-conventions.md#concurrency-control-and-consistency

      false

      string

      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 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.

      false

      string

      + +
      +
      +

      v1.StatusDetails

      +
      +

      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.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).

      false

      string

      group

      The group attribute of the resource associated with the status StatusReason.

      false

      string

      kind

      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/api-conventions.md#types-kinds

      false

      string

      uid

      UID of the resource. (when there is a single resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids

      false

      string

      causes

      The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.

      false

      v1.StatusCause array

      retryAfterSeconds

      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.

      false

      integer (int32)

      + +
      +
      +

      v1.Preconditions

      +
      +

      Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      uid

      Specifies the target UID.

      false

      types.UID

      + +
      +
      +

      v1.Initializers

      +
      +

      Initializers tracks the progress of initialization.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      pending

      Pending is a list of initializers that must execute in order before this object is visible. When the last pending initializer is removed, and no failing result is set, the initializers struct will be set to nil and the object is considered as initialized and visible to all clients.

      true

      v1.Initializer array

      result

      If result is set with the Failure field, the object will be persisted to storage and then deleted, ensuring that other clients can observe the deletion.

      false

      v1.Status

      + +
      +
      +

      v1.Initializer

      +
      +

      Initializer is information about an initializer that has not yet completed.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      name of the process that is responsible for initializing this object.

      true

      string

      + +
      +
      +

      v1.ObjectReference

      +
      +

      ObjectReference contains enough information to let you inspect or modify the referred object.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      string

      namespace

      Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/

      false

      string

      name

      Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      false

      string

      uid

      UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids

      false

      string

      apiVersion

      API version of the referent.

      false

      string

      resourceVersion

      Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency

      false

      string

      fieldPath

      If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object.

      false

      string

      + +
      +
      +

      v1.Status

      +
      +

      Status is a return value for calls that don’t return other objects.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      status

      Status of the operation. One of: "Success" or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      string

      message

      A human-readable description of the status of this operation.

      false

      string

      reason

      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.

      false

      string

      details

      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.

      false

      v1.StatusDetails

      code

      Suggested HTTP return code for this status, 0 if not set.

      false

      integer (int32)

      + +
      +
      +

      v1beta1.EventSeries

      +
      +

      EventSeries contain information on series of events, i.e. thing that was/is happening continously for some time.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      count

      Number of occurrences in this series up to the last heartbeat time

      true

      integer (int32)

      lastObservedTime

      Time when last Event from the series was seen before last heartbeat.

      true

      string

      state

      Information whether this series is ongoing or finished.

      true

      string

      + +
      +
      +

      v1.WatchEvent

      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      type

      true

      string

      object

      true

      string

      + +
      +
      +

      v1.ObjectMeta

      +
      +

      ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      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: http://kubernetes.io/docs/user-guide/identifiers#names

      false

      string

      generateName

      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.
      +
      +If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header).
      +
      +Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency

      false

      string

      namespace

      Namespace defines the space within 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.
      +
      +Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces

      false

      string

      selfLink

      SelfLink is a URL representing this object. Populated by the system. Read-only.

      false

      string

      uid

      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.
      +
      +Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids

      false

      string

      resourceVersion

      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.
      +
      +Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency

      false

      string

      generation

      A sequence number representing a specific generation of the desired state. Populated by the system. Read-only.

      false

      integer (int64)

      creationTimestamp

      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.
      +
      +Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      string

      deletionTimestamp

      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.
      +
      +Populated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      string

      deletionGracePeriodSeconds

      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.

      false

      integer (int64)

      labels

      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: http://kubernetes.io/docs/user-guide/labels

      false

      object

      annotations

      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: http://kubernetes.io/docs/user-guide/annotations

      false

      object

      ownerReferences

      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.

      false

      v1.OwnerReference array

      initializers

      An initializer is a controller which enforces some system invariant at object creation time. This field is a list of initializers that have not yet acted on this object. If nil or empty, this object has been completely initialized. Otherwise, the object is considered uninitialized and is hidden (in list/watch and get calls) from clients that haven’t explicitly asked to observe uninitialized objects.
      +
      +When an object is created, the system will populate this list with the current set of initializers. Only privileged users may set or modify this list. Once it is empty, it may not be modified further by any user.

      false

      v1.Initializers

      finalizers

      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.

      false

      string array

      clusterName

      The name of the cluster which the object belongs to. This is used to distinguish resources with same name and namespace in different clusters. This field is not set anywhere right now and apiserver is going to ignore it if set in create or update request.

      false

      string

      + +
      +
      +

      v1.OwnerReference

      +
      +

      OwnerReference contains enough information to let you identify an owning object. Currently, an owning object must be in the same namespace, so there is no namespace field.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      apiVersion

      API version of the referent.

      true

      string

      kind

      Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      true

      string

      name

      Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names

      true

      string

      uid

      UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids

      true

      string

      controller

      If true, this reference points to the managing controller.

      false

      boolean

      false

      blockOwnerDeletion

      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. Defaults to false. To set this field, a user needs "delete" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.

      false

      boolean

      false

      + +
      +
      +

      v1.APIResource

      +
      +

      APIResource specifies the name of a resource and whether it is namespaced.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      name is the plural name of the resource.

      true

      string

      singularName

      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.

      true

      string

      namespaced

      namespaced indicates if a resource is namespaced or not.

      true

      boolean

      false

      group

      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".

      false

      string

      version

      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)".

      false

      string

      kind

      kind is the kind for the resource (e.g. Foo is the kind for a resource foo)

      true

      string

      verbs

      verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy)

      true

      string array

      shortNames

      shortNames is a list of suggested short names of the resource.

      false

      string array

      categories

      categories is a list of the grouped resources this resource belongs to (e.g. all)

      false

      string array

      + +
      +
      +

      v1.EventSource

      +
      +

      EventSource contains information for an event.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      component

      Component from which the event is generated.

      false

      string

      host

      Node name on which the event is generated.

      false

      string

      + +
      +
      +

      types.UID

      + +
      +
      +

      v1beta1.EventList

      +
      +

      EventList is a list of Event objects.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ListMeta

      items

      Items is a list of schema objects.

      true

      v1beta1.Event array

      + +
      +
      +

      v1.StatusCause

      +
      +

      StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      reason

      A machine-readable description of the cause of the error. If this value is empty there is no information available.

      false

      string

      message

      A human-readable description of the cause of the error. This field may be presented as-is to a reader.

      false

      string

      field

      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.
      +
      +Examples:
      + "name" - the field "name" on the current resource
      + "items[0].name" - the field "name" on the first array entry in "items"

      false

      string

      + +
      +
      +

      v1.DeletionPropagation

      + +
      +
      +

      any

      +
      +

      Represents an untyped JSON map - see the description of the field for more info about the structure of this object.

      +
      +
      +
      +
      +
      + + + \ No newline at end of file diff --git a/docs/api-reference/events.k8s.io/v1beta1/operations.html b/docs/api-reference/events.k8s.io/v1beta1/operations.html new file mode 100755 index 00000000000..028d7788135 --- /dev/null +++ b/docs/api-reference/events.k8s.io/v1beta1/operations.html @@ -0,0 +1,2212 @@ + + + + + + +Operations + + + + +
      +
      +

      Operations

      +
      +
      +

      get available resources

      +
      +
      +
      GET /apis/events.k8s.io/v1beta1
      +
      +
      +
      +

      Responses

      + +++++ + + + + + + + + + + + + + + +
      HTTP CodeDescriptionSchema

      default

      success

      v1.APIResourceList

      + +
      +
      +

      Consumes

      +
      +
        +
      • +

        application/json

        +
      • +
      • +

        application/yaml

        +
      • +
      • +

        application/vnd.kubernetes.protobuf

        +
      • +
      +
      +
      +
      +

      Produces

      +
      +
        +
      • +

        application/json

        +
      • +
      • +

        application/yaml

        +
      • +
      • +

        application/vnd.kubernetes.protobuf

        +
      • +
      +
      +
      +
      +

      Tags

      +
      +
        +
      • +

        apisevents.k8s.iov1beta1

        +
      • +
      +
      +
      +
      +
      +

      list or watch objects of kind Event

      +
      +
      +
      GET /apis/events.k8s.io/v1beta1/events
      +
      +
      +
      +

      Parameters

      + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. +

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      + +
      +
      +

      Responses

      + +++++ + + + + + + + + + + + + + + +
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.EventList

      + +
      +
      +

      Consumes

      +
      +
        +
      • +

        /

        +
      • +
      +
      +
      +
      +

      Produces

      +
      +
        +
      • +

        application/json

        +
      • +
      • +

        application/yaml

        +
      • +
      • +

        application/vnd.kubernetes.protobuf

        +
      • +
      • +

        application/json;stream=watch

        +
      • +
      • +

        application/vnd.kubernetes.protobuf;stream=watch

        +
      • +
      +
      +
      +
      +

      Tags

      +
      +
        +
      • +

        apisevents.k8s.iov1beta1

        +
      • +
      +
      +
      +
      +
      +

      list or watch objects of kind Event

      +
      +
      +
      GET /apis/events.k8s.io/v1beta1/namespaces/{namespace}/events
      +
      +
      +
      +

      Parameters

      + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. +

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      + +
      +
      +

      Responses

      + +++++ + + + + + + + + + + + + + + +
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.EventList

      + +
      +
      +

      Consumes

      +
      +
        +
      • +

        /

        +
      • +
      +
      +
      +
      +

      Produces

      +
      +
        +
      • +

        application/json

        +
      • +
      • +

        application/yaml

        +
      • +
      • +

        application/vnd.kubernetes.protobuf

        +
      • +
      • +

        application/json;stream=watch

        +
      • +
      • +

        application/vnd.kubernetes.protobuf;stream=watch

        +
      • +
      +
      +
      +
      +

      Tags

      +
      +
        +
      • +

        apisevents.k8s.iov1beta1

        +
      • +
      +
      +
      +
      +
      +

      delete collection of Event

      +
      +
      +
      DELETE /apis/events.k8s.io/v1beta1/namespaces/{namespace}/events
      +
      +
      +
      +

      Parameters

      + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. +

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      + +
      +
      +

      Responses

      + +++++ + + + + + + + + + + + + + + +
      HTTP CodeDescriptionSchema

      200

      success

      v1.Status

      + +
      +
      +

      Consumes

      +
      +
        +
      • +

        /

        +
      • +
      +
      +
      +
      +

      Produces

      +
      +
        +
      • +

        application/json

        +
      • +
      • +

        application/yaml

        +
      • +
      • +

        application/vnd.kubernetes.protobuf

        +
      • +
      +
      +
      +
      +

      Tags

      +
      +
        +
      • +

        apisevents.k8s.iov1beta1

        +
      • +
      +
      +
      +
      +
      +

      create an Event

      +
      +
      +
      POST /apis/events.k8s.io/v1beta1/namespaces/{namespace}/events
      +
      +
      +
      +

      Parameters

      + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1beta1.Event

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      + +
      +
      +

      Responses

      + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
      HTTP CodeDescriptionSchema

      202

      Accepted

      v1beta1.Event

      200

      success

      v1beta1.Event

      201

      Created

      v1beta1.Event

      + +
      +
      +

      Consumes

      +
      +
        +
      • +

        /

        +
      • +
      +
      +
      +
      +

      Produces

      +
      +
        +
      • +

        application/json

        +
      • +
      • +

        application/yaml

        +
      • +
      • +

        application/vnd.kubernetes.protobuf

        +
      • +
      +
      +
      +
      +

      Tags

      +
      +
        +
      • +

        apisevents.k8s.iov1beta1

        +
      • +
      +
      +
      +
      +
      +

      read the specified Event

      +
      +
      +
      GET /apis/events.k8s.io/v1beta1/namespaces/{namespace}/events/{name}
      +
      +
      +
      +

      Parameters

      + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      export

      Should this value be exported. Export strips fields that a user can not specify.

      false

      boolean

      QueryParameter

      exact

      Should the export be exact. Exact export maintains cluster-specific fields like Namespace.

      false

      boolean

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Event

      true

      string

      + +
      +
      +

      Responses

      + +++++ + + + + + + + + + + + + + + +
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.Event

      + +
      +
      +

      Consumes

      +
      +
        +
      • +

        /

        +
      • +
      +
      +
      +
      +

      Produces

      +
      +
        +
      • +

        application/json

        +
      • +
      • +

        application/yaml

        +
      • +
      • +

        application/vnd.kubernetes.protobuf

        +
      • +
      +
      +
      +
      +

      Tags

      +
      +
        +
      • +

        apisevents.k8s.iov1beta1

        +
      • +
      +
      +
      +
      +
      +

      replace the specified Event

      +
      +
      +
      PUT /apis/events.k8s.io/v1beta1/namespaces/{namespace}/events/{name}
      +
      +
      +
      +

      Parameters

      + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1beta1.Event

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Event

      true

      string

      + +
      +
      +

      Responses

      + +++++ + + + + + + + + + + + + + + + + + + + +
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.Event

      201

      Created

      v1beta1.Event

      + +
      +
      +

      Consumes

      +
      +
        +
      • +

        /

        +
      • +
      +
      +
      +
      +

      Produces

      +
      +
        +
      • +

        application/json

        +
      • +
      • +

        application/yaml

        +
      • +
      • +

        application/vnd.kubernetes.protobuf

        +
      • +
      +
      +
      +
      +

      Tags

      +
      +
        +
      • +

        apisevents.k8s.iov1beta1

        +
      • +
      +
      +
      +
      +
      +

      delete an Event

      +
      +
      +
      DELETE /apis/events.k8s.io/v1beta1/namespaces/{namespace}/events/{name}
      +
      +
      +
      +

      Parameters

      + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.DeleteOptions

      QueryParameter

      gracePeriodSeconds

      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.

      false

      integer (int32)

      QueryParameter

      orphanDependents

      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.

      false

      boolean

      QueryParameter

      propagationPolicy

      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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Event

      true

      string

      + +
      +
      +

      Responses

      + +++++ + + + + + + + + + + + + + + +
      HTTP CodeDescriptionSchema

      200

      success

      v1.Status

      + +
      +
      +

      Consumes

      +
      +
        +
      • +

        /

        +
      • +
      +
      +
      +
      +

      Produces

      +
      +
        +
      • +

        application/json

        +
      • +
      • +

        application/yaml

        +
      • +
      • +

        application/vnd.kubernetes.protobuf

        +
      • +
      +
      +
      +
      +

      Tags

      +
      +
        +
      • +

        apisevents.k8s.iov1beta1

        +
      • +
      +
      +
      +
      +
      +

      partially update the specified Event

      +
      +
      +
      PATCH /apis/events.k8s.io/v1beta1/namespaces/{namespace}/events/{name}
      +
      +
      +
      +

      Parameters

      + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Patch

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Event

      true

      string

      + +
      +
      +

      Responses

      + +++++ + + + + + + + + + + + + + + +
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.Event

      + +
      +
      +

      Consumes

      +
      +
        +
      • +

        application/json-patch+json

        +
      • +
      • +

        application/merge-patch+json

        +
      • +
      • +

        application/strategic-merge-patch+json

        +
      • +
      +
      +
      +
      +

      Produces

      +
      +
        +
      • +

        application/json

        +
      • +
      • +

        application/yaml

        +
      • +
      • +

        application/vnd.kubernetes.protobuf

        +
      • +
      +
      +
      +
      +

      Tags

      +
      +
        +
      • +

        apisevents.k8s.iov1beta1

        +
      • +
      +
      +
      +
      +
      +

      watch individual changes to a list of Event

      +
      +
      +
      GET /apis/events.k8s.io/v1beta1/watch/events
      +
      +
      +
      +

      Parameters

      + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. +

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      + +
      +
      +

      Responses

      + +++++ + + + + + + + + + + + + + + +
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      + +
      +
      +

      Consumes

      +
      +
        +
      • +

        /

        +
      • +
      +
      +
      +
      +

      Produces

      +
      +
        +
      • +

        application/json

        +
      • +
      • +

        application/yaml

        +
      • +
      • +

        application/vnd.kubernetes.protobuf

        +
      • +
      • +

        application/json;stream=watch

        +
      • +
      • +

        application/vnd.kubernetes.protobuf;stream=watch

        +
      • +
      +
      +
      +
      +

      Tags

      +
      +
        +
      • +

        apisevents.k8s.iov1beta1

        +
      • +
      +
      +
      +
      +
      +

      watch individual changes to a list of Event

      +
      +
      +
      GET /apis/events.k8s.io/v1beta1/watch/namespaces/{namespace}/events
      +
      +
      +
      +

      Parameters

      + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. +

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      + +
      +
      +

      Responses

      + +++++ + + + + + + + + + + + + + + +
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      + +
      +
      +

      Consumes

      +
      +
        +
      • +

        /

        +
      • +
      +
      +
      +
      +

      Produces

      +
      +
        +
      • +

        application/json

        +
      • +
      • +

        application/yaml

        +
      • +
      • +

        application/vnd.kubernetes.protobuf

        +
      • +
      • +

        application/json;stream=watch

        +
      • +
      • +

        application/vnd.kubernetes.protobuf;stream=watch

        +
      • +
      +
      +
      +
      +

      Tags

      +
      +
        +
      • +

        apisevents.k8s.iov1beta1

        +
      • +
      +
      +
      +
      +
      +

      watch changes to an object of kind Event

      +
      +
      +
      GET /apis/events.k8s.io/v1beta1/watch/namespaces/{namespace}/events/{name}
      +
      +
      +
      +

      Parameters

      + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. +

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Event

      true

      string

      + +
      +
      +

      Responses

      + +++++ + + + + + + + + + + + + + + +
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      + +
      +
      +

      Consumes

      +
      +
        +
      • +

        /

        +
      • +
      +
      +
      +
      +

      Produces

      +
      +
        +
      • +

        application/json

        +
      • +
      • +

        application/yaml

        +
      • +
      • +

        application/vnd.kubernetes.protobuf

        +
      • +
      • +

        application/json;stream=watch

        +
      • +
      • +

        application/vnd.kubernetes.protobuf;stream=watch

        +
      • +
      +
      +
      +
      +

      Tags

      +
      +
        +
      • +

        apisevents.k8s.iov1beta1

        +
      • +
      +
      +
      +
      +
      +
      +
      + + + \ No newline at end of file diff --git a/docs/api-reference/extensions/v1beta1/definitions.html b/docs/api-reference/extensions/v1beta1/definitions.html index 5b6c556941f..bd302b0eb89 100755 --- a/docs/api-reference/extensions/v1beta1/definitions.html +++ b/docs/api-reference/extensions/v1beta1/definitions.html @@ -726,6 +726,13 @@ Examples: /foo would allow /foo, /foo/ an

      integer (int32)

      + +

      conditions

      +

      Represents the latest available observations of a DaemonSet’s current state.

      +

      false

      +

      v1beta1.DaemonSetCondition array

      + + @@ -1388,7 +1395,7 @@ Examples: /foo would allow /foo, /foo/ an

      v1beta1.NetworkPolicyList

      -

      Network Policy List is a list of NetworkPolicy objects.

      +

      DEPRECATED 1.9 - This group version of NetworkPolicyList is deprecated by networking/v1/NetworkPolicyList. Network Policy List is a list of NetworkPolicy objects.

      @@ -2178,7 +2185,7 @@ Populated by the system. Read-only. Null for lists. More info:

      deletionTimestamp

      -
      @@ -2503,7 +2510,7 @@ When an object is created, the system will populate this list with the current s - + @@ -2517,14 +2524,14 @@ When an object is created, the system will populate this list with the current s - + - + @@ -2545,7 +2552,7 @@ When an object is created, the system will populate this list with the current s - + @@ -2566,14 +2573,14 @@ When an object is created, the system will populate this list with the current s - + - + @@ -2715,7 +2722,7 @@ When an object is created, the system will populate this list with the current s

      v1.PodAffinityTerm

      -

      Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key <topologyKey> tches that of any node on which a pod of the set of pods is running

      +

      Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key <topologyKey> matches that of any node on which a pod of the set of pods is running

      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 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.
      +

      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.

      Populated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      targetPortal

      iSCSI target portal. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      true

      string

      lun

      iSCSI target lun number.

      iSCSI Target Lun number.

      true

      integer (int32)

      iscsiInterface

      Optional: Defaults to default (tcp). iSCSI interface name that uses an iSCSI transport.

      iSCSI Interface Name that uses an iSCSI transport. Defaults to default (tcp).

      false

      string

      portals

      iSCSI target portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      false

      string array

      secretRef

      CHAP secret for iSCSI target and initiator authentication

      CHAP Secret for iSCSI target and initiator authentication

      false

      v1.LocalObjectReference

      initiatorName

      Custom iSCSI initiator name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface <target portal>:<volume name> will be created for the connection.

      Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface <target portal>:<volume name> will be created for the connection.

      false

      string

      @@ -2751,8 +2758,8 @@ When an object is created, the system will populate this list with the current s - - + + @@ -3011,7 +3018,7 @@ When an object is created, the system will populate this list with the current s - + @@ -3156,7 +3163,7 @@ When an object is created, the system will populate this list with the current s

      v1.FlexVolumeSource

      -

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.

      +

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.

      topologyKey

      This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. For PreferredDuringScheduling pod anti-affinity, empty topologyKey is interpreted as "all topologies" ("all topologies" here means all the topologyKeys indicated by scheduler command-line argument --failure-domains); for affinity and for RequiredDuringScheduling pod anti-affinity, empty topologyKey is not allowed.

      false

      This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.

      true

      string

      seLinuxOptions

      seLinuxOptions required to run as; required for MustRunAs More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md

      seLinuxOptions required to run as; required for MustRunAs More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/

      false

      v1.SELinuxOptions

      @@ -3273,7 +3280,7 @@ When an object is created, the system will populate this list with the current s

      v1beta1.IPBlock

      -

      IPBlock describes a particular CIDR (Ex. "192.168.1.1/24") that is allowed to the pods matched by a NetworkPolicySpec’s podSelector. The except entry describes CIDRs that should not be included within this rule.

      +

      DEPRECATED 1.9 - This group version of IPBlock is deprecated by networking/v1/IPBlock. IPBlock describes a particular CIDR (Ex. "192.168.1.1/24") that is allowed to the pods matched by a NetworkPolicySpec’s podSelector. The except entry describes CIDRs that should not be included within this rule.

      @@ -3663,7 +3670,7 @@ When an object is created, the system will populate this list with the current s - + @@ -3836,7 +3843,7 @@ When an object is created, the system will populate this list with the current s - + @@ -4489,7 +4496,7 @@ When an object is created, the system will populate this list with the current s - + @@ -4620,6 +4627,13 @@ When an object is created, the system will populate this list with the current s + + + + + + +

      propagationPolicy

      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.

      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.

      false

      v1.DeletionPropagation

      flexVolume

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.

      false

      v1.FlexVolumeSource

      dnsPolicy

      Set DNS policy for containers within the pod. One of ClusterFirstWithHostNet, ClusterFirst or Default. Defaults to "ClusterFirst". To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to ClusterFirstWithHostNet.

      Set DNS policy for the pod. Defaults to "ClusterFirst". Valid values are ClusterFirstWithHostNet, ClusterFirst, Default or None. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to ClusterFirstWithHostNet. Note that None policy is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it.

      false

      string

      integer (int32)

      dnsConfig

      Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy. This is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it.

      false

      v1.PodDNSConfig

      @@ -5268,21 +5282,21 @@ Examples:

      protectionDomain

      -

      The name of the Protection Domain for the configured storage (defaults to "default").

      +

      The name of the ScaleIO Protection Domain for the configured storage.

      false

      string

      storagePool

      -

      The Storage Pool associated with the protection domain (defaults to "default").

      +

      The ScaleIO Storage Pool associated with the protection domain.

      false

      string

      storageMode

      -

      Indicates whether the storage for a volume should be thick or thin (defaults to "thin").

      +

      Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned.

      false

      string

      @@ -5315,7 +5329,7 @@ Examples:

      v1beta1.NetworkPolicy

      -

      NetworkPolicy describes what network traffic is allowed for a set of Pods

      +

      DEPRECATED 1.9 - This group version of NetworkPolicy is deprecated by networking/v1/NetworkPolicy. NetworkPolicy describes what network traffic is allowed for a set of Pods

      @@ -5498,6 +5512,54 @@ Examples:
      +
      +
      +

      v1.PodDNSConfig

      +
      +

      PodDNSConfig defines the DNS parameters of a pod in addition to those generated from DNSPolicy.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      nameservers

      A list of DNS name server IP addresses. This will be appended to the base nameservers generated from DNSPolicy. Duplicated nameservers will be removed.

      false

      string array

      searches

      A list of DNS search domains for host-name lookup. This will be appended to the base search paths generated from DNSPolicy. Duplicated search paths will be removed.

      false

      string array

      options

      A list of DNS resolver options. This will be merged with the base options generated from DNSPolicy. Duplicated entries will be removed. Resolution options given in Options will override those that appear in the base DNSPolicy.

      false

      v1.PodDNSConfigOption array

      +

      v1beta1.ScaleStatus

      @@ -5549,6 +5611,9 @@ Examples:

      v1beta1.NetworkPolicySpec

      +
      +

      DEPRECATED 1.9 - This group version of NetworkPolicySpec is deprecated by networking/v1/NetworkPolicySpec.

      +
      @@ -6042,6 +6107,9 @@ Both these may change in the future. Incoming requests are matched against the h

      v1beta1.NetworkPolicyPeer

      +
      +

      DEPRECATED 1.9 - This group version of NetworkPolicyPeer is deprecated by networking/v1/NetworkPolicyPeer.

      +
      @@ -6344,6 +6412,13 @@ Both these may change in the future. Incoming requests are matched against the h + + + + + + + @@ -6387,7 +6462,7 @@ Both these may change in the future. Incoming requests are matched against the h - + @@ -6484,7 +6559,7 @@ Both these may change in the future. Incoming requests are matched against the h

      v1beta1.NetworkPolicyIngressRule

      -

      This NetworkPolicyIngressRule matches traffic if and only if the traffic matches both ports AND from.

      +

      DEPRECATED 1.9 - This group version of NetworkPolicyIngressRule is deprecated by networking/v1/NetworkPolicyIngressRule. This NetworkPolicyIngressRule matches traffic if and only if the traffic matches both ports AND from.

      volumeDevices

      volumeDevices is the list of block devices to be used by the container. This is an alpha feature and may change in the future.

      false

      v1.VolumeDevice array

      livenessProbe

      Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes

      false

      securityContext

      Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md

      Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/

      false

      v1.SecurityContext

      @@ -6659,6 +6734,40 @@ Both these may change in the future. Incoming requests are matched against the h
      +
      +
      +

      v1beta1.AllowedFlexVolume

      +
      +

      AllowedFlexVolume represents a single Flexvolume that is allowed to be used.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      driver

      Driver is the name of the Flexvolume driver.

      true

      string

      +

      v1.APIResource

      @@ -6749,6 +6858,47 @@ Both these may change in the future. Incoming requests are matched against the h +
      +
      +

      v1.VolumeDevice

      +
      +

      volumeDevice describes a mapping of a raw block device within a container.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      name must match the name of a persistentVolumeClaim in the pod

      true

      string

      devicePath

      devicePath is the path inside of the container that the device will be mapped to.

      true

      string

      +

      v1.NodeSelectorRequirement

      @@ -6863,7 +7013,7 @@ Both these may change in the future. Incoming requests are matched against the h

      v1beta1.NetworkPolicyEgressRule

      -

      NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec’s podSelector. The traffic must match both ports and to. This type is beta-level in 1.8

      +

      DEPRECATED 1.9 - This group version of NetworkPolicyEgressRule is deprecated by networking/v1/NetworkPolicyEgressRule. NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec’s podSelector. The traffic must match both ports and to. This type is beta-level in 1.8

      @@ -6992,6 +7142,47 @@ Both these may change in the future. Incoming requests are matched against the h
      +
      +
      +

      v1.PodDNSConfigOption

      +
      +

      PodDNSConfigOption defines DNS resolver options of a pod.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      Required.

      false

      string

      value

      false

      string

      +

      v1beta1.DaemonSet

      @@ -7637,6 +7828,9 @@ Both these may change in the future. Incoming requests are matched against the h

      v1beta1.NetworkPolicyPort

      +
      +

      DEPRECATED 1.9 - This group version of NetworkPolicyPort is deprecated by networking/v1/NetworkPolicyPort.

      +
      @@ -7711,7 +7905,7 @@ Both these may change in the future. Incoming requests are matched against the h - + @@ -7821,6 +8015,13 @@ Both these may change in the future. Incoming requests are matched against the h + + + + + + +

      defaultAddCapabilities

      DefaultAddCapabilities is the default set of capabilities that will be added to the container unless the pod spec specifically drops the capability. You may not list a capabiility in both DefaultAddCapabilities and RequiredDropCapabilities.

      DefaultAddCapabilities is the default set of capabilities that will be added to the container unless the pod spec specifically drops the capability. You may not list a capability in both DefaultAddCapabilities and RequiredDropCapabilities. Capabilities added here are implicitly allowed, and need not be included in the AllowedCapabilities list.

      false

      v1.Capability array

      v1beta1.AllowedHostPath array

      allowedFlexVolumes

      AllowedFlexVolumes is a whitelist of allowed Flexvolumes. Empty or nil indicates that all Flexvolumes may be used. This parameter is effective only when the usage of the Flexvolumes is allowed in the "Volumes" field.

      false

      v1beta1.AllowedFlexVolume array

      @@ -8126,6 +8327,68 @@ Both these may change in the future. Incoming requests are matched against the h +
      +
      +

      v1beta1.DaemonSetCondition

      +
      +

      DaemonSetCondition describes the state of a DaemonSet at a certain point.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      type

      Type of DaemonSet condition.

      true

      string

      status

      Status of the condition, one of True, False, Unknown.

      true

      string

      lastTransitionTime

      Last time the condition transitioned from one status to another.

      false

      string

      reason

      The reason for the condition’s last transition.

      false

      string

      message

      A human readable message indicating details about the transition.

      false

      string

      +

      v1.ConfigMapKeySelector

      diff --git a/docs/api-reference/extensions/v1beta1/operations.html b/docs/api-reference/extensions/v1beta1/operations.html index c510e89a196..1a145200b39 100755 --- a/docs/api-reference/extensions/v1beta1/operations.html +++ b/docs/api-reference/extensions/v1beta1/operations.html @@ -1782,7 +1782,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -3204,7 +3204,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -5147,7 +5147,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -6569,7 +6569,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -7607,7 +7607,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -9931,7 +9931,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      diff --git a/docs/api-reference/networking.k8s.io/v1/definitions.html b/docs/api-reference/networking.k8s.io/v1/definitions.html index 902be04482a..cd9647d8801 100755 --- a/docs/api-reference/networking.k8s.io/v1/definitions.html +++ b/docs/api-reference/networking.k8s.io/v1/definitions.html @@ -1074,7 +1074,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      propagationPolicy

      -

      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.

      +

      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.

      false

      v1.DeletionPropagation

      @@ -1403,7 +1403,7 @@ Populated by the system. Read-only. Null for lists. More info:

      deletionTimestamp

      -

      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 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.
      +

      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.

      Populated by the system when a graceful deletion is requested. Read-only. More info:
      https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      diff --git a/docs/api-reference/networking.k8s.io/v1/operations.html b/docs/api-reference/networking.k8s.io/v1/operations.html index c7017c4a857..df2b7570528 100755 --- a/docs/api-reference/networking.k8s.io/v1/operations.html +++ b/docs/api-reference/networking.k8s.io/v1/operations.html @@ -1260,7 +1260,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      diff --git a/docs/api-reference/policy/v1beta1/definitions.html b/docs/api-reference/policy/v1beta1/definitions.html index 74c98357b52..43ee3821f86 100755 --- a/docs/api-reference/policy/v1beta1/definitions.html +++ b/docs/api-reference/policy/v1beta1/definitions.html @@ -565,7 +565,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      propagationPolicy

      -

      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.

      +

      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.

      false

      v1.DeletionPropagation

      @@ -1179,7 +1179,7 @@ Populated by the system. Read-only. Null for lists. More info:

      deletionTimestamp

      -

      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 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.
      +

      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.

      Populated by the system when a graceful deletion is requested. Read-only. More info:
      https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      diff --git a/docs/api-reference/policy/v1beta1/operations.html b/docs/api-reference/policy/v1beta1/operations.html index 8dc522d7bdf..7ff01593cc0 100755 --- a/docs/api-reference/policy/v1beta1/operations.html +++ b/docs/api-reference/policy/v1beta1/operations.html @@ -1260,7 +1260,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      diff --git a/docs/api-reference/rbac.authorization.k8s.io/v1/definitions.html b/docs/api-reference/rbac.authorization.k8s.io/v1/definitions.html index efc8db10ce2..2972fea36b8 100755 --- a/docs/api-reference/rbac.authorization.k8s.io/v1/definitions.html +++ b/docs/api-reference/rbac.authorization.k8s.io/v1/definitions.html @@ -454,12 +454,6 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } -
      -
      -

      v1.Patch

      -
      -

      Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.

      -

      v1.ClusterRoleBinding

      @@ -522,75 +516,6 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } -
      -
      -

      v1.DeleteOptions

      -
      -

      DeleteOptions may be provided when deleting an API object.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      gracePeriodSeconds

      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.

      false

      integer (int64)

      preconditions

      Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.

      false

      v1.Preconditions

      orphanDependents

      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.

      false

      boolean

      false

      propagationPolicy

      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.

      false

      v1.DeletionPropagation

      -

      v1.ListMeta

      @@ -639,212 +564,6 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } -
      -
      -

      v1.StatusDetails

      -
      -

      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.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).

      false

      string

      group

      The group attribute of the resource associated with the status StatusReason.

      false

      string

      kind

      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/api-conventions.md#types-kinds

      false

      string

      uid

      UID of the resource. (when there is a single resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids

      false

      string

      causes

      The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.

      false

      v1.StatusCause array

      retryAfterSeconds

      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.

      false

      integer (int32)

      - -
      -
      -

      v1.RoleRef

      -
      -

      RoleRef contains information that points to the role being used

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      apiGroup

      APIGroup is the group for the resource being referenced

      true

      string

      kind

      Kind is the type of resource being referenced

      true

      string

      name

      Name is the name of resource being referenced

      true

      string

      - -
      -
      -

      v1.RoleBindingList

      -
      -

      RoleBindingList is a collection of RoleBindings

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata.

      false

      v1.ListMeta

      items

      Items is a list of RoleBindings

      true

      v1.RoleBinding array

      - -
      -
      -

      v1.Preconditions

      -
      -

      Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      uid

      Specifies the target UID.

      false

      types.UID

      -

      v1.Initializers

      @@ -888,9 +607,9 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
      -

      v1.Initializer

      +

      v1.Preconditions

      -

      Initializer is information about an initializer that has not yet completed.

      +

      Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.

      @@ -911,65 +630,10 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - - - - - - - -

      name

      name of the process that is responsible for initializing this object.

      true

      string

      - -
      -
      -

      v1.ClusterRole

      -
      -

      ClusterRole is a cluster level, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding or ClusterRoleBinding.

      -
      - ------- - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - + @@ -1058,6 +722,624 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      uid

      Specifies the target UID.

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata.

      false

      v1.ObjectMeta

      rules

      Rules holds all the PolicyRules for this ClusterRole

      true

      v1.PolicyRule array

      types.UID

      +
      +
      +

      v1.ClusterRole

      +
      +

      ClusterRole is a cluster level, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding or ClusterRoleBinding.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata.

      false

      v1.ObjectMeta

      rules

      Rules holds all the PolicyRules for this ClusterRole

      true

      v1.PolicyRule array

      aggregationRule

      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 stomped by the controller.

      false

      v1.AggregationRule

      + +
      +
      +

      v1.AggregationRule

      +
      +

      AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole

      +
      + +++++++ + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      clusterRoleSelectors

      ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. If any of the selectors match, then the ClusterRole’s permissions will be added

      false

      v1.LabelSelector array

      + +
      +
      +

      v1.WatchEvent

      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      type

      true

      string

      object

      true

      string

      + +
      +
      +

      v1.LabelSelector

      +
      +

      A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      matchLabels

      matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.

      false

      object

      matchExpressions

      matchExpressions is a list of label selector requirements. The requirements are ANDed.

      false

      v1.LabelSelectorRequirement array

      + +
      +
      +

      v1.ClusterRoleBindingList

      +
      +

      ClusterRoleBindingList is a collection of ClusterRoleBindings

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata.

      false

      v1.ListMeta

      items

      Items is a list of ClusterRoleBindings

      true

      v1.ClusterRoleBinding array

      + +
      +
      +

      v1.LabelSelectorRequirement

      +
      +

      A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      key

      key is the label key that the selector applies to.

      true

      string

      operator

      operator represents a key’s relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.

      true

      string

      values

      values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.

      false

      string array

      + +
      +
      +

      v1.ClusterRoleList

      +
      +

      ClusterRoleList is a collection of ClusterRoles

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata.

      false

      v1.ListMeta

      items

      Items is a list of ClusterRoles

      true

      v1.ClusterRole array

      + +
      +
      +

      v1.DeletionPropagation

      + +
      +
      +

      v1.Patch

      +
      +

      Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.

      +
      +
      +
      +

      v1.DeleteOptions

      +
      +

      DeleteOptions may be provided when deleting an API object.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      gracePeriodSeconds

      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.

      false

      integer (int64)

      preconditions

      Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.

      false

      v1.Preconditions

      orphanDependents

      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.

      false

      boolean

      false

      propagationPolicy

      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.

      false

      v1.DeletionPropagation

      + +
      +
      +

      v1.RoleRef

      +
      +

      RoleRef contains information that points to the role being used

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      apiGroup

      APIGroup is the group for the resource being referenced

      true

      string

      kind

      Kind is the type of resource being referenced

      true

      string

      name

      Name is the name of resource being referenced

      true

      string

      + +
      +
      +

      v1.StatusDetails

      +
      +

      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.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).

      false

      string

      group

      The group attribute of the resource associated with the status StatusReason.

      false

      string

      kind

      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/api-conventions.md#types-kinds

      false

      string

      uid

      UID of the resource. (when there is a single resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids

      false

      string

      causes

      The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.

      false

      v1.StatusCause array

      retryAfterSeconds

      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.

      false

      integer (int32)

      + +
      +
      +

      v1.RoleBindingList

      +
      +

      RoleBindingList is a collection of RoleBindings

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata.

      false

      v1.ListMeta

      items

      Items is a list of RoleBindings

      true

      v1.RoleBinding array

      + +
      +
      +

      v1.Initializer

      +
      +

      Initializer is information about an initializer that has not yet completed.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      name of the process that is responsible for initializing this object.

      true

      string

      +

      v1.Subject

      @@ -1113,44 +1395,6 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } -
      -
      -

      v1.WatchEvent

      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      type

      true

      string

      object

      true

      string

      -

      v1.RoleBinding

      @@ -1213,6 +1457,75 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } +
      +
      +

      v1.OwnerReference

      +
      +

      OwnerReference contains enough information to let you identify an owning object. Currently, an owning object must be in the same namespace, so there is no namespace field.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      apiVersion

      API version of the referent.

      true

      string

      kind

      Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      true

      string

      name

      Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names

      true

      string

      uid

      UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids

      true

      string

      controller

      If true, this reference points to the managing controller.

      false

      boolean

      false

      blockOwnerDeletion

      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. Defaults to false. To set this field, a user needs "delete" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.

      false

      boolean

      false

      +

      v1.ObjectMeta

      @@ -1307,7 +1620,7 @@ Populated by the system. Read-only. Null for lists. More info:

      deletionTimestamp

      -

      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 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.
      +

      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.

      Populated by the system when a graceful deletion is requested. Read-only. More info:
      https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      @@ -1368,130 +1681,6 @@ When an object is created, the system will populate this list with the current s -
      -
      -

      v1.OwnerReference

      -
      -

      OwnerReference contains enough information to let you identify an owning object. Currently, an owning object must be in the same namespace, so there is no namespace field.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      apiVersion

      API version of the referent.

      true

      string

      kind

      Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      true

      string

      name

      Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names

      true

      string

      uid

      UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids

      true

      string

      controller

      If true, this reference points to the managing controller.

      false

      boolean

      false

      blockOwnerDeletion

      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. Defaults to false. To set this field, a user needs "delete" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.

      false

      boolean

      false

      - -
      -
      -

      v1.ClusterRoleBindingList

      -
      -

      ClusterRoleBindingList is a collection of ClusterRoleBindings

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata.

      false

      v1.ListMeta

      items

      Items is a list of ClusterRoleBindings

      true

      v1.ClusterRoleBinding array

      -

      v1.Role

      @@ -1637,61 +1826,6 @@ When an object is created, the system will populate this list with the current s -
      -
      -

      v1.ClusterRoleList

      -
      -

      ClusterRoleList is a collection of ClusterRoles

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata.

      false

      v1.ListMeta

      items

      Items is a list of ClusterRoles

      true

      v1.ClusterRole array

      -

      types.UID

      @@ -1803,10 +1937,6 @@ Examples:
      -
      -
      -

      v1.DeletionPropagation

      -

      v1.PolicyRule

      diff --git a/docs/api-reference/rbac.authorization.k8s.io/v1/operations.html b/docs/api-reference/rbac.authorization.k8s.io/v1/operations.html index 7a48bc163bf..dfac8071cc2 100755 --- a/docs/api-reference/rbac.authorization.k8s.io/v1/operations.html +++ b/docs/api-reference/rbac.authorization.k8s.io/v1/operations.html @@ -1204,7 +1204,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -2170,7 +2170,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -3176,7 +3176,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -4198,7 +4198,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      diff --git a/docs/api-reference/rbac.authorization.k8s.io/v1alpha1/definitions.html b/docs/api-reference/rbac.authorization.k8s.io/v1alpha1/definitions.html index 2699ff1abee..9a95c068557 100755 --- a/docs/api-reference/rbac.authorization.k8s.io/v1alpha1/definitions.html +++ b/docs/api-reference/rbac.authorization.k8s.io/v1alpha1/definitions.html @@ -660,6 +660,40 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } +
      +
      +

      v1alpha1.AggregationRule

      +
      +

      AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole

      +
      + +++++++ + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      clusterRoleSelectors

      ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. If any of the selectors match, then the ClusterRole’s permissions will be added

      false

      v1.LabelSelector array

      +

      v1.WatchEvent

      @@ -753,6 +787,95 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } +
      +
      +

      v1.LabelSelector

      +
      +

      A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      matchLabels

      matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.

      false

      object

      matchExpressions

      matchExpressions is a list of label selector requirements. The requirements are ANDed.

      false

      v1.LabelSelectorRequirement array

      + +
      +
      +

      v1.LabelSelectorRequirement

      +
      +

      A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      key

      key is the label key that the selector applies to.

      true

      string

      operator

      operator represents a key’s relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.

      true

      string

      values

      values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.

      false

      string array

      +

      v1alpha1.RoleBinding

      @@ -1003,7 +1126,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      propagationPolicy

      -

      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.

      +

      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.

      false

      v1.DeletionPropagation

      @@ -1386,6 +1509,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      v1alpha1.PolicyRule array

      + +

      aggregationRule

      +

      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 stomped by the controller.

      +

      false

      +

      v1alpha1.AggregationRule

      + + @@ -1552,7 +1682,7 @@ Populated by the system. Read-only. Null for lists. More info:

      deletionTimestamp

      -

      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 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.
      +

      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.

      Populated by the system when a graceful deletion is requested. Read-only. More info:
      https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      diff --git a/docs/api-reference/rbac.authorization.k8s.io/v1alpha1/operations.html b/docs/api-reference/rbac.authorization.k8s.io/v1alpha1/operations.html index dd8bf2d3420..4230a2459da 100755 --- a/docs/api-reference/rbac.authorization.k8s.io/v1alpha1/operations.html +++ b/docs/api-reference/rbac.authorization.k8s.io/v1alpha1/operations.html @@ -1204,7 +1204,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -2170,7 +2170,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -3176,7 +3176,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -4198,7 +4198,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      diff --git a/docs/api-reference/rbac.authorization.k8s.io/v1beta1/definitions.html b/docs/api-reference/rbac.authorization.k8s.io/v1beta1/definitions.html index cf5169590b2..86664748e15 100755 --- a/docs/api-reference/rbac.authorization.k8s.io/v1beta1/definitions.html +++ b/docs/api-reference/rbac.authorization.k8s.io/v1beta1/definitions.html @@ -454,136 +454,6 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } -
      -
      -

      v1.Patch

      -
      -

      Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.

      -
      -
      -
      -

      v1beta1.ClusterRole

      -
      -

      ClusterRole is a cluster level, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding or ClusterRoleBinding.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata.

      false

      v1.ObjectMeta

      rules

      Rules holds all the PolicyRules for this ClusterRole

      true

      v1beta1.PolicyRule array

      - -
      -
      -

      v1.DeleteOptions

      -
      -

      DeleteOptions may be provided when deleting an API object.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      gracePeriodSeconds

      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.

      false

      integer (int64)

      preconditions

      Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.

      false

      v1.Preconditions

      orphanDependents

      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.

      false

      boolean

      false

      propagationPolicy

      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.

      false

      v1.DeletionPropagation

      -

      v1.ListMeta

      @@ -632,68 +502,6 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } -
      -
      -

      v1beta1.ClusterRoleBinding

      -
      -

      ClusterRoleBinding references a ClusterRole, but not contain it. It can reference a ClusterRole in the global namespace, and adds who information via Subject.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata.

      false

      v1.ObjectMeta

      subjects

      Subjects holds references to the objects the role applies to.

      true

      v1beta1.Subject array

      roleRef

      RoleRef can only reference a ClusterRole in the global namespace. If the RoleRef cannot be resolved, the Authorizer must return an error.

      true

      v1beta1.RoleRef

      -

      v1beta1.ClusterRoleBindingList

      @@ -749,164 +557,6 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } -
      -
      -

      v1.StatusDetails

      -
      -

      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.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).

      false

      string

      group

      The group attribute of the resource associated with the status StatusReason.

      false

      string

      kind

      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/api-conventions.md#types-kinds

      false

      string

      uid

      UID of the resource. (when there is a single resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids

      false

      string

      causes

      The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.

      false

      v1.StatusCause array

      retryAfterSeconds

      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.

      false

      integer (int32)

      - -
      -
      -

      v1beta1.ClusterRoleList

      -
      -

      ClusterRoleList is a collection of ClusterRoles

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata.

      false

      v1.ListMeta

      items

      Items is a list of ClusterRoles

      true

      v1beta1.ClusterRole array

      - -
      -
      -

      v1.Preconditions

      -
      -

      Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      uid

      Specifies the target UID.

      false

      types.UID

      -

      v1.Initializers

      @@ -950,9 +600,9 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
      -

      v1.Initializer

      +

      v1.Preconditions

      -

      Initializer is information about an initializer that has not yet completed.

      +

      Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.

      @@ -973,10 +623,10 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - - - - + + + + @@ -1103,6 +753,655 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      name

      name of the process that is responsible for initializing this object.

      true

      string

      uid

      Specifies the target UID.

      false

      types.UID

      +
      +
      +

      v1.LabelSelector

      +
      +

      A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      matchLabels

      matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.

      false

      object

      matchExpressions

      matchExpressions is a list of label selector requirements. The requirements are ANDed.

      false

      v1.LabelSelectorRequirement array

      + +
      +
      +

      v1.LabelSelectorRequirement

      +
      +

      A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      key

      key is the label key that the selector applies to.

      true

      string

      operator

      operator represents a key’s relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.

      true

      string

      values

      values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.

      false

      string array

      + +
      +
      +

      v1beta1.Role

      +
      +

      Role is a namespaced, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata.

      false

      v1.ObjectMeta

      rules

      Rules holds all the PolicyRules for this Role

      true

      v1beta1.PolicyRule array

      + +
      +
      +

      v1.DeletionPropagation

      + +
      +
      +

      v1beta1.RoleRef

      +
      +

      RoleRef contains information that points to the role being used

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      apiGroup

      APIGroup is the group for the resource being referenced

      true

      string

      kind

      Kind is the type of resource being referenced

      true

      string

      name

      Name is the name of resource being referenced

      true

      string

      + +
      +
      +

      v1beta1.PolicyRule

      +
      +

      PolicyRule holds information that describes a policy rule, but does not contain information about who the rule applies to or which namespace the rule applies to.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      verbs

      Verbs is a list of Verbs that apply to ALL the ResourceKinds and AttributeRestrictions contained in this rule. VerbAll represents all kinds.

      true

      string array

      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.

      false

      string array

      resources

      Resources is a list of resources this rule applies to. represents all resources in the specified apiGroups. /foo represents the subresource foo for all resources in the specified apiGroups.

      false

      string array

      resourceNames

      ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.

      false

      string array

      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 Since non-resource URLs are not namespaced, this field is only applicable for ClusterRoles referenced from a ClusterRoleBinding. Rules can either apply to API resources (such as "pods" or "secrets") or non-resource URL paths (such as "/api"), but not both.

      false

      string array

      + +
      +
      +

      v1.Patch

      +
      +

      Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.

      +
      +
      +
      +

      v1.DeleteOptions

      +
      +

      DeleteOptions may be provided when deleting an API object.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      gracePeriodSeconds

      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.

      false

      integer (int64)

      preconditions

      Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.

      false

      v1.Preconditions

      orphanDependents

      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.

      false

      boolean

      false

      propagationPolicy

      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.

      false

      v1.DeletionPropagation

      + +
      +
      +

      v1beta1.ClusterRole

      +
      +

      ClusterRole is a cluster level, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding or ClusterRoleBinding.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata.

      false

      v1.ObjectMeta

      rules

      Rules holds all the PolicyRules for this ClusterRole

      true

      v1beta1.PolicyRule array

      aggregationRule

      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 stomped by the controller.

      false

      v1beta1.AggregationRule

      + +
      +
      +

      v1beta1.ClusterRoleBinding

      +
      +

      ClusterRoleBinding references a ClusterRole, but not contain it. It can reference a ClusterRole in the global namespace, and adds who information via Subject.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata.

      false

      v1.ObjectMeta

      subjects

      Subjects holds references to the objects the role applies to.

      true

      v1beta1.Subject array

      roleRef

      RoleRef can only reference a ClusterRole in the global namespace. If the RoleRef cannot be resolved, the Authorizer must return an error.

      true

      v1beta1.RoleRef

      + +
      +
      +

      v1.StatusDetails

      +
      +

      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.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).

      false

      string

      group

      The group attribute of the resource associated with the status StatusReason.

      false

      string

      kind

      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/api-conventions.md#types-kinds

      false

      string

      uid

      UID of the resource. (when there is a single resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids

      false

      string

      causes

      The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.

      false

      v1.StatusCause array

      retryAfterSeconds

      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.

      false

      integer (int32)

      + +
      +
      +

      v1.Initializer

      +
      +

      Initializer is information about an initializer that has not yet completed.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      name of the process that is responsible for initializing this object.

      true

      string

      + +
      +
      +

      v1beta1.ClusterRoleList

      +
      +

      ClusterRoleList is a collection of ClusterRoles

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata.

      false

      v1.ListMeta

      items

      Items is a list of ClusterRoles

      true

      v1beta1.ClusterRole array

      + +
      +
      +

      v1beta1.AggregationRule

      +
      +

      AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole

      +
      + +++++++ + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      clusterRoleSelectors

      ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. If any of the selectors match, then the ClusterRole’s permissions will be added

      false

      v1.LabelSelector array

      +

      v1beta1.RoleList

      @@ -1158,6 +1457,75 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } +
      +
      +

      v1.OwnerReference

      +
      +

      OwnerReference contains enough information to let you identify an owning object. Currently, an owning object must be in the same namespace, so there is no namespace field.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      apiVersion

      API version of the referent.

      true

      string

      kind

      Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      true

      string

      name

      Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names

      true

      string

      uid

      UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids

      true

      string

      controller

      If true, this reference points to the managing controller.

      false

      boolean

      false

      blockOwnerDeletion

      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. Defaults to false. To set this field, a user needs "delete" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.

      false

      boolean

      false

      +

      v1.ObjectMeta

      @@ -1252,7 +1620,7 @@ Populated by the system. Read-only. Null for lists. More info:

      deletionTimestamp

      -

      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 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.
      +

      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.

      Populated by the system when a graceful deletion is requested. Read-only. More info:
      https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      @@ -1313,75 +1681,6 @@ When an object is created, the system will populate this list with the current s -
      -
      -

      v1.OwnerReference

      -
      -

      OwnerReference contains enough information to let you identify an owning object. Currently, an owning object must be in the same namespace, so there is no namespace field.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      apiVersion

      API version of the referent.

      true

      string

      kind

      Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      true

      string

      name

      Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names

      true

      string

      uid

      UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids

      true

      string

      controller

      If true, this reference points to the managing controller.

      false

      boolean

      false

      blockOwnerDeletion

      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. Defaults to false. To set this field, a user needs "delete" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.

      false

      boolean

      false

      -

      v1beta1.Subject

      @@ -1531,61 +1830,6 @@ When an object is created, the system will populate this list with the current s

      types.UID

      -
      -
      -

      v1beta1.Role

      -
      -

      Role is a namespaced, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata.

      false

      v1.ObjectMeta

      rules

      Rules holds all the PolicyRules for this Role

      true

      v1beta1.PolicyRule array

      -

      v1.StatusCause

      @@ -1638,120 +1882,6 @@ Examples:
      -
      -
      -

      v1.DeletionPropagation

      - -
      -
      -

      v1beta1.PolicyRule

      -
      -

      PolicyRule holds information that describes a policy rule, but does not contain information about who the rule applies to or which namespace the rule applies to.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      verbs

      Verbs is a list of Verbs that apply to ALL the ResourceKinds and AttributeRestrictions contained in this rule. VerbAll represents all kinds.

      true

      string array

      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.

      false

      string array

      resources

      Resources is a list of resources this rule applies to. ResourceAll represents all resources.

      false

      string array

      resourceNames

      ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.

      false

      string array

      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 Since non-resource URLs are not namespaced, this field is only applicable for ClusterRoles referenced from a ClusterRoleBinding. Rules can either apply to API resources (such as "pods" or "secrets") or non-resource URL paths (such as "/api"), but not both.

      false

      string array

      - -
      -
      -

      v1beta1.RoleRef

      -
      -

      RoleRef contains information that points to the role being used

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      apiGroup

      APIGroup is the group for the resource being referenced

      true

      string

      kind

      Kind is the type of resource being referenced

      true

      string

      name

      Name is the name of resource being referenced

      true

      string

      -

      v1beta1.RoleBinding

      diff --git a/docs/api-reference/rbac.authorization.k8s.io/v1beta1/operations.html b/docs/api-reference/rbac.authorization.k8s.io/v1beta1/operations.html index faea7e954c2..fa8d93f3331 100755 --- a/docs/api-reference/rbac.authorization.k8s.io/v1beta1/operations.html +++ b/docs/api-reference/rbac.authorization.k8s.io/v1beta1/operations.html @@ -1204,7 +1204,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -2170,7 +2170,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -3176,7 +3176,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -4198,7 +4198,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      diff --git a/docs/api-reference/scheduling.k8s.io/v1alpha1/definitions.html b/docs/api-reference/scheduling.k8s.io/v1alpha1/definitions.html index 493ee8874a3..ae3e0927564 100755 --- a/docs/api-reference/scheduling.k8s.io/v1alpha1/definitions.html +++ b/docs/api-reference/scheduling.k8s.io/v1alpha1/definitions.html @@ -503,7 +503,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      propagationPolicy

      -

      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.

      +

      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.

      false

      v1.DeletionPropagation

      @@ -952,7 +952,7 @@ Populated by the system. Read-only. Null for lists. More info:

      deletionTimestamp

      -

      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 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.
      +

      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.

      Populated by the system when a graceful deletion is requested. Read-only. More info:
      https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      diff --git a/docs/api-reference/scheduling.k8s.io/v1alpha1/operations.html b/docs/api-reference/scheduling.k8s.io/v1alpha1/operations.html index 49f5bf3e65d..e7e4c3e0c4a 100755 --- a/docs/api-reference/scheduling.k8s.io/v1alpha1/operations.html +++ b/docs/api-reference/scheduling.k8s.io/v1alpha1/operations.html @@ -1220,7 +1220,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      diff --git a/docs/api-reference/settings.k8s.io/v1alpha1/definitions.html b/docs/api-reference/settings.k8s.io/v1alpha1/definitions.html index bd68f9c62ea..aa446f591ad 100755 --- a/docs/api-reference/settings.k8s.io/v1alpha1/definitions.html +++ b/docs/api-reference/settings.k8s.io/v1alpha1/definitions.html @@ -541,21 +541,21 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      protectionDomain

      -

      The name of the Protection Domain for the configured storage (defaults to "default").

      +

      The name of the ScaleIO Protection Domain for the configured storage.

      false

      string

      storagePool

      -

      The Storage Pool associated with the protection domain (defaults to "default").

      +

      The ScaleIO Storage Pool associated with the protection domain.

      false

      string

      storageMode

      -

      Indicates whether the storage for a volume should be thick or thin (defaults to "thin").

      +

      Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned.

      false

      string

      @@ -1776,7 +1776,7 @@ Populated by the system. Read-only. Null for lists. More info:

      deletionTimestamp

      -

      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 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.
      +

      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.

      Populated by the system when a graceful deletion is requested. Read-only. More info:
      https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      @@ -2115,7 +2115,7 @@ When an object is created, the system will populate this list with the current s

      targetPortal

      -

      iSCSI target portal. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      +

      iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      true

      string

      @@ -2129,14 +2129,14 @@ When an object is created, the system will populate this list with the current s

      lun

      -

      iSCSI target lun number.

      +

      iSCSI Target Lun number.

      true

      integer (int32)

      iscsiInterface

      -

      Optional: Defaults to default (tcp). iSCSI interface name that uses an iSCSI transport.

      +

      iSCSI Interface Name that uses an iSCSI transport. Defaults to default (tcp).

      false

      string

      @@ -2157,7 +2157,7 @@ When an object is created, the system will populate this list with the current s

      portals

      -

      iSCSI target portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      +

      iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      false

      string array

      @@ -2178,14 +2178,14 @@ When an object is created, the system will populate this list with the current s

      secretRef

      -

      CHAP secret for iSCSI target and initiator authentication

      +

      CHAP Secret for iSCSI target and initiator authentication

      false

      v1.LocalObjectReference

      initiatorName

      -

      Custom iSCSI initiator name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface <target portal>:<volume name> will be created for the connection.

      +

      Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface <target portal>:<volume name> will be created for the connection.

      false

      string

      @@ -2999,7 +2999,7 @@ When an object is created, the system will populate this list with the current s

      v1.FlexVolumeSource

      -

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.

      +

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.

      @@ -3296,7 +3296,7 @@ When an object is created, the system will populate this list with the current s - + @@ -3414,7 +3414,7 @@ When an object is created, the system will populate this list with the current s - + diff --git a/docs/api-reference/settings.k8s.io/v1alpha1/operations.html b/docs/api-reference/settings.k8s.io/v1alpha1/operations.html index e5218d4442d..48648792aa9 100755 --- a/docs/api-reference/settings.k8s.io/v1alpha1/operations.html +++ b/docs/api-reference/settings.k8s.io/v1alpha1/operations.html @@ -1260,7 +1260,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - + diff --git a/docs/api-reference/storage.k8s.io/v1/definitions.html b/docs/api-reference/storage.k8s.io/v1/definitions.html index 3263c0ca075..a8b980f1f74 100755 --- a/docs/api-reference/storage.k8s.io/v1/definitions.html +++ b/docs/api-reference/storage.k8s.io/v1/definitions.html @@ -503,7 +503,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - + @@ -996,6 +996,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } + + + + + + +

      propagationPolicy

      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.

      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.

      false

      v1.DeletionPropagation

      flexVolume

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.

      false

      v1.FlexVolumeSource

      QueryParameter

      propagationPolicy

      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.

      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.

      false

      string

      propagationPolicy

      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.

      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.

      false

      v1.DeletionPropagation

      boolean

      false

      volumeBindingMode

      VolumeBindingMode indicates how PersistentVolumeClaims should be provisioned and bound. When unset, VolumeBindingImmediate is used. This field is alpha-level and is only honored by servers that enable the VolumeScheduling feature.

      false

      v1.VolumeBindingMode

      @@ -1093,7 +1100,7 @@ Populated by the system. Read-only. Null for lists. More info:

      deletionTimestamp

      -

      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 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.
      +

      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.

      Populated by the system when a graceful deletion is requested. Read-only. More info:
      https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      @@ -1377,6 +1384,10 @@ Examples:

      v1.DeletionPropagation

      +
      +
      +

      v1.VolumeBindingMode

      +

      any

      diff --git a/docs/api-reference/storage.k8s.io/v1/operations.html b/docs/api-reference/storage.k8s.io/v1/operations.html index 46493b13779..af1432763db 100755 --- a/docs/api-reference/storage.k8s.io/v1/operations.html +++ b/docs/api-reference/storage.k8s.io/v1/operations.html @@ -1220,7 +1220,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      diff --git a/docs/api-reference/storage.k8s.io/v1alpha1/definitions.html b/docs/api-reference/storage.k8s.io/v1alpha1/definitions.html new file mode 100755 index 00000000000..577f85ad45b --- /dev/null +++ b/docs/api-reference/storage.k8s.io/v1alpha1/definitions.html @@ -0,0 +1,1549 @@ + + + + + + +Top Level API Objects + + + + +
      +
      +

      Top Level API Objects

      + +
      +
      +

      Definitions

      +
      +
      +

      v1.APIResourceList

      +
      +

      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.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      groupVersion

      groupVersion is the group and version this APIResourceList is for.

      true

      string

      resources

      resources contains the name of the resources and if they are namespaced.

      true

      v1.APIResource array

      + +
      +
      +

      v1.Patch

      +
      +

      Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.

      +
      +
      +
      +

      v1alpha1.VolumeAttachmentList

      +
      +

      VolumeAttachmentList is a collection of VolumeAttachment objects.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ListMeta

      items

      Items is the list of VolumeAttachments

      true

      v1alpha1.VolumeAttachment array

      + +
      +
      +

      v1.DeleteOptions

      +
      +

      DeleteOptions may be provided when deleting an API object.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      gracePeriodSeconds

      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.

      false

      integer (int64)

      preconditions

      Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.

      false

      v1.Preconditions

      orphanDependents

      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.

      false

      boolean

      false

      propagationPolicy

      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.

      false

      v1.DeletionPropagation

      + +
      +
      +

      v1.ListMeta

      +
      +

      ListMeta describes metadata that synthetic resources must have, including lists and various status objects. A resource may have only one of {ObjectMeta, ListMeta}.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      selfLink

      selfLink is a URL representing this object. Populated by the system. Read-only.

      false

      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/api-conventions.md#concurrency-control-and-consistency

      false

      string

      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 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.

      false

      string

      + +
      +
      +

      v1.StatusDetails

      +
      +

      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.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).

      false

      string

      group

      The group attribute of the resource associated with the status StatusReason.

      false

      string

      kind

      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/api-conventions.md#types-kinds

      false

      string

      uid

      UID of the resource. (when there is a single resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids

      false

      string

      causes

      The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.

      false

      v1.StatusCause array

      retryAfterSeconds

      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.

      false

      integer (int32)

      + +
      +
      +

      v1alpha1.VolumeAttachmentSource

      +
      +

      VolumeAttachmentSource represents a volume that should be attached. Right now only PersistenVolumes can be attached via external attacher, in future we may allow also inline volumes in pods. Exactly one member can be set.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      persistentVolumeName

      Name of the persistent volume to attach.

      false

      string

      + +
      +
      +

      v1.Preconditions

      +
      +

      Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      uid

      Specifies the target UID.

      false

      types.UID

      + +
      +
      +

      v1.Initializers

      +
      +

      Initializers tracks the progress of initialization.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      pending

      Pending is a list of initializers that must execute in order before this object is visible. When the last pending initializer is removed, and no failing result is set, the initializers struct will be set to nil and the object is considered as initialized and visible to all clients.

      true

      v1.Initializer array

      result

      If result is set with the Failure field, the object will be persisted to storage and then deleted, ensuring that other clients can observe the deletion.

      false

      v1.Status

      + +
      +
      +

      v1.Initializer

      +
      +

      Initializer is information about an initializer that has not yet completed.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      name of the process that is responsible for initializing this object.

      true

      string

      + +
      +
      +

      v1.Status

      +
      +

      Status is a return value for calls that don’t return other objects.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      status

      Status of the operation. One of: "Success" or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      string

      message

      A human-readable description of the status of this operation.

      false

      string

      reason

      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.

      false

      string

      details

      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.

      false

      v1.StatusDetails

      code

      Suggested HTTP return code for this status, 0 if not set.

      false

      integer (int32)

      + +
      +
      +

      v1.WatchEvent

      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      type

      true

      string

      object

      true

      string

      + +
      +
      +

      v1.ObjectMeta

      +
      +

      ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      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: http://kubernetes.io/docs/user-guide/identifiers#names

      false

      string

      generateName

      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.
      +
      +If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header).
      +
      +Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency

      false

      string

      namespace

      Namespace defines the space within 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.
      +
      +Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces

      false

      string

      selfLink

      SelfLink is a URL representing this object. Populated by the system. Read-only.

      false

      string

      uid

      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.
      +
      +Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids

      false

      string

      resourceVersion

      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.
      +
      +Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency

      false

      string

      generation

      A sequence number representing a specific generation of the desired state. Populated by the system. Read-only.

      false

      integer (int64)

      creationTimestamp

      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.
      +
      +Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      string

      deletionTimestamp

      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.
      +
      +Populated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      string

      deletionGracePeriodSeconds

      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.

      false

      integer (int64)

      labels

      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: http://kubernetes.io/docs/user-guide/labels

      false

      object

      annotations

      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: http://kubernetes.io/docs/user-guide/annotations

      false

      object

      ownerReferences

      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.

      false

      v1.OwnerReference array

      initializers

      An initializer is a controller which enforces some system invariant at object creation time. This field is a list of initializers that have not yet acted on this object. If nil or empty, this object has been completely initialized. Otherwise, the object is considered uninitialized and is hidden (in list/watch and get calls) from clients that haven’t explicitly asked to observe uninitialized objects.
      +
      +When an object is created, the system will populate this list with the current set of initializers. Only privileged users may set or modify this list. Once it is empty, it may not be modified further by any user.

      false

      v1.Initializers

      finalizers

      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.

      false

      string array

      clusterName

      The name of the cluster which the object belongs to. This is used to distinguish resources with same name and namespace in different clusters. This field is not set anywhere right now and apiserver is going to ignore it if set in create or update request.

      false

      string

      + +
      +
      +

      v1.OwnerReference

      +
      +

      OwnerReference contains enough information to let you identify an owning object. Currently, an owning object must be in the same namespace, so there is no namespace field.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      apiVersion

      API version of the referent.

      true

      string

      kind

      Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      true

      string

      name

      Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names

      true

      string

      uid

      UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids

      true

      string

      controller

      If true, this reference points to the managing controller.

      false

      boolean

      false

      blockOwnerDeletion

      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. Defaults to false. To set this field, a user needs "delete" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.

      false

      boolean

      false

      + +
      +
      +

      v1alpha1.VolumeError

      +
      +

      VolumeError captures an error encountered during a volume operation.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      time

      Time the error was encountered.

      false

      string

      message

      String detailing the error encountered during Attach or Detach operation. This string maybe logged, so it should not contain sensitive information.

      false

      string

      + +
      +
      +

      v1alpha1.VolumeAttachment

      +
      +

      VolumeAttachment captures the intent to attach or detach the specified volume to/from the specified node.

      +
      +
      +

      VolumeAttachment objects are non-namespaced.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ObjectMeta

      spec

      Specification of the desired attach/detach volume behavior. Populated by the Kubernetes system.

      true

      v1alpha1.VolumeAttachmentSpec

      status

      Status of the VolumeAttachment request. Populated by the entity completing the attach or detach operation, i.e. the external-attacher.

      false

      v1alpha1.VolumeAttachmentStatus

      + +
      +
      +

      v1.APIResource

      +
      +

      APIResource specifies the name of a resource and whether it is namespaced.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      name is the plural name of the resource.

      true

      string

      singularName

      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.

      true

      string

      namespaced

      namespaced indicates if a resource is namespaced or not.

      true

      boolean

      false

      group

      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".

      false

      string

      version

      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)".

      false

      string

      kind

      kind is the kind for the resource (e.g. Foo is the kind for a resource foo)

      true

      string

      verbs

      verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy)

      true

      string array

      shortNames

      shortNames is a list of suggested short names of the resource.

      false

      string array

      categories

      categories is a list of the grouped resources this resource belongs to (e.g. all)

      false

      string array

      + +
      +
      +

      types.UID

      + +
      +
      +

      v1alpha1.VolumeAttachmentSpec

      +
      +

      VolumeAttachmentSpec is the specification of a VolumeAttachment request.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      attacher

      Attacher indicates the name of the volume driver that MUST handle this request. This is the name returned by GetPluginName().

      true

      string

      source

      Source represents the volume that should be attached.

      true

      v1alpha1.VolumeAttachmentSource

      nodeName

      The node that the volume should be attached to.

      true

      string

      + +
      +
      +

      v1.StatusCause

      +
      +

      StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      reason

      A machine-readable description of the cause of the error. If this value is empty there is no information available.

      false

      string

      message

      A human-readable description of the cause of the error. This field may be presented as-is to a reader.

      false

      string

      field

      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.
      +
      +Examples:
      + "name" - the field "name" on the current resource
      + "items[0].name" - the field "name" on the first array entry in "items"

      false

      string

      + +
      +
      +

      v1.DeletionPropagation

      + +
      +
      +

      v1alpha1.VolumeAttachmentStatus

      +
      +

      VolumeAttachmentStatus is the status of a VolumeAttachment request.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      attached

      Indicates the volume is successfully attached. This field must only be set by the entity completing the attach operation, i.e. the external-attacher.

      true

      boolean

      false

      attachmentMetadata

      Upon successful attach, this field is populated with any information returned by the attach operation that must be passed into subsequent WaitForAttach or Mount calls. This field must only be set by the entity completing the attach operation, i.e. the external-attacher.

      false

      object

      attachError

      The last error encountered during attach operation, if any. This field must only be set by the entity completing the attach operation, i.e. the external-attacher.

      false

      v1alpha1.VolumeError

      detachError

      The last error encountered during detach operation, if any. This field must only be set by the entity completing the detach operation, i.e. the external-attacher.

      false

      v1alpha1.VolumeError

      + +
      +
      +

      any

      +
      +

      Represents an untyped JSON map - see the description of the field for more info about the structure of this object.

      +
      +
      +
      +
      +
      + + + \ No newline at end of file diff --git a/docs/api-reference/storage.k8s.io/v1alpha1/operations.html b/docs/api-reference/storage.k8s.io/v1alpha1/operations.html new file mode 100755 index 00000000000..bb6dbc3c43f --- /dev/null +++ b/docs/api-reference/storage.k8s.io/v1alpha1/operations.html @@ -0,0 +1,1792 @@ + + + + + + +Operations + + + + +
      +
      +

      Operations

      +
      +
      +

      get available resources

      +
      +
      +
      GET /apis/storage.k8s.io/v1alpha1
      +
      +
      +
      +

      Responses

      + +++++ + + + + + + + + + + + + + + +
      HTTP CodeDescriptionSchema

      default

      success

      v1.APIResourceList

      + +
      +
      +

      Consumes

      +
      +
        +
      • +

        application/json

        +
      • +
      • +

        application/yaml

        +
      • +
      • +

        application/vnd.kubernetes.protobuf

        +
      • +
      +
      +
      +
      +

      Produces

      +
      +
        +
      • +

        application/json

        +
      • +
      • +

        application/yaml

        +
      • +
      • +

        application/vnd.kubernetes.protobuf

        +
      • +
      +
      +
      +
      +

      Tags

      +
      +
        +
      • +

        apisstorage.k8s.iov1alpha1

        +
      • +
      +
      +
      +
      +
      +

      list or watch objects of kind VolumeAttachment

      +
      +
      +
      GET /apis/storage.k8s.io/v1alpha1/volumeattachments
      +
      +
      +
      +

      Parameters

      + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. +

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      + +
      +
      +

      Responses

      + +++++ + + + + + + + + + + + + + + +
      HTTP CodeDescriptionSchema

      200

      success

      v1alpha1.VolumeAttachmentList

      + +
      +
      +

      Consumes

      +
      +
        +
      • +

        /

        +
      • +
      +
      +
      +
      +

      Produces

      +
      +
        +
      • +

        application/json

        +
      • +
      • +

        application/yaml

        +
      • +
      • +

        application/vnd.kubernetes.protobuf

        +
      • +
      • +

        application/json;stream=watch

        +
      • +
      • +

        application/vnd.kubernetes.protobuf;stream=watch

        +
      • +
      +
      +
      +
      +

      Tags

      +
      +
        +
      • +

        apisstorage.k8s.iov1alpha1

        +
      • +
      +
      +
      +
      +
      +

      delete collection of VolumeAttachment

      +
      +
      +
      DELETE /apis/storage.k8s.io/v1alpha1/volumeattachments
      +
      +
      +
      +

      Parameters

      + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. +

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      + +
      +
      +

      Responses

      + +++++ + + + + + + + + + + + + + + +
      HTTP CodeDescriptionSchema

      200

      success

      v1.Status

      + +
      +
      +

      Consumes

      +
      +
        +
      • +

        /

        +
      • +
      +
      +
      +
      +

      Produces

      +
      +
        +
      • +

        application/json

        +
      • +
      • +

        application/yaml

        +
      • +
      • +

        application/vnd.kubernetes.protobuf

        +
      • +
      +
      +
      +
      +

      Tags

      +
      +
        +
      • +

        apisstorage.k8s.iov1alpha1

        +
      • +
      +
      +
      +
      +
      +

      create a VolumeAttachment

      +
      +
      +
      POST /apis/storage.k8s.io/v1alpha1/volumeattachments
      +
      +
      +
      +

      Parameters

      + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1alpha1.VolumeAttachment

      + +
      +
      +

      Responses

      + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
      HTTP CodeDescriptionSchema

      202

      Accepted

      v1alpha1.VolumeAttachment

      200

      success

      v1alpha1.VolumeAttachment

      201

      Created

      v1alpha1.VolumeAttachment

      + +
      +
      +

      Consumes

      +
      +
        +
      • +

        /

        +
      • +
      +
      +
      +
      +

      Produces

      +
      +
        +
      • +

        application/json

        +
      • +
      • +

        application/yaml

        +
      • +
      • +

        application/vnd.kubernetes.protobuf

        +
      • +
      +
      +
      +
      +

      Tags

      +
      +
        +
      • +

        apisstorage.k8s.iov1alpha1

        +
      • +
      +
      +
      +
      +
      +

      read the specified VolumeAttachment

      +
      +
      +
      GET /apis/storage.k8s.io/v1alpha1/volumeattachments/{name}
      +
      +
      +
      +

      Parameters

      + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      export

      Should this value be exported. Export strips fields that a user can not specify.

      false

      boolean

      QueryParameter

      exact

      Should the export be exact. Exact export maintains cluster-specific fields like Namespace.

      false

      boolean

      PathParameter

      name

      name of the VolumeAttachment

      true

      string

      + +
      +
      +

      Responses

      + +++++ + + + + + + + + + + + + + + +
      HTTP CodeDescriptionSchema

      200

      success

      v1alpha1.VolumeAttachment

      + +
      +
      +

      Consumes

      +
      +
        +
      • +

        /

        +
      • +
      +
      +
      +
      +

      Produces

      +
      +
        +
      • +

        application/json

        +
      • +
      • +

        application/yaml

        +
      • +
      • +

        application/vnd.kubernetes.protobuf

        +
      • +
      +
      +
      +
      +

      Tags

      +
      +
        +
      • +

        apisstorage.k8s.iov1alpha1

        +
      • +
      +
      +
      +
      +
      +

      replace the specified VolumeAttachment

      +
      +
      +
      PUT /apis/storage.k8s.io/v1alpha1/volumeattachments/{name}
      +
      +
      +
      +

      Parameters

      + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1alpha1.VolumeAttachment

      PathParameter

      name

      name of the VolumeAttachment

      true

      string

      + +
      +
      +

      Responses

      + +++++ + + + + + + + + + + + + + + + + + + + +
      HTTP CodeDescriptionSchema

      200

      success

      v1alpha1.VolumeAttachment

      201

      Created

      v1alpha1.VolumeAttachment

      + +
      +
      +

      Consumes

      +
      +
        +
      • +

        /

        +
      • +
      +
      +
      +
      +

      Produces

      +
      +
        +
      • +

        application/json

        +
      • +
      • +

        application/yaml

        +
      • +
      • +

        application/vnd.kubernetes.protobuf

        +
      • +
      +
      +
      +
      +

      Tags

      +
      +
        +
      • +

        apisstorage.k8s.iov1alpha1

        +
      • +
      +
      +
      +
      +
      +

      delete a VolumeAttachment

      +
      +
      +
      DELETE /apis/storage.k8s.io/v1alpha1/volumeattachments/{name}
      +
      +
      +
      +

      Parameters

      + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.DeleteOptions

      QueryParameter

      gracePeriodSeconds

      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.

      false

      integer (int32)

      QueryParameter

      orphanDependents

      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.

      false

      boolean

      QueryParameter

      propagationPolicy

      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.

      false

      string

      PathParameter

      name

      name of the VolumeAttachment

      true

      string

      + +
      +
      +

      Responses

      + +++++ + + + + + + + + + + + + + + +
      HTTP CodeDescriptionSchema

      200

      success

      v1.Status

      + +
      +
      +

      Consumes

      +
      +
        +
      • +

        /

        +
      • +
      +
      +
      +
      +

      Produces

      +
      +
        +
      • +

        application/json

        +
      • +
      • +

        application/yaml

        +
      • +
      • +

        application/vnd.kubernetes.protobuf

        +
      • +
      +
      +
      +
      +

      Tags

      +
      +
        +
      • +

        apisstorage.k8s.iov1alpha1

        +
      • +
      +
      +
      +
      +
      +

      partially update the specified VolumeAttachment

      +
      +
      +
      PATCH /apis/storage.k8s.io/v1alpha1/volumeattachments/{name}
      +
      +
      +
      +

      Parameters

      + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Patch

      PathParameter

      name

      name of the VolumeAttachment

      true

      string

      + +
      +
      +

      Responses

      + +++++ + + + + + + + + + + + + + + +
      HTTP CodeDescriptionSchema

      200

      success

      v1alpha1.VolumeAttachment

      + +
      +
      +

      Consumes

      +
      +
        +
      • +

        application/json-patch+json

        +
      • +
      • +

        application/merge-patch+json

        +
      • +
      • +

        application/strategic-merge-patch+json

        +
      • +
      +
      +
      +
      +

      Produces

      +
      +
        +
      • +

        application/json

        +
      • +
      • +

        application/yaml

        +
      • +
      • +

        application/vnd.kubernetes.protobuf

        +
      • +
      +
      +
      +
      +

      Tags

      +
      +
        +
      • +

        apisstorage.k8s.iov1alpha1

        +
      • +
      +
      +
      +
      +
      +

      watch individual changes to a list of VolumeAttachment

      +
      +
      +
      GET /apis/storage.k8s.io/v1alpha1/watch/volumeattachments
      +
      +
      +
      +

      Parameters

      + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. +

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      + +
      +
      +

      Responses

      + +++++ + + + + + + + + + + + + + + +
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      + +
      +
      +

      Consumes

      +
      +
        +
      • +

        /

        +
      • +
      +
      +
      +
      +

      Produces

      +
      +
        +
      • +

        application/json

        +
      • +
      • +

        application/yaml

        +
      • +
      • +

        application/vnd.kubernetes.protobuf

        +
      • +
      • +

        application/json;stream=watch

        +
      • +
      • +

        application/vnd.kubernetes.protobuf;stream=watch

        +
      • +
      +
      +
      +
      +

      Tags

      +
      +
        +
      • +

        apisstorage.k8s.iov1alpha1

        +
      • +
      +
      +
      +
      +
      +

      watch changes to an object of kind VolumeAttachment

      +
      +
      +
      GET /apis/storage.k8s.io/v1alpha1/watch/volumeattachments/{name}
      +
      +
      +
      +

      Parameters

      + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. +

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      name

      name of the VolumeAttachment

      true

      string

      + +
      +
      +

      Responses

      + +++++ + + + + + + + + + + + + + + +
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      + +
      +
      +

      Consumes

      +
      +
        +
      • +

        /

        +
      • +
      +
      +
      +
      +

      Produces

      +
      +
        +
      • +

        application/json

        +
      • +
      • +

        application/yaml

        +
      • +
      • +

        application/vnd.kubernetes.protobuf

        +
      • +
      • +

        application/json;stream=watch

        +
      • +
      • +

        application/vnd.kubernetes.protobuf;stream=watch

        +
      • +
      +
      +
      +
      +

      Tags

      +
      +
        +
      • +

        apisstorage.k8s.iov1alpha1

        +
      • +
      +
      +
      +
      +
      +
      +
      + + + \ No newline at end of file diff --git a/docs/api-reference/storage.k8s.io/v1beta1/definitions.html b/docs/api-reference/storage.k8s.io/v1beta1/definitions.html index 0f64250074b..57732f5aa64 100755 --- a/docs/api-reference/storage.k8s.io/v1beta1/definitions.html +++ b/docs/api-reference/storage.k8s.io/v1beta1/definitions.html @@ -558,7 +558,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      propagationPolicy

      -

      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.

      +

      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.

      false

      v1.DeletionPropagation

      @@ -958,6 +958,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      boolean

      false

      + +

      volumeBindingMode

      +

      VolumeBindingMode indicates how PersistentVolumeClaims should be provisioned and bound. When unset, VolumeBindingImmediate is used. This field is alpha-level and is only honored by servers that enable the VolumeScheduling feature.

      +

      false

      +

      v1beta1.VolumeBindingMode

      + + @@ -1093,7 +1100,7 @@ Populated by the system. Read-only. Null for lists. More info:

      deletionTimestamp

      -

      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 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.
      +

      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.

      Populated by the system when a graceful deletion is requested. Read-only. More info:
      https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      @@ -1227,6 +1234,10 @@ When an object is created, the system will populate this list with the current s

      v1.PersistentVolumeReclaimPolicy

      +
      +
      +

      v1beta1.VolumeBindingMode

      +

      v1.APIResource

      diff --git a/docs/api-reference/storage.k8s.io/v1beta1/operations.html b/docs/api-reference/storage.k8s.io/v1beta1/operations.html index 12655fc1917..72217def719 100755 --- a/docs/api-reference/storage.k8s.io/v1beta1/operations.html +++ b/docs/api-reference/storage.k8s.io/v1beta1/operations.html @@ -1220,7 +1220,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      diff --git a/docs/api-reference/v1/definitions.html b/docs/api-reference/v1/definitions.html index 4f97a718d71..cbb2f013aad 100755 --- a/docs/api-reference/v1/definitions.html +++ b/docs/api-reference/v1/definitions.html @@ -479,294 +479,6 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      Definitions

      -

      v1.APIResourceList

      -
      -

      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.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      groupVersion

      groupVersion is the group and version this APIResourceList is for.

      true

      string

      resources

      resources contains the name of the resources and if they are namespaced.

      true

      v1.APIResource array

      - -
      -
      -

      v1.Affinity

      -
      -

      Affinity is a group of affinity scheduling rules.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      nodeAffinity

      Describes node affinity scheduling rules for the pod.

      false

      v1.NodeAffinity

      podAffinity

      Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)).

      false

      v1.PodAffinity

      podAntiAffinity

      Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)).

      false

      v1.PodAntiAffinity

      - -
      -
      -

      v1.Node

      -
      -

      Node is a worker node in Kubernetes. Each node will have a unique identifier in the cache (i.e. in etcd).

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ObjectMeta

      spec

      Spec defines the behavior of a node. https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      v1.NodeSpec

      status

      Most recently observed status of the node. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      v1.NodeStatus

      - -
      -
      -

      v1.PersistentVolumeClaimList

      -
      -

      PersistentVolumeClaimList is a list of PersistentVolumeClaim items.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      items

      A list of persistent volume claims. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims

      true

      v1.PersistentVolumeClaim array

      - -
      -
      -

      v1.NodeSelectorTerm

      -
      -

      A null or empty node selector term matches no objects.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      matchExpressions

      Required. A list of node selector requirements. The requirements are ANDed.

      true

      v1.NodeSelectorRequirement array

      - -
      -
      -

      v1.LocalVolumeSource

      -
      -

      Local represents directly-attached storage with node affinity

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      path

      The full path to the volume on the node For alpha, this path must be a directory Once block as a source is supported, then this path can point to a block device

      true

      string

      - -
      -

      v1.Preconditions

      Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.

      @@ -799,61 +511,6 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } -
      -
      -

      v1.SELinuxOptions

      -
      -

      SELinuxOptions are the labels to be applied to the container

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      user

      User is a SELinux user label that applies to the container.

      false

      string

      role

      Role is a SELinux role label that applies to the container.

      false

      string

      type

      Type is a SELinux type label that applies to the container.

      false

      string

      level

      Level is SELinux level label that applies to the container.

      false

      string

      -

      v1.ObjectFieldSelector

      @@ -933,102 +590,6 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      v1.MountPropagationMode

      -
      -
      -

      v1.VolumeMount

      -
      -

      VolumeMount describes a mounting of a Volume within a container.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      This must match the Name of a Volume.

      true

      string

      readOnly

      Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false.

      false

      boolean

      false

      mountPath

      Path within the container at which the volume should be mounted. Must not contain :.

      true

      string

      subPath

      Path within the volume from which the container’s volume should be mounted. Defaults to "" (volume’s root).

      false

      string

      mountPropagation

      mountPropagation determines how mounts are propagated from the host to container and the other way around. When not set, MountPropagationHostToContainer is used. This field is alpha in 1.8 and can be reworked or removed in a future release.

      false

      v1.MountPropagationMode

      - -
      -
      -

      v1.DownwardAPIProjection

      -
      -

      Represents downward API info for projecting into a projected volume. Note that this is identical to a downwardAPI volume source without the default mode.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      items

      Items is a list of DownwardAPIVolume file

      false

      v1.DownwardAPIVolumeFile array

      -

      v1.LabelSelector

      @@ -1129,75 +690,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      string

      - - - -
      -
      -

      v1.CephFSVolumeSource

      -
      -

      Represents a Ceph Filesystem mount that lasts the lifetime of a pod Cephfs volumes do not support ownership management or SELinux relabeling.

      -
      - ------- - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      monitors

      Required: Monitors is a collection of Ceph monitors More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it

      true

      string array

      volumeMode

      volumeMode defines what type of volume is required by the claim. Value of Filesystem is implied when not included in claim spec. This is an alpha feature and may change in the future.

      false

      v1.PersistentVolumeMode

      path

      Optional: Used as the mounted root, rather than the full Ceph tree, default is /

      false

      string

      user

      Optional: User is the rados user name, default is admin More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it

      false

      string

      secretFile

      Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it

      false

      string

      secretRef

      Optional: SecretRef is reference to the authentication secret for User, default is empty. More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it

      false

      v1.LocalObjectReference

      readOnly

      Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it

      false

      boolean

      false

      @@ -1300,115 +799,6 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } -
      -
      -

      v1.ResourceQuotaSpec

      -
      -

      ResourceQuotaSpec defines the desired hard limits to enforce for Quota.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      hard

      Hard is the set of desired hard limits for each named resource. More info: https://git.k8s.io/community/contributors/design-proposals/admission_control_resource_quota.md

      false

      object

      scopes

      A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.

      false

      v1.ResourceQuotaScope array

      - -
      -
      -

      v1.NamespaceStatus

      -
      -

      NamespaceStatus is information about the current status of a Namespace.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      phase

      Phase is the current lifecycle phase of the namespace. More info: https://git.k8s.io/community/contributors/design-proposals/namespaces.md#phases

      false

      string

      - -
      -
      -

      v1.NamespaceSpec

      -
      -

      NamespaceSpec describes the attributes on a Namespace.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      finalizers

      Finalizers is an opaque list of values that must be empty to permanently remove object from storage. More info: https://git.k8s.io/community/contributors/design-proposals/namespaces.md#finalizers

      false

      v1.FinalizerName array

      -

      v1.PersistentVolume

      @@ -1473,12 +863,9 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
      -

      v1.ConfigMapVolumeSource

      +

      v1.PersistentVolumeStatus

      -

      Adapts a ConfigMap into a volume.

      -
      -
      -

      The contents of the target ConfigMap’s Data field will be presented in a volume as files using the keys in the Data field as the file names, unless the items element is populated with specific mappings of keys to paths. ConfigMap volumes support ownership management and SELinux relabeling.

      +

      PersistentVolumeStatus is the current status of a persistent volume.

      @@ -1499,33 +886,26 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - - + + - - + + - + - - + + - + - - - - - - -

      name

      Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      phase

      Phase indicates if a volume is available, bound to a claim, or released by a claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#phase

      false

      string

      items

      If unspecified, each key-value pair in the Data field of the referenced ConfigMap will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the ConfigMap, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the .. path or start with ...

      message

      A human-readable message indicating details about why the volume is in this state.

      false

      v1.KeyToPath array

      string

      defaultMode

      Optional: mode bits to use on created files by default. Must be a value between 0 and 0777. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.

      reason

      Reason is a brief CamelCase string that describes any failure and is meant for machine parsing and tidy display in the CLI.

      false

      integer (int32)

      string

      optional

      Specify whether the ConfigMap or it’s keys must be defined

      false

      boolean

      false

      @@ -1584,54 +964,6 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } -
      -
      -

      v1.PersistentVolumeStatus

      -
      -

      PersistentVolumeStatus is the current status of a persistent volume.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      phase

      Phase indicates if a volume is available, bound to a claim, or released by a claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#phase

      false

      string

      message

      A human-readable message indicating details about why the volume is in this state.

      false

      string

      reason

      Reason is a brief CamelCase string that describes any failure and is meant for machine parsing and tidy display in the CLI.

      false

      string

      -

      v1.GitRepoVolumeSource

      @@ -1682,9 +1014,9 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
      -

      v1.EndpointsList

      +

      v1.PortworxVolumeSource

      -

      EndpointsList is a list of endpoints.

      +

      PortworxVolumeSource represents a Portworx volume resource.

      @@ -1705,135 +1037,22 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      items

      List of endpoints.

      true

      v1.Endpoints array

      - -
      -
      -

      v1.ReplicationControllerCondition

      -
      -

      ReplicationControllerCondition describes the state of a replication controller at a certain point.

      -
      - ------- - - - - - - - - - - - - - + + - - - - - - - - - + + - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      type

      Type of replication controller condition.

      volumeID

      VolumeID uniquely identifies a Portworx volume

      true

      string

      status

      Status of the condition, one of True, False, Unknown.

      true

      string

      lastTransitionTime

      The last time the condition transitioned from one status to another.

      fsType

      FSType represents the filesystem type to mount Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified.

      false

      string

      reason

      The reason for the condition’s last transition.

      false

      string

      message

      A human readable message indicating details about the transition.

      false

      string

      - -
      -
      -

      v1.SecretEnvSource

      -
      -

      SecretEnvSource selects a Secret to populate the environment variables with.

      -
      -
      -

      The contents of the target Secret’s Data field will represent the key-value pairs as environment variables.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - + + @@ -1841,88 +1060,6 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
      NameDescriptionRequiredSchemaDefault

      name

      Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      false

      string

      optional

      Specify whether the Secret must be defined

      readOnly

      Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.

      false

      boolean

      false

      -
      -
      -

      v1.ScaleStatus

      -
      -

      ScaleStatus represents the current status of a scale subresource.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      replicas

      actual number of observed instances of the scaled object.

      true

      integer (int32)

      selector

      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: http://kubernetes.io/docs/user-guide/labels#label-selectors

      false

      string

      - -
      -
      -

      v1.Capabilities

      -
      -

      Adds and removes POSIX capabilities from running containers.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      add

      Added capabilities

      false

      v1.Capability array

      drop

      Removed capabilities

      false

      v1.Capability array

      -

      v1.ConfigMap

      @@ -1980,9 +1117,9 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
      -

      v1.PortworxVolumeSource

      +

      v1.Capabilities

      -

      PortworxVolumeSource represents a Portworx volume resource.

      +

      Adds and removes POSIX capabilities from running containers.

      @@ -2003,26 +1140,19 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - - - - + + + + - - + + - + - - - - - - -

      volumeID

      VolumeID uniquely identifies a Portworx volume

      true

      string

      add

      Added capabilities

      false

      v1.Capability array

      fsType

      FSType represents the filesystem type to mount Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified.

      drop

      Removed capabilities

      false

      string

      v1.Capability array

      readOnly

      Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.

      false

      boolean

      false

      @@ -2083,9 +1213,9 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
      -

      v1.NodeCondition

      +

      v1.Initializer

      -

      NodeCondition contains condition information for a node.

      +

      Initializer is information about an initializer that has not yet completed.

      @@ -2106,47 +1236,12 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

      type

      Type of node condition.

      name

      name of the process that is responsible for initializing this object.

      true

      string

      status

      Status of the condition, one of True, False, Unknown.

      true

      string

      lastHeartbeatTime

      Last time we got an update on a given condition.

      false

      string

      lastTransitionTime

      Last time the condition transit from one status to another.

      false

      string

      reason

      (brief) reason for the condition’s last transition.

      false

      string

      message

      Human readable message indicating details about last transition.

      false

      string

      @@ -2205,115 +1300,6 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } -
      -
      -

      v1.Initializer

      -
      -

      Initializer is information about an initializer that has not yet completed.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      name of the process that is responsible for initializing this object.

      true

      string

      - -
      -
      -

      v1.LocalObjectReference

      -
      -

      LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      false

      string

      - -
      -
      -

      v1.ResourceQuotaStatus

      -
      -

      ResourceQuotaStatus defines the enforced hard limits and observed use.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      hard

      Hard is the set of enforced hard limits for each named resource. More info: https://git.k8s.io/community/contributors/design-proposals/admission_control_resource_quota.md

      false

      object

      used

      Used is the current observed total usage of the resource in the namespace.

      false

      object

      -

      v1.ProjectedVolumeSource

      @@ -2389,47 +1375,6 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } -
      -
      -

      v1.SecretReference

      -
      -

      SecretReference represents a Secret Reference. It has enough information to retrieve secret in any namespace

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      Name is unique within a namespace to reference a secret resource.

      false

      string

      namespace

      Namespace defines the space within which the secret name must be unique.

      false

      string

      -

      v1.ObjectMeta

      @@ -2524,7 +1469,7 @@ Populated by the system. Read-only. Null for lists. More info:

      deletionTimestamp

      -

      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 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.
      +

      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.

      Populated by the system when a graceful deletion is requested. Read-only. More info:
      https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      @@ -2585,196 +1530,6 @@ When an object is created, the system will populate this list with the current s -
      -
      -

      v1.LimitRangeSpec

      -
      -

      LimitRangeSpec defines a min/max usage limit for resources that match on kind.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      limits

      Limits is the list of LimitRangeItem objects that are enforced.

      true

      v1.LimitRangeItem array

      - -
      -
      -

      v1.AzureFileVolumeSource

      -
      -

      AzureFile represents an Azure File Service mount on the host and bind mount to the pod.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      secretName

      the name of secret that contains Azure Storage Account Name and Key

      true

      string

      shareName

      Share Name

      true

      string

      readOnly

      Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.

      false

      boolean

      false

      - -
      -
      -

      types.UID

      - -
      -
      -

      v1.ISCSIVolumeSource

      -
      -

      Represents an ISCSI disk. ISCSI volumes can only be mounted as read/write once. ISCSI volumes support ownership management and SELinux relabeling.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      targetPortal

      iSCSI target portal. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      true

      string

      iqn

      Target iSCSI Qualified Name.

      true

      string

      lun

      iSCSI target lun number.

      true

      integer (int32)

      iscsiInterface

      Optional: Defaults to default (tcp). iSCSI interface name that uses an iSCSI transport.

      false

      string

      fsType

      Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi

      false

      string

      readOnly

      ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false.

      false

      boolean

      false

      portals

      iSCSI target portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      false

      string array

      chapAuthDiscovery

      whether support iSCSI Discovery CHAP authentication

      false

      boolean

      false

      chapAuthSession

      whether support iSCSI Session CHAP authentication

      false

      boolean

      false

      secretRef

      CHAP secret for iSCSI target and initiator authentication

      false

      v1.LocalObjectReference

      initiatorName

      Custom iSCSI initiator name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface <target portal>:<volume name> will be created for the connection.

      false

      string

      -

      v1.EmptyDirVolumeSource

      @@ -2871,54 +1626,6 @@ When an object is created, the system will populate this list with the current s -
      -
      -

      v1.PodAffinityTerm

      -
      -

      Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key <topologyKey> tches that of any node on which a pod of the set of pods is running

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      labelSelector

      A label query over a set of resources, in this case pods.

      false

      v1.LabelSelector

      namespaces

      namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means "this pod’s namespace"

      false

      string array

      topologyKey

      This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. For PreferredDuringScheduling pod anti-affinity, empty topologyKey is interpreted as "all topologies" ("all topologies" here means all the topologyKeys indicated by scheduler command-line argument --failure-domains); for affinity and for RequiredDuringScheduling pod anti-affinity, empty topologyKey is not allowed.

      false

      string

      -

      v1.EnvFromSource

      @@ -3024,9 +1731,9 @@ When an object is created, the system will populate this list with the current s
      -

      v1.PersistentVolumeClaim

      +

      v1.PodAffinity

      -

      PersistentVolumeClaim is a user’s request for and claim to a persistent volume

      +

      Pod affinity is a group of inter pod affinity scheduling rules.

      @@ -3047,38 +1754,17 @@ When an object is created, the system will populate this list with the current s - - + + - + - - + + - - - - - - - - - - - - - - - - - - - - - - + @@ -3141,9 +1827,9 @@ When an object is created, the system will populate this list with the current s
      -

      v1.PodAffinity

      +

      v1.FlockerVolumeSource

      -

      Pod affinity is a group of inter pod affinity scheduling rules.

      +

      Represents a Flocker volume mounted by the Flocker agent. One and only one of datasetName and datasetUUID should be set. Flocker volumes do not support ownership management or SELinux relabeling.

      kind

      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/api-conventions.md#types-kinds

      requiredDuringSchedulingIgnoredDuringExecution

      If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied.

      false

      string

      v1.PodAffinityTerm array

      apiVersion

      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/api-conventions.md#resources

      preferredDuringSchedulingIgnoredDuringExecution

      The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred.

      false

      string

      metadata

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ObjectMeta

      spec

      Spec defines the desired characteristics of a volume requested by a pod author. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims

      false

      v1.PersistentVolumeClaimSpec

      status

      Status represents the current information/status of a persistent volume claim. Read-only. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims

      false

      v1.PersistentVolumeClaimStatus

      v1.WeightedPodAffinityTerm array

      @@ -3164,17 +1850,17 @@ When an object is created, the system will populate this list with the current s - - + + - + - - + + - + @@ -3182,9 +1868,9 @@ When an object is created, the system will populate this list with the current s
      -

      v1.ServiceAccount

      +

      v1.ListMeta

      -

      ServiceAccount binds together: * a name, understood by users, and perhaps by peripheral systems, for an identity * a principal that can be authenticated and authorized * a set of secrets

      +

      ListMeta describes metadata that synthetic resources must have, including lists and various status objects. A resource may have only one of {ObjectMeta, ListMeta}.

      requiredDuringSchedulingIgnoredDuringExecution

      If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied.

      datasetName

      Name of the dataset stored as metadata → name on the dataset for Flocker should be considered as deprecated

      false

      v1.PodAffinityTerm array

      string

      preferredDuringSchedulingIgnoredDuringExecution

      The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred.

      datasetUUID

      UUID of the dataset. This is unique identifier of a Flocker dataset

      false

      v1.WeightedPodAffinityTerm array

      string

      @@ -3205,126 +1891,23 @@ When an object is created, the system will populate this list with the current s - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - -

      kind

      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/api-conventions.md#types-kinds

      selfLink

      selfLink is a URL representing this object. Populated by the system. Read-only.

      false

      string

      apiVersion

      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/api-conventions.md#resources

      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/api-conventions.md#concurrency-control-and-consistency

      false

      string

      metadata

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      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 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.

      false

      v1.ObjectMeta

      secrets

      Secrets is the list of secrets allowed to be used by pods running using this ServiceAccount. More info: https://kubernetes.io/docs/concepts/configuration/secret

      false

      v1.ObjectReference array

      imagePullSecrets

      ImagePullSecrets is a list of references to secrets in the same namespace to use for pulling any images in pods that reference this ServiceAccount. ImagePullSecrets are distinct from Secrets because Secrets can be mounted in the pod, but ImagePullSecrets are only accessed by the kubelet. More info: https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod

      false

      v1.LocalObjectReference array

      automountServiceAccountToken

      AutomountServiceAccountToken indicates whether pods running as this service account should have an API token automatically mounted. Can be overridden at the pod level.

      false

      boolean

      false

      - -
      -
      -

      v1.PersistentVolumeClaimVolumeSource

      -
      -

      PersistentVolumeClaimVolumeSource references the user’s PVC in the same namespace. This volume finds the bound PV and mounts that volume for the pod. A PersistentVolumeClaimVolumeSource is, essentially, a wrapper around another type of volume that is owned by someone else (the system).

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      claimName

      ClaimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims

      true

      string

      readOnly

      Will force the ReadOnly setting in VolumeMounts. Default false.

      false

      boolean

      false

      - -
      -
      -

      v1.NodeAddress

      -
      -

      NodeAddress contains information for the node’s address.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - @@ -3395,9 +1978,9 @@ When an object is created, the system will populate this list with the current s
      -

      v1.ListMeta

      +

      v1.NodeAddress

      -

      ListMeta describes metadata that synthetic resources must have, including lists and various status objects. A resource may have only one of {ObjectMeta, ListMeta}.

      +

      NodeAddress contains information for the node’s address.

      NameDescriptionRequiredSchemaDefault

      type

      Node address type, one of Hostname, ExternalIP or InternalIP.

      true

      string

      address

      The node address.

      true

      string

      @@ -3418,120 +2001,17 @@ When an object is created, the system will populate this list with the current s - - - - - - - - - - - - - - - - - - - - - -

      selfLink

      selfLink is a URL representing this object. Populated by the system. Read-only.

      false

      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/api-conventions.md#concurrency-control-and-consistency

      false

      string

      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 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.

      false

      string

      - -
      -
      -

      v1.FlockerVolumeSource

      -
      -

      Represents a Flocker volume mounted by the Flocker agent. One and only one of datasetName and datasetUUID should be set. Flocker volumes do not support ownership management or SELinux relabeling.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      datasetName

      Name of the dataset stored as metadata → name on the dataset for Flocker should be considered as deprecated

      false

      string

      datasetUUID

      UUID of the dataset. This is unique identifier of a Flocker dataset

      false

      string

      - -
      -
      -

      v1.ResourceQuotaList

      -
      -

      ResourceQuotaList is a list of ResourceQuota items.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + + + + + + + + @@ -3539,9 +2019,9 @@ When an object is created, the system will populate this list with the current s
      -

      v1.PersistentVolumeClaimStatus

      +

      v1.PersistentVolumeClaimVolumeSource

      -

      PersistentVolumeClaimStatus is the current status of a persistent volume claim.

      +

      PersistentVolumeClaimVolumeSource references the user’s PVC in the same namespace. This volume finds the bound PV and mounts that volume for the pod. A PersistentVolumeClaimVolumeSource is, essentially, a wrapper around another type of volume that is owned by someone else (the system).

      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      items

      Items is a list of ResourceQuota objects. More info: https://git.k8s.io/community/contributors/design-proposals/admission_control_resource_quota.md

      type

      Node address type, one of Hostname, ExternalIP or InternalIP.

      true

      v1.ResourceQuota array

      string

      address

      The node address.

      true

      string

      @@ -3562,40 +2042,22 @@ When an object is created, the system will populate this list with the current s - - - + + + - - + + - - - - - - + - - - - - - - - -

      phase

      Phase represents the current phase of PersistentVolumeClaim.

      false

      claimName

      ClaimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims

      true

      string

      accessModes

      AccessModes contains the actual access modes the volume backing the PVC has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1

      readOnly

      Will force the ReadOnly setting in VolumeMounts. Default false.

      false

      v1.PersistentVolumeAccessMode array

      capacity

      Represents the actual resources of the underlying volume.

      boolean

      false

      object

      conditions

      Current Condition of persistent volume claim. If underlying persistent volume is being resized then the Condition will be set to ResizeStarted.

      false

      v1.PersistentVolumeClaimCondition array

      -
      -
      -

      v1.UniqueVolumeName

      -

      v1.EndpointSubset

      @@ -3778,66 +2240,11 @@ The resulting set of endpoints can be viewed as:
      -
      -
      -

      v1.EnvVarSource

      -
      -

      EnvVarSource represents a source for the value of an EnvVar.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      fieldRef

      Selects a field of the pod: supports metadata.name, metadata.namespace, metadata.labels, metadata.annotations, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP.

      false

      v1.ObjectFieldSelector

      resourceFieldRef

      Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.

      false

      v1.ResourceFieldSelector

      configMapKeyRef

      Selects a key of a ConfigMap.

      false

      v1.ConfigMapKeySelector

      secretKeyRef

      Selects a key of a secret in the pod’s namespace

      false

      v1.SecretKeySelector

      -

      v1.FlexVolumeSource

      -

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.

      +

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.

      @@ -3998,123 +2405,6 @@ The resulting set of endpoints can be viewed as:
      -
      -
      -

      v1.KeyToPath

      -
      -

      Maps a string key to a path within a volume.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      key

      The key to project.

      true

      string

      path

      The relative path of the file to map the key to. May not be an absolute path. May not contain the path element ... May not start with the string ...

      true

      string

      mode

      Optional: mode bits to use on this file, must be a value between 0 and 0777. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.

      false

      integer (int32)

      - -
      -
      -

      v1.AzureDiskVolumeSource

      -
      -

      AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      diskName

      The Name of the data disk in the blob storage

      true

      string

      diskURI

      The URI the data disk in the blob storage

      true

      string

      cachingMode

      Host Caching mode: None, Read Only, Read Write.

      false

      v1.AzureDataDiskCachingMode

      fsType

      Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.

      false

      string

      readOnly

      Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.

      false

      boolean

      false

      kind

      Expected values Shared: multiple blob disks per storage account Dedicated: single blob disk per storage account Managed: azure managed data disk (only in managed availability set). defaults to shared

      false

      v1.AzureDataDiskKind

      -

      v1.Service

      @@ -4179,303 +2469,7 @@ The resulting set of endpoints can be viewed as:
      -

      v1.VsphereVirtualDiskVolumeSource

      -
      -

      Represents a vSphere volume resource.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      volumePath

      Path that identifies vSphere volume vmdk

      true

      string

      fsType

      Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.

      false

      string

      storagePolicyName

      Storage Policy Based Management (SPBM) profile name.

      false

      string

      storagePolicyID

      Storage Policy Based Management (SPBM) profile ID associated with the StoragePolicyName.

      false

      string

      - -
      -
      -

      v1.ServiceAccountList

      -
      -

      ServiceAccountList is a list of ServiceAccount objects

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      items

      List of ServiceAccounts. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/

      true

      v1.ServiceAccount array

      - -
      -
      -

      v1.LimitRangeList

      -
      -

      LimitRangeList is a list of LimitRange items.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      items

      Items is a list of LimitRange objects. More info: https://git.k8s.io/community/contributors/design-proposals/admission_control_limit_range.md

      true

      v1.LimitRange array

      - -
      -
      -

      v1.Endpoints

      -
      -

      Endpoints is a collection of endpoints that implement the actual service. Example:
      - Name: "mysvc",
      - Subsets: [
      - {
      - Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}],
      - Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}]
      - },
      - {
      - Addresses: [{"ip": "10.10.3.3"}],
      - Ports: [{"name": "a", "port": 93}, {"name": "b", "port": 76}]
      - },
      - ]

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ObjectMeta

      subsets

      The set of all endpoints is the union of all subsets. Addresses are placed into subsets according to the IPs they share. A single address with multiple ports, some of which are ready and some of which are not (because they come from different containers) will result in the address being displayed in different subsets for the different ports. No address will appear in both Addresses and NotReadyAddresses in the same subset. Sets of addresses and ports that comprise a service.

      true

      v1.EndpointSubset array

      - -
      -
      -

      v1.DeleteOptions

      -
      -

      DeleteOptions may be provided when deleting an API object.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      gracePeriodSeconds

      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.

      false

      integer (int64)

      preconditions

      Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.

      false

      v1.Preconditions

      orphanDependents

      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.

      false

      boolean

      false

      propagationPolicy

      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.

      false

      v1.DeletionPropagation

      +

      v1.PersistentVolumeMode

      @@ -4539,229 +2533,6 @@ The resulting set of endpoints can be viewed as:
      -
      -
      -

      v1.Volume

      -
      -

      Volume represents a named volume in a pod that may be accessed by any container in the pod.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      Volume’s name. Must be a DNS_LABEL and unique within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      true

      string

      hostPath

      HostPath represents a pre-existing file or directory on the host machine that is directly exposed to the container. This is generally used for system agents or other privileged things that are allowed to see the host machine. Most containers will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath

      false

      v1.HostPathVolumeSource

      emptyDir

      EmptyDir represents a temporary directory that shares a pod’s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir

      false

      v1.EmptyDirVolumeSource

      gcePersistentDisk

      GCEPersistentDisk represents a GCE Disk resource that is attached to a kubelet’s host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk

      false

      v1.GCEPersistentDiskVolumeSource

      awsElasticBlockStore

      AWSElasticBlockStore represents an AWS Disk resource that is attached to a kubelet’s host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore

      false

      v1.AWSElasticBlockStoreVolumeSource

      gitRepo

      GitRepo represents a git repository at a particular revision.

      false

      v1.GitRepoVolumeSource

      secret

      Secret represents a secret that should populate this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret

      false

      v1.SecretVolumeSource

      nfs

      NFS represents an NFS mount on the host that shares a pod’s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs

      false

      v1.NFSVolumeSource

      iscsi

      ISCSI represents an ISCSI Disk resource that is attached to a kubelet’s host machine and then exposed to the pod. More info: https://releases.k8s.io/HEAD/examples/volumes/iscsi/README.md

      false

      v1.ISCSIVolumeSource

      glusterfs

      Glusterfs represents a Glusterfs mount on the host that shares a pod’s lifetime. More info: https://releases.k8s.io/HEAD/examples/volumes/glusterfs/README.md

      false

      v1.GlusterfsVolumeSource

      persistentVolumeClaim

      PersistentVolumeClaimVolumeSource represents a reference to a PersistentVolumeClaim in the same namespace. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims

      false

      v1.PersistentVolumeClaimVolumeSource

      rbd

      RBD represents a Rados Block Device mount on the host that shares a pod’s lifetime. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md

      false

      v1.RBDVolumeSource

      flexVolume

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.

      false

      v1.FlexVolumeSource

      cinder

      Cinder represents a cinder volume attached and mounted on kubelets host machine More info: https://releases.k8s.io/HEAD/examples/mysql-cinder-pd/README.md

      false

      v1.CinderVolumeSource

      cephfs

      CephFS represents a Ceph FS mount on the host that shares a pod’s lifetime

      false

      v1.CephFSVolumeSource

      flocker

      Flocker represents a Flocker volume attached to a kubelet’s host machine. This depends on the Flocker control service being running

      false

      v1.FlockerVolumeSource

      downwardAPI

      DownwardAPI represents downward API about the pod that should populate this volume

      false

      v1.DownwardAPIVolumeSource

      fc

      FC represents a Fibre Channel resource that is attached to a kubelet’s host machine and then exposed to the pod.

      false

      v1.FCVolumeSource

      azureFile

      AzureFile represents an Azure File Service mount on the host and bind mount to the pod.

      false

      v1.AzureFileVolumeSource

      configMap

      ConfigMap represents a configMap that should populate this volume

      false

      v1.ConfigMapVolumeSource

      vsphereVolume

      VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine

      false

      v1.VsphereVirtualDiskVolumeSource

      quobyte

      Quobyte represents a Quobyte mount on the host that shares a pod’s lifetime

      false

      v1.QuobyteVolumeSource

      azureDisk

      AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod.

      false

      v1.AzureDiskVolumeSource

      photonPersistentDisk

      PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine

      false

      v1.PhotonPersistentDiskVolumeSource

      projected

      Items for all in one resources secrets, configmaps, and downward API

      false

      v1.ProjectedVolumeSource

      portworxVolume

      PortworxVolume represents a portworx volume attached and mounted on kubelets host machine

      false

      v1.PortworxVolumeSource

      scaleIO

      ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.

      false

      v1.ScaleIOVolumeSource

      storageos

      StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes.

      false

      v1.StorageOSVolumeSource

      -

      v1.ResourceFieldSelector

      @@ -4810,54 +2581,6 @@ The resulting set of endpoints can be viewed as:
      -
      -
      -

      v1.VolumeProjection

      -
      -

      Projection that may be projected along with other supported volume types

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      secret

      information about the secret data to project

      false

      v1.SecretProjection

      downwardAPI

      information about the downwardAPI data to project

      false

      v1.DownwardAPIProjection

      configMap

      information about the configMap data to project

      false

      v1.ConfigMapProjection

      -

      v1.WeightedPodAffinityTerm

      @@ -4901,9 +2624,9 @@ The resulting set of endpoints can be viewed as:
      -

      v1.Probe

      +

      v1.ISCSIPersistentVolumeSource

      -

      Probe describes a health check to be performed against a container to determine whether it is alive or ready to receive traffic.

      +

      ISCSIPersistentVolumeSource represents an ISCSI disk. ISCSI volumes can only be mounted as read/write once. ISCSI volumes support ownership management and SELinux relabeling.

      @@ -4924,451 +2647,82 @@ The resulting set of endpoints can be viewed as:
      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

      exec

      One and only one of the following should be specified. Exec specifies the action to take.

      false

      v1.ExecAction

      httpGet

      HTTPGet specifies the http request to perform.

      false

      v1.HTTPGetAction

      tcpSocket

      TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported

      false

      v1.TCPSocketAction

      initialDelaySeconds

      Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes

      false

      integer (int32)

      timeoutSeconds

      Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes

      false

      integer (int32)

      periodSeconds

      How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1.

      false

      integer (int32)

      successThreshold

      Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness. Minimum value is 1.

      false

      integer (int32)

      failureThreshold

      Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1.

      false

      integer (int32)

      - -
      -
      -

      v1.CephFSPersistentVolumeSource

      -
      -

      Represents a Ceph Filesystem mount that lasts the lifetime of a pod Cephfs volumes do not support ownership management or SELinux relabeling.

      -
      - ------- - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + - - - - + + - - - - - - + - - - + - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      monitors

      Required: Monitors is a collection of Ceph monitors More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it

      targetPortal

      iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      true

      string

      iqn

      Target iSCSI Qualified Name.

      true

      string

      lun

      iSCSI Target Lun number.

      true

      integer (int32)

      iscsiInterface

      iSCSI Interface Name that uses an iSCSI transport. Defaults to default (tcp).

      false

      string

      fsType

      Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi

      false

      string

      readOnly

      ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false.

      false

      boolean

      false

      portals

      iSCSI Target Portal List. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      false

      string array

      path

      Optional: Used as the mounted root, rather than the full Ceph tree, default is /

      chapAuthDiscovery

      whether support iSCSI Discovery CHAP authentication

      false

      boolean

      false

      string

      user

      Optional: User is the rados user name, default is admin More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it

      chapAuthSession

      whether support iSCSI Session CHAP authentication

      false

      string

      secretFile

      Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it

      boolean

      false

      string

      secretRef

      Optional: SecretRef is reference to the authentication secret for User, default is empty. More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it

      CHAP Secret for iSCSI target and initiator authentication

      false

      v1.SecretReference

      readOnly

      Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it

      false

      boolean

      false

      - -
      -
      -

      v1.SecretKeySelector

      -
      -

      SecretKeySelector selects a key of a Secret.

      -
      - ------- - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      initiatorName

      Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface <target portal>:<volume name> will be created for the connection.

      false

      string

      key

      The key of the secret to select from. Must be a valid secret key.

      true

      string

      optional

      Specify whether the Secret or it’s key must be defined

      false

      boolean

      false

      - -
      -
      -

      v1.ReplicationController

      -
      -

      ReplicationController represents the configuration of a replication controller.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      If the Labels of a ReplicationController are empty, they are defaulted to 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/api-conventions.md#metadata

      false

      v1.ObjectMeta

      spec

      Spec defines the specification of the desired behavior of the replication controller. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      v1.ReplicationControllerSpec

      status

      Status is the most recently observed status of the replication controller. This data may be out of date by some window of time. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      v1.ReplicationControllerStatus

      - -
      -
      -

      v1.Capability

      - -
      -
      -

      v1.PodStatus

      -
      -

      PodStatus represents information about the status of a pod. Status may trail the actual state of a system.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      phase

      Current condition of the pod. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-phase

      false

      string

      conditions

      Current service state of pod. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions

      false

      v1.PodCondition array

      message

      A human readable message indicating details about why the pod is in this condition.

      false

      string

      reason

      A brief CamelCase message indicating details about why the pod is in this state. e.g. Evicted

      false

      string

      hostIP

      IP address of the host to which the pod is assigned. Empty if not yet scheduled.

      false

      string

      podIP

      IP address allocated to the pod. Routable at least within the cluster. Empty if not yet allocated.

      false

      string

      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.

      false

      string

      initContainerStatuses

      The list has one entry per init container in the manifest. The most recent successful init container will have ready = true, the most recently started container will have startTime set. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-and-container-status

      false

      v1.ContainerStatus array

      containerStatuses

      The list has one entry per container in the manifest. Each entry is currently the output of docker inspect. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-and-container-status

      false

      v1.ContainerStatus array

      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://github.com/kubernetes/kubernetes/blob/master/docs/design/resource-qos.md

      false

      string

      - -
      -
      -

      v1.DownwardAPIVolumeFile

      -
      -

      DownwardAPIVolumeFile represents information to create the file containing the pod field

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      path

      Required: Path is the relative path name of the file to be created. Must not be absolute or contain the .. path. Must be utf-8 encoded. The first item of the relative path must not start with ..

      true

      string

      fieldRef

      Required: Selects a field of the pod: only annotations, labels, name and namespace are supported.

      false

      v1.ObjectFieldSelector

      resourceFieldRef

      Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported.

      false

      v1.ResourceFieldSelector

      mode

      Optional: mode bits to use on this file, must be a value between 0 and 0777. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.

      false

      integer (int32)

      - -
      -
      -

      v1.LimitRange

      -
      -

      LimitRange sets resource usage limits for each kind of resource in a Namespace.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ObjectMeta

      spec

      Spec defines the limits enforced. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      v1.LimitRangeSpec

      @@ -5434,208 +2788,6 @@ The resulting set of endpoints can be viewed as:
      -
      -
      -

      v1.PodSpec

      -
      -

      PodSpec is a description of a pod.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      volumes

      List of volumes that can be mounted by containers belonging to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes

      false

      v1.Volume array

      initContainers

      List of initialization containers belonging to the pod. Init containers are executed in order prior to containers being started. If any init container fails, the pod is considered to have failed and is handled according to its restartPolicy. The name for an init container or normal container must be unique among all containers. Init containers may not have Lifecycle actions, Readiness probes, or Liveness probes. The resourceRequirements of an init container are taken into account during scheduling by finding the highest request/limit for each resource type, and then using the max of of that value or the sum of the normal containers. Limits are applied to init containers in a similar fashion. Init containers cannot currently be added or removed. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/

      false

      v1.Container array

      containers

      List of containers belonging to the pod. Containers cannot currently be added or removed. There must be at least one container in a Pod. Cannot be updated.

      true

      v1.Container array

      restartPolicy

      Restart policy for all containers within the pod. One of Always, OnFailure, Never. Default to Always. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy

      false

      string

      terminationGracePeriodSeconds

      Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period will be used instead. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. Defaults to 30 seconds.

      false

      integer (int64)

      activeDeadlineSeconds

      Optional duration in seconds the pod may be active on the node relative to StartTime before the system will actively try to mark it failed and kill associated containers. Value must be a positive integer.

      false

      integer (int64)

      dnsPolicy

      Set DNS policy for containers within the pod. One of ClusterFirstWithHostNet, ClusterFirst or Default. Defaults to "ClusterFirst". To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to ClusterFirstWithHostNet.

      false

      string

      nodeSelector

      NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node’s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/

      false

      object

      serviceAccountName

      ServiceAccountName is the name of the ServiceAccount to use to run this pod. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/

      false

      string

      serviceAccount

      DeprecatedServiceAccount is a depreciated alias for ServiceAccountName. Deprecated: Use serviceAccountName instead.

      false

      string

      automountServiceAccountToken

      AutomountServiceAccountToken indicates whether a service account token should be automatically mounted.

      false

      boolean

      false

      nodeName

      NodeName is a request to schedule this pod onto a specific node. If it is non-empty, the scheduler simply schedules this pod onto that node, assuming that it fits resource requirements.

      false

      string

      hostNetwork

      Host networking requested for this pod. Use the host’s network namespace. If this option is set, the ports that will be used must be specified. Default to false.

      false

      boolean

      false

      hostPID

      Use the host’s pid namespace. Optional: Default to false.

      false

      boolean

      false

      hostIPC

      Use the host’s ipc namespace. Optional: Default to false.

      false

      boolean

      false

      securityContext

      SecurityContext holds pod-level security attributes and common container settings. Optional: Defaults to empty. See type description for default values of each field.

      false

      v1.PodSecurityContext

      imagePullSecrets

      ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. If specified, these secrets will be passed to individual puller implementations for them to use. For example, in the case of docker, only DockerConfig type secrets are honored. More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod

      false

      v1.LocalObjectReference array

      hostname

      Specifies the hostname of the Pod If not specified, the pod’s hostname will be set to a system-defined value.

      false

      string

      subdomain

      If specified, the fully qualified Pod hostname will be "<hostname>.<subdomain>.<pod namespace>.svc.<cluster domain>". If not specified, the pod will not have a domainname at all.

      false

      string

      affinity

      If specified, the pod’s scheduling constraints

      false

      v1.Affinity

      schedulerName

      If specified, the pod will be dispatched by specified scheduler. If not specified, the pod will be dispatched by default scheduler.

      false

      string

      tolerations

      If specified, the pod’s tolerations.

      false

      v1.Toleration array

      hostAliases

      HostAliases is an optional list of hosts and IPs that will be injected into the pod’s hosts file if specified. This is only valid for non-hostNetwork pods.

      false

      v1.HostAlias array

      priorityClassName

      If specified, indicates the pod’s priority. "SYSTEM" is a special keyword which indicates the highest priority. Any other name must be defined by creating a PriorityClass object with that name. If not specified, the pod priority will be default or zero if there is no default.

      false

      string

      priority

      The priority value. Various system components use this field to find the priority of the pod. 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.

      false

      integer (int32)

      -

      v1.ResourceQuota

      @@ -5700,9 +2852,9 @@ The resulting set of endpoints can be viewed as:
      -

      v1.EventList

      +

      v1.GlusterfsVolumeSource

      -

      EventList is a list of events.

      +

      Represents a Glusterfs mount that lasts the lifetime of a pod. Glusterfs volumes do not support ownership management or SELinux relabeling.

      @@ -5723,176 +2875,25 @@ The resulting set of endpoints can be viewed as:
      - - - - - - - - - - - - - - - - - - - - - - - + + - + - -

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      items

      List of events

      endpoints

      EndpointsName is the endpoint name that details Glusterfs topology. More info: https://releases.k8s.io/HEAD/examples/volumes/glusterfs/README.md#create-a-pod

      true

      v1.Event array

      string

      - -
      -
      -

      v1.Lifecycle

      -
      -

      Lifecycle describes actions that the management system should take in response to container lifecycle events. For the PostStart and PreStop lifecycle handlers, management of the container blocks until the action is complete, unless the container process fails, in which case the handler is aborted.

      -
      - ------- - - - - - - + + + + + - - - - + + - - - - - - + - - - - -
      NameDescriptionRequiredSchemaDefault

      path

      Path is the Glusterfs volume path. More info: https://releases.k8s.io/HEAD/examples/volumes/glusterfs/README.md#create-a-pod

      true

      string

      postStart

      PostStart is called immediately after a container is created. If the handler fails, the container is terminated and restarted according to its restart policy. Other management of the container blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks

      readOnly

      ReadOnly here will force the Glusterfs volume to be mounted with read-only permissions. Defaults to false. More info: https://releases.k8s.io/HEAD/examples/volumes/glusterfs/README.md#create-a-pod

      false

      v1.Handler

      preStop

      PreStop is called immediately before a container is terminated. The container is terminated after the handler completes. The reason for termination is passed to the handler. Regardless of the outcome of the handler, the container is eventually terminated. Other management of the container blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks

      boolean

      false

      v1.Handler

      - -
      -
      -

      v1.ReplicationControllerSpec

      -
      -

      ReplicationControllerSpec is the specification of a replication controller.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      replicas

      Replicas is the number of desired replicas. This is a pointer to distinguish between explicit zero and unspecified. Defaults to 1. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#what-is-a-replicationcontroller

      false

      integer (int32)

      minReadySeconds

      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)

      false

      integer (int32)

      selector

      Selector is a label query over pods that should match the Replicas count. If Selector is empty, it is defaulted to the labels present on the Pod template. Label keys and values that must match in order to be controlled by this replication controller, if empty defaulted to labels on Pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors

      false

      object

      template

      Template is the object that describes the pod that will be created if insufficient replicas are detected. This takes precedence over a TemplateRef. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template

      false

      v1.PodTemplateSpec

      - -
      -
      -

      v1.Handler

      -
      -

      Handler defines a specific action that should be taken

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      exec

      One and only one of the following should be specified. Exec specifies the action to take.

      false

      v1.ExecAction

      httpGet

      HTTPGet specifies the http request to perform.

      false

      v1.HTTPGetAction

      tcpSocket

      TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported

      false

      v1.TCPSocketAction

      @@ -5996,9 +2997,9 @@ The resulting set of endpoints can be viewed as:
      -

      v1.GlusterfsVolumeSource

      +

      v1.Handler

      -

      Represents a Glusterfs mount that lasts the lifetime of a pod. Glusterfs volumes do not support ownership management or SELinux relabeling.

      +

      Handler defines a specific action that should be taken

      @@ -6019,34 +3020,34 @@ The resulting set of endpoints can be viewed as:
      - - - - + + + + - - - - + + + + - - - - + + + +

      endpoints

      EndpointsName is the endpoint name that details Glusterfs topology. More info: https://releases.k8s.io/HEAD/examples/volumes/glusterfs/README.md#create-a-pod

      true

      string

      exec

      One and only one of the following should be specified. Exec specifies the action to take.

      false

      v1.ExecAction

      path

      Path is the Glusterfs volume path. More info: https://releases.k8s.io/HEAD/examples/volumes/glusterfs/README.md#create-a-pod

      true

      string

      httpGet

      HTTPGet specifies the http request to perform.

      false

      v1.HTTPGetAction

      readOnly

      ReadOnly here will force the Glusterfs volume to be mounted with read-only permissions. Defaults to false. More info: https://releases.k8s.io/HEAD/examples/volumes/glusterfs/README.md#create-a-pod

      false

      boolean

      tcpSocket

      TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported

      false

      v1.TCPSocketAction

      -

      v1.AttachedVolume

      +

      v1.ReplicationControllerSpec

      -

      AttachedVolume describes a volume attached to a node

      +

      ReplicationControllerSpec is the specification of a replication controller.

      @@ -6067,16 +3068,71 @@ The resulting set of endpoints can be viewed as:
      - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

      name

      Name of the attached volume

      true

      replicas

      Replicas is the number of desired replicas. This is a pointer to distinguish between explicit zero and unspecified. Defaults to 1. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#what-is-a-replicationcontroller

      false

      integer (int32)

      minReadySeconds

      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)

      false

      integer (int32)

      selector

      Selector is a label query over pods that should match the Replicas count. If Selector is empty, it is defaulted to the labels present on the Pod template. Label keys and values that must match in order to be controlled by this replication controller, if empty defaulted to labels on Pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors

      false

      object

      template

      Template is the object that describes the pod that will be created if insufficient replicas are detected. This takes precedence over a TemplateRef. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template

      false

      v1.PodTemplateSpec

      + +
      +
      +

      v1.EventSource

      +
      +

      EventSource contains information for an event.

      +
      + +++++++ + + + + + + + + + + + + + + - - - + + + @@ -6145,47 +3201,6 @@ The resulting set of endpoints can be viewed as:
      NameDescriptionRequiredSchemaDefault

      component

      Component from which the event is generated.

      false

      string

      devicePath

      DevicePath represents the device path where the volume should be available

      true

      host

      Node name on which the event is generated.

      false

      string

      -
      -
      -

      v1.EventSource

      -
      -

      EventSource contains information for an event.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      component

      Component from which the event is generated.

      false

      string

      host

      Node name on which the event is generated.

      false

      string

      -

      v1.StatusCause

      @@ -6272,158 +3287,6 @@ Examples:
      -
      -
      -

      v1.PodCondition

      -
      -

      PodCondition contains details for the current condition of this pod.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      type

      Type is the type of the condition. Currently only Ready. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions

      true

      string

      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

      true

      string

      lastProbeTime

      Last time we probed the condition.

      false

      string

      lastTransitionTime

      Last time the condition transitioned from one status to another.

      false

      string

      reason

      Unique, one-word, CamelCase reason for the condition’s last transition.

      false

      string

      message

      Human-readable message indicating details about last transition.

      false

      string

      - -
      -
      -

      v1.RBDVolumeSource

      -
      -

      Represents a Rados Block Device mount that lasts the lifetime of a pod. RBD volumes support ownership management and SELinux relabeling.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      monitors

      A collection of Ceph monitors. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      true

      string array

      image

      The rados image name. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      true

      string

      fsType

      Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd

      false

      string

      pool

      The rados pool name. Default is rbd. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      false

      string

      user

      The rados user name. Default is admin. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      false

      string

      keyring

      Keyring is the path to key ring for RBDUser. Default is /etc/ceph/keyring. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      false

      string

      secretRef

      SecretRef is name of the authentication secret for RBDUser. If provided overrides keyring. Default is nil. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      false

      v1.LocalObjectReference

      readOnly

      ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      false

      boolean

      false

      -

      v1.ConfigMapProjection

      @@ -6475,47 +3338,6 @@ Examples:
      -
      -
      -

      v1.PhotonPersistentDiskVolumeSource

      -
      -

      Represents a Photon Controller persistent disk resource.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      pdID

      ID that identifies Photon Controller persistent disk

      true

      string

      fsType

      Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.

      false

      string

      -

      v1.ScaleIOVolumeSource

      @@ -6570,21 +3392,21 @@ Examples:

      protectionDomain

      -

      The name of the Protection Domain for the configured storage (defaults to "default").

      +

      The name of the ScaleIO Protection Domain for the configured storage.

      false

      string

      storagePool

      -

      The Storage Pool associated with the protection domain (defaults to "default").

      +

      The ScaleIO Storage Pool associated with the protection domain.

      false

      string

      storageMode

      -

      Indicates whether the storage for a volume should be thick or thin (defaults to "thin").

      +

      Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned.

      false

      string

      @@ -6613,10 +3435,6 @@ Examples:
      -
      -
      -

      v1.HostPathType

      -

      v1.Initializers

      @@ -6658,89 +3476,6 @@ Examples:
      -
      -
      -

      v1.Status

      -
      -

      Status is a return value for calls that don’t return other objects.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      status

      Status of the operation. One of: "Success" or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      string

      message

      A human-readable description of the status of this operation.

      false

      string

      reason

      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.

      false

      string

      details

      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.

      false

      v1.StatusDetails

      code

      Suggested HTTP return code for this status, 0 if not set.

      false

      integer (int32)

      -

      v1.PodTemplate

      @@ -6919,109 +3654,6 @@ Examples:
      -
      -
      -

      v1.FCVolumeSource

      -
      -

      Represents a Fibre Channel volume. Fibre Channel volumes can only be mounted as read/write once. Fibre Channel volumes support ownership management and SELinux relabeling.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      targetWWNs

      Optional: FC target worldwide names (WWNs)

      false

      string array

      lun

      Optional: FC target lun number

      false

      integer (int32)

      fsType

      Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.

      false

      string

      readOnly

      Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.

      false

      boolean

      false

      wwids

      Optional: FC volume world wide identifiers (wwids) Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously.

      false

      string array

      - -
      -
      -

      v1.PodAntiAffinity

      -
      -

      Pod anti affinity is a group of inter pod anti affinity scheduling rules.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      requiredDuringSchedulingIgnoredDuringExecution

      If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied.

      false

      v1.PodAffinityTerm array

      preferredDuringSchedulingIgnoredDuringExecution

      The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred.

      false

      v1.WeightedPodAffinityTerm array

      -

      v1.EndpointPort

      @@ -7072,13 +3704,9 @@ Examples:
      -

      v1.DeletionPropagation

      - -
      -
      -

      v1.TCPSocketAction

      +

      v1.PodAntiAffinity

      -

      TCPSocketAction describes an action based on opening a socket

      +

      Pod anti affinity is a group of inter pod anti affinity scheduling rules.

      @@ -7099,15 +3727,63 @@ Examples:
      - - - + + + + + + + + + + + + + + +

      port

      Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME.

      true

      requiredDuringSchedulingIgnoredDuringExecution

      If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied.

      false

      v1.PodAffinityTerm array

      preferredDuringSchedulingIgnoredDuringExecution

      The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred.

      false

      v1.WeightedPodAffinityTerm array

      + +
      +
      +

      v1.EventSeries

      +
      +

      EventSeries contain information on series of events, i.e. thing that was/is happening continously for some time.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + - - + + @@ -7246,40 +3922,6 @@ Examples:
      NameDescriptionRequiredSchemaDefault

      count

      Number of occurrences in this series up to the last heartbeat time

      false

      integer (int32)

      lastObservedTime

      Time of the last occurence observed

      false

      string

      host

      Optional: Host name to connect to, defaults to the pod IP.

      state

      State of this Series: Ongoing or Finished

      false

      string

      -
      -
      -

      v1.LoadBalancerStatus

      -
      -

      LoadBalancerStatus represents the status of a load-balancer.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      ingress

      Ingress is a list containing ingress points for the load-balancer. Traffic intended for the service should be sent to these ingress points.

      false

      v1.LoadBalancerIngress array

      -

      v1.SecretList

      @@ -7335,237 +3977,6 @@ Examples:
      -
      -
      -

      v1.Container

      -
      -

      A single application container that you want to run within a pod.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      Name of the container specified as a DNS_LABEL. Each container in a pod must have a unique name (DNS_LABEL). Cannot be updated.

      true

      string

      image

      Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images This field is optional to allow higher level config management to default or override container images in workload controllers like Deployments and StatefulSets.

      false

      string

      command

      Entrypoint array. Not executed within a shell. The docker image’s ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container’s environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell

      false

      string array

      args

      Arguments to the entrypoint. The docker image’s CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container’s environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell

      false

      string array

      workingDir

      Container’s working directory. If not specified, the container runtime’s default will be used, which might be configured in the container image. Cannot be updated.

      false

      string

      ports

      List of ports to expose from the container. Exposing a port here gives the system additional information about the network connections a container uses, but is primarily informational. Not specifying a port here DOES NOT prevent that port from being exposed. Any port which is listening on the default "0.0.0.0" address inside a container will be accessible from the network. Cannot be updated.

      false

      v1.ContainerPort array

      envFrom

      List of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. 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. Cannot be updated.

      false

      v1.EnvFromSource array

      env

      List of environment variables to set in the container. Cannot be updated.

      false

      v1.EnvVar array

      resources

      Compute Resources required by this container. Cannot be updated. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources

      false

      v1.ResourceRequirements

      volumeMounts

      Pod volumes to mount into the container’s filesystem. Cannot be updated.

      false

      v1.VolumeMount array

      livenessProbe

      Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes

      false

      v1.Probe

      readinessProbe

      Periodic probe of container service readiness. Container will be removed from service endpoints if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes

      false

      v1.Probe

      lifecycle

      Actions that the management system should take in response to container lifecycle events. Cannot be updated.

      false

      v1.Lifecycle

      terminationMessagePath

      Optional: Path at which the file to which the container’s termination message will be written is mounted into the container’s filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.

      false

      string

      terminationMessagePolicy

      Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated.

      false

      string

      imagePullPolicy

      Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images

      false

      string

      securityContext

      Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md

      false

      v1.SecurityContext

      stdin

      Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false.

      false

      boolean

      false

      stdinOnce

      Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false

      false

      boolean

      false

      tty

      Whether this container should allocate a TTY for itself, also requires stdin to be true. Default is false.

      false

      boolean

      false

      - -
      -
      -

      v1.PodSecurityContext

      -
      -

      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.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      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.

      false

      v1.SELinuxOptions

      runAsUser

      The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container.

      false

      integer (int64)

      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 SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.

      false

      boolean

      false

      supplementalGroups

      A list of groups applied to the first process run in each container, in addition to the container’s primary GID. If unspecified, no groups will be added to any container.

      false

      integer (int32) array

      fsGroup

      A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod:
      -
      -1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR’d with rw-rw

      false

      integer (int64)

      -

      v1.PersistentVolumeSpec

      @@ -7636,14 +4047,14 @@ Examples:

      rbd

      RBD represents a Rados Block Device mount on the host that shares a pod’s lifetime. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md

      false

      -

      v1.RBDVolumeSource

      +

      v1.RBDPersistentVolumeSource

      iscsi

      ISCSI represents an ISCSI Disk resource that is attached to a kubelet’s host machine and then exposed to the pod. Provisioned by an admin.

      false

      -

      v1.ISCSIVolumeSource

      +

      v1.ISCSIPersistentVolumeSource

      @@ -7676,9 +4087,9 @@ Examples:

      flexVolume

      -

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.

      +

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.

      false

      -

      v1.FlexVolumeSource

      +

      v1.FlexPersistentVolumeSource

      @@ -7727,7 +4138,7 @@ Examples:

      scaleIO

      ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.

      false

      -

      v1.ScaleIOVolumeSource

      +

      v1.ScaleIOPersistentVolumeSource

      @@ -7745,6 +4156,13 @@ Examples:
      +

      csi

      +

      CSI represents storage that handled by an external CSI driver

      +

      false

      +

      v1.CSIPersistentVolumeSource

      + + +

      accessModes

      AccessModes contains all ways the volume can be mounted. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes

      false

      @@ -7779,14 +4197,21 @@ Examples:

      string array

      + +

      volumeMode

      +

      volumeMode defines if a volume is intended to be used with a formatted filesystem or to remain in raw block state. Value of Filesystem is implied when not included in spec. This is an alpha feature and may change in the future.

      +

      false

      +

      v1.PersistentVolumeMode

      + +
      -

      v1.ReplicationControllerStatus

      +

      v1.PodSecurityContext

      -

      ReplicationControllerStatus represents the current status of a replication controller.

      +

      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.

      @@ -7807,45 +4232,40 @@ Examples:
      - - - - - - - - - + + - + - - - - - - - - - - - - - - - - + + - - + + - + + + + + + + + + + + + + + + @@ -7917,164 +4337,6 @@ Examples:

      replicas

      Replicas is the most recently oberved number of replicas. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#what-is-a-replicationcontroller

      true

      integer (int32)

      fullyLabeledReplicas

      The number of pods that have labels matching the labels of the pod template of the replication controller.

      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.

      false

      integer (int32)

      v1.SELinuxOptions

      readyReplicas

      The number of ready replicas for this replication controller.

      false

      integer (int32)

      availableReplicas

      The number of available replicas (ready for at least minReadySeconds) for this replication controller.

      false

      integer (int32)

      observedGeneration

      ObservedGeneration reflects the generation of the most recently observed replication controller.

      runAsUser

      The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container.

      false

      integer (int64)

      conditions

      Represents the latest available observations of a replication controller’s current state.

      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 SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.

      false

      v1.ReplicationControllerCondition array

      boolean

      false

      supplementalGroups

      A list of groups applied to the first process run in each container, in addition to the container’s primary GID. If unspecified, no groups will be added to any container.

      false

      integer (int32) array

      fsGroup

      A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod:
      +
      +1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR’d with rw-rw

      false

      integer (int64)

      -
      -
      -

      v1.OwnerReference

      -
      -

      OwnerReference contains enough information to let you identify an owning object. Currently, an owning object must be in the same namespace, so there is no namespace field.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      apiVersion

      API version of the referent.

      true

      string

      kind

      Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      true

      string

      name

      Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names

      true

      string

      uid

      UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids

      true

      string

      controller

      If true, this reference points to the managing controller.

      false

      boolean

      false

      blockOwnerDeletion

      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. Defaults to false. To set this field, a user needs "delete" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.

      false

      boolean

      false

      - -
      -
      -

      v1.ComponentCondition

      -
      -

      Information about the condition of a component.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      type

      Type of condition for a component. Valid value: "Healthy"

      true

      string

      status

      Status of the condition for a component. Valid values for "Healthy": "True", "False", or "Unknown".

      true

      string

      message

      Message about the condition for a component. For example, information about a health check.

      false

      string

      error

      Condition error code for a component. For example, a health check error code.

      false

      string

      - -
      -
      -

      v1.ScaleSpec

      -
      -

      ScaleSpec describes the attributes of a scale subresource.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      replicas

      desired number of instances for the scaled object.

      false

      integer (int32)

      -

      v1.ComponentStatusList

      @@ -8130,6 +4392,40 @@ Examples:
      +
      +
      +

      v1.ScaleSpec

      +
      +

      ScaleSpec describes the attributes of a scale subresource.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      replicas

      desired number of instances for the scaled object.

      false

      integer (int32)

      +

      v1.ClientIPConfig

      @@ -8166,9 +4462,9 @@ Examples:
      -

      v1.APIResource

      +

      v1.VolumeDevice

      -

      APIResource specifies the name of a resource and whether it is namespaced.

      +

      volumeDevice describes a mapping of a raw block device within a container.

      @@ -8190,67 +4486,18 @@ Examples:
      - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

      name

      name is the plural name of the resource.

      name must match the name of a persistentVolumeClaim in the pod

      true

      string

      singularName

      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.

      devicePath

      devicePath is the path inside of the container that the device will be mapped to.

      true

      string

      namespaced

      namespaced indicates if a resource is namespaced or not.

      true

      boolean

      false

      group

      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".

      false

      string

      version

      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)".

      false

      string

      kind

      kind is the kind for the resource (e.g. Foo is the kind for a resource foo)

      true

      string

      verbs

      verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy)

      true

      string array

      shortNames

      shortNames is a list of suggested short names of the resource.

      false

      string array

      categories

      categories is a list of the grouped resources this resource belongs to (e.g. all)

      false

      string array

      @@ -8394,6 +4641,130 @@ Examples:
      +
      +
      +

      v1.PodDNSConfigOption

      +
      +

      PodDNSConfigOption defines DNS resolver options of a pod.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      Required.

      false

      string

      value

      false

      string

      + +
      +
      +

      v1.RBDPersistentVolumeSource

      +
      +

      Represents a Rados Block Device mount that lasts the lifetime of a pod. RBD volumes support ownership management and SELinux relabeling.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      monitors

      A collection of Ceph monitors. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      true

      string array

      image

      The rados image name. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      true

      string

      fsType

      Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd

      false

      string

      pool

      The rados pool name. Default is rbd. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      false

      string

      user

      The rados user name. Default is admin. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      false

      string

      keyring

      Keyring is the path to key ring for RBDUser. Default is /etc/ceph/keyring. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      false

      string

      secretRef

      SecretRef is name of the authentication secret for RBDUser. If provided overrides keyring. Default is nil. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      false

      v1.SecretReference

      readOnly

      ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      false

      boolean

      false

      +

      v1.ContainerStateTerminated

      @@ -8470,61 +4841,6 @@ Examples:
      -
      -
      -

      v1.Binding

      -
      -

      Binding ties one object to another; for example, a pod is bound to a node by a scheduler. Deprecated in 1.7, please use the bindings subresource of pods instead.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ObjectMeta

      target

      The target object that you want to bind to the standard object.

      true

      v1.ObjectReference

      -

      v1.CinderVolumeSource

      @@ -8573,54 +4889,6 @@ Examples:
      -
      -
      -

      v1.ContainerState

      -
      -

      ContainerState holds a possible state of container. Only one of its members may be specified. If none of them is specified, the default one is ContainerStateWaiting.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      waiting

      Details about a waiting container

      false

      v1.ContainerStateWaiting

      running

      Details about a running container

      false

      v1.ContainerStateRunning

      terminated

      Details about a terminated container

      false

      v1.ContainerStateTerminated

      -

      v1.SecurityContext

      @@ -8755,6 +5023,68 @@ Examples:
      +
      +
      +

      v1.QuobyteVolumeSource

      +
      +

      Represents a Quobyte mount that lasts the lifetime of a pod. Quobyte volumes do not support ownership management or SELinux relabeling.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      registry

      Registry represents a single or multiple Quobyte Registry services specified as a string as host:port pair (multiple entries are separated with commas) which acts as the central registry for volumes

      true

      string

      volume

      Volume is a string that references an already created Quobyte volume by name.

      true

      string

      readOnly

      ReadOnly here will force the Quobyte volume to be mounted with read-only permissions. Defaults to false.

      false

      boolean

      false

      user

      User to map volume access to Defaults to serivceaccount user

      false

      string

      group

      Group to map volume access to Default is no group

      false

      string

      +

      v1.ContainerStatus

      @@ -8838,68 +5168,6 @@ Examples:
      -
      -
      -

      v1.QuobyteVolumeSource

      -
      -

      Represents a Quobyte mount that lasts the lifetime of a pod. Quobyte volumes do not support ownership management or SELinux relabeling.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      registry

      Registry represents a single or multiple Quobyte Registry services specified as a string as host:port pair (multiple entries are separated with commas) which acts as the central registry for volumes

      true

      string

      volume

      Volume is a string that references an already created Quobyte volume by name.

      true

      string

      readOnly

      ReadOnly here will force the Quobyte volume to be mounted with read-only permissions. Defaults to false.

      false

      boolean

      false

      user

      User to map volume access to Defaults to serivceaccount user

      false

      string

      group

      Group to map volume access to Default is no group

      false

      string

      -

      v1.ContainerImage

      @@ -8945,61 +5213,6 @@ Examples:

      v1.ResourceQuotaScope

      -
      -
      -

      v1.ReplicationControllerList

      -
      -

      ReplicationControllerList is a collection of replication controllers.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      items

      List of replication controllers. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller

      true

      v1.ReplicationController array

      -

      v1.NodeDaemonEndpoints

      @@ -9034,113 +5247,6 @@ Examples:
      -
      -
      -

      v1.Secret

      -
      -

      Secret holds secret data of a certain type. The total bytes of the values in the Data field must be less than MaxSecretSize bytes.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ObjectMeta

      data

      Data contains the secret data. Each key must consist of alphanumeric characters, -, _ or .. The serialized form of the secret data is a base64 encoded string, representing the arbitrary (possibly non-string) data value here. Described in https://tools.ietf.org/html/rfc4648#section-4

      false

      object

      stringData

      stringData allows specifying non-binary secret data in string form. It is provided as a write-only convenience method. All keys and values are merged into the data field on write, overwriting any existing values. It is never output when reading from the API.

      false

      object

      type

      Used to facilitate programmatic handling of secret data.

      false

      string

      - -
      -
      -

      v1.WatchEvent

      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      type

      true

      string

      object

      true

      string

      -

      v1.Event

      @@ -9242,102 +5348,48 @@ Examples:

      string

      - - - -
      -
      -

      v1.EnvVar

      -
      -

      EnvVar represents an environment variable present in a Container.

      -
      - ------- - - - - - - - - - - - - - - - - - - - + + - - + + - + - -
      NameDescriptionRequiredSchemaDefault

      name

      Name of the environment variable. Must be a C_IDENTIFIER.

      true

      string

      value

      Variable references $(VAR_NAME) are expanded using the previous defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".

      eventTime

      Time when this Event was first observed.

      false

      string

      valueFrom

      Source for the environment variable’s value. Cannot be used if value is not empty.

      series

      Data about the Event series this event represents or nil if it’s a singleton Event.

      false

      v1.EnvVarSource

      v1.EventSeries

      - -
      -
      -

      v1.LabelSelectorRequirement

      -
      -

      A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.

      -
      - ------- - - - - - - + + + + + - - - - + + + + + + + + + - - + + - - - - - - -
      NameDescriptionRequiredSchemaDefault

      action

      What action was taken/failed regarding to the Regarding object.

      false

      string

      key

      key is the label key that the selector applies to.

      related

      Optional secondary object for more complex actions.

      false

      v1.ObjectReference

      reportingComponent

      Name of the controller that emitted this Event, e.g. kubernetes.io/kubelet.

      true

      string

      operator

      operator represents a key’s relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.

      reportingInstance

      ID of the controller instance, e.g. kubelet-xyzf.

      true

      string

      values

      values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.

      false

      string array

      @@ -9345,47 +5397,6 @@ Examples:

      v1.PersistentVolumeAccessMode

      -
      -
      -

      v1.ResourceRequirements

      -
      -

      ResourceRequirements describes the compute resource requirements.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      limits

      Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/

      false

      object

      requests

      Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/

      false

      object

      -

      v1.ComponentStatus

      @@ -9482,75 +5493,6 @@ Examples:
      -
      -
      -

      v1.LimitRangeItem

      -
      -

      LimitRangeItem defines a min/max usage limit for any resource that matches on kind.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      type

      Type of resource that this limit applies to.

      false

      string

      max

      Max usage constraints on this kind by resource name.

      false

      object

      min

      Min usage constraints on this kind by resource name.

      false

      object

      default

      Default resource requirement limit value by resource name if resource limit is omitted.

      false

      object

      defaultRequest

      DefaultRequest is the default resource requirement request value by resource name if resource request is omitted.

      false

      object

      maxLimitRequestRatio

      MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource.

      false

      object

      -

      v1.PodTemplateSpec

      @@ -9592,150 +5534,6 @@ Examples:
      -
      -
      -

      v1.PodList

      -
      -

      PodList is a list of Pods.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      items

      List of pods. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md

      true

      v1.Pod array

      - -
      -
      -

      v1.ServiceList

      -
      -

      ServiceList holds a list of services.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      items

      List of services

      true

      v1.Service array

      - -
      -
      -

      v1.NodeSelector

      -
      -

      A node selector represents the union of the results of one or more label queries over a set of nodes; that is, it represents the OR of the selectors represented by the node selector terms.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      nodeSelectorTerms

      Required. A list of node selector terms. The terms are ORed.

      true

      v1.NodeSelectorTerm array

      -

      v1.PersistentVolumeList

      @@ -9793,18 +5591,9 @@ Examples:
      -

      v1.Patch

      +

      v1.NodeSelector

      -

      Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.

      -
      -
      -
      -

      v1.ConfigMapEnvSource

      -
      -

      ConfigMapEnvSource selects a ConfigMap to populate the environment variables with.

      -
      -
      -

      The contents of the target ConfigMap’s Data field will represent the key-value pairs as environment variables.

      +

      A node selector represents the union of the results of one or more label queries over a set of nodes; that is, it represents the OR of the selectors represented by the node selector terms.

      @@ -9825,22 +5614,21 @@ Examples:
      - - - - + + + + - - - - - - -

      name

      Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      false

      string

      nodeSelectorTerms

      Required. A list of node selector terms. The terms are ORed.

      true

      v1.NodeSelectorTerm array

      optional

      Specify whether the ConfigMap must be defined

      false

      boolean

      false

      +
      +
      +

      v1.Patch

      +
      +

      Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.

      +

      v1.StorageOSVolumeSource

      @@ -9903,123 +5691,6 @@ Examples:
      -
      -
      -

      v1.ObjectReference

      -
      -

      ObjectReference contains enough information to let you inspect or modify the referred object.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      string

      namespace

      Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/

      false

      string

      name

      Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      false

      string

      uid

      UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids

      false

      string

      apiVersion

      API version of the referent.

      false

      string

      resourceVersion

      Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency

      false

      string

      fieldPath

      If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object.

      false

      string

      - -
      -
      -

      v1.ContainerStateWaiting

      -
      -

      ContainerStateWaiting is a waiting state of a container.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      reason

      (brief) reason the container is not yet running.

      false

      string

      message

      Message regarding why the container is not yet running.

      false

      string

      -

      v1.NodeAffinity

      @@ -10061,6 +5732,10 @@ Examples:
      +
      +
      +

      v1.AzureDataDiskKind

      +

      v1.PreferredSchedulingTerm

      @@ -10102,10 +5777,6 @@ Examples:
      -
      -
      -

      v1.AzureDataDiskKind

      -

      v1.NodeConfigSource

      @@ -10381,7 +6052,7 @@ Examples:

      externalName

      -

      externalName is the external reference that kubedns or equivalent will return as a CNAME record for this service. No proxying will be involved. Must be a valid DNS name and requires Type to be ExternalName.

      +

      externalName is the external reference that kubedns or equivalent will return as a CNAME record for this service. No proxying will be involved. Must be a valid RFC-1123 hostname (https://tools.ietf.org/html/rfc1123) and requires Type to be ExternalName.

      false

      string

      @@ -10534,6 +6205,5043 @@ Examples:
      +
      +
      +

      v1.APIResourceList

      +
      +

      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.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      groupVersion

      groupVersion is the group and version this APIResourceList is for.

      true

      string

      resources

      resources contains the name of the resources and if they are namespaced.

      true

      v1.APIResource array

      + +
      +
      +

      v1.Node

      +
      +

      Node is a worker node in Kubernetes. Each node will have a unique identifier in the cache (i.e. in etcd).

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ObjectMeta

      spec

      Spec defines the behavior of a node. https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      v1.NodeSpec

      status

      Most recently observed status of the node. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      v1.NodeStatus

      + +
      +
      +

      v1.Affinity

      +
      +

      Affinity is a group of affinity scheduling rules.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      nodeAffinity

      Describes node affinity scheduling rules for the pod.

      false

      v1.NodeAffinity

      podAffinity

      Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)).

      false

      v1.PodAffinity

      podAntiAffinity

      Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)).

      false

      v1.PodAntiAffinity

      + +
      +
      +

      v1.PersistentVolumeClaimList

      +
      +

      PersistentVolumeClaimList is a list of PersistentVolumeClaim items.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      items

      A list of persistent volume claims. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims

      true

      v1.PersistentVolumeClaim array

      + +
      +
      +

      v1.LocalVolumeSource

      +
      +

      Local represents directly-attached storage with node affinity

      +
      + +++++++ + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      path

      The full path to the volume on the node For alpha, this path must be a directory Once block as a source is supported, then this path can point to a block device

      true

      string

      + +
      +
      +

      v1.NodeSelectorTerm

      +
      +

      A null or empty node selector term matches no objects.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      matchExpressions

      Required. A list of node selector requirements. The requirements are ANDed.

      true

      v1.NodeSelectorRequirement array

      + +
      +
      +

      v1.SELinuxOptions

      +
      +

      SELinuxOptions are the labels to be applied to the container

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      user

      User is a SELinux user label that applies to the container.

      false

      string

      role

      Role is a SELinux role label that applies to the container.

      false

      string

      type

      Type is a SELinux type label that applies to the container.

      false

      string

      level

      Level is SELinux level label that applies to the container.

      false

      string

      + +
      +
      +

      v1.VolumeMount

      +
      +

      VolumeMount describes a mounting of a Volume within a container.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      This must match the Name of a Volume.

      true

      string

      readOnly

      Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false.

      false

      boolean

      false

      mountPath

      Path within the container at which the volume should be mounted. Must not contain :.

      true

      string

      subPath

      Path within the volume from which the container’s volume should be mounted. Defaults to "" (volume’s root).

      false

      string

      mountPropagation

      mountPropagation determines how mounts are propagated from the host to container and the other way around. When not set, MountPropagationHostToContainer is used. This field is alpha in 1.8 and can be reworked or removed in a future release.

      false

      v1.MountPropagationMode

      + +
      +
      +

      v1.DownwardAPIProjection

      +
      +

      Represents downward API info for projecting into a projected volume. Note that this is identical to a downwardAPI volume source without the default mode.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      items

      Items is a list of DownwardAPIVolume file

      false

      v1.DownwardAPIVolumeFile array

      + +
      +
      +

      v1.CephFSVolumeSource

      +
      +

      Represents a Ceph Filesystem mount that lasts the lifetime of a pod Cephfs volumes do not support ownership management or SELinux relabeling.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      monitors

      Required: Monitors is a collection of Ceph monitors More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it

      true

      string array

      path

      Optional: Used as the mounted root, rather than the full Ceph tree, default is /

      false

      string

      user

      Optional: User is the rados user name, default is admin More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it

      false

      string

      secretFile

      Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it

      false

      string

      secretRef

      Optional: SecretRef is reference to the authentication secret for User, default is empty. More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it

      false

      v1.LocalObjectReference

      readOnly

      Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it

      false

      boolean

      false

      + +
      +
      +

      v1.NamespaceStatus

      +
      +

      NamespaceStatus is information about the current status of a Namespace.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      phase

      Phase is the current lifecycle phase of the namespace. More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/

      false

      string

      + +
      +
      +

      v1.ResourceQuotaSpec

      +
      +

      ResourceQuotaSpec defines the desired hard limits to enforce for Quota.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      hard

      Hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/

      false

      object

      scopes

      A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.

      false

      v1.ResourceQuotaScope array

      + +
      +
      +

      v1.NamespaceSpec

      +
      +

      NamespaceSpec describes the attributes on a Namespace.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      finalizers

      Finalizers is an opaque list of values that must be empty to permanently remove object from storage. More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/

      false

      v1.FinalizerName array

      + +
      +
      +

      v1.ConfigMapVolumeSource

      +
      +

      Adapts a ConfigMap into a volume.

      +
      +
      +

      The contents of the target ConfigMap’s Data field will be presented in a volume as files using the keys in the Data field as the file names, unless the items element is populated with specific mappings of keys to paths. ConfigMap volumes support ownership management and SELinux relabeling.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      false

      string

      items

      If unspecified, each key-value pair in the Data field of the referenced ConfigMap will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the ConfigMap, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the .. path or start with ...

      false

      v1.KeyToPath array

      defaultMode

      Optional: mode bits to use on created files by default. Must be a value between 0 and 0777. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.

      false

      integer (int32)

      optional

      Specify whether the ConfigMap or it’s keys must be defined

      false

      boolean

      false

      + +
      +
      +

      v1.FlexPersistentVolumeSource

      +
      +

      FlexPersistentVolumeSource represents a generic persistent volume resource that is provisioned/attached using an exec based plugin.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      driver

      Driver is the name of the driver to use for this volume.

      true

      string

      fsType

      Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script.

      false

      string

      secretRef

      Optional: SecretRef is reference to the secret object containing sensitive information to pass to the plugin scripts. This may be empty if no secret object is specified. If the secret object contains more than one secret, all secrets are passed to the plugin scripts.

      false

      v1.SecretReference

      readOnly

      Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.

      false

      boolean

      false

      options

      Optional: Extra command options if any.

      false

      object

      + +
      +
      +

      v1.EndpointsList

      +
      +

      EndpointsList is a list of endpoints.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      items

      List of endpoints.

      true

      v1.Endpoints array

      + +
      +
      +

      v1.SecretEnvSource

      +
      +

      SecretEnvSource selects a Secret to populate the environment variables with.

      +
      +
      +

      The contents of the target Secret’s Data field will represent the key-value pairs as environment variables.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      false

      string

      optional

      Specify whether the Secret must be defined

      false

      boolean

      false

      + +
      +
      +

      v1.ReplicationControllerCondition

      +
      +

      ReplicationControllerCondition describes the state of a replication controller at a certain point.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      type

      Type of replication controller condition.

      true

      string

      status

      Status of the condition, one of True, False, Unknown.

      true

      string

      lastTransitionTime

      The last time the condition transitioned from one status to another.

      false

      string

      reason

      The reason for the condition’s last transition.

      false

      string

      message

      A human readable message indicating details about the transition.

      false

      string

      + +
      +
      +

      v1.ScaleStatus

      +
      +

      ScaleStatus represents the current status of a scale subresource.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      replicas

      actual number of observed instances of the scaled object.

      true

      integer (int32)

      selector

      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: http://kubernetes.io/docs/user-guide/labels#label-selectors

      false

      string

      + +
      +
      +

      v1.NodeCondition

      +
      +

      NodeCondition contains condition information for a node.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      type

      Type of node condition.

      true

      string

      status

      Status of the condition, one of True, False, Unknown.

      true

      string

      lastHeartbeatTime

      Last time we got an update on a given condition.

      false

      string

      lastTransitionTime

      Last time the condition transit from one status to another.

      false

      string

      reason

      (brief) reason for the condition’s last transition.

      false

      string

      message

      Human readable message indicating details about last transition.

      false

      string

      + +
      +
      +

      v1.LocalObjectReference

      +
      +

      LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      false

      string

      + +
      +
      +

      v1.ResourceQuotaStatus

      +
      +

      ResourceQuotaStatus defines the enforced hard limits and observed use.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      hard

      Hard is the set of enforced hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/

      false

      object

      used

      Used is the current observed total usage of the resource in the namespace.

      false

      object

      + +
      +
      +

      v1.SecretReference

      +
      +

      SecretReference represents a Secret Reference. It has enough information to retrieve secret in any namespace

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      Name is unique within a namespace to reference a secret resource.

      false

      string

      namespace

      Namespace defines the space within which the secret name must be unique.

      false

      string

      + +
      +
      +

      v1.LimitRangeSpec

      +
      +

      LimitRangeSpec defines a min/max usage limit for resources that match on kind.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      limits

      Limits is the list of LimitRangeItem objects that are enforced.

      true

      v1.LimitRangeItem array

      + +
      +
      +

      types.UID

      + +
      +
      +

      v1.AzureFileVolumeSource

      +
      +

      AzureFile represents an Azure File Service mount on the host and bind mount to the pod.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      secretName

      the name of secret that contains Azure Storage Account Name and Key

      true

      string

      shareName

      Share Name

      true

      string

      readOnly

      Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.

      false

      boolean

      false

      + +
      +
      +

      v1.ISCSIVolumeSource

      +
      +

      Represents an ISCSI disk. ISCSI volumes can only be mounted as read/write once. ISCSI volumes support ownership management and SELinux relabeling.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      targetPortal

      iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      true

      string

      iqn

      Target iSCSI Qualified Name.

      true

      string

      lun

      iSCSI Target Lun number.

      true

      integer (int32)

      iscsiInterface

      iSCSI Interface Name that uses an iSCSI transport. Defaults to default (tcp).

      false

      string

      fsType

      Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi

      false

      string

      readOnly

      ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false.

      false

      boolean

      false

      portals

      iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      false

      string array

      chapAuthDiscovery

      whether support iSCSI Discovery CHAP authentication

      false

      boolean

      false

      chapAuthSession

      whether support iSCSI Session CHAP authentication

      false

      boolean

      false

      secretRef

      CHAP Secret for iSCSI target and initiator authentication

      false

      v1.LocalObjectReference

      initiatorName

      Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface <target portal>:<volume name> will be created for the connection.

      false

      string

      + +
      +
      +

      v1.PodAffinityTerm

      +
      +

      Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key <topologyKey> matches that of any node on which a pod of the set of pods is running

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      labelSelector

      A label query over a set of resources, in this case pods.

      false

      v1.LabelSelector

      namespaces

      namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means "this pod’s namespace"

      false

      string array

      topologyKey

      This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.

      true

      string

      + +
      +
      +

      v1.PersistentVolumeClaim

      +
      +

      PersistentVolumeClaim is a user’s request for and claim to a persistent volume

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ObjectMeta

      spec

      Spec defines the desired characteristics of a volume requested by a pod author. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims

      false

      v1.PersistentVolumeClaimSpec

      status

      Status represents the current information/status of a persistent volume claim. Read-only. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims

      false

      v1.PersistentVolumeClaimStatus

      + +
      +
      +

      v1.ServiceAccount

      +
      +

      ServiceAccount binds together: * a name, understood by users, and perhaps by peripheral systems, for an identity * a principal that can be authenticated and authorized * a set of secrets

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ObjectMeta

      secrets

      Secrets is the list of secrets allowed to be used by pods running using this ServiceAccount. More info: https://kubernetes.io/docs/concepts/configuration/secret

      false

      v1.ObjectReference array

      imagePullSecrets

      ImagePullSecrets is a list of references to secrets in the same namespace to use for pulling any images in pods that reference this ServiceAccount. ImagePullSecrets are distinct from Secrets because Secrets can be mounted in the pod, but ImagePullSecrets are only accessed by the kubelet. More info: https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod

      false

      v1.LocalObjectReference array

      automountServiceAccountToken

      AutomountServiceAccountToken indicates whether pods running as this service account should have an API token automatically mounted. Can be overridden at the pod level.

      false

      boolean

      false

      + +
      +
      +

      v1.PersistentVolumeClaimStatus

      +
      +

      PersistentVolumeClaimStatus is the current status of a persistent volume claim.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      phase

      Phase represents the current phase of PersistentVolumeClaim.

      false

      string

      accessModes

      AccessModes contains the actual access modes the volume backing the PVC has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1

      false

      v1.PersistentVolumeAccessMode array

      capacity

      Represents the actual resources of the underlying volume.

      false

      object

      conditions

      Current Condition of persistent volume claim. If underlying persistent volume is being resized then the Condition will be set to ResizeStarted.

      false

      v1.PersistentVolumeClaimCondition array

      + +
      +
      +

      v1.ResourceQuotaList

      +
      +

      ResourceQuotaList is a list of ResourceQuota items.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      items

      Items is a list of ResourceQuota objects. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/

      true

      v1.ResourceQuota array

      + +
      +
      +

      v1.UniqueVolumeName

      + +
      +
      +

      v1.CSIPersistentVolumeSource

      +
      +

      Represents storage that is managed by an external CSI volume driver

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      driver

      Driver is the name of the driver to use for this volume. Required.

      true

      string

      volumeHandle

      VolumeHandle is the unique volume name returned by the CSI volume plugin’s CreateVolume to refer to the volume on all subsequent calls. Required.

      true

      string

      readOnly

      Optional: The value to pass to ControllerPublishVolumeRequest. Defaults to false (read/write).

      false

      boolean

      false

      + +
      +
      +

      v1.EnvVarSource

      +
      +

      EnvVarSource represents a source for the value of an EnvVar.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      fieldRef

      Selects a field of the pod: supports metadata.name, metadata.namespace, metadata.labels, metadata.annotations, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP.

      false

      v1.ObjectFieldSelector

      resourceFieldRef

      Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.

      false

      v1.ResourceFieldSelector

      configMapKeyRef

      Selects a key of a ConfigMap.

      false

      v1.ConfigMapKeySelector

      secretKeyRef

      Selects a key of a secret in the pod’s namespace

      false

      v1.SecretKeySelector

      + +
      +
      +

      v1.AzureDiskVolumeSource

      +
      +

      AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      diskName

      The Name of the data disk in the blob storage

      true

      string

      diskURI

      The URI the data disk in the blob storage

      true

      string

      cachingMode

      Host Caching mode: None, Read Only, Read Write.

      false

      v1.AzureDataDiskCachingMode

      fsType

      Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.

      false

      string

      readOnly

      Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.

      false

      boolean

      false

      kind

      Expected values Shared: multiple blob disks per storage account Dedicated: single blob disk per storage account Managed: azure managed data disk (only in managed availability set). defaults to shared

      false

      v1.AzureDataDiskKind

      + +
      +
      +

      v1.KeyToPath

      +
      +

      Maps a string key to a path within a volume.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      key

      The key to project.

      true

      string

      path

      The relative path of the file to map the key to. May not be an absolute path. May not contain the path element ... May not start with the string ...

      true

      string

      mode

      Optional: mode bits to use on this file, must be a value between 0 and 0777. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.

      false

      integer (int32)

      + +
      +
      +

      v1.VsphereVirtualDiskVolumeSource

      +
      +

      Represents a vSphere volume resource.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      volumePath

      Path that identifies vSphere volume vmdk

      true

      string

      fsType

      Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.

      false

      string

      storagePolicyName

      Storage Policy Based Management (SPBM) profile name.

      false

      string

      storagePolicyID

      Storage Policy Based Management (SPBM) profile ID associated with the StoragePolicyName.

      false

      string

      + +
      +
      +

      v1.LimitRangeList

      +
      +

      LimitRangeList is a list of LimitRange items.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      items

      Items is a list of LimitRange objects. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/

      true

      v1.LimitRange array

      + +
      +
      +

      v1.ServiceAccountList

      +
      +

      ServiceAccountList is a list of ServiceAccount objects

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      items

      List of ServiceAccounts. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/

      true

      v1.ServiceAccount array

      + +
      +
      +

      v1.Endpoints

      +
      +

      Endpoints is a collection of endpoints that implement the actual service. Example:
      + Name: "mysvc",
      + Subsets: [
      + {
      + Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}],
      + Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}]
      + },
      + {
      + Addresses: [{"ip": "10.10.3.3"}],
      + Ports: [{"name": "a", "port": 93}, {"name": "b", "port": 76}]
      + },
      + ]

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ObjectMeta

      subsets

      The set of all endpoints is the union of all subsets. Addresses are placed into subsets according to the IPs they share. A single address with multiple ports, some of which are ready and some of which are not (because they come from different containers) will result in the address being displayed in different subsets for the different ports. No address will appear in both Addresses and NotReadyAddresses in the same subset. Sets of addresses and ports that comprise a service.

      true

      v1.EndpointSubset array

      + +
      +
      +

      v1.DeleteOptions

      +
      +

      DeleteOptions may be provided when deleting an API object.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      gracePeriodSeconds

      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.

      false

      integer (int64)

      preconditions

      Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.

      false

      v1.Preconditions

      orphanDependents

      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.

      false

      boolean

      false

      propagationPolicy

      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.

      false

      v1.DeletionPropagation

      + +
      +
      +

      v1.Volume

      +
      +

      Volume represents a named volume in a pod that may be accessed by any container in the pod.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      Volume’s name. Must be a DNS_LABEL and unique within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      true

      string

      hostPath

      HostPath represents a pre-existing file or directory on the host machine that is directly exposed to the container. This is generally used for system agents or other privileged things that are allowed to see the host machine. Most containers will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath

      false

      v1.HostPathVolumeSource

      emptyDir

      EmptyDir represents a temporary directory that shares a pod’s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir

      false

      v1.EmptyDirVolumeSource

      gcePersistentDisk

      GCEPersistentDisk represents a GCE Disk resource that is attached to a kubelet’s host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk

      false

      v1.GCEPersistentDiskVolumeSource

      awsElasticBlockStore

      AWSElasticBlockStore represents an AWS Disk resource that is attached to a kubelet’s host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore

      false

      v1.AWSElasticBlockStoreVolumeSource

      gitRepo

      GitRepo represents a git repository at a particular revision.

      false

      v1.GitRepoVolumeSource

      secret

      Secret represents a secret that should populate this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret

      false

      v1.SecretVolumeSource

      nfs

      NFS represents an NFS mount on the host that shares a pod’s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs

      false

      v1.NFSVolumeSource

      iscsi

      ISCSI represents an ISCSI Disk resource that is attached to a kubelet’s host machine and then exposed to the pod. More info: https://releases.k8s.io/HEAD/examples/volumes/iscsi/README.md

      false

      v1.ISCSIVolumeSource

      glusterfs

      Glusterfs represents a Glusterfs mount on the host that shares a pod’s lifetime. More info: https://releases.k8s.io/HEAD/examples/volumes/glusterfs/README.md

      false

      v1.GlusterfsVolumeSource

      persistentVolumeClaim

      PersistentVolumeClaimVolumeSource represents a reference to a PersistentVolumeClaim in the same namespace. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims

      false

      v1.PersistentVolumeClaimVolumeSource

      rbd

      RBD represents a Rados Block Device mount on the host that shares a pod’s lifetime. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md

      false

      v1.RBDVolumeSource

      flexVolume

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.

      false

      v1.FlexVolumeSource

      cinder

      Cinder represents a cinder volume attached and mounted on kubelets host machine More info: https://releases.k8s.io/HEAD/examples/mysql-cinder-pd/README.md

      false

      v1.CinderVolumeSource

      cephfs

      CephFS represents a Ceph FS mount on the host that shares a pod’s lifetime

      false

      v1.CephFSVolumeSource

      flocker

      Flocker represents a Flocker volume attached to a kubelet’s host machine. This depends on the Flocker control service being running

      false

      v1.FlockerVolumeSource

      downwardAPI

      DownwardAPI represents downward API about the pod that should populate this volume

      false

      v1.DownwardAPIVolumeSource

      fc

      FC represents a Fibre Channel resource that is attached to a kubelet’s host machine and then exposed to the pod.

      false

      v1.FCVolumeSource

      azureFile

      AzureFile represents an Azure File Service mount on the host and bind mount to the pod.

      false

      v1.AzureFileVolumeSource

      configMap

      ConfigMap represents a configMap that should populate this volume

      false

      v1.ConfigMapVolumeSource

      vsphereVolume

      VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine

      false

      v1.VsphereVirtualDiskVolumeSource

      quobyte

      Quobyte represents a Quobyte mount on the host that shares a pod’s lifetime

      false

      v1.QuobyteVolumeSource

      azureDisk

      AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod.

      false

      v1.AzureDiskVolumeSource

      photonPersistentDisk

      PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine

      false

      v1.PhotonPersistentDiskVolumeSource

      projected

      Items for all in one resources secrets, configmaps, and downward API

      false

      v1.ProjectedVolumeSource

      portworxVolume

      PortworxVolume represents a portworx volume attached and mounted on kubelets host machine

      false

      v1.PortworxVolumeSource

      scaleIO

      ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.

      false

      v1.ScaleIOVolumeSource

      storageos

      StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes.

      false

      v1.StorageOSVolumeSource

      + +
      +
      +

      v1.VolumeProjection

      +
      +

      Projection that may be projected along with other supported volume types

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      secret

      information about the secret data to project

      false

      v1.SecretProjection

      downwardAPI

      information about the downwardAPI data to project

      false

      v1.DownwardAPIProjection

      configMap

      information about the configMap data to project

      false

      v1.ConfigMapProjection

      + +
      +
      +

      v1.CephFSPersistentVolumeSource

      +
      +

      Represents a Ceph Filesystem mount that lasts the lifetime of a pod Cephfs volumes do not support ownership management or SELinux relabeling.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      monitors

      Required: Monitors is a collection of Ceph monitors More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it

      true

      string array

      path

      Optional: Used as the mounted root, rather than the full Ceph tree, default is /

      false

      string

      user

      Optional: User is the rados user name, default is admin More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it

      false

      string

      secretFile

      Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it

      false

      string

      secretRef

      Optional: SecretRef is reference to the authentication secret for User, default is empty. More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it

      false

      v1.SecretReference

      readOnly

      Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it

      false

      boolean

      false

      + +
      +
      +

      v1.Probe

      +
      +

      Probe describes a health check to be performed against a container to determine whether it is alive or ready to receive traffic.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      exec

      One and only one of the following should be specified. Exec specifies the action to take.

      false

      v1.ExecAction

      httpGet

      HTTPGet specifies the http request to perform.

      false

      v1.HTTPGetAction

      tcpSocket

      TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported

      false

      v1.TCPSocketAction

      initialDelaySeconds

      Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes

      false

      integer (int32)

      timeoutSeconds

      Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes

      false

      integer (int32)

      periodSeconds

      How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1.

      false

      integer (int32)

      successThreshold

      Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness. Minimum value is 1.

      false

      integer (int32)

      failureThreshold

      Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1.

      false

      integer (int32)

      + +
      +
      +

      v1.SecretKeySelector

      +
      +

      SecretKeySelector selects a key of a Secret.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      false

      string

      key

      The key of the secret to select from. Must be a valid secret key.

      true

      string

      optional

      Specify whether the Secret or it’s key must be defined

      false

      boolean

      false

      + +
      +
      +

      v1.Capability

      + +
      +
      +

      v1.ReplicationController

      +
      +

      ReplicationController represents the configuration of a replication controller.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      If the Labels of a ReplicationController are empty, they are defaulted to 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/api-conventions.md#metadata

      false

      v1.ObjectMeta

      spec

      Spec defines the specification of the desired behavior of the replication controller. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      v1.ReplicationControllerSpec

      status

      Status is the most recently observed status of the replication controller. This data may be out of date by some window of time. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      v1.ReplicationControllerStatus

      + +
      +
      +

      v1.LimitRange

      +
      +

      LimitRange sets resource usage limits for each kind of resource in a Namespace.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ObjectMeta

      spec

      Spec defines the limits enforced. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      v1.LimitRangeSpec

      + +
      +
      +

      v1.DownwardAPIVolumeFile

      +
      +

      DownwardAPIVolumeFile represents information to create the file containing the pod field

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      path

      Required: Path is the relative path name of the file to be created. Must not be absolute or contain the .. path. Must be utf-8 encoded. The first item of the relative path must not start with ..

      true

      string

      fieldRef

      Required: Selects a field of the pod: only annotations, labels, name and namespace are supported.

      false

      v1.ObjectFieldSelector

      resourceFieldRef

      Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported.

      false

      v1.ResourceFieldSelector

      mode

      Optional: mode bits to use on this file, must be a value between 0 and 0777. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.

      false

      integer (int32)

      + +
      +
      +

      v1.PodStatus

      +
      +

      PodStatus represents information about the status of a pod. Status may trail the actual state of a system.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      phase

      Current condition of the pod. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-phase

      false

      string

      conditions

      Current service state of pod. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions

      false

      v1.PodCondition array

      message

      A human readable message indicating details about why the pod is in this condition.

      false

      string

      reason

      A brief CamelCase message indicating details about why the pod is in this state. e.g. Evicted

      false

      string

      hostIP

      IP address of the host to which the pod is assigned. Empty if not yet scheduled.

      false

      string

      podIP

      IP address allocated to the pod. Routable at least within the cluster. Empty if not yet allocated.

      false

      string

      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.

      false

      string

      initContainerStatuses

      The list has one entry per init container in the manifest. The most recent successful init container will have ready = true, the most recently started container will have startTime set. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-and-container-status

      false

      v1.ContainerStatus array

      containerStatuses

      The list has one entry per container in the manifest. Each entry is currently the output of docker inspect. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-and-container-status

      false

      v1.ContainerStatus array

      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://github.com/kubernetes/kubernetes/blob/master/docs/design/resource-qos.md

      false

      string

      + +
      +
      +

      v1.PodSpec

      +
      +

      PodSpec is a description of a pod.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      volumes

      List of volumes that can be mounted by containers belonging to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes

      false

      v1.Volume array

      initContainers

      List of initialization containers belonging to the pod. Init containers are executed in order prior to containers being started. If any init container fails, the pod is considered to have failed and is handled according to its restartPolicy. The name for an init container or normal container must be unique among all containers. Init containers may not have Lifecycle actions, Readiness probes, or Liveness probes. The resourceRequirements of an init container are taken into account during scheduling by finding the highest request/limit for each resource type, and then using the max of of that value or the sum of the normal containers. Limits are applied to init containers in a similar fashion. Init containers cannot currently be added or removed. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/

      false

      v1.Container array

      containers

      List of containers belonging to the pod. Containers cannot currently be added or removed. There must be at least one container in a Pod. Cannot be updated.

      true

      v1.Container array

      restartPolicy

      Restart policy for all containers within the pod. One of Always, OnFailure, Never. Default to Always. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy

      false

      string

      terminationGracePeriodSeconds

      Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period will be used instead. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. Defaults to 30 seconds.

      false

      integer (int64)

      activeDeadlineSeconds

      Optional duration in seconds the pod may be active on the node relative to StartTime before the system will actively try to mark it failed and kill associated containers. Value must be a positive integer.

      false

      integer (int64)

      dnsPolicy

      Set DNS policy for the pod. Defaults to "ClusterFirst". Valid values are ClusterFirstWithHostNet, ClusterFirst, Default or None. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to ClusterFirstWithHostNet. Note that None policy is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it.

      false

      string

      nodeSelector

      NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node’s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/

      false

      object

      serviceAccountName

      ServiceAccountName is the name of the ServiceAccount to use to run this pod. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/

      false

      string

      serviceAccount

      DeprecatedServiceAccount is a depreciated alias for ServiceAccountName. Deprecated: Use serviceAccountName instead.

      false

      string

      automountServiceAccountToken

      AutomountServiceAccountToken indicates whether a service account token should be automatically mounted.

      false

      boolean

      false

      nodeName

      NodeName is a request to schedule this pod onto a specific node. If it is non-empty, the scheduler simply schedules this pod onto that node, assuming that it fits resource requirements.

      false

      string

      hostNetwork

      Host networking requested for this pod. Use the host’s network namespace. If this option is set, the ports that will be used must be specified. Default to false.

      false

      boolean

      false

      hostPID

      Use the host’s pid namespace. Optional: Default to false.

      false

      boolean

      false

      hostIPC

      Use the host’s ipc namespace. Optional: Default to false.

      false

      boolean

      false

      securityContext

      SecurityContext holds pod-level security attributes and common container settings. Optional: Defaults to empty. See type description for default values of each field.

      false

      v1.PodSecurityContext

      imagePullSecrets

      ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. If specified, these secrets will be passed to individual puller implementations for them to use. For example, in the case of docker, only DockerConfig type secrets are honored. More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod

      false

      v1.LocalObjectReference array

      hostname

      Specifies the hostname of the Pod If not specified, the pod’s hostname will be set to a system-defined value.

      false

      string

      subdomain

      If specified, the fully qualified Pod hostname will be "<hostname>.<subdomain>.<pod namespace>.svc.<cluster domain>". If not specified, the pod will not have a domainname at all.

      false

      string

      affinity

      If specified, the pod’s scheduling constraints

      false

      v1.Affinity

      schedulerName

      If specified, the pod will be dispatched by specified scheduler. If not specified, the pod will be dispatched by default scheduler.

      false

      string

      tolerations

      If specified, the pod’s tolerations.

      false

      v1.Toleration array

      hostAliases

      HostAliases is an optional list of hosts and IPs that will be injected into the pod’s hosts file if specified. This is only valid for non-hostNetwork pods.

      false

      v1.HostAlias array

      priorityClassName

      If specified, indicates the pod’s priority. "SYSTEM" is a special keyword which indicates the highest priority. Any other name must be defined by creating a PriorityClass object with that name. If not specified, the pod priority will be default or zero if there is no default.

      false

      string

      priority

      The priority value. Various system components use this field to find the priority of the pod. 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.

      false

      integer (int32)

      dnsConfig

      Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy. This is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it.

      false

      v1.PodDNSConfig

      + +
      +
      +

      v1.EventList

      +
      +

      EventList is a list of events.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      items

      List of events

      true

      v1.Event array

      + +
      +
      +

      v1.Lifecycle

      +
      +

      Lifecycle describes actions that the management system should take in response to container lifecycle events. For the PostStart and PreStop lifecycle handlers, management of the container blocks until the action is complete, unless the container process fails, in which case the handler is aborted.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      postStart

      PostStart is called immediately after a container is created. If the handler fails, the container is terminated and restarted according to its restart policy. Other management of the container blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks

      false

      v1.Handler

      preStop

      PreStop is called immediately before a container is terminated. The container is terminated after the handler completes. The reason for termination is passed to the handler. Regardless of the outcome of the handler, the container is eventually terminated. Other management of the container blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks

      false

      v1.Handler

      + +
      +
      +

      v1.AttachedVolume

      +
      +

      AttachedVolume describes a volume attached to a node

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      Name of the attached volume

      true

      string

      devicePath

      DevicePath represents the device path where the volume should be available

      true

      string

      + +
      +
      +

      v1.PodCondition

      +
      +

      PodCondition contains details for the current condition of this pod.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      type

      Type is the type of the condition. Currently only Ready. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions

      true

      string

      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

      true

      string

      lastProbeTime

      Last time we probed the condition.

      false

      string

      lastTransitionTime

      Last time the condition transitioned from one status to another.

      false

      string

      reason

      Unique, one-word, CamelCase reason for the condition’s last transition.

      false

      string

      message

      Human-readable message indicating details about last transition.

      false

      string

      + +
      +
      +

      v1.RBDVolumeSource

      +
      +

      Represents a Rados Block Device mount that lasts the lifetime of a pod. RBD volumes support ownership management and SELinux relabeling.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      monitors

      A collection of Ceph monitors. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      true

      string array

      image

      The rados image name. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      true

      string

      fsType

      Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd

      false

      string

      pool

      The rados pool name. Default is rbd. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      false

      string

      user

      The rados user name. Default is admin. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      false

      string

      keyring

      Keyring is the path to key ring for RBDUser. Default is /etc/ceph/keyring. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      false

      string

      secretRef

      SecretRef is name of the authentication secret for RBDUser. If provided overrides keyring. Default is nil. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      false

      v1.LocalObjectReference

      readOnly

      ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      false

      boolean

      false

      + +
      +
      +

      v1.PhotonPersistentDiskVolumeSource

      +
      +

      Represents a Photon Controller persistent disk resource.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      pdID

      ID that identifies Photon Controller persistent disk

      true

      string

      fsType

      Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.

      false

      string

      + +
      +
      +

      v1.HostPathType

      + +
      +
      +

      v1.Status

      +
      +

      Status is a return value for calls that don’t return other objects.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      status

      Status of the operation. One of: "Success" or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      string

      message

      A human-readable description of the status of this operation.

      false

      string

      reason

      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.

      false

      string

      details

      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.

      false

      v1.StatusDetails

      code

      Suggested HTTP return code for this status, 0 if not set.

      false

      integer (int32)

      + +
      +
      +

      v1.PodDNSConfig

      +
      +

      PodDNSConfig defines the DNS parameters of a pod in addition to those generated from DNSPolicy.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      nameservers

      A list of DNS name server IP addresses. This will be appended to the base nameservers generated from DNSPolicy. Duplicated nameservers will be removed.

      false

      string array

      searches

      A list of DNS search domains for host-name lookup. This will be appended to the base search paths generated from DNSPolicy. Duplicated search paths will be removed.

      false

      string array

      options

      A list of DNS resolver options. This will be merged with the base options generated from DNSPolicy. Duplicated entries will be removed. Resolution options given in Options will override those that appear in the base DNSPolicy.

      false

      v1.PodDNSConfigOption array

      + +
      +
      +

      v1.FCVolumeSource

      +
      +

      Represents a Fibre Channel volume. Fibre Channel volumes can only be mounted as read/write once. Fibre Channel volumes support ownership management and SELinux relabeling.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      targetWWNs

      Optional: FC target worldwide names (WWNs)

      false

      string array

      lun

      Optional: FC target lun number

      false

      integer (int32)

      fsType

      Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.

      false

      string

      readOnly

      Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.

      false

      boolean

      false

      wwids

      Optional: FC volume world wide identifiers (wwids) Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously.

      false

      string array

      + +
      +
      +

      v1.DeletionPropagation

      + +
      +
      +

      v1.TCPSocketAction

      +
      +

      TCPSocketAction describes an action based on opening a socket

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      port

      Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME.

      true

      string

      host

      Optional: Host name to connect to, defaults to the pod IP.

      false

      string

      + +
      +
      +

      v1.LoadBalancerStatus

      +
      +

      LoadBalancerStatus represents the status of a load-balancer.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      ingress

      Ingress is a list containing ingress points for the load-balancer. Traffic intended for the service should be sent to these ingress points.

      false

      v1.LoadBalancerIngress array

      + +
      +
      +

      v1.Container

      +
      +

      A single application container that you want to run within a pod.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      Name of the container specified as a DNS_LABEL. Each container in a pod must have a unique name (DNS_LABEL). Cannot be updated.

      true

      string

      image

      Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images This field is optional to allow higher level config management to default or override container images in workload controllers like Deployments and StatefulSets.

      false

      string

      command

      Entrypoint array. Not executed within a shell. The docker image’s ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container’s environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell

      false

      string array

      args

      Arguments to the entrypoint. The docker image’s CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container’s environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell

      false

      string array

      workingDir

      Container’s working directory. If not specified, the container runtime’s default will be used, which might be configured in the container image. Cannot be updated.

      false

      string

      ports

      List of ports to expose from the container. Exposing a port here gives the system additional information about the network connections a container uses, but is primarily informational. Not specifying a port here DOES NOT prevent that port from being exposed. Any port which is listening on the default "0.0.0.0" address inside a container will be accessible from the network. Cannot be updated.

      false

      v1.ContainerPort array

      envFrom

      List of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. 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. Cannot be updated.

      false

      v1.EnvFromSource array

      env

      List of environment variables to set in the container. Cannot be updated.

      false

      v1.EnvVar array

      resources

      Compute Resources required by this container. Cannot be updated. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources

      false

      v1.ResourceRequirements

      volumeMounts

      Pod volumes to mount into the container’s filesystem. Cannot be updated.

      false

      v1.VolumeMount array

      volumeDevices

      volumeDevices is the list of block devices to be used by the container. This is an alpha feature and may change in the future.

      false

      v1.VolumeDevice array

      livenessProbe

      Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes

      false

      v1.Probe

      readinessProbe

      Periodic probe of container service readiness. Container will be removed from service endpoints if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes

      false

      v1.Probe

      lifecycle

      Actions that the management system should take in response to container lifecycle events. Cannot be updated.

      false

      v1.Lifecycle

      terminationMessagePath

      Optional: Path at which the file to which the container’s termination message will be written is mounted into the container’s filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.

      false

      string

      terminationMessagePolicy

      Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated.

      false

      string

      imagePullPolicy

      Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images

      false

      string

      securityContext

      Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/

      false

      v1.SecurityContext

      stdin

      Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false.

      false

      boolean

      false

      stdinOnce

      Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false

      false

      boolean

      false

      tty

      Whether this container should allocate a TTY for itself, also requires stdin to be true. Default is false.

      false

      boolean

      false

      + +
      +
      +

      v1.ReplicationControllerStatus

      +
      +

      ReplicationControllerStatus represents the current status of a replication controller.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      replicas

      Replicas is the most recently oberved number of replicas. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#what-is-a-replicationcontroller

      true

      integer (int32)

      fullyLabeledReplicas

      The number of pods that have labels matching the labels of the pod template of the replication controller.

      false

      integer (int32)

      readyReplicas

      The number of ready replicas for this replication controller.

      false

      integer (int32)

      availableReplicas

      The number of available replicas (ready for at least minReadySeconds) for this replication controller.

      false

      integer (int32)

      observedGeneration

      ObservedGeneration reflects the generation of the most recently observed replication controller.

      false

      integer (int64)

      conditions

      Represents the latest available observations of a replication controller’s current state.

      false

      v1.ReplicationControllerCondition array

      + +
      +
      +

      v1.ComponentCondition

      +
      +

      Information about the condition of a component.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      type

      Type of condition for a component. Valid value: "Healthy"

      true

      string

      status

      Status of the condition for a component. Valid values for "Healthy": "True", "False", or "Unknown".

      true

      string

      message

      Message about the condition for a component. For example, information about a health check.

      false

      string

      error

      Condition error code for a component. For example, a health check error code.

      false

      string

      + +
      +
      +

      v1.OwnerReference

      +
      +

      OwnerReference contains enough information to let you identify an owning object. Currently, an owning object must be in the same namespace, so there is no namespace field.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      apiVersion

      API version of the referent.

      true

      string

      kind

      Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      true

      string

      name

      Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names

      true

      string

      uid

      UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids

      true

      string

      controller

      If true, this reference points to the managing controller.

      false

      boolean

      false

      blockOwnerDeletion

      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. Defaults to false. To set this field, a user needs "delete" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.

      false

      boolean

      false

      + +
      +
      +

      v1.APIResource

      +
      +

      APIResource specifies the name of a resource and whether it is namespaced.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      name is the plural name of the resource.

      true

      string

      singularName

      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.

      true

      string

      namespaced

      namespaced indicates if a resource is namespaced or not.

      true

      boolean

      false

      group

      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".

      false

      string

      version

      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)".

      false

      string

      kind

      kind is the kind for the resource (e.g. Foo is the kind for a resource foo)

      true

      string

      verbs

      verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy)

      true

      string array

      shortNames

      shortNames is a list of suggested short names of the resource.

      false

      string array

      categories

      categories is a list of the grouped resources this resource belongs to (e.g. all)

      false

      string array

      + +
      +
      +

      v1.Binding

      +
      +

      Binding ties one object to another; for example, a pod is bound to a node by a scheduler. Deprecated in 1.7, please use the bindings subresource of pods instead.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ObjectMeta

      target

      The target object that you want to bind to the standard object.

      true

      v1.ObjectReference

      + +
      +
      +

      v1.ContainerState

      +
      +

      ContainerState holds a possible state of container. Only one of its members may be specified. If none of them is specified, the default one is ContainerStateWaiting.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      waiting

      Details about a waiting container

      false

      v1.ContainerStateWaiting

      running

      Details about a running container

      false

      v1.ContainerStateRunning

      terminated

      Details about a terminated container

      false

      v1.ContainerStateTerminated

      + +
      +
      +

      v1.ReplicationControllerList

      +
      +

      ReplicationControllerList is a collection of replication controllers.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      items

      List of replication controllers. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller

      true

      v1.ReplicationController array

      + +
      +
      +

      v1.WatchEvent

      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      type

      true

      string

      object

      true

      string

      + +
      +
      +

      v1.Secret

      +
      +

      Secret holds secret data of a certain type. The total bytes of the values in the Data field must be less than MaxSecretSize bytes.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ObjectMeta

      data

      Data contains the secret data. Each key must consist of alphanumeric characters, -, _ or .. The serialized form of the secret data is a base64 encoded string, representing the arbitrary (possibly non-string) data value here. Described in https://tools.ietf.org/html/rfc4648#section-4

      false

      object

      stringData

      stringData allows specifying non-binary secret data in string form. It is provided as a write-only convenience method. All keys and values are merged into the data field on write, overwriting any existing values. It is never output when reading from the API.

      false

      object

      type

      Used to facilitate programmatic handling of secret data.

      false

      string

      + +
      +
      +

      v1.LabelSelectorRequirement

      +
      +

      A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      key

      key is the label key that the selector applies to.

      true

      string

      operator

      operator represents a key’s relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.

      true

      string

      values

      values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.

      false

      string array

      + +
      +
      +

      v1.EnvVar

      +
      +

      EnvVar represents an environment variable present in a Container.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      Name of the environment variable. Must be a C_IDENTIFIER.

      true

      string

      value

      Variable references $(VAR_NAME) are expanded using the previous defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".

      false

      string

      valueFrom

      Source for the environment variable’s value. Cannot be used if value is not empty.

      false

      v1.EnvVarSource

      + +
      +
      +

      v1.ResourceRequirements

      +
      +

      ResourceRequirements describes the compute resource requirements.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      limits

      Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/

      false

      object

      requests

      Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/

      false

      object

      + +
      +
      +

      v1.LimitRangeItem

      +
      +

      LimitRangeItem defines a min/max usage limit for any resource that matches on kind.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      type

      Type of resource that this limit applies to.

      false

      string

      max

      Max usage constraints on this kind by resource name.

      false

      object

      min

      Min usage constraints on this kind by resource name.

      false

      object

      default

      Default resource requirement limit value by resource name if resource limit is omitted.

      false

      object

      defaultRequest

      DefaultRequest is the default resource requirement request value by resource name if resource request is omitted.

      false

      object

      maxLimitRequestRatio

      MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource.

      false

      object

      + +
      +
      +

      v1.PodList

      +
      +

      PodList is a list of Pods.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      items

      List of pods. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md

      true

      v1.Pod array

      + +
      +
      +

      v1.ServiceList

      +
      +

      ServiceList holds a list of services.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      items

      List of services

      true

      v1.Service array

      + +
      +
      +

      v1.ConfigMapEnvSource

      +
      +

      ConfigMapEnvSource selects a ConfigMap to populate the environment variables with.

      +
      +
      +

      The contents of the target ConfigMap’s Data field will represent the key-value pairs as environment variables.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      name

      Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      false

      string

      optional

      Specify whether the ConfigMap must be defined

      false

      boolean

      false

      + +
      +
      +

      v1.ObjectReference

      +
      +

      ObjectReference contains enough information to let you inspect or modify the referred object.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      kind

      Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      string

      namespace

      Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/

      false

      string

      name

      Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      false

      string

      uid

      UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids

      false

      string

      apiVersion

      API version of the referent.

      false

      string

      resourceVersion

      Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency

      false

      string

      fieldPath

      If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object.

      false

      string

      + +
      +
      +

      v1.ContainerStateWaiting

      +
      +

      ContainerStateWaiting is a waiting state of a container.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      reason

      (brief) reason the container is not yet running.

      false

      string

      message

      Message regarding why the container is not yet running.

      false

      string

      + +
      +
      +

      v1.ScaleIOPersistentVolumeSource

      +
      +

      ScaleIOPersistentVolumeSource represents a persistent ScaleIO volume

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      gateway

      The host address of the ScaleIO API Gateway.

      true

      string

      system

      The name of the storage system as configured in ScaleIO.

      true

      string

      secretRef

      SecretRef references to the secret for ScaleIO user and other sensitive information. If this is not provided, Login operation will fail.

      true

      v1.SecretReference

      sslEnabled

      Flag to enable/disable SSL communication with Gateway, default false

      false

      boolean

      false

      protectionDomain

      The name of the ScaleIO Protection Domain for the configured storage.

      false

      string

      storagePool

      The ScaleIO Storage Pool associated with the protection domain.

      false

      string

      storageMode

      Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned.

      false

      string

      volumeName

      The name of a volume already created in the ScaleIO system that is associated with this volume source.

      false

      string

      fsType

      Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.

      false

      string

      readOnly

      Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.

      false

      boolean

      false

      + +
      +
      +

      v1.EndpointAddress

      +
      +

      EndpointAddress is a tuple that describes single IP address.

      +
      + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionRequiredSchemaDefault

      ip

      The IP of this endpoint. May not be loopback (127.0.0.0/8), link-local (169.254.0.0/16), or link-local multicast ((224.0.0.0/24). IPv6 is also accepted but not fully supported on all platforms. Also, certain kubernetes components, like kube-proxy, are not IPv6 ready.

      true

      string

      hostname

      The Hostname of this endpoint

      false

      string

      nodeName

      Optional: Node hosting this endpoint. This can be used to determine endpoints local to a node.

      false

      string

      targetRef

      Reference to object providing the endpoint.

      false

      v1.ObjectReference

      +

      v1.NodeSpec

      @@ -10603,61 +11311,6 @@ Examples:
      -
      -
      -

      v1.EndpointAddress

      -
      -

      EndpointAddress is a tuple that describes single IP address.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      ip

      The IP of this endpoint. May not be loopback (127.0.0.0/8), link-local (169.254.0.0/16), or link-local multicast ((224.0.0.0/24). IPv6 is also accepted but not fully supported on all platforms. Also, certain kubernetes components, like kube-proxy, are not IPv6 ready.

      true

      string

      hostname

      The Hostname of this endpoint

      false

      string

      nodeName

      Optional: Node hosting this endpoint. This can be used to determine endpoints local to a node.

      false

      string

      targetRef

      Reference to object providing the endpoint.

      false

      v1.ObjectReference

      -

      v1.DaemonEndpoint

      diff --git a/docs/api-reference/v1/operations.html b/docs/api-reference/v1/operations.html index 9547c4d70c4..3dceb028226 100755 --- a/docs/api-reference/v1/operations.html +++ b/docs/api-reference/v1/operations.html @@ -2665,7 +2665,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -3703,7 +3703,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -4741,7 +4741,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -5779,7 +5779,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -6817,7 +6817,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -8239,7 +8239,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -12105,7 +12105,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -13143,7 +13143,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -14949,7 +14949,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -16371,7 +16371,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -17409,7 +17409,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -20328,7 +20328,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -21794,7 +21794,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      @@ -24400,7 +24400,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      QueryParameter

      propagationPolicy

      -

      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.

      +

      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.

      false

      string

      diff --git a/docs/man/man1/kubeadm-alpha-phase-addon-all.1 b/docs/man/man1/kubeadm-alpha-phase-addon-all.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-addon-all.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-addon-kube-dns.1 b/docs/man/man1/kubeadm-alpha-phase-addon-kube-dns.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-addon-kube-dns.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-addon-kube-proxy.1 b/docs/man/man1/kubeadm-alpha-phase-addon-kube-proxy.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-addon-kube-proxy.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-addon.1 b/docs/man/man1/kubeadm-alpha-phase-addon.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-addon.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-bootstrap-token-all.1 b/docs/man/man1/kubeadm-alpha-phase-bootstrap-token-all.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-bootstrap-token-all.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-bootstrap-token-cluster-info.1 b/docs/man/man1/kubeadm-alpha-phase-bootstrap-token-cluster-info.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-bootstrap-token-cluster-info.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-bootstrap-token-create.1 b/docs/man/man1/kubeadm-alpha-phase-bootstrap-token-create.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-bootstrap-token-create.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-bootstrap-token-node-allow-auto-approve.1 b/docs/man/man1/kubeadm-alpha-phase-bootstrap-token-node-allow-auto-approve.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-bootstrap-token-node-allow-auto-approve.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-bootstrap-token-node-allow-post-csrs.1 b/docs/man/man1/kubeadm-alpha-phase-bootstrap-token-node-allow-post-csrs.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-bootstrap-token-node-allow-post-csrs.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-bootstrap-token-node.1 b/docs/man/man1/kubeadm-alpha-phase-bootstrap-token-node.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-bootstrap-token-node.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-bootstrap-token.1 b/docs/man/man1/kubeadm-alpha-phase-bootstrap-token.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-bootstrap-token.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-certs-all.1 b/docs/man/man1/kubeadm-alpha-phase-certs-all.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-certs-all.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-certs-apiserver-kubelet-client.1 b/docs/man/man1/kubeadm-alpha-phase-certs-apiserver-kubelet-client.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-certs-apiserver-kubelet-client.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-certs-apiserver.1 b/docs/man/man1/kubeadm-alpha-phase-certs-apiserver.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-certs-apiserver.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-certs-ca.1 b/docs/man/man1/kubeadm-alpha-phase-certs-ca.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-certs-ca.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-certs-front-proxy-ca.1 b/docs/man/man1/kubeadm-alpha-phase-certs-front-proxy-ca.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-certs-front-proxy-ca.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-certs-front-proxy-client.1 b/docs/man/man1/kubeadm-alpha-phase-certs-front-proxy-client.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-certs-front-proxy-client.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-certs-sa.1 b/docs/man/man1/kubeadm-alpha-phase-certs-sa.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-certs-sa.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-certs.1 b/docs/man/man1/kubeadm-alpha-phase-certs.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-certs.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-controlplane-all.1 b/docs/man/man1/kubeadm-alpha-phase-controlplane-all.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-controlplane-all.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-controlplane-apiserver.1 b/docs/man/man1/kubeadm-alpha-phase-controlplane-apiserver.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-controlplane-apiserver.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-controlplane-controller-manager.1 b/docs/man/man1/kubeadm-alpha-phase-controlplane-controller-manager.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-controlplane-controller-manager.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-controlplane-scheduler.1 b/docs/man/man1/kubeadm-alpha-phase-controlplane-scheduler.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-controlplane-scheduler.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-controlplane.1 b/docs/man/man1/kubeadm-alpha-phase-controlplane.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-controlplane.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-etcd-local.1 b/docs/man/man1/kubeadm-alpha-phase-etcd-local.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-etcd-local.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-etcd.1 b/docs/man/man1/kubeadm-alpha-phase-etcd.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-etcd.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-kubeconfig-admin.1 b/docs/man/man1/kubeadm-alpha-phase-kubeconfig-admin.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-kubeconfig-admin.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-kubeconfig-all.1 b/docs/man/man1/kubeadm-alpha-phase-kubeconfig-all.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-kubeconfig-all.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-kubeconfig-controller-manager.1 b/docs/man/man1/kubeadm-alpha-phase-kubeconfig-controller-manager.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-kubeconfig-controller-manager.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-kubeconfig-kubelet.1 b/docs/man/man1/kubeadm-alpha-phase-kubeconfig-kubelet.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-kubeconfig-kubelet.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-kubeconfig-scheduler.1 b/docs/man/man1/kubeadm-alpha-phase-kubeconfig-scheduler.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-kubeconfig-scheduler.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-kubeconfig-user.1 b/docs/man/man1/kubeadm-alpha-phase-kubeconfig-user.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-kubeconfig-user.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-kubeconfig.1 b/docs/man/man1/kubeadm-alpha-phase-kubeconfig.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-kubeconfig.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-mark-master.1 b/docs/man/man1/kubeadm-alpha-phase-mark-master.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-mark-master.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-preflight-master.1 b/docs/man/man1/kubeadm-alpha-phase-preflight-master.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-preflight-master.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-preflight-node.1 b/docs/man/man1/kubeadm-alpha-phase-preflight-node.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-preflight-node.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-preflight.1 b/docs/man/man1/kubeadm-alpha-phase-preflight.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-preflight.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-selfhosting-convert-from-staticpods.1 b/docs/man/man1/kubeadm-alpha-phase-selfhosting-convert-from-staticpods.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-selfhosting-convert-from-staticpods.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-selfhosting.1 b/docs/man/man1/kubeadm-alpha-phase-selfhosting.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-selfhosting.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase-upload-config.1 b/docs/man/man1/kubeadm-alpha-phase-upload-config.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase-upload-config.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha-phase.1 b/docs/man/man1/kubeadm-alpha-phase.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha-phase.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-alpha.1 b/docs/man/man1/kubeadm-alpha.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-alpha.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-completion.1 b/docs/man/man1/kubeadm-completion.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-completion.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-config-upload-from-file.1 b/docs/man/man1/kubeadm-config-upload-from-file.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-config-upload-from-file.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-config-upload-from-flags.1 b/docs/man/man1/kubeadm-config-upload-from-flags.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-config-upload-from-flags.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-config-upload.1 b/docs/man/man1/kubeadm-config-upload.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-config-upload.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-config-view.1 b/docs/man/man1/kubeadm-config-view.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-config-view.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-config.1 b/docs/man/man1/kubeadm-config.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-config.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-init.1 b/docs/man/man1/kubeadm-init.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-init.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-join.1 b/docs/man/man1/kubeadm-join.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-join.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-reset.1 b/docs/man/man1/kubeadm-reset.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-reset.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-token-create.1 b/docs/man/man1/kubeadm-token-create.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-token-create.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-token-delete.1 b/docs/man/man1/kubeadm-token-delete.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-token-delete.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-token-generate.1 b/docs/man/man1/kubeadm-token-generate.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-token-generate.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-token-list.1 b/docs/man/man1/kubeadm-token-list.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-token-list.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-token.1 b/docs/man/man1/kubeadm-token.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-token.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-upgrade-apply.1 b/docs/man/man1/kubeadm-upgrade-apply.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-upgrade-apply.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-upgrade-plan.1 b/docs/man/man1/kubeadm-upgrade-plan.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-upgrade-plan.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-upgrade.1 b/docs/man/man1/kubeadm-upgrade.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-upgrade.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-version.1 b/docs/man/man1/kubeadm-version.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-version.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm.1 b/docs/man/man1/kubeadm.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubectl-alpha-diff.1 b/docs/man/man1/kubectl-alpha-diff.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubectl-alpha-diff.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubectl-create-priorityclass.1 b/docs/man/man1/kubectl-create-priorityclass.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubectl-create-priorityclass.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/user-guide/kubectl/kubectl_alpha.md b/docs/user-guide/kubectl/kubectl_alpha.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/user-guide/kubectl/kubectl_alpha.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/user-guide/kubectl/kubectl_alpha_diff.md b/docs/user-guide/kubectl/kubectl_alpha_diff.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/user-guide/kubectl/kubectl_alpha_diff.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/user-guide/kubectl/kubectl_create_priorityclass.md b/docs/user-guide/kubectl/kubectl_create_priorityclass.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/user-guide/kubectl/kubectl_create_priorityclass.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/examples/BUILD b/examples/BUILD index f972b148487..8fa87a454e9 100644 --- a/examples/BUILD +++ b/examples/BUILD @@ -29,18 +29,18 @@ go_test( importpath = "k8s.io/kubernetes/examples_test", tags = ["manual"], # this test is broken and examples in-tree is deprecated deps = [ - "//pkg/api:go_default_library", "//pkg/api/testapi:go_default_library", - "//pkg/api/validation:go_default_library", "//pkg/apis/apps:go_default_library", "//pkg/apis/apps/validation:go_default_library", "//pkg/apis/batch:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/apis/extensions/validation:go_default_library", "//pkg/capabilities:go_default_library", "//pkg/registry/batch/job:go_default_library", - "//plugin/pkg/scheduler/api:go_default_library", - "//plugin/pkg/scheduler/api/latest:go_default_library", + "//pkg/scheduler/api:go_default_library", + "//pkg/scheduler/api/latest:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/examples/OWNERS b/examples/OWNERS index d74aa8a617a..315623ca8b4 100644 --- a/examples/OWNERS +++ b/examples/OWNERS @@ -2,6 +2,7 @@ reviewers: - brendandburns - thockin - zmerlynn + - zouyee approvers: - brendandburns - eparis diff --git a/examples/cockroachdb/cockroachdb-statefulset.yaml b/examples/cockroachdb/cockroachdb-statefulset.yaml index a3eab10393d..0afdef391e1 100644 --- a/examples/cockroachdb/cockroachdb-statefulset.yaml +++ b/examples/cockroachdb/cockroachdb-statefulset.yaml @@ -163,8 +163,6 @@ spec: volumeClaimTemplates: - metadata: name: datadir - annotations: - volume.alpha.kubernetes.io/storage-class: anything spec: accessModes: - "ReadWriteOnce" diff --git a/examples/examples_test.go b/examples/examples_test.go index 64fdc9627c6..33ec647fff0 100644 --- a/examples/examples_test.go +++ b/examples/examples_test.go @@ -32,18 +32,18 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/yaml" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" - "k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/apis/apps" appsvalidation "k8s.io/kubernetes/pkg/apis/apps/validation" "k8s.io/kubernetes/pkg/apis/batch" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/validation" "k8s.io/kubernetes/pkg/apis/extensions" expvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation" "k8s.io/kubernetes/pkg/capabilities" "k8s.io/kubernetes/pkg/registry/batch/job" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - schedulerapilatest "k8s.io/kubernetes/plugin/pkg/scheduler/api/latest" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + schedulerapilatest "k8s.io/kubernetes/pkg/scheduler/api/latest" ) func validateObject(obj runtime.Object) (errors field.ErrorList) { diff --git a/examples/explorer/BUILD b/examples/explorer/BUILD index c4c7bc13024..9485a3b6aaa 100644 --- a/examples/explorer/BUILD +++ b/examples/explorer/BUILD @@ -8,8 +8,8 @@ load( go_binary( name = "explorer", + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/examples/explorer", - library = ":go_default_library", ) go_library( diff --git a/examples/guestbook-go/BUILD b/examples/guestbook-go/BUILD index a2a5b55ab1e..d18db9f7fc2 100644 --- a/examples/guestbook-go/BUILD +++ b/examples/guestbook-go/BUILD @@ -8,8 +8,8 @@ load( go_binary( name = "guestbook-go", + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/examples/guestbook-go", - library = ":go_default_library", ) go_library( diff --git a/examples/https-nginx/BUILD b/examples/https-nginx/BUILD index a4a15ffe53d..d380f2eeb17 100644 --- a/examples/https-nginx/BUILD +++ b/examples/https-nginx/BUILD @@ -8,8 +8,8 @@ load( go_binary( name = "https-nginx", + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/examples/https-nginx", - library = ":go_default_library", ) go_library( @@ -17,8 +17,9 @@ go_library( srcs = ["make_secret.go"], importpath = "k8s.io/kubernetes/examples/https-nginx", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/install:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", ], diff --git a/examples/https-nginx/make_secret.go b/examples/https-nginx/make_secret.go index 8299b5042c1..e85838ee740 100644 --- a/examples/https-nginx/make_secret.go +++ b/examples/https-nginx/make_secret.go @@ -28,10 +28,11 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" // This installs the legacy v1 API - _ "k8s.io/kubernetes/pkg/api/install" + _ "k8s.io/kubernetes/pkg/apis/core/install" ) // TODO: @@ -66,5 +67,5 @@ func main() { "nginx.key": nginxKey, }, } - fmt.Printf(runtime.EncodeOrDie(api.Codecs.LegacyCodec(api.Registry.EnabledVersions()...), secret)) + fmt.Printf(runtime.EncodeOrDie(legacyscheme.Codecs.LegacyCodec(legacyscheme.Registry.EnabledVersions()...), secret)) } diff --git a/examples/podsecuritypolicy/rbac/bindings.yaml b/examples/podsecuritypolicy/rbac/bindings.yaml index 17a686755a8..f61b73b21a6 100644 --- a/examples/podsecuritypolicy/rbac/bindings.yaml +++ b/examples/podsecuritypolicy/rbac/bindings.yaml @@ -1,5 +1,5 @@ -# privilegedPSP gives the privilegedPSP role -# to the group privileged. +# privileged-psp-users gives the privileged-psp-user role +# to the group privileged-psp-users. apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: @@ -13,8 +13,8 @@ roleRef: kind: ClusterRole name: privileged-psp-user --- -# restrictedPSP grants the restrictedPSP role to -# the groups restricted and privileged. +# restricted-psp-users grants the restricted-psp-user role to +# the groups restricted-psp-users and privileged-psp-users. apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: @@ -32,7 +32,7 @@ roleRef: name: restricted-psp-user --- # edit grants edit role to the groups -# restricted and privileged. +# restricted-psp-users and privileged-psp-users. apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: diff --git a/examples/podsecuritypolicy/rbac/policies.yaml b/examples/podsecuritypolicy/rbac/policies.yaml index 670a0e44486..ab4abd730c4 100644 --- a/examples/podsecuritypolicy/rbac/policies.yaml +++ b/examples/podsecuritypolicy/rbac/policies.yaml @@ -16,6 +16,12 @@ spec: - '*' allowedCapabilities: - '*' + hostPID: true + hostIPC: true + hostNetwork: true + hostPorts: + - min: 1 + max: 65536 --- apiVersion: extensions/v1beta1 kind: PodSecurityPolicy @@ -38,4 +44,6 @@ spec: - 'configMap' - 'persistentVolumeClaim' - 'projected' - + hostPID: false + hostIPC: false + hostNetwork: false diff --git a/examples/podsecuritypolicy/rbac/roles.yaml b/examples/podsecuritypolicy/rbac/roles.yaml index b7ee13db472..2598e83bc1b 100644 --- a/examples/podsecuritypolicy/rbac/roles.yaml +++ b/examples/podsecuritypolicy/rbac/roles.yaml @@ -1,10 +1,9 @@ -# restrictedPSP grants access to use -# the restricted PSP. +# restricted-psp-user grants access to use the restricted PSP. apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: restricted-psp-user -rules: +rules: - apiGroups: - extensions resources: @@ -14,13 +13,12 @@ rules: verbs: - use --- -# privilegedPSP grants access to use the privileged -# PSP. +# privileged-psp-user grants access to use the privileged PSP. apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: privileged-psp-user -rules: +rules: - apiGroups: - extensions resources: diff --git a/examples/sharing-clusters/BUILD b/examples/sharing-clusters/BUILD index 4892ce653ed..e2ff3a26a88 100644 --- a/examples/sharing-clusters/BUILD +++ b/examples/sharing-clusters/BUILD @@ -8,8 +8,8 @@ load( go_binary( name = "sharing-clusters", + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/examples/sharing-clusters", - library = ":go_default_library", ) go_library( @@ -17,7 +17,8 @@ go_library( srcs = ["make_secret.go"], importpath = "k8s.io/kubernetes/examples/sharing-clusters", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", ], diff --git a/examples/sharing-clusters/make_secret.go b/examples/sharing-clusters/make_secret.go index 4721c1167e9..ece55eab334 100644 --- a/examples/sharing-clusters/make_secret.go +++ b/examples/sharing-clusters/make_secret.go @@ -25,7 +25,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" ) // TODO: @@ -59,5 +60,5 @@ func main() { "config": cfg, }, } - fmt.Printf(runtime.EncodeOrDie(api.Codecs.LegacyCodec(api.Registry.EnabledVersions()...), secret)) + fmt.Printf(runtime.EncodeOrDie(legacyscheme.Codecs.LegacyCodec(legacyscheme.Registry.EnabledVersions()...), secret)) } diff --git a/examples/storage/cassandra/cassandra-statefulset.yaml b/examples/storage/cassandra/cassandra-statefulset.yaml index 62d7d2a7131..f91f40360e6 100644 --- a/examples/storage/cassandra/cassandra-statefulset.yaml +++ b/examples/storage/cassandra/cassandra-statefulset.yaml @@ -82,16 +82,15 @@ spec: volumeClaimTemplates: - metadata: name: cassandra-data - annotations: - volume.beta.kubernetes.io/storage-class: fast spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 1Gi + storageClassName: fast --- kind: StorageClass -apiVersion: storage.k8s.io/v1beta1 +apiVersion: storage.k8s.io/v1 metadata: name: fast provisioner: kubernetes.io/gce-pd diff --git a/examples/storage/minio/minio-distributed-statefulset.yaml b/examples/storage/minio/minio-distributed-statefulset.yaml index 5457df249cf..de6eef0a9f0 100644 --- a/examples/storage/minio/minio-distributed-statefulset.yaml +++ b/examples/storage/minio/minio-distributed-statefulset.yaml @@ -1,8 +1,11 @@ -apiVersion: apps/v1beta1 +apiVersion: apps/v1beta2 kind: StatefulSet metadata: name: minio spec: + selector: + matchLabels: + app: minio serviceName: minio replicas: 4 template: @@ -39,8 +42,6 @@ spec: volumeClaimTemplates: - metadata: name: data - annotations: - volume.alpha.kubernetes.io/storage-class: anything spec: accessModes: - ReadWriteOnce diff --git a/examples/storage/minio/minio-standalone-deployment.yaml b/examples/storage/minio/minio-standalone-deployment.yaml index c6cd6d5ae93..a6dee7d03c5 100644 --- a/examples/storage/minio/minio-standalone-deployment.yaml +++ b/examples/storage/minio/minio-standalone-deployment.yaml @@ -1,9 +1,12 @@ -apiVersion: extensions/v1beta1 +apiVersion: apps/v1beta2 kind: Deployment metadata: # This name uniquely identifies the Deployment name: minio-deployment spec: + selector: + matchLabels: + app: minio strategy: type: Recreate template: diff --git a/examples/storage/minio/minio-standalone-pvc.yaml b/examples/storage/minio/minio-standalone-pvc.yaml index edd05215a9e..66b1c9800f2 100644 --- a/examples/storage/minio/minio-standalone-pvc.yaml +++ b/examples/storage/minio/minio-standalone-pvc.yaml @@ -3,8 +3,6 @@ kind: PersistentVolumeClaim metadata: # This name uniquely identifies the PVC. Will be used in deployment below. name: minio-pv-claim - annotations: - volume.alpha.kubernetes.io/storage-class: anything labels: app: minio-storage-claim spec: diff --git a/examples/volumes/nfs/provisioner/nfs-server-gce-pv.yaml b/examples/volumes/nfs/provisioner/nfs-server-gce-pv.yaml index 92f9f573584..cccb9a42a4a 100644 --- a/examples/volumes/nfs/provisioner/nfs-server-gce-pv.yaml +++ b/examples/volumes/nfs/provisioner/nfs-server-gce-pv.yaml @@ -4,8 +4,6 @@ metadata: name: nfs-pv-provisioning-demo labels: demo: nfs-pv-provisioning - annotations: - volume.alpha.kubernetes.io/storage-class: any spec: accessModes: [ "ReadWriteOnce" ] resources: diff --git a/examples/volumes/portworx/portworx-volume-pvcsc.yaml b/examples/volumes/portworx/portworx-volume-pvcsc.yaml index b07ddb3029e..736e67feeaf 100644 --- a/examples/volumes/portworx/portworx-volume-pvcsc.yaml +++ b/examples/volumes/portworx/portworx-volume-pvcsc.yaml @@ -2,11 +2,10 @@ kind: PersistentVolumeClaim apiVersion: v1 metadata: name: pvcsc001 - annotations: - volume.beta.kubernetes.io/storage-class: portworx-io-priority-high spec: accessModes: - ReadWriteOnce resources: requests: storage: 2Gi + storageClassName: portworx-io-priority-high diff --git a/examples/volumes/portworx/portworx-volume-sc-high.yaml b/examples/volumes/portworx/portworx-volume-sc-high.yaml index b9a0a51dc37..8fc7323778f 100644 --- a/examples/volumes/portworx/portworx-volume-sc-high.yaml +++ b/examples/volumes/portworx/portworx-volume-sc-high.yaml @@ -1,5 +1,5 @@ kind: StorageClass -apiVersion: storage.k8s.io/v1beta1 +apiVersion: storage.k8s.io/v1 metadata: name: portworx-io-priority-high provisioner: kubernetes.io/portworx-volume diff --git a/examples/volumes/scaleio/sc-pvc.yaml b/examples/volumes/scaleio/sc-pvc.yaml index 3937a1e8254..078fb63548f 100644 --- a/examples/volumes/scaleio/sc-pvc.yaml +++ b/examples/volumes/scaleio/sc-pvc.yaml @@ -2,11 +2,10 @@ kind: PersistentVolumeClaim apiVersion: v1 metadata: name: pvc-sio-small - annotations: - volume.beta.kubernetes.io/storage-class: sio-small spec: accessModes: - ReadWriteOnce resources: requests: storage: 10Gi + storageClassName: sio-small diff --git a/examples/volumes/scaleio/sc.yaml b/examples/volumes/scaleio/sc.yaml index 85de382bf1b..2ff4c028f5d 100644 --- a/examples/volumes/scaleio/sc.yaml +++ b/examples/volumes/scaleio/sc.yaml @@ -1,5 +1,5 @@ kind: StorageClass -apiVersion: storage.k8s.io/v1beta1 +apiVersion: storage.k8s.io/v1 metadata: name: sio-small provisioner: kubernetes.io/scaleio diff --git a/examples/volumes/vsphere/simple-statefulset.yaml b/examples/volumes/vsphere/simple-statefulset.yaml index 465c7c5eddf..3684a3b64e1 100644 --- a/examples/volumes/vsphere/simple-statefulset.yaml +++ b/examples/volumes/vsphere/simple-statefulset.yaml @@ -37,10 +37,9 @@ spec: volumeClaimTemplates: - metadata: name: www - annotations: - volume.beta.kubernetes.io/storage-class: thin-disk spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 1Gi + storageClassName: thin-disk diff --git a/examples/volumes/vsphere/simple-storageclass.yaml b/examples/volumes/vsphere/simple-storageclass.yaml index bfa4dae4c31..87327836f00 100644 --- a/examples/volumes/vsphere/simple-storageclass.yaml +++ b/examples/volumes/vsphere/simple-storageclass.yaml @@ -1,5 +1,5 @@ kind: StorageClass -apiVersion: storage.k8s.io/v1beta1 +apiVersion: storage.k8s.io/v1 metadata: name: thin-disk provisioner: kubernetes.io/vsphere-volume diff --git a/examples/volumes/vsphere/vsphere-volume-pvcsc.yaml b/examples/volumes/vsphere/vsphere-volume-pvcsc.yaml index f73ed91b5bd..03f3f8f8fe2 100644 --- a/examples/volumes/vsphere/vsphere-volume-pvcsc.yaml +++ b/examples/volumes/vsphere/vsphere-volume-pvcsc.yaml @@ -2,11 +2,10 @@ kind: PersistentVolumeClaim apiVersion: v1 metadata: name: pvcsc001 - annotations: - volume.beta.kubernetes.io/storage-class: fast spec: accessModes: - ReadWriteOnce resources: requests: storage: 2Gi + storageClassName: fast diff --git a/examples/volumes/vsphere/vsphere-volume-sc-fast.yaml b/examples/volumes/vsphere/vsphere-volume-sc-fast.yaml index b2b436f8e0d..959e28e8839 100644 --- a/examples/volumes/vsphere/vsphere-volume-sc-fast.yaml +++ b/examples/volumes/vsphere/vsphere-volume-sc-fast.yaml @@ -1,5 +1,5 @@ kind: StorageClass -apiVersion: storage.k8s.io/v1beta1 +apiVersion: storage.k8s.io/v1 metadata: name: fast provisioner: kubernetes.io/vsphere-volume diff --git a/examples/volumes/vsphere/vsphere-volume-sc-vsancapabilities-with-datastore.yaml b/examples/volumes/vsphere/vsphere-volume-sc-vsancapabilities-with-datastore.yaml index 2fd9316668f..5c4a9a66522 100644 --- a/examples/volumes/vsphere/vsphere-volume-sc-vsancapabilities-with-datastore.yaml +++ b/examples/volumes/vsphere/vsphere-volume-sc-vsancapabilities-with-datastore.yaml @@ -1,5 +1,5 @@ kind: StorageClass -apiVersion: storage.k8s.io/v1beta1 +apiVersion: storage.k8s.io/v1 metadata: name: fast provisioner: kubernetes.io/vsphere-volume diff --git a/examples/volumes/vsphere/vsphere-volume-sc-vsancapabilities.yaml b/examples/volumes/vsphere/vsphere-volume-sc-vsancapabilities.yaml index ad2ff9d15c1..5f8f9f2fc11 100644 --- a/examples/volumes/vsphere/vsphere-volume-sc-vsancapabilities.yaml +++ b/examples/volumes/vsphere/vsphere-volume-sc-vsancapabilities.yaml @@ -1,5 +1,5 @@ kind: StorageClass -apiVersion: storage.k8s.io/v1beta1 +apiVersion: storage.k8s.io/v1 metadata: name: fast provisioner: kubernetes.io/vsphere-volume diff --git a/examples/volumes/vsphere/vsphere-volume-sc-with-datastore.yaml b/examples/volumes/vsphere/vsphere-volume-sc-with-datastore.yaml index 5e468ef7777..3fb56bf1ab7 100644 --- a/examples/volumes/vsphere/vsphere-volume-sc-with-datastore.yaml +++ b/examples/volumes/vsphere/vsphere-volume-sc-with-datastore.yaml @@ -1,5 +1,5 @@ kind: StorageClass -apiVersion: storage.k8s.io/v1beta1 +apiVersion: storage.k8s.io/v1 metadata: name: fast provisioner: kubernetes.io/vsphere-volume diff --git a/federation/BUILD b/federation/BUILD deleted file mode 100644 index 5473e901773..00000000000 --- a/federation/BUILD +++ /dev/null @@ -1,44 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load("@io_bazel//tools/build_defs/pkg:pkg.bzl", "pkg_tar") - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/apis/core:all-srcs", - "//federation/apis/federation:all-srcs", - "//federation/client/cache:all-srcs", - "//federation/client/clientset_generated/federation_clientset:all-srcs", - "//federation/cluster:all-srcs", - "//federation/cmd/federation-apiserver:all-srcs", - "//federation/cmd/federation-controller-manager:all-srcs", - "//federation/cmd/genfeddocs:all-srcs", - "//federation/cmd/kubefed:all-srcs", - "//federation/develop:all-srcs", - "//federation/pkg/dnsprovider:all-srcs", - "//federation/pkg/federatedtypes:all-srcs", - "//federation/pkg/federation-controller:all-srcs", - "//federation/pkg/kubefed:all-srcs", - "//federation/plugin/pkg/admission/schedulingpolicy:all-srcs", - "//federation/registry/cluster:all-srcs", - "//federation/test/e2e:all-srcs", - "//federation/test/integration:all-srcs", - ], - tags = ["automanaged"], -) - -pkg_tar( - name = "release", - files = glob([ - "deploy/**", - ]) + ["//federation/cluster:all-srcs"], - package_dir = "federation", -) diff --git a/federation/Makefile b/federation/Makefile deleted file mode 100644 index 091f8021202..00000000000 --- a/federation/Makefile +++ /dev/null @@ -1,70 +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. - -DBG_MAKEFILE ?= -ifeq ($(DBG_MAKEFILE),1) - $(warning ***** starting makefile for goal(s) "$(MAKECMDGOALS)") - $(warning ***** $(shell date)) -else - # If we're not debugging the Makefile, don't echo recipes. - MAKEFLAGS += -s -endif - -.PHONY: all -all: init build push deploy - -.PHONY: init -init: - ./develop/develop.sh init - -.PHONY: build -build: build_binaries build_image - -.PHONY: push -push: - ./develop/develop.sh push - -.PHONY: deploy -deploy: deploy_clusters deploy_federation - -.PHONY: destroy -destroy: destroy_federation destroy_clusters - -.PHONY: build_binaries -build_binaries: - ./develop/develop.sh build_binaries - -.PHONY: build_image -build_image: - ./develop/develop.sh build_image - -.PHONY: deploy_clusters -deploy_clusters: - ./develop/develop.sh deploy_clusters - -.PHONY: deploy_federation -deploy_federation: - ./develop/develop.sh deploy_federation - -.PHONY: destroy_federation -destroy_federation: - ./develop/develop.sh destroy_federation - -.PHONY: destroy_clusters -destroy_clusters: - ./develop/develop.sh destroy_clusters - -.PHONY: redeploy_federation -redeploy_federation: - ./develop/develop.sh redeploy_federation diff --git a/federation/OWNERS b/federation/OWNERS deleted file mode 100644 index 84b3679855f..00000000000 --- a/federation/OWNERS +++ /dev/null @@ -1,17 +0,0 @@ -reviewers: - - colhom - - csbell - - irfanurrehman - - madhusudancs - - marun - - mwielgus - - nikhiljindal - - quinton-hoole - - shashidharatd -approvers: - - csbell - - irfanurrehman - - madhusudancs - - mwielgus - - nikhiljindal - - quinton-hoole diff --git a/federation/README.md b/federation/README.md deleted file mode 100644 index 8003e9fd189..00000000000 --- a/federation/README.md +++ /dev/null @@ -1,67 +0,0 @@ -# Cluster Federation - -Kubernetes Cluster Federation enables users to federate multiple -Kubernetes clusters. Please see the [user guide](https://kubernetes.io/docs/concepts/cluster-administration/federation-service-discovery/) -and the [admin guide](https://kubernetes.io/docs/tutorials/federation/set-up-cluster-federation-kubefed/) -for more details about setting up and using the Cluster Federation. - -# Building Kubernetes Cluster Federation - -Please see the [Kubernetes Development Guide](https://github.com/kubernetes/community/blob/master/contributors/devel/development.md) -for initial setup. Once you have the development environment setup -as explained in that guide, you also need to install [`jq`](https://stedolan.github.io/jq/download/) - - -Building cluster federation artifacts should be as simple as running: - -```shell -make build -``` - -You can specify the docker registry to tag the image using the -KUBE_REGISTRY environment variable. Please make sure that you use -the same value in all the subsequent commands. - -To push the built docker images to the registry, run: - -```shell -make push -``` - -To initialize the deployment run: - -(This pulls the installer images) - -```shell -make init -``` - -To deploy the clusters and install the federation components, edit the -`${KUBE_ROOT}/_output/federation/config.json` file to describe your -clusters and run: - -```shell -make deploy -``` - -To turn down the federation components and tear down the clusters run: - -```shell -make destroy -``` - -# Ideas for improvement - -1. Continue with `destroy` phase even in the face of errors. - - The bash script sets `set -e errexit` which causes the script to exit - at the very first error. This should be the default mode for deploying - components but not for destroying/cleanup. - - -[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/federation/README.md?pixel)]() diff --git a/federation/apis/core/BUILD b/federation/apis/core/BUILD deleted file mode 100644 index b5d58afb5cb..00000000000 --- a/federation/apis/core/BUILD +++ /dev/null @@ -1,38 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["register.go"], - importpath = "k8s.io/kubernetes/federation/apis/core", - deps = [ - "//pkg/api:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/apis/core/install:all-srcs", - "//federation/apis/core/v1:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/apis/core/install/BUILD b/federation/apis/core/install/BUILD deleted file mode 100644 index 2c1a036a69f..00000000000 --- a/federation/apis/core/install/BUILD +++ /dev/null @@ -1,33 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["install.go"], - importpath = "k8s.io/kubernetes/federation/apis/core/install", - deps = [ - "//federation/apis/core:go_default_library", - "//federation/apis/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/apis/core/install/install.go b/federation/apis/core/install/install.go deleted file mode 100644 index ca250a4f433..00000000000 --- a/federation/apis/core/install/install.go +++ /dev/null @@ -1,54 +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 install - -import ( - "k8s.io/apimachinery/pkg/apimachinery/announced" - "k8s.io/apimachinery/pkg/apimachinery/registered" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/federation/apis/core" - corev1 "k8s.io/kubernetes/federation/apis/core/v1" -) - -func init() { - Install(core.GroupFactoryRegistry, core.Registry, core.Scheme) -} - -// Install registers the API group and adds types to a scheme -func Install(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *registered.APIRegistrationManager, scheme *runtime.Scheme) { - if err := announced.NewGroupMetaFactory( - &announced.GroupMetaFactoryArgs{ - GroupName: core.GroupName, - VersionPreferenceOrder: []string{corev1.SchemeGroupVersion.Version}, - AddInternalObjectsToScheme: core.AddToScheme, - RootScopedKinds: sets.NewString( - "Namespace", - ), - IgnoredKinds: sets.NewString( - "ListOptions", - "DeleteOptions", - "Status", - ), - }, - announced.VersionToSchemeFunc{ - corev1.SchemeGroupVersion.Version: corev1.AddToScheme, - }, - ).Announce(groupFactoryRegistry).RegisterAndEnable(registry, scheme); err != nil { - panic(err) - } -} diff --git a/federation/apis/core/register.go b/federation/apis/core/register.go deleted file mode 100644 index efb4dd5845a..00000000000 --- a/federation/apis/core/register.go +++ /dev/null @@ -1,102 +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 core - -import ( - "os" - - "k8s.io/apimachinery/pkg/apimachinery/announced" - "k8s.io/apimachinery/pkg/apimachinery/registered" - 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/kubernetes/pkg/api" -) - -// NOTE: the registry, scheme and codecs are created here to allow to install a federation core group -// that is completely independent from the Kubernetes core group. It's only used for the core group itself. -// The other apigroups in the federation apiserver use the Kubernetes registry, scheme and codecs. - -// GroupFactoryRegistry is the APIGroupFactoryRegistry (overlaps a bit with Registry, see comments in package for details) -var GroupFactoryRegistry = make(announced.APIGroupFactoryRegistry) - -// Registry is an instance of an API registry. This is an interim step to start removing the idea of a global -// API registry. -var Registry = registered.NewOrDie(os.Getenv("KUBE_API_VERSIONS")) - -// Scheme is the default instance of runtime.Scheme to which types in the Kubernetes API are already registered. -var Scheme = runtime.NewScheme() - -// Codecs provides access to encoding and decoding for the scheme -var Codecs = serializer.NewCodecFactory(Scheme) - -// GroupName is the group name use in this package -const GroupName = "" - -// SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal} - -// Unversioned is group version for unversioned API objects -// TODO: this should be v1 probably -var Unversioned = schema.GroupVersion{Group: "", Version: "v1"} - -// ParameterCodec handles versioning of objects that are converted to query parameters. -var ParameterCodec = runtime.NewParameterCodec(Scheme) - -// 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() -} - -var ( - SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) - AddToScheme = SchemeBuilder.AddToScheme -) - -func addKnownTypes(scheme *runtime.Scheme) error { - if err := scheme.AddIgnoredConversionType(&metav1.TypeMeta{}, &metav1.TypeMeta{}); err != nil { - return err - } - scheme.AddKnownTypes(SchemeGroupVersion, - &api.ServiceList{}, - &api.Service{}, - &api.Namespace{}, - &api.NamespaceList{}, - &api.Secret{}, - &api.SecretList{}, - &api.Event{}, - &api.EventList{}, - &api.ConfigMap{}, - &api.ConfigMapList{}, - ) - - // Register Unversioned types under their own special group - scheme.AddUnversionedTypes(Unversioned, - &metav1.Status{}, - &metav1.APIVersions{}, - &metav1.APIGroupList{}, - &metav1.APIGroup{}, - &metav1.APIResourceList{}, - ) - return nil -} diff --git a/federation/apis/core/v1/BUILD b/federation/apis/core/v1/BUILD deleted file mode 100644 index 1077ddd954a..00000000000 --- a/federation/apis/core/v1/BUILD +++ /dev/null @@ -1,38 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "conversion.go", - "defaults.go", - "doc.go", - "register.go", - "zz_generated.conversion.go", - ], - importpath = "k8s.io/kubernetes/federation/apis/core/v1", - deps = [ - "//pkg/api/v1:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/apis/core/v1/conversion.go b/federation/apis/core/v1/conversion.go deleted file mode 100644 index 2e9ed1e7411..00000000000 --- a/federation/apis/core/v1/conversion.go +++ /dev/null @@ -1,70 +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 v1 - -import ( - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api/v1" -) - -func addConversionFuncs(scheme *runtime.Scheme) error { - // Add non-generated conversion functions - err := scheme.AddConversionFuncs( - v1.Convert_v1_DeleteOptions_To_api_DeleteOptions, - v1.Convert_api_DeleteOptions_To_v1_DeleteOptions, - v1.Convert_v1_List_To_api_List, - v1.Convert_api_List_To_v1_List, - v1.Convert_v1_ListOptions_To_api_ListOptions, - v1.Convert_api_ListOptions_To_v1_ListOptions, - v1.Convert_v1_ObjectFieldSelector_To_api_ObjectFieldSelector, - v1.Convert_api_ObjectFieldSelector_To_v1_ObjectFieldSelector, - v1.Convert_v1_ObjectMeta_To_api_ObjectMeta, - v1.Convert_api_ObjectMeta_To_v1_ObjectMeta, - v1.Convert_v1_ObjectReference_To_api_ObjectReference, - v1.Convert_api_ObjectReference_To_v1_ObjectReference, - v1.Convert_v1_Secret_To_api_Secret, - v1.Convert_api_Secret_To_v1_Secret, - v1.Convert_v1_SecretList_To_api_SecretList, - v1.Convert_api_SecretList_To_v1_SecretList, - v1.Convert_v1_Service_To_api_Service, - v1.Convert_api_Service_To_v1_Service, - v1.Convert_v1_ServiceList_To_api_ServiceList, - v1.Convert_api_ServiceList_To_v1_ServiceList, - v1.Convert_v1_ServicePort_To_api_ServicePort, - v1.Convert_api_ServicePort_To_v1_ServicePort, - v1.Convert_v1_ServiceProxyOptions_To_api_ServiceProxyOptions, - v1.Convert_api_ServiceProxyOptions_To_v1_ServiceProxyOptions, - v1.Convert_v1_ServiceSpec_To_api_ServiceSpec, - v1.Convert_api_ServiceSpec_To_v1_ServiceSpec, - v1.Convert_v1_ServiceStatus_To_api_ServiceStatus, - v1.Convert_api_ServiceStatus_To_v1_ServiceStatus, - ) - if err != nil { - return err - } - - if err := v1.AddFieldLabelConversionsForEvent(scheme); err != nil { - return nil - } - if err := v1.AddFieldLabelConversionsForNamespace(scheme); err != nil { - return nil - } - if err := v1.AddFieldLabelConversionsForSecret(scheme); err != nil { - return nil - } - return nil -} diff --git a/federation/apis/core/v1/defaults.go b/federation/apis/core/v1/defaults.go deleted file mode 100644 index 48a5b2581aa..00000000000 --- a/federation/apis/core/v1/defaults.go +++ /dev/null @@ -1,26 +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 v1 - -import ( - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api/v1" -) - -func addDefaultingFuncs(scheme *runtime.Scheme) error { - return v1.RegisterDefaults(scheme) -} diff --git a/federation/apis/core/v1/doc.go b/federation/apis/core/v1/doc.go deleted file mode 100644 index 50a5741fc48..00000000000 --- a/federation/apis/core/v1/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. -*/ - -// This is probably not required for now because we are reusing the types -// in k8s.io/kubernetes/pkg/api. But adding it now as a guard to ensure -// that others don't stumble on this in the future when they add new -// types here since adding it now doesn't hurt. -// +k8s:conversion-gen=k8s.io/kubernetes/federation/apis/core - -package v1 diff --git a/federation/apis/core/v1/register.go b/federation/apis/core/v1/register.go deleted file mode 100644 index dc3e6430199..00000000000 --- a/federation/apis/core/v1/register.go +++ /dev/null @@ -1,68 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES 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/api/core/v1" - 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 = "" - -// 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, addConversionFuncs, addDefaultingFuncs) -} - -// Adds the list of known types to api.Scheme. -func addKnownTypes(scheme *runtime.Scheme) error { - scheme.AddKnownTypes(SchemeGroupVersion, - &v1.Service{}, - &v1.Namespace{}, - &v1.NamespaceList{}, - &v1.ServiceList{}, - &v1.Secret{}, - &v1.SecretList{}, - &v1.Event{}, - &v1.EventList{}, - &v1.ConfigMap{}, - &v1.ConfigMapList{}, - ) - - // Add common types - scheme.AddKnownTypes(SchemeGroupVersion, &metav1.Status{}) - - // Add the watch version that applies - metav1.AddToGroupVersion(scheme, SchemeGroupVersion) - return nil -} diff --git a/federation/apis/core/v1/zz_generated.conversion.go b/federation/apis/core/v1/zz_generated.conversion.go deleted file mode 100644 index 0221d60e0ae..00000000000 --- a/federation/apis/core/v1/zz_generated.conversion.go +++ /dev/null @@ -1,35 +0,0 @@ -// +build !ignore_autogenerated - -/* -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. -*/ - -// This file was autogenerated by conversion-gen. Do not edit it manually! - -package v1 - -import ( - runtime "k8s.io/apimachinery/pkg/runtime" -) - -func init() { - localSchemeBuilder.Register(RegisterConversions) -} - -// RegisterConversions adds conversion functions to the given scheme. -// Public to allow building arbitrary schemes. -func RegisterConversions(scheme *runtime.Scheme) error { - return scheme.AddGeneratedConversionFuncs() -} diff --git a/federation/apis/federation/BUILD b/federation/apis/federation/BUILD deleted file mode 100644 index 8118508131a..00000000000 --- a/federation/apis/federation/BUILD +++ /dev/null @@ -1,44 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "annotations.go", - "doc.go", - "register.go", - "types.go", - "zz_generated.deepcopy.go", - ], - importpath = "k8s.io/kubernetes/federation/apis/federation", - deps = [ - "//pkg/api:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/apis/federation/install:all-srcs", - "//federation/apis/federation/v1beta1:all-srcs", - "//federation/apis/federation/validation:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/apis/federation/annotations.go b/federation/apis/federation/annotations.go deleted file mode 100644 index 9a9e519f363..00000000000 --- a/federation/apis/federation/annotations.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 federation - -// FederationNameAnnotation is the annotation which holds the name of -// the federation that a federation control plane component is associated -// with. It must be applied to all the API types that represent that federations -// control plane's components in the host cluster and in joining clusters. -const FederationNameAnnotation = "federation.alpha.kubernetes.io/federation-name" - -// ClusterNameAnnotation is the annotation which holds the name of -// the cluster that an object is associated with. If the object is -// not associated with any cluster, then this annotation is not -// required. -const ClusterNameAnnotation = "federation.alpha.kubernetes.io/cluster-name" diff --git a/federation/apis/federation/doc.go b/federation/apis/federation/doc.go deleted file mode 100644 index 4c51dfeebef..00000000000 --- a/federation/apis/federation/doc.go +++ /dev/null @@ -1,19 +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,register - -package federation // import "k8s.io/kubernetes/federation/apis/federation" diff --git a/federation/apis/federation/install/BUILD b/federation/apis/federation/install/BUILD deleted file mode 100644 index 54427a4749b..00000000000 --- a/federation/apis/federation/install/BUILD +++ /dev/null @@ -1,50 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = ["install.go"], - importpath = "k8s.io/kubernetes/federation/apis/federation/install", - deps = [ - "//federation/apis/federation:go_default_library", - "//federation/apis/federation/v1beta1:go_default_library", - "//pkg/api:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["install_test.go"], - importpath = "k8s.io/kubernetes/federation/apis/federation/install", - library = ":go_default_library", - deps = [ - "//federation/apis/federation:go_default_library", - "//federation/apis/federation/v1beta1:go_default_library", - "//pkg/api:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/apis/federation/install/install.go b/federation/apis/federation/install/install.go deleted file mode 100644 index a4ceffdb140..00000000000 --- a/federation/apis/federation/install/install.go +++ /dev/null @@ -1,51 +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 install - -import ( - "k8s.io/apimachinery/pkg/apimachinery/announced" - "k8s.io/apimachinery/pkg/apimachinery/registered" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api" - - "k8s.io/kubernetes/federation/apis/federation" - "k8s.io/kubernetes/federation/apis/federation/v1beta1" -) - -func init() { - Install(api.GroupFactoryRegistry, api.Registry, api.Scheme) -} - -// Install registers the API group and adds types to a scheme -func Install(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *registered.APIRegistrationManager, scheme *runtime.Scheme) { - if err := announced.NewGroupMetaFactory( - &announced.GroupMetaFactoryArgs{ - GroupName: federation.GroupName, - VersionPreferenceOrder: []string{v1beta1.SchemeGroupVersion.Version}, - AddInternalObjectsToScheme: federation.AddToScheme, - RootScopedKinds: sets.NewString( - "Cluster", - ), - }, - announced.VersionToSchemeFunc{ - v1beta1.SchemeGroupVersion.Version: v1beta1.AddToScheme, - }, - ).Announce(groupFactoryRegistry).RegisterAndEnable(registry, scheme); err != nil { - panic(err) - } -} diff --git a/federation/apis/federation/install/install_test.go b/federation/apis/federation/install/install_test.go deleted file mode 100644 index 9521ae2c34b..00000000000 --- a/federation/apis/federation/install/install_test.go +++ /dev/null @@ -1,119 +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 install - -import ( - "encoding/json" - "testing" - - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/federation/apis/federation" - "k8s.io/kubernetes/federation/apis/federation/v1beta1" - "k8s.io/kubernetes/pkg/api" -) - -func TestResourceVersioner(t *testing.T) { - accessor := meta.NewAccessor() - cluster := federation.Cluster{ObjectMeta: metav1.ObjectMeta{ResourceVersion: "10"}} - version, err := accessor.ResourceVersion(&cluster) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if version != "10" { - t.Errorf("unexpected version %v", version) - } - - clusterList := federation.ClusterList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}} - version, err = accessor.ResourceVersion(&clusterList) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if version != "10" { - t.Errorf("unexpected version %v", version) - } -} - -func TestCodec(t *testing.T) { - cluster := federation.Cluster{} - // We do want to use package registered rather than testapi here, because we - // want to test if the package install and package registered work as expected. - data, err := runtime.Encode(api.Codecs.LegacyCodec(api.Registry.GroupOrDie(federation.GroupName).GroupVersion), &cluster) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - other := federation.Cluster{} - if err := json.Unmarshal(data, &other); err != nil { - t.Fatalf("unexpected error: %v", err) - } - if other.APIVersion != api.Registry.GroupOrDie(federation.GroupName).GroupVersion.String() || other.Kind != "Cluster" { - t.Errorf("unexpected unmarshalled object %#v", other) - } -} - -func TestInterfacesFor(t *testing.T) { - if _, err := api.Registry.GroupOrDie(federation.GroupName).InterfacesFor(federation.SchemeGroupVersion); err == nil { - t.Fatalf("unexpected non-error: %v", err) - } - for i, version := range api.Registry.GroupOrDie(federation.GroupName).GroupVersions { - if vi, err := api.Registry.GroupOrDie(federation.GroupName).InterfacesFor(version); err != nil || vi == nil { - t.Fatalf("%d: unexpected result: %v", i, err) - } - } -} - -func TestRESTMapper(t *testing.T) { - gv := v1beta1.SchemeGroupVersion - clusterGVK := gv.WithKind("Cluster") - - if gvk, err := api.Registry.GroupOrDie(federation.GroupName).RESTMapper.KindFor(gv.WithResource("clusters")); err != nil || gvk != clusterGVK { - t.Errorf("unexpected version mapping: %v %v", gvk, err) - } - - if m, err := api.Registry.GroupOrDie(federation.GroupName).RESTMapper.RESTMapping(clusterGVK.GroupKind(), ""); err != nil || m.GroupVersionKind != clusterGVK || m.Resource != "clusters" { - t.Errorf("unexpected version mapping: %#v %v", m, err) - } - - for _, version := range api.Registry.GroupOrDie(federation.GroupName).GroupVersions { - mapping, err := api.Registry.GroupOrDie(federation.GroupName).RESTMapper.RESTMapping(clusterGVK.GroupKind(), version.Version) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - - if mapping.Resource != "clusters" { - t.Errorf("incorrect resource name: %#v", mapping) - } - if mapping.GroupVersionKind.GroupVersion() != version { - t.Errorf("incorrect groupVersion: %v", mapping) - } - - interfaces, _ := api.Registry.GroupOrDie(federation.GroupName).InterfacesFor(version) - if mapping.ObjectConvertor != interfaces.ObjectConvertor { - t.Errorf("unexpected: %#v, expected: %#v", mapping, interfaces) - } - - rc := &federation.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "foo"}} - name, err := mapping.MetadataAccessor.Name(rc) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - if name != "foo" { - t.Errorf("unable to retrieve object meta with: %v", mapping.MetadataAccessor) - } - } -} diff --git a/federation/apis/federation/register.go b/federation/apis/federation/register.go deleted file mode 100644 index 9fe9bfc865a..00000000000 --- a/federation/apis/federation/register.go +++ /dev/null @@ -1,51 +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 federation - -import ( - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -// GroupName is the group name use in this package -const GroupName = "federation" - -// 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() -} - -var ( - SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) - AddToScheme = SchemeBuilder.AddToScheme -) - -func addKnownTypes(scheme *runtime.Scheme) error { - scheme.AddKnownTypes(SchemeGroupVersion, - &Cluster{}, - &ClusterList{}, - ) - return nil -} diff --git a/federation/apis/federation/types.go b/federation/apis/federation/types.go deleted file mode 100644 index 0e0aeb38f76..00000000000 --- a/federation/apis/federation/types.go +++ /dev/null @@ -1,178 +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 federation - -import ( - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" -) - -// ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match. -type ServerAddressByClientCIDR struct { - // The CIDR with which clients can match their IP to figure out the server address that they should use. - ClientCIDR string - // Address of this server, suitable for a client that matches the above CIDR. - // This can be a hostname, hostname:port, IP or IP:port. - ServerAddress string -} - -// ClusterSpec describes the attributes of a kubernetes cluster. -type ClusterSpec struct { - // A map of client CIDR to server address. - // This is to help clients reach servers in the most network-efficient way possible. - // Clients can use the appropriate server address as per the CIDR that they match. - // In case of multiple matches, clients should use the longest matching CIDR. - ServerAddressByClientCIDRs []ServerAddressByClientCIDR - // Name of the secret containing kubeconfig to access this cluster. - // The secret is read from the kubernetes cluster that is hosting federation control plane. - // Admin needs to ensure that the required secret exists. Secret should be in the same namespace where federation control plane is hosted and it should have kubeconfig in its data with key "kubeconfig". - // This will later be changed to a reference to secret in federation control plane when the federation control plane supports secrets. - // This can be left empty if the cluster allows insecure access. - // +optional - SecretRef *api.LocalObjectReference -} - -type ClusterConditionType string - -// These are valid conditions of a cluster. -const ( - // ClusterReady means the cluster is ready to accept workloads. - ClusterReady ClusterConditionType = "Ready" - // ClusterOffline means the cluster is temporarily down or not reachable - ClusterOffline ClusterConditionType = "Offline" -) - -// ClusterCondition describes current state of a cluster. -type ClusterCondition struct { - // Type of cluster condition, Complete or Failed. - Type ClusterConditionType - // Status of the condition, one of True, False, Unknown. - Status api.ConditionStatus - // Last time the condition was checked. - // +optional - LastProbeTime metav1.Time - // Last time the condition transit from one status to another. - // +optional - LastTransitionTime metav1.Time - // (brief) reason for the condition's last transition. - // +optional - Reason string - // Human readable message indicating details about last transition. - // +optional - Message string -} - -// ClusterStatus is information about the current status of a cluster updated by cluster controller periodically. -type ClusterStatus struct { - // Conditions is an array of current cluster conditions. - // +optional - Conditions []ClusterCondition - // Zones is the list of availability zones in which the nodes of the cluster exist, e.g. 'us-east1-a'. - // These will always be in the same region. - // +optional - Zones []string - // Region is the name of the region in which all of the nodes in the cluster exist. e.g. 'us-east1'. - // +optional - Region string -} - -// +genclient -// +genclient:nonNamespaced -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// Information about a registered cluster in a federated kubernetes setup. Clusters are not namespaced and have unique names in the federation. -type Cluster struct { - metav1.TypeMeta - // Standard object's metadata. - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata - // +optional - metav1.ObjectMeta - - // Spec defines the behavior of the Cluster. - // +optional - Spec ClusterSpec - // Status describes the current status of a Cluster - // +optional - Status ClusterStatus -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// A list of all the kubernetes clusters registered to the federation -type ClusterList struct { - metav1.TypeMeta - // Standard list metadata. - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds - // +optional - metav1.ListMeta - - // List of Cluster objects. - Items []Cluster -} - -// Temporary/alpha structures to support custom replica assignments within Federated workloads. - -// A set of preferences that can be added to federated version of workloads (deployments, replicasets, ..) -// as a json-serialized annotation. The preferences allow the users to express in which clusters they -// want to put their replicas within the mentioned workload objects. -type ReplicaAllocationPreferences struct { - // If set to true then already scheduled and running replicas may be moved to other clusters - // in order to match current state to the specified preferences. Otherwise, if set to false, - // up and running replicas will not be moved. - // +optional - Rebalance bool - - // A mapping between cluster names and preferences regarding a local workload object (dep, rs, .. ) in - // these clusters. - // "*" (if provided) applies to all clusters if an explicit mapping is not provided. - // If omitted, clusters without explicit preferences should not have any replicas scheduled. - // +optional - Clusters map[string]ClusterPreferences -} - -// Preferences regarding number of replicas assigned to a cluster workload object (dep, rs, ..) within -// a federated workload object. -type ClusterPreferences struct { - // Minimum number of replicas that should be assigned to this cluster workload object. 0 by default. - // +optional - MinReplicas int64 - - // Maximum number of replicas that should be assigned to this cluster workload object. - // Unbounded if no value provided (default). - // +optional - MaxReplicas *int64 - - // A number expressing the preference to put an additional replica to this cluster workload object. - // 0 by default. - Weight int64 -} - -// Annotation for a federated service to keep record of service loadbalancer ingresses in federated cluster -type FederatedServiceIngress struct { - // List of loadbalancer ingress of a service in all federated clusters - // +optional - Items []ClusterServiceIngress `json:"items,omitempty"` -} - -// Loadbalancer ingresses of a service within a federated cluster -type ClusterServiceIngress struct { - // Cluster is the name of the federated cluster - Cluster string `json:"cluster"` - // List of loadbalancer ingresses of a federated service within a federated cluster - Items []v1.LoadBalancerIngress `json:"items"` -} diff --git a/federation/apis/federation/v1beta1/BUILD b/federation/apis/federation/v1beta1/BUILD deleted file mode 100644 index 2533e534ff2..00000000000 --- a/federation/apis/federation/v1beta1/BUILD +++ /dev/null @@ -1,51 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "defaults.go", - "doc.go", - "generated.pb.go", - "register.go", - "types.go", - "types_swagger_doc_generated.go", - "zz_generated.conversion.go", - "zz_generated.deepcopy.go", - "zz_generated.defaults.go", - ], - importpath = "k8s.io/kubernetes/federation/apis/federation/v1beta1", - deps = [ - "//federation/apis/federation:go_default_library", - "//pkg/api:go_default_library", - "//vendor/github.com/gogo/protobuf/proto:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) - -filegroup( - name = "go_default_library_protos", - srcs = ["generated.proto"], - visibility = ["//visibility:public"], -) diff --git a/federation/apis/federation/v1beta1/defaults.go b/federation/apis/federation/v1beta1/defaults.go deleted file mode 100644 index 37abb53bd21..00000000000 --- a/federation/apis/federation/v1beta1/defaults.go +++ /dev/null @@ -1,25 +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 v1beta1 - -import ( - "k8s.io/apimachinery/pkg/runtime" -) - -func addDefaultingFuncs(scheme *runtime.Scheme) error { - return RegisterDefaults(scheme) -} diff --git a/federation/apis/federation/v1beta1/doc.go b/federation/apis/federation/v1beta1/doc.go deleted file mode 100644 index 7382fb5b8e7..00000000000 --- a/federation/apis/federation/v1beta1/doc.go +++ /dev/null @@ -1,21 +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,register -// +k8s:conversion-gen=k8s.io/kubernetes/federation/apis/federation -// +k8s:openapi-gen=true -// +k8s:defaulter-gen=TypeMeta -package v1beta1 // import "k8s.io/kubernetes/federation/apis/federation/v1beta1" diff --git a/federation/apis/federation/v1beta1/generated.pb.go b/federation/apis/federation/v1beta1/generated.pb.go deleted file mode 100644 index a93e24c3220..00000000000 --- a/federation/apis/federation/v1beta1/generated.pb.go +++ /dev/null @@ -1,1766 +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. -*/ - -// Code generated by protoc-gen-gogo. -// source: k8s.io/kubernetes/federation/apis/federation/v1beta1/generated.proto -// DO NOT EDIT! - -/* - Package v1beta1 is a generated protocol buffer package. - - It is generated from these files: - k8s.io/kubernetes/federation/apis/federation/v1beta1/generated.proto - - It has these top-level messages: - Cluster - ClusterCondition - ClusterList - ClusterSelectorRequirement - ClusterSpec - ClusterStatus - ServerAddressByClientCIDR -*/ -package v1beta1 - -import proto "github.com/gogo/protobuf/proto" -import fmt "fmt" -import math "math" - -import k8s_io_api_core_v1 "k8s.io/api/core/v1" - -import strings "strings" -import reflect "reflect" - -import io "io" - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package - -func (m *Cluster) Reset() { *m = Cluster{} } -func (*Cluster) ProtoMessage() {} -func (*Cluster) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{0} } - -func (m *ClusterCondition) Reset() { *m = ClusterCondition{} } -func (*ClusterCondition) ProtoMessage() {} -func (*ClusterCondition) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{1} } - -func (m *ClusterList) Reset() { *m = ClusterList{} } -func (*ClusterList) ProtoMessage() {} -func (*ClusterList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{2} } - -func (m *ClusterSelectorRequirement) Reset() { *m = ClusterSelectorRequirement{} } -func (*ClusterSelectorRequirement) ProtoMessage() {} -func (*ClusterSelectorRequirement) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{3} -} - -func (m *ClusterSpec) Reset() { *m = ClusterSpec{} } -func (*ClusterSpec) ProtoMessage() {} -func (*ClusterSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{4} } - -func (m *ClusterStatus) Reset() { *m = ClusterStatus{} } -func (*ClusterStatus) ProtoMessage() {} -func (*ClusterStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{5} } - -func (m *ServerAddressByClientCIDR) Reset() { *m = ServerAddressByClientCIDR{} } -func (*ServerAddressByClientCIDR) ProtoMessage() {} -func (*ServerAddressByClientCIDR) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{6} -} - -func init() { - proto.RegisterType((*Cluster)(nil), "k8s.io.kubernetes.federation.apis.federation.v1beta1.Cluster") - proto.RegisterType((*ClusterCondition)(nil), "k8s.io.kubernetes.federation.apis.federation.v1beta1.ClusterCondition") - proto.RegisterType((*ClusterList)(nil), "k8s.io.kubernetes.federation.apis.federation.v1beta1.ClusterList") - proto.RegisterType((*ClusterSelectorRequirement)(nil), "k8s.io.kubernetes.federation.apis.federation.v1beta1.ClusterSelectorRequirement") - proto.RegisterType((*ClusterSpec)(nil), "k8s.io.kubernetes.federation.apis.federation.v1beta1.ClusterSpec") - proto.RegisterType((*ClusterStatus)(nil), "k8s.io.kubernetes.federation.apis.federation.v1beta1.ClusterStatus") - proto.RegisterType((*ServerAddressByClientCIDR)(nil), "k8s.io.kubernetes.federation.apis.federation.v1beta1.ServerAddressByClientCIDR") -} -func (m *Cluster) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *Cluster) MarshalTo(dAtA []byte) (int, error) { - var i int - _ = i - var l int - _ = l - dAtA[i] = 0xa - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n1, err := m.ObjectMeta.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n1 - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) - n2, err := m.Spec.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n2 - dAtA[i] = 0x1a - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) - n3, err := m.Status.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n3 - return i, nil -} - -func (m *ClusterCondition) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ClusterCondition) MarshalTo(dAtA []byte) (int, error) { - var i int - _ = i - var l int - _ = l - dAtA[i] = 0xa - i++ - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) - i += copy(dAtA[i:], m.Type) - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Status))) - i += copy(dAtA[i:], m.Status) - dAtA[i] = 0x1a - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.LastProbeTime.Size())) - n4, err := m.LastProbeTime.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n4 - dAtA[i] = 0x22 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.LastTransitionTime.Size())) - n5, err := m.LastTransitionTime.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n5 - dAtA[i] = 0x2a - i++ - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) - i += copy(dAtA[i:], m.Reason) - dAtA[i] = 0x32 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) - i += copy(dAtA[i:], m.Message) - return i, nil -} - -func (m *ClusterList) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ClusterList) MarshalTo(dAtA []byte) (int, error) { - var i int - _ = i - var l int - _ = l - dAtA[i] = 0xa - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n6, err := m.ListMeta.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n6 - if len(m.Items) > 0 { - for _, msg := range m.Items { - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) - n, err := msg.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n - } - } - return i, nil -} - -func (m *ClusterSelectorRequirement) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ClusterSelectorRequirement) MarshalTo(dAtA []byte) (int, error) { - var i int - _ = i - var l int - _ = l - dAtA[i] = 0xa - i++ - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Key))) - i += copy(dAtA[i:], m.Key) - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Operator))) - i += copy(dAtA[i:], m.Operator) - if len(m.Values) > 0 { - for _, s := range m.Values { - dAtA[i] = 0x1a - i++ - l = len(s) - for l >= 1<<7 { - dAtA[i] = uint8(uint64(l)&0x7f | 0x80) - l >>= 7 - i++ - } - dAtA[i] = uint8(l) - i++ - i += copy(dAtA[i:], s) - } - } - return i, nil -} - -func (m *ClusterSpec) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ClusterSpec) MarshalTo(dAtA []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.ServerAddressByClientCIDRs) > 0 { - for _, msg := range m.ServerAddressByClientCIDRs { - dAtA[i] = 0xa - i++ - i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) - n, err := msg.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n - } - } - if m.SecretRef != nil { - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.SecretRef.Size())) - n7, err := m.SecretRef.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n7 - } - return i, nil -} - -func (m *ClusterStatus) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ClusterStatus) MarshalTo(dAtA []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.Conditions) > 0 { - for _, msg := range m.Conditions { - dAtA[i] = 0xa - i++ - i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) - n, err := msg.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n - } - } - if len(m.Zones) > 0 { - for _, s := range m.Zones { - dAtA[i] = 0x2a - i++ - l = len(s) - for l >= 1<<7 { - dAtA[i] = uint8(uint64(l)&0x7f | 0x80) - l >>= 7 - i++ - } - dAtA[i] = uint8(l) - i++ - i += copy(dAtA[i:], s) - } - } - dAtA[i] = 0x32 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Region))) - i += copy(dAtA[i:], m.Region) - return i, nil -} - -func (m *ServerAddressByClientCIDR) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ServerAddressByClientCIDR) MarshalTo(dAtA []byte) (int, error) { - var i int - _ = i - var l int - _ = l - dAtA[i] = 0xa - i++ - i = encodeVarintGenerated(dAtA, i, uint64(len(m.ClientCIDR))) - i += copy(dAtA[i:], m.ClientCIDR) - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(len(m.ServerAddress))) - i += copy(dAtA[i:], m.ServerAddress) - return i, nil -} - -func encodeFixed64Generated(dAtA []byte, offset int, v uint64) int { - dAtA[offset] = uint8(v) - dAtA[offset+1] = uint8(v >> 8) - dAtA[offset+2] = uint8(v >> 16) - dAtA[offset+3] = uint8(v >> 24) - dAtA[offset+4] = uint8(v >> 32) - dAtA[offset+5] = uint8(v >> 40) - dAtA[offset+6] = uint8(v >> 48) - dAtA[offset+7] = uint8(v >> 56) - return offset + 8 -} -func encodeFixed32Generated(dAtA []byte, offset int, v uint32) int { - dAtA[offset] = uint8(v) - dAtA[offset+1] = uint8(v >> 8) - dAtA[offset+2] = uint8(v >> 16) - dAtA[offset+3] = uint8(v >> 24) - return offset + 4 -} -func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - dAtA[offset] = uint8(v) - return offset + 1 -} -func (m *Cluster) Size() (n int) { - 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 *ClusterCondition) Size() (n int) { - 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.LastProbeTime.Size() - 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 *ClusterList) Size() (n int) { - 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 *ClusterSelectorRequirement) Size() (n int) { - var l int - _ = l - l = len(m.Key) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Operator) - n += 1 + l + sovGenerated(uint64(l)) - if len(m.Values) > 0 { - for _, s := range m.Values { - l = len(s) - n += 1 + l + sovGenerated(uint64(l)) - } - } - return n -} - -func (m *ClusterSpec) Size() (n int) { - var l int - _ = l - if len(m.ServerAddressByClientCIDRs) > 0 { - for _, e := range m.ServerAddressByClientCIDRs { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - } - if m.SecretRef != nil { - l = m.SecretRef.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - return n -} - -func (m *ClusterStatus) Size() (n int) { - 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.Zones) > 0 { - for _, s := range m.Zones { - l = len(s) - n += 1 + l + sovGenerated(uint64(l)) - } - } - l = len(m.Region) - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func (m *ServerAddressByClientCIDR) Size() (n int) { - var l int - _ = l - l = len(m.ClientCIDR) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.ServerAddress) - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func sovGenerated(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } - } - return n -} -func sozGenerated(x uint64) (n int) { - return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (this *Cluster) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&Cluster{`, - `ObjectMeta:` + strings.Replace(strings.Replace(this.ObjectMeta.String(), "ObjectMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ObjectMeta", 1), `&`, ``, 1) + `,`, - `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "ClusterSpec", "ClusterSpec", 1), `&`, ``, 1) + `,`, - `Status:` + strings.Replace(strings.Replace(this.Status.String(), "ClusterStatus", "ClusterStatus", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s -} -func (this *ClusterCondition) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ClusterCondition{`, - `Type:` + fmt.Sprintf("%v", this.Type) + `,`, - `Status:` + fmt.Sprintf("%v", this.Status) + `,`, - `LastProbeTime:` + strings.Replace(strings.Replace(this.LastProbeTime.String(), "Time", "k8s_io_apimachinery_pkg_apis_meta_v1.Time", 1), `&`, ``, 1) + `,`, - `LastTransitionTime:` + strings.Replace(strings.Replace(this.LastTransitionTime.String(), "Time", "k8s_io_apimachinery_pkg_apis_meta_v1.Time", 1), `&`, ``, 1) + `,`, - `Reason:` + fmt.Sprintf("%v", this.Reason) + `,`, - `Message:` + fmt.Sprintf("%v", this.Message) + `,`, - `}`, - }, "") - return s -} -func (this *ClusterList) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ClusterList{`, - `ListMeta:` + strings.Replace(strings.Replace(this.ListMeta.String(), "ListMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ListMeta", 1), `&`, ``, 1) + `,`, - `Items:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Items), "Cluster", "Cluster", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s -} -func (this *ClusterSelectorRequirement) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ClusterSelectorRequirement{`, - `Key:` + fmt.Sprintf("%v", this.Key) + `,`, - `Operator:` + fmt.Sprintf("%v", this.Operator) + `,`, - `Values:` + fmt.Sprintf("%v", this.Values) + `,`, - `}`, - }, "") - return s -} -func (this *ClusterSpec) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ClusterSpec{`, - `ServerAddressByClientCIDRs:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ServerAddressByClientCIDRs), "ServerAddressByClientCIDR", "ServerAddressByClientCIDR", 1), `&`, ``, 1) + `,`, - `SecretRef:` + strings.Replace(fmt.Sprintf("%v", this.SecretRef), "LocalObjectReference", "k8s_io_api_core_v1.LocalObjectReference", 1) + `,`, - `}`, - }, "") - return s -} -func (this *ClusterStatus) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ClusterStatus{`, - `Conditions:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Conditions), "ClusterCondition", "ClusterCondition", 1), `&`, ``, 1) + `,`, - `Zones:` + fmt.Sprintf("%v", this.Zones) + `,`, - `Region:` + fmt.Sprintf("%v", this.Region) + `,`, - `}`, - }, "") - return s -} -func (this *ServerAddressByClientCIDR) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ServerAddressByClientCIDR{`, - `ClientCIDR:` + fmt.Sprintf("%v", this.ClientCIDR) + `,`, - `ServerAddress:` + fmt.Sprintf("%v", this.ServerAddress) + `,`, - `}`, - }, "") - 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 *Cluster) 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: Cluster: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Cluster: 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 > 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 > 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 > 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 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ClusterCondition) 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: ClusterCondition: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ClusterCondition: 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 > l { - return io.ErrUnexpectedEOF - } - m.Type = ClusterConditionType(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 > 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 LastProbeTime", 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 > l { - return io.ErrUnexpectedEOF - } - if err := m.LastProbeTime.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - 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 > l { - return io.ErrUnexpectedEOF - } - if err := m.LastTransitionTime.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 5: - 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 > l { - return io.ErrUnexpectedEOF - } - m.Reason = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 6: - 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 > 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 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ClusterList) 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: ClusterList: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ClusterList: 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 > 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 > l { - return io.ErrUnexpectedEOF - } - m.Items = append(m.Items, Cluster{}) - 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 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ClusterSelectorRequirement) 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: ClusterSelectorRequirement: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ClusterSelectorRequirement: 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 > 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 Operator", 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 > l { - return io.ErrUnexpectedEOF - } - m.Operator = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Values", 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 > l { - return io.ErrUnexpectedEOF - } - m.Values = append(m.Values, string(dAtA[iNdEx:postIndex])) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ClusterSpec) 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: ClusterSpec: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ClusterSpec: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ServerAddressByClientCIDRs", 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 > l { - return io.ErrUnexpectedEOF - } - m.ServerAddressByClientCIDRs = append(m.ServerAddressByClientCIDRs, ServerAddressByClientCIDR{}) - if err := m.ServerAddressByClientCIDRs[len(m.ServerAddressByClientCIDRs)-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 SecretRef", 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 > l { - return io.ErrUnexpectedEOF - } - if m.SecretRef == nil { - m.SecretRef = &k8s_io_api_core_v1.LocalObjectReference{} - } - if err := m.SecretRef.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 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ClusterStatus) 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: ClusterStatus: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ClusterStatus: 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 > l { - return io.ErrUnexpectedEOF - } - m.Conditions = append(m.Conditions, ClusterCondition{}) - if err := m.Conditions[len(m.Conditions)-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 Zones", 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 > l { - return io.ErrUnexpectedEOF - } - m.Zones = append(m.Zones, string(dAtA[iNdEx:postIndex])) - iNdEx = postIndex - case 6: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Region", 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 > l { - return io.ErrUnexpectedEOF - } - m.Region = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ServerAddressByClientCIDR) 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: ServerAddressByClientCIDR: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ServerAddressByClientCIDR: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ClientCIDR", 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 > l { - return io.ErrUnexpectedEOF - } - m.ClientCIDR = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ServerAddress", 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 > l { - return io.ErrUnexpectedEOF - } - m.ServerAddress = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if 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 - 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 - } - } - return iNdEx, nil - case 1: - iNdEx += 8 - return iNdEx, nil - 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 - } - } - iNdEx += length - if length < 0 { - return 0, ErrInvalidLengthGenerated - } - return iNdEx, nil - case 3: - for { - var innerWire uint64 - var start int = iNdEx - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowGenerated - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - innerWire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - innerWireType := int(innerWire & 0x7) - if innerWireType == 4 { - break - } - next, err := skipGenerated(dAtA[start:]) - if err != nil { - return 0, err - } - iNdEx = start + next - } - return iNdEx, nil - case 4: - return iNdEx, nil - case 5: - iNdEx += 4 - return iNdEx, nil - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - } - panic("unreachable") -} - -var ( - ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") -) - -func init() { - proto.RegisterFile("k8s.io/kubernetes/federation/apis/federation/v1beta1/generated.proto", fileDescriptorGenerated) -} - -var fileDescriptorGenerated = []byte{ - // 871 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x54, 0xcf, 0x6f, 0xe3, 0x44, - 0x14, 0x8e, 0x9b, 0x26, 0x6d, 0xa6, 0x04, 0x56, 0x23, 0x90, 0x42, 0x24, 0x1c, 0x64, 0x21, 0x54, - 0x10, 0x6b, 0xd3, 0x52, 0xa1, 0x95, 0x10, 0x87, 0x75, 0x56, 0x48, 0x2b, 0xba, 0x2a, 0x9a, 0x16, - 0x0e, 0x2b, 0x0e, 0x4c, 0x9c, 0x57, 0x77, 0x48, 0xec, 0x31, 0x33, 0xe3, 0x48, 0xd9, 0x13, 0x7f, - 0x00, 0x48, 0x9c, 0xf9, 0x37, 0x38, 0x73, 0xef, 0x09, 0xf6, 0xc0, 0x61, 0x4f, 0x11, 0x0d, 0xff, - 0xc5, 0x9e, 0xd0, 0x8c, 0x27, 0x4e, 0xbc, 0x69, 0x60, 0xb7, 0xbd, 0x79, 0x3e, 0xbf, 0xef, 0xfb, - 0xde, 0xbc, 0x1f, 0x83, 0x1e, 0x8c, 0xee, 0x49, 0x9f, 0xf1, 0x60, 0x94, 0x0f, 0x40, 0xa4, 0xa0, - 0x40, 0x06, 0xe7, 0x30, 0x04, 0x41, 0x15, 0xe3, 0x69, 0x40, 0x33, 0x56, 0x39, 0x4f, 0x0e, 0x06, - 0xa0, 0xe8, 0x41, 0x10, 0x43, 0xaa, 0x21, 0x18, 0xfa, 0x99, 0xe0, 0x8a, 0xe3, 0xa3, 0x42, 0xc5, - 0x5f, 0xaa, 0xf8, 0x4b, 0x96, 0xaf, 0x55, 0x56, 0xcf, 0x56, 0xa5, 0x7b, 0x37, 0x66, 0xea, 0x22, - 0x1f, 0xf8, 0x11, 0x4f, 0x82, 0x98, 0xc7, 0x3c, 0x30, 0x62, 0x83, 0xfc, 0xdc, 0x9c, 0xcc, 0xc1, - 0x7c, 0x15, 0x26, 0x5d, 0xcf, 0xa6, 0x4a, 0x33, 0x16, 0x44, 0x5c, 0x40, 0x30, 0x59, 0x4b, 0xa4, - 0x7b, 0xb4, 0x8c, 0x49, 0x68, 0x74, 0xc1, 0x52, 0x10, 0xd3, 0x20, 0x1b, 0xc5, 0xc5, 0x4d, 0x12, - 0x50, 0xf4, 0x3a, 0x56, 0xb0, 0x89, 0x25, 0xf2, 0x54, 0xb1, 0x04, 0xd6, 0x08, 0x9f, 0xfe, 0x1f, - 0x41, 0x46, 0x17, 0x90, 0xd0, 0x35, 0xde, 0x27, 0x9b, 0x78, 0xb9, 0x62, 0xe3, 0x80, 0xa5, 0x4a, - 0x2a, 0xf1, 0x22, 0xc9, 0xfb, 0x7d, 0x0b, 0xed, 0xf4, 0xc7, 0xb9, 0x54, 0x20, 0xf0, 0x77, 0x68, - 0x57, 0x5f, 0x62, 0x48, 0x15, 0xed, 0x38, 0xef, 0x3a, 0xfb, 0x7b, 0x87, 0x1f, 0xfb, 0xb6, 0xf6, - 0xab, 0x9a, 0x7e, 0x36, 0x8a, 0x8b, 0xb2, 0xeb, 0x68, 0x7f, 0x72, 0xe0, 0x9f, 0x0c, 0xbe, 0x87, - 0x48, 0x3d, 0x02, 0x45, 0x43, 0x7c, 0x39, 0xeb, 0xd5, 0xe6, 0xb3, 0x1e, 0x5a, 0x62, 0xa4, 0x54, - 0xc5, 0x11, 0xda, 0x96, 0x19, 0x44, 0x9d, 0x2d, 0xa3, 0x7e, 0xdf, 0xbf, 0x49, 0x67, 0x7d, 0x9b, - 0xee, 0x69, 0x06, 0x51, 0xf8, 0x9a, 0xb5, 0xdb, 0xd6, 0x27, 0x62, 0xc4, 0xf1, 0x08, 0x35, 0xa5, - 0xa2, 0x2a, 0x97, 0x9d, 0xba, 0xb1, 0xe9, 0xdf, 0xce, 0xc6, 0x48, 0x85, 0xaf, 0x5b, 0xa3, 0x66, - 0x71, 0x26, 0xd6, 0xc2, 0xfb, 0xa3, 0x8e, 0xee, 0xd8, 0xc8, 0x3e, 0x4f, 0x87, 0x4c, 0x4b, 0xe0, - 0x7b, 0x68, 0x5b, 0x4d, 0x33, 0x30, 0x45, 0x6c, 0x85, 0xef, 0x2d, 0x72, 0x3c, 0x9b, 0x66, 0xf0, - 0x7c, 0xd6, 0x7b, 0xf3, 0xc5, 0x78, 0x8d, 0x13, 0xc3, 0xc0, 0xc7, 0x65, 0xee, 0x5b, 0x86, 0x7b, - 0x54, 0xb5, 0x7d, 0x3e, 0xeb, 0x5d, 0x33, 0xa8, 0x7e, 0xa9, 0x54, 0x4d, 0x0e, 0xc7, 0xa8, 0x3d, - 0xa6, 0x52, 0x7d, 0x25, 0xf8, 0x00, 0xce, 0x58, 0x02, 0xb6, 0x20, 0x1f, 0xbe, 0x5c, 0x57, 0x35, - 0x23, 0x7c, 0xcb, 0x26, 0xd0, 0x3e, 0x5e, 0x15, 0x22, 0x55, 0x5d, 0x3c, 0x41, 0x58, 0x03, 0x67, - 0x82, 0xa6, 0xb2, 0xb8, 0x92, 0x76, 0xdb, 0x7e, 0x65, 0xb7, 0xae, 0x75, 0xc3, 0xc7, 0x6b, 0x6a, - 0xe4, 0x1a, 0x07, 0xfc, 0x3e, 0x6a, 0x0a, 0xa0, 0x92, 0xa7, 0x9d, 0x86, 0x29, 0x57, 0xd9, 0x25, - 0x62, 0x50, 0x62, 0xff, 0xe2, 0x0f, 0xd0, 0x4e, 0x02, 0x52, 0xd2, 0x18, 0x3a, 0x4d, 0x13, 0xf8, - 0x86, 0x0d, 0xdc, 0x79, 0x54, 0xc0, 0x64, 0xf1, 0xdf, 0xfb, 0xd3, 0x41, 0x7b, 0xb6, 0x41, 0xc7, - 0x4c, 0x2a, 0xfc, 0xed, 0xda, 0x52, 0xf8, 0x2f, 0x77, 0x21, 0xcd, 0x36, 0x2b, 0x71, 0xc7, 0x7a, - 0xed, 0x2e, 0x90, 0x95, 0x85, 0x18, 0xa0, 0x06, 0x53, 0x90, 0xe8, 0x76, 0xd7, 0xf7, 0xf7, 0x0e, - 0x3f, 0xbf, 0xd5, 0xa8, 0x86, 0x6d, 0xeb, 0xd4, 0x78, 0xa8, 0x35, 0x49, 0x21, 0xed, 0xfd, 0xec, - 0xa0, 0xee, 0x62, 0x98, 0x61, 0x0c, 0x91, 0xe2, 0x82, 0xc0, 0x0f, 0x39, 0x13, 0x90, 0x40, 0xaa, - 0xf0, 0x3b, 0xa8, 0x3e, 0x82, 0xa9, 0x9d, 0xd5, 0x3d, 0xab, 0x50, 0xff, 0x12, 0xa6, 0x44, 0xe3, - 0xf8, 0x23, 0xb4, 0xcb, 0x33, 0x6d, 0xc8, 0x85, 0x9d, 0xc9, 0xf2, 0x3e, 0x27, 0x16, 0x27, 0x65, - 0x04, 0xf6, 0x50, 0x73, 0x42, 0xc7, 0x39, 0xe8, 0xdd, 0xab, 0xef, 0xb7, 0x42, 0xa4, 0x9b, 0xf1, - 0x8d, 0x41, 0x88, 0xfd, 0xe3, 0xfd, 0xba, 0x55, 0x56, 0x58, 0x6f, 0x2d, 0xfe, 0xcd, 0x41, 0x5d, - 0x09, 0x62, 0x02, 0xe2, 0xfe, 0x70, 0x28, 0x40, 0xca, 0x70, 0xda, 0x1f, 0x33, 0x48, 0x55, 0xff, - 0xe1, 0x03, 0x22, 0x3b, 0x8e, 0xa9, 0xcc, 0xc9, 0xcd, 0x2a, 0x73, 0xba, 0x49, 0x37, 0xf4, 0xec, - 0x2d, 0xba, 0x1b, 0x43, 0x24, 0xf9, 0x8f, 0xb4, 0xf0, 0xd7, 0xa8, 0x25, 0x21, 0x12, 0xa0, 0x08, - 0x9c, 0xdb, 0xf7, 0x6c, 0x7f, 0x65, 0x30, 0x7c, 0xbd, 0x9b, 0x66, 0x0c, 0x78, 0x44, 0xc7, 0xc5, - 0x63, 0x48, 0xe0, 0x1c, 0x04, 0xa4, 0x11, 0x84, 0xed, 0xf9, 0xac, 0xd7, 0x3a, 0x5d, 0xd0, 0xc9, - 0x52, 0xc9, 0xfb, 0xcb, 0x41, 0xed, 0xca, 0xcb, 0x83, 0x9f, 0x20, 0x14, 0x2d, 0xf6, 0x7b, 0x51, - 0x8d, 0x2f, 0x6e, 0x35, 0x27, 0xe5, 0x73, 0xb1, 0x7c, 0xad, 0x4b, 0x48, 0x92, 0x15, 0x37, 0xdc, - 0x43, 0x8d, 0x27, 0x3c, 0x05, 0xd9, 0x69, 0x98, 0x6e, 0xb6, 0xf4, 0x6c, 0x3d, 0xd6, 0x00, 0x29, - 0xf0, 0x62, 0x01, 0x63, 0xc6, 0x53, 0xbb, 0x57, 0x2b, 0x0b, 0xa8, 0x51, 0x62, 0xff, 0x7a, 0x3f, - 0x39, 0xe8, 0xed, 0x8d, 0x85, 0xc6, 0x87, 0x08, 0x45, 0xe5, 0xc9, 0x4e, 0xe2, 0x32, 0xb5, 0xf2, - 0x0f, 0x59, 0x89, 0xc2, 0x9f, 0xa1, 0x76, 0xa5, 0x3b, 0x76, 0x38, 0xcb, 0xf7, 0xaa, 0xe2, 0x46, - 0xaa, 0xb1, 0xe1, 0xdd, 0xcb, 0x2b, 0xb7, 0xf6, 0xf4, 0xca, 0xad, 0x3d, 0xbb, 0x72, 0x6b, 0x3f, - 0xce, 0x5d, 0xe7, 0x72, 0xee, 0x3a, 0x4f, 0xe7, 0xae, 0xf3, 0x6c, 0xee, 0x3a, 0x7f, 0xcf, 0x5d, - 0xe7, 0x97, 0x7f, 0xdc, 0xda, 0xe3, 0x1d, 0x5b, 0xb7, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0xed, - 0x72, 0x6d, 0x62, 0xc8, 0x08, 0x00, 0x00, -} diff --git a/federation/apis/federation/v1beta1/generated.proto b/federation/apis/federation/v1beta1/generated.proto deleted file mode 100644 index 5e1f6652874..00000000000 --- a/federation/apis/federation/v1beta1/generated.proto +++ /dev/null @@ -1,150 +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. -*/ - - -// This file was autogenerated by go-to-protobuf. Do not edit it manually! - -syntax = 'proto2'; - -package k8s.io.kubernetes.federation.apis.federation.v1beta1; - -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"; -import "k8s.io/apimachinery/pkg/util/intstr/generated.proto"; - -// Package-wide variables from generator "generated". -option go_package = "v1beta1"; - -// Information about a registered cluster in a federated kubernetes setup. Clusters are not namespaced and have unique names in the federation. -message Cluster { - // Standard object's metadata. - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata - // +optional - optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - - // Spec defines the behavior of the Cluster. - // +optional - optional ClusterSpec spec = 2; - - // Status describes the current status of a Cluster - // +optional - optional ClusterStatus status = 3; -} - -// ClusterCondition describes current state of a cluster. -message ClusterCondition { - // Type of cluster condition, Complete or Failed. - optional string type = 1; - - // Status of the condition, one of True, False, Unknown. - optional string status = 2; - - // Last time the condition was checked. - // +optional - optional k8s.io.apimachinery.pkg.apis.meta.v1.Time lastProbeTime = 3; - - // Last time the condition transit from one status to another. - // +optional - optional k8s.io.apimachinery.pkg.apis.meta.v1.Time lastTransitionTime = 4; - - // (brief) reason for the condition's last transition. - // +optional - optional string reason = 5; - - // Human readable message indicating details about last transition. - // +optional - optional string message = 6; -} - -// A list of all the kubernetes clusters registered to the federation -message ClusterList { - // Standard list metadata. - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds - // +optional - optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; - - // List of Cluster objects. - repeated Cluster items = 2; -} - -// ClusterSelectorRequirement contains values, a key, and an operator that relates the key and values. -// The zero value of ClusterSelectorRequirement is invalid. -// ClusterSelectorRequirement implements both set based match and exact match -message ClusterSelectorRequirement { - // +patchMergeKey=key - // +patchStrategy=merge - optional string key = 1; - - // The Operator defines how the Key is matched to the Values. One of "in", "notin", - // "exists", "!", "=", "!=", "gt" or "lt". - optional string operator = 2; - - // An array of string values. If the operator is "in" or "notin", - // the values array must be non-empty. If the operator is "exists" or "!", - // the values array must be empty. If the operator is "gt" or "lt", the values - // array must have a single element, which will be interpreted as an integer. - // This array is replaced during a strategic merge patch. - // +optional - repeated string values = 3; -} - -// ClusterSpec describes the attributes of a kubernetes cluster. -message ClusterSpec { - // A map of client CIDR to server address. - // This is to help clients reach servers in the most network-efficient way possible. - // Clients can use the appropriate server address as per the CIDR that they match. - // In case of multiple matches, clients should use the longest matching CIDR. - // +patchMergeKey=clientCIDR - // +patchStrategy=merge - repeated ServerAddressByClientCIDR serverAddressByClientCIDRs = 1; - - // Name of the secret containing kubeconfig to access this cluster. - // The secret is read from the kubernetes cluster that is hosting federation control plane. - // Admin needs to ensure that the required secret exists. Secret should be in the same namespace where federation control plane is hosted and it should have kubeconfig in its data with key "kubeconfig". - // This will later be changed to a reference to secret in federation control plane when the federation control plane supports secrets. - // This can be left empty if the cluster allows insecure access. - // +optional - optional k8s.io.api.core.v1.LocalObjectReference secretRef = 2; -} - -// ClusterStatus is information about the current status of a cluster updated by cluster controller periodically. -message ClusterStatus { - // Conditions is an array of current cluster conditions. - // +optional - repeated ClusterCondition conditions = 1; - - // Zones is the list of availability zones in which the nodes of the cluster exist, e.g. 'us-east1-a'. - // These will always be in the same region. - // +optional - repeated string zones = 5; - - // Region is the name of the region in which all of the nodes in the cluster exist. e.g. 'us-east1'. - // +optional - optional string region = 6; -} - -// ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match. -message ServerAddressByClientCIDR { - // The CIDR with which clients can match their IP to figure out the server address that they should use. - optional string clientCIDR = 1; - - // Address of this server, suitable for a client that matches the above CIDR. - // This can be a hostname, hostname:port, IP or IP:port. - optional string serverAddress = 2; -} - diff --git a/federation/apis/federation/v1beta1/register.go b/federation/apis/federation/v1beta1/register.go deleted file mode 100644 index b14f5b1b061..00000000000 --- a/federation/apis/federation/v1beta1/register.go +++ /dev/null @@ -1,53 +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 v1beta1 - -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 = "federation" - -// SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"} - -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, addDefaultingFuncs) -} - -func addKnownTypes(scheme *runtime.Scheme) error { - scheme.AddKnownTypes(SchemeGroupVersion, - &Cluster{}, - &ClusterList{}, - ) - metav1.AddToGroupVersion(scheme, SchemeGroupVersion) - return nil -} diff --git a/federation/apis/federation/v1beta1/types.go b/federation/apis/federation/v1beta1/types.go deleted file mode 100644 index b8c9cca3fdb..00000000000 --- a/federation/apis/federation/v1beta1/types.go +++ /dev/null @@ -1,161 +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 v1beta1 - -import ( - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match. -type ServerAddressByClientCIDR struct { - // The CIDR with which clients can match their IP to figure out the server address that they should use. - ClientCIDR string `json:"clientCIDR" protobuf:"bytes,1,opt,name=clientCIDR"` - // Address of this server, suitable for a client that matches the above CIDR. - // This can be a hostname, hostname:port, IP or IP:port. - ServerAddress string `json:"serverAddress" protobuf:"bytes,2,opt,name=serverAddress"` -} - -// ClusterSpec describes the attributes of a kubernetes cluster. -type ClusterSpec struct { - // A map of client CIDR to server address. - // This is to help clients reach servers in the most network-efficient way possible. - // Clients can use the appropriate server address as per the CIDR that they match. - // In case of multiple matches, clients should use the longest matching CIDR. - // +patchMergeKey=clientCIDR - // +patchStrategy=merge - ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs" patchStrategy:"merge" patchMergeKey:"clientCIDR" protobuf:"bytes,1,rep,name=serverAddressByClientCIDRs"` - // Name of the secret containing kubeconfig to access this cluster. - // The secret is read from the kubernetes cluster that is hosting federation control plane. - // Admin needs to ensure that the required secret exists. Secret should be in the same namespace where federation control plane is hosted and it should have kubeconfig in its data with key "kubeconfig". - // This will later be changed to a reference to secret in federation control plane when the federation control plane supports secrets. - // This can be left empty if the cluster allows insecure access. - // +optional - SecretRef *v1.LocalObjectReference `json:"secretRef,omitempty" protobuf:"bytes,2,opt,name=secretRef"` -} - -type ClusterConditionType string - -// These are valid conditions of a cluster. -const ( - // ClusterReady means the cluster is ready to accept workloads. - ClusterReady ClusterConditionType = "Ready" - // ClusterOffline means the cluster is temporarily down or not reachable - ClusterOffline ClusterConditionType = "Offline" -) - -// ClusterCondition describes current state of a cluster. -type ClusterCondition struct { - // Type of cluster condition, Complete or Failed. - Type ClusterConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=ClusterConditionType"` - // Status of the condition, one of True, False, Unknown. - Status v1.ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=k8s.io/kubernetes/pkg/api/v1.ConditionStatus"` - // Last time the condition was checked. - // +optional - LastProbeTime metav1.Time `json:"lastProbeTime,omitempty" protobuf:"bytes,3,opt,name=lastProbeTime"` - // Last time the condition transit from one status to another. - // +optional - LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,4,opt,name=lastTransitionTime"` - // (brief) reason for the condition's last transition. - // +optional - Reason string `json:"reason,omitempty" protobuf:"bytes,5,opt,name=reason"` - // Human readable message indicating details about last transition. - // +optional - Message string `json:"message,omitempty" protobuf:"bytes,6,opt,name=message"` -} - -// ClusterStatus is information about the current status of a cluster updated by cluster controller periodically. -type ClusterStatus struct { - // Conditions is an array of current cluster conditions. - // +optional - Conditions []ClusterCondition `json:"conditions,omitempty" protobuf:"bytes,1,rep,name=conditions"` - // Zones is the list of availability zones in which the nodes of the cluster exist, e.g. 'us-east1-a'. - // These will always be in the same region. - // +optional - Zones []string `json:"zones,omitempty" protobuf:"bytes,5,rep,name=zones"` - // Region is the name of the region in which all of the nodes in the cluster exist. e.g. 'us-east1'. - // +optional - Region string `json:"region,omitempty" protobuf:"bytes,6,opt,name=region"` -} - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// +genclient:nonNamespaced - -// Information about a registered cluster in a federated kubernetes setup. Clusters are not namespaced and have unique names in the federation. -type Cluster struct { - metav1.TypeMeta `json:",inline"` - // Standard object's metadata. - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata - // +optional - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // Spec defines the behavior of the Cluster. - // +optional - Spec ClusterSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` - // Status describes the current status of a Cluster - // +optional - Status ClusterStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// A list of all the kubernetes clusters registered to the federation -type ClusterList struct { - metav1.TypeMeta `json:",inline"` - // Standard list metadata. - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds - // +optional - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // List of Cluster objects. - Items []Cluster `json:"items" protobuf:"bytes,2,rep,name=items"` -} - -// Expressed as value of annotation for selecting the clusters on which a resource is created. -type ClusterSelector []ClusterSelectorRequirement - -// ClusterSelectorRequirement contains values, a key, and an operator that relates the key and values. -// The zero value of ClusterSelectorRequirement is invalid. -// ClusterSelectorRequirement implements both set based match and exact match -type ClusterSelectorRequirement struct { - // +patchMergeKey=key - // +patchStrategy=merge - Key string `json:"key" patchStrategy:"merge" patchMergeKey:"key" protobuf:"bytes,1,opt,name=key"` - // The Operator defines how the Key is matched to the Values. One of "in", "notin", - // "exists", "!", "=", "!=", "gt" or "lt". - Operator string `json:"operator" protobuf:"bytes,2,opt,name=operator"` - // An array of string values. If the operator is "in" or "notin", - // the values array must be non-empty. If the operator is "exists" or "!", - // the values array must be empty. If the operator is "gt" or "lt", the values - // array must have a single element, which will be interpreted as an integer. - // This array is replaced during a strategic merge patch. - // +optional - Values []string `json:"values,omitempty" protobuf:"bytes,3,rep,name=values"` -} - -const ( - // FederationNamespaceSystem is the system namespace where we place federation control plane components. - FederationNamespaceSystem string = "federation-system" - - // FederationClusterSelectorAnnotation is used to determine placement of objects on federated clusters - FederationClusterSelectorAnnotation string = "federation.alpha.kubernetes.io/cluster-selector" - - // FederationOnlyClusterSelector is the cluster selector to indicate any object in - // federation having this annotation should not be synced to federated clusters. - FederationOnlyClusterSelector string = "federation.kubernetes.io/federation-control-plane=true" -) diff --git a/federation/apis/federation/v1beta1/types_swagger_doc_generated.go b/federation/apis/federation/v1beta1/types_swagger_doc_generated.go deleted file mode 100644 index dda8cf524b9..00000000000 --- a/federation/apis/federation/v1beta1/types_swagger_doc_generated.go +++ /dev/null @@ -1,106 +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 v1beta1 - -// 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-generated-swagger-docs.sh - -// AUTO-GENERATED FUNCTIONS START HERE -var map_Cluster = map[string]string{ - "": "Information about a registered cluster in a federated kubernetes setup. Clusters are not namespaced and have unique names in the federation.", - "metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", - "spec": "Spec defines the behavior of the Cluster.", - "status": "Status describes the current status of a Cluster", -} - -func (Cluster) SwaggerDoc() map[string]string { - return map_Cluster -} - -var map_ClusterCondition = map[string]string{ - "": "ClusterCondition describes current state of a cluster.", - "type": "Type of cluster condition, Complete or Failed.", - "status": "Status of the condition, one of True, False, Unknown.", - "lastProbeTime": "Last time the condition was checked.", - "lastTransitionTime": "Last time the condition transit from one status to another.", - "reason": "(brief) reason for the condition's last transition.", - "message": "Human readable message indicating details about last transition.", -} - -func (ClusterCondition) SwaggerDoc() map[string]string { - return map_ClusterCondition -} - -var map_ClusterList = map[string]string{ - "": "A list of all the kubernetes clusters registered to the federation", - "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds", - "items": "List of Cluster objects.", -} - -func (ClusterList) SwaggerDoc() map[string]string { - return map_ClusterList -} - -var map_ClusterSelectorRequirement = map[string]string{ - "": "ClusterSelectorRequirement contains values, a key, and an operator that relates the key and values. The zero value of ClusterSelectorRequirement is invalid. ClusterSelectorRequirement implements both set based match and exact match", - "operator": "The Operator defines how the Key is matched to the Values. One of \"in\", \"notin\", \"exists\", \"!\", \"=\", \"!=\", \"gt\" or \"lt\".", - "values": "An array of string values. If the operator is \"in\" or \"notin\", the values array must be non-empty. If the operator is \"exists\" or \"!\", the values array must be empty. If the operator is \"gt\" or \"lt\", the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch.", -} - -func (ClusterSelectorRequirement) SwaggerDoc() map[string]string { - return map_ClusterSelectorRequirement -} - -var map_ClusterSpec = map[string]string{ - "": "ClusterSpec describes the attributes of a kubernetes cluster.", - "serverAddressByClientCIDRs": "A map of client CIDR to server address. This is to help clients reach servers in the most network-efficient way possible. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR.", - "secretRef": "Name of the secret containing kubeconfig to access this cluster. The secret is read from the kubernetes cluster that is hosting federation control plane. Admin needs to ensure that the required secret exists. Secret should be in the same namespace where federation control plane is hosted and it should have kubeconfig in its data with key \"kubeconfig\". This will later be changed to a reference to secret in federation control plane when the federation control plane supports secrets. This can be left empty if the cluster allows insecure access.", -} - -func (ClusterSpec) SwaggerDoc() map[string]string { - return map_ClusterSpec -} - -var map_ClusterStatus = map[string]string{ - "": "ClusterStatus is information about the current status of a cluster updated by cluster controller periodically.", - "conditions": "Conditions is an array of current cluster conditions.", - "zones": "Zones is the list of availability zones in which the nodes of the cluster exist, e.g. 'us-east1-a'. These will always be in the same region.", - "region": "Region is the name of the region in which all of the nodes in the cluster exist. e.g. 'us-east1'.", -} - -func (ClusterStatus) SwaggerDoc() map[string]string { - return map_ClusterStatus -} - -var map_ServerAddressByClientCIDR = map[string]string{ - "": "ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match.", - "clientCIDR": "The CIDR with which clients can match their IP to figure out the server address that they should use.", - "serverAddress": "Address of this server, suitable for a client that matches the above CIDR. This can be a hostname, hostname:port, IP or IP:port.", -} - -func (ServerAddressByClientCIDR) SwaggerDoc() map[string]string { - return map_ServerAddressByClientCIDR -} - -// AUTO-GENERATED FUNCTIONS END HERE diff --git a/federation/apis/federation/v1beta1/zz_generated.conversion.go b/federation/apis/federation/v1beta1/zz_generated.conversion.go deleted file mode 100644 index 1f9e733ddcd..00000000000 --- a/federation/apis/federation/v1beta1/zz_generated.conversion.go +++ /dev/null @@ -1,205 +0,0 @@ -// +build !ignore_autogenerated - -/* -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. -*/ - -// This file was autogenerated by conversion-gen. Do not edit it manually! - -package v1beta1 - -import ( - v1 "k8s.io/api/core/v1" - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" - federation "k8s.io/kubernetes/federation/apis/federation" - api "k8s.io/kubernetes/pkg/api" - unsafe "unsafe" -) - -func init() { - localSchemeBuilder.Register(RegisterConversions) -} - -// RegisterConversions adds conversion functions to the given scheme. -// Public to allow building arbitrary schemes. -func RegisterConversions(scheme *runtime.Scheme) error { - return scheme.AddGeneratedConversionFuncs( - Convert_v1beta1_Cluster_To_federation_Cluster, - Convert_federation_Cluster_To_v1beta1_Cluster, - Convert_v1beta1_ClusterCondition_To_federation_ClusterCondition, - Convert_federation_ClusterCondition_To_v1beta1_ClusterCondition, - Convert_v1beta1_ClusterList_To_federation_ClusterList, - Convert_federation_ClusterList_To_v1beta1_ClusterList, - Convert_v1beta1_ClusterSpec_To_federation_ClusterSpec, - Convert_federation_ClusterSpec_To_v1beta1_ClusterSpec, - Convert_v1beta1_ClusterStatus_To_federation_ClusterStatus, - Convert_federation_ClusterStatus_To_v1beta1_ClusterStatus, - Convert_v1beta1_ServerAddressByClientCIDR_To_federation_ServerAddressByClientCIDR, - Convert_federation_ServerAddressByClientCIDR_To_v1beta1_ServerAddressByClientCIDR, - ) -} - -func autoConvert_v1beta1_Cluster_To_federation_Cluster(in *Cluster, out *federation.Cluster, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1beta1_ClusterSpec_To_federation_ClusterSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1beta1_ClusterStatus_To_federation_ClusterStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1beta1_Cluster_To_federation_Cluster is an autogenerated conversion function. -func Convert_v1beta1_Cluster_To_federation_Cluster(in *Cluster, out *federation.Cluster, s conversion.Scope) error { - return autoConvert_v1beta1_Cluster_To_federation_Cluster(in, out, s) -} - -func autoConvert_federation_Cluster_To_v1beta1_Cluster(in *federation.Cluster, out *Cluster, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_federation_ClusterSpec_To_v1beta1_ClusterSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_federation_ClusterStatus_To_v1beta1_ClusterStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_federation_Cluster_To_v1beta1_Cluster is an autogenerated conversion function. -func Convert_federation_Cluster_To_v1beta1_Cluster(in *federation.Cluster, out *Cluster, s conversion.Scope) error { - return autoConvert_federation_Cluster_To_v1beta1_Cluster(in, out, s) -} - -func autoConvert_v1beta1_ClusterCondition_To_federation_ClusterCondition(in *ClusterCondition, out *federation.ClusterCondition, s conversion.Scope) error { - out.Type = federation.ClusterConditionType(in.Type) - out.Status = api.ConditionStatus(in.Status) - out.LastProbeTime = in.LastProbeTime - out.LastTransitionTime = in.LastTransitionTime - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -// Convert_v1beta1_ClusterCondition_To_federation_ClusterCondition is an autogenerated conversion function. -func Convert_v1beta1_ClusterCondition_To_federation_ClusterCondition(in *ClusterCondition, out *federation.ClusterCondition, s conversion.Scope) error { - return autoConvert_v1beta1_ClusterCondition_To_federation_ClusterCondition(in, out, s) -} - -func autoConvert_federation_ClusterCondition_To_v1beta1_ClusterCondition(in *federation.ClusterCondition, out *ClusterCondition, s conversion.Scope) error { - out.Type = ClusterConditionType(in.Type) - out.Status = v1.ConditionStatus(in.Status) - out.LastProbeTime = in.LastProbeTime - out.LastTransitionTime = in.LastTransitionTime - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -// Convert_federation_ClusterCondition_To_v1beta1_ClusterCondition is an autogenerated conversion function. -func Convert_federation_ClusterCondition_To_v1beta1_ClusterCondition(in *federation.ClusterCondition, out *ClusterCondition, s conversion.Scope) error { - return autoConvert_federation_ClusterCondition_To_v1beta1_ClusterCondition(in, out, s) -} - -func autoConvert_v1beta1_ClusterList_To_federation_ClusterList(in *ClusterList, out *federation.ClusterList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]federation.Cluster)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_v1beta1_ClusterList_To_federation_ClusterList is an autogenerated conversion function. -func Convert_v1beta1_ClusterList_To_federation_ClusterList(in *ClusterList, out *federation.ClusterList, s conversion.Scope) error { - return autoConvert_v1beta1_ClusterList_To_federation_ClusterList(in, out, s) -} - -func autoConvert_federation_ClusterList_To_v1beta1_ClusterList(in *federation.ClusterList, out *ClusterList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]Cluster)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_federation_ClusterList_To_v1beta1_ClusterList is an autogenerated conversion function. -func Convert_federation_ClusterList_To_v1beta1_ClusterList(in *federation.ClusterList, out *ClusterList, s conversion.Scope) error { - return autoConvert_federation_ClusterList_To_v1beta1_ClusterList(in, out, s) -} - -func autoConvert_v1beta1_ClusterSpec_To_federation_ClusterSpec(in *ClusterSpec, out *federation.ClusterSpec, s conversion.Scope) error { - out.ServerAddressByClientCIDRs = *(*[]federation.ServerAddressByClientCIDR)(unsafe.Pointer(&in.ServerAddressByClientCIDRs)) - out.SecretRef = (*api.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) - return nil -} - -// Convert_v1beta1_ClusterSpec_To_federation_ClusterSpec is an autogenerated conversion function. -func Convert_v1beta1_ClusterSpec_To_federation_ClusterSpec(in *ClusterSpec, out *federation.ClusterSpec, s conversion.Scope) error { - return autoConvert_v1beta1_ClusterSpec_To_federation_ClusterSpec(in, out, s) -} - -func autoConvert_federation_ClusterSpec_To_v1beta1_ClusterSpec(in *federation.ClusterSpec, out *ClusterSpec, s conversion.Scope) error { - out.ServerAddressByClientCIDRs = *(*[]ServerAddressByClientCIDR)(unsafe.Pointer(&in.ServerAddressByClientCIDRs)) - out.SecretRef = (*v1.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) - return nil -} - -// Convert_federation_ClusterSpec_To_v1beta1_ClusterSpec is an autogenerated conversion function. -func Convert_federation_ClusterSpec_To_v1beta1_ClusterSpec(in *federation.ClusterSpec, out *ClusterSpec, s conversion.Scope) error { - return autoConvert_federation_ClusterSpec_To_v1beta1_ClusterSpec(in, out, s) -} - -func autoConvert_v1beta1_ClusterStatus_To_federation_ClusterStatus(in *ClusterStatus, out *federation.ClusterStatus, s conversion.Scope) error { - out.Conditions = *(*[]federation.ClusterCondition)(unsafe.Pointer(&in.Conditions)) - out.Zones = *(*[]string)(unsafe.Pointer(&in.Zones)) - out.Region = in.Region - return nil -} - -// Convert_v1beta1_ClusterStatus_To_federation_ClusterStatus is an autogenerated conversion function. -func Convert_v1beta1_ClusterStatus_To_federation_ClusterStatus(in *ClusterStatus, out *federation.ClusterStatus, s conversion.Scope) error { - return autoConvert_v1beta1_ClusterStatus_To_federation_ClusterStatus(in, out, s) -} - -func autoConvert_federation_ClusterStatus_To_v1beta1_ClusterStatus(in *federation.ClusterStatus, out *ClusterStatus, s conversion.Scope) error { - out.Conditions = *(*[]ClusterCondition)(unsafe.Pointer(&in.Conditions)) - out.Zones = *(*[]string)(unsafe.Pointer(&in.Zones)) - out.Region = in.Region - return nil -} - -// Convert_federation_ClusterStatus_To_v1beta1_ClusterStatus is an autogenerated conversion function. -func Convert_federation_ClusterStatus_To_v1beta1_ClusterStatus(in *federation.ClusterStatus, out *ClusterStatus, s conversion.Scope) error { - return autoConvert_federation_ClusterStatus_To_v1beta1_ClusterStatus(in, out, s) -} - -func autoConvert_v1beta1_ServerAddressByClientCIDR_To_federation_ServerAddressByClientCIDR(in *ServerAddressByClientCIDR, out *federation.ServerAddressByClientCIDR, s conversion.Scope) error { - out.ClientCIDR = in.ClientCIDR - out.ServerAddress = in.ServerAddress - return nil -} - -// Convert_v1beta1_ServerAddressByClientCIDR_To_federation_ServerAddressByClientCIDR is an autogenerated conversion function. -func Convert_v1beta1_ServerAddressByClientCIDR_To_federation_ServerAddressByClientCIDR(in *ServerAddressByClientCIDR, out *federation.ServerAddressByClientCIDR, s conversion.Scope) error { - return autoConvert_v1beta1_ServerAddressByClientCIDR_To_federation_ServerAddressByClientCIDR(in, out, s) -} - -func autoConvert_federation_ServerAddressByClientCIDR_To_v1beta1_ServerAddressByClientCIDR(in *federation.ServerAddressByClientCIDR, out *ServerAddressByClientCIDR, s conversion.Scope) error { - out.ClientCIDR = in.ClientCIDR - out.ServerAddress = in.ServerAddress - return nil -} - -// Convert_federation_ServerAddressByClientCIDR_To_v1beta1_ServerAddressByClientCIDR is an autogenerated conversion function. -func Convert_federation_ServerAddressByClientCIDR_To_v1beta1_ServerAddressByClientCIDR(in *federation.ServerAddressByClientCIDR, out *ServerAddressByClientCIDR, s conversion.Scope) error { - return autoConvert_federation_ServerAddressByClientCIDR_To_v1beta1_ServerAddressByClientCIDR(in, out, s) -} diff --git a/federation/apis/federation/v1beta1/zz_generated.deepcopy.go b/federation/apis/federation/v1beta1/zz_generated.deepcopy.go deleted file mode 100644 index 716b3af29f2..00000000000 --- a/federation/apis/federation/v1beta1/zz_generated.deepcopy.go +++ /dev/null @@ -1,245 +0,0 @@ -// +build !ignore_autogenerated - -/* -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. -*/ - -// This file was autogenerated by deepcopy-gen. Do not edit it manually! - -package v1beta1 - -import ( - v1 "k8s.io/api/core/v1" - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" -) - -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Cluster).DeepCopyInto(out.(*Cluster)) - return nil - }, InType: reflect.TypeOf(&Cluster{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterCondition).DeepCopyInto(out.(*ClusterCondition)) - return nil - }, InType: reflect.TypeOf(&ClusterCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterList).DeepCopyInto(out.(*ClusterList)) - return nil - }, InType: reflect.TypeOf(&ClusterList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterSelectorRequirement).DeepCopyInto(out.(*ClusterSelectorRequirement)) - return nil - }, InType: reflect.TypeOf(&ClusterSelectorRequirement{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterSpec).DeepCopyInto(out.(*ClusterSpec)) - return nil - }, InType: reflect.TypeOf(&ClusterSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterStatus).DeepCopyInto(out.(*ClusterStatus)) - return nil - }, InType: reflect.TypeOf(&ClusterStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ServerAddressByClientCIDR).DeepCopyInto(out.(*ServerAddressByClientCIDR)) - return nil - }, InType: reflect.TypeOf(&ServerAddressByClientCIDR{})}, - ) -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Cluster) DeepCopyInto(out *Cluster) { - *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 Cluster. -func (in *Cluster) DeepCopy() *Cluster { - if in == nil { - return nil - } - out := new(Cluster) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Cluster) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterCondition) DeepCopyInto(out *ClusterCondition) { - *out = *in - in.LastProbeTime.DeepCopyInto(&out.LastProbeTime) - in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterCondition. -func (in *ClusterCondition) DeepCopy() *ClusterCondition { - if in == nil { - return nil - } - out := new(ClusterCondition) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterList) DeepCopyInto(out *ClusterList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Cluster, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterList. -func (in *ClusterList) DeepCopy() *ClusterList { - if in == nil { - return nil - } - out := new(ClusterList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ClusterList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterSelectorRequirement) DeepCopyInto(out *ClusterSelectorRequirement) { - *out = *in - if in.Values != nil { - in, out := &in.Values, &out.Values - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterSelectorRequirement. -func (in *ClusterSelectorRequirement) DeepCopy() *ClusterSelectorRequirement { - if in == nil { - return nil - } - out := new(ClusterSelectorRequirement) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterSpec) DeepCopyInto(out *ClusterSpec) { - *out = *in - if in.ServerAddressByClientCIDRs != nil { - in, out := &in.ServerAddressByClientCIDRs, &out.ServerAddressByClientCIDRs - *out = make([]ServerAddressByClientCIDR, len(*in)) - copy(*out, *in) - } - if in.SecretRef != nil { - in, out := &in.SecretRef, &out.SecretRef - if *in == nil { - *out = nil - } else { - *out = new(v1.LocalObjectReference) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterSpec. -func (in *ClusterSpec) DeepCopy() *ClusterSpec { - if in == nil { - return nil - } - out := new(ClusterSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterStatus) DeepCopyInto(out *ClusterStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]ClusterCondition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Zones != nil { - in, out := &in.Zones, &out.Zones - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterStatus. -func (in *ClusterStatus) DeepCopy() *ClusterStatus { - if in == nil { - return nil - } - out := new(ClusterStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServerAddressByClientCIDR) DeepCopyInto(out *ServerAddressByClientCIDR) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerAddressByClientCIDR. -func (in *ServerAddressByClientCIDR) DeepCopy() *ServerAddressByClientCIDR { - if in == nil { - return nil - } - out := new(ServerAddressByClientCIDR) - in.DeepCopyInto(out) - return out -} diff --git a/federation/apis/federation/v1beta1/zz_generated.defaults.go b/federation/apis/federation/v1beta1/zz_generated.defaults.go deleted file mode 100644 index e24e70be38b..00000000000 --- a/federation/apis/federation/v1beta1/zz_generated.defaults.go +++ /dev/null @@ -1,32 +0,0 @@ -// +build !ignore_autogenerated - -/* -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. -*/ - -// This file was autogenerated by defaulter-gen. Do not edit it manually! - -package v1beta1 - -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/federation/apis/federation/validation/BUILD b/federation/apis/federation/validation/BUILD deleted file mode 100644 index 9c7c0caac8c..00000000000 --- a/federation/apis/federation/validation/BUILD +++ /dev/null @@ -1,44 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = ["validation.go"], - importpath = "k8s.io/kubernetes/federation/apis/federation/validation", - deps = [ - "//federation/apis/federation:go_default_library", - "//pkg/api/validation:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["validation_test.go"], - importpath = "k8s.io/kubernetes/federation/apis/federation/validation", - library = ":go_default_library", - deps = [ - "//federation/apis/federation:go_default_library", - "//pkg/api:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/apis/federation/validation/validation.go b/federation/apis/federation/validation/validation.go deleted file mode 100644 index 8db2ff48580..00000000000 --- a/federation/apis/federation/validation/validation.go +++ /dev/null @@ -1,64 +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 validation - -import ( - "fmt" - "net" - - "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/federation/apis/federation" - "k8s.io/kubernetes/pkg/api/validation" -) - -func ValidateClusterSpec(spec *federation.ClusterSpec, fieldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - // address is required. - if len(spec.ServerAddressByClientCIDRs) == 0 { - allErrs = append(allErrs, field.Required(fieldPath.Child("serverAddressByClientCIDRs"), "")) - } else { - for i, address := range spec.ServerAddressByClientCIDRs { - idxPath := fieldPath.Child("serverAddressByClientCIDRs").Index(i) - if len(address.ClientCIDR) > 0 { - if _, _, err := net.ParseCIDR(address.ClientCIDR); err != nil { - allErrs = append(allErrs, field.Invalid(idxPath.Child("clientCIDR"), address.ClientCIDR, fmt.Sprintf("must be a valid CIDR: %v", err))) - } - } - } - } - return allErrs -} - -func ValidateCluster(cluster *federation.Cluster) field.ErrorList { - allErrs := validation.ValidateObjectMeta(&cluster.ObjectMeta, false, validation.ValidateClusterName, field.NewPath("metadata")) - allErrs = append(allErrs, ValidateClusterSpec(&cluster.Spec, field.NewPath("spec"))...) - return allErrs -} - -func ValidateClusterUpdate(cluster, oldCluster *federation.Cluster) field.ErrorList { - allErrs := validation.ValidateObjectMetaUpdate(&cluster.ObjectMeta, &oldCluster.ObjectMeta, field.NewPath("metadata")) - if cluster.Name != oldCluster.Name { - allErrs = append(allErrs, field.Invalid(field.NewPath("meta", "name"), - cluster.Name+" != "+oldCluster.Name, "cannot change cluster name")) - } - return allErrs -} - -func ValidateClusterStatusUpdate(cluster, oldCluster *federation.Cluster) field.ErrorList { - allErrs := validation.ValidateObjectMetaUpdate(&cluster.ObjectMeta, &oldCluster.ObjectMeta, field.NewPath("metadata")) - return allErrs -} diff --git a/federation/apis/federation/validation/validation_test.go b/federation/apis/federation/validation/validation_test.go deleted file mode 100644 index 10054bf2945..00000000000 --- a/federation/apis/federation/validation/validation_test.go +++ /dev/null @@ -1,330 +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 validation - -import ( - "testing" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/federation/apis/federation" - "k8s.io/kubernetes/pkg/api" -) - -func TestValidateClusterSpec(t *testing.T) { - type validateClusterSpecTest struct { - testName string - spec *federation.ClusterSpec - path *field.Path - } - - successCases := []validateClusterSpecTest{ - { - testName: "normal CIDR", - spec: &federation.ClusterSpec{ - ServerAddressByClientCIDRs: []federation.ServerAddressByClientCIDR{ - { - ClientCIDR: "0.0.0.0/0", - ServerAddress: "localhost:8888", - }, - }, - }, - path: field.NewPath("spec"), - }, - { - testName: "missing CIDR", - spec: &federation.ClusterSpec{ - ServerAddressByClientCIDRs: []federation.ServerAddressByClientCIDR{ - { - ClientCIDR: "", - ServerAddress: "localhost:8888", - }, - }, - }, - path: field.NewPath("spec"), - }, - { - testName: "no host in CIDR", - spec: &federation.ClusterSpec{ - ServerAddressByClientCIDRs: []federation.ServerAddressByClientCIDR{ - { - ClientCIDR: "0.0.0.0/32", - ServerAddress: "localhost:8888", - }, - }, - }, - path: field.NewPath("spec"), - }, - } - for _, successCase := range successCases { - errs := ValidateClusterSpec(successCase.spec, successCase.path) - if len(errs) != 0 { - t.Errorf("expect success for testname: %q but got: %v", successCase.testName, errs) - } - } - - errorCases := []validateClusterSpecTest{ - { - testName: "invalid CIDR : network missing", - spec: &federation.ClusterSpec{ - ServerAddressByClientCIDRs: []federation.ServerAddressByClientCIDR{ - { - ClientCIDR: "0.0.0.0", - ServerAddress: "localhost:8888", - }, - }, - }, - path: field.NewPath("spec"), - }, - { - testName: "invalid CIDR : invalid address value", - spec: &federation.ClusterSpec{ - ServerAddressByClientCIDRs: []federation.ServerAddressByClientCIDR{ - { - ClientCIDR: "256.0.0.0/16", - ServerAddress: "localhost:8888", - }, - }, - }, - path: field.NewPath("spec"), - }, - { - testName: "invalid CIDR : invalid address formation", - spec: &federation.ClusterSpec{ - ServerAddressByClientCIDRs: []federation.ServerAddressByClientCIDR{ - { - ClientCIDR: "0.0.0/16", - ServerAddress: "localhost:8888", - }, - }, - }, - path: field.NewPath("spec"), - }, - { - testName: "invalid CIDR : invalid network num", - spec: &federation.ClusterSpec{ - ServerAddressByClientCIDRs: []federation.ServerAddressByClientCIDR{ - { - ClientCIDR: "0.0.0.0/33", - ServerAddress: "localhost:8888", - }, - }, - }, - path: field.NewPath("spec"), - }, - } - - for _, errorCase := range errorCases { - errs := ValidateClusterSpec(errorCase.spec, errorCase.path) - if len(errs) == 0 { - t.Errorf("expect failure for testname : %q", errorCase.testName) - } - } - -} - -func TestValidateCluster(t *testing.T) { - successCases := []federation.Cluster{ - { - ObjectMeta: metav1.ObjectMeta{Name: "cluster-s"}, - Spec: federation.ClusterSpec{ - ServerAddressByClientCIDRs: []federation.ServerAddressByClientCIDR{ - { - ClientCIDR: "0.0.0.0/0", - ServerAddress: "localhost:8888", - }, - }, - }, - }, - } - for _, successCase := range successCases { - errs := ValidateCluster(&successCase) - if len(errs) != 0 { - t.Errorf("expect success: %v", errs) - } - } - - errorCases := map[string]federation.Cluster{ - "missing cluster addresses": { - ObjectMeta: metav1.ObjectMeta{Name: "cluster-f"}, - }, - "empty cluster addresses": { - ObjectMeta: metav1.ObjectMeta{Name: "cluster-f"}, - Spec: federation.ClusterSpec{ - ServerAddressByClientCIDRs: []federation.ServerAddressByClientCIDR{}, - }}, - "invalid_label": { - ObjectMeta: metav1.ObjectMeta{ - Name: "cluster-f", - Labels: map[string]string{ - "NoUppercaseOrSpecialCharsLike=Equals": "bar", - }, - }, - }, - "invalid cluster name (is a subdomain)": { - ObjectMeta: metav1.ObjectMeta{Name: "mycluster.mycompany"}, - Spec: federation.ClusterSpec{ - ServerAddressByClientCIDRs: []federation.ServerAddressByClientCIDR{ - { - ClientCIDR: "0.0.0.0/0", - ServerAddress: "localhost:8888", - }, - }, - }, - }, - } - for testName, errorCase := range errorCases { - errs := ValidateCluster(&errorCase) - if len(errs) == 0 { - t.Errorf("expected failure for %s", testName) - } - } -} - -func TestValidateClusterUpdate(t *testing.T) { - type clusterUpdateTest struct { - old federation.Cluster - update federation.Cluster - } - successCases := []clusterUpdateTest{ - { - old: federation.Cluster{ - ObjectMeta: metav1.ObjectMeta{Name: "cluster-s"}, - Spec: federation.ClusterSpec{ - ServerAddressByClientCIDRs: []federation.ServerAddressByClientCIDR{ - { - ClientCIDR: "0.0.0.0/0", - ServerAddress: "localhost:8888", - }, - }, - }, - }, - update: federation.Cluster{ - ObjectMeta: metav1.ObjectMeta{Name: "cluster-s"}, - Spec: federation.ClusterSpec{ - ServerAddressByClientCIDRs: []federation.ServerAddressByClientCIDR{ - { - ClientCIDR: "0.0.0.0/0", - ServerAddress: "localhost:8888", - }, - }, - }, - }, - }, - } - for _, successCase := range successCases { - successCase.old.ObjectMeta.ResourceVersion = "1" - successCase.update.ObjectMeta.ResourceVersion = "1" - errs := ValidateClusterUpdate(&successCase.update, &successCase.old) - if len(errs) != 0 { - t.Errorf("expect success: %v", errs) - } - } - - errorCases := map[string]clusterUpdateTest{ - "cluster name changed": { - old: federation.Cluster{ - ObjectMeta: metav1.ObjectMeta{Name: "cluster-s"}, - Spec: federation.ClusterSpec{ - ServerAddressByClientCIDRs: []federation.ServerAddressByClientCIDR{ - { - ClientCIDR: "0.0.0.0/0", - ServerAddress: "localhost:8888", - }, - }, - }, - }, - update: federation.Cluster{ - ObjectMeta: metav1.ObjectMeta{Name: "cluster-newname"}, - Spec: federation.ClusterSpec{ - ServerAddressByClientCIDRs: []federation.ServerAddressByClientCIDR{ - { - ClientCIDR: "0.0.0.0/0", - ServerAddress: "localhost:8888", - }, - }, - }, - }, - }, - } - for testName, errorCase := range errorCases { - errs := ValidateClusterUpdate(&errorCase.update, &errorCase.old) - if len(errs) == 0 { - t.Errorf("expected failure: %s", testName) - } - } -} - -func TestValidateClusterStatusUpdate(t *testing.T) { - type clusterUpdateTest struct { - old federation.Cluster - update federation.Cluster - } - successCases := []clusterUpdateTest{ - { - old: federation.Cluster{ - ObjectMeta: metav1.ObjectMeta{Name: "cluster-s"}, - Spec: federation.ClusterSpec{ - ServerAddressByClientCIDRs: []federation.ServerAddressByClientCIDR{ - { - ClientCIDR: "0.0.0.0/0", - ServerAddress: "localhost:8888", - }, - }, - }, - Status: federation.ClusterStatus{ - Conditions: []federation.ClusterCondition{ - {Type: federation.ClusterReady, Status: api.ConditionTrue}, - }, - }, - }, - update: federation.Cluster{ - ObjectMeta: metav1.ObjectMeta{Name: "cluster-s"}, - Spec: federation.ClusterSpec{ - ServerAddressByClientCIDRs: []federation.ServerAddressByClientCIDR{ - { - ClientCIDR: "0.0.0.0/0", - ServerAddress: "localhost:8888", - }, - }, - }, - Status: federation.ClusterStatus{ - Conditions: []federation.ClusterCondition{ - {Type: federation.ClusterReady, Status: api.ConditionTrue}, - {Type: federation.ClusterOffline, Status: api.ConditionTrue}, - }, - }, - }, - }, - } - for _, successCase := range successCases { - successCase.old.ObjectMeta.ResourceVersion = "1" - successCase.update.ObjectMeta.ResourceVersion = "1" - errs := ValidateClusterUpdate(&successCase.update, &successCase.old) - if len(errs) != 0 { - t.Errorf("expect success: %v", errs) - } - } - - errorCases := map[string]clusterUpdateTest{} - for testName, errorCase := range errorCases { - errs := ValidateClusterStatusUpdate(&errorCase.update, &errorCase.old) - if len(errs) == 0 { - t.Errorf("expected failure: %s", testName) - } - } -} diff --git a/federation/apis/federation/zz_generated.deepcopy.go b/federation/apis/federation/zz_generated.deepcopy.go deleted file mode 100644 index a944361d962..00000000000 --- a/federation/apis/federation/zz_generated.deepcopy.go +++ /dev/null @@ -1,331 +0,0 @@ -// +build !ignore_autogenerated - -/* -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. -*/ - -// This file was autogenerated by deepcopy-gen. Do not edit it manually! - -package federation - -import ( - v1 "k8s.io/api/core/v1" - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" - api "k8s.io/kubernetes/pkg/api" - reflect "reflect" -) - -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Cluster).DeepCopyInto(out.(*Cluster)) - return nil - }, InType: reflect.TypeOf(&Cluster{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterCondition).DeepCopyInto(out.(*ClusterCondition)) - return nil - }, InType: reflect.TypeOf(&ClusterCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterList).DeepCopyInto(out.(*ClusterList)) - return nil - }, InType: reflect.TypeOf(&ClusterList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterPreferences).DeepCopyInto(out.(*ClusterPreferences)) - return nil - }, InType: reflect.TypeOf(&ClusterPreferences{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterServiceIngress).DeepCopyInto(out.(*ClusterServiceIngress)) - return nil - }, InType: reflect.TypeOf(&ClusterServiceIngress{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterSpec).DeepCopyInto(out.(*ClusterSpec)) - return nil - }, InType: reflect.TypeOf(&ClusterSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterStatus).DeepCopyInto(out.(*ClusterStatus)) - return nil - }, InType: reflect.TypeOf(&ClusterStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*FederatedServiceIngress).DeepCopyInto(out.(*FederatedServiceIngress)) - return nil - }, InType: reflect.TypeOf(&FederatedServiceIngress{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicaAllocationPreferences).DeepCopyInto(out.(*ReplicaAllocationPreferences)) - return nil - }, InType: reflect.TypeOf(&ReplicaAllocationPreferences{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ServerAddressByClientCIDR).DeepCopyInto(out.(*ServerAddressByClientCIDR)) - return nil - }, InType: reflect.TypeOf(&ServerAddressByClientCIDR{})}, - ) -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Cluster) DeepCopyInto(out *Cluster) { - *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 Cluster. -func (in *Cluster) DeepCopy() *Cluster { - if in == nil { - return nil - } - out := new(Cluster) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Cluster) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterCondition) DeepCopyInto(out *ClusterCondition) { - *out = *in - in.LastProbeTime.DeepCopyInto(&out.LastProbeTime) - in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterCondition. -func (in *ClusterCondition) DeepCopy() *ClusterCondition { - if in == nil { - return nil - } - out := new(ClusterCondition) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterList) DeepCopyInto(out *ClusterList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Cluster, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterList. -func (in *ClusterList) DeepCopy() *ClusterList { - if in == nil { - return nil - } - out := new(ClusterList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ClusterList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterPreferences) DeepCopyInto(out *ClusterPreferences) { - *out = *in - if in.MaxReplicas != nil { - in, out := &in.MaxReplicas, &out.MaxReplicas - if *in == nil { - *out = nil - } else { - *out = new(int64) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterPreferences. -func (in *ClusterPreferences) DeepCopy() *ClusterPreferences { - if in == nil { - return nil - } - out := new(ClusterPreferences) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterServiceIngress) DeepCopyInto(out *ClusterServiceIngress) { - *out = *in - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]v1.LoadBalancerIngress, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterServiceIngress. -func (in *ClusterServiceIngress) DeepCopy() *ClusterServiceIngress { - if in == nil { - return nil - } - out := new(ClusterServiceIngress) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterSpec) DeepCopyInto(out *ClusterSpec) { - *out = *in - if in.ServerAddressByClientCIDRs != nil { - in, out := &in.ServerAddressByClientCIDRs, &out.ServerAddressByClientCIDRs - *out = make([]ServerAddressByClientCIDR, len(*in)) - copy(*out, *in) - } - if in.SecretRef != nil { - in, out := &in.SecretRef, &out.SecretRef - if *in == nil { - *out = nil - } else { - *out = new(api.LocalObjectReference) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterSpec. -func (in *ClusterSpec) DeepCopy() *ClusterSpec { - if in == nil { - return nil - } - out := new(ClusterSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterStatus) DeepCopyInto(out *ClusterStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]ClusterCondition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Zones != nil { - in, out := &in.Zones, &out.Zones - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterStatus. -func (in *ClusterStatus) DeepCopy() *ClusterStatus { - if in == nil { - return nil - } - out := new(ClusterStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FederatedServiceIngress) DeepCopyInto(out *FederatedServiceIngress) { - *out = *in - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ClusterServiceIngress, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederatedServiceIngress. -func (in *FederatedServiceIngress) DeepCopy() *FederatedServiceIngress { - if in == nil { - return nil - } - out := new(FederatedServiceIngress) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ReplicaAllocationPreferences) DeepCopyInto(out *ReplicaAllocationPreferences) { - *out = *in - if in.Clusters != nil { - in, out := &in.Clusters, &out.Clusters - *out = make(map[string]ClusterPreferences, len(*in)) - for key, val := range *in { - newVal := new(ClusterPreferences) - val.DeepCopyInto(newVal) - (*out)[key] = *newVal - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicaAllocationPreferences. -func (in *ReplicaAllocationPreferences) DeepCopy() *ReplicaAllocationPreferences { - if in == nil { - return nil - } - out := new(ReplicaAllocationPreferences) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServerAddressByClientCIDR) DeepCopyInto(out *ServerAddressByClientCIDR) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerAddressByClientCIDR. -func (in *ServerAddressByClientCIDR) DeepCopy() *ServerAddressByClientCIDR { - if in == nil { - return nil - } - out := new(ServerAddressByClientCIDR) - in.DeepCopyInto(out) - return out -} diff --git a/federation/apis/openapi-spec/swagger.json b/federation/apis/openapi-spec/swagger.json deleted file mode 100644 index db795e8452d..00000000000 --- a/federation/apis/openapi-spec/swagger.json +++ /dev/null @@ -1,15312 +0,0 @@ -{ - "swagger": "2.0", - "info": { - "title": "Generic API Server", - "version": "v1.9.0" - }, - "paths": { - "/api/": { - "get": { - "description": "get available API versions", - "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core" - ], - "operationId": "getCoreAPIVersions", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIVersions" - } - }, - "401": { - "description": "Unauthorized" - } - } - } - }, - "/api/v1/": { - "get": { - "description": "get available resources", - "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "getCoreV1APIResources", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" - } - }, - "401": { - "description": "Unauthorized" - } - } - } - }, - "/api/v1/configmaps": { - "get": { - "description": "list or watch objects of kind ConfigMap", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "listCoreV1ConfigMapForAllNamespaces", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.ConfigMapList" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "ConfigMap" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/api/v1/events": { - "get": { - "description": "list or watch objects of kind Event", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "listCoreV1EventForAllNamespaces", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.EventList" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Event" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/api/v1/namespaces": { - "get": { - "description": "list or watch objects of kind Namespace", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "listCoreV1Namespace", - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.NamespaceList" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Namespace" - } - }, - "post": { - "description": "create a Namespace", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "createCoreV1Namespace", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Namespace" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Namespace" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Namespace" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Namespace" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "post", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Namespace" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/api/v1/namespaces/{namespace}/configmaps": { - "get": { - "description": "list or watch objects of kind ConfigMap", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "listCoreV1NamespacedConfigMap", - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.ConfigMapList" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "ConfigMap" - } - }, - "post": { - "description": "create a ConfigMap", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "createCoreV1NamespacedConfigMap", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.ConfigMap" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.ConfigMap" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.ConfigMap" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.ConfigMap" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "post", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "ConfigMap" - } - }, - "delete": { - "description": "delete collection of ConfigMap", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "deleteCoreV1CollectionNamespacedConfigMap", - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "deletecollection", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "ConfigMap" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/api/v1/namespaces/{namespace}/configmaps/{name}": { - "get": { - "description": "read the specified ConfigMap", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "readCoreV1NamespacedConfigMap", - "parameters": [ - { - "uniqueItems": true, - "type": "boolean", - "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", - "name": "exact", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Should this value be exported. Export strips fields that a user can not specify.", - "name": "export", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.ConfigMap" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "ConfigMap" - } - }, - "put": { - "description": "replace the specified ConfigMap", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "replaceCoreV1NamespacedConfigMap", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.ConfigMap" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.ConfigMap" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.ConfigMap" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "ConfigMap" - } - }, - "delete": { - "description": "delete a ConfigMap", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "deleteCoreV1NamespacedConfigMap", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" - } - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "gracePeriodSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "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.", - "name": "orphanDependents", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "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.", - "name": "propagationPolicy", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "delete", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "ConfigMap" - } - }, - "patch": { - "description": "partially update the specified ConfigMap", - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "patchCoreV1NamespacedConfigMap", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.ConfigMap" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "ConfigMap" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "name of the ConfigMap", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/api/v1/namespaces/{namespace}/events": { - "get": { - "description": "list or watch objects of kind Event", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "listCoreV1NamespacedEvent", - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.EventList" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Event" - } - }, - "post": { - "description": "create an Event", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "createCoreV1NamespacedEvent", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Event" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Event" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Event" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Event" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "post", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Event" - } - }, - "delete": { - "description": "delete collection of Event", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "deleteCoreV1CollectionNamespacedEvent", - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "deletecollection", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Event" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/api/v1/namespaces/{namespace}/events/{name}": { - "get": { - "description": "read the specified Event", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "readCoreV1NamespacedEvent", - "parameters": [ - { - "uniqueItems": true, - "type": "boolean", - "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", - "name": "exact", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Should this value be exported. Export strips fields that a user can not specify.", - "name": "export", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Event" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Event" - } - }, - "put": { - "description": "replace the specified Event", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "replaceCoreV1NamespacedEvent", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Event" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Event" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Event" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Event" - } - }, - "delete": { - "description": "delete an Event", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "deleteCoreV1NamespacedEvent", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" - } - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "gracePeriodSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "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.", - "name": "orphanDependents", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "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.", - "name": "propagationPolicy", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "delete", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Event" - } - }, - "patch": { - "description": "partially update the specified Event", - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "patchCoreV1NamespacedEvent", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Event" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Event" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "name of the Event", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/api/v1/namespaces/{namespace}/secrets": { - "get": { - "description": "list or watch objects of kind Secret", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "listCoreV1NamespacedSecret", - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.SecretList" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Secret" - } - }, - "post": { - "description": "create a Secret", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "createCoreV1NamespacedSecret", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Secret" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Secret" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Secret" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Secret" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "post", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Secret" - } - }, - "delete": { - "description": "delete collection of Secret", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "deleteCoreV1CollectionNamespacedSecret", - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "deletecollection", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Secret" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/api/v1/namespaces/{namespace}/secrets/{name}": { - "get": { - "description": "read the specified Secret", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "readCoreV1NamespacedSecret", - "parameters": [ - { - "uniqueItems": true, - "type": "boolean", - "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", - "name": "exact", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Should this value be exported. Export strips fields that a user can not specify.", - "name": "export", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Secret" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Secret" - } - }, - "put": { - "description": "replace the specified Secret", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "replaceCoreV1NamespacedSecret", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Secret" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Secret" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Secret" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Secret" - } - }, - "delete": { - "description": "delete a Secret", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "deleteCoreV1NamespacedSecret", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" - } - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "gracePeriodSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "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.", - "name": "orphanDependents", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "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.", - "name": "propagationPolicy", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "delete", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Secret" - } - }, - "patch": { - "description": "partially update the specified Secret", - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "patchCoreV1NamespacedSecret", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Secret" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Secret" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "name of the Secret", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/api/v1/namespaces/{namespace}/services": { - "get": { - "description": "list or watch objects of kind Service", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "listCoreV1NamespacedService", - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.ServiceList" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Service" - } - }, - "post": { - "description": "create a Service", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "createCoreV1NamespacedService", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Service" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Service" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Service" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Service" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "post", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Service" - } - }, - "delete": { - "description": "delete collection of Service", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "deleteCoreV1CollectionNamespacedService", - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "deletecollection", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Service" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/api/v1/namespaces/{namespace}/services/{name}": { - "get": { - "description": "read the specified Service", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "readCoreV1NamespacedService", - "parameters": [ - { - "uniqueItems": true, - "type": "boolean", - "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", - "name": "exact", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Should this value be exported. Export strips fields that a user can not specify.", - "name": "export", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Service" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Service" - } - }, - "put": { - "description": "replace the specified Service", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "replaceCoreV1NamespacedService", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Service" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Service" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Service" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Service" - } - }, - "delete": { - "description": "delete a Service", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "deleteCoreV1NamespacedService", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" - } - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "gracePeriodSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "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.", - "name": "orphanDependents", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "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.", - "name": "propagationPolicy", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "delete", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Service" - } - }, - "patch": { - "description": "partially update the specified Service", - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "patchCoreV1NamespacedService", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Service" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Service" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "name of the Service", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/api/v1/namespaces/{namespace}/services/{name}/status": { - "get": { - "description": "read status of the specified Service", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "readCoreV1NamespacedServiceStatus", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Service" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Service" - } - }, - "put": { - "description": "replace status of the specified Service", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "replaceCoreV1NamespacedServiceStatus", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Service" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Service" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Service" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Service" - } - }, - "patch": { - "description": "partially update status of the specified Service", - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "patchCoreV1NamespacedServiceStatus", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Service" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Service" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "name of the Service", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/api/v1/namespaces/{name}": { - "get": { - "description": "read the specified Namespace", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "readCoreV1Namespace", - "parameters": [ - { - "uniqueItems": true, - "type": "boolean", - "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", - "name": "exact", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Should this value be exported. Export strips fields that a user can not specify.", - "name": "export", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Namespace" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Namespace" - } - }, - "put": { - "description": "replace the specified Namespace", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "replaceCoreV1Namespace", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Namespace" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Namespace" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Namespace" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Namespace" - } - }, - "delete": { - "description": "delete a Namespace", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "deleteCoreV1Namespace", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" - } - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "gracePeriodSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "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.", - "name": "orphanDependents", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "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.", - "name": "propagationPolicy", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "delete", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Namespace" - } - }, - "patch": { - "description": "partially update the specified Namespace", - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "patchCoreV1Namespace", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Namespace" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Namespace" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "name of the Namespace", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/api/v1/namespaces/{name}/finalize": { - "put": { - "description": "replace finalize of the specified Namespace", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "replaceCoreV1NamespaceFinalize", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Namespace" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Namespace" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Namespace" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Namespace" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "name of the Namespace", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/api/v1/namespaces/{name}/status": { - "get": { - "description": "read status of the specified Namespace", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "readCoreV1NamespaceStatus", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Namespace" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Namespace" - } - }, - "put": { - "description": "replace status of the specified Namespace", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "replaceCoreV1NamespaceStatus", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Namespace" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Namespace" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Namespace" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Namespace" - } - }, - "patch": { - "description": "partially update status of the specified Namespace", - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "patchCoreV1NamespaceStatus", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.Namespace" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Namespace" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "name of the Namespace", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/api/v1/secrets": { - "get": { - "description": "list or watch objects of kind Secret", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "listCoreV1SecretForAllNamespaces", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.SecretList" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Secret" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/api/v1/services": { - "get": { - "description": "list or watch objects of kind Service", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "listCoreV1ServiceForAllNamespaces", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.core.v1.ServiceList" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Service" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/api/v1/watch/configmaps": { - "get": { - "description": "watch individual changes to a list of ConfigMap", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "watchCoreV1ConfigMapListForAllNamespaces", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watchlist", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "ConfigMap" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/api/v1/watch/events": { - "get": { - "description": "watch individual changes to a list of Event", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "watchCoreV1EventListForAllNamespaces", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watchlist", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Event" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/api/v1/watch/namespaces": { - "get": { - "description": "watch individual changes to a list of Namespace", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "watchCoreV1NamespaceList", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watchlist", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Namespace" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/api/v1/watch/namespaces/{namespace}/configmaps": { - "get": { - "description": "watch individual changes to a list of ConfigMap", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "watchCoreV1NamespacedConfigMapList", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watchlist", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "ConfigMap" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/api/v1/watch/namespaces/{namespace}/configmaps/{name}": { - "get": { - "description": "watch changes to an object of kind ConfigMap", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "watchCoreV1NamespacedConfigMap", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watch", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "ConfigMap" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "name of the ConfigMap", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/api/v1/watch/namespaces/{namespace}/events": { - "get": { - "description": "watch individual changes to a list of Event", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "watchCoreV1NamespacedEventList", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watchlist", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Event" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/api/v1/watch/namespaces/{namespace}/events/{name}": { - "get": { - "description": "watch changes to an object of kind Event", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "watchCoreV1NamespacedEvent", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watch", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Event" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "name of the Event", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/api/v1/watch/namespaces/{namespace}/secrets": { - "get": { - "description": "watch individual changes to a list of Secret", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "watchCoreV1NamespacedSecretList", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watchlist", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Secret" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/api/v1/watch/namespaces/{namespace}/secrets/{name}": { - "get": { - "description": "watch changes to an object of kind Secret", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "watchCoreV1NamespacedSecret", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watch", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Secret" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "name of the Secret", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/api/v1/watch/namespaces/{namespace}/services": { - "get": { - "description": "watch individual changes to a list of Service", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "watchCoreV1NamespacedServiceList", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watchlist", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Service" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/api/v1/watch/namespaces/{namespace}/services/{name}": { - "get": { - "description": "watch changes to an object of kind Service", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "watchCoreV1NamespacedService", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watch", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Service" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "name of the Service", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/api/v1/watch/namespaces/{name}": { - "get": { - "description": "watch changes to an object of kind Namespace", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "watchCoreV1Namespace", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watch", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Namespace" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "name of the Namespace", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/api/v1/watch/secrets": { - "get": { - "description": "watch individual changes to a list of Secret", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "watchCoreV1SecretListForAllNamespaces", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watchlist", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Secret" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/api/v1/watch/services": { - "get": { - "description": "watch individual changes to a list of Service", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "core_v1" - ], - "operationId": "watchCoreV1ServiceListForAllNamespaces", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watchlist", - "x-kubernetes-group-version-kind": { - "group": "", - "version": "v1", - "kind": "Service" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/apis/": { - "get": { - "description": "get available API versions", - "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "apis" - ], - "operationId": "getAPIVersions", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIGroupList" - } - }, - "401": { - "description": "Unauthorized" - } - } - } - }, - "/apis/extensions/": { - "get": { - "description": "get information of a group", - "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions" - ], - "operationId": "getExtensionsAPIGroup", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup" - } - }, - "401": { - "description": "Unauthorized" - } - } - } - }, - "/apis/extensions/v1beta1/": { - "get": { - "description": "get available resources", - "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "getExtensionsV1beta1APIResources", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" - } - }, - "401": { - "description": "Unauthorized" - } - } - } - }, - "/apis/extensions/v1beta1/daemonsets": { - "get": { - "description": "list or watch objects of kind DaemonSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "listExtensionsV1beta1DaemonSetForAllNamespaces", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DaemonSetList" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "DaemonSet" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/deployments": { - "get": { - "description": "list or watch objects of kind Deployment", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "listExtensionsV1beta1DeploymentForAllNamespaces", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DeploymentList" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Deployment" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/ingresses": { - "get": { - "description": "list or watch objects of kind Ingress", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "listExtensionsV1beta1IngressForAllNamespaces", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.IngressList" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Ingress" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/namespaces/{namespace}/daemonsets": { - "get": { - "description": "list or watch objects of kind DaemonSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "listExtensionsV1beta1NamespacedDaemonSet", - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DaemonSetList" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "DaemonSet" - } - }, - "post": { - "description": "create a DaemonSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "createExtensionsV1beta1NamespacedDaemonSet", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DaemonSet" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DaemonSet" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DaemonSet" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DaemonSet" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "post", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "DaemonSet" - } - }, - "delete": { - "description": "delete collection of DaemonSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "deleteExtensionsV1beta1CollectionNamespacedDaemonSet", - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "deletecollection", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "DaemonSet" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/namespaces/{namespace}/daemonsets/{name}": { - "get": { - "description": "read the specified DaemonSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "readExtensionsV1beta1NamespacedDaemonSet", - "parameters": [ - { - "uniqueItems": true, - "type": "boolean", - "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", - "name": "exact", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Should this value be exported. Export strips fields that a user can not specify.", - "name": "export", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DaemonSet" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "DaemonSet" - } - }, - "put": { - "description": "replace the specified DaemonSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "replaceExtensionsV1beta1NamespacedDaemonSet", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DaemonSet" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DaemonSet" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DaemonSet" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "DaemonSet" - } - }, - "delete": { - "description": "delete a DaemonSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "deleteExtensionsV1beta1NamespacedDaemonSet", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" - } - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "gracePeriodSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "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.", - "name": "orphanDependents", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "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.", - "name": "propagationPolicy", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "delete", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "DaemonSet" - } - }, - "patch": { - "description": "partially update the specified DaemonSet", - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "patchExtensionsV1beta1NamespacedDaemonSet", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DaemonSet" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "DaemonSet" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "name of the DaemonSet", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/namespaces/{namespace}/daemonsets/{name}/status": { - "get": { - "description": "read status of the specified DaemonSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "readExtensionsV1beta1NamespacedDaemonSetStatus", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DaemonSet" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "DaemonSet" - } - }, - "put": { - "description": "replace status of the specified DaemonSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "replaceExtensionsV1beta1NamespacedDaemonSetStatus", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DaemonSet" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DaemonSet" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DaemonSet" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "DaemonSet" - } - }, - "patch": { - "description": "partially update status of the specified DaemonSet", - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "patchExtensionsV1beta1NamespacedDaemonSetStatus", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DaemonSet" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "DaemonSet" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "name of the DaemonSet", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/namespaces/{namespace}/deployments": { - "get": { - "description": "list or watch objects of kind Deployment", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "listExtensionsV1beta1NamespacedDeployment", - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DeploymentList" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Deployment" - } - }, - "post": { - "description": "create a Deployment", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "createExtensionsV1beta1NamespacedDeployment", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Deployment" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Deployment" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Deployment" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Deployment" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "post", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Deployment" - } - }, - "delete": { - "description": "delete collection of Deployment", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "deleteExtensionsV1beta1CollectionNamespacedDeployment", - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "deletecollection", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Deployment" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/namespaces/{namespace}/deployments/{name}": { - "get": { - "description": "read the specified Deployment", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "readExtensionsV1beta1NamespacedDeployment", - "parameters": [ - { - "uniqueItems": true, - "type": "boolean", - "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", - "name": "exact", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Should this value be exported. Export strips fields that a user can not specify.", - "name": "export", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Deployment" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Deployment" - } - }, - "put": { - "description": "replace the specified Deployment", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "replaceExtensionsV1beta1NamespacedDeployment", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Deployment" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Deployment" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Deployment" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Deployment" - } - }, - "delete": { - "description": "delete a Deployment", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "deleteExtensionsV1beta1NamespacedDeployment", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" - } - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "gracePeriodSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "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.", - "name": "orphanDependents", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "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.", - "name": "propagationPolicy", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "delete", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Deployment" - } - }, - "patch": { - "description": "partially update the specified Deployment", - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "patchExtensionsV1beta1NamespacedDeployment", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Deployment" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Deployment" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "name of the Deployment", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/namespaces/{namespace}/deployments/{name}/rollback": { - "post": { - "description": "create rollback of a Deployment", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "createExtensionsV1beta1NamespacedDeploymentRollback", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DeploymentRollback" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DeploymentRollback" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DeploymentRollback" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DeploymentRollback" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "post", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "DeploymentRollback" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "name of the DeploymentRollback", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/namespaces/{namespace}/deployments/{name}/scale": { - "get": { - "description": "read scale of the specified Deployment", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "readExtensionsV1beta1NamespacedDeploymentScale", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Scale" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Scale" - } - }, - "put": { - "description": "replace scale of the specified Deployment", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "replaceExtensionsV1beta1NamespacedDeploymentScale", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Scale" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Scale" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Scale" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Scale" - } - }, - "patch": { - "description": "partially update scale of the specified Deployment", - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "patchExtensionsV1beta1NamespacedDeploymentScale", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Scale" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Scale" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "name of the Scale", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/namespaces/{namespace}/deployments/{name}/status": { - "get": { - "description": "read status of the specified Deployment", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "readExtensionsV1beta1NamespacedDeploymentStatus", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Deployment" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Deployment" - } - }, - "put": { - "description": "replace status of the specified Deployment", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "replaceExtensionsV1beta1NamespacedDeploymentStatus", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Deployment" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Deployment" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Deployment" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Deployment" - } - }, - "patch": { - "description": "partially update status of the specified Deployment", - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "patchExtensionsV1beta1NamespacedDeploymentStatus", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Deployment" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Deployment" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "name of the Deployment", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/namespaces/{namespace}/ingresses": { - "get": { - "description": "list or watch objects of kind Ingress", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "listExtensionsV1beta1NamespacedIngress", - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.IngressList" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Ingress" - } - }, - "post": { - "description": "create an Ingress", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "createExtensionsV1beta1NamespacedIngress", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Ingress" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Ingress" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Ingress" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Ingress" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "post", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Ingress" - } - }, - "delete": { - "description": "delete collection of Ingress", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "deleteExtensionsV1beta1CollectionNamespacedIngress", - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "deletecollection", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Ingress" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/namespaces/{namespace}/ingresses/{name}": { - "get": { - "description": "read the specified Ingress", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "readExtensionsV1beta1NamespacedIngress", - "parameters": [ - { - "uniqueItems": true, - "type": "boolean", - "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", - "name": "exact", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Should this value be exported. Export strips fields that a user can not specify.", - "name": "export", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Ingress" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Ingress" - } - }, - "put": { - "description": "replace the specified Ingress", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "replaceExtensionsV1beta1NamespacedIngress", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Ingress" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Ingress" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Ingress" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Ingress" - } - }, - "delete": { - "description": "delete an Ingress", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "deleteExtensionsV1beta1NamespacedIngress", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" - } - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "gracePeriodSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "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.", - "name": "orphanDependents", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "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.", - "name": "propagationPolicy", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "delete", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Ingress" - } - }, - "patch": { - "description": "partially update the specified Ingress", - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "patchExtensionsV1beta1NamespacedIngress", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Ingress" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Ingress" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "name of the Ingress", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/namespaces/{namespace}/ingresses/{name}/status": { - "get": { - "description": "read status of the specified Ingress", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "readExtensionsV1beta1NamespacedIngressStatus", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Ingress" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Ingress" - } - }, - "put": { - "description": "replace status of the specified Ingress", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "replaceExtensionsV1beta1NamespacedIngressStatus", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Ingress" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Ingress" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Ingress" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Ingress" - } - }, - "patch": { - "description": "partially update status of the specified Ingress", - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "patchExtensionsV1beta1NamespacedIngressStatus", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Ingress" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Ingress" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "name of the Ingress", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/namespaces/{namespace}/replicasets": { - "get": { - "description": "list or watch objects of kind ReplicaSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "listExtensionsV1beta1NamespacedReplicaSet", - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.ReplicaSetList" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "ReplicaSet" - } - }, - "post": { - "description": "create a ReplicaSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "createExtensionsV1beta1NamespacedReplicaSet", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.ReplicaSet" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.ReplicaSet" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.ReplicaSet" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.ReplicaSet" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "post", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "ReplicaSet" - } - }, - "delete": { - "description": "delete collection of ReplicaSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "deleteExtensionsV1beta1CollectionNamespacedReplicaSet", - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "deletecollection", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "ReplicaSet" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/namespaces/{namespace}/replicasets/{name}": { - "get": { - "description": "read the specified ReplicaSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "readExtensionsV1beta1NamespacedReplicaSet", - "parameters": [ - { - "uniqueItems": true, - "type": "boolean", - "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", - "name": "exact", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Should this value be exported. Export strips fields that a user can not specify.", - "name": "export", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.ReplicaSet" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "ReplicaSet" - } - }, - "put": { - "description": "replace the specified ReplicaSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "replaceExtensionsV1beta1NamespacedReplicaSet", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.ReplicaSet" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.ReplicaSet" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.ReplicaSet" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "ReplicaSet" - } - }, - "delete": { - "description": "delete a ReplicaSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "deleteExtensionsV1beta1NamespacedReplicaSet", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" - } - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "gracePeriodSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "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.", - "name": "orphanDependents", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "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.", - "name": "propagationPolicy", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "delete", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "ReplicaSet" - } - }, - "patch": { - "description": "partially update the specified ReplicaSet", - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "patchExtensionsV1beta1NamespacedReplicaSet", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.ReplicaSet" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "ReplicaSet" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "name of the ReplicaSet", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/namespaces/{namespace}/replicasets/{name}/scale": { - "get": { - "description": "read scale of the specified ReplicaSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "readExtensionsV1beta1NamespacedReplicaSetScale", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Scale" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Scale" - } - }, - "put": { - "description": "replace scale of the specified ReplicaSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "replaceExtensionsV1beta1NamespacedReplicaSetScale", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Scale" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Scale" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Scale" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Scale" - } - }, - "patch": { - "description": "partially update scale of the specified ReplicaSet", - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "patchExtensionsV1beta1NamespacedReplicaSetScale", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Scale" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Scale" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "name of the Scale", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/namespaces/{namespace}/replicasets/{name}/status": { - "get": { - "description": "read status of the specified ReplicaSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "readExtensionsV1beta1NamespacedReplicaSetStatus", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.ReplicaSet" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "ReplicaSet" - } - }, - "put": { - "description": "replace status of the specified ReplicaSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "replaceExtensionsV1beta1NamespacedReplicaSetStatus", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.ReplicaSet" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.ReplicaSet" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.ReplicaSet" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "ReplicaSet" - } - }, - "patch": { - "description": "partially update status of the specified ReplicaSet", - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "patchExtensionsV1beta1NamespacedReplicaSetStatus", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.ReplicaSet" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "ReplicaSet" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "name of the ReplicaSet", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/replicasets": { - "get": { - "description": "list or watch objects of kind ReplicaSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "listExtensionsV1beta1ReplicaSetForAllNamespaces", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.ReplicaSetList" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "ReplicaSet" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/watch/daemonsets": { - "get": { - "description": "watch individual changes to a list of DaemonSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "watchExtensionsV1beta1DaemonSetListForAllNamespaces", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watchlist", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "DaemonSet" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/watch/deployments": { - "get": { - "description": "watch individual changes to a list of Deployment", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "watchExtensionsV1beta1DeploymentListForAllNamespaces", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watchlist", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Deployment" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/watch/ingresses": { - "get": { - "description": "watch individual changes to a list of Ingress", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "watchExtensionsV1beta1IngressListForAllNamespaces", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watchlist", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Ingress" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/watch/namespaces/{namespace}/daemonsets": { - "get": { - "description": "watch individual changes to a list of DaemonSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "watchExtensionsV1beta1NamespacedDaemonSetList", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watchlist", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "DaemonSet" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/watch/namespaces/{namespace}/daemonsets/{name}": { - "get": { - "description": "watch changes to an object of kind DaemonSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "watchExtensionsV1beta1NamespacedDaemonSet", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watch", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "DaemonSet" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "name of the DaemonSet", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/watch/namespaces/{namespace}/deployments": { - "get": { - "description": "watch individual changes to a list of Deployment", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "watchExtensionsV1beta1NamespacedDeploymentList", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watchlist", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Deployment" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/watch/namespaces/{namespace}/deployments/{name}": { - "get": { - "description": "watch changes to an object of kind Deployment", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "watchExtensionsV1beta1NamespacedDeployment", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watch", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Deployment" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "name of the Deployment", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/watch/namespaces/{namespace}/ingresses": { - "get": { - "description": "watch individual changes to a list of Ingress", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "watchExtensionsV1beta1NamespacedIngressList", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watchlist", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Ingress" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/watch/namespaces/{namespace}/ingresses/{name}": { - "get": { - "description": "watch changes to an object of kind Ingress", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "watchExtensionsV1beta1NamespacedIngress", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watch", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "Ingress" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "name of the Ingress", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/watch/namespaces/{namespace}/replicasets": { - "get": { - "description": "watch individual changes to a list of ReplicaSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "watchExtensionsV1beta1NamespacedReplicaSetList", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watchlist", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "ReplicaSet" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/watch/namespaces/{namespace}/replicasets/{name}": { - "get": { - "description": "watch changes to an object of kind ReplicaSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "watchExtensionsV1beta1NamespacedReplicaSet", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watch", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "ReplicaSet" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "name of the ReplicaSet", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/apis/extensions/v1beta1/watch/replicasets": { - "get": { - "description": "watch individual changes to a list of ReplicaSet", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "extensions_v1beta1" - ], - "operationId": "watchExtensionsV1beta1ReplicaSetListForAllNamespaces", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watchlist", - "x-kubernetes-group-version-kind": { - "group": "extensions", - "version": "v1beta1", - "kind": "ReplicaSet" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/apis/federation/": { - "get": { - "description": "get information of a group", - "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "federation" - ], - "operationId": "getFederationAPIGroup", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup" - } - }, - "401": { - "description": "Unauthorized" - } - } - } - }, - "/apis/federation/v1beta1/": { - "get": { - "description": "get available resources", - "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "federation_v1beta1" - ], - "operationId": "getFederationV1beta1APIResources", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" - } - }, - "401": { - "description": "Unauthorized" - } - } - } - }, - "/apis/federation/v1beta1/clusters": { - "get": { - "description": "list or watch objects of kind Cluster", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "federation_v1beta1" - ], - "operationId": "listFederationV1beta1Cluster", - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.kubernetes.federation.apis.federation.v1beta1.ClusterList" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "list", - "x-kubernetes-group-version-kind": { - "group": "federation", - "version": "v1beta1", - "kind": "Cluster" - } - }, - "post": { - "description": "create a Cluster", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "federation_v1beta1" - ], - "operationId": "createFederationV1beta1Cluster", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.kubernetes.federation.apis.federation.v1beta1.Cluster" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.kubernetes.federation.apis.federation.v1beta1.Cluster" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.kubernetes.federation.apis.federation.v1beta1.Cluster" - } - }, - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/io.k8s.kubernetes.federation.apis.federation.v1beta1.Cluster" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "post", - "x-kubernetes-group-version-kind": { - "group": "federation", - "version": "v1beta1", - "kind": "Cluster" - } - }, - "delete": { - "description": "delete collection of Cluster", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "federation_v1beta1" - ], - "operationId": "deleteFederationV1beta1CollectionCluster", - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "deletecollection", - "x-kubernetes-group-version-kind": { - "group": "federation", - "version": "v1beta1", - "kind": "Cluster" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/apis/federation/v1beta1/clusters/{name}": { - "get": { - "description": "read the specified Cluster", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "federation_v1beta1" - ], - "operationId": "readFederationV1beta1Cluster", - "parameters": [ - { - "uniqueItems": true, - "type": "boolean", - "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", - "name": "exact", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Should this value be exported. Export strips fields that a user can not specify.", - "name": "export", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.kubernetes.federation.apis.federation.v1beta1.Cluster" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "federation", - "version": "v1beta1", - "kind": "Cluster" - } - }, - "put": { - "description": "replace the specified Cluster", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "federation_v1beta1" - ], - "operationId": "replaceFederationV1beta1Cluster", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.kubernetes.federation.apis.federation.v1beta1.Cluster" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.kubernetes.federation.apis.federation.v1beta1.Cluster" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.kubernetes.federation.apis.federation.v1beta1.Cluster" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "federation", - "version": "v1beta1", - "kind": "Cluster" - } - }, - "delete": { - "description": "delete a Cluster", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "federation_v1beta1" - ], - "operationId": "deleteFederationV1beta1Cluster", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" - } - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "gracePeriodSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "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.", - "name": "orphanDependents", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "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.", - "name": "propagationPolicy", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "delete", - "x-kubernetes-group-version-kind": { - "group": "federation", - "version": "v1beta1", - "kind": "Cluster" - } - }, - "patch": { - "description": "partially update the specified Cluster", - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "federation_v1beta1" - ], - "operationId": "patchFederationV1beta1Cluster", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.kubernetes.federation.apis.federation.v1beta1.Cluster" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "federation", - "version": "v1beta1", - "kind": "Cluster" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "name of the Cluster", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/apis/federation/v1beta1/clusters/{name}/status": { - "put": { - "description": "replace status of the specified Cluster", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "federation_v1beta1" - ], - "operationId": "replaceFederationV1beta1ClusterStatus", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.kubernetes.federation.apis.federation.v1beta1.Cluster" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.kubernetes.federation.apis.federation.v1beta1.Cluster" - } - }, - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/io.k8s.kubernetes.federation.apis.federation.v1beta1.Cluster" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "federation", - "version": "v1beta1", - "kind": "Cluster" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "name of the Cluster", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, - "/apis/federation/v1beta1/watch/clusters": { - "get": { - "description": "watch individual changes to a list of Cluster", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "federation_v1beta1" - ], - "operationId": "watchFederationV1beta1ClusterList", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watchlist", - "x-kubernetes-group-version-kind": { - "group": "federation", - "version": "v1beta1", - "kind": "Cluster" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/apis/federation/v1beta1/watch/clusters/{name}": { - "get": { - "description": "watch changes to an object of kind Cluster", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "schemes": [ - "https" - ], - "tags": [ - "federation_v1beta1" - ], - "operationId": "watchFederationV1beta1Cluster", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - } - }, - "401": { - "description": "Unauthorized" - } - }, - "x-kubernetes-action": "watch", - "x-kubernetes-group-version-kind": { - "group": "federation", - "version": "v1beta1", - "kind": "Cluster" - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "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 indicating the client must restart their list without the continue field. This 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.", - "name": "continue", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "name": "fieldSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "If true, partially initialized resources are included in the response.", - "name": "includeUninitialized", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "name": "labelSelector", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "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.", - "name": "limit", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "name of the Cluster", - "name": "name", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "name": "resourceVersion", - "in": "query" - }, - { - "uniqueItems": true, - "type": "integer", - "description": "Timeout for the list/watch call.", - "name": "timeoutSeconds", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "name": "watch", - "in": "query" - } - ] - }, - "/logs/": { - "get": { - "schemes": [ - "https" - ], - "tags": [ - "logs" - ], - "operationId": "logFileListHandler", - "responses": { - "401": { - "description": "Unauthorized" - } - } - } - }, - "/logs/{logpath}": { - "get": { - "schemes": [ - "https" - ], - "tags": [ - "logs" - ], - "operationId": "logFileHandler", - "responses": { - "401": { - "description": "Unauthorized" - } - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "path to the log", - "name": "logpath", - "in": "path", - "required": true - } - ] - }, - "/version/": { - "get": { - "description": "get the code version", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "schemes": [ - "https" - ], - "tags": [ - "version" - ], - "operationId": "getCodeVersion", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.version.Info" - } - }, - "401": { - "description": "Unauthorized" - } - } - } - } - }, - "definitions": { - "intstr.IntOrString": { - "description": "Deprecated. Please use io.k8s.apimachinery.pkg.util.intstr.IntOrString instead.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString" - }, - "io.k8s.api.core.v1.AWSElasticBlockStoreVolumeSource": { - "description": "Represents a Persistent Disk resource in AWS.\n\nAn AWS EBS disk must exist before mounting to a container. The disk must also be in the same AWS zone as the kubelet. An AWS EBS disk can only be mounted as read/write once. AWS EBS volumes support ownership management and SELinux relabeling.", - "required": [ - "volumeID" - ], - "properties": { - "fsType": { - "description": "Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore", - "type": "string" - }, - "partition": { - "description": "The partition in the volume that you want to mount. If omitted, the default is to mount by volume name. Examples: For volume /dev/sda1, you specify the partition as \"1\". Similarly, the volume partition for /dev/sda is \"0\" (or you can leave the property empty).", - "type": "integer", - "format": "int32" - }, - "readOnly": { - "description": "Specify \"true\" to force and set the ReadOnly property in VolumeMounts to \"true\". If omitted, the default is \"false\". More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore", - "type": "boolean" - }, - "volumeID": { - "description": "Unique ID of the persistent disk resource in AWS (Amazon EBS volume). More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.Affinity": { - "description": "Affinity is a group of affinity scheduling rules.", - "properties": { - "nodeAffinity": { - "description": "Describes node affinity scheduling rules for the pod.", - "$ref": "#/definitions/io.k8s.api.core.v1.NodeAffinity" - }, - "podAffinity": { - "description": "Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)).", - "$ref": "#/definitions/io.k8s.api.core.v1.PodAffinity" - }, - "podAntiAffinity": { - "description": "Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)).", - "$ref": "#/definitions/io.k8s.api.core.v1.PodAntiAffinity" - } - } - }, - "io.k8s.api.core.v1.AzureDiskVolumeSource": { - "description": "AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod.", - "required": [ - "diskName", - "diskURI" - ], - "properties": { - "cachingMode": { - "description": "Host Caching mode: None, Read Only, Read Write.", - "type": "string" - }, - "diskName": { - "description": "The Name of the data disk in the blob storage", - "type": "string" - }, - "diskURI": { - "description": "The URI the data disk in the blob storage", - "type": "string" - }, - "fsType": { - "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.", - "type": "string" - }, - "kind": { - "description": "Expected values Shared: multiple blob disks per storage account Dedicated: single blob disk per storage account Managed: azure managed data disk (only in managed availability set). defaults to shared", - "type": "string" - }, - "readOnly": { - "description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", - "type": "boolean" - } - } - }, - "io.k8s.api.core.v1.AzureFileVolumeSource": { - "description": "AzureFile represents an Azure File Service mount on the host and bind mount to the pod.", - "required": [ - "secretName", - "shareName" - ], - "properties": { - "readOnly": { - "description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", - "type": "boolean" - }, - "secretName": { - "description": "the name of secret that contains Azure Storage Account Name and Key", - "type": "string" - }, - "shareName": { - "description": "Share Name", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.Capabilities": { - "description": "Adds and removes POSIX capabilities from running containers.", - "properties": { - "add": { - "description": "Added capabilities", - "type": "array", - "items": { - "type": "string" - } - }, - "drop": { - "description": "Removed capabilities", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "io.k8s.api.core.v1.CephFSVolumeSource": { - "description": "Represents a Ceph Filesystem mount that lasts the lifetime of a pod Cephfs volumes do not support ownership management or SELinux relabeling.", - "required": [ - "monitors" - ], - "properties": { - "monitors": { - "description": "Required: Monitors is a collection of Ceph monitors More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it", - "type": "array", - "items": { - "type": "string" - } - }, - "path": { - "description": "Optional: Used as the mounted root, rather than the full Ceph tree, default is /", - "type": "string" - }, - "readOnly": { - "description": "Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it", - "type": "boolean" - }, - "secretFile": { - "description": "Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it", - "type": "string" - }, - "secretRef": { - "description": "Optional: SecretRef is reference to the authentication secret for User, default is empty. More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it", - "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference" - }, - "user": { - "description": "Optional: User is the rados user name, default is admin More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.CinderVolumeSource": { - "description": "Represents a cinder volume resource in Openstack. A Cinder volume must exist before mounting to a container. The volume must also be in the same region as the kubelet. Cinder volumes support ownership management and SELinux relabeling.", - "required": [ - "volumeID" - ], - "properties": { - "fsType": { - "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://releases.k8s.io/HEAD/examples/mysql-cinder-pd/README.md", - "type": "string" - }, - "readOnly": { - "description": "Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://releases.k8s.io/HEAD/examples/mysql-cinder-pd/README.md", - "type": "boolean" - }, - "volumeID": { - "description": "volume id used to identify the volume in cinder More info: https://releases.k8s.io/HEAD/examples/mysql-cinder-pd/README.md", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.ClientIPConfig": { - "description": "ClientIPConfig represents the configurations of Client IP based session affinity.", - "properties": { - "timeoutSeconds": { - "description": "timeoutSeconds specifies the seconds of ClientIP type session sticky time. The value must be \u003e0 \u0026\u0026 \u003c=86400(for 1 day) if ServiceAffinity == \"ClientIP\". Default value is 10800(for 3 hours).", - "type": "integer", - "format": "int32" - } - } - }, - "io.k8s.api.core.v1.ConfigMap": { - "description": "ConfigMap holds configuration data for pods to consume.", - "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/api-conventions.md#resources", - "type": "string" - }, - "data": { - "description": "Data contains the configuration data. Each key must consist of alphanumeric characters, '-', '_' or '.'.", - "type": "object", - "additionalProperties": { - "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/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "", - "version": "v1", - "kind": "ConfigMap" - } - ] - }, - "io.k8s.api.core.v1.ConfigMapEnvSource": { - "description": "ConfigMapEnvSource selects a ConfigMap to populate the environment variables with.\n\nThe contents of the target ConfigMap's Data field will represent the key-value pairs as environment variables.", - "properties": { - "name": { - "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", - "type": "string" - }, - "optional": { - "description": "Specify whether the ConfigMap must be defined", - "type": "boolean" - } - } - }, - "io.k8s.api.core.v1.ConfigMapKeySelector": { - "description": "Selects a key from a ConfigMap.", - "required": [ - "key" - ], - "properties": { - "key": { - "description": "The key to select.", - "type": "string" - }, - "name": { - "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", - "type": "string" - }, - "optional": { - "description": "Specify whether the ConfigMap or it's key must be defined", - "type": "boolean" - } - } - }, - "io.k8s.api.core.v1.ConfigMapList": { - "description": "ConfigMapList is a resource containing a list of ConfigMap objects.", - "required": [ - "items" - ], - "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/api-conventions.md#resources", - "type": "string" - }, - "items": { - "description": "Items is the list of ConfigMaps.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.ConfigMap" - } - }, - "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/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "description": "More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "", - "version": "v1", - "kind": "ConfigMapList" - } - ] - }, - "io.k8s.api.core.v1.ConfigMapProjection": { - "description": "Adapts a ConfigMap into a projected volume.\n\nThe contents of the target ConfigMap's Data field will be presented in a projected volume as files using the keys in the Data field as the file names, unless the items element is populated with specific mappings of keys to paths. Note that this is identical to a configmap volume source without the default mode.", - "properties": { - "items": { - "description": "If unspecified, each key-value pair in the Data field of the referenced ConfigMap will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the ConfigMap, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.KeyToPath" - } - }, - "name": { - "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", - "type": "string" - }, - "optional": { - "description": "Specify whether the ConfigMap or it's keys must be defined", - "type": "boolean" - } - } - }, - "io.k8s.api.core.v1.ConfigMapVolumeSource": { - "description": "Adapts a ConfigMap into a volume.\n\nThe contents of the target ConfigMap's Data field will be presented in a volume as files using the keys in the Data field as the file names, unless the items element is populated with specific mappings of keys to paths. ConfigMap volumes support ownership management and SELinux relabeling.", - "properties": { - "defaultMode": { - "description": "Optional: mode bits to use on created files by default. Must be a value between 0 and 0777. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.", - "type": "integer", - "format": "int32" - }, - "items": { - "description": "If unspecified, each key-value pair in the Data field of the referenced ConfigMap will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the ConfigMap, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.KeyToPath" - } - }, - "name": { - "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", - "type": "string" - }, - "optional": { - "description": "Specify whether the ConfigMap or it's keys must be defined", - "type": "boolean" - } - } - }, - "io.k8s.api.core.v1.Container": { - "description": "A single application container that you want to run within a pod.", - "required": [ - "name" - ], - "properties": { - "args": { - "description": "Arguments to the entrypoint. The docker image's CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell", - "type": "array", - "items": { - "type": "string" - } - }, - "command": { - "description": "Entrypoint array. Not executed within a shell. The docker image's ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell", - "type": "array", - "items": { - "type": "string" - } - }, - "env": { - "description": "List of environment variables to set in the container. Cannot be updated.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.EnvVar" - }, - "x-kubernetes-patch-merge-key": "name", - "x-kubernetes-patch-strategy": "merge" - }, - "envFrom": { - "description": "List of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. 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. Cannot be updated.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.EnvFromSource" - } - }, - "image": { - "description": "Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images This field is optional to allow higher level config management to default or override container images in workload controllers like Deployments and StatefulSets.", - "type": "string" - }, - "imagePullPolicy": { - "description": "Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images", - "type": "string" - }, - "lifecycle": { - "description": "Actions that the management system should take in response to container lifecycle events. Cannot be updated.", - "$ref": "#/definitions/io.k8s.api.core.v1.Lifecycle" - }, - "livenessProbe": { - "description": "Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", - "$ref": "#/definitions/io.k8s.api.core.v1.Probe" - }, - "name": { - "description": "Name of the container specified as a DNS_LABEL. Each container in a pod must have a unique name (DNS_LABEL). Cannot be updated.", - "type": "string" - }, - "ports": { - "description": "List of ports to expose from the container. Exposing a port here gives the system additional information about the network connections a container uses, but is primarily informational. Not specifying a port here DOES NOT prevent that port from being exposed. Any port which is listening on the default \"0.0.0.0\" address inside a container will be accessible from the network. Cannot be updated.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.ContainerPort" - }, - "x-kubernetes-patch-merge-key": "containerPort", - "x-kubernetes-patch-strategy": "merge" - }, - "readinessProbe": { - "description": "Periodic probe of container service readiness. Container will be removed from service endpoints if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", - "$ref": "#/definitions/io.k8s.api.core.v1.Probe" - }, - "resources": { - "description": "Compute Resources required by this container. Cannot be updated. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources", - "$ref": "#/definitions/io.k8s.api.core.v1.ResourceRequirements" - }, - "securityContext": { - "description": "Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md", - "$ref": "#/definitions/io.k8s.api.core.v1.SecurityContext" - }, - "stdin": { - "description": "Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false.", - "type": "boolean" - }, - "stdinOnce": { - "description": "Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false", - "type": "boolean" - }, - "terminationMessagePath": { - "description": "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.", - "type": "string" - }, - "terminationMessagePolicy": { - "description": "Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated.", - "type": "string" - }, - "tty": { - "description": "Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false.", - "type": "boolean" - }, - "volumeMounts": { - "description": "Pod volumes to mount into the container's filesystem. Cannot be updated.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.VolumeMount" - }, - "x-kubernetes-patch-merge-key": "mountPath", - "x-kubernetes-patch-strategy": "merge" - }, - "workingDir": { - "description": "Container's working directory. If not specified, the container runtime's default will be used, which might be configured in the container image. Cannot be updated.", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.ContainerPort": { - "description": "ContainerPort represents a network port in a single container.", - "required": [ - "containerPort" - ], - "properties": { - "containerPort": { - "description": "Number of port to expose on the pod's IP address. This must be a valid port number, 0 \u003c x \u003c 65536.", - "type": "integer", - "format": "int32" - }, - "hostIP": { - "description": "What host IP to bind the external port to.", - "type": "string" - }, - "hostPort": { - "description": "Number of port to expose on the host. If specified, this must be a valid port number, 0 \u003c x \u003c 65536. If HostNetwork is specified, this must match ContainerPort. Most containers do not need this.", - "type": "integer", - "format": "int32" - }, - "name": { - "description": "If specified, this must be an IANA_SVC_NAME and unique within the pod. Each named port in a pod must have a unique name. Name for the port that can be referred to by services.", - "type": "string" - }, - "protocol": { - "description": "Protocol for port. Must be UDP or TCP. Defaults to \"TCP\".", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.DownwardAPIProjection": { - "description": "Represents downward API info for projecting into a projected volume. Note that this is identical to a downwardAPI volume source without the default mode.", - "properties": { - "items": { - "description": "Items is a list of DownwardAPIVolume file", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.DownwardAPIVolumeFile" - } - } - } - }, - "io.k8s.api.core.v1.DownwardAPIVolumeFile": { - "description": "DownwardAPIVolumeFile represents information to create the file containing the pod field", - "required": [ - "path" - ], - "properties": { - "fieldRef": { - "description": "Required: Selects a field of the pod: only annotations, labels, name and namespace are supported.", - "$ref": "#/definitions/io.k8s.api.core.v1.ObjectFieldSelector" - }, - "mode": { - "description": "Optional: mode bits to use on this file, must be a value between 0 and 0777. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.", - "type": "integer", - "format": "int32" - }, - "path": { - "description": "Required: Path is the relative path name of the file to be created. Must not be absolute or contain the '..' path. Must be utf-8 encoded. The first item of the relative path must not start with '..'", - "type": "string" - }, - "resourceFieldRef": { - "description": "Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported.", - "$ref": "#/definitions/io.k8s.api.core.v1.ResourceFieldSelector" - } - } - }, - "io.k8s.api.core.v1.DownwardAPIVolumeSource": { - "description": "DownwardAPIVolumeSource represents a volume containing downward API info. Downward API volumes support ownership management and SELinux relabeling.", - "properties": { - "defaultMode": { - "description": "Optional: mode bits to use on created files by default. Must be a value between 0 and 0777. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.", - "type": "integer", - "format": "int32" - }, - "items": { - "description": "Items is a list of downward API volume file", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.DownwardAPIVolumeFile" - } - } - } - }, - "io.k8s.api.core.v1.EmptyDirVolumeSource": { - "description": "Represents an empty directory for a pod. Empty directory volumes support ownership management and SELinux relabeling.", - "properties": { - "medium": { - "description": "What type of storage medium should back this directory. The default is \"\" which means to use the node's default medium. Must be an empty string (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir", - "type": "string" - }, - "sizeLimit": { - "description": "Total amount of local storage required for this EmptyDir volume. The size limit is also applicable for memory medium. The maximum usage on memory medium EmptyDir would be the minimum value between the SizeLimit specified here and the sum of memory limits of all containers in a pod. The default is nil which means that the limit is undefined. More info: http://kubernetes.io/docs/user-guide/volumes#emptydir", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.api.resource.Quantity" - } - } - }, - "io.k8s.api.core.v1.EnvFromSource": { - "description": "EnvFromSource represents the source of a set of ConfigMaps", - "properties": { - "configMapRef": { - "description": "The ConfigMap to select from", - "$ref": "#/definitions/io.k8s.api.core.v1.ConfigMapEnvSource" - }, - "prefix": { - "description": "An optional identifer to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER.", - "type": "string" - }, - "secretRef": { - "description": "The Secret to select from", - "$ref": "#/definitions/io.k8s.api.core.v1.SecretEnvSource" - } - } - }, - "io.k8s.api.core.v1.EnvVar": { - "description": "EnvVar represents an environment variable present in a Container.", - "required": [ - "name" - ], - "properties": { - "name": { - "description": "Name of the environment variable. Must be a C_IDENTIFIER.", - "type": "string" - }, - "value": { - "description": "Variable references $(VAR_NAME) are expanded using the previous defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to \"\".", - "type": "string" - }, - "valueFrom": { - "description": "Source for the environment variable's value. Cannot be used if value is not empty.", - "$ref": "#/definitions/io.k8s.api.core.v1.EnvVarSource" - } - } - }, - "io.k8s.api.core.v1.EnvVarSource": { - "description": "EnvVarSource represents a source for the value of an EnvVar.", - "properties": { - "configMapKeyRef": { - "description": "Selects a key of a ConfigMap.", - "$ref": "#/definitions/io.k8s.api.core.v1.ConfigMapKeySelector" - }, - "fieldRef": { - "description": "Selects a field of the pod: supports metadata.name, metadata.namespace, metadata.labels, metadata.annotations, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP.", - "$ref": "#/definitions/io.k8s.api.core.v1.ObjectFieldSelector" - }, - "resourceFieldRef": { - "description": "Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.", - "$ref": "#/definitions/io.k8s.api.core.v1.ResourceFieldSelector" - }, - "secretKeyRef": { - "description": "Selects a key of a secret in the pod's namespace", - "$ref": "#/definitions/io.k8s.api.core.v1.SecretKeySelector" - } - } - }, - "io.k8s.api.core.v1.Event": { - "description": "Event is a report of an event somewhere in the cluster.", - "required": [ - "metadata", - "involvedObject" - ], - "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/api-conventions.md#resources", - "type": "string" - }, - "count": { - "description": "The number of times this event has occurred.", - "type": "integer", - "format": "int32" - }, - "firstTimestamp": { - "description": "The time at which the event was first recorded. (Time of server receipt is in TypeMeta.)", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" - }, - "involvedObject": { - "description": "The object that this event is about.", - "$ref": "#/definitions/io.k8s.api.core.v1.ObjectReference" - }, - "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/api-conventions.md#types-kinds", - "type": "string" - }, - "lastTimestamp": { - "description": "The time at which the most recent occurrence of this event was recorded.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" - }, - "message": { - "description": "A human-readable description of the status of this operation.", - "type": "string" - }, - "metadata": { - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" - }, - "reason": { - "description": "This should be a short, machine understandable string that gives the reason for the transition into the object's current status.", - "type": "string" - }, - "source": { - "description": "The component reporting this event. Should be a short machine understandable string.", - "$ref": "#/definitions/io.k8s.api.core.v1.EventSource" - }, - "type": { - "description": "Type of this event (Normal, Warning), new types could be added in the future", - "type": "string" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "", - "version": "v1", - "kind": "Event" - } - ] - }, - "io.k8s.api.core.v1.EventList": { - "description": "EventList is a list of events.", - "required": [ - "items" - ], - "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/api-conventions.md#resources", - "type": "string" - }, - "items": { - "description": "List of events", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.Event" - } - }, - "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/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "", - "version": "v1", - "kind": "EventList" - } - ] - }, - "io.k8s.api.core.v1.EventSource": { - "description": "EventSource contains information for an event.", - "properties": { - "component": { - "description": "Component from which the event is generated.", - "type": "string" - }, - "host": { - "description": "Node name on which the event is generated.", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.ExecAction": { - "description": "ExecAction describes a \"run in container\" action.", - "properties": { - "command": { - "description": "Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy.", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "io.k8s.api.core.v1.FCVolumeSource": { - "description": "Represents a Fibre Channel volume. Fibre Channel volumes can only be mounted as read/write once. Fibre Channel volumes support ownership management and SELinux relabeling.", - "properties": { - "fsType": { - "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.", - "type": "string" - }, - "lun": { - "description": "Optional: FC target lun number", - "type": "integer", - "format": "int32" - }, - "readOnly": { - "description": "Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", - "type": "boolean" - }, - "targetWWNs": { - "description": "Optional: FC target worldwide names (WWNs)", - "type": "array", - "items": { - "type": "string" - } - }, - "wwids": { - "description": "Optional: FC volume world wide identifiers (wwids) Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously.", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "io.k8s.api.core.v1.FlexVolumeSource": { - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.", - "required": [ - "driver" - ], - "properties": { - "driver": { - "description": "Driver is the name of the driver to use for this volume.", - "type": "string" - }, - "fsType": { - "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". The default filesystem depends on FlexVolume script.", - "type": "string" - }, - "options": { - "description": "Optional: Extra command options if any.", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "readOnly": { - "description": "Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", - "type": "boolean" - }, - "secretRef": { - "description": "Optional: SecretRef is reference to the secret object containing sensitive information to pass to the plugin scripts. This may be empty if no secret object is specified. If the secret object contains more than one secret, all secrets are passed to the plugin scripts.", - "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference" - } - } - }, - "io.k8s.api.core.v1.FlockerVolumeSource": { - "description": "Represents a Flocker volume mounted by the Flocker agent. One and only one of datasetName and datasetUUID should be set. Flocker volumes do not support ownership management or SELinux relabeling.", - "properties": { - "datasetName": { - "description": "Name of the dataset stored as metadata -\u003e name on the dataset for Flocker should be considered as deprecated", - "type": "string" - }, - "datasetUUID": { - "description": "UUID of the dataset. This is unique identifier of a Flocker dataset", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.GCEPersistentDiskVolumeSource": { - "description": "Represents a Persistent Disk resource in Google Compute Engine.\n\nA GCE PD must exist before mounting to a container. The disk must also be in the same GCE project and zone as the kubelet. A GCE PD can only be mounted as read/write once or read-only many times. GCE PDs support ownership management and SELinux relabeling.", - "required": [ - "pdName" - ], - "properties": { - "fsType": { - "description": "Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk", - "type": "string" - }, - "partition": { - "description": "The partition in the volume that you want to mount. If omitted, the default is to mount by volume name. Examples: For volume /dev/sda1, you specify the partition as \"1\". Similarly, the volume partition for /dev/sda is \"0\" (or you can leave the property empty). More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk", - "type": "integer", - "format": "int32" - }, - "pdName": { - "description": "Unique name of the PD resource in GCE. Used to identify the disk in GCE. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk", - "type": "string" - }, - "readOnly": { - "description": "ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk", - "type": "boolean" - } - } - }, - "io.k8s.api.core.v1.GitRepoVolumeSource": { - "description": "Represents a volume that is populated with the contents of a git repository. Git repo volumes do not support ownership management. Git repo volumes support SELinux relabeling.", - "required": [ - "repository" - ], - "properties": { - "directory": { - "description": "Target directory name. Must not contain or start with '..'. If '.' is supplied, the volume directory will be the git repository. Otherwise, if specified, the volume will contain the git repository in the subdirectory with the given name.", - "type": "string" - }, - "repository": { - "description": "Repository URL", - "type": "string" - }, - "revision": { - "description": "Commit hash for the specified revision.", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.GlusterfsVolumeSource": { - "description": "Represents a Glusterfs mount that lasts the lifetime of a pod. Glusterfs volumes do not support ownership management or SELinux relabeling.", - "required": [ - "endpoints", - "path" - ], - "properties": { - "endpoints": { - "description": "EndpointsName is the endpoint name that details Glusterfs topology. More info: https://releases.k8s.io/HEAD/examples/volumes/glusterfs/README.md#create-a-pod", - "type": "string" - }, - "path": { - "description": "Path is the Glusterfs volume path. More info: https://releases.k8s.io/HEAD/examples/volumes/glusterfs/README.md#create-a-pod", - "type": "string" - }, - "readOnly": { - "description": "ReadOnly here will force the Glusterfs volume to be mounted with read-only permissions. Defaults to false. More info: https://releases.k8s.io/HEAD/examples/volumes/glusterfs/README.md#create-a-pod", - "type": "boolean" - } - } - }, - "io.k8s.api.core.v1.HTTPGetAction": { - "description": "HTTPGetAction describes an action based on HTTP Get requests.", - "required": [ - "port" - ], - "properties": { - "host": { - "description": "Host name to connect to, defaults to the pod IP. You probably want to set \"Host\" in httpHeaders instead.", - "type": "string" - }, - "httpHeaders": { - "description": "Custom headers to set in the request. HTTP allows repeated headers.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.HTTPHeader" - } - }, - "path": { - "description": "Path to access on the HTTP server.", - "type": "string" - }, - "port": { - "description": "Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString" - }, - "scheme": { - "description": "Scheme to use for connecting to the host. Defaults to HTTP.", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.HTTPHeader": { - "description": "HTTPHeader describes a custom header to be used in HTTP probes", - "required": [ - "name", - "value" - ], - "properties": { - "name": { - "description": "The header field name", - "type": "string" - }, - "value": { - "description": "The header field value", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.Handler": { - "description": "Handler defines a specific action that should be taken", - "properties": { - "exec": { - "description": "One and only one of the following should be specified. Exec specifies the action to take.", - "$ref": "#/definitions/io.k8s.api.core.v1.ExecAction" - }, - "httpGet": { - "description": "HTTPGet specifies the http request to perform.", - "$ref": "#/definitions/io.k8s.api.core.v1.HTTPGetAction" - }, - "tcpSocket": { - "description": "TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported", - "$ref": "#/definitions/io.k8s.api.core.v1.TCPSocketAction" - } - } - }, - "io.k8s.api.core.v1.HostAlias": { - "description": "HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the pod's hosts file.", - "properties": { - "hostnames": { - "description": "Hostnames for the above IP address.", - "type": "array", - "items": { - "type": "string" - } - }, - "ip": { - "description": "IP address of the host file entry.", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.HostPathVolumeSource": { - "description": "Represents a host path mapped into a pod. Host path volumes do not support ownership management or SELinux relabeling.", - "required": [ - "path" - ], - "properties": { - "path": { - "description": "Path of the directory on the host. If the path is a symlink, it will follow the link to the real path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath", - "type": "string" - }, - "type": { - "description": "Type for HostPath Volume Defaults to \"\" More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.ISCSIVolumeSource": { - "description": "Represents an ISCSI disk. ISCSI volumes can only be mounted as read/write once. ISCSI volumes support ownership management and SELinux relabeling.", - "required": [ - "targetPortal", - "iqn", - "lun" - ], - "properties": { - "chapAuthDiscovery": { - "description": "whether support iSCSI Discovery CHAP authentication", - "type": "boolean" - }, - "chapAuthSession": { - "description": "whether support iSCSI Session CHAP authentication", - "type": "boolean" - }, - "fsType": { - "description": "Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi", - "type": "string" - }, - "initiatorName": { - "description": "Custom iSCSI initiator name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface \u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection.", - "type": "string" - }, - "iqn": { - "description": "Target iSCSI Qualified Name.", - "type": "string" - }, - "iscsiInterface": { - "description": "Optional: Defaults to 'default' (tcp). iSCSI interface name that uses an iSCSI transport.", - "type": "string" - }, - "lun": { - "description": "iSCSI target lun number.", - "type": "integer", - "format": "int32" - }, - "portals": { - "description": "iSCSI target portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).", - "type": "array", - "items": { - "type": "string" - } - }, - "readOnly": { - "description": "ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false.", - "type": "boolean" - }, - "secretRef": { - "description": "CHAP secret for iSCSI target and initiator authentication", - "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference" - }, - "targetPortal": { - "description": "iSCSI target portal. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.KeyToPath": { - "description": "Maps a string key to a path within a volume.", - "required": [ - "key", - "path" - ], - "properties": { - "key": { - "description": "The key to project.", - "type": "string" - }, - "mode": { - "description": "Optional: mode bits to use on this file, must be a value between 0 and 0777. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.", - "type": "integer", - "format": "int32" - }, - "path": { - "description": "The relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'.", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.Lifecycle": { - "description": "Lifecycle describes actions that the management system should take in response to container lifecycle events. For the PostStart and PreStop lifecycle handlers, management of the container blocks until the action is complete, unless the container process fails, in which case the handler is aborted.", - "properties": { - "postStart": { - "description": "PostStart is called immediately after a container is created. If the handler fails, the container is terminated and restarted according to its restart policy. Other management of the container blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks", - "$ref": "#/definitions/io.k8s.api.core.v1.Handler" - }, - "preStop": { - "description": "PreStop is called immediately before a container is terminated. The container is terminated after the handler completes. The reason for termination is passed to the handler. Regardless of the outcome of the handler, the container is eventually terminated. Other management of the container blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks", - "$ref": "#/definitions/io.k8s.api.core.v1.Handler" - } - } - }, - "io.k8s.api.core.v1.LoadBalancerIngress": { - "description": "LoadBalancerIngress represents the status of a load-balancer ingress point: traffic intended for the service should be sent to an ingress point.", - "properties": { - "hostname": { - "description": "Hostname is set for load-balancer ingress points that are DNS based (typically AWS load-balancers)", - "type": "string" - }, - "ip": { - "description": "IP is set for load-balancer ingress points that are IP based (typically GCE or OpenStack load-balancers)", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.LoadBalancerStatus": { - "description": "LoadBalancerStatus represents the status of a load-balancer.", - "properties": { - "ingress": { - "description": "Ingress is a list containing ingress points for the load-balancer. Traffic intended for the service should be sent to these ingress points.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.LoadBalancerIngress" - } - } - } - }, - "io.k8s.api.core.v1.LocalObjectReference": { - "description": "LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace.", - "properties": { - "name": { - "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.NFSVolumeSource": { - "description": "Represents an NFS mount that lasts the lifetime of a pod. NFS volumes do not support ownership management or SELinux relabeling.", - "required": [ - "server", - "path" - ], - "properties": { - "path": { - "description": "Path that is exported by the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs", - "type": "string" - }, - "readOnly": { - "description": "ReadOnly here will force the NFS export to be mounted with read-only permissions. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs", - "type": "boolean" - }, - "server": { - "description": "Server is the hostname or IP address of the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.Namespace": { - "description": "Namespace provides a scope for Names. Use of multiple namespaces is optional.", - "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/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/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" - }, - "spec": { - "description": "Spec defines the behavior of the Namespace. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status", - "$ref": "#/definitions/io.k8s.api.core.v1.NamespaceSpec" - }, - "status": { - "description": "Status describes the current status of a Namespace. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status", - "$ref": "#/definitions/io.k8s.api.core.v1.NamespaceStatus" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "", - "version": "v1", - "kind": "Namespace" - } - ] - }, - "io.k8s.api.core.v1.NamespaceList": { - "description": "NamespaceList is a list of Namespaces.", - "required": [ - "items" - ], - "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/api-conventions.md#resources", - "type": "string" - }, - "items": { - "description": "Items is the list of Namespace objects in the list. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.Namespace" - } - }, - "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/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "", - "version": "v1", - "kind": "NamespaceList" - } - ] - }, - "io.k8s.api.core.v1.NamespaceSpec": { - "description": "NamespaceSpec describes the attributes on a Namespace.", - "properties": { - "finalizers": { - "description": "Finalizers is an opaque list of values that must be empty to permanently remove object from storage. More info: https://git.k8s.io/community/contributors/design-proposals/namespaces.md#finalizers", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "io.k8s.api.core.v1.NamespaceStatus": { - "description": "NamespaceStatus is information about the current status of a Namespace.", - "properties": { - "phase": { - "description": "Phase is the current lifecycle phase of the namespace. More info: https://git.k8s.io/community/contributors/design-proposals/namespaces.md#phases", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.NodeAffinity": { - "description": "Node affinity is a group of node affinity scheduling rules.", - "properties": { - "preferredDuringSchedulingIgnoredDuringExecution": { - "description": "The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding \"weight\" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.PreferredSchedulingTerm" - } - }, - "requiredDuringSchedulingIgnoredDuringExecution": { - "description": "If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node.", - "$ref": "#/definitions/io.k8s.api.core.v1.NodeSelector" - } - } - }, - "io.k8s.api.core.v1.NodeSelector": { - "description": "A node selector represents the union of the results of one or more label queries over a set of nodes; that is, it represents the OR of the selectors represented by the node selector terms.", - "required": [ - "nodeSelectorTerms" - ], - "properties": { - "nodeSelectorTerms": { - "description": "Required. A list of node selector terms. The terms are ORed.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.NodeSelectorTerm" - } - } - } - }, - "io.k8s.api.core.v1.NodeSelectorRequirement": { - "description": "A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", - "required": [ - "key", - "operator" - ], - "properties": { - "key": { - "description": "The label key that the selector applies to.", - "type": "string" - }, - "operator": { - "description": "Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.", - "type": "string" - }, - "values": { - "description": "An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch.", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "io.k8s.api.core.v1.NodeSelectorTerm": { - "description": "A null or empty node selector term matches no objects.", - "required": [ - "matchExpressions" - ], - "properties": { - "matchExpressions": { - "description": "Required. A list of node selector requirements. The requirements are ANDed.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.NodeSelectorRequirement" - } - } - } - }, - "io.k8s.api.core.v1.ObjectFieldSelector": { - "description": "ObjectFieldSelector selects an APIVersioned field of an object.", - "required": [ - "fieldPath" - ], - "properties": { - "apiVersion": { - "description": "Version of the schema the FieldPath is written in terms of, defaults to \"v1\".", - "type": "string" - }, - "fieldPath": { - "description": "Path of the field to select in the specified API version.", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.ObjectReference": { - "description": "ObjectReference contains enough information to let you inspect or modify the referred object.", - "properties": { - "apiVersion": { - "description": "API version of the referent.", - "type": "string" - }, - "fieldPath": { - "description": "If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: \"spec.containers{name}\" (where \"name\" refers to the name of the container that triggered the event) or if no container name is specified \"spec.containers[2]\" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object.", - "type": "string" - }, - "kind": { - "description": "Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds", - "type": "string" - }, - "name": { - "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", - "type": "string" - }, - "namespace": { - "description": "Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/", - "type": "string" - }, - "resourceVersion": { - "description": "Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency", - "type": "string" - }, - "uid": { - "description": "UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.PersistentVolumeClaimVolumeSource": { - "description": "PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace. This volume finds the bound PV and mounts that volume for the pod. A PersistentVolumeClaimVolumeSource is, essentially, a wrapper around another type of volume that is owned by someone else (the system).", - "required": [ - "claimName" - ], - "properties": { - "claimName": { - "description": "ClaimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims", - "type": "string" - }, - "readOnly": { - "description": "Will force the ReadOnly setting in VolumeMounts. Default false.", - "type": "boolean" - } - } - }, - "io.k8s.api.core.v1.PhotonPersistentDiskVolumeSource": { - "description": "Represents a Photon Controller persistent disk resource.", - "required": [ - "pdID" - ], - "properties": { - "fsType": { - "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.", - "type": "string" - }, - "pdID": { - "description": "ID that identifies Photon Controller persistent disk", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.PodAffinity": { - "description": "Pod affinity is a group of inter pod affinity scheduling rules.", - "properties": { - "preferredDuringSchedulingIgnoredDuringExecution": { - "description": "The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.WeightedPodAffinityTerm" - } - }, - "requiredDuringSchedulingIgnoredDuringExecution": { - "description": "If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.PodAffinityTerm" - } - } - } - }, - "io.k8s.api.core.v1.PodAffinityTerm": { - "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key \u003ctopologyKey\u003e tches that of any node on which a pod of the set of pods is running", - "properties": { - "labelSelector": { - "description": "A label query over a set of resources, in this case pods.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" - }, - "namespaces": { - "description": "namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means \"this pod's namespace\"", - "type": "array", - "items": { - "type": "string" - } - }, - "topologyKey": { - "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. For PreferredDuringScheduling pod anti-affinity, empty topologyKey is interpreted as \"all topologies\" (\"all topologies\" here means all the topologyKeys indicated by scheduler command-line argument --failure-domains); for affinity and for RequiredDuringScheduling pod anti-affinity, empty topologyKey is not allowed.", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.PodAntiAffinity": { - "description": "Pod anti affinity is a group of inter pod anti affinity scheduling rules.", - "properties": { - "preferredDuringSchedulingIgnoredDuringExecution": { - "description": "The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.WeightedPodAffinityTerm" - } - }, - "requiredDuringSchedulingIgnoredDuringExecution": { - "description": "If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.PodAffinityTerm" - } - } - } - }, - "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": { - "fsGroup": { - "description": "A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod:\n\n1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw----\n\nIf unset, the Kubelet will not modify the ownership and permissions of any volume.", - "type": "integer", - "format": "int64" - }, - "runAsNonRoot": { - "description": "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 SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", - "type": "boolean" - }, - "runAsUser": { - "description": "The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container.", - "type": "integer", - "format": "int64" - }, - "seLinuxOptions": { - "description": "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.", - "$ref": "#/definitions/io.k8s.api.core.v1.SELinuxOptions" - }, - "supplementalGroups": { - "description": "A list of groups applied to the first process run in each container, in addition to the container's primary GID. If unspecified, no groups will be added to any container.", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - } - } - }, - "io.k8s.api.core.v1.PodSpec": { - "description": "PodSpec is a description of a pod.", - "required": [ - "containers" - ], - "properties": { - "activeDeadlineSeconds": { - "description": "Optional duration in seconds the pod may be active on the node relative to StartTime before the system will actively try to mark it failed and kill associated containers. Value must be a positive integer.", - "type": "integer", - "format": "int64" - }, - "affinity": { - "description": "If specified, the pod's scheduling constraints", - "$ref": "#/definitions/io.k8s.api.core.v1.Affinity" - }, - "automountServiceAccountToken": { - "description": "AutomountServiceAccountToken indicates whether a service account token should be automatically mounted.", - "type": "boolean" - }, - "containers": { - "description": "List of containers belonging to the pod. Containers cannot currently be added or removed. There must be at least one container in a Pod. Cannot be updated.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.Container" - }, - "x-kubernetes-patch-merge-key": "name", - "x-kubernetes-patch-strategy": "merge" - }, - "dnsPolicy": { - "description": "Set DNS policy for containers within the pod. One of 'ClusterFirstWithHostNet', 'ClusterFirst' or 'Default'. Defaults to \"ClusterFirst\". To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'.", - "type": "string" - }, - "hostAliases": { - "description": "HostAliases is an optional list of hosts and IPs that will be injected into the pod's hosts file if specified. This is only valid for non-hostNetwork pods.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.HostAlias" - }, - "x-kubernetes-patch-merge-key": "ip", - "x-kubernetes-patch-strategy": "merge" - }, - "hostIPC": { - "description": "Use the host's ipc namespace. Optional: Default to false.", - "type": "boolean" - }, - "hostNetwork": { - "description": "Host networking requested for this pod. Use the host's network namespace. If this option is set, the ports that will be used must be specified. Default to false.", - "type": "boolean" - }, - "hostPID": { - "description": "Use the host's pid namespace. Optional: Default to false.", - "type": "boolean" - }, - "hostname": { - "description": "Specifies the hostname of the Pod If not specified, the pod's hostname will be set to a system-defined value.", - "type": "string" - }, - "imagePullSecrets": { - "description": "ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. If specified, these secrets will be passed to individual puller implementations for them to use. For example, in the case of docker, only DockerConfig type secrets are honored. More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference" - }, - "x-kubernetes-patch-merge-key": "name", - "x-kubernetes-patch-strategy": "merge" - }, - "initContainers": { - "description": "List of initialization containers belonging to the pod. Init containers are executed in order prior to containers being started. If any init container fails, the pod is considered to have failed and is handled according to its restartPolicy. The name for an init container or normal container must be unique among all containers. Init containers may not have Lifecycle actions, Readiness probes, or Liveness probes. The resourceRequirements of an init container are taken into account during scheduling by finding the highest request/limit for each resource type, and then using the max of of that value or the sum of the normal containers. Limits are applied to init containers in a similar fashion. Init containers cannot currently be added or removed. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.Container" - }, - "x-kubernetes-patch-merge-key": "name", - "x-kubernetes-patch-strategy": "merge" - }, - "nodeName": { - "description": "NodeName is a request to schedule this pod onto a specific node. If it is non-empty, the scheduler simply schedules this pod onto that node, assuming that it fits resource requirements.", - "type": "string" - }, - "nodeSelector": { - "description": "NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node's labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "priority": { - "description": "The priority value. Various system components use this field to find the priority of the pod. 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.", - "type": "integer", - "format": "int32" - }, - "priorityClassName": { - "description": "If specified, indicates the pod's priority. \"SYSTEM\" is a special keyword which indicates the highest priority. Any other name must be defined by creating a PriorityClass object with that name. If not specified, the pod priority will be default or zero if there is no default.", - "type": "string" - }, - "restartPolicy": { - "description": "Restart policy for all containers within the pod. One of Always, OnFailure, Never. Default to Always. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy", - "type": "string" - }, - "schedulerName": { - "description": "If specified, the pod will be dispatched by specified scheduler. If not specified, the pod will be dispatched by default scheduler.", - "type": "string" - }, - "securityContext": { - "description": "SecurityContext holds pod-level security attributes and common container settings. Optional: Defaults to empty. See type description for default values of each field.", - "$ref": "#/definitions/io.k8s.api.core.v1.PodSecurityContext" - }, - "serviceAccount": { - "description": "DeprecatedServiceAccount is a depreciated alias for ServiceAccountName. Deprecated: Use serviceAccountName instead.", - "type": "string" - }, - "serviceAccountName": { - "description": "ServiceAccountName is the name of the ServiceAccount to use to run this pod. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/", - "type": "string" - }, - "subdomain": { - "description": "If specified, the fully qualified Pod hostname will be \"\u003chostname\u003e.\u003csubdomain\u003e.\u003cpod namespace\u003e.svc.\u003ccluster domain\u003e\". If not specified, the pod will not have a domainname at all.", - "type": "string" - }, - "terminationGracePeriodSeconds": { - "description": "Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period will be used instead. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. Defaults to 30 seconds.", - "type": "integer", - "format": "int64" - }, - "tolerations": { - "description": "If specified, the pod's tolerations.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.Toleration" - } - }, - "volumes": { - "description": "List of volumes that can be mounted by containers belonging to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.Volume" - }, - "x-kubernetes-patch-merge-key": "name", - "x-kubernetes-patch-strategy": "merge,retainKeys" - } - } - }, - "io.k8s.api.core.v1.PodTemplateSpec": { - "description": "PodTemplateSpec describes the data a pod should have when created from a template", - "properties": { - "metadata": { - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" - }, - "spec": { - "description": "Specification of the desired behavior of the pod. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status", - "$ref": "#/definitions/io.k8s.api.core.v1.PodSpec" - } - } - }, - "io.k8s.api.core.v1.PortworxVolumeSource": { - "description": "PortworxVolumeSource represents a Portworx volume resource.", - "required": [ - "volumeID" - ], - "properties": { - "fsType": { - "description": "FSType represents the filesystem type to mount Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\". Implicitly inferred to be \"ext4\" if unspecified.", - "type": "string" - }, - "readOnly": { - "description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", - "type": "boolean" - }, - "volumeID": { - "description": "VolumeID uniquely identifies a Portworx volume", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.PreferredSchedulingTerm": { - "description": "An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op).", - "required": [ - "weight", - "preference" - ], - "properties": { - "preference": { - "description": "A node selector term, associated with the corresponding weight.", - "$ref": "#/definitions/io.k8s.api.core.v1.NodeSelectorTerm" - }, - "weight": { - "description": "Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100.", - "type": "integer", - "format": "int32" - } - } - }, - "io.k8s.api.core.v1.Probe": { - "description": "Probe describes a health check to be performed against a container to determine whether it is alive or ready to receive traffic.", - "properties": { - "exec": { - "description": "One and only one of the following should be specified. Exec specifies the action to take.", - "$ref": "#/definitions/io.k8s.api.core.v1.ExecAction" - }, - "failureThreshold": { - "description": "Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1.", - "type": "integer", - "format": "int32" - }, - "httpGet": { - "description": "HTTPGet specifies the http request to perform.", - "$ref": "#/definitions/io.k8s.api.core.v1.HTTPGetAction" - }, - "initialDelaySeconds": { - "description": "Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", - "type": "integer", - "format": "int32" - }, - "periodSeconds": { - "description": "How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1.", - "type": "integer", - "format": "int32" - }, - "successThreshold": { - "description": "Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness. Minimum value is 1.", - "type": "integer", - "format": "int32" - }, - "tcpSocket": { - "description": "TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported", - "$ref": "#/definitions/io.k8s.api.core.v1.TCPSocketAction" - }, - "timeoutSeconds": { - "description": "Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", - "type": "integer", - "format": "int32" - } - } - }, - "io.k8s.api.core.v1.ProjectedVolumeSource": { - "description": "Represents a projected volume source", - "required": [ - "sources" - ], - "properties": { - "defaultMode": { - "description": "Mode bits to use on created files by default. Must be a value between 0 and 0777. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.", - "type": "integer", - "format": "int32" - }, - "sources": { - "description": "list of volume projections", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.VolumeProjection" - } - } - } - }, - "io.k8s.api.core.v1.QuobyteVolumeSource": { - "description": "Represents a Quobyte mount that lasts the lifetime of a pod. Quobyte volumes do not support ownership management or SELinux relabeling.", - "required": [ - "registry", - "volume" - ], - "properties": { - "group": { - "description": "Group to map volume access to Default is no group", - "type": "string" - }, - "readOnly": { - "description": "ReadOnly here will force the Quobyte volume to be mounted with read-only permissions. Defaults to false.", - "type": "boolean" - }, - "registry": { - "description": "Registry represents a single or multiple Quobyte Registry services specified as a string as host:port pair (multiple entries are separated with commas) which acts as the central registry for volumes", - "type": "string" - }, - "user": { - "description": "User to map volume access to Defaults to serivceaccount user", - "type": "string" - }, - "volume": { - "description": "Volume is a string that references an already created Quobyte volume by name.", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.RBDVolumeSource": { - "description": "Represents a Rados Block Device mount that lasts the lifetime of a pod. RBD volumes support ownership management and SELinux relabeling.", - "required": [ - "monitors", - "image" - ], - "properties": { - "fsType": { - "description": "Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd", - "type": "string" - }, - "image": { - "description": "The rados image name. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it", - "type": "string" - }, - "keyring": { - "description": "Keyring is the path to key ring for RBDUser. Default is /etc/ceph/keyring. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it", - "type": "string" - }, - "monitors": { - "description": "A collection of Ceph monitors. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it", - "type": "array", - "items": { - "type": "string" - } - }, - "pool": { - "description": "The rados pool name. Default is rbd. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it", - "type": "string" - }, - "readOnly": { - "description": "ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it", - "type": "boolean" - }, - "secretRef": { - "description": "SecretRef is name of the authentication secret for RBDUser. If provided overrides keyring. Default is nil. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it", - "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference" - }, - "user": { - "description": "The rados user name. Default is admin. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.ResourceFieldSelector": { - "description": "ResourceFieldSelector represents container resources (cpu, memory) and their output format", - "required": [ - "resource" - ], - "properties": { - "containerName": { - "description": "Container name: required for volumes, optional for env vars", - "type": "string" - }, - "divisor": { - "description": "Specifies the output format of the exposed resources, defaults to \"1\"", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.api.resource.Quantity" - }, - "resource": { - "description": "Required: resource to select", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.ResourceRequirements": { - "description": "ResourceRequirements describes the compute resource requirements.", - "properties": { - "limits": { - "description": "Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.api.resource.Quantity" - } - }, - "requests": { - "description": "Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.api.resource.Quantity" - } - } - } - }, - "io.k8s.api.core.v1.SELinuxOptions": { - "description": "SELinuxOptions are the labels to be applied to the container", - "properties": { - "level": { - "description": "Level is SELinux level label that applies to the container.", - "type": "string" - }, - "role": { - "description": "Role is a SELinux role label that applies to the container.", - "type": "string" - }, - "type": { - "description": "Type is a SELinux type label that applies to the container.", - "type": "string" - }, - "user": { - "description": "User is a SELinux user label that applies to the container.", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.ScaleIOVolumeSource": { - "description": "ScaleIOVolumeSource represents a persistent ScaleIO volume", - "required": [ - "gateway", - "system", - "secretRef" - ], - "properties": { - "fsType": { - "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.", - "type": "string" - }, - "gateway": { - "description": "The host address of the ScaleIO API Gateway.", - "type": "string" - }, - "protectionDomain": { - "description": "The name of the Protection Domain for the configured storage (defaults to \"default\").", - "type": "string" - }, - "readOnly": { - "description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", - "type": "boolean" - }, - "secretRef": { - "description": "SecretRef references to the secret for ScaleIO user and other sensitive information. If this is not provided, Login operation will fail.", - "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference" - }, - "sslEnabled": { - "description": "Flag to enable/disable SSL communication with Gateway, default false", - "type": "boolean" - }, - "storageMode": { - "description": "Indicates whether the storage for a volume should be thick or thin (defaults to \"thin\").", - "type": "string" - }, - "storagePool": { - "description": "The Storage Pool associated with the protection domain (defaults to \"default\").", - "type": "string" - }, - "system": { - "description": "The name of the storage system as configured in ScaleIO.", - "type": "string" - }, - "volumeName": { - "description": "The name of a volume already created in the ScaleIO system that is associated with this volume source.", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.Secret": { - "description": "Secret holds secret data of a certain type. The total bytes of the values in the Data field must be less than MaxSecretSize bytes.", - "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/api-conventions.md#resources", - "type": "string" - }, - "data": { - "description": "Data contains the secret data. Each key must consist of alphanumeric characters, '-', '_' or '.'. The serialized form of the secret data is a base64 encoded string, representing the arbitrary (possibly non-string) data value here. Described in https://tools.ietf.org/html/rfc4648#section-4", - "type": "object", - "additionalProperties": { - "type": "string", - "format": "byte" - } - }, - "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/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" - }, - "stringData": { - "description": "stringData allows specifying non-binary secret data in string form. It is provided as a write-only convenience method. All keys and values are merged into the data field on write, overwriting any existing values. It is never output when reading from the API.", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "type": { - "description": "Used to facilitate programmatic handling of secret data.", - "type": "string" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "", - "version": "v1", - "kind": "Secret" - } - ] - }, - "io.k8s.api.core.v1.SecretEnvSource": { - "description": "SecretEnvSource selects a Secret to populate the environment variables with.\n\nThe contents of the target Secret's Data field will represent the key-value pairs as environment variables.", - "properties": { - "name": { - "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", - "type": "string" - }, - "optional": { - "description": "Specify whether the Secret must be defined", - "type": "boolean" - } - } - }, - "io.k8s.api.core.v1.SecretKeySelector": { - "description": "SecretKeySelector selects a key of a Secret.", - "required": [ - "key" - ], - "properties": { - "key": { - "description": "The key of the secret to select from. Must be a valid secret key.", - "type": "string" - }, - "name": { - "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", - "type": "string" - }, - "optional": { - "description": "Specify whether the Secret or it's key must be defined", - "type": "boolean" - } - } - }, - "io.k8s.api.core.v1.SecretList": { - "description": "SecretList is a list of Secret.", - "required": [ - "items" - ], - "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/api-conventions.md#resources", - "type": "string" - }, - "items": { - "description": "Items is a list of secret objects. More info: https://kubernetes.io/docs/concepts/configuration/secret", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.Secret" - } - }, - "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/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "", - "version": "v1", - "kind": "SecretList" - } - ] - }, - "io.k8s.api.core.v1.SecretProjection": { - "description": "Adapts a secret into a projected volume.\n\nThe contents of the target Secret's Data field will be presented in a projected volume as files using the keys in the Data field as the file names. Note that this is identical to a secret volume source without the default mode.", - "properties": { - "items": { - "description": "If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.KeyToPath" - } - }, - "name": { - "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", - "type": "string" - }, - "optional": { - "description": "Specify whether the Secret or its key must be defined", - "type": "boolean" - } - } - }, - "io.k8s.api.core.v1.SecretVolumeSource": { - "description": "Adapts a Secret into a volume.\n\nThe contents of the target Secret's Data field will be presented in a volume as files using the keys in the Data field as the file names. Secret volumes support ownership management and SELinux relabeling.", - "properties": { - "defaultMode": { - "description": "Optional: mode bits to use on created files by default. Must be a value between 0 and 0777. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.", - "type": "integer", - "format": "int32" - }, - "items": { - "description": "If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.KeyToPath" - } - }, - "optional": { - "description": "Specify whether the Secret or it's keys must be defined", - "type": "boolean" - }, - "secretName": { - "description": "Name of the secret in the pod's namespace to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.SecurityContext": { - "description": "SecurityContext holds security configuration that will be applied to a container. Some fields are present in both SecurityContext and PodSecurityContext. When both are set, the values in SecurityContext take precedence.", - "properties": { - "allowPrivilegeEscalation": { - "description": "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", - "type": "boolean" - }, - "capabilities": { - "description": "The capabilities to add/drop when running containers. Defaults to the default set of capabilities granted by the container runtime.", - "$ref": "#/definitions/io.k8s.api.core.v1.Capabilities" - }, - "privileged": { - "description": "Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false.", - "type": "boolean" - }, - "readOnlyRootFilesystem": { - "description": "Whether this container has a read-only root filesystem. Default is false.", - "type": "boolean" - }, - "runAsNonRoot": { - "description": "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.", - "type": "boolean" - }, - "runAsUser": { - "description": "The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", - "type": "integer", - "format": "int64" - }, - "seLinuxOptions": { - "description": "The SELinux context to be applied to the container. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", - "$ref": "#/definitions/io.k8s.api.core.v1.SELinuxOptions" - } - } - }, - "io.k8s.api.core.v1.Service": { - "description": "Service is a named abstraction of software service (for example, mysql) consisting of local port (for example 3306) that the proxy listens on, and the selector that determines which pods will answer requests sent through the proxy.", - "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/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/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" - }, - "spec": { - "description": "Spec defines the behavior of a service. https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status", - "$ref": "#/definitions/io.k8s.api.core.v1.ServiceSpec" - }, - "status": { - "description": "Most recently observed status of the service. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status", - "$ref": "#/definitions/io.k8s.api.core.v1.ServiceStatus" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "", - "version": "v1", - "kind": "Service" - } - ] - }, - "io.k8s.api.core.v1.ServiceList": { - "description": "ServiceList holds a list of services.", - "required": [ - "items" - ], - "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/api-conventions.md#resources", - "type": "string" - }, - "items": { - "description": "List of services", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.Service" - } - }, - "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/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "", - "version": "v1", - "kind": "ServiceList" - } - ] - }, - "io.k8s.api.core.v1.ServicePort": { - "description": "ServicePort contains information on service's port.", - "required": [ - "port" - ], - "properties": { - "name": { - "description": "The name of this port within the service. This must be a DNS_LABEL. All ports within a ServiceSpec must have unique names. This maps to the 'Name' field in EndpointPort objects. Optional if only one ServicePort is defined on this service.", - "type": "string" - }, - "nodePort": { - "description": "The port on each node on which this service is exposed when type=NodePort or LoadBalancer. Usually assigned by the system. If specified, it will be allocated to the service if unused or else creation of the service will fail. Default is to auto-allocate a port if the ServiceType of this Service requires one. More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport", - "type": "integer", - "format": "int32" - }, - "port": { - "description": "The port that will be exposed by this service.", - "type": "integer", - "format": "int32" - }, - "protocol": { - "description": "The IP protocol for this port. Supports \"TCP\" and \"UDP\". Default is TCP.", - "type": "string" - }, - "targetPort": { - "description": "Number or name of the port to access on the pods targeted by the service. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. If this is a string, it will be looked up as a named port in the target Pod's container ports. If this is not specified, the value of the 'port' field is used (an identity map). This field is ignored for services with clusterIP=None, and should be omitted or set equal to the 'port' field. More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString" - } - } - }, - "io.k8s.api.core.v1.ServiceSpec": { - "description": "ServiceSpec describes the attributes that a user creates on a service.", - "properties": { - "clusterIP": { - "description": "clusterIP is the IP address of the service and is usually assigned randomly by the master. If an address is specified manually and is not in use by others, it will be allocated to the service; otherwise, creation of the service will fail. This field can not be changed through updates. Valid values are \"None\", empty string (\"\"), or a valid IP address. \"None\" can be specified for headless services when proxying is not required. Only applies to types ClusterIP, NodePort, and LoadBalancer. Ignored if type is ExternalName. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies", - "type": "string" - }, - "externalIPs": { - "description": "externalIPs is a list of IP addresses for which nodes in the cluster will also accept traffic for this service. These IPs are not managed by Kubernetes. The user is responsible for ensuring that traffic arrives at a node with this IP. A common example is external load-balancers that are not part of the Kubernetes system.", - "type": "array", - "items": { - "type": "string" - } - }, - "externalName": { - "description": "externalName is the external reference that kubedns or equivalent will return as a CNAME record for this service. No proxying will be involved. Must be a valid DNS name and requires Type to be ExternalName.", - "type": "string" - }, - "externalTrafficPolicy": { - "description": "externalTrafficPolicy denotes if this Service desires to route external traffic to node-local or cluster-wide endpoints. \"Local\" preserves the client source IP and avoids a second hop for LoadBalancer and Nodeport type services, but risks potentially imbalanced traffic spreading. \"Cluster\" obscures the client source IP and may cause a second hop to another node, but should have good overall load-spreading.", - "type": "string" - }, - "healthCheckNodePort": { - "description": "healthCheckNodePort specifies the healthcheck nodePort for the service. If not specified, HealthCheckNodePort is created by the service api backend with the allocated nodePort. Will use user-specified nodePort value if specified by the client. Only effects when Type is set to LoadBalancer and ExternalTrafficPolicy is set to Local.", - "type": "integer", - "format": "int32" - }, - "loadBalancerIP": { - "description": "Only applies to Service Type: LoadBalancer LoadBalancer will get created with the IP specified in this field. This feature depends on whether the underlying cloud-provider supports specifying the loadBalancerIP when a load balancer is created. This field will be ignored if the cloud-provider does not support the feature.", - "type": "string" - }, - "loadBalancerSourceRanges": { - "description": "If specified and supported by the platform, this will restrict traffic through the cloud-provider load-balancer will be restricted to the specified client IPs. This field will be ignored if the cloud-provider does not support the feature.\" More info: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/", - "type": "array", - "items": { - "type": "string" - } - }, - "ports": { - "description": "The list of ports that are exposed by this service. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.core.v1.ServicePort" - }, - "x-kubernetes-patch-merge-key": "port", - "x-kubernetes-patch-strategy": "merge" - }, - "publishNotReadyAddresses": { - "description": "publishNotReadyAddresses, when set to true, indicates that DNS implementations must publish the notReadyAddresses of subsets for the Endpoints associated with the Service. The default value is false. The primary use case for setting this field is to use a StatefulSet's Headless Service to propagate SRV records for its Pods without respect to their readiness for purpose of peer discovery. This field will replace the service.alpha.kubernetes.io/tolerate-unready-endpoints when that annotation is deprecated and all clients have been converted to use this field.", - "type": "boolean" - }, - "selector": { - "description": "Route service traffic to pods with label keys and values matching this selector. If empty or not present, the service is assumed to have an external process managing its endpoints, which Kubernetes will not modify. Only applies to types ClusterIP, NodePort, and LoadBalancer. Ignored if type is ExternalName. More info: https://kubernetes.io/docs/concepts/services-networking/service/", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "sessionAffinity": { - "description": "Supports \"ClientIP\" and \"None\". Used to maintain session affinity. Enable client IP based session affinity. Must be ClientIP or None. Defaults to None. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies", - "type": "string" - }, - "sessionAffinityConfig": { - "description": "sessionAffinityConfig contains the configurations of session affinity.", - "$ref": "#/definitions/io.k8s.api.core.v1.SessionAffinityConfig" - }, - "type": { - "description": "type determines how the Service is exposed. Defaults to ClusterIP. Valid options are ExternalName, ClusterIP, NodePort, and LoadBalancer. \"ExternalName\" maps to the specified externalName. \"ClusterIP\" allocates a cluster-internal IP address for load-balancing to endpoints. Endpoints are determined by the selector or if that is not specified, by manual construction of an Endpoints object. If clusterIP is \"None\", no virtual IP is allocated and the endpoints are published as a set of endpoints rather than a stable IP. \"NodePort\" builds on ClusterIP and allocates a port on every node which routes to the clusterIP. \"LoadBalancer\" builds on NodePort and creates an external load-balancer (if supported in the current cloud) which routes to the clusterIP. More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.ServiceStatus": { - "description": "ServiceStatus represents the current status of a service.", - "properties": { - "loadBalancer": { - "description": "LoadBalancer contains the current status of the load-balancer, if one is present.", - "$ref": "#/definitions/io.k8s.api.core.v1.LoadBalancerStatus" - } - } - }, - "io.k8s.api.core.v1.SessionAffinityConfig": { - "description": "SessionAffinityConfig represents the configurations of session affinity.", - "properties": { - "clientIP": { - "description": "clientIP contains the configurations of Client IP based session affinity.", - "$ref": "#/definitions/io.k8s.api.core.v1.ClientIPConfig" - } - } - }, - "io.k8s.api.core.v1.StorageOSVolumeSource": { - "description": "Represents a StorageOS persistent volume resource.", - "properties": { - "fsType": { - "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.", - "type": "string" - }, - "readOnly": { - "description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", - "type": "boolean" - }, - "secretRef": { - "description": "SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted.", - "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference" - }, - "volumeName": { - "description": "VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace.", - "type": "string" - }, - "volumeNamespace": { - "description": "VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to \"default\" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created.", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.TCPSocketAction": { - "description": "TCPSocketAction describes an action based on opening a socket", - "required": [ - "port" - ], - "properties": { - "host": { - "description": "Optional: Host name to connect to, defaults to the pod IP.", - "type": "string" - }, - "port": { - "description": "Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString" - } - } - }, - "io.k8s.api.core.v1.Toleration": { - "description": "The pod this Toleration is attached to tolerates any taint that matches the triple \u003ckey,value,effect\u003e using the matching operator \u003coperator\u003e.", - "properties": { - "effect": { - "description": "Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute.", - "type": "string" - }, - "key": { - "description": "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.", - "type": "string" - }, - "operator": { - "description": "Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category.", - "type": "string" - }, - "tolerationSeconds": { - "description": "TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system.", - "type": "integer", - "format": "int64" - }, - "value": { - "description": "Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string.", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.Volume": { - "description": "Volume represents a named volume in a pod that may be accessed by any container in the pod.", - "required": [ - "name" - ], - "properties": { - "awsElasticBlockStore": { - "description": "AWSElasticBlockStore represents an AWS Disk resource that is attached to a kubelet's host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore", - "$ref": "#/definitions/io.k8s.api.core.v1.AWSElasticBlockStoreVolumeSource" - }, - "azureDisk": { - "description": "AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod.", - "$ref": "#/definitions/io.k8s.api.core.v1.AzureDiskVolumeSource" - }, - "azureFile": { - "description": "AzureFile represents an Azure File Service mount on the host and bind mount to the pod.", - "$ref": "#/definitions/io.k8s.api.core.v1.AzureFileVolumeSource" - }, - "cephfs": { - "description": "CephFS represents a Ceph FS mount on the host that shares a pod's lifetime", - "$ref": "#/definitions/io.k8s.api.core.v1.CephFSVolumeSource" - }, - "cinder": { - "description": "Cinder represents a cinder volume attached and mounted on kubelets host machine More info: https://releases.k8s.io/HEAD/examples/mysql-cinder-pd/README.md", - "$ref": "#/definitions/io.k8s.api.core.v1.CinderVolumeSource" - }, - "configMap": { - "description": "ConfigMap represents a configMap that should populate this volume", - "$ref": "#/definitions/io.k8s.api.core.v1.ConfigMapVolumeSource" - }, - "downwardAPI": { - "description": "DownwardAPI represents downward API about the pod that should populate this volume", - "$ref": "#/definitions/io.k8s.api.core.v1.DownwardAPIVolumeSource" - }, - "emptyDir": { - "description": "EmptyDir represents a temporary directory that shares a pod's lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir", - "$ref": "#/definitions/io.k8s.api.core.v1.EmptyDirVolumeSource" - }, - "fc": { - "description": "FC represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod.", - "$ref": "#/definitions/io.k8s.api.core.v1.FCVolumeSource" - }, - "flexVolume": { - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.", - "$ref": "#/definitions/io.k8s.api.core.v1.FlexVolumeSource" - }, - "flocker": { - "description": "Flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running", - "$ref": "#/definitions/io.k8s.api.core.v1.FlockerVolumeSource" - }, - "gcePersistentDisk": { - "description": "GCEPersistentDisk represents a GCE Disk resource that is attached to a kubelet's host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk", - "$ref": "#/definitions/io.k8s.api.core.v1.GCEPersistentDiskVolumeSource" - }, - "gitRepo": { - "description": "GitRepo represents a git repository at a particular revision.", - "$ref": "#/definitions/io.k8s.api.core.v1.GitRepoVolumeSource" - }, - "glusterfs": { - "description": "Glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. More info: https://releases.k8s.io/HEAD/examples/volumes/glusterfs/README.md", - "$ref": "#/definitions/io.k8s.api.core.v1.GlusterfsVolumeSource" - }, - "hostPath": { - "description": "HostPath represents a pre-existing file or directory on the host machine that is directly exposed to the container. This is generally used for system agents or other privileged things that are allowed to see the host machine. Most containers will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath", - "$ref": "#/definitions/io.k8s.api.core.v1.HostPathVolumeSource" - }, - "iscsi": { - "description": "ISCSI represents an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod. More info: https://releases.k8s.io/HEAD/examples/volumes/iscsi/README.md", - "$ref": "#/definitions/io.k8s.api.core.v1.ISCSIVolumeSource" - }, - "name": { - "description": "Volume's name. Must be a DNS_LABEL and unique within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", - "type": "string" - }, - "nfs": { - "description": "NFS represents an NFS mount on the host that shares a pod's lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs", - "$ref": "#/definitions/io.k8s.api.core.v1.NFSVolumeSource" - }, - "persistentVolumeClaim": { - "description": "PersistentVolumeClaimVolumeSource represents a reference to a PersistentVolumeClaim in the same namespace. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims", - "$ref": "#/definitions/io.k8s.api.core.v1.PersistentVolumeClaimVolumeSource" - }, - "photonPersistentDisk": { - "description": "PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine", - "$ref": "#/definitions/io.k8s.api.core.v1.PhotonPersistentDiskVolumeSource" - }, - "portworxVolume": { - "description": "PortworxVolume represents a portworx volume attached and mounted on kubelets host machine", - "$ref": "#/definitions/io.k8s.api.core.v1.PortworxVolumeSource" - }, - "projected": { - "description": "Items for all in one resources secrets, configmaps, and downward API", - "$ref": "#/definitions/io.k8s.api.core.v1.ProjectedVolumeSource" - }, - "quobyte": { - "description": "Quobyte represents a Quobyte mount on the host that shares a pod's lifetime", - "$ref": "#/definitions/io.k8s.api.core.v1.QuobyteVolumeSource" - }, - "rbd": { - "description": "RBD represents a Rados Block Device mount on the host that shares a pod's lifetime. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md", - "$ref": "#/definitions/io.k8s.api.core.v1.RBDVolumeSource" - }, - "scaleIO": { - "description": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.", - "$ref": "#/definitions/io.k8s.api.core.v1.ScaleIOVolumeSource" - }, - "secret": { - "description": "Secret represents a secret that should populate this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret", - "$ref": "#/definitions/io.k8s.api.core.v1.SecretVolumeSource" - }, - "storageos": { - "description": "StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes.", - "$ref": "#/definitions/io.k8s.api.core.v1.StorageOSVolumeSource" - }, - "vsphereVolume": { - "description": "VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine", - "$ref": "#/definitions/io.k8s.api.core.v1.VsphereVirtualDiskVolumeSource" - } - } - }, - "io.k8s.api.core.v1.VolumeMount": { - "description": "VolumeMount describes a mounting of a Volume within a container.", - "required": [ - "name", - "mountPath" - ], - "properties": { - "mountPath": { - "description": "Path within the container at which the volume should be mounted. Must not contain ':'.", - "type": "string" - }, - "mountPropagation": { - "description": "mountPropagation determines how mounts are propagated from the host to container and the other way around. When not set, MountPropagationHostToContainer is used. This field is alpha in 1.8 and can be reworked or removed in a future release.", - "type": "string" - }, - "name": { - "description": "This must match the Name of a Volume.", - "type": "string" - }, - "readOnly": { - "description": "Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false.", - "type": "boolean" - }, - "subPath": { - "description": "Path within the volume from which the container's volume should be mounted. Defaults to \"\" (volume's root).", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.VolumeProjection": { - "description": "Projection that may be projected along with other supported volume types", - "properties": { - "configMap": { - "description": "information about the configMap data to project", - "$ref": "#/definitions/io.k8s.api.core.v1.ConfigMapProjection" - }, - "downwardAPI": { - "description": "information about the downwardAPI data to project", - "$ref": "#/definitions/io.k8s.api.core.v1.DownwardAPIProjection" - }, - "secret": { - "description": "information about the secret data to project", - "$ref": "#/definitions/io.k8s.api.core.v1.SecretProjection" - } - } - }, - "io.k8s.api.core.v1.VsphereVirtualDiskVolumeSource": { - "description": "Represents a vSphere volume resource.", - "required": [ - "volumePath" - ], - "properties": { - "fsType": { - "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.", - "type": "string" - }, - "storagePolicyID": { - "description": "Storage Policy Based Management (SPBM) profile ID associated with the StoragePolicyName.", - "type": "string" - }, - "storagePolicyName": { - "description": "Storage Policy Based Management (SPBM) profile name.", - "type": "string" - }, - "volumePath": { - "description": "Path that identifies vSphere volume vmdk", - "type": "string" - } - } - }, - "io.k8s.api.core.v1.WeightedPodAffinityTerm": { - "description": "The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s)", - "required": [ - "weight", - "podAffinityTerm" - ], - "properties": { - "podAffinityTerm": { - "description": "Required. A pod affinity term, associated with the corresponding weight.", - "$ref": "#/definitions/io.k8s.api.core.v1.PodAffinityTerm" - }, - "weight": { - "description": "weight associated with matching the corresponding podAffinityTerm, in the range 1-100.", - "type": "integer", - "format": "int32" - } - } - }, - "io.k8s.api.extensions.v1beta1.DaemonSet": { - "description": "DEPRECATED - This group version of DaemonSet is deprecated by apps/v1beta2/DaemonSet. See the release notes for more information. DaemonSet represents the configuration of a daemon set.", - "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/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/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" - }, - "spec": { - "description": "The desired behavior of this daemon set. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status", - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DaemonSetSpec" - }, - "status": { - "description": "The current status of this daemon set. This data may be out of date by some window of time. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status", - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DaemonSetStatus" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "extensions", - "version": "v1beta1", - "kind": "DaemonSet" - } - ] - }, - "io.k8s.api.extensions.v1beta1.DaemonSetList": { - "description": "DaemonSetList is a collection of daemon sets.", - "required": [ - "items" - ], - "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/api-conventions.md#resources", - "type": "string" - }, - "items": { - "description": "A list of daemon sets.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DaemonSet" - } - }, - "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/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "extensions", - "version": "v1beta1", - "kind": "DaemonSetList" - } - ] - }, - "io.k8s.api.extensions.v1beta1.DaemonSetSpec": { - "description": "DaemonSetSpec is the specification of a daemon set.", - "required": [ - "template" - ], - "properties": { - "minReadySeconds": { - "description": "The minimum number of seconds for which a newly created DaemonSet 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).", - "type": "integer", - "format": "int32" - }, - "revisionHistoryLimit": { - "description": "The number of old history to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.", - "type": "integer", - "format": "int32" - }, - "selector": { - "description": "A label query over pods that are managed by the daemon set. Must match in order to be controlled. If empty, defaulted to labels on Pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" - }, - "template": { - "description": "An object that describes the pod that will be created. The DaemonSet will create exactly one copy of this pod on every node that matches the template's node selector (or on every node if no node selector is specified). More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template", - "$ref": "#/definitions/io.k8s.api.core.v1.PodTemplateSpec" - }, - "templateGeneration": { - "description": "DEPRECATED. A sequence number representing a specific generation of the template. Populated by the system. It can be set only during the creation.", - "type": "integer", - "format": "int64" - }, - "updateStrategy": { - "description": "An update strategy to replace existing DaemonSet pods with new pods.", - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DaemonSetUpdateStrategy" - } - } - }, - "io.k8s.api.extensions.v1beta1.DaemonSetStatus": { - "description": "DaemonSetStatus represents the current status of a daemon set.", - "required": [ - "currentNumberScheduled", - "numberMisscheduled", - "desiredNumberScheduled", - "numberReady" - ], - "properties": { - "collisionCount": { - "description": "Count of hash collisions for the DaemonSet. The DaemonSet controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ControllerRevision.", - "type": "integer", - "format": "int32" - }, - "currentNumberScheduled": { - "description": "The number of nodes that are running at least 1 daemon pod and are supposed to run the daemon pod. More info: https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/", - "type": "integer", - "format": "int32" - }, - "desiredNumberScheduled": { - "description": "The total number of nodes that should be running the daemon pod (including nodes correctly running the daemon pod). More info: https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/", - "type": "integer", - "format": "int32" - }, - "numberAvailable": { - "description": "The number of nodes that should be running the daemon pod and have one or more of the daemon pod running and available (ready for at least spec.minReadySeconds)", - "type": "integer", - "format": "int32" - }, - "numberMisscheduled": { - "description": "The number of nodes that are running the daemon pod, but are not supposed to run the daemon pod. More info: https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/", - "type": "integer", - "format": "int32" - }, - "numberReady": { - "description": "The number of nodes that should be running the daemon pod and have one or more of the daemon pod running and ready.", - "type": "integer", - "format": "int32" - }, - "numberUnavailable": { - "description": "The number of nodes that should be running the daemon pod and have none of the daemon pod running and available (ready for at least spec.minReadySeconds)", - "type": "integer", - "format": "int32" - }, - "observedGeneration": { - "description": "The most recent generation observed by the daemon set controller.", - "type": "integer", - "format": "int64" - }, - "updatedNumberScheduled": { - "description": "The total number of nodes that are running updated daemon pod", - "type": "integer", - "format": "int32" - } - } - }, - "io.k8s.api.extensions.v1beta1.DaemonSetUpdateStrategy": { - "properties": { - "rollingUpdate": { - "description": "Rolling update config params. Present only if type = \"RollingUpdate\".", - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.RollingUpdateDaemonSet" - }, - "type": { - "description": "Type of daemon set update. Can be \"RollingUpdate\" or \"OnDelete\". Default is OnDelete.", - "type": "string" - } - } - }, - "io.k8s.api.extensions.v1beta1.Deployment": { - "description": "DEPRECATED - This group version of Deployment is deprecated by apps/v1beta2/Deployment. See the release notes for more information. Deployment enables declarative updates for Pods and ReplicaSets.", - "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/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/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "description": "Standard object metadata.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" - }, - "spec": { - "description": "Specification of the desired behavior of the Deployment.", - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DeploymentSpec" - }, - "status": { - "description": "Most recently observed status of the Deployment.", - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DeploymentStatus" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "extensions", - "version": "v1beta1", - "kind": "Deployment" - } - ] - }, - "io.k8s.api.extensions.v1beta1.DeploymentCondition": { - "description": "DeploymentCondition describes the state of a deployment at a certain point.", - "required": [ - "type", - "status" - ], - "properties": { - "lastTransitionTime": { - "description": "Last time the condition transitioned from one status to another.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" - }, - "lastUpdateTime": { - "description": "The last time this condition was updated.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" - }, - "message": { - "description": "A human readable message indicating details about the transition.", - "type": "string" - }, - "reason": { - "description": "The reason for the condition's last transition.", - "type": "string" - }, - "status": { - "description": "Status of the condition, one of True, False, Unknown.", - "type": "string" - }, - "type": { - "description": "Type of deployment condition.", - "type": "string" - } - } - }, - "io.k8s.api.extensions.v1beta1.DeploymentList": { - "description": "DeploymentList is a list of Deployments.", - "required": [ - "items" - ], - "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/api-conventions.md#resources", - "type": "string" - }, - "items": { - "description": "Items is the list of Deployments.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Deployment" - } - }, - "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/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "description": "Standard list metadata.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "extensions", - "version": "v1beta1", - "kind": "DeploymentList" - } - ] - }, - "io.k8s.api.extensions.v1beta1.DeploymentRollback": { - "description": "DEPRECATED. DeploymentRollback stores the information required to rollback a deployment.", - "required": [ - "name", - "rollbackTo" - ], - "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/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/api-conventions.md#types-kinds", - "type": "string" - }, - "name": { - "description": "Required: This must match the Name of a deployment.", - "type": "string" - }, - "rollbackTo": { - "description": "The config of this deployment rollback.", - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.RollbackConfig" - }, - "updatedAnnotations": { - "description": "The annotations to be updated to a deployment", - "type": "object", - "additionalProperties": { - "type": "string" - } - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "extensions", - "version": "v1beta1", - "kind": "DeploymentRollback" - } - ] - }, - "io.k8s.api.extensions.v1beta1.DeploymentSpec": { - "description": "DeploymentSpec is the specification of the desired behavior of the Deployment.", - "required": [ - "template" - ], - "properties": { - "minReadySeconds": { - "description": "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)", - "type": "integer", - "format": "int32" - }, - "paused": { - "description": "Indicates that the deployment is paused and will not be processed by the deployment controller.", - "type": "boolean" - }, - "progressDeadlineSeconds": { - "description": "The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that progress will not be estimated during the time a deployment is paused. This is not set by default.", - "type": "integer", - "format": "int32" - }, - "replicas": { - "description": "Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1.", - "type": "integer", - "format": "int32" - }, - "revisionHistoryLimit": { - "description": "The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified.", - "type": "integer", - "format": "int32" - }, - "rollbackTo": { - "description": "DEPRECATED. The config this deployment is rolling back to. Will be cleared after rollback is done.", - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.RollbackConfig" - }, - "selector": { - "description": "Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" - }, - "strategy": { - "description": "The deployment strategy to use to replace existing pods with new ones.", - "x-kubernetes-patch-strategy": "retainKeys", - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DeploymentStrategy" - }, - "template": { - "description": "Template describes the pods that will be created.", - "$ref": "#/definitions/io.k8s.api.core.v1.PodTemplateSpec" - } - } - }, - "io.k8s.api.extensions.v1beta1.DeploymentStatus": { - "description": "DeploymentStatus is the most recently observed status of the Deployment.", - "properties": { - "availableReplicas": { - "description": "Total number of available pods (ready for at least minReadySeconds) targeted by this deployment.", - "type": "integer", - "format": "int32" - }, - "collisionCount": { - "description": "Count of hash collisions for the Deployment. The Deployment controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ReplicaSet.", - "type": "integer", - "format": "int32" - }, - "conditions": { - "description": "Represents the latest available observations of a deployment's current state.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DeploymentCondition" - }, - "x-kubernetes-patch-merge-key": "type", - "x-kubernetes-patch-strategy": "merge" - }, - "observedGeneration": { - "description": "The generation observed by the deployment controller.", - "type": "integer", - "format": "int64" - }, - "readyReplicas": { - "description": "Total number of ready pods targeted by this deployment.", - "type": "integer", - "format": "int32" - }, - "replicas": { - "description": "Total number of non-terminated pods targeted by this deployment (their labels match the selector).", - "type": "integer", - "format": "int32" - }, - "unavailableReplicas": { - "description": "Total number of unavailable pods targeted by this deployment. This is the total number of pods that are still required for the deployment to have 100% available capacity. They may either be pods that are running but not yet available or pods that still have not been created.", - "type": "integer", - "format": "int32" - }, - "updatedReplicas": { - "description": "Total number of non-terminated pods targeted by this deployment that have the desired template spec.", - "type": "integer", - "format": "int32" - } - } - }, - "io.k8s.api.extensions.v1beta1.DeploymentStrategy": { - "description": "DeploymentStrategy describes how to replace existing pods with new ones.", - "properties": { - "rollingUpdate": { - "description": "Rolling update config params. Present only if DeploymentStrategyType = RollingUpdate.", - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.RollingUpdateDeployment" - }, - "type": { - "description": "Type of deployment. Can be \"Recreate\" or \"RollingUpdate\". Default is RollingUpdate.", - "type": "string" - } - } - }, - "io.k8s.api.extensions.v1beta1.HTTPIngressPath": { - "description": "HTTPIngressPath associates a path regex with a backend. Incoming urls matching the path are forwarded to the backend.", - "required": [ - "backend" - ], - "properties": { - "backend": { - "description": "Backend defines the referenced service endpoint to which the traffic will be forwarded to.", - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.IngressBackend" - }, - "path": { - "description": "Path is an extended POSIX regex as defined by IEEE Std 1003.1, (i.e this follows the egrep/unix syntax, not the perl syntax) matched against the path of an incoming request. Currently it can contain characters disallowed from the conventional \"path\" part of a URL as defined by RFC 3986. Paths must begin with a '/'. If unspecified, the path defaults to a catch all sending traffic to the backend.", - "type": "string" - } - } - }, - "io.k8s.api.extensions.v1beta1.HTTPIngressRuleValue": { - "description": "HTTPIngressRuleValue is a list of http selectors pointing to backends. In the example: http://\u003chost\u003e/\u003cpath\u003e?\u003csearchpart\u003e -\u003e backend where where parts of the url correspond to RFC 3986, this resource will be used to match against everything after the last '/' and before the first '?' or '#'.", - "required": [ - "paths" - ], - "properties": { - "paths": { - "description": "A collection of paths that map requests to backends.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.HTTPIngressPath" - } - } - } - }, - "io.k8s.api.extensions.v1beta1.Ingress": { - "description": "Ingress is a collection of rules that allow inbound connections to reach the endpoints defined by a backend. An Ingress can be configured to give services externally-reachable urls, load balance traffic, terminate SSL, offer name based virtual hosting etc.", - "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/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/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" - }, - "spec": { - "description": "Spec is the desired state of the Ingress. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status", - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.IngressSpec" - }, - "status": { - "description": "Status is the current state of the Ingress. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status", - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.IngressStatus" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "extensions", - "version": "v1beta1", - "kind": "Ingress" - } - ] - }, - "io.k8s.api.extensions.v1beta1.IngressBackend": { - "description": "IngressBackend describes all endpoints for a given service and port.", - "required": [ - "serviceName", - "servicePort" - ], - "properties": { - "serviceName": { - "description": "Specifies the name of the referenced service.", - "type": "string" - }, - "servicePort": { - "description": "Specifies the port of the referenced service.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString" - } - } - }, - "io.k8s.api.extensions.v1beta1.IngressList": { - "description": "IngressList is a collection of Ingress.", - "required": [ - "items" - ], - "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/api-conventions.md#resources", - "type": "string" - }, - "items": { - "description": "Items is the list of Ingress.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.Ingress" - } - }, - "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/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "extensions", - "version": "v1beta1", - "kind": "IngressList" - } - ] - }, - "io.k8s.api.extensions.v1beta1.IngressRule": { - "description": "IngressRule represents the rules mapping the paths under a specified host to the related backend services. Incoming requests are first evaluated for a host match, then routed to the backend associated with the matching IngressRuleValue.", - "properties": { - "host": { - "description": "Host is the fully qualified domain name of a network host, as defined by RFC 3986. Note the following deviations from the \"host\" part of the URI as defined in the RFC: 1. IPs are not allowed. Currently an IngressRuleValue can only apply to the\n\t IP in the Spec of the parent Ingress.\n2. The `:` delimiter is not respected because ports are not allowed.\n\t Currently the port of an Ingress is implicitly :80 for http and\n\t :443 for https.\nBoth these may change in the future. Incoming requests are matched against the host before the IngressRuleValue. If the host is unspecified, the Ingress routes all traffic based on the specified IngressRuleValue.", - "type": "string" - }, - "http": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.HTTPIngressRuleValue" - } - } - }, - "io.k8s.api.extensions.v1beta1.IngressSpec": { - "description": "IngressSpec describes the Ingress the user wishes to exist.", - "properties": { - "backend": { - "description": "A default backend capable of servicing requests that don't match any rule. At least one of 'backend' or 'rules' must be specified. This field is optional to allow the loadbalancer controller or defaulting logic to specify a global default.", - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.IngressBackend" - }, - "rules": { - "description": "A list of host rules used to configure the Ingress. If unspecified, or no rule matches, all traffic is sent to the default backend.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.IngressRule" - } - }, - "tls": { - "description": "TLS configuration. Currently the Ingress only supports a single TLS port, 443. If multiple members of this list specify different hosts, they will be multiplexed on the same port according to the hostname specified through the SNI TLS extension, if the ingress controller fulfilling the ingress supports SNI.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.IngressTLS" - } - } - } - }, - "io.k8s.api.extensions.v1beta1.IngressStatus": { - "description": "IngressStatus describe the current state of the Ingress.", - "properties": { - "loadBalancer": { - "description": "LoadBalancer contains the current status of the load-balancer.", - "$ref": "#/definitions/io.k8s.api.core.v1.LoadBalancerStatus" - } - } - }, - "io.k8s.api.extensions.v1beta1.IngressTLS": { - "description": "IngressTLS describes the transport layer security associated with an Ingress.", - "properties": { - "hosts": { - "description": "Hosts are a list of hosts included in the TLS certificate. The values in this list must match the name/s used in the tlsSecret. Defaults to the wildcard host setting for the loadbalancer controller fulfilling this Ingress, if left unspecified.", - "type": "array", - "items": { - "type": "string" - } - }, - "secretName": { - "description": "SecretName is the name of the secret used to terminate SSL traffic on 443. Field is left optional to allow SSL routing based on SNI hostname alone. If the SNI host in a listener conflicts with the \"Host\" header field used by an IngressRule, the SNI host is used for termination and value of the Host header is used for routing.", - "type": "string" - } - } - }, - "io.k8s.api.extensions.v1beta1.ReplicaSet": { - "description": "DEPRECATED - This group version of ReplicaSet is deprecated by apps/v1beta2/ReplicaSet. See the release notes for more information. ReplicaSet ensures that a specified number of pod replicas are running at any given time.", - "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/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/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "description": "If the Labels of a ReplicaSet are empty, they are defaulted to be the same as the Pod(s) that the ReplicaSet manages. Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" - }, - "spec": { - "description": "Spec defines the specification of the desired behavior of the ReplicaSet. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status", - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.ReplicaSetSpec" - }, - "status": { - "description": "Status is the most recently observed status of the ReplicaSet. This data may be out of date by some window of time. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status", - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.ReplicaSetStatus" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "extensions", - "version": "v1beta1", - "kind": "ReplicaSet" - } - ] - }, - "io.k8s.api.extensions.v1beta1.ReplicaSetCondition": { - "description": "ReplicaSetCondition describes the state of a replica set at a certain point.", - "required": [ - "type", - "status" - ], - "properties": { - "lastTransitionTime": { - "description": "The last time the condition transitioned from one status to another.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" - }, - "message": { - "description": "A human readable message indicating details about the transition.", - "type": "string" - }, - "reason": { - "description": "The reason for the condition's last transition.", - "type": "string" - }, - "status": { - "description": "Status of the condition, one of True, False, Unknown.", - "type": "string" - }, - "type": { - "description": "Type of replica set condition.", - "type": "string" - } - } - }, - "io.k8s.api.extensions.v1beta1.ReplicaSetList": { - "description": "ReplicaSetList is a collection of ReplicaSets.", - "required": [ - "items" - ], - "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/api-conventions.md#resources", - "type": "string" - }, - "items": { - "description": "List of ReplicaSets. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.ReplicaSet" - } - }, - "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/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "extensions", - "version": "v1beta1", - "kind": "ReplicaSetList" - } - ] - }, - "io.k8s.api.extensions.v1beta1.ReplicaSetSpec": { - "description": "ReplicaSetSpec is the specification of a ReplicaSet.", - "properties": { - "minReadySeconds": { - "description": "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)", - "type": "integer", - "format": "int32" - }, - "replicas": { - "description": "Replicas is the number of desired replicas. This is a pointer to distinguish between explicit zero and unspecified. Defaults to 1. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller", - "type": "integer", - "format": "int32" - }, - "selector": { - "description": "Selector is a label query over pods that should match the replica count. If the selector is empty, it is defaulted to the labels present on the pod template. Label keys and values that must match in order to be controlled by this replica set. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" - }, - "template": { - "description": "Template is the object that describes the pod that will be created if insufficient replicas are detected. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template", - "$ref": "#/definitions/io.k8s.api.core.v1.PodTemplateSpec" - } - } - }, - "io.k8s.api.extensions.v1beta1.ReplicaSetStatus": { - "description": "ReplicaSetStatus represents the current status of a ReplicaSet.", - "required": [ - "replicas" - ], - "properties": { - "availableReplicas": { - "description": "The number of available replicas (ready for at least minReadySeconds) for this replica set.", - "type": "integer", - "format": "int32" - }, - "conditions": { - "description": "Represents the latest available observations of a replica set's current state.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.ReplicaSetCondition" - }, - "x-kubernetes-patch-merge-key": "type", - "x-kubernetes-patch-strategy": "merge" - }, - "fullyLabeledReplicas": { - "description": "The number of pods that have labels matching the labels of the pod template of the replicaset.", - "type": "integer", - "format": "int32" - }, - "observedGeneration": { - "description": "ObservedGeneration reflects the generation of the most recently observed ReplicaSet.", - "type": "integer", - "format": "int64" - }, - "readyReplicas": { - "description": "The number of ready replicas for this replica set.", - "type": "integer", - "format": "int32" - }, - "replicas": { - "description": "Replicas is the most recently oberved number of replicas. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller", - "type": "integer", - "format": "int32" - } - } - }, - "io.k8s.api.extensions.v1beta1.RollbackConfig": { - "description": "DEPRECATED.", - "properties": { - "revision": { - "description": "The revision to rollback to. If set to 0, rollback to the last revision.", - "type": "integer", - "format": "int64" - } - } - }, - "io.k8s.api.extensions.v1beta1.RollingUpdateDaemonSet": { - "description": "Spec to control the desired behavior of daemon set rolling update.", - "properties": { - "maxUnavailable": { - "description": "The maximum number of DaemonSet pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of total number of DaemonSet pods at the start of the update (ex: 10%). Absolute number is calculated from percentage by rounding up. This cannot be 0. Default value is 1. Example: when this is set to 30%, at most 30% of the total number of nodes that should be running the daemon pod (i.e. status.desiredNumberScheduled) can have their pods stopped for an update at any given time. The update starts by stopping at most 30% of those DaemonSet pods and then brings up new DaemonSet pods in their place. Once the new pods are available, it then proceeds onto other DaemonSet pods, thus ensuring that at least 70% of original number of DaemonSet pods are available at all times during the update.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString" - } - } - }, - "io.k8s.api.extensions.v1beta1.RollingUpdateDeployment": { - "description": "Spec to control the desired behavior of rolling update.", - "properties": { - "maxSurge": { - "description": "The maximum number of pods that can be scheduled above the desired number of pods. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up. By default, a value of 1 is used. Example: when this is set to 30%, the new RC can be scaled up immediately when the rolling update starts, such that the total number of old and new pods do not exceed 130% of desired pods. Once old pods have been killed, new RC can be scaled up further, ensuring that total number of pods running at any time during the update is atmost 130% of desired pods.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString" - }, - "maxUnavailable": { - "description": "The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding down. This can not be 0 if MaxSurge is 0. By default, a fixed value of 1 is used. Example: when this is set to 30%, the old RC can be scaled down to 70% of desired pods immediately when the rolling update starts. Once new pods are ready, old RC can be scaled down further, followed by scaling up the new RC, ensuring that the total number of pods available at all times during the update is at least 70% of desired pods.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString" - } - } - }, - "io.k8s.api.extensions.v1beta1.Scale": { - "description": "represents a scaling request for a resource.", - "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/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/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" - }, - "spec": { - "description": "defines the behavior of the scale. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status.", - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.ScaleSpec" - }, - "status": { - "description": "current status of the scale. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status. Read-only.", - "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.ScaleStatus" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "extensions", - "version": "v1beta1", - "kind": "Scale" - } - ] - }, - "io.k8s.api.extensions.v1beta1.ScaleSpec": { - "description": "describes the attributes of a scale subresource", - "properties": { - "replicas": { - "description": "desired number of instances for the scaled object.", - "type": "integer", - "format": "int32" - } - } - }, - "io.k8s.api.extensions.v1beta1.ScaleStatus": { - "description": "represents the current status of a scale subresource.", - "required": [ - "replicas" - ], - "properties": { - "replicas": { - "description": "actual number of observed instances of the scaled object.", - "type": "integer", - "format": "int32" - }, - "selector": { - "description": "label query over pods that should match the replicas count. More info: http://kubernetes.io/docs/user-guide/labels#label-selectors", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "targetSelector": { - "description": "label selector for pods that should match the replicas count. This is a serializated version of both map-based and more expressive set-based selectors. This is done to avoid introspection in the clients. The string will be in the same format as the query-param syntax. If the target type only supports map-based selectors, both this field and map-based selector field are populated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", - "type": "string" - } - } - }, - "io.k8s.apimachinery.pkg.api.resource.Quantity": { - "type": "string" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup": { - "description": "APIGroup contains the name, the supported versions, and the preferred version of a group.", - "required": [ - "name", - "versions", - "serverAddressByClientCIDRs" - ], - "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/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/api-conventions.md#types-kinds", - "type": "string" - }, - "name": { - "description": "name is the name of the group.", - "type": "string" - }, - "preferredVersion": { - "description": "preferredVersion is the version preferred by the API server, which probably is the storage version.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.GroupVersionForDiscovery" - }, - "serverAddressByClientCIDRs": { - "description": "a map of client CIDR to server address that is serving this group. This is to help clients reach servers in the most network-efficient way possible. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR. The server returns only those CIDRs that it thinks that the client can match. For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ServerAddressByClientCIDR" - } - }, - "versions": { - "description": "versions are the versions supported in this group.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.GroupVersionForDiscovery" - } - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "", - "version": "v1", - "kind": "APIGroup" - } - ] - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.APIGroupList": { - "description": "APIGroupList is a list of APIGroup, to allow clients to discover the API at /apis.", - "required": [ - "groups" - ], - "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/api-conventions.md#resources", - "type": "string" - }, - "groups": { - "description": "groups is a list of APIGroup.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup" - } - }, - "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/api-conventions.md#types-kinds", - "type": "string" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "", - "version": "v1", - "kind": "APIGroupList" - } - ] - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.APIResource": { - "description": "APIResource specifies the name of a resource and whether it is namespaced.", - "required": [ - "name", - "singularName", - "namespaced", - "kind", - "verbs" - ], - "properties": { - "categories": { - "description": "categories is a list of the grouped resources this resource belongs to (e.g. 'all')", - "type": "array", - "items": { - "type": "string" - } - }, - "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": { - "description": "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')", - "type": "string" - }, - "name": { - "description": "name is the plural name of the resource.", - "type": "string" - }, - "namespaced": { - "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.", - "type": "array", - "items": { - "type": "string" - } - }, - "singularName": { - "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" - }, - "verbs": { - "description": "verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy)", - "type": "array", - "items": { - "type": "string" - } - }, - "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" - } - } - }, - "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.", - "required": [ - "groupVersion", - "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/api-conventions.md#resources", - "type": "string" - }, - "groupVersion": { - "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/api-conventions.md#types-kinds", - "type": "string" - }, - "resources": { - "description": "resources contains the name of the resources and if they are namespaced.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResource" - } - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "", - "version": "v1", - "kind": "APIResourceList" - } - ] - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.APIVersions": { - "description": "APIVersions lists the versions that are available, to allow clients to discover the API at /api, which is the root path of the legacy v1 API.", - "required": [ - "versions", - "serverAddressByClientCIDRs" - ], - "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/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/api-conventions.md#types-kinds", - "type": "string" - }, - "serverAddressByClientCIDRs": { - "description": "a map of client CIDR to server address that is serving this group. This is to help clients reach servers in the most network-efficient way possible. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR. The server returns only those CIDRs that it thinks that the client can match. For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ServerAddressByClientCIDR" - } - }, - "versions": { - "description": "versions are the api versions that are available.", - "type": "array", - "items": { - "type": "string" - } - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "", - "version": "v1", - "kind": "APIVersions" - } - ] - }, - "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/api-conventions.md#resources", - "type": "string" - }, - "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.", - "type": "integer", - "format": "int64" - }, - "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/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": { - "description": "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Preconditions" - }, - "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.", - "type": "string" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "", - "version": "v1", - "kind": "DeleteOptions" - }, - { - "group": "admission.k8s.io", - "version": "v1alpha1", - "kind": "DeleteOptions" - }, - { - "group": "admissionregistration.k8s.io", - "version": "v1alpha1", - "kind": "DeleteOptions" - }, - { - "group": "apps", - "version": "v1", - "kind": "DeleteOptions" - }, - { - "group": "apps", - "version": "v1beta1", - "kind": "DeleteOptions" - }, - { - "group": "apps", - "version": "v1beta2", - "kind": "DeleteOptions" - }, - { - "group": "authentication.k8s.io", - "version": "v1", - "kind": "DeleteOptions" - }, - { - "group": "authentication.k8s.io", - "version": "v1beta1", - "kind": "DeleteOptions" - }, - { - "group": "authorization.k8s.io", - "version": "v1", - "kind": "DeleteOptions" - }, - { - "group": "authorization.k8s.io", - "version": "v1beta1", - "kind": "DeleteOptions" - }, - { - "group": "autoscaling", - "version": "v1", - "kind": "DeleteOptions" - }, - { - "group": "autoscaling", - "version": "v2beta1", - "kind": "DeleteOptions" - }, - { - "group": "batch", - "version": "v1", - "kind": "DeleteOptions" - }, - { - "group": "batch", - "version": "v1beta1", - "kind": "DeleteOptions" - }, - { - "group": "batch", - "version": "v2alpha1", - "kind": "DeleteOptions" - }, - { - "group": "certificates.k8s.io", - "version": "v1beta1", - "kind": "DeleteOptions" - }, - { - "group": "extensions", - "version": "v1beta1", - "kind": "DeleteOptions" - }, - { - "group": "federation", - "version": "v1beta1", - "kind": "DeleteOptions" - }, - { - "group": "imagepolicy.k8s.io", - "version": "v1alpha1", - "kind": "DeleteOptions" - }, - { - "group": "networking.k8s.io", - "version": "v1", - "kind": "DeleteOptions" - }, - { - "group": "policy", - "version": "v1beta1", - "kind": "DeleteOptions" - }, - { - "group": "rbac.authorization.k8s.io", - "version": "v1", - "kind": "DeleteOptions" - }, - { - "group": "rbac.authorization.k8s.io", - "version": "v1alpha1", - "kind": "DeleteOptions" - }, - { - "group": "rbac.authorization.k8s.io", - "version": "v1beta1", - "kind": "DeleteOptions" - }, - { - "group": "scheduling.k8s.io", - "version": "v1alpha1", - "kind": "DeleteOptions" - }, - { - "group": "settings.k8s.io", - "version": "v1alpha1", - "kind": "DeleteOptions" - }, - { - "group": "storage.k8s.io", - "version": "v1", - "kind": "DeleteOptions" - }, - { - "group": "storage.k8s.io", - "version": "v1beta1", - "kind": "DeleteOptions" - } - ] - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.GroupVersionForDiscovery": { - "description": "GroupVersion contains the \"group/version\" and \"version\" string of a version. It is made a struct to keep extensibility.", - "required": [ - "groupVersion", - "version" - ], - "properties": { - "groupVersion": { - "description": "groupVersion specifies the API group and version in the form \"group/version\"", - "type": "string" - }, - "version": { - "description": "version specifies the version in the form of \"version\". This is to save the clients the trouble of splitting the GroupVersion.", - "type": "string" - } - } - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Initializer": { - "description": "Initializer is information about an initializer that has not yet completed.", - "required": [ - "name" - ], - "properties": { - "name": { - "description": "name of the process that is responsible for initializing this object.", - "type": "string" - } - } - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Initializers": { - "description": "Initializers tracks the progress of initialization.", - "required": [ - "pending" - ], - "properties": { - "pending": { - "description": "Pending is a list of initializers that must execute in order before this object is visible. When the last pending initializer is removed, and no failing result is set, the initializers struct will be set to nil and the object is considered as initialized and visible to all clients.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Initializer" - }, - "x-kubernetes-patch-merge-key": "name", - "x-kubernetes-patch-strategy": "merge" - }, - "result": { - "description": "If result is set with the Failure field, the object will be persisted to storage and then deleted, ensuring that other clients can observe the deletion.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - } - } - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector": { - "description": "A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.", - "properties": { - "matchExpressions": { - "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelectorRequirement" - } - }, - "matchLabels": { - "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.", - "type": "object", - "additionalProperties": { - "type": "string" - } - } - } - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelectorRequirement": { - "description": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", - "required": [ - "key", - "operator" - ], - "properties": { - "key": { - "description": "key is the label key that the selector applies to.", - "type": "string", - "x-kubernetes-patch-merge-key": "key", - "x-kubernetes-patch-strategy": "merge" - }, - "operator": { - "description": "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.", - "type": "string" - }, - "values": { - "description": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "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 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.", - "type": "string" - }, - "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/api-conventions.md#concurrency-control-and-consistency", - "type": "string" - }, - "selfLink": { - "description": "selfLink is a URL representing this object. Populated by the system. Read-only.", - "type": "string" - } - } - }, - "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": { - "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: http://kubernetes.io/docs/user-guide/annotations", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "clusterName": { - "description": "The name of the cluster which the object belongs to. This is used to distinguish resources with same name and namespace in different clusters. This field is not set anywhere right now and apiserver is going to ignore it if set in create or update request.", - "type": "string" - }, - "creationTimestamp": { - "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/api-conventions.md#metadata", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" - }, - "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.", - "type": "integer", - "format": "int64" - }, - "deletionTimestamp": { - "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 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/api-conventions.md#metadata", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" - }, - "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.", - "type": "array", - "items": { - "type": "string" - }, - "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 NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header).\n\nApplied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/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.", - "type": "integer", - "format": "int64" - }, - "initializers": { - "description": "An initializer is a controller which enforces some system invariant at object creation time. This field is a list of initializers that have not yet acted on this object. If nil or empty, this object has been completely initialized. Otherwise, the object is considered uninitialized and is hidden (in list/watch and get calls) from clients that haven't explicitly asked to observe uninitialized objects.\n\nWhen an object is created, the system will populate this list with the current set of initializers. Only privileged users may set or modify this list. Once it is empty, it may not be modified further by any user.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Initializers" - }, - "labels": { - "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: http://kubernetes.io/docs/user-guide/labels", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "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: http://kubernetes.io/docs/user-guide/identifiers#names", - "type": "string" - }, - "namespace": { - "description": "Namespace defines the space within 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: http://kubernetes.io/docs/user-guide/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.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference" - }, - "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/api-conventions.md#concurrency-control-and-consistency", - "type": "string" - }, - "selfLink": { - "description": "SelfLink is a URL representing this object. Populated by the system. Read-only.", - "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: http://kubernetes.io/docs/user-guide/identifiers#uids", - "type": "string" - } - } - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference": { - "description": "OwnerReference contains enough information to let you identify an owning object. Currently, an owning object must be in the same namespace, so there is no namespace field.", - "required": [ - "apiVersion", - "kind", - "name", - "uid" - ], - "properties": { - "apiVersion": { - "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. 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": { - "description": "Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds", - "type": "string" - }, - "name": { - "description": "Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names", - "type": "string" - }, - "uid": { - "description": "UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids", - "type": "string" - } - } - }, - "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." - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Preconditions": { - "description": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", - "properties": { - "uid": { - "description": "Specifies the target UID.", - "type": "string" - } - } - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.ServerAddressByClientCIDR": { - "description": "ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match.", - "required": [ - "clientCIDR", - "serverAddress" - ], - "properties": { - "clientCIDR": { - "description": "The CIDR with which clients can match their IP to figure out the server address that they should use.", - "type": "string" - }, - "serverAddress": { - "description": "Address of this server, suitable for a client that matches the above CIDR. This can be a hostname, hostname:port, IP or IP:port.", - "type": "string" - } - } - }, - "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/api-conventions.md#resources", - "type": "string" - }, - "code": { - "description": "Suggested HTTP return code for this status, 0 if not set.", - "type": "integer", - "format": "int32" - }, - "details": { - "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.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails" - }, - "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/api-conventions.md#types-kinds", - "type": "string" - }, - "message": { - "description": "A human-readable description of the status of this operation.", - "type": "string" - }, - "metadata": { - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - }, - "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/api-conventions.md#spec-and-status", - "type": "string" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "", - "version": "v1", - "kind": "Status" - } - ] - }, - "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" - } - } - }, - "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.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause" - } - }, - "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/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.", - "type": "integer", - "format": "int32" - }, - "uid": { - "description": "UID of the resource. (when there is a single resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids", - "type": "string" - } - } - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.Time": { - "type": "string", - "format": "date-time" - }, - "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent": { - "description": "Event represents a single event to a watched resource.", - "required": [ - "type", - "object" - ], - "properties": { - "object": { - "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.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.runtime.RawExtension" - }, - "type": { - "type": "string" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "", - "version": "v1", - "kind": "WatchEvent" - }, - { - "group": "admission.k8s.io", - "version": "v1alpha1", - "kind": "WatchEvent" - }, - { - "group": "admissionregistration.k8s.io", - "version": "v1alpha1", - "kind": "WatchEvent" - }, - { - "group": "apps", - "version": "v1", - "kind": "WatchEvent" - }, - { - "group": "apps", - "version": "v1beta1", - "kind": "WatchEvent" - }, - { - "group": "apps", - "version": "v1beta2", - "kind": "WatchEvent" - }, - { - "group": "authentication.k8s.io", - "version": "v1", - "kind": "WatchEvent" - }, - { - "group": "authentication.k8s.io", - "version": "v1beta1", - "kind": "WatchEvent" - }, - { - "group": "authorization.k8s.io", - "version": "v1", - "kind": "WatchEvent" - }, - { - "group": "authorization.k8s.io", - "version": "v1beta1", - "kind": "WatchEvent" - }, - { - "group": "autoscaling", - "version": "v1", - "kind": "WatchEvent" - }, - { - "group": "autoscaling", - "version": "v2beta1", - "kind": "WatchEvent" - }, - { - "group": "batch", - "version": "v1", - "kind": "WatchEvent" - }, - { - "group": "batch", - "version": "v1beta1", - "kind": "WatchEvent" - }, - { - "group": "batch", - "version": "v2alpha1", - "kind": "WatchEvent" - }, - { - "group": "certificates.k8s.io", - "version": "v1beta1", - "kind": "WatchEvent" - }, - { - "group": "extensions", - "version": "v1beta1", - "kind": "WatchEvent" - }, - { - "group": "federation", - "version": "v1beta1", - "kind": "WatchEvent" - }, - { - "group": "imagepolicy.k8s.io", - "version": "v1alpha1", - "kind": "WatchEvent" - }, - { - "group": "networking.k8s.io", - "version": "v1", - "kind": "WatchEvent" - }, - { - "group": "policy", - "version": "v1beta1", - "kind": "WatchEvent" - }, - { - "group": "rbac.authorization.k8s.io", - "version": "v1", - "kind": "WatchEvent" - }, - { - "group": "rbac.authorization.k8s.io", - "version": "v1alpha1", - "kind": "WatchEvent" - }, - { - "group": "rbac.authorization.k8s.io", - "version": "v1beta1", - "kind": "WatchEvent" - }, - { - "group": "scheduling.k8s.io", - "version": "v1alpha1", - "kind": "WatchEvent" - }, - { - "group": "settings.k8s.io", - "version": "v1alpha1", - "kind": "WatchEvent" - }, - { - "group": "storage.k8s.io", - "version": "v1", - "kind": "WatchEvent" - }, - { - "group": "storage.k8s.io", - "version": "v1beta1", - "kind": "WatchEvent" - } - ] - }, - "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: type MyAPIObject struct {\n\truntime.TypeMeta `json:\",inline\"`\n\tMyPlugin runtime.Object `json:\"myPlugin\"`\n} type PluginA struct {\n\tAOption string `json:\"aOption\"`\n}\n\n// External package: type MyAPIObject struct {\n\truntime.TypeMeta `json:\",inline\"`\n\tMyPlugin runtime.RawExtension `json:\"myPlugin\"`\n} type PluginA struct {\n\tAOption string `json:\"aOption\"`\n}\n\n// On the wire, the JSON will look something like this: {\n\t\"kind\":\"MyAPIObject\",\n\t\"apiVersion\":\"v1\",\n\t\"myPlugin\": {\n\t\t\"kind\":\"PluginA\",\n\t\t\"aOption\":\"foo\",\n\t},\n}\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.)", - "required": [ - "Raw" - ], - "properties": { - "Raw": { - "description": "Raw is the underlying serialization of this object.", - "type": "string", - "format": "byte" - } - } - }, - "io.k8s.apimachinery.pkg.util.intstr.IntOrString": { - "type": "string", - "format": "int-or-string" - }, - "io.k8s.apimachinery.pkg.version.Info": { - "description": "Info contains versioning information. how we'll want to distribute that information.", - "required": [ - "major", - "minor", - "gitVersion", - "gitCommit", - "gitTreeState", - "buildDate", - "goVersion", - "compiler", - "platform" - ], - "properties": { - "buildDate": { - "type": "string" - }, - "compiler": { - "type": "string" - }, - "gitCommit": { - "type": "string" - }, - "gitTreeState": { - "type": "string" - }, - "gitVersion": { - "type": "string" - }, - "goVersion": { - "type": "string" - }, - "major": { - "type": "string" - }, - "minor": { - "type": "string" - }, - "platform": { - "type": "string" - } - } - }, - "io.k8s.kubernetes.federation.apis.federation.v1beta1.Cluster": { - "description": "Information about a registered cluster in a federated kubernetes setup. Clusters are not namespaced and have unique names in the federation.", - "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/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/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" - }, - "spec": { - "description": "Spec defines the behavior of the Cluster.", - "$ref": "#/definitions/io.k8s.kubernetes.federation.apis.federation.v1beta1.ClusterSpec" - }, - "status": { - "description": "Status describes the current status of a Cluster", - "$ref": "#/definitions/io.k8s.kubernetes.federation.apis.federation.v1beta1.ClusterStatus" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "federation", - "version": "v1beta1", - "kind": "Cluster" - } - ] - }, - "io.k8s.kubernetes.federation.apis.federation.v1beta1.ClusterCondition": { - "description": "ClusterCondition describes current state of a cluster.", - "required": [ - "type", - "status" - ], - "properties": { - "lastProbeTime": { - "description": "Last time the condition was checked.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" - }, - "lastTransitionTime": { - "description": "Last time the condition transit from one status to another.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" - }, - "message": { - "description": "Human readable message indicating details about last transition.", - "type": "string" - }, - "reason": { - "description": "(brief) reason for the condition's last transition.", - "type": "string" - }, - "status": { - "description": "Status of the condition, one of True, False, Unknown.", - "type": "string" - }, - "type": { - "description": "Type of cluster condition, Complete or Failed.", - "type": "string" - } - } - }, - "io.k8s.kubernetes.federation.apis.federation.v1beta1.ClusterList": { - "description": "A list of all the kubernetes clusters registered to the federation", - "required": [ - "items" - ], - "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/api-conventions.md#resources", - "type": "string" - }, - "items": { - "description": "List of Cluster objects.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.kubernetes.federation.apis.federation.v1beta1.Cluster" - } - }, - "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/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "federation", - "version": "v1beta1", - "kind": "ClusterList" - } - ] - }, - "io.k8s.kubernetes.federation.apis.federation.v1beta1.ClusterSpec": { - "description": "ClusterSpec describes the attributes of a kubernetes cluster.", - "required": [ - "serverAddressByClientCIDRs" - ], - "properties": { - "secretRef": { - "description": "Name of the secret containing kubeconfig to access this cluster. The secret is read from the kubernetes cluster that is hosting federation control plane. Admin needs to ensure that the required secret exists. Secret should be in the same namespace where federation control plane is hosted and it should have kubeconfig in its data with key \"kubeconfig\". This will later be changed to a reference to secret in federation control plane when the federation control plane supports secrets. This can be left empty if the cluster allows insecure access.", - "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference" - }, - "serverAddressByClientCIDRs": { - "description": "A map of client CIDR to server address. This is to help clients reach servers in the most network-efficient way possible. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.kubernetes.federation.apis.federation.v1beta1.ServerAddressByClientCIDR" - }, - "x-kubernetes-patch-merge-key": "clientCIDR", - "x-kubernetes-patch-strategy": "merge" - } - } - }, - "io.k8s.kubernetes.federation.apis.federation.v1beta1.ClusterStatus": { - "description": "ClusterStatus is information about the current status of a cluster updated by cluster controller periodically.", - "properties": { - "conditions": { - "description": "Conditions is an array of current cluster conditions.", - "type": "array", - "items": { - "$ref": "#/definitions/io.k8s.kubernetes.federation.apis.federation.v1beta1.ClusterCondition" - } - }, - "region": { - "description": "Region is the name of the region in which all of the nodes in the cluster exist. e.g. 'us-east1'.", - "type": "string" - }, - "zones": { - "description": "Zones is the list of availability zones in which the nodes of the cluster exist, e.g. 'us-east1-a'. These will always be in the same region.", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "io.k8s.kubernetes.federation.apis.federation.v1beta1.ServerAddressByClientCIDR": { - "description": "ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match.", - "required": [ - "clientCIDR", - "serverAddress" - ], - "properties": { - "clientCIDR": { - "description": "The CIDR with which clients can match their IP to figure out the server address that they should use.", - "type": "string" - }, - "serverAddress": { - "description": "Address of this server, suitable for a client that matches the above CIDR. This can be a hostname, hostname:port, IP or IP:port.", - "type": "string" - } - } - }, - "resource.Quantity": { - "description": "Deprecated. Please use io.k8s.apimachinery.pkg.api.resource.Quantity instead.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.api.resource.Quantity" - }, - "runtime.RawExtension": { - "description": "Deprecated. Please use io.k8s.apimachinery.pkg.runtime.RawExtension instead.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.runtime.RawExtension" - }, - "v1.APIGroup": { - "description": "Deprecated. Please use io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup instead.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup" - }, - "v1.APIGroupList": { - "description": "Deprecated. Please use io.k8s.apimachinery.pkg.apis.meta.v1.APIGroupList instead.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIGroupList" - }, - "v1.APIResource": { - "description": "Deprecated. Please use io.k8s.apimachinery.pkg.apis.meta.v1.APIResource instead.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResource" - }, - "v1.APIResourceList": { - "description": "Deprecated. Please use io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList instead.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" - }, - "v1.APIVersions": { - "description": "Deprecated. Please use io.k8s.apimachinery.pkg.apis.meta.v1.APIVersions instead.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIVersions" - }, - "v1.DeleteOptions": { - "description": "Deprecated. Please use io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions instead.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" - }, - "v1.GroupVersionForDiscovery": { - "description": "Deprecated. Please use io.k8s.apimachinery.pkg.apis.meta.v1.GroupVersionForDiscovery instead.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.GroupVersionForDiscovery" - }, - "v1.LabelSelector": { - "description": "Deprecated. Please use io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector instead.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" - }, - "v1.LabelSelectorRequirement": { - "description": "Deprecated. Please use io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelectorRequirement instead.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelectorRequirement" - }, - "v1.ListMeta": { - "description": "Deprecated. Please use io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta instead.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - }, - "v1.ObjectMeta": { - "description": "Deprecated. Please use io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta instead.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" - }, - "v1.OwnerReference": { - "description": "Deprecated. Please use io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference instead.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference" - }, - "v1.Patch": { - "description": "Deprecated. Please use io.k8s.apimachinery.pkg.apis.meta.v1.Patch instead.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - }, - "v1.Preconditions": { - "description": "Deprecated. Please use io.k8s.apimachinery.pkg.apis.meta.v1.Preconditions instead.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Preconditions" - }, - "v1.ServerAddressByClientCIDR": { - "description": "Deprecated. Please use io.k8s.apimachinery.pkg.apis.meta.v1.ServerAddressByClientCIDR instead.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ServerAddressByClientCIDR" - }, - "v1.Status": { - "description": "Deprecated. Please use io.k8s.apimachinery.pkg.apis.meta.v1.Status instead.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" - }, - "v1.StatusCause": { - "description": "Deprecated. Please use io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause instead.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause" - }, - "v1.StatusDetails": { - "description": "Deprecated. Please use io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails instead.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails" - }, - "v1.Time": { - "description": "Deprecated. Please use io.k8s.apimachinery.pkg.apis.meta.v1.Time instead.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" - }, - "v1.WatchEvent": { - "description": "Deprecated. Please use io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent instead.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" - }, - "v1beta1.Cluster": { - "description": "Deprecated. Please use io.k8s.kubernetes.federation.apis.federation.v1beta1.Cluster instead.", - "$ref": "#/definitions/io.k8s.kubernetes.federation.apis.federation.v1beta1.Cluster" - }, - "v1beta1.ClusterCondition": { - "description": "Deprecated. Please use io.k8s.kubernetes.federation.apis.federation.v1beta1.ClusterCondition instead.", - "$ref": "#/definitions/io.k8s.kubernetes.federation.apis.federation.v1beta1.ClusterCondition" - }, - "v1beta1.ClusterList": { - "description": "Deprecated. Please use io.k8s.kubernetes.federation.apis.federation.v1beta1.ClusterList instead.", - "$ref": "#/definitions/io.k8s.kubernetes.federation.apis.federation.v1beta1.ClusterList" - }, - "v1beta1.ClusterSpec": { - "description": "Deprecated. Please use io.k8s.kubernetes.federation.apis.federation.v1beta1.ClusterSpec instead.", - "$ref": "#/definitions/io.k8s.kubernetes.federation.apis.federation.v1beta1.ClusterSpec" - }, - "v1beta1.ClusterStatus": { - "description": "Deprecated. Please use io.k8s.kubernetes.federation.apis.federation.v1beta1.ClusterStatus instead.", - "$ref": "#/definitions/io.k8s.kubernetes.federation.apis.federation.v1beta1.ClusterStatus" - }, - "v1beta1.ServerAddressByClientCIDR": { - "description": "Deprecated. Please use io.k8s.kubernetes.federation.apis.federation.v1beta1.ServerAddressByClientCIDR instead.", - "$ref": "#/definitions/io.k8s.kubernetes.federation.apis.federation.v1beta1.ServerAddressByClientCIDR" - }, - "version.Info": { - "description": "Deprecated. Please use io.k8s.apimachinery.pkg.version.Info instead.", - "$ref": "#/definitions/io.k8s.apimachinery.pkg.version.Info" - } - }, - "securityDefinitions": { - "BearerToken": { - "description": "Bearer Token authentication", - "type": "apiKey", - "name": "authorization", - "in": "header" - } - }, - "security": [ - { - "BearerToken": [] - } - ] - } diff --git a/federation/apis/swagger-spec/api.json b/federation/apis/swagger-spec/api.json deleted file mode 100644 index 3897ab506c8..00000000000 --- a/federation/apis/swagger-spec/api.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "swaggerVersion": "1.2", - "apiVersion": "", - "basePath": "https://10.10.10.10:6443", - "resourcePath": "/api", - "info": { - "title": "", - "description": "" - }, - "apis": [ - { - "path": "/api", - "description": "get available API versions", - "operations": [ - { - "type": "v1.APIVersions", - "method": "GET", - "summary": "get available API versions", - "nickname": "getAPIVersions", - "parameters": [], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ] - } - ] - } - ], - "models": { - "v1.APIVersions": { - "id": "v1.APIVersions", - "description": "APIVersions lists the versions that are available, to allow clients to discover the API at /api, which is the root path of the legacy v1 API.", - "required": [ - "versions", - "serverAddressByClientCIDRs" - ], - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "versions": { - "type": "array", - "items": { - "type": "string" - }, - "description": "versions are the api versions that are available." - }, - "serverAddressByClientCIDRs": { - "type": "array", - "items": { - "$ref": "v1.ServerAddressByClientCIDR" - }, - "description": "a map of client CIDR to server address that is serving this group. This is to help clients reach servers in the most network-efficient way possible. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR. The server returns only those CIDRs that it thinks that the client can match. For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP." - } - } - }, - "v1.ServerAddressByClientCIDR": { - "id": "v1.ServerAddressByClientCIDR", - "description": "ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match.", - "required": [ - "clientCIDR", - "serverAddress" - ], - "properties": { - "clientCIDR": { - "type": "string", - "description": "The CIDR with which clients can match their IP to figure out the server address that they should use." - }, - "serverAddress": { - "type": "string", - "description": "Address of this server, suitable for a client that matches the above CIDR. This can be a hostname, hostname:port, IP or IP:port." - } - } - } - } - } diff --git a/federation/apis/swagger-spec/apis.json b/federation/apis/swagger-spec/apis.json deleted file mode 100644 index 18d960ca599..00000000000 --- a/federation/apis/swagger-spec/apis.json +++ /dev/null @@ -1,138 +0,0 @@ -{ - "swaggerVersion": "1.2", - "apiVersion": "", - "basePath": "https://10.10.10.10:6443", - "resourcePath": "/apis", - "info": { - "title": "", - "description": "" - }, - "apis": [ - { - "path": "/apis", - "description": "get available API versions", - "operations": [ - { - "type": "v1.APIGroupList", - "method": "GET", - "summary": "get available API versions", - "nickname": "getAPIVersions", - "parameters": [], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ] - } - ] - } - ], - "models": { - "v1.APIGroupList": { - "id": "v1.APIGroupList", - "description": "APIGroupList is a list of APIGroup, to allow clients to discover the API at /apis.", - "required": [ - "groups" - ], - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "groups": { - "type": "array", - "items": { - "$ref": "v1.APIGroup" - }, - "description": "groups is a list of APIGroup." - } - } - }, - "v1.APIGroup": { - "id": "v1.APIGroup", - "description": "APIGroup contains the name, the supported versions, and the preferred version of a group.", - "required": [ - "name", - "versions", - "serverAddressByClientCIDRs" - ], - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "name": { - "type": "string", - "description": "name is the name of the group." - }, - "versions": { - "type": "array", - "items": { - "$ref": "v1.GroupVersionForDiscovery" - }, - "description": "versions are the versions supported in this group." - }, - "preferredVersion": { - "$ref": "v1.GroupVersionForDiscovery", - "description": "preferredVersion is the version preferred by the API server, which probably is the storage version." - }, - "serverAddressByClientCIDRs": { - "type": "array", - "items": { - "$ref": "v1.ServerAddressByClientCIDR" - }, - "description": "a map of client CIDR to server address that is serving this group. This is to help clients reach servers in the most network-efficient way possible. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR. The server returns only those CIDRs that it thinks that the client can match. For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP." - } - } - }, - "v1.GroupVersionForDiscovery": { - "id": "v1.GroupVersionForDiscovery", - "description": "GroupVersion contains the \"group/version\" and \"version\" string of a version. It is made a struct to keep extensibility.", - "required": [ - "groupVersion", - "version" - ], - "properties": { - "groupVersion": { - "type": "string", - "description": "groupVersion specifies the API group and version in the form \"group/version\"" - }, - "version": { - "type": "string", - "description": "version specifies the version in the form of \"version\". This is to save the clients the trouble of splitting the GroupVersion." - } - } - }, - "v1.ServerAddressByClientCIDR": { - "id": "v1.ServerAddressByClientCIDR", - "description": "ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match.", - "required": [ - "clientCIDR", - "serverAddress" - ], - "properties": { - "clientCIDR": { - "type": "string", - "description": "The CIDR with which clients can match their IP to figure out the server address that they should use." - }, - "serverAddress": { - "type": "string", - "description": "Address of this server, suitable for a client that matches the above CIDR. This can be a hostname, hostname:port, IP or IP:port." - } - } - } - } - } diff --git a/federation/apis/swagger-spec/extensions.json b/federation/apis/swagger-spec/extensions.json deleted file mode 100644 index 2e2fc1afd97..00000000000 --- a/federation/apis/swagger-spec/extensions.json +++ /dev/null @@ -1,114 +0,0 @@ -{ - "swaggerVersion": "1.2", - "apiVersion": "", - "basePath": "https://10.10.10.10:6443", - "resourcePath": "/apis/extensions", - "info": { - "title": "", - "description": "" - }, - "apis": [ - { - "path": "/apis/extensions", - "description": "get information of a group", - "operations": [ - { - "type": "v1.APIGroup", - "method": "GET", - "summary": "get information of a group", - "nickname": "getAPIGroup", - "parameters": [], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ] - } - ] - } - ], - "models": { - "v1.APIGroup": { - "id": "v1.APIGroup", - "description": "APIGroup contains the name, the supported versions, and the preferred version of a group.", - "required": [ - "name", - "versions", - "serverAddressByClientCIDRs" - ], - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "name": { - "type": "string", - "description": "name is the name of the group." - }, - "versions": { - "type": "array", - "items": { - "$ref": "v1.GroupVersionForDiscovery" - }, - "description": "versions are the versions supported in this group." - }, - "preferredVersion": { - "$ref": "v1.GroupVersionForDiscovery", - "description": "preferredVersion is the version preferred by the API server, which probably is the storage version." - }, - "serverAddressByClientCIDRs": { - "type": "array", - "items": { - "$ref": "v1.ServerAddressByClientCIDR" - }, - "description": "a map of client CIDR to server address that is serving this group. This is to help clients reach servers in the most network-efficient way possible. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR. The server returns only those CIDRs that it thinks that the client can match. For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP." - } - } - }, - "v1.GroupVersionForDiscovery": { - "id": "v1.GroupVersionForDiscovery", - "description": "GroupVersion contains the \"group/version\" and \"version\" string of a version. It is made a struct to keep extensibility.", - "required": [ - "groupVersion", - "version" - ], - "properties": { - "groupVersion": { - "type": "string", - "description": "groupVersion specifies the API group and version in the form \"group/version\"" - }, - "version": { - "type": "string", - "description": "version specifies the version in the form of \"version\". This is to save the clients the trouble of splitting the GroupVersion." - } - } - }, - "v1.ServerAddressByClientCIDR": { - "id": "v1.ServerAddressByClientCIDR", - "description": "ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match.", - "required": [ - "clientCIDR", - "serverAddress" - ], - "properties": { - "clientCIDR": { - "type": "string", - "description": "The CIDR with which clients can match their IP to figure out the server address that they should use." - }, - "serverAddress": { - "type": "string", - "description": "Address of this server, suitable for a client that matches the above CIDR. This can be a hostname, hostname:port, IP or IP:port." - } - } - } - } - } diff --git a/federation/apis/swagger-spec/extensions_v1beta1.json b/federation/apis/swagger-spec/extensions_v1beta1.json deleted file mode 100644 index 12e43aac627..00000000000 --- a/federation/apis/swagger-spec/extensions_v1beta1.json +++ /dev/null @@ -1,8283 +0,0 @@ -{ - "swaggerVersion": "1.2", - "apiVersion": "extensions/v1beta1", - "basePath": "https://10.10.10.10:6443", - "resourcePath": "/apis/extensions/v1beta1", - "info": { - "title": "", - "description": "" - }, - "apis": [ - { - "path": "/apis/extensions/v1beta1/namespaces/{namespace}/daemonsets", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1beta1.DaemonSetList", - "method": "GET", - "summary": "list or watch objects of kind DaemonSet", - "nickname": "listNamespacedDaemonSet", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.DaemonSetList" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.DaemonSet", - "method": "POST", - "summary": "create a DaemonSet", - "nickname": "createNamespacedDaemonSet", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1beta1.DaemonSet", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.DaemonSet" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1beta1.DaemonSet" - }, - { - "code": 202, - "message": "Accepted", - "responseModel": "v1beta1.DaemonSet" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Status", - "method": "DELETE", - "summary": "delete collection of DaemonSet", - "nickname": "deletecollectionNamespacedDaemonSet", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Status" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/watch/namespaces/{namespace}/daemonsets", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch individual changes to a list of DaemonSet", - "nickname": "watchNamespacedDaemonSetList", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/namespaces/{namespace}/daemonsets/{name}", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1beta1.DaemonSet", - "method": "GET", - "summary": "read the specified DaemonSet", - "nickname": "readNamespacedDaemonSet", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "export", - "description": "Should this value be exported. Export strips fields that a user can not specify.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "exact", - "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the DaemonSet", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.DaemonSet" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.DaemonSet", - "method": "PUT", - "summary": "replace the specified DaemonSet", - "nickname": "replaceNamespacedDaemonSet", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1beta1.DaemonSet", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the DaemonSet", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.DaemonSet" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1beta1.DaemonSet" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.DaemonSet", - "method": "PATCH", - "summary": "partially update the specified DaemonSet", - "nickname": "patchNamespacedDaemonSet", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Patch", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the DaemonSet", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.DaemonSet" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ] - }, - { - "type": "v1.Status", - "method": "DELETE", - "summary": "delete a DaemonSet", - "nickname": "deleteNamespacedDaemonSet", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.DeleteOptions", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the DaemonSet", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Status" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/watch/namespaces/{namespace}/daemonsets/{name}", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch changes to an object of kind DaemonSet", - "nickname": "watchNamespacedDaemonSet", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the DaemonSet", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/daemonsets", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1beta1.DaemonSetList", - "method": "GET", - "summary": "list or watch objects of kind DaemonSet", - "nickname": "listDaemonSetForAllNamespaces", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.DaemonSetList" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/watch/daemonsets", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch individual changes to a list of DaemonSet", - "nickname": "watchDaemonSetListForAllNamespaces", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/namespaces/{namespace}/daemonsets/{name}/status", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1beta1.DaemonSet", - "method": "GET", - "summary": "read status of the specified DaemonSet", - "nickname": "readNamespacedDaemonSetStatus", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the DaemonSet", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.DaemonSet" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.DaemonSet", - "method": "PUT", - "summary": "replace status of the specified DaemonSet", - "nickname": "replaceNamespacedDaemonSetStatus", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1beta1.DaemonSet", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the DaemonSet", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.DaemonSet" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1beta1.DaemonSet" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.DaemonSet", - "method": "PATCH", - "summary": "partially update status of the specified DaemonSet", - "nickname": "patchNamespacedDaemonSetStatus", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Patch", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the DaemonSet", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.DaemonSet" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/namespaces/{namespace}/deployments", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1beta1.DeploymentList", - "method": "GET", - "summary": "list or watch objects of kind Deployment", - "nickname": "listNamespacedDeployment", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.DeploymentList" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.Deployment", - "method": "POST", - "summary": "create a Deployment", - "nickname": "createNamespacedDeployment", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1beta1.Deployment", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.Deployment" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1beta1.Deployment" - }, - { - "code": 202, - "message": "Accepted", - "responseModel": "v1beta1.Deployment" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Status", - "method": "DELETE", - "summary": "delete collection of Deployment", - "nickname": "deletecollectionNamespacedDeployment", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Status" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/watch/namespaces/{namespace}/deployments", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch individual changes to a list of Deployment", - "nickname": "watchNamespacedDeploymentList", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/namespaces/{namespace}/deployments/{name}", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1beta1.Deployment", - "method": "GET", - "summary": "read the specified Deployment", - "nickname": "readNamespacedDeployment", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "export", - "description": "Should this value be exported. Export strips fields that a user can not specify.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "exact", - "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Deployment", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.Deployment" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.Deployment", - "method": "PUT", - "summary": "replace the specified Deployment", - "nickname": "replaceNamespacedDeployment", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1beta1.Deployment", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Deployment", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.Deployment" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1beta1.Deployment" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.Deployment", - "method": "PATCH", - "summary": "partially update the specified Deployment", - "nickname": "patchNamespacedDeployment", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Patch", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Deployment", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.Deployment" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ] - }, - { - "type": "v1.Status", - "method": "DELETE", - "summary": "delete a Deployment", - "nickname": "deleteNamespacedDeployment", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.DeleteOptions", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Deployment", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Status" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/watch/namespaces/{namespace}/deployments/{name}", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch changes to an object of kind Deployment", - "nickname": "watchNamespacedDeployment", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Deployment", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/deployments", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1beta1.DeploymentList", - "method": "GET", - "summary": "list or watch objects of kind Deployment", - "nickname": "listDeploymentForAllNamespaces", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.DeploymentList" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/watch/deployments", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch individual changes to a list of Deployment", - "nickname": "watchDeploymentListForAllNamespaces", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/namespaces/{namespace}/deployments/{name}/rollback", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1beta1.DeploymentRollback", - "method": "POST", - "summary": "create rollback of a Deployment", - "nickname": "createNamespacedDeploymentRollback", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1beta1.DeploymentRollback", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the DeploymentRollback", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.DeploymentRollback" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1beta1.DeploymentRollback" - }, - { - "code": 202, - "message": "Accepted", - "responseModel": "v1beta1.DeploymentRollback" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/namespaces/{namespace}/deployments/{name}/scale", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1beta1.Scale", - "method": "GET", - "summary": "read scale of the specified Deployment", - "nickname": "readNamespacedDeploymentScale", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Scale", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.Scale" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.Scale", - "method": "PUT", - "summary": "replace scale of the specified Deployment", - "nickname": "replaceNamespacedDeploymentScale", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1beta1.Scale", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Scale", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.Scale" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1beta1.Scale" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.Scale", - "method": "PATCH", - "summary": "partially update scale of the specified Deployment", - "nickname": "patchNamespacedDeploymentScale", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Patch", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Scale", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.Scale" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/namespaces/{namespace}/deployments/{name}/status", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1beta1.Deployment", - "method": "GET", - "summary": "read status of the specified Deployment", - "nickname": "readNamespacedDeploymentStatus", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Deployment", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.Deployment" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.Deployment", - "method": "PUT", - "summary": "replace status of the specified Deployment", - "nickname": "replaceNamespacedDeploymentStatus", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1beta1.Deployment", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Deployment", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.Deployment" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1beta1.Deployment" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.Deployment", - "method": "PATCH", - "summary": "partially update status of the specified Deployment", - "nickname": "patchNamespacedDeploymentStatus", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Patch", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Deployment", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.Deployment" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/namespaces/{namespace}/ingresses", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1beta1.IngressList", - "method": "GET", - "summary": "list or watch objects of kind Ingress", - "nickname": "listNamespacedIngress", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.IngressList" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.Ingress", - "method": "POST", - "summary": "create an Ingress", - "nickname": "createNamespacedIngress", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1beta1.Ingress", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.Ingress" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1beta1.Ingress" - }, - { - "code": 202, - "message": "Accepted", - "responseModel": "v1beta1.Ingress" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Status", - "method": "DELETE", - "summary": "delete collection of Ingress", - "nickname": "deletecollectionNamespacedIngress", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Status" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/watch/namespaces/{namespace}/ingresses", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch individual changes to a list of Ingress", - "nickname": "watchNamespacedIngressList", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/namespaces/{namespace}/ingresses/{name}", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1beta1.Ingress", - "method": "GET", - "summary": "read the specified Ingress", - "nickname": "readNamespacedIngress", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "export", - "description": "Should this value be exported. Export strips fields that a user can not specify.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "exact", - "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Ingress", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.Ingress" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.Ingress", - "method": "PUT", - "summary": "replace the specified Ingress", - "nickname": "replaceNamespacedIngress", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1beta1.Ingress", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Ingress", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.Ingress" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1beta1.Ingress" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.Ingress", - "method": "PATCH", - "summary": "partially update the specified Ingress", - "nickname": "patchNamespacedIngress", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Patch", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Ingress", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.Ingress" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ] - }, - { - "type": "v1.Status", - "method": "DELETE", - "summary": "delete an Ingress", - "nickname": "deleteNamespacedIngress", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.DeleteOptions", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Ingress", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Status" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/watch/namespaces/{namespace}/ingresses/{name}", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch changes to an object of kind Ingress", - "nickname": "watchNamespacedIngress", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Ingress", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/ingresses", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1beta1.IngressList", - "method": "GET", - "summary": "list or watch objects of kind Ingress", - "nickname": "listIngressForAllNamespaces", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.IngressList" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/watch/ingresses", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch individual changes to a list of Ingress", - "nickname": "watchIngressListForAllNamespaces", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/namespaces/{namespace}/ingresses/{name}/status", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1beta1.Ingress", - "method": "GET", - "summary": "read status of the specified Ingress", - "nickname": "readNamespacedIngressStatus", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Ingress", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.Ingress" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.Ingress", - "method": "PUT", - "summary": "replace status of the specified Ingress", - "nickname": "replaceNamespacedIngressStatus", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1beta1.Ingress", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Ingress", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.Ingress" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1beta1.Ingress" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.Ingress", - "method": "PATCH", - "summary": "partially update status of the specified Ingress", - "nickname": "patchNamespacedIngressStatus", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Patch", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Ingress", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.Ingress" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/namespaces/{namespace}/replicasets", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1beta1.ReplicaSetList", - "method": "GET", - "summary": "list or watch objects of kind ReplicaSet", - "nickname": "listNamespacedReplicaSet", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.ReplicaSetList" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.ReplicaSet", - "method": "POST", - "summary": "create a ReplicaSet", - "nickname": "createNamespacedReplicaSet", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1beta1.ReplicaSet", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.ReplicaSet" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1beta1.ReplicaSet" - }, - { - "code": 202, - "message": "Accepted", - "responseModel": "v1beta1.ReplicaSet" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Status", - "method": "DELETE", - "summary": "delete collection of ReplicaSet", - "nickname": "deletecollectionNamespacedReplicaSet", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Status" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/watch/namespaces/{namespace}/replicasets", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch individual changes to a list of ReplicaSet", - "nickname": "watchNamespacedReplicaSetList", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/namespaces/{namespace}/replicasets/{name}", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1beta1.ReplicaSet", - "method": "GET", - "summary": "read the specified ReplicaSet", - "nickname": "readNamespacedReplicaSet", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "export", - "description": "Should this value be exported. Export strips fields that a user can not specify.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "exact", - "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the ReplicaSet", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.ReplicaSet" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.ReplicaSet", - "method": "PUT", - "summary": "replace the specified ReplicaSet", - "nickname": "replaceNamespacedReplicaSet", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1beta1.ReplicaSet", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the ReplicaSet", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.ReplicaSet" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1beta1.ReplicaSet" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.ReplicaSet", - "method": "PATCH", - "summary": "partially update the specified ReplicaSet", - "nickname": "patchNamespacedReplicaSet", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Patch", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the ReplicaSet", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.ReplicaSet" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ] - }, - { - "type": "v1.Status", - "method": "DELETE", - "summary": "delete a ReplicaSet", - "nickname": "deleteNamespacedReplicaSet", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.DeleteOptions", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the ReplicaSet", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Status" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/watch/namespaces/{namespace}/replicasets/{name}", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch changes to an object of kind ReplicaSet", - "nickname": "watchNamespacedReplicaSet", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the ReplicaSet", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/replicasets", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1beta1.ReplicaSetList", - "method": "GET", - "summary": "list or watch objects of kind ReplicaSet", - "nickname": "listReplicaSetForAllNamespaces", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.ReplicaSetList" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/watch/replicasets", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch individual changes to a list of ReplicaSet", - "nickname": "watchReplicaSetListForAllNamespaces", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/namespaces/{namespace}/replicasets/{name}/scale", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1beta1.Scale", - "method": "GET", - "summary": "read scale of the specified ReplicaSet", - "nickname": "readNamespacedReplicaSetScale", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Scale", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.Scale" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.Scale", - "method": "PUT", - "summary": "replace scale of the specified ReplicaSet", - "nickname": "replaceNamespacedReplicaSetScale", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1beta1.Scale", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Scale", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.Scale" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1beta1.Scale" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.Scale", - "method": "PATCH", - "summary": "partially update scale of the specified ReplicaSet", - "nickname": "patchNamespacedReplicaSetScale", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Patch", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Scale", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.Scale" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1/namespaces/{namespace}/replicasets/{name}/status", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1beta1.ReplicaSet", - "method": "GET", - "summary": "read status of the specified ReplicaSet", - "nickname": "readNamespacedReplicaSetStatus", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the ReplicaSet", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.ReplicaSet" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.ReplicaSet", - "method": "PUT", - "summary": "replace status of the specified ReplicaSet", - "nickname": "replaceNamespacedReplicaSetStatus", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1beta1.ReplicaSet", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the ReplicaSet", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.ReplicaSet" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1beta1.ReplicaSet" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.ReplicaSet", - "method": "PATCH", - "summary": "partially update status of the specified ReplicaSet", - "nickname": "patchNamespacedReplicaSetStatus", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Patch", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the ReplicaSet", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.ReplicaSet" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ] - } - ] - }, - { - "path": "/apis/extensions/v1beta1", - "description": "API at /apis/extensions/v1beta1", - "operations": [ - { - "type": "v1.APIResourceList", - "method": "GET", - "summary": "get available resources", - "nickname": "getAPIResources", - "parameters": [], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ] - } - ] - } - ], - "models": { - "v1beta1.DaemonSetList": { - "id": "v1beta1.DaemonSetList", - "description": "DaemonSetList is a collection of daemon sets.", - "required": [ - "items" - ], - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ListMeta", - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" - }, - "items": { - "type": "array", - "items": { - "$ref": "v1beta1.DaemonSet" - }, - "description": "A list of daemon sets." - } - } - }, - "v1.ListMeta": { - "id": "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": { - "selfLink": { - "type": "string", - "description": "selfLink is a URL representing this object. Populated by the system. Read-only." - }, - "resourceVersion": { - "type": "string", - "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/api-conventions.md#concurrency-control-and-consistency" - }, - "continue": { - "type": "string", - "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 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." - } - } - }, - "v1beta1.DaemonSet": { - "id": "v1beta1.DaemonSet", - "description": "DEPRECATED - This group version of DaemonSet is deprecated by apps/v1beta2/DaemonSet. See the release notes for more information. DaemonSet represents the configuration of a daemon set.", - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ObjectMeta", - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" - }, - "spec": { - "$ref": "v1beta1.DaemonSetSpec", - "description": "The desired behavior of this daemon set. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status" - }, - "status": { - "$ref": "v1beta1.DaemonSetStatus", - "description": "The current status of this daemon set. This data may be out of date by some window of time. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status" - } - } - }, - "v1.ObjectMeta": { - "id": "v1.ObjectMeta", - "description": "ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.", - "properties": { - "name": { - "type": "string", - "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: http://kubernetes.io/docs/user-guide/identifiers#names" - }, - "generateName": { - "type": "string", - "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 NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header).\n\nApplied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency" - }, - "namespace": { - "type": "string", - "description": "Namespace defines the space within 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: http://kubernetes.io/docs/user-guide/namespaces" - }, - "selfLink": { - "type": "string", - "description": "SelfLink is a URL representing this object. Populated by the system. Read-only." - }, - "uid": { - "type": "string", - "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: http://kubernetes.io/docs/user-guide/identifiers#uids" - }, - "resourceVersion": { - "type": "string", - "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/api-conventions.md#concurrency-control-and-consistency" - }, - "generation": { - "type": "integer", - "format": "int64", - "description": "A sequence number representing a specific generation of the desired state. Populated by the system. Read-only." - }, - "creationTimestamp": { - "type": "string", - "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/api-conventions.md#metadata" - }, - "deletionTimestamp": { - "type": "string", - "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 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/api-conventions.md#metadata" - }, - "deletionGracePeriodSeconds": { - "type": "integer", - "format": "int64", - "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." - }, - "labels": { - "type": "object", - "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: http://kubernetes.io/docs/user-guide/labels" - }, - "annotations": { - "type": "object", - "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: http://kubernetes.io/docs/user-guide/annotations" - }, - "ownerReferences": { - "type": "array", - "items": { - "$ref": "v1.OwnerReference" - }, - "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." - }, - "initializers": { - "$ref": "v1.Initializers", - "description": "An initializer is a controller which enforces some system invariant at object creation time. This field is a list of initializers that have not yet acted on this object. If nil or empty, this object has been completely initialized. Otherwise, the object is considered uninitialized and is hidden (in list/watch and get calls) from clients that haven't explicitly asked to observe uninitialized objects.\n\nWhen an object is created, the system will populate this list with the current set of initializers. Only privileged users may set or modify this list. Once it is empty, it may not be modified further by any user." - }, - "finalizers": { - "type": "array", - "items": { - "type": "string" - }, - "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." - }, - "clusterName": { - "type": "string", - "description": "The name of the cluster which the object belongs to. This is used to distinguish resources with same name and namespace in different clusters. This field is not set anywhere right now and apiserver is going to ignore it if set in create or update request." - } - } - }, - "v1.OwnerReference": { - "id": "v1.OwnerReference", - "description": "OwnerReference contains enough information to let you identify an owning object. Currently, an owning object must be in the same namespace, so there is no namespace field.", - "required": [ - "apiVersion", - "kind", - "name", - "uid" - ], - "properties": { - "apiVersion": { - "type": "string", - "description": "API version of the referent." - }, - "kind": { - "type": "string", - "description": "Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds" - }, - "name": { - "type": "string", - "description": "Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names" - }, - "uid": { - "type": "string", - "description": "UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids" - }, - "controller": { - "type": "boolean", - "description": "If true, this reference points to the managing controller." - }, - "blockOwnerDeletion": { - "type": "boolean", - "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. Defaults to false. To set this field, a user needs \"delete\" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned." - } - } - }, - "v1.Initializers": { - "id": "v1.Initializers", - "description": "Initializers tracks the progress of initialization.", - "required": [ - "pending" - ], - "properties": { - "pending": { - "type": "array", - "items": { - "$ref": "v1.Initializer" - }, - "description": "Pending is a list of initializers that must execute in order before this object is visible. When the last pending initializer is removed, and no failing result is set, the initializers struct will be set to nil and the object is considered as initialized and visible to all clients." - }, - "result": { - "$ref": "v1.Status", - "description": "If result is set with the Failure field, the object will be persisted to storage and then deleted, ensuring that other clients can observe the deletion." - } - } - }, - "v1.Initializer": { - "id": "v1.Initializer", - "description": "Initializer is information about an initializer that has not yet completed.", - "required": [ - "name" - ], - "properties": { - "name": { - "type": "string", - "description": "name of the process that is responsible for initializing this object." - } - } - }, - "v1.Status": { - "id": "v1.Status", - "description": "Status is a return value for calls that don't return other objects.", - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ListMeta", - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds" - }, - "status": { - "type": "string", - "description": "Status of the operation. One of: \"Success\" or \"Failure\". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status" - }, - "message": { - "type": "string", - "description": "A human-readable description of the status of this operation." - }, - "reason": { - "type": "string", - "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." - }, - "details": { - "$ref": "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." - }, - "code": { - "type": "integer", - "format": "int32", - "description": "Suggested HTTP return code for this status, 0 if not set." - } - } - }, - "v1.StatusDetails": { - "id": "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": { - "name": { - "type": "string", - "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described)." - }, - "group": { - "type": "string", - "description": "The group attribute of the resource associated with the status StatusReason." - }, - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "uid": { - "type": "string", - "description": "UID of the resource. (when there is a single resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids" - }, - "causes": { - "type": "array", - "items": { - "$ref": "v1.StatusCause" - }, - "description": "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes." - }, - "retryAfterSeconds": { - "type": "integer", - "format": "int32", - "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." - } - } - }, - "v1.StatusCause": { - "id": "v1.StatusCause", - "description": "StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.", - "properties": { - "reason": { - "type": "string", - "description": "A machine-readable description of the cause of the error. If this value is empty there is no information available." - }, - "message": { - "type": "string", - "description": "A human-readable description of the cause of the error. This field may be presented as-is to a reader." - }, - "field": { - "type": "string", - "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\"" - } - } - }, - "v1beta1.DaemonSetSpec": { - "id": "v1beta1.DaemonSetSpec", - "description": "DaemonSetSpec is the specification of a daemon set.", - "required": [ - "template" - ], - "properties": { - "selector": { - "$ref": "v1.LabelSelector", - "description": "A label query over pods that are managed by the daemon set. Must match in order to be controlled. If empty, defaulted to labels on Pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors" - }, - "template": { - "$ref": "v1.PodTemplateSpec", - "description": "An object that describes the pod that will be created. The DaemonSet will create exactly one copy of this pod on every node that matches the template's node selector (or on every node if no node selector is specified). More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template" - }, - "updateStrategy": { - "$ref": "v1beta1.DaemonSetUpdateStrategy", - "description": "An update strategy to replace existing DaemonSet pods with new pods." - }, - "minReadySeconds": { - "type": "integer", - "format": "int32", - "description": "The minimum number of seconds for which a newly created DaemonSet 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)." - }, - "templateGeneration": { - "type": "integer", - "format": "int64", - "description": "DEPRECATED. A sequence number representing a specific generation of the template. Populated by the system. It can be set only during the creation." - }, - "revisionHistoryLimit": { - "type": "integer", - "format": "int32", - "description": "The number of old history to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10." - } - } - }, - "v1.LabelSelector": { - "id": "v1.LabelSelector", - "description": "A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.", - "properties": { - "matchLabels": { - "type": "object", - "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed." - }, - "matchExpressions": { - "type": "array", - "items": { - "$ref": "v1.LabelSelectorRequirement" - }, - "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed." - } - } - }, - "v1.LabelSelectorRequirement": { - "id": "v1.LabelSelectorRequirement", - "description": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", - "required": [ - "key", - "operator" - ], - "properties": { - "key": { - "type": "string", - "description": "key is the label key that the selector applies to." - }, - "operator": { - "type": "string", - "description": "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist." - }, - "values": { - "type": "array", - "items": { - "type": "string" - }, - "description": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch." - } - } - }, - "v1.PodTemplateSpec": { - "id": "v1.PodTemplateSpec", - "description": "PodTemplateSpec describes the data a pod should have when created from a template", - "properties": { - "metadata": { - "$ref": "v1.ObjectMeta", - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" - }, - "spec": { - "$ref": "v1.PodSpec", - "description": "Specification of the desired behavior of the pod. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status" - } - } - }, - "v1.PodSpec": { - "id": "v1.PodSpec", - "description": "PodSpec is a description of a pod.", - "required": [ - "containers" - ], - "properties": { - "volumes": { - "type": "array", - "items": { - "$ref": "v1.Volume" - }, - "description": "List of volumes that can be mounted by containers belonging to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes" - }, - "initContainers": { - "type": "array", - "items": { - "$ref": "v1.Container" - }, - "description": "List of initialization containers belonging to the pod. Init containers are executed in order prior to containers being started. If any init container fails, the pod is considered to have failed and is handled according to its restartPolicy. The name for an init container or normal container must be unique among all containers. Init containers may not have Lifecycle actions, Readiness probes, or Liveness probes. The resourceRequirements of an init container are taken into account during scheduling by finding the highest request/limit for each resource type, and then using the max of of that value or the sum of the normal containers. Limits are applied to init containers in a similar fashion. Init containers cannot currently be added or removed. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/" - }, - "containers": { - "type": "array", - "items": { - "$ref": "v1.Container" - }, - "description": "List of containers belonging to the pod. Containers cannot currently be added or removed. There must be at least one container in a Pod. Cannot be updated." - }, - "restartPolicy": { - "type": "string", - "description": "Restart policy for all containers within the pod. One of Always, OnFailure, Never. Default to Always. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy" - }, - "terminationGracePeriodSeconds": { - "type": "integer", - "format": "int64", - "description": "Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period will be used instead. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. Defaults to 30 seconds." - }, - "activeDeadlineSeconds": { - "type": "integer", - "format": "int64", - "description": "Optional duration in seconds the pod may be active on the node relative to StartTime before the system will actively try to mark it failed and kill associated containers. Value must be a positive integer." - }, - "dnsPolicy": { - "type": "string", - "description": "Set DNS policy for containers within the pod. One of 'ClusterFirstWithHostNet', 'ClusterFirst' or 'Default'. Defaults to \"ClusterFirst\". To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'." - }, - "nodeSelector": { - "type": "object", - "description": "NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node's labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/" - }, - "serviceAccountName": { - "type": "string", - "description": "ServiceAccountName is the name of the ServiceAccount to use to run this pod. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/" - }, - "serviceAccount": { - "type": "string", - "description": "DeprecatedServiceAccount is a depreciated alias for ServiceAccountName. Deprecated: Use serviceAccountName instead." - }, - "automountServiceAccountToken": { - "type": "boolean", - "description": "AutomountServiceAccountToken indicates whether a service account token should be automatically mounted." - }, - "nodeName": { - "type": "string", - "description": "NodeName is a request to schedule this pod onto a specific node. If it is non-empty, the scheduler simply schedules this pod onto that node, assuming that it fits resource requirements." - }, - "hostNetwork": { - "type": "boolean", - "description": "Host networking requested for this pod. Use the host's network namespace. If this option is set, the ports that will be used must be specified. Default to false." - }, - "hostPID": { - "type": "boolean", - "description": "Use the host's pid namespace. Optional: Default to false." - }, - "hostIPC": { - "type": "boolean", - "description": "Use the host's ipc namespace. Optional: Default to false." - }, - "securityContext": { - "$ref": "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." - }, - "imagePullSecrets": { - "type": "array", - "items": { - "$ref": "v1.LocalObjectReference" - }, - "description": "ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. If specified, these secrets will be passed to individual puller implementations for them to use. For example, in the case of docker, only DockerConfig type secrets are honored. More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod" - }, - "hostname": { - "type": "string", - "description": "Specifies the hostname of the Pod If not specified, the pod's hostname will be set to a system-defined value." - }, - "subdomain": { - "type": "string", - "description": "If specified, the fully qualified Pod hostname will be \"\u003chostname\u003e.\u003csubdomain\u003e.\u003cpod namespace\u003e.svc.\u003ccluster domain\u003e\". If not specified, the pod will not have a domainname at all." - }, - "affinity": { - "$ref": "v1.Affinity", - "description": "If specified, the pod's scheduling constraints" - }, - "schedulerName": { - "type": "string", - "description": "If specified, the pod will be dispatched by specified scheduler. If not specified, the pod will be dispatched by default scheduler." - }, - "tolerations": { - "type": "array", - "items": { - "$ref": "v1.Toleration" - }, - "description": "If specified, the pod's tolerations." - }, - "hostAliases": { - "type": "array", - "items": { - "$ref": "v1.HostAlias" - }, - "description": "HostAliases is an optional list of hosts and IPs that will be injected into the pod's hosts file if specified. This is only valid for non-hostNetwork pods." - }, - "priorityClassName": { - "type": "string", - "description": "If specified, indicates the pod's priority. \"SYSTEM\" is a special keyword which indicates the highest priority. Any other name must be defined by creating a PriorityClass object with that name. If not specified, the pod priority will be default or zero if there is no default." - }, - "priority": { - "type": "integer", - "format": "int32", - "description": "The priority value. Various system components use this field to find the priority of the pod. 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." - } - } - }, - "v1.Volume": { - "id": "v1.Volume", - "description": "Volume represents a named volume in a pod that may be accessed by any container in the pod.", - "required": [ - "name" - ], - "properties": { - "name": { - "type": "string", - "description": "Volume's name. Must be a DNS_LABEL and unique within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names" - }, - "hostPath": { - "$ref": "v1.HostPathVolumeSource", - "description": "HostPath represents a pre-existing file or directory on the host machine that is directly exposed to the container. This is generally used for system agents or other privileged things that are allowed to see the host machine. Most containers will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath" - }, - "emptyDir": { - "$ref": "v1.EmptyDirVolumeSource", - "description": "EmptyDir represents a temporary directory that shares a pod's lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir" - }, - "gcePersistentDisk": { - "$ref": "v1.GCEPersistentDiskVolumeSource", - "description": "GCEPersistentDisk represents a GCE Disk resource that is attached to a kubelet's host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk" - }, - "awsElasticBlockStore": { - "$ref": "v1.AWSElasticBlockStoreVolumeSource", - "description": "AWSElasticBlockStore represents an AWS Disk resource that is attached to a kubelet's host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore" - }, - "gitRepo": { - "$ref": "v1.GitRepoVolumeSource", - "description": "GitRepo represents a git repository at a particular revision." - }, - "secret": { - "$ref": "v1.SecretVolumeSource", - "description": "Secret represents a secret that should populate this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret" - }, - "nfs": { - "$ref": "v1.NFSVolumeSource", - "description": "NFS represents an NFS mount on the host that shares a pod's lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs" - }, - "iscsi": { - "$ref": "v1.ISCSIVolumeSource", - "description": "ISCSI represents an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod. More info: https://releases.k8s.io/HEAD/examples/volumes/iscsi/README.md" - }, - "glusterfs": { - "$ref": "v1.GlusterfsVolumeSource", - "description": "Glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. More info: https://releases.k8s.io/HEAD/examples/volumes/glusterfs/README.md" - }, - "persistentVolumeClaim": { - "$ref": "v1.PersistentVolumeClaimVolumeSource", - "description": "PersistentVolumeClaimVolumeSource represents a reference to a PersistentVolumeClaim in the same namespace. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims" - }, - "rbd": { - "$ref": "v1.RBDVolumeSource", - "description": "RBD represents a Rados Block Device mount on the host that shares a pod's lifetime. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md" - }, - "flexVolume": { - "$ref": "v1.FlexVolumeSource", - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future." - }, - "cinder": { - "$ref": "v1.CinderVolumeSource", - "description": "Cinder represents a cinder volume attached and mounted on kubelets host machine More info: https://releases.k8s.io/HEAD/examples/mysql-cinder-pd/README.md" - }, - "cephfs": { - "$ref": "v1.CephFSVolumeSource", - "description": "CephFS represents a Ceph FS mount on the host that shares a pod's lifetime" - }, - "flocker": { - "$ref": "v1.FlockerVolumeSource", - "description": "Flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running" - }, - "downwardAPI": { - "$ref": "v1.DownwardAPIVolumeSource", - "description": "DownwardAPI represents downward API about the pod that should populate this volume" - }, - "fc": { - "$ref": "v1.FCVolumeSource", - "description": "FC represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod." - }, - "azureFile": { - "$ref": "v1.AzureFileVolumeSource", - "description": "AzureFile represents an Azure File Service mount on the host and bind mount to the pod." - }, - "configMap": { - "$ref": "v1.ConfigMapVolumeSource", - "description": "ConfigMap represents a configMap that should populate this volume" - }, - "vsphereVolume": { - "$ref": "v1.VsphereVirtualDiskVolumeSource", - "description": "VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine" - }, - "quobyte": { - "$ref": "v1.QuobyteVolumeSource", - "description": "Quobyte represents a Quobyte mount on the host that shares a pod's lifetime" - }, - "azureDisk": { - "$ref": "v1.AzureDiskVolumeSource", - "description": "AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod." - }, - "photonPersistentDisk": { - "$ref": "v1.PhotonPersistentDiskVolumeSource", - "description": "PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine" - }, - "projected": { - "$ref": "v1.ProjectedVolumeSource", - "description": "Items for all in one resources secrets, configmaps, and downward API" - }, - "portworxVolume": { - "$ref": "v1.PortworxVolumeSource", - "description": "PortworxVolume represents a portworx volume attached and mounted on kubelets host machine" - }, - "scaleIO": { - "$ref": "v1.ScaleIOVolumeSource", - "description": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes." - }, - "storageos": { - "$ref": "v1.StorageOSVolumeSource", - "description": "StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes." - } - } - }, - "v1.HostPathVolumeSource": { - "id": "v1.HostPathVolumeSource", - "description": "Represents a host path mapped into a pod. Host path volumes do not support ownership management or SELinux relabeling.", - "required": [ - "path" - ], - "properties": { - "path": { - "type": "string", - "description": "Path of the directory on the host. If the path is a symlink, it will follow the link to the real path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath" - }, - "type": { - "$ref": "v1.HostPathType", - "description": "Type for HostPath Volume Defaults to \"\" More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath" - } - } - }, - "v1.HostPathType": { - "id": "v1.HostPathType", - "properties": {} - }, - "v1.EmptyDirVolumeSource": { - "id": "v1.EmptyDirVolumeSource", - "description": "Represents an empty directory for a pod. Empty directory volumes support ownership management and SELinux relabeling.", - "properties": { - "medium": { - "type": "string", - "description": "What type of storage medium should back this directory. The default is \"\" which means to use the node's default medium. Must be an empty string (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir" - }, - "sizeLimit": { - "type": "string", - "description": "Total amount of local storage required for this EmptyDir volume. The size limit is also applicable for memory medium. The maximum usage on memory medium EmptyDir would be the minimum value between the SizeLimit specified here and the sum of memory limits of all containers in a pod. The default is nil which means that the limit is undefined. More info: http://kubernetes.io/docs/user-guide/volumes#emptydir" - } - } - }, - "v1.GCEPersistentDiskVolumeSource": { - "id": "v1.GCEPersistentDiskVolumeSource", - "description": "Represents a Persistent Disk resource in Google Compute Engine.\n\nA GCE PD must exist before mounting to a container. The disk must also be in the same GCE project and zone as the kubelet. A GCE PD can only be mounted as read/write once or read-only many times. GCE PDs support ownership management and SELinux relabeling.", - "required": [ - "pdName" - ], - "properties": { - "pdName": { - "type": "string", - "description": "Unique name of the PD resource in GCE. Used to identify the disk in GCE. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk" - }, - "fsType": { - "type": "string", - "description": "Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk" - }, - "partition": { - "type": "integer", - "format": "int32", - "description": "The partition in the volume that you want to mount. If omitted, the default is to mount by volume name. Examples: For volume /dev/sda1, you specify the partition as \"1\". Similarly, the volume partition for /dev/sda is \"0\" (or you can leave the property empty). More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk" - }, - "readOnly": { - "type": "boolean", - "description": "ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk" - } - } - }, - "v1.AWSElasticBlockStoreVolumeSource": { - "id": "v1.AWSElasticBlockStoreVolumeSource", - "description": "Represents a Persistent Disk resource in AWS.\n\nAn AWS EBS disk must exist before mounting to a container. The disk must also be in the same AWS zone as the kubelet. An AWS EBS disk can only be mounted as read/write once. AWS EBS volumes support ownership management and SELinux relabeling.", - "required": [ - "volumeID" - ], - "properties": { - "volumeID": { - "type": "string", - "description": "Unique ID of the persistent disk resource in AWS (Amazon EBS volume). More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore" - }, - "fsType": { - "type": "string", - "description": "Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore" - }, - "partition": { - "type": "integer", - "format": "int32", - "description": "The partition in the volume that you want to mount. If omitted, the default is to mount by volume name. Examples: For volume /dev/sda1, you specify the partition as \"1\". Similarly, the volume partition for /dev/sda is \"0\" (or you can leave the property empty)." - }, - "readOnly": { - "type": "boolean", - "description": "Specify \"true\" to force and set the ReadOnly property in VolumeMounts to \"true\". If omitted, the default is \"false\". More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore" - } - } - }, - "v1.GitRepoVolumeSource": { - "id": "v1.GitRepoVolumeSource", - "description": "Represents a volume that is populated with the contents of a git repository. Git repo volumes do not support ownership management. Git repo volumes support SELinux relabeling.", - "required": [ - "repository" - ], - "properties": { - "repository": { - "type": "string", - "description": "Repository URL" - }, - "revision": { - "type": "string", - "description": "Commit hash for the specified revision." - }, - "directory": { - "type": "string", - "description": "Target directory name. Must not contain or start with '..'. If '.' is supplied, the volume directory will be the git repository. Otherwise, if specified, the volume will contain the git repository in the subdirectory with the given name." - } - } - }, - "v1.SecretVolumeSource": { - "id": "v1.SecretVolumeSource", - "description": "Adapts a Secret into a volume.\n\nThe contents of the target Secret's Data field will be presented in a volume as files using the keys in the Data field as the file names. Secret volumes support ownership management and SELinux relabeling.", - "properties": { - "secretName": { - "type": "string", - "description": "Name of the secret in the pod's namespace to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret" - }, - "items": { - "type": "array", - "items": { - "$ref": "v1.KeyToPath" - }, - "description": "If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'." - }, - "defaultMode": { - "type": "integer", - "format": "int32", - "description": "Optional: mode bits to use on created files by default. Must be a value between 0 and 0777. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set." - }, - "optional": { - "type": "boolean", - "description": "Specify whether the Secret or it's keys must be defined" - } - } - }, - "v1.KeyToPath": { - "id": "v1.KeyToPath", - "description": "Maps a string key to a path within a volume.", - "required": [ - "key", - "path" - ], - "properties": { - "key": { - "type": "string", - "description": "The key to project." - }, - "path": { - "type": "string", - "description": "The relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'." - }, - "mode": { - "type": "integer", - "format": "int32", - "description": "Optional: mode bits to use on this file, must be a value between 0 and 0777. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set." - } - } - }, - "v1.NFSVolumeSource": { - "id": "v1.NFSVolumeSource", - "description": "Represents an NFS mount that lasts the lifetime of a pod. NFS volumes do not support ownership management or SELinux relabeling.", - "required": [ - "server", - "path" - ], - "properties": { - "server": { - "type": "string", - "description": "Server is the hostname or IP address of the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs" - }, - "path": { - "type": "string", - "description": "Path that is exported by the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs" - }, - "readOnly": { - "type": "boolean", - "description": "ReadOnly here will force the NFS export to be mounted with read-only permissions. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs" - } - } - }, - "v1.ISCSIVolumeSource": { - "id": "v1.ISCSIVolumeSource", - "description": "Represents an ISCSI disk. ISCSI volumes can only be mounted as read/write once. ISCSI volumes support ownership management and SELinux relabeling.", - "required": [ - "targetPortal", - "iqn", - "lun" - ], - "properties": { - "targetPortal": { - "type": "string", - "description": "iSCSI target portal. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." - }, - "iqn": { - "type": "string", - "description": "Target iSCSI Qualified Name." - }, - "lun": { - "type": "integer", - "format": "int32", - "description": "iSCSI target lun number." - }, - "iscsiInterface": { - "type": "string", - "description": "Optional: Defaults to 'default' (tcp). iSCSI interface name that uses an iSCSI transport." - }, - "fsType": { - "type": "string", - "description": "Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi" - }, - "readOnly": { - "type": "boolean", - "description": "ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false." - }, - "portals": { - "type": "array", - "items": { - "type": "string" - }, - "description": "iSCSI target portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260)." - }, - "chapAuthDiscovery": { - "type": "boolean", - "description": "whether support iSCSI Discovery CHAP authentication" - }, - "chapAuthSession": { - "type": "boolean", - "description": "whether support iSCSI Session CHAP authentication" - }, - "secretRef": { - "$ref": "v1.LocalObjectReference", - "description": "CHAP secret for iSCSI target and initiator authentication" - }, - "initiatorName": { - "type": "string", - "description": "Custom iSCSI initiator name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface \u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection." - } - } - }, - "v1.LocalObjectReference": { - "id": "v1.LocalObjectReference", - "description": "LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace.", - "properties": { - "name": { - "type": "string", - "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names" - } - } - }, - "v1.GlusterfsVolumeSource": { - "id": "v1.GlusterfsVolumeSource", - "description": "Represents a Glusterfs mount that lasts the lifetime of a pod. Glusterfs volumes do not support ownership management or SELinux relabeling.", - "required": [ - "endpoints", - "path" - ], - "properties": { - "endpoints": { - "type": "string", - "description": "EndpointsName is the endpoint name that details Glusterfs topology. More info: https://releases.k8s.io/HEAD/examples/volumes/glusterfs/README.md#create-a-pod" - }, - "path": { - "type": "string", - "description": "Path is the Glusterfs volume path. More info: https://releases.k8s.io/HEAD/examples/volumes/glusterfs/README.md#create-a-pod" - }, - "readOnly": { - "type": "boolean", - "description": "ReadOnly here will force the Glusterfs volume to be mounted with read-only permissions. Defaults to false. More info: https://releases.k8s.io/HEAD/examples/volumes/glusterfs/README.md#create-a-pod" - } - } - }, - "v1.PersistentVolumeClaimVolumeSource": { - "id": "v1.PersistentVolumeClaimVolumeSource", - "description": "PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace. This volume finds the bound PV and mounts that volume for the pod. A PersistentVolumeClaimVolumeSource is, essentially, a wrapper around another type of volume that is owned by someone else (the system).", - "required": [ - "claimName" - ], - "properties": { - "claimName": { - "type": "string", - "description": "ClaimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims" - }, - "readOnly": { - "type": "boolean", - "description": "Will force the ReadOnly setting in VolumeMounts. Default false." - } - } - }, - "v1.RBDVolumeSource": { - "id": "v1.RBDVolumeSource", - "description": "Represents a Rados Block Device mount that lasts the lifetime of a pod. RBD volumes support ownership management and SELinux relabeling.", - "required": [ - "monitors", - "image" - ], - "properties": { - "monitors": { - "type": "array", - "items": { - "type": "string" - }, - "description": "A collection of Ceph monitors. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it" - }, - "image": { - "type": "string", - "description": "The rados image name. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it" - }, - "fsType": { - "type": "string", - "description": "Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd" - }, - "pool": { - "type": "string", - "description": "The rados pool name. Default is rbd. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it" - }, - "user": { - "type": "string", - "description": "The rados user name. Default is admin. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it" - }, - "keyring": { - "type": "string", - "description": "Keyring is the path to key ring for RBDUser. Default is /etc/ceph/keyring. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it" - }, - "secretRef": { - "$ref": "v1.LocalObjectReference", - "description": "SecretRef is name of the authentication secret for RBDUser. If provided overrides keyring. Default is nil. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it" - }, - "readOnly": { - "type": "boolean", - "description": "ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it" - } - } - }, - "v1.FlexVolumeSource": { - "id": "v1.FlexVolumeSource", - "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.", - "required": [ - "driver" - ], - "properties": { - "driver": { - "type": "string", - "description": "Driver is the name of the driver to use for this volume." - }, - "fsType": { - "type": "string", - "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". The default filesystem depends on FlexVolume script." - }, - "secretRef": { - "$ref": "v1.LocalObjectReference", - "description": "Optional: SecretRef is reference to the secret object containing sensitive information to pass to the plugin scripts. This may be empty if no secret object is specified. If the secret object contains more than one secret, all secrets are passed to the plugin scripts." - }, - "readOnly": { - "type": "boolean", - "description": "Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts." - }, - "options": { - "type": "object", - "description": "Optional: Extra command options if any." - } - } - }, - "v1.CinderVolumeSource": { - "id": "v1.CinderVolumeSource", - "description": "Represents a cinder volume resource in Openstack. A Cinder volume must exist before mounting to a container. The volume must also be in the same region as the kubelet. Cinder volumes support ownership management and SELinux relabeling.", - "required": [ - "volumeID" - ], - "properties": { - "volumeID": { - "type": "string", - "description": "volume id used to identify the volume in cinder More info: https://releases.k8s.io/HEAD/examples/mysql-cinder-pd/README.md" - }, - "fsType": { - "type": "string", - "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://releases.k8s.io/HEAD/examples/mysql-cinder-pd/README.md" - }, - "readOnly": { - "type": "boolean", - "description": "Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://releases.k8s.io/HEAD/examples/mysql-cinder-pd/README.md" - } - } - }, - "v1.CephFSVolumeSource": { - "id": "v1.CephFSVolumeSource", - "description": "Represents a Ceph Filesystem mount that lasts the lifetime of a pod Cephfs volumes do not support ownership management or SELinux relabeling.", - "required": [ - "monitors" - ], - "properties": { - "monitors": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Required: Monitors is a collection of Ceph monitors More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it" - }, - "path": { - "type": "string", - "description": "Optional: Used as the mounted root, rather than the full Ceph tree, default is /" - }, - "user": { - "type": "string", - "description": "Optional: User is the rados user name, default is admin More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it" - }, - "secretFile": { - "type": "string", - "description": "Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it" - }, - "secretRef": { - "$ref": "v1.LocalObjectReference", - "description": "Optional: SecretRef is reference to the authentication secret for User, default is empty. More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it" - }, - "readOnly": { - "type": "boolean", - "description": "Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it" - } - } - }, - "v1.FlockerVolumeSource": { - "id": "v1.FlockerVolumeSource", - "description": "Represents a Flocker volume mounted by the Flocker agent. One and only one of datasetName and datasetUUID should be set. Flocker volumes do not support ownership management or SELinux relabeling.", - "properties": { - "datasetName": { - "type": "string", - "description": "Name of the dataset stored as metadata -\u003e name on the dataset for Flocker should be considered as deprecated" - }, - "datasetUUID": { - "type": "string", - "description": "UUID of the dataset. This is unique identifier of a Flocker dataset" - } - } - }, - "v1.DownwardAPIVolumeSource": { - "id": "v1.DownwardAPIVolumeSource", - "description": "DownwardAPIVolumeSource represents a volume containing downward API info. Downward API volumes support ownership management and SELinux relabeling.", - "properties": { - "items": { - "type": "array", - "items": { - "$ref": "v1.DownwardAPIVolumeFile" - }, - "description": "Items is a list of downward API volume file" - }, - "defaultMode": { - "type": "integer", - "format": "int32", - "description": "Optional: mode bits to use on created files by default. Must be a value between 0 and 0777. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set." - } - } - }, - "v1.DownwardAPIVolumeFile": { - "id": "v1.DownwardAPIVolumeFile", - "description": "DownwardAPIVolumeFile represents information to create the file containing the pod field", - "required": [ - "path" - ], - "properties": { - "path": { - "type": "string", - "description": "Required: Path is the relative path name of the file to be created. Must not be absolute or contain the '..' path. Must be utf-8 encoded. The first item of the relative path must not start with '..'" - }, - "fieldRef": { - "$ref": "v1.ObjectFieldSelector", - "description": "Required: Selects a field of the pod: only annotations, labels, name and namespace are supported." - }, - "resourceFieldRef": { - "$ref": "v1.ResourceFieldSelector", - "description": "Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported." - }, - "mode": { - "type": "integer", - "format": "int32", - "description": "Optional: mode bits to use on this file, must be a value between 0 and 0777. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set." - } - } - }, - "v1.ObjectFieldSelector": { - "id": "v1.ObjectFieldSelector", - "description": "ObjectFieldSelector selects an APIVersioned field of an object.", - "required": [ - "fieldPath" - ], - "properties": { - "apiVersion": { - "type": "string", - "description": "Version of the schema the FieldPath is written in terms of, defaults to \"v1\"." - }, - "fieldPath": { - "type": "string", - "description": "Path of the field to select in the specified API version." - } - } - }, - "v1.ResourceFieldSelector": { - "id": "v1.ResourceFieldSelector", - "description": "ResourceFieldSelector represents container resources (cpu, memory) and their output format", - "required": [ - "resource" - ], - "properties": { - "containerName": { - "type": "string", - "description": "Container name: required for volumes, optional for env vars" - }, - "resource": { - "type": "string", - "description": "Required: resource to select" - }, - "divisor": { - "type": "string", - "description": "Specifies the output format of the exposed resources, defaults to \"1\"" - } - } - }, - "v1.FCVolumeSource": { - "id": "v1.FCVolumeSource", - "description": "Represents a Fibre Channel volume. Fibre Channel volumes can only be mounted as read/write once. Fibre Channel volumes support ownership management and SELinux relabeling.", - "properties": { - "targetWWNs": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Optional: FC target worldwide names (WWNs)" - }, - "lun": { - "type": "integer", - "format": "int32", - "description": "Optional: FC target lun number" - }, - "fsType": { - "type": "string", - "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified." - }, - "readOnly": { - "type": "boolean", - "description": "Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts." - }, - "wwids": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Optional: FC volume world wide identifiers (wwids) Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously." - } - } - }, - "v1.AzureFileVolumeSource": { - "id": "v1.AzureFileVolumeSource", - "description": "AzureFile represents an Azure File Service mount on the host and bind mount to the pod.", - "required": [ - "secretName", - "shareName" - ], - "properties": { - "secretName": { - "type": "string", - "description": "the name of secret that contains Azure Storage Account Name and Key" - }, - "shareName": { - "type": "string", - "description": "Share Name" - }, - "readOnly": { - "type": "boolean", - "description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts." - } - } - }, - "v1.ConfigMapVolumeSource": { - "id": "v1.ConfigMapVolumeSource", - "description": "Adapts a ConfigMap into a volume.\n\nThe contents of the target ConfigMap's Data field will be presented in a volume as files using the keys in the Data field as the file names, unless the items element is populated with specific mappings of keys to paths. ConfigMap volumes support ownership management and SELinux relabeling.", - "properties": { - "name": { - "type": "string", - "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names" - }, - "items": { - "type": "array", - "items": { - "$ref": "v1.KeyToPath" - }, - "description": "If unspecified, each key-value pair in the Data field of the referenced ConfigMap will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the ConfigMap, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'." - }, - "defaultMode": { - "type": "integer", - "format": "int32", - "description": "Optional: mode bits to use on created files by default. Must be a value between 0 and 0777. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set." - }, - "optional": { - "type": "boolean", - "description": "Specify whether the ConfigMap or it's keys must be defined" - } - } - }, - "v1.VsphereVirtualDiskVolumeSource": { - "id": "v1.VsphereVirtualDiskVolumeSource", - "description": "Represents a vSphere volume resource.", - "required": [ - "volumePath" - ], - "properties": { - "volumePath": { - "type": "string", - "description": "Path that identifies vSphere volume vmdk" - }, - "fsType": { - "type": "string", - "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified." - }, - "storagePolicyName": { - "type": "string", - "description": "Storage Policy Based Management (SPBM) profile name." - }, - "storagePolicyID": { - "type": "string", - "description": "Storage Policy Based Management (SPBM) profile ID associated with the StoragePolicyName." - } - } - }, - "v1.QuobyteVolumeSource": { - "id": "v1.QuobyteVolumeSource", - "description": "Represents a Quobyte mount that lasts the lifetime of a pod. Quobyte volumes do not support ownership management or SELinux relabeling.", - "required": [ - "registry", - "volume" - ], - "properties": { - "registry": { - "type": "string", - "description": "Registry represents a single or multiple Quobyte Registry services specified as a string as host:port pair (multiple entries are separated with commas) which acts as the central registry for volumes" - }, - "volume": { - "type": "string", - "description": "Volume is a string that references an already created Quobyte volume by name." - }, - "readOnly": { - "type": "boolean", - "description": "ReadOnly here will force the Quobyte volume to be mounted with read-only permissions. Defaults to false." - }, - "user": { - "type": "string", - "description": "User to map volume access to Defaults to serivceaccount user" - }, - "group": { - "type": "string", - "description": "Group to map volume access to Default is no group" - } - } - }, - "v1.AzureDiskVolumeSource": { - "id": "v1.AzureDiskVolumeSource", - "description": "AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod.", - "required": [ - "diskName", - "diskURI" - ], - "properties": { - "diskName": { - "type": "string", - "description": "The Name of the data disk in the blob storage" - }, - "diskURI": { - "type": "string", - "description": "The URI the data disk in the blob storage" - }, - "cachingMode": { - "$ref": "v1.AzureDataDiskCachingMode", - "description": "Host Caching mode: None, Read Only, Read Write." - }, - "fsType": { - "type": "string", - "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified." - }, - "readOnly": { - "type": "boolean", - "description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts." - }, - "kind": { - "$ref": "v1.AzureDataDiskKind", - "description": "Expected values Shared: multiple blob disks per storage account Dedicated: single blob disk per storage account Managed: azure managed data disk (only in managed availability set). defaults to shared" - } - } - }, - "v1.AzureDataDiskCachingMode": { - "id": "v1.AzureDataDiskCachingMode", - "properties": {} - }, - "v1.AzureDataDiskKind": { - "id": "v1.AzureDataDiskKind", - "properties": {} - }, - "v1.PhotonPersistentDiskVolumeSource": { - "id": "v1.PhotonPersistentDiskVolumeSource", - "description": "Represents a Photon Controller persistent disk resource.", - "required": [ - "pdID" - ], - "properties": { - "pdID": { - "type": "string", - "description": "ID that identifies Photon Controller persistent disk" - }, - "fsType": { - "type": "string", - "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified." - } - } - }, - "v1.ProjectedVolumeSource": { - "id": "v1.ProjectedVolumeSource", - "description": "Represents a projected volume source", - "required": [ - "sources" - ], - "properties": { - "sources": { - "type": "array", - "items": { - "$ref": "v1.VolumeProjection" - }, - "description": "list of volume projections" - }, - "defaultMode": { - "type": "integer", - "format": "int32", - "description": "Mode bits to use on created files by default. Must be a value between 0 and 0777. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set." - } - } - }, - "v1.VolumeProjection": { - "id": "v1.VolumeProjection", - "description": "Projection that may be projected along with other supported volume types", - "properties": { - "secret": { - "$ref": "v1.SecretProjection", - "description": "information about the secret data to project" - }, - "downwardAPI": { - "$ref": "v1.DownwardAPIProjection", - "description": "information about the downwardAPI data to project" - }, - "configMap": { - "$ref": "v1.ConfigMapProjection", - "description": "information about the configMap data to project" - } - } - }, - "v1.SecretProjection": { - "id": "v1.SecretProjection", - "description": "Adapts a secret into a projected volume.\n\nThe contents of the target Secret's Data field will be presented in a projected volume as files using the keys in the Data field as the file names. Note that this is identical to a secret volume source without the default mode.", - "properties": { - "name": { - "type": "string", - "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names" - }, - "items": { - "type": "array", - "items": { - "$ref": "v1.KeyToPath" - }, - "description": "If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'." - }, - "optional": { - "type": "boolean", - "description": "Specify whether the Secret or its key must be defined" - } - } - }, - "v1.DownwardAPIProjection": { - "id": "v1.DownwardAPIProjection", - "description": "Represents downward API info for projecting into a projected volume. Note that this is identical to a downwardAPI volume source without the default mode.", - "properties": { - "items": { - "type": "array", - "items": { - "$ref": "v1.DownwardAPIVolumeFile" - }, - "description": "Items is a list of DownwardAPIVolume file" - } - } - }, - "v1.ConfigMapProjection": { - "id": "v1.ConfigMapProjection", - "description": "Adapts a ConfigMap into a projected volume.\n\nThe contents of the target ConfigMap's Data field will be presented in a projected volume as files using the keys in the Data field as the file names, unless the items element is populated with specific mappings of keys to paths. Note that this is identical to a configmap volume source without the default mode.", - "properties": { - "name": { - "type": "string", - "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names" - }, - "items": { - "type": "array", - "items": { - "$ref": "v1.KeyToPath" - }, - "description": "If unspecified, each key-value pair in the Data field of the referenced ConfigMap will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the ConfigMap, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'." - }, - "optional": { - "type": "boolean", - "description": "Specify whether the ConfigMap or it's keys must be defined" - } - } - }, - "v1.PortworxVolumeSource": { - "id": "v1.PortworxVolumeSource", - "description": "PortworxVolumeSource represents a Portworx volume resource.", - "required": [ - "volumeID" - ], - "properties": { - "volumeID": { - "type": "string", - "description": "VolumeID uniquely identifies a Portworx volume" - }, - "fsType": { - "type": "string", - "description": "FSType represents the filesystem type to mount Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\". Implicitly inferred to be \"ext4\" if unspecified." - }, - "readOnly": { - "type": "boolean", - "description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts." - } - } - }, - "v1.ScaleIOVolumeSource": { - "id": "v1.ScaleIOVolumeSource", - "description": "ScaleIOVolumeSource represents a persistent ScaleIO volume", - "required": [ - "gateway", - "system", - "secretRef" - ], - "properties": { - "gateway": { - "type": "string", - "description": "The host address of the ScaleIO API Gateway." - }, - "system": { - "type": "string", - "description": "The name of the storage system as configured in ScaleIO." - }, - "secretRef": { - "$ref": "v1.LocalObjectReference", - "description": "SecretRef references to the secret for ScaleIO user and other sensitive information. If this is not provided, Login operation will fail." - }, - "sslEnabled": { - "type": "boolean", - "description": "Flag to enable/disable SSL communication with Gateway, default false" - }, - "protectionDomain": { - "type": "string", - "description": "The name of the Protection Domain for the configured storage (defaults to \"default\")." - }, - "storagePool": { - "type": "string", - "description": "The Storage Pool associated with the protection domain (defaults to \"default\")." - }, - "storageMode": { - "type": "string", - "description": "Indicates whether the storage for a volume should be thick or thin (defaults to \"thin\")." - }, - "volumeName": { - "type": "string", - "description": "The name of a volume already created in the ScaleIO system that is associated with this volume source." - }, - "fsType": { - "type": "string", - "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified." - }, - "readOnly": { - "type": "boolean", - "description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts." - } - } - }, - "v1.StorageOSVolumeSource": { - "id": "v1.StorageOSVolumeSource", - "description": "Represents a StorageOS persistent volume resource.", - "properties": { - "volumeName": { - "type": "string", - "description": "VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace." - }, - "volumeNamespace": { - "type": "string", - "description": "VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to \"default\" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created." - }, - "fsType": { - "type": "string", - "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified." - }, - "readOnly": { - "type": "boolean", - "description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts." - }, - "secretRef": { - "$ref": "v1.LocalObjectReference", - "description": "SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted." - } - } - }, - "v1.Container": { - "id": "v1.Container", - "description": "A single application container that you want to run within a pod.", - "required": [ - "name" - ], - "properties": { - "name": { - "type": "string", - "description": "Name of the container specified as a DNS_LABEL. Each container in a pod must have a unique name (DNS_LABEL). Cannot be updated." - }, - "image": { - "type": "string", - "description": "Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images This field is optional to allow higher level config management to default or override container images in workload controllers like Deployments and StatefulSets." - }, - "command": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Entrypoint array. Not executed within a shell. The docker image's ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell" - }, - "args": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Arguments to the entrypoint. The docker image's CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell" - }, - "workingDir": { - "type": "string", - "description": "Container's working directory. If not specified, the container runtime's default will be used, which might be configured in the container image. Cannot be updated." - }, - "ports": { - "type": "array", - "items": { - "$ref": "v1.ContainerPort" - }, - "description": "List of ports to expose from the container. Exposing a port here gives the system additional information about the network connections a container uses, but is primarily informational. Not specifying a port here DOES NOT prevent that port from being exposed. Any port which is listening on the default \"0.0.0.0\" address inside a container will be accessible from the network. Cannot be updated." - }, - "envFrom": { - "type": "array", - "items": { - "$ref": "v1.EnvFromSource" - }, - "description": "List of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. 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. Cannot be updated." - }, - "env": { - "type": "array", - "items": { - "$ref": "v1.EnvVar" - }, - "description": "List of environment variables to set in the container. Cannot be updated." - }, - "resources": { - "$ref": "v1.ResourceRequirements", - "description": "Compute Resources required by this container. Cannot be updated. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources" - }, - "volumeMounts": { - "type": "array", - "items": { - "$ref": "v1.VolumeMount" - }, - "description": "Pod volumes to mount into the container's filesystem. Cannot be updated." - }, - "livenessProbe": { - "$ref": "v1.Probe", - "description": "Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes" - }, - "readinessProbe": { - "$ref": "v1.Probe", - "description": "Periodic probe of container service readiness. Container will be removed from service endpoints if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes" - }, - "lifecycle": { - "$ref": "v1.Lifecycle", - "description": "Actions that the management system should take in response to container lifecycle events. Cannot be updated." - }, - "terminationMessagePath": { - "type": "string", - "description": "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated." - }, - "terminationMessagePolicy": { - "type": "string", - "description": "Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated." - }, - "imagePullPolicy": { - "type": "string", - "description": "Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images" - }, - "securityContext": { - "$ref": "v1.SecurityContext", - "description": "Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md" - }, - "stdin": { - "type": "boolean", - "description": "Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false." - }, - "stdinOnce": { - "type": "boolean", - "description": "Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false" - }, - "tty": { - "type": "boolean", - "description": "Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false." - } - } - }, - "v1.ContainerPort": { - "id": "v1.ContainerPort", - "description": "ContainerPort represents a network port in a single container.", - "required": [ - "containerPort" - ], - "properties": { - "name": { - "type": "string", - "description": "If specified, this must be an IANA_SVC_NAME and unique within the pod. Each named port in a pod must have a unique name. Name for the port that can be referred to by services." - }, - "hostPort": { - "type": "integer", - "format": "int32", - "description": "Number of port to expose on the host. If specified, this must be a valid port number, 0 \u003c x \u003c 65536. If HostNetwork is specified, this must match ContainerPort. Most containers do not need this." - }, - "containerPort": { - "type": "integer", - "format": "int32", - "description": "Number of port to expose on the pod's IP address. This must be a valid port number, 0 \u003c x \u003c 65536." - }, - "protocol": { - "type": "string", - "description": "Protocol for port. Must be UDP or TCP. Defaults to \"TCP\"." - }, - "hostIP": { - "type": "string", - "description": "What host IP to bind the external port to." - } - } - }, - "v1.EnvFromSource": { - "id": "v1.EnvFromSource", - "description": "EnvFromSource represents the source of a set of ConfigMaps", - "properties": { - "prefix": { - "type": "string", - "description": "An optional identifer to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER." - }, - "configMapRef": { - "$ref": "v1.ConfigMapEnvSource", - "description": "The ConfigMap to select from" - }, - "secretRef": { - "$ref": "v1.SecretEnvSource", - "description": "The Secret to select from" - } - } - }, - "v1.ConfigMapEnvSource": { - "id": "v1.ConfigMapEnvSource", - "description": "ConfigMapEnvSource selects a ConfigMap to populate the environment variables with.\n\nThe contents of the target ConfigMap's Data field will represent the key-value pairs as environment variables.", - "properties": { - "name": { - "type": "string", - "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names" - }, - "optional": { - "type": "boolean", - "description": "Specify whether the ConfigMap must be defined" - } - } - }, - "v1.SecretEnvSource": { - "id": "v1.SecretEnvSource", - "description": "SecretEnvSource selects a Secret to populate the environment variables with.\n\nThe contents of the target Secret's Data field will represent the key-value pairs as environment variables.", - "properties": { - "name": { - "type": "string", - "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names" - }, - "optional": { - "type": "boolean", - "description": "Specify whether the Secret must be defined" - } - } - }, - "v1.EnvVar": { - "id": "v1.EnvVar", - "description": "EnvVar represents an environment variable present in a Container.", - "required": [ - "name" - ], - "properties": { - "name": { - "type": "string", - "description": "Name of the environment variable. Must be a C_IDENTIFIER." - }, - "value": { - "type": "string", - "description": "Variable references $(VAR_NAME) are expanded using the previous defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to \"\"." - }, - "valueFrom": { - "$ref": "v1.EnvVarSource", - "description": "Source for the environment variable's value. Cannot be used if value is not empty." - } - } - }, - "v1.EnvVarSource": { - "id": "v1.EnvVarSource", - "description": "EnvVarSource represents a source for the value of an EnvVar.", - "properties": { - "fieldRef": { - "$ref": "v1.ObjectFieldSelector", - "description": "Selects a field of the pod: supports metadata.name, metadata.namespace, metadata.labels, metadata.annotations, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP." - }, - "resourceFieldRef": { - "$ref": "v1.ResourceFieldSelector", - "description": "Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported." - }, - "configMapKeyRef": { - "$ref": "v1.ConfigMapKeySelector", - "description": "Selects a key of a ConfigMap." - }, - "secretKeyRef": { - "$ref": "v1.SecretKeySelector", - "description": "Selects a key of a secret in the pod's namespace" - } - } - }, - "v1.ConfigMapKeySelector": { - "id": "v1.ConfigMapKeySelector", - "description": "Selects a key from a ConfigMap.", - "required": [ - "key" - ], - "properties": { - "name": { - "type": "string", - "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names" - }, - "key": { - "type": "string", - "description": "The key to select." - }, - "optional": { - "type": "boolean", - "description": "Specify whether the ConfigMap or it's key must be defined" - } - } - }, - "v1.SecretKeySelector": { - "id": "v1.SecretKeySelector", - "description": "SecretKeySelector selects a key of a Secret.", - "required": [ - "key" - ], - "properties": { - "name": { - "type": "string", - "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names" - }, - "key": { - "type": "string", - "description": "The key of the secret to select from. Must be a valid secret key." - }, - "optional": { - "type": "boolean", - "description": "Specify whether the Secret or it's key must be defined" - } - } - }, - "v1.ResourceRequirements": { - "id": "v1.ResourceRequirements", - "description": "ResourceRequirements describes the compute resource requirements.", - "properties": { - "limits": { - "type": "object", - "description": "Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/" - }, - "requests": { - "type": "object", - "description": "Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/" - } - } - }, - "v1.VolumeMount": { - "id": "v1.VolumeMount", - "description": "VolumeMount describes a mounting of a Volume within a container.", - "required": [ - "name", - "mountPath" - ], - "properties": { - "name": { - "type": "string", - "description": "This must match the Name of a Volume." - }, - "readOnly": { - "type": "boolean", - "description": "Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false." - }, - "mountPath": { - "type": "string", - "description": "Path within the container at which the volume should be mounted. Must not contain ':'." - }, - "subPath": { - "type": "string", - "description": "Path within the volume from which the container's volume should be mounted. Defaults to \"\" (volume's root)." - }, - "mountPropagation": { - "$ref": "v1.MountPropagationMode", - "description": "mountPropagation determines how mounts are propagated from the host to container and the other way around. When not set, MountPropagationHostToContainer is used. This field is alpha in 1.8 and can be reworked or removed in a future release." - } - } - }, - "v1.MountPropagationMode": { - "id": "v1.MountPropagationMode", - "properties": {} - }, - "v1.Probe": { - "id": "v1.Probe", - "description": "Probe describes a health check to be performed against a container to determine whether it is alive or ready to receive traffic.", - "properties": { - "exec": { - "$ref": "v1.ExecAction", - "description": "One and only one of the following should be specified. Exec specifies the action to take." - }, - "httpGet": { - "$ref": "v1.HTTPGetAction", - "description": "HTTPGet specifies the http request to perform." - }, - "tcpSocket": { - "$ref": "v1.TCPSocketAction", - "description": "TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported" - }, - "initialDelaySeconds": { - "type": "integer", - "format": "int32", - "description": "Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes" - }, - "timeoutSeconds": { - "type": "integer", - "format": "int32", - "description": "Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes" - }, - "periodSeconds": { - "type": "integer", - "format": "int32", - "description": "How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1." - }, - "successThreshold": { - "type": "integer", - "format": "int32", - "description": "Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness. Minimum value is 1." - }, - "failureThreshold": { - "type": "integer", - "format": "int32", - "description": "Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1." - } - } - }, - "v1.ExecAction": { - "id": "v1.ExecAction", - "description": "ExecAction describes a \"run in container\" action.", - "properties": { - "command": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy." - } - } - }, - "v1.HTTPGetAction": { - "id": "v1.HTTPGetAction", - "description": "HTTPGetAction describes an action based on HTTP Get requests.", - "required": [ - "port" - ], - "properties": { - "path": { - "type": "string", - "description": "Path to access on the HTTP server." - }, - "port": { - "type": "string", - "description": "Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME." - }, - "host": { - "type": "string", - "description": "Host name to connect to, defaults to the pod IP. You probably want to set \"Host\" in httpHeaders instead." - }, - "scheme": { - "type": "string", - "description": "Scheme to use for connecting to the host. Defaults to HTTP." - }, - "httpHeaders": { - "type": "array", - "items": { - "$ref": "v1.HTTPHeader" - }, - "description": "Custom headers to set in the request. HTTP allows repeated headers." - } - } - }, - "v1.HTTPHeader": { - "id": "v1.HTTPHeader", - "description": "HTTPHeader describes a custom header to be used in HTTP probes", - "required": [ - "name", - "value" - ], - "properties": { - "name": { - "type": "string", - "description": "The header field name" - }, - "value": { - "type": "string", - "description": "The header field value" - } - } - }, - "v1.TCPSocketAction": { - "id": "v1.TCPSocketAction", - "description": "TCPSocketAction describes an action based on opening a socket", - "required": [ - "port" - ], - "properties": { - "port": { - "type": "string", - "description": "Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME." - }, - "host": { - "type": "string", - "description": "Optional: Host name to connect to, defaults to the pod IP." - } - } - }, - "v1.Lifecycle": { - "id": "v1.Lifecycle", - "description": "Lifecycle describes actions that the management system should take in response to container lifecycle events. For the PostStart and PreStop lifecycle handlers, management of the container blocks until the action is complete, unless the container process fails, in which case the handler is aborted.", - "properties": { - "postStart": { - "$ref": "v1.Handler", - "description": "PostStart is called immediately after a container is created. If the handler fails, the container is terminated and restarted according to its restart policy. Other management of the container blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks" - }, - "preStop": { - "$ref": "v1.Handler", - "description": "PreStop is called immediately before a container is terminated. The container is terminated after the handler completes. The reason for termination is passed to the handler. Regardless of the outcome of the handler, the container is eventually terminated. Other management of the container blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks" - } - } - }, - "v1.Handler": { - "id": "v1.Handler", - "description": "Handler defines a specific action that should be taken", - "properties": { - "exec": { - "$ref": "v1.ExecAction", - "description": "One and only one of the following should be specified. Exec specifies the action to take." - }, - "httpGet": { - "$ref": "v1.HTTPGetAction", - "description": "HTTPGet specifies the http request to perform." - }, - "tcpSocket": { - "$ref": "v1.TCPSocketAction", - "description": "TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported" - } - } - }, - "v1.SecurityContext": { - "id": "v1.SecurityContext", - "description": "SecurityContext holds security configuration that will be applied to a container. Some fields are present in both SecurityContext and PodSecurityContext. When both are set, the values in SecurityContext take precedence.", - "properties": { - "capabilities": { - "$ref": "v1.Capabilities", - "description": "The capabilities to add/drop when running containers. Defaults to the default set of capabilities granted by the container runtime." - }, - "privileged": { - "type": "boolean", - "description": "Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false." - }, - "seLinuxOptions": { - "$ref": "v1.SELinuxOptions", - "description": "The SELinux context to be applied to the container. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence." - }, - "runAsUser": { - "type": "integer", - "format": "int64", - "description": "The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence." - }, - "runAsNonRoot": { - "type": "boolean", - "description": "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": { - "type": "boolean", - "description": "Whether this container has a read-only root filesystem. Default is false." - }, - "allowPrivilegeEscalation": { - "type": "boolean", - "description": "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" - } - } - }, - "v1.Capabilities": { - "id": "v1.Capabilities", - "description": "Adds and removes POSIX capabilities from running containers.", - "properties": { - "add": { - "type": "array", - "items": { - "$ref": "v1.Capability" - }, - "description": "Added capabilities" - }, - "drop": { - "type": "array", - "items": { - "$ref": "v1.Capability" - }, - "description": "Removed capabilities" - } - } - }, - "v1.Capability": { - "id": "v1.Capability", - "properties": {} - }, - "v1.SELinuxOptions": { - "id": "v1.SELinuxOptions", - "description": "SELinuxOptions are the labels to be applied to the container", - "properties": { - "user": { - "type": "string", - "description": "User is a SELinux user label that applies to the container." - }, - "role": { - "type": "string", - "description": "Role is a SELinux role label that applies to the container." - }, - "type": { - "type": "string", - "description": "Type is a SELinux type label that applies to the container." - }, - "level": { - "type": "string", - "description": "Level is SELinux level label that applies to the container." - } - } - }, - "v1.PodSecurityContext": { - "id": "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": { - "seLinuxOptions": { - "$ref": "v1.SELinuxOptions", - "description": "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." - }, - "runAsUser": { - "type": "integer", - "format": "int64", - "description": "The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container." - }, - "runAsNonRoot": { - "type": "boolean", - "description": "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 SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence." - }, - "supplementalGroups": { - "type": "array", - "items": { - "type": "integer" - }, - "description": "A list of groups applied to the first process run in each container, in addition to the container's primary GID. If unspecified, no groups will be added to any container." - }, - "fsGroup": { - "type": "integer", - "format": "int64", - "description": "A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod:\n\n1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw " - } - } - }, - "v1.Affinity": { - "id": "v1.Affinity", - "description": "Affinity is a group of affinity scheduling rules.", - "properties": { - "nodeAffinity": { - "$ref": "v1.NodeAffinity", - "description": "Describes node affinity scheduling rules for the pod." - }, - "podAffinity": { - "$ref": "v1.PodAffinity", - "description": "Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s))." - }, - "podAntiAffinity": { - "$ref": "v1.PodAntiAffinity", - "description": "Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s))." - } - } - }, - "v1.NodeAffinity": { - "id": "v1.NodeAffinity", - "description": "Node affinity is a group of node affinity scheduling rules.", - "properties": { - "requiredDuringSchedulingIgnoredDuringExecution": { - "$ref": "v1.NodeSelector", - "description": "If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node." - }, - "preferredDuringSchedulingIgnoredDuringExecution": { - "type": "array", - "items": { - "$ref": "v1.PreferredSchedulingTerm" - }, - "description": "The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding \"weight\" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred." - } - } - }, - "v1.NodeSelector": { - "id": "v1.NodeSelector", - "description": "A node selector represents the union of the results of one or more label queries over a set of nodes; that is, it represents the OR of the selectors represented by the node selector terms.", - "required": [ - "nodeSelectorTerms" - ], - "properties": { - "nodeSelectorTerms": { - "type": "array", - "items": { - "$ref": "v1.NodeSelectorTerm" - }, - "description": "Required. A list of node selector terms. The terms are ORed." - } - } - }, - "v1.NodeSelectorTerm": { - "id": "v1.NodeSelectorTerm", - "description": "A null or empty node selector term matches no objects.", - "required": [ - "matchExpressions" - ], - "properties": { - "matchExpressions": { - "type": "array", - "items": { - "$ref": "v1.NodeSelectorRequirement" - }, - "description": "Required. A list of node selector requirements. The requirements are ANDed." - } - } - }, - "v1.NodeSelectorRequirement": { - "id": "v1.NodeSelectorRequirement", - "description": "A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", - "required": [ - "key", - "operator" - ], - "properties": { - "key": { - "type": "string", - "description": "The label key that the selector applies to." - }, - "operator": { - "type": "string", - "description": "Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt." - }, - "values": { - "type": "array", - "items": { - "type": "string" - }, - "description": "An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch." - } - } - }, - "v1.PreferredSchedulingTerm": { - "id": "v1.PreferredSchedulingTerm", - "description": "An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op).", - "required": [ - "weight", - "preference" - ], - "properties": { - "weight": { - "type": "integer", - "format": "int32", - "description": "Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100." - }, - "preference": { - "$ref": "v1.NodeSelectorTerm", - "description": "A node selector term, associated with the corresponding weight." - } - } - }, - "v1.PodAffinity": { - "id": "v1.PodAffinity", - "description": "Pod affinity is a group of inter pod affinity scheduling rules.", - "properties": { - "requiredDuringSchedulingIgnoredDuringExecution": { - "type": "array", - "items": { - "$ref": "v1.PodAffinityTerm" - }, - "description": "If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied." - }, - "preferredDuringSchedulingIgnoredDuringExecution": { - "type": "array", - "items": { - "$ref": "v1.WeightedPodAffinityTerm" - }, - "description": "The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred." - } - } - }, - "v1.PodAffinityTerm": { - "id": "v1.PodAffinityTerm", - "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key \u003ctopologyKey\u003e tches that of any node on which a pod of the set of pods is running", - "properties": { - "labelSelector": { - "$ref": "v1.LabelSelector", - "description": "A label query over a set of resources, in this case pods." - }, - "namespaces": { - "type": "array", - "items": { - "type": "string" - }, - "description": "namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means \"this pod's namespace\"" - }, - "topologyKey": { - "type": "string", - "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. For PreferredDuringScheduling pod anti-affinity, empty topologyKey is interpreted as \"all topologies\" (\"all topologies\" here means all the topologyKeys indicated by scheduler command-line argument --failure-domains); for affinity and for RequiredDuringScheduling pod anti-affinity, empty topologyKey is not allowed." - } - } - }, - "v1.WeightedPodAffinityTerm": { - "id": "v1.WeightedPodAffinityTerm", - "description": "The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s)", - "required": [ - "weight", - "podAffinityTerm" - ], - "properties": { - "weight": { - "type": "integer", - "format": "int32", - "description": "weight associated with matching the corresponding podAffinityTerm, in the range 1-100." - }, - "podAffinityTerm": { - "$ref": "v1.PodAffinityTerm", - "description": "Required. A pod affinity term, associated with the corresponding weight." - } - } - }, - "v1.PodAntiAffinity": { - "id": "v1.PodAntiAffinity", - "description": "Pod anti affinity is a group of inter pod anti affinity scheduling rules.", - "properties": { - "requiredDuringSchedulingIgnoredDuringExecution": { - "type": "array", - "items": { - "$ref": "v1.PodAffinityTerm" - }, - "description": "If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied." - }, - "preferredDuringSchedulingIgnoredDuringExecution": { - "type": "array", - "items": { - "$ref": "v1.WeightedPodAffinityTerm" - }, - "description": "The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred." - } - } - }, - "v1.Toleration": { - "id": "v1.Toleration", - "description": "The pod this Toleration is attached to tolerates any taint that matches the triple \u003ckey,value,effect\u003e using the matching operator \u003coperator\u003e.", - "properties": { - "key": { - "type": "string", - "description": "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." - }, - "operator": { - "type": "string", - "description": "Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category." - }, - "value": { - "type": "string", - "description": "Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string." - }, - "effect": { - "type": "string", - "description": "Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute." - }, - "tolerationSeconds": { - "type": "integer", - "format": "int64", - "description": "TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system." - } - } - }, - "v1.HostAlias": { - "id": "v1.HostAlias", - "description": "HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the pod's hosts file.", - "properties": { - "ip": { - "type": "string", - "description": "IP address of the host file entry." - }, - "hostnames": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Hostnames for the above IP address." - } - } - }, - "v1beta1.DaemonSetUpdateStrategy": { - "id": "v1beta1.DaemonSetUpdateStrategy", - "properties": { - "type": { - "type": "string", - "description": "Type of daemon set update. Can be \"RollingUpdate\" or \"OnDelete\". Default is OnDelete." - }, - "rollingUpdate": { - "$ref": "v1beta1.RollingUpdateDaemonSet", - "description": "Rolling update config params. Present only if type = \"RollingUpdate\"." - } - } - }, - "v1beta1.RollingUpdateDaemonSet": { - "id": "v1beta1.RollingUpdateDaemonSet", - "description": "Spec to control the desired behavior of daemon set rolling update.", - "properties": { - "maxUnavailable": { - "type": "string", - "description": "The maximum number of DaemonSet pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of total number of DaemonSet pods at the start of the update (ex: 10%). Absolute number is calculated from percentage by rounding up. This cannot be 0. Default value is 1. Example: when this is set to 30%, at most 30% of the total number of nodes that should be running the daemon pod (i.e. status.desiredNumberScheduled) can have their pods stopped for an update at any given time. The update starts by stopping at most 30% of those DaemonSet pods and then brings up new DaemonSet pods in their place. Once the new pods are available, it then proceeds onto other DaemonSet pods, thus ensuring that at least 70% of original number of DaemonSet pods are available at all times during the update." - } - } - }, - "v1beta1.DaemonSetStatus": { - "id": "v1beta1.DaemonSetStatus", - "description": "DaemonSetStatus represents the current status of a daemon set.", - "required": [ - "currentNumberScheduled", - "numberMisscheduled", - "desiredNumberScheduled", - "numberReady" - ], - "properties": { - "currentNumberScheduled": { - "type": "integer", - "format": "int32", - "description": "The number of nodes that are running at least 1 daemon pod and are supposed to run the daemon pod. More info: https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/" - }, - "numberMisscheduled": { - "type": "integer", - "format": "int32", - "description": "The number of nodes that are running the daemon pod, but are not supposed to run the daemon pod. More info: https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/" - }, - "desiredNumberScheduled": { - "type": "integer", - "format": "int32", - "description": "The total number of nodes that should be running the daemon pod (including nodes correctly running the daemon pod). More info: https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/" - }, - "numberReady": { - "type": "integer", - "format": "int32", - "description": "The number of nodes that should be running the daemon pod and have one or more of the daemon pod running and ready." - }, - "observedGeneration": { - "type": "integer", - "format": "int64", - "description": "The most recent generation observed by the daemon set controller." - }, - "updatedNumberScheduled": { - "type": "integer", - "format": "int32", - "description": "The total number of nodes that are running updated daemon pod" - }, - "numberAvailable": { - "type": "integer", - "format": "int32", - "description": "The number of nodes that should be running the daemon pod and have one or more of the daemon pod running and available (ready for at least spec.minReadySeconds)" - }, - "numberUnavailable": { - "type": "integer", - "format": "int32", - "description": "The number of nodes that should be running the daemon pod and have none of the daemon pod running and available (ready for at least spec.minReadySeconds)" - }, - "collisionCount": { - "type": "integer", - "format": "int32", - "description": "Count of hash collisions for the DaemonSet. The DaemonSet controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ControllerRevision." - } - } - }, - "v1.WatchEvent": { - "id": "v1.WatchEvent", - "required": [ - "type", - "object" - ], - "properties": { - "type": { - "type": "string" - }, - "object": { - "type": "string" - } - } - }, - "v1.Patch": { - "id": "v1.Patch", - "description": "Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.", - "properties": {} - }, - "v1.DeleteOptions": { - "id": "v1.DeleteOptions", - "description": "DeleteOptions may be provided when deleting an API object.", - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "gracePeriodSeconds": { - "type": "integer", - "format": "int64", - "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." - }, - "preconditions": { - "$ref": "v1.Preconditions", - "description": "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned." - }, - "orphanDependents": { - "type": "boolean", - "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." - }, - "propagationPolicy": { - "$ref": "v1.DeletionPropagation", - "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." - } - } - }, - "v1.Preconditions": { - "id": "v1.Preconditions", - "description": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", - "properties": { - "uid": { - "$ref": "types.UID", - "description": "Specifies the target UID." - } - } - }, - "types.UID": { - "id": "types.UID", - "properties": {} - }, - "v1.DeletionPropagation": { - "id": "v1.DeletionPropagation", - "properties": {} - }, - "v1beta1.DeploymentList": { - "id": "v1beta1.DeploymentList", - "description": "DeploymentList is a list of Deployments.", - "required": [ - "items" - ], - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ListMeta", - "description": "Standard list metadata." - }, - "items": { - "type": "array", - "items": { - "$ref": "v1beta1.Deployment" - }, - "description": "Items is the list of Deployments." - } - } - }, - "v1beta1.Deployment": { - "id": "v1beta1.Deployment", - "description": "DEPRECATED - This group version of Deployment is deprecated by apps/v1beta2/Deployment. See the release notes for more information. Deployment enables declarative updates for Pods and ReplicaSets.", - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ObjectMeta", - "description": "Standard object metadata." - }, - "spec": { - "$ref": "v1beta1.DeploymentSpec", - "description": "Specification of the desired behavior of the Deployment." - }, - "status": { - "$ref": "v1beta1.DeploymentStatus", - "description": "Most recently observed status of the Deployment." - } - } - }, - "v1beta1.DeploymentSpec": { - "id": "v1beta1.DeploymentSpec", - "description": "DeploymentSpec is the specification of the desired behavior of the Deployment.", - "required": [ - "template" - ], - "properties": { - "replicas": { - "type": "integer", - "format": "int32", - "description": "Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1." - }, - "selector": { - "$ref": "v1.LabelSelector", - "description": "Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment." - }, - "template": { - "$ref": "v1.PodTemplateSpec", - "description": "Template describes the pods that will be created." - }, - "strategy": { - "$ref": "v1beta1.DeploymentStrategy", - "description": "The deployment strategy to use to replace existing pods with new ones." - }, - "minReadySeconds": { - "type": "integer", - "format": "int32", - "description": "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)" - }, - "revisionHistoryLimit": { - "type": "integer", - "format": "int32", - "description": "The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified." - }, - "paused": { - "type": "boolean", - "description": "Indicates that the deployment is paused and will not be processed by the deployment controller." - }, - "rollbackTo": { - "$ref": "v1beta1.RollbackConfig", - "description": "DEPRECATED. The config this deployment is rolling back to. Will be cleared after rollback is done." - }, - "progressDeadlineSeconds": { - "type": "integer", - "format": "int32", - "description": "The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that progress will not be estimated during the time a deployment is paused. This is not set by default." - } - } - }, - "v1beta1.DeploymentStrategy": { - "id": "v1beta1.DeploymentStrategy", - "description": "DeploymentStrategy describes how to replace existing pods with new ones.", - "properties": { - "type": { - "type": "string", - "description": "Type of deployment. Can be \"Recreate\" or \"RollingUpdate\". Default is RollingUpdate." - }, - "rollingUpdate": { - "$ref": "v1beta1.RollingUpdateDeployment", - "description": "Rolling update config params. Present only if DeploymentStrategyType = RollingUpdate." - } - } - }, - "v1beta1.RollingUpdateDeployment": { - "id": "v1beta1.RollingUpdateDeployment", - "description": "Spec to control the desired behavior of rolling update.", - "properties": { - "maxUnavailable": { - "type": "string", - "description": "The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding down. This can not be 0 if MaxSurge is 0. By default, a fixed value of 1 is used. Example: when this is set to 30%, the old RC can be scaled down to 70% of desired pods immediately when the rolling update starts. Once new pods are ready, old RC can be scaled down further, followed by scaling up the new RC, ensuring that the total number of pods available at all times during the update is at least 70% of desired pods." - }, - "maxSurge": { - "type": "string", - "description": "The maximum number of pods that can be scheduled above the desired number of pods. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up. By default, a value of 1 is used. Example: when this is set to 30%, the new RC can be scaled up immediately when the rolling update starts, such that the total number of old and new pods do not exceed 130% of desired pods. Once old pods have been killed, new RC can be scaled up further, ensuring that total number of pods running at any time during the update is atmost 130% of desired pods." - } - } - }, - "v1beta1.RollbackConfig": { - "id": "v1beta1.RollbackConfig", - "description": "DEPRECATED.", - "properties": { - "revision": { - "type": "integer", - "format": "int64", - "description": "The revision to rollback to. If set to 0, rollback to the last revision." - } - } - }, - "v1beta1.DeploymentStatus": { - "id": "v1beta1.DeploymentStatus", - "description": "DeploymentStatus is the most recently observed status of the Deployment.", - "properties": { - "observedGeneration": { - "type": "integer", - "format": "int64", - "description": "The generation observed by the deployment controller." - }, - "replicas": { - "type": "integer", - "format": "int32", - "description": "Total number of non-terminated pods targeted by this deployment (their labels match the selector)." - }, - "updatedReplicas": { - "type": "integer", - "format": "int32", - "description": "Total number of non-terminated pods targeted by this deployment that have the desired template spec." - }, - "readyReplicas": { - "type": "integer", - "format": "int32", - "description": "Total number of ready pods targeted by this deployment." - }, - "availableReplicas": { - "type": "integer", - "format": "int32", - "description": "Total number of available pods (ready for at least minReadySeconds) targeted by this deployment." - }, - "unavailableReplicas": { - "type": "integer", - "format": "int32", - "description": "Total number of unavailable pods targeted by this deployment. This is the total number of pods that are still required for the deployment to have 100% available capacity. They may either be pods that are running but not yet available or pods that still have not been created." - }, - "conditions": { - "type": "array", - "items": { - "$ref": "v1beta1.DeploymentCondition" - }, - "description": "Represents the latest available observations of a deployment's current state." - }, - "collisionCount": { - "type": "integer", - "format": "int32", - "description": "Count of hash collisions for the Deployment. The Deployment controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ReplicaSet." - } - } - }, - "v1beta1.DeploymentCondition": { - "id": "v1beta1.DeploymentCondition", - "description": "DeploymentCondition describes the state of a deployment at a certain point.", - "required": [ - "type", - "status" - ], - "properties": { - "type": { - "type": "string", - "description": "Type of deployment condition." - }, - "status": { - "type": "string", - "description": "Status of the condition, one of True, False, Unknown." - }, - "lastUpdateTime": { - "type": "string", - "description": "The last time this condition was updated." - }, - "lastTransitionTime": { - "type": "string", - "description": "Last time the condition transitioned from one status to another." - }, - "reason": { - "type": "string", - "description": "The reason for the condition's last transition." - }, - "message": { - "type": "string", - "description": "A human readable message indicating details about the transition." - } - } - }, - "v1beta1.DeploymentRollback": { - "id": "v1beta1.DeploymentRollback", - "description": "DEPRECATED. DeploymentRollback stores the information required to rollback a deployment.", - "required": [ - "name", - "rollbackTo" - ], - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "name": { - "type": "string", - "description": "Required: This must match the Name of a deployment." - }, - "updatedAnnotations": { - "type": "object", - "description": "The annotations to be updated to a deployment" - }, - "rollbackTo": { - "$ref": "v1beta1.RollbackConfig", - "description": "The config of this deployment rollback." - } - } - }, - "v1beta1.Scale": { - "id": "v1beta1.Scale", - "description": "represents a scaling request for a resource.", - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ObjectMeta", - "description": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata." - }, - "spec": { - "$ref": "v1beta1.ScaleSpec", - "description": "defines the behavior of the scale. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status." - }, - "status": { - "$ref": "v1beta1.ScaleStatus", - "description": "current status of the scale. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status. Read-only." - } - } - }, - "v1beta1.ScaleSpec": { - "id": "v1beta1.ScaleSpec", - "description": "describes the attributes of a scale subresource", - "properties": { - "replicas": { - "type": "integer", - "format": "int32", - "description": "desired number of instances for the scaled object." - } - } - }, - "v1beta1.ScaleStatus": { - "id": "v1beta1.ScaleStatus", - "description": "represents the current status of a scale subresource.", - "required": [ - "replicas" - ], - "properties": { - "replicas": { - "type": "integer", - "format": "int32", - "description": "actual number of observed instances of the scaled object." - }, - "selector": { - "type": "object", - "description": "label query over pods that should match the replicas count. More info: http://kubernetes.io/docs/user-guide/labels#label-selectors" - }, - "targetSelector": { - "type": "string", - "description": "label selector for pods that should match the replicas count. This is a serializated version of both map-based and more expressive set-based selectors. This is done to avoid introspection in the clients. The string will be in the same format as the query-param syntax. If the target type only supports map-based selectors, both this field and map-based selector field are populated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors" - } - } - }, - "v1beta1.IngressList": { - "id": "v1beta1.IngressList", - "description": "IngressList is a collection of Ingress.", - "required": [ - "items" - ], - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ListMeta", - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" - }, - "items": { - "type": "array", - "items": { - "$ref": "v1beta1.Ingress" - }, - "description": "Items is the list of Ingress." - } - } - }, - "v1beta1.Ingress": { - "id": "v1beta1.Ingress", - "description": "Ingress is a collection of rules that allow inbound connections to reach the endpoints defined by a backend. An Ingress can be configured to give services externally-reachable urls, load balance traffic, terminate SSL, offer name based virtual hosting etc.", - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ObjectMeta", - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" - }, - "spec": { - "$ref": "v1beta1.IngressSpec", - "description": "Spec is the desired state of the Ingress. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status" - }, - "status": { - "$ref": "v1beta1.IngressStatus", - "description": "Status is the current state of the Ingress. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status" - } - } - }, - "v1beta1.IngressSpec": { - "id": "v1beta1.IngressSpec", - "description": "IngressSpec describes the Ingress the user wishes to exist.", - "properties": { - "backend": { - "$ref": "v1beta1.IngressBackend", - "description": "A default backend capable of servicing requests that don't match any rule. At least one of 'backend' or 'rules' must be specified. This field is optional to allow the loadbalancer controller or defaulting logic to specify a global default." - }, - "tls": { - "type": "array", - "items": { - "$ref": "v1beta1.IngressTLS" - }, - "description": "TLS configuration. Currently the Ingress only supports a single TLS port, 443. If multiple members of this list specify different hosts, they will be multiplexed on the same port according to the hostname specified through the SNI TLS extension, if the ingress controller fulfilling the ingress supports SNI." - }, - "rules": { - "type": "array", - "items": { - "$ref": "v1beta1.IngressRule" - }, - "description": "A list of host rules used to configure the Ingress. If unspecified, or no rule matches, all traffic is sent to the default backend." - } - } - }, - "v1beta1.IngressBackend": { - "id": "v1beta1.IngressBackend", - "description": "IngressBackend describes all endpoints for a given service and port.", - "required": [ - "serviceName", - "servicePort" - ], - "properties": { - "serviceName": { - "type": "string", - "description": "Specifies the name of the referenced service." - }, - "servicePort": { - "type": "string", - "description": "Specifies the port of the referenced service." - } - } - }, - "v1beta1.IngressTLS": { - "id": "v1beta1.IngressTLS", - "description": "IngressTLS describes the transport layer security associated with an Ingress.", - "properties": { - "hosts": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Hosts are a list of hosts included in the TLS certificate. The values in this list must match the name/s used in the tlsSecret. Defaults to the wildcard host setting for the loadbalancer controller fulfilling this Ingress, if left unspecified." - }, - "secretName": { - "type": "string", - "description": "SecretName is the name of the secret used to terminate SSL traffic on 443. Field is left optional to allow SSL routing based on SNI hostname alone. If the SNI host in a listener conflicts with the \"Host\" header field used by an IngressRule, the SNI host is used for termination and value of the Host header is used for routing." - } - } - }, - "v1beta1.IngressRule": { - "id": "v1beta1.IngressRule", - "description": "IngressRule represents the rules mapping the paths under a specified host to the related backend services. Incoming requests are first evaluated for a host match, then routed to the backend associated with the matching IngressRuleValue.", - "properties": { - "host": { - "type": "string", - "description": "Host is the fully qualified domain name of a network host, as defined by RFC 3986. Note the following deviations from the \"host\" part of the URI as defined in the RFC: 1. IPs are not allowed. Currently an IngressRuleValue can only apply to the\n\t IP in the Spec of the parent Ingress.\n2. The `:` delimiter is not respected because ports are not allowed.\n\t Currently the port of an Ingress is implicitly :80 for http and\n\t :443 for https.\nBoth these may change in the future. Incoming requests are matched against the host before the IngressRuleValue. If the host is unspecified, the Ingress routes all traffic based on the specified IngressRuleValue." - }, - "http": { - "$ref": "v1beta1.HTTPIngressRuleValue" - } - } - }, - "v1beta1.HTTPIngressRuleValue": { - "id": "v1beta1.HTTPIngressRuleValue", - "description": "HTTPIngressRuleValue is a list of http selectors pointing to backends. In the example: http://\u003chost\u003e/\u003cpath\u003e?\u003csearchpart\u003e -\u003e backend where where parts of the url correspond to RFC 3986, this resource will be used to match against everything after the last '/' and before the first '?' or '#'.", - "required": [ - "paths" - ], - "properties": { - "paths": { - "type": "array", - "items": { - "$ref": "v1beta1.HTTPIngressPath" - }, - "description": "A collection of paths that map requests to backends." - } - } - }, - "v1beta1.HTTPIngressPath": { - "id": "v1beta1.HTTPIngressPath", - "description": "HTTPIngressPath associates a path regex with a backend. Incoming urls matching the path are forwarded to the backend.", - "required": [ - "backend" - ], - "properties": { - "path": { - "type": "string", - "description": "Path is an extended POSIX regex as defined by IEEE Std 1003.1, (i.e this follows the egrep/unix syntax, not the perl syntax) matched against the path of an incoming request. Currently it can contain characters disallowed from the conventional \"path\" part of a URL as defined by RFC 3986. Paths must begin with a '/'. If unspecified, the path defaults to a catch all sending traffic to the backend." - }, - "backend": { - "$ref": "v1beta1.IngressBackend", - "description": "Backend defines the referenced service endpoint to which the traffic will be forwarded to." - } - } - }, - "v1beta1.IngressStatus": { - "id": "v1beta1.IngressStatus", - "description": "IngressStatus describe the current state of the Ingress.", - "properties": { - "loadBalancer": { - "$ref": "v1.LoadBalancerStatus", - "description": "LoadBalancer contains the current status of the load-balancer." - } - } - }, - "v1.LoadBalancerStatus": { - "id": "v1.LoadBalancerStatus", - "description": "LoadBalancerStatus represents the status of a load-balancer.", - "properties": { - "ingress": { - "type": "array", - "items": { - "$ref": "v1.LoadBalancerIngress" - }, - "description": "Ingress is a list containing ingress points for the load-balancer. Traffic intended for the service should be sent to these ingress points." - } - } - }, - "v1.LoadBalancerIngress": { - "id": "v1.LoadBalancerIngress", - "description": "LoadBalancerIngress represents the status of a load-balancer ingress point: traffic intended for the service should be sent to an ingress point.", - "properties": { - "ip": { - "type": "string", - "description": "IP is set for load-balancer ingress points that are IP based (typically GCE or OpenStack load-balancers)" - }, - "hostname": { - "type": "string", - "description": "Hostname is set for load-balancer ingress points that are DNS based (typically AWS load-balancers)" - } - } - }, - "v1beta1.ReplicaSetList": { - "id": "v1beta1.ReplicaSetList", - "description": "ReplicaSetList is a collection of ReplicaSets.", - "required": [ - "items" - ], - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ListMeta", - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds" - }, - "items": { - "type": "array", - "items": { - "$ref": "v1beta1.ReplicaSet" - }, - "description": "List of ReplicaSets. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller" - } - } - }, - "v1beta1.ReplicaSet": { - "id": "v1beta1.ReplicaSet", - "description": "DEPRECATED - This group version of ReplicaSet is deprecated by apps/v1beta2/ReplicaSet. See the release notes for more information. ReplicaSet ensures that a specified number of pod replicas are running at any given time.", - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ObjectMeta", - "description": "If the Labels of a ReplicaSet are empty, they are defaulted to be the same as the Pod(s) that the ReplicaSet manages. Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" - }, - "spec": { - "$ref": "v1beta1.ReplicaSetSpec", - "description": "Spec defines the specification of the desired behavior of the ReplicaSet. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status" - }, - "status": { - "$ref": "v1beta1.ReplicaSetStatus", - "description": "Status is the most recently observed status of the ReplicaSet. This data may be out of date by some window of time. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status" - } - } - }, - "v1beta1.ReplicaSetSpec": { - "id": "v1beta1.ReplicaSetSpec", - "description": "ReplicaSetSpec is the specification of a ReplicaSet.", - "properties": { - "replicas": { - "type": "integer", - "format": "int32", - "description": "Replicas is the number of desired replicas. This is a pointer to distinguish between explicit zero and unspecified. Defaults to 1. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller" - }, - "minReadySeconds": { - "type": "integer", - "format": "int32", - "description": "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)" - }, - "selector": { - "$ref": "v1.LabelSelector", - "description": "Selector is a label query over pods that should match the replica count. If the selector is empty, it is defaulted to the labels present on the pod template. Label keys and values that must match in order to be controlled by this replica set. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors" - }, - "template": { - "$ref": "v1.PodTemplateSpec", - "description": "Template is the object that describes the pod that will be created if insufficient replicas are detected. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template" - } - } - }, - "v1beta1.ReplicaSetStatus": { - "id": "v1beta1.ReplicaSetStatus", - "description": "ReplicaSetStatus represents the current status of a ReplicaSet.", - "required": [ - "replicas" - ], - "properties": { - "replicas": { - "type": "integer", - "format": "int32", - "description": "Replicas is the most recently oberved number of replicas. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller" - }, - "fullyLabeledReplicas": { - "type": "integer", - "format": "int32", - "description": "The number of pods that have labels matching the labels of the pod template of the replicaset." - }, - "readyReplicas": { - "type": "integer", - "format": "int32", - "description": "The number of ready replicas for this replica set." - }, - "availableReplicas": { - "type": "integer", - "format": "int32", - "description": "The number of available replicas (ready for at least minReadySeconds) for this replica set." - }, - "observedGeneration": { - "type": "integer", - "format": "int64", - "description": "ObservedGeneration reflects the generation of the most recently observed ReplicaSet." - }, - "conditions": { - "type": "array", - "items": { - "$ref": "v1beta1.ReplicaSetCondition" - }, - "description": "Represents the latest available observations of a replica set's current state." - } - } - }, - "v1beta1.ReplicaSetCondition": { - "id": "v1beta1.ReplicaSetCondition", - "description": "ReplicaSetCondition describes the state of a replica set at a certain point.", - "required": [ - "type", - "status" - ], - "properties": { - "type": { - "type": "string", - "description": "Type of replica set condition." - }, - "status": { - "type": "string", - "description": "Status of the condition, one of True, False, Unknown." - }, - "lastTransitionTime": { - "type": "string", - "description": "The last time the condition transitioned from one status to another." - }, - "reason": { - "type": "string", - "description": "The reason for the condition's last transition." - }, - "message": { - "type": "string", - "description": "A human readable message indicating details about the transition." - } - } - }, - "v1.APIResourceList": { - "id": "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.", - "required": [ - "groupVersion", - "resources" - ], - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "groupVersion": { - "type": "string", - "description": "groupVersion is the group and version this APIResourceList is for." - }, - "resources": { - "type": "array", - "items": { - "$ref": "v1.APIResource" - }, - "description": "resources contains the name of the resources and if they are namespaced." - } - } - }, - "v1.APIResource": { - "id": "v1.APIResource", - "description": "APIResource specifies the name of a resource and whether it is namespaced.", - "required": [ - "name", - "singularName", - "namespaced", - "kind", - "verbs" - ], - "properties": { - "name": { - "type": "string", - "description": "name is the plural name of the resource." - }, - "singularName": { - "type": "string", - "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." - }, - "namespaced": { - "type": "boolean", - "description": "namespaced indicates if a resource is namespaced or not." - }, - "group": { - "type": "string", - "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\"." - }, - "version": { - "type": "string", - "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)\"." - }, - "kind": { - "type": "string", - "description": "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')" - }, - "verbs": { - "type": "array", - "items": { - "type": "string" - }, - "description": "verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy)" - }, - "shortNames": { - "type": "array", - "items": { - "type": "string" - }, - "description": "shortNames is a list of suggested short names of the resource." - }, - "categories": { - "type": "array", - "items": { - "type": "string" - }, - "description": "categories is a list of the grouped resources this resource belongs to (e.g. 'all')" - } - } - } - } - } diff --git a/federation/apis/swagger-spec/federation.json b/federation/apis/swagger-spec/federation.json deleted file mode 100644 index cb6deba1ccb..00000000000 --- a/federation/apis/swagger-spec/federation.json +++ /dev/null @@ -1,114 +0,0 @@ -{ - "swaggerVersion": "1.2", - "apiVersion": "", - "basePath": "https://10.10.10.10:6443", - "resourcePath": "/apis/federation", - "info": { - "title": "", - "description": "" - }, - "apis": [ - { - "path": "/apis/federation", - "description": "get information of a group", - "operations": [ - { - "type": "v1.APIGroup", - "method": "GET", - "summary": "get information of a group", - "nickname": "getAPIGroup", - "parameters": [], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ] - } - ] - } - ], - "models": { - "v1.APIGroup": { - "id": "v1.APIGroup", - "description": "APIGroup contains the name, the supported versions, and the preferred version of a group.", - "required": [ - "name", - "versions", - "serverAddressByClientCIDRs" - ], - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "name": { - "type": "string", - "description": "name is the name of the group." - }, - "versions": { - "type": "array", - "items": { - "$ref": "v1.GroupVersionForDiscovery" - }, - "description": "versions are the versions supported in this group." - }, - "preferredVersion": { - "$ref": "v1.GroupVersionForDiscovery", - "description": "preferredVersion is the version preferred by the API server, which probably is the storage version." - }, - "serverAddressByClientCIDRs": { - "type": "array", - "items": { - "$ref": "v1.ServerAddressByClientCIDR" - }, - "description": "a map of client CIDR to server address that is serving this group. This is to help clients reach servers in the most network-efficient way possible. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR. The server returns only those CIDRs that it thinks that the client can match. For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP." - } - } - }, - "v1.GroupVersionForDiscovery": { - "id": "v1.GroupVersionForDiscovery", - "description": "GroupVersion contains the \"group/version\" and \"version\" string of a version. It is made a struct to keep extensibility.", - "required": [ - "groupVersion", - "version" - ], - "properties": { - "groupVersion": { - "type": "string", - "description": "groupVersion specifies the API group and version in the form \"group/version\"" - }, - "version": { - "type": "string", - "description": "version specifies the version in the form of \"version\". This is to save the clients the trouble of splitting the GroupVersion." - } - } - }, - "v1.ServerAddressByClientCIDR": { - "id": "v1.ServerAddressByClientCIDR", - "description": "ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match.", - "required": [ - "clientCIDR", - "serverAddress" - ], - "properties": { - "clientCIDR": { - "type": "string", - "description": "The CIDR with which clients can match their IP to figure out the server address that they should use." - }, - "serverAddress": { - "type": "string", - "description": "Address of this server, suitable for a client that matches the above CIDR. This can be a hostname, hostname:port, IP or IP:port." - } - } - } - } - } diff --git a/federation/apis/swagger-spec/federation_v1beta1.json b/federation/apis/swagger-spec/federation_v1beta1.json deleted file mode 100644 index 925cfba20fc..00000000000 --- a/federation/apis/swagger-spec/federation_v1beta1.json +++ /dev/null @@ -1,1361 +0,0 @@ -{ - "swaggerVersion": "1.2", - "apiVersion": "federation/v1beta1", - "basePath": "https://10.10.10.10:6443", - "resourcePath": "/apis/federation/v1beta1", - "info": { - "title": "", - "description": "" - }, - "apis": [ - { - "path": "/apis/federation/v1beta1/clusters", - "description": "API at /apis/federation/v1beta1", - "operations": [ - { - "type": "v1beta1.ClusterList", - "method": "GET", - "summary": "list or watch objects of kind Cluster", - "nickname": "listCluster", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.ClusterList" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.Cluster", - "method": "POST", - "summary": "create a Cluster", - "nickname": "createCluster", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1beta1.Cluster", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.Cluster" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1beta1.Cluster" - }, - { - "code": 202, - "message": "Accepted", - "responseModel": "v1beta1.Cluster" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Status", - "method": "DELETE", - "summary": "delete collection of Cluster", - "nickname": "deletecollectionCluster", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Status" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/federation/v1beta1/watch/clusters", - "description": "API at /apis/federation/v1beta1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch individual changes to a list of Cluster", - "nickname": "watchClusterList", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/federation/v1beta1/clusters/{name}", - "description": "API at /apis/federation/v1beta1", - "operations": [ - { - "type": "v1beta1.Cluster", - "method": "GET", - "summary": "read the specified Cluster", - "nickname": "readCluster", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "export", - "description": "Should this value be exported. Export strips fields that a user can not specify.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "exact", - "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Cluster", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.Cluster" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.Cluster", - "method": "PUT", - "summary": "replace the specified Cluster", - "nickname": "replaceCluster", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1beta1.Cluster", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Cluster", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.Cluster" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1beta1.Cluster" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1beta1.Cluster", - "method": "PATCH", - "summary": "partially update the specified Cluster", - "nickname": "patchCluster", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Patch", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Cluster", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.Cluster" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ] - }, - { - "type": "v1.Status", - "method": "DELETE", - "summary": "delete a Cluster", - "nickname": "deleteCluster", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.DeleteOptions", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Cluster", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Status" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/federation/v1beta1/watch/clusters/{name}", - "description": "API at /apis/federation/v1beta1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch changes to an object of kind Cluster", - "nickname": "watchCluster", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Cluster", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/federation/v1beta1/clusters/{name}/status", - "description": "API at /apis/federation/v1beta1", - "operations": [ - { - "type": "v1beta1.Cluster", - "method": "PUT", - "summary": "replace status of the specified Cluster", - "nickname": "replaceClusterStatus", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1beta1.Cluster", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Cluster", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1beta1.Cluster" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1beta1.Cluster" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/apis/federation/v1beta1", - "description": "API at /apis/federation/v1beta1", - "operations": [ - { - "type": "v1.APIResourceList", - "method": "GET", - "summary": "get available resources", - "nickname": "getAPIResources", - "parameters": [], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ] - } - ] - } - ], - "models": { - "v1beta1.ClusterList": { - "id": "v1beta1.ClusterList", - "description": "A list of all the kubernetes clusters registered to the federation", - "required": [ - "items" - ], - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ListMeta", - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds" - }, - "items": { - "type": "array", - "items": { - "$ref": "v1beta1.Cluster" - }, - "description": "List of Cluster objects." - } - } - }, - "v1.ListMeta": { - "id": "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": { - "selfLink": { - "type": "string", - "description": "selfLink is a URL representing this object. Populated by the system. Read-only." - }, - "resourceVersion": { - "type": "string", - "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/api-conventions.md#concurrency-control-and-consistency" - }, - "continue": { - "type": "string", - "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 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." - } - } - }, - "v1beta1.Cluster": { - "id": "v1beta1.Cluster", - "description": "Information about a registered cluster in a federated kubernetes setup. Clusters are not namespaced and have unique names in the federation.", - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ObjectMeta", - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" - }, - "spec": { - "$ref": "v1beta1.ClusterSpec", - "description": "Spec defines the behavior of the Cluster." - }, - "status": { - "$ref": "v1beta1.ClusterStatus", - "description": "Status describes the current status of a Cluster" - } - } - }, - "v1.ObjectMeta": { - "id": "v1.ObjectMeta", - "description": "ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.", - "properties": { - "name": { - "type": "string", - "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: http://kubernetes.io/docs/user-guide/identifiers#names" - }, - "generateName": { - "type": "string", - "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 NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header).\n\nApplied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency" - }, - "namespace": { - "type": "string", - "description": "Namespace defines the space within 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: http://kubernetes.io/docs/user-guide/namespaces" - }, - "selfLink": { - "type": "string", - "description": "SelfLink is a URL representing this object. Populated by the system. Read-only." - }, - "uid": { - "type": "string", - "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: http://kubernetes.io/docs/user-guide/identifiers#uids" - }, - "resourceVersion": { - "type": "string", - "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/api-conventions.md#concurrency-control-and-consistency" - }, - "generation": { - "type": "integer", - "format": "int64", - "description": "A sequence number representing a specific generation of the desired state. Populated by the system. Read-only." - }, - "creationTimestamp": { - "type": "string", - "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/api-conventions.md#metadata" - }, - "deletionTimestamp": { - "type": "string", - "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 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/api-conventions.md#metadata" - }, - "deletionGracePeriodSeconds": { - "type": "integer", - "format": "int64", - "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." - }, - "labels": { - "type": "object", - "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: http://kubernetes.io/docs/user-guide/labels" - }, - "annotations": { - "type": "object", - "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: http://kubernetes.io/docs/user-guide/annotations" - }, - "ownerReferences": { - "type": "array", - "items": { - "$ref": "v1.OwnerReference" - }, - "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." - }, - "initializers": { - "$ref": "v1.Initializers", - "description": "An initializer is a controller which enforces some system invariant at object creation time. This field is a list of initializers that have not yet acted on this object. If nil or empty, this object has been completely initialized. Otherwise, the object is considered uninitialized and is hidden (in list/watch and get calls) from clients that haven't explicitly asked to observe uninitialized objects.\n\nWhen an object is created, the system will populate this list with the current set of initializers. Only privileged users may set or modify this list. Once it is empty, it may not be modified further by any user." - }, - "finalizers": { - "type": "array", - "items": { - "type": "string" - }, - "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." - }, - "clusterName": { - "type": "string", - "description": "The name of the cluster which the object belongs to. This is used to distinguish resources with same name and namespace in different clusters. This field is not set anywhere right now and apiserver is going to ignore it if set in create or update request." - } - } - }, - "v1.OwnerReference": { - "id": "v1.OwnerReference", - "description": "OwnerReference contains enough information to let you identify an owning object. Currently, an owning object must be in the same namespace, so there is no namespace field.", - "required": [ - "apiVersion", - "kind", - "name", - "uid" - ], - "properties": { - "apiVersion": { - "type": "string", - "description": "API version of the referent." - }, - "kind": { - "type": "string", - "description": "Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds" - }, - "name": { - "type": "string", - "description": "Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names" - }, - "uid": { - "type": "string", - "description": "UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids" - }, - "controller": { - "type": "boolean", - "description": "If true, this reference points to the managing controller." - }, - "blockOwnerDeletion": { - "type": "boolean", - "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. Defaults to false. To set this field, a user needs \"delete\" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned." - } - } - }, - "v1.Initializers": { - "id": "v1.Initializers", - "description": "Initializers tracks the progress of initialization.", - "required": [ - "pending" - ], - "properties": { - "pending": { - "type": "array", - "items": { - "$ref": "v1.Initializer" - }, - "description": "Pending is a list of initializers that must execute in order before this object is visible. When the last pending initializer is removed, and no failing result is set, the initializers struct will be set to nil and the object is considered as initialized and visible to all clients." - }, - "result": { - "$ref": "v1.Status", - "description": "If result is set with the Failure field, the object will be persisted to storage and then deleted, ensuring that other clients can observe the deletion." - } - } - }, - "v1.Initializer": { - "id": "v1.Initializer", - "description": "Initializer is information about an initializer that has not yet completed.", - "required": [ - "name" - ], - "properties": { - "name": { - "type": "string", - "description": "name of the process that is responsible for initializing this object." - } - } - }, - "v1.Status": { - "id": "v1.Status", - "description": "Status is a return value for calls that don't return other objects.", - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ListMeta", - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds" - }, - "status": { - "type": "string", - "description": "Status of the operation. One of: \"Success\" or \"Failure\". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status" - }, - "message": { - "type": "string", - "description": "A human-readable description of the status of this operation." - }, - "reason": { - "type": "string", - "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." - }, - "details": { - "$ref": "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." - }, - "code": { - "type": "integer", - "format": "int32", - "description": "Suggested HTTP return code for this status, 0 if not set." - } - } - }, - "v1.StatusDetails": { - "id": "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": { - "name": { - "type": "string", - "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described)." - }, - "group": { - "type": "string", - "description": "The group attribute of the resource associated with the status StatusReason." - }, - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "uid": { - "type": "string", - "description": "UID of the resource. (when there is a single resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids" - }, - "causes": { - "type": "array", - "items": { - "$ref": "v1.StatusCause" - }, - "description": "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes." - }, - "retryAfterSeconds": { - "type": "integer", - "format": "int32", - "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." - } - } - }, - "v1.StatusCause": { - "id": "v1.StatusCause", - "description": "StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.", - "properties": { - "reason": { - "type": "string", - "description": "A machine-readable description of the cause of the error. If this value is empty there is no information available." - }, - "message": { - "type": "string", - "description": "A human-readable description of the cause of the error. This field may be presented as-is to a reader." - }, - "field": { - "type": "string", - "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\"" - } - } - }, - "v1beta1.ClusterSpec": { - "id": "v1beta1.ClusterSpec", - "description": "ClusterSpec describes the attributes of a kubernetes cluster.", - "required": [ - "serverAddressByClientCIDRs" - ], - "properties": { - "serverAddressByClientCIDRs": { - "type": "array", - "items": { - "$ref": "v1beta1.ServerAddressByClientCIDR" - }, - "description": "A map of client CIDR to server address. This is to help clients reach servers in the most network-efficient way possible. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR." - }, - "secretRef": { - "$ref": "v1.LocalObjectReference", - "description": "Name of the secret containing kubeconfig to access this cluster. The secret is read from the kubernetes cluster that is hosting federation control plane. Admin needs to ensure that the required secret exists. Secret should be in the same namespace where federation control plane is hosted and it should have kubeconfig in its data with key \"kubeconfig\". This will later be changed to a reference to secret in federation control plane when the federation control plane supports secrets. This can be left empty if the cluster allows insecure access." - } - } - }, - "v1beta1.ServerAddressByClientCIDR": { - "id": "v1beta1.ServerAddressByClientCIDR", - "description": "ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match.", - "required": [ - "clientCIDR", - "serverAddress" - ], - "properties": { - "clientCIDR": { - "type": "string", - "description": "The CIDR with which clients can match their IP to figure out the server address that they should use." - }, - "serverAddress": { - "type": "string", - "description": "Address of this server, suitable for a client that matches the above CIDR. This can be a hostname, hostname:port, IP or IP:port." - } - } - }, - "v1.LocalObjectReference": { - "id": "v1.LocalObjectReference", - "description": "LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace.", - "properties": { - "name": { - "type": "string", - "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names" - } - } - }, - "v1beta1.ClusterStatus": { - "id": "v1beta1.ClusterStatus", - "description": "ClusterStatus is information about the current status of a cluster updated by cluster controller periodically.", - "properties": { - "conditions": { - "type": "array", - "items": { - "$ref": "v1beta1.ClusterCondition" - }, - "description": "Conditions is an array of current cluster conditions." - }, - "zones": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Zones is the list of availability zones in which the nodes of the cluster exist, e.g. 'us-east1-a'. These will always be in the same region." - }, - "region": { - "type": "string", - "description": "Region is the name of the region in which all of the nodes in the cluster exist. e.g. 'us-east1'." - } - } - }, - "v1beta1.ClusterCondition": { - "id": "v1beta1.ClusterCondition", - "description": "ClusterCondition describes current state of a cluster.", - "required": [ - "type", - "status" - ], - "properties": { - "type": { - "type": "string", - "description": "Type of cluster condition, Complete or Failed." - }, - "status": { - "type": "string", - "description": "Status of the condition, one of True, False, Unknown." - }, - "lastProbeTime": { - "type": "string", - "description": "Last time the condition was checked." - }, - "lastTransitionTime": { - "type": "string", - "description": "Last time the condition transit from one status to another." - }, - "reason": { - "type": "string", - "description": "(brief) reason for the condition's last transition." - }, - "message": { - "type": "string", - "description": "Human readable message indicating details about last transition." - } - } - }, - "v1.WatchEvent": { - "id": "v1.WatchEvent", - "required": [ - "type", - "object" - ], - "properties": { - "type": { - "type": "string" - }, - "object": { - "type": "string" - } - } - }, - "v1.Patch": { - "id": "v1.Patch", - "description": "Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.", - "properties": {} - }, - "v1.DeleteOptions": { - "id": "v1.DeleteOptions", - "description": "DeleteOptions may be provided when deleting an API object.", - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "gracePeriodSeconds": { - "type": "integer", - "format": "int64", - "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." - }, - "preconditions": { - "$ref": "v1.Preconditions", - "description": "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned." - }, - "orphanDependents": { - "type": "boolean", - "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." - }, - "propagationPolicy": { - "$ref": "v1.DeletionPropagation", - "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." - } - } - }, - "v1.Preconditions": { - "id": "v1.Preconditions", - "description": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", - "properties": { - "uid": { - "$ref": "types.UID", - "description": "Specifies the target UID." - } - } - }, - "types.UID": { - "id": "types.UID", - "properties": {} - }, - "v1.DeletionPropagation": { - "id": "v1.DeletionPropagation", - "properties": {} - }, - "v1.APIResourceList": { - "id": "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.", - "required": [ - "groupVersion", - "resources" - ], - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "groupVersion": { - "type": "string", - "description": "groupVersion is the group and version this APIResourceList is for." - }, - "resources": { - "type": "array", - "items": { - "$ref": "v1.APIResource" - }, - "description": "resources contains the name of the resources and if they are namespaced." - } - } - }, - "v1.APIResource": { - "id": "v1.APIResource", - "description": "APIResource specifies the name of a resource and whether it is namespaced.", - "required": [ - "name", - "singularName", - "namespaced", - "kind", - "verbs" - ], - "properties": { - "name": { - "type": "string", - "description": "name is the plural name of the resource." - }, - "singularName": { - "type": "string", - "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." - }, - "namespaced": { - "type": "boolean", - "description": "namespaced indicates if a resource is namespaced or not." - }, - "group": { - "type": "string", - "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\"." - }, - "version": { - "type": "string", - "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)\"." - }, - "kind": { - "type": "string", - "description": "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')" - }, - "verbs": { - "type": "array", - "items": { - "type": "string" - }, - "description": "verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy)" - }, - "shortNames": { - "type": "array", - "items": { - "type": "string" - }, - "description": "shortNames is a list of suggested short names of the resource." - }, - "categories": { - "type": "array", - "items": { - "type": "string" - }, - "description": "categories is a list of the grouped resources this resource belongs to (e.g. 'all')" - } - } - } - } - } diff --git a/federation/apis/swagger-spec/logs.json b/federation/apis/swagger-spec/logs.json deleted file mode 100644 index e8fae7c9a2c..00000000000 --- a/federation/apis/swagger-spec/logs.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "swaggerVersion": "1.2", - "apiVersion": "", - "basePath": "https://10.10.10.10:6443", - "resourcePath": "/logs", - "info": { - "title": "", - "description": "" - }, - "apis": [ - { - "path": "/logs/{logpath}", - "description": "get log files", - "operations": [ - { - "type": "void", - "method": "GET", - "nickname": "logFileHandler", - "parameters": [ - { - "type": "string", - "paramType": "path", - "name": "logpath", - "description": "path to the log", - "required": true, - "allowMultiple": false - } - ] - } - ] - }, - { - "path": "/logs", - "description": "get log files", - "operations": [ - { - "type": "void", - "method": "GET", - "nickname": "logFileListHandler", - "parameters": [] - } - ] - } - ], - "models": {} - } diff --git a/federation/apis/swagger-spec/resourceListing.json b/federation/apis/swagger-spec/resourceListing.json deleted file mode 100644 index 6d377163514..00000000000 --- a/federation/apis/swagger-spec/resourceListing.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "swaggerVersion": "1.2", - "apis": [ - { - "path": "/version", - "description": "git code version from which this is built" - }, - { - "path": "/apis", - "description": "get available API versions" - }, - { - "path": "/logs", - "description": "get log files" - }, - { - "path": "/apis/federation/v1beta1", - "description": "API at /apis/federation/v1beta1" - }, - { - "path": "/apis/federation", - "description": "get information of a group" - }, - { - "path": "/api/v1", - "description": "API at /api/v1" - }, - { - "path": "/api", - "description": "get available API versions" - }, - { - "path": "/apis/extensions/v1beta1", - "description": "API at /apis/extensions/v1beta1" - }, - { - "path": "/apis/extensions", - "description": "get information of a group" - } - ], - "apiVersion": "", - "info": { - "title": "", - "description": "" - } - } diff --git a/federation/apis/swagger-spec/v1.json b/federation/apis/swagger-spec/v1.json deleted file mode 100644 index 0e79209b0a6..00000000000 --- a/federation/apis/swagger-spec/v1.json +++ /dev/null @@ -1,5841 +0,0 @@ -{ - "swaggerVersion": "1.2", - "apiVersion": "v1", - "basePath": "https://10.10.10.10:6443", - "resourcePath": "/api/v1", - "info": { - "title": "", - "description": "" - }, - "apis": [ - { - "path": "/api/v1/namespaces/{namespace}/configmaps", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.ConfigMapList", - "method": "GET", - "summary": "list or watch objects of kind ConfigMap", - "nickname": "listNamespacedConfigMap", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.ConfigMapList" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.ConfigMap", - "method": "POST", - "summary": "create a ConfigMap", - "nickname": "createNamespacedConfigMap", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.ConfigMap", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.ConfigMap" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1.ConfigMap" - }, - { - "code": 202, - "message": "Accepted", - "responseModel": "v1.ConfigMap" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Status", - "method": "DELETE", - "summary": "delete collection of ConfigMap", - "nickname": "deletecollectionNamespacedConfigMap", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Status" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/watch/namespaces/{namespace}/configmaps", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch individual changes to a list of ConfigMap", - "nickname": "watchNamespacedConfigMapList", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/namespaces/{namespace}/configmaps/{name}", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.ConfigMap", - "method": "GET", - "summary": "read the specified ConfigMap", - "nickname": "readNamespacedConfigMap", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "export", - "description": "Should this value be exported. Export strips fields that a user can not specify.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "exact", - "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the ConfigMap", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.ConfigMap" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.ConfigMap", - "method": "PUT", - "summary": "replace the specified ConfigMap", - "nickname": "replaceNamespacedConfigMap", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.ConfigMap", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the ConfigMap", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.ConfigMap" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1.ConfigMap" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.ConfigMap", - "method": "PATCH", - "summary": "partially update the specified ConfigMap", - "nickname": "patchNamespacedConfigMap", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Patch", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the ConfigMap", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.ConfigMap" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ] - }, - { - "type": "v1.Status", - "method": "DELETE", - "summary": "delete a ConfigMap", - "nickname": "deleteNamespacedConfigMap", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.DeleteOptions", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the ConfigMap", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Status" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/watch/namespaces/{namespace}/configmaps/{name}", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch changes to an object of kind ConfigMap", - "nickname": "watchNamespacedConfigMap", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the ConfigMap", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/configmaps", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.ConfigMapList", - "method": "GET", - "summary": "list or watch objects of kind ConfigMap", - "nickname": "listConfigMapForAllNamespaces", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.ConfigMapList" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/watch/configmaps", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch individual changes to a list of ConfigMap", - "nickname": "watchConfigMapListForAllNamespaces", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/namespaces/{namespace}/events", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.EventList", - "method": "GET", - "summary": "list or watch objects of kind Event", - "nickname": "listNamespacedEvent", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.EventList" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Event", - "method": "POST", - "summary": "create an Event", - "nickname": "createNamespacedEvent", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Event", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Event" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1.Event" - }, - { - "code": 202, - "message": "Accepted", - "responseModel": "v1.Event" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Status", - "method": "DELETE", - "summary": "delete collection of Event", - "nickname": "deletecollectionNamespacedEvent", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Status" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/watch/namespaces/{namespace}/events", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch individual changes to a list of Event", - "nickname": "watchNamespacedEventList", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/namespaces/{namespace}/events/{name}", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.Event", - "method": "GET", - "summary": "read the specified Event", - "nickname": "readNamespacedEvent", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "export", - "description": "Should this value be exported. Export strips fields that a user can not specify.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "exact", - "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Event", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Event" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Event", - "method": "PUT", - "summary": "replace the specified Event", - "nickname": "replaceNamespacedEvent", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Event", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Event", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Event" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1.Event" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Event", - "method": "PATCH", - "summary": "partially update the specified Event", - "nickname": "patchNamespacedEvent", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Patch", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Event", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Event" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ] - }, - { - "type": "v1.Status", - "method": "DELETE", - "summary": "delete an Event", - "nickname": "deleteNamespacedEvent", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.DeleteOptions", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Event", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Status" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/watch/namespaces/{namespace}/events/{name}", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch changes to an object of kind Event", - "nickname": "watchNamespacedEvent", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Event", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/events", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.EventList", - "method": "GET", - "summary": "list or watch objects of kind Event", - "nickname": "listEventForAllNamespaces", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.EventList" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/watch/events", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch individual changes to a list of Event", - "nickname": "watchEventListForAllNamespaces", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/namespaces", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.NamespaceList", - "method": "GET", - "summary": "list or watch objects of kind Namespace", - "nickname": "listNamespace", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.NamespaceList" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Namespace", - "method": "POST", - "summary": "create a Namespace", - "nickname": "createNamespace", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Namespace", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Namespace" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1.Namespace" - }, - { - "code": 202, - "message": "Accepted", - "responseModel": "v1.Namespace" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/watch/namespaces", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch individual changes to a list of Namespace", - "nickname": "watchNamespaceList", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/namespaces/{name}", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.Namespace", - "method": "GET", - "summary": "read the specified Namespace", - "nickname": "readNamespace", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "export", - "description": "Should this value be exported. Export strips fields that a user can not specify.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "exact", - "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Namespace", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Namespace" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Namespace", - "method": "PUT", - "summary": "replace the specified Namespace", - "nickname": "replaceNamespace", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Namespace", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Namespace", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Namespace" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1.Namespace" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Namespace", - "method": "PATCH", - "summary": "partially update the specified Namespace", - "nickname": "patchNamespace", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Patch", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Namespace", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Namespace" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ] - }, - { - "type": "v1.Status", - "method": "DELETE", - "summary": "delete a Namespace", - "nickname": "deleteNamespace", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.DeleteOptions", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Namespace", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Status" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/watch/namespaces/{name}", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch changes to an object of kind Namespace", - "nickname": "watchNamespace", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Namespace", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/namespaces/{name}/finalize", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.Namespace", - "method": "PUT", - "summary": "replace finalize of the specified Namespace", - "nickname": "replaceNamespaceFinalize", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Namespace", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Namespace", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Namespace" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1.Namespace" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/namespaces/{name}/status", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.Namespace", - "method": "GET", - "summary": "read status of the specified Namespace", - "nickname": "readNamespaceStatus", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Namespace", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Namespace" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Namespace", - "method": "PUT", - "summary": "replace status of the specified Namespace", - "nickname": "replaceNamespaceStatus", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Namespace", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Namespace", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Namespace" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1.Namespace" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Namespace", - "method": "PATCH", - "summary": "partially update status of the specified Namespace", - "nickname": "patchNamespaceStatus", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Patch", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Namespace", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Namespace" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ] - } - ] - }, - { - "path": "/api/v1/namespaces/{namespace}/secrets", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.SecretList", - "method": "GET", - "summary": "list or watch objects of kind Secret", - "nickname": "listNamespacedSecret", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.SecretList" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Secret", - "method": "POST", - "summary": "create a Secret", - "nickname": "createNamespacedSecret", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Secret", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Secret" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1.Secret" - }, - { - "code": 202, - "message": "Accepted", - "responseModel": "v1.Secret" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Status", - "method": "DELETE", - "summary": "delete collection of Secret", - "nickname": "deletecollectionNamespacedSecret", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Status" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/watch/namespaces/{namespace}/secrets", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch individual changes to a list of Secret", - "nickname": "watchNamespacedSecretList", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/namespaces/{namespace}/secrets/{name}", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.Secret", - "method": "GET", - "summary": "read the specified Secret", - "nickname": "readNamespacedSecret", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "export", - "description": "Should this value be exported. Export strips fields that a user can not specify.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "exact", - "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Secret", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Secret" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Secret", - "method": "PUT", - "summary": "replace the specified Secret", - "nickname": "replaceNamespacedSecret", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Secret", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Secret", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Secret" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1.Secret" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Secret", - "method": "PATCH", - "summary": "partially update the specified Secret", - "nickname": "patchNamespacedSecret", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Patch", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Secret", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Secret" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ] - }, - { - "type": "v1.Status", - "method": "DELETE", - "summary": "delete a Secret", - "nickname": "deleteNamespacedSecret", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.DeleteOptions", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Secret", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Status" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/watch/namespaces/{namespace}/secrets/{name}", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch changes to an object of kind Secret", - "nickname": "watchNamespacedSecret", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Secret", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/secrets", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.SecretList", - "method": "GET", - "summary": "list or watch objects of kind Secret", - "nickname": "listSecretForAllNamespaces", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.SecretList" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/watch/secrets", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch individual changes to a list of Secret", - "nickname": "watchSecretListForAllNamespaces", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/namespaces/{namespace}/services", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.ServiceList", - "method": "GET", - "summary": "list or watch objects of kind Service", - "nickname": "listNamespacedService", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.ServiceList" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Service", - "method": "POST", - "summary": "create a Service", - "nickname": "createNamespacedService", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Service", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Service" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1.Service" - }, - { - "code": 202, - "message": "Accepted", - "responseModel": "v1.Service" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Status", - "method": "DELETE", - "summary": "delete collection of Service", - "nickname": "deletecollectionNamespacedService", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Status" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/watch/namespaces/{namespace}/services", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch individual changes to a list of Service", - "nickname": "watchNamespacedServiceList", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/namespaces/{namespace}/services/{name}", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.Service", - "method": "GET", - "summary": "read the specified Service", - "nickname": "readNamespacedService", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "export", - "description": "Should this value be exported. Export strips fields that a user can not specify.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "exact", - "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Service", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Service" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Service", - "method": "PUT", - "summary": "replace the specified Service", - "nickname": "replaceNamespacedService", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Service", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Service", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Service" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1.Service" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Service", - "method": "PATCH", - "summary": "partially update the specified Service", - "nickname": "patchNamespacedService", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Patch", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Service", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Service" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ] - }, - { - "type": "v1.Status", - "method": "DELETE", - "summary": "delete a Service", - "nickname": "deleteNamespacedService", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.DeleteOptions", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Service", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Status" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/watch/namespaces/{namespace}/services/{name}", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch changes to an object of kind Service", - "nickname": "watchNamespacedService", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Service", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/services", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.ServiceList", - "method": "GET", - "summary": "list or watch objects of kind Service", - "nickname": "listServiceForAllNamespaces", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.ServiceList" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/watch/services", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.WatchEvent", - "method": "GET", - "summary": "watch individual changes to a list of Service", - "nickname": "watchServiceListForAllNamespaces", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "labelSelector", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "fieldSelector", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "includeUninitialized", - "description": "If true, partially initialized resources are included in the response.", - "required": false, - "allowMultiple": false - }, - { - "type": "boolean", - "paramType": "query", - "name": "watch", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "resourceVersion", - "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "timeoutSeconds", - "description": "Timeout for the list/watch call.", - "required": false, - "allowMultiple": false - }, - { - "type": "integer", - "paramType": "query", - "name": "limit", - "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.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "query", - "name": "continue", - "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 indicating the client must restart their list without the continue field. This 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.", - "required": false, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.WatchEvent" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf", - "application/json;stream=watch", - "application/vnd.kubernetes.protobuf;stream=watch" - ], - "consumes": [ - "*/*" - ] - } - ] - }, - { - "path": "/api/v1/namespaces/{namespace}/services/{name}/status", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.Service", - "method": "GET", - "summary": "read status of the specified Service", - "nickname": "readNamespacedServiceStatus", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Service", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Service" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Service", - "method": "PUT", - "summary": "replace status of the specified Service", - "nickname": "replaceNamespacedServiceStatus", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Service", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Service", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Service" - }, - { - "code": 201, - "message": "Created", - "responseModel": "v1.Service" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "*/*" - ] - }, - { - "type": "v1.Service", - "method": "PATCH", - "summary": "partially update status of the specified Service", - "nickname": "patchNamespacedServiceStatus", - "parameters": [ - { - "type": "string", - "paramType": "query", - "name": "pretty", - "description": "If 'true', then the output is pretty printed.", - "required": false, - "allowMultiple": false - }, - { - "type": "v1.Patch", - "paramType": "body", - "name": "body", - "description": "", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "namespace", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "allowMultiple": false - }, - { - "type": "string", - "paramType": "path", - "name": "name", - "description": "name of the Service", - "required": true, - "allowMultiple": false - } - ], - "responseMessages": [ - { - "code": 200, - "message": "OK", - "responseModel": "v1.Service" - } - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json" - ] - } - ] - }, - { - "path": "/api/v1", - "description": "API at /api/v1", - "operations": [ - { - "type": "v1.APIResourceList", - "method": "GET", - "summary": "get available resources", - "nickname": "getAPIResources", - "parameters": [], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "consumes": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ] - } - ] - } - ], - "models": { - "v1.ConfigMapList": { - "id": "v1.ConfigMapList", - "description": "ConfigMapList is a resource containing a list of ConfigMap objects.", - "required": [ - "items" - ], - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ListMeta", - "description": "More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" - }, - "items": { - "type": "array", - "items": { - "$ref": "v1.ConfigMap" - }, - "description": "Items is the list of ConfigMaps." - } - } - }, - "v1.ListMeta": { - "id": "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": { - "selfLink": { - "type": "string", - "description": "selfLink is a URL representing this object. Populated by the system. Read-only." - }, - "resourceVersion": { - "type": "string", - "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/api-conventions.md#concurrency-control-and-consistency" - }, - "continue": { - "type": "string", - "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 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." - } - } - }, - "v1.ConfigMap": { - "id": "v1.ConfigMap", - "description": "ConfigMap holds configuration data for pods to consume.", - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ObjectMeta", - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" - }, - "data": { - "type": "object", - "description": "Data contains the configuration data. Each key must consist of alphanumeric characters, '-', '_' or '.'." - } - } - }, - "v1.ObjectMeta": { - "id": "v1.ObjectMeta", - "description": "ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.", - "properties": { - "name": { - "type": "string", - "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: http://kubernetes.io/docs/user-guide/identifiers#names" - }, - "generateName": { - "type": "string", - "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 NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header).\n\nApplied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency" - }, - "namespace": { - "type": "string", - "description": "Namespace defines the space within 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: http://kubernetes.io/docs/user-guide/namespaces" - }, - "selfLink": { - "type": "string", - "description": "SelfLink is a URL representing this object. Populated by the system. Read-only." - }, - "uid": { - "type": "string", - "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: http://kubernetes.io/docs/user-guide/identifiers#uids" - }, - "resourceVersion": { - "type": "string", - "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/api-conventions.md#concurrency-control-and-consistency" - }, - "generation": { - "type": "integer", - "format": "int64", - "description": "A sequence number representing a specific generation of the desired state. Populated by the system. Read-only." - }, - "creationTimestamp": { - "type": "string", - "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/api-conventions.md#metadata" - }, - "deletionTimestamp": { - "type": "string", - "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 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/api-conventions.md#metadata" - }, - "deletionGracePeriodSeconds": { - "type": "integer", - "format": "int64", - "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." - }, - "labels": { - "type": "object", - "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: http://kubernetes.io/docs/user-guide/labels" - }, - "annotations": { - "type": "object", - "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: http://kubernetes.io/docs/user-guide/annotations" - }, - "ownerReferences": { - "type": "array", - "items": { - "$ref": "v1.OwnerReference" - }, - "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." - }, - "initializers": { - "$ref": "v1.Initializers", - "description": "An initializer is a controller which enforces some system invariant at object creation time. This field is a list of initializers that have not yet acted on this object. If nil or empty, this object has been completely initialized. Otherwise, the object is considered uninitialized and is hidden (in list/watch and get calls) from clients that haven't explicitly asked to observe uninitialized objects.\n\nWhen an object is created, the system will populate this list with the current set of initializers. Only privileged users may set or modify this list. Once it is empty, it may not be modified further by any user." - }, - "finalizers": { - "type": "array", - "items": { - "type": "string" - }, - "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." - }, - "clusterName": { - "type": "string", - "description": "The name of the cluster which the object belongs to. This is used to distinguish resources with same name and namespace in different clusters. This field is not set anywhere right now and apiserver is going to ignore it if set in create or update request." - } - } - }, - "v1.OwnerReference": { - "id": "v1.OwnerReference", - "description": "OwnerReference contains enough information to let you identify an owning object. Currently, an owning object must be in the same namespace, so there is no namespace field.", - "required": [ - "apiVersion", - "kind", - "name", - "uid" - ], - "properties": { - "apiVersion": { - "type": "string", - "description": "API version of the referent." - }, - "kind": { - "type": "string", - "description": "Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds" - }, - "name": { - "type": "string", - "description": "Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names" - }, - "uid": { - "type": "string", - "description": "UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids" - }, - "controller": { - "type": "boolean", - "description": "If true, this reference points to the managing controller." - }, - "blockOwnerDeletion": { - "type": "boolean", - "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. Defaults to false. To set this field, a user needs \"delete\" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned." - } - } - }, - "v1.Initializers": { - "id": "v1.Initializers", - "description": "Initializers tracks the progress of initialization.", - "required": [ - "pending" - ], - "properties": { - "pending": { - "type": "array", - "items": { - "$ref": "v1.Initializer" - }, - "description": "Pending is a list of initializers that must execute in order before this object is visible. When the last pending initializer is removed, and no failing result is set, the initializers struct will be set to nil and the object is considered as initialized and visible to all clients." - }, - "result": { - "$ref": "v1.Status", - "description": "If result is set with the Failure field, the object will be persisted to storage and then deleted, ensuring that other clients can observe the deletion." - } - } - }, - "v1.Initializer": { - "id": "v1.Initializer", - "description": "Initializer is information about an initializer that has not yet completed.", - "required": [ - "name" - ], - "properties": { - "name": { - "type": "string", - "description": "name of the process that is responsible for initializing this object." - } - } - }, - "v1.Status": { - "id": "v1.Status", - "description": "Status is a return value for calls that don't return other objects.", - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ListMeta", - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds" - }, - "status": { - "type": "string", - "description": "Status of the operation. One of: \"Success\" or \"Failure\". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status" - }, - "message": { - "type": "string", - "description": "A human-readable description of the status of this operation." - }, - "reason": { - "type": "string", - "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." - }, - "details": { - "$ref": "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." - }, - "code": { - "type": "integer", - "format": "int32", - "description": "Suggested HTTP return code for this status, 0 if not set." - } - } - }, - "v1.StatusDetails": { - "id": "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": { - "name": { - "type": "string", - "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described)." - }, - "group": { - "type": "string", - "description": "The group attribute of the resource associated with the status StatusReason." - }, - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "uid": { - "type": "string", - "description": "UID of the resource. (when there is a single resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids" - }, - "causes": { - "type": "array", - "items": { - "$ref": "v1.StatusCause" - }, - "description": "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes." - }, - "retryAfterSeconds": { - "type": "integer", - "format": "int32", - "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." - } - } - }, - "v1.StatusCause": { - "id": "v1.StatusCause", - "description": "StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.", - "properties": { - "reason": { - "type": "string", - "description": "A machine-readable description of the cause of the error. If this value is empty there is no information available." - }, - "message": { - "type": "string", - "description": "A human-readable description of the cause of the error. This field may be presented as-is to a reader." - }, - "field": { - "type": "string", - "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\"" - } - } - }, - "v1.WatchEvent": { - "id": "v1.WatchEvent", - "required": [ - "type", - "object" - ], - "properties": { - "type": { - "type": "string" - }, - "object": { - "type": "string" - } - } - }, - "v1.Patch": { - "id": "v1.Patch", - "description": "Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.", - "properties": {} - }, - "v1.DeleteOptions": { - "id": "v1.DeleteOptions", - "description": "DeleteOptions may be provided when deleting an API object.", - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "gracePeriodSeconds": { - "type": "integer", - "format": "int64", - "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." - }, - "preconditions": { - "$ref": "v1.Preconditions", - "description": "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned." - }, - "orphanDependents": { - "type": "boolean", - "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." - }, - "propagationPolicy": { - "$ref": "v1.DeletionPropagation", - "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." - } - } - }, - "v1.Preconditions": { - "id": "v1.Preconditions", - "description": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", - "properties": { - "uid": { - "$ref": "types.UID", - "description": "Specifies the target UID." - } - } - }, - "types.UID": { - "id": "types.UID", - "properties": {} - }, - "v1.DeletionPropagation": { - "id": "v1.DeletionPropagation", - "properties": {} - }, - "v1.EventList": { - "id": "v1.EventList", - "description": "EventList is a list of events.", - "required": [ - "items" - ], - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ListMeta", - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds" - }, - "items": { - "type": "array", - "items": { - "$ref": "v1.Event" - }, - "description": "List of events" - } - } - }, - "v1.Event": { - "id": "v1.Event", - "description": "Event is a report of an event somewhere in the cluster.", - "required": [ - "metadata", - "involvedObject" - ], - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ObjectMeta", - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" - }, - "involvedObject": { - "$ref": "v1.ObjectReference", - "description": "The object that this event is about." - }, - "reason": { - "type": "string", - "description": "This should be a short, machine understandable string that gives the reason for the transition into the object's current status." - }, - "message": { - "type": "string", - "description": "A human-readable description of the status of this operation." - }, - "source": { - "$ref": "v1.EventSource", - "description": "The component reporting this event. Should be a short machine understandable string." - }, - "firstTimestamp": { - "type": "string", - "description": "The time at which the event was first recorded. (Time of server receipt is in TypeMeta.)" - }, - "lastTimestamp": { - "type": "string", - "description": "The time at which the most recent occurrence of this event was recorded." - }, - "count": { - "type": "integer", - "format": "int32", - "description": "The number of times this event has occurred." - }, - "type": { - "type": "string", - "description": "Type of this event (Normal, Warning), new types could be added in the future" - } - } - }, - "v1.ObjectReference": { - "id": "v1.ObjectReference", - "description": "ObjectReference contains enough information to let you inspect or modify the referred object.", - "properties": { - "kind": { - "type": "string", - "description": "Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds" - }, - "namespace": { - "type": "string", - "description": "Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/" - }, - "name": { - "type": "string", - "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names" - }, - "uid": { - "type": "string", - "description": "UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids" - }, - "apiVersion": { - "type": "string", - "description": "API version of the referent." - }, - "resourceVersion": { - "type": "string", - "description": "Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency" - }, - "fieldPath": { - "type": "string", - "description": "If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: \"spec.containers{name}\" (where \"name\" refers to the name of the container that triggered the event) or if no container name is specified \"spec.containers[2]\" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object." - } - } - }, - "v1.EventSource": { - "id": "v1.EventSource", - "description": "EventSource contains information for an event.", - "properties": { - "component": { - "type": "string", - "description": "Component from which the event is generated." - }, - "host": { - "type": "string", - "description": "Node name on which the event is generated." - } - } - }, - "v1.NamespaceList": { - "id": "v1.NamespaceList", - "description": "NamespaceList is a list of Namespaces.", - "required": [ - "items" - ], - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ListMeta", - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds" - }, - "items": { - "type": "array", - "items": { - "$ref": "v1.Namespace" - }, - "description": "Items is the list of Namespace objects in the list. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/" - } - } - }, - "v1.Namespace": { - "id": "v1.Namespace", - "description": "Namespace provides a scope for Names. Use of multiple namespaces is optional.", - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ObjectMeta", - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" - }, - "spec": { - "$ref": "v1.NamespaceSpec", - "description": "Spec defines the behavior of the Namespace. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status" - }, - "status": { - "$ref": "v1.NamespaceStatus", - "description": "Status describes the current status of a Namespace. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status" - } - } - }, - "v1.NamespaceSpec": { - "id": "v1.NamespaceSpec", - "description": "NamespaceSpec describes the attributes on a Namespace.", - "properties": { - "finalizers": { - "type": "array", - "items": { - "$ref": "v1.FinalizerName" - }, - "description": "Finalizers is an opaque list of values that must be empty to permanently remove object from storage. More info: https://git.k8s.io/community/contributors/design-proposals/namespaces.md#finalizers" - } - } - }, - "v1.FinalizerName": { - "id": "v1.FinalizerName", - "properties": {} - }, - "v1.NamespaceStatus": { - "id": "v1.NamespaceStatus", - "description": "NamespaceStatus is information about the current status of a Namespace.", - "properties": { - "phase": { - "type": "string", - "description": "Phase is the current lifecycle phase of the namespace. More info: https://git.k8s.io/community/contributors/design-proposals/namespaces.md#phases" - } - } - }, - "v1.SecretList": { - "id": "v1.SecretList", - "description": "SecretList is a list of Secret.", - "required": [ - "items" - ], - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ListMeta", - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds" - }, - "items": { - "type": "array", - "items": { - "$ref": "v1.Secret" - }, - "description": "Items is a list of secret objects. More info: https://kubernetes.io/docs/concepts/configuration/secret" - } - } - }, - "v1.Secret": { - "id": "v1.Secret", - "description": "Secret holds secret data of a certain type. The total bytes of the values in the Data field must be less than MaxSecretSize bytes.", - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ObjectMeta", - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" - }, - "data": { - "type": "object", - "description": "Data contains the secret data. Each key must consist of alphanumeric characters, '-', '_' or '.'. The serialized form of the secret data is a base64 encoded string, representing the arbitrary (possibly non-string) data value here. Described in https://tools.ietf.org/html/rfc4648#section-4" - }, - "stringData": { - "type": "object", - "description": "stringData allows specifying non-binary secret data in string form. It is provided as a write-only convenience method. All keys and values are merged into the data field on write, overwriting any existing values. It is never output when reading from the API." - }, - "type": { - "type": "string", - "description": "Used to facilitate programmatic handling of secret data." - } - } - }, - "v1.ServiceList": { - "id": "v1.ServiceList", - "description": "ServiceList holds a list of services.", - "required": [ - "items" - ], - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ListMeta", - "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds" - }, - "items": { - "type": "array", - "items": { - "$ref": "v1.Service" - }, - "description": "List of services" - } - } - }, - "v1.Service": { - "id": "v1.Service", - "description": "Service is a named abstraction of software service (for example, mysql) consisting of local port (for example 3306) that the proxy listens on, and the selector that determines which pods will answer requests sent through the proxy.", - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "metadata": { - "$ref": "v1.ObjectMeta", - "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" - }, - "spec": { - "$ref": "v1.ServiceSpec", - "description": "Spec defines the behavior of a service. https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status" - }, - "status": { - "$ref": "v1.ServiceStatus", - "description": "Most recently observed status of the service. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status" - } - } - }, - "v1.ServiceSpec": { - "id": "v1.ServiceSpec", - "description": "ServiceSpec describes the attributes that a user creates on a service.", - "properties": { - "ports": { - "type": "array", - "items": { - "$ref": "v1.ServicePort" - }, - "description": "The list of ports that are exposed by this service. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies" - }, - "selector": { - "type": "object", - "description": "Route service traffic to pods with label keys and values matching this selector. If empty or not present, the service is assumed to have an external process managing its endpoints, which Kubernetes will not modify. Only applies to types ClusterIP, NodePort, and LoadBalancer. Ignored if type is ExternalName. More info: https://kubernetes.io/docs/concepts/services-networking/service/" - }, - "clusterIP": { - "type": "string", - "description": "clusterIP is the IP address of the service and is usually assigned randomly by the master. If an address is specified manually and is not in use by others, it will be allocated to the service; otherwise, creation of the service will fail. This field can not be changed through updates. Valid values are \"None\", empty string (\"\"), or a valid IP address. \"None\" can be specified for headless services when proxying is not required. Only applies to types ClusterIP, NodePort, and LoadBalancer. Ignored if type is ExternalName. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies" - }, - "type": { - "type": "string", - "description": "type determines how the Service is exposed. Defaults to ClusterIP. Valid options are ExternalName, ClusterIP, NodePort, and LoadBalancer. \"ExternalName\" maps to the specified externalName. \"ClusterIP\" allocates a cluster-internal IP address for load-balancing to endpoints. Endpoints are determined by the selector or if that is not specified, by manual construction of an Endpoints object. If clusterIP is \"None\", no virtual IP is allocated and the endpoints are published as a set of endpoints rather than a stable IP. \"NodePort\" builds on ClusterIP and allocates a port on every node which routes to the clusterIP. \"LoadBalancer\" builds on NodePort and creates an external load-balancer (if supported in the current cloud) which routes to the clusterIP. More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services " - }, - "externalIPs": { - "type": "array", - "items": { - "type": "string" - }, - "description": "externalIPs is a list of IP addresses for which nodes in the cluster will also accept traffic for this service. These IPs are not managed by Kubernetes. The user is responsible for ensuring that traffic arrives at a node with this IP. A common example is external load-balancers that are not part of the Kubernetes system." - }, - "sessionAffinity": { - "type": "string", - "description": "Supports \"ClientIP\" and \"None\". Used to maintain session affinity. Enable client IP based session affinity. Must be ClientIP or None. Defaults to None. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies" - }, - "loadBalancerIP": { - "type": "string", - "description": "Only applies to Service Type: LoadBalancer LoadBalancer will get created with the IP specified in this field. This feature depends on whether the underlying cloud-provider supports specifying the loadBalancerIP when a load balancer is created. This field will be ignored if the cloud-provider does not support the feature." - }, - "loadBalancerSourceRanges": { - "type": "array", - "items": { - "type": "string" - }, - "description": "If specified and supported by the platform, this will restrict traffic through the cloud-provider load-balancer will be restricted to the specified client IPs. This field will be ignored if the cloud-provider does not support the feature.\" More info: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/" - }, - "externalName": { - "type": "string", - "description": "externalName is the external reference that kubedns or equivalent will return as a CNAME record for this service. No proxying will be involved. Must be a valid DNS name and requires Type to be ExternalName." - }, - "externalTrafficPolicy": { - "type": "string", - "description": "externalTrafficPolicy denotes if this Service desires to route external traffic to node-local or cluster-wide endpoints. \"Local\" preserves the client source IP and avoids a second hop for LoadBalancer and Nodeport type services, but risks potentially imbalanced traffic spreading. \"Cluster\" obscures the client source IP and may cause a second hop to another node, but should have good overall load-spreading." - }, - "healthCheckNodePort": { - "type": "integer", - "format": "int32", - "description": "healthCheckNodePort specifies the healthcheck nodePort for the service. If not specified, HealthCheckNodePort is created by the service api backend with the allocated nodePort. Will use user-specified nodePort value if specified by the client. Only effects when Type is set to LoadBalancer and ExternalTrafficPolicy is set to Local." - }, - "publishNotReadyAddresses": { - "type": "boolean", - "description": "publishNotReadyAddresses, when set to true, indicates that DNS implementations must publish the notReadyAddresses of subsets for the Endpoints associated with the Service. The default value is false. The primary use case for setting this field is to use a StatefulSet's Headless Service to propagate SRV records for its Pods without respect to their readiness for purpose of peer discovery. This field will replace the service.alpha.kubernetes.io/tolerate-unready-endpoints when that annotation is deprecated and all clients have been converted to use this field." - }, - "sessionAffinityConfig": { - "$ref": "v1.SessionAffinityConfig", - "description": "sessionAffinityConfig contains the configurations of session affinity." - } - } - }, - "v1.ServicePort": { - "id": "v1.ServicePort", - "description": "ServicePort contains information on service's port.", - "required": [ - "port" - ], - "properties": { - "name": { - "type": "string", - "description": "The name of this port within the service. This must be a DNS_LABEL. All ports within a ServiceSpec must have unique names. This maps to the 'Name' field in EndpointPort objects. Optional if only one ServicePort is defined on this service." - }, - "protocol": { - "type": "string", - "description": "The IP protocol for this port. Supports \"TCP\" and \"UDP\". Default is TCP." - }, - "port": { - "type": "integer", - "format": "int32", - "description": "The port that will be exposed by this service." - }, - "targetPort": { - "type": "string", - "description": "Number or name of the port to access on the pods targeted by the service. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. If this is a string, it will be looked up as a named port in the target Pod's container ports. If this is not specified, the value of the 'port' field is used (an identity map). This field is ignored for services with clusterIP=None, and should be omitted or set equal to the 'port' field. More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service" - }, - "nodePort": { - "type": "integer", - "format": "int32", - "description": "The port on each node on which this service is exposed when type=NodePort or LoadBalancer. Usually assigned by the system. If specified, it will be allocated to the service if unused or else creation of the service will fail. Default is to auto-allocate a port if the ServiceType of this Service requires one. More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport" - } - } - }, - "v1.SessionAffinityConfig": { - "id": "v1.SessionAffinityConfig", - "description": "SessionAffinityConfig represents the configurations of session affinity.", - "properties": { - "clientIP": { - "$ref": "v1.ClientIPConfig", - "description": "clientIP contains the configurations of Client IP based session affinity." - } - } - }, - "v1.ClientIPConfig": { - "id": "v1.ClientIPConfig", - "description": "ClientIPConfig represents the configurations of Client IP based session affinity.", - "properties": { - "timeoutSeconds": { - "type": "integer", - "format": "int32", - "description": "timeoutSeconds specifies the seconds of ClientIP type session sticky time. The value must be \u003e0 \u0026\u0026 \u003c=86400(for 1 day) if ServiceAffinity == \"ClientIP\". Default value is 10800(for 3 hours)." - } - } - }, - "v1.ServiceStatus": { - "id": "v1.ServiceStatus", - "description": "ServiceStatus represents the current status of a service.", - "properties": { - "loadBalancer": { - "$ref": "v1.LoadBalancerStatus", - "description": "LoadBalancer contains the current status of the load-balancer, if one is present." - } - } - }, - "v1.LoadBalancerStatus": { - "id": "v1.LoadBalancerStatus", - "description": "LoadBalancerStatus represents the status of a load-balancer.", - "properties": { - "ingress": { - "type": "array", - "items": { - "$ref": "v1.LoadBalancerIngress" - }, - "description": "Ingress is a list containing ingress points for the load-balancer. Traffic intended for the service should be sent to these ingress points." - } - } - }, - "v1.LoadBalancerIngress": { - "id": "v1.LoadBalancerIngress", - "description": "LoadBalancerIngress represents the status of a load-balancer ingress point: traffic intended for the service should be sent to an ingress point.", - "properties": { - "ip": { - "type": "string", - "description": "IP is set for load-balancer ingress points that are IP based (typically GCE or OpenStack load-balancers)" - }, - "hostname": { - "type": "string", - "description": "Hostname is set for load-balancer ingress points that are DNS based (typically AWS load-balancers)" - } - } - }, - "v1.APIResourceList": { - "id": "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.", - "required": [ - "groupVersion", - "resources" - ], - "properties": { - "kind": { - "type": "string", - "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/api-conventions.md#types-kinds" - }, - "apiVersion": { - "type": "string", - "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/api-conventions.md#resources" - }, - "groupVersion": { - "type": "string", - "description": "groupVersion is the group and version this APIResourceList is for." - }, - "resources": { - "type": "array", - "items": { - "$ref": "v1.APIResource" - }, - "description": "resources contains the name of the resources and if they are namespaced." - } - } - }, - "v1.APIResource": { - "id": "v1.APIResource", - "description": "APIResource specifies the name of a resource and whether it is namespaced.", - "required": [ - "name", - "singularName", - "namespaced", - "kind", - "verbs" - ], - "properties": { - "name": { - "type": "string", - "description": "name is the plural name of the resource." - }, - "singularName": { - "type": "string", - "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." - }, - "namespaced": { - "type": "boolean", - "description": "namespaced indicates if a resource is namespaced or not." - }, - "group": { - "type": "string", - "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\"." - }, - "version": { - "type": "string", - "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)\"." - }, - "kind": { - "type": "string", - "description": "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')" - }, - "verbs": { - "type": "array", - "items": { - "type": "string" - }, - "description": "verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy)" - }, - "shortNames": { - "type": "array", - "items": { - "type": "string" - }, - "description": "shortNames is a list of suggested short names of the resource." - }, - "categories": { - "type": "array", - "items": { - "type": "string" - }, - "description": "categories is a list of the grouped resources this resource belongs to (e.g. 'all')" - } - } - } - } - } diff --git a/federation/apis/swagger-spec/version.json b/federation/apis/swagger-spec/version.json deleted file mode 100644 index 1b60aa14b9e..00000000000 --- a/federation/apis/swagger-spec/version.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "swaggerVersion": "1.2", - "apiVersion": "", - "basePath": "https://10.10.10.10:6443", - "resourcePath": "/version", - "info": { - "title": "", - "description": "" - }, - "apis": [ - { - "path": "/version", - "description": "git code version from which this is built", - "operations": [ - { - "type": "version.Info", - "method": "GET", - "summary": "get the code version", - "nickname": "getCodeVersion", - "parameters": [], - "produces": [ - "application/json" - ], - "consumes": [ - "application/json" - ] - } - ] - } - ], - "models": { - "version.Info": { - "id": "version.Info", - "required": [ - "major", - "minor", - "gitVersion", - "gitCommit", - "gitTreeState", - "buildDate", - "goVersion", - "compiler", - "platform" - ], - "properties": { - "major": { - "type": "string" - }, - "minor": { - "type": "string" - }, - "gitVersion": { - "type": "string" - }, - "gitCommit": { - "type": "string" - }, - "gitTreeState": { - "type": "string" - }, - "buildDate": { - "type": "string" - }, - "goVersion": { - "type": "string" - }, - "compiler": { - "type": "string" - }, - "platform": { - "type": "string" - } - } - } - } - } diff --git a/federation/client/cache/BUILD b/federation/client/cache/BUILD deleted file mode 100644 index f0690fafee8..00000000000 --- a/federation/client/cache/BUILD +++ /dev/null @@ -1,29 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["cluster_cache.go"], - importpath = "k8s.io/kubernetes/federation/client/cache", - deps = [ - "//federation/apis/federation/v1beta1:go_default_library", - "//vendor/k8s.io/client-go/tools/cache:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/client/cache/cluster_cache.go b/federation/client/cache/cluster_cache.go deleted file mode 100644 index 82c30de3e7b..00000000000 --- a/federation/client/cache/cluster_cache.go +++ /dev/null @@ -1,35 +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 cache - -import ( - kubecache "k8s.io/client-go/tools/cache" - "k8s.io/kubernetes/federation/apis/federation/v1beta1" -) - -// StoreToClusterLister makes a Store have the List method of the metav1.ClusterInterface -// The Store must contain (only) clusters. -type StoreToClusterLister struct { - kubecache.Store -} - -func (s *StoreToClusterLister) List() (clusters v1beta1.ClusterList, err error) { - for _, m := range s.Store.List() { - clusters.Items = append(clusters.Items, *(m.(*v1beta1.Cluster))) - } - return clusters, nil -} diff --git a/federation/client/clientset_generated/federation_clientset/BUILD b/federation/client/clientset_generated/federation_clientset/BUILD deleted file mode 100644 index 9ff95a31b3f..00000000000 --- a/federation/client/clientset_generated/federation_clientset/BUILD +++ /dev/null @@ -1,50 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "clientset.go", - "doc.go", - "import_known_versions.go", - ], - importpath = "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset", - deps = [ - "//federation/apis/federation/install:go_default_library", - "//federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1:go_default_library", - "//federation/client/clientset_generated/federation_clientset/typed/batch/v1:go_default_library", - "//federation/client/clientset_generated/federation_clientset/typed/core/v1:go_default_library", - "//federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1:go_default_library", - "//federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/client-go/discovery:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - "//vendor/k8s.io/client-go/util/flowcontrol:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/client/clientset_generated/federation_clientset/fake:all-srcs", - "//federation/client/clientset_generated/federation_clientset/scheme:all-srcs", - "//federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1:all-srcs", - "//federation/client/clientset_generated/federation_clientset/typed/batch/v1:all-srcs", - "//federation/client/clientset_generated/federation_clientset/typed/core/v1:all-srcs", - "//federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1:all-srcs", - "//federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/client/clientset_generated/federation_clientset/clientset.go b/federation/client/clientset_generated/federation_clientset/clientset.go deleted file mode 100644 index 38b461c111a..00000000000 --- a/federation/client/clientset_generated/federation_clientset/clientset.go +++ /dev/null @@ -1,186 +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 federation_clientset - -import ( - glog "github.com/golang/glog" - discovery "k8s.io/client-go/discovery" - rest "k8s.io/client-go/rest" - flowcontrol "k8s.io/client-go/util/flowcontrol" - autoscalingv1 "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1" - batchv1 "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/batch/v1" - corev1 "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/core/v1" - extensionsv1beta1 "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1" - federationv1beta1 "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1" -) - -type Interface interface { - Discovery() discovery.DiscoveryInterface - AutoscalingV1() autoscalingv1.AutoscalingV1Interface - // Deprecated: please explicitly pick a version if possible. - Autoscaling() autoscalingv1.AutoscalingV1Interface - BatchV1() batchv1.BatchV1Interface - // Deprecated: please explicitly pick a version if possible. - Batch() batchv1.BatchV1Interface - CoreV1() corev1.CoreV1Interface - // Deprecated: please explicitly pick a version if possible. - Core() corev1.CoreV1Interface - ExtensionsV1beta1() extensionsv1beta1.ExtensionsV1beta1Interface - // Deprecated: please explicitly pick a version if possible. - Extensions() extensionsv1beta1.ExtensionsV1beta1Interface - FederationV1beta1() federationv1beta1.FederationV1beta1Interface - // Deprecated: please explicitly pick a version if possible. - Federation() federationv1beta1.FederationV1beta1Interface -} - -// Clientset contains the clients for groups. Each group has exactly one -// version included in a Clientset. -type Clientset struct { - *discovery.DiscoveryClient - autoscalingV1 *autoscalingv1.AutoscalingV1Client - batchV1 *batchv1.BatchV1Client - coreV1 *corev1.CoreV1Client - extensionsV1beta1 *extensionsv1beta1.ExtensionsV1beta1Client - federationV1beta1 *federationv1beta1.FederationV1beta1Client -} - -// AutoscalingV1 retrieves the AutoscalingV1Client -func (c *Clientset) AutoscalingV1() autoscalingv1.AutoscalingV1Interface { - return c.autoscalingV1 -} - -// Deprecated: Autoscaling retrieves the default version of AutoscalingClient. -// Please explicitly pick a version. -func (c *Clientset) Autoscaling() autoscalingv1.AutoscalingV1Interface { - return c.autoscalingV1 -} - -// BatchV1 retrieves the BatchV1Client -func (c *Clientset) BatchV1() batchv1.BatchV1Interface { - return c.batchV1 -} - -// Deprecated: Batch retrieves the default version of BatchClient. -// Please explicitly pick a version. -func (c *Clientset) Batch() batchv1.BatchV1Interface { - return c.batchV1 -} - -// CoreV1 retrieves the CoreV1Client -func (c *Clientset) CoreV1() corev1.CoreV1Interface { - return c.coreV1 -} - -// Deprecated: Core retrieves the default version of CoreClient. -// Please explicitly pick a version. -func (c *Clientset) Core() corev1.CoreV1Interface { - return c.coreV1 -} - -// ExtensionsV1beta1 retrieves the ExtensionsV1beta1Client -func (c *Clientset) ExtensionsV1beta1() extensionsv1beta1.ExtensionsV1beta1Interface { - return c.extensionsV1beta1 -} - -// Deprecated: Extensions retrieves the default version of ExtensionsClient. -// Please explicitly pick a version. -func (c *Clientset) Extensions() extensionsv1beta1.ExtensionsV1beta1Interface { - return c.extensionsV1beta1 -} - -// FederationV1beta1 retrieves the FederationV1beta1Client -func (c *Clientset) FederationV1beta1() federationv1beta1.FederationV1beta1Interface { - return c.federationV1beta1 -} - -// Deprecated: Federation retrieves the default version of FederationClient. -// Please explicitly pick a version. -func (c *Clientset) Federation() federationv1beta1.FederationV1beta1Interface { - return c.federationV1beta1 -} - -// Discovery retrieves the DiscoveryClient -func (c *Clientset) Discovery() discovery.DiscoveryInterface { - if c == nil { - return nil - } - return c.DiscoveryClient -} - -// NewForConfig creates a new Clientset for the given config. -func NewForConfig(c *rest.Config) (*Clientset, error) { - configShallowCopy := *c - if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { - configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) - } - var cs Clientset - var err error - cs.autoscalingV1, err = autoscalingv1.NewForConfig(&configShallowCopy) - if err != nil { - return nil, err - } - cs.batchV1, err = batchv1.NewForConfig(&configShallowCopy) - if err != nil { - return nil, err - } - cs.coreV1, err = corev1.NewForConfig(&configShallowCopy) - if err != nil { - return nil, err - } - cs.extensionsV1beta1, err = extensionsv1beta1.NewForConfig(&configShallowCopy) - if err != nil { - return nil, err - } - cs.federationV1beta1, err = federationv1beta1.NewForConfig(&configShallowCopy) - if err != nil { - return nil, err - } - - cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy) - if err != nil { - glog.Errorf("failed to create the DiscoveryClient: %v", err) - return nil, err - } - return &cs, nil -} - -// NewForConfigOrDie creates a new Clientset for the given config and -// panics if there is an error in the config. -func NewForConfigOrDie(c *rest.Config) *Clientset { - var cs Clientset - cs.autoscalingV1 = autoscalingv1.NewForConfigOrDie(c) - cs.batchV1 = batchv1.NewForConfigOrDie(c) - cs.coreV1 = corev1.NewForConfigOrDie(c) - cs.extensionsV1beta1 = extensionsv1beta1.NewForConfigOrDie(c) - cs.federationV1beta1 = federationv1beta1.NewForConfigOrDie(c) - - cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c) - return &cs -} - -// New creates a new Clientset for the given RESTClient. -func New(c rest.Interface) *Clientset { - var cs Clientset - cs.autoscalingV1 = autoscalingv1.New(c) - cs.batchV1 = batchv1.New(c) - cs.coreV1 = corev1.New(c) - cs.extensionsV1beta1 = extensionsv1beta1.New(c) - cs.federationV1beta1 = federationv1beta1.New(c) - - cs.DiscoveryClient = discovery.NewDiscoveryClient(c) - return &cs -} diff --git a/federation/client/clientset_generated/federation_clientset/doc.go b/federation/client/clientset_generated/federation_clientset/doc.go deleted file mode 100644 index 3ef671cc0d7..00000000000 --- a/federation/client/clientset_generated/federation_clientset/doc.go +++ /dev/null @@ -1,18 +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. -*/ - -// This package has the automatically generated clientset. -package federation_clientset diff --git a/federation/client/clientset_generated/federation_clientset/fake/BUILD b/federation/client/clientset_generated/federation_clientset/fake/BUILD deleted file mode 100644 index 2bbe84f4bfd..00000000000 --- a/federation/client/clientset_generated/federation_clientset/fake/BUILD +++ /dev/null @@ -1,55 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "clientset_generated.go", - "doc.go", - "register.go", - ], - importpath = "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/fake", - deps = [ - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/client/clientset_generated/federation_clientset:go_default_library", - "//federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1:go_default_library", - "//federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/fake:go_default_library", - "//federation/client/clientset_generated/federation_clientset/typed/batch/v1:go_default_library", - "//federation/client/clientset_generated/federation_clientset/typed/batch/v1/fake:go_default_library", - "//federation/client/clientset_generated/federation_clientset/typed/core/v1:go_default_library", - "//federation/client/clientset_generated/federation_clientset/typed/core/v1/fake:go_default_library", - "//federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1:go_default_library", - "//federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake:go_default_library", - "//federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1:go_default_library", - "//federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/fake:go_default_library", - "//vendor/k8s.io/api/autoscaling/v1:go_default_library", - "//vendor/k8s.io/api/batch/v1:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/discovery:go_default_library", - "//vendor/k8s.io/client-go/discovery/fake:go_default_library", - "//vendor/k8s.io/client-go/testing:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/client/clientset_generated/federation_clientset/fake/clientset_generated.go b/federation/client/clientset_generated/federation_clientset/fake/clientset_generated.go deleted file mode 100644 index 86f58101396..00000000000 --- a/federation/client/clientset_generated/federation_clientset/fake/clientset_generated.go +++ /dev/null @@ -1,119 +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 fake - -import ( - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/client-go/discovery" - fakediscovery "k8s.io/client-go/discovery/fake" - "k8s.io/client-go/testing" - clientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - autoscalingv1 "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1" - fakeautoscalingv1 "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/fake" - batchv1 "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/batch/v1" - fakebatchv1 "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/batch/v1/fake" - corev1 "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/core/v1" - fakecorev1 "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake" - extensionsv1beta1 "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1" - fakeextensionsv1beta1 "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake" - federationv1beta1 "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1" - fakefederationv1beta1 "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/fake" -) - -// NewSimpleClientset returns a clientset that will respond with the provided objects. -// It's backed by a very simple object tracker that processes creates, updates and deletions as-is, -// without applying any validations and/or defaults. It shouldn't be considered a replacement -// for a real clientset and is mostly useful in simple unit tests. -func NewSimpleClientset(objects ...runtime.Object) *Clientset { - o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) - for _, obj := range objects { - if err := o.Add(obj); err != nil { - panic(err) - } - } - - fakePtr := testing.Fake{} - fakePtr.AddReactor("*", "*", testing.ObjectReaction(o)) - fakePtr.AddWatchReactor("*", testing.DefaultWatchReactor(watch.NewFake(), nil)) - - return &Clientset{fakePtr, &fakediscovery.FakeDiscovery{Fake: &fakePtr}} -} - -// Clientset implements clientset.Interface. Meant to be embedded into a -// struct to get a default implementation. This makes faking out just the method -// you want to test easier. -type Clientset struct { - testing.Fake - discovery *fakediscovery.FakeDiscovery -} - -func (c *Clientset) Discovery() discovery.DiscoveryInterface { - return c.discovery -} - -var _ clientset.Interface = &Clientset{} - -// AutoscalingV1 retrieves the AutoscalingV1Client -func (c *Clientset) AutoscalingV1() autoscalingv1.AutoscalingV1Interface { - return &fakeautoscalingv1.FakeAutoscalingV1{Fake: &c.Fake} -} - -// Autoscaling retrieves the AutoscalingV1Client -func (c *Clientset) Autoscaling() autoscalingv1.AutoscalingV1Interface { - return &fakeautoscalingv1.FakeAutoscalingV1{Fake: &c.Fake} -} - -// BatchV1 retrieves the BatchV1Client -func (c *Clientset) BatchV1() batchv1.BatchV1Interface { - return &fakebatchv1.FakeBatchV1{Fake: &c.Fake} -} - -// Batch retrieves the BatchV1Client -func (c *Clientset) Batch() batchv1.BatchV1Interface { - return &fakebatchv1.FakeBatchV1{Fake: &c.Fake} -} - -// CoreV1 retrieves the CoreV1Client -func (c *Clientset) CoreV1() corev1.CoreV1Interface { - return &fakecorev1.FakeCoreV1{Fake: &c.Fake} -} - -// Core retrieves the CoreV1Client -func (c *Clientset) Core() corev1.CoreV1Interface { - return &fakecorev1.FakeCoreV1{Fake: &c.Fake} -} - -// ExtensionsV1beta1 retrieves the ExtensionsV1beta1Client -func (c *Clientset) ExtensionsV1beta1() extensionsv1beta1.ExtensionsV1beta1Interface { - return &fakeextensionsv1beta1.FakeExtensionsV1beta1{Fake: &c.Fake} -} - -// Extensions retrieves the ExtensionsV1beta1Client -func (c *Clientset) Extensions() extensionsv1beta1.ExtensionsV1beta1Interface { - return &fakeextensionsv1beta1.FakeExtensionsV1beta1{Fake: &c.Fake} -} - -// FederationV1beta1 retrieves the FederationV1beta1Client -func (c *Clientset) FederationV1beta1() federationv1beta1.FederationV1beta1Interface { - return &fakefederationv1beta1.FakeFederationV1beta1{Fake: &c.Fake} -} - -// Federation retrieves the FederationV1beta1Client -func (c *Clientset) Federation() federationv1beta1.FederationV1beta1Interface { - return &fakefederationv1beta1.FakeFederationV1beta1{Fake: &c.Fake} -} diff --git a/federation/client/clientset_generated/federation_clientset/fake/register.go b/federation/client/clientset_generated/federation_clientset/fake/register.go deleted file mode 100644 index 77da043a52c..00000000000 --- a/federation/client/clientset_generated/federation_clientset/fake/register.go +++ /dev/null @@ -1,61 +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 fake - -import ( - autoscalingv1 "k8s.io/api/autoscaling/v1" - batchv1 "k8s.io/api/batch/v1" - corev1 "k8s.io/api/core/v1" - extensionsv1beta1 "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" - serializer "k8s.io/apimachinery/pkg/runtime/serializer" - federationv1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1" -) - -var scheme = runtime.NewScheme() -var codecs = serializer.NewCodecFactory(scheme) -var parameterCodec = runtime.NewParameterCodec(scheme) - -func init() { - v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) - AddToScheme(scheme) -} - -// AddToScheme adds all types of this clientset into the given scheme. This allows composition -// of clientsets, like in: -// -// import ( -// "k8s.io/client-go/kubernetes" -// clientsetscheme "k8s.io/client-go/kuberentes/scheme" -// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" -// ) -// -// kclientset, _ := kubernetes.NewForConfig(c) -// aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) -// -// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types -// correctly. -func AddToScheme(scheme *runtime.Scheme) { - autoscalingv1.AddToScheme(scheme) - batchv1.AddToScheme(scheme) - corev1.AddToScheme(scheme) - extensionsv1beta1.AddToScheme(scheme) - federationv1beta1.AddToScheme(scheme) - -} diff --git a/federation/client/clientset_generated/federation_clientset/import_known_versions.go b/federation/client/clientset_generated/federation_clientset/import_known_versions.go deleted file mode 100644 index eb07236607e..00000000000 --- a/federation/client/clientset_generated/federation_clientset/import_known_versions.go +++ /dev/null @@ -1,25 +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 federation_clientset - -// These imports are the API groups the client will support. -import ( - _ "k8s.io/kubernetes/federation/apis/federation/install" -) - -func init() { -} diff --git a/federation/client/clientset_generated/federation_clientset/scheme/BUILD b/federation/client/clientset_generated/federation_clientset/scheme/BUILD deleted file mode 100644 index dcf65058e28..00000000000 --- a/federation/client/clientset_generated/federation_clientset/scheme/BUILD +++ /dev/null @@ -1,39 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "doc.go", - "register.go", - ], - importpath = "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/scheme", - deps = [ - "//federation/apis/federation/v1beta1:go_default_library", - "//vendor/k8s.io/api/autoscaling/v1:go_default_library", - "//vendor/k8s.io/api/batch/v1:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/client/clientset_generated/federation_clientset/scheme/register.go b/federation/client/clientset_generated/federation_clientset/scheme/register.go deleted file mode 100644 index db48b8e89b9..00000000000 --- a/federation/client/clientset_generated/federation_clientset/scheme/register.go +++ /dev/null @@ -1,61 +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 scheme - -import ( - autoscalingv1 "k8s.io/api/autoscaling/v1" - batchv1 "k8s.io/api/batch/v1" - corev1 "k8s.io/api/core/v1" - extensionsv1beta1 "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" - serializer "k8s.io/apimachinery/pkg/runtime/serializer" - federationv1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1" -) - -var Scheme = runtime.NewScheme() -var Codecs = serializer.NewCodecFactory(Scheme) -var ParameterCodec = runtime.NewParameterCodec(Scheme) - -func init() { - v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) - AddToScheme(Scheme) -} - -// AddToScheme adds all types of this clientset into the given scheme. This allows composition -// of clientsets, like in: -// -// import ( -// "k8s.io/client-go/kubernetes" -// clientsetscheme "k8s.io/client-go/kuberentes/scheme" -// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" -// ) -// -// kclientset, _ := kubernetes.NewForConfig(c) -// aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) -// -// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types -// correctly. -func AddToScheme(scheme *runtime.Scheme) { - autoscalingv1.AddToScheme(scheme) - batchv1.AddToScheme(scheme) - corev1.AddToScheme(scheme) - extensionsv1beta1.AddToScheme(scheme) - federationv1beta1.AddToScheme(scheme) - -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/BUILD b/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/BUILD deleted file mode 100644 index a98699060d8..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/BUILD +++ /dev/null @@ -1,42 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "autoscaling_client.go", - "doc.go", - "generated_expansion.go", - "horizontalpodautoscaler.go", - ], - importpath = "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1", - deps = [ - "//federation/client/clientset_generated/federation_clientset/scheme:go_default_library", - "//vendor/k8s.io/api/autoscaling/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/fake:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/autoscaling_client.go b/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/autoscaling_client.go deleted file mode 100644 index af02fdc6efb..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/autoscaling_client.go +++ /dev/null @@ -1,88 +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 v1 - -import ( - v1 "k8s.io/api/autoscaling/v1" - serializer "k8s.io/apimachinery/pkg/runtime/serializer" - rest "k8s.io/client-go/rest" - "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/scheme" -) - -type AutoscalingV1Interface interface { - RESTClient() rest.Interface - HorizontalPodAutoscalersGetter -} - -// AutoscalingV1Client is used to interact with features provided by the autoscaling group. -type AutoscalingV1Client struct { - restClient rest.Interface -} - -func (c *AutoscalingV1Client) HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerInterface { - return newHorizontalPodAutoscalers(c, namespace) -} - -// NewForConfig creates a new AutoscalingV1Client for the given config. -func NewForConfig(c *rest.Config) (*AutoscalingV1Client, error) { - config := *c - if err := setConfigDefaults(&config); err != nil { - return nil, err - } - client, err := rest.RESTClientFor(&config) - if err != nil { - return nil, err - } - return &AutoscalingV1Client{client}, nil -} - -// NewForConfigOrDie creates a new AutoscalingV1Client for the given config and -// panics if there is an error in the config. -func NewForConfigOrDie(c *rest.Config) *AutoscalingV1Client { - client, err := NewForConfig(c) - if err != nil { - panic(err) - } - return client -} - -// New creates a new AutoscalingV1Client for the given RESTClient. -func New(c rest.Interface) *AutoscalingV1Client { - return &AutoscalingV1Client{c} -} - -func setConfigDefaults(config *rest.Config) error { - gv := v1.SchemeGroupVersion - config.GroupVersion = &gv - config.APIPath = "/apis" - config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs} - - if config.UserAgent == "" { - config.UserAgent = rest.DefaultKubernetesUserAgent() - } - - return nil -} - -// RESTClient returns a RESTClient that is used to communicate -// with API server by this client implementation. -func (c *AutoscalingV1Client) RESTClient() rest.Interface { - if c == nil { - return nil - } - return c.restClient -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/fake/BUILD b/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/fake/BUILD deleted file mode 100644 index 1b524c0105a..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/fake/BUILD +++ /dev/null @@ -1,40 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "doc.go", - "fake_autoscaling_client.go", - "fake_horizontalpodautoscaler.go", - ], - importpath = "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/fake", - deps = [ - "//federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1:go_default_library", - "//vendor/k8s.io/api/autoscaling/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - "//vendor/k8s.io/client-go/testing:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/fake/fake_autoscaling_client.go b/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/fake/fake_autoscaling_client.go deleted file mode 100644 index f6ec657c445..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/fake/fake_autoscaling_client.go +++ /dev/null @@ -1,38 +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 fake - -import ( - rest "k8s.io/client-go/rest" - testing "k8s.io/client-go/testing" - v1 "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1" -) - -type FakeAutoscalingV1 struct { - *testing.Fake -} - -func (c *FakeAutoscalingV1) HorizontalPodAutoscalers(namespace string) v1.HorizontalPodAutoscalerInterface { - return &FakeHorizontalPodAutoscalers{c, namespace} -} - -// RESTClient returns a RESTClient that is used to communicate -// with API server by this client implementation. -func (c *FakeAutoscalingV1) RESTClient() rest.Interface { - var ret *rest.RESTClient - return ret -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/fake/fake_horizontalpodautoscaler.go b/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/fake/fake_horizontalpodautoscaler.go deleted file mode 100644 index e7e660bdd79..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/fake/fake_horizontalpodautoscaler.go +++ /dev/null @@ -1,138 +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 fake - -import ( - autoscaling_v1 "k8s.io/api/autoscaling/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" -) - -// FakeHorizontalPodAutoscalers implements HorizontalPodAutoscalerInterface -type FakeHorizontalPodAutoscalers struct { - Fake *FakeAutoscalingV1 - ns string -} - -var horizontalpodautoscalersResource = schema.GroupVersionResource{Group: "autoscaling", Version: "v1", Resource: "horizontalpodautoscalers"} - -var horizontalpodautoscalersKind = schema.GroupVersionKind{Group: "autoscaling", Version: "v1", Kind: "HorizontalPodAutoscaler"} - -// Get takes name of the horizontalPodAutoscaler, and returns the corresponding horizontalPodAutoscaler object, and an error if there is any. -func (c *FakeHorizontalPodAutoscalers) Get(name string, options v1.GetOptions) (result *autoscaling_v1.HorizontalPodAutoscaler, err error) { - obj, err := c.Fake. - Invokes(testing.NewGetAction(horizontalpodautoscalersResource, c.ns, name), &autoscaling_v1.HorizontalPodAutoscaler{}) - - if obj == nil { - return nil, err - } - return obj.(*autoscaling_v1.HorizontalPodAutoscaler), err -} - -// List takes label and field selectors, and returns the list of HorizontalPodAutoscalers that match those selectors. -func (c *FakeHorizontalPodAutoscalers) List(opts v1.ListOptions) (result *autoscaling_v1.HorizontalPodAutoscalerList, err error) { - obj, err := c.Fake. - Invokes(testing.NewListAction(horizontalpodautoscalersResource, horizontalpodautoscalersKind, c.ns, opts), &autoscaling_v1.HorizontalPodAutoscalerList{}) - - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &autoscaling_v1.HorizontalPodAutoscalerList{} - for _, item := range obj.(*autoscaling_v1.HorizontalPodAutoscalerList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested horizontalPodAutoscalers. -func (c *FakeHorizontalPodAutoscalers) Watch(opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchAction(horizontalpodautoscalersResource, c.ns, opts)) - -} - -// Create takes the representation of a horizontalPodAutoscaler and creates it. Returns the server's representation of the horizontalPodAutoscaler, and an error, if there is any. -func (c *FakeHorizontalPodAutoscalers) Create(horizontalPodAutoscaler *autoscaling_v1.HorizontalPodAutoscaler) (result *autoscaling_v1.HorizontalPodAutoscaler, err error) { - obj, err := c.Fake. - Invokes(testing.NewCreateAction(horizontalpodautoscalersResource, c.ns, horizontalPodAutoscaler), &autoscaling_v1.HorizontalPodAutoscaler{}) - - if obj == nil { - return nil, err - } - return obj.(*autoscaling_v1.HorizontalPodAutoscaler), err -} - -// Update takes the representation of a horizontalPodAutoscaler and updates it. Returns the server's representation of the horizontalPodAutoscaler, and an error, if there is any. -func (c *FakeHorizontalPodAutoscalers) Update(horizontalPodAutoscaler *autoscaling_v1.HorizontalPodAutoscaler) (result *autoscaling_v1.HorizontalPodAutoscaler, err error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateAction(horizontalpodautoscalersResource, c.ns, horizontalPodAutoscaler), &autoscaling_v1.HorizontalPodAutoscaler{}) - - if obj == nil { - return nil, err - } - return obj.(*autoscaling_v1.HorizontalPodAutoscaler), err -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeHorizontalPodAutoscalers) UpdateStatus(horizontalPodAutoscaler *autoscaling_v1.HorizontalPodAutoscaler) (*autoscaling_v1.HorizontalPodAutoscaler, error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(horizontalpodautoscalersResource, "status", c.ns, horizontalPodAutoscaler), &autoscaling_v1.HorizontalPodAutoscaler{}) - - if obj == nil { - return nil, err - } - return obj.(*autoscaling_v1.HorizontalPodAutoscaler), err -} - -// Delete takes name of the horizontalPodAutoscaler and deletes it. Returns an error if one occurs. -func (c *FakeHorizontalPodAutoscalers) Delete(name string, options *v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteAction(horizontalpodautoscalersResource, c.ns, name), &autoscaling_v1.HorizontalPodAutoscaler{}) - - return err -} - -// DeleteCollection deletes a collection of objects. -func (c *FakeHorizontalPodAutoscalers) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(horizontalpodautoscalersResource, c.ns, listOptions) - - _, err := c.Fake.Invokes(action, &autoscaling_v1.HorizontalPodAutoscalerList{}) - return err -} - -// Patch applies the patch and returns the patched horizontalPodAutoscaler. -func (c *FakeHorizontalPodAutoscalers) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *autoscaling_v1.HorizontalPodAutoscaler, err error) { - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(horizontalpodautoscalersResource, c.ns, name, data, subresources...), &autoscaling_v1.HorizontalPodAutoscaler{}) - - if obj == nil { - return nil, err - } - return obj.(*autoscaling_v1.HorizontalPodAutoscaler), err -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/generated_expansion.go b/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/generated_expansion.go deleted file mode 100644 index effefbd50b6..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/generated_expansion.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 v1 - -type HorizontalPodAutoscalerExpansion interface{} diff --git a/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/horizontalpodautoscaler.go b/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/horizontalpodautoscaler.go deleted file mode 100644 index 980221240cc..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/horizontalpodautoscaler.go +++ /dev/null @@ -1,172 +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 v1 - -import ( - v1 "k8s.io/api/autoscaling/v1" - meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" - scheme "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/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(*v1.HorizontalPodAutoscaler) (*v1.HorizontalPodAutoscaler, error) - Update(*v1.HorizontalPodAutoscaler) (*v1.HorizontalPodAutoscaler, error) - UpdateStatus(*v1.HorizontalPodAutoscaler) (*v1.HorizontalPodAutoscaler, error) - Delete(name string, options *meta_v1.DeleteOptions) error - DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error - Get(name string, options meta_v1.GetOptions) (*v1.HorizontalPodAutoscaler, error) - List(opts meta_v1.ListOptions) (*v1.HorizontalPodAutoscalerList, error) - Watch(opts meta_v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.HorizontalPodAutoscaler, err error) - HorizontalPodAutoscalerExpansion -} - -// horizontalPodAutoscalers implements HorizontalPodAutoscalerInterface -type horizontalPodAutoscalers struct { - client rest.Interface - ns string -} - -// newHorizontalPodAutoscalers returns a HorizontalPodAutoscalers -func newHorizontalPodAutoscalers(c *AutoscalingV1Client, namespace string) *horizontalPodAutoscalers { - return &horizontalPodAutoscalers{ - client: c.RESTClient(), - ns: namespace, - } -} - -// Get takes name of the horizontalPodAutoscaler, and returns the corresponding horizontalPodAutoscaler object, and an error if there is any. -func (c *horizontalPodAutoscalers) Get(name string, options meta_v1.GetOptions) (result *v1.HorizontalPodAutoscaler, err error) { - result = &v1.HorizontalPodAutoscaler{} - err = c.client.Get(). - Namespace(c.ns). - Resource("horizontalpodautoscalers"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of HorizontalPodAutoscalers that match those selectors. -func (c *horizontalPodAutoscalers) List(opts meta_v1.ListOptions) (result *v1.HorizontalPodAutoscalerList, err error) { - result = &v1.HorizontalPodAutoscalerList{} - err = c.client.Get(). - Namespace(c.ns). - Resource("horizontalpodautoscalers"). - VersionedParams(&opts, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested horizontalPodAutoscalers. -func (c *horizontalPodAutoscalers) Watch(opts meta_v1.ListOptions) (watch.Interface, error) { - opts.Watch = true - return c.client.Get(). - Namespace(c.ns). - Resource("horizontalpodautoscalers"). - VersionedParams(&opts, scheme.ParameterCodec). - Watch() -} - -// Create takes the representation of a horizontalPodAutoscaler and creates it. Returns the server's representation of the horizontalPodAutoscaler, and an error, if there is any. -func (c *horizontalPodAutoscalers) Create(horizontalPodAutoscaler *v1.HorizontalPodAutoscaler) (result *v1.HorizontalPodAutoscaler, err error) { - result = &v1.HorizontalPodAutoscaler{} - err = c.client.Post(). - Namespace(c.ns). - Resource("horizontalpodautoscalers"). - Body(horizontalPodAutoscaler). - Do(). - Into(result) - return -} - -// Update takes the representation of a horizontalPodAutoscaler and updates it. Returns the server's representation of the horizontalPodAutoscaler, and an error, if there is any. -func (c *horizontalPodAutoscalers) Update(horizontalPodAutoscaler *v1.HorizontalPodAutoscaler) (result *v1.HorizontalPodAutoscaler, err error) { - result = &v1.HorizontalPodAutoscaler{} - err = c.client.Put(). - Namespace(c.ns). - Resource("horizontalpodautoscalers"). - Name(horizontalPodAutoscaler.Name). - Body(horizontalPodAutoscaler). - Do(). - Into(result) - return -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). - -func (c *horizontalPodAutoscalers) UpdateStatus(horizontalPodAutoscaler *v1.HorizontalPodAutoscaler) (result *v1.HorizontalPodAutoscaler, err error) { - result = &v1.HorizontalPodAutoscaler{} - err = c.client.Put(). - Namespace(c.ns). - Resource("horizontalpodautoscalers"). - Name(horizontalPodAutoscaler.Name). - SubResource("status"). - Body(horizontalPodAutoscaler). - Do(). - Into(result) - return -} - -// Delete takes name of the horizontalPodAutoscaler and deletes it. Returns an error if one occurs. -func (c *horizontalPodAutoscalers) Delete(name string, options *meta_v1.DeleteOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("horizontalpodautoscalers"). - Name(name). - Body(options). - Do(). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *horizontalPodAutoscalers) DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("horizontalpodautoscalers"). - VersionedParams(&listOptions, scheme.ParameterCodec). - Body(options). - Do(). - Error() -} - -// Patch applies the patch and returns the patched horizontalPodAutoscaler. -func (c *horizontalPodAutoscalers) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.HorizontalPodAutoscaler, err error) { - result = &v1.HorizontalPodAutoscaler{} - err = c.client.Patch(pt). - Namespace(c.ns). - Resource("horizontalpodautoscalers"). - SubResource(subresources...). - Name(name). - Body(data). - Do(). - Into(result) - return -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/batch/v1/BUILD b/federation/client/clientset_generated/federation_clientset/typed/batch/v1/BUILD deleted file mode 100644 index de8553722da..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/batch/v1/BUILD +++ /dev/null @@ -1,42 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "batch_client.go", - "doc.go", - "generated_expansion.go", - "job.go", - ], - importpath = "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/batch/v1", - deps = [ - "//federation/client/clientset_generated/federation_clientset/scheme:go_default_library", - "//vendor/k8s.io/api/batch/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/client/clientset_generated/federation_clientset/typed/batch/v1/fake:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/client/clientset_generated/federation_clientset/typed/batch/v1/batch_client.go b/federation/client/clientset_generated/federation_clientset/typed/batch/v1/batch_client.go deleted file mode 100644 index d16249a0b11..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/batch/v1/batch_client.go +++ /dev/null @@ -1,88 +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 v1 - -import ( - v1 "k8s.io/api/batch/v1" - serializer "k8s.io/apimachinery/pkg/runtime/serializer" - rest "k8s.io/client-go/rest" - "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/scheme" -) - -type BatchV1Interface interface { - RESTClient() rest.Interface - JobsGetter -} - -// BatchV1Client is used to interact with features provided by the batch group. -type BatchV1Client struct { - restClient rest.Interface -} - -func (c *BatchV1Client) Jobs(namespace string) JobInterface { - return newJobs(c, namespace) -} - -// NewForConfig creates a new BatchV1Client for the given config. -func NewForConfig(c *rest.Config) (*BatchV1Client, error) { - config := *c - if err := setConfigDefaults(&config); err != nil { - return nil, err - } - client, err := rest.RESTClientFor(&config) - if err != nil { - return nil, err - } - return &BatchV1Client{client}, nil -} - -// NewForConfigOrDie creates a new BatchV1Client for the given config and -// panics if there is an error in the config. -func NewForConfigOrDie(c *rest.Config) *BatchV1Client { - client, err := NewForConfig(c) - if err != nil { - panic(err) - } - return client -} - -// New creates a new BatchV1Client for the given RESTClient. -func New(c rest.Interface) *BatchV1Client { - return &BatchV1Client{c} -} - -func setConfigDefaults(config *rest.Config) error { - gv := v1.SchemeGroupVersion - config.GroupVersion = &gv - config.APIPath = "/apis" - config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs} - - if config.UserAgent == "" { - config.UserAgent = rest.DefaultKubernetesUserAgent() - } - - return nil -} - -// RESTClient returns a RESTClient that is used to communicate -// with API server by this client implementation. -func (c *BatchV1Client) RESTClient() rest.Interface { - if c == nil { - return nil - } - return c.restClient -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/batch/v1/doc.go b/federation/client/clientset_generated/federation_clientset/typed/batch/v1/doc.go deleted file mode 100644 index b6a2a467285..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/batch/v1/doc.go +++ /dev/null @@ -1,18 +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. -*/ - -// This package has the automatically generated typed clients. -package v1 diff --git a/federation/client/clientset_generated/federation_clientset/typed/batch/v1/fake/BUILD b/federation/client/clientset_generated/federation_clientset/typed/batch/v1/fake/BUILD deleted file mode 100644 index 732d32ebabb..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/batch/v1/fake/BUILD +++ /dev/null @@ -1,40 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "doc.go", - "fake_batch_client.go", - "fake_job.go", - ], - importpath = "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/batch/v1/fake", - deps = [ - "//federation/client/clientset_generated/federation_clientset/typed/batch/v1:go_default_library", - "//vendor/k8s.io/api/batch/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - "//vendor/k8s.io/client-go/testing:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/client/clientset_generated/federation_clientset/typed/batch/v1/fake/doc.go b/federation/client/clientset_generated/federation_clientset/typed/batch/v1/fake/doc.go deleted file mode 100644 index c58fac35e4b..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/batch/v1/fake/doc.go +++ /dev/null @@ -1,18 +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 fake has the automatically generated clients. -package fake diff --git a/federation/client/clientset_generated/federation_clientset/typed/batch/v1/fake/fake_batch_client.go b/federation/client/clientset_generated/federation_clientset/typed/batch/v1/fake/fake_batch_client.go deleted file mode 100644 index 332d5d8f4e3..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/batch/v1/fake/fake_batch_client.go +++ /dev/null @@ -1,38 +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 fake - -import ( - rest "k8s.io/client-go/rest" - testing "k8s.io/client-go/testing" - v1 "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/batch/v1" -) - -type FakeBatchV1 struct { - *testing.Fake -} - -func (c *FakeBatchV1) Jobs(namespace string) v1.JobInterface { - return &FakeJobs{c, namespace} -} - -// RESTClient returns a RESTClient that is used to communicate -// with API server by this client implementation. -func (c *FakeBatchV1) RESTClient() rest.Interface { - var ret *rest.RESTClient - return ret -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/batch/v1/fake/fake_job.go b/federation/client/clientset_generated/federation_clientset/typed/batch/v1/fake/fake_job.go deleted file mode 100644 index b51c8f5cd6b..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/batch/v1/fake/fake_job.go +++ /dev/null @@ -1,138 +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 fake - -import ( - batch_v1 "k8s.io/api/batch/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" -) - -// FakeJobs implements JobInterface -type FakeJobs struct { - Fake *FakeBatchV1 - ns string -} - -var jobsResource = schema.GroupVersionResource{Group: "batch", Version: "v1", Resource: "jobs"} - -var jobsKind = schema.GroupVersionKind{Group: "batch", Version: "v1", Kind: "Job"} - -// Get takes name of the job, and returns the corresponding job object, and an error if there is any. -func (c *FakeJobs) Get(name string, options v1.GetOptions) (result *batch_v1.Job, err error) { - obj, err := c.Fake. - Invokes(testing.NewGetAction(jobsResource, c.ns, name), &batch_v1.Job{}) - - if obj == nil { - return nil, err - } - return obj.(*batch_v1.Job), err -} - -// List takes label and field selectors, and returns the list of Jobs that match those selectors. -func (c *FakeJobs) List(opts v1.ListOptions) (result *batch_v1.JobList, err error) { - obj, err := c.Fake. - Invokes(testing.NewListAction(jobsResource, jobsKind, c.ns, opts), &batch_v1.JobList{}) - - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &batch_v1.JobList{} - for _, item := range obj.(*batch_v1.JobList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested jobs. -func (c *FakeJobs) Watch(opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchAction(jobsResource, c.ns, opts)) - -} - -// Create takes the representation of a job and creates it. Returns the server's representation of the job, and an error, if there is any. -func (c *FakeJobs) Create(job *batch_v1.Job) (result *batch_v1.Job, err error) { - obj, err := c.Fake. - Invokes(testing.NewCreateAction(jobsResource, c.ns, job), &batch_v1.Job{}) - - if obj == nil { - return nil, err - } - return obj.(*batch_v1.Job), err -} - -// Update takes the representation of a job and updates it. Returns the server's representation of the job, and an error, if there is any. -func (c *FakeJobs) Update(job *batch_v1.Job) (result *batch_v1.Job, err error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateAction(jobsResource, c.ns, job), &batch_v1.Job{}) - - if obj == nil { - return nil, err - } - return obj.(*batch_v1.Job), err -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeJobs) UpdateStatus(job *batch_v1.Job) (*batch_v1.Job, error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(jobsResource, "status", c.ns, job), &batch_v1.Job{}) - - if obj == nil { - return nil, err - } - return obj.(*batch_v1.Job), err -} - -// Delete takes name of the job and deletes it. Returns an error if one occurs. -func (c *FakeJobs) Delete(name string, options *v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteAction(jobsResource, c.ns, name), &batch_v1.Job{}) - - return err -} - -// DeleteCollection deletes a collection of objects. -func (c *FakeJobs) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(jobsResource, c.ns, listOptions) - - _, err := c.Fake.Invokes(action, &batch_v1.JobList{}) - return err -} - -// Patch applies the patch and returns the patched job. -func (c *FakeJobs) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *batch_v1.Job, err error) { - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(jobsResource, c.ns, name, data, subresources...), &batch_v1.Job{}) - - if obj == nil { - return nil, err - } - return obj.(*batch_v1.Job), err -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/batch/v1/generated_expansion.go b/federation/client/clientset_generated/federation_clientset/typed/batch/v1/generated_expansion.go deleted file mode 100644 index 68d7741fa0b..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/batch/v1/generated_expansion.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 v1 - -type JobExpansion interface{} diff --git a/federation/client/clientset_generated/federation_clientset/typed/batch/v1/job.go b/federation/client/clientset_generated/federation_clientset/typed/batch/v1/job.go deleted file mode 100644 index 74e10550f86..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/batch/v1/job.go +++ /dev/null @@ -1,172 +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 v1 - -import ( - v1 "k8s.io/api/batch/v1" - meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" - scheme "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/scheme" -) - -// JobsGetter has a method to return a JobInterface. -// A group's client should implement this interface. -type JobsGetter interface { - Jobs(namespace string) JobInterface -} - -// JobInterface has methods to work with Job resources. -type JobInterface interface { - Create(*v1.Job) (*v1.Job, error) - Update(*v1.Job) (*v1.Job, error) - UpdateStatus(*v1.Job) (*v1.Job, error) - Delete(name string, options *meta_v1.DeleteOptions) error - DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error - Get(name string, options meta_v1.GetOptions) (*v1.Job, error) - List(opts meta_v1.ListOptions) (*v1.JobList, error) - Watch(opts meta_v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.Job, err error) - JobExpansion -} - -// jobs implements JobInterface -type jobs struct { - client rest.Interface - ns string -} - -// newJobs returns a Jobs -func newJobs(c *BatchV1Client, namespace string) *jobs { - return &jobs{ - client: c.RESTClient(), - ns: namespace, - } -} - -// Get takes name of the job, and returns the corresponding job object, and an error if there is any. -func (c *jobs) Get(name string, options meta_v1.GetOptions) (result *v1.Job, err error) { - result = &v1.Job{} - err = c.client.Get(). - Namespace(c.ns). - Resource("jobs"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of Jobs that match those selectors. -func (c *jobs) List(opts meta_v1.ListOptions) (result *v1.JobList, err error) { - result = &v1.JobList{} - err = c.client.Get(). - Namespace(c.ns). - Resource("jobs"). - VersionedParams(&opts, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested jobs. -func (c *jobs) Watch(opts meta_v1.ListOptions) (watch.Interface, error) { - opts.Watch = true - return c.client.Get(). - Namespace(c.ns). - Resource("jobs"). - VersionedParams(&opts, scheme.ParameterCodec). - Watch() -} - -// Create takes the representation of a job and creates it. Returns the server's representation of the job, and an error, if there is any. -func (c *jobs) Create(job *v1.Job) (result *v1.Job, err error) { - result = &v1.Job{} - err = c.client.Post(). - Namespace(c.ns). - Resource("jobs"). - Body(job). - Do(). - Into(result) - return -} - -// Update takes the representation of a job and updates it. Returns the server's representation of the job, and an error, if there is any. -func (c *jobs) Update(job *v1.Job) (result *v1.Job, err error) { - result = &v1.Job{} - err = c.client.Put(). - Namespace(c.ns). - Resource("jobs"). - Name(job.Name). - Body(job). - Do(). - Into(result) - return -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). - -func (c *jobs) UpdateStatus(job *v1.Job) (result *v1.Job, err error) { - result = &v1.Job{} - err = c.client.Put(). - Namespace(c.ns). - Resource("jobs"). - Name(job.Name). - SubResource("status"). - Body(job). - Do(). - Into(result) - return -} - -// Delete takes name of the job and deletes it. Returns an error if one occurs. -func (c *jobs) Delete(name string, options *meta_v1.DeleteOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("jobs"). - Name(name). - Body(options). - Do(). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *jobs) DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("jobs"). - VersionedParams(&listOptions, scheme.ParameterCodec). - Body(options). - Do(). - Error() -} - -// Patch applies the patch and returns the patched job. -func (c *jobs) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.Job, err error) { - result = &v1.Job{} - err = c.client.Patch(pt). - Namespace(c.ns). - Resource("jobs"). - SubResource(subresources...). - Name(name). - Body(data). - Do(). - Into(result) - return -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/core/v1/BUILD b/federation/client/clientset_generated/federation_clientset/typed/core/v1/BUILD deleted file mode 100644 index 6b2eb308a5b..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/core/v1/BUILD +++ /dev/null @@ -1,47 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "configmap.go", - "core_client.go", - "doc.go", - "event.go", - "generated_expansion.go", - "namespace.go", - "namespace_expansion.go", - "secret.go", - "service.go", - ], - importpath = "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/core/v1", - deps = [ - "//federation/client/clientset_generated/federation_clientset/scheme:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/client/clientset_generated/federation_clientset/typed/core/v1/fake:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/client/clientset_generated/federation_clientset/typed/core/v1/configmap.go b/federation/client/clientset_generated/federation_clientset/typed/core/v1/configmap.go deleted file mode 100644 index d1f66980bf2..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/core/v1/configmap.go +++ /dev/null @@ -1,155 +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 v1 - -import ( - v1 "k8s.io/api/core/v1" - meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" - scheme "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/scheme" -) - -// ConfigMapsGetter has a method to return a ConfigMapInterface. -// A group's client should implement this interface. -type ConfigMapsGetter interface { - ConfigMaps(namespace string) ConfigMapInterface -} - -// ConfigMapInterface has methods to work with ConfigMap resources. -type ConfigMapInterface interface { - Create(*v1.ConfigMap) (*v1.ConfigMap, error) - Update(*v1.ConfigMap) (*v1.ConfigMap, error) - Delete(name string, options *meta_v1.DeleteOptions) error - DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error - Get(name string, options meta_v1.GetOptions) (*v1.ConfigMap, error) - List(opts meta_v1.ListOptions) (*v1.ConfigMapList, error) - Watch(opts meta_v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.ConfigMap, err error) - ConfigMapExpansion -} - -// configMaps implements ConfigMapInterface -type configMaps struct { - client rest.Interface - ns string -} - -// newConfigMaps returns a ConfigMaps -func newConfigMaps(c *CoreV1Client, namespace string) *configMaps { - return &configMaps{ - client: c.RESTClient(), - ns: namespace, - } -} - -// Get takes name of the configMap, and returns the corresponding configMap object, and an error if there is any. -func (c *configMaps) Get(name string, options meta_v1.GetOptions) (result *v1.ConfigMap, err error) { - result = &v1.ConfigMap{} - err = c.client.Get(). - Namespace(c.ns). - Resource("configmaps"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of ConfigMaps that match those selectors. -func (c *configMaps) List(opts meta_v1.ListOptions) (result *v1.ConfigMapList, err error) { - result = &v1.ConfigMapList{} - err = c.client.Get(). - Namespace(c.ns). - Resource("configmaps"). - VersionedParams(&opts, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested configMaps. -func (c *configMaps) Watch(opts meta_v1.ListOptions) (watch.Interface, error) { - opts.Watch = true - return c.client.Get(). - Namespace(c.ns). - Resource("configmaps"). - VersionedParams(&opts, scheme.ParameterCodec). - Watch() -} - -// Create takes the representation of a configMap and creates it. Returns the server's representation of the configMap, and an error, if there is any. -func (c *configMaps) Create(configMap *v1.ConfigMap) (result *v1.ConfigMap, err error) { - result = &v1.ConfigMap{} - err = c.client.Post(). - Namespace(c.ns). - Resource("configmaps"). - Body(configMap). - Do(). - Into(result) - return -} - -// Update takes the representation of a configMap and updates it. Returns the server's representation of the configMap, and an error, if there is any. -func (c *configMaps) Update(configMap *v1.ConfigMap) (result *v1.ConfigMap, err error) { - result = &v1.ConfigMap{} - err = c.client.Put(). - Namespace(c.ns). - Resource("configmaps"). - Name(configMap.Name). - Body(configMap). - Do(). - Into(result) - return -} - -// Delete takes name of the configMap and deletes it. Returns an error if one occurs. -func (c *configMaps) Delete(name string, options *meta_v1.DeleteOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("configmaps"). - Name(name). - Body(options). - Do(). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *configMaps) DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("configmaps"). - VersionedParams(&listOptions, scheme.ParameterCodec). - Body(options). - Do(). - Error() -} - -// Patch applies the patch and returns the patched configMap. -func (c *configMaps) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.ConfigMap, err error) { - result = &v1.ConfigMap{} - err = c.client.Patch(pt). - Namespace(c.ns). - Resource("configmaps"). - SubResource(subresources...). - Name(name). - Body(data). - Do(). - Into(result) - return -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/core/v1/core_client.go b/federation/client/clientset_generated/federation_clientset/typed/core/v1/core_client.go deleted file mode 100644 index a3a96f85045..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/core/v1/core_client.go +++ /dev/null @@ -1,108 +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 v1 - -import ( - v1 "k8s.io/api/core/v1" - serializer "k8s.io/apimachinery/pkg/runtime/serializer" - rest "k8s.io/client-go/rest" - "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/scheme" -) - -type CoreV1Interface interface { - RESTClient() rest.Interface - ConfigMapsGetter - EventsGetter - NamespacesGetter - SecretsGetter - ServicesGetter -} - -// CoreV1Client is used to interact with features provided by the group. -type CoreV1Client struct { - restClient rest.Interface -} - -func (c *CoreV1Client) ConfigMaps(namespace string) ConfigMapInterface { - return newConfigMaps(c, namespace) -} - -func (c *CoreV1Client) Events(namespace string) EventInterface { - return newEvents(c, namespace) -} - -func (c *CoreV1Client) Namespaces() NamespaceInterface { - return newNamespaces(c) -} - -func (c *CoreV1Client) Secrets(namespace string) SecretInterface { - return newSecrets(c, namespace) -} - -func (c *CoreV1Client) Services(namespace string) ServiceInterface { - return newServices(c, namespace) -} - -// NewForConfig creates a new CoreV1Client for the given config. -func NewForConfig(c *rest.Config) (*CoreV1Client, error) { - config := *c - if err := setConfigDefaults(&config); err != nil { - return nil, err - } - client, err := rest.RESTClientFor(&config) - if err != nil { - return nil, err - } - return &CoreV1Client{client}, nil -} - -// NewForConfigOrDie creates a new CoreV1Client for the given config and -// panics if there is an error in the config. -func NewForConfigOrDie(c *rest.Config) *CoreV1Client { - client, err := NewForConfig(c) - if err != nil { - panic(err) - } - return client -} - -// New creates a new CoreV1Client for the given RESTClient. -func New(c rest.Interface) *CoreV1Client { - return &CoreV1Client{c} -} - -func setConfigDefaults(config *rest.Config) error { - gv := v1.SchemeGroupVersion - config.GroupVersion = &gv - config.APIPath = "/api" - config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs} - - if config.UserAgent == "" { - config.UserAgent = rest.DefaultKubernetesUserAgent() - } - - return nil -} - -// RESTClient returns a RESTClient that is used to communicate -// with API server by this client implementation. -func (c *CoreV1Client) RESTClient() rest.Interface { - if c == nil { - return nil - } - return c.restClient -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/core/v1/doc.go b/federation/client/clientset_generated/federation_clientset/typed/core/v1/doc.go deleted file mode 100644 index b6a2a467285..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/core/v1/doc.go +++ /dev/null @@ -1,18 +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. -*/ - -// This package has the automatically generated typed clients. -package v1 diff --git a/federation/client/clientset_generated/federation_clientset/typed/core/v1/event.go b/federation/client/clientset_generated/federation_clientset/typed/core/v1/event.go deleted file mode 100644 index 8c9a91a3481..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/core/v1/event.go +++ /dev/null @@ -1,155 +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 v1 - -import ( - v1 "k8s.io/api/core/v1" - meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" - scheme "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/scheme" -) - -// EventsGetter has a method to return a EventInterface. -// A group's client should implement this interface. -type EventsGetter interface { - Events(namespace string) EventInterface -} - -// EventInterface has methods to work with Event resources. -type EventInterface interface { - Create(*v1.Event) (*v1.Event, error) - Update(*v1.Event) (*v1.Event, error) - Delete(name string, options *meta_v1.DeleteOptions) error - DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error - Get(name string, options meta_v1.GetOptions) (*v1.Event, error) - List(opts meta_v1.ListOptions) (*v1.EventList, error) - Watch(opts meta_v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.Event, err error) - EventExpansion -} - -// events implements EventInterface -type events struct { - client rest.Interface - ns string -} - -// newEvents returns a Events -func newEvents(c *CoreV1Client, namespace string) *events { - return &events{ - client: c.RESTClient(), - ns: namespace, - } -} - -// Get takes name of the event, and returns the corresponding event object, and an error if there is any. -func (c *events) Get(name string, options meta_v1.GetOptions) (result *v1.Event, err error) { - result = &v1.Event{} - err = c.client.Get(). - Namespace(c.ns). - Resource("events"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of Events that match those selectors. -func (c *events) List(opts meta_v1.ListOptions) (result *v1.EventList, err error) { - result = &v1.EventList{} - err = c.client.Get(). - Namespace(c.ns). - Resource("events"). - VersionedParams(&opts, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested events. -func (c *events) Watch(opts meta_v1.ListOptions) (watch.Interface, error) { - opts.Watch = true - return c.client.Get(). - Namespace(c.ns). - Resource("events"). - VersionedParams(&opts, scheme.ParameterCodec). - Watch() -} - -// Create takes the representation of a event and creates it. Returns the server's representation of the event, and an error, if there is any. -func (c *events) Create(event *v1.Event) (result *v1.Event, err error) { - result = &v1.Event{} - err = c.client.Post(). - Namespace(c.ns). - Resource("events"). - Body(event). - Do(). - Into(result) - return -} - -// Update takes the representation of a event and updates it. Returns the server's representation of the event, and an error, if there is any. -func (c *events) Update(event *v1.Event) (result *v1.Event, err error) { - result = &v1.Event{} - err = c.client.Put(). - Namespace(c.ns). - Resource("events"). - Name(event.Name). - Body(event). - Do(). - Into(result) - return -} - -// Delete takes name of the event and deletes it. Returns an error if one occurs. -func (c *events) Delete(name string, options *meta_v1.DeleteOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("events"). - Name(name). - Body(options). - Do(). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *events) DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("events"). - VersionedParams(&listOptions, scheme.ParameterCodec). - Body(options). - Do(). - Error() -} - -// Patch applies the patch and returns the patched event. -func (c *events) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.Event, err error) { - result = &v1.Event{} - err = c.client.Patch(pt). - Namespace(c.ns). - Resource("events"). - SubResource(subresources...). - Name(name). - Body(data). - Do(). - Into(result) - return -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/BUILD b/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/BUILD deleted file mode 100644 index a74bbec58dc..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/BUILD +++ /dev/null @@ -1,45 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "doc.go", - "fake_configmap.go", - "fake_core_client.go", - "fake_event.go", - "fake_namespace.go", - "fake_namespace_expansion.go", - "fake_secret.go", - "fake_service.go", - ], - importpath = "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake", - deps = [ - "//federation/client/clientset_generated/federation_clientset/typed/core/v1:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - "//vendor/k8s.io/client-go/testing:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/doc.go b/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/doc.go deleted file mode 100644 index c58fac35e4b..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/doc.go +++ /dev/null @@ -1,18 +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 fake has the automatically generated clients. -package fake diff --git a/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/fake_configmap.go b/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/fake_configmap.go deleted file mode 100644 index 262a4c5e800..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/fake_configmap.go +++ /dev/null @@ -1,126 +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 fake - -import ( - core_v1 "k8s.io/api/core/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" -) - -// FakeConfigMaps implements ConfigMapInterface -type FakeConfigMaps struct { - Fake *FakeCoreV1 - ns string -} - -var configmapsResource = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "configmaps"} - -var configmapsKind = schema.GroupVersionKind{Group: "", Version: "v1", Kind: "ConfigMap"} - -// Get takes name of the configMap, and returns the corresponding configMap object, and an error if there is any. -func (c *FakeConfigMaps) Get(name string, options v1.GetOptions) (result *core_v1.ConfigMap, err error) { - obj, err := c.Fake. - Invokes(testing.NewGetAction(configmapsResource, c.ns, name), &core_v1.ConfigMap{}) - - if obj == nil { - return nil, err - } - return obj.(*core_v1.ConfigMap), err -} - -// List takes label and field selectors, and returns the list of ConfigMaps that match those selectors. -func (c *FakeConfigMaps) List(opts v1.ListOptions) (result *core_v1.ConfigMapList, err error) { - obj, err := c.Fake. - Invokes(testing.NewListAction(configmapsResource, configmapsKind, c.ns, opts), &core_v1.ConfigMapList{}) - - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &core_v1.ConfigMapList{} - for _, item := range obj.(*core_v1.ConfigMapList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested configMaps. -func (c *FakeConfigMaps) Watch(opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchAction(configmapsResource, c.ns, opts)) - -} - -// Create takes the representation of a configMap and creates it. Returns the server's representation of the configMap, and an error, if there is any. -func (c *FakeConfigMaps) Create(configMap *core_v1.ConfigMap) (result *core_v1.ConfigMap, err error) { - obj, err := c.Fake. - Invokes(testing.NewCreateAction(configmapsResource, c.ns, configMap), &core_v1.ConfigMap{}) - - if obj == nil { - return nil, err - } - return obj.(*core_v1.ConfigMap), err -} - -// Update takes the representation of a configMap and updates it. Returns the server's representation of the configMap, and an error, if there is any. -func (c *FakeConfigMaps) Update(configMap *core_v1.ConfigMap) (result *core_v1.ConfigMap, err error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateAction(configmapsResource, c.ns, configMap), &core_v1.ConfigMap{}) - - if obj == nil { - return nil, err - } - return obj.(*core_v1.ConfigMap), err -} - -// Delete takes name of the configMap and deletes it. Returns an error if one occurs. -func (c *FakeConfigMaps) Delete(name string, options *v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteAction(configmapsResource, c.ns, name), &core_v1.ConfigMap{}) - - return err -} - -// DeleteCollection deletes a collection of objects. -func (c *FakeConfigMaps) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(configmapsResource, c.ns, listOptions) - - _, err := c.Fake.Invokes(action, &core_v1.ConfigMapList{}) - return err -} - -// Patch applies the patch and returns the patched configMap. -func (c *FakeConfigMaps) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core_v1.ConfigMap, err error) { - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(configmapsResource, c.ns, name, data, subresources...), &core_v1.ConfigMap{}) - - if obj == nil { - return nil, err - } - return obj.(*core_v1.ConfigMap), err -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/fake_core_client.go b/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/fake_core_client.go deleted file mode 100644 index 6e6b30cfe14..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/fake_core_client.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 fake - -import ( - rest "k8s.io/client-go/rest" - testing "k8s.io/client-go/testing" - v1 "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/core/v1" -) - -type FakeCoreV1 struct { - *testing.Fake -} - -func (c *FakeCoreV1) ConfigMaps(namespace string) v1.ConfigMapInterface { - return &FakeConfigMaps{c, namespace} -} - -func (c *FakeCoreV1) Events(namespace string) v1.EventInterface { - return &FakeEvents{c, namespace} -} - -func (c *FakeCoreV1) Namespaces() v1.NamespaceInterface { - return &FakeNamespaces{c} -} - -func (c *FakeCoreV1) Secrets(namespace string) v1.SecretInterface { - return &FakeSecrets{c, namespace} -} - -func (c *FakeCoreV1) Services(namespace string) v1.ServiceInterface { - return &FakeServices{c, namespace} -} - -// RESTClient returns a RESTClient that is used to communicate -// with API server by this client implementation. -func (c *FakeCoreV1) RESTClient() rest.Interface { - var ret *rest.RESTClient - return ret -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/fake_event.go b/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/fake_event.go deleted file mode 100644 index 60d6b45a588..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/fake_event.go +++ /dev/null @@ -1,126 +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 fake - -import ( - core_v1 "k8s.io/api/core/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" -) - -// FakeEvents implements EventInterface -type FakeEvents struct { - Fake *FakeCoreV1 - ns string -} - -var eventsResource = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "events"} - -var eventsKind = schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Event"} - -// Get takes name of the event, and returns the corresponding event object, and an error if there is any. -func (c *FakeEvents) Get(name string, options v1.GetOptions) (result *core_v1.Event, err error) { - obj, err := c.Fake. - Invokes(testing.NewGetAction(eventsResource, c.ns, name), &core_v1.Event{}) - - if obj == nil { - return nil, err - } - return obj.(*core_v1.Event), err -} - -// List takes label and field selectors, and returns the list of Events that match those selectors. -func (c *FakeEvents) List(opts v1.ListOptions) (result *core_v1.EventList, err error) { - obj, err := c.Fake. - Invokes(testing.NewListAction(eventsResource, eventsKind, c.ns, opts), &core_v1.EventList{}) - - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &core_v1.EventList{} - for _, item := range obj.(*core_v1.EventList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested events. -func (c *FakeEvents) Watch(opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchAction(eventsResource, c.ns, opts)) - -} - -// Create takes the representation of a event and creates it. Returns the server's representation of the event, and an error, if there is any. -func (c *FakeEvents) Create(event *core_v1.Event) (result *core_v1.Event, err error) { - obj, err := c.Fake. - Invokes(testing.NewCreateAction(eventsResource, c.ns, event), &core_v1.Event{}) - - if obj == nil { - return nil, err - } - return obj.(*core_v1.Event), err -} - -// Update takes the representation of a event and updates it. Returns the server's representation of the event, and an error, if there is any. -func (c *FakeEvents) Update(event *core_v1.Event) (result *core_v1.Event, err error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateAction(eventsResource, c.ns, event), &core_v1.Event{}) - - if obj == nil { - return nil, err - } - return obj.(*core_v1.Event), err -} - -// Delete takes name of the event and deletes it. Returns an error if one occurs. -func (c *FakeEvents) Delete(name string, options *v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteAction(eventsResource, c.ns, name), &core_v1.Event{}) - - return err -} - -// DeleteCollection deletes a collection of objects. -func (c *FakeEvents) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(eventsResource, c.ns, listOptions) - - _, err := c.Fake.Invokes(action, &core_v1.EventList{}) - return err -} - -// Patch applies the patch and returns the patched event. -func (c *FakeEvents) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core_v1.Event, err error) { - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(eventsResource, c.ns, name, data, subresources...), &core_v1.Event{}) - - if obj == nil { - return nil, err - } - return obj.(*core_v1.Event), err -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/fake_namespace.go b/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/fake_namespace.go deleted file mode 100644 index 8c03925a7fe..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/fake_namespace.go +++ /dev/null @@ -1,129 +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 fake - -import ( - core_v1 "k8s.io/api/core/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" -) - -// FakeNamespaces implements NamespaceInterface -type FakeNamespaces struct { - Fake *FakeCoreV1 -} - -var namespacesResource = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "namespaces"} - -var namespacesKind = schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"} - -// Get takes name of the namespace, and returns the corresponding namespace object, and an error if there is any. -func (c *FakeNamespaces) Get(name string, options v1.GetOptions) (result *core_v1.Namespace, err error) { - obj, err := c.Fake. - Invokes(testing.NewRootGetAction(namespacesResource, name), &core_v1.Namespace{}) - if obj == nil { - return nil, err - } - return obj.(*core_v1.Namespace), err -} - -// List takes label and field selectors, and returns the list of Namespaces that match those selectors. -func (c *FakeNamespaces) List(opts v1.ListOptions) (result *core_v1.NamespaceList, err error) { - obj, err := c.Fake. - Invokes(testing.NewRootListAction(namespacesResource, namespacesKind, opts), &core_v1.NamespaceList{}) - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &core_v1.NamespaceList{} - for _, item := range obj.(*core_v1.NamespaceList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested namespaces. -func (c *FakeNamespaces) Watch(opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewRootWatchAction(namespacesResource, opts)) -} - -// Create takes the representation of a namespace and creates it. Returns the server's representation of the namespace, and an error, if there is any. -func (c *FakeNamespaces) Create(namespace *core_v1.Namespace) (result *core_v1.Namespace, err error) { - obj, err := c.Fake. - Invokes(testing.NewRootCreateAction(namespacesResource, namespace), &core_v1.Namespace{}) - if obj == nil { - return nil, err - } - return obj.(*core_v1.Namespace), err -} - -// Update takes the representation of a namespace and updates it. Returns the server's representation of the namespace, and an error, if there is any. -func (c *FakeNamespaces) Update(namespace *core_v1.Namespace) (result *core_v1.Namespace, err error) { - obj, err := c.Fake. - Invokes(testing.NewRootUpdateAction(namespacesResource, namespace), &core_v1.Namespace{}) - if obj == nil { - return nil, err - } - return obj.(*core_v1.Namespace), err -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeNamespaces) UpdateStatus(namespace *core_v1.Namespace) (*core_v1.Namespace, error) { - obj, err := c.Fake. - Invokes(testing.NewRootUpdateSubresourceAction(namespacesResource, "status", namespace), &core_v1.Namespace{}) - if obj == nil { - return nil, err - } - return obj.(*core_v1.Namespace), err -} - -// Delete takes name of the namespace and deletes it. Returns an error if one occurs. -func (c *FakeNamespaces) Delete(name string, options *v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewRootDeleteAction(namespacesResource, name), &core_v1.Namespace{}) - return err -} - -// DeleteCollection deletes a collection of objects. -func (c *FakeNamespaces) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - action := testing.NewRootDeleteCollectionAction(namespacesResource, listOptions) - - _, err := c.Fake.Invokes(action, &core_v1.NamespaceList{}) - return err -} - -// Patch applies the patch and returns the patched namespace. -func (c *FakeNamespaces) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core_v1.Namespace, err error) { - obj, err := c.Fake. - Invokes(testing.NewRootPatchSubresourceAction(namespacesResource, name, data, subresources...), &core_v1.Namespace{}) - if obj == nil { - return nil, err - } - return obj.(*core_v1.Namespace), err -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/fake_namespace_expansion.go b/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/fake_namespace_expansion.go deleted file mode 100644 index 7b2cf605d38..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/fake_namespace_expansion.go +++ /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. -*/ - -package fake - -import ( - "k8s.io/api/core/v1" - core "k8s.io/client-go/testing" -) - -func (c *FakeNamespaces) Finalize(namespace *v1.Namespace) (*v1.Namespace, error) { - action := core.CreateActionImpl{} - action.Verb = "create" - action.Resource = namespacesResource - action.Subresource = "finalize" - action.Object = namespace - - obj, err := c.Fake.Invokes(action, namespace) - if obj == nil { - return nil, err - } - - return obj.(*v1.Namespace), err -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/fake_secret.go b/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/fake_secret.go deleted file mode 100644 index 58c6dc9c6da..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/fake_secret.go +++ /dev/null @@ -1,126 +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 fake - -import ( - core_v1 "k8s.io/api/core/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" -) - -// FakeSecrets implements SecretInterface -type FakeSecrets struct { - Fake *FakeCoreV1 - ns string -} - -var secretsResource = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "secrets"} - -var secretsKind = schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Secret"} - -// Get takes name of the secret, and returns the corresponding secret object, and an error if there is any. -func (c *FakeSecrets) Get(name string, options v1.GetOptions) (result *core_v1.Secret, err error) { - obj, err := c.Fake. - Invokes(testing.NewGetAction(secretsResource, c.ns, name), &core_v1.Secret{}) - - if obj == nil { - return nil, err - } - return obj.(*core_v1.Secret), err -} - -// List takes label and field selectors, and returns the list of Secrets that match those selectors. -func (c *FakeSecrets) List(opts v1.ListOptions) (result *core_v1.SecretList, err error) { - obj, err := c.Fake. - Invokes(testing.NewListAction(secretsResource, secretsKind, c.ns, opts), &core_v1.SecretList{}) - - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &core_v1.SecretList{} - for _, item := range obj.(*core_v1.SecretList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested secrets. -func (c *FakeSecrets) Watch(opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchAction(secretsResource, c.ns, opts)) - -} - -// Create takes the representation of a secret and creates it. Returns the server's representation of the secret, and an error, if there is any. -func (c *FakeSecrets) Create(secret *core_v1.Secret) (result *core_v1.Secret, err error) { - obj, err := c.Fake. - Invokes(testing.NewCreateAction(secretsResource, c.ns, secret), &core_v1.Secret{}) - - if obj == nil { - return nil, err - } - return obj.(*core_v1.Secret), err -} - -// Update takes the representation of a secret and updates it. Returns the server's representation of the secret, and an error, if there is any. -func (c *FakeSecrets) Update(secret *core_v1.Secret) (result *core_v1.Secret, err error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateAction(secretsResource, c.ns, secret), &core_v1.Secret{}) - - if obj == nil { - return nil, err - } - return obj.(*core_v1.Secret), err -} - -// Delete takes name of the secret and deletes it. Returns an error if one occurs. -func (c *FakeSecrets) Delete(name string, options *v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteAction(secretsResource, c.ns, name), &core_v1.Secret{}) - - return err -} - -// DeleteCollection deletes a collection of objects. -func (c *FakeSecrets) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(secretsResource, c.ns, listOptions) - - _, err := c.Fake.Invokes(action, &core_v1.SecretList{}) - return err -} - -// Patch applies the patch and returns the patched secret. -func (c *FakeSecrets) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core_v1.Secret, err error) { - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(secretsResource, c.ns, name, data, subresources...), &core_v1.Secret{}) - - if obj == nil { - return nil, err - } - return obj.(*core_v1.Secret), err -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/fake_service.go b/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/fake_service.go deleted file mode 100644 index c734ff5b56f..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/core/v1/fake/fake_service.go +++ /dev/null @@ -1,138 +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 fake - -import ( - core_v1 "k8s.io/api/core/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" -) - -// FakeServices implements ServiceInterface -type FakeServices struct { - Fake *FakeCoreV1 - ns string -} - -var servicesResource = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "services"} - -var servicesKind = schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Service"} - -// Get takes name of the service, and returns the corresponding service object, and an error if there is any. -func (c *FakeServices) Get(name string, options v1.GetOptions) (result *core_v1.Service, err error) { - obj, err := c.Fake. - Invokes(testing.NewGetAction(servicesResource, c.ns, name), &core_v1.Service{}) - - if obj == nil { - return nil, err - } - return obj.(*core_v1.Service), err -} - -// List takes label and field selectors, and returns the list of Services that match those selectors. -func (c *FakeServices) List(opts v1.ListOptions) (result *core_v1.ServiceList, err error) { - obj, err := c.Fake. - Invokes(testing.NewListAction(servicesResource, servicesKind, c.ns, opts), &core_v1.ServiceList{}) - - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &core_v1.ServiceList{} - for _, item := range obj.(*core_v1.ServiceList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested services. -func (c *FakeServices) Watch(opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchAction(servicesResource, c.ns, opts)) - -} - -// Create takes the representation of a service and creates it. Returns the server's representation of the service, and an error, if there is any. -func (c *FakeServices) Create(service *core_v1.Service) (result *core_v1.Service, err error) { - obj, err := c.Fake. - Invokes(testing.NewCreateAction(servicesResource, c.ns, service), &core_v1.Service{}) - - if obj == nil { - return nil, err - } - return obj.(*core_v1.Service), err -} - -// Update takes the representation of a service and updates it. Returns the server's representation of the service, and an error, if there is any. -func (c *FakeServices) Update(service *core_v1.Service) (result *core_v1.Service, err error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateAction(servicesResource, c.ns, service), &core_v1.Service{}) - - if obj == nil { - return nil, err - } - return obj.(*core_v1.Service), err -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeServices) UpdateStatus(service *core_v1.Service) (*core_v1.Service, error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(servicesResource, "status", c.ns, service), &core_v1.Service{}) - - if obj == nil { - return nil, err - } - return obj.(*core_v1.Service), err -} - -// Delete takes name of the service and deletes it. Returns an error if one occurs. -func (c *FakeServices) Delete(name string, options *v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteAction(servicesResource, c.ns, name), &core_v1.Service{}) - - return err -} - -// DeleteCollection deletes a collection of objects. -func (c *FakeServices) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(servicesResource, c.ns, listOptions) - - _, err := c.Fake.Invokes(action, &core_v1.ServiceList{}) - return err -} - -// Patch applies the patch and returns the patched service. -func (c *FakeServices) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core_v1.Service, err error) { - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(servicesResource, c.ns, name, data, subresources...), &core_v1.Service{}) - - if obj == nil { - return nil, err - } - return obj.(*core_v1.Service), err -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/core/v1/generated_expansion.go b/federation/client/clientset_generated/federation_clientset/typed/core/v1/generated_expansion.go deleted file mode 100644 index 6c1fd71f351..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/core/v1/generated_expansion.go +++ /dev/null @@ -1,25 +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 v1 - -type ConfigMapExpansion interface{} - -type EventExpansion interface{} - -type SecretExpansion interface{} - -type ServiceExpansion interface{} diff --git a/federation/client/clientset_generated/federation_clientset/typed/core/v1/namespace.go b/federation/client/clientset_generated/federation_clientset/typed/core/v1/namespace.go deleted file mode 100644 index 34f16aa6d0c..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/core/v1/namespace.go +++ /dev/null @@ -1,161 +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 v1 - -import ( - v1 "k8s.io/api/core/v1" - meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" - scheme "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/scheme" -) - -// NamespacesGetter has a method to return a NamespaceInterface. -// A group's client should implement this interface. -type NamespacesGetter interface { - Namespaces() NamespaceInterface -} - -// NamespaceInterface has methods to work with Namespace resources. -type NamespaceInterface interface { - Create(*v1.Namespace) (*v1.Namespace, error) - Update(*v1.Namespace) (*v1.Namespace, error) - UpdateStatus(*v1.Namespace) (*v1.Namespace, error) - Delete(name string, options *meta_v1.DeleteOptions) error - DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error - Get(name string, options meta_v1.GetOptions) (*v1.Namespace, error) - List(opts meta_v1.ListOptions) (*v1.NamespaceList, error) - Watch(opts meta_v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.Namespace, err error) - NamespaceExpansion -} - -// namespaces implements NamespaceInterface -type namespaces struct { - client rest.Interface -} - -// newNamespaces returns a Namespaces -func newNamespaces(c *CoreV1Client) *namespaces { - return &namespaces{ - client: c.RESTClient(), - } -} - -// Get takes name of the namespace, and returns the corresponding namespace object, and an error if there is any. -func (c *namespaces) Get(name string, options meta_v1.GetOptions) (result *v1.Namespace, err error) { - result = &v1.Namespace{} - err = c.client.Get(). - Resource("namespaces"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of Namespaces that match those selectors. -func (c *namespaces) List(opts meta_v1.ListOptions) (result *v1.NamespaceList, err error) { - result = &v1.NamespaceList{} - err = c.client.Get(). - Resource("namespaces"). - VersionedParams(&opts, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested namespaces. -func (c *namespaces) Watch(opts meta_v1.ListOptions) (watch.Interface, error) { - opts.Watch = true - return c.client.Get(). - Resource("namespaces"). - VersionedParams(&opts, scheme.ParameterCodec). - Watch() -} - -// Create takes the representation of a namespace and creates it. Returns the server's representation of the namespace, and an error, if there is any. -func (c *namespaces) Create(namespace *v1.Namespace) (result *v1.Namespace, err error) { - result = &v1.Namespace{} - err = c.client.Post(). - Resource("namespaces"). - Body(namespace). - Do(). - Into(result) - return -} - -// Update takes the representation of a namespace and updates it. Returns the server's representation of the namespace, and an error, if there is any. -func (c *namespaces) Update(namespace *v1.Namespace) (result *v1.Namespace, err error) { - result = &v1.Namespace{} - err = c.client.Put(). - Resource("namespaces"). - Name(namespace.Name). - Body(namespace). - Do(). - Into(result) - return -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). - -func (c *namespaces) UpdateStatus(namespace *v1.Namespace) (result *v1.Namespace, err error) { - result = &v1.Namespace{} - err = c.client.Put(). - Resource("namespaces"). - Name(namespace.Name). - SubResource("status"). - Body(namespace). - Do(). - Into(result) - return -} - -// Delete takes name of the namespace and deletes it. Returns an error if one occurs. -func (c *namespaces) Delete(name string, options *meta_v1.DeleteOptions) error { - return c.client.Delete(). - Resource("namespaces"). - Name(name). - Body(options). - Do(). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *namespaces) DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error { - return c.client.Delete(). - Resource("namespaces"). - VersionedParams(&listOptions, scheme.ParameterCodec). - Body(options). - Do(). - Error() -} - -// Patch applies the patch and returns the patched namespace. -func (c *namespaces) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.Namespace, err error) { - result = &v1.Namespace{} - err = c.client.Patch(pt). - Resource("namespaces"). - SubResource(subresources...). - Name(name). - Body(data). - Do(). - Into(result) - return -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/core/v1/namespace_expansion.go b/federation/client/clientset_generated/federation_clientset/typed/core/v1/namespace_expansion.go deleted file mode 100644 index 17effe29c65..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/core/v1/namespace_expansion.go +++ /dev/null @@ -1,31 +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 v1 - -import "k8s.io/api/core/v1" - -// The NamespaceExpansion interface allows manually adding extra methods to the NamespaceInterface. -type NamespaceExpansion interface { - Finalize(item *v1.Namespace) (*v1.Namespace, error) -} - -// Finalize takes the representation of a namespace to update. Returns the server's representation of the namespace, and an error, if it occurs. -func (c *namespaces) Finalize(namespace *v1.Namespace) (result *v1.Namespace, err error) { - result = &v1.Namespace{} - err = c.client.Put().Resource("namespaces").Name(namespace.Name).SubResource("finalize").Body(namespace).Do().Into(result) - return -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/core/v1/secret.go b/federation/client/clientset_generated/federation_clientset/typed/core/v1/secret.go deleted file mode 100644 index 1551a51dedf..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/core/v1/secret.go +++ /dev/null @@ -1,155 +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 v1 - -import ( - v1 "k8s.io/api/core/v1" - meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" - scheme "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/scheme" -) - -// SecretsGetter has a method to return a SecretInterface. -// A group's client should implement this interface. -type SecretsGetter interface { - Secrets(namespace string) SecretInterface -} - -// SecretInterface has methods to work with Secret resources. -type SecretInterface interface { - Create(*v1.Secret) (*v1.Secret, error) - Update(*v1.Secret) (*v1.Secret, error) - Delete(name string, options *meta_v1.DeleteOptions) error - DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error - Get(name string, options meta_v1.GetOptions) (*v1.Secret, error) - List(opts meta_v1.ListOptions) (*v1.SecretList, error) - Watch(opts meta_v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.Secret, err error) - SecretExpansion -} - -// secrets implements SecretInterface -type secrets struct { - client rest.Interface - ns string -} - -// newSecrets returns a Secrets -func newSecrets(c *CoreV1Client, namespace string) *secrets { - return &secrets{ - client: c.RESTClient(), - ns: namespace, - } -} - -// Get takes name of the secret, and returns the corresponding secret object, and an error if there is any. -func (c *secrets) Get(name string, options meta_v1.GetOptions) (result *v1.Secret, err error) { - result = &v1.Secret{} - err = c.client.Get(). - Namespace(c.ns). - Resource("secrets"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of Secrets that match those selectors. -func (c *secrets) List(opts meta_v1.ListOptions) (result *v1.SecretList, err error) { - result = &v1.SecretList{} - err = c.client.Get(). - Namespace(c.ns). - Resource("secrets"). - VersionedParams(&opts, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested secrets. -func (c *secrets) Watch(opts meta_v1.ListOptions) (watch.Interface, error) { - opts.Watch = true - return c.client.Get(). - Namespace(c.ns). - Resource("secrets"). - VersionedParams(&opts, scheme.ParameterCodec). - Watch() -} - -// Create takes the representation of a secret and creates it. Returns the server's representation of the secret, and an error, if there is any. -func (c *secrets) Create(secret *v1.Secret) (result *v1.Secret, err error) { - result = &v1.Secret{} - err = c.client.Post(). - Namespace(c.ns). - Resource("secrets"). - Body(secret). - Do(). - Into(result) - return -} - -// Update takes the representation of a secret and updates it. Returns the server's representation of the secret, and an error, if there is any. -func (c *secrets) Update(secret *v1.Secret) (result *v1.Secret, err error) { - result = &v1.Secret{} - err = c.client.Put(). - Namespace(c.ns). - Resource("secrets"). - Name(secret.Name). - Body(secret). - Do(). - Into(result) - return -} - -// Delete takes name of the secret and deletes it. Returns an error if one occurs. -func (c *secrets) Delete(name string, options *meta_v1.DeleteOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("secrets"). - Name(name). - Body(options). - Do(). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *secrets) DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("secrets"). - VersionedParams(&listOptions, scheme.ParameterCodec). - Body(options). - Do(). - Error() -} - -// Patch applies the patch and returns the patched secret. -func (c *secrets) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.Secret, err error) { - result = &v1.Secret{} - err = c.client.Patch(pt). - Namespace(c.ns). - Resource("secrets"). - SubResource(subresources...). - Name(name). - Body(data). - Do(). - Into(result) - return -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/core/v1/service.go b/federation/client/clientset_generated/federation_clientset/typed/core/v1/service.go deleted file mode 100644 index 47c4ea2407d..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/core/v1/service.go +++ /dev/null @@ -1,172 +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 v1 - -import ( - v1 "k8s.io/api/core/v1" - meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" - scheme "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/scheme" -) - -// ServicesGetter has a method to return a ServiceInterface. -// A group's client should implement this interface. -type ServicesGetter interface { - Services(namespace string) ServiceInterface -} - -// ServiceInterface has methods to work with Service resources. -type ServiceInterface interface { - Create(*v1.Service) (*v1.Service, error) - Update(*v1.Service) (*v1.Service, error) - UpdateStatus(*v1.Service) (*v1.Service, error) - Delete(name string, options *meta_v1.DeleteOptions) error - DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error - Get(name string, options meta_v1.GetOptions) (*v1.Service, error) - List(opts meta_v1.ListOptions) (*v1.ServiceList, error) - Watch(opts meta_v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.Service, err error) - ServiceExpansion -} - -// services implements ServiceInterface -type services struct { - client rest.Interface - ns string -} - -// newServices returns a Services -func newServices(c *CoreV1Client, namespace string) *services { - return &services{ - client: c.RESTClient(), - ns: namespace, - } -} - -// Get takes name of the service, and returns the corresponding service object, and an error if there is any. -func (c *services) Get(name string, options meta_v1.GetOptions) (result *v1.Service, err error) { - result = &v1.Service{} - err = c.client.Get(). - Namespace(c.ns). - Resource("services"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of Services that match those selectors. -func (c *services) List(opts meta_v1.ListOptions) (result *v1.ServiceList, err error) { - result = &v1.ServiceList{} - err = c.client.Get(). - Namespace(c.ns). - Resource("services"). - VersionedParams(&opts, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested services. -func (c *services) Watch(opts meta_v1.ListOptions) (watch.Interface, error) { - opts.Watch = true - return c.client.Get(). - Namespace(c.ns). - Resource("services"). - VersionedParams(&opts, scheme.ParameterCodec). - Watch() -} - -// Create takes the representation of a service and creates it. Returns the server's representation of the service, and an error, if there is any. -func (c *services) Create(service *v1.Service) (result *v1.Service, err error) { - result = &v1.Service{} - err = c.client.Post(). - Namespace(c.ns). - Resource("services"). - Body(service). - Do(). - Into(result) - return -} - -// Update takes the representation of a service and updates it. Returns the server's representation of the service, and an error, if there is any. -func (c *services) Update(service *v1.Service) (result *v1.Service, err error) { - result = &v1.Service{} - err = c.client.Put(). - Namespace(c.ns). - Resource("services"). - Name(service.Name). - Body(service). - Do(). - Into(result) - return -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). - -func (c *services) UpdateStatus(service *v1.Service) (result *v1.Service, err error) { - result = &v1.Service{} - err = c.client.Put(). - Namespace(c.ns). - Resource("services"). - Name(service.Name). - SubResource("status"). - Body(service). - Do(). - Into(result) - return -} - -// Delete takes name of the service and deletes it. Returns an error if one occurs. -func (c *services) Delete(name string, options *meta_v1.DeleteOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("services"). - Name(name). - Body(options). - Do(). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *services) DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("services"). - VersionedParams(&listOptions, scheme.ParameterCodec). - Body(options). - Do(). - Error() -} - -// Patch applies the patch and returns the patched service. -func (c *services) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.Service, err error) { - result = &v1.Service{} - err = c.client.Patch(pt). - Namespace(c.ns). - Resource("services"). - SubResource(subresources...). - Name(name). - Body(data). - Do(). - Into(result) - return -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/BUILD b/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/BUILD deleted file mode 100644 index 045f12f3f6b..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/BUILD +++ /dev/null @@ -1,46 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "daemonset.go", - "deployment.go", - "deployment_expansion.go", - "doc.go", - "extensions_client.go", - "generated_expansion.go", - "ingress.go", - "replicaset.go", - ], - importpath = "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1", - deps = [ - "//federation/client/clientset_generated/federation_clientset/scheme:go_default_library", - "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/daemonset.go b/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/daemonset.go deleted file mode 100644 index f688789d564..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/daemonset.go +++ /dev/null @@ -1,172 +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 v1beta1 - -import ( - v1beta1 "k8s.io/api/extensions/v1beta1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" - scheme "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/scheme" -) - -// DaemonSetsGetter has a method to return a DaemonSetInterface. -// A group's client should implement this interface. -type DaemonSetsGetter interface { - DaemonSets(namespace string) DaemonSetInterface -} - -// DaemonSetInterface has methods to work with DaemonSet resources. -type DaemonSetInterface interface { - Create(*v1beta1.DaemonSet) (*v1beta1.DaemonSet, error) - Update(*v1beta1.DaemonSet) (*v1beta1.DaemonSet, error) - UpdateStatus(*v1beta1.DaemonSet) (*v1beta1.DaemonSet, error) - Delete(name string, options *v1.DeleteOptions) error - DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*v1beta1.DaemonSet, error) - List(opts v1.ListOptions) (*v1beta1.DaemonSetList, error) - Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1beta1.DaemonSet, err error) - DaemonSetExpansion -} - -// daemonSets implements DaemonSetInterface -type daemonSets struct { - client rest.Interface - ns string -} - -// newDaemonSets returns a DaemonSets -func newDaemonSets(c *ExtensionsV1beta1Client, namespace string) *daemonSets { - return &daemonSets{ - client: c.RESTClient(), - ns: namespace, - } -} - -// Get takes name of the daemonSet, and returns the corresponding daemonSet object, and an error if there is any. -func (c *daemonSets) Get(name string, options v1.GetOptions) (result *v1beta1.DaemonSet, err error) { - result = &v1beta1.DaemonSet{} - err = c.client.Get(). - Namespace(c.ns). - Resource("daemonsets"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of DaemonSets that match those selectors. -func (c *daemonSets) List(opts v1.ListOptions) (result *v1beta1.DaemonSetList, err error) { - result = &v1beta1.DaemonSetList{} - err = c.client.Get(). - Namespace(c.ns). - Resource("daemonsets"). - VersionedParams(&opts, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested daemonSets. -func (c *daemonSets) Watch(opts v1.ListOptions) (watch.Interface, error) { - opts.Watch = true - return c.client.Get(). - Namespace(c.ns). - Resource("daemonsets"). - VersionedParams(&opts, scheme.ParameterCodec). - Watch() -} - -// Create takes the representation of a daemonSet and creates it. Returns the server's representation of the daemonSet, and an error, if there is any. -func (c *daemonSets) Create(daemonSet *v1beta1.DaemonSet) (result *v1beta1.DaemonSet, err error) { - result = &v1beta1.DaemonSet{} - err = c.client.Post(). - Namespace(c.ns). - Resource("daemonsets"). - Body(daemonSet). - Do(). - Into(result) - return -} - -// Update takes the representation of a daemonSet and updates it. Returns the server's representation of the daemonSet, and an error, if there is any. -func (c *daemonSets) Update(daemonSet *v1beta1.DaemonSet) (result *v1beta1.DaemonSet, err error) { - result = &v1beta1.DaemonSet{} - err = c.client.Put(). - Namespace(c.ns). - Resource("daemonsets"). - Name(daemonSet.Name). - Body(daemonSet). - Do(). - Into(result) - return -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). - -func (c *daemonSets) UpdateStatus(daemonSet *v1beta1.DaemonSet) (result *v1beta1.DaemonSet, err error) { - result = &v1beta1.DaemonSet{} - err = c.client.Put(). - Namespace(c.ns). - Resource("daemonsets"). - Name(daemonSet.Name). - SubResource("status"). - Body(daemonSet). - Do(). - Into(result) - return -} - -// Delete takes name of the daemonSet and deletes it. Returns an error if one occurs. -func (c *daemonSets) Delete(name string, options *v1.DeleteOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("daemonsets"). - Name(name). - Body(options). - Do(). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *daemonSets) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("daemonsets"). - VersionedParams(&listOptions, scheme.ParameterCodec). - Body(options). - Do(). - Error() -} - -// Patch applies the patch and returns the patched daemonSet. -func (c *daemonSets) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1beta1.DaemonSet, err error) { - result = &v1beta1.DaemonSet{} - err = c.client.Patch(pt). - Namespace(c.ns). - Resource("daemonsets"). - SubResource(subresources...). - Name(name). - Body(data). - Do(). - Into(result) - return -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/deployment.go b/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/deployment.go deleted file mode 100644 index f26630be8f3..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/deployment.go +++ /dev/null @@ -1,203 +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 v1beta1 - -import ( - v1beta1 "k8s.io/api/extensions/v1beta1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" - scheme "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/scheme" -) - -// DeploymentsGetter has a method to return a DeploymentInterface. -// A group's client should implement this interface. -type DeploymentsGetter interface { - Deployments(namespace string) DeploymentInterface -} - -// DeploymentInterface has methods to work with Deployment resources. -type DeploymentInterface interface { - Create(*v1beta1.Deployment) (*v1beta1.Deployment, error) - Update(*v1beta1.Deployment) (*v1beta1.Deployment, error) - UpdateStatus(*v1beta1.Deployment) (*v1beta1.Deployment, error) - Delete(name string, options *v1.DeleteOptions) error - DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*v1beta1.Deployment, error) - List(opts v1.ListOptions) (*v1beta1.DeploymentList, error) - Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1beta1.Deployment, err error) - GetScale(deploymentName string, options v1.GetOptions) (*v1beta1.Scale, error) - UpdateScale(deploymentName string, scale *v1beta1.Scale) (*v1beta1.Scale, error) - - DeploymentExpansion -} - -// deployments implements DeploymentInterface -type deployments struct { - client rest.Interface - ns string -} - -// newDeployments returns a Deployments -func newDeployments(c *ExtensionsV1beta1Client, namespace string) *deployments { - return &deployments{ - client: c.RESTClient(), - ns: namespace, - } -} - -// Get takes name of the deployment, and returns the corresponding deployment object, and an error if there is any. -func (c *deployments) Get(name string, options v1.GetOptions) (result *v1beta1.Deployment, err error) { - result = &v1beta1.Deployment{} - err = c.client.Get(). - Namespace(c.ns). - Resource("deployments"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of Deployments that match those selectors. -func (c *deployments) List(opts v1.ListOptions) (result *v1beta1.DeploymentList, err error) { - result = &v1beta1.DeploymentList{} - err = c.client.Get(). - Namespace(c.ns). - Resource("deployments"). - VersionedParams(&opts, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested deployments. -func (c *deployments) Watch(opts v1.ListOptions) (watch.Interface, error) { - opts.Watch = true - return c.client.Get(). - Namespace(c.ns). - Resource("deployments"). - VersionedParams(&opts, scheme.ParameterCodec). - Watch() -} - -// Create takes the representation of a deployment and creates it. Returns the server's representation of the deployment, and an error, if there is any. -func (c *deployments) Create(deployment *v1beta1.Deployment) (result *v1beta1.Deployment, err error) { - result = &v1beta1.Deployment{} - err = c.client.Post(). - Namespace(c.ns). - Resource("deployments"). - Body(deployment). - Do(). - Into(result) - return -} - -// Update takes the representation of a deployment and updates it. Returns the server's representation of the deployment, and an error, if there is any. -func (c *deployments) Update(deployment *v1beta1.Deployment) (result *v1beta1.Deployment, err error) { - result = &v1beta1.Deployment{} - err = c.client.Put(). - Namespace(c.ns). - Resource("deployments"). - Name(deployment.Name). - Body(deployment). - Do(). - Into(result) - return -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). - -func (c *deployments) UpdateStatus(deployment *v1beta1.Deployment) (result *v1beta1.Deployment, err error) { - result = &v1beta1.Deployment{} - err = c.client.Put(). - Namespace(c.ns). - Resource("deployments"). - Name(deployment.Name). - SubResource("status"). - Body(deployment). - Do(). - Into(result) - return -} - -// Delete takes name of the deployment and deletes it. Returns an error if one occurs. -func (c *deployments) Delete(name string, options *v1.DeleteOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("deployments"). - Name(name). - Body(options). - Do(). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *deployments) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("deployments"). - VersionedParams(&listOptions, scheme.ParameterCodec). - Body(options). - Do(). - Error() -} - -// Patch applies the patch and returns the patched deployment. -func (c *deployments) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1beta1.Deployment, err error) { - result = &v1beta1.Deployment{} - err = c.client.Patch(pt). - Namespace(c.ns). - Resource("deployments"). - SubResource(subresources...). - Name(name). - Body(data). - Do(). - Into(result) - return -} - -// GetScale takes name of the deployment, and returns the corresponding v1beta1.Scale object, and an error if there is any. -func (c *deployments) GetScale(deploymentName string, options v1.GetOptions) (result *v1beta1.Scale, err error) { - result = &v1beta1.Scale{} - err = c.client.Get(). - Namespace(c.ns). - Resource("deployments"). - Name(deploymentName). - SubResource("scale"). - VersionedParams(&options, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// UpdateScale takes the top resource name and the representation of a scale and updates it. Returns the server's representation of the scale, and an error, if there is any. -func (c *deployments) UpdateScale(deploymentName string, scale *v1beta1.Scale) (result *v1beta1.Scale, err error) { - result = &v1beta1.Scale{} - err = c.client.Put(). - Namespace(c.ns). - Resource("deployments"). - Name(deploymentName). - SubResource("scale"). - Body(scale). - Do(). - Into(result) - return -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/deployment_expansion.go b/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/deployment_expansion.go deleted file mode 100644 index 24734be6a6e..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/deployment_expansion.go +++ /dev/null @@ -1,29 +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 v1beta1 - -import "k8s.io/api/extensions/v1beta1" - -// The DeploymentExpansion interface allows manually adding extra methods to the DeploymentInterface. -type DeploymentExpansion interface { - Rollback(*v1beta1.DeploymentRollback) error -} - -// Rollback applied the provided DeploymentRollback to the named deployment in the current namespace. -func (c *deployments) Rollback(deploymentRollback *v1beta1.DeploymentRollback) error { - return c.client.Post().Namespace(c.ns).Resource("deployments").Name(deploymentRollback.Name).SubResource("rollback").Body(deploymentRollback).Do().Error() -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/doc.go b/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/doc.go deleted file mode 100644 index 1b50aa19970..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/doc.go +++ /dev/null @@ -1,18 +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. -*/ - -// This package has the automatically generated typed clients. -package v1beta1 diff --git a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/extensions_client.go b/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/extensions_client.go deleted file mode 100644 index 2f50f3bb02b..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/extensions_client.go +++ /dev/null @@ -1,103 +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 v1beta1 - -import ( - v1beta1 "k8s.io/api/extensions/v1beta1" - serializer "k8s.io/apimachinery/pkg/runtime/serializer" - rest "k8s.io/client-go/rest" - "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/scheme" -) - -type ExtensionsV1beta1Interface interface { - RESTClient() rest.Interface - DaemonSetsGetter - DeploymentsGetter - IngressesGetter - ReplicaSetsGetter -} - -// ExtensionsV1beta1Client is used to interact with features provided by the extensions group. -type ExtensionsV1beta1Client struct { - restClient rest.Interface -} - -func (c *ExtensionsV1beta1Client) DaemonSets(namespace string) DaemonSetInterface { - return newDaemonSets(c, namespace) -} - -func (c *ExtensionsV1beta1Client) Deployments(namespace string) DeploymentInterface { - return newDeployments(c, namespace) -} - -func (c *ExtensionsV1beta1Client) Ingresses(namespace string) IngressInterface { - return newIngresses(c, namespace) -} - -func (c *ExtensionsV1beta1Client) ReplicaSets(namespace string) ReplicaSetInterface { - return newReplicaSets(c, namespace) -} - -// NewForConfig creates a new ExtensionsV1beta1Client for the given config. -func NewForConfig(c *rest.Config) (*ExtensionsV1beta1Client, error) { - config := *c - if err := setConfigDefaults(&config); err != nil { - return nil, err - } - client, err := rest.RESTClientFor(&config) - if err != nil { - return nil, err - } - return &ExtensionsV1beta1Client{client}, nil -} - -// NewForConfigOrDie creates a new ExtensionsV1beta1Client for the given config and -// panics if there is an error in the config. -func NewForConfigOrDie(c *rest.Config) *ExtensionsV1beta1Client { - client, err := NewForConfig(c) - if err != nil { - panic(err) - } - return client -} - -// New creates a new ExtensionsV1beta1Client for the given RESTClient. -func New(c rest.Interface) *ExtensionsV1beta1Client { - return &ExtensionsV1beta1Client{c} -} - -func setConfigDefaults(config *rest.Config) error { - gv := v1beta1.SchemeGroupVersion - config.GroupVersion = &gv - config.APIPath = "/apis" - config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs} - - if config.UserAgent == "" { - config.UserAgent = rest.DefaultKubernetesUserAgent() - } - - return nil -} - -// RESTClient returns a RESTClient that is used to communicate -// with API server by this client implementation. -func (c *ExtensionsV1beta1Client) RESTClient() rest.Interface { - if c == nil { - return nil - } - return c.restClient -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake/BUILD b/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake/BUILD deleted file mode 100644 index 4aed5799bf6..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake/BUILD +++ /dev/null @@ -1,44 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "doc.go", - "fake_daemonset.go", - "fake_deployment.go", - "fake_deployment_expansion.go", - "fake_extensions_client.go", - "fake_ingress.go", - "fake_replicaset.go", - ], - importpath = "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake", - deps = [ - "//federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - "//vendor/k8s.io/client-go/testing:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake/doc.go b/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake/doc.go deleted file mode 100644 index c58fac35e4b..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake/doc.go +++ /dev/null @@ -1,18 +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 fake has the automatically generated clients. -package fake diff --git a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake/fake_daemonset.go b/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake/fake_daemonset.go deleted file mode 100644 index 3a3220a0593..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake/fake_daemonset.go +++ /dev/null @@ -1,138 +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 fake - -import ( - v1beta1 "k8s.io/api/extensions/v1beta1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" -) - -// FakeDaemonSets implements DaemonSetInterface -type FakeDaemonSets struct { - Fake *FakeExtensionsV1beta1 - ns string -} - -var daemonsetsResource = schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "daemonsets"} - -var daemonsetsKind = schema.GroupVersionKind{Group: "extensions", Version: "v1beta1", Kind: "DaemonSet"} - -// Get takes name of the daemonSet, and returns the corresponding daemonSet object, and an error if there is any. -func (c *FakeDaemonSets) Get(name string, options v1.GetOptions) (result *v1beta1.DaemonSet, err error) { - obj, err := c.Fake. - Invokes(testing.NewGetAction(daemonsetsResource, c.ns, name), &v1beta1.DaemonSet{}) - - if obj == nil { - return nil, err - } - return obj.(*v1beta1.DaemonSet), err -} - -// List takes label and field selectors, and returns the list of DaemonSets that match those selectors. -func (c *FakeDaemonSets) List(opts v1.ListOptions) (result *v1beta1.DaemonSetList, err error) { - obj, err := c.Fake. - Invokes(testing.NewListAction(daemonsetsResource, daemonsetsKind, c.ns, opts), &v1beta1.DaemonSetList{}) - - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &v1beta1.DaemonSetList{} - for _, item := range obj.(*v1beta1.DaemonSetList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested daemonSets. -func (c *FakeDaemonSets) Watch(opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchAction(daemonsetsResource, c.ns, opts)) - -} - -// Create takes the representation of a daemonSet and creates it. Returns the server's representation of the daemonSet, and an error, if there is any. -func (c *FakeDaemonSets) Create(daemonSet *v1beta1.DaemonSet) (result *v1beta1.DaemonSet, err error) { - obj, err := c.Fake. - Invokes(testing.NewCreateAction(daemonsetsResource, c.ns, daemonSet), &v1beta1.DaemonSet{}) - - if obj == nil { - return nil, err - } - return obj.(*v1beta1.DaemonSet), err -} - -// Update takes the representation of a daemonSet and updates it. Returns the server's representation of the daemonSet, and an error, if there is any. -func (c *FakeDaemonSets) Update(daemonSet *v1beta1.DaemonSet) (result *v1beta1.DaemonSet, err error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateAction(daemonsetsResource, c.ns, daemonSet), &v1beta1.DaemonSet{}) - - if obj == nil { - return nil, err - } - return obj.(*v1beta1.DaemonSet), err -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeDaemonSets) UpdateStatus(daemonSet *v1beta1.DaemonSet) (*v1beta1.DaemonSet, error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(daemonsetsResource, "status", c.ns, daemonSet), &v1beta1.DaemonSet{}) - - if obj == nil { - return nil, err - } - return obj.(*v1beta1.DaemonSet), err -} - -// Delete takes name of the daemonSet and deletes it. Returns an error if one occurs. -func (c *FakeDaemonSets) Delete(name string, options *v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteAction(daemonsetsResource, c.ns, name), &v1beta1.DaemonSet{}) - - return err -} - -// DeleteCollection deletes a collection of objects. -func (c *FakeDaemonSets) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(daemonsetsResource, c.ns, listOptions) - - _, err := c.Fake.Invokes(action, &v1beta1.DaemonSetList{}) - return err -} - -// Patch applies the patch and returns the patched daemonSet. -func (c *FakeDaemonSets) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1beta1.DaemonSet, err error) { - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(daemonsetsResource, c.ns, name, data, subresources...), &v1beta1.DaemonSet{}) - - if obj == nil { - return nil, err - } - return obj.(*v1beta1.DaemonSet), err -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake/fake_deployment.go b/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake/fake_deployment.go deleted file mode 100644 index d5abace975d..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake/fake_deployment.go +++ /dev/null @@ -1,160 +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 fake - -import ( - v1beta1 "k8s.io/api/extensions/v1beta1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" -) - -// FakeDeployments implements DeploymentInterface -type FakeDeployments struct { - Fake *FakeExtensionsV1beta1 - ns string -} - -var deploymentsResource = schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"} - -var deploymentsKind = schema.GroupVersionKind{Group: "extensions", Version: "v1beta1", Kind: "Deployment"} - -// Get takes name of the deployment, and returns the corresponding deployment object, and an error if there is any. -func (c *FakeDeployments) Get(name string, options v1.GetOptions) (result *v1beta1.Deployment, err error) { - obj, err := c.Fake. - Invokes(testing.NewGetAction(deploymentsResource, c.ns, name), &v1beta1.Deployment{}) - - if obj == nil { - return nil, err - } - return obj.(*v1beta1.Deployment), err -} - -// List takes label and field selectors, and returns the list of Deployments that match those selectors. -func (c *FakeDeployments) List(opts v1.ListOptions) (result *v1beta1.DeploymentList, err error) { - obj, err := c.Fake. - Invokes(testing.NewListAction(deploymentsResource, deploymentsKind, c.ns, opts), &v1beta1.DeploymentList{}) - - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &v1beta1.DeploymentList{} - for _, item := range obj.(*v1beta1.DeploymentList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested deployments. -func (c *FakeDeployments) Watch(opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchAction(deploymentsResource, c.ns, opts)) - -} - -// Create takes the representation of a deployment and creates it. Returns the server's representation of the deployment, and an error, if there is any. -func (c *FakeDeployments) Create(deployment *v1beta1.Deployment) (result *v1beta1.Deployment, err error) { - obj, err := c.Fake. - Invokes(testing.NewCreateAction(deploymentsResource, c.ns, deployment), &v1beta1.Deployment{}) - - if obj == nil { - return nil, err - } - return obj.(*v1beta1.Deployment), err -} - -// Update takes the representation of a deployment and updates it. Returns the server's representation of the deployment, and an error, if there is any. -func (c *FakeDeployments) Update(deployment *v1beta1.Deployment) (result *v1beta1.Deployment, err error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateAction(deploymentsResource, c.ns, deployment), &v1beta1.Deployment{}) - - if obj == nil { - return nil, err - } - return obj.(*v1beta1.Deployment), err -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeDeployments) UpdateStatus(deployment *v1beta1.Deployment) (*v1beta1.Deployment, error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(deploymentsResource, "status", c.ns, deployment), &v1beta1.Deployment{}) - - if obj == nil { - return nil, err - } - return obj.(*v1beta1.Deployment), err -} - -// Delete takes name of the deployment and deletes it. Returns an error if one occurs. -func (c *FakeDeployments) Delete(name string, options *v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteAction(deploymentsResource, c.ns, name), &v1beta1.Deployment{}) - - return err -} - -// DeleteCollection deletes a collection of objects. -func (c *FakeDeployments) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(deploymentsResource, c.ns, listOptions) - - _, err := c.Fake.Invokes(action, &v1beta1.DeploymentList{}) - return err -} - -// Patch applies the patch and returns the patched deployment. -func (c *FakeDeployments) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1beta1.Deployment, err error) { - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(deploymentsResource, c.ns, name, data, subresources...), &v1beta1.Deployment{}) - - if obj == nil { - return nil, err - } - return obj.(*v1beta1.Deployment), err -} - -// GetScale takes name of the deployment, and returns the corresponding scale object, and an error if there is any. -func (c *FakeDeployments) GetScale(deploymentName string, options v1.GetOptions) (result *v1beta1.Scale, err error) { - obj, err := c.Fake. - Invokes(testing.NewGetSubresourceAction(deploymentsResource, c.ns, "scale", deploymentName), &v1beta1.Scale{}) - - if obj == nil { - return nil, err - } - return obj.(*v1beta1.Scale), err -} - -// UpdateScale takes the representation of a scale and updates it. Returns the server's representation of the scale, and an error, if there is any. -func (c *FakeDeployments) UpdateScale(deploymentName string, scale *v1beta1.Scale) (result *v1beta1.Scale, err error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(deploymentsResource, "scale", c.ns, scale), &v1beta1.Scale{}) - - if obj == nil { - return nil, err - } - return obj.(*v1beta1.Scale), err -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake/fake_deployment_expansion.go b/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake/fake_deployment_expansion.go deleted file mode 100644 index af2bc0f713a..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake/fake_deployment_expansion.go +++ /dev/null @@ -1,33 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package fake - -import ( - "k8s.io/api/extensions/v1beta1" - core "k8s.io/client-go/testing" -) - -func (c *FakeDeployments) Rollback(deploymentRollback *v1beta1.DeploymentRollback) error { - action := core.CreateActionImpl{} - action.Verb = "create" - action.Resource = deploymentsResource - action.Subresource = "rollback" - action.Object = deploymentRollback - - _, err := c.Fake.Invokes(action, deploymentRollback) - return err -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake/fake_extensions_client.go b/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake/fake_extensions_client.go deleted file mode 100644 index b6a4cd5b6fa..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake/fake_extensions_client.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 fake - -import ( - rest "k8s.io/client-go/rest" - testing "k8s.io/client-go/testing" - v1beta1 "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1" -) - -type FakeExtensionsV1beta1 struct { - *testing.Fake -} - -func (c *FakeExtensionsV1beta1) DaemonSets(namespace string) v1beta1.DaemonSetInterface { - return &FakeDaemonSets{c, namespace} -} - -func (c *FakeExtensionsV1beta1) Deployments(namespace string) v1beta1.DeploymentInterface { - return &FakeDeployments{c, namespace} -} - -func (c *FakeExtensionsV1beta1) Ingresses(namespace string) v1beta1.IngressInterface { - return &FakeIngresses{c, namespace} -} - -func (c *FakeExtensionsV1beta1) ReplicaSets(namespace string) v1beta1.ReplicaSetInterface { - return &FakeReplicaSets{c, namespace} -} - -// RESTClient returns a RESTClient that is used to communicate -// with API server by this client implementation. -func (c *FakeExtensionsV1beta1) RESTClient() rest.Interface { - var ret *rest.RESTClient - return ret -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake/fake_ingress.go b/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake/fake_ingress.go deleted file mode 100644 index 5a6f93e0e05..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake/fake_ingress.go +++ /dev/null @@ -1,138 +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 fake - -import ( - v1beta1 "k8s.io/api/extensions/v1beta1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" -) - -// FakeIngresses implements IngressInterface -type FakeIngresses struct { - Fake *FakeExtensionsV1beta1 - ns string -} - -var ingressesResource = schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "ingresses"} - -var ingressesKind = schema.GroupVersionKind{Group: "extensions", Version: "v1beta1", Kind: "Ingress"} - -// Get takes name of the ingress, and returns the corresponding ingress object, and an error if there is any. -func (c *FakeIngresses) Get(name string, options v1.GetOptions) (result *v1beta1.Ingress, err error) { - obj, err := c.Fake. - Invokes(testing.NewGetAction(ingressesResource, c.ns, name), &v1beta1.Ingress{}) - - if obj == nil { - return nil, err - } - return obj.(*v1beta1.Ingress), err -} - -// List takes label and field selectors, and returns the list of Ingresses that match those selectors. -func (c *FakeIngresses) List(opts v1.ListOptions) (result *v1beta1.IngressList, err error) { - obj, err := c.Fake. - Invokes(testing.NewListAction(ingressesResource, ingressesKind, c.ns, opts), &v1beta1.IngressList{}) - - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &v1beta1.IngressList{} - for _, item := range obj.(*v1beta1.IngressList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested ingresses. -func (c *FakeIngresses) Watch(opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchAction(ingressesResource, c.ns, opts)) - -} - -// Create takes the representation of a ingress and creates it. Returns the server's representation of the ingress, and an error, if there is any. -func (c *FakeIngresses) Create(ingress *v1beta1.Ingress) (result *v1beta1.Ingress, err error) { - obj, err := c.Fake. - Invokes(testing.NewCreateAction(ingressesResource, c.ns, ingress), &v1beta1.Ingress{}) - - if obj == nil { - return nil, err - } - return obj.(*v1beta1.Ingress), err -} - -// Update takes the representation of a ingress and updates it. Returns the server's representation of the ingress, and an error, if there is any. -func (c *FakeIngresses) Update(ingress *v1beta1.Ingress) (result *v1beta1.Ingress, err error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateAction(ingressesResource, c.ns, ingress), &v1beta1.Ingress{}) - - if obj == nil { - return nil, err - } - return obj.(*v1beta1.Ingress), err -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeIngresses) UpdateStatus(ingress *v1beta1.Ingress) (*v1beta1.Ingress, error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(ingressesResource, "status", c.ns, ingress), &v1beta1.Ingress{}) - - if obj == nil { - return nil, err - } - return obj.(*v1beta1.Ingress), err -} - -// Delete takes name of the ingress and deletes it. Returns an error if one occurs. -func (c *FakeIngresses) Delete(name string, options *v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteAction(ingressesResource, c.ns, name), &v1beta1.Ingress{}) - - return err -} - -// DeleteCollection deletes a collection of objects. -func (c *FakeIngresses) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(ingressesResource, c.ns, listOptions) - - _, err := c.Fake.Invokes(action, &v1beta1.IngressList{}) - return err -} - -// Patch applies the patch and returns the patched ingress. -func (c *FakeIngresses) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1beta1.Ingress, err error) { - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(ingressesResource, c.ns, name, data, subresources...), &v1beta1.Ingress{}) - - if obj == nil { - return nil, err - } - return obj.(*v1beta1.Ingress), err -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake/fake_replicaset.go b/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake/fake_replicaset.go deleted file mode 100644 index e8d22c6b49e..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/fake/fake_replicaset.go +++ /dev/null @@ -1,160 +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 fake - -import ( - v1beta1 "k8s.io/api/extensions/v1beta1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" -) - -// FakeReplicaSets implements ReplicaSetInterface -type FakeReplicaSets struct { - Fake *FakeExtensionsV1beta1 - ns string -} - -var replicasetsResource = schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "replicasets"} - -var replicasetsKind = schema.GroupVersionKind{Group: "extensions", Version: "v1beta1", Kind: "ReplicaSet"} - -// Get takes name of the replicaSet, and returns the corresponding replicaSet object, and an error if there is any. -func (c *FakeReplicaSets) Get(name string, options v1.GetOptions) (result *v1beta1.ReplicaSet, err error) { - obj, err := c.Fake. - Invokes(testing.NewGetAction(replicasetsResource, c.ns, name), &v1beta1.ReplicaSet{}) - - if obj == nil { - return nil, err - } - return obj.(*v1beta1.ReplicaSet), err -} - -// List takes label and field selectors, and returns the list of ReplicaSets that match those selectors. -func (c *FakeReplicaSets) List(opts v1.ListOptions) (result *v1beta1.ReplicaSetList, err error) { - obj, err := c.Fake. - Invokes(testing.NewListAction(replicasetsResource, replicasetsKind, c.ns, opts), &v1beta1.ReplicaSetList{}) - - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &v1beta1.ReplicaSetList{} - for _, item := range obj.(*v1beta1.ReplicaSetList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested replicaSets. -func (c *FakeReplicaSets) Watch(opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchAction(replicasetsResource, c.ns, opts)) - -} - -// Create takes the representation of a replicaSet and creates it. Returns the server's representation of the replicaSet, and an error, if there is any. -func (c *FakeReplicaSets) Create(replicaSet *v1beta1.ReplicaSet) (result *v1beta1.ReplicaSet, err error) { - obj, err := c.Fake. - Invokes(testing.NewCreateAction(replicasetsResource, c.ns, replicaSet), &v1beta1.ReplicaSet{}) - - if obj == nil { - return nil, err - } - return obj.(*v1beta1.ReplicaSet), err -} - -// Update takes the representation of a replicaSet and updates it. Returns the server's representation of the replicaSet, and an error, if there is any. -func (c *FakeReplicaSets) Update(replicaSet *v1beta1.ReplicaSet) (result *v1beta1.ReplicaSet, err error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateAction(replicasetsResource, c.ns, replicaSet), &v1beta1.ReplicaSet{}) - - if obj == nil { - return nil, err - } - return obj.(*v1beta1.ReplicaSet), err -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeReplicaSets) UpdateStatus(replicaSet *v1beta1.ReplicaSet) (*v1beta1.ReplicaSet, error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(replicasetsResource, "status", c.ns, replicaSet), &v1beta1.ReplicaSet{}) - - if obj == nil { - return nil, err - } - return obj.(*v1beta1.ReplicaSet), err -} - -// Delete takes name of the replicaSet and deletes it. Returns an error if one occurs. -func (c *FakeReplicaSets) Delete(name string, options *v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteAction(replicasetsResource, c.ns, name), &v1beta1.ReplicaSet{}) - - return err -} - -// DeleteCollection deletes a collection of objects. -func (c *FakeReplicaSets) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(replicasetsResource, c.ns, listOptions) - - _, err := c.Fake.Invokes(action, &v1beta1.ReplicaSetList{}) - return err -} - -// Patch applies the patch and returns the patched replicaSet. -func (c *FakeReplicaSets) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1beta1.ReplicaSet, err error) { - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(replicasetsResource, c.ns, name, data, subresources...), &v1beta1.ReplicaSet{}) - - if obj == nil { - return nil, err - } - return obj.(*v1beta1.ReplicaSet), err -} - -// GetScale takes name of the replicaSet, and returns the corresponding scale object, and an error if there is any. -func (c *FakeReplicaSets) GetScale(replicaSetName string, options v1.GetOptions) (result *v1beta1.Scale, err error) { - obj, err := c.Fake. - Invokes(testing.NewGetSubresourceAction(replicasetsResource, c.ns, "scale", replicaSetName), &v1beta1.Scale{}) - - if obj == nil { - return nil, err - } - return obj.(*v1beta1.Scale), err -} - -// UpdateScale takes the representation of a scale and updates it. Returns the server's representation of the scale, and an error, if there is any. -func (c *FakeReplicaSets) UpdateScale(replicaSetName string, scale *v1beta1.Scale) (result *v1beta1.Scale, err error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(replicasetsResource, "scale", c.ns, scale), &v1beta1.Scale{}) - - if obj == nil { - return nil, err - } - return obj.(*v1beta1.Scale), err -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/generated_expansion.go b/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/generated_expansion.go deleted file mode 100644 index e67c4e30250..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/generated_expansion.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. -*/ - -package v1beta1 - -type DaemonSetExpansion interface{} - -type IngressExpansion interface{} - -type ReplicaSetExpansion interface{} diff --git a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/ingress.go b/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/ingress.go deleted file mode 100644 index 2b5e1f071e6..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/ingress.go +++ /dev/null @@ -1,172 +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 v1beta1 - -import ( - v1beta1 "k8s.io/api/extensions/v1beta1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" - scheme "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/scheme" -) - -// IngressesGetter has a method to return a IngressInterface. -// A group's client should implement this interface. -type IngressesGetter interface { - Ingresses(namespace string) IngressInterface -} - -// IngressInterface has methods to work with Ingress resources. -type IngressInterface interface { - Create(*v1beta1.Ingress) (*v1beta1.Ingress, error) - Update(*v1beta1.Ingress) (*v1beta1.Ingress, error) - UpdateStatus(*v1beta1.Ingress) (*v1beta1.Ingress, error) - Delete(name string, options *v1.DeleteOptions) error - DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*v1beta1.Ingress, error) - List(opts v1.ListOptions) (*v1beta1.IngressList, error) - Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1beta1.Ingress, err error) - IngressExpansion -} - -// ingresses implements IngressInterface -type ingresses struct { - client rest.Interface - ns string -} - -// newIngresses returns a Ingresses -func newIngresses(c *ExtensionsV1beta1Client, namespace string) *ingresses { - return &ingresses{ - client: c.RESTClient(), - ns: namespace, - } -} - -// Get takes name of the ingress, and returns the corresponding ingress object, and an error if there is any. -func (c *ingresses) Get(name string, options v1.GetOptions) (result *v1beta1.Ingress, err error) { - result = &v1beta1.Ingress{} - err = c.client.Get(). - Namespace(c.ns). - Resource("ingresses"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of Ingresses that match those selectors. -func (c *ingresses) List(opts v1.ListOptions) (result *v1beta1.IngressList, err error) { - result = &v1beta1.IngressList{} - err = c.client.Get(). - Namespace(c.ns). - Resource("ingresses"). - VersionedParams(&opts, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested ingresses. -func (c *ingresses) Watch(opts v1.ListOptions) (watch.Interface, error) { - opts.Watch = true - return c.client.Get(). - Namespace(c.ns). - Resource("ingresses"). - VersionedParams(&opts, scheme.ParameterCodec). - Watch() -} - -// Create takes the representation of a ingress and creates it. Returns the server's representation of the ingress, and an error, if there is any. -func (c *ingresses) Create(ingress *v1beta1.Ingress) (result *v1beta1.Ingress, err error) { - result = &v1beta1.Ingress{} - err = c.client.Post(). - Namespace(c.ns). - Resource("ingresses"). - Body(ingress). - Do(). - Into(result) - return -} - -// Update takes the representation of a ingress and updates it. Returns the server's representation of the ingress, and an error, if there is any. -func (c *ingresses) Update(ingress *v1beta1.Ingress) (result *v1beta1.Ingress, err error) { - result = &v1beta1.Ingress{} - err = c.client.Put(). - Namespace(c.ns). - Resource("ingresses"). - Name(ingress.Name). - Body(ingress). - Do(). - Into(result) - return -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). - -func (c *ingresses) UpdateStatus(ingress *v1beta1.Ingress) (result *v1beta1.Ingress, err error) { - result = &v1beta1.Ingress{} - err = c.client.Put(). - Namespace(c.ns). - Resource("ingresses"). - Name(ingress.Name). - SubResource("status"). - Body(ingress). - Do(). - Into(result) - return -} - -// Delete takes name of the ingress and deletes it. Returns an error if one occurs. -func (c *ingresses) Delete(name string, options *v1.DeleteOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("ingresses"). - Name(name). - Body(options). - Do(). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *ingresses) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("ingresses"). - VersionedParams(&listOptions, scheme.ParameterCodec). - Body(options). - Do(). - Error() -} - -// Patch applies the patch and returns the patched ingress. -func (c *ingresses) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1beta1.Ingress, err error) { - result = &v1beta1.Ingress{} - err = c.client.Patch(pt). - Namespace(c.ns). - Resource("ingresses"). - SubResource(subresources...). - Name(name). - Body(data). - Do(). - Into(result) - return -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/replicaset.go b/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/replicaset.go deleted file mode 100644 index 39038780739..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1/replicaset.go +++ /dev/null @@ -1,203 +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 v1beta1 - -import ( - v1beta1 "k8s.io/api/extensions/v1beta1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" - scheme "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/scheme" -) - -// ReplicaSetsGetter has a method to return a ReplicaSetInterface. -// A group's client should implement this interface. -type ReplicaSetsGetter interface { - ReplicaSets(namespace string) ReplicaSetInterface -} - -// ReplicaSetInterface has methods to work with ReplicaSet resources. -type ReplicaSetInterface interface { - Create(*v1beta1.ReplicaSet) (*v1beta1.ReplicaSet, error) - Update(*v1beta1.ReplicaSet) (*v1beta1.ReplicaSet, error) - UpdateStatus(*v1beta1.ReplicaSet) (*v1beta1.ReplicaSet, error) - Delete(name string, options *v1.DeleteOptions) error - DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*v1beta1.ReplicaSet, error) - List(opts v1.ListOptions) (*v1beta1.ReplicaSetList, error) - Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1beta1.ReplicaSet, err error) - GetScale(replicaSetName string, options v1.GetOptions) (*v1beta1.Scale, error) - UpdateScale(replicaSetName string, scale *v1beta1.Scale) (*v1beta1.Scale, error) - - ReplicaSetExpansion -} - -// replicaSets implements ReplicaSetInterface -type replicaSets struct { - client rest.Interface - ns string -} - -// newReplicaSets returns a ReplicaSets -func newReplicaSets(c *ExtensionsV1beta1Client, namespace string) *replicaSets { - return &replicaSets{ - client: c.RESTClient(), - ns: namespace, - } -} - -// Get takes name of the replicaSet, and returns the corresponding replicaSet object, and an error if there is any. -func (c *replicaSets) Get(name string, options v1.GetOptions) (result *v1beta1.ReplicaSet, err error) { - result = &v1beta1.ReplicaSet{} - err = c.client.Get(). - Namespace(c.ns). - Resource("replicasets"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of ReplicaSets that match those selectors. -func (c *replicaSets) List(opts v1.ListOptions) (result *v1beta1.ReplicaSetList, err error) { - result = &v1beta1.ReplicaSetList{} - err = c.client.Get(). - Namespace(c.ns). - Resource("replicasets"). - VersionedParams(&opts, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested replicaSets. -func (c *replicaSets) Watch(opts v1.ListOptions) (watch.Interface, error) { - opts.Watch = true - return c.client.Get(). - Namespace(c.ns). - Resource("replicasets"). - VersionedParams(&opts, scheme.ParameterCodec). - Watch() -} - -// Create takes the representation of a replicaSet and creates it. Returns the server's representation of the replicaSet, and an error, if there is any. -func (c *replicaSets) Create(replicaSet *v1beta1.ReplicaSet) (result *v1beta1.ReplicaSet, err error) { - result = &v1beta1.ReplicaSet{} - err = c.client.Post(). - Namespace(c.ns). - Resource("replicasets"). - Body(replicaSet). - Do(). - Into(result) - return -} - -// Update takes the representation of a replicaSet and updates it. Returns the server's representation of the replicaSet, and an error, if there is any. -func (c *replicaSets) Update(replicaSet *v1beta1.ReplicaSet) (result *v1beta1.ReplicaSet, err error) { - result = &v1beta1.ReplicaSet{} - err = c.client.Put(). - Namespace(c.ns). - Resource("replicasets"). - Name(replicaSet.Name). - Body(replicaSet). - Do(). - Into(result) - return -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). - -func (c *replicaSets) UpdateStatus(replicaSet *v1beta1.ReplicaSet) (result *v1beta1.ReplicaSet, err error) { - result = &v1beta1.ReplicaSet{} - err = c.client.Put(). - Namespace(c.ns). - Resource("replicasets"). - Name(replicaSet.Name). - SubResource("status"). - Body(replicaSet). - Do(). - Into(result) - return -} - -// Delete takes name of the replicaSet and deletes it. Returns an error if one occurs. -func (c *replicaSets) Delete(name string, options *v1.DeleteOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("replicasets"). - Name(name). - Body(options). - Do(). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *replicaSets) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("replicasets"). - VersionedParams(&listOptions, scheme.ParameterCodec). - Body(options). - Do(). - Error() -} - -// Patch applies the patch and returns the patched replicaSet. -func (c *replicaSets) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1beta1.ReplicaSet, err error) { - result = &v1beta1.ReplicaSet{} - err = c.client.Patch(pt). - Namespace(c.ns). - Resource("replicasets"). - SubResource(subresources...). - Name(name). - Body(data). - Do(). - Into(result) - return -} - -// GetScale takes name of the replicaSet, and returns the corresponding v1beta1.Scale object, and an error if there is any. -func (c *replicaSets) GetScale(replicaSetName string, options v1.GetOptions) (result *v1beta1.Scale, err error) { - result = &v1beta1.Scale{} - err = c.client.Get(). - Namespace(c.ns). - Resource("replicasets"). - Name(replicaSetName). - SubResource("scale"). - VersionedParams(&options, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// UpdateScale takes the top resource name and the representation of a scale and updates it. Returns the server's representation of the scale, and an error, if there is any. -func (c *replicaSets) UpdateScale(replicaSetName string, scale *v1beta1.Scale) (result *v1beta1.Scale, err error) { - result = &v1beta1.Scale{} - err = c.client.Put(). - Namespace(c.ns). - Resource("replicasets"). - Name(replicaSetName). - SubResource("scale"). - Body(scale). - Do(). - Into(result) - return -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/BUILD b/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/BUILD deleted file mode 100644 index f9668f20566..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/BUILD +++ /dev/null @@ -1,42 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "cluster.go", - "doc.go", - "federation_client.go", - "generated_expansion.go", - ], - importpath = "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1", - deps = [ - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/client/clientset_generated/federation_clientset/scheme:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/fake:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/cluster.go b/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/cluster.go deleted file mode 100644 index d02abdf672b..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/cluster.go +++ /dev/null @@ -1,161 +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 v1beta1 - -import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" - v1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1" - scheme "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/scheme" -) - -// ClustersGetter has a method to return a ClusterInterface. -// A group's client should implement this interface. -type ClustersGetter interface { - Clusters() ClusterInterface -} - -// ClusterInterface has methods to work with Cluster resources. -type ClusterInterface interface { - Create(*v1beta1.Cluster) (*v1beta1.Cluster, error) - Update(*v1beta1.Cluster) (*v1beta1.Cluster, error) - UpdateStatus(*v1beta1.Cluster) (*v1beta1.Cluster, error) - Delete(name string, options *v1.DeleteOptions) error - DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*v1beta1.Cluster, error) - List(opts v1.ListOptions) (*v1beta1.ClusterList, error) - Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1beta1.Cluster, err error) - ClusterExpansion -} - -// clusters implements ClusterInterface -type clusters struct { - client rest.Interface -} - -// newClusters returns a Clusters -func newClusters(c *FederationV1beta1Client) *clusters { - return &clusters{ - client: c.RESTClient(), - } -} - -// Get takes name of the cluster, and returns the corresponding cluster object, and an error if there is any. -func (c *clusters) Get(name string, options v1.GetOptions) (result *v1beta1.Cluster, err error) { - result = &v1beta1.Cluster{} - err = c.client.Get(). - Resource("clusters"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of Clusters that match those selectors. -func (c *clusters) List(opts v1.ListOptions) (result *v1beta1.ClusterList, err error) { - result = &v1beta1.ClusterList{} - err = c.client.Get(). - Resource("clusters"). - VersionedParams(&opts, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested clusters. -func (c *clusters) Watch(opts v1.ListOptions) (watch.Interface, error) { - opts.Watch = true - return c.client.Get(). - Resource("clusters"). - VersionedParams(&opts, scheme.ParameterCodec). - Watch() -} - -// Create takes the representation of a cluster and creates it. Returns the server's representation of the cluster, and an error, if there is any. -func (c *clusters) Create(cluster *v1beta1.Cluster) (result *v1beta1.Cluster, err error) { - result = &v1beta1.Cluster{} - err = c.client.Post(). - Resource("clusters"). - Body(cluster). - Do(). - Into(result) - return -} - -// Update takes the representation of a cluster and updates it. Returns the server's representation of the cluster, and an error, if there is any. -func (c *clusters) Update(cluster *v1beta1.Cluster) (result *v1beta1.Cluster, err error) { - result = &v1beta1.Cluster{} - err = c.client.Put(). - Resource("clusters"). - Name(cluster.Name). - Body(cluster). - Do(). - Into(result) - return -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). - -func (c *clusters) UpdateStatus(cluster *v1beta1.Cluster) (result *v1beta1.Cluster, err error) { - result = &v1beta1.Cluster{} - err = c.client.Put(). - Resource("clusters"). - Name(cluster.Name). - SubResource("status"). - Body(cluster). - Do(). - Into(result) - return -} - -// Delete takes name of the cluster and deletes it. Returns an error if one occurs. -func (c *clusters) Delete(name string, options *v1.DeleteOptions) error { - return c.client.Delete(). - Resource("clusters"). - Name(name). - Body(options). - Do(). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *clusters) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - return c.client.Delete(). - Resource("clusters"). - VersionedParams(&listOptions, scheme.ParameterCodec). - Body(options). - Do(). - Error() -} - -// Patch applies the patch and returns the patched cluster. -func (c *clusters) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1beta1.Cluster, err error) { - result = &v1beta1.Cluster{} - err = c.client.Patch(pt). - Resource("clusters"). - SubResource(subresources...). - Name(name). - Body(data). - Do(). - Into(result) - return -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/doc.go b/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/doc.go deleted file mode 100644 index 1b50aa19970..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/doc.go +++ /dev/null @@ -1,18 +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. -*/ - -// This package has the automatically generated typed clients. -package v1beta1 diff --git a/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/fake/BUILD b/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/fake/BUILD deleted file mode 100644 index c8735ad587f..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/fake/BUILD +++ /dev/null @@ -1,40 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "doc.go", - "fake_cluster.go", - "fake_federation_client.go", - ], - importpath = "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/fake", - deps = [ - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - "//vendor/k8s.io/client-go/testing:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/fake/doc.go b/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/fake/doc.go deleted file mode 100644 index c58fac35e4b..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/fake/doc.go +++ /dev/null @@ -1,18 +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 fake has the automatically generated clients. -package fake diff --git a/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/fake/fake_cluster.go b/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/fake/fake_cluster.go deleted file mode 100644 index 94478a8c8aa..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/fake/fake_cluster.go +++ /dev/null @@ -1,129 +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 fake - -import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" - v1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1" -) - -// FakeClusters implements ClusterInterface -type FakeClusters struct { - Fake *FakeFederationV1beta1 -} - -var clustersResource = schema.GroupVersionResource{Group: "federation", Version: "v1beta1", Resource: "clusters"} - -var clustersKind = schema.GroupVersionKind{Group: "federation", Version: "v1beta1", Kind: "Cluster"} - -// Get takes name of the cluster, and returns the corresponding cluster object, and an error if there is any. -func (c *FakeClusters) Get(name string, options v1.GetOptions) (result *v1beta1.Cluster, err error) { - obj, err := c.Fake. - Invokes(testing.NewRootGetAction(clustersResource, name), &v1beta1.Cluster{}) - if obj == nil { - return nil, err - } - return obj.(*v1beta1.Cluster), err -} - -// List takes label and field selectors, and returns the list of Clusters that match those selectors. -func (c *FakeClusters) List(opts v1.ListOptions) (result *v1beta1.ClusterList, err error) { - obj, err := c.Fake. - Invokes(testing.NewRootListAction(clustersResource, clustersKind, opts), &v1beta1.ClusterList{}) - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &v1beta1.ClusterList{} - for _, item := range obj.(*v1beta1.ClusterList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested clusters. -func (c *FakeClusters) Watch(opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewRootWatchAction(clustersResource, opts)) -} - -// Create takes the representation of a cluster and creates it. Returns the server's representation of the cluster, and an error, if there is any. -func (c *FakeClusters) Create(cluster *v1beta1.Cluster) (result *v1beta1.Cluster, err error) { - obj, err := c.Fake. - Invokes(testing.NewRootCreateAction(clustersResource, cluster), &v1beta1.Cluster{}) - if obj == nil { - return nil, err - } - return obj.(*v1beta1.Cluster), err -} - -// Update takes the representation of a cluster and updates it. Returns the server's representation of the cluster, and an error, if there is any. -func (c *FakeClusters) Update(cluster *v1beta1.Cluster) (result *v1beta1.Cluster, err error) { - obj, err := c.Fake. - Invokes(testing.NewRootUpdateAction(clustersResource, cluster), &v1beta1.Cluster{}) - if obj == nil { - return nil, err - } - return obj.(*v1beta1.Cluster), err -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeClusters) UpdateStatus(cluster *v1beta1.Cluster) (*v1beta1.Cluster, error) { - obj, err := c.Fake. - Invokes(testing.NewRootUpdateSubresourceAction(clustersResource, "status", cluster), &v1beta1.Cluster{}) - if obj == nil { - return nil, err - } - return obj.(*v1beta1.Cluster), err -} - -// Delete takes name of the cluster and deletes it. Returns an error if one occurs. -func (c *FakeClusters) Delete(name string, options *v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewRootDeleteAction(clustersResource, name), &v1beta1.Cluster{}) - return err -} - -// DeleteCollection deletes a collection of objects. -func (c *FakeClusters) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - action := testing.NewRootDeleteCollectionAction(clustersResource, listOptions) - - _, err := c.Fake.Invokes(action, &v1beta1.ClusterList{}) - return err -} - -// Patch applies the patch and returns the patched cluster. -func (c *FakeClusters) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1beta1.Cluster, err error) { - obj, err := c.Fake. - Invokes(testing.NewRootPatchSubresourceAction(clustersResource, name, data, subresources...), &v1beta1.Cluster{}) - if obj == nil { - return nil, err - } - return obj.(*v1beta1.Cluster), err -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/fake/fake_federation_client.go b/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/fake/fake_federation_client.go deleted file mode 100644 index 73edbff85a3..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/fake/fake_federation_client.go +++ /dev/null @@ -1,38 +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 fake - -import ( - rest "k8s.io/client-go/rest" - testing "k8s.io/client-go/testing" - v1beta1 "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1" -) - -type FakeFederationV1beta1 struct { - *testing.Fake -} - -func (c *FakeFederationV1beta1) Clusters() v1beta1.ClusterInterface { - return &FakeClusters{c} -} - -// RESTClient returns a RESTClient that is used to communicate -// with API server by this client implementation. -func (c *FakeFederationV1beta1) RESTClient() rest.Interface { - var ret *rest.RESTClient - return ret -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/federation_client.go b/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/federation_client.go deleted file mode 100644 index e5c8d1d0409..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/federation_client.go +++ /dev/null @@ -1,88 +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 v1beta1 - -import ( - serializer "k8s.io/apimachinery/pkg/runtime/serializer" - rest "k8s.io/client-go/rest" - v1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1" - "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/scheme" -) - -type FederationV1beta1Interface interface { - RESTClient() rest.Interface - ClustersGetter -} - -// FederationV1beta1Client is used to interact with features provided by the federation group. -type FederationV1beta1Client struct { - restClient rest.Interface -} - -func (c *FederationV1beta1Client) Clusters() ClusterInterface { - return newClusters(c) -} - -// NewForConfig creates a new FederationV1beta1Client for the given config. -func NewForConfig(c *rest.Config) (*FederationV1beta1Client, error) { - config := *c - if err := setConfigDefaults(&config); err != nil { - return nil, err - } - client, err := rest.RESTClientFor(&config) - if err != nil { - return nil, err - } - return &FederationV1beta1Client{client}, nil -} - -// NewForConfigOrDie creates a new FederationV1beta1Client for the given config and -// panics if there is an error in the config. -func NewForConfigOrDie(c *rest.Config) *FederationV1beta1Client { - client, err := NewForConfig(c) - if err != nil { - panic(err) - } - return client -} - -// New creates a new FederationV1beta1Client for the given RESTClient. -func New(c rest.Interface) *FederationV1beta1Client { - return &FederationV1beta1Client{c} -} - -func setConfigDefaults(config *rest.Config) error { - gv := v1beta1.SchemeGroupVersion - config.GroupVersion = &gv - config.APIPath = "/apis" - config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs} - - if config.UserAgent == "" { - config.UserAgent = rest.DefaultKubernetesUserAgent() - } - - return nil -} - -// RESTClient returns a RESTClient that is used to communicate -// with API server by this client implementation. -func (c *FederationV1beta1Client) RESTClient() rest.Interface { - if c == nil { - return nil - } - return c.restClient -} diff --git a/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/generated_expansion.go b/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/generated_expansion.go deleted file mode 100644 index fca6b514936..00000000000 --- a/federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1/generated_expansion.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 v1beta1 - -type ClusterExpansion interface{} diff --git a/federation/cluster/BUILD b/federation/cluster/BUILD deleted file mode 100644 index 7e76248ad95..00000000000 --- a/federation/cluster/BUILD +++ /dev/null @@ -1,14 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/cluster/common.sh b/federation/cluster/common.sh deleted file mode 100644 index dba2354b4df..00000000000 --- a/federation/cluster/common.sh +++ /dev/null @@ -1,118 +0,0 @@ -# Copyright 2014 The Kubernetes Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# required: -# KUBE_ROOT: path of the root of the Kubernetes repository - -: "${KUBE_ROOT?Must set KUBE_ROOT env var}" - -# Provides the kubeconfig-federation-context() function -source "${KUBE_ROOT}/cluster/kube-util.sh" - -# For `kube::log::status` function -source "${KUBE_ROOT}/cluster/lib/logging.sh" - -# kubefed configuration -FEDERATION_NAME="${FEDERATION_NAME:-e2e-federation}" -FEDERATION_NAMESPACE=${FEDERATION_NAMESPACE:-federation-system} -FEDERATION_KUBE_CONTEXT="${FEDERATION_KUBE_CONTEXT:-${FEDERATION_NAME}}" -FEDERATION_USE_PV_FOR_ETCD=${FEDERATION_USE_PV_FOR_ETCD:-false} -HOST_CLUSTER_ZONE="${FEDERATION_HOST_CLUSTER_ZONE:-}" -# If $HOST_CLUSTER_ZONE isn't specified, arbitrarily choose -# last zone as the host cluster zone. -if [[ -z "${HOST_CLUSTER_ZONE}" ]]; then - E2E_ZONES_ARR=(${E2E_ZONES:-}) - if [[ ${#E2E_ZONES_ARR[@]} > 0 ]]; then - HOST_CLUSTER_ZONE=${E2E_ZONES_ARR[-1]} - fi -fi - -HOST_CLUSTER_CONTEXT="${FEDERATION_HOST_CLUSTER_CONTEXT:-}" -if [[ -z "${HOST_CLUSTER_CONTEXT}" ]]; then - # Sets ${CLUSTER_CONTEXT} - if [[ -z "${HOST_CLUSTER_ZONE:-}" ]]; then - echo "At least one of FEDERATION_HOST_CLUSTER_CONTEXT, FEDERATION_HOST_CLUSTER_ZONE or E2E_ZONES is required." - exit 1 - fi - kubeconfig-federation-context "${HOST_CLUSTER_ZONE:-}" - HOST_CLUSTER_CONTEXT="${CLUSTER_CONTEXT}" -fi - -function federation_cluster_contexts() { - local -r contexts=$("${KUBE_ROOT}/cluster/kubectl.sh" config get-contexts -o name) - federation_contexts=() - for context in ${contexts}; do - # Skip federation context - if [[ "${context}" == "${FEDERATION_KUBE_CONTEXT}" ]]; then - continue - fi - # Skip contexts not beginning with "federation" - if [[ "${context}" != federation* ]]; then - continue - fi - federation_contexts+=("${context}") - done - echo ${federation_contexts[@]:-} -} - - -host_kubectl="${KUBE_ROOT}/cluster/kubectl.sh --namespace=${FEDERATION_NAMESPACE}" - -function cleanup-federation-api-objects { - # This is a cleanup function. We cannot stop on errors here. So disable - # errexit in this function. - set +o errexit - - echo "Cleaning Federation control plane objects" - kube::log::status "Removing namespace \"${FEDERATION_NAMESPACE}\" from \"${FEDERATION_KUBE_CONTEXT}\"" - # Try deleting until the namespace is completely gone. - while $host_kubectl --context="${FEDERATION_KUBE_CONTEXT}" delete namespace "${FEDERATION_NAMESPACE}" >/dev/null 2>&1; do - # It is usually slower to remove a namespace because it involves - # performing a cascading deletion of all the resources in the - # namespace. So we sleep a little longer than other resources - # before retrying - sleep 5 - done - kube::log::status "Removed namespace \"${FEDERATION_NAMESPACE}\" from \"${FEDERATION_KUBE_CONTEXT}\"" - - # This is a big hammer. We get rid of federation-system namespace from - # all the clusters - for context in $(federation_cluster_contexts); do - ( - local -r role="federation-controller-manager:${FEDERATION_NAME}-${context}-${HOST_CLUSTER_CONTEXT}" - kube::log::status "Removing namespace \"${FEDERATION_NAMESPACE}\", cluster role \"${role}\" and cluster role binding \"${role}\" from \"${context}\"" - # Try deleting until the namespace is completely gone. - while $host_kubectl --context="${context}" delete namespace "${FEDERATION_NAMESPACE}" >/dev/null 2>&1; do - # It is usually slower to remove a namespace because it involves - # performing a cascading deletion of all the resources in the - # namespace. So we sleep a little longer than other resources - # before retrying - sleep 5 - done - kube::log::status "Removed namespace \"${FEDERATION_NAMESPACE}\" from \"${context}\"" - - while $host_kubectl --context="${context}" delete clusterrole "${role}" >/dev/null 2>&1; do - sleep 2 - done - kube::log::status "Removed cluster role \"${role}\" from \"${context}\"" - - while $host_kubectl --context="${context}" delete clusterrolebinding "${role}" >/dev/null 2>&1; do - sleep 2 - done - kube::log::status "Removed cluster role binding \"${role}\" from \"${context}\"" - ) & - done - wait - set -o errexit -} diff --git a/federation/cluster/federation-down.sh b/federation/cluster/federation-down.sh deleted file mode 100755 index dfbe63edae9..00000000000 --- a/federation/cluster/federation-down.sh +++ /dev/null @@ -1,75 +0,0 @@ -#!/bin/bash - -# Copyright 2014 The Kubernetes Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -o errexit -set -o nounset -set -o pipefail - -KUBE_ROOT=$(readlink -m $(dirname "${BASH_SOURCE}")/../../) - -# For $FEDERATION_NAME, $FEDERATION_NAMESPACE, $FEDERATION_KUBE_CONTEXT, -# and $HOST_CLUSTER_CONTEXT. -source "${KUBE_ROOT}/federation/cluster/common.sh" - -# federation_clusters returns a list of all the clusters in -# federation, if at all the federation control plane exists -# and there are any clusters registered. -function federation_clusters() { - if clusters=$("${KUBE_ROOT}/cluster/kubectl.sh" \ - --context="${FEDERATION_KUBE_CONTEXT}" \ - -o jsonpath --template '{.items[*].metadata.name}' \ - get clusters) ; then - echo ${clusters} - return - fi - echo "" -} - -# unjoin_clusters unjoins all the clusters from federation. -function unjoin_clusters() { - # Unjoin only those clusters that are registered with the - # given federation. This is slightly different than - # joining clusters where we join all the clusters in the - # current kubeconfig with the "federation" prefix. - for context in $(federation_clusters); do - kube::log::status "Unjoining cluster \"${context}\" from federation \"${FEDERATION_NAME}\"" - - "${KUBE_ROOT}/federation/develop/kubefed.sh" unjoin \ - "${context}" \ - --federation-system-namespace=${FEDERATION_NAMESPACE} \ - --context="${FEDERATION_KUBE_CONTEXT}" \ - --host-cluster-context="${HOST_CLUSTER_CONTEXT}" \ - --v=4 - done -} - -unjoin_clusters - -if cleanup-federation-api-objects; then - # TODO(madhusudancs): This is an arbitrary amount of sleep to give - # Kubernetes clusters enough time to delete the underlying cloud - # provider resources corresponding to the Kubernetes resources we - # deleted as part of the test tear downs. It is shameful that we - # are doing this, but this is just a bandage to stop the bleeding. - # Please don't use this pattern anywhere. Remove this when proper - # cloud provider cleanups are implemented in the individual test - # `AfterEach` blocks. - # Also, we wait only if the cleanup succeeds. - kube::log::status "Waiting for 2 minutes to allow controllers to clean up federation components..." - sleep 2m -else - echo "Couldn't cleanup federation api objects" -fi diff --git a/federation/cluster/federation-up.sh b/federation/cluster/federation-up.sh deleted file mode 100755 index 47f985732c5..00000000000 --- a/federation/cluster/federation-up.sh +++ /dev/null @@ -1,138 +0,0 @@ -#!/bin/bash - -# Copyright 2014 The Kubernetes Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -o errexit -set -o nounset -set -o pipefail - -# This script is only used for e2e tests! Don't use it in production! -# This is also a temporary bridge to slowly switch over everything to -# federation/develop.sh. Carefully moving things step-by-step, ensuring -# things don't break. -# TODO(madhusudancs): Remove this script and its dependencies. - - -KUBE_ROOT=$(dirname "${BASH_SOURCE}")/../.. -# For $FEDERATION_NAME, $FEDERATION_NAMESPACE, $FEDERATION_KUBE_CONTEXT, -# $HOST_CLUSTER_CONTEXT and $FEDERATION_USE_PV_FOR_ETCD. -source "${KUBE_ROOT}/federation/cluster/common.sh" - -DNS_ZONE_NAME="${FEDERATION_DNS_ZONE_NAME:-}" -DNS_PROVIDER="${FEDERATION_DNS_PROVIDER:-google-clouddns}" - -# get_version returns the version in KUBERNETES_RELEASE or defaults to the -# value in the federation `versions` file. -# TODO(madhusudancs): This is a duplicate of the function in -# federation/develop/develop.sh with a minor difference. This -# function tries to default to the version information in -# _output/federation/versions file where as the one in develop.sh -# tries to default to the version in the kubernetes versions file. -# These functions should be consolidated to read the version from -# kubernetes version defs file. -function get_version() { - local -r versions_file="${KUBE_ROOT}/_output/federation/versions" - - if [[ -n "${KUBERNETES_RELEASE:-}" ]]; then - echo "${KUBERNETES_RELEASE//+/_}" - return - fi - - if [[ ! -f "${versions_file}" ]]; then - echo "Couldn't determine the release version: neither the " \ - "KUBERNETES_RELEASE environment variable is set, nor does " \ - "the versions file exist at ${versions_file}" - exit 1 - fi - - # Read the version back from the versions file if no version is given. - local -r kube_version="$(cat "${versions_file}" | python -c '\ -import json, sys;\ -print json.load(sys.stdin)["KUBE_VERSION"]')" - - echo "${kube_version//+/_}" -} - -function wait_for_rbac() { - # The very first thing that kubefed does when it comes up is run RBAC API - # discovery. If it doesn't appear to be available, issue 'get role' to ensure - # that kubectl updates its cache. - ${KUBE_ROOT}/cluster/kubectl.sh get role - local i=1 - local timeout=60 - while [[ ${i} -le ${timeout} ]]; do - if [[ "$(${KUBE_ROOT}/cluster/kubectl.sh api-versions)" =~ "rbac.authorization.k8s.io/" ]]; then - break - fi - ${KUBE_ROOT}/cluster/kubectl.sh get role - sleep 1 - i=$((i+1)) - done - if [[ ${i} -gt ${timeout} ]]; then - kube::log::status "rbac.authorization.k8s.io API group not available after at least ${timeout} seconds:" - kube::log::status "$(${KUBE_ROOT}/cluster/kubectl.sh api-versions)" - exit 123 - fi - kube::log::status "rbac.authorization.k8s.io API group is available" -} - -# Initializes the control plane. -# TODO(madhusudancs): Move this to federation/develop.sh. -function init() { - kube::log::status "Deploying federation control plane for ${FEDERATION_NAME} in cluster ${HOST_CLUSTER_CONTEXT}" - - local -r project="${KUBE_PROJECT:-${PROJECT:-}}" - local -r kube_registry="${KUBE_REGISTRY:-gcr.io/${project}}" - local -r kube_version="$(get_version)" - - kube::log::status "DNS_ZONE_NAME: \"${DNS_ZONE_NAME}\", DNS_PROVIDER: \"${DNS_PROVIDER}\"" - kube::log::status "Image: \"${kube_registry}/hyperkube-amd64:${kube_version}\"" - - wait_for_rbac - - # Send INT after 20m and KILL 1m after that if process is still alive. - timeout --signal=INT --kill-after=1m 20m \ - "${KUBE_ROOT}/federation/develop/kubefed.sh" init \ - "${FEDERATION_NAME}" \ - --federation-system-namespace=${FEDERATION_NAMESPACE} \ - --host-cluster-context="${HOST_CLUSTER_CONTEXT}" \ - --dns-zone-name="${DNS_ZONE_NAME}" \ - --dns-provider="${DNS_PROVIDER}" \ - --image="${kube_registry}/hyperkube-amd64:${kube_version}" \ - --apiserver-enable-basic-auth=true \ - --apiserver-enable-token-auth=true \ - --apiserver-arg-overrides="--runtime-config=api/all=true,--v=4" \ - --controllermanager-arg-overrides="--v=4" \ - --etcd-persistent-storage=${FEDERATION_USE_PV_FOR_ETCD} \ - --v=4 -} - -# join_clusters joins the clusters in the local kubeconfig to federation. The clusters -# and their kubeconfig entries in the local kubeconfig are created while deploying clusters, i.e. when kube-up is run. -function join_clusters() { - for context in $(federation_cluster_contexts); do - kube::log::status "Joining cluster with name '${context}' to federation with name '${FEDERATION_NAME}'" - - "${KUBE_ROOT}/federation/develop/kubefed.sh" join \ - "${context}" \ - --federation-system-namespace=${FEDERATION_NAMESPACE} \ - --host-cluster-context="${HOST_CLUSTER_CONTEXT}" \ - --context="${FEDERATION_KUBE_CONTEXT}" \ - --v=4 - done -} - -init -join_clusters diff --git a/federation/cluster/log-dump.sh b/federation/cluster/log-dump.sh deleted file mode 100755 index 45e0e2a176f..00000000000 --- a/federation/cluster/log-dump.sh +++ /dev/null @@ -1,89 +0,0 @@ -#!/bin/bash - -# Copyright 2017 The Kubernetes Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Call this to dump all Federation pod logs into the folder specified in $1 -# (defaults to _artifacts). - -set -o errexit -set -o nounset -set -o pipefail - -# For FEDERATION_NAMESPACE -KUBE_ROOT=$(dirname "${BASH_SOURCE}")/../.. -source "${KUBE_ROOT}/federation/cluster/common.sh" - -readonly REPORT_DIR="${1:-_artifacts}" -OUTPUT_DIR="${REPORT_DIR}/federation" - -# Dumps logs for all pods in a federation. -function dump_federation_pod_logs() { - local -r federation_pod_names_string="$(kubectl get pods -l 'app=federated-cluster' --namespace=${FEDERATION_NAMESPACE} -o name)" - if [[ -z "${federation_pod_names_string}" ]]; then - return - fi - - local -r federation_pod_names=(${federation_pod_names_string}) - for pod_name in ${federation_pod_names[@]}; do - # The API server pod has two containers - if [[ "${pod_name}" == *apiserver* ]]; then - dump_apiserver_pod_logs "${pod_name}" - continue - fi - - kubectl logs "${pod_name}" --namespace="${FEDERATION_NAMESPACE}" \ - >"${OUTPUT_DIR}/${pod_name#pods/}.log" - done -} - -# Dumps logs from all containers in an API server pod. -# Arguments: -# - the name of the API server pod, with a pods/ prefix. -function dump_apiserver_pod_logs() { - local -r apiserver_pod_containers=(apiserver etcd) - for container in ${apiserver_pod_containers[@]}; do - kubectl logs "${1}" -c "${container}" --namespace="${FEDERATION_NAMESPACE}" \ - >"${OUTPUT_DIR}/${1#pods/}-${container}.log" - done -} - -# Dumps logs from all containers in the DNS pods. -# TODO: This currently only grabs DNS pod logs from the host cluster. It should -# grab those logs from all clusters in the federation. -function dump_dns_pod_logs() { - local -r dns_pod_names_string="$(kubectl get pods -l 'k8s-app=kube-dns' --namespace=kube-system -o name)" - if [[ -z "${dns_pod_names_string}" ]]; then - return - fi - - local -r dns_pod_names=(${dns_pod_names_string}) - local -r dns_pod_containers=(kubedns dnsmasq sidecar) - - for pod_name in ${dns_pod_names[@]}; do - # As of 3/2017, the only pod that matches the kube-dns label is kube-dns, and - # it has three containers. - for container in ${dns_pod_containers[@]}; do - kubectl logs "${pod_name}" -c "${container}" --namespace=kube-system \ - >"${OUTPUT_DIR}/${pod_name#pods/}-${container}.log" - done - done -} - - -echo "Dumping Federation and DNS pod logs to ${REPORT_DIR}" -mkdir -p "${OUTPUT_DIR}" - -dump_federation_pod_logs -dump_dns_pod_logs diff --git a/federation/cluster/upgrade.sh b/federation/cluster/upgrade.sh deleted file mode 100755 index cf8a66b561c..00000000000 --- a/federation/cluster/upgrade.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash - -# Copyright 2017 The Kubernetes Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -o errexit -set -o nounset -set -o pipefail - -KUBE_ROOT=$(dirname "${BASH_SOURCE}")/../.. -# For $FEDERATION_NAME, $FEDERATION_NAMESPACE, $HOST_CLUSTER_CONTEXT, -source "${KUBE_ROOT}/federation/cluster/common.sh" - -KUBE_VERSION="${1}" - -host_kubectl="${KUBE_ROOT}/cluster/kubectl.sh --context=${HOST_CLUSTER_CONTEXT} --namespace=${FEDERATION_NAMESPACE}" - -function upgrade() { - local -r project="${KUBE_PROJECT:-${PROJECT:-}}" - local -r kube_registry="${KUBE_REGISTRY:-gcr.io/${project}}" - local -r image_version="${kube_registry}/hyperkube-amd64:${KUBE_VERSION}" - - kube::log::status "Upgrading federation control plane ${FEDERATION_NAME} with image ${image_version}" - - # Upgrade apiserver image - ${host_kubectl} set image deployment/federation-apiserver apiserver=${image_version} - - # Upgrade controller-manager image - ${host_kubectl} set image deployment/federation-controller-manager controller-manager=${image_version} -} - -upgrade diff --git a/federation/cmd/federation-apiserver/BUILD b/federation/cmd/federation-apiserver/BUILD deleted file mode 100644 index 7944bd736b1..00000000000 --- a/federation/cmd/federation-apiserver/BUILD +++ /dev/null @@ -1,44 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_binary", - "go_library", -) - -go_binary( - name = "federation-apiserver", - importpath = "k8s.io/kubernetes/federation/cmd/federation-apiserver", - library = ":go_default_library", -) - -go_library( - name = "go_default_library", - srcs = ["apiserver.go"], - importpath = "k8s.io/kubernetes/federation/cmd/federation-apiserver", - deps = [ - "//federation/cmd/federation-apiserver/app:go_default_library", - "//federation/cmd/federation-apiserver/app/options:go_default_library", - "//pkg/version/verflag:go_default_library", - "//vendor/github.com/spf13/pflag:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library", - "//vendor/k8s.io/apiserver/pkg/util/logs:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/cmd/federation-apiserver/app:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/cmd/federation-apiserver/OWNERS b/federation/cmd/federation-apiserver/OWNERS deleted file mode 100644 index 7d859665735..00000000000 --- a/federation/cmd/federation-apiserver/OWNERS +++ /dev/null @@ -1,10 +0,0 @@ -approvers: -- deads2k -- lavalamp -- smarterclayton -- nikhiljindal -reviewers: -- lavalamp -- smarterclayton -- nikhiljindal -- deads2k diff --git a/federation/cmd/federation-apiserver/apiserver.go b/federation/cmd/federation-apiserver/apiserver.go deleted file mode 100644 index 3423fd96e56..00000000000 --- a/federation/cmd/federation-apiserver/apiserver.go +++ /dev/null @@ -1,53 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// apiserver is the main api server and master for the cluster. -// it is responsible for serving the cluster management API. -package main - -import ( - "fmt" - "math/rand" - "os" - "time" - - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/apiserver/pkg/util/flag" - "k8s.io/apiserver/pkg/util/logs" - "k8s.io/kubernetes/federation/cmd/federation-apiserver/app" - "k8s.io/kubernetes/federation/cmd/federation-apiserver/app/options" - "k8s.io/kubernetes/pkg/version/verflag" - - "github.com/spf13/pflag" -) - -func main() { - rand.Seed(time.Now().UTC().UnixNano()) - - s := options.NewServerRunOptions() - s.AddFlags(pflag.CommandLine) - - flag.InitFlags() - logs.InitLogs() - defer logs.FlushLogs() - - verflag.PrintAndExitIfRequested() - - if err := app.Run(s, wait.NeverStop); err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) - os.Exit(1) - } -} diff --git a/federation/cmd/federation-apiserver/app/BUILD b/federation/cmd/federation-apiserver/app/BUILD deleted file mode 100644 index 469cfe45c21..00000000000 --- a/federation/cmd/federation-apiserver/app/BUILD +++ /dev/null @@ -1,104 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "autoscaling.go", - "batch.go", - "core.go", - "extensions.go", - "federation.go", - "install.go", - "plugins.go", - "server.go", - ], - importpath = "k8s.io/kubernetes/federation/cmd/federation-apiserver/app", - deps = [ - "//federation/apis/core:go_default_library", - "//federation/apis/core/install:go_default_library", - "//federation/apis/core/v1:go_default_library", - "//federation/apis/federation:go_default_library", - "//federation/apis/federation/install:go_default_library", - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/cmd/federation-apiserver/app/options:go_default_library", - "//federation/plugin/pkg/admission/schedulingpolicy:go_default_library", - "//federation/registry/cluster/etcd:go_default_library", - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", - "//pkg/apis/autoscaling:go_default_library", - "//pkg/apis/autoscaling/install:go_default_library", - "//pkg/apis/batch:go_default_library", - "//pkg/apis/batch/install:go_default_library", - "//pkg/apis/extensions:go_default_library", - "//pkg/apis/extensions/install:go_default_library", - "//pkg/client/clientset_generated/internalclientset:go_default_library", - "//pkg/client/informers/informers_generated/internalversion:go_default_library", - "//pkg/cloudprovider/providers:go_default_library", - "//pkg/generated/openapi:go_default_library", - "//pkg/kubeapiserver:go_default_library", - "//pkg/kubeapiserver/admission:go_default_library", - "//pkg/kubeapiserver/options:go_default_library", - "//pkg/kubeapiserver/server:go_default_library", - "//pkg/quota/install:go_default_library", - "//pkg/registry/autoscaling/horizontalpodautoscaler/storage:go_default_library", - "//pkg/registry/batch/job/storage:go_default_library", - "//pkg/registry/cachesize:go_default_library", - "//pkg/registry/core/configmap/storage:go_default_library", - "//pkg/registry/core/event/storage:go_default_library", - "//pkg/registry/core/namespace/storage:go_default_library", - "//pkg/registry/core/secret/storage:go_default_library", - "//pkg/registry/core/service/storage:go_default_library", - "//pkg/registry/extensions/daemonset/storage:go_default_library", - "//pkg/registry/extensions/deployment/storage:go_default_library", - "//pkg/registry/extensions/ingress/storage:go_default_library", - "//pkg/registry/extensions/replicaset/storage:go_default_library", - "//pkg/routes:go_default_library", - "//pkg/version:go_default_library", - "//plugin/pkg/admission/admit:go_default_library", - "//plugin/pkg/admission/deny:go_default_library", - "//plugin/pkg/admission/gc:go_default_library", - "//vendor/github.com/go-openapi/spec:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/github.com/spf13/cobra:go_default_library", - "//vendor/github.com/spf13/pflag:go_default_library", - "//vendor/k8s.io/api/apps/v1beta2:go_default_library", - "//vendor/k8s.io/api/autoscaling/v1:go_default_library", - "//vendor/k8s.io/api/batch/v1:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", - "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", - "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", - "//vendor/k8s.io/apiserver/pkg/server:go_default_library", - "//vendor/k8s.io/apiserver/pkg/server/filters:go_default_library", - "//vendor/k8s.io/apiserver/pkg/server/options:go_default_library", - "//vendor/k8s.io/apiserver/pkg/server/storage:go_default_library", - "//vendor/k8s.io/client-go/informers:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/kube-openapi/pkg/common:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/cmd/federation-apiserver/app/options:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/cmd/federation-apiserver/app/autoscaling.go b/federation/cmd/federation-apiserver/app/autoscaling.go deleted file mode 100644 index 4a8589a4f26..00000000000 --- a/federation/cmd/federation-apiserver/app/autoscaling.go +++ /dev/null @@ -1,61 +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 app - -import ( - "github.com/golang/glog" - autoscalingv1 "k8s.io/api/autoscaling/v1" - "k8s.io/apiserver/pkg/registry/generic" - "k8s.io/apiserver/pkg/registry/rest" - genericapiserver "k8s.io/apiserver/pkg/server" - "k8s.io/apiserver/pkg/server/storage" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/autoscaling" - _ "k8s.io/kubernetes/pkg/apis/autoscaling/install" - hpastorage "k8s.io/kubernetes/pkg/registry/autoscaling/horizontalpodautoscaler/storage" -) - -func installAutoscalingAPIs(g *genericapiserver.GenericAPIServer, optsGetter generic.RESTOptionsGetter, apiResourceConfigSource storage.APIResourceConfigSource) { - hpaStorageFn := func() map[string]rest.Storage { - hpaStorage, hpaStatusStorage := hpastorage.NewREST(optsGetter) - return map[string]rest.Storage{ - "horizontalpodautoscalers": hpaStorage, - "horizontalpodautoscalers/status": hpaStatusStorage, - } - } - resourcesStorageMap := map[string]getResourcesStorageFunc{ - "horizontalpodautoscalers": hpaStorageFn, - } - shouldInstallGroup, resources := enabledResources(autoscalingv1.SchemeGroupVersion, resourcesStorageMap, apiResourceConfigSource) - if !shouldInstallGroup { - return - } - autoscalingGroupMeta := api.Registry.GroupOrDie(autoscaling.GroupName) - apiGroupInfo := genericapiserver.APIGroupInfo{ - GroupMeta: *autoscalingGroupMeta, - VersionedResourcesStorageMap: map[string]map[string]rest.Storage{ - "v1": resources, - }, - OptionsExternalVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion, - Scheme: api.Scheme, - ParameterCodec: api.ParameterCodec, - NegotiatedSerializer: api.Codecs, - } - if err := g.InstallAPIGroup(&apiGroupInfo); err != nil { - glog.Fatalf("Error in registering group versions: %v", err) - } -} diff --git a/federation/cmd/federation-apiserver/app/batch.go b/federation/cmd/federation-apiserver/app/batch.go deleted file mode 100644 index 5d589ce11b2..00000000000 --- a/federation/cmd/federation-apiserver/app/batch.go +++ /dev/null @@ -1,61 +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 app - -import ( - "github.com/golang/glog" - batchv1 "k8s.io/api/batch/v1" - "k8s.io/apiserver/pkg/registry/generic" - "k8s.io/apiserver/pkg/registry/rest" - genericapiserver "k8s.io/apiserver/pkg/server" - "k8s.io/apiserver/pkg/server/storage" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/batch" - _ "k8s.io/kubernetes/pkg/apis/batch/install" - jobstorage "k8s.io/kubernetes/pkg/registry/batch/job/storage" -) - -func installBatchAPIs(g *genericapiserver.GenericAPIServer, optsGetter generic.RESTOptionsGetter, apiResourceConfigSource storage.APIResourceConfigSource) { - jobsStorageFn := func() map[string]rest.Storage { - jobStorage := jobstorage.NewStorage(optsGetter) - return map[string]rest.Storage{ - "jobs": jobStorage.Job, - "jobs/status": jobStorage.Status, - } - } - resourcesStorageMap := map[string]getResourcesStorageFunc{ - "jobs": jobsStorageFn, - } - shouldInstallGroup, resources := enabledResources(batchv1.SchemeGroupVersion, resourcesStorageMap, apiResourceConfigSource) - if !shouldInstallGroup { - return - } - batchGroupMeta := api.Registry.GroupOrDie(batch.GroupName) - apiGroupInfo := genericapiserver.APIGroupInfo{ - GroupMeta: *batchGroupMeta, - VersionedResourcesStorageMap: map[string]map[string]rest.Storage{ - "v1": resources, - }, - OptionsExternalVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion, - Scheme: api.Scheme, - ParameterCodec: api.ParameterCodec, - NegotiatedSerializer: api.Codecs, - } - if err := g.InstallAPIGroup(&apiGroupInfo); err != nil { - glog.Fatalf("Error in registering group versions: %v", err) - } -} diff --git a/federation/cmd/federation-apiserver/app/core.go b/federation/cmd/federation-apiserver/app/core.go deleted file mode 100644 index 6282530f8a6..00000000000 --- a/federation/cmd/federation-apiserver/app/core.go +++ /dev/null @@ -1,104 +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 app - -import ( - "github.com/golang/glog" - - // HACK to ensure that rest mapper from pkg/api is registered for groupName="". - // This is required because both pkg/api/install and federation/apis/core/install - // are installing their respective groupMeta at the same groupName. - // federation/apis/core/install has only a subset of resources and hence if it gets registered first, then installation of v1 API fails in pkg/master. - // TODO(nikhiljindal): Fix this by ensuring that pkg/api/install and federation/apis/core/install do not conflict with each other. - _ "k8s.io/kubernetes/pkg/api/install" - - "k8s.io/apiserver/pkg/registry/generic" - "k8s.io/apiserver/pkg/registry/rest" - genericapiserver "k8s.io/apiserver/pkg/server" - "k8s.io/apiserver/pkg/server/storage" - "k8s.io/kubernetes/federation/apis/core" - _ "k8s.io/kubernetes/federation/apis/core/install" - corev1 "k8s.io/kubernetes/federation/apis/core/v1" - "k8s.io/kubernetes/federation/cmd/federation-apiserver/app/options" - "k8s.io/kubernetes/pkg/api" - configmapstore "k8s.io/kubernetes/pkg/registry/core/configmap/storage" - eventstore "k8s.io/kubernetes/pkg/registry/core/event/storage" - namespacestore "k8s.io/kubernetes/pkg/registry/core/namespace/storage" - secretstore "k8s.io/kubernetes/pkg/registry/core/secret/storage" - servicestore "k8s.io/kubernetes/pkg/registry/core/service/storage" -) - -func installCoreAPIs(s *options.ServerRunOptions, g *genericapiserver.GenericAPIServer, optsGetter generic.RESTOptionsGetter, apiResourceConfigSource storage.APIResourceConfigSource) { - servicesStorageFn := func() map[string]rest.Storage { - serviceStore, serviceStatusStore := servicestore.NewREST(optsGetter) - return map[string]rest.Storage{ - "services": serviceStore, - "services/status": serviceStatusStore, - } - } - namespacesStorageFn := func() map[string]rest.Storage { - namespaceStore, namespaceStatusStore, namespaceFinalizeStore := namespacestore.NewREST(optsGetter) - return map[string]rest.Storage{ - "namespaces": namespaceStore, - "namespaces/status": namespaceStatusStore, - "namespaces/finalize": namespaceFinalizeStore, - } - } - secretsStorageFn := func() map[string]rest.Storage { - secretStore := secretstore.NewREST(optsGetter) - return map[string]rest.Storage{ - "secrets": secretStore, - } - } - configmapsStorageFn := func() map[string]rest.Storage { - configMapStore := configmapstore.NewREST(optsGetter) - return map[string]rest.Storage{ - "configmaps": configMapStore, - } - } - eventsStorageFn := func() map[string]rest.Storage { - eventStore := eventstore.NewREST(optsGetter, uint64(s.EventTTL.Seconds())) - return map[string]rest.Storage{ - "events": eventStore, - } - } - resourcesStorageMap := map[string]getResourcesStorageFunc{ - "services": servicesStorageFn, - "namespaces": namespacesStorageFn, - "secrets": secretsStorageFn, - "configmaps": configmapsStorageFn, - "events": eventsStorageFn, - } - shouldInstallGroup, resources := enabledResources(corev1.SchemeGroupVersion, resourcesStorageMap, apiResourceConfigSource) - if !shouldInstallGroup { - return - } - coreGroupMeta := api.Registry.GroupOrDie(core.GroupName) - apiGroupInfo := genericapiserver.APIGroupInfo{ - GroupMeta: *coreGroupMeta, - VersionedResourcesStorageMap: map[string]map[string]rest.Storage{ - corev1.SchemeGroupVersion.Version: resources, - }, - OptionsExternalVersion: &api.Registry.GroupOrDie(core.GroupName).GroupVersion, - Scheme: core.Scheme, - ParameterCodec: core.ParameterCodec, - NegotiatedSerializer: core.Codecs, - } - if err := g.InstallLegacyAPIGroup(genericapiserver.DefaultLegacyAPIPrefix, &apiGroupInfo); err != nil { - glog.Fatalf("Error in registering group version: %+v.\n Error: %v\n", apiGroupInfo, err) - } -} diff --git a/federation/cmd/federation-apiserver/app/extensions.go b/federation/cmd/federation-apiserver/app/extensions.go deleted file mode 100644 index e6cceda5d5a..00000000000 --- a/federation/cmd/federation-apiserver/app/extensions.go +++ /dev/null @@ -1,91 +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 app - -import ( - "github.com/golang/glog" - extensionsv1beta1 "k8s.io/api/extensions/v1beta1" - "k8s.io/apiserver/pkg/registry/generic" - "k8s.io/apiserver/pkg/registry/rest" - genericapiserver "k8s.io/apiserver/pkg/server" - "k8s.io/apiserver/pkg/server/storage" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/extensions" - _ "k8s.io/kubernetes/pkg/apis/extensions/install" - daemonsetstore "k8s.io/kubernetes/pkg/registry/extensions/daemonset/storage" - deploymentstore "k8s.io/kubernetes/pkg/registry/extensions/deployment/storage" - ingressstore "k8s.io/kubernetes/pkg/registry/extensions/ingress/storage" - replicasetstore "k8s.io/kubernetes/pkg/registry/extensions/replicaset/storage" -) - -func installExtensionsAPIs(g *genericapiserver.GenericAPIServer, optsGetter generic.RESTOptionsGetter, apiResourceConfigSource storage.APIResourceConfigSource) { - replicasetsStorageFn := func() map[string]rest.Storage { - replicaSetStorage := replicasetstore.NewStorage(optsGetter) - return map[string]rest.Storage{ - "replicasets": replicaSetStorage.ReplicaSet, - "replicasets/status": replicaSetStorage.Status, - "replicasets/scale": replicaSetStorage.Scale, - } - } - deploymentsStorageFn := func() map[string]rest.Storage { - deploymentStorage := deploymentstore.NewStorage(optsGetter) - return map[string]rest.Storage{ - "deployments": deploymentStorage.Deployment, - "deployments/status": deploymentStorage.Status, - "deployments/scale": deploymentStorage.Scale, - "deployments/rollback": deploymentStorage.Rollback, - } - } - ingressesStorageFn := func() map[string]rest.Storage { - ingressStorage, ingressStatusStorage := ingressstore.NewREST(optsGetter) - return map[string]rest.Storage{ - "ingresses": ingressStorage, - "ingresses/status": ingressStatusStorage, - } - } - daemonsetsStorageFn := func() map[string]rest.Storage { - daemonSetStorage, daemonSetStatusStorage := daemonsetstore.NewREST(optsGetter) - return map[string]rest.Storage{ - "daemonsets": daemonSetStorage, - "daemonsets/status": daemonSetStatusStorage, - } - } - resourcesStorageMap := map[string]getResourcesStorageFunc{ - "replicasets": replicasetsStorageFn, - "deployments": deploymentsStorageFn, - "ingresses": ingressesStorageFn, - "daemonsets": daemonsetsStorageFn, - } - shouldInstallGroup, resources := enabledResources(extensionsv1beta1.SchemeGroupVersion, resourcesStorageMap, apiResourceConfigSource) - if !shouldInstallGroup { - return - } - extensionsGroupMeta := api.Registry.GroupOrDie(extensions.GroupName) - apiGroupInfo := genericapiserver.APIGroupInfo{ - GroupMeta: *extensionsGroupMeta, - VersionedResourcesStorageMap: map[string]map[string]rest.Storage{ - "v1beta1": resources, - }, - OptionsExternalVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion, - Scheme: api.Scheme, - ParameterCodec: api.ParameterCodec, - NegotiatedSerializer: api.Codecs, - } - if err := g.InstallAPIGroup(&apiGroupInfo); err != nil { - glog.Fatalf("Error in registering group versions: %v", err) - } -} diff --git a/federation/cmd/federation-apiserver/app/federation.go b/federation/cmd/federation-apiserver/app/federation.go deleted file mode 100644 index 4870ce5fe41..00000000000 --- a/federation/cmd/federation-apiserver/app/federation.go +++ /dev/null @@ -1,63 +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 app - -import ( - "github.com/golang/glog" - - "k8s.io/apiserver/pkg/registry/generic" - "k8s.io/apiserver/pkg/registry/rest" - genericapiserver "k8s.io/apiserver/pkg/server" - "k8s.io/apiserver/pkg/server/storage" - "k8s.io/kubernetes/federation/apis/federation" - _ "k8s.io/kubernetes/federation/apis/federation/install" - fedv1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1" - clusteretcd "k8s.io/kubernetes/federation/registry/cluster/etcd" - "k8s.io/kubernetes/pkg/api" -) - -func installFederationAPIs(g *genericapiserver.GenericAPIServer, optsGetter generic.RESTOptionsGetter, apiResourceConfigSource storage.APIResourceConfigSource) { - groupName := federation.GroupName - clustersStorageFn := func() map[string]rest.Storage { - clusterStorage, clusterStatusStorage := clusteretcd.NewREST(optsGetter) - return map[string]rest.Storage{ - "clusters": clusterStorage, - "clusters/status": clusterStatusStorage, - } - } - resourcesStorageMap := map[string]getResourcesStorageFunc{ - "clusters": clustersStorageFn, - } - shouldInstallGroup, resources := enabledResources(fedv1beta1.SchemeGroupVersion, resourcesStorageMap, apiResourceConfigSource) - if !shouldInstallGroup { - return - } - federationGroupMeta := api.Registry.GroupOrDie(groupName) - apiGroupInfo := genericapiserver.APIGroupInfo{ - GroupMeta: *federationGroupMeta, - VersionedResourcesStorageMap: map[string]map[string]rest.Storage{ - "v1beta1": resources, - }, - OptionsExternalVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion, - Scheme: api.Scheme, - ParameterCodec: api.ParameterCodec, - NegotiatedSerializer: api.Codecs, - } - if err := g.InstallAPIGroup(&apiGroupInfo); err != nil { - glog.Fatalf("Error in registering group versions: %v", err) - } -} diff --git a/federation/cmd/federation-apiserver/app/install.go b/federation/cmd/federation-apiserver/app/install.go deleted file mode 100644 index 4e75a2df1a4..00000000000 --- a/federation/cmd/federation-apiserver/app/install.go +++ /dev/null @@ -1,55 +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 app - -import ( - "github.com/golang/glog" - - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/apiserver/pkg/server/storage" -) - -// Function to get a map of resources and the corresponding storages. -type getResourcesStorageFunc func() map[string]rest.Storage - -// Filters the resources from the given resources storage map to those that are enabled in the given apiResourceConfigSource. -// resourcesStorageMap is expected to contain all resources in a group version. -// Returns false if none of the resources are enabled and hence the whole group version should be disabled. -func enabledResources(groupVersion schema.GroupVersion, resourcesStorageMap map[string]getResourcesStorageFunc, apiResourceConfigSource storage.APIResourceConfigSource) (bool, map[string]rest.Storage) { - enabledResources := map[string]rest.Storage{} - groupName := groupVersion.Group - if !apiResourceConfigSource.AnyResourcesForGroupEnabled(groupName) { - glog.V(1).Infof("Skipping disabled API group %q", groupName) - return false, enabledResources - } - for resource, fn := range resourcesStorageMap { - if apiResourceConfigSource.ResourceEnabled(groupVersion.WithResource(resource)) { - resources := fn() - for k, v := range resources { - enabledResources[k] = v - } - } else { - glog.V(1).Infof("Skipping disabled resource %s in API group %q", resource, groupName) - } - } - if len(enabledResources) == 0 { - glog.V(1).Infof("Skipping API group %q since there is no enabled resource", groupName) - return false, enabledResources - } - return true, enabledResources -} diff --git a/federation/cmd/federation-apiserver/app/options/BUILD b/federation/cmd/federation-apiserver/app/options/BUILD deleted file mode 100644 index fb3c638a06f..00000000000 --- a/federation/cmd/federation-apiserver/app/options/BUILD +++ /dev/null @@ -1,35 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "options.go", - "validation.go", - ], - importpath = "k8s.io/kubernetes/federation/cmd/federation-apiserver/app/options", - deps = [ - "//pkg/features:go_default_library", - "//pkg/kubeapiserver/options:go_default_library", - "//vendor/github.com/spf13/pflag:go_default_library", - "//vendor/k8s.io/apiserver/pkg/server/options:go_default_library", - "//vendor/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/cmd/federation-apiserver/app/options/options.go b/federation/cmd/federation-apiserver/app/options/options.go deleted file mode 100644 index 3dcb9492421..00000000000 --- a/federation/cmd/federation-apiserver/app/options/options.go +++ /dev/null @@ -1,94 +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 options contains flags and options for initializing federation-apiserver. -package options - -import ( - "time" - - genericoptions "k8s.io/apiserver/pkg/server/options" - "k8s.io/apiserver/pkg/storage/storagebackend" - kubeoptions "k8s.io/kubernetes/pkg/kubeapiserver/options" - - // add the kubernetes feature gates - _ "k8s.io/kubernetes/pkg/features" - - "github.com/spf13/pflag" -) - -// Runtime options for the federation-apiserver. -type ServerRunOptions struct { - GenericServerRunOptions *genericoptions.ServerRunOptions - Etcd *genericoptions.EtcdOptions - SecureServing *genericoptions.SecureServingOptions - InsecureServing *kubeoptions.InsecureServingOptions - Audit *genericoptions.AuditOptions - Features *genericoptions.FeatureOptions - Admission *genericoptions.AdmissionOptions - Authentication *kubeoptions.BuiltInAuthenticationOptions - Authorization *kubeoptions.BuiltInAuthorizationOptions - CloudProvider *kubeoptions.CloudProviderOptions - StorageSerialization *kubeoptions.StorageSerializationOptions - APIEnablement *kubeoptions.APIEnablementOptions - - EventTTL time.Duration -} - -// NewServerRunOptions creates a new ServerRunOptions object with default values. -func NewServerRunOptions() *ServerRunOptions { - s := ServerRunOptions{ - GenericServerRunOptions: genericoptions.NewServerRunOptions(), - Etcd: genericoptions.NewEtcdOptions(storagebackend.NewDefaultConfig(kubeoptions.DefaultEtcdPathPrefix, nil)), - SecureServing: kubeoptions.NewSecureServingOptions(), - InsecureServing: kubeoptions.NewInsecureServingOptions(), - Audit: genericoptions.NewAuditOptions(), - Features: genericoptions.NewFeatureOptions(), - Admission: genericoptions.NewAdmissionOptions(), - Authentication: kubeoptions.NewBuiltInAuthenticationOptions().WithAll(), - Authorization: kubeoptions.NewBuiltInAuthorizationOptions(), - CloudProvider: kubeoptions.NewCloudProviderOptions(), - StorageSerialization: kubeoptions.NewStorageSerializationOptions(), - APIEnablement: kubeoptions.NewAPIEnablementOptions(), - - EventTTL: 1 * time.Hour, - } - // Overwrite the default for storage data format. - s.Etcd.DefaultStorageMediaType = "application/vnd.kubernetes.protobuf" - // Set the default for admission plugins names - s.Admission.PluginNames = []string{"AlwaysAdmit"} - return &s -} - -// AddFlags adds flags for ServerRunOptions fields to be specified via FlagSet. -func (s *ServerRunOptions) AddFlags(fs *pflag.FlagSet) { - // Add the generic flags. - s.GenericServerRunOptions.AddUniversalFlags(fs) - s.Etcd.AddFlags(fs) - s.SecureServing.AddFlags(fs) - s.InsecureServing.AddFlags(fs) - s.Audit.AddFlags(fs) - s.Features.AddFlags(fs) - s.Authentication.AddFlags(fs) - s.Authorization.AddFlags(fs) - s.CloudProvider.AddFlags(fs) - s.StorageSerialization.AddFlags(fs) - s.APIEnablement.AddFlags(fs) - s.Admission.AddFlags(fs) - - fs.DurationVar(&s.EventTTL, "event-ttl", s.EventTTL, - "Amount of time to retain events.") -} diff --git a/federation/cmd/federation-apiserver/app/options/validation.go b/federation/cmd/federation-apiserver/app/options/validation.go deleted file mode 100644 index e1f1f2310f5..00000000000 --- a/federation/cmd/federation-apiserver/app/options/validation.go +++ /dev/null @@ -1,58 +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 options - -import "fmt" - -func (options *ServerRunOptions) Validate() []error { - var errors []error - if errs := options.GenericServerRunOptions.Validate(); len(errs) > 0 { - errors = append(errors, errs...) - } - if errs := options.Etcd.Validate(); len(errs) > 0 { - errors = append(errors, errs...) - } - if errs := options.SecureServing.Validate(); len(errs) > 0 { - errors = append(errors, errs...) - } - if errs := options.InsecureServing.Validate("insecure-port"); len(errs) > 0 { - errors = append(errors, errs...) - } - if errs := options.Audit.Validate(); len(errs) > 0 { - errors = append(errors, errs...) - } - if errs := options.Features.Validate(); len(errs) > 0 { - errors = append(errors, errs...) - } - if errs := options.Admission.Validate(); len(errs) > 0 { - errors = append(errors, errs...) - } - if errs := options.Authentication.Validate(); len(errs) > 0 { - errors = append(errors, errs...) - } - if errs := options.Authorization.Validate(); len(errs) > 0 { - errors = append(errors, errs...) - } - if errs := options.CloudProvider.Validate(); len(errs) > 0 { - errors = append(errors, errs...) - } - if options.EventTTL <= 0 { - errors = append(errors, fmt.Errorf("--event-ttl must be greater than 0")) - } - // TODO: add more checks - return errors -} diff --git a/federation/cmd/federation-apiserver/app/plugins.go b/federation/cmd/federation-apiserver/app/plugins.go deleted file mode 100644 index a10a3edbabe..00000000000 --- a/federation/cmd/federation-apiserver/app/plugins.go +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES 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 - -// This file exists to force the desired plugin implementations to be linked. -// This should probably be part of some configuration fed into the build for a -// given binary target. -import ( - // Cloud providers - _ "k8s.io/kubernetes/pkg/cloudprovider/providers" - - // Admission policies - "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/federation/plugin/pkg/admission/schedulingpolicy" - "k8s.io/kubernetes/plugin/pkg/admission/admit" - "k8s.io/kubernetes/plugin/pkg/admission/deny" - "k8s.io/kubernetes/plugin/pkg/admission/gc" -) - -// RegisterAllAdmissionPlugins registers all admission plugins -func RegisterAllAdmissionPlugins(plugins *admission.Plugins) { - admit.Register(plugins) - deny.Register(plugins) - gc.Register(plugins) - schedulingpolicy.Register(plugins) -} diff --git a/federation/cmd/federation-apiserver/app/server.go b/federation/cmd/federation-apiserver/app/server.go deleted file mode 100644 index cb901459ac6..00000000000 --- a/federation/cmd/federation-apiserver/app/server.go +++ /dev/null @@ -1,486 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES 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 does all of the work necessary to create a Kubernetes -// APIServer by binding together the API, master and APIServer infrastructure. -// It can be configured and called directly or via the hyperkube cache. -package app - -import ( - "fmt" - "io/ioutil" - "strings" - "time" - - "github.com/go-openapi/spec" - "github.com/golang/glog" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - - appsv1beta2 "k8s.io/api/apps/v1beta2" - apiv1 "k8s.io/api/core/v1" - extensionsapiv1beta1 "k8s.io/api/extensions/v1beta1" - "k8s.io/apimachinery/pkg/runtime/schema" - utilerrors "k8s.io/apimachinery/pkg/util/errors" - "k8s.io/apimachinery/pkg/util/sets" - genericapiserver "k8s.io/apiserver/pkg/server" - "k8s.io/apiserver/pkg/server/filters" - serveroptions "k8s.io/apiserver/pkg/server/options" - serverstorage "k8s.io/apiserver/pkg/server/storage" - clientgoinformers "k8s.io/client-go/informers" - clientgoclientset "k8s.io/client-go/kubernetes" - openapicommon "k8s.io/kube-openapi/pkg/common" - federationv1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1" - "k8s.io/kubernetes/federation/cmd/federation-apiserver/app/options" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" - informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" - "k8s.io/kubernetes/pkg/generated/openapi" - "k8s.io/kubernetes/pkg/kubeapiserver" - kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission" - kubeoptions "k8s.io/kubernetes/pkg/kubeapiserver/options" - kubeserver "k8s.io/kubernetes/pkg/kubeapiserver/server" - quotainstall "k8s.io/kubernetes/pkg/quota/install" - "k8s.io/kubernetes/pkg/registry/cachesize" - "k8s.io/kubernetes/pkg/routes" - "k8s.io/kubernetes/pkg/version" -) - -// NewAPIServerCommand creates a *cobra.Command object with default parameters -func NewAPIServerCommand() *cobra.Command { - s := options.NewServerRunOptions() - s.AddFlags(pflag.CommandLine) - cmd := &cobra.Command{ - Use: "federation-apiserver", - Long: `The Kubernetes federation API server validates and configures data -for the api objects which include pods, services, replicationcontrollers, and -others. The API Server services REST operations and provides the frontend to the -cluster's shared state through which all other components interact.`, - Run: func(cmd *cobra.Command, args []string) { - }, - } - return cmd -} - -// Run runs the specified APIServer. It only returns if stopCh is closed -// or one of the ports cannot be listened on initially. -func Run(s *options.ServerRunOptions, stopCh <-chan struct{}) error { - err := NonBlockingRun(s, stopCh) - if err != nil { - return err - } - <-stopCh - return nil -} - -// NonBlockingRun runs the specified APIServer and configures it to -// stop with the given channel. -func NonBlockingRun(s *options.ServerRunOptions, stopCh <-chan struct{}) error { - // register all admission plugins - RegisterAllAdmissionPlugins(s.Admission.Plugins) - - // set defaults - if err := s.GenericServerRunOptions.DefaultAdvertiseAddress(s.SecureServing); err != nil { - return err - } - if err := kubeoptions.DefaultAdvertiseAddress(s.GenericServerRunOptions, s.InsecureServing); err != nil { - return err - } - if err := s.SecureServing.MaybeDefaultWithSelfSignedCerts(s.GenericServerRunOptions.AdvertiseAddress.String(), nil, nil); err != nil { - return fmt.Errorf("error creating self-signed certificates: %v", err) - } - if err := s.CloudProvider.DefaultExternalHost(s.GenericServerRunOptions); err != nil { - return fmt.Errorf("error setting the external host value: %v", err) - } - - s.Authentication.ApplyAuthorization(s.Authorization) - - // validate options - if errs := s.Validate(); len(errs) != 0 { - return utilerrors.NewAggregate(errs) - } - - genericConfig := genericapiserver.NewConfig(api.Codecs) - if err := s.GenericServerRunOptions.ApplyTo(genericConfig); err != nil { - return err - } - insecureServingOptions, err := s.InsecureServing.ApplyTo(genericConfig) - if err != nil { - return err - } - if err := s.SecureServing.ApplyTo(genericConfig); err != nil { - return err - } - if err := s.Authentication.ApplyTo(genericConfig); err != nil { - return err - } - if err := s.Audit.ApplyTo(genericConfig); err != nil { - return err - } - if err := s.Features.ApplyTo(genericConfig); err != nil { - return err - } - - resourceConfig := defaultResourceConfig() - - if s.Etcd.StorageConfig.DeserializationCacheSize == 0 { - // When size of cache is not explicitly set, set it to 50000 - s.Etcd.StorageConfig.DeserializationCacheSize = 50000 - } - storageGroupsToEncodingVersion, err := s.StorageSerialization.StorageGroupsToEncodingVersion() - if err != nil { - return fmt.Errorf("error generating storage version map: %s", err) - } - storageFactory, err := kubeapiserver.NewStorageFactory( - s.Etcd.StorageConfig, s.Etcd.DefaultStorageMediaType, api.Codecs, - serverstorage.NewDefaultResourceEncodingConfig(api.Registry), storageGroupsToEncodingVersion, - []schema.GroupVersionResource{}, resourceConfig, s.APIEnablement.RuntimeConfig) - if err != nil { - return fmt.Errorf("error in initializing storage factory: %s", err) - } - - for _, override := range s.Etcd.EtcdServersOverrides { - tokens := strings.Split(override, "#") - if len(tokens) != 2 { - glog.Errorf("invalid value of etcd server overrides: %s", override) - continue - } - - apiresource := strings.Split(tokens[0], "/") - if len(apiresource) != 2 { - glog.Errorf("invalid resource definition: %s", tokens[0]) - continue - } - group := apiresource[0] - resource := apiresource[1] - groupResource := schema.GroupResource{Group: group, Resource: resource} - - servers := strings.Split(tokens[1], ";") - storageFactory.SetEtcdLocation(groupResource, servers) - } - if err := s.Etcd.ApplyWithStorageFactoryTo(storageFactory, genericConfig); err != nil { - return err - } - - apiAuthenticator, securityDefinitions, err := s.Authentication.ToAuthenticationConfig().New() - if err != nil { - return fmt.Errorf("invalid Authentication Config: %v", err) - } - - kubeClientConfig := genericConfig.LoopbackClientConfig - client, err := internalclientset.NewForConfig(kubeClientConfig) - if err != nil { - return fmt.Errorf("failed to create clientset: %v", err) - } - - sharedInformers := informers.NewSharedInformerFactory(client, 10*time.Minute) - clientgoExternalClient, err := clientgoclientset.NewForConfig(kubeClientConfig) - if err != nil { - return fmt.Errorf("failed to create real external clientset: %v", err) - } - versionedInformers := clientgoinformers.NewSharedInformerFactory(clientgoExternalClient, 10*time.Minute) - - authorizationConfig := s.Authorization.ToAuthorizationConfig(sharedInformers) - apiAuthorizer, _, err := authorizationConfig.New() - if err != nil { - return fmt.Errorf("invalid Authorization Config: %v", err) - } - - var cloudConfig []byte - if s.CloudProvider.CloudConfigFile != "" { - cloudConfig, err = ioutil.ReadFile(s.CloudProvider.CloudConfigFile) - if err != nil { - glog.Fatalf("Error reading from cloud configuration file %s: %#v", s.CloudProvider.CloudConfigFile, err) - } - } - - // NOTE: we do not provide informers to the quota registry because admission level decisions - // do not require us to open watches for all items tracked by quota. - quotaRegistry := quotainstall.NewRegistry(nil, nil) - pluginInitializer := kubeapiserveradmission.NewPluginInitializer(client, sharedInformers, cloudConfig, nil, quotaRegistry) - - err = s.Admission.ApplyTo( - genericConfig, - versionedInformers, - nil, - nil, - kubeClientConfig, - api.Scheme, - pluginInitializer, - ) - if err != nil { - return fmt.Errorf("failed to initialize plugins: %v", err) - } - - kubeVersion := version.Get() - genericConfig.Version = &kubeVersion - genericConfig.Authenticator = apiAuthenticator - genericConfig.Authorizer = apiAuthorizer - genericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(openapi.GetOpenAPIDefinitions, api.Scheme) - genericConfig.OpenAPIConfig.PostProcessSpec = postProcessOpenAPISpecForBackwardCompatibility - genericConfig.OpenAPIConfig.SecurityDefinitions = securityDefinitions - genericConfig.SwaggerConfig = genericapiserver.DefaultSwaggerConfig() - genericConfig.LongRunningFunc = filters.BasicLongRunningRequestCheck( - sets.NewString("watch", "proxy"), - sets.NewString("attach", "exec", "proxy", "log", "portforward"), - ) - - // TODO: Move this to generic api server (Need to move the command line flag). - if s.Etcd.EnableWatchCache { - glog.V(2).Infof("Initializing cache sizes based on %dMB limit", s.GenericServerRunOptions.TargetRAMMB) - sizes := cachesize.NewHeuristicWatchCacheSizes(s.GenericServerRunOptions.TargetRAMMB) - if userSpecified, err := serveroptions.ParseWatchCacheSizes(s.Etcd.WatchCacheSizes); err == nil { - for resource, size := range userSpecified { - sizes[resource] = size - } - } - s.Etcd.WatchCacheSizes, err = serveroptions.WriteWatchCacheSizes(sizes) - if err != nil { - return err - } - } - - m, err := genericConfig.Complete(versionedInformers).New("federation", genericapiserver.EmptyDelegate) - if err != nil { - return err - } - - routes.UIRedirect{}.Install(m.Handler.NonGoRestfulMux) - routes.Logs{}.Install(m.Handler.GoRestfulContainer) - - apiResourceConfigSource := storageFactory.APIResourceConfigSource - installFederationAPIs(m, genericConfig.RESTOptionsGetter, apiResourceConfigSource) - installCoreAPIs(s, m, genericConfig.RESTOptionsGetter, apiResourceConfigSource) - installExtensionsAPIs(m, genericConfig.RESTOptionsGetter, apiResourceConfigSource) - installBatchAPIs(m, genericConfig.RESTOptionsGetter, apiResourceConfigSource) - installAutoscalingAPIs(m, genericConfig.RESTOptionsGetter, apiResourceConfigSource) - - // run the insecure server now - if insecureServingOptions != nil { - insecureHandlerChain := kubeserver.BuildInsecureHandlerChain(m.UnprotectedHandler(), genericConfig) - if err := kubeserver.NonBlockingRun(insecureServingOptions, insecureHandlerChain, stopCh); err != nil { - return err - } - } - - err = m.PrepareRun().NonBlockingRun(stopCh) - if err == nil { - sharedInformers.Start(stopCh) - } - return err -} - -func defaultResourceConfig() *serverstorage.ResourceConfig { - rc := serverstorage.NewResourceConfig() - - rc.EnableVersions( - federationv1beta1.SchemeGroupVersion, - ) - - // All core resources except these are disabled by default. - rc.EnableResources( - apiv1.SchemeGroupVersion.WithResource("secrets"), - apiv1.SchemeGroupVersion.WithResource("services"), - apiv1.SchemeGroupVersion.WithResource("namespaces"), - apiv1.SchemeGroupVersion.WithResource("events"), - apiv1.SchemeGroupVersion.WithResource("configmaps"), - ) - // All extension resources except these are disabled by default. - rc.EnableResources( - extensionsapiv1beta1.SchemeGroupVersion.WithResource("daemonsets"), - extensionsapiv1beta1.SchemeGroupVersion.WithResource("deployments"), - extensionsapiv1beta1.SchemeGroupVersion.WithResource("ingresses"), - extensionsapiv1beta1.SchemeGroupVersion.WithResource("replicasets"), - ) - // All apps resources except these are disabled by default. - rc.EnableResources( - appsv1beta2.SchemeGroupVersion.WithResource("daemonsets"), - appsv1beta2.SchemeGroupVersion.WithResource("deployments"), - appsv1beta2.SchemeGroupVersion.WithResource("replicasets"), - ) - return rc -} - -// PostProcessSpec adds removed definitions for backward compatibility -func postProcessOpenAPISpecForBackwardCompatibility(s *spec.Swagger) (*spec.Swagger, error) { - compatibilityMap := map[string]string{ - "v1beta1.ReplicaSetList": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ReplicaSetList", - "v1.FlockerVolumeSource": "io.k8s.kubernetes.pkg.api.v1.FlockerVolumeSource", - "v1.FlexVolumeSource": "io.k8s.kubernetes.pkg.api.v1.FlexVolumeSource", - "v1.SecretKeySelector": "io.k8s.kubernetes.pkg.api.v1.SecretKeySelector", - "v1.DeleteOptions": "io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions", - "v1.ServiceSpec": "io.k8s.kubernetes.pkg.api.v1.ServiceSpec", - "v1.NamespaceStatus": "io.k8s.kubernetes.pkg.api.v1.NamespaceStatus", - "v1.Affinity": "io.k8s.kubernetes.pkg.api.v1.Affinity", - "v1.PodAffinity": "io.k8s.kubernetes.pkg.api.v1.PodAffinity", - "v1.EnvVarSource": "io.k8s.kubernetes.pkg.api.v1.EnvVarSource", - "v1.ListMeta": "io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta", - "v1.ObjectMeta": "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", - "v1.APIGroupList": "io.k8s.apimachinery.pkg.apis.meta.v1.APIGroupList", - "v1.EnvFromSource": "io.k8s.kubernetes.pkg.api.v1.EnvFromSource", - "v1.Service": "io.k8s.kubernetes.pkg.api.v1.Service", - "v1.HorizontalPodAutoscaler": "io.k8s.kubernetes.pkg.apis.autoscaling.v1.HorizontalPodAutoscaler", - "v1.StatusCause": "io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause", - "v1.ObjectFieldSelector": "io.k8s.kubernetes.pkg.api.v1.ObjectFieldSelector", - "v1.QuobyteVolumeSource": "io.k8s.kubernetes.pkg.api.v1.QuobyteVolumeSource", - "v1beta1.ReplicaSetSpec": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ReplicaSetSpec", - "v1.LabelSelector": "io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", - "v1.DownwardAPIVolumeFile": "io.k8s.kubernetes.pkg.api.v1.DownwardAPIVolumeFile", - "v1.GCEPersistentDiskVolumeSource": "io.k8s.kubernetes.pkg.api.v1.GCEPersistentDiskVolumeSource", - "v1beta1.ClusterCondition": "io.k8s.kubernetes.federation.apis.federation.v1beta1.ClusterCondition", - "v1.JobCondition": "io.k8s.kubernetes.pkg.apis.batch.v1.JobCondition", - "v1.LabelSelectorRequirement": "io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelectorRequirement", - "v1beta1.Deployment": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.Deployment", - "v1.LoadBalancerIngress": "io.k8s.kubernetes.pkg.api.v1.LoadBalancerIngress", - "v1.SecretList": "io.k8s.kubernetes.pkg.api.v1.SecretList", - "v1.ServicePort": "io.k8s.kubernetes.pkg.api.v1.ServicePort", - "v1.Namespace": "io.k8s.kubernetes.pkg.api.v1.Namespace", - "v1beta1.ReplicaSetCondition": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ReplicaSetCondition", - "v1.CrossVersionObjectReference": "io.k8s.kubernetes.pkg.apis.autoscaling.v1.CrossVersionObjectReference", - "v1.ConfigMapVolumeSource": "io.k8s.kubernetes.pkg.api.v1.ConfigMapVolumeSource", - "v1.FCVolumeSource": "io.k8s.kubernetes.pkg.api.v1.FCVolumeSource", - "v1.GroupVersionForDiscovery": "io.k8s.apimachinery.pkg.apis.meta.v1.GroupVersionForDiscovery", - "v1beta1.ClusterStatus": "io.k8s.kubernetes.federation.apis.federation.v1beta1.ClusterStatus", - "v1.Job": "io.k8s.kubernetes.pkg.apis.batch.v1.Job", - "v1.PersistentVolumeClaimVolumeSource": "io.k8s.kubernetes.pkg.api.v1.PersistentVolumeClaimVolumeSource", - "v1.Handler": "io.k8s.kubernetes.pkg.api.v1.Handler", - "v1.ServerAddressByClientCIDR": "io.k8s.apimachinery.pkg.apis.meta.v1.ServerAddressByClientCIDR", - "v1.PodAntiAffinity": "io.k8s.kubernetes.pkg.api.v1.PodAntiAffinity", - "v1.ISCSIVolumeSource": "io.k8s.kubernetes.pkg.api.v1.ISCSIVolumeSource", - "v1.WeightedPodAffinityTerm": "io.k8s.kubernetes.pkg.api.v1.WeightedPodAffinityTerm", - "v1.HorizontalPodAutoscalerSpec": "io.k8s.kubernetes.pkg.apis.autoscaling.v1.HorizontalPodAutoscalerSpec", - "v1.HorizontalPodAutoscalerList": "io.k8s.kubernetes.pkg.apis.autoscaling.v1.HorizontalPodAutoscalerList", - "v1.Probe": "io.k8s.kubernetes.pkg.api.v1.Probe", - "v1.APIGroup": "io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup", - "v1beta1.DeploymentList": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DeploymentList", - "v1.NodeAffinity": "io.k8s.kubernetes.pkg.api.v1.NodeAffinity", - "v1.SecretEnvSource": "io.k8s.kubernetes.pkg.api.v1.SecretEnvSource", - "v1beta1.DeploymentStatus": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DeploymentStatus", - "v1.CinderVolumeSource": "io.k8s.kubernetes.pkg.api.v1.CinderVolumeSource", - "v1.NodeSelectorTerm": "io.k8s.kubernetes.pkg.api.v1.NodeSelectorTerm", - "v1.Patch": "io.k8s.apimachinery.pkg.apis.meta.v1.Patch", - "v1.SecretVolumeSource": "io.k8s.kubernetes.pkg.api.v1.SecretVolumeSource", - "v1.Secret": "io.k8s.kubernetes.pkg.api.v1.Secret", - "v1.NodeSelector": "io.k8s.kubernetes.pkg.api.v1.NodeSelector", - "runtime.RawExtension": "io.k8s.apimachinery.pkg.runtime.RawExtension", - "v1.PreferredSchedulingTerm": "io.k8s.kubernetes.pkg.api.v1.PreferredSchedulingTerm", - "v1beta1.ClusterList": "io.k8s.kubernetes.federation.apis.federation.v1beta1.ClusterList", - "v1.KeyToPath": "io.k8s.kubernetes.pkg.api.v1.KeyToPath", - "intstr.IntOrString": "io.k8s.apimachinery.pkg.util.intstr.IntOrString", - "v1beta1.ClusterSpec": "io.k8s.kubernetes.federation.apis.federation.v1beta1.ClusterSpec", - "v1.ServiceList": "io.k8s.kubernetes.pkg.api.v1.ServiceList", - "v1beta1.DeploymentStrategy": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DeploymentStrategy", - "v1beta1.IngressBackend": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IngressBackend", - "v1.Time": "io.k8s.apimachinery.pkg.apis.meta.v1.Time", - "v1.ContainerPort": "io.k8s.kubernetes.pkg.api.v1.ContainerPort", - "v1beta1.HTTPIngressRuleValue": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.HTTPIngressRuleValue", - "v1.AzureFileVolumeSource": "io.k8s.kubernetes.pkg.api.v1.AzureFileVolumeSource", - "v1.PodTemplateSpec": "io.k8s.kubernetes.pkg.api.v1.PodTemplateSpec", - "v1.PodSpec": "io.k8s.kubernetes.pkg.api.v1.PodSpec", - "v1beta1.ReplicaSetStatus": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ReplicaSetStatus", - "v1.CephFSVolumeSource": "io.k8s.kubernetes.pkg.api.v1.CephFSVolumeSource", - "v1.Volume": "io.k8s.kubernetes.pkg.api.v1.Volume", - "v1beta1.Ingress": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.Ingress", - "v1.PodAffinityTerm": "io.k8s.kubernetes.pkg.api.v1.PodAffinityTerm", - "v1.ObjectReference": "io.k8s.kubernetes.pkg.api.v1.ObjectReference", - "v1.ServiceStatus": "io.k8s.kubernetes.pkg.api.v1.ServiceStatus", - "v1.APIResource": "io.k8s.apimachinery.pkg.apis.meta.v1.APIResource", - "v1.AzureDiskVolumeSource": "io.k8s.kubernetes.pkg.api.v1.AzureDiskVolumeSource", - "v1.ConfigMap": "io.k8s.kubernetes.pkg.api.v1.ConfigMap", - "v1beta1.IngressSpec": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IngressSpec", - "v1.APIVersions": "io.k8s.apimachinery.pkg.apis.meta.v1.APIVersions", - "resource.Quantity": "io.k8s.apimachinery.pkg.api.resource.Quantity", - "v1.Event": "io.k8s.kubernetes.pkg.api.v1.Event", - "v1.JobStatus": "io.k8s.kubernetes.pkg.apis.batch.v1.JobStatus", - "v1beta1.ServerAddressByClientCIDR": "io.k8s.kubernetes.federation.apis.federation.v1beta1.ServerAddressByClientCIDR", - "v1.LocalObjectReference": "io.k8s.kubernetes.pkg.api.v1.LocalObjectReference", - "v1.HostPathVolumeSource": "io.k8s.kubernetes.pkg.api.v1.HostPathVolumeSource", - "v1.LoadBalancerStatus": "io.k8s.kubernetes.pkg.api.v1.LoadBalancerStatus", - "v1beta1.HTTPIngressPath": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.HTTPIngressPath", - "v1beta1.DeploymentSpec": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DeploymentSpec", - "v1.ExecAction": "io.k8s.kubernetes.pkg.api.v1.ExecAction", - "v1.HorizontalPodAutoscalerStatus": "io.k8s.kubernetes.pkg.apis.autoscaling.v1.HorizontalPodAutoscalerStatus", - "v1.JobSpec": "io.k8s.kubernetes.pkg.apis.batch.v1.JobSpec", - "v1beta1.DaemonSetSpec": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DaemonSetSpec", - "v1.SELinuxOptions": "io.k8s.kubernetes.pkg.api.v1.SELinuxOptions", - "v1beta1.IngressTLS": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IngressTLS", - "v1beta1.ScaleStatus": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ScaleStatus", - "v1.NamespaceSpec": "io.k8s.kubernetes.pkg.api.v1.NamespaceSpec", - "v1.StatusDetails": "io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails", - "v1beta1.IngressList": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IngressList", - "v1beta1.DeploymentRollback": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DeploymentRollback", - "v1.GlusterfsVolumeSource": "io.k8s.kubernetes.pkg.api.v1.GlusterfsVolumeSource", - "v1.JobList": "io.k8s.kubernetes.pkg.apis.batch.v1.JobList", - "v1.EventList": "io.k8s.kubernetes.pkg.api.v1.EventList", - "v1beta1.IngressRule": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IngressRule", - "v1.APIResourceList": "io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList", - "v1.ConfigMapKeySelector": "io.k8s.kubernetes.pkg.api.v1.ConfigMapKeySelector", - "v1.PhotonPersistentDiskVolumeSource": "io.k8s.kubernetes.pkg.api.v1.PhotonPersistentDiskVolumeSource", - "v1.HTTPHeader": "io.k8s.kubernetes.pkg.api.v1.HTTPHeader", - "version.Info": "io.k8s.apimachinery.pkg.version.Info", - "v1.EventSource": "io.k8s.kubernetes.pkg.api.v1.EventSource", - "v1.OwnerReference": "io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference", - "v1beta1.ScaleSpec": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ScaleSpec", - "v1.GitRepoVolumeSource": "io.k8s.kubernetes.pkg.api.v1.GitRepoVolumeSource", - "v1.ConfigMapEnvSource": "io.k8s.kubernetes.pkg.api.v1.ConfigMapEnvSource", - "v1beta1.DeploymentCondition": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DeploymentCondition", - "v1.EnvVar": "io.k8s.kubernetes.pkg.api.v1.EnvVar", - "v1.DownwardAPIVolumeSource": "io.k8s.kubernetes.pkg.api.v1.DownwardAPIVolumeSource", - "v1.SecurityContext": "io.k8s.kubernetes.pkg.api.v1.SecurityContext", - "v1beta1.IngressStatus": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.IngressStatus", - "v1beta1.Cluster": "io.k8s.kubernetes.federation.apis.federation.v1beta1.Cluster", - "v1.Capabilities": "io.k8s.kubernetes.pkg.api.v1.Capabilities", - "v1.AWSElasticBlockStoreVolumeSource": "io.k8s.kubernetes.pkg.api.v1.AWSElasticBlockStoreVolumeSource", - "v1beta1.ReplicaSet": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.ReplicaSet", - "v1.ConfigMapList": "io.k8s.kubernetes.pkg.api.v1.ConfigMapList", - "v1.Lifecycle": "io.k8s.kubernetes.pkg.api.v1.Lifecycle", - "v1beta1.Scale": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.Scale", - "v1beta1.DaemonSet": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DaemonSet", - "v1beta1.RollingUpdateDeployment": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.RollingUpdateDeployment", - "v1beta1.DaemonSetStatus": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DaemonSetStatus", - "v1.Preconditions": "io.k8s.apimachinery.pkg.apis.meta.v1.Preconditions", - "v1beta1.DaemonSetList": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.DaemonSetList", - "v1.RBDVolumeSource": "io.k8s.kubernetes.pkg.api.v1.RBDVolumeSource", - "v1.NFSVolumeSource": "io.k8s.kubernetes.pkg.api.v1.NFSVolumeSource", - "v1.NodeSelectorRequirement": "io.k8s.kubernetes.pkg.api.v1.NodeSelectorRequirement", - "v1.ResourceRequirements": "io.k8s.kubernetes.pkg.api.v1.ResourceRequirements", - "v1.WatchEvent": "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent", - "v1.HTTPGetAction": "io.k8s.kubernetes.pkg.api.v1.HTTPGetAction", - "v1beta1.RollbackConfig": "io.k8s.kubernetes.pkg.apis.extensions.v1beta1.RollbackConfig", - "v1.PodSecurityContext": "io.k8s.kubernetes.pkg.api.v1.PodSecurityContext", - "v1.VolumeMount": "io.k8s.kubernetes.pkg.api.v1.VolumeMount", - "v1.NamespaceList": "io.k8s.kubernetes.pkg.api.v1.NamespaceList", - "v1.TCPSocketAction": "io.k8s.kubernetes.pkg.api.v1.TCPSocketAction", - "v1.ResourceFieldSelector": "io.k8s.kubernetes.pkg.api.v1.ResourceFieldSelector", - "v1.Container": "io.k8s.kubernetes.pkg.api.v1.Container", - "v1.VsphereVirtualDiskVolumeSource": "io.k8s.kubernetes.pkg.api.v1.VsphereVirtualDiskVolumeSource", - "v1.EmptyDirVolumeSource": "io.k8s.kubernetes.pkg.api.v1.EmptyDirVolumeSource", - "v1.Status": "io.k8s.apimachinery.pkg.apis.meta.v1.Status", - } - - for k, v := range compatibilityMap { - if _, found := s.Definitions[v]; !found { - continue - } - s.Definitions[k] = spec.Schema{ - SchemaProps: spec.SchemaProps{ - Ref: spec.MustCreateRef("#/definitions/" + openapicommon.EscapeJsonPointer(v)), - Description: fmt.Sprintf("Deprecated. Please use %s instead.", v), - }, - } - } - return s, nil -} diff --git a/federation/cmd/federation-controller-manager/BUILD b/federation/cmd/federation-controller-manager/BUILD deleted file mode 100644 index a1ebcd13562..00000000000 --- a/federation/cmd/federation-controller-manager/BUILD +++ /dev/null @@ -1,46 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_binary", - "go_library", -) - -go_binary( - name = "federation-controller-manager", - importpath = "k8s.io/kubernetes/federation/cmd/federation-controller-manager", - library = ":go_default_library", -) - -go_library( - name = "go_default_library", - srcs = ["controller-manager.go"], - importpath = "k8s.io/kubernetes/federation/cmd/federation-controller-manager", - deps = [ - "//federation/cmd/federation-controller-manager/app:go_default_library", - "//federation/cmd/federation-controller-manager/app/options:go_default_library", - "//pkg/util/reflector/prometheus:go_default_library", - "//pkg/util/workqueue/prometheus:go_default_library", - "//pkg/version/verflag:go_default_library", - "//vendor/github.com/spf13/pflag:go_default_library", - "//vendor/k8s.io/apiserver/pkg/server/healthz:go_default_library", - "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library", - "//vendor/k8s.io/apiserver/pkg/util/logs:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/cmd/federation-controller-manager/app:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/cmd/federation-controller-manager/OWNERS b/federation/cmd/federation-controller-manager/OWNERS deleted file mode 100644 index 571c01c0529..00000000000 --- a/federation/cmd/federation-controller-manager/OWNERS +++ /dev/null @@ -1,8 +0,0 @@ -approvers: -- quinton-hoole -- nikhiljindal -- madhusudancs -reviewers: -- quinton-hoole -- nikhiljindal -- madhusudancs diff --git a/federation/cmd/federation-controller-manager/app/BUILD b/federation/cmd/federation-controller-manager/app/BUILD deleted file mode 100644 index e388480f59d..00000000000 --- a/federation/cmd/federation-controller-manager/app/BUILD +++ /dev/null @@ -1,82 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = [ - "controllermanager.go", - "plugins.go", - ], - importpath = "k8s.io/kubernetes/federation/cmd/federation-controller-manager/app", - deps = [ - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/client/clientset_generated/federation_clientset:go_default_library", - "//federation/cmd/federation-controller-manager/app/options:go_default_library", - "//federation/pkg/dnsprovider/providers/aws/route53:go_default_library", - "//federation/pkg/dnsprovider/providers/coredns:go_default_library", - "//federation/pkg/dnsprovider/providers/google/clouddns:go_default_library", - "//federation/pkg/federatedtypes:go_default_library", - "//federation/pkg/federation-controller/cluster:go_default_library", - "//federation/pkg/federation-controller/ingress:go_default_library", - "//federation/pkg/federation-controller/job:go_default_library", - "//federation/pkg/federation-controller/service:go_default_library", - "//federation/pkg/federation-controller/service/dns:go_default_library", - "//federation/pkg/federation-controller/sync:go_default_library", - "//federation/pkg/federation-controller/util/eventsink:go_default_library", - "//pkg/api:go_default_library", - "//pkg/util/configz:go_default_library", - "//pkg/version:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", - "//vendor/github.com/spf13/cobra:go_default_library", - "//vendor/github.com/spf13/pflag:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/apiserver/pkg/server/healthz:go_default_library", - "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library", - "//vendor/k8s.io/client-go/discovery:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", - "//vendor/k8s.io/client-go/tools/leaderelection:go_default_library", - "//vendor/k8s.io/client-go/tools/leaderelection/resourcelock:go_default_library", - "//vendor/k8s.io/client-go/tools/record:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["controllermanager_test.go"], - importpath = "k8s.io/kubernetes/federation/cmd/federation-controller-manager/app", - library = ":go_default_library", - deps = [ - "//federation/pkg/federation-controller/ingress:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/cmd/federation-controller-manager/app/options:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/cmd/federation-controller-manager/app/controllermanager.go b/federation/cmd/federation-controller-manager/app/controllermanager.go deleted file mode 100644 index 19345963061..00000000000 --- a/federation/cmd/federation-controller-manager/app/controllermanager.go +++ /dev/null @@ -1,318 +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 app implements a server that runs a set of active -// components. This includes cluster controller - -package app - -import ( - "net" - "net/http" - "net/http/pprof" - "os" - goruntime "runtime" - "strconv" - "time" - - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/apiserver/pkg/server/healthz" - utilflag "k8s.io/apiserver/pkg/util/flag" - "k8s.io/client-go/kubernetes" - restclient "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/tools/leaderelection" - "k8s.io/client-go/tools/leaderelection/resourcelock" - "k8s.io/client-go/tools/record" - federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" - federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - "k8s.io/kubernetes/federation/cmd/federation-controller-manager/app/options" - "k8s.io/kubernetes/federation/pkg/federatedtypes" - clustercontroller "k8s.io/kubernetes/federation/pkg/federation-controller/cluster" - ingresscontroller "k8s.io/kubernetes/federation/pkg/federation-controller/ingress" - jobcontroller "k8s.io/kubernetes/federation/pkg/federation-controller/job" - servicecontroller "k8s.io/kubernetes/federation/pkg/federation-controller/service" - servicednscontroller "k8s.io/kubernetes/federation/pkg/federation-controller/service/dns" - synccontroller "k8s.io/kubernetes/federation/pkg/federation-controller/sync" - "k8s.io/kubernetes/federation/pkg/federation-controller/util/eventsink" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/util/configz" - "k8s.io/kubernetes/pkg/version" - - "github.com/golang/glog" - "github.com/prometheus/client_golang/prometheus" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/client-go/discovery" -) - -const ( - apiserverWaitTimeout = 2 * time.Minute - apiserverRetryInterval = 2 * time.Second -) - -// NewControllerManagerCommand creates a *cobra.Command object with default parameters -func NewControllerManagerCommand() *cobra.Command { - s := options.NewCMServer() - s.AddFlags(pflag.CommandLine) - cmd := &cobra.Command{ - Use: "federation-controller-manager", - Long: `The federation controller manager is a daemon that embeds -the core control loops shipped with federation. In applications of robotics and -automation, a control loop is a non-terminating loop that regulates the state of -the system. In federation, a controller is a control loop that watches the shared -state of the federation cluster through the apiserver and makes changes attempting -to move the current state towards the desired state. Examples of controllers that -ship with federation today is the cluster controller.`, - Run: func(cmd *cobra.Command, args []string) { - }, - } - - return cmd -} - -// Run runs the CMServer. This should never exit. -func Run(s *options.CMServer) error { - glog.Infof("%+v", version.Get()) - if c, err := configz.New("componentconfig"); err == nil { - c.Set(s.ControllerManagerConfiguration) - } else { - glog.Errorf("unable to register configz: %s", err) - } - - restClientCfg, err := clientcmd.BuildConfigFromFlags(s.Master, s.Kubeconfig) - if err != nil || restClientCfg == nil { - glog.V(2).Infof("Couldn't build the rest client config from flags: %v", err) - return err - } - - // Override restClientCfg qps/burst settings from flags - restClientCfg.QPS = s.APIServerQPS - restClientCfg.Burst = s.APIServerBurst - - go func() { - mux := http.NewServeMux() - healthz.InstallHandler(mux) - if s.EnableProfiling { - mux.HandleFunc("/debug/pprof/", pprof.Index) - mux.HandleFunc("/debug/pprof/profile", pprof.Profile) - mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) - mux.HandleFunc("/debug/pprof/trace", pprof.Trace) - if s.EnableContentionProfiling { - goruntime.SetBlockProfileRate(1) - } - } - mux.Handle("/metrics", prometheus.Handler()) - - server := &http.Server{ - Addr: net.JoinHostPort(s.Address, strconv.Itoa(s.Port)), - Handler: mux, - } - glog.Fatal(server.ListenAndServe()) - }() - - federationClientset, err := federationclientset.NewForConfig(restclient.AddUserAgent(restClientCfg, "federation-controller-manager")) - if err != nil { - glog.Fatalf("Invalid API configuration: %v", err) - } - - run := func(stop <-chan struct{}) { - err := StartControllers(s, restClientCfg, stop) - glog.Fatalf("error running controllers: %v", err) - panic("unreachable") - } - - if !s.LeaderElection.LeaderElect { - run(nil) - // unreachable - } - - if err := ensureFederationNamespace(federationClientset, s.FederationOnlyNamespace); err != nil { - glog.Fatalf("Failed to ensure federation only namespace %s: %v", s.FederationOnlyNamespace, err) - } - - leaderElectionClient := kubernetes.NewForConfigOrDie(restclient.AddUserAgent(restClientCfg, "leader-election")) - eventBroadcaster := record.NewBroadcaster() - eventBroadcaster.StartLogging(glog.Infof) - eventBroadcaster.StartRecordingToSink(eventsink.NewFederatedEventSink(federationClientset)) - recorder := eventBroadcaster.NewRecorder(api.Scheme, v1.EventSource{Component: "controller-manager"}) - - id, err := os.Hostname() - if err != nil { - return err - } - - rl := resourcelock.ConfigMapLock{ - ConfigMapMeta: metav1.ObjectMeta{ - Namespace: s.FederationOnlyNamespace, - Name: "federation-controller-manager-leader-election", - Annotations: map[string]string{ - federationapi.FederationClusterSelectorAnnotation: federationapi.FederationOnlyClusterSelector, - }}, - Client: leaderElectionClient.CoreV1(), - LockConfig: resourcelock.ResourceLockConfig{ - Identity: id, - EventRecorder: recorder, - }, - } - - leaderelection.RunOrDie(leaderelection.LeaderElectionConfig{ - Lock: &rl, - LeaseDuration: s.LeaderElection.LeaseDuration.Duration, - RenewDeadline: s.LeaderElection.RenewDeadline.Duration, - RetryPeriod: s.LeaderElection.RetryPeriod.Duration, - Callbacks: leaderelection.LeaderCallbacks{ - OnStartedLeading: run, - OnStoppedLeading: func() { - glog.Fatalf("leaderelection lost") - }, - }, - }) - - panic("unreachable") -} - -func StartControllers(s *options.CMServer, restClientCfg *restclient.Config, stopChan <-chan struct{}) error { - minimizeLatency := false - - discoveryClient := discovery.NewDiscoveryClientForConfigOrDie(restClientCfg) - serverResources, err := discoveryClient.ServerResources() - if err != nil { - glog.Fatalf("Could not find resources from API Server: %v", err) - } - - clustercontroller.StartClusterController(restClientCfg, stopChan, s.ClusterMonitorPeriod.Duration) - - if controllerEnabled(s.Controllers, serverResources, servicecontroller.ControllerName, servicecontroller.RequiredResources, true) { - if controllerEnabled(s.Controllers, serverResources, servicednscontroller.ControllerName, servicecontroller.RequiredResources, true) { - serviceDNScontrollerClientset := federationclientset.NewForConfigOrDie(restclient.AddUserAgent(restClientCfg, servicednscontroller.UserAgentName)) - serviceDNSController, err := servicednscontroller.NewServiceDNSController(serviceDNScontrollerClientset, s.DnsProvider, s.DnsConfigFile, s.FederationName, s.ServiceDnsSuffix, s.ZoneName, s.ZoneID) - if err != nil { - glog.Fatalf("Failed to start service dns controller: %v", err) - } else { - go serviceDNSController.DNSControllerRun(s.ConcurrentServiceSyncs, wait.NeverStop) - } - } - - glog.V(3).Infof("Loading client config for service controller %q", servicecontroller.UserAgentName) - scClientset := federationclientset.NewForConfigOrDie(restclient.AddUserAgent(restClientCfg, servicecontroller.UserAgentName)) - serviceController := servicecontroller.New(scClientset) - go serviceController.Run(s.ConcurrentServiceSyncs, stopChan) - } - - adapterSpecificArgs := make(map[string]interface{}) - adapterSpecificArgs[federatedtypes.HpaKind] = &s.HpaScaleForbiddenWindow - for kind, federatedType := range federatedtypes.FederatedTypes() { - if controllerEnabled(s.Controllers, serverResources, federatedType.ControllerName, federatedType.RequiredResources, true) { - synccontroller.StartFederationSyncController(kind, federatedType.AdapterFactory, restClientCfg, stopChan, minimizeLatency, adapterSpecificArgs) - } - } - - if controllerEnabled(s.Controllers, serverResources, jobcontroller.ControllerName, jobcontroller.RequiredResources, true) { - glog.V(3).Infof("Loading client config for job controller %q", jobcontroller.UserAgentName) - jobClientset := federationclientset.NewForConfigOrDie(restclient.AddUserAgent(restClientCfg, jobcontroller.UserAgentName)) - jobController := jobcontroller.NewJobController(jobClientset) - glog.V(3).Infof("Running job controller") - go jobController.Run(s.ConcurrentJobSyncs, wait.NeverStop) - } - - if controllerEnabled(s.Controllers, serverResources, ingresscontroller.ControllerName, ingresscontroller.RequiredResources, true) { - glog.V(3).Infof("Loading client config for ingress controller %q", ingresscontroller.UserAgentName) - ingClientset := federationclientset.NewForConfigOrDie(restclient.AddUserAgent(restClientCfg, ingresscontroller.UserAgentName)) - ingressController := ingresscontroller.NewIngressController(ingClientset) - glog.V(3).Infof("Running ingress controller") - ingressController.Run(stopChan) - } - - select {} -} - -func controllerEnabled(controllers utilflag.ConfigurationMap, serverResources []*metav1.APIResourceList, controller string, requiredResources []schema.GroupVersionResource, defaultValue bool) bool { - controllerConfig, ok := controllers[controller] - if ok { - if controllerConfig == "false" { - glog.Infof("%s controller disabled by config", controller) - return false - } - if controllerConfig == "true" { - if !hasRequiredResources(serverResources, requiredResources) { - glog.Fatalf("%s controller enabled explicitly but API Server does not have required resources", controller) - panic("unreachable") - } - return true - } - } else if defaultValue { - if !hasRequiredResources(serverResources, requiredResources) { - glog.Warningf("%s controller disabled because API Server does not have required resources", controller) - return false - } - } - return defaultValue -} - -func hasRequiredResources(serverResources []*metav1.APIResourceList, requiredResources []schema.GroupVersionResource) bool { - for _, resource := range requiredResources { - found := false - for _, serverResource := range serverResources { - if serverResource.GroupVersion == resource.GroupVersion().String() { - for _, apiResource := range serverResource.APIResources { - if apiResource.Name == resource.Resource { - found = true - break - } - } - } - } - if !found { - return false - } - } - return true -} - -func ensureFederationNamespace(clientset *federationclientset.Clientset, namespace string) error { - ns := v1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: namespace, - Annotations: map[string]string{ - federationapi.FederationClusterSelectorAnnotation: federationapi.FederationOnlyClusterSelector, - }, - }, - } - // Probably this is the first operation by controller manager on api server. So retry the operation - // until timeout to handle scenario where api server is not yet ready. - err := wait.PollImmediate(apiserverRetryInterval, apiserverWaitTimeout, func() (bool, error) { - var err error - _, err = clientset.CoreV1().Namespaces().Get(namespace, metav1.GetOptions{}) - if err != nil { - if !errors.IsNotFound(err) { - glog.V(2).Infof("Failed to get namespace %s: %v", namespace, err) - return false, nil - } - _, err := clientset.CoreV1().Namespaces().Create(&ns) - if err != nil { - glog.V(2).Infof("Failed to create namespace %s: %v", namespace, err) - return false, nil - } - } - return true, nil - }) - return err -} diff --git a/federation/cmd/federation-controller-manager/app/controllermanager_test.go b/federation/cmd/federation-controller-manager/app/controllermanager_test.go deleted file mode 100644 index 0d44fe709d2..00000000000 --- a/federation/cmd/federation-controller-manager/app/controllermanager_test.go +++ /dev/null @@ -1,88 +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 app - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - utilflag "k8s.io/apiserver/pkg/util/flag" - ingresscontroller "k8s.io/kubernetes/federation/pkg/federation-controller/ingress" - "testing" -) - -func TestControllerEnabled(t *testing.T) { - - testCases := []struct { - controllersConfig utilflag.ConfigurationMap - serverResources []*metav1.APIResourceList - controller string - requiredResources []schema.GroupVersionResource - defaultValue bool - expectedResult bool - }{ - // no override, API server has Ingress enabled - { - controllersConfig: utilflag.ConfigurationMap{}, - serverResources: []*metav1.APIResourceList{ - { - GroupVersion: "extensions/v1beta1", - APIResources: []metav1.APIResource{ - {Name: "ingresses", Namespaced: true, Kind: "Ingress"}, - }, - }, - }, - controller: ingresscontroller.ControllerName, - requiredResources: ingresscontroller.RequiredResources, - defaultValue: true, - expectedResult: true, - }, - // no override, API server has Ingress disabled - { - controllersConfig: utilflag.ConfigurationMap{}, - serverResources: []*metav1.APIResourceList{}, - controller: ingresscontroller.ControllerName, - requiredResources: ingresscontroller.RequiredResources, - defaultValue: true, - expectedResult: false, - }, - // API server has Ingress enabled, override config to disable Ingress controller - { - controllersConfig: utilflag.ConfigurationMap{ - ingresscontroller.ControllerName: "false", - }, - serverResources: []*metav1.APIResourceList{ - { - GroupVersion: "extensions/v1beta1", - APIResources: []metav1.APIResource{ - {Name: "ingresses", Namespaced: true, Kind: "Ingress"}, - }, - }, - }, - controller: ingresscontroller.ControllerName, - requiredResources: ingresscontroller.RequiredResources, - defaultValue: true, - expectedResult: false, - }, - } - - for _, test := range testCases { - actualEnabled := controllerEnabled(test.controllersConfig, test.serverResources, test.controller, test.requiredResources, test.defaultValue) - if actualEnabled != test.expectedResult { - t.Errorf("%s controller: expected %v, got %v", test.controller, test.expectedResult, actualEnabled) - } - } -} diff --git a/federation/cmd/federation-controller-manager/app/options/BUILD b/federation/cmd/federation-controller-manager/app/options/BUILD deleted file mode 100644 index 7794cd93656..00000000000 --- a/federation/cmd/federation-controller-manager/app/options/BUILD +++ /dev/null @@ -1,33 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["options.go"], - importpath = "k8s.io/kubernetes/federation/cmd/federation-controller-manager/app/options", - deps = [ - "//federation/pkg/dnsprovider:go_default_library", - "//pkg/apis/componentconfig:go_default_library", - "//pkg/client/leaderelectionconfig:go_default_library", - "//vendor/github.com/spf13/pflag:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/cmd/federation-controller-manager/app/options/options.go b/federation/cmd/federation-controller-manager/app/options/options.go deleted file mode 100644 index 470d61e6052..00000000000 --- a/federation/cmd/federation-controller-manager/app/options/options.go +++ /dev/null @@ -1,152 +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 options provides the flags used for the controller manager. - -package options - -import ( - "fmt" - "time" - - "github.com/spf13/pflag" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - utilflag "k8s.io/apiserver/pkg/util/flag" - "k8s.io/kubernetes/federation/pkg/dnsprovider" - "k8s.io/kubernetes/pkg/apis/componentconfig" - "k8s.io/kubernetes/pkg/client/leaderelectionconfig" -) - -type ControllerManagerConfiguration struct { - // port is the port that the controller-manager's http service runs on. - Port int `json:"port"` - // address is the IP address to serve on (set to 0.0.0.0 for all interfaces). - Address string `json:"address"` - // federation name. - FederationName string `json:"federationName"` - // zone name, like example.com. - ZoneName string `json:"zoneName"` - // zone ID, for use when zoneName is ambiguous. - ZoneID string `json:"zoneID"` - // ServiceDnsSuffix is the dns suffix to use when publishing federated services. - ServiceDnsSuffix string `json:"serviceDnsSuffix"` - // dnsProvider is the provider for dns services. - DnsProvider string `json:"dnsProvider"` - // dnsConfigFile is the path to the dns provider configuration file. - DnsConfigFile string `json:"dnsConfigFile"` - // concurrentServiceSyncs is the number of services that are - // allowed to sync concurrently. Larger number = more responsive service - // management, but more CPU (and network) load. - ConcurrentServiceSyncs int `json:"concurrentServiceSyncs"` - // concurrentReplicaSetSyncs is the number of ReplicaSets that are - // allowed to sync concurrently. Larger number = more responsive service - // management, but more CPU (and network) load. - ConcurrentReplicaSetSyncs int `json:"concurrentReplicaSetSyncs"` - // concurrentJobSyncs is the number of Jobs that are - // allowed to sync concurrently. Larger number = more responsive service - // management, but more CPU (and network) load. - ConcurrentJobSyncs int `json:"concurrentJobSyncs"` - // clusterMonitorPeriod is the period for syncing ClusterStatus in cluster controller. - ClusterMonitorPeriod metav1.Duration `json:"clusterMonitorPeriod"` - // APIServerQPS is the QPS to use while talking with federation apiserver. - APIServerQPS float32 `json:"federatedAPIQPS"` - // APIServerBurst is the burst to use while talking with federation apiserver. - APIServerBurst int `json:"federatedAPIBurst"` - // enableProfiling enables profiling via web interface host:port/debug/pprof/ - EnableProfiling bool `json:"enableProfiling"` - // enableContentionProfiling enables lock contention profiling, if enableProfiling is true. - EnableContentionProfiling bool `json:"enableContentionProfiling"` - // leaderElection defines the configuration of leader election client. - LeaderElection componentconfig.LeaderElectionConfiguration `json:"leaderElection"` - // contentType is contentType of requests sent to apiserver. - ContentType string `json:"contentType"` - // ConfigurationMap determining which controllers should be enabled or disabled - Controllers utilflag.ConfigurationMap `json:"controllers"` - // HpaScaleForbiddenWindow is the duration used by federation hpa controller to - // determine if it can move max and/or min replicas around (or not), of a cluster local - // hpa object, by comparing current time with the last scaled time of that cluster local hpa. - // Lower value will result in faster response to scalibility conditions achieved - // by cluster local hpas on local replicas, but too low a value can result in thrashing. - // Higher values will result in slower response to scalibility conditions on local replicas. - HpaScaleForbiddenWindow metav1.Duration `json:"HpaScaleForbiddenWindow"` - // pre-configured namespace name that would be created only in federation control plane - FederationOnlyNamespace string `json:"federationOnlyNamespaceName"` -} - -// CMServer is the main context object for the controller manager. -type CMServer struct { - ControllerManagerConfiguration - Master string - Kubeconfig string -} - -const ( - // FederatedControllerManagerPort is the default port for the federation controller manager status server. - // May be overridden by a flag at startup. - FederatedControllerManagerPort = 10253 -) - -// NewCMServer creates a new CMServer with a default config. -func NewCMServer() *CMServer { - s := CMServer{ - ControllerManagerConfiguration: ControllerManagerConfiguration{ - Port: FederatedControllerManagerPort, - Address: "0.0.0.0", - ConcurrentServiceSyncs: 10, - ConcurrentReplicaSetSyncs: 10, - ClusterMonitorPeriod: metav1.Duration{Duration: 40 * time.Second}, - ConcurrentJobSyncs: 10, - APIServerQPS: 20.0, - APIServerBurst: 30, - LeaderElection: leaderelectionconfig.DefaultLeaderElectionConfiguration(), - Controllers: make(utilflag.ConfigurationMap), - HpaScaleForbiddenWindow: metav1.Duration{Duration: 2 * time.Minute}, - FederationOnlyNamespace: "federation-only", - }, - } - return &s -} - -// AddFlags adds flags for a specific CMServer to the specified FlagSet -func (s *CMServer) AddFlags(fs *pflag.FlagSet) { - fs.IntVar(&s.Port, "port", s.Port, "The port that the controller-manager's http service runs on") - fs.Var(componentconfig.IPVar{Val: &s.Address}, "address", "The IP address to serve on (set to 0.0.0.0 for all interfaces)") - fs.StringVar(&s.FederationName, "federation-name", s.FederationName, "Federation name.") - fs.StringVar(&s.ZoneName, "zone-name", s.ZoneName, "Zone name, like example.com.") - fs.StringVar(&s.ZoneID, "zone-id", s.ZoneID, "Zone ID, needed if the zone name is not unique.") - fs.StringVar(&s.ServiceDnsSuffix, "service-dns-suffix", s.ServiceDnsSuffix, "DNS Suffix to use when publishing federated service names. Defaults to zone-name") - fs.IntVar(&s.ConcurrentServiceSyncs, "concurrent-service-syncs", s.ConcurrentServiceSyncs, "The number of service syncing operations that will be done concurrently. Larger number = faster endpoint updating, but more CPU (and network) load") - fs.IntVar(&s.ConcurrentReplicaSetSyncs, "concurrent-replicaset-syncs", s.ConcurrentReplicaSetSyncs, "The number of ReplicaSets syncing operations that will be done concurrently. Larger number = faster endpoint updating, but more CPU (and network) load") - fs.IntVar(&s.ConcurrentJobSyncs, "concurrent-job-syncs", s.ConcurrentJobSyncs, "The number of Jobs syncing operations that will be done concurrently. Larger number = faster endpoint updating, but more CPU (and network) load") - fs.DurationVar(&s.ClusterMonitorPeriod.Duration, "cluster-monitor-period", s.ClusterMonitorPeriod.Duration, "The period for syncing ClusterStatus in ClusterController.") - fs.BoolVar(&s.EnableProfiling, "profiling", true, "Enable profiling via web interface host:port/debug/pprof/") - fs.BoolVar(&s.EnableContentionProfiling, "contention-profiling", false, "Enable lock contention profiling, if profiling is enabled") - fs.StringVar(&s.Master, "master", s.Master, "The address of the federation API server (overrides any value in kubeconfig)") - fs.StringVar(&s.Kubeconfig, "kubeconfig", s.Kubeconfig, "Path to kubeconfig file with authorization and master location information.") - fs.StringVar(&s.ContentType, "kube-api-content-type", s.ContentType, "ContentType of requests sent to apiserver. Passing application/vnd.kubernetes.protobuf is an experimental feature now.") - fs.Float32Var(&s.APIServerQPS, "federated-api-qps", s.APIServerQPS, "QPS to use while talking with federation apiserver") - fs.IntVar(&s.APIServerBurst, "federated-api-burst", s.APIServerBurst, "Burst to use while talking with federation apiserver") - fs.StringVar(&s.DnsProvider, "dns-provider", s.DnsProvider, "DNS provider. Valid values are: "+fmt.Sprintf("%q", dnsprovider.RegisteredDnsProviders())) - fs.StringVar(&s.DnsConfigFile, "dns-provider-config", s.DnsConfigFile, "Path to config file for configuring DNS provider.") - fs.DurationVar(&s.HpaScaleForbiddenWindow.Duration, "hpa-scale-forbidden-window", s.HpaScaleForbiddenWindow.Duration, "The time window wrt cluster local hpa lastscale time, during which federated hpa would not move the hpa max/min replicas around") - fs.Var(&s.Controllers, "controllers", ""+ - "A set of key=value pairs that describe controller configuration "+ - "to enable/disable specific controllers. Key should be the resource name (like services) and value should be true or false. "+ - "For example: services=false,ingresses=false") - fs.StringVar(&s.FederationOnlyNamespace, "federation-only-namespace", s.FederationOnlyNamespace, "Name of the namespace that would be created only in federation control plane.") - leaderelectionconfig.BindFlags(&s.LeaderElection, fs) -} diff --git a/federation/cmd/federation-controller-manager/app/plugins.go b/federation/cmd/federation-controller-manager/app/plugins.go deleted file mode 100644 index ad6e0fb1ef9..00000000000 --- a/federation/cmd/federation-controller-manager/app/plugins.go +++ /dev/null @@ -1,27 +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 app - -// This file exists to force the desired plugin implementations to be linked. -// This should probably be part of some configuration fed into the build for a -// given binary target. -import ( - // DNS providers - _ "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/aws/route53" - _ "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/coredns" - _ "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns" -) diff --git a/federation/cmd/federation-controller-manager/controller-manager.go b/federation/cmd/federation-controller-manager/controller-manager.go deleted file mode 100644 index 69ba6f21ef5..00000000000 --- a/federation/cmd/federation-controller-manager/controller-manager.go +++ /dev/null @@ -1,52 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES 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" - - "github.com/spf13/pflag" - "k8s.io/apiserver/pkg/server/healthz" - "k8s.io/apiserver/pkg/util/flag" - "k8s.io/apiserver/pkg/util/logs" - "k8s.io/kubernetes/federation/cmd/federation-controller-manager/app" - "k8s.io/kubernetes/federation/cmd/federation-controller-manager/app/options" - _ "k8s.io/kubernetes/pkg/util/reflector/prometheus" // for reflector metric registration - _ "k8s.io/kubernetes/pkg/util/workqueue/prometheus" // for workqueue metric registration - "k8s.io/kubernetes/pkg/version/verflag" -) - -func init() { - healthz.DefaultHealthz() -} - -func main() { - s := options.NewCMServer() - s.AddFlags(pflag.CommandLine) - - flag.InitFlags() - logs.InitLogs() - defer logs.FlushLogs() - - verflag.PrintAndExitIfRequested() - - if err := app.Run(s); err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) - os.Exit(1) - } -} diff --git a/federation/cmd/genfeddocs/BUILD b/federation/cmd/genfeddocs/BUILD deleted file mode 100644 index 0a537a48d5c..00000000000 --- a/federation/cmd/genfeddocs/BUILD +++ /dev/null @@ -1,41 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_binary", - "go_library", -) - -go_binary( - name = "genfeddocs", - importpath = "k8s.io/kubernetes/federation/cmd/genfeddocs", - library = ":go_default_library", -) - -go_library( - name = "go_default_library", - srcs = ["gen_fed_docs.go"], - importpath = "k8s.io/kubernetes/federation/cmd/genfeddocs", - deps = [ - "//cmd/genutils:go_default_library", - "//federation/cmd/federation-apiserver/app:go_default_library", - "//federation/cmd/federation-controller-manager/app:go_default_library", - "//federation/cmd/kubefed/app:go_default_library", - "//federation/pkg/kubefed:go_default_library", - "//pkg/kubectl/cmd/util:go_default_library", - "//vendor/github.com/spf13/cobra/doc:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/cmd/genfeddocs/gen_fed_docs.go b/federation/cmd/genfeddocs/gen_fed_docs.go deleted file mode 100644 index 237120837c9..00000000000 --- a/federation/cmd/genfeddocs/gen_fed_docs.go +++ /dev/null @@ -1,71 +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 main - -import ( - "fmt" - "os" - - "github.com/spf13/cobra/doc" - "k8s.io/kubernetes/cmd/genutils" - fedapiservapp "k8s.io/kubernetes/federation/cmd/federation-apiserver/app" - fedcmapp "k8s.io/kubernetes/federation/cmd/federation-controller-manager/app" - kubefedapp "k8s.io/kubernetes/federation/cmd/kubefed/app" - "k8s.io/kubernetes/federation/pkg/kubefed" - cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" -) - -// Note: We have a separate binary for generating federation docs and kube docs because of the way api groups are api.Registry. -// If we import both kube-apiserver and federation-apiserver in the same binary then api groups from both kube and federation will get registered in both the apiservers -// and hence will produce incorrect flag values. -// We can potentially merge cmd/kubegendocs and this when we have fixed that problem. -func main() { - // use os.Args instead of "flags" because "flags" will mess up the man pages! - path := "" - module := "" - if len(os.Args) == 3 { - path = os.Args[1] - module = os.Args[2] - } else { - fmt.Fprintf(os.Stderr, "usage: %s [output directory] [module] \n", os.Args[0]) - os.Exit(1) - } - - outDir, err := genutils.OutDir(path) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to get output directory: %v\n", err) - os.Exit(1) - } - - switch module { - case "federation-apiserver": - // generate docs for federated-apiserver - apiserver := fedapiservapp.NewAPIServerCommand() - doc.GenMarkdownTree(apiserver, outDir) - case "federation-controller-manager": - // generate docs for kube-controller-manager - controllermanager := fedcmapp.NewControllerManagerCommand() - doc.GenMarkdownTree(controllermanager, outDir) - case "kubefed": - // generate docs for kubefed - kubefed := kubefed.NewKubeFedCommand(cmdutil.NewFactory(nil), os.Stdin, os.Stdout, os.Stderr, kubefedapp.GetDefaultServerImage(), kubefedapp.DefaultEtcdImage) - doc.GenMarkdownTree(kubefed, outDir) - default: - fmt.Fprintf(os.Stderr, "Module %s is not supported", module) - os.Exit(1) - } -} diff --git a/federation/cmd/kubefed/BUILD b/federation/cmd/kubefed/BUILD deleted file mode 100644 index 9a3734d9324..00000000000 --- a/federation/cmd/kubefed/BUILD +++ /dev/null @@ -1,36 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_binary", - "go_library", -) - -go_binary( - name = "kubefed", - importpath = "k8s.io/kubernetes/federation/cmd/kubefed", - library = ":go_default_library", -) - -go_library( - name = "go_default_library", - srcs = ["kubefed.go"], - importpath = "k8s.io/kubernetes/federation/cmd/kubefed", - deps = ["//federation/cmd/kubefed/app:go_default_library"], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/cmd/kubefed/app:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/cmd/kubefed/app/BUILD b/federation/cmd/kubefed/app/BUILD deleted file mode 100644 index 76307bbd723..00000000000 --- a/federation/cmd/kubefed/app/BUILD +++ /dev/null @@ -1,33 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["kubefed.go"], - importpath = "k8s.io/kubernetes/federation/cmd/kubefed/app", - deps = [ - "//federation/pkg/kubefed:go_default_library", - "//pkg/client/metrics/prometheus:go_default_library", - "//pkg/kubectl/cmd/util:go_default_library", - "//pkg/kubectl/util/logs:go_default_library", - "//pkg/version:go_default_library", - "//pkg/version/prometheus:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/cmd/kubefed/app/kubefed.go b/federation/cmd/kubefed/app/kubefed.go deleted file mode 100644 index 72ccb7faca6..00000000000 --- a/federation/cmd/kubefed/app/kubefed.go +++ /dev/null @@ -1,47 +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 app - -import ( - "fmt" - "os" - "strings" - - "k8s.io/kubernetes/federation/pkg/kubefed" - _ "k8s.io/kubernetes/pkg/client/metrics/prometheus" // for client metric registration - cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" - "k8s.io/kubernetes/pkg/kubectl/util/logs" - "k8s.io/kubernetes/pkg/version" - _ "k8s.io/kubernetes/pkg/version/prometheus" // for version metric registration -) - -const ( - hyperkubeImageName = "gcr.io/google_containers/hyperkube-amd64" - DefaultEtcdImage = "gcr.io/google_containers/etcd:3.1.10" -) - -func GetDefaultServerImage() string { - return fmt.Sprintf("%s:%s", hyperkubeImageName, strings.Replace(version.Get().String(), "+", "_", 1)) -} - -func Run() error { - logs.InitLogs() - defer logs.FlushLogs() - - cmd := kubefed.NewKubeFedCommand(cmdutil.NewFactory(nil), os.Stdin, os.Stdout, os.Stderr, GetDefaultServerImage(), DefaultEtcdImage) - return cmd.Execute() -} diff --git a/federation/cmd/kubefed/kubefed.go b/federation/cmd/kubefed/kubefed.go deleted file mode 100644 index 72a03efc6fd..00000000000 --- a/federation/cmd/kubefed/kubefed.go +++ /dev/null @@ -1,30 +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 main - -import ( - "os" - - "k8s.io/kubernetes/federation/cmd/kubefed/app" -) - -func main() { - if err := app.Run(); err != nil { - os.Exit(1) - } - os.Exit(0) -} diff --git a/federation/deploy/config.json.sample b/federation/deploy/config.json.sample deleted file mode 100644 index 9bb7e0a7329..00000000000 --- a/federation/deploy/config.json.sample +++ /dev/null @@ -1,83 +0,0 @@ -[ - { - "phase1": { - "num_nodes": 3, - "cluster_name": "cluster1-kubernetes", - "cloud_provider": "gce", - "cluster_cidr": "10.180.0.0/14", - "gce": { - "os_image": "ubuntu-1604-xenial-v20160420c", - "instance_type": "n1-standard-2", - "project": "", - "region": "us-central1", - "zone": "us-central1-a", - "network": "federation" - } - }, - "phase2": { - "docker_registry": "gcr.io/google-containers", - "kubernetes_version": "v1.4.0" - }, - "phase3": { - "run_addons": true, - "kube_proxy": true, - "dashboard": true, - "heapster": true, - "kube_dns": true - } - }, - { - "phase1": { - "num_nodes": 3, - "cluster_name": "cluster2-kubernetes", - "cloud_provider": "gce", - "cluster_cidr": "10.184.0.0/14", - "gce": { - "os_image": "ubuntu-1604-xenial-v20160420c", - "instance_type": "n1-standard-2", - "project": "", - "region": "us-central1", - "zone": "us-central1-b", - "network": "federation" - } - }, - "phase2": { - "docker_registry": "gcr.io/google-containers", - "kubernetes_version": "v1.4.0" - }, - "phase3": { - "run_addons": true, - "kube_proxy": true, - "dashboard": true, - "heapster": true, - "kube_dns": true - } - }, - { - "phase1": { - "num_nodes": 3, - "cluster_name": "cluster3-kubernetes", - "cloud_provider": "gce", - "cluster_cidr": "10.188.0.0/14", - "gce": { - "os_image": "ubuntu-1604-xenial-v20160420c", - "instance_type": "n1-standard-2", - "project": "", - "region": "us-central1", - "zone": "us-central1-f", - "network": "federation" - } - }, - "phase2": { - "docker_registry": "gcr.io/google-containers", - "kubernetes_version": "v1.4.0" - }, - "phase3": { - "run_addons": true, - "kube_proxy": true, - "dashboard": true, - "heapster": true, - "kube_dns": true - } - } -] diff --git a/federation/deploy/deploy.sh b/federation/deploy/deploy.sh deleted file mode 100755 index c8e2383df84..00000000000 --- a/federation/deploy/deploy.sh +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env bash - -# 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. - -# This script turns up/turns down Kubernetes clusters and federation -# components using the built hyperkube image. -# e.g. run as: -# FEDERATION_OUTPUT_ROOT="./_output" ./deploy.sh deploy_clusters -# -# will deploy the kubernetes clusters using the configuration specified -# in $FEDERATION_OUTPUT_ROOT/config.json. -# -# See config.json.sample for a config.json example. - -set -o errexit -set -o nounset -set -o pipefail - -KUBE_ROOT=$(dirname "${BASH_SOURCE}")/../.. - -# Provides the $KUBERNETES_PROVIDER variable and detect-project function -source "${KUBE_ROOT}/cluster/kube-util.sh" -# Provides logging facilities -source "${KUBE_ROOT}/cluster/lib/logging.sh" - -readonly KUBE_ANYWHERE_FEDERATION_IMAGE="gcr.io/madhusudancs-containers/kubernetes-anywhere-federation" -readonly KUBE_ANYWHERE_FEDERATION_VERSION="v0.9.0" -readonly KUBE_ANYWHERE_FEDERATION_CHARTS_IMAGE="gcr.io/madhusudancs-containers/federation-charts" -readonly KUBE_ANYWHERE_FEDERATION_CHARTS_VERSION="v0.9.1" - -readonly GOOGLE_APPLICATION_CREDENTIALS="${GOOGLE_APPLICATION_CREDENTIALS:-${HOME}/.config/gcloud/application_default_credentials.json}" -readonly KUBE_CONFIG_DIR="${KUBE_CONFIG_DIR:-${HOME}/.kube}" -readonly KUBE_CONFIG="${KUBE_CONFIG:-${HOME}/.kube/config}" - -function pull_installer() { - kube::log::status "Pulling installer images" - docker pull "${KUBE_ANYWHERE_FEDERATION_IMAGE}:${KUBE_ANYWHERE_FEDERATION_VERSION}" - docker pull "${KUBE_ANYWHERE_FEDERATION_CHARTS_IMAGE}:${KUBE_ANYWHERE_FEDERATION_CHARTS_VERSION}" -} - -function ensure_files() { - kube::log::status "Ensure provider is supported" - if [[ "${KUBERNETES_PROVIDER:-}" != "gce" && "${KUBERNETES_PROVIDER:-}" != "gke" ]]; then - echo "Supported providers: \"gce\", \"gke\"" - exit 1 - fi - - kube::log::status "Ensure credential files exist" - if [[ ! -f "${GOOGLE_APPLICATION_CREDENTIALS}" ]]; then - echo "Please ensure Google credentials file \""${GOOGLE_APPLICATION_CREDENTIALS}"\" exists." - exit 1 - fi - - if [[ ! -f "${KUBE_CONFIG}" ]]; then - echo "Please ensure kubeconfig file \""${KUBE_CONFIG}"\" exists." - exit 1 - fi -} - -function kube_action() { - : "${FEDERATION_OUTPUT_ROOT:?must be set}" - - local -r action="${1:-}" - kube::log::status "Action: ${action} clusters" - docker run \ - --user="$(id -u):$(id -g)" \ - -m 12G \ - -v "${GOOGLE_APPLICATION_CREDENTIALS}:/.config/gcloud/application_default_credentials.json:ro" \ - -v "${KUBE_CONFIG_DIR}:/.kube" \ - -v "${FEDERATION_OUTPUT_ROOT}:/_output" \ - "${KUBE_ANYWHERE_FEDERATION_IMAGE}:${KUBE_ANYWHERE_FEDERATION_VERSION}" \ - "${action}" -} - -function federation_action() { - : "${FEDERATION_OUTPUT_ROOT:?must be set}" - - local -r action="${1:-}" - kube::log::status "Action: ${action} federation components" - # For non-GKE clusters just mounting kubeconfig is sufficient. But we - # need gcloud credentials for GKE clusters, so we pass both kubeconfig - # and gcloud credentials - docker run \ - -m 12G \ - -v "${GOOGLE_APPLICATION_CREDENTIALS}:/root/.config/gcloud/application_default_credentials.json:ro" \ - -v "${KUBE_CONFIG}:/root/.kube/config" \ - -v "${FEDERATION_OUTPUT_ROOT}:/_output" \ - "${KUBE_ANYWHERE_FEDERATION_CHARTS_IMAGE}:${KUBE_ANYWHERE_FEDERATION_CHARTS_VERSION}" \ - "${action}" -} - -function redeploy_federation() { - : "${FEDERATION_OUTPUT_ROOT:?must be set}" - - local -r action="${1:-}" - kube::log::status "${action} federation components" - docker run \ - -m 12G \ - -v "${KUBE_CONFIG}:/root/.kube/config:ro" \ - -v "${FEDERATION_OUTPUT_ROOT}:/_output" \ - "${KUBE_ANYWHERE_FEDERATION_CHARTS_IMAGE}:${KUBE_ANYWHERE_FEDERATION_CHARTS_VERSION}" \ - "${action}" -} - -readonly ACTION="${1:-}" -case "${ACTION}" in - "") - echo 'Action must be one of [init, deploy_clusters, deploy_federation, \ - destroy_federation, destroy_clusters, redeploy_federation], \ - got: '"${ACTION}" - exit 1 - ;; - "init") - pull_installer - ;; - "deploy_clusters") - ensure_files - kube_action deploy - ;; - "deploy_federation") - ensure_files - federation_action deploy - ;; - "destroy_federation") - federation_action destroy - ;; - "destroy_clusters") - kube_action destroy - ;; - "redeploy_federation") - redeploy_federation - ;; -esac diff --git a/federation/develop/BUILD b/federation/develop/BUILD deleted file mode 100644 index 7e76248ad95..00000000000 --- a/federation/develop/BUILD +++ /dev/null @@ -1,14 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/develop/develop.sh b/federation/develop/develop.sh deleted file mode 100755 index 1a6859b98dc..00000000000 --- a/federation/develop/develop.sh +++ /dev/null @@ -1,196 +0,0 @@ -#!/usr/bin/env bash - -# 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. - -# This script will build the hyperkube image and push it to the repository -# referred to by KUBE_REGISTRY. The image will be given a version tag with -# the value from KUBE_VERSION. -# e.g. run as: -# KUBE_REGISTRY=localhost:5000/anushku \ -# KUBE_VERSION=1.3.0-dev ./build.sh build_image -# -# will build the Docker images with the specified repository name and the -# image version tag. - -set -o errexit -set -o nounset -set -o pipefail - -KUBE_ROOT="$(dirname "${BASH_SOURCE}")/../.." -DEPLOY_ROOT="${KUBE_ROOT}/federation/deploy" -CUR_ROOT="$(pwd)" - -source "${KUBE_ROOT}/build/common.sh" -source "${KUBE_ROOT}/build/util.sh" -# Provides the detect-project function -source "${KUBE_ROOT}/cluster/kube-util.sh" -# Provides logging facilities -source "${KUBE_ROOT}/cluster/lib/logging.sh" - -readonly TMP_DIR="$(mktemp -d)" -readonly FEDERATION_OUTPUT_ROOT="${LOCAL_OUTPUT_ROOT}/federation" -readonly VERSIONS_FILE="${FEDERATION_OUTPUT_ROOT}/versions" - -if [[ "${KUBERNETES_PROVIDER}" == "gke" || "${KUBERNETES_PROVIDER}" == "gce" ]]; then - detect-project - readonly KUBE_PROJECT="${KUBE_PROJECT:-${PROJECT:-}}" - readonly KUBE_REGISTRY="${KUBE_REGISTRY:-gcr.io/${KUBE_PROJECT}}" -else - readonly KUBE_PROJECT="${KUBE_PROJECT:-${PROJECT:-federation}}" - readonly KUBE_REGISTRY="${KUBE_REGISTRY:-localhost:5000/${KUBE_PROJECT}}" -fi - -# In dev environments this value must be recomputed after build. See -# the build_image() function. So not making it readonly -KUBE_VERSION="${KUBE_VERSION:-}" - - -function cleanup { - rm -rf "${TMP_DIR}" - cd "${CUR_ROOT}" -} -trap cleanup EXIT - -function dirty_sha() { - local -r index="${KUBE_ROOT}/.git/index" - local -r objects_dir="${KUBE_ROOT}/.git/objects" - - local -r tmp_dir="${TMP_DIR}/.git" - local -r tmp_index="${tmp_dir}/index" - local -r tmp_objects_dir="${tmp_dir}/objects" - - mkdir -p "${tmp_objects_dir}" - cp "${index}" "${tmp_index}" - - local -r files=$(git ls-files -m -o -d --exclude-standard) - GIT_INDEX_FILE="${tmp_index}" git add ${files} - GIT_ALTERNATE_OBJECT_DIRECTORIES="${objects_dir}" GIT_OBJECT_DIRECTORY="${tmp_objects_dir}" GIT_INDEX_FILE="${tmp_index}" git write-tree -} - -function build_binaries() { - cd "${KUBE_ROOT}" - kube::build::verify_prereqs - kube::build::build_image - kube::build::run_build_command make WHAT="cmd/kubectl cmd/hyperkube" - kube::build::copy_output -} - -function build_image() { - # Recompute KUBE_VERSION because it might have changed after rebuild. - local kube_version="" - if [[ -n "${KUBE_VERSION:-}" ]]; then - kube_version="${KUBE_VERSION}" - else - kube_version="$(kube::release::semantic_image_tag_version)" - # Also append the dirty tree SHA to keep the versions unique across - # builds. - if [[ "${kube_version}" == *-dirty ]]; then - kube_version+=".$(dirty_sha)" - fi - fi - - # Write the generated version to the output versions file so that we can - # reuse it. - mkdir -p "${FEDERATION_OUTPUT_ROOT}" - echo "{ - \"KUBE_VERSION\": \"${kube_version}\" -}" > "${VERSIONS_FILE}" - kube::log::status "Wrote to version file ${VERSIONS_FILE}: ${kube_version}" - - BASEIMAGE="ubuntu:16.04" \ - REGISTRY="${KUBE_REGISTRY}" \ - VERSION="${kube_version}" \ - make -C "${KUBE_ROOT}/cluster/images/hyperkube" build -} - -function get_version() { - local kube_version="" - if [[ -n "${KUBE_VERSION:-}" ]]; then - kube_version="${KUBE_VERSION}" - else - # Read the version back from the versions file if no version is given. - kube_version="$(cat ${VERSIONS_FILE} | python -c '\ -import json, sys;\ -print json.load(sys.stdin)["KUBE_VERSION"]')" - fi - echo "${kube_version}" -} - -function push() { - local -r kube_version="$(get_version)" - - kube::log::status "Pushing hyperkube image to the registry" - gcloud docker -- push "${KUBE_REGISTRY}/hyperkube-amd64:${kube_version}" -} - -readonly ACTION="${1:-}" -case "${ACTION}" in - "") - echo 'Action must be one of [init, build_binaries, build_image, push, \ - deploy_clusters, deploy_federation, destroy_federation, destroy_clusters \ - redeploy_federation], \ - got: '"${ACTION}" - exit 1 - ;; - "build_binaries") - build_binaries - ;; - "build_image") - build_image - ;; - "push") - push - ;; - # Following functions belong to deploy.sh, they are driven from here - # convenience during development because FEDERATION_OUTPUT_ROOT is - # already defined during development here in this script. Also, we - # execute the following commands in their own subshells to avoid them - # messing with variables in this script. - "init") - ( - "${DEPLOY_ROOT}/deploy.sh" init - ) - ;; - "deploy_clusters") - ( - export FEDERATION_OUTPUT_ROOT - "${DEPLOY_ROOT}/deploy.sh" deploy_clusters - ) - ;; - "deploy_federation") - ( - export FEDERATION_OUTPUT_ROOT - "${DEPLOY_ROOT}/deploy.sh" deploy_federation - ) - ;; - "destroy_federation") - ( - export FEDERATION_OUTPUT_ROOT - "${DEPLOY_ROOT}/deploy.sh" destroy_federation - ) - ;; - "destroy_clusters") - ( - export FEDERATION_OUTPUT_ROOT - "${DEPLOY_ROOT}/deploy.sh" destroy_clusters - ) - ;; - "redeploy_federation") - ( - export FEDERATION_OUTPUT_ROOT - "${DEPLOY_ROOT}/deploy.sh" redeploy_federation - ) - ;; -esac diff --git a/federation/develop/kubefed.sh b/federation/develop/kubefed.sh deleted file mode 100755 index 4c3caf81d21..00000000000 --- a/federation/develop/kubefed.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash - -# 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. - -set -o errexit -set -o nounset -set -o pipefail - -# "-=-=-=-=-=-=-=-=-=-=" -# This script is only for CI testing purposes. Don't use it in production. -# "-=-=-=-=-=-=-=-=-=-=" - -KUBE_ROOT=${KUBE_ROOT:-$(dirname "${BASH_SOURCE}")/../..} -source "${KUBE_ROOT}/cluster/clientbin.sh" - -# If KUBEFED_PATH isn't set, gather up the list of likely places and use ls -# to find the latest one. -if [[ -z "${KUBEFED_PATH:-}" ]]; then - kubefed=$( get_bin "kubefed" "federation/cmd/kubefed" ) - - if [[ ! -x "$kubefed" ]]; then - print_error "kubefed" - exit 1 - fi -elif [[ ! -x "${KUBEFED_PATH}" ]]; then - { - echo "KUBEFED_PATH environment variable set to '${KUBEFED_PATH}', but " - echo "this doesn't seem to be a valid executable." - } >&2 - exit 1 -fi -kubefed="${KUBEFED_PATH:-${kubefed}}" - -# Use the arguments to the script if it is set, a null string -# otherwise. -"${kubefed}" "${@+$@}" diff --git a/federation/develop/push-federation-images.sh b/federation/develop/push-federation-images.sh deleted file mode 100755 index 06c5f0821f1..00000000000 --- a/federation/develop/push-federation-images.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -# Copyright 2014 The Kubernetes Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Pushes federation container images to existing repositories - -set -o errexit -set -o nounset -set -o pipefail - -KUBE_ROOT=$(dirname "${BASH_SOURCE}")/../.. - -make -C "${KUBE_ROOT}/federation/" build_image -make -C "${KUBE_ROOT}/federation/" push diff --git a/federation/docs/api-reference/README.md b/federation/docs/api-reference/README.md deleted file mode 100644 index 7a361053efe..00000000000 --- a/federation/docs/api-reference/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# API Reference - -Federation API server supports the following group versions: - -* federation/v1beta1: [operations](https://htmlpreview.github.io/?https://github.com/kubernetes/kubernetes/blob/HEAD/federation/docs/api-reference/federation/v1beta1/operations.html), [model definitions](https://htmlpreview.github.io/?https://github.com/kubernetes/kubernetes/blob/HEAD/federation/docs/api-reference/federation/v1beta1/definitions.html) -* v1: [operations](https://htmlpreview.github.io/?https://github.com/kubernetes/kubernetes/blob/HEAD/federation/docs/api-reference/v1/operations.html), [model definitions](https://htmlpreview.github.io/?https://github.com/kubernetes/kubernetes/blob/HEAD/federation/docs/api-reference/v1/definitions.html) -* extensions/v1beta1: [operations](https://htmlpreview.github.io/?https://github.com/kubernetes/kubernetes/blob/HEAD/federation/docs/api-reference/extensions/v1beta1/operations.html), [model definitions](https://htmlpreview.github.io/?https://github.com/kubernetes/kubernetes/blob/HEAD/federation/docs/api-reference/extensions/v1beta1/definitions.html) - - - -[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/federation/docs/api-reference/README.md?pixel)]() - diff --git a/federation/docs/api-reference/extensions/v1beta1/definitions.html b/federation/docs/api-reference/extensions/v1beta1/definitions.html deleted file mode 100755 index b0ebdc07fae..00000000000 --- a/federation/docs/api-reference/extensions/v1beta1/definitions.html +++ /dev/null @@ -1,7374 +0,0 @@ - - - - - - -Top Level API Objects - - - - -
      - -
      -

      Definitions

      -
      -
      -

      v1beta1.DeploymentStatus

      -
      -

      DeploymentStatus is the most recently observed status of the Deployment.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      observedGeneration

      The generation observed by the deployment controller.

      false

      integer (int64)

      replicas

      Total number of non-terminated pods targeted by this deployment (their labels match the selector).

      false

      integer (int32)

      updatedReplicas

      Total number of non-terminated pods targeted by this deployment that have the desired template spec.

      false

      integer (int32)

      readyReplicas

      Total number of ready pods targeted by this deployment.

      false

      integer (int32)

      availableReplicas

      Total number of available pods (ready for at least minReadySeconds) targeted by this deployment.

      false

      integer (int32)

      unavailableReplicas

      Total number of unavailable pods targeted by this deployment. This is the total number of pods that are still required for the deployment to have 100% available capacity. They may either be pods that are running but not yet available or pods that still have not been created.

      false

      integer (int32)

      conditions

      Represents the latest available observations of a deployment’s current state.

      false

      v1beta1.DeploymentCondition array

      collisionCount

      Count of hash collisions for the Deployment. The Deployment controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ReplicaSet.

      false

      integer (int32)

      - -
      -
      -

      v1.APIResourceList

      -
      -

      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.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      groupVersion

      groupVersion is the group and version this APIResourceList is for.

      true

      string

      resources

      resources contains the name of the resources and if they are namespaced.

      true

      v1.APIResource array

      - -
      -
      -

      v1.Affinity

      -
      -

      Affinity is a group of affinity scheduling rules.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      nodeAffinity

      Describes node affinity scheduling rules for the pod.

      false

      v1.NodeAffinity

      podAffinity

      Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)).

      false

      v1.PodAffinity

      podAntiAffinity

      Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)).

      false

      v1.PodAntiAffinity

      - -
      -
      -

      v1beta1.DaemonSetStatus

      -
      -

      DaemonSetStatus represents the current status of a daemon set.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      currentNumberScheduled

      The number of nodes that are running at least 1 daemon pod and are supposed to run the daemon pod. More info: https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/

      true

      integer (int32)

      numberMisscheduled

      The number of nodes that are running the daemon pod, but are not supposed to run the daemon pod. More info: https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/

      true

      integer (int32)

      desiredNumberScheduled

      The total number of nodes that should be running the daemon pod (including nodes correctly running the daemon pod). More info: https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/

      true

      integer (int32)

      numberReady

      The number of nodes that should be running the daemon pod and have one or more of the daemon pod running and ready.

      true

      integer (int32)

      observedGeneration

      The most recent generation observed by the daemon set controller.

      false

      integer (int64)

      updatedNumberScheduled

      The total number of nodes that are running updated daemon pod

      false

      integer (int32)

      numberAvailable

      The number of nodes that should be running the daemon pod and have one or more of the daemon pod running and available (ready for at least spec.minReadySeconds)

      false

      integer (int32)

      numberUnavailable

      The number of nodes that should be running the daemon pod and have none of the daemon pod running and available (ready for at least spec.minReadySeconds)

      false

      integer (int32)

      collisionCount

      Count of hash collisions for the DaemonSet. The DaemonSet controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ControllerRevision.

      false

      integer (int32)

      - -
      -
      -

      v1.NodeSelectorTerm

      -
      -

      A null or empty node selector term matches no objects.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      matchExpressions

      Required. A list of node selector requirements. The requirements are ANDed.

      true

      v1.NodeSelectorRequirement array

      - -
      -
      -

      v1.Preconditions

      -
      -

      Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      uid

      Specifies the target UID.

      false

      types.UID

      - -
      -
      -

      v1.ObjectFieldSelector

      -
      -

      ObjectFieldSelector selects an APIVersioned field of an object.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      apiVersion

      Version of the schema the FieldPath is written in terms of, defaults to "v1".

      false

      string

      fieldPath

      Path of the field to select in the specified API version.

      true

      string

      - -
      -
      -

      v1.SELinuxOptions

      -
      -

      SELinuxOptions are the labels to be applied to the container

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      user

      User is a SELinux user label that applies to the container.

      false

      string

      role

      Role is a SELinux role label that applies to the container.

      false

      string

      type

      Type is a SELinux type label that applies to the container.

      false

      string

      level

      Level is SELinux level label that applies to the container.

      false

      string

      - -
      -
      -

      v1beta1.IngressSpec

      -
      -

      IngressSpec describes the Ingress the user wishes to exist.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      backend

      A default backend capable of servicing requests that don’t match any rule. At least one of backend or rules must be specified. This field is optional to allow the loadbalancer controller or defaulting logic to specify a global default.

      false

      v1beta1.IngressBackend

      tls

      TLS configuration. Currently the Ingress only supports a single TLS port, 443. If multiple members of this list specify different hosts, they will be multiplexed on the same port according to the hostname specified through the SNI TLS extension, if the ingress controller fulfilling the ingress supports SNI.

      false

      v1beta1.IngressTLS array

      rules

      A list of host rules used to configure the Ingress. If unspecified, or no rule matches, all traffic is sent to the default backend.

      false

      v1beta1.IngressRule array

      - -
      -
      -

      v1.VolumeMount

      -
      -

      VolumeMount describes a mounting of a Volume within a container.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      This must match the Name of a Volume.

      true

      string

      readOnly

      Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false.

      false

      boolean

      false

      mountPath

      Path within the container at which the volume should be mounted. Must not contain :.

      true

      string

      subPath

      Path within the volume from which the container’s volume should be mounted. Defaults to "" (volume’s root).

      false

      string

      mountPropagation

      mountPropagation determines how mounts are propagated from the host to container and the other way around. When not set, MountPropagationHostToContainer is used. This field is alpha in 1.8 and can be reworked or removed in a future release.

      false

      v1.MountPropagationMode

      - -
      -
      -

      v1.MountPropagationMode

      - -
      -
      -

      v1.DownwardAPIProjection

      -
      -

      Represents downward API info for projecting into a projected volume. Note that this is identical to a downwardAPI volume source without the default mode.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      items

      Items is a list of DownwardAPIVolume file

      false

      v1.DownwardAPIVolumeFile array

      - -
      -
      -

      v1.LabelSelector

      -
      -

      A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      matchLabels

      matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.

      false

      object

      matchExpressions

      matchExpressions is a list of label selector requirements. The requirements are ANDed.

      false

      v1.LabelSelectorRequirement array

      - -
      -
      -

      v1beta1.IngressBackend

      -
      -

      IngressBackend describes all endpoints for a given service and port.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      serviceName

      Specifies the name of the referenced service.

      true

      string

      servicePort

      Specifies the port of the referenced service.

      true

      string

      - -
      -
      -

      v1beta1.ReplicaSetList

      -
      -

      ReplicaSetList is a collection of ReplicaSets.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      items

      List of ReplicaSets. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller

      true

      v1beta1.ReplicaSet array

      - -
      -
      -

      v1.CephFSVolumeSource

      -
      -

      Represents a Ceph Filesystem mount that lasts the lifetime of a pod Cephfs volumes do not support ownership management or SELinux relabeling.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      monitors

      Required: Monitors is a collection of Ceph monitors More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it

      true

      string array

      path

      Optional: Used as the mounted root, rather than the full Ceph tree, default is /

      false

      string

      user

      Optional: User is the rados user name, default is admin More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it

      false

      string

      secretFile

      Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it

      false

      string

      secretRef

      Optional: SecretRef is reference to the authentication secret for User, default is empty. More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it

      false

      v1.LocalObjectReference

      readOnly

      Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://releases.k8s.io/HEAD/examples/volumes/cephfs/README.md#how-to-use-it

      false

      boolean

      false

      - -
      -
      -

      v1beta1.IngressStatus

      -
      -

      IngressStatus describe the current state of the Ingress.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      loadBalancer

      LoadBalancer contains the current status of the load-balancer.

      false

      v1.LoadBalancerStatus

      - -
      -
      -

      v1.DownwardAPIVolumeSource

      -
      -

      DownwardAPIVolumeSource represents a volume containing downward API info. Downward API volumes support ownership management and SELinux relabeling.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      items

      Items is a list of downward API volume file

      false

      v1.DownwardAPIVolumeFile array

      defaultMode

      Optional: mode bits to use on created files by default. Must be a value between 0 and 0777. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.

      false

      integer (int32)

      - -
      -
      -

      v1beta1.ReplicaSetCondition

      -
      -

      ReplicaSetCondition describes the state of a replica set at a certain point.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      type

      Type of replica set condition.

      true

      string

      status

      Status of the condition, one of True, False, Unknown.

      true

      string

      lastTransitionTime

      The last time the condition transitioned from one status to another.

      false

      string

      reason

      The reason for the condition’s last transition.

      false

      string

      message

      A human readable message indicating details about the transition.

      false

      string

      - -
      -
      -

      v1.GCEPersistentDiskVolumeSource

      -
      -

      Represents a Persistent Disk resource in Google Compute Engine.

      -
      -
      -

      A GCE PD must exist before mounting to a container. The disk must also be in the same GCE project and zone as the kubelet. A GCE PD can only be mounted as read/write once or read-only many times. GCE PDs support ownership management and SELinux relabeling.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      pdName

      Unique name of the PD resource in GCE. Used to identify the disk in GCE. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk

      true

      string

      fsType

      Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk

      false

      string

      partition

      The partition in the volume that you want to mount. If omitted, the default is to mount by volume name. Examples: For volume /dev/sda1, you specify the partition as "1". Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk

      false

      integer (int32)

      readOnly

      ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk

      false

      boolean

      false

      - -
      -
      -

      v1beta1.RollingUpdateDeployment

      -
      -

      Spec to control the desired behavior of rolling update.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      maxUnavailable

      The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding down. This can not be 0 if MaxSurge is 0. By default, a fixed value of 1 is used. Example: when this is set to 30%, the old RC can be scaled down to 70% of desired pods immediately when the rolling update starts. Once new pods are ready, old RC can be scaled down further, followed by scaling up the new RC, ensuring that the total number of pods available at all times during the update is at least 70% of desired pods.

      false

      string

      maxSurge

      The maximum number of pods that can be scheduled above the desired number of pods. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up. By default, a value of 1 is used. Example: when this is set to 30%, the new RC can be scaled up immediately when the rolling update starts, such that the total number of old and new pods do not exceed 130% of desired pods. Once old pods have been killed, new RC can be scaled up further, ensuring that total number of pods running at any time during the update is atmost 130% of desired pods.

      false

      string

      - -
      -
      -

      v1beta1.HTTPIngressRuleValue

      -
      -

      HTTPIngressRuleValue is a list of http selectors pointing to backends. In the example: http://<host>/<path>?<searchpart> → backend where where parts of the url correspond to RFC 3986, this resource will be used to match against everything after the last / and before the first ? or #.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      paths

      A collection of paths that map requests to backends.

      true

      v1beta1.HTTPIngressPath array

      - -
      -
      -

      v1.ConfigMapVolumeSource

      -
      -

      Adapts a ConfigMap into a volume.

      -
      -
      -

      The contents of the target ConfigMap’s Data field will be presented in a volume as files using the keys in the Data field as the file names, unless the items element is populated with specific mappings of keys to paths. ConfigMap volumes support ownership management and SELinux relabeling.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      false

      string

      items

      If unspecified, each key-value pair in the Data field of the referenced ConfigMap will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the ConfigMap, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the .. path or start with ...

      false

      v1.KeyToPath array

      defaultMode

      Optional: mode bits to use on created files by default. Must be a value between 0 and 0777. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.

      false

      integer (int32)

      optional

      Specify whether the ConfigMap or it’s keys must be defined

      false

      boolean

      false

      - -
      -
      -

      v1.GitRepoVolumeSource

      -
      -

      Represents a volume that is populated with the contents of a git repository. Git repo volumes do not support ownership management. Git repo volumes support SELinux relabeling.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      repository

      Repository URL

      true

      string

      revision

      Commit hash for the specified revision.

      false

      string

      directory

      Target directory name. Must not contain or start with ... If . is supplied, the volume directory will be the git repository. Otherwise, if specified, the volume will contain the git repository in the subdirectory with the given name.

      false

      string

      - -
      -
      -

      v1.SecretEnvSource

      -
      -

      SecretEnvSource selects a Secret to populate the environment variables with.

      -
      -
      -

      The contents of the target Secret’s Data field will represent the key-value pairs as environment variables.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      false

      string

      optional

      Specify whether the Secret must be defined

      false

      boolean

      false

      - -
      -
      -

      v1.PortworxVolumeSource

      -
      -

      PortworxVolumeSource represents a Portworx volume resource.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      volumeID

      VolumeID uniquely identifies a Portworx volume

      true

      string

      fsType

      FSType represents the filesystem type to mount Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified.

      false

      string

      readOnly

      Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.

      false

      boolean

      false

      - -
      -
      -

      v1.Capabilities

      -
      -

      Adds and removes POSIX capabilities from running containers.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      add

      Added capabilities

      false

      v1.Capability array

      drop

      Removed capabilities

      false

      v1.Capability array

      - -
      -
      -

      v1.Initializer

      -
      -

      Initializer is information about an initializer that has not yet completed.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      name of the process that is responsible for initializing this object.

      true

      string

      - -
      -
      -

      v1.LocalObjectReference

      -
      -

      LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      false

      string

      - -
      -
      -

      v1.ProjectedVolumeSource

      -
      -

      Represents a projected volume source

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      sources

      list of volume projections

      true

      v1.VolumeProjection array

      defaultMode

      Mode bits to use on created files by default. Must be a value between 0 and 0777. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.

      false

      integer (int32)

      - -
      -
      -

      v1.ExecAction

      -
      -

      ExecAction describes a "run in container" action.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      command

      Command is the command line to execute inside the container, the working directory for the command is root (/) in the container’s filesystem. The command is simply exec’d, it is not run inside a shell, so traditional shell instructions ('

      ', etc) won’t work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy.

      false

      string array

      - -
      -
      -

      v1beta1.RollingUpdateDaemonSet

      -
      -

      Spec to control the desired behavior of daemon set rolling update.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      maxUnavailable

      The maximum number of DaemonSet pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of total number of DaemonSet pods at the start of the update (ex: 10%). Absolute number is calculated from percentage by rounding up. This cannot be 0. Default value is 1. Example: when this is set to 30%, at most 30% of the total number of nodes that should be running the daemon pod (i.e. status.desiredNumberScheduled) can have their pods stopped for an update at any given time. The update starts by stopping at most 30% of those DaemonSet pods and then brings up new DaemonSet pods in their place. Once the new pods are available, it then proceeds onto other DaemonSet pods, thus ensuring that at least 70% of original number of DaemonSet pods are available at all times during the update.

      false

      string

      - -
      -
      -

      v1.ObjectMeta

      -
      -

      ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      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: http://kubernetes.io/docs/user-guide/identifiers#names

      false

      string

      generateName

      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.
      -
      -If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header).
      -
      -Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency

      false

      string

      namespace

      Namespace defines the space within 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.
      -
      -Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces

      false

      string

      selfLink

      SelfLink is a URL representing this object. Populated by the system. Read-only.

      false

      string

      uid

      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.
      -
      -Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids

      false

      string

      resourceVersion

      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.
      -
      -Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency

      false

      string

      generation

      A sequence number representing a specific generation of the desired state. Populated by the system. Read-only.

      false

      integer (int64)

      creationTimestamp

      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.
      -
      -Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      string

      deletionTimestamp

      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 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.
      -
      -Populated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      string

      deletionGracePeriodSeconds

      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.

      false

      integer (int64)

      labels

      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: http://kubernetes.io/docs/user-guide/labels

      false

      object

      annotations

      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: http://kubernetes.io/docs/user-guide/annotations

      false

      object

      ownerReferences

      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.

      false

      v1.OwnerReference array

      initializers

      An initializer is a controller which enforces some system invariant at object creation time. This field is a list of initializers that have not yet acted on this object. If nil or empty, this object has been completely initialized. Otherwise, the object is considered uninitialized and is hidden (in list/watch and get calls) from clients that haven’t explicitly asked to observe uninitialized objects.
      -
      -When an object is created, the system will populate this list with the current set of initializers. Only privileged users may set or modify this list. Once it is empty, it may not be modified further by any user.

      false

      v1.Initializers

      finalizers

      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.

      false

      string array

      clusterName

      The name of the cluster which the object belongs to. This is used to distinguish resources with same name and namespace in different clusters. This field is not set anywhere right now and apiserver is going to ignore it if set in create or update request.

      false

      string

      - -
      -
      -

      v1beta1.ReplicaSetSpec

      -
      -

      ReplicaSetSpec is the specification of a ReplicaSet.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      replicas

      Replicas is the number of desired replicas. This is a pointer to distinguish between explicit zero and unspecified. Defaults to 1. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller

      false

      integer (int32)

      minReadySeconds

      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)

      false

      integer (int32)

      selector

      Selector is a label query over pods that should match the replica count. If the selector is empty, it is defaulted to the labels present on the pod template. Label keys and values that must match in order to be controlled by this replica set. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors

      false

      v1.LabelSelector

      template

      Template is the object that describes the pod that will be created if insufficient replicas are detected. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template

      false

      v1.PodTemplateSpec

      - -
      -
      -

      v1beta1.Deployment

      -
      -

      DEPRECATED - This group version of Deployment is deprecated by apps/v1beta2/Deployment. See the release notes for more information. Deployment enables declarative updates for Pods and ReplicaSets.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object metadata.

      false

      v1.ObjectMeta

      spec

      Specification of the desired behavior of the Deployment.

      false

      v1beta1.DeploymentSpec

      status

      Most recently observed status of the Deployment.

      false

      v1beta1.DeploymentStatus

      - -
      -
      -

      v1beta1.DaemonSetSpec

      -
      -

      DaemonSetSpec is the specification of a daemon set.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      selector

      A label query over pods that are managed by the daemon set. Must match in order to be controlled. If empty, defaulted to labels on Pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors

      false

      v1.LabelSelector

      template

      An object that describes the pod that will be created. The DaemonSet will create exactly one copy of this pod on every node that matches the template’s node selector (or on every node if no node selector is specified). More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template

      true

      v1.PodTemplateSpec

      updateStrategy

      An update strategy to replace existing DaemonSet pods with new pods.

      false

      v1beta1.DaemonSetUpdateStrategy

      minReadySeconds

      The minimum number of seconds for which a newly created DaemonSet 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).

      false

      integer (int32)

      templateGeneration

      DEPRECATED. A sequence number representing a specific generation of the template. Populated by the system. It can be set only during the creation.

      false

      integer (int64)

      revisionHistoryLimit

      The number of old history to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.

      false

      integer (int32)

      - -
      -
      -

      types.UID

      - -
      -
      -

      v1.AzureFileVolumeSource

      -
      -

      AzureFile represents an Azure File Service mount on the host and bind mount to the pod.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      secretName

      the name of secret that contains Azure Storage Account Name and Key

      true

      string

      shareName

      Share Name

      true

      string

      readOnly

      Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.

      false

      boolean

      false

      - -
      -
      -

      v1.ISCSIVolumeSource

      -
      -

      Represents an ISCSI disk. ISCSI volumes can only be mounted as read/write once. ISCSI volumes support ownership management and SELinux relabeling.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      targetPortal

      iSCSI target portal. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      true

      string

      iqn

      Target iSCSI Qualified Name.

      true

      string

      lun

      iSCSI target lun number.

      true

      integer (int32)

      iscsiInterface

      Optional: Defaults to default (tcp). iSCSI interface name that uses an iSCSI transport.

      false

      string

      fsType

      Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi

      false

      string

      readOnly

      ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false.

      false

      boolean

      false

      portals

      iSCSI target portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      false

      string array

      chapAuthDiscovery

      whether support iSCSI Discovery CHAP authentication

      false

      boolean

      false

      chapAuthSession

      whether support iSCSI Session CHAP authentication

      false

      boolean

      false

      secretRef

      CHAP secret for iSCSI target and initiator authentication

      false

      v1.LocalObjectReference

      initiatorName

      Custom iSCSI initiator name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface <target portal>:<volume name> will be created for the connection.

      false

      string

      - -
      -
      -

      v1beta1.IngressList

      -
      -

      IngressList is a collection of Ingress.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ListMeta

      items

      Items is the list of Ingress.

      true

      v1beta1.Ingress array

      - -
      -
      -

      v1.EmptyDirVolumeSource

      -
      -

      Represents an empty directory for a pod. Empty directory volumes support ownership management and SELinux relabeling.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      medium

      What type of storage medium should back this directory. The default is "" which means to use the node’s default medium. Must be an empty string (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir

      false

      string

      sizeLimit

      Total amount of local storage required for this EmptyDir volume. The size limit is also applicable for memory medium. The maximum usage on memory medium EmptyDir would be the minimum value between the SizeLimit specified here and the sum of memory limits of all containers in a pod. The default is nil which means that the limit is undefined. More info: http://kubernetes.io/docs/user-guide/volumes#emptydir

      false

      string

      - -
      -
      -

      v1beta1.ScaleSpec

      -
      -

      describes the attributes of a scale subresource

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      replicas

      desired number of instances for the scaled object.

      false

      integer (int32)

      - -
      -
      -

      v1.PodAffinityTerm

      -
      -

      Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key <topologyKey> tches that of any node on which a pod of the set of pods is running

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      labelSelector

      A label query over a set of resources, in this case pods.

      false

      v1.LabelSelector

      namespaces

      namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means "this pod’s namespace"

      false

      string array

      topologyKey

      This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. For PreferredDuringScheduling pod anti-affinity, empty topologyKey is interpreted as "all topologies" ("all topologies" here means all the topologyKeys indicated by scheduler command-line argument --failure-domains); for affinity and for RequiredDuringScheduling pod anti-affinity, empty topologyKey is not allowed.

      false

      string

      - -
      -
      -

      v1.EnvFromSource

      -
      -

      EnvFromSource represents the source of a set of ConfigMaps

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      prefix

      An optional identifer to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER.

      false

      string

      configMapRef

      The ConfigMap to select from

      false

      v1.ConfigMapEnvSource

      secretRef

      The Secret to select from

      false

      v1.SecretEnvSource

      - -
      -
      -

      v1.PodAffinity

      -
      -

      Pod affinity is a group of inter pod affinity scheduling rules.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      requiredDuringSchedulingIgnoredDuringExecution

      If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied.

      false

      v1.PodAffinityTerm array

      preferredDuringSchedulingIgnoredDuringExecution

      The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred.

      false

      v1.WeightedPodAffinityTerm array

      - -
      -
      -

      v1.FlockerVolumeSource

      -
      -

      Represents a Flocker volume mounted by the Flocker agent. One and only one of datasetName and datasetUUID should be set. Flocker volumes do not support ownership management or SELinux relabeling.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      datasetName

      Name of the dataset stored as metadata → name on the dataset for Flocker should be considered as deprecated

      false

      string

      datasetUUID

      UUID of the dataset. This is unique identifier of a Flocker dataset

      false

      string

      - -
      -
      -

      v1.PersistentVolumeClaimVolumeSource

      -
      -

      PersistentVolumeClaimVolumeSource references the user’s PVC in the same namespace. This volume finds the bound PV and mounts that volume for the pod. A PersistentVolumeClaimVolumeSource is, essentially, a wrapper around another type of volume that is owned by someone else (the system).

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      claimName

      ClaimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims

      true

      string

      readOnly

      Will force the ReadOnly setting in VolumeMounts. Default false.

      false

      boolean

      false

      - -
      -
      -

      v1.ListMeta

      -
      -

      ListMeta describes metadata that synthetic resources must have, including lists and various status objects. A resource may have only one of {ObjectMeta, ListMeta}.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      selfLink

      selfLink is a URL representing this object. Populated by the system. Read-only.

      false

      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/api-conventions.md#concurrency-control-and-consistency

      false

      string

      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 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.

      false

      string

      - -
      -
      -

      v1beta1.RollbackConfig

      -
      -

      DEPRECATED.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      revision

      The revision to rollback to. If set to 0, rollback to the last revision.

      false

      integer (int64)

      - -
      -
      -

      v1.SecretVolumeSource

      -
      -

      Adapts a Secret into a volume.

      -
      -
      -

      The contents of the target Secret’s Data field will be presented in a volume as files using the keys in the Data field as the file names. Secret volumes support ownership management and SELinux relabeling.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      secretName

      Name of the secret in the pod’s namespace to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret

      false

      string

      items

      If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the .. path or start with ...

      false

      v1.KeyToPath array

      defaultMode

      Optional: mode bits to use on created files by default. Must be a value between 0 and 0777. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.

      false

      integer (int32)

      optional

      Specify whether the Secret or it’s keys must be defined

      false

      boolean

      false

      - -
      -
      -

      v1.FlexVolumeSource

      -
      -

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      driver

      Driver is the name of the driver to use for this volume.

      true

      string

      fsType

      Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script.

      false

      string

      secretRef

      Optional: SecretRef is reference to the secret object containing sensitive information to pass to the plugin scripts. This may be empty if no secret object is specified. If the secret object contains more than one secret, all secrets are passed to the plugin scripts.

      false

      v1.LocalObjectReference

      readOnly

      Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.

      false

      boolean

      false

      options

      Optional: Extra command options if any.

      false

      object

      - -
      -
      -

      v1.EnvVarSource

      -
      -

      EnvVarSource represents a source for the value of an EnvVar.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      fieldRef

      Selects a field of the pod: supports metadata.name, metadata.namespace, metadata.labels, metadata.annotations, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP.

      false

      v1.ObjectFieldSelector

      resourceFieldRef

      Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.

      false

      v1.ResourceFieldSelector

      configMapKeyRef

      Selects a key of a ConfigMap.

      false

      v1.ConfigMapKeySelector

      secretKeyRef

      Selects a key of a secret in the pod’s namespace

      false

      v1.SecretKeySelector

      - -
      -
      -

      v1.LoadBalancerIngress

      -
      -

      LoadBalancerIngress represents the status of a load-balancer ingress point: traffic intended for the service should be sent to an ingress point.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      ip

      IP is set for load-balancer ingress points that are IP based (typically GCE or OpenStack load-balancers)

      false

      string

      hostname

      Hostname is set for load-balancer ingress points that are DNS based (typically AWS load-balancers)

      false

      string

      - -
      -
      -

      v1.AzureDiskVolumeSource

      -
      -

      AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      diskName

      The Name of the data disk in the blob storage

      true

      string

      diskURI

      The URI the data disk in the blob storage

      true

      string

      cachingMode

      Host Caching mode: None, Read Only, Read Write.

      false

      v1.AzureDataDiskCachingMode

      fsType

      Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.

      false

      string

      readOnly

      Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.

      false

      boolean

      false

      kind

      Expected values Shared: multiple blob disks per storage account Dedicated: single blob disk per storage account Managed: azure managed data disk (only in managed availability set). defaults to shared

      false

      v1.AzureDataDiskKind

      - -
      -
      -

      v1.KeyToPath

      -
      -

      Maps a string key to a path within a volume.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      key

      The key to project.

      true

      string

      path

      The relative path of the file to map the key to. May not be an absolute path. May not contain the path element ... May not start with the string ...

      true

      string

      mode

      Optional: mode bits to use on this file, must be a value between 0 and 0777. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.

      false

      integer (int32)

      - -
      -
      -

      v1beta1.DaemonSetUpdateStrategy

      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      type

      Type of daemon set update. Can be "RollingUpdate" or "OnDelete". Default is OnDelete.

      false

      string

      rollingUpdate

      Rolling update config params. Present only if type = "RollingUpdate".

      false

      v1beta1.RollingUpdateDaemonSet

      - -
      -
      -

      v1.VsphereVirtualDiskVolumeSource

      -
      -

      Represents a vSphere volume resource.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      volumePath

      Path that identifies vSphere volume vmdk

      true

      string

      fsType

      Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.

      false

      string

      storagePolicyName

      Storage Policy Based Management (SPBM) profile name.

      false

      string

      storagePolicyID

      Storage Policy Based Management (SPBM) profile ID associated with the StoragePolicyName.

      false

      string

      - -
      -
      -

      v1.DeleteOptions

      -
      -

      DeleteOptions may be provided when deleting an API object.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      gracePeriodSeconds

      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.

      false

      integer (int64)

      preconditions

      Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.

      false

      v1.Preconditions

      orphanDependents

      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.

      false

      boolean

      false

      propagationPolicy

      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.

      false

      v1.DeletionPropagation

      - -
      -
      -

      v1beta1.DaemonSetList

      -
      -

      DaemonSetList is a collection of daemon sets.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ListMeta

      items

      A list of daemon sets.

      true

      v1beta1.DaemonSet array

      - -
      -
      -

      v1.Volume

      -
      -

      Volume represents a named volume in a pod that may be accessed by any container in the pod.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      Volume’s name. Must be a DNS_LABEL and unique within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      true

      string

      hostPath

      HostPath represents a pre-existing file or directory on the host machine that is directly exposed to the container. This is generally used for system agents or other privileged things that are allowed to see the host machine. Most containers will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath

      false

      v1.HostPathVolumeSource

      emptyDir

      EmptyDir represents a temporary directory that shares a pod’s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir

      false

      v1.EmptyDirVolumeSource

      gcePersistentDisk

      GCEPersistentDisk represents a GCE Disk resource that is attached to a kubelet’s host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk

      false

      v1.GCEPersistentDiskVolumeSource

      awsElasticBlockStore

      AWSElasticBlockStore represents an AWS Disk resource that is attached to a kubelet’s host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore

      false

      v1.AWSElasticBlockStoreVolumeSource

      gitRepo

      GitRepo represents a git repository at a particular revision.

      false

      v1.GitRepoVolumeSource

      secret

      Secret represents a secret that should populate this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret

      false

      v1.SecretVolumeSource

      nfs

      NFS represents an NFS mount on the host that shares a pod’s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs

      false

      v1.NFSVolumeSource

      iscsi

      ISCSI represents an ISCSI Disk resource that is attached to a kubelet’s host machine and then exposed to the pod. More info: https://releases.k8s.io/HEAD/examples/volumes/iscsi/README.md

      false

      v1.ISCSIVolumeSource

      glusterfs

      Glusterfs represents a Glusterfs mount on the host that shares a pod’s lifetime. More info: https://releases.k8s.io/HEAD/examples/volumes/glusterfs/README.md

      false

      v1.GlusterfsVolumeSource

      persistentVolumeClaim

      PersistentVolumeClaimVolumeSource represents a reference to a PersistentVolumeClaim in the same namespace. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims

      false

      v1.PersistentVolumeClaimVolumeSource

      rbd

      RBD represents a Rados Block Device mount on the host that shares a pod’s lifetime. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md

      false

      v1.RBDVolumeSource

      flexVolume

      FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.

      false

      v1.FlexVolumeSource

      cinder

      Cinder represents a cinder volume attached and mounted on kubelets host machine More info: https://releases.k8s.io/HEAD/examples/mysql-cinder-pd/README.md

      false

      v1.CinderVolumeSource

      cephfs

      CephFS represents a Ceph FS mount on the host that shares a pod’s lifetime

      false

      v1.CephFSVolumeSource

      flocker

      Flocker represents a Flocker volume attached to a kubelet’s host machine. This depends on the Flocker control service being running

      false

      v1.FlockerVolumeSource

      downwardAPI

      DownwardAPI represents downward API about the pod that should populate this volume

      false

      v1.DownwardAPIVolumeSource

      fc

      FC represents a Fibre Channel resource that is attached to a kubelet’s host machine and then exposed to the pod.

      false

      v1.FCVolumeSource

      azureFile

      AzureFile represents an Azure File Service mount on the host and bind mount to the pod.

      false

      v1.AzureFileVolumeSource

      configMap

      ConfigMap represents a configMap that should populate this volume

      false

      v1.ConfigMapVolumeSource

      vsphereVolume

      VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine

      false

      v1.VsphereVirtualDiskVolumeSource

      quobyte

      Quobyte represents a Quobyte mount on the host that shares a pod’s lifetime

      false

      v1.QuobyteVolumeSource

      azureDisk

      AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod.

      false

      v1.AzureDiskVolumeSource

      photonPersistentDisk

      PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine

      false

      v1.PhotonPersistentDiskVolumeSource

      projected

      Items for all in one resources secrets, configmaps, and downward API

      false

      v1.ProjectedVolumeSource

      portworxVolume

      PortworxVolume represents a portworx volume attached and mounted on kubelets host machine

      false

      v1.PortworxVolumeSource

      scaleIO

      ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.

      false

      v1.ScaleIOVolumeSource

      storageos

      StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes.

      false

      v1.StorageOSVolumeSource

      - -
      -
      -

      v1.ResourceFieldSelector

      -
      -

      ResourceFieldSelector represents container resources (cpu, memory) and their output format

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      containerName

      Container name: required for volumes, optional for env vars

      false

      string

      resource

      Required: resource to select

      true

      string

      divisor

      Specifies the output format of the exposed resources, defaults to "1"

      false

      string

      - -
      -
      -

      v1.VolumeProjection

      -
      -

      Projection that may be projected along with other supported volume types

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      secret

      information about the secret data to project

      false

      v1.SecretProjection

      downwardAPI

      information about the downwardAPI data to project

      false

      v1.DownwardAPIProjection

      configMap

      information about the configMap data to project

      false

      v1.ConfigMapProjection

      - -
      -
      -

      v1.Probe

      -
      -

      Probe describes a health check to be performed against a container to determine whether it is alive or ready to receive traffic.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      exec

      One and only one of the following should be specified. Exec specifies the action to take.

      false

      v1.ExecAction

      httpGet

      HTTPGet specifies the http request to perform.

      false

      v1.HTTPGetAction

      tcpSocket

      TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported

      false

      v1.TCPSocketAction

      initialDelaySeconds

      Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes

      false

      integer (int32)

      timeoutSeconds

      Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes

      false

      integer (int32)

      periodSeconds

      How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1.

      false

      integer (int32)

      successThreshold

      Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness. Minimum value is 1.

      false

      integer (int32)

      failureThreshold

      Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1.

      false

      integer (int32)

      - -
      -
      -

      v1.WeightedPodAffinityTerm

      -
      -

      The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s)

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      weight

      weight associated with matching the corresponding podAffinityTerm, in the range 1-100.

      true

      integer (int32)

      podAffinityTerm

      Required. A pod affinity term, associated with the corresponding weight.

      true

      v1.PodAffinityTerm

      - -
      -
      -

      v1beta1.DeploymentSpec

      -
      -

      DeploymentSpec is the specification of the desired behavior of the Deployment.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      replicas

      Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1.

      false

      integer (int32)

      selector

      Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment.

      false

      v1.LabelSelector

      template

      Template describes the pods that will be created.

      true

      v1.PodTemplateSpec

      strategy

      The deployment strategy to use to replace existing pods with new ones.

      false

      v1beta1.DeploymentStrategy

      minReadySeconds

      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)

      false

      integer (int32)

      revisionHistoryLimit

      The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified.

      false

      integer (int32)

      paused

      Indicates that the deployment is paused and will not be processed by the deployment controller.

      false

      boolean

      false

      rollbackTo

      DEPRECATED. The config this deployment is rolling back to. Will be cleared after rollback is done.

      false

      v1beta1.RollbackConfig

      progressDeadlineSeconds

      The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that progress will not be estimated during the time a deployment is paused. This is not set by default.

      false

      integer (int32)

      - -
      -
      -

      v1.SecretKeySelector

      -
      -

      SecretKeySelector selects a key of a Secret.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      false

      string

      key

      The key of the secret to select from. Must be a valid secret key.

      true

      string

      optional

      Specify whether the Secret or it’s key must be defined

      false

      boolean

      false

      - -
      -
      -

      v1.Capability

      - -
      -
      -

      v1.DownwardAPIVolumeFile

      -
      -

      DownwardAPIVolumeFile represents information to create the file containing the pod field

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      path

      Required: Path is the relative path name of the file to be created. Must not be absolute or contain the .. path. Must be utf-8 encoded. The first item of the relative path must not start with ..

      true

      string

      fieldRef

      Required: Selects a field of the pod: only annotations, labels, name and namespace are supported.

      false

      v1.ObjectFieldSelector

      resourceFieldRef

      Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported.

      false

      v1.ResourceFieldSelector

      mode

      Optional: mode bits to use on this file, must be a value between 0 and 0777. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.

      false

      integer (int32)

      - -
      -
      -

      v1.PodSpec

      -
      -

      PodSpec is a description of a pod.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      volumes

      List of volumes that can be mounted by containers belonging to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes

      false

      v1.Volume array

      initContainers

      List of initialization containers belonging to the pod. Init containers are executed in order prior to containers being started. If any init container fails, the pod is considered to have failed and is handled according to its restartPolicy. The name for an init container or normal container must be unique among all containers. Init containers may not have Lifecycle actions, Readiness probes, or Liveness probes. The resourceRequirements of an init container are taken into account during scheduling by finding the highest request/limit for each resource type, and then using the max of of that value or the sum of the normal containers. Limits are applied to init containers in a similar fashion. Init containers cannot currently be added or removed. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/

      false

      v1.Container array

      containers

      List of containers belonging to the pod. Containers cannot currently be added or removed. There must be at least one container in a Pod. Cannot be updated.

      true

      v1.Container array

      restartPolicy

      Restart policy for all containers within the pod. One of Always, OnFailure, Never. Default to Always. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy

      false

      string

      terminationGracePeriodSeconds

      Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period will be used instead. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. Defaults to 30 seconds.

      false

      integer (int64)

      activeDeadlineSeconds

      Optional duration in seconds the pod may be active on the node relative to StartTime before the system will actively try to mark it failed and kill associated containers. Value must be a positive integer.

      false

      integer (int64)

      dnsPolicy

      Set DNS policy for containers within the pod. One of ClusterFirstWithHostNet, ClusterFirst or Default. Defaults to "ClusterFirst". To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to ClusterFirstWithHostNet.

      false

      string

      nodeSelector

      NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node’s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/

      false

      object

      serviceAccountName

      ServiceAccountName is the name of the ServiceAccount to use to run this pod. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/

      false

      string

      serviceAccount

      DeprecatedServiceAccount is a depreciated alias for ServiceAccountName. Deprecated: Use serviceAccountName instead.

      false

      string

      automountServiceAccountToken

      AutomountServiceAccountToken indicates whether a service account token should be automatically mounted.

      false

      boolean

      false

      nodeName

      NodeName is a request to schedule this pod onto a specific node. If it is non-empty, the scheduler simply schedules this pod onto that node, assuming that it fits resource requirements.

      false

      string

      hostNetwork

      Host networking requested for this pod. Use the host’s network namespace. If this option is set, the ports that will be used must be specified. Default to false.

      false

      boolean

      false

      hostPID

      Use the host’s pid namespace. Optional: Default to false.

      false

      boolean

      false

      hostIPC

      Use the host’s ipc namespace. Optional: Default to false.

      false

      boolean

      false

      securityContext

      SecurityContext holds pod-level security attributes and common container settings. Optional: Defaults to empty. See type description for default values of each field.

      false

      v1.PodSecurityContext

      imagePullSecrets

      ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. If specified, these secrets will be passed to individual puller implementations for them to use. For example, in the case of docker, only DockerConfig type secrets are honored. More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod

      false

      v1.LocalObjectReference array

      hostname

      Specifies the hostname of the Pod If not specified, the pod’s hostname will be set to a system-defined value.

      false

      string

      subdomain

      If specified, the fully qualified Pod hostname will be "<hostname>.<subdomain>.<pod namespace>.svc.<cluster domain>". If not specified, the pod will not have a domainname at all.

      false

      string

      affinity

      If specified, the pod’s scheduling constraints

      false

      v1.Affinity

      schedulerName

      If specified, the pod will be dispatched by specified scheduler. If not specified, the pod will be dispatched by default scheduler.

      false

      string

      tolerations

      If specified, the pod’s tolerations.

      false

      v1.Toleration array

      hostAliases

      HostAliases is an optional list of hosts and IPs that will be injected into the pod’s hosts file if specified. This is only valid for non-hostNetwork pods.

      false

      v1.HostAlias array

      priorityClassName

      If specified, indicates the pod’s priority. "SYSTEM" is a special keyword which indicates the highest priority. Any other name must be defined by creating a PriorityClass object with that name. If not specified, the pod priority will be default or zero if there is no default.

      false

      string

      priority

      The priority value. Various system components use this field to find the priority of the pod. 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.

      false

      integer (int32)

      - -
      -
      -

      v1.ContainerPort

      -
      -

      ContainerPort represents a network port in a single container.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      If specified, this must be an IANA_SVC_NAME and unique within the pod. Each named port in a pod must have a unique name. Name for the port that can be referred to by services.

      false

      string

      hostPort

      Number of port to expose on the host. If specified, this must be a valid port number, 0 < x < 65536. If HostNetwork is specified, this must match ContainerPort. Most containers do not need this.

      false

      integer (int32)

      containerPort

      Number of port to expose on the pod’s IP address. This must be a valid port number, 0 < x < 65536.

      true

      integer (int32)

      protocol

      Protocol for port. Must be UDP or TCP. Defaults to "TCP".

      false

      string

      hostIP

      What host IP to bind the external port to.

      false

      string

      - -
      -
      -

      v1.Lifecycle

      -
      -

      Lifecycle describes actions that the management system should take in response to container lifecycle events. For the PostStart and PreStop lifecycle handlers, management of the container blocks until the action is complete, unless the container process fails, in which case the handler is aborted.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      postStart

      PostStart is called immediately after a container is created. If the handler fails, the container is terminated and restarted according to its restart policy. Other management of the container blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks

      false

      v1.Handler

      preStop

      PreStop is called immediately before a container is terminated. The container is terminated after the handler completes. The reason for termination is passed to the handler. Regardless of the outcome of the handler, the container is eventually terminated. Other management of the container blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks

      false

      v1.Handler

      - -
      -
      -

      v1.GlusterfsVolumeSource

      -
      -

      Represents a Glusterfs mount that lasts the lifetime of a pod. Glusterfs volumes do not support ownership management or SELinux relabeling.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      endpoints

      EndpointsName is the endpoint name that details Glusterfs topology. More info: https://releases.k8s.io/HEAD/examples/volumes/glusterfs/README.md#create-a-pod

      true

      string

      path

      Path is the Glusterfs volume path. More info: https://releases.k8s.io/HEAD/examples/volumes/glusterfs/README.md#create-a-pod

      true

      string

      readOnly

      ReadOnly here will force the Glusterfs volume to be mounted with read-only permissions. Defaults to false. More info: https://releases.k8s.io/HEAD/examples/volumes/glusterfs/README.md#create-a-pod

      false

      boolean

      false

      - -
      -
      -

      v1.Handler

      -
      -

      Handler defines a specific action that should be taken

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      exec

      One and only one of the following should be specified. Exec specifies the action to take.

      false

      v1.ExecAction

      httpGet

      HTTPGet specifies the http request to perform.

      false

      v1.HTTPGetAction

      tcpSocket

      TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported

      false

      v1.TCPSocketAction

      - -
      -
      -

      v1.Toleration

      -
      -

      The pod this Toleration is attached to tolerates any taint that matches the triple <key,value,effect> using the matching operator <operator>.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      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.

      false

      string

      operator

      Operator represents a key’s relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category.

      false

      string

      value

      Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string.

      false

      string

      effect

      Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute.

      false

      string

      tolerationSeconds

      TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system.

      false

      integer (int64)

      - -
      -
      -

      v1beta1.IngressTLS

      -
      -

      IngressTLS describes the transport layer security associated with an Ingress.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      hosts

      Hosts are a list of hosts included in the TLS certificate. The values in this list must match the name/s used in the tlsSecret. Defaults to the wildcard host setting for the loadbalancer controller fulfilling this Ingress, if left unspecified.

      false

      string array

      secretName

      SecretName is the name of the secret used to terminate SSL traffic on 443. Field is left optional to allow SSL routing based on SNI hostname alone. If the SNI host in a listener conflicts with the "Host" header field used by an IngressRule, the SNI host is used for termination and value of the Host header is used for routing.

      false

      string

      - -
      -
      -

      v1.StatusCause

      -
      -

      StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      reason

      A machine-readable description of the cause of the error. If this value is empty there is no information available.

      false

      string

      message

      A human-readable description of the cause of the error. This field may be presented as-is to a reader.

      false

      string

      field

      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.
      -
      -Examples:
      - "name" - the field "name" on the current resource
      - "items[0].name" - the field "name" on the first array entry in "items"

      false

      string

      - -
      -
      -

      v1beta1.Scale

      -
      -

      represents a scaling request for a resource.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata.

      false

      v1.ObjectMeta

      spec

      defines the behavior of the scale. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status.

      false

      v1beta1.ScaleSpec

      status

      current status of the scale. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status. Read-only.

      false

      v1beta1.ScaleStatus

      - -
      -
      -

      v1.RBDVolumeSource

      -
      -

      Represents a Rados Block Device mount that lasts the lifetime of a pod. RBD volumes support ownership management and SELinux relabeling.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      monitors

      A collection of Ceph monitors. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      true

      string array

      image

      The rados image name. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      true

      string

      fsType

      Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd

      false

      string

      pool

      The rados pool name. Default is rbd. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      false

      string

      user

      The rados user name. Default is admin. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      false

      string

      keyring

      Keyring is the path to key ring for RBDUser. Default is /etc/ceph/keyring. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      false

      string

      secretRef

      SecretRef is name of the authentication secret for RBDUser. If provided overrides keyring. Default is nil. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      false

      v1.LocalObjectReference

      readOnly

      ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it

      false

      boolean

      false

      - -
      -
      -

      v1.ConfigMapProjection

      -
      -

      Adapts a ConfigMap into a projected volume.

      -
      -
      -

      The contents of the target ConfigMap’s Data field will be presented in a projected volume as files using the keys in the Data field as the file names, unless the items element is populated with specific mappings of keys to paths. Note that this is identical to a configmap volume source without the default mode.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      false

      string

      items

      If unspecified, each key-value pair in the Data field of the referenced ConfigMap will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the ConfigMap, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the .. path or start with ...

      false

      v1.KeyToPath array

      optional

      Specify whether the ConfigMap or it’s keys must be defined

      false

      boolean

      false

      - -
      -
      -

      v1.PhotonPersistentDiskVolumeSource

      -
      -

      Represents a Photon Controller persistent disk resource.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      pdID

      ID that identifies Photon Controller persistent disk

      true

      string

      fsType

      Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.

      false

      string

      - -
      -
      -

      v1.ScaleIOVolumeSource

      -
      -

      ScaleIOVolumeSource represents a persistent ScaleIO volume

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      gateway

      The host address of the ScaleIO API Gateway.

      true

      string

      system

      The name of the storage system as configured in ScaleIO.

      true

      string

      secretRef

      SecretRef references to the secret for ScaleIO user and other sensitive information. If this is not provided, Login operation will fail.

      true

      v1.LocalObjectReference

      sslEnabled

      Flag to enable/disable SSL communication with Gateway, default false

      false

      boolean

      false

      protectionDomain

      The name of the Protection Domain for the configured storage (defaults to "default").

      false

      string

      storagePool

      The Storage Pool associated with the protection domain (defaults to "default").

      false

      string

      storageMode

      Indicates whether the storage for a volume should be thick or thin (defaults to "thin").

      false

      string

      volumeName

      The name of a volume already created in the ScaleIO system that is associated with this volume source.

      false

      string

      fsType

      Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.

      false

      string

      readOnly

      Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.

      false

      boolean

      false

      - -
      -
      -

      v1.HostPathType

      - -
      -
      -

      v1.Initializers

      -
      -

      Initializers tracks the progress of initialization.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      pending

      Pending is a list of initializers that must execute in order before this object is visible. When the last pending initializer is removed, and no failing result is set, the initializers struct will be set to nil and the object is considered as initialized and visible to all clients.

      true

      v1.Initializer array

      result

      If result is set with the Failure field, the object will be persisted to storage and then deleted, ensuring that other clients can observe the deletion.

      false

      v1.Status

      - -
      -
      -

      v1.Status

      -
      -

      Status is a return value for calls that don’t return other objects.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      status

      Status of the operation. One of: "Success" or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      string

      message

      A human-readable description of the status of this operation.

      false

      string

      reason

      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.

      false

      string

      details

      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.

      false

      v1.StatusDetails

      code

      Suggested HTTP return code for this status, 0 if not set.

      false

      integer (int32)

      - -
      -
      -

      v1beta1.ScaleStatus

      -
      -

      represents the current status of a scale subresource.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      replicas

      actual number of observed instances of the scaled object.

      true

      integer (int32)

      selector

      label query over pods that should match the replicas count. More info: http://kubernetes.io/docs/user-guide/labels#label-selectors

      false

      object

      targetSelector

      label selector for pods that should match the replicas count. This is a serializated version of both map-based and more expressive set-based selectors. This is done to avoid introspection in the clients. The string will be in the same format as the query-param syntax. If the target type only supports map-based selectors, both this field and map-based selector field are populated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors

      false

      string

      - -
      -
      -

      v1.NFSVolumeSource

      -
      -

      Represents an NFS mount that lasts the lifetime of a pod. NFS volumes do not support ownership management or SELinux relabeling.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      server

      Server is the hostname or IP address of the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs

      true

      string

      path

      Path that is exported by the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs

      true

      string

      readOnly

      ReadOnly here will force the NFS export to be mounted with read-only permissions. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs

      false

      boolean

      false

      - -
      -
      -

      v1beta1.DeploymentList

      -
      -

      DeploymentList is a list of Deployments.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata.

      false

      v1.ListMeta

      items

      Items is the list of Deployments.

      true

      v1beta1.Deployment array

      - -
      -
      -

      v1beta1.DeploymentRollback

      -
      -

      DEPRECATED. DeploymentRollback stores the information required to rollback a deployment.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      name

      Required: This must match the Name of a deployment.

      true

      string

      updatedAnnotations

      The annotations to be updated to a deployment

      false

      object

      rollbackTo

      The config of this deployment rollback.

      true

      v1beta1.RollbackConfig

      - -
      -
      -

      v1.HTTPHeader

      -
      -

      HTTPHeader describes a custom header to be used in HTTP probes

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      The header field name

      true

      string

      value

      The header field value

      true

      string

      - -
      -
      -

      v1.FCVolumeSource

      -
      -

      Represents a Fibre Channel volume. Fibre Channel volumes can only be mounted as read/write once. Fibre Channel volumes support ownership management and SELinux relabeling.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      targetWWNs

      Optional: FC target worldwide names (WWNs)

      false

      string array

      lun

      Optional: FC target lun number

      false

      integer (int32)

      fsType

      Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.

      false

      string

      readOnly

      Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.

      false

      boolean

      false

      wwids

      Optional: FC volume world wide identifiers (wwids) Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously.

      false

      string array

      - -
      -
      -

      v1.PodAntiAffinity

      -
      -

      Pod anti affinity is a group of inter pod anti affinity scheduling rules.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      requiredDuringSchedulingIgnoredDuringExecution

      If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied.

      false

      v1.PodAffinityTerm array

      preferredDuringSchedulingIgnoredDuringExecution

      The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred.

      false

      v1.WeightedPodAffinityTerm array

      - -
      -
      -

      v1.DeletionPropagation

      - -
      -
      -

      v1beta1.DeploymentStrategy

      -
      -

      DeploymentStrategy describes how to replace existing pods with new ones.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      type

      Type of deployment. Can be "Recreate" or "RollingUpdate". Default is RollingUpdate.

      false

      string

      rollingUpdate

      Rolling update config params. Present only if DeploymentStrategyType = RollingUpdate.

      false

      v1beta1.RollingUpdateDeployment

      - -
      -
      -

      v1.TCPSocketAction

      -
      -

      TCPSocketAction describes an action based on opening a socket

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      port

      Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME.

      true

      string

      host

      Optional: Host name to connect to, defaults to the pod IP.

      false

      string

      - -
      -
      -

      v1beta1.IngressRule

      -
      -

      IngressRule represents the rules mapping the paths under a specified host to the related backend services. Incoming requests are first evaluated for a host match, then routed to the backend associated with the matching IngressRuleValue.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      host

      Host is the fully qualified domain name of a network host, as defined by RFC 3986. Note the following deviations from the "host" part of the URI as defined in the RFC: 1. IPs are not allowed. Currently an IngressRuleValue can only apply to the
      - IP in the Spec of the parent Ingress.
      -2. The : delimiter is not respected because ports are not allowed.
      - Currently the port of an Ingress is implicitly :80 for http and
      - :443 for https.
      -Both these may change in the future. Incoming requests are matched against the host before the IngressRuleValue. If the host is unspecified, the Ingress routes all traffic based on the specified IngressRuleValue.

      false

      string

      http

      false

      v1beta1.HTTPIngressRuleValue

      - -
      -
      -

      v1.HTTPGetAction

      -
      -

      HTTPGetAction describes an action based on HTTP Get requests.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      path

      Path to access on the HTTP server.

      false

      string

      port

      Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME.

      true

      string

      host

      Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead.

      false

      string

      scheme

      Scheme to use for connecting to the host. Defaults to HTTP.

      false

      string

      httpHeaders

      Custom headers to set in the request. HTTP allows repeated headers.

      false

      v1.HTTPHeader array

      - -
      -
      -

      v1.StatusDetails

      -
      -

      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.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).

      false

      string

      group

      The group attribute of the resource associated with the status StatusReason.

      false

      string

      kind

      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/api-conventions.md#types-kinds

      false

      string

      uid

      UID of the resource. (when there is a single resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids

      false

      string

      causes

      The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.

      false

      v1.StatusCause array

      retryAfterSeconds

      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.

      false

      integer (int32)

      - -
      -
      -

      v1.LoadBalancerStatus

      -
      -

      LoadBalancerStatus represents the status of a load-balancer.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      ingress

      Ingress is a list containing ingress points for the load-balancer. Traffic intended for the service should be sent to these ingress points.

      false

      v1.LoadBalancerIngress array

      - -
      -
      -

      v1.Container

      -
      -

      A single application container that you want to run within a pod.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      Name of the container specified as a DNS_LABEL. Each container in a pod must have a unique name (DNS_LABEL). Cannot be updated.

      true

      string

      image

      Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images This field is optional to allow higher level config management to default or override container images in workload controllers like Deployments and StatefulSets.

      false

      string

      command

      Entrypoint array. Not executed within a shell. The docker image’s ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container’s environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell

      false

      string array

      args

      Arguments to the entrypoint. The docker image’s CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container’s environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell

      false

      string array

      workingDir

      Container’s working directory. If not specified, the container runtime’s default will be used, which might be configured in the container image. Cannot be updated.

      false

      string

      ports

      List of ports to expose from the container. Exposing a port here gives the system additional information about the network connections a container uses, but is primarily informational. Not specifying a port here DOES NOT prevent that port from being exposed. Any port which is listening on the default "0.0.0.0" address inside a container will be accessible from the network. Cannot be updated.

      false

      v1.ContainerPort array

      envFrom

      List of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. 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. Cannot be updated.

      false

      v1.EnvFromSource array

      env

      List of environment variables to set in the container. Cannot be updated.

      false

      v1.EnvVar array

      resources

      Compute Resources required by this container. Cannot be updated. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources

      false

      v1.ResourceRequirements

      volumeMounts

      Pod volumes to mount into the container’s filesystem. Cannot be updated.

      false

      v1.VolumeMount array

      livenessProbe

      Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes

      false

      v1.Probe

      readinessProbe

      Periodic probe of container service readiness. Container will be removed from service endpoints if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes

      false

      v1.Probe

      lifecycle

      Actions that the management system should take in response to container lifecycle events. Cannot be updated.

      false

      v1.Lifecycle

      terminationMessagePath

      Optional: Path at which the file to which the container’s termination message will be written is mounted into the container’s filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.

      false

      string

      terminationMessagePolicy

      Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated.

      false

      string

      imagePullPolicy

      Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images

      false

      string

      securityContext

      Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md

      false

      v1.SecurityContext

      stdin

      Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false.

      false

      boolean

      false

      stdinOnce

      Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false

      false

      boolean

      false

      tty

      Whether this container should allocate a TTY for itself, also requires stdin to be true. Default is false.

      false

      boolean

      false

      - -
      -
      -

      v1.PodSecurityContext

      -
      -

      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.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      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.

      false

      v1.SELinuxOptions

      runAsUser

      The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container.

      false

      integer (int64)

      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 SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.

      false

      boolean

      false

      supplementalGroups

      A list of groups applied to the first process run in each container, in addition to the container’s primary GID. If unspecified, no groups will be added to any container.

      false

      integer (int32) array

      fsGroup

      A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod:
      -
      -1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR’d with rw-rw

      false

      integer (int64)

      - -
      -
      -

      v1.OwnerReference

      -
      -

      OwnerReference contains enough information to let you identify an owning object. Currently, an owning object must be in the same namespace, so there is no namespace field.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      apiVersion

      API version of the referent.

      true

      string

      kind

      Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      true

      string

      name

      Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names

      true

      string

      uid

      UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids

      true

      string

      controller

      If true, this reference points to the managing controller.

      false

      boolean

      false

      blockOwnerDeletion

      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. Defaults to false. To set this field, a user needs "delete" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.

      false

      boolean

      false

      - -
      -
      -

      v1beta1.ReplicaSetStatus

      -
      -

      ReplicaSetStatus represents the current status of a ReplicaSet.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      replicas

      Replicas is the most recently oberved number of replicas. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller

      true

      integer (int32)

      fullyLabeledReplicas

      The number of pods that have labels matching the labels of the pod template of the replicaset.

      false

      integer (int32)

      readyReplicas

      The number of ready replicas for this replica set.

      false

      integer (int32)

      availableReplicas

      The number of available replicas (ready for at least minReadySeconds) for this replica set.

      false

      integer (int32)

      observedGeneration

      ObservedGeneration reflects the generation of the most recently observed ReplicaSet.

      false

      integer (int64)

      conditions

      Represents the latest available observations of a replica set’s current state.

      false

      v1beta1.ReplicaSetCondition array

      - -
      -
      -

      v1.APIResource

      -
      -

      APIResource specifies the name of a resource and whether it is namespaced.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      name is the plural name of the resource.

      true

      string

      singularName

      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.

      true

      string

      namespaced

      namespaced indicates if a resource is namespaced or not.

      true

      boolean

      false

      group

      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".

      false

      string

      version

      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)".

      false

      string

      kind

      kind is the kind for the resource (e.g. Foo is the kind for a resource foo)

      true

      string

      verbs

      verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy)

      true

      string array

      shortNames

      shortNames is a list of suggested short names of the resource.

      false

      string array

      categories

      categories is a list of the grouped resources this resource belongs to (e.g. all)

      false

      string array

      - -
      -
      -

      v1.NodeSelectorRequirement

      -
      -

      A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      key

      The label key that the selector applies to.

      true

      string

      operator

      Represents a key’s relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.

      true

      string

      values

      An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch.

      false

      string array

      - -
      -
      -

      v1beta1.ReplicaSet

      -
      -

      DEPRECATED - This group version of ReplicaSet is deprecated by apps/v1beta2/ReplicaSet. See the release notes for more information. ReplicaSet ensures that a specified number of pod replicas are running at any given time.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      If the Labels of a ReplicaSet are empty, they are defaulted to be the same as the Pod(s) that the ReplicaSet manages. Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ObjectMeta

      spec

      Spec defines the specification of the desired behavior of the ReplicaSet. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      v1beta1.ReplicaSetSpec

      status

      Status is the most recently observed status of the ReplicaSet. This data may be out of date by some window of time. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      v1beta1.ReplicaSetStatus

      - -
      -
      -

      v1.HostPathVolumeSource

      -
      -

      Represents a host path mapped into a pod. Host path volumes do not support ownership management or SELinux relabeling.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      path

      Path of the directory on the host. If the path is a symlink, it will follow the link to the real path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath

      true

      string

      type

      Type for HostPath Volume Defaults to "" More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath

      false

      v1.HostPathType

      - -
      -
      -

      v1.SecretProjection

      -
      -

      Adapts a secret into a projected volume.

      -
      -
      -

      The contents of the target Secret’s Data field will be presented in a projected volume as files using the keys in the Data field as the file names. Note that this is identical to a secret volume source without the default mode.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      false

      string

      items

      If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the .. path or start with ...

      false

      v1.KeyToPath array

      optional

      Specify whether the Secret or its key must be defined

      false

      boolean

      false

      - -
      -
      -

      v1beta1.DaemonSet

      -
      -

      DEPRECATED - This group version of DaemonSet is deprecated by apps/v1beta2/DaemonSet. See the release notes for more information. DaemonSet represents the configuration of a daemon set.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ObjectMeta

      spec

      The desired behavior of this daemon set. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      v1beta1.DaemonSetSpec

      status

      The current status of this daemon set. This data may be out of date by some window of time. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      v1beta1.DaemonSetStatus

      - -
      -
      -

      v1.CinderVolumeSource

      -
      -

      Represents a cinder volume resource in Openstack. A Cinder volume must exist before mounting to a container. The volume must also be in the same region as the kubelet. Cinder volumes support ownership management and SELinux relabeling.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      volumeID

      volume id used to identify the volume in cinder More info: https://releases.k8s.io/HEAD/examples/mysql-cinder-pd/README.md

      true

      string

      fsType

      Filesystem type to mount. Must be a filesystem type supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://releases.k8s.io/HEAD/examples/mysql-cinder-pd/README.md

      false

      string

      readOnly

      Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://releases.k8s.io/HEAD/examples/mysql-cinder-pd/README.md

      false

      boolean

      false

      - -
      -
      -

      v1.SecurityContext

      -
      -

      SecurityContext holds security configuration that will be applied to a container. Some fields are present in both SecurityContext and PodSecurityContext. When both are set, the values in SecurityContext take precedence.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      capabilities

      The capabilities to add/drop when running containers. Defaults to the default set of capabilities granted by the container runtime.

      false

      v1.Capabilities

      privileged

      Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false.

      false

      boolean

      false

      seLinuxOptions

      The SELinux context to be applied to the container. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.

      false

      v1.SELinuxOptions

      runAsUser

      The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.

      false

      integer (int64)

      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.

      false

      boolean

      false

      readOnlyRootFilesystem

      Whether this container has a read-only root filesystem. Default is false.

      false

      boolean

      false

      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

      false

      boolean

      false

      - -
      -
      -

      v1.AWSElasticBlockStoreVolumeSource

      -
      -

      Represents a Persistent Disk resource in AWS.

      -
      -
      -

      An AWS EBS disk must exist before mounting to a container. The disk must also be in the same AWS zone as the kubelet. An AWS EBS disk can only be mounted as read/write once. AWS EBS volumes support ownership management and SELinux relabeling.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      volumeID

      Unique ID of the persistent disk resource in AWS (Amazon EBS volume). More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore

      true

      string

      fsType

      Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore

      false

      string

      partition

      The partition in the volume that you want to mount. If omitted, the default is to mount by volume name. Examples: For volume /dev/sda1, you specify the partition as "1". Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty).

      false

      integer (int32)

      readOnly

      Specify "true" to force and set the ReadOnly property in VolumeMounts to "true". If omitted, the default is "false". More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore

      false

      boolean

      false

      - -
      -
      -

      v1.QuobyteVolumeSource

      -
      -

      Represents a Quobyte mount that lasts the lifetime of a pod. Quobyte volumes do not support ownership management or SELinux relabeling.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      registry

      Registry represents a single or multiple Quobyte Registry services specified as a string as host:port pair (multiple entries are separated with commas) which acts as the central registry for volumes

      true

      string

      volume

      Volume is a string that references an already created Quobyte volume by name.

      true

      string

      readOnly

      ReadOnly here will force the Quobyte volume to be mounted with read-only permissions. Defaults to false.

      false

      boolean

      false

      user

      User to map volume access to Defaults to serivceaccount user

      false

      string

      group

      Group to map volume access to Default is no group

      false

      string

      - -
      -
      -

      v1.WatchEvent

      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      type

      true

      string

      object

      true

      string

      - -
      -
      -

      v1.LabelSelectorRequirement

      -
      -

      A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      key

      key is the label key that the selector applies to.

      true

      string

      operator

      operator represents a key’s relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.

      true

      string

      values

      values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.

      false

      string array

      - -
      -
      -

      v1.EnvVar

      -
      -

      EnvVar represents an environment variable present in a Container.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      Name of the environment variable. Must be a C_IDENTIFIER.

      true

      string

      value

      Variable references $(VAR_NAME) are expanded using the previous defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".

      false

      string

      valueFrom

      Source for the environment variable’s value. Cannot be used if value is not empty.

      false

      v1.EnvVarSource

      - -
      -
      -

      v1.ResourceRequirements

      -
      -

      ResourceRequirements describes the compute resource requirements.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      limits

      Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/

      false

      object

      requests

      Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/

      false

      object

      - -
      -
      -

      v1.HostAlias

      -
      -

      HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the pod’s hosts file.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      ip

      IP address of the host file entry.

      false

      string

      hostnames

      Hostnames for the above IP address.

      false

      string array

      - -
      -
      -

      v1.PodTemplateSpec

      -
      -

      PodTemplateSpec describes the data a pod should have when created from a template

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      metadata

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ObjectMeta

      spec

      Specification of the desired behavior of the pod. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      v1.PodSpec

      - -
      -
      -

      v1.NodeSelector

      -
      -

      A node selector represents the union of the results of one or more label queries over a set of nodes; that is, it represents the OR of the selectors represented by the node selector terms.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      nodeSelectorTerms

      Required. A list of node selector terms. The terms are ORed.

      true

      v1.NodeSelectorTerm array

      - -
      -
      -

      v1.Patch

      -
      -

      Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.

      -
      -
      -
      -

      v1beta1.DeploymentCondition

      -
      -

      DeploymentCondition describes the state of a deployment at a certain point.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      type

      Type of deployment condition.

      true

      string

      status

      Status of the condition, one of True, False, Unknown.

      true

      string

      lastUpdateTime

      The last time this condition was updated.

      false

      string

      lastTransitionTime

      Last time the condition transitioned from one status to another.

      false

      string

      reason

      The reason for the condition’s last transition.

      false

      string

      message

      A human readable message indicating details about the transition.

      false

      string

      - -
      -
      -

      v1.ConfigMapEnvSource

      -
      -

      ConfigMapEnvSource selects a ConfigMap to populate the environment variables with.

      -
      -
      -

      The contents of the target ConfigMap’s Data field will represent the key-value pairs as environment variables.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      false

      string

      optional

      Specify whether the ConfigMap must be defined

      false

      boolean

      false

      - -
      -
      -

      v1.StorageOSVolumeSource

      -
      -

      Represents a StorageOS persistent volume resource.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      volumeName

      VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace.

      false

      string

      volumeNamespace

      VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod’s namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to "default" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created.

      false

      string

      fsType

      Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.

      false

      string

      readOnly

      Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.

      false

      boolean

      false

      secretRef

      SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted.

      false

      v1.LocalObjectReference

      - -
      -
      -

      v1.NodeAffinity

      -
      -

      Node affinity is a group of node affinity scheduling rules.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      requiredDuringSchedulingIgnoredDuringExecution

      If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node.

      false

      v1.NodeSelector

      preferredDuringSchedulingIgnoredDuringExecution

      The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred.

      false

      v1.PreferredSchedulingTerm array

      - -
      -
      -

      v1.AzureDataDiskKind

      - -
      -
      -

      v1.PreferredSchedulingTerm

      -
      -

      An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it’s a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op).

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      weight

      Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100.

      true

      integer (int32)

      preference

      A node selector term, associated with the corresponding weight.

      true

      v1.NodeSelectorTerm

      - -
      -
      -

      v1.ConfigMapKeySelector

      -
      -

      Selects a key from a ConfigMap.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      false

      string

      key

      The key to select.

      true

      string

      optional

      Specify whether the ConfigMap or it’s key must be defined

      false

      boolean

      false

      - -
      -
      -

      v1beta1.HTTPIngressPath

      -
      -

      HTTPIngressPath associates a path regex with a backend. Incoming urls matching the path are forwarded to the backend.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      path

      Path is an extended POSIX regex as defined by IEEE Std 1003.1, (i.e this follows the egrep/unix syntax, not the perl syntax) matched against the path of an incoming request. Currently it can contain characters disallowed from the conventional "path" part of a URL as defined by RFC 3986. Paths must begin with a /. If unspecified, the path defaults to a catch all sending traffic to the backend.

      false

      string

      backend

      Backend defines the referenced service endpoint to which the traffic will be forwarded to.

      true

      v1beta1.IngressBackend

      - -
      -
      -

      v1beta1.Ingress

      -
      -

      Ingress is a collection of rules that allow inbound connections to reach the endpoints defined by a backend. An Ingress can be configured to give services externally-reachable urls, load balance traffic, terminate SSL, offer name based virtual hosting etc.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ObjectMeta

      spec

      Spec is the desired state of the Ingress. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      v1beta1.IngressSpec

      status

      Status is the current state of the Ingress. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      v1beta1.IngressStatus

      - -
      -
      -

      v1.AzureDataDiskCachingMode

      - -
      -
      -

      any

      -
      -

      Represents an untyped JSON map - see the description of the field for more info about the structure of this object.

      -
      -
      -
      -
      -
      - - - \ No newline at end of file diff --git a/federation/docs/api-reference/extensions/v1beta1/operations.html b/federation/docs/api-reference/extensions/v1beta1/operations.html deleted file mode 100755 index f7c0c679697..00000000000 --- a/federation/docs/api-reference/extensions/v1beta1/operations.html +++ /dev/null @@ -1,9927 +0,0 @@ - - - - - - -Operations - - - - -
      -
      -

      Operations

      -
      -
      -

      get available resources

      -
      -
      -
      GET /apis/extensions/v1beta1
      -
      -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      default

      success

      v1.APIResourceList

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      list or watch objects of kind DaemonSet

      -
      -
      -
      GET /apis/extensions/v1beta1/daemonsets
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.DaemonSetList

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      list or watch objects of kind Deployment

      -
      -
      -
      GET /apis/extensions/v1beta1/deployments
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.DeploymentList

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      list or watch objects of kind Ingress

      -
      -
      -
      GET /apis/extensions/v1beta1/ingresses
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.IngressList

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      list or watch objects of kind DaemonSet

      -
      -
      -
      GET /apis/extensions/v1beta1/namespaces/{namespace}/daemonsets
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.DaemonSetList

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      delete collection of DaemonSet

      -
      -
      -
      DELETE /apis/extensions/v1beta1/namespaces/{namespace}/daemonsets
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Status

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      create a DaemonSet

      -
      -
      -
      POST /apis/extensions/v1beta1/namespaces/{namespace}/daemonsets
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1beta1.DaemonSet

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      202

      Accepted

      v1beta1.DaemonSet

      200

      success

      v1beta1.DaemonSet

      201

      Created

      v1beta1.DaemonSet

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      read the specified DaemonSet

      -
      -
      -
      GET /apis/extensions/v1beta1/namespaces/{namespace}/daemonsets/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      export

      Should this value be exported. Export strips fields that a user can not specify.

      false

      boolean

      QueryParameter

      exact

      Should the export be exact. Exact export maintains cluster-specific fields like Namespace.

      false

      boolean

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the DaemonSet

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.DaemonSet

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      replace the specified DaemonSet

      -
      -
      -
      PUT /apis/extensions/v1beta1/namespaces/{namespace}/daemonsets/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1beta1.DaemonSet

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the DaemonSet

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.DaemonSet

      201

      Created

      v1beta1.DaemonSet

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      delete a DaemonSet

      -
      -
      -
      DELETE /apis/extensions/v1beta1/namespaces/{namespace}/daemonsets/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.DeleteOptions

      QueryParameter

      gracePeriodSeconds

      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.

      false

      integer (int32)

      QueryParameter

      orphanDependents

      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.

      false

      boolean

      QueryParameter

      propagationPolicy

      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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the DaemonSet

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Status

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      partially update the specified DaemonSet

      -
      -
      -
      PATCH /apis/extensions/v1beta1/namespaces/{namespace}/daemonsets/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Patch

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the DaemonSet

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.DaemonSet

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        application/json-patch+json

        -
      • -
      • -

        application/merge-patch+json

        -
      • -
      • -

        application/strategic-merge-patch+json

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      read status of the specified DaemonSet

      -
      -
      -
      GET /apis/extensions/v1beta1/namespaces/{namespace}/daemonsets/{name}/status
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the DaemonSet

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.DaemonSet

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      replace status of the specified DaemonSet

      -
      -
      -
      PUT /apis/extensions/v1beta1/namespaces/{namespace}/daemonsets/{name}/status
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1beta1.DaemonSet

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the DaemonSet

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.DaemonSet

      201

      Created

      v1beta1.DaemonSet

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      partially update status of the specified DaemonSet

      -
      -
      -
      PATCH /apis/extensions/v1beta1/namespaces/{namespace}/daemonsets/{name}/status
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Patch

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the DaemonSet

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.DaemonSet

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        application/json-patch+json

        -
      • -
      • -

        application/merge-patch+json

        -
      • -
      • -

        application/strategic-merge-patch+json

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      list or watch objects of kind Deployment

      -
      -
      -
      GET /apis/extensions/v1beta1/namespaces/{namespace}/deployments
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.DeploymentList

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      delete collection of Deployment

      -
      -
      -
      DELETE /apis/extensions/v1beta1/namespaces/{namespace}/deployments
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Status

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      create a Deployment

      -
      -
      -
      POST /apis/extensions/v1beta1/namespaces/{namespace}/deployments
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1beta1.Deployment

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      202

      Accepted

      v1beta1.Deployment

      200

      success

      v1beta1.Deployment

      201

      Created

      v1beta1.Deployment

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      read the specified Deployment

      -
      -
      -
      GET /apis/extensions/v1beta1/namespaces/{namespace}/deployments/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      export

      Should this value be exported. Export strips fields that a user can not specify.

      false

      boolean

      QueryParameter

      exact

      Should the export be exact. Exact export maintains cluster-specific fields like Namespace.

      false

      boolean

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Deployment

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.Deployment

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      replace the specified Deployment

      -
      -
      -
      PUT /apis/extensions/v1beta1/namespaces/{namespace}/deployments/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1beta1.Deployment

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Deployment

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.Deployment

      201

      Created

      v1beta1.Deployment

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      delete a Deployment

      -
      -
      -
      DELETE /apis/extensions/v1beta1/namespaces/{namespace}/deployments/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.DeleteOptions

      QueryParameter

      gracePeriodSeconds

      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.

      false

      integer (int32)

      QueryParameter

      orphanDependents

      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.

      false

      boolean

      QueryParameter

      propagationPolicy

      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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Deployment

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Status

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      partially update the specified Deployment

      -
      -
      -
      PATCH /apis/extensions/v1beta1/namespaces/{namespace}/deployments/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Patch

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Deployment

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.Deployment

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        application/json-patch+json

        -
      • -
      • -

        application/merge-patch+json

        -
      • -
      • -

        application/strategic-merge-patch+json

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      create rollback of a Deployment

      -
      -
      -
      POST /apis/extensions/v1beta1/namespaces/{namespace}/deployments/{name}/rollback
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1beta1.DeploymentRollback

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the DeploymentRollback

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      202

      Accepted

      v1beta1.DeploymentRollback

      200

      success

      v1beta1.DeploymentRollback

      201

      Created

      v1beta1.DeploymentRollback

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      read scale of the specified Deployment

      -
      -
      -
      GET /apis/extensions/v1beta1/namespaces/{namespace}/deployments/{name}/scale
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Scale

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.Scale

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      replace scale of the specified Deployment

      -
      -
      -
      PUT /apis/extensions/v1beta1/namespaces/{namespace}/deployments/{name}/scale
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1beta1.Scale

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Scale

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.Scale

      201

      Created

      v1beta1.Scale

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      partially update scale of the specified Deployment

      -
      -
      -
      PATCH /apis/extensions/v1beta1/namespaces/{namespace}/deployments/{name}/scale
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Patch

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Scale

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.Scale

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        application/json-patch+json

        -
      • -
      • -

        application/merge-patch+json

        -
      • -
      • -

        application/strategic-merge-patch+json

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      read status of the specified Deployment

      -
      -
      -
      GET /apis/extensions/v1beta1/namespaces/{namespace}/deployments/{name}/status
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Deployment

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.Deployment

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      replace status of the specified Deployment

      -
      -
      -
      PUT /apis/extensions/v1beta1/namespaces/{namespace}/deployments/{name}/status
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1beta1.Deployment

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Deployment

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.Deployment

      201

      Created

      v1beta1.Deployment

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      partially update status of the specified Deployment

      -
      -
      -
      PATCH /apis/extensions/v1beta1/namespaces/{namespace}/deployments/{name}/status
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Patch

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Deployment

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.Deployment

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        application/json-patch+json

        -
      • -
      • -

        application/merge-patch+json

        -
      • -
      • -

        application/strategic-merge-patch+json

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      list or watch objects of kind Ingress

      -
      -
      -
      GET /apis/extensions/v1beta1/namespaces/{namespace}/ingresses
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.IngressList

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      delete collection of Ingress

      -
      -
      -
      DELETE /apis/extensions/v1beta1/namespaces/{namespace}/ingresses
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Status

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      create an Ingress

      -
      -
      -
      POST /apis/extensions/v1beta1/namespaces/{namespace}/ingresses
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1beta1.Ingress

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      202

      Accepted

      v1beta1.Ingress

      200

      success

      v1beta1.Ingress

      201

      Created

      v1beta1.Ingress

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      read the specified Ingress

      -
      -
      -
      GET /apis/extensions/v1beta1/namespaces/{namespace}/ingresses/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      export

      Should this value be exported. Export strips fields that a user can not specify.

      false

      boolean

      QueryParameter

      exact

      Should the export be exact. Exact export maintains cluster-specific fields like Namespace.

      false

      boolean

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Ingress

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.Ingress

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      replace the specified Ingress

      -
      -
      -
      PUT /apis/extensions/v1beta1/namespaces/{namespace}/ingresses/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1beta1.Ingress

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Ingress

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.Ingress

      201

      Created

      v1beta1.Ingress

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      delete an Ingress

      -
      -
      -
      DELETE /apis/extensions/v1beta1/namespaces/{namespace}/ingresses/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.DeleteOptions

      QueryParameter

      gracePeriodSeconds

      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.

      false

      integer (int32)

      QueryParameter

      orphanDependents

      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.

      false

      boolean

      QueryParameter

      propagationPolicy

      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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Ingress

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Status

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      partially update the specified Ingress

      -
      -
      -
      PATCH /apis/extensions/v1beta1/namespaces/{namespace}/ingresses/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Patch

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Ingress

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.Ingress

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        application/json-patch+json

        -
      • -
      • -

        application/merge-patch+json

        -
      • -
      • -

        application/strategic-merge-patch+json

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      read status of the specified Ingress

      -
      -
      -
      GET /apis/extensions/v1beta1/namespaces/{namespace}/ingresses/{name}/status
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Ingress

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.Ingress

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      replace status of the specified Ingress

      -
      -
      -
      PUT /apis/extensions/v1beta1/namespaces/{namespace}/ingresses/{name}/status
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1beta1.Ingress

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Ingress

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.Ingress

      201

      Created

      v1beta1.Ingress

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      partially update status of the specified Ingress

      -
      -
      -
      PATCH /apis/extensions/v1beta1/namespaces/{namespace}/ingresses/{name}/status
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Patch

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Ingress

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.Ingress

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        application/json-patch+json

        -
      • -
      • -

        application/merge-patch+json

        -
      • -
      • -

        application/strategic-merge-patch+json

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      list or watch objects of kind ReplicaSet

      -
      -
      -
      GET /apis/extensions/v1beta1/namespaces/{namespace}/replicasets
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.ReplicaSetList

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      delete collection of ReplicaSet

      -
      -
      -
      DELETE /apis/extensions/v1beta1/namespaces/{namespace}/replicasets
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Status

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      create a ReplicaSet

      -
      -
      -
      POST /apis/extensions/v1beta1/namespaces/{namespace}/replicasets
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1beta1.ReplicaSet

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      202

      Accepted

      v1beta1.ReplicaSet

      200

      success

      v1beta1.ReplicaSet

      201

      Created

      v1beta1.ReplicaSet

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      read the specified ReplicaSet

      -
      -
      -
      GET /apis/extensions/v1beta1/namespaces/{namespace}/replicasets/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      export

      Should this value be exported. Export strips fields that a user can not specify.

      false

      boolean

      QueryParameter

      exact

      Should the export be exact. Exact export maintains cluster-specific fields like Namespace.

      false

      boolean

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the ReplicaSet

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.ReplicaSet

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      replace the specified ReplicaSet

      -
      -
      -
      PUT /apis/extensions/v1beta1/namespaces/{namespace}/replicasets/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1beta1.ReplicaSet

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the ReplicaSet

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.ReplicaSet

      201

      Created

      v1beta1.ReplicaSet

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      delete a ReplicaSet

      -
      -
      -
      DELETE /apis/extensions/v1beta1/namespaces/{namespace}/replicasets/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.DeleteOptions

      QueryParameter

      gracePeriodSeconds

      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.

      false

      integer (int32)

      QueryParameter

      orphanDependents

      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.

      false

      boolean

      QueryParameter

      propagationPolicy

      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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the ReplicaSet

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Status

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      partially update the specified ReplicaSet

      -
      -
      -
      PATCH /apis/extensions/v1beta1/namespaces/{namespace}/replicasets/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Patch

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the ReplicaSet

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.ReplicaSet

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        application/json-patch+json

        -
      • -
      • -

        application/merge-patch+json

        -
      • -
      • -

        application/strategic-merge-patch+json

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      read scale of the specified ReplicaSet

      -
      -
      -
      GET /apis/extensions/v1beta1/namespaces/{namespace}/replicasets/{name}/scale
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Scale

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.Scale

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      replace scale of the specified ReplicaSet

      -
      -
      -
      PUT /apis/extensions/v1beta1/namespaces/{namespace}/replicasets/{name}/scale
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1beta1.Scale

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Scale

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.Scale

      201

      Created

      v1beta1.Scale

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      partially update scale of the specified ReplicaSet

      -
      -
      -
      PATCH /apis/extensions/v1beta1/namespaces/{namespace}/replicasets/{name}/scale
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Patch

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Scale

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.Scale

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        application/json-patch+json

        -
      • -
      • -

        application/merge-patch+json

        -
      • -
      • -

        application/strategic-merge-patch+json

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      read status of the specified ReplicaSet

      -
      -
      -
      GET /apis/extensions/v1beta1/namespaces/{namespace}/replicasets/{name}/status
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the ReplicaSet

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.ReplicaSet

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      replace status of the specified ReplicaSet

      -
      -
      -
      PUT /apis/extensions/v1beta1/namespaces/{namespace}/replicasets/{name}/status
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1beta1.ReplicaSet

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the ReplicaSet

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.ReplicaSet

      201

      Created

      v1beta1.ReplicaSet

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      partially update status of the specified ReplicaSet

      -
      -
      -
      PATCH /apis/extensions/v1beta1/namespaces/{namespace}/replicasets/{name}/status
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Patch

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the ReplicaSet

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.ReplicaSet

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        application/json-patch+json

        -
      • -
      • -

        application/merge-patch+json

        -
      • -
      • -

        application/strategic-merge-patch+json

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      list or watch objects of kind ReplicaSet

      -
      -
      -
      GET /apis/extensions/v1beta1/replicasets
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.ReplicaSetList

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      watch individual changes to a list of DaemonSet

      -
      -
      -
      GET /apis/extensions/v1beta1/watch/daemonsets
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      watch individual changes to a list of Deployment

      -
      -
      -
      GET /apis/extensions/v1beta1/watch/deployments
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      watch individual changes to a list of Ingress

      -
      -
      -
      GET /apis/extensions/v1beta1/watch/ingresses
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      watch individual changes to a list of DaemonSet

      -
      -
      -
      GET /apis/extensions/v1beta1/watch/namespaces/{namespace}/daemonsets
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      watch changes to an object of kind DaemonSet

      -
      -
      -
      GET /apis/extensions/v1beta1/watch/namespaces/{namespace}/daemonsets/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the DaemonSet

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      watch individual changes to a list of Deployment

      -
      -
      -
      GET /apis/extensions/v1beta1/watch/namespaces/{namespace}/deployments
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      watch changes to an object of kind Deployment

      -
      -
      -
      GET /apis/extensions/v1beta1/watch/namespaces/{namespace}/deployments/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Deployment

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      watch individual changes to a list of Ingress

      -
      -
      -
      GET /apis/extensions/v1beta1/watch/namespaces/{namespace}/ingresses
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      watch changes to an object of kind Ingress

      -
      -
      -
      GET /apis/extensions/v1beta1/watch/namespaces/{namespace}/ingresses/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Ingress

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      watch individual changes to a list of ReplicaSet

      -
      -
      -
      GET /apis/extensions/v1beta1/watch/namespaces/{namespace}/replicasets
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      watch changes to an object of kind ReplicaSet

      -
      -
      -
      GET /apis/extensions/v1beta1/watch/namespaces/{namespace}/replicasets/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the ReplicaSet

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -

      watch individual changes to a list of ReplicaSet

      -
      -
      -
      GET /apis/extensions/v1beta1/watch/replicasets
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisextensionsv1beta1

        -
      • -
      -
      -
      -
      -
      -
      -
      - - - \ No newline at end of file diff --git a/federation/docs/api-reference/federation/v1beta1/definitions.html b/federation/docs/api-reference/federation/v1beta1/definitions.html deleted file mode 100755 index d9d1083923e..00000000000 --- a/federation/docs/api-reference/federation/v1beta1/definitions.html +++ /dev/null @@ -1,1601 +0,0 @@ - - - - - - -Top Level API Objects - - - - -
      -
      -

      Top Level API Objects

      - -
      -
      -

      Definitions

      -
      -
      -

      v1.APIResourceList

      -
      -

      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.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      groupVersion

      groupVersion is the group and version this APIResourceList is for.

      true

      string

      resources

      resources contains the name of the resources and if they are namespaced.

      true

      v1.APIResource array

      - -
      -
      -

      v1.Patch

      -
      -

      Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.

      -
      -
      -
      -

      v1.DeleteOptions

      -
      -

      DeleteOptions may be provided when deleting an API object.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      gracePeriodSeconds

      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.

      false

      integer (int64)

      preconditions

      Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.

      false

      v1.Preconditions

      orphanDependents

      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.

      false

      boolean

      false

      propagationPolicy

      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.

      false

      v1.DeletionPropagation

      - -
      -
      -

      v1beta1.ServerAddressByClientCIDR

      -
      -

      ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      clientCIDR

      The CIDR with which clients can match their IP to figure out the server address that they should use.

      true

      string

      serverAddress

      Address of this server, suitable for a client that matches the above CIDR. This can be a hostname, hostname:port, IP or IP:port.

      true

      string

      - -
      -
      -

      v1beta1.ClusterList

      -
      -

      A list of all the kubernetes clusters registered to the federation

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      items

      List of Cluster objects.

      true

      v1beta1.Cluster array

      - -
      -
      -

      v1.ListMeta

      -
      -

      ListMeta describes metadata that synthetic resources must have, including lists and various status objects. A resource may have only one of {ObjectMeta, ListMeta}.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      selfLink

      selfLink is a URL representing this object. Populated by the system. Read-only.

      false

      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/api-conventions.md#concurrency-control-and-consistency

      false

      string

      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 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.

      false

      string

      - -
      -
      -

      v1.StatusDetails

      -
      -

      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.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).

      false

      string

      group

      The group attribute of the resource associated with the status StatusReason.

      false

      string

      kind

      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/api-conventions.md#types-kinds

      false

      string

      uid

      UID of the resource. (when there is a single resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids

      false

      string

      causes

      The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.

      false

      v1.StatusCause array

      retryAfterSeconds

      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.

      false

      integer (int32)

      - -
      -
      -

      v1beta1.ClusterStatus

      -
      -

      ClusterStatus is information about the current status of a cluster updated by cluster controller periodically.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      conditions

      Conditions is an array of current cluster conditions.

      false

      v1beta1.ClusterCondition array

      zones

      Zones is the list of availability zones in which the nodes of the cluster exist, e.g. us-east1-a. These will always be in the same region.

      false

      string array

      region

      Region is the name of the region in which all of the nodes in the cluster exist. e.g. us-east1.

      false

      string

      - -
      -
      -

      v1.Preconditions

      -
      -

      Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      uid

      Specifies the target UID.

      false

      types.UID

      - -
      -
      -

      v1.Initializers

      -
      -

      Initializers tracks the progress of initialization.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      pending

      Pending is a list of initializers that must execute in order before this object is visible. When the last pending initializer is removed, and no failing result is set, the initializers struct will be set to nil and the object is considered as initialized and visible to all clients.

      true

      v1.Initializer array

      result

      If result is set with the Failure field, the object will be persisted to storage and then deleted, ensuring that other clients can observe the deletion.

      false

      v1.Status

      - -
      -
      -

      v1.Initializer

      -
      -

      Initializer is information about an initializer that has not yet completed.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      name of the process that is responsible for initializing this object.

      true

      string

      - -
      -
      -

      v1.LocalObjectReference

      -
      -

      LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      false

      string

      - -
      -
      -

      v1.Status

      -
      -

      Status is a return value for calls that don’t return other objects.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      status

      Status of the operation. One of: "Success" or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      string

      message

      A human-readable description of the status of this operation.

      false

      string

      reason

      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.

      false

      string

      details

      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.

      false

      v1.StatusDetails

      code

      Suggested HTTP return code for this status, 0 if not set.

      false

      integer (int32)

      - -
      -
      -

      v1beta1.ClusterSpec

      -
      -

      ClusterSpec describes the attributes of a kubernetes cluster.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      serverAddressByClientCIDRs

      A map of client CIDR to server address. This is to help clients reach servers in the most network-efficient way possible. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR.

      true

      v1beta1.ServerAddressByClientCIDR array

      secretRef

      Name of the secret containing kubeconfig to access this cluster. The secret is read from the kubernetes cluster that is hosting federation control plane. Admin needs to ensure that the required secret exists. Secret should be in the same namespace where federation control plane is hosted and it should have kubeconfig in its data with key "kubeconfig". This will later be changed to a reference to secret in federation control plane when the federation control plane supports secrets. This can be left empty if the cluster allows insecure access.

      false

      v1.LocalObjectReference

      - -
      -
      -

      v1.WatchEvent

      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      type

      true

      string

      object

      true

      string

      - -
      -
      -

      v1beta1.ClusterCondition

      -
      -

      ClusterCondition describes current state of a cluster.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      type

      Type of cluster condition, Complete or Failed.

      true

      string

      status

      Status of the condition, one of True, False, Unknown.

      true

      string

      lastProbeTime

      Last time the condition was checked.

      false

      string

      lastTransitionTime

      Last time the condition transit from one status to another.

      false

      string

      reason

      (brief) reason for the condition’s last transition.

      false

      string

      message

      Human readable message indicating details about last transition.

      false

      string

      - -
      -
      -

      v1.ObjectMeta

      -
      -

      ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      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: http://kubernetes.io/docs/user-guide/identifiers#names

      false

      string

      generateName

      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.
      -
      -If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header).
      -
      -Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency

      false

      string

      namespace

      Namespace defines the space within 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.
      -
      -Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces

      false

      string

      selfLink

      SelfLink is a URL representing this object. Populated by the system. Read-only.

      false

      string

      uid

      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.
      -
      -Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids

      false

      string

      resourceVersion

      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.
      -
      -Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency

      false

      string

      generation

      A sequence number representing a specific generation of the desired state. Populated by the system. Read-only.

      false

      integer (int64)

      creationTimestamp

      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.
      -
      -Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      string

      deletionTimestamp

      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 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.
      -
      -Populated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      string

      deletionGracePeriodSeconds

      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.

      false

      integer (int64)

      labels

      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: http://kubernetes.io/docs/user-guide/labels

      false

      object

      annotations

      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: http://kubernetes.io/docs/user-guide/annotations

      false

      object

      ownerReferences

      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.

      false

      v1.OwnerReference array

      initializers

      An initializer is a controller which enforces some system invariant at object creation time. This field is a list of initializers that have not yet acted on this object. If nil or empty, this object has been completely initialized. Otherwise, the object is considered uninitialized and is hidden (in list/watch and get calls) from clients that haven’t explicitly asked to observe uninitialized objects.
      -
      -When an object is created, the system will populate this list with the current set of initializers. Only privileged users may set or modify this list. Once it is empty, it may not be modified further by any user.

      false

      v1.Initializers

      finalizers

      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.

      false

      string array

      clusterName

      The name of the cluster which the object belongs to. This is used to distinguish resources with same name and namespace in different clusters. This field is not set anywhere right now and apiserver is going to ignore it if set in create or update request.

      false

      string

      - -
      -
      -

      v1.OwnerReference

      -
      -

      OwnerReference contains enough information to let you identify an owning object. Currently, an owning object must be in the same namespace, so there is no namespace field.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      apiVersion

      API version of the referent.

      true

      string

      kind

      Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      true

      string

      name

      Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names

      true

      string

      uid

      UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids

      true

      string

      controller

      If true, this reference points to the managing controller.

      false

      boolean

      false

      blockOwnerDeletion

      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. Defaults to false. To set this field, a user needs "delete" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.

      false

      boolean

      false

      - -
      -
      -

      v1.APIResource

      -
      -

      APIResource specifies the name of a resource and whether it is namespaced.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      name is the plural name of the resource.

      true

      string

      singularName

      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.

      true

      string

      namespaced

      namespaced indicates if a resource is namespaced or not.

      true

      boolean

      false

      group

      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".

      false

      string

      version

      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)".

      false

      string

      kind

      kind is the kind for the resource (e.g. Foo is the kind for a resource foo)

      true

      string

      verbs

      verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy)

      true

      string array

      shortNames

      shortNames is a list of suggested short names of the resource.

      false

      string array

      categories

      categories is a list of the grouped resources this resource belongs to (e.g. all)

      false

      string array

      - -
      -
      -

      types.UID

      - -
      -
      -

      v1.StatusCause

      -
      -

      StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      reason

      A machine-readable description of the cause of the error. If this value is empty there is no information available.

      false

      string

      message

      A human-readable description of the cause of the error. This field may be presented as-is to a reader.

      false

      string

      field

      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.
      -
      -Examples:
      - "name" - the field "name" on the current resource
      - "items[0].name" - the field "name" on the first array entry in "items"

      false

      string

      - -
      -
      -

      v1.DeletionPropagation

      - -
      -
      -

      v1beta1.Cluster

      -
      -

      Information about a registered cluster in a federated kubernetes setup. Clusters are not namespaced and have unique names in the federation.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ObjectMeta

      spec

      Spec defines the behavior of the Cluster.

      false

      v1beta1.ClusterSpec

      status

      Status describes the current status of a Cluster

      false

      v1beta1.ClusterStatus

      - -
      -
      -

      any

      -
      -

      Represents an untyped JSON map - see the description of the field for more info about the structure of this object.

      -
      -
      -
      -
      -
      - - - \ No newline at end of file diff --git a/federation/docs/api-reference/federation/v1beta1/operations.html b/federation/docs/api-reference/federation/v1beta1/operations.html deleted file mode 100755 index 7a5dc69ea93..00000000000 --- a/federation/docs/api-reference/federation/v1beta1/operations.html +++ /dev/null @@ -1,1916 +0,0 @@ - - - - - - -Operations - - - - -
      -
      -

      Operations

      -
      -
      -

      get available resources

      -
      -
      -
      GET /apis/federation/v1beta1
      -
      -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      default

      success

      v1.APIResourceList

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisfederationv1beta1

        -
      • -
      -
      -
      -
      -
      -

      list or watch objects of kind Cluster

      -
      -
      -
      GET /apis/federation/v1beta1/clusters
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.ClusterList

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisfederationv1beta1

        -
      • -
      -
      -
      -
      -
      -

      delete collection of Cluster

      -
      -
      -
      DELETE /apis/federation/v1beta1/clusters
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Status

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisfederationv1beta1

        -
      • -
      -
      -
      -
      -
      -

      create a Cluster

      -
      -
      -
      POST /apis/federation/v1beta1/clusters
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1beta1.Cluster

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      202

      Accepted

      v1beta1.Cluster

      200

      success

      v1beta1.Cluster

      201

      Created

      v1beta1.Cluster

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisfederationv1beta1

        -
      • -
      -
      -
      -
      -
      -

      read the specified Cluster

      -
      -
      -
      GET /apis/federation/v1beta1/clusters/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      export

      Should this value be exported. Export strips fields that a user can not specify.

      false

      boolean

      QueryParameter

      exact

      Should the export be exact. Exact export maintains cluster-specific fields like Namespace.

      false

      boolean

      PathParameter

      name

      name of the Cluster

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.Cluster

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisfederationv1beta1

        -
      • -
      -
      -
      -
      -
      -

      replace the specified Cluster

      -
      -
      -
      PUT /apis/federation/v1beta1/clusters/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1beta1.Cluster

      PathParameter

      name

      name of the Cluster

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.Cluster

      201

      Created

      v1beta1.Cluster

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisfederationv1beta1

        -
      • -
      -
      -
      -
      -
      -

      delete a Cluster

      -
      -
      -
      DELETE /apis/federation/v1beta1/clusters/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.DeleteOptions

      QueryParameter

      gracePeriodSeconds

      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.

      false

      integer (int32)

      QueryParameter

      orphanDependents

      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.

      false

      boolean

      QueryParameter

      propagationPolicy

      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.

      false

      string

      PathParameter

      name

      name of the Cluster

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Status

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisfederationv1beta1

        -
      • -
      -
      -
      -
      -
      -

      partially update the specified Cluster

      -
      -
      -
      PATCH /apis/federation/v1beta1/clusters/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Patch

      PathParameter

      name

      name of the Cluster

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.Cluster

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        application/json-patch+json

        -
      • -
      • -

        application/merge-patch+json

        -
      • -
      • -

        application/strategic-merge-patch+json

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisfederationv1beta1

        -
      • -
      -
      -
      -
      -
      -

      replace status of the specified Cluster

      -
      -
      -
      PUT /apis/federation/v1beta1/clusters/{name}/status
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1beta1.Cluster

      PathParameter

      name

      name of the Cluster

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1beta1.Cluster

      201

      Created

      v1beta1.Cluster

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisfederationv1beta1

        -
      • -
      -
      -
      -
      -
      -

      watch individual changes to a list of Cluster

      -
      -
      -
      GET /apis/federation/v1beta1/watch/clusters
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisfederationv1beta1

        -
      • -
      -
      -
      -
      -
      -

      watch changes to an object of kind Cluster

      -
      -
      -
      GET /apis/federation/v1beta1/watch/clusters/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      name

      name of the Cluster

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apisfederationv1beta1

        -
      • -
      -
      -
      -
      -
      -
      -
      - - - \ No newline at end of file diff --git a/federation/docs/api-reference/v1/definitions.html b/federation/docs/api-reference/v1/definitions.html deleted file mode 100755 index 8f49de7b16b..00000000000 --- a/federation/docs/api-reference/v1/definitions.html +++ /dev/null @@ -1,2448 +0,0 @@ - - - - - - -Top Level API Objects - - - - -
      -
      -

      Top Level API Objects

      - -
      -
      -

      Definitions

      -
      -
      -

      v1.APIResourceList

      -
      -

      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.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      groupVersion

      groupVersion is the group and version this APIResourceList is for.

      true

      string

      resources

      resources contains the name of the resources and if they are namespaced.

      true

      v1.APIResource array

      - -
      -
      -

      v1.NamespaceList

      -
      -

      NamespaceList is a list of Namespaces.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      items

      Items is the list of Namespace objects in the list. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/

      true

      v1.Namespace array

      - -
      -
      -

      v1.ListMeta

      -
      -

      ListMeta describes metadata that synthetic resources must have, including lists and various status objects. A resource may have only one of {ObjectMeta, ListMeta}.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      selfLink

      selfLink is a URL representing this object. Populated by the system. Read-only.

      false

      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/api-conventions.md#concurrency-control-and-consistency

      false

      string

      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 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.

      false

      string

      - -
      -
      -

      v1.Namespace

      -
      -

      Namespace provides a scope for Names. Use of multiple namespaces is optional.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ObjectMeta

      spec

      Spec defines the behavior of the Namespace. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      v1.NamespaceSpec

      status

      Status describes the current status of a Namespace. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      v1.NamespaceStatus

      - -
      -
      -

      v1.Initializers

      -
      -

      Initializers tracks the progress of initialization.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      pending

      Pending is a list of initializers that must execute in order before this object is visible. When the last pending initializer is removed, and no failing result is set, the initializers struct will be set to nil and the object is considered as initialized and visible to all clients.

      true

      v1.Initializer array

      result

      If result is set with the Failure field, the object will be persisted to storage and then deleted, ensuring that other clients can observe the deletion.

      false

      v1.Status

      - -
      -
      -

      v1.Preconditions

      -
      -

      Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      uid

      Specifies the target UID.

      false

      types.UID

      - -
      -
      -

      v1.Status

      -
      -

      Status is a return value for calls that don’t return other objects.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      status

      Status of the operation. One of: "Success" or "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      string

      message

      A human-readable description of the status of this operation.

      false

      string

      reason

      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.

      false

      string

      details

      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.

      false

      v1.StatusDetails

      code

      Suggested HTTP return code for this status, 0 if not set.

      false

      integer (int32)

      - -
      -
      -

      v1.ServiceStatus

      -
      -

      ServiceStatus represents the current status of a service.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      loadBalancer

      LoadBalancer contains the current status of the load-balancer, if one is present.

      false

      v1.LoadBalancerStatus

      - -
      -
      -

      v1.Secret

      -
      -

      Secret holds secret data of a certain type. The total bytes of the values in the Data field must be less than MaxSecretSize bytes.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ObjectMeta

      data

      Data contains the secret data. Each key must consist of alphanumeric characters, -, _ or .. The serialized form of the secret data is a base64 encoded string, representing the arbitrary (possibly non-string) data value here. Described in https://tools.ietf.org/html/rfc4648#section-4

      false

      object

      stringData

      stringData allows specifying non-binary secret data in string form. It is provided as a write-only convenience method. All keys and values are merged into the data field on write, overwriting any existing values. It is never output when reading from the API.

      false

      object

      type

      Used to facilitate programmatic handling of secret data.

      false

      string

      - -
      -
      -

      v1.WatchEvent

      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      type

      true

      string

      object

      true

      string

      - -
      -
      -

      v1.Event

      -
      -

      Event is a report of an event somewhere in the cluster.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      true

      v1.ObjectMeta

      involvedObject

      The object that this event is about.

      true

      v1.ObjectReference

      reason

      This should be a short, machine understandable string that gives the reason for the transition into the object’s current status.

      false

      string

      message

      A human-readable description of the status of this operation.

      false

      string

      source

      The component reporting this event. Should be a short machine understandable string.

      false

      v1.EventSource

      firstTimestamp

      The time at which the event was first recorded. (Time of server receipt is in TypeMeta.)

      false

      string

      lastTimestamp

      The time at which the most recent occurrence of this event was recorded.

      false

      string

      count

      The number of times this event has occurred.

      false

      integer (int32)

      type

      Type of this event (Normal, Warning), new types could be added in the future

      false

      string

      - -
      -
      -

      v1.LoadBalancerIngress

      -
      -

      LoadBalancerIngress represents the status of a load-balancer ingress point: traffic intended for the service should be sent to an ingress point.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      ip

      IP is set for load-balancer ingress points that are IP based (typically GCE or OpenStack load-balancers)

      false

      string

      hostname

      Hostname is set for load-balancer ingress points that are DNS based (typically AWS load-balancers)

      false

      string

      - -
      -
      -

      v1.DeletionPropagation

      - -
      -
      -

      v1.NamespaceStatus

      -
      -

      NamespaceStatus is information about the current status of a Namespace.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      phase

      Phase is the current lifecycle phase of the namespace. More info: https://git.k8s.io/community/contributors/design-proposals/namespaces.md#phases

      false

      string

      - -
      -
      -

      v1.ServiceList

      -
      -

      ServiceList holds a list of services.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      items

      List of services

      true

      v1.Service array

      - -
      -
      -

      v1.NamespaceSpec

      -
      -

      NamespaceSpec describes the attributes on a Namespace.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      finalizers

      Finalizers is an opaque list of values that must be empty to permanently remove object from storage. More info: https://git.k8s.io/community/contributors/design-proposals/namespaces.md#finalizers

      false

      v1.FinalizerName array

      - -
      -
      -

      v1.Service

      -
      -

      Service is a named abstraction of software service (for example, mysql) consisting of local port (for example 3306) that the proxy listens on, and the selector that determines which pods will answer requests sent through the proxy.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ObjectMeta

      spec

      Spec defines the behavior of a service. https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      v1.ServiceSpec

      status

      Most recently observed status of the service. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

      false

      v1.ServiceStatus

      - -
      -
      -

      v1.Patch

      -
      -

      Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.

      -
      -
      -
      -

      v1.ConfigMapList

      -
      -

      ConfigMapList is a resource containing a list of ConfigMap objects.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ListMeta

      items

      Items is the list of ConfigMaps.

      true

      v1.ConfigMap array

      - -
      -
      -

      v1.DeleteOptions

      -
      -

      DeleteOptions may be provided when deleting an API object.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      gracePeriodSeconds

      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.

      false

      integer (int64)

      preconditions

      Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.

      false

      v1.Preconditions

      orphanDependents

      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.

      false

      boolean

      false

      propagationPolicy

      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.

      false

      v1.DeletionPropagation

      - -
      -
      -

      v1.StatusDetails

      -
      -

      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.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).

      false

      string

      group

      The group attribute of the resource associated with the status StatusReason.

      false

      string

      kind

      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/api-conventions.md#types-kinds

      false

      string

      uid

      UID of the resource. (when there is a single resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids

      false

      string

      causes

      The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.

      false

      v1.StatusCause array

      retryAfterSeconds

      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.

      false

      integer (int32)

      - -
      -
      -

      v1.ConfigMap

      -
      -

      ConfigMap holds configuration data for pods to consume.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard object’s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      v1.ObjectMeta

      data

      Data contains the configuration data. Each key must consist of alphanumeric characters, -, _ or ..

      false

      object

      - -
      -
      -

      v1.Initializer

      -
      -

      Initializer is information about an initializer that has not yet completed.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      name of the process that is responsible for initializing this object.

      true

      string

      - -
      -
      -

      v1.ObjectReference

      -
      -

      ObjectReference contains enough information to let you inspect or modify the referred object.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      string

      namespace

      Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/

      false

      string

      name

      Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

      false

      string

      uid

      UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids

      false

      string

      apiVersion

      API version of the referent.

      false

      string

      resourceVersion

      Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency

      false

      string

      fieldPath

      If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object.

      false

      string

      - -
      -
      -

      v1.LoadBalancerStatus

      -
      -

      LoadBalancerStatus represents the status of a load-balancer.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      ingress

      Ingress is a list containing ingress points for the load-balancer. Traffic intended for the service should be sent to these ingress points.

      false

      v1.LoadBalancerIngress array

      - -
      -
      -

      v1.SecretList

      -
      -

      SecretList is a list of Secret.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      items

      Items is a list of secret objects. More info: https://kubernetes.io/docs/concepts/configuration/secret

      true

      v1.Secret array

      - -
      -
      -

      v1.FinalizerName

      - -
      -
      -

      v1.ServicePort

      -
      -

      ServicePort contains information on service’s port.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      The name of this port within the service. This must be a DNS_LABEL. All ports within a ServiceSpec must have unique names. This maps to the Name field in EndpointPort objects. Optional if only one ServicePort is defined on this service.

      false

      string

      protocol

      The IP protocol for this port. Supports "TCP" and "UDP". Default is TCP.

      false

      string

      port

      The port that will be exposed by this service.

      true

      integer (int32)

      targetPort

      Number or name of the port to access on the pods targeted by the service. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. If this is a string, it will be looked up as a named port in the target Pod’s container ports. If this is not specified, the value of the port field is used (an identity map). This field is ignored for services with clusterIP=None, and should be omitted or set equal to the port field. More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service

      false

      string

      nodePort

      The port on each node on which this service is exposed when type=NodePort or LoadBalancer. Usually assigned by the system. If specified, it will be allocated to the service if unused or else creation of the service will fail. Default is to auto-allocate a port if the ServiceType of this Service requires one. More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport

      false

      integer (int32)

      - -
      -
      -

      v1.OwnerReference

      -
      -

      OwnerReference contains enough information to let you identify an owning object. Currently, an owning object must be in the same namespace, so there is no namespace field.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      apiVersion

      API version of the referent.

      true

      string

      kind

      Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      true

      string

      name

      Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names

      true

      string

      uid

      UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids

      true

      string

      controller

      If true, this reference points to the managing controller.

      false

      boolean

      false

      blockOwnerDeletion

      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. Defaults to false. To set this field, a user needs "delete" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.

      false

      boolean

      false

      - -
      -
      -

      v1.ObjectMeta

      -
      -

      ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      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: http://kubernetes.io/docs/user-guide/identifiers#names

      false

      string

      generateName

      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.
      -
      -If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header).
      -
      -Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency

      false

      string

      namespace

      Namespace defines the space within 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.
      -
      -Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces

      false

      string

      selfLink

      SelfLink is a URL representing this object. Populated by the system. Read-only.

      false

      string

      uid

      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.
      -
      -Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids

      false

      string

      resourceVersion

      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.
      -
      -Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency

      false

      string

      generation

      A sequence number representing a specific generation of the desired state. Populated by the system. Read-only.

      false

      integer (int64)

      creationTimestamp

      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.
      -
      -Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      string

      deletionTimestamp

      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 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.
      -
      -Populated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

      false

      string

      deletionGracePeriodSeconds

      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.

      false

      integer (int64)

      labels

      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: http://kubernetes.io/docs/user-guide/labels

      false

      object

      annotations

      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: http://kubernetes.io/docs/user-guide/annotations

      false

      object

      ownerReferences

      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.

      false

      v1.OwnerReference array

      initializers

      An initializer is a controller which enforces some system invariant at object creation time. This field is a list of initializers that have not yet acted on this object. If nil or empty, this object has been completely initialized. Otherwise, the object is considered uninitialized and is hidden (in list/watch and get calls) from clients that haven’t explicitly asked to observe uninitialized objects.
      -
      -When an object is created, the system will populate this list with the current set of initializers. Only privileged users may set or modify this list. Once it is empty, it may not be modified further by any user.

      false

      v1.Initializers

      finalizers

      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.

      false

      string array

      clusterName

      The name of the cluster which the object belongs to. This is used to distinguish resources with same name and namespace in different clusters. This field is not set anywhere right now and apiserver is going to ignore it if set in create or update request.

      false

      string

      - -
      -
      -

      v1.EventList

      -
      -

      EventList is a list of events.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      kind

      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/api-conventions.md#types-kinds

      false

      string

      apiVersion

      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/api-conventions.md#resources

      false

      string

      metadata

      Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

      false

      v1.ListMeta

      items

      List of events

      true

      v1.Event array

      - -
      -
      -

      v1.ClientIPConfig

      -
      -

      ClientIPConfig represents the configurations of Client IP based session affinity.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      timeoutSeconds

      timeoutSeconds specifies the seconds of ClientIP type session sticky time. The value must be >0 && ⇐86400(for 1 day) if ServiceAffinity == "ClientIP". Default value is 10800(for 3 hours).

      false

      integer (int32)

      - -
      -
      -

      v1.APIResource

      -
      -

      APIResource specifies the name of a resource and whether it is namespaced.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      name

      name is the plural name of the resource.

      true

      string

      singularName

      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.

      true

      string

      namespaced

      namespaced indicates if a resource is namespaced or not.

      true

      boolean

      false

      group

      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".

      false

      string

      version

      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)".

      false

      string

      kind

      kind is the kind for the resource (e.g. Foo is the kind for a resource foo)

      true

      string

      verbs

      verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy)

      true

      string array

      shortNames

      shortNames is a list of suggested short names of the resource.

      false

      string array

      categories

      categories is a list of the grouped resources this resource belongs to (e.g. all)

      false

      string array

      - -
      -
      -

      v1.ServiceSpec

      -
      -

      ServiceSpec describes the attributes that a user creates on a service.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      ports

      The list of ports that are exposed by this service. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies

      false

      v1.ServicePort array

      selector

      Route service traffic to pods with label keys and values matching this selector. If empty or not present, the service is assumed to have an external process managing its endpoints, which Kubernetes will not modify. Only applies to types ClusterIP, NodePort, and LoadBalancer. Ignored if type is ExternalName. More info: https://kubernetes.io/docs/concepts/services-networking/service/

      false

      object

      clusterIP

      clusterIP is the IP address of the service and is usually assigned randomly by the master. If an address is specified manually and is not in use by others, it will be allocated to the service; otherwise, creation of the service will fail. This field can not be changed through updates. Valid values are "None", empty string (""), or a valid IP address. "None" can be specified for headless services when proxying is not required. Only applies to types ClusterIP, NodePort, and LoadBalancer. Ignored if type is ExternalName. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies

      false

      string

      type

      type determines how the Service is exposed. Defaults to ClusterIP. Valid options are ExternalName, ClusterIP, NodePort, and LoadBalancer. "ExternalName" maps to the specified externalName. "ClusterIP" allocates a cluster-internal IP address for load-balancing to endpoints. Endpoints are determined by the selector or if that is not specified, by manual construction of an Endpoints object. If clusterIP is "None", no virtual IP is allocated and the endpoints are published as a set of endpoints rather than a stable IP. "NodePort" builds on ClusterIP and allocates a port on every node which routes to the clusterIP. "LoadBalancer" builds on NodePort and creates an external load-balancer (if supported in the current cloud) which routes to the clusterIP. More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services

      false

      string

      externalIPs

      externalIPs is a list of IP addresses for which nodes in the cluster will also accept traffic for this service. These IPs are not managed by Kubernetes. The user is responsible for ensuring that traffic arrives at a node with this IP. A common example is external load-balancers that are not part of the Kubernetes system.

      false

      string array

      sessionAffinity

      Supports "ClientIP" and "None". Used to maintain session affinity. Enable client IP based session affinity. Must be ClientIP or None. Defaults to None. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies

      false

      string

      loadBalancerIP

      Only applies to Service Type: LoadBalancer LoadBalancer will get created with the IP specified in this field. This feature depends on whether the underlying cloud-provider supports specifying the loadBalancerIP when a load balancer is created. This field will be ignored if the cloud-provider does not support the feature.

      false

      string

      loadBalancerSourceRanges

      If specified and supported by the platform, this will restrict traffic through the cloud-provider load-balancer will be restricted to the specified client IPs. This field will be ignored if the cloud-provider does not support the feature." More info: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/

      false

      string array

      externalName

      externalName is the external reference that kubedns or equivalent will return as a CNAME record for this service. No proxying will be involved. Must be a valid DNS name and requires Type to be ExternalName.

      false

      string

      externalTrafficPolicy

      externalTrafficPolicy denotes if this Service desires to route external traffic to node-local or cluster-wide endpoints. "Local" preserves the client source IP and avoids a second hop for LoadBalancer and Nodeport type services, but risks potentially imbalanced traffic spreading. "Cluster" obscures the client source IP and may cause a second hop to another node, but should have good overall load-spreading.

      false

      string

      healthCheckNodePort

      healthCheckNodePort specifies the healthcheck nodePort for the service. If not specified, HealthCheckNodePort is created by the service api backend with the allocated nodePort. Will use user-specified nodePort value if specified by the client. Only effects when Type is set to LoadBalancer and ExternalTrafficPolicy is set to Local.

      false

      integer (int32)

      publishNotReadyAddresses

      publishNotReadyAddresses, when set to true, indicates that DNS implementations must publish the notReadyAddresses of subsets for the Endpoints associated with the Service. The default value is false. The primary use case for setting this field is to use a StatefulSet’s Headless Service to propagate SRV records for its Pods without respect to their readiness for purpose of peer discovery. This field will replace the service.alpha.kubernetes.io/tolerate-unready-endpoints when that annotation is deprecated and all clients have been converted to use this field.

      false

      boolean

      false

      sessionAffinityConfig

      sessionAffinityConfig contains the configurations of session affinity.

      false

      v1.SessionAffinityConfig

      - -
      -
      -

      v1.EventSource

      -
      -

      EventSource contains information for an event.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      component

      Component from which the event is generated.

      false

      string

      host

      Node name on which the event is generated.

      false

      string

      - -
      -
      -

      types.UID

      - -
      -
      -

      v1.StatusCause

      -
      -

      StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.

      -
      - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      reason

      A machine-readable description of the cause of the error. If this value is empty there is no information available.

      false

      string

      message

      A human-readable description of the cause of the error. This field may be presented as-is to a reader.

      false

      string

      field

      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.
      -
      -Examples:
      - "name" - the field "name" on the current resource
      - "items[0].name" - the field "name" on the first array entry in "items"

      false

      string

      - -
      -
      -

      v1.SessionAffinityConfig

      -
      -

      SessionAffinityConfig represents the configurations of session affinity.

      -
      - ------- - - - - - - - - - - - - - - - - - - -
      NameDescriptionRequiredSchemaDefault

      clientIP

      clientIP contains the configurations of Client IP based session affinity.

      false

      v1.ClientIPConfig

      - -
      -
      -

      any

      -
      -

      Represents an untyped JSON map - see the description of the field for more info about the structure of this object.

      -
      -
      -
      -
      -
      - - - \ No newline at end of file diff --git a/federation/docs/api-reference/v1/operations.html b/federation/docs/api-reference/v1/operations.html deleted file mode 100755 index e9558c40259..00000000000 --- a/federation/docs/api-reference/v1/operations.html +++ /dev/null @@ -1,9524 +0,0 @@ - - - - - - -Operations - - - - -
      -
      -

      Operations

      -
      -
      -

      get available resources

      -
      -
      -
      GET /api/v1
      -
      -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      default

      success

      v1.APIResourceList

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      list or watch objects of kind ConfigMap

      -
      -
      -
      GET /api/v1/configmaps
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.ConfigMapList

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      list or watch objects of kind Event

      -
      -
      -
      GET /api/v1/events
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.EventList

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      list or watch objects of kind Namespace

      -
      -
      -
      GET /api/v1/namespaces
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.NamespaceList

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      create a Namespace

      -
      -
      -
      POST /api/v1/namespaces
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Namespace

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      202

      Accepted

      v1.Namespace

      200

      success

      v1.Namespace

      201

      Created

      v1.Namespace

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      list or watch objects of kind ConfigMap

      -
      -
      -
      GET /api/v1/namespaces/{namespace}/configmaps
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.ConfigMapList

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      delete collection of ConfigMap

      -
      -
      -
      DELETE /api/v1/namespaces/{namespace}/configmaps
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Status

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      create a ConfigMap

      -
      -
      -
      POST /api/v1/namespaces/{namespace}/configmaps
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.ConfigMap

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      202

      Accepted

      v1.ConfigMap

      200

      success

      v1.ConfigMap

      201

      Created

      v1.ConfigMap

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      read the specified ConfigMap

      -
      -
      -
      GET /api/v1/namespaces/{namespace}/configmaps/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      export

      Should this value be exported. Export strips fields that a user can not specify.

      false

      boolean

      QueryParameter

      exact

      Should the export be exact. Exact export maintains cluster-specific fields like Namespace.

      false

      boolean

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the ConfigMap

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.ConfigMap

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      replace the specified ConfigMap

      -
      -
      -
      PUT /api/v1/namespaces/{namespace}/configmaps/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.ConfigMap

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the ConfigMap

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.ConfigMap

      201

      Created

      v1.ConfigMap

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      delete a ConfigMap

      -
      -
      -
      DELETE /api/v1/namespaces/{namespace}/configmaps/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.DeleteOptions

      QueryParameter

      gracePeriodSeconds

      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.

      false

      integer (int32)

      QueryParameter

      orphanDependents

      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.

      false

      boolean

      QueryParameter

      propagationPolicy

      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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the ConfigMap

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Status

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      partially update the specified ConfigMap

      -
      -
      -
      PATCH /api/v1/namespaces/{namespace}/configmaps/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Patch

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the ConfigMap

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.ConfigMap

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        application/json-patch+json

        -
      • -
      • -

        application/merge-patch+json

        -
      • -
      • -

        application/strategic-merge-patch+json

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      list or watch objects of kind Event

      -
      -
      -
      GET /api/v1/namespaces/{namespace}/events
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.EventList

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      delete collection of Event

      -
      -
      -
      DELETE /api/v1/namespaces/{namespace}/events
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Status

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      create an Event

      -
      -
      -
      POST /api/v1/namespaces/{namespace}/events
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Event

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      202

      Accepted

      v1.Event

      200

      success

      v1.Event

      201

      Created

      v1.Event

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      read the specified Event

      -
      -
      -
      GET /api/v1/namespaces/{namespace}/events/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      export

      Should this value be exported. Export strips fields that a user can not specify.

      false

      boolean

      QueryParameter

      exact

      Should the export be exact. Exact export maintains cluster-specific fields like Namespace.

      false

      boolean

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Event

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Event

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      replace the specified Event

      -
      -
      -
      PUT /api/v1/namespaces/{namespace}/events/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Event

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Event

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Event

      201

      Created

      v1.Event

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      delete an Event

      -
      -
      -
      DELETE /api/v1/namespaces/{namespace}/events/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.DeleteOptions

      QueryParameter

      gracePeriodSeconds

      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.

      false

      integer (int32)

      QueryParameter

      orphanDependents

      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.

      false

      boolean

      QueryParameter

      propagationPolicy

      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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Event

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Status

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      partially update the specified Event

      -
      -
      -
      PATCH /api/v1/namespaces/{namespace}/events/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Patch

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Event

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Event

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        application/json-patch+json

        -
      • -
      • -

        application/merge-patch+json

        -
      • -
      • -

        application/strategic-merge-patch+json

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      list or watch objects of kind Secret

      -
      -
      -
      GET /api/v1/namespaces/{namespace}/secrets
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.SecretList

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      delete collection of Secret

      -
      -
      -
      DELETE /api/v1/namespaces/{namespace}/secrets
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Status

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      create a Secret

      -
      -
      -
      POST /api/v1/namespaces/{namespace}/secrets
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Secret

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      202

      Accepted

      v1.Secret

      200

      success

      v1.Secret

      201

      Created

      v1.Secret

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      read the specified Secret

      -
      -
      -
      GET /api/v1/namespaces/{namespace}/secrets/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      export

      Should this value be exported. Export strips fields that a user can not specify.

      false

      boolean

      QueryParameter

      exact

      Should the export be exact. Exact export maintains cluster-specific fields like Namespace.

      false

      boolean

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Secret

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Secret

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      replace the specified Secret

      -
      -
      -
      PUT /api/v1/namespaces/{namespace}/secrets/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Secret

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Secret

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Secret

      201

      Created

      v1.Secret

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      delete a Secret

      -
      -
      -
      DELETE /api/v1/namespaces/{namespace}/secrets/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.DeleteOptions

      QueryParameter

      gracePeriodSeconds

      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.

      false

      integer (int32)

      QueryParameter

      orphanDependents

      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.

      false

      boolean

      QueryParameter

      propagationPolicy

      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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Secret

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Status

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      partially update the specified Secret

      -
      -
      -
      PATCH /api/v1/namespaces/{namespace}/secrets/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Patch

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Secret

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Secret

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        application/json-patch+json

        -
      • -
      • -

        application/merge-patch+json

        -
      • -
      • -

        application/strategic-merge-patch+json

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      list or watch objects of kind Service

      -
      -
      -
      GET /api/v1/namespaces/{namespace}/services
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.ServiceList

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      delete collection of Service

      -
      -
      -
      DELETE /api/v1/namespaces/{namespace}/services
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Status

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      create a Service

      -
      -
      -
      POST /api/v1/namespaces/{namespace}/services
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Service

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      202

      Accepted

      v1.Service

      200

      success

      v1.Service

      201

      Created

      v1.Service

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      read the specified Service

      -
      -
      -
      GET /api/v1/namespaces/{namespace}/services/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      export

      Should this value be exported. Export strips fields that a user can not specify.

      false

      boolean

      QueryParameter

      exact

      Should the export be exact. Exact export maintains cluster-specific fields like Namespace.

      false

      boolean

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Service

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Service

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      replace the specified Service

      -
      -
      -
      PUT /api/v1/namespaces/{namespace}/services/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Service

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Service

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Service

      201

      Created

      v1.Service

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      delete a Service

      -
      -
      -
      DELETE /api/v1/namespaces/{namespace}/services/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.DeleteOptions

      QueryParameter

      gracePeriodSeconds

      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.

      false

      integer (int32)

      QueryParameter

      orphanDependents

      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.

      false

      boolean

      QueryParameter

      propagationPolicy

      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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Service

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Status

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      partially update the specified Service

      -
      -
      -
      PATCH /api/v1/namespaces/{namespace}/services/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Patch

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Service

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Service

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        application/json-patch+json

        -
      • -
      • -

        application/merge-patch+json

        -
      • -
      • -

        application/strategic-merge-patch+json

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      read status of the specified Service

      -
      -
      -
      GET /api/v1/namespaces/{namespace}/services/{name}/status
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Service

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Service

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      replace status of the specified Service

      -
      -
      -
      PUT /api/v1/namespaces/{namespace}/services/{name}/status
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Service

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Service

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Service

      201

      Created

      v1.Service

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      partially update status of the specified Service

      -
      -
      -
      PATCH /api/v1/namespaces/{namespace}/services/{name}/status
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Patch

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Service

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Service

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        application/json-patch+json

        -
      • -
      • -

        application/merge-patch+json

        -
      • -
      • -

        application/strategic-merge-patch+json

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      read the specified Namespace

      -
      -
      -
      GET /api/v1/namespaces/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      export

      Should this value be exported. Export strips fields that a user can not specify.

      false

      boolean

      QueryParameter

      exact

      Should the export be exact. Exact export maintains cluster-specific fields like Namespace.

      false

      boolean

      PathParameter

      name

      name of the Namespace

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Namespace

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      replace the specified Namespace

      -
      -
      -
      PUT /api/v1/namespaces/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Namespace

      PathParameter

      name

      name of the Namespace

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Namespace

      201

      Created

      v1.Namespace

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      delete a Namespace

      -
      -
      -
      DELETE /api/v1/namespaces/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.DeleteOptions

      QueryParameter

      gracePeriodSeconds

      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.

      false

      integer (int32)

      QueryParameter

      orphanDependents

      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.

      false

      boolean

      QueryParameter

      propagationPolicy

      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.

      false

      string

      PathParameter

      name

      name of the Namespace

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Status

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      partially update the specified Namespace

      -
      -
      -
      PATCH /api/v1/namespaces/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Patch

      PathParameter

      name

      name of the Namespace

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Namespace

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        application/json-patch+json

        -
      • -
      • -

        application/merge-patch+json

        -
      • -
      • -

        application/strategic-merge-patch+json

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      replace finalize of the specified Namespace

      -
      -
      -
      PUT /api/v1/namespaces/{name}/finalize
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Namespace

      PathParameter

      name

      name of the Namespace

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Namespace

      201

      Created

      v1.Namespace

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      read status of the specified Namespace

      -
      -
      -
      GET /api/v1/namespaces/{name}/status
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      PathParameter

      name

      name of the Namespace

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Namespace

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      replace status of the specified Namespace

      -
      -
      -
      PUT /api/v1/namespaces/{name}/status
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Namespace

      PathParameter

      name

      name of the Namespace

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Namespace

      201

      Created

      v1.Namespace

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      partially update status of the specified Namespace

      -
      -
      -
      PATCH /api/v1/namespaces/{name}/status
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      BodyParameter

      body

      true

      v1.Patch

      PathParameter

      name

      name of the Namespace

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.Namespace

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        application/json-patch+json

        -
      • -
      • -

        application/merge-patch+json

        -
      • -
      • -

        application/strategic-merge-patch+json

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      list or watch objects of kind Secret

      -
      -
      -
      GET /api/v1/secrets
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.SecretList

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      list or watch objects of kind Service

      -
      -
      -
      GET /api/v1/services
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.ServiceList

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      watch individual changes to a list of ConfigMap

      -
      -
      -
      GET /api/v1/watch/configmaps
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      watch individual changes to a list of Event

      -
      -
      -
      GET /api/v1/watch/events
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      watch individual changes to a list of Namespace

      -
      -
      -
      GET /api/v1/watch/namespaces
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      watch individual changes to a list of ConfigMap

      -
      -
      -
      GET /api/v1/watch/namespaces/{namespace}/configmaps
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      watch changes to an object of kind ConfigMap

      -
      -
      -
      GET /api/v1/watch/namespaces/{namespace}/configmaps/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the ConfigMap

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      watch individual changes to a list of Event

      -
      -
      -
      GET /api/v1/watch/namespaces/{namespace}/events
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      watch changes to an object of kind Event

      -
      -
      -
      GET /api/v1/watch/namespaces/{namespace}/events/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Event

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      watch individual changes to a list of Secret

      -
      -
      -
      GET /api/v1/watch/namespaces/{namespace}/secrets
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      watch changes to an object of kind Secret

      -
      -
      -
      GET /api/v1/watch/namespaces/{namespace}/secrets/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Secret

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      watch individual changes to a list of Service

      -
      -
      -
      GET /api/v1/watch/namespaces/{namespace}/services
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      watch changes to an object of kind Service

      -
      -
      -
      GET /api/v1/watch/namespaces/{namespace}/services/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      namespace

      object name and auth scope, such as for teams and projects

      true

      string

      PathParameter

      name

      name of the Service

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      watch changes to an object of kind Namespace

      -
      -
      -
      GET /api/v1/watch/namespaces/{name}
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      PathParameter

      name

      name of the Namespace

      true

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      watch individual changes to a list of Secret

      -
      -
      -
      GET /api/v1/watch/secrets
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -

      watch individual changes to a list of Service

      -
      -
      -
      GET /api/v1/watch/services
      -
      -
      -
      -

      Parameters

      - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TypeNameDescriptionRequiredSchemaDefault

      QueryParameter

      pretty

      If true, then the output is pretty printed.

      false

      string

      QueryParameter

      labelSelector

      A selector to restrict the list of returned objects by their labels. Defaults to everything.

      false

      string

      QueryParameter

      fieldSelector

      A selector to restrict the list of returned objects by their fields. Defaults to everything.

      false

      string

      QueryParameter

      includeUninitialized

      If true, partially initialized resources are included in the response.

      false

      boolean

      QueryParameter

      watch

      Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.

      false

      boolean

      QueryParameter

      resourceVersion

      When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it’s 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.

      false

      string

      QueryParameter

      timeoutSeconds

      Timeout for the list/watch call.

      false

      integer (int32)

      QueryParameter

      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. -

      The 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.

      false

      integer (int32)

      QueryParameter

      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 indicating the client must restart their list without the continue field. This 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.

      false

      string

      - -
      -
      -

      Responses

      - ----- - - - - - - - - - - - - - - -
      HTTP CodeDescriptionSchema

      200

      success

      v1.WatchEvent

      - -
      -
      -

      Consumes

      -
      -
        -
      • -

        /

        -
      • -
      -
      -
      -
      -

      Produces

      -
      -
        -
      • -

        application/json

        -
      • -
      • -

        application/yaml

        -
      • -
      • -

        application/vnd.kubernetes.protobuf

        -
      • -
      • -

        application/json;stream=watch

        -
      • -
      • -

        application/vnd.kubernetes.protobuf;stream=watch

        -
      • -
      -
      -
      -
      -

      Tags

      -
      -
        -
      • -

        apiv1

        -
      • -
      -
      -
      -
      -
      -
      -
      - - - \ No newline at end of file diff --git a/federation/pkg/dnsprovider/BUILD b/federation/pkg/dnsprovider/BUILD deleted file mode 100644 index 47159f43168..00000000000 --- a/federation/pkg/dnsprovider/BUILD +++ /dev/null @@ -1,49 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = [ - "dns.go", - "doc.go", - "plugins.go", - ], - importpath = "k8s.io/kubernetes/federation/pkg/dnsprovider", - deps = [ - "//federation/pkg/dnsprovider/rrstype:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["dns_test.go"], - importpath = "k8s.io/kubernetes/federation/pkg/dnsprovider", - library = ":go_default_library", - deps = ["//federation/pkg/dnsprovider/rrstype:go_default_library"], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/pkg/dnsprovider/providers/aws/route53:all-srcs", - "//federation/pkg/dnsprovider/providers/coredns:all-srcs", - "//federation/pkg/dnsprovider/providers/google/clouddns:all-srcs", - "//federation/pkg/dnsprovider/rrstype:all-srcs", - "//federation/pkg/dnsprovider/tests:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/pkg/dnsprovider/dns.go b/federation/pkg/dnsprovider/dns.go deleted file mode 100644 index b707280e1d5..00000000000 --- a/federation/pkg/dnsprovider/dns.go +++ /dev/null @@ -1,114 +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 dnsprovider - -import ( - "reflect" - - "k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype" -) - -// Interface is an abstract, pluggable interface for DNS providers. -type Interface interface { - // Zones returns the provider's Zones interface, or false if not supported. - Zones() (Zones, bool) -} - -type Zones interface { - // List returns the managed Zones, or an error if the list operation failed. - List() ([]Zone, error) - // Add creates and returns a new managed zone, or an error if the operation failed - Add(Zone) (Zone, error) - // Remove deletes a managed zone, or returns an error if the operation failed. - Remove(Zone) error - // New allocates a new Zone, which can then be passed to Add() - // Arguments are as per the Zone interface below. - New(name string) (Zone, error) -} - -type Zone interface { - // Name returns the name of the zone, e.g. "example.com" - Name() string - // ID returns the unique provider identifier for the zone - ID() string - // ResourceRecordSets returns the provider's ResourceRecordSets interface, or false if not supported. - ResourceRecordSets() (ResourceRecordSets, bool) -} - -type ResourceRecordSets interface { - // List returns the ResourceRecordSets of the Zone, or an error if the list operation failed. - List() ([]ResourceRecordSet, error) - // Get returns the ResourceRecordSet list with the name in the Zone. - // This is a list because there might be multiple records of different - // types for a given name. If the named resource record sets do not - // exist, but no error occurred, the returned record set will be empty - // and error will be nil. - Get(name string) ([]ResourceRecordSet, error) - // New allocates a new ResourceRecordSet, which can then be passed to ResourceRecordChangeset Add() or Remove() - // Arguments are as per the ResourceRecordSet interface below. - New(name string, rrdatas []string, ttl int64, rrstype rrstype.RrsType) ResourceRecordSet - // StartChangeset begins a new batch operation of changes against the Zone - StartChangeset() ResourceRecordChangeset - // Zone returns the parent zone - Zone() Zone -} - -// ResourceRecordChangeset accumulates a set of changes, that can then be applied with Apply -type ResourceRecordChangeset interface { - // Add adds the creation of a ResourceRecordSet in the Zone to the changeset - Add(ResourceRecordSet) ResourceRecordChangeset - // Remove adds the removal of a ResourceRecordSet in the Zone to the changeset - // The supplied ResourceRecordSet must match one of the existing recordsets (obtained via List()) exactly. - Remove(ResourceRecordSet) ResourceRecordChangeset - // Upsert adds an "create or update" operation for the ResourceRecordSet in the Zone to the changeset - // Note: the implementation may translate this into a Remove followed by an Add operation. - // If you have the pre-image, it will likely be more efficient to call Remove and Add. - Upsert(ResourceRecordSet) ResourceRecordChangeset - // Apply applies the accumulated operations to the Zone. - Apply() error - // IsEmpty returns true if there are no accumulated operations. - IsEmpty() bool - // ResourceRecordSets returns the parent ResourceRecordSets - ResourceRecordSets() ResourceRecordSets -} - -type ResourceRecordSet interface { - // Name returns the name of the ResourceRecordSet, e.g. "www.example.com". - Name() string - // Rrdatas returns the Resource Record Datas of the record set. - Rrdatas() []string - // Ttl returns the time-to-live of the record set, in seconds. - Ttl() int64 - // Type returns the type of the record set (A, CNAME, SRV, etc) - Type() rrstype.RrsType -} - -/* ResourceRecordSetsEquivalent compares two ResourceRecordSets for semantic equivalence. - Go's equality operator doesn't work the way we want it to in this case, - hence the need for this function. - More specifically (from the Go spec): - "Two struct values are equal if their corresponding non-blank fields are equal." - In our case, there may be some private internal member variables that may not be not equal, - but we want the two structs to be considered equivalent anyway, if the fields exposed - via their interfaces are equal. -*/ -func ResourceRecordSetsEquivalent(r1, r2 ResourceRecordSet) bool { - if r1.Name() == r2.Name() && reflect.DeepEqual(r1.Rrdatas(), r2.Rrdatas()) && r1.Ttl() == r2.Ttl() && r1.Type() == r2.Type() { - return true - } - return false -} diff --git a/federation/pkg/dnsprovider/dns_test.go b/federation/pkg/dnsprovider/dns_test.go deleted file mode 100644 index b5a2344c35b..00000000000 --- a/federation/pkg/dnsprovider/dns_test.go +++ /dev/null @@ -1,96 +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 dnsprovider - -import ( - "testing" - - "k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype" -) - -// Compile time interface check -var _ ResourceRecordSet = record{} - -type record struct { - name string - rrdatas []string - ttl int64 - type_ string -} - -func (r record) Name() string { - return r.name -} - -func (r record) Ttl() int64 { - return r.ttl -} - -func (r record) Rrdatas() []string { - return r.rrdatas -} - -func (r record) Type() rrstype.RrsType { - return rrstype.RrsType(r.type_) -} - -const testDNSZone string = "foo.com" - -var testData = []struct { - inputs [2]record - expectedOutput bool -}{ - { - [2]record{ - {"foo", []string{"1.2.3.4", "5,6,7,8"}, 180, "A"}, // Identical - {"foo", []string{"1.2.3.4", "5,6,7,8"}, 180, "A"}}, true, - }, - { - [2]record{ - {"foo", []string{"1.2.3.4", "5,6,7,8"}, 180, "A"}, // Identical except Name - {"bar", []string{"1.2.3.4", "5,6,7,8"}, 180, "A"}}, false, - }, - { - [2]record{ - {"foo", []string{"1.2.3.4", "5,6,7,8"}, 180, "A"}, // Identical except Rrdata - {"foo", []string{"1.2.3.4", "5,6,7,9"}, 180, "A"}}, false, - }, - { - [2]record{ - {"foo", []string{"1.2.3.4", "5,6,7,8"}, 180, "A"}, // Identical except Rrdata ordering reversed - {"foo", []string{"5,6,7,8", "1.2.3.4"}, 180, "A"}}, false, - }, - { - [2]record{ - {"foo", []string{"1.2.3.4", "5,6,7,8"}, 180, "A"}, // Identical except TTL - {"foo", []string{"1.2.3.4", "5,6,7,8"}, 150, "A"}}, false, - }, - { - [2]record{ - {"foo", []string{"1.2.3.4", "5,6,7,8"}, 180, "A"}, // Identical except Type - {"foo", []string{"1.2.3.4", "5,6,7,8"}, 180, "CNAME"}}, false, - }, -} - -func TestEquivalent(t *testing.T) { - for _, test := range testData { - output := ResourceRecordSetsEquivalent(test.inputs[0], test.inputs[1]) - if output != test.expectedOutput { - t.Errorf("Expected equivalence comparison of %q and %q to yield %v, but it vielded %v", test.inputs[0], test.inputs[1], test.expectedOutput, output) - } - } -} diff --git a/federation/pkg/dnsprovider/doc.go b/federation/pkg/dnsprovider/doc.go deleted file mode 100644 index 201c57a0502..00000000000 --- a/federation/pkg/dnsprovider/doc.go +++ /dev/null @@ -1,21 +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. -*/ - -/* -dnsprovider supplies interfaces for dns service providers (e.g. Google Cloud DNS, AWS route53, etc). -Implementations exist in the providers sub-package -*/ -package dnsprovider // import "k8s.io/kubernetes/federation/pkg/dnsprovider" diff --git a/federation/pkg/dnsprovider/plugins.go b/federation/pkg/dnsprovider/plugins.go deleted file mode 100644 index affaf404e3c..00000000000 --- a/federation/pkg/dnsprovider/plugins.go +++ /dev/null @@ -1,109 +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 dnsprovider - -import ( - "fmt" - "io" - "os" - "sync" - - "github.com/golang/glog" -) - -// Factory is a function that returns a dnsprovider.Interface. -// The config parameter provides an io.Reader handler to the factory in -// order to load specific configurations. If no configuration is provided -// the parameter is nil. -type Factory func(config io.Reader) (Interface, error) - -// All registered dns providers. -var providersMutex sync.Mutex -var providers = make(map[string]Factory) - -// RegisterDnsProvider registers a dnsprovider.Factory by name. This -// is expected to happen during startup. -func RegisterDnsProvider(name string, cloud Factory) { - providersMutex.Lock() - defer providersMutex.Unlock() - if _, found := providers[name]; found { - glog.Fatalf("DNS provider %q was registered twice", name) - } - glog.V(1).Infof("Registered DNS provider %q", name) - providers[name] = cloud -} - -// GetDnsProvider creates an instance of the named DNS provider, or nil if -// the name is not known. The error return is only used if the named provider -// was known but failed to initialize. The config parameter specifies the -// io.Reader handler of the configuration file for the DNS provider, or nil -// for no configuration. -func GetDnsProvider(name string, config io.Reader) (Interface, error) { - providersMutex.Lock() - defer providersMutex.Unlock() - f, found := providers[name] - if !found { - return nil, nil - } - return f(config) -} - -// Returns a list of registered dns providers. -func RegisteredDnsProviders() []string { - registeredProviders := make([]string, len(providers)) - i := 0 - for provider := range providers { - registeredProviders[i] = provider - i = i + 1 - } - return registeredProviders -} - -// InitDnsProvider creates an instance of the named DNS provider. -func InitDnsProvider(name string, configFilePath string) (Interface, error) { - var dns Interface - var err error - - if name == "" { - glog.Info("No DNS provider specified.") - return nil, nil - } - - if configFilePath != "" { - var config *os.File - config, err = os.Open(configFilePath) - if err != nil { - return nil, fmt.Errorf("Couldn't open DNS provider configuration %s: %#v", configFilePath, err) - } - - defer config.Close() - dns, err = GetDnsProvider(name, config) - } else { - // Pass explicit nil so plugins can actually check for nil. See - // "Why is my nil error value not equal to nil?" in golang.org/doc/faq. - dns, err = GetDnsProvider(name, nil) - } - - if err != nil { - return nil, fmt.Errorf("could not init DNS provider %q: %v", name, err) - } - if dns == nil { - return nil, fmt.Errorf("unknown DNS provider %q", name) - } - - return dns, nil -} diff --git a/federation/pkg/dnsprovider/providers/aws/route53/BUILD b/federation/pkg/dnsprovider/providers/aws/route53/BUILD deleted file mode 100644 index 444ff4067d1..00000000000 --- a/federation/pkg/dnsprovider/providers/aws/route53/BUILD +++ /dev/null @@ -1,63 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = [ - "interface.go", - "route53.go", - "rrchangeset.go", - "rrset.go", - "rrsets.go", - "zone.go", - "zones.go", - ], - importpath = "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/aws/route53", - deps = [ - "//federation/pkg/dnsprovider:go_default_library", - "//federation/pkg/dnsprovider/providers/aws/route53/stubs:go_default_library", - "//federation/pkg/dnsprovider/rrstype:go_default_library", - "//vendor/github.com/aws/aws-sdk-go/aws:go_default_library", - "//vendor/github.com/aws/aws-sdk-go/aws/request:go_default_library", - "//vendor/github.com/aws/aws-sdk-go/aws/session:go_default_library", - "//vendor/github.com/aws/aws-sdk-go/service/route53:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["route53_test.go"], - importpath = "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/aws/route53", - library = ":go_default_library", - deps = [ - "//federation/pkg/dnsprovider:go_default_library", - "//federation/pkg/dnsprovider/providers/aws/route53/stubs:go_default_library", - "//federation/pkg/dnsprovider/rrstype:go_default_library", - "//federation/pkg/dnsprovider/tests:go_default_library", - "//vendor/github.com/aws/aws-sdk-go/aws:go_default_library", - "//vendor/github.com/aws/aws-sdk-go/service/route53:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/pkg/dnsprovider/providers/aws/route53/stubs:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/pkg/dnsprovider/providers/aws/route53/interface.go b/federation/pkg/dnsprovider/providers/aws/route53/interface.go deleted file mode 100644 index b5f17188728..00000000000 --- a/federation/pkg/dnsprovider/providers/aws/route53/interface.go +++ /dev/null @@ -1,39 +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 route53 - -import ( - "k8s.io/kubernetes/federation/pkg/dnsprovider" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/aws/route53/stubs" -) - -// Compile time check for interface adherence -var _ dnsprovider.Interface = Interface{} - -type Interface struct { - service stubs.Route53API -} - -// New builds an Interface, with a specified Route53API implementation. -// This is useful for testing purposes, but also if we want an instance with with custom AWS options. -func New(service stubs.Route53API) *Interface { - return &Interface{service} -} - -func (i Interface) Zones() (zones dnsprovider.Zones, supported bool) { - return Zones{&i}, true -} diff --git a/federation/pkg/dnsprovider/providers/aws/route53/route53.go b/federation/pkg/dnsprovider/providers/aws/route53/route53.go deleted file mode 100644 index 4e440f7a3da..00000000000 --- a/federation/pkg/dnsprovider/providers/aws/route53/route53.go +++ /dev/null @@ -1,72 +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. -*/ - -// route53 is the implementation of pkg/dnsprovider interface for AWS Route53 -package route53 - -import ( - "io" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/request" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/route53" - "github.com/golang/glog" - "k8s.io/kubernetes/federation/pkg/dnsprovider" -) - -const ( - ProviderName = "aws-route53" -) - -func init() { - dnsprovider.RegisterDnsProvider(ProviderName, func(config io.Reader) (dnsprovider.Interface, error) { - return newRoute53(config) - }) -} - -// route53HandlerLogger is a request handler for aws-sdk-go that logs route53 requests -func route53HandlerLogger(req *request.Request) { - service := req.ClientInfo.ServiceName - - name := "?" - if req.Operation != nil { - name = req.Operation.Name - } - - glog.V(4).Infof("AWS request: %s %s", service, name) -} - -// newRoute53 creates a new instance of an AWS Route53 DNS Interface. -func newRoute53(config io.Reader) (*Interface, error) { - // Connect to AWS Route53 - TODO: Do more sophisticated auth - - awsConfig := aws.NewConfig() - - // This avoids a confusing error message when we fail to get credentials - // e.g. https://github.com/kubernetes/kops/issues/605 - awsConfig = awsConfig.WithCredentialsChainVerboseErrors(true) - - svc := route53.New(session.New(), awsConfig) - - // Add our handler that will log requests - svc.Handlers.Sign.PushFrontNamed(request.NamedHandler{ - Name: "k8s/logger", - Fn: route53HandlerLogger, - }) - - return New(svc), nil -} diff --git a/federation/pkg/dnsprovider/providers/aws/route53/route53_test.go b/federation/pkg/dnsprovider/providers/aws/route53/route53_test.go deleted file mode 100644 index 19d8967fdeb..00000000000 --- a/federation/pkg/dnsprovider/providers/aws/route53/route53_test.go +++ /dev/null @@ -1,295 +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 route53 - -import ( - "flag" - "fmt" - "os" - "testing" - - "k8s.io/kubernetes/federation/pkg/dnsprovider" - route53testing "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/aws/route53/stubs" - "k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/route53" - "k8s.io/kubernetes/federation/pkg/dnsprovider/tests" -) - -func newTestInterface() (dnsprovider.Interface, error) { - // Use this to test the real cloud service. - // return dnsprovider.GetDnsProvider(ProviderName, strings.NewReader("\n[global]\nproject-id = federation0-cluster00")) - return newFakeInterface() // Use this to stub out the entire cloud service -} - -func newFakeInterface() (dnsprovider.Interface, error) { - var service route53testing.Route53API - service = route53testing.NewRoute53APIStub() - iface := New(service) - // Add a fake zone to test against. - params := &route53.CreateHostedZoneInput{ - CallerReference: aws.String("Nonce"), // Required - Name: aws.String("example.com"), // Required - } - _, err := iface.service.CreateHostedZone(params) - if err != nil { - return nil, err - } - return iface, nil -} - -var interface_ dnsprovider.Interface - -func TestMain(m *testing.M) { - fmt.Printf("Parsing flags.\n") - flag.Parse() - var err error - fmt.Printf("Getting new test interface.\n") - interface_, err = newTestInterface() - if err != nil { - fmt.Printf("Error creating interface: %v", err) - os.Exit(1) - } - fmt.Printf("Running tests...\n") - os.Exit(m.Run()) -} - -// zones returns the zones interface for the configured dns provider account/project, -// or fails if it can't be found -func zones(t *testing.T) dnsprovider.Zones { - zonesInterface, supported := interface_.Zones() - if !supported { - t.Fatalf("Zones interface not supported by interface %v", interface_) - } else { - t.Logf("Got zones %v\n", zonesInterface) - } - return zonesInterface -} - -// firstZone returns the first zone for the configured dns provider account/project, -// or fails if it can't be found -func firstZone(t *testing.T) dnsprovider.Zone { - t.Logf("Getting zones") - z := zones(t) - zones, err := z.List() - if err != nil { - t.Fatalf("Failed to list zones: %v", err) - } else { - t.Logf("Got zone list: %v\n", zones) - } - if len(zones) < 1 { - t.Fatalf("Zone listing returned %d, expected >= %d", len(zones), 1) - } else { - t.Logf("Got at least 1 zone in list:%v\n", zones[0]) - } - return zones[0] -} - -/* rrs returns the ResourceRecordSets interface for a given zone */ -func rrs(t *testing.T, zone dnsprovider.Zone) (r dnsprovider.ResourceRecordSets) { - rrsets, supported := zone.ResourceRecordSets() - if !supported { - t.Fatalf("ResourceRecordSets interface not supported by zone %v", zone) - return r - } - return rrsets -} - -func listRrsOrFail(t *testing.T, rrsets dnsprovider.ResourceRecordSets) []dnsprovider.ResourceRecordSet { - rrset, err := rrsets.List() - if err != nil { - t.Fatalf("Failed to list recordsets: %v", err) - } else { - if len(rrset) < 0 { - t.Fatalf("Record set length=%d, expected >=0", len(rrset)) - } else { - t.Logf("Got %d recordsets: %v", len(rrset), rrset) - } - } - return rrset -} - -func getExampleRrs(zone dnsprovider.Zone) dnsprovider.ResourceRecordSet { - rrsets, _ := zone.ResourceRecordSets() - return rrsets.New("www11."+zone.Name(), []string{"10.10.10.10", "169.20.20.20"}, 180, rrstype.A) -} - -func getInvalidRrs(zone dnsprovider.Zone) dnsprovider.ResourceRecordSet { - rrsets, _ := zone.ResourceRecordSets() - return rrsets.New("www12."+zone.Name(), []string{"rubbish", "rubbish"}, 180, rrstype.A) -} - -func addRrsetOrFail(t *testing.T, rrsets dnsprovider.ResourceRecordSets, rrset dnsprovider.ResourceRecordSet) { - err := rrsets.StartChangeset().Add(rrset).Apply() - if err != nil { - t.Fatalf("Failed to add recordsets: %v", err) - } -} - -/* TestZonesList verifies that listing of zones succeeds */ -func TestZonesList(t *testing.T) { - firstZone(t) -} - -/* TestZonesID verifies that the id of the zone is returned with the prefix removed */ -func TestZonesID(t *testing.T) { - zone := firstZone(t) - - // Check /hostedzone/ prefix is removed - zoneID := zone.ID() - if zoneID != zone.Name() { - t.Fatalf("Unexpected zone id: %q", zoneID) - } -} - -/* TestZoneAddSuccess verifies that addition of a valid managed DNS zone succeeds */ -func TestZoneAddSuccess(t *testing.T) { - testZoneName := "ubernetes.testing" - z := zones(t) - input, err := z.New(testZoneName) - if err != nil { - t.Errorf("Failed to allocate new zone object %s: %v", testZoneName, err) - } - zone, err := z.Add(input) - if err != nil { - t.Errorf("Failed to create new managed DNS zone %s: %v", testZoneName, err) - } - defer func(zone dnsprovider.Zone) { - if zone != nil { - if err := z.Remove(zone); err != nil { - t.Errorf("Failed to delete zone %v: %v", zone, err) - } - } - }(zone) - t.Logf("Successfully added managed DNS zone: %v", zone) -} - -/* TestResourceRecordSetsList verifies that listing of RRS's succeeds */ -func TestResourceRecordSetsList(t *testing.T) { - listRrsOrFail(t, rrs(t, firstZone(t))) -} - -/* TestResourceRecordSetsAddSuccess verifies that addition of a valid RRS succeeds */ -func TestResourceRecordSetsAddSuccess(t *testing.T) { - zone := firstZone(t) - sets := rrs(t, zone) - set := getExampleRrs(zone) - addRrsetOrFail(t, sets, set) - defer sets.StartChangeset().Remove(set).Apply() - t.Logf("Successfully added resource record set: %v", set) -} - -/* TestResourceRecordSetsAdditionVisible verifies that added RRS is visible after addition */ -func TestResourceRecordSetsAdditionVisible(t *testing.T) { - zone := firstZone(t) - sets := rrs(t, zone) - rrset := getExampleRrs(zone) - addRrsetOrFail(t, sets, rrset) - defer sets.StartChangeset().Remove(rrset).Apply() - t.Logf("Successfully added resource record set: %v", rrset) - found := false - for _, record := range listRrsOrFail(t, sets) { - if record.Name() == rrset.Name() { - found = true - break - } - } - if !found { - t.Errorf("Failed to find added resource record set %s", rrset.Name()) - } -} - -/* TestResourceRecordSetsAddDuplicateFail verifies that addition of a duplicate RRS fails */ -func TestResourceRecordSetsAddDuplicateFail(t *testing.T) { - zone := firstZone(t) - sets := rrs(t, zone) - rrset := getExampleRrs(zone) - addRrsetOrFail(t, sets, rrset) - defer sets.StartChangeset().Remove(rrset).Apply() - t.Logf("Successfully added resource record set: %v", rrset) - // Try to add it again, and verify that the call fails. - err := sets.StartChangeset().Add(rrset).Apply() - if err == nil { - defer sets.StartChangeset().Remove(rrset).Apply() - t.Errorf("Should have failed to add duplicate resource record %v, but succeeded instead.", rrset) - } else { - t.Logf("Correctly failed to add duplicate resource record %v: %v", rrset, err) - } -} - -/* TestResourceRecordSetsRemove verifies that the removal of an existing RRS succeeds */ -func TestResourceRecordSetsRemove(t *testing.T) { - zone := firstZone(t) - sets := rrs(t, zone) - rrset := getExampleRrs(zone) - addRrsetOrFail(t, sets, rrset) - err := sets.StartChangeset().Remove(rrset).Apply() - if err != nil { - // Try again to clean up. - defer sets.StartChangeset().Remove(rrset).Apply() - t.Errorf("Failed to remove resource record set %v after adding", rrset) - } else { - t.Logf("Successfully removed resource set %v after adding", rrset) - } -} - -/* TestResourceRecordSetsRemoveGone verifies that a removed RRS no longer exists */ -func TestResourceRecordSetsRemoveGone(t *testing.T) { - zone := firstZone(t) - sets := rrs(t, zone) - rrset := getExampleRrs(zone) - addRrsetOrFail(t, sets, rrset) - err := sets.StartChangeset().Remove(rrset).Apply() - if err != nil { - // Try again to clean up. - defer sets.StartChangeset().Remove(rrset).Apply() - t.Errorf("Failed to remove resource record set %v after adding", rrset) - } else { - t.Logf("Successfully removed resource set %v after adding", rrset) - } - // Check that it's gone - list := listRrsOrFail(t, sets) - found := false - for _, set := range list { - if set.Name() == rrset.Name() { - found = true - break - } - } - if found { - t.Errorf("Deleted resource record set %v is still present", rrset) - } -} - -/* TestResourceRecordSetsReplace verifies that replacing an RRS works */ -func TestResourceRecordSetsReplace(t *testing.T) { - zone := firstZone(t) - tests.CommonTestResourceRecordSetsReplace(t, zone) -} - -/* TestResourceRecordSetsReplaceAll verifies that we can remove an RRS and create one with a different name*/ -func TestResourceRecordSetsReplaceAll(t *testing.T) { - zone := firstZone(t) - tests.CommonTestResourceRecordSetsReplaceAll(t, zone) -} - -/* TestResourceRecordSetsDifferentTypes verifies that we can add records of the same name but different types */ -func TestResourceRecordSetsDifferentTypes(t *testing.T) { - zone := firstZone(t) - tests.CommonTestResourceRecordSetsDifferentTypes(t, zone) -} diff --git a/federation/pkg/dnsprovider/providers/aws/route53/rrchangeset.go b/federation/pkg/dnsprovider/providers/aws/route53/rrchangeset.go deleted file mode 100644 index 7727159fa32..00000000000 --- a/federation/pkg/dnsprovider/providers/aws/route53/rrchangeset.go +++ /dev/null @@ -1,134 +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 route53 - -import ( - "bytes" - "fmt" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/route53" - "github.com/golang/glog" - "k8s.io/kubernetes/federation/pkg/dnsprovider" -) - -// Compile time check for interface adherence -var _ dnsprovider.ResourceRecordChangeset = &ResourceRecordChangeset{} - -type ResourceRecordChangeset struct { - zone *Zone - rrsets *ResourceRecordSets - - additions []dnsprovider.ResourceRecordSet - removals []dnsprovider.ResourceRecordSet - upserts []dnsprovider.ResourceRecordSet -} - -func (c *ResourceRecordChangeset) Add(rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordChangeset { - c.additions = append(c.additions, rrset) - return c -} - -func (c *ResourceRecordChangeset) Remove(rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordChangeset { - c.removals = append(c.removals, rrset) - return c -} - -func (c *ResourceRecordChangeset) Upsert(rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordChangeset { - c.upserts = append(c.upserts, rrset) - return c -} - -// buildChange converts a dnsprovider.ResourceRecordSet to a route53.Change request -func buildChange(action string, rrs dnsprovider.ResourceRecordSet) *route53.Change { - change := &route53.Change{ - Action: aws.String(action), - ResourceRecordSet: &route53.ResourceRecordSet{ - Name: aws.String(rrs.Name()), - Type: aws.String(string(rrs.Type())), - TTL: aws.Int64(rrs.Ttl()), - }, - } - - for _, rrdata := range rrs.Rrdatas() { - rr := &route53.ResourceRecord{ - Value: aws.String(rrdata), - } - change.ResourceRecordSet.ResourceRecords = append(change.ResourceRecordSet.ResourceRecords, rr) - } - return change -} - -func (c *ResourceRecordChangeset) Apply() error { - hostedZoneID := c.zone.impl.Id - - var changes []*route53.Change - - for _, removal := range c.removals { - change := buildChange(route53.ChangeActionDelete, removal) - changes = append(changes, change) - } - - for _, addition := range c.additions { - change := buildChange(route53.ChangeActionCreate, addition) - changes = append(changes, change) - } - - for _, upsert := range c.upserts { - change := buildChange(route53.ChangeActionUpsert, upsert) - changes = append(changes, change) - } - - if len(changes) == 0 { - return nil - } - - if glog.V(8) { - var sb bytes.Buffer - for _, change := range changes { - sb.WriteString(fmt.Sprintf("\t%s %s %s\n", aws.StringValue(change.Action), aws.StringValue(change.ResourceRecordSet.Type), aws.StringValue(change.ResourceRecordSet.Name))) - } - - glog.V(8).Infof("Route53 Changeset:\n%s", sb.String()) - } - - service := c.zone.zones.interface_.service - - request := &route53.ChangeResourceRecordSetsInput{ - ChangeBatch: &route53.ChangeBatch{ - Changes: changes, - }, - HostedZoneId: hostedZoneID, - } - - _, err := service.ChangeResourceRecordSets(request) - if err != nil { - // Cast err to awserr.Error to get the Code and - // Message from an error. - return err - } - return nil -} - -func (c *ResourceRecordChangeset) IsEmpty() bool { - return len(c.removals) == 0 && len(c.additions) == 0 -} - -// ResourceRecordSets returns the parent ResourceRecordSets -func (c *ResourceRecordChangeset) ResourceRecordSets() dnsprovider.ResourceRecordSets { - return c.rrsets -} diff --git a/federation/pkg/dnsprovider/providers/aws/route53/rrset.go b/federation/pkg/dnsprovider/providers/aws/route53/rrset.go deleted file mode 100644 index cee6fcc3425..00000000000 --- a/federation/pkg/dnsprovider/providers/aws/route53/rrset.go +++ /dev/null @@ -1,63 +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 route53 - -import ( - "k8s.io/kubernetes/federation/pkg/dnsprovider" - "k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/route53" -) - -// Compile time check for interface adherence -var _ dnsprovider.ResourceRecordSet = ResourceRecordSet{} - -type ResourceRecordSet struct { - impl *route53.ResourceRecordSet - rrsets *ResourceRecordSets -} - -func (rrset ResourceRecordSet) Name() string { - return aws.StringValue(rrset.impl.Name) -} - -func (rrset ResourceRecordSet) Rrdatas() []string { - // Sigh - need to unpack the strings out of the route53 ResourceRecords - result := make([]string, len(rrset.impl.ResourceRecords)) - for i, record := range rrset.impl.ResourceRecords { - result[i] = aws.StringValue(record.Value) - } - return result -} - -func (rrset ResourceRecordSet) Ttl() int64 { - return aws.Int64Value(rrset.impl.TTL) -} - -func (rrset ResourceRecordSet) Type() rrstype.RrsType { - return rrstype.RrsType(aws.StringValue(rrset.impl.Type)) -} - -// Route53ResourceRecordSet returns the route53 ResourceRecordSet object for the ResourceRecordSet -// This is a "back door" that allows for limited access to the ResourceRecordSet, -// without having to requery it, so that we can expose AWS specific functionality. -// Using this method should be avoided where possible; instead prefer to add functionality -// to the cross-provider ResourceRecordSet interface. -func (rrset ResourceRecordSet) Route53ResourceRecordSet() *route53.ResourceRecordSet { - return rrset.impl -} diff --git a/federation/pkg/dnsprovider/providers/aws/route53/rrsets.go b/federation/pkg/dnsprovider/providers/aws/route53/rrsets.go deleted file mode 100644 index 67470c8778f..00000000000 --- a/federation/pkg/dnsprovider/providers/aws/route53/rrsets.go +++ /dev/null @@ -1,106 +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 route53 - -import ( - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/route53" - "k8s.io/kubernetes/federation/pkg/dnsprovider" - "k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype" -) - -// Compile time check for interface adherence -var _ dnsprovider.ResourceRecordSets = ResourceRecordSets{} - -type ResourceRecordSets struct { - zone *Zone -} - -func (rrsets ResourceRecordSets) List() ([]dnsprovider.ResourceRecordSet, error) { - input := route53.ListResourceRecordSetsInput{ - HostedZoneId: rrsets.zone.impl.Id, - } - - var list []dnsprovider.ResourceRecordSet - err := rrsets.zone.zones.interface_.service.ListResourceRecordSetsPages(&input, func(page *route53.ListResourceRecordSetsOutput, lastPage bool) bool { - for _, rrset := range page.ResourceRecordSets { - list = append(list, &ResourceRecordSet{rrset, &rrsets}) - } - return true - }) - if err != nil { - return nil, err - } - return list, nil -} - -func (rrsets ResourceRecordSets) Get(name string) ([]dnsprovider.ResourceRecordSet, error) { - // This list implementation is very similar to the one implemented in - // the List() method above, but it restricts the retrieved list to - // the records whose name match the given `name`. - input := route53.ListResourceRecordSetsInput{ - HostedZoneId: rrsets.zone.impl.Id, - StartRecordName: aws.String(name), - } - - var list []dnsprovider.ResourceRecordSet - err := rrsets.zone.zones.interface_.service.ListResourceRecordSetsPages(&input, func(page *route53.ListResourceRecordSetsOutput, lastPage bool) bool { - for _, rrset := range page.ResourceRecordSets { - if aws.StringValue(rrset.Name) != name { - return false - } - list = append(list, &ResourceRecordSet{rrset, &rrsets}) - } - return true - }) - if err != nil { - return nil, err - } - - return list, nil -} - -func (r ResourceRecordSets) StartChangeset() dnsprovider.ResourceRecordChangeset { - return &ResourceRecordChangeset{ - zone: r.zone, - rrsets: &r, - } -} - -func (r ResourceRecordSets) New(name string, rrdatas []string, ttl int64, rrstype rrstype.RrsType) dnsprovider.ResourceRecordSet { - rrstypeStr := string(rrstype) - rrs := &route53.ResourceRecordSet{ - Name: &name, - Type: &rrstypeStr, - TTL: &ttl, - } - for _, rrdata := range rrdatas { - rrs.ResourceRecords = append(rrs.ResourceRecords, &route53.ResourceRecord{ - Value: aws.String(rrdata), - }) - } - - return ResourceRecordSet{ - rrs, - &r, - } -} - -// Zone returns the parent zone -func (rrset ResourceRecordSets) Zone() dnsprovider.Zone { - return rrset.zone -} diff --git a/federation/pkg/dnsprovider/providers/aws/route53/stubs/BUILD b/federation/pkg/dnsprovider/providers/aws/route53/stubs/BUILD deleted file mode 100644 index 13f4e589b4c..00000000000 --- a/federation/pkg/dnsprovider/providers/aws/route53/stubs/BUILD +++ /dev/null @@ -1,29 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["route53api.go"], - importpath = "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/aws/route53/stubs", - deps = [ - "//vendor/github.com/aws/aws-sdk-go/aws:go_default_library", - "//vendor/github.com/aws/aws-sdk-go/service/route53:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/pkg/dnsprovider/providers/aws/route53/stubs/route53api.go b/federation/pkg/dnsprovider/providers/aws/route53/stubs/route53api.go deleted file mode 100644 index b09155a6da0..00000000000 --- a/federation/pkg/dnsprovider/providers/aws/route53/stubs/route53api.go +++ /dev/null @@ -1,133 +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. -*/ - -/* internal implements a stub for the AWS Route53 API, used primarily for unit testing purposes */ -package stubs - -import ( - "fmt" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/route53" -) - -// Compile time check for interface conformance -var _ Route53API = &Route53APIStub{} - -/* Route53API is the subset of the AWS Route53 API that we actually use. Add methods as required. Signatures must match exactly. */ -type Route53API interface { - ListResourceRecordSetsPages(input *route53.ListResourceRecordSetsInput, fn func(p *route53.ListResourceRecordSetsOutput, lastPage bool) (shouldContinue bool)) error - ChangeResourceRecordSets(*route53.ChangeResourceRecordSetsInput) (*route53.ChangeResourceRecordSetsOutput, error) - ListHostedZonesPages(input *route53.ListHostedZonesInput, fn func(p *route53.ListHostedZonesOutput, lastPage bool) (shouldContinue bool)) error - CreateHostedZone(*route53.CreateHostedZoneInput) (*route53.CreateHostedZoneOutput, error) - DeleteHostedZone(*route53.DeleteHostedZoneInput) (*route53.DeleteHostedZoneOutput, error) -} - -// Route53APIStub is a minimal implementation of Route53API, used primarily for unit testing. -// See http://http://docs.aws.amazon.com/sdk-for-go/api/service/route53.html for descriptions -// of all of its methods. -type Route53APIStub struct { - zones map[string]*route53.HostedZone - recordSets map[string]map[string][]*route53.ResourceRecordSet -} - -// NewRoute53APIStub returns an initialized Route53APIStub -func NewRoute53APIStub() *Route53APIStub { - return &Route53APIStub{ - zones: make(map[string]*route53.HostedZone), - recordSets: make(map[string]map[string][]*route53.ResourceRecordSet), - } -} - -func (r *Route53APIStub) ListResourceRecordSetsPages(input *route53.ListResourceRecordSetsInput, fn func(p *route53.ListResourceRecordSetsOutput, lastPage bool) (shouldContinue bool)) error { - output := route53.ListResourceRecordSetsOutput{} // TODO: Support optional input args. - if len(r.recordSets) <= 0 { - output.ResourceRecordSets = []*route53.ResourceRecordSet{} - } else if _, ok := r.recordSets[*input.HostedZoneId]; !ok { - output.ResourceRecordSets = []*route53.ResourceRecordSet{} - } else { - for _, rrsets := range r.recordSets[*input.HostedZoneId] { - for _, rrset := range rrsets { - output.ResourceRecordSets = append(output.ResourceRecordSets, rrset) - } - } - } - lastPage := true - fn(&output, lastPage) - return nil -} - -func (r *Route53APIStub) ChangeResourceRecordSets(input *route53.ChangeResourceRecordSetsInput) (*route53.ChangeResourceRecordSetsOutput, error) { - output := &route53.ChangeResourceRecordSetsOutput{} - recordSets, ok := r.recordSets[*input.HostedZoneId] - if !ok { - recordSets = make(map[string][]*route53.ResourceRecordSet) - } - - for _, change := range input.ChangeBatch.Changes { - key := *change.ResourceRecordSet.Name + "::" + *change.ResourceRecordSet.Type - switch *change.Action { - case route53.ChangeActionCreate: - if _, found := recordSets[key]; found { - return nil, fmt.Errorf("Attempt to create duplicate rrset %s", key) // TODO: Return AWS errors with codes etc - } - recordSets[key] = append(recordSets[key], change.ResourceRecordSet) - case route53.ChangeActionDelete: - if _, found := recordSets[key]; !found { - return nil, fmt.Errorf("Attempt to delete non-existent rrset %s", key) // TODO: Check other fields too - } - delete(recordSets, key) - case route53.ChangeActionUpsert: - // TODO - not used yet - } - } - r.recordSets[*input.HostedZoneId] = recordSets - return output, nil // TODO: We should ideally return status etc, but we don't' use that yet. -} - -func (r *Route53APIStub) ListHostedZonesPages(input *route53.ListHostedZonesInput, fn func(p *route53.ListHostedZonesOutput, lastPage bool) (shouldContinue bool)) error { - output := &route53.ListHostedZonesOutput{} - for _, zone := range r.zones { - output.HostedZones = append(output.HostedZones, zone) - } - lastPage := true - fn(output, lastPage) - return nil -} - -func (r *Route53APIStub) CreateHostedZone(input *route53.CreateHostedZoneInput) (*route53.CreateHostedZoneOutput, error) { - name := aws.StringValue(input.Name) - id := "/hostedzone/" + name - if _, ok := r.zones[id]; ok { - return nil, fmt.Errorf("Error creating hosted DNS zone: %s already exists", id) - } - r.zones[id] = &route53.HostedZone{ - Id: aws.String(id), - Name: aws.String(name), - } - return &route53.CreateHostedZoneOutput{HostedZone: r.zones[id]}, nil -} - -func (r *Route53APIStub) DeleteHostedZone(input *route53.DeleteHostedZoneInput) (*route53.DeleteHostedZoneOutput, error) { - if _, ok := r.zones[*input.Id]; !ok { - return nil, fmt.Errorf("Error deleting hosted DNS zone: %s does not exist", *input.Id) - } - if len(r.recordSets[*input.Id]) > 0 { - return nil, fmt.Errorf("Error deleting hosted DNS zone: %s has resource records", *input.Id) - } - delete(r.zones, *input.Id) - return &route53.DeleteHostedZoneOutput{}, nil -} diff --git a/federation/pkg/dnsprovider/providers/aws/route53/zone.go b/federation/pkg/dnsprovider/providers/aws/route53/zone.go deleted file mode 100644 index 7d82783ba09..00000000000 --- a/federation/pkg/dnsprovider/providers/aws/route53/zone.go +++ /dev/null @@ -1,56 +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 route53 - -import ( - "strings" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/route53" - "k8s.io/kubernetes/federation/pkg/dnsprovider" -) - -// Compile time check for interface adherence -var _ dnsprovider.Zone = &Zone{} - -type Zone struct { - impl *route53.HostedZone - zones *Zones -} - -func (zone *Zone) Name() string { - return aws.StringValue(zone.impl.Name) -} - -func (zone *Zone) ID() string { - id := aws.StringValue(zone.impl.Id) - id = strings.TrimPrefix(id, "/hostedzone/") - return id -} - -func (zone *Zone) ResourceRecordSets() (dnsprovider.ResourceRecordSets, bool) { - return &ResourceRecordSets{zone}, true -} - -// Route53HostedZone returns the route53 HostedZone object for the zone. -// This is a "back door" that allows for limited access to the HostedZone, -// without having to requery it, so that we can expose AWS specific functionality. -// Using this method should be avoided where possible; instead prefer to add functionality -// to the cross-provider Zone interface. -func (zone *Zone) Route53HostedZone() *route53.HostedZone { - return zone.impl -} diff --git a/federation/pkg/dnsprovider/providers/aws/route53/zones.go b/federation/pkg/dnsprovider/providers/aws/route53/zones.go deleted file mode 100644 index 4cb9e9c5585..00000000000 --- a/federation/pkg/dnsprovider/providers/aws/route53/zones.go +++ /dev/null @@ -1,73 +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 route53 - -import ( - "github.com/aws/aws-sdk-go/service/route53" - - "k8s.io/apimachinery/pkg/util/uuid" - "k8s.io/kubernetes/federation/pkg/dnsprovider" -) - -// Compile time check for interface adherence -var _ dnsprovider.Zones = Zones{} - -type Zones struct { - interface_ *Interface -} - -func (zones Zones) List() ([]dnsprovider.Zone, error) { - var zoneList []dnsprovider.Zone - - input := route53.ListHostedZonesInput{} - err := zones.interface_.service.ListHostedZonesPages(&input, func(page *route53.ListHostedZonesOutput, lastPage bool) bool { - for _, zone := range page.HostedZones { - zoneList = append(zoneList, &Zone{zone, &zones}) - } - return true - }) - if err != nil { - return []dnsprovider.Zone{}, err - } - return zoneList, nil -} - -func (zones Zones) Add(zone dnsprovider.Zone) (dnsprovider.Zone, error) { - dnsName := zone.Name() - callerReference := string(uuid.NewUUID()) - input := route53.CreateHostedZoneInput{Name: &dnsName, CallerReference: &callerReference} - output, err := zones.interface_.service.CreateHostedZone(&input) - if err != nil { - return nil, err - } - return &Zone{output.HostedZone, &zones}, nil -} - -func (zones Zones) Remove(zone dnsprovider.Zone) error { - zoneId := zone.(*Zone).impl.Id - input := route53.DeleteHostedZoneInput{Id: zoneId} - _, err := zones.interface_.service.DeleteHostedZone(&input) - if err != nil { - return err - } - return nil -} -func (zones Zones) New(name string) (dnsprovider.Zone, error) { - id := string(uuid.NewUUID()) - managedZone := route53.HostedZone{Id: &id, Name: &name} - return &Zone{&managedZone, &zones}, nil -} diff --git a/federation/pkg/dnsprovider/providers/coredns/BUILD b/federation/pkg/dnsprovider/providers/coredns/BUILD deleted file mode 100644 index 856dca37c6b..00000000000 --- a/federation/pkg/dnsprovider/providers/coredns/BUILD +++ /dev/null @@ -1,62 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = [ - "coredns.go", - "interface.go", - "rrchangeset.go", - "rrset.go", - "rrsets.go", - "zone.go", - "zones.go", - ], - importpath = "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/coredns", - deps = [ - "//federation/pkg/dnsprovider:go_default_library", - "//federation/pkg/dnsprovider/providers/coredns/stubs:go_default_library", - "//federation/pkg/dnsprovider/rrstype:go_default_library", - "//vendor/github.com/coreos/etcd/client:go_default_library", - "//vendor/github.com/coreos/etcd/pkg/transport:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/github.com/miekg/coredns/middleware/etcd/msg:go_default_library", - "//vendor/golang.org/x/net/context:go_default_library", - "//vendor/gopkg.in/gcfg.v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["coredns_test.go"], - importpath = "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/coredns", - library = ":go_default_library", - deps = [ - "//federation/pkg/dnsprovider:go_default_library", - "//federation/pkg/dnsprovider/providers/coredns/stubs:go_default_library", - "//federation/pkg/dnsprovider/rrstype:go_default_library", - "//federation/pkg/dnsprovider/tests:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/pkg/dnsprovider/providers/coredns/stubs:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/pkg/dnsprovider/providers/coredns/coredns.go b/federation/pkg/dnsprovider/providers/coredns/coredns.go deleted file mode 100644 index 02f50e33633..00000000000 --- a/federation/pkg/dnsprovider/providers/coredns/coredns.go +++ /dev/null @@ -1,144 +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 coredns is the implementation of pkg/dnsprovider interface for CoreDNS -package coredns - -import ( - "crypto/tls" - "fmt" - "io" - "net" - "net/http" - "strconv" - "strings" - "time" - - etcdc "github.com/coreos/etcd/client" - "github.com/coreos/etcd/pkg/transport" - "github.com/golang/glog" - "gopkg.in/gcfg.v1" - utilnet "k8s.io/apimachinery/pkg/util/net" - "k8s.io/kubernetes/federation/pkg/dnsprovider" -) - -// "coredns" should be used to use this DNS provider -const ( - ProviderName = "coredns" -) - -// Config to override defaults -type Config struct { - Global struct { - EtcdEndpoints string `gcfg:"etcd-endpoints"` - CertFile string `gcfg:"etcd-cert-file"` - KeyFile string `gcfg:"etcd-key-file"` - CAFile string `gcfg:"etcd-ca-file"` - DNSZones string `gcfg:"zones"` - CoreDNSEndpoints string `gcfg:"coredns-endpoints"` - } -} - -func init() { - dnsprovider.RegisterDnsProvider(ProviderName, func(config io.Reader) (dnsprovider.Interface, error) { - return newCoreDNSProviderInterface(config) - }) -} - -func newTransportForETCD2(certFile, keyFile, caFile string) (*http.Transport, error) { - var cfg *tls.Config - if len(certFile) == 0 && len(keyFile) == 0 && len(caFile) == 0 { - cfg = nil - } else { - info := transport.TLSInfo{ - CertFile: certFile, - KeyFile: keyFile, - CAFile: caFile, - } - var err error - cfg, err = info.ClientConfig() - if err != nil { - return nil, fmt.Errorf("error creating tls config: %v", err) - } - } - // Copied from etcd.DefaultTransport declaration. - // TODO: Determine if transport needs optimization - tr := utilnet.SetTransportDefaults(&http.Transport{ - Proxy: http.ProxyFromEnvironment, - Dial: (&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - }).Dial, - TLSHandshakeTimeout: 10 * time.Second, - TLSClientConfig: cfg, - }) - return tr, nil -} - -// newCoreDnsProviderInterface creates a new instance of an CoreDNS DNS Interface. -func newCoreDNSProviderInterface(config io.Reader) (*Interface, error) { - etcdEndpoints := "http://federation-dns-server-etcd:2379" - etcdPathPrefix := "skydns" - dnsZones := "" - var certFile, keyFile, caFile string - - // Possibly override defaults with config below - if config != nil { - var cfg Config - if err := gcfg.ReadInto(&cfg, config); err != nil { - glog.Errorf("Couldn't read config: %v", err) - return nil, err - } - etcdEndpoints = cfg.Global.EtcdEndpoints - dnsZones = cfg.Global.DNSZones - certFile = cfg.Global.CertFile - caFile = cfg.Global.CAFile - keyFile = cfg.Global.KeyFile - } - glog.Infof("Using CoreDNS DNS provider") - - if dnsZones == "" { - return nil, fmt.Errorf("Need to provide at least one DNS Zone") - } - glog.Infof("Creating etcd transport with %s, %s, %s", certFile, keyFile, caFile) - etcdTransport, err := newTransportForETCD2(certFile, keyFile, caFile) - if err != nil { - return nil, fmt.Errorf("error creating transport for etcd: %v", err) - } - - etcdCfg := etcdc.Config{ - Endpoints: strings.Split(etcdEndpoints, ","), - Transport: etcdTransport, - } - - c, err := etcdc.New(etcdCfg) - if err != nil { - return nil, fmt.Errorf("Create etcd client from the config failed") - } - etcdKeysAPI := etcdc.NewKeysAPI(c) - - intf := newInterfaceWithStub(etcdKeysAPI) - intf.etcdPathPrefix = etcdPathPrefix - zoneList := strings.Split(dnsZones, ",") - - intf.zones = Zones{intf: intf} - for index, zoneName := range zoneList { - zone := Zone{domain: zoneName, id: strconv.Itoa(index), zones: &intf.zones} - intf.zones.zoneList = append(intf.zones.zoneList, zone) - } - - return intf, nil -} diff --git a/federation/pkg/dnsprovider/providers/coredns/coredns_test.go b/federation/pkg/dnsprovider/providers/coredns/coredns_test.go deleted file mode 100644 index 39012573fd3..00000000000 --- a/federation/pkg/dnsprovider/providers/coredns/coredns_test.go +++ /dev/null @@ -1,270 +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 coredns - -import ( - "flag" - "fmt" - "os" - "strconv" - "testing" - - "k8s.io/kubernetes/federation/pkg/dnsprovider" - corednstesting "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/coredns/stubs" - "k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype" - - "k8s.io/kubernetes/federation/pkg/dnsprovider/tests" - "strings" -) - -func newTestInterface() (dnsprovider.Interface, error) { - // Use this to test the real cloud service. - // return dnsprovider.GetDnsProvider(ProviderName, strings.NewReader("\n[global]\nproject-id = federation0-cluster00")) - return newFakeInterface() // Use this to stub out the entire cloud service -} - -func newFakeInterface() (dnsprovider.Interface, error) { - var service corednstesting.EtcdKeysAPI - service = corednstesting.NewEtcdKeysAPIStub() - intf := newInterfaceWithStub(service) - intf.etcdPathPrefix = "skydns" - - zoneList := strings.Split("example.com,federation.io", ",") - intf.zones = Zones{intf: intf} - for index, zoneName := range zoneList { - zone := Zone{domain: zoneName, id: strconv.Itoa(index), zones: &intf.zones} - intf.zones.zoneList = append(intf.zones.zoneList, zone) - } - - return intf, nil -} - -var intf dnsprovider.Interface - -func TestMain(m *testing.M) { - fmt.Printf("Parsing flags.\n") - flag.Parse() - var err error - fmt.Printf("Getting new test interface.\n") - intf, err = newTestInterface() - if err != nil { - fmt.Printf("Error creating interface: %v", err) - os.Exit(1) - } - fmt.Printf("Running tests...\n") - os.Exit(m.Run()) -} - -// zones returns the zones interface for the configured dns provider account/project, -// or fails if it can't be found -func zones(t *testing.T) dnsprovider.Zones { - zonesInterface, supported := intf.Zones() - if !supported { - t.Fatalf("Zones interface not supported by interface %v", intf) - } else { - t.Logf("Got zones %v\n", zonesInterface) - } - return zonesInterface -} - -// firstZone returns the first zone for the configured dns provider account/project, -// or fails if it can't be found -func firstZone(t *testing.T) dnsprovider.Zone { - t.Logf("Getting zones") - z := zones(t) - zones, err := z.List() - if err != nil { - t.Fatalf("Failed to list zones: %v", err) - } else { - t.Logf("Got zone list: %v\n", zones) - } - if len(zones) < 1 { - t.Fatalf("Zone listing returned %d, expected >= %d", len(zones), 1) - } else { - t.Logf("Got at least 1 zone in list:%v\n", zones[0]) - } - return zones[0] -} - -/* rrs returns the ResourceRecordSets interface for a given zone */ -func rrs(t *testing.T, zone dnsprovider.Zone) (r dnsprovider.ResourceRecordSets) { - rrsets, supported := zone.ResourceRecordSets() - if !supported { - t.Fatalf("ResourceRecordSets interface not supported by zone %v", zone) - return r - } - return rrsets -} - -func listRrsOrFail(t *testing.T, rrsets dnsprovider.ResourceRecordSets) []dnsprovider.ResourceRecordSet { - rrset, err := rrsets.List() - if err != nil { - t.Fatalf("Failed to list recordsets: %v", err) - } else { - if len(rrset) < 0 { - t.Fatalf("Record set length=%d, expected >=0", len(rrset)) - } else { - t.Logf("Got %d recordsets: %v", len(rrset), rrset) - } - } - return rrset -} - -func getRrOrFail(t *testing.T, rrsets dnsprovider.ResourceRecordSets, name string) []dnsprovider.ResourceRecordSet { - rrsetList, err := rrsets.Get(name) - if err != nil { - t.Fatalf("Failed to get recordset: %v", err) - } else if len(rrsetList) == 0 { - t.Logf("Did not Get recordset: %v", name) - } else { - t.Logf("Got recordsets: %v", rrsetList) - } - return rrsetList -} - -func getExampleRrs(zone dnsprovider.Zone) dnsprovider.ResourceRecordSet { - rrsets, _ := zone.ResourceRecordSets() - return rrsets.New("www11."+zone.Name(), []string{"10.10.10.10", "169.20.20.20"}, 180, rrstype.A) -} - -func addRrsetOrFail(t *testing.T, rrsets dnsprovider.ResourceRecordSets, rrset dnsprovider.ResourceRecordSet) { - err := rrsets.StartChangeset().Add(rrset).Apply() - if err != nil { - t.Fatalf("Failed to add recordsets: %v", err) - } -} - -/* TestZonesList verifies that listing of zones succeeds */ -func TestZonesList(t *testing.T) { - firstZone(t) -} - -/* TestZonesID verifies that the id of the zone is unique */ -func TestZonesID(t *testing.T) { - zone := firstZone(t) - - zoneID := zone.ID() - if zoneID != "0" { - t.Fatalf("Unexpected zone id: %q", zoneID) - } -} - -/* TestResourceRecordSetsGet verifies that getting of RRS succeeds */ -func TestResourceRecordSetsGet(t *testing.T) { - getRrOrFail(t, rrs(t, firstZone(t)), "example.com") -} - -/* TestResourceRecordSetsAddSuccess verifies that addition of a valid RRS succeeds */ -func TestResourceRecordSetsAddSuccess(t *testing.T) { - zone := firstZone(t) - sets := rrs(t, zone) - set := getExampleRrs(zone) - addRrsetOrFail(t, sets, set) - defer sets.StartChangeset().Remove(set).Apply() - t.Logf("Successfully added resource record set: %v", set) - if sets.Zone().ID() != zone.ID() { - t.Errorf("Zone for rrset does not match expected") - } -} - -/* TestResourceRecordSetsAdditionVisible verifies that added RRS is visible after addition */ -func TestResourceRecordSetsAdditionVisible(t *testing.T) { - zone := firstZone(t) - sets := rrs(t, zone) - rrset := getExampleRrs(zone) - addRrsetOrFail(t, sets, rrset) - defer sets.StartChangeset().Remove(rrset).Apply() - t.Logf("Successfully added resource record set: %v", rrset) - - record := getRrOrFail(t, sets, rrset.Name()) - if record == nil { - t.Errorf("Failed to find added resource record set %s", rrset.Name()) - } -} - -/* TestResourceRecordSetsAddDuplicateFail verifies that addition of a duplicate RRS fails */ -func TestResourceRecordSetsAddDuplicateFail(t *testing.T) { - zone := firstZone(t) - sets := rrs(t, zone) - rrset := getExampleRrs(zone) - addRrsetOrFail(t, sets, rrset) - defer sets.StartChangeset().Remove(rrset).Apply() - t.Logf("Successfully added resource record set: %v", rrset) - // Try to add it again, and verify that the call fails. - err := sets.StartChangeset().Add(rrset).Apply() - if err == nil { - defer sets.StartChangeset().Remove(rrset).Apply() - t.Errorf("Should have failed to add duplicate resource record %v, but succeeded instead.", rrset) - } else { - t.Logf("Correctly failed to add duplicate resource record %v: %v", rrset, err) - } -} - -/* TestResourceRecordSetsRemove verifies that the removal of an existing RRS succeeds */ -func TestResourceRecordSetsRemove(t *testing.T) { - zone := firstZone(t) - sets := rrs(t, zone) - rrset := getExampleRrs(zone) - addRrsetOrFail(t, sets, rrset) - err := sets.StartChangeset().Remove(rrset).Apply() - if err != nil { - // Try again to clean up. - defer sets.StartChangeset().Remove(rrset).Apply() - t.Errorf("Failed to remove resource record set %v after adding", rrset) - } else { - t.Logf("Successfully removed resource set %v after adding", rrset) - } -} - -/* TestResourceRecordSetsRemoveGone verifies that a removed RRS no longer exists */ -func TestResourceRecordSetsRemoveGone(t *testing.T) { - zone := firstZone(t) - sets := rrs(t, zone) - rrset := getExampleRrs(zone) - addRrsetOrFail(t, sets, rrset) - err := sets.StartChangeset().Remove(rrset).Apply() - if err != nil { - // Try again to clean up. - defer sets.StartChangeset().Remove(rrset).Apply() - t.Errorf("Failed to remove resource record set %v after adding", rrset) - } else { - t.Logf("Successfully removed resource set %v after adding", rrset) - } - - record := getRrOrFail(t, sets, rrset.Name()) - if record != nil { - t.Errorf("Deleted resource record set %v is still present", rrset) - } -} - -/* TestResourceRecordSetsReplace verifies that replacing an RRS works */ -func TestResourceRecordSetsReplace(t *testing.T) { - zone := firstZone(t) - tests.CommonTestResourceRecordSetsReplace(t, zone) -} - -/* TestResourceRecordSetsReplaceAll verifies that we can remove an RRS and create one with a different name */ -func TestResourceRecordSetsReplaceAll(t *testing.T) { - zone := firstZone(t) - tests.CommonTestResourceRecordSetsReplaceAll(t, zone) -} - -/* TestResourceRecordSetsDifferentTypes verifies that we can add records with same name, but different types */ -func TestResourceRecordSetsDifferentTypes(t *testing.T) { - zone := firstZone(t) - tests.CommonTestResourceRecordSetsDifferentTypes(t, zone) -} diff --git a/federation/pkg/dnsprovider/providers/coredns/interface.go b/federation/pkg/dnsprovider/providers/coredns/interface.go deleted file mode 100644 index d579fd4725c..00000000000 --- a/federation/pkg/dnsprovider/providers/coredns/interface.go +++ /dev/null @@ -1,41 +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 coredns - -import ( - "k8s.io/kubernetes/federation/pkg/dnsprovider" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/coredns/stubs" -) - -// Compile time check for interface adherence -var _ dnsprovider.Interface = Interface{} - -type Interface struct { - etcdKeysAPI stubs.EtcdKeysAPI - etcdPathPrefix string - zones Zones -} - -// newInterfaceWithStub facilitates stubbing out the underlying etcd -// library for testing purposes. It returns an provider-independent interface. -func newInterfaceWithStub(etcdKeysAPI stubs.EtcdKeysAPI) *Interface { - return &Interface{etcdKeysAPI: etcdKeysAPI} -} - -func (i Interface) Zones() (dnsprovider.Zones, bool) { - return i.zones, true -} diff --git a/federation/pkg/dnsprovider/providers/coredns/rrchangeset.go b/federation/pkg/dnsprovider/providers/coredns/rrchangeset.go deleted file mode 100644 index a2f4bab5160..00000000000 --- a/federation/pkg/dnsprovider/providers/coredns/rrchangeset.go +++ /dev/null @@ -1,148 +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 coredns - -import ( - "encoding/json" - "fmt" - "hash/fnv" - - etcdc "github.com/coreos/etcd/client" - dnsmsg "github.com/miekg/coredns/middleware/etcd/msg" - "golang.org/x/net/context" - "k8s.io/kubernetes/federation/pkg/dnsprovider" -) - -// Compile time check for interface adherence -var _ dnsprovider.ResourceRecordChangeset = &ResourceRecordChangeset{} - -type ChangeSetType string - -const ( - ADDITION = ChangeSetType("ADDITION") - DELETION = ChangeSetType("DELETION") - UPSERT = ChangeSetType("UPSERT") -) - -type ChangeSet struct { - cstype ChangeSetType - rrset dnsprovider.ResourceRecordSet -} - -type ResourceRecordChangeset struct { - zone *Zone - rrsets *ResourceRecordSets - - changeset []ChangeSet -} - -func (c *ResourceRecordChangeset) Add(rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordChangeset { - c.changeset = append(c.changeset, ChangeSet{cstype: ADDITION, rrset: rrset}) - return c -} - -func (c *ResourceRecordChangeset) Remove(rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordChangeset { - c.changeset = append(c.changeset, ChangeSet{cstype: DELETION, rrset: rrset}) - return c -} - -func (c *ResourceRecordChangeset) IsEmpty() bool { - return len(c.changeset) == 0 -} - -func (c *ResourceRecordChangeset) Upsert(rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordChangeset { - c.changeset = append(c.changeset, ChangeSet{cstype: UPSERT, rrset: rrset}) - return c -} - -func (c *ResourceRecordChangeset) Apply() error { - ctx := context.Background() - etcdPathPrefix := c.zone.zones.intf.etcdPathPrefix - getOpts := &etcdc.GetOptions{} - setOpts := &etcdc.SetOptions{} - deleteOpts := &etcdc.DeleteOptions{ - Recursive: true, - } - - for _, changeset := range c.changeset { - switch changeset.cstype { - case ADDITION, UPSERT: - checkNotExists := changeset.cstype == ADDITION - - // TODO: I think the semantics of the other providers are different; they operate at the record level, not the individual rrdata level - // In other words: we should insert/replace all the records for the key - for _, rrdata := range changeset.rrset.Rrdatas() { - b, err := json.Marshal(&dnsmsg.Service{Host: rrdata, TTL: uint32(changeset.rrset.Ttl()), Group: changeset.rrset.Name()}) - if err != nil { - return err - } - recordValue := string(b) - recordLabel := getHash(rrdata) - recordKey := buildDNSNameString(changeset.rrset.Name(), recordLabel) - - if checkNotExists { - response, err := c.zone.zones.intf.etcdKeysAPI.Get(ctx, dnsmsg.Path(recordKey, etcdPathPrefix), getOpts) - if err == nil && response != nil { - return fmt.Errorf("Key already exist, key: %v", recordKey) - } - } - - _, err = c.zone.zones.intf.etcdKeysAPI.Set(ctx, dnsmsg.Path(recordKey, etcdPathPrefix), recordValue, setOpts) - if err != nil { - return err - } - } - - case DELETION: - // TODO: I think the semantics of the other providers are different; they operate at the record level, not the individual rrdata level - // In other words: we should delete all the records for the key, only if it matches exactly - for _, rrdata := range changeset.rrset.Rrdatas() { - recordLabel := getHash(rrdata) - recordKey := buildDNSNameString(changeset.rrset.Name(), recordLabel) - _, err := c.zone.zones.intf.etcdKeysAPI.Delete(ctx, dnsmsg.Path(recordKey, etcdPathPrefix), deleteOpts) - if err != nil { - return err - } - } - // TODO: We need to cleanup empty dirs in etcd - } - } - return nil -} - -// ResourceRecordSets returns the parent ResourceRecordSets -func (c *ResourceRecordChangeset) ResourceRecordSets() dnsprovider.ResourceRecordSets { - return c.rrsets -} - -func getHash(text string) string { - h := fnv.New32a() - h.Write([]byte(text)) - return fmt.Sprintf("%x", h.Sum32()) -} - -func buildDNSNameString(labels ...string) string { - var res string - for _, label := range labels { - if res == "" { - res = label - } else { - res = fmt.Sprintf("%s.%s", label, res) - } - } - return res -} diff --git a/federation/pkg/dnsprovider/providers/coredns/rrset.go b/federation/pkg/dnsprovider/providers/coredns/rrset.go deleted file mode 100644 index 394303667ef..00000000000 --- a/federation/pkg/dnsprovider/providers/coredns/rrset.go +++ /dev/null @@ -1,49 +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 coredns - -import ( - "k8s.io/kubernetes/federation/pkg/dnsprovider" - "k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype" -) - -// Compile time check for interface adherence -var _ dnsprovider.ResourceRecordSet = ResourceRecordSet{} - -type ResourceRecordSet struct { - name string - rrdatas []string - ttl int64 - rrsType rrstype.RrsType - rrsets *ResourceRecordSets -} - -func (rrset ResourceRecordSet) Name() string { - return rrset.name -} - -func (rrset ResourceRecordSet) Rrdatas() []string { - return rrset.rrdatas -} - -func (rrset ResourceRecordSet) Ttl() int64 { - return rrset.ttl -} - -func (rrset ResourceRecordSet) Type() rrstype.RrsType { - return rrset.rrsType -} diff --git a/federation/pkg/dnsprovider/providers/coredns/rrsets.go b/federation/pkg/dnsprovider/providers/coredns/rrsets.go deleted file mode 100644 index dbd85cab35c..00000000000 --- a/federation/pkg/dnsprovider/providers/coredns/rrsets.go +++ /dev/null @@ -1,114 +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 coredns - -import ( - "encoding/json" - "fmt" - etcdc "github.com/coreos/etcd/client" - "github.com/golang/glog" - dnsmsg "github.com/miekg/coredns/middleware/etcd/msg" - "golang.org/x/net/context" - "k8s.io/kubernetes/federation/pkg/dnsprovider" - "k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype" - "net" -) - -// Compile time check for interface adherence -var _ dnsprovider.ResourceRecordSets = ResourceRecordSets{} - -type ResourceRecordSets struct { - zone *Zone -} - -func (rrsets ResourceRecordSets) List() ([]dnsprovider.ResourceRecordSet, error) { - var list []dnsprovider.ResourceRecordSet - return list, fmt.Errorf("OperationNotSupported") -} - -func (rrsets ResourceRecordSets) Get(name string) ([]dnsprovider.ResourceRecordSet, error) { - getOpts := &etcdc.GetOptions{ - Recursive: true, - } - etcdPathPrefix := rrsets.zone.zones.intf.etcdPathPrefix - response, err := rrsets.zone.zones.intf.etcdKeysAPI.Get(context.Background(), dnsmsg.Path(name, etcdPathPrefix), getOpts) - if err != nil { - if etcdc.IsKeyNotFound(err) { - glog.V(2).Infof("Subdomain %q does not exist", name) - return nil, nil - } - return nil, fmt.Errorf("Failed to get service from etcd, err: %v", err) - } - if emptyResponse(response) { - glog.V(2).Infof("Subdomain %q does not exist in etcd", name) - return nil, nil - } - - var list []dnsprovider.ResourceRecordSet - - for _, node := range response.Node.Nodes { - service := dnsmsg.Service{} - err = json.Unmarshal([]byte(node.Value), &service) - if err != nil { - return nil, fmt.Errorf("Failed to unmarshall json data, err: %v", err) - } - - rrset := ResourceRecordSet{name: name, rrdatas: []string{}, rrsets: &rrsets} - ip := net.ParseIP(service.Host) - switch { - case ip == nil: - rrset.rrsType = rrstype.CNAME - case ip.To4() != nil: - rrset.rrsType = rrstype.A - case ip.To16() != nil: - rrset.rrsType = rrstype.AAAA - default: - // Cannot occur - } - rrset.rrdatas = append(rrset.rrdatas, service.Host) - rrset.ttl = int64(service.TTL) - list = append(list, rrset) - } - - return list, nil -} - -func (rrsets ResourceRecordSets) StartChangeset() dnsprovider.ResourceRecordChangeset { - return &ResourceRecordChangeset{ - zone: rrsets.zone, - rrsets: &rrsets, - } -} - -func (rrsets ResourceRecordSets) New(name string, rrdatas []string, ttl int64, rrsType rrstype.RrsType) dnsprovider.ResourceRecordSet { - return ResourceRecordSet{ - name: name, - rrdatas: rrdatas, - ttl: ttl, - rrsType: rrsType, - rrsets: &rrsets, - } -} - -// Zone returns the parent zone -func (rrset ResourceRecordSets) Zone() dnsprovider.Zone { - return rrset.zone -} - -func emptyResponse(resp *etcdc.Response) bool { - return resp == nil || resp.Node == nil || (len(resp.Node.Value) == 0 && len(resp.Node.Nodes) == 0) -} diff --git a/federation/pkg/dnsprovider/providers/coredns/stubs/BUILD b/federation/pkg/dnsprovider/providers/coredns/stubs/BUILD deleted file mode 100644 index 775a3844aa1..00000000000 --- a/federation/pkg/dnsprovider/providers/coredns/stubs/BUILD +++ /dev/null @@ -1,29 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["corednsapi.go"], - importpath = "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/coredns/stubs", - deps = [ - "//vendor/github.com/coreos/etcd/client:go_default_library", - "//vendor/golang.org/x/net/context:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/pkg/dnsprovider/providers/coredns/stubs/corednsapi.go b/federation/pkg/dnsprovider/providers/coredns/stubs/corednsapi.go deleted file mode 100644 index 4e392257bb3..00000000000 --- a/federation/pkg/dnsprovider/providers/coredns/stubs/corednsapi.go +++ /dev/null @@ -1,85 +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 stubs implements a stub for the EtcdKeysAPI, used primarily for unit testing purposes -package stubs - -import ( - "strings" - - etcd "github.com/coreos/etcd/client" - "golang.org/x/net/context" -) - -// Compile time check for interface conformance -var _ EtcdKeysAPI = &EtcdKeysAPIStub{} - -type EtcdKeysAPI interface { - Set(context context.Context, key, value string, options *etcd.SetOptions) (*etcd.Response, error) - Get(context context.Context, key string, options *etcd.GetOptions) (*etcd.Response, error) - Delete(context context.Context, key string, options *etcd.DeleteOptions) (*etcd.Response, error) -} - -type EtcdKeysAPIStub struct { - writes map[string]string -} - -// NewEtcdKeysAPIStub returns an initialized EtcdKeysAPIStub -func NewEtcdKeysAPIStub() *EtcdKeysAPIStub { - return &EtcdKeysAPIStub{make(map[string]string)} -} - -func (ec *EtcdKeysAPIStub) Set(context context.Context, key, value string, options *etcd.SetOptions) (*etcd.Response, error) { - ec.writes[key] = value - return nil, nil -} - -func (ec *EtcdKeysAPIStub) Delete(context context.Context, key string, options *etcd.DeleteOptions) (*etcd.Response, error) { - for p := range ec.writes { - if (options.Recursive && strings.HasPrefix(p, key)) || (!options.Recursive && p == key) { - delete(ec.writes, p) - } - } - return nil, nil -} - -func (ec *EtcdKeysAPIStub) Get(context context.Context, key string, options *etcd.GetOptions) (*etcd.Response, error) { - nodes := ec.GetAll(key) - if len(nodes) == 0 { - return nil, nil - } - if len(nodes) == 1 && nodes[key] != "" { - return &etcd.Response{Node: &etcd.Node{Key: key, Value: nodes[key], Dir: false}}, nil - } - - node := &etcd.Node{Key: key, Dir: true, Nodes: etcd.Nodes{}} - for k, v := range nodes { - n := &etcd.Node{Key: k, Value: v} - node.Nodes = append(node.Nodes, n) - } - return &etcd.Response{Node: node}, nil -} - -func (ec *EtcdKeysAPIStub) GetAll(key string) map[string]string { - nodes := make(map[string]string) - key = strings.ToLower(key) - for path := range ec.writes { - if strings.HasPrefix(path, key) { - nodes[path] = ec.writes[path] - } - } - return nodes -} diff --git a/federation/pkg/dnsprovider/providers/coredns/zone.go b/federation/pkg/dnsprovider/providers/coredns/zone.go deleted file mode 100644 index 66cb7bb4879..00000000000 --- a/federation/pkg/dnsprovider/providers/coredns/zone.go +++ /dev/null @@ -1,42 +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 coredns - -import ( - "k8s.io/kubernetes/federation/pkg/dnsprovider" -) - -// Compile time check for interface adherence -var _ dnsprovider.Zone = Zone{} - -type Zone struct { - domain string - id string - zones *Zones -} - -func (zone Zone) Name() string { - return zone.domain -} - -func (zone Zone) ID() string { - return zone.id -} - -func (zone Zone) ResourceRecordSets() (dnsprovider.ResourceRecordSets, bool) { - return &ResourceRecordSets{zone: &zone}, true -} diff --git a/federation/pkg/dnsprovider/providers/coredns/zones.go b/federation/pkg/dnsprovider/providers/coredns/zones.go deleted file mode 100644 index 14e34d96652..00000000000 --- a/federation/pkg/dnsprovider/providers/coredns/zones.go +++ /dev/null @@ -1,49 +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 coredns - -import ( - "fmt" - "k8s.io/kubernetes/federation/pkg/dnsprovider" -) - -// Compile time check for interface adherence -var _ dnsprovider.Zones = Zones{} - -type Zones struct { - intf *Interface - zoneList []Zone -} - -func (zones Zones) List() ([]dnsprovider.Zone, error) { - var zoneList []dnsprovider.Zone - for _, zone := range zones.zoneList { - zoneList = append(zoneList, zone) - } - return zoneList, nil -} - -func (zones Zones) Add(zone dnsprovider.Zone) (dnsprovider.Zone, error) { - return &Zone{}, fmt.Errorf("OperationNotSupported") -} - -func (zones Zones) Remove(zone dnsprovider.Zone) error { - return fmt.Errorf("OperationNotSupported") -} -func (zones Zones) New(name string) (dnsprovider.Zone, error) { - return &Zone{}, fmt.Errorf("OperationNotSupported") -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/BUILD b/federation/pkg/dnsprovider/providers/google/clouddns/BUILD deleted file mode 100644 index 954ee8f416d..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/BUILD +++ /dev/null @@ -1,64 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = [ - "clouddns.go", - "interface.go", - "rrchangeset.go", - "rrset.go", - "rrsets.go", - "zone.go", - "zones.go", - ], - importpath = "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns", - deps = [ - "//federation/pkg/dnsprovider:go_default_library", - "//federation/pkg/dnsprovider/providers/google/clouddns/internal:go_default_library", - "//federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces:go_default_library", - "//federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs:go_default_library", - "//federation/pkg/dnsprovider/rrstype:go_default_library", - "//pkg/cloudprovider/providers/gce:go_default_library", - "//vendor/cloud.google.com/go/compute/metadata:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/golang.org/x/oauth2:go_default_library", - "//vendor/golang.org/x/oauth2/google:go_default_library", - "//vendor/google.golang.org/api/compute/v1:go_default_library", - "//vendor/google.golang.org/api/dns/v1:go_default_library", - "//vendor/gopkg.in/gcfg.v1:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["clouddns_test.go"], - importpath = "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns", - library = ":go_default_library", - deps = [ - "//federation/pkg/dnsprovider:go_default_library", - "//federation/pkg/dnsprovider/rrstype:go_default_library", - "//federation/pkg/dnsprovider/tests:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/pkg/dnsprovider/providers/google/clouddns/internal:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/clouddns.go b/federation/pkg/dnsprovider/providers/google/clouddns/clouddns.go deleted file mode 100644 index 0242087b1fa..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/clouddns.go +++ /dev/null @@ -1,116 +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. -*/ - -// clouddns is the implementation of pkg/dnsprovider interface for Google Cloud DNS -package clouddns - -import ( - "io" - - "cloud.google.com/go/compute/metadata" - "github.com/golang/glog" - "golang.org/x/oauth2" - "golang.org/x/oauth2/google" - compute "google.golang.org/api/compute/v1" - dns "google.golang.org/api/dns/v1" - gcfg "gopkg.in/gcfg.v1" - - "k8s.io/kubernetes/federation/pkg/dnsprovider" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs" - "k8s.io/kubernetes/pkg/cloudprovider/providers/gce" -) - -const ( - ProviderName = "google-clouddns" -) - -func init() { - dnsprovider.RegisterDnsProvider(ProviderName, func(config io.Reader) (dnsprovider.Interface, error) { - return newCloudDns(config) - }) -} - -type Config struct { - Global struct { - TokenURL string `gcfg:"token-url"` - TokenBody string `gcfg:"token-body"` - ProjectID string `gcfg:"project-id"` - } -} - -// newCloudDns creates a new instance of a Google Cloud DNS Interface. -func newCloudDns(config io.Reader) (*Interface, error) { - projectID, _ := metadata.ProjectID() // On error we get an empty string, which is fine for now. - var tokenSource oauth2.TokenSource - // Possibly override defaults with config below - if config != nil { - var cfg Config - if err := gcfg.ReadInto(&cfg, config); err != nil { - glog.Errorf("Couldn't read config: %v", err) - return nil, err - } - glog.Infof("Using Google Cloud DNS provider config %+v", cfg) - if cfg.Global.ProjectID != "" { - projectID = cfg.Global.ProjectID - } - if cfg.Global.TokenURL != "" { - tokenSource = gce.NewAltTokenSource(cfg.Global.TokenURL, cfg.Global.TokenBody) - } - } - return CreateInterface(projectID, tokenSource) -} - -// CreateInterface creates a clouddns.Interface object using the specified parameters. -// If no tokenSource is specified, uses oauth2.DefaultTokenSource. -func CreateInterface(projectID string, tokenSource oauth2.TokenSource) (*Interface, error) { - if tokenSource == nil { - var err error - tokenSource, err = google.DefaultTokenSource( - oauth2.NoContext, - compute.CloudPlatformScope, - compute.ComputeScope) - glog.Infof("Using DefaultTokenSource %#v", tokenSource) - if err != nil { - return nil, err - } - } else { - glog.Infof("Using existing Token Source %#v", tokenSource) - } - - oauthClient := oauth2.NewClient(oauth2.NoContext, tokenSource) - - service, err := dns.New(oauthClient) - if err != nil { - glog.Errorf("Failed to get Cloud DNS client: %v", err) - } - glog.Infof("Successfully got DNS service: %v\n", service) - return newInterfaceWithStub(projectID, internal.NewService(service)), nil -} - -// NewFakeInterface returns a fake clouddns interface, useful for unit testing purposes. -func NewFakeInterface() (dnsprovider.Interface, error) { - service := stubs.NewService() - interface_ := newInterfaceWithStub("", service) - zones := service.ManagedZones_ - // Add a fake zone to test against. - zone := &stubs.ManagedZone{Service: zones, Name_: "example.com", Rrsets: []stubs.ResourceRecordSet{}, Id_: 1} - call := zones.Create(interface_.project(), zone) - if _, err := call.Do(); err != nil { - return nil, err - } - return interface_, nil -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/clouddns_test.go b/federation/pkg/dnsprovider/providers/google/clouddns/clouddns_test.go deleted file mode 100644 index 705bb145d55..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/clouddns_test.go +++ /dev/null @@ -1,273 +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 clouddns - -import ( - "flag" - "fmt" - "os" - "testing" - - "k8s.io/kubernetes/federation/pkg/dnsprovider" - "k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype" - "k8s.io/kubernetes/federation/pkg/dnsprovider/tests" -) - -func newTestInterface() (dnsprovider.Interface, error) { - // Use this to test the real cloud service - insert appropriate project-id. Default token source will be used. See - // https://github.com/golang/oauth2/blob/master/google/default.go for details. - // return dnsprovider.GetDnsProvider(ProviderName, strings.NewReader("\n[global]\nproject-id = federation0-cluster00")) - return NewFakeInterface() // Use this to stub out the entire cloud service -} - -var interface_ dnsprovider.Interface - -func TestMain(m *testing.M) { - flag.Parse() - var err error - interface_, err = newTestInterface() - if err != nil { - fmt.Printf("Error creating interface: %v", err) - os.Exit(1) - } - os.Exit(m.Run()) -} - -// zones returns the zones interface for the configured dns provider account/project, -// or fails if it can't be found -func zones(t *testing.T) dnsprovider.Zones { - zonesInterface, supported := interface_.Zones() - if !supported { - t.Fatalf("Zones interface not supported by interface %v", interface_) - } else { - t.Logf("Got zones %v\n", zonesInterface) - } - return zonesInterface -} - -// firstZone returns the first zone for the configured dns provider account/project, -// or fails if it can't be found -func firstZone(t *testing.T) dnsprovider.Zone { - t.Logf("Getting zones") - zones, err := zones(t).List() - if err != nil { - t.Fatalf("Failed to list zones: %v", err) - } else { - t.Logf("Got zone list: %v\n", zones) - } - if len(zones) < 1 { - t.Fatalf("Zone listing returned %d, expected >= %d", len(zones), 1) - } else { - t.Logf("Got at least 1 zone in list:%v\n", zones[0]) - } - return zones[0] -} - -/* rrs returns the ResourceRecordSets interface for a given zone */ -func rrs(t *testing.T, zone dnsprovider.Zone) (r dnsprovider.ResourceRecordSets) { - rrsets, supported := zone.ResourceRecordSets() - if !supported { - t.Fatalf("ResourceRecordSets interface not supported by zone %v", zone) - return r - } - return rrsets -} - -func listRrsOrFail(t *testing.T, rrsets dnsprovider.ResourceRecordSets) []dnsprovider.ResourceRecordSet { - rrset, err := rrsets.List() - if err != nil { - t.Fatalf("Failed to list recordsets: %v", err) - } else { - if len(rrset) < 0 { - t.Fatalf("Record set length=%d, expected >=0", len(rrset)) - } else { - t.Logf("Got %d recordsets: %v", len(rrset), rrset) - } - } - return rrset -} - -func getExampleRrs(zone dnsprovider.Zone) dnsprovider.ResourceRecordSet { - rrsets, _ := zone.ResourceRecordSets() - return rrsets.New("www11."+zone.Name(), []string{"10.10.10.10", "169.20.20.20"}, 180, rrstype.A) -} - -func getInvalidRrs(zone dnsprovider.Zone) dnsprovider.ResourceRecordSet { - rrsets, _ := zone.ResourceRecordSets() - return rrsets.New("www12."+zone.Name(), []string{"rubbish", "rubbish"}, 180, rrstype.A) -} - -func addRrsetOrFail(t *testing.T, rrsets dnsprovider.ResourceRecordSets, rrset dnsprovider.ResourceRecordSet) { - err := rrsets.StartChangeset().Add(rrset).Apply() - if err != nil { - t.Fatalf("Failed to add recordsets: %v", err) - } -} - -/* TestZonesList verifies that listing of zones succeeds */ -func TestZonesList(t *testing.T) { - firstZone(t) -} - -/* TestZonesID verifies that the id of the zone is returned with the prefix removed */ -func TestZonesID(t *testing.T) { - zone := firstZone(t) - - zoneID := zone.ID() - if zoneID != "1" { - t.Fatalf("Unexpected zone id: %q", zoneID) - } -} - -/* TestZoneAddSuccess verifies that addition of a valid managed DNS zone succeeds */ -func TestZoneAddSuccess(t *testing.T) { - testZoneName := "ubernetesv2.test." - t.Logf("Getting zones") - z := zones(t) - t.Logf("Got zones, making new Zone") - input, err := z.New(testZoneName) - if err != nil { - t.Errorf("Failed to allocate new zone object %s: %v", testZoneName, err) - } - zone, err := z.Add(input) - if err != nil { - t.Errorf("Failed to create new managed DNS zone %s: %v", testZoneName, err) - } - defer func(zone dnsprovider.Zone) { - if zone != nil { - if err := z.Remove(zone); err != nil { - t.Errorf("Failed to delete zone %v: %v", zone, err) - } - } - }(zone) - t.Logf("Successfully added managed DNS zone: %v", zone) -} - -/* TestResourceRecordSetsList verifies that listing of RRS's succeeds */ -func TestResourceRecordSetsList(t *testing.T) { - listRrsOrFail(t, rrs(t, firstZone(t))) -} - -/* TestResourceRecordSetsAddSuccess verifies that addition of a valid RRS succeeds */ -func TestResourceRecordSetsAddSuccess(t *testing.T) { - zone := firstZone(t) - sets := rrs(t, zone) - set := getExampleRrs(zone) - addRrsetOrFail(t, sets, set) - defer sets.StartChangeset().Remove(set).Apply() - t.Logf("Successfully added resource record set: %v", set) -} - -/* TestResourceRecordSetsAdditionVisible verifies that added RRS is visible after addition */ -func TestResourceRecordSetsAdditionVisible(t *testing.T) { - zone := firstZone(t) - sets := rrs(t, zone) - rrset := getExampleRrs(zone) - addRrsetOrFail(t, sets, rrset) - defer sets.StartChangeset().Remove(rrset).Apply() - t.Logf("Successfully added resource record set: %v", rrset) - found := false - for _, record := range listRrsOrFail(t, sets) { - if record.Name() == rrset.Name() { - found = true - break - } - } - if !found { - t.Errorf("Failed to find added resource record set %s", rrset.Name()) - } -} - -/* TestResourceRecordSetsAddDuplicateFail verifies that addition of a duplicate RRS fails */ -func TestResourceRecordSetsAddDuplicateFail(t *testing.T) { - zone := firstZone(t) - sets := rrs(t, zone) - rrset := getExampleRrs(zone) - addRrsetOrFail(t, sets, rrset) - defer sets.StartChangeset().Remove(rrset).Apply() - t.Logf("Successfully added resource record set: %v", rrset) - // Try to add it again, and verify that the call fails. - err := sets.StartChangeset().Add(rrset).Apply() - if err == nil { - defer sets.StartChangeset().Remove(rrset).Apply() - t.Errorf("Should have failed to add duplicate resource record %v, but succeeded instead.", rrset) - } else { - t.Logf("Correctly failed to add duplicate resource record %v: %v", rrset, err) - } -} - -/* TestResourceRecordSetsRemove verifies that the removal of an existing RRS succeeds */ -func TestResourceRecordSetsRemove(t *testing.T) { - zone := firstZone(t) - sets := rrs(t, zone) - rrset := getExampleRrs(zone) - addRrsetOrFail(t, sets, rrset) - err := sets.StartChangeset().Remove(rrset).Apply() - if err != nil { - // Try again to clean up. - defer sets.StartChangeset().Remove(rrset).Apply() - t.Errorf("Failed to remove resource record set %v after adding: %v", rrset, err) - } else { - t.Logf("Successfully removed resource set %v after adding", rrset) - } -} - -/* TestResourceRecordSetsRemoveGone verifies that a removed RRS no longer exists */ -func TestResourceRecordSetsRemoveGone(t *testing.T) { - zone := firstZone(t) - sets := rrs(t, zone) - rrset := getExampleRrs(zone) - addRrsetOrFail(t, sets, rrset) - err := sets.StartChangeset().Remove(rrset).Apply() - if err != nil { - // Try again to clean up. - defer sets.StartChangeset().Remove(rrset).Apply() - t.Errorf("Failed to remove resource record set %v after adding: %v", rrset, err) - } else { - t.Logf("Successfully removed resource set %v after adding", rrset) - } - // Check that it's gone - list := listRrsOrFail(t, sets) - found := false - for _, set := range list { - if set.Name() == rrset.Name() { - found = true - break - } - } - if found { - t.Errorf("Deleted resource record set %v is still present", rrset) - } -} - -/* TestResourceRecordSetsReplace verifies that replacing an RRS works */ -func TestResourceRecordSetsReplace(t *testing.T) { - zone := firstZone(t) - tests.CommonTestResourceRecordSetsReplace(t, zone) -} - -/* TestResourceRecordSetsReplaceAll verifies that we can remove an RRS and create one with a different name*/ -func TestResourceRecordSetsReplaceAll(t *testing.T) { - zone := firstZone(t) - tests.CommonTestResourceRecordSetsReplaceAll(t, zone) -} - -/* TestResourceRecordSetsDifferentType verifies that we can add records of the same name but different types */ -func TestResourceRecordSetsDifferentTypes(t *testing.T) { - zone := firstZone(t) - tests.CommonTestResourceRecordSetsDifferentTypes(t, zone) -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/interface.go b/federation/pkg/dnsprovider/providers/google/clouddns/interface.go deleted file mode 100644 index 6fca4eb84c1..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/interface.go +++ /dev/null @@ -1,43 +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 clouddns - -import ( - "k8s.io/kubernetes/federation/pkg/dnsprovider" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" -) - -var _ dnsprovider.Interface = Interface{} - -type Interface struct { - project_ string - service interfaces.Service -} - -// newInterfaceWithStub facilitates stubbing out the underlying Google Cloud DNS -// library for testing purposes. It returns an provider-independent interface. -func newInterfaceWithStub(project string, service interfaces.Service) *Interface { - return &Interface{project, service} -} - -func (i Interface) Zones() (zones dnsprovider.Zones, supported bool) { - return Zones{i.service.ManagedZones(), &i}, true -} - -func (i Interface) project() string { - return i.project_ -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/BUILD b/federation/pkg/dnsprovider/providers/google/clouddns/internal/BUILD deleted file mode 100644 index 570e4e243cd..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/BUILD +++ /dev/null @@ -1,53 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "change.go", - "changes_create_call.go", - "changes_service.go", - "clouddns.go", - "managed_zone.go", - "managed_zone_create_call.go", - "managed_zones_delete_call.go", - "managed_zones_get_call.go", - "managed_zones_list_call.go", - "managed_zones_list_response.go", - "managed_zones_service.go", - "rrset.go", - "rrsets_list_call.go", - "rrsets_list_response.go", - "rrsets_service.go", - "service.go", - ], - importpath = "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal", - deps = [ - "//federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces:go_default_library", - "//federation/pkg/dnsprovider/rrstype:go_default_library", - "//vendor/google.golang.org/api/dns/v1:go_default_library", - "//vendor/google.golang.org/api/googleapi:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces:all-srcs", - "//federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/change.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/change.go deleted file mode 100644 index e229a7954d7..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/change.go +++ /dev/null @@ -1,43 +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 internal - -import ( - dns "google.golang.org/api/dns/v1" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" -) - -// Compile time check for interface adherence -var _ interfaces.Change = Change{} - -type Change struct{ impl *dns.Change } - -func (c Change) Additions() (rrsets []interfaces.ResourceRecordSet) { - rrsets = make([]interfaces.ResourceRecordSet, len(c.impl.Additions)) - for index, addition := range c.impl.Additions { - rrsets[index] = interfaces.ResourceRecordSet(&ResourceRecordSet{addition}) - } - return rrsets -} - -func (c Change) Deletions() (rrsets []interfaces.ResourceRecordSet) { - rrsets = make([]interfaces.ResourceRecordSet, len(c.impl.Deletions)) - for index, deletion := range c.impl.Deletions { - rrsets[index] = interfaces.ResourceRecordSet(&ResourceRecordSet{deletion}) - } - return rrsets -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/changes_create_call.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/changes_create_call.go deleted file mode 100644 index b1a8310bcd4..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/changes_create_call.go +++ /dev/null @@ -1,34 +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 internal - -import ( - dns "google.golang.org/api/dns/v1" - "google.golang.org/api/googleapi" - - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" -) - -// Compile time check for interface adherence -var _ interfaces.ChangesCreateCall = ChangesCreateCall{} - -type ChangesCreateCall struct{ impl *dns.ChangesCreateCall } - -func (c ChangesCreateCall) Do(opts ...googleapi.CallOption) (interfaces.Change, error) { - ch, err := c.impl.Do(opts...) - return &Change{ch}, err -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/changes_service.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/changes_service.go deleted file mode 100644 index 887ed9df272..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/changes_service.go +++ /dev/null @@ -1,43 +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 internal - -import ( - dns "google.golang.org/api/dns/v1" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" -) - -// Compile time check for interface adherence -var _ interfaces.ChangesService = ChangesService{} - -type ChangesService struct{ impl *dns.ChangesService } - -func (c ChangesService) Create(project string, managedZone string, change interfaces.Change) interfaces.ChangesCreateCall { - return &ChangesCreateCall{c.impl.Create(project, managedZone, change.(*Change).impl)} -} - -func (c ChangesService) NewChange(additions, deletions []interfaces.ResourceRecordSet) interfaces.Change { - adds := make([]*dns.ResourceRecordSet, len(additions)) - deletes := make([]*dns.ResourceRecordSet, len(deletions)) - for i, a := range additions { - adds[i] = a.(*ResourceRecordSet).impl - } - for i, d := range deletions { - deletes[i] = d.(*ResourceRecordSet).impl - } - return &Change{&dns.Change{Additions: adds, Deletions: deletes}} -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/clouddns.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/clouddns.go deleted file mode 100644 index 3c8f8c53395..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/clouddns.go +++ /dev/null @@ -1,35 +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 internal - -// Implementation of internal/interfaces/* on top of Google Cloud DNS API. -// See https://godoc.org/google.golang.org/api/dns/v1 for details -// This facilitates stubbing out Google Cloud DNS for unit testing. -// Only the parts of the API that we use are included. -// Others can be added as needed. - -import dns "google.golang.org/api/dns/v1" - -type ( - Project struct{ impl *dns.Project } - - ProjectsGetCall struct{ impl *dns.ProjectsGetCall } - - ProjectsService struct{ impl *dns.ProjectsService } - - Quota struct{ impl *dns.Quota } -) diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces/BUILD b/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces/BUILD deleted file mode 100644 index 7304f953750..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces/BUILD +++ /dev/null @@ -1,29 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["interfaces.go"], - importpath = "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces", - deps = [ - "//federation/pkg/dnsprovider/rrstype:go_default_library", - "//vendor/google.golang.org/api/googleapi:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces/interfaces.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces/interfaces.go deleted file mode 100644 index 7d9f1663984..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces/interfaces.go +++ /dev/null @@ -1,212 +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 interfaces - -import ( - "context" - - "google.golang.org/api/googleapi" - "k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype" -) - -// Interfaces to directly mirror the Google Cloud DNS API structures. -// See https://godoc.org/google.golang.org/api/dns/v1 for details -// This facilitates stubbing out Google Cloud DNS for unit testing. -// Only the parts of the API that we use are included. -// Others can be added as needed. - -type ( - Change interface { - Additions() []ResourceRecordSet - Deletions() []ResourceRecordSet - // Id() string // TODO: Add as needed - // Kind() string // TODO: Add as needed - // StartTime() string // TODO: Add as needed - // Status() string // TODO: Add as needed - } - - ChangesCreateCall interface { - // Context(ctx context.Context) *ChangesCreateCall // TODO: Add as needed - Do(opts ...googleapi.CallOption) (Change, error) - // Fields(s ...googleapi.Field) *ChangesCreateCall // TODO: Add as needed - } - - ChangesGetCall interface { - // Context(ctx context.Context) *ChangesGetCall // TODO: Add as needed - Do(opts ...googleapi.CallOption) (*Change, error) - // Fields(s ...googleapi.Field) *ChangesGetCall // TODO: Add as needed - // IfNoneMatch(entityTag string) *ChangesGetCall // TODO: Add as needed - } - - ChangesListCall interface { - // Context(ctx context.Context) *ChangesListCall // TODO: Add as needed - Do(opts ...googleapi.CallOption) (*ChangesListResponse, error) - // Fields(s ...googleapi.Field) *ChangesListCall // TODO: Add as needed - // IfNoneMatch(entityTag string) *ChangesListCall // TODO: Add as needed - // MaxResults(maxResults int64) *ChangesListCall // TODO: Add as needed - // PageToken(pageToken string) *ChangesListCall // TODO: Add as needed - // Pages(ctx context.Context, f func(*ChangesListResponse) error) error // TODO: Add as needed - // SortBy(sortBy string) *ChangesListCall // TODO: Add as needed - // SortOrder(sortOrder string) *ChangesListCall // TODO: Add as needed - } - - ChangesListResponse interface { - // Changes() []*Change // TODO: Add as needed - // Kind() string // TODO: Add as needed - // NextPageToken() string // TODO: Add as needed - // ServerResponse() googleapi.ServerResponse // TODO: Add as needed - // ForceSendFields() []string // TODO: Add as needed - } - - ChangesService interface { - // Create(project string, managedZone string, change *Change) *ChangesCreateCall // TODO: Add as needed - Create(project string, managedZone string, change Change) ChangesCreateCall - NewChange(additions, deletions []ResourceRecordSet) Change - - // Get(project string, managedZone string, changeId string) *ChangesGetCall // TODO: Add as needed - // List(project string, managedZone string) *ChangesListCall // TODO: Add as needed - } - - ManagedZone interface { - // CreationTime() string // TODO: Add as needed - // Description() string // TODO: Add as needed - DnsName() string - Id() uint64 - // Kind() string // TODO: Add as needed - Name() string - // NameServerSet() string // TODO: Add as needed - // NameServers() []string // TODO: Add as needed - // ServerResponse() googleapi.ServerResponse // TODO: Add as needed - // ForceSendFields() []string // TODO: Add as needed - } - - ManagedZonesCreateCall interface { - // Context(ctx context.Context) *ManagedZonesCreateCall // TODO: Add as needed - Do(opts ...googleapi.CallOption) (ManagedZone, error) - // Fields(s ...googleapi.Field) *ManagedZonesCreateCall // TODO: Add as needed - } - - ManagedZonesDeleteCall interface { - // Context(ctx context.Context) *ManagedZonesDeleteCall // TODO: Add as needed - Do(opts ...googleapi.CallOption) error - // Fields(s ...googleapi.Field) *ManagedZonesDeleteCall // TODO: Add as needed - } - - ManagedZonesGetCall interface { - // Context(ctx context.Context) *ManagedZonesGetCall // TODO: Add as needed - Do(opts ...googleapi.CallOption) (ManagedZone, error) - // Fields(s ...googleapi.Field) *ManagedZonesGetCall // TODO: Add as needed - // IfNoneMatch(entityTag string) *ManagedZonesGetCall // TODO: Add as needed - } - - ManagedZonesListCall interface { - // Context(ctx context.Context) *ManagedZonesListCall // TODO: Add as needed - DnsName(dnsName string) ManagedZonesListCall - Do(opts ...googleapi.CallOption) (ManagedZonesListResponse, error) - // Fields(s ...googleapi.Field) *ManagedZonesListCall // TODO: Add as needed - // IfNoneMatch(entityTag string) *ManagedZonesListCall // TODO: Add as needed - // MaxResults(maxResults int64) *ManagedZonesListCall // TODO: Add as needed - // PageToken(pageToken string) *ManagedZonesListCall // TODO: Add as needed - // Pages(ctx context.Context, f func(*ManagedZonesListResponse) error) error // TODO: Add as needed - } - - ManagedZonesListResponse interface { - // Kind() string // TODO: Add as needed - // ManagedZones() []*ManagedZone // TODO: Add as needed - ManagedZones() []ManagedZone - // NextPageToken string // TODO: Add as needed - // ServerResponse() googleapi.ServerResponse // TODO: Add as needed - // ForceSendFields() []string // TODO: Add as needed - } - - ManagedZonesService interface { - // NewManagedZonesService(s *Service) *ManagedZonesService // TODO: Add to service if needed - Create(project string, managedZone ManagedZone) ManagedZonesCreateCall - Delete(project string, managedZone string) ManagedZonesDeleteCall - Get(project string, managedZone string) ManagedZonesGetCall - List(project string) ManagedZonesListCall - NewManagedZone(dnsName string) ManagedZone - } - - Project interface { - // Id() string // TODO: Add as needed - // Kind() string // TODO: Add as needed - // Number() uint64 // TODO: Add as needed - // Quota() *Quota // TODO: Add as needed - // ServerResponse() googleapi.ServerResponse // TODO: Add as needed - // ForceSendFields() []string // TODO: Add as needed - } - - ProjectsGetCall interface { - // TODO: Add as needed - } - - ProjectsService interface { - // TODO: Add as needed - } - - Quota interface { - // TODO: Add as needed - } - - ResourceRecordSet interface { - // Kind() string // TODO: Add as needed - Name() string - Rrdatas() []string - Ttl() int64 - Type() string - // ForceSendFields []string // TODO: Add as needed - } - - ResourceRecordSetsListCall interface { - // Context(ctx context.Context) *ResourceRecordSetsListCall // TODO: Add as needed - Do(opts ...googleapi.CallOption) (ResourceRecordSetsListResponse, error) - Pages(ctx context.Context, f func(ResourceRecordSetsListResponse) error) error - // Fields(s ...googleapi.Field) *ResourceRecordSetsListCall // TODO: Add as needed - // IfNoneMatch(entityTag string) *ResourceRecordSetsListCall // TODO: Add as needed - // MaxResults(maxResults int64) *ResourceRecordSetsListCall // TODO: Add as needed - Name(name string) ResourceRecordSetsListCall - // PageToken(pageToken string) *ResourceRecordSetsListCall // TODO: Add as needed - Type(type_ string) ResourceRecordSetsListCall - } - - ResourceRecordSetsListResponse interface { - // Kind() string // TODO: Add as needed - // NextPageToken() string // TODO: Add as needed - Rrsets() []ResourceRecordSet - // ServerResponse() googleapi.ServerResponse // TODO: Add as needed - // ForceSendFields() []string // TODO: Add as needed - } - - ResourceRecordSetsService interface { - List(project string, managedZone string) ResourceRecordSetsListCall - // Get returns a list of resources records with the matching name - Get(project, managedZone, name string) ResourceRecordSetsListCall - // NewResourceRecordSetsService(s *Service) *ResourceRecordSetsService // TODO: add to service as needed - NewResourceRecordSet(name string, rrdatas []string, ttl int64, type_ rrstype.RrsType) ResourceRecordSet - } - - Service interface { - // BasePath() string // TODO: Add as needed - // UserAgent() string // TODO: Add as needed - Changes() ChangesService - ManagedZones() ManagedZonesService - Projects() ProjectsService - ResourceRecordSets() ResourceRecordSetsService - } - // New(client *http.Client) (*Service, error) // TODO: Add as needed -) diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/managed_zone.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/managed_zone.go deleted file mode 100644 index 862897743f9..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/managed_zone.go +++ /dev/null @@ -1,39 +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 internal - -import ( - dns "google.golang.org/api/dns/v1" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" -) - -// Compile time check for interface adherence -var _ interfaces.ManagedZone = ManagedZone{} - -type ManagedZone struct{ impl *dns.ManagedZone } - -func (m ManagedZone) Name() string { - return m.impl.Name -} - -func (m ManagedZone) Id() uint64 { - return m.impl.Id -} - -func (m ManagedZone) DnsName() string { - return m.impl.DnsName -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/managed_zone_create_call.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/managed_zone_create_call.go deleted file mode 100644 index 133cd145b48..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/managed_zone_create_call.go +++ /dev/null @@ -1,33 +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 internal - -import ( - dns "google.golang.org/api/dns/v1" - "google.golang.org/api/googleapi" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" -) - -// Compile time check for interface adherence -var _ interfaces.ManagedZonesCreateCall = ManagedZonesCreateCall{} - -type ManagedZonesCreateCall struct{ impl *dns.ManagedZonesCreateCall } - -func (call ManagedZonesCreateCall) Do(opts ...googleapi.CallOption) (interfaces.ManagedZone, error) { - m, err := call.impl.Do(opts...) - return &ManagedZone{m}, err -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/managed_zones_delete_call.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/managed_zones_delete_call.go deleted file mode 100644 index 2e96763e010..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/managed_zones_delete_call.go +++ /dev/null @@ -1,32 +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 internal - -import ( - dns "google.golang.org/api/dns/v1" - "google.golang.org/api/googleapi" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" -) - -// Compile time check for interface adherence -var _ interfaces.ManagedZonesDeleteCall = ManagedZonesDeleteCall{} - -type ManagedZonesDeleteCall struct{ impl *dns.ManagedZonesDeleteCall } - -func (call ManagedZonesDeleteCall) Do(opts ...googleapi.CallOption) error { - return call.impl.Do(opts...) -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/managed_zones_get_call.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/managed_zones_get_call.go deleted file mode 100644 index 2a6cadfed99..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/managed_zones_get_call.go +++ /dev/null @@ -1,33 +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 internal - -import ( - dns "google.golang.org/api/dns/v1" - "google.golang.org/api/googleapi" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" -) - -// Compile time check for interface adherence -var _ interfaces.ManagedZonesGetCall = ManagedZonesGetCall{} - -type ManagedZonesGetCall struct{ impl *dns.ManagedZonesGetCall } - -func (call ManagedZonesGetCall) Do(opts ...googleapi.CallOption) (interfaces.ManagedZone, error) { - m, err := call.impl.Do(opts...) - return &ManagedZone{m}, err -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/managed_zones_list_call.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/managed_zones_list_call.go deleted file mode 100644 index b7041046477..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/managed_zones_list_call.go +++ /dev/null @@ -1,38 +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 internal - -import ( - dns "google.golang.org/api/dns/v1" - "google.golang.org/api/googleapi" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" -) - -// Compile time check for interface adherence -var _ interfaces.ManagedZonesListCall = &ManagedZonesListCall{} - -type ManagedZonesListCall struct{ impl *dns.ManagedZonesListCall } - -func (call *ManagedZonesListCall) Do(opts ...googleapi.CallOption) (interfaces.ManagedZonesListResponse, error) { - response, err := call.impl.Do(opts...) - return &ManagedZonesListResponse{response}, err -} - -func (call *ManagedZonesListCall) DnsName(dnsName string) interfaces.ManagedZonesListCall { - call.impl.DnsName(dnsName) - return call -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/managed_zones_list_response.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/managed_zones_list_response.go deleted file mode 100644 index b254f5eb56e..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/managed_zones_list_response.go +++ /dev/null @@ -1,35 +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 internal - -import ( - dns "google.golang.org/api/dns/v1" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" -) - -// Compile time check for interface adherence -var _ interfaces.ManagedZonesListResponse = &ManagedZonesListResponse{} - -type ManagedZonesListResponse struct{ impl *dns.ManagedZonesListResponse } - -func (response *ManagedZonesListResponse) ManagedZones() []interfaces.ManagedZone { - zones := make([]interfaces.ManagedZone, len(response.impl.ManagedZones)) - for i, z := range response.impl.ManagedZones { - zones[i] = &ManagedZone{z} - } - return zones -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/managed_zones_service.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/managed_zones_service.go deleted file mode 100644 index 2ea9e2484f9..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/managed_zones_service.go +++ /dev/null @@ -1,51 +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 internal - -import ( - "strings" - - dns "google.golang.org/api/dns/v1" - "k8s.io/apimachinery/pkg/util/uuid" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" -) - -// Compile time check for interface adherence -var _ interfaces.ManagedZonesService = &ManagedZonesService{} - -type ManagedZonesService struct{ impl *dns.ManagedZonesService } - -func (m *ManagedZonesService) Create(project string, managedzone interfaces.ManagedZone) interfaces.ManagedZonesCreateCall { - return &ManagedZonesCreateCall{m.impl.Create(project, managedzone.(*ManagedZone).impl)} -} - -func (m *ManagedZonesService) Delete(project, managedZone string) interfaces.ManagedZonesDeleteCall { - return &ManagedZonesDeleteCall{m.impl.Delete(project, managedZone)} -} - -func (m *ManagedZonesService) Get(project, managedZone string) interfaces.ManagedZonesGetCall { - return &ManagedZonesGetCall{m.impl.Get(project, managedZone)} -} - -func (m *ManagedZonesService) List(project string) interfaces.ManagedZonesListCall { - return &ManagedZonesListCall{m.impl.List(project)} -} - -func (m *ManagedZonesService) NewManagedZone(dnsName string) interfaces.ManagedZone { - name := "x" + strings.Replace(string(uuid.NewUUID()), "-", "", -1)[0:30] // Unique name, strip out the "-" chars to shorten it, start with a lower case alpha, and truncate to Cloud DNS 32 character limit - return &ManagedZone{impl: &dns.ManagedZone{Name: name, Description: "Kubernetes Federated Service", DnsName: dnsName}} -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/rrset.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/rrset.go deleted file mode 100644 index 2b7c0ce24e5..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/rrset.go +++ /dev/null @@ -1,32 +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 internal - -import ( - dns "google.golang.org/api/dns/v1" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" -) - -// Compile time check for interface adherence -var _ interfaces.ResourceRecordSet = ResourceRecordSet{} - -type ResourceRecordSet struct{ impl *dns.ResourceRecordSet } - -func (r ResourceRecordSet) Name() string { return r.impl.Name } -func (r ResourceRecordSet) Rrdatas() []string { return r.impl.Rrdatas } -func (r ResourceRecordSet) Ttl() int64 { return r.impl.Ttl } -func (r ResourceRecordSet) Type() string { return r.impl.Type } diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/rrsets_list_call.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/rrsets_list_call.go deleted file mode 100644 index f497ebcfbce..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/rrsets_list_call.go +++ /dev/null @@ -1,53 +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 internal - -import ( - "context" - - dns "google.golang.org/api/dns/v1" - "google.golang.org/api/googleapi" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" -) - -// Compile time check for interface adherence -var _ interfaces.ResourceRecordSetsListCall = &ResourceRecordSetsListCall{} - -type ResourceRecordSetsListCall struct { - impl *dns.ResourceRecordSetsListCall -} - -func (call *ResourceRecordSetsListCall) Do(opts ...googleapi.CallOption) (interfaces.ResourceRecordSetsListResponse, error) { - response, err := call.impl.Do(opts...) - return &ResourceRecordSetsListResponse{response}, err -} - -func (call *ResourceRecordSetsListCall) Pages(ctx context.Context, f func(interfaces.ResourceRecordSetsListResponse) error) error { - return call.impl.Pages(ctx, func(page *dns.ResourceRecordSetsListResponse) error { - return f(&ResourceRecordSetsListResponse{page}) - }) -} - -func (call *ResourceRecordSetsListCall) Name(name string) interfaces.ResourceRecordSetsListCall { - call.impl.Name(name) - return call -} - -func (call *ResourceRecordSetsListCall) Type(type_ string) interfaces.ResourceRecordSetsListCall { - call.impl.Type(type_) - return call -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/rrsets_list_response.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/rrsets_list_response.go deleted file mode 100644 index a6ff28df8f9..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/rrsets_list_response.go +++ /dev/null @@ -1,38 +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 internal - -import ( - dns "google.golang.org/api/dns/v1" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" -) - -// Compile time check for interface adherence -var _ interfaces.ResourceRecordSetsListResponse = &ResourceRecordSetsListResponse{} - -type ResourceRecordSetsListResponse struct { - impl *dns.ResourceRecordSetsListResponse -} - -func (response *ResourceRecordSetsListResponse) Rrsets() []interfaces.ResourceRecordSet { - rrsets := make([]interfaces.ResourceRecordSet, len(response.impl.Rrsets)) - for i, rrset := range response.impl.Rrsets { - rrsets[i] = &ResourceRecordSet{rrset} - } - return rrsets - -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/rrsets_service.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/rrsets_service.go deleted file mode 100644 index a971fd7513c..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/rrsets_service.go +++ /dev/null @@ -1,43 +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 internal - -import ( - dns "google.golang.org/api/dns/v1" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" - "k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype" -) - -// Compile time check for interface adherence -var _ interfaces.ResourceRecordSetsService = &ResourceRecordSetsService{} - -type ResourceRecordSetsService struct { - impl *dns.ResourceRecordSetsService -} - -func (service ResourceRecordSetsService) List(project string, managedZone string) interfaces.ResourceRecordSetsListCall { - return &ResourceRecordSetsListCall{service.impl.List(project, managedZone)} -} - -func (service ResourceRecordSetsService) Get(project, managedZone, name string) interfaces.ResourceRecordSetsListCall { - return &ResourceRecordSetsListCall{service.impl.List(project, managedZone).Name(name)} -} - -func (service ResourceRecordSetsService) NewResourceRecordSet(name string, rrdatas []string, ttl int64, type_ rrstype.RrsType) interfaces.ResourceRecordSet { - rrset := dns.ResourceRecordSet{Name: name, Rrdatas: rrdatas, Ttl: ttl, Type: string(type_)} - return &ResourceRecordSet{&rrset} -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/service.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/service.go deleted file mode 100644 index 8ac5e63b74e..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/service.go +++ /dev/null @@ -1,49 +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 internal - -import ( - dns "google.golang.org/api/dns/v1" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" -) - -// Compile time check for interface adherence -var _ interfaces.Service = &Service{} - -type Service struct { - impl *dns.Service -} - -func NewService(service *dns.Service) *Service { - return &Service{service} -} - -func (s *Service) Changes() interfaces.ChangesService { - return &ChangesService{s.impl.Changes} -} - -func (s *Service) ManagedZones() interfaces.ManagedZonesService { - return &ManagedZonesService{s.impl.ManagedZones} -} - -func (s *Service) Projects() interfaces.ProjectsService { - return &ProjectsService{s.impl.Projects} -} - -func (s *Service) ResourceRecordSets() interfaces.ResourceRecordSetsService { - return &ResourceRecordSetsService{s.impl.ResourceRecordSets} -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/BUILD b/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/BUILD deleted file mode 100644 index 5013c4117ef..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/BUILD +++ /dev/null @@ -1,48 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "change.go", - "changes_create_call.go", - "changes_service.go", - "clouddns.go", - "managed_zone.go", - "managed_zone_create_call.go", - "managed_zones_delete_call.go", - "managed_zones_get_call.go", - "managed_zones_list_call.go", - "managed_zones_list_response.go", - "managed_zones_service.go", - "rrset.go", - "rrsets_list_call.go", - "rrsets_list_response.go", - "rrsets_service.go", - "service.go", - ], - importpath = "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs", - deps = [ - "//federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces:go_default_library", - "//federation/pkg/dnsprovider/rrstype:go_default_library", - "//vendor/google.golang.org/api/dns/v1:go_default_library", - "//vendor/google.golang.org/api/googleapi:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/change.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/change.go deleted file mode 100644 index 83e5d947967..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/change.go +++ /dev/null @@ -1,36 +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 stubs - -import "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" - -// Compile time check for interface adherence -var _ interfaces.Change = &Change{} - -type Change struct { - Service *ChangesService - Additions_ []interfaces.ResourceRecordSet - Deletions_ []interfaces.ResourceRecordSet -} - -func (c *Change) Additions() (rrsets []interfaces.ResourceRecordSet) { - return c.Additions_ -} - -func (c *Change) Deletions() (rrsets []interfaces.ResourceRecordSet) { - return c.Deletions_ -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/changes_create_call.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/changes_create_call.go deleted file mode 100644 index ab27a12f75e..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/changes_create_call.go +++ /dev/null @@ -1,67 +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 stubs - -import ( - "fmt" - - "google.golang.org/api/googleapi" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" -) - -// Compile time check for interface adherence -var _ interfaces.ChangesCreateCall = ChangesCreateCall{} - -type ChangesCreateCall struct { - Service *ChangesService - Project string - Zone string - Change interfaces.Change - Error error // Use this to over-ride response if necessary -} - -func hashKey(set interfaces.ResourceRecordSet) string { - return fmt.Sprintf("%s-%d-%s", set.Name(), set.Ttl(), string(set.Type())) -} - -func (c ChangesCreateCall) Do(opts ...googleapi.CallOption) (interfaces.Change, error) { - if c.Error != nil { - return nil, c.Error - } - zone := (c.Service.Service.ManagedZones_.Impl[c.Project][c.Zone]).(*ManagedZone) - rrsets := map[string]ResourceRecordSet{} // compute the new state - for _, set := range zone.Rrsets { - rrsets[hashKey(set)] = set - } - for _, del := range c.Change.Deletions() { - if _, found := rrsets[hashKey(del)]; !found { - return nil, fmt.Errorf("Attempt to delete non-existent rrset %v", del) - } - delete(rrsets, hashKey(del)) - } - for _, add := range c.Change.Additions() { - if _, found := rrsets[hashKey(add)]; found { - return nil, fmt.Errorf("Attempt to insert duplicate rrset %v", add) - } - rrsets[hashKey(add)] = add.(ResourceRecordSet) - } - zone.Rrsets = []ResourceRecordSet{} - for _, rrset := range rrsets { - zone.Rrsets = append(zone.Rrsets, rrset) - } - return c.Change, nil -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/changes_service.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/changes_service.go deleted file mode 100644 index 39b8bd14f21..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/changes_service.go +++ /dev/null @@ -1,34 +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 stubs - -import "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" - -// Compile time check for interface adherence -var _ interfaces.ChangesService = &ChangesService{} - -type ChangesService struct { - Service *Service -} - -func (c *ChangesService) Create(project string, managedZone string, change interfaces.Change) interfaces.ChangesCreateCall { - return &ChangesCreateCall{c, project, managedZone, change, nil} -} - -func (c *ChangesService) NewChange(additions, deletions []interfaces.ResourceRecordSet) interfaces.Change { - return &Change{c, additions, deletions} -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/clouddns.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/clouddns.go deleted file mode 100644 index e870ee4091e..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/clouddns.go +++ /dev/null @@ -1,33 +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 stubs - -// Implementation of internal/interfaces/* on top of Google Cloud DNS API. -// See https://godoc.org/google.golang.org/api/dns/v1 for details -// This facilitates stubbing out Google Cloud DNS for unit testing. -// Only the parts of the API that we use are included. -// Others can be added as needed. - -import dns "google.golang.org/api/dns/v1" - -type ( - // TODO: We don't need these yet, so they remain unimplemented. Add later as required. - Project struct{ impl *dns.Project } - ProjectsGetCall struct{ impl *dns.ProjectsGetCall } - ProjectsService struct{ impl *dns.ProjectsService } - Quota struct{ impl *dns.Quota } -) diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/managed_zone.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/managed_zone.go deleted file mode 100644 index 2096da400bd..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/managed_zone.go +++ /dev/null @@ -1,41 +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 stubs - -import "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" - -// Compile time check for interface adherence -var _ interfaces.ManagedZone = ManagedZone{} - -type ManagedZone struct { - Service *ManagedZonesService - Name_ string - Id_ uint64 - Rrsets []ResourceRecordSet -} - -func (m ManagedZone) Name() string { - return m.Name_ -} - -func (m ManagedZone) Id() uint64 { - return m.Id_ -} - -func (m ManagedZone) DnsName() string { - return m.Name_ // Don't bother storing a separate DNS name -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/managed_zone_create_call.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/managed_zone_create_call.go deleted file mode 100644 index f35f585cb51..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/managed_zone_create_call.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 stubs - -import ( - "fmt" - - "google.golang.org/api/googleapi" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" -) - -// Compile time check for interface adherence -var _ interfaces.ManagedZonesCreateCall = ManagedZonesCreateCall{} - -type ManagedZonesCreateCall struct { - Error *error // Use to override response for testing - Service *ManagedZonesService - Project string - ManagedZone interfaces.ManagedZone -} - -func (call ManagedZonesCreateCall) Do(opts ...googleapi.CallOption) (interfaces.ManagedZone, error) { - if call.Error != nil { - return nil, *call.Error - } - if call.Service.Impl[call.Project][call.ManagedZone.DnsName()] != nil { - return nil, fmt.Errorf("Error - attempt to create duplicate zone %s in project %s.", - call.ManagedZone.DnsName(), call.Project) - } - if call.Service.Impl == nil { - call.Service.Impl = map[string]map[string]interfaces.ManagedZone{} - } - if call.Service.Impl[call.Project] == nil { - call.Service.Impl[call.Project] = map[string]interfaces.ManagedZone{} - } - call.Service.Impl[call.Project][call.ManagedZone.DnsName()] = call.ManagedZone - return call.ManagedZone, nil -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/managed_zones_delete_call.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/managed_zones_delete_call.go deleted file mode 100644 index c518e76ebad..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/managed_zones_delete_call.go +++ /dev/null @@ -1,53 +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 stubs - -import ( - "fmt" - - "google.golang.org/api/googleapi" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" -) - -// Compile time check for interface adherence -var _ interfaces.ManagedZonesDeleteCall = ManagedZonesDeleteCall{} - -type ManagedZonesDeleteCall struct { - Service *ManagedZonesService - Project string - ZoneName string - Error *error // Use this to override response for testing if required -} - -func (call ManagedZonesDeleteCall) Do(opts ...googleapi.CallOption) error { - if call.Error != nil { // Return the override value - return *call.Error - } else { // Just try to delete it from the in-memory array. - project, ok := call.Service.Impl[call.Project] - if ok { - zone, ok := project[call.ZoneName] - if ok { - delete(project, zone.Name()) - return nil - } else { - return fmt.Errorf("Failed to find zone %s in project %s to delete it", call.ZoneName, call.Project) - } - } else { - return fmt.Errorf("Failed to find project %s to delete zone %s from it", call.Project, call.ZoneName) - } - } -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/managed_zones_get_call.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/managed_zones_get_call.go deleted file mode 100644 index f2923c3abd3..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/managed_zones_get_call.go +++ /dev/null @@ -1,42 +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 stubs - -import ( - "google.golang.org/api/googleapi" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" -) - -// Compile time check for interface adherence -var _ interfaces.ManagedZonesGetCall = ManagedZonesGetCall{} - -type ManagedZonesGetCall struct { - Service *ManagedZonesService - Project string - ZoneName string - Response interfaces.ManagedZone // Use this to override response if required - Error *error // Use this to override response if required - DnsName_ string -} - -func (call ManagedZonesGetCall) Do(opts ...googleapi.CallOption) (interfaces.ManagedZone, error) { - if call.Response != nil { - return call.Response, *call.Error - } else { - return call.Service.Impl[call.Project][call.ZoneName], nil - } -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/managed_zones_list_call.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/managed_zones_list_call.go deleted file mode 100644 index 4b7156ffbc5..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/managed_zones_list_call.go +++ /dev/null @@ -1,59 +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 stubs - -import ( - "fmt" - - "google.golang.org/api/googleapi" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" -) - -// Compile time check for interface adherence -var _ interfaces.ManagedZonesListCall = &ManagedZonesListCall{} - -type ManagedZonesListCall struct { - Service *ManagedZonesService - Project string - Response *interfaces.ManagedZonesListResponse // Use this to override response if required - Error *error // Use this to override response if required - DnsName_ string -} - -func (call *ManagedZonesListCall) Do(opts ...googleapi.CallOption) (interfaces.ManagedZonesListResponse, error) { - if call.Response != nil { - return *call.Response, *call.Error - } else { - proj, projectFound := call.Service.Impl[call.Project] - if !projectFound { - return nil, fmt.Errorf("Project %s not found.", call.Project) - } - if call.DnsName_ != "" { - return &ManagedZonesListResponse{[]interfaces.ManagedZone{proj[call.DnsName_]}}, nil - } - list := []interfaces.ManagedZone{} - for _, zone := range proj { - list = append(list, zone) - } - return &ManagedZonesListResponse{list}, nil - } -} - -func (call *ManagedZonesListCall) DnsName(dnsName string) interfaces.ManagedZonesListCall { - call.DnsName_ = dnsName - return call -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/managed_zones_list_response.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/managed_zones_list_response.go deleted file mode 100644 index 095cae2ba80..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/managed_zones_list_response.go +++ /dev/null @@ -1,28 +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 stubs - -import "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" - -// Compile time check for interface adherence -var _ interfaces.ManagedZonesListResponse = &ManagedZonesListResponse{} - -type ManagedZonesListResponse struct{ ManagedZones_ []interfaces.ManagedZone } - -func (response *ManagedZonesListResponse) ManagedZones() []interfaces.ManagedZone { - return response.ManagedZones_ -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/managed_zones_service.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/managed_zones_service.go deleted file mode 100644 index 5b8513f2f73..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/managed_zones_service.go +++ /dev/null @@ -1,46 +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 stubs - -import "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" - -// Compile time check for interface adherence -var _ interfaces.ManagedZonesService = &ManagedZonesService{} - -type ManagedZonesService struct { - Impl map[string]map[string]interfaces.ManagedZone -} - -func (m *ManagedZonesService) Create(project string, managedzone interfaces.ManagedZone) interfaces.ManagedZonesCreateCall { - return &ManagedZonesCreateCall{nil, m, project, managedzone.(*ManagedZone)} -} - -func (m *ManagedZonesService) Delete(project string, managedZone string) interfaces.ManagedZonesDeleteCall { - return &ManagedZonesDeleteCall{m, project, managedZone, nil} -} - -func (m *ManagedZonesService) Get(project string, managedZone string) interfaces.ManagedZonesGetCall { - return &ManagedZonesGetCall{m, project, managedZone, nil, nil, ""} -} - -func (m *ManagedZonesService) List(project string) interfaces.ManagedZonesListCall { - return &ManagedZonesListCall{m, project, nil, nil, ""} -} - -func (m *ManagedZonesService) NewManagedZone(dnsName string) interfaces.ManagedZone { - return &ManagedZone{Service: m, Name_: dnsName} -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/rrset.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/rrset.go deleted file mode 100644 index 3412775c17c..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/rrset.go +++ /dev/null @@ -1,34 +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 stubs - -import "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" - -// Compile time check for interface adherence -var _ interfaces.ResourceRecordSet = ResourceRecordSet{} - -type ResourceRecordSet struct { - Name_ string - Rrdatas_ []string - Ttl_ int64 - Type_ string -} - -func (r ResourceRecordSet) Name() string { return r.Name_ } -func (r ResourceRecordSet) Rrdatas() []string { return r.Rrdatas_ } -func (r ResourceRecordSet) Ttl() int64 { return r.Ttl_ } -func (r ResourceRecordSet) Type() string { return r.Type_ } diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/rrsets_list_call.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/rrsets_list_call.go deleted file mode 100644 index 8695142bff5..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/rrsets_list_call.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 stubs - -import ( - "context" - - "google.golang.org/api/googleapi" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" -) - -// Compile time check for interface adherence -var _ interfaces.ResourceRecordSetsListCall = &ResourceRecordSetsListCall{} - -type ResourceRecordSetsListCall struct { - Response_ *ResourceRecordSetsListResponse - Err_ error - Name_ string - Type_ string -} - -func (call *ResourceRecordSetsListCall) Do(opts ...googleapi.CallOption) (interfaces.ResourceRecordSetsListResponse, error) { - return call.Response_, call.Err_ -} - -func (call *ResourceRecordSetsListCall) Pages(ctx context.Context, f func(interfaces.ResourceRecordSetsListResponse) error) error { - return f(call.Response_) -} - -func (call *ResourceRecordSetsListCall) Name(name string) interfaces.ResourceRecordSetsListCall { - call.Name_ = name - return call -} - -func (call *ResourceRecordSetsListCall) Type(type_ string) interfaces.ResourceRecordSetsListCall { - call.Type_ = type_ - return call -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/rrsets_list_response.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/rrsets_list_response.go deleted file mode 100644 index a60d8dceffb..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/rrsets_list_response.go +++ /dev/null @@ -1,30 +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 stubs - -import "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" - -// Compile time check for interface adherence -var _ interfaces.ResourceRecordSetsListResponse = &ResourceRecordSetsListResponse{} - -type ResourceRecordSetsListResponse struct { - impl []interfaces.ResourceRecordSet -} - -func (response *ResourceRecordSetsListResponse) Rrsets() []interfaces.ResourceRecordSet { - return response.impl -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/rrsets_service.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/rrsets_service.go deleted file mode 100644 index 13e50d8b03b..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/rrsets_service.go +++ /dev/null @@ -1,83 +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 stubs - -import ( - "fmt" - - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" - "k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype" -) - -// Compile time check for interface adherence -var _ interfaces.ResourceRecordSetsService = &ResourceRecordSetsService{} - -type ResourceRecordSetsService struct { - Service *Service - ListCall interfaces.ResourceRecordSetsListCall // Use to override response if required for testing -} - -func (s ResourceRecordSetsService) managedZone(project, managedZone string) (*ManagedZone, error) { - p := s.Service.ManagedZones_.Impl[project] - if p == nil { - return nil, fmt.Errorf("Project not found: %s", project) - } - z := s.Service.ManagedZones_.Impl[project][managedZone] - if z == nil { - return nil, fmt.Errorf("Zone %s not found in project %s", managedZone, project) - } - return z.(*ManagedZone), nil -} - -func (s ResourceRecordSetsService) List(project string, managedZone string) interfaces.ResourceRecordSetsListCall { - if s.ListCall != nil { - return s.ListCall - } - zone, err := s.managedZone(project, managedZone) - if err != nil { - return &ResourceRecordSetsListCall{Err_: err} - } - - response := &ResourceRecordSetsListResponse{} - for _, set := range zone.Rrsets { - response.impl = append(response.impl, set) - } - return &ResourceRecordSetsListCall{Response_: response} -} - -func (s ResourceRecordSetsService) Get(project, managedZone, name string) interfaces.ResourceRecordSetsListCall { - if s.ListCall != nil { - return s.ListCall - } - zone, err := s.managedZone(project, managedZone) - if err != nil { - return &ResourceRecordSetsListCall{Err_: err} - } - - response := &ResourceRecordSetsListResponse{} - for _, set := range zone.Rrsets { - if set.Name_ == name { - response.impl = append(response.impl, set) - } - } - return &ResourceRecordSetsListCall{Response_: response} -} - -func (service ResourceRecordSetsService) NewResourceRecordSet(name string, rrdatas []string, ttl int64, type_ rrstype.RrsType) interfaces.ResourceRecordSet { - rrset := ResourceRecordSet{Name_: name, Rrdatas_: rrdatas, Ttl_: ttl, Type_: string(type_)} - return rrset -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/service.go b/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/service.go deleted file mode 100644 index 561678cf936..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs/service.go +++ /dev/null @@ -1,54 +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 stubs - -import "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" - -// Compile time check for interface adherence -var _ interfaces.Service = &Service{} - -type Service struct { - Changes_ *ChangesService - ManagedZones_ *ManagedZonesService - Projects_ *ProjectsService - Rrsets_ *ResourceRecordSetsService -} - -func NewService() *Service { - s := &Service{} - s.Changes_ = &ChangesService{s} - s.ManagedZones_ = &ManagedZonesService{} - s.Projects_ = &ProjectsService{} - s.Rrsets_ = &ResourceRecordSetsService{s, nil} - return s -} - -func (s *Service) Changes() interfaces.ChangesService { - return s.Changes_ -} - -func (s *Service) ManagedZones() interfaces.ManagedZonesService { - return s.ManagedZones_ -} - -func (s *Service) Projects() interfaces.ProjectsService { - return s.Projects_ -} - -func (s *Service) ResourceRecordSets() interfaces.ResourceRecordSetsService { - return s.Rrsets_ -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/rrchangeset.go b/federation/pkg/dnsprovider/providers/google/clouddns/rrchangeset.go deleted file mode 100644 index c81d03dd237..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/rrchangeset.go +++ /dev/null @@ -1,124 +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 clouddns - -import ( - "fmt" - - "k8s.io/kubernetes/federation/pkg/dnsprovider" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" -) - -// Compile time check for interface adherence -var _ dnsprovider.ResourceRecordChangeset = &ResourceRecordChangeset{} - -type ResourceRecordChangeset struct { - rrsets *ResourceRecordSets - - additions []dnsprovider.ResourceRecordSet - removals []dnsprovider.ResourceRecordSet - upserts []dnsprovider.ResourceRecordSet -} - -func (c *ResourceRecordChangeset) Add(rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordChangeset { - c.additions = append(c.additions, rrset) - return c -} - -func (c *ResourceRecordChangeset) Remove(rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordChangeset { - c.removals = append(c.removals, rrset) - return c -} - -func (c *ResourceRecordChangeset) Upsert(rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordChangeset { - c.upserts = append(c.upserts, rrset) - return c -} - -func (c *ResourceRecordChangeset) Apply() error { - rrsets := c.rrsets - - service := rrsets.zone.zones.interface_.service.Changes() - - var additions []interfaces.ResourceRecordSet - for _, r := range c.additions { - additions = append(additions, r.(ResourceRecordSet).impl) - } - var deletions []interfaces.ResourceRecordSet - for _, r := range c.removals { - deletions = append(deletions, r.(ResourceRecordSet).impl) - } - - if len(c.upserts) != 0 { - // TODO: We could maybe tweak this to fetch just the records we care about - // although not clear when this would be a win. N=1 obviously so though... - before, err := c.rrsets.List() - if err != nil { - return fmt.Errorf("error fetching recordset images for upsert operation: %v", err) - } - - upsertMap := make(map[string]dnsprovider.ResourceRecordSet) - for _, upsert := range c.upserts { - key := string(upsert.Type()) + "::" + upsert.Name() - upsertMap[key] = upsert - } - - for _, b := range before { - key := string(b.Type()) + "::" + b.Name() - upsert := upsertMap[key] - if upsert == nil { - continue - } - - deletions = append(deletions, b.(ResourceRecordSet).impl) - additions = append(additions, upsert.(ResourceRecordSet).impl) - - // Mark as seen - delete(upsertMap, key) - } - - // Anything left in the map must be an addition - for _, upsert := range upsertMap { - additions = append(additions, upsert.(ResourceRecordSet).impl) - } - } - - change := service.NewChange(additions, deletions) - newChange, err := service.Create(rrsets.project(), rrsets.zone.impl.Name(), change).Do() - if err != nil { - return err - } - newAdditions := newChange.Additions() - if len(newAdditions) != len(additions) { - return fmt.Errorf("Internal error when adding resource record set. Call succeeded but number of records returned is incorrect. Records sent=%d, records returned=%d, additions:%v", len(additions), len(newAdditions), c.additions) - } - newDeletions := newChange.Deletions() - if len(newDeletions) != len(deletions) { - return fmt.Errorf("Internal error when deleting resource record set. Call succeeded but number of records returned is incorrect. Records sent=%d, records returned=%d, deletions:%v", len(deletions), len(newDeletions), c.removals) - } - - return nil -} - -func (c *ResourceRecordChangeset) IsEmpty() bool { - return len(c.additions) == 0 && len(c.removals) == 0 -} - -// ResourceRecordSets returns the parent ResourceRecordSets -func (c *ResourceRecordChangeset) ResourceRecordSets() dnsprovider.ResourceRecordSets { - return c.rrsets -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/rrset.go b/federation/pkg/dnsprovider/providers/google/clouddns/rrset.go deleted file mode 100644 index f357e1c9e7c..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/rrset.go +++ /dev/null @@ -1,53 +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 clouddns - -import ( - "fmt" - - "k8s.io/kubernetes/federation/pkg/dnsprovider" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" - "k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype" -) - -// Compile time check for interface adherence -var _ dnsprovider.ResourceRecordSet = ResourceRecordSet{} - -type ResourceRecordSet struct { - impl interfaces.ResourceRecordSet - rrsets *ResourceRecordSets -} - -func (rrset ResourceRecordSet) String() string { - return fmt.Sprintf("<(clouddns) %q type=%s rrdatas=%q ttl=%v>", rrset.Name(), rrset.Type(), rrset.Rrdatas(), rrset.Ttl()) -} - -func (rrset ResourceRecordSet) Name() string { - return rrset.impl.Name() -} - -func (rrset ResourceRecordSet) Rrdatas() []string { - return rrset.impl.Rrdatas() -} - -func (rrset ResourceRecordSet) Ttl() int64 { - return rrset.impl.Ttl() -} - -func (rrset ResourceRecordSet) Type() rrstype.RrsType { - return rrstype.RrsType(rrset.impl.Type()) -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/rrsets.go b/federation/pkg/dnsprovider/providers/google/clouddns/rrsets.go deleted file mode 100644 index db9411c7661..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/rrsets.go +++ /dev/null @@ -1,94 +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 clouddns - -import ( - "context" - - "k8s.io/kubernetes/federation/pkg/dnsprovider" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" - "k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype" -) - -// Compile time check for interface adherence -var _ dnsprovider.ResourceRecordSets = ResourceRecordSets{} - -type ResourceRecordSets struct { - zone *Zone - impl interfaces.ResourceRecordSetsService -} - -// List returns a list of resource records in the given project and -// managed zone. -// !!CAUTION!! Your memory might explode if you have a huge number of -// records in your managed zone. -func (rrsets ResourceRecordSets) List() ([]dnsprovider.ResourceRecordSet, error) { - var list []dnsprovider.ResourceRecordSet - - ctx := context.Background() - - call := rrsets.impl.List(rrsets.project(), rrsets.zone.impl.Name()) - err := call.Pages(ctx, func(page interfaces.ResourceRecordSetsListResponse) error { - for _, rrset := range page.Rrsets() { - list = append(list, ResourceRecordSet{rrset, &rrsets}) - } - return nil - }) - if err != nil { - return nil, err - } - - return list, nil -} - -func (rrsets ResourceRecordSets) Get(name string) ([]dnsprovider.ResourceRecordSet, error) { - var list []dnsprovider.ResourceRecordSet - - ctx := context.Background() - - call := rrsets.impl.Get(rrsets.project(), rrsets.zone.impl.Name(), name) - err := call.Pages(ctx, func(page interfaces.ResourceRecordSetsListResponse) error { - for _, rrset := range page.Rrsets() { - list = append(list, ResourceRecordSet{rrset, &rrsets}) - } - return nil - }) - if err != nil { - return nil, err - } - - return list, nil -} - -func (r ResourceRecordSets) StartChangeset() dnsprovider.ResourceRecordChangeset { - return &ResourceRecordChangeset{ - rrsets: &r, - } -} - -func (r ResourceRecordSets) New(name string, rrdatas []string, ttl int64, rrstype rrstype.RrsType) dnsprovider.ResourceRecordSet { - return ResourceRecordSet{r.impl.NewResourceRecordSet(name, rrdatas, ttl, rrstype), &r} -} - -func (rrsets ResourceRecordSets) project() string { - return rrsets.zone.project() -} - -// Zone returns the parent zone -func (rrset ResourceRecordSets) Zone() dnsprovider.Zone { - return rrset.zone -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/zone.go b/federation/pkg/dnsprovider/providers/google/clouddns/zone.go deleted file mode 100644 index 97e775f90f7..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/zone.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 clouddns - -import ( - "strconv" - - "k8s.io/kubernetes/federation/pkg/dnsprovider" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" -) - -// Compile time check for interface adherence -var _ dnsprovider.Zone = &Zone{} - -type Zone struct { - impl interfaces.ManagedZone - zones *Zones -} - -func (zone *Zone) Name() string { - return zone.impl.DnsName() -} - -func (zone *Zone) ID() string { - return strconv.FormatUint(zone.impl.Id(), 10) -} - -func (zone *Zone) ResourceRecordSets() (dnsprovider.ResourceRecordSets, bool) { - return &ResourceRecordSets{zone, zone.zones.interface_.service.ResourceRecordSets()}, true -} - -func (zone Zone) project() string { - return zone.zones.project() -} diff --git a/federation/pkg/dnsprovider/providers/google/clouddns/zones.go b/federation/pkg/dnsprovider/providers/google/clouddns/zones.go deleted file mode 100644 index 62d56aef486..00000000000 --- a/federation/pkg/dnsprovider/providers/google/clouddns/zones.go +++ /dev/null @@ -1,68 +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 clouddns - -import ( - "k8s.io/kubernetes/federation/pkg/dnsprovider" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces" -) - -// Compile time check for interface adherence -var _ dnsprovider.Zones = Zones{} - -type Zones struct { - impl interfaces.ManagedZonesService - interface_ *Interface -} - -func (zones Zones) List() ([]dnsprovider.Zone, error) { - response, err := zones.impl.List(zones.project()).Do() - if err != nil { - return nil, err - } - managedZones := response.ManagedZones() - zoneList := make([]dnsprovider.Zone, len(managedZones)) - for i, zone := range managedZones { - zoneList[i] = &Zone{zone, &zones} - } - return zoneList, nil -} - -func (zones Zones) Add(zone dnsprovider.Zone) (dnsprovider.Zone, error) { - managedZone := zones.impl.NewManagedZone(zone.Name()) - response, err := zones.impl.Create(zones.project(), managedZone).Do() - if err != nil { - return nil, err - } - return &Zone{response, &zones}, nil -} - -func (zones Zones) Remove(zone dnsprovider.Zone) error { - if err := zones.impl.Delete(zones.project(), zone.(*Zone).impl.Name()).Do(); err != nil { - return err - } - return nil -} - -func (zones Zones) New(name string) (dnsprovider.Zone, error) { - managedZone := zones.impl.NewManagedZone(name) - return &Zone{managedZone, &zones}, nil -} - -func (zones Zones) project() string { - return zones.interface_.project() -} diff --git a/federation/pkg/dnsprovider/rrstype/BUILD b/federation/pkg/dnsprovider/rrstype/BUILD deleted file mode 100644 index 1df8d0c2d44..00000000000 --- a/federation/pkg/dnsprovider/rrstype/BUILD +++ /dev/null @@ -1,25 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["rrstype.go"], - importpath = "k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype", -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/pkg/dnsprovider/rrstype/rrstype.go b/federation/pkg/dnsprovider/rrstype/rrstype.go deleted file mode 100644 index acf9e90b312..00000000000 --- a/federation/pkg/dnsprovider/rrstype/rrstype.go +++ /dev/null @@ -1,28 +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 rrstype - -type ( - RrsType string -) - -const ( - A = RrsType("A") - AAAA = RrsType("AAAA") - CNAME = RrsType("CNAME") - // TODO: Add other types as required -) diff --git a/federation/pkg/dnsprovider/tests/BUILD b/federation/pkg/dnsprovider/tests/BUILD deleted file mode 100644 index 193e4e5a0af..00000000000 --- a/federation/pkg/dnsprovider/tests/BUILD +++ /dev/null @@ -1,29 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["commontests.go"], - importpath = "k8s.io/kubernetes/federation/pkg/dnsprovider/tests", - deps = [ - "//federation/pkg/dnsprovider:go_default_library", - "//federation/pkg/dnsprovider/rrstype:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/pkg/dnsprovider/tests/commontests.go b/federation/pkg/dnsprovider/tests/commontests.go deleted file mode 100644 index 6e1584ad86f..00000000000 --- a/federation/pkg/dnsprovider/tests/commontests.go +++ /dev/null @@ -1,194 +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 tests - -import ( - "reflect" - "testing" - - "k8s.io/kubernetes/federation/pkg/dnsprovider" - "k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype" -) - -/* CommonTestResourceRecordSetsReplace verifies that replacing an RRS works */ -func CommonTestResourceRecordSetsReplace(t *testing.T, zone dnsprovider.Zone) { - rrsets, _ := zone.ResourceRecordSets() - - sets := rrs(t, zone) - rrset := rrsets.New("alpha.test.com", []string{"8.8.4.4"}, 40, rrstype.A) - addRrsetOrFail(t, sets, rrset) - defer sets.StartChangeset().Remove(rrset).Apply() - - // Replace the record (change ttl and rrdatas) - newRrset := rrsets.New("alpha.test.com", []string{"8.8.8.8"}, 80, rrstype.A) - err := sets.StartChangeset().Add(newRrset).Remove(rrset).Apply() - if err != nil { - t.Errorf("Failed to replace resource record set %v -> %v: %v", rrset, newRrset, err) - } else { - defer sets.StartChangeset().Remove(newRrset).Apply() - t.Logf("Correctly replaced resource record %v -> %v", rrset, newRrset) - } - - // Check that the record was updated - assertHasRecord(t, sets, newRrset) -} - -/* CommonTestResourceRecordSetsReplaceAll verifies that we can remove an RRS and create one with a different name*/ -func CommonTestResourceRecordSetsReplaceAll(t *testing.T, zone dnsprovider.Zone) { - rrsets, _ := zone.ResourceRecordSets() - - sets := rrs(t, zone) - rrset := rrsets.New("alpha.test.com", []string{"8.8.4.4"}, 40, rrstype.A) - addRrsetOrFail(t, sets, rrset) - defer sets.StartChangeset().Remove(rrset).Apply() - - newRrset := rrsets.New("beta.test.com", []string{"8.8.8.8"}, 80, rrstype.A) - - // Try to add it again, and verify that the call fails. - err := sets.StartChangeset().Add(newRrset).Remove(rrset).Apply() - if err != nil { - t.Errorf("Failed to replace resource record set %v -> %v: %v", rrset, newRrset, err) - } else { - defer sets.StartChangeset().Remove(newRrset).Apply() - t.Logf("Correctly replaced resource record %v -> %v", rrset, newRrset) - } - - // Check that it was updated - assertHasRecord(t, sets, newRrset) - assertNotHasRecord(t, sets, rrset.Name(), rrset.Type()) -} - -/* CommonTestResourceRecordSetsDifferentType verifies that we can add records of the same name but different types */ -func CommonTestResourceRecordSetsDifferentTypes(t *testing.T, zone dnsprovider.Zone) { - rrsets, _ := zone.ResourceRecordSets() - - sets := rrs(t, zone) - rrset := rrsets.New("alpha.test.com", []string{"8.8.4.4"}, 40, rrstype.A) - addRrsetOrFail(t, sets, rrset) - defer sets.StartChangeset().Remove(rrset).Apply() - - aaaaRrset := rrsets.New("alpha.test.com", []string{"2001:4860:4860::8888"}, 80, rrstype.AAAA) - - // Add the resource with the same name but different type - err := sets.StartChangeset().Add(aaaaRrset).Apply() - if err != nil { - t.Errorf("Failed to add resource record set %v: %v", aaaaRrset, err) - } - defer sets.StartChangeset().Remove(aaaaRrset).Apply() - - // Check that both records exist - assertHasRecord(t, sets, aaaaRrset) - assertHasRecord(t, sets, rrset) -} - -/* rrs returns the ResourceRecordSets interface for a given zone */ -func rrs(t *testing.T, zone dnsprovider.Zone) (r dnsprovider.ResourceRecordSets) { - rrsets, supported := zone.ResourceRecordSets() - if !supported { - t.Fatalf("ResourceRecordSets interface not supported by zone %v", zone) - return r - } - return rrsets -} - -func getRrOrFail(t *testing.T, rrsets dnsprovider.ResourceRecordSets, name string) []dnsprovider.ResourceRecordSet { - rrsetList, err := rrsets.Get(name) - if err != nil { - t.Fatalf("Failed to get recordset: %v", err) - } else if len(rrsetList) == 0 { - t.Logf("Did not Get recordset: %v", name) - } else { - t.Logf("Got recordset: %v", rrsetList[0].Name()) - } - return rrsetList -} - -// assertHasRecord tests that rrsets has a record equivalent to rrset -func assertHasRecord(t *testing.T, rrsets dnsprovider.ResourceRecordSets, rrset dnsprovider.ResourceRecordSet) { - var found dnsprovider.ResourceRecordSet - - rrs, err := rrsets.List() - if err != nil { - if err.Error() == "OperationNotSupported" { - foundList := getRrOrFail(t, rrsets, rrset.Name()) - for i, elem := range foundList { - if elem.Name() == rrset.Name() && elem.Type() == rrset.Type() { - found = foundList[i] - break - } - } - } else { - t.Fatalf("Failed to list recordsets: %v", err) - } - } else { - if len(rrs) < 0 { - t.Fatalf("Record set length=%d, expected >=0", len(rrs)) - } else { - t.Logf("Got %d recordsets: %v", len(rrs), rrs) - } - - for _, r := range rrs { - if r.Name() != rrset.Name() || r.Type() != rrset.Type() { - continue - } - - if found != nil { - t.Errorf("found duplicate resource record set: %q and %q", r, found) - } - found = r - } - } - - if found == nil { - t.Errorf("resource record set %v not found", rrset) - } else { - assertEquivalent(t, found, rrset) - } -} - -// assertNotHasRecord tests that rrsets does not have a record matching name and type -func assertNotHasRecord(t *testing.T, rrsets dnsprovider.ResourceRecordSets, name string, rrstype rrstype.RrsType) { - found := getRrOrFail(t, rrsets, name) - if found != nil { - t.Errorf("resource record set found unexpectedly: %v", found) - } -} - -// assertEquivalent tests that l is equal to r, for the methods in ResourceRecordSet -func assertEquivalent(t *testing.T, l, r dnsprovider.ResourceRecordSet) { - if l.Name() != r.Name() { - t.Errorf("resource record sets not equal %v vs %v", l, r) - } - if l.Type() != r.Type() { - t.Errorf("resource record sets not equal %v vs %v", l, r) - } - if l.Ttl() != r.Ttl() { - t.Errorf("resource record sets not equal %v vs %v", l, r) - } - if !reflect.DeepEqual(l.Rrdatas(), r.Rrdatas()) { - t.Errorf("resource record sets not equal %v vs %v", l, r) - } -} - -func addRrsetOrFail(t *testing.T, rrsets dnsprovider.ResourceRecordSets, rrset dnsprovider.ResourceRecordSet) { - err := rrsets.StartChangeset().Add(rrset).Apply() - if err != nil { - t.Fatalf("Failed to add recordset %v: %v", rrset, err) - } else { - t.Logf("Successfully added resource record set: %v", rrset) - } -} diff --git a/federation/pkg/federatedtypes/BUILD b/federation/pkg/federatedtypes/BUILD deleted file mode 100644 index 3b2ce333af6..00000000000 --- a/federation/pkg/federatedtypes/BUILD +++ /dev/null @@ -1,88 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = [ - "hpa_test.go", - "scheduling_test.go", - ], - importpath = "k8s.io/kubernetes/federation/pkg/federatedtypes", - library = ":go_default_library", - deps = [ - "//federation/pkg/federation-controller/util/test:go_default_library", - "//vendor/github.com/stretchr/testify/assert:go_default_library", - "//vendor/k8s.io/api/autoscaling/v1:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - ], -) - -go_library( - name = "go_default_library", - srcs = [ - "adapter.go", - "configmap.go", - "daemonset.go", - "deployment.go", - "hpa.go", - "namespace.go", - "qualifiedname.go", - "registry.go", - "replicaset.go", - "scheduling.go", - "secret.go", - ], - importpath = "k8s.io/kubernetes/federation/pkg/federatedtypes", - deps = [ - "//federation/apis/federation:go_default_library", - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/client/clientset_generated/federation_clientset:go_default_library", - "//federation/pkg/federation-controller/util:go_default_library", - "//federation/pkg/federation-controller/util/hpa:go_default_library", - "//federation/pkg/federation-controller/util/planner:go_default_library", - "//federation/pkg/federation-controller/util/podanalyzer:go_default_library", - "//federation/pkg/federation-controller/util/replicapreferences:go_default_library", - "//pkg/api:go_default_library", - "//pkg/apis/extensions:go_default_library", - "//pkg/controller/namespace/deletion:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/api/autoscaling/v1:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/dynamic:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - "//vendor/k8s.io/client-go/tools/record:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/pkg/federatedtypes/crudtester:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/pkg/federatedtypes/adapter.go b/federation/pkg/federatedtypes/adapter.go deleted file mode 100644 index 1dbda2a498e..00000000000 --- a/federation/pkg/federatedtypes/adapter.go +++ /dev/null @@ -1,79 +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 federatedtypes - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - pkgruntime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/watch" - kubeclientset "k8s.io/client-go/kubernetes" - restclient "k8s.io/client-go/rest" - federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" -) - -// FederatedTypeAdapter defines operations for interacting with a -// federated type. Code written to this interface can then target any -// type for which an implementation of this interface exists. -type FederatedTypeAdapter interface { - Kind() string - ObjectType() pkgruntime.Object - IsExpectedType(obj interface{}) bool - Copy(obj pkgruntime.Object) pkgruntime.Object - Equivalent(obj1, obj2 pkgruntime.Object) bool - QualifiedName(obj pkgruntime.Object) QualifiedName - ObjectMeta(obj pkgruntime.Object) *metav1.ObjectMeta - - // Fed* operations target the federation control plane - FedCreate(obj pkgruntime.Object) (pkgruntime.Object, error) - FedDelete(qualifiedName QualifiedName, options *metav1.DeleteOptions) error - FedGet(qualifiedName QualifiedName) (pkgruntime.Object, error) - FedList(namespace string, options metav1.ListOptions) (pkgruntime.Object, error) - FedUpdate(obj pkgruntime.Object) (pkgruntime.Object, error) - FedWatch(namespace string, options metav1.ListOptions) (watch.Interface, error) - - // The following operations are intended to target a cluster that is a member of a federation - ClusterCreate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) - ClusterDelete(client kubeclientset.Interface, qualifiedName QualifiedName, options *metav1.DeleteOptions) error - ClusterGet(client kubeclientset.Interface, qualifiedName QualifiedName) (pkgruntime.Object, error) - ClusterList(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (pkgruntime.Object, error) - ClusterUpdate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) - ClusterWatch(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (watch.Interface, error) - - IsSchedulingAdapter() bool - - NewTestObject(namespace string) pkgruntime.Object -} - -// AdapterFactory defines the function signature for factory methods -// that create instances of FederatedTypeAdapter. Such methods should -// be registered with RegisterAdapterFactory to ensure the type -// adapter is discoverable. -type AdapterFactory func(client federationclientset.Interface, config *restclient.Config, adapterSpecificArgs map[string]interface{}) FederatedTypeAdapter - -// SetAnnotation sets the given key and value in the given object's ObjectMeta.Annotations map -func SetAnnotation(adapter FederatedTypeAdapter, obj pkgruntime.Object, key, value string) { - meta := adapter.ObjectMeta(obj) - if meta.Annotations == nil { - meta.Annotations = make(map[string]string) - } - meta.Annotations[key] = value -} - -// ObjectKey returns a cluster-unique key for the given object -func ObjectKey(adapter FederatedTypeAdapter, obj pkgruntime.Object) string { - return adapter.QualifiedName(obj).String() -} diff --git a/federation/pkg/federatedtypes/configmap.go b/federation/pkg/federatedtypes/configmap.go deleted file mode 100644 index 79fdea2caf1..00000000000 --- a/federation/pkg/federatedtypes/configmap.go +++ /dev/null @@ -1,150 +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 federatedtypes - -import ( - apiv1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - pkgruntime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/watch" - kubeclientset "k8s.io/client-go/kubernetes" - restclient "k8s.io/client-go/rest" - federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - "k8s.io/kubernetes/federation/pkg/federation-controller/util" -) - -const ( - ConfigMapKind = "configmap" - ConfigMapControllerName = "configmaps" -) - -func init() { - RegisterFederatedType(ConfigMapKind, ConfigMapControllerName, []schema.GroupVersionResource{apiv1.SchemeGroupVersion.WithResource(ConfigMapControllerName)}, NewConfigMapAdapter) -} - -type ConfigMapAdapter struct { - client federationclientset.Interface -} - -func NewConfigMapAdapter(client federationclientset.Interface, config *restclient.Config, adapterSpecificArgs map[string]interface{}) FederatedTypeAdapter { - return &ConfigMapAdapter{client: client} -} - -func (a *ConfigMapAdapter) Kind() string { - return ConfigMapKind -} - -func (a *ConfigMapAdapter) ObjectType() pkgruntime.Object { - return &apiv1.ConfigMap{} -} - -func (a *ConfigMapAdapter) IsExpectedType(obj interface{}) bool { - _, ok := obj.(*apiv1.ConfigMap) - return ok -} - -func (a *ConfigMapAdapter) Copy(obj pkgruntime.Object) pkgruntime.Object { - configmap := obj.(*apiv1.ConfigMap) - return &apiv1.ConfigMap{ - ObjectMeta: util.DeepCopyRelevantObjectMeta(configmap.ObjectMeta), - Data: configmap.Data, - } -} - -func (a *ConfigMapAdapter) Equivalent(obj1, obj2 pkgruntime.Object) bool { - configmap1 := obj1.(*apiv1.ConfigMap) - configmap2 := obj2.(*apiv1.ConfigMap) - return util.ConfigMapEquivalent(configmap1, configmap2) -} - -func (a *ConfigMapAdapter) QualifiedName(obj pkgruntime.Object) QualifiedName { - configmap := obj.(*apiv1.ConfigMap) - return QualifiedName{Namespace: configmap.Namespace, Name: configmap.Name} -} - -func (a *ConfigMapAdapter) ObjectMeta(obj pkgruntime.Object) *metav1.ObjectMeta { - return &obj.(*apiv1.ConfigMap).ObjectMeta -} - -func (a *ConfigMapAdapter) FedCreate(obj pkgruntime.Object) (pkgruntime.Object, error) { - configmap := obj.(*apiv1.ConfigMap) - return a.client.CoreV1().ConfigMaps(configmap.Namespace).Create(configmap) -} - -func (a *ConfigMapAdapter) FedDelete(qualifiedName QualifiedName, options *metav1.DeleteOptions) error { - return a.client.CoreV1().ConfigMaps(qualifiedName.Namespace).Delete(qualifiedName.Name, options) -} - -func (a *ConfigMapAdapter) FedGet(qualifiedName QualifiedName) (pkgruntime.Object, error) { - return a.client.CoreV1().ConfigMaps(qualifiedName.Namespace).Get(qualifiedName.Name, metav1.GetOptions{}) -} - -func (a *ConfigMapAdapter) FedList(namespace string, options metav1.ListOptions) (pkgruntime.Object, error) { - return a.client.CoreV1().ConfigMaps(namespace).List(options) -} - -func (a *ConfigMapAdapter) FedUpdate(obj pkgruntime.Object) (pkgruntime.Object, error) { - configmap := obj.(*apiv1.ConfigMap) - return a.client.CoreV1().ConfigMaps(configmap.Namespace).Update(configmap) -} - -func (a *ConfigMapAdapter) FedWatch(namespace string, options metav1.ListOptions) (watch.Interface, error) { - return a.client.CoreV1().ConfigMaps(namespace).Watch(options) -} - -func (a *ConfigMapAdapter) ClusterCreate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) { - configmap := obj.(*apiv1.ConfigMap) - return client.CoreV1().ConfigMaps(configmap.Namespace).Create(configmap) -} - -func (a *ConfigMapAdapter) ClusterDelete(client kubeclientset.Interface, qualifiedName QualifiedName, options *metav1.DeleteOptions) error { - return client.CoreV1().ConfigMaps(qualifiedName.Namespace).Delete(qualifiedName.Name, options) -} - -func (a *ConfigMapAdapter) ClusterGet(client kubeclientset.Interface, qualifiedName QualifiedName) (pkgruntime.Object, error) { - return client.CoreV1().ConfigMaps(qualifiedName.Namespace).Get(qualifiedName.Name, metav1.GetOptions{}) -} - -func (a *ConfigMapAdapter) ClusterList(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (pkgruntime.Object, error) { - return client.CoreV1().ConfigMaps(namespace).List(options) -} - -func (a *ConfigMapAdapter) ClusterUpdate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) { - configmap := obj.(*apiv1.ConfigMap) - return client.CoreV1().ConfigMaps(configmap.Namespace).Update(configmap) -} - -func (a *ConfigMapAdapter) ClusterWatch(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (watch.Interface, error) { - return client.CoreV1().ConfigMaps(namespace).Watch(options) -} - -func (a *ConfigMapAdapter) IsSchedulingAdapter() bool { - return false -} - -func (a *ConfigMapAdapter) NewTestObject(namespace string) pkgruntime.Object { - return &apiv1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: "test-configmap-", - Namespace: namespace, - }, - Data: map[string]string{ - "A": "ala ma kota", - }, - } -} diff --git a/federation/pkg/federatedtypes/crudtester/BUILD b/federation/pkg/federatedtypes/crudtester/BUILD deleted file mode 100644 index 508bd3bad36..00000000000 --- a/federation/pkg/federatedtypes/crudtester/BUILD +++ /dev/null @@ -1,33 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["crudtester.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federatedtypes/crudtester", - deps = [ - "//federation/pkg/federatedtypes:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/pkg/federatedtypes/crudtester/crudtester.go b/federation/pkg/federatedtypes/crudtester/crudtester.go deleted file mode 100644 index e0da833cee1..00000000000 --- a/federation/pkg/federatedtypes/crudtester/crudtester.go +++ /dev/null @@ -1,247 +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 crudtester - -import ( - "fmt" - "time" - - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - pkgruntime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/wait" - clientset "k8s.io/client-go/kubernetes" - "k8s.io/kubernetes/federation/pkg/federatedtypes" -) - -const ( - AnnotationTestFederationCRUDUpdate string = "federation.kubernetes.io/test-federation-crud-update" -) - -// TestLogger defines operations common across different types of testing -type TestLogger interface { - Fatalf(format string, args ...interface{}) - Fatal(msg string) - Logf(format string, args ...interface{}) -} - -// FederatedTypeCRUDTester exercises Create/Read/Update/Delete operations for -// federated types via the Federation API and validates that the -// results of those operations are propagated to clusters that are -// members of a federation. -type FederatedTypeCRUDTester struct { - tl TestLogger - adapter federatedtypes.FederatedTypeAdapter - kind string - clusterClients []clientset.Interface - waitInterval time.Duration - // Federation operations will use wait.ForeverTestTimeout. Any - // operation that involves member clusters may take longer due to - // propagation latency. - clusterWaitTimeout time.Duration -} - -func NewFederatedTypeCRUDTester(testLogger TestLogger, adapter federatedtypes.FederatedTypeAdapter, clusterClients []clientset.Interface, waitInterval, clusterWaitTimeout time.Duration) *FederatedTypeCRUDTester { - return &FederatedTypeCRUDTester{ - tl: testLogger, - adapter: adapter, - kind: adapter.Kind(), - clusterClients: clusterClients, - waitInterval: waitInterval, - clusterWaitTimeout: clusterWaitTimeout, - } -} - -func (c *FederatedTypeCRUDTester) CheckLifecycle(desiredObject pkgruntime.Object) { - obj := c.CheckCreate(desiredObject) - c.CheckUpdate(obj) - - // Validate the golden path - removal of dependents - orphanDependents := false - c.CheckDelete(obj, &orphanDependents) -} - -func (c *FederatedTypeCRUDTester) Create(desiredObject pkgruntime.Object) pkgruntime.Object { - namespace := c.adapter.ObjectMeta(desiredObject).Namespace - resourceMsg := fmt.Sprintf("federated %s", c.kind) - if len(namespace) > 0 { - resourceMsg = fmt.Sprintf("%s in namespace %q", resourceMsg, namespace) - } - - c.tl.Logf("Creating new %s", resourceMsg) - - obj, err := c.adapter.FedCreate(desiredObject) - if err != nil { - c.tl.Fatalf("Error creating %s: %v", resourceMsg, err) - } - - qualifiedName := c.adapter.QualifiedName(obj) - c.tl.Logf("Created new federated %s %q", c.kind, qualifiedName) - - return obj -} - -func (c *FederatedTypeCRUDTester) CheckCreate(desiredObject pkgruntime.Object) pkgruntime.Object { - obj := c.Create(desiredObject) - - c.CheckPropagation(obj) - - return obj -} - -func (c *FederatedTypeCRUDTester) CheckUpdate(obj pkgruntime.Object) { - qualifiedName := c.adapter.QualifiedName(obj) - - var initialAnnotation string - meta := c.adapter.ObjectMeta(obj) - if meta.Annotations != nil { - initialAnnotation = meta.Annotations[AnnotationTestFederationCRUDUpdate] - } - - c.tl.Logf("Updating federated %s %q", c.kind, qualifiedName) - updatedObj, err := c.updateFedObject(obj) - if err != nil { - c.tl.Fatalf("Error updating federated %s %q: %v", c.kind, qualifiedName, err) - } - - // updateFedObject is expected to have changed the value of the annotation - meta = c.adapter.ObjectMeta(updatedObj) - updatedAnnotation := meta.Annotations[AnnotationTestFederationCRUDUpdate] - if updatedAnnotation == initialAnnotation { - c.tl.Fatalf("Federated %s %q not mutated", c.kind, qualifiedName) - } - - c.CheckPropagation(updatedObj) -} - -func (c *FederatedTypeCRUDTester) CheckDelete(obj pkgruntime.Object, orphanDependents *bool) { - qualifiedName := c.adapter.QualifiedName(obj) - - c.tl.Logf("Deleting federated %s %q", c.kind, qualifiedName) - err := c.adapter.FedDelete(qualifiedName, &metav1.DeleteOptions{OrphanDependents: orphanDependents}) - if err != nil { - c.tl.Fatalf("Error deleting federated %s %q: %v", c.kind, qualifiedName, err) - } - - deletingInCluster := (orphanDependents != nil && *orphanDependents == false) - - waitTimeout := wait.ForeverTestTimeout - if deletingInCluster { - // May need extra time to delete both federation and cluster resources - waitTimeout = c.clusterWaitTimeout - } - - // Wait for deletion. The federation resource will only be removed once orphan deletion has been - // completed or deemed unnecessary. - err = wait.PollImmediate(c.waitInterval, waitTimeout, func() (bool, error) { - _, err := c.adapter.FedGet(qualifiedName) - if errors.IsNotFound(err) { - return true, nil - } - return false, err - }) - if err != nil { - c.tl.Fatalf("Error deleting federated %s %q: %v", c.kind, qualifiedName, err) - } - - var stateMsg string = "present" - if deletingInCluster { - stateMsg = "not present" - } - for _, client := range c.clusterClients { - _, err := c.adapter.ClusterGet(client, qualifiedName) - switch { - case !deletingInCluster && errors.IsNotFound(err): - c.tl.Fatalf("Federated %s %q was unexpectedly deleted from a member cluster", c.kind, qualifiedName) - case deletingInCluster && err == nil: - c.tl.Fatalf("Federated %s %q was unexpectedly orphaned in a member cluster", c.kind, qualifiedName) - case err != nil && !errors.IsNotFound(err): - c.tl.Fatalf("Error while checking whether %s %q is %s in member clusters: %v", c.kind, qualifiedName, stateMsg, err) - } - } -} - -// CheckPropagation checks propagation for the crud tester's clients -func (c *FederatedTypeCRUDTester) CheckPropagation(obj pkgruntime.Object) { - c.CheckPropagationForClients(obj, c.clusterClients, true) -} - -// CheckPropagationForClients checks propagation for the provided clients -func (c *FederatedTypeCRUDTester) CheckPropagationForClients(obj pkgruntime.Object, clusterClients []clientset.Interface, objExpected bool) { - qualifiedName := c.adapter.QualifiedName(obj) - - c.tl.Logf("Waiting for %s %q in %d clusters", c.kind, qualifiedName, len(clusterClients)) - for _, client := range clusterClients { - err := c.waitForResource(client, obj) - switch { - case err == wait.ErrWaitTimeout: - if objExpected { - c.tl.Fatalf("Timeout verifying %s %q in a member cluster: %v", c.kind, qualifiedName, err) - } - case err != nil: - c.tl.Fatalf("Failed to verify %s %q in a member cluster: %v", c.kind, qualifiedName, err) - case err == nil && !objExpected: - c.tl.Fatalf("Found unexpected object %s %q in a member cluster: %v", c.kind, qualifiedName, err) - } - } -} - -func (c *FederatedTypeCRUDTester) waitForResource(client clientset.Interface, obj pkgruntime.Object) error { - qualifiedName := c.adapter.QualifiedName(obj) - err := wait.PollImmediate(c.waitInterval, c.clusterWaitTimeout, func() (bool, error) { - equivalenceFunc := c.adapter.Equivalent - if c.adapter.IsSchedulingAdapter() { - schedulingAdapter, ok := c.adapter.(federatedtypes.SchedulingAdapter) - if !ok { - c.tl.Fatalf("Adapter for kind %q does not properly implement SchedulingAdapter.", c.adapter.Kind()) - } - equivalenceFunc = schedulingAdapter.EquivalentIgnoringSchedule - } - - clusterObj, err := c.adapter.ClusterGet(client, qualifiedName) - if err == nil && equivalenceFunc(clusterObj, obj) { - return true, nil - } - if errors.IsNotFound(err) { - return false, nil - } - return false, err - }) - return err -} - -func (c *FederatedTypeCRUDTester) updateFedObject(obj pkgruntime.Object) (pkgruntime.Object, error) { - err := wait.PollImmediate(c.waitInterval, wait.ForeverTestTimeout, func() (bool, error) { - // Target the metadata for simplicity (it's type-agnostic) - federatedtypes.SetAnnotation(c.adapter, obj, AnnotationTestFederationCRUDUpdate, "updated") - - _, err := c.adapter.FedUpdate(obj) - if errors.IsConflict(err) { - // The resource was updated by the federation controller. - // Get the latest version and retry. - qualifiedName := c.adapter.QualifiedName(obj) - obj, err = c.adapter.FedGet(qualifiedName) - return false, err - } - // Be tolerant of a slow server - if errors.IsServerTimeout(err) { - return false, nil - } - return (err == nil), err - }) - return obj, err -} diff --git a/federation/pkg/federatedtypes/daemonset.go b/federation/pkg/federatedtypes/daemonset.go deleted file mode 100644 index 156cdb420b8..00000000000 --- a/federation/pkg/federatedtypes/daemonset.go +++ /dev/null @@ -1,167 +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 federatedtypes - -import ( - "reflect" - - "k8s.io/api/core/v1" - extensionsv1 "k8s.io/api/extensions/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - pkgruntime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/watch" - kubeclientset "k8s.io/client-go/kubernetes" - restclient "k8s.io/client-go/rest" - federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - "k8s.io/kubernetes/federation/pkg/federation-controller/util" -) - -const ( - DaemonSetKind = "daemonset" - DaemonSetControllerName = "daemonsets" -) - -func init() { - RegisterFederatedType(DaemonSetKind, DaemonSetControllerName, []schema.GroupVersionResource{extensionsv1.SchemeGroupVersion.WithResource(DaemonSetControllerName)}, NewDaemonSetAdapter) -} - -type DaemonSetAdapter struct { - client federationclientset.Interface -} - -func NewDaemonSetAdapter(client federationclientset.Interface, config *restclient.Config, adapterSpecificArgs map[string]interface{}) FederatedTypeAdapter { - return &DaemonSetAdapter{client: client} -} - -func (a *DaemonSetAdapter) Kind() string { - return DaemonSetKind -} - -func (a *DaemonSetAdapter) ObjectType() pkgruntime.Object { - return &extensionsv1.DaemonSet{} -} - -func (a *DaemonSetAdapter) IsExpectedType(obj interface{}) bool { - _, ok := obj.(*extensionsv1.DaemonSet) - return ok -} - -func (a *DaemonSetAdapter) Copy(obj pkgruntime.Object) pkgruntime.Object { - daemonset := obj.(*extensionsv1.DaemonSet) - return &extensionsv1.DaemonSet{ - ObjectMeta: util.DeepCopyRelevantObjectMeta(daemonset.ObjectMeta), - Spec: *daemonset.Spec.DeepCopy(), - } -} - -func (a *DaemonSetAdapter) Equivalent(obj1, obj2 pkgruntime.Object) bool { - daemonset1 := obj1.(*extensionsv1.DaemonSet) - daemonset2 := obj2.(*extensionsv1.DaemonSet) - return util.ObjectMetaEquivalent(daemonset1.ObjectMeta, daemonset2.ObjectMeta) && reflect.DeepEqual(daemonset1.Spec, daemonset2.Spec) -} - -func (a *DaemonSetAdapter) QualifiedName(obj pkgruntime.Object) QualifiedName { - daemonset := obj.(*extensionsv1.DaemonSet) - return QualifiedName{Namespace: daemonset.Namespace, Name: daemonset.Name} -} - -func (a *DaemonSetAdapter) ObjectMeta(obj pkgruntime.Object) *metav1.ObjectMeta { - return &obj.(*extensionsv1.DaemonSet).ObjectMeta -} - -func (a *DaemonSetAdapter) FedCreate(obj pkgruntime.Object) (pkgruntime.Object, error) { - daemonset := obj.(*extensionsv1.DaemonSet) - return a.client.Extensions().DaemonSets(daemonset.Namespace).Create(daemonset) -} - -func (a *DaemonSetAdapter) FedDelete(qualifiedName QualifiedName, options *metav1.DeleteOptions) error { - return a.client.Extensions().DaemonSets(qualifiedName.Namespace).Delete(qualifiedName.Name, options) -} - -func (a *DaemonSetAdapter) FedGet(qualifiedName QualifiedName) (pkgruntime.Object, error) { - return a.client.Extensions().DaemonSets(qualifiedName.Namespace).Get(qualifiedName.Name, metav1.GetOptions{}) -} - -func (a *DaemonSetAdapter) FedList(namespace string, options metav1.ListOptions) (pkgruntime.Object, error) { - return a.client.Extensions().DaemonSets(namespace).List(options) -} - -func (a *DaemonSetAdapter) FedUpdate(obj pkgruntime.Object) (pkgruntime.Object, error) { - daemonset := obj.(*extensionsv1.DaemonSet) - return a.client.Extensions().DaemonSets(daemonset.Namespace).Update(daemonset) -} - -func (a *DaemonSetAdapter) FedWatch(namespace string, options metav1.ListOptions) (watch.Interface, error) { - return a.client.Extensions().DaemonSets(namespace).Watch(options) -} - -func (a *DaemonSetAdapter) ClusterCreate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) { - daemonset := obj.(*extensionsv1.DaemonSet) - return client.Extensions().DaemonSets(daemonset.Namespace).Create(daemonset) -} - -func (a *DaemonSetAdapter) ClusterDelete(client kubeclientset.Interface, qualifiedName QualifiedName, options *metav1.DeleteOptions) error { - return client.Extensions().DaemonSets(qualifiedName.Namespace).Delete(qualifiedName.Name, options) -} - -func (a *DaemonSetAdapter) ClusterGet(client kubeclientset.Interface, qualifiedName QualifiedName) (pkgruntime.Object, error) { - return client.Extensions().DaemonSets(qualifiedName.Namespace).Get(qualifiedName.Name, metav1.GetOptions{}) -} - -func (a *DaemonSetAdapter) ClusterList(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (pkgruntime.Object, error) { - return client.Extensions().DaemonSets(namespace).List(options) -} - -func (a *DaemonSetAdapter) ClusterUpdate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) { - daemonset := obj.(*extensionsv1.DaemonSet) - return client.Extensions().DaemonSets(daemonset.Namespace).Update(daemonset) -} - -func (a *DaemonSetAdapter) ClusterWatch(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (watch.Interface, error) { - return client.Extensions().DaemonSets(namespace).Watch(options) -} - -func (a *DaemonSetAdapter) IsSchedulingAdapter() bool { - return false -} - -func (a *DaemonSetAdapter) NewTestObject(namespace string) pkgruntime.Object { - return &extensionsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: "test-daemonset-", - Namespace: namespace, - Labels: map[string]string{"app": "test-daemonset"}, - }, - Spec: extensionsv1.DaemonSetSpec{ - Template: v1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{"name": "test-pod"}, - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: "test-daemonset", - Image: "images/test-daemonset", - Ports: []v1.ContainerPort{{ContainerPort: 9376}}, - }, - }, - }, - }, - }, - } -} diff --git a/federation/pkg/federatedtypes/deployment.go b/federation/pkg/federatedtypes/deployment.go deleted file mode 100644 index cc6eccbd70b..00000000000 --- a/federation/pkg/federatedtypes/deployment.go +++ /dev/null @@ -1,189 +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 federatedtypes - -import ( - apiv1 "k8s.io/api/core/v1" - extensionsv1 "k8s.io/api/extensions/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - pkgruntime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/watch" - kubeclientset "k8s.io/client-go/kubernetes" - restclient "k8s.io/client-go/rest" - federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - fedutil "k8s.io/kubernetes/federation/pkg/federation-controller/util" -) - -const ( - DeploymentKind = "deployment" - DeploymentControllerName = "deployments" - FedDeploymentPreferencesAnnotation = "federation.kubernetes.io/deployment-preferences" -) - -func init() { - RegisterFederatedType(DeploymentKind, DeploymentControllerName, []schema.GroupVersionResource{extensionsv1.SchemeGroupVersion.WithResource(DeploymentControllerName)}, NewDeploymentAdapter) -} - -type DeploymentAdapter struct { - *replicaSchedulingAdapter - client federationclientset.Interface -} - -func NewDeploymentAdapter(client federationclientset.Interface, config *restclient.Config, adapterSpecificArgs map[string]interface{}) FederatedTypeAdapter { - schedulingAdapter := replicaSchedulingAdapter{ - preferencesAnnotationName: FedDeploymentPreferencesAnnotation, - updateStatusFunc: func(obj pkgruntime.Object, schedulingInfo interface{}) error { - deployment := obj.(*extensionsv1.Deployment) - typedStatus := schedulingInfo.(*ReplicaSchedulingInfo).Status - if typedStatus.Replicas != deployment.Status.Replicas || typedStatus.UpdatedReplicas != deployment.Status.UpdatedReplicas || - typedStatus.ReadyReplicas != deployment.Status.ReadyReplicas || typedStatus.AvailableReplicas != deployment.Status.AvailableReplicas { - deployment.Status = extensionsv1.DeploymentStatus{ - Replicas: typedStatus.Replicas, - UpdatedReplicas: typedStatus.UpdatedReplicas, - ReadyReplicas: typedStatus.ReadyReplicas, - AvailableReplicas: typedStatus.AvailableReplicas, - } - _, err := client.Extensions().Deployments(deployment.Namespace).UpdateStatus(deployment) - return err - } - return nil - }, - } - - return &DeploymentAdapter{&schedulingAdapter, client} -} - -func (a *DeploymentAdapter) Kind() string { - return DeploymentKind -} - -func (a *DeploymentAdapter) ObjectType() pkgruntime.Object { - return &extensionsv1.Deployment{} -} - -func (a *DeploymentAdapter) IsExpectedType(obj interface{}) bool { - _, ok := obj.(*extensionsv1.Deployment) - return ok -} - -func (a *DeploymentAdapter) Copy(obj pkgruntime.Object) pkgruntime.Object { - deployment := obj.(*extensionsv1.Deployment) - return fedutil.DeepCopyDeployment(deployment) -} - -func (a *DeploymentAdapter) Equivalent(obj1, obj2 pkgruntime.Object) bool { - deployment1 := obj1.(*extensionsv1.Deployment) - deployment2 := obj2.(*extensionsv1.Deployment) - return fedutil.DeploymentEquivalent(deployment1, deployment2) -} - -func (a *DeploymentAdapter) QualifiedName(obj pkgruntime.Object) QualifiedName { - deployment := obj.(*extensionsv1.Deployment) - return QualifiedName{Namespace: deployment.Namespace, Name: deployment.Name} -} - -func (a *DeploymentAdapter) ObjectMeta(obj pkgruntime.Object) *metav1.ObjectMeta { - return &obj.(*extensionsv1.Deployment).ObjectMeta -} - -func (a *DeploymentAdapter) FedCreate(obj pkgruntime.Object) (pkgruntime.Object, error) { - deployment := obj.(*extensionsv1.Deployment) - return a.client.Extensions().Deployments(deployment.Namespace).Create(deployment) -} - -func (a *DeploymentAdapter) FedDelete(qualifiedName QualifiedName, options *metav1.DeleteOptions) error { - return a.client.Extensions().Deployments(qualifiedName.Namespace).Delete(qualifiedName.Name, options) -} - -func (a *DeploymentAdapter) FedGet(qualifiedName QualifiedName) (pkgruntime.Object, error) { - return a.client.Extensions().Deployments(qualifiedName.Namespace).Get(qualifiedName.Name, metav1.GetOptions{}) -} - -func (a *DeploymentAdapter) FedList(namespace string, options metav1.ListOptions) (pkgruntime.Object, error) { - return a.client.Extensions().Deployments(namespace).List(options) -} - -func (a *DeploymentAdapter) FedUpdate(obj pkgruntime.Object) (pkgruntime.Object, error) { - deployment := obj.(*extensionsv1.Deployment) - return a.client.Extensions().Deployments(deployment.Namespace).Update(deployment) -} - -func (a *DeploymentAdapter) FedWatch(namespace string, options metav1.ListOptions) (watch.Interface, error) { - return a.client.Extensions().Deployments(namespace).Watch(options) -} - -func (a *DeploymentAdapter) ClusterCreate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) { - deployment := obj.(*extensionsv1.Deployment) - return client.Extensions().Deployments(deployment.Namespace).Create(deployment) -} - -func (a *DeploymentAdapter) ClusterDelete(client kubeclientset.Interface, qualifiedName QualifiedName, options *metav1.DeleteOptions) error { - return client.Extensions().Deployments(qualifiedName.Namespace).Delete(qualifiedName.Name, options) -} - -func (a *DeploymentAdapter) ClusterGet(client kubeclientset.Interface, qualifiedName QualifiedName) (pkgruntime.Object, error) { - return client.Extensions().Deployments(qualifiedName.Namespace).Get(qualifiedName.Name, metav1.GetOptions{}) -} - -func (a *DeploymentAdapter) ClusterList(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (pkgruntime.Object, error) { - return client.Extensions().Deployments(namespace).List(options) -} - -func (a *DeploymentAdapter) ClusterUpdate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) { - deployment := obj.(*extensionsv1.Deployment) - return client.Extensions().Deployments(deployment.Namespace).Update(deployment) -} - -func (a *DeploymentAdapter) ClusterWatch(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (watch.Interface, error) { - return client.Extensions().Deployments(namespace).Watch(options) -} - -func (a *DeploymentAdapter) EquivalentIgnoringSchedule(obj1, obj2 pkgruntime.Object) bool { - deployment1 := obj1.(*extensionsv1.Deployment) - deployment2 := a.Copy(obj2).(*extensionsv1.Deployment) - deployment2.Spec.Replicas = deployment1.Spec.Replicas - return fedutil.DeploymentEquivalent(deployment1, deployment2) -} - -func (a *DeploymentAdapter) NewTestObject(namespace string) pkgruntime.Object { - replicas := int32(3) - zero := int64(0) - return &extensionsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: "test-deployment-", - Namespace: namespace, - }, - Spec: extensionsv1.DeploymentSpec{ - Replicas: &replicas, - Template: apiv1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{"foo": "bar"}, - }, - Spec: apiv1.PodSpec{ - TerminationGracePeriodSeconds: &zero, - Containers: []apiv1.Container{ - { - Name: "nginx", - Image: "nginx", - }, - }, - }, - }, - }, - } -} diff --git a/federation/pkg/federatedtypes/hpa.go b/federation/pkg/federatedtypes/hpa.go deleted file mode 100644 index dbba7b5b0aa..00000000000 --- a/federation/pkg/federatedtypes/hpa.go +++ /dev/null @@ -1,1015 +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 federatedtypes - -import ( - "fmt" - "time" - - autoscalingv1 "k8s.io/api/autoscaling/v1" - "k8s.io/api/extensions/v1beta1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - pkgruntime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/watch" - kubeclientset "k8s.io/client-go/kubernetes" - restclient "k8s.io/client-go/rest" - federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" - federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - fedutil "k8s.io/kubernetes/federation/pkg/federation-controller/util" - hpautil "k8s.io/kubernetes/federation/pkg/federation-controller/util/hpa" - extensionsinternal "k8s.io/kubernetes/pkg/apis/extensions" - - "github.com/golang/glog" -) - -const ( - HpaKind = "horizontalpodautoscaler" - HpaControllerName = "horizontalpodautoscalers" - // This is used as the default min for hpa object submitted - // to federation, in a situation where the default is for - // some reason not present (Spec.MinReplicas == nil) - hpaMinReplicaDefault = int32(1) - // This is a tunable which does not change replica nums - // on an existing local hpa, before this timeout, if it - // did scale already (avoids thrashing of replicas around). - ScaleForbiddenWindow = 2 * time.Minute -) - -func init() { - RegisterFederatedType(HpaKind, HpaControllerName, []schema.GroupVersionResource{autoscalingv1.SchemeGroupVersion.WithResource(HpaControllerName)}, NewHpaAdapter) -} - -type HpaAdapter struct { - client federationclientset.Interface - scaleForbiddenWindow time.Duration -} - -func NewHpaAdapter(client federationclientset.Interface, config *restclient.Config, adapterSpecificArgs map[string]interface{}) FederatedTypeAdapter { - var scaleForbiddenWindow time.Duration - if adapterSpecificArgs != nil && adapterSpecificArgs[HpaKind] != nil { - scaleForbiddenWindow = adapterSpecificArgs[HpaKind].(*metav1.Duration).Duration - } else { - scaleForbiddenWindow = ScaleForbiddenWindow - } - - return &HpaAdapter{ - client: client, - scaleForbiddenWindow: scaleForbiddenWindow, - } -} - -func (a *HpaAdapter) Kind() string { - return HpaKind -} - -func (a *HpaAdapter) ObjectType() pkgruntime.Object { - return &autoscalingv1.HorizontalPodAutoscaler{} -} - -func (a *HpaAdapter) IsExpectedType(obj interface{}) bool { - _, ok := obj.(*autoscalingv1.HorizontalPodAutoscaler) - return ok -} - -func (a *HpaAdapter) Copy(obj pkgruntime.Object) pkgruntime.Object { - hpa := obj.(*autoscalingv1.HorizontalPodAutoscaler) - return &autoscalingv1.HorizontalPodAutoscaler{ - ObjectMeta: fedutil.DeepCopyRelevantObjectMeta(hpa.ObjectMeta), - Spec: *hpa.Spec.DeepCopy(), - } -} - -func (a *HpaAdapter) Equivalent(obj1, obj2 pkgruntime.Object) bool { - return fedutil.ObjectMetaAndSpecEquivalent(obj1, obj2) -} - -func (a *HpaAdapter) QualifiedName(obj pkgruntime.Object) QualifiedName { - hpa := obj.(*autoscalingv1.HorizontalPodAutoscaler) - return QualifiedName{Namespace: hpa.Namespace, Name: hpa.Name} -} - -func (a *HpaAdapter) ObjectMeta(obj pkgruntime.Object) *metav1.ObjectMeta { - return &obj.(*autoscalingv1.HorizontalPodAutoscaler).ObjectMeta -} - -func (a *HpaAdapter) FedCreate(obj pkgruntime.Object) (pkgruntime.Object, error) { - hpa := obj.(*autoscalingv1.HorizontalPodAutoscaler) - return a.client.AutoscalingV1().HorizontalPodAutoscalers(hpa.Namespace).Create(hpa) -} - -func (a *HpaAdapter) FedDelete(qualifiedName QualifiedName, options *metav1.DeleteOptions) error { - return a.client.AutoscalingV1().HorizontalPodAutoscalers(qualifiedName.Namespace).Delete(qualifiedName.Name, options) -} - -func (a *HpaAdapter) FedGet(qualifiedName QualifiedName) (pkgruntime.Object, error) { - return a.client.AutoscalingV1().HorizontalPodAutoscalers(qualifiedName.Namespace).Get(qualifiedName.Name, metav1.GetOptions{}) -} - -func (a *HpaAdapter) FedList(namespace string, options metav1.ListOptions) (pkgruntime.Object, error) { - return a.client.AutoscalingV1().HorizontalPodAutoscalers(namespace).List(options) -} - -func (a *HpaAdapter) FedUpdate(obj pkgruntime.Object) (pkgruntime.Object, error) { - hpa := obj.(*autoscalingv1.HorizontalPodAutoscaler) - return a.client.AutoscalingV1().HorizontalPodAutoscalers(hpa.Namespace).Update(hpa) -} - -func (a *HpaAdapter) FedWatch(namespace string, options metav1.ListOptions) (watch.Interface, error) { - return a.client.AutoscalingV1().HorizontalPodAutoscalers(namespace).Watch(options) -} - -func (a *HpaAdapter) ClusterCreate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) { - hpa := obj.(*autoscalingv1.HorizontalPodAutoscaler) - return client.AutoscalingV1().HorizontalPodAutoscalers(hpa.Namespace).Create(hpa) -} - -func (a *HpaAdapter) ClusterDelete(client kubeclientset.Interface, qualifiedName QualifiedName, options *metav1.DeleteOptions) error { - return client.AutoscalingV1().HorizontalPodAutoscalers(qualifiedName.Namespace).Delete(qualifiedName.Name, options) -} - -func (a *HpaAdapter) ClusterGet(client kubeclientset.Interface, qualifiedName QualifiedName) (pkgruntime.Object, error) { - return client.AutoscalingV1().HorizontalPodAutoscalers(qualifiedName.Namespace).Get(qualifiedName.Name, metav1.GetOptions{}) -} - -func (a *HpaAdapter) ClusterList(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (pkgruntime.Object, error) { - return client.AutoscalingV1().HorizontalPodAutoscalers(namespace).List(options) -} - -func (a *HpaAdapter) ClusterUpdate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) { - hpa := obj.(*autoscalingv1.HorizontalPodAutoscaler) - return client.AutoscalingV1().HorizontalPodAutoscalers(hpa.Namespace).Update(hpa) -} - -func (a *HpaAdapter) ClusterWatch(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (watch.Interface, error) { - return client.AutoscalingV1().HorizontalPodAutoscalers(namespace).Watch(options) -} - -func (a *HpaAdapter) NewTestObject(namespace string) pkgruntime.Object { - var min int32 = 4 - var targetCPU int32 = 70 - return &autoscalingv1.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: "test-hpa-", - Namespace: namespace, - }, - Spec: autoscalingv1.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{ - Kind: "ReplicaSet", - Name: "myrs", - }, - MinReplicas: &min, - MaxReplicas: int32(10), - TargetCPUUtilizationPercentage: &targetCPU, - }, - } -} - -func (a *HpaAdapter) IsSchedulingAdapter() bool { - return true -} - -func (a *HpaAdapter) EquivalentIgnoringSchedule(obj1, obj2 pkgruntime.Object) bool { - hpa1 := obj1.(*autoscalingv1.HorizontalPodAutoscaler) - hpa2 := a.Copy(obj2).(*autoscalingv1.HorizontalPodAutoscaler) - if hpa1.Spec.MinReplicas == nil { - hpa2.Spec.MinReplicas = nil - } else if hpa2.Spec.MinReplicas == nil { - var r int32 = *hpa1.Spec.MinReplicas - hpa2.Spec.MinReplicas = &r - } else { - *hpa2.Spec.MinReplicas = *hpa1.Spec.MinReplicas - } - hpa2.Spec.MaxReplicas = hpa1.Spec.MaxReplicas - return fedutil.ObjectMetaAndSpecEquivalent(hpa1, hpa2) -} - -type replicaNums struct { - min int32 - max int32 -} - -type hpaFederatedStatus struct { - lastScaleTime *metav1.Time - // Indicates how many clusters have hpa/replicas. - // Used to average the cpu utilization which is - // reflected to the federation user. - count int32 - aggregateCPUUtilizationPercentage *int32 - currentReplicas int32 - desiredReplicas int32 -} - -type hpaSchedulingInfo struct { - scheduleState map[string]*replicaNums - fedStatus hpaFederatedStatus -} - -// List of cluster names. -type hpaLists struct { - // Stores names of those clusters which can offer min. - availableMin sets.String - // Stores names of those clusters which can offer max. - availableMax sets.String - // Stores names of those clusters which do not have hpa yet. - noHpa sets.String -} - -func (a *HpaAdapter) GetSchedule(obj pkgruntime.Object, key string, clusters []*federationapi.Cluster, informer fedutil.FederatedInformer) (interface{}, error) { - currentClusterObjs, err := getCurrentClusterObjs(informer, key, clusters) - if err != nil { - return nil, err - } - - // Initialise averaged cpu utilisation for this reconcile. - var ccup int32 = 0 - fedStatus := hpaFederatedStatus{ - aggregateCPUUtilizationPercentage: &ccup, - count: int32(0), - desiredReplicas: int32(0), - currentReplicas: int32(0), - } - fedHpa := obj.(*autoscalingv1.HorizontalPodAutoscaler) - // We assign the last known scale time here, which we update with - // the latest time from among all clusters in ScheduleObject() - if fedHpa.Status.LastScaleTime != nil { - t := metav1.NewTime(fedHpa.Status.LastScaleTime.Time) - fedStatus.lastScaleTime = &t - } - - return &hpaSchedulingInfo{ - scheduleState: a.getHpaScheduleState(obj, currentClusterObjs), - fedStatus: fedStatus, - }, nil -} - -func getCurrentClusterObjs(informer fedutil.FederatedInformer, key string, clusters []*federationapi.Cluster) (map[string]pkgruntime.Object, error) { - currentClusterObjs := make(map[string]pkgruntime.Object) - for _, cluster := range clusters { - clusterName := cluster.Name - clusterObj, found, err := informer.GetTargetStore().GetByKey(clusterName, key) - if err != nil { - return nil, err - } - currentClusterObjs[clusterName] = nil - if found { - currentClusterObjs[clusterName] = clusterObj.(pkgruntime.Object) - } - } - return currentClusterObjs, nil -} - -// The algorithm used for scheduling is briefed as below: -// -// 1. Find clusters which can offer max and min, if any (lists.availableMax and -// lists.availableMin) in one pass on all clusters. -// -// 2. Reduce the replicas (both min and max) if needed (situation when fedHpa -// has lesser replicas then all cluster local hpa replicas totalled together). -// In this step reduce first from those hpas which already have max (and min) -// reducible. Once such clusters are over and reduction still needed, reduce -// one at a time from all clusters, randomly. This step will ensure that the -// exceeding replicas in local hpas are reduced to match the fedHpa. -// This step would ideally be a noop in most cases because its rare that fedHpa -// would have lesser replicas then the cluster local total (probably when user -// forces update if fedHpa). -// -// 3. Distribute the replicas. In this step we have replicas to distribute (which -// are fed replicas exceeding the sum total of local cluster replicas). If clusters -// already have replicas, one replica from each cluster which can offer replicas -// (both for max and min) are also added to this replicas to distribute numbers (min -// and max). -// 3a. We first do a sub-pass to distribute to clusters which need replicas, considering -// those as clusters in crucial need of replicas. -// 3b. After previous sub-pass, if we still have replicas remaining, in the sub-pass -// we distribute to those clusters which do not yet have any hpa. -// 3c. After previous if we still have more to distribute, then we distribute to all -// clusters randomly, giving replica distribution count (rdc=total-fed-replicas/no-of-clusters) -// to each at a time. -// -// The above algorithm is run to first distribute max and then distribute min to those clusters -// which get max. -func (a *HpaAdapter) getHpaScheduleState(fedObj pkgruntime.Object, currentObjs map[string]pkgruntime.Object) map[string]*replicaNums { - fedHpa := fedObj.(*autoscalingv1.HorizontalPodAutoscaler) - requestedMin := hpaMinReplicaDefault - if fedHpa.Spec.MinReplicas != nil { - requestedMin = *fedHpa.Spec.MinReplicas - } - requestedReplicas := replicaNums{ - min: requestedMin, - max: fedHpa.Spec.MaxReplicas, - } - // replica distribution count, per cluster - rdc := replicaNums{ - min: requestedReplicas.min / int32(len(currentObjs)), - max: requestedReplicas.max / int32(len(currentObjs)), - } - if rdc.min < 1 { - rdc.min = 1 - } - // TODO: Is there a better way? - // We need to cap the lowest limit of Max to 2, because in a - // situation like both min and max become 1 (same) for all clusters, - // no rebalancing would happen. - if rdc.max < 2 { - rdc.max = 2 - } - - // Pass 1: Analyse existing local hpa's if any. - // clusterLists holds the list of those clusters which can offer - // min and max replicas, to those which want them. - // For example new clusters joining the federation and/or - // those clusters which need to increase or reduce replicas - // beyond min/max limits. - // schedStatus currently have status of existing hpas. - // It will eventually have desired status for this reconcile. - clusterLists, currentReplicas, scheduleState := a.prepareForScheduling(currentObjs) - - remainingReplicas := replicaNums{ - min: requestedReplicas.min - currentReplicas.min, - max: requestedReplicas.max - currentReplicas.max, - } - - // Pass 2: reduction of replicas if needed ( situation that fedHpa updated replicas - // to lesser then existing). - // In this pass, we remain pessimistic and reduce one replica per cluster at a time. - if remainingReplicas.min < 0 { - excessMin := (remainingReplicas.min * int32(-1)) - remainingReplicas.min = reduceMinReplicas(excessMin, clusterLists.availableMin, scheduleState) - } - if remainingReplicas.max < 0 { - excessMax := (remainingReplicas.max * int32(-1)) - remainingReplicas.max = reduceMaxReplicas(excessMax, clusterLists.availableMax, scheduleState) - } - - toDistribute := replicaNums{ - min: remainingReplicas.min + int32(clusterLists.availableMin.Len()), - max: remainingReplicas.max + int32(clusterLists.availableMax.Len()), - } - - // Pass 3: Distribute Max and then Min. - // Here we first distribute max and then (in the next loop) - // distribute min into those clusters which already get the - // max fixed. - // In this process we might not meet the min limit and total of - // min limits might remain more then the requested federated min. - // This is partially because a min per cluster cannot be lesser - // then 1, but min could be requested as 1 at federation. - // Additionally we first increase replicas into those clusters - // which already have hpa's and are in a condition to increase. - // This will save cluster related resources for the user, such that - // if an already existing cluster can satisfy users request why send - // the workload to another. - // We then go ahead to give the replicas to those which do not - // have any hpa. In this pass however we try to ensure that all - // our Max are consumed in this reconcile. - a.distributeMaxReplicas(toDistribute.max, clusterLists, rdc, currentObjs, scheduleState) - - // We distribute min to those clusters which: - // 1 - can adjust min (our increase step would be only 1) - // 2 - which do not have this hpa and got max(increase step rdcMin) - // We might exhaust all min replicas here, with - // some clusters still needing them. We adjust this in finalise by - // assigning min replicas to 1 into those clusters which got max - // but min remains 0. - a.distributeMinReplicas(toDistribute.min, clusterLists, rdc, currentObjs, scheduleState) - - return finaliseScheduleState(scheduleState) -} - -func (a *HpaAdapter) ScheduleObject(cluster *federationapi.Cluster, clusterObj pkgruntime.Object, federationObjCopy pkgruntime.Object, schedulingInfo interface{}) (pkgruntime.Object, ScheduleAction, error) { - // Update federated status info - typedInfo := schedulingInfo.(*hpaSchedulingInfo) - if clusterObj != nil { - clusterHpa := clusterObj.(*autoscalingv1.HorizontalPodAutoscaler) - if clusterHpa.Status.CurrentCPUUtilizationPercentage != nil { - *typedInfo.fedStatus.aggregateCPUUtilizationPercentage += - (*clusterHpa.Status.CurrentCPUUtilizationPercentage * clusterHpa.Status.CurrentReplicas) - typedInfo.fedStatus.count += clusterHpa.Status.CurrentReplicas - } - if clusterHpa.Status.LastScaleTime != nil { - t := metav1.NewTime(clusterHpa.Status.LastScaleTime.Time) - if typedInfo.fedStatus.lastScaleTime != nil && - t.After(typedInfo.fedStatus.lastScaleTime.Time) { - typedInfo.fedStatus.lastScaleTime = &t - } - } - - typedInfo.fedStatus.currentReplicas += clusterHpa.Status.CurrentReplicas - typedInfo.fedStatus.desiredReplicas += clusterHpa.Status.DesiredReplicas - } - - // Update the cluster obj and the needed action on the cluster - clusterHpaState := typedInfo.scheduleState[cluster.Name] - desiredHpa := federationObjCopy.(*autoscalingv1.HorizontalPodAutoscaler) - if clusterHpaState != nil { - desiredHpa.Spec.MaxReplicas = clusterHpaState.max - if desiredHpa.Spec.MinReplicas == nil { - min := int32(0) - desiredHpa.Spec.MinReplicas = &min - } - *desiredHpa.Spec.MinReplicas = clusterHpaState.min - } - - var defaultAction ScheduleAction = "" - switch { - case clusterHpaState != nil && clusterObj != nil: - return desiredHpa, defaultAction, nil - case clusterHpaState != nil && clusterObj == nil: - return desiredHpa, ActionAdd, nil - case clusterHpaState == nil && clusterObj != nil: - return nil, ActionDelete, nil - } - return nil, defaultAction, nil -} - -func (a *HpaAdapter) UpdateFederatedStatus(obj pkgruntime.Object, schedulingInfo interface{}) error { - fedHpa := obj.(*autoscalingv1.HorizontalPodAutoscaler) - needUpdate, newFedHpaStatus := updateStatus(fedHpa, schedulingInfo.(*hpaSchedulingInfo).fedStatus) - if needUpdate { - fedHpa.Status = newFedHpaStatus - _, err := a.client.AutoscalingV1().HorizontalPodAutoscalers(fedHpa.Namespace).UpdateStatus(fedHpa) - if err != nil { - return fmt.Errorf("Error updating hpa: %s status in federation: %v", fedHpa.Name, err) - } - } - - if err := a.updateClusterListOnTargetObject(fedHpa, schedulingInfo.(*hpaSchedulingInfo).scheduleState); err != nil { - return fmt.Errorf("Error updating cluster list on object targetted by hpa: %s: %v", fedHpa.Name, err) - } - return nil -} - -func updateStatus(fedHpa *autoscalingv1.HorizontalPodAutoscaler, newStatus hpaFederatedStatus) (bool, autoscalingv1.HorizontalPodAutoscalerStatus) { - averageCPUUtilizationPercentage := int32(0) - // Average out the available current utilisation - if *newStatus.aggregateCPUUtilizationPercentage != 0 && newStatus.count != 0 { - averageCPUUtilizationPercentage = *newStatus.aggregateCPUUtilizationPercentage / newStatus.count - } - gen := fedHpa.Generation - newFedHpaStatus := autoscalingv1.HorizontalPodAutoscalerStatus{ObservedGeneration: &gen} - needUpdate := false - if (fedHpa.Status.CurrentCPUUtilizationPercentage == nil && - averageCPUUtilizationPercentage != 0) || - (fedHpa.Status.CurrentCPUUtilizationPercentage != nil && - averageCPUUtilizationPercentage != - *fedHpa.Status.CurrentCPUUtilizationPercentage) { - needUpdate = true - newFedHpaStatus.CurrentCPUUtilizationPercentage = &averageCPUUtilizationPercentage - } - if (fedHpa.Status.LastScaleTime == nil && newStatus.lastScaleTime != nil) || - (fedHpa.Status.LastScaleTime != nil && newStatus.lastScaleTime == nil) || - ((fedHpa.Status.LastScaleTime != nil && newStatus.lastScaleTime != nil) && - newStatus.lastScaleTime.After(fedHpa.Status.LastScaleTime.Time)) { - needUpdate = true - newFedHpaStatus.LastScaleTime = newStatus.lastScaleTime - } - if fedHpa.Status.DesiredReplicas != newStatus.desiredReplicas { - needUpdate = true - newFedHpaStatus.CurrentReplicas = newStatus.currentReplicas - } - if fedHpa.Status.CurrentReplicas != newStatus.currentReplicas { - needUpdate = true - newFedHpaStatus.DesiredReplicas = newStatus.desiredReplicas - } - return needUpdate, newFedHpaStatus -} - -// prepareForScheduling prepares the lists and totals from the -// existing objs. -// currentObjs has the list of all clusters, with obj as nil -// for those clusters which do not have hpa yet. -func (a *HpaAdapter) prepareForScheduling(currentObjs map[string]pkgruntime.Object) (hpaLists, replicaNums, map[string]*replicaNums) { - lists := hpaLists{ - availableMax: sets.NewString(), - availableMin: sets.NewString(), - noHpa: sets.NewString(), - } - existingTotal := replicaNums{ - min: int32(0), - max: int32(0), - } - - scheduleState := make(map[string]*replicaNums) - for cluster, obj := range currentObjs { - if obj == nil { - lists.noHpa.Insert(cluster) - scheduleState[cluster] = nil - continue - } - - if a.maxReplicasReducible(obj) { - lists.availableMax.Insert(cluster) - } - if a.minReplicasReducible(obj) { - lists.availableMin.Insert(cluster) - } - - replicas := replicaNums{min: 0, max: 0} - scheduleState[cluster] = &replicas - if obj.(*autoscalingv1.HorizontalPodAutoscaler).Spec.MinReplicas != nil { - existingTotal.min += *obj.(*autoscalingv1.HorizontalPodAutoscaler).Spec.MinReplicas - replicas.min = *obj.(*autoscalingv1.HorizontalPodAutoscaler).Spec.MinReplicas - } - existingTotal.max += obj.(*autoscalingv1.HorizontalPodAutoscaler).Spec.MaxReplicas - replicas.max = obj.(*autoscalingv1.HorizontalPodAutoscaler).Spec.MaxReplicas - } - - return lists, existingTotal, scheduleState -} - -// Note: reduceMinReplicas and reduceMaxReplicas, look quite similar in flow -// and code, however there are subtle differences. They together can be made -// into 1 function with an arg governing the functionality difference and -// additional args (superset of args in both) as needed. Doing so however -// makes the logical flow quite less readable. They are thus left as 2 for -// readability. - -// reduceMinReplicas reduces the min replicas from existing clusters. -// At the end of the function excessMin should be 0 and the MinList -// and the scheduledReplicas properly updated in place. -func reduceMinReplicas(excessMin int32, availableMinList sets.String, scheduled map[string]*replicaNums) int32 { - if excessMin > 0 { - // first we try reducing from those clusters which already offer min - if availableMinList.Len() > 0 { - for _, cluster := range availableMinList.List() { - replicas := scheduled[cluster] - if replicas.min > 1 { - replicas.min-- - availableMinList.Delete(cluster) - excessMin-- - if excessMin <= 0 { - break - } - } - } - } - } - - // If we could not get needed replicas from already offered min above - // we abruptly start removing replicas from some/all clusters. - // Here we might make some min to 0 signalling that this hpa might be a - // candidate to be removed from this cluster altogether. - for excessMin > 0 { - for _, replicas := range scheduled { - if replicas != nil && - replicas.min > 0 { - replicas.min-- - excessMin-- - if excessMin <= 0 { - break - } - } - } - } - - return excessMin -} - -// reduceMaxReplicas reduces the max replicas from existing clusters. -// At the end of the function excessMax should be 0 and the MaxList -// and the scheduledReplicas properly updated in place. -func reduceMaxReplicas(excessMax int32, availableMaxList sets.String, scheduled map[string]*replicaNums) int32 { - if excessMax > 0 { - // first we try reducing from those clusters which already offer max - if availableMaxList.Len() > 0 { - for _, cluster := range availableMaxList.List() { - replicas := scheduled[cluster] - if replicas != nil && !((replicas.max - replicas.min) < 0) { - replicas.max-- - availableMaxList.Delete(cluster) - excessMax-- - if excessMax <= 0 { - break - } - } - } - } - } - // If we could not get needed replicas to reduce from already offered - // max above we abruptly start removing replicas from some/all clusters. - // Here we might make some max and min to 0, signalling that this hpa be - // removed from this cluster altogether - for excessMax > 0 { - for _, replicas := range scheduled { - if replicas != nil && - !((replicas.max - replicas.min) < 0) { - replicas.max-- - excessMax-- - if excessMax <= 0 { - break - } - } - } - } - - return excessMax -} - -// distributeMaxReplicas -// Takes input: -// toDistributeMax: number of replicas to distribute. -// lists: cluster name lists, which have clusters with available max, -// available min and those with no hpas yet. -// rdc: replicadistributioncount for max and min. -// currentObjs: list of current cluster hpas. -// scheduled: schedule state which will be updated in place. -func (a *HpaAdapter) distributeMaxReplicas(toDistributeMax int32, lists hpaLists, rdc replicaNums, - currentObjs map[string]pkgruntime.Object, scheduled map[string]*replicaNums) int32 { - for cluster, replicas := range scheduled { - if toDistributeMax == 0 { - break - } - if replicas == nil { - continue - } - if a.maxReplicasNeeded(currentObjs[cluster]) { - replicas.max++ - if lists.availableMax.Len() > 0 { - popped, notEmpty := lists.availableMax.PopAny() - if notEmpty { - // Boundary checks have happened earlier in - // minReplicasReducible(). - scheduled[popped].max-- - } - } - // Any which ways utilise available map replicas - toDistributeMax-- - } - } - - // If we have new clusters where we can give our replicas, - // then give away all our replicas to the new clusters first. - if lists.noHpa.Len() > 0 { - for toDistributeMax > 0 { - for _, cluster := range lists.noHpa.UnsortedList() { - if scheduled[cluster] == nil { - scheduled[cluster] = &replicaNums{min: 0, max: 0} - } - replicas := scheduled[cluster] - // first give away max from clusters offering them - // this case especially helps getting hpa into newly joining - // clusters. - if lists.availableMax.Len() > 0 { - popped, notEmpty := lists.availableMax.PopAny() - if notEmpty { - // Boundary checks to reduce max have happened earlier in - // minReplicasReducible(). - replicas.max++ - scheduled[popped].max-- - toDistributeMax-- - continue - } - } - if toDistributeMax < rdc.max { - replicas.max += toDistributeMax - toDistributeMax = 0 - break - } - replicas.max += rdc.max - toDistributeMax -= rdc.max - } - } - } else { // we have no new clusters but if still have max replicas to distribute; - // just distribute all in current clusters. - for toDistributeMax > 0 { - for cluster, replicas := range scheduled { - if replicas == nil { - replicas = &replicaNums{min: 0, max: 0} - scheduled[cluster] = replicas - } - // First give away max from clusters offering them. - // This case especially helps getting hpa into newly joining - // clusters. - if lists.availableMax.Len() > 0 { - popped, notEmpty := lists.availableMax.PopAny() - if notEmpty { - // Boundary checks have happened earlier in - // minReplicasReducible(). - replicas.max++ - scheduled[popped].max-- - toDistributeMax-- - continue - } - } - if toDistributeMax < rdc.max { - replicas.max += toDistributeMax - toDistributeMax = 0 - break - } - replicas.max += rdc.max - toDistributeMax -= rdc.max - } - } - } - return toDistributeMax -} - -// distributeMinReplicas -// Takes input: -// toDistributeMin: number of replicas to distribute. -// lists: cluster name lists, which have clusters with available max, -// available min and those with no hpas yet. -// rdc: replicadistributioncount for max and min. -// currentObjs: list of current cluster hpas. -// scheduled: schedule state which will be updated in place. -func (a *HpaAdapter) distributeMinReplicas(toDistributeMin int32, lists hpaLists, rdc replicaNums, - currentObjs map[string]pkgruntime.Object, scheduled map[string]*replicaNums) int32 { - for cluster, replicas := range scheduled { - if toDistributeMin == 0 { - break - } - // We have distriubted Max and thus scheduled might not be nil - // but probably current (what we got originally) is nil(no hpa) - if replicas == nil || currentObjs[cluster] == nil { - continue - } - if a.minReplicasIncreasable(currentObjs[cluster]) { - if lists.availableMin.Len() > 0 { - popped, notEmpty := lists.availableMin.PopAny() - if notEmpty { - // Boundary checks have happened earlier. - scheduled[popped].min-- - replicas.min++ - toDistributeMin-- - } - } - } - } - - if lists.noHpa.Len() > 0 { - // TODO: can this become an infinite loop? - for toDistributeMin > 0 { - for _, cluster := range lists.noHpa.UnsortedList() { - replicas := scheduled[cluster] - if replicas == nil { - // We did not get max here so this cluster - // remains without hpa - continue - } - var replicaNum int32 = 0 - if toDistributeMin < rdc.min { - replicaNum = toDistributeMin - } else { - replicaNum = rdc.min - } - if (replicas.max - replicaNum) < replicas.min { - // Cannot increase the min in this cluster - // as it will go beyond max - continue - } - if lists.availableMin.Len() > 0 { - popped, notEmpty := lists.availableMin.PopAny() - if notEmpty { - // Boundary checks have happened earlier. - scheduled[popped].min-- - replicas.min++ - toDistributeMin-- - continue - } - } - replicas.min += replicaNum - toDistributeMin -= replicaNum - } - } - } else { // we have no new clusters but if still have min replicas to distribute; - // just distribute all in current clusters. - for toDistributeMin > 0 { - for _, replicas := range scheduled { - if replicas == nil { - // We did not get max here so this cluster - // remains without hpa - continue - } - var replicaNum int32 = 0 - if toDistributeMin < rdc.min { - replicaNum = toDistributeMin - } else { - replicaNum = rdc.min - } - if (replicas.max - replicaNum) < replicas.min { - // Cannot increase the min in this cluster - // as it will go beyond max - continue - } - if lists.availableMin.Len() > 0 { - popped, notEmpty := lists.availableMin.PopAny() - if notEmpty { - // Boundary checks have happened earlier. - scheduled[popped].min-- - replicas.min++ - toDistributeMin-- - continue - } - } - replicas.min += replicaNum - toDistributeMin -= replicaNum - } - } - } - return toDistributeMin -} - -// finaliseScheduleState ensures that the minReplica count is made to 1 -// for those clusters which got max, but did not get min. This is because -// k8s hpa does not accept hpas with 0 min replicas. -// The replica num distribution can thus have more mins then fedHpa requested -// but its better then having all replicas go into one cluster (if fedHpa -// requested min=1 (which is the most usual case). -func finaliseScheduleState(scheduled map[string]*replicaNums) map[string]*replicaNums { - for _, replicas := range scheduled { - if (replicas != nil) && (replicas.min <= 0) && (replicas.max > 0) { - // Min total does not necessarily meet the federated min limit. - replicas.min = 1 - } - } - return scheduled -} - -// isPristine is used to determine if so far local controller has been -// able to really determine, what should be the desired replica number for -// this cluster. -// This is used to get hpas into those clusters which might join fresh, -// and so far other cluster hpas haven't really reached anywhere. -// TODO: There is a flaw here, that a just born object would also offer its -// replicas which can also lead to fast thrashing. -// The only better way is to either ensure that object creation time stamp is set -// and can be used authoritatively; or have another field on the local object -// which is mandatorily set on creation and can be used authoritatively. -// Should we abuse annotations again for this, or this can be a proper requirement? -func isPristine(hpa *autoscalingv1.HorizontalPodAutoscaler) bool { - if hpa.Status.LastScaleTime == nil && - hpa.Status.DesiredReplicas == 0 { - return true - } - return false -} - -// isScaleable tells if it already has been a reasonable amount of -// time since this hpa scaled. Its used to avoid fast thrashing. -func (a *HpaAdapter) isScaleable(hpa *autoscalingv1.HorizontalPodAutoscaler) bool { - if hpa.Status.LastScaleTime == nil { - return false - } - t := hpa.Status.LastScaleTime.Add(a.scaleForbiddenWindow) - if t.After(time.Now()) { - return false - } - return true -} - -func (a *HpaAdapter) maxReplicasReducible(obj pkgruntime.Object) bool { - hpa := obj.(*autoscalingv1.HorizontalPodAutoscaler) - if (hpa.Spec.MinReplicas != nil) && - (((hpa.Spec.MaxReplicas - 1) - *hpa.Spec.MinReplicas) < 0) { - return false - } - if isPristine(hpa) { - return true - } - if !a.isScaleable(hpa) { - return false - } - if (hpa.Status.DesiredReplicas < hpa.Status.CurrentReplicas) || - ((hpa.Status.DesiredReplicas == hpa.Status.CurrentReplicas) && - (hpa.Status.DesiredReplicas < hpa.Spec.MaxReplicas)) { - return true - } - return false -} - -// minReplicasReducible checks if this cluster (hpa) can offer replicas which are -// stuck here because of min limit. -// Its noteworthy, that min and max are adjusted separately, but if the replicas -// are not being used here, the max adjustment will lead it to become equal to min, -// but will not be able to scale down further and offer max to some other cluster -// which needs replicas. -func (a *HpaAdapter) minReplicasReducible(obj pkgruntime.Object) bool { - hpa := obj.(*autoscalingv1.HorizontalPodAutoscaler) - if isPristine(hpa) && (hpa.Spec.MinReplicas != nil) && - (*hpa.Spec.MinReplicas > 1) && - (*hpa.Spec.MinReplicas <= hpa.Spec.MaxReplicas) { - return true - } - if !a.isScaleable(hpa) { - return false - } - if (hpa.Spec.MinReplicas != nil) && - (*hpa.Spec.MinReplicas > 1) && - (hpa.Status.DesiredReplicas == hpa.Status.CurrentReplicas) && - (hpa.Status.CurrentReplicas == *hpa.Spec.MinReplicas) { - return true - } - return false -} - -func (a *HpaAdapter) maxReplicasNeeded(obj pkgruntime.Object) bool { - hpa := obj.(*autoscalingv1.HorizontalPodAutoscaler) - if !a.isScaleable(hpa) { - return false - } - - if (hpa.Status.CurrentReplicas == hpa.Status.DesiredReplicas) && - (hpa.Status.CurrentReplicas == hpa.Spec.MaxReplicas) { - return true - } - return false -} - -func (a *HpaAdapter) minReplicasIncreasable(obj pkgruntime.Object) bool { - hpa := obj.(*autoscalingv1.HorizontalPodAutoscaler) - if !a.isScaleable(hpa) || - ((hpa.Spec.MinReplicas != nil) && - (*hpa.Spec.MinReplicas) >= hpa.Spec.MaxReplicas) { - return false - } - - if (hpa.Spec.MinReplicas != nil) && - (hpa.Status.DesiredReplicas > *hpa.Spec.MinReplicas) { - return true - } - return false -} - -// updateClusterListOnTargetObject passes the necessary info to the target object, -// so that the corresponding controller can act on that. -// This is used because if an hpa is active on a federated object it is supposed -// to control the replicas and presence/absence of target object from federated clusters. -func (a *HpaAdapter) updateClusterListOnTargetObject(fedHpa *autoscalingv1.HorizontalPodAutoscaler, scheduleStatus map[string]*replicaNums) error { - if len(fedHpa.Spec.ScaleTargetRef.Kind) <= 0 || len(fedHpa.Spec.ScaleTargetRef.Name) <= 0 { - // nothing to do - glog.Infof("Fed HPA: cluster list update on target object skipped for target obj: %s, kind: %s", fedHpa.Spec.ScaleTargetRef.Name, fedHpa.Spec.ScaleTargetRef.Kind) - return nil - } - - names := []string{} - for clusterName, replicas := range scheduleStatus { - if replicas != nil { - names = append(names, clusterName) - } - } - clusterNames := hpautil.ClusterNames{Names: names} - qualifiedKind := extensionsinternal.Kind(fedHpa.Spec.ScaleTargetRef.Kind) - targetObj, err := getRuntimeObjectForKind(a.client, qualifiedKind, fedHpa.Namespace, fedHpa.Spec.ScaleTargetRef.Name) - if errors.IsNotFound(err) { - // Nothing to do; the target object does not exist in federation. - glog.Infof("Fed HPA: cluster list update on target object skipped for target obj: %s, kind: %s. Target object missing in federation", fedHpa.Spec.ScaleTargetRef.Name, fedHpa.Spec.ScaleTargetRef.Kind) - return nil - } - if err != nil { - return err - } - - updatedObj := hpautil.SetHpaTargetClusterList(targetObj, clusterNames) - _, err = updateRuntimeObjectForKind(a.client, qualifiedKind, fedHpa.Namespace, updatedObj) - if err != nil { - return err - } - return nil -} - -// getRuntimeObjectForKind gets the hpa targetted object from the federation control plane. -// As of now, federation only supports "ReplicaSets" and "Deployments", which is the reason -// this function only lists these two types. -// TODO: update a similar info in federated hpa documentation. -func getRuntimeObjectForKind(c federationclientset.Interface, kind schema.GroupKind, ns, name string) (pkgruntime.Object, error) { - switch kind { - case extensionsinternal.Kind("ReplicaSet"): - return c.ExtensionsV1beta1().ReplicaSets(ns).Get(name, metav1.GetOptions{}) - case extensionsinternal.Kind("Deployment"): - return c.ExtensionsV1beta1().Deployments(ns).Get(name, metav1.GetOptions{}) - default: - return nil, fmt.Errorf("Unsupported federated kind targeted by hpa: %v", kind) - } -} - -// updateRuntimeObjectForKind updates the hpa targetted object in the federation control plane. -// As of now, federation only supports "ReplicaSets" and "Deployments", which is the reason -// this function only lists these two types. -// TODO: update a similar info in federated hpa documentation. -func updateRuntimeObjectForKind(c federationclientset.Interface, kind schema.GroupKind, ns string, obj pkgruntime.Object) (pkgruntime.Object, error) { - switch kind { - case extensionsinternal.Kind("ReplicaSet"): - return c.ExtensionsV1beta1().ReplicaSets(ns).Update(obj.(*v1beta1.ReplicaSet)) - case extensionsinternal.Kind("Deployment"): - return c.ExtensionsV1beta1().Deployments(ns).Update(obj.(*v1beta1.Deployment)) - default: - return nil, fmt.Errorf("Unsupported federated kind targeted by hpa: %v", kind) - } -} diff --git a/federation/pkg/federatedtypes/hpa_test.go b/federation/pkg/federatedtypes/hpa_test.go deleted file mode 100644 index 95447b18d54..00000000000 --- a/federation/pkg/federatedtypes/hpa_test.go +++ /dev/null @@ -1,264 +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 federatedtypes - -import ( - "testing" - - autoscalingv1 "k8s.io/api/autoscaling/v1" - apiv1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - pkgruntime "k8s.io/apimachinery/pkg/runtime" - . "k8s.io/kubernetes/federation/pkg/federation-controller/util/test" - - "github.com/stretchr/testify/assert" -) - -type replicas struct { - min int32 - max int32 -} - -func TestGetHpaScheduleState(t *testing.T) { - defaultFedHpa := newHpaWithReplicas(NewInt32(1), NewInt32(70), 10) - testCases := map[string]struct { - fedHpa *autoscalingv1.HorizontalPodAutoscaler - localHpas map[string]pkgruntime.Object - expectedReplicas map[string]*replicas - }{ - "Distribiutes replicas randomly if no existing hpa in any local cluster": { - localHpas: func() map[string]pkgruntime.Object { - hpas := make(map[string]pkgruntime.Object) - hpas["c1"] = nil - hpas["c2"] = nil - return hpas - }(), - }, - "Cluster with no hpa gets replicas if other clusters have replicas": { - localHpas: func() map[string]pkgruntime.Object { - hpas := make(map[string]pkgruntime.Object) - hpas["c1"] = newHpaWithReplicas(NewInt32(1), NewInt32(70), 10) - hpas["c2"] = nil - return hpas - }(), - expectedReplicas: map[string]*replicas{ - "c1": { - min: int32(1), - max: int32(9), - }, - "c2": { - min: int32(1), - max: int32(1), - }, - }, - }, - "Cluster needing max replicas gets it if there is another cluster to offer max": { - localHpas: func() map[string]pkgruntime.Object { - hpa1 := newHpaWithReplicas(NewInt32(1), NewInt32(70), 7) - hpa1 = updateHpaStatus(hpa1, NewInt32(50), 5, 5, true) - hpa2 := newHpaWithReplicas(NewInt32(1), NewInt32(70), 1) - hpa2 = updateHpaStatus(hpa2, NewInt32(70), 1, 1, true) - // include third object to ensure, it does not break the test - hpa3 := newHpaWithReplicas(NewInt32(1), NewInt32(70), 2) - hpa3 = updateHpaStatus(hpa3, NewInt32(70), 1, 1, false) - hpas := make(map[string]pkgruntime.Object) - hpas["c1"] = hpa1 - hpas["c2"] = hpa2 - hpas["c3"] = hpa3 - return hpas - }(), - expectedReplicas: map[string]*replicas{ - "c1": { - min: int32(1), - max: int32(6), - }, - "c2": { - min: int32(1), - max: int32(2), - }, - "c3": { - min: int32(1), - max: int32(2), - }, - }, - }, - "Cluster needing max replicas does not get it if there is no cluster offerring max": { - localHpas: func() map[string]pkgruntime.Object { - hpa1 := newHpaWithReplicas(NewInt32(1), NewInt32(70), 9) - hpa1 = updateHpaStatus(hpa1, NewInt32(70), 9, 9, false) - hpa2 := newHpaWithReplicas(NewInt32(1), NewInt32(70), 1) - hpa2 = updateHpaStatus(hpa2, NewInt32(70), 1, 1, true) - hpas := make(map[string]pkgruntime.Object) - hpas["c1"] = hpa1 - hpas["c2"] = hpa2 - return hpas - }(), - expectedReplicas: map[string]*replicas{ - "c1": { - min: int32(1), - max: int32(9), - }, - "c2": { - min: int32(1), - max: int32(1), - }, - }, - }, - "Cluster which can increase min replicas gets to increase min if there is a cluster offering min": { - fedHpa: newHpaWithReplicas(NewInt32(4), NewInt32(70), 10), - localHpas: func() map[string]pkgruntime.Object { - hpa1 := newHpaWithReplicas(NewInt32(3), NewInt32(70), 6) - hpa1 = updateHpaStatus(hpa1, NewInt32(50), 3, 3, true) - hpa2 := newHpaWithReplicas(NewInt32(1), NewInt32(70), 4) - hpa2 = updateHpaStatus(hpa2, NewInt32(50), 3, 3, true) - hpas := make(map[string]pkgruntime.Object) - hpas["c1"] = hpa1 - hpas["c2"] = hpa2 - return hpas - }(), - expectedReplicas: map[string]*replicas{ - "c1": { - min: int32(2), - max: int32(6), - }, - "c2": { - min: int32(2), - max: int32(4), - }, - }, - }, - "Cluster which can increase min replicas does not increase if there are no clusters offering min": { - fedHpa: newHpaWithReplicas(NewInt32(4), NewInt32(70), 10), - localHpas: func() map[string]pkgruntime.Object { - hpa1 := newHpaWithReplicas(NewInt32(3), NewInt32(70), 6) - hpa1 = updateHpaStatus(hpa1, NewInt32(50), 4, 4, true) - hpa2 := newHpaWithReplicas(NewInt32(1), NewInt32(70), 4) - hpa2 = updateHpaStatus(hpa2, NewInt32(50), 3, 3, true) - hpas := make(map[string]pkgruntime.Object) - hpas["c1"] = hpa1 - hpas["c2"] = hpa2 - return hpas - }(), - expectedReplicas: map[string]*replicas{ - "c1": { - min: int32(3), - max: int32(6), - }, - "c2": { - min: int32(1), - max: int32(4), - }, - }, - }, - "Increasing replicas on fed object increases the same on clusters": { - // Existing total of local min, max = 1+1, 5+5 decreasing to below - fedHpa: newHpaWithReplicas(NewInt32(4), NewInt32(70), 14), - localHpas: func() map[string]pkgruntime.Object { - // does not matter if scaleability is true - hpas := make(map[string]pkgruntime.Object) - hpas["c1"] = newHpaWithReplicas(NewInt32(1), NewInt32(70), 5) - hpas["c2"] = newHpaWithReplicas(NewInt32(1), NewInt32(70), 5) - return hpas - }(), - // We dont know which cluster gets how many, but the resultant total should match - }, - "Decreasing replicas on fed object decreases the same on clusters": { - // Existing total of local min, max = 2+2, 8+8 decreasing to below - fedHpa: newHpaWithReplicas(NewInt32(3), NewInt32(70), 8), - localHpas: func() map[string]pkgruntime.Object { - // does not matter if scaleability is true - hpas := make(map[string]pkgruntime.Object) - hpas["c1"] = newHpaWithReplicas(NewInt32(2), NewInt32(70), 8) - hpas["c2"] = newHpaWithReplicas(NewInt32(2), NewInt32(70), 8) - return hpas - }(), - // We dont know which cluster gets how many, but the resultant total should match - }, - } - - adapter := &HpaAdapter{ - scaleForbiddenWindow: ScaleForbiddenWindow, - } - for testName, testCase := range testCases { - t.Run(testName, func(t *testing.T) { - if testCase.fedHpa == nil { - testCase.fedHpa = defaultFedHpa - } - scheduledState := adapter.getHpaScheduleState(testCase.fedHpa, testCase.localHpas) - checkClusterConditions(t, testCase.fedHpa, scheduledState) - if testCase.expectedReplicas != nil { - for cluster, replicas := range testCase.expectedReplicas { - scheduledReplicas := scheduledState[cluster] - assert.Equal(t, replicas.min, scheduledReplicas.min) - assert.Equal(t, replicas.max, scheduledReplicas.max) - } - } - }) - } -} - -func updateHpaStatus(hpa *autoscalingv1.HorizontalPodAutoscaler, currentUtilisation *int32, current, desired int32, scaleable bool) *autoscalingv1.HorizontalPodAutoscaler { - hpa.Status.CurrentReplicas = current - hpa.Status.DesiredReplicas = desired - hpa.Status.CurrentCPUUtilizationPercentage = currentUtilisation - now := metav1.Now() - scaledTime := now - if scaleable { - // definitely more then ScaleForbiddenWindow time ago - scaledTime = metav1.NewTime(now.Time.Add(-2 * ScaleForbiddenWindow)) - } - hpa.Status.LastScaleTime = &scaledTime - return hpa -} - -func checkClusterConditions(t *testing.T, fedHpa *autoscalingv1.HorizontalPodAutoscaler, scheduled map[string]*replicaNums) { - minTotal := int32(0) - maxTotal := int32(0) - for _, replicas := range scheduled { - minTotal += replicas.min - maxTotal += replicas.max - } - - // - Total of max matches the fed max - assert.Equal(t, fedHpa.Spec.MaxReplicas, maxTotal) - // - Total of min is not less then fed min - assert.Condition(t, func() bool { - if *fedHpa.Spec.MinReplicas <= minTotal { - return true - } - return false - }) -} - -func newHpaWithReplicas(min, targetUtilisation *int32, max int32) *autoscalingv1.HorizontalPodAutoscaler { - return &autoscalingv1.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - Name: "myhpa", - Namespace: apiv1.NamespaceDefault, - SelfLink: "/api/mylink", - }, - Spec: autoscalingv1.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{ - Kind: "HorizontalPodAutoscaler", - Name: "target-", - }, - MinReplicas: min, - MaxReplicas: max, - TargetCPUUtilizationPercentage: targetUtilisation, - }, - } -} diff --git a/federation/pkg/federatedtypes/namespace.go b/federation/pkg/federatedtypes/namespace.go deleted file mode 100644 index ae4892c6bc2..00000000000 --- a/federation/pkg/federatedtypes/namespace.go +++ /dev/null @@ -1,215 +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 federatedtypes - -import ( - "fmt" - - apiv1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - pkgruntime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/client-go/dynamic" - kubeclientset "k8s.io/client-go/kubernetes" - restclient "k8s.io/client-go/rest" - "k8s.io/client-go/tools/record" - federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - "k8s.io/kubernetes/federation/pkg/federation-controller/util" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/controller/namespace/deletion" - - "github.com/golang/glog" -) - -const ( - NamespaceKind = "namespace" - NamespaceControllerName = "namespaces" -) - -func init() { - RegisterFederatedType(NamespaceKind, NamespaceControllerName, []schema.GroupVersionResource{apiv1.SchemeGroupVersion.WithResource(NamespaceControllerName)}, NewNamespaceAdapter) -} - -type NamespaceAdapter struct { - client federationclientset.Interface - deleter deletion.NamespacedResourcesDeleterInterface -} - -func NewNamespaceAdapter(client federationclientset.Interface, config *restclient.Config, adapterSpecificArgs map[string]interface{}) FederatedTypeAdapter { - dynamicClientPool := dynamic.NewDynamicClientPool(config) - discoverResourcesFunc := client.Discovery().ServerPreferredNamespacedResources - deleter := deletion.NewNamespacedResourcesDeleter( - client.Core().Namespaces(), - dynamicClientPool, - nil, - discoverResourcesFunc, - apiv1.FinalizerKubernetes, - false) - return &NamespaceAdapter{client: client, deleter: deleter} -} - -func (a *NamespaceAdapter) Kind() string { - return NamespaceKind -} - -func (a *NamespaceAdapter) ObjectType() pkgruntime.Object { - return &apiv1.Namespace{} -} - -func (a *NamespaceAdapter) IsExpectedType(obj interface{}) bool { - _, ok := obj.(*apiv1.Namespace) - return ok -} - -func (a *NamespaceAdapter) Copy(obj pkgruntime.Object) pkgruntime.Object { - namespace := obj.(*apiv1.Namespace) - return &apiv1.Namespace{ - ObjectMeta: util.DeepCopyRelevantObjectMeta(namespace.ObjectMeta), - Spec: *namespace.Spec.DeepCopy(), - } -} - -func (a *NamespaceAdapter) Equivalent(obj1, obj2 pkgruntime.Object) bool { - return util.ObjectMetaAndSpecEquivalent(obj1, obj2) -} - -func (a *NamespaceAdapter) QualifiedName(obj pkgruntime.Object) QualifiedName { - namespace := obj.(*apiv1.Namespace) - return QualifiedName{Name: namespace.Name} -} - -func (a *NamespaceAdapter) ObjectMeta(obj pkgruntime.Object) *metav1.ObjectMeta { - return &obj.(*apiv1.Namespace).ObjectMeta -} - -func (a *NamespaceAdapter) FedCreate(obj pkgruntime.Object) (pkgruntime.Object, error) { - namespace := obj.(*apiv1.Namespace) - return a.client.CoreV1().Namespaces().Create(namespace) -} - -func (a *NamespaceAdapter) FedDelete(qualifiedName QualifiedName, options *metav1.DeleteOptions) error { - return a.client.CoreV1().Namespaces().Delete(qualifiedName.Name, options) -} - -func (a *NamespaceAdapter) FedGet(qualifiedName QualifiedName) (pkgruntime.Object, error) { - return a.client.CoreV1().Namespaces().Get(qualifiedName.Name, metav1.GetOptions{}) -} - -func (a *NamespaceAdapter) FedList(namespace string, options metav1.ListOptions) (pkgruntime.Object, error) { - return a.client.CoreV1().Namespaces().List(options) -} - -func (a *NamespaceAdapter) FedUpdate(obj pkgruntime.Object) (pkgruntime.Object, error) { - namespace := obj.(*apiv1.Namespace) - return a.client.CoreV1().Namespaces().Update(namespace) -} - -func (a *NamespaceAdapter) FedWatch(namespace string, options metav1.ListOptions) (watch.Interface, error) { - return a.client.CoreV1().Namespaces().Watch(options) -} - -func (a *NamespaceAdapter) ClusterCreate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) { - namespace := obj.(*apiv1.Namespace) - return client.CoreV1().Namespaces().Create(namespace) -} - -func (a *NamespaceAdapter) ClusterDelete(client kubeclientset.Interface, qualifiedName QualifiedName, options *metav1.DeleteOptions) error { - return client.CoreV1().Namespaces().Delete(qualifiedName.Name, options) -} - -func (a *NamespaceAdapter) ClusterGet(client kubeclientset.Interface, qualifiedName QualifiedName) (pkgruntime.Object, error) { - return client.CoreV1().Namespaces().Get(qualifiedName.Name, metav1.GetOptions{}) -} - -func (a *NamespaceAdapter) ClusterList(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (pkgruntime.Object, error) { - return client.CoreV1().Namespaces().List(options) -} - -func (a *NamespaceAdapter) ClusterUpdate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) { - namespace := obj.(*apiv1.Namespace) - return client.CoreV1().Namespaces().Update(namespace) -} - -func (a *NamespaceAdapter) ClusterWatch(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (watch.Interface, error) { - return client.CoreV1().Namespaces().Watch(options) -} - -func (a *NamespaceAdapter) IsSchedulingAdapter() bool { - return false -} - -func (a *NamespaceAdapter) NewTestObject(namespace string) pkgruntime.Object { - return &apiv1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: "test-namespace-", - }, - Spec: apiv1.NamespaceSpec{ - Finalizers: []apiv1.FinalizerName{apiv1.FinalizerKubernetes}, - }, - } -} - -// CleanUpNamespace deletes all resources in a given namespace. -func (a *NamespaceAdapter) CleanUpNamespace(obj pkgruntime.Object, eventRecorder record.EventRecorder) (pkgruntime.Object, error) { - namespace := obj.(*apiv1.Namespace) - name := namespace.Name - - // Set Terminating status. - updatedNamespace := &apiv1.Namespace{ - ObjectMeta: namespace.ObjectMeta, - Spec: namespace.Spec, - Status: apiv1.NamespaceStatus{ - Phase: apiv1.NamespaceTerminating, - }, - } - var err error - if namespace.Status.Phase != apiv1.NamespaceTerminating { - glog.V(2).Infof("Marking ns %s as terminating", name) - eventRecorder.Event(namespace, api.EventTypeNormal, "DeleteNamespace", fmt.Sprintf("Marking for deletion")) - _, err = a.FedUpdate(updatedNamespace) - if err != nil { - return nil, fmt.Errorf("failed to update namespace: %v", err) - } - } - - if hasFinalizerInSpec(updatedNamespace, apiv1.FinalizerKubernetes) { - // Delete resources in this namespace. - err = a.deleter.Delete(name) - if err != nil { - return nil, fmt.Errorf("error in deleting resources in namespace %s: %v", name, err) - } - glog.V(2).Infof("Removed kubernetes finalizer from ns %s", name) - // Fetch the updated Namespace. - obj, err = a.FedGet(QualifiedName{Name: name}) - updatedNamespace = obj.(*apiv1.Namespace) - if err != nil { - return nil, fmt.Errorf("error in fetching updated namespace %s: %s", name, err) - } - } - - return updatedNamespace, nil -} - -func hasFinalizerInSpec(namespace *apiv1.Namespace, finalizer apiv1.FinalizerName) bool { - for i := range namespace.Spec.Finalizers { - if namespace.Spec.Finalizers[i] == finalizer { - return true - } - } - return false -} diff --git a/federation/pkg/federatedtypes/qualifiedname.go b/federation/pkg/federatedtypes/qualifiedname.go deleted file mode 100644 index 95f0df11042..00000000000 --- a/federation/pkg/federatedtypes/qualifiedname.go +++ /dev/null @@ -1,41 +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 federatedtypes - -import ( - "fmt" -) - -// QualifiedName comprises a resource name with an optional namespace. -// If namespace is provided, a QualifiedName will be rendered as -// "/". If not, it will be rendered as "name". This -// is intended to allow the FederatedTypeAdapter interface and its -// consumers to operate on both namespaces and namespace-qualified -// resources. - -type QualifiedName struct { - Namespace string - Name string -} - -// String returns the general purpose string representation -func (n QualifiedName) String() string { - if len(n.Namespace) == 0 { - return n.Name - } - return fmt.Sprintf("%s/%s", n.Namespace, n.Name) -} diff --git a/federation/pkg/federatedtypes/registry.go b/federation/pkg/federatedtypes/registry.go deleted file mode 100644 index 720b32a8546..00000000000 --- a/federation/pkg/federatedtypes/registry.go +++ /dev/null @@ -1,59 +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 federatedtypes - -import ( - "fmt" - - "k8s.io/apimachinery/pkg/runtime/schema" -) - -// FederatedType configures federation for a kubernetes type -type FederatedType struct { - Kind string - ControllerName string - RequiredResources []schema.GroupVersionResource - AdapterFactory AdapterFactory -} - -var typeRegistry = make(map[string]FederatedType) - -// RegisterFederatedType ensures that configuration for the given kind will be returned by the FederatedTypes method. -func RegisterFederatedType(kind, controllerName string, requiredResources []schema.GroupVersionResource, factory AdapterFactory) { - _, ok := typeRegistry[kind] - if ok { - // TODO Is panicking ok given that this is part of a type-registration mechanism - panic(fmt.Sprintf("Federated type %q has already been registered", kind)) - } - typeRegistry[kind] = FederatedType{ - Kind: kind, - ControllerName: controllerName, - RequiredResources: requiredResources, - AdapterFactory: factory, - } -} - -// FederatedTypes returns a mapping of kind (e.g. "secret") to the -// type information required to configure its federation. -func FederatedTypes() map[string]FederatedType { - // TODO copy RequiredResources to avoid accidental mutation - result := make(map[string]FederatedType) - for key, value := range typeRegistry { - result[key] = value - } - return result -} diff --git a/federation/pkg/federatedtypes/replicaset.go b/federation/pkg/federatedtypes/replicaset.go deleted file mode 100644 index 0477c787318..00000000000 --- a/federation/pkg/federatedtypes/replicaset.go +++ /dev/null @@ -1,189 +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 federatedtypes - -import ( - apiv1 "k8s.io/api/core/v1" - extensionsv1 "k8s.io/api/extensions/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - pkgruntime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/watch" - kubeclientset "k8s.io/client-go/kubernetes" - restclient "k8s.io/client-go/rest" - federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - fedutil "k8s.io/kubernetes/federation/pkg/federation-controller/util" -) - -const ( - ReplicaSetKind = "replicaset" - ReplicaSetControllerName = "replicasets" - FedReplicaSetPreferencesAnnotation = "federation.kubernetes.io/replica-set-preferences" -) - -func init() { - RegisterFederatedType(ReplicaSetKind, ReplicaSetControllerName, []schema.GroupVersionResource{extensionsv1.SchemeGroupVersion.WithResource(ReplicaSetControllerName)}, NewReplicaSetAdapter) -} - -type ReplicaSetAdapter struct { - *replicaSchedulingAdapter - client federationclientset.Interface -} - -func NewReplicaSetAdapter(client federationclientset.Interface, config *restclient.Config, adapterSpecificArgs map[string]interface{}) FederatedTypeAdapter { - replicaSchedulingAdapter := replicaSchedulingAdapter{ - preferencesAnnotationName: FedReplicaSetPreferencesAnnotation, - updateStatusFunc: func(obj pkgruntime.Object, schedulingInfo interface{}) error { - rs := obj.(*extensionsv1.ReplicaSet) - typedStatus := schedulingInfo.(*ReplicaSchedulingInfo).Status - if typedStatus.Replicas != rs.Status.Replicas || typedStatus.FullyLabeledReplicas != rs.Status.FullyLabeledReplicas || - typedStatus.ReadyReplicas != rs.Status.ReadyReplicas || typedStatus.AvailableReplicas != rs.Status.AvailableReplicas { - rs.Status = extensionsv1.ReplicaSetStatus{ - Replicas: typedStatus.Replicas, - FullyLabeledReplicas: typedStatus.Replicas, - ReadyReplicas: typedStatus.ReadyReplicas, - AvailableReplicas: typedStatus.AvailableReplicas, - } - _, err := client.Extensions().ReplicaSets(rs.Namespace).UpdateStatus(rs) - return err - } - return nil - }, - } - return &ReplicaSetAdapter{&replicaSchedulingAdapter, client} -} - -func (a *ReplicaSetAdapter) Kind() string { - return ReplicaSetKind -} - -func (a *ReplicaSetAdapter) ObjectType() pkgruntime.Object { - return &extensionsv1.ReplicaSet{} -} - -func (a *ReplicaSetAdapter) IsExpectedType(obj interface{}) bool { - _, ok := obj.(*extensionsv1.ReplicaSet) - return ok -} - -func (a *ReplicaSetAdapter) Copy(obj pkgruntime.Object) pkgruntime.Object { - rs := obj.(*extensionsv1.ReplicaSet) - return &extensionsv1.ReplicaSet{ - ObjectMeta: fedutil.DeepCopyRelevantObjectMeta(rs.ObjectMeta), - Spec: *rs.Spec.DeepCopy(), - } -} - -func (a *ReplicaSetAdapter) Equivalent(obj1, obj2 pkgruntime.Object) bool { - return fedutil.ObjectMetaAndSpecEquivalent(obj1, obj2) -} - -func (a *ReplicaSetAdapter) QualifiedName(obj pkgruntime.Object) QualifiedName { - replicaset := obj.(*extensionsv1.ReplicaSet) - return QualifiedName{Namespace: replicaset.Namespace, Name: replicaset.Name} -} - -func (a *ReplicaSetAdapter) ObjectMeta(obj pkgruntime.Object) *metav1.ObjectMeta { - return &obj.(*extensionsv1.ReplicaSet).ObjectMeta -} - -func (a *ReplicaSetAdapter) FedCreate(obj pkgruntime.Object) (pkgruntime.Object, error) { - replicaset := obj.(*extensionsv1.ReplicaSet) - return a.client.Extensions().ReplicaSets(replicaset.Namespace).Create(replicaset) -} - -func (a *ReplicaSetAdapter) FedDelete(qualifiedName QualifiedName, options *metav1.DeleteOptions) error { - return a.client.Extensions().ReplicaSets(qualifiedName.Namespace).Delete(qualifiedName.Name, options) -} - -func (a *ReplicaSetAdapter) FedGet(qualifiedName QualifiedName) (pkgruntime.Object, error) { - return a.client.Extensions().ReplicaSets(qualifiedName.Namespace).Get(qualifiedName.Name, metav1.GetOptions{}) -} - -func (a *ReplicaSetAdapter) FedList(namespace string, options metav1.ListOptions) (pkgruntime.Object, error) { - return a.client.Extensions().ReplicaSets(namespace).List(options) -} - -func (a *ReplicaSetAdapter) FedUpdate(obj pkgruntime.Object) (pkgruntime.Object, error) { - replicaset := obj.(*extensionsv1.ReplicaSet) - return a.client.Extensions().ReplicaSets(replicaset.Namespace).Update(replicaset) -} - -func (a *ReplicaSetAdapter) FedWatch(namespace string, options metav1.ListOptions) (watch.Interface, error) { - return a.client.Extensions().ReplicaSets(namespace).Watch(options) -} - -func (a *ReplicaSetAdapter) ClusterCreate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) { - replicaset := obj.(*extensionsv1.ReplicaSet) - return client.Extensions().ReplicaSets(replicaset.Namespace).Create(replicaset) -} - -func (a *ReplicaSetAdapter) ClusterDelete(client kubeclientset.Interface, qualifiedName QualifiedName, options *metav1.DeleteOptions) error { - return client.Extensions().ReplicaSets(qualifiedName.Namespace).Delete(qualifiedName.Name, options) -} - -func (a *ReplicaSetAdapter) ClusterGet(client kubeclientset.Interface, qualifiedName QualifiedName) (pkgruntime.Object, error) { - return client.Extensions().ReplicaSets(qualifiedName.Namespace).Get(qualifiedName.Name, metav1.GetOptions{}) -} - -func (a *ReplicaSetAdapter) ClusterList(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (pkgruntime.Object, error) { - return client.Extensions().ReplicaSets(namespace).List(options) -} - -func (a *ReplicaSetAdapter) ClusterUpdate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) { - replicaset := obj.(*extensionsv1.ReplicaSet) - return client.Extensions().ReplicaSets(replicaset.Namespace).Update(replicaset) -} - -func (a *ReplicaSetAdapter) ClusterWatch(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (watch.Interface, error) { - return client.Extensions().ReplicaSets(namespace).Watch(options) -} - -func (a *ReplicaSetAdapter) EquivalentIgnoringSchedule(obj1, obj2 pkgruntime.Object) bool { - replicaset1 := obj1.(*extensionsv1.ReplicaSet) - replicaset2 := a.Copy(obj2).(*extensionsv1.ReplicaSet) - replicaset2.Spec.Replicas = replicaset1.Spec.Replicas - return fedutil.ObjectMetaAndSpecEquivalent(replicaset1, replicaset2) -} - -func (a *ReplicaSetAdapter) NewTestObject(namespace string) pkgruntime.Object { - replicas := int32(3) - zero := int64(0) - return &extensionsv1.ReplicaSet{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: "test-replicaset-", - Namespace: namespace, - }, - Spec: extensionsv1.ReplicaSetSpec{ - Replicas: &replicas, - Template: apiv1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{"foo": "bar"}, - }, - Spec: apiv1.PodSpec{ - TerminationGracePeriodSeconds: &zero, - Containers: []apiv1.Container{ - { - Name: "nginx", - Image: "nginx", - }, - }, - }, - }, - }, - } -} diff --git a/federation/pkg/federatedtypes/scheduling.go b/federation/pkg/federatedtypes/scheduling.go deleted file mode 100644 index cdbd3970344..00000000000 --- a/federation/pkg/federatedtypes/scheduling.go +++ /dev/null @@ -1,356 +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 federatedtypes - -import ( - "bytes" - "fmt" - "reflect" - "sort" - "time" - - apiv1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - pkgruntime "k8s.io/apimachinery/pkg/runtime" - fedapi "k8s.io/kubernetes/federation/apis/federation" - federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" - fedutil "k8s.io/kubernetes/federation/pkg/federation-controller/util" - hpautil "k8s.io/kubernetes/federation/pkg/federation-controller/util/hpa" - "k8s.io/kubernetes/federation/pkg/federation-controller/util/planner" - "k8s.io/kubernetes/federation/pkg/federation-controller/util/podanalyzer" - "k8s.io/kubernetes/federation/pkg/federation-controller/util/replicapreferences" - - "github.com/golang/glog" -) - -// ScheduleAction is used by the interface ScheduleObject of SchedulingAdapter -// to sync controller reconcile to convey the action type needed for the -// particular cluster local object in ScheduleObject -type ScheduleAction string - -const ( - ActionAdd = "add" - ActionDelete = "delete" -) - -// ReplicaStatus contains the details of status fields from the cluster objects, -// which need accumulation to update the status of the federated object. -type ReplicaStatus struct { - Replicas int32 - UpdatedReplicas int32 - FullyLabeledReplicas int32 - ReadyReplicas int32 - AvailableReplicas int32 -} - -// ReplicaScheduleState is the result of adapter specific schedule() function, -// which is then used to update objects into clusters. -type ReplicaScheduleState struct { - isSelected bool - replicas int64 -} - -// ReplicaSchedulingInfo wraps the information that a replica type (rs or deployment) -// SchedulingAdapter needs to update objects per a schedule. -type ReplicaSchedulingInfo struct { - ScheduleState map[string]*ReplicaScheduleState - Status ReplicaStatus -} - -// SchedulingAdapter defines operations for interacting with a -// federated type that requires more complex synchronization logic. -type SchedulingAdapter interface { - GetSchedule(obj pkgruntime.Object, key string, clusters []*federationapi.Cluster, informer fedutil.FederatedInformer) (interface{}, error) - ScheduleObject(cluster *federationapi.Cluster, clusterObj pkgruntime.Object, federationObjCopy pkgruntime.Object, schedulingInfo interface{}) (pkgruntime.Object, ScheduleAction, error) - UpdateFederatedStatus(obj pkgruntime.Object, schedulingInfo interface{}) error - - // EquivalentIgnoringSchedule returns whether obj1 and obj2 are - // equivalent ignoring differences due to scheduling. - EquivalentIgnoringSchedule(obj1, obj2 pkgruntime.Object) bool -} - -// replicaSchedulingAdapter is meant to be embedded in other type adapters that require -// workload scheduling with actual pod replicas. -type replicaSchedulingAdapter struct { - preferencesAnnotationName string - updateStatusFunc func(pkgruntime.Object, interface{}) error -} - -func (a *replicaSchedulingAdapter) IsSchedulingAdapter() bool { - return true -} - -func isSelected(names []string, name string) bool { - for _, val := range names { - if val == name { - return true - } - } - return false -} - -func isObjHpaControlled(fedObj pkgruntime.Object) (bool, error) { - hpaSelectedClusters, error := hpautil.GetHpaTargetClusterList(fedObj) - if error != nil { - return false, error - } - - if hpaSelectedClusters == nil { - return false, nil - } - return true, nil -} - -// initializeScheduleState initializes the schedule state for consumption by schedule -// functions (schedule or simple schedule). After this initialization the state would -// already have information, if only a subset of clusters targetted by hpa, or all clusters -// need to be considered by the actual scheduling functions. -// The return bool named hpaControlled tells if this object is controlled by hpa or not. -func initializeScheduleState(fedObj pkgruntime.Object, clusterNames []string) (map[string]*ReplicaScheduleState, bool, error) { - initialState := make(map[string]*ReplicaScheduleState) - hpaControlled := false - hpaSelectedClusters, error := hpautil.GetHpaTargetClusterList(fedObj) - if error != nil { - return nil, hpaControlled, error - } - - if hpaSelectedClusters != nil { - hpaControlled = true - } - for _, clusterName := range clusterNames { - replicaState := ReplicaScheduleState{ - isSelected: false, - replicas: 0, - } - if hpaControlled { - if isSelected(hpaSelectedClusters.Names, clusterName) { - replicaState.isSelected = true - } - } - initialState[clusterName] = &replicaState - } - return initialState, hpaControlled, nil -} - -func (a *replicaSchedulingAdapter) GetSchedule(obj pkgruntime.Object, key string, clusters []*federationapi.Cluster, informer fedutil.FederatedInformer) (interface{}, error) { - var clusterNames []string - for _, cluster := range clusters { - clusterNames = append(clusterNames, cluster.Name) - } - - // Schedule the pods across the existing clusters. - objectGetter := func(clusterName, key string) (interface{}, bool, error) { - return informer.GetTargetStore().GetByKey(clusterName, key) - } - podsGetter := func(clusterName string, obj pkgruntime.Object) (*apiv1.PodList, error) { - clientset, err := informer.GetClientsetForCluster(clusterName) - if err != nil { - return nil, err - } - selectorObj := reflect.ValueOf(obj).Elem().FieldByName("Spec").FieldByName("Selector").Interface().(*metav1.LabelSelector) - selector, err := metav1.LabelSelectorAsSelector(selectorObj) - if err != nil { - return nil, fmt.Errorf("invalid selector: %v", err) - } - metadata, err := meta.Accessor(obj) - if err != nil { - return nil, err - } - return clientset.Core().Pods(metadata.GetNamespace()).List(metav1.ListOptions{LabelSelector: selector.String()}) - } - - initializedState, hpaControlled, err := initializeScheduleState(obj, clusterNames) - if err != nil { - return nil, err - } - - if hpaControlled { - state, err := simpleSchedule(initializedState, key, objectGetter) - if err != nil { - return nil, err - } - return &ReplicaSchedulingInfo{ - ScheduleState: state, - Status: ReplicaStatus{}, - }, nil - } - - currentReplicasPerCluster, estimatedCapacity, err := clustersReplicaState(clusterNames, key, objectGetter, podsGetter) - if err != nil { - return nil, err - } - - fedPref, err := replicapreferences.GetAllocationPreferences(obj, a.preferencesAnnotationName) - if err != nil { - glog.Infof("Invalid workload-type specific preference, using default. object: %v, err: %v", obj, err) - } - if fedPref == nil { - fedPref = &fedapi.ReplicaAllocationPreferences{ - Clusters: map[string]fedapi.ClusterPreferences{ - "*": {Weight: 1}, - }, - } - } - - plnr := planner.NewPlanner(fedPref) - - return &ReplicaSchedulingInfo{ - ScheduleState: schedule(plnr, obj, key, clusterNames, currentReplicasPerCluster, estimatedCapacity, initializedState), - Status: ReplicaStatus{}, - }, nil -} - -func (a *replicaSchedulingAdapter) ScheduleObject(cluster *federationapi.Cluster, clusterObj pkgruntime.Object, federationObjCopy pkgruntime.Object, schedulingInfo interface{}) (pkgruntime.Object, ScheduleAction, error) { - typedSchedulingInfo := schedulingInfo.(*ReplicaSchedulingInfo) - clusterScheduleState := typedSchedulingInfo.ScheduleState[cluster.Name] - - if clusterObj != nil { - schedulingStatusVal := reflect.ValueOf(typedSchedulingInfo).Elem().FieldByName("Status") - objStatusVal := reflect.ValueOf(clusterObj).Elem().FieldByName("Status") - for i := 0; i < schedulingStatusVal.NumField(); i++ { - schedulingStatusField := schedulingStatusVal.Field(i) - schedulingStatusFieldName := schedulingStatusVal.Type().Field(i).Name - objStatusField := objStatusVal.FieldByName(schedulingStatusFieldName) - if objStatusField.IsValid() { - current := schedulingStatusField.Int() - additional := objStatusField.Int() - schedulingStatusField.SetInt(current + additional) - } - } - } - - var action ScheduleAction = "" - specReplicas := int32(0) - // If the cluster has been selected (isSelected = true; for example by hpa) - // and the obj does not get any replicas, then it should create one with - // 0 replicas (which can then be scaled by hpa in that cluster). - // On the other hand we keep the action as "unassigned" if this cluster was - // not selected, and let the sync controller decide what to do. - if clusterScheduleState.isSelected { - specReplicas = int32(clusterScheduleState.replicas) - action = ActionAdd - } - reflect.ValueOf(federationObjCopy).Elem().FieldByName("Spec").FieldByName("Replicas").Set(reflect.ValueOf(&specReplicas)) - return federationObjCopy, action, nil -} - -func (a *replicaSchedulingAdapter) UpdateFederatedStatus(obj pkgruntime.Object, schedulingInfo interface{}) error { - return a.updateStatusFunc(obj, schedulingInfo) -} - -// simpleSchedule get replicas from only those clusters which are selected (by hpa scheduler). -// This aim of this is to ensure that this controller does not update objects, which are -// targetted by hpa. -func simpleSchedule(scheduleState map[string]*ReplicaScheduleState, key string, objectGetter func(clusterName string, key string) (interface{}, bool, error)) (map[string]*ReplicaScheduleState, error) { - for clusterName, state := range scheduleState { - // Get and consider replicas only for those clusters which are selected by hpa. - if state.isSelected { - obj, exists, err := objectGetter(clusterName, key) - if err != nil { - return nil, err - } - if !exists { - continue - } - state.replicas = reflect.ValueOf(obj).Elem().FieldByName("Spec").FieldByName("Replicas").Elem().Int() - } - } - - return scheduleState, nil -} - -func schedule(planner *planner.Planner, obj pkgruntime.Object, key string, clusterNames []string, currentReplicasPerCluster map[string]int64, estimatedCapacity map[string]int64, initialState map[string]*ReplicaScheduleState) map[string]*ReplicaScheduleState { - // TODO: integrate real scheduler - replicas := reflect.ValueOf(obj).Elem().FieldByName("Spec").FieldByName("Replicas").Elem().Int() - scheduleResult, overflow := planner.Plan(replicas, clusterNames, currentReplicasPerCluster, estimatedCapacity, key) - - // Ensure that all current clusters end up in the scheduling result. - // initialState, is preinitialized with all isSelected to false. - result := initialState - for clusterName := range currentReplicasPerCluster { - // We consider 0 replicas equaling to no need of creating a new object. - // isSchedule remains false in such case. - result[clusterName].replicas = 0 - } - - for clusterName, replicas := range scheduleResult { - result[clusterName].isSelected = true - result[clusterName].replicas = replicas - } - for clusterName, replicas := range overflow { - result[clusterName].isSelected = true - result[clusterName].replicas += replicas - } - - if glog.V(4) { - buf := bytes.NewBufferString(fmt.Sprintf("Schedule - %q\n", key)) - sort.Strings(clusterNames) - for _, clusterName := range clusterNames { - cur := currentReplicasPerCluster[clusterName] - target := scheduleResult[clusterName] - fmt.Fprintf(buf, "%s: current: %d target: %d", clusterName, cur, target) - if over, found := overflow[clusterName]; found { - fmt.Fprintf(buf, " overflow: %d", over) - } - if capacity, found := estimatedCapacity[clusterName]; found { - fmt.Fprintf(buf, " capacity: %d", capacity) - } - fmt.Fprintf(buf, "\n") - } - glog.V(4).Infof(buf.String()) - } - return result -} - -// clusterReplicaState returns information about the scheduling state of the pods running in the federated clusters. -func clustersReplicaState( - clusterNames []string, - key string, - objectGetter func(clusterName string, key string) (interface{}, bool, error), - podsGetter func(clusterName string, obj pkgruntime.Object) (*apiv1.PodList, error)) (currentReplicasPerCluster map[string]int64, estimatedCapacity map[string]int64, err error) { - - currentReplicasPerCluster = make(map[string]int64) - estimatedCapacity = make(map[string]int64) - - for _, clusterName := range clusterNames { - obj, exists, err := objectGetter(clusterName, key) - if err != nil { - return nil, nil, err - } - if !exists { - continue - } - replicas := reflect.ValueOf(obj).Elem().FieldByName("Spec").FieldByName("Replicas").Elem().Int() - readyReplicas := reflect.ValueOf(obj).Elem().FieldByName("Status").FieldByName("ReadyReplicas").Int() - if replicas == readyReplicas { - currentReplicasPerCluster[clusterName] = readyReplicas - } else { - pods, err := podsGetter(clusterName, obj.(pkgruntime.Object)) - if err != nil { - return nil, nil, err - } - podStatus := podanalyzer.AnalyzePods(pods, time.Now()) - currentReplicasPerCluster[clusterName] = int64(podStatus.RunningAndReady) // include pending as well? - unschedulable := int64(podStatus.Unschedulable) - if unschedulable > 0 { - estimatedCapacity[clusterName] = replicas - unschedulable - } - } - } - return currentReplicasPerCluster, estimatedCapacity, nil -} diff --git a/federation/pkg/federatedtypes/scheduling_test.go b/federation/pkg/federatedtypes/scheduling_test.go deleted file mode 100644 index d938e525a56..00000000000 --- a/federation/pkg/federatedtypes/scheduling_test.go +++ /dev/null @@ -1,163 +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 federatedtypes - -import ( - "fmt" - "testing" - "time" - - apiv1 "k8s.io/api/core/v1" - extensionsv1 "k8s.io/api/extensions/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - pkgruntime "k8s.io/apimachinery/pkg/runtime" - - "github.com/stretchr/testify/assert" -) - -func TestClusterReplicaState(t *testing.T) { - uncalledPodsGetter := func(clusterName string, obj pkgruntime.Object) (*apiv1.PodList, error) { - t.Fatal("podsGetter should not be called when workload objects are all ready.") - return nil, nil - } - - podsByReplicaSet := make(map[pkgruntime.Object][]*apiv1.Pod) - podsGetter := func(clusterName string, obj pkgruntime.Object) (*apiv1.PodList, error) { - pods, ok := podsByReplicaSet[obj] - if !ok { - t.Fatalf("No pods found in test data for replica set %v", obj) - return nil, fmt.Errorf("Not found") - } - var podListPods []apiv1.Pod - for _, pod := range pods { - podListPods = append(podListPods, *pod) - } - return &apiv1.PodList{Items: podListPods}, nil - } - - readyCondition := apiv1.PodCondition{Type: apiv1.PodReady} - unschedulableCondition := apiv1.PodCondition{ - Type: apiv1.PodScheduled, - Status: apiv1.ConditionFalse, - Reason: apiv1.PodReasonUnschedulable, - LastTransitionTime: metav1.NewTime(time.Now().Add(-1 * time.Hour)), - } - - one := int64(1) - two := int64(2) - - tests := map[string]struct { - rs1Replicas int32 - rs2Replicas int32 - rs1ReadyReplicas int32 - rs2ReadyReplicas int32 - podsGetter func(clusterName string, obj pkgruntime.Object) (*apiv1.PodList, error) - pod1Phase apiv1.PodPhase - pod1Condition apiv1.PodCondition - pod2Phase apiv1.PodPhase - pod2Condition apiv1.PodCondition - cluster1Replicas *int64 - cluster2Replicas *int64 - cluster1UnschedulableReplicas *int64 - cluster2UnschedulableReplicas *int64 - }{ - "All replica sets have an equal number of requested and ready replicas.": {rs1Replicas: 2, rs2Replicas: 2, rs1ReadyReplicas: 2, rs2ReadyReplicas: 2, podsGetter: uncalledPodsGetter, cluster1Replicas: &two, cluster2Replicas: &two}, - "One replica set has a pending schedulable pod": {rs1Replicas: 2, rs2Replicas: 2, rs1ReadyReplicas: 1, rs2ReadyReplicas: 2, podsGetter: podsGetter, pod1Phase: apiv1.PodRunning, pod1Condition: readyCondition, pod2Phase: apiv1.PodPending, cluster1Replicas: &one, cluster2Replicas: &two}, - "One replica set has an unschedulable pod": {rs1Replicas: 2, rs2Replicas: 2, rs1ReadyReplicas: 1, rs2ReadyReplicas: 2, podsGetter: podsGetter, pod1Phase: apiv1.PodRunning, pod1Condition: readyCondition, pod2Phase: apiv1.PodPending, pod2Condition: unschedulableCondition, cluster1Replicas: &one, cluster2Replicas: &two, cluster1UnschedulableReplicas: &one}, - } - - for name, tt := range tests { - t.Run(name, func(t *testing.T) { - clusters := []string{"one", "two"} - replicaSetsByCluster := make(map[string]*extensionsv1.ReplicaSet) - replicaSetGetter := func(clusterName string, key string) (interface{}, bool, error) { - rs, ok := replicaSetsByCluster[clusterName] - if !ok { - t.Fatalf("No replica set found in test data for %v", clusterName) - return nil, false, fmt.Errorf("Not found") - } - return rs, true, nil - } - rs1 := newReplicaSetWithReplicas("one", tt.rs1Replicas) - rs2 := newReplicaSetWithReplicas("two", tt.rs2Replicas) - rs1.Spec.Replicas = &tt.rs1Replicas - rs2.Spec.Replicas = &tt.rs2Replicas - rs1.Status.ReadyReplicas = tt.rs1ReadyReplicas - rs2.Status.ReadyReplicas = tt.rs2ReadyReplicas - - replicaSetsByCluster["one"] = rs1 - replicaSetsByCluster["two"] = rs2 - - pod1 := newPod("one") - pod2 := newPod("two") - podThree := newPod("three") - podFour := newPod("four") - - pod1.Status.Phase = tt.pod1Phase - pod2.Status.Phase = tt.pod2Phase - pod1.Status.Conditions = []apiv1.PodCondition{tt.pod1Condition} - pod2.Status.Conditions = []apiv1.PodCondition{tt.pod2Condition} - - podsByReplicaSet[rs1] = []*apiv1.Pod{pod1, pod2} - podsByReplicaSet[rs2] = []*apiv1.Pod{podThree, podFour} - - current, estimatedCapacity, err := clustersReplicaState(clusters, "", replicaSetGetter, tt.podsGetter) - - assert.Nil(t, err) - - wantedCurrent := make(map[string]int64) - if tt.cluster1Replicas != nil { - wantedCurrent["one"] = *tt.cluster1Replicas - } - if tt.cluster2Replicas != nil { - wantedCurrent["two"] = *tt.cluster2Replicas - } - assert.Equal(t, wantedCurrent, current) - - wantedEstimatedCapacity := make(map[string]int64) - if tt.cluster1UnschedulableReplicas != nil { - wantedEstimatedCapacity["one"] = *tt.cluster1UnschedulableReplicas - } - if tt.cluster2UnschedulableReplicas != nil { - wantedEstimatedCapacity["two"] = *tt.cluster2UnschedulableReplicas - } - assert.Equal(t, wantedEstimatedCapacity, estimatedCapacity) - }) - } -} - -func newReplicaSetWithReplicas(name string, replicas int32) *extensionsv1.ReplicaSet { - return &extensionsv1.ReplicaSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: metav1.NamespaceDefault, - SelfLink: "/api/v1/namespaces/default/replicasets/name", - }, - Spec: extensionsv1.ReplicaSetSpec{ - Replicas: &replicas, - }, - } -} - -func newPod(name string) *apiv1.Pod { - return &apiv1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: metav1.NamespaceDefault, - }, - } -} diff --git a/federation/pkg/federatedtypes/secret.go b/federation/pkg/federatedtypes/secret.go deleted file mode 100644 index 09548ca62a1..00000000000 --- a/federation/pkg/federatedtypes/secret.go +++ /dev/null @@ -1,152 +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 federatedtypes - -import ( - apiv1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - pkgruntime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/watch" - kubeclientset "k8s.io/client-go/kubernetes" - restclient "k8s.io/client-go/rest" - federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - "k8s.io/kubernetes/federation/pkg/federation-controller/util" -) - -const ( - SecretKind = "secret" - SecretControllerName = "secrets" -) - -func init() { - RegisterFederatedType(SecretKind, SecretControllerName, []schema.GroupVersionResource{apiv1.SchemeGroupVersion.WithResource(SecretControllerName)}, NewSecretAdapter) -} - -type SecretAdapter struct { - client federationclientset.Interface -} - -func NewSecretAdapter(client federationclientset.Interface, config *restclient.Config, adapterSpecificArgs map[string]interface{}) FederatedTypeAdapter { - return &SecretAdapter{client: client} -} - -func (a *SecretAdapter) Kind() string { - return SecretKind -} - -func (a *SecretAdapter) ObjectType() pkgruntime.Object { - return &apiv1.Secret{} -} - -func (a *SecretAdapter) IsExpectedType(obj interface{}) bool { - _, ok := obj.(*apiv1.Secret) - return ok -} - -func (a *SecretAdapter) Copy(obj pkgruntime.Object) pkgruntime.Object { - secret := obj.(*apiv1.Secret) - return &apiv1.Secret{ - ObjectMeta: util.DeepCopyRelevantObjectMeta(secret.ObjectMeta), - Data: secret.Data, - Type: secret.Type, - } -} - -func (a *SecretAdapter) Equivalent(obj1, obj2 pkgruntime.Object) bool { - secret1 := obj1.(*apiv1.Secret) - secret2 := obj2.(*apiv1.Secret) - return util.SecretEquivalent(*secret1, *secret2) -} - -func (a *SecretAdapter) QualifiedName(obj pkgruntime.Object) QualifiedName { - secret := obj.(*apiv1.Secret) - return QualifiedName{Namespace: secret.Namespace, Name: secret.Name} -} - -func (a *SecretAdapter) ObjectMeta(obj pkgruntime.Object) *metav1.ObjectMeta { - return &obj.(*apiv1.Secret).ObjectMeta -} - -func (a *SecretAdapter) FedCreate(obj pkgruntime.Object) (pkgruntime.Object, error) { - secret := obj.(*apiv1.Secret) - return a.client.CoreV1().Secrets(secret.Namespace).Create(secret) -} - -func (a *SecretAdapter) FedDelete(qualifiedName QualifiedName, options *metav1.DeleteOptions) error { - return a.client.CoreV1().Secrets(qualifiedName.Namespace).Delete(qualifiedName.Name, options) -} - -func (a *SecretAdapter) FedGet(qualifiedName QualifiedName) (pkgruntime.Object, error) { - return a.client.CoreV1().Secrets(qualifiedName.Namespace).Get(qualifiedName.Name, metav1.GetOptions{}) -} - -func (a *SecretAdapter) FedList(namespace string, options metav1.ListOptions) (pkgruntime.Object, error) { - return a.client.CoreV1().Secrets(namespace).List(options) -} - -func (a *SecretAdapter) FedUpdate(obj pkgruntime.Object) (pkgruntime.Object, error) { - secret := obj.(*apiv1.Secret) - return a.client.CoreV1().Secrets(secret.Namespace).Update(secret) -} - -func (a *SecretAdapter) FedWatch(namespace string, options metav1.ListOptions) (watch.Interface, error) { - return a.client.CoreV1().Secrets(namespace).Watch(options) -} - -func (a *SecretAdapter) ClusterCreate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) { - secret := obj.(*apiv1.Secret) - return client.CoreV1().Secrets(secret.Namespace).Create(secret) -} - -func (a *SecretAdapter) ClusterDelete(client kubeclientset.Interface, qualifiedName QualifiedName, options *metav1.DeleteOptions) error { - return client.CoreV1().Secrets(qualifiedName.Namespace).Delete(qualifiedName.Name, options) -} - -func (a *SecretAdapter) ClusterGet(client kubeclientset.Interface, qualifiedName QualifiedName) (pkgruntime.Object, error) { - return client.CoreV1().Secrets(qualifiedName.Namespace).Get(qualifiedName.Name, metav1.GetOptions{}) -} - -func (a *SecretAdapter) ClusterList(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (pkgruntime.Object, error) { - return client.CoreV1().Secrets(namespace).List(options) -} - -func (a *SecretAdapter) ClusterUpdate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) { - secret := obj.(*apiv1.Secret) - return client.CoreV1().Secrets(secret.Namespace).Update(secret) -} - -func (a *SecretAdapter) ClusterWatch(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (watch.Interface, error) { - return client.CoreV1().Secrets(namespace).Watch(options) -} - -func (a *SecretAdapter) IsSchedulingAdapter() bool { - return false -} - -func (a *SecretAdapter) NewTestObject(namespace string) pkgruntime.Object { - return &apiv1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: "test-secret-", - Namespace: namespace, - }, - Data: map[string][]byte{ - "A": []byte("ala ma kota"), - }, - Type: apiv1.SecretTypeOpaque, - } -} diff --git a/federation/pkg/federation-controller/BUILD b/federation/pkg/federation-controller/BUILD deleted file mode 100644 index 0a1dc2f9770..00000000000 --- a/federation/pkg/federation-controller/BUILD +++ /dev/null @@ -1,33 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["doc.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller", -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/pkg/federation-controller/cluster:all-srcs", - "//federation/pkg/federation-controller/ingress:all-srcs", - "//federation/pkg/federation-controller/job:all-srcs", - "//federation/pkg/federation-controller/service:all-srcs", - "//federation/pkg/federation-controller/sync:all-srcs", - "//federation/pkg/federation-controller/util:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/pkg/federation-controller/OWNERS b/federation/pkg/federation-controller/OWNERS deleted file mode 100644 index 571c01c0529..00000000000 --- a/federation/pkg/federation-controller/OWNERS +++ /dev/null @@ -1,8 +0,0 @@ -approvers: -- quinton-hoole -- nikhiljindal -- madhusudancs -reviewers: -- quinton-hoole -- nikhiljindal -- madhusudancs diff --git a/federation/pkg/federation-controller/cluster/BUILD b/federation/pkg/federation-controller/cluster/BUILD deleted file mode 100644 index 9c50487b2fa..00000000000 --- a/federation/pkg/federation-controller/cluster/BUILD +++ /dev/null @@ -1,67 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = ["clustercontroller_test.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/cluster", - library = ":go_default_library", - deps = [ - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/client/clientset_generated/federation_clientset:go_default_library", - "//pkg/api/testapi:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", - ], -) - -go_library( - name = "go_default_library", - srcs = [ - "cluster_client.go", - "clustercontroller.go", - "doc.go", - ], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/cluster", - deps = [ - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/client/cache:go_default_library", - "//federation/client/clientset_generated/federation_clientset:go_default_library", - "//federation/pkg/federation-controller/util:go_default_library", - "//pkg/api:go_default_library", - "//pkg/client/clientset_generated/internalclientset:go_default_library", - "//pkg/controller:go_default_library", - "//pkg/kubelet/apis:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - "//vendor/k8s.io/client-go/tools/cache:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/pkg/federation-controller/cluster/cluster_client.go b/federation/pkg/federation-controller/cluster/cluster_client.go deleted file mode 100644 index c9f963588c6..00000000000 --- a/federation/pkg/federation-controller/cluster/cluster_client.go +++ /dev/null @@ -1,170 +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 cluster - -import ( - "fmt" - "strings" - - "github.com/golang/glog" - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/sets" - restclient "k8s.io/client-go/rest" - federation_v1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1" - "k8s.io/kubernetes/federation/pkg/federation-controller/util" - "k8s.io/kubernetes/pkg/api" - clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" - kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis" -) - -const ( - UserAgentName = "Cluster-Controller" -) - -type ClusterClient struct { - kubeClient *clientset.Clientset -} - -func NewClusterClientSet(c *federation_v1beta1.Cluster) (*ClusterClient, error) { - clusterConfig, err := util.BuildClusterConfig(c) - if err != nil { - return nil, err - } - var clusterClientSet = ClusterClient{} - if clusterConfig != nil { - clusterClientSet.kubeClient = clientset.NewForConfigOrDie((restclient.AddUserAgent(clusterConfig, UserAgentName))) - if clusterClientSet.kubeClient == nil { - return nil, nil - } - } - return &clusterClientSet, nil -} - -// GetClusterHealthStatus gets the kubernetes cluster health status by requesting "/healthz" -func (self *ClusterClient) GetClusterHealthStatus() *federation_v1beta1.ClusterStatus { - clusterStatus := federation_v1beta1.ClusterStatus{} - currentTime := metav1.Now() - newClusterReadyCondition := federation_v1beta1.ClusterCondition{ - Type: federation_v1beta1.ClusterReady, - Status: v1.ConditionTrue, - Reason: "ClusterReady", - Message: "/healthz responded with ok", - LastProbeTime: currentTime, - LastTransitionTime: currentTime, - } - newClusterNotReadyCondition := federation_v1beta1.ClusterCondition{ - Type: federation_v1beta1.ClusterReady, - Status: v1.ConditionFalse, - Reason: "ClusterNotReady", - Message: "/healthz responded without ok", - LastProbeTime: currentTime, - LastTransitionTime: currentTime, - } - newNodeOfflineCondition := federation_v1beta1.ClusterCondition{ - Type: federation_v1beta1.ClusterOffline, - Status: v1.ConditionTrue, - Reason: "ClusterNotReachable", - Message: "cluster is not reachable", - LastProbeTime: currentTime, - LastTransitionTime: currentTime, - } - newNodeNotOfflineCondition := federation_v1beta1.ClusterCondition{ - Type: federation_v1beta1.ClusterOffline, - Status: v1.ConditionFalse, - Reason: "ClusterReachable", - Message: "cluster is reachable", - LastProbeTime: currentTime, - LastTransitionTime: currentTime, - } - body, err := self.kubeClient.DiscoveryClient.RESTClient().Get().AbsPath("/healthz").Do().Raw() - if err != nil { - clusterStatus.Conditions = append(clusterStatus.Conditions, newNodeOfflineCondition) - } else { - if !strings.EqualFold(string(body), "ok") { - clusterStatus.Conditions = append(clusterStatus.Conditions, newClusterNotReadyCondition, newNodeNotOfflineCondition) - } else { - clusterStatus.Conditions = append(clusterStatus.Conditions, newClusterReadyCondition) - } - } - - zones, region, err := self.GetClusterZones() - if err != nil { - glog.Warningf("Failed to get zones and region for cluster with client %v: %v", self, err) - } else { - clusterStatus.Zones = zones - clusterStatus.Region = region - } - - return &clusterStatus -} - -// GetClusterZones gets the kubernetes cluster zones and region by inspecting labels on nodes in the cluster. -func (self *ClusterClient) GetClusterZones() (zones []string, region string, err error) { - return getZoneNames(self.kubeClient) -} - -// Find the name of the zone in which a Node is running -func getZoneNameForNode(node api.Node) (string, error) { - for key, value := range node.Labels { - if key == kubeletapis.LabelZoneFailureDomain { - return value, nil - } - } - return "", fmt.Errorf("Zone name for node %s not found. No label with key %s", - node.Name, kubeletapis.LabelZoneFailureDomain) -} - -// Find the name of the region in which a Node is running -func getRegionNameForNode(node api.Node) (string, error) { - for key, value := range node.Labels { - if key == kubeletapis.LabelZoneRegion { - return value, nil - } - } - return "", fmt.Errorf("Region name for node %s not found. No label with key %s", - node.Name, kubeletapis.LabelZoneRegion) -} - -// Find the names of all zones and the region in which we have nodes in this cluster. -func getZoneNames(client *clientset.Clientset) (zones []string, region string, err error) { - zoneNames := sets.NewString() - nodes, err := client.Core().Nodes().List(metav1.ListOptions{}) - if err != nil { - glog.Errorf("Failed to list nodes while getting zone names: %v", err) - return nil, "", err - } - for i, node := range nodes.Items { - // TODO: quinton-hoole make this more efficient. - // For non-multi-zone clusters the zone will - // be identical for all nodes, so we only need to look at one node - // For multi-zone clusters we know at build time - // which zones are included. Rather get this info from there, because it's cheaper. - zoneName, err := getZoneNameForNode(node) - if err != nil { - return nil, "", err - } - zoneNames.Insert(zoneName) - if i == 0 { - region, err = getRegionNameForNode(node) - if err != nil { - return nil, "", err - } - } - } - return zoneNames.List(), region, nil -} diff --git a/federation/pkg/federation-controller/cluster/clustercontroller.go b/federation/pkg/federation-controller/cluster/clustercontroller.go deleted file mode 100644 index 6820a71e766..00000000000 --- a/federation/pkg/federation-controller/cluster/clustercontroller.go +++ /dev/null @@ -1,209 +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 cluster - -import ( - "strings" - "sync" - "time" - - "github.com/golang/glog" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/apimachinery/pkg/watch" - restclient "k8s.io/client-go/rest" - "k8s.io/client-go/tools/cache" - federationv1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1" - clustercache "k8s.io/kubernetes/federation/client/cache" - federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - "k8s.io/kubernetes/pkg/controller" -) - -type ClusterController struct { - // federationClient used to operate cluster - federationClient federationclientset.Interface - - // clusterMonitorPeriod is the period for updating status of cluster - clusterMonitorPeriod time.Duration - - mu sync.RWMutex - knownClusterSet sets.String - // clusterClusterStatusMap is a mapping of clusterName and cluster status of last sampling - clusterClusterStatusMap map[string]federationv1beta1.ClusterStatus - // clusterKubeClientMap is a mapping of clusterName and restclient - clusterKubeClientMap map[string]ClusterClient - - // cluster framework and store - clusterController cache.Controller - clusterStore clustercache.StoreToClusterLister -} - -// StartClusterController starts a new cluster controller -func StartClusterController(config *restclient.Config, stopChan <-chan struct{}, clusterMonitorPeriod time.Duration) { - restclient.AddUserAgent(config, "cluster-controller") - client := federationclientset.NewForConfigOrDie(config) - controller := newClusterController(client, clusterMonitorPeriod) - glog.Infof("Starting cluster controller") - controller.Run(stopChan) -} - -// newClusterController returns a new cluster controller -func newClusterController(federationClient federationclientset.Interface, clusterMonitorPeriod time.Duration) *ClusterController { - cc := &ClusterController{ - knownClusterSet: make(sets.String), - federationClient: federationClient, - clusterMonitorPeriod: clusterMonitorPeriod, - clusterClusterStatusMap: make(map[string]federationv1beta1.ClusterStatus), - clusterKubeClientMap: make(map[string]ClusterClient), - } - cc.clusterStore.Store, cc.clusterController = cache.NewInformer( - &cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { - return cc.federationClient.Federation().Clusters().List(options) - }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return cc.federationClient.Federation().Clusters().Watch(options) - }, - }, - &federationv1beta1.Cluster{}, - controller.NoResyncPeriodFunc(), - cache.ResourceEventHandlerFuncs{ - DeleteFunc: cc.delFromClusterSet, - AddFunc: cc.addToClusterSet, - }, - ) - return cc -} - -// delFromClusterSet delete a cluster from clusterSet and -// delete the corresponding restclient from the map clusterKubeClientMap -func (cc *ClusterController) delFromClusterSet(obj interface{}) { - cc.mu.Lock() - defer cc.mu.Unlock() - cluster := obj.(*federationv1beta1.Cluster) - cc.delFromClusterSetByName(cluster.Name) -} - -// delFromClusterSetByName delete a cluster from clusterSet by name and -// delete the corresponding restclient from the map clusterKubeClientMap. -// Caller must make sure that they hold the mutex -func (cc *ClusterController) delFromClusterSetByName(clusterName string) { - glog.V(1).Infof("ClusterController observed a cluster deletion: %v", clusterName) - cc.knownClusterSet.Delete(clusterName) - delete(cc.clusterKubeClientMap, clusterName) - delete(cc.clusterClusterStatusMap, clusterName) -} - -func (cc *ClusterController) addToClusterSet(obj interface{}) { - cc.mu.Lock() - defer cc.mu.Unlock() - cluster := obj.(*federationv1beta1.Cluster) - cc.addToClusterSetWithoutLock(cluster) -} - -// addToClusterSetWithoutLock inserts the new cluster to clusterSet and create -// a corresponding restclient to map clusterKubeClientMap if the cluster is not -// known. Caller must make sure that they hold the mutex. -func (cc *ClusterController) addToClusterSetWithoutLock(cluster *federationv1beta1.Cluster) { - if cc.knownClusterSet.Has(cluster.Name) { - return - } - glog.V(1).Infof("ClusterController observed a new cluster: %v", cluster.Name) - cc.knownClusterSet.Insert(cluster.Name) - // create the restclient of cluster - restClient, err := NewClusterClientSet(cluster) - if err != nil || restClient == nil { - glog.Errorf("Failed to create corresponding restclient of kubernetes cluster: %v", err) - return - } - cc.clusterKubeClientMap[cluster.Name] = *restClient -} - -// Run begins watching and syncing. -func (cc *ClusterController) Run(stopChan <-chan struct{}) { - defer utilruntime.HandleCrash() - go cc.clusterController.Run(stopChan) - // monitor cluster status periodically, in phase 1 we just get the health state from "/healthz" - go wait.Until(func() { - if err := cc.updateClusterStatus(); err != nil { - glog.Errorf("Error monitoring cluster status: %v", err) - } - }, cc.clusterMonitorPeriod, stopChan) -} - -// updateClusterStatus checks cluster status and get the metrics from cluster's restapi -func (cc *ClusterController) updateClusterStatus() error { - clusters, err := cc.federationClient.Federation().Clusters().List(metav1.ListOptions{}) - if err != nil { - return err - } - - for _, cluster := range clusters.Items { - cc.mu.RLock() - // skip updating status of the cluster which is not yet added to knownClusterSet. - if !cc.knownClusterSet.Has(cluster.Name) { - cc.mu.RUnlock() - continue - } - clusterClient, clientFound := cc.clusterKubeClientMap[cluster.Name] - clusterStatusOld, statusFound := cc.clusterClusterStatusMap[cluster.Name] - cc.mu.RUnlock() - - if !clientFound { - glog.Warningf("Failed to get client for cluster %s", cluster.Name) - continue - } - clusterStatusNew := clusterClient.GetClusterHealthStatus() - if !statusFound { - glog.Infof("There is no status stored for cluster: %v before", cluster.Name) - } else { - hasTransition := false - if len(clusterStatusNew.Conditions) != len(clusterStatusOld.Conditions) { - hasTransition = true - } else { - for i := 0; i < len(clusterStatusNew.Conditions); i++ { - if !(strings.EqualFold(string(clusterStatusNew.Conditions[i].Type), string(clusterStatusOld.Conditions[i].Type)) && - strings.EqualFold(string(clusterStatusNew.Conditions[i].Status), string(clusterStatusOld.Conditions[i].Status))) { - hasTransition = true - break - } - } - } - - if !hasTransition { - for j := 0; j < len(clusterStatusNew.Conditions); j++ { - clusterStatusNew.Conditions[j].LastTransitionTime = clusterStatusOld.Conditions[j].LastTransitionTime - } - } - } - - cc.mu.Lock() - cc.clusterClusterStatusMap[cluster.Name] = *clusterStatusNew - cc.mu.Unlock() - cluster.Status = *clusterStatusNew - cluster, err := cc.federationClient.Federation().Clusters().UpdateStatus(&cluster) - if err != nil { - glog.Warningf("Failed to update the status of cluster: %v ,error is : %v", cluster.Name, err) - // Don't return err here, as we want to continue processing remaining clusters. - continue - } - } - return nil -} diff --git a/federation/pkg/federation-controller/cluster/clustercontroller_test.go b/federation/pkg/federation-controller/cluster/clustercontroller_test.go deleted file mode 100644 index 8ae0acc029d..00000000000 --- a/federation/pkg/federation-controller/cluster/clustercontroller_test.go +++ /dev/null @@ -1,173 +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 cluster - -import ( - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "testing" - "time" - - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/uuid" - restclient "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - federationv1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1" - federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - "k8s.io/kubernetes/pkg/api/testapi" -) - -func newCluster(clusterName string, serverUrl string) *federationv1beta1.Cluster { - cluster := federationv1beta1.Cluster{ - TypeMeta: metav1.TypeMeta{APIVersion: testapi.Federation.GroupVersion().String()}, - ObjectMeta: metav1.ObjectMeta{ - UID: uuid.NewUUID(), - Name: clusterName, - }, - Spec: federationv1beta1.ClusterSpec{ - ServerAddressByClientCIDRs: []federationv1beta1.ServerAddressByClientCIDR{ - { - ClientCIDR: "0.0.0.0/0", - ServerAddress: serverUrl, - }, - }, - }, - } - return &cluster -} - -func newClusterList(cluster *federationv1beta1.Cluster) *federationv1beta1.ClusterList { - clusterList := federationv1beta1.ClusterList{ - TypeMeta: metav1.TypeMeta{APIVersion: testapi.Federation.GroupVersion().String()}, - ListMeta: metav1.ListMeta{ - SelfLink: "foobar", - }, - Items: []federationv1beta1.Cluster{}, - } - clusterList.Items = append(clusterList.Items, *cluster) - return &clusterList -} - -// init a fake http handler, simulate a federation apiserver, response the "DELETE" "PUT" "GET" "UPDATE" -// when "canBeGotten" is false, means that user can not get the cluster cluster from apiserver -func createHttptestFakeHandlerForFederation(clusterList *federationv1beta1.ClusterList, canBeGotten bool) *http.HandlerFunc { - fakeHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - clusterListString, _ := json.Marshal(*clusterList) - w.Header().Set("Content-Type", "application/json") - switch r.Method { - case "PUT": - fmt.Fprintln(w, string(clusterListString)) - case "GET": - if canBeGotten { - fmt.Fprintln(w, string(clusterListString)) - } else { - fmt.Fprintln(w, "") - } - default: - fmt.Fprintln(w, "") - } - }) - return &fakeHandler -} - -// init a fake http handler, simulate a cluster apiserver, response the "/healthz" -// when "canBeGotten" is false, means that user can not get response from apiserver -func createHttptestFakeHandlerForCluster(canBeGotten bool) *http.HandlerFunc { - fakeHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - switch r.Method { - case "GET": - if canBeGotten { - fmt.Fprintln(w, "ok") - } else { - w.WriteHeader(http.StatusNotFound) - } - default: - fmt.Fprintln(w, "") - } - }) - return &fakeHandler -} - -func TestUpdateClusterStatusOK(t *testing.T) { - clusterName := "foobarCluster" - // create dummy httpserver - testClusterServer := httptest.NewServer(createHttptestFakeHandlerForCluster(true)) - defer testClusterServer.Close() - federationCluster := newCluster(clusterName, testClusterServer.URL) - federationClusterList := newClusterList(federationCluster) - - testFederationServer := httptest.NewServer(createHttptestFakeHandlerForFederation(federationClusterList, true)) - defer testFederationServer.Close() - - restClientCfg, err := clientcmd.BuildConfigFromFlags(testFederationServer.URL, "") - if err != nil { - t.Errorf("Failed to build client config") - } - federationClientSet := federationclientset.NewForConfigOrDie(restclient.AddUserAgent(restClientCfg, "cluster-controller")) - - manager := newClusterController(federationClientSet, 5) - manager.addToClusterSet(federationCluster) - err = manager.updateClusterStatus() - if err != nil { - t.Errorf("Failed to Update Cluster Status: %v", err) - } - clusterStatus, found := manager.clusterClusterStatusMap[clusterName] - if !found { - t.Errorf("Failed to Update Cluster Status") - } else { - if (clusterStatus.Conditions[1].Status != v1.ConditionFalse) || (clusterStatus.Conditions[1].Type != federationv1beta1.ClusterOffline) { - t.Errorf("Failed to Update Cluster Status") - } - } -} - -// Test races between informer's updates and routine updates of cluster status -// Issue https://github.com/kubernetes/kubernetes/issues/49958 -func TestUpdateClusterRace(t *testing.T) { - clusterName := "foobarCluster" - // create dummy httpserver - testClusterServer := httptest.NewServer(createHttptestFakeHandlerForCluster(true)) - defer testClusterServer.Close() - federationCluster := newCluster(clusterName, testClusterServer.URL) - federationClusterList := newClusterList(federationCluster) - - testFederationServer := httptest.NewServer(createHttptestFakeHandlerForFederation(federationClusterList, true)) - defer testFederationServer.Close() - - restClientCfg, err := clientcmd.BuildConfigFromFlags(testFederationServer.URL, "") - if err != nil { - t.Errorf("Failed to build client config") - } - federationClientSet := federationclientset.NewForConfigOrDie(restclient.AddUserAgent(restClientCfg, "cluster-controller")) - - manager := newClusterController(federationClientSet, 1*time.Millisecond) - - stop := make(chan struct{}) - manager.Run(stop) - - // try to trigger the race in UpdateClusterStatus - for i := 0; i < 10; i++ { - manager.addToClusterSet(federationCluster) - manager.delFromClusterSet(federationCluster) - } - - close(stop) -} diff --git a/federation/pkg/federation-controller/cluster/doc.go b/federation/pkg/federation-controller/cluster/doc.go deleted file mode 100644 index d311db09456..00000000000 --- a/federation/pkg/federation-controller/cluster/doc.go +++ /dev/null @@ -1,18 +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 cluster contains code for syncing cluster -package cluster // import "k8s.io/kubernetes/federation/pkg/federation-controller/cluster" diff --git a/federation/pkg/federation-controller/doc.go b/federation/pkg/federation-controller/doc.go deleted file mode 100644 index 657ce63c1eb..00000000000 --- a/federation/pkg/federation-controller/doc.go +++ /dev/null @@ -1,19 +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 federation_controller contains code for controllers (like the cluster -// controller). -package federation_controller // import "k8s.io/kubernetes/federation/pkg/federation-controller" diff --git a/federation/pkg/federation-controller/ingress/BUILD b/federation/pkg/federation-controller/ingress/BUILD deleted file mode 100644 index 8c094ac5b21..00000000000 --- a/federation/pkg/federation-controller/ingress/BUILD +++ /dev/null @@ -1,76 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = ["ingress_controller.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/ingress", - deps = [ - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/client/clientset_generated/federation_clientset:go_default_library", - "//federation/pkg/federation-controller/util:go_default_library", - "//federation/pkg/federation-controller/util/clusterselector:go_default_library", - "//federation/pkg/federation-controller/util/deletionhelper:go_default_library", - "//federation/pkg/federation-controller/util/eventsink:go_default_library", - "//pkg/api:go_default_library", - "//pkg/controller:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/tools/cache:go_default_library", - "//vendor/k8s.io/client-go/tools/record:go_default_library", - "//vendor/k8s.io/client-go/util/flowcontrol:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["ingress_controller_test.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/ingress", - library = ":go_default_library", - deps = [ - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/client/clientset_generated/federation_clientset/fake:go_default_library", - "//federation/pkg/federation-controller/util:go_default_library", - "//federation/pkg/federation-controller/util/deletionhelper:go_default_library", - "//federation/pkg/federation-controller/util/finalizers:go_default_library", - "//federation/pkg/federation-controller/util/test:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/github.com/stretchr/testify/assert:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", - "//vendor/k8s.io/client-go/tools/cache:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/pkg/federation-controller/ingress/ingress_controller.go b/federation/pkg/federation-controller/ingress/ingress_controller.go deleted file mode 100644 index e7a8680b5e7..00000000000 --- a/federation/pkg/federation-controller/ingress/ingress_controller.go +++ /dev/null @@ -1,889 +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 ingress - -import ( - "crypto/md5" - "fmt" - "sync" - "time" - - "k8s.io/api/core/v1" - extensionsv1beta1 "k8s.io/api/extensions/v1beta1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - pkgruntime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/watch" - kubeclientset "k8s.io/client-go/kubernetes" - "k8s.io/client-go/tools/cache" - "k8s.io/client-go/tools/record" - "k8s.io/client-go/util/flowcontrol" - federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" - federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - "k8s.io/kubernetes/federation/pkg/federation-controller/util" - "k8s.io/kubernetes/federation/pkg/federation-controller/util/clusterselector" - "k8s.io/kubernetes/federation/pkg/federation-controller/util/deletionhelper" - "k8s.io/kubernetes/federation/pkg/federation-controller/util/eventsink" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/controller" - - "github.com/golang/glog" -) - -const ( - // Special cluster name which denotes all clusters - only used internally. It's not a valid cluster name, so effectively reserved. - allClustersKey = ".ALL_CLUSTERS" - // TODO: Get the constants below directly from the Kubernetes Ingress Controller constants - but thats in a separate repo - staticIPNameKeyWritable = "kubernetes.io/ingress.global-static-ip-name" // The writable annotation on Ingress to tell the controller to use a specific, named, static IP - staticIPNameKeyReadonly = "ingress.kubernetes.io/static-ip" // The readonly key via which the cluster's Ingress Controller communicates which static IP it used. If staticIPNameKeyWritable above is specified, it is used. - uidAnnotationKey = "kubernetes.io/ingress.uid" // The annotation on federation clusters, where we store the ingress UID - uidConfigMapName = "ingress-uid" // Name of the config-map and key the ingress controller stores its uid in. - uidConfigMapNamespace = "kube-system" - uidKey = "uid" - providerUidKey = "provider-uid" - // Annotation on the ingress in federation control plane that is used to keep - // track of the first cluster in which we create ingress. - // We wait for ingress to be created in this cluster before creating it any - // other cluster. - firstClusterAnnotation = "ingress.federation.kubernetes.io/first-cluster" - ControllerName = "ingresses" - UserAgentName = "federation-ingresses-controller" -) - -var ( - RequiredResources = []schema.GroupVersionResource{extensionsv1beta1.SchemeGroupVersion.WithResource("ingresses")} -) - -type IngressController struct { - sync.Mutex // Lock used for leader election - // For triggering single ingress reconciliation. This is used when there is an - // add/update/delete operation on an ingress in either federated API server or - // in some member of the federation. - ingressDeliverer *util.DelayingDeliverer - - // For triggering reconciliation of cluster ingress controller configmap and - // all ingresses. This is used when a new cluster becomes available. - clusterDeliverer *util.DelayingDeliverer - - // For triggering reconciliation of cluster ingress controller configmap. - // This is used when a configmap is updated in the cluster. - configMapDeliverer *util.DelayingDeliverer - - // Contains ingresses present in members of federation. - ingressFederatedInformer util.FederatedInformer - // Contains ingress controller configmaps present in members of federation. - configMapFederatedInformer util.FederatedInformer - // For updating ingresses in members of federation. - federatedIngressUpdater util.FederatedUpdater - // For updating configmaps in members of federation. - federatedConfigMapUpdater util.FederatedUpdater - // Definitions of ingresses that should be federated. - ingressInformerStore cache.Store - // Informer controller for ingresses that should be federated. - ingressInformerController cache.Controller - - // Client to federated api server. - federatedApiClient federationclientset.Interface - - // Backoff manager for ingresses - ingressBackoff *flowcontrol.Backoff - // Backoff manager for configmaps - configMapBackoff *flowcontrol.Backoff - - // For events - eventRecorder record.EventRecorder - - deletionHelper *deletionhelper.DeletionHelper - - ingressReviewDelay time.Duration - configMapReviewDelay time.Duration - clusterAvailableDelay time.Duration - smallDelay time.Duration - updateTimeout time.Duration -} - -// NewIngressController returns a new ingress controller -func NewIngressController(client federationclientset.Interface) *IngressController { - glog.V(4).Infof("->NewIngressController V(4)") - broadcaster := record.NewBroadcaster() - broadcaster.StartRecordingToSink(eventsink.NewFederatedEventSink(client)) - recorder := broadcaster.NewRecorder(api.Scheme, v1.EventSource{Component: UserAgentName}) - ic := &IngressController{ - federatedApiClient: client, - ingressReviewDelay: time.Second * 10, - configMapReviewDelay: time.Second * 10, - clusterAvailableDelay: time.Second * 20, - smallDelay: time.Second * 3, - updateTimeout: time.Second * 30, - ingressBackoff: flowcontrol.NewBackOff(5*time.Second, time.Minute), - eventRecorder: recorder, - configMapBackoff: flowcontrol.NewBackOff(5*time.Second, time.Minute), - } - - // Build deliverers for triggering reconciliations. - ic.ingressDeliverer = util.NewDelayingDeliverer() - ic.clusterDeliverer = util.NewDelayingDeliverer() - ic.configMapDeliverer = util.NewDelayingDeliverer() - - // Start informer in federated API servers on ingresses that should be federated. - ic.ingressInformerStore, ic.ingressInformerController = cache.NewInformer( - &cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (pkgruntime.Object, error) { - return client.Extensions().Ingresses(metav1.NamespaceAll).List(options) - }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return client.Extensions().Ingresses(metav1.NamespaceAll).Watch(options) - }, - }, - &extensionsv1beta1.Ingress{}, - controller.NoResyncPeriodFunc(), - util.NewTriggerOnAllChanges( - func(obj pkgruntime.Object) { - ic.deliverIngressObj(obj, 0, false) - }, - )) - - // Federated informer on ingresses in members of federation. - ic.ingressFederatedInformer = util.NewFederatedInformer( - client, - func(cluster *federationapi.Cluster, targetClient kubeclientset.Interface) (cache.Store, cache.Controller) { - return cache.NewInformer( - &cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (pkgruntime.Object, error) { - return targetClient.Extensions().Ingresses(metav1.NamespaceAll).List(options) - }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return targetClient.Extensions().Ingresses(metav1.NamespaceAll).Watch(options) - }, - }, - &extensionsv1beta1.Ingress{}, - controller.NoResyncPeriodFunc(), - // Trigger reconciliation whenever something in federated cluster is changed. In most cases it - // would be just confirmation that some ingress operation succeeded. - util.NewTriggerOnAllChanges( - func(obj pkgruntime.Object) { - ic.deliverIngressObj(obj, ic.ingressReviewDelay, false) - }, - )) - }, - - &util.ClusterLifecycleHandlerFuncs{ - ClusterAvailable: func(cluster *federationapi.Cluster) { - // When new cluster becomes available process all the ingresses again, and configure it's ingress controller's configmap with the correct UID - ic.clusterDeliverer.DeliverAfter(cluster.Name, cluster, ic.clusterAvailableDelay) - }, - }, - ) - - // Federated informer on configmaps for ingress controllers in members of the federation. - ic.configMapFederatedInformer = util.NewFederatedInformer( - client, - func(cluster *federationapi.Cluster, targetClient kubeclientset.Interface) (cache.Store, cache.Controller) { - glog.V(4).Infof("Returning new informer for cluster %q", cluster.Name) - return cache.NewInformer( - &cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (pkgruntime.Object, error) { - if targetClient == nil { - glog.Errorf("Internal error: targetClient is nil") - } - return targetClient.Core().ConfigMaps(uidConfigMapNamespace).List(options) // we only want to list one by name - unfortunately Kubernetes don't have a selector for that. - }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - if targetClient == nil { - glog.Errorf("Internal error: targetClient is nil") - } - return targetClient.Core().ConfigMaps(uidConfigMapNamespace).Watch(options) // as above - }, - }, - &v1.ConfigMap{}, - controller.NoResyncPeriodFunc(), - // Trigger reconciliation whenever the ingress controller's configmap in a federated cluster is changed. In most cases it - // would be just confirmation that the configmap for the ingress controller is correct. - util.NewTriggerOnAllChanges( - func(obj pkgruntime.Object) { - ic.deliverConfigMapObj(cluster.Name, obj, ic.configMapReviewDelay, false) - }, - )) - }, - - &util.ClusterLifecycleHandlerFuncs{ - ClusterAvailable: func(cluster *federationapi.Cluster) { - ic.clusterDeliverer.DeliverAfter(cluster.Name, cluster, ic.clusterAvailableDelay) - }, - }, - ) - - // Federated ingress updater along with Create/Update/Delete operations. - ic.federatedIngressUpdater = util.NewFederatedUpdater(ic.ingressFederatedInformer, "ingress", ic.updateTimeout, ic.eventRecorder, - func(client kubeclientset.Interface, obj pkgruntime.Object) error { - ingress := obj.(*extensionsv1beta1.Ingress) - glog.V(4).Infof("Attempting to create Ingress: %v", ingress) - _, err := client.Extensions().Ingresses(ingress.Namespace).Create(ingress) - if err != nil { - glog.Errorf("Error creating ingress %q: %v", types.NamespacedName{Name: ingress.Name, Namespace: ingress.Namespace}, err) - } else { - glog.V(4).Infof("Successfully created ingress %q", types.NamespacedName{Name: ingress.Name, Namespace: ingress.Namespace}) - } - return err - }, - func(client kubeclientset.Interface, obj pkgruntime.Object) error { - ingress := obj.(*extensionsv1beta1.Ingress) - glog.V(4).Infof("Attempting to update Ingress: %v", ingress) - _, err := client.Extensions().Ingresses(ingress.Namespace).Update(ingress) - if err != nil { - glog.V(4).Infof("Failed to update Ingress: %v", err) - } else { - glog.V(4).Infof("Successfully updated Ingress: %q", types.NamespacedName{Name: ingress.Name, Namespace: ingress.Namespace}) - } - return err - }, - func(client kubeclientset.Interface, obj pkgruntime.Object) error { - ingress := obj.(*extensionsv1beta1.Ingress) - glog.V(4).Infof("Attempting to delete Ingress: %v", ingress) - orphanDependents := false - err := client.Extensions().Ingresses(ingress.Namespace).Delete(ingress.Name, &metav1.DeleteOptions{OrphanDependents: &orphanDependents}) - return err - }) - - // Federated configmap updater along with Create/Update/Delete operations. Only Update should ever be called. - ic.federatedConfigMapUpdater = util.NewFederatedUpdater(ic.configMapFederatedInformer, "configmap", ic.updateTimeout, ic.eventRecorder, - func(client kubeclientset.Interface, obj pkgruntime.Object) error { - configMap := obj.(*v1.ConfigMap) - configMapName := types.NamespacedName{Name: configMap.Name, Namespace: configMap.Namespace} - glog.Errorf("Internal error: Incorrectly attempting to create ConfigMap: %q", configMapName) - _, err := client.Core().ConfigMaps(configMap.Namespace).Create(configMap) - return err - }, - func(client kubeclientset.Interface, obj pkgruntime.Object) error { - configMap := obj.(*v1.ConfigMap) - configMapName := types.NamespacedName{Name: configMap.Name, Namespace: configMap.Namespace} - glog.V(4).Infof("Attempting to update ConfigMap: %v", configMap) - _, err := client.Core().ConfigMaps(configMap.Namespace).Update(configMap) - if err == nil { - glog.V(4).Infof("Successfully updated ConfigMap %q %v", configMapName, configMap) - } else { - glog.V(4).Infof("Failed to update ConfigMap %q: %v", configMapName, err) - } - return err - }, - func(client kubeclientset.Interface, obj pkgruntime.Object) error { - configMap := obj.(*v1.ConfigMap) - configMapName := types.NamespacedName{Name: configMap.Name, Namespace: configMap.Namespace} - glog.Errorf("Internal error: Incorrectly attempting to delete ConfigMap: %q", configMapName) - err := client.Core().ConfigMaps(configMap.Namespace).Delete(configMap.Name, &metav1.DeleteOptions{}) - return err - }) - - ic.deletionHelper = deletionhelper.NewDeletionHelper( - ic.updateIngress, - // objNameFunc - func(obj pkgruntime.Object) string { - ingress := obj.(*extensionsv1beta1.Ingress) - return fmt.Sprintf("%s/%s", ingress.Namespace, ingress.Name) - }, - ic.ingressFederatedInformer, - ic.federatedIngressUpdater, - ) - return ic -} - -// Sends the given updated object to apiserver. -// Assumes that the given object is an ingress. -func (ic *IngressController) updateIngress(obj pkgruntime.Object) (pkgruntime.Object, error) { - ingress := obj.(*extensionsv1beta1.Ingress) - return ic.federatedApiClient.Extensions().Ingresses(ingress.Namespace).Update(ingress) -} - -func (ic *IngressController) Run(stopChan <-chan struct{}) { - glog.Infof("Starting Ingress Controller") - go ic.ingressInformerController.Run(stopChan) - glog.Infof("... Starting Ingress Federated Informer") - ic.ingressFederatedInformer.Start() - glog.Infof("... Starting ConfigMap Federated Informer") - ic.configMapFederatedInformer.Start() - go func() { - <-stopChan - glog.Infof("Stopping Ingress Federated Informer") - ic.ingressFederatedInformer.Stop() - glog.Infof("Stopping ConfigMap Federated Informer") - ic.configMapFederatedInformer.Stop() - glog.Infof("Stopping ingress deliverer") - ic.ingressDeliverer.Stop() - glog.Infof("Stopping configmap deliverer") - ic.configMapDeliverer.Stop() - glog.Infof("Stopping cluster deliverer") - ic.clusterDeliverer.Stop() - }() - ic.ingressDeliverer.StartWithHandler(func(item *util.DelayingDelivererItem) { - ingress := item.Value.(types.NamespacedName) - glog.V(4).Infof("Ingress change delivered, reconciling: %v", ingress) - ic.reconcileIngress(ingress) - }) - ic.clusterDeliverer.StartWithHandler(func(item *util.DelayingDelivererItem) { - clusterName := item.Key - if clusterName != allClustersKey { - glog.V(4).Infof("Cluster change delivered for cluster %q, reconciling configmap and ingress for that cluster", clusterName) - } else { - glog.V(4).Infof("Cluster change delivered for all clusters, reconciling configmaps and ingresses for all clusters") - } - ic.reconcileIngressesOnClusterChange(clusterName) - ic.reconcileConfigMapForCluster(clusterName) - }) - ic.configMapDeliverer.StartWithHandler(func(item *util.DelayingDelivererItem) { - clusterName := item.Key - if clusterName != allClustersKey { - glog.V(4).Infof("ConfigMap change delivered for cluster %q, reconciling configmap for that cluster", clusterName) - } else { - glog.V(4).Infof("ConfigMap change delivered for all clusters, reconciling configmaps for all clusters") - } - ic.reconcileConfigMapForCluster(clusterName) - }) - - util.StartBackoffGC(ic.ingressBackoff, stopChan) - util.StartBackoffGC(ic.configMapBackoff, stopChan) -} - -func (ic *IngressController) deliverIngressObj(obj interface{}, delay time.Duration, failed bool) { - ingress := obj.(*extensionsv1beta1.Ingress) - ic.deliverIngress(types.NamespacedName{Namespace: ingress.Namespace, Name: ingress.Name}, delay, failed) -} - -func (ic *IngressController) deliverIngress(ingress types.NamespacedName, delay time.Duration, failed bool) { - glog.V(4).Infof("Delivering ingress: %s with delay: %v error: %v", ingress, delay, failed) - key := ingress.String() - if failed { - ic.ingressBackoff.Next(key, time.Now()) - delay = delay + ic.ingressBackoff.Get(key) - } else { - ic.ingressBackoff.Reset(key) - } - ic.ingressDeliverer.DeliverAfter(key, ingress, delay) -} - -func (ic *IngressController) deliverConfigMapObj(clusterName string, obj interface{}, delay time.Duration, failed bool) { - configMap := obj.(*v1.ConfigMap) - ic.deliverConfigMap(clusterName, types.NamespacedName{Namespace: configMap.Namespace, Name: configMap.Name}, delay, failed) -} - -func (ic *IngressController) deliverConfigMap(cluster string, configMap types.NamespacedName, delay time.Duration, failed bool) { - key := cluster - if failed { - ic.configMapBackoff.Next(key, time.Now()) - delay = delay + ic.configMapBackoff.Get(key) - } else { - ic.configMapBackoff.Reset(key) - } - glog.V(4).Infof("Delivering ConfigMap for cluster %q (delay %q): %s", cluster, delay, configMap) - ic.configMapDeliverer.DeliverAfter(key, configMap, delay) -} - -// Check whether all data stores are in sync. False is returned if any of the informer/stores is not yet -// synced with the corresponding api server. -func (ic *IngressController) isSynced() bool { - if !ic.ingressFederatedInformer.ClustersSynced() { - glog.V(2).Infof("Cluster list not synced for ingress federated informer") - return false - } - clusters, err := ic.ingressFederatedInformer.GetReadyClusters() - if err != nil { - glog.Errorf("Failed to get ready clusters for ingress federated informer: %v", err) - return false - } - if !ic.ingressFederatedInformer.GetTargetStore().ClustersSynced(clusters) { - glog.V(2).Infof("Target store not synced for ingress federated informer") - return false - } - if !ic.configMapFederatedInformer.ClustersSynced() { - glog.V(2).Infof("Cluster list not synced for config map federated informer") - return false - } - clusters, err = ic.configMapFederatedInformer.GetReadyClusters() - if err != nil { - glog.Errorf("Failed to get ready clusters for configmap federated informer: %v", err) - return false - } - if !ic.configMapFederatedInformer.GetTargetStore().ClustersSynced(clusters) { - glog.V(2).Infof("Target store not synced for configmap federated informer") - return false - } - glog.V(4).Infof("Cluster list is synced") - return true -} - -// The function triggers reconciliation of all federated ingresses. clusterName is the name of the cluster that changed -// but all ingresses in all clusters are reconciled -func (ic *IngressController) reconcileIngressesOnClusterChange(clusterName string) { - glog.V(4).Infof("Reconciling ingresses on cluster change for cluster %q", clusterName) - if !ic.isSynced() { - glog.V(4).Infof("Not synced, will try again later to reconcile ingresses.") - ic.clusterDeliverer.DeliverAfter(clusterName, nil, ic.clusterAvailableDelay) - } - ingressList := ic.ingressInformerStore.List() - if len(ingressList) <= 0 { - glog.V(4).Infof("No federated ingresses to reconcile.") - } - - for _, obj := range ingressList { - ingress := obj.(*extensionsv1beta1.Ingress) - nsName := types.NamespacedName{Name: ingress.Name, Namespace: ingress.Namespace} - glog.V(4).Infof("Delivering federated ingress %q for cluster %q", nsName, clusterName) - ic.deliverIngress(nsName, ic.smallDelay, false) - } -} - -/* - reconcileConfigMapForCluster ensures that the configmap for the ingress controller in the cluster has objectmeta.data.UID - consistent with all the other clusters in the federation. If clusterName == allClustersKey, then all available clusters - configmaps are reconciled. -*/ -func (ic *IngressController) reconcileConfigMapForCluster(clusterName string) { - glog.V(4).Infof("Reconciling ConfigMap for cluster(s) %q", clusterName) - - if !ic.isSynced() { - ic.configMapDeliverer.DeliverAfter(clusterName, nil, ic.clusterAvailableDelay) - return - } - - if clusterName == allClustersKey { - clusters, err := ic.configMapFederatedInformer.GetReadyClusters() - if err != nil { - glog.Errorf("Failed to get ready clusters. redelivering %q: %v", clusterName, err) - ic.configMapDeliverer.DeliverAfter(clusterName, nil, ic.clusterAvailableDelay) - return - } - for _, cluster := range clusters { - glog.V(4).Infof("Delivering ConfigMap for cluster(s) %q", clusterName) - ic.configMapDeliverer.DeliverAt(cluster.Name, nil, time.Now()) - } - return - } else { - cluster, found, err := ic.configMapFederatedInformer.GetReadyCluster(clusterName) - if err != nil || !found { - glog.Errorf("Internal error: Cluster %q queued for configmap reconciliation, but not found. Will try again later: error = %v", clusterName, err) - ic.configMapDeliverer.DeliverAfter(clusterName, nil, ic.clusterAvailableDelay) - return - } - uidConfigMapNamespacedName := types.NamespacedName{Name: uidConfigMapName, Namespace: uidConfigMapNamespace} - configMapObj, found, err := ic.configMapFederatedInformer.GetTargetStore().GetByKey(cluster.Name, uidConfigMapNamespacedName.String()) - if !found || err != nil { - logmsg := fmt.Sprintf("Failed to get ConfigMap %q for cluster %q. Will try again later", uidConfigMapNamespacedName, cluster.Name) - if err != nil { - logmsg = fmt.Sprintf("%v: %v", logmsg, err) - } - if len(ic.ingressInformerStore.List()) > 0 { // Error-level if ingresses are active, Info-level otherwise. - glog.Errorf(logmsg) - } else { - glog.V(4).Infof(logmsg) - } - ic.configMapDeliverer.DeliverAfter(clusterName, nil, ic.configMapReviewDelay) - return - } - glog.V(4).Infof("Successfully got ConfigMap %q for cluster %q.", uidConfigMapNamespacedName, clusterName) - configMap, ok := configMapObj.(*v1.ConfigMap) - if !ok { - glog.Errorf("Internal error: The object in the ConfigMap cache for cluster %q configmap %q is not a *ConfigMap", cluster.Name, uidConfigMapNamespacedName) - return - } - ic.reconcileConfigMap(cluster, configMap) - return - } -} - -// getProviderUid returns a provider ID based on the provided clusterName. -func getProviderUid(clusterName string) string { - hashedName := md5.Sum([]byte(clusterName)) - return fmt.Sprintf("%x", hashedName[:8]) -} - -/* - reconcileConfigMap ensures that the configmap in the cluster has a UID - consistent with the federation cluster's associated annotation. - - 1. If the UID in the configmap differs from the UID stored in the cluster's annotation, the configmap is updated. - 2. If the UID annotation is missing from the cluster, the cluster's UID annotation is updated to be consistent - with the master cluster. - 3. If there is no elected master cluster, this cluster attempts to elect itself as the master cluster. - - In cases 2 and 3, the configmaps will be updated in the next cycle, triggered by the federation cluster update(s) - -*/ -func (ic *IngressController) reconcileConfigMap(cluster *federationapi.Cluster, configMap *v1.ConfigMap) { - ic.Lock() // TODO: Reduce the scope of this master election lock. - defer ic.Unlock() - - configMapNsName := types.NamespacedName{Name: configMap.Name, Namespace: configMap.Namespace} - glog.V(4).Infof("Reconciling ConfigMap %q in cluster %q", configMapNsName, cluster.Name) - - clusterIngressUID, clusterIngressUIDExists := cluster.ObjectMeta.Annotations[uidAnnotationKey] - configMapUID, ok := configMap.Data[uidKey] - - if !ok { - glog.Errorf("Warning: ConfigMap %q in cluster %q does not contain data key %q. Therefore it cannot become the master.", configMapNsName, cluster.Name, uidKey) - } - - if !clusterIngressUIDExists || clusterIngressUID == "" { - glog.V(4).Infof("Cluster %q is the only master", cluster.Name) - // Second argument is the fallback, in case this is the only cluster, in which case it becomes the master - var err error - if clusterIngressUID, err = ic.updateClusterIngressUIDToMasters(cluster, configMapUID); err != nil { - return - } - // If we successfully update the Cluster Object, fallthrough and update the configMap. - } - - // Figure out providerUid. - providerUid := getProviderUid(cluster.Name) - configMapProviderUid := configMap.Data[providerUidKey] - - if configMapUID == clusterIngressUID && configMapProviderUid == providerUid { - glog.V(4).Infof("Ingress configMap update is not required: UID %q and ProviderUid %q are equal", configMapUID, providerUid) - } else { - if configMapUID != clusterIngressUID { - glog.V(4).Infof("Ingress configMap update is required for UID: configMapUID %q not equal to clusterIngressUID %q", configMapUID, clusterIngressUID) - } else if configMapProviderUid != providerUid { - glog.V(4).Infof("Ingress configMap update is required: configMapProviderUid %q not equal to providerUid %q", configMapProviderUid, providerUid) - } - configMap.Data[uidKey] = clusterIngressUID - configMap.Data[providerUidKey] = providerUid - operations := []util.FederatedOperation{{ - Type: util.OperationTypeUpdate, - Obj: configMap, - ClusterName: cluster.Name, - Key: configMapNsName.String(), - }} - glog.V(4).Infof("Calling federatedConfigMapUpdater.Update() - operations: %v", operations) - err := ic.federatedConfigMapUpdater.Update(operations) - if err != nil { - glog.Errorf("Failed to execute update of ConfigMap %q on cluster %q: %v", configMapNsName, cluster.Name, err) - ic.configMapDeliverer.DeliverAfter(cluster.Name, nil, ic.configMapReviewDelay) - } - } -} - -/* - getMasterCluster returns the cluster which is the elected master w.r.t. ingress UID, and it's ingress UID. - If there is no elected master cluster, an error is returned. - All other clusters must use the ingress UID of the elected master. -*/ -func (ic *IngressController) getMasterCluster() (master *federationapi.Cluster, ingressUID string, err error) { - clusters, err := ic.configMapFederatedInformer.GetReadyClusters() - if err != nil { - glog.Errorf("Failed to get cluster list: %v", err) - return nil, "", err - } - - for _, c := range clusters { - UID, exists := c.ObjectMeta.Annotations[uidAnnotationKey] - if exists && UID != "" { // Found the master cluster - glog.V(4).Infof("Found master cluster %q with annotation %q=%q", c.Name, uidAnnotationKey, UID) - return c, UID, nil - } - } - return nil, "", fmt.Errorf("Failed to find master cluster with annotation %q", uidAnnotationKey) -} - -/* - updateClusterIngressUIDToMasters takes the ingress UID annotation on the master cluster and applies it to cluster. - If there is no master cluster, then fallbackUID is used (and hence this cluster becomes the master). -*/ -func (ic *IngressController) updateClusterIngressUIDToMasters(cluster *federationapi.Cluster, fallbackUID string) (string, error) { - masterCluster, masterUID, err := ic.getMasterCluster() - cluster = cluster.DeepCopy() // Make a clone so that we don't clobber our input param - if err == nil { - if masterCluster.Name != cluster.Name { // We're not the master, need to get in sync - if cluster.ObjectMeta.Annotations == nil { - cluster.ObjectMeta.Annotations = map[string]string{} - } - cluster.ObjectMeta.Annotations[uidAnnotationKey] = masterUID - if _, err = ic.federatedApiClient.Federation().Clusters().Update(cluster); err != nil { - glog.Errorf("Failed to add master ingress UID annotation (%q = %q) from master cluster %q to cluster %q, will try again later: %v", uidAnnotationKey, masterUID, masterCluster.Name, cluster.Name, err) - return "", err - } else { - glog.V(4).Infof("Successfully added master ingress UID annotation (%q = %q) from master cluster %q to cluster %q.", uidAnnotationKey, masterUID, masterCluster.Name, cluster.Name) - return masterUID, nil - } - } else { - glog.V(4).Infof("Cluster %q with ingress UID is already the master with annotation (%q = %q), no need to update.", cluster.Name, uidAnnotationKey, cluster.ObjectMeta.Annotations[uidAnnotationKey]) - return cluster.ObjectMeta.Annotations[uidAnnotationKey], nil - } - } else { - glog.V(2).Infof("No master cluster found to source an ingress UID from for cluster %q.", cluster.Name) - if fallbackUID != "" { - glog.V(2).Infof("Attempting to elect new master cluster %q with ingress UID %q = %q", cluster.Name, uidAnnotationKey, fallbackUID) - if cluster.ObjectMeta.Annotations == nil { - cluster.ObjectMeta.Annotations = map[string]string{} - } - cluster.ObjectMeta.Annotations[uidAnnotationKey] = fallbackUID - if _, err = ic.federatedApiClient.Federation().Clusters().Update(cluster); err != nil { - glog.Errorf("Failed to add ingress UID annotation (%q = %q) to cluster %q. No master elected. Will try again later: %v", uidAnnotationKey, fallbackUID, cluster.Name, err) - return "", err - } else { - glog.V(4).Infof("Successfully added ingress UID annotation (%q = %q) to cluster %q.", uidAnnotationKey, fallbackUID, cluster.Name) - return fallbackUID, nil - } - } else { - glog.Errorf("No master cluster exists, and fallbackUID for cluster %q is nil. This probably means that no clusters have an ingress controller configmap with key %q. Federated Ingress currently supports clusters running Google Loadbalancer Controller (\"GLBC\")", cluster.Name, uidKey) - return "", err - } - } -} - -func (ic *IngressController) isClusterReady(clusterName string) bool { - cluster, isReady, err := ic.ingressFederatedInformer.GetReadyCluster(clusterName) - return isReady && err == nil && cluster != nil -} - -// updateAnnotationOnIngress updates the annotation with the given key on the given federated ingress. -// Queues the ingress for resync when done. -func (ic *IngressController) updateAnnotationOnIngress(ingress *extensionsv1beta1.Ingress, key, value string) { - if ingress.ObjectMeta.Annotations == nil { - ingress.ObjectMeta.Annotations = make(map[string]string) - } - ingress.ObjectMeta.Annotations[key] = value - ingressName := types.NamespacedName{Name: ingress.Name, Namespace: ingress.Namespace} - glog.V(4).Infof("Attempting to update annotation %s:%s on base federated ingress: %v", key, value, ingressName) - if updatedFedIngress, err := ic.federatedApiClient.Extensions().Ingresses(ingress.Namespace).Update(ingress); err != nil { - glog.Errorf("Failed to update annotation %s:%s on federated ingress %q, will try again later: %v", key, value, ingressName, err) - ic.deliverIngress(ingressName, ic.ingressReviewDelay, true) - return - } else { - glog.V(4).Infof("Successfully updated annotation %s:%s on federated ingress %q, after update: %q", key, value, ingress, updatedFedIngress) - ic.deliverIngress(ingressName, ic.smallDelay, false) - return - } -} - -func (ic *IngressController) reconcileIngress(ingress types.NamespacedName) { - glog.V(4).Infof("Reconciling ingress %q for all clusters", ingress) - if !ic.isSynced() { - ic.deliverIngress(ingress, ic.clusterAvailableDelay, false) - return - } - - key := ingress.String() - baseIngressObjFromStore, exist, err := ic.ingressInformerStore.GetByKey(key) - if err != nil { - glog.Errorf("Failed to query main ingress store for %v: %v", ingress, err) - ic.deliverIngress(ingress, 0, true) - return - } - if !exist { - // Not federated ingress, ignoring. - glog.V(4).Infof("Ingress %q is not federated. Ignoring.", ingress) - return - } - baseIngress := baseIngressObjFromStore.(*extensionsv1beta1.Ingress).DeepCopy() - glog.V(4).Infof("Base (federated) ingress: %v", baseIngress) - - if baseIngress.DeletionTimestamp != nil { - if err := ic.delete(baseIngress); err != nil { - glog.Errorf("Failed to delete %s: %v", ingress, err) - ic.eventRecorder.Eventf(baseIngress, api.EventTypeWarning, "DeleteFailed", - "Ingress delete failed: %v", err) - ic.deliverIngress(ingress, 0, true) - } - return - } - - glog.V(3).Infof("Ensuring delete object from underlying clusters finalizer for ingress: %s", - baseIngress.Name) - // Add the required finalizers before creating a ingress in underlying clusters. - updatedIngressObj, err := ic.deletionHelper.EnsureFinalizers(baseIngress) - if err != nil { - glog.Errorf("Failed to ensure delete object from underlying clusters finalizer in ingress %s: %v", - baseIngress.Name, err) - ic.deliverIngress(ingress, 0, true) - return - } - baseIngress = updatedIngressObj.(*extensionsv1beta1.Ingress) - - glog.V(3).Infof("Syncing ingress %s in underlying clusters", baseIngress.Name) - - clusters, err := ic.ingressFederatedInformer.GetReadyClusters() - if err != nil { - glog.Errorf("Failed to get cluster list: %v", err) - ic.deliverIngress(ingress, ic.clusterAvailableDelay, false) - return - } else { - glog.V(4).Infof("Found %d ready clusters across which to reconcile ingress %q", len(clusters), ingress) - } - - operations := make([]util.FederatedOperation, 0) - - for _, cluster := range clusters { - baseIPName, baseIPAnnotationExists := baseIngress.ObjectMeta.Annotations[staticIPNameKeyWritable] - firstClusterName, firstClusterExists := baseIngress.ObjectMeta.Annotations[firstClusterAnnotation] - clusterIngressObj, clusterIngressFound, err := ic.ingressFederatedInformer.GetTargetStore().GetByKey(cluster.Name, key) - if err != nil { - glog.Errorf("Failed to get cached ingress %s for cluster %s, will retry: %v", ingress, cluster.Name, err) - ic.deliverIngress(ingress, 0, true) - return - } - desiredIngress := &extensionsv1beta1.Ingress{} - desiredIngress.ObjectMeta = *baseIngress.ObjectMeta.DeepCopy() - desiredIngress.Spec = *desiredIngress.Spec.DeepCopy() - - glog.V(4).Infof("Desired Ingress: %v", desiredIngress) - - send, err := clusterselector.SendToCluster(cluster.Labels, desiredIngress.ObjectMeta.Annotations) - if err != nil { - glog.Errorf("Error processing ClusterSelector cluster: %s for Ingress map: %s error: %s", cluster.Name, key, err.Error()) - return - } - - switch { - case !clusterIngressFound && send: - glog.V(4).Infof("No existing Ingress %s in cluster %s - checking if appropriate to queue a create operation", ingress, cluster.Name) - // We can't supply server-created fields when creating a new object. - desiredIngress.ObjectMeta = util.DeepCopyRelevantObjectMeta(baseIngress.ObjectMeta) - - // We always first create an ingress in the first available cluster. Once that ingress - // has been created and allocated a global IP (visible via an annotation), - // we record that annotation on the federated ingress, and create all other cluster - // ingresses with that same global IP. - // Note: If the first cluster becomes (e.g. temporarily) unavailable, the - // second cluster will become the first cluster, but eventually all ingresses - // will share the single global IP recorded in the annotation of the - // federated ingress. - haveFirstCluster := firstClusterExists && firstClusterName != "" && ic.isClusterReady(firstClusterName) - if !haveFirstCluster { - glog.V(4).Infof("No cluster has been chosen as the first cluster. Electing cluster %s as the first cluster to create ingress in", cluster.Name) - ic.updateAnnotationOnIngress(baseIngress, firstClusterAnnotation, cluster.Name) - return - } - if baseIPAnnotationExists || firstClusterName == cluster.Name { - if baseIPAnnotationExists { - glog.V(4).Infof("No existing Ingress %s in cluster %s and static IP annotation (%q) exists on base ingress - queuing a create operation", ingress, cluster.Name, staticIPNameKeyWritable) - } else { - glog.V(4).Infof("No existing Ingress %s in cluster %s and no static IP annotation (%q) on base ingress - queuing a create operation in first cluster", ingress, cluster.Name, staticIPNameKeyWritable) - } - operations = append(operations, util.FederatedOperation{ - Type: util.OperationTypeAdd, - Obj: desiredIngress, - ClusterName: cluster.Name, - Key: key, - }) - } else { - glog.V(4).Infof("No annotation %q exists on ingress %q in federation and waiting for ingress in cluster %s. Not queueing create operation for ingress until annotation exists", staticIPNameKeyWritable, ingress, firstClusterName) - } - case clusterIngressFound && !send: - glog.V(5).Infof("Removing Ingress: %s from cluster: %s reason: cluster selectors do not match: %-v %-v", key, cluster.Name, cluster.ObjectMeta.Labels, desiredIngress.ObjectMeta.Annotations[federationapi.FederationClusterSelectorAnnotation]) - operations = append(operations, util.FederatedOperation{ - Type: util.OperationTypeDelete, - Obj: desiredIngress, - ClusterName: cluster.Name, - Key: key, - }) - case clusterIngressFound && send: - clusterIngress := clusterIngressObj.(*extensionsv1beta1.Ingress) - glog.V(4).Infof("Found existing Ingress %s in cluster %s - checking if update is required (in either direction)", ingress, cluster.Name) - clusterIPName, clusterIPNameExists := clusterIngress.ObjectMeta.Annotations[staticIPNameKeyReadonly] - baseLBStatusExists := len(baseIngress.Status.LoadBalancer.Ingress) > 0 - clusterLBStatusExists := len(clusterIngress.Status.LoadBalancer.Ingress) > 0 - logStr := fmt.Sprintf("Cluster ingress %q has annotation %q=%q, loadbalancer status exists? [%v], federated ingress has annotation %q=%q, loadbalancer status exists? [%v]. %%s annotation and/or loadbalancer status from cluster ingress to federated ingress.", ingress, staticIPNameKeyReadonly, clusterIPName, clusterLBStatusExists, staticIPNameKeyWritable, baseIPName, baseLBStatusExists) - if (!baseIPAnnotationExists && clusterIPNameExists) || (!baseLBStatusExists && clusterLBStatusExists) { // copy the IP name from the readonly annotation on the cluster ingress, to the writable annotation on the federated ingress - glog.V(4).Infof(logStr, "Transferring") - if !baseIPAnnotationExists && clusterIPNameExists { - ic.updateAnnotationOnIngress(baseIngress, staticIPNameKeyWritable, clusterIPName) - return - } - if !baseLBStatusExists && clusterLBStatusExists { - baseIngress.Status.LoadBalancer = *clusterIngress.Status.LoadBalancer.DeepCopy() - glog.V(4).Infof("Attempting to update base federated ingress status: %v", baseIngress) - if updatedFedIngress, err := ic.federatedApiClient.Extensions().Ingresses(baseIngress.Namespace).UpdateStatus(baseIngress); err != nil { - glog.Errorf("Failed to update federated ingress status of %q (loadbalancer status), will try again later: %v", ingress, err) - ic.deliverIngress(ingress, ic.ingressReviewDelay, true) - return - } else { - glog.V(4).Infof("Successfully updated federated ingress status of %q (added loadbalancer status), after update: %q", ingress, updatedFedIngress) - ic.deliverIngress(ingress, ic.smallDelay, false) - return - } - } - } else { - glog.V(4).Infof(logStr, "Not transferring") - } - // Update existing cluster ingress, if needed. - if util.ObjectMetaAndSpecEquivalent(baseIngress, clusterIngress) { - glog.V(4).Infof("Ingress %q in cluster %q does not need an update: cluster ingress is equivalent to federated ingress", ingress, cluster.Name) - } else { - glog.V(4).Infof("Ingress %s in cluster %s needs an update: cluster ingress %v is not equivalent to federated ingress %v", ingress, cluster.Name, clusterIngress, desiredIngress) - clusterIngress.ObjectMeta.DeepCopyInto(&desiredIngress.ObjectMeta) - // Merge any annotations and labels on the federated ingress onto the underlying cluster ingress, - // overwriting duplicates. - if desiredIngress.ObjectMeta.Annotations == nil { - desiredIngress.ObjectMeta.Annotations = make(map[string]string) - } - for key, val := range baseIngress.ObjectMeta.Annotations { - desiredIngress.ObjectMeta.Annotations[key] = val - } - if desiredIngress.ObjectMeta.Labels == nil { - desiredIngress.ObjectMeta.Labels = make(map[string]string) - } - for key, val := range baseIngress.ObjectMeta.Labels { - desiredIngress.ObjectMeta.Labels[key] = val - } - - operations = append(operations, util.FederatedOperation{ - Type: util.OperationTypeUpdate, - Obj: desiredIngress, - ClusterName: cluster.Name, - Key: key, - }) - // TODO: Transfer any readonly (target-proxy, url-map etc) annotations from the master cluster to the federation, if this is the master cluster. - // This is only for consistency, so that the federation ingress metadata matches the underlying clusters. It's not actually required } - } - } - } - - if len(operations) == 0 { - // Everything is in order - glog.V(4).Infof("Ingress %q is up-to-date in all clusters - no propagation to clusters required.", ingress) - return - } - glog.V(4).Infof("Calling federatedUpdater.Update() - operations: %v", operations) - err = ic.federatedIngressUpdater.Update(operations) - if err != nil { - glog.Errorf("Failed to execute updates for %s: %v", ingress, err) - ic.deliverIngress(ingress, ic.ingressReviewDelay, true) - return - } - // Schedule another periodic reconciliation, only to account for possible bugs in watch processing. - ic.deliverIngress(ingress, ic.ingressReviewDelay, false) -} - -// delete deletes the given ingress or returns error if the deletion was not complete. -func (ic *IngressController) delete(ingress *extensionsv1beta1.Ingress) error { - glog.V(3).Infof("Handling deletion of ingress: %v", *ingress) - _, err := ic.deletionHelper.HandleObjectInUnderlyingClusters(ingress) - if err != nil { - return err - } - - err = ic.federatedApiClient.Extensions().Ingresses(ingress.Namespace).Delete(ingress.Name, nil) - if err != nil { - // Its all good if the error is not found error. That means it is deleted already and we do not have to do anything. - // This is expected when we are processing an update as a result of ingress finalizer deletion. - // The process that deleted the last finalizer is also going to delete the ingress and we do not have to do anything. - if !errors.IsNotFound(err) { - return fmt.Errorf("failed to delete ingress: %v", err) - } - } - return nil -} diff --git a/federation/pkg/federation-controller/ingress/ingress_controller_test.go b/federation/pkg/federation-controller/ingress/ingress_controller_test.go deleted file mode 100644 index 429c8f4b1ba..00000000000 --- a/federation/pkg/federation-controller/ingress/ingress_controller_test.go +++ /dev/null @@ -1,397 +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 ingress - -import ( - "fmt" - "reflect" - "testing" - "time" - - apiv1 "k8s.io/api/core/v1" - extensionsv1beta1 "k8s.io/api/extensions/v1beta1" - "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" - kubeclientset "k8s.io/client-go/kubernetes" - fakekubeclientset "k8s.io/client-go/kubernetes/fake" - "k8s.io/client-go/tools/cache" - federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" - fakefedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/fake" - "k8s.io/kubernetes/federation/pkg/federation-controller/util" - "k8s.io/kubernetes/federation/pkg/federation-controller/util/deletionhelper" - finalizersutil "k8s.io/kubernetes/federation/pkg/federation-controller/util/finalizers" - . "k8s.io/kubernetes/federation/pkg/federation-controller/util/test" - - "github.com/golang/glog" - "github.com/stretchr/testify/assert" -) - -const ( - maxTrials = 20 - clusters string = "clusters" - ingresses string = "ingresses" - configmaps string = "configmaps" -) - -func TestIngressController(t *testing.T) { - fakeClusterList := federationapi.ClusterList{Items: []federationapi.Cluster{}} - fakeConfigMapList1 := apiv1.ConfigMapList{Items: []apiv1.ConfigMap{}} - fakeConfigMapList2 := apiv1.ConfigMapList{Items: []apiv1.ConfigMap{}} - cluster1 := NewCluster("cluster1", apiv1.ConditionTrue) - cluster2 := NewCluster("cluster2", apiv1.ConditionTrue) - cfg1 := NewConfigMap("foo") - cfg2 := NewConfigMap("bar") // Different UID from cfg1, so that we can check that they get reconciled. - assert.NotEqual(t, cfg1.Data[uidKey], cfg2.Data[uidKey], fmt.Sprintf("ConfigMap in cluster 2 must initially not equal that in cluster 1 for this test - please fix test")) - - t.Log("Creating fake infrastructure") - fedClient := &fakefedclientset.Clientset{} - RegisterFakeList(clusters, &fedClient.Fake, &fakeClusterList) - RegisterFakeList(ingresses, &fedClient.Fake, &extensionsv1beta1.IngressList{Items: []extensionsv1beta1.Ingress{}}) - fedIngressWatch := RegisterFakeWatch(ingresses, &fedClient.Fake) - clusterWatch := RegisterFakeWatch(clusters, &fedClient.Fake) - fedClusterUpdateChan := RegisterFakeCopyOnUpdate(clusters, &fedClient.Fake, clusterWatch) - fedIngressUpdateChan := RegisterFakeCopyOnUpdate(ingresses, &fedClient.Fake, fedIngressWatch) - - cluster1Client := &fakekubeclientset.Clientset{} - RegisterFakeList(ingresses, &cluster1Client.Fake, &extensionsv1beta1.IngressList{Items: []extensionsv1beta1.Ingress{}}) - RegisterFakeList(configmaps, &cluster1Client.Fake, &fakeConfigMapList1) - cluster1IngressWatch := RegisterFakeWatch(ingresses, &cluster1Client.Fake) - cluster1ConfigMapWatch := RegisterFakeWatch(configmaps, &cluster1Client.Fake) - cluster1IngressCreateChan := RegisterFakeCopyOnCreate(ingresses, &cluster1Client.Fake, cluster1IngressWatch) - cluster1IngressUpdateChan := RegisterFakeCopyOnUpdate(ingresses, &cluster1Client.Fake, cluster1IngressWatch) - cluster1ConfigMapUpdateChan := RegisterFakeCopyOnUpdate(configmaps, &cluster1Client.Fake, cluster1ConfigMapWatch) - - cluster2Client := &fakekubeclientset.Clientset{} - RegisterFakeList(ingresses, &cluster2Client.Fake, &extensionsv1beta1.IngressList{Items: []extensionsv1beta1.Ingress{}}) - RegisterFakeList(configmaps, &cluster2Client.Fake, &fakeConfigMapList2) - cluster2IngressWatch := RegisterFakeWatch(ingresses, &cluster2Client.Fake) - cluster2ConfigMapWatch := RegisterFakeWatch(configmaps, &cluster2Client.Fake) - cluster2IngressCreateChan := RegisterFakeCopyOnCreate(ingresses, &cluster2Client.Fake, cluster2IngressWatch) - cluster2ConfigMapUpdateChan := RegisterFakeCopyOnUpdate(configmaps, &cluster2Client.Fake, cluster2ConfigMapWatch) - - clientFactoryFunc := func(cluster *federationapi.Cluster) (kubeclientset.Interface, error) { - switch cluster.Name { - case cluster1.Name: - return cluster1Client, nil - case cluster2.Name: - return cluster2Client, nil - default: - return nil, fmt.Errorf("Unknown cluster") - } - } - ingressController := NewIngressController(fedClient) - ingressInformer := ToFederatedInformerForTestOnly(ingressController.ingressFederatedInformer) - ingressInformer.SetClientFactory(clientFactoryFunc) - configMapInformer := ToFederatedInformerForTestOnly(ingressController.configMapFederatedInformer) - configMapInformer.SetClientFactory(clientFactoryFunc) - ingressController.clusterAvailableDelay = time.Second - ingressController.ingressReviewDelay = 100 * time.Millisecond - ingressController.configMapReviewDelay = 100 * time.Millisecond - ingressController.smallDelay = 100 * time.Millisecond - ingressController.updateTimeout = 5 * time.Second - - stop := make(chan struct{}) - t.Log("Running Ingress Controller") - ingressController.Run(stop) - - // TODO: Here we are creating the ingress with first cluster annotation. - // Add another test without that annotation when - // https://github.com/kubernetes/kubernetes/issues/36540 is fixed. - fedIngress := extensionsv1beta1.Ingress{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-ingress", - Namespace: "mynamespace", - SelfLink: "/api/v1/namespaces/mynamespace/ingress/test-ingress", - Annotations: map[string]string{ - firstClusterAnnotation: cluster1.Name, - }, - }, - Status: extensionsv1beta1.IngressStatus{ - LoadBalancer: apiv1.LoadBalancerStatus{ - Ingress: make([]apiv1.LoadBalancerIngress, 0, 0), - }, - }, - } - - t.Log("Adding cluster 1") - clusterWatch.Add(cluster1) - - t.Log("Adding Ingress UID ConfigMap to cluster 1") - cluster1ConfigMapWatch.Add(cfg1) - - t.Log("Checking that UID annotation on Cluster 1 annotation was correctly updated prior to adding Federated Ingress") - cluster := GetClusterFromChan(fedClusterUpdateChan) - assert.NotNil(t, cluster) - assert.Equal(t, cluster.ObjectMeta.Annotations[uidAnnotationKey], cfg1.Data[uidKey]) - - t.Log("Adding cluster 2") - clusterWatch.Add(cluster2) - cluster2ConfigMapWatch.Add(cfg2) - - t.Log("Checking that a configmap updates are propagated prior to Federated Ingress") - referenceUid := cfg1.Data[uidKey] - uid1, providerId1 := GetConfigMapUidAndProviderId(t, cluster1ConfigMapUpdateChan) - uid2, providerId2 := GetConfigMapUidAndProviderId(t, cluster2ConfigMapUpdateChan) - t.Logf("uid2 = %v and ref = %v", uid2, referenceUid) - assert.True(t, referenceUid == uid1, "Expected cluster1 configmap uid %q to be equal to referenceUid %q", uid1, referenceUid) - assert.True(t, referenceUid == uid2, "Expected cluster2 configmap uid %q to be equal to referenceUid %q", uid2, referenceUid) - assert.True(t, providerId1 != providerId2, "Expected cluster1 providerUid %q to be unique and different from cluster2 providerUid %q", providerId1, providerId2) - - // Test add federated ingress. - t.Log("Adding Federated Ingress") - fedIngressWatch.Add(&fedIngress) - - t.Log("Checking that appropriate finalizers are added") - // There should be an update to add both the finalizers. - updatedIngress := GetIngressFromChan(t, fedIngressUpdateChan) - AssertHasFinalizer(t, updatedIngress, deletionhelper.FinalizerDeleteFromUnderlyingClusters) - AssertHasFinalizer(t, updatedIngress, metav1.FinalizerOrphanDependents) - fedIngress = *updatedIngress - - t.Log("Checking that Ingress was correctly created in cluster 1") - createdIngress := GetIngressFromChan(t, cluster1IngressCreateChan) - assert.NotNil(t, createdIngress) - cluster1Ingress := *createdIngress - assert.True(t, reflect.DeepEqual(fedIngress.Spec, cluster1Ingress.Spec), "Spec of created ingress is not equal") - assert.True(t, util.ObjectMetaEquivalent(fedIngress.ObjectMeta, cluster1Ingress.ObjectMeta), - "Metadata of created object is not equivalent") - - // Wait for finalizers to appear in federation store. - assert.NoError(t, WaitForFinalizersInFederationStore(ingressController, ingressController.ingressInformerStore, - types.NamespacedName{Namespace: fedIngress.Namespace, Name: fedIngress.Name}.String()), "finalizers not found in federated ingress") - - // Wait for the cluster ingress to appear in cluster store. - assert.NoError(t, WaitForIngressInClusterStore(ingressController.ingressFederatedInformer.GetTargetStore(), cluster1.Name, - types.NamespacedName{Namespace: createdIngress.Namespace, Name: createdIngress.Name}.String()), - "Created ingress not found in underlying cluster store") - - // Test that IP address gets transferred from cluster ingress to federated ingress. - t.Log("Checking that IP address gets transferred from cluster ingress to federated ingress") - cluster1Ingress.Status.LoadBalancer.Ingress = append(cluster1Ingress.Status.LoadBalancer.Ingress, - apiv1.LoadBalancerIngress{IP: "1.2.3.4"}) - glog.Infof("Setting artificial IP address for cluster1 ingress") - - for trial := 0; trial < maxTrials; trial++ { - cluster1IngressWatch.Modify(&cluster1Ingress) - // Wait for store to see the updated cluster ingress. - key := types.NamespacedName{Namespace: createdIngress.Namespace, Name: createdIngress.Name}.String() - if err := WaitForStatusUpdate(t, ingressController.ingressFederatedInformer.GetTargetStore(), - cluster1.Name, key, cluster1Ingress.Status.LoadBalancer, time.Second); err != nil { - continue - } - if err := WaitForFedStatusUpdate(t, ingressController.ingressInformerStore, - key, cluster1Ingress.Status.LoadBalancer, time.Second); err != nil { - continue - } - } - - for trial := 0; trial < maxTrials; trial++ { - updatedIngress = GetIngressFromChan(t, fedIngressUpdateChan) - assert.NotNil(t, updatedIngress, "Cluster's ingress load balancer status was not correctly transferred to the federated ingress") - if updatedIngress == nil { - return - } - if reflect.DeepEqual(cluster1Ingress.Status.LoadBalancer.Ingress, updatedIngress.Status.LoadBalancer.Ingress) { - fedIngress.Status.LoadBalancer = updatedIngress.Status.LoadBalancer - break - } else { - glog.Infof("Status check failed: expected: %v actual: %v", cluster1Ingress.Status, updatedIngress.Status) - } - } - glog.Infof("Status check: expected: %v actual: %v", cluster1Ingress.Status, updatedIngress.Status) - assert.True(t, reflect.DeepEqual(cluster1Ingress.Status.LoadBalancer.Ingress, updatedIngress.Status.LoadBalancer.Ingress), - fmt.Sprintf("Ingress IP was not transferred from cluster ingress to federated ingress. %v is not equal to %v", - cluster1Ingress.Status.LoadBalancer.Ingress, updatedIngress.Status.LoadBalancer.Ingress)) - - assert.NoError(t, WaitForStatusUpdate(t, ingressController.ingressFederatedInformer.GetTargetStore(), - cluster1.Name, types.NamespacedName{Namespace: createdIngress.Namespace, Name: createdIngress.Name}.String(), - cluster1Ingress.Status.LoadBalancer, time.Second)) - assert.NoError(t, WaitForFedStatusUpdate(t, ingressController.ingressInformerStore, - types.NamespacedName{Namespace: createdIngress.Namespace, Name: createdIngress.Name}.String(), - cluster1Ingress.Status.LoadBalancer, time.Second)) - t.Logf("expected: %v, actual: %v", createdIngress, updatedIngress) - - // Test update federated ingress. - if fedIngress.ObjectMeta.Annotations == nil { - fedIngress.ObjectMeta.Annotations = make(map[string]string) - } - fedIngress.ObjectMeta.Annotations["A"] = "B" - fedIngress.ObjectMeta.Annotations[federationapi.FederationClusterSelectorAnnotation] = `[{"key": "cluster", "operator": "in", "values": ["cluster1","cluster2"]}]` - t.Log("Modifying Federated Ingress") - fedIngressWatch.Modify(&fedIngress) - t.Log("Checking that Ingress was correctly updated in cluster 1") - var updatedIngress2 *extensionsv1beta1.Ingress - - for trial := 0; trial < maxTrials; trial++ { - updatedIngress2 = GetIngressFromChan(t, cluster1IngressUpdateChan) - assert.NotNil(t, updatedIngress2) - if updatedIngress2 == nil { - return - } - if reflect.DeepEqual(fedIngress.Spec, updatedIngress.Spec) && - updatedIngress2.ObjectMeta.Annotations["A"] == fedIngress.ObjectMeta.Annotations["A"] { - break - } - } - - assert.True(t, reflect.DeepEqual(updatedIngress2.Spec, fedIngress.Spec), "Spec of updated ingress is not equal") - assert.Equal(t, updatedIngress2.ObjectMeta.Annotations["A"], fedIngress.ObjectMeta.Annotations["A"], "Updated annotation not transferred from federated to cluster ingress.") - - fedIngress.Annotations[staticIPNameKeyWritable] = "foo" // Make sure that the base object has a static IP name first. - fedIngressWatch.Modify(&fedIngress) - - t.Log("Checking that the ingress got created in cluster 2 after a global ip was assigned") - createdIngress2 := GetIngressFromChan(t, cluster2IngressCreateChan) - assert.NotNil(t, createdIngress2) - assert.True(t, reflect.DeepEqual(fedIngress.Spec, createdIngress2.Spec), "Spec of created ingress is not equal") - t.Logf("created meta: %v fed meta: %v", createdIngress2.ObjectMeta, fedIngress.ObjectMeta) - assert.True(t, util.ObjectMetaEquivalent(fedIngress.ObjectMeta, createdIngress2.ObjectMeta), "Metadata of created object is not equivalent") - - close(stop) -} - -func GetConfigMapUidAndProviderId(t *testing.T, c chan runtime.Object) (string, string) { - updatedConfigMap := GetConfigMapFromChan(c) - assert.NotNil(t, updatedConfigMap, "ConfigMap should have received an update") - assert.NotNil(t, updatedConfigMap.Data, "ConfigMap data is empty") - - for _, key := range []string{uidKey, providerUidKey} { - val, ok := updatedConfigMap.Data[key] - assert.True(t, ok, fmt.Sprintf("Didn't receive an update for key %v: %v", key, updatedConfigMap.Data)) - assert.True(t, len(val) > 0, fmt.Sprintf("Received an empty update for key %v", key)) - } - return updatedConfigMap.Data[uidKey], updatedConfigMap.Data[providerUidKey] -} - -func GetIngressFromChan(t *testing.T, c chan runtime.Object) *extensionsv1beta1.Ingress { - obj := GetObjectFromChan(c) - - if obj == nil { - return nil - } - - ingress, ok := obj.(*extensionsv1beta1.Ingress) - if !ok { - t.Logf("Object on channel was not of type *extensionsv1beta1.Ingress: %v", obj) - } - return ingress -} - -func GetConfigMapFromChan(c chan runtime.Object) *apiv1.ConfigMap { - if configMap := GetObjectFromChan(c); configMap == nil { - return nil - } else { - return configMap.(*apiv1.ConfigMap) - } - -} - -func GetClusterFromChan(c chan runtime.Object) *federationapi.Cluster { - if cluster := GetObjectFromChan(c); cluster == nil { - return nil - } else { - return cluster.(*federationapi.Cluster) - } -} - -func NewConfigMap(uid string) *apiv1.ConfigMap { - return &apiv1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: uidConfigMapName, - Namespace: uidConfigMapNamespace, - SelfLink: "/api/v1/namespaces/" + uidConfigMapNamespace + "/configmap/" + uidConfigMapName, - // TODO: Remove: Annotations: map[string]string{}, - }, - Data: map[string]string{ - uidKey: uid, - }, - } -} - -// Wait for finalizers to appear in federation store. -func WaitForFinalizersInFederationStore(ingressController *IngressController, store cache.Store, key string) error { - retryInterval := 100 * time.Millisecond - timeout := wait.ForeverTestTimeout - err := wait.PollImmediate(retryInterval, timeout, func() (bool, error) { - obj, found, err := store.GetByKey(key) - if !found || err != nil { - return false, err - } - ingress := obj.(*extensionsv1beta1.Ingress) - hasOrphanFinalizer, err := finalizersutil.HasFinalizer(ingress, metav1.FinalizerOrphanDependents) - if err != nil { - return false, err - } - hasDeleteFinalizer, err := finalizersutil.HasFinalizer(ingress, deletionhelper.FinalizerDeleteFromUnderlyingClusters) - if err != nil { - return false, err - } - if hasOrphanFinalizer && hasDeleteFinalizer { - return true, nil - } - return false, nil - }) - return err -} - -// Wait for the cluster ingress to appear in cluster store. -func WaitForIngressInClusterStore(store util.FederatedReadOnlyStore, clusterName, key string) error { - retryInterval := 100 * time.Millisecond - timeout := wait.ForeverTestTimeout - err := wait.PollImmediate(retryInterval, timeout, func() (bool, error) { - _, found, err := store.GetByKey(clusterName, key) - if found && err == nil { - return true, nil - } - if errors.IsNotFound(err) { - return false, nil - } - return false, err - }) - return err -} - -// Wait for ingress status to be updated to match the desiredStatus. -func WaitForStatusUpdate(t *testing.T, store util.FederatedReadOnlyStore, clusterName, key string, desiredStatus apiv1.LoadBalancerStatus, timeout time.Duration) error { - retryInterval := 100 * time.Millisecond - err := wait.PollImmediate(retryInterval, timeout, func() (bool, error) { - obj, found, err := store.GetByKey(clusterName, key) - if !found || err != nil { - return false, err - } - ingress := obj.(*extensionsv1beta1.Ingress) - return reflect.DeepEqual(ingress.Status.LoadBalancer, desiredStatus), nil - }) - return err -} - -// Wait for ingress status to be updated to match the desiredStatus. -func WaitForFedStatusUpdate(t *testing.T, store cache.Store, key string, desiredStatus apiv1.LoadBalancerStatus, timeout time.Duration) error { - retryInterval := 100 * time.Millisecond - err := wait.PollImmediate(retryInterval, timeout, func() (bool, error) { - obj, found, err := store.GetByKey(key) - if !found || err != nil { - return false, err - } - ingress := obj.(*extensionsv1beta1.Ingress) - return reflect.DeepEqual(ingress.Status.LoadBalancer, desiredStatus), nil - }) - return err -} diff --git a/federation/pkg/federation-controller/job/BUILD b/federation/pkg/federation-controller/job/BUILD deleted file mode 100644 index 429b75dd2a0..00000000000 --- a/federation/pkg/federation-controller/job/BUILD +++ /dev/null @@ -1,78 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = ["jobcontroller.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/job", - deps = [ - "//federation/apis/federation:go_default_library", - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/client/clientset_generated/federation_clientset:go_default_library", - "//federation/pkg/federation-controller/util:go_default_library", - "//federation/pkg/federation-controller/util/deletionhelper:go_default_library", - "//federation/pkg/federation-controller/util/eventsink:go_default_library", - "//federation/pkg/federation-controller/util/planner:go_default_library", - "//federation/pkg/federation-controller/util/replicapreferences:go_default_library", - "//pkg/api:go_default_library", - "//pkg/controller:go_default_library", - "//vendor/github.com/davecgh/go-spew/spew:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/api/batch/v1:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/tools/cache:go_default_library", - "//vendor/k8s.io/client-go/tools/record:go_default_library", - "//vendor/k8s.io/client-go/util/flowcontrol:go_default_library", - "//vendor/k8s.io/client-go/util/workqueue:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["jobcontroller_test.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/job", - library = ":go_default_library", - deps = [ - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/client/clientset_generated/federation_clientset/fake:go_default_library", - "//federation/pkg/federation-controller/util:go_default_library", - "//federation/pkg/federation-controller/util/finalizers:go_default_library", - "//federation/pkg/federation-controller/util/test:go_default_library", - "//pkg/apis/batch/v1:go_default_library", - "//vendor/github.com/stretchr/testify/assert:go_default_library", - "//vendor/k8s.io/api/batch/v1:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", - "//vendor/k8s.io/client-go/testing:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/pkg/federation-controller/job/jobcontroller.go b/federation/pkg/federation-controller/job/jobcontroller.go deleted file mode 100644 index 783badfd75c..00000000000 --- a/federation/pkg/federation-controller/job/jobcontroller.go +++ /dev/null @@ -1,561 +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 job - -import ( - "fmt" - "reflect" - "time" - - "github.com/davecgh/go-spew/spew" - "github.com/golang/glog" - - batchv1 "k8s.io/api/batch/v1" - clientv1 "k8s.io/api/core/v1" - "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/wait" - "k8s.io/apimachinery/pkg/watch" - kubeclientset "k8s.io/client-go/kubernetes" - "k8s.io/client-go/tools/cache" - "k8s.io/client-go/tools/record" - "k8s.io/client-go/util/flowcontrol" - "k8s.io/client-go/util/workqueue" - fed "k8s.io/kubernetes/federation/apis/federation" - fedv1 "k8s.io/kubernetes/federation/apis/federation/v1beta1" - fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - fedutil "k8s.io/kubernetes/federation/pkg/federation-controller/util" - "k8s.io/kubernetes/federation/pkg/federation-controller/util/deletionhelper" - "k8s.io/kubernetes/federation/pkg/federation-controller/util/eventsink" - "k8s.io/kubernetes/federation/pkg/federation-controller/util/planner" - "k8s.io/kubernetes/federation/pkg/federation-controller/util/replicapreferences" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/controller" -) - -const ( - fedJobPreferencesAnnotation = "federation.kubernetes.io/job-preferences" - allClustersKey = "THE_ALL_CLUSTER_KEY" - // UserAgentName is the user agent used in the federation client - UserAgentName = "Federation-Job-Controller" - // ControllerName is name of this controller - ControllerName = "jobs" -) - -var ( - // RequiredResources is the resource group version of the type this controller manages - RequiredResources = []schema.GroupVersionResource{batchv1.SchemeGroupVersion.WithResource("jobs")} - jobReviewDelay = 10 * time.Second - clusterAvailableDelay = 20 * time.Second - clusterUnavailableDelay = 60 * time.Second - updateTimeout = 30 * time.Second - backoffInitial = 5 * time.Second - backoffMax = 1 * time.Minute -) - -// FederationJobController synchronizes the state of a federated job object -// to clusters that are members of the federation. -type FederationJobController struct { - fedClient fedclientset.Interface - - jobController cache.Controller - jobStore cache.Store - - fedJobInformer fedutil.FederatedInformer - - jobDeliverer *fedutil.DelayingDeliverer - clusterDeliverer *fedutil.DelayingDeliverer - jobWorkQueue workqueue.Interface - // For updating members of federation. - fedUpdater fedutil.FederatedUpdater - - jobBackoff *flowcontrol.Backoff - // For events - eventRecorder record.EventRecorder - - defaultPlanner *planner.Planner - deletionHelper *deletionhelper.DeletionHelper -} - -// NewJobController creates a new federation job controller -func NewJobController(fedClient fedclientset.Interface) *FederationJobController { - broadcaster := record.NewBroadcaster() - broadcaster.StartRecordingToSink(eventsink.NewFederatedEventSink(fedClient)) - recorder := broadcaster.NewRecorder(api.Scheme, clientv1.EventSource{Component: "federated-job-controller"}) - fjc := &FederationJobController{ - fedClient: fedClient, - jobDeliverer: fedutil.NewDelayingDeliverer(), - clusterDeliverer: fedutil.NewDelayingDeliverer(), - jobWorkQueue: workqueue.New(), - jobBackoff: flowcontrol.NewBackOff(backoffInitial, backoffMax), - defaultPlanner: planner.NewPlanner(&fed.ReplicaAllocationPreferences{ - Clusters: map[string]fed.ClusterPreferences{ - "*": {Weight: 1}, - }, - }), - eventRecorder: recorder, - } - - jobFedInformerFactory := func(cluster *fedv1.Cluster, clientset kubeclientset.Interface) (cache.Store, cache.Controller) { - return cache.NewInformer( - &cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { - return clientset.BatchV1().Jobs(metav1.NamespaceAll).List(options) - }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return clientset.BatchV1().Jobs(metav1.NamespaceAll).Watch(options) - }, - }, - &batchv1.Job{}, - controller.NoResyncPeriodFunc(), - fedutil.NewTriggerOnAllChanges( - func(obj runtime.Object) { fjc.deliverLocalJob(obj, jobReviewDelay) }, - ), - ) - } - clusterLifecycle := fedutil.ClusterLifecycleHandlerFuncs{ - ClusterAvailable: func(cluster *fedv1.Cluster) { - fjc.clusterDeliverer.DeliverAfter(allClustersKey, nil, clusterAvailableDelay) - }, - ClusterUnavailable: func(cluster *fedv1.Cluster, _ []interface{}) { - fjc.clusterDeliverer.DeliverAfter(allClustersKey, nil, clusterUnavailableDelay) - }, - } - fjc.fedJobInformer = fedutil.NewFederatedInformer(fedClient, jobFedInformerFactory, &clusterLifecycle) - - fjc.jobStore, fjc.jobController = cache.NewInformer( - &cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { - return fjc.fedClient.BatchV1().Jobs(metav1.NamespaceAll).List(options) - }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return fjc.fedClient.BatchV1().Jobs(metav1.NamespaceAll).Watch(options) - }, - }, - &batchv1.Job{}, - controller.NoResyncPeriodFunc(), - fedutil.NewTriggerOnMetaAndSpecChanges( - func(obj runtime.Object) { fjc.deliverFedJobObj(obj, 0) }, - ), - ) - - fjc.fedUpdater = fedutil.NewFederatedUpdater(fjc.fedJobInformer, "job", updateTimeout, fjc.eventRecorder, - func(client kubeclientset.Interface, obj runtime.Object) error { - rs := obj.(*batchv1.Job) - _, err := client.BatchV1().Jobs(rs.Namespace).Create(rs) - return err - }, - func(client kubeclientset.Interface, obj runtime.Object) error { - rs := obj.(*batchv1.Job) - _, err := client.BatchV1().Jobs(rs.Namespace).Update(rs) - return err - }, - func(client kubeclientset.Interface, obj runtime.Object) error { - rs := obj.(*batchv1.Job) - err := client.BatchV1().Jobs(rs.Namespace).Delete(rs.Name, &metav1.DeleteOptions{}) - return err - }) - - fjc.deletionHelper = deletionhelper.NewDeletionHelper( - fjc.updateJob, - // objNameFunc - func(obj runtime.Object) string { - job := obj.(*batchv1.Job) - return job.Name - }, - fjc.fedJobInformer, - fjc.fedUpdater, - ) - - return fjc -} - -// Sends the given updated object to apiserver. -// Assumes that the given object is a job. -func (fjc *FederationJobController) updateJob(obj runtime.Object) (runtime.Object, error) { - job := obj.(*batchv1.Job) - return fjc.fedClient.BatchV1().Jobs(job.Namespace).Update(job) -} - -// Run starts the syncing of federation jobs to the clusters. -func (fjc *FederationJobController) Run(workers int, stopCh <-chan struct{}) { - go fjc.jobController.Run(stopCh) - fjc.fedJobInformer.Start() - - fjc.jobDeliverer.StartWithHandler(func(item *fedutil.DelayingDelivererItem) { - fjc.jobWorkQueue.Add(item.Key) - }) - fjc.clusterDeliverer.StartWithHandler(func(_ *fedutil.DelayingDelivererItem) { - fjc.reconcileJobsOnClusterChange() - }) - - for !fjc.isSynced() { - time.Sleep(5 * time.Millisecond) - } - - for i := 0; i < workers; i++ { - go wait.Until(fjc.worker, time.Second, stopCh) - } - - fedutil.StartBackoffGC(fjc.jobBackoff, stopCh) - - <-stopCh - glog.Infof("Shutting down FederationJobController") - fjc.jobDeliverer.Stop() - fjc.clusterDeliverer.Stop() - fjc.jobWorkQueue.ShutDown() - fjc.fedJobInformer.Stop() -} - -func (fjc *FederationJobController) isSynced() bool { - if !fjc.fedJobInformer.ClustersSynced() { - glog.V(3).Infof("Cluster list not synced") - return false - } - clusters, err := fjc.fedJobInformer.GetReadyClusters() - if err != nil { - glog.Errorf("Failed to get ready clusters: %v", err) - return false - } - if !fjc.fedJobInformer.GetTargetStore().ClustersSynced(clusters) { - glog.V(2).Infof("cluster job list not synced") - return false - } - - if !fjc.jobController.HasSynced() { - glog.V(2).Infof("federation job list not synced") - return false - } - return true -} - -func (fjc *FederationJobController) deliverLocalJob(obj interface{}, duration time.Duration) { - key, err := controller.KeyFunc(obj) - if err != nil { - glog.Errorf("Couldn't get key for object %v: %v", obj, err) - return - } - _, exists, err := fjc.jobStore.GetByKey(key) - if err != nil { - glog.Errorf("Couldn't get federated job %v: %v", key, err) - return - } - if exists { // ignore jobs exists only in local k8s - fjc.deliverJobByKey(key, duration, false) - } -} - -func (fjc *FederationJobController) deliverFedJobObj(obj interface{}, delay time.Duration) { - key, err := controller.KeyFunc(obj) - if err != nil { - glog.Errorf("Couldn't get key for object %+v: %v", obj, err) - return - } - fjc.deliverJobByKey(key, delay, false) -} - -func (fjc *FederationJobController) deliverJobByKey(key string, delay time.Duration, failed bool) { - if failed { - fjc.jobBackoff.Next(key, time.Now()) - delay = delay + fjc.jobBackoff.Get(key) - } else { - fjc.jobBackoff.Reset(key) - } - fjc.jobDeliverer.DeliverAfter(key, nil, delay) -} - -type reconciliationStatus string - -const ( - statusAllOk = reconciliationStatus("ALL_OK") - statusNeedRecheck = reconciliationStatus("RECHECK") - statusError = reconciliationStatus("ERROR") - statusNotSynced = reconciliationStatus("NOSYNC") -) - -func (fjc *FederationJobController) worker() { - for { - item, quit := fjc.jobWorkQueue.Get() - if quit { - return - } - key := item.(string) - status, err := fjc.reconcileJob(key) - fjc.jobWorkQueue.Done(item) - if err != nil { - glog.Errorf("Error syncing job controller: %v", err) - fjc.deliverJobByKey(key, 0, true) - } else { - switch status { - case statusAllOk: - break - case statusError: - fjc.deliverJobByKey(key, 0, true) - case statusNeedRecheck: - fjc.deliverJobByKey(key, jobReviewDelay, false) - case statusNotSynced: - fjc.deliverJobByKey(key, clusterAvailableDelay, false) - default: - glog.Errorf("Unhandled reconciliation status: %s", status) - fjc.deliverJobByKey(key, jobReviewDelay, false) - } - } - } -} - -type scheduleResult struct { - Parallelism *int32 - Completions *int32 -} - -func (fjc *FederationJobController) schedule(fjob *batchv1.Job, clusters []*fedv1.Cluster) map[string]scheduleResult { - plnr := fjc.defaultPlanner - frsPref, err := replicapreferences.GetAllocationPreferences(fjob, fedJobPreferencesAnnotation) - if err != nil { - glog.Warningf("Invalid job specific preference, use default. rs: %v, err: %v", fjob, err) - } - if frsPref != nil { // create a new planner if user specified a preference - plnr = planner.NewPlanner(frsPref) - } - - parallelism := int64(*fjob.Spec.Parallelism) - var clusterNames []string - for _, cluster := range clusters { - clusterNames = append(clusterNames, cluster.Name) - } - parallelismResult, _ := plnr.Plan(parallelism, clusterNames, nil, nil, fjob.Namespace+"/"+fjob.Name) - - if frsPref != nil { - for _, clusterPref := range frsPref.Clusters { - clusterPref.MinReplicas = 0 - clusterPref.MaxReplicas = nil - } - plnr = planner.NewPlanner(frsPref) - } - clusterNames = nil - for clusterName := range parallelismResult { - clusterNames = append(clusterNames, clusterName) - } - completionsResult := make(map[string]int64) - if fjob.Spec.Completions != nil { - completionsResult, _ = plnr.Plan(int64(*fjob.Spec.Completions), clusterNames, nil, nil, fjob.Namespace+"/"+fjob.Name) - } - - results := make(map[string]scheduleResult) - for _, clusterName := range clusterNames { - paralle := int32(parallelismResult[clusterName]) - complet := int32(completionsResult[clusterName]) - result := scheduleResult{ - Parallelism: ¶lle, - } - if fjob.Spec.Completions != nil { - result.Completions = &complet - } - results[clusterName] = result - } - - return results -} - -func (fjc *FederationJobController) reconcileJob(key string) (reconciliationStatus, error) { - if !fjc.isSynced() { - return statusNotSynced, nil - } - - glog.V(4).Infof("Start reconcile job %q", key) - startTime := time.Now() - defer glog.V(4).Infof("Finished reconcile job %q (%v)", key, time.Now().Sub(startTime)) - - objFromStore, exists, err := fjc.jobStore.GetByKey(key) - if err != nil { - return statusError, err - } - if !exists { - // deleted federated job, nothing need to do - return statusAllOk, nil - } - - // Create a copy before modifying the obj to prevent race condition with other readers of obj from store. - fjob, ok := objFromStore.(*batchv1.Job) - if !ok { - return statusError, err - } - fjob = fjob.DeepCopy() - - // delete job - if fjob.DeletionTimestamp != nil { - if err := fjc.delete(fjob); err != nil { - fjc.eventRecorder.Eventf(fjob, api.EventTypeNormal, "DeleteFailed", "Job delete failed: %v", err) - return statusError, err - } - return statusAllOk, nil - } - - glog.V(3).Infof("Ensuring delete object from underlying clusters finalizer for job: %s\n", key) - // Add the required finalizers before creating a job in underlying clusters. - updatedJobObj, err := fjc.deletionHelper.EnsureFinalizers(fjob) - if err != nil { - return statusError, err - } - fjob = updatedJobObj.(*batchv1.Job) - - clusters, err := fjc.fedJobInformer.GetReadyClusters() - if err != nil { - return statusError, err - } - - scheduleResult := fjc.schedule(fjob, clusters) - glog.V(3).Infof("Start syncing local job %s: %s\n", key, spew.Sprintf("%v", scheduleResult)) - - fedStatus := batchv1.JobStatus{} - var fedStatusFailedCondition *batchv1.JobCondition - var fedStatusCompleteCondition *batchv1.JobCondition - var operations []fedutil.FederatedOperation - for clusterName, result := range scheduleResult { - ljobObj, exists, err := fjc.fedJobInformer.GetTargetStore().GetByKey(clusterName, key) - if err != nil { - return statusError, err - } - ljob := &batchv1.Job{ - ObjectMeta: fedutil.DeepCopyRelevantObjectMeta(fjob.ObjectMeta), - Spec: *fjob.Spec.DeepCopy(), - } - // use selector generated at federation level, or user specified value - manualSelector := true - ljob.Spec.ManualSelector = &manualSelector - ljob.Spec.Parallelism = result.Parallelism - ljob.Spec.Completions = result.Completions - - if !exists { - if *ljob.Spec.Parallelism > 0 { - fjc.eventRecorder.Eventf(fjob, api.EventTypeNormal, "CreateInCluster", "Creating job in cluster %s", clusterName) - operations = append(operations, fedutil.FederatedOperation{ - Type: fedutil.OperationTypeAdd, - Obj: ljob, - ClusterName: clusterName, - }) - } - } else { - currentLjob := ljobObj.(*batchv1.Job) - - // Update existing job, if needed. - if !fedutil.ObjectMetaAndSpecEquivalent(ljob, currentLjob) { - fjc.eventRecorder.Eventf(fjob, api.EventTypeNormal, "UpdateInCluster", "Updating job in cluster %s", clusterName) - operations = append(operations, fedutil.FederatedOperation{ - Type: fedutil.OperationTypeUpdate, - Obj: ljob, - ClusterName: clusterName, - }) - } - - // collect local job status - for _, condition := range currentLjob.Status.Conditions { - if condition.Type == batchv1.JobComplete { - if fedStatusCompleteCondition == nil || - fedStatusCompleteCondition.LastTransitionTime.Before(&condition.LastTransitionTime) { - fedStatusCompleteCondition = &condition - } - } else if condition.Type == batchv1.JobFailed { - if fedStatusFailedCondition == nil || - fedStatusFailedCondition.LastTransitionTime.Before(&condition.LastTransitionTime) { - fedStatusFailedCondition = &condition - } - } - } - if currentLjob.Status.StartTime != nil { - if fedStatus.StartTime == nil || fedStatus.StartTime.After(currentLjob.Status.StartTime.Time) { - fedStatus.StartTime = currentLjob.Status.StartTime - } - } - if currentLjob.Status.CompletionTime != nil { - if fedStatus.CompletionTime == nil || fedStatus.CompletionTime.Before(currentLjob.Status.CompletionTime) { - fedStatus.CompletionTime = currentLjob.Status.CompletionTime - } - } - fedStatus.Active += currentLjob.Status.Active - fedStatus.Succeeded += currentLjob.Status.Succeeded - fedStatus.Failed += currentLjob.Status.Failed - } - } - - // federated job fails if any local job failes - if fedStatusFailedCondition != nil { - fedStatus.Conditions = append(fedStatus.Conditions, *fedStatusFailedCondition) - } else if fedStatusCompleteCondition != nil { - fedStatus.Conditions = append(fedStatus.Conditions, *fedStatusCompleteCondition) - } - if !reflect.DeepEqual(fedStatus, fjob.Status) { - fjob.Status = fedStatus - _, err = fjc.fedClient.BatchV1().Jobs(fjob.Namespace).UpdateStatus(fjob) - if err != nil { - return statusError, err - } - } - - if len(operations) == 0 { - // Everything is in order - return statusAllOk, nil - } - - if glog.V(4) { - for i, op := range operations { - job := op.Obj.(*batchv1.Job) - glog.V(4).Infof("operation[%d]: %s, %s/%s/%s, %d", i, op.Type, op.ClusterName, job.Namespace, job.Name, *job.Spec.Parallelism) - } - } - err = fjc.fedUpdater.Update(operations) - if err != nil { - return statusError, err - } - - // Some operations were made, reconcile after a while. - return statusNeedRecheck, nil - -} - -func (fjc *FederationJobController) reconcileJobsOnClusterChange() { - if !fjc.isSynced() { - fjc.clusterDeliverer.DeliverAfter(allClustersKey, nil, clusterAvailableDelay) - } - jobs := fjc.jobStore.List() - for _, job := range jobs { - key, _ := controller.KeyFunc(job) - fjc.deliverJobByKey(key, 0, false) - } -} - -// delete deletes the given job or returns error if the deletion was not complete. -func (fjc *FederationJobController) delete(job *batchv1.Job) error { - glog.V(3).Infof("Handling deletion of job: %s/%s\n", job.Namespace, job.Name) - _, err := fjc.deletionHelper.HandleObjectInUnderlyingClusters(job) - if err != nil { - return err - } - - err = fjc.fedClient.BatchV1().Jobs(job.Namespace).Delete(job.Name, nil) - if err != nil { - // Its all good if the error is not found error. That means it is deleted already and we do not have to do anything. - // This is expected when we are processing an update as a result of job finalizer deletion. - // The process that deleted the last finalizer is also going to delete the job and we do not have to do anything. - if !errors.IsNotFound(err) { - return fmt.Errorf("failed to delete job: %s/%s, %v", job.Namespace, job.Name, err) - } - } - return nil -} diff --git a/federation/pkg/federation-controller/job/jobcontroller_test.go b/federation/pkg/federation-controller/job/jobcontroller_test.go deleted file mode 100644 index 65a869baa4a..00000000000 --- a/federation/pkg/federation-controller/job/jobcontroller_test.go +++ /dev/null @@ -1,282 +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 job - -import ( - "flag" - "fmt" - "testing" - "time" - - batchv1 "k8s.io/api/batch/v1" - apiv1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/watch" - kubeclientset "k8s.io/client-go/kubernetes" - kubeclientfake "k8s.io/client-go/kubernetes/fake" - core "k8s.io/client-go/testing" - fedv1 "k8s.io/kubernetes/federation/apis/federation/v1beta1" - fedclientfake "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/fake" - fedutil "k8s.io/kubernetes/federation/pkg/federation-controller/util" - finalizersutil "k8s.io/kubernetes/federation/pkg/federation-controller/util/finalizers" - testutil "k8s.io/kubernetes/federation/pkg/federation-controller/util/test" - batchv1internal "k8s.io/kubernetes/pkg/apis/batch/v1" - - "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/util/sets" - "reflect" - "strings" -) - -func installWatchReactor(fakeClien *core.Fake, resource string) chan runtime.Object { - objChan := make(chan runtime.Object, 100) - - fakeWatch := watch.NewRaceFreeFake() - fakeClien.PrependWatchReactor(resource, core.DefaultWatchReactor(fakeWatch, nil)) - fakeClien.PrependReactor("create", resource, func(action core.Action) (handled bool, ret runtime.Object, err error) { - obj := action.(core.CreateAction).GetObject() - batchv1internal.SetDefaults_Job(obj.(*batchv1.Job)) - fakeWatch.Add(obj) - objChan <- obj - return false, nil, nil - }) - fakeClien.PrependReactor("update", resource, func(action core.Action) (handled bool, ret runtime.Object, err error) { - obj := action.(core.UpdateAction).GetObject() - fakeWatch.Modify(obj) - objChan <- obj - return false, nil, nil - }) - fakeClien.PrependReactor("delete", resource, func(action core.Action) (handled bool, ret runtime.Object, err error) { - obj := &batchv1.Job{ - ObjectMeta: metav1.ObjectMeta{ - Name: action.(core.DeleteAction).GetName(), - Namespace: action.GetNamespace(), - }, - } - fakeWatch.Delete(obj) - objChan <- obj - return false, nil, nil - }) - - return objChan -} - -func TestJobController(t *testing.T) { - flag.Set("logtostderr", "true") - flag.Set("v", "5") - flag.Parse() - - jobReviewDelay = 50 * time.Millisecond - clusterAvailableDelay = 200 * time.Millisecond - clusterUnavailableDelay = 200 * time.Millisecond - - fedclientset := fedclientfake.NewSimpleClientset() - fedChan := installWatchReactor(&fedclientset.Fake, "jobs") - - fedclientset.Federation().Clusters().Create(testutil.NewCluster("k8s-1", apiv1.ConditionTrue)) - fedclientset.Federation().Clusters().Create(testutil.NewCluster("k8s-2", apiv1.ConditionTrue)) - - kube1clientset := kubeclientfake.NewSimpleClientset() - kube1Chan := installWatchReactor(&kube1clientset.Fake, "jobs") - kube2clientset := kubeclientfake.NewSimpleClientset() - kube2Chan := installWatchReactor(&kube2clientset.Fake, "jobs") - - fedInformerClientFactory := func(cluster *fedv1.Cluster) (kubeclientset.Interface, error) { - switch cluster.Name { - case "k8s-1": - return kube1clientset, nil - case "k8s-2": - return kube2clientset, nil - default: - return nil, fmt.Errorf("Unknown cluster: %v", cluster.Name) - } - } - jobController := NewJobController(fedclientset) - fedjobinformer := testutil.ToFederatedInformerForTestOnly(jobController.fedJobInformer) - fedjobinformer.SetClientFactory(fedInformerClientFactory) - - stopChan := make(chan struct{}) - defer close(stopChan) - go jobController.Run(5, stopChan) - - test := func(job *batchv1.Job, parallelism1, parallelism2, completions1, completions2 int32) { - job, _ = fedclientset.Batch().Jobs(metav1.NamespaceDefault).Create(job) - - joinErrors := func(errors []error) error { - if len(errors) == 0 { - return nil - } - errorStrings := []string{} - for _, err := range errors { - errorStrings = append(errorStrings, err.Error()) - } - return fmt.Errorf("%s", strings.Join(errorStrings, "\n")) - } - - // check local jobs are created with correct spec - checkLocalJob := func(parallelism, completions int32) testutil.CheckingFunction { - return func(obj runtime.Object) error { - errors := []error{} - ljob := obj.(*batchv1.Job) - if !fedutil.ObjectMetaEquivalent(job.ObjectMeta, ljob.ObjectMeta) { - errors = append(errors, fmt.Errorf("Job meta un-equivalent: %#v (expected) != %#v (actual)", job.ObjectMeta, ljob.ObjectMeta)) - } - if err := checkEqual(t, *ljob.Spec.Parallelism, parallelism, "Spec.Parallelism"); err != nil { - errors = append(errors, err) - } - if ljob.Spec.Completions != nil { - if err := checkEqual(t, *ljob.Spec.Completions, completions, "Spec.Completions"); err != nil { - errors = append(errors, err) - } - } - return joinErrors(errors) - } - } - checkFedJob := func(obj runtime.Object) error { - errors := []error{} - return joinErrors(errors) - } - assert.NoError(t, testutil.CheckObjectFromChan(kube1Chan, checkLocalJob(parallelism1, completions1))) - assert.NoError(t, testutil.CheckObjectFromChan(kube2Chan, checkLocalJob(parallelism2, completions2))) - assert.NoError(t, testutil.CheckObjectFromChan(fedChan, checkFedJob)) - - // finish local jobs - job1, _ := kube1clientset.Batch().Jobs(metav1.NamespaceDefault).Get(job.Name, metav1.GetOptions{}) - finishJob(job1, 100*time.Millisecond) - job1, _ = kube1clientset.Batch().Jobs(metav1.NamespaceDefault).UpdateStatus(job1) - job2, _ := kube2clientset.Batch().Jobs(metav1.NamespaceDefault).Get(job.Name, metav1.GetOptions{}) - finishJob(job2, 100*time.Millisecond) - job2, _ = kube2clientset.Batch().Jobs(metav1.NamespaceDefault).UpdateStatus(job2) - - // check fed job status updated - assert.NoError(t, testutil.CheckObjectFromChan(fedChan, func(obj runtime.Object) error { - errors := []error{} - job := obj.(*batchv1.Job) - if err := checkEqual(t, *job.Spec.Parallelism, *job1.Spec.Parallelism+*job2.Spec.Parallelism, "Spec.Parallelism"); err != nil { - errors = append(errors, err) - } - if job.Spec.Completions != nil { - if err := checkEqual(t, *job.Spec.Completions, *job1.Spec.Completions+*job2.Spec.Completions, "Spec.Completions"); err != nil { - errors = append(errors, err) - } - } - if err := checkEqual(t, job.Status.Succeeded, job1.Status.Succeeded+job2.Status.Succeeded, "Status.Succeeded"); err != nil { - errors = append(errors, err) - } - return joinErrors(errors) - })) - - // delete fed job by set deletion time, and remove orphan finalizer - job, _ = fedclientset.Batch().Jobs(metav1.NamespaceDefault).Get(job.Name, metav1.GetOptions{}) - deletionTimestamp := metav1.Now() - job.DeletionTimestamp = &deletionTimestamp - finalizersutil.RemoveFinalizers(job, sets.NewString(metav1.FinalizerOrphanDependents)) - fedclientset.Batch().Jobs(metav1.NamespaceDefault).Update(job) - - // check jobs are deleted - checkDeleted := func(obj runtime.Object) error { - djob := obj.(*batchv1.Job) - deletedJob := &batchv1.Job{ - ObjectMeta: metav1.ObjectMeta{ - Name: djob.Name, - Namespace: djob.Namespace, - }, - } - if !reflect.DeepEqual(djob, deletedJob) { - return fmt.Errorf("%s/%s should be deleted", djob.Namespace, djob.Name) - } - return nil - } - assert.NoError(t, testutil.CheckObjectFromChan(kube1Chan, checkDeleted)) - assert.NoError(t, testutil.CheckObjectFromChan(kube2Chan, checkDeleted)) - assert.NoError(t, testutil.CheckObjectFromChan(fedChan, checkDeleted)) - } - - test(newJob("job1", 2, 7), 1, 1, 4, 3) - test(newJob("job2", 2, -1), 1, 1, -1, -1) - test(newJob("job3", 7, 2), 4, 3, 1, 1) - test(newJob("job4", 7, 1), 4, 3, 1, 0) -} - -func checkEqual(_ *testing.T, expected, actual interface{}, msg string) error { - if !assert.ObjectsAreEqual(expected, actual) { - return fmt.Errorf("%s not equal: %#v (expected) != %#v (actual)", msg, expected, actual) - } - return nil -} - -func newJob(name string, parallelism int32, completions int32) *batchv1.Job { - job := batchv1.Job{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: metav1.NamespaceDefault, - SelfLink: "/api/v1/namespaces/default/jobs/name", - }, - Spec: batchv1.JobSpec{ - Parallelism: ¶llelism, - Completions: &completions, - Template: apiv1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "foo": name, - }, - }, - Spec: apiv1.PodSpec{ - Containers: []apiv1.Container{ - {Image: "foo/bar"}, - }, - RestartPolicy: apiv1.RestartPolicyNever, - }, - }, - }, - } - if parallelism < 0 { - job.Spec.Parallelism = nil - } - if completions < 0 { - job.Spec.Completions = nil - } - - batchv1internal.SetDefaults_Job(&job) - return &job -} - -func newCondition(conditionType batchv1.JobConditionType, reason, message string) batchv1.JobCondition { - return batchv1.JobCondition{ - Type: conditionType, - Status: apiv1.ConditionTrue, - LastProbeTime: metav1.Now(), - LastTransitionTime: metav1.Now(), - Reason: reason, - Message: message, - } -} - -func finishJob(job *batchv1.Job, duration time.Duration) { - job.Status.Conditions = append(job.Status.Conditions, newCondition(batchv1.JobComplete, "", "")) - if job.Spec.Completions == nil { - job.Status.Succeeded = 1 - } else { - job.Status.Succeeded = *job.Spec.Completions - } - now := metav1.Now() - job.Status.StartTime = &now - time.Sleep(duration) - now = metav1.Now() - job.Status.CompletionTime = &now -} diff --git a/federation/pkg/federation-controller/service/BUILD b/federation/pkg/federation-controller/service/BUILD deleted file mode 100644 index c81787a7de2..00000000000 --- a/federation/pkg/federation-controller/service/BUILD +++ /dev/null @@ -1,86 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = ["servicecontroller.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/service", - deps = [ - "//federation/apis/federation:go_default_library", - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/client/clientset_generated/federation_clientset:go_default_library", - "//federation/pkg/federation-controller/service/ingress:go_default_library", - "//federation/pkg/federation-controller/util:go_default_library", - "//federation/pkg/federation-controller/util/clusterselector:go_default_library", - "//federation/pkg/federation-controller/util/deletionhelper:go_default_library", - "//federation/pkg/federation-controller/util/eventsink:go_default_library", - "//pkg/api:go_default_library", - "//pkg/controller:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/listers/core/v1:go_default_library", - "//vendor/k8s.io/client-go/tools/cache:go_default_library", - "//vendor/k8s.io/client-go/tools/record:go_default_library", - "//vendor/k8s.io/client-go/util/flowcontrol:go_default_library", - "//vendor/k8s.io/client-go/util/workqueue:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["servicecontroller_test.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/service", - library = ":go_default_library", - deps = [ - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/client/clientset_generated/federation_clientset/fake:go_default_library", - "//federation/pkg/federation-controller/service/ingress:go_default_library", - "//federation/pkg/federation-controller/util:go_default_library", - "//federation/pkg/federation-controller/util/deletionhelper:go_default_library", - "//federation/pkg/federation-controller/util/test:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/github.com/stretchr/testify/require:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", - "//vendor/k8s.io/client-go/listers/core/v1:go_default_library", - "//vendor/k8s.io/client-go/tools/cache:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/pkg/federation-controller/service/dns:all-srcs", - "//federation/pkg/federation-controller/service/ingress:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/pkg/federation-controller/service/dns/BUILD b/federation/pkg/federation-controller/service/dns/BUILD deleted file mode 100644 index 7c1a2c8b12a..00000000000 --- a/federation/pkg/federation-controller/service/dns/BUILD +++ /dev/null @@ -1,62 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = ["dns_test.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/service/dns", - library = ":go_default_library", - deps = [ - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/client/clientset_generated/federation_clientset/fake:go_default_library", - "//federation/pkg/dnsprovider/providers/google/clouddns:go_default_library", - "//federation/pkg/federation-controller/service/ingress:go_default_library", - "//federation/pkg/federation-controller/util/test:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - ], -) - -go_library( - name = "go_default_library", - srcs = ["dns.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/service/dns", - deps = [ - "//federation/client/clientset_generated/federation_clientset:go_default_library", - "//federation/pkg/dnsprovider:go_default_library", - "//federation/pkg/dnsprovider/rrstype:go_default_library", - "//federation/pkg/federation-controller/service/ingress:go_default_library", - "//federation/pkg/federation-controller/util:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/listers/core/v1:go_default_library", - "//vendor/k8s.io/client-go/tools/cache:go_default_library", - "//vendor/k8s.io/client-go/util/flowcontrol:go_default_library", - "//vendor/k8s.io/client-go/util/workqueue:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/pkg/federation-controller/service/dns/dns.go b/federation/pkg/federation-controller/service/dns/dns.go deleted file mode 100644 index e478d76e6bf..00000000000 --- a/federation/pkg/federation-controller/service/dns/dns.go +++ /dev/null @@ -1,549 +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 dns - -import ( - "fmt" - "net" - "strings" - "time" - - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - pkgruntime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/apimachinery/pkg/watch" - corelisters "k8s.io/client-go/listers/core/v1" - "k8s.io/client-go/tools/cache" - "k8s.io/client-go/util/flowcontrol" - "k8s.io/client-go/util/workqueue" - fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - "k8s.io/kubernetes/federation/pkg/dnsprovider" - "k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype" - "k8s.io/kubernetes/federation/pkg/federation-controller/service/ingress" - "k8s.io/kubernetes/federation/pkg/federation-controller/util" - - "github.com/golang/glog" -) - -const ( - ControllerName = "service-dns" - - UserAgentName = "federation-service-dns-controller" - - // minDNSTTL is the minimum safe DNS TTL value to use (in seconds). We use this as the TTL for all DNS records. - minDNSTTL = 180 - - serviceSyncPeriod = 30 * time.Second -) - -type ServiceDNSController struct { - // Client to federation api server - federationClient fedclientset.Interface - dns dnsprovider.Interface - federationName string - // serviceDNSSuffix is the DNS suffix we use when publishing service DNS names - serviceDNSSuffix string - // zoneName and zoneID are used to identify the zone in which to put records - zoneName string - zoneID string - dnsZones dnsprovider.Zones - // each federation should be configured with a single zone (e.g. "mycompany.com") - dnsZone dnsprovider.Zone - // Informer Store for federated services - serviceStore corelisters.ServiceLister - // Informer controller for federated services - serviceController cache.Controller - workQueue workqueue.Interface - objectDeliverer *util.DelayingDeliverer - flowcontrolBackoff *flowcontrol.Backoff -} - -// NewServiceDNSController returns a new service dns controller to manage DNS records for federated services -func NewServiceDNSController(client fedclientset.Interface, dnsProvider, dnsProviderConfig, federationName, - serviceDNSSuffix, zoneName, zoneID string) (*ServiceDNSController, error) { - dns, err := dnsprovider.InitDnsProvider(dnsProvider, dnsProviderConfig) - if err != nil { - runtime.HandleError(fmt.Errorf("DNS provider could not be initialized: %v", err)) - return nil, err - } - d := &ServiceDNSController{ - federationClient: client, - dns: dns, - federationName: federationName, - serviceDNSSuffix: serviceDNSSuffix, - zoneName: zoneName, - zoneID: zoneID, - workQueue: workqueue.New(), - objectDeliverer: util.NewDelayingDeliverer(), - flowcontrolBackoff: flowcontrol.NewBackOff(5*time.Second, time.Minute), - } - if err := d.validateConfig(); err != nil { - runtime.HandleError(fmt.Errorf("Invalid configuration passed to DNS provider: %v", err)) - return nil, err - } - if err := d.retrieveOrCreateDNSZone(); err != nil { - runtime.HandleError(fmt.Errorf("Failed to retrieve DNS zone: %v", err)) - return nil, err - } - - // Start informer in federated API servers on federated services - var serviceIndexer cache.Indexer - serviceIndexer, d.serviceController = cache.NewIndexerInformer( - &cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (pkgruntime.Object, error) { - return client.Core().Services(metav1.NamespaceAll).List(options) - }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return client.Core().Services(metav1.NamespaceAll).Watch(options) - }, - }, - &v1.Service{}, - serviceSyncPeriod, - util.NewTriggerOnAllChanges(func(obj pkgruntime.Object) { d.workQueue.Add(obj) }), - cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, - ) - d.serviceStore = corelisters.NewServiceLister(serviceIndexer) - - return d, nil -} - -func (s *ServiceDNSController) DNSControllerRun(workers int, stopCh <-chan struct{}) { - defer runtime.HandleCrash() - defer s.workQueue.ShutDown() - - glog.Infof("Starting federation service dns controller") - - s.objectDeliverer.StartWithHandler(func(item *util.DelayingDelivererItem) { - s.workQueue.Add(item.Value.(*v1.Service)) - }) - defer s.objectDeliverer.Stop() - - util.StartBackoffGC(s.flowcontrolBackoff, stopCh) - go s.serviceController.Run(stopCh) - - for i := 0; i < workers; i++ { - go wait.Until(s.worker, time.Second, stopCh) - } - - <-stopCh - glog.Infof("Stopping federation service dns controller") -} - -// Adds backoff to delay if this delivery is related to some failure. Resets backoff if there was no failure. -func (s *ServiceDNSController) deliverService(service *v1.Service, delay time.Duration, failed bool) { - if failed { - s.flowcontrolBackoff.Next(service.String(), time.Now()) - delay = delay + s.flowcontrolBackoff.Get(service.String()) - } else { - s.flowcontrolBackoff.Reset(service.String()) - } - s.objectDeliverer.DeliverAfter(service.String(), service, delay) -} - -func wantsDNSRecords(service *v1.Service) bool { - return service.Spec.Type == v1.ServiceTypeLoadBalancer -} - -func (s *ServiceDNSController) workerFunction() bool { - item, quit := s.workQueue.Get() - if quit { - return true - } - defer s.workQueue.Done(item) - - service := item.(*v1.Service) - - if !wantsDNSRecords(service) { - return false - } - - ingress, err := ingress.ParseFederatedServiceIngress(service) - if err != nil { - runtime.HandleError(fmt.Errorf("Error in parsing lb ingress for service %s/%s: %v", service.Namespace, service.Name, err)) - return false - } - for _, clusterIngress := range ingress.Items { - err = s.ensureDNSRecords(clusterIngress.Cluster, service) - if err != nil { - runtime.HandleError(fmt.Errorf("Error when ensuring DNS records for service %s/%s: %v", service.Namespace, service.Name, err)) - s.deliverService(service, 0, true) - } - } - return false -} - -func (s *ServiceDNSController) worker() { - for { - if quit := s.workerFunction(); quit { - glog.Infof("service dns controller worker queue shutting down") - return - } - } -} - -func (s *ServiceDNSController) validateConfig() error { - if s.federationName == "" { - return fmt.Errorf("DNSController should not be run without federationName") - } - if s.zoneName == "" && s.zoneID == "" { - return fmt.Errorf("DNSController must be run with either zoneName or zoneID") - } - if s.serviceDNSSuffix == "" { - if s.zoneName == "" { - return fmt.Errorf("DNSController must be run with zoneName, if serviceDnsSuffix is not set") - } - s.serviceDNSSuffix = s.zoneName - } - if s.dns == nil { - return fmt.Errorf("DNSController should not be run without a dnsprovider") - } - zones, ok := s.dns.Zones() - if !ok { - return fmt.Errorf("the dns provider does not support zone enumeration, which is required for creating dns records") - } - s.dnsZones = zones - return nil -} - -func (s *ServiceDNSController) retrieveOrCreateDNSZone() error { - matchingZones, err := getDNSZones(s.zoneName, s.zoneID, s.dnsZones) - if err != nil { - return fmt.Errorf("error querying for DNS zones: %v", err) - } - switch len(matchingZones) { - case 0: // No matching zones for s.zoneName, so create one - if s.zoneName == "" { - return fmt.Errorf("DNSController must be run with zoneName to create zone automatically") - } - glog.Infof("DNS zone %q not found. Creating DNS zone %q.", s.zoneName, s.zoneName) - managedZone, err := s.dnsZones.New(s.zoneName) - if err != nil { - return err - } - zone, err := s.dnsZones.Add(managedZone) - if err != nil { - return err - } - glog.Infof("DNS zone %q successfully created. Note that DNS resolution will not work until you have registered this name with "+ - "a DNS registrar and they have changed the authoritative name servers for your domain to point to your DNS provider", zone.Name()) - case 1: // s.zoneName matches exactly one DNS zone - s.dnsZone = matchingZones[0] - default: // s.zoneName matches more than one DNS zone - return fmt.Errorf("Multiple matching DNS zones found for %q; please specify zoneID", s.zoneName) - } - return nil -} - -// getHealthyEndpoints returns the hostnames and/or IP addresses of healthy endpoints for the service, at a zone, region and global level (or an error) -func (s *ServiceDNSController) getHealthyEndpoints(clusterName string, service *v1.Service) (zoneEndpoints, regionEndpoints, globalEndpoints []string, err error) { - var ( - zoneNames []string - regionName string - ) - if zoneNames, regionName, err = s.getClusterZoneNames(clusterName); err != nil { - return nil, nil, nil, err - } - - // If federated service is deleted, return empty endpoints, so that DNS records are removed - if service.DeletionTimestamp != nil { - return zoneEndpoints, regionEndpoints, globalEndpoints, nil - } - - serviceIngress, err := ingress.ParseFederatedServiceIngress(service) - if err != nil { - return nil, nil, nil, err - } - - for _, lbClusterIngress := range serviceIngress.Items { - lbClusterName := lbClusterIngress.Cluster - lbZoneNames, lbRegionName, err := s.getClusterZoneNames(lbClusterName) - if err != nil { - return nil, nil, nil, err - } - for _, ingress := range lbClusterIngress.Items { - var address string - // We should get either an IP address or a hostname - use whichever one we get - if ingress.IP != "" { - address = ingress.IP - } else if ingress.Hostname != "" { - address = ingress.Hostname - } - if len(address) <= 0 { - return nil, nil, nil, fmt.Errorf("Service %s/%s in cluster %s has neither LoadBalancerStatus.ingress.ip nor LoadBalancerStatus.ingress.hostname. Cannot use it as endpoint for federated service", - service.Name, service.Namespace, clusterName) - } - for _, lbZoneName := range lbZoneNames { - for _, zoneName := range zoneNames { - if lbZoneName == zoneName { - zoneEndpoints = append(zoneEndpoints, address) - } - } - } - if lbRegionName == regionName { - regionEndpoints = append(regionEndpoints, address) - } - globalEndpoints = append(globalEndpoints, address) - } - } - return zoneEndpoints, regionEndpoints, globalEndpoints, nil -} - -// getClusterZoneNames returns the name of the zones (and the region) where the specified cluster exists (e.g. zones "us-east1-c" on GCE, or "us-east-1b" on AWS) -func (s *ServiceDNSController) getClusterZoneNames(clusterName string) ([]string, string, error) { - cluster, err := s.federationClient.Federation().Clusters().Get(clusterName, metav1.GetOptions{}) - if err != nil { - return nil, "", err - } - return cluster.Status.Zones, cluster.Status.Region, nil -} - -// getDNSZones returns the DNS zones matching dnsZoneName and dnsZoneID (if specified) -func getDNSZones(dnsZoneName string, dnsZoneID string, dnsZonesInterface dnsprovider.Zones) ([]dnsprovider.Zone, error) { - // TODO: We need query-by-name and query-by-id functions - dnsZones, err := dnsZonesInterface.List() - if err != nil { - return nil, err - } - - var matches []dnsprovider.Zone - findName := strings.TrimSuffix(dnsZoneName, ".") - for _, dnsZone := range dnsZones { - if dnsZoneID != "" { - if dnsZoneID != dnsZone.ID() { - continue - } - } - if findName != "" { - if strings.TrimSuffix(dnsZone.Name(), ".") != findName { - continue - } - } - matches = append(matches, dnsZone) - } - - return matches, nil -} - -// NOTE: that if the named resource record set does not exist, but no -// error occurred, the returned list will be empty, and the error will -// be nil -func getRrset(dnsName string, rrsetsInterface dnsprovider.ResourceRecordSets) ([]dnsprovider.ResourceRecordSet, error) { - return rrsetsInterface.Get(dnsName) -} - -func findRrset(list []dnsprovider.ResourceRecordSet, rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordSet { - for i, elem := range list { - if dnsprovider.ResourceRecordSetsEquivalent(rrset, elem) { - return list[i] - } - } - return nil -} - -/* getResolvedEndpoints performs DNS resolution on the provided slice of endpoints (which might be DNS names or IPv4 addresses) - and returns a list of IPv4 addresses. If any of the endpoints are neither valid IPv4 addresses nor resolvable DNS names, - non-nil error is also returned (possibly along with a partially complete list of resolved endpoints. -*/ -func getResolvedEndpoints(endpoints []string) ([]string, error) { - resolvedEndpoints := sets.String{} - for _, endpoint := range endpoints { - if net.ParseIP(endpoint) == nil { - // It's not a valid IP address, so assume it's a DNS name, and try to resolve it, - // replacing its DNS name with its IP addresses in expandedEndpoints - ipAddrs, err := net.LookupHost(endpoint) - if err != nil { - return resolvedEndpoints.List(), err - } - for _, ip := range ipAddrs { - resolvedEndpoints = resolvedEndpoints.Union(sets.NewString(ip)) - } - } else { - resolvedEndpoints = resolvedEndpoints.Union(sets.NewString(endpoint)) - } - } - return resolvedEndpoints.List(), nil -} - -/* ensureDNSRrsets ensures (idempotently, and with minimum mutations) that all of the DNS resource record sets for dnsName are consistent with endpoints. - if endpoints is nil or empty, a CNAME record to uplevelCname is ensured. -*/ -func (s *ServiceDNSController) ensureDNSRrsets(dnsZone dnsprovider.Zone, dnsName string, endpoints []string, uplevelCname string) error { - rrsets, supported := dnsZone.ResourceRecordSets() - if !supported { - return fmt.Errorf("Failed to ensure DNS records for %s. DNS provider does not support the ResourceRecordSets interface", dnsName) - } - rrsetList, err := getRrset(dnsName, rrsets) // TODO: rrsets.Get(dnsName) - if err != nil { - return err - } - if len(rrsetList) == 0 { - glog.V(4).Infof("No recordsets found for DNS name %q. Need to add either A records (if we have healthy endpoints), or a CNAME record to %q", dnsName, uplevelCname) - if len(endpoints) < 1 { - glog.V(4).Infof("There are no healthy endpoint addresses at level %q, so CNAME to %q, if provided", dnsName, uplevelCname) - if uplevelCname != "" { - glog.V(4).Infof("Creating CNAME to %q for %q", uplevelCname, dnsName) - newRrset := rrsets.New(dnsName, []string{uplevelCname}, minDNSTTL, rrstype.CNAME) - glog.V(4).Infof("Adding recordset %v", newRrset) - err = rrsets.StartChangeset().Add(newRrset).Apply() - if err != nil { - return err - } - glog.V(4).Infof("Successfully created CNAME to %q for %q", uplevelCname, dnsName) - } else { - glog.V(4).Infof("We want no record for %q, and we have no record, so we're all good.", dnsName) - } - } else { - // We have valid endpoint addresses, so just add them as A records. - // But first resolve DNS names, as some cloud providers (like AWS) expose - // load balancers behind DNS names, not IP addresses. - glog.V(4).Infof("We have valid endpoint addresses %v at level %q, so add them as A records, after resolving DNS names", endpoints, dnsName) - resolvedEndpoints, err := getResolvedEndpoints(endpoints) - if err != nil { - return err // TODO: We could potentially add the ones we did get back, even if some of them failed to resolve. - } - newRrset := rrsets.New(dnsName, resolvedEndpoints, minDNSTTL, rrstype.A) - glog.V(4).Infof("Adding recordset %v", newRrset) - err = rrsets.StartChangeset().Add(newRrset).Apply() - if err != nil { - return err - } - glog.V(4).Infof("Successfully added recordset %v", newRrset) - } - } else { - // the rrsets already exists, so make it right. - glog.V(4).Infof("Recordset %v already exists. Ensuring that it is correct.", rrsetList) - if len(endpoints) < 1 { - // Need an appropriate CNAME record. Check that we have it. - newRrset := rrsets.New(dnsName, []string{uplevelCname}, minDNSTTL, rrstype.CNAME) - glog.V(4).Infof("No healthy endpoints for %s. Have recordsets %v. Need recordset %v", dnsName, rrsetList, newRrset) - found := findRrset(rrsetList, newRrset) - if found != nil { - // The existing rrset is equivalent to the required one - our work is done here - glog.V(4).Infof("Existing recordset %v is equivalent to needed recordset %v, our work is done here.", rrsetList, newRrset) - return nil - } else { - // Need to replace the existing one with a better one (or just remove it if we have no healthy endpoints). - glog.V(4).Infof("Existing recordset %v not equivalent to needed recordset %v removing existing and adding needed.", rrsetList, newRrset) - changeSet := rrsets.StartChangeset() - for i := range rrsetList { - changeSet = changeSet.Remove(rrsetList[i]) - } - if uplevelCname != "" { - changeSet = changeSet.Add(newRrset) - if err := changeSet.Apply(); err != nil { - return err - } - glog.V(4).Infof("Successfully replaced needed recordset %v -> %v", found, newRrset) - } else { - if err := changeSet.Apply(); err != nil { - return err - } - glog.V(4).Infof("Successfully removed existing recordset %v", found) - glog.V(4).Infof("Uplevel CNAME is empty string. Not adding recordset %v", newRrset) - } - } - } else { - // We have an rrset in DNS, possibly with some missing addresses and some unwanted addresses. - // And we have healthy endpoints. Just replace what's there with the healthy endpoints, if it's not already correct. - glog.V(4).Infof("%s: Healthy endpoints %v exist. Recordset %v exists. Reconciling.", dnsName, endpoints, rrsetList) - resolvedEndpoints, err := getResolvedEndpoints(endpoints) - if err != nil { // Some invalid addresses or otherwise unresolvable DNS names. - return err // TODO: We could potentially add the ones we did get back, even if some of them failed to resolve. - } - newRrset := rrsets.New(dnsName, resolvedEndpoints, minDNSTTL, rrstype.A) - glog.V(4).Infof("Have recordset %v. Need recordset %v", rrsetList, newRrset) - found := findRrset(rrsetList, newRrset) - if found != nil { - glog.V(4).Infof("Existing recordset %v is equivalent to needed recordset %v, our work is done here.", found, newRrset) - // TODO: We could be more thorough about checking for equivalence to avoid unnecessary updates, but in the - // worst case we'll just replace what's there with an equivalent, if not exactly identical record set. - return nil - } else { - // Need to replace the existing one with a better one - glog.V(4).Infof("Existing recordset %v is not equivalent to needed recordset %v, removing existing and adding needed.", found, newRrset) - changeSet := rrsets.StartChangeset() - for i := range rrsetList { - changeSet = changeSet.Remove(rrsetList[i]) - } - changeSet = changeSet.Add(newRrset) - if err = changeSet.Apply(); err != nil { - return err - } - glog.V(4).Infof("Successfully replaced recordset %v -> %v", found, newRrset) - } - } - } - return nil -} - -/* ensureDNSRecords ensures (idempotently, and with minimum mutations) that all of the DNS records for a service in a given cluster are correct, -given the current state of that service in that cluster. This should be called every time the state of a service might have changed -(either w.r.t. its loadbalancer address, or if the number of healthy backend endpoints for that service transitioned from zero to non-zero -(or vice versa). Only shards of the service which have both a loadbalancer ingress IP address or hostname AND at least one healthy backend endpoint -are included in DNS records for that service (at all of zone, region and global levels). All other addresses are removed. Also, if no shards exist -in the zone or region of the cluster, a CNAME reference to the next higher level is ensured to exist. */ -func (s *ServiceDNSController) ensureDNSRecords(clusterName string, service *v1.Service) error { - // Quinton: Pseudocode.... - // See https://github.com/kubernetes/kubernetes/pull/25107#issuecomment-218026648 - // For each service we need the following DNS names: - // mysvc.myns.myfed.svc.z1.r1.mydomain.com (for zone z1 in region r1) - // - an A record to IP address of specific shard in that zone (if that shard exists and has healthy endpoints) - // - OR a CNAME record to the next level up, i.e. mysvc.myns.myfed.svc.r1.mydomain.com (if a healthy shard does not exist in zone z1) - // mysvc.myns.myfed.svc.r1.mydomain.com - // - a set of A records to IP addresses of all healthy shards in region r1, if one or more of these exist - // - OR a CNAME record to the next level up, i.e. mysvc.myns.myfed.svc.mydomain.com (if no healthy shards exist in region r1) - // mysvc.myns.myfed.svc.mydomain.com - // - a set of A records to IP addresses of all healthy shards in all regions, if one or more of these exist. - // - no record (NXRECORD response) if no healthy shards exist in any regions - // - // Each service has the current known state of loadbalancer ingress for the federated cluster stored in annotations. - // So generate the DNS records based on the current state and ensure those desired DNS records match the - // actual DNS records (add new records, remove deleted records, and update changed records). - // - serviceName := service.Name - namespaceName := service.Namespace - zoneNames, regionName, err := s.getClusterZoneNames(clusterName) - if err != nil { - return err - } - if zoneNames == nil { - return fmt.Errorf("failed to get cluster zone names") - } - commonPrefix := serviceName + "." + namespaceName + "." + s.federationName + ".svc" - // dnsNames is the path up the DNS search tree, starting at the leaf - dnsNames := []string{ - strings.Join([]string{commonPrefix, zoneNames[0], regionName, s.serviceDNSSuffix}, "."), // zone level - TODO might need other zone names for multi-zone clusters - strings.Join([]string{commonPrefix, regionName, s.serviceDNSSuffix}, "."), // region level, one up from zone level - strings.Join([]string{commonPrefix, s.serviceDNSSuffix}, "."), // global level, one up from region level - "", // nowhere to go up from global level - } - - zoneEndpoints, regionEndpoints, globalEndpoints, err := s.getHealthyEndpoints(clusterName, service) - if err != nil { - return err - } - endpoints := [][]string{zoneEndpoints, regionEndpoints, globalEndpoints} - for i, endpoint := range endpoints { - if err = s.ensureDNSRrsets(s.dnsZone, dnsNames[i], endpoint, dnsNames[i+1]); err != nil { - return err - } - } - return nil -} diff --git a/federation/pkg/federation-controller/service/dns/dns_test.go b/federation/pkg/federation-controller/service/dns/dns_test.go deleted file mode 100644 index b30c23c7ef6..00000000000 --- a/federation/pkg/federation-controller/service/dns/dns_test.go +++ /dev/null @@ -1,251 +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 dns - -import ( - "fmt" - "sort" - "testing" - "time" - - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/federation/apis/federation/v1beta1" - fakefedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/fake" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns" // Only for unit testing purposes. - "k8s.io/kubernetes/federation/pkg/federation-controller/service/ingress" - . "k8s.io/kubernetes/federation/pkg/federation-controller/util/test" -) - -// NewClusterWithRegionZone builds a new cluster object with given region and zone attributes. -func NewClusterWithRegionZone(name string, readyStatus v1.ConditionStatus, region, zone string) *v1beta1.Cluster { - cluster := NewCluster(name, readyStatus) - cluster.Status.Zones = []string{zone} - cluster.Status.Region = region - return cluster -} - -func TestServiceController_ensureDnsRecords(t *testing.T) { - cluster1Name := "c1" - cluster2Name := "c2" - cluster1 := NewClusterWithRegionZone(cluster1Name, v1.ConditionTrue, "fooregion", "foozone") - cluster2 := NewClusterWithRegionZone(cluster2Name, v1.ConditionTrue, "barregion", "barzone") - globalDNSName := "servicename.servicenamespace.myfederation.svc.federation.example.com" - fooRegionDNSName := "servicename.servicenamespace.myfederation.svc.fooregion.federation.example.com" - fooZoneDNSName := "servicename.servicenamespace.myfederation.svc.foozone.fooregion.federation.example.com" - barRegionDNSName := "servicename.servicenamespace.myfederation.svc.barregion.federation.example.com" - barZoneDNSName := "servicename.servicenamespace.myfederation.svc.barzone.barregion.federation.example.com" - - tests := []struct { - name string - service v1.Service - expected sets.String - }{ - { - name: "ServiceWithSingleLBIngress", - service: v1.Service{ - ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{ - ingress.FederatedServiceIngressAnnotation: ingress.NewFederatedServiceIngress(). - AddEndpoints(cluster1Name, []string{"198.51.100.1"}). - AddEndpoints(cluster2Name, []string{}). - String()}, - }, - }, - expected: sets.NewString( - "example.com:"+globalDNSName+":A:180:[198.51.100.1]", - "example.com:"+fooRegionDNSName+":A:180:[198.51.100.1]", - "example.com:"+fooZoneDNSName+":A:180:[198.51.100.1]", - "example.com:"+barRegionDNSName+":CNAME:180:["+globalDNSName+"]", - "example.com:"+barZoneDNSName+":CNAME:180:["+barRegionDNSName+"]", - ), - }, - /* - TODO: getResolvedEndpoints preforms DNS lookup. - Mock and maybe look at error handling when some endpoints resolve, but also caching? - { - name: "withname", - service: v1.Service{ - ObjectMeta: metav1.ObjectMeta{}, - }, - expected: []string{ - "example.com:"+globalDNSName+":A:180:[198.51.100.1]", - "example.com:"+fooRegionDNSName+":A:180:[198.51.100.1]", - "example.com:"+fooZoneDNSName+":A:180:[198.51.100.1]", - }, - }, - */ - { - name: "ServiceWithNoLBIngress", - service: v1.Service{ - ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{ - ingress.FederatedServiceIngressAnnotation: ingress.NewFederatedServiceIngress(). - AddEndpoints(cluster1Name, []string{}). - AddEndpoints(cluster2Name, []string{}). - String()}, - }, - }, - expected: sets.NewString( - "example.com:"+fooRegionDNSName+":CNAME:180:["+globalDNSName+"]", - "example.com:"+fooZoneDNSName+":CNAME:180:["+fooRegionDNSName+"]", - "example.com:"+barRegionDNSName+":CNAME:180:["+globalDNSName+"]", - "example.com:"+barZoneDNSName+":CNAME:180:["+barRegionDNSName+"]", - ), - }, - { - name: "ServiceWithMultipleLBIngress", - service: v1.Service{ - ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{ - ingress.FederatedServiceIngressAnnotation: ingress.NewFederatedServiceIngress(). - AddEndpoints(cluster1Name, []string{"198.51.100.1"}). - AddEndpoints(cluster2Name, []string{"198.51.200.1"}). - String()}, - }, - }, - expected: sets.NewString( - "example.com:"+globalDNSName+":A:180:[198.51.100.1 198.51.200.1]", - "example.com:"+fooRegionDNSName+":A:180:[198.51.100.1]", - "example.com:"+fooZoneDNSName+":A:180:[198.51.100.1]", - "example.com:"+barRegionDNSName+":A:180:[198.51.200.1]", - "example.com:"+barZoneDNSName+":A:180:[198.51.200.1]", - ), - }, - { - name: "ServiceWithLBIngressAndServiceDeleted", - service: v1.Service{ - ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{ - ingress.FederatedServiceIngressAnnotation: ingress.NewFederatedServiceIngress(). - AddEndpoints(cluster1Name, []string{"198.51.100.1"}). - AddEndpoints(cluster2Name, []string{"198.51.200.1"}). - String()}, - DeletionTimestamp: &metav1.Time{Time: time.Now()}, - }, - }, - expected: sets.NewString( - // TODO: Ideally we should expect that there are no DNS records when federated service is deleted. Need to remove these leaks in future - "example.com:"+fooRegionDNSName+":CNAME:180:["+globalDNSName+"]", - "example.com:"+fooZoneDNSName+":CNAME:180:["+fooRegionDNSName+"]", - "example.com:"+barRegionDNSName+":CNAME:180:["+globalDNSName+"]", - "example.com:"+barZoneDNSName+":CNAME:180:["+barRegionDNSName+"]", - ), - }, - { - name: "ServiceWithMultipleLBIngressAndOneLBIngressGettingRemoved", - service: v1.Service{ - ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{ - ingress.FederatedServiceIngressAnnotation: ingress.NewFederatedServiceIngress(). - AddEndpoints(cluster1Name, []string{"198.51.100.1"}). - AddEndpoints(cluster2Name, []string{"198.51.200.1"}). - RemoveEndpoint(cluster2Name, "198.51.200.1"). - String()}, - }, - }, - expected: sets.NewString( - "example.com:"+globalDNSName+":A:180:[198.51.100.1]", - "example.com:"+fooRegionDNSName+":A:180:[198.51.100.1]", - "example.com:"+fooZoneDNSName+":A:180:[198.51.100.1]", - "example.com:"+barRegionDNSName+":CNAME:180:["+globalDNSName+"]", - "example.com:"+barZoneDNSName+":CNAME:180:["+barRegionDNSName+"]", - ), - }, - { - name: "ServiceWithMultipleLBIngressAndAllLBIngressGettingRemoved", - service: v1.Service{ - ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{ - ingress.FederatedServiceIngressAnnotation: ingress.NewFederatedServiceIngress(). - AddEndpoints(cluster1Name, []string{"198.51.100.1"}). - AddEndpoints(cluster2Name, []string{"198.51.200.1"}). - RemoveEndpoint(cluster1Name, "198.51.100.1"). - RemoveEndpoint(cluster2Name, "198.51.200.1"). - String()}, - }, - }, - expected: sets.NewString( - "example.com:"+fooRegionDNSName+":CNAME:180:["+globalDNSName+"]", - "example.com:"+fooZoneDNSName+":CNAME:180:["+fooRegionDNSName+"]", - "example.com:"+barRegionDNSName+":CNAME:180:["+globalDNSName+"]", - "example.com:"+barZoneDNSName+":CNAME:180:["+barRegionDNSName+"]", - ), - }, - } - for _, test := range tests { - fakedns, _ := clouddns.NewFakeInterface() - fakednsZones, ok := fakedns.Zones() - if !ok { - t.Error("Unable to fetch zones") - } - fakeClient := &fakefedclientset.Clientset{} - RegisterFakeClusterGet(&fakeClient.Fake, &v1beta1.ClusterList{Items: []v1beta1.Cluster{*cluster1, *cluster2}}) - d := ServiceDNSController{ - federationClient: fakeClient, - dns: fakedns, - dnsZones: fakednsZones, - serviceDNSSuffix: "federation.example.com", - zoneName: "example.com", - federationName: "myfederation", - } - - dnsZones, err := getDNSZones(d.zoneName, d.zoneID, d.dnsZones) - if err != nil { - t.Errorf("Test failed for %s, Get DNS Zones failed: %v", test.name, err) - } - d.dnsZone = dnsZones[0] - test.service.Name = "servicename" - test.service.Namespace = "servicenamespace" - - ingress, err := ingress.ParseFederatedServiceIngress(&test.service) - if err != nil { - t.Errorf("Error in parsing lb ingress for service %s/%s: %v", test.service.Namespace, test.service.Name, err) - return - } - for _, clusterIngress := range ingress.Items { - d.ensureDNSRecords(clusterIngress.Cluster, &test.service) - } - - zones, err := fakednsZones.List() - if err != nil { - t.Errorf("error querying zones: %v", err) - } - - // Dump every record to a testable-by-string-comparison form - records := sets.NewString() - for _, z := range zones { - zoneName := z.Name() - - rrs, ok := z.ResourceRecordSets() - if !ok { - t.Errorf("cannot get rrs for zone %q", zoneName) - } - - rrList, err := rrs.List() - if err != nil { - t.Errorf("error querying rr for zone %q: %v", zoneName, err) - } - for _, rr := range rrList { - rrdatas := rr.Rrdatas() - - // Put in consistent (testable-by-string-comparison) order - sort.Strings(rrdatas) - records.Insert(fmt.Sprintf("%s:%s:%s:%d:%s", zoneName, rr.Name(), rr.Type(), rr.Ttl(), rrdatas)) - } - } - - if !records.Equal(test.expected) { - t.Errorf("Test %q failed. Actual=%v, Expected=%v", test.name, records, test.expected) - } - } -} diff --git a/federation/pkg/federation-controller/service/ingress/BUILD b/federation/pkg/federation-controller/service/ingress/BUILD deleted file mode 100644 index a849819ed57..00000000000 --- a/federation/pkg/federation-controller/service/ingress/BUILD +++ /dev/null @@ -1,29 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["ingress.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/service/ingress", - deps = [ - "//federation/apis/federation:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/pkg/federation-controller/service/ingress/ingress.go b/federation/pkg/federation-controller/service/ingress/ingress.go deleted file mode 100644 index 776ccf78fe1..00000000000 --- a/federation/pkg/federation-controller/service/ingress/ingress.go +++ /dev/null @@ -1,136 +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 ingress - -import ( - "encoding/json" - "sort" - "strings" - - "k8s.io/api/core/v1" - fedapi "k8s.io/kubernetes/federation/apis/federation" -) - -// Compile time check for interface adherence -var _ sort.Interface = &FederatedServiceIngress{} - -const ( - FederatedServiceIngressAnnotation = "federation.kubernetes.io/service-ingresses" -) - -// FederatedServiceIngress implements sort.Interface. -type FederatedServiceIngress struct { - fedapi.FederatedServiceIngress -} - -func NewFederatedServiceIngress() *FederatedServiceIngress { - return &FederatedServiceIngress{} -} - -func (ingress *FederatedServiceIngress) String() string { - annotationBytes, _ := json.Marshal(ingress) - return string(annotationBytes[:]) -} - -// Len is to satisfy of sort.Interface. -func (ingress *FederatedServiceIngress) Len() int { - return len(ingress.Items) -} - -// Less is to satisfy of sort.Interface. -func (ingress *FederatedServiceIngress) Less(i, j int) bool { - return (strings.Compare(ingress.Items[i].Cluster, ingress.Items[j].Cluster) < 0) -} - -// Swap is to satisfy of sort.Interface. -func (ingress *FederatedServiceIngress) Swap(i, j int) { - ingress.Items[i].Cluster, ingress.Items[j].Cluster = ingress.Items[j].Cluster, ingress.Items[i].Cluster - ingress.Items[i].Items, ingress.Items[j].Items = ingress.Items[j].Items, ingress.Items[i].Items -} - -// GetClusterLoadBalancerIngresses returns loadbalancer ingresses for given cluster if exist otherwise returns an empty slice -func (ingress *FederatedServiceIngress) GetClusterLoadBalancerIngresses(cluster string) []v1.LoadBalancerIngress { - for _, clusterIngress := range ingress.Items { - if cluster == clusterIngress.Cluster { - return clusterIngress.Items - } - } - return []v1.LoadBalancerIngress{} -} - -// AddClusterLoadBalancerIngresses adds the ladbalancer ingresses for a given cluster to federated service ingress -func (ingress *FederatedServiceIngress) AddClusterLoadBalancerIngresses(cluster string, loadbalancerIngresses []v1.LoadBalancerIngress) { - for i, clusterIngress := range ingress.Items { - if cluster == clusterIngress.Cluster { - ingress.Items[i].Items = append(ingress.Items[i].Items, loadbalancerIngresses...) - return - } - } - clusterNewIngress := fedapi.ClusterServiceIngress{Cluster: cluster, Items: loadbalancerIngresses} - ingress.Items = append(ingress.Items, clusterNewIngress) - sort.Sort(ingress) -} - -// AddEndpoints add one or more endpoints to federated service ingress. -// endpoints are federated cluster's loadbalancer ip/hostname for the service -func (ingress *FederatedServiceIngress) AddEndpoints(cluster string, endpoints []string) *FederatedServiceIngress { - lbIngress := []v1.LoadBalancerIngress{} - for _, endpoint := range endpoints { - lbIngress = append(lbIngress, v1.LoadBalancerIngress{IP: endpoint}) - } - ingress.AddClusterLoadBalancerIngresses(cluster, lbIngress) - return ingress -} - -// RemoveEndpoint removes a single endpoint (ip/hostname) from the federated service ingress -func (ingress *FederatedServiceIngress) RemoveEndpoint(cluster string, endpoint string) *FederatedServiceIngress { - for i, clusterIngress := range ingress.Items { - if cluster == clusterIngress.Cluster { - for j, lbIngress := range clusterIngress.Items { - if lbIngress.IP == endpoint { - ingress.Items[i].Items = append(ingress.Items[i].Items[:j], ingress.Items[i].Items[j+1:]...) - } - } - } - } - return ingress -} - -// ParseFederatedServiceIngress extracts federated service ingresses from a federated service -func ParseFederatedServiceIngress(service *v1.Service) (*FederatedServiceIngress, error) { - ingress := FederatedServiceIngress{} - if service.Annotations == nil { - return &ingress, nil - } - federatedServiceIngressString, found := service.Annotations[FederatedServiceIngressAnnotation] - if !found { - return &ingress, nil - } - if err := json.Unmarshal([]byte(federatedServiceIngressString), &ingress); err != nil { - return &ingress, err - } - return &ingress, nil -} - -// UpdateIngressAnnotation updates the federated service with service ingress annotation -func UpdateIngressAnnotation(service *v1.Service, ingress *FederatedServiceIngress) *v1.Service { - if service.Annotations == nil { - service.Annotations = make(map[string]string) - } - service.Annotations[FederatedServiceIngressAnnotation] = ingress.String() - return service -} diff --git a/federation/pkg/federation-controller/service/servicecontroller.go b/federation/pkg/federation-controller/service/servicecontroller.go deleted file mode 100644 index bda8b83a871..00000000000 --- a/federation/pkg/federation-controller/service/servicecontroller.go +++ /dev/null @@ -1,726 +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 service - -import ( - "fmt" - "reflect" - "sort" - "strings" - "time" - - "github.com/golang/glog" - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - pkgruntime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/apimachinery/pkg/watch" - kubeclientset "k8s.io/client-go/kubernetes" - corelisters "k8s.io/client-go/listers/core/v1" - cache "k8s.io/client-go/tools/cache" - "k8s.io/client-go/tools/record" - "k8s.io/client-go/util/flowcontrol" - "k8s.io/client-go/util/workqueue" - fedapi "k8s.io/kubernetes/federation/apis/federation" - v1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1" - fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - "k8s.io/kubernetes/federation/pkg/federation-controller/service/ingress" - fedutil "k8s.io/kubernetes/federation/pkg/federation-controller/util" - "k8s.io/kubernetes/federation/pkg/federation-controller/util/clusterselector" - "k8s.io/kubernetes/federation/pkg/federation-controller/util/deletionhelper" - "k8s.io/kubernetes/federation/pkg/federation-controller/util/eventsink" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/controller" -) - -const ( - serviceSyncPeriod = 30 * time.Second - - UserAgentName = "federation-service-controller" - - reviewDelay = 10 * time.Second - updateTimeout = 30 * time.Second - allClustersKey = "ALL_CLUSTERS" - clusterAvailableDelay = time.Second * 20 - ControllerName = "services" -) - -var ( - RequiredResources = []schema.GroupVersionResource{v1.SchemeGroupVersion.WithResource("services")} -) - -type ServiceController struct { - federationClient fedclientset.Interface - // A store of services, populated by the serviceController - serviceStore corelisters.ServiceLister - // Watches changes to all services - serviceController cache.Controller - federatedInformer fedutil.FederatedInformer - eventBroadcaster record.EventBroadcaster - eventRecorder record.EventRecorder - // services that need to be synced - queue *workqueue.Type - - // For triggering all services reconciliation. This is used when - // a new cluster becomes available. - clusterDeliverer *fedutil.DelayingDeliverer - - deletionHelper *deletionhelper.DeletionHelper - - reviewDelay time.Duration - clusterAvailableDelay time.Duration - updateTimeout time.Duration - - endpointFederatedInformer fedutil.FederatedInformer - federatedUpdater fedutil.FederatedUpdater - objectDeliverer *fedutil.DelayingDeliverer - flowcontrolBackoff *flowcontrol.Backoff -} - -// New returns a new service controller to keep service objects between -// the federation and member clusters in sync. -func New(federationClient fedclientset.Interface) *ServiceController { - broadcaster := record.NewBroadcaster() - broadcaster.StartRecordingToSink(eventsink.NewFederatedEventSink(federationClient)) - recorder := broadcaster.NewRecorder(api.Scheme, v1.EventSource{Component: UserAgentName}) - - s := &ServiceController{ - federationClient: federationClient, - eventBroadcaster: broadcaster, - eventRecorder: recorder, - queue: workqueue.New(), - reviewDelay: reviewDelay, - clusterAvailableDelay: clusterAvailableDelay, - updateTimeout: updateTimeout, - flowcontrolBackoff: flowcontrol.NewBackOff(5*time.Second, time.Minute), - } - s.objectDeliverer = fedutil.NewDelayingDeliverer() - s.clusterDeliverer = fedutil.NewDelayingDeliverer() - var serviceIndexer cache.Indexer - serviceIndexer, s.serviceController = cache.NewIndexerInformer( - &cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (pkgruntime.Object, error) { - return s.federationClient.Core().Services(metav1.NamespaceAll).List(options) - }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return s.federationClient.Core().Services(metav1.NamespaceAll).Watch(options) - }, - }, - &v1.Service{}, - serviceSyncPeriod, - fedutil.NewTriggerOnAllChanges(func(obj pkgruntime.Object) { - glog.V(5).Infof("Delivering notification from federation: %v", obj) - s.deliverObject(obj, 0, false) - }), - cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, - ) - s.serviceStore = corelisters.NewServiceLister(serviceIndexer) - - clusterLifecycle := fedutil.ClusterLifecycleHandlerFuncs{ - ClusterAvailable: func(cluster *v1beta1.Cluster) { - s.clusterDeliverer.DeliverAfter(allClustersKey, nil, clusterAvailableDelay) - }, - } - fedInformerFactory := func(cluster *v1beta1.Cluster, targetClient kubeclientset.Interface) (cache.Store, cache.Controller) { - return cache.NewInformer( - &cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (pkgruntime.Object, error) { - return targetClient.Core().Services(metav1.NamespaceAll).List(options) - }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return targetClient.Core().Services(metav1.NamespaceAll).Watch(options) - }, - }, - &v1.Service{}, - controller.NoResyncPeriodFunc(), - // Trigger reconciliation whenever something in federated cluster is changed. In most cases it - // would be just confirmation that some service operation succeeded. - fedutil.NewTriggerOnAllChanges( - func(obj pkgruntime.Object) { - glog.V(5).Infof("Delivering service notification from federated cluster %s: %v", cluster.Name, obj) - s.deliverObject(obj, s.reviewDelay, false) - }, - )) - } - - s.federatedInformer = fedutil.NewFederatedInformer(federationClient, fedInformerFactory, &clusterLifecycle) - - s.federatedUpdater = fedutil.NewFederatedUpdater(s.federatedInformer, "service", updateTimeout, s.eventRecorder, - func(client kubeclientset.Interface, obj pkgruntime.Object) error { - svc := obj.(*v1.Service) - _, err := client.Core().Services(svc.Namespace).Create(svc) - return err - }, - func(client kubeclientset.Interface, obj pkgruntime.Object) error { - svc := obj.(*v1.Service) - _, err := client.Core().Services(svc.Namespace).Update(svc) - return err - }, - func(client kubeclientset.Interface, obj pkgruntime.Object) error { - svc := obj.(*v1.Service) - orphanDependents := false - err := client.Core().Services(svc.Namespace).Delete(svc.Name, &metav1.DeleteOptions{OrphanDependents: &orphanDependents}) - return err - }) - - // Federated informers on endpoints in federated clusters. - // This will enable to check if service ingress endpoints in federated clusters are reachable - s.endpointFederatedInformer = fedutil.NewFederatedInformer( - federationClient, - func(cluster *v1beta1.Cluster, targetClient kubeclientset.Interface) ( - cache.Store, cache.Controller) { - return cache.NewInformer( - &cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (pkgruntime.Object, error) { - return targetClient.Core().Endpoints(metav1.NamespaceAll).List(options) - }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return targetClient.Core().Endpoints(metav1.NamespaceAll).Watch(options) - }, - }, - &v1.Endpoints{}, - controller.NoResyncPeriodFunc(), - fedutil.NewTriggerOnMetaAndFieldChanges( - "Subsets", - func(obj pkgruntime.Object) { - glog.V(5).Infof("Delivering endpoint notification from federated cluster %s :%v", cluster.Name, obj) - s.deliverObject(obj, s.reviewDelay, false) - }, - )) - }, - &fedutil.ClusterLifecycleHandlerFuncs{}, - ) - - s.deletionHelper = deletionhelper.NewDeletionHelper( - s.updateService, - // objNameFunc - func(obj pkgruntime.Object) string { - service := obj.(*v1.Service) - return fmt.Sprintf("%s/%s", service.Namespace, service.Name) - }, - s.federatedInformer, - s.federatedUpdater, - ) - - return s -} - -// Sends the given updated object to apiserver. -// Assumes that the given object is a service. -func (s *ServiceController) updateService(obj pkgruntime.Object) (pkgruntime.Object, error) { - service := obj.(*v1.Service) - return s.federationClient.Core().Services(service.Namespace).Update(service) -} - -// Run starts informers, delay deliverers and workers. Workers continuously watch for events which could -// be from federation or federated clusters and tries to reconcile the service objects from federation to -// federated clusters. -func (s *ServiceController) Run(workers int, stopCh <-chan struct{}) { - glog.Infof("Starting federation service controller") - - defer runtime.HandleCrash() - defer s.queue.ShutDown() - - s.federatedInformer.Start() - defer s.federatedInformer.Stop() - - s.endpointFederatedInformer.Start() - defer s.endpointFederatedInformer.Stop() - - s.objectDeliverer.StartWithHandler(func(item *fedutil.DelayingDelivererItem) { - s.queue.Add(item.Value.(string)) - }) - defer s.objectDeliverer.Stop() - - s.clusterDeliverer.StartWithHandler(func(_ *fedutil.DelayingDelivererItem) { - s.deliverServicesOnClusterChange() - }) - defer s.clusterDeliverer.Stop() - - fedutil.StartBackoffGC(s.flowcontrolBackoff, stopCh) - go s.serviceController.Run(stopCh) - - for i := 0; i < workers; i++ { - go wait.Until(s.fedServiceWorker, time.Second, stopCh) - } - - <-stopCh - glog.Infof("Shutting down federation service controller") -} - -type reconciliationStatus string - -const ( - statusAllOk = reconciliationStatus("ALL_OK") - statusRecoverableError = reconciliationStatus("RECOVERABLE_ERROR") - statusNonRecoverableError = reconciliationStatus("NON_RECOVERABLE_ERROR") - statusNotSynced = reconciliationStatus("NOSYNC") -) - -func (s *ServiceController) workerFunction() bool { - key, quit := s.queue.Get() - if quit { - return true - } - defer s.queue.Done(key) - - service := key.(string) - status := s.reconcileService(service) - switch status { - case statusAllOk: - // do nothing, reconcile is successful. - case statusNotSynced: - glog.V(5).Infof("Delivering notification for %q after clusterAvailableDelay", service) - s.deliverService(service, s.clusterAvailableDelay, false) - case statusRecoverableError: - s.deliverService(service, 0, true) - case statusNonRecoverableError: - // do nothing, error is already logged. - } - return false -} - -// fedServiceWorker runs a worker thread that just dequeues items, processes them, and marks them done. -func (s *ServiceController) fedServiceWorker() { - for { - if quit := s.workerFunction(); quit { - glog.Infof("service controller worker queue shutting down") - return - } - } -} - -// delete deletes the given service or returns error if the deletion was not complete. -func (s *ServiceController) delete(service *v1.Service) error { - glog.V(3).Infof("Handling deletion of service: %v", *service) - _, err := s.deletionHelper.HandleObjectInUnderlyingClusters(service) - if err != nil { - return err - } - - err = s.federationClient.Core().Services(service.Namespace).Delete(service.Name, nil) - if err != nil { - // Its all good if the error is not found error. That means it is deleted already and we do not have to do anything. - // This is expected when we are processing an update as a result of service finalizer deletion. - // The process that deleted the last finalizer is also going to delete the service and we do not have to do anything. - if !errors.IsNotFound(err) { - return fmt.Errorf("failed to delete service: %v", err) - } - } - return nil -} - -func (s *ServiceController) deliverServicesOnClusterChange() { - if !s.isSynced() { - s.clusterDeliverer.DeliverAfter(allClustersKey, nil, s.clusterAvailableDelay) - } - glog.V(5).Infof("Delivering all service as cluster status changed") - serviceList, err := s.serviceStore.List(labels.Everything()) - if err != nil { - runtime.HandleError(fmt.Errorf("error listing federated services: %v", err)) - s.clusterDeliverer.DeliverAfter(allClustersKey, nil, 0) - } - for _, service := range serviceList { - s.deliverObject(service, 0, false) - } -} - -func (s *ServiceController) deliverObject(object interface{}, delay time.Duration, failed bool) { - switch value := object.(type) { - case *v1.Service: - s.deliverService(types.NamespacedName{Namespace: value.Namespace, Name: value.Name}.String(), delay, failed) - case *v1.Endpoints: - s.deliverService(types.NamespacedName{Namespace: value.Namespace, Name: value.Name}.String(), delay, failed) - default: - glog.Warningf("Unknown object received: %v", object) - } -} - -// Adds backoff to delay if this delivery is related to some failure. Resets backoff if there was no failure. -func (s *ServiceController) deliverService(key string, delay time.Duration, failed bool) { - if failed { - s.flowcontrolBackoff.Next(key, time.Now()) - delay = delay + s.flowcontrolBackoff.Get(key) - } else { - s.flowcontrolBackoff.Reset(key) - } - s.objectDeliverer.DeliverAfter(key, key, delay) -} - -// Check whether all data stores are in sync. False is returned if any of the informer/stores is not yet synced with -// the corresponding api server. -func (s *ServiceController) isSynced() bool { - if !s.federatedInformer.ClustersSynced() { - glog.V(2).Infof("Cluster list not synced") - return false - } - serviceClusters, err := s.federatedInformer.GetReadyClusters() - if err != nil { - runtime.HandleError(fmt.Errorf("Failed to get ready clusters: %v", err)) - return false - } - if !s.federatedInformer.GetTargetStore().ClustersSynced(serviceClusters) { - return false - } - - if !s.endpointFederatedInformer.ClustersSynced() { - glog.V(2).Infof("Cluster list not synced") - return false - } - endpointClusters, err := s.endpointFederatedInformer.GetReadyClusters() - if err != nil { - runtime.HandleError(fmt.Errorf("Failed to get ready clusters: %v", err)) - return false - } - if !s.endpointFederatedInformer.GetTargetStore().ClustersSynced(endpointClusters) { - return false - } - - return true -} - -// reconcileService triggers reconciliation of a federated service with corresponding services in federated clusters. -// This function is called on service Addition/Deletion/Update either in federated cluster or in federation. -func (s *ServiceController) reconcileService(key string) reconciliationStatus { - if !s.isSynced() { - glog.V(4).Infof("Data store not synced, delaying reconcilation: %v", key) - return statusNotSynced - } - - namespace, name, err := cache.SplitMetaNamespaceKey(key) - if err != nil { - runtime.HandleError(fmt.Errorf("Invalid key %q received, unable to split key to namespace and name, err: %v", key, err)) - return statusNonRecoverableError - } - - service, err := s.serviceStore.Services(namespace).Get(name) - if errors.IsNotFound(err) { - // Not a federated service, ignoring. - return statusAllOk - } else if err != nil { - runtime.HandleError(fmt.Errorf("Failed to retrieve federated service %q from store: %v", key, err)) - return statusRecoverableError - } - - glog.V(3).Infof("Reconciling federated service: %s", key) - - // Create a copy before modifying the service to prevent race condition with other readers of service from store - fedService := service.DeepCopy() - - // Handle deletion of federated service - if fedService.DeletionTimestamp != nil { - if err := s.delete(fedService); err != nil { - runtime.HandleError(fmt.Errorf("Failed to delete %s: %v", key, err)) - s.eventRecorder.Eventf(fedService, api.EventTypeWarning, "DeleteFailed", "Deleting service failed: %v", err) - return statusRecoverableError - } - glog.V(3).Infof("Deleting federated service succeeded: %s", key) - s.eventRecorder.Eventf(fedService, api.EventTypeNormal, "DeleteSucceed", "Deleting service succeeded") - return statusAllOk - } - - // Add the required finalizers before creating a service in underlying clusters. This ensures that the - // dependent services in underlying clusters are deleted when the federated service is deleted. - updatedServiceObj, err := s.deletionHelper.EnsureFinalizers(fedService) - if err != nil { - runtime.HandleError(fmt.Errorf("Failed to ensure setting finalizer for service %s: %v", key, err)) - return statusRecoverableError - } - fedService = updatedServiceObj.(*v1.Service) - - // Synchronize the federated service in all underlying ready clusters. - clusters, err := s.federatedInformer.GetReadyClusters() - if err != nil { - runtime.HandleError(fmt.Errorf("Failed to get ready cluster list: %v", err)) - return statusRecoverableError - } - - newLBStatus := newLoadbalancerStatus() - newServiceIngress := ingress.NewFederatedServiceIngress() - operations := make([]fedutil.FederatedOperation, 0) - for _, cluster := range clusters { - // Aggregate all operations to perform on all federated clusters - operation, err := getOperationsToPerformOnCluster(s.federatedInformer, cluster, fedService, clusterselector.SendToCluster) - if err != nil { - return statusRecoverableError - } - if operation != nil { - operations = append(operations, *operation) - } - - // Aggregate LoadBalancerStatus from all services in federated clusters to update status in federated service - lbStatus, err := s.getServiceStatusInCluster(cluster, key) - if err != nil { - return statusRecoverableError - } - if len(lbStatus.Ingress) > 0 { - newLBStatus.Ingress = append(newLBStatus.Ingress, lbStatus.Ingress...) - - // Add/Update federated service ingress only if there are reachable endpoints backing the lb service - endpoints, err := s.getServiceEndpointsInCluster(cluster, key) - if err != nil { - return statusRecoverableError - } - // if there are no endpoints created for the service then the loadbalancer ingress - // is not reachable, so do not consider such loadbalancer ingresses for federated - // service ingresses - if len(endpoints) > 0 { - clusterIngress := fedapi.ClusterServiceIngress{ - Cluster: cluster.Name, - Items: lbStatus.Ingress, - } - newServiceIngress.Items = append(newServiceIngress.Items, clusterIngress) - } - } - } - - if len(operations) != 0 { - err = s.federatedUpdater.Update(operations) - if err != nil { - if !errors.IsAlreadyExists(err) { - runtime.HandleError(fmt.Errorf("Failed to execute updates for %s: %v", key, err)) - return statusRecoverableError - } - } - } - - // Update the federated service if there are any updates in clustered service (status/endpoints) - err = s.updateFederatedService(fedService, newLBStatus, newServiceIngress) - if err != nil { - return statusRecoverableError - } - - glog.V(5).Infof("Everything is in order in federated clusters for service %s", key) - return statusAllOk -} - -type clusterSelectorFunc func(map[string]string, map[string]string) (bool, error) - -// getOperationsToPerformOnCluster returns the operations to be performed so that clustered service is in sync with federated service -func getOperationsToPerformOnCluster(informer fedutil.FederatedInformer, cluster *v1beta1.Cluster, fedService *v1.Service, selector clusterSelectorFunc) (*fedutil.FederatedOperation, error) { - var operation *fedutil.FederatedOperation - var operationType fedutil.FederatedOperationType = "" - - key := types.NamespacedName{Namespace: fedService.Namespace, Name: fedService.Name}.String() - clusterServiceObj, found, err := informer.GetTargetStore().GetByKey(cluster.Name, key) - if err != nil { - runtime.HandleError(fmt.Errorf("Failed to get %s service from %s: %v", key, cluster.Name, err)) - return nil, err - } - - send, err := selector(cluster.Labels, fedService.ObjectMeta.Annotations) - if err != nil { - glog.Errorf("Error processing ClusterSelector cluster: %s for service map: %s error: %s", cluster.Name, key, err.Error()) - return nil, err - } else if !send { - glog.V(5).Infof("Skipping cluster: %s for service: %s reason: cluster selectors do not match: %-v %-v", cluster.Name, key, cluster.ObjectMeta.Labels, fedService.ObjectMeta.Annotations[v1beta1.FederationClusterSelectorAnnotation]) - } - - desiredService := &v1.Service{ - ObjectMeta: fedutil.DeepCopyRelevantObjectMeta(fedService.ObjectMeta), - Spec: *fedService.Spec.DeepCopy(), - } - switch { - case found && send: - clusterService, ok := clusterServiceObj.(*v1.Service) - if !ok { - runtime.HandleError(fmt.Errorf("Unexpected error for %q: %v", key, err)) - return nil, err - } - - // ClusterIP and NodePort are allocated to Service by cluster, so retain the same if any while updating - desiredService.Spec.ClusterIP = clusterService.Spec.ClusterIP - for _, cPort := range clusterService.Spec.Ports { - for i, fPort := range clusterService.Spec.Ports { - if fPort.Name == cPort.Name && fPort.Protocol == cPort.Protocol && fPort.Port == cPort.Port { - desiredService.Spec.Ports[i].NodePort = cPort.NodePort - } - } - } - - // Update existing service, if needed. - if !Equivalent(desiredService, clusterService) { - operationType = fedutil.OperationTypeUpdate - - glog.V(4).Infof("Service in underlying cluster %s does not match, Desired: %+v, Existing: %+v", cluster.Name, desiredService, clusterService) - - // ResourceVersion of cluster service can be different from federated service, - // so do not update ResourceVersion while updating cluster service - desiredService.ResourceVersion = clusterService.ResourceVersion - } else { - glog.V(5).Infof("Service in underlying cluster %s is up to date: %+v", cluster.Name, desiredService) - } - case found && !send: - operationType = fedutil.OperationTypeDelete - case !found && send: - operationType = fedutil.OperationTypeAdd - desiredService.ResourceVersion = "" - - glog.V(4).Infof("Creating service in underlying cluster %s: %+v", cluster.Name, desiredService) - } - - if len(operationType) > 0 { - operation = &fedutil.FederatedOperation{ - Type: operationType, - Obj: desiredService, - ClusterName: cluster.Name, - Key: key, - } - } - return operation, nil -} - -// getServiceStatusInCluster returns service status in federated cluster -func (s *ServiceController) getServiceStatusInCluster(cluster *v1beta1.Cluster, key string) (*v1.LoadBalancerStatus, error) { - lbStatus := &v1.LoadBalancerStatus{} - - clusterServiceObj, serviceFound, err := s.federatedInformer.GetTargetStore().GetByKey(cluster.Name, key) - if err != nil { - runtime.HandleError(fmt.Errorf("Failed to get %s service from %s: %v", key, cluster.Name, err)) - return lbStatus, err - } - if serviceFound { - clusterService, ok := clusterServiceObj.(*v1.Service) - if !ok { - err = fmt.Errorf("Unknown object received: %v", clusterServiceObj) - runtime.HandleError(err) - return lbStatus, err - } - lbStatus = &clusterService.Status.LoadBalancer - newLbStatus := &loadbalancerStatus{*lbStatus} - sort.Sort(newLbStatus) - } - return lbStatus, nil -} - -// getServiceEndpointsInCluster returns ready endpoints corresponding to service in federated cluster -func (s *ServiceController) getServiceEndpointsInCluster(cluster *v1beta1.Cluster, key string) ([]v1.EndpointAddress, error) { - addresses := []v1.EndpointAddress{} - - clusterEndpointsObj, endpointsFound, err := s.endpointFederatedInformer.GetTargetStore().GetByKey(cluster.Name, key) - if err != nil { - runtime.HandleError(fmt.Errorf("Failed to get %s endpoint from %s: %v", key, cluster.Name, err)) - return addresses, err - } - if endpointsFound { - clusterEndpoints, ok := clusterEndpointsObj.(*v1.Endpoints) - if !ok { - glog.Warningf("Unknown object received: %v", clusterEndpointsObj) - return addresses, fmt.Errorf("Unknown object received: %v", clusterEndpointsObj) - } - for _, subset := range clusterEndpoints.Subsets { - if len(subset.Addresses) > 0 { - addresses = append(addresses, subset.Addresses...) - } - } - } - return addresses, nil -} - -// updateFederatedService updates the federated service with aggregated lbStatus and serviceIngresses -// and also updates the dns records as needed -func (s *ServiceController) updateFederatedService(fedService *v1.Service, newLBStatus *loadbalancerStatus, newServiceIngress *ingress.FederatedServiceIngress) error { - key := types.NamespacedName{Namespace: fedService.Namespace, Name: fedService.Name}.String() - needUpdate := false - - // Sort the endpoints so that we can compare - sort.Sort(newLBStatus) - if !reflect.DeepEqual(fedService.Status.LoadBalancer.Ingress, newLBStatus.Ingress) { - fedService.Status.LoadBalancer.Ingress = newLBStatus.Ingress - glog.V(3).Infof("Federated service loadbalancer status updated for %s: %v", key, newLBStatus.Ingress) - needUpdate = true - } - - existingServiceIngress, err := ingress.ParseFederatedServiceIngress(fedService) - if err != nil { - runtime.HandleError(fmt.Errorf("Failed to parse endpoint annotations for service %s: %v", key, err)) - return err - } - - // TODO: We should have a reliable cluster health check(should consider quorum) to detect cluster is not - // reachable and remove dns records for them. Until a reliable cluster health check is available, below code is - // a workaround to not remove the existing dns records which were created before the cluster went offline. - unreadyClusters, err := s.federatedInformer.GetUnreadyClusters() - if err != nil { - runtime.HandleError(fmt.Errorf("Failed to get unready cluster list: %v", err)) - return err - } - for _, cluster := range unreadyClusters { - lbIngress := existingServiceIngress.GetClusterLoadBalancerIngresses(cluster.Name) - newServiceIngress.AddClusterLoadBalancerIngresses(cluster.Name, lbIngress) - glog.V(5).Infof("Cluster %s is Offline, Preserving previously available status for Service %s", cluster.Name, key) - } - - // Update federated service status and/or ingress annotations if changed - sort.Sort(newServiceIngress) - if !reflect.DeepEqual(existingServiceIngress.Items, newServiceIngress.Items) { - fedService = ingress.UpdateIngressAnnotation(fedService, newServiceIngress) - glog.V(3).Infof("Federated service loadbalancer ingress updated for %s: existing: %#v, desired: %#v", key, existingServiceIngress, newServiceIngress) - needUpdate = true - } - - if needUpdate { - var err error - fedService, err = s.federationClient.Core().Services(fedService.Namespace).UpdateStatus(fedService) - if err != nil { - runtime.HandleError(fmt.Errorf("Error updating the federation service object %s: %v", key, err)) - return err - } - } - - return nil -} - -// Equivalent Checks if cluster-independent, user provided data in two given services are equal. If in the future the -// services structure is expanded then any field that is not populated by the api server should be included here. -func Equivalent(s1, s2 *v1.Service) bool { - // TODO: should also check for all annotations except FederationServiceIngressAnnotation - return s1.Name == s2.Name && s1.Namespace == s2.Namespace && - (reflect.DeepEqual(s1.Labels, s2.Labels) || (len(s1.Labels) == 0 && len(s2.Labels) == 0)) && - reflect.DeepEqual(s1.Spec, s2.Spec) -} - -type loadbalancerStatus struct { - v1.LoadBalancerStatus -} - -func newLoadbalancerStatus() *loadbalancerStatus { - return &loadbalancerStatus{} -} - -func (lbs loadbalancerStatus) Len() int { - return len(lbs.Ingress) -} - -func (lbs loadbalancerStatus) Less(i, j int) bool { - ipComparison := strings.Compare(lbs.Ingress[i].IP, lbs.Ingress[j].IP) - hostnameComparison := strings.Compare(lbs.Ingress[i].Hostname, lbs.Ingress[j].Hostname) - if ipComparison < 0 || (ipComparison == 0 && hostnameComparison < 0) { - return true - } - return false -} - -func (lbs loadbalancerStatus) Swap(i, j int) { - lbs.Ingress[i].IP, lbs.Ingress[j].IP = lbs.Ingress[j].IP, lbs.Ingress[i].IP - lbs.Ingress[i].Hostname, lbs.Ingress[j].Hostname = lbs.Ingress[j].Hostname, lbs.Ingress[i].Hostname -} diff --git a/federation/pkg/federation-controller/service/servicecontroller_test.go b/federation/pkg/federation-controller/service/servicecontroller_test.go deleted file mode 100644 index a20a039469a..00000000000 --- a/federation/pkg/federation-controller/service/servicecontroller_test.go +++ /dev/null @@ -1,383 +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 service - -import ( - "fmt" - "reflect" - "strings" - "testing" - "time" - - "github.com/golang/glog" - "github.com/stretchr/testify/require" - "k8s.io/api/core/v1" - "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" - kubeclientset "k8s.io/client-go/kubernetes" - fakekubeclientset "k8s.io/client-go/kubernetes/fake" - corelisters "k8s.io/client-go/listers/core/v1" - "k8s.io/client-go/tools/cache" - "k8s.io/kubernetes/federation/apis/federation/v1beta1" - fakefedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/fake" - "k8s.io/kubernetes/federation/pkg/federation-controller/service/ingress" - fedutil "k8s.io/kubernetes/federation/pkg/federation-controller/util" - "k8s.io/kubernetes/federation/pkg/federation-controller/util/deletionhelper" - . "k8s.io/kubernetes/federation/pkg/federation-controller/util/test" -) - -const ( - retryInterval = 100 * time.Millisecond - - clusters string = "clusters" - services string = "services" - endpoints string = "endpoints" - - lbIngress1 = "10.20.30.40" - lbIngress2 = "10.20.30.50" - serviceEndpoint1 = "192.168.0.1" - serviceEndpoint2 = "192.168.1.1" -) - -var awfulError error = errors.NewGone("Something bad happened") - -func TestServiceController(t *testing.T) { - glog.Infof("Creating fake infrastructure") - fedClient := &fakefedclientset.Clientset{} - cluster1 := NewCluster("cluster1", v1.ConditionTrue) - cluster2 := NewCluster("cluster2", v1.ConditionTrue) - - RegisterFakeClusterGet(&fedClient.Fake, &v1beta1.ClusterList{Items: []v1beta1.Cluster{*cluster1, *cluster2}}) - RegisterFakeList(clusters, &fedClient.Fake, &v1beta1.ClusterList{Items: []v1beta1.Cluster{*cluster1, *cluster2}}) - fedclusterWatch := RegisterFakeWatch(clusters, &fedClient.Fake) - RegisterFakeList(services, &fedClient.Fake, &v1.ServiceList{Items: []v1.Service{}}) - fedServiceWatch := RegisterFakeWatch(services, &fedClient.Fake) - RegisterFakeOnCreate(clusters, &fedClient.Fake, fedclusterWatch) - RegisterFakeOnUpdate(clusters, &fedClient.Fake, fedclusterWatch) - RegisterFakeOnCreate(services, &fedClient.Fake, fedServiceWatch) - RegisterFakeOnUpdate(services, &fedClient.Fake, fedServiceWatch) - RegisterFakeOnDelete(services, &fedClient.Fake, fedServiceWatch, serviceObjectGetter) - - cluster1Client := &fakekubeclientset.Clientset{} - RegisterFakeList(services, &cluster1Client.Fake, &v1.ServiceList{Items: []v1.Service{}}) - c1ServiceWatch := RegisterFakeWatch(services, &cluster1Client.Fake) - RegisterFakeList(endpoints, &cluster1Client.Fake, &v1.EndpointsList{Items: []v1.Endpoints{}}) - c1EndpointWatch := RegisterFakeWatch(endpoints, &cluster1Client.Fake) - RegisterFakeOnCreate(services, &cluster1Client.Fake, c1ServiceWatch) - RegisterFakeOnUpdate(services, &cluster1Client.Fake, c1ServiceWatch) - RegisterFakeOnDelete(services, &cluster1Client.Fake, c1ServiceWatch, serviceObjectGetter) - RegisterFakeOnCreate(endpoints, &cluster1Client.Fake, c1EndpointWatch) - RegisterFakeOnUpdate(endpoints, &cluster1Client.Fake, c1EndpointWatch) - - cluster2Client := &fakekubeclientset.Clientset{} - RegisterFakeList(services, &cluster2Client.Fake, &v1.ServiceList{Items: []v1.Service{}}) - c2ServiceWatch := RegisterFakeWatch(services, &cluster2Client.Fake) - RegisterFakeList(endpoints, &cluster2Client.Fake, &v1.EndpointsList{Items: []v1.Endpoints{}}) - c2EndpointWatch := RegisterFakeWatch(endpoints, &cluster2Client.Fake) - RegisterFakeOnCreate(services, &cluster2Client.Fake, c2ServiceWatch) - RegisterFakeOnUpdate(services, &cluster2Client.Fake, c2ServiceWatch) - RegisterFakeOnDelete(services, &cluster2Client.Fake, c2ServiceWatch, serviceObjectGetter) - RegisterFakeOnCreate(endpoints, &cluster2Client.Fake, c2EndpointWatch) - RegisterFakeOnUpdate(endpoints, &cluster2Client.Fake, c2EndpointWatch) - - fedInformerClientFactory := func(cluster *v1beta1.Cluster) (kubeclientset.Interface, error) { - switch cluster.Name { - case cluster1.Name: - return cluster1Client, nil - case cluster2.Name: - return cluster2Client, nil - default: - return nil, fmt.Errorf("Unknown cluster: %v", cluster.Name) - } - } - - sc := New(fedClient) - ToFederatedInformerForTestOnly(sc.federatedInformer).SetClientFactory(fedInformerClientFactory) - ToFederatedInformerForTestOnly(sc.endpointFederatedInformer).SetClientFactory(fedInformerClientFactory) - sc.clusterAvailableDelay = 100 * time.Millisecond - sc.reviewDelay = 50 * time.Millisecond - sc.updateTimeout = 5 * time.Second - - stop := make(chan struct{}) - glog.Infof("Running Service Controller") - go sc.Run(5, stop) - - glog.Infof("Adding cluster 1") - fedclusterWatch.Add(cluster1) - - service := NewService("test-service-1", 80) - - glog.Infof("Adding federated service") - fedServiceWatch.Add(service) - key := types.NamespacedName{Namespace: service.Namespace, Name: service.Name}.String() - - glog.Infof("Test service was correctly created in cluster 1") - require.NoError(t, WaitForClusterService(t, sc.federatedInformer.GetTargetStore(), cluster1.Name, - key, service, wait.ForeverTestTimeout)) - - glog.Infof("Adding cluster 2") - fedclusterWatch.Add(cluster2) - - glog.Infof("Test service was correctly created in cluster 2") - require.NoError(t, WaitForClusterService(t, sc.federatedInformer.GetTargetStore(), cluster2.Name, - key, service, wait.ForeverTestTimeout)) - - glog.Infof("Test federation service is updated when cluster1 service status is updated") - service.Status = v1.ServiceStatus{ - LoadBalancer: v1.LoadBalancerStatus{ - Ingress: []v1.LoadBalancerIngress{ - {IP: lbIngress1}, - }}} - - desiredStatus := service.Status - desiredService := &v1.Service{Status: desiredStatus} - - c1ServiceWatch.Modify(service) - require.NoError(t, WaitForClusterService(t, sc.federatedInformer.GetTargetStore(), cluster1.Name, - key, service, wait.ForeverTestTimeout)) - require.NoError(t, WaitForFederatedServiceUpdate(t, sc.serviceStore, - key, desiredService, serviceStatusCompare, wait.ForeverTestTimeout)) - - glog.Infof("Test federation service is updated when cluster1 endpoint for the service is created") - desiredIngressAnnotation := ingress.NewFederatedServiceIngress(). - AddEndpoints("cluster1", []string{lbIngress1}). - String() - desiredService = &v1.Service{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{ingress.FederatedServiceIngressAnnotation: desiredIngressAnnotation}}} - c1EndpointWatch.Add(NewEndpoint("test-service-1", serviceEndpoint1)) - require.NoError(t, WaitForFederatedServiceUpdate(t, sc.serviceStore, - key, desiredService, serviceIngressCompare, wait.ForeverTestTimeout)) - - glog.Infof("Test federation service is updated when cluster2 service status is updated") - service.Status = v1.ServiceStatus{ - LoadBalancer: v1.LoadBalancerStatus{ - Ingress: []v1.LoadBalancerIngress{ - {IP: lbIngress2}, - }}} - desiredStatus.LoadBalancer.Ingress = append(desiredStatus.LoadBalancer.Ingress, v1.LoadBalancerIngress{IP: lbIngress2}) - desiredService = &v1.Service{Status: desiredStatus} - - c2ServiceWatch.Modify(service) - require.NoError(t, WaitForClusterService(t, sc.federatedInformer.GetTargetStore(), cluster2.Name, - key, service, wait.ForeverTestTimeout)) - require.NoError(t, WaitForFederatedServiceUpdate(t, sc.serviceStore, - key, desiredService, serviceStatusCompare, wait.ForeverTestTimeout)) - - glog.Infof("Test federation service is updated when cluster2 endpoint for the service is created") - desiredIngressAnnotation = ingress.NewFederatedServiceIngress(). - AddEndpoints("cluster1", []string{lbIngress1}). - AddEndpoints("cluster2", []string{lbIngress2}). - String() - desiredService = &v1.Service{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{ingress.FederatedServiceIngressAnnotation: desiredIngressAnnotation}}} - c2EndpointWatch.Add(NewEndpoint("test-service-1", serviceEndpoint2)) - require.NoError(t, WaitForFederatedServiceUpdate(t, sc.serviceStore, - key, desiredService, serviceIngressCompare, wait.ForeverTestTimeout)) - - glog.Infof("Test federation service is updated when cluster1 endpoint for the service is deleted") - desiredIngressAnnotation = ingress.NewFederatedServiceIngress(). - AddEndpoints("cluster2", []string{lbIngress2}). - String() - desiredService = &v1.Service{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{ingress.FederatedServiceIngressAnnotation: desiredIngressAnnotation}}} - c1EndpointWatch.Delete(NewEndpoint("test-service-1", serviceEndpoint1)) - require.NoError(t, WaitForFederatedServiceUpdate(t, sc.serviceStore, - key, desiredService, serviceIngressCompare, wait.ForeverTestTimeout)) - - glog.Infof("Test modifying federated service by changing the port") - service.Spec.Ports[0].Port = 9090 - fedServiceWatch.Modify(service) - require.NoError(t, WaitForClusterService(t, sc.federatedInformer.GetTargetStore(), cluster1.Name, - key, service, wait.ForeverTestTimeout)) - - glog.Infof("Test cluster service is recreated when deleted") - c1Service := NewService("test-service-1", 80) - c1Service.DeletionTimestamp = &metav1.Time{Time: time.Now()} - c1ServiceWatch.Delete(c1Service) - require.NoError(t, WaitForClusterService(t, sc.federatedInformer.GetTargetStore(), cluster1.Name, - key, service, wait.ForeverTestTimeout)) - - glog.Infof("Test cluster services are deleted when federated service is deleted") - service.ObjectMeta.Finalizers = append(service.ObjectMeta.Finalizers, deletionhelper.FinalizerDeleteFromUnderlyingClusters) - service.DeletionTimestamp = &metav1.Time{Time: time.Now()} - fedServiceWatch.Modify(service) - require.NoError(t, WaitForClusterServiceDelete(t, sc.federatedInformer.GetTargetStore(), cluster1.Name, - key, wait.ForeverTestTimeout)) - require.NoError(t, WaitForClusterServiceDelete(t, sc.federatedInformer.GetTargetStore(), cluster2.Name, - key, wait.ForeverTestTimeout)) - - close(stop) -} - -func TestGetOperationsToPerformOnCluster(t *testing.T) { - obj := NewService("test-service-1", 80) - cluster1 := NewCluster("cluster1", v1.ConditionTrue) - fedClient := &fakefedclientset.Clientset{} - sc := New(fedClient) - - testCases := map[string]struct { - expectedSendErr bool - sendToCluster bool - operationType fedutil.FederatedOperationType - }{ - "sendToCluster error returned": { - expectedSendErr: true, - }, - "Missing object and not matching ClusterSelector should result in no operations": { - sendToCluster: false, - }, - "Missing object and matching ClusterSelector should result in add operation": { - operationType: fedutil.OperationTypeAdd, - sendToCluster: true, - }, - // Update and Delete scenarios are tested in TestServiceController - } - for testName, testCase := range testCases { - t.Run(testName, func(t *testing.T) { - - operations, err := getOperationsToPerformOnCluster(sc.federatedInformer, cluster1, obj, func(map[string]string, map[string]string) (bool, error) { - if testCase.expectedSendErr { - return false, awfulError - } - return testCase.sendToCluster, nil - }) - if testCase.expectedSendErr { - require.Error(t, err, "An error was expected") - } else { - require.NoError(t, err, "An error was not expected") - } - if len(testCase.operationType) == 0 { - require.Nil(t, operations, "An operation was not expected") - } else { - require.NotNil(t, operations, "A single operation was expected") - require.Equal(t, testCase.operationType, operations.Type, "Unexpected operation returned") - } - }) - } -} - -// serviceObjectGetter gives dummy service objects to use with RegisterFakeOnDelete -// This is just so that federated informer can be tested for delete scenarios. -func serviceObjectGetter(name, namespace string) runtime.Object { - service := new(v1.Service) - service.Namespace = namespace - service.Name = name - return service -} - -func NewService(name string, port int32) *v1.Service { - return &v1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: v1.NamespaceDefault, - SelfLink: "/api/v1/namespaces/default/services/" + name, - Labels: map[string]string{"app": name}, - }, - Spec: v1.ServiceSpec{ - Ports: []v1.ServicePort{{Port: port}}, - Type: v1.ServiceTypeLoadBalancer, - }, - } -} - -func NewEndpoint(name, ip string) *v1.Endpoints { - return &v1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: v1.NamespaceDefault, - SelfLink: "/api/v1/namespaces/default/endpoints/" + name, - Labels: map[string]string{"app": name}, - }, - Subsets: []v1.EndpointSubset{{ - Addresses: []v1.EndpointAddress{{ - IP: ip, - }}}, - }, - } -} - -// WaitForClusterService waits for the cluster service to be created matching the desiredService. -func WaitForClusterService(t *testing.T, store fedutil.FederatedReadOnlyStore, clusterName, key string, desiredService *v1.Service, timeout time.Duration) error { - err := wait.PollImmediate(retryInterval, timeout, func() (bool, error) { - obj, found, err := store.GetByKey(clusterName, key) - if !found || err != nil { - return false, err - } - service := obj.(*v1.Service) - if !Equivalent(service, desiredService) { - glog.V(5).Infof("Waiting for clustered service, Desired: %v, Current: %v", desiredService, service) - return false, nil - } - glog.V(5).Infof("Clustered service is up to date: %v", service) - return true, nil - }) - return err -} - -// WaitForClusterServiceDelete waits for the cluster service to be deleted. -func WaitForClusterServiceDelete(t *testing.T, store fedutil.FederatedReadOnlyStore, clusterName, key string, timeout time.Duration) error { - err := wait.PollImmediate(retryInterval, timeout, func() (bool, error) { - _, found, _ := store.GetByKey(clusterName, key) - if !found { - return true, nil - } - return false, nil - }) - return err -} - -type serviceCompare func(current, desired *v1.Service) (match bool) - -func serviceStatusCompare(current, desired *v1.Service) bool { - if !reflect.DeepEqual(current.Status.LoadBalancer, desired.Status.LoadBalancer) { - glog.V(5).Infof("Waiting for loadbalancer status, Current: %v, Desired: %v", current.Status.LoadBalancer, desired.Status.LoadBalancer) - return false - } - glog.V(5).Infof("Loadbalancer status match: %v", current.Status.LoadBalancer) - return true -} - -func serviceIngressCompare(current, desired *v1.Service) bool { - if strings.Compare(current.Annotations[ingress.FederatedServiceIngressAnnotation], desired.Annotations[ingress.FederatedServiceIngressAnnotation]) != 0 { - glog.V(5).Infof("Waiting for loadbalancer ingress, Current: %v, Desired: %v", current.Annotations[ingress.FederatedServiceIngressAnnotation], desired.Annotations[ingress.FederatedServiceIngressAnnotation]) - return false - } - glog.V(5).Infof("Loadbalancer ingress match: %v", current.Annotations[ingress.FederatedServiceIngressAnnotation]) - return true -} - -// WaitForFederatedServiceUpdate waits for federated service updates to match the desiredService. -func WaitForFederatedServiceUpdate(t *testing.T, store corelisters.ServiceLister, key string, desiredService *v1.Service, match serviceCompare, timeout time.Duration) error { - err := wait.PollImmediate(retryInterval, timeout, func() (bool, error) { - namespace, name, err := cache.SplitMetaNamespaceKey(key) - if err != nil { - return false, err - } - service, err := store.Services(namespace).Get(name) - switch { - case errors.IsNotFound(err): - return false, nil - case err != nil: - return false, err - case !match(service, desiredService): - return false, nil - default: - return true, nil - } - }) - return err -} diff --git a/federation/pkg/federation-controller/sync/BUILD b/federation/pkg/federation-controller/sync/BUILD deleted file mode 100644 index fc722ae4393..00000000000 --- a/federation/pkg/federation-controller/sync/BUILD +++ /dev/null @@ -1,68 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = ["controller_test.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/sync", - library = ":go_default_library", - deps = [ - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/pkg/federatedtypes:go_default_library", - "//federation/pkg/federation-controller/util:go_default_library", - "//federation/pkg/federation-controller/util/test:go_default_library", - "//vendor/github.com/stretchr/testify/require:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - ], -) - -go_library( - name = "go_default_library", - srcs = ["controller.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/sync", - deps = [ - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/client/clientset_generated/federation_clientset:go_default_library", - "//federation/pkg/federatedtypes:go_default_library", - "//federation/pkg/federation-controller/util:go_default_library", - "//federation/pkg/federation-controller/util/clusterselector:go_default_library", - "//federation/pkg/federation-controller/util/deletionhelper:go_default_library", - "//federation/pkg/federation-controller/util/eventsink:go_default_library", - "//pkg/api:go_default_library", - "//pkg/controller:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - "//vendor/k8s.io/client-go/tools/cache:go_default_library", - "//vendor/k8s.io/client-go/tools/record:go_default_library", - "//vendor/k8s.io/client-go/util/flowcontrol:go_default_library", - "//vendor/k8s.io/client-go/util/workqueue:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/pkg/federation-controller/sync/controller.go b/federation/pkg/federation-controller/sync/controller.go deleted file mode 100644 index e57fd6595e2..00000000000 --- a/federation/pkg/federation-controller/sync/controller.go +++ /dev/null @@ -1,597 +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 sync - -import ( - "fmt" - "time" - - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - pkgruntime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/apimachinery/pkg/watch" - kubeclientset "k8s.io/client-go/kubernetes" - restclient "k8s.io/client-go/rest" - "k8s.io/client-go/tools/cache" - "k8s.io/client-go/tools/record" - "k8s.io/client-go/util/flowcontrol" - "k8s.io/client-go/util/workqueue" - federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" - federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - "k8s.io/kubernetes/federation/pkg/federatedtypes" - "k8s.io/kubernetes/federation/pkg/federation-controller/util" - "k8s.io/kubernetes/federation/pkg/federation-controller/util/clusterselector" - "k8s.io/kubernetes/federation/pkg/federation-controller/util/deletionhelper" - "k8s.io/kubernetes/federation/pkg/federation-controller/util/eventsink" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/controller" - - "github.com/golang/glog" -) - -const ( - allClustersKey = "ALL_CLUSTERS" -) - -// FederationSyncController synchronizes the state of a federated type -// to clusters that are members of the federation. -type FederationSyncController struct { - // For triggering reconciliation of a single resource. This is - // used when there is an add/update/delete operation on a resource - // in either federated API server or in some member of the - // federation. - deliverer *util.DelayingDeliverer - - // For triggering reconciliation of all target resources. This is - // used when a new cluster becomes available. - clusterDeliverer *util.DelayingDeliverer - - // Contains resources present in members of federation. - informer util.FederatedInformer - // For updating members of federation. - updater util.FederatedUpdater - // Definitions of resources that should be federated. - store cache.Store - // Informer controller for resources that should be federated. - controller cache.Controller - - // Work queue allowing parallel processing of resources - workQueue workqueue.Interface - - // Backoff manager - backoff *flowcontrol.Backoff - - // For events - eventRecorder record.EventRecorder - - deletionHelper *deletionhelper.DeletionHelper - - reviewDelay time.Duration - clusterAvailableDelay time.Duration - clusterUnavailableDelay time.Duration - smallDelay time.Duration - updateTimeout time.Duration - - adapter federatedtypes.FederatedTypeAdapter -} - -// StartFederationSyncController starts a new sync controller for a type adapter -func StartFederationSyncController(kind string, adapterFactory federatedtypes.AdapterFactory, config *restclient.Config, stopChan <-chan struct{}, minimizeLatency bool, adapterSpecificArgs map[string]interface{}) { - restclient.AddUserAgent(config, fmt.Sprintf("federation-%s-controller", kind)) - client := federationclientset.NewForConfigOrDie(config) - adapter := adapterFactory(client, config, adapterSpecificArgs) - controller := newFederationSyncController(client, adapter) - if minimizeLatency { - controller.minimizeLatency() - } - glog.Infof(fmt.Sprintf("Starting federated sync controller for %s resources", kind)) - controller.Run(stopChan) -} - -// newFederationSyncController returns a new sync controller for the given client and type adapter -func newFederationSyncController(client federationclientset.Interface, adapter federatedtypes.FederatedTypeAdapter) *FederationSyncController { - broadcaster := record.NewBroadcaster() - broadcaster.StartRecordingToSink(eventsink.NewFederatedEventSink(client)) - recorder := broadcaster.NewRecorder(api.Scheme, v1.EventSource{Component: fmt.Sprintf("federation-%v-controller", adapter.Kind())}) - - s := &FederationSyncController{ - reviewDelay: time.Second * 10, - clusterAvailableDelay: time.Second * 20, - clusterUnavailableDelay: time.Second * 60, - smallDelay: time.Second * 3, - updateTimeout: time.Second * 30, - workQueue: workqueue.New(), - backoff: flowcontrol.NewBackOff(5*time.Second, time.Minute), - eventRecorder: recorder, - adapter: adapter, - } - - // Build delivereres for triggering reconciliations. - s.deliverer = util.NewDelayingDeliverer() - s.clusterDeliverer = util.NewDelayingDeliverer() - - // Start informer in federated API servers on the resource type that should be federated. - s.store, s.controller = cache.NewInformer( - &cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (pkgruntime.Object, error) { - return adapter.FedList(metav1.NamespaceAll, options) - }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return adapter.FedWatch(metav1.NamespaceAll, options) - }, - }, - adapter.ObjectType(), - controller.NoResyncPeriodFunc(), - util.NewTriggerOnAllChanges(func(obj pkgruntime.Object) { s.deliverObj(obj, 0, false) })) - - // Federated informer on the resource type in members of federation. - s.informer = util.NewFederatedInformer( - client, - func(cluster *federationapi.Cluster, targetClient kubeclientset.Interface) (cache.Store, cache.Controller) { - return cache.NewInformer( - &cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (pkgruntime.Object, error) { - return adapter.ClusterList(targetClient, metav1.NamespaceAll, options) - }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return adapter.ClusterWatch(targetClient, metav1.NamespaceAll, options) - }, - }, - adapter.ObjectType(), - controller.NoResyncPeriodFunc(), - // Trigger reconciliation whenever something in federated cluster is changed. In most cases it - // would be just confirmation that some operation on the target resource type had succeeded. - util.NewTriggerOnAllChanges( - func(obj pkgruntime.Object) { - s.deliverObj(obj, s.reviewDelay, false) - }, - )) - }, - - &util.ClusterLifecycleHandlerFuncs{ - ClusterAvailable: func(cluster *federationapi.Cluster) { - // When new cluster becomes available process all the target resources again. - s.clusterDeliverer.DeliverAt(allClustersKey, nil, time.Now().Add(s.clusterAvailableDelay)) - }, - // When a cluster becomes unavailable process all the target resources again. - ClusterUnavailable: func(cluster *federationapi.Cluster, _ []interface{}) { - s.clusterDeliverer.DeliverAt(allClustersKey, nil, time.Now().Add(s.clusterUnavailableDelay)) - }, - }, - ) - - // Federated updeater along with Create/Update/Delete operations. - s.updater = util.NewFederatedUpdater(s.informer, adapter.Kind(), s.updateTimeout, s.eventRecorder, - func(client kubeclientset.Interface, obj pkgruntime.Object) error { - _, err := adapter.ClusterCreate(client, obj) - return err - }, - func(client kubeclientset.Interface, obj pkgruntime.Object) error { - _, err := adapter.ClusterUpdate(client, obj) - return err - }, - func(client kubeclientset.Interface, obj pkgruntime.Object) error { - qualifiedName := adapter.QualifiedName(obj) - orphanDependents := false - err := adapter.ClusterDelete(client, qualifiedName, &metav1.DeleteOptions{OrphanDependents: &orphanDependents}) - return err - }) - - s.deletionHelper = deletionhelper.NewDeletionHelper( - s.updateObject, - // objNameFunc - func(obj pkgruntime.Object) string { - return adapter.QualifiedName(obj).String() - }, - s.informer, - s.updater, - ) - - return s -} - -// minimizeLatency reduces delays and timeouts to make the controller more responsive (useful for testing). -func (s *FederationSyncController) minimizeLatency() { - s.clusterAvailableDelay = time.Second - s.clusterUnavailableDelay = time.Second - s.reviewDelay = 50 * time.Millisecond - s.smallDelay = 20 * time.Millisecond - s.updateTimeout = 5 * time.Second -} - -// Sends the given updated object to apiserver. -func (s *FederationSyncController) updateObject(obj pkgruntime.Object) (pkgruntime.Object, error) { - return s.adapter.FedUpdate(obj) -} - -func (s *FederationSyncController) Run(stopChan <-chan struct{}) { - go s.controller.Run(stopChan) - s.informer.Start() - s.deliverer.StartWithHandler(func(item *util.DelayingDelivererItem) { - s.workQueue.Add(item) - }) - s.clusterDeliverer.StartWithHandler(func(_ *util.DelayingDelivererItem) { - s.reconcileOnClusterChange() - }) - - // TODO: Allow multiple workers. - go wait.Until(s.worker, time.Second, stopChan) - - util.StartBackoffGC(s.backoff, stopChan) - - // Ensure all goroutines are cleaned up when the stop channel closes - go func() { - <-stopChan - s.informer.Stop() - s.workQueue.ShutDown() - s.deliverer.Stop() - s.clusterDeliverer.Stop() - }() -} - -type reconciliationStatus int - -const ( - statusAllOK reconciliationStatus = iota - statusNeedsRecheck - statusError - statusNotSynced -) - -func (s *FederationSyncController) worker() { - for { - obj, quit := s.workQueue.Get() - if quit { - return - } - - item := obj.(*util.DelayingDelivererItem) - qualifiedName := item.Value.(*federatedtypes.QualifiedName) - status := s.reconcile(*qualifiedName) - s.workQueue.Done(item) - - switch status { - case statusAllOK: - break - case statusError: - s.deliver(*qualifiedName, 0, true) - case statusNeedsRecheck: - s.deliver(*qualifiedName, s.reviewDelay, false) - case statusNotSynced: - s.deliver(*qualifiedName, s.clusterAvailableDelay, false) - } - } -} - -func (s *FederationSyncController) deliverObj(obj pkgruntime.Object, delay time.Duration, failed bool) { - qualifiedName := s.adapter.QualifiedName(obj) - s.deliver(qualifiedName, delay, failed) -} - -// Adds backoff to delay if this delivery is related to some failure. Resets backoff if there was no failure. -func (s *FederationSyncController) deliver(qualifiedName federatedtypes.QualifiedName, delay time.Duration, failed bool) { - key := qualifiedName.String() - if failed { - s.backoff.Next(key, time.Now()) - delay = delay + s.backoff.Get(key) - } else { - s.backoff.Reset(key) - } - s.deliverer.DeliverAfter(key, &qualifiedName, delay) -} - -// Check whether all data stores are in sync. False is returned if any of the informer/stores is not yet -// synced with the corresponding api server. -func (s *FederationSyncController) isSynced() bool { - if !s.informer.ClustersSynced() { - glog.V(2).Infof("Cluster list not synced") - return false - } - clusters, err := s.informer.GetReadyClusters() - if err != nil { - runtime.HandleError(fmt.Errorf("Failed to get ready clusters: %v", err)) - return false - } - if !s.informer.GetTargetStore().ClustersSynced(clusters) { - return false - } - return true -} - -// The function triggers reconciliation of all target federated resources. -func (s *FederationSyncController) reconcileOnClusterChange() { - if !s.isSynced() { - s.clusterDeliverer.DeliverAt(allClustersKey, nil, time.Now().Add(s.clusterAvailableDelay)) - } - for _, obj := range s.store.List() { - qualifiedName := s.adapter.QualifiedName(obj.(pkgruntime.Object)) - s.deliver(qualifiedName, s.smallDelay, false) - } -} - -func (s *FederationSyncController) reconcile(qualifiedName federatedtypes.QualifiedName) reconciliationStatus { - if !s.isSynced() { - return statusNotSynced - } - - kind := s.adapter.Kind() - key := qualifiedName.String() - - glog.V(4).Infof("Starting to reconcile %v %v", kind, key) - startTime := time.Now() - defer glog.V(4).Infof("Finished reconciling %v %v (duration: %v)", kind, key, time.Now().Sub(startTime)) - - obj, err := s.objFromCache(kind, key) - if err != nil { - return statusError - } - if obj == nil { - return statusAllOK - } - - meta := s.adapter.ObjectMeta(obj) - if meta.DeletionTimestamp != nil { - err := s.delete(obj, kind, qualifiedName) - if err != nil { - msg := "Failed to delete %s %q: %v" - args := []interface{}{kind, qualifiedName, err} - runtime.HandleError(fmt.Errorf(msg, args...)) - s.eventRecorder.Eventf(obj, api.EventTypeWarning, "DeleteFailed", msg, args...) - return statusError - } - return statusAllOK - } - - glog.V(3).Infof("Ensuring finalizers exist on %s %q", kind, key) - obj, err = s.deletionHelper.EnsureFinalizers(obj) - if err != nil { - runtime.HandleError(fmt.Errorf("Failed to ensure finalizers for %s %q: %v", kind, key, err)) - return statusError - } - - operationsAccessor := func(adapter federatedtypes.FederatedTypeAdapter, selectedClusters []*federationapi.Cluster, unselectedClusters []*federationapi.Cluster, obj pkgruntime.Object, schedulingInfo interface{}) ([]util.FederatedOperation, error) { - operations, err := clusterOperations(adapter, selectedClusters, unselectedClusters, obj, key, schedulingInfo, func(clusterName string) (interface{}, bool, error) { - return s.informer.GetTargetStore().GetByKey(clusterName, key) - }) - if err != nil { - s.eventRecorder.Eventf(obj, api.EventTypeWarning, "FedClusterOperationsError", "Error obtaining sync operations for %s: %s error: %s", kind, key, err.Error()) - } - return operations, err - } - - return syncToClusters( - s.informer.GetReadyClusters, - operationsAccessor, - selectedClusters, - s.updater.Update, - s.adapter, - s.informer, - obj, - ) -} - -func (s *FederationSyncController) objFromCache(kind, key string) (pkgruntime.Object, error) { - cachedObj, exist, err := s.store.GetByKey(key) - if err != nil { - wrappedErr := fmt.Errorf("Failed to query %s store for %q: %v", kind, key, err) - runtime.HandleError(wrappedErr) - return nil, err - } - if !exist { - return nil, nil - } - return cachedObj.(pkgruntime.Object).DeepCopyObject(), nil -} - -// delete deletes the given resource or returns error if the deletion was not complete. -func (s *FederationSyncController) delete(obj pkgruntime.Object, kind string, qualifiedName federatedtypes.QualifiedName) error { - glog.V(3).Infof("Handling deletion of %s %q", kind, qualifiedName) - - // Perform pre-deletion cleanup for the namespace adapter - namespaceAdapter, ok := s.adapter.(*federatedtypes.NamespaceAdapter) - if ok { - var err error - obj, err = namespaceAdapter.CleanUpNamespace(obj, s.eventRecorder) - if err != nil { - return err - } - } - - _, err := s.deletionHelper.HandleObjectInUnderlyingClusters(obj) - if err != nil { - return err - } - - err = s.adapter.FedDelete(qualifiedName, nil) - if err != nil { - // Its all good if the error is not found error. That means it is deleted already and we do not have to do anything. - // This is expected when we are processing an update as a result of finalizer deletion. - // The process that deleted the last finalizer is also going to delete the resource and we do not have to do anything. - if !errors.IsNotFound(err) { - return err - } - } - return nil -} - -type clustersAccessorFunc func() ([]*federationapi.Cluster, error) -type operationsFunc func(federatedtypes.FederatedTypeAdapter, []*federationapi.Cluster, []*federationapi.Cluster, pkgruntime.Object, interface{}) ([]util.FederatedOperation, error) -type clusterSelectorFunc func(*metav1.ObjectMeta, func(map[string]string, map[string]string) (bool, error), []*federationapi.Cluster) ([]*federationapi.Cluster, []*federationapi.Cluster, error) -type executionFunc func([]util.FederatedOperation) error - -// syncToClusters ensures that the state of the given object is synchronized to member clusters. -func syncToClusters(clustersAccessor clustersAccessorFunc, operationsAccessor operationsFunc, selector clusterSelectorFunc, execute executionFunc, adapter federatedtypes.FederatedTypeAdapter, informer util.FederatedInformer, obj pkgruntime.Object) reconciliationStatus { - kind := adapter.Kind() - key := federatedtypes.ObjectKey(adapter, obj) - - glog.V(3).Infof("Syncing %s %q in underlying clusters", kind, key) - - clusters, err := clustersAccessor() - if err != nil { - runtime.HandleError(fmt.Errorf("Failed to get cluster list: %v", err)) - return statusNotSynced - } - - selectedClusters, unselectedClusters, err := selector(adapter.ObjectMeta(obj), clusterselector.SendToCluster, clusters) - if err != nil { - return statusError - } - - var schedulingInfo interface{} - if adapter.IsSchedulingAdapter() { - schedulingAdapter, ok := adapter.(federatedtypes.SchedulingAdapter) - if !ok { - glog.Fatalf("Adapter for kind %q does not properly implement SchedulingAdapter.", kind) - } - schedulingInfo, err = schedulingAdapter.GetSchedule(obj, key, selectedClusters, informer) - if err != nil { - runtime.HandleError(fmt.Errorf("adapter.GetSchedule() failed on adapter for %s %q: %v", kind, key, err)) - return statusError - } - } - - operations, err := operationsAccessor(adapter, selectedClusters, unselectedClusters, obj, schedulingInfo) - if err != nil { - return statusError - } - - if adapter.IsSchedulingAdapter() { - schedulingAdapter, ok := adapter.(federatedtypes.SchedulingAdapter) - if !ok { - glog.Fatalf("Adapter for kind %q does not properly implement SchedulingAdapter.", kind) - } - err = schedulingAdapter.UpdateFederatedStatus(obj, schedulingInfo) - if err != nil { - runtime.HandleError(fmt.Errorf("adapter.UpdateFinished() failed on adapter for %s %q: %v", kind, key, err)) - return statusError - } - } - - if len(operations) == 0 { - return statusAllOK - } - - err = execute(operations) - if err != nil { - runtime.HandleError(fmt.Errorf("Failed to execute updates for %s %q: %v", kind, key, err)) - return statusError - } - - // Everything is in order but let's be double sure - return statusNeedsRecheck -} - -// selectedClusters filters the provided clusters into two slices, one containing the clusters selected by selector and the other containing the rest of the provided clusters. -func selectedClusters(objMeta *metav1.ObjectMeta, selector func(map[string]string, map[string]string) (bool, error), clusters []*federationapi.Cluster) ([]*federationapi.Cluster, []*federationapi.Cluster, error) { - selectedClusters := []*federationapi.Cluster{} - unselectedClusters := []*federationapi.Cluster{} - - for _, cluster := range clusters { - send, err := selector(cluster.Labels, objMeta.Annotations) - if err != nil { - return nil, nil, err - } else if !send { - unselectedClusters = append(unselectedClusters, cluster) - } else { - selectedClusters = append(selectedClusters, cluster) - } - } - return selectedClusters, unselectedClusters, nil -} - -type clusterObjectAccessorFunc func(clusterName string) (interface{}, bool, error) - -// clusterOperations returns the list of operations needed to synchronize the state of the given object to the provided clusters -func clusterOperations(adapter federatedtypes.FederatedTypeAdapter, selectedClusters []*federationapi.Cluster, unselectedClusters []*federationapi.Cluster, obj pkgruntime.Object, key string, schedulingInfo interface{}, accessor clusterObjectAccessorFunc) ([]util.FederatedOperation, error) { - operations := make([]util.FederatedOperation, 0) - - kind := adapter.Kind() - for _, cluster := range selectedClusters { - // The data should not be modified. - desiredObj := adapter.Copy(obj) - - clusterObj, found, err := accessor(cluster.Name) - if err != nil { - wrappedErr := fmt.Errorf("Failed to get %s %q from cluster %q: %v", kind, key, cluster.Name, err) - runtime.HandleError(wrappedErr) - return nil, wrappedErr - } - - var scheduleAction federatedtypes.ScheduleAction = federatedtypes.ActionAdd - if adapter.IsSchedulingAdapter() { - schedulingAdapter, ok := adapter.(federatedtypes.SchedulingAdapter) - if !ok { - err = fmt.Errorf("adapter for kind %s does not properly implement SchedulingAdapter.", kind) - glog.Fatalf("Error: %v", err) - } - var clusterTypedObj pkgruntime.Object = nil - if clusterObj != nil { - clusterTypedObj = clusterObj.(pkgruntime.Object) - } - desiredObj, scheduleAction, err = schedulingAdapter.ScheduleObject(cluster, clusterTypedObj, desiredObj, schedulingInfo) - if err != nil { - runtime.HandleError(err) - return nil, err - } - } - - var operationType util.FederatedOperationType = "" - if found { - if scheduleAction == federatedtypes.ActionDelete { - operationType = util.OperationTypeDelete - } else { - clusterObj := clusterObj.(pkgruntime.Object) - if !adapter.Equivalent(desiredObj, clusterObj) { - operationType = util.OperationTypeUpdate - } - } - } else if scheduleAction == federatedtypes.ActionAdd { - operationType = util.OperationTypeAdd - } - - if len(operationType) > 0 { - operations = append(operations, util.FederatedOperation{ - Type: operationType, - Obj: desiredObj, - ClusterName: cluster.Name, - Key: key, - }) - } - } - - for _, cluster := range unselectedClusters { - clusterObj, found, err := accessor(cluster.Name) - if err != nil { - wrappedErr := fmt.Errorf("Failed to get %s %q from cluster %q: %v", kind, key, cluster.Name, err) - runtime.HandleError(wrappedErr) - return nil, wrappedErr - } - if found { - operations = append(operations, util.FederatedOperation{ - Type: util.OperationTypeDelete, - Obj: clusterObj.(pkgruntime.Object), - ClusterName: cluster.Name, - Key: key, - }) - } - } - - return operations, nil -} diff --git a/federation/pkg/federation-controller/sync/controller_test.go b/federation/pkg/federation-controller/sync/controller_test.go deleted file mode 100644 index dd5c7bf8f1e..00000000000 --- a/federation/pkg/federation-controller/sync/controller_test.go +++ /dev/null @@ -1,231 +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 sync - -import ( - "errors" - "testing" - - apiv1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - pkgruntime "k8s.io/apimachinery/pkg/runtime" - federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" - "k8s.io/kubernetes/federation/pkg/federatedtypes" - "k8s.io/kubernetes/federation/pkg/federation-controller/util" - fedtest "k8s.io/kubernetes/federation/pkg/federation-controller/util/test" - - "github.com/stretchr/testify/require" -) - -var awfulError error = errors.New("Something bad happened") - -func TestSyncToClusters(t *testing.T) { - adapter := &federatedtypes.SecretAdapter{} - obj := adapter.NewTestObject("foo") - - testCases := map[string]struct { - clusterError bool - operationsError bool - executionError bool - operations []util.FederatedOperation - status reconciliationStatus - }{ - "Error listing clusters redelivers with cluster delay": { - clusterError: true, - status: statusNotSynced, - }, - "Error retrieving cluster operations redelivers": { - operationsError: true, - status: statusError, - }, - "No operations returns ok": { - status: statusAllOK, - }, - "Execution error redelivers": { - executionError: true, - operations: []util.FederatedOperation{{}}, - status: statusError, - }, - "Successful update indicates recheck": { - operations: []util.FederatedOperation{{}}, - status: statusNeedsRecheck, - }, - } - - for testName, testCase := range testCases { - t.Run(testName, func(t *testing.T) { - status := syncToClusters( - func() ([]*federationapi.Cluster, error) { - if testCase.clusterError { - return nil, awfulError - } - return nil, nil - }, - func(federatedtypes.FederatedTypeAdapter, []*federationapi.Cluster, []*federationapi.Cluster, pkgruntime.Object, interface{}) ([]util.FederatedOperation, error) { - if testCase.operationsError { - return nil, awfulError - } - return testCase.operations, nil - }, - func(objMeta *metav1.ObjectMeta, selector func(map[string]string, map[string]string) (bool, error), clusters []*federationapi.Cluster) ([]*federationapi.Cluster, []*federationapi.Cluster, error) { - return clusters, []*federationapi.Cluster{}, nil - }, - func([]util.FederatedOperation) error { - if testCase.executionError { - return awfulError - } - return nil - }, - adapter, - nil, - obj, - ) - require.Equal(t, testCase.status, status, "Unexpected status!") - }) - } -} - -func TestSelectedClusters(t *testing.T) { - clusterOne := fedtest.NewCluster("cluster1", apiv1.ConditionTrue) - clusterOne.Labels = map[string]string{"name": "cluster1"} - clusterTwo := fedtest.NewCluster("cluster2", apiv1.ConditionTrue) - clusterTwo.Labels = map[string]string{"name": "cluster2"} - - clusters := []*federationapi.Cluster{clusterOne, clusterTwo} - testCases := map[string]struct { - expectedSelectorError bool - clusterOneSelected bool - clusterTwoSelected bool - expectedSelectedClusters []*federationapi.Cluster - expectedUnselectedClusters []*federationapi.Cluster - }{ - "Selector returned error": { - expectedSelectorError: true, - }, - "All clusters selected": { - clusterOneSelected: true, - clusterTwoSelected: true, - expectedSelectedClusters: clusters, - expectedUnselectedClusters: []*federationapi.Cluster{}, - }, - "One cluster selected": { - clusterOneSelected: true, - expectedSelectedClusters: []*federationapi.Cluster{clusterOne}, - expectedUnselectedClusters: []*federationapi.Cluster{clusterTwo}, - }, - "No clusters selected": { - expectedSelectedClusters: []*federationapi.Cluster{}, - expectedUnselectedClusters: clusters, - }, - } - - for testName, testCase := range testCases { - t.Run(testName, func(t *testing.T) { - selectedClusters, unselectedClusters, err := selectedClusters(&metav1.ObjectMeta{}, func(labels map[string]string, annotations map[string]string) (bool, error) { - if testCase.expectedSelectorError { - return false, awfulError - } - if labels["name"] == "cluster1" { - return testCase.clusterOneSelected, nil - } - if labels["name"] == "cluster2" { - return testCase.clusterTwoSelected, nil - } - t.Errorf("Unexpected cluster") - return false, nil - }, clusters) - - if testCase.expectedSelectorError { - require.Error(t, err, "An error was expected") - } else { - require.NoError(t, err, "An error was not expected") - } - require.Equal(t, testCase.expectedSelectedClusters, selectedClusters, "Expected the correct clusters to be selected.") - require.Equal(t, testCase.expectedUnselectedClusters, unselectedClusters, "Expected the correct clusters to be unselected.") - }) - } -} - -func TestClusterOperations(t *testing.T) { - adapter := &federatedtypes.SecretAdapter{} - obj := adapter.NewTestObject("foo") - differingObj := adapter.Copy(obj) - federatedtypes.SetAnnotation(adapter, differingObj, "foo", "bar") - - testCases := map[string]struct { - clusterObject pkgruntime.Object - expectedErr bool - sendToCluster bool - - operationType util.FederatedOperationType - }{ - "Accessor error returned": { - expectedErr: true, - }, - "Missing cluster object should result in add operation": { - operationType: util.OperationTypeAdd, - sendToCluster: true, - }, - "Differing cluster object should result in update operation": { - clusterObject: differingObj, - operationType: util.OperationTypeUpdate, - sendToCluster: true, - }, - "Matching object and not matching ClusterSelector should result in delete operation": { - clusterObject: obj, - operationType: util.OperationTypeDelete, - sendToCluster: false, - }, - "Matching cluster object should not result in an operation": { - clusterObject: obj, - sendToCluster: true, - }, - } - for testName, testCase := range testCases { - t.Run(testName, func(t *testing.T) { - clusters := []*federationapi.Cluster{fedtest.NewCluster("cluster1", apiv1.ConditionTrue)} - key := federatedtypes.ObjectKey(adapter, obj) - - var selectedClusters, unselectedClusters []*federationapi.Cluster - if testCase.sendToCluster { - selectedClusters = clusters - unselectedClusters = []*federationapi.Cluster{} - } else { - selectedClusters = []*federationapi.Cluster{} - unselectedClusters = clusters - } - // TODO: Tests for ScheduleObject on type adapter - operations, err := clusterOperations(adapter, selectedClusters, unselectedClusters, obj, key, nil, func(string) (interface{}, bool, error) { - if testCase.expectedErr { - return nil, false, awfulError - } - return testCase.clusterObject, (testCase.clusterObject != nil), nil - }) - if testCase.expectedErr { - require.Error(t, err, "An error was expected") - } else { - require.NoError(t, err, "An error was not expected") - } - if len(testCase.operationType) == 0 { - require.True(t, len(operations) == 0, "An operation was not expected") - } else { - require.True(t, len(operations) == 1, "A single operation was expected") - require.Equal(t, testCase.operationType, operations[0].Type, "Unexpected operation returned") - } - }) - } -} diff --git a/federation/pkg/federation-controller/util/BUILD b/federation/pkg/federation-controller/util/BUILD deleted file mode 100644 index 52cfc5d620c..00000000000 --- a/federation/pkg/federation-controller/util/BUILD +++ /dev/null @@ -1,100 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = [ - "backoff.go", - "cluster_util.go", - "configmap.go", - "delaying_deliverer.go", - "deployment.go", - "federated_informer.go", - "federated_updater.go", - "handlers.go", - "meta.go", - "secret.go", - ], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/util", - deps = [ - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/client/clientset_generated/federation_clientset:go_default_library", - "//pkg/api:go_default_library", - "//pkg/client/clientset_generated/internalclientset:go_default_library", - "//pkg/controller/deployment/util:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - "//vendor/k8s.io/client-go/tools/cache:go_default_library", - "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", - "//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library", - "//vendor/k8s.io/client-go/tools/record:go_default_library", - "//vendor/k8s.io/client-go/util/flowcontrol:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = [ - "delaying_deliverer_test.go", - "deployment_test.go", - "federated_informer_test.go", - "federated_updater_test.go", - "handlers_test.go", - "meta_test.go", - ], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/util", - library = ":go_default_library", - deps = [ - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/client/clientset_generated/federation_clientset/fake:go_default_library", - "//pkg/controller/deployment/util:go_default_library", - "//vendor/github.com/stretchr/testify/assert:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", - "//vendor/k8s.io/client-go/testing:go_default_library", - "//vendor/k8s.io/client-go/tools/cache:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/pkg/federation-controller/util/clusterselector:all-srcs", - "//federation/pkg/federation-controller/util/deletionhelper:all-srcs", - "//federation/pkg/federation-controller/util/eventsink:all-srcs", - "//federation/pkg/federation-controller/util/finalizers:all-srcs", - "//federation/pkg/federation-controller/util/hpa:all-srcs", - "//federation/pkg/federation-controller/util/planner:all-srcs", - "//federation/pkg/federation-controller/util/podanalyzer:all-srcs", - "//federation/pkg/federation-controller/util/replicapreferences:all-srcs", - "//federation/pkg/federation-controller/util/test:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/pkg/federation-controller/util/backoff.go b/federation/pkg/federation-controller/util/backoff.go deleted file mode 100644 index b5b01213a04..00000000000 --- a/federation/pkg/federation-controller/util/backoff.go +++ /dev/null @@ -1,36 +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 util - -import ( - "time" - - "k8s.io/client-go/util/flowcontrol" -) - -func StartBackoffGC(backoff *flowcontrol.Backoff, stopCh <-chan struct{}) { - go func() { - for { - select { - case <-time.After(time.Minute): - backoff.GC() - case <-stopCh: - return - } - } - }() -} diff --git a/federation/pkg/federation-controller/util/cluster_util.go b/federation/pkg/federation-controller/util/cluster_util.go deleted file mode 100644 index a695870c24b..00000000000 --- a/federation/pkg/federation-controller/util/cluster_util.go +++ /dev/null @@ -1,148 +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 util - -import ( - "fmt" - "net" - "os" - "time" - - "github.com/golang/glog" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - utilnet "k8s.io/apimachinery/pkg/util/net" - "k8s.io/apimachinery/pkg/util/wait" - restclient "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - clientcmdapi "k8s.io/client-go/tools/clientcmd/api" - federation_v1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1" - "k8s.io/kubernetes/pkg/api" - clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" -) - -const ( - KubeAPIQPS = 20.0 - KubeAPIBurst = 30 - KubeconfigSecretDataKey = "kubeconfig" - getSecretTimeout = 1 * time.Minute -) - -func BuildClusterConfig(c *federation_v1beta1.Cluster) (*restclient.Config, error) { - var serverAddress string - var clusterConfig *restclient.Config - hostIP, err := utilnet.ChooseHostInterface() - if err != nil { - return nil, err - } - - for _, item := range c.Spec.ServerAddressByClientCIDRs { - _, cidrnet, err := net.ParseCIDR(item.ClientCIDR) - if err != nil { - return nil, err - } - myaddr := net.ParseIP(hostIP.String()) - if cidrnet.Contains(myaddr) == true { - serverAddress = item.ServerAddress - break - } - } - if serverAddress != "" { - if c.Spec.SecretRef == nil { - glog.Infof("didn't find secretRef for cluster %s. Trying insecure access", c.Name) - clusterConfig, err = clientcmd.BuildConfigFromFlags(serverAddress, "") - } else { - if c.Spec.SecretRef.Name == "" { - return nil, fmt.Errorf("found secretRef but no secret name for cluster %s", c.Name) - } - var secret *api.Secret - secret, err = getSecret(c.Spec.SecretRef.Name) - if err != nil { - return nil, err - } - // Pre-1.7, the secret contained a serialized kubeconfig which contained appropriate credentials. - // Post-1.7, the secret contains credentials for a service account. - // Check for the service account credentials, and use them if they exist; if not, use the - // serialized kubeconfig. - token, tokenFound := secret.Data["token"] - ca, caFound := secret.Data["ca.crt"] - if tokenFound != caFound { - return nil, fmt.Errorf("secret should have values for either both 'ca.crt' and 'token' in its Data, or neither: %v", secret) - } else if tokenFound && caFound { - clusterConfig, err = clientcmd.BuildConfigFromFlags(serverAddress, "") - clusterConfig.CAData = ca - clusterConfig.BearerToken = string(token) - } else { - kubeconfigGetter := KubeconfigGetterForSecret(secret) - clusterConfig, err = clientcmd.BuildConfigFromKubeconfigGetter(serverAddress, kubeconfigGetter) - } - } - if err != nil { - return nil, err - } - clusterConfig.QPS = KubeAPIQPS - clusterConfig.Burst = KubeAPIBurst - } - return clusterConfig, nil -} - -// getSecret gets a secret from the cluster. -func getSecret(secretName string) (*api.Secret, error) { - // Get the namespace this is running in from the env variable. - namespace := os.Getenv("POD_NAMESPACE") - if namespace == "" { - return nil, fmt.Errorf("unexpected: POD_NAMESPACE env var returned empty string") - } - // Get a client to talk to the k8s apiserver, to fetch secrets from it. - cc, err := restclient.InClusterConfig() - if err != nil { - return nil, fmt.Errorf("error in creating in-cluster config: %s", err) - } - client, err := clientset.NewForConfig(cc) - if err != nil { - return nil, fmt.Errorf("error in creating in-cluster client: %s", err) - } - var secret *api.Secret - err = wait.PollImmediate(1*time.Second, getSecretTimeout, func() (bool, error) { - secret, err = client.Core().Secrets(namespace).Get(secretName, metav1.GetOptions{}) - if err == nil { - return true, nil - } - glog.Warningf("error in fetching secret: %s", err) - return false, nil - }) - if err != nil { - return nil, fmt.Errorf("timed out waiting for secret: %s", err) - } - if secret == nil { - return nil, fmt.Errorf("unexpected: received null secret %s", secretName) - } - return secret, nil -} - -// KubeconfigGetterForSecret gets the kubeconfig from the given secret. -// This is to inject a different KubeconfigGetter in tests. We don't use -// the standard one which calls NewInCluster in tests to avoid having to -// set up service accounts and mount files with secret tokens. -var KubeconfigGetterForSecret = func(secret *api.Secret) clientcmd.KubeconfigGetter { - return func() (*clientcmdapi.Config, error) { - data, ok := secret.Data[KubeconfigSecretDataKey] - if !ok { - return nil, fmt.Errorf("secret does not have data with key %s", KubeconfigSecretDataKey) - } - return clientcmd.Load(data) - } -} diff --git a/federation/pkg/federation-controller/util/clusterselector/BUILD b/federation/pkg/federation-controller/util/clusterselector/BUILD deleted file mode 100644 index 7298f368467..00000000000 --- a/federation/pkg/federation-controller/util/clusterselector/BUILD +++ /dev/null @@ -1,42 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = ["clusterselector_test.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/util/clusterselector", - library = ":go_default_library", - deps = [ - "//federation/apis/federation/v1beta1:go_default_library", - "//vendor/github.com/stretchr/testify/require:go_default_library", - ], -) - -go_library( - name = "go_default_library", - srcs = ["clusterselector.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/util/clusterselector", - deps = [ - "//federation/apis/federation/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/selection:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/pkg/federation-controller/util/clusterselector/clusterselector.go b/federation/pkg/federation-controller/util/clusterselector/clusterselector.go deleted file mode 100644 index e4a56f25a90..00000000000 --- a/federation/pkg/federation-controller/util/clusterselector/clusterselector.go +++ /dev/null @@ -1,86 +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 clusterselector - -import ( - "encoding/json" - - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/selection" - federation_v1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1" -) - -// Parses the cluster selector annotation to find out if the object with that annotation should be forwarded to a cluster with the given clusterLabels. -func SendToCluster(clusterLabels map[string]string, annotations map[string]string) (bool, error) { - // Check if a ClusterSelector annotation exists and send to all clusters when it does not exist - val, ok := annotations[federation_v1beta1.FederationClusterSelectorAnnotation] - if !ok { - return true, nil - } - - selector, err := getSelector(val) - if err != nil { - return false, err - } - return selector.Matches(labels.Set(clusterLabels)), nil -} - -func getSelector(annotation string) (labels.Selector, error) { - selector := labels.NewSelector() - requirements := make([]federation_v1beta1.ClusterSelectorRequirement, 0) - err := json.Unmarshal([]byte(annotation), &requirements) - if err != nil { - return nil, err - } - - for _, requirement := range requirements { - r, err := labels.NewRequirement(requirement.Key, ConvertOperator(requirement.Operator), requirement.Values) - if err != nil { - // Stop processing and assume failure since we have no way of knowing the end users intent for this or any other clusters. - return nil, err - } - selector = selector.Add(*r) - } - - return selector, nil -} - -// ConvertOperator converts a string operator into selection.Operator type -func ConvertOperator(source string) selection.Operator { - var op selection.Operator - switch source { - case "!", "DoesNotExist": - op = selection.DoesNotExist - case "=": - op = selection.Equals - case "==": - op = selection.DoubleEquals - case "in", "In": - op = selection.In - case "!=": - op = selection.NotEquals - case "notin", "NotIn": - op = selection.NotIn - case "exists", "Exists": - op = selection.Exists - case "gt", "Gt", ">": - op = selection.GreaterThan - case "lt", "Lt", "<": - op = selection.LessThan - } - return op -} diff --git a/federation/pkg/federation-controller/util/clusterselector/clusterselector_test.go b/federation/pkg/federation-controller/util/clusterselector/clusterselector_test.go deleted file mode 100644 index 247ef299cc1..00000000000 --- a/federation/pkg/federation-controller/util/clusterselector/clusterselector_test.go +++ /dev/null @@ -1,98 +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 clusterselector - -import ( - "testing" - - "github.com/stretchr/testify/require" - - federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" -) - -func TestSendToCluster(t *testing.T) { - - clusterLabels := map[string]string{ - "location": "europe", - "environment": "prod", - "version": "15", - } - - testCases := map[string]struct { - objectAnnotations map[string]string - expectedResult bool - expectedErr bool - }{ - "match with single annotation": { - objectAnnotations: map[string]string{ - federationapi.FederationClusterSelectorAnnotation: `[{"key": "location", "operator": "in", "values": ["europe"]}]`, - }, - expectedResult: true, - }, - "match on multiple annotations": { - objectAnnotations: map[string]string{ - federationapi.FederationClusterSelectorAnnotation: `[{"key": "location", "operator": "in", "values": ["europe"]}, {"key": "environment", "operator": "==", "values": ["prod"]}]`, - }, - expectedResult: true, - }, - "mismatch on one annotation": { - objectAnnotations: map[string]string{ - federationapi.FederationClusterSelectorAnnotation: `[{"key": "location", "operator": "in", "values": ["europe"]}, {"key": "environment", "operator": "==", "values": ["test"]}]`, - }, - expectedResult: false, - }, - "match on not equal annotation": { - objectAnnotations: map[string]string{ - federationapi.FederationClusterSelectorAnnotation: `[{"key": "location", "operator": "!=", "values": ["usa"]}, {"key": "environment", "operator": "in", "values": ["prod"]}]`, - }, - expectedResult: true, - }, - "match on greater than annotation": { - objectAnnotations: map[string]string{ - federationapi.FederationClusterSelectorAnnotation: `[{"key": "version", "operator": ">", "values": ["14"]}]`, - }, - expectedResult: true, - }, - "mismatch on greater than annotation": { - objectAnnotations: map[string]string{ - federationapi.FederationClusterSelectorAnnotation: `[{"key": "version", "operator": ">", "values": ["15"]}]`, - }, - expectedResult: false, - }, - "unable to parse annotation": { - objectAnnotations: map[string]string{ - federationapi.FederationClusterSelectorAnnotation: `[{"not able to parse",}]`, - }, - expectedResult: false, - expectedErr: true, - }, - } - - for testName, testCase := range testCases { - t.Run(testName, func(t *testing.T) { - result, err := SendToCluster(clusterLabels, testCase.objectAnnotations) - - if testCase.expectedErr { - require.Error(t, err, "An error was expected") - } else { - require.NoError(t, err, "An error was not expected") - } - - require.Equal(t, testCase.expectedResult, result, "Unexpected response from SendToCluster") - }) - } -} diff --git a/federation/pkg/federation-controller/util/configmap.go b/federation/pkg/federation-controller/util/configmap.go deleted file mode 100644 index c09df8ab00d..00000000000 --- a/federation/pkg/federation-controller/util/configmap.go +++ /dev/null @@ -1,31 +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 util - -import ( - "reflect" - - api_v1 "k8s.io/api/core/v1" -) - -// Checks if cluster-independent, user provided data in two given ConfigMaps are equal. If in -// the future the ConfigMap structure is expanded then any field that is not populated. -// by the api server should be included here. -func ConfigMapEquivalent(s1, s2 *api_v1.ConfigMap) bool { - return ObjectMetaEquivalent(s1.ObjectMeta, s2.ObjectMeta) && - reflect.DeepEqual(s1.Data, s2.Data) -} diff --git a/federation/pkg/federation-controller/util/delaying_deliverer.go b/federation/pkg/federation-controller/util/delaying_deliverer.go deleted file mode 100644 index e087ad6d975..00000000000 --- a/federation/pkg/federation-controller/util/delaying_deliverer.go +++ /dev/null @@ -1,183 +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. -*/ - -// TODO: consider moving it to a more generic package. -package util - -import ( - "container/heap" - "time" -) - -const ( - // TODO: Investigate what capacity is right. - delayingDelivererUpdateChanCapacity = 1000 -) - -// DelayingDelivererItem is structure delivered by DelayingDeliverer to the -// target channel. -type DelayingDelivererItem struct { - // Key under which the value was added to deliverer. - Key string - // Value of the item. - Value interface{} - // When the item should be delivered. - DeliveryTime time.Time -} - -type delivererHeap struct { - keyPosition map[string]int - data []*DelayingDelivererItem -} - -// Functions required by container.Heap. - -func (dh *delivererHeap) Len() int { return len(dh.data) } -func (dh *delivererHeap) Less(i, j int) bool { - return dh.data[i].DeliveryTime.Before(dh.data[j].DeliveryTime) -} -func (dh *delivererHeap) Swap(i, j int) { - dh.keyPosition[dh.data[i].Key] = j - dh.keyPosition[dh.data[j].Key] = i - dh.data[i], dh.data[j] = dh.data[j], dh.data[i] -} - -func (dh *delivererHeap) Push(x interface{}) { - item := x.(*DelayingDelivererItem) - dh.data = append(dh.data, item) - dh.keyPosition[item.Key] = len(dh.data) - 1 -} - -func (dh *delivererHeap) Pop() interface{} { - n := len(dh.data) - item := dh.data[n-1] - dh.data = dh.data[:n-1] - delete(dh.keyPosition, item.Key) - return item -} - -// A structure that pushes the items to the target channel at a given time. -type DelayingDeliverer struct { - // Channel to deliver the data when their time comes. - targetChannel chan *DelayingDelivererItem - // Store for data - heap *delivererHeap - // Channel to feed the main goroutine with updates. - updateChannel chan *DelayingDelivererItem - // To stop the main goroutine. - stopChannel chan struct{} -} - -func NewDelayingDeliverer() *DelayingDeliverer { - return NewDelayingDelivererWithChannel(make(chan *DelayingDelivererItem, 100)) -} - -func NewDelayingDelivererWithChannel(targetChannel chan *DelayingDelivererItem) *DelayingDeliverer { - return &DelayingDeliverer{ - targetChannel: targetChannel, - heap: &delivererHeap{ - keyPosition: make(map[string]int), - data: make([]*DelayingDelivererItem, 0), - }, - updateChannel: make(chan *DelayingDelivererItem, delayingDelivererUpdateChanCapacity), - stopChannel: make(chan struct{}), - } -} - -// Deliver all items due before or equal to timestamp. -func (d *DelayingDeliverer) deliver(timestamp time.Time) { - for d.heap.Len() > 0 { - if timestamp.Before(d.heap.data[0].DeliveryTime) { - return - } - item := heap.Pop(d.heap).(*DelayingDelivererItem) - d.targetChannel <- item - } -} - -func (d *DelayingDeliverer) run() { - for { - now := time.Now() - d.deliver(now) - - nextWakeUp := now.Add(time.Hour) - if d.heap.Len() > 0 { - nextWakeUp = d.heap.data[0].DeliveryTime - } - sleepTime := nextWakeUp.Sub(now) - - select { - case <-time.After(sleepTime): - break // just wake up and process the data - case item := <-d.updateChannel: - if position, found := d.heap.keyPosition[item.Key]; found { - if item.DeliveryTime.Before(d.heap.data[position].DeliveryTime) { - d.heap.data[position] = item - heap.Fix(d.heap, position) - } - // Ignore if later. - } else { - heap.Push(d.heap, item) - } - case <-d.stopChannel: - return - } - } -} - -// Starts the DelayingDeliverer. -func (d *DelayingDeliverer) Start() { - go d.run() -} - -// Stops the DelayingDeliverer. Undelivered items are discarded. -func (d *DelayingDeliverer) Stop() { - close(d.stopChannel) -} - -// Delivers value at the given time. -func (d *DelayingDeliverer) DeliverAt(key string, value interface{}, deliveryTime time.Time) { - d.updateChannel <- &DelayingDelivererItem{ - Key: key, - Value: value, - DeliveryTime: deliveryTime, - } -} - -// Delivers value after the given delay. -func (d *DelayingDeliverer) DeliverAfter(key string, value interface{}, delay time.Duration) { - d.DeliverAt(key, value, time.Now().Add(delay)) -} - -// Gets target channel of the deliverer. -func (d *DelayingDeliverer) GetTargetChannel() chan *DelayingDelivererItem { - return d.targetChannel -} - -// Starts Delaying deliverer with a handler listening on the target channel. -func (d *DelayingDeliverer) StartWithHandler(handler func(*DelayingDelivererItem)) { - go func() { - for { - select { - case item := <-d.targetChannel: - handler(item) - case <-d.stopChannel: - return - } - } - }() - d.Start() -} diff --git a/federation/pkg/federation-controller/util/delaying_deliverer_test.go b/federation/pkg/federation-controller/util/delaying_deliverer_test.go deleted file mode 100644 index 006bab4c455..00000000000 --- a/federation/pkg/federation-controller/util/delaying_deliverer_test.go +++ /dev/null @@ -1,63 +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 util - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestDelayingDeliverer(t *testing.T) { - targetChannel := make(chan *DelayingDelivererItem) - now := time.Now() - d := NewDelayingDelivererWithChannel(targetChannel) - d.Start() - defer d.Stop() - startupDelay := time.Second - d.DeliverAt("a", "aaa", now.Add(startupDelay+2*time.Millisecond)) - d.DeliverAt("b", "bbb", now.Add(startupDelay+3*time.Millisecond)) - d.DeliverAt("c", "ccc", now.Add(startupDelay+1*time.Millisecond)) - d.DeliverAt("e", "eee", now.Add(time.Hour)) - d.DeliverAt("e", "eee", now) - - d.DeliverAt("d", "ddd", now.Add(time.Hour)) - - i0 := <-targetChannel - assert.Equal(t, "e", i0.Key) - assert.Equal(t, "eee", i0.Value.(string)) - assert.Equal(t, now, i0.DeliveryTime) - - i1 := <-targetChannel - received1 := time.Now() - assert.True(t, received1.Sub(now).Nanoseconds() > startupDelay.Nanoseconds()) - assert.Equal(t, "c", i1.Key) - - i2 := <-targetChannel - assert.Equal(t, "a", i2.Key) - - i3 := <-targetChannel - assert.Equal(t, "b", i3.Key) - - select { - case <-targetChannel: - t.Fatalf("Nothing should be received") - case <-time.After(time.Second): - // Ok. Expected - } -} diff --git a/federation/pkg/federation-controller/util/deletionhelper/BUILD b/federation/pkg/federation-controller/util/deletionhelper/BUILD deleted file mode 100644 index ae9397cde3f..00000000000 --- a/federation/pkg/federation-controller/util/deletionhelper/BUILD +++ /dev/null @@ -1,33 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["deletion_helper.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/util/deletionhelper", - deps = [ - "//federation/pkg/federation-controller/util:go_default_library", - "//federation/pkg/federation-controller/util/finalizers:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/pkg/federation-controller/util/deletionhelper/deletion_helper.go b/federation/pkg/federation-controller/util/deletionhelper/deletion_helper.go deleted file mode 100644 index d18570a9821..00000000000 --- a/federation/pkg/federation-controller/util/deletionhelper/deletion_helper.go +++ /dev/null @@ -1,210 +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 to help federation controllers to delete federated resources from -// underlying clusters when the resource is deleted from federation control -// plane. -package deletionhelper - -import ( - "fmt" - "strings" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/federation/pkg/federation-controller/util" - finalizersutil "k8s.io/kubernetes/federation/pkg/federation-controller/util/finalizers" - - "github.com/golang/glog" -) - -const ( - // Add this finalizer to a federation resource if the resource should be - // deleted from all underlying clusters before being deleted from - // federation control plane. - // This is ignored if FinalizerOrphan is also present on the resource. - // In that case, both finalizers are removed from the resource and the - // resource is deleted from federation control plane without affecting - // the underlying clusters. - FinalizerDeleteFromUnderlyingClusters string = "federation.kubernetes.io/delete-from-underlying-clusters" -) - -type UpdateObjFunc func(runtime.Object) (runtime.Object, error) -type ObjNameFunc func(runtime.Object) string - -type DeletionHelper struct { - updateObjFunc UpdateObjFunc - objNameFunc ObjNameFunc - informer util.FederatedInformer - updater util.FederatedUpdater -} - -func NewDeletionHelper( - updateObjFunc UpdateObjFunc, objNameFunc ObjNameFunc, - informer util.FederatedInformer, updater util.FederatedUpdater) *DeletionHelper { - return &DeletionHelper{ - updateObjFunc: updateObjFunc, - objNameFunc: objNameFunc, - informer: informer, - updater: updater, - } -} - -// Ensures that the given object has both FinalizerDeleteFromUnderlyingClusters -// and FinalizerOrphan finalizers. -// We do this so that the controller is always notified when a federation resource is deleted. -// If user deletes the resource with nil DeleteOptions or -// DeletionOptions.OrphanDependents = true then the apiserver removes the orphan finalizer -// and deletion helper does a cascading deletion. -// Otherwise, deletion helper just removes the federation resource and orphans -// the corresponding resources in underlying clusters. -// This method should be called before creating objects in underlying clusters. -func (dh *DeletionHelper) EnsureFinalizers(obj runtime.Object) ( - runtime.Object, error) { - finalizers := sets.String{} - hasFinalizer, err := finalizersutil.HasFinalizer(obj, FinalizerDeleteFromUnderlyingClusters) - if err != nil { - return obj, err - } - if !hasFinalizer { - finalizers.Insert(FinalizerDeleteFromUnderlyingClusters) - } - hasFinalizer, err = finalizersutil.HasFinalizer(obj, metav1.FinalizerOrphanDependents) - if err != nil { - return obj, err - } - if !hasFinalizer { - finalizers.Insert(metav1.FinalizerOrphanDependents) - } - if finalizers.Len() != 0 { - glog.V(2).Infof("Adding finalizers %v to %s", finalizers.List(), dh.objNameFunc(obj)) - return dh.addFinalizers(obj, finalizers) - } - return obj, nil -} - -// Deletes the resources corresponding to the given federated resource from -// all underlying clusters, unless it has the FinalizerOrphan finalizer. -// Removes FinalizerOrphan and FinalizerDeleteFromUnderlyingClusters finalizers -// when done. -// Callers are expected to keep calling this (with appropriate backoff) until -// it succeeds. -func (dh *DeletionHelper) HandleObjectInUnderlyingClusters(obj runtime.Object) ( - runtime.Object, error) { - objName := dh.objNameFunc(obj) - glog.V(2).Infof("Handling deletion of federated dependents for object: %s", objName) - hasFinalizer, err := finalizersutil.HasFinalizer(obj, FinalizerDeleteFromUnderlyingClusters) - if err != nil { - return obj, err - } - if !hasFinalizer { - glog.V(2).Infof("obj does not have %s finalizer. Nothing to do", FinalizerDeleteFromUnderlyingClusters) - return obj, nil - } - hasOrphanFinalizer, err := finalizersutil.HasFinalizer(obj, metav1.FinalizerOrphanDependents) - if err != nil { - return obj, err - } - if hasOrphanFinalizer { - glog.V(2).Infof("Found finalizer orphan. Nothing to do, just remove the finalizer") - // If the obj has FinalizerOrphan finalizer, then we need to orphan the - // corresponding objects in underlying clusters. - // Just remove both the finalizers in that case. - finalizers := sets.NewString(FinalizerDeleteFromUnderlyingClusters, metav1.FinalizerOrphanDependents) - return dh.removeFinalizers(obj, finalizers) - } - - glog.V(2).Infof("Deleting obj %s from underlying clusters", objName) - // Else, we need to delete the obj from all underlying clusters. - unreadyClusters, err := dh.informer.GetUnreadyClusters() - if err != nil { - return nil, fmt.Errorf("failed to get a list of unready clusters: %v", err) - } - // TODO: Handle the case when cluster resource is watched after this is executed. - // This can happen if a namespace is deleted before its creation had been - // observed in all underlying clusters. - storeKey := dh.informer.GetTargetStore().GetKeyFor(obj) - clusterNsObjs, err := dh.informer.GetTargetStore().GetFromAllClusters(storeKey) - glog.V(3).Infof("Found %d objects in underlying clusters", len(clusterNsObjs)) - if err != nil { - return nil, fmt.Errorf("failed to get object %s from underlying clusters: %v", objName, err) - } - operations := make([]util.FederatedOperation, 0) - for _, clusterNsObj := range clusterNsObjs { - operations = append(operations, util.FederatedOperation{ - Type: util.OperationTypeDelete, - ClusterName: clusterNsObj.ClusterName, - Obj: clusterNsObj.Object.(runtime.Object), - Key: objName, - }) - } - err = dh.updater.Update(operations) - if err != nil { - return nil, fmt.Errorf("failed to execute updates for obj %s: %v", objName, err) - } - if len(operations) > 0 { - // We have deleted a bunch of resources. - // Wait for the store to observe all the deletions. - var clusterNames []string - for _, op := range operations { - clusterNames = append(clusterNames, op.ClusterName) - } - return nil, fmt.Errorf("waiting for object %s to be deleted from clusters: %s", objName, strings.Join(clusterNames, ", ")) - } - - // We have now deleted the object from all *ready* clusters. - // But still need to wait for clusters that are not ready to ensure that - // the object has been deleted from *all* clusters. - if len(unreadyClusters) != 0 { - var clusterNames []string - for _, cluster := range unreadyClusters { - clusterNames = append(clusterNames, cluster.Name) - } - return nil, fmt.Errorf("waiting for clusters %s to become ready to verify that obj %s has been deleted", strings.Join(clusterNames, ", "), objName) - } - - // All done. Just remove the finalizer. - return dh.removeFinalizers(obj, sets.NewString(FinalizerDeleteFromUnderlyingClusters)) -} - -// Adds the given finalizers to the given objects ObjectMeta. -func (dh *DeletionHelper) addFinalizers(obj runtime.Object, finalizers sets.String) (runtime.Object, error) { - isUpdated, err := finalizersutil.AddFinalizers(obj, finalizers) - if err != nil || !isUpdated { - return obj, err - } - // Send the update to apiserver. - updatedObj, err := dh.updateObjFunc(obj) - if err != nil { - return nil, fmt.Errorf("failed to add finalizers %v to object %s: %v", finalizers, dh.objNameFunc(obj), err) - } - return updatedObj, nil -} - -// Removes the given finalizers from the given objects ObjectMeta. -func (dh *DeletionHelper) removeFinalizers(obj runtime.Object, finalizers sets.String) (runtime.Object, error) { - isUpdated, err := finalizersutil.RemoveFinalizers(obj, finalizers) - if err != nil || !isUpdated { - return obj, err - } - // Send the update to apiserver. - updatedObj, err := dh.updateObjFunc(obj) - if err != nil { - return nil, fmt.Errorf("failed to remove finalizers %v from object %s: %v", finalizers, dh.objNameFunc(obj), err) - } - return updatedObj, nil -} diff --git a/federation/pkg/federation-controller/util/deployment.go b/federation/pkg/federation-controller/util/deployment.go deleted file mode 100644 index 8c9627affe5..00000000000 --- a/federation/pkg/federation-controller/util/deployment.go +++ /dev/null @@ -1,75 +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 util - -import ( - "reflect" - - extensions_v1 "k8s.io/api/extensions/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - deputils "k8s.io/kubernetes/pkg/controller/deployment/util" -) - -// Checks if cluster-independent, user provided data in two given Deployment are equal. -// This function assumes that revisions are not kept in sync across the clusters. -func DeploymentEquivalent(a, b *extensions_v1.Deployment) bool { - if a.Name != b.Name { - return false - } - if a.Namespace != b.Namespace { - return false - } - if !reflect.DeepEqual(a.Labels, b.Labels) && (len(a.Labels) != 0 || len(b.Labels) != 0) { - return false - } - hasKeysAndVals := func(x, y map[string]string) bool { - if x == nil { - x = map[string]string{} - } - if y == nil { - y = map[string]string{} - } - for k, v := range x { - if k == deputils.RevisionAnnotation { - continue - } - v2, found := y[k] - if !found || v != v2 { - return false - } - } - return true - } - return hasKeysAndVals(a.Annotations, b.Annotations) && - hasKeysAndVals(b.Annotations, a.Annotations) && - reflect.DeepEqual(a.Spec, b.Spec) -} - -// Copies object meta for Deployment, skipping revision information. -func DeepCopyDeploymentObjectMeta(meta metav1.ObjectMeta) metav1.ObjectMeta { - meta = DeepCopyRelevantObjectMeta(meta) - delete(meta.Annotations, deputils.RevisionAnnotation) - return meta -} - -// Copies object meta for Deployment, skipping revision information. -func DeepCopyDeployment(a *extensions_v1.Deployment) *extensions_v1.Deployment { - return &extensions_v1.Deployment{ - ObjectMeta: DeepCopyDeploymentObjectMeta(a.ObjectMeta), - Spec: *a.Spec.DeepCopy(), - } -} diff --git a/federation/pkg/federation-controller/util/deployment_test.go b/federation/pkg/federation-controller/util/deployment_test.go deleted file mode 100644 index 97d59699f89..00000000000 --- a/federation/pkg/federation-controller/util/deployment_test.go +++ /dev/null @@ -1,70 +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 util - -import ( - "testing" - - extensionsv1 "k8s.io/api/extensions/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - deputils "k8s.io/kubernetes/pkg/controller/deployment/util" - - "github.com/stretchr/testify/assert" -) - -func TestDeploymentEquivalent(t *testing.T) { - d1 := newDeployment() - d2 := newDeployment() - d2.Annotations = make(map[string]string) - - d3 := newDeployment() - d3.Annotations = map[string]string{"a": "b"} - - d4 := newDeployment() - d4.Annotations = map[string]string{deputils.RevisionAnnotation: "9"} - - assert.True(t, DeploymentEquivalent(d1, d2)) - assert.True(t, DeploymentEquivalent(d1, d2)) - assert.True(t, DeploymentEquivalent(d1, d4)) - assert.True(t, DeploymentEquivalent(d4, d1)) - assert.False(t, DeploymentEquivalent(d3, d4)) - assert.False(t, DeploymentEquivalent(d3, d1)) - assert.True(t, DeploymentEquivalent(d3, d3)) -} - -func TestDeploymentCopy(t *testing.T) { - d1 := newDeployment() - d1.Annotations = map[string]string{deputils.RevisionAnnotation: "9", "a": "b"} - d2 := DeepCopyDeployment(d1) - assert.True(t, DeploymentEquivalent(d1, d2)) - assert.Contains(t, d2.Annotations, "a") - assert.NotContains(t, d2.Annotations, deputils.RevisionAnnotation) -} - -func newDeployment() *extensionsv1.Deployment { - replicas := int32(5) - return &extensionsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "wrr", - Namespace: metav1.NamespaceDefault, - SelfLink: "/api/v1/namespaces/default/deployments/name123", - }, - Spec: extensionsv1.DeploymentSpec{ - Replicas: &replicas, - }, - } -} diff --git a/federation/pkg/federation-controller/util/eventsink/BUILD b/federation/pkg/federation-controller/util/eventsink/BUILD deleted file mode 100644 index bf5fbe5657d..00000000000 --- a/federation/pkg/federation-controller/util/eventsink/BUILD +++ /dev/null @@ -1,52 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = ["eventsink.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/util/eventsink", - deps = [ - "//federation/client/clientset_generated/federation_clientset:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/client-go/tools/record:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["eventsink_test.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/util/eventsink", - library = ":go_default_library", - deps = [ - "//federation/client/clientset_generated/federation_clientset/fake:go_default_library", - "//federation/pkg/federation-controller/util/test:go_default_library", - "//vendor/github.com/stretchr/testify/assert:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/client-go/testing:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/pkg/federation-controller/util/eventsink/eventsink.go b/federation/pkg/federation-controller/util/eventsink/eventsink.go deleted file mode 100644 index 343fef5df73..00000000000 --- a/federation/pkg/federation-controller/util/eventsink/eventsink.go +++ /dev/null @@ -1,111 +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 eventsink - -import ( - "reflect" - - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/conversion" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/tools/record" - fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" -) - -// Implements k8s.io/client-go/tools/record.EventSink. -type FederatedEventSink struct { - clientset fedclientset.Interface -} - -// To check if all required functions are implemented. -var _ record.EventSink = &FederatedEventSink{} - -func NewFederatedEventSink(clientset fedclientset.Interface) *FederatedEventSink { - return &FederatedEventSink{ - clientset: clientset, - } -} - -// TODO this is uses a reflection conversion path and is very expensive. federation should update to use client-go - -var scheme = runtime.NewScheme() - -func init() { - // register client-go's and kube's Event type under two different GroupVersions - // TODO: switch to client-go client for events - scheme.AddKnownTypes(v1.SchemeGroupVersion, &v1.Event{}) - scheme.AddKnownTypes(schema.GroupVersion{Group: "fake-kube-" + v1.SchemeGroupVersion.Group, Version: v1.SchemeGroupVersion.Version}, &v1.Event{}) - - if err := scheme.AddConversionFuncs( - metav1.Convert_unversioned_Time_To_unversioned_Time, - ); err != nil { - panic(err) - } - if err := scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{ - Fn: func(in, out interface{}, c *conversion.Cloner) error { - in.(*metav1.Time).DeepCopyInto(out.(*metav1.Time)) - return nil - }, - InType: reflect.TypeOf(&metav1.Time{}), - }, - ); err != nil { - panic(err) - } -} - -func (fes *FederatedEventSink) Create(event *v1.Event) (*v1.Event, error) { - ret, err := fes.clientset.Core().Events(event.Namespace).Create(event) - if err != nil { - return nil, err - } - - retEvent := &v1.Event{} - if err := scheme.Convert(ret, retEvent, nil); err != nil { - return nil, err - } - return retEvent, nil -} - -func (fes *FederatedEventSink) Update(event *v1.Event) (*v1.Event, error) { - ret, err := fes.clientset.Core().Events(event.Namespace).Update(event) - if err != nil { - return nil, err - } - - retEvent := &v1.Event{} - if err := scheme.Convert(ret, retEvent, nil); err != nil { - return nil, err - } - return retEvent, nil -} - -func (fes *FederatedEventSink) Patch(event *v1.Event, data []byte) (*v1.Event, error) { - ret, err := fes.clientset.Core().Events(event.Namespace).Patch(event.Name, types.StrategicMergePatchType, data) - if err != nil { - return nil, err - } - - retEvent := &v1.Event{} - if err := scheme.Convert(ret, retEvent, nil); err != nil { - return nil, err - } - return retEvent, nil -} diff --git a/federation/pkg/federation-controller/util/eventsink/eventsink_test.go b/federation/pkg/federation-controller/util/eventsink/eventsink_test.go deleted file mode 100644 index 230ef8d0d5c..00000000000 --- a/federation/pkg/federation-controller/util/eventsink/eventsink_test.go +++ /dev/null @@ -1,71 +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 eventsink - -import ( - "testing" - - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - core "k8s.io/client-go/testing" - fakefedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/fake" - . "k8s.io/kubernetes/federation/pkg/federation-controller/util/test" - - "github.com/stretchr/testify/assert" -) - -func TestEventSink(t *testing.T) { - fakeFederationClient := &fakefedclientset.Clientset{} - createdChan := make(chan runtime.Object, 100) - fakeFederationClient.AddReactor("create", "events", func(action core.Action) (bool, runtime.Object, error) { - createAction := action.(core.CreateAction) - obj := createAction.GetObject() - createdChan <- obj - return true, obj, nil - }) - updateChan := make(chan runtime.Object, 100) - fakeFederationClient.AddReactor("update", "events", func(action core.Action) (bool, runtime.Object, error) { - updateAction := action.(core.UpdateAction) - obj := updateAction.GetObject() - updateChan <- obj - return true, obj, nil - }) - - event := v1.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bzium", - Namespace: "ns", - }, - } - sink := NewFederatedEventSink(fakeFederationClient) - eventUpdated, err := sink.Create(&event) - assert.NoError(t, err) - eventV1 := GetObjectFromChan(createdChan).(*v1.Event) - assert.NotNil(t, eventV1) - // Just some simple sanity checks. - assert.Equal(t, event.Name, eventV1.Name) - assert.Equal(t, event.Name, eventUpdated.Name) - - eventUpdated, err = sink.Update(&event) - assert.NoError(t, err) - eventV1 = GetObjectFromChan(updateChan).(*v1.Event) - assert.NotNil(t, eventV1) - // Just some simple sanity checks. - assert.Equal(t, event.Name, eventV1.Name) - assert.Equal(t, event.Name, eventUpdated.Name) -} diff --git a/federation/pkg/federation-controller/util/federated_informer.go b/federation/pkg/federation-controller/util/federated_informer.go deleted file mode 100644 index b3b0dedb820..00000000000 --- a/federation/pkg/federation-controller/util/federated_informer.go +++ /dev/null @@ -1,524 +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 util - -import ( - "fmt" - "reflect" - "sync" - "time" - - apiv1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - pkgruntime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/watch" - kubeclientset "k8s.io/client-go/kubernetes" - restclient "k8s.io/client-go/rest" - "k8s.io/client-go/tools/cache" - federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" - federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - - "github.com/golang/glog" -) - -const ( - clusterSyncPeriod = 10 * time.Minute - userAgentName = "federation-controller" -) - -// An object with an origin information. -type FederatedObject struct { - Object interface{} - ClusterName string -} - -// FederatedReadOnlyStore is an overlay over multiple stores created in federated clusters. -type FederatedReadOnlyStore interface { - // Returns all items in the store. - List() ([]FederatedObject, error) - - // Returns all items from a cluster. - ListFromCluster(clusterName string) ([]interface{}, error) - - // GetKeyFor returns the key under which the item would be put in the store. - GetKeyFor(item interface{}) string - - // GetByKey returns the item stored under the given key in the specified cluster (if exist). - GetByKey(clusterName string, key string) (interface{}, bool, error) - - // Returns the items stored under the given key in all clusters. - GetFromAllClusters(key string) ([]FederatedObject, error) - - // Checks whether stores for all clusters form the lists (and only these) are there and - // are synced. This is only a basic check whether the data inside of the store is usable. - // It is not a full synchronization/locking mechanism it only tries to ensure that out-of-sync - // issues occur less often. All users of the interface should assume - // that there may be significant delays in content updates of all kinds and write their - // code that it doesn't break if something is slightly out-of-sync. - ClustersSynced(clusters []*federationapi.Cluster) bool -} - -// An interface to access federation members and clients. -type FederationView interface { - // GetClientsetForCluster returns a clientset for the cluster, if present. - GetClientsetForCluster(clusterName string) (kubeclientset.Interface, error) - - // GetUnreadyClusters returns a list of all clusters that are not ready yet. - GetUnreadyClusters() ([]*federationapi.Cluster, error) - - // GetReadyClusters returns all clusters for which the sub-informers are run. - GetReadyClusters() ([]*federationapi.Cluster, error) - - // GetReadyCluster returns the cluster with the given name, if found. - GetReadyCluster(name string) (*federationapi.Cluster, bool, error) - - // ClustersSynced returns true if the view is synced (for the first time). - ClustersSynced() bool -} - -// A structure that combines an informer running against federated api server and listening for cluster updates -// with multiple Kubernetes API informers (called target informers) running against federation members. Whenever a new -// cluster is added to the federation an informer is created for it using TargetInformerFactory. Informers are stopped -// when a cluster is either put offline of deleted. It is assumed that some controller keeps an eye on the cluster list -// and thus the clusters in ETCD are up to date. -type FederatedInformer interface { - FederationView - - // Returns a store created over all stores from target informers. - GetTargetStore() FederatedReadOnlyStore - - // Starts all the processes. - Start() - - // Stops all the processes inside the informer. - Stop() -} - -// FederatedInformer with extra method for setting fake clients. -type FederatedInformerForTestOnly interface { - FederatedInformer - - SetClientFactory(func(*federationapi.Cluster) (kubeclientset.Interface, error)) -} - -// A function that should be used to create an informer on the target object. Store should use -// cache.DeletionHandlingMetaNamespaceKeyFunc as a keying function. -type TargetInformerFactory func(*federationapi.Cluster, kubeclientset.Interface) (cache.Store, cache.Controller) - -// A structure with cluster lifecycle handler functions. Cluster is available (and ClusterAvailable is fired) -// when it is created in federated etcd and ready. Cluster becomes unavailable (and ClusterUnavailable is fired) -// when it is either deleted or becomes not ready. When cluster spec (IP)is modified both ClusterAvailable -// and ClusterUnavailable are fired. -type ClusterLifecycleHandlerFuncs struct { - // Fired when the cluster becomes available. - ClusterAvailable func(*federationapi.Cluster) - // Fired when the cluster becomes unavailable. The second arg contains data that was present - // in the cluster before deletion. - ClusterUnavailable func(*federationapi.Cluster, []interface{}) -} - -// Builds a FederatedInformer for the given federation client and factory. -func NewFederatedInformer( - federationClient federationclientset.Interface, - targetInformerFactory TargetInformerFactory, - clusterLifecycle *ClusterLifecycleHandlerFuncs) FederatedInformer { - - federatedInformer := &federatedInformerImpl{ - targetInformerFactory: targetInformerFactory, - clientFactory: func(cluster *federationapi.Cluster) (kubeclientset.Interface, error) { - clusterConfig, err := BuildClusterConfig(cluster) - if err == nil && clusterConfig != nil { - clientset := kubeclientset.NewForConfigOrDie(restclient.AddUserAgent(clusterConfig, userAgentName)) - return clientset, nil - } - return nil, err - }, - targetInformers: make(map[string]informer), - } - - getClusterData := func(name string) []interface{} { - data, err := federatedInformer.GetTargetStore().ListFromCluster(name) - if err != nil { - glog.Errorf("Failed to list %s content: %v", name, err) - return make([]interface{}, 0) - } - return data - } - - federatedInformer.clusterInformer.store, federatedInformer.clusterInformer.controller = cache.NewInformer( - &cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (pkgruntime.Object, error) { - return federationClient.Federation().Clusters().List(options) - }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return federationClient.Federation().Clusters().Watch(options) - }, - }, - &federationapi.Cluster{}, - clusterSyncPeriod, - cache.ResourceEventHandlerFuncs{ - DeleteFunc: func(old interface{}) { - oldCluster, ok := old.(*federationapi.Cluster) - if ok { - var data []interface{} - if clusterLifecycle.ClusterUnavailable != nil { - data = getClusterData(oldCluster.Name) - } - federatedInformer.deleteCluster(oldCluster) - if clusterLifecycle.ClusterUnavailable != nil { - clusterLifecycle.ClusterUnavailable(oldCluster, data) - } - } - }, - AddFunc: func(cur interface{}) { - curCluster, ok := cur.(*federationapi.Cluster) - if ok && isClusterReady(curCluster) { - federatedInformer.addCluster(curCluster) - if clusterLifecycle.ClusterAvailable != nil { - clusterLifecycle.ClusterAvailable(curCluster) - } - } else { - glog.Errorf("Cluster %v not added. Not of correct type, or cluster not ready.", cur) - } - }, - UpdateFunc: func(old, cur interface{}) { - oldCluster, ok := old.(*federationapi.Cluster) - if !ok { - glog.Errorf("Internal error: Cluster %v not updated. Old cluster not of correct type.", old) - return - } - curCluster, ok := cur.(*federationapi.Cluster) - if !ok { - glog.Errorf("Internal error: Cluster %v not updated. New cluster not of correct type.", cur) - return - } - if isClusterReady(oldCluster) != isClusterReady(curCluster) || !reflect.DeepEqual(oldCluster.Spec, curCluster.Spec) || !reflect.DeepEqual(oldCluster.ObjectMeta.Annotations, curCluster.ObjectMeta.Annotations) { - var data []interface{} - if clusterLifecycle.ClusterUnavailable != nil { - data = getClusterData(oldCluster.Name) - } - federatedInformer.deleteCluster(oldCluster) - if clusterLifecycle.ClusterUnavailable != nil { - clusterLifecycle.ClusterUnavailable(oldCluster, data) - } - - if isClusterReady(curCluster) { - federatedInformer.addCluster(curCluster) - if clusterLifecycle.ClusterAvailable != nil { - clusterLifecycle.ClusterAvailable(curCluster) - } - } - } else { - glog.V(4).Infof("Cluster %v not updated to %v as ready status and specs are identical", oldCluster, curCluster) - } - }, - }, - ) - return federatedInformer -} - -func isClusterReady(cluster *federationapi.Cluster) bool { - for _, condition := range cluster.Status.Conditions { - if condition.Type == federationapi.ClusterReady { - if condition.Status == apiv1.ConditionTrue { - return true - } - } - } - return false -} - -type informer struct { - controller cache.Controller - store cache.Store - stopChan chan struct{} -} - -type federatedInformerImpl struct { - sync.Mutex - - // Informer on federated clusters. - clusterInformer informer - - // Target informers factory - targetInformerFactory TargetInformerFactory - - // Structures returned by targetInformerFactory - targetInformers map[string]informer - - // A function to build clients. - clientFactory func(*federationapi.Cluster) (kubeclientset.Interface, error) -} - -// *federatedInformerImpl implements FederatedInformer interface. -var _ FederatedInformer = &federatedInformerImpl{} - -type federatedStoreImpl struct { - federatedInformer *federatedInformerImpl -} - -func (f *federatedInformerImpl) Stop() { - glog.V(4).Infof("Stopping federated informer.") - f.Lock() - defer f.Unlock() - - glog.V(4).Infof("... Closing cluster informer channel.") - close(f.clusterInformer.stopChan) - for key, informer := range f.targetInformers { - glog.V(4).Infof("... Closing informer channel for %q.", key) - close(informer.stopChan) - // Remove each informer after it has been stopped to prevent - // subsequent cluster deletion from attempting to double close - // an informer's stop channel. - delete(f.targetInformers, key) - } -} - -func (f *federatedInformerImpl) Start() { - f.Lock() - defer f.Unlock() - - f.clusterInformer.stopChan = make(chan struct{}) - go f.clusterInformer.controller.Run(f.clusterInformer.stopChan) -} - -func (f *federatedInformerImpl) SetClientFactory(clientFactory func(*federationapi.Cluster) (kubeclientset.Interface, error)) { - f.Lock() - defer f.Unlock() - - f.clientFactory = clientFactory -} - -// GetClientsetForCluster returns a clientset for the cluster, if present. -func (f *federatedInformerImpl) GetClientsetForCluster(clusterName string) (kubeclientset.Interface, error) { - f.Lock() - defer f.Unlock() - return f.getClientsetForClusterUnlocked(clusterName) -} - -func (f *federatedInformerImpl) getClientsetForClusterUnlocked(clusterName string) (kubeclientset.Interface, error) { - // No locking needed. Will happen in f.GetCluster. - glog.V(4).Infof("Getting clientset for cluster %q", clusterName) - if cluster, found, err := f.getReadyClusterUnlocked(clusterName); found && err == nil { - glog.V(4).Infof("Got clientset for cluster %q", clusterName) - return f.clientFactory(cluster) - } else { - if err != nil { - return nil, err - } - } - return nil, fmt.Errorf("cluster %q not found", clusterName) -} - -func (f *federatedInformerImpl) GetUnreadyClusters() ([]*federationapi.Cluster, error) { - f.Lock() - defer f.Unlock() - - items := f.clusterInformer.store.List() - result := make([]*federationapi.Cluster, 0, len(items)) - for _, item := range items { - if cluster, ok := item.(*federationapi.Cluster); ok { - if !isClusterReady(cluster) { - result = append(result, cluster) - } - } else { - return nil, fmt.Errorf("wrong data in FederatedInformerImpl cluster store: %v", item) - } - } - return result, nil -} - -// GetReadyClusters returns all clusters for which the sub-informers are run. -func (f *federatedInformerImpl) GetReadyClusters() ([]*federationapi.Cluster, error) { - f.Lock() - defer f.Unlock() - - items := f.clusterInformer.store.List() - result := make([]*federationapi.Cluster, 0, len(items)) - for _, item := range items { - if cluster, ok := item.(*federationapi.Cluster); ok { - if isClusterReady(cluster) { - result = append(result, cluster) - } - } else { - return nil, fmt.Errorf("wrong data in FederatedInformerImpl cluster store: %v", item) - } - } - return result, nil -} - -// GetCluster returns the cluster with the given name, if found. -func (f *federatedInformerImpl) GetReadyCluster(name string) (*federationapi.Cluster, bool, error) { - f.Lock() - defer f.Unlock() - return f.getReadyClusterUnlocked(name) -} - -func (f *federatedInformerImpl) getReadyClusterUnlocked(name string) (*federationapi.Cluster, bool, error) { - if obj, exist, err := f.clusterInformer.store.GetByKey(name); exist && err == nil { - if cluster, ok := obj.(*federationapi.Cluster); ok { - if isClusterReady(cluster) { - return cluster, true, nil - } - return nil, false, nil - - } - return nil, false, fmt.Errorf("wrong data in FederatedInformerImpl cluster store: %v", obj) - - } else { - return nil, false, err - } -} - -// Synced returns true if the view is synced (for the first time) -func (f *federatedInformerImpl) ClustersSynced() bool { - return f.clusterInformer.controller.HasSynced() -} - -// Adds the given cluster to federated informer. -func (f *federatedInformerImpl) addCluster(cluster *federationapi.Cluster) { - f.Lock() - defer f.Unlock() - name := cluster.Name - if client, err := f.getClientsetForClusterUnlocked(name); err == nil { - store, controller := f.targetInformerFactory(cluster, client) - targetInformer := informer{ - controller: controller, - store: store, - stopChan: make(chan struct{}), - } - f.targetInformers[name] = targetInformer - go targetInformer.controller.Run(targetInformer.stopChan) - } else { - // TODO: create also an event for cluster. - glog.Errorf("Failed to create a client for cluster: %v", err) - } -} - -// Removes the cluster from federated informer. -func (f *federatedInformerImpl) deleteCluster(cluster *federationapi.Cluster) { - f.Lock() - defer f.Unlock() - name := cluster.Name - if targetInformer, found := f.targetInformers[name]; found { - close(targetInformer.stopChan) - } - delete(f.targetInformers, name) -} - -// Returns a store created over all stores from target informers. -func (f *federatedInformerImpl) GetTargetStore() FederatedReadOnlyStore { - return &federatedStoreImpl{ - federatedInformer: f, - } -} - -// Returns all items in the store. -func (fs *federatedStoreImpl) List() ([]FederatedObject, error) { - fs.federatedInformer.Lock() - defer fs.federatedInformer.Unlock() - - result := make([]FederatedObject, 0) - for clusterName, targetInformer := range fs.federatedInformer.targetInformers { - for _, value := range targetInformer.store.List() { - result = append(result, FederatedObject{ClusterName: clusterName, Object: value}) - } - } - return result, nil -} - -// Returns all items in the given cluster. -func (fs *federatedStoreImpl) ListFromCluster(clusterName string) ([]interface{}, error) { - fs.federatedInformer.Lock() - defer fs.federatedInformer.Unlock() - - result := make([]interface{}, 0) - if targetInformer, found := fs.federatedInformer.targetInformers[clusterName]; found { - values := targetInformer.store.List() - result = append(result, values...) - } - return result, nil -} - -// GetByKey returns the item stored under the given key in the specified cluster (if exist). -func (fs *federatedStoreImpl) GetByKey(clusterName string, key string) (interface{}, bool, error) { - fs.federatedInformer.Lock() - defer fs.federatedInformer.Unlock() - if targetInformer, found := fs.federatedInformer.targetInformers[clusterName]; found { - return targetInformer.store.GetByKey(key) - } - return nil, false, nil -} - -// Returns the items stored under the given key in all clusters. -func (fs *federatedStoreImpl) GetFromAllClusters(key string) ([]FederatedObject, error) { - fs.federatedInformer.Lock() - defer fs.federatedInformer.Unlock() - - result := make([]FederatedObject, 0) - for clusterName, targetInformer := range fs.federatedInformer.targetInformers { - value, exist, err := targetInformer.store.GetByKey(key) - if err != nil { - return nil, err - } - if exist { - result = append(result, FederatedObject{ClusterName: clusterName, Object: value}) - } - } - return result, nil -} - -// GetKeyFor returns the key under which the item would be put in the store. -func (fs *federatedStoreImpl) GetKeyFor(item interface{}) string { - // TODO: support other keying functions. - key, _ := cache.DeletionHandlingMetaNamespaceKeyFunc(item) - return key -} - -// Checks whether stores for all clusters form the lists (and only these) are there and -// are synced. -func (fs *federatedStoreImpl) ClustersSynced(clusters []*federationapi.Cluster) bool { - - // Get the list of informers to check under a lock and check it outside. - okSoFar, informersToCheck := func() (bool, []informer) { - fs.federatedInformer.Lock() - defer fs.federatedInformer.Unlock() - - if len(fs.federatedInformer.targetInformers) != len(clusters) { - return false, []informer{} - } - informersToCheck := make([]informer, 0, len(clusters)) - for _, cluster := range clusters { - if targetInformer, found := fs.federatedInformer.targetInformers[cluster.Name]; found { - informersToCheck = append(informersToCheck, targetInformer) - } else { - return false, []informer{} - } - } - return true, informersToCheck - }() - - if !okSoFar { - return false - } - for _, informerToCheck := range informersToCheck { - if !informerToCheck.controller.HasSynced() { - return false - } - } - return true -} diff --git a/federation/pkg/federation-controller/util/federated_informer_test.go b/federation/pkg/federation-controller/util/federated_informer_test.go deleted file mode 100644 index ce4ab70a1d9..00000000000 --- a/federation/pkg/federation-controller/util/federated_informer_test.go +++ /dev/null @@ -1,150 +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 util - -import ( - "testing" - "time" - - apiv1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/watch" - kubeclientset "k8s.io/client-go/kubernetes" - fakekubeclientset "k8s.io/client-go/kubernetes/fake" - core "k8s.io/client-go/testing" - "k8s.io/client-go/tools/cache" - federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" - fakefederationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/fake" - - "github.com/stretchr/testify/assert" -) - -// Basic test for Federated Informer. Checks whether the subinformer are added and deleted -// when the corresponding cluster entries appear and disappear from etcd. -func TestFederatedInformer(t *testing.T) { - fakeFederationClient := &fakefederationclientset.Clientset{} - - // Add a single cluster to federation and remove it when needed. - cluster := federationapi.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "mycluster", - }, - Status: federationapi.ClusterStatus{ - Conditions: []federationapi.ClusterCondition{ - {Type: federationapi.ClusterReady, Status: apiv1.ConditionTrue}, - }, - }, - } - fakeFederationClient.AddReactor("list", "clusters", func(action core.Action) (bool, runtime.Object, error) { - return true, &federationapi.ClusterList{Items: []federationapi.Cluster{cluster}}, nil - }) - deleteChan := make(chan struct{}) - fakeFederationClient.AddWatchReactor("clusters", func(action core.Action) (bool, watch.Interface, error) { - fakeWatch := watch.NewFake() - go func() { - <-deleteChan - fakeWatch.Delete(&cluster) - }() - return true, fakeWatch, nil - }) - - fakeKubeClient := &fakekubeclientset.Clientset{} - // There is a single service ns1/s1 in cluster mycluster. - service := apiv1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "ns1", - Name: "s1", - }, - } - fakeKubeClient.AddReactor("list", "services", func(action core.Action) (bool, runtime.Object, error) { - return true, &apiv1.ServiceList{Items: []apiv1.Service{service}}, nil - }) - fakeKubeClient.AddWatchReactor("services", func(action core.Action) (bool, watch.Interface, error) { - return true, watch.NewFake(), nil - }) - - targetInformerFactory := func(cluster *federationapi.Cluster, clientset kubeclientset.Interface) (cache.Store, cache.Controller) { - return cache.NewInformer( - &cache.ListWatch{ - ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { - return clientset.Core().Services(metav1.NamespaceAll).List(options) - }, - WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return clientset.Core().Services(metav1.NamespaceAll).Watch(options) - }, - }, - &apiv1.Service{}, - 10*time.Second, - cache.ResourceEventHandlerFuncs{}) - } - - addedClusters := make(chan string, 1) - deletedClusters := make(chan string, 1) - lifecycle := ClusterLifecycleHandlerFuncs{ - ClusterAvailable: func(cluster *federationapi.Cluster) { - addedClusters <- cluster.Name - close(addedClusters) - }, - ClusterUnavailable: func(cluster *federationapi.Cluster, _ []interface{}) { - deletedClusters <- cluster.Name - close(deletedClusters) - }, - } - - informer := NewFederatedInformer(fakeFederationClient, targetInformerFactory, &lifecycle).(*federatedInformerImpl) - informer.clientFactory = func(cluster *federationapi.Cluster) (kubeclientset.Interface, error) { - return fakeKubeClient, nil - } - assert.NotNil(t, informer) - informer.Start() - - // Wait until mycluster is synced. - for !informer.GetTargetStore().ClustersSynced([]*federationapi.Cluster{&cluster}) { - time.Sleep(time.Millisecond * 100) - } - readyClusters, err := informer.GetReadyClusters() - assert.NoError(t, err) - assert.Contains(t, readyClusters, &cluster) - serviceList, err := informer.GetTargetStore().List() - assert.NoError(t, err) - federatedService := FederatedObject{ClusterName: "mycluster", Object: &service} - assert.Contains(t, serviceList, federatedService) - service1, found, err := informer.GetTargetStore().GetByKey("mycluster", "ns1/s1") - assert.NoError(t, err) - assert.True(t, found) - assert.EqualValues(t, &service, service1) - assert.Equal(t, "mycluster", <-addedClusters) - - // All checked, lets delete the cluster. - deleteChan <- struct{}{} - for !informer.GetTargetStore().ClustersSynced([]*federationapi.Cluster{}) { - time.Sleep(time.Millisecond * 100) - } - readyClusters, err = informer.GetReadyClusters() - assert.NoError(t, err) - assert.Empty(t, readyClusters) - - serviceList, err = informer.GetTargetStore().List() - assert.NoError(t, err) - assert.Empty(t, serviceList) - - assert.Equal(t, "mycluster", <-deletedClusters) - - // Test complete. - informer.Stop() -} diff --git a/federation/pkg/federation-controller/util/federated_updater.go b/federation/pkg/federation-controller/util/federated_updater.go deleted file mode 100644 index af37580465e..00000000000 --- a/federation/pkg/federation-controller/util/federated_updater.go +++ /dev/null @@ -1,157 +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 util - -import ( - "fmt" - "strings" - "time" - - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - pkgruntime "k8s.io/apimachinery/pkg/runtime" - kubeclientset "k8s.io/client-go/kubernetes" - "k8s.io/client-go/tools/record" - "k8s.io/kubernetes/pkg/api" -) - -// Type of the operation that can be executed in Federated. -type FederatedOperationType string - -const ( - OperationTypeAdd = "add" - OperationTypeUpdate = "update" - OperationTypeDelete = "delete" -) - -// FederatedOperation definition contains type (add/update/delete) and the object itself. -type FederatedOperation struct { - Type FederatedOperationType - ClusterName string - Obj pkgruntime.Object - Key string -} - -// A helper that executes the given set of updates on federation, in parallel. -type FederatedUpdater interface { - // Executes the given set of operations. - Update([]FederatedOperation) error -} - -// A function that executes some operation using the passed client and object. -type FederatedOperationHandler func(kubeclientset.Interface, pkgruntime.Object) error - -type federatedUpdaterImpl struct { - federation FederationView - - kind string - - timeout time.Duration - - eventRecorder record.EventRecorder - - addFunction FederatedOperationHandler - updateFunction FederatedOperationHandler - deleteFunction FederatedOperationHandler -} - -func NewFederatedUpdater(federation FederationView, kind string, timeout time.Duration, recorder record.EventRecorder, add, update, del FederatedOperationHandler) FederatedUpdater { - return &federatedUpdaterImpl{ - federation: federation, - kind: kind, - timeout: timeout, - eventRecorder: recorder, - addFunction: add, - updateFunction: update, - deleteFunction: del, - } -} - -func (fu *federatedUpdaterImpl) recordEvent(obj runtime.Object, eventType, eventVerb string, args ...interface{}) { - messageFmt := eventVerb + " %s %q in cluster %s" - fu.eventRecorder.Eventf(obj, api.EventTypeNormal, eventType, messageFmt, args...) -} - -// Update executes the given set of operations within the timeout specified for -// the instance. Timeout is best-effort. There is no guarantee that the -// underlying operations are stopped when it is reached. However the function -// will return after the timeout with a non-nil error. -func (fu *federatedUpdaterImpl) Update(ops []FederatedOperation) error { - done := make(chan error, len(ops)) - for _, op := range ops { - go func(op FederatedOperation) { - clusterName := op.ClusterName - - // TODO: Ensure that the clientset has reasonable timeout. - clientset, err := fu.federation.GetClientsetForCluster(clusterName) - if err != nil { - done <- err - return - } - - eventArgs := []interface{}{fu.kind, op.Key, clusterName} - baseEventType := fmt.Sprintf("%s", op.Type) - eventType := fmt.Sprintf("%sInCluster", strings.Title(baseEventType)) - - switch op.Type { - case OperationTypeAdd: - // TODO s+OperationTypeAdd+OperationTypeCreate+ - baseEventType = "create" - eventType := "CreateInCluster" - - fu.recordEvent(op.Obj, eventType, "Creating", eventArgs...) - err = fu.addFunction(clientset, op.Obj) - case OperationTypeUpdate: - fu.recordEvent(op.Obj, eventType, "Updating", eventArgs...) - err = fu.updateFunction(clientset, op.Obj) - case OperationTypeDelete: - fu.recordEvent(op.Obj, eventType, "Deleting", eventArgs...) - err = fu.deleteFunction(clientset, op.Obj) - // IsNotFound error is fine since that means the object is deleted already. - if errors.IsNotFound(err) { - err = nil - } - } - - if err != nil { - eventType := eventType + "Failed" - messageFmt := "Failed to " + baseEventType + " %s %q in cluster %s: %v" - eventArgs = append(eventArgs, err) - fu.eventRecorder.Eventf(op.Obj, api.EventTypeWarning, eventType, messageFmt, eventArgs...) - } - - done <- err - }(op) - } - start := time.Now() - for i := 0; i < len(ops); i++ { - now := time.Now() - if !now.Before(start.Add(fu.timeout)) { - return fmt.Errorf("failed to finish all operations in %v", fu.timeout) - } - select { - case err := <-done: - if err != nil { - return err - } - case <-time.After(start.Add(fu.timeout).Sub(now)): - return fmt.Errorf("failed to finish all operations in %v", fu.timeout) - } - } - // All operations finished in time. - return nil -} diff --git a/federation/pkg/federation-controller/util/federated_updater_test.go b/federation/pkg/federation-controller/util/federated_updater_test.go deleted file mode 100644 index 242cc906dda..00000000000 --- a/federation/pkg/federation-controller/util/federated_updater_test.go +++ /dev/null @@ -1,157 +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 util - -import ( - "fmt" - "testing" - "time" - - apiv1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - pkgruntime "k8s.io/apimachinery/pkg/runtime" - kubeclientset "k8s.io/client-go/kubernetes" - fakekubeclientset "k8s.io/client-go/kubernetes/fake" - federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" - - "github.com/stretchr/testify/assert" -) - -// Fake federation view. -type fakeFederationView struct { -} - -// Verify that fakeFederationView implements FederationView interface -var _ FederationView = &fakeFederationView{} - -func (f *fakeFederationView) GetClientsetForCluster(clusterName string) (kubeclientset.Interface, error) { - return &fakekubeclientset.Clientset{}, nil -} - -func (f *fakeFederationView) GetReadyClusters() ([]*federationapi.Cluster, error) { - return []*federationapi.Cluster{}, nil -} - -func (f *fakeFederationView) GetUnreadyClusters() ([]*federationapi.Cluster, error) { - return []*federationapi.Cluster{}, nil -} - -func (f *fakeFederationView) GetReadyCluster(name string) (*federationapi.Cluster, bool, error) { - return nil, false, nil -} - -func (f *fakeFederationView) ClustersSynced() bool { - return true -} - -type fakeEventRecorder struct{} - -func (f *fakeEventRecorder) Event(object pkgruntime.Object, eventtype, reason, message string) {} -func (f *fakeEventRecorder) Eventf(object pkgruntime.Object, eventtype, reason, messageFmt string, args ...interface{}) { -} -func (f *fakeEventRecorder) PastEventf(object pkgruntime.Object, timestamp metav1.Time, eventtype, reason, messageFmt string, args ...interface{}) { -} - -func TestFederatedUpdaterOK(t *testing.T) { - addChan := make(chan string, 5) - updateChan := make(chan string, 5) - - updater := NewFederatedUpdater(&fakeFederationView{}, "foo", time.Minute, &fakeEventRecorder{}, - func(_ kubeclientset.Interface, obj pkgruntime.Object) error { - service := obj.(*apiv1.Service) - addChan <- service.Name - return nil - }, - func(_ kubeclientset.Interface, obj pkgruntime.Object) error { - service := obj.(*apiv1.Service) - updateChan <- service.Name - return nil - }, - noop) - - err := updater.Update([]FederatedOperation{ - { - Type: OperationTypeAdd, - Obj: makeService("A", "s1"), - }, - { - Type: OperationTypeUpdate, - Obj: makeService("B", "s2"), - }, - }) - assert.NoError(t, err) - add := <-addChan - update := <-updateChan - assert.Equal(t, "s1", add) - assert.Equal(t, "s2", update) -} - -func TestFederatedUpdaterError(t *testing.T) { - updater := NewFederatedUpdater(&fakeFederationView{}, "foo", time.Minute, &fakeEventRecorder{}, - func(_ kubeclientset.Interface, obj pkgruntime.Object) error { - return fmt.Errorf("boom") - }, noop, noop) - - err := updater.Update([]FederatedOperation{ - { - Type: OperationTypeAdd, - Obj: makeService("A", "s1"), - }, - { - Type: OperationTypeUpdate, - Obj: makeService("B", "s1"), - }, - }) - assert.Error(t, err) -} - -func TestFederatedUpdaterTimeout(t *testing.T) { - start := time.Now() - updater := NewFederatedUpdater(&fakeFederationView{}, "foo", time.Second, &fakeEventRecorder{}, - func(_ kubeclientset.Interface, obj pkgruntime.Object) error { - time.Sleep(time.Minute) - return nil - }, - noop, noop) - - err := updater.Update([]FederatedOperation{ - { - Type: OperationTypeAdd, - Obj: makeService("A", "s1"), - }, - { - Type: OperationTypeUpdate, - Obj: makeService("B", "s1"), - }, - }) - end := time.Now() - assert.Error(t, err) - assert.True(t, start.Add(10*time.Second).After(end)) -} - -func makeService(cluster, name string) *apiv1.Service { - return &apiv1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "ns1", - Name: name, - }, - } -} - -func noop(_ kubeclientset.Interface, _ pkgruntime.Object) error { - return nil -} diff --git a/federation/pkg/federation-controller/util/finalizers/BUILD b/federation/pkg/federation-controller/util/finalizers/BUILD deleted file mode 100644 index ec6a91ef73a..00000000000 --- a/federation/pkg/federation-controller/util/finalizers/BUILD +++ /dev/null @@ -1,45 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = ["finalizers.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/util/finalizers", - deps = [ - "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) - -go_test( - name = "go_default_test", - srcs = ["finalizers_test.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/util/finalizers", - library = ":go_default_library", - deps = [ - "//vendor/github.com/stretchr/testify/assert:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - ], -) diff --git a/federation/pkg/federation-controller/util/finalizers/finalizers.go b/federation/pkg/federation-controller/util/finalizers/finalizers.go deleted file mode 100644 index c1644887fc6..00000000000 --- a/federation/pkg/federation-controller/util/finalizers/finalizers.go +++ /dev/null @@ -1,66 +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. -*/ - -// Helper functions for manipulating finalizers. -package finalizers - -import ( - meta "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/sets" -) - -// HasFinalizer returns true if the given object has the given finalizer in its ObjectMeta. -func HasFinalizer(obj runtime.Object, finalizer string) (bool, error) { - accessor, err := meta.Accessor(obj) - if err != nil { - return false, err - } - finalizers := sets.NewString(accessor.GetFinalizers()...) - return finalizers.Has(finalizer), nil -} - -// AddFinalizers adds the given finalizers to the given objects ObjectMeta. -// Returns true if the object was updated. -func AddFinalizers(obj runtime.Object, newFinalizers sets.String) (bool, error) { - accessor, err := meta.Accessor(obj) - if err != nil { - return false, err - } - oldFinalizers := sets.NewString(accessor.GetFinalizers()...) - if oldFinalizers.IsSuperset(newFinalizers) { - return false, nil - } - allFinalizers := oldFinalizers.Union(newFinalizers) - accessor.SetFinalizers(allFinalizers.List()) - return true, nil -} - -// RemoveFinalizers removes the given finalizers from the given objects ObjectMeta. -// Returns true if the object was updated. -func RemoveFinalizers(obj runtime.Object, finalizers sets.String) (bool, error) { - accessor, err := meta.Accessor(obj) - if err != nil { - return false, err - } - oldFinalizers := sets.NewString(accessor.GetFinalizers()...) - if oldFinalizers.Intersection(finalizers).Len() == 0 { - return false, nil - } - newFinalizers := oldFinalizers.Difference(finalizers) - accessor.SetFinalizers(newFinalizers.List()) - return true, nil -} diff --git a/federation/pkg/federation-controller/util/finalizers/finalizers_test.go b/federation/pkg/federation-controller/util/finalizers/finalizers_test.go deleted file mode 100644 index 7783c8f972b..00000000000 --- a/federation/pkg/federation-controller/util/finalizers/finalizers_test.go +++ /dev/null @@ -1,171 +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 finalizers - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - "k8s.io/api/core/v1" - meta "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/sets" -) - -func newObj(finalizers []string) runtime.Object { - pod := v1.Pod{} - pod.ObjectMeta.Finalizers = finalizers - return &pod -} - -func TestHasFinalizer(t *testing.T) { - testCases := []struct { - obj runtime.Object - finalizer string - result bool - }{ - { - newObj([]string{}), - "", - false, - }, - { - newObj([]string{}), - "someFinalizer", - false, - }, - { - newObj([]string{"someFinalizer"}), - "", - false, - }, - { - newObj([]string{"someFinalizer"}), - "anotherFinalizer", - false, - }, - { - newObj([]string{"someFinalizer"}), - "someFinalizer", - true, - }, - { - newObj([]string{"anotherFinalizer", "someFinalizer"}), - "someFinalizer", - true, - }, - } - for index, test := range testCases { - hasFinalizer, _ := HasFinalizer(test.obj, test.finalizer) - assert.Equal(t, hasFinalizer, test.result, fmt.Sprintf("Test case %d failed. Expected: %v, actual: %v", index, test.result, hasFinalizer)) - } -} - -func TestAddFinalizers(t *testing.T) { - testCases := []struct { - obj runtime.Object - finalizers sets.String - isUpdated bool - newFinalizers []string - }{ - { - newObj([]string{}), - sets.NewString(), - false, - []string{}, - }, - { - newObj([]string{}), - sets.NewString("someFinalizer"), - true, - []string{"someFinalizer"}, - }, - { - newObj([]string{"someFinalizer"}), - sets.NewString(), - false, - []string{"someFinalizer"}, - }, - { - newObj([]string{"someFinalizer"}), - sets.NewString("anotherFinalizer"), - true, - []string{"anotherFinalizer", "someFinalizer"}, - }, - { - newObj([]string{"someFinalizer"}), - sets.NewString("someFinalizer"), - false, - []string{"someFinalizer"}, - }, - } - for index, test := range testCases { - isUpdated, _ := AddFinalizers(test.obj, test.finalizers) - assert.Equal(t, isUpdated, test.isUpdated, fmt.Sprintf("Test case %d failed. Expected isUpdated: %v, actual: %v", index, test.isUpdated, isUpdated)) - accessor, _ := meta.Accessor(test.obj) - newFinalizers := accessor.GetFinalizers() - assert.Equal(t, test.newFinalizers, newFinalizers, fmt.Sprintf("Test case %d failed. Expected finalizers: %v, actual: %v", index, test.newFinalizers, newFinalizers)) - } -} - -func TestRemoveFinalizers(t *testing.T) { - testCases := []struct { - obj runtime.Object - finalizers sets.String - isUpdated bool - newFinalizers []string - }{ - { - newObj([]string{}), - sets.NewString(), - false, - []string{}, - }, - { - newObj([]string{}), - sets.NewString("someFinalizer"), - false, - []string{}, - }, - { - newObj([]string{"someFinalizer"}), - sets.NewString(), - false, - []string{"someFinalizer"}, - }, - { - newObj([]string{"someFinalizer"}), - sets.NewString("anotherFinalizer"), - false, - []string{"someFinalizer"}, - }, - { - newObj([]string{"someFinalizer", "anotherFinalizer"}), - sets.NewString("someFinalizer"), - true, - []string{"anotherFinalizer"}, - }, - } - for index, test := range testCases { - isUpdated, _ := RemoveFinalizers(test.obj, test.finalizers) - assert.Equal(t, isUpdated, test.isUpdated, fmt.Sprintf("Test case %d failed. Expected isUpdated: %v, actual: %v", index, test.isUpdated, isUpdated)) - accessor, _ := meta.Accessor(test.obj) - newFinalizers := accessor.GetFinalizers() - assert.Equal(t, test.newFinalizers, newFinalizers, fmt.Sprintf("Test case %d failed. Expected finalizers: %v, actual: %v", index, test.newFinalizers, newFinalizers)) - } -} diff --git a/federation/pkg/federation-controller/util/handlers.go b/federation/pkg/federation-controller/util/handlers.go deleted file mode 100644 index 406d5ca3161..00000000000 --- a/federation/pkg/federation-controller/util/handlers.go +++ /dev/null @@ -1,112 +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 util - -import ( - "fmt" - "reflect" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - pkgruntime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/tools/cache" -) - -// Returns cache.ResourceEventHandlerFuncs that trigger the given function -// on all object changes. -func NewTriggerOnAllChanges(triggerFunc func(pkgruntime.Object)) *cache.ResourceEventHandlerFuncs { - return &cache.ResourceEventHandlerFuncs{ - DeleteFunc: func(old interface{}) { - oldObj := old.(pkgruntime.Object) - triggerFunc(oldObj) - }, - AddFunc: func(cur interface{}) { - curObj := cur.(pkgruntime.Object) - triggerFunc(curObj) - }, - UpdateFunc: func(old, cur interface{}) { - curObj := cur.(pkgruntime.Object) - if !reflect.DeepEqual(old, cur) { - triggerFunc(curObj) - } - }, - } -} - -// Returns cache.ResourceEventHandlerFuncs that trigger the given function -// on object add and delete as well as spec/object meta on update. -func NewTriggerOnMetaAndSpecChanges(triggerFunc func(pkgruntime.Object)) *cache.ResourceEventHandlerFuncs { - getFieldOrPanic := func(obj interface{}, fieldName string) interface{} { - val := reflect.ValueOf(obj).Elem().FieldByName(fieldName) - if val.IsValid() { - return val.Interface() - } else { - panic(fmt.Errorf("field not found: %s", fieldName)) - } - } - return &cache.ResourceEventHandlerFuncs{ - DeleteFunc: func(old interface{}) { - oldObj := old.(pkgruntime.Object) - triggerFunc(oldObj) - }, - AddFunc: func(cur interface{}) { - curObj := cur.(pkgruntime.Object) - triggerFunc(curObj) - }, - UpdateFunc: func(old, cur interface{}) { - curObj := cur.(pkgruntime.Object) - oldMeta := getFieldOrPanic(old, "ObjectMeta").(metav1.ObjectMeta) - curMeta := getFieldOrPanic(cur, "ObjectMeta").(metav1.ObjectMeta) - if !ObjectMetaEquivalent(oldMeta, curMeta) || - !reflect.DeepEqual(oldMeta.DeletionTimestamp, curMeta.DeletionTimestamp) || - !reflect.DeepEqual(getFieldOrPanic(old, "Spec"), getFieldOrPanic(cur, "Spec")) { - triggerFunc(curObj) - } - }, - } -} - -// Returns cache.ResourceEventHandlerFuncs that trigger the given function -// on object add/delete or ObjectMeta or given field is updated. -func NewTriggerOnMetaAndFieldChanges(field string, triggerFunc func(pkgruntime.Object)) *cache.ResourceEventHandlerFuncs { - getFieldOrPanic := func(obj interface{}, fieldName string) interface{} { - val := reflect.ValueOf(obj).Elem().FieldByName(fieldName) - if val.IsValid() { - return val.Interface() - } else { - panic(fmt.Errorf("field not found: %s", fieldName)) - } - } - return &cache.ResourceEventHandlerFuncs{ - DeleteFunc: func(old interface{}) { - oldObj := old.(pkgruntime.Object) - triggerFunc(oldObj) - }, - AddFunc: func(cur interface{}) { - curObj := cur.(pkgruntime.Object) - triggerFunc(curObj) - }, - UpdateFunc: func(old, cur interface{}) { - curObj := cur.(pkgruntime.Object) - oldMeta := getFieldOrPanic(old, "ObjectMeta").(metav1.ObjectMeta) - curMeta := getFieldOrPanic(cur, "ObjectMeta").(metav1.ObjectMeta) - if !ObjectMetaEquivalent(oldMeta, curMeta) || - !reflect.DeepEqual(getFieldOrPanic(old, field), getFieldOrPanic(cur, field)) { - triggerFunc(curObj) - } - }, - } -} diff --git a/federation/pkg/federation-controller/util/handlers_test.go b/federation/pkg/federation-controller/util/handlers_test.go deleted file mode 100644 index 20c27533528..00000000000 --- a/federation/pkg/federation-controller/util/handlers_test.go +++ /dev/null @@ -1,100 +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 util - -import ( - "testing" - - apiv1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - pkgruntime "k8s.io/apimachinery/pkg/runtime" - - "github.com/stretchr/testify/assert" -) - -func TestHandlers(t *testing.T) { - // There is a single service ns1/s1 in cluster mycluster. - service := apiv1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "ns1", - Name: "s1", - }, - } - service2 := apiv1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "ns1", - Name: "s1", - Annotations: map[string]string{ - "A": "B", - }, - }, - } - triggerChan := make(chan struct{}, 1) - triggered := func() bool { - select { - case <-triggerChan: - return true - default: - return false - } - } - - trigger := NewTriggerOnAllChanges( - func(obj pkgruntime.Object) { - triggerChan <- struct{}{} - }) - - trigger.OnAdd(&service) - assert.True(t, triggered()) - trigger.OnDelete(&service) - assert.True(t, triggered()) - trigger.OnUpdate(&service, &service) - assert.False(t, triggered()) - trigger.OnUpdate(&service, &service2) - assert.True(t, triggered()) - - trigger2 := NewTriggerOnMetaAndSpecChanges( - func(obj pkgruntime.Object) { - triggerChan <- struct{}{} - }, - ) - - trigger2.OnAdd(&service) - assert.True(t, triggered()) - trigger2.OnDelete(&service) - assert.True(t, triggered()) - trigger2.OnUpdate(&service, &service) - assert.False(t, triggered()) - trigger2.OnUpdate(&service, &service2) - assert.True(t, triggered()) - - service3 := apiv1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "ns1", - Name: "s1", - }, - Status: apiv1.ServiceStatus{ - LoadBalancer: apiv1.LoadBalancerStatus{ - Ingress: []apiv1.LoadBalancerIngress{{ - Hostname: "A", - }}, - }, - }, - } - trigger2.OnUpdate(&service, &service3) - assert.False(t, triggered()) -} diff --git a/federation/pkg/federation-controller/util/hpa/BUILD b/federation/pkg/federation-controller/util/hpa/BUILD deleted file mode 100644 index 1b86ba4583c..00000000000 --- a/federation/pkg/federation-controller/util/hpa/BUILD +++ /dev/null @@ -1,39 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = ["hpa.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/util/hpa", - visibility = ["//visibility:public"], - deps = [ - "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["hpa_test.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/util/hpa", - library = ":go_default_library", - deps = [ - "//vendor/github.com/stretchr/testify/require:go_default_library", - "//vendor/k8s.io/api/autoscaling/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], - visibility = ["//visibility:public"], -) diff --git a/federation/pkg/federation-controller/util/hpa/hpa.go b/federation/pkg/federation-controller/util/hpa/hpa.go deleted file mode 100644 index d7a056f5e28..00000000000 --- a/federation/pkg/federation-controller/util/hpa/hpa.go +++ /dev/null @@ -1,75 +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 hpa - -import ( - "encoding/json" - - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/runtime" -) - -const ( - // FederatedAnnotationOnHpaTargetObj as key, is used by hpa controller to - // set selected cluster name list as annotation on the target object. - FederatedAnnotationOnHpaTargetObj = "federation.kubernetes.io/hpa-target-cluster-list" -) - -// ClusterNames stores the list of clusters represented by names as appearing on federation -// cluster objects. This is set by federation hpa and used by target objects federation -// controller to restrict that target object to only these clusters. -type ClusterNames struct { - Names []string -} - -func (cn *ClusterNames) String() string { - annotationBytes, _ := json.Marshal(cn) - return string(annotationBytes[:]) -} - -// GetHpaTargetClusterList is used to get the list of clusters from the target object -// annotations. -func GetHpaTargetClusterList(obj runtime.Object) (*ClusterNames, error) { - accessor, _ := meta.Accessor(obj) - targetObjAnno := accessor.GetAnnotations() - if targetObjAnno == nil { - return nil, nil - } - targetObjAnnoString, exists := targetObjAnno[FederatedAnnotationOnHpaTargetObj] - if !exists { - return nil, nil - } - - clusterNames := &ClusterNames{} - if err := json.Unmarshal([]byte(targetObjAnnoString), clusterNames); err != nil { - return nil, err - } - return clusterNames, nil -} - -// SetHpaTargetClusterList is used to set the list of clusters on the target object -// annotations. -func SetHpaTargetClusterList(obj runtime.Object, clusterNames ClusterNames) runtime.Object { - accessor, _ := meta.Accessor(obj) - anno := accessor.GetAnnotations() - if anno == nil { - anno = make(map[string]string) - accessor.SetAnnotations(anno) - } - anno[FederatedAnnotationOnHpaTargetObj] = clusterNames.String() - return obj -} diff --git a/federation/pkg/federation-controller/util/hpa/hpa_test.go b/federation/pkg/federation-controller/util/hpa/hpa_test.go deleted file mode 100644 index d12943bae72..00000000000 --- a/federation/pkg/federation-controller/util/hpa/hpa_test.go +++ /dev/null @@ -1,115 +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 hpa - -import ( - "testing" - - autoscalingv1 "k8s.io/api/autoscaling/v1" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/stretchr/testify/require" -) - -func TestGetHpaTargetClusterList(t *testing.T) { - // Any object is fine for this test. - obj := &autoscalingv1.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - Name: "myhpa", - Namespace: "myNamespace", - SelfLink: "/api/mylink", - }, - } - - testCases := map[string]struct { - clusterNames *ClusterNames - expectedErr bool - }{ - "Wrong data set on annotations should return unmarshalling error when retrieving": { - expectedErr: true, - }, - "Get clusternames on annotations with 2 clusters, should have same names, which were set": { - clusterNames: &ClusterNames{ - Names: []string{ - "c1", - "c2", - }, - }, - }, - } - - for testName, testCase := range testCases { - t.Run(testName, func(t *testing.T) { - accessor, _ := meta.Accessor(obj) - anno := accessor.GetAnnotations() - if anno == nil { - anno = make(map[string]string) - accessor.SetAnnotations(anno) - } - if testCase.expectedErr { - anno[FederatedAnnotationOnHpaTargetObj] = "{" //some random string - } else { - anno[FederatedAnnotationOnHpaTargetObj] = testCase.clusterNames.String() - } - - readNames, err := GetHpaTargetClusterList(obj) - - if testCase.expectedErr { - require.Error(t, err, "An error was expected") - } else { - require.Equal(t, testCase.clusterNames, readNames, "Names should have been equal") - } - }) - } -} - -func TestSetHpaTargetClusterList(t *testing.T) { - // Any object is fine for this test. - obj := &autoscalingv1.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - Name: "myhpa", - Namespace: "myNamespace", - SelfLink: "/api/mylink", - }, - } - - testCases := map[string]struct { - clusterNames ClusterNames - expectedErr bool - }{ - "Get clusternames on annotations with 2 clusters, should have same names, which were set": { - clusterNames: ClusterNames{ - Names: []string{ - "c1", - "c2", - }, - }, - }, - } - - for testName, testCase := range testCases { - t.Run(testName, func(t *testing.T) { - - SetHpaTargetClusterList(obj, testCase.clusterNames) - readNames, err := GetHpaTargetClusterList(obj) - require.NoError(t, err, "An error should not have happened") - require.Equal(t, &testCase.clusterNames, readNames, "Names should have been equal") - - }) - } -} diff --git a/federation/pkg/federation-controller/util/meta.go b/federation/pkg/federation-controller/util/meta.go deleted file mode 100644 index 2bd14d16595..00000000000 --- a/federation/pkg/federation-controller/util/meta.go +++ /dev/null @@ -1,85 +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 util - -import ( - "reflect" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" -) - -// Copies cluster-independent, user provided data from the given ObjectMeta struct. If in -// the future the ObjectMeta structure is expanded then any field that is not populated -// by the api server should be included here. -func copyObjectMeta(obj metav1.ObjectMeta) metav1.ObjectMeta { - return metav1.ObjectMeta{ - Name: obj.Name, - Namespace: obj.Namespace, - Labels: obj.Labels, - Annotations: obj.Annotations, - } -} - -// Deep copies cluster-independent, user provided data from the given ObjectMeta struct. If in -// the future the ObjectMeta structure is expanded then any field that is not populated -// by the api server should be included here. -func DeepCopyRelevantObjectMeta(obj metav1.ObjectMeta) metav1.ObjectMeta { - copyMeta := copyObjectMeta(obj) - if obj.Labels != nil { - copyMeta.Labels = make(map[string]string) - for key, val := range obj.Labels { - copyMeta.Labels[key] = val - } - } - if obj.Annotations != nil { - copyMeta.Annotations = make(map[string]string) - for key, val := range obj.Annotations { - copyMeta.Annotations[key] = val - } - } - return copyMeta -} - -// Checks if cluster-independent, user provided data in two given ObjectMeta are equal. If in -// the future the ObjectMeta structure is expanded then any field that is not populated -// by the api server should be included here. -func ObjectMetaEquivalent(a, b metav1.ObjectMeta) bool { - if a.Name != b.Name { - return false - } - if a.Namespace != b.Namespace { - return false - } - if !reflect.DeepEqual(a.Labels, b.Labels) && (len(a.Labels) != 0 || len(b.Labels) != 0) { - return false - } - if !reflect.DeepEqual(a.Annotations, b.Annotations) && (len(a.Annotations) != 0 || len(b.Annotations) != 0) { - return false - } - return true -} - -// Checks if cluster-independent, user provided data in ObjectMeta and Spec in two given top -// level api objects are equivalent. -func ObjectMetaAndSpecEquivalent(a, b runtime.Object) bool { - objectMetaA := reflect.ValueOf(a).Elem().FieldByName("ObjectMeta").Interface().(metav1.ObjectMeta) - objectMetaB := reflect.ValueOf(b).Elem().FieldByName("ObjectMeta").Interface().(metav1.ObjectMeta) - specA := reflect.ValueOf(a).Elem().FieldByName("Spec").Interface() - specB := reflect.ValueOf(b).Elem().FieldByName("Spec").Interface() - return ObjectMetaEquivalent(objectMetaA, objectMetaB) && reflect.DeepEqual(specA, specB) -} diff --git a/federation/pkg/federation-controller/util/meta_test.go b/federation/pkg/federation-controller/util/meta_test.go deleted file mode 100644 index b41c26297a5..00000000000 --- a/federation/pkg/federation-controller/util/meta_test.go +++ /dev/null @@ -1,117 +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 util - -import ( - "testing" - - api_v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/stretchr/testify/assert" -) - -func TestObjectMeta(t *testing.T) { - o1 := metav1.ObjectMeta{ - Namespace: "ns1", - Name: "s1", - UID: "1231231412", - ResourceVersion: "999", - } - o2 := copyObjectMeta(o1) - o3 := metav1.ObjectMeta{ - Namespace: "ns1", - Name: "s1", - UID: "1231231412", - Annotations: map[string]string{"A": "B"}, - } - o4 := metav1.ObjectMeta{ - Namespace: "ns1", - Name: "s1", - UID: "1231255531412", - Annotations: map[string]string{"A": "B"}, - } - o5 := metav1.ObjectMeta{ - Namespace: "ns1", - Name: "s1", - ResourceVersion: "1231231412", - Annotations: map[string]string{"A": "B"}, - } - o6 := metav1.ObjectMeta{ - Namespace: "ns1", - Name: "s1", - ResourceVersion: "1231255531412", - Annotations: map[string]string{"A": "B"}, - } - o7 := metav1.ObjectMeta{ - Namespace: "ns1", - Name: "s1", - ResourceVersion: "1231255531412", - Annotations: map[string]string{}, - Labels: map[string]string{}, - } - o8 := metav1.ObjectMeta{ - Namespace: "ns1", - Name: "s1", - ResourceVersion: "1231255531412", - } - assert.Equal(t, 0, len(o2.UID)) - assert.Equal(t, 0, len(o2.ResourceVersion)) - assert.Equal(t, o1.Name, o2.Name) - assert.True(t, ObjectMetaEquivalent(o1, o2)) - assert.False(t, ObjectMetaEquivalent(o1, o3)) - assert.True(t, ObjectMetaEquivalent(o3, o4)) - assert.True(t, ObjectMetaEquivalent(o5, o6)) - assert.True(t, ObjectMetaEquivalent(o3, o5)) - assert.True(t, ObjectMetaEquivalent(o7, o8)) - assert.True(t, ObjectMetaEquivalent(o8, o7)) -} - -func TestObjectMetaAndSpec(t *testing.T) { - s1 := api_v1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "ns1", - Name: "s1", - }, - Spec: api_v1.ServiceSpec{ - ExternalName: "Service1", - }, - } - s1b := s1 - s2 := api_v1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "ns1", - Name: "s2", - }, - Spec: api_v1.ServiceSpec{ - ExternalName: "Service1", - }, - } - s3 := api_v1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "ns1", - Name: "s1", - }, - Spec: api_v1.ServiceSpec{ - ExternalName: "Service2", - }, - } - assert.True(t, ObjectMetaAndSpecEquivalent(&s1, &s1b)) - assert.False(t, ObjectMetaAndSpecEquivalent(&s1, &s2)) - assert.False(t, ObjectMetaAndSpecEquivalent(&s1, &s3)) - assert.False(t, ObjectMetaAndSpecEquivalent(&s2, &s3)) -} diff --git a/federation/pkg/federation-controller/util/planner/BUILD b/federation/pkg/federation-controller/util/planner/BUILD deleted file mode 100644 index 132e36601ac..00000000000 --- a/federation/pkg/federation-controller/util/planner/BUILD +++ /dev/null @@ -1,38 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = ["planner.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/util/planner", - deps = ["//federation/apis/federation:go_default_library"], -) - -go_test( - name = "go_default_test", - srcs = ["planner_test.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/util/planner", - library = ":go_default_library", - deps = [ - "//federation/apis/federation:go_default_library", - "//vendor/github.com/stretchr/testify/assert:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/pkg/federation-controller/util/planner/planner.go b/federation/pkg/federation-controller/util/planner/planner.go deleted file mode 100644 index f25fbbc0ce9..00000000000 --- a/federation/pkg/federation-controller/util/planner/planner.go +++ /dev/null @@ -1,238 +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 planner - -import ( - "hash/fnv" - "sort" - - fedapi "k8s.io/kubernetes/federation/apis/federation" -) - -// Planner decides how many out of the given replicas should be placed in each of the -// federated clusters. -type Planner struct { - preferences *fedapi.ReplicaAllocationPreferences -} - -type namedClusterPreferences struct { - clusterName string - hash uint32 - fedapi.ClusterPreferences -} - -type byWeight []*namedClusterPreferences - -func (a byWeight) Len() int { return len(a) } -func (a byWeight) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -// Preferences are sorted according by decreasing weight and increasing hash (built on top of cluster name and rs name). -// Sorting is made by a hash to avoid assigning single-replica rs to the alphabetically smallest cluster. -func (a byWeight) Less(i, j int) bool { - return (a[i].Weight > a[j].Weight) || (a[i].Weight == a[j].Weight && a[i].hash < a[j].hash) -} - -func NewPlanner(preferences *fedapi.ReplicaAllocationPreferences) *Planner { - return &Planner{ - preferences: preferences, - } -} - -// Distribute the desired number of replicas among the given cluster according to the planner preferences. -// The function tries its best to assign each cluster the preferred number of replicas, however if -// sum of MinReplicas for all cluster is bigger thant replicasToDistribute then some cluster will not -// have all of the replicas assigned. In such case a cluster with higher weight has priority over -// cluster with lower weight (or with lexicographically smaller name in case of draw). -// It can also use the current replica count and estimated capacity to provide better planning and -// adhere to rebalance policy. To avoid prioritization of clusters with smaller lexicographical names -// a semi-random string (like replica set name) can be provided. -// Two maps are returned: -// * a map that contains information how many replicas will be possible to run in a cluster. -// * a map that contains information how many extra replicas would be nice to schedule in a cluster so, -// if by chance, they are scheduled we will be closer to the desired replicas layout. -func (p *Planner) Plan(replicasToDistribute int64, availableClusters []string, currentReplicaCount map[string]int64, - estimatedCapacity map[string]int64, replicaSetKey string) (map[string]int64, map[string]int64) { - - preferences := make([]*namedClusterPreferences, 0, len(availableClusters)) - plan := make(map[string]int64, len(preferences)) - overflow := make(map[string]int64, len(preferences)) - - named := func(name string, pref fedapi.ClusterPreferences) *namedClusterPreferences { - // Seems to work better than addler for our case. - hasher := fnv.New32() - hasher.Write([]byte(name)) - hasher.Write([]byte(replicaSetKey)) - - return &namedClusterPreferences{ - clusterName: name, - hash: hasher.Sum32(), - ClusterPreferences: pref, - } - } - - for _, cluster := range availableClusters { - if localRSP, found := p.preferences.Clusters[cluster]; found { - preferences = append(preferences, named(cluster, localRSP)) - } else { - if localRSP, found := p.preferences.Clusters["*"]; found { - preferences = append(preferences, named(cluster, localRSP)) - } else { - plan[cluster] = int64(0) - } - } - } - sort.Sort(byWeight(preferences)) - - remainingReplicas := replicasToDistribute - - // Assign each cluster the minimum number of replicas it requested. - for _, preference := range preferences { - min := minInt64(preference.MinReplicas, remainingReplicas) - if capacity, hasCapacity := estimatedCapacity[preference.clusterName]; hasCapacity { - min = minInt64(min, capacity) - } - remainingReplicas -= min - plan[preference.clusterName] = min - } - - // This map contains information how many replicas were assigned to - // the cluster based only on the current replica count and - // rebalance=false preference. It will be later used in remaining replica - // distribution code. - preallocated := make(map[string]int64) - - if p.preferences.Rebalance == false { - for _, preference := range preferences { - planned := plan[preference.clusterName] - count, hasSome := currentReplicaCount[preference.clusterName] - if hasSome && count > planned { - target := count - if preference.MaxReplicas != nil { - target = minInt64(*preference.MaxReplicas, target) - } - if capacity, hasCapacity := estimatedCapacity[preference.clusterName]; hasCapacity { - target = minInt64(capacity, target) - } - extra := minInt64(target-planned, remainingReplicas) - if extra < 0 { - extra = 0 - } - remainingReplicas -= extra - preallocated[preference.clusterName] = extra - plan[preference.clusterName] = extra + planned - } - } - } - - modified := true - - // It is possible single pass of the loop is not enough to distribute all replicas among clusters due - // to weight, max and rounding corner cases. In such case we iterate until either - // there is no replicas or no cluster gets any more replicas or the number - // of attempts is less than available cluster count. If there is no preallocated pods - // every loop either distributes all remainingReplicas or maxes out at least one cluster. - // If there are preallocated then the replica spreading may take longer. - // We reduce the number of pending preallocated replicas by at least half with each iteration so - // we may need log(replicasAtStart) iterations. - // TODO: Prove that clusterCount * log(replicas) iterations solves the problem or adjust the number. - // TODO: This algorithm is O(clusterCount^2 * log(replicas)) which is good for up to 100 clusters. - // Find something faster. - for trial := 0; modified && remainingReplicas > 0; trial++ { - - modified = false - weightSum := int64(0) - for _, preference := range preferences { - weightSum += preference.Weight - } - newPreferences := make([]*namedClusterPreferences, 0, len(preferences)) - - distributeInThisLoop := remainingReplicas - - for _, preference := range preferences { - if weightSum > 0 { - start := plan[preference.clusterName] - // Distribute the remaining replicas, rounding fractions always up. - extra := (distributeInThisLoop*preference.Weight + weightSum - 1) / weightSum - extra = minInt64(extra, remainingReplicas) - - // Account preallocated. - prealloc := preallocated[preference.clusterName] - usedPrealloc := minInt64(extra, prealloc) - preallocated[preference.clusterName] = prealloc - usedPrealloc - extra = extra - usedPrealloc - if usedPrealloc > 0 { - modified = true - } - - // In total there should be the amount that was there at start plus whatever is due - // in this iteration - total := start + extra - - // Check if we don't overflow the cluster, and if yes don't consider this cluster - // in any of the following iterations. - full := false - if preference.MaxReplicas != nil && total > *preference.MaxReplicas { - total = *preference.MaxReplicas - full = true - } - if capacity, hasCapacity := estimatedCapacity[preference.clusterName]; hasCapacity && total > capacity { - overflow[preference.clusterName] = total - capacity - total = capacity - full = true - } - - if !full { - newPreferences = append(newPreferences, preference) - } - - // Only total-start replicas were actually taken. - remainingReplicas -= (total - start) - plan[preference.clusterName] = total - - // Something extra got scheduled on this cluster. - if total > start { - modified = true - } - } else { - break - } - } - preferences = newPreferences - } - - if p.preferences.Rebalance { - return plan, overflow - } else { - // If rebalance = false then overflow is trimmed at the level - // of replicas that it failed to place somewhere. - newOverflow := make(map[string]int64) - for key, value := range overflow { - value = minInt64(value, remainingReplicas) - if value > 0 { - newOverflow[key] = value - } - } - return plan, newOverflow - } -} - -func minInt64(a int64, b int64) int64 { - if a < b { - return a - } - return b -} diff --git a/federation/pkg/federation-controller/util/planner/planner_test.go b/federation/pkg/federation-controller/util/planner/planner_test.go deleted file mode 100644 index 597ee607083..00000000000 --- a/federation/pkg/federation-controller/util/planner/planner_test.go +++ /dev/null @@ -1,348 +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 planner - -import ( - "testing" - - fedapi "k8s.io/kubernetes/federation/apis/federation" - - "github.com/stretchr/testify/assert" -) - -func doCheck(t *testing.T, pref map[string]fedapi.ClusterPreferences, replicas int64, clusters []string, expected map[string]int64) { - planer := NewPlanner(&fedapi.ReplicaAllocationPreferences{ - Clusters: pref, - }) - plan, overflow := planer.Plan(replicas, clusters, map[string]int64{}, map[string]int64{}, "") - assert.EqualValues(t, expected, plan) - assert.Equal(t, 0, len(overflow)) -} - -func doCheckWithExisting(t *testing.T, pref map[string]fedapi.ClusterPreferences, replicas int64, clusters []string, - existing map[string]int64, expected map[string]int64) { - planer := NewPlanner(&fedapi.ReplicaAllocationPreferences{ - Clusters: pref, - }) - plan, overflow := planer.Plan(replicas, clusters, existing, map[string]int64{}, "") - assert.Equal(t, 0, len(overflow)) - assert.EqualValues(t, expected, plan) -} - -func doCheckWithExistingAndCapacity(t *testing.T, rebalance bool, pref map[string]fedapi.ClusterPreferences, replicas int64, clusters []string, - existing map[string]int64, - capacity map[string]int64, - expected map[string]int64, - expectedOverflow map[string]int64) { - planer := NewPlanner(&fedapi.ReplicaAllocationPreferences{ - Rebalance: rebalance, - Clusters: pref, - }) - plan, overflow := planer.Plan(replicas, clusters, existing, capacity, "") - assert.EqualValues(t, expected, plan) - assert.Equal(t, expectedOverflow, overflow) -} - -func pint(val int64) *int64 { - return &val -} - -func TestEqual(t *testing.T) { - doCheck(t, map[string]fedapi.ClusterPreferences{ - "*": {Weight: 1}}, - 50, []string{"A", "B", "C"}, - // hash dependent - map[string]int64{"A": 16, "B": 17, "C": 17}) - - doCheck(t, map[string]fedapi.ClusterPreferences{ - "*": {Weight: 1}}, - 50, []string{"A", "B"}, - map[string]int64{"A": 25, "B": 25}) - - doCheck(t, map[string]fedapi.ClusterPreferences{ - "*": {Weight: 1}}, - 1, []string{"A", "B"}, - // hash dependent - map[string]int64{"A": 0, "B": 1}) - - doCheck(t, map[string]fedapi.ClusterPreferences{ - "*": {Weight: 1}}, - 1, []string{"A", "B", "C", "D"}, - // hash dependent - map[string]int64{"A": 0, "B": 0, "C": 0, "D": 1}) - - doCheck(t, map[string]fedapi.ClusterPreferences{ - "*": {Weight: 1}}, - 1, []string{"A"}, - map[string]int64{"A": 1}) - - doCheck(t, map[string]fedapi.ClusterPreferences{ - "*": {Weight: 1}}, - 1, []string{}, - map[string]int64{}) -} - -func TestEqualWithExisting(t *testing.T) { - doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{ - "*": {Weight: 1}}, - 50, []string{"A", "B", "C"}, - map[string]int64{"C": 30}, - map[string]int64{"A": 10, "B": 10, "C": 30}) - - doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{ - "*": {Weight: 1}}, - 50, []string{"A", "B"}, - map[string]int64{"A": 30}, - map[string]int64{"A": 30, "B": 20}) - - doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{ - "*": {Weight: 1}}, - 15, []string{"A", "B"}, - map[string]int64{"A": 0, "B": 8}, - map[string]int64{"A": 7, "B": 8}) - - doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{ - "*": {Weight: 1}}, - 15, []string{"A", "B"}, - map[string]int64{"A": 1, "B": 8}, - map[string]int64{"A": 7, "B": 8}) - - doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{ - "*": {Weight: 1}}, - 15, []string{"A", "B"}, - map[string]int64{"A": 4, "B": 8}, - map[string]int64{"A": 7, "B": 8}) - - doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{ - "*": {Weight: 1}}, - 15, []string{"A", "B"}, - map[string]int64{"A": 5, "B": 8}, - map[string]int64{"A": 7, "B": 8}) - - doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{ - "*": {Weight: 1}}, - 15, []string{"A", "B"}, - map[string]int64{"A": 6, "B": 8}, - map[string]int64{"A": 7, "B": 8}) - - doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{ - "*": {Weight: 1}}, - 15, []string{"A", "B"}, - map[string]int64{"A": 7, "B": 8}, - map[string]int64{"A": 7, "B": 8}) - - doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{ - "*": {Weight: 1}}, - 500000, []string{"A", "B"}, - map[string]int64{"A": 300000}, - map[string]int64{"A": 300000, "B": 200000}) - - doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{ - "*": {Weight: 1}}, - 50, []string{"A", "B"}, - map[string]int64{"A": 10}, - map[string]int64{"A": 25, "B": 25}) - - doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{ - "*": {Weight: 1}}, - 50, []string{"A", "B"}, - map[string]int64{"A": 10, "B": 70}, - // hash dependent - // TODO: Should be 10:40, update algorithm. Issue: #31816 - map[string]int64{"A": 0, "B": 50}) - - doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{ - "*": {Weight: 1}}, - 1, []string{"A", "B"}, - map[string]int64{"A": 30}, - map[string]int64{"A": 1, "B": 0}) - - doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{ - "*": {Weight: 1}}, - 50, []string{"A", "B"}, - map[string]int64{"A": 10, "B": 20}, - map[string]int64{"A": 25, "B": 25}) -} - -func TestWithExistingAndCapacity(t *testing.T) { - // desired without capacity: map[string]int64{"A": 17, "B": 17, "C": 16}) - doCheckWithExistingAndCapacity(t, true, map[string]fedapi.ClusterPreferences{ - "*": {Weight: 1}}, - 50, []string{"A", "B", "C"}, - map[string]int64{}, - map[string]int64{"C": 10}, - map[string]int64{"A": 20, "B": 20, "C": 10}, - map[string]int64{"C": 7}) - - // desired B:50 C:0 - doCheckWithExistingAndCapacity(t, true, map[string]fedapi.ClusterPreferences{ - "A": {Weight: 10000}, - "B": {Weight: 1}}, - 50, []string{"B", "C"}, - map[string]int64{}, - map[string]int64{"B": 10}, - map[string]int64{"B": 10, "C": 0}, - map[string]int64{"B": 40}, - ) - - // desired A:20 B:40 - doCheckWithExistingAndCapacity(t, true, map[string]fedapi.ClusterPreferences{ - "A": {Weight: 1}, - "B": {Weight: 2}}, - 60, []string{"A", "B", "C"}, - map[string]int64{}, - map[string]int64{"B": 10}, - map[string]int64{"A": 50, "B": 10, "C": 0}, - map[string]int64{"B": 30}) - - // map[string]int64{"A": 10, "B": 30, "C": 21, "D": 10}) - doCheckWithExistingAndCapacity(t, true, map[string]fedapi.ClusterPreferences{ - "A": {Weight: 10000, MaxReplicas: pint(10)}, - "B": {Weight: 1}, - "C": {Weight: 1, MaxReplicas: pint(21)}, - "D": {Weight: 1, MaxReplicas: pint(10)}}, - 71, []string{"A", "B", "C", "D"}, - map[string]int64{}, - map[string]int64{"C": 10}, - map[string]int64{"A": 10, "B": 41, "C": 10, "D": 10}, - map[string]int64{"C": 11}, - ) - - // desired A:20 B:20 - doCheckWithExistingAndCapacity(t, false, map[string]fedapi.ClusterPreferences{ - "A": {Weight: 1}, - "B": {Weight: 1}}, - 60, []string{"A", "B", "C"}, - map[string]int64{}, - map[string]int64{"A": 10, "B": 10}, - map[string]int64{"A": 10, "B": 10, "C": 0}, - map[string]int64{"A": 20, "B": 20}) - - // desired A:10 B:50 although A:50 B:10 is fuly acceptable because rebalance = false - doCheckWithExistingAndCapacity(t, false, map[string]fedapi.ClusterPreferences{ - "A": {Weight: 1}, - "B": {Weight: 5}}, - 60, []string{"A", "B", "C"}, - map[string]int64{}, - map[string]int64{"B": 10}, - map[string]int64{"A": 50, "B": 10, "C": 0}, - map[string]int64{}) - - doCheckWithExistingAndCapacity(t, false, map[string]fedapi.ClusterPreferences{ - "*": {MinReplicas: 20, Weight: 0}}, - 50, []string{"A", "B", "C"}, - map[string]int64{}, - map[string]int64{"B": 10}, - map[string]int64{"A": 20, "B": 10, "C": 20}, - map[string]int64{}) - - // Actually we would like to have extra 20 in B but 15 is also good. - doCheckWithExistingAndCapacity(t, true, map[string]fedapi.ClusterPreferences{ - "*": {MinReplicas: 20, Weight: 1}}, - 60, []string{"A", "B"}, - map[string]int64{}, - map[string]int64{"B": 10}, - map[string]int64{"A": 50, "B": 10}, - map[string]int64{"B": 15}) -} - -func TestMin(t *testing.T) { - doCheck(t, map[string]fedapi.ClusterPreferences{ - "*": {MinReplicas: 2, Weight: 0}}, - 50, []string{"A", "B", "C"}, - map[string]int64{"A": 2, "B": 2, "C": 2}) - - doCheck(t, map[string]fedapi.ClusterPreferences{ - "*": {MinReplicas: 20, Weight: 0}}, - 50, []string{"A", "B", "C"}, - // hash dependant. - map[string]int64{"A": 10, "B": 20, "C": 20}) - - doCheck(t, map[string]fedapi.ClusterPreferences{ - "*": {MinReplicas: 20, Weight: 0}, - "A": {MinReplicas: 100, Weight: 1}}, - 50, []string{"A", "B", "C"}, - map[string]int64{"A": 50, "B": 0, "C": 0}) - - doCheck(t, map[string]fedapi.ClusterPreferences{ - "*": {MinReplicas: 10, Weight: 1, MaxReplicas: pint(12)}}, - 50, []string{"A", "B", "C"}, - map[string]int64{"A": 12, "B": 12, "C": 12}) -} - -func TestMax(t *testing.T) { - doCheck(t, map[string]fedapi.ClusterPreferences{ - "*": {Weight: 1, MaxReplicas: pint(2)}}, - 50, []string{"A", "B", "C"}, - map[string]int64{"A": 2, "B": 2, "C": 2}) - - doCheck(t, map[string]fedapi.ClusterPreferences{ - "*": {Weight: 0, MaxReplicas: pint(2)}}, - 50, []string{"A", "B", "C"}, - map[string]int64{"A": 0, "B": 0, "C": 0}) -} - -func TestWeight(t *testing.T) { - doCheck(t, map[string]fedapi.ClusterPreferences{ - "A": {Weight: 1}, - "B": {Weight: 2}}, - 60, []string{"A", "B", "C"}, - map[string]int64{"A": 20, "B": 40, "C": 0}) - - doCheck(t, map[string]fedapi.ClusterPreferences{ - "A": {Weight: 10000}, - "B": {Weight: 1}}, - 50, []string{"A", "B", "C"}, - map[string]int64{"A": 50, "B": 0, "C": 0}) - - doCheck(t, map[string]fedapi.ClusterPreferences{ - "A": {Weight: 10000}, - "B": {Weight: 1}}, - 50, []string{"B", "C"}, - map[string]int64{"B": 50, "C": 0}) - - doCheck(t, map[string]fedapi.ClusterPreferences{ - "A": {Weight: 10000, MaxReplicas: pint(10)}, - "B": {Weight: 1}, - "C": {Weight: 1}}, - 50, []string{"A", "B", "C"}, - map[string]int64{"A": 10, "B": 20, "C": 20}) - - doCheck(t, map[string]fedapi.ClusterPreferences{ - "A": {Weight: 10000, MaxReplicas: pint(10)}, - "B": {Weight: 1}, - "C": {Weight: 1, MaxReplicas: pint(10)}}, - 50, []string{"A", "B", "C"}, - map[string]int64{"A": 10, "B": 30, "C": 10}) - - doCheck(t, map[string]fedapi.ClusterPreferences{ - "A": {Weight: 10000, MaxReplicas: pint(10)}, - "B": {Weight: 1}, - "C": {Weight: 1, MaxReplicas: pint(21)}, - "D": {Weight: 1, MaxReplicas: pint(10)}}, - 71, []string{"A", "B", "C", "D"}, - map[string]int64{"A": 10, "B": 30, "C": 21, "D": 10}) - - doCheck(t, map[string]fedapi.ClusterPreferences{ - "A": {Weight: 10000, MaxReplicas: pint(10)}, - "B": {Weight: 1}, - "C": {Weight: 1, MaxReplicas: pint(21)}, - "D": {Weight: 1, MaxReplicas: pint(10)}, - "E": {Weight: 1}}, - 91, []string{"A", "B", "C", "D", "E"}, - map[string]int64{"A": 10, "B": 25, "C": 21, "D": 10, "E": 25}) -} diff --git a/federation/pkg/federation-controller/util/podanalyzer/BUILD b/federation/pkg/federation-controller/util/podanalyzer/BUILD deleted file mode 100644 index e94314ca6bd..00000000000 --- a/federation/pkg/federation-controller/util/podanalyzer/BUILD +++ /dev/null @@ -1,40 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = ["pod_helper.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/util/podanalyzer", - deps = ["//vendor/k8s.io/api/core/v1:go_default_library"], -) - -go_test( - name = "go_default_test", - srcs = ["pod_helper_test.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/util/podanalyzer", - library = ":go_default_library", - deps = [ - "//vendor/github.com/stretchr/testify/assert:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/pkg/federation-controller/util/podanalyzer/pod_helper.go b/federation/pkg/federation-controller/util/podanalyzer/pod_helper.go deleted file mode 100644 index 53b308f1d96..00000000000 --- a/federation/pkg/federation-controller/util/podanalyzer/pod_helper.go +++ /dev/null @@ -1,63 +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 podanalyzer - -import ( - "time" - - api_v1 "k8s.io/api/core/v1" -) - -type PodAnalysisResult struct { - // Total number of pods created. - Total int - // Number of pods that are running and ready. - RunningAndReady int - // Number of pods that have been in unschedulable state for UnshedulableThreshold seconds. - Unschedulable int - - // TODO: Handle other scenarios like pod waiting too long for scheduler etc. -} - -const ( - // TODO: make it configurable - UnschedulableThreshold = 60 * time.Second -) - -// AnalyzePods calculates how many pods from the list are in one of -// the meaningful (from the replica set perspective) states. This function is -// a temporary workaround against the current lack of ownerRef in pods. -func AnalyzePods(pods *api_v1.PodList, currentTime time.Time) PodAnalysisResult { - result := PodAnalysisResult{} - for _, pod := range pods.Items { - result.Total++ - for _, condition := range pod.Status.Conditions { - if pod.Status.Phase == api_v1.PodRunning { - if condition.Type == api_v1.PodReady { - result.RunningAndReady++ - } - } else if condition.Type == api_v1.PodScheduled && - condition.Status == api_v1.ConditionFalse && - condition.Reason == api_v1.PodReasonUnschedulable && - condition.LastTransitionTime.Add(UnschedulableThreshold).Before(currentTime) { - - result.Unschedulable++ - } - } - } - return result -} diff --git a/federation/pkg/federation-controller/util/podanalyzer/pod_helper_test.go b/federation/pkg/federation-controller/util/podanalyzer/pod_helper_test.go deleted file mode 100644 index 99a16112012..00000000000 --- a/federation/pkg/federation-controller/util/podanalyzer/pod_helper_test.go +++ /dev/null @@ -1,98 +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 podanalyzer - -import ( - "testing" - "time" - - api_v1 "k8s.io/api/core/v1" - "k8s.io/api/extensions/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/stretchr/testify/assert" -) - -func TestAnalyze(t *testing.T) { - now := time.Now() - podRunning := newPod("p1", - api_v1.PodStatus{ - Phase: api_v1.PodRunning, - Conditions: []api_v1.PodCondition{ - { - Type: api_v1.PodReady, - Status: api_v1.ConditionTrue, - }, - }, - }) - podUnschedulable := newPod("pU", - api_v1.PodStatus{ - Phase: api_v1.PodPending, - Conditions: []api_v1.PodCondition{ - { - Type: api_v1.PodScheduled, - Status: api_v1.ConditionFalse, - Reason: api_v1.PodReasonUnschedulable, - LastTransitionTime: metav1.Time{Time: now.Add(-10 * time.Minute)}, - }, - }, - }) - podOther := newPod("pO", - api_v1.PodStatus{ - Phase: api_v1.PodPending, - Conditions: []api_v1.PodCondition{}, - }) - - result := AnalyzePods(&api_v1.PodList{Items: []api_v1.Pod{*podRunning, *podRunning, *podRunning, *podUnschedulable, *podUnschedulable}}, now) - assert.Equal(t, PodAnalysisResult{ - Total: 5, - RunningAndReady: 3, - Unschedulable: 2, - }, result) - - result = AnalyzePods(&api_v1.PodList{Items: []api_v1.Pod{*podOther}}, now) - assert.Equal(t, PodAnalysisResult{ - Total: 1, - RunningAndReady: 0, - Unschedulable: 0, - }, result) -} - -func newReplicaSet(selectorMap map[string]string) *v1beta1.ReplicaSet { - replicas := int32(3) - rs := &v1beta1.ReplicaSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foobar", - Namespace: metav1.NamespaceDefault, - }, - Spec: v1beta1.ReplicaSetSpec{ - Replicas: &replicas, - Selector: &metav1.LabelSelector{MatchLabels: selectorMap}, - }, - } - return rs -} - -func newPod(name string, status api_v1.PodStatus) *api_v1.Pod { - return &api_v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: metav1.NamespaceDefault, - }, - Status: status, - } -} diff --git a/federation/pkg/federation-controller/util/replicapreferences/BUILD b/federation/pkg/federation-controller/util/replicapreferences/BUILD deleted file mode 100644 index d8c4266095a..00000000000 --- a/federation/pkg/federation-controller/util/replicapreferences/BUILD +++ /dev/null @@ -1,45 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = ["preferences.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/util/replicapreferences", - deps = [ - "//federation/apis/federation:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) - -go_test( - name = "go_default_test", - srcs = ["preferences_test.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/util/replicapreferences", - library = ":go_default_library", - deps = [ - "//vendor/github.com/stretchr/testify/assert:go_default_library", - "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - ], -) diff --git a/federation/pkg/federation-controller/util/replicapreferences/preferences.go b/federation/pkg/federation-controller/util/replicapreferences/preferences.go deleted file mode 100644 index 030b3c01aa6..00000000000 --- a/federation/pkg/federation-controller/util/replicapreferences/preferences.go +++ /dev/null @@ -1,55 +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 replicapreferences - -import ( - "encoding/json" - - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/runtime" - fed "k8s.io/kubernetes/federation/apis/federation" -) - -// GetAllocationPreferences reads the preferences from the annotations on the given object. -// It takes in an object and determines the supported types. -// Callers need to pass the string key used to store the annotations. -// Returns nil if the annotations with the given key are not found. -func GetAllocationPreferences(obj runtime.Object, key string) (*fed.ReplicaAllocationPreferences, error) { - if obj == nil { - return nil, nil - } - - accessor, err := meta.Accessor(obj) - if err != nil { - return nil, err - } - annotations := accessor.GetAnnotations() - if annotations == nil { - return nil, nil - } - - prefString, found := annotations[key] - if !found { - return nil, nil - } - - var pref fed.ReplicaAllocationPreferences - if err := json.Unmarshal([]byte(prefString), &pref); err != nil { - return nil, err - } - return &pref, nil -} diff --git a/federation/pkg/federation-controller/util/replicapreferences/preferences_test.go b/federation/pkg/federation-controller/util/replicapreferences/preferences_test.go deleted file mode 100644 index 4a91097f846..00000000000 --- a/federation/pkg/federation-controller/util/replicapreferences/preferences_test.go +++ /dev/null @@ -1,92 +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 replicapreferences - -import ( - "testing" - - extensionsv1 "k8s.io/api/extensions/v1beta1" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/runtime" -) - -const ( - TestPreferencesAnnotationKey = "federation.kubernetes.io/test-preferences" -) - -func TestGetAllocationPreferences(t *testing.T) { - testCases := []struct { - testname string - prefs string - obj runtime.Object - errorExpected bool - }{ - { - testname: "good preferences", - prefs: `{"rebalance": true, - "clusters": { - "k8s-1": {"minReplicas": 10, "maxReplicas": 20, "weight": 2}, - "*": {"weight": 1} - }}`, - obj: &extensionsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-obj", - Namespace: metav1.NamespaceDefault, - SelfLink: "/api/v1/namespaces/default/obj/test-obj", - }, - }, - errorExpected: false, - }, - { - testname: "failed preferences", - prefs: `{`, // bad json - obj: &extensionsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-obj", - Namespace: metav1.NamespaceDefault, - SelfLink: "/api/v1/namespaces/default/obj/test-obj", - }, - }, - errorExpected: true, - }, - } - - // prepare the objects - for _, tc := range testCases { - accessor, _ := meta.Accessor(tc.obj) - anno := accessor.GetAnnotations() - if anno == nil { - anno = make(map[string]string) - accessor.SetAnnotations(anno) - } - anno[TestPreferencesAnnotationKey] = tc.prefs - } - - // test get preferences - for _, tc := range testCases { - pref, err := GetAllocationPreferences(tc.obj, TestPreferencesAnnotationKey) - if tc.errorExpected { - assert.NotNil(t, err) - } else { - assert.NotNil(t, pref) - assert.Nil(t, err) - } - } -} diff --git a/federation/pkg/federation-controller/util/secret.go b/federation/pkg/federation-controller/util/secret.go deleted file mode 100644 index e3635fbb831..00000000000 --- a/federation/pkg/federation-controller/util/secret.go +++ /dev/null @@ -1,32 +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 util - -import ( - "reflect" - - api_v1 "k8s.io/api/core/v1" -) - -// Checks if cluster-independent, user provided data in two given Secrets are equal. If in -// the future the Secret structure is expanded then any field that is not populated. -// by the api server should be included here. -func SecretEquivalent(s1, s2 api_v1.Secret) bool { - return ObjectMetaEquivalent(s1.ObjectMeta, s2.ObjectMeta) && - reflect.DeepEqual(s1.Data, s2.Data) && - reflect.DeepEqual(s1.Type, s2.Type) -} diff --git a/federation/pkg/federation-controller/util/test/BUILD b/federation/pkg/federation-controller/util/test/BUILD deleted file mode 100644 index 6a56a395e61..00000000000 --- a/federation/pkg/federation-controller/util/test/BUILD +++ /dev/null @@ -1,39 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["test_helper.go"], - importpath = "k8s.io/kubernetes/federation/pkg/federation-controller/util/test", - deps = [ - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/pkg/federation-controller/util:go_default_library", - "//federation/pkg/federation-controller/util/finalizers:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/github.com/stretchr/testify/assert:go_default_library", - "//vendor/github.com/stretchr/testify/require:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/testing:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/pkg/federation-controller/util/test/test_helper.go b/federation/pkg/federation-controller/util/test/test_helper.go deleted file mode 100644 index 84db1babe3f..00000000000 --- a/federation/pkg/federation-controller/util/test/test_helper.go +++ /dev/null @@ -1,445 +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 testutil - -import ( - "fmt" - "os" - "reflect" - "runtime/pprof" - "sync" - "testing" - "time" - - apiv1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/apimachinery/pkg/watch" - core "k8s.io/client-go/testing" - federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" - "k8s.io/kubernetes/federation/pkg/federation-controller/util" - finalizersutil "k8s.io/kubernetes/federation/pkg/federation-controller/util/finalizers" - - "github.com/golang/glog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const ( - pushTimeout = 5 * time.Second -) - -// A structure that distributes events to multiple watchers. -type WatcherDispatcher struct { - sync.Mutex - watchers []*watch.RaceFreeFakeWatcher - eventsSoFar []*watch.Event - orderExecution chan func() - stopChan chan struct{} -} - -func (wd *WatcherDispatcher) register(watcher *watch.RaceFreeFakeWatcher) { - wd.Lock() - defer wd.Unlock() - wd.watchers = append(wd.watchers, watcher) - for _, event := range wd.eventsSoFar { - watcher.Action(event.Type, event.Object) - } -} - -func (wd *WatcherDispatcher) Stop() { - wd.Lock() - defer wd.Unlock() - close(wd.stopChan) - glog.Infof("Stopping WatcherDispatcher") - for _, watcher := range wd.watchers { - watcher.Stop() - } -} - -// Add sends an add event. -func (wd *WatcherDispatcher) Add(obj runtime.Object) { - wd.Lock() - defer wd.Unlock() - wd.eventsSoFar = append(wd.eventsSoFar, &watch.Event{Type: watch.Added, Object: obj.DeepCopyObject()}) - for _, watcher := range wd.watchers { - if !watcher.IsStopped() { - watcher.Add(obj.DeepCopyObject()) - } - } -} - -// Modify sends a modify event. -func (wd *WatcherDispatcher) Modify(obj runtime.Object) { - wd.Lock() - defer wd.Unlock() - glog.V(4).Infof("->WatcherDispatcher.Modify(%v)", obj) - wd.eventsSoFar = append(wd.eventsSoFar, &watch.Event{Type: watch.Modified, Object: obj.DeepCopyObject()}) - for i, watcher := range wd.watchers { - if !watcher.IsStopped() { - glog.V(4).Infof("->Watcher(%d).Modify(%v)", i, obj) - watcher.Modify(obj.DeepCopyObject()) - } else { - glog.V(4).Infof("->Watcher(%d) is stopped. Not calling Modify(%v)", i, obj) - } - } -} - -// Delete sends a delete event. -func (wd *WatcherDispatcher) Delete(lastValue runtime.Object) { - wd.Lock() - defer wd.Unlock() - wd.eventsSoFar = append(wd.eventsSoFar, &watch.Event{Type: watch.Deleted, Object: lastValue.DeepCopyObject()}) - for _, watcher := range wd.watchers { - if !watcher.IsStopped() { - watcher.Delete(lastValue.DeepCopyObject()) - } - } -} - -// Error sends an Error event. -func (wd *WatcherDispatcher) Error(errValue runtime.Object) { - wd.Lock() - defer wd.Unlock() - wd.eventsSoFar = append(wd.eventsSoFar, &watch.Event{Type: watch.Error, Object: errValue.DeepCopyObject()}) - for _, watcher := range wd.watchers { - if !watcher.IsStopped() { - watcher.Error(errValue.DeepCopyObject()) - } - } -} - -// Action sends an event of the requested type, for table-based testing. -func (wd *WatcherDispatcher) Action(action watch.EventType, obj runtime.Object) { - wd.Lock() - defer wd.Unlock() - wd.eventsSoFar = append(wd.eventsSoFar, &watch.Event{Type: action, Object: obj.DeepCopyObject()}) - for _, watcher := range wd.watchers { - if !watcher.IsStopped() { - watcher.Action(action, obj.DeepCopyObject()) - } - } -} - -// RegisterFakeWatch adds a new fake watcher for the specified resource in the given fake client. -// All subsequent requests for a watch on the client will result in returning this fake watcher. -func RegisterFakeWatch(resource string, client *core.Fake) *WatcherDispatcher { - dispatcher := &WatcherDispatcher{ - watchers: make([]*watch.RaceFreeFakeWatcher, 0), - eventsSoFar: make([]*watch.Event, 0), - orderExecution: make(chan func(), 100), - stopChan: make(chan struct{}), - } - go func() { - for { - select { - case fun := <-dispatcher.orderExecution: - fun() - case <-dispatcher.stopChan: - return - } - } - }() - - client.AddWatchReactor(resource, func(action core.Action) (bool, watch.Interface, error) { - watcher := watch.NewRaceFreeFake() - dispatcher.register(watcher) - return true, watcher, nil - }) - return dispatcher -} - -// RegisterFakeList registers a list response for the specified resource inside the given fake client. -// The passed value will be returned with every list call. -func RegisterFakeList(resource string, client *core.Fake, obj runtime.Object) { - client.AddReactor("list", resource, func(action core.Action) (bool, runtime.Object, error) { - return true, obj, nil - }) -} - -// RegisterFakeClusterGet registers a get response for the cluster resource inside the given fake client. -func RegisterFakeClusterGet(client *core.Fake, obj runtime.Object) { - clusterList, ok := obj.(*federationapi.ClusterList) - client.AddReactor("get", "clusters", func(action core.Action) (bool, runtime.Object, error) { - name := action.(core.GetAction).GetName() - if ok { - for _, cluster := range clusterList.Items { - if cluster.Name == name { - return true, &cluster, nil - } - } - } - return false, nil, fmt.Errorf("could not find the requested cluster: %s", name) - }) -} - -// RegisterFakeOnCreate registers a reactor in the given fake client that passes -// all created objects to the given watcher. -func RegisterFakeOnCreate(resource string, client *core.Fake, watcher *WatcherDispatcher) { - client.AddReactor("create", resource, func(action core.Action) (bool, runtime.Object, error) { - createAction := action.(core.CreateAction) - originalObj := createAction.GetObject() - // Create a copy of the object here to prevent data races while reading the object in go routine. - obj := originalObj.DeepCopyObject() - watcher.orderExecution <- func() { - glog.V(4).Infof("Object created: %v", obj) - watcher.Add(obj) - } - return true, originalObj, nil - }) -} - -// RegisterFakeCopyOnCreate registers a reactor in the given fake client that passes -// all created objects to the given watcher and also copies them to a channel for -// in-test inspection. -func RegisterFakeCopyOnCreate(resource string, client *core.Fake, watcher *WatcherDispatcher) chan runtime.Object { - objChan := make(chan runtime.Object, 100) - client.AddReactor("create", resource, func(action core.Action) (bool, runtime.Object, error) { - createAction := action.(core.CreateAction) - originalObj := createAction.GetObject() - // Create a copy of the object here to prevent data races while reading the object in go routine. - obj := originalObj.DeepCopyObject() - watcher.orderExecution <- func() { - glog.V(4).Infof("Object created. Writing to channel: %v", obj) - watcher.Add(obj) - objChan <- obj - } - return true, originalObj, nil - }) - return objChan -} - -// RegisterFakeOnUpdate registers a reactor in the given fake client that passes -// all updated objects to the given watcher. -func RegisterFakeOnUpdate(resource string, client *core.Fake, watcher *WatcherDispatcher) { - client.AddReactor("update", resource, func(action core.Action) (bool, runtime.Object, error) { - updateAction := action.(core.UpdateAction) - originalObj := updateAction.GetObject() - glog.V(7).Infof("Updating %s: %v", resource, updateAction.GetObject()) - - // Create a copy of the object here to prevent data races while reading the object in go routine. - obj := originalObj.DeepCopyObject() - operation := func() { - glog.V(4).Infof("Object updated %v", obj) - watcher.Modify(obj) - } - select { - case watcher.orderExecution <- operation: - break - case <-time.After(pushTimeout): - glog.Errorf("Fake client execution channel blocked") - glog.Errorf("Tried to push %v", updateAction) - } - return true, originalObj, nil - }) - return -} - -// RegisterFakeCopyOnUpdate registers a reactor in the given fake client that passes -// all updated objects to the given watcher and also copies them to a channel for -// in-test inspection. -func RegisterFakeCopyOnUpdate(resource string, client *core.Fake, watcher *WatcherDispatcher) chan runtime.Object { - objChan := make(chan runtime.Object, 100) - client.AddReactor("update", resource, func(action core.Action) (bool, runtime.Object, error) { - updateAction := action.(core.UpdateAction) - originalObj := updateAction.GetObject() - glog.V(7).Infof("Updating %s: %v", resource, updateAction.GetObject()) - - // Create a copy of the object here to prevent data races while reading the object in go routine. - obj := originalObj.DeepCopyObject() - operation := func() { - glog.V(4).Infof("Object updated. Writing to channel: %v", obj) - watcher.Modify(obj) - objChan <- obj - } - select { - case watcher.orderExecution <- operation: - break - case <-time.After(pushTimeout): - glog.Errorf("Fake client execution channel blocked") - glog.Errorf("Tried to push %v", updateAction) - } - return true, originalObj, nil - }) - return objChan -} - -// RegisterFakeOnDelete registers a reactor in the given fake client that passes -// all deleted objects to the given watcher. Since we could get only name of the -// deleted object from DeleteAction, this register function relies on the getObject -// function passed to get the object by name and pass it watcher. -func RegisterFakeOnDelete(resource string, client *core.Fake, watcher *WatcherDispatcher, getObject func(name, namespace string) runtime.Object) { - client.AddReactor("delete", resource, func(action core.Action) (bool, runtime.Object, error) { - deleteAction := action.(core.DeleteAction) - obj := getObject(deleteAction.GetName(), deleteAction.GetNamespace()) - glog.V(7).Infof("Deleting %s: %v", resource, obj) - - operation := func() { - glog.V(4).Infof("Object deleted %v", obj) - watcher.Delete(obj) - } - select { - case watcher.orderExecution <- operation: - break - case <-time.After(pushTimeout): - glog.Errorf("Fake client execution channel blocked") - glog.Errorf("Tried to push %v", deleteAction) - } - return true, obj, nil - }) - return -} - -// Adds an update reactor to the given fake client. -// The reactor just returns the object passed to update action. -// This is used as a hack to workaround https://github.com/kubernetes/kubernetes/issues/40939. -// Without this, all update actions using fake client return empty objects. -func AddFakeUpdateReactor(resource string, client *core.Fake) { - client.AddReactor("update", resource, func(action core.Action) (bool, runtime.Object, error) { - updateAction := action.(core.UpdateAction) - originalObj := updateAction.GetObject() - return true, originalObj, nil - }) -} - -// GetObjectFromChan tries to get an api object from the given channel -// within a reasonable time. -func GetObjectFromChan(c chan runtime.Object) runtime.Object { - select { - case obj := <-c: - return obj - case <-time.After(wait.ForeverTestTimeout): - pprof.Lookup("goroutine").WriteTo(os.Stderr, 1) - return nil - } -} - -type CheckingFunction func(runtime.Object) error - -// CheckObjectFromChan tries to get an object matching the given check function -// within a reasonable time. -func CheckObjectFromChan(c chan runtime.Object, checkFunction CheckingFunction) error { - delay := 20 * time.Second - var lastError error - for { - select { - case obj := <-c: - if lastError = checkFunction(obj); lastError == nil { - return nil - } - glog.Infof("Check function failed with %v", lastError) - delay = 5 * time.Second - case <-time.After(delay): - pprof.Lookup("goroutine").WriteTo(os.Stderr, 1) - if lastError == nil { - return fmt.Errorf("Failed to get an object from channel") - } else { - return lastError - } - } - } -} - -// CompareObjectMeta returns an error when the given objects are not equivalent. -func CompareObjectMeta(a, b metav1.ObjectMeta) error { - if a.Namespace != b.Namespace { - return fmt.Errorf("Different namespace expected:%s observed:%s", a.Namespace, b.Namespace) - } - if a.Name != b.Name { - return fmt.Errorf("Different name expected:%s observed:%s", a.Name, b.Name) - } - if !reflect.DeepEqual(a.Labels, b.Labels) && (len(a.Labels) != 0 || len(b.Labels) != 0) { - return fmt.Errorf("Labels are different expected:%v observed:%v", a.Labels, b.Labels) - } - if !reflect.DeepEqual(a.Annotations, b.Annotations) && (len(a.Annotations) != 0 || len(b.Annotations) != 0) { - return fmt.Errorf("Annotations are different expected:%v observed:%v", a.Annotations, b.Annotations) - } - return nil -} - -func ToFederatedInformerForTestOnly(informer util.FederatedInformer) util.FederatedInformerForTestOnly { - inter := informer.(interface{}) - return inter.(util.FederatedInformerForTestOnly) -} - -// NewCluster builds a new cluster object. -func NewCluster(name string, readyStatus apiv1.ConditionStatus) *federationapi.Cluster { - return &federationapi.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Annotations: map[string]string{}, - Labels: map[string]string{"cluster": name}, - }, - Status: federationapi.ClusterStatus{ - Conditions: []federationapi.ClusterCondition{ - {Type: federationapi.ClusterReady, Status: readyStatus}, - }, - Zones: []string{"foozone"}, - Region: "fooregion", - }, - } -} - -// Ensure a key is in the store before returning (or timeout w/ error) -func WaitForStoreUpdate(store util.FederatedReadOnlyStore, clusterName, key string, timeout time.Duration) error { - retryInterval := 100 * time.Millisecond - err := wait.PollImmediate(retryInterval, timeout, func() (bool, error) { - _, found, err := store.GetByKey(clusterName, key) - return found, err - }) - return err -} - -// Ensure a key is in the store before returning (or timeout w/ error) -func WaitForStoreUpdateChecking(store util.FederatedReadOnlyStore, clusterName, key string, timeout time.Duration, - checkFunction CheckingFunction) error { - retryInterval := 500 * time.Millisecond - var lastError error - err := wait.PollImmediate(retryInterval, timeout, func() (bool, error) { - item, found, err := store.GetByKey(clusterName, key) - if err != nil || !found { - return found, err - } - runtimeObj := item.(runtime.Object) - lastError = checkFunction(runtimeObj) - glog.V(2).Infof("Check function failed for %s %v %v", key, runtimeObj, lastError) - return lastError == nil, nil - }) - return err -} - -func MetaAndSpecCheckingFunction(expected runtime.Object) CheckingFunction { - return func(obj runtime.Object) error { - if util.ObjectMetaAndSpecEquivalent(obj, expected) { - return nil - } - return fmt.Errorf("Object different expected=%#v received=%#v", expected, obj) - } -} - -func AssertHasFinalizer(t *testing.T, obj runtime.Object, finalizer string) { - hasFinalizer, err := finalizersutil.HasFinalizer(obj, finalizer) - require.Nil(t, err) - assert.True(t, hasFinalizer) -} - -func NewInt32(val int32) *int32 { - p := new(int32) - *p = val - return p -} diff --git a/federation/pkg/kubefed/BUILD b/federation/pkg/kubefed/BUILD deleted file mode 100644 index 762466855f9..00000000000 --- a/federation/pkg/kubefed/BUILD +++ /dev/null @@ -1,98 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = [ - "join.go", - "kubefed.go", - "unjoin.go", - ], - importpath = "k8s.io/kubernetes/federation/pkg/kubefed", - deps = [ - "//federation/apis/federation:go_default_library", - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/pkg/kubefed/init:go_default_library", - "//federation/pkg/kubefed/util:go_default_library", - "//pkg/api:go_default_library", - "//pkg/apis/extensions:go_default_library", - "//pkg/apis/rbac:go_default_library", - "//pkg/client/clientset_generated/internalclientset:go_default_library", - "//pkg/kubectl:go_default_library", - "//pkg/kubectl/cmd:go_default_library", - "//pkg/kubectl/cmd/templates:go_default_library", - "//pkg/kubectl/cmd/util:go_default_library", - "//pkg/kubectl/resource:go_default_library", - "//pkg/kubectl/util/i18n:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/github.com/spf13/cobra:go_default_library", - "//vendor/github.com/spf13/pflag:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library", - "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library", - "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", - "//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = [ - "join_test.go", - "unjoin_test.go", - ], - importpath = "k8s.io/kubernetes/federation/pkg/kubefed", - library = ":go_default_library", - deps = [ - "//federation/apis/federation:go_default_library", - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/pkg/kubefed/testing:go_default_library", - "//federation/pkg/kubefed/util:go_default_library", - "//pkg/api:go_default_library", - "//pkg/api/testapi:go_default_library", - "//pkg/apis/rbac/v1:go_default_library", - "//pkg/kubectl:go_default_library", - "//pkg/kubectl/cmd/testing:go_default_library", - "//pkg/kubectl/cmd/util:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/api/rbac/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", - "//vendor/k8s.io/client-go/dynamic:go_default_library", - "//vendor/k8s.io/client-go/rest/fake:go_default_library", - "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", - "//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/pkg/kubefed/init:all-srcs", - "//federation/pkg/kubefed/testing:all-srcs", - "//federation/pkg/kubefed/util:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/pkg/kubefed/init/BUILD b/federation/pkg/kubefed/init/BUILD deleted file mode 100644 index 46fd43e89ef..00000000000 --- a/federation/pkg/kubefed/init/BUILD +++ /dev/null @@ -1,85 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = ["init.go"], - importpath = "k8s.io/kubernetes/federation/pkg/kubefed/init", - deps = [ - "//cmd/kubeadm/app/util/kubeconfig:go_default_library", - "//federation/apis/federation:go_default_library", - "//federation/pkg/dnsprovider/providers/coredns:go_default_library", - "//federation/pkg/kubefed/util:go_default_library", - "//pkg/api:go_default_library", - "//pkg/apis/extensions:go_default_library", - "//pkg/apis/rbac:go_default_library", - "//pkg/client/clientset_generated/internalclientset:go_default_library", - "//pkg/kubectl/cmd/templates:go_default_library", - "//pkg/kubectl/cmd/util:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/github.com/spf13/cobra:go_default_library", - "//vendor/github.com/spf13/pflag:go_default_library", - "//vendor/gopkg.in/gcfg.v1:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", - "//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library", - "//vendor/k8s.io/client-go/util/cert:go_default_library", - "//vendor/k8s.io/client-go/util/cert/triple:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["init_test.go"], - importpath = "k8s.io/kubernetes/federation/pkg/kubefed/init", - library = ":go_default_library", - deps = [ - "//federation/apis/federation:go_default_library", - "//federation/pkg/dnsprovider/providers/coredns:go_default_library", - "//federation/pkg/kubefed/testing:go_default_library", - "//federation/pkg/kubefed/util:go_default_library", - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", - "//pkg/api/testapi:go_default_library", - "//pkg/apis/rbac:go_default_library", - "//pkg/kubectl/cmd/testing:go_default_library", - "//pkg/kubectl/cmd/util:go_default_library", - "//vendor/gopkg.in/gcfg.v1:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/api/rbac/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - "//vendor/k8s.io/client-go/dynamic:go_default_library", - "//vendor/k8s.io/client-go/rest/fake:go_default_library", - "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/pkg/kubefed/init/init.go b/federation/pkg/kubefed/init/init.go deleted file mode 100644 index 60803e75ec2..00000000000 --- a/federation/pkg/kubefed/init/init.go +++ /dev/null @@ -1,1213 +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. -*/ - -// TODO(madhusdancs): -// 1. Make printSuccess prepend protocol/scheme to the IPs/hostnames. -// 2. Separate etcd container from API server pod as a first step towards enabling HA. -// 3. Make API server and controller manager replicas customizable via the HA work. -package init - -import ( - "fmt" - "io" - "io/ioutil" - "net" - "os" - "sort" - "strconv" - "strings" - "time" - - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/apimachinery/pkg/util/uuid" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/client-go/tools/clientcmd" - clientcmdapi "k8s.io/client-go/tools/clientcmd/api" - certutil "k8s.io/client-go/util/cert" - triple "k8s.io/client-go/util/cert/triple" - kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" - "k8s.io/kubernetes/federation/apis/federation" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/coredns" - "k8s.io/kubernetes/federation/pkg/kubefed/util" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/extensions" - "k8s.io/kubernetes/pkg/apis/rbac" - client "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" - "k8s.io/kubernetes/pkg/kubectl/cmd/templates" - cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" - - "github.com/golang/glog" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - "gopkg.in/gcfg.v1" -) - -const ( - APIServerCN = "federation-apiserver" - ControllerManagerCN = "federation-controller-manager" - AdminCN = "admin" - HostClusterLocalDNSZoneName = "cluster.local." - APIServerNameSuffix = "apiserver" - CMNameSuffix = "controller-manager" - CredentialSuffix = "credentials" - KubeconfigNameSuffix = "kubeconfig" - - // User name used by federation controller manager to make - // calls to federation API server. - ControllerManagerUser = "federation-controller-manager" - - // Name of the ServiceAccount used by the federation controller manager - // to access the secrets in the host cluster. - ControllerManagerSA = "federation-controller-manager" - - // Group name of the legacy/core API group - legacyAPIGroup = "" - - lbAddrRetryInterval = 5 * time.Second - podWaitInterval = 2 * time.Second - - apiserverServiceTypeFlag = "api-server-service-type" - apiserverAdvertiseAddressFlag = "api-server-advertise-address" - apiserverPortFlag = "api-server-port" - - dnsProviderSecretName = "federation-dns-provider.conf" - - apiServerSecurePortName = "https" - // Set the secure port to 8443 to avoid requiring root privileges - // to bind to port < 1000. The apiserver's service will still - // expose on port 443. - apiServerSecurePort = 8443 -) - -var ( - init_long = templates.LongDesc(` - Init initializes a federation control plane. - - Federation control plane is hosted inside a Kubernetes - cluster. The host cluster must be specified using the - --host-cluster-context flag.`) - init_example = templates.Examples(` - # Initialize federation control plane for a federation - # named foo in the host cluster whose local kubeconfig - # context is bar. - kubefed init foo --host-cluster-context=bar`) - - componentLabel = map[string]string{ - "app": "federated-cluster", - } - - apiserverSvcSelector = map[string]string{ - "app": "federated-cluster", - "module": "federation-apiserver", - } - - apiserverPodLabels = map[string]string{ - "app": "federated-cluster", - "module": "federation-apiserver", - } - - controllerManagerPodLabels = map[string]string{ - "app": "federated-cluster", - "module": "federation-controller-manager", - } -) - -type initFederation struct { - commonOptions util.SubcommandOptions - options initFederationOptions -} - -type initFederationOptions struct { - dnsZoneName string - serverImage string - dnsProvider string - dnsProviderConfig string - etcdImage string - etcdPVCapacity string - etcdPVStorageClass string - etcdPersistentStorage bool - dryRun bool - apiServerOverridesString string - apiServerOverrides map[string]string - controllerManagerOverridesString string - controllerManagerOverrides map[string]string - apiServerServiceTypeString string - apiServerServiceType v1.ServiceType - apiServerAdvertiseAddress string - apiServerNodePortPort int32 - apiServerNodePortPortPtr *int32 - apiServerEnableHTTPBasicAuth bool - apiServerEnableTokenAuth bool - nodeSelector map[string]string - nodeSelectorString string -} - -func (o *initFederationOptions) Bind(flags *pflag.FlagSet, defaultServerImage, defaultEtcdImage string) { - flags.StringVar(&o.dnsZoneName, "dns-zone-name", "", "DNS suffix for this federation. Federated Service DNS names are published with this suffix.") - flags.StringVar(&o.serverImage, "image", defaultServerImage, "Image to use for federation API server and controller manager binaries.") - flags.StringVar(&o.dnsProvider, "dns-provider", "", "Dns provider to be used for this deployment.") - flags.StringVar(&o.dnsProviderConfig, "dns-provider-config", "", "Config file path on local file system for configuring DNS provider.") - flags.StringVar(&o.etcdImage, "etcd-image", defaultEtcdImage, "Image to use for etcd server.") - flags.StringVar(&o.etcdPVCapacity, "etcd-pv-capacity", "10Gi", "Size of persistent volume claim to be used for etcd.") - flags.StringVar(&o.etcdPVStorageClass, "etcd-pv-storage-class", "", "The storage class of the persistent volume claim used for etcd. Must be provided if a default storage class is not enabled for the host cluster.") - flags.BoolVar(&o.etcdPersistentStorage, "etcd-persistent-storage", true, "Use persistent volume for etcd. Defaults to 'true'.") - flags.BoolVar(&o.dryRun, "dry-run", false, "dry run without sending commands to server.") - flags.StringVar(&o.apiServerOverridesString, "apiserver-arg-overrides", "", "comma separated list of federation-apiserver arguments to override: Example \"--arg1=value1,--arg2=value2...\"") - flags.StringVar(&o.controllerManagerOverridesString, "controllermanager-arg-overrides", "", "comma separated list of federation-controller-manager arguments to override: Example \"--arg1=value1,--arg2=value2...\"") - flags.StringVar(&o.apiServerServiceTypeString, apiserverServiceTypeFlag, string(v1.ServiceTypeLoadBalancer), "The type of service to create for federation API server. Options: 'LoadBalancer' (default), 'NodePort'.") - flags.StringVar(&o.apiServerAdvertiseAddress, apiserverAdvertiseAddressFlag, "", "Preferred address to advertise api server nodeport service. Valid only if '"+apiserverServiceTypeFlag+"=NodePort'.") - flags.Int32Var(&o.apiServerNodePortPort, apiserverPortFlag, 0, "Preferred port to use for api server nodeport service (0 for random port assignment). Valid only if '"+apiserverServiceTypeFlag+"=NodePort'.") - flags.BoolVar(&o.apiServerEnableHTTPBasicAuth, "apiserver-enable-basic-auth", false, "Enables HTTP Basic authentication for the federation-apiserver. Defaults to false.") - flags.BoolVar(&o.apiServerEnableTokenAuth, "apiserver-enable-token-auth", false, "Enables token authentication for the federation-apiserver. Defaults to false.") - flags.StringVar(&o.nodeSelectorString, "node-selector", "", "comma separated list of nodeSelector arguments: Example \"arg1=value1,arg2=value2...\"") -} - -// NewCmdInit defines the `init` command that bootstraps a federation -// control plane inside a set of host clusters. -func NewCmdInit(cmdOut io.Writer, config util.AdminConfig, defaultServerImage, defaultEtcdImage string) *cobra.Command { - opts := &initFederation{} - - cmd := &cobra.Command{ - Use: "init FEDERATION_NAME --host-cluster-context=HOST_CONTEXT", - Short: "Initialize a federation control plane", - Long: init_long, - Example: init_example, - Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(opts.Complete(cmd, args)) - cmdutil.CheckErr(opts.Run(cmdOut, config)) - }, - } - - flags := cmd.Flags() - opts.commonOptions.Bind(flags) - opts.options.Bind(flags, defaultServerImage, defaultEtcdImage) - - return cmd -} - -type entityKeyPairs struct { - ca *triple.KeyPair - server *triple.KeyPair - controllerManager *triple.KeyPair - admin *triple.KeyPair -} - -type credentials struct { - username string - password string - token string - certEntKeyPairs *entityKeyPairs -} - -// Complete ensures that options are valid and marshals them if necessary. -func (i *initFederation) Complete(cmd *cobra.Command, args []string) error { - if len(i.options.dnsProvider) == 0 { - return fmt.Errorf("--dns-provider is mandatory") - } - - err := i.commonOptions.SetName(cmd, args) - if err != nil { - return err - } - - i.options.apiServerServiceType = v1.ServiceType(i.options.apiServerServiceTypeString) - if i.options.apiServerServiceType != v1.ServiceTypeLoadBalancer && i.options.apiServerServiceType != v1.ServiceTypeNodePort { - return fmt.Errorf("invalid %s: %s, should be either %s or %s", apiserverServiceTypeFlag, i.options.apiServerServiceType, v1.ServiceTypeLoadBalancer, v1.ServiceTypeNodePort) - } - if i.options.apiServerAdvertiseAddress != "" { - ip := net.ParseIP(i.options.apiServerAdvertiseAddress) - if ip == nil { - return fmt.Errorf("invalid %s: %s, should be a valid ip address", apiserverAdvertiseAddressFlag, i.options.apiServerAdvertiseAddress) - } - if i.options.apiServerServiceType != v1.ServiceTypeNodePort { - return fmt.Errorf("%s should be passed only with '%s=NodePort'", apiserverAdvertiseAddressFlag, apiserverServiceTypeFlag) - } - } - - if i.options.apiServerNodePortPort != 0 { - if i.options.apiServerServiceType != v1.ServiceTypeNodePort { - return fmt.Errorf("%s should be passed only with '%s=NodePort'", apiserverPortFlag, apiserverServiceTypeFlag) - } - i.options.apiServerNodePortPortPtr = &i.options.apiServerNodePortPort - } else { - i.options.apiServerNodePortPortPtr = nil - } - if i.options.apiServerNodePortPort < 0 || i.options.apiServerNodePortPort > 65535 { - return fmt.Errorf("Please provide a valid port number for %s", apiserverPortFlag) - } - - i.options.apiServerOverrides, err = marshallOverrides(i.options.apiServerOverridesString) - if err != nil { - return fmt.Errorf("error marshalling --apiserver-arg-overrides: %v", err) - } - i.options.controllerManagerOverrides, err = marshallOverrides(i.options.controllerManagerOverridesString) - if err != nil { - return fmt.Errorf("error marshalling --controllermanager-arg-overrides: %v", err) - } - i.options.nodeSelector, err = marshallOverrides(i.options.nodeSelectorString) - if err != nil { - return fmt.Errorf("error marshalling --node-selector: %v", err) - } - - if i.options.dnsProviderConfig != "" { - if _, err := os.Stat(i.options.dnsProviderConfig); err != nil { - return fmt.Errorf("error reading file provided to --dns-provider-config flag, err: %v", err) - } - } - - return nil -} - -// Run initializes a federation control plane. -// See the design doc in https://github.com/kubernetes/kubernetes/pull/34484 -// for details. -func (i *initFederation) Run(cmdOut io.Writer, config util.AdminConfig) error { - hostFactory := config.ClusterFactory(i.commonOptions.Host, i.commonOptions.Kubeconfig) - hostClientset, err := hostFactory.ClientSet() - if err != nil { - return err - } - - rbacAvailable := true - rbacVersionedClientset, err := util.GetVersionedClientForRBACOrFail(hostFactory) - if err != nil { - if _, ok := err.(*util.NoRBACAPIError); !ok { - return err - } - // If the error is type NoRBACAPIError, We continue to create the rest of - // the resources, without the SA and roles (in the absence of RBAC support). - rbacAvailable = false - } - - serverName := fmt.Sprintf("%s-%s", i.commonOptions.Name, APIServerNameSuffix) - serverCredName := fmt.Sprintf("%s-%s", serverName, CredentialSuffix) - cmName := fmt.Sprintf("%s-%s", i.commonOptions.Name, CMNameSuffix) - cmKubeconfigName := fmt.Sprintf("%s-%s", cmName, KubeconfigNameSuffix) - - var dnsProviderConfigBytes []byte - if i.options.dnsProviderConfig != "" { - dnsProviderConfigBytes, err = ioutil.ReadFile(i.options.dnsProviderConfig) - if err != nil { - return fmt.Errorf("Error reading file provided to --dns-provider-config flag, err: %v", err) - } - } - - fmt.Fprintf(cmdOut, "Creating a namespace %s for federation system components...", i.commonOptions.FederationSystemNamespace) - glog.V(4).Infof("Creating a namespace %s for federation system components", i.commonOptions.FederationSystemNamespace) - _, err = createNamespace(hostClientset, i.commonOptions.Name, i.commonOptions.FederationSystemNamespace, i.options.dryRun) - if err != nil { - return err - } - - fmt.Fprintln(cmdOut, " done") - - fmt.Fprint(cmdOut, "Creating federation control plane service...") - glog.V(4).Info("Creating federation control plane service") - svc, ips, hostnames, err := createService(cmdOut, hostClientset, i.commonOptions.FederationSystemNamespace, serverName, i.commonOptions.Name, i.options.apiServerAdvertiseAddress, i.options.apiServerNodePortPortPtr, i.options.apiServerServiceType, i.options.dryRun) - if err != nil { - return err - } - fmt.Fprintln(cmdOut, " done") - glog.V(4).Infof("Created service named %s with IP addresses %v, hostnames %v", svc.Name, ips, hostnames) - - fmt.Fprint(cmdOut, "Creating federation control plane objects (credentials, persistent volume claim)...") - glog.V(4).Info("Generating TLS certificates and credentials for communicating with the federation API server") - credentials, err := generateCredentials(i.commonOptions.FederationSystemNamespace, i.commonOptions.Name, svc.Name, HostClusterLocalDNSZoneName, serverCredName, ips, hostnames, i.options.apiServerEnableHTTPBasicAuth, i.options.apiServerEnableTokenAuth, i.options.dryRun) - if err != nil { - return err - } - - // Create the secret containing the credentials. - _, err = createAPIServerCredentialsSecret(hostClientset, i.commonOptions.FederationSystemNamespace, serverCredName, i.commonOptions.Name, credentials, i.options.dryRun) - if err != nil { - return err - } - glog.V(4).Info("Certificates and credentials generated") - - glog.V(4).Info("Creating an entry in the kubeconfig file with the certificate and credential data") - _, err = createControllerManagerKubeconfigSecret(hostClientset, i.commonOptions.FederationSystemNamespace, i.commonOptions.Name, svc.Name, cmKubeconfigName, credentials.certEntKeyPairs, i.options.dryRun) - if err != nil { - return err - } - glog.V(4).Info("Credentials secret successfully created") - - var pvc *api.PersistentVolumeClaim - if i.options.etcdPersistentStorage { - glog.V(4).Info("Creating a persistent volume and a claim to store the federation API server's state, including etcd data") - pvc, err = createPVC(hostClientset, i.commonOptions.FederationSystemNamespace, svc.Name, i.commonOptions.Name, i.options.etcdPVCapacity, i.options.etcdPVStorageClass, i.options.dryRun) - if err != nil { - return err - } - glog.V(4).Info("Persistent volume and claim created") - fmt.Fprintln(cmdOut, " done") - } - - // Since only one IP address can be specified as advertise address, - // we arbitrarily pick the first available IP address - // Pick user provided apiserverAdvertiseAddress over other available IP addresses. - advertiseAddress := i.options.apiServerAdvertiseAddress - if advertiseAddress == "" && len(ips) > 0 { - advertiseAddress = ips[0] - } - - fmt.Fprint(cmdOut, "Creating federation component deployments...") - glog.V(4).Info("Creating federation control plane components") - _, err = createAPIServer(hostClientset, i.commonOptions.FederationSystemNamespace, serverName, i.commonOptions.Name, i.options.serverImage, i.options.etcdImage, advertiseAddress, serverCredName, i.options.apiServerEnableHTTPBasicAuth, i.options.apiServerEnableTokenAuth, i.options.apiServerOverrides, pvc, i.options.dryRun, i.options.nodeSelector) - if err != nil { - return err - } - glog.V(4).Info("Successfully created federation API server") - - sa := &api.ServiceAccount{} - sa.Name = "" - // Create a service account and related RBAC roles if the host cluster has RBAC support. - // TODO: We must evaluate creating a separate service account even when RBAC support is missing - if rbacAvailable { - glog.V(4).Info("Creating service account for federation controller manager in the host cluster") - sa, err = createControllerManagerSA(rbacVersionedClientset, i.commonOptions.FederationSystemNamespace, i.commonOptions.Name, i.options.dryRun) - if err != nil { - return err - } - glog.V(4).Info("Successfully created federation controller manager service account") - - glog.V(4).Info("Creating RBAC role and role bindings for the federation controller manager's service account") - _, _, err = createRoleBindings(rbacVersionedClientset, i.commonOptions.FederationSystemNamespace, sa.Name, i.commonOptions.Name, i.options.dryRun) - if err != nil { - return err - } - glog.V(4).Info("Successfully created RBAC role and role bindings") - } - - glog.V(4).Info("Creating a DNS provider config secret") - dnsProviderSecret, err := createDNSProviderConfigSecret(hostClientset, i.commonOptions.FederationSystemNamespace, dnsProviderSecretName, i.commonOptions.Name, dnsProviderConfigBytes, i.options.dryRun) - if err != nil { - return err - } - glog.V(4).Info("Successfully created DNS provider config secret") - - glog.V(4).Info("Creating federation controller manager deployment") - - _, err = createControllerManager(hostClientset, i.commonOptions.FederationSystemNamespace, i.commonOptions.Name, svc.Name, cmName, i.options.serverImage, cmKubeconfigName, i.options.dnsZoneName, i.options.dnsProvider, i.options.dnsProviderConfig, sa.Name, dnsProviderSecret, i.options.controllerManagerOverrides, i.options.dryRun, i.options.nodeSelector) - if err != nil { - return err - } - glog.V(4).Info("Successfully created federation controller manager deployment") - fmt.Fprintln(cmdOut, " done") - - fmt.Fprint(cmdOut, "Updating kubeconfig...") - glog.V(4).Info("Updating kubeconfig") - // Pick the first ip/hostname to update the api server endpoint in kubeconfig and also to give information to user - // In case of NodePort Service for api server, ips are node external ips. - endpoint := "" - if len(ips) > 0 { - endpoint = ips[0] - } else if len(hostnames) > 0 { - endpoint = hostnames[0] - } - // If the service is nodeport, need to append the port to endpoint as it is non-standard port - if i.options.apiServerServiceType == v1.ServiceTypeNodePort { - endpoint = endpoint + ":" + strconv.Itoa(int(svc.Spec.Ports[0].NodePort)) - } - - err = updateKubeconfig(config, i.commonOptions.Name, endpoint, i.commonOptions.Kubeconfig, credentials, i.options.dryRun) - if err != nil { - glog.V(4).Infof("Failed to update kubeconfig: %v", err) - return err - } - fmt.Fprintln(cmdOut, " done") - glog.V(4).Info("Successfully updated kubeconfig") - - if !i.options.dryRun { - fmt.Fprint(cmdOut, "Waiting for federation control plane to come up...") - glog.V(4).Info("Waiting for federation control plane to come up") - fedPods := []string{serverName, cmName} - err = waitForPods(cmdOut, hostClientset, fedPods, i.commonOptions.FederationSystemNamespace) - if err != nil { - return err - } - err = waitSrvHealthy(cmdOut, config, i.commonOptions.Name, i.commonOptions.Kubeconfig) - if err != nil { - return err - } - glog.V(4).Info("Federation control plane running") - fmt.Fprintln(cmdOut, " done") - return printSuccess(cmdOut, ips, hostnames, svc) - } - _, err = fmt.Fprintln(cmdOut, "Federation control plane runs (dry run)") - glog.V(4).Info("Federation control plane runs (dry run)") - return err -} - -func createNamespace(clientset client.Interface, federationName, namespace string, dryRun bool) (*api.Namespace, error) { - ns := &api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: namespace, - Annotations: map[string]string{federation.FederationNameAnnotation: federationName}, - }, - } - - if dryRun { - return ns, nil - } - - return clientset.Core().Namespaces().Create(ns) -} - -func createService(cmdOut io.Writer, clientset client.Interface, namespace, svcName, federationName, apiserverAdvertiseAddress string, apiserverPort *int32, apiserverServiceType v1.ServiceType, dryRun bool) (*api.Service, []string, []string, error) { - port := api.ServicePort{ - Name: "https", - Protocol: "TCP", - Port: 443, - TargetPort: intstr.FromString(apiServerSecurePortName), - } - if apiserverServiceType == v1.ServiceTypeNodePort && apiserverPort != nil { - port.NodePort = *apiserverPort - } - svc := &api.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: svcName, - Namespace: namespace, - Labels: componentLabel, - Annotations: map[string]string{federation.FederationNameAnnotation: federationName}, - }, - Spec: api.ServiceSpec{ - Type: api.ServiceType(apiserverServiceType), - Selector: apiserverSvcSelector, - Ports: []api.ServicePort{port}, - }, - } - - if dryRun { - return svc, nil, nil, nil - } - - var err error - svc, err = clientset.Core().Services(namespace).Create(svc) - if err != nil { - return nil, nil, nil, err - } - - ips := []string{} - hostnames := []string{} - if apiserverServiceType == v1.ServiceTypeLoadBalancer { - ips, hostnames, err = waitForLoadBalancerAddress(cmdOut, clientset, svc, dryRun) - } else { - if apiserverAdvertiseAddress != "" { - ips = append(ips, apiserverAdvertiseAddress) - } else { - ips, err = getClusterNodeIPs(clientset) - } - } - if err != nil { - return svc, nil, nil, err - } - - return svc, ips, hostnames, err -} - -func getClusterNodeIPs(clientset client.Interface) ([]string, error) { - preferredAddressTypes := []api.NodeAddressType{ - api.NodeExternalIP, - api.NodeInternalIP, - } - nodeList, err := clientset.Core().Nodes().List(metav1.ListOptions{}) - if err != nil { - return nil, err - } - nodeAddresses := []string{} - for _, node := range nodeList.Items { - OuterLoop: - for _, addressType := range preferredAddressTypes { - for _, address := range node.Status.Addresses { - if address.Type == addressType { - nodeAddresses = append(nodeAddresses, address.Address) - break OuterLoop - } - } - } - } - - return nodeAddresses, nil -} - -func waitForLoadBalancerAddress(cmdOut io.Writer, clientset client.Interface, svc *api.Service, dryRun bool) ([]string, []string, error) { - ips := []string{} - hostnames := []string{} - - if dryRun { - return ips, hostnames, nil - } - - err := wait.PollImmediateInfinite(lbAddrRetryInterval, func() (bool, error) { - fmt.Fprint(cmdOut, ".") - pollSvc, err := clientset.Core().Services(svc.Namespace).Get(svc.Name, metav1.GetOptions{}) - if err != nil { - return false, nil - } - if ings := pollSvc.Status.LoadBalancer.Ingress; len(ings) > 0 { - for _, ing := range ings { - if len(ing.IP) > 0 { - ips = append(ips, ing.IP) - } - if len(ing.Hostname) > 0 { - hostnames = append(hostnames, ing.Hostname) - } - } - if len(ips) > 0 || len(hostnames) > 0 { - return true, nil - } - } - return false, nil - }) - if err != nil { - return nil, nil, err - } - - return ips, hostnames, nil -} - -func generateCredentials(svcNamespace, name, svcName, localDNSZoneName, serverCredName string, ips, hostnames []string, enableHTTPBasicAuth, enableTokenAuth, dryRun bool) (*credentials, error) { - credentials := credentials{ - username: AdminCN, - } - if enableHTTPBasicAuth { - credentials.password = string(uuid.NewUUID()) - } - if enableTokenAuth { - credentials.token = string(uuid.NewUUID()) - } - - entKeyPairs, err := genCerts(svcNamespace, name, svcName, localDNSZoneName, ips, hostnames) - if err != nil { - return nil, err - } - credentials.certEntKeyPairs = entKeyPairs - return &credentials, nil -} - -func genCerts(svcNamespace, name, svcName, localDNSZoneName string, ips, hostnames []string) (*entityKeyPairs, error) { - ca, err := triple.NewCA(name) - if err != nil { - return nil, fmt.Errorf("failed to create CA key and certificate: %v", err) - } - server, err := triple.NewServerKeyPair(ca, APIServerCN, svcName, svcNamespace, localDNSZoneName, ips, hostnames) - if err != nil { - return nil, fmt.Errorf("failed to create federation API server key and certificate: %v", err) - } - cm, err := triple.NewClientKeyPair(ca, ControllerManagerCN, nil) - if err != nil { - return nil, fmt.Errorf("failed to create federation controller manager client key and certificate: %v", err) - } - admin, err := triple.NewClientKeyPair(ca, AdminCN, nil) - if err != nil { - return nil, fmt.Errorf("failed to create client key and certificate for an admin: %v", err) - } - return &entityKeyPairs{ - ca: ca, - server: server, - controllerManager: cm, - admin: admin, - }, nil -} - -func createAPIServerCredentialsSecret(clientset client.Interface, namespace, credentialsName, federationName string, credentials *credentials, dryRun bool) (*api.Secret, error) { - // Build the secret object with API server credentials. - data := map[string][]byte{ - "ca.crt": certutil.EncodeCertPEM(credentials.certEntKeyPairs.ca.Cert), - "server.crt": certutil.EncodeCertPEM(credentials.certEntKeyPairs.server.Cert), - "server.key": certutil.EncodePrivateKeyPEM(credentials.certEntKeyPairs.server.Key), - } - if credentials.password != "" { - data["basicauth.csv"] = authFileContents(credentials.username, credentials.password) - } - if credentials.token != "" { - data["token.csv"] = authFileContents(credentials.username, credentials.token) - } - - secret := &api.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: credentialsName, - Namespace: namespace, - Annotations: map[string]string{federation.FederationNameAnnotation: federationName}, - }, - Data: data, - } - - if dryRun { - return secret, nil - } - // Boilerplate to create the secret in the host cluster. - return clientset.Core().Secrets(namespace).Create(secret) -} - -func createControllerManagerKubeconfigSecret(clientset client.Interface, namespace, name, svcName, kubeconfigName string, entKeyPairs *entityKeyPairs, dryRun bool) (*api.Secret, error) { - config := kubeconfigutil.CreateWithCerts( - fmt.Sprintf("https://%s", svcName), - name, - ControllerManagerUser, - certutil.EncodeCertPEM(entKeyPairs.ca.Cert), - certutil.EncodePrivateKeyPEM(entKeyPairs.controllerManager.Key), - certutil.EncodeCertPEM(entKeyPairs.controllerManager.Cert), - ) - - return util.CreateKubeconfigSecret(clientset, config, namespace, kubeconfigName, name, "", dryRun) -} - -func createPVC(clientset client.Interface, namespace, svcName, federationName, etcdPVCapacity, etcdPVStorageClass string, dryRun bool) (*api.PersistentVolumeClaim, error) { - capacity, err := resource.ParseQuantity(etcdPVCapacity) - if err != nil { - return nil, err - } - - var storageClassName *string - if len(etcdPVStorageClass) > 0 { - storageClassName = &etcdPVStorageClass - } - - pvc := &api.PersistentVolumeClaim{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-etcd-claim", svcName), - Namespace: namespace, - Labels: componentLabel, - Annotations: map[string]string{ - federation.FederationNameAnnotation: federationName, - }, - }, - Spec: api.PersistentVolumeClaimSpec{ - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadWriteOnce, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceStorage: capacity, - }, - }, - StorageClassName: storageClassName, - }, - } - - if dryRun { - return pvc, nil - } - - return clientset.Core().PersistentVolumeClaims(namespace).Create(pvc) -} - -func createAPIServer(clientset client.Interface, namespace, name, federationName, serverImage, etcdImage, advertiseAddress, credentialsName string, hasHTTPBasicAuthFile, hasTokenAuthFile bool, argOverrides map[string]string, pvc *api.PersistentVolumeClaim, dryRun bool, nodeSelector map[string]string) (*extensions.Deployment, error) { - command := []string{ - "/hyperkube", - "federation-apiserver", - } - argsMap := map[string]string{ - "--bind-address": "0.0.0.0", - "--etcd-servers": "http://localhost:2379", - "--secure-port": fmt.Sprintf("%d", apiServerSecurePort), - "--client-ca-file": "/etc/federation/apiserver/ca.crt", - "--tls-cert-file": "/etc/federation/apiserver/server.crt", - "--tls-private-key-file": "/etc/federation/apiserver/server.key", - "--admission-control": "NamespaceLifecycle", - } - - if advertiseAddress != "" { - argsMap["--advertise-address"] = advertiseAddress - } - if hasHTTPBasicAuthFile { - argsMap["--basic-auth-file"] = "/etc/federation/apiserver/basicauth.csv" - } - if hasTokenAuthFile { - argsMap["--token-auth-file"] = "/etc/federation/apiserver/token.csv" - } - - args := argMapsToArgStrings(argsMap, argOverrides) - command = append(command, args...) - - dep := &extensions.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Labels: componentLabel, - Annotations: map[string]string{federation.FederationNameAnnotation: federationName}, - }, - Spec: extensions.DeploymentSpec{ - Replicas: 1, - Template: api.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Labels: apiserverPodLabels, - Annotations: map[string]string{federation.FederationNameAnnotation: federationName}, - }, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Name: "apiserver", - Image: serverImage, - Command: command, - Ports: []api.ContainerPort{ - { - Name: apiServerSecurePortName, - ContainerPort: apiServerSecurePort, - }, - { - Name: "local", - ContainerPort: 8080, - }, - }, - VolumeMounts: []api.VolumeMount{ - { - Name: credentialsName, - MountPath: "/etc/federation/apiserver", - ReadOnly: true, - }, - }, - }, - { - Name: "etcd", - Image: etcdImage, - Command: []string{ - "/usr/local/bin/etcd", - "--data-dir", - "/var/etcd/data", - }, - }, - }, - NodeSelector: nodeSelector, - Volumes: []api.Volume{ - { - Name: credentialsName, - VolumeSource: api.VolumeSource{ - Secret: &api.SecretVolumeSource{ - SecretName: credentialsName, - }, - }, - }, - }, - }, - }, - }, - } - - if pvc != nil { - dataVolumeName := "etcddata" - etcdVolume := api.Volume{ - Name: dataVolumeName, - VolumeSource: api.VolumeSource{ - PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{ - ClaimName: pvc.Name, - }, - }, - } - etcdVolumeMount := api.VolumeMount{ - Name: dataVolumeName, - MountPath: "/var/etcd", - } - - dep.Spec.Template.Spec.Volumes = append(dep.Spec.Template.Spec.Volumes, etcdVolume) - for i, container := range dep.Spec.Template.Spec.Containers { - if container.Name == "etcd" { - dep.Spec.Template.Spec.Containers[i].VolumeMounts = append(dep.Spec.Template.Spec.Containers[i].VolumeMounts, etcdVolumeMount) - } - } - } - - if dryRun { - return dep, nil - } - - createdDep, err := clientset.Extensions().Deployments(namespace).Create(dep) - return createdDep, err -} - -func createControllerManagerSA(clientset client.Interface, namespace, federationName string, dryRun bool) (*api.ServiceAccount, error) { - sa := &api.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: ControllerManagerSA, - Namespace: namespace, - Labels: componentLabel, - Annotations: map[string]string{federation.FederationNameAnnotation: federationName}, - }, - } - if dryRun { - return sa, nil - } - return clientset.Core().ServiceAccounts(namespace).Create(sa) -} - -func createRoleBindings(clientset client.Interface, namespace, saName, federationName string, dryRun bool) (*rbac.Role, *rbac.RoleBinding, error) { - roleName := "federation-system:federation-controller-manager" - role := &rbac.Role{ - // a role to use for bootstrapping the federation-controller-manager so it can access - // secrets in the host cluster to access other clusters. - ObjectMeta: metav1.ObjectMeta{ - Name: roleName, - Namespace: namespace, - Labels: componentLabel, - Annotations: map[string]string{federation.FederationNameAnnotation: federationName}, - }, - Rules: []rbac.PolicyRule{ - rbac.NewRule("get", "list", "watch").Groups(legacyAPIGroup).Resources("secrets").RuleOrDie(), - }, - } - - rolebinding, err := rbac.NewRoleBinding(roleName, namespace).SAs(namespace, saName).Binding() - if err != nil { - return nil, nil, err - } - rolebinding.Labels = componentLabel - rolebinding.Annotations = map[string]string{federation.FederationNameAnnotation: federationName} - - if dryRun { - return role, &rolebinding, nil - } - - newRole, err := clientset.Rbac().Roles(namespace).Create(role) - if err != nil { - return nil, nil, err - } - - newRolebinding, err := clientset.Rbac().RoleBindings(namespace).Create(&rolebinding) - return newRole, newRolebinding, err -} - -func createControllerManager(clientset client.Interface, namespace, name, svcName, cmName, image, kubeconfigName, dnsZoneName, dnsProvider, dnsProviderConfig, saName string, dnsProviderSecret *api.Secret, argOverrides map[string]string, dryRun bool, nodeSelector map[string]string) (*extensions.Deployment, error) { - command := []string{ - "/hyperkube", - "federation-controller-manager", - } - argsMap := map[string]string{ - "--kubeconfig": "/etc/federation/controller-manager/kubeconfig", - } - - argsMap["--master"] = fmt.Sprintf("https://%s", svcName) - argsMap["--dns-provider"] = dnsProvider - argsMap["--federation-name"] = name - argsMap["--zone-name"] = dnsZoneName - - args := argMapsToArgStrings(argsMap, argOverrides) - command = append(command, args...) - - dep := &extensions.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: cmName, - Namespace: namespace, - Labels: componentLabel, - // We additionally update the details (in annotations) about the - // kube-dns config map which needs to be created in the clusters - // registering to this federation (at kubefed join). - // We wont otherwise have this information available at kubefed join. - Annotations: map[string]string{ - // TODO: the name/domain name pair should ideally be checked for naming convention - // as done in kube-dns federation flags check. - // https://github.com/kubernetes/dns/blob/master/pkg/dns/federation/federation.go - // TODO v2: Until kube-dns can handle trailing periods we strip them all. - // See https://github.com/kubernetes/dns/issues/67 - util.FedDomainMapKey: fmt.Sprintf("%s=%s", name, strings.TrimRight(dnsZoneName, ".")), - federation.FederationNameAnnotation: name, - }, - }, - Spec: extensions.DeploymentSpec{ - Replicas: 1, - Template: api.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Name: cmName, - Labels: controllerManagerPodLabels, - Annotations: map[string]string{federation.FederationNameAnnotation: name}, - }, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Name: "controller-manager", - Image: image, - Command: command, - VolumeMounts: []api.VolumeMount{ - { - Name: kubeconfigName, - MountPath: "/etc/federation/controller-manager", - ReadOnly: true, - }, - }, - Env: []api.EnvVar{ - { - Name: "POD_NAMESPACE", - ValueFrom: &api.EnvVarSource{ - FieldRef: &api.ObjectFieldSelector{ - FieldPath: "metadata.namespace", - }, - }, - }, - }, - }, - }, - NodeSelector: nodeSelector, - Volumes: []api.Volume{ - { - Name: kubeconfigName, - VolumeSource: api.VolumeSource{ - Secret: &api.SecretVolumeSource{ - SecretName: kubeconfigName, - }, - }, - }, - }, - }, - }, - }, - } - - if saName != "" { - dep.Spec.Template.Spec.ServiceAccountName = saName - } - - if dnsProviderSecret != nil { - dep = addDNSProviderConfig(dep, dnsProviderSecret.Name) - if dnsProvider == util.FedDNSProviderCoreDNS { - var err error - dep, err = addCoreDNSServerAnnotation(dep, dnsZoneName, dnsProviderConfig) - if err != nil { - return nil, err - } - } - } - - if dryRun { - return dep, nil - } - - return clientset.Extensions().Deployments(namespace).Create(dep) -} - -func marshallOverrides(overrideArgString string) (map[string]string, error) { - if overrideArgString == "" { - return nil, nil - } - - argsMap := make(map[string]string) - overrideArgs := strings.Split(overrideArgString, ",") - for _, overrideArg := range overrideArgs { - splitArg := strings.SplitN(overrideArg, "=", 2) - if len(splitArg) != 2 { - return nil, fmt.Errorf("wrong format for override arg: %s", overrideArg) - } - key := strings.TrimSpace(splitArg[0]) - val := strings.TrimSpace(splitArg[1]) - if len(key) == 0 { - return nil, fmt.Errorf("wrong format for override arg: %s, arg name cannot be empty", overrideArg) - } - argsMap[key] = val - } - return argsMap, nil -} - -func argMapsToArgStrings(argsMap, overrides map[string]string) []string { - for key, val := range overrides { - argsMap[key] = val - } - args := []string{} - for key, value := range argsMap { - args = append(args, fmt.Sprintf("%s=%s", key, value)) - } - // This is needed for the unit test deep copy to get an exact match - sort.Strings(args) - return args -} - -func waitForPods(cmdOut io.Writer, clientset client.Interface, fedPods []string, namespace string) error { - err := wait.PollInfinite(podWaitInterval, func() (bool, error) { - fmt.Fprint(cmdOut, ".") - podCheck := len(fedPods) - podList, err := clientset.Core().Pods(namespace).List(metav1.ListOptions{}) - if err != nil { - return false, nil - } - for _, pod := range podList.Items { - for _, fedPod := range fedPods { - if strings.HasPrefix(pod.Name, fedPod) && pod.Status.Phase == "Running" { - podCheck -= 1 - } - } - //ensure that all pods are in running state or keep waiting - if podCheck == 0 { - return true, nil - } - } - return false, nil - }) - return err -} - -func waitSrvHealthy(cmdOut io.Writer, config util.AdminConfig, context, kubeconfig string) error { - fedClientSet, err := config.FederationClientset(context, kubeconfig) - if err != nil { - return err - } - fedDiscoveryClient := fedClientSet.Discovery() - err = wait.PollInfinite(podWaitInterval, func() (bool, error) { - fmt.Fprint(cmdOut, ".") - body, err := fedDiscoveryClient.RESTClient().Get().AbsPath("/healthz").Do().Raw() - if err != nil { - return false, nil - } - if strings.EqualFold(string(body), "ok") { - return true, nil - } - return false, nil - }) - return err -} - -func printSuccess(cmdOut io.Writer, ips, hostnames []string, svc *api.Service) error { - svcEndpoints := append(ips, hostnames...) - endpoints := strings.Join(svcEndpoints, ", ") - if svc.Spec.Type == api.ServiceTypeNodePort { - endpoints = ips[0] + ":" + strconv.Itoa(int(svc.Spec.Ports[0].NodePort)) - if len(ips) > 1 { - endpoints = endpoints + ", ..." - } - } - - _, err := fmt.Fprintf(cmdOut, "Federation API server is running at: %s\n", endpoints) - return err -} - -func updateKubeconfig(config util.AdminConfig, name, endpoint, kubeConfigPath string, credentials *credentials, dryRun bool) error { - po := config.PathOptions() - po.LoadingRules.ExplicitPath = kubeConfigPath - kubeconfig, err := po.GetStartingConfig() - if err != nil { - return err - } - - // Populate API server endpoint info. - cluster := clientcmdapi.NewCluster() - // Prefix "https" as the URL scheme to endpoint. - if !strings.HasPrefix(endpoint, "https://") { - endpoint = fmt.Sprintf("https://%s", endpoint) - } - cluster.Server = endpoint - cluster.CertificateAuthorityData = certutil.EncodeCertPEM(credentials.certEntKeyPairs.ca.Cert) - - // Populate credentials. - authInfo := clientcmdapi.NewAuthInfo() - authInfo.ClientCertificateData = certutil.EncodeCertPEM(credentials.certEntKeyPairs.admin.Cert) - authInfo.ClientKeyData = certutil.EncodePrivateKeyPEM(credentials.certEntKeyPairs.admin.Key) - authInfo.Token = credentials.token - - var httpBasicAuthInfo *clientcmdapi.AuthInfo - if credentials.password != "" { - httpBasicAuthInfo = clientcmdapi.NewAuthInfo() - httpBasicAuthInfo.Password = credentials.password - httpBasicAuthInfo.Username = credentials.username - } - - // Populate context. - context := clientcmdapi.NewContext() - context.Cluster = name - context.AuthInfo = name - - // Update the config struct with API server endpoint info, - // credentials and context. - kubeconfig.Clusters[name] = cluster - kubeconfig.AuthInfos[name] = authInfo - if httpBasicAuthInfo != nil { - kubeconfig.AuthInfos[fmt.Sprintf("%s-basic-auth", name)] = httpBasicAuthInfo - } - kubeconfig.Contexts[name] = context - - if !dryRun { - // Write the update kubeconfig. - if err := clientcmd.ModifyConfig(po, *kubeconfig, true); err != nil { - return err - } - } - - return nil -} - -func createDNSProviderConfigSecret(clientset client.Interface, namespace, name, federationName string, dnsProviderConfigBytes []byte, dryRun bool) (*api.Secret, error) { - if dnsProviderConfigBytes == nil { - return nil, nil - } - - secretSpec := &api.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Annotations: map[string]string{federation.FederationNameAnnotation: federationName}, - }, - Data: map[string][]byte{ - name: dnsProviderConfigBytes, - }, - } - - var secret *api.Secret - var err error - if !dryRun { - secret, err = clientset.Core().Secrets(namespace).Create(secretSpec) - if err != nil { - return nil, err - } - } - return secret, nil -} - -func addDNSProviderConfig(dep *extensions.Deployment, secretName string) *extensions.Deployment { - const ( - dnsProviderConfigVolume = "config-volume" - dnsProviderConfigMountPath = "/etc/federation/dns-provider" - ) - - // Create a volume from dns-provider secret - volume := api.Volume{ - Name: dnsProviderConfigVolume, - VolumeSource: api.VolumeSource{ - Secret: &api.SecretVolumeSource{ - SecretName: secretName, - }, - }, - } - dep.Spec.Template.Spec.Volumes = append(dep.Spec.Template.Spec.Volumes, volume) - - // Mount dns-provider secret volume to controller-manager container - volumeMount := api.VolumeMount{ - Name: dnsProviderConfigVolume, - MountPath: dnsProviderConfigMountPath, - ReadOnly: true, - } - dep.Spec.Template.Spec.Containers[0].VolumeMounts = append(dep.Spec.Template.Spec.Containers[0].VolumeMounts, volumeMount) - dep.Spec.Template.Spec.Containers[0].Command = append(dep.Spec.Template.Spec.Containers[0].Command, fmt.Sprintf("--dns-provider-config=%s/%s", dnsProviderConfigMountPath, secretName)) - - return dep -} - -// authFileContents returns a CSV string containing the contents of an -// authentication file in the format required by the federation-apiserver. -func authFileContents(username, authSecret string) []byte { - return []byte(fmt.Sprintf("%s,%s,%s\n", authSecret, username, uuid.NewUUID())) -} - -func addCoreDNSServerAnnotation(deployment *extensions.Deployment, dnsZoneName, dnsProviderConfig string) (*extensions.Deployment, error) { - var cfg coredns.Config - if err := gcfg.ReadFileInto(&cfg, dnsProviderConfig); err != nil { - return nil, err - } - - deployment.Annotations[util.FedDNSZoneName] = dnsZoneName - deployment.Annotations[util.FedNameServer] = cfg.Global.CoreDNSEndpoints - deployment.Annotations[util.FedDNSProvider] = util.FedDNSProviderCoreDNS - return deployment, nil -} diff --git a/federation/pkg/kubefed/init/init_test.go b/federation/pkg/kubefed/init/init_test.go deleted file mode 100644 index f33a8665ff6..00000000000 --- a/federation/pkg/kubefed/init/init_test.go +++ /dev/null @@ -1,1605 +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 init - -import ( - "bytes" - "crypto/tls" - "crypto/x509" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/http/httptest" - "net/url" - "os" - "sort" - "strconv" - "strings" - "testing" - "time" - - "k8s.io/api/core/v1" - "k8s.io/api/extensions/v1beta1" - rbacv1 "k8s.io/api/rbac/v1" - apiequality "k8s.io/apimachinery/pkg/api/equality" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/diff" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/rest/fake" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/kubernetes/federation/apis/federation" - "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/coredns" - kubefedtesting "k8s.io/kubernetes/federation/pkg/kubefed/testing" - "k8s.io/kubernetes/federation/pkg/kubefed/util" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper" - "k8s.io/kubernetes/pkg/api/testapi" - "k8s.io/kubernetes/pkg/apis/rbac" - cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" - cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" - - "gopkg.in/gcfg.v1" -) - -const ( - testNamespace = "test-ns" - testSvcName = "test-service" - testCertValidity = 1 * time.Hour - - helloMsg = "Hello, certificate test!" - - lbIP = "10.20.30.40" - nodeIP = "10.20.30.50" - nodePort = 32111 - - testAPIGroup = "testGroup" - testAPIVersion = "testVersion" -) - -func TestInitFederation(t *testing.T) { - cmdErrMsg := "" - cmdutil.BehaviorOnFatal(func(str string, code int) { - cmdErrMsg = str - }) - - fakeKubeFiles, err := kubefedtesting.FakeKubeconfigFiles() - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - defer kubefedtesting.RemoveFakeKubeconfigFiles(fakeKubeFiles) - - testCases := []struct { - federation string - kubeconfigGlobal string - kubeconfigExplicit string - dnsZoneName string - lbIP string - apiserverServiceType v1.ServiceType - advertiseAddress string - serverImage string - etcdImage string - etcdPVCapacity string - etcdPVStorageClass string - etcdPersistence string - expectedErr string - dnsProvider string - dnsProviderConfig string - dryRun string - apiserverArgOverrides string - cmArgOverrides string - apiserverEnableHTTPBasicAuth bool - apiserverEnableTokenAuth bool - isRBACAPIAvailable bool - nodeSelector string - }{ - { - federation: "union", - kubeconfigGlobal: fakeKubeFiles[0], - kubeconfigExplicit: "", - dnsZoneName: "example.test.", - lbIP: lbIP, - apiserverServiceType: v1.ServiceTypeLoadBalancer, - serverImage: "example.test/foo:bar", - etcdPVCapacity: "5Gi", - etcdPersistence: "true", - expectedErr: "", - dnsProvider: util.FedDNSProviderCoreDNS, - dnsProviderConfig: "dns-provider.conf", - dryRun: "", - apiserverArgOverrides: "--client-ca-file=override,--log-dir=override", - cmArgOverrides: "--dns-provider=override,--log-dir=override", - nodeSelector: "disk=ssh,role=node", - }, - { - federation: "union", - kubeconfigGlobal: fakeKubeFiles[1], - kubeconfigExplicit: fakeKubeFiles[2], - dnsZoneName: "example.test.", - lbIP: lbIP, - apiserverServiceType: v1.ServiceTypeLoadBalancer, - serverImage: "example.test/foo:bar", - etcdPVCapacity: "", //test for default value of pvc-size - etcdPersistence: "true", - expectedErr: "", - dryRun: "", - }, - { - federation: "union", - kubeconfigGlobal: fakeKubeFiles[0], - kubeconfigExplicit: "", - dnsZoneName: "example.test.", - lbIP: lbIP, - apiserverServiceType: v1.ServiceTypeLoadBalancer, - serverImage: "example.test/foo:bar", - etcdPVCapacity: "", - etcdPersistence: "true", - expectedErr: "", - dryRun: "valid-run", - }, - { - federation: "union", - kubeconfigGlobal: fakeKubeFiles[0], - kubeconfigExplicit: "", - dnsZoneName: "example.test.", - lbIP: lbIP, - apiserverServiceType: v1.ServiceTypeLoadBalancer, - serverImage: "example.test/foo:bar", - etcdPVCapacity: "5Gi", - etcdPersistence: "false", - expectedErr: "", - dryRun: "", - }, - { - federation: "union", - kubeconfigGlobal: fakeKubeFiles[0], - kubeconfigExplicit: "", - dnsZoneName: "example.test.", - apiserverServiceType: v1.ServiceTypeNodePort, - serverImage: "example.test/foo:bar", - etcdPVCapacity: "5Gi", - etcdPersistence: "true", - expectedErr: "", - dryRun: "", - }, - { - federation: "union", - kubeconfigGlobal: fakeKubeFiles[0], - kubeconfigExplicit: "", - dnsZoneName: "example.test.", - apiserverServiceType: v1.ServiceTypeNodePort, - advertiseAddress: nodeIP, - serverImage: "example.test/foo:bar", - etcdPVCapacity: "5Gi", - etcdPersistence: "true", - expectedErr: "", - dryRun: "", - }, - { - federation: "union", - kubeconfigGlobal: fakeKubeFiles[0], - kubeconfigExplicit: "", - dnsZoneName: "example.test.", - apiserverServiceType: v1.ServiceTypeNodePort, - advertiseAddress: nodeIP, - serverImage: "example.test/foo:bar", - etcdImage: "gcr.io/google_containers/etcd:latest", - etcdPVCapacity: "5Gi", - etcdPVStorageClass: "fast", - etcdPersistence: "true", - expectedErr: "", - dryRun: "", - apiserverEnableHTTPBasicAuth: true, - apiserverEnableTokenAuth: true, - isRBACAPIAvailable: true, - }, - } - - defaultEtcdImage := "gcr.io/google_containers/etcd:3.1.10" - - //TODO: implement a negative case for dry run - - for i, tc := range testCases { - cmdErrMsg = "" - tmpDirPath := "" - buf := bytes.NewBuffer([]byte{}) - - if tc.dnsProvider == "" { - tc.dnsProvider = "google-clouddns" - } - if tc.dnsProviderConfig != "" { - tmpfile, err := ioutil.TempFile("", tc.dnsProviderConfig) - if err != nil { - t.Fatalf("[%d] unexpected error: %v", i, err) - } - tc.dnsProviderConfig = tmpfile.Name() - defer os.Remove(tmpfile.Name()) - } - - // Check pkg/kubectl/cmd/testing/fake (fakeAPIFactory.DiscoveryClient()) for details of tmpDir - // We want an unique discovery cache path for each test run, else the case from previous case would be used - tmpDirPath, err = ioutil.TempDir("", "") - if err != nil { - t.Fatalf("[%d] unexpected error: %v", i, err) - } - defer os.Remove(tmpDirPath) - - // If tc.etcdImage is set, setting the etcd image via the flag will be - // validated. If not set, the default value will be validated. - if tc.etcdImage == "" { - tc.etcdImage = defaultEtcdImage - } - - hostFactory, err := fakeInitHostFactory(tc.apiserverServiceType, tc.federation, util.DefaultFederationSystemNamespace, tc.advertiseAddress, tc.lbIP, tc.dnsZoneName, tc.serverImage, tc.etcdImage, tc.dnsProvider, tc.dnsProviderConfig, tc.etcdPersistence, tc.etcdPVCapacity, tc.etcdPVStorageClass, tc.apiserverArgOverrides, tc.cmArgOverrides, tmpDirPath, tc.apiserverEnableHTTPBasicAuth, tc.apiserverEnableTokenAuth, tc.isRBACAPIAvailable, tc.nodeSelector) - if err != nil { - t.Fatalf("[%d] unexpected error: %v", i, err) - } - - adminConfig, err := kubefedtesting.NewFakeAdminConfig(hostFactory, nil, "", tc.kubeconfigGlobal) - if err != nil { - t.Fatalf("[%d] unexpected error: %v", i, err) - } - - cmd := NewCmdInit(buf, adminConfig, "serverImage", defaultEtcdImage) - - cmd.Flags().Set("kubeconfig", tc.kubeconfigExplicit) - cmd.Flags().Set("host-cluster-context", "substrate") - cmd.Flags().Set("dns-zone-name", tc.dnsZoneName) - cmd.Flags().Set("image", tc.serverImage) - cmd.Flags().Set("etcd-image", tc.etcdImage) - cmd.Flags().Set("dns-provider", tc.dnsProvider) - cmd.Flags().Set("apiserver-arg-overrides", tc.apiserverArgOverrides) - cmd.Flags().Set("controllermanager-arg-overrides", tc.cmArgOverrides) - - if tc.dnsProviderConfig != "" { - cmd.Flags().Set("dns-provider-config", tc.dnsProviderConfig) - } - if tc.etcdPVCapacity != "" { - cmd.Flags().Set("etcd-pv-capacity", tc.etcdPVCapacity) - } - if tc.etcdPVStorageClass != "" { - cmd.Flags().Set("etcd-pv-storage-class", tc.etcdPVStorageClass) - } - if tc.etcdPersistence != "true" { - cmd.Flags().Set("etcd-persistent-storage", tc.etcdPersistence) - } - if tc.apiserverServiceType != v1.ServiceTypeLoadBalancer { - cmd.Flags().Set(apiserverServiceTypeFlag, string(tc.apiserverServiceType)) - cmd.Flags().Set(apiserverAdvertiseAddressFlag, tc.advertiseAddress) - } - if tc.dryRun == "valid-run" { - cmd.Flags().Set("dry-run", "true") - } - if tc.apiserverEnableHTTPBasicAuth { - cmd.Flags().Set("apiserver-enable-basic-auth", "true") - } - if tc.apiserverEnableTokenAuth { - cmd.Flags().Set("apiserver-enable-token-auth", "true") - } - if tc.nodeSelector != "" { - cmd.Flags().Set("node-selector", tc.nodeSelector) - } - - cmd.Run(cmd, []string{tc.federation}) - - if tc.expectedErr == "" { - // uses the name from the federation, not the response - // Actual data passed are tested in the fake secret and cluster - // REST clients. - endpoint := getEndpoint(tc.apiserverServiceType, tc.lbIP, tc.advertiseAddress) - wantedSuffix := fmt.Sprintf("Federation API server is running at: %s\n", endpoint) - if tc.dryRun != "" { - wantedSuffix = fmt.Sprintf("Federation control plane runs (dry run)\n") - } - - if got := buf.String(); !strings.HasSuffix(got, wantedSuffix) { - t.Errorf("[%d] unexpected output: got: %s, wanted suffix: %s", i, got, wantedSuffix) - if cmdErrMsg != "" { - t.Errorf("[%d] unexpected error message: %s", i, cmdErrMsg) - } - } - } else { - if cmdErrMsg != tc.expectedErr { - t.Errorf("[%d] expected error: %s, got: %s, output: %s", i, tc.expectedErr, cmdErrMsg, buf.String()) - } - return - } - - testKubeconfigUpdate(t, tc.apiserverServiceType, tc.federation, tc.advertiseAddress, tc.lbIP, tc.kubeconfigGlobal, tc.kubeconfigExplicit, tc.apiserverEnableHTTPBasicAuth, tc.apiserverEnableTokenAuth) - } -} - -func TestMarshallAndMergeOverrides(t *testing.T) { - testCases := []struct { - overrideParams string - expectedSet sets.String - expectedErr string - }{ - { - overrideParams: "valid-format-param1=override1,valid-format-param2=override2", - expectedSet: sets.NewString("arg2=val2", "arg1=val1", "valid-format-param1=override1", "valid-format-param2=override2"), - expectedErr: "", - }, - { - overrideParams: "valid-format-param1=override1,arg1=override1", - expectedSet: sets.NewString("arg2=val2", "arg1=override1", "valid-format-param1=override1"), - expectedErr: "", - }, - { - overrideParams: "zero-value-arg=", - expectedSet: sets.NewString("arg2=val2", "arg1=val1", "zero-value-arg="), - expectedErr: "", - }, - { - overrideParams: "wrong-format-arg", - expectedErr: "wrong format for override arg: wrong-format-arg", - }, - { - // TODO: Multiple arg values separated by , are not supported yet - overrideParams: "multiple-equalto-char=first-key=1", - expectedSet: sets.NewString("arg2=val2", "arg1=val1", "multiple-equalto-char=first-key=1"), - expectedErr: "", - }, - { - overrideParams: "=wrong-format-only-value", - expectedErr: "wrong format for override arg: =wrong-format-only-value, arg name cannot be empty", - }, - } - - for i, tc := range testCases { - args, err := marshallOverrides(tc.overrideParams) - if tc.expectedErr == "" { - origArgs := map[string]string{ - "arg1": "val1", - "arg2": "val2", - } - merged := argMapsToArgStrings(origArgs, args) - - got := sets.NewString(merged...) - want := tc.expectedSet - - if !got.Equal(want) { - t.Errorf("[%d] unexpected output: got: %v, want: %v", i, got, want) - } - } else { - if err.Error() != tc.expectedErr { - t.Errorf("[%d] unexpected error output: got: %s, want: %s", i, err.Error(), tc.expectedErr) - } - } - } -} - -// TestCertsTLS tests TLS handshake with client authentication for any server -// name. There is a separate test below to test the certificate generation -// end-to-end over HTTPS. -// TODO(madhusudancs): Consider using a deterministic random number generator -// for generating certificates in tests. -func TestCertsTLS(t *testing.T) { - params := []certParams{ - { - cAddr: "10.1.2.3", - ips: []string{"10.1.2.3", "10.2.3.4"}, - hostnames: []string{"federation.test", "federation2.test"}, - }, - { - cAddr: "10.10.20.30", - ips: []string{"10.20.30.40", "10.64.128.4"}, - hostnames: []string{"tls.federation.test"}, - }, - } - - tlsCfgs, err := tlsConfigs(params) - if err != nil { - t.Errorf("failed to generate tls configs: %v", err) - // No point in proceeding further - return - } - - testCases := []struct { - serverName string - sCfg *tls.Config - cCfg *tls.Config - failType string - }{ - { - serverName: "10.1.2.3", - sCfg: tlsCfgs[0].server, - cCfg: tlsCfgs[0].client, - }, - { - serverName: "10.2.3.4", - sCfg: tlsCfgs[0].server, - cCfg: tlsCfgs[0].client, - }, - { - serverName: "federation.test", - sCfg: tlsCfgs[0].server, - cCfg: tlsCfgs[0].client, - }, - { - serverName: "federation2.test", - sCfg: tlsCfgs[0].server, - cCfg: tlsCfgs[0].client, - }, - { - serverName: "10.20.30.40", - sCfg: tlsCfgs[1].server, - cCfg: tlsCfgs[1].client, - }, - { - serverName: "tls.federation.test", - sCfg: tlsCfgs[1].server, - cCfg: tlsCfgs[1].client, - }, - { - serverName: "10.100.200.50", - sCfg: tlsCfgs[0].server, - cCfg: tlsCfgs[0].client, - failType: "HostnameError", - }, - { - serverName: "noexist.test", - sCfg: tlsCfgs[0].server, - cCfg: tlsCfgs[0].client, - failType: "HostnameError", - }, - { - serverName: "10.64.128.4", - sCfg: tlsCfgs[0].server, - cCfg: tlsCfgs[0].client, - failType: "HostnameError", - }, - { - serverName: "tls.federation.test", - sCfg: tlsCfgs[0].server, - cCfg: tlsCfgs[0].client, - failType: "HostnameError", - }, - { - serverName: "10.1.2.3", - sCfg: tlsCfgs[0].server, - cCfg: tlsCfgs[1].client, - failType: "UnknownAuthorityError", - }, - { - serverName: "federation2.test", - sCfg: tlsCfgs[0].server, - cCfg: tlsCfgs[1].client, - failType: "UnknownAuthorityError", - }, - { - serverName: "10.1.2.3", - sCfg: tlsCfgs[1].server, - cCfg: tlsCfgs[0].client, - failType: "HostnameError", - }, - { - serverName: "federation2.test", - sCfg: tlsCfgs[1].server, - cCfg: tlsCfgs[0].client, - failType: "HostnameError", - }, - } - - for i, tc := range testCases { - // Make a copy of the client config before modifying it. - // We can't do a regular pointer deref shallow copy because - // tls.Config contains an unexported sync.Once field which - // must not be copied. This was pointed out by go vet. - cCfg := copyTLSConfig(tc.cCfg) - cCfg.ServerName = tc.serverName - cCfg.BuildNameToCertificate() - - err := tlsHandshake(t, tc.sCfg, cCfg) - if len(tc.failType) > 0 { - switch tc.failType { - case "HostnameError": - if _, ok := err.(x509.HostnameError); !ok { - t.Errorf("[%d] unexpected error: want x509.HostnameError, got: %T", i, err) - } - case "UnknownAuthorityError": - if _, ok := err.(x509.UnknownAuthorityError); !ok { - t.Errorf("[%d] unexpected error: want x509.UnknownAuthorityError, got: %T", i, err) - } - default: - t.Errorf("cannot handle error type: %s", tc.failType) - - } - } else if err != nil { - t.Errorf("[%d] unexpected error: %v", i, err) - } - } -} - -// TestCertsHTTPS cannot test client authentication for non-localhost server -// names, but it tests TLS handshake end-to-end over HTTPS. -func TestCertsHTTPS(t *testing.T) { - params := []certParams{ - { - // Unfortunately, due to the limitation in the way Go - // net/http/httptest package sets up the test HTTPS/TLS server, - // 127.0.0.1 is the only accepted server address. So, we need to - // generate certificates for this address. - cAddr: "127.0.0.1", - ips: []string{"127.0.0.1"}, - hostnames: []string{}, - }, - { - // Unfortunately, due to the limitation in the way Go - // net/http/httptest package sets up the test HTTPS/TLS server, - // 127.0.0.1 is the only accepted server address. So, we need to - // generate certificates for this address. - cAddr: "localhost", - ips: []string{"127.0.0.1"}, - hostnames: []string{"localhost"}, - }, - } - - tlsCfgs, err := tlsConfigs(params) - if err != nil { - t.Errorf("failed to generate tls configs: %v", err) - // No point in proceeding further - return - } - - testCases := []struct { - sCfg *tls.Config - cCfg *tls.Config - fail bool - }{ - { - sCfg: tlsCfgs[0].server, - cCfg: tlsCfgs[0].client, - fail: false, - }, - { - sCfg: tlsCfgs[0].server, - cCfg: tlsCfgs[1].client, - fail: true, - }, - { - sCfg: tlsCfgs[1].server, - cCfg: tlsCfgs[0].client, - fail: true, - }, - } - - for i, tc := range testCases { - // Make a copy of the client config before modifying it. - // We can't do a regular pointer deref shallow copy because - // tls.Config contains an unexported sync.Once field which - // must not be copied. This was pointed out by go vet. - cCfg := copyTLSConfig(tc.cCfg) - cCfg.BuildNameToCertificate() - - s, err := fakeHTTPSServer(tc.sCfg) - if err != nil { - t.Errorf("[%d] unexpected error starting TLS server: %v", i, err) - // No point in proceeding - continue - } - defer s.Close() - - tr := &http.Transport{ - TLSClientConfig: cCfg, - } - client := &http.Client{Transport: tr} - resp, err := client.Get(s.URL) - if tc.fail { - _, ok := err.(*url.Error) - if !ok || !strings.HasSuffix(err.Error(), "x509: certificate signed by unknown authority") { - t.Errorf("[%d] unexpected error: want x509.HostnameError, got: %T", i, err) - } - // We are done for this test. - continue - } else if err != nil { - t.Errorf("[%d] unexpected error while sending GET request to the server: %T", i, err) - // No point in proceeding - continue - } - defer resp.Body.Close() - - got, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Errorf("[%d] unexpected error reading server response: %v", i, err) - } else if string(got) != helloMsg { - t.Errorf("[%d] want %q, got %q", i, helloMsg, got) - } - } -} - -func fakeInitHostFactory(apiserverServiceType v1.ServiceType, federationName, namespaceName, advertiseAddress, lbIp, dnsZoneName, serverImage, etcdImage, dnsProvider, dnsProviderConfig, etcdPersistence, etcdPVCapacity, etcdPVStorageClass, apiserverOverrideArg, cmOverrideArg, tmpDirPath string, apiserverEnableHTTPBasicAuth, apiserverEnableTokenAuth, isRBACAPIAvailable bool, nodeSelectorString string) (cmdutil.Factory, error) { - svcName := federationName + "-apiserver" - svcUrlPrefix := "/api/v1/namespaces/federation-system/services" - credSecretName := svcName + "-credentials" - cmKubeconfigSecretName := federationName + "-controller-manager-kubeconfig" - pvCap := "10Gi" - if etcdPVCapacity != "" { - pvCap = etcdPVCapacity - } - - capacity, err := resource.ParseQuantity(pvCap) - if err != nil { - return nil, err - } - pvcName := svcName + "-etcd-claim" - replicas := int32(1) - - namespace := v1.Namespace{ - TypeMeta: metav1.TypeMeta{ - Kind: "Namespace", - APIVersion: testapi.Default.GroupVersion().String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: namespaceName, - Annotations: map[string]string{ - federation.FederationNameAnnotation: federationName, - }, - }, - } - - svc := v1.Service{ - TypeMeta: metav1.TypeMeta{ - Kind: "Service", - APIVersion: testapi.Default.GroupVersion().String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespaceName, - Name: svcName, - Labels: componentLabel, - Annotations: map[string]string{ - federation.FederationNameAnnotation: federationName, - }, - }, - Spec: v1.ServiceSpec{ - Type: apiserverServiceType, - Selector: apiserverSvcSelector, - Ports: []v1.ServicePort{ - { - Name: "https", - Protocol: "TCP", - Port: 443, - TargetPort: intstr.FromString(apiServerSecurePortName), - }, - }, - }, - } - - svcWithLB := svc - svcWithLB.Status = v1.ServiceStatus{ - LoadBalancer: v1.LoadBalancerStatus{ - Ingress: []v1.LoadBalancerIngress{ - { - IP: lbIp, - }, - }, - }, - } - - credSecret := v1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - APIVersion: testapi.Default.GroupVersion().String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: credSecretName, - Namespace: namespaceName, - Annotations: map[string]string{ - federation.FederationNameAnnotation: federationName, - }, - }, - Data: nil, - } - - cmKubeconfigSecret := v1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - APIVersion: testapi.Default.GroupVersion().String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: cmKubeconfigSecretName, - Namespace: namespaceName, - Annotations: map[string]string{ - federation.FederationNameAnnotation: federationName, - }, - }, - Data: nil, - } - - cmDNSProviderSecret := v1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - APIVersion: testapi.Default.GroupVersion().String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: dnsProviderSecretName, - Namespace: namespaceName, - Annotations: map[string]string{ - federation.FederationNameAnnotation: federationName, - }, - }, - Data: nil, - } - - var storageClassName *string - if len(etcdPVStorageClass) > 0 { - storageClassName = &etcdPVStorageClass - } - - pvc := v1.PersistentVolumeClaim{ - TypeMeta: metav1.TypeMeta{ - Kind: "PersistentVolumeClaim", - APIVersion: testapi.Default.GroupVersion().String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: pvcName, - Namespace: namespaceName, - Labels: componentLabel, - Annotations: map[string]string{ - federation.FederationNameAnnotation: federationName, - }, - }, - Spec: v1.PersistentVolumeClaimSpec{ - AccessModes: []v1.PersistentVolumeAccessMode{ - v1.ReadWriteOnce, - }, - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceStorage: capacity, - }, - }, - StorageClassName: storageClassName, - }, - } - - sa := v1.ServiceAccount{ - TypeMeta: metav1.TypeMeta{ - Kind: "ServiceAccount", - APIVersion: testapi.Default.GroupVersion().String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "federation-controller-manager", - Namespace: namespaceName, - Labels: componentLabel, - Annotations: map[string]string{ - federation.FederationNameAnnotation: federationName, - }, - }, - } - - role := rbacv1.Role{ - TypeMeta: metav1.TypeMeta{ - Kind: "Role", - APIVersion: rbacv1.SchemeGroupVersion.String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "federation-system:federation-controller-manager", - Namespace: namespaceName, - Labels: componentLabel, - Annotations: map[string]string{ - federation.FederationNameAnnotation: federationName, - }, - }, - Rules: []rbacv1.PolicyRule{ - { - Verbs: []string{"get", "list", "watch"}, - APIGroups: []string{""}, - Resources: []string{"secrets"}, - }, - }, - } - - rolebinding := rbacv1.RoleBinding{ - TypeMeta: metav1.TypeMeta{ - Kind: "RoleBinding", - APIVersion: rbacv1.SchemeGroupVersion.String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "federation-system:federation-controller-manager", - Namespace: namespaceName, - Labels: componentLabel, - Annotations: map[string]string{ - federation.FederationNameAnnotation: federationName, - }, - }, - Subjects: []rbacv1.Subject{ - { - Kind: "ServiceAccount", - APIGroup: "", - Name: "federation-controller-manager", - Namespace: "federation-system", - }, - }, - RoleRef: rbacv1.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "Role", - Name: "federation-system:federation-controller-manager", - }, - } - - node := v1.Node{ - TypeMeta: metav1.TypeMeta{ - Kind: "Node", - APIVersion: testapi.Extensions.GroupVersion().String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: nodeIP, - }, - Status: v1.NodeStatus{ - Addresses: []v1.NodeAddress{ - { - Type: v1.NodeExternalIP, - Address: nodeIP, - }, - }, - }, - } - nodeList := v1.NodeList{} - nodeList.Items = append(nodeList.Items, node) - - address := lbIp - if apiserverServiceType == v1.ServiceTypeNodePort { - if advertiseAddress != "" { - address = advertiseAddress - } else { - address = nodeIP - } - } - - apiserverCommand := []string{ - "/hyperkube", - "federation-apiserver", - } - apiserverArgs := []string{ - "--bind-address=0.0.0.0", - "--etcd-servers=http://localhost:2379", - fmt.Sprintf("--secure-port=%d", apiServerSecurePort), - "--tls-cert-file=/etc/federation/apiserver/server.crt", - "--tls-private-key-file=/etc/federation/apiserver/server.key", - "--admission-control=NamespaceLifecycle", - fmt.Sprintf("--advertise-address=%s", address), - } - - if apiserverOverrideArg != "" { - apiserverArgs = append(apiserverArgs, "--client-ca-file=override") - apiserverArgs = append(apiserverArgs, "--log-dir=override") - - } else { - apiserverArgs = append(apiserverArgs, "--client-ca-file=/etc/federation/apiserver/ca.crt") - } - if apiserverEnableHTTPBasicAuth { - apiserverArgs = append(apiserverArgs, "--basic-auth-file=/etc/federation/apiserver/basicauth.csv") - } - if apiserverEnableTokenAuth { - apiserverArgs = append(apiserverArgs, "--token-auth-file=/etc/federation/apiserver/token.csv") - } - sort.Strings(apiserverArgs) - apiserverCommand = append(apiserverCommand, apiserverArgs...) - - nodeSelector, err := marshallOverrides(nodeSelectorString) - if err != nil { - return nil, fmt.Errorf("error marshalling --node-selector: %v", err) - } - - apiserver := &v1beta1.Deployment{ - TypeMeta: metav1.TypeMeta{ - Kind: "Deployment", - APIVersion: testapi.Extensions.GroupVersion().String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: svcName, - Namespace: namespaceName, - Labels: componentLabel, - Annotations: map[string]string{federation.FederationNameAnnotation: federationName}, - }, - Spec: v1beta1.DeploymentSpec{ - Replicas: &replicas, - Selector: nil, - Template: v1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Name: svcName, - Labels: apiserverPodLabels, - Annotations: map[string]string{federation.FederationNameAnnotation: federationName}, - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: "apiserver", - Image: serverImage, - Command: apiserverCommand, - Ports: []v1.ContainerPort{ - { - Name: apiServerSecurePortName, - ContainerPort: apiServerSecurePort, - }, - { - Name: "local", - ContainerPort: 8080, - }, - }, - VolumeMounts: []v1.VolumeMount{ - { - Name: credSecretName, - MountPath: "/etc/federation/apiserver", - ReadOnly: true, - }, - }, - }, - { - Name: "etcd", - Image: etcdImage, - Command: []string{ - "/usr/local/bin/etcd", - "--data-dir", - "/var/etcd/data", - }, - }, - }, - NodeSelector: nodeSelector, - Volumes: []v1.Volume{ - { - Name: credSecretName, - VolumeSource: v1.VolumeSource{ - Secret: &v1.SecretVolumeSource{ - SecretName: credSecretName, - }, - }, - }, - }, - }, - }, - }, - } - if etcdPersistence == "true" { - dataVolumeName := "etcddata" - etcdVolume := v1.Volume{ - Name: dataVolumeName, - VolumeSource: v1.VolumeSource{ - PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ - ClaimName: pvcName, - }, - }, - } - etcdVolumeMount := v1.VolumeMount{ - Name: dataVolumeName, - MountPath: "/var/etcd", - } - - apiserver.Spec.Template.Spec.Volumes = append(apiserver.Spec.Template.Spec.Volumes, etcdVolume) - for i, container := range apiserver.Spec.Template.Spec.Containers { - if container.Name == "etcd" { - apiserver.Spec.Template.Spec.Containers[i].VolumeMounts = append(apiserver.Spec.Template.Spec.Containers[i].VolumeMounts, etcdVolumeMount) - } - } - } - - cmCommand := []string{ - "/hyperkube", - "federation-controller-manager", - } - - cmArgs := []string{ - "--kubeconfig=/etc/federation/controller-manager/kubeconfig", - fmt.Sprintf("--federation-name=%s", federationName), - fmt.Sprintf("--zone-name=%s", dnsZoneName), - fmt.Sprintf("--master=https://%s", svcName), - } - - if cmOverrideArg != "" { - cmArgs = append(cmArgs, "--dns-provider=override") - cmArgs = append(cmArgs, "--log-dir=override") - } else { - cmArgs = append(cmArgs, fmt.Sprintf("--dns-provider=%s", dnsProvider)) - } - - sort.Strings(cmArgs) - cmCommand = append(cmCommand, cmArgs...) - - cmName := federationName + "-controller-manager" - cm := &v1beta1.Deployment{ - TypeMeta: metav1.TypeMeta{ - Kind: "Deployment", - APIVersion: testapi.Extensions.GroupVersion().String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: cmName, - Namespace: namespaceName, - Labels: componentLabel, - Annotations: map[string]string{ - util.FedDomainMapKey: fmt.Sprintf("%s=%s", federationName, strings.TrimRight(dnsZoneName, ".")), - federation.FederationNameAnnotation: federationName, - }, - }, - Spec: v1beta1.DeploymentSpec{ - Replicas: &replicas, - Selector: nil, - Template: v1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Name: cmName, - Labels: controllerManagerPodLabels, - Annotations: map[string]string{federation.FederationNameAnnotation: federationName}, - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: "controller-manager", - Image: serverImage, - Command: cmCommand, - VolumeMounts: []v1.VolumeMount{ - { - Name: cmKubeconfigSecretName, - MountPath: "/etc/federation/controller-manager", - ReadOnly: true, - }, - }, - Env: []v1.EnvVar{ - { - Name: "POD_NAMESPACE", - ValueFrom: &v1.EnvVarSource{ - FieldRef: &v1.ObjectFieldSelector{ - FieldPath: "metadata.namespace", - }, - }, - }, - }, - }, - }, - NodeSelector: nodeSelector, - Volumes: []v1.Volume{ - { - Name: cmKubeconfigSecretName, - VolumeSource: v1.VolumeSource{ - Secret: &v1.SecretVolumeSource{ - SecretName: cmKubeconfigSecretName, - }, - }, - }, - }, - }, - }, - }, - } - if isRBACAPIAvailable { - cm.Spec.Template.Spec.ServiceAccountName = "federation-controller-manager" - cm.Spec.Template.Spec.DeprecatedServiceAccount = "federation-controller-manager" - } - if dnsProviderConfig != "" { - cm = addDNSProviderConfigTest(cm, cmDNSProviderSecret.Name) - if dnsProvider == util.FedDNSProviderCoreDNS { - cm, err = addCoreDNSServerAnnotationTest(cm, dnsZoneName, dnsProviderConfig) - if err != nil { - return nil, err - } - } - } - - podList := v1.PodList{} - apiServerPod := v1.Pod{ - TypeMeta: metav1.TypeMeta{ - Kind: "Pod", - APIVersion: testapi.Extensions.GroupVersion().String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: svcName, - Namespace: namespaceName, - }, - Status: v1.PodStatus{ - Phase: "Running", - }, - } - - cmPod := v1.Pod{ - TypeMeta: metav1.TypeMeta{ - Kind: "Pod", - APIVersion: testapi.Extensions.GroupVersion().String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: cmName, - Namespace: namespaceName, - }, - Status: v1.PodStatus{ - Phase: "Running", - }, - } - - podList.Items = append(podList.Items, apiServerPod) - podList.Items = append(podList.Items, cmPod) - - apiGroupList := &metav1.APIGroupList{} - testGroup := metav1.APIGroup{ - Name: testAPIGroup, - Versions: []metav1.GroupVersionForDiscovery{ - { - GroupVersion: testAPIGroup + "/" + testAPIVersion, - Version: testAPIVersion, - }, - }, - } - rbacGroup := metav1.APIGroup{ - Name: rbac.GroupName, - Versions: []metav1.GroupVersionForDiscovery{ - { - GroupVersion: rbac.GroupName + "/v1", - Version: "v1", - }, - }, - } - - apiGroupList.Groups = append(apiGroupList.Groups, testGroup) - if isRBACAPIAvailable { - apiGroupList.Groups = append(apiGroupList.Groups, rbacGroup) - } - - f, tf, codec, _ := cmdtesting.NewAPIFactory() - extCodec := testapi.Extensions.Codec() - rbacCodec := testapi.Rbac.Codec() - ns := dynamic.ContentConfig().NegotiatedSerializer - tf.ClientConfig = kubefedtesting.DefaultClientConfig() - tf.TmpDir = tmpDirPath - tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, - NegotiatedSerializer: ns, - Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { - switch p, m := req.URL.Path, req.Method; { - case p == "/healthz": - return &http.Response{StatusCode: http.StatusOK, Header: kubefedtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte("ok")))}, nil - case p == "/api" && m == http.MethodGet: - return &http.Response{StatusCode: http.StatusOK, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &metav1.APIVersions{})}, nil - case p == "/apis" && m == http.MethodGet: - return &http.Response{StatusCode: http.StatusOK, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, apiGroupList)}, nil - case p == "/api/v1/namespaces" && m == http.MethodPost: - body, err := ioutil.ReadAll(req.Body) - if err != nil { - return nil, err - } - var got v1.Namespace - _, _, err = codec.Decode(body, nil, &got) - if err != nil { - return nil, err - } - if !apiequality.Semantic.DeepEqual(got, namespace) { - return nil, fmt.Errorf("unexpected namespace object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, namespace)) - } - return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &namespace)}, nil - case p == svcUrlPrefix && m == http.MethodPost: - body, err := ioutil.ReadAll(req.Body) - if err != nil { - return nil, err - } - var got v1.Service - _, _, err = codec.Decode(body, nil, &got) - if err != nil { - return nil, err - } - if !apiequality.Semantic.DeepEqual(got, svc) { - return nil, fmt.Errorf("unexpected service object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, svc)) - } - if apiserverServiceType == v1.ServiceTypeNodePort { - svc.Spec.Type = v1.ServiceTypeNodePort - svc.Spec.Ports[0].NodePort = nodePort - } - return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &svc)}, nil - case strings.HasPrefix(p, svcUrlPrefix) && m == http.MethodGet: - got := strings.TrimPrefix(p, svcUrlPrefix+"/") - if got != svcName { - return nil, errors.NewNotFound(api.Resource("services"), got) - } - return &http.Response{StatusCode: http.StatusOK, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &svcWithLB)}, nil - case p == "/api/v1/namespaces/federation-system/secrets" && m == http.MethodPost: - body, err := ioutil.ReadAll(req.Body) - if err != nil { - return nil, err - } - var got, want v1.Secret - _, _, err = codec.Decode(body, nil, &got) - if err != nil { - return nil, err - } - - switch got.Name { - case credSecretName: - want = credSecret - if apiserverEnableHTTPBasicAuth { - if got.Data["basicauth.csv"] == nil { - return nil, fmt.Errorf("expected secret data key 'basicauth.csv', but got nil") - } - } else { - if got.Data["basicauth.csv"] != nil { - return nil, fmt.Errorf("unexpected secret data key 'basicauth.csv'") - } - } - if apiserverEnableTokenAuth { - if got.Data["token.csv"] == nil { - return nil, fmt.Errorf("expected secret data key 'token.csv', but got nil") - } - } else { - if got.Data["token.csv"] != nil { - return nil, fmt.Errorf("unexpected secret data key 'token.csv'") - } - } - case cmKubeconfigSecretName: - want = cmKubeconfigSecret - case dnsProviderSecretName: - want = cmDNSProviderSecret - } - got.Data = nil - if !apiequality.Semantic.DeepEqual(got, want) { - return nil, fmt.Errorf("unexpected secret object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, want)) - } - return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &want)}, nil - case p == "/api/v1/namespaces/federation-system/persistentvolumeclaims" && m == http.MethodPost: - body, err := ioutil.ReadAll(req.Body) - if err != nil { - return nil, err - } - var got v1.PersistentVolumeClaim - _, _, err = codec.Decode(body, nil, &got) - if err != nil { - return nil, err - } - if !apiequality.Semantic.DeepEqual(got, pvc) { - return nil, fmt.Errorf("unexpected PVC object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, pvc)) - } - return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &pvc)}, nil - case p == "/apis/extensions/v1beta1/namespaces/federation-system/deployments" && m == http.MethodPost: - body, err := ioutil.ReadAll(req.Body) - if err != nil { - return nil, err - } - var got, want v1beta1.Deployment - _, _, err = codec.Decode(body, nil, &got) - if err != nil { - return nil, err - } - switch got.Name { - case svcName: - want = *apiserver - case cmName: - want = *cm - } - //want = *cm - if !apiequality.Semantic.DeepEqual(got, want) { - return nil, fmt.Errorf("unexpected deployment object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, want)) - } - return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(extCodec, &want)}, nil - case p == "/api/v1/namespaces/federation-system/pods" && m == http.MethodGet: - return &http.Response{StatusCode: http.StatusOK, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &podList)}, nil - case p == "/api/v1/namespaces/federation-system/serviceaccounts" && m == http.MethodPost: - body, err := ioutil.ReadAll(req.Body) - if err != nil { - return nil, err - } - var got v1.ServiceAccount - _, _, err = codec.Decode(body, nil, &got) - if err != nil { - return nil, err - } - if !helper.Semantic.DeepEqual(got, sa) { - return nil, fmt.Errorf("unexpected service account object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, sa)) - } - return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &sa)}, nil - case p == "/apis/rbac.authorization.k8s.io/v1/namespaces/federation-system/roles" && m == http.MethodPost: - body, err := ioutil.ReadAll(req.Body) - if err != nil { - return nil, err - } - var got rbacv1.Role - _, _, err = codec.Decode(body, nil, &got) - if err != nil { - return nil, err - } - if !helper.Semantic.DeepEqual(got, role) { - return nil, fmt.Errorf("unexpected role object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, role)) - } - return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(rbacCodec, &role)}, nil - case p == "/apis/rbac.authorization.k8s.io/v1/namespaces/federation-system/rolebindings" && m == http.MethodPost: - body, err := ioutil.ReadAll(req.Body) - if err != nil { - return nil, err - } - var got rbacv1.RoleBinding - _, _, err = codec.Decode(body, nil, &got) - if err != nil { - return nil, err - } - if !helper.Semantic.DeepEqual(got, rolebinding) { - return nil, fmt.Errorf("unexpected rolebinding object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, rolebinding)) - } - return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(rbacCodec, &rolebinding)}, nil - case p == "/api/v1/nodes" && m == http.MethodGet: - return &http.Response{StatusCode: http.StatusOK, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &nodeList)}, nil - default: - return nil, fmt.Errorf("unexpected request: %#v\n%#v", req.URL, req) - } - }), - } - return f, nil -} - -func testKubeconfigUpdate(t *testing.T, apiserverServiceType v1.ServiceType, federationName, advertiseAddress, lbIP, kubeconfigGlobal, kubeconfigExplicit string, apiserverEnableHTTPBasicAuth, apiserverEnableTokenAuth bool) { - filename := kubeconfigGlobal - if kubeconfigExplicit != "" { - filename = kubeconfigExplicit - } - config, err := clientcmd.LoadFromFile(filename) - if err != nil { - t.Errorf("Failed to open kubeconfig file: %v", err) - return - } - - cluster, ok := config.Clusters[federationName] - if !ok { - t.Errorf("No cluster info for %q", federationName) - return - } - endpoint := getEndpoint(apiserverServiceType, lbIP, advertiseAddress) - if !strings.HasSuffix(endpoint, "https://") { - endpoint = fmt.Sprintf("https://%s", endpoint) - } - if cluster.Server != endpoint { - t.Errorf("Want federation API server endpoint %q, got %q", endpoint, cluster.Server) - } - - authInfo, ok := config.AuthInfos[federationName] - if !ok { - t.Errorf("No credentials for %q", federationName) - return - } - if len(authInfo.ClientCertificateData) == 0 { - t.Errorf("Expected client certificate to be non-empty") - return - } - if len(authInfo.ClientKeyData) == 0 { - t.Errorf("Expected client key to be non-empty") - return - } - if !apiserverEnableTokenAuth && len(authInfo.Token) != 0 { - t.Errorf("Expected token to be empty: got: %s", authInfo.Token) - } - if apiserverEnableTokenAuth && len(authInfo.Token) == 0 { - t.Errorf("Expected token to be non-empty") - } - - httpBasicAuthInfo, ok := config.AuthInfos[fmt.Sprintf("%s-basic-auth", federationName)] - if !apiserverEnableHTTPBasicAuth && ok { - t.Errorf("Expected basic auth AuthInfo entry not to exist: got %v", httpBasicAuthInfo) - return - } - - if apiserverEnableHTTPBasicAuth { - if !ok { - t.Errorf("Expected basic auth AuthInfo entry to exist") - return - } - if httpBasicAuthInfo.Username != "admin" { - t.Errorf("Unexpected username in basic auth AuthInfo entry: got %s, want admin", httpBasicAuthInfo.Username) - } - if len(httpBasicAuthInfo.Password) == 0 { - t.Errorf("Expected basic auth AuthInfo entry to contain password") - } - } - - context, ok := config.Contexts[federationName] - if !ok { - t.Errorf("No context for %q", federationName) - return - } - if context.Cluster != federationName { - t.Errorf("Want context cluster name: %q, got: %q", federationName, context.Cluster) - } - if context.AuthInfo != federationName { - t.Errorf("Want context auth info: %q, got: %q", federationName, context.AuthInfo) - } -} - -type clientServerTLSConfigs struct { - server *tls.Config - client *tls.Config -} - -type certParams struct { - cAddr string - ips []string - hostnames []string -} - -func tlsHandshake(t *testing.T, sCfg, cCfg *tls.Config) error { - // Tried to use net.Pipe() instead of TCP. But the connections returned by - // net.Pipe() do a fully-synchronous reads and writes on both the ends. - // So if a TLS handshake fails, they can't return the error until the - // other side reads the message which it did not expect. Since the other - // side does not read the message it did not expect, the server and - // clients hang. Since TCP is non-blocking we use that as transport - // instead. One could have as well used a Unix Domain Socket, but TCP is - // more portable. - s, err := tls.Listen("tcp", "", sCfg) - if err != nil { - return fmt.Errorf("failed to create a test TLS server: %v", err) - } - defer s.Close() - - errCh := make(chan error) - go func() { - for { - conn, err := s.Accept() - if err != nil { - errCh <- fmt.Errorf("failed to accept a TLS connection: %v", err) - return - } - gotByte := make([]byte, len(helloMsg)) - _, err = conn.Read(gotByte) - if err != nil && err != io.EOF { - errCh <- fmt.Errorf("failed to read input: %v", err) - } else if got := string(gotByte); got != helloMsg { - errCh <- fmt.Errorf("got %q, want %q", got, helloMsg) - } - errCh <- nil - return - } - }() - - // workaround [::] not working in ipv4 only systems (https://github.com/golang/go/issues/18806) - // TODO: remove with Golang 1.9 with https://go-review.googlesource.com/c/45088/ - addr := strings.TrimPrefix(s.Addr().String(), "[::]") - - c, err := tls.Dial("tcp", addr, cCfg) - if err != nil { - // Intentionally not serializing the error received because we want to - // test for the failure case in the caller test function. - return err - } - defer c.Close() - if _, err := c.Write([]byte(helloMsg)); err != nil { - return fmt.Errorf("failed to write to server: %v", err) - } - - return <-errCh -} - -func fakeHTTPSServer(sCfg *tls.Config) (*httptest.Server, error) { - s := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, helloMsg) - })) - - s.TLS.Certificates = sCfg.Certificates - s.TLS.RootCAs = sCfg.RootCAs - s.TLS.ClientAuth = sCfg.ClientAuth - s.TLS.ClientCAs = sCfg.ClientCAs - s.TLS.InsecureSkipVerify = sCfg.InsecureSkipVerify - return s, nil -} - -func tlsConfigs(params []certParams) ([]clientServerTLSConfigs, error) { - tlsCfgs := []clientServerTLSConfigs{} - for i, p := range params { - sCfg, cCfg, err := genServerClientTLSConfigs(testNamespace, p.cAddr, testSvcName, HostClusterLocalDNSZoneName, p.ips, p.hostnames) - if err != nil { - return nil, fmt.Errorf("[%d] failed to generate tls configs: %v", i, err) - } - tlsCfgs = append(tlsCfgs, clientServerTLSConfigs{sCfg, cCfg}) - } - return tlsCfgs, nil -} - -func genServerClientTLSConfigs(namespace, name, svcName, localDNSZoneName string, ips, hostnames []string) (*tls.Config, *tls.Config, error) { - entKeyPairs, err := genCerts(namespace, name, svcName, localDNSZoneName, ips, hostnames) - if err != nil { - return nil, nil, fmt.Errorf("unexpected error generating certs: %v", err) - } - - roots := x509.NewCertPool() - roots.AddCert(entKeyPairs.ca.Cert) - - serverCert := tls.Certificate{ - Certificate: [][]byte{ - entKeyPairs.server.Cert.Raw, - }, - PrivateKey: entKeyPairs.server.Key, - } - - cmCert := tls.Certificate{ - Certificate: [][]byte{ - entKeyPairs.controllerManager.Cert.Raw, - }, - PrivateKey: entKeyPairs.controllerManager.Key, - } - - sCfg := &tls.Config{ - Certificates: []tls.Certificate{serverCert}, - RootCAs: roots, - ClientAuth: tls.RequireAndVerifyClientCert, - ClientCAs: roots, - InsecureSkipVerify: false, - } - - cCfg := &tls.Config{ - Certificates: []tls.Certificate{cmCert}, - RootCAs: roots, - } - - return sCfg, cCfg, nil -} - -func copyTLSConfig(cfg *tls.Config) *tls.Config { - // We are copying only the required fields. - return &tls.Config{ - Certificates: cfg.Certificates, - RootCAs: cfg.RootCAs, - ClientAuth: cfg.ClientAuth, - ClientCAs: cfg.ClientCAs, - InsecureSkipVerify: cfg.InsecureSkipVerify, - } -} - -func getEndpoint(apiserverServiceType v1.ServiceType, lbIP, advertiseAddress string) string { - endpoint := lbIP - if apiserverServiceType == v1.ServiceTypeNodePort { - if advertiseAddress != "" { - endpoint = advertiseAddress + ":" + strconv.Itoa(nodePort) - } else { - endpoint = nodeIP + ":" + strconv.Itoa(nodePort) - } - } - return endpoint -} - -// TODO: Reuse the function addDNSProviderConfig once that function is converted to use versioned objects. -func addDNSProviderConfigTest(dep *v1beta1.Deployment, secretName string) *v1beta1.Deployment { - const ( - dnsProviderConfigVolume = "config-volume" - dnsProviderConfigMountPath = "/etc/federation/dns-provider" - ) - - // Create a volume from dns-provider secret - volume := v1.Volume{ - Name: dnsProviderConfigVolume, - VolumeSource: v1.VolumeSource{ - Secret: &v1.SecretVolumeSource{ - SecretName: secretName, - }, - }, - } - dep.Spec.Template.Spec.Volumes = append(dep.Spec.Template.Spec.Volumes, volume) - - // Mount dns-provider secret volume to controller-manager container - volumeMount := v1.VolumeMount{ - Name: dnsProviderConfigVolume, - MountPath: dnsProviderConfigMountPath, - ReadOnly: true, - } - dep.Spec.Template.Spec.Containers[0].VolumeMounts = append(dep.Spec.Template.Spec.Containers[0].VolumeMounts, volumeMount) - dep.Spec.Template.Spec.Containers[0].Command = append(dep.Spec.Template.Spec.Containers[0].Command, fmt.Sprintf("--dns-provider-config=%s/%s", dnsProviderConfigMountPath, secretName)) - - return dep -} - -// TODO: Reuse the function addCoreDNSServerAnnotation once that function is converted to use versioned objects. -func addCoreDNSServerAnnotationTest(deployment *v1beta1.Deployment, dnsZoneName, dnsProviderConfig string) (*v1beta1.Deployment, error) { - var cfg coredns.Config - if err := gcfg.ReadFileInto(&cfg, dnsProviderConfig); err != nil { - return nil, err - } - - deployment.Annotations[util.FedDNSZoneName] = dnsZoneName - deployment.Annotations[util.FedNameServer] = cfg.Global.CoreDNSEndpoints - deployment.Annotations[util.FedDNSProvider] = util.FedDNSProviderCoreDNS - return deployment, nil -} diff --git a/federation/pkg/kubefed/join.go b/federation/pkg/kubefed/join.go deleted file mode 100644 index 7f59f74fd8a..00000000000 --- a/federation/pkg/kubefed/join.go +++ /dev/null @@ -1,700 +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 kubefed - -import ( - "fmt" - "io" - "strings" - "time" - - "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" - "k8s.io/apiserver/pkg/storage/names" - clientcmdapi "k8s.io/client-go/tools/clientcmd/api" - "k8s.io/kubernetes/federation/apis/federation" - "k8s.io/kubernetes/federation/pkg/kubefed/util" - "k8s.io/kubernetes/pkg/api" - extensions "k8s.io/kubernetes/pkg/apis/extensions" - "k8s.io/kubernetes/pkg/apis/rbac" - "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" - "k8s.io/kubernetes/pkg/kubectl" - kubectlcmd "k8s.io/kubernetes/pkg/kubectl/cmd" - "k8s.io/kubernetes/pkg/kubectl/cmd/templates" - cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" - - "github.com/golang/glog" - "github.com/spf13/cobra" - "github.com/spf13/pflag" -) - -const ( - // defaultClusterCIDR is the default CIDR range accepted by the - // joining API server. See `apis/federation.ClusterSpec` for - // details. - // TODO(madhusudancs): Make this value customizable. - defaultClientCIDR = "0.0.0.0/0" - CMNameSuffix = "controller-manager" - serviceAccountSecretTimeout = 30 * time.Second -) - -var ( - join_long = templates.LongDesc(` - Join adds a cluster to a federation. - - Current context is assumed to be a federation API - server. Please use the --context flag otherwise.`) - join_example = templates.Examples(` - # Join a cluster to a federation by specifying the - # cluster name and the context name of the federation - # control plane's host cluster. Cluster name must be - # a valid RFC 1123 subdomain name. Cluster context - # must be specified if the cluster name is different - # than the cluster's context in the local kubeconfig. - kubefed join foo --host-cluster-context=bar`) -) - -type joinFederation struct { - commonOptions util.SubcommandOptions - options joinFederationOptions -} - -type joinFederationOptions struct { - clusterContext string - secretName string - dryRun bool -} - -func (o *joinFederationOptions) Bind(flags *pflag.FlagSet) { - flags.StringVar(&o.clusterContext, "cluster-context", "", "Name of the cluster's context in the local kubeconfig. Defaults to cluster name if unspecified.") - flags.StringVar(&o.secretName, "secret-name", "", "Name of the secret where the cluster's credentials will be stored in the host cluster. This name should be a valid RFC 1035 label. Defaults to cluster name if unspecified.") - flags.MarkDeprecated("secret-name", "kubefed now generates a secret name, and this flag will be removed in a future release.") -} - -// NewCmdJoin defines the `join` command that joins a cluster to a -// federation. -func NewCmdJoin(f cmdutil.Factory, cmdOut io.Writer, config util.AdminConfig) *cobra.Command { - opts := &joinFederation{} - - cmd := &cobra.Command{ - Use: "join CLUSTER_NAME --host-cluster-context=HOST_CONTEXT", - Short: "Join a cluster to a federation", - Long: join_long, - Example: join_example, - Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(opts.Complete(cmd, args, config)) - cmdutil.CheckErr(opts.Run(f, cmdOut, config, cmd)) - }, - } - - cmdutil.AddApplyAnnotationFlags(cmd) - cmdutil.AddValidateFlags(cmd) - cmdutil.AddPrinterFlags(cmd) - cmdutil.AddGeneratorFlags(cmd, cmdutil.ClusterV1Beta1GeneratorName) - - flags := cmd.Flags() - opts.commonOptions.Bind(flags) - opts.options.Bind(flags) - - return cmd -} - -// Complete ensures that options are valid and marshals them if necessary. -func (j *joinFederation) Complete(cmd *cobra.Command, args []string, config util.AdminConfig) error { - err := j.commonOptions.SetName(cmd, args) - if err != nil { - return err - } - - j.options.dryRun = cmdutil.GetDryRunFlag(cmd) - - if j.options.clusterContext == "" { - j.options.clusterContext = j.commonOptions.Name - } - - glog.V(2).Infof("Args and flags: name %s, host: %s, host-system-namespace: %s, kubeconfig: %s, cluster-context: %s, secret-name: %s, dry-run: %s", j.commonOptions.Name, j.commonOptions.Host, j.commonOptions.FederationSystemNamespace, j.commonOptions.Kubeconfig, j.options.clusterContext, j.options.secretName, j.options.dryRun) - - glog.V(2).Infof("Performing preflight checks.") - err = j.performPreflightChecks(config) - if err != nil { - return err - } - return nil -} - -// performPreflightChecks checks that the host and joining clusters are in -// a consistent state. -// TODO: This currently only verifies a few things. Add more checks. -func (j *joinFederation) performPreflightChecks(config util.AdminConfig) error { - joiningClusterFactory := j.joningClusterFactory(config) - - // If RBAC is not available, then skip checking for a service account. - // If RBAC availability cannot be determined, return an error. - rbacVersionedClientset, err := util.GetVersionedClientForRBACOrFail(joiningClusterFactory) - if err != nil { - if _, ok := err.(*util.NoRBACAPIError); ok { - return nil - } - return err - } - - // Make sure there is no existing service account in the joining cluster. - saName := util.ClusterServiceAccountName(j.commonOptions.Name, j.commonOptions.Host) - sa, err := rbacVersionedClientset.Core().ServiceAccounts(j.commonOptions.FederationSystemNamespace).Get(saName, metav1.GetOptions{}) - if errors.IsNotFound(err) { - return nil - } else if err != nil { - return err - } else if sa != nil { - return fmt.Errorf("service account already exists in joining cluster") - } - - return nil -} - -// joiningClusterClientset returns a factory for the joining cluster. -func (j *joinFederation) joningClusterFactory(config util.AdminConfig) cmdutil.Factory { - return config.ClusterFactory(j.options.clusterContext, j.commonOptions.Kubeconfig) -} - -// Run is the implementation of the `join federation` command. -func (j *joinFederation) Run(f cmdutil.Factory, cmdOut io.Writer, config util.AdminConfig, cmd *cobra.Command) error { - clusterContext := j.options.clusterContext - dryRun := j.options.dryRun - federationNamespace := j.commonOptions.FederationSystemNamespace - host := j.commonOptions.Host - kubeconfig := j.commonOptions.Kubeconfig - joiningClusterName := j.commonOptions.Name - secretName := j.options.secretName - if secretName == "" { - secretName = names.SimpleNameGenerator.GenerateName(j.commonOptions.Name + "-") - } - - joiningClusterFactory := j.joningClusterFactory(config) - joiningClusterClientset, err := joiningClusterFactory.ClientSet() - if err != nil { - glog.V(2).Infof("Could not create client for joining cluster: %v", err) - return err - } - - hostFactory := config.ClusterFactory(host, kubeconfig) - hostClientset, err := hostFactory.ClientSet() - if err != nil { - glog.V(2).Infof("Could not create client for host cluster: %v", err) - return err - } - - federationName, err := getFederationName(hostClientset, federationNamespace) - if err != nil { - glog.V(2).Infof("Failed to get the federation name: %v", err) - return err - } - - glog.V(2).Info("Creating federation system namespace in joining cluster") - _, err = createFederationSystemNamespace(joiningClusterClientset, federationNamespace, federationName, joiningClusterName, dryRun) - if err != nil { - glog.V(2).Infof("Error creating federation system namespace in joining cluster: %v", err) - return err - } - glog.V(2).Info("Created federation system namespace in joining cluster") - - po := config.PathOptions() - po.LoadingRules.ExplicitPath = kubeconfig - clientConfig, err := po.GetStartingConfig() - if err != nil { - glog.V(2).Infof("Could not load clientConfig from %s: %v", kubeconfig, err) - return err - } - - serviceAccountName := "" - clusterRoleName := "" - // Check for RBAC in the joining cluster. If it supports RBAC, then create - // a service account and use its credentials; otherwise, use the credentials - // from the local kubeconfig. - glog.V(2).Info("Creating cluster credentials secret") - rbacClientset, err := util.GetVersionedClientForRBACOrFail(joiningClusterFactory) - if err == nil { - if _, serviceAccountName, clusterRoleName, err = createRBACSecret(hostClientset, rbacClientset, federationNamespace, federationName, joiningClusterName, host, clusterContext, secretName, dryRun); err != nil { - glog.V(2).Infof("Could not create cluster credentials secret: %v", err) - return err - } - } else { - if _, ok := err.(*util.NoRBACAPIError); ok { - - // We are not using the `kubectl create secret` machinery through - // `RunCreateSubcommand` as we do to the cluster resource below - // because we have a bunch of requirements that the machinery does - // not satisfy. - // 1. We want to create the secret in a specific namespace, which - // is neither the "default" namespace nor the one specified - // via the `--namespace` flag. - // 2. `SecretGeneratorV1` requires LiteralSources in a string-ified - // form that it parses to generate the secret data key-value - // pairs. We, however, have the key-value pairs ready without a - // need for parsing. - // 3. The result printing mechanism needs to be mostly quiet. We - // don't have to print the created secret in the default case. - // Having said that, secret generation machinery could be altered to - // suit our needs, but it is far less invasive and readable this way. - _, err = createSecret(hostClientset, clientConfig, federationNamespace, federationName, joiningClusterName, clusterContext, secretName, dryRun) - if err != nil { - glog.V(2).Infof("Failed creating the cluster credentials secret: %v", err) - return err - } - } else { - glog.V(2).Infof("Failed to get or verify absence of RBAC client: %v", err) - return err - } - } - glog.V(2).Info("Cluster credentials secret created") - - glog.V(2).Info("Creating a generator for the cluster API object") - generator, err := clusterGenerator(clientConfig, joiningClusterName, clusterContext, secretName, serviceAccountName, clusterRoleName) - if err != nil { - glog.V(2).Infof("Failed to create a generator for the cluster API object: %v", err) - return err - } - glog.V(2).Info("Created a generator for the cluster API object") - - glog.V(2).Info("Running create cluster command against the federation API server") - err = kubectlcmd.RunCreateSubcommand(f, cmd, cmdOut, &kubectlcmd.CreateSubcommandOptions{ - Name: joiningClusterName, - StructuredGenerator: generator, - DryRun: dryRun, - OutputFormat: cmdutil.GetFlagString(cmd, "output"), - }) - if err != nil { - glog.V(2).Infof("Failed running create cluster command against the federation API server: %v", err) - return err - } - glog.V(2).Info("Successfully ran create cluster command against the federation API server") - - // We further need to create a configmap named kube-config in the - // just registered cluster which will be consumed by the kube-dns - // of this cluster. - glog.V(2).Info("Creating configmap in host cluster") - _, err = createConfigMap(hostClientset, config, federationNamespace, federationName, joiningClusterName, clusterContext, kubeconfig, dryRun) - if err != nil { - glog.V(2).Infof("Failed to create configmap in cluster: %v", err) - return err - } - glog.V(2).Info("Created configmap in host cluster") - - return err -} - -// minifyConfig is a wrapper around `clientcmdapi.MinifyConfig()` that -// sets the current context to the given context before calling -// `clientcmdapi.MinifyConfig()`. -func minifyConfig(clientConfig *clientcmdapi.Config, context string) (*clientcmdapi.Config, error) { - // MinifyConfig inline-modifies the passed clientConfig. So we make a - // copy of it before passing the config to it. A shallow copy is - // sufficient because the underlying fields will be reconstructed by - // MinifyConfig anyway. - newClientConfig := *clientConfig - newClientConfig.CurrentContext = context - err := clientcmdapi.MinifyConfig(&newClientConfig) - if err != nil { - return nil, err - } - return &newClientConfig, nil -} - -// createSecret extracts the kubeconfig for a given cluster and populates -// a secret with that kubeconfig. -func createSecret(clientset internalclientset.Interface, clientConfig *clientcmdapi.Config, namespace, federationName, joiningClusterName, contextName, secretName string, dryRun bool) (runtime.Object, error) { - // Minify the kubeconfig to ensure that there is only information - // relevant to the cluster we are registering. - newClientConfig, err := minifyConfig(clientConfig, contextName) - if err != nil { - glog.V(2).Infof("Failed to minify the kubeconfig for the given context %q: %v", contextName, err) - return nil, err - } - - // Flatten the kubeconfig to ensure that all the referenced file - // contents are inlined. - err = clientcmdapi.FlattenConfig(newClientConfig) - if err != nil { - glog.V(2).Infof("Failed to flatten the kubeconfig for the given context %q: %v", contextName, err) - return nil, err - } - - return util.CreateKubeconfigSecret(clientset, newClientConfig, namespace, secretName, federationName, joiningClusterName, dryRun) -} - -// createConfigMap creates a configmap with name kube-dns in the joining cluster -// which stores the information about this federation zone name. -// If the configmap with this name already exists, its updated with this information. -func createConfigMap(hostClientSet internalclientset.Interface, config util.AdminConfig, fedSystemNamespace, federationName, joiningClusterName, targetClusterContext, kubeconfigPath string, dryRun bool) (*api.ConfigMap, error) { - cmDep, err := getCMDeployment(hostClientSet, fedSystemNamespace) - if err != nil { - return nil, err - } - domainMap, ok := cmDep.Annotations[util.FedDomainMapKey] - if !ok { - return nil, fmt.Errorf("kube-dns config map data missing from controller manager annotations") - } - - targetFactory := config.ClusterFactory(targetClusterContext, kubeconfigPath) - targetClientSet, err := targetFactory.ClientSet() - if err != nil { - return nil, err - } - - existingConfigMap, err := targetClientSet.Core().ConfigMaps(metav1.NamespaceSystem).Get(util.KubeDnsConfigmapName, metav1.GetOptions{}) - if isNotFound(err) { - newConfigMap := &api.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: util.KubeDnsConfigmapName, - Namespace: metav1.NamespaceSystem, - Annotations: map[string]string{ - federation.FederationNameAnnotation: federationName, - federation.ClusterNameAnnotation: joiningClusterName, - }, - }, - Data: map[string]string{ - util.FedDomainMapKey: domainMap, - }, - } - newConfigMap = populateStubDomainsIfRequired(newConfigMap, cmDep.Annotations) - - if dryRun { - return newConfigMap, nil - } - return targetClientSet.Core().ConfigMaps(metav1.NamespaceSystem).Create(newConfigMap) - } - if err != nil { - return nil, err - } - - if existingConfigMap.Data == nil { - existingConfigMap.Data = make(map[string]string) - } - if _, ok := existingConfigMap.Data[util.FedDomainMapKey]; ok { - // Append this federation info - existingConfigMap.Data[util.FedDomainMapKey] = appendConfigMapString(existingConfigMap.Data[util.FedDomainMapKey], cmDep.Annotations[util.FedDomainMapKey]) - - } else { - // For some reason the configMap exists but this data is empty - existingConfigMap.Data[util.FedDomainMapKey] = cmDep.Annotations[util.FedDomainMapKey] - } - - if dryRun { - return existingConfigMap, nil - } - return targetClientSet.Core().ConfigMaps(metav1.NamespaceSystem).Update(existingConfigMap) -} - -// clusterGenerator extracts the cluster information from the supplied -// kubeconfig and builds a StructuredGenerator for the -// `federation/cluster` API resource. -func clusterGenerator(clientConfig *clientcmdapi.Config, name, contextName, secretName, serviceAccountName, clusterRoleName string) (kubectl.StructuredGenerator, error) { - // Get the context from the config. - ctx, found := clientConfig.Contexts[contextName] - if !found { - return nil, fmt.Errorf("cluster context %q not found", contextName) - } - - // Get the cluster object corresponding to the supplied context. - cluster, found := clientConfig.Clusters[ctx.Cluster] - if !found { - return nil, fmt.Errorf("cluster endpoint not found for %q", name) - } - - // Extract the scheme portion of the cluster APIServer endpoint and - // default it to `https` if it isn't specified. - scheme := extractScheme(cluster.Server) - serverAddress := cluster.Server - if scheme == "" { - // Use "https" as the default scheme. - scheme := "https" - serverAddress = strings.Join([]string{scheme, serverAddress}, "://") - } - - generator := &kubectl.ClusterGeneratorV1Beta1{ - Name: name, - ClientCIDR: defaultClientCIDR, - ServerAddress: serverAddress, - SecretName: secretName, - ServiceAccountName: serviceAccountName, - ClusterRoleName: clusterRoleName, - } - return generator, nil -} - -// extractScheme parses the given URL to extract the scheme portion -// out of it. -func extractScheme(url string) string { - scheme := "" - segs := strings.SplitN(url, "://", 2) - if len(segs) == 2 { - scheme = segs[0] - } - return scheme -} - -func getCMDeployment(hostClientSet internalclientset.Interface, fedNamespace string) (*extensions.Deployment, error) { - depList, err := hostClientSet.Extensions().Deployments(fedNamespace).List(metav1.ListOptions{}) - if err != nil { - return nil, err - } - - for _, dep := range depList.Items { - if strings.HasSuffix(dep.Name, CMNameSuffix) { - return &dep, nil - } - } - return nil, fmt.Errorf("could not find the deployment for controller manager in host cluster") -} - -func appendConfigMapString(existing string, toAppend string) string { - if existing == "" { - return toAppend - } - - values := strings.Split(existing, ",") - for _, v := range values { - // Somehow this federation string is already present, - // Nothing should be done - if v == toAppend { - return existing - } - } - return fmt.Sprintf("%s,%s", existing, toAppend) -} - -// getFederationName gets the federation name from the appropriate annotation on the -// control manager deployment. -func getFederationName(hostClientSet internalclientset.Interface, fedNamespace string) (string, error) { - d, err := getCMDeployment(hostClientSet, fedNamespace) - if err != nil { - return "", err - } - - name, ok := d.Annotations[federation.FederationNameAnnotation] - if !ok { - return "", fmt.Errorf("Federation control manager does not have federation name annotation. Please recreate the federation with a newer version of kubefed, or use an older version of kubefed to join this cluster.") - } - - return name, nil -} - -func populateStubDomainsIfRequired(configMap *api.ConfigMap, annotations map[string]string) *api.ConfigMap { - dnsProvider := annotations[util.FedDNSProvider] - dnsZoneName := annotations[util.FedDNSZoneName] - nameServer := annotations[util.FedNameServer] - - if dnsProvider != util.FedDNSProviderCoreDNS || dnsZoneName == "" || nameServer == "" { - return configMap - } - configMap.Data[util.KubeDnsStubDomains] = fmt.Sprintf(`{"%s":["%s"]}`, dnsZoneName, nameServer) - return configMap -} - -// createFederationSystemNamespace creates the federation-system namespace in the cluster -// associated with clusterClientset, if it doesn't already exist. -func createFederationSystemNamespace(clusterClientset internalclientset.Interface, federationNamespace, federationName, joiningClusterName string, dryRun bool) (*api.Namespace, error) { - federationNS := &api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: federationNamespace, - Annotations: map[string]string{ - federation.FederationNameAnnotation: federationName, - federation.ClusterNameAnnotation: joiningClusterName, - }, - }, - } - - if dryRun { - return federationNS, nil - } - - _, err := clusterClientset.Core().Namespaces().Create(federationNS) - if err != nil && !errors.IsAlreadyExists(err) { - glog.V(2).Infof("Could not create federation-system namespace in client: %v", err) - return nil, err - } - return federationNS, nil -} - -// createRBACSecret creates a secret in the joining cluster using a service account, and -// populates that secret into the host cluster to allow it to access the joining cluster. -func createRBACSecret(hostClusterClientset, joiningClusterClientset internalclientset.Interface, namespace, federationName, joiningClusterName, hostClusterContext, joiningClusterContext, secretName string, dryRun bool) (*api.Secret, string, string, error) { - glog.V(2).Info("Creating service account in joining cluster") - saName, err := createServiceAccount(joiningClusterClientset, namespace, federationName, joiningClusterName, hostClusterContext, dryRun) - if err != nil { - glog.V(2).Infof("Error creating service account in joining cluster: %v", err) - return nil, "", "", err - } - glog.V(2).Infof("Created service account in joining cluster") - - glog.V(2).Info("Creating role binding for service account in joining cluster") - crb, err := createClusterRoleBinding(joiningClusterClientset, saName, namespace, federationName, joiningClusterName, dryRun) - if err != nil { - glog.V(2).Infof("Error creating role binding for service account in joining cluster: %v", err) - return nil, "", "", err - } - glog.V(2).Info("Created role binding for service account in joining cluster") - - glog.V(2).Info("Creating secret in host cluster") - secret, err := populateSecretInHostCluster(joiningClusterClientset, hostClusterClientset, saName, namespace, federationName, joiningClusterName, secretName, dryRun) - if err != nil { - glog.V(2).Infof("Error creating secret in host cluster: %v", err) - return nil, "", "", err - } - glog.V(2).Info("Created secret in host cluster") - return secret, saName, crb.Name, nil -} - -// createServiceAccount creates a service account in the cluster associated with clusterClientset with -// credentials that will be used by the host cluster to access its API server. -func createServiceAccount(clusterClientset internalclientset.Interface, namespace, federationName, joiningClusterName, hostContext string, dryRun bool) (string, error) { - saName := util.ClusterServiceAccountName(joiningClusterName, hostContext) - sa := &api.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: saName, - Namespace: namespace, - Annotations: map[string]string{ - federation.FederationNameAnnotation: federationName, - federation.ClusterNameAnnotation: joiningClusterName, - }, - }, - } - - if dryRun { - return saName, nil - } - - // Create a new service account. - _, err := clusterClientset.Core().ServiceAccounts(namespace).Create(sa) - if err != nil { - return "", err - } - - return saName, nil -} - -// createClusterRoleBinding creates an RBAC role and binding that allows the -// service account identified by saName to access all resources in all namespaces -// in the cluster associated with clusterClientset. -func createClusterRoleBinding(clusterClientset internalclientset.Interface, saName, namespace, federationName, joiningClusterName string, dryRun bool) (*rbac.ClusterRoleBinding, error) { - roleName := util.ClusterRoleName(federationName, saName) - role := &rbac.ClusterRole{ - ObjectMeta: metav1.ObjectMeta{ - Name: roleName, - Namespace: namespace, - Annotations: map[string]string{ - federation.FederationNameAnnotation: federationName, - federation.ClusterNameAnnotation: joiningClusterName, - }, - }, - Rules: []rbac.PolicyRule{ - rbac.NewRule(rbac.VerbAll).Groups(rbac.APIGroupAll).Resources(rbac.ResourceAll).RuleOrDie(), - rbac.NewRule("get").URLs("/healthz").RuleOrDie(), - }, - } - - // TODO: This should limit its access to only necessary resources. - rolebinding, err := rbac.NewClusterBinding(roleName).SAs(namespace, saName).Binding() - rolebinding.ObjectMeta.Namespace = namespace - rolebinding.ObjectMeta.Annotations = map[string]string{ - federation.FederationNameAnnotation: federationName, - federation.ClusterNameAnnotation: joiningClusterName, - } - if err != nil { - glog.V(2).Infof("Could not create role binding for service account: %v", err) - return nil, err - } - - if dryRun { - return &rolebinding, nil - } - - _, err = clusterClientset.Rbac().ClusterRoles().Create(role) - if err != nil { - glog.V(2).Infof("Could not create role for service account in joining cluster: %v", err) - return nil, err - } - - _, err = clusterClientset.Rbac().ClusterRoleBindings().Create(&rolebinding) - if err != nil { - glog.V(2).Infof("Could not create role binding for service account in joining cluster: %v", err) - return nil, err - } - - return &rolebinding, nil -} - -// populateSecretInHostCluster copies the service account secret for saName from the cluster -// referenced by clusterClientset to the client referenced by hostClientset, putting it in a secret -// named secretName in the provided namespace. -func populateSecretInHostCluster(clusterClientset, hostClientset internalclientset.Interface, saName, namespace, federationName, joiningClusterName, secretName string, dryRun bool) (*api.Secret, error) { - if dryRun { - // The secret is created indirectly with the service account, and so there is no local copy to return in a dry run. - return nil, nil - } - // Get the secret from the joining cluster. - var sa *api.ServiceAccount - err := wait.PollImmediate(1*time.Second, serviceAccountSecretTimeout, func() (bool, error) { - var err error - sa, err = clusterClientset.Core().ServiceAccounts(namespace).Get(saName, metav1.GetOptions{}) - if err != nil { - return false, nil - } - return len(sa.Secrets) == 1, nil - }) - if err != nil { - return nil, err - } - - glog.V(2).Infof("Getting secret named: %s", sa.Secrets[0].Name) - var secret *api.Secret - err = wait.PollImmediate(1*time.Second, serviceAccountSecretTimeout, func() (bool, error) { - var err error - secret, err = clusterClientset.Core().Secrets(namespace).Get(sa.Secrets[0].Name, metav1.GetOptions{}) - if err != nil { - return false, nil - } - return true, nil - }) - if err != nil { - glog.V(2).Infof("Could not get service account secret from joining cluster: %v", err) - return nil, err - } - - // Create a parallel secret in the host cluster. - v1Secret := api.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: secretName, - Namespace: namespace, - Annotations: map[string]string{ - federation.FederationNameAnnotation: federationName, - federation.ClusterNameAnnotation: joiningClusterName, - }, - }, - Data: secret.Data, - } - - glog.V(2).Infof("Creating secret in host cluster named: %s", v1Secret.Name) - _, err = hostClientset.Core().Secrets(namespace).Create(&v1Secret) - if err != nil { - glog.V(2).Infof("Could not create secret in host cluster: %v", err) - return nil, err - } - return &v1Secret, nil -} diff --git a/federation/pkg/kubefed/join_test.go b/federation/pkg/kubefed/join_test.go deleted file mode 100644 index c988765a31a..00000000000 --- a/federation/pkg/kubefed/join_test.go +++ /dev/null @@ -1,617 +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 kubefed - -import ( - "bytes" - "fmt" - "io/ioutil" - "net/http" - "os" - "testing" - - "k8s.io/api/core/v1" - "k8s.io/api/extensions/v1beta1" - rbacv1 "k8s.io/api/rbac/v1" - apiequality "k8s.io/apimachinery/pkg/api/equality" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/diff" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/rest/fake" - "k8s.io/client-go/tools/clientcmd" - clientcmdapi "k8s.io/client-go/tools/clientcmd/api" - "k8s.io/kubernetes/federation/apis/federation" - federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" - kubefedtesting "k8s.io/kubernetes/federation/pkg/kubefed/testing" - "k8s.io/kubernetes/federation/pkg/kubefed/util" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/testapi" - k8srbacv1 "k8s.io/kubernetes/pkg/apis/rbac/v1" - "k8s.io/kubernetes/pkg/kubectl" - cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" - cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" -) - -const ( - // testFederationName is a name to use for the federation in tests. Since the federation - // name is recovered from the federation itself, this constant is an appropriate - // functional replica. - testFederationName = "test-federation" - - zoneName = "test-dns-zone" - coreDNSServer = "11.22.33.44:53" -) - -func TestJoinFederation(t *testing.T) { - cmdErrMsg := "" - cmdutil.BehaviorOnFatal(func(str string, code int) { - cmdErrMsg = str - }) - - fakeKubeFiles, err := kubefedtesting.FakeKubeconfigFiles() - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - defer kubefedtesting.RemoveFakeKubeconfigFiles(fakeKubeFiles) - - testCases := []struct { - cluster string - clusterCtx string - secret string - server string - token string - kubeconfigGlobal string - kubeconfigExplicit string - expectedServer string - expectedErr string - dnsProvider string - isRBACAPIAvailable bool - }{ - { - cluster: "syndicate", - clusterCtx: "", - server: "https://10.20.30.40", - token: "badge", - kubeconfigGlobal: fakeKubeFiles[0], - kubeconfigExplicit: "", - expectedServer: "https://10.20.30.40", - expectedErr: "", - dnsProvider: util.FedDNSProviderCoreDNS, - isRBACAPIAvailable: true, - }, - { - cluster: "syndicate", - clusterCtx: "", - secret: "", - server: "https://10.20.30.40", - token: "badge", - kubeconfigGlobal: fakeKubeFiles[0], - kubeconfigExplicit: "", - expectedServer: "https://10.20.30.40", - expectedErr: "", - isRBACAPIAvailable: false, - }, - { - cluster: "ally", - clusterCtx: "", - server: "ally256.example.com:80", - token: "souvenir", - kubeconfigGlobal: fakeKubeFiles[0], - kubeconfigExplicit: fakeKubeFiles[1], - expectedServer: "https://ally256.example.com:80", - expectedErr: "", - isRBACAPIAvailable: true, - }, - { - cluster: "confederate", - clusterCtx: "", - server: "10.8.8.8", - token: "totem", - kubeconfigGlobal: fakeKubeFiles[1], - kubeconfigExplicit: fakeKubeFiles[2], - expectedServer: "https://10.8.8.8", - expectedErr: "", - isRBACAPIAvailable: true, - }, - { - cluster: "associate", - clusterCtx: "confederate", - server: "10.8.8.8", - token: "totem", - kubeconfigGlobal: fakeKubeFiles[1], - kubeconfigExplicit: fakeKubeFiles[2], - expectedServer: "https://10.8.8.8", - expectedErr: "", - isRBACAPIAvailable: true, - }, - { - cluster: "affiliate", - clusterCtx: "", - server: "https://10.20.30.40", - token: "badge", - kubeconfigGlobal: fakeKubeFiles[0], - kubeconfigExplicit: "", - expectedServer: "https://10.20.30.40", - expectedErr: fmt.Sprintf("error: cluster context %q not found", "affiliate"), - isRBACAPIAvailable: true, - }, - { - cluster: "associate", - clusterCtx: "confederate", - secret: "confidential", - server: "10.8.8.8", - token: "totem", - kubeconfigGlobal: fakeKubeFiles[1], - kubeconfigExplicit: fakeKubeFiles[2], - expectedServer: "https://10.8.8.8", - expectedErr: "", - }, - } - - for i, tc := range testCases { - cmdErrMsg = "" - f := testJoinFederationFactory(tc.cluster, tc.secret, tc.expectedServer, tc.isRBACAPIAvailable) - buf := bytes.NewBuffer([]byte{}) - - hostFactory, err := fakeJoinHostFactory(tc.cluster, tc.clusterCtx, tc.secret, tc.server, tc.token, tc.dnsProvider, tc.isRBACAPIAvailable) - if err != nil { - t.Fatalf("[%d] unexpected error: %v", i, err) - } - - // The fake discovery client caches results by default, so invalidate it by modifying the temporary directory. - // Refer to pkg/kubectl/cmd/testing/fake (fakeAPIFactory.DiscoveryClient()) for details of tmpDir - tmpDirPath, err := ioutil.TempDir("", "") - if err != nil { - t.Fatalf("[%d] unexpected error: %v", i, err) - } - defer os.Remove(tmpDirPath) - - targetClusterFactory, err := fakeJoinTargetClusterFactory(tc.cluster, tc.clusterCtx, tc.dnsProvider, tmpDirPath, tc.isRBACAPIAvailable) - if err != nil { - t.Fatalf("[%d] unexpected error: %v", i, err) - } - - targetClusterContext := tc.clusterCtx - if targetClusterContext == "" { - targetClusterContext = tc.cluster - } - adminConfig, err := kubefedtesting.NewFakeAdminConfig(hostFactory, targetClusterFactory, targetClusterContext, tc.kubeconfigGlobal) - if err != nil { - t.Fatalf("[%d] unexpected error: %v", i, err) - } - - cmd := NewCmdJoin(f, buf, adminConfig) - - cmd.Flags().Set("kubeconfig", tc.kubeconfigExplicit) - cmd.Flags().Set("host-cluster-context", "substrate") - if tc.clusterCtx != "" { - cmd.Flags().Set("cluster-context", tc.clusterCtx) - } - if tc.secret != "" { - cmd.Flags().Set("secret-name", tc.secret) - } - - cmd.Run(cmd, []string{tc.cluster}) - - if tc.expectedErr == "" { - // uses the name from the cluster, not the response - // Actual data passed are tested in the fake secret and cluster - // REST clients. - if msg := buf.String(); msg != fmt.Sprintf("cluster %q created\n", tc.cluster) { - t.Errorf("[%d] unexpected output: %s", i, msg) - if cmdErrMsg != "" { - t.Errorf("[%d] unexpected error message: %s", i, cmdErrMsg) - } - } - } else { - if cmdErrMsg != tc.expectedErr { - t.Errorf("[%d] expected error: %s, got: %s, output: %s", i, tc.expectedErr, cmdErrMsg, buf.String()) - } - } - } -} - -func testJoinFederationFactory(clusterName, secretName, server string, isRBACAPIAvailable bool) cmdutil.Factory { - - want := fakeCluster(clusterName, secretName, server, isRBACAPIAvailable) - f, tf, _, _ := cmdtesting.NewAPIFactory() - codec := testapi.Federation.Codec() - ns := dynamic.ContentConfig().NegotiatedSerializer - tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, - NegotiatedSerializer: ns, - Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { - switch p, m := req.URL.Path, req.Method; { - case p == "/clusters" && m == http.MethodPost: - body, err := ioutil.ReadAll(req.Body) - if err != nil { - return nil, err - } - var got federationapi.Cluster - _, _, err = codec.Decode(body, nil, &got) - if err != nil { - return nil, err - } - // If the secret name was generated, test it separately. - if secretName == "" { - if got.Spec.SecretRef.Name == "" { - return nil, fmt.Errorf("expected a generated secret name, got \"\"") - } - got.Spec.SecretRef.Name = "" - } - if !apiequality.Semantic.DeepEqual(got, want) { - return nil, fmt.Errorf("Unexpected cluster object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, want)) - } - return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &want)}, nil - default: - return nil, fmt.Errorf("unexpected request: %#v\n%#v", req.URL, req) - } - }), - } - tf.Namespace = "test" - return f -} - -func fakeJoinHostFactory(clusterName, clusterCtx, secretName, server, token, dnsProvider string, isRBACAPIAvailable bool) (cmdutil.Factory, error) { - if clusterCtx == "" { - clusterCtx = clusterName - } - - placeholderSecretName := secretName - if placeholderSecretName == "" { - placeholderSecretName = "secretName" - } - var secretObject v1.Secret - if isRBACAPIAvailable { - secretObject = v1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: placeholderSecretName, - Namespace: util.DefaultFederationSystemNamespace, - Annotations: map[string]string{ - federation.FederationNameAnnotation: testFederationName, - federation.ClusterNameAnnotation: clusterName, - }, - }, - Data: map[string][]byte{ - "ca.crt": []byte("cert"), - "token": []byte("token"), - }, - } - } else { - kubeconfig := clientcmdapi.Config{ - Clusters: map[string]*clientcmdapi.Cluster{ - clusterCtx: { - Server: server, - }, - }, - AuthInfos: map[string]*clientcmdapi.AuthInfo{ - clusterCtx: { - Token: token, - }, - }, - Contexts: map[string]*clientcmdapi.Context{ - clusterCtx: { - Cluster: clusterCtx, - AuthInfo: clusterCtx, - }, - }, - CurrentContext: clusterCtx, - } - configBytes, err := clientcmd.Write(kubeconfig) - if err != nil { - return nil, err - } - - secretObject = v1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: placeholderSecretName, - Namespace: util.DefaultFederationSystemNamespace, - Annotations: map[string]string{ - federation.FederationNameAnnotation: testFederationName, - federation.ClusterNameAnnotation: clusterName, - }, - }, - Data: map[string][]byte{ - "kubeconfig": configBytes, - }, - } - } - - cmName := "controller-manager" - deployment := v1beta1.Deployment{ - TypeMeta: metav1.TypeMeta{ - Kind: "Deployment", - APIVersion: testapi.Extensions.GroupVersion().String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: cmName, - Namespace: util.DefaultFederationSystemNamespace, - Annotations: map[string]string{ - util.FedDomainMapKey: fmt.Sprintf("%s=%s", clusterCtx, zoneName), - federation.FederationNameAnnotation: testFederationName, - }, - }, - } - if dnsProvider == util.FedDNSProviderCoreDNS { - deployment.Annotations[util.FedDNSZoneName] = zoneName - deployment.Annotations[util.FedNameServer] = coreDNSServer - deployment.Annotations[util.FedDNSProvider] = util.FedDNSProviderCoreDNS - } - deploymentList := v1beta1.DeploymentList{Items: []v1beta1.Deployment{deployment}} - - f, tf, codec, _ := cmdtesting.NewAPIFactory() - extensionCodec := testapi.Extensions.Codec() - ns := dynamic.ContentConfig().NegotiatedSerializer - tf.ClientConfig = kubefedtesting.DefaultClientConfig() - tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, - NegotiatedSerializer: ns, - Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { - switch p, m := req.URL.Path, req.Method; { - case p == "/api/v1/namespaces/federation-system/secrets" && m == http.MethodPost: - body, err := ioutil.ReadAll(req.Body) - if err != nil { - return nil, err - } - var got v1.Secret - _, _, err = codec.Decode(body, nil, &got) - if err != nil { - return nil, err - } - - // If the secret name was generated, test it separately. - if secretName == "" { - if got.Name == "" { - return nil, fmt.Errorf("expected a generated secret name, got \"\"") - } - got.Name = placeholderSecretName - } - - if !apiequality.Semantic.DeepEqual(got, secretObject) { - return nil, fmt.Errorf("Unexpected secret object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, secretObject)) - } - return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &secretObject)}, nil - case p == "/apis/extensions/v1beta1/namespaces/federation-system/deployments" && m == http.MethodGet: - return &http.Response{StatusCode: http.StatusOK, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(extensionCodec, &deploymentList)}, nil - default: - return nil, fmt.Errorf("unexpected request: %#v\n%#v", req.URL, req) - } - }), - } - return f, nil -} - -func serviceAccountName(clusterName string) string { - return fmt.Sprintf("%s-substrate", clusterName) -} - -func fakeJoinTargetClusterFactory(clusterName, clusterCtx, dnsProvider, tmpDirPath string, isRBACAPIAvailable bool) (cmdutil.Factory, error) { - if clusterCtx == "" { - clusterCtx = clusterName - } - - configmapObject := &v1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: util.KubeDnsConfigmapName, - Namespace: metav1.NamespaceSystem, - Annotations: map[string]string{ - federation.FederationNameAnnotation: testFederationName, - federation.ClusterNameAnnotation: clusterName, - }, - }, - Data: map[string]string{ - util.FedDomainMapKey: fmt.Sprintf("%s=%s", clusterCtx, zoneName), - }, - } - - saSecretName := "serviceaccountsecret" - saSecret := v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: saSecretName, - Namespace: util.DefaultFederationSystemNamespace, - Annotations: map[string]string{ - federation.FederationNameAnnotation: testFederationName, - federation.ClusterNameAnnotation: clusterName, - }, - }, - Data: map[string][]byte{ - "ca.crt": []byte("cert"), - "token": []byte("token"), - }, - Type: v1.SecretTypeServiceAccountToken, - } - - saName := serviceAccountName(clusterName) - - serviceAccount := v1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: saName, - Annotations: map[string]string{ - federation.FederationNameAnnotation: testFederationName, - federation.ClusterNameAnnotation: clusterName, - }, - }, - Secrets: []v1.ObjectReference{ - {Name: saSecretName}, - }, - } - if dnsProvider == util.FedDNSProviderCoreDNS { - annotations := map[string]string{ - util.FedDNSProvider: util.FedDNSProviderCoreDNS, - util.FedDNSZoneName: zoneName, - util.FedNameServer: coreDNSServer, - } - configmapObject = populateStubDomainsIfRequiredTest(configmapObject, annotations) - } - - namespace := v1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "federation-system", - Annotations: map[string]string{ - federation.FederationNameAnnotation: testFederationName, - federation.ClusterNameAnnotation: clusterName, - }, - }, - } - - roleName := util.ClusterRoleName(testFederationName, saName) - clusterRole := rbacv1.ClusterRole{ - ObjectMeta: metav1.ObjectMeta{ - Name: roleName, - Namespace: util.DefaultFederationSystemNamespace, - Annotations: map[string]string{ - federation.FederationNameAnnotation: testFederationName, - federation.ClusterNameAnnotation: clusterName, - }, - }, - Rules: []rbacv1.PolicyRule{ - k8srbacv1.NewRule(rbacv1.VerbAll).Groups(rbacv1.APIGroupAll).Resources(rbacv1.ResourceAll).RuleOrDie(), - }, - } - - clusterRoleBinding, err := k8srbacv1.NewClusterBinding(roleName).SAs(util.DefaultFederationSystemNamespace, saName).Binding() - if err != nil { - return nil, err - } - - testGroup := metav1.APIGroup{ - Name: "testAPIGroup", - Versions: []metav1.GroupVersionForDiscovery{ - { - GroupVersion: "testAPIGroup/testAPIVersion", - Version: "testAPIVersion", - }, - }, - } - apiGroupList := &metav1.APIGroupList{} - apiGroupList.Groups = append(apiGroupList.Groups, testGroup) - if isRBACAPIAvailable { - rbacGroup := metav1.APIGroup{ - Name: rbacv1.GroupName, - Versions: []metav1.GroupVersionForDiscovery{ - { - GroupVersion: rbacv1.GroupName + "/v1", - Version: "v1", - }, - }, - } - apiGroupList.Groups = append(apiGroupList.Groups, rbacGroup) - } - - f, tf, codec, _ := cmdtesting.NewAPIFactory() - defaultCodec := testapi.Default.Codec() - rbacCodec := testapi.Rbac.Codec() - ns := dynamic.ContentConfig().NegotiatedSerializer - tf.TmpDir = tmpDirPath - tf.ClientConfig = kubefedtesting.DefaultClientConfig() - tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, - NegotiatedSerializer: ns, - Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { - switch p, m, r := req.URL.Path, req.Method, isRBACAPIAvailable; { - case p == "/api/v1/namespaces" && m == http.MethodPost: - return &http.Response{StatusCode: http.StatusOK, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(defaultCodec, &namespace)}, nil - - case p == "/api" && m == http.MethodGet: - return &http.Response{StatusCode: http.StatusOK, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &metav1.APIVersions{})}, nil - case p == "/apis" && m == http.MethodGet: - return &http.Response{StatusCode: http.StatusOK, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, apiGroupList)}, nil - - case p == fmt.Sprintf("/api/v1/namespaces/federation-system/serviceaccounts/%s", saName) && m == http.MethodGet && r: - return &http.Response{StatusCode: http.StatusOK, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(defaultCodec, &serviceAccount)}, nil - case p == "/api/v1/namespaces/federation-system/serviceaccounts" && m == http.MethodPost && r: - return &http.Response{StatusCode: http.StatusOK, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(defaultCodec, &serviceAccount)}, nil - - case p == "/apis/rbac.authorization.k8s.io/v1/clusterroles" && m == http.MethodPost && r: - return &http.Response{StatusCode: http.StatusOK, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(rbacCodec, &clusterRole)}, nil - case p == "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings" && m == http.MethodPost && r: - return &http.Response{StatusCode: http.StatusOK, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(rbacCodec, &clusterRoleBinding)}, nil - - case p == "/api/v1/namespaces/federation-system/secrets/serviceaccountsecret" && m == http.MethodGet && r: - return &http.Response{StatusCode: http.StatusOK, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(defaultCodec, &saSecret)}, nil - - case p == "/api/v1/namespaces/kube-system/configmaps/" && m == http.MethodPost: - body, err := ioutil.ReadAll(req.Body) - if err != nil { - return nil, err - } - var got v1.ConfigMap - _, _, err = codec.Decode(body, nil, &got) - if err != nil { - return nil, err - } - if !apiequality.Semantic.DeepEqual(&got, configmapObject) { - return nil, fmt.Errorf("Unexpected configmap object\n\tDiff: %s", diff.ObjectGoPrintDiff(&got, configmapObject)) - } - return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, configmapObject)}, nil - default: - return nil, fmt.Errorf("unexpected request: %#v\n%#v", req.URL, req) - } - }), - } - - return f, nil -} - -func fakeCluster(clusterName, secretName, server string, isRBACAPIAvailable bool) federationapi.Cluster { - cluster := federationapi.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: clusterName, - }, - Spec: federationapi.ClusterSpec{ - ServerAddressByClientCIDRs: []federationapi.ServerAddressByClientCIDR{ - { - ClientCIDR: defaultClientCIDR, - ServerAddress: server, - }, - }, - SecretRef: &v1.LocalObjectReference{ - Name: secretName, - }, - }, - } - if isRBACAPIAvailable { - saName := serviceAccountName(clusterName) - annotations := map[string]string{ - kubectl.ServiceAccountNameAnnotation: saName, - kubectl.ClusterRoleNameAnnotation: util.ClusterRoleName(testFederationName, saName), - } - cluster.ObjectMeta.SetAnnotations(annotations) - } - return cluster -} - -// TODO: Reuse the function populateStubDomainsIfRequired once that function is converted to use versioned objects. -func populateStubDomainsIfRequiredTest(configMap *v1.ConfigMap, annotations map[string]string) *v1.ConfigMap { - dnsProvider := annotations[util.FedDNSProvider] - dnsZoneName := annotations[util.FedDNSZoneName] - nameServer := annotations[util.FedNameServer] - - if dnsProvider != util.FedDNSProviderCoreDNS || dnsZoneName == "" || nameServer == "" { - return configMap - } - configMap.Data[util.KubeDnsStubDomains] = fmt.Sprintf(`{"%s":["%s"]}`, dnsZoneName, nameServer) - return configMap -} diff --git a/federation/pkg/kubefed/kubefed.go b/federation/pkg/kubefed/kubefed.go deleted file mode 100644 index dede0e282ca..00000000000 --- a/federation/pkg/kubefed/kubefed.go +++ /dev/null @@ -1,91 +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 kubefed - -import ( - "io" - - "k8s.io/apiserver/pkg/util/flag" - "k8s.io/client-go/tools/clientcmd" - kubefedinit "k8s.io/kubernetes/federation/pkg/kubefed/init" - "k8s.io/kubernetes/federation/pkg/kubefed/util" - kubectl "k8s.io/kubernetes/pkg/kubectl/cmd" - "k8s.io/kubernetes/pkg/kubectl/cmd/templates" - cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" - "k8s.io/kubernetes/pkg/kubectl/util/i18n" - - "github.com/spf13/cobra" -) - -var ( - kubefedVersionExample = templates.Examples(i18n.T(` - # Print the client and server versions for the current context - kubefed version`)) - kubefedOptionsExample = templates.Examples(i18n.T(` - # Print flags inherited by all commands - kubefed options`)) -) - -// NewKubeFedCommand creates the `kubefed` command and its nested children. -func NewKubeFedCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer, defaultServerImage, defaultEtcdImage string) *cobra.Command { - // Parent command to which all subcommands are added. - cmds := &cobra.Command{ - Use: "kubefed", - Short: "kubefed controls a Kubernetes Cluster Federation", - Long: templates.LongDesc(` - kubefed controls a Kubernetes Cluster Federation. - - Find more information at https://github.com/kubernetes/kubernetes.`), - Run: runHelp, - } - - f.BindFlags(cmds.PersistentFlags()) - f.BindExternalFlags(cmds.PersistentFlags()) - - // From this point and forward we get warnings on flags that contain "_" separators - cmds.SetGlobalNormalizationFunc(flag.WarnWordSepNormalizeFunc) - - groups := templates.CommandGroups{ - { - Message: "Basic Commands:", - Commands: []*cobra.Command{ - kubefedinit.NewCmdInit(out, util.NewAdminConfig(clientcmd.NewDefaultPathOptions()), defaultServerImage, defaultEtcdImage), - NewCmdJoin(f, out, util.NewAdminConfig(clientcmd.NewDefaultPathOptions())), - NewCmdUnjoin(f, out, err, util.NewAdminConfig(clientcmd.NewDefaultPathOptions())), - }, - }, - } - groups.Add(cmds) - - filters := []string{ - "options", - } - templates.ActsAsRootCommand(cmds, filters, groups...) - - cmdVersion := kubectl.NewCmdVersion(f, out) - cmdVersion.Example = kubefedVersionExample - cmds.AddCommand(cmdVersion) - cmdOptions := kubectl.NewCmdOptions(out) - cmdOptions.Example = kubefedOptionsExample - cmds.AddCommand(cmdOptions) - - return cmds -} - -func runHelp(cmd *cobra.Command, args []string) { - cmd.Help() -} diff --git a/federation/pkg/kubefed/testing/BUILD b/federation/pkg/kubefed/testing/BUILD deleted file mode 100644 index 6e2ba4610f1..00000000000 --- a/federation/pkg/kubefed/testing/BUILD +++ /dev/null @@ -1,35 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["testing.go"], - importpath = "k8s.io/kubernetes/federation/pkg/kubefed/testing", - deps = [ - "//federation/client/clientset_generated/federation_clientset:go_default_library", - "//federation/pkg/kubefed/util:go_default_library", - "//pkg/api:go_default_library", - "//pkg/kubectl/cmd/util:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", - "//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/pkg/kubefed/testing/testing.go b/federation/pkg/kubefed/testing/testing.go deleted file mode 100644 index 58c5780565b..00000000000 --- a/federation/pkg/kubefed/testing/testing.go +++ /dev/null @@ -1,187 +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 testing - -import ( - "bytes" - "io" - "io/ioutil" - "net/http" - "os" - - "k8s.io/apimachinery/pkg/runtime" - restclient "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - clientcmdapi "k8s.io/client-go/tools/clientcmd/api" - fedclient "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - "k8s.io/kubernetes/federation/pkg/kubefed/util" - "k8s.io/kubernetes/pkg/api" - cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" -) - -type fakeAdminConfig struct { - pathOptions *clientcmd.PathOptions - hostFactory cmdutil.Factory - targetClusterFactory cmdutil.Factory - targetClusterContext string -} - -func NewFakeAdminConfig(hostFactory cmdutil.Factory, targetFactory cmdutil.Factory, targetClusterContext, kubeconfigGlobal string) (util.AdminConfig, error) { - pathOptions := clientcmd.NewDefaultPathOptions() - pathOptions.GlobalFile = kubeconfigGlobal - pathOptions.EnvVar = "" - - return &fakeAdminConfig{ - pathOptions: pathOptions, - hostFactory: hostFactory, - targetClusterFactory: targetFactory, - targetClusterContext: targetClusterContext, - }, nil -} - -func (f *fakeAdminConfig) PathOptions() *clientcmd.PathOptions { - return f.pathOptions -} - -func (f *fakeAdminConfig) FederationClientset(context, kubeconfigPath string) (*fedclient.Clientset, error) { - fakeRestClient, err := f.hostFactory.RESTClient() - if err != nil { - return nil, err - } - - // we ignore the function params and use the client from - // the same fakefactory to create a federation clientset - // our fake factory exposes only the healthz api for this client - return fedclient.New(fakeRestClient), nil -} - -func (f *fakeAdminConfig) ClusterFactory(context, kubeconfigPath string) cmdutil.Factory { - if f.targetClusterContext != "" && f.targetClusterContext == context { - return f.targetClusterFactory - } - return f.hostFactory -} - -func FakeKubeconfigFiles() ([]string, error) { - kubeconfigs := []clientcmdapi.Config{ - { - Clusters: map[string]*clientcmdapi.Cluster{ - "syndicate": { - Server: "https://10.20.30.40", - }, - }, - AuthInfos: map[string]*clientcmdapi.AuthInfo{ - "syndicate": { - Token: "badge", - }, - }, - Contexts: map[string]*clientcmdapi.Context{ - "syndicate": { - Cluster: "syndicate", - AuthInfo: "syndicate", - }, - }, - CurrentContext: "syndicate", - }, - { - Clusters: map[string]*clientcmdapi.Cluster{ - "ally": { - Server: "ally256.example.com:80", - }, - }, - AuthInfos: map[string]*clientcmdapi.AuthInfo{ - "ally": { - Token: "souvenir", - }, - }, - Contexts: map[string]*clientcmdapi.Context{ - "ally": { - Cluster: "ally", - AuthInfo: "ally", - }, - }, - CurrentContext: "ally", - }, - { - Clusters: map[string]*clientcmdapi.Cluster{ - "ally": { - Server: "https://ally64.example.com", - }, - "confederate": { - Server: "10.8.8.8", - }, - }, - AuthInfos: map[string]*clientcmdapi.AuthInfo{ - "ally": { - Token: "souvenir", - }, - "confederate": { - Token: "totem", - }, - }, - Contexts: map[string]*clientcmdapi.Context{ - "ally": { - Cluster: "ally", - AuthInfo: "ally", - }, - "confederate": { - Cluster: "confederate", - AuthInfo: "confederate", - }, - }, - CurrentContext: "confederate", - }, - } - kubefiles := []string{} - for _, cfg := range kubeconfigs { - fakeKubeFile, _ := ioutil.TempFile("", "") - err := clientcmd.WriteToFile(cfg, fakeKubeFile.Name()) - if err != nil { - return nil, err - } - - kubefiles = append(kubefiles, fakeKubeFile.Name()) - } - return kubefiles, nil -} - -func RemoveFakeKubeconfigFiles(kubefiles []string) { - for _, file := range kubefiles { - os.Remove(file) - } -} - -func DefaultHeader() http.Header { - header := http.Header{} - header.Set("Content-Type", runtime.ContentTypeJSON) - return header -} - -func ObjBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser { - return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj)))) -} - -func DefaultClientConfig() *restclient.Config { - return &restclient.Config{ - APIPath: "/api", - ContentConfig: restclient.ContentConfig{ - NegotiatedSerializer: api.Codecs, - ContentType: runtime.ContentTypeJSON, - GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion, - }, - } -} diff --git a/federation/pkg/kubefed/unjoin.go b/federation/pkg/kubefed/unjoin.go deleted file mode 100644 index 40f79f7b38c..00000000000 --- a/federation/pkg/kubefed/unjoin.go +++ /dev/null @@ -1,333 +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 kubefed - -import ( - "fmt" - "io" - "net/url" - "strings" - - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" - "k8s.io/kubernetes/federation/pkg/kubefed/util" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" - "k8s.io/kubernetes/pkg/kubectl" - "k8s.io/kubernetes/pkg/kubectl/cmd/templates" - cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" - "k8s.io/kubernetes/pkg/kubectl/resource" - - "github.com/spf13/cobra" - "github.com/spf13/pflag" -) - -var ( - unjoin_long = templates.LongDesc(` - Unjoin a cluster from a federation. - - Current context is assumed to be a federation endpoint. - Please use the --context flag otherwise.`) - unjoin_example = templates.Examples(` - # Unjoin the specified cluster from a federation. - # Federation control plane's host cluster context name - # must be specified via the --host-cluster-context flag - # to properly cleanup the credentials. - kubectl unjoin foo --host-cluster-context=bar --cluster-context=baz`) -) - -type unjoinFederation struct { - commonOptions util.SubcommandOptions - options unjoinFederationOptions -} - -type unjoinFederationOptions struct { - clusterContext string -} - -func (o *unjoinFederationOptions) Bind(flags *pflag.FlagSet) { - flags.StringVar(&o.clusterContext, "cluster-context", "", "Name of the cluster's context in the local kubeconfig. Defaults to cluster name if unspecified.") -} - -// NewCmdUnjoin defines the `unjoin` command that removes a cluster -// from a federation. -func NewCmdUnjoin(f cmdutil.Factory, cmdOut, cmdErr io.Writer, config util.AdminConfig) *cobra.Command { - opts := &unjoinFederation{} - - cmd := &cobra.Command{ - Use: "unjoin CLUSTER_NAME --host-cluster-context=HOST_CONTEXT", - Short: "Unjoin a cluster from a federation", - Long: unjoin_long, - Example: unjoin_example, - Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(opts.commonOptions.SetName(cmd, args)) - cmdutil.CheckErr(opts.Run(f, cmdOut, cmdErr, config)) - }, - } - - flags := cmd.Flags() - opts.commonOptions.Bind(flags) - opts.options.Bind(flags) - - return cmd -} - -// unjoinFederation is the implementation of the `unjoin` command. -func (u *unjoinFederation) Run(f cmdutil.Factory, cmdOut, cmdErr io.Writer, config util.AdminConfig) error { - if u.options.clusterContext == "" { - u.options.clusterContext = u.commonOptions.Name - } - - cluster, err := popCluster(f, u.commonOptions.Name) - if err != nil { - return err - } - if cluster == nil { - fmt.Fprintf(cmdErr, "WARNING: cluster %q not found in federation, so its credentials' secret couldn't be deleted", u.commonOptions.Name) - return nil - } - - // We want a separate client factory to communicate with the - // federation host cluster. See join_federation.go for details. - hostFactory := config.ClusterFactory(u.commonOptions.Host, u.commonOptions.Kubeconfig) - hostClientset, err := hostFactory.ClientSet() - if err != nil { - return err - } - - secretName := cluster.Spec.SecretRef.Name - secret, err := hostClientset.Core().Secrets(u.commonOptions.FederationSystemNamespace).Get(secretName, metav1.GetOptions{}) - if isNotFound(err) { - // If this is the case, we cannot get the cluster clientset to delete the - // config map from that cluster and obviously cannot delete the not existing secret. - // We just publish the warning as cluster has already been removed from federation. - fmt.Fprintf(cmdErr, "WARNING: secret %q not found in the host cluster, so it couldn't be deleted. Cluster has already been removed from the federation.", secretName) - return nil - } else if err != nil { - fmt.Fprintf(cmdErr, "WARNING: Error retrieving secret from the base cluster") - return err - } - - unjoiningClusterFactory := config.ClusterFactory(u.options.clusterContext, u.commonOptions.Kubeconfig) - unjoiningClusterClientset, err := unjoiningClusterFactory.ClientSet() - outerErr := err - if err != nil { - // Attempt to get a clientset using information from the cluster. - unjoiningClusterClientset, err = getClientsetFromCluster(secret, cluster) - if err != nil { - return fmt.Errorf("unable to get clientset from kubeconfig or cluster: %v, %v", outerErr, err) - } - } - - err = deleteSecret(hostClientset, cluster.Spec.SecretRef.Name, u.commonOptions.FederationSystemNamespace) - if err != nil { - fmt.Fprintf(cmdErr, "WARNING: secret %q could not be deleted: %v", secretName, err) - // We anyways continue to try and delete the config map but with above warning - } - - // We need to ensure updating the config map created in the deregistered cluster - // This configmap was created/updated when the cluster joined this federation to aid - // the kube-dns of that cluster to aid service discovery. - err = updateConfigMapFromCluster(hostClientset, unjoiningClusterClientset, u.commonOptions.FederationSystemNamespace) - if err != nil { - fmt.Fprintf(cmdErr, "WARNING: Encountered error in deleting kube-dns configmap: %v", err) - // We anyways continue to print success message but with above warning - } - - // Delete the service account in the unjoining cluster. - err = deleteServiceAccountFromCluster(unjoiningClusterClientset, cluster, u.commonOptions.FederationSystemNamespace) - if err != nil { - fmt.Fprintf(cmdErr, "WARNING: Encountered error in deleting service account: %v", err) - return err - } - - // Delete the cluster role and role binding in the unjoining cluster. - err = deleteClusterRoleBindingFromCluster(unjoiningClusterClientset, cluster) - if err != nil { - fmt.Fprintf(cmdErr, "WARNING: Encountered error in deleting cluster role bindings: %v", err) - return err - } - - _, err = fmt.Fprintf(cmdOut, "Successfully removed cluster %q from federation\n", u.commonOptions.Name) - return err -} - -// popCluster fetches the cluster object with the given name, deletes -// it and returns the deleted cluster object. -func popCluster(f cmdutil.Factory, name string) (*federationapi.Cluster, error) { - mapper, typer := f.Object() - gvks, _, err := typer.ObjectKinds(&federationapi.Cluster{}) - if err != nil { - return nil, err - } - gvk := gvks[0] - mapping, err := mapper.RESTMapping(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, gvk.Version) - if err != nil { - return nil, err - } - client, err := f.ClientForMapping(mapping) - if err != nil { - return nil, err - } - - rh := resource.NewHelper(client, mapping) - obj, err := rh.Get("", name, false) - - if isNotFound(err) { - // Cluster isn't registered, there isn't anything to be done here. - return nil, nil - } else if err != nil { - return nil, err - } - cluster, ok := obj.(*federationapi.Cluster) - if !ok { - return nil, fmt.Errorf("unexpected object type: expected \"federation/v1beta1.Cluster\", got %T: obj: %#v", obj, obj) - } - - // Remove the cluster resource in the federation API server by - // calling rh.Delete() - return cluster, rh.Delete("", name) -} - -func updateConfigMapFromCluster(hostClientset, unjoiningClusterClientset internalclientset.Interface, fedSystemNamespace string) error { - cmDep, err := getCMDeployment(hostClientset, fedSystemNamespace) - if err != nil { - return err - } - domainMap, ok := cmDep.Annotations[util.FedDomainMapKey] - if !ok { - return fmt.Errorf("kube-dns config map data missing from controller manager annotations") - } - - configMap, err := unjoiningClusterClientset.Core().ConfigMaps(metav1.NamespaceSystem).Get(util.KubeDnsConfigmapName, metav1.GetOptions{}) - if err != nil { - return err - } - - needUpdate := false - if _, ok := configMap.Data[util.FedDomainMapKey]; ok { - configMap.Data[util.FedDomainMapKey] = removeConfigMapString(configMap.Data[util.FedDomainMapKey], domainMap) - needUpdate = true - } - - if _, ok := configMap.Data[util.KubeDnsStubDomains]; ok { - delete(configMap.Data, util.KubeDnsStubDomains) - needUpdate = true - } - - if needUpdate { - _, err = unjoiningClusterClientset.Core().ConfigMaps(metav1.NamespaceSystem).Update(configMap) - } - return err -} - -// deleteSecret deletes the secret with the given name from the host -// cluster. -func deleteSecret(clientset internalclientset.Interface, name, namespace string) error { - orphanDependents := false - return clientset.Core().Secrets(namespace).Delete(name, &metav1.DeleteOptions{OrphanDependents: &orphanDependents}) -} - -// isNotFound checks if the given error is a NotFound status error. -func isNotFound(err error) bool { - statusErr := err - if urlErr, ok := err.(*url.Error); ok { - statusErr = urlErr.Err - } - return errors.IsNotFound(statusErr) -} - -func getClientsetFromCluster(secret *api.Secret, cluster *federationapi.Cluster) (*internalclientset.Clientset, error) { - serverAddress, err := util.GetServerAddress(cluster) - if err != nil { - return nil, err - } - if serverAddress == "" { - return nil, fmt.Errorf("failed to get server address for the cluster: %s", cluster.Name) - } - - clientset, err := util.GetClientsetFromSecret(secret, serverAddress) - if err != nil { - return nil, err - } - if clientset == nil { - // There is a possibility that the clientset is nil without any error reported - return nil, fmt.Errorf("failed for get client to access cluster: %s", cluster.Name) - } - - return clientset, nil -} - -// removeConfigMapString returns an empty string if last value is removed -// or returns the remaining comma separated strings minus the one to be removed -func removeConfigMapString(str string, toRemove string) string { - if str == "" { - return "" - } - - values := strings.Split(str, ",") - if len(values) == 1 { - if values[0] == toRemove { - return "" - } else { - // Somehow our federation string is not here - // Dont do anything further - return values[0] - } - } - - for i, v := range values { - if v == toRemove { - values = append(values[:i], values[i+1:]...) - break - } - } - return strings.Join(values, ",") -} - -// deleteServiceAccountFromCluster removes the service account that the federation control plane uses -// to access the cluster from the cluster that is leaving the federation. -func deleteServiceAccountFromCluster(unjoiningClusterClientset internalclientset.Interface, cluster *federationapi.Cluster, fedSystemNamespace string) error { - serviceAccountName, ok := cluster.ObjectMeta.Annotations[kubectl.ServiceAccountNameAnnotation] - if !ok { - // If there is no service account name annotation, assume that this cluster does not have a federation control plane service account. - return nil - } - return unjoiningClusterClientset.Core().ServiceAccounts(fedSystemNamespace).Delete(serviceAccountName, &metav1.DeleteOptions{}) -} - -// deleteClusterRoleBindingFromCluster deletes the ClusterRole and ClusterRoleBinding from the -// cluster that is leaving the federation. -func deleteClusterRoleBindingFromCluster(unjoiningClusterClientset internalclientset.Interface, cluster *federationapi.Cluster) error { - clusterRoleName, ok := cluster.ObjectMeta.Annotations[kubectl.ClusterRoleNameAnnotation] - if !ok { - // If there is no cluster role name annotation, assume that this cluster does not have cluster role bindings. - return nil - } - - err := unjoiningClusterClientset.Rbac().ClusterRoleBindings().Delete(clusterRoleName, &metav1.DeleteOptions{}) - if err != nil && !errors.IsMethodNotSupported(err) && !errors.IsNotFound(err) { - return err - } - err = unjoiningClusterClientset.Rbac().ClusterRoles().Delete(clusterRoleName, &metav1.DeleteOptions{}) - if err != nil && !errors.IsMethodNotSupported(err) && !errors.IsNotFound(err) { - return err - } - return nil -} diff --git a/federation/pkg/kubefed/unjoin_test.go b/federation/pkg/kubefed/unjoin_test.go deleted file mode 100644 index 27f09999a0d..00000000000 --- a/federation/pkg/kubefed/unjoin_test.go +++ /dev/null @@ -1,307 +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 kubefed - -import ( - "bytes" - "fmt" - "net/http" - "strings" - "testing" - - "k8s.io/api/core/v1" - "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/client-go/dynamic" - "k8s.io/client-go/rest/fake" - "k8s.io/client-go/tools/clientcmd" - clientcmdapi "k8s.io/client-go/tools/clientcmd/api" - federationapi "k8s.io/kubernetes/federation/apis/federation" - fedv1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1" - kubefedtesting "k8s.io/kubernetes/federation/pkg/kubefed/testing" - "k8s.io/kubernetes/federation/pkg/kubefed/util" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/testapi" - cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" - cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" -) - -func TestUnjoinFederation(t *testing.T) { - cmdErrMsg := "" - cmdutil.BehaviorOnFatal(func(str string, code int) { - cmdErrMsg = str - }) - - fakeKubeFiles, err := kubefedtesting.FakeKubeconfigFiles() - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - defer kubefedtesting.RemoveFakeKubeconfigFiles(fakeKubeFiles) - - testCases := []struct { - cluster string - wantCluster string - wantSecret string - kubeconfigGlobal string - kubeconfigExplicit string - expectedServer string - expectedErr string - }{ - // Tests that the contexts and credentials are read from the - // global, default kubeconfig and the correct cluster resource - // is deregisterd and configmap kube-dns is removed from that cluster. - { - cluster: "syndicate", - wantCluster: "syndicate", - wantSecret: "", - kubeconfigGlobal: fakeKubeFiles[0], - kubeconfigExplicit: "", - expectedServer: "https://10.20.30.40", - expectedErr: "", - }, - // Tests that the contexts and credentials are read from the - // explicit kubeconfig file specified and the correct cluster - // resource is deregisterd and configmap kube-dns is removed from that cluster. - // kubeconfig contains a single cluster and context. - { - cluster: "ally", - wantCluster: "ally", - wantSecret: "", - kubeconfigGlobal: fakeKubeFiles[0], - kubeconfigExplicit: fakeKubeFiles[1], - expectedServer: "http://ally256.example.com:80", - expectedErr: "", - }, - // Tests that the contexts and credentials are read from the - // explicit kubeconfig file specified and the correct cluster - // resource is deregisterd and configmap kube-dns is removed from that - // cluster. kubeconfig consists of multiple clusters and contexts. - { - cluster: "confederate", - wantCluster: "confederate", - wantSecret: "", - kubeconfigGlobal: fakeKubeFiles[1], - kubeconfigExplicit: fakeKubeFiles[2], - expectedServer: "https://10.8.8.8", - expectedErr: "", - }, - // Negative test to ensure that we get the right warning - // when the specified cluster to deregister is not found. - { - cluster: "noexist", - wantCluster: "affiliate", - wantSecret: "", - kubeconfigGlobal: fakeKubeFiles[0], - kubeconfigExplicit: "", - expectedServer: "https://10.20.30.40", - expectedErr: fmt.Sprintf("WARNING: cluster %q not found in federation, so its credentials' secret couldn't be deleted", "affiliate"), - }, - // Negative test to ensure that we get the right warning - // when the specified cluster's credentials secret is not - // found. - { - cluster: "affiliate", - wantCluster: "affiliate", - wantSecret: "noexist", - kubeconfigGlobal: fakeKubeFiles[0], - kubeconfigExplicit: "", - expectedServer: "https://10.20.30.40", - expectedErr: fmt.Sprintf("WARNING: secret %q not found in the host cluster, so it couldn't be deleted. Cluster has already been removed from the federation.", "noexist"), - }, - // TODO: Figure out a way to test the scenarios of configmap deletion - // As of now we delete the config map after deriving the clientset using - // the cluster object we retrieved from the federation server and the - // secret object retrieved from the base cluster. - // Still to find out a way to introduce some fakes and unit test this path. - } - - for i, tc := range testCases { - cmdErrMsg = "" - f := testUnjoinFederationFactory(tc.cluster, tc.expectedServer, tc.wantSecret) - buf := bytes.NewBuffer([]byte{}) - errBuf := bytes.NewBuffer([]byte{}) - - hostFactory := fakeUnjoinHostFactory(tc.cluster) - adminConfig, err := kubefedtesting.NewFakeAdminConfig(hostFactory, nil, "", tc.kubeconfigGlobal) - if err != nil { - t.Fatalf("[%d] unexpected error: %v", i, err) - } - - cmd := NewCmdUnjoin(f, buf, errBuf, adminConfig) - - cmd.Flags().Set("kubeconfig", tc.kubeconfigExplicit) - cmd.Flags().Set("host", "substrate") - cmd.Run(cmd, []string{tc.wantCluster}) - - if tc.expectedErr == "" { - // uses the name from the cluster, not the response - // Actual data passed are tested in the fake secret and cluster - // REST clients. - if msg := buf.String(); msg != fmt.Sprintf("Successfully removed cluster %q from federation\n", tc.cluster) { - t.Errorf("[%d] unexpected output: %s", i, msg) - if cmdErrMsg != "" { - t.Errorf("[%d] unexpected error message: %s", i, cmdErrMsg) - } - } - // TODO: There are warnings posted on errBuf, which we ignore as of now - // and we should be able to test out these warnings also in future. - // This is linked to the previous todo comment. - } else { - if errMsg := errBuf.String(); errMsg != tc.expectedErr { - t.Errorf("[%d] expected warning: %s, got: %s, output: %s", i, tc.expectedErr, errMsg, buf.String()) - } - - } - } -} - -func testUnjoinFederationFactory(name, server, secret string) cmdutil.Factory { - urlPrefix := "/clusters/" - - cluster := fakeCluster(name, name, server, true) - if secret != "" { - cluster.Spec.SecretRef.Name = secret - } - - f, tf, _, _ := cmdtesting.NewAPIFactory() - codec := testapi.Federation.Codec() - tf.ClientConfig = kubefedtesting.DefaultClientConfig() - ns := serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{Serializer: runtime.NewCodec(f.JSONEncoder(), api.Codecs.UniversalDecoder(fedv1beta1.SchemeGroupVersion))}) - tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, - NegotiatedSerializer: ns, - GroupName: "federation", - Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { - switch p, m := req.URL.Path, req.Method; { - case strings.HasPrefix(p, urlPrefix): - got := strings.TrimPrefix(p, urlPrefix) - if got != name { - return nil, errors.NewNotFound(federationapi.Resource("clusters"), got) - } - - switch m { - case http.MethodGet: - return &http.Response{StatusCode: http.StatusOK, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &cluster)}, nil - case http.MethodDelete: - status := metav1.Status{ - Status: "Success", - } - return &http.Response{StatusCode: http.StatusOK, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &status)}, nil - default: - return nil, fmt.Errorf("unexpected method: %#v\n%#v", req.URL, req) - } - default: - return nil, fmt.Errorf("unexpected request: %#v\n%#v", req.URL, req) - } - }), - } - tf.Namespace = "test" - return f -} - -func fakeUnjoinHostFactory(clusterName string) cmdutil.Factory { - secretsPrefix := "/api/v1/namespaces/federation-system/secrets/" - clusterRolePrefix := "/apis/rbac.authorization.k8s.io/v1/clusterroles/" - serviceAccountPrefix := "/api/v1/namespaces/federation-system/serviceaccounts/" - clusterRoleBindingPrefix := "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings/" - - // Using dummy bytes for now - configBytes, _ := clientcmd.Write(clientcmdapi.Config{}) - secretObject := v1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: clusterName, - Namespace: util.DefaultFederationSystemNamespace, - }, - Data: map[string][]byte{ - "kubeconfig": configBytes, - }, - } - - f, tf, codec, _ := cmdtesting.NewAPIFactory() - ns := dynamic.ContentConfig().NegotiatedSerializer - tf.ClientConfig = kubefedtesting.DefaultClientConfig() - tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, - NegotiatedSerializer: ns, - Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { - switch p, m := req.URL.Path, req.Method; { - case strings.HasPrefix(p, secretsPrefix): - switch m { - case http.MethodDelete: - got := strings.TrimPrefix(p, secretsPrefix) - if got != clusterName { - return nil, errors.NewNotFound(api.Resource("secrets"), got) - } - status := metav1.Status{ - Status: "Success", - } - return &http.Response{StatusCode: http.StatusOK, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &status)}, nil - case http.MethodGet: - got := strings.TrimPrefix(p, secretsPrefix) - if got != clusterName { - return nil, errors.NewNotFound(api.Resource("secrets"), got) - } - return &http.Response{StatusCode: http.StatusOK, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &secretObject)}, nil - default: - return nil, fmt.Errorf("unexpected request method: %#v\n%#v", req.URL, req) - } - case strings.HasPrefix(p, serviceAccountPrefix) && m == http.MethodDelete: - got := strings.TrimPrefix(p, serviceAccountPrefix) - want := serviceAccountName(clusterName) - if got != want { - return nil, errors.NewNotFound(api.Resource("serviceaccounts"), got) - } - - status := metav1.Status{ - Status: "Success", - } - return &http.Response{StatusCode: http.StatusOK, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &status)}, nil - case strings.HasPrefix(p, clusterRoleBindingPrefix) && m == http.MethodDelete: - got := strings.TrimPrefix(p, clusterRoleBindingPrefix) - want := util.ClusterRoleName(testFederationName, serviceAccountName(clusterName)) - if got != want { - return nil, errors.NewNotFound(api.Resource("clusterrolebindings"), got) - } - - status := metav1.Status{ - Status: "Success", - } - return &http.Response{StatusCode: http.StatusOK, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &status)}, nil - case strings.HasPrefix(p, clusterRolePrefix) && m == http.MethodDelete: - got := strings.TrimPrefix(p, clusterRolePrefix) - want := util.ClusterRoleName(testFederationName, serviceAccountName(clusterName)) - if got != want { - return nil, errors.NewNotFound(api.Resource("clusterroles"), got) - } - - status := metav1.Status{ - Status: "Success", - } - return &http.Response{StatusCode: http.StatusOK, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &status)}, nil - default: - return nil, fmt.Errorf("unexpected request: %#v\n%#v", req.URL, req) - } - }), - } - return f -} diff --git a/federation/pkg/kubefed/util/BUILD b/federation/pkg/kubefed/util/BUILD deleted file mode 100644 index 66cfce81920..00000000000 --- a/federation/pkg/kubefed/util/BUILD +++ /dev/null @@ -1,47 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["util.go"], - importpath = "k8s.io/kubernetes/federation/pkg/kubefed/util", - deps = [ - "//federation/apis/federation:go_default_library", - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/client/clientset_generated/federation_clientset:go_default_library", - "//pkg/api:go_default_library", - "//pkg/apis/rbac:go_default_library", - "//pkg/apis/rbac/v1:go_default_library", - "//pkg/apis/rbac/v1alpha1:go_default_library", - "//pkg/apis/rbac/v1beta1:go_default_library", - "//pkg/client/clientset_generated/internalclientset:go_default_library", - "//pkg/kubectl/cmd:go_default_library", - "//pkg/kubectl/cmd/util:go_default_library", - "//vendor/github.com/spf13/cobra:go_default_library", - "//vendor/github.com/spf13/pflag:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", - "//vendor/k8s.io/client-go/discovery:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", - "//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/pkg/kubefed/util/util.go b/federation/pkg/kubefed/util/util.go deleted file mode 100644 index 427bbc27982..00000000000 --- a/federation/pkg/kubefed/util/util.go +++ /dev/null @@ -1,347 +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 util - -import ( - "fmt" - "net" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - utilnet "k8s.io/apimachinery/pkg/util/net" - "k8s.io/client-go/discovery" - restclient "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - clientcmdapi "k8s.io/client-go/tools/clientcmd/api" - federationapi "k8s.io/kubernetes/federation/apis/federation" - fedv1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1" - fedclient "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/rbac" - rbacv1 "k8s.io/kubernetes/pkg/apis/rbac/v1" - rbacv1alpha1 "k8s.io/kubernetes/pkg/apis/rbac/v1alpha1" - rbacv1beta1 "k8s.io/kubernetes/pkg/apis/rbac/v1beta1" - client "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" - kubectlcmd "k8s.io/kubernetes/pkg/kubectl/cmd" - cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" - - "github.com/spf13/cobra" - "github.com/spf13/pflag" -) - -const ( - // KubeconfigSecretDataKey is the key name used in the secret to - // stores a cluster's credentials. - KubeconfigSecretDataKey = "kubeconfig" - - // Used in and to create the kube-dns configmap storing the zone info - FedDomainMapKey = "federations" - KubeDnsConfigmapName = "kube-dns" - FedDNSZoneName = "dns-zone-name" - FedNameServer = "nameserver" - FedDNSProvider = "dns-provider" - FedDNSProviderCoreDNS = "coredns" - KubeDnsStubDomains = "stubDomains" - - // DefaultFederationSystemNamespace is the namespace in which - // federation system components are hosted. - DefaultFederationSystemNamespace = "federation-system" - - // Used to build a clientset for a cluster using the secret - userAgentName = "kubefed-tool" - KubeAPIQPS = 20.0 - KubeAPIBurst = 30 - - rbacAPINotAvailable = "RBAC API not available" -) - -// used to identify the rbac api availability error. -type NoRBACAPIError struct { - s string -} - -func (n *NoRBACAPIError) Error() string { - return n.s -} - -// AdminConfig provides a filesystem based kubeconfig (via -// `PathOptions()`) and a mechanism to talk to the federation -// host cluster and the federation control plane api server. -type AdminConfig interface { - // PathOptions provides filesystem based kubeconfig access. - PathOptions() *clientcmd.PathOptions - // FedClientSet provides a federation API compliant clientset - // to communicate with the federation control plane api server - FederationClientset(context, kubeconfigPath string) (*fedclient.Clientset, error) - // ClusterFactory provides a mechanism to communicate with the - // cluster derived from the context and the kubeconfig. - ClusterFactory(context, kubeconfigPath string) cmdutil.Factory -} - -// adminConfig implements the AdminConfig interface. -type adminConfig struct { - pathOptions *clientcmd.PathOptions -} - -// NewAdminConfig creates an admin config for `kubefed` commands. -func NewAdminConfig(pathOptions *clientcmd.PathOptions) AdminConfig { - return &adminConfig{ - pathOptions: pathOptions, - } -} - -func (a *adminConfig) PathOptions() *clientcmd.PathOptions { - return a.pathOptions -} - -func (a *adminConfig) FederationClientset(context, kubeconfigPath string) (*fedclient.Clientset, error) { - fedConfig := a.getClientConfig(context, kubeconfigPath) - fedClientConfig, err := fedConfig.ClientConfig() - if err != nil { - return nil, err - } - - return fedclient.NewForConfigOrDie(fedClientConfig), nil -} - -func (a *adminConfig) ClusterFactory(context, kubeconfigPath string) cmdutil.Factory { - hostClientConfig := a.getClientConfig(context, kubeconfigPath) - return cmdutil.NewFactory(hostClientConfig) -} - -func (a *adminConfig) getClientConfig(context, kubeconfigPath string) clientcmd.ClientConfig { - loadingRules := *a.pathOptions.LoadingRules - loadingRules.Precedence = a.pathOptions.GetLoadingPrecedence() - loadingRules.ExplicitPath = kubeconfigPath - overrides := &clientcmd.ConfigOverrides{ - CurrentContext: context, - } - - return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(&loadingRules, overrides) -} - -// SubcommandOptions holds the configuration required by the subcommands of -// `kubefed`. -type SubcommandOptions struct { - Name string - Host string - FederationSystemNamespace string - Kubeconfig string -} - -func (o *SubcommandOptions) Bind(flags *pflag.FlagSet) { - flags.StringVar(&o.Kubeconfig, "kubeconfig", "", "Path to the kubeconfig file to use for CLI requests.") - flags.StringVar(&o.Host, "host-cluster-context", "", "Host cluster context") - flags.StringVar(&o.FederationSystemNamespace, "federation-system-namespace", DefaultFederationSystemNamespace, "Namespace in the host cluster where the federation system components are installed") -} - -func (o *SubcommandOptions) SetName(cmd *cobra.Command, args []string) error { - name, err := kubectlcmd.NameFromCommandArgs(cmd, args) - if err != nil { - return err - } - o.Name = name - return nil -} - -func CreateKubeconfigSecret(clientset client.Interface, kubeconfig *clientcmdapi.Config, namespace, name, federationName, clusterName string, dryRun bool) (*api.Secret, error) { - configBytes, err := clientcmd.Write(*kubeconfig) - if err != nil { - return nil, err - } - annotations := map[string]string{ - federationapi.FederationNameAnnotation: federationName, - } - - if clusterName != "" { - annotations[federationapi.ClusterNameAnnotation] = clusterName - } - - // Build the secret object with the minified and flattened - // kubeconfig content. - secret := &api.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Annotations: annotations, - }, - Data: map[string][]byte{ - KubeconfigSecretDataKey: configBytes, - }, - } - - if !dryRun { - return clientset.Core().Secrets(namespace).Create(secret) - } - return secret, nil -} - -var kubeconfigGetterForSecret = func(secret *api.Secret) clientcmd.KubeconfigGetter { - return func() (*clientcmdapi.Config, error) { - var data []byte - ok := false - data, ok = secret.Data[KubeconfigSecretDataKey] - if !ok { - return nil, fmt.Errorf("secret does not have data with key: %s", KubeconfigSecretDataKey) - } - return clientcmd.Load(data) - } -} - -func GetClientsetFromSecret(secret *api.Secret, serverAddress string) (*client.Clientset, error) { - clusterConfig, err := buildConfigFromSecret(secret, serverAddress) - if err == nil && clusterConfig != nil { - clientset := client.NewForConfigOrDie(restclient.AddUserAgent(clusterConfig, userAgentName)) - return clientset, nil - } - return nil, err -} - -func GetServerAddress(c *fedv1beta1.Cluster) (string, error) { - hostIP, err := utilnet.ChooseHostInterface() - if err != nil { - return "", err - } - - for _, item := range c.Spec.ServerAddressByClientCIDRs { - _, cidrnet, err := net.ParseCIDR(item.ClientCIDR) - if err != nil { - return "", err - } - if cidrnet.Contains(hostIP) { - return item.ServerAddress, nil - } - } - - return "", nil -} - -func buildConfigFromSecret(secret *api.Secret, serverAddress string) (*restclient.Config, error) { - var clusterConfig *restclient.Config - var err error - // Pre-1.7, the secret contained a serialized kubeconfig which contained appropriate credentials. - // Post-1.7, the secret contains credentials for a service account. - // Check for the service account credentials, and use them if they exist; if not, use the - // serialized kubeconfig. - token, tokenFound := secret.Data["token"] - ca, caFound := secret.Data["ca.crt"] - if tokenFound != caFound { - return nil, fmt.Errorf("secret should have values for either both 'ca.crt' and 'token' in its Data, or neither: %v", secret) - } else if tokenFound && caFound { - clusterConfig, err = clientcmd.BuildConfigFromFlags(serverAddress, "") - clusterConfig.CAData = ca - clusterConfig.BearerToken = string(token) - } else { - kubeconfigGetter := kubeconfigGetterForSecret(secret) - clusterConfig, err = clientcmd.BuildConfigFromKubeconfigGetter(serverAddress, kubeconfigGetter) - } - - if err != nil { - return nil, err - } - - clusterConfig.QPS = KubeAPIQPS - clusterConfig.Burst = KubeAPIBurst - - return clusterConfig, nil -} - -// GetVersionedClientForRBACOrFail discovers the versioned rbac APIs and gets the versioned -// clientset for either the preferred version or the first listed version (if no preference listed) -// TODO: We need to evaluate the usage of RESTMapper interface to achieve the same functionality -func GetVersionedClientForRBACOrFail(hostFactory cmdutil.Factory) (client.Interface, error) { - discoveryclient, err := hostFactory.DiscoveryClient() - if err != nil { - return nil, err - } - - rbacVersion, err := getRBACVersion(discoveryclient) - if err != nil && !discoveryclient.Fresh() { - discoveryclient.Invalidate() - rbacVersion, err = getRBACVersion(discoveryclient) - } - if err != nil { - return nil, err - } - - return hostFactory.ClientSetForVersion(rbacVersion) -} - -func getRBACVersion(discoveryclient discovery.CachedDiscoveryInterface) (*schema.GroupVersion, error) { - - groupList, err := discoveryclient.ServerGroups() - if err != nil { - return nil, fmt.Errorf("Couldn't get clientset to create RBAC roles in the host cluster: %v", err) - } - - // These are the RBAC versions we can speak - knownVersions := map[schema.GroupVersion]bool{ - rbacv1.SchemeGroupVersion: true, - rbacv1alpha1.SchemeGroupVersion: true, - rbacv1beta1.SchemeGroupVersion: true, - } - - // This holds any RBAC versions listed in discovery we do not know how to speak - unknownVersions := []schema.GroupVersion{} - - for _, g := range groupList.Groups { - if g.Name == rbac.GroupName { - if g.PreferredVersion.GroupVersion != "" { - gv, err := schema.ParseGroupVersion(g.PreferredVersion.GroupVersion) - if err != nil { - return nil, err - } - if knownVersions[gv] { - return &gv, nil - } - } - for _, version := range g.Versions { - if version.GroupVersion != "" { - gv, err := schema.ParseGroupVersion(version.GroupVersion) - if err != nil { - return nil, err - } - if knownVersions[gv] { - return &gv, nil - } else { - unknownVersions = append(unknownVersions, gv) - } - } - } - } - } - - if len(unknownVersions) > 0 { - return nil, &NoRBACAPIError{fmt.Sprintf("%s\nUnknown RBAC API versions: %v", rbacAPINotAvailable, unknownVersions)} - } - - return nil, &NoRBACAPIError{rbacAPINotAvailable} -} - -// ClusterServiceAccountName returns the name of a service account -// whose credentials are used by the host cluster to access the -// client cluster. -func ClusterServiceAccountName(joiningClusterName, hostContext string) string { - return fmt.Sprintf("%s-%s", joiningClusterName, hostContext) -} - -// ClusterRoleName returns the name of a ClusterRole and its associated -// ClusterRoleBinding that are used to allow the service account to -// access necessary resources on the cluster. -func ClusterRoleName(federationName, serviceAccountName string) string { - return fmt.Sprintf("federation-controller-manager:%s-%s", federationName, serviceAccountName) -} diff --git a/federation/plugin/pkg/admission/schedulingpolicy/BUILD b/federation/plugin/pkg/admission/schedulingpolicy/BUILD deleted file mode 100644 index f42e990f8c7..00000000000 --- a/federation/plugin/pkg/admission/schedulingpolicy/BUILD +++ /dev/null @@ -1,68 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = [ - "admission_test.go", - "merge_test.go", - ], - importpath = "k8s.io/kubernetes/federation/plugin/pkg/admission/schedulingpolicy", - library = ":go_default_library", - deps = [ - "//pkg/api:go_default_library", - "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", - "//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library", - "//vendor/k8s.io/client-go/testing:go_default_library", - ], -) - -go_library( - name = "go_default_library", - srcs = [ - "admission.go", - "merge.go", - "query.go", - ], - importpath = "k8s.io/kubernetes/federation/plugin/pkg/admission/schedulingpolicy", - deps = [ - "//pkg/api:go_default_library", - "//pkg/api/ref:go_default_library", - "//pkg/client/clientset_generated/internalclientset:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library", - "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", - "//vendor/k8s.io/apiserver/pkg/util/webhook:go_default_library", - "//vendor/k8s.io/client-go/dynamic:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/plugin/pkg/admission/schedulingpolicy/admission.go b/federation/plugin/pkg/admission/schedulingpolicy/admission.go deleted file mode 100644 index 0397554bc76..00000000000 --- a/federation/plugin/pkg/admission/schedulingpolicy/admission.go +++ /dev/null @@ -1,213 +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 schedulingpolicy implements a webhook that queries an external API -// to obtain scheduling decisions for Federated sources. -package schedulingpolicy - -import ( - "fmt" - "io" - "time" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/apimachinery/pkg/util/yaml" - "k8s.io/apiserver/pkg/admission" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/ref" - "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" -) - -const ( - pluginName = "SchedulingPolicy" - configKey = "schedulingPolicy" - policyConfigMapNamespace = "kube-federation-scheduling-policy" - - // Default backoff delay for policy engine query retries. The actual - // backoff implementation is handled by k8s.io/apiserver/pkg/util/webhook. - // If the admission controller config file does not specify a backoff, this - // one is used. - defaultRetryBackoff = time.Millisecond * 100 -) - -type admissionConfig struct { - Kubeconfig string `json:"kubeconfig"` - RetryBackoff time.Duration `json:"retryBackoff"` -} - -type admissionController struct { - *admission.Handler - policyEngineClient *rest.RESTClient // client to communicate with policy engine - policyEngineRetryBackoff time.Duration // backoff for policy engine queries - client internalclientset.Interface // client to communicate with federation-apiserver -} - -// Register registers the plugin. -func Register(plugins *admission.Plugins) { - plugins.Register(pluginName, func(file io.Reader) (admission.Interface, error) { - return newAdmissionController(file) - }) -} - -func newAdmissionController(file io.Reader) (*admissionController, error) { - config, err := loadConfig(file) - if err != nil { - return nil, err - } - - policyEngineClient, err := loadRestClient(config.Kubeconfig) - if err != nil { - return nil, err - } - - c := &admissionController{ - Handler: admission.NewHandler(admission.Create, admission.Update), - policyEngineClient: policyEngineClient, - policyEngineRetryBackoff: config.RetryBackoff, - } - - return c, nil -} - -func (c *admissionController) Validate() error { - if c.client == nil { - return fmt.Errorf("%s requires a client", pluginName) - } - return nil -} - -func (c *admissionController) SetInternalKubeClientSet(client internalclientset.Interface) { - c.client = client -} - -func (c *admissionController) Admit(a admission.Attributes) (err error) { - exists, err := c.policyExists() - if err != nil { - return c.handleError(a, err) - } - - if !exists { - return nil - } - - obj := a.GetObject() - decision, err := newPolicyEngineQuery(c.policyEngineClient, c.policyEngineRetryBackoff, obj, a.GetKind()).Do() - - if err != nil { - return c.handleError(a, err) - } - - if err := decision.Error(); err != nil { - return c.handleError(a, err) - } - - mergeAnnotations(obj, decision.Annotations) - - return nil -} - -func (c *admissionController) handleError(a admission.Attributes, err error) error { - - c.publishEvent(a, err.Error()) - - return admission.NewForbidden(a, err) -} - -func (c *admissionController) publishEvent(a admission.Attributes, msg string) { - - obj := a.GetObject() - - ref, err := ref.GetReference(api.Scheme, obj) - if err != nil { - runtime.HandleError(err) - return - } - - event := &api.Event{ - InvolvedObject: *ref, - Message: msg, - Source: api.EventSource{ - Component: fmt.Sprintf("schedulingpolicy"), - }, - Type: "Warning", - } - - if _, err := c.client.Core().Events(a.GetNamespace()).Create(event); err != nil { - runtime.HandleError(err) - return - } -} - -func (c *admissionController) policyExists() (bool, error) { - lst, err := c.client.Core().ConfigMaps(policyConfigMapNamespace).List(metav1.ListOptions{}) - if err != nil { - return true, err - } - return len(lst.Items) > 0, nil -} - -func loadConfig(file io.Reader) (*admissionConfig, error) { - var cfg admissionConfig - if file == nil { - return nil, fmt.Errorf("--admission-control-config-file not specified or invalid") - } - - if err := yaml.NewYAMLOrJSONDecoder(file, 4096).Decode(&cfg); err != nil { - return nil, err - } - - if len(cfg.Kubeconfig) == 0 { - return nil, fmt.Errorf("kubeconfig path must not be empty") - } - - if cfg.RetryBackoff == 0 { - cfg.RetryBackoff = defaultRetryBackoff - } else { - // Scale up value from config (which is unmarshalled as ns). - cfg.RetryBackoff *= time.Millisecond - } - - if cfg.RetryBackoff.Nanoseconds() < 0 { - return nil, fmt.Errorf("retryBackoff must not be negative") - } - - return &cfg, nil -} - -func loadRestClient(kubeConfigFile string) (*rest.RESTClient, error) { - - loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() - loadingRules.ExplicitPath = kubeConfigFile - loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{}) - - clientConfig, err := loader.ClientConfig() - if err != nil { - return nil, err - } - - clientConfig.ContentConfig.NegotiatedSerializer = dynamic.ContentConfig().NegotiatedSerializer - - restClient, err := rest.UnversionedRESTClientFor(clientConfig) - if err != nil { - return nil, err - } - - return restClient, nil -} diff --git a/federation/plugin/pkg/admission/schedulingpolicy/admission_test.go b/federation/plugin/pkg/admission/schedulingpolicy/admission_test.go deleted file mode 100644 index 78713433282..00000000000 --- a/federation/plugin/pkg/admission/schedulingpolicy/admission_test.go +++ /dev/null @@ -1,474 +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 schedulingpolicy - -import ( - "bytes" - "encoding/json" - "fmt" - "html/template" - "io" - "io/ioutil" - "net/http" - "net/http/httptest" - "os" - "reflect" - "testing" - - extensionsv1 "k8s.io/api/extensions/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apiserver/pkg/admission" - "k8s.io/apiserver/pkg/authentication/user" - core "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" -) - -func TestNewAdmissionController(t *testing.T) { - tempfile, err := ioutil.TempFile("", "") - if err != nil { - t.Fatalf("Unexpected error while creating temporary file: %v", err) - } - p := tempfile.Name() - defer os.Remove(p) - - kubeconfig := ` -clusters: - - name: foo - cluster: - server: https://example.com -users: - - name: alice - user: - token: deadbeef -contexts: - - name: default - context: - cluster: foo - user: alice -current-context: default -` - - if _, err := tempfile.WriteString(kubeconfig); err != nil { - t.Fatalf("Unexpected error while writing test kubeconfig file: %v", err) - } - - tests := []struct { - note string - input string - wantErr bool - }{ - {"no config", "", true}, - {"bad json", `{"foo": `, true}, - {"bad yaml", `{foo" `, true}, - { - "missing kubeconfig", - `{"foo": {}}`, - true, - }, - { - "kubeconfig not found", - `{ - "kubeconfig": "/kube-federation-scheduling-policy-file-not-found-test" - }`, - true, - }, - { - "bad retry backoff", - fmt.Sprintf(` - { - "kubeconfig": %q, - "retryBackoff": -1 - } - `, p), - true, - }, - { - "a valid config", - fmt.Sprintf(` - { - "kubeconfig": %q - } - `, p), - false, - }, - { - "a valid config with retry backoff", - fmt.Sprintf(` - { - "kubeconfig": %q, - "retryBackoff": 200 - } - `, p), - false, - }, - } - - for _, tc := range tests { - var file io.Reader - if tc.input == "" { - file = nil - } else { - file = bytes.NewBufferString(tc.input) - } - - _, err := newAdmissionController(file) - - if tc.wantErr && err == nil { - t.Errorf("%v: Expected error", tc.note) - } else if !tc.wantErr && err != nil { - t.Errorf("%v: Unexpected error: %v", tc.note, err) - } - } -} - -func TestAdmitQueryPayload(t *testing.T) { - var body interface{} - - serve := func(w http.ResponseWriter, r *http.Request) { - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - t.Fatalf("Unexpected error reading admission payload: %v", err) - } - - // No errors or annotations. - w.Write([]byte(`{}`)) - } - - controller, err := newControllerWithTestServer(serve, true) - if err != nil { - t.Fatalf("Unexpected error while creating test admission controller/server: %v", err) - } - - rs := makeReplicaSet() - rs.Spec.MinReadySeconds = 100 - attrs := makeAdmissionRecord(rs) - err = controller.Admit(attrs) - - if err != nil { - t.Fatalf("Unexpected error from admission controller: %v", err) - } - - obj := body.(map[string]interface{}) - metadata := obj["metadata"].(map[string]interface{}) - spec := obj["spec"].(map[string]interface{}) - name := metadata["name"].(string) - minReadySeconds := spec["minReadySeconds"].(float64) - - expectedName := "myapp" - if name != expectedName { - t.Fatalf("Expected replicaset.metadata.name to be %v but got: %v", expectedName, name) - } - - expectedMinReadySeconds := float64(100) - if minReadySeconds != expectedMinReadySeconds { - t.Fatalf("Expected replicaset.spec.minReadySeconds to be %v but got: %v", expectedMinReadySeconds, minReadySeconds) - } -} - -func TestAdmitFailInternal(t *testing.T) { - serve := func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(200) - } - - controller, err := newControllerWithTestServer(serve, false) - if err != nil { - t.Fatalf("Unexpected error while creating test admission controller/server: %v", err) - } - - mockClient := &fake.Clientset{} - mockClient.AddReactor("list", "configmaps", func(action core.Action) (bool, runtime.Object, error) { - return true, nil, fmt.Errorf("unknown error") - }) - - controller.SetInternalKubeClientSet(mockClient) - - attrs := makeAdmissionRecord(makeReplicaSet()) - err = controller.Admit(attrs) - - if err == nil { - t.Fatalf("Expected admission controller to fail closed") - } -} - -func TestAdmitPolicyDoesNotExist(t *testing.T) { - - controller, err := newControllerWithTestServer(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(404) - }, false) - if err != nil { - t.Fatalf("Unexpected error while creating test admission controller/server: %v", err) - } - - attrs := makeAdmissionRecord(makeReplicaSet()) - err = controller.Admit(attrs) - - if err != nil { - t.Fatalf("Expected admission controller to fail open but got error: %v", err) - } -} - -func TestAdmitFailClosed(t *testing.T) { - tests := []struct { - note string - statusCode int - body string - }{ - {"server error", 500, ""}, - {"unmarshal error", 200, "{"}, - {"undefined result", 404, ``}, - {"policy errors", 200, `{"errors": ["conflicting replica-set-preferences"]}`}, - } - - for _, tc := range tests { - - serve := func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(tc.statusCode) - if len(tc.body) > 0 { - w.Write([]byte(tc.body)) - } - } - - controller, err := newControllerWithTestServer(serve, true) - - if err != nil { - t.Errorf("%v: Unexpected error while creating test admission controller/server: %v", tc.note, err) - continue - } - - obj := makeReplicaSet() - attrs := admission.NewAttributesRecord(obj, nil, obj.GroupVersionKind(), obj.Namespace, obj.Name, api.Resource("replicasets").WithVersion("version"), "", admission.Create, &user.DefaultInfo{}) - err = controller.Admit(attrs) - if err == nil { - t.Errorf("%v: Expected admission controller to fail closed", tc.note) - } - - } -} - -func TestAdmitRetries(t *testing.T) { - var numQueries int - - controller, err := newControllerWithTestServer(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(500) - numQueries++ - }, true) - - if err != nil { - t.Fatalf("Unexpected error while creating test admission controller/server: %v", err) - } - - err = controller.Admit(makeAdmissionRecord(makeReplicaSet())) - - if err == nil { - t.Fatalf("Expected admission controller to fail closed") - } - - if numQueries <= 1 { - t.Fatalf("Expected multiple queries/retries but got (numQueries): %v", numQueries) - } -} - -func TestAdmitSuccessWithAnnotationMerge(t *testing.T) { - controller, err := newControllerWithTestServer(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(` - { - "annotations": { - "foo": "bar-2" - } - } - `)) - }, true) - if err != nil { - t.Fatalf("Unexpected error while creating test admission controller/server: %v", err) - } - - obj := makeReplicaSet() - obj.Annotations = map[string]string{} - obj.Annotations["foo"] = "bar" - obj.Annotations["bar"] = "baz" - - attrs := admission.NewAttributesRecord(obj, nil, obj.GroupVersionKind(), obj.Namespace, obj.Name, api.Resource("replicasets").WithVersion("version"), "", admission.Create, &user.DefaultInfo{}) - err = controller.Admit(attrs) - if err != nil { - t.Fatalf("Unexpected error from admission controller: %v", err) - } - - annotations := attrs.GetObject().(*extensionsv1.ReplicaSet).Annotations - expected := map[string]string{ - "foo": "bar-2", - "bar": "baz", - } - - if !reflect.DeepEqual(annotations, expected) { - t.Fatalf("Expected annotations to be %v but got: %v", expected, annotations) - } -} - -func newControllerWithTestServer(f func(w http.ResponseWriter, r *http.Request), policiesExist bool) (*admissionController, error) { - server, err := newTestServer(f) - if err != nil { - return nil, err - } - - kubeConfigFile, err := makeKubeConfigFile(server.URL, "/some/path/to/decision") - if err != nil { - return nil, err - } - - defer os.Remove(kubeConfigFile) - - configFile, err := makeAdmissionControlConfigFile(kubeConfigFile) - if err != nil { - return nil, err - } - - defer os.Remove(configFile) - - file, err := os.Open(configFile) - if err != nil { - return nil, err - } - defer file.Close() - - controller, err := newAdmissionController(file) - if err != nil { - return nil, err - } - - mockClient := &fake.Clientset{} - - var items []api.ConfigMap - - if policiesExist { - items = append(items, api.ConfigMap{}) - } - - mockClient.AddReactor("list", "configmaps", func(action core.Action) (bool, runtime.Object, error) { - if action.GetNamespace() == policyConfigMapNamespace { - return true, &api.ConfigMapList{Items: items}, nil - } - return true, nil, nil - }) - - controller.SetInternalKubeClientSet(mockClient) - - return controller, nil -} - -func newTestServer(f func(w http.ResponseWriter, r *http.Request)) (*httptest.Server, error) { - server := httptest.NewUnstartedServer(http.HandlerFunc(f)) - server.Start() - return server, nil -} - -func makeAdmissionControlConfigFile(kubeConfigFile string) (string, error) { - tempfile, err := ioutil.TempFile("", "") - if err != nil { - return "", err - } - - p := tempfile.Name() - - configFileTmpl := ` -kubeconfig: {{ .KubeConfigFile }} -retryBackoff: {{ .RetryBackoff }} -` - type configFileTemplateInput struct { - KubeConfigFile string - RetryBackoff int - } - - input := configFileTemplateInput{ - KubeConfigFile: kubeConfigFile, - RetryBackoff: 1, - } - - tmpl, err := template.New("scheduling-policy-config").Parse(configFileTmpl) - if err != nil { - return "", err - } - - if err := tmpl.Execute(tempfile, input); err != nil { - return "", err - } - - return p, nil -} - -func makeKubeConfigFile(baseURL, path string) (string, error) { - tempfile, err := ioutil.TempFile("", "") - if err != nil { - return "", err - } - - p := tempfile.Name() - - kubeConfigTmpl := ` -clusters: - - name: test - cluster: - server: {{ .BaseURL }}{{ .Path }} -users: - - name: alice - user: - token: deadbeef -contexts: - - name: default - context: - cluster: test - user: alice -current-context: default` - - type kubeConfigTemplateInput struct { - BaseURL string - Path string - } - - input := kubeConfigTemplateInput{ - BaseURL: baseURL, - Path: path, - } - - tmpl, err := template.New("kubeconfig").Parse(kubeConfigTmpl) - if err != nil { - return "", err - } - - if err := tmpl.Execute(tempfile, input); err != nil { - return "", err - } - - return p, nil -} - -func makeAdmissionRecord(obj *extensionsv1.ReplicaSet) admission.Attributes { - return admission.NewAttributesRecord(obj, nil, obj.GroupVersionKind(), obj.Namespace, obj.Name, api.Resource("replicasets").WithVersion("version"), "", admission.Create, &user.DefaultInfo{}) -} - -func makeReplicaSet() *extensionsv1.ReplicaSet { - return &extensionsv1.ReplicaSet{ - TypeMeta: metav1.TypeMeta{ - Kind: "ReplicaSet", - APIVersion: "extensions/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "myapp", - }, - Spec: extensionsv1.ReplicaSetSpec{}, - } -} diff --git a/federation/plugin/pkg/admission/schedulingpolicy/merge.go b/federation/plugin/pkg/admission/schedulingpolicy/merge.go deleted file mode 100644 index 69c06e80b65..00000000000 --- a/federation/plugin/pkg/admission/schedulingpolicy/merge.go +++ /dev/null @@ -1,45 +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 schedulingpolicy - -import ( - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/runtime" -) - -// mergeAnnotations updates obj so that the provided annotations supersede the -// existing annotations. -func mergeAnnotations(obj runtime.Object, annotations map[string]string) error { - if len(annotations) == 0 { - return nil - } - - accessor, err := meta.Accessor(obj) - if err != nil { - return err - } - - orig := accessor.GetAnnotations() - for k := range orig { - if _, ok := annotations[k]; !ok { - annotations[k] = orig[k] - } - } - - accessor.SetAnnotations(annotations) - return nil -} diff --git a/federation/plugin/pkg/admission/schedulingpolicy/merge_test.go b/federation/plugin/pkg/admission/schedulingpolicy/merge_test.go deleted file mode 100644 index 55493978b5a..00000000000 --- a/federation/plugin/pkg/admission/schedulingpolicy/merge_test.go +++ /dev/null @@ -1,66 +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 schedulingpolicy - -import ( - "encoding/json" - "reflect" - "testing" - - "k8s.io/api/core/v1" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func TestMergeAnnotations(t *testing.T) { - tests := []struct { - note string - input *v1.Pod - annotations string - expected string - }{ - {"nil annotations", &v1.Pod{}, `{"foo": "bar"}`, `{"foo": "bar"}`}, - {"empty annotations", &v1.Pod{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{}}}, `{"foo": "bar"}`, `{"foo": "bar"}`}, - {"existing annotation", &v1.Pod{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"foo": "baz"}}}, `{"foo": "bar"}`, `{"foo": "bar"}`}, - {"different annotation", &v1.Pod{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"baz": "qux"}}}, `{"foo": "bar"}`, `{"baz": "qux", "foo": "bar"}`}, - } - - for _, tc := range tests { - - annotations := map[string]string{} - - if err := json.Unmarshal([]byte(tc.annotations), &annotations); err != nil { - panic(err) - } - - expected := map[string]string{} - - if err := json.Unmarshal([]byte(tc.expected), &expected); err != nil { - panic(err) - } - - err := mergeAnnotations(tc.input, annotations) - if err != nil { - t.Errorf("Unexpected error: %v", err) - } - - if !reflect.DeepEqual(tc.input.ObjectMeta.Annotations, expected) { - t.Errorf("%v: Expected annotations to equal %v but got: %v", tc.note, expected, tc.input.ObjectMeta.Annotations) - } - } - -} diff --git a/federation/plugin/pkg/admission/schedulingpolicy/query.go b/federation/plugin/pkg/admission/schedulingpolicy/query.go deleted file mode 100644 index 206f2c404a9..00000000000 --- a/federation/plugin/pkg/admission/schedulingpolicy/query.go +++ /dev/null @@ -1,145 +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 schedulingpolicy - -import ( - "bytes" - "encoding/json" - "fmt" - "strings" - "time" - - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apiserver/pkg/util/webhook" - "k8s.io/client-go/rest" - "k8s.io/kubernetes/pkg/api" -) - -// policyUndefinedError represents an undefined response from the policy -// engine. This typically means the relevant policy has not been loaded into -// the engine. -type policyUndefinedError struct{} - -func (policyUndefinedError) Error() string { - return "policy decision is undefined" -} - -// policyEngineQuery represents a single query against the policy engine. -type policyEngineQuery struct { - client *rest.RESTClient - retryBackoff time.Duration - obj runtime.Object - gvk schema.GroupVersionKind -} - -// newPolicyEngineQuery returns a policyEngineQuery that can be executed. -func newPolicyEngineQuery(client *rest.RESTClient, retryBackoff time.Duration, obj runtime.Object, gvk schema.GroupVersionKind) *policyEngineQuery { - return &policyEngineQuery{ - client: client, - retryBackoff: retryBackoff, - obj: obj, - gvk: gvk, - } -} - -// Do returns the result of the policy engine query. If the policy decision is -// undefined or an unknown error occurs, err is non-nil. Otherwise, result is -// non-nil and contains the result of policy evaluation. -func (query *policyEngineQuery) Do() (decision *policyDecision, err error) { - - bs, err := query.encode() - if err != nil { - return nil, err - } - - var result rest.Result - - err = webhook.WithExponentialBackoff(query.retryBackoff, func() error { - result = query.client.Post(). - Body(bs). - Do() - return result.Error() - }) - - if err != nil { - if errors.IsNotFound(err) { - return nil, policyUndefinedError{} - } - return nil, err - } - - return decodeResult(result) -} - -// encode returns the encoded version of the query's runtime.Object. -func (query *policyEngineQuery) encode() ([]byte, error) { - - var info runtime.SerializerInfo - infos := api.Codecs.SupportedMediaTypes() - - for i := range infos { - if infos[i].MediaType == "application/json" { - info = infos[i] - } - } - - if info.Serializer == nil { - return nil, fmt.Errorf("serialization not supported") - } - - codec := api.Codecs.EncoderForVersion(info.Serializer, query.gvk.GroupVersion()) - - var buf bytes.Buffer - if err := codec.Encode(query.obj, &buf); err != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -// policyDecision represents a response from the policy engine. -type policyDecision struct { - Errors []string `json:"errors,omitempty"` - Annotations map[string]string `json:"annotations,omitempty"` -} - -// Error returns an error if the policy raised an error. -func (d *policyDecision) Error() error { - if len(d.Errors) == 0 { - return nil - } - return fmt.Errorf("reason(s): %v", strings.Join(d.Errors, "; ")) -} - -func decodeResult(result rest.Result) (*policyDecision, error) { - - bs, err := result.Raw() - if err != nil { - return nil, err - } - - buf := bytes.NewBuffer(bs) - var decision policyDecision - - if err := json.NewDecoder(buf).Decode(&decision); err != nil { - return nil, err - } - - return &decision, nil -} diff --git a/federation/registry/cluster/BUILD b/federation/registry/cluster/BUILD deleted file mode 100644 index aad940a50ba..00000000000 --- a/federation/registry/cluster/BUILD +++ /dev/null @@ -1,66 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = [ - "registry.go", - "strategy.go", - ], - importpath = "k8s.io/kubernetes/federation/registry/cluster", - deps = [ - "//federation/apis/federation:go_default_library", - "//federation/apis/federation/validation:go_default_library", - "//pkg/api:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", - "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", - "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", - "//vendor/k8s.io/apiserver/pkg/storage:go_default_library", - "//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["strategy_test.go"], - importpath = "k8s.io/kubernetes/federation/registry/cluster", - library = ":go_default_library", - deps = [ - "//federation/apis/federation:go_default_library", - "//pkg/api:go_default_library", - "//pkg/api/testapi:go_default_library", - "//pkg/api/testing:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/registry/cluster/etcd:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/registry/cluster/etcd/BUILD b/federation/registry/cluster/etcd/BUILD deleted file mode 100644 index 36c78f95e5e..00000000000 --- a/federation/registry/cluster/etcd/BUILD +++ /dev/null @@ -1,53 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = ["etcd.go"], - importpath = "k8s.io/kubernetes/federation/registry/cluster/etcd", - deps = [ - "//federation/apis/federation:go_default_library", - "//federation/registry/cluster:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", - "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", - "//vendor/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library", - "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["etcd_test.go"], - importpath = "k8s.io/kubernetes/federation/registry/cluster/etcd", - library = ":go_default_library", - deps = [ - "//federation/apis/federation:go_default_library", - "//pkg/api:go_default_library", - "//pkg/registry/registrytest:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", - "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/registry/cluster/etcd/etcd.go b/federation/registry/cluster/etcd/etcd.go deleted file mode 100644 index 082de252925..00000000000 --- a/federation/registry/cluster/etcd/etcd.go +++ /dev/null @@ -1,68 +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 etcd - -import ( - "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" - "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/federation/apis/federation" - "k8s.io/kubernetes/federation/registry/cluster" -) - -type REST struct { - *genericregistry.Store -} - -type StatusREST struct { - store *genericregistry.Store -} - -func (r *StatusREST) New() runtime.Object { - return &federation.Cluster{} -} - -// Update alters the status subset of an object. -func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - return r.store.Update(ctx, name, objInfo) -} - -// NewREST returns a RESTStorage object that will work against clusters. -func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST) { - store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &federation.Cluster{} }, - NewListFunc: func() runtime.Object { return &federation.ClusterList{} }, - PredicateFunc: cluster.MatchCluster, - DefaultQualifiedResource: federation.Resource("clusters"), - - CreateStrategy: cluster.Strategy, - UpdateStrategy: cluster.Strategy, - DeleteStrategy: cluster.Strategy, - ReturnDeletedObject: true, - } - options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: cluster.GetAttrs} - if err := store.CompleteWithOptions(options); err != nil { - panic(err) // TODO: Propagate error up - } - - statusStore := *store - statusStore.UpdateStrategy = cluster.StatusStrategy - - return &REST{store}, &StatusREST{store: &statusStore} -} diff --git a/federation/registry/cluster/etcd/etcd_test.go b/federation/registry/cluster/etcd/etcd_test.go deleted file mode 100644 index f263f3f9c3d..00000000000 --- a/federation/registry/cluster/etcd/etcd_test.go +++ /dev/null @@ -1,146 +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 etcd - -import ( - "testing" - - 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/apiserver/pkg/registry/generic" - etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/federation/apis/federation" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/registry/registrytest" -) - -func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) { - storageConfig, server := registrytest.NewEtcdStorage(t, federation.GroupName) - restOptions := generic.RESTOptions{ - StorageConfig: storageConfig, - Decorator: generic.UndecoratedStorage, - DeleteCollectionWorkers: 1, - ResourcePrefix: "clusters", - } - storage, _ := NewREST(restOptions) - return storage, server -} - -func validNewCluster() *federation.Cluster { - return &federation.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Labels: map[string]string{ - "name": "foo", - }, - }, - Spec: federation.ClusterSpec{ - ServerAddressByClientCIDRs: []federation.ServerAddressByClientCIDR{ - { - ClientCIDR: "0.0.0.0/0", - ServerAddress: "localhost:8888", - }, - }, - }, - Status: federation.ClusterStatus{ - Conditions: []federation.ClusterCondition{ - {Type: federation.ClusterReady, Status: api.ConditionFalse}, - }, - }, - } -} - -func TestCreate(t *testing.T) { - storage, server := newStorage(t) - defer server.Terminate(t) - test := registrytest.New(t, storage.Store).ClusterScope() - cluster := validNewCluster() - cluster.ObjectMeta = metav1.ObjectMeta{GenerateName: "foo"} - test.TestCreate( - cluster, - &federation.Cluster{ - ObjectMeta: metav1.ObjectMeta{Name: "-a123-a_"}, - }, - ) -} - -func TestUpdate(t *testing.T) { - storage, server := newStorage(t) - defer server.Terminate(t) - test := registrytest.New(t, storage.Store).ClusterScope() - test.TestUpdate( - // valid - validNewCluster(), - // updateFunc - func(obj runtime.Object) runtime.Object { - object := obj.(*federation.Cluster) - object.Spec.SecretRef = &api.LocalObjectReference{ - Name: "bar", - } - return object - }, - ) -} - -func TestDelete(t *testing.T) { - storage, server := newStorage(t) - defer server.Terminate(t) - test := registrytest.New(t, storage.Store).ClusterScope().ReturnDeletedObject() - test.TestDelete(validNewCluster()) -} - -func TestGet(t *testing.T) { - storage, server := newStorage(t) - defer server.Terminate(t) - test := registrytest.New(t, storage.Store).ClusterScope() - test.TestGet(validNewCluster()) -} - -func TestList(t *testing.T) { - storage, server := newStorage(t) - defer server.Terminate(t) - test := registrytest.New(t, storage.Store).ClusterScope() - test.TestList(validNewCluster()) -} - -func TestWatch(t *testing.T) { - storage, server := newStorage(t) - defer server.Terminate(t) - test := registrytest.New(t, storage.Store).ClusterScope() - test.TestWatch( - validNewCluster(), - // matching labels - []labels.Set{ - {"name": "foo"}, - }, - // not matching labels - []labels.Set{ - {"name": "bar"}, - {"foo": "bar"}, - }, - // matching fields - []fields.Set{ - {"metadata.name": "foo"}, - }, - // not matching fields - []fields.Set{ - {"metadata.name": "bar"}, - }, - ) -} diff --git a/federation/registry/cluster/registry.go b/federation/registry/cluster/registry.go deleted file mode 100644 index eca1cf7a187..00000000000 --- a/federation/registry/cluster/registry.go +++ /dev/null @@ -1,82 +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 cluster - -import ( - metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/watch" - genericapirequest "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/federation/apis/federation" -) - -// Registry is an interface implemented by things that know how to store Cluster objects. -type Registry interface { - ListClusters(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*federation.ClusterList, error) - WatchCluster(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) - GetCluster(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (*federation.Cluster, error) - CreateCluster(ctx genericapirequest.Context, cluster *federation.Cluster) error - UpdateCluster(ctx genericapirequest.Context, cluster *federation.Cluster) error - DeleteCluster(ctx genericapirequest.Context, name string) error -} - -// storage puts strong typing around storage calls -type storage struct { - rest.StandardStorage -} - -// NewRegistry returns a new Registry interface for the given Storage. Any mismatched -// types will panic. -func NewRegistry(s rest.StandardStorage) Registry { - return &storage{s} -} - -func (s *storage) ListClusters(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*federation.ClusterList, error) { - obj, err := s.List(ctx, options) - if err != nil { - return nil, err - } - return obj.(*federation.ClusterList), nil -} - -func (s *storage) WatchCluster(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) { - return s.Watch(ctx, options) -} - -func (s *storage) GetCluster(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (*federation.Cluster, error) { - obj, err := s.Get(ctx, name, options) - if err != nil { - return nil, err - } - return obj.(*federation.Cluster), nil -} - -func (s *storage) CreateCluster(ctx genericapirequest.Context, cluster *federation.Cluster) error { - _, err := s.Create(ctx, cluster, false) - return err -} - -func (s *storage) UpdateCluster(ctx genericapirequest.Context, cluster *federation.Cluster) error { - _, _, err := s.Update(ctx, cluster.Name, rest.DefaultUpdatedObjectInfo(cluster)) - return err -} - -func (s *storage) DeleteCluster(ctx genericapirequest.Context, name string) error { - _, _, err := s.Delete(ctx, name, nil) - return err -} diff --git a/federation/registry/cluster/strategy.go b/federation/registry/cluster/strategy.go deleted file mode 100644 index c278fd73905..00000000000 --- a/federation/registry/cluster/strategy.go +++ /dev/null @@ -1,121 +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 cluster - -import ( - "fmt" - - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/validation/field" - genericapirequest "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/apiserver/pkg/registry/generic" - apistorage "k8s.io/apiserver/pkg/storage" - "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/federation/apis/federation" - "k8s.io/kubernetes/federation/apis/federation/validation" - "k8s.io/kubernetes/pkg/api" -) - -type clusterStrategy struct { - runtime.ObjectTyper - names.NameGenerator -} - -var Strategy = clusterStrategy{api.Scheme, names.SimpleNameGenerator} - -func (clusterStrategy) NamespaceScoped() bool { - return false -} - -func ClusterToSelectableFields(cluster *federation.Cluster) fields.Set { - return generic.ObjectMetaFieldsSet(&cluster.ObjectMeta, false) -} - -// GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { - cluster, ok := obj.(*federation.Cluster) - if !ok { - return nil, nil, false, fmt.Errorf("given object is not a cluster.") - } - return labels.Set(cluster.ObjectMeta.Labels), ClusterToSelectableFields(cluster), cluster.Initializers != nil, nil -} - -func MatchCluster(label labels.Selector, field fields.Selector) apistorage.SelectionPredicate { - return apistorage.SelectionPredicate{ - Label: label, - Field: field, - GetAttrs: GetAttrs, - } -} - -// PrepareForCreate clears fields that are not allowed to be set by end users on creation. -func (clusterStrategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) { - cluster := obj.(*federation.Cluster) - cluster.Status = federation.ClusterStatus{} -} - -// Validate validates a new cluster. -func (clusterStrategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList { - cluster := obj.(*federation.Cluster) - return validation.ValidateCluster(cluster) -} - -// Canonicalize normalizes the object after validation. -func (clusterStrategy) Canonicalize(obj runtime.Object) { -} - -// AllowCreateOnUpdate is false for cluster. -func (clusterStrategy) AllowCreateOnUpdate() bool { - return false -} - -// PrepareForUpdate clears fields that are not allowed to be set by end users on update. -func (clusterStrategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime.Object) { - cluster := obj.(*federation.Cluster) - oldCluster := old.(*federation.Cluster) - cluster.Status = oldCluster.Status -} - -// ValidateUpdate is the default update validation for an end user. -func (clusterStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList { - return validation.ValidateClusterUpdate(obj.(*federation.Cluster), old.(*federation.Cluster)) -} -func (clusterStrategy) AllowUnconditionalUpdate() bool { - return true -} - -type clusterStatusStrategy struct { - clusterStrategy -} - -var StatusStrategy = clusterStatusStrategy{Strategy} - -func (clusterStatusStrategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) { - _ = obj.(*federation.Cluster) -} -func (clusterStatusStrategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime.Object) { - cluster := obj.(*federation.Cluster) - oldCluster := old.(*federation.Cluster) - cluster.Spec = oldCluster.Spec -} - -// ValidateUpdate is the default update validation for an end user. -func (clusterStatusStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList { - return validation.ValidateClusterStatusUpdate(obj.(*federation.Cluster), old.(*federation.Cluster)) -} diff --git a/federation/registry/cluster/strategy_test.go b/federation/registry/cluster/strategy_test.go deleted file mode 100644 index 8a53ad3f292..00000000000 --- a/federation/registry/cluster/strategy_test.go +++ /dev/null @@ -1,167 +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 cluster - -import ( - "testing" - - "reflect" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/labels" - genericapirequest "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/kubernetes/federation/apis/federation" - "k8s.io/kubernetes/pkg/api" - apitesting "k8s.io/kubernetes/pkg/api/testing" - - // install all api groups for testing - _ "k8s.io/kubernetes/pkg/api/testapi" -) - -func validNewCluster() *federation.Cluster { - return &federation.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - ResourceVersion: "4", - Labels: map[string]string{ - "name": "foo", - }, - }, - Spec: federation.ClusterSpec{ - ServerAddressByClientCIDRs: []federation.ServerAddressByClientCIDR{ - { - ClientCIDR: "0.0.0.0/0", - ServerAddress: "localhost:8888", - }, - }, - }, - Status: federation.ClusterStatus{ - Conditions: []federation.ClusterCondition{ - {Type: federation.ClusterReady, Status: api.ConditionTrue}, - }, - }, - } -} - -func invalidNewCluster() *federation.Cluster { - // Create a cluster with empty ServerAddressByClientCIDRs (which is a required field). - return &federation.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo2", - ResourceVersion: "5", - }, - Status: federation.ClusterStatus{ - Conditions: []federation.ClusterCondition{ - {Type: federation.ClusterReady, Status: api.ConditionFalse}, - }, - }, - } -} - -func TestClusterStrategy(t *testing.T) { - ctx := genericapirequest.NewDefaultContext() - if Strategy.NamespaceScoped() { - t.Errorf("Cluster should not be namespace scoped") - } - if Strategy.AllowCreateOnUpdate() { - t.Errorf("Cluster should not allow create on update") - } - - cluster := validNewCluster() - Strategy.PrepareForCreate(ctx, cluster) - if len(cluster.Status.Conditions) != 0 { - t.Errorf("Cluster should not allow setting conditions on create") - } - errs := Strategy.Validate(ctx, cluster) - if len(errs) != 0 { - t.Errorf("Unexpected error validating %v", errs) - } - - invalidCluster := invalidNewCluster() - Strategy.PrepareForUpdate(ctx, invalidCluster, cluster) - if reflect.DeepEqual(invalidCluster.Spec, cluster.Spec) || - !reflect.DeepEqual(invalidCluster.Status, cluster.Status) { - t.Error("Only spec is expected being changed") - } - errs = Strategy.ValidateUpdate(ctx, invalidCluster, cluster) - if len(errs) == 0 { - t.Errorf("Expected a validation error") - } - if cluster.ResourceVersion != "4" { - t.Errorf("Incoming resource version on update should not be mutated") - } -} - -func TestClusterStatusStrategy(t *testing.T) { - ctx := genericapirequest.NewDefaultContext() - if StatusStrategy.NamespaceScoped() { - t.Errorf("Cluster should not be namespace scoped") - } - if StatusStrategy.AllowCreateOnUpdate() { - t.Errorf("Cluster should not allow create on update") - } - - cluster := validNewCluster() - invalidCluster := invalidNewCluster() - StatusStrategy.PrepareForUpdate(ctx, cluster, invalidCluster) - if !reflect.DeepEqual(invalidCluster.Spec, cluster.Spec) || - reflect.DeepEqual(invalidCluster.Status, cluster.Status) { - t.Logf("== cluster.Spec: %v\n", cluster.Spec) - t.Logf("== cluster.Status: %v\n", cluster.Status) - t.Logf("== invalidCluster.Spec: %v\n", cluster.Spec) - t.Logf("== invalidCluster.Spec: %v\n", cluster.Status) - t.Error("Only spec is expected being changed") - } - errs := Strategy.ValidateUpdate(ctx, invalidCluster, cluster) - if len(errs) == 0 { - t.Errorf("Expected a validation error") - } - if cluster.ResourceVersion != "4" { - t.Errorf("Incoming resource version on update should not be mutated") - } -} - -func TestMatchCluster(t *testing.T) { - testFieldMap := map[bool][]fields.Set{ - true: { - {"metadata.name": "foo"}, - }, - false: { - {"foo": "bar"}, - }, - } - - for expectedResult, fieldSet := range testFieldMap { - for _, field := range fieldSet { - m := MatchCluster(labels.Everything(), field.AsSelector()) - _, matchesSingle := m.MatchesSingle() - if e, a := expectedResult, matchesSingle; e != a { - t.Errorf("%+v: expected %v, got %v", fieldSet, e, a) - } - } - } -} - -func TestSelectableFieldLabelConversions(t *testing.T) { - apitesting.TestSelectableFieldLabelConversionsOfKind(t, - api.Registry.GroupOrDie(federation.GroupName).GroupVersion.String(), - "Cluster", - ClusterToSelectableFields(&federation.Cluster{}), - nil, - ) -} diff --git a/federation/test/e2e/BUILD b/federation/test/e2e/BUILD deleted file mode 100644 index be0be028596..00000000000 --- a/federation/test/e2e/BUILD +++ /dev/null @@ -1,71 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "apiserver.go", - "authn.go", - "crud.go", - "event.go", - "ingress.go", - "job.go", - "namespace.go", - "replicaset.go", - "service.go", - "upgrade.go", - "util.go", - ], - importpath = "k8s.io/kubernetes/federation/test/e2e", - deps = [ - "//federation/apis/federation:go_default_library", - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/client/clientset_generated/federation_clientset:go_default_library", - "//federation/client/clientset_generated/federation_clientset/typed/core/v1:go_default_library", - "//federation/pkg/federatedtypes:go_default_library", - "//federation/pkg/federation-controller/util:go_default_library", - "//federation/test/e2e/framework:go_default_library", - "//federation/test/e2e/upgrades:go_default_library", - "//pkg/cloudprovider:go_default_library", - "//test/e2e/chaosmonkey:go_default_library", - "//test/e2e/common:go_default_library", - "//test/e2e/framework:go_default_library", - "//test/utils/image:go_default_library", - "//vendor/github.com/onsi/ginkgo:go_default_library", - "//vendor/github.com/onsi/gomega:go_default_library", - "//vendor/k8s.io/api/batch/v1:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/rand:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/test/e2e/framework:all-srcs", - "//federation/test/e2e/upgrades:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/test/e2e/OWNERS b/federation/test/e2e/OWNERS deleted file mode 100644 index b60580337e5..00000000000 --- a/federation/test/e2e/OWNERS +++ /dev/null @@ -1,16 +0,0 @@ -reviewers: - - colhom - - csbell - - irfanurrehman - - madhusudancs - - mwielgus - - nikhiljindal - - quinton-hoole - - shashidharatd -approvers: - - csbell - - madhusudancs - - mwielgus - - nikhiljindal - - quinton-hoole - - shashidharatd diff --git a/federation/test/e2e/README.md b/federation/test/e2e/README.md deleted file mode 100644 index ff69219ac5d..00000000000 --- a/federation/test/e2e/README.md +++ /dev/null @@ -1 +0,0 @@ -See [e2e-tests](https://git.k8s.io/community/contributors/devel/e2e-tests.md#federation-e2e-tests) diff --git a/federation/test/e2e/apiserver.go b/federation/test/e2e/apiserver.go deleted file mode 100644 index f46a34de3a3..00000000000 --- a/federation/test/e2e/apiserver.go +++ /dev/null @@ -1,122 +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 e2e - -import ( - "fmt" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - fedframework "k8s.io/kubernetes/federation/test/e2e/framework" - "k8s.io/kubernetes/test/e2e/framework" -) - -// Create/delete cluster api objects -var _ = framework.KubeDescribe("Federation apiserver [Feature:Federation]", func() { - f := fedframework.NewDefaultFederatedFramework("federation-cluster") - testClusterPrefix := "test" - - Describe("Cluster objects [Serial]", func() { - AfterEach(func() { - fedframework.SkipUnlessFederated(f.ClientSet) - - // Delete registered clusters. - // This is if a test failed, it should not affect other tests. - clusterList, err := f.FederationClientset.Federation().Clusters().List(metav1.ListOptions{LabelSelector: "prefix=" + testClusterPrefix}) - Expect(err).NotTo(HaveOccurred()) - for _, cluster := range clusterList.Items { - err := f.FederationClientset.Federation().Clusters().Delete(cluster.Name, &metav1.DeleteOptions{}) - Expect(err).NotTo(HaveOccurred()) - } - }) - - It("should be created and deleted successfully", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - - contexts := f.GetUnderlyingFederatedContexts() - - By(fmt.Sprintf("Creating %d cluster objects", len(contexts))) - for _, context := range contexts { - createClusterObjectOrFail(f, &context, testClusterPrefix) - } - - By(fmt.Sprintf("Checking that %d clusters are ready", len(contexts))) - for _, context := range contexts { - fedframework.ClusterIsReadyOrFail(f, context.Name) - } - framework.Logf("%d clusters are Ready", len(contexts)) - - // Verify that deletion works. - framework.Logf("Deleting %d clusters", len(contexts)) - for _, context := range contexts { - clusterName := testClusterPrefix + context.Name - framework.Logf("Deleting cluster object: %s (%s, secret: %s)", clusterName, context.Cluster.Cluster.Server, context.Name) - err := f.FederationClientset.Federation().Clusters().Delete(clusterName, &metav1.DeleteOptions{}) - framework.ExpectNoError(err, fmt.Sprintf("unexpected error in deleting cluster %s: %+v", clusterName, err)) - framework.Logf("Successfully deleted cluster object: %s (%s, secret: %s)", clusterName, context.Cluster.Cluster.Server, context.Name) - } - - // There should not be any remaining cluster. - framework.Logf("Verifying that zero test clusters remain") - clusterList, err := f.FederationClientset.Federation().Clusters().List(metav1.ListOptions{LabelSelector: "prefix=" + testClusterPrefix}) - Expect(err).NotTo(HaveOccurred()) - if len(clusterList.Items) != 0 { - framework.Failf("there should not have been any remaining clusters. Found: %+v", clusterList) - } - framework.Logf("Verified that zero clusters remain") - }) - }) - - Describe("Admission control [NoCluster]", func() { - AfterEach(func() { - fedframework.SkipUnlessFederated(f.ClientSet) - }) - - It("should not be able to create resources if namespace does not exist", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - - // Creating a service in a non-existing namespace should fail. - svcNamespace := "federation-admission-test-ns" - svcName := "myns" - clientset := f.FederationClientset - framework.Logf("Trying to create service %s in namespace %s, expect to get error", svcName, svcNamespace) - if _, err := clientset.Core().Services(svcNamespace).Create(newService(svcName, svcNamespace)); err == nil { - framework.Failf("Expected to get an error while creating a service in a non-existing namespace") - } - - // Note: We have other tests that verify that we can create resources in existing namespaces, so we dont test it again here. - }) - }) -}) - -func newService(name, namespace string) *v1.Service { - return &v1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: v1.ServiceSpec{ - Ports: []v1.ServicePort{ - { - Port: 80, - }, - }, - }, - } -} diff --git a/federation/test/e2e/authn.go b/federation/test/e2e/authn.go deleted file mode 100644 index bf8f94dbbd2..00000000000 --- a/federation/test/e2e/authn.go +++ /dev/null @@ -1,207 +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 e2e - -import ( - "fmt" - - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - fedframework "k8s.io/kubernetes/federation/test/e2e/framework" - "k8s.io/kubernetes/test/e2e/framework" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -// TODO: These tests should be integration tests rather than e2e tests, when the -// integration test harness is ready. -var _ = framework.KubeDescribe("[Feature:Federation]", func() { - f := fedframework.NewDefaultFederatedFramework("federation-apiserver-authn") - - var _ = Describe("Federation API server authentication [NoCluster]", func() { - BeforeEach(func() { - fedframework.SkipUnlessFederated(f.ClientSet) - }) - - It("should accept cluster resources when the client has certificate authentication credentials", func() { - fcs, err := federationClientSetWithCert() - framework.ExpectNoError(err) - - nsName := f.FederationNamespace.Name - svc := createServiceOrFail(fcs, nsName, FederatedServiceName) - deleteServiceOrFail(f.FederationClientset, nsName, svc.Name, nil) - }) - - It("should accept cluster resources when the client has HTTP Basic authentication credentials", func() { - fcs, err := federationClientSetWithBasicAuth(true /* valid */) - framework.ExpectNoError(err) - - nsName := f.FederationNamespace.Name - svc, err := createService(fcs, nsName, FederatedServiceName) - Expect(err).NotTo(HaveOccurred()) - deleteServiceOrFail(fcs, nsName, svc.Name, nil) - }) - - It("should accept cluster resources when the client has token authentication credentials", func() { - fcs, err := federationClientSetWithToken(true /* valid */) - framework.ExpectNoError(err) - - nsName := f.FederationNamespace.Name - svc, err := createService(fcs, nsName, FederatedServiceName) - Expect(err).NotTo(HaveOccurred()) - deleteServiceOrFail(fcs, nsName, svc.Name, nil) - }) - - It("should not accept cluster resources when the client has no authentication credentials", func() { - fcs, err := unauthenticatedFederationClientSet() - framework.ExpectNoError(err) - - nsName := f.FederationNamespace.Name - _, err = createService(fcs, nsName, FederatedServiceName) - Expect(errors.IsUnauthorized(err)).To(BeTrue()) - }) - - // TODO: Add a test for invalid certificate credentials. The certificate is validated for - // correct format, so it cannot contain random noise. - - It("should not accept cluster resources when the client has invalid HTTP Basic authentication credentials", func() { - fcs, err := federationClientSetWithBasicAuth(false /* invalid */) - framework.ExpectNoError(err) - - nsName := f.FederationNamespace.Name - _, err = createService(fcs, nsName, FederatedServiceName) - Expect(errors.IsUnauthorized(err)).To(BeTrue()) - }) - - It("should not accept cluster resources when the client has invalid token authentication credentials", func() { - fcs, err := federationClientSetWithToken(false /* invalid */) - framework.ExpectNoError(err) - - nsName := f.FederationNamespace.Name - _, err = createService(fcs, nsName, FederatedServiceName) - Expect(errors.IsUnauthorized(err)).To(BeTrue()) - }) - - }) -}) - -// unauthenticatedFederationClientSet returns a Federation Clientset configured with -// no authentication credentials. -func unauthenticatedFederationClientSet() (*federation_clientset.Clientset, error) { - config, err := fedframework.LoadFederatedConfig(&clientcmd.ConfigOverrides{}) - if err != nil { - return nil, err - } - config.Insecure = true - config.CAData = []byte{} - config.CertData = []byte{} - config.KeyData = []byte{} - config.BearerToken = "" - - c, err := federation_clientset.NewForConfig(config) - if err != nil { - return nil, fmt.Errorf("error creating federation clientset: %v", err) - } - - return c, nil -} - -// federationClientSetWithCert returns a Federation Clientset configured with -// certificate authentication credentials. -func federationClientSetWithCert() (*federation_clientset.Clientset, error) { - config, err := fedframework.LoadFederatedConfig(&clientcmd.ConfigOverrides{}) - if err != nil { - return nil, err - } - - config.BearerToken = "" - - c, err := federation_clientset.NewForConfig(config) - if err != nil { - return nil, fmt.Errorf("error creating federation clientset: %v", err) - } - - return c, nil -} - -// federationClientSetWithBasicAuth returns a Federation Clientset configured with -// HTTP Basic authentication credentials. -func federationClientSetWithBasicAuth(valid bool) (*federation_clientset.Clientset, error) { - config, err := fedframework.LoadFederatedConfig(&clientcmd.ConfigOverrides{}) - if err != nil { - return nil, err - } - - config.Insecure = true - config.CAData = []byte{} - config.CertData = []byte{} - config.KeyData = []byte{} - config.BearerToken = "" - - if !valid { - config.Username = "" - config.Password = "" - } else { - // This is a hacky approach to getting the basic auth credentials, but since - // the token and the username/password cannot live in the same AuthInfo object, - // and because we do not want to store basic auth credentials with token and - // certificate credentials for security reasons, we must dig it out by hand. - c, err := framework.RestclientConfig(framework.TestContext.FederatedKubeContext) - if err != nil { - return nil, err - } - if authInfo, ok := c.AuthInfos[fmt.Sprintf("%s-basic-auth", framework.TestContext.FederatedKubeContext)]; ok { - config.Username = authInfo.Username - config.Password = authInfo.Password - } - } - - c, err := federation_clientset.NewForConfig(config) - if err != nil { - return nil, fmt.Errorf("error creating federation clientset: %v", err) - } - - return c, nil -} - -// federationClientSetWithToken returns a Federation Clientset configured with -// token authentication credentials. -func federationClientSetWithToken(valid bool) (*federation_clientset.Clientset, error) { - config, err := fedframework.LoadFederatedConfig(&clientcmd.ConfigOverrides{}) - if err != nil { - return nil, err - } - config.Insecure = true - config.CAData = []byte{} - config.CertData = []byte{} - config.KeyData = []byte{} - config.Username = "" - config.Password = "" - - if !valid { - config.BearerToken = "invalid" - } - - c, err := federation_clientset.NewForConfig(config) - if err != nil { - return nil, fmt.Errorf("error creating federation clientset: %v", err) - } - - return c, nil -} diff --git a/federation/test/e2e/crud.go b/federation/test/e2e/crud.go deleted file mode 100644 index a7b3a94b4d0..00000000000 --- a/federation/test/e2e/crud.go +++ /dev/null @@ -1,55 +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 e2e - -import ( - "fmt" - - . "github.com/onsi/ginkgo" - - kubeclientset "k8s.io/client-go/kubernetes" - "k8s.io/kubernetes/federation/pkg/federatedtypes" - fedframework "k8s.io/kubernetes/federation/test/e2e/framework" - "k8s.io/kubernetes/test/e2e/framework" -) - -var _ = framework.KubeDescribe("Federated types [Feature:Federation][Experimental] ", func() { - var clusterClients []kubeclientset.Interface - - f := fedframework.NewDefaultFederatedFramework("federated-types") - - fedTypes := federatedtypes.FederatedTypes() - for name := range fedTypes { - fedType := fedTypes[name] - Describe(fmt.Sprintf("Federated %q resources", name), func() { - It("should be created, read, updated and deleted successfully", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - - // Load clients only if not skipping to avoid doing - // unnecessary work. Assume clients can be shared - // across tests. - if clusterClients == nil { - clusterClients = f.GetClusterClients() - } - adapter := fedType.AdapterFactory(f.FederationClientset, f.FederationConfig, nil) - crudTester := fedframework.NewFederatedTypeCRUDTester(adapter, clusterClients) - obj := adapter.NewTestObject(f.FederationNamespace.Name) - crudTester.CheckLifecycle(obj) - }) - }) - } -}) diff --git a/federation/test/e2e/event.go b/federation/test/e2e/event.go deleted file mode 100644 index 00328c5b716..00000000000 --- a/federation/test/e2e/event.go +++ /dev/null @@ -1,99 +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 e2e - -import ( - "fmt" - - . "github.com/onsi/ginkgo" - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - fedframework "k8s.io/kubernetes/federation/test/e2e/framework" - "k8s.io/kubernetes/test/e2e/framework" - - . "github.com/onsi/gomega" -) - -const ( - FederationEventName = "federation-event" -) - -// Create/delete event api objects. -var _ = framework.KubeDescribe("Federation events [Feature:Federation]", func() { - f := fedframework.NewDefaultFederatedFramework("federation-event") - - Describe("Event objects [NoCluster]", func() { - AfterEach(func() { - fedframework.SkipUnlessFederated(f.ClientSet) - - nsName := f.FederationNamespace.Name - // Delete registered events. - eventList, err := f.FederationClientset.Core().Events(nsName).List(metav1.ListOptions{}) - Expect(err).NotTo(HaveOccurred()) - for _, event := range eventList.Items { - err := f.FederationClientset.Core().Events(nsName).Delete(event.Name, &metav1.DeleteOptions{}) - Expect(err).NotTo(HaveOccurred()) - } - }) - - It("should be created and deleted successfully", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - - nsName := f.FederationNamespace.Name - event := createEventOrFail(f.FederationClientset, nsName) - By(fmt.Sprintf("Creation of event %q in namespace %q succeeded. Deleting event.", event.Name, nsName)) - // Cleanup - err := f.FederationClientset.Core().Events(nsName).Delete(event.Name, &metav1.DeleteOptions{}) - framework.ExpectNoError(err, "Error deleting event %q in namespace %q", event.Name, event.Namespace) - By(fmt.Sprintf("Deletion of event %q in namespace %q succeeded.", event.Name, nsName)) - }) - - }) -}) - -func createEventOrFail(clientset *federation_clientset.Clientset, namespace string) *v1.Event { - if clientset == nil || len(namespace) == 0 { - Fail(fmt.Sprintf("Internal error: invalid parameters passed to createEventOrFail: clientset: %v, namespace: %v", clientset, namespace)) - } - By(fmt.Sprintf("Creating federated event %q in namespace %q", FederationEventName, namespace)) - - event := &v1.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: FederationEventName, - Namespace: namespace, - }, - InvolvedObject: v1.ObjectReference{ - Kind: "Pod", - Name: "pod-name", - Namespace: namespace, - UID: "C934D34AFB20242", - APIVersion: "version", - }, - Source: v1.EventSource{ - Component: "kubelet", - Host: "kublet.node1", - }, - Count: 1, - Type: v1.EventTypeNormal, - } - - _, err := clientset.Core().Events(namespace).Create(event) - framework.ExpectNoError(err, "Creating event %q in namespace %q", event.Name, namespace) - By(fmt.Sprintf("Successfully created federated event %q in namespace %q", FederationEventName, namespace)) - return event -} diff --git a/federation/test/e2e/framework/BUILD b/federation/test/e2e/framework/BUILD deleted file mode 100644 index 4e28fd8b968..00000000000 --- a/federation/test/e2e/framework/BUILD +++ /dev/null @@ -1,51 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "cluster.go", - "crudtester.go", - "framework.go", - "util.go", - ], - importpath = "k8s.io/kubernetes/federation/test/e2e/framework", - deps = [ - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/client/clientset_generated/federation_clientset:go_default_library", - "//federation/pkg/federatedtypes:go_default_library", - "//federation/pkg/federatedtypes/crudtester:go_default_library", - "//federation/pkg/federation-controller/util:go_default_library", - "//pkg/api:go_default_library", - "//pkg/api/validation:go_default_library", - "//test/e2e/framework:go_default_library", - "//vendor/github.com/onsi/ginkgo:go_default_library", - "//vendor/github.com/onsi/gomega:go_default_library", - "//vendor/gopkg.in/yaml.v2:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/test/e2e/framework/cluster.go b/federation/test/e2e/framework/cluster.go deleted file mode 100644 index 8d879a2c247..00000000000 --- a/federation/test/e2e/framework/cluster.go +++ /dev/null @@ -1,240 +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 framework - -import ( - "fmt" - "strings" - "time" - - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/wait" - kubeclientset "k8s.io/client-go/kubernetes" - restclient "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" - "k8s.io/kubernetes/federation/pkg/federation-controller/util" - "k8s.io/kubernetes/test/e2e/framework" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -const federatedClustersWaitTimeout = 1 * time.Minute - -// ClusterSlice is a slice of clusters -type ClusterSlice []*Cluster - -// Cluster keeps track of the name and client of a cluster in the federation -type Cluster struct { - Name string - *kubeclientset.Clientset -} - -// registeredClustersFromConfig configures clientsets for registered clusters from the e2e kubeconfig -func registeredClustersFromConfig(f *Framework) ClusterSlice { - contexts := f.GetUnderlyingFederatedContexts() - - By("Obtaining a list of all the clusters") - clusterList := waitForAllRegisteredClusters(f, len(contexts)) - - framework.Logf("Checking that %d clusters are Ready", len(contexts)) - for _, context := range contexts { - ClusterIsReadyOrFail(f, context.Name) - - } - framework.Logf("%d clusters are Ready", len(contexts)) - - clusters := ClusterSlice{} - for i, c := range clusterList.Items { - framework.Logf("Creating a clientset for the cluster %s", c.Name) - Expect(framework.TestContext.KubeConfig).ToNot(Equal(""), "KubeConfig must be specified to load clusters' client config") - config := restConfigFromContext(c, i) - clusters = append(clusters, &Cluster{ - Name: c.Name, - Clientset: clientsetFromConfig(f, config, c.Spec.ServerAddressByClientCIDRs[0].ServerAddress), - }) - - } - waitForNamespaceInFederatedClusters(clusters, f.FederationNamespace.Name) - return clusters -} - -// waitForAllRegisteredClusters waits for all clusters defined in e2e context to be created -// return ClusterList until the listed cluster items equals clusterCount -func waitForAllRegisteredClusters(f *Framework, clusterCount int) *federationapi.ClusterList { - var clusterList *federationapi.ClusterList - if err := wait.PollImmediate(framework.Poll, federatedClustersWaitTimeout, func() (bool, error) { - var err error - clusterList, err = f.FederationClientset.Federation().Clusters().List(metav1.ListOptions{}) - if err != nil { - return false, err - } - framework.Logf("%d clusters registered, waiting for %d", len(clusterList.Items), clusterCount) - if len(clusterList.Items) == clusterCount { - return true, nil - } - return false, nil - }); err != nil { - framework.Failf("Failed to list registered clusters: %+v", err) - } - return clusterList -} - -func restConfigFromContext(c federationapi.Cluster, i int) *restclient.Config { - kubecfg, err := clientcmd.LoadFromFile(framework.TestContext.KubeConfig) - framework.ExpectNoError(err, "error loading KubeConfig: %v", err) - - ccfg := clientcmd.NewNonInteractiveClientConfig(*kubecfg, c.Name, &clientcmd.ConfigOverrides{}, clientcmd.NewDefaultClientConfigLoadingRules()) - cfg, err := ccfg.ClientConfig() - framework.ExpectNoError(err, "Error creating client config in cluster #%d (%q)", i, c.Name) - return cfg -} - -func clientsetFromConfig(f *Framework, cfg *restclient.Config, host string) *kubeclientset.Clientset { - cfg.Host = host - cfg.QPS = f.Framework.Options.ClientQPS - cfg.Burst = f.Framework.Options.ClientBurst - return kubeclientset.NewForConfigOrDie(restclient.AddUserAgent(cfg, "federation-e2e")) -} - -// waitForNamespaceInFederatedClusters waits for the federated namespace to be created in federated clusters -func waitForNamespaceInFederatedClusters(clusters ClusterSlice, nsName string) { - for _, c := range clusters { - name := c.Name - By(fmt.Sprintf("Waiting for namespace %q to be created in cluster %q", nsName, name)) - err := wait.PollImmediate(framework.Poll, FederatedDefaultTestTimeout, func() (bool, error) { - _, err := c.Clientset.CoreV1().Namespaces().Get(nsName, metav1.GetOptions{}) - if errors.IsNotFound(err) { - return false, nil - } else if err != nil { - framework.Logf("An error occurred waiting for namespace %q to be created in cluster %q: %v", nsName, name, err) - return false, nil - } - By(fmt.Sprintf("Namespace %q exists in cluster %q", nsName, name)) - return true, nil - }) - framework.ExpectNoError(err, "Failed to verify federated namespace %q creation in cluster %q", nsName, name) - } -} - -// ClusterIsReadyOrFail checks whether the named cluster is ready -func ClusterIsReadyOrFail(f *Framework, clusterName string) { - By(fmt.Sprintf("Checking readiness of cluster %q", clusterName)) - err := wait.PollImmediate(framework.Poll, FederatedDefaultTestTimeout, func() (bool, error) { - c, err := f.FederationClientset.Federation().Clusters().Get(clusterName, metav1.GetOptions{}) - if err != nil { - return false, err - } - for _, condition := range c.Status.Conditions { - if condition.Type == federationapi.ClusterReady && condition.Status == v1.ConditionTrue { - return true, nil - } - } - return false, nil - }) - framework.ExpectNoError(err, fmt.Sprintf("Unexpected error in verifying if cluster %q is ready: %+v", clusterName, err)) - framework.Logf("Cluster %s is Ready", clusterName) -} - -// Cache the cluster config to avoid having to retrieve it for each test -type clusterConfig struct { - name string - host string - config []byte -} - -var cachedClusterConfigs []*clusterConfig - -// registeredClustersFromSecrets configures clientsets for cluster access from secrets in the host cluster -func registeredClustersFromSecrets(f *Framework) ClusterSlice { - if cachedClusterConfigs == nil { - cachedClusterConfigs = clusterConfigFromSecrets(f) - } - - clusters := ClusterSlice{} - for _, clusterConf := range cachedClusterConfigs { - restConfig := restConfigForCluster(clusterConf) - clientset := clientsetFromConfig(f, restConfig, clusterConf.host) - clusters = append(clusters, &Cluster{ - Name: clusterConf.name, - Clientset: clientset, - }) - } - - waitForNamespaceInFederatedClusters(clusters, f.FederationNamespace.Name) - - return clusters -} - -// clusterConfigFromSecrets retrieves cluster configuration from -// secrets in the host cluster -func clusterConfigFromSecrets(f *Framework) []*clusterConfig { - By("Obtaining a list of registered clusters") - clusterList, err := f.FederationClientset.Federation().Clusters().List(metav1.ListOptions{}) - framework.ExpectNoError(err, fmt.Sprintf("Error retrieving list of federated clusters: %+v", err)) - if len(clusterList.Items) == 0 { - framework.Failf("No registered clusters found") - } - - clusterConfigs := []*clusterConfig{} - for _, c := range clusterList.Items { - ClusterIsReadyOrFail(f, c.Name) - config := clusterConfigFromSecret(f, c.Name, c.Spec.SecretRef.Name) - clusterConfigs = append(clusterConfigs, &clusterConfig{ - name: c.Name, - host: c.Spec.ServerAddressByClientCIDRs[0].ServerAddress, - config: config, - }) - } - - return clusterConfigs -} - -// clusterConfigFromSecret retrieves configuration for a accessing a -// cluster from a secret in the host cluster -func clusterConfigFromSecret(f *Framework, clusterName string, secretName string) []byte { - By(fmt.Sprintf("Loading configuration for cluster %q", clusterName)) - namespace := framework.FederationSystemNamespace() - secret, err := f.Framework.ClientSet.Core().Secrets(namespace).Get(secretName, metav1.GetOptions{}) - framework.ExpectNoError(err, fmt.Sprintf("Error loading config secret \"%s/%s\" for cluster %q: %+v", namespace, secretName, clusterName, err)) - - config, ok := secret.Data[util.KubeconfigSecretDataKey] - if !ok || len(config) == 0 { - framework.Failf("Secret \"%s/%s\" for cluster %q has no value for key %q", namespace, secretName, clusterName, util.KubeconfigSecretDataKey) - } - - return config -} - -// restConfigForCluster creates a rest client config for the given cluster config -func restConfigForCluster(clusterConf *clusterConfig) *restclient.Config { - cfg, err := clientcmd.Load(clusterConf.config) - framework.ExpectNoError(err, fmt.Sprintf("Error loading configuration for cluster %q: %+v", clusterConf.name, err)) - - restConfig, err := clientcmd.NewDefaultClientConfig(*cfg, &clientcmd.ConfigOverrides{}).ClientConfig() - framework.ExpectNoError(err, fmt.Sprintf("Error creating client for cluster %q: %+v", clusterConf.name, err)) - return restConfig -} - -func GetZoneFromClusterName(clusterName string) string { - // Ref: https://github.com/kubernetes/kubernetes/blob/master/cluster/kube-util.sh#L55 - prefix := "federation-e2e-" + framework.TestContext.Provider + "-" - return strings.TrimPrefix(clusterName, prefix) -} diff --git a/federation/test/e2e/framework/crudtester.go b/federation/test/e2e/framework/crudtester.go deleted file mode 100644 index 0512f6354c4..00000000000 --- a/federation/test/e2e/framework/crudtester.go +++ /dev/null @@ -1,44 +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 framework - -import ( - . "github.com/onsi/ginkgo" - kubeclientset "k8s.io/client-go/kubernetes" - "k8s.io/kubernetes/federation/pkg/federatedtypes" - "k8s.io/kubernetes/federation/pkg/federatedtypes/crudtester" - "k8s.io/kubernetes/test/e2e/framework" -) - -// Adapt the methods to log/fail in e2e to the interface expected by CRUDHelper -type e2eTestLogger struct{} - -func (e2eTestLogger) Fatal(msg string) { - Fail(msg) -} - -func (e2eTestLogger) Fatalf(format string, args ...interface{}) { - framework.Failf(format, args...) -} - -func (e2eTestLogger) Logf(format string, args ...interface{}) { - framework.Logf(format, args...) -} - -func NewFederatedTypeCRUDTester(adapter federatedtypes.FederatedTypeAdapter, clusterClients []kubeclientset.Interface) *crudtester.FederatedTypeCRUDTester { - return crudtester.NewFederatedTypeCRUDTester(&e2eTestLogger{}, adapter, clusterClients, framework.Poll, FederatedDefaultTestTimeout) -} diff --git a/federation/test/e2e/framework/framework.go b/federation/test/e2e/framework/framework.go deleted file mode 100644 index 1ddc3a7a55e..00000000000 --- a/federation/test/e2e/framework/framework.go +++ /dev/null @@ -1,256 +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 framework - -import ( - "fmt" - "io/ioutil" - "strings" - "time" - - "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/wait" - kubeclientset "k8s.io/client-go/kubernetes" - restclient "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - "k8s.io/kubernetes/test/e2e/framework" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - yaml "gopkg.in/yaml.v2" -) - -// Framework extends e2e Framework and adds federation specific fields -type Framework struct { - *framework.Framework - - // To make sure that this framework cleans up after itself, no matter what, - // we install a Cleanup action before each test and clear it after. If we - // should abort, the AfterSuite hook should run all Cleanup actions. - cleanupHandle framework.CleanupActionHandle - - FederationConfig *restclient.Config - - FederationClientset *federation_clientset.Clientset - - FederationNamespace *v1.Namespace -} - -func NewDefaultFederatedFramework(baseName string) *Framework { - f := &Framework{} - - // Register the federation cleanup before initializing the default - // e2e framework to ensure it gets called before the default - // framework's cleanup. - AfterEach(f.FederationAfterEach) - - f.Framework = framework.NewDefaultFramework(baseName) - f.Framework.SkipNamespaceCreation = true - - // Register the federation setup after initializing the default - // e2e framework to ensure it gets called after the default - // framework's setup. - BeforeEach(f.FederationBeforeEach) - - return f -} - -// FederationBeforeEach checks for federation apiserver is ready and makes a namespace. -func (f *Framework) FederationBeforeEach() { - // The fact that we need this feels like a bug in ginkgo. - // https://github.com/onsi/ginkgo/issues/222 - f.cleanupHandle = framework.AddCleanupAction(f.FederationAfterEach) - - if f.FederationConfig == nil { - By("Reading the federation configuration") - var err error - f.FederationConfig, err = LoadFederatedConfig(&clientcmd.ConfigOverrides{}) - Expect(err).NotTo(HaveOccurred()) - } - if f.FederationClientset == nil { - By("Creating a release 1.5 federation Clientset") - var err error - f.FederationClientset, err = LoadFederationClientset(f.FederationConfig) - Expect(err).NotTo(HaveOccurred()) - } - By("Waiting for federation-apiserver to be ready") - err := WaitForFederationApiserverReady(f.FederationClientset) - Expect(err).NotTo(HaveOccurred()) - By("federation-apiserver is ready") - - By("Creating a federation namespace") - ns, err := f.createFederationNamespace(f.BaseName) - Expect(err).NotTo(HaveOccurred()) - f.FederationNamespace = ns - By(fmt.Sprintf("Created federation namespace %s", ns.Name)) -} - -func (f *Framework) deleteFederationNs() { - ns := f.FederationNamespace - By(fmt.Sprintf("Destroying federation namespace %q for this suite.", ns.Name)) - timeout := 5 * time.Minute - if f.NamespaceDeletionTimeout != 0 { - timeout = f.NamespaceDeletionTimeout - } - - clientset := f.FederationClientset - // First delete the namespace from federation apiserver. - // Also delete the corresponding namespaces from underlying clusters. - orphanDependents := false - if err := clientset.Core().Namespaces().Delete(ns.Name, &metav1.DeleteOptions{OrphanDependents: &orphanDependents}); err != nil { - framework.Failf("Error while deleting federation namespace %s: %s", ns.Name, err) - } - // Verify that it got deleted. - err := wait.PollImmediate(5*time.Second, timeout, func() (bool, error) { - if _, err := clientset.Core().Namespaces().Get(ns.Name, metav1.GetOptions{}); err != nil { - if apierrors.IsNotFound(err) { - return true, nil - } - framework.Logf("Error while waiting for namespace to be terminated: %v", err) - return false, nil - } - return false, nil - }) - if err != nil { - if !apierrors.IsNotFound(err) { - framework.Failf("Couldn't delete ns %q: %s", ns.Name, err) - } else { - framework.Logf("Namespace %v was already deleted", ns.Name) - } - } -} - -// FederationAfterEach deletes the namespace, after reading its events. -func (f *Framework) FederationAfterEach() { - framework.RemoveCleanupAction(f.cleanupHandle) - - // DeleteNamespace at the very end in defer, to avoid any - // expectation failures preventing deleting the namespace. - defer func() { - // Whether to delete namespace is determined by 3 factors: delete-namespace flag, delete-namespace-on-failure flag and the test result - // if delete-namespace set to false, namespace will always be preserved. - // if delete-namespace is true and delete-namespace-on-failure is false, namespace will be preserved if test failed. - if framework.TestContext.DeleteNamespace && (framework.TestContext.DeleteNamespaceOnFailure || !CurrentGinkgoTestDescription().Failed) { - // Delete the federation namespace. - f.deleteFederationNs() - } - - // Paranoia-- prevent reuse! - f.FederationNamespace = nil - - if f.FederationClientset == nil { - framework.Logf("Warning: framework is marked federated, but has no federation 1.5 clientset") - return - } - }() - - // Print events if the test failed. - if CurrentGinkgoTestDescription().Failed && framework.TestContext.DumpLogsOnFailure { - // Dump federation events in federation namespace. - framework.DumpEventsInNamespace(func(opts metav1.ListOptions, ns string) (*v1.EventList, error) { - return f.FederationClientset.Core().Events(ns).List(opts) - }, f.FederationNamespace.Name) - } -} - -func (f *Framework) createFederationNamespace(baseName string) (*v1.Namespace, error) { - clientset := f.FederationClientset - namespaceObj := &v1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: fmt.Sprintf("e2e-tests-%v-", baseName), - }, - } - // Be robust about making the namespace creation call. - var got *v1.Namespace - if err := wait.PollImmediate(framework.Poll, framework.SingleCallTimeout, func() (bool, error) { - var err error - got, err = clientset.Core().Namespaces().Create(namespaceObj) - if err != nil { - framework.Logf("Unexpected error while creating namespace: %v", err) - return false, nil - } - return true, nil - }); err != nil { - return nil, err - } - return got, nil -} - -type E2EContext struct { - // Raw context name, - RawName string `yaml:"rawName"` - // A valid dns subdomain which can be used as the name of kubernetes resources. - Name string `yaml:"name"` - Cluster *framework.KubeCluster `yaml:"cluster"` - User *framework.KubeUser `yaml:"user"` -} - -func (f *Framework) GetUnderlyingFederatedContexts() []E2EContext { - kubeconfig := framework.KubeConfig{} - configBytes, err := ioutil.ReadFile(framework.TestContext.KubeConfig) - framework.ExpectNoError(err) - err = yaml.Unmarshal(configBytes, &kubeconfig) - framework.ExpectNoError(err) - - e2eContexts := []E2EContext{} - for _, context := range kubeconfig.Contexts { - if strings.HasPrefix(context.Name, "federation") && context.Name != framework.TestContext.FederatedKubeContext { - user := kubeconfig.FindUser(context.Context.User) - if user == nil { - framework.Failf("Could not find user for context %+v", context) - } - - cluster := kubeconfig.FindCluster(context.Context.Cluster) - if cluster == nil { - framework.Failf("Could not find cluster for context %+v", context) - } - - dnsSubdomainName, err := GetValidDNSSubdomainName(context.Name) - if err != nil { - framework.Failf("Could not convert context name %s to a valid dns subdomain name, error: %s", context.Name, err) - } - e2eContexts = append(e2eContexts, E2EContext{ - RawName: context.Name, - Name: dnsSubdomainName, - Cluster: cluster, - User: user, - }) - } - } - - return e2eContexts -} - -func (f *Framework) GetRegisteredClusters() ClusterSlice { - if framework.TestContext.FederationConfigFromCluster { - return registeredClustersFromSecrets(f) - } else { - return registeredClustersFromConfig(f) - } -} - -func (f *Framework) GetClusterClients() []kubeclientset.Interface { - clusters := f.GetRegisteredClusters() - var clusterClients []kubeclientset.Interface - for _, c := range clusters { - clusterClients = append(clusterClients, c.Clientset) - } - return clusterClients -} diff --git a/federation/test/e2e/framework/util.go b/federation/test/e2e/framework/util.go deleted file mode 100644 index d9851a44c1d..00000000000 --- a/federation/test/e2e/framework/util.go +++ /dev/null @@ -1,150 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package framework - -import ( - "fmt" - "os" - "path" - "regexp" - "strings" - "time" - - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - validationutil "k8s.io/apimachinery/pkg/util/validation" - "k8s.io/apimachinery/pkg/util/wait" - clientset "k8s.io/client-go/kubernetes" - restclient "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/validation" - "k8s.io/kubernetes/test/e2e/framework" -) - -const FederatedDefaultTestTimeout = 5 * time.Minute - -// Detects whether the federation namespace exists in the underlying cluster -func SkipUnlessFederated(c clientset.Interface) { - federationNS := framework.FederationSystemNamespace() - - _, err := c.Core().Namespaces().Get(federationNS, metav1.GetOptions{}) - if err != nil { - if apierrors.IsNotFound(err) { - framework.Skipf("Could not find federation namespace %s: skipping federated test", federationNS) - } else { - framework.Failf("Unexpected error getting namespace: %v", err) - } - } -} - -// WaitForFederationApiserverReady waits for the federation apiserver to be ready. -// It tests the readiness by sending a GET request and expecting a non error response. -func WaitForFederationApiserverReady(c *federation_clientset.Clientset) error { - return wait.PollImmediate(time.Second, 1*time.Minute, func() (bool, error) { - _, err := c.Federation().Clusters().List(metav1.ListOptions{}) - if err != nil { - return false, nil - } - return true, nil - }) -} - -func LoadFederationClientset(config *restclient.Config) (*federation_clientset.Clientset, error) { - c, err := federation_clientset.NewForConfig(config) - if err != nil { - return nil, fmt.Errorf("error creating federation clientset: %v", err.Error()) - } - return c, nil -} - -func LoadFederatedConfig(overrides *clientcmd.ConfigOverrides) (*restclient.Config, error) { - c, err := framework.RestclientConfig(framework.TestContext.FederatedKubeContext) - if err != nil { - return nil, fmt.Errorf("error creating federation client config: %v", err.Error()) - } - cfg, err := clientcmd.NewDefaultClientConfig(*c, &clientcmd.ConfigOverrides{}).ClientConfig() - if cfg != nil { - //TODO(colhom): this is only here because https://github.com/kubernetes/kubernetes/issues/25422 - cfg.NegotiatedSerializer = api.Codecs - } - if err != nil { - return cfg, fmt.Errorf("error creating federation default client config: %v", err.Error()) - } - return cfg, nil -} - -// GetValidDNSSubdomainName massages the given name to be a valid dns subdomain name. -// Most resources (such as secrets, clusters) require the names to be valid dns subdomain. -// This is a generic function (not specific to federation). Should be moved to a more generic location if others want to use it. -func GetValidDNSSubdomainName(name string) (string, error) { - // "_" are not allowed. Replace them by "-". - name = regexp.MustCompile("_").ReplaceAllLiteralString(name, "-") - maxLength := validationutil.DNS1123SubdomainMaxLength - if len(name) > maxLength { - name = name[0 : maxLength-1] - } - // Verify that name now passes the validation. - if errors := validation.NameIsDNSSubdomain(name, false); len(errors) != 0 { - return "", fmt.Errorf("errors in converting name to a valid DNS subdomain %s", errors) - } - return name, nil -} - -func FederationControlPlaneUpgrade(version string) error { - version = "v" + version - _, _, err := framework.RunCmd(path.Join(framework.TestContext.RepoRoot, "federation/cluster/upgrade.sh"), version) - return err -} - -func CheckFederationVersion(c federation_clientset.Interface, want string) error { - framework.Logf("Checking federation version") - v, err := c.Discovery().ServerVersion() - if err != nil { - return fmt.Errorf("CheckFederationVersion() couldn't get the master version: %v", err) - } - // We do prefix trimming and then matching because: - // want looks like: 0.19.3-815-g50e67d4 - // got looks like: v0.19.3-815-g50e67d4034e858-dirty - got := strings.TrimPrefix(v.GitVersion, "v") - if !strings.HasPrefix(got, want) { - return fmt.Errorf("federation had apiserver version %s which does not start with %s", - got, want) - } - framework.Logf("Federation is at version %s", want) - return nil -} - -func MasterUpgrade(context, version string) error { - switch framework.TestContext.Provider { - case "gce": - return masterUpgradeGCE(context, version) - default: - return fmt.Errorf("MasterUpgrade() is not implemented for provider %s", framework.TestContext.Provider) - } -} - -func masterUpgradeGCE(context, rawVersion string) error { - version := "v" + rawVersion - // TODO: this breaks if we want to upgrade 2 clusters in same zone. use alternate methods in future to get zone of a cluster - zone := strings.TrimPrefix(context, "federation-e2e-"+framework.TestContext.Provider+"-") - - env := append(os.Environ(), "KUBE_CONTEXT="+context, "ZONE="+zone) - _, _, err := framework.RunCmdEnv(env, path.Join(framework.TestContext.RepoRoot, "cluster/gce/upgrade.sh"), "-M", version) - return err -} diff --git a/federation/test/e2e/ingress.go b/federation/test/e2e/ingress.go deleted file mode 100644 index 9ae324fc7ad..00000000000 --- a/federation/test/e2e/ingress.go +++ /dev/null @@ -1,638 +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 e2e - -import ( - "crypto/tls" - "fmt" - "net/http" - "os" - "reflect" - "strconv" - "strings" - "time" - - "k8s.io/api/core/v1" - "k8s.io/api/extensions/v1beta1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - utilnet "k8s.io/apimachinery/pkg/util/net" - "k8s.io/apimachinery/pkg/util/wait" - kubeclientset "k8s.io/client-go/kubernetes" - fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - "k8s.io/kubernetes/federation/pkg/federation-controller/util" - - fedframework "k8s.io/kubernetes/federation/test/e2e/framework" - "k8s.io/kubernetes/test/e2e/framework" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -const ( - MaxRetriesOnFederatedApiserver = 3 - FederatedIngressTimeout = 15 * time.Minute - FederatedIngressDeleteTimeout = 2 * time.Minute - FederatedIngressName = "federated-ingress" - FederatedIngressServiceName = "federated-ingress-service" - FederatedIngressTLSSecretName = "federated-ingress-tls-secret" - FederatedIngressServicePodName = "federated-ingress-service-test-pod" - FederatedIngressHost = "test-f8n.k8s.io." - - FederatedSecretTimeout = 60 * time.Second - - // TLS Certificate and Key for the ingress resource - // Generated using: - // $ openssl req -nodes -x509 -newkey rsa:2048 -keyout fedingtestkey.pem -out fedingtestcrt.pem -days 2485 - // 2485 days is an arbitrary large number chosen below int32 seconds. - FederatedIngressTLSCrt = `-----BEGIN CERTIFICATE----- -MIIDaTCCAlGgAwIBAgIJANwsCbwxm9pyMA0GCSqGSIb3DQEBCwUAMEoxCzAJBgNV -BAYTAlVTMRMwEQYDVQQIDApTb21lLVN0YXRlMQswCQYDVQQKDAJOQTEZMBcGA1UE -AwwQdGVzdC1mOG4uazhzLmlvLjAgFw0xNjEyMTYwNjA1NDRaGA8yMDg1MDEwMzA2 -MDU0NFowSjELMAkGA1UEBhMCVVMxEzARBgNVBAgMClNvbWUtU3RhdGUxCzAJBgNV -BAoMAk5BMRkwFwYDVQQDDBB0ZXN0LWY4bi5rOHMuaW8uMIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEAmsHYnLhqSeO1Q6SEjaiPiLUQV8tyGfttwNQiOT5u -ULz6ZWYA40m/1hhla9KH9sJZ515Iq+jTtiVH0rUjryT96SjxitLCAZlxVwQ63B50 -aZF2T2OPSzvrmN+J6VGcRIq0N8fUeyp2WTIEdWlpQ7DTmDNArQqFSIvJndkLow3d -hec7O+PErnvZQQC9zqa23rGndDzlgDJ4HJGAQNm3uYVh5WHv+wziP67T/82bEGgO -A6EdDPWzpYxzAA1wsqz9lX5jitlbKdI56698fPR2KRelySf7OXVvZCS4/ED1lF4k -b7fQgtBhAWe1BkuAMUl7vdRjMps7nkxmBSuxBkVQ7sb5AwIDAQABo1AwTjAdBgNV -HQ4EFgQUjf53O/W/iE2mxuJkNjZGUfjJ9RUwHwYDVR0jBBgwFoAUjf53O/W/iE2m -xuJkNjZGUfjJ9RUwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEABE7B -bAiORDBA3qE5lh6JCs/lEfz93E/gOhD9oDnm9SRND4kjy7qeGxk4Wzsd/Vr+R2mi -EZ40d4MA/mCCPnYsNQoEXMFc8IvwAbzkhh2gqTNgG0/Ks0A1mIPQNpvUcSetS4IV -732DvB3nSnFtlzf6afw+V1Vf5ydRNuM/c9GEOOHSz+rs+9M364d+wNaFD64M72ol -iDMAdtcrhOqkQi0lUING904jlJcyYM5oVNCCtme4F8nkIX9bxP/9Ea6VhDGPeJiX -tVwZuudkoEbrFlEYbyLrbVeVa9oTf4Jn66iz49/+th+bUtEoTt9gk9Cul5TFgfzx -EscdahceC7afheq6zg== ------END CERTIFICATE-----` - - FederatedIngressTLSKey = `-----BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCawdicuGpJ47VD -pISNqI+ItRBXy3IZ+23A1CI5Pm5QvPplZgDjSb/WGGVr0of2wlnnXkir6NO2JUfS -tSOvJP3pKPGK0sIBmXFXBDrcHnRpkXZPY49LO+uY34npUZxEirQ3x9R7KnZZMgR1 -aWlDsNOYM0CtCoVIi8md2QujDd2F5zs748Sue9lBAL3Oprbesad0POWAMngckYBA -2be5hWHlYe/7DOI/rtP/zZsQaA4DoR0M9bOljHMADXCyrP2VfmOK2Vsp0jnrr3x8 -9HYpF6XJJ/s5dW9kJLj8QPWUXiRvt9CC0GEBZ7UGS4AxSXu91GMymzueTGYFK7EG -RVDuxvkDAgMBAAECggEAYrXGPqB6W0r88XpceibL9rzXAcjorJ3s8ZPdiHnDz4fa -hxa69j6yOBMzjcSpqMFqquM+ozhM4d+BomqbqjmEI1ZUSuIHkRGYc5JlIMXkJvn7 -ZsPwQGKl8cqTotjFPgrizLmPVEhPWLFImsNzuxNsw6XdWQJe5VkUbrRkccqEQ8Wt -xwq/SlRercIMnRVLOOESq8EyjOY4yDgOdIifq9K9xiI8W6nMiPs0X5AcIJoTMbCe -cX0zUqW317awDWWP8u2GswwDDm4qPeWnXOrDkDx8Eo0dWJbmxw9su0XrM6KMvEMe -2a/Fy/enr5Cc6/jgsh3gO5sa8dJ1Cu+wexcoEbez8QKBgQDMXlXJu/C7djke94s3 -vGxati7AGO95bBQHW+cPuN4l0rfPZ8YuUAWD4csW4BOlUPAOukreD/SKdanigR3N -FqVPeI8rXd5kzy8/lPIOGuSkkVEpKsAJ7prFbSUVKjVPYQk2dsOEeR0r7pr2FxC9 -SBhVS/LgmPYh++iny9D0aU23hQKBgQDB2t55OE+00vgoauUc10LEY+J6tiwXuNm7 -43JtrH5ET4N+TJ2BOUl5f88TY/3QuTu6vYwlxjyn+LFuWQNhShX6lFMjt5zqPTdw -ZPDA+9B6a45cV3YjXjRsYidpWj0D2lJgy0DbucC4f3eIhNGyFUbAQB9npKDzOeUh -7Z+p/Grg5wKBgGUnVCLzySWgUImJUPkXZDJJ9j3SmcVpv0gdLvLTN/FUqPIZlTgb -F3+9ZL4/zrmGpCtF/gSHtSxLLPkVm2CFkvEQ5Rw76/XNrr8zw9NDcGQcISXVKRRB -a43IhhBBwf02NE8m3YNWRyAVi9G+fOSTKKgfXWnZjAoqG2/iK9ytum/ZAoGAYlP8 -KIxxkYy5Jvchg4GEck0f4ZJpxxaSCoWR0yN9YHTcg8Gk2pkONbyocnNzmN17+HqQ -jdCBj8nLZedsmXqUr2dwzFskEoQ+jJoGrDyOQKoxqZELcWElQhx/VSbacAvbYRF3 -snwDzxGItgx4uNWl73oW8+FDalvhZ1Y6eGR6ad0CgYEAtlNa92Fbvd3r9O2mdyWe -D2SXNMi45+wsNafX2sdkyb+qNN6qZXC9ylUl9h0zdky88JNgtAOgxIaRIdEZajnD -/Zq17sTNtgpm53x16gOAgD8M+/wmBZxA+/IKfFCubuV77MbQoPfcjT5wBMRnFQnY -Ks7c+dzaRlgDKZ6v/L/8iZU= ------END PRIVATE KEY-----` -) - -const ( - // timeout on a single http request. - reqTimeout = 10 * time.Second -) - -var _ = framework.KubeDescribe("Federated ingresses [Feature:Federation]", func() { - f := fedframework.NewDefaultFederatedFramework("federated-ingress") - - // Create/delete ingress api objects - // Validate federation apiserver, does not rely on underlying clusters or federation ingress controller. - Describe("Federated Ingresses [NoCluster]", func() { - AfterEach(func() { - nsName := f.FederationNamespace.Name - // Delete all ingresses. - deleteAllIngressesOrFail(f.FederationClientset, nsName) - }) - - It("should be created and deleted successfully", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - framework.SkipUnlessProviderIs("gce", "gke") // TODO: Federated ingress is not yet supported on non-GCP platforms. - nsName := f.FederationNamespace.Name - ingress := createIngressOrFail(f.FederationClientset, nsName, FederatedIngressServiceName, FederatedIngressTLSSecretName) - By(fmt.Sprintf("Creation of ingress %q in namespace %q succeeded. Deleting ingress.", ingress.Name, nsName)) - // Cleanup - err := f.FederationClientset.Extensions().Ingresses(nsName).Delete(ingress.Name, &metav1.DeleteOptions{}) - framework.ExpectNoError(err, "Error deleting ingress %q in namespace %q", ingress.Name, ingress.Namespace) - By(fmt.Sprintf("Deletion of ingress %q in namespace %q succeeded.", ingress.Name, nsName)) - }) - }) - - // e2e cases for federation ingress controller - var _ = Describe("Federated Ingresses [Slow]", func() { - var ( - clusters fedframework.ClusterSlice - federationName, ns string - jig *federationTestJig - service *v1.Service - secret *v1.Secret - ) - - // register clusters in federation apiserver - BeforeEach(func() { - fedframework.SkipUnlessFederated(f.ClientSet) - framework.SkipUnlessProviderIs("gce", "gke") // TODO: Federated ingress is not yet supported on non-GCP platforms. - if federationName = os.Getenv("FEDERATION_NAME"); federationName == "" { - federationName = DefaultFederationName - } - jig = newFederationTestJig(f.FederationClientset) - clusters = f.GetRegisteredClusters() - ns = f.FederationNamespace.Name - // create backend service - service = createLBServiceOrFail(f.FederationClientset, ns, FederatedIngressServiceName, clusters) - // create the TLS secret - secret = createTLSSecretOrFail(f.FederationClientset, ns, FederatedIngressTLSSecretName) - // wait for services objects sync - waitForServiceShardsOrFail(ns, service, clusters) - // wait for TLS secret sync - waitForSecretShardsOrFail(ns, secret, clusters) - }) - - AfterEach(func() { - // Delete all ingresses. - deleteAllIngressesOrFail(f.FederationClientset, ns) - if secret != nil { - By("Deleting secret") - orphanDependents := false - deleteSecretOrFail(f.FederationClientset, ns, secret.Name, &orphanDependents) - secret = nil - } else { - By("No secret to delete. Secret is nil") - } - if service != nil { - By("Deleting service") - deleteServiceOrFail(f.FederationClientset, ns, service.Name, nil) - By("Cleanup service shards and provider resources") - cleanupServiceShardsAndProviderResources(ns, service, clusters) - service = nil - } else { - By("No service to delete. Service is nil") - } - }) - - It("should create and update matching ingresses in underlying clusters", func() { - ingress := createIngressOrFail(f.FederationClientset, ns, FederatedIngressServiceName, FederatedIngressTLSSecretName) - // wait for ingress shards being created - waitForIngressShardsOrFail(ns, ingress, clusters) - ingress = updateIngressOrFail(f.FederationClientset, ns) - waitForIngressShardsUpdatedOrFail(ns, ingress, clusters) - }) - - It("should be deleted from underlying clusters when OrphanDependents is false", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - orphanDependents := false - verifyCascadingDeletionForIngress(f.FederationClientset, clusters, &orphanDependents, ns) - By(fmt.Sprintf("Verified that ingresses were deleted from underlying clusters")) - }) - - It("should not be deleted from underlying clusters when OrphanDependents is true", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - orphanDependents := true - verifyCascadingDeletionForIngress(f.FederationClientset, clusters, &orphanDependents, ns) - By(fmt.Sprintf("Verified that ingresses were not deleted from underlying clusters")) - }) - - It("should not be deleted from underlying clusters when OrphanDependents is nil", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - verifyCascadingDeletionForIngress(f.FederationClientset, clusters, nil, ns) - By(fmt.Sprintf("Verified that ingresses were not deleted from underlying clusters")) - }) - - var _ = Describe("Ingress connectivity and DNS", func() { - - var backendPods BackendPodMap - - BeforeEach(func() { - fedframework.SkipUnlessFederated(f.ClientSet) - // create backend pod - backendPods = createBackendPodsOrFail(clusters, ns, FederatedIngressServicePodName) - // create ingress object - jig.ing = createIngressOrFail(f.FederationClientset, ns, service.Name, FederatedIngressTLSSecretName) - // wait for ingress objects sync - waitForIngressShardsOrFail(ns, jig.ing, clusters) - By(fmt.Sprintf("Ingress created as %v", jig.ing.Name)) - }) - - AfterEach(func() { - deleteBackendPodsOrFail(clusters, backendPods) - backendPods = nil - - if jig.ing != nil { - By(fmt.Sprintf("Deleting ingress %v on all clusters", jig.ing.Name)) - orphanDependents := false - deleteIngressOrFail(f.FederationClientset, ns, jig.ing.Name, &orphanDependents) - jig.ing = nil - } else { - By("No ingress to delete. Ingress is nil") - } - }) - - PIt("should be able to discover a federated ingress service via DNS", func() { - // we are about the ingress name - svcDNSNames := []string{ - fmt.Sprintf("%s.%s", FederatedIngressServiceName, ns), - fmt.Sprintf("%s.%s.svc.cluster.local.", FederatedIngressServiceName, ns), - // TODO these two entries are not set yet - //fmt.Sprintf("%s.%s.%s", FederatedIngressServiceName, ns, federationName), - //fmt.Sprintf("%s.%s.%s.svc.cluster.local.", FederatedIngressServiceName, ns, federationName), - } - // check dns records in underlying cluster - for i, DNSName := range svcDNSNames { - discoverService(f, DNSName, true, "federated-ingress-e2e-discovery-pod-"+strconv.Itoa(i)) - } - // TODO check dns record in global dns server - }) - - It("should be able to connect to a federated ingress via its load balancer", func() { - By(fmt.Sprintf("Waiting for Federated Ingress on %v", jig.ing.Name)) - // check the traffic on federation ingress - jig.waitForFederatedIngress() - }) - - }) - }) -}) - -// Deletes all Ingresses in the given namespace name. -func deleteAllIngressesOrFail(clientset *fedclientset.Clientset, nsName string) { - orphanDependents := false - err := clientset.Extensions().Ingresses(nsName).DeleteCollection(&metav1.DeleteOptions{OrphanDependents: &orphanDependents}, metav1.ListOptions{}) - Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error in deleting ingresses in namespace: %s", nsName)) -} - -// equivalent returns true if the two ingress spec are equivalent. -func equivalentIngress(federatedIngress, clusterIngress v1beta1.Ingress) bool { - return reflect.DeepEqual(clusterIngress.Spec, federatedIngress.Spec) -} - -// verifyCascadingDeletionForIngress verifies that ingresses are deleted from -// underlying clusters when orphan dependents is false and they are not deleted -// when orphan dependents is true. -func verifyCascadingDeletionForIngress(clientset *fedclientset.Clientset, clusters fedframework.ClusterSlice, orphanDependents *bool, nsName string) { - ingress := createIngressOrFail(clientset, nsName, FederatedIngressServiceName, FederatedIngressTLSSecretName) - ingressName := ingress.Name - // Check subclusters if the ingress was created there. - By(fmt.Sprintf("Waiting for ingress %s to be created in all underlying clusters", ingressName)) - waitForIngressShardsOrFail(nsName, ingress, clusters) - - By(fmt.Sprintf("Deleting ingress %s", ingressName)) - deleteIngressOrFail(clientset, nsName, ingressName, orphanDependents) - - By(fmt.Sprintf("Verifying ingresses %s in underlying clusters", ingressName)) - errMessages := []string{} - // ingress should be present in underlying clusters unless orphanDependents is false. - shouldExist := orphanDependents == nil || *orphanDependents == true - for _, cluster := range clusters { - clusterName := cluster.Name - _, err := cluster.Extensions().Ingresses(nsName).Get(ingressName, metav1.GetOptions{}) - if shouldExist && errors.IsNotFound(err) { - errMessages = append(errMessages, fmt.Sprintf("unexpected NotFound error for ingress %s in cluster %s, expected ingress to exist", ingressName, clusterName)) - } else if !shouldExist && !errors.IsNotFound(err) { - errMessages = append(errMessages, fmt.Sprintf("expected NotFound error for ingress %s in cluster %s, got error: %v", ingressName, clusterName, err)) - } - } - if len(errMessages) != 0 { - framework.Failf("%s", strings.Join(errMessages, "; ")) - } -} - -// waitForIngressOrFail waits until a ingress is either present or absent in the cluster specified by clientset. -// If the condition is not met within timout, it fails the calling test. -func waitForIngressOrFail(clientset *kubeclientset.Clientset, namespace string, ingress *v1beta1.Ingress, present bool, timeout time.Duration) { - By(fmt.Sprintf("Fetching a federated ingress shard of ingress %q in namespace %q from cluster", ingress.Name, namespace)) - var clusterIngress *v1beta1.Ingress - err := wait.PollImmediate(framework.Poll, timeout, func() (bool, error) { - var err error - clusterIngress, err = clientset.ExtensionsV1beta1().Ingresses(namespace).Get(ingress.Name, metav1.GetOptions{}) - if (!present) && errors.IsNotFound(err) { // We want it gone, and it's gone. - By(fmt.Sprintf("Success: shard of federated ingress %q in namespace %q in cluster is absent", ingress.Name, namespace)) - return true, nil // Success - } - if present && err == nil { // We want it present, and the Get succeeded, so we're all good. - By(fmt.Sprintf("Success: shard of federated ingress %q in namespace %q in cluster is present", ingress.Name, namespace)) - return true, nil // Success - } - By(fmt.Sprintf("Ingress %q in namespace %q in cluster. Found: %v, waiting for Found: %v, trying again in %s (err=%v)", ingress.Name, namespace, clusterIngress != nil && err == nil, present, framework.Poll, err)) - return false, nil - }) - framework.ExpectNoError(err, "Failed to verify ingress %q in namespace %q in cluster: Present=%v", ingress.Name, namespace, present) - - if present && clusterIngress != nil { - Expect(equivalentIngress(*clusterIngress, *ingress)) - } -} - -// waitForIngressShardsOrFail waits for the ingress to appear in all clusters -func waitForIngressShardsOrFail(namespace string, ingress *v1beta1.Ingress, clusters fedframework.ClusterSlice) { - framework.Logf("Waiting for ingress %q in %d clusters", ingress.Name, len(clusters)) - for _, c := range clusters { - waitForIngressOrFail(c.Clientset, namespace, ingress, true, FederatedIngressTimeout) - } -} - -// waitForIngressShardsUpdatedOrFail waits for the ingress to be updated in all clusters -func waitForIngressShardsUpdatedOrFail(namespace string, ingress *v1beta1.Ingress, clusters fedframework.ClusterSlice) { - framework.Logf("Waiting for ingress %q in %d clusters", ingress.Name, len(clusters)) - for _, c := range clusters { - waitForIngressUpdateOrFail(c.Clientset, namespace, ingress, FederatedIngressTimeout) - } -} - -// waitForIngressUpdateOrFail waits until a ingress is updated in the specified cluster with same spec of federated ingress. -// If the condition is not met within timeout, it fails the calling test. -func waitForIngressUpdateOrFail(clientset *kubeclientset.Clientset, namespace string, ingress *v1beta1.Ingress, timeout time.Duration) { - By(fmt.Sprintf("Fetching a federated ingress shard of ingress %q in namespace %q from cluster", ingress.Name, namespace)) - err := wait.PollImmediate(framework.Poll, timeout, func() (bool, error) { - clusterIngress, err := clientset.ExtensionsV1beta1().Ingresses(namespace).Get(ingress.Name, metav1.GetOptions{}) - if err == nil { // We want it present, and the Get succeeded, so we're all good. - if equivalentIngress(*clusterIngress, *ingress) { - By(fmt.Sprintf("Success: shard of federated ingress %q in namespace %q in cluster is updated", ingress.Name, namespace)) - return true, nil - } - By(fmt.Sprintf("Ingress %q in namespace %q in cluster, waiting for service being updated, trying again in %s (err=%v)", ingress.Name, namespace, framework.Poll, err)) - return false, nil - } - By(fmt.Sprintf("Ingress %q in namespace %q in cluster, waiting for service being updated, trying again in %s (err=%v)", ingress.Name, namespace, framework.Poll, err)) - return false, nil - }) - framework.ExpectNoError(err, "Failed to verify ingress %q in namespace %q in cluster", ingress.Name, namespace) -} - -// waitForIngressShardsGoneOrFail waits for the ingress to disappear in all clusters -func waitForIngressShardsGoneOrFail(namespace string, ingress *v1beta1.Ingress, clusters fedframework.ClusterSlice) { - framework.Logf("Waiting for ingress %q in %d clusters", ingress.Name, len(clusters)) - for _, c := range clusters { - waitForIngressOrFail(c.Clientset, namespace, ingress, false, FederatedIngressTimeout) - } -} - -func deleteIngressOrFail(clientset *fedclientset.Clientset, namespace string, ingressName string, orphanDependents *bool) { - if clientset == nil || len(namespace) == 0 || len(ingressName) == 0 { - Fail(fmt.Sprintf("Internal error: invalid parameters passed to deleteIngressOrFail: clientset: %v, namespace: %v, ingress: %v", clientset, namespace, ingressName)) - } - err := clientset.ExtensionsV1beta1().Ingresses(namespace).Delete(ingressName, &metav1.DeleteOptions{OrphanDependents: orphanDependents}) - framework.ExpectNoError(err, "Error deleting ingress %q from namespace %q", ingressName, namespace) - // Wait for the ingress to be deleted. - err = wait.Poll(framework.Poll, FederatedIngressDeleteTimeout, func() (bool, error) { - _, err := clientset.Extensions().Ingresses(namespace).Get(ingressName, metav1.GetOptions{}) - if err != nil && errors.IsNotFound(err) { - return true, nil - } - return false, err - }) - if err != nil { - framework.Failf("Error in deleting ingress %s: %v", ingressName, err) - } -} - -// TODO: quinton: This is largely a cut 'n paste of the above. Yuck! Refactor as soon as we have a common interface implmented by both fedclientset.Clientset and kubeclientset.Clientset -func deleteClusterIngressOrFail(clusterName string, clientset *kubeclientset.Clientset, namespace string, ingressName string) { - if clientset == nil || len(namespace) == 0 || len(ingressName) == 0 { - Fail(fmt.Sprintf("Internal error: invalid parameters passed to deleteClusterIngressOrFail: cluster: %q, clientset: %v, namespace: %v, ingress: %v", clusterName, clientset, namespace, ingressName)) - } - err := clientset.ExtensionsV1beta1().Ingresses(namespace).Delete(ingressName, metav1.NewDeleteOptions(0)) - framework.ExpectNoError(err, "Error deleting cluster ingress %q/%q from cluster %q", namespace, ingressName, clusterName) -} - -func createIngressOrFail(clientset *fedclientset.Clientset, namespace, serviceName, secretName string) *v1beta1.Ingress { - if clientset == nil || len(namespace) == 0 { - Fail(fmt.Sprintf("Internal error: invalid parameters passed to createIngressOrFail: clientset: %v, namespace: %v", clientset, namespace)) - } - By(fmt.Sprintf("Creating federated ingress %q in namespace %q", FederatedIngressName, namespace)) - - ingress := &v1beta1.Ingress{ - ObjectMeta: metav1.ObjectMeta{ - Name: FederatedIngressName, - }, - Spec: v1beta1.IngressSpec{ - Backend: &v1beta1.IngressBackend{ - ServiceName: serviceName, - ServicePort: intstr.FromInt(80), - }, - TLS: []v1beta1.IngressTLS{ - { - SecretName: secretName, - }, - }, - }, - } - - newIng, err := clientset.Extensions().Ingresses(namespace).Create(ingress) - framework.ExpectNoError(err, "Creating ingress %q in namespace %q", ingress.Name, namespace) - By(fmt.Sprintf("Successfully created federated ingress %q in namespace %q", FederatedIngressName, namespace)) - return newIng -} - -func updateIngressOrFail(clientset *fedclientset.Clientset, namespace string) (newIng *v1beta1.Ingress) { - var err error - if clientset == nil || len(namespace) == 0 { - Fail(fmt.Sprintf("Internal error: invalid parameters passed to createIngressOrFail: clientset: %v, namespace: %v", clientset, namespace)) - } - ingress := &v1beta1.Ingress{ - ObjectMeta: metav1.ObjectMeta{ - Name: FederatedIngressName, - }, - Spec: v1beta1.IngressSpec{ - Backend: &v1beta1.IngressBackend{ - ServiceName: "updated-testingress-service", - ServicePort: intstr.FromInt(80), - }, - }, - } - - err = waitForFederatedIngressExists(clientset, namespace, FederatedIngressName, FederatedIngressTimeout) - if err != nil { - framework.Failf("failed to get ingress %q: %v", FederatedIngressName, err) - } - for i := 0; i < MaxRetriesOnFederatedApiserver; i++ { - newIng, err = clientset.Extensions().Ingresses(namespace).Update(ingress) - if err == nil { - framework.DescribeIng(namespace) - return newIng - } - if !errors.IsConflict(err) && !errors.IsServerTimeout(err) { - framework.Failf("failed to update ingress %q: %v", FederatedIngressName, err) - } - } - framework.Failf("too many retries updating ingress %q", FederatedIngressName) - return nil -} - -func (j *federationTestJig) waitForFederatedIngress() { - // Wait for the loadbalancer IP. - address, err := waitForFederatedIngressAddress(j.client, j.ing.Namespace, j.ing.Name, framework.LoadBalancerPollTimeout) - if err != nil { - framework.Failf("Ingress failed to acquire an IP address within %v", framework.LoadBalancerPollTimeout) - } - j.address = address - framework.Logf("Found address %v for ingress %v", j.address, j.ing.Name) - - client := &http.Client{ - // This is mostly `http.DefaultTransport` except for the - // `TLSClientConfig`. - Transport: utilnet.SetTransportDefaults(&http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - }), - Timeout: reqTimeout, - } - - // Verify that simple GET works. - route := fmt.Sprintf("https://%v", address) - framework.Logf("Testing route %v with simple GET", route) - framework.ExpectNoError(framework.PollURL(route, FederatedIngressHost, framework.LoadBalancerPollTimeout, framework.LoadBalancerPollInterval, client, false)) -} - -func createTLSSecretOrFail(clientset *fedclientset.Clientset, namespace, secretName string) *v1.Secret { - if clientset == nil || len(namespace) == 0 { - framework.Logf("Internal error: invalid parameters passed to createTLSSecretOrFail: clientset: %v, namespace: %v", clientset, namespace) - } - secret := &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: secretName, - }, - Type: v1.SecretTypeOpaque, - Data: map[string][]byte{ - "tls.crt": []byte(FederatedIngressTLSCrt), - "tls.key": []byte(FederatedIngressTLSKey), - }, - } - By(fmt.Sprintf("Creating federated secret %q in namespace %q", secretName, namespace)) - newSecret, err := clientset.Core().Secrets(namespace).Create(secret) - framework.ExpectNoError(err, "creating secret %q in namespace %q", secret.Name, namespace) - return newSecret -} - -type federationTestJig struct { - // TODO add TLS check later - rootCAs map[string][]byte - address string - ing *v1beta1.Ingress - client *fedclientset.Clientset -} - -func newFederationTestJig(c *fedclientset.Clientset) *federationTestJig { - return &federationTestJig{client: c, rootCAs: map[string][]byte{}} -} - -// WaitForFederatedIngressAddress waits for the Ingress to acquire an address. -func waitForFederatedIngressAddress(c *fedclientset.Clientset, ns, ingName string, timeout time.Duration) (string, error) { - var address string - err := wait.PollImmediate(10*time.Second, timeout, func() (bool, error) { - ipOrNameList, err := getFederatedIngressAddress(c, ns, ingName) - if err != nil || len(ipOrNameList) == 0 { - framework.Logf("Waiting for Ingress %v to acquire IP, error %v", ingName, err) - return false, nil - } - address = ipOrNameList[0] - return true, nil - }) - return address, err -} - -// waitForFederatedIngressExists waits for the Ingress object exists. -func waitForFederatedIngressExists(c *fedclientset.Clientset, ns, ingName string, timeout time.Duration) error { - err := wait.PollImmediate(10*time.Second, timeout, func() (bool, error) { - _, err := c.Extensions().Ingresses(ns).Get(ingName, metav1.GetOptions{}) - if err != nil { - framework.Logf("Waiting for Ingress %v, error %v", ingName, err) - return false, nil - } - return true, nil - }) - return err -} - -// getFederatedIngressAddress returns the ips/hostnames associated with the Ingress. -func getFederatedIngressAddress(client *fedclientset.Clientset, ns, name string) ([]string, error) { - ing, err := client.Extensions().Ingresses(ns).Get(name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - addresses := []string{} - for _, a := range ing.Status.LoadBalancer.Ingress { - if a.IP != "" { - addresses = append(addresses, a.IP) - } - if a.Hostname != "" { - addresses = append(addresses, a.Hostname) - } - } - return addresses, nil -} - -func waitForSecretShardsOrFail(nsName string, secret *v1.Secret, clusters fedframework.ClusterSlice) { - framework.Logf("Waiting for secret %q in %d clusters", secret.Name, len(clusters)) - for _, c := range clusters { - waitForSecretOrFail(c.Clientset, nsName, secret, true, FederatedSecretTimeout) - } -} - -func waitForSecretOrFail(clientset *kubeclientset.Clientset, nsName string, secret *v1.Secret, present bool, timeout time.Duration) { - By(fmt.Sprintf("Fetching a federated secret shard of secret %q in namespace %q from cluster", secret.Name, nsName)) - var clusterSecret *v1.Secret - err := wait.PollImmediate(framework.Poll, timeout, func() (bool, error) { - var err error - clusterSecret, err = clientset.CoreV1().Secrets(nsName).Get(secret.Name, metav1.GetOptions{}) - if (!present) && errors.IsNotFound(err) { // We want it gone, and it's gone. - By(fmt.Sprintf("Success: shard of federated secret %q in namespace %q in cluster is absent", secret.Name, nsName)) - return true, nil // Success - } - if present && err == nil { // We want it present, and the Get succeeded, so we're all good. - By(fmt.Sprintf("Success: shard of federated secret %q in namespace %q in cluster is present", secret.Name, nsName)) - return true, nil // Success - } - By(fmt.Sprintf("Secret %q in namespace %q in cluster. Found: %v, waiting for Found: %v, trying again in %s (err=%v)", secret.Name, nsName, clusterSecret != nil && err == nil, present, framework.Poll, err)) - return false, nil - }) - framework.ExpectNoError(err, "Failed to verify secret %q in namespace %q in cluster: Present=%v", secret.Name, nsName, present) - - if present && clusterSecret != nil { - Expect(util.SecretEquivalent(*clusterSecret, *secret)) - } -} - -func deleteSecretOrFail(clientset *fedclientset.Clientset, nsName string, secretName string, orphanDependents *bool) { - By(fmt.Sprintf("Deleting secret %q in namespace %q", secretName, nsName)) - err := clientset.Core().Secrets(nsName).Delete(secretName, &metav1.DeleteOptions{OrphanDependents: orphanDependents}) - if err != nil && !errors.IsNotFound(err) { - framework.ExpectNoError(err, "Error deleting secret %q in namespace %q", secretName, nsName) - } - - // Wait for the secret to be deleted. - err = wait.Poll(5*time.Second, wait.ForeverTestTimeout, func() (bool, error) { - _, err := clientset.Core().Secrets(nsName).Get(secretName, metav1.GetOptions{}) - if err != nil && errors.IsNotFound(err) { - return true, nil - } - return false, err - }) - if err != nil { - framework.Failf("Error in deleting secret %s: %v", secretName, err) - } -} diff --git a/federation/test/e2e/job.go b/federation/test/e2e/job.go deleted file mode 100644 index 5e1e3a67927..00000000000 --- a/federation/test/e2e/job.go +++ /dev/null @@ -1,291 +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 e2e - -import ( - "fmt" - "strings" - "time" - - batchv1 "k8s.io/api/batch/v1" - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/wait" - fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - fedutil "k8s.io/kubernetes/federation/pkg/federation-controller/util" - fedframework "k8s.io/kubernetes/federation/test/e2e/framework" - "k8s.io/kubernetes/test/e2e/framework" - imageutils "k8s.io/kubernetes/test/utils/image" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/api/errors" -) - -const ( - FederationJobName = "federation-job" -) - -var _ = framework.KubeDescribe("Federation jobs [Feature:Federation]", func() { - - f := fedframework.NewDefaultFederatedFramework("federation-job") - - Describe("Job objects [NoCluster]", func() { - AfterEach(func() { - fedframework.SkipUnlessFederated(f.ClientSet) - - // Delete all jobs. - nsName := f.FederationNamespace.Name - deleteAllJobsOrFail(f.FederationClientset, nsName) - }) - - It("should be created and deleted successfully", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - - nsName := f.FederationNamespace.Name - job := createJobOrFail(f.FederationClientset, nsName) - By(fmt.Sprintf("Creation of job %q in namespace %q succeeded. Deleting job.", job.Name, nsName)) - // Cleanup - err := f.FederationClientset.Batch().Jobs(nsName).Delete(job.Name, &metav1.DeleteOptions{}) - framework.ExpectNoError(err, "Error deleting job %q in namespace %q", job.Name, job.Namespace) - By(fmt.Sprintf("Deletion of job %q in namespace %q succeeded.", job.Name, nsName)) - }) - - }) - - // e2e cases for federated job controller - Describe("Federated Job", func() { - var ( - clusters fedframework.ClusterSlice - ) - BeforeEach(func() { - fedframework.SkipUnlessFederated(f.ClientSet) - clusters = f.GetRegisteredClusters() - }) - - AfterEach(func() { - nsName := f.FederationNamespace.Name - deleteAllJobsOrFail(f.FederationClientset, nsName) - }) - - It("should create and update matching jobs in underlying clusters", func() { - nsName := f.FederationNamespace.Name - job := createJobOrFail(f.FederationClientset, nsName) - defer func() { - // cleanup. deletion of jobs is not supported for underlying clusters - By(fmt.Sprintf("Deleting job %q/%q", nsName, job.Name)) - waitForJobOrFail(f.FederationClientset, nsName, job.Name, clusters) - f.FederationClientset.Batch().Jobs(nsName).Delete(job.Name, &metav1.DeleteOptions{}) - }() - - waitForJobOrFail(f.FederationClientset, nsName, job.Name, clusters) - By(fmt.Sprintf("Successfuly created and synced job %q/%q to clusters", nsName, job.Name)) - }) - - It("should be deleted from underlying clusters when OrphanDependents is false", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - nsName := f.FederationNamespace.Name - orphanDependents := false - verifyCascadingDeletionForJob(f.FederationClientset, clusters, &orphanDependents, nsName) - By(fmt.Sprintf("Verified that jobs were deleted from underlying clusters")) - }) - - It("should not be deleted from underlying clusters when OrphanDependents is true", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - nsName := f.FederationNamespace.Name - orphanDependents := true - verifyCascadingDeletionForJob(f.FederationClientset, clusters, &orphanDependents, nsName) - By(fmt.Sprintf("Verified that jobs were not deleted from underlying clusters")) - }) - - It("should not be deleted from underlying clusters when OrphanDependents is nil", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - nsName := f.FederationNamespace.Name - verifyCascadingDeletionForJob(f.FederationClientset, clusters, nil, nsName) - By(fmt.Sprintf("Verified that jobs were not deleted from underlying clusters")) - }) - - }) -}) - -// deleteAllJobsOrFail deletes all jobs in the given namespace name. -func deleteAllJobsOrFail(clientset *fedclientset.Clientset, nsName string) { - jobList, err := clientset.Batch().Jobs(nsName).List(metav1.ListOptions{}) - Expect(err).NotTo(HaveOccurred()) - orphanDependents := false - for _, job := range jobList.Items { - deleteJobOrFail(clientset, nsName, job.Name, &orphanDependents) - } -} - -// verifyCascadingDeletionForJob verifies that job are deleted -// from underlying clusters when orphan dependents is false and they are not -// deleted when orphan dependents is true. -func verifyCascadingDeletionForJob(clientset *fedclientset.Clientset, clusters fedframework.ClusterSlice, orphanDependents *bool, nsName string) { - job := createJobOrFail(clientset, nsName) - jobName := job.Name - // Check subclusters if the job was created there. - By(fmt.Sprintf("Waiting for job %s to be created in all underlying clusters", jobName)) - err := wait.Poll(5*time.Second, 2*time.Minute, func() (bool, error) { - for _, cluster := range clusters { - _, err := cluster.Batch().Jobs(nsName).Get(jobName, metav1.GetOptions{}) - if err != nil && errors.IsNotFound(err) { - return false, nil - } - if err != nil { - return false, err - } - } - return true, nil - }) - framework.ExpectNoError(err, "Not all jobs created") - - By(fmt.Sprintf("Deleting job %s", jobName)) - deleteJobOrFail(clientset, nsName, jobName, orphanDependents) - - By(fmt.Sprintf("Verifying job %s in underlying clusters", jobName)) - errMessages := []string{} - // job should be present in underlying clusters unless orphanDependents is false. - shouldExist := orphanDependents == nil || *orphanDependents == true - for _, cluster := range clusters { - clusterName := cluster.Name - _, err := cluster.Batch().Jobs(nsName).Get(jobName, metav1.GetOptions{}) - if shouldExist && errors.IsNotFound(err) { - errMessages = append(errMessages, fmt.Sprintf("unexpected NotFound error for job %s in cluster %s, expected job to exist", jobName, clusterName)) - } else if !shouldExist && !errors.IsNotFound(err) { - errMessages = append(errMessages, fmt.Sprintf("expected NotFound error for job %s in cluster %s, got error: %v", jobName, clusterName, err)) - } - } - if len(errMessages) != 0 { - framework.Failf("%s", strings.Join(errMessages, "; ")) - } -} - -func waitForJobOrFail(c *fedclientset.Clientset, namespace string, jobName string, clusters fedframework.ClusterSlice) { - err := waitForJob(c, namespace, jobName, clusters) - framework.ExpectNoError(err, "Failed to verify job %q/%q, err: %v", namespace, jobName, err) -} - -func waitForJob(c *fedclientset.Clientset, namespace string, jobName string, clusters fedframework.ClusterSlice) error { - err := wait.Poll(10*time.Second, fedframework.FederatedDefaultTestTimeout, func() (bool, error) { - fjob, err := c.Batch().Jobs(namespace).Get(jobName, metav1.GetOptions{}) - if err != nil { - return false, err - } - succeeded := int32(0) - for _, cluster := range clusters { - job, err := cluster.Batch().Jobs(namespace).Get(jobName, metav1.GetOptions{}) - if err != nil && !errors.IsNotFound(err) { - By(fmt.Sprintf("Failed getting job: %q/%q/%q, err: %v", cluster.Name, namespace, jobName, err)) - return false, err - } - if err == nil { - if !verifyJob(fjob, job) { - By(fmt.Sprintf("Job meta or spec not match for cluster %q:\n federation: %v\n cluster: %v", cluster.Name, fjob, job)) - return false, nil - } - succeeded += job.Status.Succeeded - } - } - if succeeded == fjob.Status.Succeeded && - (fjob.Spec.Completions != nil && succeeded == *fjob.Spec.Completions) { - return true, nil - } - By(fmt.Sprintf("Job statuses not match, federation succeeded: %v/%v, clusters succeeded: %v\n", - fjob.Status.Succeeded, func(p *int32) int32 { - if p != nil { - return *p - } else { - return -1 - } - }(fjob.Spec.Completions), succeeded)) - return false, nil - }) - - return err -} - -func verifyJob(fedJob, localJob *batchv1.Job) bool { - localJob = localJob.DeepCopy() - localJob.Spec.ManualSelector = fedJob.Spec.ManualSelector - localJob.Spec.Completions = fedJob.Spec.Completions - localJob.Spec.Parallelism = fedJob.Spec.Parallelism - localJob.Spec.BackoffLimit = fedJob.Spec.BackoffLimit - return fedutil.ObjectMetaAndSpecEquivalent(fedJob, localJob) -} - -func createJobOrFail(clientset *fedclientset.Clientset, namespace string) *batchv1.Job { - if clientset == nil || len(namespace) == 0 { - Fail(fmt.Sprintf("Internal error: invalid parameters passed to createJobOrFail: clientset: %v, namespace: %v", clientset, namespace)) - } - By(fmt.Sprintf("Creating federation job %q in namespace %q", FederationJobName, namespace)) - - job := newJobForFed(namespace, FederationJobName, 5, 5) - - _, err := clientset.Batch().Jobs(namespace).Create(job) - framework.ExpectNoError(err, "Creating job %q in namespace %q", job.Name, namespace) - By(fmt.Sprintf("Successfully created federation job %q in namespace %q", FederationJobName, namespace)) - return job -} - -func deleteJobOrFail(clientset *fedclientset.Clientset, nsName string, jobName string, orphanDependents *bool) { - By(fmt.Sprintf("Deleting job %q in namespace %q", jobName, nsName)) - err := clientset.Batch().Jobs(nsName).Delete(jobName, &metav1.DeleteOptions{OrphanDependents: orphanDependents}) - if err != nil && !errors.IsNotFound(err) { - framework.ExpectNoError(err, "Error deleting job %q in namespace %q", jobName, nsName) - } - - // Wait for the job to be deleted. - err = wait.Poll(10*time.Second, fedframework.FederatedDefaultTestTimeout, func() (bool, error) { - _, err := clientset.Batch().Jobs(nsName).Get(jobName, metav1.GetOptions{}) - if err != nil && errors.IsNotFound(err) { - return true, nil - } - return false, err - }) - if err != nil { - framework.Failf("Error in deleting job %s: %v", jobName, err) - } -} - -func newJobForFed(namespace string, name string, completions int32, parallelism int32) *batchv1.Job { - return &batchv1.Job{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: batchv1.JobSpec{ - Parallelism: ¶llelism, - Completions: &completions, - Template: v1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{"name": "fjob"}, - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: "sleep", - Image: imageutils.GetBusyBoxImage(), - Command: []string{"sleep", "1"}, - }, - }, - RestartPolicy: v1.RestartPolicyNever, - }, - }, - }, - } -} diff --git a/federation/test/e2e/namespace.go b/federation/test/e2e/namespace.go deleted file mode 100644 index f92db2cf3af..00000000000 --- a/federation/test/e2e/namespace.go +++ /dev/null @@ -1,236 +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 e2e - -import ( - "fmt" - "strings" - "time" - - "k8s.io/api/core/v1" - "k8s.io/api/extensions/v1beta1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/apiserver/pkg/storage/names" - clientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/typed/core/v1" - fedframework "k8s.io/kubernetes/federation/test/e2e/framework" - "k8s.io/kubernetes/test/e2e/framework" - - . "github.com/onsi/ginkgo" -) - -const ( - eventNamePrefix = "e2e-namespace-test-event-" - namespacePrefix = "e2e-namespace-test-" - replicaSetNamePrefix = "e2e-namespace-test-rs-" -) - -// Create/delete ingress api objects -var _ = framework.KubeDescribe("Federation namespace [Feature:Federation]", func() { - f := fedframework.NewDefaultFederatedFramework("federation-namespace") - - Describe("Namespace objects", func() { - var clusters fedframework.ClusterSlice - - var nsName string - - BeforeEach(func() { - fedframework.SkipUnlessFederated(f.ClientSet) - clusters = f.GetRegisteredClusters() - }) - - AfterEach(func() { - fedframework.SkipUnlessFederated(f.ClientSet) - deleteNamespace(nil, nsName, - f.FederationClientset.Core().Namespaces().Get, - f.FederationClientset.Core().Namespaces().Delete) - for _, cluster := range clusters { - deleteNamespace(nil, nsName, - cluster.CoreV1().Namespaces().Get, - cluster.CoreV1().Namespaces().Delete) - } - }) - - // See https://github.com/kubernetes/kubernetes/issues/38225 - It("deletes replicasets in the namespace when the namespace is deleted", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - - nsName = createNamespace(f.FederationClientset.Core().Namespaces()) - rsName := names.SimpleNameGenerator.GenerateName(replicaSetNamePrefix) - replicaCount := int32(2) - rs := &v1beta1.ReplicaSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: rsName, - Namespace: nsName, - }, - Spec: v1beta1.ReplicaSetSpec{ - Replicas: &replicaCount, - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"name": "myrs"}, - }, - Template: v1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{"name": "myrs"}, - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: "nginx", - Image: "nginx", - }, - }, - }, - }, - }, - } - - By(fmt.Sprintf("Creating replicaset %s in namespace %s", rsName, nsName)) - _, err := f.FederationClientset.Extensions().ReplicaSets(nsName).Create(rs) - if err != nil { - framework.Failf("Failed to create replicaset %v in namespace %s, err: %s", rs, nsName, err) - } - - By(fmt.Sprintf("Deleting namespace %s", nsName)) - deleteNamespace(nil, nsName, - f.FederationClientset.Core().Namespaces().Get, - f.FederationClientset.Core().Namespaces().Delete) - - By(fmt.Sprintf("Verify that replicaset %s was deleted as well", rsName)) - - waitForReplicaSetToBeDeletedOrFail(f.FederationClientset, nsName, rsName) - }) - - It("all resources in the namespace should be deleted when namespace is deleted", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - - nsName = createNamespace(f.FederationClientset.Core().Namespaces()) - - // Create resources in the namespace. - event := v1.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: names.SimpleNameGenerator.GenerateName(eventNamePrefix), - Namespace: nsName, - }, - InvolvedObject: v1.ObjectReference{ - Kind: "Pod", - Namespace: nsName, - Name: "sample-pod", - }, - } - By(fmt.Sprintf("Creating event %s in namespace %s", event.Name, nsName)) - _, err := f.FederationClientset.Core().Events(nsName).Create(&event) - if err != nil { - framework.Failf("Failed to create event %v in namespace %s, err: %s", event, nsName, err) - } - - By(fmt.Sprintf("Deleting namespace %s", nsName)) - deleteNamespace(nil, nsName, - f.FederationClientset.Core().Namespaces().Get, - f.FederationClientset.Core().Namespaces().Delete) - - By(fmt.Sprintf("Verify that event %s was deleted as well", event.Name)) - latestEvent, err := f.FederationClientset.Core().Events(nsName).Get(event.Name, metav1.GetOptions{}) - if !errors.IsNotFound(err) { - framework.Failf("Event %s should have been deleted. Found: %v", event.Name, latestEvent) - } - By(fmt.Sprintf("Verified that deletion succeeded")) - }) - }) -}) - -// verifyNsCascadingDeletion verifies that namespaces are deleted from -// underlying clusters when orphan dependents is false and they are not -// deleted when orphan dependents is true. -func verifyNsCascadingDeletion(nsClient clientset.NamespaceInterface, clusters fedframework.ClusterSlice, orphanDependents *bool) string { - nsName := createNamespace(nsClient) - // Check subclusters if the namespace was created there. - By(fmt.Sprintf("Waiting for namespace %s to be created in all underlying clusters", nsName)) - err := wait.Poll(5*time.Second, 2*time.Minute, func() (bool, error) { - for _, cluster := range clusters { - _, err := cluster.CoreV1().Namespaces().Get(nsName, metav1.GetOptions{}) - if err != nil && !errors.IsNotFound(err) { - return false, err - } - if err != nil { - return false, nil - } - } - return true, nil - }) - framework.ExpectNoError(err, "Not all namespaces created") - - By(fmt.Sprintf("Deleting namespace %s", nsName)) - deleteNamespace(orphanDependents, nsName, nsClient.Get, nsClient.Delete) - - By(fmt.Sprintf("Verifying namespaces %s in underlying clusters", nsName)) - errMessages := []string{} - // namespace should be present in underlying clusters unless orphanDependents is false. - shouldExist := orphanDependents == nil || *orphanDependents == true - for _, cluster := range clusters { - clusterName := cluster.Name - _, err := cluster.CoreV1().Namespaces().Get(nsName, metav1.GetOptions{}) - if shouldExist && errors.IsNotFound(err) { - errMessages = append(errMessages, fmt.Sprintf("unexpected NotFound error for namespace %s in cluster %s, expected namespace to exist", nsName, clusterName)) - } else if !shouldExist && !errors.IsNotFound(err) { - errMessages = append(errMessages, fmt.Sprintf("expected NotFound error for namespace %s in cluster %s, got error: %v", nsName, clusterName, err)) - } - } - if len(errMessages) != 0 { - framework.Failf("%s", strings.Join(errMessages, "; ")) - } - return nsName -} - -func createNamespace(nsClient clientset.NamespaceInterface) string { - ns := v1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: names.SimpleNameGenerator.GenerateName(namespacePrefix), - }, - } - By(fmt.Sprintf("Creating namespace %s", ns.Name)) - _, err := nsClient.Create(&ns) - framework.ExpectNoError(err, "Failed to create namespace %s", ns.Name) - By(fmt.Sprintf("Created namespace %s", ns.Name)) - return ns.Name -} - -func deleteNamespace(orphanDependents *bool, namespace string, getter func(name string, options metav1.GetOptions) (*v1.Namespace, error), deleter func(string, *metav1.DeleteOptions) error) { - By(fmt.Sprintf("Deleting namespace: %s", namespace)) - err := deleter(namespace, &metav1.DeleteOptions{OrphanDependents: orphanDependents}) - if errors.IsNotFound(err) { - return - } else if err != nil { - framework.Failf("Failed to set %s for deletion: %v", namespace, err) - } - waitForNamespaceDeletion(namespace, getter) -} - -func waitForNamespaceDeletion(namespace string, getter func(name string, options metav1.GetOptions) (*v1.Namespace, error)) { - err := wait.Poll(5*time.Second, 2*time.Minute, func() (bool, error) { - _, err := getter(namespace, metav1.GetOptions{}) - if errors.IsNotFound(err) { - return true, nil - } else if err != nil { - return false, err - } - return false, nil - }) - if err != nil { - framework.Failf("Namespaces not deleted: %v", err) - } -} diff --git a/federation/test/e2e/replicaset.go b/federation/test/e2e/replicaset.go deleted file mode 100644 index 7a307fb95f6..00000000000 --- a/federation/test/e2e/replicaset.go +++ /dev/null @@ -1,461 +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 e2e - -import ( - "encoding/json" - "fmt" - "reflect" - "time" - - "k8s.io/api/core/v1" - "k8s.io/api/extensions/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/wait" - fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - fedutil "k8s.io/kubernetes/federation/pkg/federation-controller/util" - fedframework "k8s.io/kubernetes/federation/test/e2e/framework" - "k8s.io/kubernetes/test/e2e/framework" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/util/uuid" - "k8s.io/kubernetes/federation/apis/federation" - federatedtypes "k8s.io/kubernetes/federation/pkg/federatedtypes" -) - -const ( - FederationReplicaSetPrefix = "federation-replicaset-" -) - -// Create/delete replicaset api objects -var _ = framework.KubeDescribe("Federated ReplicaSet [Feature:Federation]", func() { - f := fedframework.NewDefaultFederatedFramework("federation-replicaset") - - Describe("ReplicaSet objects [NoCluster]", func() { - AfterEach(func() { - fedframework.SkipUnlessFederated(f.ClientSet) - - // Delete all replicasets. - nsName := f.FederationNamespace.Name - deleteAllReplicaSetsOrFail(f.FederationClientset, nsName) - }) - - It("should be created and deleted successfully", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - - nsName := f.FederationNamespace.Name - rs := createReplicaSetOrFail(f.FederationClientset, newReplicaSet(nsName, FederationReplicaSetPrefix, 5, nil)) - By(fmt.Sprintf("Creation of replicaset %q in namespace %q succeeded. Deleting replicaset.", rs.Name, nsName)) - // Cleanup - err := f.FederationClientset.Extensions().ReplicaSets(nsName).Delete(rs.Name, &metav1.DeleteOptions{}) - framework.ExpectNoError(err, "Error deleting replicaset %q in namespace %q", rs.Name, rs.Namespace) - By(fmt.Sprintf("Deletion of replicaset %q in namespace %q succeeded.", rs.Name, nsName)) - }) - - }) - - // e2e cases for federated replicaset controller - Describe("Features", func() { - var ( - clusters fedframework.ClusterSlice - ) - - BeforeEach(func() { - fedframework.SkipUnlessFederated(f.ClientSet) - clusters = f.GetRegisteredClusters() - }) - - // e2e cases for federated replicaset controller - Describe("Preferences", func() { - var ( - rs *v1beta1.ReplicaSet - ) - - AfterEach(func() { - // Delete all replicasets. - nsName := f.FederationNamespace.Name - if rs != nil { - orphanDependents := false - By(fmt.Sprintf("Deleting replicaset \"%s/%s\"", nsName, rs.Name)) - deleteReplicaSetOrFail(f.FederationClientset, nsName, rs.Name, &orphanDependents) - rs = nil - } - }) - - It("should create replicasets with weight preference", func() { - pref, replicas, expect := generateFedRSPrefsWithWeight(clusters) - rs = createAndUpdateFedRSWithPref(f.FederationClientset, f.FederationNamespace.Name, clusters, pref, replicas, expect) - }) - - It("should create replicasets with min replicas preference", func() { - pref, replicas, expect := generateFedRSPrefsWithMin(clusters) - rs = createAndUpdateFedRSWithPref(f.FederationClientset, f.FederationNamespace.Name, clusters, pref, replicas, expect) - }) - - It("should create replicasets with max replicas preference", func() { - pref, replicas, expect := generateFedRSPrefsWithMax(clusters) - rs = createAndUpdateFedRSWithPref(f.FederationClientset, f.FederationNamespace.Name, clusters, pref, replicas, expect) - }) - - // test for rebalancing - PIt("should create replicasets and rebalance them", func() { - nsName := f.FederationNamespace.Name - pref1, pref2, replicas, expect1, expect2 := generateFedRSPrefsForRebalancing(clusters) - - By("Testing replicaset rebalancing") - framework.Logf("Replicas: %d", replicas) - framework.Logf("Preference 1: %#v", pref1) - framework.Logf("Preference 2: %#v", pref2) - - rs = newReplicaSet(nsName, FederationReplicaSetPrefix, replicas, pref1) - rs = createReplicaSetOrFail(f.FederationClientset, rs) - waitForReplicaSetOrFail(f.FederationClientset, nsName, rs.Name, clusters, expect1) - By(fmt.Sprintf("Successfully created and synced replicaset \"%s/%s\" (%v/%v) to clusters", nsName, rs.Name, rs.Status.Replicas, *rs.Spec.Replicas)) - - rs = newReplicaSetWithName(nsName, rs.Name, replicas, pref2) - updateReplicaSetOrFail(f.FederationClientset, rs) - waitForReplicaSetOrFail(f.FederationClientset, nsName, rs.Name, clusters, expect1) - By(fmt.Sprintf("Successfully updated and synced replicaset \"%s/%s\" (%v/%v) to clusters", nsName, rs.Name, rs.Status.Replicas, *rs.Spec.Replicas)) - - pref2 = updateFedRSPrefsRebalance(pref2, true) - rs = newReplicaSetWithName(nsName, rs.Name, replicas, pref2) - updateReplicaSetOrFail(f.FederationClientset, rs) - waitForReplicaSetOrFail(f.FederationClientset, nsName, rs.Name, clusters, expect2) - By(fmt.Sprintf("Successfully updated and synced replicaset \"%s/%s\" (%v/%v) to clusters", nsName, rs.Name, rs.Status.Replicas, *rs.Spec.Replicas)) - }) - }) - }) -}) - -func createAndWaitForReplicasetOrFail(clientset *fedclientset.Clientset, nsName string, clusters fedframework.ClusterSlice) *v1beta1.ReplicaSet { - rs := createReplicaSetOrFail(clientset, newReplicaSet(nsName, FederationReplicaSetPrefix, 5, nil)) - // Check subclusters if the replicaSet was created there. - By(fmt.Sprintf("Waiting for replica sets %s to be created in all underlying clusters", rs.Name)) - err := wait.Poll(5*time.Second, 2*time.Minute, func() (bool, error) { - for _, cluster := range clusters { - _, err := cluster.Extensions().ReplicaSets(nsName).Get(rs.Name, metav1.GetOptions{}) - if err != nil && errors.IsNotFound(err) { - return false, nil - } - if err != nil { - return false, err - } - } - return true, nil - }) - framework.ExpectNoError(err, "Not all replica sets created") - return rs -} - -func createAndUpdateFedRSWithPref(clientset *fedclientset.Clientset, nsName string, clusters fedframework.ClusterSlice, pref *federation.ReplicaAllocationPreferences, replicas int32, expect map[string]int32) *v1beta1.ReplicaSet { - framework.Logf("Replicas: %d, Preference: %#v", replicas, pref) - rs := newReplicaSet(nsName, FederationReplicaSetPrefix, replicas, pref) - rs = createReplicaSetOrFail(clientset, rs) - - waitForReplicaSetOrFail(clientset, nsName, rs.Name, clusters, expect) - By(fmt.Sprintf("Successfully created and synced replicaset \"%s/%s\" (%v/%v) to clusters", nsName, rs.Name, rs.Status.Replicas, *rs.Spec.Replicas)) - - rs = newReplicaSetWithName(nsName, rs.Name, 0, pref) - updateReplicaSetOrFail(clientset, rs) - waitForReplicaSetOrFail(clientset, nsName, rs.Name, clusters, nil) - By(fmt.Sprintf("Successfully updated and synced replicaset \"%s/%s\" (%v/%v) to clusters", nsName, rs.Name, rs.Status.Replicas, *rs.Spec.Replicas)) - - rs = newReplicaSetWithName(nsName, rs.Name, replicas, pref) - updateReplicaSetOrFail(clientset, rs) - waitForReplicaSetOrFail(clientset, nsName, rs.Name, clusters, expect) - By(fmt.Sprintf("Successfully updated and synced replicaset \"%s/%s\" (%v/%v) to clusters", nsName, rs.Name, rs.Status.Replicas, *rs.Spec.Replicas)) - - return rs -} - -// deleteAllReplicaSetsOrFail deletes all replicasets in the given namespace name. -func deleteAllReplicaSetsOrFail(clientset *fedclientset.Clientset, nsName string) { - replicasetList, err := clientset.Extensions().ReplicaSets(nsName).List(metav1.ListOptions{}) - Expect(err).NotTo(HaveOccurred()) - orphanDependents := false - for _, replicaset := range replicasetList.Items { - deleteReplicaSetOrFail(clientset, nsName, replicaset.Name, &orphanDependents) - } -} - -func generateFedRSPrefsWithWeight(clusters fedframework.ClusterSlice) (pref *federation.ReplicaAllocationPreferences, replicas int32, expect map[string]int32) { - By("Generating replicaset preferences with weights") - clusterNames := extractClusterNames(clusters) - pref = &federation.ReplicaAllocationPreferences{ - Clusters: map[string]federation.ClusterPreferences{}, - } - replicas = 0 - expect = map[string]int32{} - - for i, clusterName := range clusterNames { - if i != 0 { // do not set weight for cluster[0] thus it should have no replicas scheduled - pref.Clusters[clusterName] = federation.ClusterPreferences{ - Weight: int64(i), - } - replicas += int32(i) - expect[clusterName] = int32(i) - } - } - return -} - -func generateFedRSPrefsWithMin(clusters fedframework.ClusterSlice) (pref *federation.ReplicaAllocationPreferences, replicas int32, expect map[string]int32) { - By("Generating replicaset preferences with min replicas") - clusterNames := extractClusterNames(clusters) - pref = &federation.ReplicaAllocationPreferences{ - Clusters: map[string]federation.ClusterPreferences{ - clusterNames[0]: {Weight: 100}, - }, - } - replicas = 0 - expect = map[string]int32{} - - for i, clusterName := range clusterNames { - if i != 0 { // do not set weight and minReplicas for cluster[0] thus it should have no replicas scheduled - pref.Clusters[clusterName] = federation.ClusterPreferences{ - Weight: int64(1), - MinReplicas: int64(i + 2), - } - replicas += int32(i + 2) - expect[clusterName] = int32(i + 2) - } - } - // the extra replica goes to cluster[0] which has the highest weight - replicas += 1 - expect[clusterNames[0]] = 1 - return -} - -func generateFedRSPrefsWithMax(clusters fedframework.ClusterSlice) (pref *federation.ReplicaAllocationPreferences, replicas int32, expect map[string]int32) { - By("Generating replicaset preferences with max replicas") - clusterNames := extractClusterNames(clusters) - pref = &federation.ReplicaAllocationPreferences{ - Clusters: map[string]federation.ClusterPreferences{ - clusterNames[0]: {Weight: 1}, - }, - } - replicas = 0 - expect = map[string]int32{} - - for i, clusterName := range clusterNames { - if i != 0 { // do not set maxReplicas for cluster[0] thus replicas exceeds the total maxReplicas turned to cluster[0] - maxReplicas := int64(i) - pref.Clusters[clusterName] = federation.ClusterPreferences{ - Weight: int64(100), - MaxReplicas: &maxReplicas, - } - replicas += int32(i) - expect[clusterName] = int32(i) - } - } - // extra replicas go to cluster[0] although it has the lowest weight as others hit the MaxReplicas - replicas += 5 - expect[clusterNames[0]] = 5 - return -} - -func updateFedRSPrefsRebalance(pref *federation.ReplicaAllocationPreferences, rebalance bool) *federation.ReplicaAllocationPreferences { - pref.Rebalance = rebalance - return pref -} - -func generateFedRSPrefsForRebalancing(clusters fedframework.ClusterSlice) (pref1, pref2 *federation.ReplicaAllocationPreferences, replicas int32, expect1, expect2 map[string]int32) { - By("Generating replicaset for rebalancing") - clusterNames := extractClusterNames(clusters) - replicas = 3 - - pref1 = &federation.ReplicaAllocationPreferences{ - Clusters: map[string]federation.ClusterPreferences{ - clusterNames[0]: {Weight: 1}, - clusterNames[1]: {Weight: 2}, - }, - } - expect1 = map[string]int32{ - clusterNames[0]: 1, - clusterNames[1]: 2, - } - pref2 = &federation.ReplicaAllocationPreferences{ - Clusters: map[string]federation.ClusterPreferences{ - clusterNames[0]: {Weight: 2}, - clusterNames[1]: {Weight: 1}, - }, - } - expect2 = map[string]int32{ - clusterNames[0]: 2, - clusterNames[1]: 1, - } - return -} - -func waitForReplicaSetOrFail(c *fedclientset.Clientset, namespace string, replicaSetName string, clusters fedframework.ClusterSlice, expect map[string]int32) { - err := waitForReplicaSet(c, namespace, replicaSetName, clusters, expect) - framework.ExpectNoError(err, "Failed to verify replica set \"%s/%s\", err: %v", namespace, replicaSetName, err) -} - -func waitForReplicaSet(c *fedclientset.Clientset, namespace string, replicaSetName string, clusters fedframework.ClusterSlice, expect map[string]int32) error { - framework.Logf("waitForReplicaSet: %s/%s; clusters: %v; expect: %v", namespace, replicaSetName, clusters, expect) - err := wait.Poll(10*time.Second, fedframework.FederatedDefaultTestTimeout, func() (bool, error) { - frs, err := c.ExtensionsV1beta1().ReplicaSets(namespace).Get(replicaSetName, metav1.GetOptions{}) - if err != nil { - return false, err - } - specReplicas, statusReplicas := int32(0), int32(0) - for _, cluster := range clusters { - // TODO: switch to use AppsV1beta2 ReplicaSet when apps/v1beta2 is enabled by default - rs, err := cluster.ExtensionsV1beta1().ReplicaSets(namespace).Get(replicaSetName, metav1.GetOptions{}) - if err != nil && !errors.IsNotFound(err) { - framework.Logf("Failed getting replicaset: \"%s/%s/%s\", err: %v", cluster.Name, namespace, replicaSetName, err) - return false, err - } - if errors.IsNotFound(err) { - if expect != nil && expect[cluster.Name] > 0 { - framework.Logf("Replicaset \"%s/%s/%s\" with replica count %d does not exist", cluster.Name, namespace, replicaSetName, expect[cluster.Name]) - return false, nil - } - } else { - if !equivalentReplicaSet(frs, rs) { - framework.Logf("Replicaset meta or spec does not match for cluster %q:\n federation: %v\n cluster: %v", cluster.Name, frs, rs) - return false, nil - } - if expect != nil && *rs.Spec.Replicas < expect[cluster.Name] { - framework.Logf("Replicas do not match for \"%s/%s/%s\": expected: >= %v, actual: %v", cluster.Name, namespace, replicaSetName, expect[cluster.Name], *rs.Spec.Replicas) - return false, nil - } - specReplicas += *rs.Spec.Replicas - statusReplicas += rs.Status.Replicas - } - } - if *frs.Spec.Replicas == 0 && frs.Status.Replicas != 0 { - framework.Logf("ReplicaSet \"%s/%s\" with zero replicas should match the status as no overflow happens: expected: 0, actual: %v", namespace, replicaSetName, frs.Status.Replicas) - return false, nil - } - if statusReplicas == frs.Status.Replicas && specReplicas >= *frs.Spec.Replicas { - return true, nil - } - framework.Logf("Replicas do not match, federation replicas: %v/%v, cluster replicas: %v/%v", frs.Status.Replicas, *frs.Spec.Replicas, statusReplicas, specReplicas) - return false, nil - }) - - return err -} - -func equivalentReplicaSet(fedReplicaSet, localReplicaSet *v1beta1.ReplicaSet) bool { - localReplicaSetSpec := localReplicaSet.Spec - localReplicaSetSpec.Replicas = fedReplicaSet.Spec.Replicas - return fedutil.ObjectMetaEquivalent(fedReplicaSet.ObjectMeta, localReplicaSet.ObjectMeta) && - reflect.DeepEqual(fedReplicaSet.Spec, localReplicaSetSpec) -} - -func createReplicaSetOrFail(clientset *fedclientset.Clientset, replicaset *v1beta1.ReplicaSet) *v1beta1.ReplicaSet { - namespace := replicaset.Namespace - if clientset == nil || len(namespace) == 0 { - Fail(fmt.Sprintf("Internal error: invalid parameters passed to createReplicaSetOrFail: clientset: %v, namespace: %v", clientset, namespace)) - } - By(fmt.Sprintf("Creating federation replicaset %q in namespace %q", replicaset.Name, namespace)) - - newRS, err := clientset.Extensions().ReplicaSets(namespace).Create(replicaset) - framework.ExpectNoError(err, "Creating replicaset %q in namespace %q", replicaset.Name, namespace) - By(fmt.Sprintf("Successfully created federation replicaset %q in namespace %q", newRS.Name, namespace)) - return newRS -} - -func deleteReplicaSetOrFail(clientset *fedclientset.Clientset, nsName string, replicaSetName string, orphanDependents *bool) { - By(fmt.Sprintf("Deleting replica set %q in namespace %q", replicaSetName, nsName)) - err := clientset.Extensions().ReplicaSets(nsName).Delete(replicaSetName, &metav1.DeleteOptions{OrphanDependents: orphanDependents}) - if err != nil && !errors.IsNotFound(err) { - framework.ExpectNoError(err, "Error deleting replica set %q in namespace %q", replicaSetName, nsName) - } - - waitForReplicaSetToBeDeletedOrFail(clientset, nsName, replicaSetName) -} - -func updateReplicaSetOrFail(clientset *fedclientset.Clientset, replicaset *v1beta1.ReplicaSet) *v1beta1.ReplicaSet { - namespace := replicaset.Namespace - if clientset == nil || len(namespace) == 0 { - Fail(fmt.Sprintf("Internal error: invalid parameters passed to updateReplicaSetOrFail: clientset: %v, namespace: %v", clientset, namespace)) - } - By(fmt.Sprintf("Updating federation replicaset %q in namespace %q", replicaset.Name, namespace)) - - newRS, err := clientset.ExtensionsV1beta1().ReplicaSets(namespace).Update(replicaset) - framework.ExpectNoError(err, "Updating replicaset %q in namespace %q", replicaset.Name, namespace) - By(fmt.Sprintf("Successfully updated federation replicaset %q in namespace %q", replicaset.Name, namespace)) - - return newRS -} - -func newReplicaSetObj(namespace string, replicas int32, pref *federation.ReplicaAllocationPreferences) *v1beta1.ReplicaSet { - // When the tests are run in parallel, replicasets from different tests can - // collide with each other. Prevent that by creating a unique label and - // label selector for each created replica set. - uuidString := string(uuid.NewUUID()) - rsLabel := fmt.Sprintf("myrs-%s", uuidString) - - rs := &v1beta1.ReplicaSet{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Annotations: map[string]string{}, - }, - Spec: v1beta1.ReplicaSetSpec{ - Replicas: &replicas, - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"name": rsLabel}, - }, - Template: v1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{"name": rsLabel}, - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: "nginx", - Image: "nginx", - }, - }, - }, - }, - }, - } - if pref != nil { - prefBytes, _ := json.Marshal(pref) - prefString := string(prefBytes) - rs.Annotations[federatedtypes.FedReplicaSetPreferencesAnnotation] = prefString - } - return rs - -} - -func newReplicaSet(namespace string, prefix string, replicas int32, pref *federation.ReplicaAllocationPreferences) *v1beta1.ReplicaSet { - rs := newReplicaSetObj(namespace, replicas, pref) - rs.GenerateName = prefix - return rs -} - -func newReplicaSetWithName(namespace string, name string, replicas int32, pref *federation.ReplicaAllocationPreferences) *v1beta1.ReplicaSet { - rs := newReplicaSetObj(namespace, replicas, pref) - rs.Name = name - return rs -} - -func extractClusterNames(clusters fedframework.ClusterSlice) []string { - clusterNames := make([]string, 0, len(clusters)) - for _, cluster := range clusters { - clusterNames = append(clusterNames, cluster.Name) - } - return clusterNames -} diff --git a/federation/test/e2e/service.go b/federation/test/e2e/service.go deleted file mode 100644 index 5703e9be3d2..00000000000 --- a/federation/test/e2e/service.go +++ /dev/null @@ -1,371 +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 e2e - -import ( - "fmt" - "os" - "reflect" - "strconv" - "strings" - "time" - - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/wait" - fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - fedframework "k8s.io/kubernetes/federation/test/e2e/framework" - "k8s.io/kubernetes/test/e2e/framework" - - . "github.com/onsi/ginkgo" -) - -const ( - FederatedServiceName = "federated-service" - FederatedServicePodName = "federated-service-test-pod" -) - -var FederatedServiceLabels = map[string]string{ - "foo": "bar", -} - -var _ = framework.KubeDescribe("Federated Services [Feature:Federation]", func() { - f := fedframework.NewDefaultFederatedFramework("federated-service") - var clusters fedframework.ClusterSlice - var federationName string - - var _ = Describe("Without Clusters [NoCluster]", func() { - BeforeEach(func() { - fedframework.SkipUnlessFederated(f.ClientSet) - // Placeholder - }) - - AfterEach(func() { - fedframework.SkipUnlessFederated(f.ClientSet) - }) - - It("should succeed when a service is created", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - - nsName := f.FederationNamespace.Name - service := createServiceOrFail(f.FederationClientset, nsName, FederatedServiceName) - By(fmt.Sprintf("Creation of service %q in namespace %q succeeded. Deleting service.", service.Name, nsName)) - - // Cleanup - err := f.FederationClientset.CoreV1().Services(nsName).Delete(service.Name, &metav1.DeleteOptions{}) - framework.ExpectNoError(err, "Error deleting service %q in namespace %q", service.Name, service.Namespace) - By(fmt.Sprintf("Deletion of service %q in namespace %q succeeded.", service.Name, nsName)) - }) - }) - - var _ = Describe("with clusters", func() { - BeforeEach(func() { - fedframework.SkipUnlessFederated(f.ClientSet) - - // TODO: Federation API server should be able to answer this. - if federationName = os.Getenv("FEDERATION_NAME"); federationName == "" { - federationName = DefaultFederationName - } - - clusters = f.GetRegisteredClusters() - }) - - Describe("Federated Service", func() { - var ( - service *v1.Service - nsName string - ) - - BeforeEach(func() { - fedframework.SkipUnlessFederated(f.ClientSet) - nsName = f.FederationNamespace.Name - }) - - AfterEach(func() { - fedframework.SkipUnlessFederated(f.ClientSet) - - if service != nil { - By(fmt.Sprintf("Deleting service shards and their provider resources in underlying clusters for service %q in namespace %q", service.Name, nsName)) - cleanupServiceShardsAndProviderResources(nsName, service, clusters) - service = nil - nsName = "" - } - }) - - It("should create and update matching services in underlying clusters", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - service = createServiceOrFail(f.FederationClientset, nsName, FederatedServiceName) - defer func() { // Cleanup - By(fmt.Sprintf("Deleting service %q in namespace %q", service.Name, nsName)) - err := f.FederationClientset.CoreV1().Services(nsName).Delete(service.Name, &metav1.DeleteOptions{}) - framework.ExpectNoError(err, "Error deleting service %q in namespace %q", service.Name, nsName) - }() - By(fmt.Sprintf("Wait for service shards to be created in all clusters for service \"%s/%s\"", nsName, service.Name)) - waitForServiceShardsOrFail(nsName, service, clusters) - framework.Logf("Successfully created and synced service \"%s/%s\" to all clusters", nsName, service.Name) - By(fmt.Sprintf("Update federated service \"%s/%s\"", nsName, service.Name)) - service = updateServiceOrFail(f.FederationClientset, nsName, FederatedServiceName) - waitForServiceShardsOrFail(nsName, service, clusters) - framework.Logf("Successfully updated and synced service \"%s/%s\" to clusters", nsName, service.Name) - }) - - It("should be deleted from underlying clusters when OrphanDependents is false", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - orphanDependents := false - verifyCascadingDeletionForService(f.FederationClientset, clusters, &orphanDependents, nsName) - By(fmt.Sprintf("Verified that services were deleted from underlying clusters")) - }) - - It("should not be deleted from underlying clusters when OrphanDependents is true", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - orphanDependents := true - verifyCascadingDeletionForService(f.FederationClientset, clusters, &orphanDependents, nsName) - By(fmt.Sprintf("Verified that services were not deleted from underlying clusters")) - }) - - It("should not be deleted from underlying clusters when OrphanDependents is nil", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - verifyCascadingDeletionForService(f.FederationClientset, clusters, nil, nsName) - By(fmt.Sprintf("Verified that services were not deleted from underlying clusters")) - }) - - It("should recreate service shard in underlying clusters when service shard is deleted", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - service = createServiceOrFail(f.FederationClientset, nsName, FederatedServiceName) - defer func() { - // Cleanup - By(fmt.Sprintf("Deleting service %q in namespace %q", service.Name, nsName)) - err := f.FederationClientset.CoreV1().Services(nsName).Delete(service.Name, &metav1.DeleteOptions{}) - framework.ExpectNoError(err, "Error deleting service %q in namespace %q", service.Name, nsName) - }() - By(fmt.Sprintf("Wait for service shards to be created in all clusters for service \"%s/%s\"", nsName, service.Name)) - waitForServiceShardsOrFail(nsName, service, clusters) - framework.Logf("Successfully created and synced service \"%s/%s\" to all clusters", nsName, service.Name) - - By(fmt.Sprintf("Deleting a service shard in one underlying cluster")) - primaryClusterName := clusters[0].Name - err := deleteServiceShard(clusters[0], nsName, FederatedServiceName) - framework.ExpectNoError(err, fmt.Sprintf("while deleting service shard %q in cluster %q", FederatedServiceName, primaryClusterName)) - - waitForServiceShardsOrFail(nsName, service, clusters) - framework.Logf("Successfully recreated service shard \"%s/%s\" in %q cluster", nsName, service.Name, primaryClusterName) - }) - }) - - var _ = Describe("DNS", func() { - - var ( - service *v1.Service - serviceShard *v1.Service - backendPods BackendPodMap - ) - - BeforeEach(func() { - fedframework.SkipUnlessFederated(f.ClientSet) - - nsName := f.FederationNamespace.Name - - backendPods = createBackendPodsOrFail(clusters, nsName, FederatedServicePodName) - - service = createLBServiceOrFail(f.FederationClientset, nsName, FederatedServiceName, clusters) - serviceShard := service.DeepCopy() - - waitForServiceShardsOrFail(nsName, serviceShard, clusters) - }) - - AfterEach(func() { - fedframework.SkipUnlessFederated(f.ClientSet) - - nsName := f.FederationNamespace.Name - deleteBackendPodsOrFail(clusters, backendPods) - backendPods = nil - - if service != nil { - deleteServiceOrFail(f.FederationClientset, nsName, service.Name, nil) - service = nil - } else { - By("No service to delete. Service is nil") - } - - if serviceShard != nil { - By(fmt.Sprintf("Deleting service shards and their provider resources in underlying clusters for service %q in namespace %q", serviceShard.Name, nsName)) - cleanupServiceShardsAndProviderResources(nsName, serviceShard, clusters) - serviceShard = nil - } else { - By("No service shards to delete. `serviceShard` is nil") - } - }) - - It("should be able to discover a federated service", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - - nsName := f.FederationNamespace.Name - svcDNSNames := []string{ - FederatedServiceName, - fmt.Sprintf("%s.%s", FederatedServiceName, nsName), - fmt.Sprintf("%s.%s.svc.cluster.local.", FederatedServiceName, nsName), - fmt.Sprintf("%s.%s.%s", FederatedServiceName, nsName, federationName), - fmt.Sprintf("%s.%s.%s.svc.cluster.local.", FederatedServiceName, nsName, federationName), - } - // TODO(mml): This could be much faster. We can launch all the test - // pods, perhaps in the BeforeEach, and then just poll until we get - // successes/failures from them all. - for i, DNSName := range svcDNSNames { - discoverService(f, DNSName, true, "federated-service-e2e-discovery-pod-"+strconv.Itoa(i)) - } - By("Verified that DNS rules are working as expected") - - By("Deleting the service to verify that DNS rules still work") - err := f.FederationClientset.CoreV1().Services(nsName).Delete(FederatedServiceName, &metav1.DeleteOptions{}) - framework.ExpectNoError(err, "Error deleting service %q in namespace %q", service.Name, service.Namespace) - // Service is deleted, unset the test block-global service variable. - service = nil - - for i, DNSName := range svcDNSNames { - discoverService(f, DNSName, true, "federated-service-e2e-discovery-pod-"+strconv.Itoa(i)) - } - By("Verified that deleting the service does not affect DNS records") - }) - - Context("non-local federated service", func() { - BeforeEach(func() { - fedframework.SkipUnlessFederated(f.ClientSet) - - // Delete the backend pod from the shard which is local to the discovery pod. - primaryCluster := clusters[0] - backendPod := backendPods[primaryCluster.Name] - deleteOneBackendPodOrFail(primaryCluster, backendPod) - - }) - - PIt("should be able to discover a non-local federated service", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - - nsName := f.FederationNamespace.Name - svcDNSNames := []string{ - fmt.Sprintf("%s.%s.%s", FederatedServiceName, nsName, federationName), - fmt.Sprintf("%s.%s.%s.svc.cluster.local.", FederatedServiceName, nsName, federationName), - } - for i, name := range svcDNSNames { - discoverService(f, name, true, "federated-service-e2e-discovery-pod-"+strconv.Itoa(i)) - } - }) - - // TODO(mml): This currently takes 9 minutes. Consider reducing the - // TTL and/or running the pods in parallel. - Context("[Slow] missing local service", func() { - It("should never find DNS entries for a missing local service", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - - nsName := f.FederationNamespace.Name - localSvcDNSNames := []string{ - FederatedServiceName, - fmt.Sprintf("%s.%s", FederatedServiceName, nsName), - fmt.Sprintf("%s.%s.svc.cluster.local.", FederatedServiceName, nsName), - } - for i, name := range localSvcDNSNames { - discoverService(f, name, false, "federated-service-e2e-discovery-pod-"+strconv.Itoa(i)) - } - }) - }) - }) - }) - }) -}) - -// verifyCascadingDeletionForService verifies that services are deleted from -// underlying clusters when orphan dependents is false and they are not -// deleted when orphan dependents is true. -func verifyCascadingDeletionForService(clientset *fedclientset.Clientset, clusters fedframework.ClusterSlice, orphanDependents *bool, nsName string) { - service := createServiceOrFail(clientset, nsName, FederatedServiceName) - serviceName := service.Name - // Check subclusters if the service was created there. - By(fmt.Sprintf("Waiting for service %s to be created in all underlying clusters", serviceName)) - err := wait.Poll(5*time.Second, 2*time.Minute, func() (bool, error) { - for _, cluster := range clusters { - _, err := cluster.CoreV1().Services(nsName).Get(serviceName, metav1.GetOptions{}) - if err != nil { - if !errors.IsNotFound(err) { - return false, err - } - return false, nil - } - } - return true, nil - }) - framework.ExpectNoError(err, "Not all services created") - - By(fmt.Sprintf("Deleting service %s", serviceName)) - deleteServiceOrFail(clientset, nsName, serviceName, orphanDependents) - - By(fmt.Sprintf("Verifying services %s in underlying clusters", serviceName)) - errMessages := []string{} - // service should be present in underlying clusters unless orphanDependents is false. - shouldExist := orphanDependents == nil || *orphanDependents == true - for _, cluster := range clusters { - clusterName := cluster.Name - _, err := cluster.CoreV1().Services(nsName).Get(serviceName, metav1.GetOptions{}) - if shouldExist && errors.IsNotFound(err) { - errMessages = append(errMessages, fmt.Sprintf("unexpected NotFound error for service %s in cluster %s, expected service to exist", serviceName, clusterName)) - } else if !shouldExist && !errors.IsNotFound(err) { - errMessages = append(errMessages, fmt.Sprintf("expected NotFound error for service %s in cluster %s, got error: %v", serviceName, clusterName, err)) - } - } - if len(errMessages) != 0 { - framework.Failf("%s", strings.Join(errMessages, "; ")) - } -} - -func updateServiceOrFail(clientset *fedclientset.Clientset, namespace, name string) *v1.Service { - service, err := clientset.CoreV1().Services(namespace).Get(name, metav1.GetOptions{}) - framework.ExpectNoError(err, "Getting service %q in namespace %q", name, namespace) - service.Spec.Selector["name"] = "update-demo" - newService, err := clientset.CoreV1().Services(namespace).Update(service) - By(fmt.Sprintf("Successfully updated federated service %q in namespace %q", name, namespace)) - return newService -} - -func deleteServiceShard(c *fedframework.Cluster, namespace, service string) error { - err := c.Clientset.CoreV1().Services(namespace).Delete(service, &metav1.DeleteOptions{}) - if err != nil && !errors.IsNotFound(err) { - framework.Logf("Failed to delete service %q in namespace %q, in cluster %q", service, namespace, c.Name) - return err - } - By(fmt.Sprintf("Service %q in namespace %q in cluster %q deleted", service, namespace, c.Name)) - return nil -} - -// equivalent returns true if the two services are equivalent. Fields which are expected to differ between -// federated services and the underlying cluster services (e.g. ClusterIP, NodePort) are ignored. -func equivalent(federationService, clusterService v1.Service) bool { - clusterService.Spec.ClusterIP = federationService.Spec.ClusterIP - for i := range clusterService.Spec.Ports { - clusterService.Spec.Ports[i].NodePort = federationService.Spec.Ports[i].NodePort - } - - if federationService.Name != clusterService.Name || federationService.Namespace != clusterService.Namespace { - return false - } - if !reflect.DeepEqual(federationService.Labels, clusterService.Labels) && (len(federationService.Labels) != 0 || len(clusterService.Labels) != 0) { - return false - } - if !reflect.DeepEqual(federationService.Spec, clusterService.Spec) { - return false - } - return true -} diff --git a/federation/test/e2e/upgrade.go b/federation/test/e2e/upgrade.go deleted file mode 100644 index e7606862100..00000000000 --- a/federation/test/e2e/upgrade.go +++ /dev/null @@ -1,139 +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 e2e - -import ( - fedframework "k8s.io/kubernetes/federation/test/e2e/framework" - "k8s.io/kubernetes/federation/test/e2e/upgrades" - "k8s.io/kubernetes/test/e2e/chaosmonkey" - "k8s.io/kubernetes/test/e2e/framework" - - . "github.com/onsi/ginkgo" -) - -var upgradeTests = upgrades.SimpleUpgradeTests() - -var _ = framework.KubeDescribe("Upgrade [Feature:Upgrade]", func() { - f := fedframework.NewDefaultFederatedFramework("federation-upgrade") - - framework.KubeDescribe("Federation Control Plane upgrade", func() { - It("should maintain a functioning federation [Feature:FCPUpgrade]", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - cm := chaosmonkey.New(func() { - federationControlPlaneUpgrade(f) - }) - for _, t := range upgradeTests { - cm.RegisterInterface(&chaosMonkeyAdapter{ - test: t, - framework: f, - upgradeType: upgrades.FCPUpgrade, - }) - } - cm.Do() - }) - }) - - framework.KubeDescribe("Federated clusters upgrade", func() { - It("should maintain a functioning federation [Feature:FederatedClustersUpgrade]", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - cm := chaosmonkey.New(func() { - federatedClustersUpgrade(f) - }) - for _, t := range upgradeTests { - cm.RegisterInterface(&chaosMonkeyAdapter{ - test: t, - framework: f, - upgradeType: upgrades.FederatedClustersUpgrade, - }) - } - cm.Do() - }) - }) - - framework.KubeDescribe("FCP upgrade followed by federated clusters upgrade", func() { - It("should maintain a functioning federation [Feature:FCPUpgradeFollowedByFederatedClustersUpgrade]", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - cm := chaosmonkey.New(func() { - federationControlPlaneUpgrade(f) - federatedClustersUpgrade(f) - }) - for _, t := range upgradeTests { - cm.RegisterInterface(&chaosMonkeyAdapter{ - test: t, - framework: f, - upgradeType: upgrades.FCPUpgradeFollowedByFederatedClustersUpgrade, - }) - } - cm.Do() - }) - }) - - framework.KubeDescribe("Federated clusters upgrade followed by FCP upgrade", func() { - It("should maintain a functioning federation [Feature:FederatedClustersUpgradeFollowedByFCPUpgrade]", func() { - fedframework.SkipUnlessFederated(f.ClientSet) - cm := chaosmonkey.New(func() { - federatedClustersUpgrade(f) - federationControlPlaneUpgrade(f) - }) - for _, t := range upgradeTests { - cm.RegisterInterface(&chaosMonkeyAdapter{ - test: t, - framework: f, - upgradeType: upgrades.FederatedClustersUpgradeFollowedByFCPUpgrade, - }) - } - cm.Do() - }) - }) -}) - -type chaosMonkeyAdapter struct { - test upgrades.Test - framework *fedframework.Framework - upgradeType upgrades.FederationUpgradeType -} - -func (cma *chaosMonkeyAdapter) Setup() { - cma.test.Setup(cma.framework) -} - -func (cma *chaosMonkeyAdapter) Test(stopCh <-chan struct{}) { - cma.test.Test(cma.framework, stopCh, cma.upgradeType) -} - -func (cma *chaosMonkeyAdapter) Teardown() { - cma.test.Teardown(cma.framework) -} - -func federationControlPlaneUpgrade(f *fedframework.Framework) { - federationVersion, err := framework.RealVersion(framework.TestContext.FederationUpgradeTarget) - framework.ExpectNoError(err) - framework.ExpectNoError(fedframework.FederationControlPlaneUpgrade(federationVersion)) - framework.ExpectNoError(fedframework.CheckFederationVersion(f.FederationClientset, federationVersion)) -} - -func federatedClustersUpgrade(f *fedframework.Framework) { - k8sVersion, err := framework.RealVersion(framework.TestContext.UpgradeTarget) - framework.ExpectNoError(err) - clusters := f.GetRegisteredClusters() - for _, cluster := range clusters { - framework.ExpectNoError(fedframework.MasterUpgrade(cluster.Name, k8sVersion)) - framework.ExpectNoError(framework.CheckMasterVersion(cluster.Clientset, k8sVersion)) - - // TODO: Need to add Node upgrade. Add once this framework is stable - } -} diff --git a/federation/test/e2e/upgrades/BUILD b/federation/test/e2e/upgrades/BUILD deleted file mode 100644 index 0907ed85879..00000000000 --- a/federation/test/e2e/upgrades/BUILD +++ /dev/null @@ -1,35 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "simple.go", - "upgrade.go", - ], - importpath = "k8s.io/kubernetes/federation/test/e2e/upgrades", - deps = [ - "//federation/pkg/federatedtypes:go_default_library", - "//federation/pkg/federatedtypes/crudtester:go_default_library", - "//federation/test/e2e/framework:go_default_library", - "//vendor/github.com/onsi/ginkgo:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/test/e2e/upgrades/simple.go b/federation/test/e2e/upgrades/simple.go deleted file mode 100644 index 880b6edced7..00000000000 --- a/federation/test/e2e/upgrades/simple.go +++ /dev/null @@ -1,72 +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 upgrades - -import ( - "fmt" - - pkgruntime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/federation/pkg/federatedtypes" - crudtester "k8s.io/kubernetes/federation/pkg/federatedtypes/crudtester" - fedframework "k8s.io/kubernetes/federation/test/e2e/framework" - - . "github.com/onsi/ginkgo" -) - -// SimpleUpgradeTest validates that a federated resource remains -// propagated before and after a control plane upgrade -type SimpleUpgradeTest struct { - kind string - adapterFactory federatedtypes.AdapterFactory - crudTester *crudtester.FederatedTypeCRUDTester - obj pkgruntime.Object -} - -// Setup creates a resource and validates its propagation to member clusters -func (ut *SimpleUpgradeTest) Setup(f *fedframework.Framework) { - adapter := ut.adapterFactory(f.FederationClientset, f.FederationConfig, nil) - clients := f.GetClusterClients() - ut.crudTester = fedframework.NewFederatedTypeCRUDTester(adapter, clients) - - By(fmt.Sprintf("Creating a resource of kind %q and validating propagation to member clusters", ut.kind)) - obj := adapter.NewTestObject(f.Namespace.Name) - ut.obj = ut.crudTester.CheckCreate(obj) -} - -// Test validates that a resource remains propagated post-upgrade -func (ut *SimpleUpgradeTest) Test(f *fedframework.Framework, done <-chan struct{}, upgrade FederationUpgradeType) { - <-done - By(fmt.Sprintf("Validating that a resource of kind %q remains propagated to member clusters after upgrade", ut.kind)) - ut.crudTester.CheckPropagation(ut.obj) -} - -// Teardown cleans up remaining resources -func (ut *SimpleUpgradeTest) Teardown(f *fedframework.Framework) { - // Rely on the namespace deletion to clean up everything -} - -// SimpleUpgradeTests collects simple upgrade tests for registered federated types -func SimpleUpgradeTests() []Test { - tests := []Test{} - for kind, fedType := range federatedtypes.FederatedTypes() { - tests = append(tests, &SimpleUpgradeTest{ - kind: kind, - adapterFactory: fedType.AdapterFactory, - }) - } - return tests -} diff --git a/federation/test/e2e/upgrades/upgrade.go b/federation/test/e2e/upgrades/upgrade.go deleted file mode 100644 index e2c9bb911d4..00000000000 --- a/federation/test/e2e/upgrades/upgrade.go +++ /dev/null @@ -1,56 +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 upgrades provides a framework for testing Kubernetes federation -// features before, during, and after different types of upgrades. -package upgrades - -import fedframework "k8s.io/kubernetes/federation/test/e2e/framework" - -// FederationUpgradeType represents different types of federation upgrades. -type FederationUpgradeType int - -const ( - // FCPUpgrade indicates that federation control plane is being upgraded. - FCPUpgrade FederationUpgradeType = iota - - // FederatedClustersUpgrade indicates that federated clusters are being upgraded. - FederatedClustersUpgrade - - // FCPUpgradeFollowedByFederatedClustersUpgrade indicates that federation control plane is upgraded - // followed by federated clusters upgrade. - FCPUpgradeFollowedByFederatedClustersUpgrade - - // FederatedClustersUpgradeFollowedByFCPUpgrade indicates that federated clusters are upgraded - // followed by federation control plane upgrade. - FederatedClustersUpgradeFollowedByFCPUpgrade -) - -// Test is an interface for federation upgrade tests. -type Test interface { - // Setup should create and verify whatever objects need to - // exist before the upgrade disruption starts. - Setup(f *fedframework.Framework) - - // Test will run during the upgrade. When the upgrade is - // complete, done will be closed and final validation can - // begin. - Test(f *fedframework.Framework, done <-chan struct{}, upgrade FederationUpgradeType) - - // TearDown should clean up any objects that are created that - // aren't already cleaned up by the framework. - Teardown(f *fedframework.Framework) -} diff --git a/federation/test/e2e/util.go b/federation/test/e2e/util.go deleted file mode 100644 index f30d039455a..00000000000 --- a/federation/test/e2e/util.go +++ /dev/null @@ -1,513 +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 e2e - -import ( - "fmt" - "time" - - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/apimachinery/pkg/util/rand" - "k8s.io/apimachinery/pkg/util/wait" - kubeclientset "k8s.io/client-go/kubernetes" - federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" - fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - fedframework "k8s.io/kubernetes/federation/test/e2e/framework" - "k8s.io/kubernetes/pkg/cloudprovider" - "k8s.io/kubernetes/test/e2e/common" - "k8s.io/kubernetes/test/e2e/framework" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - imageutils "k8s.io/kubernetes/test/utils/image" -) - -var ( - DefaultFederationName = "e2e-federation" - // We use this to decide how long to wait for our DNS probes to succeed. - DNSTTL = 180 * time.Second // TODO: make k8s.io/kubernetes/federation/pkg/federation-controller/service.minDnsTtl exported, and import it here. -) - -const ( - // [30000, 32767] is the allowed default service nodeport range and our - // tests just use the defaults. - FederatedSvcNodePortFirst = 30000 - FederatedSvcNodePortLast = 32767 -) - -var FederationSuite common.Suite - -func createClusterObjectOrFail(f *fedframework.Framework, context *fedframework.E2EContext, clusterNamePrefix string) { - clusterName := clusterNamePrefix + context.Name - framework.Logf("Creating cluster object: %s (%s, secret: %s)", clusterName, context.Cluster.Cluster.Server, context.Name) - cluster := federationapi.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: clusterName, - }, - Spec: federationapi.ClusterSpec{ - ServerAddressByClientCIDRs: []federationapi.ServerAddressByClientCIDR{ - { - ClientCIDR: "0.0.0.0/0", - ServerAddress: context.Cluster.Cluster.Server, - }, - }, - SecretRef: &v1.LocalObjectReference{ - // Note: Name must correlate with federation build script secret name, - // which currently matches the cluster name. - // See federation/cluster/common.sh:132 - Name: context.Name, - }, - }, - } - if clusterNamePrefix != "" { - cluster.Labels = map[string]string{"prefix": clusterNamePrefix} - } - _, err := f.FederationClientset.Federation().Clusters().Create(&cluster) - framework.ExpectNoError(err, fmt.Sprintf("creating cluster: %+v", err)) - framework.Logf("Successfully created cluster object: %s (%s, secret: %s)", clusterName, context.Cluster.Cluster.Server, context.Name) -} - -// waitForServiceOrFail waits until a service is either present or absent in the cluster specified by clientset. -// If the condition is not met within timout, it fails the calling test. -func waitForServiceOrFail(clientset *kubeclientset.Clientset, namespace string, service *v1.Service, present bool, timeout time.Duration) { - By(fmt.Sprintf("Fetching a federated service shard of service %q in namespace %q from cluster", service.Name, namespace)) - err := wait.PollImmediate(framework.Poll, timeout, func() (bool, error) { - clusterService, err := clientset.CoreV1().Services(namespace).Get(service.Name, metav1.GetOptions{}) - if (!present) && errors.IsNotFound(err) { // We want it gone, and it's gone. - By(fmt.Sprintf("Success: shard of federated service %q in namespace %q in cluster is absent", service.Name, namespace)) - return true, nil // Success - } - if present && err == nil { // We want it present, and the Get succeeded, so we're all good. - if equivalent(*clusterService, *service) { - By(fmt.Sprintf("Success: shard of federated service %q in namespace %q in cluster is present", service.Name, namespace)) - return true, nil // Success - } - return false, nil - } - By(fmt.Sprintf("Service %q in namespace %q in cluster. Found: %v, waiting for Found: %v, trying again in %s (err=%v)", service.Name, namespace, clusterService != nil && err == nil, present, framework.Poll, err)) - return false, nil - }) - framework.ExpectNoError(err, "Failed to verify service %q in namespace %q in cluster: Present=%v", service.Name, namespace, present) -} - -// waitForServiceShardsOrFail waits for the service to appear in all clusters -func waitForServiceShardsOrFail(namespace string, service *v1.Service, clusters fedframework.ClusterSlice) { - framework.Logf("Waiting for service %q in %d clusters", service.Name, len(clusters)) - for _, c := range clusters { - waitForServiceOrFail(c.Clientset, namespace, service, true, fedframework.FederatedDefaultTestTimeout) - } -} - -func createService(clientset *fedclientset.Clientset, namespace, name string) (*v1.Service, error) { - if clientset == nil || len(namespace) == 0 { - return nil, fmt.Errorf("Internal error: invalid parameters passed to createService: clientset: %v, namespace: %v", clientset, namespace) - } - By(fmt.Sprintf("Creating federated service %q in namespace %q", name, namespace)) - - service := &v1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: v1.ServiceSpec{ - Selector: FederatedServiceLabels, - Type: v1.ServiceTypeClusterIP, - Ports: []v1.ServicePort{ - { - Name: "http", - Protocol: v1.ProtocolTCP, - Port: 80, - TargetPort: intstr.FromInt(8080), - }, - }, - SessionAffinity: v1.ServiceAffinityNone, - }, - } - - By(fmt.Sprintf("Trying to create service %q in namespace %q", service.Name, namespace)) - return clientset.CoreV1().Services(namespace).Create(service) -} - -func createLBService(clientset *fedclientset.Clientset, namespace, name string, clusters fedframework.ClusterSlice) (*v1.Service, error) { - if clientset == nil || len(namespace) == 0 { - return nil, fmt.Errorf("Internal error: invalid parameters passed to createService: clientset: %v, namespace: %v", clientset, namespace) - } - By(fmt.Sprintf("Creating federated service (type: load balancer) %q in namespace %q", name, namespace)) - - // Tests can be run in parallel, so we need a different nodePort for - // each test. - // we add in a array all the "available" ports - availablePorts := make([]int32, FederatedSvcNodePortLast-FederatedSvcNodePortFirst) - for i := range availablePorts { - availablePorts[i] = int32(FederatedSvcNodePortFirst + i) - } - - var err error - var service *v1.Service - retry := 10 // the function should retry the service creation on different port only 10 time. - - // until the availablePort list is not empty, lets try to create the service - for len(availablePorts) > 0 && retry > 0 { - // select the Id of an available port - i := rand.Intn(len(availablePorts)) - - By(fmt.Sprintf("try creating federated service %q in namespace %q with nodePort %d", name, namespace, availablePorts[i])) - - service, err = createServiceWithNodePort(clientset, namespace, name, availablePorts[i]) - if err == nil { - // check if service have been created properly in all clusters. - // if the service is not present in one of the clusters, we should cleanup all services - if err = checkServicesCreation(namespace, name, clusters); err == nil { - // everything was created properly so returns the federated service. - return service, nil - } - } - - // in case of error, cleanup everything - if service != nil { - if err = deleteService(clientset, namespace, name, nil); err != nil { - framework.ExpectNoError(err, "Deleting service %q after a partial createService() error", service.Name) - return nil, err - } - - cleanupServiceShardsAndProviderResources(namespace, service, clusters) - } - - // creation failed, lets try with another port - // first remove from the availablePorts the port with which the creation failed - availablePorts = append(availablePorts[:i], availablePorts[i+1:]...) - retry-- - } - - return nil, err -} - -func createServiceWithNodePort(clientset *fedclientset.Clientset, namespace, name string, nodePort int32) (*v1.Service, error) { - service := &v1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: v1.ServiceSpec{ - Selector: FederatedServiceLabels, - Type: v1.ServiceTypeLoadBalancer, - Ports: []v1.ServicePort{ - { - Name: "http", - Protocol: v1.ProtocolTCP, - Port: 80, - TargetPort: intstr.FromInt(8080), - NodePort: nodePort, - }, - }, - SessionAffinity: v1.ServiceAffinityNone, - }, - } - - By(fmt.Sprintf("Trying to create service %q in namespace %q", service.Name, namespace)) - return clientset.CoreV1().Services(namespace).Create(service) -} - -// checkServicesCreation checks if the service have been created successfuly in all the clusters. -// if the service is not present in at least one of the clusters, this function returns an error. -func checkServicesCreation(namespace, serviceName string, clusters fedframework.ClusterSlice) error { - framework.Logf("check if service %q have been created in %d clusters", serviceName, len(clusters)) - for _, cluster := range clusters { - name := cluster.Name - err := wait.PollImmediate(framework.Poll, fedframework.FederatedDefaultTestTimeout, func() (bool, error) { - var err error - _, err = cluster.Clientset.CoreV1().Services(namespace).Get(serviceName, metav1.GetOptions{}) - if err != nil && !errors.IsNotFound(err) { - // Get failed with an error, try again. - framework.Logf("Failed to find service %q in namespace %q, in cluster %q: %v. Trying again in %s", serviceName, namespace, name, err, framework.Poll) - return false, err - } else if errors.IsNotFound(err) { - framework.Logf("Service %q in namespace %q in cluster %q not found. Trying again in %s", serviceName, namespace, name, framework.Poll) - return false, nil - } - By(fmt.Sprintf("Service %q in namespace %q in cluster %q found", serviceName, namespace, name)) - return true, nil - }) - if err != nil { - return err - } - } - return nil -} - -func createServiceOrFail(clientset *fedclientset.Clientset, namespace, name string) *v1.Service { - service, err := createService(clientset, namespace, name) - framework.ExpectNoError(err, "Creating service %q in namespace %q", service.Name, namespace) - By(fmt.Sprintf("Successfully created federated service %q in namespace %q", name, namespace)) - return service -} - -func createLBServiceOrFail(clientset *fedclientset.Clientset, namespace, name string, clusters fedframework.ClusterSlice) *v1.Service { - service, err := createLBService(clientset, namespace, name, clusters) - framework.ExpectNoError(err, "Creating service %q in namespace %q", service.Name, namespace) - By(fmt.Sprintf("Successfully created federated service (type: load balancer) %q in namespace %q", name, namespace)) - return service -} - -func deleteServiceOrFail(clientset *fedclientset.Clientset, namespace string, serviceName string, orphanDependents *bool) { - if clientset == nil || len(namespace) == 0 || len(serviceName) == 0 { - Fail(fmt.Sprintf("Internal error: invalid parameters passed to deleteServiceOrFail: clientset: %v, namespace: %v, service: %v", clientset, namespace, serviceName)) - } - framework.Logf("Deleting service %q in namespace %v", serviceName, namespace) - - err := deleteService(clientset, namespace, serviceName, orphanDependents) - if err != nil { - framework.ExpectNoError(err, "Error deleting service %q from namespace %q", serviceName, namespace) - } -} - -func deleteService(clientset *fedclientset.Clientset, namespace string, serviceName string, orphanDependents *bool) error { - err := clientset.CoreV1().Services(namespace).Delete(serviceName, &metav1.DeleteOptions{OrphanDependents: orphanDependents}) - if err != nil { - return err - } - // Wait for the service to be deleted. - err = wait.Poll(5*time.Second, fedframework.FederatedDefaultTestTimeout, func() (bool, error) { - _, err := clientset.Core().Services(namespace).Get(serviceName, metav1.GetOptions{}) - if err != nil && errors.IsNotFound(err) { - return true, nil - } - return false, err - }) - return err -} - -func cleanupServiceShardsAndProviderResources(namespace string, service *v1.Service, clusters fedframework.ClusterSlice) { - framework.Logf("Deleting service %q in %d clusters", service.Name, len(clusters)) - for _, c := range clusters { - name := c.Name - var cSvc *v1.Service - - err := wait.PollImmediate(framework.Poll, fedframework.FederatedDefaultTestTimeout, func() (bool, error) { - var err error - cSvc, err = c.Clientset.CoreV1().Services(namespace).Get(service.Name, metav1.GetOptions{}) - if err != nil && !errors.IsNotFound(err) { - // Get failed with an error, try again. - framework.Logf("Failed to find service %q in namespace %q, in cluster %q: %v. Trying again in %s", service.Name, namespace, name, err, framework.Poll) - return false, nil - } else if errors.IsNotFound(err) { - cSvc = nil - By(fmt.Sprintf("Service %q in namespace %q in cluster %q not found", service.Name, namespace, name)) - return true, err - } - By(fmt.Sprintf("Service %q in namespace %q in cluster %q found", service.Name, namespace, name)) - return true, err - }) - - if err != nil || cSvc == nil { - By(fmt.Sprintf("Failed to find service %q in namespace %q, in cluster %q in %s", service.Name, namespace, name, fedframework.FederatedDefaultTestTimeout)) - continue - } - - if cSvc.Spec.Type == v1.ServiceTypeLoadBalancer { - // In federation tests, e2e zone names are used to derive federation member cluster names - zone := fedframework.GetZoneFromClusterName(name) - serviceLBName := cloudprovider.GetLoadBalancerName(cSvc) - framework.Logf("cleaning cloud provider resource for service %q in namespace %q, in cluster %q", service.Name, namespace, name) - framework.CleanupServiceResources(c.Clientset, serviceLBName, zone) - } - - err = cleanupServiceShard(c.Clientset, name, namespace, cSvc, fedframework.FederatedDefaultTestTimeout) - if err != nil { - framework.Logf("Failed to delete service %q in namespace %q, in cluster %q: %v", service.Name, namespace, name, err) - } - } -} - -func cleanupServiceShard(clientset *kubeclientset.Clientset, clusterName, namespace string, service *v1.Service, timeout time.Duration) error { - err := wait.PollImmediate(framework.Poll, timeout, func() (bool, error) { - err := clientset.CoreV1().Services(namespace).Delete(service.Name, &metav1.DeleteOptions{}) - if err != nil && !errors.IsNotFound(err) { - // Deletion failed with an error, try again. - framework.Logf("Failed to delete service %q in namespace %q, in cluster %q", service.Name, namespace, clusterName) - return false, nil - } - By(fmt.Sprintf("Service %q in namespace %q in cluster %q deleted", service.Name, namespace, clusterName)) - return true, nil - }) - return err -} - -func podExitCodeDetector(f *fedframework.Framework, name, namespace string, code int32) func() error { - // If we ever get any container logs, stash them here. - logs := "" - - logerr := func(err error) error { - if err == nil { - return nil - } - if logs == "" { - return err - } - return fmt.Errorf("%s (%v)", logs, err) - } - - return func() error { - pod, err := f.ClientSet.Core().Pods(namespace).Get(name, metav1.GetOptions{}) - if err != nil { - return logerr(err) - } - if len(pod.Status.ContainerStatuses) < 1 { - return logerr(fmt.Errorf("no container statuses")) - } - - // Best effort attempt to grab pod logs for debugging - logs, err = framework.GetPodLogs(f.ClientSet, namespace, name, pod.Spec.Containers[0].Name) - if err != nil { - framework.Logf("Cannot fetch pod logs: %v", err) - } - - status := pod.Status.ContainerStatuses[0] - if status.State.Terminated == nil { - return logerr(fmt.Errorf("container is not in terminated state")) - } - if status.State.Terminated.ExitCode == code { - return nil - } - - return logerr(fmt.Errorf("exited %d", status.State.Terminated.ExitCode)) - } -} - -func discoverService(f *fedframework.Framework, name string, exists bool, podName string) { - command := []string{"sh", "-c", fmt.Sprintf("until nslookup '%s'; do sleep 10; done", name)} - By(fmt.Sprintf("Looking up %q", name)) - - pod := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: podName, - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: "federated-service-discovery-container", - Image: imageutils.GetBusyBoxImage(), - Command: command, - }, - }, - RestartPolicy: v1.RestartPolicyOnFailure, - }, - } - - nsName := f.FederationNamespace.Name - By(fmt.Sprintf("Creating pod %q in namespace %q", pod.Name, nsName)) - _, err := f.ClientSet.Core().Pods(nsName).Create(pod) - framework.ExpectNoError(err, "Trying to create pod to run %q", command) - By(fmt.Sprintf("Successfully created pod %q in namespace %q", pod.Name, nsName)) - defer func() { - By(fmt.Sprintf("Deleting pod %q from namespace %q", podName, nsName)) - err := f.ClientSet.Core().Pods(nsName).Delete(podName, metav1.NewDeleteOptions(0)) - framework.ExpectNoError(err, "Deleting pod %q from namespace %q", podName, nsName) - By(fmt.Sprintf("Deleted pod %q from namespace %q", podName, nsName)) - }() - - if exists { - // TODO(mml): Eventually check the IP address is correct, too. - Eventually(podExitCodeDetector(f, podName, nsName, 0), 3*DNSTTL, time.Second*2). - Should(BeNil(), "%q should exit 0, but it never did", command) - } else { - Eventually(podExitCodeDetector(f, podName, nsName, 0), 3*DNSTTL, time.Second*2). - ShouldNot(BeNil(), "%q should eventually not exit 0, but it always did", command) - } -} - -// BackendPodMap maps a cluster name to a backend pod created in that cluster -type BackendPodMap map[string]*v1.Pod - -// createBackendPodsOrFail creates one pod in each cluster, and returns the created pods. If creation of any pod fails, -// the test fails (possibly with a partially created set of pods). No retries are attempted. -func createBackendPodsOrFail(clusters fedframework.ClusterSlice, namespace string, name string) BackendPodMap { - pod := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - // Namespace: namespace, - Labels: FederatedServiceLabels, - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: name, - Image: "gcr.io/google_containers/echoserver:1.6", - }, - }, - RestartPolicy: v1.RestartPolicyAlways, - }, - } - podMap := make(BackendPodMap) - for _, c := range clusters { - name := c.Name - By(fmt.Sprintf("Creating pod %q in namespace %q in cluster %q", pod.Name, namespace, name)) - createdPod, err := c.Clientset.CoreV1().Pods(namespace).Create(pod) - framework.ExpectNoError(err, "Creating pod %q in namespace %q in cluster %q", name, namespace, name) - By(fmt.Sprintf("Successfully created pod %q in namespace %q in cluster %q: %v", pod.Name, namespace, name, *createdPod)) - podMap[name] = createdPod - } - return podMap -} - -// deleteOneBackendPodOrFail deletes exactly one backend pod which must not be nil -// The test fails if there are any errors. -func deleteOneBackendPodOrFail(c *fedframework.Cluster, pod *v1.Pod) { - Expect(pod).ToNot(BeNil()) - err := c.Clientset.CoreV1().Pods(pod.Namespace).Delete(pod.Name, metav1.NewDeleteOptions(0)) - msgFmt := fmt.Sprintf("Deleting Pod %q in namespace %q in cluster %q %%v", pod.Name, pod.Namespace, c.Name) - if errors.IsNotFound(err) { - framework.Logf(msgFmt, "does not exist. No need to delete it.") - return - } - framework.ExpectNoError(err, msgFmt, "") - framework.Logf(msgFmt, "was deleted") -} - -// deleteBackendPodsOrFail deletes one pod from each cluster that has one. -// If deletion of any pod fails, the test fails (possibly with a partially deleted set of pods). No retries are attempted. -func deleteBackendPodsOrFail(clusters fedframework.ClusterSlice, backendPods BackendPodMap) { - if backendPods == nil { - return - } - for _, c := range clusters { - if pod, ok := backendPods[c.Name]; ok { - deleteOneBackendPodOrFail(c, pod) - } else { - By(fmt.Sprintf("No backend pod to delete for cluster %q", c.Name)) - } - } -} - -// waitForReplicatSetToBeDeletedOrFail waits for the named ReplicaSet in namespace to be deleted. -// If the deletion fails, the enclosing test fails. -func waitForReplicaSetToBeDeletedOrFail(clientset *fedclientset.Clientset, namespace string, replicaSet string) { - err := wait.Poll(5*time.Second, fedframework.FederatedDefaultTestTimeout, func() (bool, error) { - _, err := clientset.Extensions().ReplicaSets(namespace).Get(replicaSet, metav1.GetOptions{}) - if err != nil && errors.IsNotFound(err) { - return true, nil - } - return false, err - }) - - if err != nil { - framework.Failf("Error in deleting replica set %s: %v", replicaSet, err) - } -} diff --git a/federation/test/integration/BUILD b/federation/test/integration/BUILD deleted file mode 100644 index c6e7fcc929f..00000000000 --- a/federation/test/integration/BUILD +++ /dev/null @@ -1,50 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_test", -) - -go_test( - name = "go_default_test", - size = "large", - srcs = [ - "api_test.go", - "crud_test.go", - "main_test.go", - ], - importpath = "k8s.io/kubernetes/federation/test/integration", - tags = ["integration"], - deps = [ - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/pkg/federatedtypes:go_default_library", - "//federation/pkg/federatedtypes/crudtester:go_default_library", - "//federation/test/integration/framework:go_default_library", - "//test/integration/framework:go_default_library", - "//vendor/github.com/pborman/uuid:go_default_library", - "//vendor/github.com/stretchr/testify/assert:go_default_library", - "//vendor/k8s.io/api/autoscaling/v1:go_default_library", - "//vendor/k8s.io/api/batch/v1:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//federation/test/integration/framework:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/federation/test/integration/OWNERS b/federation/test/integration/OWNERS deleted file mode 100644 index 9c31922034f..00000000000 --- a/federation/test/integration/OWNERS +++ /dev/null @@ -1,16 +0,0 @@ -reviewers: - - colhom - - csbell - - irfanurrehman - - madhusudancs - - marun - - mwielgus - - nikhiljindal - - quinton-hoole - - shashidharatd -approvers: - - csbell - - madhusudancs - - mwielgus - - nikhiljindal - - quinton-hoole diff --git a/federation/test/integration/api_test.go b/federation/test/integration/api_test.go deleted file mode 100644 index b53311e0bb0..00000000000 --- a/federation/test/integration/api_test.go +++ /dev/null @@ -1,417 +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 integration - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "testing" - - "github.com/stretchr/testify/assert" - - autoscaling_v1 "k8s.io/api/autoscaling/v1" - batch_v1 "k8s.io/api/batch/v1" - "k8s.io/api/core/v1" - ext_v1b1 "k8s.io/api/extensions/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - fed_v1b1 "k8s.io/kubernetes/federation/apis/federation/v1beta1" - "k8s.io/kubernetes/federation/test/integration/framework" -) - -// List of group versions that are enabled by default. -var enabledGroupVersions = []schema.GroupVersion{ - fed_v1b1.SchemeGroupVersion, - ext_v1b1.SchemeGroupVersion, -} - -// List of group versions that are disabled by default. -var disabledGroupVersions = []schema.GroupVersion{ - batch_v1.SchemeGroupVersion, - autoscaling_v1.SchemeGroupVersion, -} - -type apiTestFunc func(t *testing.T, host string, expectedGroupVersions []schema.GroupVersion) - -func testFederationAPI(t *testing.T, runtimeConfig string, expectedGroupVersions []schema.GroupVersion) { - f := &framework.FederationAPIFixture{} - if runtimeConfig == "" { - f.SetUp(t) - } else { - runOptions := framework.GetRunOptions() - runOptions.APIEnablement.RuntimeConfig.Set(runtimeConfig) - f.SetUpWithRunOptions(t, runOptions) - } - defer f.TearDown(t) - - testCases := map[string]apiTestFunc{ - "swaggerSpec": testSwaggerSpec, - "support": testSupport, - "apiGroupList": testAPIGroupList, - "apiGroup": testAPIGroup, - "apiResourceList": testAPIResourceList, - } - for testName, testFunc := range testCases { - t.Run(testName, func(t *testing.T) { - testFunc(t, f.Host, expectedGroupVersions) - }) - } -} - -// Verifies that only default APIs are enabled when no runtime config is set. -func TestDefaultRun(t *testing.T) { - testFederationAPI(t, "", enabledGroupVersions) -} - -// Verifies that all APIs are enabled when runtime config is set to all. -func TestRunWithRuntimeConfigAll(t *testing.T) { - expectedGroupVersions := enabledGroupVersions - expectedGroupVersions = append(enabledGroupVersions, disabledGroupVersions...) - testFederationAPI(t, "api/all=true", expectedGroupVersions) -} - -func readResponse(serverURL string) ([]byte, error) { - response, err := http.Get(serverURL) - if err != nil { - return nil, fmt.Errorf("Error in fetching %s: %v", serverURL, err) - } - defer response.Body.Close() - if response.StatusCode != http.StatusOK { - return nil, fmt.Errorf("unexpected status: %d for URL: %s, expected status: %d", response.StatusCode, serverURL, http.StatusOK) - } - contents, err := ioutil.ReadAll(response.Body) - if err != nil { - return nil, fmt.Errorf("Error reading response from %s: %v", serverURL, err) - } - return contents, nil -} - -func testSwaggerSpec(t *testing.T, host string, expectedGroupVersions []schema.GroupVersion) { - serverURL := host + "/swaggerapi" - _, err := readResponse(serverURL) - if err != nil { - t.Fatalf("%v", err) - } -} - -func testSupport(t *testing.T, host string, expectedGroupVersions []schema.GroupVersion) { - serverURL := host + "/version" - _, err := readResponse(serverURL) - if err != nil { - t.Fatalf("%v", err) - } -} - -func findGroup(groups []metav1.APIGroup, groupName string) *metav1.APIGroup { - for _, group := range groups { - if group.Name == groupName { - return &group - } - } - return nil -} - -func testAPIGroupList(t *testing.T, host string, expectedGroupVersions []schema.GroupVersion) { - groupVersionForDiscoveryMap := make(map[string]metav1.GroupVersionForDiscovery) - for _, groupVersion := range expectedGroupVersions { - groupVersionForDiscoveryMap[groupVersion.Group] = metav1.GroupVersionForDiscovery{ - GroupVersion: groupVersion.String(), - Version: groupVersion.Version, - } - } - - serverURL := host + "/apis" - contents, err := readResponse(serverURL) - if err != nil { - t.Fatalf("%v", err) - } - var apiGroupList metav1.APIGroupList - err = json.Unmarshal(contents, &apiGroupList) - if err != nil { - t.Fatalf("Error in unmarshalling response from server %s: %v", serverURL, err) - } - - assert.Equal(t, len(apiGroupList.Groups), len(expectedGroupVersions), "expected: %v, actual: %v", expectedGroupVersions, apiGroupList.Groups) - for _, groupVersion := range expectedGroupVersions { - found := findGroup(apiGroupList.Groups, groupVersion.Group) - assert.NotNil(t, found) - assert.Equal(t, groupVersion.Group, found.Name) - assert.Equal(t, 1, len(found.Versions)) - groupVersionForDiscovery := groupVersionForDiscoveryMap[groupVersion.Group] - assert.Equal(t, groupVersionForDiscovery, found.Versions[0]) - assert.Equal(t, groupVersionForDiscovery, found.PreferredVersion) - } -} - -func testAPIGroup(t *testing.T, host string, expectedGroupVersions []schema.GroupVersion) { - for _, groupVersion := range expectedGroupVersions { - serverURL := host + "/apis/" + groupVersion.Group - contents, err := readResponse(serverURL) - if err != nil { - t.Fatalf("%v", err) - } - var apiGroup metav1.APIGroup - err = json.Unmarshal(contents, &apiGroup) - if err != nil { - t.Fatalf("Error in unmarshalling response from server %s: %v", serverURL, err) - } - // empty APIVersion for extensions group - if groupVersion.Group == "extensions" { - assert.Equal(t, "", apiGroup.APIVersion) - } else { - assert.Equal(t, "v1", apiGroup.APIVersion) - } - assert.Equal(t, apiGroup.Name, groupVersion.Group) - assert.Equal(t, 1, len(apiGroup.Versions)) - assert.Equal(t, groupVersion.String(), apiGroup.Versions[0].GroupVersion) - assert.Equal(t, groupVersion.Version, apiGroup.Versions[0].Version) - assert.Equal(t, apiGroup.PreferredVersion, apiGroup.Versions[0]) - } - - testCoreAPIGroup(t, host) -} - -func testCoreAPIGroup(t *testing.T, host string) { - serverURL := host + "/api" - contents, err := readResponse(serverURL) - if err != nil { - t.Fatalf("%v", err) - } - var apiVersions metav1.APIVersions - err = json.Unmarshal(contents, &apiVersions) - if err != nil { - t.Fatalf("Error in unmarshalling response from server %s: %v", serverURL, err) - } - assert.Equal(t, 1, len(apiVersions.Versions)) - assert.Equal(t, "v1", apiVersions.Versions[0]) - assert.NotEmpty(t, apiVersions.ServerAddressByClientCIDRs) -} - -func findResource(resources []metav1.APIResource, resourceName string) *metav1.APIResource { - for _, resource := range resources { - if resource.Name == resourceName { - return &resource - } - } - return nil -} - -func testAPIResourceList(t *testing.T, host string, expectedGroupVersions []schema.GroupVersion) { - testFederationResourceList(t, host) - testCoreResourceList(t, host) - testExtensionsResourceList(t, host) - if contains(expectedGroupVersions, batch_v1.SchemeGroupVersion) { - testBatchResourceList(t, host) - } - if contains(expectedGroupVersions, autoscaling_v1.SchemeGroupVersion) { - testAutoscalingResourceList(t, host) - } -} - -func contains(gvs []schema.GroupVersion, requiredGV schema.GroupVersion) bool { - for _, gv := range gvs { - if gv.String() == requiredGV.String() { - return true - } - } - return false -} - -func testFederationResourceList(t *testing.T, host string) { - serverURL := host + "/apis/" + fed_v1b1.SchemeGroupVersion.String() - contents, err := readResponse(serverURL) - if err != nil { - t.Fatalf("%v", err) - } - var apiResourceList metav1.APIResourceList - err = json.Unmarshal(contents, &apiResourceList) - if err != nil { - t.Fatalf("Error in unmarshalling response from server %s: %v", serverURL, err) - } - assert.Equal(t, "v1", apiResourceList.APIVersion) - assert.Equal(t, fed_v1b1.SchemeGroupVersion.String(), apiResourceList.GroupVersion) - // Assert that there are exactly 2 resources. - assert.Equal(t, 2, len(apiResourceList.APIResources)) - - found := findResource(apiResourceList.APIResources, "clusters") - assert.NotNil(t, found) - assert.False(t, found.Namespaced) - found = findResource(apiResourceList.APIResources, "clusters/status") - assert.NotNil(t, found) - assert.False(t, found.Namespaced) -} - -func testCoreResourceList(t *testing.T, host string) { - serverURL := host + "/api/" + v1.SchemeGroupVersion.String() - contents, err := readResponse(serverURL) - if err != nil { - t.Fatalf("%v", err) - } - var apiResourceList metav1.APIResourceList - err = json.Unmarshal(contents, &apiResourceList) - if err != nil { - t.Fatalf("Error in unmarshalling response from server %s: %v", serverURL, err) - } - assert.Equal(t, "", apiResourceList.APIVersion) - assert.Equal(t, v1.SchemeGroupVersion.String(), apiResourceList.GroupVersion) - assert.Equal(t, 8, len(apiResourceList.APIResources), "ResourceList: %v", apiResourceList.APIResources) - - // Verify services. - found := findResource(apiResourceList.APIResources, "services") - assert.NotNil(t, found) - assert.True(t, found.Namespaced) - found = findResource(apiResourceList.APIResources, "services/status") - assert.NotNil(t, found) - assert.True(t, found.Namespaced) - - // Verify namespaces. - found = findResource(apiResourceList.APIResources, "namespaces") - assert.NotNil(t, found) - assert.False(t, found.Namespaced) - found = findResource(apiResourceList.APIResources, "namespaces/status") - assert.NotNil(t, found) - assert.False(t, found.Namespaced) - found = findResource(apiResourceList.APIResources, "namespaces/finalize") - assert.NotNil(t, found) - assert.False(t, found.Namespaced) - - // Verify events. - found = findResource(apiResourceList.APIResources, "events") - assert.NotNil(t, found) - assert.True(t, found.Namespaced) - - // Verify secrets. - found = findResource(apiResourceList.APIResources, "secrets") - assert.NotNil(t, found) - assert.True(t, found.Namespaced) - - // Verify config maps. - found = findResource(apiResourceList.APIResources, "configmaps") - assert.NotNil(t, found) - assert.True(t, found.Namespaced) -} - -func testExtensionsResourceList(t *testing.T, host string) { - serverURL := host + "/apis/" + ext_v1b1.SchemeGroupVersion.String() - contents, err := readResponse(serverURL) - if err != nil { - t.Fatalf("%v", err) - } - var apiResourceList metav1.APIResourceList - err = json.Unmarshal(contents, &apiResourceList) - if err != nil { - t.Fatalf("Error in unmarshalling response from server %s: %v", serverURL, err) - } - // empty APIVersion for extensions group - assert.Equal(t, "", apiResourceList.APIVersion) - assert.Equal(t, ext_v1b1.SchemeGroupVersion.String(), apiResourceList.GroupVersion) - // Assert that there are exactly 11 resources. - assert.Equal(t, 11, len(apiResourceList.APIResources)) - - // Verify replicasets. - found := findResource(apiResourceList.APIResources, "replicasets") - assert.NotNil(t, found) - assert.True(t, found.Namespaced) - found = findResource(apiResourceList.APIResources, "replicasets/status") - assert.NotNil(t, found) - assert.True(t, found.Namespaced) - found = findResource(apiResourceList.APIResources, "replicasets/scale") - assert.NotNil(t, found) - assert.True(t, found.Namespaced) - - // Verify ingress. - found = findResource(apiResourceList.APIResources, "ingresses") - assert.NotNil(t, found) - assert.True(t, found.Namespaced) - found = findResource(apiResourceList.APIResources, "ingresses/status") - assert.NotNil(t, found) - assert.True(t, found.Namespaced) - - // Verify daemonsets. - found = findResource(apiResourceList.APIResources, "daemonsets") - assert.NotNil(t, found) - assert.True(t, found.Namespaced) - found = findResource(apiResourceList.APIResources, "daemonsets/status") - assert.NotNil(t, found) - assert.True(t, found.Namespaced) - - // Verify deployments. - found = findResource(apiResourceList.APIResources, "deployments") - assert.NotNil(t, found) - assert.True(t, found.Namespaced) - found = findResource(apiResourceList.APIResources, "deployments/status") - assert.NotNil(t, found) - assert.True(t, found.Namespaced) - found = findResource(apiResourceList.APIResources, "deployments/scale") - assert.NotNil(t, found) - assert.True(t, found.Namespaced) - found = findResource(apiResourceList.APIResources, "deployments/rollback") -} - -func testBatchResourceList(t *testing.T, host string) { - serverURL := host + "/apis/" + batch_v1.SchemeGroupVersion.String() - contents, err := readResponse(serverURL) - if err != nil { - t.Fatalf("%v", err) - } - var apiResourceList metav1.APIResourceList - err = json.Unmarshal(contents, &apiResourceList) - if err != nil { - t.Fatalf("Error in unmarshalling response from server %s: %v", serverURL, err) - } - // empty APIVersion for extensions group - assert.Equal(t, "v1", apiResourceList.APIVersion) - assert.Equal(t, batch_v1.SchemeGroupVersion.String(), apiResourceList.GroupVersion) - // Assert that there are exactly this number of resources. - assert.Equal(t, 2, len(apiResourceList.APIResources)) - - // Verify jobs - found := findResource(apiResourceList.APIResources, "jobs") - assert.NotNil(t, found) - assert.True(t, found.Namespaced) - found = findResource(apiResourceList.APIResources, "jobs/status") - assert.NotNil(t, found) - assert.True(t, found.Namespaced) -} - -func testAutoscalingResourceList(t *testing.T, host string) { - serverURL := host + "/apis/" + autoscaling_v1.SchemeGroupVersion.String() - contents, err := readResponse(serverURL) - if err != nil { - t.Fatalf("%v", err) - } - var apiResourceList metav1.APIResourceList - err = json.Unmarshal(contents, &apiResourceList) - if err != nil { - t.Fatalf("Error in unmarshalling response from server %s: %v", serverURL, err) - } - // empty APIVersion for extensions group - assert.Equal(t, "v1", apiResourceList.APIVersion) - assert.Equal(t, autoscaling_v1.SchemeGroupVersion.String(), apiResourceList.GroupVersion) - // Assert that there are exactly this number of resources. - assert.Equal(t, 2, len(apiResourceList.APIResources)) - - // Verify hpa - found := findResource(apiResourceList.APIResources, "horizontalpodautoscalers") - assert.NotNil(t, found) - assert.True(t, found.Namespaced) - found = findResource(apiResourceList.APIResources, "horizontalpodautoscalers/status") - assert.NotNil(t, found) - assert.True(t, found.Namespaced) -} diff --git a/federation/test/integration/crud_test.go b/federation/test/integration/crud_test.go deleted file mode 100644 index cd56c09d18c..00000000000 --- a/federation/test/integration/crud_test.go +++ /dev/null @@ -1,114 +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 integration - -import ( - "fmt" - "testing" - - "github.com/pborman/uuid" - - pkgruntime "k8s.io/apimachinery/pkg/runtime" - federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" - "k8s.io/kubernetes/federation/pkg/federatedtypes" - "k8s.io/kubernetes/federation/pkg/federatedtypes/crudtester" - "k8s.io/kubernetes/federation/test/integration/framework" -) - -// TestFederationCRUD validates create/read/update/delete operations for federated resource types. -func TestFederationCRUD(t *testing.T) { - fedFixture := framework.FederationFixture{DesiredClusterCount: 2} - fedFixture.SetUp(t) - defer fedFixture.TearDown(t) - - federatedTypes := federatedtypes.FederatedTypes() - for kind, fedType := range federatedTypes { - t.Run(kind, func(t *testing.T) { - fixture, crudTester, obj, _ := initCRUDTest(t, &fedFixture, fedType.AdapterFactory, kind) - defer fixture.TearDown(t) - - crudTester.CheckLifecycle(obj) - }) - } - - // The following tests target a single type since the underlying logic is common across all types. - kind := federatedtypes.SecretKind - adapterFactory := federatedtypes.NewSecretAdapter - - // Validate deletion handling where orphanDependents is true or nil - orphanedDependents := true - testCases := map[string]*bool{ - "Resource should not be deleted from underlying clusters when OrphanDependents is true": &orphanedDependents, - "Resource should not be deleted from underlying clusters when OrphanDependents is nil": nil, - } - for testName, orphanDependents := range testCases { - t.Run(testName, func(t *testing.T) { - fixture, crudTester, obj, _ := initCRUDTest(t, &fedFixture, adapterFactory, kind) - defer fixture.TearDown(t) - - updatedObj := crudTester.CheckCreate(obj) - crudTester.CheckDelete(updatedObj, orphanDependents) - }) - } - - t.Run("Resource should be propagated to a newly added cluster", func(t *testing.T) { - fixture, crudTester, obj, _ := initCRUDTest(t, &fedFixture, adapterFactory, kind) - defer fixture.TearDown(t) - - updatedObj := crudTester.CheckCreate(obj) - // Start a new cluster and validate that the resource is propagated to it. - fedFixture.StartCluster(t) - // Check propagation to the new cluster by providing the updated set of clients - objectExpected := true - crudTester.CheckPropagationForClients(updatedObj, fedFixture.ClusterClients, objectExpected) - }) - - t.Run("Resource should only be propagated to the cluster with a matching selector", func(t *testing.T) { - fixture, crudTester, obj, adapter := initCRUDTest(t, &fedFixture, adapterFactory, kind) - defer fixture.TearDown(t) - - // Set an annotation to specify that the object is isolated to cluster 1. - federatedtypes.SetAnnotation(adapter, obj, federationapi.FederationClusterSelectorAnnotation, `[{"key": "cluster", "operator": "==", "values": ["1"]}]`) - - updatedObj := crudTester.Create(obj) - - // Check propagation to the first cluster - objectExpected := true - crudTester.CheckPropagationForClients(updatedObj, fedFixture.ClusterClients[0:1], objectExpected) - - // Verify the object is not sent to the second cluster - objectExpected = false - crudTester.CheckPropagationForClients(updatedObj, fedFixture.ClusterClients[1:2], objectExpected) - - }) -} - -// initCRUDTest initializes common elements of a crud test -func initCRUDTest(t *testing.T, fedFixture *framework.FederationFixture, adapterFactory federatedtypes.AdapterFactory, kind string) ( - *framework.ControllerFixture, *crudtester.FederatedTypeCRUDTester, pkgruntime.Object, federatedtypes.FederatedTypeAdapter) { - config := fedFixture.APIFixture.NewConfig() - fixture := framework.NewControllerFixture(t, kind, adapterFactory, config) - - client := fedFixture.APIFixture.NewClient(fmt.Sprintf("crud-test-%s", kind)) - adapter := adapterFactory(client, config, nil) - - crudTester := framework.NewFederatedTypeCRUDTester(t, adapter, fedFixture.ClusterClients) - - obj := adapter.NewTestObject(uuid.New()) - - return fixture, crudTester, obj, adapter -} diff --git a/federation/test/integration/framework/BUILD b/federation/test/integration/framework/BUILD deleted file mode 100644 index 4f696212823..00000000000 --- a/federation/test/integration/framework/BUILD +++ /dev/null @@ -1,49 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "api.go", - "controller.go", - "crudtester.go", - "federation.go", - "util.go", - ], - importpath = "k8s.io/kubernetes/federation/test/integration/framework", - deps = [ - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/client/clientset_generated/federation_clientset:go_default_library", - "//federation/cmd/federation-apiserver/app:go_default_library", - "//federation/cmd/federation-apiserver/app/options:go_default_library", - "//federation/pkg/federatedtypes:go_default_library", - "//federation/pkg/federatedtypes/crudtester:go_default_library", - "//federation/pkg/federation-controller/cluster:go_default_library", - "//federation/pkg/federation-controller/sync:go_default_library", - "//pkg/master:go_default_library", - "//test/e2e_node/services:go_default_library", - "//test/integration/framework:go_default_library", - "//vendor/github.com/pborman/uuid:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/federation/test/integration/framework/api.go b/federation/test/integration/framework/api.go deleted file mode 100644 index f1d79cd56dd..00000000000 --- a/federation/test/integration/framework/api.go +++ /dev/null @@ -1,133 +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 framework - -import ( - "fmt" - "net/http" - "testing" - - "github.com/pborman/uuid" - - "k8s.io/apimachinery/pkg/util/wait" - restclient "k8s.io/client-go/rest" - federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - "k8s.io/kubernetes/federation/cmd/federation-apiserver/app" - "k8s.io/kubernetes/federation/cmd/federation-apiserver/app/options" - "k8s.io/kubernetes/test/integration/framework" -) - -const apiNoun = "federation apiserver" - -// GetRunOptions returns the default run options that can be used to run a test federation apiserver. -func GetRunOptions() *options.ServerRunOptions { - r := options.NewServerRunOptions() - r.Etcd.StorageConfig.ServerList = []string{framework.GetEtcdURL()} - // Use a unique prefix to ensure isolation from other tests using the same etcd instance - r.Etcd.StorageConfig.Prefix = uuid.New() - // Disable secure serving - r.SecureServing.BindPort = 0 - return r -} - -// FederationAPIFixture manages a federation api server -type FederationAPIFixture struct { - Host string - stopChan chan struct{} -} - -// SetUp runs federation apiserver with default run options. -func (f *FederationAPIFixture) SetUp(t *testing.T) { - f.SetUpWithRunOptions(t, GetRunOptions()) -} - -// SetUpWithRunOptions runs federation apiserver with the given run options. -// Uses default run options if runOptions is nil. -func (f *FederationAPIFixture) SetUpWithRunOptions(t *testing.T, runOptions *options.ServerRunOptions) { - if f.stopChan != nil { - t.Fatal("SetUp() already called") - } - defer TearDownOnPanic(t, f) - - f.stopChan = make(chan struct{}) - - err := startServer(t, runOptions, f.stopChan) - if err != nil { - t.Fatal(err) - } - - f.Host = fmt.Sprintf("http://%s:%d", runOptions.InsecureServing.BindAddress, runOptions.InsecureServing.BindPort) - - err = waitForServer(t, f.Host) - if err != nil { - t.Fatal(err) - } -} - -func (f *FederationAPIFixture) TearDown(t *testing.T) { - if f.stopChan != nil { - close(f.stopChan) - f.stopChan = nil - } -} - -func (f *FederationAPIFixture) NewConfig() *restclient.Config { - return &restclient.Config{Host: f.Host} -} - -func (f *FederationAPIFixture) NewClient(userAgent string) federationclientset.Interface { - config := f.NewConfig() - restclient.AddUserAgent(config, userAgent) - return federationclientset.NewForConfigOrDie(config) -} - -func startServer(t *testing.T, runOptions *options.ServerRunOptions, stopChan <-chan struct{}) error { - err := wait.PollImmediate(DefaultWaitInterval, wait.ForeverTestTimeout, func() (bool, error) { - port, err := framework.FindFreeLocalPort() - if err != nil { - t.Logf("Error allocating an ephemeral port: %v", err) - return false, nil - } - - runOptions.InsecureServing.BindPort = port - err = app.NonBlockingRun(runOptions, stopChan) - if err != nil { - t.Logf("Error starting the %s: %v", apiNoun, err) - return false, nil - } - return true, nil - }) - if err != nil { - return fmt.Errorf("Timed out waiting for the %s: %v", apiNoun, err) - } - return nil -} - -func waitForServer(t *testing.T, host string) error { - err := wait.PollImmediate(DefaultWaitInterval, wait.ForeverTestTimeout, func() (bool, error) { - _, err := http.Get(host) - if err != nil { - t.Logf("Error when trying to contact the API: %v", err) - return false, nil - } - return true, nil - }) - if err != nil { - return fmt.Errorf("Timed out waiting for the %s: %v", apiNoun, err) - } - return nil -} diff --git a/federation/test/integration/framework/controller.go b/federation/test/integration/framework/controller.go deleted file mode 100644 index 2c5e925b1dd..00000000000 --- a/federation/test/integration/framework/controller.go +++ /dev/null @@ -1,43 +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 framework - -import ( - "testing" - - restclient "k8s.io/client-go/rest" - "k8s.io/kubernetes/federation/pkg/federatedtypes" - synccontroller "k8s.io/kubernetes/federation/pkg/federation-controller/sync" -) - -// ControllerFixture manages a federation controller for testing. -type ControllerFixture struct { - stopChan chan struct{} -} - -// NewControllerFixture initializes a new controller fixture -func NewControllerFixture(t *testing.T, kind string, adapterFactory federatedtypes.AdapterFactory, config *restclient.Config) *ControllerFixture { - f := &ControllerFixture{ - stopChan: make(chan struct{}), - } - synccontroller.StartFederationSyncController(kind, adapterFactory, config, f.stopChan, true, nil) - return f -} - -func (f *ControllerFixture) TearDown(t *testing.T) { - close(f.stopChan) -} diff --git a/federation/test/integration/framework/crudtester.go b/federation/test/integration/framework/crudtester.go deleted file mode 100644 index db02d48793e..00000000000 --- a/federation/test/integration/framework/crudtester.go +++ /dev/null @@ -1,47 +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 framework - -import ( - "testing" - - "k8s.io/apimachinery/pkg/util/wait" - clientset "k8s.io/client-go/kubernetes" - "k8s.io/kubernetes/federation/pkg/federatedtypes" - "k8s.io/kubernetes/federation/pkg/federatedtypes/crudtester" -) - -type IntegrationLogger struct { - t *testing.T -} - -func (l *IntegrationLogger) Logf(format string, args ...interface{}) { - l.t.Logf(format, args...) -} - -func (l *IntegrationLogger) Fatalf(format string, args ...interface{}) { - l.t.Fatalf(format, args...) -} - -func (l *IntegrationLogger) Fatal(msg string) { - l.t.Fatal(msg) -} - -func NewFederatedTypeCRUDTester(t *testing.T, adapter federatedtypes.FederatedTypeAdapter, clusterClients []clientset.Interface) *crudtester.FederatedTypeCRUDTester { - logger := &IntegrationLogger{t} - return crudtester.NewFederatedTypeCRUDTester(logger, adapter, clusterClients, DefaultWaitInterval, wait.ForeverTestTimeout) -} diff --git a/federation/test/integration/framework/federation.go b/federation/test/integration/framework/federation.go deleted file mode 100644 index c1ee1bee855..00000000000 --- a/federation/test/integration/framework/federation.go +++ /dev/null @@ -1,141 +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 framework - -import ( - "fmt" - "testing" - "time" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - clientset "k8s.io/client-go/kubernetes" - federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" - federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - clustercontroller "k8s.io/kubernetes/federation/pkg/federation-controller/cluster" - "k8s.io/kubernetes/pkg/master" - "k8s.io/kubernetes/test/e2e_node/services" - "k8s.io/kubernetes/test/integration/framework" -) - -type MemberCluster struct { - CloseFn framework.CloseFunc - Config *master.Config - Client clientset.Interface - Host string - namespaceController *services.NamespaceController -} - -// FederationFixture manages a federation api server and a set of member clusters -type FederationFixture struct { - APIFixture *FederationAPIFixture - DesiredClusterCount int - Clusters []*MemberCluster - - ClusterClients []clientset.Interface - ClusterController *clustercontroller.ClusterController - fedClient federationclientset.Interface - stopChan chan struct{} -} - -func (f *FederationFixture) SetUp(t *testing.T) { - if f.APIFixture != nil { - t.Fatal("Fixture already started") - } - if f.DesiredClusterCount < 1 { - f.DesiredClusterCount = 1 - } - defer TearDownOnPanic(t, f) - - t.Logf("Starting a federation of %d clusters", f.DesiredClusterCount) - - f.APIFixture = &FederationAPIFixture{} - runOptions := GetRunOptions() - // Enable all apis features for test. - runOptions.APIEnablement.RuntimeConfig.Set("api/all=true") - f.APIFixture.SetUpWithRunOptions(t, runOptions) - - f.stopChan = make(chan struct{}) - monitorPeriod := 1 * time.Second - clustercontroller.StartClusterController(f.APIFixture.NewConfig(), f.stopChan, monitorPeriod) - - f.fedClient = f.APIFixture.NewClient("federation-fixture") - for i := 0; i < f.DesiredClusterCount; i++ { - f.StartCluster(t) - } -} - -func (f *FederationFixture) StartCluster(t *testing.T) { - config := framework.NewMasterConfig() - _, _, closeFn := framework.RunAMaster(config) - host := config.GenericConfig.LoopbackClientConfig.Host - - clusterClient := clientset.NewForConfigOrDie(config.GenericConfig.LoopbackClientConfig) - f.ClusterClients = append(f.ClusterClients, clusterClient) - memberCluster := &MemberCluster{ - CloseFn: closeFn, - Config: config, - Client: clusterClient, - Host: host, - namespaceController: services.NewNamespaceController(host), - } - f.Clusters = append(f.Clusters, memberCluster) - err := memberCluster.namespaceController.Start() - if err != nil { - t.Fatal(err) - } - - clusterId := len(f.ClusterClients) - - t.Logf("Federated cluster %d serving on %s", clusterId, host) - - cluster := &federationapi.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("cluster-%d", clusterId), - Labels: map[string]string{"cluster": fmt.Sprintf("%d", clusterId)}, - }, - Spec: federationapi.ClusterSpec{ - ServerAddressByClientCIDRs: []federationapi.ServerAddressByClientCIDR{ - { - ClientCIDR: "0.0.0.0/0", - ServerAddress: host, - }, - }, - // Use insecure access - SecretRef: nil, - }, - } - f.fedClient.FederationV1beta1().Clusters().Create(cluster) -} - -func (f *FederationFixture) TearDown(t *testing.T) { - if f.stopChan != nil { - close(f.stopChan) - f.stopChan = nil - } - for _, cluster := range f.Clusters { - // Need to close controllers with active connections to the - // cluster api before stopping the api or the connections will - // hang until tcp timeout. - cluster.namespaceController.Stop() - cluster.CloseFn() - } - f.Clusters = nil - if f.APIFixture != nil { - f.APIFixture.TearDown(t) - f.APIFixture = nil - } -} diff --git a/federation/test/integration/framework/util.go b/federation/test/integration/framework/util.go deleted file mode 100644 index 6ce72607a61..00000000000 --- a/federation/test/integration/framework/util.go +++ /dev/null @@ -1,40 +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 framework - -import ( - "testing" - "time" -) - -const ( - DefaultWaitInterval = 50 * time.Millisecond -) - -// SetUp is likely to be fixture-specific, but TearDown needs to be -// consistent to enable TearDownOnPanic. -type TestFixture interface { - TearDown(t *testing.T) -} - -// TearDownOnPanic can be used to ensure cleanup on setup failure. -func TearDownOnPanic(t *testing.T, f TestFixture) { - if r := recover(); r != nil { - f.TearDown(t) - panic(r) - } -} diff --git a/federation/test/integration/main_test.go b/federation/test/integration/main_test.go deleted file mode 100644 index 0a64b7565e4..00000000000 --- a/federation/test/integration/main_test.go +++ /dev/null @@ -1,27 +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 integration - -import ( - "testing" - - "k8s.io/kubernetes/test/integration/framework" -) - -func TestMain(m *testing.M) { - framework.EtcdMain(m.Run) -} diff --git a/hack/.golint_failures b/hack/.golint_failures index dd074d20ef6..c7cd6939add 100644 --- a/hack/.golint_failures +++ b/hack/.golint_failures @@ -1,87 +1,33 @@ cluster/images/etcd-version-monitor -cmd/genutils cmd/gke-certificates-controller/app cmd/hyperkube cmd/kube-controller-manager/app cmd/kube-proxy/app +cmd/kube-scheduler/app cmd/kubeadm/app cmd/kubeadm/app/apis/kubeadm cmd/kubeadm/app/apis/kubeadm/v1alpha1 +cmd/kubeadm/app/phases/etcd/spec cmd/kubelet/app cmd/kubelet/app/options cmd/kubemark examples/guestbook-go -federation/apis/core -federation/apis/core/v1 -federation/apis/federation -federation/apis/federation/v1beta1 -federation/apis/federation/validation -federation/client/cache -federation/cmd/federation-apiserver/app -federation/cmd/federation-apiserver/app/options -federation/cmd/federation-controller-manager/app -federation/cmd/federation-controller-manager/app/options -federation/cmd/kubefed/app -federation/pkg/dnsprovider -federation/pkg/dnsprovider/providers/aws/route53 -federation/pkg/dnsprovider/providers/aws/route53/stubs -federation/pkg/dnsprovider/providers/coredns -federation/pkg/dnsprovider/providers/coredns/stubs -federation/pkg/dnsprovider/providers/google/clouddns -federation/pkg/dnsprovider/providers/google/clouddns/internal -federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces -federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs -federation/pkg/dnsprovider/rrstype -federation/pkg/dnsprovider/tests -federation/pkg/federatedtypes -federation/pkg/federatedtypes/crudtester -federation/pkg/federation-controller -federation/pkg/federation-controller/cluster -federation/pkg/federation-controller/ingress -federation/pkg/federation-controller/service -federation/pkg/federation-controller/service/dns -federation/pkg/federation-controller/service/ingress -federation/pkg/federation-controller/sync -federation/pkg/federation-controller/util -federation/pkg/federation-controller/util/clusterselector -federation/pkg/federation-controller/util/deletionhelper -federation/pkg/federation-controller/util/eventsink -federation/pkg/federation-controller/util/finalizers -federation/pkg/federation-controller/util/planner -federation/pkg/federation-controller/util/podanalyzer -federation/pkg/federation-controller/util/test -federation/pkg/kubefed -federation/pkg/kubefed/init -federation/pkg/kubefed/testing -federation/pkg/kubefed/util -federation/registry/cluster -federation/registry/cluster/etcd -federation/test/e2e -federation/test/e2e/framework -federation/test/e2e/upgrades -federation/test/integration/framework -pkg/api pkg/api/endpoints -pkg/api/helper -pkg/api/helper/qos pkg/api/ref pkg/api/testapi pkg/api/testing pkg/api/testing/compat pkg/api/unversioned pkg/api/v1/endpoints -pkg/api/v1/helper -pkg/api/v1/helper/qos pkg/api/v1/pod pkg/api/v1/resource -pkg/api/v1/validation -pkg/api/validation pkg/apis/abac pkg/apis/abac/latest pkg/apis/admission -pkg/apis/admission/v1alpha1 +pkg/apis/admission/v1beta1 pkg/apis/admissionregistration pkg/apis/admissionregistration/v1alpha1 +pkg/apis/admissionregistration/v1beta1 pkg/apis/admissionregistration/validation pkg/apis/apps pkg/apis/apps/validation @@ -101,14 +47,21 @@ pkg/apis/certificates/v1beta1 pkg/apis/certificates/validation pkg/apis/componentconfig pkg/apis/componentconfig/v1alpha1 +pkg/apis/core +pkg/apis/core/helper +pkg/apis/core/helper/qos +pkg/apis/core/v1/helper +pkg/apis/core/v1/helper/qos +pkg/apis/core/v1/validation +pkg/apis/core/validation +pkg/apis/events +pkg/apis/events/v1beta1 pkg/apis/extensions pkg/apis/extensions/validation pkg/apis/imagepolicy pkg/apis/imagepolicy/v1alpha1 -pkg/apis/meta/v1 pkg/apis/networking pkg/apis/policy -pkg/apis/policy/v1alpha1 pkg/apis/policy/v1beta1 pkg/apis/policy/validation pkg/apis/rbac/v1 @@ -120,9 +73,8 @@ pkg/apis/settings pkg/apis/settings/v1alpha1 pkg/apis/storage pkg/apis/storage/util -pkg/apis/storage/v1 pkg/apis/storage/v1/util -pkg/apis/storage/v1beta1 +pkg/apis/storage/v1alpha1 pkg/apis/storage/v1beta1/util pkg/auth/authorizer/abac pkg/capabilities @@ -145,6 +97,7 @@ pkg/controller/certificates pkg/controller/certificates/approver pkg/controller/certificates/signer pkg/controller/cloud +pkg/controller/clusterroleaggregation pkg/controller/cronjob pkg/controller/daemon pkg/controller/daemon/util @@ -188,6 +141,7 @@ pkg/kubeapiserver/authorizer/modes pkg/kubeapiserver/options pkg/kubeapiserver/server pkg/kubectl +pkg/kubectl/categories pkg/kubectl/cmd pkg/kubectl/cmd/auth pkg/kubectl/cmd/config @@ -201,7 +155,6 @@ pkg/kubectl/cmd/util/jsonmerge pkg/kubectl/cmd/util/sanity pkg/kubectl/metricsutil pkg/kubectl/resource -pkg/kubectl/testing pkg/kubectl/util pkg/kubectl/util/crlf pkg/kubectl/util/slice @@ -209,7 +162,7 @@ pkg/kubelet pkg/kubelet/apis pkg/kubelet/apis/cri/testing pkg/kubelet/apis/cri/v1alpha1/runtime -pkg/kubelet/apis/deviceplugin/v1alpha1 +pkg/kubelet/apis/deviceplugin/v1alpha pkg/kubelet/apis/kubeletconfig pkg/kubelet/apis/kubeletconfig/v1alpha1 pkg/kubelet/cadvisor @@ -223,7 +176,6 @@ pkg/kubelet/container/testing pkg/kubelet/custommetrics pkg/kubelet/dockershim pkg/kubelet/dockershim/cm -pkg/kubelet/dockershim/errors pkg/kubelet/dockershim/libdocker pkg/kubelet/dockershim/remote pkg/kubelet/dockershim/testing @@ -279,16 +231,18 @@ pkg/probe/exec pkg/probe/http pkg/probe/tcp pkg/proxy +pkg/proxy/apis/kubeproxyconfig +pkg/proxy/apis/kubeproxyconfig/v1alpha1 pkg/proxy/iptables pkg/proxy/userspace pkg/proxy/util pkg/proxy/winkernel pkg/proxy/winuserspace pkg/quota/evaluator/core -pkg/quota/generic -pkg/registry/admissionregistration/externaladmissionhookconfiguration/storage pkg/registry/admissionregistration/initializerconfiguration/storage +pkg/registry/admissionregistration/mutatingwebhookconfiguration/storage pkg/registry/admissionregistration/rest +pkg/registry/admissionregistration/validatingwebhookconfiguration/storage pkg/registry/apps/rest pkg/registry/apps/statefulset pkg/registry/apps/statefulset/storage @@ -340,6 +294,7 @@ pkg/registry/core/service/portallocator pkg/registry/core/service/portallocator/controller pkg/registry/core/service/storage pkg/registry/core/serviceaccount/storage +pkg/registry/events/rest pkg/registry/extensions/controller/storage pkg/registry/extensions/daemonset pkg/registry/extensions/daemonset/storage @@ -376,6 +331,19 @@ pkg/registry/storage/rest pkg/registry/storage/storageclass pkg/registry/storage/storageclass/storage pkg/routes +pkg/scheduler/algorithm +pkg/scheduler/algorithm/predicates +pkg/scheduler/algorithm/priorities +pkg/scheduler/algorithm/priorities/util +pkg/scheduler/api +pkg/scheduler/api/latest +pkg/scheduler/api/v1 +pkg/scheduler/core +pkg/scheduler/factory +pkg/scheduler/metrics +pkg/scheduler/schedulercache +pkg/scheduler/testing +pkg/scheduler/util pkg/security/apparmor pkg/security/podsecuritypolicy pkg/security/podsecuritypolicy/group @@ -402,6 +370,7 @@ pkg/util/labels pkg/util/mount pkg/util/netsh/testing pkg/util/node +pkg/util/normalizer pkg/util/oom pkg/util/parsers pkg/util/procfs @@ -444,7 +413,6 @@ pkg/volume/storageos pkg/volume/testing pkg/volume/util pkg/volume/vsphere_volume -plugin/cmd/kube-scheduler/app plugin/pkg/admission/antiaffinity plugin/pkg/admission/eventratelimit/apis/eventratelimit plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1 @@ -464,24 +432,11 @@ plugin/pkg/admission/security plugin/pkg/admission/security/podsecuritypolicy plugin/pkg/admission/serviceaccount plugin/pkg/admission/storageclass/setdefault -plugin/pkg/admission/webhook plugin/pkg/auth/authorizer/node plugin/pkg/auth/authorizer/rbac -plugin/pkg/scheduler/algorithm -plugin/pkg/scheduler/algorithm/predicates -plugin/pkg/scheduler/algorithm/priorities -plugin/pkg/scheduler/algorithm/priorities/util -plugin/pkg/scheduler/api -plugin/pkg/scheduler/api/latest -plugin/pkg/scheduler/api/v1 -plugin/pkg/scheduler/core -plugin/pkg/scheduler/factory -plugin/pkg/scheduler/metrics -plugin/pkg/scheduler/schedulercache -plugin/pkg/scheduler/testing -plugin/pkg/scheduler/util -staging/src/k8s.io/api/admission/v1alpha1 +staging/src/k8s.io/api/admission/v1beta1 staging/src/k8s.io/api/admissionregistration/v1alpha1 +staging/src/k8s.io/api/admissionregistration/v1beta1 staging/src/k8s.io/api/apps/v1 staging/src/k8s.io/api/apps/v1beta1 staging/src/k8s.io/api/apps/v1beta2 @@ -496,6 +451,7 @@ staging/src/k8s.io/api/batch/v1beta1 staging/src/k8s.io/api/batch/v2alpha1 staging/src/k8s.io/api/certificates/v1beta1 staging/src/k8s.io/api/core/v1 +staging/src/k8s.io/api/events/v1beta1 staging/src/k8s.io/api/extensions/v1beta1 staging/src/k8s.io/api/imagepolicy/v1alpha1 staging/src/k8s.io/api/networking/v1 @@ -506,10 +462,16 @@ staging/src/k8s.io/api/rbac/v1beta1 staging/src/k8s.io/api/scheduling/v1alpha1 staging/src/k8s.io/api/settings/v1alpha1 staging/src/k8s.io/api/storage/v1 +staging/src/k8s.io/api/storage/v1alpha1 staging/src/k8s.io/api/storage/v1beta1 -staging/src/k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1 -staging/src/k8s.io/apiextensions-apiserver/examples/client-go/client -staging/src/k8s.io/apiextensions-apiserver/examples/client-go/controller +staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr +staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1 +staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned +staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/fake +staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/scheme +staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1 +staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/fake +staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/internalinterfaces staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1 staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver @@ -541,14 +503,12 @@ staging/src/k8s.io/apimachinery/pkg/apimachinery/announced staging/src/k8s.io/apimachinery/pkg/apimachinery/registered staging/src/k8s.io/apimachinery/pkg/apis/meta/fuzzer staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion -staging/src/k8s.io/apimachinery/pkg/apis/meta/v1 staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation staging/src/k8s.io/apimachinery/pkg/apis/meta/v1alpha1 staging/src/k8s.io/apimachinery/pkg/apis/testapigroup staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1 staging/src/k8s.io/apimachinery/pkg/conversion -staging/src/k8s.io/apimachinery/pkg/conversion/unstructured staging/src/k8s.io/apimachinery/pkg/labels staging/src/k8s.io/apimachinery/pkg/runtime/schema staging/src/k8s.io/apimachinery/pkg/runtime/serializer @@ -586,6 +546,12 @@ staging/src/k8s.io/apiserver/pkg/admission staging/src/k8s.io/apiserver/pkg/admission/configuration staging/src/k8s.io/apiserver/pkg/admission/plugin/initialization staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle +staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config +staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission +staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1 +staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating +staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts +staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating staging/src/k8s.io/apiserver/pkg/apis/apiserver staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1 staging/src/k8s.io/apiserver/pkg/apis/audit @@ -594,6 +560,8 @@ staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1 staging/src/k8s.io/apiserver/pkg/apis/audit/validation staging/src/k8s.io/apiserver/pkg/apis/example staging/src/k8s.io/apiserver/pkg/apis/example/v1 +staging/src/k8s.io/apiserver/pkg/apis/example2 +staging/src/k8s.io/apiserver/pkg/apis/example2/v1 staging/src/k8s.io/apiserver/pkg/audit staging/src/k8s.io/apiserver/pkg/audit/policy staging/src/k8s.io/apiserver/pkg/authentication/authenticatorfactory @@ -619,6 +587,7 @@ staging/src/k8s.io/apiserver/pkg/features staging/src/k8s.io/apiserver/pkg/registry/generic staging/src/k8s.io/apiserver/pkg/registry/generic/registry staging/src/k8s.io/apiserver/pkg/registry/generic/rest +staging/src/k8s.io/apiserver/pkg/registry/generic/testing staging/src/k8s.io/apiserver/pkg/registry/rest staging/src/k8s.io/apiserver/pkg/registry/rest/resttest staging/src/k8s.io/apiserver/pkg/server @@ -663,6 +632,8 @@ staging/src/k8s.io/client-go/kubernetes/fake staging/src/k8s.io/client-go/kubernetes/scheme staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1alpha1 staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1alpha1/fake +staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1 +staging/src/k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1/fake staging/src/k8s.io/client-go/kubernetes/typed/apps/v1 staging/src/k8s.io/client-go/kubernetes/typed/apps/v1/fake staging/src/k8s.io/client-go/kubernetes/typed/apps/v1beta1 @@ -691,6 +662,8 @@ staging/src/k8s.io/client-go/kubernetes/typed/certificates/v1beta1 staging/src/k8s.io/client-go/kubernetes/typed/certificates/v1beta1/fake staging/src/k8s.io/client-go/kubernetes/typed/core/v1 staging/src/k8s.io/client-go/kubernetes/typed/core/v1/fake +staging/src/k8s.io/client-go/kubernetes/typed/events/v1beta1 +staging/src/k8s.io/client-go/kubernetes/typed/events/v1beta1/fake staging/src/k8s.io/client-go/kubernetes/typed/extensions/v1beta1 staging/src/k8s.io/client-go/kubernetes/typed/extensions/v1beta1/fake staging/src/k8s.io/client-go/kubernetes/typed/networking/v1 @@ -709,12 +682,24 @@ staging/src/k8s.io/client-go/kubernetes/typed/settings/v1alpha1 staging/src/k8s.io/client-go/kubernetes/typed/settings/v1alpha1/fake staging/src/k8s.io/client-go/kubernetes/typed/storage/v1 staging/src/k8s.io/client-go/kubernetes/typed/storage/v1/fake +staging/src/k8s.io/client-go/kubernetes/typed/storage/v1alpha1 +staging/src/k8s.io/client-go/kubernetes/typed/storage/v1alpha1/fake staging/src/k8s.io/client-go/kubernetes/typed/storage/v1beta1 staging/src/k8s.io/client-go/kubernetes/typed/storage/v1beta1/fake staging/src/k8s.io/client-go/plugin/pkg/auth/authenticator/token/oidc/testing staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc staging/src/k8s.io/client-go/rest staging/src/k8s.io/client-go/rest/fake +staging/src/k8s.io/client-go/scale +staging/src/k8s.io/client-go/scale/fake +staging/src/k8s.io/client-go/scale/scheme +staging/src/k8s.io/client-go/scale/scheme/appsint +staging/src/k8s.io/client-go/scale/scheme/appsv1beta1 +staging/src/k8s.io/client-go/scale/scheme/appsv1beta2 +staging/src/k8s.io/client-go/scale/scheme/autoscalingv1 +staging/src/k8s.io/client-go/scale/scheme/extensionsint +staging/src/k8s.io/client-go/scale/scheme/extensionsv1beta1 +staging/src/k8s.io/client-go/scale/scheme/extensionsv1beta1 staging/src/k8s.io/client-go/testing staging/src/k8s.io/client-go/tools/cache staging/src/k8s.io/client-go/tools/cache/testing @@ -741,7 +726,6 @@ staging/src/k8s.io/code-generator/cmd/client-gen/generators/scheme staging/src/k8s.io/code-generator/cmd/client-gen/types staging/src/k8s.io/code-generator/cmd/conversion-gen/generators staging/src/k8s.io/code-generator/cmd/go-to-protobuf/protobuf -staging/src/k8s.io/code-generator/cmd/informer-gen/generators staging/src/k8s.io/code-generator/cmd/lister-gen/generators staging/src/k8s.io/kube-aggregator/pkg/apis/apiregistration staging/src/k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1 @@ -779,6 +763,14 @@ staging/src/k8s.io/sample-apiserver/pkg/client/informers/internalversion/interna staging/src/k8s.io/sample-apiserver/pkg/cmd/server staging/src/k8s.io/sample-apiserver/pkg/registry/wardle/fischer staging/src/k8s.io/sample-apiserver/pkg/registry/wardle/flunder +staging/src/k8s.io/sample-controller/pkg/apis/samplecontroller +staging/src/k8s.io/sample-controller/pkg/apis/samplecontroller/v1alpha1 +staging/src/k8s.io/sample-controller/pkg/client/clientset/versioned +staging/src/k8s.io/sample-controller/pkg/client/clientset/versioned/fake +staging/src/k8s.io/sample-controller/pkg/client/clientset/versioned/scheme +staging/src/k8s.io/sample-controller/pkg/client/clientset/versioned/typed/samplecontroller/v1alpha1 +staging/src/k8s.io/sample-controller/pkg/client/clientset/versioned/typed/samplecontroller/v1alpha1/fake +staging/src/k8s.io/sample-controller/pkg/client/informers/externalversions/internalinterfaces test/e2e test/e2e/apimachinery test/e2e/apps @@ -788,6 +780,7 @@ test/e2e/chaosmonkey test/e2e/common test/e2e/framework test/e2e/framework/metrics +test/e2e/framework/timer test/e2e/instrumentation test/e2e/instrumentation/logging test/e2e/instrumentation/monitoring @@ -801,6 +794,8 @@ test/e2e/scalability test/e2e/scheduling test/e2e/servicecatalog test/e2e/storage +test/e2e/storage/utils +test/e2e/storage/vsphere test/e2e/ui test/e2e/upgrades test/e2e/upgrades/apps diff --git a/hack/BUILD b/hack/BUILD index cec56b8df59..f8800daed27 100644 --- a/hack/BUILD +++ b/hack/BUILD @@ -57,16 +57,16 @@ test_suite( go_binary( name = "hack", + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/hack", - library = ":go_default_library", ) go_test( name = "go_default_test", srcs = ["e2e_test.go"], data = glob(["testdata/**"]), + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/hack", - library = ":go_default_library", ) go_library( diff --git a/hack/OWNERS b/hack/OWNERS index 376fd8ffa20..5ff25ea901f 100644 --- a/hack/OWNERS +++ b/hack/OWNERS @@ -9,11 +9,11 @@ reviewers: - zmerlynn - sttts - gmarek + - vishh approvers: - cblecker - deads2k - eparis - - fabianofranz - fejta - ixdy - jbeda @@ -25,3 +25,4 @@ approvers: - zmerlynn - sttts - gmarek + - vishh diff --git a/hack/boilerplate/boilerplate.py b/hack/boilerplate/boilerplate.py index a5205f47002..9ff1d776da3 100755 --- a/hack/boilerplate/boilerplate.py +++ b/hack/boilerplate/boilerplate.py @@ -107,7 +107,7 @@ def file_passes(filename, refs, regexs): print('File %s is missing the year' % filename, file=verbose_out) return False - # Replace all occurrences of the regex "2017|2016|2015|2014" with "YEAR" + # Replace all occurrences of the regex "2014|2015|2016|2017|2018" with "YEAR" p = regexs["date"] for i, d in enumerate(data): (data[i], found) = p.subn('YEAR', d) @@ -175,8 +175,8 @@ def get_regexs(): regexs = {} # Search for "YEAR" which exists in the boilerplate, but shouldn't in the real thing regexs["year"] = re.compile( 'YEAR' ) - # dates can be 2014, 2015, 2016, or 2017; company holder names can be anything - regexs["date"] = re.compile( '(2014|2015|2016|2017)' ) + # dates can be 2014, 2015, 2016, 2017, or 2018; company holder names can be anything + regexs["date"] = re.compile( '(2014|2015|2016|2017|2018)' ) # strip // +build \n\n build constraints regexs["go_build_constraints"] = re.compile(r"^(// \+build.*\n)+\n", re.MULTILINE) # strip #!.* from shell scripts diff --git a/hack/cherry_pick_pull.sh b/hack/cherry_pick_pull.sh index 1a909302d47..3dab5a4947f 100755 --- a/hack/cherry_pick_pull.sh +++ b/hack/cherry_pick_pull.sh @@ -131,12 +131,13 @@ function make-a-pr() { # when we shove the heredoc at hub directly, tickling the ioctl # crash. prtext="$(mktemp -t prtext.XXXX)" # cleaned in return_to_kansas + local numandtitle=$(printf '%s\n' "${SUBJECTS[@]}") cat >"${prtext}" < [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

      secretName

      secretName is the name of a secret in the pod’s namespace; see http://releases.k8s.io/HEAD/docs/volumes.md#secrets

      true

      +

      iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      +

      true

      +

      string

      + + + +

      iqn

      +

      Target iSCSI Qualified Name.

      +

      true

      +

      string

      + + + +

      lun

      +

      iSCSI Target Lun number.

      +

      true

      +

      integer (int32)

      + + + +

      iscsiInterface

      +

      iSCSI Interface Name that uses an iSCSI transport. Defaults to default (tcp).

      +

      false

      +

      string

      + + + +

      fsType

      +

      Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi

      +

      false

      +

      string

      + + + +

      readOnly

      +

      ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false.

      +

      false

      +

      boolean

      +

      false

      + + +

      portals

      +

      iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).

      +

      false

      +

      string array

      + + + +

      chapAuthDiscovery

      +

      whether support iSCSI Discovery CHAP authentication

      +

      false

      +

      boolean

      +

      false

      + + +

      chapAuthSession

      +

      whether support iSCSI Session CHAP authentication

      +

      false

      +

      boolean

      +

      false

      + + +

      secretRef

      +

      CHAP Secret for iSCSI target and initiator authentication

      +

      false

      +

      v1.LocalObjectReference

      + + + +

      initiatorName

      +

      Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface <target portal>:<volume name> will be created for the connection.

      +

      false

      string

      @@ -5907,4 +5979,4 @@ Last updated 2015-07-16 20:51:53 UTC
      - \ No newline at end of file + diff --git a/hack/generate-docs.sh b/hack/generate-docs.sh index c2ba6981158..8fdf81a7d38 100755 --- a/hack/generate-docs.sh +++ b/hack/generate-docs.sh @@ -32,7 +32,6 @@ BINS=( cmd/genkubedocs cmd/genman cmd/genyaml - federation/cmd/genfeddocs ) make -C "${KUBE_ROOT}" WHAT="${BINS[*]}" diff --git a/hack/ginkgo-e2e.sh b/hack/ginkgo-e2e.sh index 67c69b9ddc2..dd1e0a1ab39 100755 --- a/hack/ginkgo-e2e.sh +++ b/hack/ginkgo-e2e.sh @@ -148,7 +148,7 @@ export PATH=$(dirname "${e2e_test}"):"${PATH}" --network="${KUBE_GCE_NETWORK:-${KUBE_GKE_NETWORK:-e2e}}" \ --node-tag="${NODE_TAG:-}" \ --master-tag="${MASTER_TAG:-}" \ - --federated-kube-context="${FEDERATION_KUBE_CONTEXT:-e2e-federation}" \ + --cluster-monitoring-mode="${KUBE_ENABLE_CLUSTER_MONITORING:-influxdb}" \ ${KUBE_CONTAINER_RUNTIME:+"--container-runtime=${KUBE_CONTAINER_RUNTIME}"} \ ${MASTER_OS_DISTRIBUTION:+"--master-os-distro=${MASTER_OS_DISTRIBUTION}"} \ ${NODE_OS_DISTRIBUTION:+"--node-os-distro=${NODE_OS_DISTRIBUTION}"} \ diff --git a/hack/import-restrictions.yaml b/hack/import-restrictions.yaml new file mode 100644 index 00000000000..c9b99ae2436 --- /dev/null +++ b/hack/import-restrictions.yaml @@ -0,0 +1,87 @@ +- baseImportPath: "./pkg/apis/core/" + allowedImports: + - k8s.io/apimachinery + - k8s.io/apiserver/pkg/util/feature + - k8s.io/kubernetes/pkg/apis/core + - k8s.io/kubernetes/pkg/features + - k8s.io/kubernetes/pkg/fieldpath + - k8s.io/kubernetes/pkg/util + - k8s.io/api/core/v1 + + # the following are temporary and should go away. Think twice (or more) before adding anything here. + # Main goal: pkg/apis should be as self-contained as possible. + - k8s.io/kubernetes/pkg/apis/extensions + - k8s.io/kubernetes/pkg/api/legacyscheme + - k8s.io/kubernetes/pkg/api/testapi + - k8s.io/api/extensions/v1beta1 + ignoredSubTrees: + - "./pkg/apis/core/validation" + +- baseImportPath: "./vendor/k8s.io/apimachinery/" + allowedImports: + - k8s.io/apimachinery + - k8s.io/kube-openapi + +- baseImportPath: "./vendor/k8s.io/api/" + allowedImports: + - k8s.io/api + - k8s.io/apimachinery + +- baseImportPath: "./vendor/k8s.io/code-generator/" + ignoredSubTrees: + - "./vendor/k8s.io/code-generator/_test" + allowedImports: + - k8s.io/gengo + - k8s.io/code-generator + - k8s.io/kube-openapi + +- baseImportPath: "./vendor/k8s.io/client-go/" + allowedImports: + - k8s.io/api + - k8s.io/apimachinery + - k8s.io/client-go + +- baseImportPath: "./vendor/k8s.io/apiserver/" + allowedImports: + - k8s.io/api + - k8s.io/apimachinery + - k8s.io/apiserver + - k8s.io/client-go + - k8s.io/kube-openapi + +- baseImportPath: "./vendor/k8s.io/metrics/" + allowedImports: + - k8s.io/api + - k8s.io/apimachinery + - k8s.io/client-go + - k8s.io/metrics + +- baseImportPath: "./vendor/k8s.io/kube-aggregator/" + allowedImports: + - k8s.io/api + - k8s.io/apimachinery + - k8s.io/apiserver + - k8s.io/client-go + - k8s.io/kube-aggregator + - k8s.io/kube-openapi + +- baseImportPath: "./vendor/k8s.io/sample-apiserver/" + allowedImports: + - k8s.io/api + - k8s.io/apimachinery + - k8s.io/apiserver + - k8s.io/client-go + - k8s.io/sample-apiserver + +- baseImportPath: "./vendor/k8s.io/apiextensions-apiserver/" + allowedImports: + - k8s.io/api + - k8s.io/apiextensions-apiserver + - k8s.io/apimachinery + - k8s.io/apiserver + - k8s.io/client-go + +- baseImportPath: "./vendor/k8s.io/kube-openapi/" + allowedImports: + - k8s.io/kube-openapi + - k8s.io/gengo diff --git a/hack/jenkins/build-federation.sh b/hack/jenkins/build-federation.sh deleted file mode 100755 index 54a54ed60f2..00000000000 --- a/hack/jenkins/build-federation.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -# Copyright 2017 The Kubernetes Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -o errexit -set -o nounset -set -o pipefail -set -o xtrace - -KUBE_ROOT=$(dirname "${BASH_SOURCE}")/../.. - -make -C "${KUBE_ROOT}/federation/" build -make -C "${KUBE_ROOT}/federation/" push diff --git a/hack/jenkins/build.sh b/hack/jenkins/build.sh index 8aacdf8190c..ca25459a93b 100755 --- a/hack/jenkins/build.sh +++ b/hack/jenkins/build.sh @@ -35,8 +35,6 @@ export PATH=$PATH:/usr/local/go/bin # Skip gcloud update checking export CLOUDSDK_COMPONENT_MANAGER_DISABLE_UPDATE_CHECK=true -# FEDERATION? -: ${FEDERATION:="false"} : ${KUBE_RELEASE_RUN_TESTS:="n"} export KUBE_RELEASE_RUN_TESTS @@ -67,10 +65,9 @@ else [[ -n "${KUBE_GCS_RELEASE_BUCKET-}" ]] \ && bucket_flag="--bucket=${KUBE_GCS_RELEASE_BUCKET-}" - ${FEDERATION} && federation_flag="--federation" [[ -n "${KUBE_GCS_RELEASE_SUFFIX-}" ]] \ && gcs_suffix_flag="--gcs-suffix=${KUBE_GCS_RELEASE_SUFFIX-}" - ${push_build} ${bucket_flag-} ${federation_flag-} ${gcs_suffix_flag-} \ + ${push_build} ${bucket_flag-} ${gcs_suffix_flag-} \ --nomock --verbose --ci fi diff --git a/hack/jenkins/test-dockerized.sh b/hack/jenkins/test-dockerized.sh index 5269c16abd1..f539cd8dfb2 100755 --- a/hack/jenkins/test-dockerized.sh +++ b/hack/jenkins/test-dockerized.sh @@ -55,7 +55,6 @@ make generated_files go install ./cmd/... ./hack/install-etcd.sh -make test make test-cmd make test-integration ./hack/test-update-storage-objects.sh diff --git a/hack/lib/.gitattributes b/hack/lib/.gitattributes new file mode 100644 index 00000000000..1f211b5ce60 --- /dev/null +++ b/hack/lib/.gitattributes @@ -0,0 +1 @@ +version.sh export-subst diff --git a/hack/lib/golang.sh b/hack/lib/golang.sh index 03eb361822a..3e12b3170a4 100755 --- a/hack/lib/golang.sh +++ b/hack/lib/golang.sh @@ -29,9 +29,10 @@ kube::golang::server_targets() { cmd/kubelet cmd/kubeadm cmd/hyperkube + cmd/kube-scheduler vendor/k8s.io/kube-aggregator vendor/k8s.io/apiextensions-apiserver - plugin/cmd/kube-scheduler + cluster/gce/gci/mounter ) echo "${targets[@]}" } @@ -44,6 +45,7 @@ readonly KUBE_SERVER_BINARIES=("${KUBE_SERVER_TARGETS[@]##*/}") kube::golang::node_targets() { local targets=( cmd/kube-proxy + cmd/kubeadm cmd/kubelet ) echo "${targets[@]}" @@ -125,7 +127,6 @@ fi # If you update this list, please also update build/BUILD. readonly KUBE_CLIENT_TARGETS=( cmd/kubectl - federation/cmd/kubefed ) readonly KUBE_CLIENT_BINARIES=("${KUBE_CLIENT_TARGETS[@]##*/}") readonly KUBE_CLIENT_BINARIES_WIN=("${KUBE_CLIENT_BINARIES[@]/%/.exe}") @@ -140,7 +141,6 @@ kube::golang::test_targets() { cmd/genyaml cmd/genswaggertypedocs cmd/linkcheck - federation/cmd/genfeddocs vendor/github.com/onsi/ginkgo/ginkgo test/e2e/e2e.test ) @@ -153,12 +153,10 @@ readonly KUBE_TEST_BINARIES_WIN=("${KUBE_TEST_BINARIES[@]/%/.exe}") readonly KUBE_TEST_PORTABLE=( test/e2e/testing-manifests test/kubemark - federation/develop hack/e2e.go hack/e2e-internal hack/get-build.sh hack/ginkgo-e2e.sh - hack/federated-ginkgo-e2e.sh hack/lib ) @@ -249,20 +247,6 @@ kube::golang::host_platform() { echo "$(go env GOHOSTOS)/$(go env GOHOSTARCH)" } -kube::golang::current_platform() { - local os="${GOOS-}" - if [[ -z $os ]]; then - os=$(go env GOHOSTOS) - fi - - local arch="${GOARCH-}" - if [[ -z $arch ]]; then - arch=$(go env GOHOSTARCH) - fi - - echo "$os/$arch" -} - # Takes the platform name ($1) and sets the appropriate golang env variables # for that platform. kube::golang::set_platform_envs() { @@ -338,7 +322,7 @@ EOF local go_version go_version=($(go version)) local minimum_go_version - minimum_go_version=go1.8.3 + minimum_go_version=go1.9.1 if [[ "${go_version[2]}" < "${minimum_go_version}" && "${go_version[2]}" != "devel" ]]; then kube::log::usage_from_stdin < .generated_html popd > /dev/null - if LANG=C sed --help 2>&1 | grep -q GNU; then - SED="sed" - elif which gsed &>/dev/null; then - SED="gsed" - else - echo "Failed to find GNU sed as sed or gsed. If you are on Mac: brew install gnu-sed." >&2 - exit 1 - fi + kube::util::ensure-gnu-sed while read file; do if [[ -e "${output_dir}/${file}" && -e "${output_tmp}/${file}" ]]; then echo "comparing ${output_dir}/${file} with ${output_tmp}/${file}" # Remove the timestamp to reduce conflicts in PR(s) - $SED -i 's/^Last updated.*$//' "${output_tmp}/${file}" + ${SED} -i 's/^Last updated.*$//' "${output_tmp}/${file}" # By now, the contents should be normalized and stripped of any # auto-managed content. diff --git a/hack/lib/test.sh b/hack/lib/test.sh index b9e28912fa0..86c49105c48 100644 --- a/hack/lib/test.sh +++ b/hack/lib/test.sh @@ -265,7 +265,7 @@ kube::test::if_has_string() { local message=$1 local match=$2 - if echo "$message" | grep -q "$match"; then + if grep -q "${match}" <<< "${message}"; then echo "Successful" echo "message:$message" echo "has:$match" @@ -283,7 +283,7 @@ kube::test::if_has_not_string() { local message=$1 local match=$2 - if echo "$message" | grep -q "$match"; then + if grep -q "${match}" <<< "${message}"; then echo "FAIL!" echo "message:$message" echo "has:$match" diff --git a/hack/lib/util.sh b/hack/lib/util.sh index 4ddc7bbf992..0035ced815f 100755 --- a/hack/lib/util.sh +++ b/hack/lib/util.sh @@ -42,32 +42,6 @@ kube::util::wait_for_url() { return 1 } -# returns a random port -kube::util::get_random_port() { - awk -v min=1024 -v max=65535 'BEGIN{srand(); print int(min+rand()*(max-min+1))}' -} - -# use netcat to check if the host($1):port($2) is free (return 0 means free, 1 means used) -kube::util::test_host_port_free() { - local host=$1 - local port=$2 - local success=0 - local fail=1 - - which nc >/dev/null || { - kube::log::usage "netcat isn't installed, can't verify if ${host}:${port} is free, skipping the check..." - return ${success} - } - - if [ ! $(nc -vz "${host}" "${port}") ]; then - kube::log::status "${host}:${port} is free, proceeding..." - return ${success} - else - kube::log::status "${host}:${port} is already used" - return ${fail} - fi -} - # Example: kube::util::trap_add 'echo "in trap DEBUG"' DEBUG # See: http://stackoverflow.com/questions/3338030/multiple-bash-traps-for-the-same-signal kube::util::trap_add() { @@ -174,10 +148,11 @@ kube::util::find-binary-for-platform() { "${KUBE_ROOT}/platforms/${platform}/${lookfor}" ) # Also search for binary in bazel build tree. - # In some cases we have to name the binary $BINARY_bin, since there was a - # directory named $BINARY next to it. + # The bazel go rules place binaries in subtrees like + # "bazel-bin/source/path/linux_amd64_pure_stripped/binaryname", so make sure + # the platform name is matched in the path. locations+=($(find "${KUBE_ROOT}/bazel-bin/" -type f -executable \ - \( -name "${lookfor}" -o -name "${lookfor}_bin" \) 2>/dev/null || true) ) + -path "*/${platform/\//_}*/${lookfor}" 2>/dev/null || true) ) # List most recently-updated location. local -r bin=$( (ls -t "${locations[@]}" 2>/dev/null || true) | head -1 ) @@ -209,13 +184,7 @@ kube::util::gen-docs() { "${genkubedocs}" "${dest}/docs/admin/" "kube-proxy" "${genkubedocs}" "${dest}/docs/admin/" "kube-scheduler" "${genkubedocs}" "${dest}/docs/admin/" "kubelet" - - # We don't really need federation-apiserver and federation-controller-manager - # binaries to generate the docs. We just pass their names to decide which docs - # to generate. The actual binary for running federation is hyperkube. - "${genfeddocs}" "${dest}/docs/admin/" "federation-apiserver" - "${genfeddocs}" "${dest}/docs/admin/" "federation-controller-manager" - "${genfeddocs}" "${dest}/docs/admin/" "kubefed" + "${genkubedocs}" "${dest}/docs/admin/" "kubeadm" mkdir -p "${dest}/docs/man/man1/" "${genman}" "${dest}/docs/man/man1/" "kube-apiserver" @@ -225,6 +194,7 @@ kube::util::gen-docs() { "${genman}" "${dest}/docs/man/man1/" "kube-scheduler" "${genman}" "${dest}/docs/man/man1/" "kubelet" "${genman}" "${dest}/docs/man/man1/" "kubectl" + "${genman}" "${dest}/docs/man/man1/" "kubeadm" mkdir -p "${dest}/docs/yaml/kubectl/" "${genyaml}" "${dest}/docs/yaml/kubectl/" @@ -264,50 +234,6 @@ kube::util::remove-gen-docs() { fi } -# Takes a path $1 to traverse for md files to append the ga-beacon tracking -# link to, if needed. If $2 is set, just print files that are missing -# the link. -kube::util::gen-analytics() { - local path="$1" - local dryrun="${2:-}" - local mdfiles dir link - # find has some strange inconsistencies between darwin/linux. The - # path to search must end in '/' for linux, but darwin will put an extra - # slash in results if there is a trailing '/'. - if [[ $( uname ) == 'Linux' ]]; then - dir="${path}/" - else - dir="${path}" - fi - # We don't touch files in special dirs, and the kubectl docs are - # autogenerated by gendocs. - # Don't descend into .directories - mdfiles=($( find "${dir}" -name "*.md" -type f \ - -not -path '*/\.*' \ - -not -path "${path}/vendor/*" \ - -not -path "${path}/staging/*" \ - -not -path "${path}/third_party/*" \ - -not -path "${path}/_gopath/*" \ - -not -path "${path}/_output/*" \ - -not -path "${path}/docs/user-guide/kubectl/kubectl*" )) - for f in "${mdfiles[@]}"; do - link=$(kube::util::analytics-link "${f#${path}/}") - if grep -q -F -x "${link}" "${f}"; then - continue - elif [[ -z "${dryrun}" ]]; then - echo -e "\n\n${link}" >> "${f}" - else - echo "$f" - fi - done -} - -# Prints analytics link to append to a file at path $1. -kube::util::analytics-link() { - local path="$1" - echo "[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/${path}?pixel)]()" -} - # Takes a group/version and returns the path to its location on disk, sans # "pkg". E.g.: # * default behavior: extensions/v1beta1 -> apis/extensions/v1beta1 @@ -343,10 +269,7 @@ kube::util::group-version-to-pkg-path() { case "${group_version}" in # both group and version are "", this occurs when we generate deep copies for internal objects of the legacy v1 API. __internal) - echo "pkg/api" - ;; - federation/v1beta1) - echo "federation/apis/federation/v1beta1" + echo "pkg/apis/core" ;; meta/v1) echo "vendor/k8s.io/apimachinery/pkg/apis/meta/v1" @@ -553,10 +476,15 @@ kube::util::go_install_from_commit() { kube::util::ensure-temp-dir mkdir -p "${KUBE_TEMP}/go/src" - GOPATH="${KUBE_TEMP}/go" go get -d -u "${pkg}" + # TODO(spiffxp): remove this brittle workaround for go getting a package that doesn't exist at HEAD + repo=$(echo ${pkg} | cut -d/ -f1-3) + git clone "https://${repo}" "${KUBE_TEMP}/go/src/${repo}" + # GOPATH="${KUBE_TEMP}/go" go get -d -u "${pkg}" ( - cd "${KUBE_TEMP}/go/src/${pkg}" + cd "${KUBE_TEMP}/go/src/${repo}" + git fetch # TODO(spiffxp): workaround git checkout -q "${commit}" + GOPATH="${KUBE_TEMP}/go" go get -d "${pkg}" #TODO(spiffxp): workaround GOPATH="${KUBE_TEMP}/go" go install "${pkg}" ) PATH="${KUBE_TEMP}/go/bin:${PATH}" @@ -815,12 +743,12 @@ function kube::util::ensure-cfssl { kernel=$(uname -s) case "${kernel}" in Linux) - curl -s -L -o cfssl https://pkg.cfssl.org/R1.2/cfssl_linux-amd64 - curl -s -L -o cfssljson https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64 + curl --retry 10 -L -o cfssl https://pkg.cfssl.org/R1.2/cfssl_linux-amd64 + curl --retry 10 -L -o cfssljson https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64 ;; Darwin) - curl -s -L -o cfssl https://pkg.cfssl.org/R1.2/cfssl_darwin-amd64 - curl -s -L -o cfssljson https://pkg.cfssl.org/R1.2/cfssljson_darwin-amd64 + curl --retry 10 -L -o cfssl https://pkg.cfssl.org/R1.2/cfssl_darwin-amd64 + curl --retry 10 -L -o cfssljson https://pkg.cfssl.org/R1.2/cfssljson_darwin-amd64 ;; *) echo "Unknown, unsupported platform: ${kernel}." >&2 @@ -853,6 +781,23 @@ function kube::util::ensure_dockerized { fi } +# kube::util::ensure-gnu-sed +# Determines which sed binary is gnu-sed on linux/darwin +# +# Sets: +# SED: The name of the gnu-sed binary +# +function kube::util::ensure-gnu-sed { + if LANG=C sed --help 2>&1 | grep -q GNU; then + SED="sed" + elif which gsed &>/dev/null; then + SED="gsed" + else + kube::log::error "Failed to find GNU sed as sed or gsed. If you are on Mac: brew install gnu-sed." >&2 + return 1 + fi +} + # Some useful colors. if [[ -z "${color_start-}" ]]; then declare -r color_start="\033[" diff --git a/hack/lib/version.sh b/hack/lib/version.sh index 7a1d676e5a7..1e819b6e51a 100644 --- a/hack/lib/version.sh +++ b/hack/lib/version.sh @@ -22,6 +22,7 @@ # source code. # KUBE_GIT_TREE_STATE - "clean" indicates no changes since the git commit id # "dirty" indicates source code changes after the git commit id +# "archive" indicates the tree was produced by 'git archive' # KUBE_GIT_VERSION - "vX.Y" used to indicate the last release version. # KUBE_GIT_MAJOR - The major part of the version # KUBE_GIT_MINOR - The minor component of the version @@ -36,6 +37,19 @@ kube::version::get_version_vars() { return fi + # If the kubernetes source was exported through git archive, then + # we likely don't have a git tree, but these magic values may be filled in. + if [[ '$Format:%%$' == "%" ]]; then + KUBE_GIT_COMMIT='$Format:%H$' + KUBE_GIT_TREE_STATE="archive" + # When a 'git archive' is exported, the '$Format:%D$' below will look + # something like 'HEAD -> release-1.8, tag: v1.8.3' where then 'tag: ' + # can be extracted from it. + if [[ '$Format:%D$' =~ tag:\ (v[^ ]+) ]]; then + KUBE_GIT_VERSION="${BASH_REMATCH[1]}" + fi + fi + local git=(git --work-tree "${KUBE_ROOT}") if [[ -n ${KUBE_GIT_COMMIT-} ]] || KUBE_GIT_COMMIT=$("${git[@]}" rev-parse "HEAD^{commit}" 2>/dev/null); then @@ -48,7 +62,7 @@ kube::version::get_version_vars() { fi fi - # Use git describe to find the version based on annotated tags. + # Use git describe to find the version based on tags. if [[ -n ${KUBE_GIT_VERSION-} ]] || KUBE_GIT_VERSION=$("${git[@]}" describe --tags --abbrev=14 "${KUBE_GIT_COMMIT}^{commit}" 2>/dev/null); then # This translates the "git describe" to an actual semver.org # compatible semantic version that looks something like this: @@ -119,8 +133,8 @@ kube::version::ldflag() { local val=${2} # If you update these, also update the list pkg/version/def.bzl. - echo "-X ${KUBE_GO_PACKAGE}/pkg/version.${key}=${val}" - echo "-X ${KUBE_GO_PACKAGE}/vendor/k8s.io/client-go/pkg/version.${key}=${val}" + echo "-X '${KUBE_GO_PACKAGE}/pkg/version.${key}=${val}'" + echo "-X '${KUBE_GO_PACKAGE}/vendor/k8s.io/client-go/pkg/version.${key}=${val}'" } # Prints the value that needs to be passed to the -ldflags parameter of go build diff --git a/hack/local-up-cluster.sh b/hack/local-up-cluster.sh index 0301d0037f5..98a0a6b958e 100755 --- a/hack/local-up-cluster.sh +++ b/hack/local-up-cluster.sh @@ -57,6 +57,7 @@ EVICTION_PRESSURE_TRANSITION_PERIOD=${EVICTION_PRESSURE_TRANSITION_PERIOD:-"1m"} # and we don't know the IP of the DNS pod to pass in as --cluster-dns. # To set this up by hand, set this flag and change DNS_SERVER_IP. # Note also that you need API_HOST (defined above) for correct DNS. +KUBEPROXY_MODE=${KUBEPROXY_MODE:-""} ENABLE_CLUSTER_DNS=${KUBE_ENABLE_CLUSTER_DNS:-true} DNS_SERVER_IP=${KUBE_DNS_SERVER_IP:-10.0.0.10} DNS_DOMAIN=${KUBE_DNS_NAME:-"cluster.local"} @@ -70,6 +71,8 @@ FEATURE_GATES=${FEATURE_GATES:-"AllAlpha=false"} STORAGE_BACKEND=${STORAGE_BACKEND:-"etcd3"} # enable swagger ui ENABLE_SWAGGER_UI=${ENABLE_SWAGGER_UI:-false} +# enable Pod priority and preemption +ENABLE_POD_PRIORITY_PREEMPTION=${ENABLE_POD_PRIORITY_PREEMPTION:-""} # enable kubernetes dashboard ENABLE_CLUSTER_DASHBOARD=${KUBE_ENABLE_CLUSTER_DASHBOARD:-false} @@ -114,6 +117,16 @@ if [ "${CLOUD_PROVIDER}" == "openstack" ]; then fi fi +# set feature gates if using ipvs mode +if [ "${KUBEPROXY_MODE}" == "ipvs" ]; then + FEATURE_GATES="$FEATURE_GATES,SupportIPVSProxyMode=true" +fi + +# set feature gates if enable Pod priority and preemption +if [ "${ENABLE_POD_PRIORITY_PREEMPTION}" == true ]; then + FEATURE_GATES="$FEATURE_GATES,PodPriority=true" +fi + # warn if users are running with swap allowed if [ "${FAIL_SWAP_ON}" == "false" ]; then echo "WARNING : The kubelet is configured to not fail if swap is enabled; production deployments should disable swap." @@ -411,9 +424,19 @@ function start_apiserver { if [[ -n "${NODE_ADMISSION}" ]]; then security_admission=",NodeRestriction" fi + if [ "${ENABLE_POD_PRIORITY_PREEMPTION}" == true ]; then + security_admission=",Priority" + if [[ -n "${RUNTIME_CONFIG}" ]]; then + RUNTIME_CONFIG+="," + fi + RUNTIME_CONFIG+="scheduling.k8s.io/v1alpha1=true" + fi + # Admission Controllers to invoke prior to persisting objects in cluster - ADMISSION_CONTROL=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount${security_admission},DefaultStorageClass,DefaultTolerationSeconds,ResourceQuota + # + # ResourceQuota must come last, or a creation is recorded, but the pod may be forbidden. + ADMISSION_CONTROL=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount${security_admission},DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota,PodPreset # This is the default dir and filename where the apiserver will generate a self-signed cert # which should be able to be used as the CA to verify itself @@ -456,6 +479,13 @@ function start_apiserver { RUNTIME_CONFIG+="admissionregistration.k8s.io/v1alpha1" fi + if [[ ${ADMISSION_CONTROL} == *"PodPreset"* ]]; then + if [[ -n "${RUNTIME_CONFIG}" ]]; then + RUNTIME_CONFIG+="," + fi + RUNTIME_CONFIG+="settings.k8s.io/v1alpha1" + fi + runtime_config="" if [[ -n "${RUNTIME_CONFIG}" ]]; then runtime_config="--runtime-config=${RUNTIME_CONFIG}" @@ -731,13 +761,18 @@ function start_kubeproxy { PROXY_LOG=${LOG_DIR}/kube-proxy.log cat < /tmp/kube-proxy.yaml -apiVersion: componentconfig/v1alpha1 +apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration clientConnection: kubeconfig: ${CERT_DIR}/kube-proxy.kubeconfig hostnameOverride: ${HOSTNAME_OVERRIDE} featureGates: ${FEATURE_GATES} +mode: ${KUBEPROXY_MODE} EOF + if [ "${KUBEPROXY_MODE}" == "ipvs" ]; then + # Load kernel modules required by IPVS proxier + sudo modprobe -a ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh nf_conntrack_ipv4 + fi sudo "${GO_OUT}/hyperkube" proxy \ --config=/tmp/kube-proxy.yaml \ @@ -749,6 +784,7 @@ EOF ${CONTROLPLANE_SUDO} "${GO_OUT}/hyperkube" scheduler \ --v=${LOG_LEVEL} \ --kubeconfig "$CERT_DIR"/scheduler.kubeconfig \ + --feature-gates="${FEATURE_GATES}" \ --master="https://${API_HOST}:${API_SECURE_PORT}" >"${SCHEDULER_LOG}" 2>&1 & SCHEDULER_PID=$! } @@ -771,8 +807,11 @@ function start_kubedashboard { if [[ "${ENABLE_CLUSTER_DASHBOARD}" = true ]]; then echo "Creating kubernetes-dashboard" # use kubectl to create the dashboard - ${KUBECTL} --kubeconfig="${CERT_DIR}/admin.kubeconfig" create -f ${KUBE_ROOT}/cluster/addons/dashboard/dashboard-controller.yaml - ${KUBECTL} --kubeconfig="${CERT_DIR}/admin.kubeconfig" create -f ${KUBE_ROOT}/cluster/addons/dashboard/dashboard-service.yaml + ${KUBECTL} --kubeconfig="${CERT_DIR}/admin.kubeconfig" apply -f ${KUBE_ROOT}/cluster/addons/dashboard/dashboard-secret.yaml + ${KUBECTL} --kubeconfig="${CERT_DIR}/admin.kubeconfig" apply -f ${KUBE_ROOT}/cluster/addons/dashboard/dashboard-configmap.yaml + ${KUBECTL} --kubeconfig="${CERT_DIR}/admin.kubeconfig" apply -f ${KUBE_ROOT}/cluster/addons/dashboard/dashboard-rbac.yaml + ${KUBECTL} --kubeconfig="${CERT_DIR}/admin.kubeconfig" apply -f ${KUBE_ROOT}/cluster/addons/dashboard/dashboard-controller.yaml + ${KUBECTL} --kubeconfig="${CERT_DIR}/admin.kubeconfig" apply -f ${KUBE_ROOT}/cluster/addons/dashboard/dashboard-service.yaml echo "kubernetes-dashboard deployment and service successfully deployed." fi } @@ -801,8 +840,12 @@ function create_storage_class { function print_success { if [[ "${START_MODE}" != "kubeletonly" ]]; then + if [[ "${ENABLE_DAEMON}" = false ]]; then + echo "Local Kubernetes cluster is running. Press Ctrl-C to shut it down." + else + echo "Local Kubernetes cluster is running." + fi cat </dev/null; then - SED=gsed - fi - if ! ($SED --version 2>&1 | grep -q GNU); then - echo "!!! GNU sed is required. If on OS X, use 'brew install gnu-sed'." - exit 1 - fi + kube::util::ensure-gnu-sed kube::log::status "Building kubectl" make -C "${KUBE_ROOT}" WHAT="cmd/kubectl" @@ -400,7 +393,7 @@ run_pod_tests() { create_and_use_new_namespace kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" '' # Command - echo "${output_pod}" | $SED '/namespace:/d' | kubectl create -f - "${kube_flags[@]}" + echo "${output_pod}" | ${SED} '/namespace:/d' | kubectl create -f - "${kube_flags[@]}" # Post-condition: valid-pod POD is created kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" 'valid-pod:' @@ -437,6 +430,13 @@ run_pod_tests() { kubectl create -f test/fixtures/doc-yaml/admin/limitrange/valid-pod.yaml "${kube_flags[@]}" # Post-condition: valid-pod POD is created kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" 'valid-pod:' + # Command + output_message=$(kubectl get pods --field-selector metadata.name=valid-pod "${kube_flags[@]}") + kube::test::if_has_string "${output_message}" "valid-pod" + # Command + phase=$(kubectl get "${kube_flags[@]}" pod valid-pod -o go-template='{{ .status.phase }}') + output_message=$(kubectl get pods --field-selector status.phase="${phase}" "${kube_flags[@]}") + kube::test::if_has_string "${output_message}" "valid-pod" ### Delete PODs with no parameter mustn't kill everything # Pre-condition: valid-pod POD exists @@ -655,7 +655,7 @@ run_pod_tests() { kube::test::get_object_assert rc "{{range.items}}{{$id_field}}:{{end}}" '' ## kubectl create --edit can update the label filed of multiple resources. tmp-editor.sh is a fake editor TEMP=$(mktemp /tmp/tmp-editor-XXXXXXXX.sh) - echo -e "#!/bin/bash\n$SED -i \"s/mock/modified/g\" \$1" > ${TEMP} + echo -e "#!/bin/bash\n${SED} -i \"s/mock/modified/g\" \$1" > ${TEMP} chmod +x ${TEMP} # Command EDITOR=${TEMP} kubectl create --edit -f hack/testdata/multi-resource-json.json "${kube_flags[@]}" @@ -719,9 +719,9 @@ run_pod_tests() { kube::test::get_object_assert pods "{{range.items}}{{$image_field}}:{{end}}" 'changed-with-yaml:' ## Patch pod from JSON can change image # Command - kubectl patch "${kube_flags[@]}" -f test/fixtures/doc-yaml/admin/limitrange/valid-pod.yaml -p='{"spec":{"containers":[{"name": "kubernetes-serve-hostname", "image": "gcr.io/google_containers/pause-amd64:3.0"}]}}' - # Post-condition: valid-pod POD has image gcr.io/google_containers/pause-amd64:3.0 - kube::test::get_object_assert pods "{{range.items}}{{$image_field}}:{{end}}" 'gcr.io/google_containers/pause-amd64:3.0:' + kubectl patch "${kube_flags[@]}" -f test/fixtures/doc-yaml/admin/limitrange/valid-pod.yaml -p='{"spec":{"containers":[{"name": "kubernetes-serve-hostname", "image": "gcr.io/google_containers/pause-amd64:3.1"}]}}' + # Post-condition: valid-pod POD has image gcr.io/google_containers/pause-amd64:3.1 + kube::test::get_object_assert pods "{{range.items}}{{$image_field}}:{{end}}" 'gcr.io/google_containers/pause-amd64:3.1:' ## If resourceVersion is specified in the patch, it will be treated as a precondition, i.e., if the resourceVersion is different from that is stored in the server, the Patch should be rejected ERROR_FILE="${KUBE_TEMP}/conflict-error" @@ -758,7 +758,7 @@ run_pod_tests() { ## --force replace pod can change other field, e.g., spec.container.name # Command - kubectl get "${kube_flags[@]}" pod valid-pod -o json | $SED 's/"kubernetes-serve-hostname"/"replaced-k8s-serve-hostname"/g' > /tmp/tmp-valid-pod.json + kubectl get "${kube_flags[@]}" pod valid-pod -o json | ${SED} 's/"kubernetes-serve-hostname"/"replaced-k8s-serve-hostname"/g' > /tmp/tmp-valid-pod.json kubectl replace "${kube_flags[@]}" --force -f /tmp/tmp-valid-pod.json # Post-condition: spec.container.name = "replaced-k8s-serve-hostname" kube::test::get_object_assert 'pod valid-pod' "{{(index .spec.containers 0).name}}" 'replaced-k8s-serve-hostname' @@ -802,7 +802,7 @@ __EOF__ kubectl delete node node-v1-test "${kube_flags[@]}" ## kubectl edit can update the image field of a POD. tmp-editor.sh is a fake editor - echo -e "#!/bin/bash\n$SED -i \"s/nginx/gcr.io\/google_containers\/serve_hostname/g\" \$1" > /tmp/tmp-editor.sh + echo -e "#!/bin/bash\n${SED} -i \"s/nginx/gcr.io\/google_containers\/serve_hostname/g\" \$1" > /tmp/tmp-editor.sh chmod +x /tmp/tmp-editor.sh # Pre-condition: valid-pod POD has image nginx kube::test::get_object_assert pods "{{range.items}}{{$image_field}}:{{end}}" 'nginx:' @@ -882,7 +882,7 @@ __EOF__ # Post-Condition: pod "test-pod" doesn't have configuration annotation ! [[ "$(kubectl get pods test-pod -o yaml "${kube_flags[@]}" | grep kubectl.kubernetes.io/last-applied-configuration)" ]] ## 2. kubectl replace doesn't set the annotation - kubectl get pods test-pod -o yaml "${kube_flags[@]}" | $SED 's/test-pod-label/test-pod-replaced/g' > "${KUBE_TEMP}"/test-pod-replace.yaml + kubectl get pods test-pod -o yaml "${kube_flags[@]}" | ${SED} 's/test-pod-label/test-pod-replaced/g' > "${KUBE_TEMP}"/test-pod-replace.yaml # Command: replace the pod "test-pod" kubectl replace -f "${KUBE_TEMP}"/test-pod-replace.yaml "${kube_flags[@]}" # Post-Condition: pod "test-pod" is replaced @@ -898,7 +898,7 @@ __EOF__ [[ "$(kubectl get pods test-pod -o yaml "${kube_flags[@]}" | grep kubectl.kubernetes.io/last-applied-configuration)" ]] kubectl get pods test-pod -o yaml "${kube_flags[@]}" | grep kubectl.kubernetes.io/last-applied-configuration > "${KUBE_TEMP}"/annotation-configuration ## 4. kubectl replace updates an existing annotation - kubectl get pods test-pod -o yaml "${kube_flags[@]}" | $SED 's/test-pod-applied/test-pod-replaced/g' > "${KUBE_TEMP}"/test-pod-replace.yaml + kubectl get pods test-pod -o yaml "${kube_flags[@]}" | ${SED} 's/test-pod-applied/test-pod-replaced/g' > "${KUBE_TEMP}"/test-pod-replace.yaml # Command: replace the pod "test-pod" kubectl replace -f "${KUBE_TEMP}"/test-pod-replace.yaml "${kube_flags[@]}" # Post-Condition: pod "test-pod" is replaced @@ -915,6 +915,35 @@ __EOF__ set +o errexit } +# runs specific kubectl create tests +run_create_secret_tests() { + set -o nounset + set -o errexit + + ### Create generic secret with explicit namespace + # Pre-condition: secret 'mysecret' does not exist + output_message=$(! kubectl get secrets mysecret 2>&1 "${kube_flags[@]}") + kube::test::if_has_string "${output_message}" 'secrets "mysecret" not found' + # Command + output_message=$(kubectl create "${kube_flags[@]}" secret generic mysecret --dry-run --from-literal=foo=bar -o jsonpath='{.metadata.namespace}' --namespace=user-specified) + # Post-condition: mysecret still not created since --dry-run was used + # Output from 'create' command should contain the specified --namespace value + failure_message=$(! kubectl get secrets mysecret 2>&1 "${kube_flags[@]}") + kube::test::if_has_string "${failure_message}" 'secrets "mysecret" not found' + kube::test::if_has_string "${output_message}" 'user-specified' + # Command + output_message=$(kubectl create "${kube_flags[@]}" secret generic mysecret --dry-run --from-literal=foo=bar -o jsonpath='{.metadata.namespace}') + # Post-condition: jsonpath for .metadata.namespace should be empty for object since --namespace was not explicitly specified + kube::test::if_empty_string "${output_message}" + + kubectl create configmap tester-create-cm -o json --dry-run | kubectl create "${kube_flags[@]}" --raw /api/v1/namespaces/default/configmaps -f - + kubectl delete -ndefault "${kube_flags[@]}" configmap tester-create-cm + + set +o nounset + set +o errexit +} + + # Runs tests related to kubectl apply. run_kubectl_apply_tests() { set -o nounset @@ -1120,6 +1149,24 @@ run_kubectl_apply_deployments_tests() { kube::test::wait_object_assert replicasets "{{range.items}}{{$id_field}}:{{end}}" '' kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" '' + # kubectl apply deployment --overwrite=true --force=true + # Pre-Condition: no deployment exists + kube::test::get_object_assert deployments "{{range.items}}{{$id_field}}:{{end}}" '' + # apply deployment nginx + kubectl apply -f hack/testdata/deployment-label-change1.yaml "${kube_flags[@]}" + # check right deployment exists + kube::test::get_object_assert 'deployment nginx' "{{${id_field}}}" 'nginx' + # apply deployment with wrong labels mismatch selector throws errors + output_message=$(! kubectl apply -f hack/testdata/deployment-label-change2.yaml 2>&1 "${kube_flags[@]}") + kube::test::if_has_string "${output_message}" 'Invalid value' + # apply deployment with --force and --overwrite will success + kubectl apply -f hack/testdata/deployment-label-change2.yaml --overwrite=true --force=true --grace-period=10 + # check the changed deployment + output_message=$(kubectl apply view-last-applied deploy/nginx -o json 2>&1 "${kube_flags[@]}" |grep nginx2) + kube::test::if_has_string "${output_message}" '"name": "nginx2"' + # cleanup + kubectl delete deployments --all --grace-period=10 + set +o nounset set +o errexit } @@ -1149,7 +1196,7 @@ run_save_config_tests() { ! [[ "$(kubectl get pods test-pod -o yaml "${kube_flags[@]}" | grep kubectl.kubernetes.io/last-applied-configuration)" ]] # Command: edit the pod "test-pod" temp_editor="${KUBE_TEMP}/tmp-editor.sh" - echo -e "#!/bin/bash\n$SED -i \"s/test-pod-label/test-pod-label-edited/g\" \$@" > "${temp_editor}" + echo -e "#!/bin/bash\n${SED} -i \"s/test-pod-label/test-pod-label-edited/g\" \$@" > "${temp_editor}" chmod +x "${temp_editor}" EDITOR=${temp_editor} kubectl edit pod test-pod --save-config "${kube_flags[@]}" # Post-Condition: pod "test-pod" has configuration annotation @@ -1224,7 +1271,7 @@ run_kubectl_run_tests() { # Post-Condition: Job "pi" is created kube::test::get_object_assert jobs "{{range.items}}{{$id_field}}:{{end}}" 'pi:' # Describe command (resource only) should print detailed information - kube::test::describe_resource_assert pods "Name:" "Image:" "Node:" "Labels:" "Status:" "Created By" + kube::test::describe_resource_assert pods "Name:" "Image:" "Node:" "Labels:" "Status:" "Controlled By" # Clean up kubectl delete jobs pi "${kube_flags[@]}" # Post-condition: no pods exist. @@ -1345,17 +1392,28 @@ run_kubectl_get_tests() { fi ### Test kubectl get all - output_message=$(kubectl --v=6 --namespace default get all 2>&1 "${kube_flags[@]}") + output_message=$(kubectl --v=6 --namespace default get all --chunk-size=0 2>&1 "${kube_flags[@]}") # Post-condition: Check if we get 200 OK from all the url(s) kube::test::if_has_string "${output_message}" "/api/v1/namespaces/default/pods 200 OK" kube::test::if_has_string "${output_message}" "/api/v1/namespaces/default/replicationcontrollers 200 OK" kube::test::if_has_string "${output_message}" "/api/v1/namespaces/default/services 200 OK" - kube::test::if_has_string "${output_message}" "/apis/apps/v1beta1/namespaces/default/statefulsets 200 OK" + kube::test::if_has_string "${output_message}" "/apis/apps/v1/namespaces/default/statefulsets 200 OK" kube::test::if_has_string "${output_message}" "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers 200" kube::test::if_has_string "${output_message}" "/apis/batch/v1/namespaces/default/jobs 200 OK" kube::test::if_has_string "${output_message}" "/apis/extensions/v1beta1/namespaces/default/deployments 200 OK" kube::test::if_has_string "${output_message}" "/apis/extensions/v1beta1/namespaces/default/replicasets 200 OK" + ### Test kubectl get chunk size + output_message=$(kubectl --v=6 get clusterrole --chunk-size=10 2>&1 "${kube_flags[@]}") + # Post-condition: Check if we get a limit and continue + kube::test::if_has_string "${output_message}" "/clusterroles?limit=10 200 OK" + kube::test::if_has_string "${output_message}" "/v1/clusterroles?continue=" + + ### Test kubectl get chunk size defaults to 500 + output_message=$(kubectl --v=6 get clusterrole 2>&1 "${kube_flags[@]}") + # Post-condition: Check if we get a limit and continue + kube::test::if_has_string "${output_message}" "/clusterroles?limit=500 200 OK" + ### Test --allow-missing-template-keys # Pre-condition: no POD exists create_and_use_new_namespace @@ -2169,8 +2227,8 @@ run_secrets_test() { kubectl create secret docker-registry test-secret --docker-username=test-user --docker-password=test-password --docker-email='test-user@test.com' --namespace=test-secrets # Post-condition: secret exists and has expected values kube::test::get_object_assert 'secret/test-secret --namespace=test-secrets' "{{$id_field}}" 'test-secret' - kube::test::get_object_assert 'secret/test-secret --namespace=test-secrets' "{{$secret_type}}" 'kubernetes.io/dockercfg' - [[ "$(kubectl get secret/test-secret --namespace=test-secrets -o yaml "${kube_flags[@]}" | grep '.dockercfg:')" ]] + kube::test::get_object_assert 'secret/test-secret --namespace=test-secrets' "{{$secret_type}}" 'kubernetes.io/dockerconfigjson' + [[ "$(kubectl get secret/test-secret --namespace=test-secrets -o yaml "${kube_flags[@]}" | grep '.dockerconfigjson:')" ]] # Clean-up kubectl delete secret test-secret --namespace=test-secrets @@ -2698,7 +2756,7 @@ run_deployment_tests() { output_message=$(kubectl get deployment.extensions -o=jsonpath='{.items[0].apiVersion}' 2>&1 "${kube_flags[@]}") kube::test::if_has_string "${output_message}" 'extensions/v1beta1' output_message=$(kubectl get deployment.apps -o=jsonpath='{.items[0].apiVersion}' 2>&1 "${kube_flags[@]}") - kube::test::if_has_string "${output_message}" 'apps/v1beta1' + kube::test::if_has_string "${output_message}" 'apps/v1' # Clean up kubectl delete deployment test-nginx-extensions "${kube_flags[@]}" @@ -2713,11 +2771,11 @@ run_deployment_tests() { output_message=$(kubectl get deployment.extensions -o=jsonpath='{.items[0].apiVersion}' 2>&1 "${kube_flags[@]}") kube::test::if_has_string "${output_message}" 'extensions/v1beta1' output_message=$(kubectl get deployment.apps -o=jsonpath='{.items[0].apiVersion}' 2>&1 "${kube_flags[@]}") - kube::test::if_has_string "${output_message}" 'apps/v1beta1' + kube::test::if_has_string "${output_message}" 'apps/v1' # Describe command (resource only) should print detailed information kube::test::describe_resource_assert rs "Name:" "Pod Template:" "Labels:" "Selector:" "Controlled By" "Replicas:" "Pods Status:" "Volumes:" # Describe command (resource only) should print detailed information - kube::test::describe_resource_assert pods "Name:" "Image:" "Node:" "Labels:" "Status:" "Created By" "Controlled By" + kube::test::describe_resource_assert pods "Name:" "Image:" "Node:" "Labels:" "Status:" "Controlled By" # Clean up kubectl delete deployment test-nginx-apps "${kube_flags[@]}" @@ -2817,7 +2875,7 @@ run_deployment_tests() { kubectl get rs "${newrs}" -o yaml | grep "deployment.kubernetes.io/revision-history: 1,3" # Check that trying to watch the status of a superseded revision returns an error ! kubectl rollout status deployment/nginx --revision=3 - cat hack/testdata/deployment-revision1.yaml | $SED "s/name: nginx$/name: nginx2/" | kubectl create -f - "${kube_flags[@]}" + cat hack/testdata/deployment-revision1.yaml | ${SED} "s/name: nginx$/name: nginx2/" | kubectl create -f - "${kube_flags[@]}" # Deletion of both deployments should not be blocked kubectl delete deployment nginx2 "${kube_flags[@]}" # Clean up @@ -2957,7 +3015,7 @@ run_rs_tests() { # Describe command should print events information when show-events=true kube::test::describe_resource_events_assert rs true # Describe command (resource only) should print detailed information - kube::test::describe_resource_assert pods "Name:" "Image:" "Node:" "Labels:" "Status:" "Created By" "Controlled By" + kube::test::describe_resource_assert pods "Name:" "Image:" "Node:" "Labels:" "Status:" "Controlled By" ### Scale replica set frontend with current-replicas and replicas # Pre-condition: 3 replicas @@ -3297,7 +3355,7 @@ run_multi_resources_tests() { fi # Command: kubectl edit multiple resources temp_editor="${KUBE_TEMP}/tmp-editor.sh" - echo -e "#!/bin/bash\n$SED -i \"s/status\:\ replaced/status\:\ edited/g\" \$@" > "${temp_editor}" + echo -e "#!/bin/bash\n${SED} -i \"s/status\:\ replaced/status\:\ edited/g\" \$@" > "${temp_editor}" chmod +x "${temp_editor}" EDITOR="${temp_editor}" kubectl edit "${kube_flags[@]}" -f "${file}" # Post-condition: mock service (and mock2) and mock rc (and mock2) are edited @@ -3522,7 +3580,7 @@ run_clusterroles_tests() { kubectl set subject "${kube_flags[@]}" clusterrolebinding super-sa --serviceaccount=otherfoo:foo kube::test::get_object_assert clusterrolebinding/super-sa "{{range.subjects}}{{.namespace}}:{{end}}" 'otherns:otherfoo:' kube::test::get_object_assert clusterrolebinding/super-sa "{{range.subjects}}{{.name}}:{{end}}" 'sa-name:foo:' - + # test `kubectl create rolebinding` # test `kubectl set subject rolebinding` kubectl create "${kube_flags[@]}" rolebinding admin --clusterrole=admin --user=default-admin @@ -3648,6 +3706,11 @@ run_kubectl_create_error_tests() { fi rm "${ERROR_FILE}" + # Posting a pod to namespaces should fail. Also tests --raw forcing the post location + [ "$( kubectl convert -f test/fixtures/doc-yaml/admin/limitrange/valid-pod.yaml -o json | kubectl create "${kube_flags[@]}" --raw /api/v1/namespaces -f - --v=8 2>&1 | grep 'cannot be handled as a Namespace: converting (v1.Pod)')" ] + + [ "$( kubectl create "${kube_flags[@]}" --raw /api/v1/namespaces -f test/fixtures/doc-yaml/admin/limitrange/valid-pod.yaml --edit 2>&1 | grep 'raw and --edit are mutually exclusive')" ] + set +o nounset set +o errexit } @@ -3696,13 +3759,13 @@ run_client_config_tests() { # Pre-condition: context "missing-context" does not exist # Command output_message=$(! kubectl get pod --context="missing-context" 2>&1) - kube::test::if_has_string "${output_message}" 'context "missing-context" does not exist' + kube::test::if_has_string "${output_message}" 'context was not found for specified context: missing-context' # Post-condition: invalid or missing context returns error # Pre-condition: cluster "missing-cluster" does not exist # Command output_message=$(! kubectl get pod --cluster="missing-cluster" 2>&1) - kube::test::if_has_string "${output_message}" 'cluster "missing-cluster" does not exist' + kube::test::if_has_string "${output_message}" 'no server found for cluster "missing-cluster"' # Post-condition: invalid or missing cluster returns error # Pre-condition: user "missing-user" does not exist @@ -4173,6 +4236,14 @@ run_kubectl_all_namespace_tests() { # Post-condition: valid-pod doesn't exist kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" '' + ### Verify flag all-namespaces is ignored for rootScoped resources + # Pre-condition: node exists + kube::test::get_object_assert nodes "{{range.items}}{{$id_field}}:{{end}}" '127.0.0.1:' + # Command + output_message=$(kubectl get nodes --all-namespaces 2>&1) + # Post-condition: output with no NAMESPACE field + kube::test::if_has_not_string "${output_message}" "NAMESPACE" + set +o nounset set +o errexit } @@ -4229,6 +4300,51 @@ run_cluster_management_tests() { kube::test::get_object_assert nodes "{{range.items}}{{$id_field}}:{{end}}" '127.0.0.1:' + # create test pods we can work with + kubectl create -f - "${kube_flags[@]}" << __EOF__ +{ + "kind": "Pod", + "apiVersion": "v1", + "metadata": { + "name": "test-pod-1", + "labels": { + "e": "f" + } + }, + "spec": { + "containers": [ + { + "name": "container-1", + "resources": {}, + "image": "test-image" + } + ] + } +} +__EOF__ + + kubectl create -f - "${kube_flags[@]}" << __EOF__ +{ + "kind": "Pod", + "apiVersion": "v1", + "metadata": { + "name": "test-pod-2", + "labels": { + "c": "d" + } + }, + "spec": { + "containers": [ + { + "name": "container-1", + "resources": {}, + "image": "test-image" + } + ] + } +} +__EOF__ + ### kubectl cordon update with --dry-run does not mark node unschedulable # Pre-condition: node is schedulable kube::test::get_object_assert "nodes 127.0.0.1" "{{.spec.unschedulable}}" '' @@ -4243,6 +4359,20 @@ run_cluster_management_tests() { kube::test::get_object_assert nodes "{{range.items}}{{$id_field}}:{{end}}" '127.0.0.1:' kube::test::get_object_assert "nodes 127.0.0.1" "{{.spec.unschedulable}}" '' + ### kubectl drain with --pod-selector only evicts pods that match the given selector + # Pre-condition: node is schedulable + kube::test::get_object_assert "nodes 127.0.0.1" "{{.spec.unschedulable}}" '' + # Pre-condition: test-pod-1 and test-pod-2 exist + kube::test::get_object_assert "pods" "{{range .items}}{{.metadata.name}},{{end}}" 'test-pod-1,test-pod-2,' + kubectl drain "127.0.0.1" --pod-selector 'e in (f)' + # only "test-pod-1" should have been matched and deleted - test-pod-2 should still exist + kube::test::get_object_assert "pods/test-pod-2" "{{.metadata.name}}" 'test-pod-2' + # delete pod no longer in use + kubectl delete pod/test-pod-2 + # Post-condition: node is schedulable + kubectl uncordon "127.0.0.1" + kube::test::get_object_assert "nodes 127.0.0.1" "{{.spec.unschedulable}}" '' + ### kubectl uncordon update with --dry-run is a no-op # Pre-condition: node is already schedulable kube::test::get_object_assert "nodes 127.0.0.1" "{{.spec.unschedulable}}" '' @@ -4263,11 +4393,17 @@ run_cluster_management_tests() { response=$(! kubectl cordon 2>&1) kube::test::if_has_string "${response}" 'error\: USAGE\: cordon NODE' - ### kubectl cordon selects all nodes with an empty --selector= + ### kubectl cordon selects no nodes with an empty --selector= # Pre-condition: node "127.0.0.1" is uncordoned kubectl uncordon "127.0.0.1" - response=$(kubectl cordon --selector=) + response=$(! kubectl cordon --selector= 2>&1) + kube::test::if_has_string "${response}" 'must provide one or more resources' + # test=label matches our node + response=$(kubectl cordon --selector test=label) kube::test::if_has_string "${response}" 'node "127.0.0.1" cordoned' + # invalid=label does not match any nodes + response=$(kubectl cordon --selector invalid=label) + kube::test::if_has_not_string "${response}" 'cordoned' # Post-condition: node "127.0.0.1" is cordoned kube::test::get_object_assert "nodes 127.0.0.1" "{{.spec.unschedulable}}" 'true' @@ -4547,8 +4683,6 @@ runTests() { fi if kube::test::if_supports_resource "${pods}" ; then - # TODO: Move apply tests to run on rs instead of pods so that they can be - # run for federation apiserver as well. record_command run_kubectl_apply_tests record_command run_kubectl_run_tests record_command run_kubectl_create_filter_tests @@ -4563,18 +4697,22 @@ runTests() { ############### if kube::test::if_supports_resource "${pods}" ; then - # TODO: Move get tests to run on rs instead of pods so that they can be - # run for federation apiserver as well. record_command run_kubectl_get_tests fi + + ###################### + # Create # + ###################### + if kube::test::if_supports_resource "${secrets}" ; then + record_command run_create_secret_tests + fi + ################## # Global timeout # ################## if kube::test::if_supports_resource "${pods}" ; then - # TODO: Move request timeout tests to run on rs instead of pods so that they - # can be run for federation apiserver as well. record_command run_kubectl_request_timeout_tests fi @@ -4854,7 +4992,9 @@ runTests() { ############################ if kube::test::if_supports_resource "${pods}" ; then - record_command run_kubectl_all_namespace_tests + if kube::test::if_supports_resource "${nodes}" ; then + record_command run_kubectl_all_namespace_tests + fi fi ################ diff --git a/hack/make-rules/test-federation-cmd.sh b/hack/make-rules/test-federation-cmd.sh deleted file mode 100755 index 9543cb3c6d7..00000000000 --- a/hack/make-rules/test-federation-cmd.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/bin/bash - -# 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. - -# This command checks that the built commands can function together for -# simple scenarios. It does not require Docker. - -set -o errexit -set -o nounset -set -o pipefail - -KUBE_ROOT=$(dirname "${BASH_SOURCE}")/../.. -source "${KUBE_ROOT}/hack/lib/init.sh" -source "${KUBE_ROOT}/hack/lib/test.sh" -source "${KUBE_ROOT}/hack/make-rules/test-cmd-util.sh" - -function run_federation_apiserver() { - kube::log::status "Building federation-apiserver" - make -C "${KUBE_ROOT}" WHAT="federation/cmd/federation-apiserver" - - # Start federation-apiserver - kube::log::status "Starting federation-apiserver" - - # Admission Controllers to invoke prior to persisting objects in cluster - ADMISSION_CONTROL="NamespaceLifecycle" - - "${KUBE_OUTPUT_HOSTBIN}/federation-apiserver" \ - --insecure-port="${API_PORT}" \ - --secure-port="${SECURE_API_PORT}" \ - --admission-control="${ADMISSION_CONTROL}" \ - --etcd-servers="http://${ETCD_HOST}:${ETCD_PORT}" \ - --storage-media-type="${KUBE_TEST_API_STORAGE_TYPE-}" \ - --cert-dir="${TMPDIR:-/tmp/}" \ - --token-auth-file=hack/testdata/auth-tokens.csv 1>&2 & - APISERVER_PID=$! - - kube::util::wait_for_url "http://127.0.0.1:${API_PORT}/healthz" "apiserver" -} - -function run_federation_controller_manager() { - kube::log::status "Building federation-controller-manager" - make -C "${KUBE_ROOT}" WHAT="federation/cmd/federation-controller-manager" - - # Create a kubeconfig for federation apiserver. - local kubeconfig="${KUBE_TEMP}/kubeconfig" - touch "${kubeconfig}" - kubectl config set-cluster "apiserver" --server="http://127.0.0.1:${API_PORT}" --insecure-skip-tls-verify=true --kubeconfig="${kubeconfig}" - kubectl config set-context "context" --cluster="apiserver" --kubeconfig="${kubeconfig}" - kubectl config use-context "context" --kubeconfig="${kubeconfig}" - - # Start controller manager - kube::log::status "Starting federation-controller-manager" - "${KUBE_OUTPUT_HOSTBIN}/federation-controller-manager" \ - --port="${CTLRMGR_PORT}" \ - --kubeconfig="${kubeconfig}" \ - --kube-api-content-type="${KUBE_TEST_API_TYPE-}" \ - --controllers="service-dns=false" \ - --master="127.0.0.1:${API_PORT}" 1>&2 & - CTLRMGR_PID=$! - - kube::util::wait_for_url "http://127.0.0.1:${CTLRMGR_PORT}/healthz" "controller-manager" -} - -kube::log::status "Running kubectl tests for federation-apiserver" - -setup -run_federation_apiserver -run_federation_controller_manager -# TODO: Fix for replicasets and deployments. -SUPPORTED_RESOURCES=("configmaps" "daemonsets" "events" "ingress" "namespaces" "services" "secrets") -# Set wait for deletion to true for federation apiserver since resources are -# deleted asynchronously. -# This is a temporary workaround until https://github.com/kubernetes/kubernetes/issues/42594 is fixed. -WAIT_FOR_DELETION="true" -# WARNING: Do not wrap this call in a subshell to capture output, e.g. output=$(runTests) -# Doing so will suppress errexit behavior inside runTests -runTests - -kube::log::status "TESTS PASSED" diff --git a/hack/make-rules/test-integration.sh b/hack/make-rules/test-integration.sh index 53804c37f10..c180f45e89e 100755 --- a/hack/make-rules/test-integration.sh +++ b/hack/make-rules/test-integration.sh @@ -49,9 +49,6 @@ kube::test::find_integration_test_dirs() { find vendor/k8s.io/apiextensions-apiserver/test/integration/ -name '*_test.go' -print0 \ | xargs -0n1 dirname | sed "s|^|${KUBE_GO_PACKAGE}/|" \ | LC_ALL=C sort -u - find federation/test/integration/ -name '*_test.go' -print0 \ - | xargs -0n1 dirname | sed "s|^|${KUBE_GO_PACKAGE}/|" \ - | LC_ALL=C sort -u ) } diff --git a/hack/make-rules/test.sh b/hack/make-rules/test.sh index 151060ed8b0..54feeea8ea8 100755 --- a/hack/make-rules/test.sh +++ b/hack/make-rules/test.sh @@ -61,7 +61,6 @@ kube::test::find_dirs() { -o -path './third_party/*' \ -o -path './staging/*' \ -o -path './vendor/*' \ - -o -path './federation/test/*' \ \) -prune \ \) -name '*_test.go' -print0 | xargs -0n1 dirname | sed "s|^\./|${KUBE_GO_PACKAGE}/|" | LC_ALL=C sort -u @@ -113,7 +112,7 @@ KUBE_GOVERALLS_BIN=${KUBE_GOVERALLS_BIN:-} # "v1,compute/v1alpha1,experimental/v1alpha2;v1,compute/v2,experimental/v1alpha3" # FIXME: due to current implementation of a test client (see: pkg/api/testapi/testapi.go) # ONLY the last version is tested in each group. -ALL_VERSIONS_CSV=$(IFS=',';echo "${KUBE_AVAILABLE_GROUP_VERSIONS[*]// /,}";IFS=$),federation/v1beta1 +ALL_VERSIONS_CSV=$(IFS=',';echo "${KUBE_AVAILABLE_GROUP_VERSIONS[*]// /,}";IFS=$) KUBE_TEST_API_VERSIONS="${KUBE_TEST_API_VERSIONS:-${ALL_VERSIONS_CSV}}" # once we have multiple group supports # Create a junit-style XML test report in this directory if set. @@ -289,7 +288,7 @@ runTests() { ${KUBE_RACE} ${KUBE_TIMEOUT} "${@}" \ "${testargs[@]:+${testargs[@]}}" \ | tee ${junit_filename_prefix:+"${junit_filename_prefix}.stdout"} \ - | grep "${go_test_grep_pattern}" && rc=$? || rc=$? + | grep --binary-files=text "${go_test_grep_pattern}" && rc=$? || rc=$? produceJUnitXMLReport "${junit_filename_prefix}" return ${rc} fi diff --git a/hack/make-rules/verify.sh b/hack/make-rules/verify.sh index 8545d681c00..97018e12f48 100755 --- a/hack/make-rules/verify.sh +++ b/hack/make-rules/verify.sh @@ -39,10 +39,10 @@ QUICK_PATTERNS+=( "verify-generated-files-remake" "verify-godep-licenses.sh" "verify-gofmt.sh" + "verify-imports.sh" "verify-pkg-names.sh" "verify-readonly-packages.sh" "verify-staging-client-go.sh" - "verify-staging-imports.sh" "verify-test-images.sh" "verify-test-owners.sh" ) @@ -92,24 +92,26 @@ function run-checks { local -r pattern=$1 local -r runner=$2 + local t for t in $(ls ${pattern}) do + local check_name="$(basename "${t}")" if is-excluded "${t}" ; then - echo "Skipping ${t}" + echo "Skipping ${check_name}" continue fi if ${QUICK} && ! is-quick "${t}" ; then - echo "Skipping ${t} in quick mode" + echo "Skipping ${check_name} in quick mode" continue fi - echo -e "Verifying ${t}" + echo -e "Verifying ${check_name}" local start=$(date +%s) run-cmd "${runner}" "${t}" && tr=$? || tr=$? local elapsed=$(($(date +%s) - ${start})) if [[ ${tr} -eq 0 ]]; then - echo -e "${color_green}SUCCESS${color_norm} ${t}\t${elapsed}s" + echo -e "${color_green}SUCCESS${color_norm} ${check_name}\t${elapsed}s" else - echo -e "${color_red}FAILED${color_norm} ${t}\t${elapsed}s" + echo -e "${color_red}FAILED${color_norm} ${check_name}\t${elapsed}s" ret=1 FAILED_TESTS+=(${t}) fi diff --git a/hack/staging-import-restrictions.json b/hack/staging-import-restrictions.json deleted file mode 100644 index 76e4e3b6b34..00000000000 --- a/hack/staging-import-restrictions.json +++ /dev/null @@ -1,92 +0,0 @@ -[ - { - "baseImportPath": "./vendor/k8s.io/apimachinery/", - "allowedImports": [ - "k8s.io/apimachinery", - "k8s.io/kube-openapi" - ] - }, - { - "baseImportPath": "./vendor/k8s.io/api/", - "allowedImports": [ - "k8s.io/api", - "k8s.io/apimachinery" - ] - }, - { - "baseImportPath": "./vendor/k8s.io/code-generator/", - "ignoredSubTrees": [ - "./vendor/k8s.io/code-generator/_test" - ], - "allowedImports": [ - "k8s.io/gengo", - "k8s.io/code-generator", - "k8s.io/kube-openapi" - ] - }, - { - "baseImportPath": "./vendor/k8s.io/client-go/", - "allowedImports": [ - "k8s.io/api", - "k8s.io/apimachinery", - "k8s.io/client-go" - ] - }, - { - "baseImportPath": "./vendor/k8s.io/apiserver/", - "allowedImports": [ - "k8s.io/api", - "k8s.io/apimachinery", - "k8s.io/apiserver", - "k8s.io/client-go", - "k8s.io/kube-openapi" - ] - }, - { - "baseImportPath": "./vendor/k8s.io/metrics/", - "allowedImports": [ - "k8s.io/api", - "k8s.io/apimachinery", - "k8s.io/client-go", - "k8s.io/metrics" - ] - }, - { - "baseImportPath": "./vendor/k8s.io/kube-aggregator/", - "allowedImports": [ - "k8s.io/api", - "k8s.io/apimachinery", - "k8s.io/apiserver", - "k8s.io/client-go", - "k8s.io/kube-aggregator", - "k8s.io/kube-openapi" - ] - }, - { - "baseImportPath": "./vendor/k8s.io/sample-apiserver/", - "allowedImports": [ - "k8s.io/api", - "k8s.io/apimachinery", - "k8s.io/apiserver", - "k8s.io/client-go", - "k8s.io/sample-apiserver" - ] - }, - { - "baseImportPath": "./vendor/k8s.io/apiextensions-apiserver/", - "allowedImports": [ - "k8s.io/api", - "k8s.io/apiextensions-apiserver", - "k8s.io/apimachinery", - "k8s.io/apiserver", - "k8s.io/client-go" - ] - }, - { - "baseImportPath": "./vendor/k8s.io/kube-openapi/", - "allowedImports": [ - "k8s.io/kube-openapi", - "k8s.io/gengo" - ] - } -] \ No newline at end of file diff --git a/hack/testdata/deployment-label-change1.yaml b/hack/testdata/deployment-label-change1.yaml new file mode 100644 index 00000000000..cfcbae3d741 --- /dev/null +++ b/hack/testdata/deployment-label-change1.yaml @@ -0,0 +1,18 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: nginx + labels: + name: nginx +spec: + replicas: 3 + template: + metadata: + labels: + name: nginx1 + spec: + containers: + - name: nginx + image: gcr.io/google-containers/nginx:test-cmd + ports: + - containerPort: 80 diff --git a/hack/testdata/deployment-label-change2.yaml b/hack/testdata/deployment-label-change2.yaml new file mode 100644 index 00000000000..7f551d32112 --- /dev/null +++ b/hack/testdata/deployment-label-change2.yaml @@ -0,0 +1,18 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: nginx + labels: + name: nginx +spec: + replicas: 3 + template: + metadata: + labels: + name: nginx2 + spec: + containers: + - name: nginx + image: gcr.io/google-containers/nginx:test-cmd + ports: + - containerPort: 80 diff --git a/hack/testdata/pod-with-large-name.yaml b/hack/testdata/pod-with-large-name.yaml index 8e8f0e07cac..5d6276ba00f 100644 --- a/hack/testdata/pod-with-large-name.yaml +++ b/hack/testdata/pod-with-large-name.yaml @@ -8,4 +8,4 @@ metadata: spec: containers: - name: kubernetes-serve-hostname - image: gcr.io/google_containers/serve_hostname:v1.4 + image: gcr.io/kubernetes-e2e-test-images/serve-hostname-amd64:1.1 diff --git a/hack/testdata/pod-with-precision.json b/hack/testdata/pod-with-precision.json index 5aac946cd2b..ce59d9c100b 100644 --- a/hack/testdata/pod-with-precision.json +++ b/hack/testdata/pod-with-precision.json @@ -9,7 +9,7 @@ "containers": [ { "name": "kubernetes-pause", - "image": "gcr.io/google_containers/pause-amd64:3.0" + "image": "gcr.io/google_containers/pause-amd64:3.1" } ], "restartPolicy": "Never", diff --git a/hack/update-all.sh b/hack/update-all.sh index 7fbfeb84af4..71c58f28807 100755 --- a/hack/update-all.sh +++ b/hack/update-all.sh @@ -58,15 +58,13 @@ fi BASH_TARGETS=" update-generated-protobuf update-codegen + update-generated-runtime + update-generated-device-plugin update-generated-docs update-generated-swagger-docs update-swagger-spec update-openapi-spec update-api-reference-docs - update-federation-openapi-spec - update-federation-swagger-spec - update-federation-generated-swagger-docs - update-federation-api-reference-docs update-staging-godeps update-bazel" diff --git a/hack/update-bazel.sh b/hack/update-bazel.sh index 6bfa9b062d2..e569d94e9c7 100755 --- a/hack/update-bazel.sh +++ b/hack/update-bazel.sh @@ -20,14 +20,7 @@ set -o pipefail export KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. source "${KUBE_ROOT}/hack/lib/init.sh" -if LANG=C sed --help 2>&1 | grep -q GNU; then - SED="sed" -elif which gsed &>/dev/null; then - SED="gsed" -else - echo "Failed to find GNU sed as sed or gsed. If you are on Mac: brew install gnu-sed." >&2 - exit 1 -fi +kube::util::ensure-gnu-sed # Remove generated files prior to running kazel. # TODO(spxtr): Remove this line once Bazel is the only way to build. @@ -36,10 +29,10 @@ rm -f "${KUBE_ROOT}/pkg/generated/openapi/zz_generated.openapi.go" # The git commit sha1s here should match the values in $KUBE_ROOT/WORKSPACE. kube::util::go_install_from_commit \ github.com/kubernetes/repo-infra/kazel \ - e26fc85d14a1d3dc25569831acc06919673c545a + ae4e9a3906ace4ba657b7a09242610c6266e832c kube::util::go_install_from_commit \ github.com/bazelbuild/rules_go/go/tools/gazelle/gazelle \ - a280fbac1a0a4c67b0eee660b4fd1b3db7c9f058 + 737df20c53499fd84b67f04c6ca9ccdee2e77089 touch "${KUBE_ROOT}/vendor/BUILD" diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index 8fae4073c72..9c29807b438 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -40,6 +40,7 @@ informergen=$(kube::util::find-binary "informer-gen") GROUP_VERSIONS=(${KUBE_AVAILABLE_GROUP_VERSIONS}) GV_DIRS=() +INTERNAL_DIRS=() for gv in "${GROUP_VERSIONS[@]}"; do # add items, but strip off any leading apis/ you find to match command expectations api_dir=$(kube::util::group-version-to-pkg-path "${gv}") @@ -47,28 +48,36 @@ for gv in "${GROUP_VERSIONS[@]}"; do nopkg_dir=${nopkg_dir#vendor/k8s.io/api/} pkg_dir=${nopkg_dir#apis/} + # skip groups that aren't being served, clients for these don't matter if [[ " ${KUBE_NONSERVER_GROUP_VERSIONS} " == *" ${gv} "* ]]; then continue fi GV_DIRS+=("${pkg_dir}") + + # collect internal groups + int_group="${pkg_dir%/*}/" + if [[ "${pkg_dir}" = core/* ]]; then + int_group="api/" + fi + if ! [[ " ${INTERNAL_DIRS[@]:-} " =~ " ${int_group} " ]]; then + INTERNAL_DIRS+=("${int_group}") + fi done # delimit by commas for the command GV_DIRS_CSV=$(IFS=',';echo "${GV_DIRS[*]// /,}";IFS=$) +INTERNAL_DIRS_CSV=$(IFS=',';echo "${INTERNAL_DIRS[*]// /,}";IFS=$) # This can be called with one flag, --verify-only, so it works for both the # update- and verify- scripts. -${clientgen} "$@" -${clientgen} --output-base "${KUBE_ROOT}/vendor" --clientset-path="k8s.io/client-go" --clientset-name="kubernetes" --input-base="k8s.io/kubernetes/vendor/k8s.io/api" --input="${GV_DIRS_CSV}" "$@" -# Clientgen for federation clientset. -${clientgen} --clientset-name=federation_clientset --clientset-path=k8s.io/kubernetes/federation/client/clientset_generated --input-base="k8s.io/kubernetes/vendor/k8s.io/api" --input="../../../federation/apis/federation/v1beta1","core/v1","extensions/v1beta1","batch/v1","autoscaling/v1" --included-types-overrides="core/v1/Service,core/v1/Namespace,extensions/v1beta1/ReplicaSet,core/v1/Secret,extensions/v1beta1/Ingress,extensions/v1beta1/Deployment,extensions/v1beta1/DaemonSet,core/v1/ConfigMap,core/v1/Event,batch/v1/Job,autoscaling/v1/HorizontalPodAutoscaler" "$@" +${clientgen} --input-base="k8s.io/kubernetes/pkg/apis" --input="${INTERNAL_DIRS_CSV}" "$@" +${clientgen} --output-base "${KUBE_ROOT}/vendor" --output-package="k8s.io/client-go" --clientset-name="kubernetes" --input-base="k8s.io/kubernetes/vendor/k8s.io/api" --input="${GV_DIRS_CSV}" "$@" listergen_internal_apis=( -pkg/api $( cd ${KUBE_ROOT} - find pkg/apis -maxdepth 2 -name types.go | xargs -n1 dirname | sort + find pkg/apis -maxdepth 2 -name types.go | xargs -n1 dirname | sort ) ) listergen_internal_apis=(${listergen_internal_apis[@]/#/k8s.io/kubernetes/}) @@ -78,18 +87,16 @@ ${listergen} --input-dirs "${listergen_internal_apis_csv}" "$@" listergen_external_apis=( $( cd ${KUBE_ROOT}/staging/src - # because client-gen doesn't do policy/v1alpha1, we have to skip it too - find k8s.io/api -name types.go | xargs -n1 dirname | sort | grep -v pkg.apis.policy.v1alpha1 + find k8s.io/api -name types.go | xargs -n1 dirname | sort ) ) listergen_external_apis_csv=$(IFS=,; echo "${listergen_external_apis[*]}") ${listergen} --output-base "${KUBE_ROOT}/vendor" --output-package "k8s.io/client-go/listers" --input-dirs "${listergen_external_apis_csv}" "$@" informergen_internal_apis=( -pkg/api $( cd ${KUBE_ROOT} - find pkg/apis -maxdepth 2 -name types.go | xargs -n1 dirname | sort + find pkg/apis -maxdepth 2 -name types.go | xargs -n1 dirname | sort ) ) informergen_internal_apis=(${informergen_internal_apis[@]/#/k8s.io/kubernetes/}) @@ -125,5 +132,6 @@ ${informergen} \ CODEGEN_PKG=./vendor/k8s.io/code-generator vendor/k8s.io/code-generator/hack/update-codegen.sh CODEGEN_PKG=./vendor/k8s.io/code-generator vendor/k8s.io/kube-aggregator/hack/update-codegen.sh CODEGEN_PKG=./vendor/k8s.io/code-generator vendor/k8s.io/sample-apiserver/hack/update-codegen.sh +CODEGEN_PKG=./vendor/k8s.io/code-generator vendor/k8s.io/sample-controller/hack/update-codegen.sh CODEGEN_PKG=./vendor/k8s.io/code-generator vendor/k8s.io/apiextensions-apiserver/hack/update-codegen.sh CODEGEN_PKG=./vendor/k8s.io/code-generator vendor/k8s.io/metrics/hack/update-codegen.sh diff --git a/hack/update-federation-api-reference-docs.sh b/hack/update-federation-api-reference-docs.sh deleted file mode 100755 index c4f7e6818f8..00000000000 --- a/hack/update-federation-api-reference-docs.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash - -# 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. - -# Generates updated api-reference docs from the latest swagger spec for -# federation apiserver. The docs are generated at federation/docs/api-reference -# Usage: ./update-federation-api-reference-docs.sh - -set -o errexit -set -o nounset -set -o pipefail - -echo "Note: This assumes that swagger spec has been updated. Please run hack/update-federation-swagger-spec.sh to ensure that." - -KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. -source "${KUBE_ROOT}/hack/lib/init.sh" -source "${KUBE_ROOT}/hack/lib/swagger.sh" -kube::golang::setup_env - -REPO_DIR=${REPO_DIR:-"${KUBE_ROOT}"} -DEFAULT_OUTPUT="${REPO_DIR}/federation/docs/api-reference" -OUTPUT=${1:-${DEFAULT_OUTPUT}} - -SWAGGER_SPEC_PATH="${REPO_DIR}/federation/apis/swagger-spec" - -GROUP_VERSIONS=("federation/v1beta1" "v1" "extensions/v1beta1") -GV_DIRS=() -for gv in "${GROUP_VERSIONS[@]}"; do - if [[ ${gv} == "federation/v1beta1" ]]; then - GV_DIRS+=("${REPO_DIR}/$(kube::util::group-version-to-pkg-path "${gv}")") - else - GV_DIRS+=("${REPO_DIR}/$(kube::util::group-version-to-pkg-path "${gv}")") - fi -done - -GROUP_VERSIONS="${GROUP_VERSIONS[@]}" GV_DIRS="${GV_DIRS[@]}" kube::swagger::gen_api_ref_docs "${SWAGGER_SPEC_PATH}" "${OUTPUT}" - -# ex: ts=2 sw=2 et filetype=sh diff --git a/hack/update-federation-generated-swagger-docs.sh b/hack/update-federation-generated-swagger-docs.sh deleted file mode 100755 index c6ff916bdc2..00000000000 --- a/hack/update-federation-generated-swagger-docs.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash - -# 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. - -# Generates `types_swagger_doc_generated.go` files for federation API group -# versions. That file contains functions on API structs that return the comments -# that should be surfaced for the corresponding API type in our API docs. - -set -o errexit -set -o nounset -set -o pipefail - -KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. -source "${KUBE_ROOT}/hack/lib/init.sh" -source "${KUBE_ROOT}/hack/lib/swagger.sh" - -kube::golang::setup_env - -GROUP_VERSIONS=(federation/v1beta1) -GV_DIRS=() -for gv in "${GROUP_VERSIONS[@]}"; do - GV_DIRS+=("$(kube::util::group-version-to-pkg-path "${gv}")") -done - -# To avoid compile errors, remove the currently existing files. -for gv_dir in "${GV_DIRS[@]}"; do - rm -f "${gv_dir}/types_swagger_doc_generated.go" -done -for i in "${!GROUP_VERSIONS[@]}"; do - kube::swagger::gen_types_swagger_doc "${GROUP_VERSIONS[i]}" "${GV_DIRS[i]}" -done diff --git a/hack/update-federation-openapi-spec.sh b/hack/update-federation-openapi-spec.sh deleted file mode 100755 index aa9d08981b5..00000000000 --- a/hack/update-federation-openapi-spec.sh +++ /dev/null @@ -1,74 +0,0 @@ -#!/bin/bash - -# 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. - -# Script to fetch latest openapi spec from federation-apiserver -# Puts the updated spec at federation/apis/openapi-spec/ - -set -o errexit -set -o nounset -set -o pipefail - -KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. -OPENAPI_ROOT_DIR="${KUBE_ROOT}/federation/apis/openapi-spec" -source "${KUBE_ROOT}/hack/lib/init.sh" - -kube::golang::setup_env - -make -C "${KUBE_ROOT}" WHAT="cmd/hyperkube" - -function cleanup() -{ - [[ -n "${APISERVER_PID-}" ]] && kill ${APISERVER_PID} 1>&2 2>/dev/null - - kube::etcd::cleanup - - kube::log::status "Clean up complete" -} - -trap cleanup EXIT SIGINT - -kube::golang::setup_env - -TMP_DIR=$(mktemp -d /tmp/update-federation-openapi-spec.XXXX) -ETCD_HOST=${ETCD_HOST:-127.0.0.1} -ETCD_PORT=${ETCD_PORT:-2379} -API_PORT=${API_PORT:-8050} -API_HOST=${API_HOST:-127.0.0.1} - -kube::etcd::start - -echo "dummy_token,admin,admin" > $TMP_DIR/tokenauth.csv - -# Start federation-apiserver -kube::log::status "Starting federation-apiserver" -"${KUBE_OUTPUT_HOSTBIN}/hyperkube" federation-apiserver \ - --insecure-bind-address="${API_HOST}" \ - --bind-address="${API_HOST}" \ - --insecure-port="${API_PORT}" \ - --etcd-servers="http://${ETCD_HOST}:${ETCD_PORT}" \ - --advertise-address="10.10.10.10" \ - --cert-dir="${TMP_DIR}/certs" \ - --token-auth-file=$TMP_DIR/tokenauth.csv >/tmp/openapi-federation-api-server.log 2>&1 & -APISERVER_PID=$! -kube::util::wait_for_url "${API_HOST}:${API_PORT}/healthz" "apiserver: " - -kube::log::status "Updating " ${OPENAPI_ROOT_DIR} - -curl -w "\n" -fs "${API_HOST}:${API_PORT}/swagger.json" > "${OPENAPI_ROOT_DIR}/swagger.json" - -kube::log::status "SUCCESS" - -# ex: ts=2 sw=2 et filetype=sh diff --git a/hack/update-federation-swagger-spec.sh b/hack/update-federation-swagger-spec.sh deleted file mode 100755 index 7c46a2938a5..00000000000 --- a/hack/update-federation-swagger-spec.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/bin/bash - -# 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. - -# Script to fetch latest swagger spec from federation-apiserver -# Puts the updated spec at federation/apis/swagger-spec/ - -set -o errexit -set -o nounset -set -o pipefail - -cat << __EOF__ -Note: This assumes that the 'types_swagger_doc_generated.go' file has been -updated for all API group versions. If you are unsure, please run -hack/update-generated-swagger-docs.sh and -hack/update-federation-generated-swagger-docs.sh first. -__EOF__ - -KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. -SWAGGER_ROOT_DIR="${KUBE_ROOT}/federation/apis/swagger-spec" -source "${KUBE_ROOT}/hack/lib/init.sh" - -kube::golang::setup_env - -make -C "${KUBE_ROOT}" WHAT="cmd/hyperkube" - -function cleanup() -{ - [[ -n ${APISERVER_PID-} ]] && kill ${APISERVER_PID} 1>&2 2>/dev/null - - kube::etcd::cleanup - - kube::log::status "Clean up complete" -} - -trap cleanup EXIT SIGINT - -kube::golang::setup_env - -TMP_DIR=$(mktemp -d /tmp/update-federation-swagger-spec.XXXX) -ETCD_HOST=${ETCD_HOST:-127.0.0.1} -ETCD_PORT=${ETCD_PORT:-2379} -API_PORT=${API_PORT:-8050} -API_HOST=${API_HOST:-127.0.0.1} - -kube::etcd::start - -# Start federation-apiserver -kube::log::status "Starting federation-apiserver" -"${KUBE_OUTPUT_HOSTBIN}/hyperkube" federation-apiserver \ - --insecure-bind-address="${API_HOST}" \ - --bind-address="${API_HOST}" \ - --insecure-port="${API_PORT}" \ - --etcd-servers="http://${ETCD_HOST}:${ETCD_PORT}" \ - --advertise-address="10.10.10.10" \ - --cert-dir="${TMP_DIR}/certs" >/tmp/swagger-federation-api-server.log 2>&1 & -APISERVER_PID=$! - -kube::util::wait_for_url "${API_HOST}:${API_PORT}/" "apiserver: " - -SWAGGER_API_PATH="${API_HOST}:${API_PORT}/swaggerapi/" -DEFAULT_GROUP_VERSIONS="v1 extensions/v1beta1 federation/v1beta1" -VERSIONS=${VERSIONS:-$DEFAULT_GROUP_VERSIONS} - -kube::log::status "Updating " ${SWAGGER_ROOT_DIR} - -SWAGGER_API_PATH="${SWAGGER_API_PATH}" SWAGGER_ROOT_DIR="${SWAGGER_ROOT_DIR}" VERSIONS="${VERSIONS}" kube::util::fetch-swagger-spec - -kube::log::status "SUCCESS" - -# ex: ts=2 sw=2 et filetype=sh diff --git a/hack/update-generated-device-plugin-dockerized.sh b/hack/update-generated-device-plugin-dockerized.sh index 15003fd0ce0..0398058b03f 100755 --- a/hack/update-generated-device-plugin-dockerized.sh +++ b/hack/update-generated-device-plugin-dockerized.sh @@ -19,8 +19,7 @@ set -o nounset set -o pipefail KUBE_ROOT="$(cd "$(dirname "${BASH_SOURCE}")/../" && pwd -P)" -DEVICE_PLUGIN_ROOT="${KUBE_ROOT}/pkg/kubelet/apis/deviceplugin/v1alpha1/" +DEVICE_PLUGIN_ROOT="${KUBE_ROOT}/pkg/kubelet/apis/deviceplugin/v1alpha/" source "${KUBE_ROOT}/hack/lib/protoc.sh" - kube::protoc::generate_proto ${DEVICE_PLUGIN_ROOT} diff --git a/hack/update-generated-docs.sh b/hack/update-generated-docs.sh index 38595ff71b0..6d5ed939ff1 100755 --- a/hack/update-generated-docs.sh +++ b/hack/update-generated-docs.sh @@ -32,7 +32,6 @@ BINS=( cmd/genkubedocs cmd/genman cmd/genyaml - federation/cmd/genfeddocs ) make -C "${KUBE_ROOT}" WHAT="${BINS[*]}" diff --git a/hack/update-generated-protobuf-dockerized.sh b/hack/update-generated-protobuf-dockerized.sh index 3ab8608b36a..761d3700e00 100755 --- a/hack/update-generated-protobuf-dockerized.sh +++ b/hack/update-generated-protobuf-dockerized.sh @@ -60,6 +60,7 @@ PACKAGES=( k8s.io/api/apps/v1 k8s.io/api/authentication/v1 k8s.io/api/authentication/v1beta1 + k8s.io/api/events/v1beta1 k8s.io/api/rbac/v1alpha1 k8s.io/api/rbac/v1beta1 k8s.io/api/rbac/v1 @@ -67,17 +68,19 @@ PACKAGES=( k8s.io/api/imagepolicy/v1alpha1 k8s.io/api/scheduling/v1alpha1 k8s.io/api/settings/v1alpha1 + k8s.io/api/storage/v1alpha1 k8s.io/api/storage/v1beta1 k8s.io/api/storage/v1 k8s.io/api/admissionregistration/v1alpha1 - k8s.io/api/admission/v1alpha1 + k8s.io/api/admissionregistration/v1beta1 + k8s.io/api/admission/v1beta1 k8s.io/api/networking/v1 - k8s.io/kubernetes/federation/apis/federation/v1beta1 k8s.io/metrics/pkg/apis/metrics/v1alpha1 k8s.io/metrics/pkg/apis/metrics/v1beta1 k8s.io/metrics/pkg/apis/custom_metrics/v1beta1 k8s.io/apiserver/pkg/apis/audit/v1alpha1 k8s.io/apiserver/pkg/apis/audit/v1beta1 + k8s.io/apiserver/pkg/apis/example2/v1 ) # requires the 'proto' tag to build (will remove when ready) diff --git a/hack/update-generated-swagger-docs.sh b/hack/update-generated-swagger-docs.sh index 44e5458bbcc..20aef7fa0fa 100755 --- a/hack/update-generated-swagger-docs.sh +++ b/hack/update-generated-swagger-docs.sh @@ -14,9 +14,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Generates `types_swagger_doc_generated.go` files for federation API group -# versions. That file contains functions on API structs that return the comments -# that should be surfaced for the corresponding API type in our API docs. +# Generates `types_swagger_doc_generated.go` files for API group +# versions. That file contains functions on API structs that return +# the comments that should be surfaced for the corresponding API type +# in our API docs. set -o errexit set -o nounset diff --git a/hack/update-godep-licenses.sh b/hack/update-godep-licenses.sh index 93f10f7df2a..8d5a38c20ac 100755 --- a/hack/update-godep-licenses.sh +++ b/hack/update-godep-licenses.sh @@ -75,6 +75,14 @@ process_content () { go4.org/*) package_root=$(echo ${package} |awk -F/ '{ print $1 }') ;; + gopkg.in/*) + # Root of gopkg.in package always ends with '.v(number)' and my contain + # more than two path elements. For example: + # - gopkg.in/yaml.v2 + # - gopkg.in/inf.v0 + # - gopkg.in/square/go-jose.v2 + package_root=$(echo ${package} |grep -oh '.*\.v[0-9]') + ;; *) package_root=$(echo ${package} |awk -F/ '{ print $1"/"$2 }') ;; diff --git a/hack/verify-api-reference-docs.sh b/hack/verify-api-reference-docs.sh index ec1dca69911..e41256f9c9a 100755 --- a/hack/verify-api-reference-docs.sh +++ b/hack/verify-api-reference-docs.sh @@ -36,7 +36,7 @@ trap "rm -rf ${TMP_ROOT}" EXIT SIGINT echo "diffing ${API_REFERENCE_DOCS_ROOT} against freshly generated docs" ret=0 -diff -NauprB -I 'Last update' --exclude=*.md "${API_REFERENCE_DOCS_ROOT}" "${OUTPUT_DIR}" || ret=$? +diff -NauprB -I 'Last update' --exclude=*.md --exclude=OWNERS "${API_REFERENCE_DOCS_ROOT}" "${OUTPUT_DIR}" || ret=$? if [[ $ret -eq 0 ]] then echo "${API_REFERENCE_DOCS_ROOT} up to date." diff --git a/hack/verify-codegen.sh b/hack/verify-codegen.sh index af94a334ae0..02b979c2f9a 100755 --- a/hack/verify-codegen.sh +++ b/hack/verify-codegen.sh @@ -30,6 +30,7 @@ kube::golang::setup_env CODEGEN_PKG=./vendor/k8s.io/code-generator vendor/k8s.io/code-generator/hack/verify-codegen.sh CODEGEN_PKG=./vendor/k8s.io/code-generator vendor/k8s.io/kube-aggregator/hack/verify-codegen.sh CODEGEN_PKG=./vendor/k8s.io/code-generator vendor/k8s.io/sample-apiserver/hack/verify-codegen.sh +CODEGEN_PKG=./vendor/k8s.io/code-generator vendor/k8s.io/sample-controller/hack/verify-codegen.sh CODEGEN_PKG=./vendor/k8s.io/code-generator vendor/k8s.io/apiextensions-apiserver/hack/verify-codegen.sh CODEGEN_PKG=./vendor/k8s.io/code-generator vendor/k8s.io/metrics/hack/verify-codegen.sh diff --git a/hack/verify-description.sh b/hack/verify-description.sh index 8fca1b2fc85..95bb0680031 100755 --- a/hack/verify-description.sh +++ b/hack/verify-description.sh @@ -43,8 +43,7 @@ find_files() { -o -wholename '*/vendor/*' \ \) -prune \ \) \ - \( -wholename '*pkg/api/v*/types.go' \ - -o -wholename '*pkg/apis/*/v*/types.go' \ + \( -wholename '*pkg/apis/*/v*/types.go' \ -o -wholename '*pkg/api/unversioned/types.go' \ \) } @@ -71,7 +70,7 @@ for file in $versioned_api_files; do fi done -internal_types_files="${KUBE_ROOT}/pkg/api/types.go ${KUBE_ROOT}/pkg/apis/extensions/types.go" +internal_types_files="${KUBE_ROOT}/pkg/apis/core/types.go ${KUBE_ROOT}/pkg/apis/extensions/types.go" for internal_types_file in $internal_types_files; do if [[ ! -e $internal_types_file ]]; then echo "Internal types file ${internal_types_file} does not exist" diff --git a/hack/verify-federation-api-reference-docs.sh b/hack/verify-federation-api-reference-docs.sh deleted file mode 100755 index 6cf951866ec..00000000000 --- a/hack/verify-federation-api-reference-docs.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash - -# Copyright 2017 The Kubernetes Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Verifies that api reference docs are up to date. - -set -o errexit -set -o nounset -set -o pipefail - -KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. -source "${KUBE_ROOT}/hack/lib/init.sh" - -kube::golang::setup_env - -API_REFERENCE_DOCS_ROOT="${KUBE_ROOT}/federation/docs/api-reference" -OUTPUT_DIR="${KUBE_ROOT}/_tmp_federation/api-reference" -mkdir -p ${OUTPUT_DIR} -TMP_ROOT="${KUBE_ROOT}/_tmp_federation" -trap "rm -rf ${TMP_ROOT}" EXIT SIGINT - -# Generate API reference docs in tmp. -"./hack/update-federation-api-reference-docs.sh" "${OUTPUT_DIR}" - -echo "diffing ${API_REFERENCE_DOCS_ROOT} against freshly generated docs" -ret=0 -diff -NauprB -I 'Last update' --exclude=*.md "${API_REFERENCE_DOCS_ROOT}" "${OUTPUT_DIR}" || ret=$? -if [[ $ret -eq 0 ]] -then - echo "${API_REFERENCE_DOCS_ROOT} up to date." -else - echo "${API_REFERENCE_DOCS_ROOT} is out of date. Please run hack/update-federation-api-reference-docs.sh" - exit 1 -fi - -# ex: ts=2 sw=2 et filetype=sh diff --git a/hack/verify-federation-generated-swagger-docs.sh b/hack/verify-federation-generated-swagger-docs.sh deleted file mode 100755 index 36e25a970e0..00000000000 --- a/hack/verify-federation-generated-swagger-docs.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/bash - -# Copyright 2017 The Kubernetes Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -o errexit -set -o nounset -set -o pipefail - -KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. -source "${KUBE_ROOT}/hack/lib/init.sh" - -kube::golang::setup_env - -make -C "${KUBE_ROOT}" WHAT=cmd/genswaggertypedocs - -# Find binary -genswaggertypedocs=$(kube::util::find-binary "genswaggertypedocs") - -if [[ ! -x "$genswaggertypedocs" ]]; then - { - echo "It looks as if you don't have a compiled genswaggertypedocs binary" - echo - echo "If you are running from a clone of the git repo, please run" - echo "'make WHAT=cmd/genswaggertypedocs'." - } >&2 - exit 1 -fi - -DIFFROOT="${KUBE_ROOT}/federation" -TMP_DIFFROOT="${KUBE_ROOT}/_tmp/federation" -_tmp="${KUBE_ROOT}/_tmp_federation" - -cleanup() { - rm -rf "${_tmp}" -} -trap "cleanup" EXIT SIGINT - -cleanup - -mkdir -p "${TMP_DIFFROOT}" -cp -a "${DIFFROOT}"/* "${TMP_DIFFROOT}/" - -"${KUBE_ROOT}/hack/update-federation-generated-swagger-docs.sh" -echo "diffing ${DIFFROOT} against freshly generated swagger type documentation" -ret=0 -diff -Naupr -I 'Auto generated by' "${DIFFROOT}" "${TMP_DIFFROOT}" || ret=$? -cp -a "${TMP_DIFFROOT}"/* "${DIFFROOT}/" -if [[ $ret -eq 0 ]] -then - echo "${DIFFROOT} up to date." -else - echo "${DIFFROOT} is out of date. Please run hack/update-federation-generated-swagger-docs.sh" - exit 1 -fi diff --git a/hack/verify-federation-openapi-spec.sh b/hack/verify-federation-openapi-spec.sh deleted file mode 100755 index df407a08425..00000000000 --- a/hack/verify-federation-openapi-spec.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash - -# 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. - -set -o errexit -set -o nounset -set -o pipefail - -KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. -source "${KUBE_ROOT}/hack/lib/init.sh" - -kube::golang::setup_env - -SPECROOT="${KUBE_ROOT}/federation/apis/openapi-spec" -TMP_SPECROOT="${KUBE_ROOT}/_tmp_federation/openapi-spec" -_tmp="${KUBE_ROOT}/_tmp_federation" - -mkdir -p "${_tmp}" -cp -a "${SPECROOT}" "${TMP_SPECROOT}" -trap "cp -a ${TMP_SPECROOT} ${SPECROOT}/..; rm -rf ${_tmp}" EXIT SIGINT -rm ${SPECROOT}/* - -"${KUBE_ROOT}/hack/update-federation-openapi-spec.sh" -echo "diffing ${SPECROOT} against freshly generated federation openapi spec" -ret=0 -diff -Naupr -I 'Auto generated by' "${SPECROOT}" "${TMP_SPECROOT}" || ret=$? -if [[ $ret -eq 0 ]] -then - echo "${SPECROOT} up to date." -else - echo "${SPECROOT} is out of date. Please run hack/update-federation-openapi-spec.sh" - exit 1 -fi - -# ex: ts=2 sw=2 et filetype=sh diff --git a/hack/verify-federation-swagger-spec.sh b/hack/verify-federation-swagger-spec.sh deleted file mode 100755 index 0a33f2ffaac..00000000000 --- a/hack/verify-federation-swagger-spec.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash - -# Copyright 2017 The Kubernetes Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -o errexit -set -o nounset -set -o pipefail - -KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. -source "${KUBE_ROOT}/hack/lib/init.sh" - -kube::golang::setup_env - -make -C "${KUBE_ROOT}" WHAT=federation/cmd/federation-apiserver - -apiserver=$(kube::util::find-binary "federation-apiserver") - -SPECROOT="${KUBE_ROOT}/federation/apis/swagger-spec" -TMP_SPECROOT="${KUBE_ROOT}/_tmp_federation/swagger-spec" -_tmp="${KUBE_ROOT}/_tmp_federation" - -mkdir -p "${_tmp}" -trap "rm -rf ${_tmp}" EXIT SIGINT -cp -a "${SPECROOT}" "${TMP_SPECROOT}" - -"${KUBE_ROOT}/hack/update-federation-swagger-spec.sh" -echo "diffing ${SPECROOT} against freshly generated federation swagger spec" -ret=0 -diff -Naupr -I 'Auto generated by' "${SPECROOT}" "${TMP_SPECROOT}" || ret=$? -cp -a ${TMP_SPECROOT} "${KUBE_ROOT}/federation/apis" -if [[ $ret -eq 0 ]] -then - echo "${SPECROOT} up to date." -else - echo "${SPECROOT} is out of date. Please run hack/update-federation-swagger-spec.sh" - exit 1 -fi - -# ex: ts=2 sw=2 et filetype=sh diff --git a/hack/verify-generated-device-plugin.sh b/hack/verify-generated-device-plugin.sh index 10797f3a5ca..c98cebae968 100755 --- a/hack/verify-generated-device-plugin.sh +++ b/hack/verify-generated-device-plugin.sh @@ -19,8 +19,8 @@ set -o nounset set -o pipefail KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. -DEVICE_PLUGIN_ROOT="${KUBE_ROOT}/pkg/kubelet/apis/deviceplugin/v1alpha1/" ERROR="Device plugin api is out of date. Please run hack/update-generated-device-plugin.sh" +DEVICE_PLUGIN_ROOT="${KUBE_ROOT}/pkg/kubelet/apis/deviceplugin/v1alpha/" source "${KUBE_ROOT}/hack/lib/protoc.sh" kube::golang::setup_env diff --git a/hack/verify-generated-docs.sh b/hack/verify-generated-docs.sh index e2d1899ffdd..9331343b1e5 100755 --- a/hack/verify-generated-docs.sh +++ b/hack/verify-generated-docs.sh @@ -28,7 +28,6 @@ BINS=( cmd/genkubedocs cmd/genman cmd/genyaml - federation/cmd/genfeddocs ) make -C "${KUBE_ROOT}" WHAT="${BINS[*]}" diff --git a/hack/verify-imports.sh b/hack/verify-imports.sh new file mode 100755 index 00000000000..a845fa2a0b3 --- /dev/null +++ b/hack/verify-imports.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# Copyright 2017 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. +source "${KUBE_ROOT}/hack/lib/init.sh" + +kube::golang::setup_env + +make -C "${KUBE_ROOT}" WHAT=cmd/importverifier + +# Find binary +importverifier=$(kube::util::find-binary "importverifier") + +if [[ ! -x "$importverifier" ]]; then + { + echo "It looks as if you don't have a compiled importverifier binary" + echo + echo "If you are running from a clone of the git repo, please run" + echo "'make WHAT=cmd/importverifier'." + } >&2 + exit 1 +fi + +"${importverifier}" "k8s.io/" "${KUBE_ROOT}/hack/import-restrictions.yaml" diff --git a/hack/verify-pkg-names.sh b/hack/verify-pkg-names.sh index 7d103da9fb3..8fbb09282a2 100755 --- a/hack/verify-pkg-names.sh +++ b/hack/verify-pkg-names.sh @@ -26,7 +26,7 @@ source "${KUBE_ROOT}/hack/lib/init.sh" kube::golang::verify_go_version cd "${KUBE_ROOT}" -if git --no-pager grep -E $'^(import |\t)[a-z]+[A-Z_][a-zA-Z]* "[^"]+"$' -- '**/*.go' ':(exclude)vendor/*' ':(exclude)staging/src/k8s.io/client-go/*vendor/*' ':(exclude)staging/src/k8s.io/metrics/*' ':(exclude)pkg/apis/admission/v1alpha1/zz_generated.conversion.go' ':(exclude)staging/src/k8s.io/sample-apiserver/pkg/client/informers/*' ':(exclude)staging/src/k8s.io/code-generator/_examples/*informers/*'; then +if git --no-pager grep -E $'^(import |\t)[a-z]+[A-Z_][a-zA-Z]* "[^"]+"$' -- '**/*.go' ':(exclude)vendor/*' ':(exclude)staging/src/k8s.io/client-go/*vendor/*' ':(exclude)staging/src/k8s.io/metrics/*' ':(exclude)pkg/apis/admission/v1beta1/zz_generated.conversion.go' ':(exclude)staging/src/k8s.io/sample-apiserver/pkg/client/informers/*' ':(exclude)staging/src/k8s.io/code-generator/_examples/*informers/*'; then echo "!!! Some package aliases break go conventions." echo "To fix these errors, do not use capitalized or underlined characters" echo "in pkg aliases. Refer to https://blog.golang.org/package-names for more info." diff --git a/hack/verify-staging-godeps.sh b/hack/verify-staging-godeps.sh index 7a8c5f9977f..e3d463f9c43 100755 --- a/hack/verify-staging-godeps.sh +++ b/hack/verify-staging-godeps.sh @@ -19,4 +19,4 @@ set -o nounset set -o pipefail KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. -KUBE_VERBOSE=3 KUBE_RUN_COPY_OUTPUT=N ${KUBE_ROOT}/hack/update-staging-godeps.sh -d -f "$@" +KUBE_VERBOSE="${KUBE_VERBOSE:-3}" KUBE_RUN_COPY_OUTPUT=N ${KUBE_ROOT}/hack/update-staging-godeps.sh -d -f "$@" diff --git a/hack/verify-staging-imports.sh b/hack/verify-staging-imports.sh deleted file mode 100755 index 30b8247e2f4..00000000000 --- a/hack/verify-staging-imports.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash - -# Copyright 2017 The Kubernetes Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -o errexit -set -o nounset -set -o pipefail - -KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. -source "${KUBE_ROOT}/hack/lib/init.sh" - -kube::golang::setup_env - -make -C "${KUBE_ROOT}" WHAT=cmd/importverifier - -# Find binary -importverifier=$(kube::util::find-binary "importverifier") - -if [[ ! -x "$importverifier" ]]; then - { - echo "It looks as if you don't have a compiled importverifier binary" - echo - echo "If you are running from a clone of the git repo, please run" - echo "'make WHAT=cmd/importverifier'." - } >&2 - exit 1 -fi - -"${importverifier}" "k8s.io/" "${KUBE_ROOT}/hack/staging-import-restrictions.json" \ No newline at end of file diff --git a/pkg/BUILD b/pkg/BUILD index c9206cd8432..5e06162d6c4 100644 --- a/pkg/BUILD +++ b/pkg/BUILD @@ -11,8 +11,23 @@ filegroup( name = "all-srcs", srcs = [ ":package-srcs", - "//pkg/api:all-srcs", - "//pkg/apimachinery/tests:all-srcs", + "//pkg/api/endpoints:all-srcs", + "//pkg/api/events:all-srcs", + "//pkg/api/legacyscheme:all-srcs", + "//pkg/api/persistentvolume:all-srcs", + "//pkg/api/persistentvolumeclaim:all-srcs", + "//pkg/api/pod:all-srcs", + "//pkg/api/ref:all-srcs", + "//pkg/api/resource:all-srcs", + "//pkg/api/service:all-srcs", + "//pkg/api/testapi:all-srcs", + "//pkg/api/testing:all-srcs", + "//pkg/api/unversioned:all-srcs", + "//pkg/api/v1/endpoints:all-srcs", + "//pkg/api/v1/node:all-srcs", + "//pkg/api/v1/pod:all-srcs", + "//pkg/api/v1/resource:all-srcs", + "//pkg/api/v1/service:all-srcs", "//pkg/apis/abac:all-srcs", "//pkg/apis/admission:all-srcs", "//pkg/apis/admissionregistration:all-srcs", @@ -23,9 +38,10 @@ filegroup( "//pkg/apis/batch:all-srcs", "//pkg/apis/certificates:all-srcs", "//pkg/apis/componentconfig:all-srcs", + "//pkg/apis/core:all-srcs", + "//pkg/apis/events:all-srcs", "//pkg/apis/extensions:all-srcs", "//pkg/apis/imagepolicy:all-srcs", - "//pkg/apis/meta/v1:all-srcs", "//pkg/apis/networking:all-srcs", "//pkg/apis/policy:all-srcs", "//pkg/apis/rbac:all-srcs", @@ -42,6 +58,7 @@ filegroup( "//pkg/client/informers/informers_generated/internalversion:all-srcs", "//pkg/client/leaderelectionconfig:all-srcs", "//pkg/client/listers/admissionregistration/internalversion:all-srcs", + "//pkg/client/listers/apis/admissionregistration:all-srcs", "//pkg/client/listers/apps/internalversion:all-srcs", "//pkg/client/listers/authentication/internalversion:all-srcs", "//pkg/client/listers/authorization/internalversion:all-srcs", @@ -67,7 +84,6 @@ filegroup( "//pkg/features:all-srcs", "//pkg/fieldpath:all-srcs", "//pkg/generated:all-srcs", - "//pkg/hyperkube:all-srcs", "//pkg/kubeapiserver:all-srcs", "//pkg/kubectl:all-srcs", "//pkg/kubelet:all-srcs", @@ -79,6 +95,7 @@ filegroup( "//pkg/quota:all-srcs", "//pkg/registry:all-srcs", "//pkg/routes:all-srcs", + "//pkg/scheduler:all-srcs", "//pkg/security:all-srcs", "//pkg/securitycontext:all-srcs", "//pkg/serviceaccount:all-srcs", diff --git a/pkg/api/BUILD b/pkg/api/BUILD deleted file mode 100644 index 8b270690907..00000000000 --- a/pkg/api/BUILD +++ /dev/null @@ -1,73 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = [ - "annotation_key_constants.go", - "doc.go", - "field_constants.go", - "json.go", - "objectreference.go", - "register.go", - "resource.go", - "taint.go", - "toleration.go", - "types.go", - "zz_generated.deepcopy.go", - ], - importpath = "k8s.io/kubernetes/pkg/api", - visibility = ["//visibility:public"], - deps = [ - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["taint_test.go"], - importpath = "k8s.io/kubernetes/pkg/api", - library = ":go_default_library", -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//pkg/api/endpoints:all-srcs", - "//pkg/api/events:all-srcs", - "//pkg/api/fuzzer:all-srcs", - "//pkg/api/helper:all-srcs", - "//pkg/api/install:all-srcs", - "//pkg/api/persistentvolume:all-srcs", - "//pkg/api/pod:all-srcs", - "//pkg/api/ref:all-srcs", - "//pkg/api/resource:all-srcs", - "//pkg/api/service:all-srcs", - "//pkg/api/testapi:all-srcs", - "//pkg/api/testing:all-srcs", - "//pkg/api/unversioned:all-srcs", - "//pkg/api/v1:all-srcs", - "//pkg/api/validation:all-srcs", - ], - tags = ["automanaged"], - visibility = ["//visibility:public"], -) diff --git a/pkg/api/OWNERS b/pkg/api/OWNERS index 0605b27b2aa..8f7783f9f0b 100644 --- a/pkg/api/OWNERS +++ b/pkg/api/OWNERS @@ -1,44 +1,4 @@ approvers: -- erictune -- lavalamp -- smarterclayton -- thockin -- liggitt -# - bgrant0607 # manual escalations only +- api-approvers reviewers: -- thockin -- lavalamp -- smarterclayton -- wojtek-t -- deads2k -- yujuhong -- brendandburns -- derekwaynecarr -- caesarxuchao -- vishh -- mikedanese -- liggitt -- nikhiljindal -- gmarek -- erictune -- davidopp -- pmorie -- sttts -- dchen1107 -- saad-ali -- zmerlynn -- luxas -- janetkuo -- justinsb -- pwittrock -- roberthbailey -- ncdc -- tallclair -- yifan-gu -- eparis -- mwielgus -- timothysc -- soltysh -- piosz -- jsafrane -- jbeda +- api-reviewers diff --git a/pkg/api/doc.go b/pkg/api/doc.go deleted file mode 100644 index 283a83e402d..00000000000 --- a/pkg/api/doc.go +++ /dev/null @@ -1,24 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT 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,register - -// Package api contains the latest (or "internal") version of the -// Kubernetes API objects. This is the API objects as represented in memory. -// The contract presented to clients is located in the versioned packages, -// which are sub-directories. The first one is "v1". Those packages -// describe how a particular version is serialized to storage/network. -package api // import "k8s.io/kubernetes/pkg/api" diff --git a/pkg/api/endpoints/BUILD b/pkg/api/endpoints/BUILD index 9ece3f3caff..e88d60d2880 100644 --- a/pkg/api/endpoints/BUILD +++ b/pkg/api/endpoints/BUILD @@ -11,7 +11,7 @@ go_library( srcs = ["util.go"], importpath = "k8s.io/kubernetes/pkg/api/endpoints", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/util/hash:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", ], @@ -20,10 +20,10 @@ go_library( go_test( name = "go_default_test", srcs = ["util_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/api/endpoints", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/github.com/davecgh/go-spew/spew:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", ], diff --git a/pkg/api/endpoints/util.go b/pkg/api/endpoints/util.go index 37084d64973..3d7b6e514f6 100644 --- a/pkg/api/endpoints/util.go +++ b/pkg/api/endpoints/util.go @@ -24,7 +24,7 @@ import ( "sort" "k8s.io/apimachinery/pkg/types" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" hashutil "k8s.io/kubernetes/pkg/util/hash" ) @@ -89,8 +89,7 @@ type addressKey struct { // any existing ready state. func mapAddressByPort(addr *api.EndpointAddress, port api.EndpointPort, ready bool, allAddrs map[addressKey]*api.EndpointAddress, portToAddrReadyMap map[api.EndpointPort]addressSet) *api.EndpointAddress { // use addressKey to distinguish between two endpoints that are identical addresses - // but may have come from different hosts, for attribution. For instance, Mesos - // assigns pods the node IP, but the pods are distinct. + // but may have come from different hosts, for attribution. key := addressKey{ip: addr.IP} if addr.TargetRef != nil { key.uid = addr.TargetRef.UID diff --git a/pkg/api/endpoints/util_test.go b/pkg/api/endpoints/util_test.go index c060e347997..f35ea9ea5f8 100644 --- a/pkg/api/endpoints/util_test.go +++ b/pkg/api/endpoints/util_test.go @@ -22,7 +22,7 @@ import ( "github.com/davecgh/go-spew/spew" "k8s.io/apimachinery/pkg/types" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) func podRef(uid string) *api.ObjectReference { diff --git a/pkg/api/events/BUILD b/pkg/api/events/BUILD index dbc42583d73..64ab6d316f4 100644 --- a/pkg/api/events/BUILD +++ b/pkg/api/events/BUILD @@ -10,16 +10,16 @@ go_library( name = "go_default_library", srcs = ["sorted_event_list.go"], importpath = "k8s.io/kubernetes/pkg/api/events", - deps = ["//pkg/api:go_default_library"], + deps = ["//pkg/apis/core:go_default_library"], ) go_test( name = "go_default_test", srcs = ["sorted_event_list_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/api/events", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", ], ) diff --git a/pkg/api/events/sorted_event_list.go b/pkg/api/events/sorted_event_list.go index b96c615deba..9976c10ce73 100644 --- a/pkg/api/events/sorted_event_list.go +++ b/pkg/api/events/sorted_event_list.go @@ -17,7 +17,7 @@ limitations under the License. package events import ( - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // SortableEvents implements sort.Interface for []api.Event based on the Timestamp field diff --git a/pkg/api/events/sorted_event_list_test.go b/pkg/api/events/sorted_event_list_test.go index bae6f2e0ddc..687f6fa19f8 100644 --- a/pkg/api/events/sorted_event_list_test.go +++ b/pkg/api/events/sorted_event_list_test.go @@ -22,7 +22,7 @@ import ( "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestSortableEvents(t *testing.T) { diff --git a/pkg/api/fuzzer/BUILD b/pkg/api/fuzzer/BUILD deleted file mode 100644 index 959d1f338b5..00000000000 --- a/pkg/api/fuzzer/BUILD +++ /dev/null @@ -1,36 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["fuzzer.go"], - importpath = "k8s.io/kubernetes/pkg/api/fuzzer", - deps = [ - "//pkg/api:go_default_library", - "//vendor/github.com/google/gofuzz:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/pkg/api/fuzzer/fuzzer.go b/pkg/api/fuzzer/fuzzer.go deleted file mode 100644 index ea9a8f7a97e..00000000000 --- a/pkg/api/fuzzer/fuzzer.go +++ /dev/null @@ -1,465 +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 fuzzer - -import ( - "reflect" - "strconv" - - fuzz "github.com/google/gofuzz" - - "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/kubernetes/pkg/api" -) - -// Funcs returns the fuzzer functions for the core api group. -var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { - return []interface{}{ - func(q *resource.Quantity, c fuzz.Continue) { - *q = *resource.NewQuantity(c.Int63n(1000), resource.DecimalExponent) - }, - func(j *api.ObjectReference, c fuzz.Continue) { - // We have to customize the randomization of TypeMetas because their - // APIVersion and Kind must remain blank in memory. - j.APIVersion = c.RandString() - j.Kind = c.RandString() - j.Namespace = c.RandString() - j.Name = c.RandString() - j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10) - j.FieldPath = c.RandString() - }, - func(j *api.ListOptions, c fuzz.Continue) { - label, _ := labels.Parse("a=b") - j.LabelSelector = label - field, _ := fields.ParseSelector("a=b") - j.FieldSelector = field - }, - func(j *api.PodExecOptions, c fuzz.Continue) { - j.Stdout = true - j.Stderr = true - }, - func(j *api.PodAttachOptions, c fuzz.Continue) { - j.Stdout = true - j.Stderr = true - }, - func(j *api.PodPortForwardOptions, c fuzz.Continue) { - if c.RandBool() { - j.Ports = make([]int32, c.Intn(10)) - for i := range j.Ports { - j.Ports[i] = c.Int31n(65535) - } - } - }, - func(s *api.PodSpec, c fuzz.Continue) { - c.FuzzNoCustom(s) - // has a default value - ttl := int64(30) - if c.RandBool() { - ttl = int64(c.Uint32()) - } - s.TerminationGracePeriodSeconds = &ttl - - c.Fuzz(s.SecurityContext) - - if s.SecurityContext == nil { - s.SecurityContext = new(api.PodSecurityContext) - } - if s.Affinity == nil { - s.Affinity = new(api.Affinity) - } - if s.SchedulerName == "" { - s.SchedulerName = api.DefaultSchedulerName - } - }, - func(j *api.PodPhase, c fuzz.Continue) { - statuses := []api.PodPhase{api.PodPending, api.PodRunning, api.PodFailed, api.PodUnknown} - *j = statuses[c.Rand.Intn(len(statuses))] - }, - func(j *api.Binding, c fuzz.Continue) { - c.Fuzz(&j.ObjectMeta) - j.Target.Name = c.RandString() - }, - func(j *api.ReplicationControllerSpec, c fuzz.Continue) { - c.FuzzNoCustom(j) // fuzz self without calling this function again - //j.TemplateRef = nil // this is required for round trip - }, - func(j *api.List, c fuzz.Continue) { - c.FuzzNoCustom(j) // fuzz self without calling this function again - // TODO: uncomment when round trip starts from a versioned object - if false { //j.Items == nil { - j.Items = []runtime.Object{} - } - }, - func(q *api.ResourceRequirements, c fuzz.Continue) { - randomQuantity := func() resource.Quantity { - var q resource.Quantity - c.Fuzz(&q) - // precalc the string for benchmarking purposes - _ = q.String() - return q - } - q.Limits = make(api.ResourceList) - q.Requests = make(api.ResourceList) - cpuLimit := randomQuantity() - q.Limits[api.ResourceCPU] = *cpuLimit.Copy() - q.Requests[api.ResourceCPU] = *cpuLimit.Copy() - memoryLimit := randomQuantity() - q.Limits[api.ResourceMemory] = *memoryLimit.Copy() - q.Requests[api.ResourceMemory] = *memoryLimit.Copy() - storageLimit := randomQuantity() - q.Limits[api.ResourceStorage] = *storageLimit.Copy() - q.Requests[api.ResourceStorage] = *storageLimit.Copy() - }, - func(q *api.LimitRangeItem, c fuzz.Continue) { - var cpuLimit resource.Quantity - c.Fuzz(&cpuLimit) - - q.Type = api.LimitTypeContainer - q.Default = make(api.ResourceList) - q.Default[api.ResourceCPU] = *(cpuLimit.Copy()) - - q.DefaultRequest = make(api.ResourceList) - q.DefaultRequest[api.ResourceCPU] = *(cpuLimit.Copy()) - - q.Max = make(api.ResourceList) - q.Max[api.ResourceCPU] = *(cpuLimit.Copy()) - - q.Min = make(api.ResourceList) - q.Min[api.ResourceCPU] = *(cpuLimit.Copy()) - - q.MaxLimitRequestRatio = make(api.ResourceList) - q.MaxLimitRequestRatio[api.ResourceCPU] = resource.MustParse("10") - }, - func(p *api.PullPolicy, c fuzz.Continue) { - policies := []api.PullPolicy{api.PullAlways, api.PullNever, api.PullIfNotPresent} - *p = policies[c.Rand.Intn(len(policies))] - }, - func(rp *api.RestartPolicy, c fuzz.Continue) { - policies := []api.RestartPolicy{api.RestartPolicyAlways, api.RestartPolicyNever, api.RestartPolicyOnFailure} - *rp = policies[c.Rand.Intn(len(policies))] - }, - // api.DownwardAPIVolumeFile needs to have a specific func since FieldRef has to be - // defaulted to a version otherwise roundtrip will fail - func(m *api.DownwardAPIVolumeFile, c fuzz.Continue) { - m.Path = c.RandString() - versions := []string{"v1"} - m.FieldRef = &api.ObjectFieldSelector{} - m.FieldRef.APIVersion = versions[c.Rand.Intn(len(versions))] - m.FieldRef.FieldPath = c.RandString() - c.Fuzz(m.Mode) - if m.Mode != nil { - *m.Mode &= 0777 - } - }, - func(s *api.SecretVolumeSource, c fuzz.Continue) { - c.FuzzNoCustom(s) // fuzz self without calling this function again - - if c.RandBool() { - opt := c.RandBool() - s.Optional = &opt - } - // DefaultMode should always be set, it has a default - // value and it is expected to be between 0 and 0777 - var mode int32 - c.Fuzz(&mode) - mode &= 0777 - s.DefaultMode = &mode - }, - func(cm *api.ConfigMapVolumeSource, c fuzz.Continue) { - c.FuzzNoCustom(cm) // fuzz self without calling this function again - - if c.RandBool() { - opt := c.RandBool() - cm.Optional = &opt - } - // DefaultMode should always be set, it has a default - // value and it is expected to be between 0 and 0777 - var mode int32 - c.Fuzz(&mode) - mode &= 0777 - cm.DefaultMode = &mode - }, - func(d *api.DownwardAPIVolumeSource, c fuzz.Continue) { - c.FuzzNoCustom(d) // fuzz self without calling this function again - - // DefaultMode should always be set, it has a default - // value and it is expected to be between 0 and 0777 - var mode int32 - c.Fuzz(&mode) - mode &= 0777 - d.DefaultMode = &mode - }, - func(s *api.ProjectedVolumeSource, c fuzz.Continue) { - c.FuzzNoCustom(s) // fuzz self without calling this function again - - // DefaultMode should always be set, it has a default - // value and it is expected to be between 0 and 0777 - var mode int32 - c.Fuzz(&mode) - mode &= 0777 - s.DefaultMode = &mode - }, - func(k *api.KeyToPath, c fuzz.Continue) { - c.FuzzNoCustom(k) // fuzz self without calling this function again - k.Key = c.RandString() - k.Path = c.RandString() - - // Mode is not mandatory, but if it is set, it should be - // a value between 0 and 0777 - if k.Mode != nil { - *k.Mode &= 0777 - } - }, - func(vs *api.VolumeSource, c fuzz.Continue) { - // Exactly one of the fields must be set. - v := reflect.ValueOf(vs).Elem() - i := int(c.RandUint64() % uint64(v.NumField())) - t := v.Field(i).Addr() - for v.Field(i).IsNil() { - c.Fuzz(t.Interface()) - } - }, - func(i *api.ISCSIVolumeSource, c fuzz.Continue) { - i.ISCSIInterface = c.RandString() - if i.ISCSIInterface == "" { - i.ISCSIInterface = "default" - } - }, - func(d *api.DNSPolicy, c fuzz.Continue) { - policies := []api.DNSPolicy{api.DNSClusterFirst, api.DNSDefault} - *d = policies[c.Rand.Intn(len(policies))] - }, - func(p *api.Protocol, c fuzz.Continue) { - protocols := []api.Protocol{api.ProtocolTCP, api.ProtocolUDP} - *p = protocols[c.Rand.Intn(len(protocols))] - }, - func(p *api.ServiceAffinity, c fuzz.Continue) { - types := []api.ServiceAffinity{api.ServiceAffinityClientIP, api.ServiceAffinityNone} - *p = types[c.Rand.Intn(len(types))] - }, - func(p *api.ServiceType, c fuzz.Continue) { - types := []api.ServiceType{api.ServiceTypeClusterIP, api.ServiceTypeNodePort, api.ServiceTypeLoadBalancer} - *p = types[c.Rand.Intn(len(types))] - }, - func(p *api.ServiceExternalTrafficPolicyType, c fuzz.Continue) { - types := []api.ServiceExternalTrafficPolicyType{api.ServiceExternalTrafficPolicyTypeCluster, api.ServiceExternalTrafficPolicyTypeLocal} - *p = types[c.Rand.Intn(len(types))] - }, - func(ct *api.Container, c fuzz.Continue) { - c.FuzzNoCustom(ct) // fuzz self without calling this function again - ct.TerminationMessagePath = "/" + ct.TerminationMessagePath // Must be non-empty - ct.TerminationMessagePolicy = "File" - }, - func(p *api.Probe, c fuzz.Continue) { - c.FuzzNoCustom(p) - // These fields have default values. - intFieldsWithDefaults := [...]string{"TimeoutSeconds", "PeriodSeconds", "SuccessThreshold", "FailureThreshold"} - v := reflect.ValueOf(p).Elem() - for _, field := range intFieldsWithDefaults { - f := v.FieldByName(field) - if f.Int() == 0 { - f.SetInt(1) - } - } - }, - func(ev *api.EnvVar, c fuzz.Continue) { - ev.Name = c.RandString() - if c.RandBool() { - ev.Value = c.RandString() - } else { - ev.ValueFrom = &api.EnvVarSource{} - ev.ValueFrom.FieldRef = &api.ObjectFieldSelector{} - - versions := []schema.GroupVersion{ - {Group: "admission.k8s.io", Version: "v1alpha1"}, - {Group: "apps", Version: "v1beta1"}, - {Group: "apps", Version: "v1beta2"}, - {Group: "foo", Version: "v42"}, - } - - ev.ValueFrom.FieldRef.APIVersion = versions[c.Rand.Intn(len(versions))].String() - ev.ValueFrom.FieldRef.FieldPath = c.RandString() - } - }, - func(ev *api.EnvFromSource, c fuzz.Continue) { - if c.RandBool() { - ev.Prefix = "p_" - } - if c.RandBool() { - c.Fuzz(&ev.ConfigMapRef) - } else { - c.Fuzz(&ev.SecretRef) - } - }, - func(cm *api.ConfigMapEnvSource, c fuzz.Continue) { - c.FuzzNoCustom(cm) // fuzz self without calling this function again - if c.RandBool() { - opt := c.RandBool() - cm.Optional = &opt - } - }, - func(s *api.SecretEnvSource, c fuzz.Continue) { - c.FuzzNoCustom(s) // fuzz self without calling this function again - }, - func(sc *api.SecurityContext, c fuzz.Continue) { - c.FuzzNoCustom(sc) // fuzz self without calling this function again - if c.RandBool() { - priv := c.RandBool() - sc.Privileged = &priv - } - - if c.RandBool() { - sc.Capabilities = &api.Capabilities{ - Add: make([]api.Capability, 0), - Drop: make([]api.Capability, 0), - } - c.Fuzz(&sc.Capabilities.Add) - c.Fuzz(&sc.Capabilities.Drop) - } - }, - func(s *api.Secret, c fuzz.Continue) { - c.FuzzNoCustom(s) // fuzz self without calling this function again - s.Type = api.SecretTypeOpaque - }, - func(r *api.RBDVolumeSource, c fuzz.Continue) { - r.RBDPool = c.RandString() - if r.RBDPool == "" { - r.RBDPool = "rbd" - } - r.RadosUser = c.RandString() - if r.RadosUser == "" { - r.RadosUser = "admin" - } - r.Keyring = c.RandString() - if r.Keyring == "" { - r.Keyring = "/etc/ceph/keyring" - } - }, - func(obj *api.HostPathVolumeSource, c fuzz.Continue) { - c.FuzzNoCustom(obj) - types := []api.HostPathType{api.HostPathUnset, api.HostPathDirectoryOrCreate, api.HostPathDirectory, - api.HostPathFileOrCreate, api.HostPathFile, api.HostPathSocket, api.HostPathCharDev, api.HostPathBlockDev} - typeVol := types[c.Rand.Intn(len(types))] - if obj.Type == nil { - obj.Type = &typeVol - } - }, - func(pv *api.PersistentVolume, c fuzz.Continue) { - c.FuzzNoCustom(pv) // fuzz self without calling this function again - types := []api.PersistentVolumePhase{api.VolumeAvailable, api.VolumePending, api.VolumeBound, api.VolumeReleased, api.VolumeFailed} - pv.Status.Phase = types[c.Rand.Intn(len(types))] - pv.Status.Message = c.RandString() - reclamationPolicies := []api.PersistentVolumeReclaimPolicy{api.PersistentVolumeReclaimRecycle, api.PersistentVolumeReclaimRetain} - pv.Spec.PersistentVolumeReclaimPolicy = reclamationPolicies[c.Rand.Intn(len(reclamationPolicies))] - }, - func(pvc *api.PersistentVolumeClaim, c fuzz.Continue) { - c.FuzzNoCustom(pvc) // fuzz self without calling this function again - types := []api.PersistentVolumeClaimPhase{api.ClaimBound, api.ClaimPending, api.ClaimLost} - pvc.Status.Phase = types[c.Rand.Intn(len(types))] - }, - func(obj *api.AzureDiskVolumeSource, c fuzz.Continue) { - if obj.CachingMode == nil { - obj.CachingMode = new(api.AzureDataDiskCachingMode) - *obj.CachingMode = api.AzureDataDiskCachingReadWrite - } - if obj.Kind == nil { - obj.Kind = new(api.AzureDataDiskKind) - *obj.Kind = api.AzureSharedBlobDisk - } - if obj.FSType == nil { - obj.FSType = new(string) - *obj.FSType = "ext4" - } - if obj.ReadOnly == nil { - obj.ReadOnly = new(bool) - *obj.ReadOnly = false - } - }, - func(sio *api.ScaleIOVolumeSource, c fuzz.Continue) { - sio.ProtectionDomain = c.RandString() - if sio.ProtectionDomain == "" { - sio.ProtectionDomain = "default" - } - sio.StoragePool = c.RandString() - if sio.StoragePool == "" { - sio.StoragePool = "default" - } - sio.StorageMode = c.RandString() - if sio.StorageMode == "" { - sio.StorageMode = "ThinProvisioned" - } - sio.FSType = c.RandString() - if sio.FSType == "" { - sio.FSType = "xfs" - } - }, - func(s *api.NamespaceSpec, c fuzz.Continue) { - s.Finalizers = []api.FinalizerName{api.FinalizerKubernetes} - }, - func(s *api.NamespaceStatus, c fuzz.Continue) { - s.Phase = api.NamespaceActive - }, - func(http *api.HTTPGetAction, c fuzz.Continue) { - c.FuzzNoCustom(http) // fuzz self without calling this function again - http.Path = "/" + http.Path // can't be blank - http.Scheme = "x" + http.Scheme // can't be blank - }, - func(ss *api.ServiceSpec, c fuzz.Continue) { - c.FuzzNoCustom(ss) // fuzz self without calling this function again - if len(ss.Ports) == 0 { - // There must be at least 1 port. - ss.Ports = append(ss.Ports, api.ServicePort{}) - c.Fuzz(&ss.Ports[0]) - } - for i := range ss.Ports { - switch ss.Ports[i].TargetPort.Type { - case intstr.Int: - ss.Ports[i].TargetPort.IntVal = 1 + ss.Ports[i].TargetPort.IntVal%65535 // non-zero - case intstr.String: - ss.Ports[i].TargetPort.StrVal = "x" + ss.Ports[i].TargetPort.StrVal // non-empty - } - } - types := []api.ServiceAffinity{api.ServiceAffinityNone, api.ServiceAffinityClientIP} - ss.SessionAffinity = types[c.Rand.Intn(len(types))] - switch ss.SessionAffinity { - case api.ServiceAffinityClientIP: - timeoutSeconds := int32(c.Rand.Intn(int(api.MaxClientIPServiceAffinitySeconds))) - ss.SessionAffinityConfig = &api.SessionAffinityConfig{ - ClientIP: &api.ClientIPConfig{ - TimeoutSeconds: &timeoutSeconds, - }, - } - case api.ServiceAffinityNone: - ss.SessionAffinityConfig = nil - } - }, - func(n *api.Node, c fuzz.Continue) { - c.FuzzNoCustom(n) - n.Spec.ExternalID = "external" - }, - func(s *api.NodeStatus, c fuzz.Continue) { - c.FuzzNoCustom(s) - s.Allocatable = s.Capacity - }, - } -} diff --git a/pkg/api/helper/BUILD b/pkg/api/helper/BUILD deleted file mode 100644 index f8bd4dbb822..00000000000 --- a/pkg/api/helper/BUILD +++ /dev/null @@ -1,51 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = ["helpers_test.go"], - importpath = "k8s.io/kubernetes/pkg/api/helper", - library = ":go_default_library", - deps = [ - "//pkg/api:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - ], -) - -go_library( - name = "go_default_library", - srcs = ["helpers.go"], - importpath = "k8s.io/kubernetes/pkg/api/helper", - deps = [ - "//pkg/api:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/selection:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//pkg/api/helper/qos:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/pkg/api/helper/helpers.go b/pkg/api/helper/helpers.go deleted file mode 100644 index f352f580fe1..00000000000 --- a/pkg/api/helper/helpers.go +++ /dev/null @@ -1,655 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES 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 ( - "encoding/json" - "fmt" - "strings" - - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/conversion" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/selection" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api" -) - -// IsHugePageResourceName returns true if the resource name has the huge page -// resource prefix. -func IsHugePageResourceName(name api.ResourceName) bool { - return strings.HasPrefix(string(name), api.ResourceHugePagesPrefix) -} - -// HugePageResourceName returns a ResourceName with the canonical hugepage -// prefix prepended for the specified page size. The page size is converted -// to its canonical representation. -func HugePageResourceName(pageSize resource.Quantity) api.ResourceName { - return api.ResourceName(fmt.Sprintf("%s%s", api.ResourceHugePagesPrefix, pageSize.String())) -} - -// HugePageSizeFromResourceName returns the page size for the specified huge page -// resource name. If the specified input is not a valid huge page resource name -// an error is returned. -func HugePageSizeFromResourceName(name api.ResourceName) (resource.Quantity, error) { - if !IsHugePageResourceName(name) { - return resource.Quantity{}, fmt.Errorf("resource name: %s is not valid hugepage name", name) - } - pageSize := strings.TrimPrefix(string(name), api.ResourceHugePagesPrefix) - return resource.ParseQuantity(pageSize) -} - -// NonConvertibleFields iterates over the provided map and filters out all but -// any keys with the "non-convertible.kubernetes.io" prefix. -func NonConvertibleFields(annotations map[string]string) map[string]string { - nonConvertibleKeys := map[string]string{} - for key, value := range annotations { - if strings.HasPrefix(key, api.NonConvertibleAnnotationPrefix) { - nonConvertibleKeys[key] = value - } - } - return nonConvertibleKeys -} - -// Semantic can do semantic deep equality checks for api objects. -// Example: apiequality.Semantic.DeepEqual(aPod, aPodWithNonNilButEmptyMaps) == true -var Semantic = conversion.EqualitiesOrDie( - func(a, b resource.Quantity) bool { - // Ignore formatting, only care that numeric value stayed the same. - // TODO: if we decide it's important, it should be safe to start comparing the format. - // - // Uninitialized quantities are equivalent to 0 quantities. - return a.Cmp(b) == 0 - }, - func(a, b metav1.MicroTime) bool { - return a.UTC() == b.UTC() - }, - func(a, b metav1.Time) bool { - return a.UTC() == b.UTC() - }, - func(a, b labels.Selector) bool { - return a.String() == b.String() - }, - func(a, b fields.Selector) bool { - return a.String() == b.String() - }, -) - -var standardResourceQuotaScopes = sets.NewString( - string(api.ResourceQuotaScopeTerminating), - string(api.ResourceQuotaScopeNotTerminating), - string(api.ResourceQuotaScopeBestEffort), - string(api.ResourceQuotaScopeNotBestEffort), -) - -// IsStandardResourceQuotaScope returns true if the scope is a standard value -func IsStandardResourceQuotaScope(str string) bool { - return standardResourceQuotaScopes.Has(str) -} - -var podObjectCountQuotaResources = sets.NewString( - string(api.ResourcePods), -) - -var podComputeQuotaResources = sets.NewString( - string(api.ResourceCPU), - string(api.ResourceMemory), - string(api.ResourceLimitsCPU), - string(api.ResourceLimitsMemory), - string(api.ResourceRequestsCPU), - string(api.ResourceRequestsMemory), -) - -// IsResourceQuotaScopeValidForResource returns true if the resource applies to the specified scope -func IsResourceQuotaScopeValidForResource(scope api.ResourceQuotaScope, resource string) bool { - switch scope { - case api.ResourceQuotaScopeTerminating, api.ResourceQuotaScopeNotTerminating, api.ResourceQuotaScopeNotBestEffort: - return podObjectCountQuotaResources.Has(resource) || podComputeQuotaResources.Has(resource) - case api.ResourceQuotaScopeBestEffort: - return podObjectCountQuotaResources.Has(resource) - default: - return true - } -} - -var standardContainerResources = sets.NewString( - string(api.ResourceCPU), - string(api.ResourceMemory), - string(api.ResourceEphemeralStorage), -) - -// IsStandardContainerResourceName returns true if the container can make a resource request -// for the specified resource -func IsStandardContainerResourceName(str string) bool { - return standardContainerResources.Has(str) || IsHugePageResourceName(api.ResourceName(str)) -} - -// IsExtendedResourceName returns true if the resource name is not in the -// default namespace, or it has the opaque integer resource prefix. -func IsExtendedResourceName(name api.ResourceName) bool { - // TODO: Remove OIR part following deprecation. - return !IsDefaultNamespaceResource(name) || IsOpaqueIntResourceName(name) -} - -// IsDefaultNamespaceResource returns true if the resource name is in the -// *kubernetes.io/ namespace. Partially-qualified (unprefixed) names are -// implicitly in the kubernetes.io/ namespace. -func IsDefaultNamespaceResource(name api.ResourceName) bool { - return !strings.Contains(string(name), "/") || - strings.Contains(string(name), api.ResourceDefaultNamespacePrefix) -} - -// IsOpaqueIntResourceName returns true if the resource name has the opaque -// integer resource prefix. -func IsOpaqueIntResourceName(name api.ResourceName) bool { - return strings.HasPrefix(string(name), api.ResourceOpaqueIntPrefix) -} - -// OpaqueIntResourceName returns a ResourceName with the canonical opaque -// integer prefix prepended. If the argument already has the prefix, it is -// returned unmodified. -func OpaqueIntResourceName(name string) api.ResourceName { - if IsOpaqueIntResourceName(api.ResourceName(name)) { - return api.ResourceName(name) - } - return api.ResourceName(fmt.Sprintf("%s%s", api.ResourceOpaqueIntPrefix, name)) -} - -var overcommitBlacklist = sets.NewString(string(api.ResourceNvidiaGPU)) - -// IsOvercommitAllowed returns true if the resource is in the default -// namespace and not blacklisted. -func IsOvercommitAllowed(name api.ResourceName) bool { - return IsDefaultNamespaceResource(name) && - !IsHugePageResourceName(name) && - !overcommitBlacklist.Has(string(name)) -} - -var standardLimitRangeTypes = sets.NewString( - string(api.LimitTypePod), - string(api.LimitTypeContainer), - string(api.LimitTypePersistentVolumeClaim), -) - -// IsStandardLimitRangeType returns true if the type is Pod or Container -func IsStandardLimitRangeType(str string) bool { - return standardLimitRangeTypes.Has(str) -} - -var standardQuotaResources = sets.NewString( - string(api.ResourceCPU), - string(api.ResourceMemory), - string(api.ResourceEphemeralStorage), - string(api.ResourceRequestsCPU), - string(api.ResourceRequestsMemory), - string(api.ResourceRequestsStorage), - string(api.ResourceRequestsEphemeralStorage), - string(api.ResourceLimitsCPU), - string(api.ResourceLimitsMemory), - string(api.ResourceLimitsEphemeralStorage), - string(api.ResourcePods), - string(api.ResourceQuotas), - string(api.ResourceServices), - string(api.ResourceReplicationControllers), - string(api.ResourceSecrets), - string(api.ResourcePersistentVolumeClaims), - string(api.ResourceConfigMaps), - string(api.ResourceServicesNodePorts), - string(api.ResourceServicesLoadBalancers), -) - -// IsStandardQuotaResourceName returns true if the resource is known to -// the quota tracking system -func IsStandardQuotaResourceName(str string) bool { - return standardQuotaResources.Has(str) -} - -var standardResources = sets.NewString( - string(api.ResourceCPU), - string(api.ResourceMemory), - string(api.ResourceEphemeralStorage), - string(api.ResourceRequestsCPU), - string(api.ResourceRequestsMemory), - string(api.ResourceRequestsEphemeralStorage), - string(api.ResourceLimitsCPU), - string(api.ResourceLimitsMemory), - string(api.ResourceLimitsEphemeralStorage), - string(api.ResourcePods), - string(api.ResourceQuotas), - string(api.ResourceServices), - string(api.ResourceReplicationControllers), - string(api.ResourceSecrets), - string(api.ResourceConfigMaps), - string(api.ResourcePersistentVolumeClaims), - string(api.ResourceStorage), - string(api.ResourceRequestsStorage), - string(api.ResourceServicesNodePorts), - string(api.ResourceServicesLoadBalancers), -) - -// IsStandardResourceName returns true if the resource is known to the system -func IsStandardResourceName(str string) bool { - return standardResources.Has(str) || IsHugePageResourceName(api.ResourceName(str)) -} - -var integerResources = sets.NewString( - string(api.ResourcePods), - string(api.ResourceQuotas), - string(api.ResourceServices), - string(api.ResourceReplicationControllers), - string(api.ResourceSecrets), - string(api.ResourceConfigMaps), - string(api.ResourcePersistentVolumeClaims), - string(api.ResourceServicesNodePorts), - string(api.ResourceServicesLoadBalancers), -) - -// IsIntegerResourceName returns true if the resource is measured in integer values -func IsIntegerResourceName(str string) bool { - return integerResources.Has(str) || IsExtendedResourceName(api.ResourceName(str)) -} - -// Extended and HugePages resources -func IsScalarResourceName(name api.ResourceName) bool { - return IsExtendedResourceName(name) || IsHugePageResourceName(name) -} - -// this function aims to check if the service's ClusterIP is set or not -// the objective is not to perform validation here -func IsServiceIPSet(service *api.Service) bool { - return service.Spec.ClusterIP != api.ClusterIPNone && service.Spec.ClusterIP != "" -} - -// this function aims to check if the service's cluster IP is requested or not -func IsServiceIPRequested(service *api.Service) bool { - // ExternalName services are CNAME aliases to external ones. Ignore the IP. - if service.Spec.Type == api.ServiceTypeExternalName { - return false - } - return service.Spec.ClusterIP == "" -} - -var standardFinalizers = sets.NewString( - string(api.FinalizerKubernetes), - metav1.FinalizerOrphanDependents, - metav1.FinalizerDeleteDependents, -) - -// HasAnnotation returns a bool if passed in annotation exists -func HasAnnotation(obj api.ObjectMeta, ann string) bool { - _, found := obj.Annotations[ann] - return found -} - -// SetMetaDataAnnotation sets the annotation and value -func SetMetaDataAnnotation(obj *api.ObjectMeta, ann string, value string) { - if obj.Annotations == nil { - obj.Annotations = make(map[string]string) - } - obj.Annotations[ann] = value -} - -func IsStandardFinalizerName(str string) bool { - return standardFinalizers.Has(str) -} - -// AddToNodeAddresses appends the NodeAddresses to the passed-by-pointer slice, -// only if they do not already exist -func AddToNodeAddresses(addresses *[]api.NodeAddress, addAddresses ...api.NodeAddress) { - for _, add := range addAddresses { - exists := false - for _, existing := range *addresses { - if existing.Address == add.Address && existing.Type == add.Type { - exists = true - break - } - } - if !exists { - *addresses = append(*addresses, add) - } - } -} - -// TODO: make method on LoadBalancerStatus? -func LoadBalancerStatusEqual(l, r *api.LoadBalancerStatus) bool { - return ingressSliceEqual(l.Ingress, r.Ingress) -} - -func ingressSliceEqual(lhs, rhs []api.LoadBalancerIngress) bool { - if len(lhs) != len(rhs) { - return false - } - for i := range lhs { - if !ingressEqual(&lhs[i], &rhs[i]) { - return false - } - } - return true -} - -func ingressEqual(lhs, rhs *api.LoadBalancerIngress) bool { - if lhs.IP != rhs.IP { - return false - } - if lhs.Hostname != rhs.Hostname { - return false - } - return true -} - -// TODO: make method on LoadBalancerStatus? -func LoadBalancerStatusDeepCopy(lb *api.LoadBalancerStatus) *api.LoadBalancerStatus { - c := &api.LoadBalancerStatus{} - c.Ingress = make([]api.LoadBalancerIngress, len(lb.Ingress)) - for i := range lb.Ingress { - c.Ingress[i] = lb.Ingress[i] - } - return c -} - -// GetAccessModesAsString returns a string representation of an array of access modes. -// modes, when present, are always in the same order: RWO,ROX,RWX. -func GetAccessModesAsString(modes []api.PersistentVolumeAccessMode) string { - modes = removeDuplicateAccessModes(modes) - modesStr := []string{} - if containsAccessMode(modes, api.ReadWriteOnce) { - modesStr = append(modesStr, "RWO") - } - if containsAccessMode(modes, api.ReadOnlyMany) { - modesStr = append(modesStr, "ROX") - } - if containsAccessMode(modes, api.ReadWriteMany) { - modesStr = append(modesStr, "RWX") - } - return strings.Join(modesStr, ",") -} - -// GetAccessModesAsString returns an array of AccessModes from a string created by GetAccessModesAsString -func GetAccessModesFromString(modes string) []api.PersistentVolumeAccessMode { - strmodes := strings.Split(modes, ",") - accessModes := []api.PersistentVolumeAccessMode{} - for _, s := range strmodes { - s = strings.Trim(s, " ") - switch { - case s == "RWO": - accessModes = append(accessModes, api.ReadWriteOnce) - case s == "ROX": - accessModes = append(accessModes, api.ReadOnlyMany) - case s == "RWX": - accessModes = append(accessModes, api.ReadWriteMany) - } - } - return accessModes -} - -// removeDuplicateAccessModes returns an array of access modes without any duplicates -func removeDuplicateAccessModes(modes []api.PersistentVolumeAccessMode) []api.PersistentVolumeAccessMode { - accessModes := []api.PersistentVolumeAccessMode{} - for _, m := range modes { - if !containsAccessMode(accessModes, m) { - accessModes = append(accessModes, m) - } - } - return accessModes -} - -func containsAccessMode(modes []api.PersistentVolumeAccessMode, mode api.PersistentVolumeAccessMode) bool { - for _, m := range modes { - if m == mode { - return true - } - } - return false -} - -// NodeSelectorRequirementsAsSelector converts the []NodeSelectorRequirement api type into a struct that implements -// labels.Selector. -func NodeSelectorRequirementsAsSelector(nsm []api.NodeSelectorRequirement) (labels.Selector, error) { - if len(nsm) == 0 { - return labels.Nothing(), nil - } - selector := labels.NewSelector() - for _, expr := range nsm { - var op selection.Operator - switch expr.Operator { - case api.NodeSelectorOpIn: - op = selection.In - case api.NodeSelectorOpNotIn: - op = selection.NotIn - case api.NodeSelectorOpExists: - op = selection.Exists - case api.NodeSelectorOpDoesNotExist: - op = selection.DoesNotExist - case api.NodeSelectorOpGt: - op = selection.GreaterThan - case api.NodeSelectorOpLt: - op = selection.LessThan - default: - return nil, fmt.Errorf("%q is not a valid node selector operator", expr.Operator) - } - r, err := labels.NewRequirement(expr.Key, op, expr.Values) - if err != nil { - return nil, err - } - selector = selector.Add(*r) - } - return selector, nil -} - -// GetTolerationsFromPodAnnotations gets the json serialized tolerations data from Pod.Annotations -// and converts it to the []Toleration type in api. -func GetTolerationsFromPodAnnotations(annotations map[string]string) ([]api.Toleration, error) { - var tolerations []api.Toleration - if len(annotations) > 0 && annotations[api.TolerationsAnnotationKey] != "" { - err := json.Unmarshal([]byte(annotations[api.TolerationsAnnotationKey]), &tolerations) - if err != nil { - return tolerations, err - } - } - return tolerations, nil -} - -// AddOrUpdateTolerationInPod tries to add a toleration to the pod's toleration list. -// Returns true if something was updated, false otherwise. -func AddOrUpdateTolerationInPod(pod *api.Pod, toleration *api.Toleration) bool { - podTolerations := pod.Spec.Tolerations - - var newTolerations []api.Toleration - updated := false - for i := range podTolerations { - if toleration.MatchToleration(&podTolerations[i]) { - if Semantic.DeepEqual(toleration, podTolerations[i]) { - return false - } - newTolerations = append(newTolerations, *toleration) - updated = true - continue - } - - newTolerations = append(newTolerations, podTolerations[i]) - } - - if !updated { - newTolerations = append(newTolerations, *toleration) - } - - pod.Spec.Tolerations = newTolerations - return true -} - -// TolerationToleratesTaint checks if the toleration tolerates the taint. -func TolerationToleratesTaint(toleration *api.Toleration, taint *api.Taint) bool { - if len(toleration.Effect) != 0 && toleration.Effect != taint.Effect { - return false - } - - if toleration.Key != taint.Key { - return false - } - // TODO: Use proper defaulting when Toleration becomes a field of PodSpec - if (len(toleration.Operator) == 0 || toleration.Operator == api.TolerationOpEqual) && toleration.Value == taint.Value { - return true - } - if toleration.Operator == api.TolerationOpExists { - return true - } - return false -} - -// TaintToleratedByTolerations checks if taint is tolerated by any of the tolerations. -func TaintToleratedByTolerations(taint *api.Taint, tolerations []api.Toleration) bool { - tolerated := false - for i := range tolerations { - if TolerationToleratesTaint(&tolerations[i], taint) { - tolerated = true - break - } - } - return tolerated -} - -// GetTaintsFromNodeAnnotations gets the json serialized taints data from Pod.Annotations -// and converts it to the []Taint type in api. -func GetTaintsFromNodeAnnotations(annotations map[string]string) ([]api.Taint, error) { - var taints []api.Taint - if len(annotations) > 0 && annotations[api.TaintsAnnotationKey] != "" { - err := json.Unmarshal([]byte(annotations[api.TaintsAnnotationKey]), &taints) - if err != nil { - return []api.Taint{}, err - } - } - return taints, nil -} - -// SysctlsFromPodAnnotations parses the sysctl annotations into a slice of safe Sysctls -// and a slice of unsafe Sysctls. This is only a convenience wrapper around -// SysctlsFromPodAnnotation. -func SysctlsFromPodAnnotations(a map[string]string) ([]api.Sysctl, []api.Sysctl, error) { - safe, err := SysctlsFromPodAnnotation(a[api.SysctlsPodAnnotationKey]) - if err != nil { - return nil, nil, err - } - unsafe, err := SysctlsFromPodAnnotation(a[api.UnsafeSysctlsPodAnnotationKey]) - if err != nil { - return nil, nil, err - } - - return safe, unsafe, nil -} - -// SysctlsFromPodAnnotation parses an annotation value into a slice of Sysctls. -func SysctlsFromPodAnnotation(annotation string) ([]api.Sysctl, error) { - if len(annotation) == 0 { - return nil, nil - } - - kvs := strings.Split(annotation, ",") - sysctls := make([]api.Sysctl, len(kvs)) - for i, kv := range kvs { - cs := strings.Split(kv, "=") - if len(cs) != 2 || len(cs[0]) == 0 { - return nil, fmt.Errorf("sysctl %q not of the format sysctl_name=value", kv) - } - sysctls[i].Name = cs[0] - sysctls[i].Value = cs[1] - } - return sysctls, nil -} - -// PodAnnotationsFromSysctls creates an annotation value for a slice of Sysctls. -func PodAnnotationsFromSysctls(sysctls []api.Sysctl) string { - if len(sysctls) == 0 { - return "" - } - - kvs := make([]string, len(sysctls)) - for i := range sysctls { - kvs[i] = fmt.Sprintf("%s=%s", sysctls[i].Name, sysctls[i].Value) - } - return strings.Join(kvs, ",") -} - -// GetPersistentVolumeClass returns StorageClassName. -func GetPersistentVolumeClass(volume *api.PersistentVolume) string { - // Use beta annotation first - if class, found := volume.Annotations[api.BetaStorageClassAnnotation]; found { - return class - } - - return volume.Spec.StorageClassName -} - -// GetPersistentVolumeClaimClass returns StorageClassName. If no storage class was -// requested, it returns "". -func GetPersistentVolumeClaimClass(claim *api.PersistentVolumeClaim) string { - // Use beta annotation first - if class, found := claim.Annotations[api.BetaStorageClassAnnotation]; found { - return class - } - - if claim.Spec.StorageClassName != nil { - return *claim.Spec.StorageClassName - } - - return "" -} - -// PersistentVolumeClaimHasClass returns true if given claim has set StorageClassName field. -func PersistentVolumeClaimHasClass(claim *api.PersistentVolumeClaim) bool { - // Use beta annotation first - if _, found := claim.Annotations[api.BetaStorageClassAnnotation]; found { - return true - } - - if claim.Spec.StorageClassName != nil { - return true - } - - return false -} - -// GetStorageNodeAffinityFromAnnotation gets the json serialized data from PersistentVolume.Annotations -// and converts it to the NodeAffinity type in api. -// TODO: update when storage node affinity graduates to beta -func GetStorageNodeAffinityFromAnnotation(annotations map[string]string) (*api.NodeAffinity, error) { - if len(annotations) > 0 && annotations[api.AlphaStorageNodeAffinityAnnotation] != "" { - var affinity api.NodeAffinity - err := json.Unmarshal([]byte(annotations[api.AlphaStorageNodeAffinityAnnotation]), &affinity) - if err != nil { - return nil, err - } - return &affinity, nil - } - return nil, nil -} - -// Converts NodeAffinity type to Alpha annotation for use in PersistentVolumes -// TODO: update when storage node affinity graduates to beta -func StorageNodeAffinityToAlphaAnnotation(annotations map[string]string, affinity *api.NodeAffinity) error { - if affinity == nil { - return nil - } - - json, err := json.Marshal(*affinity) - if err != nil { - return err - } - annotations[api.AlphaStorageNodeAffinityAnnotation] = string(json) - return nil -} diff --git a/pkg/api/helper/helpers_test.go b/pkg/api/helper/helpers_test.go deleted file mode 100644 index 7631b8bba0d..00000000000 --- a/pkg/api/helper/helpers_test.go +++ /dev/null @@ -1,490 +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 helper - -import ( - "reflect" - "testing" - - "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/kubernetes/pkg/api" -) - -func TestSemantic(t *testing.T) { - table := []struct { - a, b interface{} - shouldEqual bool - }{ - {resource.MustParse("0"), resource.Quantity{}, true}, - {resource.Quantity{}, resource.MustParse("0"), true}, - {resource.Quantity{}, resource.MustParse("1m"), false}, - { - resource.NewQuantity(5, resource.BinarySI), - resource.NewQuantity(5, resource.DecimalSI), - true, - }, - {resource.MustParse("2m"), resource.MustParse("1m"), false}, - } - - for index, item := range table { - if e, a := item.shouldEqual, Semantic.DeepEqual(item.a, item.b); e != a { - t.Errorf("case[%d], expected %v, got %v.", index, e, a) - } - } -} - -func TestIsStandardResource(t *testing.T) { - testCases := []struct { - input string - output bool - }{ - {"cpu", true}, - {"memory", true}, - {"disk", false}, - {"blah", false}, - {"x.y.z", false}, - {"hugepages-2Mi", true}, - } - for i, tc := range testCases { - if IsStandardResourceName(tc.input) != tc.output { - t.Errorf("case[%d], input: %s, expected: %t, got: %t", i, tc.input, tc.output, !tc.output) - } - } -} - -func TestIsStandardContainerResource(t *testing.T) { - testCases := []struct { - input string - output bool - }{ - {"cpu", true}, - {"memory", true}, - {"disk", false}, - {"hugepages-2Mi", true}, - } - for i, tc := range testCases { - if IsStandardContainerResourceName(tc.input) != tc.output { - t.Errorf("case[%d], input: %s, expected: %t, got: %t", i, tc.input, tc.output, !tc.output) - } - } -} - -func TestAddToNodeAddresses(t *testing.T) { - testCases := []struct { - existing []api.NodeAddress - toAdd []api.NodeAddress - expected []api.NodeAddress - }{ - { - existing: []api.NodeAddress{}, - toAdd: []api.NodeAddress{}, - expected: []api.NodeAddress{}, - }, - { - existing: []api.NodeAddress{}, - toAdd: []api.NodeAddress{ - {Type: api.NodeExternalIP, Address: "1.1.1.1"}, - {Type: api.NodeHostName, Address: "localhost"}, - }, - expected: []api.NodeAddress{ - {Type: api.NodeExternalIP, Address: "1.1.1.1"}, - {Type: api.NodeHostName, Address: "localhost"}, - }, - }, - { - existing: []api.NodeAddress{}, - toAdd: []api.NodeAddress{ - {Type: api.NodeExternalIP, Address: "1.1.1.1"}, - {Type: api.NodeExternalIP, Address: "1.1.1.1"}, - }, - expected: []api.NodeAddress{ - {Type: api.NodeExternalIP, Address: "1.1.1.1"}, - }, - }, - { - existing: []api.NodeAddress{ - {Type: api.NodeExternalIP, Address: "1.1.1.1"}, - {Type: api.NodeInternalIP, Address: "10.1.1.1"}, - }, - toAdd: []api.NodeAddress{ - {Type: api.NodeExternalIP, Address: "1.1.1.1"}, - {Type: api.NodeHostName, Address: "localhost"}, - }, - expected: []api.NodeAddress{ - {Type: api.NodeExternalIP, Address: "1.1.1.1"}, - {Type: api.NodeInternalIP, Address: "10.1.1.1"}, - {Type: api.NodeHostName, Address: "localhost"}, - }, - }, - } - - for i, tc := range testCases { - AddToNodeAddresses(&tc.existing, tc.toAdd...) - if !Semantic.DeepEqual(tc.expected, tc.existing) { - t.Errorf("case[%d], expected: %v, got: %v", i, tc.expected, tc.existing) - } - } -} - -func TestGetAccessModesFromString(t *testing.T) { - modes := GetAccessModesFromString("ROX") - if !containsAccessMode(modes, api.ReadOnlyMany) { - t.Errorf("Expected mode %s, but got %+v", api.ReadOnlyMany, modes) - } - - modes = GetAccessModesFromString("ROX,RWX") - if !containsAccessMode(modes, api.ReadOnlyMany) { - t.Errorf("Expected mode %s, but got %+v", api.ReadOnlyMany, modes) - } - if !containsAccessMode(modes, api.ReadWriteMany) { - t.Errorf("Expected mode %s, but got %+v", api.ReadWriteMany, modes) - } - - modes = GetAccessModesFromString("RWO,ROX,RWX") - if !containsAccessMode(modes, api.ReadOnlyMany) { - t.Errorf("Expected mode %s, but got %+v", api.ReadOnlyMany, modes) - } - if !containsAccessMode(modes, api.ReadWriteMany) { - t.Errorf("Expected mode %s, but got %+v", api.ReadWriteMany, modes) - } -} - -func TestRemoveDuplicateAccessModes(t *testing.T) { - modes := []api.PersistentVolumeAccessMode{ - api.ReadWriteOnce, api.ReadOnlyMany, api.ReadOnlyMany, api.ReadOnlyMany, - } - modes = removeDuplicateAccessModes(modes) - if len(modes) != 2 { - t.Errorf("Expected 2 distinct modes in set but found %v", len(modes)) - } -} - -func TestNodeSelectorRequirementsAsSelector(t *testing.T) { - matchExpressions := []api.NodeSelectorRequirement{{ - Key: "foo", - Operator: api.NodeSelectorOpIn, - Values: []string{"bar", "baz"}, - }} - mustParse := func(s string) labels.Selector { - out, e := labels.Parse(s) - if e != nil { - panic(e) - } - return out - } - tc := []struct { - in []api.NodeSelectorRequirement - out labels.Selector - expectErr bool - }{ - {in: nil, out: labels.Nothing()}, - {in: []api.NodeSelectorRequirement{}, out: labels.Nothing()}, - { - in: matchExpressions, - out: mustParse("foo in (baz,bar)"), - }, - { - in: []api.NodeSelectorRequirement{{ - Key: "foo", - Operator: api.NodeSelectorOpExists, - Values: []string{"bar", "baz"}, - }}, - expectErr: true, - }, - { - in: []api.NodeSelectorRequirement{{ - Key: "foo", - Operator: api.NodeSelectorOpGt, - Values: []string{"1"}, - }}, - out: mustParse("foo>1"), - }, - { - in: []api.NodeSelectorRequirement{{ - Key: "bar", - Operator: api.NodeSelectorOpLt, - Values: []string{"7"}, - }}, - out: mustParse("bar<7"), - }, - } - - for i, tc := range tc { - out, err := NodeSelectorRequirementsAsSelector(tc.in) - if err == nil && tc.expectErr { - t.Errorf("[%v]expected error but got none.", i) - } - if err != nil && !tc.expectErr { - t.Errorf("[%v]did not expect error but got: %v", i, err) - } - if !reflect.DeepEqual(out, tc.out) { - t.Errorf("[%v]expected:\n\t%+v\nbut got:\n\t%+v", i, tc.out, out) - } - } -} - -func TestSysctlsFromPodAnnotation(t *testing.T) { - type Test struct { - annotation string - expectValue []api.Sysctl - expectErr bool - } - for i, test := range []Test{ - { - annotation: "", - expectValue: nil, - }, - { - annotation: "foo.bar", - expectErr: true, - }, - { - annotation: "=123", - expectErr: true, - }, - { - annotation: "foo.bar=", - expectValue: []api.Sysctl{{Name: "foo.bar", Value: ""}}, - }, - { - annotation: "foo.bar=42", - expectValue: []api.Sysctl{{Name: "foo.bar", Value: "42"}}, - }, - { - annotation: "foo.bar=42,", - expectErr: true, - }, - { - annotation: "foo.bar=42,abc.def=1", - expectValue: []api.Sysctl{{Name: "foo.bar", Value: "42"}, {Name: "abc.def", Value: "1"}}, - }, - } { - sysctls, err := SysctlsFromPodAnnotation(test.annotation) - if test.expectErr && err == nil { - t.Errorf("[%v]expected error but got none", i) - } else if !test.expectErr && err != nil { - t.Errorf("[%v]did not expect error but got: %v", i, err) - } else if !reflect.DeepEqual(sysctls, test.expectValue) { - t.Errorf("[%v]expect value %v but got %v", i, test.expectValue, sysctls) - } - } -} - -// TODO: remove when alpha support for topology constraints is removed -func TestGetNodeAffinityFromAnnotations(t *testing.T) { - testCases := []struct { - annotations map[string]string - expectErr bool - }{ - { - annotations: nil, - expectErr: false, - }, - { - annotations: map[string]string{}, - expectErr: false, - }, - { - annotations: map[string]string{ - api.AlphaStorageNodeAffinityAnnotation: `{ - "requiredDuringSchedulingIgnoredDuringExecution": { - "nodeSelectorTerms": [ - { "matchExpressions": [ - { "key": "test-key1", - "operator": "In", - "values": ["test-value1", "test-value2"] - }, - { "key": "test-key2", - "operator": "In", - "values": ["test-value1", "test-value2"] - } - ]} - ]} - }`, - }, - expectErr: false, - }, - { - annotations: map[string]string{ - api.AlphaStorageNodeAffinityAnnotation: `[{ - "requiredDuringSchedulingIgnoredDuringExecution": { - "nodeSelectorTerms": [ - { "matchExpressions": [ - { "key": "test-key1", - "operator": "In", - "values": ["test-value1", "test-value2"] - }, - { "key": "test-key2", - "operator": "In", - "values": ["test-value1", "test-value2"] - } - ]} - ]} - }]`, - }, - expectErr: true, - }, - { - annotations: map[string]string{ - api.AlphaStorageNodeAffinityAnnotation: `{ - "requiredDuringSchedulingIgnoredDuringExecution": { - "nodeSelectorTerms": - "matchExpressions": [ - { "key": "test-key1", - "operator": "In", - "values": ["test-value1", "test-value2"] - }, - { "key": "test-key2", - "operator": "In", - "values": ["test-value1", "test-value2"] - } - ]} - } - }`, - }, - expectErr: true, - }, - } - - for i, tc := range testCases { - _, err := GetStorageNodeAffinityFromAnnotation(tc.annotations) - if err == nil && tc.expectErr { - t.Errorf("[%v]expected error but got none.", i) - } - if err != nil && !tc.expectErr { - t.Errorf("[%v]did not expect error but got: %v", i, err) - } - } -} - -func TestIsHugePageResourceName(t *testing.T) { - testCases := []struct { - name api.ResourceName - result bool - }{ - { - name: api.ResourceName("hugepages-2Mi"), - result: true, - }, - { - name: api.ResourceName("hugepages-1Gi"), - result: true, - }, - { - name: api.ResourceName("cpu"), - result: false, - }, - { - name: api.ResourceName("memory"), - result: false, - }, - } - for _, testCase := range testCases { - if testCase.result != IsHugePageResourceName(testCase.name) { - t.Errorf("resource: %v expected result: %v", testCase.name, testCase.result) - } - } -} - -func TestHugePageResourceName(t *testing.T) { - testCases := []struct { - pageSize resource.Quantity - name api.ResourceName - }{ - { - pageSize: resource.MustParse("2Mi"), - name: api.ResourceName("hugepages-2Mi"), - }, - { - pageSize: resource.MustParse("1Gi"), - name: api.ResourceName("hugepages-1Gi"), - }, - { - // verify we do not regress our canonical representation - pageSize: *resource.NewQuantity(int64(2097152), resource.BinarySI), - name: api.ResourceName("hugepages-2Mi"), - }, - } - for _, testCase := range testCases { - if result := HugePageResourceName(testCase.pageSize); result != testCase.name { - t.Errorf("pageSize: %v, expected: %v, but got: %v", testCase.pageSize.String(), testCase.name, result.String()) - } - } -} - -func TestHugePageSizeFromResourceName(t *testing.T) { - testCases := []struct { - name api.ResourceName - expectErr bool - pageSize resource.Quantity - }{ - { - name: api.ResourceName("hugepages-2Mi"), - pageSize: resource.MustParse("2Mi"), - expectErr: false, - }, - { - name: api.ResourceName("hugepages-1Gi"), - pageSize: resource.MustParse("1Gi"), - expectErr: false, - }, - { - name: api.ResourceName("hugepages-bad"), - expectErr: true, - }, - } - for _, testCase := range testCases { - value, err := HugePageSizeFromResourceName(testCase.name) - if testCase.expectErr && err == nil { - t.Errorf("Expected an error for %v", testCase.name) - } else if !testCase.expectErr && err != nil { - t.Errorf("Unexpected error for %v, got %v", testCase.name, err) - } else if testCase.pageSize.Value() != value.Value() { - t.Errorf("Unexpected pageSize for resource %v got %v", testCase.name, value.String()) - } - } -} - -func TestIsOvercommitAllowed(t *testing.T) { - testCases := []struct { - name api.ResourceName - allowed bool - }{ - { - name: api.ResourceCPU, - allowed: true, - }, - { - name: api.ResourceMemory, - allowed: true, - }, - { - name: api.ResourceNvidiaGPU, - allowed: false, - }, - { - name: HugePageResourceName(resource.MustParse("2Mi")), - allowed: false, - }, - } - for _, testCase := range testCases { - if testCase.allowed != IsOvercommitAllowed(testCase.name) { - t.Errorf("Unexpected result for %v", testCase.name) - } - } -} diff --git a/pkg/api/helper/qos/BUILD b/pkg/api/helper/qos/BUILD deleted file mode 100644 index b56b19db173..00000000000 --- a/pkg/api/helper/qos/BUILD +++ /dev/null @@ -1,31 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["qos.go"], - importpath = "k8s.io/kubernetes/pkg/api/helper/qos", - deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/pkg/api/helper/qos/qos.go b/pkg/api/helper/qos/qos.go deleted file mode 100644 index cc58bde0c31..00000000000 --- a/pkg/api/helper/qos/qos.go +++ /dev/null @@ -1,97 +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. -*/ - -// NOTE: DO NOT use those helper functions through client-go, the -// package path will be changed in the future. -package qos - -import ( - "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper" -) - -func isSupportedQoSComputeResource(name api.ResourceName) bool { - supportedQoSComputeResources := sets.NewString(string(api.ResourceCPU), string(api.ResourceMemory)) - return supportedQoSComputeResources.Has(string(name)) || helper.IsHugePageResourceName(name) -} - -// GetPodQOS returns the QoS class of a pod. -// A pod is besteffort if none of its containers have specified any requests or limits. -// A pod is guaranteed only when requests and limits are specified for all the containers and they are equal. -// A pod is burstable if limits and requests do not match across all containers. -func GetPodQOS(pod *api.Pod) api.PodQOSClass { - requests := api.ResourceList{} - limits := api.ResourceList{} - zeroQuantity := resource.MustParse("0") - isGuaranteed := true - for _, container := range pod.Spec.Containers { - // process requests - for name, quantity := range container.Resources.Requests { - if !isSupportedQoSComputeResource(name) { - continue - } - if quantity.Cmp(zeroQuantity) == 1 { - delta := quantity.Copy() - if _, exists := requests[name]; !exists { - requests[name] = *delta - } else { - delta.Add(requests[name]) - requests[name] = *delta - } - } - } - // process limits - qosLimitsFound := sets.NewString() - for name, quantity := range container.Resources.Limits { - if !isSupportedQoSComputeResource(name) { - continue - } - if quantity.Cmp(zeroQuantity) == 1 { - qosLimitsFound.Insert(string(name)) - delta := quantity.Copy() - if _, exists := limits[name]; !exists { - limits[name] = *delta - } else { - delta.Add(limits[name]) - limits[name] = *delta - } - } - } - - if !qosLimitsFound.HasAll(string(api.ResourceMemory), string(api.ResourceCPU)) { - isGuaranteed = false - } - } - if len(requests) == 0 && len(limits) == 0 { - return api.PodQOSBestEffort - } - // Check is requests match limits for all resources. - if isGuaranteed { - for name, req := range requests { - if lim, exists := limits[name]; !exists || lim.Cmp(req) != 0 { - isGuaranteed = false - break - } - } - } - if isGuaranteed && - len(requests) == len(limits) { - return api.PodQOSGuaranteed - } - return api.PodQOSBurstable -} diff --git a/pkg/api/install/BUILD b/pkg/api/install/BUILD deleted file mode 100644 index 1ac75fd800b..00000000000 --- a/pkg/api/install/BUILD +++ /dev/null @@ -1,48 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = ["install.go"], - importpath = "k8s.io/kubernetes/pkg/api/install", - deps = [ - "//pkg/api:go_default_library", - "//pkg/api/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["install_test.go"], - importpath = "k8s.io/kubernetes/pkg/api/install", - library = ":go_default_library", - deps = [ - "//pkg/api:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/pkg/api/install/install.go b/pkg/api/install/install.go deleted file mode 100644 index cd3879ccb6a..00000000000 --- a/pkg/api/install/install.go +++ /dev/null @@ -1,69 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES 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 v1 monolithic api, making it available as an -// option to all of the API encoding/decoding machinery. -package install - -import ( - "k8s.io/apimachinery/pkg/apimachinery/announced" - "k8s.io/apimachinery/pkg/apimachinery/registered" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/v1" -) - -func init() { - Install(api.GroupFactoryRegistry, api.Registry, api.Scheme) -} - -// Install registers the API group and adds types to a scheme -func Install(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *registered.APIRegistrationManager, scheme *runtime.Scheme) { - if err := announced.NewGroupMetaFactory( - &announced.GroupMetaFactoryArgs{ - GroupName: api.GroupName, - VersionPreferenceOrder: []string{v1.SchemeGroupVersion.Version}, - AddInternalObjectsToScheme: api.AddToScheme, - RootScopedKinds: sets.NewString( - "Node", - "Namespace", - "PersistentVolume", - "ComponentStatus", - ), - IgnoredKinds: sets.NewString( - "ListOptions", - "DeleteOptions", - "Status", - "PodLogOptions", - "PodExecOptions", - "PodAttachOptions", - "PodPortForwardOptions", - "PodProxyOptions", - "NodeProxyOptions", - "ServiceProxyOptions", - "ThirdPartyResource", - "ThirdPartyResourceData", - "ThirdPartyResourceList", - ), - }, - announced.VersionToSchemeFunc{ - v1.SchemeGroupVersion.Version: v1.AddToScheme, - }, - ).Announce(groupFactoryRegistry).RegisterAndEnable(registry, scheme); err != nil { - panic(err) - } -} diff --git a/pkg/api/install/install_test.go b/pkg/api/install/install_test.go deleted file mode 100644 index d0a2b0b30a0..00000000000 --- a/pkg/api/install/install_test.go +++ /dev/null @@ -1,139 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES 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 - -import ( - "encoding/json" - "reflect" - "testing" - - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - internal "k8s.io/kubernetes/pkg/api" -) - -func TestResourceVersioner(t *testing.T) { - g, err := internal.Registry.Group(v1.GroupName) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - intf, err := g.DefaultInterfacesFor(v1.SchemeGroupVersion) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - accessor := intf.MetadataAccessor - - pod := internal.Pod{ObjectMeta: metav1.ObjectMeta{ResourceVersion: "10"}} - version, err := accessor.ResourceVersion(&pod) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if version != "10" { - t.Errorf("unexpected version %v", version) - } - - podList := internal.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}} - version, err = accessor.ResourceVersion(&podList) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if version != "10" { - t.Errorf("unexpected version %v", version) - } -} - -func TestCodec(t *testing.T) { - pod := internal.Pod{} - // We do want to use package registered rather than testapi here, because we - // want to test if the package install and package registered work as expected. - data, err := runtime.Encode(internal.Codecs.LegacyCodec(internal.Registry.GroupOrDie(internal.GroupName).GroupVersion), &pod) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - other := internal.Pod{} - if err := json.Unmarshal(data, &other); err != nil { - t.Fatalf("unexpected error: %v", err) - } - if other.APIVersion != internal.Registry.GroupOrDie(internal.GroupName).GroupVersion.Version || other.Kind != "Pod" { - t.Errorf("unexpected unmarshalled object %#v", other) - } -} - -func TestInterfacesFor(t *testing.T) { - if _, err := internal.Registry.GroupOrDie(internal.GroupName).InterfacesFor(internal.SchemeGroupVersion); err == nil { - t.Fatalf("unexpected non-error: %v", err) - } - for i, version := range internal.Registry.GroupOrDie(internal.GroupName).GroupVersions { - if vi, err := internal.Registry.GroupOrDie(internal.GroupName).InterfacesFor(version); err != nil || vi == nil { - t.Fatalf("%d: unexpected result: %v", i, err) - } - } -} - -func TestRESTMapper(t *testing.T) { - gv := schema.GroupVersion{Group: "", Version: "v1"} - rcGVK := gv.WithKind("ReplicationController") - podTemplateGVK := gv.WithKind("PodTemplate") - - if gvk, err := internal.Registry.RESTMapper().KindFor(internal.SchemeGroupVersion.WithResource("replicationcontrollers")); err != nil || gvk != rcGVK { - t.Errorf("unexpected version mapping: %v %v", gvk, err) - } - - if m, err := internal.Registry.GroupOrDie(internal.GroupName).RESTMapper.RESTMapping(podTemplateGVK.GroupKind(), ""); err != nil || m.GroupVersionKind != podTemplateGVK || m.Resource != "podtemplates" { - t.Errorf("unexpected version mapping: %#v %v", m, err) - } - - for _, version := range internal.Registry.GroupOrDie(internal.GroupName).GroupVersions { - mapping, err := internal.Registry.GroupOrDie(internal.GroupName).RESTMapper.RESTMapping(rcGVK.GroupKind(), version.Version) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - - if mapping.Resource != "replicationControllers" && mapping.Resource != "replicationcontrollers" { - t.Errorf("incorrect resource name: %#v", mapping) - } - if mapping.GroupVersionKind.GroupVersion() != version { - t.Errorf("incorrect version: %v", mapping) - } - - interfaces, _ := internal.Registry.GroupOrDie(internal.GroupName).InterfacesFor(version) - if mapping.ObjectConvertor != interfaces.ObjectConvertor { - t.Errorf("unexpected: %#v, expected: %#v", mapping, interfaces) - } - - rc := &internal.ReplicationController{ObjectMeta: metav1.ObjectMeta{Name: "foo"}} - name, err := mapping.MetadataAccessor.Name(rc) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - if name != "foo" { - t.Errorf("unable to retrieve object meta with: %v", mapping.MetadataAccessor) - } - } -} - -func TestUnversioned(t *testing.T) { - for _, obj := range []runtime.Object{ - &metav1.Status{}, - } { - if unversioned, ok := internal.Scheme.IsUnversioned(obj); !unversioned || !ok { - t.Errorf("%v is expected to be unversioned", reflect.TypeOf(obj)) - } - } -} diff --git a/pkg/api/legacyscheme/BUILD b/pkg/api/legacyscheme/BUILD new file mode 100644 index 00000000000..42136a95342 --- /dev/null +++ b/pkg/api/legacyscheme/BUILD @@ -0,0 +1,28 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["scheme.go"], + importpath = "k8s.io/kubernetes/pkg/api/legacyscheme", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/api/legacyscheme/scheme.go b/pkg/api/legacyscheme/scheme.go new file mode 100644 index 00000000000..64c63a3606e --- /dev/null +++ b/pkg/api/legacyscheme/scheme.go @@ -0,0 +1,46 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package legacyscheme + +import ( + "os" + + "k8s.io/apimachinery/pkg/apimachinery/announced" + "k8s.io/apimachinery/pkg/apimachinery/registered" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" +) + +// GroupFactoryRegistry is the APIGroupFactoryRegistry (overlaps a bit with Registry, see comments in package for details) +var GroupFactoryRegistry = make(announced.APIGroupFactoryRegistry) + +// Registry is an instance of an API registry. This is an interim step to start removing the idea of a global +// API registry. +var Registry = registered.NewOrDie(os.Getenv("KUBE_API_VERSIONS")) + +// Scheme is the default instance of runtime.Scheme to which types in the Kubernetes API are already registered. +// NOTE: If you are copying this file to start a new api group, STOP! Copy the +// extensions group instead. This Scheme is special and should appear ONLY in +// the api group, unless you really know what you're doing. +// TODO(lavalamp): make the above error impossible. +var Scheme = runtime.NewScheme() + +// Codecs provides access to encoding and decoding for the scheme +var Codecs = serializer.NewCodecFactory(Scheme) + +// ParameterCodec handles versioning of objects that are converted to query parameters. +var ParameterCodec = runtime.NewParameterCodec(Scheme) diff --git a/pkg/api/persistentvolume/BUILD b/pkg/api/persistentvolume/BUILD index d91bb5d1bea..87cfdf076b8 100644 --- a/pkg/api/persistentvolume/BUILD +++ b/pkg/api/persistentvolume/BUILD @@ -10,7 +10,11 @@ go_library( name = "go_default_library", srcs = ["util.go"], importpath = "k8s.io/kubernetes/pkg/api/persistentvolume", - deps = ["//pkg/api:go_default_library"], + deps = [ + "//pkg/apis/core:go_default_library", + "//pkg/features:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + ], ) filegroup( @@ -29,11 +33,13 @@ filegroup( go_test( name = "go_default_test", srcs = ["util_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/api/persistentvolume", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/features:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", ], ) diff --git a/pkg/api/persistentvolume/util.go b/pkg/api/persistentvolume/util.go index 0a8de7c5e68..76b2b1c51c6 100644 --- a/pkg/api/persistentvolume/util.go +++ b/pkg/api/persistentvolume/util.go @@ -17,7 +17,9 @@ limitations under the License. package persistentvolume import ( - "k8s.io/kubernetes/pkg/api" + utilfeature "k8s.io/apiserver/pkg/util/feature" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/features" ) func getClaimRefNamespace(pv *api.PersistentVolume) string { @@ -60,20 +62,50 @@ func VisitPVSecretNames(pv *api.PersistentVolume, visitor Visitor) bool { } } case source.FlexVolume != nil: - if source.FlexVolume.SecretRef != nil && !visitor(getClaimRefNamespace(pv), source.FlexVolume.SecretRef.Name) { - return false + if source.FlexVolume.SecretRef != nil { + // previously persisted PV objects use claimRef namespace + ns := getClaimRefNamespace(pv) + if len(source.FlexVolume.SecretRef.Namespace) > 0 { + // use the secret namespace if namespace is set + ns = source.FlexVolume.SecretRef.Namespace + } + if !visitor(ns, source.FlexVolume.SecretRef.Name) { + return false + } } case source.RBD != nil: - if source.RBD.SecretRef != nil && !visitor(getClaimRefNamespace(pv), source.RBD.SecretRef.Name) { - return false + if source.RBD.SecretRef != nil { + // previously persisted PV objects use claimRef namespace + ns := getClaimRefNamespace(pv) + if len(source.RBD.SecretRef.Namespace) > 0 { + // use the secret namespace if namespace is set + ns = source.RBD.SecretRef.Namespace + } + if !visitor(ns, source.RBD.SecretRef.Name) { + return false + } } case source.ScaleIO != nil: - if source.ScaleIO.SecretRef != nil && !visitor(getClaimRefNamespace(pv), source.ScaleIO.SecretRef.Name) { - return false + if source.ScaleIO.SecretRef != nil { + ns := getClaimRefNamespace(pv) + if source.ScaleIO.SecretRef != nil && len(source.ScaleIO.SecretRef.Namespace) > 0 { + ns = source.ScaleIO.SecretRef.Namespace + } + if !visitor(ns, source.ScaleIO.SecretRef.Name) { + return false + } } case source.ISCSI != nil: - if source.ISCSI.SecretRef != nil && !visitor(getClaimRefNamespace(pv), source.ISCSI.SecretRef.Name) { - return false + if source.ISCSI.SecretRef != nil { + // previously persisted PV objects use claimRef namespace + ns := getClaimRefNamespace(pv) + if len(source.ISCSI.SecretRef.Namespace) > 0 { + // use the secret namespace if namespace is set + ns = source.ISCSI.SecretRef.Namespace + } + if !visitor(ns, source.ISCSI.SecretRef.Name) { + return false + } } case source.StorageOS != nil: if source.StorageOS.SecretRef != nil && !visitor(source.StorageOS.SecretRef.Namespace, source.StorageOS.SecretRef.Name) { @@ -82,3 +114,11 @@ func VisitPVSecretNames(pv *api.PersistentVolume, visitor Visitor) bool { } return true } + +// DropDisabledAlphaFields removes disabled fields from the pv spec. +// This should be called from PrepareForCreate/PrepareForUpdate for all resources containing a pv spec. +func DropDisabledAlphaFields(pvSpec *api.PersistentVolumeSpec) { + if !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + pvSpec.VolumeMode = nil + } +} diff --git a/pkg/api/persistentvolume/util_test.go b/pkg/api/persistentvolume/util_test.go index 8e83f0644c6..ca2d827eb7d 100644 --- a/pkg/api/persistentvolume/util_test.go +++ b/pkg/api/persistentvolume/util_test.go @@ -24,7 +24,9 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" + utilfeature "k8s.io/apiserver/pkg/util/feature" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/features" ) func TestPVSecrets(t *testing.T) { @@ -59,26 +61,54 @@ func TestPVSecrets(t *testing.T) { {Spec: api.PersistentVolumeSpec{ ClaimRef: &api.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"}, PersistentVolumeSource: api.PersistentVolumeSource{ - FlexVolume: &api.FlexVolumeSource{ - SecretRef: &api.LocalObjectReference{ + FlexVolume: &api.FlexPersistentVolumeSource{ + SecretRef: &api.SecretReference{ + Name: "Spec.PersistentVolumeSource.FlexVolume.SecretRef", + Namespace: "flexns"}}}}}, + {Spec: api.PersistentVolumeSpec{ + ClaimRef: &api.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"}, + PersistentVolumeSource: api.PersistentVolumeSource{ + FlexVolume: &api.FlexPersistentVolumeSource{ + SecretRef: &api.SecretReference{ Name: "Spec.PersistentVolumeSource.FlexVolume.SecretRef"}}}}}, {Spec: api.PersistentVolumeSpec{ ClaimRef: &api.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"}, PersistentVolumeSource: api.PersistentVolumeSource{ - RBD: &api.RBDVolumeSource{ - SecretRef: &api.LocalObjectReference{ + RBD: &api.RBDPersistentVolumeSource{ + SecretRef: &api.SecretReference{ Name: "Spec.PersistentVolumeSource.RBD.SecretRef"}}}}}, {Spec: api.PersistentVolumeSpec{ ClaimRef: &api.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"}, PersistentVolumeSource: api.PersistentVolumeSource{ - ScaleIO: &api.ScaleIOVolumeSource{ - SecretRef: &api.LocalObjectReference{ + RBD: &api.RBDPersistentVolumeSource{ + SecretRef: &api.SecretReference{ + Name: "Spec.PersistentVolumeSource.RBD.SecretRef", + Namespace: "rbdns"}}}}}, + {Spec: api.PersistentVolumeSpec{ + ClaimRef: &api.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"}, + PersistentVolumeSource: api.PersistentVolumeSource{ + ScaleIO: &api.ScaleIOPersistentVolumeSource{ + SecretRef: &api.SecretReference{ Name: "Spec.PersistentVolumeSource.ScaleIO.SecretRef"}}}}}, {Spec: api.PersistentVolumeSpec{ ClaimRef: &api.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"}, PersistentVolumeSource: api.PersistentVolumeSource{ - ISCSI: &api.ISCSIVolumeSource{ - SecretRef: &api.LocalObjectReference{ + ScaleIO: &api.ScaleIOPersistentVolumeSource{ + SecretRef: &api.SecretReference{ + Name: "Spec.PersistentVolumeSource.ScaleIO.SecretRef", + Namespace: "scaleions"}}}}}, + {Spec: api.PersistentVolumeSpec{ + ClaimRef: &api.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"}, + PersistentVolumeSource: api.PersistentVolumeSource{ + ISCSI: &api.ISCSIPersistentVolumeSource{ + SecretRef: &api.SecretReference{ + Name: "Spec.PersistentVolumeSource.ISCSI.SecretRef", + Namespace: "iscsi"}}}}}, + {Spec: api.PersistentVolumeSpec{ + ClaimRef: &api.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"}, + PersistentVolumeSource: api.PersistentVolumeSource{ + ISCSI: &api.ISCSIPersistentVolumeSource{ + SecretRef: &api.SecretReference{ Name: "Spec.PersistentVolumeSource.ISCSI.SecretRef"}}}}}, {Spec: api.PersistentVolumeSpec{ ClaimRef: &api.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"}, @@ -137,12 +167,22 @@ func TestPVSecrets(t *testing.T) { expectedNamespacedNames := sets.NewString( "claimrefns/Spec.PersistentVolumeSource.AzureFile.SecretName", "Spec.PersistentVolumeSource.AzureFile.SecretNamespace/Spec.PersistentVolumeSource.AzureFile.SecretName", + "claimrefns/Spec.PersistentVolumeSource.CephFS.SecretRef", "cephfs/Spec.PersistentVolumeSource.CephFS.SecretRef", + "claimrefns/Spec.PersistentVolumeSource.FlexVolume.SecretRef", + "flexns/Spec.PersistentVolumeSource.FlexVolume.SecretRef", + "claimrefns/Spec.PersistentVolumeSource.RBD.SecretRef", + "rbdns/Spec.PersistentVolumeSource.RBD.SecretRef", + "claimrefns/Spec.PersistentVolumeSource.ScaleIO.SecretRef", + "scaleions/Spec.PersistentVolumeSource.ScaleIO.SecretRef", + "claimrefns/Spec.PersistentVolumeSource.ISCSI.SecretRef", + "iscsi/Spec.PersistentVolumeSource.ISCSI.SecretRef", + "storageosns/Spec.PersistentVolumeSource.StorageOS.SecretRef", ) if missingNames := expectedNamespacedNames.Difference(extractedNamesWithNamespace); len(missingNames) > 0 { @@ -188,3 +228,62 @@ func collectSecretPaths(t *testing.T, path *field.Path, name string, tp reflect. return secretPaths } + +func newHostPathType(pathType string) *api.HostPathType { + hostPathType := new(api.HostPathType) + *hostPathType = api.HostPathType(pathType) + return hostPathType +} + +func TestDropAlphaPVVolumeMode(t *testing.T) { + vmode := api.PersistentVolumeFilesystem + + // PersistentVolume with VolumeMode set + pv := api.PersistentVolume{ + Spec: api.PersistentVolumeSpec{ + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + PersistentVolumeSource: api.PersistentVolumeSource{ + HostPath: &api.HostPathVolumeSource{ + Path: "/foo", + Type: newHostPathType(string(api.HostPathDirectory)), + }, + }, + StorageClassName: "test-storage-class", + VolumeMode: &vmode, + }, + } + + // Enable alpha feature BlockVolume + err1 := utilfeature.DefaultFeatureGate.Set("BlockVolume=true") + if err1 != nil { + t.Fatalf("Failed to enable feature gate for BlockVolume: %v", err1) + } + + // now test dropping the fields - should not be dropped + DropDisabledAlphaFields(&pv.Spec) + + // check to make sure VolumeDevices is still present + // if featureset is set to true + if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + if pv.Spec.VolumeMode == nil { + t.Error("VolumeMode in pv.Spec should not have been dropped based on feature-gate") + } + } + + // Disable alpha feature BlockVolume + err := utilfeature.DefaultFeatureGate.Set("BlockVolume=false") + if err != nil { + t.Fatalf("Failed to disable feature gate for BlockVolume: %v", err) + } + + // now test dropping the fields + DropDisabledAlphaFields(&pv.Spec) + + // check to make sure VolumeDevices is nil + // if featureset is set to false + if !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + if pv.Spec.VolumeMode != nil { + t.Error("DropDisabledAlphaFields VolumeMode for pv.Spec failed") + } + } +} diff --git a/pkg/api/persistentvolumeclaim/BUILD b/pkg/api/persistentvolumeclaim/BUILD new file mode 100644 index 00000000000..064f9c82ace --- /dev/null +++ b/pkg/api/persistentvolumeclaim/BUILD @@ -0,0 +1,43 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_library( + name = "go_default_library", + srcs = ["util.go"], + importpath = "k8s.io/kubernetes/pkg/api/persistentvolumeclaim", + deps = [ + "//pkg/apis/core:go_default_library", + "//pkg/features:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) + +go_test( + name = "go_default_test", + srcs = ["util_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/api/persistentvolumeclaim", + deps = [ + "//pkg/apis/core:go_default_library", + "//pkg/features:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + ], +) diff --git a/pkg/api/persistentvolumeclaim/OWNERS b/pkg/api/persistentvolumeclaim/OWNERS new file mode 100755 index 00000000000..8575aa9767a --- /dev/null +++ b/pkg/api/persistentvolumeclaim/OWNERS @@ -0,0 +1,4 @@ +reviewers: +- smarterclayton +- jsafrane +- david-mcmahon diff --git a/pkg/api/persistentvolumeclaim/util.go b/pkg/api/persistentvolumeclaim/util.go new file mode 100644 index 00000000000..a6321d3404f --- /dev/null +++ b/pkg/api/persistentvolumeclaim/util.go @@ -0,0 +1,31 @@ +/* +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 persistentvolumeclaim + +import ( + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/features" +) + +// DropDisabledAlphaFields removes disabled fields from the pvc spec. +// This should be called from PrepareForCreate/PrepareForUpdate for all resources containing a pvc spec. +func DropDisabledAlphaFields(pvcSpec *core.PersistentVolumeClaimSpec) { + if !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + pvcSpec.VolumeMode = nil + } +} diff --git a/pkg/api/persistentvolumeclaim/util_test.go b/pkg/api/persistentvolumeclaim/util_test.go new file mode 100644 index 00000000000..e12acb3a9e7 --- /dev/null +++ b/pkg/api/persistentvolumeclaim/util_test.go @@ -0,0 +1,71 @@ +/* +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 persistentvolumeclaim + +import ( + "testing" + + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/features" +) + +func TestDropAlphaPVCVolumeMode(t *testing.T) { + vmode := core.PersistentVolumeFilesystem + + // PersistentVolume with VolumeMode set + pvc := core.PersistentVolumeClaim{ + Spec: core.PersistentVolumeClaimSpec{ + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + VolumeMode: &vmode, + }, + } + + // Enable alpha feature BlockVolume + err1 := utilfeature.DefaultFeatureGate.Set("BlockVolume=true") + if err1 != nil { + t.Fatalf("Failed to enable feature gate for BlockVolume: %v", err1) + } + + // now test dropping the fields - should not be dropped + DropDisabledAlphaFields(&pvc.Spec) + + // check to make sure VolumeDevices is still present + // if featureset is set to true + if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + if pvc.Spec.VolumeMode == nil { + t.Error("VolumeMode in pvc.Spec should not have been dropped based on feature-gate") + } + } + + // Disable alpha feature BlockVolume + err := utilfeature.DefaultFeatureGate.Set("BlockVolume=false") + if err != nil { + t.Fatalf("Failed to disable feature gate for BlockVolume: %v", err) + } + + // now test dropping the fields + DropDisabledAlphaFields(&pvc.Spec) + + // check to make sure VolumeDevices is nil + // if featureset is set to false + if !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + if pvc.Spec.VolumeMode != nil { + t.Error("DropDisabledAlphaFields VolumeMode for pvc.Spec failed") + } + } +} diff --git a/pkg/api/pod/BUILD b/pkg/api/pod/BUILD index 02631dda2d4..d29ddd25d64 100644 --- a/pkg/api/pod/BUILD +++ b/pkg/api/pod/BUILD @@ -11,7 +11,7 @@ go_library( srcs = ["util.go"], importpath = "k8s.io/kubernetes/pkg/api/pod", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/features:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", @@ -34,11 +34,13 @@ filegroup( go_test( name = "go_default_test", srcs = ["util_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/api/pod", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/features:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", ], ) diff --git a/pkg/api/pod/util.go b/pkg/api/pod/util.go index 7c8afdf46a8..3cb7d69f399 100644 --- a/pkg/api/pod/util.go +++ b/pkg/api/pod/util.go @@ -19,7 +19,7 @@ package pod import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/features" ) @@ -243,12 +243,15 @@ func DropDisabledAlphaFields(podSpec *api.PodSpec) { } } } + for i := range podSpec.Containers { DropDisabledVolumeMountsAlphaFields(podSpec.Containers[i].VolumeMounts) } for i := range podSpec.InitContainers { DropDisabledVolumeMountsAlphaFields(podSpec.InitContainers[i].VolumeMounts) } + + DropDisabledVolumeDevicesAlphaFields(podSpec) } // DropDisabledVolumeMountsAlphaFields removes disabled fields from []VolumeMount. @@ -260,3 +263,16 @@ func DropDisabledVolumeMountsAlphaFields(volumeMounts []api.VolumeMount) { } } } + +// DropDisabledVolumeDevicesAlphaFields removes disabled fields from []VolumeDevice. +// This should be called from PrepareForCreate/PrepareForUpdate for all resources containing a VolumeDevice +func DropDisabledVolumeDevicesAlphaFields(podSpec *api.PodSpec) { + if !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + for i := range podSpec.Containers { + podSpec.Containers[i].VolumeDevices = nil + } + for i := range podSpec.InitContainers { + podSpec.InitContainers[i].VolumeDevices = nil + } + } +} diff --git a/pkg/api/pod/util_test.go b/pkg/api/pod/util_test.go index 169e7d116d6..1c7eb328fca 100644 --- a/pkg/api/pod/util_test.go +++ b/pkg/api/pod/util_test.go @@ -24,7 +24,9 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" + utilfeature "k8s.io/apiserver/pkg/util/feature" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/features" ) func TestPodSecrets(t *testing.T) { @@ -125,7 +127,7 @@ func TestPodSecrets(t *testing.T) { "Spec.Volumes[*].VolumeSource.ISCSI.SecretRef", "Spec.Volumes[*].VolumeSource.StorageOS.SecretRef", ) - secretPaths := collectSecretPaths(t, nil, "", reflect.TypeOf(&api.Pod{})) + secretPaths := collectResourcePaths(t, "secret", nil, "", reflect.TypeOf(&api.Pod{})) secretPaths = secretPaths.Difference(excludedSecretPaths) if missingPaths := expectedSecretPaths.Difference(secretPaths); len(missingPaths) > 0 { t.Logf("Missing expected secret paths:\n%s", strings.Join(missingPaths.List(), "\n")) @@ -146,36 +148,193 @@ func TestPodSecrets(t *testing.T) { } } -// collectSecretPaths traverses the object, computing all the struct paths that lead to fields with "secret" in the name. -func collectSecretPaths(t *testing.T, path *field.Path, name string, tp reflect.Type) sets.String { - secretPaths := sets.NewString() +// collectResourcePaths traverses the object, computing all the struct paths that lead to fields with resourcename in the name. +func collectResourcePaths(t *testing.T, resourcename string, path *field.Path, name string, tp reflect.Type) sets.String { + resourcename = strings.ToLower(resourcename) + resourcePaths := sets.NewString() if tp.Kind() == reflect.Ptr { - secretPaths.Insert(collectSecretPaths(t, path, name, tp.Elem()).List()...) - return secretPaths + resourcePaths.Insert(collectResourcePaths(t, resourcename, path, name, tp.Elem()).List()...) + return resourcePaths } - if strings.Contains(strings.ToLower(name), "secret") { - secretPaths.Insert(path.String()) + if strings.Contains(strings.ToLower(name), resourcename) { + resourcePaths.Insert(path.String()) } switch tp.Kind() { case reflect.Ptr: - secretPaths.Insert(collectSecretPaths(t, path, name, tp.Elem()).List()...) + resourcePaths.Insert(collectResourcePaths(t, resourcename, path, name, tp.Elem()).List()...) case reflect.Struct: for i := 0; i < tp.NumField(); i++ { field := tp.Field(i) - secretPaths.Insert(collectSecretPaths(t, path.Child(field.Name), field.Name, field.Type).List()...) + resourcePaths.Insert(collectResourcePaths(t, resourcename, path.Child(field.Name), field.Name, field.Type).List()...) } case reflect.Interface: - t.Errorf("cannot find secret fields in interface{} field %s", path.String()) + t.Errorf("cannot find %s fields in interface{} field %s", resourcename, path.String()) case reflect.Map: - secretPaths.Insert(collectSecretPaths(t, path.Key("*"), "", tp.Elem()).List()...) + resourcePaths.Insert(collectResourcePaths(t, resourcename, path.Key("*"), "", tp.Elem()).List()...) case reflect.Slice: - secretPaths.Insert(collectSecretPaths(t, path.Key("*"), "", tp.Elem()).List()...) + resourcePaths.Insert(collectResourcePaths(t, resourcename, path.Key("*"), "", tp.Elem()).List()...) default: // all primitive types } - return secretPaths + return resourcePaths +} + +func TestPodConfigmaps(t *testing.T) { + // Stub containing all possible ConfigMap references in a pod. + // The names of the referenced ConfigMaps match struct paths detected by reflection. + pod := &api.Pod{ + Spec: api.PodSpec{ + Containers: []api.Container{{ + EnvFrom: []api.EnvFromSource{{ + ConfigMapRef: &api.ConfigMapEnvSource{ + LocalObjectReference: api.LocalObjectReference{ + Name: "Spec.Containers[*].EnvFrom[*].ConfigMapRef"}}}}, + Env: []api.EnvVar{{ + ValueFrom: &api.EnvVarSource{ + ConfigMapKeyRef: &api.ConfigMapKeySelector{ + LocalObjectReference: api.LocalObjectReference{ + Name: "Spec.Containers[*].Env[*].ValueFrom.ConfigMapKeyRef"}}}}}}}, + InitContainers: []api.Container{{ + EnvFrom: []api.EnvFromSource{{ + ConfigMapRef: &api.ConfigMapEnvSource{ + LocalObjectReference: api.LocalObjectReference{ + Name: "Spec.InitContainers[*].EnvFrom[*].ConfigMapRef"}}}}, + Env: []api.EnvVar{{ + ValueFrom: &api.EnvVarSource{ + ConfigMapKeyRef: &api.ConfigMapKeySelector{ + LocalObjectReference: api.LocalObjectReference{ + Name: "Spec.InitContainers[*].Env[*].ValueFrom.ConfigMapKeyRef"}}}}}}}, + Volumes: []api.Volume{{ + VolumeSource: api.VolumeSource{ + Projected: &api.ProjectedVolumeSource{ + Sources: []api.VolumeProjection{{ + ConfigMap: &api.ConfigMapProjection{ + LocalObjectReference: api.LocalObjectReference{ + Name: "Spec.Volumes[*].VolumeSource.Projected.Sources[*].ConfigMap"}}}}}}}, { + VolumeSource: api.VolumeSource{ + ConfigMap: &api.ConfigMapVolumeSource{ + LocalObjectReference: api.LocalObjectReference{ + Name: "Spec.Volumes[*].VolumeSource.ConfigMap"}}}}}, + }, + } + extractedNames := sets.NewString() + VisitPodConfigmapNames(pod, func(name string) bool { + extractedNames.Insert(name) + return true + }) + + // expectedPaths holds struct paths to fields with "ConfigMap" in the name that are references to ConfigMap API objects. + // every path here should be represented as an example in the Pod stub above, with the ConfigMap name set to the path. + expectedPaths := sets.NewString( + "Spec.Containers[*].EnvFrom[*].ConfigMapRef", + "Spec.Containers[*].Env[*].ValueFrom.ConfigMapKeyRef", + "Spec.InitContainers[*].EnvFrom[*].ConfigMapRef", + "Spec.InitContainers[*].Env[*].ValueFrom.ConfigMapKeyRef", + "Spec.Volumes[*].VolumeSource.Projected.Sources[*].ConfigMap", + "Spec.Volumes[*].VolumeSource.ConfigMap", + ) + collectPaths := collectResourcePaths(t, "ConfigMap", nil, "", reflect.TypeOf(&api.Pod{})) + if missingPaths := expectedPaths.Difference(collectPaths); len(missingPaths) > 0 { + t.Logf("Missing expected paths:\n%s", strings.Join(missingPaths.List(), "\n")) + t.Error("Missing expected paths. Verify VisitPodConfigmapNames() is correctly finding the missing paths, then correct expectedPaths") + } + if extraPaths := collectPaths.Difference(expectedPaths); len(extraPaths) > 0 { + t.Logf("Extra paths:\n%s", strings.Join(extraPaths.List(), "\n")) + t.Error("Extra fields with resource in the name found. Verify VisitPodConfigmapNames() is including these fields if appropriate, then correct expectedPaths") + } + + if missingNames := expectedPaths.Difference(extractedNames); len(missingNames) > 0 { + t.Logf("Missing expected names:\n%s", strings.Join(missingNames.List(), "\n")) + t.Error("Missing expected names. Verify the pod stub above includes these references, then verify VisitPodConfigmapNames() is correctly finding the missing names") + } + if extraNames := extractedNames.Difference(expectedPaths); len(extraNames) > 0 { + t.Logf("Extra names:\n%s", strings.Join(extraNames.List(), "\n")) + t.Error("Extra names extracted. Verify VisitPodConfigmapNames() is correctly extracting resource names") + } +} + +func TestDropAlphaVolumeDevices(t *testing.T) { + testPod := api.Pod{ + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyNever, + Containers: []api.Container{ + { + Name: "container1", + Image: "testimage", + VolumeDevices: []api.VolumeDevice{ + { + Name: "myvolume", + DevicePath: "/usr/test", + }, + }, + }, + }, + InitContainers: []api.Container{ + { + Name: "container1", + Image: "testimage", + VolumeDevices: []api.VolumeDevice{ + { + Name: "myvolume", + DevicePath: "/usr/test", + }, + }, + }, + }, + Volumes: []api.Volume{ + { + Name: "myvolume", + VolumeSource: api.VolumeSource{ + HostPath: &api.HostPathVolumeSource{ + Path: "/dev/xvdc", + }, + }, + }, + }, + }, + } + + // Enable alpha feature BlockVolume + err1 := utilfeature.DefaultFeatureGate.Set("BlockVolume=true") + if err1 != nil { + t.Fatalf("Failed to enable feature gate for BlockVolume: %v", err1) + } + + // now test dropping the fields - should not be dropped + DropDisabledAlphaFields(&testPod.Spec) + + // check to make sure VolumeDevices is still present + // if featureset is set to true + if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + if testPod.Spec.Containers[0].VolumeDevices == nil { + t.Error("VolumeDevices in Container should not have been dropped based on feature-gate") + } + if testPod.Spec.InitContainers[0].VolumeDevices == nil { + t.Error("VolumeDevices in Container should not have been dropped based on feature-gate") + } + } + + // Disable alpha feature BlockVolume + err := utilfeature.DefaultFeatureGate.Set("BlockVolume=false") + if err != nil { + t.Fatalf("Failed to disable feature gate for BlockVolume: %v", err) + } + + // now test dropping the fields + DropDisabledAlphaFields(&testPod.Spec) + + // check to make sure VolumeDevices is nil + // if featureset is set to false + if !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + if testPod.Spec.Containers[0].VolumeDevices != nil { + t.Error("DropDisabledAlphaFields for Containers failed") + } + if testPod.Spec.InitContainers[0].VolumeDevices != nil { + t.Error("DropDisabledAlphaFields for InitContainers failed") + } + } } diff --git a/pkg/api/ref/BUILD b/pkg/api/ref/BUILD index b891fb41c50..84bbbf74261 100644 --- a/pkg/api/ref/BUILD +++ b/pkg/api/ref/BUILD @@ -9,10 +9,11 @@ load( go_test( name = "go_default_test", srcs = ["ref_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/api/ref", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", @@ -24,7 +25,7 @@ go_library( srcs = ["ref.go"], importpath = "k8s.io/kubernetes/pkg/api/ref", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/pkg/api/ref/ref.go b/pkg/api/ref/ref.go index a60013a4479..d6576750ad1 100644 --- a/pkg/api/ref/ref.go +++ b/pkg/api/ref/ref.go @@ -25,7 +25,7 @@ import ( "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) var ( diff --git a/pkg/api/ref/ref_test.go b/pkg/api/ref/ref_test.go index 2c5be62e046..f6c63bb14c1 100644 --- a/pkg/api/ref/ref_test.go +++ b/pkg/api/ref/ref_test.go @@ -23,7 +23,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" ) type FakeAPIObject struct{} @@ -51,8 +52,8 @@ func TestGetReference(t *testing.T) { // when vendoring kube, if you don't force the set of registered versions (like make test does) // then you run into trouble because the types aren't registered in the scheme by anything. This does the // register manually to allow unit test execution - if _, _, err := api.Scheme.ObjectKinds(&api.Pod{}); err != nil { - api.AddToScheme(api.Scheme) + if _, _, err := legacyscheme.Scheme.ObjectKinds(&api.Pod{}); err != nil { + api.AddToScheme(legacyscheme.Scheme) } table := map[string]struct { @@ -135,7 +136,7 @@ func TestGetReference(t *testing.T) { } for name, item := range table { - ref, err := GetPartialReference(api.Scheme, item.obj, item.fieldPath) + ref, err := GetPartialReference(legacyscheme.Scheme, item.obj, item.fieldPath) if e, a := item.shouldErr, (err != nil); e != a { t.Errorf("%v: expected %v, got %v, err %v", name, e, a, err) continue diff --git a/pkg/api/register.go b/pkg/api/register.go deleted file mode 100644 index 39562e5a566..00000000000 --- a/pkg/api/register.go +++ /dev/null @@ -1,124 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package api - -import ( - "os" - - "k8s.io/apimachinery/pkg/apimachinery/announced" - "k8s.io/apimachinery/pkg/apimachinery/registered" - 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" -) - -// GroupFactoryRegistry is the APIGroupFactoryRegistry (overlaps a bit with Registry, see comments in package for details) -var GroupFactoryRegistry = make(announced.APIGroupFactoryRegistry) - -// Registry is an instance of an API registry. This is an interim step to start removing the idea of a global -// API registry. -var Registry = registered.NewOrDie(os.Getenv("KUBE_API_VERSIONS")) - -// Scheme is the default instance of runtime.Scheme to which types in the Kubernetes API are already registered. -// NOTE: If you are copying this file to start a new api group, STOP! Copy the -// extensions group instead. This Scheme is special and should appear ONLY in -// the api group, unless you really know what you're doing. -// TODO(lavalamp): make the above error impossible. -var Scheme = runtime.NewScheme() - -// Codecs provides access to encoding and decoding for the scheme -var Codecs = serializer.NewCodecFactory(Scheme) - -// GroupName is the group name use in this package -const GroupName = "" - -// SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal} - -// ParameterCodec handles versioning of objects that are converted to query parameters. -var ParameterCodec = runtime.NewParameterCodec(Scheme) - -// 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() -} - -var ( - SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) - AddToScheme = SchemeBuilder.AddToScheme -) - -func addKnownTypes(scheme *runtime.Scheme) error { - if err := scheme.AddIgnoredConversionType(&metav1.TypeMeta{}, &metav1.TypeMeta{}); err != nil { - return err - } - scheme.AddKnownTypes(SchemeGroupVersion, - &Pod{}, - &PodList{}, - &PodStatusResult{}, - &PodTemplate{}, - &PodTemplateList{}, - &ReplicationControllerList{}, - &ReplicationController{}, - &ServiceList{}, - &Service{}, - &ServiceProxyOptions{}, - &NodeList{}, - &Node{}, - &NodeConfigSource{}, - &NodeProxyOptions{}, - &Endpoints{}, - &EndpointsList{}, - &Binding{}, - &Event{}, - &EventList{}, - &List{}, - &LimitRange{}, - &LimitRangeList{}, - &ResourceQuota{}, - &ResourceQuotaList{}, - &Namespace{}, - &NamespaceList{}, - &ServiceAccount{}, - &ServiceAccountList{}, - &Secret{}, - &SecretList{}, - &PersistentVolume{}, - &PersistentVolumeList{}, - &PersistentVolumeClaim{}, - &PersistentVolumeClaimList{}, - &PodAttachOptions{}, - &PodLogOptions{}, - &PodExecOptions{}, - &PodPortForwardOptions{}, - &PodProxyOptions{}, - &ComponentStatus{}, - &ComponentStatusList{}, - &SerializedReference{}, - &RangeAllocation{}, - &ConfigMap{}, - &ConfigMapList{}, - ) - - return nil -} diff --git a/pkg/api/resource/BUILD b/pkg/api/resource/BUILD index d4bb2fac078..c27900c7a1e 100644 --- a/pkg/api/resource/BUILD +++ b/pkg/api/resource/BUILD @@ -11,7 +11,7 @@ go_library( srcs = ["helpers.go"], importpath = "k8s.io/kubernetes/pkg/api/resource", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", ], ) @@ -32,11 +32,10 @@ filegroup( go_test( name = "go_default_test", srcs = ["helpers_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/api/resource", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", ], ) diff --git a/pkg/api/resource/helpers.go b/pkg/api/resource/helpers.go index eecc26ed222..6947799e877 100644 --- a/pkg/api/resource/helpers.go +++ b/pkg/api/resource/helpers.go @@ -22,12 +22,12 @@ import ( "strconv" "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // PodRequestsAndLimits returns a dictionary of all defined resources summed up for all // containers of the pod. -func PodRequestsAndLimits(pod *api.Pod) (reqs map[api.ResourceName]resource.Quantity, limits map[api.ResourceName]resource.Quantity, err error) { +func PodRequestsAndLimits(pod *api.Pod) (reqs map[api.ResourceName]resource.Quantity, limits map[api.ResourceName]resource.Quantity) { reqs, limits = map[api.ResourceName]resource.Quantity{}, map[api.ResourceName]resource.Quantity{} for _, container := range pod.Spec.Containers { for name, quantity := range container.Resources.Requests { diff --git a/pkg/api/resource/helpers_test.go b/pkg/api/resource/helpers_test.go index cab00528709..b8917a58483 100644 --- a/pkg/api/resource/helpers_test.go +++ b/pkg/api/resource/helpers_test.go @@ -18,11 +18,9 @@ package resource import ( "testing" - "time" "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestResourceHelpers(t *testing.T) { @@ -62,21 +60,3 @@ func TestDefaultResourceHelpers(t *testing.T) { t.Errorf("expected %v, actual %v", resource.BinarySI, resourceList.Memory().Format) } } - -func newPod(now metav1.Time, ready bool, beforeSec int) *api.Pod { - conditionStatus := api.ConditionFalse - if ready { - conditionStatus = api.ConditionTrue - } - return &api.Pod{ - Status: api.PodStatus{ - Conditions: []api.PodCondition{ - { - Type: api.PodReady, - LastTransitionTime: metav1.NewTime(now.Time.Add(-1 * time.Duration(beforeSec) * time.Second)), - Status: conditionStatus, - }, - }, - }, - } -} diff --git a/pkg/api/service/BUILD b/pkg/api/service/BUILD index 236edbec5bc..b57c5872f04 100644 --- a/pkg/api/service/BUILD +++ b/pkg/api/service/BUILD @@ -11,7 +11,7 @@ go_library( srcs = ["util.go"], importpath = "k8s.io/kubernetes/pkg/api/service", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/util/net/sets:go_default_library", ], ) @@ -19,10 +19,10 @@ go_library( go_test( name = "go_default_test", srcs = ["util_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/api/service", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/util/net/sets:go_default_library", ], ) diff --git a/pkg/api/service/util.go b/pkg/api/service/util.go index 242aab77f1f..5de5f276574 100644 --- a/pkg/api/service/util.go +++ b/pkg/api/service/util.go @@ -18,10 +18,9 @@ package service import ( "fmt" - "strings" - - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" netsets "k8s.io/kubernetes/pkg/util/net/sets" + "strings" ) const ( diff --git a/pkg/api/service/util_test.go b/pkg/api/service/util_test.go index c3eb0d1fb9b..46790a170b4 100644 --- a/pkg/api/service/util_test.go +++ b/pkg/api/service/util_test.go @@ -20,7 +20,7 @@ import ( "strings" "testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" netsets "k8s.io/kubernetes/pkg/util/net/sets" ) diff --git a/pkg/api/testapi/BUILD b/pkg/api/testapi/BUILD index 34f0734d083..c1747a9236a 100644 --- a/pkg/api/testapi/BUILD +++ b/pkg/api/testapi/BUILD @@ -11,10 +11,7 @@ go_library( srcs = ["testapi.go"], importpath = "k8s.io/kubernetes/pkg/api/testapi", deps = [ - "//federation/apis/federation:go_default_library", - "//federation/apis/federation/install:go_default_library", - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/admission:go_default_library", "//pkg/apis/admission/install:go_default_library", "//pkg/apis/admissionregistration:go_default_library", @@ -31,6 +28,10 @@ go_library( "//pkg/apis/certificates:go_default_library", "//pkg/apis/certificates/install:go_default_library", "//pkg/apis/componentconfig/install:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/install:go_default_library", + "//pkg/apis/events:go_default_library", + "//pkg/apis/events/install:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/apis/extensions/install:go_default_library", "//pkg/apis/imagepolicy:go_default_library", @@ -57,8 +58,8 @@ go_library( go_test( name = "go_default_test", srcs = ["testapi_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/api/testapi", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/pkg/api/testapi/testapi.go b/pkg/api/testapi/testapi.go index 340a58a98db..c1232e91ce2 100644 --- a/pkg/api/testapi/testapi.go +++ b/pkg/api/testapi/testapi.go @@ -34,8 +34,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer/recognizer" - "k8s.io/kubernetes/federation/apis/federation" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/admission" "k8s.io/kubernetes/pkg/apis/admissionregistration" "k8s.io/kubernetes/pkg/apis/apps" @@ -43,6 +42,8 @@ import ( "k8s.io/kubernetes/pkg/apis/autoscaling" "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/certificates" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/events" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/imagepolicy" "k8s.io/kubernetes/pkg/apis/networking" @@ -52,8 +53,6 @@ import ( "k8s.io/kubernetes/pkg/apis/settings" "k8s.io/kubernetes/pkg/apis/storage" - _ "k8s.io/kubernetes/federation/apis/federation/install" - _ "k8s.io/kubernetes/pkg/api/install" _ "k8s.io/kubernetes/pkg/apis/admission/install" _ "k8s.io/kubernetes/pkg/apis/admissionregistration/install" _ "k8s.io/kubernetes/pkg/apis/apps/install" @@ -63,6 +62,8 @@ import ( _ "k8s.io/kubernetes/pkg/apis/batch/install" _ "k8s.io/kubernetes/pkg/apis/certificates/install" _ "k8s.io/kubernetes/pkg/apis/componentconfig/install" + _ "k8s.io/kubernetes/pkg/apis/core/install" + _ "k8s.io/kubernetes/pkg/apis/events/install" _ "k8s.io/kubernetes/pkg/apis/extensions/install" _ "k8s.io/kubernetes/pkg/apis/imagepolicy/install" _ "k8s.io/kubernetes/pkg/apis/networking/install" @@ -80,9 +81,9 @@ var ( Autoscaling TestGroup Batch TestGroup Extensions TestGroup + Events TestGroup Apps TestGroup Policy TestGroup - Federation TestGroup Rbac TestGroup Certificates TestGroup Scheduling TestGroup @@ -110,7 +111,7 @@ func init() { if err != nil { panic(err) } - serializer, ok = runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), mediaType) + serializer, ok = runtime.SerializerInfoForMediaType(legacyscheme.Codecs.SupportedMediaTypes(), mediaType) if !ok { panic(fmt.Sprintf("no serializer for %s", apiMediaType)) } @@ -122,7 +123,7 @@ func init() { if err != nil { panic(err) } - storageSerializer, ok = runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), mediaType) + storageSerializer, ok = runtime.SerializerInfoForMediaType(legacyscheme.Codecs.SupportedMediaTypes(), mediaType) if !ok { panic(fmt.Sprintf("no serializer for %s", storageMediaType)) } @@ -143,186 +144,186 @@ func init() { Groups[groupVersion.Group] = TestGroup{ externalGroupVersion: groupVersion, internalGroupVersion: internalGroupVersion, - internalTypes: api.Scheme.KnownTypes(internalGroupVersion), - externalTypes: api.Scheme.KnownTypes(groupVersion), + internalTypes: legacyscheme.Scheme.KnownTypes(internalGroupVersion), + externalTypes: legacyscheme.Scheme.KnownTypes(groupVersion), } } } if _, ok := Groups[api.GroupName]; !ok { - externalGroupVersion := schema.GroupVersion{Group: api.GroupName, Version: api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version} + externalGroupVersion := schema.GroupVersion{Group: api.GroupName, Version: legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.Version} Groups[api.GroupName] = TestGroup{ externalGroupVersion: externalGroupVersion, internalGroupVersion: api.SchemeGroupVersion, - internalTypes: api.Scheme.KnownTypes(api.SchemeGroupVersion), - externalTypes: api.Scheme.KnownTypes(externalGroupVersion), + internalTypes: legacyscheme.Scheme.KnownTypes(api.SchemeGroupVersion), + externalTypes: legacyscheme.Scheme.KnownTypes(externalGroupVersion), } } if _, ok := Groups[extensions.GroupName]; !ok { - externalGroupVersion := schema.GroupVersion{Group: extensions.GroupName, Version: api.Registry.GroupOrDie(extensions.GroupName).GroupVersion.Version} + externalGroupVersion := schema.GroupVersion{Group: extensions.GroupName, Version: legacyscheme.Registry.GroupOrDie(extensions.GroupName).GroupVersion.Version} Groups[extensions.GroupName] = TestGroup{ externalGroupVersion: externalGroupVersion, internalGroupVersion: extensions.SchemeGroupVersion, - internalTypes: api.Scheme.KnownTypes(extensions.SchemeGroupVersion), - externalTypes: api.Scheme.KnownTypes(externalGroupVersion), + internalTypes: legacyscheme.Scheme.KnownTypes(extensions.SchemeGroupVersion), + externalTypes: legacyscheme.Scheme.KnownTypes(externalGroupVersion), } } if _, ok := Groups[autoscaling.GroupName]; !ok { internalTypes := make(map[string]reflect.Type) - for k, t := range api.Scheme.KnownTypes(extensions.SchemeGroupVersion) { + for k, t := range legacyscheme.Scheme.KnownTypes(extensions.SchemeGroupVersion) { if k == "Scale" { continue } internalTypes[k] = t } - externalGroupVersion := schema.GroupVersion{Group: autoscaling.GroupName, Version: api.Registry.GroupOrDie(autoscaling.GroupName).GroupVersion.Version} + externalGroupVersion := schema.GroupVersion{Group: autoscaling.GroupName, Version: legacyscheme.Registry.GroupOrDie(autoscaling.GroupName).GroupVersion.Version} Groups[autoscaling.GroupName] = TestGroup{ externalGroupVersion: externalGroupVersion, internalGroupVersion: extensions.SchemeGroupVersion, internalTypes: internalTypes, - externalTypes: api.Scheme.KnownTypes(externalGroupVersion), + externalTypes: legacyscheme.Scheme.KnownTypes(externalGroupVersion), } } if _, ok := Groups[autoscaling.GroupName+"IntraGroup"]; !ok { internalTypes := make(map[string]reflect.Type) - for k, t := range api.Scheme.KnownTypes(extensions.SchemeGroupVersion) { + for k, t := range legacyscheme.Scheme.KnownTypes(extensions.SchemeGroupVersion) { if k == "Scale" { internalTypes[k] = t break } } - externalGroupVersion := schema.GroupVersion{Group: autoscaling.GroupName, Version: api.Registry.GroupOrDie(autoscaling.GroupName).GroupVersion.Version} + externalGroupVersion := schema.GroupVersion{Group: autoscaling.GroupName, Version: legacyscheme.Registry.GroupOrDie(autoscaling.GroupName).GroupVersion.Version} Groups[autoscaling.GroupName] = TestGroup{ externalGroupVersion: externalGroupVersion, internalGroupVersion: autoscaling.SchemeGroupVersion, internalTypes: internalTypes, - externalTypes: api.Scheme.KnownTypes(externalGroupVersion), + externalTypes: legacyscheme.Scheme.KnownTypes(externalGroupVersion), } } if _, ok := Groups[batch.GroupName]; !ok { - externalGroupVersion := schema.GroupVersion{Group: batch.GroupName, Version: api.Registry.GroupOrDie(batch.GroupName).GroupVersion.Version} + externalGroupVersion := schema.GroupVersion{Group: batch.GroupName, Version: legacyscheme.Registry.GroupOrDie(batch.GroupName).GroupVersion.Version} Groups[batch.GroupName] = TestGroup{ externalGroupVersion: externalGroupVersion, internalGroupVersion: batch.SchemeGroupVersion, - internalTypes: api.Scheme.KnownTypes(batch.SchemeGroupVersion), - externalTypes: api.Scheme.KnownTypes(externalGroupVersion), + internalTypes: legacyscheme.Scheme.KnownTypes(batch.SchemeGroupVersion), + externalTypes: legacyscheme.Scheme.KnownTypes(externalGroupVersion), } } if _, ok := Groups[apps.GroupName]; !ok { - externalGroupVersion := schema.GroupVersion{Group: apps.GroupName, Version: api.Registry.GroupOrDie(apps.GroupName).GroupVersion.Version} + externalGroupVersion := schema.GroupVersion{Group: apps.GroupName, Version: legacyscheme.Registry.GroupOrDie(apps.GroupName).GroupVersion.Version} Groups[apps.GroupName] = TestGroup{ externalGroupVersion: externalGroupVersion, internalGroupVersion: apps.SchemeGroupVersion, - internalTypes: api.Scheme.KnownTypes(apps.SchemeGroupVersion), - externalTypes: api.Scheme.KnownTypes(externalGroupVersion), + internalTypes: legacyscheme.Scheme.KnownTypes(apps.SchemeGroupVersion), + externalTypes: legacyscheme.Scheme.KnownTypes(externalGroupVersion), } } if _, ok := Groups[policy.GroupName]; !ok { - externalGroupVersion := schema.GroupVersion{Group: policy.GroupName, Version: api.Registry.GroupOrDie(policy.GroupName).GroupVersion.Version} + externalGroupVersion := schema.GroupVersion{Group: policy.GroupName, Version: legacyscheme.Registry.GroupOrDie(policy.GroupName).GroupVersion.Version} Groups[policy.GroupName] = TestGroup{ externalGroupVersion: externalGroupVersion, internalGroupVersion: policy.SchemeGroupVersion, - internalTypes: api.Scheme.KnownTypes(policy.SchemeGroupVersion), - externalTypes: api.Scheme.KnownTypes(externalGroupVersion), - } - } - if _, ok := Groups[federation.GroupName]; !ok { - externalGroupVersion := schema.GroupVersion{Group: federation.GroupName, Version: api.Registry.GroupOrDie(federation.GroupName).GroupVersion.Version} - Groups[federation.GroupName] = TestGroup{ - externalGroupVersion: externalGroupVersion, - internalGroupVersion: federation.SchemeGroupVersion, - internalTypes: api.Scheme.KnownTypes(federation.SchemeGroupVersion), - externalTypes: api.Scheme.KnownTypes(externalGroupVersion), + internalTypes: legacyscheme.Scheme.KnownTypes(policy.SchemeGroupVersion), + externalTypes: legacyscheme.Scheme.KnownTypes(externalGroupVersion), } } if _, ok := Groups[rbac.GroupName]; !ok { - externalGroupVersion := schema.GroupVersion{Group: rbac.GroupName, Version: api.Registry.GroupOrDie(rbac.GroupName).GroupVersion.Version} + externalGroupVersion := schema.GroupVersion{Group: rbac.GroupName, Version: legacyscheme.Registry.GroupOrDie(rbac.GroupName).GroupVersion.Version} Groups[rbac.GroupName] = TestGroup{ externalGroupVersion: externalGroupVersion, internalGroupVersion: rbac.SchemeGroupVersion, - internalTypes: api.Scheme.KnownTypes(rbac.SchemeGroupVersion), - externalTypes: api.Scheme.KnownTypes(externalGroupVersion), + internalTypes: legacyscheme.Scheme.KnownTypes(rbac.SchemeGroupVersion), + externalTypes: legacyscheme.Scheme.KnownTypes(externalGroupVersion), } } if _, ok := Groups[scheduling.GroupName]; !ok { - externalGroupVersion := schema.GroupVersion{Group: scheduling.GroupName, Version: api.Registry.GroupOrDie(scheduling.GroupName).GroupVersion.Version} + externalGroupVersion := schema.GroupVersion{Group: scheduling.GroupName, Version: legacyscheme.Registry.GroupOrDie(scheduling.GroupName).GroupVersion.Version} Groups[scheduling.GroupName] = TestGroup{ externalGroupVersion: externalGroupVersion, internalGroupVersion: scheduling.SchemeGroupVersion, - internalTypes: api.Scheme.KnownTypes(scheduling.SchemeGroupVersion), - externalTypes: api.Scheme.KnownTypes(externalGroupVersion), + internalTypes: legacyscheme.Scheme.KnownTypes(scheduling.SchemeGroupVersion), + externalTypes: legacyscheme.Scheme.KnownTypes(externalGroupVersion), } } if _, ok := Groups[settings.GroupName]; !ok { - externalGroupVersion := schema.GroupVersion{Group: settings.GroupName, Version: api.Registry.GroupOrDie(settings.GroupName).GroupVersion.Version} + externalGroupVersion := schema.GroupVersion{Group: settings.GroupName, Version: legacyscheme.Registry.GroupOrDie(settings.GroupName).GroupVersion.Version} Groups[settings.GroupName] = TestGroup{ externalGroupVersion: externalGroupVersion, internalGroupVersion: settings.SchemeGroupVersion, - internalTypes: api.Scheme.KnownTypes(settings.SchemeGroupVersion), - externalTypes: api.Scheme.KnownTypes(externalGroupVersion), + internalTypes: legacyscheme.Scheme.KnownTypes(settings.SchemeGroupVersion), + externalTypes: legacyscheme.Scheme.KnownTypes(externalGroupVersion), } } if _, ok := Groups[storage.GroupName]; !ok { - externalGroupVersion := schema.GroupVersion{Group: storage.GroupName, Version: api.Registry.GroupOrDie(storage.GroupName).GroupVersion.Version} + externalGroupVersion := schema.GroupVersion{Group: storage.GroupName, Version: legacyscheme.Registry.GroupOrDie(storage.GroupName).GroupVersion.Version} Groups[storage.GroupName] = TestGroup{ externalGroupVersion: externalGroupVersion, internalGroupVersion: storage.SchemeGroupVersion, - internalTypes: api.Scheme.KnownTypes(storage.SchemeGroupVersion), - externalTypes: api.Scheme.KnownTypes(externalGroupVersion), + internalTypes: legacyscheme.Scheme.KnownTypes(storage.SchemeGroupVersion), + externalTypes: legacyscheme.Scheme.KnownTypes(externalGroupVersion), } } if _, ok := Groups[certificates.GroupName]; !ok { - externalGroupVersion := schema.GroupVersion{Group: certificates.GroupName, Version: api.Registry.GroupOrDie(certificates.GroupName).GroupVersion.Version} + externalGroupVersion := schema.GroupVersion{Group: certificates.GroupName, Version: legacyscheme.Registry.GroupOrDie(certificates.GroupName).GroupVersion.Version} Groups[certificates.GroupName] = TestGroup{ externalGroupVersion: externalGroupVersion, internalGroupVersion: certificates.SchemeGroupVersion, - internalTypes: api.Scheme.KnownTypes(certificates.SchemeGroupVersion), - externalTypes: api.Scheme.KnownTypes(externalGroupVersion), + internalTypes: legacyscheme.Scheme.KnownTypes(certificates.SchemeGroupVersion), + externalTypes: legacyscheme.Scheme.KnownTypes(externalGroupVersion), } } if _, ok := Groups[imagepolicy.GroupName]; !ok { - externalGroupVersion := schema.GroupVersion{Group: imagepolicy.GroupName, Version: api.Registry.GroupOrDie(imagepolicy.GroupName).GroupVersion.Version} + externalGroupVersion := schema.GroupVersion{Group: imagepolicy.GroupName, Version: legacyscheme.Registry.GroupOrDie(imagepolicy.GroupName).GroupVersion.Version} Groups[imagepolicy.GroupName] = TestGroup{ externalGroupVersion: externalGroupVersion, internalGroupVersion: imagepolicy.SchemeGroupVersion, - internalTypes: api.Scheme.KnownTypes(imagepolicy.SchemeGroupVersion), - externalTypes: api.Scheme.KnownTypes(externalGroupVersion), + internalTypes: legacyscheme.Scheme.KnownTypes(imagepolicy.SchemeGroupVersion), + externalTypes: legacyscheme.Scheme.KnownTypes(externalGroupVersion), } } if _, ok := Groups[authorization.GroupName]; !ok { - externalGroupVersion := schema.GroupVersion{Group: authorization.GroupName, Version: api.Registry.GroupOrDie(authorization.GroupName).GroupVersion.Version} + externalGroupVersion := schema.GroupVersion{Group: authorization.GroupName, Version: legacyscheme.Registry.GroupOrDie(authorization.GroupName).GroupVersion.Version} Groups[authorization.GroupName] = TestGroup{ externalGroupVersion: externalGroupVersion, internalGroupVersion: authorization.SchemeGroupVersion, - internalTypes: api.Scheme.KnownTypes(authorization.SchemeGroupVersion), - externalTypes: api.Scheme.KnownTypes(externalGroupVersion), + internalTypes: legacyscheme.Scheme.KnownTypes(authorization.SchemeGroupVersion), + externalTypes: legacyscheme.Scheme.KnownTypes(externalGroupVersion), } } if _, ok := Groups[admissionregistration.GroupName]; !ok { - externalGroupVersion := schema.GroupVersion{Group: admissionregistration.GroupName, Version: api.Registry.GroupOrDie(admissionregistration.GroupName).GroupVersion.Version} + externalGroupVersion := schema.GroupVersion{Group: admissionregistration.GroupName, Version: legacyscheme.Registry.GroupOrDie(admissionregistration.GroupName).GroupVersion.Version} Groups[admissionregistration.GroupName] = TestGroup{ externalGroupVersion: externalGroupVersion, internalGroupVersion: admissionregistration.SchemeGroupVersion, - internalTypes: api.Scheme.KnownTypes(admissionregistration.SchemeGroupVersion), - externalTypes: api.Scheme.KnownTypes(externalGroupVersion), + internalTypes: legacyscheme.Scheme.KnownTypes(admissionregistration.SchemeGroupVersion), + externalTypes: legacyscheme.Scheme.KnownTypes(externalGroupVersion), } } if _, ok := Groups[admission.GroupName]; !ok { - externalGroupVersion := schema.GroupVersion{Group: admission.GroupName, Version: api.Registry.GroupOrDie(admission.GroupName).GroupVersion.Version} + externalGroupVersion := schema.GroupVersion{Group: admission.GroupName, Version: legacyscheme.Registry.GroupOrDie(admission.GroupName).GroupVersion.Version} Groups[admission.GroupName] = TestGroup{ externalGroupVersion: externalGroupVersion, internalGroupVersion: admission.SchemeGroupVersion, - internalTypes: api.Scheme.KnownTypes(admission.SchemeGroupVersion), - externalTypes: api.Scheme.KnownTypes(externalGroupVersion), + internalTypes: legacyscheme.Scheme.KnownTypes(admission.SchemeGroupVersion), + externalTypes: legacyscheme.Scheme.KnownTypes(externalGroupVersion), } } if _, ok := Groups[networking.GroupName]; !ok { - externalGroupVersion := schema.GroupVersion{Group: networking.GroupName, Version: api.Registry.GroupOrDie(networking.GroupName).GroupVersion.Version} + externalGroupVersion := schema.GroupVersion{Group: networking.GroupName, Version: legacyscheme.Registry.GroupOrDie(networking.GroupName).GroupVersion.Version} Groups[networking.GroupName] = TestGroup{ externalGroupVersion: externalGroupVersion, internalGroupVersion: networking.SchemeGroupVersion, - internalTypes: api.Scheme.KnownTypes(networking.SchemeGroupVersion), - externalTypes: api.Scheme.KnownTypes(externalGroupVersion), + internalTypes: legacyscheme.Scheme.KnownTypes(networking.SchemeGroupVersion), + externalTypes: legacyscheme.Scheme.KnownTypes(externalGroupVersion), + } + } + if _, ok := Groups[events.GroupName]; !ok { + externalGroupVersion := schema.GroupVersion{Group: events.GroupName, Version: legacyscheme.Registry.GroupOrDie(events.GroupName).GroupVersion.Version} + Groups[events.GroupName] = TestGroup{ + externalGroupVersion: externalGroupVersion, + internalGroupVersion: events.SchemeGroupVersion, + internalTypes: legacyscheme.Scheme.KnownTypes(events.SchemeGroupVersion), + externalTypes: legacyscheme.Scheme.KnownTypes(externalGroupVersion), } } @@ -333,7 +334,7 @@ func init() { Policy = Groups[policy.GroupName] Certificates = Groups[certificates.GroupName] Extensions = Groups[extensions.GroupName] - Federation = Groups[federation.GroupName] + Events = Groups[events.GroupName] Rbac = Groups[rbac.GroupName] Scheduling = Groups[scheduling.GroupName] Settings = Groups[settings.GroupName] @@ -373,14 +374,14 @@ func (g TestGroup) ExternalTypes() map[string]reflect.Type { // KUBE_TEST_API_TYPE env var. func (g TestGroup) Codec() runtime.Codec { if serializer.Serializer == nil { - return api.Codecs.LegacyCodec(g.externalGroupVersion) + return legacyscheme.Codecs.LegacyCodec(g.externalGroupVersion) } - return api.Codecs.CodecForVersions(serializer.Serializer, api.Codecs.UniversalDeserializer(), schema.GroupVersions{g.externalGroupVersion}, nil) + return legacyscheme.Codecs.CodecForVersions(serializer.Serializer, legacyscheme.Codecs.UniversalDeserializer(), schema.GroupVersions{g.externalGroupVersion}, nil) } // NegotiatedSerializer returns the negotiated serializer for the server. func (g TestGroup) NegotiatedSerializer() runtime.NegotiatedSerializer { - return api.Codecs + return legacyscheme.Codecs } func StorageMediaType() string { @@ -393,7 +394,7 @@ func (g TestGroup) StorageCodec() runtime.Codec { s := storageSerializer.Serializer if s == nil { - return api.Codecs.LegacyCodec(g.externalGroupVersion) + return legacyscheme.Codecs.LegacyCodec(g.externalGroupVersion) } // etcd2 only supports string data - we must wrap any result before returning @@ -401,15 +402,15 @@ func (g TestGroup) StorageCodec() runtime.Codec { if !storageSerializer.EncodesAsText { s = runtime.NewBase64Serializer(s, s) } - ds := recognizer.NewDecoder(s, api.Codecs.UniversalDeserializer()) + ds := recognizer.NewDecoder(s, legacyscheme.Codecs.UniversalDeserializer()) - return api.Codecs.CodecForVersions(s, ds, schema.GroupVersions{g.externalGroupVersion}, nil) + return legacyscheme.Codecs.CodecForVersions(s, ds, schema.GroupVersions{g.externalGroupVersion}, nil) } -// Converter returns the api.Scheme for the API version to test against, as set by the +// Converter returns the legacyscheme.Scheme for the API version to test against, as set by the // KUBE_TEST_API env var. func (g TestGroup) Converter() runtime.ObjectConvertor { - interfaces, err := api.Registry.GroupOrDie(g.externalGroupVersion.Group).InterfacesFor(g.externalGroupVersion) + interfaces, err := legacyscheme.Registry.GroupOrDie(g.externalGroupVersion.Group).InterfacesFor(g.externalGroupVersion) if err != nil { panic(err) } @@ -419,7 +420,7 @@ func (g TestGroup) Converter() runtime.ObjectConvertor { // MetadataAccessor returns the MetadataAccessor for the API version to test against, // as set by the KUBE_TEST_API env var. func (g TestGroup) MetadataAccessor() meta.MetadataAccessor { - interfaces, err := api.Registry.GroupOrDie(g.externalGroupVersion.Group).InterfacesFor(g.externalGroupVersion) + interfaces, err := legacyscheme.Registry.GroupOrDie(g.externalGroupVersion.Group).InterfacesFor(g.externalGroupVersion) if err != nil { panic(err) } @@ -493,9 +494,9 @@ func (g TestGroup) SubResourcePath(resource, namespace, name, sub string) string return path } -// RESTMapper returns RESTMapper in api.Registry. +// RESTMapper returns RESTMapper in legacyscheme.Registry. func (g TestGroup) RESTMapper() meta.RESTMapper { - return api.Registry.RESTMapper() + return legacyscheme.Registry.RESTMapper() } // ExternalGroupVersions returns all external group versions allowed for the server. @@ -510,7 +511,7 @@ func ExternalGroupVersions() schema.GroupVersions { // GetCodecForObject gets codec based on runtime.Object func GetCodecForObject(obj runtime.Object) (runtime.Codec, error) { - kinds, _, err := api.Scheme.ObjectKinds(obj) + kinds, _, err := legacyscheme.Scheme.ObjectKinds(obj) if err != nil { return nil, fmt.Errorf("unexpected encoding error: %v", err) } @@ -521,13 +522,13 @@ func GetCodecForObject(obj runtime.Object) (runtime.Codec, error) { continue } - if api.Scheme.Recognizes(kind) { + if legacyscheme.Scheme.Recognizes(kind) { return group.Codec(), nil } } // Codec used for unversioned types - if api.Scheme.Recognizes(kind) { - serializer, ok := runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), runtime.ContentTypeJSON) + if legacyscheme.Scheme.Recognizes(kind) { + serializer, ok := runtime.SerializerInfoForMediaType(legacyscheme.Codecs.SupportedMediaTypes(), runtime.ContentTypeJSON) if !ok { return nil, fmt.Errorf("no serializer registered for json") } diff --git a/pkg/api/testapi/testapi_test.go b/pkg/api/testapi/testapi_test.go index b7081f2817d..f90cb812c66 100644 --- a/pkg/api/testapi/testapi_test.go +++ b/pkg/api/testapi/testapi_test.go @@ -25,8 +25,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) -// TODO these tests don't add much value for testing things that have groups - func TestResourcePathWithPrefix(t *testing.T) { testCases := []struct { prefix string @@ -46,6 +44,25 @@ func TestResourcePathWithPrefix(t *testing.T) { t.Errorf("Expected: %s, got: %s for prefix: %s, resource: %s, namespace: %s and name: %s", item.expected, actual, item.prefix, item.resource, item.namespace, item.name) } } + + testGroupCases := []struct { + prefix string + resource string + namespace string + name string + expected string + }{ + {"prefix", "resource", "mynamespace", "myresource", "/apis/" + Admission.GroupVersion().Group + "/" + Admission.GroupVersion().Version + "/prefix/namespaces/mynamespace/resource/myresource"}, + {"prefix", "resource", "", "myresource", "/apis/" + Admission.GroupVersion().Group + "/" + Admission.GroupVersion().Version + "/prefix/resource/myresource"}, + {"prefix", "resource", "mynamespace", "", "/apis/" + Admission.GroupVersion().Group + "/" + Admission.GroupVersion().Version + "/prefix/namespaces/mynamespace/resource"}, + {"prefix", "resource", "", "", "/apis/" + Admission.GroupVersion().Group + "/" + Admission.GroupVersion().Version + "/prefix/resource"}, + {"", "resource", "mynamespace", "myresource", "/apis/" + Admission.GroupVersion().Group + "/" + Admission.GroupVersion().Version + "/namespaces/mynamespace/resource/myresource"}, + } + for _, item := range testGroupCases { + if actual := Admission.ResourcePathWithPrefix(item.prefix, item.resource, item.namespace, item.name); actual != item.expected { + t.Errorf("Expected: %s, got: %s for prefix: %s, resource: %s, namespace: %s and name: %s", item.expected, actual, item.prefix, item.resource, item.namespace, item.name) + } + } } func TestResourcePath(t *testing.T) { @@ -67,6 +84,26 @@ func TestResourcePath(t *testing.T) { } } +func TestSubResourcePath(t *testing.T) { + testCases := []struct { + resource string + namespace string + name string + sub string + expected string + }{ + {"resource", "mynamespace", "myresource", "mysub", "/api/" + Default.GroupVersion().Version + "/namespaces/mynamespace/resource/myresource/mysub"}, + {"resource", "", "myresource", "mysub", "/api/" + Default.GroupVersion().Version + "/resource/myresource/mysub"}, + {"resource", "mynamespace", "", "mysub", "/api/" + Default.GroupVersion().Version + "/namespaces/mynamespace/resource/mysub"}, + {"resource", "", "", "mysub", "/api/" + Default.GroupVersion().Version + "/resource/mysub"}, + } + for _, item := range testCases { + if actual := Default.SubResourcePath(item.resource, item.namespace, item.name, item.sub); actual != item.expected { + t.Errorf("Expected: %s, got: %s for resource: %s, namespace: %s and name: %s", item.expected, actual, item.resource, item.namespace, item.name) + } + } +} + var status = &metav1.Status{ Status: metav1.StatusFailure, Code: 200, @@ -74,6 +111,36 @@ var status = &metav1.Status{ Message: "", } +func TestSelfLink(t *testing.T) { + testCases := []struct { + resource string + name string + expected string + }{ + {"resource", "name", "/api/" + Default.GroupVersion().Version + "/resource/name"}, + {"resource", "", "/api/" + Default.GroupVersion().Version + "/resource"}, + } + for _, item := range testCases { + if actual := Default.SelfLink(item.resource, item.name); actual != item.expected { + t.Errorf("Expected: %s, got: %s for resource: %s and name: %s", item.expected, actual, item.resource, item.name) + } + } + + testGroupCases := []struct { + resource string + name string + expected string + }{ + {"resource", "name", "/apis/" + Admission.GroupVersion().Group + "/" + Admission.GroupVersion().Version + "/resource/name"}, + {"resource", "", "/apis/" + Admission.GroupVersion().Group + "/" + Admission.GroupVersion().Version + "/resource"}, + } + for _, item := range testGroupCases { + if actual := Admission.SelfLink(item.resource, item.name); actual != item.expected { + t.Errorf("Expected: %s, got: %s for resource: %s and name: %s", item.expected, actual, item.resource, item.name) + } + } +} + func TestV1EncodeDecodeStatus(t *testing.T) { v1Codec := Default.Codec() diff --git a/pkg/api/testing/BUILD b/pkg/api/testing/BUILD index f0146ea6564..aae6f1e6f64 100644 --- a/pkg/api/testing/BUILD +++ b/pkg/api/testing/BUILD @@ -16,14 +16,14 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/api/testing", deps = [ - "//cmd/kubeadm/app/apis/kubeadm/fuzzer:go_default_library", - "//pkg/api:go_default_library", - "//pkg/api/fuzzer:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/admissionregistration/fuzzer:go_default_library", "//pkg/apis/apps/fuzzer:go_default_library", "//pkg/apis/autoscaling/fuzzer:go_default_library", "//pkg/apis/batch/fuzzer:go_default_library", "//pkg/apis/certificates/fuzzer:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/fuzzer:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/apis/extensions/fuzzer:go_default_library", "//pkg/apis/extensions/v1beta1:go_default_library", @@ -71,19 +71,19 @@ go_test( "serialization_test.go", "unstructured_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/api/testing", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/testapi:go_default_library", "//pkg/api/testing/compat:go_default_library", - "//pkg/api/v1:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/install:go_default_library", + "//pkg/apis/core/v1:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/apis/extensions/v1beta1:go_default_library", "//vendor/github.com/gogo/protobuf/proto:go_default_library", - "//vendor/github.com/golang/protobuf/proto:go_default_library", "//vendor/github.com/google/gofuzz:go_default_library", "//vendor/github.com/json-iterator/go:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", @@ -93,8 +93,8 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/api/testing/fuzzer:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/testing/roundtrip:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion/unstructured:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer/protobuf:go_default_library", diff --git a/pkg/api/testing/backward_compatibility_test.go b/pkg/api/testing/backward_compatibility_test.go index 82ac3ca81ae..ad536de5adc 100644 --- a/pkg/api/testing/backward_compatibility_test.go +++ b/pkg/api/testing/backward_compatibility_test.go @@ -22,11 +22,11 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testing/compat" - "k8s.io/kubernetes/pkg/api/validation" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/validation" - _ "k8s.io/kubernetes/pkg/api/install" + _ "k8s.io/kubernetes/pkg/apis/core/install" ) func TestCompatibility_v1_PodSecurityContext(t *testing.T) { diff --git a/pkg/api/testing/compat/BUILD b/pkg/api/testing/compat/BUILD index ee936913976..b08e25c8df7 100644 --- a/pkg/api/testing/compat/BUILD +++ b/pkg/api/testing/compat/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["compatibility_tester.go"], importpath = "k8s.io/kubernetes/pkg/api/testing/compat", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/printers:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", diff --git a/pkg/api/testing/compat/compatibility_tester.go b/pkg/api/testing/compat/compatibility_tester.go index 953eda803e4..5fbdc01e301 100644 --- a/pkg/api/testing/compat/compatibility_tester.go +++ b/pkg/api/testing/compat/compatibility_tester.go @@ -29,7 +29,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/printers" ) @@ -48,7 +48,7 @@ func TestCompatibility( ) { // Decode - codec := api.Codecs.LegacyCodec(version) + codec := legacyscheme.Codecs.LegacyCodec(version) obj, err := runtime.Decode(codec, input) if err != nil { t.Fatalf("Unexpected error: %v", err) diff --git a/pkg/api/testing/conversion.go b/pkg/api/testing/conversion.go index e0609410c1f..5ff3e9d7605 100644 --- a/pkg/api/testing/conversion.go +++ b/pkg/api/testing/conversion.go @@ -20,7 +20,7 @@ import ( "testing" "k8s.io/apimachinery/pkg/fields" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" ) // TestSelectableFieldLabelConversions verifies that given resource have field @@ -46,7 +46,7 @@ func TestSelectableFieldLabelConversionsOfKind(t *testing.T, apiVersion string, t.Logf("FIXME: \"name\" is deprecated by \"metadata.name\", it should be removed from selectable fields of kind=%s", kind) continue } - newLabel, newValue, err := api.Scheme.ConvertFieldLabel(apiVersion, kind, label, value) + newLabel, newValue, err := legacyscheme.Scheme.ConvertFieldLabel(apiVersion, kind, label, value) if err != nil { t.Errorf("kind=%s label=%s: got unexpected error: %v", kind, label, err) } else { @@ -64,7 +64,7 @@ func TestSelectableFieldLabelConversionsOfKind(t *testing.T, apiVersion string, } for _, label := range badFieldLabels { - _, _, err := api.Scheme.ConvertFieldLabel(apiVersion, kind, label, "value") + _, _, err := legacyscheme.Scheme.ConvertFieldLabel(apiVersion, kind, label, "value") if err == nil { t.Errorf("kind=%s label=%s: got unexpected non-error", kind, label) } diff --git a/pkg/api/testing/conversion_test.go b/pkg/api/testing/conversion_test.go index a594431ecad..3cb174fcd86 100644 --- a/pkg/api/testing/conversion_test.go +++ b/pkg/api/testing/conversion_test.go @@ -25,12 +25,13 @@ import ( "k8s.io/apimachinery/pkg/api/testing/fuzzer" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/diff" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/testapi" + api "k8s.io/kubernetes/pkg/apis/core" ) func BenchmarkPodConversion(b *testing.B) { - apiObjectFuzzer := fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(benchmarkSeed), api.Codecs) + apiObjectFuzzer := fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(benchmarkSeed), legacyscheme.Codecs) items := make([]api.Pod, 4) for i := range items { apiObjectFuzzer.Fuzz(&items[i]) @@ -42,10 +43,10 @@ func BenchmarkPodConversion(b *testing.B) { items = append(items, benchmarkPod) width := len(items) - scheme := api.Scheme + scheme := legacyscheme.Scheme for i := 0; i < b.N; i++ { pod := &items[i%width] - versionedObj, err := scheme.UnsafeConvertToVersion(pod, api.Registry.GroupOrDie(api.GroupName).GroupVersion) + versionedObj, err := scheme.UnsafeConvertToVersion(pod, legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion) if err != nil { b.Fatalf("Conversion error: %v", err) } @@ -65,11 +66,11 @@ func BenchmarkNodeConversion(b *testing.B) { b.Fatalf("Unexpected error decoding node: %v", err) } - scheme := api.Scheme + scheme := legacyscheme.Scheme var result *api.Node b.ResetTimer() for i := 0; i < b.N; i++ { - versionedObj, err := scheme.UnsafeConvertToVersion(&node, api.Registry.GroupOrDie(api.GroupName).GroupVersion) + versionedObj, err := scheme.UnsafeConvertToVersion(&node, legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion) if err != nil { b.Fatalf("Conversion error: %v", err) } @@ -95,11 +96,11 @@ func BenchmarkReplicationControllerConversion(b *testing.B) { b.Fatalf("Unexpected error decoding node: %v", err) } - scheme := api.Scheme + scheme := legacyscheme.Scheme var result *api.ReplicationController b.ResetTimer() for i := 0; i < b.N; i++ { - versionedObj, err := scheme.UnsafeConvertToVersion(&replicationController, api.Registry.GroupOrDie(api.GroupName).GroupVersion) + versionedObj, err := scheme.UnsafeConvertToVersion(&replicationController, legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion) if err != nil { b.Fatalf("Conversion error: %v", err) } diff --git a/pkg/api/testing/copy_test.go b/pkg/api/testing/copy_test.go index e1002706dc1..103246bb306 100644 --- a/pkg/api/testing/copy_test.go +++ b/pkg/api/testing/copy_test.go @@ -28,15 +28,16 @@ import ( "k8s.io/apimachinery/pkg/api/testing/roundtrip" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/diff" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/testapi" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestDeepCopyApiObjects(t *testing.T) { for i := 0; i < *roundtrip.FuzzIters; i++ { - for _, version := range []schema.GroupVersion{testapi.Default.InternalGroupVersion(), api.Registry.GroupOrDie(api.GroupName).GroupVersion} { - f := fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(rand.Int63()), api.Codecs) - for kind := range api.Scheme.KnownTypes(version) { + for _, version := range []schema.GroupVersion{testapi.Default.InternalGroupVersion(), legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion} { + f := fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(rand.Int63()), legacyscheme.Codecs) + for kind := range legacyscheme.Scheme.KnownTypes(version) { doDeepCopyTest(t, version.WithKind(kind), f) } } @@ -44,7 +45,7 @@ func TestDeepCopyApiObjects(t *testing.T) { } func doDeepCopyTest(t *testing.T, kind schema.GroupVersionKind, f *fuzz.Fuzzer) { - item, err := api.Scheme.New(kind) + item, err := legacyscheme.Scheme.New(kind) if err != nil { t.Fatalf("Could not create a %v: %s", kind, err) } @@ -55,7 +56,7 @@ func doDeepCopyTest(t *testing.T, kind schema.GroupVersionKind, f *fuzz.Fuzzer) } prefuzzData := &bytes.Buffer{} - if err := api.Codecs.LegacyCodec(kind.GroupVersion()).Encode(item, prefuzzData); err != nil { + if err := legacyscheme.Codecs.LegacyCodec(kind.GroupVersion()).Encode(item, prefuzzData); err != nil { t.Errorf("Could not encode a %v: %s", kind, err) return } @@ -64,7 +65,7 @@ func doDeepCopyTest(t *testing.T, kind schema.GroupVersionKind, f *fuzz.Fuzzer) f.Fuzz(itemCopy) postfuzzData := &bytes.Buffer{} - if err := api.Codecs.LegacyCodec(kind.GroupVersion()).Encode(item, postfuzzData); err != nil { + if err := legacyscheme.Codecs.LegacyCodec(kind.GroupVersion()).Encode(item, postfuzzData); err != nil { t.Errorf("Could not encode a %v: %s", kind, err) return } @@ -78,8 +79,8 @@ func doDeepCopyTest(t *testing.T, kind schema.GroupVersionKind, f *fuzz.Fuzzer) func TestDeepCopySingleType(t *testing.T) { for i := 0; i < *roundtrip.FuzzIters; i++ { - for _, version := range []schema.GroupVersion{testapi.Default.InternalGroupVersion(), api.Registry.GroupOrDie(api.GroupName).GroupVersion} { - f := fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(rand.Int63()), api.Codecs) + for _, version := range []schema.GroupVersion{testapi.Default.InternalGroupVersion(), legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion} { + f := fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(rand.Int63()), legacyscheme.Codecs) doDeepCopyTest(t, version.WithKind("Pod"), f) } } diff --git a/pkg/api/testing/deep_copy_test.go b/pkg/api/testing/deep_copy_test.go index a9580235e6e..27c9f42f6aa 100644 --- a/pkg/api/testing/deep_copy_test.go +++ b/pkg/api/testing/deep_copy_test.go @@ -25,8 +25,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" + api "k8s.io/kubernetes/pkg/apis/core" ) func parseTimeOrDie(ts string) metav1.Time { diff --git a/pkg/api/testing/defaulting_test.go b/pkg/api/testing/defaulting_test.go index a54d49a21fc..25ccdbaeca3 100644 --- a/pkg/api/testing/defaulting_test.go +++ b/pkg/api/testing/defaulting_test.go @@ -31,7 +31,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/diff" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" ) type orderedGroupVersionKinds []schema.GroupVersionKind @@ -58,8 +58,6 @@ func TestDefaulting(t *testing.T) { {Group: "", Version: "v1", Kind: "PersistentVolumeList"}: {}, {Group: "", Version: "v1", Kind: "PersistentVolumeClaim"}: {}, {Group: "", Version: "v1", Kind: "PersistentVolumeClaimList"}: {}, - {Group: "", Version: "v1", Kind: "PodAttachOptions"}: {}, - {Group: "", Version: "v1", Kind: "PodExecOptions"}: {}, {Group: "", Version: "v1", Kind: "Pod"}: {}, {Group: "", Version: "v1", Kind: "PodList"}: {}, {Group: "", Version: "v1", Kind: "PodTemplate"}: {}, @@ -74,6 +72,8 @@ func TestDefaulting(t *testing.T) { {Group: "apps", Version: "v1beta1", Kind: "StatefulSetList"}: {}, {Group: "apps", Version: "v1beta2", Kind: "StatefulSet"}: {}, {Group: "apps", Version: "v1beta2", Kind: "StatefulSetList"}: {}, + {Group: "apps", Version: "v1", Kind: "StatefulSet"}: {}, + {Group: "apps", Version: "v1", Kind: "StatefulSetList"}: {}, {Group: "autoscaling", Version: "v1", Kind: "HorizontalPodAutoscaler"}: {}, {Group: "autoscaling", Version: "v1", Kind: "HorizontalPodAutoscalerList"}: {}, {Group: "autoscaling", Version: "v2beta1", Kind: "HorizontalPodAutoscaler"}: {}, @@ -95,48 +95,54 @@ func TestDefaulting(t *testing.T) { // This object contains only int fields which currently breaks the defaulting test because // it's pretty stupid. Once we add non integer fields, we should uncomment this. // {Group: "kubeadm.k8s.io", Version: "v1alpha1", Kind: "NodeConfiguration"}: {}, - {Group: "extensions", Version: "v1beta1", Kind: "DaemonSet"}: {}, - {Group: "extensions", Version: "v1beta1", Kind: "DaemonSetList"}: {}, - {Group: "apps", Version: "v1beta2", Kind: "DaemonSet"}: {}, - {Group: "apps", Version: "v1beta2", Kind: "DaemonSetList"}: {}, - {Group: "apps", Version: "v1", Kind: "DaemonSet"}: {}, - {Group: "apps", Version: "v1", Kind: "DaemonSetList"}: {}, - {Group: "extensions", Version: "v1beta1", Kind: "Deployment"}: {}, - {Group: "extensions", Version: "v1beta1", Kind: "DeploymentList"}: {}, - {Group: "apps", Version: "v1beta1", Kind: "Deployment"}: {}, - {Group: "apps", Version: "v1beta1", Kind: "DeploymentList"}: {}, - {Group: "apps", Version: "v1beta2", Kind: "Deployment"}: {}, - {Group: "apps", Version: "v1beta2", Kind: "DeploymentList"}: {}, - {Group: "extensions", Version: "v1beta1", Kind: "PodSecurityPolicy"}: {}, - {Group: "extensions", Version: "v1beta1", Kind: "PodSecurityPolicyList"}: {}, - {Group: "apps", Version: "v1beta2", Kind: "ReplicaSet"}: {}, - {Group: "apps", Version: "v1beta2", Kind: "ReplicaSetList"}: {}, - {Group: "extensions", Version: "v1beta1", Kind: "ReplicaSet"}: {}, - {Group: "extensions", Version: "v1beta1", Kind: "ReplicaSetList"}: {}, - {Group: "extensions", Version: "v1beta1", Kind: "NetworkPolicy"}: {}, - {Group: "extensions", Version: "v1beta1", Kind: "NetworkPolicyList"}: {}, - {Group: "rbac.authorization.k8s.io", Version: "v1alpha1", Kind: "ClusterRoleBinding"}: {}, - {Group: "rbac.authorization.k8s.io", Version: "v1alpha1", Kind: "ClusterRoleBindingList"}: {}, - {Group: "rbac.authorization.k8s.io", Version: "v1alpha1", Kind: "RoleBinding"}: {}, - {Group: "rbac.authorization.k8s.io", Version: "v1alpha1", Kind: "RoleBindingList"}: {}, - {Group: "rbac.authorization.k8s.io", Version: "v1beta1", Kind: "ClusterRoleBinding"}: {}, - {Group: "rbac.authorization.k8s.io", Version: "v1beta1", Kind: "ClusterRoleBindingList"}: {}, - {Group: "rbac.authorization.k8s.io", Version: "v1beta1", Kind: "RoleBinding"}: {}, - {Group: "rbac.authorization.k8s.io", Version: "v1beta1", Kind: "RoleBindingList"}: {}, - {Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRoleBinding"}: {}, - {Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRoleBindingList"}: {}, - {Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "RoleBinding"}: {}, - {Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "RoleBindingList"}: {}, - {Group: "settings.k8s.io", Version: "v1alpha1", Kind: "PodPreset"}: {}, - {Group: "settings.k8s.io", Version: "v1alpha1", Kind: "PodPresetList"}: {}, - {Group: "admissionregistration.k8s.io", Version: "v1alpha1", Kind: "ExternalAdmissionHookConfiguration"}: {}, - {Group: "admissionregistration.k8s.io", Version: "v1alpha1", Kind: "ExternalAdmissionHookConfigurationList"}: {}, - {Group: "networking.k8s.io", Version: "v1", Kind: "NetworkPolicy"}: {}, - {Group: "networking.k8s.io", Version: "v1", Kind: "NetworkPolicyList"}: {}, - {Group: "storage.k8s.io", Version: "v1beta1", Kind: "StorageClass"}: {}, - {Group: "storage.k8s.io", Version: "v1beta1", Kind: "StorageClassList"}: {}, - {Group: "storage.k8s.io", Version: "v1", Kind: "StorageClass"}: {}, - {Group: "storage.k8s.io", Version: "v1", Kind: "StorageClassList"}: {}, + {Group: "extensions", Version: "v1beta1", Kind: "DaemonSet"}: {}, + {Group: "extensions", Version: "v1beta1", Kind: "DaemonSetList"}: {}, + {Group: "apps", Version: "v1beta2", Kind: "DaemonSet"}: {}, + {Group: "apps", Version: "v1beta2", Kind: "DaemonSetList"}: {}, + {Group: "apps", Version: "v1", Kind: "DaemonSet"}: {}, + {Group: "apps", Version: "v1", Kind: "DaemonSetList"}: {}, + {Group: "extensions", Version: "v1beta1", Kind: "Deployment"}: {}, + {Group: "extensions", Version: "v1beta1", Kind: "DeploymentList"}: {}, + {Group: "apps", Version: "v1beta1", Kind: "Deployment"}: {}, + {Group: "apps", Version: "v1beta1", Kind: "DeploymentList"}: {}, + {Group: "apps", Version: "v1beta2", Kind: "Deployment"}: {}, + {Group: "apps", Version: "v1beta2", Kind: "DeploymentList"}: {}, + {Group: "apps", Version: "v1", Kind: "Deployment"}: {}, + {Group: "apps", Version: "v1", Kind: "DeploymentList"}: {}, + {Group: "extensions", Version: "v1beta1", Kind: "PodSecurityPolicy"}: {}, + {Group: "extensions", Version: "v1beta1", Kind: "PodSecurityPolicyList"}: {}, + {Group: "apps", Version: "v1beta2", Kind: "ReplicaSet"}: {}, + {Group: "apps", Version: "v1beta2", Kind: "ReplicaSetList"}: {}, + {Group: "apps", Version: "v1", Kind: "ReplicaSet"}: {}, + {Group: "apps", Version: "v1", Kind: "ReplicaSetList"}: {}, + {Group: "extensions", Version: "v1beta1", Kind: "ReplicaSet"}: {}, + {Group: "extensions", Version: "v1beta1", Kind: "ReplicaSetList"}: {}, + {Group: "extensions", Version: "v1beta1", Kind: "NetworkPolicy"}: {}, + {Group: "extensions", Version: "v1beta1", Kind: "NetworkPolicyList"}: {}, + {Group: "rbac.authorization.k8s.io", Version: "v1alpha1", Kind: "ClusterRoleBinding"}: {}, + {Group: "rbac.authorization.k8s.io", Version: "v1alpha1", Kind: "ClusterRoleBindingList"}: {}, + {Group: "rbac.authorization.k8s.io", Version: "v1alpha1", Kind: "RoleBinding"}: {}, + {Group: "rbac.authorization.k8s.io", Version: "v1alpha1", Kind: "RoleBindingList"}: {}, + {Group: "rbac.authorization.k8s.io", Version: "v1beta1", Kind: "ClusterRoleBinding"}: {}, + {Group: "rbac.authorization.k8s.io", Version: "v1beta1", Kind: "ClusterRoleBindingList"}: {}, + {Group: "rbac.authorization.k8s.io", Version: "v1beta1", Kind: "RoleBinding"}: {}, + {Group: "rbac.authorization.k8s.io", Version: "v1beta1", Kind: "RoleBindingList"}: {}, + {Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRoleBinding"}: {}, + {Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRoleBindingList"}: {}, + {Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "RoleBinding"}: {}, + {Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "RoleBindingList"}: {}, + {Group: "settings.k8s.io", Version: "v1alpha1", Kind: "PodPreset"}: {}, + {Group: "settings.k8s.io", Version: "v1alpha1", Kind: "PodPresetList"}: {}, + {Group: "admissionregistration.k8s.io", Version: "v1beta1", Kind: "ValidatingWebhookConfiguration"}: {}, + {Group: "admissionregistration.k8s.io", Version: "v1beta1", Kind: "ValidatingWebhookConfigurationList"}: {}, + {Group: "admissionregistration.k8s.io", Version: "v1beta1", Kind: "MutatingWebhookConfiguration"}: {}, + {Group: "admissionregistration.k8s.io", Version: "v1beta1", Kind: "MutatingWebhookConfigurationList"}: {}, + {Group: "networking.k8s.io", Version: "v1", Kind: "NetworkPolicy"}: {}, + {Group: "networking.k8s.io", Version: "v1", Kind: "NetworkPolicyList"}: {}, + {Group: "storage.k8s.io", Version: "v1beta1", Kind: "StorageClass"}: {}, + {Group: "storage.k8s.io", Version: "v1beta1", Kind: "StorageClassList"}: {}, + {Group: "storage.k8s.io", Version: "v1", Kind: "StorageClass"}: {}, + {Group: "storage.k8s.io", Version: "v1", Kind: "StorageClassList"}: {}, } f := fuzz.New().NilChance(.5).NumElements(1, 1).RandSource(rand.NewSource(1)) @@ -157,7 +163,7 @@ func TestDefaulting(t *testing.T) { }, ) - scheme := api.Scheme + scheme := legacyscheme.Scheme var testTypes orderedGroupVersionKinds for gvk := range scheme.AllKnownTypes() { if gvk.Version == runtime.APIVersionInternal { @@ -216,7 +222,7 @@ func BenchmarkPodDefaulting(b *testing.B) { f.Fuzz(&items[i]) } - scheme := api.Scheme + scheme := legacyscheme.Scheme b.ResetTimer() for i := 0; i < b.N; i++ { pod := &items[i%len(items)] diff --git a/pkg/api/testing/fuzzer.go b/pkg/api/testing/fuzzer.go index 8fc37898d49..4779ae9ec46 100644 --- a/pkg/api/testing/fuzzer.go +++ b/pkg/api/testing/fuzzer.go @@ -27,14 +27,13 @@ import ( genericfuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer" "k8s.io/apimachinery/pkg/runtime" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" - kubeadmfuzzer "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/fuzzer" - "k8s.io/kubernetes/pkg/api" - corefuzzer "k8s.io/kubernetes/pkg/api/fuzzer" admissionregistrationfuzzer "k8s.io/kubernetes/pkg/apis/admissionregistration/fuzzer" appsfuzzer "k8s.io/kubernetes/pkg/apis/apps/fuzzer" autoscalingfuzzer "k8s.io/kubernetes/pkg/apis/autoscaling/fuzzer" batchfuzzer "k8s.io/kubernetes/pkg/apis/batch/fuzzer" certificatesfuzzer "k8s.io/kubernetes/pkg/apis/certificates/fuzzer" + api "k8s.io/kubernetes/pkg/apis/core" + corefuzzer "k8s.io/kubernetes/pkg/apis/core/fuzzer" "k8s.io/kubernetes/pkg/apis/extensions" extensionsfuzzer "k8s.io/kubernetes/pkg/apis/extensions/fuzzer" extensionsv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" @@ -98,7 +97,6 @@ var FuzzerFuncs = fuzzer.MergeFuzzerFuncs( batchfuzzer.Funcs, autoscalingfuzzer.Funcs, rbacfuzzer.Funcs, - kubeadmfuzzer.Funcs, policyfuzzer.Funcs, certificatesfuzzer.Funcs, admissionregistrationfuzzer.Funcs, diff --git a/pkg/api/testing/meta_test.go b/pkg/api/testing/meta_test.go index 32a6cc67f89..d7a62798cb1 100644 --- a/pkg/api/testing/meta_test.go +++ b/pkg/api/testing/meta_test.go @@ -24,16 +24,16 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" ) var _ metav1.Object = &metav1.ObjectMeta{} func TestAccessorImplementations(t *testing.T) { - for _, gv := range api.Registry.EnabledVersions() { + for _, gv := range legacyscheme.Registry.EnabledVersions() { internalGV := schema.GroupVersion{Group: gv.Group, Version: runtime.APIVersionInternal} for _, gv := range []schema.GroupVersion{gv, internalGV} { - for kind, knownType := range api.Scheme.KnownTypes(gv) { + for kind, knownType := range legacyscheme.Scheme.KnownTypes(gv) { value := reflect.New(knownType) obj := value.Interface() if _, ok := obj.(runtime.Object); !ok { diff --git a/pkg/api/testing/pod_specs.go b/pkg/api/testing/pod_specs.go index 2064532dfce..688788dcefd 100644 --- a/pkg/api/testing/pod_specs.go +++ b/pkg/api/testing/pod_specs.go @@ -18,7 +18,7 @@ package testing import ( "k8s.io/api/core/v1" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // DeepEqualSafePodSpec returns a PodSpec which is ready to be used with apiequality.Semantic.DeepEqual diff --git a/pkg/api/testing/serialization_proto_test.go b/pkg/api/testing/serialization_proto_test.go index 9e3b040958f..45c90e818a0 100644 --- a/pkg/api/testing/serialization_proto_test.go +++ b/pkg/api/testing/serialization_proto_test.go @@ -33,16 +33,17 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer/protobuf" "k8s.io/apimachinery/pkg/util/diff" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" _ "k8s.io/kubernetes/pkg/apis/extensions" _ "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" ) func TestUniversalDeserializer(t *testing.T) { expected := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "test"}} - d := api.Codecs.UniversalDeserializer() + d := legacyscheme.Codecs.UniversalDeserializer() for _, mediaType := range []string{"application/json", "application/yaml", "application/vnd.kubernetes.protobuf"} { - info, ok := runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), mediaType) + info, ok := runtime.SerializerInfoForMediaType(legacyscheme.Codecs.SupportedMediaTypes(), mediaType) if !ok { t.Fatal(mediaType) } @@ -61,7 +62,7 @@ func TestUniversalDeserializer(t *testing.T) { } func TestAllFieldsHaveTags(t *testing.T) { - for gvk, obj := range api.Scheme.AllKnownTypes() { + for gvk, obj := range legacyscheme.Scheme.AllKnownTypes() { if gvk.Version == runtime.APIVersionInternal { // internal versions are not serialized to protobuf continue @@ -97,7 +98,7 @@ func fieldsHaveProtobufTags(obj reflect.Type) error { func TestProtobufRoundTrip(t *testing.T) { obj := &v1.Pod{} - fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(benchmarkSeed), api.Codecs).Fuzz(obj) + fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(benchmarkSeed), legacyscheme.Codecs).Fuzz(obj) // InitContainers are turned into annotations by conversion. obj.Spec.InitContainers = nil obj.Status.InitContainerStatuses = nil @@ -137,12 +138,12 @@ func BenchmarkEncodeCodecFromInternalProtobuf(b *testing.B) { width := len(items) encodable := make([]api.Pod, width) for i := range items { - if err := api.Scheme.Convert(&items[i], &encodable[i], nil); err != nil { + if err := legacyscheme.Scheme.Convert(&items[i], &encodable[i], nil); err != nil { b.Fatal(err) } } s := protobuf.NewSerializer(nil, nil, "application/arbitrary.content.type") - codec := api.Codecs.EncoderForVersion(s, v1.SchemeGroupVersion) + codec := legacyscheme.Codecs.EncoderForVersion(s, v1.SchemeGroupVersion) b.ResetTimer() for i := 0; i < b.N; i++ { if _, err := runtime.Encode(codec, &encodable[i%width]); err != nil { @@ -169,8 +170,8 @@ func BenchmarkEncodeProtobufGeneratedMarshal(b *testing.B) { func BenchmarkDecodeCodecToInternalProtobuf(b *testing.B) { items := benchmarkItems(b) width := len(items) - s := protobuf.NewSerializer(api.Scheme, api.Scheme, "application/arbitrary.content.type") - encoder := api.Codecs.EncoderForVersion(s, v1.SchemeGroupVersion) + s := protobuf.NewSerializer(legacyscheme.Scheme, legacyscheme.Scheme, "application/arbitrary.content.type") + encoder := legacyscheme.Codecs.EncoderForVersion(s, v1.SchemeGroupVersion) var encoded [][]byte for i := range items { data, err := runtime.Encode(encoder, &items[i]) @@ -180,7 +181,7 @@ func BenchmarkDecodeCodecToInternalProtobuf(b *testing.B) { encoded = append(encoded, data) } - decoder := api.Codecs.DecoderToVersion(s, api.SchemeGroupVersion) + decoder := legacyscheme.Codecs.DecoderToVersion(s, api.SchemeGroupVersion) b.ResetTimer() for i := 0; i < b.N; i++ { if _, err := runtime.Decode(decoder, encoded[i%width]); err != nil { diff --git a/pkg/api/testing/serialization_test.go b/pkg/api/testing/serialization_test.go index ab139c88f72..36b96f23ca9 100644 --- a/pkg/api/testing/serialization_test.go +++ b/pkg/api/testing/serialization_test.go @@ -23,10 +23,8 @@ import ( "io/ioutil" "math/rand" "reflect" - "strings" "testing" - "github.com/golang/protobuf/proto" jsoniter "github.com/json-iterator/go" "k8s.io/api/core/v1" @@ -43,9 +41,10 @@ import ( "k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/watch" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/testapi" - k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" + api "k8s.io/kubernetes/pkg/apis/core" + k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" "k8s.io/kubernetes/pkg/apis/extensions" k8s_v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" ) @@ -53,7 +52,7 @@ import ( // fuzzInternalObject fuzzes an arbitrary runtime object using the appropriate // fuzzer registered with the apitesting package. func fuzzInternalObject(t *testing.T, forVersion schema.GroupVersion, item runtime.Object, seed int64) runtime.Object { - fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(seed), api.Codecs).Fuzz(item) + fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(seed), legacyscheme.Codecs).Fuzz(item) j, err := meta.TypeAccessor(item) if err != nil { @@ -65,17 +64,6 @@ func fuzzInternalObject(t *testing.T, forVersion schema.GroupVersion, item runti return item } -// dataAsString returns the given byte array as a string; handles detecting -// protocol buffers. -func dataAsString(data []byte) string { - dataString := string(data) - if !strings.HasPrefix(dataString, "{") { - dataString = "\n" + hex.Dump(data) - proto.NewBuffer(make([]byte, 0, 1024)).DebugPrint("decoded object", data) - } - return dataString -} - func Convert_v1beta1_ReplicaSet_to_api_ReplicationController(in *v1beta1.ReplicaSet, out *api.ReplicationController, s conversion.Scope) error { intermediate1 := &extensions.ReplicaSet{} if err := k8s_v1beta1.Convert_v1beta1_ReplicaSet_To_extensions_ReplicaSet(in, intermediate1, s); err != nil { @@ -87,11 +75,11 @@ func Convert_v1beta1_ReplicaSet_to_api_ReplicationController(in *v1beta1.Replica return err } - return k8s_api_v1.Convert_v1_ReplicationController_To_api_ReplicationController(intermediate2, out, s) + return k8s_api_v1.Convert_v1_ReplicationController_To_core_ReplicationController(intermediate2, out, s) } func TestSetControllerConversion(t *testing.T) { - if err := api.Scheme.AddConversionFuncs(Convert_v1beta1_ReplicaSet_to_api_ReplicationController); err != nil { + if err := legacyscheme.Scheme.AddConversionFuncs(Convert_v1beta1_ReplicaSet_to_api_ReplicationController); err != nil { t.Fatal(err) } @@ -103,14 +91,23 @@ func TestSetControllerConversion(t *testing.T) { fuzzInternalObject(t, extGroup.InternalGroupVersion(), rs, rand.Int63()) + // explicitly set the selector to something that is convertible to old-style selectors + // (since normally we'll fuzz the selectors with things that aren't convertible) + rs.Spec.Selector = &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + "baz": "quux", + }, + } + t.Logf("rs._internal.extensions -> rs.v1beta1.extensions") data, err := runtime.Encode(extGroup.Codec(), rs) if err != nil { t.Fatalf("unexpected encoding error: %v", err) } - decoder := api.Codecs.DecoderToVersion( - api.Codecs.UniversalDeserializer(), + decoder := legacyscheme.Codecs.DecoderToVersion( + legacyscheme.Codecs.UniversalDeserializer(), runtime.NewMultiGroupVersioner( *defaultGroup.GroupVersion(), schema.GroupKind{Group: defaultGroup.GroupVersion().Group}, @@ -139,13 +136,13 @@ func TestSetControllerConversion(t *testing.T) { // debug issues that arise while adding a new API type. func TestSpecificKind(t *testing.T) { // Uncomment the following line to enable logging of which conversions - // api.Scheme.Log(t) + // legacyscheme.Scheme.Log(t) internalGVK := schema.GroupVersionKind{Group: "extensions", Version: runtime.APIVersionInternal, Kind: "DaemonSet"} seed := rand.Int63() - fuzzer := fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(seed), api.Codecs) + fuzzer := fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(seed), legacyscheme.Codecs) - roundtrip.RoundTripSpecificKind(t, internalGVK, api.Scheme, api.Codecs, fuzzer, nil) + roundtrip.RoundTripSpecificKind(t, internalGVK, legacyscheme.Scheme, legacyscheme.Codecs, fuzzer, nil) } var nonRoundTrippableTypes = sets.NewString( @@ -171,16 +168,16 @@ func TestCommonKindsRegistered(t *testing.T) { for _, group := range testapi.Groups { gv := group.GroupVersion() gvk := gv.WithKind(kind) - obj, err := api.Scheme.New(gvk) + obj, err := legacyscheme.Scheme.New(gvk) if err != nil { t.Error(err) } defaults := gv.WithKind("") var got *schema.GroupVersionKind - if obj, got, err = api.Codecs.LegacyCodec().Decode([]byte(`{"kind":"`+kind+`"}`), &defaults, obj); err != nil || gvk != *got { + if obj, got, err = legacyscheme.Codecs.LegacyCodec().Decode([]byte(`{"kind":"`+kind+`"}`), &defaults, obj); err != nil || gvk != *got { t.Errorf("expected %v: %v %v", gvk, got, err) } - data, err := runtime.Encode(api.Codecs.LegacyCodec(*gv), obj) + data, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(*gv), obj) if err != nil { t.Errorf("expected %v: %v\n%s", gvk, err, string(data)) continue @@ -204,14 +201,14 @@ func TestCommonKindsRegistered(t *testing.T) { // in all of the API groups registered for test in the testapi package. func TestRoundTripTypes(t *testing.T) { seed := rand.Int63() - fuzzer := fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(seed), api.Codecs) + fuzzer := fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(seed), legacyscheme.Codecs) nonRoundTrippableTypes := map[schema.GroupVersionKind]bool{ {Group: "componentconfig", Version: runtime.APIVersionInternal, Kind: "KubeProxyConfiguration"}: true, {Group: "componentconfig", Version: runtime.APIVersionInternal, Kind: "KubeSchedulerConfiguration"}: true, } - roundtrip.RoundTripTypes(t, api.Scheme, api.Codecs, fuzzer, nonRoundTrippableTypes) + roundtrip.RoundTripTypes(t, legacyscheme.Scheme, legacyscheme.Codecs, fuzzer, nonRoundTrippableTypes) } // TestEncodePtr tests that a pointer to a golang type can be encoded and @@ -299,15 +296,15 @@ func TestUnversionedTypes(t *testing.T) { // TestObjectWatchFraming establishes that a watch event can be encoded and // decoded correctly through each of the supported RFC2046 media types. func TestObjectWatchFraming(t *testing.T) { - f := fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(benchmarkSeed), api.Codecs) + f := fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(benchmarkSeed), legacyscheme.Codecs) secret := &api.Secret{} f.Fuzz(secret) secret.Data["binary"] = []byte{0x00, 0x10, 0x30, 0x55, 0xff, 0x00} secret.Data["utf8"] = []byte("a string with \u0345 characters") secret.Data["long"] = bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x00}, 1000) - converted, _ := api.Scheme.ConvertToVersion(secret, v1.SchemeGroupVersion) + converted, _ := legacyscheme.Scheme.ConvertToVersion(secret, v1.SchemeGroupVersion) v1secret := converted.(*v1.Secret) - for _, info := range api.Codecs.SupportedMediaTypes() { + for _, info := range legacyscheme.Codecs.SupportedMediaTypes() { if info.StreamSerializer == nil { continue } @@ -318,7 +315,7 @@ func TestObjectWatchFraming(t *testing.T) { t.Errorf("no embedded serializer for %s", info.MediaType) continue } - innerDecode := api.Codecs.DecoderToVersion(embedded, api.SchemeGroupVersion) + innerDecode := legacyscheme.Codecs.DecoderToVersion(embedded, api.SchemeGroupVersion) // write a single object through the framer and back out obj := &bytes.Buffer{} @@ -381,13 +378,13 @@ func TestObjectWatchFraming(t *testing.T) { const benchmarkSeed = 100 func benchmarkItems(b *testing.B) []v1.Pod { - apiObjectFuzzer := fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(benchmarkSeed), api.Codecs) + apiObjectFuzzer := fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(benchmarkSeed), legacyscheme.Codecs) items := make([]v1.Pod, 10) for i := range items { var pod api.Pod apiObjectFuzzer.Fuzz(&pod) pod.Spec.InitContainers, pod.Status.InitContainerStatuses = nil, nil - out, err := api.Scheme.ConvertToVersion(&pod, v1.SchemeGroupVersion) + out, err := legacyscheme.Scheme.ConvertToVersion(&pod, v1.SchemeGroupVersion) if err != nil { panic(err) } @@ -417,7 +414,7 @@ func BenchmarkEncodeCodecFromInternal(b *testing.B) { width := len(items) encodable := make([]api.Pod, width) for i := range items { - if err := api.Scheme.Convert(&items[i], &encodable[i], nil); err != nil { + if err := legacyscheme.Scheme.Convert(&items[i], &encodable[i], nil); err != nil { b.Fatal(err) } } @@ -535,8 +532,9 @@ func BenchmarkDecodeIntoJSON(b *testing.B) { b.StopTimer() } -// BenchmarkDecodeJSON provides a baseline for JSON decode performance -func BenchmarkDecodeIntoJSONCodecGen(b *testing.B) { +// BenchmarkDecodeIntoJSONCodecGenConfigFast provides a baseline +// for JSON decode performance with jsoniter.ConfigFast +func BenchmarkDecodeIntoJSONCodecGenConfigFast(b *testing.B) { kcodec := testapi.Default.Codec() items := benchmarkItems(b) width := len(items) @@ -558,3 +556,29 @@ func BenchmarkDecodeIntoJSONCodecGen(b *testing.B) { } b.StopTimer() } + +// BenchmarkDecodeIntoJSONCodecGenConfigCompatibleWithStandardLibrary +// provides a baseline for JSON decode performance +// with jsoniter.ConfigCompatibleWithStandardLibrary +func BenchmarkDecodeIntoJSONCodecGenConfigCompatibleWithStandardLibrary(b *testing.B) { + kcodec := testapi.Default.Codec() + items := benchmarkItems(b) + width := len(items) + encoded := make([][]byte, width) + for i := range items { + data, err := runtime.Encode(kcodec, &items[i]) + if err != nil { + b.Fatal(err) + } + encoded[i] = data + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + obj := v1.Pod{} + if err := jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal(encoded[i%width], &obj); err != nil { + b.Fatal(err) + } + } + b.StopTimer() +} diff --git a/pkg/api/testing/unstructured_test.go b/pkg/api/testing/unstructured_test.go index 4b2f622885c..005b5d4a9ae 100644 --- a/pkg/api/testing/unstructured_test.go +++ b/pkg/api/testing/unstructured_test.go @@ -25,25 +25,28 @@ import ( "k8s.io/api/core/v1" apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/testing/fuzzer" - "k8s.io/apimachinery/pkg/conversion/unstructured" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + metaunstruct "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/json" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/testapi" + api "k8s.io/kubernetes/pkg/apis/core" ) func doRoundTrip(t *testing.T, group testapi.TestGroup, kind string) { // We do fuzzing on the internal version of the object, and only then // convert to the external version. This is because custom fuzzing // function are only supported for internal objects. - internalObj, err := api.Scheme.New(group.InternalGroupVersion().WithKind(kind)) + internalObj, err := legacyscheme.Scheme.New(group.InternalGroupVersion().WithKind(kind)) if err != nil { t.Fatalf("Couldn't create internal object %v: %v", kind, err) } seed := rand.Int63() - fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(seed), api.Codecs). + fuzzer.FuzzerFor(FuzzerFuncs, rand.NewSource(seed), legacyscheme.Codecs). // We are explicitly overwriting custom fuzzing functions, to ensure // that InitContainers and their statuses are not generated. This is // because in thise test we are simply doing json operations, in which @@ -59,11 +62,11 @@ func doRoundTrip(t *testing.T, group testapi.TestGroup, kind string) { }, ).Fuzz(internalObj) - item, err := api.Scheme.New(group.GroupVersion().WithKind(kind)) + item, err := legacyscheme.Scheme.New(group.GroupVersion().WithKind(kind)) if err != nil { t.Fatalf("Couldn't create external object %v: %v", kind, err) } - if err := api.Scheme.Convert(internalObj, item, nil); err != nil { + if err := legacyscheme.Scheme.Convert(internalObj, item, nil); err != nil { t.Fatalf("Conversion for %v failed: %v", kind, err) } @@ -95,14 +98,14 @@ func doRoundTrip(t *testing.T, group testapi.TestGroup, kind string) { return } - newUnstr, err := unstructured.DefaultConverter.ToUnstructured(item) + newUnstr, err := runtime.NewTestUnstructuredConverter(apiequality.Semantic).ToUnstructured(item) if err != nil { t.Errorf("ToUnstructured failed: %v", err) return } newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object) - err = unstructured.DefaultConverter.FromUnstructured(newUnstr, newObj) + err = runtime.DefaultUnstructuredConverter.FromUnstructured(newUnstr, newObj) if err != nil { t.Errorf("FromUnstructured failed: %v", err) return @@ -130,17 +133,54 @@ func TestRoundTrip(t *testing.T) { } } +func TestRoundTripWithEmptyCreationTimestamp(t *testing.T) { + for groupKey, group := range testapi.Groups { + for kind := range group.ExternalTypes() { + if nonRoundTrippableTypes.Has(kind) { + continue + } + item, err := legacyscheme.Scheme.New(group.GroupVersion().WithKind(kind)) + if err != nil { + t.Fatalf("Couldn't create external object %v: %v", kind, err) + } + t.Logf("Testing: %v in %v", kind, groupKey) + + unstrBody, err := runtime.DefaultUnstructuredConverter.ToUnstructured(item) + if err != nil { + t.Fatalf("ToUnstructured failed: %v", err) + } + + unstructObj := &metaunstruct.Unstructured{} + unstructObj.Object = unstrBody + + if meta, err := meta.Accessor(unstructObj); err == nil { + meta.SetCreationTimestamp(metav1.Time{}) + } else { + t.Fatalf("Unable to set creation timestamp: %v", err) + } + + // attempt to re-convert unstructured object - conversion should not fail + // based on empty metadata fields, such as creationTimestamp + newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object) + err = runtime.NewTestUnstructuredConverter(apiequality.Semantic).FromUnstructured(unstructObj.Object, newObj) + if err != nil { + t.Fatalf("FromUnstructured failed: %v", err) + } + } + } +} + func BenchmarkToFromUnstructured(b *testing.B) { items := benchmarkItems(b) size := len(items) b.ResetTimer() for i := 0; i < b.N; i++ { - unstr, err := unstructured.DefaultConverter.ToUnstructured(&items[i%size]) + unstr, err := runtime.NewTestUnstructuredConverter(apiequality.Semantic).ToUnstructured(&items[i%size]) if err != nil { b.Fatalf("unexpected error: %v", err) } obj := v1.Pod{} - if err := unstructured.DefaultConverter.FromUnstructured(unstr, &obj); err != nil { + if err := runtime.NewTestUnstructuredConverter(apiequality.Semantic).FromUnstructured(unstr, &obj); err != nil { b.Fatalf("unexpected error: %v", err) } } diff --git a/pkg/api/types.go b/pkg/api/types.go deleted file mode 100644 index d04612291e9..00000000000 --- a/pkg/api/types.go +++ /dev/null @@ -1,4287 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package api - -import ( - "k8s.io/apimachinery/pkg/api/resource" - metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" -) - -// Common string formats -// --------------------- -// Many fields in this API have formatting requirements. The commonly used -// formats are defined here. -// -// C_IDENTIFIER: This is a string that conforms to the definition of an "identifier" -// in the C language. This is captured by the following regex: -// [A-Za-z_][A-Za-z0-9_]* -// This defines the format, but not the length restriction, which should be -// specified at the definition of any field of this type. -// -// DNS_LABEL: This is a string, no more than 63 characters long, that conforms -// to the definition of a "label" in RFCs 1035 and 1123. This is captured -// by the following regex: -// [a-z0-9]([-a-z0-9]*[a-z0-9])? -// -// DNS_SUBDOMAIN: This is a string, no more than 253 characters long, that conforms -// to the definition of a "subdomain" in RFCs 1035 and 1123. This is captured -// by the following regex: -// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* -// or more simply: -// DNS_LABEL(\.DNS_LABEL)* -// -// IANA_SVC_NAME: This is a string, no more than 15 characters long, that -// conforms to the definition of IANA service name in RFC 6335. -// It must contains at least one letter [a-z] and it must contains only [a-z0-9-]. -// Hypens ('-') cannot be leading or trailing character of the string -// and cannot be adjacent to other hyphens. - -// ObjectMeta is metadata that all persisted resources must have, which includes all objects -// users must create. -// DEPRECATED: Use k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta instead - this type will be removed soon. -type ObjectMeta struct { - // Name is unique within a namespace. Name 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. - // +optional - Name string - - // GenerateName indicates that the name should be made unique by the server prior to persisting - // it. A non-empty value for the field indicates the name will be made unique (and the name - // returned to the client will be different than the name passed). The value of this field will - // be combined with a unique suffix on the server if the Name field has not been provided. - // The provided value must be valid within the rules for Name, and may be truncated by the length - // of the suffix required to make the value unique on the server. - // - // If this field is specified, and Name is not present, the server will NOT return a 409 if the - // generated name exists - instead, it will either return 201 Created or 500 with Reason - // ServerTimeout indicating a unique name could not be found in the time allotted, and the client - // should retry (optionally after the time indicated in the Retry-After header). - // +optional - GenerateName string - - // Namespace defines the space within which 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. - // +optional - Namespace string - - // SelfLink is a URL representing this object. - // +optional - SelfLink string - - // 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. - // +optional - UID types.UID - - // An opaque value that represents the version of this resource. 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 values may only be valid for a particular - // resource or set of resources. Only servers will generate resource versions. - // +optional - ResourceVersion string - - // A sequence number representing a specific generation of the desired state. - // Populated by the system. Read-only. - // +optional - Generation int64 - - // 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. - // +optional - CreationTimestamp metav1.Time - - // 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 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. - // - // Populated by the system when a graceful deletion is requested. - // Read-only. - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata - // +optional - DeletionTimestamp *metav1.Time - - // DeletionGracePeriodSeconds records the graceful deletion value set when graceful deletion - // was requested. Represents the most recent grace period, and may only be shortened once set. - // +optional - DeletionGracePeriodSeconds *int64 - - // Labels are key value pairs that may be used to scope and select individual resources. - // Label keys are of the form: - // label-key ::= prefixed-name | name - // prefixed-name ::= prefix '/' name - // prefix ::= DNS_SUBDOMAIN - // name ::= DNS_LABEL - // The prefix is optional. If the prefix is not specified, the key is assumed to be private - // to the user. Other system components that wish to use labels must specify a prefix. The - // "kubernetes.io/" prefix is reserved for use by kubernetes components. - // +optional - Labels map[string]string - - // Annotations are unstructured key value data stored with a resource that may be set by - // external tooling. They are not queryable and should be preserved when modifying - // objects. Annotation keys have the same formatting restrictions as Label keys. See the - // comments on Labels for details. - // +optional - Annotations map[string]string - - // 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. - // +optional - OwnerReferences []metav1.OwnerReference - - // An initializer is a controller which enforces some system invariant at object creation time. - // This field is a list of initializers that have not yet acted on this object. If nil or empty, - // this object has been completely initialized. Otherwise, the object is considered uninitialized - // and is hidden (in list/watch and get calls) from clients that haven't explicitly asked to - // observe uninitialized objects. - // - // When an object is created, the system will populate this list with the current set of initializers. - // Only privileged users may set or modify this list. Once it is empty, it may not be modified further - // by any user. - Initializers *metav1.Initializers - - // 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. - // +optional - Finalizers []string - - // The name of the cluster which the object belongs to. - // This is used to distinguish resources with same name and namespace in different clusters. - // This field is not set anywhere right now and apiserver is going to ignore it if set in create or update request. - // +optional - ClusterName string -} - -const ( - // NamespaceDefault means the object is in the default namespace which is applied when not specified by clients - NamespaceDefault string = "default" - // NamespaceAll is the default argument to specify on a context when you want to list or filter resources across all namespaces - NamespaceAll string = "" - // NamespaceNone is the argument for a context when there is no namespace. - NamespaceNone string = "" - // NamespaceSystem is the system namespace where we place system components. - NamespaceSystem string = "kube-system" - // NamespacePublic is the namespace where we place public info (ConfigMaps) - NamespacePublic string = "kube-public" - // TerminationMessagePathDefault means the default path to capture the application termination message running in a container - TerminationMessagePathDefault string = "/dev/termination-log" -) - -// Volume represents a named volume in a pod that may be accessed by any containers in the pod. -type Volume struct { - // Required: This must be a DNS_LABEL. Each volume in a pod must have - // a unique name. - Name string - // The VolumeSource represents the location and type of a volume to mount. - // This is optional for now. If not specified, the Volume is implied to be an EmptyDir. - // This implied behavior is deprecated and will be removed in a future version. - // +optional - VolumeSource -} - -// VolumeSource represents the source location of a volume to mount. -// Only one of its members may be specified. -type VolumeSource struct { - // HostPath represents file or directory on the host machine that is - // directly exposed to the container. This is generally used for system - // agents or other privileged things that are allowed to see the host - // machine. Most containers will NOT need this. - // --- - // TODO(jonesdl) We need to restrict who can use host directory mounts and who can/can not - // mount host directories as read/write. - // +optional - HostPath *HostPathVolumeSource - // EmptyDir represents a temporary directory that shares a pod's lifetime. - // +optional - EmptyDir *EmptyDirVolumeSource - // GCEPersistentDisk represents a GCE Disk resource that is attached to a - // kubelet's host machine and then exposed to the pod. - // +optional - GCEPersistentDisk *GCEPersistentDiskVolumeSource - // AWSElasticBlockStore represents an AWS EBS disk that is attached to a - // kubelet's host machine and then exposed to the pod. - // +optional - AWSElasticBlockStore *AWSElasticBlockStoreVolumeSource - // GitRepo represents a git repository at a particular revision. - // +optional - GitRepo *GitRepoVolumeSource - // Secret represents a secret that should populate this volume. - // +optional - Secret *SecretVolumeSource - // NFS represents an NFS mount on the host that shares a pod's lifetime - // +optional - NFS *NFSVolumeSource - // ISCSIVolumeSource represents an ISCSI Disk resource that is attached to a - // kubelet's host machine and then exposed to the pod. - // +optional - ISCSI *ISCSIVolumeSource - // Glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime - // +optional - Glusterfs *GlusterfsVolumeSource - // PersistentVolumeClaimVolumeSource represents a reference to a PersistentVolumeClaim in the same namespace - // +optional - PersistentVolumeClaim *PersistentVolumeClaimVolumeSource - // RBD represents a Rados Block Device mount on the host that shares a pod's lifetime - // +optional - RBD *RBDVolumeSource - - // Quobyte represents a Quobyte mount on the host that shares a pod's lifetime - // +optional - Quobyte *QuobyteVolumeSource - - // FlexVolume represents a generic volume resource that is - // provisioned/attached using an exec based plugin. This is an alpha feature and may change in future. - // +optional - FlexVolume *FlexVolumeSource - - // Cinder represents a cinder volume attached and mounted on kubelets host machine - // +optional - Cinder *CinderVolumeSource - - // CephFS represents a Cephfs mount on the host that shares a pod's lifetime - // +optional - CephFS *CephFSVolumeSource - - // Flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running - // +optional - Flocker *FlockerVolumeSource - - // DownwardAPI represents metadata about the pod that should populate this volume - // +optional - DownwardAPI *DownwardAPIVolumeSource - // FC represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod. - // +optional - FC *FCVolumeSource - // AzureFile represents an Azure File Service mount on the host and bind mount to the pod. - // +optional - AzureFile *AzureFileVolumeSource - // ConfigMap represents a configMap that should populate this volume - // +optional - ConfigMap *ConfigMapVolumeSource - // VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine - // +optional - VsphereVolume *VsphereVirtualDiskVolumeSource - // AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. - // +optional - AzureDisk *AzureDiskVolumeSource - // PhotonPersistentDisk represents a Photon Controller persistent disk attached and mounted on kubelets host machine - PhotonPersistentDisk *PhotonPersistentDiskVolumeSource - // Items for all in one resources secrets, configmaps, and downward API - Projected *ProjectedVolumeSource - // PortworxVolume represents a portworx volume attached and mounted on kubelets host machine - // +optional - PortworxVolume *PortworxVolumeSource - // ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. - // +optional - ScaleIO *ScaleIOVolumeSource - // StorageOS represents a StorageOS volume that is attached to the kubelet's host machine and mounted into the pod - // +optional - StorageOS *StorageOSVolumeSource -} - -// Similar to VolumeSource but meant for the administrator who creates PVs. -// Exactly one of its members must be set. -type PersistentVolumeSource struct { - // GCEPersistentDisk represents a GCE Disk resource that is attached to a - // kubelet's host machine and then exposed to the pod. - // +optional - GCEPersistentDisk *GCEPersistentDiskVolumeSource - // AWSElasticBlockStore represents an AWS EBS disk that is attached to a - // kubelet's host machine and then exposed to the pod. - // +optional - AWSElasticBlockStore *AWSElasticBlockStoreVolumeSource - // HostPath represents a directory on the host. - // Provisioned by a developer or tester. - // This is useful for single-node development and testing only! - // On-host storage is not supported in any way and WILL NOT WORK in a multi-node cluster. - // +optional - HostPath *HostPathVolumeSource - // Glusterfs represents a Glusterfs volume that is attached to a host and exposed to the pod - // +optional - Glusterfs *GlusterfsVolumeSource - // NFS represents an NFS mount on the host that shares a pod's lifetime - // +optional - NFS *NFSVolumeSource - // RBD represents a Rados Block Device mount on the host that shares a pod's lifetime - // +optional - RBD *RBDVolumeSource - // Quobyte represents a Quobyte mount on the host that shares a pod's lifetime - // +optional - Quobyte *QuobyteVolumeSource - // ISCSIVolumeSource represents an ISCSI resource that is attached to a - // kubelet's host machine and then exposed to the pod. - // +optional - ISCSI *ISCSIVolumeSource - // FlexVolume represents a generic volume resource that is - // provisioned/attached using an exec based plugin. This is an alpha feature and may change in future. - // +optional - FlexVolume *FlexVolumeSource - // Cinder represents a cinder volume attached and mounted on kubelets host machine - // +optional - Cinder *CinderVolumeSource - // CephFS represents a Ceph FS mount on the host that shares a pod's lifetime - // +optional - CephFS *CephFSPersistentVolumeSource - // FC represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod. - // +optional - FC *FCVolumeSource - // Flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running - // +optional - Flocker *FlockerVolumeSource - // AzureFile represents an Azure File Service mount on the host and bind mount to the pod. - // +optional - AzureFile *AzureFilePersistentVolumeSource - // VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine - // +optional - VsphereVolume *VsphereVirtualDiskVolumeSource - // AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. - // +optional - AzureDisk *AzureDiskVolumeSource - // PhotonPersistentDisk represents a Photon Controller persistent disk attached and mounted on kubelets host machine - PhotonPersistentDisk *PhotonPersistentDiskVolumeSource - // PortworxVolume represents a portworx volume attached and mounted on kubelets host machine - // +optional - PortworxVolume *PortworxVolumeSource - // ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. - // +optional - ScaleIO *ScaleIOVolumeSource - // Local represents directly-attached storage with node affinity - // +optional - Local *LocalVolumeSource - // StorageOS represents a StorageOS volume that is attached to the kubelet's host machine and mounted into the pod - // More info: https://releases.k8s.io/HEAD/examples/volumes/storageos/README.md - // +optional - StorageOS *StorageOSPersistentVolumeSource -} - -type PersistentVolumeClaimVolumeSource struct { - // ClaimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume - ClaimName string - // Optional: Defaults to false (read/write). ReadOnly here - // will force the ReadOnly setting in VolumeMounts - // +optional - ReadOnly bool -} - -const ( - // BetaStorageClassAnnotation represents the beta/previous StorageClass annotation. - // It's currently still used and will be held for backwards compatibility - BetaStorageClassAnnotation = "volume.beta.kubernetes.io/storage-class" - - // MountOptionAnnotation defines mount option annotation used in PVs - MountOptionAnnotation = "volume.beta.kubernetes.io/mount-options" - - // AlphaStorageNodeAffinityAnnotation defines node affinity policies for a PersistentVolume. - // Value is a string of the json representation of type NodeAffinity - AlphaStorageNodeAffinityAnnotation = "volume.alpha.kubernetes.io/node-affinity" -) - -// +genclient -// +genclient:nonNamespaced -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -type PersistentVolume struct { - metav1.TypeMeta - // +optional - metav1.ObjectMeta - - //Spec defines a persistent volume owned by the cluster - // +optional - Spec PersistentVolumeSpec - - // Status represents the current information about persistent volume. - // +optional - Status PersistentVolumeStatus -} - -type PersistentVolumeSpec struct { - // Resources represents the actual resources of the volume - Capacity ResourceList - // Source represents the location and type of a volume to mount. - PersistentVolumeSource - // AccessModes contains all ways the volume can be mounted - // +optional - AccessModes []PersistentVolumeAccessMode - // ClaimRef is part of a bi-directional binding between PersistentVolume and PersistentVolumeClaim. - // ClaimRef is expected to be non-nil when bound. - // claim.VolumeName is the authoritative bind between PV and PVC. - // When set to non-nil value, PVC.Spec.Selector of the referenced PVC is - // ignored, i.e. labels of this PV do not need to match PVC selector. - // +optional - ClaimRef *ObjectReference - // Optional: what happens to a persistent volume when released from its claim. - // +optional - PersistentVolumeReclaimPolicy PersistentVolumeReclaimPolicy - // Name of StorageClass to which this persistent volume belongs. Empty value - // means that this volume does not belong to any StorageClass. - // +optional - StorageClassName string - // A list of mount options, e.g. ["ro", "soft"]. Not validated - mount will - // simply fail if one is invalid. - // +optional - MountOptions []string -} - -// PersistentVolumeReclaimPolicy describes a policy for end-of-life maintenance of persistent volumes -type PersistentVolumeReclaimPolicy string - -const ( - // PersistentVolumeReclaimRecycle means the volume will be recycled back into the pool of unbound persistent volumes on release from its claim. - // The volume plugin must support Recycling. - PersistentVolumeReclaimRecycle PersistentVolumeReclaimPolicy = "Recycle" - // PersistentVolumeReclaimDelete means the volume will be deleted from Kubernetes on release from its claim. - // The volume plugin must support Deletion. - PersistentVolumeReclaimDelete PersistentVolumeReclaimPolicy = "Delete" - // PersistentVolumeReclaimRetain means the volume will be left in its current phase (Released) for manual reclamation by the administrator. - // The default policy is Retain. - PersistentVolumeReclaimRetain PersistentVolumeReclaimPolicy = "Retain" -) - -type PersistentVolumeStatus struct { - // Phase indicates if a volume is available, bound to a claim, or released by a claim - // +optional - Phase PersistentVolumePhase - // A human-readable message indicating details about why the volume is in this state. - // +optional - Message string - // Reason is a brief CamelCase string that describes any failure and is meant for machine parsing and tidy display in the CLI - // +optional - Reason string -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -type PersistentVolumeList struct { - metav1.TypeMeta - // +optional - metav1.ListMeta - Items []PersistentVolume -} - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// PersistentVolumeClaim is a user's request for and claim to a persistent volume -type PersistentVolumeClaim struct { - metav1.TypeMeta - // +optional - metav1.ObjectMeta - - // Spec defines the volume requested by a pod author - // +optional - Spec PersistentVolumeClaimSpec - - // Status represents the current information about a claim - // +optional - Status PersistentVolumeClaimStatus -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -type PersistentVolumeClaimList struct { - metav1.TypeMeta - // +optional - metav1.ListMeta - Items []PersistentVolumeClaim -} - -// PersistentVolumeClaimSpec describes the common attributes of storage devices -// and allows a Source for provider-specific attributes -type PersistentVolumeClaimSpec struct { - // Contains the types of access modes required - // +optional - AccessModes []PersistentVolumeAccessMode - // A label query over volumes to consider for binding. This selector is - // ignored when VolumeName is set - // +optional - Selector *metav1.LabelSelector - // Resources represents the minimum resources required - // +optional - Resources ResourceRequirements - // VolumeName is the binding reference to the PersistentVolume backing this - // claim. When set to non-empty value Selector is not evaluated - // +optional - VolumeName string - // Name of the StorageClass required by the claim. - // More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#class-1 - // +optional - StorageClassName *string -} - -type PersistentVolumeClaimConditionType string - -// These are valid conditions of Pvc -const ( - // An user trigger resize of pvc has been started - PersistentVolumeClaimResizing PersistentVolumeClaimConditionType = "Resizing" -) - -type PersistentVolumeClaimCondition struct { - Type PersistentVolumeClaimConditionType - Status ConditionStatus - // +optional - LastProbeTime metav1.Time - // +optional - LastTransitionTime metav1.Time - // +optional - Reason string - // +optional - Message string -} - -type PersistentVolumeClaimStatus struct { - // Phase represents the current phase of PersistentVolumeClaim - // +optional - Phase PersistentVolumeClaimPhase - // AccessModes contains all ways the volume backing the PVC can be mounted - // +optional - AccessModes []PersistentVolumeAccessMode - // Represents the actual resources of the underlying volume - // +optional - Capacity ResourceList - // +optional - Conditions []PersistentVolumeClaimCondition -} - -type PersistentVolumeAccessMode string - -const ( - // can be mounted read/write mode to exactly 1 host - ReadWriteOnce PersistentVolumeAccessMode = "ReadWriteOnce" - // can be mounted in read-only mode to many hosts - ReadOnlyMany PersistentVolumeAccessMode = "ReadOnlyMany" - // can be mounted in read/write mode to many hosts - ReadWriteMany PersistentVolumeAccessMode = "ReadWriteMany" -) - -type PersistentVolumePhase string - -const ( - // used for PersistentVolumes that are not available - VolumePending PersistentVolumePhase = "Pending" - // used for PersistentVolumes that are not yet bound - // Available volumes are held by the binder and matched to PersistentVolumeClaims - VolumeAvailable PersistentVolumePhase = "Available" - // used for PersistentVolumes that are bound - VolumeBound PersistentVolumePhase = "Bound" - // used for PersistentVolumes where the bound PersistentVolumeClaim was deleted - // released volumes must be recycled before becoming available again - // this phase is used by the persistent volume claim binder to signal to another process to reclaim the resource - VolumeReleased PersistentVolumePhase = "Released" - // used for PersistentVolumes that failed to be correctly recycled or deleted after being released from a claim - VolumeFailed PersistentVolumePhase = "Failed" -) - -type PersistentVolumeClaimPhase string - -const ( - // used for PersistentVolumeClaims that are not yet bound - ClaimPending PersistentVolumeClaimPhase = "Pending" - // used for PersistentVolumeClaims that are bound - ClaimBound PersistentVolumeClaimPhase = "Bound" - // used for PersistentVolumeClaims that lost their underlying - // PersistentVolume. The claim was bound to a PersistentVolume and this - // volume does not exist any longer and all data on it was lost. - ClaimLost PersistentVolumeClaimPhase = "Lost" -) - -type HostPathType string - -const ( - // For backwards compatible, leave it empty if unset - HostPathUnset HostPathType = "" - // If nothing exists at the given path, an empty directory will be created there - // as needed with file mode 0755, having the same group and ownership with Kubelet. - HostPathDirectoryOrCreate HostPathType = "DirectoryOrCreate" - // A directory must exist at the given path - HostPathDirectory HostPathType = "Directory" - // If nothing exists at the given path, an empty file will be created there - // as needed with file mode 0644, having the same group and ownership with Kubelet. - HostPathFileOrCreate HostPathType = "FileOrCreate" - // A file must exist at the given path - HostPathFile HostPathType = "File" - // A UNIX socket must exist at the given path - HostPathSocket HostPathType = "Socket" - // A character device must exist at the given path - HostPathCharDev HostPathType = "CharDevice" - // A block device must exist at the given path - HostPathBlockDev HostPathType = "BlockDevice" -) - -// Represents a host path mapped into a pod. -// Host path volumes do not support ownership management or SELinux relabeling. -type HostPathVolumeSource struct { - // If the path is a symlink, it will follow the link to the real path. - Path string - // Defaults to "" - Type *HostPathType -} - -// Represents an empty directory for a pod. -// Empty directory volumes support ownership management and SELinux relabeling. -type EmptyDirVolumeSource struct { - // TODO: Longer term we want to represent the selection of underlying - // media more like a scheduling problem - user says what traits they - // need, we give them a backing store that satisfies that. For now - // this will cover the most common needs. - // Optional: what type of storage medium should back this directory. - // The default is "" which means to use the node's default medium. - // +optional - Medium StorageMedium - // Total amount of local storage required for this EmptyDir volume. - // The size limit is also applicable for memory medium. - // The maximum usage on memory medium EmptyDir would be the minimum value between - // the SizeLimit specified here and the sum of memory limits of all containers in a pod. - // The default is nil which means that the limit is undefined. - // More info: http://kubernetes.io/docs/user-guide/volumes#emptydir - // +optional - SizeLimit *resource.Quantity -} - -// StorageMedium defines ways that storage can be allocated to a volume. -type StorageMedium string - -const ( - StorageMediumDefault StorageMedium = "" // use whatever the default is for the node - StorageMediumMemory StorageMedium = "Memory" // use memory (tmpfs) - StorageMediumHugePages StorageMedium = "HugePages" // use hugepages -) - -// Protocol defines network protocols supported for things like container ports. -type Protocol string - -const ( - // ProtocolTCP is the TCP protocol. - ProtocolTCP Protocol = "TCP" - // ProtocolUDP is the UDP protocol. - ProtocolUDP Protocol = "UDP" -) - -// Represents a Persistent Disk resource in Google Compute Engine. -// -// A GCE PD must exist before mounting to a container. The disk must -// also be in the same GCE project and zone as the kubelet. A GCE PD -// can only be mounted as read/write once or read-only many times. GCE -// PDs support ownership management and SELinux relabeling. -type GCEPersistentDiskVolumeSource struct { - // Unique name of the PD resource. Used to identify the disk in GCE - PDName string - // Filesystem type to mount. - // Must be a filesystem type supported by the host operating system. - // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. - // TODO: how do we prevent errors in the filesystem from compromising the machine - // +optional - FSType string - // Optional: Partition on the disk to mount. - // If omitted, kubelet will attempt to mount the device name. - // Ex. For /dev/sda1, this field is "1", for /dev/sda, this field is 0 or empty. - // +optional - Partition int32 - // Optional: Defaults to false (read/write). ReadOnly here will force - // the ReadOnly setting in VolumeMounts. - // +optional - ReadOnly bool -} - -// Represents an ISCSI disk. -// ISCSI volumes can only be mounted as read/write once. -// ISCSI volumes support ownership management and SELinux relabeling. -type ISCSIVolumeSource struct { - // Required: iSCSI target portal - // the portal is either an IP or ip_addr:port if port is other than default (typically TCP ports 860 and 3260) - // +optional - TargetPortal string - // Required: target iSCSI Qualified Name - // +optional - IQN string - // Required: iSCSI target lun number - // +optional - Lun int32 - // Optional: Defaults to 'default' (tcp). iSCSI interface name that uses an iSCSI transport. - // +optional - ISCSIInterface string - // Filesystem type to mount. - // Must be a filesystem type supported by the host operating system. - // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. - // TODO: how do we prevent errors in the filesystem from compromising the machine - // +optional - FSType string - // Optional: Defaults to false (read/write). ReadOnly here will force - // the ReadOnly setting in VolumeMounts. - // +optional - ReadOnly bool - // Optional: list of iSCSI target portal ips for high availability. - // the portal is either an IP or ip_addr:port if port is other than default (typically TCP ports 860 and 3260) - // +optional - Portals []string - // Optional: whether support iSCSI Discovery CHAP authentication - // +optional - DiscoveryCHAPAuth bool - // Optional: whether support iSCSI Session CHAP authentication - // +optional - SessionCHAPAuth bool - // Optional: CHAP secret for iSCSI target and initiator authentication. - // The secret is used if either DiscoveryCHAPAuth or SessionCHAPAuth is true - // +optional - SecretRef *LocalObjectReference - // Optional: Custom initiator name per volume. - // If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface - // : will be created for the connection. - // +optional - InitiatorName *string -} - -// Represents a Fibre Channel volume. -// Fibre Channel volumes can only be mounted as read/write once. -// Fibre Channel volumes support ownership management and SELinux relabeling. -type FCVolumeSource struct { - // Optional: FC target worldwide names (WWNs) - // +optional - TargetWWNs []string - // Optional: FC target lun number - // +optional - Lun *int32 - // Filesystem type to mount. - // Must be a filesystem type supported by the host operating system. - // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. - // TODO: how do we prevent errors in the filesystem from compromising the machine - // +optional - FSType string - // Optional: Defaults to false (read/write). ReadOnly here will force - // the ReadOnly setting in VolumeMounts. - // +optional - ReadOnly bool - // Optional: FC volume World Wide Identifiers (WWIDs) - // Either WWIDs or TargetWWNs and Lun must be set, but not both simultaneously. - // +optional - WWIDs []string -} - -// FlexVolume represents a generic volume resource that is -// provisioned/attached using an exec based plugin. This is an alpha feature and may change in future. -type FlexVolumeSource struct { - // Driver is the name of the driver to use for this volume. - Driver string - // Filesystem type to mount. - // Must be a filesystem type supported by the host operating system. - // Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. - // +optional - FSType string - // Optional: SecretRef is reference to the secret object containing - // sensitive information to pass to the plugin scripts. This may be - // empty if no secret object is specified. If the secret object - // contains more than one secret, all secrets are passed to the plugin - // scripts. - // +optional - SecretRef *LocalObjectReference - // Optional: Defaults to false (read/write). ReadOnly here will force - // the ReadOnly setting in VolumeMounts. - // +optional - ReadOnly bool - // Optional: Extra driver options if any. - // +optional - Options map[string]string -} - -// Represents a Persistent Disk resource in AWS. -// -// An AWS EBS disk must exist before mounting to a container. The disk -// must also be in the same AWS zone as the kubelet. An AWS EBS disk -// can only be mounted as read/write once. AWS EBS volumes support -// ownership management and SELinux relabeling. -type AWSElasticBlockStoreVolumeSource struct { - // Unique id of the persistent disk resource. Used to identify the disk in AWS - VolumeID string - // Filesystem type to mount. - // Must be a filesystem type supported by the host operating system. - // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. - // TODO: how do we prevent errors in the filesystem from compromising the machine - // +optional - FSType string - // Optional: Partition on the disk to mount. - // If omitted, kubelet will attempt to mount the device name. - // Ex. For /dev/sda1, this field is "1", for /dev/sda, this field is 0 or empty. - // +optional - Partition int32 - // Optional: Defaults to false (read/write). ReadOnly here will force - // the ReadOnly setting in VolumeMounts. - // +optional - ReadOnly bool -} - -// Represents a volume that is populated with the contents of a git repository. -// Git repo volumes do not support ownership management. -// Git repo volumes support SELinux relabeling. -type GitRepoVolumeSource struct { - // Repository URL - Repository string - // Commit hash, this is optional - // +optional - Revision string - // Clone target, this is optional - // Must not contain or start with '..'. If '.' is supplied, the volume directory will be the - // git repository. Otherwise, if specified, the volume will contain the git repository in - // the subdirectory with the given name. - // +optional - Directory string - // TODO: Consider credentials here. -} - -// Adapts a Secret into a volume. -// -// The contents of the target Secret's Data field will be presented in a volume -// as files using the keys in the Data field as the file names. -// Secret volumes support ownership management and SELinux relabeling. -type SecretVolumeSource struct { - // Name of the secret in the pod's namespace to use. - // +optional - SecretName string - // If unspecified, each key-value pair in the Data field of the referenced - // Secret will be projected into the volume as a file whose name is the - // key and content is the value. If specified, the listed keys will be - // projected into the specified paths, and unlisted keys will not be - // present. If a key is specified which is not present in the Secret, - // the volume setup will error unless it is marked optional. Paths must be - // relative and may not contain the '..' path or start with '..'. - // +optional - Items []KeyToPath - // Mode bits to use on created files by default. Must be a value between - // 0 and 0777. - // Directories within the path are not affected by this setting. - // This might be in conflict with other options that affect the file - // mode, like fsGroup, and the result can be other mode bits set. - // +optional - DefaultMode *int32 - // Specify whether the Secret or its key must be defined - // +optional - Optional *bool -} - -// Adapts a secret into a projected volume. -// -// The contents of the target Secret's Data field will be presented in a -// projected volume as files using the keys in the Data field as the file names. -// Note that this is identical to a secret volume source without the default -// mode. -type SecretProjection struct { - LocalObjectReference - // If unspecified, each key-value pair in the Data field of the referenced - // Secret will be projected into the volume as a file whose name is the - // key and content is the value. If specified, the listed keys will be - // projected into the specified paths, and unlisted keys will not be - // present. If a key is specified which is not present in the Secret, - // the volume setup will error unless it is marked optional. Paths must be - // relative and may not contain the '..' path or start with '..'. - // +optional - Items []KeyToPath - // Specify whether the Secret or its key must be defined - // +optional - Optional *bool -} - -// Represents an NFS mount that lasts the lifetime of a pod. -// NFS volumes do not support ownership management or SELinux relabeling. -type NFSVolumeSource struct { - // Server is the hostname or IP address of the NFS server - Server string - - // Path is the exported NFS share - Path string - - // Optional: Defaults to false (read/write). ReadOnly here will force - // the NFS export to be mounted with read-only permissions - // +optional - ReadOnly bool -} - -// Represents a Quobyte mount that lasts the lifetime of a pod. -// Quobyte volumes do not support ownership management or SELinux relabeling. -type QuobyteVolumeSource struct { - // Registry represents a single or multiple Quobyte Registry services - // specified as a string as host:port pair (multiple entries are separated with commas) - // which acts as the central registry for volumes - Registry string - - // Volume is a string that references an already created Quobyte volume by name. - Volume string - - // Defaults to false (read/write). ReadOnly here will force - // the Quobyte to be mounted with read-only permissions - // +optional - ReadOnly bool - - // User to map volume access to - // Defaults to the root user - // +optional - User string - - // Group to map volume access to - // Default is no group - // +optional - Group string -} - -// Represents a Glusterfs mount that lasts the lifetime of a pod. -// Glusterfs volumes do not support ownership management or SELinux relabeling. -type GlusterfsVolumeSource struct { - // Required: EndpointsName is the endpoint name that details Glusterfs topology - EndpointsName string - - // Required: Path is the Glusterfs volume path - Path string - - // Optional: Defaults to false (read/write). ReadOnly here will force - // the Glusterfs to be mounted with read-only permissions - // +optional - ReadOnly bool -} - -// Represents a Rados Block Device mount that lasts the lifetime of a pod. -// RBD volumes support ownership management and SELinux relabeling. -type RBDVolumeSource struct { - // Required: CephMonitors is a collection of Ceph monitors - CephMonitors []string - // Required: RBDImage is the rados image name - RBDImage string - // Filesystem type to mount. - // Must be a filesystem type supported by the host operating system. - // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. - // TODO: how do we prevent errors in the filesystem from compromising the machine - // +optional - FSType string - // Optional: RadosPool is the rados pool name,default is rbd - // +optional - RBDPool string - // Optional: RBDUser is the rados user name, default is admin - // +optional - RadosUser string - // Optional: Keyring is the path to key ring for RBDUser, default is /etc/ceph/keyring - // +optional - Keyring string - // Optional: SecretRef is name of the authentication secret for RBDUser, default is nil. - // +optional - SecretRef *LocalObjectReference - // Optional: Defaults to false (read/write). ReadOnly here will force - // the ReadOnly setting in VolumeMounts. - // +optional - ReadOnly bool -} - -// Represents a cinder volume resource in Openstack. A Cinder volume -// must exist before mounting to a container. The volume must also be -// in the same region as the kubelet. Cinder volumes support ownership -// management and SELinux relabeling. -type CinderVolumeSource struct { - // Unique id of the volume used to identify the cinder volume - VolumeID string - // Filesystem type to mount. - // Must be a filesystem type supported by the host operating system. - // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. - // +optional - FSType string - // Optional: Defaults to false (read/write). ReadOnly here will force - // the ReadOnly setting in VolumeMounts. - // +optional - ReadOnly bool -} - -// Represents a Ceph Filesystem mount that lasts the lifetime of a pod -// Cephfs volumes do not support ownership management or SELinux relabeling. -type CephFSVolumeSource struct { - // Required: Monitors is a collection of Ceph monitors - Monitors []string - // Optional: Used as the mounted root, rather than the full Ceph tree, default is / - // +optional - Path string - // Optional: User is the rados user name, default is admin - // +optional - User string - // Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret - // +optional - SecretFile string - // Optional: SecretRef is reference to the authentication secret for User, default is empty. - // +optional - SecretRef *LocalObjectReference - // Optional: Defaults to false (read/write). ReadOnly here will force - // the ReadOnly setting in VolumeMounts. - // +optional - ReadOnly bool -} - -// SecretReference represents a Secret Reference. It has enough information to retrieve secret -// in any namespace -type SecretReference struct { - // Name is unique within a namespace to reference a secret resource. - // +optional - Name string - // Namespace defines the space within which the secret name must be unique. - // +optional - Namespace string -} - -// Represents a Ceph Filesystem mount that lasts the lifetime of a pod -// Cephfs volumes do not support ownership management or SELinux relabeling. -type CephFSPersistentVolumeSource struct { - // Required: Monitors is a collection of Ceph monitors - Monitors []string - // Optional: Used as the mounted root, rather than the full Ceph tree, default is / - // +optional - Path string - // Optional: User is the rados user name, default is admin - // +optional - User string - // Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret - // +optional - SecretFile string - // Optional: SecretRef is reference to the authentication secret for User, default is empty. - // +optional - SecretRef *SecretReference - // Optional: Defaults to false (read/write). ReadOnly here will force - // the ReadOnly setting in VolumeMounts. - // +optional - ReadOnly bool -} - -// Represents a Flocker volume mounted by the Flocker agent. -// One and only one of datasetName and datasetUUID should be set. -// Flocker volumes do not support ownership management or SELinux relabeling. -type FlockerVolumeSource struct { - // Name of the dataset stored as metadata -> name on the dataset for Flocker - // should be considered as deprecated - // +optional - DatasetName string - // UUID of the dataset. This is unique identifier of a Flocker dataset - // +optional - DatasetUUID string -} - -// Represents a volume containing downward API info. -// Downward API volumes support ownership management and SELinux relabeling. -type DownwardAPIVolumeSource struct { - // Items is a list of DownwardAPIVolume file - // +optional - Items []DownwardAPIVolumeFile - // Mode bits to use on created files by default. Must be a value between - // 0 and 0777. - // Directories within the path are not affected by this setting. - // This might be in conflict with other options that affect the file - // mode, like fsGroup, and the result can be other mode bits set. - // +optional - DefaultMode *int32 -} - -// Represents a single file containing information from the downward API -type DownwardAPIVolumeFile struct { - // Required: Path is the relative path name of the file to be created. Must not be absolute or contain the '..' path. Must be utf-8 encoded. The first item of the relative path must not start with '..' - Path string - // Required: Selects a field of the pod: only annotations, labels, name, namespace and uid are supported. - // +optional - FieldRef *ObjectFieldSelector - // Selects a resource of the container: only resources limits and requests - // (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. - // +optional - ResourceFieldRef *ResourceFieldSelector - // Optional: mode bits to use on this file, must be a value between 0 - // and 0777. If not specified, the volume defaultMode will be used. - // This might be in conflict with other options that affect the file - // mode, like fsGroup, and the result can be other mode bits set. - // +optional - Mode *int32 -} - -// Represents downward API info for projecting into a projected volume. -// Note that this is identical to a downwardAPI volume source without the default -// mode. -type DownwardAPIProjection struct { - // Items is a list of DownwardAPIVolume file - // +optional - Items []DownwardAPIVolumeFile -} - -// AzureFile represents an Azure File Service mount on the host and bind mount to the pod. -type AzureFileVolumeSource struct { - // the name of secret that contains Azure Storage Account Name and Key - SecretName string - // Share Name - ShareName string - // Defaults to false (read/write). ReadOnly here will force - // the ReadOnly setting in VolumeMounts. - // +optional - ReadOnly bool -} - -// AzureFile represents an Azure File Service mount on the host and bind mount to the pod. -type AzureFilePersistentVolumeSource struct { - // the name of secret that contains Azure Storage Account Name and Key - SecretName string - // Share Name - ShareName string - // Defaults to false (read/write). ReadOnly here will force - // the ReadOnly setting in VolumeMounts. - // +optional - ReadOnly bool - // the namespace of the secret that contains Azure Storage Account Name and Key - // default is the same as the Pod - // +optional - SecretNamespace *string -} - -// Represents a vSphere volume resource. -type VsphereVirtualDiskVolumeSource struct { - // Path that identifies vSphere volume vmdk - VolumePath string - // Filesystem type to mount. - // Must be a filesystem type supported by the host operating system. - // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. - // +optional - FSType string - // Storage Policy Based Management (SPBM) profile name. - // +optional - StoragePolicyName string - // Storage Policy Based Management (SPBM) profile ID associated with the StoragePolicyName. - // +optional - StoragePolicyID string -} - -// Represents a Photon Controller persistent disk resource. -type PhotonPersistentDiskVolumeSource struct { - // ID that identifies Photon Controller persistent disk - PdID string - // Filesystem type to mount. - // Must be a filesystem type supported by the host operating system. - // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. - FSType string -} - -// PortworxVolumeSource represents a Portworx volume resource. -type PortworxVolumeSource struct { - // VolumeID uniquely identifies a Portworx volume - VolumeID string - // FSType represents the filesystem type to mount - // Must be a filesystem type supported by the host operating system. - // Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. - // +optional - FSType string - // Defaults to false (read/write). ReadOnly here will force - // the ReadOnly setting in VolumeMounts. - // +optional - ReadOnly bool -} - -type AzureDataDiskCachingMode string -type AzureDataDiskKind string - -const ( - AzureDataDiskCachingNone AzureDataDiskCachingMode = "None" - AzureDataDiskCachingReadOnly AzureDataDiskCachingMode = "ReadOnly" - AzureDataDiskCachingReadWrite AzureDataDiskCachingMode = "ReadWrite" - - AzureSharedBlobDisk AzureDataDiskKind = "Shared" - AzureDedicatedBlobDisk AzureDataDiskKind = "Dedicated" - AzureManagedDisk AzureDataDiskKind = "Managed" -) - -// AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. -type AzureDiskVolumeSource struct { - // The Name of the data disk in the blob storage - DiskName string - // The URI of the data disk in the blob storage - DataDiskURI string - // Host Caching mode: None, Read Only, Read Write. - // +optional - CachingMode *AzureDataDiskCachingMode - // Filesystem type to mount. - // Must be a filesystem type supported by the host operating system. - // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. - // +optional - FSType *string - // Defaults to false (read/write). ReadOnly here will force - // the ReadOnly setting in VolumeMounts. - // +optional - ReadOnly *bool - // Expected values Shared: multiple blob disks per storage account Dedicated: single blob disk per storage account Managed: azure managed data disk (only in managed availability set). defaults to shared - Kind *AzureDataDiskKind -} - -// ScaleIOVolumeSource represents a persistent ScaleIO volume -type ScaleIOVolumeSource struct { - // The host address of the ScaleIO API Gateway. - Gateway string - // The name of the storage system as configured in ScaleIO. - System string - // SecretRef references to the secret for ScaleIO user and other - // sensitive information. If this is not provided, Login operation will fail. - SecretRef *LocalObjectReference - // Flag to enable/disable SSL communication with Gateway, default false - // +optional - SSLEnabled bool - // The name of the Protection Domain for the configured storage (defaults to "default"). - // +optional - ProtectionDomain string - // The Storage Pool associated with the protection domain (defaults to "default"). - // +optional - StoragePool string - // Indicates whether the storage for a volume should be thick or thin (defaults to "thin"). - // +optional - StorageMode string - // The name of a volume already created in the ScaleIO system - // that is associated with this volume source. - VolumeName string - // Filesystem type to mount. - // Must be a filesystem type supported by the host operating system. - // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. - // +optional - FSType string - // Defaults to false (read/write). ReadOnly here will force - // the ReadOnly setting in VolumeMounts. - // +optional - ReadOnly bool -} - -// Represents a StorageOS persistent volume resource. -type StorageOSVolumeSource struct { - // VolumeName is the human-readable name of the StorageOS volume. Volume - // names are only unique within a namespace. - VolumeName string - // VolumeNamespace specifies the scope of the volume within StorageOS. If no - // namespace is specified then the Pod's namespace will be used. This allows the - // Kubernetes name scoping to be mirrored within StorageOS for tighter integration. - // Set VolumeName to any name to override the default behaviour. - // Set to "default" if you are not using namespaces within StorageOS. - // Namespaces that do not pre-exist within StorageOS will be created. - // +optional - VolumeNamespace string - // Filesystem type to mount. - // Must be a filesystem type supported by the host operating system. - // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. - // +optional - FSType string - // Defaults to false (read/write). ReadOnly here will force - // the ReadOnly setting in VolumeMounts. - // +optional - ReadOnly bool - // SecretRef specifies the secret to use for obtaining the StorageOS API - // credentials. If not specified, default values will be attempted. - // +optional - SecretRef *LocalObjectReference -} - -// Represents a StorageOS persistent volume resource. -type StorageOSPersistentVolumeSource struct { - // VolumeName is the human-readable name of the StorageOS volume. Volume - // names are only unique within a namespace. - VolumeName string - // VolumeNamespace specifies the scope of the volume within StorageOS. If no - // namespace is specified then the Pod's namespace will be used. This allows the - // Kubernetes name scoping to be mirrored within StorageOS for tighter integration. - // Set VolumeName to any name to override the default behaviour. - // Set to "default" if you are not using namespaces within StorageOS. - // Namespaces that do not pre-exist within StorageOS will be created. - // +optional - VolumeNamespace string - // Filesystem type to mount. - // Must be a filesystem type supported by the host operating system. - // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. - // +optional - FSType string - // Defaults to false (read/write). ReadOnly here will force - // the ReadOnly setting in VolumeMounts. - // +optional - ReadOnly bool - // SecretRef specifies the secret to use for obtaining the StorageOS API - // credentials. If not specified, default values will be attempted. - // +optional - SecretRef *ObjectReference -} - -// Adapts a ConfigMap into a volume. -// -// The contents of the target ConfigMap's Data field will be presented in a -// volume as files using the keys in the Data field as the file names, unless -// the items element is populated with specific mappings of keys to paths. -// ConfigMap volumes support ownership management and SELinux relabeling. -type ConfigMapVolumeSource struct { - LocalObjectReference - // If unspecified, each key-value pair in the Data field of the referenced - // ConfigMap will be projected into the volume as a file whose name is the - // key and content is the value. If specified, the listed keys will be - // projected into the specified paths, and unlisted keys will not be - // present. If a key is specified which is not present in the ConfigMap, - // the volume setup will error unless it is marked optional. Paths must be - // relative and may not contain the '..' path or start with '..'. - // +optional - Items []KeyToPath - // Mode bits to use on created files by default. Must be a value between - // 0 and 0777. - // Directories within the path are not affected by this setting. - // This might be in conflict with other options that affect the file - // mode, like fsGroup, and the result can be other mode bits set. - // +optional - DefaultMode *int32 - // Specify whether the ConfigMap or it's keys must be defined - // +optional - Optional *bool -} - -// Adapts a ConfigMap into a projected volume. -// -// The contents of the target ConfigMap's Data field will be presented in a -// projected volume as files using the keys in the Data field as the file names, -// unless the items element is populated with specific mappings of keys to paths. -// Note that this is identical to a configmap volume source without the default -// mode. -type ConfigMapProjection struct { - LocalObjectReference - // If unspecified, each key-value pair in the Data field of the referenced - // ConfigMap will be projected into the volume as a file whose name is the - // key and content is the value. If specified, the listed keys will be - // projected into the specified paths, and unlisted keys will not be - // present. If a key is specified which is not present in the ConfigMap, - // the volume setup will error unless it is marked optional. Paths must be - // relative and may not contain the '..' path or start with '..'. - // +optional - Items []KeyToPath - // Specify whether the ConfigMap or it's keys must be defined - // +optional - Optional *bool -} - -// Represents a projected volume source -type ProjectedVolumeSource struct { - // list of volume projections - Sources []VolumeProjection - // Mode bits to use on created files by default. Must be a value between - // 0 and 0777. - // Directories within the path are not affected by this setting. - // This might be in conflict with other options that affect the file - // mode, like fsGroup, and the result can be other mode bits set. - // +optional - DefaultMode *int32 -} - -// Projection that may be projected along with other supported volume types -type VolumeProjection struct { - // all types below are the supported types for projection into the same volume - - // information about the secret data to project - Secret *SecretProjection - // information about the downwardAPI data to project - DownwardAPI *DownwardAPIProjection - // information about the configMap data to project - ConfigMap *ConfigMapProjection -} - -// Maps a string key to a path within a volume. -type KeyToPath struct { - // The key to project. - Key string - - // The relative path of the file to map the key to. - // May not be an absolute path. - // May not contain the path element '..'. - // May not start with the string '..'. - Path string - // Optional: mode bits to use on this file, should be a value between 0 - // and 0777. If not specified, the volume defaultMode will be used. - // This might be in conflict with other options that affect the file - // mode, like fsGroup, and the result can be other mode bits set. - // +optional - Mode *int32 -} - -// Local represents directly-attached storage with node affinity -type LocalVolumeSource struct { - // The full path to the volume on the node - // For alpha, this path must be a directory - // Once block as a source is supported, then this path can point to a block device - Path string -} - -// ContainerPort represents a network port in a single container -type ContainerPort struct { - // Optional: If specified, this must be an IANA_SVC_NAME Each named port - // in a pod must have a unique name. - // +optional - Name string - // Optional: If specified, this must be a valid port number, 0 < x < 65536. - // If HostNetwork is specified, this must match ContainerPort. - // +optional - HostPort int32 - // Required: This must be a valid port number, 0 < x < 65536. - ContainerPort int32 - // Required: Supports "TCP" and "UDP". - // +optional - Protocol Protocol - // Optional: What host IP to bind the external port to. - // +optional - HostIP string -} - -// VolumeMount describes a mounting of a Volume within a container. -type VolumeMount struct { - // Required: This must match the Name of a Volume [above]. - Name string - // Optional: Defaults to false (read-write). - // +optional - ReadOnly bool - // Required. Must not contain ':'. - MountPath string - // Path within the volume from which the container's volume should be mounted. - // Defaults to "" (volume's root). - // +optional - SubPath string - // mountPropagation determines how mounts are propagated from the host - // to container and the other way around. - // When not set, MountPropagationHostToContainer is used. - // This field is alpha in 1.8 and can be reworked or removed in a future - // release. - // +optional - MountPropagation *MountPropagationMode -} - -// MountPropagationMode describes mount propagation. -type MountPropagationMode string - -const ( - // MountPropagationHostToContainer means that the volume in a container will - // receive new mounts from the host or other containers, but filesystems - // mounted inside the container won't be propagated to the host or other - // containers. - // Note that this mode is recursively applied to all mounts in the volume - // ("rslave" in Linux terminology). - MountPropagationHostToContainer MountPropagationMode = "HostToContainer" - // MountPropagationBidirectional means that the volume in a container will - // receive new mounts from the host or other containers, and its own mounts - // will be propagated from the container to the host or other containers. - // Note that this mode is recursively applied to all mounts in the volume - // ("rshared" in Linux terminology). - MountPropagationBidirectional MountPropagationMode = "Bidirectional" -) - -// EnvVar represents an environment variable present in a Container. -type EnvVar struct { - // Required: This must be a C_IDENTIFIER. - Name string - // Optional: no more than one of the following may be specified. - // Optional: Defaults to ""; variable references $(VAR_NAME) are expanded - // using the previous defined environment variables in the container and - // any service environment variables. If a variable cannot be resolved, - // the reference in the input string will be unchanged. The $(VAR_NAME) - // syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped - // references will never be expanded, regardless of whether the variable - // exists or not. - // +optional - Value string - // Optional: Specifies a source the value of this var should come from. - // +optional - ValueFrom *EnvVarSource -} - -// EnvVarSource represents a source for the value of an EnvVar. -// Only one of its fields may be set. -type EnvVarSource struct { - // Selects a field of the pod: supports metadata.name, metadata.namespace, metadata.labels, metadata.annotations, - // metadata.uid, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP. - // +optional - FieldRef *ObjectFieldSelector - // Selects a resource of the container: only resources limits and requests - // (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. - // +optional - ResourceFieldRef *ResourceFieldSelector - // Selects a key of a ConfigMap. - // +optional - ConfigMapKeyRef *ConfigMapKeySelector - // Selects a key of a secret in the pod's namespace. - // +optional - SecretKeyRef *SecretKeySelector -} - -// ObjectFieldSelector selects an APIVersioned field of an object. -type ObjectFieldSelector struct { - // Required: Version of the schema the FieldPath is written in terms of. - // If no value is specified, it will be defaulted to the APIVersion of the - // enclosing object. - APIVersion string - // Required: Path of the field to select in the specified API version - FieldPath string -} - -// ResourceFieldSelector represents container resources (cpu, memory) and their output format -type ResourceFieldSelector struct { - // Container name: required for volumes, optional for env vars - // +optional - ContainerName string - // Required: resource to select - Resource string - // Specifies the output format of the exposed resources, defaults to "1" - // +optional - Divisor resource.Quantity -} - -// Selects a key from a ConfigMap. -type ConfigMapKeySelector struct { - // The ConfigMap to select from. - LocalObjectReference - // The key to select. - Key string - // Specify whether the ConfigMap or it's key must be defined - // +optional - Optional *bool -} - -// SecretKeySelector selects a key of a Secret. -type SecretKeySelector struct { - // The name of the secret in the pod's namespace to select from. - LocalObjectReference - // The key of the secret to select from. Must be a valid secret key. - Key string - // Specify whether the Secret or it's key must be defined - // +optional - Optional *bool -} - -// EnvFromSource represents the source of a set of ConfigMaps -type EnvFromSource struct { - // An optional identifier to prepend to each key in the ConfigMap. - // +optional - Prefix string - // The ConfigMap to select from. - //+optional - ConfigMapRef *ConfigMapEnvSource - // The Secret to select from. - //+optional - SecretRef *SecretEnvSource -} - -// ConfigMapEnvSource selects a ConfigMap to populate the environment -// variables with. -// -// The contents of the target ConfigMap's Data field will represent the -// key-value pairs as environment variables. -type ConfigMapEnvSource struct { - // The ConfigMap to select from. - LocalObjectReference - // Specify whether the ConfigMap must be defined - // +optional - Optional *bool -} - -// SecretEnvSource selects a Secret to populate the environment -// variables with. -// -// The contents of the target Secret's Data field will represent the -// key-value pairs as environment variables. -type SecretEnvSource struct { - // The Secret to select from. - LocalObjectReference - // Specify whether the Secret must be defined - // +optional - Optional *bool -} - -// HTTPHeader describes a custom header to be used in HTTP probes -type HTTPHeader struct { - // The header field name - Name string - // The header field value - Value string -} - -// HTTPGetAction describes an action based on HTTP Get requests. -type HTTPGetAction struct { - // Optional: Path to access on the HTTP server. - // +optional - Path string - // Required: Name or number of the port to access on the container. - // +optional - Port intstr.IntOrString - // Optional: Host name to connect to, defaults to the pod IP. You - // probably want to set "Host" in httpHeaders instead. - // +optional - Host string - // Optional: Scheme to use for connecting to the host, defaults to HTTP. - // +optional - Scheme URIScheme - // Optional: Custom headers to set in the request. HTTP allows repeated headers. - // +optional - HTTPHeaders []HTTPHeader -} - -// URIScheme identifies the scheme used for connection to a host for Get actions -type URIScheme string - -const ( - // URISchemeHTTP means that the scheme used will be http:// - URISchemeHTTP URIScheme = "HTTP" - // URISchemeHTTPS means that the scheme used will be https:// - URISchemeHTTPS URIScheme = "HTTPS" -) - -// TCPSocketAction describes an action based on opening a socket -type TCPSocketAction struct { - // Required: Port to connect to. - // +optional - Port intstr.IntOrString - // Optional: Host name to connect to, defaults to the pod IP. - // +optional - Host string -} - -// ExecAction describes a "run in container" action. -type ExecAction struct { - // Command is the command line to execute inside the container, the working directory for the - // command is root ('/') in the container's filesystem. The command is simply exec'd, it is - // not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use - // a shell, you need to explicitly call out to that shell. - // +optional - Command []string -} - -// Probe describes a health check to be performed against a container to determine whether it is -// alive or ready to receive traffic. -type Probe struct { - // The action taken to determine the health of a container - Handler - // Length of time before health checking is activated. In seconds. - // +optional - InitialDelaySeconds int32 - // Length of time before health checking times out. In seconds. - // +optional - TimeoutSeconds int32 - // How often (in seconds) to perform the probe. - // +optional - PeriodSeconds int32 - // Minimum consecutive successes for the probe to be considered successful after having failed. - // Must be 1 for liveness. - // +optional - SuccessThreshold int32 - // Minimum consecutive failures for the probe to be considered failed after having succeeded. - // +optional - FailureThreshold int32 -} - -// PullPolicy describes a policy for if/when to pull a container image -type PullPolicy string - -const ( - // PullAlways means that kubelet always attempts to pull the latest image. Container will fail If the pull fails. - PullAlways PullPolicy = "Always" - // PullNever means that kubelet never pulls an image, but only uses a local image. Container will fail if the image isn't present - PullNever PullPolicy = "Never" - // PullIfNotPresent means that kubelet pulls if the image isn't present on disk. Container will fail if the image isn't present and the pull fails. - PullIfNotPresent PullPolicy = "IfNotPresent" -) - -// TerminationMessagePolicy describes how termination messages are retrieved from a container. -type TerminationMessagePolicy string - -const ( - // TerminationMessageReadFile is the default behavior and will set the container status message to - // the contents of the container's terminationMessagePath when the container exits. - TerminationMessageReadFile TerminationMessagePolicy = "File" - // TerminationMessageFallbackToLogsOnError will read the most recent contents of the container logs - // for the container status message when the container exits with an error and the - // terminationMessagePath has no contents. - TerminationMessageFallbackToLogsOnError TerminationMessagePolicy = "FallbackToLogsOnError" -) - -// Capability represent POSIX capabilities type -type Capability string - -// Capabilities represent POSIX capabilities that can be added or removed to a running container. -type Capabilities struct { - // Added capabilities - // +optional - Add []Capability - // Removed capabilities - // +optional - Drop []Capability -} - -// ResourceRequirements describes the compute resource requirements. -type ResourceRequirements struct { - // Limits describes the maximum amount of compute resources allowed. - // +optional - Limits ResourceList - // Requests describes the minimum amount of compute resources required. - // If Request is omitted for a container, it defaults to Limits if that is explicitly specified, - // otherwise to an implementation-defined value - // +optional - Requests ResourceList -} - -// Container represents a single container that is expected to be run on the host. -type Container struct { - // Required: This must be a DNS_LABEL. Each container in a pod must - // have a unique name. - Name string - // Required. - Image string - // Optional: The docker image's entrypoint is used if this is not provided; cannot be updated. - // Variable references $(VAR_NAME) are expanded using the container's environment. If a variable - // cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax - // can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, - // regardless of whether the variable exists or not. - // +optional - Command []string - // Optional: The docker image's cmd is used if this is not provided; cannot be updated. - // Variable references $(VAR_NAME) are expanded using the container's environment. If a variable - // cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax - // can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, - // regardless of whether the variable exists or not. - // +optional - Args []string - // Optional: Defaults to Docker's default. - // +optional - WorkingDir string - // +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 - // 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. - // Cannot be updated. - // +optional - EnvFrom []EnvFromSource - // +optional - Env []EnvVar - // Compute resource requirements. - // +optional - Resources ResourceRequirements - // +optional - VolumeMounts []VolumeMount - // +optional - LivenessProbe *Probe - // +optional - ReadinessProbe *Probe - // +optional - Lifecycle *Lifecycle - // Required. - // +optional - TerminationMessagePath string - // +optional - TerminationMessagePolicy TerminationMessagePolicy - // Required: Policy for pulling images for this container - ImagePullPolicy PullPolicy - // Optional: SecurityContext defines the security options the container should be run with. - // If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext. - // +optional - SecurityContext *SecurityContext - - // Variables for interactive containers, these have very specialized use-cases (e.g. debugging) - // and shouldn't be used for general purpose containers. - // +optional - Stdin bool - // +optional - StdinOnce bool - // +optional - TTY bool -} - -// Handler defines a specific action that should be taken -// TODO: pass structured data to these actions, and document that data here. -type Handler struct { - // One and only one of the following should be specified. - // Exec specifies the action to take. - // +optional - Exec *ExecAction - // HTTPGet specifies the http request to perform. - // +optional - HTTPGet *HTTPGetAction - // TCPSocket specifies an action involving a TCP port. - // TODO: implement a realistic TCP lifecycle hook - // +optional - TCPSocket *TCPSocketAction -} - -// Lifecycle describes actions that the management system should take in response to container lifecycle -// events. For the PostStart and PreStop lifecycle handlers, management of the container blocks -// until the action is complete, unless the container process fails, in which case the handler is aborted. -type Lifecycle struct { - // PostStart is called immediately after a container is created. If the handler fails, the container - // is terminated and restarted. - // +optional - PostStart *Handler - // PreStop is called immediately before a container is terminated. The reason for termination is - // passed to the handler. Regardless of the outcome of the handler, the container is eventually terminated. - // +optional - PreStop *Handler -} - -// The below types are used by kube_client and api_server. - -type ConditionStatus string - -// These are valid condition statuses. "ConditionTrue" means a resource is in the condition; -// "ConditionFalse" means a resource is not in the condition; "ConditionUnknown" means kubernetes -// can't decide if a resource is in the condition or not. In the future, we could add other -// intermediate conditions, e.g. ConditionDegraded. -const ( - ConditionTrue ConditionStatus = "True" - ConditionFalse ConditionStatus = "False" - ConditionUnknown ConditionStatus = "Unknown" -) - -type ContainerStateWaiting struct { - // A brief CamelCase string indicating details about why the container is in waiting state. - // +optional - Reason string - // A human-readable message indicating details about why the container is in waiting state. - // +optional - Message string -} - -type ContainerStateRunning struct { - // +optional - StartedAt metav1.Time -} - -type ContainerStateTerminated struct { - ExitCode int32 - // +optional - Signal int32 - // +optional - Reason string - // +optional - Message string - // +optional - StartedAt metav1.Time - // +optional - FinishedAt metav1.Time - // +optional - ContainerID string -} - -// ContainerState holds a possible state of container. -// Only one of its members may be specified. -// If none of them is specified, the default one is ContainerStateWaiting. -type ContainerState struct { - // +optional - Waiting *ContainerStateWaiting - // +optional - Running *ContainerStateRunning - // +optional - Terminated *ContainerStateTerminated -} - -type ContainerStatus struct { - // Each container in a pod must have a unique name. - Name string - // +optional - State ContainerState - // +optional - LastTerminationState ContainerState - // Ready specifies whether the container has passed its readiness check. - Ready bool - // Note that this is calculated from dead containers. But those containers are subject to - // garbage collection. This value will get capped at 5 by GC. - RestartCount int32 - Image string - ImageID string - // +optional - ContainerID string -} - -// PodPhase is a label for the condition of a pod at the current time. -type PodPhase string - -// These are the valid statuses of pods. -const ( - // PodPending means the pod has been accepted by the system, but one or more of the containers - // has not been started. This includes time before being bound to a node, as well as time spent - // pulling images onto the host. - PodPending PodPhase = "Pending" - // PodRunning means the pod has been bound to a node and all of the containers have been started. - // At least one container is still running or is in the process of being restarted. - PodRunning PodPhase = "Running" - // PodSucceeded means that all containers in the pod have voluntarily terminated - // with a container exit code of 0, and the system is not going to restart any of these containers. - PodSucceeded PodPhase = "Succeeded" - // PodFailed means that all containers in the pod have terminated, and at least one container has - // terminated in a failure (exited with a non-zero exit code or was stopped by the system). - PodFailed PodPhase = "Failed" - // PodUnknown means that 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. - PodUnknown PodPhase = "Unknown" -) - -type PodConditionType string - -// These are valid conditions of pod. -const ( - // PodScheduled represents status of the scheduling process for this pod. - PodScheduled PodConditionType = "PodScheduled" - // PodReady means the pod is able to service requests and should be added to the - // load balancing pools of all matching services. - PodReady PodConditionType = "Ready" - // PodInitialized means that all init containers in the pod have started successfully. - PodInitialized PodConditionType = "Initialized" - // PodReasonUnschedulable reason in PodScheduled PodCondition means that the scheduler - // can't schedule the pod right now, for example due to insufficient resources in the cluster. - PodReasonUnschedulable = "Unschedulable" -) - -type PodCondition struct { - Type PodConditionType - Status ConditionStatus - // +optional - LastProbeTime metav1.Time - // +optional - LastTransitionTime metav1.Time - // +optional - Reason string - // +optional - Message string -} - -// RestartPolicy describes how the container should be restarted. -// Only one of the following restart policies may be specified. -// If none of the following policies is specified, the default one -// is RestartPolicyAlways. -type RestartPolicy string - -const ( - RestartPolicyAlways RestartPolicy = "Always" - RestartPolicyOnFailure RestartPolicy = "OnFailure" - RestartPolicyNever RestartPolicy = "Never" -) - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// PodList is a list of Pods. -type PodList struct { - metav1.TypeMeta - // +optional - metav1.ListMeta - - Items []Pod -} - -// DNSPolicy defines how a pod's DNS will be configured. -type DNSPolicy string - -const ( - // DNSClusterFirstWithHostNet indicates that the pod should use cluster DNS - // first, if it is available, then fall back on the default - // (as determined by kubelet) DNS settings. - DNSClusterFirstWithHostNet DNSPolicy = "ClusterFirstWithHostNet" - - // DNSClusterFirst indicates that the pod should use cluster DNS - // first unless hostNetwork is true, if it is available, then - // fall back on the default (as determined by kubelet) DNS settings. - DNSClusterFirst DNSPolicy = "ClusterFirst" - - // DNSDefault indicates that the pod should use the default (as - // determined by kubelet) DNS settings. - DNSDefault DNSPolicy = "Default" -) - -// A node selector represents the union of the results of one or more label queries -// over a set of nodes; that is, it represents the OR of the selectors represented -// by the node selector terms. -type NodeSelector struct { - //Required. A list of node selector terms. The terms are ORed. - NodeSelectorTerms []NodeSelectorTerm -} - -// A null or empty node selector term matches no objects. -type NodeSelectorTerm struct { - //Required. A list of node selector requirements. The requirements are ANDed. - MatchExpressions []NodeSelectorRequirement -} - -// A node selector requirement is a selector that contains values, a key, and an operator -// that relates the key and values. -type NodeSelectorRequirement struct { - // The label key that the selector applies to. - Key string - // Represents a key's relationship to a set of values. - // Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - Operator NodeSelectorOperator - // An array of string values. If the operator is In or NotIn, - // the values array must be non-empty. If the operator is Exists or DoesNotExist, - // the values array must be empty. If the operator is Gt or Lt, the values - // array must have a single element, which will be interpreted as an integer. - // This array is replaced during a strategic merge patch. - // +optional - Values []string -} - -// A node selector operator is the set of operators that can be used in -// a node selector requirement. -type NodeSelectorOperator string - -const ( - NodeSelectorOpIn NodeSelectorOperator = "In" - NodeSelectorOpNotIn NodeSelectorOperator = "NotIn" - NodeSelectorOpExists NodeSelectorOperator = "Exists" - NodeSelectorOpDoesNotExist NodeSelectorOperator = "DoesNotExist" - NodeSelectorOpGt NodeSelectorOperator = "Gt" - NodeSelectorOpLt NodeSelectorOperator = "Lt" -) - -// Affinity is a group of affinity scheduling rules. -type Affinity struct { - // Describes node affinity scheduling rules for the pod. - // +optional - NodeAffinity *NodeAffinity - // Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). - // +optional - PodAffinity *PodAffinity - // Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). - // +optional - PodAntiAffinity *PodAntiAffinity -} - -// Pod affinity is a group of inter pod affinity scheduling rules. -type PodAffinity struct { - // NOT YET IMPLEMENTED. TODO: Uncomment field once it is implemented. - // If the affinity requirements specified by this field are not met at - // scheduling time, the pod will not be scheduled onto the node. - // If the affinity requirements specified by this field cease to be met - // at some point during pod execution (e.g. due to a pod label update), the - // system will try to eventually evict the pod from its node. - // When there are multiple elements, the lists of nodes corresponding to each - // podAffinityTerm are intersected, i.e. all terms must be satisfied. - // +optional - // RequiredDuringSchedulingRequiredDuringExecution []PodAffinityTerm - - // If the affinity requirements specified by this field are not met at - // scheduling time, the pod will not be scheduled onto the node. - // If the affinity requirements specified by this field cease to be met - // at some point during pod execution (e.g. due to a pod label update), the - // system may or may not try to eventually evict the pod from its node. - // When there are multiple elements, the lists of nodes corresponding to each - // podAffinityTerm are intersected, i.e. all terms must be satisfied. - // +optional - RequiredDuringSchedulingIgnoredDuringExecution []PodAffinityTerm - // The scheduler will prefer to schedule pods to nodes that satisfy - // the affinity expressions specified by this field, but it may choose - // a node that violates one or more of the expressions. The node that is - // most preferred is the one with the greatest sum of weights, i.e. - // for each node that meets all of the scheduling requirements (resource - // request, requiredDuringScheduling affinity expressions, etc.), - // compute a sum by iterating through the elements of this field and adding - // "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the - // node(s) with the highest sum are the most preferred. - // +optional - PreferredDuringSchedulingIgnoredDuringExecution []WeightedPodAffinityTerm -} - -// Pod anti affinity is a group of inter pod anti affinity scheduling rules. -type PodAntiAffinity struct { - // NOT YET IMPLEMENTED. TODO: Uncomment field once it is implemented. - // If the anti-affinity requirements specified by this field are not met at - // scheduling time, the pod will not be scheduled onto the node. - // If the anti-affinity requirements specified by this field cease to be met - // at some point during pod execution (e.g. due to a pod label update), the - // system will try to eventually evict the pod from its node. - // When there are multiple elements, the lists of nodes corresponding to each - // podAffinityTerm are intersected, i.e. all terms must be satisfied. - // +optional - // RequiredDuringSchedulingRequiredDuringExecution []PodAffinityTerm - - // If the anti-affinity requirements specified by this field are not met at - // scheduling time, the pod will not be scheduled onto the node. - // If the anti-affinity requirements specified by this field cease to be met - // at some point during pod execution (e.g. due to a pod label update), the - // system may or may not try to eventually evict the pod from its node. - // When there are multiple elements, the lists of nodes corresponding to each - // podAffinityTerm are intersected, i.e. all terms must be satisfied. - // +optional - RequiredDuringSchedulingIgnoredDuringExecution []PodAffinityTerm - // The scheduler will prefer to schedule pods to nodes that satisfy - // the anti-affinity expressions specified by this field, but it may choose - // a node that violates one or more of the expressions. The node that is - // most preferred is the one with the greatest sum of weights, i.e. - // for each node that meets all of the scheduling requirements (resource - // request, requiredDuringScheduling anti-affinity expressions, etc.), - // compute a sum by iterating through the elements of this field and adding - // "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the - // node(s) with the highest sum are the most preferred. - // +optional - PreferredDuringSchedulingIgnoredDuringExecution []WeightedPodAffinityTerm -} - -// The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) -type WeightedPodAffinityTerm struct { - // weight associated with matching the corresponding podAffinityTerm, - // in the range 1-100. - Weight int32 - // Required. A pod affinity term, associated with the corresponding weight. - PodAffinityTerm PodAffinityTerm -} - -// Defines a set of pods (namely those matching the labelSelector -// relative to the given namespace(s)) that this pod should be -// co-located (affinity) or not co-located (anti-affinity) with, -// where co-located is defined as running on a node whose value of -// the label with key matches that of any node on which -// a pod of the set of pods is running. -type PodAffinityTerm struct { - // A label query over a set of resources, in this case pods. - // +optional - LabelSelector *metav1.LabelSelector - // namespaces specifies which namespaces the labelSelector applies to (matches against); - // null or empty list means "this pod's namespace" - Namespaces []string - // This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - // the labelSelector in the specified namespaces, where co-located is defined as running on a node - // whose value of the label with key topologyKey matches that of any node on which any of the - // selected pods is running. - // Empty topologyKey is not allowed. - // +optional - TopologyKey string -} - -// Node affinity is a group of node affinity scheduling rules. -type NodeAffinity struct { - // NOT YET IMPLEMENTED. TODO: Uncomment field once it is implemented. - // If the affinity requirements specified by this field are not met at - // scheduling time, the pod will not be scheduled onto the node. - // If the affinity requirements specified by this field cease to be met - // at some point during pod execution (e.g. due to an update), the system - // will try to eventually evict the pod from its node. - // +optional - // RequiredDuringSchedulingRequiredDuringExecution *NodeSelector - - // If the affinity requirements specified by this field are not met at - // scheduling time, the pod will not be scheduled onto the node. - // If the affinity requirements specified by this field cease to be met - // at some point during pod execution (e.g. due to an update), the system - // may or may not try to eventually evict the pod from its node. - // +optional - RequiredDuringSchedulingIgnoredDuringExecution *NodeSelector - // The scheduler will prefer to schedule pods to nodes that satisfy - // the affinity expressions specified by this field, but it may choose - // a node that violates one or more of the expressions. The node that is - // most preferred is the one with the greatest sum of weights, i.e. - // for each node that meets all of the scheduling requirements (resource - // request, requiredDuringScheduling affinity expressions, etc.), - // compute a sum by iterating through the elements of this field and adding - // "weight" to the sum if the node matches the corresponding matchExpressions; the - // node(s) with the highest sum are the most preferred. - // +optional - PreferredDuringSchedulingIgnoredDuringExecution []PreferredSchedulingTerm -} - -// An empty preferred scheduling term matches all objects with implicit weight 0 -// (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). -type PreferredSchedulingTerm struct { - // Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. - Weight int32 - // A node selector term, associated with the corresponding weight. - Preference NodeSelectorTerm -} - -// The node this Taint is attached to has the "effect" on -// any pod that does not tolerate the Taint. -type Taint struct { - // Required. The taint key to be applied to a node. - Key string - // Required. The taint value corresponding to the taint key. - // +optional - Value string - // Required. The effect of the taint on pods - // that do not tolerate the taint. - // Valid effects are NoSchedule, PreferNoSchedule and NoExecute. - Effect TaintEffect - // TimeAdded represents the time at which the taint was added. - // It is only written for NoExecute taints. - // +optional - TimeAdded *metav1.Time -} - -type TaintEffect string - -const ( - // Do not allow new pods to schedule onto the node unless they tolerate the taint, - // but allow all pods submitted to Kubelet without going through the scheduler - // to start, and allow all already-running pods to continue running. - // Enforced by the scheduler. - TaintEffectNoSchedule TaintEffect = "NoSchedule" - // Like TaintEffectNoSchedule, but the scheduler tries not to schedule - // new pods onto the node, rather than prohibiting new pods from scheduling - // onto the node entirely. Enforced by the scheduler. - TaintEffectPreferNoSchedule TaintEffect = "PreferNoSchedule" - // NOT YET IMPLEMENTED. TODO: Uncomment field once it is implemented. - // Like TaintEffectNoSchedule, but additionally do not allow pods submitted to - // Kubelet without going through the scheduler to start. - // Enforced by Kubelet and the scheduler. - // TaintEffectNoScheduleNoAdmit TaintEffect = "NoScheduleNoAdmit" - - // Evict any already-running pods that do not tolerate the taint. - // Currently enforced by NodeController. - TaintEffectNoExecute TaintEffect = "NoExecute" -) - -// The pod this Toleration is attached to tolerates any taint that matches -// the triple using the matching operator . -type Toleration struct { - // 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. - // +optional - Key string - // Operator represents a key's relationship to the value. - // Valid operators are Exists and Equal. Defaults to Equal. - // Exists is equivalent to wildcard for value, so that a pod can - // tolerate all taints of a particular category. - // +optional - Operator TolerationOperator - // Value is the taint value the toleration matches to. - // If the operator is Exists, the value should be empty, otherwise just a regular string. - // +optional - Value string - // Effect indicates the taint effect to match. Empty means match all taint effects. - // When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. - // +optional - Effect TaintEffect - // TolerationSeconds represents the period of time the toleration (which must be - // of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, - // it is not set, which means tolerate the taint forever (do not evict). Zero and - // negative values will be treated as 0 (evict immediately) by the system. - // +optional - TolerationSeconds *int64 -} - -// A toleration operator is the set of operators that can be used in a toleration. -type TolerationOperator string - -const ( - TolerationOpExists TolerationOperator = "Exists" - TolerationOpEqual TolerationOperator = "Equal" -) - -// PodSpec is a description of a pod -type PodSpec struct { - Volumes []Volume - // List of initialization containers belonging to the pod. - InitContainers []Container - // List of containers belonging to the pod. - Containers []Container - // +optional - RestartPolicy RestartPolicy - // Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. - // Value must be non-negative integer. The value zero indicates delete immediately. - // If this value is nil, the default grace period will be used instead. - // The grace period is the duration in seconds after the processes running in the pod are sent - // a termination signal and the time when the processes are forcibly halted with a kill signal. - // Set this value longer than the expected cleanup time for your process. - // +optional - TerminationGracePeriodSeconds *int64 - // Optional duration in seconds relative to the StartTime that the pod may be active on a node - // before the system actively tries to terminate the pod; value must be positive integer - // +optional - ActiveDeadlineSeconds *int64 - // Required: Set DNS policy. - // +optional - DNSPolicy DNSPolicy - // NodeSelector is a selector which must be true for the pod to fit on a node - // +optional - NodeSelector map[string]string - - // ServiceAccountName is the name of the ServiceAccount to use to run this pod - // The pod will be allowed to use secrets referenced by the ServiceAccount - ServiceAccountName string - // AutomountServiceAccountToken indicates whether a service account token should be automatically mounted. - // +optional - AutomountServiceAccountToken *bool - - // NodeName is a request to schedule this pod onto a specific node. If it is non-empty, - // the scheduler simply schedules this pod onto that node, assuming that it fits resource - // requirements. - // +optional - NodeName string - // SecurityContext holds pod-level security attributes and common container settings. - // Optional: Defaults to empty. See type description for default values of each field. - // +optional - SecurityContext *PodSecurityContext - // ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. - // If specified, these secrets will be passed to individual puller implementations for them to use. For example, - // in the case of docker, only DockerConfig type secrets are honored. - // +optional - ImagePullSecrets []LocalObjectReference - // Specifies the hostname of the Pod. - // If not specified, the pod's hostname will be set to a system-defined value. - // +optional - Hostname string - // If specified, the fully qualified Pod hostname will be "...svc.". - // If not specified, the pod will not have a domainname at all. - // +optional - Subdomain string - // If specified, the pod's scheduling constraints - // +optional - Affinity *Affinity - // If specified, the pod will be dispatched by specified scheduler. - // If not specified, the pod will be dispatched by default scheduler. - // +optional - SchedulerName string - // If specified, the pod's tolerations. - // +optional - Tolerations []Toleration - // HostAliases is an optional list of hosts and IPs that will be injected into the pod's hosts - // file if specified. This is only valid for non-hostNetwork pods. - // +optional - HostAliases []HostAlias - // If specified, indicates the pod's priority. "SYSTEM" is a special keyword - // which indicates the highest priority. Any other name must be defined by - // creating a PriorityClass object with that name. - // If not specified, the pod priority will be default or zero if there is no - // default. - // +optional - PriorityClassName string - // The priority value. Various system components use this field to find the - // priority of the pod. 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. - // +optional - Priority *int32 -} - -// HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the -// pod's hosts file. -type HostAlias struct { - IP string - Hostnames []string -} - -// Sysctl defines a kernel parameter to be set -type Sysctl struct { - // Name of a property to set - Name string - // Value of a property to set - Value 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. -type PodSecurityContext struct { - // Use the host's network namespace. If this option is set, the ports that will be - // used must be specified. - // Optional: Default to false - // +k8s:conversion-gen=false - // +optional - HostNetwork bool - // Use the host's pid namespace. - // Optional: Default to false. - // +k8s:conversion-gen=false - // +optional - HostPID bool - // Use the host's ipc namespace. - // Optional: Default to false. - // +k8s:conversion-gen=false - // +optional - HostIPC bool - // 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. - // +optional - SELinuxOptions *SELinuxOptions - // The UID to run the entrypoint of the container process. - // Defaults to user specified in image metadata if unspecified. - // May also be set in SecurityContext. If set in both SecurityContext and - // PodSecurityContext, the value specified in SecurityContext takes precedence - // for that container. - // +optional - RunAsUser *int64 - // 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 SecurityContext. If set in both SecurityContext and - // PodSecurityContext, the value specified in SecurityContext takes precedence. - // +optional - RunAsNonRoot *bool - // A list of groups applied to the first process run in each container, in addition - // to the container's primary GID. If unspecified, no groups will be added to - // any container. - // +optional - SupplementalGroups []int64 - // A special supplemental group that applies to all containers in a pod. - // Some volume types allow the Kubelet to change the ownership of that volume - // to be owned by the pod: - // - // 1. The owning GID will be the FSGroup - // 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) - // 3. The permission bits are OR'd with rw-rw---- - // - // If unset, the Kubelet will not modify the ownership and permissions of any volume. - // +optional - FSGroup *int64 -} - -// PodQOSClass defines the supported qos classes of Pods. -type PodQOSClass string - -const ( - // PodQOSGuaranteed is the Guaranteed qos class. - PodQOSGuaranteed PodQOSClass = "Guaranteed" - // PodQOSBurstable is the Burstable qos class. - PodQOSBurstable PodQOSClass = "Burstable" - // PodQOSBestEffort is the BestEffort qos class. - PodQOSBestEffort PodQOSClass = "BestEffort" -) - -// PodStatus represents information about the status of a pod. Status may trail the actual -// state of a system. -type PodStatus struct { - // +optional - Phase PodPhase - // +optional - Conditions []PodCondition - // A human readable message indicating details about why the pod is in this state. - // +optional - Message string - // A brief CamelCase message indicating details about why the pod is in this state. e.g. 'Evicted' - // +optional - Reason string - - // +optional - HostIP string - // +optional - PodIP string - - // 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. - // +optional - StartTime *metav1.Time - // +optional - QOSClass PodQOSClass - - // The list has one entry per init container in the manifest. The most recent successful - // init container will have ready = true, the most recently started container will have - // startTime set. - // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-and-container-status - InitContainerStatuses []ContainerStatus - // The list has one entry per container in the manifest. Each entry is - // currently the output of `docker inspect`. This output format is *not* - // final and should not be relied upon. - // TODO: Make real decisions about what our info should look like. Re-enable fuzz test - // when we have done this. - // +optional - ContainerStatuses []ContainerStatus -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// PodStatusResult is a wrapper for PodStatus returned by kubelet that can be encode/decoded -type PodStatusResult struct { - metav1.TypeMeta - // +optional - metav1.ObjectMeta - // Status represents the current information about a pod. This data may not be up - // to date. - // +optional - Status PodStatus -} - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// Pod is a collection of containers, used as either input (create, update) or as output (list, get). -type Pod struct { - metav1.TypeMeta - // +optional - metav1.ObjectMeta - - // Spec defines the behavior of a pod. - // +optional - Spec PodSpec - - // Status represents the current information about a pod. This data may not be up - // to date. - // +optional - Status PodStatus -} - -// PodTemplateSpec describes the data a pod should have when created from a template -type PodTemplateSpec struct { - // Metadata of the pods created from this template. - // +optional - metav1.ObjectMeta - - // Spec defines the behavior of a pod. - // +optional - Spec PodSpec -} - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// PodTemplate describes a template for creating copies of a predefined pod. -type PodTemplate struct { - metav1.TypeMeta - // +optional - metav1.ObjectMeta - - // Template defines the pods that will be created from this pod template - // +optional - Template PodTemplateSpec -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// PodTemplateList is a list of PodTemplates. -type PodTemplateList struct { - metav1.TypeMeta - // +optional - metav1.ListMeta - - Items []PodTemplate -} - -// ReplicationControllerSpec is the specification of a replication controller. -// As the internal representation of a replication controller, it may have either -// a TemplateRef or a Template set. -type ReplicationControllerSpec struct { - // Replicas is the number of desired replicas. - Replicas int32 - - // 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 - MinReadySeconds int32 - - // Selector is a label query over pods that should match the Replicas count. - Selector map[string]string - - // TemplateRef is a reference to an object that describes the pod that will be created if - // insufficient replicas are detected. This reference is ignored if a Template is set. - // Must be set before converting to a versioned API object - // +optional - //TemplateRef *ObjectReference - - // Template is the object that describes the pod that will be created if - // insufficient replicas are detected. Internally, this takes precedence over a - // TemplateRef. - // +optional - Template *PodTemplateSpec -} - -// ReplicationControllerStatus represents the current status of a replication -// controller. -type ReplicationControllerStatus struct { - // Replicas is the number of actual replicas. - Replicas int32 - - // The number of pods that have labels matching the labels of the pod template of the replication controller. - // +optional - FullyLabeledReplicas int32 - - // The number of ready replicas for this replication controller. - // +optional - ReadyReplicas int32 - - // The number of available replicas (ready for at least minReadySeconds) for this replication controller. - // +optional - AvailableReplicas int32 - - // ObservedGeneration is the most recent generation observed by the controller. - // +optional - ObservedGeneration int64 - - // Represents the latest available observations of a replication controller's current state. - // +optional - Conditions []ReplicationControllerCondition -} - -type ReplicationControllerConditionType string - -// These are valid conditions of a replication controller. -const ( - // ReplicationControllerReplicaFailure is added in a replication controller when one of its pods - // fails to be created due to insufficient quota, limit ranges, pod security policy, node selectors, - // etc. or deleted due to kubelet being down or finalizers are failing. - ReplicationControllerReplicaFailure ReplicationControllerConditionType = "ReplicaFailure" -) - -// ReplicationControllerCondition describes the state of a replication controller at a certain point. -type ReplicationControllerCondition struct { - // Type of replication controller condition. - Type ReplicationControllerConditionType - // Status of the condition, one of True, False, Unknown. - Status ConditionStatus - // The last time the condition transitioned from one status to another. - // +optional - LastTransitionTime metav1.Time - // The reason for the condition's last transition. - // +optional - Reason string - // A human readable message indicating details about the transition. - // +optional - Message string -} - -// +genclient -// +genclient:method=GetScale,verb=get,subresource=scale,result=k8s.io/kubernetes/pkg/apis/extensions.Scale -// +genclient:method=UpdateScale,verb=update,subresource=scale,input=k8s.io/kubernetes/pkg/apis/extensions.Scale,result=k8s.io/kubernetes/pkg/apis/extensions.Scale -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ReplicationController represents the configuration of a replication controller. -type ReplicationController struct { - metav1.TypeMeta - // +optional - metav1.ObjectMeta - - // Spec defines the desired behavior of this replication controller. - // +optional - Spec ReplicationControllerSpec - - // Status is the current status of this replication controller. This data may be - // out of date by some window of time. - // +optional - Status ReplicationControllerStatus -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ReplicationControllerList is a collection of replication controllers. -type ReplicationControllerList struct { - metav1.TypeMeta - // +optional - metav1.ListMeta - - Items []ReplicationController -} - -const ( - // ClusterIPNone - do not assign a cluster IP - // no proxying required and no environment variables should be created for pods - ClusterIPNone = "None" -) - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ServiceList holds a list of services. -type ServiceList struct { - metav1.TypeMeta - // +optional - metav1.ListMeta - - Items []Service -} - -// Session Affinity Type string -type ServiceAffinity string - -const ( - // ServiceAffinityClientIP is the Client IP based. - ServiceAffinityClientIP ServiceAffinity = "ClientIP" - - // ServiceAffinityNone - no session affinity. - ServiceAffinityNone ServiceAffinity = "None" -) - -const ( - // DefaultClientIPServiceAffinitySeconds is the default timeout seconds - // of Client IP based session affinity - 3 hours. - DefaultClientIPServiceAffinitySeconds int32 = 10800 - // MaxClientIPServiceAffinitySeconds is the max timeout seconds - // of Client IP based session affinity - 1 day. - MaxClientIPServiceAffinitySeconds int32 = 86400 -) - -// SessionAffinityConfig represents the configurations of session affinity. -type SessionAffinityConfig struct { - // clientIP contains the configurations of Client IP based session affinity. - // +optional - ClientIP *ClientIPConfig -} - -// ClientIPConfig represents the configurations of Client IP based session affinity. -type ClientIPConfig struct { - // timeoutSeconds specifies the seconds of ClientIP type session sticky time. - // The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". - // Default value is 10800(for 3 hours). - // +optional - TimeoutSeconds *int32 -} - -// Service Type string describes ingress methods for a service -type ServiceType string - -const ( - // ServiceTypeClusterIP means a service will only be accessible inside the - // cluster, via the ClusterIP. - ServiceTypeClusterIP ServiceType = "ClusterIP" - - // ServiceTypeNodePort means a service will be exposed on one port of - // every node, in addition to 'ClusterIP' type. - ServiceTypeNodePort ServiceType = "NodePort" - - // ServiceTypeLoadBalancer means a service will be exposed via an - // external load balancer (if the cloud provider supports it), in addition - // to 'NodePort' type. - ServiceTypeLoadBalancer ServiceType = "LoadBalancer" - - // ServiceTypeExternalName means a service consists of only a reference to - // an external name that kubedns or equivalent will return as a CNAME - // record, with no exposing or proxying of any pods involved. - ServiceTypeExternalName ServiceType = "ExternalName" -) - -// Service External Traffic Policy Type string -type ServiceExternalTrafficPolicyType string - -const ( - // ServiceExternalTrafficPolicyTypeLocal specifies node-local endpoints behavior. - ServiceExternalTrafficPolicyTypeLocal ServiceExternalTrafficPolicyType = "Local" - // ServiceExternalTrafficPolicyTypeCluster specifies cluster-wide (legacy) behavior. - ServiceExternalTrafficPolicyTypeCluster ServiceExternalTrafficPolicyType = "Cluster" -) - -// ServiceStatus represents the current status of a service -type ServiceStatus struct { - // LoadBalancer contains the current status of the load-balancer, - // if one is present. - // +optional - LoadBalancer LoadBalancerStatus -} - -// LoadBalancerStatus represents the status of a load-balancer -type LoadBalancerStatus struct { - // Ingress is a list containing ingress points for the load-balancer; - // traffic intended for the service should be sent to these ingress points. - // +optional - Ingress []LoadBalancerIngress -} - -// LoadBalancerIngress represents the status of a load-balancer ingress point: -// traffic intended for the service should be sent to an ingress point. -type LoadBalancerIngress struct { - // IP is set for load-balancer ingress points that are IP based - // (typically GCE or OpenStack load-balancers) - // +optional - IP string - - // Hostname is set for load-balancer ingress points that are DNS based - // (typically AWS load-balancers) - // +optional - Hostname string -} - -// ServiceSpec describes the attributes that a user creates on a service -type ServiceSpec struct { - // Type determines how the Service is exposed. Defaults to ClusterIP. Valid - // options are ExternalName, ClusterIP, NodePort, and LoadBalancer. - // "ExternalName" maps to the specified externalName. - // "ClusterIP" allocates a cluster-internal IP address for load-balancing to - // endpoints. Endpoints are determined by the selector or if that is not - // specified, by manual construction of an Endpoints object. If clusterIP is - // "None", no virtual IP is allocated and the endpoints are published as a - // set of endpoints rather than a stable IP. - // "NodePort" builds on ClusterIP and allocates a port on every node which - // routes to the clusterIP. - // "LoadBalancer" builds on NodePort and creates an - // external load-balancer (if supported in the current cloud) which routes - // to the clusterIP. - // More info: https://kubernetes.io/docs/concepts/services-networking/service/ - // +optional - Type ServiceType - - // Required: The list of ports that are exposed by this service. - Ports []ServicePort - - // Route service traffic to pods with label keys and values matching this - // selector. If empty or not present, the service is assumed to have an - // external process managing its endpoints, which Kubernetes will not - // modify. Only applies to types ClusterIP, NodePort, and LoadBalancer. - // Ignored if type is ExternalName. - // More info: https://kubernetes.io/docs/concepts/services-networking/service/ - Selector map[string]string - - // ClusterIP is the IP address of the service and is usually assigned - // randomly by the master. If an address is specified manually and is not in - // use by others, it will be allocated to the service; otherwise, creation - // of the service will fail. This field can not be changed through updates. - // Valid values are "None", empty string (""), or a valid IP address. "None" - // can be specified for headless services when proxying is not required. - // Only applies to types ClusterIP, NodePort, and LoadBalancer. Ignored if - // type is ExternalName. - // More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies - // +optional - ClusterIP string - - // ExternalName is the external reference that kubedns or equivalent will - // return as a CNAME record for this service. No proxying will be involved. - // Must be a valid DNS name and requires Type to be ExternalName. - ExternalName string - - // ExternalIPs are used by external load balancers, or can be set by - // users to handle external traffic that arrives at a node. - // +optional - ExternalIPs []string - - // Only applies to Service Type: LoadBalancer - // LoadBalancer will get created with the IP specified in this field. - // This feature depends on whether the underlying cloud-provider supports specifying - // the loadBalancerIP when a load balancer is created. - // This field will be ignored if the cloud-provider does not support the feature. - // +optional - LoadBalancerIP string - - // Optional: Supports "ClientIP" and "None". Used to maintain session affinity. - // +optional - SessionAffinity ServiceAffinity - - // sessionAffinityConfig contains the configurations of session affinity. - // +optional - SessionAffinityConfig *SessionAffinityConfig - - // Optional: If specified and supported by the platform, this will restrict traffic through the cloud-provider - // load-balancer will be restricted to the specified client IPs. This field will be ignored if the - // cloud-provider does not support the feature." - // +optional - LoadBalancerSourceRanges []string - - // externalTrafficPolicy denotes if this Service desires to route external - // traffic to node-local or cluster-wide endpoints. "Local" preserves the - // client source IP and avoids a second hop for LoadBalancer and Nodeport - // type services, but risks potentially imbalanced traffic spreading. - // "Cluster" obscures the client source IP and may cause a second hop to - // another node, but should have good overall load-spreading. - // +optional - ExternalTrafficPolicy ServiceExternalTrafficPolicyType - - // healthCheckNodePort specifies the healthcheck nodePort for the service. - // If not specified, HealthCheckNodePort is created by the service api - // backend with the allocated nodePort. Will use user-specified nodePort value - // if specified by the client. Only effects when Type is set to LoadBalancer - // and ExternalTrafficPolicy is set to Local. - // +optional - HealthCheckNodePort int32 - - // publishNotReadyAddresses, when set to true, indicates that DNS implementations - // must publish the notReadyAddresses of subsets for the Endpoints associated with - // the Service. The default value is false. - // The primary use case for setting this field is to use a StatefulSet's Headless Service - // to propagate SRV records for its Pods without respect to their readiness for purpose - // of peer discovery. - // This field will replace the service.alpha.kubernetes.io/tolerate-unready-endpoints - // when that annotation is deprecated and all clients have been converted to use this - // field. - // +optional - PublishNotReadyAddresses bool -} - -type ServicePort struct { - // Optional if only one ServicePort is defined on this service: The - // name of this port within the service. This must be a DNS_LABEL. - // All ports within a ServiceSpec must have unique names. This maps to - // the 'Name' field in EndpointPort objects. - Name string - - // The IP protocol for this port. Supports "TCP" and "UDP". - Protocol Protocol - - // The port that will be exposed on the service. - Port int32 - - // Optional: The target port on pods selected by this service. If this - // is a string, it will be looked up as a named port in the target - // Pod's container ports. If this is not specified, the value - // of the 'port' field is used (an identity map). - // This field is ignored for services with clusterIP=None, and should be - // omitted or set equal to the 'port' field. - TargetPort intstr.IntOrString - - // The port on each node on which this service is exposed. - // Default is to auto-allocate a port if the ServiceType of this Service requires one. - NodePort int32 -} - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// Service is a named abstraction of software service (for example, mysql) consisting of local port -// (for example 3306) that the proxy listens on, and the selector that determines which pods -// will answer requests sent through the proxy. -type Service struct { - metav1.TypeMeta - // +optional - metav1.ObjectMeta - - // Spec defines the behavior of a service. - // +optional - Spec ServiceSpec - - // Status represents the current status of a service. - // +optional - Status ServiceStatus -} - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ServiceAccount binds together: -// * a name, understood by users, and perhaps by peripheral systems, for an identity -// * a principal that can be authenticated and authorized -// * a set of secrets -type ServiceAccount struct { - metav1.TypeMeta - // +optional - metav1.ObjectMeta - - // Secrets is the list of secrets allowed to be used by pods running using this ServiceAccount - Secrets []ObjectReference - - // ImagePullSecrets is a list of references to secrets in the same namespace to use for pulling any images - // in pods that reference this ServiceAccount. ImagePullSecrets are distinct from Secrets because Secrets - // can be mounted in the pod, but ImagePullSecrets are only accessed by the kubelet. - // +optional - ImagePullSecrets []LocalObjectReference - - // AutomountServiceAccountToken indicates whether pods running as this service account should have an API token automatically mounted. - // Can be overridden at the pod level. - // +optional - AutomountServiceAccountToken *bool -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ServiceAccountList is a list of ServiceAccount objects -type ServiceAccountList struct { - metav1.TypeMeta - // +optional - metav1.ListMeta - - Items []ServiceAccount -} - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// Endpoints is a collection of endpoints that implement the actual service. Example: -// Name: "mysvc", -// Subsets: [ -// { -// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}], -// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}] -// }, -// { -// Addresses: [{"ip": "10.10.3.3"}], -// Ports: [{"name": "a", "port": 93}, {"name": "b", "port": 76}] -// }, -// ] -type Endpoints struct { - metav1.TypeMeta - // +optional - metav1.ObjectMeta - - // The set of all endpoints is the union of all subsets. - Subsets []EndpointSubset -} - -// EndpointSubset is a group of addresses with a common set of ports. The -// expanded set of endpoints is the Cartesian product of Addresses x Ports. -// For example, given: -// { -// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}], -// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}] -// } -// The resulting set of endpoints can be viewed as: -// a: [ 10.10.1.1:8675, 10.10.2.2:8675 ], -// b: [ 10.10.1.1:309, 10.10.2.2:309 ] -type EndpointSubset struct { - Addresses []EndpointAddress - NotReadyAddresses []EndpointAddress - Ports []EndpointPort -} - -// EndpointAddress is a tuple that describes single IP address. -type EndpointAddress struct { - // The IP of this endpoint. - // IPv6 is also accepted but not fully supported on all platforms. Also, certain - // kubernetes components, like kube-proxy, are not IPv6 ready. - // TODO: This should allow hostname or IP, see #4447. - IP string - // Optional: Hostname of this endpoint - // Meant to be used by DNS servers etc. - // +optional - Hostname string - // Optional: Node hosting this endpoint. This can be used to determine endpoints local to a node. - // +optional - NodeName *string - // Optional: The kubernetes object related to the entry point. - TargetRef *ObjectReference -} - -// EndpointPort is a tuple that describes a single port. -type EndpointPort struct { - // The name of this port (corresponds to ServicePort.Name). Optional - // if only one port is defined. Must be a DNS_LABEL. - Name string - - // The port number. - Port int32 - - // The IP protocol for this port. - Protocol Protocol -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// EndpointsList is a list of endpoints. -type EndpointsList struct { - metav1.TypeMeta - // +optional - metav1.ListMeta - - Items []Endpoints -} - -// NodeSpec describes the attributes that a node is created with. -type NodeSpec struct { - // PodCIDR represents the pod IP range assigned to the node - // Note: assigning IP ranges to nodes might need to be revisited when we support migratable IPs. - // +optional - PodCIDR string - - // External ID of the node assigned by some machine database (e.g. a cloud provider) - // +optional - ExternalID string - - // ID of the node assigned by the cloud provider - // Note: format is "://" - // +optional - ProviderID string - - // Unschedulable controls node schedulability of new pods. By default node is schedulable. - // +optional - Unschedulable bool - - // If specified, the node's taints. - // +optional - Taints []Taint - - // If specified, the source to get node configuration from - // The DynamicKubeletConfig feature gate must be enabled for the Kubelet to use this field - // +optional - ConfigSource *NodeConfigSource -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// NodeConfigSource specifies a source of node configuration. Exactly one subfield must be non-nil. -type NodeConfigSource struct { - metav1.TypeMeta - ConfigMapRef *ObjectReference -} - -// DaemonEndpoint contains information about a single Daemon endpoint. -type DaemonEndpoint struct { - /* - The port tag was not properly in quotes in earlier releases, so it must be - uppercase for backwards compatibility (since it was falling back to var name of - 'Port'). - */ - - // Port number of the given endpoint. - Port int32 -} - -// NodeDaemonEndpoints lists ports opened by daemons running on the Node. -type NodeDaemonEndpoints struct { - // Endpoint on which Kubelet is listening. - // +optional - KubeletEndpoint DaemonEndpoint -} - -// NodeSystemInfo is a set of ids/uuids to uniquely identify the node. -type NodeSystemInfo struct { - // MachineID reported by the node. For unique machine identification - // in the cluster this field is preferred. Learn more from man(5) - // machine-id: http://man7.org/linux/man-pages/man5/machine-id.5.html - MachineID string - // SystemUUID reported by the node. For unique machine identification - // MachineID is preferred. This field is specific to Red Hat hosts - // https://access.redhat.com/documentation/en-US/Red_Hat_Subscription_Management/1/html/RHSM/getting-system-uuid.html - SystemUUID string - // Boot ID reported by the node. - BootID string - // Kernel Version reported by the node. - KernelVersion string - // OS Image reported by the node. - OSImage string - // ContainerRuntime Version reported by the node. - ContainerRuntimeVersion string - // Kubelet Version reported by the node. - KubeletVersion string - // KubeProxy Version reported by the node. - KubeProxyVersion string - // The Operating System reported by the node - OperatingSystem string - // The Architecture reported by the node - Architecture string -} - -// NodeStatus is information about the current status of a node. -type NodeStatus struct { - // Capacity represents the total resources of a node. - // +optional - Capacity ResourceList - // Allocatable represents the resources of a node that are available for scheduling. - // +optional - Allocatable ResourceList - // NodePhase is the current lifecycle phase of the node. - // +optional - Phase NodePhase - // Conditions is an array of current node conditions. - // +optional - Conditions []NodeCondition - // Queried from cloud provider, if available. - // +optional - Addresses []NodeAddress - // Endpoints of daemons running on the Node. - // +optional - DaemonEndpoints NodeDaemonEndpoints - // Set of ids/uuids to uniquely identify the node. - // +optional - NodeInfo NodeSystemInfo - // List of container images on this node - // +optional - Images []ContainerImage - // List of attachable volumes in use (mounted) by the node. - // +optional - VolumesInUse []UniqueVolumeName - // List of volumes that are attached to the node. - // +optional - VolumesAttached []AttachedVolume -} - -type UniqueVolumeName string - -// AttachedVolume describes a volume attached to a node -type AttachedVolume struct { - // Name of the attached volume - Name UniqueVolumeName - - // DevicePath represents the device path where the volume should be available - DevicePath string -} - -// AvoidPods describes pods that should avoid this node. This is the value for a -// Node annotation with key scheduler.alpha.kubernetes.io/preferAvoidPods and -// will eventually become a field of NodeStatus. -type AvoidPods struct { - // Bounded-sized list of signatures of pods that should avoid this node, sorted - // in timestamp order from oldest to newest. Size of the slice is unspecified. - // +optional - PreferAvoidPods []PreferAvoidPodsEntry -} - -// Describes a class of pods that should avoid this node. -type PreferAvoidPodsEntry struct { - // The class of pods. - PodSignature PodSignature - // Time at which this entry was added to the list. - // +optional - EvictionTime metav1.Time - // (brief) reason why this entry was added to the list. - // +optional - Reason string - // Human readable message indicating why this entry was added to the list. - // +optional - Message string -} - -// Describes the class of pods that should avoid this node. -// Exactly one field should be set. -type PodSignature struct { - // Reference to controller whose pods should avoid this node. - // +optional - PodController *metav1.OwnerReference -} - -// Describe a container image -type ContainerImage struct { - // Names by which this image is known. - Names []string - // The size of the image in bytes. - // +optional - SizeBytes int64 -} - -type NodePhase string - -// These are the valid phases of node. -const ( - // NodePending means the node has been created/added by the system, but not configured. - NodePending NodePhase = "Pending" - // NodeRunning means the node has been configured and has Kubernetes components running. - NodeRunning NodePhase = "Running" - // NodeTerminated means the node has been removed from the cluster. - NodeTerminated NodePhase = "Terminated" -) - -type NodeConditionType string - -// These are valid conditions of node. Currently, we don't have enough information to decide -// node condition. In the future, we will add more. The proposed set of conditions are: -// NodeReady, NodeReachable -const ( - // NodeReady means kubelet is healthy and ready to accept pods. - NodeReady NodeConditionType = "Ready" - // NodeOutOfDisk means the kubelet will not accept new pods due to insufficient free disk - // space on the node. - NodeOutOfDisk NodeConditionType = "OutOfDisk" - // NodeMemoryPressure means the kubelet is under pressure due to insufficient available memory. - NodeMemoryPressure NodeConditionType = "MemoryPressure" - // NodeDiskPressure means the kubelet is under pressure due to insufficient available disk. - NodeDiskPressure NodeConditionType = "DiskPressure" - // NodeNetworkUnavailable means that network for the node is not correctly configured. - NodeNetworkUnavailable NodeConditionType = "NetworkUnavailable" - // NodeConfigOK indicates whether the kubelet is correctly configured - NodeConfigOK NodeConditionType = "ConfigOK" -) - -type NodeCondition struct { - Type NodeConditionType - Status ConditionStatus - // +optional - LastHeartbeatTime metav1.Time - // +optional - LastTransitionTime metav1.Time - // +optional - Reason string - // +optional - Message string -} - -type NodeAddressType string - -const ( - NodeHostName NodeAddressType = "Hostname" - NodeExternalIP NodeAddressType = "ExternalIP" - NodeInternalIP NodeAddressType = "InternalIP" - NodeExternalDNS NodeAddressType = "ExternalDNS" - NodeInternalDNS NodeAddressType = "InternalDNS" -) - -type NodeAddress struct { - Type NodeAddressType - Address string -} - -// NodeResources is an object for conveying resource information about a node. -// see http://releases.k8s.io/HEAD/docs/design/resources.md for more details. -type NodeResources struct { - // Capacity represents the available resources of a node - // +optional - Capacity ResourceList -} - -// ResourceName is the name identifying various resources in a ResourceList. -type ResourceName string - -// Resource names must be not more than 63 characters, consisting of upper- or lower-case alphanumeric characters, -// with the -, _, and . characters allowed anywhere, except the first or last character. -// The default convention, matching that for annotations, is to use lower-case names, with dashes, rather than -// camel case, separating compound words. -// Fully-qualified resource typenames are constructed from a DNS-style subdomain, followed by a slash `/` and a name. -const ( - // CPU, in cores. (500m = .5 cores) - ResourceCPU ResourceName = "cpu" - // Memory, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) - ResourceMemory ResourceName = "memory" - // Volume size, in bytes (e,g. 5Gi = 5GiB = 5 * 1024 * 1024 * 1024) - ResourceStorage ResourceName = "storage" - // Local ephemeral storage, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) - // The resource name for ResourceEphemeralStorage is alpha and it can change across releases. - ResourceEphemeralStorage ResourceName = "ephemeral-storage" - // NVIDIA GPU, in devices. Alpha, might change: although fractional and allowing values >1, only one whole device per node is assigned. - ResourceNvidiaGPU ResourceName = "alpha.kubernetes.io/nvidia-gpu" -) - -const ( - // Namespace prefix for opaque counted resources (alpha). - ResourceOpaqueIntPrefix = "pod.alpha.kubernetes.io/opaque-int-resource-" - // Default namespace prefix. - ResourceDefaultNamespacePrefix = "kubernetes.io/" - // Name prefix for huge page resources (alpha). - ResourceHugePagesPrefix = "hugepages-" -) - -// ResourceList is a set of (resource name, quantity) pairs. -type ResourceList map[ResourceName]resource.Quantity - -// +genclient -// +genclient:nonNamespaced -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// Node is a worker node in Kubernetes -// The name of the node according to etcd is in ObjectMeta.Name. -type Node struct { - metav1.TypeMeta - // +optional - metav1.ObjectMeta - - // Spec defines the behavior of a node. - // +optional - Spec NodeSpec - - // Status describes the current status of a Node - // +optional - Status NodeStatus -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// NodeList is a list of nodes. -type NodeList struct { - metav1.TypeMeta - // +optional - metav1.ListMeta - - Items []Node -} - -// NamespaceSpec describes the attributes on a Namespace -type NamespaceSpec struct { - // Finalizers is an opaque list of values that must be empty to permanently remove object from storage - Finalizers []FinalizerName -} - -// FinalizerName is the name identifying a finalizer during namespace lifecycle. -type FinalizerName string - -// These are internal finalizer values to Kubernetes, must be qualified name unless defined here or -// in metav1. -const ( - FinalizerKubernetes FinalizerName = "kubernetes" -) - -// NamespaceStatus is information about the current status of a Namespace. -type NamespaceStatus struct { - // Phase is the current lifecycle phase of the namespace. - // +optional - Phase NamespacePhase -} - -type NamespacePhase string - -// These are the valid phases of a namespace. -const ( - // NamespaceActive means the namespace is available for use in the system - NamespaceActive NamespacePhase = "Active" - // NamespaceTerminating means the namespace is undergoing graceful termination - NamespaceTerminating NamespacePhase = "Terminating" -) - -// +genclient -// +genclient:nonNamespaced -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// A namespace provides a scope for Names. -// Use of multiple namespaces is optional -type Namespace struct { - metav1.TypeMeta - // +optional - metav1.ObjectMeta - - // Spec defines the behavior of the Namespace. - // +optional - Spec NamespaceSpec - - // Status describes the current status of a Namespace - // +optional - Status NamespaceStatus -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// NamespaceList is a list of Namespaces. -type NamespaceList struct { - metav1.TypeMeta - // +optional - metav1.ListMeta - - Items []Namespace -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// Binding ties one object to another; for example, a pod is bound to a node by a scheduler. -// Deprecated in 1.7, please use the bindings subresource of pods instead. -type Binding struct { - metav1.TypeMeta - // ObjectMeta describes the object that is being bound. - // +optional - metav1.ObjectMeta - - // Target is the object to bind to. - Target ObjectReference -} - -// Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out. -type Preconditions struct { - // Specifies the target UID. - // +optional - UID *types.UID -} - -// DeletionPropagation decides whether and how garbage collection will be performed. -type DeletionPropagation string - -const ( - // Orphans the dependents. - DeletePropagationOrphan DeletionPropagation = "Orphan" - // Deletes the object from the key-value store, the garbage collector will delete the dependents in the background. - DeletePropagationBackground DeletionPropagation = "Background" - // The object exists in the key-value store until the garbage collector deletes all the dependents whose ownerReference.blockOwnerDeletion=true from the key-value store. - // API sever will put the "DeletingDependents" finalizer on the object, and sets its deletionTimestamp. - // This policy is cascading, i.e., the dependents will be deleted with Foreground. - DeletePropagationForeground DeletionPropagation = "Foreground" -) - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// DeleteOptions may be provided when deleting an API object -// DEPRECATED: This type has been moved to meta/v1 and will be removed soon. -type DeleteOptions struct { - metav1.TypeMeta - - // Optional 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. - // +optional - GracePeriodSeconds *int64 - - // Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be - // returned. - // +optional - Preconditions *Preconditions - - // 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. - // +optional - OrphanDependents *bool - - // 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. - // +optional - PropagationPolicy *DeletionPropagation -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ListOptions is the query options to a standard REST list call, and has future support for -// watch calls. -// DEPRECATED: This type has been moved to meta/v1 and will be removed soon. -type ListOptions struct { - metav1.TypeMeta - - // A selector based on labels - LabelSelector labels.Selector - // A selector based on fields - FieldSelector fields.Selector - - // If true, partially initialized resources are included in the response. - IncludeUninitialized bool - - // If true, watch for changes to this list - Watch bool - // When specified with a watch call, shows changes that occur after that particular version of a resource. - // Defaults to changes from the beginning of history. - // When specified for list: - // - if unset, then the result is returned from remote storage based on quorum-read flag; - // - if it's 0, then we simply return what we currently have in cache, no guarantee; - // - if set to non zero, then the result is at least as fresh as given rv. - ResourceVersion string - // Timeout for the list/watch call. - TimeoutSeconds *int64 -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// PodLogOptions is the query options for a Pod's logs REST call -type PodLogOptions struct { - metav1.TypeMeta - - // Container for which to return logs - Container string - // If true, follow the logs for the pod - Follow bool - // If true, return previous terminated container logs - Previous bool - // 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. - SinceSeconds *int64 - // An RFC3339 timestamp 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. - SinceTime *metav1.Time - // If true, add an RFC3339 or RFC3339Nano timestamp at the beginning of every line - // of log output. - Timestamps bool - // If set, the number of lines from the end of the logs to show. If not specified, - // logs are shown from the creation of the container or sinceSeconds or sinceTime - TailLines *int64 - // If set, the number of bytes to read from the server before terminating the - // log output. This may not display a complete final line of logging, and may return - // slightly more or slightly less than the specified limit. - LimitBytes *int64 -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// PodAttachOptions is the query options to a Pod's remote attach call -// TODO: merge w/ PodExecOptions below for stdin, stdout, etc -type PodAttachOptions struct { - metav1.TypeMeta - - // Stdin if true indicates that stdin is to be redirected for the attach call - // +optional - Stdin bool - - // Stdout if true indicates that stdout is to be redirected for the attach call - // +optional - Stdout bool - - // Stderr if true indicates that stderr is to be redirected for the attach call - // +optional - Stderr bool - - // TTY if true indicates that a tty will be allocated for the attach call - // +optional - TTY bool - - // Container to attach to. - // +optional - Container string -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// PodExecOptions is the query options to a Pod's remote exec call -type PodExecOptions struct { - metav1.TypeMeta - - // Stdin if true indicates that stdin is to be redirected for the exec call - Stdin bool - - // Stdout if true indicates that stdout is to be redirected for the exec call - Stdout bool - - // Stderr if true indicates that stderr is to be redirected for the exec call - Stderr bool - - // TTY if true indicates that a tty will be allocated for the exec call - TTY bool - - // Container in which to execute the command. - Container string - - // Command is the remote command to execute; argv array; not executed within a shell. - Command []string -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// PodPortForwardOptions is the query options to a Pod's port forward call -type PodPortForwardOptions struct { - metav1.TypeMeta - - // The list of ports to forward - // +optional - Ports []int32 -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// PodProxyOptions is the query options to a Pod's proxy call -type PodProxyOptions struct { - metav1.TypeMeta - - // Path is the URL path to use for the current proxy request - Path string -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// NodeProxyOptions is the query options to a Node's proxy call -type NodeProxyOptions struct { - metav1.TypeMeta - - // Path is the URL path to use for the current proxy request - Path string -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ServiceProxyOptions is the query options to a Service's proxy call. -type ServiceProxyOptions struct { - metav1.TypeMeta - - // Path is the part of URLs that include service endpoints, suffixes, - // and parameters to use for the current proxy request to service. - // For example, the whole request URL is - // http://localhost/api/v1/namespaces/kube-system/services/elasticsearch-logging/_search?q=user:kimchy. - // Path is _search?q=user:kimchy. - Path string -} - -// ObjectReference contains enough information to let you inspect or modify the referred object. -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type ObjectReference struct { - // +optional - Kind string - // +optional - Namespace string - // +optional - Name string - // +optional - UID types.UID - // +optional - APIVersion string - // +optional - ResourceVersion string - - // Optional. If referring to a piece of an object instead of an entire object, this string - // should contain information to identify the sub-object. For example, if the object - // reference is to a container within a pod, this would take on a value like: - // "spec.containers{name}" (where "name" refers to the name of the container that triggered - // the event) or if no container name is specified "spec.containers[2]" (container with - // index 2 in this pod). This syntax is chosen only to have some well-defined way of - // referencing a part of an object. - // TODO: this design is not final and this field is subject to change in the future. - // +optional - FieldPath string -} - -// LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace. -type LocalObjectReference struct { - //TODO: Add other useful fields. apiVersion, kind, uid? - Name string -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -type SerializedReference struct { - metav1.TypeMeta - // +optional - Reference ObjectReference -} - -type EventSource struct { - // Component from which the event is generated. - // +optional - Component string - // Node name on which the event is generated. - // +optional - Host string -} - -// Valid values for event types (new types could be added in future) -const ( - // Information only and will not cause any problems - EventTypeNormal string = "Normal" - // These events are to warn that something might go wrong - EventTypeWarning string = "Warning" -) - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// Event is a report of an event somewhere in the cluster. -// TODO: Decide whether to store these separately or with the object they apply to. -type Event struct { - metav1.TypeMeta - // +optional - metav1.ObjectMeta - - // Required. The object that this event is about. - // +optional - InvolvedObject ObjectReference - - // Optional; this should be a short, machine understandable string that gives the reason - // for this event being generated. For example, if the event is reporting that a container - // can't start, the Reason might be "ImageNotFound". - // TODO: provide exact specification for format. - // +optional - Reason string - - // Optional. A human-readable description of the status of this operation. - // TODO: decide on maximum length. - // +optional - Message string - - // Optional. The component reporting this event. Should be a short machine understandable string. - // +optional - Source EventSource - - // The time at which the event was first recorded. (Time of server receipt is in TypeMeta.) - // +optional - FirstTimestamp metav1.Time - - // The time at which the most recent occurrence of this event was recorded. - // +optional - LastTimestamp metav1.Time - - // The number of times this event has occurred. - // +optional - Count int32 - - // Type of this event (Normal, Warning), new types could be added in the future. - // +optional - Type string -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// EventList is a list of events. -type EventList struct { - metav1.TypeMeta - // +optional - metav1.ListMeta - - Items []Event -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// List holds a list of objects, which may not be known by the server. -type List metainternalversion.List - -// A type of object that is limited -type LimitType string - -const ( - // Limit that applies to all pods in a namespace - LimitTypePod LimitType = "Pod" - // Limit that applies to all containers in a namespace - LimitTypeContainer LimitType = "Container" - // Limit that applies to all persistent volume claims in a namespace - LimitTypePersistentVolumeClaim LimitType = "PersistentVolumeClaim" -) - -// LimitRangeItem defines a min/max usage limit for any resource that matches on kind -type LimitRangeItem struct { - // Type of resource that this limit applies to - // +optional - Type LimitType - // Max usage constraints on this kind by resource name - // +optional - Max ResourceList - // Min usage constraints on this kind by resource name - // +optional - Min ResourceList - // Default resource requirement limit value by resource name. - // +optional - Default ResourceList - // DefaultRequest resource requirement request value by resource name. - // +optional - DefaultRequest ResourceList - // MaxLimitRequestRatio represents the max burst value for the named resource - // +optional - MaxLimitRequestRatio ResourceList -} - -// LimitRangeSpec defines a min/max usage limit for resources that match on kind -type LimitRangeSpec struct { - // Limits is the list of LimitRangeItem objects that are enforced - Limits []LimitRangeItem -} - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// LimitRange sets resource usage limits for each kind of resource in a Namespace -type LimitRange struct { - metav1.TypeMeta - // +optional - metav1.ObjectMeta - - // Spec defines the limits enforced - // +optional - Spec LimitRangeSpec -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// LimitRangeList is a list of LimitRange items. -type LimitRangeList struct { - metav1.TypeMeta - // +optional - metav1.ListMeta - - // Items is a list of LimitRange objects - Items []LimitRange -} - -// The following identify resource constants for Kubernetes object types -const ( - // Pods, number - ResourcePods ResourceName = "pods" - // Services, number - ResourceServices ResourceName = "services" - // ReplicationControllers, number - ResourceReplicationControllers ResourceName = "replicationcontrollers" - // ResourceQuotas, number - ResourceQuotas ResourceName = "resourcequotas" - // ResourceSecrets, number - ResourceSecrets ResourceName = "secrets" - // ResourceConfigMaps, number - ResourceConfigMaps ResourceName = "configmaps" - // ResourcePersistentVolumeClaims, number - ResourcePersistentVolumeClaims ResourceName = "persistentvolumeclaims" - // ResourceServicesNodePorts, number - ResourceServicesNodePorts ResourceName = "services.nodeports" - // ResourceServicesLoadBalancers, number - ResourceServicesLoadBalancers ResourceName = "services.loadbalancers" - // CPU request, in cores. (500m = .5 cores) - ResourceRequestsCPU ResourceName = "requests.cpu" - // Memory request, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) - ResourceRequestsMemory ResourceName = "requests.memory" - // Storage request, in bytes - ResourceRequestsStorage ResourceName = "requests.storage" - // Local ephemeral storage request, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) - ResourceRequestsEphemeralStorage ResourceName = "requests.ephemeral-storage" - // CPU limit, in cores. (500m = .5 cores) - ResourceLimitsCPU ResourceName = "limits.cpu" - // Memory limit, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) - ResourceLimitsMemory ResourceName = "limits.memory" - // Local ephemeral storage limit, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) - ResourceLimitsEphemeralStorage ResourceName = "limits.ephemeral-storage" -) - -// A ResourceQuotaScope defines a filter that must match each object tracked by a quota -type ResourceQuotaScope string - -const ( - // Match all pod objects where spec.activeDeadlineSeconds - ResourceQuotaScopeTerminating ResourceQuotaScope = "Terminating" - // Match all pod objects where !spec.activeDeadlineSeconds - ResourceQuotaScopeNotTerminating ResourceQuotaScope = "NotTerminating" - // Match all pod objects that have best effort quality of service - ResourceQuotaScopeBestEffort ResourceQuotaScope = "BestEffort" - // Match all pod objects that do not have best effort quality of service - ResourceQuotaScopeNotBestEffort ResourceQuotaScope = "NotBestEffort" -) - -// ResourceQuotaSpec defines the desired hard limits to enforce for Quota -type ResourceQuotaSpec struct { - // Hard is the set of desired hard limits for each named resource - // +optional - Hard ResourceList - // A collection of filters that must match each object tracked by a quota. - // If not specified, the quota matches all objects. - // +optional - Scopes []ResourceQuotaScope -} - -// ResourceQuotaStatus defines the enforced hard limits and observed use -type ResourceQuotaStatus struct { - // Hard is the set of enforced hard limits for each named resource - // +optional - Hard ResourceList - // Used is the current observed total usage of the resource in the namespace - // +optional - Used ResourceList -} - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ResourceQuota sets aggregate quota restrictions enforced per namespace -type ResourceQuota struct { - metav1.TypeMeta - // +optional - metav1.ObjectMeta - - // Spec defines the desired quota - // +optional - Spec ResourceQuotaSpec - - // Status defines the actual enforced quota and its current usage - // +optional - Status ResourceQuotaStatus -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ResourceQuotaList is a list of ResourceQuota items -type ResourceQuotaList struct { - metav1.TypeMeta - // +optional - metav1.ListMeta - - // Items is a list of ResourceQuota objects - Items []ResourceQuota -} - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// Secret holds secret data of a certain type. The total bytes of the values in -// the Data field must be less than MaxSecretSize bytes. -type Secret struct { - metav1.TypeMeta - // +optional - metav1.ObjectMeta - - // Data contains the secret data. Each key must consist of alphanumeric - // characters, '-', '_' or '.'. The serialized form of the secret data is a - // base64 encoded string, representing the arbitrary (possibly non-string) - // data value here. - // +optional - Data map[string][]byte - - // Used to facilitate programmatic handling of secret data. - // +optional - Type SecretType -} - -const MaxSecretSize = 1 * 1024 * 1024 - -type SecretType string - -const ( - // SecretTypeOpaque is the default; arbitrary user-defined data - SecretTypeOpaque SecretType = "Opaque" - - // SecretTypeServiceAccountToken contains a token that identifies a service account to the API - // - // Required fields: - // - Secret.Annotations["kubernetes.io/service-account.name"] - the name of the ServiceAccount the token identifies - // - Secret.Annotations["kubernetes.io/service-account.uid"] - the UID of the ServiceAccount the token identifies - // - Secret.Data["token"] - a token that identifies the service account to the API - SecretTypeServiceAccountToken SecretType = "kubernetes.io/service-account-token" - - // ServiceAccountNameKey is the key of the required annotation for SecretTypeServiceAccountToken secrets - ServiceAccountNameKey = "kubernetes.io/service-account.name" - // ServiceAccountUIDKey is the key of the required annotation for SecretTypeServiceAccountToken secrets - ServiceAccountUIDKey = "kubernetes.io/service-account.uid" - // ServiceAccountTokenKey is the key of the required data for SecretTypeServiceAccountToken secrets - ServiceAccountTokenKey = "token" - // ServiceAccountKubeconfigKey is the key of the optional kubeconfig data for SecretTypeServiceAccountToken secrets - ServiceAccountKubeconfigKey = "kubernetes.kubeconfig" - // ServiceAccountRootCAKey is the key of the optional root certificate authority for SecretTypeServiceAccountToken secrets - ServiceAccountRootCAKey = "ca.crt" - // ServiceAccountNamespaceKey is the key of the optional namespace to use as the default for namespaced API calls - ServiceAccountNamespaceKey = "namespace" - - // SecretTypeDockercfg contains a dockercfg file that follows the same format rules as ~/.dockercfg - // - // Required fields: - // - Secret.Data[".dockercfg"] - a serialized ~/.dockercfg file - SecretTypeDockercfg SecretType = "kubernetes.io/dockercfg" - - // DockerConfigKey is the key of the required data for SecretTypeDockercfg secrets - DockerConfigKey = ".dockercfg" - - // SecretTypeDockerConfigJson contains a dockercfg file that follows the same format rules as ~/.docker/config.json - // - // Required fields: - // - Secret.Data[".dockerconfigjson"] - a serialized ~/.docker/config.json file - SecretTypeDockerConfigJson SecretType = "kubernetes.io/dockerconfigjson" - - // DockerConfigJsonKey is the key of the required data for SecretTypeDockerConfigJson secrets - DockerConfigJsonKey = ".dockerconfigjson" - - // SecretTypeBasicAuth contains data needed for basic authentication. - // - // Required at least one of fields: - // - Secret.Data["username"] - username used for authentication - // - Secret.Data["password"] - password or token needed for authentication - SecretTypeBasicAuth SecretType = "kubernetes.io/basic-auth" - - // BasicAuthUsernameKey is the key of the username for SecretTypeBasicAuth secrets - BasicAuthUsernameKey = "username" - // BasicAuthPasswordKey is the key of the password or token for SecretTypeBasicAuth secrets - BasicAuthPasswordKey = "password" - - // SecretTypeSSHAuth contains data needed for SSH authentication. - // - // Required field: - // - Secret.Data["ssh-privatekey"] - private SSH key needed for authentication - SecretTypeSSHAuth SecretType = "kubernetes.io/ssh-auth" - - // SSHAuthPrivateKey is the key of the required SSH private key for SecretTypeSSHAuth secrets - SSHAuthPrivateKey = "ssh-privatekey" - - // SecretTypeTLS contains information about a TLS client or server secret. It - // is primarily used with TLS termination of the Ingress resource, but may be - // used in other types. - // - // Required fields: - // - Secret.Data["tls.key"] - TLS private key. - // Secret.Data["tls.crt"] - TLS certificate. - // TODO: Consider supporting different formats, specifying CA/destinationCA. - SecretTypeTLS SecretType = "kubernetes.io/tls" - - // TLSCertKey is the key for tls certificates in a TLS secret. - TLSCertKey = "tls.crt" - // TLSPrivateKeyKey is the key for the private key field in a TLS secret. - TLSPrivateKeyKey = "tls.key" - // SecretTypeBootstrapToken is used during the automated bootstrap process (first - // implemented by kubeadm). It stores tokens that are used to sign well known - // ConfigMaps. They are used for authn. - SecretTypeBootstrapToken SecretType = "bootstrap.kubernetes.io/token" -) - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -type SecretList struct { - metav1.TypeMeta - // +optional - metav1.ListMeta - - Items []Secret -} - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ConfigMap holds configuration data for components or applications to consume. -type ConfigMap struct { - metav1.TypeMeta - // +optional - metav1.ObjectMeta - - // Data contains the configuration data. - // Each key must consist of alphanumeric characters, '-', '_' or '.'. - // +optional - Data map[string]string -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ConfigMapList is a resource containing a list of ConfigMap objects. -type ConfigMapList struct { - metav1.TypeMeta - // +optional - metav1.ListMeta - - // Items is the list of ConfigMaps. - Items []ConfigMap -} - -// These constants are for remote command execution and port forwarding and are -// used by both the client side and server side components. -// -// This is probably not the ideal place for them, but it didn't seem worth it -// to create pkg/exec and pkg/portforward just to contain a single file with -// constants in it. Suggestions for more appropriate alternatives are -// definitely welcome! -const ( - // Enable stdin for remote command execution - ExecStdinParam = "input" - // Enable stdout for remote command execution - ExecStdoutParam = "output" - // Enable stderr for remote command execution - ExecStderrParam = "error" - // Enable TTY for remote command execution - ExecTTYParam = "tty" - // Command to run for remote command execution - ExecCommandParam = "command" - - // Name of header that specifies stream type - StreamType = "streamType" - // Value for streamType header for stdin stream - StreamTypeStdin = "stdin" - // Value for streamType header for stdout stream - StreamTypeStdout = "stdout" - // Value for streamType header for stderr stream - StreamTypeStderr = "stderr" - // Value for streamType header for data stream - StreamTypeData = "data" - // Value for streamType header for error stream - StreamTypeError = "error" - // Value for streamType header for terminal resize stream - StreamTypeResize = "resize" - - // Name of header that specifies the port being forwarded - PortHeader = "port" - // Name of header that specifies a request ID used to associate the error - // and data streams for a single forwarded connection - PortForwardRequestIDHeader = "requestID" -) - -// Type and constants for component health validation. -type ComponentConditionType string - -// These are the valid conditions for the component. -const ( - ComponentHealthy ComponentConditionType = "Healthy" -) - -type ComponentCondition struct { - Type ComponentConditionType - Status ConditionStatus - // +optional - Message string - // +optional - Error string -} - -// +genclient -// +genclient:nonNamespaced -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ComponentStatus (and ComponentStatusList) holds the cluster validation info. -type ComponentStatus struct { - metav1.TypeMeta - // +optional - metav1.ObjectMeta - - // +optional - Conditions []ComponentCondition -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -type ComponentStatusList struct { - metav1.TypeMeta - // +optional - metav1.ListMeta - - Items []ComponentStatus -} - -// SecurityContext holds security configuration that will be applied to a container. -// Some fields are present in both SecurityContext and PodSecurityContext. When both -// are set, the values in SecurityContext take precedence. -type SecurityContext struct { - // The capabilities to add/drop when running containers. - // Defaults to the default set of capabilities granted by the container runtime. - // +optional - Capabilities *Capabilities - // Run container in privileged mode. - // Processes in privileged containers are essentially equivalent to root on the host. - // Defaults to false. - // +optional - Privileged *bool - // The SELinux context to be applied to the container. - // If unspecified, the container runtime will allocate a random SELinux context for each - // container. May also be set in PodSecurityContext. If set in both SecurityContext and - // PodSecurityContext, the value specified in SecurityContext takes precedence. - // +optional - SELinuxOptions *SELinuxOptions - // The UID to run the entrypoint of the container process. - // Defaults to user specified in image metadata if unspecified. - // May also be set in PodSecurityContext. If set in both SecurityContext and - // PodSecurityContext, the value specified in SecurityContext takes precedence. - // +optional - RunAsUser *int64 - // 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. - // +optional - RunAsNonRoot *bool - // The read-only root filesystem allows you to restrict the locations that an application can write - // files to, ensuring the persistent data can only be written to mounts. - // +optional - ReadOnlyRootFilesystem *bool - // 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. - // +optional - AllowPrivilegeEscalation *bool -} - -// SELinuxOptions are the labels to be applied to the container. -type SELinuxOptions struct { - // SELinux user label - // +optional - User string - // SELinux role label - // +optional - Role string - // SELinux type label - // +optional - Type string - // SELinux level label. - // +optional - Level string -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// RangeAllocation is an opaque API object (not exposed to end users) that can be persisted to record -// the global allocation state of the cluster. The schema of Range and Data generic, in that Range -// should be a string representation of the inputs to a range (for instance, for IP allocation it -// might be a CIDR) and Data is an opaque blob understood by an allocator which is typically a -// binary range. Consumers should use annotations to record additional information (schema version, -// data encoding hints). A range allocation should *ALWAYS* be recreatable at any time by observation -// of the cluster, thus the object is less strongly typed than most. -type RangeAllocation struct { - metav1.TypeMeta - // +optional - metav1.ObjectMeta - // A string representing a unique label for a range of resources, such as a CIDR "10.0.0.0/8" or - // port range "10000-30000". Range is not strongly schema'd here. The Range is expected to define - // a start and end unless there is an implicit end. - Range string - // A byte array representing the serialized state of a range allocation. Additional clarifiers on - // the type or format of data should be represented with annotations. For IP allocations, this is - // represented as a bit array starting at the base IP of the CIDR in Range, with each bit representing - // a single allocated address (the fifth bit on CIDR 10.0.0.0/8 is 10.0.0.4). - Data []byte -} - -const ( - // "default-scheduler" is the name of default scheduler. - DefaultSchedulerName = "default-scheduler" - - // RequiredDuringScheduling affinity is not symmetric, but there is an implicit PreferredDuringScheduling affinity rule - // corresponding to every RequiredDuringScheduling affinity rule. - // When the --hard-pod-affinity-weight scheduler flag is not specified, - // DefaultHardPodAffinityWeight defines the weight of the implicit PreferredDuringScheduling affinity rule. - DefaultHardPodAffinitySymmetricWeight int = 1 -) diff --git a/pkg/api/v1/BUILD b/pkg/api/v1/BUILD deleted file mode 100644 index e0120ea92b6..00000000000 --- a/pkg/api/v1/BUILD +++ /dev/null @@ -1,78 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = [ - "conversion.go", - "defaults.go", - "doc.go", - "register.go", - "zz_generated.conversion.go", - "zz_generated.defaults.go", - ], - importpath = "k8s.io/kubernetes/pkg/api/v1", - deps = [ - "//pkg/api:go_default_library", - "//pkg/apis/extensions:go_default_library", - "//pkg/util/parsers:go_default_library", - "//pkg/util/pointer:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", - ], -) - -go_test( - name = "go_default_xtest", - srcs = [ - "conversion_test.go", - "defaults_test.go", - ], - importpath = "k8s.io/kubernetes/pkg/api/v1_test", - deps = [ - ":go_default_library", - "//pkg/api:go_default_library", - "//pkg/api/testapi:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//pkg/api/v1/endpoints:all-srcs", - "//pkg/api/v1/helper:all-srcs", - "//pkg/api/v1/node:all-srcs", - "//pkg/api/v1/pod:all-srcs", - "//pkg/api/v1/resource:all-srcs", - "//pkg/api/v1/service:all-srcs", - "//pkg/api/v1/validation:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/pkg/api/v1/conversion.go b/pkg/api/v1/conversion.go deleted file mode 100644 index 3076db9ccd7..00000000000 --- a/pkg/api/v1/conversion.go +++ /dev/null @@ -1,556 +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 v1 - -import ( - "fmt" - "reflect" - - "k8s.io/api/core/v1" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/conversion" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/extensions" -) - -// This is a "fast-path" that avoids reflection for common types. It focuses on the objects that are -// converted the most in the cluster. -// TODO: generate one of these for every external API group - this is to prove the impact -func addFastPathConversionFuncs(scheme *runtime.Scheme) error { - scheme.AddGenericConversionFunc(func(objA, objB interface{}, s conversion.Scope) (bool, error) { - switch a := objA.(type) { - case *v1.Pod: - switch b := objB.(type) { - case *api.Pod: - return true, Convert_v1_Pod_To_api_Pod(a, b, s) - } - case *api.Pod: - switch b := objB.(type) { - case *v1.Pod: - return true, Convert_api_Pod_To_v1_Pod(a, b, s) - } - - case *v1.Event: - switch b := objB.(type) { - case *api.Event: - return true, Convert_v1_Event_To_api_Event(a, b, s) - } - case *api.Event: - switch b := objB.(type) { - case *v1.Event: - return true, Convert_api_Event_To_v1_Event(a, b, s) - } - - case *v1.ReplicationController: - switch b := objB.(type) { - case *api.ReplicationController: - return true, Convert_v1_ReplicationController_To_api_ReplicationController(a, b, s) - } - case *api.ReplicationController: - switch b := objB.(type) { - case *v1.ReplicationController: - return true, Convert_api_ReplicationController_To_v1_ReplicationController(a, b, s) - } - - case *v1.Node: - switch b := objB.(type) { - case *api.Node: - return true, Convert_v1_Node_To_api_Node(a, b, s) - } - case *api.Node: - switch b := objB.(type) { - case *v1.Node: - return true, Convert_api_Node_To_v1_Node(a, b, s) - } - - case *v1.Namespace: - switch b := objB.(type) { - case *api.Namespace: - return true, Convert_v1_Namespace_To_api_Namespace(a, b, s) - } - case *api.Namespace: - switch b := objB.(type) { - case *v1.Namespace: - return true, Convert_api_Namespace_To_v1_Namespace(a, b, s) - } - - case *v1.Service: - switch b := objB.(type) { - case *api.Service: - return true, Convert_v1_Service_To_api_Service(a, b, s) - } - case *api.Service: - switch b := objB.(type) { - case *v1.Service: - return true, Convert_api_Service_To_v1_Service(a, b, s) - } - - case *v1.Endpoints: - switch b := objB.(type) { - case *api.Endpoints: - return true, Convert_v1_Endpoints_To_api_Endpoints(a, b, s) - } - case *api.Endpoints: - switch b := objB.(type) { - case *v1.Endpoints: - return true, Convert_api_Endpoints_To_v1_Endpoints(a, b, s) - } - - case *metav1.WatchEvent: - switch b := objB.(type) { - case *metav1.InternalEvent: - return true, metav1.Convert_versioned_Event_to_versioned_InternalEvent(a, b, s) - } - case *metav1.InternalEvent: - switch b := objB.(type) { - case *metav1.WatchEvent: - return true, metav1.Convert_versioned_InternalEvent_to_versioned_Event(a, b, s) - } - } - return false, nil - }) - return nil -} - -func addConversionFuncs(scheme *runtime.Scheme) error { - // Add non-generated conversion functions - err := scheme.AddConversionFuncs( - Convert_api_Pod_To_v1_Pod, - Convert_api_PodSpec_To_v1_PodSpec, - Convert_api_ReplicationControllerSpec_To_v1_ReplicationControllerSpec, - Convert_api_ServiceSpec_To_v1_ServiceSpec, - Convert_v1_Pod_To_api_Pod, - Convert_v1_PodSpec_To_api_PodSpec, - Convert_v1_ReplicationControllerSpec_To_api_ReplicationControllerSpec, - Convert_v1_Secret_To_api_Secret, - Convert_v1_ServiceSpec_To_api_ServiceSpec, - Convert_v1_ResourceList_To_api_ResourceList, - Convert_v1_ReplicationController_to_extensions_ReplicaSet, - Convert_v1_ReplicationControllerSpec_to_extensions_ReplicaSetSpec, - Convert_v1_ReplicationControllerStatus_to_extensions_ReplicaSetStatus, - Convert_extensions_ReplicaSet_to_v1_ReplicationController, - Convert_extensions_ReplicaSetSpec_to_v1_ReplicationControllerSpec, - Convert_extensions_ReplicaSetStatus_to_v1_ReplicationControllerStatus, - ) - if err != nil { - return err - } - - // Add field conversion funcs. - err = scheme.AddFieldLabelConversionFunc("v1", "Pod", - func(label, value string) (string, string, error) { - switch label { - case "metadata.annotations", - "metadata.labels", - "metadata.name", - "metadata.namespace", - "metadata.uid", - "spec.nodeName", - "spec.restartPolicy", - "spec.serviceAccountName", - "spec.schedulerName", - "status.phase", - "status.hostIP", - "status.podIP": - return label, value, nil - // This is for backwards compatibility with old v1 clients which send spec.host - case "spec.host": - return "spec.nodeName", value, nil - default: - return "", "", fmt.Errorf("field label not supported: %s", label) - } - }, - ) - if err != nil { - return err - } - err = scheme.AddFieldLabelConversionFunc("v1", "Node", - func(label, value string) (string, string, error) { - switch label { - case "metadata.name": - return label, value, nil - case "spec.unschedulable": - return label, value, nil - default: - return "", "", fmt.Errorf("field label not supported: %s", label) - } - }, - ) - if err != nil { - return err - } - err = scheme.AddFieldLabelConversionFunc("v1", "ReplicationController", - func(label, value string) (string, string, error) { - switch label { - case "metadata.name", - "metadata.namespace", - "status.replicas": - return label, value, nil - default: - return "", "", fmt.Errorf("field label not supported: %s", label) - } - }) - if err != nil { - return err - } - if err := AddFieldLabelConversionsForEvent(scheme); err != nil { - return err - } - if err := AddFieldLabelConversionsForNamespace(scheme); err != nil { - return err - } - if err := AddFieldLabelConversionsForSecret(scheme); err != nil { - return err - } - return nil -} - -func Convert_v1_ReplicationController_to_extensions_ReplicaSet(in *v1.ReplicationController, out *extensions.ReplicaSet, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1_ReplicationControllerSpec_to_extensions_ReplicaSetSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1_ReplicationControllerStatus_to_extensions_ReplicaSetStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -func Convert_v1_ReplicationControllerSpec_to_extensions_ReplicaSetSpec(in *v1.ReplicationControllerSpec, out *extensions.ReplicaSetSpec, s conversion.Scope) error { - out.Replicas = *in.Replicas - if in.Selector != nil { - metav1.Convert_map_to_unversioned_LabelSelector(&in.Selector, out.Selector, s) - } - if in.Template != nil { - if err := Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(in.Template, &out.Template, s); err != nil { - return err - } - } - return nil -} - -func Convert_v1_ReplicationControllerStatus_to_extensions_ReplicaSetStatus(in *v1.ReplicationControllerStatus, out *extensions.ReplicaSetStatus, s conversion.Scope) error { - out.Replicas = in.Replicas - out.FullyLabeledReplicas = in.FullyLabeledReplicas - out.ReadyReplicas = in.ReadyReplicas - out.AvailableReplicas = in.AvailableReplicas - out.ObservedGeneration = in.ObservedGeneration - return nil -} - -func Convert_extensions_ReplicaSet_to_v1_ReplicationController(in *extensions.ReplicaSet, out *v1.ReplicationController, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_extensions_ReplicaSetSpec_to_v1_ReplicationControllerSpec(&in.Spec, &out.Spec, s); err != nil { - fieldErr, ok := err.(*field.Error) - if !ok { - return err - } - if out.Annotations == nil { - out.Annotations = make(map[string]string) - } - out.Annotations[v1.NonConvertibleAnnotationPrefix+"/"+fieldErr.Field] = reflect.ValueOf(fieldErr.BadValue).String() - } - if err := Convert_extensions_ReplicaSetStatus_to_v1_ReplicationControllerStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -func Convert_extensions_ReplicaSetSpec_to_v1_ReplicationControllerSpec(in *extensions.ReplicaSetSpec, out *v1.ReplicationControllerSpec, s conversion.Scope) error { - out.Replicas = new(int32) - *out.Replicas = in.Replicas - out.MinReadySeconds = in.MinReadySeconds - var invalidErr error - if in.Selector != nil { - invalidErr = metav1.Convert_unversioned_LabelSelector_to_map(in.Selector, &out.Selector, s) - } - out.Template = new(v1.PodTemplateSpec) - if err := Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, out.Template, s); err != nil { - return err - } - return invalidErr -} - -func Convert_extensions_ReplicaSetStatus_to_v1_ReplicationControllerStatus(in *extensions.ReplicaSetStatus, out *v1.ReplicationControllerStatus, s conversion.Scope) error { - out.Replicas = in.Replicas - out.FullyLabeledReplicas = in.FullyLabeledReplicas - out.ReadyReplicas = in.ReadyReplicas - out.AvailableReplicas = in.AvailableReplicas - out.ObservedGeneration = in.ObservedGeneration - return nil -} - -func Convert_api_ReplicationControllerSpec_To_v1_ReplicationControllerSpec(in *api.ReplicationControllerSpec, out *v1.ReplicationControllerSpec, s conversion.Scope) error { - out.Replicas = &in.Replicas - out.MinReadySeconds = in.MinReadySeconds - out.Selector = in.Selector - if in.Template != nil { - out.Template = new(v1.PodTemplateSpec) - if err := Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(in.Template, out.Template, s); err != nil { - return err - } - } else { - out.Template = nil - } - return nil -} - -func Convert_v1_ReplicationControllerSpec_To_api_ReplicationControllerSpec(in *v1.ReplicationControllerSpec, out *api.ReplicationControllerSpec, s conversion.Scope) error { - if in.Replicas != nil { - out.Replicas = *in.Replicas - } - out.MinReadySeconds = in.MinReadySeconds - out.Selector = in.Selector - if in.Template != nil { - out.Template = new(api.PodTemplateSpec) - if err := Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(in.Template, out.Template, s); err != nil { - return err - } - } else { - out.Template = nil - } - return nil -} - -func Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(in *api.PodTemplateSpec, out *v1.PodTemplateSpec, s conversion.Scope) error { - if err := autoConvert_api_PodTemplateSpec_To_v1_PodTemplateSpec(in, out, s); err != nil { - return err - } - - return nil -} - -func Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(in *v1.PodTemplateSpec, out *api.PodTemplateSpec, s conversion.Scope) error { - if err := autoConvert_v1_PodTemplateSpec_To_api_PodTemplateSpec(in, out, s); err != nil { - return err - } - - return nil -} - -// The following two v1.PodSpec conversions are done here to support v1.ServiceAccount -// as an alias for ServiceAccountName. -func Convert_api_PodSpec_To_v1_PodSpec(in *api.PodSpec, out *v1.PodSpec, s conversion.Scope) error { - if err := autoConvert_api_PodSpec_To_v1_PodSpec(in, out, s); err != nil { - return err - } - - // DeprecatedServiceAccount is an alias for ServiceAccountName. - out.DeprecatedServiceAccount = in.ServiceAccountName - - if in.SecurityContext != nil { - // the host namespace fields have to be handled here for backward compatibility - // with v1.0.0 - out.HostPID = in.SecurityContext.HostPID - out.HostNetwork = in.SecurityContext.HostNetwork - out.HostIPC = in.SecurityContext.HostIPC - } - - return nil -} - -func Convert_v1_PodSpec_To_api_PodSpec(in *v1.PodSpec, out *api.PodSpec, s conversion.Scope) error { - if err := autoConvert_v1_PodSpec_To_api_PodSpec(in, out, s); err != nil { - return err - } - - // We support DeprecatedServiceAccount as an alias for ServiceAccountName. - // If both are specified, ServiceAccountName (the new field) wins. - if in.ServiceAccountName == "" { - out.ServiceAccountName = in.DeprecatedServiceAccount - } - - // the host namespace fields have to be handled specially for backward compatibility - // with v1.0.0 - if out.SecurityContext == nil { - out.SecurityContext = new(api.PodSecurityContext) - } - out.SecurityContext.HostNetwork = in.HostNetwork - out.SecurityContext.HostPID = in.HostPID - out.SecurityContext.HostIPC = in.HostIPC - - return nil -} - -func Convert_api_Pod_To_v1_Pod(in *api.Pod, out *v1.Pod, s conversion.Scope) error { - if err := autoConvert_api_Pod_To_v1_Pod(in, out, s); err != nil { - return err - } - - // drop init container annotations so they don't take effect on legacy kubelets. - // remove this once the oldest supported kubelet no longer honors the annotations over the field. - if len(out.Annotations) > 0 { - old := out.Annotations - out.Annotations = make(map[string]string, len(old)) - for k, v := range old { - out.Annotations[k] = v - } - delete(out.Annotations, "pod.beta.kubernetes.io/init-containers") - delete(out.Annotations, "pod.alpha.kubernetes.io/init-containers") - delete(out.Annotations, "pod.beta.kubernetes.io/init-container-statuses") - delete(out.Annotations, "pod.alpha.kubernetes.io/init-container-statuses") - } - - return nil -} - -func Convert_v1_Secret_To_api_Secret(in *v1.Secret, out *api.Secret, s conversion.Scope) error { - if err := autoConvert_v1_Secret_To_api_Secret(in, out, s); err != nil { - return err - } - - // StringData overwrites Data - if len(in.StringData) > 0 { - if out.Data == nil { - out.Data = map[string][]byte{} - } - for k, v := range in.StringData { - out.Data[k] = []byte(v) - } - } - - return nil -} -func Convert_api_SecurityContext_To_v1_SecurityContext(in *api.SecurityContext, out *v1.SecurityContext, s conversion.Scope) error { - if in.Capabilities != nil { - out.Capabilities = new(v1.Capabilities) - if err := Convert_api_Capabilities_To_v1_Capabilities(in.Capabilities, out.Capabilities, s); err != nil { - return err - } - } else { - out.Capabilities = nil - } - out.Privileged = in.Privileged - if in.SELinuxOptions != nil { - out.SELinuxOptions = new(v1.SELinuxOptions) - if err := Convert_api_SELinuxOptions_To_v1_SELinuxOptions(in.SELinuxOptions, out.SELinuxOptions, s); err != nil { - return err - } - } else { - out.SELinuxOptions = nil - } - out.RunAsUser = in.RunAsUser - out.RunAsNonRoot = in.RunAsNonRoot - out.ReadOnlyRootFilesystem = in.ReadOnlyRootFilesystem - out.AllowPrivilegeEscalation = in.AllowPrivilegeEscalation - return nil -} - -func Convert_api_PodSecurityContext_To_v1_PodSecurityContext(in *api.PodSecurityContext, out *v1.PodSecurityContext, s conversion.Scope) error { - out.SupplementalGroups = in.SupplementalGroups - if in.SELinuxOptions != nil { - out.SELinuxOptions = new(v1.SELinuxOptions) - if err := Convert_api_SELinuxOptions_To_v1_SELinuxOptions(in.SELinuxOptions, out.SELinuxOptions, s); err != nil { - return err - } - } else { - out.SELinuxOptions = nil - } - out.RunAsUser = in.RunAsUser - out.RunAsNonRoot = in.RunAsNonRoot - out.FSGroup = in.FSGroup - return nil -} - -func Convert_v1_PodSecurityContext_To_api_PodSecurityContext(in *v1.PodSecurityContext, out *api.PodSecurityContext, s conversion.Scope) error { - out.SupplementalGroups = in.SupplementalGroups - if in.SELinuxOptions != nil { - out.SELinuxOptions = new(api.SELinuxOptions) - if err := Convert_v1_SELinuxOptions_To_api_SELinuxOptions(in.SELinuxOptions, out.SELinuxOptions, s); err != nil { - return err - } - } else { - out.SELinuxOptions = nil - } - out.RunAsUser = in.RunAsUser - out.RunAsNonRoot = in.RunAsNonRoot - out.FSGroup = in.FSGroup - return nil -} - -// +k8s:conversion-fn=copy-only -func Convert_v1_ResourceList_To_api_ResourceList(in *v1.ResourceList, out *api.ResourceList, s conversion.Scope) error { - if *in == nil { - return nil - } - if *out == nil { - *out = make(api.ResourceList, len(*in)) - } - for key, val := range *in { - // Moved to defaults - // TODO(#18538): We round up resource values to milli scale to maintain API compatibility. - // In the future, we should instead reject values that need rounding. - // const milliScale = -3 - // val.RoundUp(milliScale) - - (*out)[api.ResourceName(key)] = val - } - return nil -} - -func AddFieldLabelConversionsForEvent(scheme *runtime.Scheme) error { - return scheme.AddFieldLabelConversionFunc("v1", "Event", - func(label, value string) (string, string, error) { - switch label { - case "involvedObject.kind", - "involvedObject.namespace", - "involvedObject.name", - "involvedObject.uid", - "involvedObject.apiVersion", - "involvedObject.resourceVersion", - "involvedObject.fieldPath", - "reason", - "source", - "type", - "metadata.namespace", - "metadata.name": - return label, value, nil - default: - return "", "", fmt.Errorf("field label not supported: %s", label) - } - }) -} - -func AddFieldLabelConversionsForNamespace(scheme *runtime.Scheme) error { - return scheme.AddFieldLabelConversionFunc("v1", "Namespace", - func(label, value string) (string, string, error) { - switch label { - case "status.phase", - "metadata.name": - return label, value, nil - default: - return "", "", fmt.Errorf("field label not supported: %s", label) - } - }) -} - -func AddFieldLabelConversionsForSecret(scheme *runtime.Scheme) error { - return scheme.AddFieldLabelConversionFunc("v1", "Secret", - func(label, value string) (string, string, error) { - switch label { - case "type", - "metadata.namespace", - "metadata.name": - return label, value, nil - default: - return "", "", fmt.Errorf("field label not supported: %s", label) - } - }) -} diff --git a/pkg/api/v1/conversion_test.go b/pkg/api/v1/conversion_test.go deleted file mode 100644 index 961701b1755..00000000000 --- a/pkg/api/v1/conversion_test.go +++ /dev/null @@ -1,227 +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 v1_test - -import ( - "net/url" - "reflect" - "testing" - "time" - - "k8s.io/api/core/v1" - 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/runtime" - "k8s.io/apimachinery/pkg/util/diff" - "k8s.io/kubernetes/pkg/api" - k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" - - // enforce that all types are installed - _ "k8s.io/kubernetes/pkg/api/testapi" -) - -func TestPodLogOptions(t *testing.T) { - sinceSeconds := int64(1) - sinceTime := metav1.NewTime(time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC).Local()) - tailLines := int64(2) - limitBytes := int64(3) - - versionedLogOptions := &v1.PodLogOptions{ - Container: "mycontainer", - Follow: true, - Previous: true, - SinceSeconds: &sinceSeconds, - SinceTime: &sinceTime, - Timestamps: true, - TailLines: &tailLines, - LimitBytes: &limitBytes, - } - unversionedLogOptions := &api.PodLogOptions{ - Container: "mycontainer", - Follow: true, - Previous: true, - SinceSeconds: &sinceSeconds, - SinceTime: &sinceTime, - Timestamps: true, - TailLines: &tailLines, - LimitBytes: &limitBytes, - } - expectedParameters := url.Values{ - "container": {"mycontainer"}, - "follow": {"true"}, - "previous": {"true"}, - "sinceSeconds": {"1"}, - "sinceTime": {"2000-01-01T12:34:56Z"}, - "timestamps": {"true"}, - "tailLines": {"2"}, - "limitBytes": {"3"}, - } - - codec := runtime.NewParameterCodec(api.Scheme) - - // unversioned -> query params - { - actualParameters, err := codec.EncodeParameters(unversionedLogOptions, v1.SchemeGroupVersion) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(actualParameters, expectedParameters) { - t.Fatalf("Expected\n%#v\ngot\n%#v", expectedParameters, actualParameters) - } - } - - // versioned -> query params - { - actualParameters, err := codec.EncodeParameters(versionedLogOptions, v1.SchemeGroupVersion) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(actualParameters, expectedParameters) { - t.Fatalf("Expected\n%#v\ngot\n%#v", expectedParameters, actualParameters) - } - } - - // query params -> versioned - { - convertedLogOptions := &v1.PodLogOptions{} - err := codec.DecodeParameters(expectedParameters, v1.SchemeGroupVersion, convertedLogOptions) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(convertedLogOptions, versionedLogOptions) { - t.Fatalf("Unexpected deserialization:\n%s", diff.ObjectGoPrintSideBySide(versionedLogOptions, convertedLogOptions)) - } - } - - // query params -> unversioned - { - convertedLogOptions := &api.PodLogOptions{} - err := codec.DecodeParameters(expectedParameters, v1.SchemeGroupVersion, convertedLogOptions) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(convertedLogOptions, unversionedLogOptions) { - t.Fatalf("Unexpected deserialization:\n%s", diff.ObjectGoPrintSideBySide(unversionedLogOptions, convertedLogOptions)) - } - } -} - -// TestPodSpecConversion tests that v1.ServiceAccount is an alias for -// ServiceAccountName. -func TestPodSpecConversion(t *testing.T) { - name, other := "foo", "bar" - - // Test internal -> v1. Should have both alias (DeprecatedServiceAccount) - // and new field (ServiceAccountName). - i := &api.PodSpec{ - ServiceAccountName: name, - } - v := v1.PodSpec{} - if err := api.Scheme.Convert(i, &v, nil); err != nil { - t.Fatalf("unexpected error: %v", err) - } - if v.ServiceAccountName != name { - t.Fatalf("want v1.ServiceAccountName %q, got %q", name, v.ServiceAccountName) - } - if v.DeprecatedServiceAccount != name { - t.Fatalf("want v1.DeprecatedServiceAccount %q, got %q", name, v.DeprecatedServiceAccount) - } - - // Test v1 -> internal. Either DeprecatedServiceAccount, ServiceAccountName, - // or both should translate to ServiceAccountName. ServiceAccountName wins - // if both are set. - testCases := []*v1.PodSpec{ - // New - {ServiceAccountName: name}, - // Alias - {DeprecatedServiceAccount: name}, - // Both: same - {ServiceAccountName: name, DeprecatedServiceAccount: name}, - // Both: different - {ServiceAccountName: name, DeprecatedServiceAccount: other}, - } - for k, v := range testCases { - got := api.PodSpec{} - err := api.Scheme.Convert(v, &got, nil) - if err != nil { - t.Fatalf("unexpected error for case %d: %v", k, err) - } - if got.ServiceAccountName != name { - t.Fatalf("want api.ServiceAccountName %q, got %q", name, got.ServiceAccountName) - } - } -} - -func TestResourceListConversion(t *testing.T) { - bigMilliQuantity := resource.NewQuantity(resource.MaxMilliValue, resource.DecimalSI) - bigMilliQuantity.Add(resource.MustParse("12345m")) - - tests := []struct { - input v1.ResourceList - expected api.ResourceList - }{ - { // No changes necessary. - input: v1.ResourceList{ - v1.ResourceMemory: resource.MustParse("30M"), - v1.ResourceCPU: resource.MustParse("100m"), - v1.ResourceStorage: resource.MustParse("1G"), - }, - expected: api.ResourceList{ - api.ResourceMemory: resource.MustParse("30M"), - api.ResourceCPU: resource.MustParse("100m"), - api.ResourceStorage: resource.MustParse("1G"), - }, - }, - { // Nano-scale values should be rounded up to milli-scale. - input: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("3.000023m"), - v1.ResourceMemory: resource.MustParse("500.000050m"), - }, - expected: api.ResourceList{ - api.ResourceCPU: resource.MustParse("4m"), - api.ResourceMemory: resource.MustParse("501m"), - }, - }, - { // Large values should still be accurate. - input: v1.ResourceList{ - v1.ResourceCPU: *bigMilliQuantity.Copy(), - v1.ResourceStorage: *bigMilliQuantity.Copy(), - }, - expected: api.ResourceList{ - api.ResourceCPU: *bigMilliQuantity.Copy(), - api.ResourceStorage: *bigMilliQuantity.Copy(), - }, - }, - } - - for i, test := range tests { - output := api.ResourceList{} - - // defaulting is a separate step from conversion that is applied when reading from the API or from etcd. - // perform that step explicitly. - k8s_api_v1.SetDefaults_ResourceList(&test.input) - - err := api.Scheme.Convert(&test.input, &output, nil) - if err != nil { - t.Fatalf("unexpected error for case %d: %v", i, err) - } - if !apiequality.Semantic.DeepEqual(test.expected, output) { - t.Errorf("unexpected conversion for case %d: Expected\n%+v;\nGot\n%+v", i, test.expected, output) - } - } -} diff --git a/pkg/api/v1/defaults.go b/pkg/api/v1/defaults.go deleted file mode 100644 index 9bf082d3d9a..00000000000 --- a/pkg/api/v1/defaults.go +++ /dev/null @@ -1,391 +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 v1 - -import ( - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/kubernetes/pkg/util/parsers" - utilpointer "k8s.io/kubernetes/pkg/util/pointer" -) - -func addDefaultingFuncs(scheme *runtime.Scheme) error { - return RegisterDefaults(scheme) -} - -func SetDefaults_ResourceList(obj *v1.ResourceList) { - for key, val := range *obj { - // TODO(#18538): We round up resource values to milli scale to maintain API compatibility. - // In the future, we should instead reject values that need rounding. - const milliScale = -3 - val.RoundUp(milliScale) - - (*obj)[v1.ResourceName(key)] = val - } -} - -func SetDefaults_PodExecOptions(obj *v1.PodExecOptions) { - obj.Stdout = true - obj.Stderr = true -} -func SetDefaults_PodAttachOptions(obj *v1.PodAttachOptions) { - obj.Stdout = true - obj.Stderr = true -} -func SetDefaults_ReplicationController(obj *v1.ReplicationController) { - var labels map[string]string - if obj.Spec.Template != nil { - labels = obj.Spec.Template.Labels - } - // TODO: support templates defined elsewhere when we support them in the API - if labels != nil { - if len(obj.Spec.Selector) == 0 { - obj.Spec.Selector = labels - } - if len(obj.Labels) == 0 { - obj.Labels = labels - } - } - if obj.Spec.Replicas == nil { - obj.Spec.Replicas = new(int32) - *obj.Spec.Replicas = 1 - } -} -func SetDefaults_Volume(obj *v1.Volume) { - if utilpointer.AllPtrFieldsNil(&obj.VolumeSource) { - obj.VolumeSource = v1.VolumeSource{ - EmptyDir: &v1.EmptyDirVolumeSource{}, - } - } -} -func SetDefaults_ContainerPort(obj *v1.ContainerPort) { - if obj.Protocol == "" { - obj.Protocol = v1.ProtocolTCP - } -} -func SetDefaults_Container(obj *v1.Container) { - if obj.ImagePullPolicy == "" { - // Ignore error and assume it has been validated elsewhere - _, tag, _, _ := parsers.ParseImageName(obj.Image) - - // Check image tag - if tag == "latest" { - obj.ImagePullPolicy = v1.PullAlways - } else { - obj.ImagePullPolicy = v1.PullIfNotPresent - } - } - if obj.TerminationMessagePath == "" { - obj.TerminationMessagePath = v1.TerminationMessagePathDefault - } - if obj.TerminationMessagePolicy == "" { - obj.TerminationMessagePolicy = v1.TerminationMessageReadFile - } -} -func SetDefaults_Service(obj *v1.Service) { - if obj.Spec.SessionAffinity == "" { - obj.Spec.SessionAffinity = v1.ServiceAffinityNone - } - if obj.Spec.SessionAffinity == v1.ServiceAffinityNone { - obj.Spec.SessionAffinityConfig = nil - } - if obj.Spec.SessionAffinity == v1.ServiceAffinityClientIP { - if obj.Spec.SessionAffinityConfig == nil || obj.Spec.SessionAffinityConfig.ClientIP == nil || obj.Spec.SessionAffinityConfig.ClientIP.TimeoutSeconds == nil { - timeoutSeconds := v1.DefaultClientIPServiceAffinitySeconds - obj.Spec.SessionAffinityConfig = &v1.SessionAffinityConfig{ - ClientIP: &v1.ClientIPConfig{ - TimeoutSeconds: &timeoutSeconds, - }, - } - } - } - if obj.Spec.Type == "" { - obj.Spec.Type = v1.ServiceTypeClusterIP - } - for i := range obj.Spec.Ports { - sp := &obj.Spec.Ports[i] - if sp.Protocol == "" { - sp.Protocol = v1.ProtocolTCP - } - if sp.TargetPort == intstr.FromInt(0) || sp.TargetPort == intstr.FromString("") { - sp.TargetPort = intstr.FromInt(int(sp.Port)) - } - } - // Defaults ExternalTrafficPolicy field for NodePort / LoadBalancer service - // to Global for consistency. - if (obj.Spec.Type == v1.ServiceTypeNodePort || - obj.Spec.Type == v1.ServiceTypeLoadBalancer) && - obj.Spec.ExternalTrafficPolicy == "" { - obj.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyTypeCluster - } -} -func SetDefaults_Pod(obj *v1.Pod) { - // If limits are specified, but requests are not, default requests to limits - // This is done here rather than a more specific defaulting pass on v1.ResourceRequirements - // because we only want this defaulting semantic to take place on a v1.Pod and not a v1.PodTemplate - for i := range obj.Spec.Containers { - // set requests to limits if requests are not specified, but limits are - if obj.Spec.Containers[i].Resources.Limits != nil { - if obj.Spec.Containers[i].Resources.Requests == nil { - obj.Spec.Containers[i].Resources.Requests = make(v1.ResourceList) - } - for key, value := range obj.Spec.Containers[i].Resources.Limits { - if _, exists := obj.Spec.Containers[i].Resources.Requests[key]; !exists { - obj.Spec.Containers[i].Resources.Requests[key] = *(value.Copy()) - } - } - } - } - for i := range obj.Spec.InitContainers { - if obj.Spec.InitContainers[i].Resources.Limits != nil { - if obj.Spec.InitContainers[i].Resources.Requests == nil { - obj.Spec.InitContainers[i].Resources.Requests = make(v1.ResourceList) - } - for key, value := range obj.Spec.InitContainers[i].Resources.Limits { - if _, exists := obj.Spec.InitContainers[i].Resources.Requests[key]; !exists { - obj.Spec.InitContainers[i].Resources.Requests[key] = *(value.Copy()) - } - } - } - } -} -func SetDefaults_PodSpec(obj *v1.PodSpec) { - if obj.DNSPolicy == "" { - obj.DNSPolicy = v1.DNSClusterFirst - } - if obj.RestartPolicy == "" { - obj.RestartPolicy = v1.RestartPolicyAlways - } - if obj.HostNetwork { - defaultHostNetworkPorts(&obj.Containers) - defaultHostNetworkPorts(&obj.InitContainers) - } - if obj.SecurityContext == nil { - obj.SecurityContext = &v1.PodSecurityContext{} - } - if obj.TerminationGracePeriodSeconds == nil { - period := int64(v1.DefaultTerminationGracePeriodSeconds) - obj.TerminationGracePeriodSeconds = &period - } - if obj.SchedulerName == "" { - obj.SchedulerName = v1.DefaultSchedulerName - } -} -func SetDefaults_Probe(obj *v1.Probe) { - if obj.TimeoutSeconds == 0 { - obj.TimeoutSeconds = 1 - } - if obj.PeriodSeconds == 0 { - obj.PeriodSeconds = 10 - } - if obj.SuccessThreshold == 0 { - obj.SuccessThreshold = 1 - } - if obj.FailureThreshold == 0 { - obj.FailureThreshold = 3 - } -} -func SetDefaults_SecretVolumeSource(obj *v1.SecretVolumeSource) { - if obj.DefaultMode == nil { - perm := int32(v1.SecretVolumeSourceDefaultMode) - obj.DefaultMode = &perm - } -} -func SetDefaults_ConfigMapVolumeSource(obj *v1.ConfigMapVolumeSource) { - if obj.DefaultMode == nil { - perm := int32(v1.ConfigMapVolumeSourceDefaultMode) - obj.DefaultMode = &perm - } -} -func SetDefaults_DownwardAPIVolumeSource(obj *v1.DownwardAPIVolumeSource) { - if obj.DefaultMode == nil { - perm := int32(v1.DownwardAPIVolumeSourceDefaultMode) - obj.DefaultMode = &perm - } -} -func SetDefaults_Secret(obj *v1.Secret) { - if obj.Type == "" { - obj.Type = v1.SecretTypeOpaque - } -} -func SetDefaults_ProjectedVolumeSource(obj *v1.ProjectedVolumeSource) { - if obj.DefaultMode == nil { - perm := int32(v1.ProjectedVolumeSourceDefaultMode) - obj.DefaultMode = &perm - } -} -func SetDefaults_PersistentVolume(obj *v1.PersistentVolume) { - if obj.Status.Phase == "" { - obj.Status.Phase = v1.VolumePending - } - if obj.Spec.PersistentVolumeReclaimPolicy == "" { - obj.Spec.PersistentVolumeReclaimPolicy = v1.PersistentVolumeReclaimRetain - } -} -func SetDefaults_PersistentVolumeClaim(obj *v1.PersistentVolumeClaim) { - if obj.Status.Phase == "" { - obj.Status.Phase = v1.ClaimPending - } -} -func SetDefaults_ISCSIVolumeSource(obj *v1.ISCSIVolumeSource) { - if obj.ISCSIInterface == "" { - obj.ISCSIInterface = "default" - } -} -func SetDefaults_AzureDiskVolumeSource(obj *v1.AzureDiskVolumeSource) { - if obj.CachingMode == nil { - obj.CachingMode = new(v1.AzureDataDiskCachingMode) - *obj.CachingMode = v1.AzureDataDiskCachingReadWrite - } - if obj.Kind == nil { - obj.Kind = new(v1.AzureDataDiskKind) - *obj.Kind = v1.AzureSharedBlobDisk - } - if obj.FSType == nil { - obj.FSType = new(string) - *obj.FSType = "ext4" - } - if obj.ReadOnly == nil { - obj.ReadOnly = new(bool) - *obj.ReadOnly = false - } -} -func SetDefaults_Endpoints(obj *v1.Endpoints) { - for i := range obj.Subsets { - ss := &obj.Subsets[i] - for i := range ss.Ports { - ep := &ss.Ports[i] - if ep.Protocol == "" { - ep.Protocol = v1.ProtocolTCP - } - } - } -} -func SetDefaults_HTTPGetAction(obj *v1.HTTPGetAction) { - if obj.Path == "" { - obj.Path = "/" - } - if obj.Scheme == "" { - obj.Scheme = v1.URISchemeHTTP - } -} -func SetDefaults_NamespaceStatus(obj *v1.NamespaceStatus) { - if obj.Phase == "" { - obj.Phase = v1.NamespaceActive - } -} -func SetDefaults_Node(obj *v1.Node) { - if obj.Spec.ExternalID == "" { - obj.Spec.ExternalID = obj.Name - } -} -func SetDefaults_NodeStatus(obj *v1.NodeStatus) { - if obj.Allocatable == nil && obj.Capacity != nil { - obj.Allocatable = make(v1.ResourceList, len(obj.Capacity)) - for key, value := range obj.Capacity { - obj.Allocatable[key] = *(value.Copy()) - } - obj.Allocatable = obj.Capacity - } -} -func SetDefaults_ObjectFieldSelector(obj *v1.ObjectFieldSelector) { - if obj.APIVersion == "" { - obj.APIVersion = "v1" - } -} -func SetDefaults_LimitRangeItem(obj *v1.LimitRangeItem) { - // for container limits, we apply default values - if obj.Type == v1.LimitTypeContainer { - - if obj.Default == nil { - obj.Default = make(v1.ResourceList) - } - if obj.DefaultRequest == nil { - obj.DefaultRequest = make(v1.ResourceList) - } - - // If a default limit is unspecified, but the max is specified, default the limit to the max - for key, value := range obj.Max { - if _, exists := obj.Default[key]; !exists { - obj.Default[key] = *(value.Copy()) - } - } - // If a default limit is specified, but the default request is not, default request to limit - for key, value := range obj.Default { - if _, exists := obj.DefaultRequest[key]; !exists { - obj.DefaultRequest[key] = *(value.Copy()) - } - } - // If a default request is not specified, but the min is provided, default request to the min - for key, value := range obj.Min { - if _, exists := obj.DefaultRequest[key]; !exists { - obj.DefaultRequest[key] = *(value.Copy()) - } - } - } -} -func SetDefaults_ConfigMap(obj *v1.ConfigMap) { - if obj.Data == nil { - obj.Data = make(map[string]string) - } -} - -// With host networking default all container ports to host ports. -func defaultHostNetworkPorts(containers *[]v1.Container) { - for i := range *containers { - for j := range (*containers)[i].Ports { - if (*containers)[i].Ports[j].HostPort == 0 { - (*containers)[i].Ports[j].HostPort = (*containers)[i].Ports[j].ContainerPort - } - } - } -} - -func SetDefaults_RBDVolumeSource(obj *v1.RBDVolumeSource) { - if obj.RBDPool == "" { - obj.RBDPool = "rbd" - } - if obj.RadosUser == "" { - obj.RadosUser = "admin" - } - if obj.Keyring == "" { - obj.Keyring = "/etc/ceph/keyring" - } -} - -func SetDefaults_ScaleIOVolumeSource(obj *v1.ScaleIOVolumeSource) { - if obj.ProtectionDomain == "" { - obj.ProtectionDomain = "default" - } - if obj.StoragePool == "" { - obj.StoragePool = "default" - } - if obj.StorageMode == "" { - obj.StorageMode = "ThinProvisioned" - } - if obj.FSType == "" { - obj.FSType = "xfs" - } -} - -func SetDefaults_HostPathVolumeSource(obj *v1.HostPathVolumeSource) { - typeVol := v1.HostPathUnset - if obj.Type == nil { - obj.Type = &typeVol - } -} diff --git a/pkg/api/v1/defaults_test.go b/pkg/api/v1/defaults_test.go deleted file mode 100644 index f5ad77dabac..00000000000 --- a/pkg/api/v1/defaults_test.go +++ /dev/null @@ -1,1337 +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 v1_test - -import ( - "fmt" - "reflect" - "testing" - - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/kubernetes/pkg/api" - k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" - - // enforce that all types are installed - _ "k8s.io/kubernetes/pkg/api/testapi" -) - -func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { - codec := api.Codecs.LegacyCodec(v1.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 = api.Scheme.Convert(obj2, obj3, nil) - if err != nil { - t.Errorf("%v\nSource: %#v", err, obj2) - return nil - } - return obj3 -} - -func TestSetDefaultReplicationController(t *testing.T) { - tests := []struct { - rc *v1.ReplicationController - expectLabels bool - expectSelector bool - }{ - { - rc: &v1.ReplicationController{ - Spec: v1.ReplicationControllerSpec{ - Template: &v1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "foo": "bar", - }, - }, - }, - }, - }, - expectLabels: true, - expectSelector: true, - }, - { - rc: &v1.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "bar": "foo", - }, - }, - Spec: v1.ReplicationControllerSpec{ - Template: &v1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "foo": "bar", - }, - }, - }, - }, - }, - expectLabels: false, - expectSelector: true, - }, - { - rc: &v1.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "bar": "foo", - }, - }, - Spec: v1.ReplicationControllerSpec{ - Selector: map[string]string{ - "some": "other", - }, - Template: &v1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "foo": "bar", - }, - }, - }, - }, - }, - expectLabels: false, - expectSelector: false, - }, - { - rc: &v1.ReplicationController{ - Spec: v1.ReplicationControllerSpec{ - Selector: map[string]string{ - "some": "other", - }, - Template: &v1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "foo": "bar", - }, - }, - }, - }, - }, - expectLabels: true, - expectSelector: false, - }, - } - - for _, test := range tests { - rc := test.rc - obj2 := roundTrip(t, runtime.Object(rc)) - rc2, ok := obj2.(*v1.ReplicationController) - if !ok { - t.Errorf("unexpected object: %v", rc2) - t.FailNow() - } - if test.expectSelector != reflect.DeepEqual(rc2.Spec.Selector, rc2.Spec.Template.Labels) { - if test.expectSelector { - t.Errorf("expected: %v, got: %v", rc2.Spec.Template.Labels, rc2.Spec.Selector) - } else { - t.Errorf("unexpected equality: %v", rc.Spec.Selector) - } - } - if test.expectLabels != reflect.DeepEqual(rc2.Labels, rc2.Spec.Template.Labels) { - if test.expectLabels { - t.Errorf("expected: %v, got: %v", rc2.Spec.Template.Labels, rc2.Labels) - } else { - t.Errorf("unexpected equality: %v", rc.Labels) - } - } - } -} - -func newInt(val int32) *int32 { - p := new(int32) - *p = val - return p -} - -func TestSetDefaultReplicationControllerReplicas(t *testing.T) { - tests := []struct { - rc v1.ReplicationController - expectReplicas int32 - }{ - { - rc: v1.ReplicationController{ - Spec: v1.ReplicationControllerSpec{ - Template: &v1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "foo": "bar", - }, - }, - }, - }, - }, - expectReplicas: 1, - }, - { - rc: v1.ReplicationController{ - Spec: v1.ReplicationControllerSpec{ - Replicas: newInt(0), - Template: &v1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "foo": "bar", - }, - }, - }, - }, - }, - expectReplicas: 0, - }, - { - rc: v1.ReplicationController{ - Spec: v1.ReplicationControllerSpec{ - Replicas: newInt(3), - Template: &v1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "foo": "bar", - }, - }, - }, - }, - }, - expectReplicas: 3, - }, - } - - for _, test := range tests { - rc := &test.rc - obj2 := roundTrip(t, runtime.Object(rc)) - rc2, ok := obj2.(*v1.ReplicationController) - if !ok { - t.Errorf("unexpected object: %v", rc2) - t.FailNow() - } - if rc2.Spec.Replicas == nil { - t.Errorf("unexpected nil Replicas") - } else if test.expectReplicas != *rc2.Spec.Replicas { - t.Errorf("expected: %d replicas, got: %d", test.expectReplicas, *rc2.Spec.Replicas) - } - } -} - -type InitContainerValidator func(got, expected *v1.Container) error - -func TestSetDefaultReplicationControllerInitContainers(t *testing.T) { - assertEnvFieldRef := func(got, expected *v1.Container) error { - if len(got.Env) != len(expected.Env) { - return fmt.Errorf("different number of env: got <%v>, expected <%v>", len(got.Env), len(expected.Env)) - } - - for j := range got.Env { - ge := &got.Env[j] - ee := &expected.Env[j] - - if ge.Name != ee.Name { - return fmt.Errorf("different name of env: got <%v>, expected <%v>", ge.Name, ee.Name) - } - - if ge.ValueFrom.FieldRef.APIVersion != ee.ValueFrom.FieldRef.APIVersion { - return fmt.Errorf("different api version of FieldRef <%v>: got <%v>, expected <%v>", - ge.Name, ge.ValueFrom.FieldRef.APIVersion, ee.ValueFrom.FieldRef.APIVersion) - } - } - return nil - } - - assertImagePullPolicy := func(got, expected *v1.Container) error { - if got.ImagePullPolicy != expected.ImagePullPolicy { - return fmt.Errorf("different image pull poicy: got <%v>, expected <%v>", got.ImagePullPolicy, expected.ImagePullPolicy) - } - return nil - } - - assertContainerPort := func(got, expected *v1.Container) error { - if len(got.Ports) != len(expected.Ports) { - return fmt.Errorf("different number of ports: got <%v>, expected <%v>", len(got.Ports), len(expected.Ports)) - } - - for i := range got.Ports { - gp := &got.Ports[i] - ep := &expected.Ports[i] - - if gp.Name != ep.Name { - return fmt.Errorf("different name of port: got <%v>, expected <%v>", gp.Name, ep.Name) - } - - if gp.Protocol != ep.Protocol { - return fmt.Errorf("different port protocol <%v>: got <%v>, expected <%v>", gp.Name, gp.Protocol, ep.Protocol) - } - } - - return nil - } - - assertResource := func(got, expected *v1.Container) error { - if len(got.Resources.Limits) != len(expected.Resources.Limits) { - return fmt.Errorf("different number of resources.Limits: got <%v>, expected <%v>", len(got.Resources.Limits), (expected.Resources.Limits)) - } - - for k, v := range got.Resources.Limits { - if ev, found := expected.Resources.Limits[v1.ResourceName(k)]; !found { - return fmt.Errorf("failed to find resource <%v> in expected resources.Limits.", k) - } else { - if ev.Value() != v.Value() { - return fmt.Errorf("different resource.Limits: got <%v>, expected <%v>.", v.Value(), ev.Value()) - } - } - } - - if len(got.Resources.Requests) != len(expected.Resources.Requests) { - return fmt.Errorf("different number of resources.Requests: got <%v>, expected <%v>", len(got.Resources.Requests), (expected.Resources.Requests)) - } - - for k, v := range got.Resources.Requests { - if ev, found := expected.Resources.Requests[v1.ResourceName(k)]; !found { - return fmt.Errorf("failed to find resource <%v> in expected resources.Requests.", k) - } else { - if ev.Value() != v.Value() { - return fmt.Errorf("different resource.Requests: got <%v>, expected <%v>.", v.Value(), ev.Value()) - } - } - } - - return nil - } - - assertProb := func(got, expected *v1.Container) error { - // Assert LivenessProbe - if got.LivenessProbe.Handler.HTTPGet.Path != expected.LivenessProbe.Handler.HTTPGet.Path || - got.LivenessProbe.Handler.HTTPGet.Scheme != expected.LivenessProbe.Handler.HTTPGet.Scheme || - got.LivenessProbe.FailureThreshold != expected.LivenessProbe.FailureThreshold || - got.LivenessProbe.SuccessThreshold != expected.LivenessProbe.SuccessThreshold || - got.LivenessProbe.PeriodSeconds != expected.LivenessProbe.PeriodSeconds || - got.LivenessProbe.TimeoutSeconds != expected.LivenessProbe.TimeoutSeconds { - return fmt.Errorf("different LivenessProbe: got <%v>, expected <%v>", got.LivenessProbe, expected.LivenessProbe) - } - - // Assert ReadinessProbe - if got.ReadinessProbe.Handler.HTTPGet.Path != expected.ReadinessProbe.Handler.HTTPGet.Path || - got.ReadinessProbe.Handler.HTTPGet.Scheme != expected.ReadinessProbe.Handler.HTTPGet.Scheme || - got.ReadinessProbe.FailureThreshold != expected.ReadinessProbe.FailureThreshold || - got.ReadinessProbe.SuccessThreshold != expected.ReadinessProbe.SuccessThreshold || - got.ReadinessProbe.PeriodSeconds != expected.ReadinessProbe.PeriodSeconds || - got.ReadinessProbe.TimeoutSeconds != expected.ReadinessProbe.TimeoutSeconds { - return fmt.Errorf("different ReadinessProbe: got <%v>, expected <%v>", got.ReadinessProbe, expected.ReadinessProbe) - } - - return nil - } - - assertLifeCycle := func(got, expected *v1.Container) error { - if got.Lifecycle.PostStart.HTTPGet.Path != expected.Lifecycle.PostStart.HTTPGet.Path || - got.Lifecycle.PostStart.HTTPGet.Scheme != expected.Lifecycle.PostStart.HTTPGet.Scheme { - return fmt.Errorf("different LifeCycle: got <%v>, expected <%v>", got.Lifecycle, expected.Lifecycle) - } - - return nil - } - - cpu, _ := resource.ParseQuantity("100m") - mem, _ := resource.ParseQuantity("100Mi") - - tests := []struct { - name string - rc v1.ReplicationController - expected []v1.Container - validators []InitContainerValidator - }{ - { - name: "imagePullIPolicy", - rc: v1.ReplicationController{ - Spec: v1.ReplicationControllerSpec{ - Template: &v1.PodTemplateSpec{ - Spec: v1.PodSpec{ - InitContainers: []v1.Container{ - { - Name: "install", - Image: "busybox", - }, - }, - }, - }, - }, - }, - expected: []v1.Container{ - { - ImagePullPolicy: v1.PullAlways, - }, - }, - validators: []InitContainerValidator{assertImagePullPolicy}, - }, - { - name: "FieldRef", - rc: v1.ReplicationController{ - Spec: v1.ReplicationControllerSpec{ - Template: &v1.PodTemplateSpec{ - Spec: v1.PodSpec{ - InitContainers: []v1.Container{ - { - Name: "fun", - Image: "alpine", - Env: []v1.EnvVar{ - { - Name: "MY_POD_IP", - ValueFrom: &v1.EnvVarSource{ - FieldRef: &v1.ObjectFieldSelector{ - APIVersion: "", - FieldPath: "status.podIP", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - expected: []v1.Container{ - { - Env: []v1.EnvVar{ - { - Name: "MY_POD_IP", - ValueFrom: &v1.EnvVarSource{ - FieldRef: &v1.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "status.podIP", - }, - }, - }, - }, - }, - }, - validators: []InitContainerValidator{assertEnvFieldRef}, - }, - { - name: "ContainerPort", - rc: v1.ReplicationController{ - Spec: v1.ReplicationControllerSpec{ - Template: &v1.PodTemplateSpec{ - Spec: v1.PodSpec{ - InitContainers: []v1.Container{ - { - Name: "fun", - Image: "alpine", - Ports: []v1.ContainerPort{ - { - Name: "default", - }, - }, - }, - }, - }, - }, - }, - }, - expected: []v1.Container{ - { - Ports: []v1.ContainerPort{ - { - Name: "default", - Protocol: v1.ProtocolTCP, - }, - }, - }, - }, - validators: []InitContainerValidator{assertContainerPort}, - }, - { - name: "Resources", - rc: v1.ReplicationController{ - Spec: v1.ReplicationControllerSpec{ - Template: &v1.PodTemplateSpec{ - Spec: v1.PodSpec{ - InitContainers: []v1.Container{ - { - Name: "fun", - Image: "alpine", - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("100m"), - v1.ResourceMemory: resource.MustParse("100Mi"), - }, - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("100m"), - v1.ResourceMemory: resource.MustParse("100Mi"), - }, - }, - }, - }, - }, - }, - }, - }, - expected: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: cpu, - v1.ResourceMemory: mem, - }, - Requests: v1.ResourceList{ - v1.ResourceCPU: cpu, - v1.ResourceMemory: mem, - }, - }, - }, - }, - validators: []InitContainerValidator{assertResource}, - }, - { - name: "Probe", - rc: v1.ReplicationController{ - Spec: v1.ReplicationControllerSpec{ - Template: &v1.PodTemplateSpec{ - Spec: v1.PodSpec{ - InitContainers: []v1.Container{ - { - Name: "fun", - Image: "alpine", - LivenessProbe: &v1.Probe{ - Handler: v1.Handler{ - HTTPGet: &v1.HTTPGetAction{ - Host: "localhost", - }, - }, - }, - ReadinessProbe: &v1.Probe{ - Handler: v1.Handler{ - HTTPGet: &v1.HTTPGetAction{ - Host: "localhost", - }, - }, - }, - }, - }, - }, - }, - }, - }, - expected: []v1.Container{ - { - LivenessProbe: &v1.Probe{ - Handler: v1.Handler{ - HTTPGet: &v1.HTTPGetAction{ - Path: "/", - Scheme: v1.URISchemeHTTP, - }, - }, - TimeoutSeconds: 1, - PeriodSeconds: 10, - SuccessThreshold: 1, - FailureThreshold: 3, - }, - ReadinessProbe: &v1.Probe{ - Handler: v1.Handler{ - HTTPGet: &v1.HTTPGetAction{ - Path: "/", - Scheme: v1.URISchemeHTTP, - }, - }, - TimeoutSeconds: 1, - PeriodSeconds: 10, - SuccessThreshold: 1, - FailureThreshold: 3, - }, - }, - }, - validators: []InitContainerValidator{assertProb}, - }, - { - name: "LifeCycle", - rc: v1.ReplicationController{ - Spec: v1.ReplicationControllerSpec{ - Template: &v1.PodTemplateSpec{ - Spec: v1.PodSpec{ - InitContainers: []v1.Container{ - { - Name: "fun", - Image: "alpine", - Ports: []v1.ContainerPort{ - { - Name: "default", - }, - }, - Lifecycle: &v1.Lifecycle{ - PostStart: &v1.Handler{ - HTTPGet: &v1.HTTPGetAction{ - Host: "localhost", - }, - }, - PreStop: &v1.Handler{ - HTTPGet: &v1.HTTPGetAction{ - Host: "localhost", - }, - }, - }, - }, - }, - }, - }, - }, - }, - expected: []v1.Container{ - { - Lifecycle: &v1.Lifecycle{ - PostStart: &v1.Handler{ - HTTPGet: &v1.HTTPGetAction{ - Path: "/", - Scheme: v1.URISchemeHTTP, - }, - }, - PreStop: &v1.Handler{ - HTTPGet: &v1.HTTPGetAction{ - Path: "/", - Scheme: v1.URISchemeHTTP, - }, - }, - }, - }, - }, - validators: []InitContainerValidator{assertLifeCycle}, - }, - } - - assertInitContainers := func(got, expected []v1.Container, validators []InitContainerValidator) error { - if len(got) != len(expected) { - return fmt.Errorf("different number of init container: got <%d>, expected <%d>", - len(got), len(expected)) - } - - for i := range got { - g := &got[i] - e := &expected[i] - - for _, validator := range validators { - if err := validator(g, e); err != nil { - return err - } - } - } - - return nil - } - - for _, test := range tests { - rc := &test.rc - obj2 := roundTrip(t, runtime.Object(rc)) - rc2, ok := obj2.(*v1.ReplicationController) - if !ok { - t.Errorf("unexpected object: %v", rc2) - t.FailNow() - } - - if err := assertInitContainers(rc2.Spec.Template.Spec.InitContainers, test.expected, test.validators); err != nil { - t.Errorf("test %v failed: %v", test.name, err) - } - } -} - -func TestSetDefaultService(t *testing.T) { - svc := &v1.Service{} - obj2 := roundTrip(t, runtime.Object(svc)) - svc2 := obj2.(*v1.Service) - if svc2.Spec.SessionAffinity != v1.ServiceAffinityNone { - t.Errorf("Expected default session affinity type:%s, got: %s", v1.ServiceAffinityNone, svc2.Spec.SessionAffinity) - } - if svc2.Spec.SessionAffinityConfig != nil { - t.Errorf("Expected empty session affinity config when session affinity type: %s, got: %v", v1.ServiceAffinityNone, svc2.Spec.SessionAffinityConfig) - } - if svc2.Spec.Type != v1.ServiceTypeClusterIP { - t.Errorf("Expected default type:%s, got: %s", v1.ServiceTypeClusterIP, svc2.Spec.Type) - } -} - -func TestSetDefaultServiceSessionAffinityConfig(t *testing.T) { - testCases := map[string]v1.Service{ - "SessionAffinityConfig is empty": { - Spec: v1.ServiceSpec{ - SessionAffinity: v1.ServiceAffinityClientIP, - SessionAffinityConfig: nil, - }, - }, - "ClientIP is empty": { - Spec: v1.ServiceSpec{ - SessionAffinity: v1.ServiceAffinityClientIP, - SessionAffinityConfig: &v1.SessionAffinityConfig{ - ClientIP: nil, - }, - }, - }, - "TimeoutSeconds is empty": { - Spec: v1.ServiceSpec{ - SessionAffinity: v1.ServiceAffinityClientIP, - SessionAffinityConfig: &v1.SessionAffinityConfig{ - ClientIP: &v1.ClientIPConfig{ - TimeoutSeconds: nil, - }, - }, - }, - }, - } - for name, test := range testCases { - obj2 := roundTrip(t, runtime.Object(&test)) - svc2 := obj2.(*v1.Service) - if svc2.Spec.SessionAffinityConfig == nil || svc2.Spec.SessionAffinityConfig.ClientIP == nil || svc2.Spec.SessionAffinityConfig.ClientIP.TimeoutSeconds == nil { - t.Fatalf("Case: %s, unexpected empty SessionAffinityConfig/ClientIP/TimeoutSeconds when session affinity type: %s, got: %v", name, v1.ServiceAffinityClientIP, svc2.Spec.SessionAffinityConfig) - } - if *svc2.Spec.SessionAffinityConfig.ClientIP.TimeoutSeconds != v1.DefaultClientIPServiceAffinitySeconds { - t.Errorf("Case: %s, default TimeoutSeconds should be %d when session affinity type: %s, got: %d", name, v1.DefaultClientIPServiceAffinitySeconds, v1.ServiceAffinityClientIP, *svc2.Spec.SessionAffinityConfig.ClientIP.TimeoutSeconds) - } - } -} - -func TestSetDefaultSecretVolumeSource(t *testing.T) { - s := v1.PodSpec{} - s.Volumes = []v1.Volume{ - { - VolumeSource: v1.VolumeSource{ - Secret: &v1.SecretVolumeSource{}, - }, - }, - } - pod := &v1.Pod{ - Spec: s, - } - output := roundTrip(t, runtime.Object(pod)) - pod2 := output.(*v1.Pod) - defaultMode := pod2.Spec.Volumes[0].VolumeSource.Secret.DefaultMode - expectedMode := v1.SecretVolumeSourceDefaultMode - - if defaultMode == nil || *defaultMode != expectedMode { - t.Errorf("Expected secret DefaultMode %v, got %v", expectedMode, defaultMode) - } -} - -func TestSetDefaultConfigMapVolumeSource(t *testing.T) { - s := v1.PodSpec{} - s.Volumes = []v1.Volume{ - { - VolumeSource: v1.VolumeSource{ - ConfigMap: &v1.ConfigMapVolumeSource{}, - }, - }, - } - pod := &v1.Pod{ - Spec: s, - } - output := roundTrip(t, runtime.Object(pod)) - pod2 := output.(*v1.Pod) - defaultMode := pod2.Spec.Volumes[0].VolumeSource.ConfigMap.DefaultMode - expectedMode := v1.ConfigMapVolumeSourceDefaultMode - - if defaultMode == nil || *defaultMode != expectedMode { - t.Errorf("Expected v1.ConfigMap DefaultMode %v, got %v", expectedMode, defaultMode) - } -} - -func TestSetDefaultDownwardAPIVolumeSource(t *testing.T) { - s := v1.PodSpec{} - s.Volumes = []v1.Volume{ - { - VolumeSource: v1.VolumeSource{ - DownwardAPI: &v1.DownwardAPIVolumeSource{}, - }, - }, - } - pod := &v1.Pod{ - Spec: s, - } - output := roundTrip(t, runtime.Object(pod)) - pod2 := output.(*v1.Pod) - defaultMode := pod2.Spec.Volumes[0].VolumeSource.DownwardAPI.DefaultMode - expectedMode := v1.DownwardAPIVolumeSourceDefaultMode - - if defaultMode == nil || *defaultMode != expectedMode { - t.Errorf("Expected DownwardAPI DefaultMode %v, got %v", expectedMode, defaultMode) - } -} - -func TestSetDefaultProjectedVolumeSource(t *testing.T) { - s := v1.PodSpec{} - s.Volumes = []v1.Volume{ - { - VolumeSource: v1.VolumeSource{ - Projected: &v1.ProjectedVolumeSource{}, - }, - }, - } - pod := &v1.Pod{ - Spec: s, - } - output := roundTrip(t, runtime.Object(pod)) - pod2 := output.(*v1.Pod) - defaultMode := pod2.Spec.Volumes[0].VolumeSource.Projected.DefaultMode - expectedMode := v1.ProjectedVolumeSourceDefaultMode - - if defaultMode == nil || *defaultMode != expectedMode { - t.Errorf("Expected v1.ProjectedVolumeSource DefaultMode %v, got %v", expectedMode, defaultMode) - } -} - -func TestSetDefaultSecret(t *testing.T) { - s := &v1.Secret{} - obj2 := roundTrip(t, runtime.Object(s)) - s2 := obj2.(*v1.Secret) - - if s2.Type != v1.SecretTypeOpaque { - t.Errorf("Expected secret type %v, got %v", v1.SecretTypeOpaque, s2.Type) - } -} - -func TestSetDefaultPersistentVolume(t *testing.T) { - pv := &v1.PersistentVolume{} - obj2 := roundTrip(t, runtime.Object(pv)) - pv2 := obj2.(*v1.PersistentVolume) - - if pv2.Status.Phase != v1.VolumePending { - t.Errorf("Expected volume phase %v, got %v", v1.VolumePending, pv2.Status.Phase) - } - if pv2.Spec.PersistentVolumeReclaimPolicy != v1.PersistentVolumeReclaimRetain { - t.Errorf("Expected pv reclaim policy %v, got %v", v1.PersistentVolumeReclaimRetain, pv2.Spec.PersistentVolumeReclaimPolicy) - } -} - -func TestSetDefaultPersistentVolumeClaim(t *testing.T) { - pvc := &v1.PersistentVolumeClaim{} - obj2 := roundTrip(t, runtime.Object(pvc)) - pvc2 := obj2.(*v1.PersistentVolumeClaim) - - if pvc2.Status.Phase != v1.ClaimPending { - t.Errorf("Expected claim phase %v, got %v", v1.ClaimPending, pvc2.Status.Phase) - } -} - -func TestSetDefaulEndpointsProtocol(t *testing.T) { - in := &v1.Endpoints{Subsets: []v1.EndpointSubset{ - {Ports: []v1.EndpointPort{{}, {Protocol: "UDP"}, {}}}, - }} - obj := roundTrip(t, runtime.Object(in)) - out := obj.(*v1.Endpoints) - - for i := range out.Subsets { - for j := range out.Subsets[i].Ports { - if in.Subsets[i].Ports[j].Protocol == "" { - if out.Subsets[i].Ports[j].Protocol != v1.ProtocolTCP { - t.Errorf("Expected protocol %s, got %s", v1.ProtocolTCP, out.Subsets[i].Ports[j].Protocol) - } - } else { - if out.Subsets[i].Ports[j].Protocol != in.Subsets[i].Ports[j].Protocol { - t.Errorf("Expected protocol %s, got %s", in.Subsets[i].Ports[j].Protocol, out.Subsets[i].Ports[j].Protocol) - } - } - } - } -} - -func TestSetDefaulServiceTargetPort(t *testing.T) { - in := &v1.Service{Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1234}}}} - obj := roundTrip(t, runtime.Object(in)) - out := obj.(*v1.Service) - if out.Spec.Ports[0].TargetPort != intstr.FromInt(1234) { - t.Errorf("Expected TargetPort to be defaulted, got %v", out.Spec.Ports[0].TargetPort) - } - - in = &v1.Service{Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1234, TargetPort: intstr.FromInt(5678)}}}} - obj = roundTrip(t, runtime.Object(in)) - out = obj.(*v1.Service) - if out.Spec.Ports[0].TargetPort != intstr.FromInt(5678) { - t.Errorf("Expected TargetPort to be unchanged, got %v", out.Spec.Ports[0].TargetPort) - } -} - -func TestSetDefaultServicePort(t *testing.T) { - // Unchanged if set. - in := &v1.Service{Spec: v1.ServiceSpec{ - Ports: []v1.ServicePort{ - {Protocol: "UDP", Port: 9376, TargetPort: intstr.FromString("p")}, - {Protocol: "UDP", Port: 8675, TargetPort: intstr.FromInt(309)}, - }, - }} - out := roundTrip(t, runtime.Object(in)).(*v1.Service) - if out.Spec.Ports[0].Protocol != v1.ProtocolUDP { - t.Errorf("Expected protocol %s, got %s", v1.ProtocolUDP, out.Spec.Ports[0].Protocol) - } - if out.Spec.Ports[0].TargetPort != intstr.FromString("p") { - t.Errorf("Expected port %v, got %v", in.Spec.Ports[0].Port, out.Spec.Ports[0].TargetPort) - } - if out.Spec.Ports[1].Protocol != v1.ProtocolUDP { - t.Errorf("Expected protocol %s, got %s", v1.ProtocolUDP, out.Spec.Ports[1].Protocol) - } - if out.Spec.Ports[1].TargetPort != intstr.FromInt(309) { - t.Errorf("Expected port %v, got %v", in.Spec.Ports[1].Port, out.Spec.Ports[1].TargetPort) - } - - // Defaulted. - in = &v1.Service{Spec: v1.ServiceSpec{ - Ports: []v1.ServicePort{ - {Protocol: "", Port: 9376, TargetPort: intstr.FromString("")}, - {Protocol: "", Port: 8675, TargetPort: intstr.FromInt(0)}, - }, - }} - out = roundTrip(t, runtime.Object(in)).(*v1.Service) - if out.Spec.Ports[0].Protocol != v1.ProtocolTCP { - t.Errorf("Expected protocol %s, got %s", v1.ProtocolTCP, out.Spec.Ports[0].Protocol) - } - if out.Spec.Ports[0].TargetPort != intstr.FromInt(int(in.Spec.Ports[0].Port)) { - t.Errorf("Expected port %v, got %v", in.Spec.Ports[0].Port, out.Spec.Ports[0].TargetPort) - } - if out.Spec.Ports[1].Protocol != v1.ProtocolTCP { - t.Errorf("Expected protocol %s, got %s", v1.ProtocolTCP, out.Spec.Ports[1].Protocol) - } - if out.Spec.Ports[1].TargetPort != intstr.FromInt(int(in.Spec.Ports[1].Port)) { - t.Errorf("Expected port %v, got %v", in.Spec.Ports[1].Port, out.Spec.Ports[1].TargetPort) - } -} - -func TestSetDefaulServiceExternalTraffic(t *testing.T) { - in := &v1.Service{} - obj := roundTrip(t, runtime.Object(in)) - out := obj.(*v1.Service) - if out.Spec.ExternalTrafficPolicy != "" { - t.Errorf("Expected ExternalTrafficPolicy to be empty, got %v", out.Spec.ExternalTrafficPolicy) - } - - in = &v1.Service{Spec: v1.ServiceSpec{Type: v1.ServiceTypeNodePort}} - obj = roundTrip(t, runtime.Object(in)) - out = obj.(*v1.Service) - if out.Spec.ExternalTrafficPolicy != v1.ServiceExternalTrafficPolicyTypeCluster { - t.Errorf("Expected ExternalTrafficPolicy to be %v, got %v", v1.ServiceExternalTrafficPolicyTypeCluster, out.Spec.ExternalTrafficPolicy) - } - - in = &v1.Service{Spec: v1.ServiceSpec{Type: v1.ServiceTypeLoadBalancer}} - obj = roundTrip(t, runtime.Object(in)) - out = obj.(*v1.Service) - if out.Spec.ExternalTrafficPolicy != v1.ServiceExternalTrafficPolicyTypeCluster { - t.Errorf("Expected ExternalTrafficPolicy to be %v, got %v", v1.ServiceExternalTrafficPolicyTypeCluster, out.Spec.ExternalTrafficPolicy) - } -} - -func TestSetDefaultNamespace(t *testing.T) { - s := &v1.Namespace{} - obj2 := roundTrip(t, runtime.Object(s)) - s2 := obj2.(*v1.Namespace) - - if s2.Status.Phase != v1.NamespaceActive { - t.Errorf("Expected phase %v, got %v", v1.NamespaceActive, s2.Status.Phase) - } -} - -func TestSetDefaultPodSpecHostNetwork(t *testing.T) { - portNum := int32(8080) - s := v1.PodSpec{} - s.HostNetwork = true - s.Containers = []v1.Container{ - { - Ports: []v1.ContainerPort{ - { - ContainerPort: portNum, - }, - }, - }, - } - s.InitContainers = []v1.Container{ - { - Ports: []v1.ContainerPort{ - { - ContainerPort: portNum, - }, - }, - }, - } - pod := &v1.Pod{ - Spec: s, - } - obj2 := roundTrip(t, runtime.Object(pod)) - pod2 := obj2.(*v1.Pod) - s2 := pod2.Spec - - hostPortNum := s2.Containers[0].Ports[0].HostPort - if hostPortNum != portNum { - t.Errorf("Expected container port to be defaulted, was made %d instead of %d", hostPortNum, portNum) - } - - hostPortNum = s2.InitContainers[0].Ports[0].HostPort - if hostPortNum != portNum { - t.Errorf("Expected container port to be defaulted, was made %d instead of %d", hostPortNum, portNum) - } -} - -func TestSetDefaultNodeExternalID(t *testing.T) { - name := "node0" - n := &v1.Node{} - n.Name = name - obj2 := roundTrip(t, runtime.Object(n)) - n2 := obj2.(*v1.Node) - if n2.Spec.ExternalID != name { - t.Errorf("Expected default External ID: %s, got: %s", name, n2.Spec.ExternalID) - } - if n2.Spec.ProviderID != "" { - t.Errorf("Expected empty default Cloud Provider ID, got: %s", n2.Spec.ProviderID) - } -} - -func TestSetDefaultNodeStatusAllocatable(t *testing.T) { - capacity := v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1000m"), - v1.ResourceMemory: resource.MustParse("10G"), - } - allocatable := v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("500m"), - v1.ResourceMemory: resource.MustParse("5G"), - } - tests := []struct { - capacity v1.ResourceList - allocatable v1.ResourceList - expectedAllocatable v1.ResourceList - }{{ // Everything set, no defaulting. - capacity: capacity, - allocatable: allocatable, - expectedAllocatable: allocatable, - }, { // Allocatable set, no defaulting. - capacity: nil, - allocatable: allocatable, - expectedAllocatable: allocatable, - }, { // Capacity set, allocatable defaults to capacity. - capacity: capacity, - allocatable: nil, - expectedAllocatable: capacity, - }, { // Nothing set, allocatable "defaults" to capacity. - capacity: nil, - allocatable: nil, - expectedAllocatable: nil, - }} - - copyResourceList := func(rl v1.ResourceList) v1.ResourceList { - if rl == nil { - return nil - } - copy := make(v1.ResourceList, len(rl)) - for k, v := range rl { - copy[k] = *v.Copy() - } - return copy - } - - resourceListsEqual := func(a v1.ResourceList, b v1.ResourceList) bool { - if len(a) != len(b) { - return false - } - for k, v := range a { - vb, found := b[k] - if !found { - return false - } - if v.Cmp(vb) != 0 { - return false - } - } - return true - } - - for i, testcase := range tests { - node := v1.Node{ - Status: v1.NodeStatus{ - Capacity: copyResourceList(testcase.capacity), - Allocatable: copyResourceList(testcase.allocatable), - }, - } - node2 := roundTrip(t, runtime.Object(&node)).(*v1.Node) - actual := node2.Status.Allocatable - expected := testcase.expectedAllocatable - if !resourceListsEqual(expected, actual) { - t.Errorf("[%d] Expected v1.NodeStatus.Allocatable: %+v; Got: %+v", i, expected, actual) - } - } -} - -func TestSetDefaultObjectFieldSelectorAPIVersion(t *testing.T) { - s := v1.PodSpec{ - Containers: []v1.Container{ - { - Env: []v1.EnvVar{ - { - ValueFrom: &v1.EnvVarSource{ - FieldRef: &v1.ObjectFieldSelector{}, - }, - }, - }, - }, - }, - } - pod := &v1.Pod{ - Spec: s, - } - obj2 := roundTrip(t, runtime.Object(pod)) - pod2 := obj2.(*v1.Pod) - s2 := pod2.Spec - - apiVersion := s2.Containers[0].Env[0].ValueFrom.FieldRef.APIVersion - if apiVersion != "v1" { - t.Errorf("Expected default APIVersion v1, got: %v", apiVersion) - } -} - -func TestSetMinimumScalePod(t *testing.T) { - // verify we default if limits are specified (and that request=0 is preserved) - s := v1.PodSpec{} - s.Containers = []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceMemory: resource.MustParse("1n"), - }, - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2n"), - }, - }, - }, - } - s.InitContainers = []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceMemory: resource.MustParse("1n"), - }, - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2n"), - }, - }, - }, - } - pod := &v1.Pod{ - Spec: s, - } - k8s_api_v1.SetObjectDefaults_Pod(pod) - - if expect := resource.MustParse("1m"); expect.Cmp(pod.Spec.Containers[0].Resources.Requests[v1.ResourceMemory]) != 0 { - t.Errorf("did not round resources: %#v", pod.Spec.Containers[0].Resources) - } - if expect := resource.MustParse("1m"); expect.Cmp(pod.Spec.InitContainers[0].Resources.Requests[v1.ResourceMemory]) != 0 { - t.Errorf("did not round resources: %#v", pod.Spec.InitContainers[0].Resources) - } -} - -func TestSetDefaultRequestsPod(t *testing.T) { - // verify we default if limits are specified (and that request=0 is preserved) - s := v1.PodSpec{} - s.Containers = []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceMemory: resource.MustParse("0"), - }, - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("100m"), - v1.ResourceMemory: resource.MustParse("1Gi"), - }, - }, - }, - } - s.InitContainers = []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceMemory: resource.MustParse("0"), - }, - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("100m"), - v1.ResourceMemory: resource.MustParse("1Gi"), - }, - }, - }, - } - pod := &v1.Pod{ - Spec: s, - } - output := roundTrip(t, runtime.Object(pod)) - pod2 := output.(*v1.Pod) - defaultRequest := pod2.Spec.Containers[0].Resources.Requests - if requestValue := defaultRequest[v1.ResourceCPU]; requestValue.String() != "100m" { - t.Errorf("Expected request cpu: %s, got: %s", "100m", requestValue.String()) - } - if requestValue := defaultRequest[v1.ResourceMemory]; requestValue.String() != "0" { - t.Errorf("Expected request memory: %s, got: %s", "0", requestValue.String()) - } - defaultRequest = pod2.Spec.InitContainers[0].Resources.Requests - if requestValue := defaultRequest[v1.ResourceCPU]; requestValue.String() != "100m" { - t.Errorf("Expected request cpu: %s, got: %s", "100m", requestValue.String()) - } - if requestValue := defaultRequest[v1.ResourceMemory]; requestValue.String() != "0" { - t.Errorf("Expected request memory: %s, got: %s", "0", requestValue.String()) - } - - // verify we do nothing if no limits are specified - s = v1.PodSpec{} - s.Containers = []v1.Container{{}} - s.InitContainers = []v1.Container{{}} - pod = &v1.Pod{ - Spec: s, - } - output = roundTrip(t, runtime.Object(pod)) - pod2 = output.(*v1.Pod) - defaultRequest = pod2.Spec.Containers[0].Resources.Requests - if requestValue := defaultRequest[v1.ResourceCPU]; requestValue.String() != "0" { - t.Errorf("Expected 0 request value, got: %s", requestValue.String()) - } - defaultRequest = pod2.Spec.InitContainers[0].Resources.Requests - if requestValue := defaultRequest[v1.ResourceCPU]; requestValue.String() != "0" { - t.Errorf("Expected 0 request value, got: %s", requestValue.String()) - } -} - -func TestDefaultRequestIsNotSetForReplicationController(t *testing.T) { - s := v1.PodSpec{} - s.Containers = []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("100m"), - }, - }, - }, - } - rc := &v1.ReplicationController{ - Spec: v1.ReplicationControllerSpec{ - Replicas: newInt(3), - Template: &v1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "foo": "bar", - }, - }, - Spec: s, - }, - }, - } - output := roundTrip(t, runtime.Object(rc)) - rc2 := output.(*v1.ReplicationController) - defaultRequest := rc2.Spec.Template.Spec.Containers[0].Resources.Requests - requestValue := defaultRequest[v1.ResourceCPU] - if requestValue.String() != "0" { - t.Errorf("Expected 0 request value, got: %s", requestValue.String()) - } -} - -func TestSetDefaultLimitRangeItem(t *testing.T) { - limitRange := &v1.LimitRange{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-defaults", - }, - Spec: v1.LimitRangeSpec{ - Limits: []v1.LimitRangeItem{{ - Type: v1.LimitTypeContainer, - Max: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("100m"), - }, - Min: v1.ResourceList{ - v1.ResourceMemory: resource.MustParse("100Mi"), - }, - Default: v1.ResourceList{}, - DefaultRequest: v1.ResourceList{}, - }}, - }, - } - - output := roundTrip(t, runtime.Object(limitRange)) - limitRange2 := output.(*v1.LimitRange) - defaultLimit := limitRange2.Spec.Limits[0].Default - defaultRequest := limitRange2.Spec.Limits[0].DefaultRequest - - // verify that default cpu was set to the max - defaultValue := defaultLimit[v1.ResourceCPU] - if defaultValue.String() != "100m" { - t.Errorf("Expected default cpu: %s, got: %s", "100m", defaultValue.String()) - } - // verify that default request was set to the limit - requestValue := defaultRequest[v1.ResourceCPU] - if requestValue.String() != "100m" { - t.Errorf("Expected request cpu: %s, got: %s", "100m", requestValue.String()) - } - // verify that if a min is provided, it will be the default if no limit is specified - requestMinValue := defaultRequest[v1.ResourceMemory] - if requestMinValue.String() != "100Mi" { - t.Errorf("Expected request memory: %s, got: %s", "100Mi", requestMinValue.String()) - } -} - -func TestSetDefaultProbe(t *testing.T) { - originalProbe := v1.Probe{} - expectedProbe := v1.Probe{ - InitialDelaySeconds: 0, - TimeoutSeconds: 1, - PeriodSeconds: 10, - SuccessThreshold: 1, - FailureThreshold: 3, - } - - pod := &v1.Pod{ - Spec: v1.PodSpec{ - Containers: []v1.Container{{LivenessProbe: &originalProbe}}, - }, - } - - output := roundTrip(t, runtime.Object(pod)).(*v1.Pod) - actualProbe := *output.Spec.Containers[0].LivenessProbe - if actualProbe != expectedProbe { - t.Errorf("Expected probe: %+v\ngot: %+v\n", expectedProbe, actualProbe) - } -} - -func TestSetDefaultSchedulerName(t *testing.T) { - pod := &v1.Pod{} - - output := roundTrip(t, runtime.Object(pod)).(*v1.Pod) - if output.Spec.SchedulerName != v1.DefaultSchedulerName { - t.Errorf("Expected scheduler name: %+v\ngot: %+v\n", v1.DefaultSchedulerName, output.Spec.SchedulerName) - } -} - -func TestSetDefaultHostPathVolumeSource(t *testing.T) { - s := v1.PodSpec{} - s.Volumes = []v1.Volume{ - { - VolumeSource: v1.VolumeSource{ - HostPath: &v1.HostPathVolumeSource{Path: "foo"}, - }, - }, - } - pod := &v1.Pod{ - Spec: s, - } - output := roundTrip(t, runtime.Object(pod)) - pod2 := output.(*v1.Pod) - defaultType := pod2.Spec.Volumes[0].VolumeSource.HostPath.Type - expectedType := v1.HostPathUnset - - if defaultType == nil || *defaultType != expectedType { - t.Errorf("Expected v1.HostPathVolumeSource default type %v, got %v", expectedType, defaultType) - } -} diff --git a/pkg/api/v1/doc.go b/pkg/api/v1/doc.go deleted file mode 100644 index 0f1e0d495e2..00000000000 --- a/pkg/api/v1/doc.go +++ /dev/null @@ -1,23 +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. -*/ - -// +k8s:conversion-gen=k8s.io/kubernetes/pkg/api -// +k8s:conversion-gen-external-types=../../../vendor/k8s.io/api/core/v1 -// +k8s:defaulter-gen=TypeMeta -// +k8s:defaulter-gen-input=../../../vendor/k8s.io/api/core/v1 - -// Package v1 is the v1 version of the API. -package v1 // import "k8s.io/kubernetes/pkg/api/v1" diff --git a/pkg/api/v1/endpoints/BUILD b/pkg/api/v1/endpoints/BUILD index f9597b402ea..d23918e050b 100644 --- a/pkg/api/v1/endpoints/BUILD +++ b/pkg/api/v1/endpoints/BUILD @@ -20,8 +20,8 @@ go_library( go_test( name = "go_default_test", srcs = ["util_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/api/v1/endpoints", - library = ":go_default_library", deps = [ "//vendor/github.com/davecgh/go-spew/spew:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/api/v1/helper/BUILD b/pkg/api/v1/helper/BUILD deleted file mode 100644 index 10448453104..00000000000 --- a/pkg/api/v1/helper/BUILD +++ /dev/null @@ -1,50 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = ["helpers_test.go"], - importpath = "k8s.io/kubernetes/pkg/api/v1/helper", - library = ":go_default_library", - deps = [ - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - ], -) - -go_library( - name = "go_default_library", - srcs = ["helpers.go"], - importpath = "k8s.io/kubernetes/pkg/api/v1/helper", - deps = [ - "//pkg/api/helper:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/selection:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//pkg/api/v1/helper/qos:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/pkg/api/v1/helper/helpers.go b/pkg/api/v1/helper/helpers.go deleted file mode 100644 index 92cacf8d282..00000000000 --- a/pkg/api/v1/helper/helpers.go +++ /dev/null @@ -1,478 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES 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 ( - "encoding/json" - "fmt" - "strings" - - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/selection" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api/helper" -) - -// IsExtendedResourceName returns true if the resource name is not in the -// default namespace, or it has the opaque integer resource prefix. -func IsExtendedResourceName(name v1.ResourceName) bool { - // TODO: Remove OIR part following deprecation. - return !IsDefaultNamespaceResource(name) || IsOpaqueIntResourceName(name) -} - -// IsDefaultNamespaceResource returns true if the resource name is in the -// *kubernetes.io/ namespace. Partially-qualified (unprefixed) names are -// implicitly in the kubernetes.io/ namespace. -func IsDefaultNamespaceResource(name v1.ResourceName) bool { - return !strings.Contains(string(name), "/") || - strings.Contains(string(name), v1.ResourceDefaultNamespacePrefix) - -} - -// IsHugePageResourceName returns true if the resource name has the huge page -// resource prefix. -func IsHugePageResourceName(name v1.ResourceName) bool { - return strings.HasPrefix(string(name), v1.ResourceHugePagesPrefix) -} - -// HugePageResourceName returns a ResourceName with the canonical hugepage -// prefix prepended for the specified page size. The page size is converted -// to its canonical representation. -func HugePageResourceName(pageSize resource.Quantity) v1.ResourceName { - return v1.ResourceName(fmt.Sprintf("%s%s", v1.ResourceHugePagesPrefix, pageSize.String())) -} - -// HugePageSizeFromResourceName returns the page size for the specified huge page -// resource name. If the specified input is not a valid huge page resource name -// an error is returned. -func HugePageSizeFromResourceName(name v1.ResourceName) (resource.Quantity, error) { - if !IsHugePageResourceName(name) { - return resource.Quantity{}, fmt.Errorf("resource name: %s is not valid hugepage name", name) - } - pageSize := strings.TrimPrefix(string(name), v1.ResourceHugePagesPrefix) - return resource.ParseQuantity(pageSize) -} - -// IsOpaqueIntResourceName returns true if the resource name has the opaque -// integer resource prefix. -func IsOpaqueIntResourceName(name v1.ResourceName) bool { - return strings.HasPrefix(string(name), v1.ResourceOpaqueIntPrefix) -} - -// OpaqueIntResourceName returns a ResourceName with the canonical opaque -// integer prefix prepended. If the argument already has the prefix, it is -// returned unmodified. -func OpaqueIntResourceName(name string) v1.ResourceName { - if IsOpaqueIntResourceName(v1.ResourceName(name)) { - return v1.ResourceName(name) - } - return v1.ResourceName(fmt.Sprintf("%s%s", v1.ResourceOpaqueIntPrefix, name)) -} - -var overcommitBlacklist = sets.NewString(string(v1.ResourceNvidiaGPU)) - -// IsOvercommitAllowed returns true if the resource is in the default -// namespace and not blacklisted and is not hugepages. -func IsOvercommitAllowed(name v1.ResourceName) bool { - return IsDefaultNamespaceResource(name) && - !IsHugePageResourceName(name) && - !overcommitBlacklist.Has(string(name)) -} - -// Extended and Hugepages resources -func IsScalarResourceName(name v1.ResourceName) bool { - return IsExtendedResourceName(name) || IsHugePageResourceName(name) -} - -// this function aims to check if the service's ClusterIP is set or not -// the objective is not to perform validation here -func IsServiceIPSet(service *v1.Service) bool { - return service.Spec.ClusterIP != v1.ClusterIPNone && service.Spec.ClusterIP != "" -} - -// this function aims to check if the service's cluster IP is requested or not -func IsServiceIPRequested(service *v1.Service) bool { - // ExternalName services are CNAME aliases to external ones. Ignore the IP. - if service.Spec.Type == v1.ServiceTypeExternalName { - return false - } - return service.Spec.ClusterIP == "" -} - -// AddToNodeAddresses appends the NodeAddresses to the passed-by-pointer slice, -// only if they do not already exist -func AddToNodeAddresses(addresses *[]v1.NodeAddress, addAddresses ...v1.NodeAddress) { - for _, add := range addAddresses { - exists := false - for _, existing := range *addresses { - if existing.Address == add.Address && existing.Type == add.Type { - exists = true - break - } - } - if !exists { - *addresses = append(*addresses, add) - } - } -} - -// TODO: make method on LoadBalancerStatus? -func LoadBalancerStatusEqual(l, r *v1.LoadBalancerStatus) bool { - return ingressSliceEqual(l.Ingress, r.Ingress) -} - -func ingressSliceEqual(lhs, rhs []v1.LoadBalancerIngress) bool { - if len(lhs) != len(rhs) { - return false - } - for i := range lhs { - if !ingressEqual(&lhs[i], &rhs[i]) { - return false - } - } - return true -} - -func ingressEqual(lhs, rhs *v1.LoadBalancerIngress) bool { - if lhs.IP != rhs.IP { - return false - } - if lhs.Hostname != rhs.Hostname { - return false - } - return true -} - -// TODO: make method on LoadBalancerStatus? -func LoadBalancerStatusDeepCopy(lb *v1.LoadBalancerStatus) *v1.LoadBalancerStatus { - c := &v1.LoadBalancerStatus{} - c.Ingress = make([]v1.LoadBalancerIngress, len(lb.Ingress)) - for i := range lb.Ingress { - c.Ingress[i] = lb.Ingress[i] - } - return c -} - -// GetAccessModesAsString returns a string representation of an array of access modes. -// modes, when present, are always in the same order: RWO,ROX,RWX. -func GetAccessModesAsString(modes []v1.PersistentVolumeAccessMode) string { - modes = removeDuplicateAccessModes(modes) - modesStr := []string{} - if containsAccessMode(modes, v1.ReadWriteOnce) { - modesStr = append(modesStr, "RWO") - } - if containsAccessMode(modes, v1.ReadOnlyMany) { - modesStr = append(modesStr, "ROX") - } - if containsAccessMode(modes, v1.ReadWriteMany) { - modesStr = append(modesStr, "RWX") - } - return strings.Join(modesStr, ",") -} - -// GetAccessModesAsString returns an array of AccessModes from a string created by GetAccessModesAsString -func GetAccessModesFromString(modes string) []v1.PersistentVolumeAccessMode { - strmodes := strings.Split(modes, ",") - accessModes := []v1.PersistentVolumeAccessMode{} - for _, s := range strmodes { - s = strings.Trim(s, " ") - switch { - case s == "RWO": - accessModes = append(accessModes, v1.ReadWriteOnce) - case s == "ROX": - accessModes = append(accessModes, v1.ReadOnlyMany) - case s == "RWX": - accessModes = append(accessModes, v1.ReadWriteMany) - } - } - return accessModes -} - -// removeDuplicateAccessModes returns an array of access modes without any duplicates -func removeDuplicateAccessModes(modes []v1.PersistentVolumeAccessMode) []v1.PersistentVolumeAccessMode { - accessModes := []v1.PersistentVolumeAccessMode{} - for _, m := range modes { - if !containsAccessMode(accessModes, m) { - accessModes = append(accessModes, m) - } - } - return accessModes -} - -func containsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool { - for _, m := range modes { - if m == mode { - return true - } - } - return false -} - -// NodeSelectorRequirementsAsSelector converts the []NodeSelectorRequirement api type into a struct that implements -// labels.Selector. -func NodeSelectorRequirementsAsSelector(nsm []v1.NodeSelectorRequirement) (labels.Selector, error) { - if len(nsm) == 0 { - return labels.Nothing(), nil - } - selector := labels.NewSelector() - for _, expr := range nsm { - var op selection.Operator - switch expr.Operator { - case v1.NodeSelectorOpIn: - op = selection.In - case v1.NodeSelectorOpNotIn: - op = selection.NotIn - case v1.NodeSelectorOpExists: - op = selection.Exists - case v1.NodeSelectorOpDoesNotExist: - op = selection.DoesNotExist - case v1.NodeSelectorOpGt: - op = selection.GreaterThan - case v1.NodeSelectorOpLt: - op = selection.LessThan - default: - return nil, fmt.Errorf("%q is not a valid node selector operator", expr.Operator) - } - r, err := labels.NewRequirement(expr.Key, op, expr.Values) - if err != nil { - return nil, err - } - selector = selector.Add(*r) - } - return selector, nil -} - -// AddOrUpdateTolerationInPodSpec tries to add a toleration to the toleration list in PodSpec. -// Returns true if something was updated, false otherwise. -func AddOrUpdateTolerationInPodSpec(spec *v1.PodSpec, toleration *v1.Toleration) bool { - podTolerations := spec.Tolerations - - var newTolerations []v1.Toleration - updated := false - for i := range podTolerations { - if toleration.MatchToleration(&podTolerations[i]) { - if helper.Semantic.DeepEqual(toleration, podTolerations[i]) { - return false - } - newTolerations = append(newTolerations, *toleration) - updated = true - continue - } - - newTolerations = append(newTolerations, podTolerations[i]) - } - - if !updated { - newTolerations = append(newTolerations, *toleration) - } - - spec.Tolerations = newTolerations - return true -} - -// AddOrUpdateTolerationInPod tries to add a toleration to the pod's toleration list. -// Returns true if something was updated, false otherwise. -func AddOrUpdateTolerationInPod(pod *v1.Pod, toleration *v1.Toleration) bool { - return AddOrUpdateTolerationInPodSpec(&pod.Spec, toleration) -} - -// TolerationsTolerateTaint checks if taint is tolerated by any of the tolerations. -func TolerationsTolerateTaint(tolerations []v1.Toleration, taint *v1.Taint) bool { - for i := range tolerations { - if tolerations[i].ToleratesTaint(taint) { - return true - } - } - return false -} - -type taintsFilterFunc func(*v1.Taint) bool - -// TolerationsTolerateTaintsWithFilter checks if given tolerations tolerates -// all the taints that apply to the filter in given taint list. -func TolerationsTolerateTaintsWithFilter(tolerations []v1.Toleration, taints []v1.Taint, applyFilter taintsFilterFunc) bool { - if len(taints) == 0 { - return true - } - - for i := range taints { - if applyFilter != nil && !applyFilter(&taints[i]) { - continue - } - - if !TolerationsTolerateTaint(tolerations, &taints[i]) { - return false - } - } - - return true -} - -// Returns true and list of Tolerations matching all Taints if all are tolerated, or false otherwise. -func GetMatchingTolerations(taints []v1.Taint, tolerations []v1.Toleration) (bool, []v1.Toleration) { - if len(taints) == 0 { - return true, []v1.Toleration{} - } - if len(tolerations) == 0 && len(taints) > 0 { - return false, []v1.Toleration{} - } - result := []v1.Toleration{} - for i := range taints { - tolerated := false - for j := range tolerations { - if tolerations[j].ToleratesTaint(&taints[i]) { - result = append(result, tolerations[j]) - tolerated = true - break - } - } - if !tolerated { - return false, []v1.Toleration{} - } - } - return true, result -} - -func GetAvoidPodsFromNodeAnnotations(annotations map[string]string) (v1.AvoidPods, error) { - var avoidPods v1.AvoidPods - if len(annotations) > 0 && annotations[v1.PreferAvoidPodsAnnotationKey] != "" { - err := json.Unmarshal([]byte(annotations[v1.PreferAvoidPodsAnnotationKey]), &avoidPods) - if err != nil { - return avoidPods, err - } - } - return avoidPods, nil -} - -// SysctlsFromPodAnnotations parses the sysctl annotations into a slice of safe Sysctls -// and a slice of unsafe Sysctls. This is only a convenience wrapper around -// SysctlsFromPodAnnotation. -func SysctlsFromPodAnnotations(a map[string]string) ([]v1.Sysctl, []v1.Sysctl, error) { - safe, err := SysctlsFromPodAnnotation(a[v1.SysctlsPodAnnotationKey]) - if err != nil { - return nil, nil, err - } - unsafe, err := SysctlsFromPodAnnotation(a[v1.UnsafeSysctlsPodAnnotationKey]) - if err != nil { - return nil, nil, err - } - - return safe, unsafe, nil -} - -// SysctlsFromPodAnnotation parses an annotation value into a slice of Sysctls. -func SysctlsFromPodAnnotation(annotation string) ([]v1.Sysctl, error) { - if len(annotation) == 0 { - return nil, nil - } - - kvs := strings.Split(annotation, ",") - sysctls := make([]v1.Sysctl, len(kvs)) - for i, kv := range kvs { - cs := strings.Split(kv, "=") - if len(cs) != 2 || len(cs[0]) == 0 { - return nil, fmt.Errorf("sysctl %q not of the format sysctl_name=value", kv) - } - sysctls[i].Name = cs[0] - sysctls[i].Value = cs[1] - } - return sysctls, nil -} - -// PodAnnotationsFromSysctls creates an annotation value for a slice of Sysctls. -func PodAnnotationsFromSysctls(sysctls []v1.Sysctl) string { - if len(sysctls) == 0 { - return "" - } - - kvs := make([]string, len(sysctls)) - for i := range sysctls { - kvs[i] = fmt.Sprintf("%s=%s", sysctls[i].Name, sysctls[i].Value) - } - return strings.Join(kvs, ",") -} - -// GetPersistentVolumeClass returns StorageClassName. -func GetPersistentVolumeClass(volume *v1.PersistentVolume) string { - // Use beta annotation first - if class, found := volume.Annotations[v1.BetaStorageClassAnnotation]; found { - return class - } - - return volume.Spec.StorageClassName -} - -// GetPersistentVolumeClaimClass returns StorageClassName. If no storage class was -// requested, it returns "". -func GetPersistentVolumeClaimClass(claim *v1.PersistentVolumeClaim) string { - // Use beta annotation first - if class, found := claim.Annotations[v1.BetaStorageClassAnnotation]; found { - return class - } - - if claim.Spec.StorageClassName != nil { - return *claim.Spec.StorageClassName - } - - return "" -} - -// PersistentVolumeClaimHasClass returns true if given claim has set StorageClassName field. -func PersistentVolumeClaimHasClass(claim *v1.PersistentVolumeClaim) bool { - // Use beta annotation first - if _, found := claim.Annotations[v1.BetaStorageClassAnnotation]; found { - return true - } - - if claim.Spec.StorageClassName != nil { - return true - } - - return false -} - -// GetStorageNodeAffinityFromAnnotation gets the json serialized data from PersistentVolume.Annotations -// and converts it to the NodeAffinity type in api. -// TODO: update when storage node affinity graduates to beta -func GetStorageNodeAffinityFromAnnotation(annotations map[string]string) (*v1.NodeAffinity, error) { - if len(annotations) > 0 && annotations[v1.AlphaStorageNodeAffinityAnnotation] != "" { - var affinity v1.NodeAffinity - err := json.Unmarshal([]byte(annotations[v1.AlphaStorageNodeAffinityAnnotation]), &affinity) - if err != nil { - return nil, err - } - return &affinity, nil - } - return nil, nil -} - -// Converts NodeAffinity type to Alpha annotation for use in PersistentVolumes -// TODO: update when storage node affinity graduates to beta -func StorageNodeAffinityToAlphaAnnotation(annotations map[string]string, affinity *v1.NodeAffinity) error { - if affinity == nil { - return nil - } - - json, err := json.Marshal(*affinity) - if err != nil { - return err - } - annotations[v1.AlphaStorageNodeAffinityAnnotation] = string(json) - return nil -} diff --git a/pkg/api/v1/helper/helpers_test.go b/pkg/api/v1/helper/helpers_test.go deleted file mode 100644 index 3b8e5f87837..00000000000 --- a/pkg/api/v1/helper/helpers_test.go +++ /dev/null @@ -1,590 +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 helper - -import ( - "fmt" - "reflect" - "testing" - - "k8s.io/api/core/v1" - apiequality "k8s.io/apimachinery/pkg/api/equality" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" -) - -func TestIsOpaqueIntResourceName(t *testing.T) { // resourceName input with the correct OpaqueIntResourceName prefix ("pod.alpha.kubernetes.io/opaque-int-resource-") should pass - testCases := []struct { - resourceName v1.ResourceName - expectVal bool - }{ - { - resourceName: "pod.alpha.kubernetes.io/opaque-int-resource-foo", - expectVal: true, // resourceName should pass because the resourceName has the correct prefix. - }, - { - resourceName: "foo", - expectVal: false, // resourceName should fail because the resourceName has the wrong prefix. - }, - { - resourceName: "", - expectVal: false, // resourceName should fail, empty resourceName. - }, - } - - 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 := IsOpaqueIntResourceName(tc.resourceName) - if v != tc.expectVal { - t.Errorf("Got %v but expected %v", v, tc.expectVal) - } - }) - } -} - -func TestOpaqueIntResourceName(t *testing.T) { // each output should have the correct appended prefix ("pod.alpha.kubernetes.io/opaque-int-resource-") for opaque counted resources. - testCases := []struct { - name string - expectVal v1.ResourceName - }{ - { - name: "foo", - expectVal: "pod.alpha.kubernetes.io/opaque-int-resource-foo", // append prefix to input string foo - }, - { - name: "", - expectVal: "pod.alpha.kubernetes.io/opaque-int-resource-", // append prefix to input empty string - }, - } - - for _, tc := range testCases { - tc := tc - t.Run(fmt.Sprintf("name input=%s, expected value=%s", tc.name, tc.expectVal), func(t *testing.T) { - t.Parallel() - v := OpaqueIntResourceName(tc.name) - if v != tc.expectVal { - t.Errorf("Got %v but expected %v", v, tc.expectVal) - } - }) - } -} - -func TestAddToNodeAddresses(t *testing.T) { - testCases := []struct { - existing []v1.NodeAddress - toAdd []v1.NodeAddress - expected []v1.NodeAddress - }{ - { - existing: []v1.NodeAddress{}, - toAdd: []v1.NodeAddress{}, - expected: []v1.NodeAddress{}, - }, - { - existing: []v1.NodeAddress{}, - toAdd: []v1.NodeAddress{ - {Type: v1.NodeExternalIP, Address: "1.1.1.1"}, - {Type: v1.NodeHostName, Address: "localhost"}, - }, - expected: []v1.NodeAddress{ - {Type: v1.NodeExternalIP, Address: "1.1.1.1"}, - {Type: v1.NodeHostName, Address: "localhost"}, - }, - }, - { - existing: []v1.NodeAddress{}, - toAdd: []v1.NodeAddress{ - {Type: v1.NodeExternalIP, Address: "1.1.1.1"}, - {Type: v1.NodeExternalIP, Address: "1.1.1.1"}, - }, - expected: []v1.NodeAddress{ - {Type: v1.NodeExternalIP, Address: "1.1.1.1"}, - }, - }, - { - existing: []v1.NodeAddress{ - {Type: v1.NodeExternalIP, Address: "1.1.1.1"}, - {Type: v1.NodeInternalIP, Address: "10.1.1.1"}, - }, - toAdd: []v1.NodeAddress{ - {Type: v1.NodeExternalIP, Address: "1.1.1.1"}, - {Type: v1.NodeHostName, Address: "localhost"}, - }, - expected: []v1.NodeAddress{ - {Type: v1.NodeExternalIP, Address: "1.1.1.1"}, - {Type: v1.NodeInternalIP, Address: "10.1.1.1"}, - {Type: v1.NodeHostName, Address: "localhost"}, - }, - }, - } - - for i, tc := range testCases { - AddToNodeAddresses(&tc.existing, tc.toAdd...) - if !apiequality.Semantic.DeepEqual(tc.expected, tc.existing) { - t.Errorf("case[%d], expected: %v, got: %v", i, tc.expected, tc.existing) - } - } -} - -func TestGetAccessModesFromString(t *testing.T) { - modes := GetAccessModesFromString("ROX") - if !containsAccessMode(modes, v1.ReadOnlyMany) { - t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes) - } - - modes = GetAccessModesFromString("ROX,RWX") - if !containsAccessMode(modes, v1.ReadOnlyMany) { - t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes) - } - if !containsAccessMode(modes, v1.ReadWriteMany) { - t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteMany, modes) - } - - modes = GetAccessModesFromString("RWO,ROX,RWX") - if !containsAccessMode(modes, v1.ReadOnlyMany) { - t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes) - } - if !containsAccessMode(modes, v1.ReadWriteMany) { - t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteMany, modes) - } -} - -func TestRemoveDuplicateAccessModes(t *testing.T) { - modes := []v1.PersistentVolumeAccessMode{ - v1.ReadWriteOnce, v1.ReadOnlyMany, v1.ReadOnlyMany, v1.ReadOnlyMany, - } - modes = removeDuplicateAccessModes(modes) - if len(modes) != 2 { - t.Errorf("Expected 2 distinct modes in set but found %v", len(modes)) - } -} - -func TestNodeSelectorRequirementsAsSelector(t *testing.T) { - matchExpressions := []v1.NodeSelectorRequirement{{ - Key: "foo", - Operator: v1.NodeSelectorOpIn, - Values: []string{"bar", "baz"}, - }} - mustParse := func(s string) labels.Selector { - out, e := labels.Parse(s) - if e != nil { - panic(e) - } - return out - } - tc := []struct { - in []v1.NodeSelectorRequirement - out labels.Selector - expectErr bool - }{ - {in: nil, out: labels.Nothing()}, - {in: []v1.NodeSelectorRequirement{}, out: labels.Nothing()}, - { - in: matchExpressions, - out: mustParse("foo in (baz,bar)"), - }, - { - in: []v1.NodeSelectorRequirement{{ - Key: "foo", - Operator: v1.NodeSelectorOpExists, - Values: []string{"bar", "baz"}, - }}, - expectErr: true, - }, - { - in: []v1.NodeSelectorRequirement{{ - Key: "foo", - Operator: v1.NodeSelectorOpGt, - Values: []string{"1"}, - }}, - out: mustParse("foo>1"), - }, - { - in: []v1.NodeSelectorRequirement{{ - Key: "bar", - Operator: v1.NodeSelectorOpLt, - Values: []string{"7"}, - }}, - out: mustParse("bar<7"), - }, - } - - for i, tc := range tc { - out, err := NodeSelectorRequirementsAsSelector(tc.in) - if err == nil && tc.expectErr { - t.Errorf("[%v]expected error but got none.", i) - } - if err != nil && !tc.expectErr { - t.Errorf("[%v]did not expect error but got: %v", i, err) - } - if !reflect.DeepEqual(out, tc.out) { - t.Errorf("[%v]expected:\n\t%+v\nbut got:\n\t%+v", i, tc.out, out) - } - } -} - -func TestTolerationsTolerateTaintsWithFilter(t *testing.T) { - testCases := []struct { - description string - tolerations []v1.Toleration - taints []v1.Taint - applyFilter taintsFilterFunc - expectTolerated bool - }{ - { - description: "empty tolerations tolerate empty taints", - tolerations: []v1.Toleration{}, - taints: []v1.Taint{}, - applyFilter: func(t *v1.Taint) bool { return true }, - expectTolerated: true, - }, - { - description: "non-empty tolerations tolerate empty taints", - tolerations: []v1.Toleration{ - { - Key: "foo", - Operator: "Exists", - Effect: v1.TaintEffectNoSchedule, - }, - }, - taints: []v1.Taint{}, - applyFilter: func(t *v1.Taint) bool { return true }, - expectTolerated: true, - }, - { - description: "tolerations match all taints, expect tolerated", - tolerations: []v1.Toleration{ - { - Key: "foo", - Operator: "Exists", - Effect: v1.TaintEffectNoSchedule, - }, - }, - taints: []v1.Taint{ - { - Key: "foo", - Effect: v1.TaintEffectNoSchedule, - }, - }, - applyFilter: func(t *v1.Taint) bool { return true }, - expectTolerated: true, - }, - { - description: "tolerations don't match taints, but no taints apply to the filter, expect tolerated", - tolerations: []v1.Toleration{ - { - Key: "foo", - Operator: "Exists", - Effect: v1.TaintEffectNoSchedule, - }, - }, - taints: []v1.Taint{ - { - Key: "bar", - Effect: v1.TaintEffectNoSchedule, - }, - }, - applyFilter: func(t *v1.Taint) bool { return false }, - expectTolerated: true, - }, - { - description: "no filterFunc indicated, means all taints apply to the filter, tolerations don't match taints, expect untolerated", - tolerations: []v1.Toleration{ - { - Key: "foo", - Operator: "Exists", - Effect: v1.TaintEffectNoSchedule, - }, - }, - taints: []v1.Taint{ - { - Key: "bar", - Effect: v1.TaintEffectNoSchedule, - }, - }, - applyFilter: nil, - expectTolerated: false, - }, - { - description: "tolerations match taints, expect tolerated", - tolerations: []v1.Toleration{ - { - Key: "foo", - Operator: "Exists", - Effect: v1.TaintEffectNoExecute, - }, - }, - taints: []v1.Taint{ - { - Key: "foo", - Effect: v1.TaintEffectNoExecute, - }, - { - Key: "bar", - Effect: v1.TaintEffectNoSchedule, - }, - }, - applyFilter: func(t *v1.Taint) bool { return t.Effect == v1.TaintEffectNoExecute }, - expectTolerated: true, - }, - } - - for _, tc := range testCases { - if tc.expectTolerated != TolerationsTolerateTaintsWithFilter(tc.tolerations, tc.taints, tc.applyFilter) { - filteredTaints := []v1.Taint{} - for _, taint := range tc.taints { - if tc.applyFilter != nil && !tc.applyFilter(&taint) { - continue - } - filteredTaints = append(filteredTaints, taint) - } - t.Errorf("[%s] expect tolerations %+v tolerate filtered taints %+v in taints %+v", tc.description, tc.tolerations, filteredTaints, tc.taints) - } - } -} - -func TestGetAvoidPodsFromNode(t *testing.T) { - controllerFlag := true - testCases := []struct { - node *v1.Node - expectValue v1.AvoidPods - expectErr bool - }{ - { - node: &v1.Node{}, - expectValue: v1.AvoidPods{}, - expectErr: false, - }, - { - node: &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - v1.PreferAvoidPodsAnnotationKey: ` - { - "preferAvoidPods": [ - { - "podSignature": { - "podController": { - "apiVersion": "v1", - "kind": "ReplicationController", - "name": "foo", - "uid": "abcdef123456", - "controller": true - } - }, - "reason": "some reason", - "message": "some message" - } - ] - }`, - }, - }, - }, - expectValue: v1.AvoidPods{ - PreferAvoidPods: []v1.PreferAvoidPodsEntry{ - { - PodSignature: v1.PodSignature{ - PodController: &metav1.OwnerReference{ - APIVersion: "v1", - Kind: "ReplicationController", - Name: "foo", - UID: "abcdef123456", - Controller: &controllerFlag, - }, - }, - Reason: "some reason", - Message: "some message", - }, - }, - }, - expectErr: false, - }, - { - node: &v1.Node{ - // Missing end symbol of "podController" and "podSignature" - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - v1.PreferAvoidPodsAnnotationKey: ` - { - "preferAvoidPods": [ - { - "podSignature": { - "podController": { - "kind": "ReplicationController", - "apiVersion": "v1" - "reason": "some reason", - "message": "some message" - } - ] - }`, - }, - }, - }, - expectValue: v1.AvoidPods{}, - expectErr: true, - }, - } - - for i, tc := range testCases { - v, err := GetAvoidPodsFromNodeAnnotations(tc.node.Annotations) - if err == nil && tc.expectErr { - t.Errorf("[%v]expected error but got none.", i) - } - if err != nil && !tc.expectErr { - t.Errorf("[%v]did not expect error but got: %v", i, err) - } - if !reflect.DeepEqual(tc.expectValue, v) { - t.Errorf("[%v]expect value %v but got %v with %v", i, tc.expectValue, v, v.PreferAvoidPods[0].PodSignature.PodController.Controller) - } - } -} - -func TestSysctlsFromPodAnnotation(t *testing.T) { - type Test struct { - annotation string - expectValue []v1.Sysctl - expectErr bool - } - for i, test := range []Test{ - { - annotation: "", - expectValue: nil, - }, - { - annotation: "foo.bar", - expectErr: true, - }, - { - annotation: "=123", - expectErr: true, - }, - { - annotation: "foo.bar=", - expectValue: []v1.Sysctl{{Name: "foo.bar", Value: ""}}, - }, - { - annotation: "foo.bar=42", - expectValue: []v1.Sysctl{{Name: "foo.bar", Value: "42"}}, - }, - { - annotation: "foo.bar=42,", - expectErr: true, - }, - { - annotation: "foo.bar=42,abc.def=1", - expectValue: []v1.Sysctl{{Name: "foo.bar", Value: "42"}, {Name: "abc.def", Value: "1"}}, - }, - } { - sysctls, err := SysctlsFromPodAnnotation(test.annotation) - if test.expectErr && err == nil { - t.Errorf("[%v]expected error but got none", i) - } else if !test.expectErr && err != nil { - t.Errorf("[%v]did not expect error but got: %v", i, err) - } else if !reflect.DeepEqual(sysctls, test.expectValue) { - t.Errorf("[%v]expect value %v but got %v", i, test.expectValue, sysctls) - } - } -} - -// TODO: remove when alpha support for topology constraints is removed -func TestGetNodeAffinityFromAnnotations(t *testing.T) { - testCases := []struct { - annotations map[string]string - expectErr bool - }{ - { - annotations: nil, - expectErr: false, - }, - { - annotations: map[string]string{}, - expectErr: false, - }, - { - annotations: map[string]string{ - v1.AlphaStorageNodeAffinityAnnotation: `{ - "requiredDuringSchedulingIgnoredDuringExecution": { - "nodeSelectorTerms": [ - { "matchExpressions": [ - { "key": "test-key1", - "operator": "In", - "values": ["test-value1", "test-value2"] - }, - { "key": "test-key2", - "operator": "In", - "values": ["test-value1", "test-value2"] - } - ]} - ]} - }`, - }, - expectErr: false, - }, - { - annotations: map[string]string{ - v1.AlphaStorageNodeAffinityAnnotation: `[{ - "requiredDuringSchedulingIgnoredDuringExecution": { - "nodeSelectorTerms": [ - { "matchExpressions": [ - { "key": "test-key1", - "operator": "In", - "values": ["test-value1", "test-value2"] - }, - { "key": "test-key2", - "operator": "In", - "values": ["test-value1", "test-value2"] - } - ]} - ]} - }]`, - }, - expectErr: true, - }, - { - annotations: map[string]string{ - v1.AlphaStorageNodeAffinityAnnotation: `{ - "requiredDuringSchedulingIgnoredDuringExecution": { - "nodeSelectorTerms": - "matchExpressions": [ - { "key": "test-key1", - "operator": "In", - "values": ["test-value1", "test-value2"] - }, - { "key": "test-key2", - "operator": "In", - "values": ["test-value1", "test-value2"] - } - ]} - } - }`, - }, - expectErr: true, - }, - } - - for i, tc := range testCases { - _, err := GetStorageNodeAffinityFromAnnotation(tc.annotations) - if err == nil && tc.expectErr { - t.Errorf("[%v]expected error but got none.", i) - } - if err != nil && !tc.expectErr { - t.Errorf("[%v]did not expect error but got: %v", i, err) - } - } -} diff --git a/pkg/api/v1/helper/qos/BUILD b/pkg/api/v1/helper/qos/BUILD deleted file mode 100644 index b2bd4319d04..00000000000 --- a/pkg/api/v1/helper/qos/BUILD +++ /dev/null @@ -1,47 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = ["qos_test.go"], - importpath = "k8s.io/kubernetes/pkg/api/v1/helper/qos", - library = ":go_default_library", - deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper/qos:go_default_library", - "//pkg/api/v1:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - ], -) - -go_library( - name = "go_default_library", - srcs = ["qos.go"], - importpath = "k8s.io/kubernetes/pkg/api/v1/helper/qos", - deps = [ - "//pkg/api/v1/helper:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/pkg/api/v1/helper/qos/qos.go b/pkg/api/v1/helper/qos/qos.go deleted file mode 100644 index 2fc7f22856a..00000000000 --- a/pkg/api/v1/helper/qos/qos.go +++ /dev/null @@ -1,98 +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 qos - -import ( - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/apimachinery/pkg/util/sets" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" -) - -// QOSList is a set of (resource name, QoS class) pairs. -type QOSList map[v1.ResourceName]v1.PodQOSClass - -func isSupportedQoSComputeResource(name v1.ResourceName) bool { - supportedQoSComputeResources := sets.NewString(string(v1.ResourceCPU), string(v1.ResourceMemory)) - return supportedQoSComputeResources.Has(string(name)) || v1helper.IsHugePageResourceName(name) -} - -// GetPodQOS returns the QoS class of a pod. -// A pod is besteffort if none of its containers have specified any requests or limits. -// A pod is guaranteed only when requests and limits are specified for all the containers and they are equal. -// A pod is burstable if limits and requests do not match across all containers. -func GetPodQOS(pod *v1.Pod) v1.PodQOSClass { - requests := v1.ResourceList{} - limits := v1.ResourceList{} - zeroQuantity := resource.MustParse("0") - isGuaranteed := true - for _, container := range pod.Spec.Containers { - // process requests - for name, quantity := range container.Resources.Requests { - if !isSupportedQoSComputeResource(name) { - continue - } - if quantity.Cmp(zeroQuantity) == 1 { - delta := quantity.Copy() - if _, exists := requests[name]; !exists { - requests[name] = *delta - } else { - delta.Add(requests[name]) - requests[name] = *delta - } - } - } - // process limits - qosLimitsFound := sets.NewString() - for name, quantity := range container.Resources.Limits { - if !isSupportedQoSComputeResource(name) { - continue - } - if quantity.Cmp(zeroQuantity) == 1 { - qosLimitsFound.Insert(string(name)) - delta := quantity.Copy() - if _, exists := limits[name]; !exists { - limits[name] = *delta - } else { - delta.Add(limits[name]) - limits[name] = *delta - } - } - } - - if !qosLimitsFound.HasAll(string(v1.ResourceMemory), string(v1.ResourceCPU)) { - isGuaranteed = false - } - } - if len(requests) == 0 && len(limits) == 0 { - return v1.PodQOSBestEffort - } - // Check is requests match limits for all resources. - if isGuaranteed { - for name, req := range requests { - if lim, exists := limits[name]; !exists || lim.Cmp(req) != 0 { - isGuaranteed = false - break - } - } - } - if isGuaranteed && - len(requests) == len(limits) { - return v1.PodQOSGuaranteed - } - return v1.PodQOSBurstable -} diff --git a/pkg/api/v1/pod/BUILD b/pkg/api/v1/pod/BUILD index 86a70f7b2c7..d9a5b0ef708 100644 --- a/pkg/api/v1/pod/BUILD +++ b/pkg/api/v1/pod/BUILD @@ -20,8 +20,8 @@ go_library( go_test( name = "go_default_test", srcs = ["util_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/api/v1/pod", - library = ":go_default_library", deps = [ "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/api/v1/resource/BUILD b/pkg/api/v1/resource/BUILD index 21979e2b6cb..93380294b42 100644 --- a/pkg/api/v1/resource/BUILD +++ b/pkg/api/v1/resource/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["helpers_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/api/v1/resource", - library = ":go_default_library", deps = [ "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/api/v1/resource/helpers.go b/pkg/api/v1/resource/helpers.go index af12f76341a..09d9ea04187 100644 --- a/pkg/api/v1/resource/helpers.go +++ b/pkg/api/v1/resource/helpers.go @@ -192,7 +192,7 @@ func MergeContainerResourceLimits(container *v1.Container, if container.Resources.Limits == nil { container.Resources.Limits = make(v1.ResourceList) } - for _, resource := range []v1.ResourceName{v1.ResourceCPU, v1.ResourceMemory} { + for _, resource := range []v1.ResourceName{v1.ResourceCPU, v1.ResourceMemory, v1.ResourceEphemeralStorage} { if quantity, exists := container.Resources.Limits[resource]; !exists || quantity.IsZero() { if cap, exists := allocatable[resource]; exists { container.Resources.Limits[resource] = *cap.Copy() diff --git a/pkg/api/v1/service/BUILD b/pkg/api/v1/service/BUILD index 6c56835787b..eff8992766f 100644 --- a/pkg/api/v1/service/BUILD +++ b/pkg/api/v1/service/BUILD @@ -19,8 +19,8 @@ go_library( go_test( name = "go_default_test", srcs = ["util_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/api/v1/service", - library = ":go_default_library", deps = [ "//pkg/util/net/sets:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/api/v1/validation/BUILD b/pkg/api/v1/validation/BUILD deleted file mode 100644 index 00ef46513fd..00000000000 --- a/pkg/api/v1/validation/BUILD +++ /dev/null @@ -1,48 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = ["validation.go"], - importpath = "k8s.io/kubernetes/pkg/api/v1/validation", - deps = [ - "//pkg/api/helper:go_default_library", - "//pkg/api/v1/helper:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) - -go_test( - name = "go_default_test", - srcs = ["validation_test.go"], - importpath = "k8s.io/kubernetes/pkg/api/v1/validation", - library = ":go_default_library", - deps = [ - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", - ], -) diff --git a/pkg/api/v1/validation/validation.go b/pkg/api/v1/validation/validation.go deleted file mode 100644 index 109054f804e..00000000000 --- a/pkg/api/v1/validation/validation.go +++ /dev/null @@ -1,171 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES 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" - - "github.com/golang/glog" - - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/validation" - "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api/helper" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" -) - -const isNegativeErrorMsg string = `must be greater than or equal to 0` -const isNotIntegerErrorMsg string = `must be an integer` - -func ValidateResourceRequirements(requirements *v1.ResourceRequirements, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - limPath := fldPath.Child("limits") - reqPath := fldPath.Child("requests") - for resourceName, quantity := range requirements.Limits { - fldPath := limPath.Key(string(resourceName)) - // Validate resource name. - allErrs = append(allErrs, validateContainerResourceName(string(resourceName), fldPath)...) - - // Validate resource quantity. - allErrs = append(allErrs, ValidateResourceQuantityValue(string(resourceName), quantity, fldPath)...) - - } - for resourceName, quantity := range requirements.Requests { - fldPath := reqPath.Key(string(resourceName)) - // Validate resource name. - allErrs = append(allErrs, validateContainerResourceName(string(resourceName), fldPath)...) - // Validate resource quantity. - allErrs = append(allErrs, ValidateResourceQuantityValue(string(resourceName), quantity, fldPath)...) - - // Check that request <= limit. - limitQuantity, exists := requirements.Limits[resourceName] - if exists { - // For GPUs, not only requests can't exceed limits, they also can't be lower, i.e. must be equal. - if quantity.Cmp(limitQuantity) != 0 && !v1helper.IsOvercommitAllowed(resourceName) { - allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be equal to %s limit", resourceName))) - } else if quantity.Cmp(limitQuantity) > 0 { - allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be less than or equal to %s limit", resourceName))) - } - } else if resourceName == v1.ResourceNvidiaGPU { - allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be equal to %s request", v1.ResourceNvidiaGPU))) - } - } - - return allErrs -} - -func validateContainerResourceName(value string, fldPath *field.Path) field.ErrorList { - allErrs := validateResourceName(value, fldPath) - if len(strings.Split(value, "/")) == 1 { - if !helper.IsStandardContainerResourceName(value) { - return append(allErrs, field.Invalid(fldPath, value, "must be a standard resource for containers")) - } - } - return allErrs -} - -// ValidateResourceQuantityValue enforces that specified quantity is valid for specified resource -func ValidateResourceQuantityValue(resource string, value resource.Quantity, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - allErrs = append(allErrs, ValidateNonnegativeQuantity(value, fldPath)...) - if helper.IsIntegerResourceName(resource) { - if value.MilliValue()%int64(1000) != int64(0) { - allErrs = append(allErrs, field.Invalid(fldPath, value, isNotIntegerErrorMsg)) - } - } - return allErrs -} - -// Validates that a Quantity is not negative -func ValidateNonnegativeQuantity(value resource.Quantity, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if value.Cmp(resource.Quantity{}) < 0 { - allErrs = append(allErrs, field.Invalid(fldPath, value.String(), isNegativeErrorMsg)) - } - return allErrs -} - -// Validate compute resource typename. -// Refer to docs/design/resources.md for more details. -func validateResourceName(value string, fldPath *field.Path) field.ErrorList { - // Opaque integer resources (OIR) deprecation began in v1.8 - // TODO: Remove warning after OIR deprecation cycle. - if v1helper.IsOpaqueIntResourceName(v1.ResourceName(value)) { - glog.Errorf("DEPRECATION WARNING! Opaque integer resources are deprecated starting with v1.8: %s", value) - } - - allErrs := field.ErrorList{} - for _, msg := range validation.IsQualifiedName(value) { - allErrs = append(allErrs, field.Invalid(fldPath, value, msg)) - } - if len(allErrs) != 0 { - return allErrs - } - - if len(strings.Split(value, "/")) == 1 { - if !helper.IsStandardResourceName(value) { - return append(allErrs, field.Invalid(fldPath, value, "must be a standard resource type or fully qualified")) - } - } - - return allErrs -} - -func ValidatePodLogOptions(opts *v1.PodLogOptions) field.ErrorList { - allErrs := field.ErrorList{} - if opts.TailLines != nil && *opts.TailLines < 0 { - allErrs = append(allErrs, field.Invalid(field.NewPath("tailLines"), *opts.TailLines, isNegativeErrorMsg)) - } - if opts.LimitBytes != nil && *opts.LimitBytes < 1 { - allErrs = append(allErrs, field.Invalid(field.NewPath("limitBytes"), *opts.LimitBytes, "must be greater than 0")) - } - switch { - case opts.SinceSeconds != nil && opts.SinceTime != nil: - allErrs = append(allErrs, field.Forbidden(field.NewPath(""), "at most one of `sinceTime` or `sinceSeconds` may be specified")) - case opts.SinceSeconds != nil: - if *opts.SinceSeconds < 1 { - allErrs = append(allErrs, field.Invalid(field.NewPath("sinceSeconds"), *opts.SinceSeconds, "must be greater than 0")) - } - } - return allErrs -} - -func AccumulateUniqueHostPorts(containers []v1.Container, accumulator *sets.String, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - for ci, ctr := range containers { - idxPath := fldPath.Index(ci) - portsPath := idxPath.Child("ports") - for pi := range ctr.Ports { - idxPath := portsPath.Index(pi) - port := ctr.Ports[pi].HostPort - if port == 0 { - continue - } - str := fmt.Sprintf("%d/%s", port, ctr.Ports[pi].Protocol) - if accumulator.Has(str) { - allErrs = append(allErrs, field.Duplicate(idxPath.Child("hostPort"), str)) - } else { - accumulator.Insert(str) - } - } - } - return allErrs -} diff --git a/pkg/api/v1/zz_generated.conversion.go b/pkg/api/v1/zz_generated.conversion.go deleted file mode 100644 index 1937a77ea49..00000000000 --- a/pkg/api/v1/zz_generated.conversion.go +++ /dev/null @@ -1,5354 +0,0 @@ -// +build !ignore_autogenerated - -/* -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. -*/ - -// This file was autogenerated by conversion-gen. Do not edit it manually! - -package v1 - -import ( - v1 "k8s.io/api/core/v1" - resource "k8s.io/apimachinery/pkg/api/resource" - meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" - types "k8s.io/apimachinery/pkg/types" - api "k8s.io/kubernetes/pkg/api" - unsafe "unsafe" -) - -func init() { - localSchemeBuilder.Register(RegisterConversions) -} - -// RegisterConversions adds conversion functions to the given scheme. -// Public to allow building arbitrary schemes. -func RegisterConversions(scheme *runtime.Scheme) error { - return scheme.AddGeneratedConversionFuncs( - Convert_v1_AWSElasticBlockStoreVolumeSource_To_api_AWSElasticBlockStoreVolumeSource, - Convert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource, - Convert_v1_Affinity_To_api_Affinity, - Convert_api_Affinity_To_v1_Affinity, - Convert_v1_AttachedVolume_To_api_AttachedVolume, - Convert_api_AttachedVolume_To_v1_AttachedVolume, - Convert_v1_AvoidPods_To_api_AvoidPods, - Convert_api_AvoidPods_To_v1_AvoidPods, - Convert_v1_AzureDiskVolumeSource_To_api_AzureDiskVolumeSource, - Convert_api_AzureDiskVolumeSource_To_v1_AzureDiskVolumeSource, - Convert_v1_AzureFilePersistentVolumeSource_To_api_AzureFilePersistentVolumeSource, - Convert_api_AzureFilePersistentVolumeSource_To_v1_AzureFilePersistentVolumeSource, - Convert_v1_AzureFileVolumeSource_To_api_AzureFileVolumeSource, - Convert_api_AzureFileVolumeSource_To_v1_AzureFileVolumeSource, - Convert_v1_Binding_To_api_Binding, - Convert_api_Binding_To_v1_Binding, - Convert_v1_Capabilities_To_api_Capabilities, - Convert_api_Capabilities_To_v1_Capabilities, - Convert_v1_CephFSPersistentVolumeSource_To_api_CephFSPersistentVolumeSource, - Convert_api_CephFSPersistentVolumeSource_To_v1_CephFSPersistentVolumeSource, - Convert_v1_CephFSVolumeSource_To_api_CephFSVolumeSource, - Convert_api_CephFSVolumeSource_To_v1_CephFSVolumeSource, - Convert_v1_CinderVolumeSource_To_api_CinderVolumeSource, - Convert_api_CinderVolumeSource_To_v1_CinderVolumeSource, - Convert_v1_ClientIPConfig_To_api_ClientIPConfig, - Convert_api_ClientIPConfig_To_v1_ClientIPConfig, - Convert_v1_ComponentCondition_To_api_ComponentCondition, - Convert_api_ComponentCondition_To_v1_ComponentCondition, - Convert_v1_ComponentStatus_To_api_ComponentStatus, - Convert_api_ComponentStatus_To_v1_ComponentStatus, - Convert_v1_ComponentStatusList_To_api_ComponentStatusList, - Convert_api_ComponentStatusList_To_v1_ComponentStatusList, - Convert_v1_ConfigMap_To_api_ConfigMap, - Convert_api_ConfigMap_To_v1_ConfigMap, - Convert_v1_ConfigMapEnvSource_To_api_ConfigMapEnvSource, - Convert_api_ConfigMapEnvSource_To_v1_ConfigMapEnvSource, - Convert_v1_ConfigMapKeySelector_To_api_ConfigMapKeySelector, - Convert_api_ConfigMapKeySelector_To_v1_ConfigMapKeySelector, - Convert_v1_ConfigMapList_To_api_ConfigMapList, - Convert_api_ConfigMapList_To_v1_ConfigMapList, - Convert_v1_ConfigMapProjection_To_api_ConfigMapProjection, - Convert_api_ConfigMapProjection_To_v1_ConfigMapProjection, - Convert_v1_ConfigMapVolumeSource_To_api_ConfigMapVolumeSource, - Convert_api_ConfigMapVolumeSource_To_v1_ConfigMapVolumeSource, - Convert_v1_Container_To_api_Container, - Convert_api_Container_To_v1_Container, - Convert_v1_ContainerImage_To_api_ContainerImage, - Convert_api_ContainerImage_To_v1_ContainerImage, - Convert_v1_ContainerPort_To_api_ContainerPort, - Convert_api_ContainerPort_To_v1_ContainerPort, - Convert_v1_ContainerState_To_api_ContainerState, - Convert_api_ContainerState_To_v1_ContainerState, - Convert_v1_ContainerStateRunning_To_api_ContainerStateRunning, - Convert_api_ContainerStateRunning_To_v1_ContainerStateRunning, - Convert_v1_ContainerStateTerminated_To_api_ContainerStateTerminated, - Convert_api_ContainerStateTerminated_To_v1_ContainerStateTerminated, - Convert_v1_ContainerStateWaiting_To_api_ContainerStateWaiting, - Convert_api_ContainerStateWaiting_To_v1_ContainerStateWaiting, - Convert_v1_ContainerStatus_To_api_ContainerStatus, - Convert_api_ContainerStatus_To_v1_ContainerStatus, - Convert_v1_DaemonEndpoint_To_api_DaemonEndpoint, - Convert_api_DaemonEndpoint_To_v1_DaemonEndpoint, - Convert_v1_DeleteOptions_To_api_DeleteOptions, - Convert_api_DeleteOptions_To_v1_DeleteOptions, - Convert_v1_DownwardAPIProjection_To_api_DownwardAPIProjection, - Convert_api_DownwardAPIProjection_To_v1_DownwardAPIProjection, - Convert_v1_DownwardAPIVolumeFile_To_api_DownwardAPIVolumeFile, - Convert_api_DownwardAPIVolumeFile_To_v1_DownwardAPIVolumeFile, - Convert_v1_DownwardAPIVolumeSource_To_api_DownwardAPIVolumeSource, - Convert_api_DownwardAPIVolumeSource_To_v1_DownwardAPIVolumeSource, - Convert_v1_EmptyDirVolumeSource_To_api_EmptyDirVolumeSource, - Convert_api_EmptyDirVolumeSource_To_v1_EmptyDirVolumeSource, - Convert_v1_EndpointAddress_To_api_EndpointAddress, - Convert_api_EndpointAddress_To_v1_EndpointAddress, - Convert_v1_EndpointPort_To_api_EndpointPort, - Convert_api_EndpointPort_To_v1_EndpointPort, - Convert_v1_EndpointSubset_To_api_EndpointSubset, - Convert_api_EndpointSubset_To_v1_EndpointSubset, - Convert_v1_Endpoints_To_api_Endpoints, - Convert_api_Endpoints_To_v1_Endpoints, - Convert_v1_EndpointsList_To_api_EndpointsList, - Convert_api_EndpointsList_To_v1_EndpointsList, - Convert_v1_EnvFromSource_To_api_EnvFromSource, - Convert_api_EnvFromSource_To_v1_EnvFromSource, - Convert_v1_EnvVar_To_api_EnvVar, - Convert_api_EnvVar_To_v1_EnvVar, - Convert_v1_EnvVarSource_To_api_EnvVarSource, - Convert_api_EnvVarSource_To_v1_EnvVarSource, - Convert_v1_Event_To_api_Event, - Convert_api_Event_To_v1_Event, - Convert_v1_EventList_To_api_EventList, - Convert_api_EventList_To_v1_EventList, - Convert_v1_EventSource_To_api_EventSource, - Convert_api_EventSource_To_v1_EventSource, - Convert_v1_ExecAction_To_api_ExecAction, - Convert_api_ExecAction_To_v1_ExecAction, - Convert_v1_FCVolumeSource_To_api_FCVolumeSource, - Convert_api_FCVolumeSource_To_v1_FCVolumeSource, - Convert_v1_FlexVolumeSource_To_api_FlexVolumeSource, - Convert_api_FlexVolumeSource_To_v1_FlexVolumeSource, - Convert_v1_FlockerVolumeSource_To_api_FlockerVolumeSource, - Convert_api_FlockerVolumeSource_To_v1_FlockerVolumeSource, - Convert_v1_GCEPersistentDiskVolumeSource_To_api_GCEPersistentDiskVolumeSource, - Convert_api_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource, - Convert_v1_GitRepoVolumeSource_To_api_GitRepoVolumeSource, - Convert_api_GitRepoVolumeSource_To_v1_GitRepoVolumeSource, - Convert_v1_GlusterfsVolumeSource_To_api_GlusterfsVolumeSource, - Convert_api_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource, - Convert_v1_HTTPGetAction_To_api_HTTPGetAction, - Convert_api_HTTPGetAction_To_v1_HTTPGetAction, - Convert_v1_HTTPHeader_To_api_HTTPHeader, - Convert_api_HTTPHeader_To_v1_HTTPHeader, - Convert_v1_Handler_To_api_Handler, - Convert_api_Handler_To_v1_Handler, - Convert_v1_HostAlias_To_api_HostAlias, - Convert_api_HostAlias_To_v1_HostAlias, - Convert_v1_HostPathVolumeSource_To_api_HostPathVolumeSource, - Convert_api_HostPathVolumeSource_To_v1_HostPathVolumeSource, - Convert_v1_ISCSIVolumeSource_To_api_ISCSIVolumeSource, - Convert_api_ISCSIVolumeSource_To_v1_ISCSIVolumeSource, - Convert_v1_KeyToPath_To_api_KeyToPath, - Convert_api_KeyToPath_To_v1_KeyToPath, - Convert_v1_Lifecycle_To_api_Lifecycle, - Convert_api_Lifecycle_To_v1_Lifecycle, - Convert_v1_LimitRange_To_api_LimitRange, - Convert_api_LimitRange_To_v1_LimitRange, - Convert_v1_LimitRangeItem_To_api_LimitRangeItem, - Convert_api_LimitRangeItem_To_v1_LimitRangeItem, - Convert_v1_LimitRangeList_To_api_LimitRangeList, - Convert_api_LimitRangeList_To_v1_LimitRangeList, - Convert_v1_LimitRangeSpec_To_api_LimitRangeSpec, - Convert_api_LimitRangeSpec_To_v1_LimitRangeSpec, - Convert_v1_List_To_api_List, - Convert_api_List_To_v1_List, - Convert_v1_ListOptions_To_api_ListOptions, - Convert_api_ListOptions_To_v1_ListOptions, - Convert_v1_LoadBalancerIngress_To_api_LoadBalancerIngress, - Convert_api_LoadBalancerIngress_To_v1_LoadBalancerIngress, - Convert_v1_LoadBalancerStatus_To_api_LoadBalancerStatus, - Convert_api_LoadBalancerStatus_To_v1_LoadBalancerStatus, - Convert_v1_LocalObjectReference_To_api_LocalObjectReference, - Convert_api_LocalObjectReference_To_v1_LocalObjectReference, - Convert_v1_LocalVolumeSource_To_api_LocalVolumeSource, - Convert_api_LocalVolumeSource_To_v1_LocalVolumeSource, - Convert_v1_NFSVolumeSource_To_api_NFSVolumeSource, - Convert_api_NFSVolumeSource_To_v1_NFSVolumeSource, - Convert_v1_Namespace_To_api_Namespace, - Convert_api_Namespace_To_v1_Namespace, - Convert_v1_NamespaceList_To_api_NamespaceList, - Convert_api_NamespaceList_To_v1_NamespaceList, - Convert_v1_NamespaceSpec_To_api_NamespaceSpec, - Convert_api_NamespaceSpec_To_v1_NamespaceSpec, - Convert_v1_NamespaceStatus_To_api_NamespaceStatus, - Convert_api_NamespaceStatus_To_v1_NamespaceStatus, - Convert_v1_Node_To_api_Node, - Convert_api_Node_To_v1_Node, - Convert_v1_NodeAddress_To_api_NodeAddress, - Convert_api_NodeAddress_To_v1_NodeAddress, - Convert_v1_NodeAffinity_To_api_NodeAffinity, - Convert_api_NodeAffinity_To_v1_NodeAffinity, - Convert_v1_NodeCondition_To_api_NodeCondition, - Convert_api_NodeCondition_To_v1_NodeCondition, - Convert_v1_NodeConfigSource_To_api_NodeConfigSource, - Convert_api_NodeConfigSource_To_v1_NodeConfigSource, - Convert_v1_NodeDaemonEndpoints_To_api_NodeDaemonEndpoints, - Convert_api_NodeDaemonEndpoints_To_v1_NodeDaemonEndpoints, - Convert_v1_NodeList_To_api_NodeList, - Convert_api_NodeList_To_v1_NodeList, - Convert_v1_NodeProxyOptions_To_api_NodeProxyOptions, - Convert_api_NodeProxyOptions_To_v1_NodeProxyOptions, - Convert_v1_NodeResources_To_api_NodeResources, - Convert_api_NodeResources_To_v1_NodeResources, - Convert_v1_NodeSelector_To_api_NodeSelector, - Convert_api_NodeSelector_To_v1_NodeSelector, - Convert_v1_NodeSelectorRequirement_To_api_NodeSelectorRequirement, - Convert_api_NodeSelectorRequirement_To_v1_NodeSelectorRequirement, - Convert_v1_NodeSelectorTerm_To_api_NodeSelectorTerm, - Convert_api_NodeSelectorTerm_To_v1_NodeSelectorTerm, - Convert_v1_NodeSpec_To_api_NodeSpec, - Convert_api_NodeSpec_To_v1_NodeSpec, - Convert_v1_NodeStatus_To_api_NodeStatus, - Convert_api_NodeStatus_To_v1_NodeStatus, - Convert_v1_NodeSystemInfo_To_api_NodeSystemInfo, - Convert_api_NodeSystemInfo_To_v1_NodeSystemInfo, - Convert_v1_ObjectFieldSelector_To_api_ObjectFieldSelector, - Convert_api_ObjectFieldSelector_To_v1_ObjectFieldSelector, - Convert_v1_ObjectMeta_To_api_ObjectMeta, - Convert_api_ObjectMeta_To_v1_ObjectMeta, - Convert_v1_ObjectReference_To_api_ObjectReference, - Convert_api_ObjectReference_To_v1_ObjectReference, - Convert_v1_PersistentVolume_To_api_PersistentVolume, - Convert_api_PersistentVolume_To_v1_PersistentVolume, - Convert_v1_PersistentVolumeClaim_To_api_PersistentVolumeClaim, - Convert_api_PersistentVolumeClaim_To_v1_PersistentVolumeClaim, - Convert_v1_PersistentVolumeClaimCondition_To_api_PersistentVolumeClaimCondition, - Convert_api_PersistentVolumeClaimCondition_To_v1_PersistentVolumeClaimCondition, - Convert_v1_PersistentVolumeClaimList_To_api_PersistentVolumeClaimList, - Convert_api_PersistentVolumeClaimList_To_v1_PersistentVolumeClaimList, - Convert_v1_PersistentVolumeClaimSpec_To_api_PersistentVolumeClaimSpec, - Convert_api_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec, - Convert_v1_PersistentVolumeClaimStatus_To_api_PersistentVolumeClaimStatus, - Convert_api_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus, - Convert_v1_PersistentVolumeClaimVolumeSource_To_api_PersistentVolumeClaimVolumeSource, - Convert_api_PersistentVolumeClaimVolumeSource_To_v1_PersistentVolumeClaimVolumeSource, - Convert_v1_PersistentVolumeList_To_api_PersistentVolumeList, - Convert_api_PersistentVolumeList_To_v1_PersistentVolumeList, - Convert_v1_PersistentVolumeSource_To_api_PersistentVolumeSource, - Convert_api_PersistentVolumeSource_To_v1_PersistentVolumeSource, - Convert_v1_PersistentVolumeSpec_To_api_PersistentVolumeSpec, - Convert_api_PersistentVolumeSpec_To_v1_PersistentVolumeSpec, - Convert_v1_PersistentVolumeStatus_To_api_PersistentVolumeStatus, - Convert_api_PersistentVolumeStatus_To_v1_PersistentVolumeStatus, - Convert_v1_PhotonPersistentDiskVolumeSource_To_api_PhotonPersistentDiskVolumeSource, - Convert_api_PhotonPersistentDiskVolumeSource_To_v1_PhotonPersistentDiskVolumeSource, - Convert_v1_Pod_To_api_Pod, - Convert_api_Pod_To_v1_Pod, - Convert_v1_PodAffinity_To_api_PodAffinity, - Convert_api_PodAffinity_To_v1_PodAffinity, - Convert_v1_PodAffinityTerm_To_api_PodAffinityTerm, - Convert_api_PodAffinityTerm_To_v1_PodAffinityTerm, - Convert_v1_PodAntiAffinity_To_api_PodAntiAffinity, - Convert_api_PodAntiAffinity_To_v1_PodAntiAffinity, - Convert_v1_PodAttachOptions_To_api_PodAttachOptions, - Convert_api_PodAttachOptions_To_v1_PodAttachOptions, - Convert_v1_PodCondition_To_api_PodCondition, - Convert_api_PodCondition_To_v1_PodCondition, - Convert_v1_PodExecOptions_To_api_PodExecOptions, - Convert_api_PodExecOptions_To_v1_PodExecOptions, - Convert_v1_PodList_To_api_PodList, - Convert_api_PodList_To_v1_PodList, - Convert_v1_PodLogOptions_To_api_PodLogOptions, - Convert_api_PodLogOptions_To_v1_PodLogOptions, - Convert_v1_PodPortForwardOptions_To_api_PodPortForwardOptions, - Convert_api_PodPortForwardOptions_To_v1_PodPortForwardOptions, - Convert_v1_PodProxyOptions_To_api_PodProxyOptions, - Convert_api_PodProxyOptions_To_v1_PodProxyOptions, - Convert_v1_PodSecurityContext_To_api_PodSecurityContext, - Convert_api_PodSecurityContext_To_v1_PodSecurityContext, - Convert_v1_PodSignature_To_api_PodSignature, - Convert_api_PodSignature_To_v1_PodSignature, - Convert_v1_PodSpec_To_api_PodSpec, - Convert_api_PodSpec_To_v1_PodSpec, - Convert_v1_PodStatus_To_api_PodStatus, - Convert_api_PodStatus_To_v1_PodStatus, - Convert_v1_PodStatusResult_To_api_PodStatusResult, - Convert_api_PodStatusResult_To_v1_PodStatusResult, - Convert_v1_PodTemplate_To_api_PodTemplate, - Convert_api_PodTemplate_To_v1_PodTemplate, - Convert_v1_PodTemplateList_To_api_PodTemplateList, - Convert_api_PodTemplateList_To_v1_PodTemplateList, - Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec, - Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec, - Convert_v1_PortworxVolumeSource_To_api_PortworxVolumeSource, - Convert_api_PortworxVolumeSource_To_v1_PortworxVolumeSource, - Convert_v1_Preconditions_To_api_Preconditions, - Convert_api_Preconditions_To_v1_Preconditions, - Convert_v1_PreferAvoidPodsEntry_To_api_PreferAvoidPodsEntry, - Convert_api_PreferAvoidPodsEntry_To_v1_PreferAvoidPodsEntry, - Convert_v1_PreferredSchedulingTerm_To_api_PreferredSchedulingTerm, - Convert_api_PreferredSchedulingTerm_To_v1_PreferredSchedulingTerm, - Convert_v1_Probe_To_api_Probe, - Convert_api_Probe_To_v1_Probe, - Convert_v1_ProjectedVolumeSource_To_api_ProjectedVolumeSource, - Convert_api_ProjectedVolumeSource_To_v1_ProjectedVolumeSource, - Convert_v1_QuobyteVolumeSource_To_api_QuobyteVolumeSource, - Convert_api_QuobyteVolumeSource_To_v1_QuobyteVolumeSource, - Convert_v1_RBDVolumeSource_To_api_RBDVolumeSource, - Convert_api_RBDVolumeSource_To_v1_RBDVolumeSource, - Convert_v1_RangeAllocation_To_api_RangeAllocation, - Convert_api_RangeAllocation_To_v1_RangeAllocation, - Convert_v1_ReplicationController_To_api_ReplicationController, - Convert_api_ReplicationController_To_v1_ReplicationController, - Convert_v1_ReplicationControllerCondition_To_api_ReplicationControllerCondition, - Convert_api_ReplicationControllerCondition_To_v1_ReplicationControllerCondition, - Convert_v1_ReplicationControllerList_To_api_ReplicationControllerList, - Convert_api_ReplicationControllerList_To_v1_ReplicationControllerList, - Convert_v1_ReplicationControllerSpec_To_api_ReplicationControllerSpec, - Convert_api_ReplicationControllerSpec_To_v1_ReplicationControllerSpec, - Convert_v1_ReplicationControllerStatus_To_api_ReplicationControllerStatus, - Convert_api_ReplicationControllerStatus_To_v1_ReplicationControllerStatus, - Convert_v1_ResourceFieldSelector_To_api_ResourceFieldSelector, - Convert_api_ResourceFieldSelector_To_v1_ResourceFieldSelector, - Convert_v1_ResourceQuota_To_api_ResourceQuota, - Convert_api_ResourceQuota_To_v1_ResourceQuota, - Convert_v1_ResourceQuotaList_To_api_ResourceQuotaList, - Convert_api_ResourceQuotaList_To_v1_ResourceQuotaList, - Convert_v1_ResourceQuotaSpec_To_api_ResourceQuotaSpec, - Convert_api_ResourceQuotaSpec_To_v1_ResourceQuotaSpec, - Convert_v1_ResourceQuotaStatus_To_api_ResourceQuotaStatus, - Convert_api_ResourceQuotaStatus_To_v1_ResourceQuotaStatus, - Convert_v1_ResourceRequirements_To_api_ResourceRequirements, - Convert_api_ResourceRequirements_To_v1_ResourceRequirements, - Convert_v1_SELinuxOptions_To_api_SELinuxOptions, - Convert_api_SELinuxOptions_To_v1_SELinuxOptions, - Convert_v1_ScaleIOVolumeSource_To_api_ScaleIOVolumeSource, - Convert_api_ScaleIOVolumeSource_To_v1_ScaleIOVolumeSource, - Convert_v1_Secret_To_api_Secret, - Convert_api_Secret_To_v1_Secret, - Convert_v1_SecretEnvSource_To_api_SecretEnvSource, - Convert_api_SecretEnvSource_To_v1_SecretEnvSource, - Convert_v1_SecretKeySelector_To_api_SecretKeySelector, - Convert_api_SecretKeySelector_To_v1_SecretKeySelector, - Convert_v1_SecretList_To_api_SecretList, - Convert_api_SecretList_To_v1_SecretList, - Convert_v1_SecretProjection_To_api_SecretProjection, - Convert_api_SecretProjection_To_v1_SecretProjection, - Convert_v1_SecretReference_To_api_SecretReference, - Convert_api_SecretReference_To_v1_SecretReference, - Convert_v1_SecretVolumeSource_To_api_SecretVolumeSource, - Convert_api_SecretVolumeSource_To_v1_SecretVolumeSource, - Convert_v1_SecurityContext_To_api_SecurityContext, - Convert_api_SecurityContext_To_v1_SecurityContext, - Convert_v1_SerializedReference_To_api_SerializedReference, - Convert_api_SerializedReference_To_v1_SerializedReference, - Convert_v1_Service_To_api_Service, - Convert_api_Service_To_v1_Service, - Convert_v1_ServiceAccount_To_api_ServiceAccount, - Convert_api_ServiceAccount_To_v1_ServiceAccount, - Convert_v1_ServiceAccountList_To_api_ServiceAccountList, - Convert_api_ServiceAccountList_To_v1_ServiceAccountList, - Convert_v1_ServiceList_To_api_ServiceList, - Convert_api_ServiceList_To_v1_ServiceList, - Convert_v1_ServicePort_To_api_ServicePort, - Convert_api_ServicePort_To_v1_ServicePort, - Convert_v1_ServiceProxyOptions_To_api_ServiceProxyOptions, - Convert_api_ServiceProxyOptions_To_v1_ServiceProxyOptions, - Convert_v1_ServiceSpec_To_api_ServiceSpec, - Convert_api_ServiceSpec_To_v1_ServiceSpec, - Convert_v1_ServiceStatus_To_api_ServiceStatus, - Convert_api_ServiceStatus_To_v1_ServiceStatus, - Convert_v1_SessionAffinityConfig_To_api_SessionAffinityConfig, - Convert_api_SessionAffinityConfig_To_v1_SessionAffinityConfig, - Convert_v1_StorageOSPersistentVolumeSource_To_api_StorageOSPersistentVolumeSource, - Convert_api_StorageOSPersistentVolumeSource_To_v1_StorageOSPersistentVolumeSource, - Convert_v1_StorageOSVolumeSource_To_api_StorageOSVolumeSource, - Convert_api_StorageOSVolumeSource_To_v1_StorageOSVolumeSource, - Convert_v1_Sysctl_To_api_Sysctl, - Convert_api_Sysctl_To_v1_Sysctl, - Convert_v1_TCPSocketAction_To_api_TCPSocketAction, - Convert_api_TCPSocketAction_To_v1_TCPSocketAction, - Convert_v1_Taint_To_api_Taint, - Convert_api_Taint_To_v1_Taint, - Convert_v1_Toleration_To_api_Toleration, - Convert_api_Toleration_To_v1_Toleration, - Convert_v1_Volume_To_api_Volume, - Convert_api_Volume_To_v1_Volume, - Convert_v1_VolumeMount_To_api_VolumeMount, - Convert_api_VolumeMount_To_v1_VolumeMount, - Convert_v1_VolumeProjection_To_api_VolumeProjection, - Convert_api_VolumeProjection_To_v1_VolumeProjection, - Convert_v1_VolumeSource_To_api_VolumeSource, - Convert_api_VolumeSource_To_v1_VolumeSource, - Convert_v1_VsphereVirtualDiskVolumeSource_To_api_VsphereVirtualDiskVolumeSource, - Convert_api_VsphereVirtualDiskVolumeSource_To_v1_VsphereVirtualDiskVolumeSource, - Convert_v1_WeightedPodAffinityTerm_To_api_WeightedPodAffinityTerm, - Convert_api_WeightedPodAffinityTerm_To_v1_WeightedPodAffinityTerm, - ) -} - -func autoConvert_v1_AWSElasticBlockStoreVolumeSource_To_api_AWSElasticBlockStoreVolumeSource(in *v1.AWSElasticBlockStoreVolumeSource, out *api.AWSElasticBlockStoreVolumeSource, s conversion.Scope) error { - out.VolumeID = in.VolumeID - out.FSType = in.FSType - out.Partition = in.Partition - out.ReadOnly = in.ReadOnly - return nil -} - -// Convert_v1_AWSElasticBlockStoreVolumeSource_To_api_AWSElasticBlockStoreVolumeSource is an autogenerated conversion function. -func Convert_v1_AWSElasticBlockStoreVolumeSource_To_api_AWSElasticBlockStoreVolumeSource(in *v1.AWSElasticBlockStoreVolumeSource, out *api.AWSElasticBlockStoreVolumeSource, s conversion.Scope) error { - return autoConvert_v1_AWSElasticBlockStoreVolumeSource_To_api_AWSElasticBlockStoreVolumeSource(in, out, s) -} - -func autoConvert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource(in *api.AWSElasticBlockStoreVolumeSource, out *v1.AWSElasticBlockStoreVolumeSource, s conversion.Scope) error { - out.VolumeID = in.VolumeID - out.FSType = in.FSType - out.Partition = in.Partition - out.ReadOnly = in.ReadOnly - return nil -} - -// Convert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource is an autogenerated conversion function. -func Convert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource(in *api.AWSElasticBlockStoreVolumeSource, out *v1.AWSElasticBlockStoreVolumeSource, s conversion.Scope) error { - return autoConvert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource(in, out, s) -} - -func autoConvert_v1_Affinity_To_api_Affinity(in *v1.Affinity, out *api.Affinity, s conversion.Scope) error { - out.NodeAffinity = (*api.NodeAffinity)(unsafe.Pointer(in.NodeAffinity)) - out.PodAffinity = (*api.PodAffinity)(unsafe.Pointer(in.PodAffinity)) - out.PodAntiAffinity = (*api.PodAntiAffinity)(unsafe.Pointer(in.PodAntiAffinity)) - return nil -} - -// Convert_v1_Affinity_To_api_Affinity is an autogenerated conversion function. -func Convert_v1_Affinity_To_api_Affinity(in *v1.Affinity, out *api.Affinity, s conversion.Scope) error { - return autoConvert_v1_Affinity_To_api_Affinity(in, out, s) -} - -func autoConvert_api_Affinity_To_v1_Affinity(in *api.Affinity, out *v1.Affinity, s conversion.Scope) error { - out.NodeAffinity = (*v1.NodeAffinity)(unsafe.Pointer(in.NodeAffinity)) - out.PodAffinity = (*v1.PodAffinity)(unsafe.Pointer(in.PodAffinity)) - out.PodAntiAffinity = (*v1.PodAntiAffinity)(unsafe.Pointer(in.PodAntiAffinity)) - return nil -} - -// Convert_api_Affinity_To_v1_Affinity is an autogenerated conversion function. -func Convert_api_Affinity_To_v1_Affinity(in *api.Affinity, out *v1.Affinity, s conversion.Scope) error { - return autoConvert_api_Affinity_To_v1_Affinity(in, out, s) -} - -func autoConvert_v1_AttachedVolume_To_api_AttachedVolume(in *v1.AttachedVolume, out *api.AttachedVolume, s conversion.Scope) error { - out.Name = api.UniqueVolumeName(in.Name) - out.DevicePath = in.DevicePath - return nil -} - -// Convert_v1_AttachedVolume_To_api_AttachedVolume is an autogenerated conversion function. -func Convert_v1_AttachedVolume_To_api_AttachedVolume(in *v1.AttachedVolume, out *api.AttachedVolume, s conversion.Scope) error { - return autoConvert_v1_AttachedVolume_To_api_AttachedVolume(in, out, s) -} - -func autoConvert_api_AttachedVolume_To_v1_AttachedVolume(in *api.AttachedVolume, out *v1.AttachedVolume, s conversion.Scope) error { - out.Name = v1.UniqueVolumeName(in.Name) - out.DevicePath = in.DevicePath - return nil -} - -// Convert_api_AttachedVolume_To_v1_AttachedVolume is an autogenerated conversion function. -func Convert_api_AttachedVolume_To_v1_AttachedVolume(in *api.AttachedVolume, out *v1.AttachedVolume, s conversion.Scope) error { - return autoConvert_api_AttachedVolume_To_v1_AttachedVolume(in, out, s) -} - -func autoConvert_v1_AvoidPods_To_api_AvoidPods(in *v1.AvoidPods, out *api.AvoidPods, s conversion.Scope) error { - out.PreferAvoidPods = *(*[]api.PreferAvoidPodsEntry)(unsafe.Pointer(&in.PreferAvoidPods)) - return nil -} - -// Convert_v1_AvoidPods_To_api_AvoidPods is an autogenerated conversion function. -func Convert_v1_AvoidPods_To_api_AvoidPods(in *v1.AvoidPods, out *api.AvoidPods, s conversion.Scope) error { - return autoConvert_v1_AvoidPods_To_api_AvoidPods(in, out, s) -} - -func autoConvert_api_AvoidPods_To_v1_AvoidPods(in *api.AvoidPods, out *v1.AvoidPods, s conversion.Scope) error { - out.PreferAvoidPods = *(*[]v1.PreferAvoidPodsEntry)(unsafe.Pointer(&in.PreferAvoidPods)) - return nil -} - -// Convert_api_AvoidPods_To_v1_AvoidPods is an autogenerated conversion function. -func Convert_api_AvoidPods_To_v1_AvoidPods(in *api.AvoidPods, out *v1.AvoidPods, s conversion.Scope) error { - return autoConvert_api_AvoidPods_To_v1_AvoidPods(in, out, s) -} - -func autoConvert_v1_AzureDiskVolumeSource_To_api_AzureDiskVolumeSource(in *v1.AzureDiskVolumeSource, out *api.AzureDiskVolumeSource, s conversion.Scope) error { - out.DiskName = in.DiskName - out.DataDiskURI = in.DataDiskURI - out.CachingMode = (*api.AzureDataDiskCachingMode)(unsafe.Pointer(in.CachingMode)) - out.FSType = (*string)(unsafe.Pointer(in.FSType)) - out.ReadOnly = (*bool)(unsafe.Pointer(in.ReadOnly)) - out.Kind = (*api.AzureDataDiskKind)(unsafe.Pointer(in.Kind)) - return nil -} - -// Convert_v1_AzureDiskVolumeSource_To_api_AzureDiskVolumeSource is an autogenerated conversion function. -func Convert_v1_AzureDiskVolumeSource_To_api_AzureDiskVolumeSource(in *v1.AzureDiskVolumeSource, out *api.AzureDiskVolumeSource, s conversion.Scope) error { - return autoConvert_v1_AzureDiskVolumeSource_To_api_AzureDiskVolumeSource(in, out, s) -} - -func autoConvert_api_AzureDiskVolumeSource_To_v1_AzureDiskVolumeSource(in *api.AzureDiskVolumeSource, out *v1.AzureDiskVolumeSource, s conversion.Scope) error { - out.DiskName = in.DiskName - out.DataDiskURI = in.DataDiskURI - out.CachingMode = (*v1.AzureDataDiskCachingMode)(unsafe.Pointer(in.CachingMode)) - out.FSType = (*string)(unsafe.Pointer(in.FSType)) - out.ReadOnly = (*bool)(unsafe.Pointer(in.ReadOnly)) - out.Kind = (*v1.AzureDataDiskKind)(unsafe.Pointer(in.Kind)) - return nil -} - -// Convert_api_AzureDiskVolumeSource_To_v1_AzureDiskVolumeSource is an autogenerated conversion function. -func Convert_api_AzureDiskVolumeSource_To_v1_AzureDiskVolumeSource(in *api.AzureDiskVolumeSource, out *v1.AzureDiskVolumeSource, s conversion.Scope) error { - return autoConvert_api_AzureDiskVolumeSource_To_v1_AzureDiskVolumeSource(in, out, s) -} - -func autoConvert_v1_AzureFilePersistentVolumeSource_To_api_AzureFilePersistentVolumeSource(in *v1.AzureFilePersistentVolumeSource, out *api.AzureFilePersistentVolumeSource, s conversion.Scope) error { - out.SecretName = in.SecretName - out.ShareName = in.ShareName - out.ReadOnly = in.ReadOnly - out.SecretNamespace = (*string)(unsafe.Pointer(in.SecretNamespace)) - return nil -} - -// Convert_v1_AzureFilePersistentVolumeSource_To_api_AzureFilePersistentVolumeSource is an autogenerated conversion function. -func Convert_v1_AzureFilePersistentVolumeSource_To_api_AzureFilePersistentVolumeSource(in *v1.AzureFilePersistentVolumeSource, out *api.AzureFilePersistentVolumeSource, s conversion.Scope) error { - return autoConvert_v1_AzureFilePersistentVolumeSource_To_api_AzureFilePersistentVolumeSource(in, out, s) -} - -func autoConvert_api_AzureFilePersistentVolumeSource_To_v1_AzureFilePersistentVolumeSource(in *api.AzureFilePersistentVolumeSource, out *v1.AzureFilePersistentVolumeSource, s conversion.Scope) error { - out.SecretName = in.SecretName - out.ShareName = in.ShareName - out.ReadOnly = in.ReadOnly - out.SecretNamespace = (*string)(unsafe.Pointer(in.SecretNamespace)) - return nil -} - -// Convert_api_AzureFilePersistentVolumeSource_To_v1_AzureFilePersistentVolumeSource is an autogenerated conversion function. -func Convert_api_AzureFilePersistentVolumeSource_To_v1_AzureFilePersistentVolumeSource(in *api.AzureFilePersistentVolumeSource, out *v1.AzureFilePersistentVolumeSource, s conversion.Scope) error { - return autoConvert_api_AzureFilePersistentVolumeSource_To_v1_AzureFilePersistentVolumeSource(in, out, s) -} - -func autoConvert_v1_AzureFileVolumeSource_To_api_AzureFileVolumeSource(in *v1.AzureFileVolumeSource, out *api.AzureFileVolumeSource, s conversion.Scope) error { - out.SecretName = in.SecretName - out.ShareName = in.ShareName - out.ReadOnly = in.ReadOnly - return nil -} - -// Convert_v1_AzureFileVolumeSource_To_api_AzureFileVolumeSource is an autogenerated conversion function. -func Convert_v1_AzureFileVolumeSource_To_api_AzureFileVolumeSource(in *v1.AzureFileVolumeSource, out *api.AzureFileVolumeSource, s conversion.Scope) error { - return autoConvert_v1_AzureFileVolumeSource_To_api_AzureFileVolumeSource(in, out, s) -} - -func autoConvert_api_AzureFileVolumeSource_To_v1_AzureFileVolumeSource(in *api.AzureFileVolumeSource, out *v1.AzureFileVolumeSource, s conversion.Scope) error { - out.SecretName = in.SecretName - out.ShareName = in.ShareName - out.ReadOnly = in.ReadOnly - return nil -} - -// Convert_api_AzureFileVolumeSource_To_v1_AzureFileVolumeSource is an autogenerated conversion function. -func Convert_api_AzureFileVolumeSource_To_v1_AzureFileVolumeSource(in *api.AzureFileVolumeSource, out *v1.AzureFileVolumeSource, s conversion.Scope) error { - return autoConvert_api_AzureFileVolumeSource_To_v1_AzureFileVolumeSource(in, out, s) -} - -func autoConvert_v1_Binding_To_api_Binding(in *v1.Binding, out *api.Binding, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1_ObjectReference_To_api_ObjectReference(&in.Target, &out.Target, s); err != nil { - return err - } - return nil -} - -// Convert_v1_Binding_To_api_Binding is an autogenerated conversion function. -func Convert_v1_Binding_To_api_Binding(in *v1.Binding, out *api.Binding, s conversion.Scope) error { - return autoConvert_v1_Binding_To_api_Binding(in, out, s) -} - -func autoConvert_api_Binding_To_v1_Binding(in *api.Binding, out *v1.Binding, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_api_ObjectReference_To_v1_ObjectReference(&in.Target, &out.Target, s); err != nil { - return err - } - return nil -} - -// Convert_api_Binding_To_v1_Binding is an autogenerated conversion function. -func Convert_api_Binding_To_v1_Binding(in *api.Binding, out *v1.Binding, s conversion.Scope) error { - return autoConvert_api_Binding_To_v1_Binding(in, out, s) -} - -func autoConvert_v1_Capabilities_To_api_Capabilities(in *v1.Capabilities, out *api.Capabilities, s conversion.Scope) error { - out.Add = *(*[]api.Capability)(unsafe.Pointer(&in.Add)) - out.Drop = *(*[]api.Capability)(unsafe.Pointer(&in.Drop)) - return nil -} - -// Convert_v1_Capabilities_To_api_Capabilities is an autogenerated conversion function. -func Convert_v1_Capabilities_To_api_Capabilities(in *v1.Capabilities, out *api.Capabilities, s conversion.Scope) error { - return autoConvert_v1_Capabilities_To_api_Capabilities(in, out, s) -} - -func autoConvert_api_Capabilities_To_v1_Capabilities(in *api.Capabilities, out *v1.Capabilities, s conversion.Scope) error { - out.Add = *(*[]v1.Capability)(unsafe.Pointer(&in.Add)) - out.Drop = *(*[]v1.Capability)(unsafe.Pointer(&in.Drop)) - return nil -} - -// Convert_api_Capabilities_To_v1_Capabilities is an autogenerated conversion function. -func Convert_api_Capabilities_To_v1_Capabilities(in *api.Capabilities, out *v1.Capabilities, s conversion.Scope) error { - return autoConvert_api_Capabilities_To_v1_Capabilities(in, out, s) -} - -func autoConvert_v1_CephFSPersistentVolumeSource_To_api_CephFSPersistentVolumeSource(in *v1.CephFSPersistentVolumeSource, out *api.CephFSPersistentVolumeSource, s conversion.Scope) error { - out.Monitors = *(*[]string)(unsafe.Pointer(&in.Monitors)) - out.Path = in.Path - out.User = in.User - out.SecretFile = in.SecretFile - out.SecretRef = (*api.SecretReference)(unsafe.Pointer(in.SecretRef)) - out.ReadOnly = in.ReadOnly - return nil -} - -// Convert_v1_CephFSPersistentVolumeSource_To_api_CephFSPersistentVolumeSource is an autogenerated conversion function. -func Convert_v1_CephFSPersistentVolumeSource_To_api_CephFSPersistentVolumeSource(in *v1.CephFSPersistentVolumeSource, out *api.CephFSPersistentVolumeSource, s conversion.Scope) error { - return autoConvert_v1_CephFSPersistentVolumeSource_To_api_CephFSPersistentVolumeSource(in, out, s) -} - -func autoConvert_api_CephFSPersistentVolumeSource_To_v1_CephFSPersistentVolumeSource(in *api.CephFSPersistentVolumeSource, out *v1.CephFSPersistentVolumeSource, s conversion.Scope) error { - out.Monitors = *(*[]string)(unsafe.Pointer(&in.Monitors)) - out.Path = in.Path - out.User = in.User - out.SecretFile = in.SecretFile - out.SecretRef = (*v1.SecretReference)(unsafe.Pointer(in.SecretRef)) - out.ReadOnly = in.ReadOnly - return nil -} - -// Convert_api_CephFSPersistentVolumeSource_To_v1_CephFSPersistentVolumeSource is an autogenerated conversion function. -func Convert_api_CephFSPersistentVolumeSource_To_v1_CephFSPersistentVolumeSource(in *api.CephFSPersistentVolumeSource, out *v1.CephFSPersistentVolumeSource, s conversion.Scope) error { - return autoConvert_api_CephFSPersistentVolumeSource_To_v1_CephFSPersistentVolumeSource(in, out, s) -} - -func autoConvert_v1_CephFSVolumeSource_To_api_CephFSVolumeSource(in *v1.CephFSVolumeSource, out *api.CephFSVolumeSource, s conversion.Scope) error { - out.Monitors = *(*[]string)(unsafe.Pointer(&in.Monitors)) - out.Path = in.Path - out.User = in.User - out.SecretFile = in.SecretFile - out.SecretRef = (*api.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) - out.ReadOnly = in.ReadOnly - return nil -} - -// Convert_v1_CephFSVolumeSource_To_api_CephFSVolumeSource is an autogenerated conversion function. -func Convert_v1_CephFSVolumeSource_To_api_CephFSVolumeSource(in *v1.CephFSVolumeSource, out *api.CephFSVolumeSource, s conversion.Scope) error { - return autoConvert_v1_CephFSVolumeSource_To_api_CephFSVolumeSource(in, out, s) -} - -func autoConvert_api_CephFSVolumeSource_To_v1_CephFSVolumeSource(in *api.CephFSVolumeSource, out *v1.CephFSVolumeSource, s conversion.Scope) error { - out.Monitors = *(*[]string)(unsafe.Pointer(&in.Monitors)) - out.Path = in.Path - out.User = in.User - out.SecretFile = in.SecretFile - out.SecretRef = (*v1.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) - out.ReadOnly = in.ReadOnly - return nil -} - -// Convert_api_CephFSVolumeSource_To_v1_CephFSVolumeSource is an autogenerated conversion function. -func Convert_api_CephFSVolumeSource_To_v1_CephFSVolumeSource(in *api.CephFSVolumeSource, out *v1.CephFSVolumeSource, s conversion.Scope) error { - return autoConvert_api_CephFSVolumeSource_To_v1_CephFSVolumeSource(in, out, s) -} - -func autoConvert_v1_CinderVolumeSource_To_api_CinderVolumeSource(in *v1.CinderVolumeSource, out *api.CinderVolumeSource, s conversion.Scope) error { - out.VolumeID = in.VolumeID - out.FSType = in.FSType - out.ReadOnly = in.ReadOnly - return nil -} - -// Convert_v1_CinderVolumeSource_To_api_CinderVolumeSource is an autogenerated conversion function. -func Convert_v1_CinderVolumeSource_To_api_CinderVolumeSource(in *v1.CinderVolumeSource, out *api.CinderVolumeSource, s conversion.Scope) error { - return autoConvert_v1_CinderVolumeSource_To_api_CinderVolumeSource(in, out, s) -} - -func autoConvert_api_CinderVolumeSource_To_v1_CinderVolumeSource(in *api.CinderVolumeSource, out *v1.CinderVolumeSource, s conversion.Scope) error { - out.VolumeID = in.VolumeID - out.FSType = in.FSType - out.ReadOnly = in.ReadOnly - return nil -} - -// Convert_api_CinderVolumeSource_To_v1_CinderVolumeSource is an autogenerated conversion function. -func Convert_api_CinderVolumeSource_To_v1_CinderVolumeSource(in *api.CinderVolumeSource, out *v1.CinderVolumeSource, s conversion.Scope) error { - return autoConvert_api_CinderVolumeSource_To_v1_CinderVolumeSource(in, out, s) -} - -func autoConvert_v1_ClientIPConfig_To_api_ClientIPConfig(in *v1.ClientIPConfig, out *api.ClientIPConfig, s conversion.Scope) error { - out.TimeoutSeconds = (*int32)(unsafe.Pointer(in.TimeoutSeconds)) - return nil -} - -// Convert_v1_ClientIPConfig_To_api_ClientIPConfig is an autogenerated conversion function. -func Convert_v1_ClientIPConfig_To_api_ClientIPConfig(in *v1.ClientIPConfig, out *api.ClientIPConfig, s conversion.Scope) error { - return autoConvert_v1_ClientIPConfig_To_api_ClientIPConfig(in, out, s) -} - -func autoConvert_api_ClientIPConfig_To_v1_ClientIPConfig(in *api.ClientIPConfig, out *v1.ClientIPConfig, s conversion.Scope) error { - out.TimeoutSeconds = (*int32)(unsafe.Pointer(in.TimeoutSeconds)) - return nil -} - -// Convert_api_ClientIPConfig_To_v1_ClientIPConfig is an autogenerated conversion function. -func Convert_api_ClientIPConfig_To_v1_ClientIPConfig(in *api.ClientIPConfig, out *v1.ClientIPConfig, s conversion.Scope) error { - return autoConvert_api_ClientIPConfig_To_v1_ClientIPConfig(in, out, s) -} - -func autoConvert_v1_ComponentCondition_To_api_ComponentCondition(in *v1.ComponentCondition, out *api.ComponentCondition, s conversion.Scope) error { - out.Type = api.ComponentConditionType(in.Type) - out.Status = api.ConditionStatus(in.Status) - out.Message = in.Message - out.Error = in.Error - return nil -} - -// Convert_v1_ComponentCondition_To_api_ComponentCondition is an autogenerated conversion function. -func Convert_v1_ComponentCondition_To_api_ComponentCondition(in *v1.ComponentCondition, out *api.ComponentCondition, s conversion.Scope) error { - return autoConvert_v1_ComponentCondition_To_api_ComponentCondition(in, out, s) -} - -func autoConvert_api_ComponentCondition_To_v1_ComponentCondition(in *api.ComponentCondition, out *v1.ComponentCondition, s conversion.Scope) error { - out.Type = v1.ComponentConditionType(in.Type) - out.Status = v1.ConditionStatus(in.Status) - out.Message = in.Message - out.Error = in.Error - return nil -} - -// Convert_api_ComponentCondition_To_v1_ComponentCondition is an autogenerated conversion function. -func Convert_api_ComponentCondition_To_v1_ComponentCondition(in *api.ComponentCondition, out *v1.ComponentCondition, s conversion.Scope) error { - return autoConvert_api_ComponentCondition_To_v1_ComponentCondition(in, out, s) -} - -func autoConvert_v1_ComponentStatus_To_api_ComponentStatus(in *v1.ComponentStatus, out *api.ComponentStatus, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Conditions = *(*[]api.ComponentCondition)(unsafe.Pointer(&in.Conditions)) - return nil -} - -// Convert_v1_ComponentStatus_To_api_ComponentStatus is an autogenerated conversion function. -func Convert_v1_ComponentStatus_To_api_ComponentStatus(in *v1.ComponentStatus, out *api.ComponentStatus, s conversion.Scope) error { - return autoConvert_v1_ComponentStatus_To_api_ComponentStatus(in, out, s) -} - -func autoConvert_api_ComponentStatus_To_v1_ComponentStatus(in *api.ComponentStatus, out *v1.ComponentStatus, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Conditions = *(*[]v1.ComponentCondition)(unsafe.Pointer(&in.Conditions)) - return nil -} - -// Convert_api_ComponentStatus_To_v1_ComponentStatus is an autogenerated conversion function. -func Convert_api_ComponentStatus_To_v1_ComponentStatus(in *api.ComponentStatus, out *v1.ComponentStatus, s conversion.Scope) error { - return autoConvert_api_ComponentStatus_To_v1_ComponentStatus(in, out, s) -} - -func autoConvert_v1_ComponentStatusList_To_api_ComponentStatusList(in *v1.ComponentStatusList, out *api.ComponentStatusList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]api.ComponentStatus)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_v1_ComponentStatusList_To_api_ComponentStatusList is an autogenerated conversion function. -func Convert_v1_ComponentStatusList_To_api_ComponentStatusList(in *v1.ComponentStatusList, out *api.ComponentStatusList, s conversion.Scope) error { - return autoConvert_v1_ComponentStatusList_To_api_ComponentStatusList(in, out, s) -} - -func autoConvert_api_ComponentStatusList_To_v1_ComponentStatusList(in *api.ComponentStatusList, out *v1.ComponentStatusList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]v1.ComponentStatus)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_api_ComponentStatusList_To_v1_ComponentStatusList is an autogenerated conversion function. -func Convert_api_ComponentStatusList_To_v1_ComponentStatusList(in *api.ComponentStatusList, out *v1.ComponentStatusList, s conversion.Scope) error { - return autoConvert_api_ComponentStatusList_To_v1_ComponentStatusList(in, out, s) -} - -func autoConvert_v1_ConfigMap_To_api_ConfigMap(in *v1.ConfigMap, out *api.ConfigMap, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Data = *(*map[string]string)(unsafe.Pointer(&in.Data)) - return nil -} - -// Convert_v1_ConfigMap_To_api_ConfigMap is an autogenerated conversion function. -func Convert_v1_ConfigMap_To_api_ConfigMap(in *v1.ConfigMap, out *api.ConfigMap, s conversion.Scope) error { - return autoConvert_v1_ConfigMap_To_api_ConfigMap(in, out, s) -} - -func autoConvert_api_ConfigMap_To_v1_ConfigMap(in *api.ConfigMap, out *v1.ConfigMap, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Data = *(*map[string]string)(unsafe.Pointer(&in.Data)) - return nil -} - -// Convert_api_ConfigMap_To_v1_ConfigMap is an autogenerated conversion function. -func Convert_api_ConfigMap_To_v1_ConfigMap(in *api.ConfigMap, out *v1.ConfigMap, s conversion.Scope) error { - return autoConvert_api_ConfigMap_To_v1_ConfigMap(in, out, s) -} - -func autoConvert_v1_ConfigMapEnvSource_To_api_ConfigMapEnvSource(in *v1.ConfigMapEnvSource, out *api.ConfigMapEnvSource, s conversion.Scope) error { - if err := Convert_v1_LocalObjectReference_To_api_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { - return err - } - out.Optional = (*bool)(unsafe.Pointer(in.Optional)) - return nil -} - -// Convert_v1_ConfigMapEnvSource_To_api_ConfigMapEnvSource is an autogenerated conversion function. -func Convert_v1_ConfigMapEnvSource_To_api_ConfigMapEnvSource(in *v1.ConfigMapEnvSource, out *api.ConfigMapEnvSource, s conversion.Scope) error { - return autoConvert_v1_ConfigMapEnvSource_To_api_ConfigMapEnvSource(in, out, s) -} - -func autoConvert_api_ConfigMapEnvSource_To_v1_ConfigMapEnvSource(in *api.ConfigMapEnvSource, out *v1.ConfigMapEnvSource, s conversion.Scope) error { - if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { - return err - } - out.Optional = (*bool)(unsafe.Pointer(in.Optional)) - return nil -} - -// Convert_api_ConfigMapEnvSource_To_v1_ConfigMapEnvSource is an autogenerated conversion function. -func Convert_api_ConfigMapEnvSource_To_v1_ConfigMapEnvSource(in *api.ConfigMapEnvSource, out *v1.ConfigMapEnvSource, s conversion.Scope) error { - return autoConvert_api_ConfigMapEnvSource_To_v1_ConfigMapEnvSource(in, out, s) -} - -func autoConvert_v1_ConfigMapKeySelector_To_api_ConfigMapKeySelector(in *v1.ConfigMapKeySelector, out *api.ConfigMapKeySelector, s conversion.Scope) error { - if err := Convert_v1_LocalObjectReference_To_api_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { - return err - } - out.Key = in.Key - out.Optional = (*bool)(unsafe.Pointer(in.Optional)) - return nil -} - -// Convert_v1_ConfigMapKeySelector_To_api_ConfigMapKeySelector is an autogenerated conversion function. -func Convert_v1_ConfigMapKeySelector_To_api_ConfigMapKeySelector(in *v1.ConfigMapKeySelector, out *api.ConfigMapKeySelector, s conversion.Scope) error { - return autoConvert_v1_ConfigMapKeySelector_To_api_ConfigMapKeySelector(in, out, s) -} - -func autoConvert_api_ConfigMapKeySelector_To_v1_ConfigMapKeySelector(in *api.ConfigMapKeySelector, out *v1.ConfigMapKeySelector, s conversion.Scope) error { - if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { - return err - } - out.Key = in.Key - out.Optional = (*bool)(unsafe.Pointer(in.Optional)) - return nil -} - -// Convert_api_ConfigMapKeySelector_To_v1_ConfigMapKeySelector is an autogenerated conversion function. -func Convert_api_ConfigMapKeySelector_To_v1_ConfigMapKeySelector(in *api.ConfigMapKeySelector, out *v1.ConfigMapKeySelector, s conversion.Scope) error { - return autoConvert_api_ConfigMapKeySelector_To_v1_ConfigMapKeySelector(in, out, s) -} - -func autoConvert_v1_ConfigMapList_To_api_ConfigMapList(in *v1.ConfigMapList, out *api.ConfigMapList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]api.ConfigMap)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_v1_ConfigMapList_To_api_ConfigMapList is an autogenerated conversion function. -func Convert_v1_ConfigMapList_To_api_ConfigMapList(in *v1.ConfigMapList, out *api.ConfigMapList, s conversion.Scope) error { - return autoConvert_v1_ConfigMapList_To_api_ConfigMapList(in, out, s) -} - -func autoConvert_api_ConfigMapList_To_v1_ConfigMapList(in *api.ConfigMapList, out *v1.ConfigMapList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]v1.ConfigMap)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_api_ConfigMapList_To_v1_ConfigMapList is an autogenerated conversion function. -func Convert_api_ConfigMapList_To_v1_ConfigMapList(in *api.ConfigMapList, out *v1.ConfigMapList, s conversion.Scope) error { - return autoConvert_api_ConfigMapList_To_v1_ConfigMapList(in, out, s) -} - -func autoConvert_v1_ConfigMapProjection_To_api_ConfigMapProjection(in *v1.ConfigMapProjection, out *api.ConfigMapProjection, s conversion.Scope) error { - if err := Convert_v1_LocalObjectReference_To_api_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { - return err - } - out.Items = *(*[]api.KeyToPath)(unsafe.Pointer(&in.Items)) - out.Optional = (*bool)(unsafe.Pointer(in.Optional)) - return nil -} - -// Convert_v1_ConfigMapProjection_To_api_ConfigMapProjection is an autogenerated conversion function. -func Convert_v1_ConfigMapProjection_To_api_ConfigMapProjection(in *v1.ConfigMapProjection, out *api.ConfigMapProjection, s conversion.Scope) error { - return autoConvert_v1_ConfigMapProjection_To_api_ConfigMapProjection(in, out, s) -} - -func autoConvert_api_ConfigMapProjection_To_v1_ConfigMapProjection(in *api.ConfigMapProjection, out *v1.ConfigMapProjection, s conversion.Scope) error { - if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { - return err - } - out.Items = *(*[]v1.KeyToPath)(unsafe.Pointer(&in.Items)) - out.Optional = (*bool)(unsafe.Pointer(in.Optional)) - return nil -} - -// Convert_api_ConfigMapProjection_To_v1_ConfigMapProjection is an autogenerated conversion function. -func Convert_api_ConfigMapProjection_To_v1_ConfigMapProjection(in *api.ConfigMapProjection, out *v1.ConfigMapProjection, s conversion.Scope) error { - return autoConvert_api_ConfigMapProjection_To_v1_ConfigMapProjection(in, out, s) -} - -func autoConvert_v1_ConfigMapVolumeSource_To_api_ConfigMapVolumeSource(in *v1.ConfigMapVolumeSource, out *api.ConfigMapVolumeSource, s conversion.Scope) error { - if err := Convert_v1_LocalObjectReference_To_api_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { - return err - } - out.Items = *(*[]api.KeyToPath)(unsafe.Pointer(&in.Items)) - out.DefaultMode = (*int32)(unsafe.Pointer(in.DefaultMode)) - out.Optional = (*bool)(unsafe.Pointer(in.Optional)) - return nil -} - -// Convert_v1_ConfigMapVolumeSource_To_api_ConfigMapVolumeSource is an autogenerated conversion function. -func Convert_v1_ConfigMapVolumeSource_To_api_ConfigMapVolumeSource(in *v1.ConfigMapVolumeSource, out *api.ConfigMapVolumeSource, s conversion.Scope) error { - return autoConvert_v1_ConfigMapVolumeSource_To_api_ConfigMapVolumeSource(in, out, s) -} - -func autoConvert_api_ConfigMapVolumeSource_To_v1_ConfigMapVolumeSource(in *api.ConfigMapVolumeSource, out *v1.ConfigMapVolumeSource, s conversion.Scope) error { - if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { - return err - } - out.Items = *(*[]v1.KeyToPath)(unsafe.Pointer(&in.Items)) - out.DefaultMode = (*int32)(unsafe.Pointer(in.DefaultMode)) - out.Optional = (*bool)(unsafe.Pointer(in.Optional)) - return nil -} - -// Convert_api_ConfigMapVolumeSource_To_v1_ConfigMapVolumeSource is an autogenerated conversion function. -func Convert_api_ConfigMapVolumeSource_To_v1_ConfigMapVolumeSource(in *api.ConfigMapVolumeSource, out *v1.ConfigMapVolumeSource, s conversion.Scope) error { - return autoConvert_api_ConfigMapVolumeSource_To_v1_ConfigMapVolumeSource(in, out, s) -} - -func autoConvert_v1_Container_To_api_Container(in *v1.Container, out *api.Container, s conversion.Scope) error { - out.Name = in.Name - out.Image = in.Image - out.Command = *(*[]string)(unsafe.Pointer(&in.Command)) - out.Args = *(*[]string)(unsafe.Pointer(&in.Args)) - out.WorkingDir = in.WorkingDir - out.Ports = *(*[]api.ContainerPort)(unsafe.Pointer(&in.Ports)) - out.EnvFrom = *(*[]api.EnvFromSource)(unsafe.Pointer(&in.EnvFrom)) - out.Env = *(*[]api.EnvVar)(unsafe.Pointer(&in.Env)) - if err := Convert_v1_ResourceRequirements_To_api_ResourceRequirements(&in.Resources, &out.Resources, s); err != nil { - return err - } - out.VolumeMounts = *(*[]api.VolumeMount)(unsafe.Pointer(&in.VolumeMounts)) - out.LivenessProbe = (*api.Probe)(unsafe.Pointer(in.LivenessProbe)) - out.ReadinessProbe = (*api.Probe)(unsafe.Pointer(in.ReadinessProbe)) - out.Lifecycle = (*api.Lifecycle)(unsafe.Pointer(in.Lifecycle)) - out.TerminationMessagePath = in.TerminationMessagePath - out.TerminationMessagePolicy = api.TerminationMessagePolicy(in.TerminationMessagePolicy) - out.ImagePullPolicy = api.PullPolicy(in.ImagePullPolicy) - if in.SecurityContext != nil { - in, out := &in.SecurityContext, &out.SecurityContext - *out = new(api.SecurityContext) - if err := Convert_v1_SecurityContext_To_api_SecurityContext(*in, *out, s); err != nil { - return err - } - } else { - out.SecurityContext = nil - } - out.Stdin = in.Stdin - out.StdinOnce = in.StdinOnce - out.TTY = in.TTY - return nil -} - -// Convert_v1_Container_To_api_Container is an autogenerated conversion function. -func Convert_v1_Container_To_api_Container(in *v1.Container, out *api.Container, s conversion.Scope) error { - return autoConvert_v1_Container_To_api_Container(in, out, s) -} - -func autoConvert_api_Container_To_v1_Container(in *api.Container, out *v1.Container, s conversion.Scope) error { - out.Name = in.Name - out.Image = in.Image - out.Command = *(*[]string)(unsafe.Pointer(&in.Command)) - out.Args = *(*[]string)(unsafe.Pointer(&in.Args)) - out.WorkingDir = in.WorkingDir - out.Ports = *(*[]v1.ContainerPort)(unsafe.Pointer(&in.Ports)) - out.EnvFrom = *(*[]v1.EnvFromSource)(unsafe.Pointer(&in.EnvFrom)) - out.Env = *(*[]v1.EnvVar)(unsafe.Pointer(&in.Env)) - if err := Convert_api_ResourceRequirements_To_v1_ResourceRequirements(&in.Resources, &out.Resources, s); err != nil { - return err - } - out.VolumeMounts = *(*[]v1.VolumeMount)(unsafe.Pointer(&in.VolumeMounts)) - out.LivenessProbe = (*v1.Probe)(unsafe.Pointer(in.LivenessProbe)) - out.ReadinessProbe = (*v1.Probe)(unsafe.Pointer(in.ReadinessProbe)) - out.Lifecycle = (*v1.Lifecycle)(unsafe.Pointer(in.Lifecycle)) - out.TerminationMessagePath = in.TerminationMessagePath - out.TerminationMessagePolicy = v1.TerminationMessagePolicy(in.TerminationMessagePolicy) - out.ImagePullPolicy = v1.PullPolicy(in.ImagePullPolicy) - if in.SecurityContext != nil { - in, out := &in.SecurityContext, &out.SecurityContext - *out = new(v1.SecurityContext) - if err := Convert_api_SecurityContext_To_v1_SecurityContext(*in, *out, s); err != nil { - return err - } - } else { - out.SecurityContext = nil - } - out.Stdin = in.Stdin - out.StdinOnce = in.StdinOnce - out.TTY = in.TTY - return nil -} - -// Convert_api_Container_To_v1_Container is an autogenerated conversion function. -func Convert_api_Container_To_v1_Container(in *api.Container, out *v1.Container, s conversion.Scope) error { - return autoConvert_api_Container_To_v1_Container(in, out, s) -} - -func autoConvert_v1_ContainerImage_To_api_ContainerImage(in *v1.ContainerImage, out *api.ContainerImage, s conversion.Scope) error { - out.Names = *(*[]string)(unsafe.Pointer(&in.Names)) - out.SizeBytes = in.SizeBytes - return nil -} - -// Convert_v1_ContainerImage_To_api_ContainerImage is an autogenerated conversion function. -func Convert_v1_ContainerImage_To_api_ContainerImage(in *v1.ContainerImage, out *api.ContainerImage, s conversion.Scope) error { - return autoConvert_v1_ContainerImage_To_api_ContainerImage(in, out, s) -} - -func autoConvert_api_ContainerImage_To_v1_ContainerImage(in *api.ContainerImage, out *v1.ContainerImage, s conversion.Scope) error { - out.Names = *(*[]string)(unsafe.Pointer(&in.Names)) - out.SizeBytes = in.SizeBytes - return nil -} - -// Convert_api_ContainerImage_To_v1_ContainerImage is an autogenerated conversion function. -func Convert_api_ContainerImage_To_v1_ContainerImage(in *api.ContainerImage, out *v1.ContainerImage, s conversion.Scope) error { - return autoConvert_api_ContainerImage_To_v1_ContainerImage(in, out, s) -} - -func autoConvert_v1_ContainerPort_To_api_ContainerPort(in *v1.ContainerPort, out *api.ContainerPort, s conversion.Scope) error { - out.Name = in.Name - out.HostPort = in.HostPort - out.ContainerPort = in.ContainerPort - out.Protocol = api.Protocol(in.Protocol) - out.HostIP = in.HostIP - return nil -} - -// Convert_v1_ContainerPort_To_api_ContainerPort is an autogenerated conversion function. -func Convert_v1_ContainerPort_To_api_ContainerPort(in *v1.ContainerPort, out *api.ContainerPort, s conversion.Scope) error { - return autoConvert_v1_ContainerPort_To_api_ContainerPort(in, out, s) -} - -func autoConvert_api_ContainerPort_To_v1_ContainerPort(in *api.ContainerPort, out *v1.ContainerPort, s conversion.Scope) error { - out.Name = in.Name - out.HostPort = in.HostPort - out.ContainerPort = in.ContainerPort - out.Protocol = v1.Protocol(in.Protocol) - out.HostIP = in.HostIP - return nil -} - -// Convert_api_ContainerPort_To_v1_ContainerPort is an autogenerated conversion function. -func Convert_api_ContainerPort_To_v1_ContainerPort(in *api.ContainerPort, out *v1.ContainerPort, s conversion.Scope) error { - return autoConvert_api_ContainerPort_To_v1_ContainerPort(in, out, s) -} - -func autoConvert_v1_ContainerState_To_api_ContainerState(in *v1.ContainerState, out *api.ContainerState, s conversion.Scope) error { - out.Waiting = (*api.ContainerStateWaiting)(unsafe.Pointer(in.Waiting)) - out.Running = (*api.ContainerStateRunning)(unsafe.Pointer(in.Running)) - out.Terminated = (*api.ContainerStateTerminated)(unsafe.Pointer(in.Terminated)) - return nil -} - -// Convert_v1_ContainerState_To_api_ContainerState is an autogenerated conversion function. -func Convert_v1_ContainerState_To_api_ContainerState(in *v1.ContainerState, out *api.ContainerState, s conversion.Scope) error { - return autoConvert_v1_ContainerState_To_api_ContainerState(in, out, s) -} - -func autoConvert_api_ContainerState_To_v1_ContainerState(in *api.ContainerState, out *v1.ContainerState, s conversion.Scope) error { - out.Waiting = (*v1.ContainerStateWaiting)(unsafe.Pointer(in.Waiting)) - out.Running = (*v1.ContainerStateRunning)(unsafe.Pointer(in.Running)) - out.Terminated = (*v1.ContainerStateTerminated)(unsafe.Pointer(in.Terminated)) - return nil -} - -// Convert_api_ContainerState_To_v1_ContainerState is an autogenerated conversion function. -func Convert_api_ContainerState_To_v1_ContainerState(in *api.ContainerState, out *v1.ContainerState, s conversion.Scope) error { - return autoConvert_api_ContainerState_To_v1_ContainerState(in, out, s) -} - -func autoConvert_v1_ContainerStateRunning_To_api_ContainerStateRunning(in *v1.ContainerStateRunning, out *api.ContainerStateRunning, s conversion.Scope) error { - out.StartedAt = in.StartedAt - return nil -} - -// Convert_v1_ContainerStateRunning_To_api_ContainerStateRunning is an autogenerated conversion function. -func Convert_v1_ContainerStateRunning_To_api_ContainerStateRunning(in *v1.ContainerStateRunning, out *api.ContainerStateRunning, s conversion.Scope) error { - return autoConvert_v1_ContainerStateRunning_To_api_ContainerStateRunning(in, out, s) -} - -func autoConvert_api_ContainerStateRunning_To_v1_ContainerStateRunning(in *api.ContainerStateRunning, out *v1.ContainerStateRunning, s conversion.Scope) error { - out.StartedAt = in.StartedAt - return nil -} - -// Convert_api_ContainerStateRunning_To_v1_ContainerStateRunning is an autogenerated conversion function. -func Convert_api_ContainerStateRunning_To_v1_ContainerStateRunning(in *api.ContainerStateRunning, out *v1.ContainerStateRunning, s conversion.Scope) error { - return autoConvert_api_ContainerStateRunning_To_v1_ContainerStateRunning(in, out, s) -} - -func autoConvert_v1_ContainerStateTerminated_To_api_ContainerStateTerminated(in *v1.ContainerStateTerminated, out *api.ContainerStateTerminated, s conversion.Scope) error { - out.ExitCode = in.ExitCode - out.Signal = in.Signal - out.Reason = in.Reason - out.Message = in.Message - out.StartedAt = in.StartedAt - out.FinishedAt = in.FinishedAt - out.ContainerID = in.ContainerID - return nil -} - -// Convert_v1_ContainerStateTerminated_To_api_ContainerStateTerminated is an autogenerated conversion function. -func Convert_v1_ContainerStateTerminated_To_api_ContainerStateTerminated(in *v1.ContainerStateTerminated, out *api.ContainerStateTerminated, s conversion.Scope) error { - return autoConvert_v1_ContainerStateTerminated_To_api_ContainerStateTerminated(in, out, s) -} - -func autoConvert_api_ContainerStateTerminated_To_v1_ContainerStateTerminated(in *api.ContainerStateTerminated, out *v1.ContainerStateTerminated, s conversion.Scope) error { - out.ExitCode = in.ExitCode - out.Signal = in.Signal - out.Reason = in.Reason - out.Message = in.Message - out.StartedAt = in.StartedAt - out.FinishedAt = in.FinishedAt - out.ContainerID = in.ContainerID - return nil -} - -// Convert_api_ContainerStateTerminated_To_v1_ContainerStateTerminated is an autogenerated conversion function. -func Convert_api_ContainerStateTerminated_To_v1_ContainerStateTerminated(in *api.ContainerStateTerminated, out *v1.ContainerStateTerminated, s conversion.Scope) error { - return autoConvert_api_ContainerStateTerminated_To_v1_ContainerStateTerminated(in, out, s) -} - -func autoConvert_v1_ContainerStateWaiting_To_api_ContainerStateWaiting(in *v1.ContainerStateWaiting, out *api.ContainerStateWaiting, s conversion.Scope) error { - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -// Convert_v1_ContainerStateWaiting_To_api_ContainerStateWaiting is an autogenerated conversion function. -func Convert_v1_ContainerStateWaiting_To_api_ContainerStateWaiting(in *v1.ContainerStateWaiting, out *api.ContainerStateWaiting, s conversion.Scope) error { - return autoConvert_v1_ContainerStateWaiting_To_api_ContainerStateWaiting(in, out, s) -} - -func autoConvert_api_ContainerStateWaiting_To_v1_ContainerStateWaiting(in *api.ContainerStateWaiting, out *v1.ContainerStateWaiting, s conversion.Scope) error { - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -// Convert_api_ContainerStateWaiting_To_v1_ContainerStateWaiting is an autogenerated conversion function. -func Convert_api_ContainerStateWaiting_To_v1_ContainerStateWaiting(in *api.ContainerStateWaiting, out *v1.ContainerStateWaiting, s conversion.Scope) error { - return autoConvert_api_ContainerStateWaiting_To_v1_ContainerStateWaiting(in, out, s) -} - -func autoConvert_v1_ContainerStatus_To_api_ContainerStatus(in *v1.ContainerStatus, out *api.ContainerStatus, s conversion.Scope) error { - out.Name = in.Name - if err := Convert_v1_ContainerState_To_api_ContainerState(&in.State, &out.State, s); err != nil { - return err - } - if err := Convert_v1_ContainerState_To_api_ContainerState(&in.LastTerminationState, &out.LastTerminationState, s); err != nil { - return err - } - out.Ready = in.Ready - out.RestartCount = in.RestartCount - out.Image = in.Image - out.ImageID = in.ImageID - out.ContainerID = in.ContainerID - return nil -} - -// Convert_v1_ContainerStatus_To_api_ContainerStatus is an autogenerated conversion function. -func Convert_v1_ContainerStatus_To_api_ContainerStatus(in *v1.ContainerStatus, out *api.ContainerStatus, s conversion.Scope) error { - return autoConvert_v1_ContainerStatus_To_api_ContainerStatus(in, out, s) -} - -func autoConvert_api_ContainerStatus_To_v1_ContainerStatus(in *api.ContainerStatus, out *v1.ContainerStatus, s conversion.Scope) error { - out.Name = in.Name - if err := Convert_api_ContainerState_To_v1_ContainerState(&in.State, &out.State, s); err != nil { - return err - } - if err := Convert_api_ContainerState_To_v1_ContainerState(&in.LastTerminationState, &out.LastTerminationState, s); err != nil { - return err - } - out.Ready = in.Ready - out.RestartCount = in.RestartCount - out.Image = in.Image - out.ImageID = in.ImageID - out.ContainerID = in.ContainerID - return nil -} - -// Convert_api_ContainerStatus_To_v1_ContainerStatus is an autogenerated conversion function. -func Convert_api_ContainerStatus_To_v1_ContainerStatus(in *api.ContainerStatus, out *v1.ContainerStatus, s conversion.Scope) error { - return autoConvert_api_ContainerStatus_To_v1_ContainerStatus(in, out, s) -} - -func autoConvert_v1_DaemonEndpoint_To_api_DaemonEndpoint(in *v1.DaemonEndpoint, out *api.DaemonEndpoint, s conversion.Scope) error { - out.Port = in.Port - return nil -} - -// Convert_v1_DaemonEndpoint_To_api_DaemonEndpoint is an autogenerated conversion function. -func Convert_v1_DaemonEndpoint_To_api_DaemonEndpoint(in *v1.DaemonEndpoint, out *api.DaemonEndpoint, s conversion.Scope) error { - return autoConvert_v1_DaemonEndpoint_To_api_DaemonEndpoint(in, out, s) -} - -func autoConvert_api_DaemonEndpoint_To_v1_DaemonEndpoint(in *api.DaemonEndpoint, out *v1.DaemonEndpoint, s conversion.Scope) error { - out.Port = in.Port - return nil -} - -// Convert_api_DaemonEndpoint_To_v1_DaemonEndpoint is an autogenerated conversion function. -func Convert_api_DaemonEndpoint_To_v1_DaemonEndpoint(in *api.DaemonEndpoint, out *v1.DaemonEndpoint, s conversion.Scope) error { - return autoConvert_api_DaemonEndpoint_To_v1_DaemonEndpoint(in, out, s) -} - -func autoConvert_v1_DeleteOptions_To_api_DeleteOptions(in *v1.DeleteOptions, out *api.DeleteOptions, s conversion.Scope) error { - out.GracePeriodSeconds = (*int64)(unsafe.Pointer(in.GracePeriodSeconds)) - out.Preconditions = (*api.Preconditions)(unsafe.Pointer(in.Preconditions)) - out.OrphanDependents = (*bool)(unsafe.Pointer(in.OrphanDependents)) - out.PropagationPolicy = (*api.DeletionPropagation)(unsafe.Pointer(in.PropagationPolicy)) - return nil -} - -// Convert_v1_DeleteOptions_To_api_DeleteOptions is an autogenerated conversion function. -func Convert_v1_DeleteOptions_To_api_DeleteOptions(in *v1.DeleteOptions, out *api.DeleteOptions, s conversion.Scope) error { - return autoConvert_v1_DeleteOptions_To_api_DeleteOptions(in, out, s) -} - -func autoConvert_api_DeleteOptions_To_v1_DeleteOptions(in *api.DeleteOptions, out *v1.DeleteOptions, s conversion.Scope) error { - out.GracePeriodSeconds = (*int64)(unsafe.Pointer(in.GracePeriodSeconds)) - out.Preconditions = (*v1.Preconditions)(unsafe.Pointer(in.Preconditions)) - out.OrphanDependents = (*bool)(unsafe.Pointer(in.OrphanDependents)) - out.PropagationPolicy = (*v1.DeletionPropagation)(unsafe.Pointer(in.PropagationPolicy)) - return nil -} - -// Convert_api_DeleteOptions_To_v1_DeleteOptions is an autogenerated conversion function. -func Convert_api_DeleteOptions_To_v1_DeleteOptions(in *api.DeleteOptions, out *v1.DeleteOptions, s conversion.Scope) error { - return autoConvert_api_DeleteOptions_To_v1_DeleteOptions(in, out, s) -} - -func autoConvert_v1_DownwardAPIProjection_To_api_DownwardAPIProjection(in *v1.DownwardAPIProjection, out *api.DownwardAPIProjection, s conversion.Scope) error { - out.Items = *(*[]api.DownwardAPIVolumeFile)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_v1_DownwardAPIProjection_To_api_DownwardAPIProjection is an autogenerated conversion function. -func Convert_v1_DownwardAPIProjection_To_api_DownwardAPIProjection(in *v1.DownwardAPIProjection, out *api.DownwardAPIProjection, s conversion.Scope) error { - return autoConvert_v1_DownwardAPIProjection_To_api_DownwardAPIProjection(in, out, s) -} - -func autoConvert_api_DownwardAPIProjection_To_v1_DownwardAPIProjection(in *api.DownwardAPIProjection, out *v1.DownwardAPIProjection, s conversion.Scope) error { - out.Items = *(*[]v1.DownwardAPIVolumeFile)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_api_DownwardAPIProjection_To_v1_DownwardAPIProjection is an autogenerated conversion function. -func Convert_api_DownwardAPIProjection_To_v1_DownwardAPIProjection(in *api.DownwardAPIProjection, out *v1.DownwardAPIProjection, s conversion.Scope) error { - return autoConvert_api_DownwardAPIProjection_To_v1_DownwardAPIProjection(in, out, s) -} - -func autoConvert_v1_DownwardAPIVolumeFile_To_api_DownwardAPIVolumeFile(in *v1.DownwardAPIVolumeFile, out *api.DownwardAPIVolumeFile, s conversion.Scope) error { - out.Path = in.Path - out.FieldRef = (*api.ObjectFieldSelector)(unsafe.Pointer(in.FieldRef)) - out.ResourceFieldRef = (*api.ResourceFieldSelector)(unsafe.Pointer(in.ResourceFieldRef)) - out.Mode = (*int32)(unsafe.Pointer(in.Mode)) - return nil -} - -// Convert_v1_DownwardAPIVolumeFile_To_api_DownwardAPIVolumeFile is an autogenerated conversion function. -func Convert_v1_DownwardAPIVolumeFile_To_api_DownwardAPIVolumeFile(in *v1.DownwardAPIVolumeFile, out *api.DownwardAPIVolumeFile, s conversion.Scope) error { - return autoConvert_v1_DownwardAPIVolumeFile_To_api_DownwardAPIVolumeFile(in, out, s) -} - -func autoConvert_api_DownwardAPIVolumeFile_To_v1_DownwardAPIVolumeFile(in *api.DownwardAPIVolumeFile, out *v1.DownwardAPIVolumeFile, s conversion.Scope) error { - out.Path = in.Path - out.FieldRef = (*v1.ObjectFieldSelector)(unsafe.Pointer(in.FieldRef)) - out.ResourceFieldRef = (*v1.ResourceFieldSelector)(unsafe.Pointer(in.ResourceFieldRef)) - out.Mode = (*int32)(unsafe.Pointer(in.Mode)) - return nil -} - -// Convert_api_DownwardAPIVolumeFile_To_v1_DownwardAPIVolumeFile is an autogenerated conversion function. -func Convert_api_DownwardAPIVolumeFile_To_v1_DownwardAPIVolumeFile(in *api.DownwardAPIVolumeFile, out *v1.DownwardAPIVolumeFile, s conversion.Scope) error { - return autoConvert_api_DownwardAPIVolumeFile_To_v1_DownwardAPIVolumeFile(in, out, s) -} - -func autoConvert_v1_DownwardAPIVolumeSource_To_api_DownwardAPIVolumeSource(in *v1.DownwardAPIVolumeSource, out *api.DownwardAPIVolumeSource, s conversion.Scope) error { - out.Items = *(*[]api.DownwardAPIVolumeFile)(unsafe.Pointer(&in.Items)) - out.DefaultMode = (*int32)(unsafe.Pointer(in.DefaultMode)) - return nil -} - -// Convert_v1_DownwardAPIVolumeSource_To_api_DownwardAPIVolumeSource is an autogenerated conversion function. -func Convert_v1_DownwardAPIVolumeSource_To_api_DownwardAPIVolumeSource(in *v1.DownwardAPIVolumeSource, out *api.DownwardAPIVolumeSource, s conversion.Scope) error { - return autoConvert_v1_DownwardAPIVolumeSource_To_api_DownwardAPIVolumeSource(in, out, s) -} - -func autoConvert_api_DownwardAPIVolumeSource_To_v1_DownwardAPIVolumeSource(in *api.DownwardAPIVolumeSource, out *v1.DownwardAPIVolumeSource, s conversion.Scope) error { - out.Items = *(*[]v1.DownwardAPIVolumeFile)(unsafe.Pointer(&in.Items)) - out.DefaultMode = (*int32)(unsafe.Pointer(in.DefaultMode)) - return nil -} - -// Convert_api_DownwardAPIVolumeSource_To_v1_DownwardAPIVolumeSource is an autogenerated conversion function. -func Convert_api_DownwardAPIVolumeSource_To_v1_DownwardAPIVolumeSource(in *api.DownwardAPIVolumeSource, out *v1.DownwardAPIVolumeSource, s conversion.Scope) error { - return autoConvert_api_DownwardAPIVolumeSource_To_v1_DownwardAPIVolumeSource(in, out, s) -} - -func autoConvert_v1_EmptyDirVolumeSource_To_api_EmptyDirVolumeSource(in *v1.EmptyDirVolumeSource, out *api.EmptyDirVolumeSource, s conversion.Scope) error { - out.Medium = api.StorageMedium(in.Medium) - out.SizeLimit = (*resource.Quantity)(unsafe.Pointer(in.SizeLimit)) - return nil -} - -// Convert_v1_EmptyDirVolumeSource_To_api_EmptyDirVolumeSource is an autogenerated conversion function. -func Convert_v1_EmptyDirVolumeSource_To_api_EmptyDirVolumeSource(in *v1.EmptyDirVolumeSource, out *api.EmptyDirVolumeSource, s conversion.Scope) error { - return autoConvert_v1_EmptyDirVolumeSource_To_api_EmptyDirVolumeSource(in, out, s) -} - -func autoConvert_api_EmptyDirVolumeSource_To_v1_EmptyDirVolumeSource(in *api.EmptyDirVolumeSource, out *v1.EmptyDirVolumeSource, s conversion.Scope) error { - out.Medium = v1.StorageMedium(in.Medium) - out.SizeLimit = (*resource.Quantity)(unsafe.Pointer(in.SizeLimit)) - return nil -} - -// Convert_api_EmptyDirVolumeSource_To_v1_EmptyDirVolumeSource is an autogenerated conversion function. -func Convert_api_EmptyDirVolumeSource_To_v1_EmptyDirVolumeSource(in *api.EmptyDirVolumeSource, out *v1.EmptyDirVolumeSource, s conversion.Scope) error { - return autoConvert_api_EmptyDirVolumeSource_To_v1_EmptyDirVolumeSource(in, out, s) -} - -func autoConvert_v1_EndpointAddress_To_api_EndpointAddress(in *v1.EndpointAddress, out *api.EndpointAddress, s conversion.Scope) error { - out.IP = in.IP - out.Hostname = in.Hostname - out.NodeName = (*string)(unsafe.Pointer(in.NodeName)) - out.TargetRef = (*api.ObjectReference)(unsafe.Pointer(in.TargetRef)) - return nil -} - -// Convert_v1_EndpointAddress_To_api_EndpointAddress is an autogenerated conversion function. -func Convert_v1_EndpointAddress_To_api_EndpointAddress(in *v1.EndpointAddress, out *api.EndpointAddress, s conversion.Scope) error { - return autoConvert_v1_EndpointAddress_To_api_EndpointAddress(in, out, s) -} - -func autoConvert_api_EndpointAddress_To_v1_EndpointAddress(in *api.EndpointAddress, out *v1.EndpointAddress, s conversion.Scope) error { - out.IP = in.IP - out.Hostname = in.Hostname - out.NodeName = (*string)(unsafe.Pointer(in.NodeName)) - out.TargetRef = (*v1.ObjectReference)(unsafe.Pointer(in.TargetRef)) - return nil -} - -// Convert_api_EndpointAddress_To_v1_EndpointAddress is an autogenerated conversion function. -func Convert_api_EndpointAddress_To_v1_EndpointAddress(in *api.EndpointAddress, out *v1.EndpointAddress, s conversion.Scope) error { - return autoConvert_api_EndpointAddress_To_v1_EndpointAddress(in, out, s) -} - -func autoConvert_v1_EndpointPort_To_api_EndpointPort(in *v1.EndpointPort, out *api.EndpointPort, s conversion.Scope) error { - out.Name = in.Name - out.Port = in.Port - out.Protocol = api.Protocol(in.Protocol) - return nil -} - -// Convert_v1_EndpointPort_To_api_EndpointPort is an autogenerated conversion function. -func Convert_v1_EndpointPort_To_api_EndpointPort(in *v1.EndpointPort, out *api.EndpointPort, s conversion.Scope) error { - return autoConvert_v1_EndpointPort_To_api_EndpointPort(in, out, s) -} - -func autoConvert_api_EndpointPort_To_v1_EndpointPort(in *api.EndpointPort, out *v1.EndpointPort, s conversion.Scope) error { - out.Name = in.Name - out.Port = in.Port - out.Protocol = v1.Protocol(in.Protocol) - return nil -} - -// Convert_api_EndpointPort_To_v1_EndpointPort is an autogenerated conversion function. -func Convert_api_EndpointPort_To_v1_EndpointPort(in *api.EndpointPort, out *v1.EndpointPort, s conversion.Scope) error { - return autoConvert_api_EndpointPort_To_v1_EndpointPort(in, out, s) -} - -func autoConvert_v1_EndpointSubset_To_api_EndpointSubset(in *v1.EndpointSubset, out *api.EndpointSubset, s conversion.Scope) error { - out.Addresses = *(*[]api.EndpointAddress)(unsafe.Pointer(&in.Addresses)) - out.NotReadyAddresses = *(*[]api.EndpointAddress)(unsafe.Pointer(&in.NotReadyAddresses)) - out.Ports = *(*[]api.EndpointPort)(unsafe.Pointer(&in.Ports)) - return nil -} - -// Convert_v1_EndpointSubset_To_api_EndpointSubset is an autogenerated conversion function. -func Convert_v1_EndpointSubset_To_api_EndpointSubset(in *v1.EndpointSubset, out *api.EndpointSubset, s conversion.Scope) error { - return autoConvert_v1_EndpointSubset_To_api_EndpointSubset(in, out, s) -} - -func autoConvert_api_EndpointSubset_To_v1_EndpointSubset(in *api.EndpointSubset, out *v1.EndpointSubset, s conversion.Scope) error { - out.Addresses = *(*[]v1.EndpointAddress)(unsafe.Pointer(&in.Addresses)) - out.NotReadyAddresses = *(*[]v1.EndpointAddress)(unsafe.Pointer(&in.NotReadyAddresses)) - out.Ports = *(*[]v1.EndpointPort)(unsafe.Pointer(&in.Ports)) - return nil -} - -// Convert_api_EndpointSubset_To_v1_EndpointSubset is an autogenerated conversion function. -func Convert_api_EndpointSubset_To_v1_EndpointSubset(in *api.EndpointSubset, out *v1.EndpointSubset, s conversion.Scope) error { - return autoConvert_api_EndpointSubset_To_v1_EndpointSubset(in, out, s) -} - -func autoConvert_v1_Endpoints_To_api_Endpoints(in *v1.Endpoints, out *api.Endpoints, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Subsets = *(*[]api.EndpointSubset)(unsafe.Pointer(&in.Subsets)) - return nil -} - -// Convert_v1_Endpoints_To_api_Endpoints is an autogenerated conversion function. -func Convert_v1_Endpoints_To_api_Endpoints(in *v1.Endpoints, out *api.Endpoints, s conversion.Scope) error { - return autoConvert_v1_Endpoints_To_api_Endpoints(in, out, s) -} - -func autoConvert_api_Endpoints_To_v1_Endpoints(in *api.Endpoints, out *v1.Endpoints, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Subsets = *(*[]v1.EndpointSubset)(unsafe.Pointer(&in.Subsets)) - return nil -} - -// Convert_api_Endpoints_To_v1_Endpoints is an autogenerated conversion function. -func Convert_api_Endpoints_To_v1_Endpoints(in *api.Endpoints, out *v1.Endpoints, s conversion.Scope) error { - return autoConvert_api_Endpoints_To_v1_Endpoints(in, out, s) -} - -func autoConvert_v1_EndpointsList_To_api_EndpointsList(in *v1.EndpointsList, out *api.EndpointsList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]api.Endpoints)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_v1_EndpointsList_To_api_EndpointsList is an autogenerated conversion function. -func Convert_v1_EndpointsList_To_api_EndpointsList(in *v1.EndpointsList, out *api.EndpointsList, s conversion.Scope) error { - return autoConvert_v1_EndpointsList_To_api_EndpointsList(in, out, s) -} - -func autoConvert_api_EndpointsList_To_v1_EndpointsList(in *api.EndpointsList, out *v1.EndpointsList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]v1.Endpoints)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_api_EndpointsList_To_v1_EndpointsList is an autogenerated conversion function. -func Convert_api_EndpointsList_To_v1_EndpointsList(in *api.EndpointsList, out *v1.EndpointsList, s conversion.Scope) error { - return autoConvert_api_EndpointsList_To_v1_EndpointsList(in, out, s) -} - -func autoConvert_v1_EnvFromSource_To_api_EnvFromSource(in *v1.EnvFromSource, out *api.EnvFromSource, s conversion.Scope) error { - out.Prefix = in.Prefix - out.ConfigMapRef = (*api.ConfigMapEnvSource)(unsafe.Pointer(in.ConfigMapRef)) - out.SecretRef = (*api.SecretEnvSource)(unsafe.Pointer(in.SecretRef)) - return nil -} - -// Convert_v1_EnvFromSource_To_api_EnvFromSource is an autogenerated conversion function. -func Convert_v1_EnvFromSource_To_api_EnvFromSource(in *v1.EnvFromSource, out *api.EnvFromSource, s conversion.Scope) error { - return autoConvert_v1_EnvFromSource_To_api_EnvFromSource(in, out, s) -} - -func autoConvert_api_EnvFromSource_To_v1_EnvFromSource(in *api.EnvFromSource, out *v1.EnvFromSource, s conversion.Scope) error { - out.Prefix = in.Prefix - out.ConfigMapRef = (*v1.ConfigMapEnvSource)(unsafe.Pointer(in.ConfigMapRef)) - out.SecretRef = (*v1.SecretEnvSource)(unsafe.Pointer(in.SecretRef)) - return nil -} - -// Convert_api_EnvFromSource_To_v1_EnvFromSource is an autogenerated conversion function. -func Convert_api_EnvFromSource_To_v1_EnvFromSource(in *api.EnvFromSource, out *v1.EnvFromSource, s conversion.Scope) error { - return autoConvert_api_EnvFromSource_To_v1_EnvFromSource(in, out, s) -} - -func autoConvert_v1_EnvVar_To_api_EnvVar(in *v1.EnvVar, out *api.EnvVar, s conversion.Scope) error { - out.Name = in.Name - out.Value = in.Value - out.ValueFrom = (*api.EnvVarSource)(unsafe.Pointer(in.ValueFrom)) - return nil -} - -// Convert_v1_EnvVar_To_api_EnvVar is an autogenerated conversion function. -func Convert_v1_EnvVar_To_api_EnvVar(in *v1.EnvVar, out *api.EnvVar, s conversion.Scope) error { - return autoConvert_v1_EnvVar_To_api_EnvVar(in, out, s) -} - -func autoConvert_api_EnvVar_To_v1_EnvVar(in *api.EnvVar, out *v1.EnvVar, s conversion.Scope) error { - out.Name = in.Name - out.Value = in.Value - out.ValueFrom = (*v1.EnvVarSource)(unsafe.Pointer(in.ValueFrom)) - return nil -} - -// Convert_api_EnvVar_To_v1_EnvVar is an autogenerated conversion function. -func Convert_api_EnvVar_To_v1_EnvVar(in *api.EnvVar, out *v1.EnvVar, s conversion.Scope) error { - return autoConvert_api_EnvVar_To_v1_EnvVar(in, out, s) -} - -func autoConvert_v1_EnvVarSource_To_api_EnvVarSource(in *v1.EnvVarSource, out *api.EnvVarSource, s conversion.Scope) error { - out.FieldRef = (*api.ObjectFieldSelector)(unsafe.Pointer(in.FieldRef)) - out.ResourceFieldRef = (*api.ResourceFieldSelector)(unsafe.Pointer(in.ResourceFieldRef)) - out.ConfigMapKeyRef = (*api.ConfigMapKeySelector)(unsafe.Pointer(in.ConfigMapKeyRef)) - out.SecretKeyRef = (*api.SecretKeySelector)(unsafe.Pointer(in.SecretKeyRef)) - return nil -} - -// Convert_v1_EnvVarSource_To_api_EnvVarSource is an autogenerated conversion function. -func Convert_v1_EnvVarSource_To_api_EnvVarSource(in *v1.EnvVarSource, out *api.EnvVarSource, s conversion.Scope) error { - return autoConvert_v1_EnvVarSource_To_api_EnvVarSource(in, out, s) -} - -func autoConvert_api_EnvVarSource_To_v1_EnvVarSource(in *api.EnvVarSource, out *v1.EnvVarSource, s conversion.Scope) error { - out.FieldRef = (*v1.ObjectFieldSelector)(unsafe.Pointer(in.FieldRef)) - out.ResourceFieldRef = (*v1.ResourceFieldSelector)(unsafe.Pointer(in.ResourceFieldRef)) - out.ConfigMapKeyRef = (*v1.ConfigMapKeySelector)(unsafe.Pointer(in.ConfigMapKeyRef)) - out.SecretKeyRef = (*v1.SecretKeySelector)(unsafe.Pointer(in.SecretKeyRef)) - return nil -} - -// Convert_api_EnvVarSource_To_v1_EnvVarSource is an autogenerated conversion function. -func Convert_api_EnvVarSource_To_v1_EnvVarSource(in *api.EnvVarSource, out *v1.EnvVarSource, s conversion.Scope) error { - return autoConvert_api_EnvVarSource_To_v1_EnvVarSource(in, out, s) -} - -func autoConvert_v1_Event_To_api_Event(in *v1.Event, out *api.Event, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1_ObjectReference_To_api_ObjectReference(&in.InvolvedObject, &out.InvolvedObject, s); err != nil { - return err - } - out.Reason = in.Reason - out.Message = in.Message - if err := Convert_v1_EventSource_To_api_EventSource(&in.Source, &out.Source, s); err != nil { - return err - } - out.FirstTimestamp = in.FirstTimestamp - out.LastTimestamp = in.LastTimestamp - out.Count = in.Count - out.Type = in.Type - return nil -} - -// Convert_v1_Event_To_api_Event is an autogenerated conversion function. -func Convert_v1_Event_To_api_Event(in *v1.Event, out *api.Event, s conversion.Scope) error { - return autoConvert_v1_Event_To_api_Event(in, out, s) -} - -func autoConvert_api_Event_To_v1_Event(in *api.Event, out *v1.Event, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_api_ObjectReference_To_v1_ObjectReference(&in.InvolvedObject, &out.InvolvedObject, s); err != nil { - return err - } - out.Reason = in.Reason - out.Message = in.Message - if err := Convert_api_EventSource_To_v1_EventSource(&in.Source, &out.Source, s); err != nil { - return err - } - out.FirstTimestamp = in.FirstTimestamp - out.LastTimestamp = in.LastTimestamp - out.Count = in.Count - out.Type = in.Type - return nil -} - -// Convert_api_Event_To_v1_Event is an autogenerated conversion function. -func Convert_api_Event_To_v1_Event(in *api.Event, out *v1.Event, s conversion.Scope) error { - return autoConvert_api_Event_To_v1_Event(in, out, s) -} - -func autoConvert_v1_EventList_To_api_EventList(in *v1.EventList, out *api.EventList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]api.Event)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_v1_EventList_To_api_EventList is an autogenerated conversion function. -func Convert_v1_EventList_To_api_EventList(in *v1.EventList, out *api.EventList, s conversion.Scope) error { - return autoConvert_v1_EventList_To_api_EventList(in, out, s) -} - -func autoConvert_api_EventList_To_v1_EventList(in *api.EventList, out *v1.EventList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]v1.Event)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_api_EventList_To_v1_EventList is an autogenerated conversion function. -func Convert_api_EventList_To_v1_EventList(in *api.EventList, out *v1.EventList, s conversion.Scope) error { - return autoConvert_api_EventList_To_v1_EventList(in, out, s) -} - -func autoConvert_v1_EventSource_To_api_EventSource(in *v1.EventSource, out *api.EventSource, s conversion.Scope) error { - out.Component = in.Component - out.Host = in.Host - return nil -} - -// Convert_v1_EventSource_To_api_EventSource is an autogenerated conversion function. -func Convert_v1_EventSource_To_api_EventSource(in *v1.EventSource, out *api.EventSource, s conversion.Scope) error { - return autoConvert_v1_EventSource_To_api_EventSource(in, out, s) -} - -func autoConvert_api_EventSource_To_v1_EventSource(in *api.EventSource, out *v1.EventSource, s conversion.Scope) error { - out.Component = in.Component - out.Host = in.Host - return nil -} - -// Convert_api_EventSource_To_v1_EventSource is an autogenerated conversion function. -func Convert_api_EventSource_To_v1_EventSource(in *api.EventSource, out *v1.EventSource, s conversion.Scope) error { - return autoConvert_api_EventSource_To_v1_EventSource(in, out, s) -} - -func autoConvert_v1_ExecAction_To_api_ExecAction(in *v1.ExecAction, out *api.ExecAction, s conversion.Scope) error { - out.Command = *(*[]string)(unsafe.Pointer(&in.Command)) - return nil -} - -// Convert_v1_ExecAction_To_api_ExecAction is an autogenerated conversion function. -func Convert_v1_ExecAction_To_api_ExecAction(in *v1.ExecAction, out *api.ExecAction, s conversion.Scope) error { - return autoConvert_v1_ExecAction_To_api_ExecAction(in, out, s) -} - -func autoConvert_api_ExecAction_To_v1_ExecAction(in *api.ExecAction, out *v1.ExecAction, s conversion.Scope) error { - out.Command = *(*[]string)(unsafe.Pointer(&in.Command)) - return nil -} - -// Convert_api_ExecAction_To_v1_ExecAction is an autogenerated conversion function. -func Convert_api_ExecAction_To_v1_ExecAction(in *api.ExecAction, out *v1.ExecAction, s conversion.Scope) error { - return autoConvert_api_ExecAction_To_v1_ExecAction(in, out, s) -} - -func autoConvert_v1_FCVolumeSource_To_api_FCVolumeSource(in *v1.FCVolumeSource, out *api.FCVolumeSource, s conversion.Scope) error { - out.TargetWWNs = *(*[]string)(unsafe.Pointer(&in.TargetWWNs)) - out.Lun = (*int32)(unsafe.Pointer(in.Lun)) - out.FSType = in.FSType - out.ReadOnly = in.ReadOnly - out.WWIDs = *(*[]string)(unsafe.Pointer(&in.WWIDs)) - return nil -} - -// Convert_v1_FCVolumeSource_To_api_FCVolumeSource is an autogenerated conversion function. -func Convert_v1_FCVolumeSource_To_api_FCVolumeSource(in *v1.FCVolumeSource, out *api.FCVolumeSource, s conversion.Scope) error { - return autoConvert_v1_FCVolumeSource_To_api_FCVolumeSource(in, out, s) -} - -func autoConvert_api_FCVolumeSource_To_v1_FCVolumeSource(in *api.FCVolumeSource, out *v1.FCVolumeSource, s conversion.Scope) error { - out.TargetWWNs = *(*[]string)(unsafe.Pointer(&in.TargetWWNs)) - out.Lun = (*int32)(unsafe.Pointer(in.Lun)) - out.FSType = in.FSType - out.ReadOnly = in.ReadOnly - out.WWIDs = *(*[]string)(unsafe.Pointer(&in.WWIDs)) - return nil -} - -// Convert_api_FCVolumeSource_To_v1_FCVolumeSource is an autogenerated conversion function. -func Convert_api_FCVolumeSource_To_v1_FCVolumeSource(in *api.FCVolumeSource, out *v1.FCVolumeSource, s conversion.Scope) error { - return autoConvert_api_FCVolumeSource_To_v1_FCVolumeSource(in, out, s) -} - -func autoConvert_v1_FlexVolumeSource_To_api_FlexVolumeSource(in *v1.FlexVolumeSource, out *api.FlexVolumeSource, s conversion.Scope) error { - out.Driver = in.Driver - out.FSType = in.FSType - out.SecretRef = (*api.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) - out.ReadOnly = in.ReadOnly - out.Options = *(*map[string]string)(unsafe.Pointer(&in.Options)) - return nil -} - -// Convert_v1_FlexVolumeSource_To_api_FlexVolumeSource is an autogenerated conversion function. -func Convert_v1_FlexVolumeSource_To_api_FlexVolumeSource(in *v1.FlexVolumeSource, out *api.FlexVolumeSource, s conversion.Scope) error { - return autoConvert_v1_FlexVolumeSource_To_api_FlexVolumeSource(in, out, s) -} - -func autoConvert_api_FlexVolumeSource_To_v1_FlexVolumeSource(in *api.FlexVolumeSource, out *v1.FlexVolumeSource, s conversion.Scope) error { - out.Driver = in.Driver - out.FSType = in.FSType - out.SecretRef = (*v1.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) - out.ReadOnly = in.ReadOnly - out.Options = *(*map[string]string)(unsafe.Pointer(&in.Options)) - return nil -} - -// Convert_api_FlexVolumeSource_To_v1_FlexVolumeSource is an autogenerated conversion function. -func Convert_api_FlexVolumeSource_To_v1_FlexVolumeSource(in *api.FlexVolumeSource, out *v1.FlexVolumeSource, s conversion.Scope) error { - return autoConvert_api_FlexVolumeSource_To_v1_FlexVolumeSource(in, out, s) -} - -func autoConvert_v1_FlockerVolumeSource_To_api_FlockerVolumeSource(in *v1.FlockerVolumeSource, out *api.FlockerVolumeSource, s conversion.Scope) error { - out.DatasetName = in.DatasetName - out.DatasetUUID = in.DatasetUUID - return nil -} - -// Convert_v1_FlockerVolumeSource_To_api_FlockerVolumeSource is an autogenerated conversion function. -func Convert_v1_FlockerVolumeSource_To_api_FlockerVolumeSource(in *v1.FlockerVolumeSource, out *api.FlockerVolumeSource, s conversion.Scope) error { - return autoConvert_v1_FlockerVolumeSource_To_api_FlockerVolumeSource(in, out, s) -} - -func autoConvert_api_FlockerVolumeSource_To_v1_FlockerVolumeSource(in *api.FlockerVolumeSource, out *v1.FlockerVolumeSource, s conversion.Scope) error { - out.DatasetName = in.DatasetName - out.DatasetUUID = in.DatasetUUID - return nil -} - -// Convert_api_FlockerVolumeSource_To_v1_FlockerVolumeSource is an autogenerated conversion function. -func Convert_api_FlockerVolumeSource_To_v1_FlockerVolumeSource(in *api.FlockerVolumeSource, out *v1.FlockerVolumeSource, s conversion.Scope) error { - return autoConvert_api_FlockerVolumeSource_To_v1_FlockerVolumeSource(in, out, s) -} - -func autoConvert_v1_GCEPersistentDiskVolumeSource_To_api_GCEPersistentDiskVolumeSource(in *v1.GCEPersistentDiskVolumeSource, out *api.GCEPersistentDiskVolumeSource, s conversion.Scope) error { - out.PDName = in.PDName - out.FSType = in.FSType - out.Partition = in.Partition - out.ReadOnly = in.ReadOnly - return nil -} - -// Convert_v1_GCEPersistentDiskVolumeSource_To_api_GCEPersistentDiskVolumeSource is an autogenerated conversion function. -func Convert_v1_GCEPersistentDiskVolumeSource_To_api_GCEPersistentDiskVolumeSource(in *v1.GCEPersistentDiskVolumeSource, out *api.GCEPersistentDiskVolumeSource, s conversion.Scope) error { - return autoConvert_v1_GCEPersistentDiskVolumeSource_To_api_GCEPersistentDiskVolumeSource(in, out, s) -} - -func autoConvert_api_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource(in *api.GCEPersistentDiskVolumeSource, out *v1.GCEPersistentDiskVolumeSource, s conversion.Scope) error { - out.PDName = in.PDName - out.FSType = in.FSType - out.Partition = in.Partition - out.ReadOnly = in.ReadOnly - return nil -} - -// Convert_api_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource is an autogenerated conversion function. -func Convert_api_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource(in *api.GCEPersistentDiskVolumeSource, out *v1.GCEPersistentDiskVolumeSource, s conversion.Scope) error { - return autoConvert_api_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource(in, out, s) -} - -func autoConvert_v1_GitRepoVolumeSource_To_api_GitRepoVolumeSource(in *v1.GitRepoVolumeSource, out *api.GitRepoVolumeSource, s conversion.Scope) error { - out.Repository = in.Repository - out.Revision = in.Revision - out.Directory = in.Directory - return nil -} - -// Convert_v1_GitRepoVolumeSource_To_api_GitRepoVolumeSource is an autogenerated conversion function. -func Convert_v1_GitRepoVolumeSource_To_api_GitRepoVolumeSource(in *v1.GitRepoVolumeSource, out *api.GitRepoVolumeSource, s conversion.Scope) error { - return autoConvert_v1_GitRepoVolumeSource_To_api_GitRepoVolumeSource(in, out, s) -} - -func autoConvert_api_GitRepoVolumeSource_To_v1_GitRepoVolumeSource(in *api.GitRepoVolumeSource, out *v1.GitRepoVolumeSource, s conversion.Scope) error { - out.Repository = in.Repository - out.Revision = in.Revision - out.Directory = in.Directory - return nil -} - -// Convert_api_GitRepoVolumeSource_To_v1_GitRepoVolumeSource is an autogenerated conversion function. -func Convert_api_GitRepoVolumeSource_To_v1_GitRepoVolumeSource(in *api.GitRepoVolumeSource, out *v1.GitRepoVolumeSource, s conversion.Scope) error { - return autoConvert_api_GitRepoVolumeSource_To_v1_GitRepoVolumeSource(in, out, s) -} - -func autoConvert_v1_GlusterfsVolumeSource_To_api_GlusterfsVolumeSource(in *v1.GlusterfsVolumeSource, out *api.GlusterfsVolumeSource, s conversion.Scope) error { - out.EndpointsName = in.EndpointsName - out.Path = in.Path - out.ReadOnly = in.ReadOnly - return nil -} - -// Convert_v1_GlusterfsVolumeSource_To_api_GlusterfsVolumeSource is an autogenerated conversion function. -func Convert_v1_GlusterfsVolumeSource_To_api_GlusterfsVolumeSource(in *v1.GlusterfsVolumeSource, out *api.GlusterfsVolumeSource, s conversion.Scope) error { - return autoConvert_v1_GlusterfsVolumeSource_To_api_GlusterfsVolumeSource(in, out, s) -} - -func autoConvert_api_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource(in *api.GlusterfsVolumeSource, out *v1.GlusterfsVolumeSource, s conversion.Scope) error { - out.EndpointsName = in.EndpointsName - out.Path = in.Path - out.ReadOnly = in.ReadOnly - return nil -} - -// Convert_api_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource is an autogenerated conversion function. -func Convert_api_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource(in *api.GlusterfsVolumeSource, out *v1.GlusterfsVolumeSource, s conversion.Scope) error { - return autoConvert_api_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource(in, out, s) -} - -func autoConvert_v1_HTTPGetAction_To_api_HTTPGetAction(in *v1.HTTPGetAction, out *api.HTTPGetAction, s conversion.Scope) error { - out.Path = in.Path - out.Port = in.Port - out.Host = in.Host - out.Scheme = api.URIScheme(in.Scheme) - out.HTTPHeaders = *(*[]api.HTTPHeader)(unsafe.Pointer(&in.HTTPHeaders)) - return nil -} - -// Convert_v1_HTTPGetAction_To_api_HTTPGetAction is an autogenerated conversion function. -func Convert_v1_HTTPGetAction_To_api_HTTPGetAction(in *v1.HTTPGetAction, out *api.HTTPGetAction, s conversion.Scope) error { - return autoConvert_v1_HTTPGetAction_To_api_HTTPGetAction(in, out, s) -} - -func autoConvert_api_HTTPGetAction_To_v1_HTTPGetAction(in *api.HTTPGetAction, out *v1.HTTPGetAction, s conversion.Scope) error { - out.Path = in.Path - out.Port = in.Port - out.Host = in.Host - out.Scheme = v1.URIScheme(in.Scheme) - out.HTTPHeaders = *(*[]v1.HTTPHeader)(unsafe.Pointer(&in.HTTPHeaders)) - return nil -} - -// Convert_api_HTTPGetAction_To_v1_HTTPGetAction is an autogenerated conversion function. -func Convert_api_HTTPGetAction_To_v1_HTTPGetAction(in *api.HTTPGetAction, out *v1.HTTPGetAction, s conversion.Scope) error { - return autoConvert_api_HTTPGetAction_To_v1_HTTPGetAction(in, out, s) -} - -func autoConvert_v1_HTTPHeader_To_api_HTTPHeader(in *v1.HTTPHeader, out *api.HTTPHeader, s conversion.Scope) error { - out.Name = in.Name - out.Value = in.Value - return nil -} - -// Convert_v1_HTTPHeader_To_api_HTTPHeader is an autogenerated conversion function. -func Convert_v1_HTTPHeader_To_api_HTTPHeader(in *v1.HTTPHeader, out *api.HTTPHeader, s conversion.Scope) error { - return autoConvert_v1_HTTPHeader_To_api_HTTPHeader(in, out, s) -} - -func autoConvert_api_HTTPHeader_To_v1_HTTPHeader(in *api.HTTPHeader, out *v1.HTTPHeader, s conversion.Scope) error { - out.Name = in.Name - out.Value = in.Value - return nil -} - -// Convert_api_HTTPHeader_To_v1_HTTPHeader is an autogenerated conversion function. -func Convert_api_HTTPHeader_To_v1_HTTPHeader(in *api.HTTPHeader, out *v1.HTTPHeader, s conversion.Scope) error { - return autoConvert_api_HTTPHeader_To_v1_HTTPHeader(in, out, s) -} - -func autoConvert_v1_Handler_To_api_Handler(in *v1.Handler, out *api.Handler, s conversion.Scope) error { - out.Exec = (*api.ExecAction)(unsafe.Pointer(in.Exec)) - out.HTTPGet = (*api.HTTPGetAction)(unsafe.Pointer(in.HTTPGet)) - out.TCPSocket = (*api.TCPSocketAction)(unsafe.Pointer(in.TCPSocket)) - return nil -} - -// Convert_v1_Handler_To_api_Handler is an autogenerated conversion function. -func Convert_v1_Handler_To_api_Handler(in *v1.Handler, out *api.Handler, s conversion.Scope) error { - return autoConvert_v1_Handler_To_api_Handler(in, out, s) -} - -func autoConvert_api_Handler_To_v1_Handler(in *api.Handler, out *v1.Handler, s conversion.Scope) error { - out.Exec = (*v1.ExecAction)(unsafe.Pointer(in.Exec)) - out.HTTPGet = (*v1.HTTPGetAction)(unsafe.Pointer(in.HTTPGet)) - out.TCPSocket = (*v1.TCPSocketAction)(unsafe.Pointer(in.TCPSocket)) - return nil -} - -// Convert_api_Handler_To_v1_Handler is an autogenerated conversion function. -func Convert_api_Handler_To_v1_Handler(in *api.Handler, out *v1.Handler, s conversion.Scope) error { - return autoConvert_api_Handler_To_v1_Handler(in, out, s) -} - -func autoConvert_v1_HostAlias_To_api_HostAlias(in *v1.HostAlias, out *api.HostAlias, s conversion.Scope) error { - out.IP = in.IP - out.Hostnames = *(*[]string)(unsafe.Pointer(&in.Hostnames)) - return nil -} - -// Convert_v1_HostAlias_To_api_HostAlias is an autogenerated conversion function. -func Convert_v1_HostAlias_To_api_HostAlias(in *v1.HostAlias, out *api.HostAlias, s conversion.Scope) error { - return autoConvert_v1_HostAlias_To_api_HostAlias(in, out, s) -} - -func autoConvert_api_HostAlias_To_v1_HostAlias(in *api.HostAlias, out *v1.HostAlias, s conversion.Scope) error { - out.IP = in.IP - out.Hostnames = *(*[]string)(unsafe.Pointer(&in.Hostnames)) - return nil -} - -// Convert_api_HostAlias_To_v1_HostAlias is an autogenerated conversion function. -func Convert_api_HostAlias_To_v1_HostAlias(in *api.HostAlias, out *v1.HostAlias, s conversion.Scope) error { - return autoConvert_api_HostAlias_To_v1_HostAlias(in, out, s) -} - -func autoConvert_v1_HostPathVolumeSource_To_api_HostPathVolumeSource(in *v1.HostPathVolumeSource, out *api.HostPathVolumeSource, s conversion.Scope) error { - out.Path = in.Path - out.Type = (*api.HostPathType)(unsafe.Pointer(in.Type)) - return nil -} - -// Convert_v1_HostPathVolumeSource_To_api_HostPathVolumeSource is an autogenerated conversion function. -func Convert_v1_HostPathVolumeSource_To_api_HostPathVolumeSource(in *v1.HostPathVolumeSource, out *api.HostPathVolumeSource, s conversion.Scope) error { - return autoConvert_v1_HostPathVolumeSource_To_api_HostPathVolumeSource(in, out, s) -} - -func autoConvert_api_HostPathVolumeSource_To_v1_HostPathVolumeSource(in *api.HostPathVolumeSource, out *v1.HostPathVolumeSource, s conversion.Scope) error { - out.Path = in.Path - out.Type = (*v1.HostPathType)(unsafe.Pointer(in.Type)) - return nil -} - -// Convert_api_HostPathVolumeSource_To_v1_HostPathVolumeSource is an autogenerated conversion function. -func Convert_api_HostPathVolumeSource_To_v1_HostPathVolumeSource(in *api.HostPathVolumeSource, out *v1.HostPathVolumeSource, s conversion.Scope) error { - return autoConvert_api_HostPathVolumeSource_To_v1_HostPathVolumeSource(in, out, s) -} - -func autoConvert_v1_ISCSIVolumeSource_To_api_ISCSIVolumeSource(in *v1.ISCSIVolumeSource, out *api.ISCSIVolumeSource, s conversion.Scope) error { - out.TargetPortal = in.TargetPortal - out.IQN = in.IQN - out.Lun = in.Lun - out.ISCSIInterface = in.ISCSIInterface - out.FSType = in.FSType - out.ReadOnly = in.ReadOnly - out.Portals = *(*[]string)(unsafe.Pointer(&in.Portals)) - out.DiscoveryCHAPAuth = in.DiscoveryCHAPAuth - out.SessionCHAPAuth = in.SessionCHAPAuth - out.SecretRef = (*api.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) - out.InitiatorName = (*string)(unsafe.Pointer(in.InitiatorName)) - return nil -} - -// Convert_v1_ISCSIVolumeSource_To_api_ISCSIVolumeSource is an autogenerated conversion function. -func Convert_v1_ISCSIVolumeSource_To_api_ISCSIVolumeSource(in *v1.ISCSIVolumeSource, out *api.ISCSIVolumeSource, s conversion.Scope) error { - return autoConvert_v1_ISCSIVolumeSource_To_api_ISCSIVolumeSource(in, out, s) -} - -func autoConvert_api_ISCSIVolumeSource_To_v1_ISCSIVolumeSource(in *api.ISCSIVolumeSource, out *v1.ISCSIVolumeSource, s conversion.Scope) error { - out.TargetPortal = in.TargetPortal - out.IQN = in.IQN - out.Lun = in.Lun - out.ISCSIInterface = in.ISCSIInterface - out.FSType = in.FSType - out.ReadOnly = in.ReadOnly - out.Portals = *(*[]string)(unsafe.Pointer(&in.Portals)) - out.DiscoveryCHAPAuth = in.DiscoveryCHAPAuth - out.SessionCHAPAuth = in.SessionCHAPAuth - out.SecretRef = (*v1.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) - out.InitiatorName = (*string)(unsafe.Pointer(in.InitiatorName)) - return nil -} - -// Convert_api_ISCSIVolumeSource_To_v1_ISCSIVolumeSource is an autogenerated conversion function. -func Convert_api_ISCSIVolumeSource_To_v1_ISCSIVolumeSource(in *api.ISCSIVolumeSource, out *v1.ISCSIVolumeSource, s conversion.Scope) error { - return autoConvert_api_ISCSIVolumeSource_To_v1_ISCSIVolumeSource(in, out, s) -} - -func autoConvert_v1_KeyToPath_To_api_KeyToPath(in *v1.KeyToPath, out *api.KeyToPath, s conversion.Scope) error { - out.Key = in.Key - out.Path = in.Path - out.Mode = (*int32)(unsafe.Pointer(in.Mode)) - return nil -} - -// Convert_v1_KeyToPath_To_api_KeyToPath is an autogenerated conversion function. -func Convert_v1_KeyToPath_To_api_KeyToPath(in *v1.KeyToPath, out *api.KeyToPath, s conversion.Scope) error { - return autoConvert_v1_KeyToPath_To_api_KeyToPath(in, out, s) -} - -func autoConvert_api_KeyToPath_To_v1_KeyToPath(in *api.KeyToPath, out *v1.KeyToPath, s conversion.Scope) error { - out.Key = in.Key - out.Path = in.Path - out.Mode = (*int32)(unsafe.Pointer(in.Mode)) - return nil -} - -// Convert_api_KeyToPath_To_v1_KeyToPath is an autogenerated conversion function. -func Convert_api_KeyToPath_To_v1_KeyToPath(in *api.KeyToPath, out *v1.KeyToPath, s conversion.Scope) error { - return autoConvert_api_KeyToPath_To_v1_KeyToPath(in, out, s) -} - -func autoConvert_v1_Lifecycle_To_api_Lifecycle(in *v1.Lifecycle, out *api.Lifecycle, s conversion.Scope) error { - out.PostStart = (*api.Handler)(unsafe.Pointer(in.PostStart)) - out.PreStop = (*api.Handler)(unsafe.Pointer(in.PreStop)) - return nil -} - -// Convert_v1_Lifecycle_To_api_Lifecycle is an autogenerated conversion function. -func Convert_v1_Lifecycle_To_api_Lifecycle(in *v1.Lifecycle, out *api.Lifecycle, s conversion.Scope) error { - return autoConvert_v1_Lifecycle_To_api_Lifecycle(in, out, s) -} - -func autoConvert_api_Lifecycle_To_v1_Lifecycle(in *api.Lifecycle, out *v1.Lifecycle, s conversion.Scope) error { - out.PostStart = (*v1.Handler)(unsafe.Pointer(in.PostStart)) - out.PreStop = (*v1.Handler)(unsafe.Pointer(in.PreStop)) - return nil -} - -// Convert_api_Lifecycle_To_v1_Lifecycle is an autogenerated conversion function. -func Convert_api_Lifecycle_To_v1_Lifecycle(in *api.Lifecycle, out *v1.Lifecycle, s conversion.Scope) error { - return autoConvert_api_Lifecycle_To_v1_Lifecycle(in, out, s) -} - -func autoConvert_v1_LimitRange_To_api_LimitRange(in *v1.LimitRange, out *api.LimitRange, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1_LimitRangeSpec_To_api_LimitRangeSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - return nil -} - -// Convert_v1_LimitRange_To_api_LimitRange is an autogenerated conversion function. -func Convert_v1_LimitRange_To_api_LimitRange(in *v1.LimitRange, out *api.LimitRange, s conversion.Scope) error { - return autoConvert_v1_LimitRange_To_api_LimitRange(in, out, s) -} - -func autoConvert_api_LimitRange_To_v1_LimitRange(in *api.LimitRange, out *v1.LimitRange, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_api_LimitRangeSpec_To_v1_LimitRangeSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - return nil -} - -// Convert_api_LimitRange_To_v1_LimitRange is an autogenerated conversion function. -func Convert_api_LimitRange_To_v1_LimitRange(in *api.LimitRange, out *v1.LimitRange, s conversion.Scope) error { - return autoConvert_api_LimitRange_To_v1_LimitRange(in, out, s) -} - -func autoConvert_v1_LimitRangeItem_To_api_LimitRangeItem(in *v1.LimitRangeItem, out *api.LimitRangeItem, s conversion.Scope) error { - out.Type = api.LimitType(in.Type) - out.Max = *(*api.ResourceList)(unsafe.Pointer(&in.Max)) - out.Min = *(*api.ResourceList)(unsafe.Pointer(&in.Min)) - out.Default = *(*api.ResourceList)(unsafe.Pointer(&in.Default)) - out.DefaultRequest = *(*api.ResourceList)(unsafe.Pointer(&in.DefaultRequest)) - out.MaxLimitRequestRatio = *(*api.ResourceList)(unsafe.Pointer(&in.MaxLimitRequestRatio)) - return nil -} - -// Convert_v1_LimitRangeItem_To_api_LimitRangeItem is an autogenerated conversion function. -func Convert_v1_LimitRangeItem_To_api_LimitRangeItem(in *v1.LimitRangeItem, out *api.LimitRangeItem, s conversion.Scope) error { - return autoConvert_v1_LimitRangeItem_To_api_LimitRangeItem(in, out, s) -} - -func autoConvert_api_LimitRangeItem_To_v1_LimitRangeItem(in *api.LimitRangeItem, out *v1.LimitRangeItem, s conversion.Scope) error { - out.Type = v1.LimitType(in.Type) - out.Max = *(*v1.ResourceList)(unsafe.Pointer(&in.Max)) - out.Min = *(*v1.ResourceList)(unsafe.Pointer(&in.Min)) - out.Default = *(*v1.ResourceList)(unsafe.Pointer(&in.Default)) - out.DefaultRequest = *(*v1.ResourceList)(unsafe.Pointer(&in.DefaultRequest)) - out.MaxLimitRequestRatio = *(*v1.ResourceList)(unsafe.Pointer(&in.MaxLimitRequestRatio)) - return nil -} - -// Convert_api_LimitRangeItem_To_v1_LimitRangeItem is an autogenerated conversion function. -func Convert_api_LimitRangeItem_To_v1_LimitRangeItem(in *api.LimitRangeItem, out *v1.LimitRangeItem, s conversion.Scope) error { - return autoConvert_api_LimitRangeItem_To_v1_LimitRangeItem(in, out, s) -} - -func autoConvert_v1_LimitRangeList_To_api_LimitRangeList(in *v1.LimitRangeList, out *api.LimitRangeList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]api.LimitRange)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_v1_LimitRangeList_To_api_LimitRangeList is an autogenerated conversion function. -func Convert_v1_LimitRangeList_To_api_LimitRangeList(in *v1.LimitRangeList, out *api.LimitRangeList, s conversion.Scope) error { - return autoConvert_v1_LimitRangeList_To_api_LimitRangeList(in, out, s) -} - -func autoConvert_api_LimitRangeList_To_v1_LimitRangeList(in *api.LimitRangeList, out *v1.LimitRangeList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]v1.LimitRange)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_api_LimitRangeList_To_v1_LimitRangeList is an autogenerated conversion function. -func Convert_api_LimitRangeList_To_v1_LimitRangeList(in *api.LimitRangeList, out *v1.LimitRangeList, s conversion.Scope) error { - return autoConvert_api_LimitRangeList_To_v1_LimitRangeList(in, out, s) -} - -func autoConvert_v1_LimitRangeSpec_To_api_LimitRangeSpec(in *v1.LimitRangeSpec, out *api.LimitRangeSpec, s conversion.Scope) error { - out.Limits = *(*[]api.LimitRangeItem)(unsafe.Pointer(&in.Limits)) - return nil -} - -// Convert_v1_LimitRangeSpec_To_api_LimitRangeSpec is an autogenerated conversion function. -func Convert_v1_LimitRangeSpec_To_api_LimitRangeSpec(in *v1.LimitRangeSpec, out *api.LimitRangeSpec, s conversion.Scope) error { - return autoConvert_v1_LimitRangeSpec_To_api_LimitRangeSpec(in, out, s) -} - -func autoConvert_api_LimitRangeSpec_To_v1_LimitRangeSpec(in *api.LimitRangeSpec, out *v1.LimitRangeSpec, s conversion.Scope) error { - out.Limits = *(*[]v1.LimitRangeItem)(unsafe.Pointer(&in.Limits)) - return nil -} - -// Convert_api_LimitRangeSpec_To_v1_LimitRangeSpec is an autogenerated conversion function. -func Convert_api_LimitRangeSpec_To_v1_LimitRangeSpec(in *api.LimitRangeSpec, out *v1.LimitRangeSpec, s conversion.Scope) error { - return autoConvert_api_LimitRangeSpec_To_v1_LimitRangeSpec(in, out, s) -} - -func autoConvert_v1_List_To_api_List(in *v1.List, out *api.List, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]runtime.Object, len(*in)) - for i := range *in { - if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v1_List_To_api_List is an autogenerated conversion function. -func Convert_v1_List_To_api_List(in *v1.List, out *api.List, s conversion.Scope) error { - return autoConvert_v1_List_To_api_List(in, out, s) -} - -func autoConvert_api_List_To_v1_List(in *api.List, out *v1.List, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]runtime.RawExtension, len(*in)) - for i := range *in { - if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_api_List_To_v1_List is an autogenerated conversion function. -func Convert_api_List_To_v1_List(in *api.List, out *v1.List, s conversion.Scope) error { - return autoConvert_api_List_To_v1_List(in, out, s) -} - -func autoConvert_v1_ListOptions_To_api_ListOptions(in *v1.ListOptions, out *api.ListOptions, s conversion.Scope) error { - if err := meta_v1.Convert_string_To_labels_Selector(&in.LabelSelector, &out.LabelSelector, s); err != nil { - return err - } - if err := meta_v1.Convert_string_To_fields_Selector(&in.FieldSelector, &out.FieldSelector, s); err != nil { - return err - } - out.IncludeUninitialized = in.IncludeUninitialized - out.Watch = in.Watch - out.ResourceVersion = in.ResourceVersion - out.TimeoutSeconds = (*int64)(unsafe.Pointer(in.TimeoutSeconds)) - return nil -} - -// Convert_v1_ListOptions_To_api_ListOptions is an autogenerated conversion function. -func Convert_v1_ListOptions_To_api_ListOptions(in *v1.ListOptions, out *api.ListOptions, s conversion.Scope) error { - return autoConvert_v1_ListOptions_To_api_ListOptions(in, out, s) -} - -func autoConvert_api_ListOptions_To_v1_ListOptions(in *api.ListOptions, out *v1.ListOptions, s conversion.Scope) error { - if err := meta_v1.Convert_labels_Selector_To_string(&in.LabelSelector, &out.LabelSelector, s); err != nil { - return err - } - if err := meta_v1.Convert_fields_Selector_To_string(&in.FieldSelector, &out.FieldSelector, s); err != nil { - return err - } - out.IncludeUninitialized = in.IncludeUninitialized - out.Watch = in.Watch - out.ResourceVersion = in.ResourceVersion - out.TimeoutSeconds = (*int64)(unsafe.Pointer(in.TimeoutSeconds)) - return nil -} - -// Convert_api_ListOptions_To_v1_ListOptions is an autogenerated conversion function. -func Convert_api_ListOptions_To_v1_ListOptions(in *api.ListOptions, out *v1.ListOptions, s conversion.Scope) error { - return autoConvert_api_ListOptions_To_v1_ListOptions(in, out, s) -} - -func autoConvert_v1_LoadBalancerIngress_To_api_LoadBalancerIngress(in *v1.LoadBalancerIngress, out *api.LoadBalancerIngress, s conversion.Scope) error { - out.IP = in.IP - out.Hostname = in.Hostname - return nil -} - -// Convert_v1_LoadBalancerIngress_To_api_LoadBalancerIngress is an autogenerated conversion function. -func Convert_v1_LoadBalancerIngress_To_api_LoadBalancerIngress(in *v1.LoadBalancerIngress, out *api.LoadBalancerIngress, s conversion.Scope) error { - return autoConvert_v1_LoadBalancerIngress_To_api_LoadBalancerIngress(in, out, s) -} - -func autoConvert_api_LoadBalancerIngress_To_v1_LoadBalancerIngress(in *api.LoadBalancerIngress, out *v1.LoadBalancerIngress, s conversion.Scope) error { - out.IP = in.IP - out.Hostname = in.Hostname - return nil -} - -// Convert_api_LoadBalancerIngress_To_v1_LoadBalancerIngress is an autogenerated conversion function. -func Convert_api_LoadBalancerIngress_To_v1_LoadBalancerIngress(in *api.LoadBalancerIngress, out *v1.LoadBalancerIngress, s conversion.Scope) error { - return autoConvert_api_LoadBalancerIngress_To_v1_LoadBalancerIngress(in, out, s) -} - -func autoConvert_v1_LoadBalancerStatus_To_api_LoadBalancerStatus(in *v1.LoadBalancerStatus, out *api.LoadBalancerStatus, s conversion.Scope) error { - out.Ingress = *(*[]api.LoadBalancerIngress)(unsafe.Pointer(&in.Ingress)) - return nil -} - -// Convert_v1_LoadBalancerStatus_To_api_LoadBalancerStatus is an autogenerated conversion function. -func Convert_v1_LoadBalancerStatus_To_api_LoadBalancerStatus(in *v1.LoadBalancerStatus, out *api.LoadBalancerStatus, s conversion.Scope) error { - return autoConvert_v1_LoadBalancerStatus_To_api_LoadBalancerStatus(in, out, s) -} - -func autoConvert_api_LoadBalancerStatus_To_v1_LoadBalancerStatus(in *api.LoadBalancerStatus, out *v1.LoadBalancerStatus, s conversion.Scope) error { - out.Ingress = *(*[]v1.LoadBalancerIngress)(unsafe.Pointer(&in.Ingress)) - return nil -} - -// Convert_api_LoadBalancerStatus_To_v1_LoadBalancerStatus is an autogenerated conversion function. -func Convert_api_LoadBalancerStatus_To_v1_LoadBalancerStatus(in *api.LoadBalancerStatus, out *v1.LoadBalancerStatus, s conversion.Scope) error { - return autoConvert_api_LoadBalancerStatus_To_v1_LoadBalancerStatus(in, out, s) -} - -func autoConvert_v1_LocalObjectReference_To_api_LocalObjectReference(in *v1.LocalObjectReference, out *api.LocalObjectReference, s conversion.Scope) error { - out.Name = in.Name - return nil -} - -// Convert_v1_LocalObjectReference_To_api_LocalObjectReference is an autogenerated conversion function. -func Convert_v1_LocalObjectReference_To_api_LocalObjectReference(in *v1.LocalObjectReference, out *api.LocalObjectReference, s conversion.Scope) error { - return autoConvert_v1_LocalObjectReference_To_api_LocalObjectReference(in, out, s) -} - -func autoConvert_api_LocalObjectReference_To_v1_LocalObjectReference(in *api.LocalObjectReference, out *v1.LocalObjectReference, s conversion.Scope) error { - out.Name = in.Name - return nil -} - -// Convert_api_LocalObjectReference_To_v1_LocalObjectReference is an autogenerated conversion function. -func Convert_api_LocalObjectReference_To_v1_LocalObjectReference(in *api.LocalObjectReference, out *v1.LocalObjectReference, s conversion.Scope) error { - return autoConvert_api_LocalObjectReference_To_v1_LocalObjectReference(in, out, s) -} - -func autoConvert_v1_LocalVolumeSource_To_api_LocalVolumeSource(in *v1.LocalVolumeSource, out *api.LocalVolumeSource, s conversion.Scope) error { - out.Path = in.Path - return nil -} - -// Convert_v1_LocalVolumeSource_To_api_LocalVolumeSource is an autogenerated conversion function. -func Convert_v1_LocalVolumeSource_To_api_LocalVolumeSource(in *v1.LocalVolumeSource, out *api.LocalVolumeSource, s conversion.Scope) error { - return autoConvert_v1_LocalVolumeSource_To_api_LocalVolumeSource(in, out, s) -} - -func autoConvert_api_LocalVolumeSource_To_v1_LocalVolumeSource(in *api.LocalVolumeSource, out *v1.LocalVolumeSource, s conversion.Scope) error { - out.Path = in.Path - return nil -} - -// Convert_api_LocalVolumeSource_To_v1_LocalVolumeSource is an autogenerated conversion function. -func Convert_api_LocalVolumeSource_To_v1_LocalVolumeSource(in *api.LocalVolumeSource, out *v1.LocalVolumeSource, s conversion.Scope) error { - return autoConvert_api_LocalVolumeSource_To_v1_LocalVolumeSource(in, out, s) -} - -func autoConvert_v1_NFSVolumeSource_To_api_NFSVolumeSource(in *v1.NFSVolumeSource, out *api.NFSVolumeSource, s conversion.Scope) error { - out.Server = in.Server - out.Path = in.Path - out.ReadOnly = in.ReadOnly - return nil -} - -// Convert_v1_NFSVolumeSource_To_api_NFSVolumeSource is an autogenerated conversion function. -func Convert_v1_NFSVolumeSource_To_api_NFSVolumeSource(in *v1.NFSVolumeSource, out *api.NFSVolumeSource, s conversion.Scope) error { - return autoConvert_v1_NFSVolumeSource_To_api_NFSVolumeSource(in, out, s) -} - -func autoConvert_api_NFSVolumeSource_To_v1_NFSVolumeSource(in *api.NFSVolumeSource, out *v1.NFSVolumeSource, s conversion.Scope) error { - out.Server = in.Server - out.Path = in.Path - out.ReadOnly = in.ReadOnly - return nil -} - -// Convert_api_NFSVolumeSource_To_v1_NFSVolumeSource is an autogenerated conversion function. -func Convert_api_NFSVolumeSource_To_v1_NFSVolumeSource(in *api.NFSVolumeSource, out *v1.NFSVolumeSource, s conversion.Scope) error { - return autoConvert_api_NFSVolumeSource_To_v1_NFSVolumeSource(in, out, s) -} - -func autoConvert_v1_Namespace_To_api_Namespace(in *v1.Namespace, out *api.Namespace, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1_NamespaceSpec_To_api_NamespaceSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1_NamespaceStatus_To_api_NamespaceStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1_Namespace_To_api_Namespace is an autogenerated conversion function. -func Convert_v1_Namespace_To_api_Namespace(in *v1.Namespace, out *api.Namespace, s conversion.Scope) error { - return autoConvert_v1_Namespace_To_api_Namespace(in, out, s) -} - -func autoConvert_api_Namespace_To_v1_Namespace(in *api.Namespace, out *v1.Namespace, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_api_NamespaceSpec_To_v1_NamespaceSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_api_NamespaceStatus_To_v1_NamespaceStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_api_Namespace_To_v1_Namespace is an autogenerated conversion function. -func Convert_api_Namespace_To_v1_Namespace(in *api.Namespace, out *v1.Namespace, s conversion.Scope) error { - return autoConvert_api_Namespace_To_v1_Namespace(in, out, s) -} - -func autoConvert_v1_NamespaceList_To_api_NamespaceList(in *v1.NamespaceList, out *api.NamespaceList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]api.Namespace)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_v1_NamespaceList_To_api_NamespaceList is an autogenerated conversion function. -func Convert_v1_NamespaceList_To_api_NamespaceList(in *v1.NamespaceList, out *api.NamespaceList, s conversion.Scope) error { - return autoConvert_v1_NamespaceList_To_api_NamespaceList(in, out, s) -} - -func autoConvert_api_NamespaceList_To_v1_NamespaceList(in *api.NamespaceList, out *v1.NamespaceList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]v1.Namespace)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_api_NamespaceList_To_v1_NamespaceList is an autogenerated conversion function. -func Convert_api_NamespaceList_To_v1_NamespaceList(in *api.NamespaceList, out *v1.NamespaceList, s conversion.Scope) error { - return autoConvert_api_NamespaceList_To_v1_NamespaceList(in, out, s) -} - -func autoConvert_v1_NamespaceSpec_To_api_NamespaceSpec(in *v1.NamespaceSpec, out *api.NamespaceSpec, s conversion.Scope) error { - out.Finalizers = *(*[]api.FinalizerName)(unsafe.Pointer(&in.Finalizers)) - return nil -} - -// Convert_v1_NamespaceSpec_To_api_NamespaceSpec is an autogenerated conversion function. -func Convert_v1_NamespaceSpec_To_api_NamespaceSpec(in *v1.NamespaceSpec, out *api.NamespaceSpec, s conversion.Scope) error { - return autoConvert_v1_NamespaceSpec_To_api_NamespaceSpec(in, out, s) -} - -func autoConvert_api_NamespaceSpec_To_v1_NamespaceSpec(in *api.NamespaceSpec, out *v1.NamespaceSpec, s conversion.Scope) error { - out.Finalizers = *(*[]v1.FinalizerName)(unsafe.Pointer(&in.Finalizers)) - return nil -} - -// Convert_api_NamespaceSpec_To_v1_NamespaceSpec is an autogenerated conversion function. -func Convert_api_NamespaceSpec_To_v1_NamespaceSpec(in *api.NamespaceSpec, out *v1.NamespaceSpec, s conversion.Scope) error { - return autoConvert_api_NamespaceSpec_To_v1_NamespaceSpec(in, out, s) -} - -func autoConvert_v1_NamespaceStatus_To_api_NamespaceStatus(in *v1.NamespaceStatus, out *api.NamespaceStatus, s conversion.Scope) error { - out.Phase = api.NamespacePhase(in.Phase) - return nil -} - -// Convert_v1_NamespaceStatus_To_api_NamespaceStatus is an autogenerated conversion function. -func Convert_v1_NamespaceStatus_To_api_NamespaceStatus(in *v1.NamespaceStatus, out *api.NamespaceStatus, s conversion.Scope) error { - return autoConvert_v1_NamespaceStatus_To_api_NamespaceStatus(in, out, s) -} - -func autoConvert_api_NamespaceStatus_To_v1_NamespaceStatus(in *api.NamespaceStatus, out *v1.NamespaceStatus, s conversion.Scope) error { - out.Phase = v1.NamespacePhase(in.Phase) - return nil -} - -// Convert_api_NamespaceStatus_To_v1_NamespaceStatus is an autogenerated conversion function. -func Convert_api_NamespaceStatus_To_v1_NamespaceStatus(in *api.NamespaceStatus, out *v1.NamespaceStatus, s conversion.Scope) error { - return autoConvert_api_NamespaceStatus_To_v1_NamespaceStatus(in, out, s) -} - -func autoConvert_v1_Node_To_api_Node(in *v1.Node, out *api.Node, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1_NodeSpec_To_api_NodeSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1_NodeStatus_To_api_NodeStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1_Node_To_api_Node is an autogenerated conversion function. -func Convert_v1_Node_To_api_Node(in *v1.Node, out *api.Node, s conversion.Scope) error { - return autoConvert_v1_Node_To_api_Node(in, out, s) -} - -func autoConvert_api_Node_To_v1_Node(in *api.Node, out *v1.Node, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_api_NodeSpec_To_v1_NodeSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_api_NodeStatus_To_v1_NodeStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_api_Node_To_v1_Node is an autogenerated conversion function. -func Convert_api_Node_To_v1_Node(in *api.Node, out *v1.Node, s conversion.Scope) error { - return autoConvert_api_Node_To_v1_Node(in, out, s) -} - -func autoConvert_v1_NodeAddress_To_api_NodeAddress(in *v1.NodeAddress, out *api.NodeAddress, s conversion.Scope) error { - out.Type = api.NodeAddressType(in.Type) - out.Address = in.Address - return nil -} - -// Convert_v1_NodeAddress_To_api_NodeAddress is an autogenerated conversion function. -func Convert_v1_NodeAddress_To_api_NodeAddress(in *v1.NodeAddress, out *api.NodeAddress, s conversion.Scope) error { - return autoConvert_v1_NodeAddress_To_api_NodeAddress(in, out, s) -} - -func autoConvert_api_NodeAddress_To_v1_NodeAddress(in *api.NodeAddress, out *v1.NodeAddress, s conversion.Scope) error { - out.Type = v1.NodeAddressType(in.Type) - out.Address = in.Address - return nil -} - -// Convert_api_NodeAddress_To_v1_NodeAddress is an autogenerated conversion function. -func Convert_api_NodeAddress_To_v1_NodeAddress(in *api.NodeAddress, out *v1.NodeAddress, s conversion.Scope) error { - return autoConvert_api_NodeAddress_To_v1_NodeAddress(in, out, s) -} - -func autoConvert_v1_NodeAffinity_To_api_NodeAffinity(in *v1.NodeAffinity, out *api.NodeAffinity, s conversion.Scope) error { - out.RequiredDuringSchedulingIgnoredDuringExecution = (*api.NodeSelector)(unsafe.Pointer(in.RequiredDuringSchedulingIgnoredDuringExecution)) - out.PreferredDuringSchedulingIgnoredDuringExecution = *(*[]api.PreferredSchedulingTerm)(unsafe.Pointer(&in.PreferredDuringSchedulingIgnoredDuringExecution)) - return nil -} - -// Convert_v1_NodeAffinity_To_api_NodeAffinity is an autogenerated conversion function. -func Convert_v1_NodeAffinity_To_api_NodeAffinity(in *v1.NodeAffinity, out *api.NodeAffinity, s conversion.Scope) error { - return autoConvert_v1_NodeAffinity_To_api_NodeAffinity(in, out, s) -} - -func autoConvert_api_NodeAffinity_To_v1_NodeAffinity(in *api.NodeAffinity, out *v1.NodeAffinity, s conversion.Scope) error { - out.RequiredDuringSchedulingIgnoredDuringExecution = (*v1.NodeSelector)(unsafe.Pointer(in.RequiredDuringSchedulingIgnoredDuringExecution)) - out.PreferredDuringSchedulingIgnoredDuringExecution = *(*[]v1.PreferredSchedulingTerm)(unsafe.Pointer(&in.PreferredDuringSchedulingIgnoredDuringExecution)) - return nil -} - -// Convert_api_NodeAffinity_To_v1_NodeAffinity is an autogenerated conversion function. -func Convert_api_NodeAffinity_To_v1_NodeAffinity(in *api.NodeAffinity, out *v1.NodeAffinity, s conversion.Scope) error { - return autoConvert_api_NodeAffinity_To_v1_NodeAffinity(in, out, s) -} - -func autoConvert_v1_NodeCondition_To_api_NodeCondition(in *v1.NodeCondition, out *api.NodeCondition, s conversion.Scope) error { - out.Type = api.NodeConditionType(in.Type) - out.Status = api.ConditionStatus(in.Status) - out.LastHeartbeatTime = in.LastHeartbeatTime - out.LastTransitionTime = in.LastTransitionTime - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -// Convert_v1_NodeCondition_To_api_NodeCondition is an autogenerated conversion function. -func Convert_v1_NodeCondition_To_api_NodeCondition(in *v1.NodeCondition, out *api.NodeCondition, s conversion.Scope) error { - return autoConvert_v1_NodeCondition_To_api_NodeCondition(in, out, s) -} - -func autoConvert_api_NodeCondition_To_v1_NodeCondition(in *api.NodeCondition, out *v1.NodeCondition, s conversion.Scope) error { - out.Type = v1.NodeConditionType(in.Type) - out.Status = v1.ConditionStatus(in.Status) - out.LastHeartbeatTime = in.LastHeartbeatTime - out.LastTransitionTime = in.LastTransitionTime - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -// Convert_api_NodeCondition_To_v1_NodeCondition is an autogenerated conversion function. -func Convert_api_NodeCondition_To_v1_NodeCondition(in *api.NodeCondition, out *v1.NodeCondition, s conversion.Scope) error { - return autoConvert_api_NodeCondition_To_v1_NodeCondition(in, out, s) -} - -func autoConvert_v1_NodeConfigSource_To_api_NodeConfigSource(in *v1.NodeConfigSource, out *api.NodeConfigSource, s conversion.Scope) error { - out.ConfigMapRef = (*api.ObjectReference)(unsafe.Pointer(in.ConfigMapRef)) - return nil -} - -// Convert_v1_NodeConfigSource_To_api_NodeConfigSource is an autogenerated conversion function. -func Convert_v1_NodeConfigSource_To_api_NodeConfigSource(in *v1.NodeConfigSource, out *api.NodeConfigSource, s conversion.Scope) error { - return autoConvert_v1_NodeConfigSource_To_api_NodeConfigSource(in, out, s) -} - -func autoConvert_api_NodeConfigSource_To_v1_NodeConfigSource(in *api.NodeConfigSource, out *v1.NodeConfigSource, s conversion.Scope) error { - out.ConfigMapRef = (*v1.ObjectReference)(unsafe.Pointer(in.ConfigMapRef)) - return nil -} - -// Convert_api_NodeConfigSource_To_v1_NodeConfigSource is an autogenerated conversion function. -func Convert_api_NodeConfigSource_To_v1_NodeConfigSource(in *api.NodeConfigSource, out *v1.NodeConfigSource, s conversion.Scope) error { - return autoConvert_api_NodeConfigSource_To_v1_NodeConfigSource(in, out, s) -} - -func autoConvert_v1_NodeDaemonEndpoints_To_api_NodeDaemonEndpoints(in *v1.NodeDaemonEndpoints, out *api.NodeDaemonEndpoints, s conversion.Scope) error { - if err := Convert_v1_DaemonEndpoint_To_api_DaemonEndpoint(&in.KubeletEndpoint, &out.KubeletEndpoint, s); err != nil { - return err - } - return nil -} - -// Convert_v1_NodeDaemonEndpoints_To_api_NodeDaemonEndpoints is an autogenerated conversion function. -func Convert_v1_NodeDaemonEndpoints_To_api_NodeDaemonEndpoints(in *v1.NodeDaemonEndpoints, out *api.NodeDaemonEndpoints, s conversion.Scope) error { - return autoConvert_v1_NodeDaemonEndpoints_To_api_NodeDaemonEndpoints(in, out, s) -} - -func autoConvert_api_NodeDaemonEndpoints_To_v1_NodeDaemonEndpoints(in *api.NodeDaemonEndpoints, out *v1.NodeDaemonEndpoints, s conversion.Scope) error { - if err := Convert_api_DaemonEndpoint_To_v1_DaemonEndpoint(&in.KubeletEndpoint, &out.KubeletEndpoint, s); err != nil { - return err - } - return nil -} - -// Convert_api_NodeDaemonEndpoints_To_v1_NodeDaemonEndpoints is an autogenerated conversion function. -func Convert_api_NodeDaemonEndpoints_To_v1_NodeDaemonEndpoints(in *api.NodeDaemonEndpoints, out *v1.NodeDaemonEndpoints, s conversion.Scope) error { - return autoConvert_api_NodeDaemonEndpoints_To_v1_NodeDaemonEndpoints(in, out, s) -} - -func autoConvert_v1_NodeList_To_api_NodeList(in *v1.NodeList, out *api.NodeList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]api.Node)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_v1_NodeList_To_api_NodeList is an autogenerated conversion function. -func Convert_v1_NodeList_To_api_NodeList(in *v1.NodeList, out *api.NodeList, s conversion.Scope) error { - return autoConvert_v1_NodeList_To_api_NodeList(in, out, s) -} - -func autoConvert_api_NodeList_To_v1_NodeList(in *api.NodeList, out *v1.NodeList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]v1.Node)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_api_NodeList_To_v1_NodeList is an autogenerated conversion function. -func Convert_api_NodeList_To_v1_NodeList(in *api.NodeList, out *v1.NodeList, s conversion.Scope) error { - return autoConvert_api_NodeList_To_v1_NodeList(in, out, s) -} - -func autoConvert_v1_NodeProxyOptions_To_api_NodeProxyOptions(in *v1.NodeProxyOptions, out *api.NodeProxyOptions, s conversion.Scope) error { - out.Path = in.Path - return nil -} - -// Convert_v1_NodeProxyOptions_To_api_NodeProxyOptions is an autogenerated conversion function. -func Convert_v1_NodeProxyOptions_To_api_NodeProxyOptions(in *v1.NodeProxyOptions, out *api.NodeProxyOptions, s conversion.Scope) error { - return autoConvert_v1_NodeProxyOptions_To_api_NodeProxyOptions(in, out, s) -} - -func autoConvert_api_NodeProxyOptions_To_v1_NodeProxyOptions(in *api.NodeProxyOptions, out *v1.NodeProxyOptions, s conversion.Scope) error { - out.Path = in.Path - return nil -} - -// Convert_api_NodeProxyOptions_To_v1_NodeProxyOptions is an autogenerated conversion function. -func Convert_api_NodeProxyOptions_To_v1_NodeProxyOptions(in *api.NodeProxyOptions, out *v1.NodeProxyOptions, s conversion.Scope) error { - return autoConvert_api_NodeProxyOptions_To_v1_NodeProxyOptions(in, out, s) -} - -func autoConvert_v1_NodeResources_To_api_NodeResources(in *v1.NodeResources, out *api.NodeResources, s conversion.Scope) error { - out.Capacity = *(*api.ResourceList)(unsafe.Pointer(&in.Capacity)) - return nil -} - -// Convert_v1_NodeResources_To_api_NodeResources is an autogenerated conversion function. -func Convert_v1_NodeResources_To_api_NodeResources(in *v1.NodeResources, out *api.NodeResources, s conversion.Scope) error { - return autoConvert_v1_NodeResources_To_api_NodeResources(in, out, s) -} - -func autoConvert_api_NodeResources_To_v1_NodeResources(in *api.NodeResources, out *v1.NodeResources, s conversion.Scope) error { - out.Capacity = *(*v1.ResourceList)(unsafe.Pointer(&in.Capacity)) - return nil -} - -// Convert_api_NodeResources_To_v1_NodeResources is an autogenerated conversion function. -func Convert_api_NodeResources_To_v1_NodeResources(in *api.NodeResources, out *v1.NodeResources, s conversion.Scope) error { - return autoConvert_api_NodeResources_To_v1_NodeResources(in, out, s) -} - -func autoConvert_v1_NodeSelector_To_api_NodeSelector(in *v1.NodeSelector, out *api.NodeSelector, s conversion.Scope) error { - out.NodeSelectorTerms = *(*[]api.NodeSelectorTerm)(unsafe.Pointer(&in.NodeSelectorTerms)) - return nil -} - -// Convert_v1_NodeSelector_To_api_NodeSelector is an autogenerated conversion function. -func Convert_v1_NodeSelector_To_api_NodeSelector(in *v1.NodeSelector, out *api.NodeSelector, s conversion.Scope) error { - return autoConvert_v1_NodeSelector_To_api_NodeSelector(in, out, s) -} - -func autoConvert_api_NodeSelector_To_v1_NodeSelector(in *api.NodeSelector, out *v1.NodeSelector, s conversion.Scope) error { - out.NodeSelectorTerms = *(*[]v1.NodeSelectorTerm)(unsafe.Pointer(&in.NodeSelectorTerms)) - return nil -} - -// Convert_api_NodeSelector_To_v1_NodeSelector is an autogenerated conversion function. -func Convert_api_NodeSelector_To_v1_NodeSelector(in *api.NodeSelector, out *v1.NodeSelector, s conversion.Scope) error { - return autoConvert_api_NodeSelector_To_v1_NodeSelector(in, out, s) -} - -func autoConvert_v1_NodeSelectorRequirement_To_api_NodeSelectorRequirement(in *v1.NodeSelectorRequirement, out *api.NodeSelectorRequirement, s conversion.Scope) error { - out.Key = in.Key - out.Operator = api.NodeSelectorOperator(in.Operator) - out.Values = *(*[]string)(unsafe.Pointer(&in.Values)) - return nil -} - -// Convert_v1_NodeSelectorRequirement_To_api_NodeSelectorRequirement is an autogenerated conversion function. -func Convert_v1_NodeSelectorRequirement_To_api_NodeSelectorRequirement(in *v1.NodeSelectorRequirement, out *api.NodeSelectorRequirement, s conversion.Scope) error { - return autoConvert_v1_NodeSelectorRequirement_To_api_NodeSelectorRequirement(in, out, s) -} - -func autoConvert_api_NodeSelectorRequirement_To_v1_NodeSelectorRequirement(in *api.NodeSelectorRequirement, out *v1.NodeSelectorRequirement, s conversion.Scope) error { - out.Key = in.Key - out.Operator = v1.NodeSelectorOperator(in.Operator) - out.Values = *(*[]string)(unsafe.Pointer(&in.Values)) - return nil -} - -// Convert_api_NodeSelectorRequirement_To_v1_NodeSelectorRequirement is an autogenerated conversion function. -func Convert_api_NodeSelectorRequirement_To_v1_NodeSelectorRequirement(in *api.NodeSelectorRequirement, out *v1.NodeSelectorRequirement, s conversion.Scope) error { - return autoConvert_api_NodeSelectorRequirement_To_v1_NodeSelectorRequirement(in, out, s) -} - -func autoConvert_v1_NodeSelectorTerm_To_api_NodeSelectorTerm(in *v1.NodeSelectorTerm, out *api.NodeSelectorTerm, s conversion.Scope) error { - out.MatchExpressions = *(*[]api.NodeSelectorRequirement)(unsafe.Pointer(&in.MatchExpressions)) - return nil -} - -// Convert_v1_NodeSelectorTerm_To_api_NodeSelectorTerm is an autogenerated conversion function. -func Convert_v1_NodeSelectorTerm_To_api_NodeSelectorTerm(in *v1.NodeSelectorTerm, out *api.NodeSelectorTerm, s conversion.Scope) error { - return autoConvert_v1_NodeSelectorTerm_To_api_NodeSelectorTerm(in, out, s) -} - -func autoConvert_api_NodeSelectorTerm_To_v1_NodeSelectorTerm(in *api.NodeSelectorTerm, out *v1.NodeSelectorTerm, s conversion.Scope) error { - out.MatchExpressions = *(*[]v1.NodeSelectorRequirement)(unsafe.Pointer(&in.MatchExpressions)) - return nil -} - -// Convert_api_NodeSelectorTerm_To_v1_NodeSelectorTerm is an autogenerated conversion function. -func Convert_api_NodeSelectorTerm_To_v1_NodeSelectorTerm(in *api.NodeSelectorTerm, out *v1.NodeSelectorTerm, s conversion.Scope) error { - return autoConvert_api_NodeSelectorTerm_To_v1_NodeSelectorTerm(in, out, s) -} - -func autoConvert_v1_NodeSpec_To_api_NodeSpec(in *v1.NodeSpec, out *api.NodeSpec, s conversion.Scope) error { - out.PodCIDR = in.PodCIDR - out.ExternalID = in.ExternalID - out.ProviderID = in.ProviderID - out.Unschedulable = in.Unschedulable - out.Taints = *(*[]api.Taint)(unsafe.Pointer(&in.Taints)) - out.ConfigSource = (*api.NodeConfigSource)(unsafe.Pointer(in.ConfigSource)) - return nil -} - -// Convert_v1_NodeSpec_To_api_NodeSpec is an autogenerated conversion function. -func Convert_v1_NodeSpec_To_api_NodeSpec(in *v1.NodeSpec, out *api.NodeSpec, s conversion.Scope) error { - return autoConvert_v1_NodeSpec_To_api_NodeSpec(in, out, s) -} - -func autoConvert_api_NodeSpec_To_v1_NodeSpec(in *api.NodeSpec, out *v1.NodeSpec, s conversion.Scope) error { - out.PodCIDR = in.PodCIDR - out.ExternalID = in.ExternalID - out.ProviderID = in.ProviderID - out.Unschedulable = in.Unschedulable - out.Taints = *(*[]v1.Taint)(unsafe.Pointer(&in.Taints)) - out.ConfigSource = (*v1.NodeConfigSource)(unsafe.Pointer(in.ConfigSource)) - return nil -} - -// Convert_api_NodeSpec_To_v1_NodeSpec is an autogenerated conversion function. -func Convert_api_NodeSpec_To_v1_NodeSpec(in *api.NodeSpec, out *v1.NodeSpec, s conversion.Scope) error { - return autoConvert_api_NodeSpec_To_v1_NodeSpec(in, out, s) -} - -func autoConvert_v1_NodeStatus_To_api_NodeStatus(in *v1.NodeStatus, out *api.NodeStatus, s conversion.Scope) error { - out.Capacity = *(*api.ResourceList)(unsafe.Pointer(&in.Capacity)) - out.Allocatable = *(*api.ResourceList)(unsafe.Pointer(&in.Allocatable)) - out.Phase = api.NodePhase(in.Phase) - out.Conditions = *(*[]api.NodeCondition)(unsafe.Pointer(&in.Conditions)) - out.Addresses = *(*[]api.NodeAddress)(unsafe.Pointer(&in.Addresses)) - if err := Convert_v1_NodeDaemonEndpoints_To_api_NodeDaemonEndpoints(&in.DaemonEndpoints, &out.DaemonEndpoints, s); err != nil { - return err - } - if err := Convert_v1_NodeSystemInfo_To_api_NodeSystemInfo(&in.NodeInfo, &out.NodeInfo, s); err != nil { - return err - } - out.Images = *(*[]api.ContainerImage)(unsafe.Pointer(&in.Images)) - out.VolumesInUse = *(*[]api.UniqueVolumeName)(unsafe.Pointer(&in.VolumesInUse)) - out.VolumesAttached = *(*[]api.AttachedVolume)(unsafe.Pointer(&in.VolumesAttached)) - return nil -} - -// Convert_v1_NodeStatus_To_api_NodeStatus is an autogenerated conversion function. -func Convert_v1_NodeStatus_To_api_NodeStatus(in *v1.NodeStatus, out *api.NodeStatus, s conversion.Scope) error { - return autoConvert_v1_NodeStatus_To_api_NodeStatus(in, out, s) -} - -func autoConvert_api_NodeStatus_To_v1_NodeStatus(in *api.NodeStatus, out *v1.NodeStatus, s conversion.Scope) error { - out.Capacity = *(*v1.ResourceList)(unsafe.Pointer(&in.Capacity)) - out.Allocatable = *(*v1.ResourceList)(unsafe.Pointer(&in.Allocatable)) - out.Phase = v1.NodePhase(in.Phase) - out.Conditions = *(*[]v1.NodeCondition)(unsafe.Pointer(&in.Conditions)) - out.Addresses = *(*[]v1.NodeAddress)(unsafe.Pointer(&in.Addresses)) - if err := Convert_api_NodeDaemonEndpoints_To_v1_NodeDaemonEndpoints(&in.DaemonEndpoints, &out.DaemonEndpoints, s); err != nil { - return err - } - if err := Convert_api_NodeSystemInfo_To_v1_NodeSystemInfo(&in.NodeInfo, &out.NodeInfo, s); err != nil { - return err - } - out.Images = *(*[]v1.ContainerImage)(unsafe.Pointer(&in.Images)) - out.VolumesInUse = *(*[]v1.UniqueVolumeName)(unsafe.Pointer(&in.VolumesInUse)) - out.VolumesAttached = *(*[]v1.AttachedVolume)(unsafe.Pointer(&in.VolumesAttached)) - return nil -} - -// Convert_api_NodeStatus_To_v1_NodeStatus is an autogenerated conversion function. -func Convert_api_NodeStatus_To_v1_NodeStatus(in *api.NodeStatus, out *v1.NodeStatus, s conversion.Scope) error { - return autoConvert_api_NodeStatus_To_v1_NodeStatus(in, out, s) -} - -func autoConvert_v1_NodeSystemInfo_To_api_NodeSystemInfo(in *v1.NodeSystemInfo, out *api.NodeSystemInfo, s conversion.Scope) error { - out.MachineID = in.MachineID - out.SystemUUID = in.SystemUUID - out.BootID = in.BootID - out.KernelVersion = in.KernelVersion - out.OSImage = in.OSImage - out.ContainerRuntimeVersion = in.ContainerRuntimeVersion - out.KubeletVersion = in.KubeletVersion - out.KubeProxyVersion = in.KubeProxyVersion - out.OperatingSystem = in.OperatingSystem - out.Architecture = in.Architecture - return nil -} - -// Convert_v1_NodeSystemInfo_To_api_NodeSystemInfo is an autogenerated conversion function. -func Convert_v1_NodeSystemInfo_To_api_NodeSystemInfo(in *v1.NodeSystemInfo, out *api.NodeSystemInfo, s conversion.Scope) error { - return autoConvert_v1_NodeSystemInfo_To_api_NodeSystemInfo(in, out, s) -} - -func autoConvert_api_NodeSystemInfo_To_v1_NodeSystemInfo(in *api.NodeSystemInfo, out *v1.NodeSystemInfo, s conversion.Scope) error { - out.MachineID = in.MachineID - out.SystemUUID = in.SystemUUID - out.BootID = in.BootID - out.KernelVersion = in.KernelVersion - out.OSImage = in.OSImage - out.ContainerRuntimeVersion = in.ContainerRuntimeVersion - out.KubeletVersion = in.KubeletVersion - out.KubeProxyVersion = in.KubeProxyVersion - out.OperatingSystem = in.OperatingSystem - out.Architecture = in.Architecture - return nil -} - -// Convert_api_NodeSystemInfo_To_v1_NodeSystemInfo is an autogenerated conversion function. -func Convert_api_NodeSystemInfo_To_v1_NodeSystemInfo(in *api.NodeSystemInfo, out *v1.NodeSystemInfo, s conversion.Scope) error { - return autoConvert_api_NodeSystemInfo_To_v1_NodeSystemInfo(in, out, s) -} - -func autoConvert_v1_ObjectFieldSelector_To_api_ObjectFieldSelector(in *v1.ObjectFieldSelector, out *api.ObjectFieldSelector, s conversion.Scope) error { - out.APIVersion = in.APIVersion - out.FieldPath = in.FieldPath - return nil -} - -// Convert_v1_ObjectFieldSelector_To_api_ObjectFieldSelector is an autogenerated conversion function. -func Convert_v1_ObjectFieldSelector_To_api_ObjectFieldSelector(in *v1.ObjectFieldSelector, out *api.ObjectFieldSelector, s conversion.Scope) error { - return autoConvert_v1_ObjectFieldSelector_To_api_ObjectFieldSelector(in, out, s) -} - -func autoConvert_api_ObjectFieldSelector_To_v1_ObjectFieldSelector(in *api.ObjectFieldSelector, out *v1.ObjectFieldSelector, s conversion.Scope) error { - out.APIVersion = in.APIVersion - out.FieldPath = in.FieldPath - return nil -} - -// Convert_api_ObjectFieldSelector_To_v1_ObjectFieldSelector is an autogenerated conversion function. -func Convert_api_ObjectFieldSelector_To_v1_ObjectFieldSelector(in *api.ObjectFieldSelector, out *v1.ObjectFieldSelector, s conversion.Scope) error { - return autoConvert_api_ObjectFieldSelector_To_v1_ObjectFieldSelector(in, out, s) -} - -func autoConvert_v1_ObjectMeta_To_api_ObjectMeta(in *v1.ObjectMeta, out *api.ObjectMeta, s conversion.Scope) error { - out.Name = in.Name - out.GenerateName = in.GenerateName - out.Namespace = in.Namespace - out.SelfLink = in.SelfLink - out.UID = types.UID(in.UID) - out.ResourceVersion = in.ResourceVersion - out.Generation = in.Generation - out.CreationTimestamp = in.CreationTimestamp - out.DeletionTimestamp = (*meta_v1.Time)(unsafe.Pointer(in.DeletionTimestamp)) - out.DeletionGracePeriodSeconds = (*int64)(unsafe.Pointer(in.DeletionGracePeriodSeconds)) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) - out.OwnerReferences = *(*[]meta_v1.OwnerReference)(unsafe.Pointer(&in.OwnerReferences)) - out.Initializers = (*meta_v1.Initializers)(unsafe.Pointer(in.Initializers)) - out.Finalizers = *(*[]string)(unsafe.Pointer(&in.Finalizers)) - out.ClusterName = in.ClusterName - return nil -} - -// Convert_v1_ObjectMeta_To_api_ObjectMeta is an autogenerated conversion function. -func Convert_v1_ObjectMeta_To_api_ObjectMeta(in *v1.ObjectMeta, out *api.ObjectMeta, s conversion.Scope) error { - return autoConvert_v1_ObjectMeta_To_api_ObjectMeta(in, out, s) -} - -func autoConvert_api_ObjectMeta_To_v1_ObjectMeta(in *api.ObjectMeta, out *v1.ObjectMeta, s conversion.Scope) error { - out.Name = in.Name - out.GenerateName = in.GenerateName - out.Namespace = in.Namespace - out.SelfLink = in.SelfLink - out.UID = types.UID(in.UID) - out.ResourceVersion = in.ResourceVersion - out.Generation = in.Generation - out.CreationTimestamp = in.CreationTimestamp - out.DeletionTimestamp = (*meta_v1.Time)(unsafe.Pointer(in.DeletionTimestamp)) - out.DeletionGracePeriodSeconds = (*int64)(unsafe.Pointer(in.DeletionGracePeriodSeconds)) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) - out.OwnerReferences = *(*[]meta_v1.OwnerReference)(unsafe.Pointer(&in.OwnerReferences)) - out.Initializers = (*meta_v1.Initializers)(unsafe.Pointer(in.Initializers)) - out.Finalizers = *(*[]string)(unsafe.Pointer(&in.Finalizers)) - out.ClusterName = in.ClusterName - return nil -} - -// Convert_api_ObjectMeta_To_v1_ObjectMeta is an autogenerated conversion function. -func Convert_api_ObjectMeta_To_v1_ObjectMeta(in *api.ObjectMeta, out *v1.ObjectMeta, s conversion.Scope) error { - return autoConvert_api_ObjectMeta_To_v1_ObjectMeta(in, out, s) -} - -func autoConvert_v1_ObjectReference_To_api_ObjectReference(in *v1.ObjectReference, out *api.ObjectReference, s conversion.Scope) error { - out.Kind = in.Kind - out.Namespace = in.Namespace - out.Name = in.Name - out.UID = types.UID(in.UID) - out.APIVersion = in.APIVersion - out.ResourceVersion = in.ResourceVersion - out.FieldPath = in.FieldPath - return nil -} - -// Convert_v1_ObjectReference_To_api_ObjectReference is an autogenerated conversion function. -func Convert_v1_ObjectReference_To_api_ObjectReference(in *v1.ObjectReference, out *api.ObjectReference, s conversion.Scope) error { - return autoConvert_v1_ObjectReference_To_api_ObjectReference(in, out, s) -} - -func autoConvert_api_ObjectReference_To_v1_ObjectReference(in *api.ObjectReference, out *v1.ObjectReference, s conversion.Scope) error { - out.Kind = in.Kind - out.Namespace = in.Namespace - out.Name = in.Name - out.UID = types.UID(in.UID) - out.APIVersion = in.APIVersion - out.ResourceVersion = in.ResourceVersion - out.FieldPath = in.FieldPath - return nil -} - -// Convert_api_ObjectReference_To_v1_ObjectReference is an autogenerated conversion function. -func Convert_api_ObjectReference_To_v1_ObjectReference(in *api.ObjectReference, out *v1.ObjectReference, s conversion.Scope) error { - return autoConvert_api_ObjectReference_To_v1_ObjectReference(in, out, s) -} - -func autoConvert_v1_PersistentVolume_To_api_PersistentVolume(in *v1.PersistentVolume, out *api.PersistentVolume, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1_PersistentVolumeSpec_To_api_PersistentVolumeSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1_PersistentVolumeStatus_To_api_PersistentVolumeStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1_PersistentVolume_To_api_PersistentVolume is an autogenerated conversion function. -func Convert_v1_PersistentVolume_To_api_PersistentVolume(in *v1.PersistentVolume, out *api.PersistentVolume, s conversion.Scope) error { - return autoConvert_v1_PersistentVolume_To_api_PersistentVolume(in, out, s) -} - -func autoConvert_api_PersistentVolume_To_v1_PersistentVolume(in *api.PersistentVolume, out *v1.PersistentVolume, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_api_PersistentVolumeSpec_To_v1_PersistentVolumeSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_api_PersistentVolumeStatus_To_v1_PersistentVolumeStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_api_PersistentVolume_To_v1_PersistentVolume is an autogenerated conversion function. -func Convert_api_PersistentVolume_To_v1_PersistentVolume(in *api.PersistentVolume, out *v1.PersistentVolume, s conversion.Scope) error { - return autoConvert_api_PersistentVolume_To_v1_PersistentVolume(in, out, s) -} - -func autoConvert_v1_PersistentVolumeClaim_To_api_PersistentVolumeClaim(in *v1.PersistentVolumeClaim, out *api.PersistentVolumeClaim, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1_PersistentVolumeClaimSpec_To_api_PersistentVolumeClaimSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1_PersistentVolumeClaimStatus_To_api_PersistentVolumeClaimStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1_PersistentVolumeClaim_To_api_PersistentVolumeClaim is an autogenerated conversion function. -func Convert_v1_PersistentVolumeClaim_To_api_PersistentVolumeClaim(in *v1.PersistentVolumeClaim, out *api.PersistentVolumeClaim, s conversion.Scope) error { - return autoConvert_v1_PersistentVolumeClaim_To_api_PersistentVolumeClaim(in, out, s) -} - -func autoConvert_api_PersistentVolumeClaim_To_v1_PersistentVolumeClaim(in *api.PersistentVolumeClaim, out *v1.PersistentVolumeClaim, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_api_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_api_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_api_PersistentVolumeClaim_To_v1_PersistentVolumeClaim is an autogenerated conversion function. -func Convert_api_PersistentVolumeClaim_To_v1_PersistentVolumeClaim(in *api.PersistentVolumeClaim, out *v1.PersistentVolumeClaim, s conversion.Scope) error { - return autoConvert_api_PersistentVolumeClaim_To_v1_PersistentVolumeClaim(in, out, s) -} - -func autoConvert_v1_PersistentVolumeClaimCondition_To_api_PersistentVolumeClaimCondition(in *v1.PersistentVolumeClaimCondition, out *api.PersistentVolumeClaimCondition, s conversion.Scope) error { - out.Type = api.PersistentVolumeClaimConditionType(in.Type) - out.Status = api.ConditionStatus(in.Status) - out.LastProbeTime = in.LastProbeTime - out.LastTransitionTime = in.LastTransitionTime - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -// Convert_v1_PersistentVolumeClaimCondition_To_api_PersistentVolumeClaimCondition is an autogenerated conversion function. -func Convert_v1_PersistentVolumeClaimCondition_To_api_PersistentVolumeClaimCondition(in *v1.PersistentVolumeClaimCondition, out *api.PersistentVolumeClaimCondition, s conversion.Scope) error { - return autoConvert_v1_PersistentVolumeClaimCondition_To_api_PersistentVolumeClaimCondition(in, out, s) -} - -func autoConvert_api_PersistentVolumeClaimCondition_To_v1_PersistentVolumeClaimCondition(in *api.PersistentVolumeClaimCondition, out *v1.PersistentVolumeClaimCondition, s conversion.Scope) error { - out.Type = v1.PersistentVolumeClaimConditionType(in.Type) - out.Status = v1.ConditionStatus(in.Status) - out.LastProbeTime = in.LastProbeTime - out.LastTransitionTime = in.LastTransitionTime - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -// Convert_api_PersistentVolumeClaimCondition_To_v1_PersistentVolumeClaimCondition is an autogenerated conversion function. -func Convert_api_PersistentVolumeClaimCondition_To_v1_PersistentVolumeClaimCondition(in *api.PersistentVolumeClaimCondition, out *v1.PersistentVolumeClaimCondition, s conversion.Scope) error { - return autoConvert_api_PersistentVolumeClaimCondition_To_v1_PersistentVolumeClaimCondition(in, out, s) -} - -func autoConvert_v1_PersistentVolumeClaimList_To_api_PersistentVolumeClaimList(in *v1.PersistentVolumeClaimList, out *api.PersistentVolumeClaimList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]api.PersistentVolumeClaim)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_v1_PersistentVolumeClaimList_To_api_PersistentVolumeClaimList is an autogenerated conversion function. -func Convert_v1_PersistentVolumeClaimList_To_api_PersistentVolumeClaimList(in *v1.PersistentVolumeClaimList, out *api.PersistentVolumeClaimList, s conversion.Scope) error { - return autoConvert_v1_PersistentVolumeClaimList_To_api_PersistentVolumeClaimList(in, out, s) -} - -func autoConvert_api_PersistentVolumeClaimList_To_v1_PersistentVolumeClaimList(in *api.PersistentVolumeClaimList, out *v1.PersistentVolumeClaimList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]v1.PersistentVolumeClaim)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_api_PersistentVolumeClaimList_To_v1_PersistentVolumeClaimList is an autogenerated conversion function. -func Convert_api_PersistentVolumeClaimList_To_v1_PersistentVolumeClaimList(in *api.PersistentVolumeClaimList, out *v1.PersistentVolumeClaimList, s conversion.Scope) error { - return autoConvert_api_PersistentVolumeClaimList_To_v1_PersistentVolumeClaimList(in, out, s) -} - -func autoConvert_v1_PersistentVolumeClaimSpec_To_api_PersistentVolumeClaimSpec(in *v1.PersistentVolumeClaimSpec, out *api.PersistentVolumeClaimSpec, s conversion.Scope) error { - out.AccessModes = *(*[]api.PersistentVolumeAccessMode)(unsafe.Pointer(&in.AccessModes)) - out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) - if err := Convert_v1_ResourceRequirements_To_api_ResourceRequirements(&in.Resources, &out.Resources, s); err != nil { - return err - } - out.VolumeName = in.VolumeName - out.StorageClassName = (*string)(unsafe.Pointer(in.StorageClassName)) - return nil -} - -// Convert_v1_PersistentVolumeClaimSpec_To_api_PersistentVolumeClaimSpec is an autogenerated conversion function. -func Convert_v1_PersistentVolumeClaimSpec_To_api_PersistentVolumeClaimSpec(in *v1.PersistentVolumeClaimSpec, out *api.PersistentVolumeClaimSpec, s conversion.Scope) error { - return autoConvert_v1_PersistentVolumeClaimSpec_To_api_PersistentVolumeClaimSpec(in, out, s) -} - -func autoConvert_api_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec(in *api.PersistentVolumeClaimSpec, out *v1.PersistentVolumeClaimSpec, s conversion.Scope) error { - out.AccessModes = *(*[]v1.PersistentVolumeAccessMode)(unsafe.Pointer(&in.AccessModes)) - out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) - if err := Convert_api_ResourceRequirements_To_v1_ResourceRequirements(&in.Resources, &out.Resources, s); err != nil { - return err - } - out.VolumeName = in.VolumeName - out.StorageClassName = (*string)(unsafe.Pointer(in.StorageClassName)) - return nil -} - -// Convert_api_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec is an autogenerated conversion function. -func Convert_api_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec(in *api.PersistentVolumeClaimSpec, out *v1.PersistentVolumeClaimSpec, s conversion.Scope) error { - return autoConvert_api_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec(in, out, s) -} - -func autoConvert_v1_PersistentVolumeClaimStatus_To_api_PersistentVolumeClaimStatus(in *v1.PersistentVolumeClaimStatus, out *api.PersistentVolumeClaimStatus, s conversion.Scope) error { - out.Phase = api.PersistentVolumeClaimPhase(in.Phase) - out.AccessModes = *(*[]api.PersistentVolumeAccessMode)(unsafe.Pointer(&in.AccessModes)) - out.Capacity = *(*api.ResourceList)(unsafe.Pointer(&in.Capacity)) - out.Conditions = *(*[]api.PersistentVolumeClaimCondition)(unsafe.Pointer(&in.Conditions)) - return nil -} - -// Convert_v1_PersistentVolumeClaimStatus_To_api_PersistentVolumeClaimStatus is an autogenerated conversion function. -func Convert_v1_PersistentVolumeClaimStatus_To_api_PersistentVolumeClaimStatus(in *v1.PersistentVolumeClaimStatus, out *api.PersistentVolumeClaimStatus, s conversion.Scope) error { - return autoConvert_v1_PersistentVolumeClaimStatus_To_api_PersistentVolumeClaimStatus(in, out, s) -} - -func autoConvert_api_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus(in *api.PersistentVolumeClaimStatus, out *v1.PersistentVolumeClaimStatus, s conversion.Scope) error { - out.Phase = v1.PersistentVolumeClaimPhase(in.Phase) - out.AccessModes = *(*[]v1.PersistentVolumeAccessMode)(unsafe.Pointer(&in.AccessModes)) - out.Capacity = *(*v1.ResourceList)(unsafe.Pointer(&in.Capacity)) - out.Conditions = *(*[]v1.PersistentVolumeClaimCondition)(unsafe.Pointer(&in.Conditions)) - return nil -} - -// Convert_api_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus is an autogenerated conversion function. -func Convert_api_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus(in *api.PersistentVolumeClaimStatus, out *v1.PersistentVolumeClaimStatus, s conversion.Scope) error { - return autoConvert_api_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus(in, out, s) -} - -func autoConvert_v1_PersistentVolumeClaimVolumeSource_To_api_PersistentVolumeClaimVolumeSource(in *v1.PersistentVolumeClaimVolumeSource, out *api.PersistentVolumeClaimVolumeSource, s conversion.Scope) error { - out.ClaimName = in.ClaimName - out.ReadOnly = in.ReadOnly - return nil -} - -// Convert_v1_PersistentVolumeClaimVolumeSource_To_api_PersistentVolumeClaimVolumeSource is an autogenerated conversion function. -func Convert_v1_PersistentVolumeClaimVolumeSource_To_api_PersistentVolumeClaimVolumeSource(in *v1.PersistentVolumeClaimVolumeSource, out *api.PersistentVolumeClaimVolumeSource, s conversion.Scope) error { - return autoConvert_v1_PersistentVolumeClaimVolumeSource_To_api_PersistentVolumeClaimVolumeSource(in, out, s) -} - -func autoConvert_api_PersistentVolumeClaimVolumeSource_To_v1_PersistentVolumeClaimVolumeSource(in *api.PersistentVolumeClaimVolumeSource, out *v1.PersistentVolumeClaimVolumeSource, s conversion.Scope) error { - out.ClaimName = in.ClaimName - out.ReadOnly = in.ReadOnly - return nil -} - -// Convert_api_PersistentVolumeClaimVolumeSource_To_v1_PersistentVolumeClaimVolumeSource is an autogenerated conversion function. -func Convert_api_PersistentVolumeClaimVolumeSource_To_v1_PersistentVolumeClaimVolumeSource(in *api.PersistentVolumeClaimVolumeSource, out *v1.PersistentVolumeClaimVolumeSource, s conversion.Scope) error { - return autoConvert_api_PersistentVolumeClaimVolumeSource_To_v1_PersistentVolumeClaimVolumeSource(in, out, s) -} - -func autoConvert_v1_PersistentVolumeList_To_api_PersistentVolumeList(in *v1.PersistentVolumeList, out *api.PersistentVolumeList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]api.PersistentVolume, len(*in)) - for i := range *in { - if err := Convert_v1_PersistentVolume_To_api_PersistentVolume(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v1_PersistentVolumeList_To_api_PersistentVolumeList is an autogenerated conversion function. -func Convert_v1_PersistentVolumeList_To_api_PersistentVolumeList(in *v1.PersistentVolumeList, out *api.PersistentVolumeList, s conversion.Scope) error { - return autoConvert_v1_PersistentVolumeList_To_api_PersistentVolumeList(in, out, s) -} - -func autoConvert_api_PersistentVolumeList_To_v1_PersistentVolumeList(in *api.PersistentVolumeList, out *v1.PersistentVolumeList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]v1.PersistentVolume, len(*in)) - for i := range *in { - if err := Convert_api_PersistentVolume_To_v1_PersistentVolume(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_api_PersistentVolumeList_To_v1_PersistentVolumeList is an autogenerated conversion function. -func Convert_api_PersistentVolumeList_To_v1_PersistentVolumeList(in *api.PersistentVolumeList, out *v1.PersistentVolumeList, s conversion.Scope) error { - return autoConvert_api_PersistentVolumeList_To_v1_PersistentVolumeList(in, out, s) -} - -func autoConvert_v1_PersistentVolumeSource_To_api_PersistentVolumeSource(in *v1.PersistentVolumeSource, out *api.PersistentVolumeSource, s conversion.Scope) error { - out.GCEPersistentDisk = (*api.GCEPersistentDiskVolumeSource)(unsafe.Pointer(in.GCEPersistentDisk)) - out.AWSElasticBlockStore = (*api.AWSElasticBlockStoreVolumeSource)(unsafe.Pointer(in.AWSElasticBlockStore)) - out.HostPath = (*api.HostPathVolumeSource)(unsafe.Pointer(in.HostPath)) - out.Glusterfs = (*api.GlusterfsVolumeSource)(unsafe.Pointer(in.Glusterfs)) - out.NFS = (*api.NFSVolumeSource)(unsafe.Pointer(in.NFS)) - out.RBD = (*api.RBDVolumeSource)(unsafe.Pointer(in.RBD)) - out.ISCSI = (*api.ISCSIVolumeSource)(unsafe.Pointer(in.ISCSI)) - out.Cinder = (*api.CinderVolumeSource)(unsafe.Pointer(in.Cinder)) - out.CephFS = (*api.CephFSPersistentVolumeSource)(unsafe.Pointer(in.CephFS)) - out.FC = (*api.FCVolumeSource)(unsafe.Pointer(in.FC)) - out.Flocker = (*api.FlockerVolumeSource)(unsafe.Pointer(in.Flocker)) - out.FlexVolume = (*api.FlexVolumeSource)(unsafe.Pointer(in.FlexVolume)) - out.AzureFile = (*api.AzureFilePersistentVolumeSource)(unsafe.Pointer(in.AzureFile)) - out.VsphereVolume = (*api.VsphereVirtualDiskVolumeSource)(unsafe.Pointer(in.VsphereVolume)) - out.Quobyte = (*api.QuobyteVolumeSource)(unsafe.Pointer(in.Quobyte)) - out.AzureDisk = (*api.AzureDiskVolumeSource)(unsafe.Pointer(in.AzureDisk)) - out.PhotonPersistentDisk = (*api.PhotonPersistentDiskVolumeSource)(unsafe.Pointer(in.PhotonPersistentDisk)) - out.PortworxVolume = (*api.PortworxVolumeSource)(unsafe.Pointer(in.PortworxVolume)) - out.ScaleIO = (*api.ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO)) - out.Local = (*api.LocalVolumeSource)(unsafe.Pointer(in.Local)) - out.StorageOS = (*api.StorageOSPersistentVolumeSource)(unsafe.Pointer(in.StorageOS)) - return nil -} - -// Convert_v1_PersistentVolumeSource_To_api_PersistentVolumeSource is an autogenerated conversion function. -func Convert_v1_PersistentVolumeSource_To_api_PersistentVolumeSource(in *v1.PersistentVolumeSource, out *api.PersistentVolumeSource, s conversion.Scope) error { - return autoConvert_v1_PersistentVolumeSource_To_api_PersistentVolumeSource(in, out, s) -} - -func autoConvert_api_PersistentVolumeSource_To_v1_PersistentVolumeSource(in *api.PersistentVolumeSource, out *v1.PersistentVolumeSource, s conversion.Scope) error { - out.GCEPersistentDisk = (*v1.GCEPersistentDiskVolumeSource)(unsafe.Pointer(in.GCEPersistentDisk)) - out.AWSElasticBlockStore = (*v1.AWSElasticBlockStoreVolumeSource)(unsafe.Pointer(in.AWSElasticBlockStore)) - out.HostPath = (*v1.HostPathVolumeSource)(unsafe.Pointer(in.HostPath)) - out.Glusterfs = (*v1.GlusterfsVolumeSource)(unsafe.Pointer(in.Glusterfs)) - out.NFS = (*v1.NFSVolumeSource)(unsafe.Pointer(in.NFS)) - out.RBD = (*v1.RBDVolumeSource)(unsafe.Pointer(in.RBD)) - out.Quobyte = (*v1.QuobyteVolumeSource)(unsafe.Pointer(in.Quobyte)) - out.ISCSI = (*v1.ISCSIVolumeSource)(unsafe.Pointer(in.ISCSI)) - out.FlexVolume = (*v1.FlexVolumeSource)(unsafe.Pointer(in.FlexVolume)) - out.Cinder = (*v1.CinderVolumeSource)(unsafe.Pointer(in.Cinder)) - out.CephFS = (*v1.CephFSPersistentVolumeSource)(unsafe.Pointer(in.CephFS)) - out.FC = (*v1.FCVolumeSource)(unsafe.Pointer(in.FC)) - out.Flocker = (*v1.FlockerVolumeSource)(unsafe.Pointer(in.Flocker)) - out.AzureFile = (*v1.AzureFilePersistentVolumeSource)(unsafe.Pointer(in.AzureFile)) - out.VsphereVolume = (*v1.VsphereVirtualDiskVolumeSource)(unsafe.Pointer(in.VsphereVolume)) - out.AzureDisk = (*v1.AzureDiskVolumeSource)(unsafe.Pointer(in.AzureDisk)) - out.PhotonPersistentDisk = (*v1.PhotonPersistentDiskVolumeSource)(unsafe.Pointer(in.PhotonPersistentDisk)) - out.PortworxVolume = (*v1.PortworxVolumeSource)(unsafe.Pointer(in.PortworxVolume)) - out.ScaleIO = (*v1.ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO)) - out.Local = (*v1.LocalVolumeSource)(unsafe.Pointer(in.Local)) - out.StorageOS = (*v1.StorageOSPersistentVolumeSource)(unsafe.Pointer(in.StorageOS)) - return nil -} - -// Convert_api_PersistentVolumeSource_To_v1_PersistentVolumeSource is an autogenerated conversion function. -func Convert_api_PersistentVolumeSource_To_v1_PersistentVolumeSource(in *api.PersistentVolumeSource, out *v1.PersistentVolumeSource, s conversion.Scope) error { - return autoConvert_api_PersistentVolumeSource_To_v1_PersistentVolumeSource(in, out, s) -} - -func autoConvert_v1_PersistentVolumeSpec_To_api_PersistentVolumeSpec(in *v1.PersistentVolumeSpec, out *api.PersistentVolumeSpec, s conversion.Scope) error { - out.Capacity = *(*api.ResourceList)(unsafe.Pointer(&in.Capacity)) - if err := Convert_v1_PersistentVolumeSource_To_api_PersistentVolumeSource(&in.PersistentVolumeSource, &out.PersistentVolumeSource, s); err != nil { - return err - } - out.AccessModes = *(*[]api.PersistentVolumeAccessMode)(unsafe.Pointer(&in.AccessModes)) - out.ClaimRef = (*api.ObjectReference)(unsafe.Pointer(in.ClaimRef)) - out.PersistentVolumeReclaimPolicy = api.PersistentVolumeReclaimPolicy(in.PersistentVolumeReclaimPolicy) - out.StorageClassName = in.StorageClassName - out.MountOptions = *(*[]string)(unsafe.Pointer(&in.MountOptions)) - return nil -} - -// Convert_v1_PersistentVolumeSpec_To_api_PersistentVolumeSpec is an autogenerated conversion function. -func Convert_v1_PersistentVolumeSpec_To_api_PersistentVolumeSpec(in *v1.PersistentVolumeSpec, out *api.PersistentVolumeSpec, s conversion.Scope) error { - return autoConvert_v1_PersistentVolumeSpec_To_api_PersistentVolumeSpec(in, out, s) -} - -func autoConvert_api_PersistentVolumeSpec_To_v1_PersistentVolumeSpec(in *api.PersistentVolumeSpec, out *v1.PersistentVolumeSpec, s conversion.Scope) error { - out.Capacity = *(*v1.ResourceList)(unsafe.Pointer(&in.Capacity)) - if err := Convert_api_PersistentVolumeSource_To_v1_PersistentVolumeSource(&in.PersistentVolumeSource, &out.PersistentVolumeSource, s); err != nil { - return err - } - out.AccessModes = *(*[]v1.PersistentVolumeAccessMode)(unsafe.Pointer(&in.AccessModes)) - out.ClaimRef = (*v1.ObjectReference)(unsafe.Pointer(in.ClaimRef)) - out.PersistentVolumeReclaimPolicy = v1.PersistentVolumeReclaimPolicy(in.PersistentVolumeReclaimPolicy) - out.StorageClassName = in.StorageClassName - out.MountOptions = *(*[]string)(unsafe.Pointer(&in.MountOptions)) - return nil -} - -// Convert_api_PersistentVolumeSpec_To_v1_PersistentVolumeSpec is an autogenerated conversion function. -func Convert_api_PersistentVolumeSpec_To_v1_PersistentVolumeSpec(in *api.PersistentVolumeSpec, out *v1.PersistentVolumeSpec, s conversion.Scope) error { - return autoConvert_api_PersistentVolumeSpec_To_v1_PersistentVolumeSpec(in, out, s) -} - -func autoConvert_v1_PersistentVolumeStatus_To_api_PersistentVolumeStatus(in *v1.PersistentVolumeStatus, out *api.PersistentVolumeStatus, s conversion.Scope) error { - out.Phase = api.PersistentVolumePhase(in.Phase) - out.Message = in.Message - out.Reason = in.Reason - return nil -} - -// Convert_v1_PersistentVolumeStatus_To_api_PersistentVolumeStatus is an autogenerated conversion function. -func Convert_v1_PersistentVolumeStatus_To_api_PersistentVolumeStatus(in *v1.PersistentVolumeStatus, out *api.PersistentVolumeStatus, s conversion.Scope) error { - return autoConvert_v1_PersistentVolumeStatus_To_api_PersistentVolumeStatus(in, out, s) -} - -func autoConvert_api_PersistentVolumeStatus_To_v1_PersistentVolumeStatus(in *api.PersistentVolumeStatus, out *v1.PersistentVolumeStatus, s conversion.Scope) error { - out.Phase = v1.PersistentVolumePhase(in.Phase) - out.Message = in.Message - out.Reason = in.Reason - return nil -} - -// Convert_api_PersistentVolumeStatus_To_v1_PersistentVolumeStatus is an autogenerated conversion function. -func Convert_api_PersistentVolumeStatus_To_v1_PersistentVolumeStatus(in *api.PersistentVolumeStatus, out *v1.PersistentVolumeStatus, s conversion.Scope) error { - return autoConvert_api_PersistentVolumeStatus_To_v1_PersistentVolumeStatus(in, out, s) -} - -func autoConvert_v1_PhotonPersistentDiskVolumeSource_To_api_PhotonPersistentDiskVolumeSource(in *v1.PhotonPersistentDiskVolumeSource, out *api.PhotonPersistentDiskVolumeSource, s conversion.Scope) error { - out.PdID = in.PdID - out.FSType = in.FSType - return nil -} - -// Convert_v1_PhotonPersistentDiskVolumeSource_To_api_PhotonPersistentDiskVolumeSource is an autogenerated conversion function. -func Convert_v1_PhotonPersistentDiskVolumeSource_To_api_PhotonPersistentDiskVolumeSource(in *v1.PhotonPersistentDiskVolumeSource, out *api.PhotonPersistentDiskVolumeSource, s conversion.Scope) error { - return autoConvert_v1_PhotonPersistentDiskVolumeSource_To_api_PhotonPersistentDiskVolumeSource(in, out, s) -} - -func autoConvert_api_PhotonPersistentDiskVolumeSource_To_v1_PhotonPersistentDiskVolumeSource(in *api.PhotonPersistentDiskVolumeSource, out *v1.PhotonPersistentDiskVolumeSource, s conversion.Scope) error { - out.PdID = in.PdID - out.FSType = in.FSType - return nil -} - -// Convert_api_PhotonPersistentDiskVolumeSource_To_v1_PhotonPersistentDiskVolumeSource is an autogenerated conversion function. -func Convert_api_PhotonPersistentDiskVolumeSource_To_v1_PhotonPersistentDiskVolumeSource(in *api.PhotonPersistentDiskVolumeSource, out *v1.PhotonPersistentDiskVolumeSource, s conversion.Scope) error { - return autoConvert_api_PhotonPersistentDiskVolumeSource_To_v1_PhotonPersistentDiskVolumeSource(in, out, s) -} - -func autoConvert_v1_Pod_To_api_Pod(in *v1.Pod, out *api.Pod, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1_PodSpec_To_api_PodSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1_PodStatus_To_api_PodStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1_Pod_To_api_Pod is an autogenerated conversion function. -func Convert_v1_Pod_To_api_Pod(in *v1.Pod, out *api.Pod, s conversion.Scope) error { - return autoConvert_v1_Pod_To_api_Pod(in, out, s) -} - -func autoConvert_api_Pod_To_v1_Pod(in *api.Pod, out *v1.Pod, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_api_PodSpec_To_v1_PodSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_api_PodStatus_To_v1_PodStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -func autoConvert_v1_PodAffinity_To_api_PodAffinity(in *v1.PodAffinity, out *api.PodAffinity, s conversion.Scope) error { - out.RequiredDuringSchedulingIgnoredDuringExecution = *(*[]api.PodAffinityTerm)(unsafe.Pointer(&in.RequiredDuringSchedulingIgnoredDuringExecution)) - out.PreferredDuringSchedulingIgnoredDuringExecution = *(*[]api.WeightedPodAffinityTerm)(unsafe.Pointer(&in.PreferredDuringSchedulingIgnoredDuringExecution)) - return nil -} - -// Convert_v1_PodAffinity_To_api_PodAffinity is an autogenerated conversion function. -func Convert_v1_PodAffinity_To_api_PodAffinity(in *v1.PodAffinity, out *api.PodAffinity, s conversion.Scope) error { - return autoConvert_v1_PodAffinity_To_api_PodAffinity(in, out, s) -} - -func autoConvert_api_PodAffinity_To_v1_PodAffinity(in *api.PodAffinity, out *v1.PodAffinity, s conversion.Scope) error { - out.RequiredDuringSchedulingIgnoredDuringExecution = *(*[]v1.PodAffinityTerm)(unsafe.Pointer(&in.RequiredDuringSchedulingIgnoredDuringExecution)) - out.PreferredDuringSchedulingIgnoredDuringExecution = *(*[]v1.WeightedPodAffinityTerm)(unsafe.Pointer(&in.PreferredDuringSchedulingIgnoredDuringExecution)) - return nil -} - -// Convert_api_PodAffinity_To_v1_PodAffinity is an autogenerated conversion function. -func Convert_api_PodAffinity_To_v1_PodAffinity(in *api.PodAffinity, out *v1.PodAffinity, s conversion.Scope) error { - return autoConvert_api_PodAffinity_To_v1_PodAffinity(in, out, s) -} - -func autoConvert_v1_PodAffinityTerm_To_api_PodAffinityTerm(in *v1.PodAffinityTerm, out *api.PodAffinityTerm, s conversion.Scope) error { - out.LabelSelector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.LabelSelector)) - out.Namespaces = *(*[]string)(unsafe.Pointer(&in.Namespaces)) - out.TopologyKey = in.TopologyKey - return nil -} - -// Convert_v1_PodAffinityTerm_To_api_PodAffinityTerm is an autogenerated conversion function. -func Convert_v1_PodAffinityTerm_To_api_PodAffinityTerm(in *v1.PodAffinityTerm, out *api.PodAffinityTerm, s conversion.Scope) error { - return autoConvert_v1_PodAffinityTerm_To_api_PodAffinityTerm(in, out, s) -} - -func autoConvert_api_PodAffinityTerm_To_v1_PodAffinityTerm(in *api.PodAffinityTerm, out *v1.PodAffinityTerm, s conversion.Scope) error { - out.LabelSelector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.LabelSelector)) - out.Namespaces = *(*[]string)(unsafe.Pointer(&in.Namespaces)) - out.TopologyKey = in.TopologyKey - return nil -} - -// Convert_api_PodAffinityTerm_To_v1_PodAffinityTerm is an autogenerated conversion function. -func Convert_api_PodAffinityTerm_To_v1_PodAffinityTerm(in *api.PodAffinityTerm, out *v1.PodAffinityTerm, s conversion.Scope) error { - return autoConvert_api_PodAffinityTerm_To_v1_PodAffinityTerm(in, out, s) -} - -func autoConvert_v1_PodAntiAffinity_To_api_PodAntiAffinity(in *v1.PodAntiAffinity, out *api.PodAntiAffinity, s conversion.Scope) error { - out.RequiredDuringSchedulingIgnoredDuringExecution = *(*[]api.PodAffinityTerm)(unsafe.Pointer(&in.RequiredDuringSchedulingIgnoredDuringExecution)) - out.PreferredDuringSchedulingIgnoredDuringExecution = *(*[]api.WeightedPodAffinityTerm)(unsafe.Pointer(&in.PreferredDuringSchedulingIgnoredDuringExecution)) - return nil -} - -// Convert_v1_PodAntiAffinity_To_api_PodAntiAffinity is an autogenerated conversion function. -func Convert_v1_PodAntiAffinity_To_api_PodAntiAffinity(in *v1.PodAntiAffinity, out *api.PodAntiAffinity, s conversion.Scope) error { - return autoConvert_v1_PodAntiAffinity_To_api_PodAntiAffinity(in, out, s) -} - -func autoConvert_api_PodAntiAffinity_To_v1_PodAntiAffinity(in *api.PodAntiAffinity, out *v1.PodAntiAffinity, s conversion.Scope) error { - out.RequiredDuringSchedulingIgnoredDuringExecution = *(*[]v1.PodAffinityTerm)(unsafe.Pointer(&in.RequiredDuringSchedulingIgnoredDuringExecution)) - out.PreferredDuringSchedulingIgnoredDuringExecution = *(*[]v1.WeightedPodAffinityTerm)(unsafe.Pointer(&in.PreferredDuringSchedulingIgnoredDuringExecution)) - return nil -} - -// Convert_api_PodAntiAffinity_To_v1_PodAntiAffinity is an autogenerated conversion function. -func Convert_api_PodAntiAffinity_To_v1_PodAntiAffinity(in *api.PodAntiAffinity, out *v1.PodAntiAffinity, s conversion.Scope) error { - return autoConvert_api_PodAntiAffinity_To_v1_PodAntiAffinity(in, out, s) -} - -func autoConvert_v1_PodAttachOptions_To_api_PodAttachOptions(in *v1.PodAttachOptions, out *api.PodAttachOptions, s conversion.Scope) error { - out.Stdin = in.Stdin - out.Stdout = in.Stdout - out.Stderr = in.Stderr - out.TTY = in.TTY - out.Container = in.Container - return nil -} - -// Convert_v1_PodAttachOptions_To_api_PodAttachOptions is an autogenerated conversion function. -func Convert_v1_PodAttachOptions_To_api_PodAttachOptions(in *v1.PodAttachOptions, out *api.PodAttachOptions, s conversion.Scope) error { - return autoConvert_v1_PodAttachOptions_To_api_PodAttachOptions(in, out, s) -} - -func autoConvert_api_PodAttachOptions_To_v1_PodAttachOptions(in *api.PodAttachOptions, out *v1.PodAttachOptions, s conversion.Scope) error { - out.Stdin = in.Stdin - out.Stdout = in.Stdout - out.Stderr = in.Stderr - out.TTY = in.TTY - out.Container = in.Container - return nil -} - -// Convert_api_PodAttachOptions_To_v1_PodAttachOptions is an autogenerated conversion function. -func Convert_api_PodAttachOptions_To_v1_PodAttachOptions(in *api.PodAttachOptions, out *v1.PodAttachOptions, s conversion.Scope) error { - return autoConvert_api_PodAttachOptions_To_v1_PodAttachOptions(in, out, s) -} - -func autoConvert_v1_PodCondition_To_api_PodCondition(in *v1.PodCondition, out *api.PodCondition, s conversion.Scope) error { - out.Type = api.PodConditionType(in.Type) - out.Status = api.ConditionStatus(in.Status) - out.LastProbeTime = in.LastProbeTime - out.LastTransitionTime = in.LastTransitionTime - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -// Convert_v1_PodCondition_To_api_PodCondition is an autogenerated conversion function. -func Convert_v1_PodCondition_To_api_PodCondition(in *v1.PodCondition, out *api.PodCondition, s conversion.Scope) error { - return autoConvert_v1_PodCondition_To_api_PodCondition(in, out, s) -} - -func autoConvert_api_PodCondition_To_v1_PodCondition(in *api.PodCondition, out *v1.PodCondition, s conversion.Scope) error { - out.Type = v1.PodConditionType(in.Type) - out.Status = v1.ConditionStatus(in.Status) - out.LastProbeTime = in.LastProbeTime - out.LastTransitionTime = in.LastTransitionTime - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -// Convert_api_PodCondition_To_v1_PodCondition is an autogenerated conversion function. -func Convert_api_PodCondition_To_v1_PodCondition(in *api.PodCondition, out *v1.PodCondition, s conversion.Scope) error { - return autoConvert_api_PodCondition_To_v1_PodCondition(in, out, s) -} - -func autoConvert_v1_PodExecOptions_To_api_PodExecOptions(in *v1.PodExecOptions, out *api.PodExecOptions, s conversion.Scope) error { - out.Stdin = in.Stdin - out.Stdout = in.Stdout - out.Stderr = in.Stderr - out.TTY = in.TTY - out.Container = in.Container - out.Command = *(*[]string)(unsafe.Pointer(&in.Command)) - return nil -} - -// Convert_v1_PodExecOptions_To_api_PodExecOptions is an autogenerated conversion function. -func Convert_v1_PodExecOptions_To_api_PodExecOptions(in *v1.PodExecOptions, out *api.PodExecOptions, s conversion.Scope) error { - return autoConvert_v1_PodExecOptions_To_api_PodExecOptions(in, out, s) -} - -func autoConvert_api_PodExecOptions_To_v1_PodExecOptions(in *api.PodExecOptions, out *v1.PodExecOptions, s conversion.Scope) error { - out.Stdin = in.Stdin - out.Stdout = in.Stdout - out.Stderr = in.Stderr - out.TTY = in.TTY - out.Container = in.Container - out.Command = *(*[]string)(unsafe.Pointer(&in.Command)) - return nil -} - -// Convert_api_PodExecOptions_To_v1_PodExecOptions is an autogenerated conversion function. -func Convert_api_PodExecOptions_To_v1_PodExecOptions(in *api.PodExecOptions, out *v1.PodExecOptions, s conversion.Scope) error { - return autoConvert_api_PodExecOptions_To_v1_PodExecOptions(in, out, s) -} - -func autoConvert_v1_PodList_To_api_PodList(in *v1.PodList, out *api.PodList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]api.Pod, len(*in)) - for i := range *in { - if err := Convert_v1_Pod_To_api_Pod(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v1_PodList_To_api_PodList is an autogenerated conversion function. -func Convert_v1_PodList_To_api_PodList(in *v1.PodList, out *api.PodList, s conversion.Scope) error { - return autoConvert_v1_PodList_To_api_PodList(in, out, s) -} - -func autoConvert_api_PodList_To_v1_PodList(in *api.PodList, out *v1.PodList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]v1.Pod, len(*in)) - for i := range *in { - if err := Convert_api_Pod_To_v1_Pod(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_api_PodList_To_v1_PodList is an autogenerated conversion function. -func Convert_api_PodList_To_v1_PodList(in *api.PodList, out *v1.PodList, s conversion.Scope) error { - return autoConvert_api_PodList_To_v1_PodList(in, out, s) -} - -func autoConvert_v1_PodLogOptions_To_api_PodLogOptions(in *v1.PodLogOptions, out *api.PodLogOptions, s conversion.Scope) error { - out.Container = in.Container - out.Follow = in.Follow - out.Previous = in.Previous - out.SinceSeconds = (*int64)(unsafe.Pointer(in.SinceSeconds)) - out.SinceTime = (*meta_v1.Time)(unsafe.Pointer(in.SinceTime)) - out.Timestamps = in.Timestamps - out.TailLines = (*int64)(unsafe.Pointer(in.TailLines)) - out.LimitBytes = (*int64)(unsafe.Pointer(in.LimitBytes)) - return nil -} - -// Convert_v1_PodLogOptions_To_api_PodLogOptions is an autogenerated conversion function. -func Convert_v1_PodLogOptions_To_api_PodLogOptions(in *v1.PodLogOptions, out *api.PodLogOptions, s conversion.Scope) error { - return autoConvert_v1_PodLogOptions_To_api_PodLogOptions(in, out, s) -} - -func autoConvert_api_PodLogOptions_To_v1_PodLogOptions(in *api.PodLogOptions, out *v1.PodLogOptions, s conversion.Scope) error { - out.Container = in.Container - out.Follow = in.Follow - out.Previous = in.Previous - out.SinceSeconds = (*int64)(unsafe.Pointer(in.SinceSeconds)) - out.SinceTime = (*meta_v1.Time)(unsafe.Pointer(in.SinceTime)) - out.Timestamps = in.Timestamps - out.TailLines = (*int64)(unsafe.Pointer(in.TailLines)) - out.LimitBytes = (*int64)(unsafe.Pointer(in.LimitBytes)) - return nil -} - -// Convert_api_PodLogOptions_To_v1_PodLogOptions is an autogenerated conversion function. -func Convert_api_PodLogOptions_To_v1_PodLogOptions(in *api.PodLogOptions, out *v1.PodLogOptions, s conversion.Scope) error { - return autoConvert_api_PodLogOptions_To_v1_PodLogOptions(in, out, s) -} - -func autoConvert_v1_PodPortForwardOptions_To_api_PodPortForwardOptions(in *v1.PodPortForwardOptions, out *api.PodPortForwardOptions, s conversion.Scope) error { - out.Ports = *(*[]int32)(unsafe.Pointer(&in.Ports)) - return nil -} - -// Convert_v1_PodPortForwardOptions_To_api_PodPortForwardOptions is an autogenerated conversion function. -func Convert_v1_PodPortForwardOptions_To_api_PodPortForwardOptions(in *v1.PodPortForwardOptions, out *api.PodPortForwardOptions, s conversion.Scope) error { - return autoConvert_v1_PodPortForwardOptions_To_api_PodPortForwardOptions(in, out, s) -} - -func autoConvert_api_PodPortForwardOptions_To_v1_PodPortForwardOptions(in *api.PodPortForwardOptions, out *v1.PodPortForwardOptions, s conversion.Scope) error { - out.Ports = *(*[]int32)(unsafe.Pointer(&in.Ports)) - return nil -} - -// Convert_api_PodPortForwardOptions_To_v1_PodPortForwardOptions is an autogenerated conversion function. -func Convert_api_PodPortForwardOptions_To_v1_PodPortForwardOptions(in *api.PodPortForwardOptions, out *v1.PodPortForwardOptions, s conversion.Scope) error { - return autoConvert_api_PodPortForwardOptions_To_v1_PodPortForwardOptions(in, out, s) -} - -func autoConvert_v1_PodProxyOptions_To_api_PodProxyOptions(in *v1.PodProxyOptions, out *api.PodProxyOptions, s conversion.Scope) error { - out.Path = in.Path - return nil -} - -// Convert_v1_PodProxyOptions_To_api_PodProxyOptions is an autogenerated conversion function. -func Convert_v1_PodProxyOptions_To_api_PodProxyOptions(in *v1.PodProxyOptions, out *api.PodProxyOptions, s conversion.Scope) error { - return autoConvert_v1_PodProxyOptions_To_api_PodProxyOptions(in, out, s) -} - -func autoConvert_api_PodProxyOptions_To_v1_PodProxyOptions(in *api.PodProxyOptions, out *v1.PodProxyOptions, s conversion.Scope) error { - out.Path = in.Path - return nil -} - -// Convert_api_PodProxyOptions_To_v1_PodProxyOptions is an autogenerated conversion function. -func Convert_api_PodProxyOptions_To_v1_PodProxyOptions(in *api.PodProxyOptions, out *v1.PodProxyOptions, s conversion.Scope) error { - return autoConvert_api_PodProxyOptions_To_v1_PodProxyOptions(in, out, s) -} - -func autoConvert_v1_PodSecurityContext_To_api_PodSecurityContext(in *v1.PodSecurityContext, out *api.PodSecurityContext, s conversion.Scope) error { - out.SELinuxOptions = (*api.SELinuxOptions)(unsafe.Pointer(in.SELinuxOptions)) - out.RunAsUser = (*int64)(unsafe.Pointer(in.RunAsUser)) - out.RunAsNonRoot = (*bool)(unsafe.Pointer(in.RunAsNonRoot)) - out.SupplementalGroups = *(*[]int64)(unsafe.Pointer(&in.SupplementalGroups)) - out.FSGroup = (*int64)(unsafe.Pointer(in.FSGroup)) - return nil -} - -func autoConvert_api_PodSecurityContext_To_v1_PodSecurityContext(in *api.PodSecurityContext, out *v1.PodSecurityContext, s conversion.Scope) error { - // INFO: in.HostNetwork opted out of conversion generation - // INFO: in.HostPID opted out of conversion generation - // INFO: in.HostIPC opted out of conversion generation - out.SELinuxOptions = (*v1.SELinuxOptions)(unsafe.Pointer(in.SELinuxOptions)) - out.RunAsUser = (*int64)(unsafe.Pointer(in.RunAsUser)) - out.RunAsNonRoot = (*bool)(unsafe.Pointer(in.RunAsNonRoot)) - out.SupplementalGroups = *(*[]int64)(unsafe.Pointer(&in.SupplementalGroups)) - out.FSGroup = (*int64)(unsafe.Pointer(in.FSGroup)) - return nil -} - -func autoConvert_v1_PodSignature_To_api_PodSignature(in *v1.PodSignature, out *api.PodSignature, s conversion.Scope) error { - out.PodController = (*meta_v1.OwnerReference)(unsafe.Pointer(in.PodController)) - return nil -} - -// Convert_v1_PodSignature_To_api_PodSignature is an autogenerated conversion function. -func Convert_v1_PodSignature_To_api_PodSignature(in *v1.PodSignature, out *api.PodSignature, s conversion.Scope) error { - return autoConvert_v1_PodSignature_To_api_PodSignature(in, out, s) -} - -func autoConvert_api_PodSignature_To_v1_PodSignature(in *api.PodSignature, out *v1.PodSignature, s conversion.Scope) error { - out.PodController = (*meta_v1.OwnerReference)(unsafe.Pointer(in.PodController)) - return nil -} - -// Convert_api_PodSignature_To_v1_PodSignature is an autogenerated conversion function. -func Convert_api_PodSignature_To_v1_PodSignature(in *api.PodSignature, out *v1.PodSignature, s conversion.Scope) error { - return autoConvert_api_PodSignature_To_v1_PodSignature(in, out, s) -} - -func autoConvert_v1_PodSpec_To_api_PodSpec(in *v1.PodSpec, out *api.PodSpec, s conversion.Scope) error { - if in.Volumes != nil { - in, out := &in.Volumes, &out.Volumes - *out = make([]api.Volume, len(*in)) - for i := range *in { - if err := Convert_v1_Volume_To_api_Volume(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Volumes = nil - } - if in.InitContainers != nil { - in, out := &in.InitContainers, &out.InitContainers - *out = make([]api.Container, len(*in)) - for i := range *in { - if err := Convert_v1_Container_To_api_Container(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.InitContainers = nil - } - if in.Containers != nil { - in, out := &in.Containers, &out.Containers - *out = make([]api.Container, len(*in)) - for i := range *in { - if err := Convert_v1_Container_To_api_Container(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Containers = nil - } - out.RestartPolicy = api.RestartPolicy(in.RestartPolicy) - out.TerminationGracePeriodSeconds = (*int64)(unsafe.Pointer(in.TerminationGracePeriodSeconds)) - out.ActiveDeadlineSeconds = (*int64)(unsafe.Pointer(in.ActiveDeadlineSeconds)) - out.DNSPolicy = api.DNSPolicy(in.DNSPolicy) - out.NodeSelector = *(*map[string]string)(unsafe.Pointer(&in.NodeSelector)) - out.ServiceAccountName = in.ServiceAccountName - // INFO: in.DeprecatedServiceAccount opted out of conversion generation - out.AutomountServiceAccountToken = (*bool)(unsafe.Pointer(in.AutomountServiceAccountToken)) - out.NodeName = in.NodeName - // INFO: in.HostNetwork opted out of conversion generation - // INFO: in.HostPID opted out of conversion generation - // INFO: in.HostIPC opted out of conversion generation - if in.SecurityContext != nil { - in, out := &in.SecurityContext, &out.SecurityContext - *out = new(api.PodSecurityContext) - if err := Convert_v1_PodSecurityContext_To_api_PodSecurityContext(*in, *out, s); err != nil { - return err - } - } else { - out.SecurityContext = nil - } - out.ImagePullSecrets = *(*[]api.LocalObjectReference)(unsafe.Pointer(&in.ImagePullSecrets)) - out.Hostname = in.Hostname - out.Subdomain = in.Subdomain - out.Affinity = (*api.Affinity)(unsafe.Pointer(in.Affinity)) - out.SchedulerName = in.SchedulerName - out.Tolerations = *(*[]api.Toleration)(unsafe.Pointer(&in.Tolerations)) - out.HostAliases = *(*[]api.HostAlias)(unsafe.Pointer(&in.HostAliases)) - out.PriorityClassName = in.PriorityClassName - out.Priority = (*int32)(unsafe.Pointer(in.Priority)) - return nil -} - -func autoConvert_api_PodSpec_To_v1_PodSpec(in *api.PodSpec, out *v1.PodSpec, s conversion.Scope) error { - if in.Volumes != nil { - in, out := &in.Volumes, &out.Volumes - *out = make([]v1.Volume, len(*in)) - for i := range *in { - if err := Convert_api_Volume_To_v1_Volume(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Volumes = nil - } - if in.InitContainers != nil { - in, out := &in.InitContainers, &out.InitContainers - *out = make([]v1.Container, len(*in)) - for i := range *in { - if err := Convert_api_Container_To_v1_Container(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.InitContainers = nil - } - if in.Containers != nil { - in, out := &in.Containers, &out.Containers - *out = make([]v1.Container, len(*in)) - for i := range *in { - if err := Convert_api_Container_To_v1_Container(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Containers = nil - } - out.RestartPolicy = v1.RestartPolicy(in.RestartPolicy) - out.TerminationGracePeriodSeconds = (*int64)(unsafe.Pointer(in.TerminationGracePeriodSeconds)) - out.ActiveDeadlineSeconds = (*int64)(unsafe.Pointer(in.ActiveDeadlineSeconds)) - out.DNSPolicy = v1.DNSPolicy(in.DNSPolicy) - out.NodeSelector = *(*map[string]string)(unsafe.Pointer(&in.NodeSelector)) - out.ServiceAccountName = in.ServiceAccountName - out.AutomountServiceAccountToken = (*bool)(unsafe.Pointer(in.AutomountServiceAccountToken)) - out.NodeName = in.NodeName - if in.SecurityContext != nil { - in, out := &in.SecurityContext, &out.SecurityContext - *out = new(v1.PodSecurityContext) - if err := Convert_api_PodSecurityContext_To_v1_PodSecurityContext(*in, *out, s); err != nil { - return err - } - } else { - out.SecurityContext = nil - } - out.ImagePullSecrets = *(*[]v1.LocalObjectReference)(unsafe.Pointer(&in.ImagePullSecrets)) - out.Hostname = in.Hostname - out.Subdomain = in.Subdomain - out.Affinity = (*v1.Affinity)(unsafe.Pointer(in.Affinity)) - out.SchedulerName = in.SchedulerName - out.Tolerations = *(*[]v1.Toleration)(unsafe.Pointer(&in.Tolerations)) - out.HostAliases = *(*[]v1.HostAlias)(unsafe.Pointer(&in.HostAliases)) - out.PriorityClassName = in.PriorityClassName - out.Priority = (*int32)(unsafe.Pointer(in.Priority)) - return nil -} - -func autoConvert_v1_PodStatus_To_api_PodStatus(in *v1.PodStatus, out *api.PodStatus, s conversion.Scope) error { - out.Phase = api.PodPhase(in.Phase) - out.Conditions = *(*[]api.PodCondition)(unsafe.Pointer(&in.Conditions)) - out.Message = in.Message - out.Reason = in.Reason - out.HostIP = in.HostIP - out.PodIP = in.PodIP - out.StartTime = (*meta_v1.Time)(unsafe.Pointer(in.StartTime)) - out.InitContainerStatuses = *(*[]api.ContainerStatus)(unsafe.Pointer(&in.InitContainerStatuses)) - out.ContainerStatuses = *(*[]api.ContainerStatus)(unsafe.Pointer(&in.ContainerStatuses)) - out.QOSClass = api.PodQOSClass(in.QOSClass) - return nil -} - -// Convert_v1_PodStatus_To_api_PodStatus is an autogenerated conversion function. -func Convert_v1_PodStatus_To_api_PodStatus(in *v1.PodStatus, out *api.PodStatus, s conversion.Scope) error { - return autoConvert_v1_PodStatus_To_api_PodStatus(in, out, s) -} - -func autoConvert_api_PodStatus_To_v1_PodStatus(in *api.PodStatus, out *v1.PodStatus, s conversion.Scope) error { - out.Phase = v1.PodPhase(in.Phase) - out.Conditions = *(*[]v1.PodCondition)(unsafe.Pointer(&in.Conditions)) - out.Message = in.Message - out.Reason = in.Reason - out.HostIP = in.HostIP - out.PodIP = in.PodIP - out.StartTime = (*meta_v1.Time)(unsafe.Pointer(in.StartTime)) - out.QOSClass = v1.PodQOSClass(in.QOSClass) - out.InitContainerStatuses = *(*[]v1.ContainerStatus)(unsafe.Pointer(&in.InitContainerStatuses)) - out.ContainerStatuses = *(*[]v1.ContainerStatus)(unsafe.Pointer(&in.ContainerStatuses)) - return nil -} - -// Convert_api_PodStatus_To_v1_PodStatus is an autogenerated conversion function. -func Convert_api_PodStatus_To_v1_PodStatus(in *api.PodStatus, out *v1.PodStatus, s conversion.Scope) error { - return autoConvert_api_PodStatus_To_v1_PodStatus(in, out, s) -} - -func autoConvert_v1_PodStatusResult_To_api_PodStatusResult(in *v1.PodStatusResult, out *api.PodStatusResult, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1_PodStatus_To_api_PodStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1_PodStatusResult_To_api_PodStatusResult is an autogenerated conversion function. -func Convert_v1_PodStatusResult_To_api_PodStatusResult(in *v1.PodStatusResult, out *api.PodStatusResult, s conversion.Scope) error { - return autoConvert_v1_PodStatusResult_To_api_PodStatusResult(in, out, s) -} - -func autoConvert_api_PodStatusResult_To_v1_PodStatusResult(in *api.PodStatusResult, out *v1.PodStatusResult, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_api_PodStatus_To_v1_PodStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_api_PodStatusResult_To_v1_PodStatusResult is an autogenerated conversion function. -func Convert_api_PodStatusResult_To_v1_PodStatusResult(in *api.PodStatusResult, out *v1.PodStatusResult, s conversion.Scope) error { - return autoConvert_api_PodStatusResult_To_v1_PodStatusResult(in, out, s) -} - -func autoConvert_v1_PodTemplate_To_api_PodTemplate(in *v1.PodTemplate, out *api.PodTemplate, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { - return err - } - return nil -} - -// Convert_v1_PodTemplate_To_api_PodTemplate is an autogenerated conversion function. -func Convert_v1_PodTemplate_To_api_PodTemplate(in *v1.PodTemplate, out *api.PodTemplate, s conversion.Scope) error { - return autoConvert_v1_PodTemplate_To_api_PodTemplate(in, out, s) -} - -func autoConvert_api_PodTemplate_To_v1_PodTemplate(in *api.PodTemplate, out *v1.PodTemplate, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { - return err - } - return nil -} - -// Convert_api_PodTemplate_To_v1_PodTemplate is an autogenerated conversion function. -func Convert_api_PodTemplate_To_v1_PodTemplate(in *api.PodTemplate, out *v1.PodTemplate, s conversion.Scope) error { - return autoConvert_api_PodTemplate_To_v1_PodTemplate(in, out, s) -} - -func autoConvert_v1_PodTemplateList_To_api_PodTemplateList(in *v1.PodTemplateList, out *api.PodTemplateList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]api.PodTemplate, len(*in)) - for i := range *in { - if err := Convert_v1_PodTemplate_To_api_PodTemplate(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v1_PodTemplateList_To_api_PodTemplateList is an autogenerated conversion function. -func Convert_v1_PodTemplateList_To_api_PodTemplateList(in *v1.PodTemplateList, out *api.PodTemplateList, s conversion.Scope) error { - return autoConvert_v1_PodTemplateList_To_api_PodTemplateList(in, out, s) -} - -func autoConvert_api_PodTemplateList_To_v1_PodTemplateList(in *api.PodTemplateList, out *v1.PodTemplateList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]v1.PodTemplate, len(*in)) - for i := range *in { - if err := Convert_api_PodTemplate_To_v1_PodTemplate(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_api_PodTemplateList_To_v1_PodTemplateList is an autogenerated conversion function. -func Convert_api_PodTemplateList_To_v1_PodTemplateList(in *api.PodTemplateList, out *v1.PodTemplateList, s conversion.Scope) error { - return autoConvert_api_PodTemplateList_To_v1_PodTemplateList(in, out, s) -} - -func autoConvert_v1_PodTemplateSpec_To_api_PodTemplateSpec(in *v1.PodTemplateSpec, out *api.PodTemplateSpec, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1_PodSpec_To_api_PodSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - return nil -} - -func autoConvert_api_PodTemplateSpec_To_v1_PodTemplateSpec(in *api.PodTemplateSpec, out *v1.PodTemplateSpec, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_api_PodSpec_To_v1_PodSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - return nil -} - -func autoConvert_v1_PortworxVolumeSource_To_api_PortworxVolumeSource(in *v1.PortworxVolumeSource, out *api.PortworxVolumeSource, s conversion.Scope) error { - out.VolumeID = in.VolumeID - out.FSType = in.FSType - out.ReadOnly = in.ReadOnly - return nil -} - -// Convert_v1_PortworxVolumeSource_To_api_PortworxVolumeSource is an autogenerated conversion function. -func Convert_v1_PortworxVolumeSource_To_api_PortworxVolumeSource(in *v1.PortworxVolumeSource, out *api.PortworxVolumeSource, s conversion.Scope) error { - return autoConvert_v1_PortworxVolumeSource_To_api_PortworxVolumeSource(in, out, s) -} - -func autoConvert_api_PortworxVolumeSource_To_v1_PortworxVolumeSource(in *api.PortworxVolumeSource, out *v1.PortworxVolumeSource, s conversion.Scope) error { - out.VolumeID = in.VolumeID - out.FSType = in.FSType - out.ReadOnly = in.ReadOnly - return nil -} - -// Convert_api_PortworxVolumeSource_To_v1_PortworxVolumeSource is an autogenerated conversion function. -func Convert_api_PortworxVolumeSource_To_v1_PortworxVolumeSource(in *api.PortworxVolumeSource, out *v1.PortworxVolumeSource, s conversion.Scope) error { - return autoConvert_api_PortworxVolumeSource_To_v1_PortworxVolumeSource(in, out, s) -} - -func autoConvert_v1_Preconditions_To_api_Preconditions(in *v1.Preconditions, out *api.Preconditions, s conversion.Scope) error { - out.UID = (*types.UID)(unsafe.Pointer(in.UID)) - return nil -} - -// Convert_v1_Preconditions_To_api_Preconditions is an autogenerated conversion function. -func Convert_v1_Preconditions_To_api_Preconditions(in *v1.Preconditions, out *api.Preconditions, s conversion.Scope) error { - return autoConvert_v1_Preconditions_To_api_Preconditions(in, out, s) -} - -func autoConvert_api_Preconditions_To_v1_Preconditions(in *api.Preconditions, out *v1.Preconditions, s conversion.Scope) error { - out.UID = (*types.UID)(unsafe.Pointer(in.UID)) - return nil -} - -// Convert_api_Preconditions_To_v1_Preconditions is an autogenerated conversion function. -func Convert_api_Preconditions_To_v1_Preconditions(in *api.Preconditions, out *v1.Preconditions, s conversion.Scope) error { - return autoConvert_api_Preconditions_To_v1_Preconditions(in, out, s) -} - -func autoConvert_v1_PreferAvoidPodsEntry_To_api_PreferAvoidPodsEntry(in *v1.PreferAvoidPodsEntry, out *api.PreferAvoidPodsEntry, s conversion.Scope) error { - if err := Convert_v1_PodSignature_To_api_PodSignature(&in.PodSignature, &out.PodSignature, s); err != nil { - return err - } - out.EvictionTime = in.EvictionTime - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -// Convert_v1_PreferAvoidPodsEntry_To_api_PreferAvoidPodsEntry is an autogenerated conversion function. -func Convert_v1_PreferAvoidPodsEntry_To_api_PreferAvoidPodsEntry(in *v1.PreferAvoidPodsEntry, out *api.PreferAvoidPodsEntry, s conversion.Scope) error { - return autoConvert_v1_PreferAvoidPodsEntry_To_api_PreferAvoidPodsEntry(in, out, s) -} - -func autoConvert_api_PreferAvoidPodsEntry_To_v1_PreferAvoidPodsEntry(in *api.PreferAvoidPodsEntry, out *v1.PreferAvoidPodsEntry, s conversion.Scope) error { - if err := Convert_api_PodSignature_To_v1_PodSignature(&in.PodSignature, &out.PodSignature, s); err != nil { - return err - } - out.EvictionTime = in.EvictionTime - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -// Convert_api_PreferAvoidPodsEntry_To_v1_PreferAvoidPodsEntry is an autogenerated conversion function. -func Convert_api_PreferAvoidPodsEntry_To_v1_PreferAvoidPodsEntry(in *api.PreferAvoidPodsEntry, out *v1.PreferAvoidPodsEntry, s conversion.Scope) error { - return autoConvert_api_PreferAvoidPodsEntry_To_v1_PreferAvoidPodsEntry(in, out, s) -} - -func autoConvert_v1_PreferredSchedulingTerm_To_api_PreferredSchedulingTerm(in *v1.PreferredSchedulingTerm, out *api.PreferredSchedulingTerm, s conversion.Scope) error { - out.Weight = in.Weight - if err := Convert_v1_NodeSelectorTerm_To_api_NodeSelectorTerm(&in.Preference, &out.Preference, s); err != nil { - return err - } - return nil -} - -// Convert_v1_PreferredSchedulingTerm_To_api_PreferredSchedulingTerm is an autogenerated conversion function. -func Convert_v1_PreferredSchedulingTerm_To_api_PreferredSchedulingTerm(in *v1.PreferredSchedulingTerm, out *api.PreferredSchedulingTerm, s conversion.Scope) error { - return autoConvert_v1_PreferredSchedulingTerm_To_api_PreferredSchedulingTerm(in, out, s) -} - -func autoConvert_api_PreferredSchedulingTerm_To_v1_PreferredSchedulingTerm(in *api.PreferredSchedulingTerm, out *v1.PreferredSchedulingTerm, s conversion.Scope) error { - out.Weight = in.Weight - if err := Convert_api_NodeSelectorTerm_To_v1_NodeSelectorTerm(&in.Preference, &out.Preference, s); err != nil { - return err - } - return nil -} - -// Convert_api_PreferredSchedulingTerm_To_v1_PreferredSchedulingTerm is an autogenerated conversion function. -func Convert_api_PreferredSchedulingTerm_To_v1_PreferredSchedulingTerm(in *api.PreferredSchedulingTerm, out *v1.PreferredSchedulingTerm, s conversion.Scope) error { - return autoConvert_api_PreferredSchedulingTerm_To_v1_PreferredSchedulingTerm(in, out, s) -} - -func autoConvert_v1_Probe_To_api_Probe(in *v1.Probe, out *api.Probe, s conversion.Scope) error { - if err := Convert_v1_Handler_To_api_Handler(&in.Handler, &out.Handler, s); err != nil { - return err - } - out.InitialDelaySeconds = in.InitialDelaySeconds - out.TimeoutSeconds = in.TimeoutSeconds - out.PeriodSeconds = in.PeriodSeconds - out.SuccessThreshold = in.SuccessThreshold - out.FailureThreshold = in.FailureThreshold - return nil -} - -// Convert_v1_Probe_To_api_Probe is an autogenerated conversion function. -func Convert_v1_Probe_To_api_Probe(in *v1.Probe, out *api.Probe, s conversion.Scope) error { - return autoConvert_v1_Probe_To_api_Probe(in, out, s) -} - -func autoConvert_api_Probe_To_v1_Probe(in *api.Probe, out *v1.Probe, s conversion.Scope) error { - if err := Convert_api_Handler_To_v1_Handler(&in.Handler, &out.Handler, s); err != nil { - return err - } - out.InitialDelaySeconds = in.InitialDelaySeconds - out.TimeoutSeconds = in.TimeoutSeconds - out.PeriodSeconds = in.PeriodSeconds - out.SuccessThreshold = in.SuccessThreshold - out.FailureThreshold = in.FailureThreshold - return nil -} - -// Convert_api_Probe_To_v1_Probe is an autogenerated conversion function. -func Convert_api_Probe_To_v1_Probe(in *api.Probe, out *v1.Probe, s conversion.Scope) error { - return autoConvert_api_Probe_To_v1_Probe(in, out, s) -} - -func autoConvert_v1_ProjectedVolumeSource_To_api_ProjectedVolumeSource(in *v1.ProjectedVolumeSource, out *api.ProjectedVolumeSource, s conversion.Scope) error { - out.Sources = *(*[]api.VolumeProjection)(unsafe.Pointer(&in.Sources)) - out.DefaultMode = (*int32)(unsafe.Pointer(in.DefaultMode)) - return nil -} - -// Convert_v1_ProjectedVolumeSource_To_api_ProjectedVolumeSource is an autogenerated conversion function. -func Convert_v1_ProjectedVolumeSource_To_api_ProjectedVolumeSource(in *v1.ProjectedVolumeSource, out *api.ProjectedVolumeSource, s conversion.Scope) error { - return autoConvert_v1_ProjectedVolumeSource_To_api_ProjectedVolumeSource(in, out, s) -} - -func autoConvert_api_ProjectedVolumeSource_To_v1_ProjectedVolumeSource(in *api.ProjectedVolumeSource, out *v1.ProjectedVolumeSource, s conversion.Scope) error { - out.Sources = *(*[]v1.VolumeProjection)(unsafe.Pointer(&in.Sources)) - out.DefaultMode = (*int32)(unsafe.Pointer(in.DefaultMode)) - return nil -} - -// Convert_api_ProjectedVolumeSource_To_v1_ProjectedVolumeSource is an autogenerated conversion function. -func Convert_api_ProjectedVolumeSource_To_v1_ProjectedVolumeSource(in *api.ProjectedVolumeSource, out *v1.ProjectedVolumeSource, s conversion.Scope) error { - return autoConvert_api_ProjectedVolumeSource_To_v1_ProjectedVolumeSource(in, out, s) -} - -func autoConvert_v1_QuobyteVolumeSource_To_api_QuobyteVolumeSource(in *v1.QuobyteVolumeSource, out *api.QuobyteVolumeSource, s conversion.Scope) error { - out.Registry = in.Registry - out.Volume = in.Volume - out.ReadOnly = in.ReadOnly - out.User = in.User - out.Group = in.Group - return nil -} - -// Convert_v1_QuobyteVolumeSource_To_api_QuobyteVolumeSource is an autogenerated conversion function. -func Convert_v1_QuobyteVolumeSource_To_api_QuobyteVolumeSource(in *v1.QuobyteVolumeSource, out *api.QuobyteVolumeSource, s conversion.Scope) error { - return autoConvert_v1_QuobyteVolumeSource_To_api_QuobyteVolumeSource(in, out, s) -} - -func autoConvert_api_QuobyteVolumeSource_To_v1_QuobyteVolumeSource(in *api.QuobyteVolumeSource, out *v1.QuobyteVolumeSource, s conversion.Scope) error { - out.Registry = in.Registry - out.Volume = in.Volume - out.ReadOnly = in.ReadOnly - out.User = in.User - out.Group = in.Group - return nil -} - -// Convert_api_QuobyteVolumeSource_To_v1_QuobyteVolumeSource is an autogenerated conversion function. -func Convert_api_QuobyteVolumeSource_To_v1_QuobyteVolumeSource(in *api.QuobyteVolumeSource, out *v1.QuobyteVolumeSource, s conversion.Scope) error { - return autoConvert_api_QuobyteVolumeSource_To_v1_QuobyteVolumeSource(in, out, s) -} - -func autoConvert_v1_RBDVolumeSource_To_api_RBDVolumeSource(in *v1.RBDVolumeSource, out *api.RBDVolumeSource, s conversion.Scope) error { - out.CephMonitors = *(*[]string)(unsafe.Pointer(&in.CephMonitors)) - out.RBDImage = in.RBDImage - out.FSType = in.FSType - out.RBDPool = in.RBDPool - out.RadosUser = in.RadosUser - out.Keyring = in.Keyring - out.SecretRef = (*api.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) - out.ReadOnly = in.ReadOnly - return nil -} - -// Convert_v1_RBDVolumeSource_To_api_RBDVolumeSource is an autogenerated conversion function. -func Convert_v1_RBDVolumeSource_To_api_RBDVolumeSource(in *v1.RBDVolumeSource, out *api.RBDVolumeSource, s conversion.Scope) error { - return autoConvert_v1_RBDVolumeSource_To_api_RBDVolumeSource(in, out, s) -} - -func autoConvert_api_RBDVolumeSource_To_v1_RBDVolumeSource(in *api.RBDVolumeSource, out *v1.RBDVolumeSource, s conversion.Scope) error { - out.CephMonitors = *(*[]string)(unsafe.Pointer(&in.CephMonitors)) - out.RBDImage = in.RBDImage - out.FSType = in.FSType - out.RBDPool = in.RBDPool - out.RadosUser = in.RadosUser - out.Keyring = in.Keyring - out.SecretRef = (*v1.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) - out.ReadOnly = in.ReadOnly - return nil -} - -// Convert_api_RBDVolumeSource_To_v1_RBDVolumeSource is an autogenerated conversion function. -func Convert_api_RBDVolumeSource_To_v1_RBDVolumeSource(in *api.RBDVolumeSource, out *v1.RBDVolumeSource, s conversion.Scope) error { - return autoConvert_api_RBDVolumeSource_To_v1_RBDVolumeSource(in, out, s) -} - -func autoConvert_v1_RangeAllocation_To_api_RangeAllocation(in *v1.RangeAllocation, out *api.RangeAllocation, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Range = in.Range - out.Data = *(*[]byte)(unsafe.Pointer(&in.Data)) - return nil -} - -// Convert_v1_RangeAllocation_To_api_RangeAllocation is an autogenerated conversion function. -func Convert_v1_RangeAllocation_To_api_RangeAllocation(in *v1.RangeAllocation, out *api.RangeAllocation, s conversion.Scope) error { - return autoConvert_v1_RangeAllocation_To_api_RangeAllocation(in, out, s) -} - -func autoConvert_api_RangeAllocation_To_v1_RangeAllocation(in *api.RangeAllocation, out *v1.RangeAllocation, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Range = in.Range - out.Data = *(*[]byte)(unsafe.Pointer(&in.Data)) - return nil -} - -// Convert_api_RangeAllocation_To_v1_RangeAllocation is an autogenerated conversion function. -func Convert_api_RangeAllocation_To_v1_RangeAllocation(in *api.RangeAllocation, out *v1.RangeAllocation, s conversion.Scope) error { - return autoConvert_api_RangeAllocation_To_v1_RangeAllocation(in, out, s) -} - -func autoConvert_v1_ReplicationController_To_api_ReplicationController(in *v1.ReplicationController, out *api.ReplicationController, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1_ReplicationControllerSpec_To_api_ReplicationControllerSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1_ReplicationControllerStatus_To_api_ReplicationControllerStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1_ReplicationController_To_api_ReplicationController is an autogenerated conversion function. -func Convert_v1_ReplicationController_To_api_ReplicationController(in *v1.ReplicationController, out *api.ReplicationController, s conversion.Scope) error { - return autoConvert_v1_ReplicationController_To_api_ReplicationController(in, out, s) -} - -func autoConvert_api_ReplicationController_To_v1_ReplicationController(in *api.ReplicationController, out *v1.ReplicationController, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_api_ReplicationControllerSpec_To_v1_ReplicationControllerSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_api_ReplicationControllerStatus_To_v1_ReplicationControllerStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_api_ReplicationController_To_v1_ReplicationController is an autogenerated conversion function. -func Convert_api_ReplicationController_To_v1_ReplicationController(in *api.ReplicationController, out *v1.ReplicationController, s conversion.Scope) error { - return autoConvert_api_ReplicationController_To_v1_ReplicationController(in, out, s) -} - -func autoConvert_v1_ReplicationControllerCondition_To_api_ReplicationControllerCondition(in *v1.ReplicationControllerCondition, out *api.ReplicationControllerCondition, s conversion.Scope) error { - out.Type = api.ReplicationControllerConditionType(in.Type) - out.Status = api.ConditionStatus(in.Status) - out.LastTransitionTime = in.LastTransitionTime - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -// Convert_v1_ReplicationControllerCondition_To_api_ReplicationControllerCondition is an autogenerated conversion function. -func Convert_v1_ReplicationControllerCondition_To_api_ReplicationControllerCondition(in *v1.ReplicationControllerCondition, out *api.ReplicationControllerCondition, s conversion.Scope) error { - return autoConvert_v1_ReplicationControllerCondition_To_api_ReplicationControllerCondition(in, out, s) -} - -func autoConvert_api_ReplicationControllerCondition_To_v1_ReplicationControllerCondition(in *api.ReplicationControllerCondition, out *v1.ReplicationControllerCondition, s conversion.Scope) error { - out.Type = v1.ReplicationControllerConditionType(in.Type) - out.Status = v1.ConditionStatus(in.Status) - out.LastTransitionTime = in.LastTransitionTime - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -// Convert_api_ReplicationControllerCondition_To_v1_ReplicationControllerCondition is an autogenerated conversion function. -func Convert_api_ReplicationControllerCondition_To_v1_ReplicationControllerCondition(in *api.ReplicationControllerCondition, out *v1.ReplicationControllerCondition, s conversion.Scope) error { - return autoConvert_api_ReplicationControllerCondition_To_v1_ReplicationControllerCondition(in, out, s) -} - -func autoConvert_v1_ReplicationControllerList_To_api_ReplicationControllerList(in *v1.ReplicationControllerList, out *api.ReplicationControllerList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]api.ReplicationController, len(*in)) - for i := range *in { - if err := Convert_v1_ReplicationController_To_api_ReplicationController(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v1_ReplicationControllerList_To_api_ReplicationControllerList is an autogenerated conversion function. -func Convert_v1_ReplicationControllerList_To_api_ReplicationControllerList(in *v1.ReplicationControllerList, out *api.ReplicationControllerList, s conversion.Scope) error { - return autoConvert_v1_ReplicationControllerList_To_api_ReplicationControllerList(in, out, s) -} - -func autoConvert_api_ReplicationControllerList_To_v1_ReplicationControllerList(in *api.ReplicationControllerList, out *v1.ReplicationControllerList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]v1.ReplicationController, len(*in)) - for i := range *in { - if err := Convert_api_ReplicationController_To_v1_ReplicationController(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_api_ReplicationControllerList_To_v1_ReplicationControllerList is an autogenerated conversion function. -func Convert_api_ReplicationControllerList_To_v1_ReplicationControllerList(in *api.ReplicationControllerList, out *v1.ReplicationControllerList, s conversion.Scope) error { - return autoConvert_api_ReplicationControllerList_To_v1_ReplicationControllerList(in, out, s) -} - -func autoConvert_v1_ReplicationControllerSpec_To_api_ReplicationControllerSpec(in *v1.ReplicationControllerSpec, out *api.ReplicationControllerSpec, s conversion.Scope) error { - if err := meta_v1.Convert_Pointer_int32_To_int32(&in.Replicas, &out.Replicas, s); err != nil { - return err - } - out.MinReadySeconds = in.MinReadySeconds - out.Selector = *(*map[string]string)(unsafe.Pointer(&in.Selector)) - if in.Template != nil { - in, out := &in.Template, &out.Template - *out = new(api.PodTemplateSpec) - if err := Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(*in, *out, s); err != nil { - return err - } - } else { - out.Template = nil - } - return nil -} - -func autoConvert_api_ReplicationControllerSpec_To_v1_ReplicationControllerSpec(in *api.ReplicationControllerSpec, out *v1.ReplicationControllerSpec, s conversion.Scope) error { - if err := meta_v1.Convert_int32_To_Pointer_int32(&in.Replicas, &out.Replicas, s); err != nil { - return err - } - out.MinReadySeconds = in.MinReadySeconds - out.Selector = *(*map[string]string)(unsafe.Pointer(&in.Selector)) - if in.Template != nil { - in, out := &in.Template, &out.Template - *out = new(v1.PodTemplateSpec) - if err := Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(*in, *out, s); err != nil { - return err - } - } else { - out.Template = nil - } - return nil -} - -func autoConvert_v1_ReplicationControllerStatus_To_api_ReplicationControllerStatus(in *v1.ReplicationControllerStatus, out *api.ReplicationControllerStatus, s conversion.Scope) error { - out.Replicas = in.Replicas - out.FullyLabeledReplicas = in.FullyLabeledReplicas - out.ReadyReplicas = in.ReadyReplicas - out.AvailableReplicas = in.AvailableReplicas - out.ObservedGeneration = in.ObservedGeneration - out.Conditions = *(*[]api.ReplicationControllerCondition)(unsafe.Pointer(&in.Conditions)) - return nil -} - -// Convert_v1_ReplicationControllerStatus_To_api_ReplicationControllerStatus is an autogenerated conversion function. -func Convert_v1_ReplicationControllerStatus_To_api_ReplicationControllerStatus(in *v1.ReplicationControllerStatus, out *api.ReplicationControllerStatus, s conversion.Scope) error { - return autoConvert_v1_ReplicationControllerStatus_To_api_ReplicationControllerStatus(in, out, s) -} - -func autoConvert_api_ReplicationControllerStatus_To_v1_ReplicationControllerStatus(in *api.ReplicationControllerStatus, out *v1.ReplicationControllerStatus, s conversion.Scope) error { - out.Replicas = in.Replicas - out.FullyLabeledReplicas = in.FullyLabeledReplicas - out.ReadyReplicas = in.ReadyReplicas - out.AvailableReplicas = in.AvailableReplicas - out.ObservedGeneration = in.ObservedGeneration - out.Conditions = *(*[]v1.ReplicationControllerCondition)(unsafe.Pointer(&in.Conditions)) - return nil -} - -// Convert_api_ReplicationControllerStatus_To_v1_ReplicationControllerStatus is an autogenerated conversion function. -func Convert_api_ReplicationControllerStatus_To_v1_ReplicationControllerStatus(in *api.ReplicationControllerStatus, out *v1.ReplicationControllerStatus, s conversion.Scope) error { - return autoConvert_api_ReplicationControllerStatus_To_v1_ReplicationControllerStatus(in, out, s) -} - -func autoConvert_v1_ResourceFieldSelector_To_api_ResourceFieldSelector(in *v1.ResourceFieldSelector, out *api.ResourceFieldSelector, s conversion.Scope) error { - out.ContainerName = in.ContainerName - out.Resource = in.Resource - out.Divisor = in.Divisor - return nil -} - -// Convert_v1_ResourceFieldSelector_To_api_ResourceFieldSelector is an autogenerated conversion function. -func Convert_v1_ResourceFieldSelector_To_api_ResourceFieldSelector(in *v1.ResourceFieldSelector, out *api.ResourceFieldSelector, s conversion.Scope) error { - return autoConvert_v1_ResourceFieldSelector_To_api_ResourceFieldSelector(in, out, s) -} - -func autoConvert_api_ResourceFieldSelector_To_v1_ResourceFieldSelector(in *api.ResourceFieldSelector, out *v1.ResourceFieldSelector, s conversion.Scope) error { - out.ContainerName = in.ContainerName - out.Resource = in.Resource - out.Divisor = in.Divisor - return nil -} - -// Convert_api_ResourceFieldSelector_To_v1_ResourceFieldSelector is an autogenerated conversion function. -func Convert_api_ResourceFieldSelector_To_v1_ResourceFieldSelector(in *api.ResourceFieldSelector, out *v1.ResourceFieldSelector, s conversion.Scope) error { - return autoConvert_api_ResourceFieldSelector_To_v1_ResourceFieldSelector(in, out, s) -} - -func autoConvert_v1_ResourceQuota_To_api_ResourceQuota(in *v1.ResourceQuota, out *api.ResourceQuota, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1_ResourceQuotaSpec_To_api_ResourceQuotaSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1_ResourceQuotaStatus_To_api_ResourceQuotaStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1_ResourceQuota_To_api_ResourceQuota is an autogenerated conversion function. -func Convert_v1_ResourceQuota_To_api_ResourceQuota(in *v1.ResourceQuota, out *api.ResourceQuota, s conversion.Scope) error { - return autoConvert_v1_ResourceQuota_To_api_ResourceQuota(in, out, s) -} - -func autoConvert_api_ResourceQuota_To_v1_ResourceQuota(in *api.ResourceQuota, out *v1.ResourceQuota, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_api_ResourceQuotaSpec_To_v1_ResourceQuotaSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_api_ResourceQuotaStatus_To_v1_ResourceQuotaStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_api_ResourceQuota_To_v1_ResourceQuota is an autogenerated conversion function. -func Convert_api_ResourceQuota_To_v1_ResourceQuota(in *api.ResourceQuota, out *v1.ResourceQuota, s conversion.Scope) error { - return autoConvert_api_ResourceQuota_To_v1_ResourceQuota(in, out, s) -} - -func autoConvert_v1_ResourceQuotaList_To_api_ResourceQuotaList(in *v1.ResourceQuotaList, out *api.ResourceQuotaList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]api.ResourceQuota)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_v1_ResourceQuotaList_To_api_ResourceQuotaList is an autogenerated conversion function. -func Convert_v1_ResourceQuotaList_To_api_ResourceQuotaList(in *v1.ResourceQuotaList, out *api.ResourceQuotaList, s conversion.Scope) error { - return autoConvert_v1_ResourceQuotaList_To_api_ResourceQuotaList(in, out, s) -} - -func autoConvert_api_ResourceQuotaList_To_v1_ResourceQuotaList(in *api.ResourceQuotaList, out *v1.ResourceQuotaList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]v1.ResourceQuota)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_api_ResourceQuotaList_To_v1_ResourceQuotaList is an autogenerated conversion function. -func Convert_api_ResourceQuotaList_To_v1_ResourceQuotaList(in *api.ResourceQuotaList, out *v1.ResourceQuotaList, s conversion.Scope) error { - return autoConvert_api_ResourceQuotaList_To_v1_ResourceQuotaList(in, out, s) -} - -func autoConvert_v1_ResourceQuotaSpec_To_api_ResourceQuotaSpec(in *v1.ResourceQuotaSpec, out *api.ResourceQuotaSpec, s conversion.Scope) error { - out.Hard = *(*api.ResourceList)(unsafe.Pointer(&in.Hard)) - out.Scopes = *(*[]api.ResourceQuotaScope)(unsafe.Pointer(&in.Scopes)) - return nil -} - -// Convert_v1_ResourceQuotaSpec_To_api_ResourceQuotaSpec is an autogenerated conversion function. -func Convert_v1_ResourceQuotaSpec_To_api_ResourceQuotaSpec(in *v1.ResourceQuotaSpec, out *api.ResourceQuotaSpec, s conversion.Scope) error { - return autoConvert_v1_ResourceQuotaSpec_To_api_ResourceQuotaSpec(in, out, s) -} - -func autoConvert_api_ResourceQuotaSpec_To_v1_ResourceQuotaSpec(in *api.ResourceQuotaSpec, out *v1.ResourceQuotaSpec, s conversion.Scope) error { - out.Hard = *(*v1.ResourceList)(unsafe.Pointer(&in.Hard)) - out.Scopes = *(*[]v1.ResourceQuotaScope)(unsafe.Pointer(&in.Scopes)) - return nil -} - -// Convert_api_ResourceQuotaSpec_To_v1_ResourceQuotaSpec is an autogenerated conversion function. -func Convert_api_ResourceQuotaSpec_To_v1_ResourceQuotaSpec(in *api.ResourceQuotaSpec, out *v1.ResourceQuotaSpec, s conversion.Scope) error { - return autoConvert_api_ResourceQuotaSpec_To_v1_ResourceQuotaSpec(in, out, s) -} - -func autoConvert_v1_ResourceQuotaStatus_To_api_ResourceQuotaStatus(in *v1.ResourceQuotaStatus, out *api.ResourceQuotaStatus, s conversion.Scope) error { - out.Hard = *(*api.ResourceList)(unsafe.Pointer(&in.Hard)) - out.Used = *(*api.ResourceList)(unsafe.Pointer(&in.Used)) - return nil -} - -// Convert_v1_ResourceQuotaStatus_To_api_ResourceQuotaStatus is an autogenerated conversion function. -func Convert_v1_ResourceQuotaStatus_To_api_ResourceQuotaStatus(in *v1.ResourceQuotaStatus, out *api.ResourceQuotaStatus, s conversion.Scope) error { - return autoConvert_v1_ResourceQuotaStatus_To_api_ResourceQuotaStatus(in, out, s) -} - -func autoConvert_api_ResourceQuotaStatus_To_v1_ResourceQuotaStatus(in *api.ResourceQuotaStatus, out *v1.ResourceQuotaStatus, s conversion.Scope) error { - out.Hard = *(*v1.ResourceList)(unsafe.Pointer(&in.Hard)) - out.Used = *(*v1.ResourceList)(unsafe.Pointer(&in.Used)) - return nil -} - -// Convert_api_ResourceQuotaStatus_To_v1_ResourceQuotaStatus is an autogenerated conversion function. -func Convert_api_ResourceQuotaStatus_To_v1_ResourceQuotaStatus(in *api.ResourceQuotaStatus, out *v1.ResourceQuotaStatus, s conversion.Scope) error { - return autoConvert_api_ResourceQuotaStatus_To_v1_ResourceQuotaStatus(in, out, s) -} - -func autoConvert_v1_ResourceRequirements_To_api_ResourceRequirements(in *v1.ResourceRequirements, out *api.ResourceRequirements, s conversion.Scope) error { - out.Limits = *(*api.ResourceList)(unsafe.Pointer(&in.Limits)) - out.Requests = *(*api.ResourceList)(unsafe.Pointer(&in.Requests)) - return nil -} - -// Convert_v1_ResourceRequirements_To_api_ResourceRequirements is an autogenerated conversion function. -func Convert_v1_ResourceRequirements_To_api_ResourceRequirements(in *v1.ResourceRequirements, out *api.ResourceRequirements, s conversion.Scope) error { - return autoConvert_v1_ResourceRequirements_To_api_ResourceRequirements(in, out, s) -} - -func autoConvert_api_ResourceRequirements_To_v1_ResourceRequirements(in *api.ResourceRequirements, out *v1.ResourceRequirements, s conversion.Scope) error { - out.Limits = *(*v1.ResourceList)(unsafe.Pointer(&in.Limits)) - out.Requests = *(*v1.ResourceList)(unsafe.Pointer(&in.Requests)) - return nil -} - -// Convert_api_ResourceRequirements_To_v1_ResourceRequirements is an autogenerated conversion function. -func Convert_api_ResourceRequirements_To_v1_ResourceRequirements(in *api.ResourceRequirements, out *v1.ResourceRequirements, s conversion.Scope) error { - return autoConvert_api_ResourceRequirements_To_v1_ResourceRequirements(in, out, s) -} - -func autoConvert_v1_SELinuxOptions_To_api_SELinuxOptions(in *v1.SELinuxOptions, out *api.SELinuxOptions, s conversion.Scope) error { - out.User = in.User - out.Role = in.Role - out.Type = in.Type - out.Level = in.Level - return nil -} - -// Convert_v1_SELinuxOptions_To_api_SELinuxOptions is an autogenerated conversion function. -func Convert_v1_SELinuxOptions_To_api_SELinuxOptions(in *v1.SELinuxOptions, out *api.SELinuxOptions, s conversion.Scope) error { - return autoConvert_v1_SELinuxOptions_To_api_SELinuxOptions(in, out, s) -} - -func autoConvert_api_SELinuxOptions_To_v1_SELinuxOptions(in *api.SELinuxOptions, out *v1.SELinuxOptions, s conversion.Scope) error { - out.User = in.User - out.Role = in.Role - out.Type = in.Type - out.Level = in.Level - return nil -} - -// Convert_api_SELinuxOptions_To_v1_SELinuxOptions is an autogenerated conversion function. -func Convert_api_SELinuxOptions_To_v1_SELinuxOptions(in *api.SELinuxOptions, out *v1.SELinuxOptions, s conversion.Scope) error { - return autoConvert_api_SELinuxOptions_To_v1_SELinuxOptions(in, out, s) -} - -func autoConvert_v1_ScaleIOVolumeSource_To_api_ScaleIOVolumeSource(in *v1.ScaleIOVolumeSource, out *api.ScaleIOVolumeSource, s conversion.Scope) error { - out.Gateway = in.Gateway - out.System = in.System - out.SecretRef = (*api.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) - out.SSLEnabled = in.SSLEnabled - out.ProtectionDomain = in.ProtectionDomain - out.StoragePool = in.StoragePool - out.StorageMode = in.StorageMode - out.VolumeName = in.VolumeName - out.FSType = in.FSType - out.ReadOnly = in.ReadOnly - return nil -} - -// Convert_v1_ScaleIOVolumeSource_To_api_ScaleIOVolumeSource is an autogenerated conversion function. -func Convert_v1_ScaleIOVolumeSource_To_api_ScaleIOVolumeSource(in *v1.ScaleIOVolumeSource, out *api.ScaleIOVolumeSource, s conversion.Scope) error { - return autoConvert_v1_ScaleIOVolumeSource_To_api_ScaleIOVolumeSource(in, out, s) -} - -func autoConvert_api_ScaleIOVolumeSource_To_v1_ScaleIOVolumeSource(in *api.ScaleIOVolumeSource, out *v1.ScaleIOVolumeSource, s conversion.Scope) error { - out.Gateway = in.Gateway - out.System = in.System - out.SecretRef = (*v1.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) - out.SSLEnabled = in.SSLEnabled - out.ProtectionDomain = in.ProtectionDomain - out.StoragePool = in.StoragePool - out.StorageMode = in.StorageMode - out.VolumeName = in.VolumeName - out.FSType = in.FSType - out.ReadOnly = in.ReadOnly - return nil -} - -// Convert_api_ScaleIOVolumeSource_To_v1_ScaleIOVolumeSource is an autogenerated conversion function. -func Convert_api_ScaleIOVolumeSource_To_v1_ScaleIOVolumeSource(in *api.ScaleIOVolumeSource, out *v1.ScaleIOVolumeSource, s conversion.Scope) error { - return autoConvert_api_ScaleIOVolumeSource_To_v1_ScaleIOVolumeSource(in, out, s) -} - -func autoConvert_v1_Secret_To_api_Secret(in *v1.Secret, out *api.Secret, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Data = *(*map[string][]byte)(unsafe.Pointer(&in.Data)) - // INFO: in.StringData opted out of conversion generation - out.Type = api.SecretType(in.Type) - return nil -} - -func autoConvert_api_Secret_To_v1_Secret(in *api.Secret, out *v1.Secret, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Data = *(*map[string][]byte)(unsafe.Pointer(&in.Data)) - out.Type = v1.SecretType(in.Type) - return nil -} - -// Convert_api_Secret_To_v1_Secret is an autogenerated conversion function. -func Convert_api_Secret_To_v1_Secret(in *api.Secret, out *v1.Secret, s conversion.Scope) error { - return autoConvert_api_Secret_To_v1_Secret(in, out, s) -} - -func autoConvert_v1_SecretEnvSource_To_api_SecretEnvSource(in *v1.SecretEnvSource, out *api.SecretEnvSource, s conversion.Scope) error { - if err := Convert_v1_LocalObjectReference_To_api_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { - return err - } - out.Optional = (*bool)(unsafe.Pointer(in.Optional)) - return nil -} - -// Convert_v1_SecretEnvSource_To_api_SecretEnvSource is an autogenerated conversion function. -func Convert_v1_SecretEnvSource_To_api_SecretEnvSource(in *v1.SecretEnvSource, out *api.SecretEnvSource, s conversion.Scope) error { - return autoConvert_v1_SecretEnvSource_To_api_SecretEnvSource(in, out, s) -} - -func autoConvert_api_SecretEnvSource_To_v1_SecretEnvSource(in *api.SecretEnvSource, out *v1.SecretEnvSource, s conversion.Scope) error { - if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { - return err - } - out.Optional = (*bool)(unsafe.Pointer(in.Optional)) - return nil -} - -// Convert_api_SecretEnvSource_To_v1_SecretEnvSource is an autogenerated conversion function. -func Convert_api_SecretEnvSource_To_v1_SecretEnvSource(in *api.SecretEnvSource, out *v1.SecretEnvSource, s conversion.Scope) error { - return autoConvert_api_SecretEnvSource_To_v1_SecretEnvSource(in, out, s) -} - -func autoConvert_v1_SecretKeySelector_To_api_SecretKeySelector(in *v1.SecretKeySelector, out *api.SecretKeySelector, s conversion.Scope) error { - if err := Convert_v1_LocalObjectReference_To_api_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { - return err - } - out.Key = in.Key - out.Optional = (*bool)(unsafe.Pointer(in.Optional)) - return nil -} - -// Convert_v1_SecretKeySelector_To_api_SecretKeySelector is an autogenerated conversion function. -func Convert_v1_SecretKeySelector_To_api_SecretKeySelector(in *v1.SecretKeySelector, out *api.SecretKeySelector, s conversion.Scope) error { - return autoConvert_v1_SecretKeySelector_To_api_SecretKeySelector(in, out, s) -} - -func autoConvert_api_SecretKeySelector_To_v1_SecretKeySelector(in *api.SecretKeySelector, out *v1.SecretKeySelector, s conversion.Scope) error { - if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { - return err - } - out.Key = in.Key - out.Optional = (*bool)(unsafe.Pointer(in.Optional)) - return nil -} - -// Convert_api_SecretKeySelector_To_v1_SecretKeySelector is an autogenerated conversion function. -func Convert_api_SecretKeySelector_To_v1_SecretKeySelector(in *api.SecretKeySelector, out *v1.SecretKeySelector, s conversion.Scope) error { - return autoConvert_api_SecretKeySelector_To_v1_SecretKeySelector(in, out, s) -} - -func autoConvert_v1_SecretList_To_api_SecretList(in *v1.SecretList, out *api.SecretList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]api.Secret, len(*in)) - for i := range *in { - if err := Convert_v1_Secret_To_api_Secret(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v1_SecretList_To_api_SecretList is an autogenerated conversion function. -func Convert_v1_SecretList_To_api_SecretList(in *v1.SecretList, out *api.SecretList, s conversion.Scope) error { - return autoConvert_v1_SecretList_To_api_SecretList(in, out, s) -} - -func autoConvert_api_SecretList_To_v1_SecretList(in *api.SecretList, out *v1.SecretList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]v1.Secret, len(*in)) - for i := range *in { - if err := Convert_api_Secret_To_v1_Secret(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_api_SecretList_To_v1_SecretList is an autogenerated conversion function. -func Convert_api_SecretList_To_v1_SecretList(in *api.SecretList, out *v1.SecretList, s conversion.Scope) error { - return autoConvert_api_SecretList_To_v1_SecretList(in, out, s) -} - -func autoConvert_v1_SecretProjection_To_api_SecretProjection(in *v1.SecretProjection, out *api.SecretProjection, s conversion.Scope) error { - if err := Convert_v1_LocalObjectReference_To_api_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { - return err - } - out.Items = *(*[]api.KeyToPath)(unsafe.Pointer(&in.Items)) - out.Optional = (*bool)(unsafe.Pointer(in.Optional)) - return nil -} - -// Convert_v1_SecretProjection_To_api_SecretProjection is an autogenerated conversion function. -func Convert_v1_SecretProjection_To_api_SecretProjection(in *v1.SecretProjection, out *api.SecretProjection, s conversion.Scope) error { - return autoConvert_v1_SecretProjection_To_api_SecretProjection(in, out, s) -} - -func autoConvert_api_SecretProjection_To_v1_SecretProjection(in *api.SecretProjection, out *v1.SecretProjection, s conversion.Scope) error { - if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { - return err - } - out.Items = *(*[]v1.KeyToPath)(unsafe.Pointer(&in.Items)) - out.Optional = (*bool)(unsafe.Pointer(in.Optional)) - return nil -} - -// Convert_api_SecretProjection_To_v1_SecretProjection is an autogenerated conversion function. -func Convert_api_SecretProjection_To_v1_SecretProjection(in *api.SecretProjection, out *v1.SecretProjection, s conversion.Scope) error { - return autoConvert_api_SecretProjection_To_v1_SecretProjection(in, out, s) -} - -func autoConvert_v1_SecretReference_To_api_SecretReference(in *v1.SecretReference, out *api.SecretReference, s conversion.Scope) error { - out.Name = in.Name - out.Namespace = in.Namespace - return nil -} - -// Convert_v1_SecretReference_To_api_SecretReference is an autogenerated conversion function. -func Convert_v1_SecretReference_To_api_SecretReference(in *v1.SecretReference, out *api.SecretReference, s conversion.Scope) error { - return autoConvert_v1_SecretReference_To_api_SecretReference(in, out, s) -} - -func autoConvert_api_SecretReference_To_v1_SecretReference(in *api.SecretReference, out *v1.SecretReference, s conversion.Scope) error { - out.Name = in.Name - out.Namespace = in.Namespace - return nil -} - -// Convert_api_SecretReference_To_v1_SecretReference is an autogenerated conversion function. -func Convert_api_SecretReference_To_v1_SecretReference(in *api.SecretReference, out *v1.SecretReference, s conversion.Scope) error { - return autoConvert_api_SecretReference_To_v1_SecretReference(in, out, s) -} - -func autoConvert_v1_SecretVolumeSource_To_api_SecretVolumeSource(in *v1.SecretVolumeSource, out *api.SecretVolumeSource, s conversion.Scope) error { - out.SecretName = in.SecretName - out.Items = *(*[]api.KeyToPath)(unsafe.Pointer(&in.Items)) - out.DefaultMode = (*int32)(unsafe.Pointer(in.DefaultMode)) - out.Optional = (*bool)(unsafe.Pointer(in.Optional)) - return nil -} - -// Convert_v1_SecretVolumeSource_To_api_SecretVolumeSource is an autogenerated conversion function. -func Convert_v1_SecretVolumeSource_To_api_SecretVolumeSource(in *v1.SecretVolumeSource, out *api.SecretVolumeSource, s conversion.Scope) error { - return autoConvert_v1_SecretVolumeSource_To_api_SecretVolumeSource(in, out, s) -} - -func autoConvert_api_SecretVolumeSource_To_v1_SecretVolumeSource(in *api.SecretVolumeSource, out *v1.SecretVolumeSource, s conversion.Scope) error { - out.SecretName = in.SecretName - out.Items = *(*[]v1.KeyToPath)(unsafe.Pointer(&in.Items)) - out.DefaultMode = (*int32)(unsafe.Pointer(in.DefaultMode)) - out.Optional = (*bool)(unsafe.Pointer(in.Optional)) - return nil -} - -// Convert_api_SecretVolumeSource_To_v1_SecretVolumeSource is an autogenerated conversion function. -func Convert_api_SecretVolumeSource_To_v1_SecretVolumeSource(in *api.SecretVolumeSource, out *v1.SecretVolumeSource, s conversion.Scope) error { - return autoConvert_api_SecretVolumeSource_To_v1_SecretVolumeSource(in, out, s) -} - -func autoConvert_v1_SecurityContext_To_api_SecurityContext(in *v1.SecurityContext, out *api.SecurityContext, s conversion.Scope) error { - out.Capabilities = (*api.Capabilities)(unsafe.Pointer(in.Capabilities)) - out.Privileged = (*bool)(unsafe.Pointer(in.Privileged)) - out.SELinuxOptions = (*api.SELinuxOptions)(unsafe.Pointer(in.SELinuxOptions)) - out.RunAsUser = (*int64)(unsafe.Pointer(in.RunAsUser)) - out.RunAsNonRoot = (*bool)(unsafe.Pointer(in.RunAsNonRoot)) - out.ReadOnlyRootFilesystem = (*bool)(unsafe.Pointer(in.ReadOnlyRootFilesystem)) - out.AllowPrivilegeEscalation = (*bool)(unsafe.Pointer(in.AllowPrivilegeEscalation)) - return nil -} - -// Convert_v1_SecurityContext_To_api_SecurityContext is an autogenerated conversion function. -func Convert_v1_SecurityContext_To_api_SecurityContext(in *v1.SecurityContext, out *api.SecurityContext, s conversion.Scope) error { - return autoConvert_v1_SecurityContext_To_api_SecurityContext(in, out, s) -} - -func autoConvert_api_SecurityContext_To_v1_SecurityContext(in *api.SecurityContext, out *v1.SecurityContext, s conversion.Scope) error { - out.Capabilities = (*v1.Capabilities)(unsafe.Pointer(in.Capabilities)) - out.Privileged = (*bool)(unsafe.Pointer(in.Privileged)) - out.SELinuxOptions = (*v1.SELinuxOptions)(unsafe.Pointer(in.SELinuxOptions)) - out.RunAsUser = (*int64)(unsafe.Pointer(in.RunAsUser)) - out.RunAsNonRoot = (*bool)(unsafe.Pointer(in.RunAsNonRoot)) - out.ReadOnlyRootFilesystem = (*bool)(unsafe.Pointer(in.ReadOnlyRootFilesystem)) - out.AllowPrivilegeEscalation = (*bool)(unsafe.Pointer(in.AllowPrivilegeEscalation)) - return nil -} - -func autoConvert_v1_SerializedReference_To_api_SerializedReference(in *v1.SerializedReference, out *api.SerializedReference, s conversion.Scope) error { - if err := Convert_v1_ObjectReference_To_api_ObjectReference(&in.Reference, &out.Reference, s); err != nil { - return err - } - return nil -} - -// Convert_v1_SerializedReference_To_api_SerializedReference is an autogenerated conversion function. -func Convert_v1_SerializedReference_To_api_SerializedReference(in *v1.SerializedReference, out *api.SerializedReference, s conversion.Scope) error { - return autoConvert_v1_SerializedReference_To_api_SerializedReference(in, out, s) -} - -func autoConvert_api_SerializedReference_To_v1_SerializedReference(in *api.SerializedReference, out *v1.SerializedReference, s conversion.Scope) error { - if err := Convert_api_ObjectReference_To_v1_ObjectReference(&in.Reference, &out.Reference, s); err != nil { - return err - } - return nil -} - -// Convert_api_SerializedReference_To_v1_SerializedReference is an autogenerated conversion function. -func Convert_api_SerializedReference_To_v1_SerializedReference(in *api.SerializedReference, out *v1.SerializedReference, s conversion.Scope) error { - return autoConvert_api_SerializedReference_To_v1_SerializedReference(in, out, s) -} - -func autoConvert_v1_Service_To_api_Service(in *v1.Service, out *api.Service, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1_ServiceSpec_To_api_ServiceSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1_ServiceStatus_To_api_ServiceStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1_Service_To_api_Service is an autogenerated conversion function. -func Convert_v1_Service_To_api_Service(in *v1.Service, out *api.Service, s conversion.Scope) error { - return autoConvert_v1_Service_To_api_Service(in, out, s) -} - -func autoConvert_api_Service_To_v1_Service(in *api.Service, out *v1.Service, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_api_ServiceSpec_To_v1_ServiceSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_api_ServiceStatus_To_v1_ServiceStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_api_Service_To_v1_Service is an autogenerated conversion function. -func Convert_api_Service_To_v1_Service(in *api.Service, out *v1.Service, s conversion.Scope) error { - return autoConvert_api_Service_To_v1_Service(in, out, s) -} - -func autoConvert_v1_ServiceAccount_To_api_ServiceAccount(in *v1.ServiceAccount, out *api.ServiceAccount, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Secrets = *(*[]api.ObjectReference)(unsafe.Pointer(&in.Secrets)) - out.ImagePullSecrets = *(*[]api.LocalObjectReference)(unsafe.Pointer(&in.ImagePullSecrets)) - out.AutomountServiceAccountToken = (*bool)(unsafe.Pointer(in.AutomountServiceAccountToken)) - return nil -} - -// Convert_v1_ServiceAccount_To_api_ServiceAccount is an autogenerated conversion function. -func Convert_v1_ServiceAccount_To_api_ServiceAccount(in *v1.ServiceAccount, out *api.ServiceAccount, s conversion.Scope) error { - return autoConvert_v1_ServiceAccount_To_api_ServiceAccount(in, out, s) -} - -func autoConvert_api_ServiceAccount_To_v1_ServiceAccount(in *api.ServiceAccount, out *v1.ServiceAccount, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Secrets = *(*[]v1.ObjectReference)(unsafe.Pointer(&in.Secrets)) - out.ImagePullSecrets = *(*[]v1.LocalObjectReference)(unsafe.Pointer(&in.ImagePullSecrets)) - out.AutomountServiceAccountToken = (*bool)(unsafe.Pointer(in.AutomountServiceAccountToken)) - return nil -} - -// Convert_api_ServiceAccount_To_v1_ServiceAccount is an autogenerated conversion function. -func Convert_api_ServiceAccount_To_v1_ServiceAccount(in *api.ServiceAccount, out *v1.ServiceAccount, s conversion.Scope) error { - return autoConvert_api_ServiceAccount_To_v1_ServiceAccount(in, out, s) -} - -func autoConvert_v1_ServiceAccountList_To_api_ServiceAccountList(in *v1.ServiceAccountList, out *api.ServiceAccountList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]api.ServiceAccount)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_v1_ServiceAccountList_To_api_ServiceAccountList is an autogenerated conversion function. -func Convert_v1_ServiceAccountList_To_api_ServiceAccountList(in *v1.ServiceAccountList, out *api.ServiceAccountList, s conversion.Scope) error { - return autoConvert_v1_ServiceAccountList_To_api_ServiceAccountList(in, out, s) -} - -func autoConvert_api_ServiceAccountList_To_v1_ServiceAccountList(in *api.ServiceAccountList, out *v1.ServiceAccountList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]v1.ServiceAccount)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_api_ServiceAccountList_To_v1_ServiceAccountList is an autogenerated conversion function. -func Convert_api_ServiceAccountList_To_v1_ServiceAccountList(in *api.ServiceAccountList, out *v1.ServiceAccountList, s conversion.Scope) error { - return autoConvert_api_ServiceAccountList_To_v1_ServiceAccountList(in, out, s) -} - -func autoConvert_v1_ServiceList_To_api_ServiceList(in *v1.ServiceList, out *api.ServiceList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]api.Service, len(*in)) - for i := range *in { - if err := Convert_v1_Service_To_api_Service(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v1_ServiceList_To_api_ServiceList is an autogenerated conversion function. -func Convert_v1_ServiceList_To_api_ServiceList(in *v1.ServiceList, out *api.ServiceList, s conversion.Scope) error { - return autoConvert_v1_ServiceList_To_api_ServiceList(in, out, s) -} - -func autoConvert_api_ServiceList_To_v1_ServiceList(in *api.ServiceList, out *v1.ServiceList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]v1.Service, len(*in)) - for i := range *in { - if err := Convert_api_Service_To_v1_Service(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_api_ServiceList_To_v1_ServiceList is an autogenerated conversion function. -func Convert_api_ServiceList_To_v1_ServiceList(in *api.ServiceList, out *v1.ServiceList, s conversion.Scope) error { - return autoConvert_api_ServiceList_To_v1_ServiceList(in, out, s) -} - -func autoConvert_v1_ServicePort_To_api_ServicePort(in *v1.ServicePort, out *api.ServicePort, s conversion.Scope) error { - out.Name = in.Name - out.Protocol = api.Protocol(in.Protocol) - out.Port = in.Port - out.TargetPort = in.TargetPort - out.NodePort = in.NodePort - return nil -} - -// Convert_v1_ServicePort_To_api_ServicePort is an autogenerated conversion function. -func Convert_v1_ServicePort_To_api_ServicePort(in *v1.ServicePort, out *api.ServicePort, s conversion.Scope) error { - return autoConvert_v1_ServicePort_To_api_ServicePort(in, out, s) -} - -func autoConvert_api_ServicePort_To_v1_ServicePort(in *api.ServicePort, out *v1.ServicePort, s conversion.Scope) error { - out.Name = in.Name - out.Protocol = v1.Protocol(in.Protocol) - out.Port = in.Port - out.TargetPort = in.TargetPort - out.NodePort = in.NodePort - return nil -} - -// Convert_api_ServicePort_To_v1_ServicePort is an autogenerated conversion function. -func Convert_api_ServicePort_To_v1_ServicePort(in *api.ServicePort, out *v1.ServicePort, s conversion.Scope) error { - return autoConvert_api_ServicePort_To_v1_ServicePort(in, out, s) -} - -func autoConvert_v1_ServiceProxyOptions_To_api_ServiceProxyOptions(in *v1.ServiceProxyOptions, out *api.ServiceProxyOptions, s conversion.Scope) error { - out.Path = in.Path - return nil -} - -// Convert_v1_ServiceProxyOptions_To_api_ServiceProxyOptions is an autogenerated conversion function. -func Convert_v1_ServiceProxyOptions_To_api_ServiceProxyOptions(in *v1.ServiceProxyOptions, out *api.ServiceProxyOptions, s conversion.Scope) error { - return autoConvert_v1_ServiceProxyOptions_To_api_ServiceProxyOptions(in, out, s) -} - -func autoConvert_api_ServiceProxyOptions_To_v1_ServiceProxyOptions(in *api.ServiceProxyOptions, out *v1.ServiceProxyOptions, s conversion.Scope) error { - out.Path = in.Path - return nil -} - -// Convert_api_ServiceProxyOptions_To_v1_ServiceProxyOptions is an autogenerated conversion function. -func Convert_api_ServiceProxyOptions_To_v1_ServiceProxyOptions(in *api.ServiceProxyOptions, out *v1.ServiceProxyOptions, s conversion.Scope) error { - return autoConvert_api_ServiceProxyOptions_To_v1_ServiceProxyOptions(in, out, s) -} - -func autoConvert_v1_ServiceSpec_To_api_ServiceSpec(in *v1.ServiceSpec, out *api.ServiceSpec, s conversion.Scope) error { - out.Ports = *(*[]api.ServicePort)(unsafe.Pointer(&in.Ports)) - out.Selector = *(*map[string]string)(unsafe.Pointer(&in.Selector)) - out.ClusterIP = in.ClusterIP - out.Type = api.ServiceType(in.Type) - out.ExternalIPs = *(*[]string)(unsafe.Pointer(&in.ExternalIPs)) - out.SessionAffinity = api.ServiceAffinity(in.SessionAffinity) - out.LoadBalancerIP = in.LoadBalancerIP - out.LoadBalancerSourceRanges = *(*[]string)(unsafe.Pointer(&in.LoadBalancerSourceRanges)) - out.ExternalName = in.ExternalName - out.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyType(in.ExternalTrafficPolicy) - out.HealthCheckNodePort = in.HealthCheckNodePort - out.PublishNotReadyAddresses = in.PublishNotReadyAddresses - out.SessionAffinityConfig = (*api.SessionAffinityConfig)(unsafe.Pointer(in.SessionAffinityConfig)) - return nil -} - -// Convert_v1_ServiceSpec_To_api_ServiceSpec is an autogenerated conversion function. -func Convert_v1_ServiceSpec_To_api_ServiceSpec(in *v1.ServiceSpec, out *api.ServiceSpec, s conversion.Scope) error { - return autoConvert_v1_ServiceSpec_To_api_ServiceSpec(in, out, s) -} - -func autoConvert_api_ServiceSpec_To_v1_ServiceSpec(in *api.ServiceSpec, out *v1.ServiceSpec, s conversion.Scope) error { - out.Type = v1.ServiceType(in.Type) - out.Ports = *(*[]v1.ServicePort)(unsafe.Pointer(&in.Ports)) - out.Selector = *(*map[string]string)(unsafe.Pointer(&in.Selector)) - out.ClusterIP = in.ClusterIP - out.ExternalName = in.ExternalName - out.ExternalIPs = *(*[]string)(unsafe.Pointer(&in.ExternalIPs)) - out.LoadBalancerIP = in.LoadBalancerIP - out.SessionAffinity = v1.ServiceAffinity(in.SessionAffinity) - out.SessionAffinityConfig = (*v1.SessionAffinityConfig)(unsafe.Pointer(in.SessionAffinityConfig)) - out.LoadBalancerSourceRanges = *(*[]string)(unsafe.Pointer(&in.LoadBalancerSourceRanges)) - out.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyType(in.ExternalTrafficPolicy) - out.HealthCheckNodePort = in.HealthCheckNodePort - out.PublishNotReadyAddresses = in.PublishNotReadyAddresses - return nil -} - -// Convert_api_ServiceSpec_To_v1_ServiceSpec is an autogenerated conversion function. -func Convert_api_ServiceSpec_To_v1_ServiceSpec(in *api.ServiceSpec, out *v1.ServiceSpec, s conversion.Scope) error { - return autoConvert_api_ServiceSpec_To_v1_ServiceSpec(in, out, s) -} - -func autoConvert_v1_ServiceStatus_To_api_ServiceStatus(in *v1.ServiceStatus, out *api.ServiceStatus, s conversion.Scope) error { - if err := Convert_v1_LoadBalancerStatus_To_api_LoadBalancerStatus(&in.LoadBalancer, &out.LoadBalancer, s); err != nil { - return err - } - return nil -} - -// Convert_v1_ServiceStatus_To_api_ServiceStatus is an autogenerated conversion function. -func Convert_v1_ServiceStatus_To_api_ServiceStatus(in *v1.ServiceStatus, out *api.ServiceStatus, s conversion.Scope) error { - return autoConvert_v1_ServiceStatus_To_api_ServiceStatus(in, out, s) -} - -func autoConvert_api_ServiceStatus_To_v1_ServiceStatus(in *api.ServiceStatus, out *v1.ServiceStatus, s conversion.Scope) error { - if err := Convert_api_LoadBalancerStatus_To_v1_LoadBalancerStatus(&in.LoadBalancer, &out.LoadBalancer, s); err != nil { - return err - } - return nil -} - -// Convert_api_ServiceStatus_To_v1_ServiceStatus is an autogenerated conversion function. -func Convert_api_ServiceStatus_To_v1_ServiceStatus(in *api.ServiceStatus, out *v1.ServiceStatus, s conversion.Scope) error { - return autoConvert_api_ServiceStatus_To_v1_ServiceStatus(in, out, s) -} - -func autoConvert_v1_SessionAffinityConfig_To_api_SessionAffinityConfig(in *v1.SessionAffinityConfig, out *api.SessionAffinityConfig, s conversion.Scope) error { - out.ClientIP = (*api.ClientIPConfig)(unsafe.Pointer(in.ClientIP)) - return nil -} - -// Convert_v1_SessionAffinityConfig_To_api_SessionAffinityConfig is an autogenerated conversion function. -func Convert_v1_SessionAffinityConfig_To_api_SessionAffinityConfig(in *v1.SessionAffinityConfig, out *api.SessionAffinityConfig, s conversion.Scope) error { - return autoConvert_v1_SessionAffinityConfig_To_api_SessionAffinityConfig(in, out, s) -} - -func autoConvert_api_SessionAffinityConfig_To_v1_SessionAffinityConfig(in *api.SessionAffinityConfig, out *v1.SessionAffinityConfig, s conversion.Scope) error { - out.ClientIP = (*v1.ClientIPConfig)(unsafe.Pointer(in.ClientIP)) - return nil -} - -// Convert_api_SessionAffinityConfig_To_v1_SessionAffinityConfig is an autogenerated conversion function. -func Convert_api_SessionAffinityConfig_To_v1_SessionAffinityConfig(in *api.SessionAffinityConfig, out *v1.SessionAffinityConfig, s conversion.Scope) error { - return autoConvert_api_SessionAffinityConfig_To_v1_SessionAffinityConfig(in, out, s) -} - -func autoConvert_v1_StorageOSPersistentVolumeSource_To_api_StorageOSPersistentVolumeSource(in *v1.StorageOSPersistentVolumeSource, out *api.StorageOSPersistentVolumeSource, s conversion.Scope) error { - out.VolumeName = in.VolumeName - out.VolumeNamespace = in.VolumeNamespace - out.FSType = in.FSType - out.ReadOnly = in.ReadOnly - out.SecretRef = (*api.ObjectReference)(unsafe.Pointer(in.SecretRef)) - return nil -} - -// Convert_v1_StorageOSPersistentVolumeSource_To_api_StorageOSPersistentVolumeSource is an autogenerated conversion function. -func Convert_v1_StorageOSPersistentVolumeSource_To_api_StorageOSPersistentVolumeSource(in *v1.StorageOSPersistentVolumeSource, out *api.StorageOSPersistentVolumeSource, s conversion.Scope) error { - return autoConvert_v1_StorageOSPersistentVolumeSource_To_api_StorageOSPersistentVolumeSource(in, out, s) -} - -func autoConvert_api_StorageOSPersistentVolumeSource_To_v1_StorageOSPersistentVolumeSource(in *api.StorageOSPersistentVolumeSource, out *v1.StorageOSPersistentVolumeSource, s conversion.Scope) error { - out.VolumeName = in.VolumeName - out.VolumeNamespace = in.VolumeNamespace - out.FSType = in.FSType - out.ReadOnly = in.ReadOnly - out.SecretRef = (*v1.ObjectReference)(unsafe.Pointer(in.SecretRef)) - return nil -} - -// Convert_api_StorageOSPersistentVolumeSource_To_v1_StorageOSPersistentVolumeSource is an autogenerated conversion function. -func Convert_api_StorageOSPersistentVolumeSource_To_v1_StorageOSPersistentVolumeSource(in *api.StorageOSPersistentVolumeSource, out *v1.StorageOSPersistentVolumeSource, s conversion.Scope) error { - return autoConvert_api_StorageOSPersistentVolumeSource_To_v1_StorageOSPersistentVolumeSource(in, out, s) -} - -func autoConvert_v1_StorageOSVolumeSource_To_api_StorageOSVolumeSource(in *v1.StorageOSVolumeSource, out *api.StorageOSVolumeSource, s conversion.Scope) error { - out.VolumeName = in.VolumeName - out.VolumeNamespace = in.VolumeNamespace - out.FSType = in.FSType - out.ReadOnly = in.ReadOnly - out.SecretRef = (*api.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) - return nil -} - -// Convert_v1_StorageOSVolumeSource_To_api_StorageOSVolumeSource is an autogenerated conversion function. -func Convert_v1_StorageOSVolumeSource_To_api_StorageOSVolumeSource(in *v1.StorageOSVolumeSource, out *api.StorageOSVolumeSource, s conversion.Scope) error { - return autoConvert_v1_StorageOSVolumeSource_To_api_StorageOSVolumeSource(in, out, s) -} - -func autoConvert_api_StorageOSVolumeSource_To_v1_StorageOSVolumeSource(in *api.StorageOSVolumeSource, out *v1.StorageOSVolumeSource, s conversion.Scope) error { - out.VolumeName = in.VolumeName - out.VolumeNamespace = in.VolumeNamespace - out.FSType = in.FSType - out.ReadOnly = in.ReadOnly - out.SecretRef = (*v1.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) - return nil -} - -// Convert_api_StorageOSVolumeSource_To_v1_StorageOSVolumeSource is an autogenerated conversion function. -func Convert_api_StorageOSVolumeSource_To_v1_StorageOSVolumeSource(in *api.StorageOSVolumeSource, out *v1.StorageOSVolumeSource, s conversion.Scope) error { - return autoConvert_api_StorageOSVolumeSource_To_v1_StorageOSVolumeSource(in, out, s) -} - -func autoConvert_v1_Sysctl_To_api_Sysctl(in *v1.Sysctl, out *api.Sysctl, s conversion.Scope) error { - out.Name = in.Name - out.Value = in.Value - return nil -} - -// Convert_v1_Sysctl_To_api_Sysctl is an autogenerated conversion function. -func Convert_v1_Sysctl_To_api_Sysctl(in *v1.Sysctl, out *api.Sysctl, s conversion.Scope) error { - return autoConvert_v1_Sysctl_To_api_Sysctl(in, out, s) -} - -func autoConvert_api_Sysctl_To_v1_Sysctl(in *api.Sysctl, out *v1.Sysctl, s conversion.Scope) error { - out.Name = in.Name - out.Value = in.Value - return nil -} - -// Convert_api_Sysctl_To_v1_Sysctl is an autogenerated conversion function. -func Convert_api_Sysctl_To_v1_Sysctl(in *api.Sysctl, out *v1.Sysctl, s conversion.Scope) error { - return autoConvert_api_Sysctl_To_v1_Sysctl(in, out, s) -} - -func autoConvert_v1_TCPSocketAction_To_api_TCPSocketAction(in *v1.TCPSocketAction, out *api.TCPSocketAction, s conversion.Scope) error { - out.Port = in.Port - out.Host = in.Host - return nil -} - -// Convert_v1_TCPSocketAction_To_api_TCPSocketAction is an autogenerated conversion function. -func Convert_v1_TCPSocketAction_To_api_TCPSocketAction(in *v1.TCPSocketAction, out *api.TCPSocketAction, s conversion.Scope) error { - return autoConvert_v1_TCPSocketAction_To_api_TCPSocketAction(in, out, s) -} - -func autoConvert_api_TCPSocketAction_To_v1_TCPSocketAction(in *api.TCPSocketAction, out *v1.TCPSocketAction, s conversion.Scope) error { - out.Port = in.Port - out.Host = in.Host - return nil -} - -// Convert_api_TCPSocketAction_To_v1_TCPSocketAction is an autogenerated conversion function. -func Convert_api_TCPSocketAction_To_v1_TCPSocketAction(in *api.TCPSocketAction, out *v1.TCPSocketAction, s conversion.Scope) error { - return autoConvert_api_TCPSocketAction_To_v1_TCPSocketAction(in, out, s) -} - -func autoConvert_v1_Taint_To_api_Taint(in *v1.Taint, out *api.Taint, s conversion.Scope) error { - out.Key = in.Key - out.Value = in.Value - out.Effect = api.TaintEffect(in.Effect) - out.TimeAdded = (*meta_v1.Time)(unsafe.Pointer(in.TimeAdded)) - return nil -} - -// Convert_v1_Taint_To_api_Taint is an autogenerated conversion function. -func Convert_v1_Taint_To_api_Taint(in *v1.Taint, out *api.Taint, s conversion.Scope) error { - return autoConvert_v1_Taint_To_api_Taint(in, out, s) -} - -func autoConvert_api_Taint_To_v1_Taint(in *api.Taint, out *v1.Taint, s conversion.Scope) error { - out.Key = in.Key - out.Value = in.Value - out.Effect = v1.TaintEffect(in.Effect) - out.TimeAdded = (*meta_v1.Time)(unsafe.Pointer(in.TimeAdded)) - return nil -} - -// Convert_api_Taint_To_v1_Taint is an autogenerated conversion function. -func Convert_api_Taint_To_v1_Taint(in *api.Taint, out *v1.Taint, s conversion.Scope) error { - return autoConvert_api_Taint_To_v1_Taint(in, out, s) -} - -func autoConvert_v1_Toleration_To_api_Toleration(in *v1.Toleration, out *api.Toleration, s conversion.Scope) error { - out.Key = in.Key - out.Operator = api.TolerationOperator(in.Operator) - out.Value = in.Value - out.Effect = api.TaintEffect(in.Effect) - out.TolerationSeconds = (*int64)(unsafe.Pointer(in.TolerationSeconds)) - return nil -} - -// Convert_v1_Toleration_To_api_Toleration is an autogenerated conversion function. -func Convert_v1_Toleration_To_api_Toleration(in *v1.Toleration, out *api.Toleration, s conversion.Scope) error { - return autoConvert_v1_Toleration_To_api_Toleration(in, out, s) -} - -func autoConvert_api_Toleration_To_v1_Toleration(in *api.Toleration, out *v1.Toleration, s conversion.Scope) error { - out.Key = in.Key - out.Operator = v1.TolerationOperator(in.Operator) - out.Value = in.Value - out.Effect = v1.TaintEffect(in.Effect) - out.TolerationSeconds = (*int64)(unsafe.Pointer(in.TolerationSeconds)) - return nil -} - -// Convert_api_Toleration_To_v1_Toleration is an autogenerated conversion function. -func Convert_api_Toleration_To_v1_Toleration(in *api.Toleration, out *v1.Toleration, s conversion.Scope) error { - return autoConvert_api_Toleration_To_v1_Toleration(in, out, s) -} - -func autoConvert_v1_Volume_To_api_Volume(in *v1.Volume, out *api.Volume, s conversion.Scope) error { - out.Name = in.Name - if err := Convert_v1_VolumeSource_To_api_VolumeSource(&in.VolumeSource, &out.VolumeSource, s); err != nil { - return err - } - return nil -} - -// Convert_v1_Volume_To_api_Volume is an autogenerated conversion function. -func Convert_v1_Volume_To_api_Volume(in *v1.Volume, out *api.Volume, s conversion.Scope) error { - return autoConvert_v1_Volume_To_api_Volume(in, out, s) -} - -func autoConvert_api_Volume_To_v1_Volume(in *api.Volume, out *v1.Volume, s conversion.Scope) error { - out.Name = in.Name - if err := Convert_api_VolumeSource_To_v1_VolumeSource(&in.VolumeSource, &out.VolumeSource, s); err != nil { - return err - } - return nil -} - -// Convert_api_Volume_To_v1_Volume is an autogenerated conversion function. -func Convert_api_Volume_To_v1_Volume(in *api.Volume, out *v1.Volume, s conversion.Scope) error { - return autoConvert_api_Volume_To_v1_Volume(in, out, s) -} - -func autoConvert_v1_VolumeMount_To_api_VolumeMount(in *v1.VolumeMount, out *api.VolumeMount, s conversion.Scope) error { - out.Name = in.Name - out.ReadOnly = in.ReadOnly - out.MountPath = in.MountPath - out.SubPath = in.SubPath - out.MountPropagation = (*api.MountPropagationMode)(unsafe.Pointer(in.MountPropagation)) - return nil -} - -// Convert_v1_VolumeMount_To_api_VolumeMount is an autogenerated conversion function. -func Convert_v1_VolumeMount_To_api_VolumeMount(in *v1.VolumeMount, out *api.VolumeMount, s conversion.Scope) error { - return autoConvert_v1_VolumeMount_To_api_VolumeMount(in, out, s) -} - -func autoConvert_api_VolumeMount_To_v1_VolumeMount(in *api.VolumeMount, out *v1.VolumeMount, s conversion.Scope) error { - out.Name = in.Name - out.ReadOnly = in.ReadOnly - out.MountPath = in.MountPath - out.SubPath = in.SubPath - out.MountPropagation = (*v1.MountPropagationMode)(unsafe.Pointer(in.MountPropagation)) - return nil -} - -// Convert_api_VolumeMount_To_v1_VolumeMount is an autogenerated conversion function. -func Convert_api_VolumeMount_To_v1_VolumeMount(in *api.VolumeMount, out *v1.VolumeMount, s conversion.Scope) error { - return autoConvert_api_VolumeMount_To_v1_VolumeMount(in, out, s) -} - -func autoConvert_v1_VolumeProjection_To_api_VolumeProjection(in *v1.VolumeProjection, out *api.VolumeProjection, s conversion.Scope) error { - out.Secret = (*api.SecretProjection)(unsafe.Pointer(in.Secret)) - out.DownwardAPI = (*api.DownwardAPIProjection)(unsafe.Pointer(in.DownwardAPI)) - out.ConfigMap = (*api.ConfigMapProjection)(unsafe.Pointer(in.ConfigMap)) - return nil -} - -// Convert_v1_VolumeProjection_To_api_VolumeProjection is an autogenerated conversion function. -func Convert_v1_VolumeProjection_To_api_VolumeProjection(in *v1.VolumeProjection, out *api.VolumeProjection, s conversion.Scope) error { - return autoConvert_v1_VolumeProjection_To_api_VolumeProjection(in, out, s) -} - -func autoConvert_api_VolumeProjection_To_v1_VolumeProjection(in *api.VolumeProjection, out *v1.VolumeProjection, s conversion.Scope) error { - out.Secret = (*v1.SecretProjection)(unsafe.Pointer(in.Secret)) - out.DownwardAPI = (*v1.DownwardAPIProjection)(unsafe.Pointer(in.DownwardAPI)) - out.ConfigMap = (*v1.ConfigMapProjection)(unsafe.Pointer(in.ConfigMap)) - return nil -} - -// Convert_api_VolumeProjection_To_v1_VolumeProjection is an autogenerated conversion function. -func Convert_api_VolumeProjection_To_v1_VolumeProjection(in *api.VolumeProjection, out *v1.VolumeProjection, s conversion.Scope) error { - return autoConvert_api_VolumeProjection_To_v1_VolumeProjection(in, out, s) -} - -func autoConvert_v1_VolumeSource_To_api_VolumeSource(in *v1.VolumeSource, out *api.VolumeSource, s conversion.Scope) error { - out.HostPath = (*api.HostPathVolumeSource)(unsafe.Pointer(in.HostPath)) - out.EmptyDir = (*api.EmptyDirVolumeSource)(unsafe.Pointer(in.EmptyDir)) - out.GCEPersistentDisk = (*api.GCEPersistentDiskVolumeSource)(unsafe.Pointer(in.GCEPersistentDisk)) - out.AWSElasticBlockStore = (*api.AWSElasticBlockStoreVolumeSource)(unsafe.Pointer(in.AWSElasticBlockStore)) - out.GitRepo = (*api.GitRepoVolumeSource)(unsafe.Pointer(in.GitRepo)) - out.Secret = (*api.SecretVolumeSource)(unsafe.Pointer(in.Secret)) - out.NFS = (*api.NFSVolumeSource)(unsafe.Pointer(in.NFS)) - out.ISCSI = (*api.ISCSIVolumeSource)(unsafe.Pointer(in.ISCSI)) - out.Glusterfs = (*api.GlusterfsVolumeSource)(unsafe.Pointer(in.Glusterfs)) - out.PersistentVolumeClaim = (*api.PersistentVolumeClaimVolumeSource)(unsafe.Pointer(in.PersistentVolumeClaim)) - out.RBD = (*api.RBDVolumeSource)(unsafe.Pointer(in.RBD)) - out.FlexVolume = (*api.FlexVolumeSource)(unsafe.Pointer(in.FlexVolume)) - out.Cinder = (*api.CinderVolumeSource)(unsafe.Pointer(in.Cinder)) - out.CephFS = (*api.CephFSVolumeSource)(unsafe.Pointer(in.CephFS)) - out.Flocker = (*api.FlockerVolumeSource)(unsafe.Pointer(in.Flocker)) - out.DownwardAPI = (*api.DownwardAPIVolumeSource)(unsafe.Pointer(in.DownwardAPI)) - out.FC = (*api.FCVolumeSource)(unsafe.Pointer(in.FC)) - out.AzureFile = (*api.AzureFileVolumeSource)(unsafe.Pointer(in.AzureFile)) - out.ConfigMap = (*api.ConfigMapVolumeSource)(unsafe.Pointer(in.ConfigMap)) - out.VsphereVolume = (*api.VsphereVirtualDiskVolumeSource)(unsafe.Pointer(in.VsphereVolume)) - out.Quobyte = (*api.QuobyteVolumeSource)(unsafe.Pointer(in.Quobyte)) - out.AzureDisk = (*api.AzureDiskVolumeSource)(unsafe.Pointer(in.AzureDisk)) - out.PhotonPersistentDisk = (*api.PhotonPersistentDiskVolumeSource)(unsafe.Pointer(in.PhotonPersistentDisk)) - out.Projected = (*api.ProjectedVolumeSource)(unsafe.Pointer(in.Projected)) - out.PortworxVolume = (*api.PortworxVolumeSource)(unsafe.Pointer(in.PortworxVolume)) - out.ScaleIO = (*api.ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO)) - out.StorageOS = (*api.StorageOSVolumeSource)(unsafe.Pointer(in.StorageOS)) - return nil -} - -// Convert_v1_VolumeSource_To_api_VolumeSource is an autogenerated conversion function. -func Convert_v1_VolumeSource_To_api_VolumeSource(in *v1.VolumeSource, out *api.VolumeSource, s conversion.Scope) error { - return autoConvert_v1_VolumeSource_To_api_VolumeSource(in, out, s) -} - -func autoConvert_api_VolumeSource_To_v1_VolumeSource(in *api.VolumeSource, out *v1.VolumeSource, s conversion.Scope) error { - out.HostPath = (*v1.HostPathVolumeSource)(unsafe.Pointer(in.HostPath)) - out.EmptyDir = (*v1.EmptyDirVolumeSource)(unsafe.Pointer(in.EmptyDir)) - out.GCEPersistentDisk = (*v1.GCEPersistentDiskVolumeSource)(unsafe.Pointer(in.GCEPersistentDisk)) - out.AWSElasticBlockStore = (*v1.AWSElasticBlockStoreVolumeSource)(unsafe.Pointer(in.AWSElasticBlockStore)) - out.GitRepo = (*v1.GitRepoVolumeSource)(unsafe.Pointer(in.GitRepo)) - out.Secret = (*v1.SecretVolumeSource)(unsafe.Pointer(in.Secret)) - out.NFS = (*v1.NFSVolumeSource)(unsafe.Pointer(in.NFS)) - out.ISCSI = (*v1.ISCSIVolumeSource)(unsafe.Pointer(in.ISCSI)) - out.Glusterfs = (*v1.GlusterfsVolumeSource)(unsafe.Pointer(in.Glusterfs)) - out.PersistentVolumeClaim = (*v1.PersistentVolumeClaimVolumeSource)(unsafe.Pointer(in.PersistentVolumeClaim)) - out.RBD = (*v1.RBDVolumeSource)(unsafe.Pointer(in.RBD)) - out.Quobyte = (*v1.QuobyteVolumeSource)(unsafe.Pointer(in.Quobyte)) - out.FlexVolume = (*v1.FlexVolumeSource)(unsafe.Pointer(in.FlexVolume)) - out.Cinder = (*v1.CinderVolumeSource)(unsafe.Pointer(in.Cinder)) - out.CephFS = (*v1.CephFSVolumeSource)(unsafe.Pointer(in.CephFS)) - out.Flocker = (*v1.FlockerVolumeSource)(unsafe.Pointer(in.Flocker)) - out.DownwardAPI = (*v1.DownwardAPIVolumeSource)(unsafe.Pointer(in.DownwardAPI)) - out.FC = (*v1.FCVolumeSource)(unsafe.Pointer(in.FC)) - out.AzureFile = (*v1.AzureFileVolumeSource)(unsafe.Pointer(in.AzureFile)) - out.ConfigMap = (*v1.ConfigMapVolumeSource)(unsafe.Pointer(in.ConfigMap)) - out.VsphereVolume = (*v1.VsphereVirtualDiskVolumeSource)(unsafe.Pointer(in.VsphereVolume)) - out.AzureDisk = (*v1.AzureDiskVolumeSource)(unsafe.Pointer(in.AzureDisk)) - out.PhotonPersistentDisk = (*v1.PhotonPersistentDiskVolumeSource)(unsafe.Pointer(in.PhotonPersistentDisk)) - out.Projected = (*v1.ProjectedVolumeSource)(unsafe.Pointer(in.Projected)) - out.PortworxVolume = (*v1.PortworxVolumeSource)(unsafe.Pointer(in.PortworxVolume)) - out.ScaleIO = (*v1.ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO)) - out.StorageOS = (*v1.StorageOSVolumeSource)(unsafe.Pointer(in.StorageOS)) - return nil -} - -// Convert_api_VolumeSource_To_v1_VolumeSource is an autogenerated conversion function. -func Convert_api_VolumeSource_To_v1_VolumeSource(in *api.VolumeSource, out *v1.VolumeSource, s conversion.Scope) error { - return autoConvert_api_VolumeSource_To_v1_VolumeSource(in, out, s) -} - -func autoConvert_v1_VsphereVirtualDiskVolumeSource_To_api_VsphereVirtualDiskVolumeSource(in *v1.VsphereVirtualDiskVolumeSource, out *api.VsphereVirtualDiskVolumeSource, s conversion.Scope) error { - out.VolumePath = in.VolumePath - out.FSType = in.FSType - out.StoragePolicyName = in.StoragePolicyName - out.StoragePolicyID = in.StoragePolicyID - return nil -} - -// Convert_v1_VsphereVirtualDiskVolumeSource_To_api_VsphereVirtualDiskVolumeSource is an autogenerated conversion function. -func Convert_v1_VsphereVirtualDiskVolumeSource_To_api_VsphereVirtualDiskVolumeSource(in *v1.VsphereVirtualDiskVolumeSource, out *api.VsphereVirtualDiskVolumeSource, s conversion.Scope) error { - return autoConvert_v1_VsphereVirtualDiskVolumeSource_To_api_VsphereVirtualDiskVolumeSource(in, out, s) -} - -func autoConvert_api_VsphereVirtualDiskVolumeSource_To_v1_VsphereVirtualDiskVolumeSource(in *api.VsphereVirtualDiskVolumeSource, out *v1.VsphereVirtualDiskVolumeSource, s conversion.Scope) error { - out.VolumePath = in.VolumePath - out.FSType = in.FSType - out.StoragePolicyName = in.StoragePolicyName - out.StoragePolicyID = in.StoragePolicyID - return nil -} - -// Convert_api_VsphereVirtualDiskVolumeSource_To_v1_VsphereVirtualDiskVolumeSource is an autogenerated conversion function. -func Convert_api_VsphereVirtualDiskVolumeSource_To_v1_VsphereVirtualDiskVolumeSource(in *api.VsphereVirtualDiskVolumeSource, out *v1.VsphereVirtualDiskVolumeSource, s conversion.Scope) error { - return autoConvert_api_VsphereVirtualDiskVolumeSource_To_v1_VsphereVirtualDiskVolumeSource(in, out, s) -} - -func autoConvert_v1_WeightedPodAffinityTerm_To_api_WeightedPodAffinityTerm(in *v1.WeightedPodAffinityTerm, out *api.WeightedPodAffinityTerm, s conversion.Scope) error { - out.Weight = in.Weight - if err := Convert_v1_PodAffinityTerm_To_api_PodAffinityTerm(&in.PodAffinityTerm, &out.PodAffinityTerm, s); err != nil { - return err - } - return nil -} - -// Convert_v1_WeightedPodAffinityTerm_To_api_WeightedPodAffinityTerm is an autogenerated conversion function. -func Convert_v1_WeightedPodAffinityTerm_To_api_WeightedPodAffinityTerm(in *v1.WeightedPodAffinityTerm, out *api.WeightedPodAffinityTerm, s conversion.Scope) error { - return autoConvert_v1_WeightedPodAffinityTerm_To_api_WeightedPodAffinityTerm(in, out, s) -} - -func autoConvert_api_WeightedPodAffinityTerm_To_v1_WeightedPodAffinityTerm(in *api.WeightedPodAffinityTerm, out *v1.WeightedPodAffinityTerm, s conversion.Scope) error { - out.Weight = in.Weight - if err := Convert_api_PodAffinityTerm_To_v1_PodAffinityTerm(&in.PodAffinityTerm, &out.PodAffinityTerm, s); err != nil { - return err - } - return nil -} - -// Convert_api_WeightedPodAffinityTerm_To_v1_WeightedPodAffinityTerm is an autogenerated conversion function. -func Convert_api_WeightedPodAffinityTerm_To_v1_WeightedPodAffinityTerm(in *api.WeightedPodAffinityTerm, out *v1.WeightedPodAffinityTerm, s conversion.Scope) error { - return autoConvert_api_WeightedPodAffinityTerm_To_v1_WeightedPodAffinityTerm(in, out, s) -} diff --git a/pkg/api/v1/zz_generated.defaults.go b/pkg/api/v1/zz_generated.defaults.go deleted file mode 100644 index c41d6ae3e37..00000000000 --- a/pkg/api/v1/zz_generated.defaults.go +++ /dev/null @@ -1,648 +0,0 @@ -// +build !ignore_autogenerated - -/* -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. -*/ - -// This file was autogenerated by defaulter-gen. Do not edit it manually! - -package v1 - -import ( - v1 "k8s.io/api/core/v1" - 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(&v1.ConfigMap{}, func(obj interface{}) { SetObjectDefaults_ConfigMap(obj.(*v1.ConfigMap)) }) - scheme.AddTypeDefaultingFunc(&v1.ConfigMapList{}, func(obj interface{}) { SetObjectDefaults_ConfigMapList(obj.(*v1.ConfigMapList)) }) - scheme.AddTypeDefaultingFunc(&v1.Endpoints{}, func(obj interface{}) { SetObjectDefaults_Endpoints(obj.(*v1.Endpoints)) }) - scheme.AddTypeDefaultingFunc(&v1.EndpointsList{}, func(obj interface{}) { SetObjectDefaults_EndpointsList(obj.(*v1.EndpointsList)) }) - scheme.AddTypeDefaultingFunc(&v1.LimitRange{}, func(obj interface{}) { SetObjectDefaults_LimitRange(obj.(*v1.LimitRange)) }) - scheme.AddTypeDefaultingFunc(&v1.LimitRangeList{}, func(obj interface{}) { SetObjectDefaults_LimitRangeList(obj.(*v1.LimitRangeList)) }) - scheme.AddTypeDefaultingFunc(&v1.Namespace{}, func(obj interface{}) { SetObjectDefaults_Namespace(obj.(*v1.Namespace)) }) - scheme.AddTypeDefaultingFunc(&v1.NamespaceList{}, func(obj interface{}) { SetObjectDefaults_NamespaceList(obj.(*v1.NamespaceList)) }) - scheme.AddTypeDefaultingFunc(&v1.Node{}, func(obj interface{}) { SetObjectDefaults_Node(obj.(*v1.Node)) }) - scheme.AddTypeDefaultingFunc(&v1.NodeList{}, func(obj interface{}) { SetObjectDefaults_NodeList(obj.(*v1.NodeList)) }) - scheme.AddTypeDefaultingFunc(&v1.PersistentVolume{}, func(obj interface{}) { SetObjectDefaults_PersistentVolume(obj.(*v1.PersistentVolume)) }) - scheme.AddTypeDefaultingFunc(&v1.PersistentVolumeClaim{}, func(obj interface{}) { SetObjectDefaults_PersistentVolumeClaim(obj.(*v1.PersistentVolumeClaim)) }) - scheme.AddTypeDefaultingFunc(&v1.PersistentVolumeClaimList{}, func(obj interface{}) { - SetObjectDefaults_PersistentVolumeClaimList(obj.(*v1.PersistentVolumeClaimList)) - }) - scheme.AddTypeDefaultingFunc(&v1.PersistentVolumeList{}, func(obj interface{}) { SetObjectDefaults_PersistentVolumeList(obj.(*v1.PersistentVolumeList)) }) - scheme.AddTypeDefaultingFunc(&v1.Pod{}, func(obj interface{}) { SetObjectDefaults_Pod(obj.(*v1.Pod)) }) - scheme.AddTypeDefaultingFunc(&v1.PodAttachOptions{}, func(obj interface{}) { SetObjectDefaults_PodAttachOptions(obj.(*v1.PodAttachOptions)) }) - scheme.AddTypeDefaultingFunc(&v1.PodExecOptions{}, func(obj interface{}) { SetObjectDefaults_PodExecOptions(obj.(*v1.PodExecOptions)) }) - scheme.AddTypeDefaultingFunc(&v1.PodList{}, func(obj interface{}) { SetObjectDefaults_PodList(obj.(*v1.PodList)) }) - scheme.AddTypeDefaultingFunc(&v1.PodTemplate{}, func(obj interface{}) { SetObjectDefaults_PodTemplate(obj.(*v1.PodTemplate)) }) - scheme.AddTypeDefaultingFunc(&v1.PodTemplateList{}, func(obj interface{}) { SetObjectDefaults_PodTemplateList(obj.(*v1.PodTemplateList)) }) - scheme.AddTypeDefaultingFunc(&v1.ReplicationController{}, func(obj interface{}) { SetObjectDefaults_ReplicationController(obj.(*v1.ReplicationController)) }) - scheme.AddTypeDefaultingFunc(&v1.ReplicationControllerList{}, func(obj interface{}) { - SetObjectDefaults_ReplicationControllerList(obj.(*v1.ReplicationControllerList)) - }) - scheme.AddTypeDefaultingFunc(&v1.ResourceQuota{}, func(obj interface{}) { SetObjectDefaults_ResourceQuota(obj.(*v1.ResourceQuota)) }) - scheme.AddTypeDefaultingFunc(&v1.ResourceQuotaList{}, func(obj interface{}) { SetObjectDefaults_ResourceQuotaList(obj.(*v1.ResourceQuotaList)) }) - scheme.AddTypeDefaultingFunc(&v1.Secret{}, func(obj interface{}) { SetObjectDefaults_Secret(obj.(*v1.Secret)) }) - scheme.AddTypeDefaultingFunc(&v1.SecretList{}, func(obj interface{}) { SetObjectDefaults_SecretList(obj.(*v1.SecretList)) }) - scheme.AddTypeDefaultingFunc(&v1.Service{}, func(obj interface{}) { SetObjectDefaults_Service(obj.(*v1.Service)) }) - scheme.AddTypeDefaultingFunc(&v1.ServiceList{}, func(obj interface{}) { SetObjectDefaults_ServiceList(obj.(*v1.ServiceList)) }) - return nil -} - -func SetObjectDefaults_ConfigMap(in *v1.ConfigMap) { - SetDefaults_ConfigMap(in) -} - -func SetObjectDefaults_ConfigMapList(in *v1.ConfigMapList) { - for i := range in.Items { - a := &in.Items[i] - SetObjectDefaults_ConfigMap(a) - } -} - -func SetObjectDefaults_Endpoints(in *v1.Endpoints) { - SetDefaults_Endpoints(in) -} - -func SetObjectDefaults_EndpointsList(in *v1.EndpointsList) { - for i := range in.Items { - a := &in.Items[i] - SetObjectDefaults_Endpoints(a) - } -} - -func SetObjectDefaults_LimitRange(in *v1.LimitRange) { - for i := range in.Spec.Limits { - a := &in.Spec.Limits[i] - SetDefaults_LimitRangeItem(a) - SetDefaults_ResourceList(&a.Max) - SetDefaults_ResourceList(&a.Min) - SetDefaults_ResourceList(&a.Default) - SetDefaults_ResourceList(&a.DefaultRequest) - SetDefaults_ResourceList(&a.MaxLimitRequestRatio) - } -} - -func SetObjectDefaults_LimitRangeList(in *v1.LimitRangeList) { - for i := range in.Items { - a := &in.Items[i] - SetObjectDefaults_LimitRange(a) - } -} - -func SetObjectDefaults_Namespace(in *v1.Namespace) { - SetDefaults_NamespaceStatus(&in.Status) -} - -func SetObjectDefaults_NamespaceList(in *v1.NamespaceList) { - for i := range in.Items { - a := &in.Items[i] - SetObjectDefaults_Namespace(a) - } -} - -func SetObjectDefaults_Node(in *v1.Node) { - SetDefaults_Node(in) - SetDefaults_NodeStatus(&in.Status) - SetDefaults_ResourceList(&in.Status.Capacity) - SetDefaults_ResourceList(&in.Status.Allocatable) -} - -func SetObjectDefaults_NodeList(in *v1.NodeList) { - for i := range in.Items { - a := &in.Items[i] - SetObjectDefaults_Node(a) - } -} - -func SetObjectDefaults_PersistentVolume(in *v1.PersistentVolume) { - SetDefaults_PersistentVolume(in) - SetDefaults_ResourceList(&in.Spec.Capacity) - if in.Spec.PersistentVolumeSource.HostPath != nil { - SetDefaults_HostPathVolumeSource(in.Spec.PersistentVolumeSource.HostPath) - } - if in.Spec.PersistentVolumeSource.RBD != nil { - SetDefaults_RBDVolumeSource(in.Spec.PersistentVolumeSource.RBD) - } - if in.Spec.PersistentVolumeSource.ISCSI != nil { - SetDefaults_ISCSIVolumeSource(in.Spec.PersistentVolumeSource.ISCSI) - } - if in.Spec.PersistentVolumeSource.AzureDisk != nil { - SetDefaults_AzureDiskVolumeSource(in.Spec.PersistentVolumeSource.AzureDisk) - } - if in.Spec.PersistentVolumeSource.ScaleIO != nil { - SetDefaults_ScaleIOVolumeSource(in.Spec.PersistentVolumeSource.ScaleIO) - } -} - -func SetObjectDefaults_PersistentVolumeClaim(in *v1.PersistentVolumeClaim) { - SetDefaults_PersistentVolumeClaim(in) - SetDefaults_ResourceList(&in.Spec.Resources.Limits) - SetDefaults_ResourceList(&in.Spec.Resources.Requests) - SetDefaults_ResourceList(&in.Status.Capacity) -} - -func SetObjectDefaults_PersistentVolumeClaimList(in *v1.PersistentVolumeClaimList) { - for i := range in.Items { - a := &in.Items[i] - SetObjectDefaults_PersistentVolumeClaim(a) - } -} - -func SetObjectDefaults_PersistentVolumeList(in *v1.PersistentVolumeList) { - for i := range in.Items { - a := &in.Items[i] - SetObjectDefaults_PersistentVolume(a) - } -} - -func SetObjectDefaults_Pod(in *v1.Pod) { - SetDefaults_Pod(in) - SetDefaults_PodSpec(&in.Spec) - for i := range in.Spec.Volumes { - a := &in.Spec.Volumes[i] - SetDefaults_Volume(a) - if a.VolumeSource.HostPath != nil { - SetDefaults_HostPathVolumeSource(a.VolumeSource.HostPath) - } - if a.VolumeSource.Secret != nil { - SetDefaults_SecretVolumeSource(a.VolumeSource.Secret) - } - if a.VolumeSource.ISCSI != nil { - SetDefaults_ISCSIVolumeSource(a.VolumeSource.ISCSI) - } - if a.VolumeSource.RBD != nil { - SetDefaults_RBDVolumeSource(a.VolumeSource.RBD) - } - if a.VolumeSource.DownwardAPI != nil { - SetDefaults_DownwardAPIVolumeSource(a.VolumeSource.DownwardAPI) - for j := range a.VolumeSource.DownwardAPI.Items { - b := &a.VolumeSource.DownwardAPI.Items[j] - if b.FieldRef != nil { - SetDefaults_ObjectFieldSelector(b.FieldRef) - } - } - } - if a.VolumeSource.ConfigMap != nil { - SetDefaults_ConfigMapVolumeSource(a.VolumeSource.ConfigMap) - } - if a.VolumeSource.AzureDisk != nil { - SetDefaults_AzureDiskVolumeSource(a.VolumeSource.AzureDisk) - } - if a.VolumeSource.Projected != nil { - SetDefaults_ProjectedVolumeSource(a.VolumeSource.Projected) - for j := range a.VolumeSource.Projected.Sources { - b := &a.VolumeSource.Projected.Sources[j] - if b.DownwardAPI != nil { - for k := range b.DownwardAPI.Items { - c := &b.DownwardAPI.Items[k] - if c.FieldRef != nil { - SetDefaults_ObjectFieldSelector(c.FieldRef) - } - } - } - } - } - if a.VolumeSource.ScaleIO != nil { - SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) - } - } - for i := range in.Spec.InitContainers { - a := &in.Spec.InitContainers[i] - SetDefaults_Container(a) - for j := range a.Ports { - b := &a.Ports[j] - SetDefaults_ContainerPort(b) - } - for j := range a.Env { - b := &a.Env[j] - if b.ValueFrom != nil { - if b.ValueFrom.FieldRef != nil { - SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) - } - } - } - SetDefaults_ResourceList(&a.Resources.Limits) - SetDefaults_ResourceList(&a.Resources.Requests) - if a.LivenessProbe != nil { - SetDefaults_Probe(a.LivenessProbe) - if a.LivenessProbe.Handler.HTTPGet != nil { - SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) - } - } - if a.ReadinessProbe != nil { - SetDefaults_Probe(a.ReadinessProbe) - if a.ReadinessProbe.Handler.HTTPGet != nil { - SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) - } - } - if a.Lifecycle != nil { - if a.Lifecycle.PostStart != nil { - if a.Lifecycle.PostStart.HTTPGet != nil { - SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) - } - } - if a.Lifecycle.PreStop != nil { - if a.Lifecycle.PreStop.HTTPGet != nil { - SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) - } - } - } - } - for i := range in.Spec.Containers { - a := &in.Spec.Containers[i] - SetDefaults_Container(a) - for j := range a.Ports { - b := &a.Ports[j] - SetDefaults_ContainerPort(b) - } - for j := range a.Env { - b := &a.Env[j] - if b.ValueFrom != nil { - if b.ValueFrom.FieldRef != nil { - SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) - } - } - } - SetDefaults_ResourceList(&a.Resources.Limits) - SetDefaults_ResourceList(&a.Resources.Requests) - if a.LivenessProbe != nil { - SetDefaults_Probe(a.LivenessProbe) - if a.LivenessProbe.Handler.HTTPGet != nil { - SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) - } - } - if a.ReadinessProbe != nil { - SetDefaults_Probe(a.ReadinessProbe) - if a.ReadinessProbe.Handler.HTTPGet != nil { - SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) - } - } - if a.Lifecycle != nil { - if a.Lifecycle.PostStart != nil { - if a.Lifecycle.PostStart.HTTPGet != nil { - SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) - } - } - if a.Lifecycle.PreStop != nil { - if a.Lifecycle.PreStop.HTTPGet != nil { - SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) - } - } - } - } -} - -func SetObjectDefaults_PodAttachOptions(in *v1.PodAttachOptions) { - SetDefaults_PodAttachOptions(in) -} - -func SetObjectDefaults_PodExecOptions(in *v1.PodExecOptions) { - SetDefaults_PodExecOptions(in) -} - -func SetObjectDefaults_PodList(in *v1.PodList) { - for i := range in.Items { - a := &in.Items[i] - SetObjectDefaults_Pod(a) - } -} - -func SetObjectDefaults_PodTemplate(in *v1.PodTemplate) { - SetDefaults_PodSpec(&in.Template.Spec) - for i := range in.Template.Spec.Volumes { - a := &in.Template.Spec.Volumes[i] - SetDefaults_Volume(a) - if a.VolumeSource.HostPath != nil { - SetDefaults_HostPathVolumeSource(a.VolumeSource.HostPath) - } - if a.VolumeSource.Secret != nil { - SetDefaults_SecretVolumeSource(a.VolumeSource.Secret) - } - if a.VolumeSource.ISCSI != nil { - SetDefaults_ISCSIVolumeSource(a.VolumeSource.ISCSI) - } - if a.VolumeSource.RBD != nil { - SetDefaults_RBDVolumeSource(a.VolumeSource.RBD) - } - if a.VolumeSource.DownwardAPI != nil { - SetDefaults_DownwardAPIVolumeSource(a.VolumeSource.DownwardAPI) - for j := range a.VolumeSource.DownwardAPI.Items { - b := &a.VolumeSource.DownwardAPI.Items[j] - if b.FieldRef != nil { - SetDefaults_ObjectFieldSelector(b.FieldRef) - } - } - } - if a.VolumeSource.ConfigMap != nil { - SetDefaults_ConfigMapVolumeSource(a.VolumeSource.ConfigMap) - } - if a.VolumeSource.AzureDisk != nil { - SetDefaults_AzureDiskVolumeSource(a.VolumeSource.AzureDisk) - } - if a.VolumeSource.Projected != nil { - SetDefaults_ProjectedVolumeSource(a.VolumeSource.Projected) - for j := range a.VolumeSource.Projected.Sources { - b := &a.VolumeSource.Projected.Sources[j] - if b.DownwardAPI != nil { - for k := range b.DownwardAPI.Items { - c := &b.DownwardAPI.Items[k] - if c.FieldRef != nil { - SetDefaults_ObjectFieldSelector(c.FieldRef) - } - } - } - } - } - if a.VolumeSource.ScaleIO != nil { - SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) - } - } - for i := range in.Template.Spec.InitContainers { - a := &in.Template.Spec.InitContainers[i] - SetDefaults_Container(a) - for j := range a.Ports { - b := &a.Ports[j] - SetDefaults_ContainerPort(b) - } - for j := range a.Env { - b := &a.Env[j] - if b.ValueFrom != nil { - if b.ValueFrom.FieldRef != nil { - SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) - } - } - } - SetDefaults_ResourceList(&a.Resources.Limits) - SetDefaults_ResourceList(&a.Resources.Requests) - if a.LivenessProbe != nil { - SetDefaults_Probe(a.LivenessProbe) - if a.LivenessProbe.Handler.HTTPGet != nil { - SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) - } - } - if a.ReadinessProbe != nil { - SetDefaults_Probe(a.ReadinessProbe) - if a.ReadinessProbe.Handler.HTTPGet != nil { - SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) - } - } - if a.Lifecycle != nil { - if a.Lifecycle.PostStart != nil { - if a.Lifecycle.PostStart.HTTPGet != nil { - SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) - } - } - if a.Lifecycle.PreStop != nil { - if a.Lifecycle.PreStop.HTTPGet != nil { - SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) - } - } - } - } - for i := range in.Template.Spec.Containers { - a := &in.Template.Spec.Containers[i] - SetDefaults_Container(a) - for j := range a.Ports { - b := &a.Ports[j] - SetDefaults_ContainerPort(b) - } - for j := range a.Env { - b := &a.Env[j] - if b.ValueFrom != nil { - if b.ValueFrom.FieldRef != nil { - SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) - } - } - } - SetDefaults_ResourceList(&a.Resources.Limits) - SetDefaults_ResourceList(&a.Resources.Requests) - if a.LivenessProbe != nil { - SetDefaults_Probe(a.LivenessProbe) - if a.LivenessProbe.Handler.HTTPGet != nil { - SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) - } - } - if a.ReadinessProbe != nil { - SetDefaults_Probe(a.ReadinessProbe) - if a.ReadinessProbe.Handler.HTTPGet != nil { - SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) - } - } - if a.Lifecycle != nil { - if a.Lifecycle.PostStart != nil { - if a.Lifecycle.PostStart.HTTPGet != nil { - SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) - } - } - if a.Lifecycle.PreStop != nil { - if a.Lifecycle.PreStop.HTTPGet != nil { - SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) - } - } - } - } -} - -func SetObjectDefaults_PodTemplateList(in *v1.PodTemplateList) { - for i := range in.Items { - a := &in.Items[i] - SetObjectDefaults_PodTemplate(a) - } -} - -func SetObjectDefaults_ReplicationController(in *v1.ReplicationController) { - SetDefaults_ReplicationController(in) - if in.Spec.Template != nil { - SetDefaults_PodSpec(&in.Spec.Template.Spec) - for i := range in.Spec.Template.Spec.Volumes { - a := &in.Spec.Template.Spec.Volumes[i] - SetDefaults_Volume(a) - if a.VolumeSource.HostPath != nil { - SetDefaults_HostPathVolumeSource(a.VolumeSource.HostPath) - } - if a.VolumeSource.Secret != nil { - SetDefaults_SecretVolumeSource(a.VolumeSource.Secret) - } - if a.VolumeSource.ISCSI != nil { - SetDefaults_ISCSIVolumeSource(a.VolumeSource.ISCSI) - } - if a.VolumeSource.RBD != nil { - SetDefaults_RBDVolumeSource(a.VolumeSource.RBD) - } - if a.VolumeSource.DownwardAPI != nil { - SetDefaults_DownwardAPIVolumeSource(a.VolumeSource.DownwardAPI) - for j := range a.VolumeSource.DownwardAPI.Items { - b := &a.VolumeSource.DownwardAPI.Items[j] - if b.FieldRef != nil { - SetDefaults_ObjectFieldSelector(b.FieldRef) - } - } - } - if a.VolumeSource.ConfigMap != nil { - SetDefaults_ConfigMapVolumeSource(a.VolumeSource.ConfigMap) - } - if a.VolumeSource.AzureDisk != nil { - SetDefaults_AzureDiskVolumeSource(a.VolumeSource.AzureDisk) - } - if a.VolumeSource.Projected != nil { - SetDefaults_ProjectedVolumeSource(a.VolumeSource.Projected) - for j := range a.VolumeSource.Projected.Sources { - b := &a.VolumeSource.Projected.Sources[j] - if b.DownwardAPI != nil { - for k := range b.DownwardAPI.Items { - c := &b.DownwardAPI.Items[k] - if c.FieldRef != nil { - SetDefaults_ObjectFieldSelector(c.FieldRef) - } - } - } - } - } - if a.VolumeSource.ScaleIO != nil { - SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) - } - } - for i := range in.Spec.Template.Spec.InitContainers { - a := &in.Spec.Template.Spec.InitContainers[i] - SetDefaults_Container(a) - for j := range a.Ports { - b := &a.Ports[j] - SetDefaults_ContainerPort(b) - } - for j := range a.Env { - b := &a.Env[j] - if b.ValueFrom != nil { - if b.ValueFrom.FieldRef != nil { - SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) - } - } - } - SetDefaults_ResourceList(&a.Resources.Limits) - SetDefaults_ResourceList(&a.Resources.Requests) - if a.LivenessProbe != nil { - SetDefaults_Probe(a.LivenessProbe) - if a.LivenessProbe.Handler.HTTPGet != nil { - SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) - } - } - if a.ReadinessProbe != nil { - SetDefaults_Probe(a.ReadinessProbe) - if a.ReadinessProbe.Handler.HTTPGet != nil { - SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) - } - } - if a.Lifecycle != nil { - if a.Lifecycle.PostStart != nil { - if a.Lifecycle.PostStart.HTTPGet != nil { - SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) - } - } - if a.Lifecycle.PreStop != nil { - if a.Lifecycle.PreStop.HTTPGet != nil { - SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) - } - } - } - } - for i := range in.Spec.Template.Spec.Containers { - a := &in.Spec.Template.Spec.Containers[i] - SetDefaults_Container(a) - for j := range a.Ports { - b := &a.Ports[j] - SetDefaults_ContainerPort(b) - } - for j := range a.Env { - b := &a.Env[j] - if b.ValueFrom != nil { - if b.ValueFrom.FieldRef != nil { - SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) - } - } - } - SetDefaults_ResourceList(&a.Resources.Limits) - SetDefaults_ResourceList(&a.Resources.Requests) - if a.LivenessProbe != nil { - SetDefaults_Probe(a.LivenessProbe) - if a.LivenessProbe.Handler.HTTPGet != nil { - SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) - } - } - if a.ReadinessProbe != nil { - SetDefaults_Probe(a.ReadinessProbe) - if a.ReadinessProbe.Handler.HTTPGet != nil { - SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) - } - } - if a.Lifecycle != nil { - if a.Lifecycle.PostStart != nil { - if a.Lifecycle.PostStart.HTTPGet != nil { - SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) - } - } - if a.Lifecycle.PreStop != nil { - if a.Lifecycle.PreStop.HTTPGet != nil { - SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) - } - } - } - } - } -} - -func SetObjectDefaults_ReplicationControllerList(in *v1.ReplicationControllerList) { - for i := range in.Items { - a := &in.Items[i] - SetObjectDefaults_ReplicationController(a) - } -} - -func SetObjectDefaults_ResourceQuota(in *v1.ResourceQuota) { - SetDefaults_ResourceList(&in.Spec.Hard) - SetDefaults_ResourceList(&in.Status.Hard) - SetDefaults_ResourceList(&in.Status.Used) -} - -func SetObjectDefaults_ResourceQuotaList(in *v1.ResourceQuotaList) { - for i := range in.Items { - a := &in.Items[i] - SetObjectDefaults_ResourceQuota(a) - } -} - -func SetObjectDefaults_Secret(in *v1.Secret) { - SetDefaults_Secret(in) -} - -func SetObjectDefaults_SecretList(in *v1.SecretList) { - for i := range in.Items { - a := &in.Items[i] - SetObjectDefaults_Secret(a) - } -} - -func SetObjectDefaults_Service(in *v1.Service) { - SetDefaults_Service(in) -} - -func SetObjectDefaults_ServiceList(in *v1.ServiceList) { - for i := range in.Items { - a := &in.Items[i] - SetObjectDefaults_Service(a) - } -} diff --git a/pkg/api/validation/BUILD b/pkg/api/validation/BUILD deleted file mode 100644 index 684e46f242d..00000000000 --- a/pkg/api/validation/BUILD +++ /dev/null @@ -1,80 +0,0 @@ -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = [ - "doc.go", - "events.go", - "validation.go", - ], - importpath = "k8s.io/kubernetes/pkg/api/validation", - visibility = ["//visibility:public"], - deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", - "//pkg/api/service:go_default_library", - "//pkg/api/v1:go_default_library", - "//pkg/api/v1/helper:go_default_library", - "//pkg/capabilities:go_default_library", - "//pkg/features:go_default_library", - "//pkg/security/apparmor:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/validation:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/validation:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", - "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = [ - "events_test.go", - "validation_test.go", - ], - importpath = "k8s.io/kubernetes/pkg/api/validation", - library = ":go_default_library", - deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", - "//pkg/api/testapi:go_default_library", - "//pkg/capabilities:go_default_library", - "//pkg/security/apparmor:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", - "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], - visibility = ["//visibility:public"], -) diff --git a/pkg/api/validation/doc.go b/pkg/api/validation/doc.go deleted file mode 100644 index 30f541de32b..00000000000 --- a/pkg/api/validation/doc.go +++ /dev/null @@ -1,19 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES 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 has functions for validating the correctness of api -// objects and explaining what is wrong with them when they aren't valid. -package validation // import "k8s.io/kubernetes/pkg/api/validation" diff --git a/pkg/api/validation/events.go b/pkg/api/validation/events.go deleted file mode 100644 index 890a6cce4f7..00000000000 --- a/pkg/api/validation/events.go +++ /dev/null @@ -1,83 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES 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" - - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation" - "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" -) - -// ValidateEvent makes sure that the event makes sense. -func ValidateEvent(event *api.Event) field.ErrorList { - allErrs := field.ErrorList{} - - // Make sure event.Namespace and the involvedObject.Namespace agree - if len(event.InvolvedObject.Namespace) == 0 { - // event.Namespace must also be empty (or "default", for compatibility with old clients) - if event.Namespace != metav1.NamespaceNone && event.Namespace != metav1.NamespaceDefault { - allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match event.namespace")) - } - } else { - // event namespace must match - if event.Namespace != event.InvolvedObject.Namespace { - allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match event.namespace")) - } - } - - // For kinds we recognize, make sure involvedObject.Namespace is set for namespaced kinds - if namespaced, err := isNamespacedKind(event.InvolvedObject.Kind, event.InvolvedObject.APIVersion); err == nil { - if namespaced && len(event.InvolvedObject.Namespace) == 0 { - allErrs = append(allErrs, field.Required(field.NewPath("involvedObject", "namespace"), fmt.Sprintf("required for kind %s", event.InvolvedObject.Kind))) - } - if !namespaced && len(event.InvolvedObject.Namespace) > 0 { - allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, fmt.Sprintf("not allowed for kind %s", event.InvolvedObject.Kind))) - } - } - - for _, msg := range validation.IsDNS1123Subdomain(event.Namespace) { - allErrs = append(allErrs, field.Invalid(field.NewPath("namespace"), event.Namespace, msg)) - } - return allErrs -} - -// Check whether the kind in groupVersion is scoped at the root of the api hierarchy -func isNamespacedKind(kind, groupVersion string) (bool, error) { - gv, err := schema.ParseGroupVersion(groupVersion) - if err != nil { - return false, err - } - g, err := api.Registry.Group(gv.Group) - if err != nil { - return false, err - } - - restMapping, err := g.RESTMapper.RESTMapping(schema.GroupKind{Group: gv.Group, Kind: kind}, gv.Version) - if err != nil { - return false, err - } - scopeName := restMapping.Scope.Name() - if scopeName == meta.RESTScopeNameNamespace { - return true, nil - } - return false, nil -} diff --git a/pkg/api/validation/events_test.go b/pkg/api/validation/events_test.go deleted file mode 100644 index 0df784f6dff..00000000000 --- a/pkg/api/validation/events_test.go +++ /dev/null @@ -1,221 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES 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/kubernetes/pkg/api" -) - -func TestValidateEvent(t *testing.T) { - table := []struct { - *api.Event - valid bool - }{ - { - &api.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test1", - Namespace: "foo", - }, - InvolvedObject: api.ObjectReference{ - Namespace: "bar", - Kind: "Pod", - }, - }, - false, - }, { - &api.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test2", - Namespace: "aoeu-_-aoeu", - }, - InvolvedObject: api.ObjectReference{ - Namespace: "aoeu-_-aoeu", - Kind: "Pod", - }, - }, - false, - }, { - &api.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test3", - Namespace: metav1.NamespaceDefault, - }, - InvolvedObject: api.ObjectReference{ - APIVersion: "v1", - Kind: "Node", - }, - }, - true, - }, { - &api.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test4", - Namespace: metav1.NamespaceDefault, - }, - InvolvedObject: api.ObjectReference{ - APIVersion: "v1", - Kind: "Namespace", - }, - }, - true, - }, { - &api.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test5", - Namespace: metav1.NamespaceDefault, - }, - InvolvedObject: api.ObjectReference{ - APIVersion: "extensions/v1beta1", - Kind: "NoKind", - Namespace: metav1.NamespaceDefault, - }, - }, - true, - }, { - &api.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test6", - Namespace: metav1.NamespaceDefault, - }, - InvolvedObject: api.ObjectReference{ - APIVersion: "extensions/v1beta1", - Kind: "Job", - Namespace: "foo", - }, - }, - false, - }, { - &api.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test7", - Namespace: metav1.NamespaceDefault, - }, - InvolvedObject: api.ObjectReference{ - APIVersion: "extensions/v1beta1", - Kind: "Job", - Namespace: metav1.NamespaceDefault, - }, - }, - true, - }, { - &api.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test8", - Namespace: metav1.NamespaceDefault, - }, - InvolvedObject: api.ObjectReference{ - APIVersion: "other/v1beta1", - Kind: "Job", - Namespace: "foo", - }, - }, - false, - }, { - &api.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test9", - Namespace: "foo", - }, - InvolvedObject: api.ObjectReference{ - APIVersion: "other/v1beta1", - Kind: "Job", - Namespace: "foo", - }, - }, - true, - }, { - &api.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test10", - Namespace: metav1.NamespaceDefault, - }, - InvolvedObject: api.ObjectReference{ - APIVersion: "extensions", - Kind: "Job", - Namespace: "foo", - }, - }, - false, - }, { - &api.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test11", - Namespace: "foo", - }, - InvolvedObject: api.ObjectReference{ - // must register in v1beta1 to be true - APIVersion: "extensions/v1beta1", - Kind: "Job", - Namespace: "foo", - }, - }, - true, - }, - { - &api.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test12", - Namespace: "foo", - }, - InvolvedObject: api.ObjectReference{ - APIVersion: "other/v1beta1", - Kind: "FooBar", - Namespace: "bar", - }, - }, - false, - }, - { - &api.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test13", - Namespace: "", - }, - InvolvedObject: api.ObjectReference{ - APIVersion: "other/v1beta1", - Kind: "FooBar", - Namespace: "bar", - }, - }, - false, - }, - { - &api.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test14", - Namespace: "foo", - }, - InvolvedObject: api.ObjectReference{ - APIVersion: "other/v1beta1", - Kind: "FooBar", - Namespace: "", - }, - }, - false, - }, - } - - for _, item := range table { - if e, a := item.valid, len(ValidateEvent(item.Event)) == 0; e != a { - t.Errorf("%v: expected %v, got %v", item.Event.Name, e, a) - } - } -} diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go deleted file mode 100644 index 3d08bbe51ee..00000000000 --- a/pkg/api/validation/validation.go +++ /dev/null @@ -1,4491 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES 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 ( - "encoding/json" - "fmt" - "math" - "net" - "path" - "path/filepath" - "reflect" - "regexp" - "strings" - - "github.com/golang/glog" - - "k8s.io/api/core/v1" - apiequality "k8s.io/apimachinery/pkg/api/equality" - "k8s.io/apimachinery/pkg/api/resource" - apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" - genericvalidation "k8s.io/apimachinery/pkg/api/validation" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/util/diff" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/validation" - "k8s.io/apimachinery/pkg/util/validation/field" - utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper" - apiservice "k8s.io/kubernetes/pkg/api/service" - k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" - "k8s.io/kubernetes/pkg/capabilities" - "k8s.io/kubernetes/pkg/features" - "k8s.io/kubernetes/pkg/security/apparmor" -) - -// TODO: delete this global variable when we enable the validation of common -// fields by default. -var RepairMalformedUpdates bool = genericvalidation.RepairMalformedUpdates - -const isNegativeErrorMsg string = apimachineryvalidation.IsNegativeErrorMsg -const isInvalidQuotaResource string = `must be a standard resource for quota` -const fieldImmutableErrorMsg string = genericvalidation.FieldImmutableErrorMsg -const isNotIntegerErrorMsg string = `must be an integer` - -var pdPartitionErrorMsg string = validation.InclusiveRangeError(1, 255) -var volumeModeErrorMsg string = "must be a number between 0 and 0777 (octal), both inclusive" - -// BannedOwners is a black list of object that are not allowed to be owners. -var BannedOwners = genericvalidation.BannedOwners - -var iscsiInitiatorIqnRegex = regexp.MustCompile(`iqn\.\d{4}-\d{2}\.([[:alnum:]-.]+)(:[^,;*&$|\s]+)$`) -var iscsiInitiatorEuiRegex = regexp.MustCompile(`^eui.[[:alnum:]]{16}$`) -var iscsiInitiatorNaaRegex = regexp.MustCompile(`^naa.[[:alnum:]]{32}$`) - -// ValidateHasLabel requires that metav1.ObjectMeta has a Label with key and expectedValue -func ValidateHasLabel(meta metav1.ObjectMeta, fldPath *field.Path, key, expectedValue string) field.ErrorList { - allErrs := field.ErrorList{} - actualValue, found := meta.Labels[key] - if !found { - allErrs = append(allErrs, field.Required(fldPath.Child("labels").Key(key), - fmt.Sprintf("must be '%s'", expectedValue))) - return allErrs - } - if actualValue != expectedValue { - allErrs = append(allErrs, field.Invalid(fldPath.Child("labels").Key(key), meta.Labels, - fmt.Sprintf("must be '%s'", expectedValue))) - } - return allErrs -} - -// ValidateAnnotations validates that a set of annotations are correctly defined. -func ValidateAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { - return genericvalidation.ValidateAnnotations(annotations, fldPath) -} - -func ValidateDNS1123Label(value string, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - for _, msg := range validation.IsDNS1123Label(value) { - allErrs = append(allErrs, field.Invalid(fldPath, value, msg)) - } - return allErrs -} - -// ValidateDNS1123Subdomain validates that a name is a proper DNS subdomain. -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)) - } - return allErrs -} - -func ValidatePodSpecificAnnotations(annotations map[string]string, spec *api.PodSpec, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - if value, isMirror := annotations[api.MirrorPodAnnotationKey]; isMirror { - if len(spec.NodeName) == 0 { - allErrs = append(allErrs, field.Invalid(fldPath.Key(api.MirrorPodAnnotationKey), value, "must set spec.nodeName if mirror pod annotation is set")) - } - } - - if annotations[api.TolerationsAnnotationKey] != "" { - allErrs = append(allErrs, ValidateTolerationsInPodAnnotations(annotations, fldPath)...) - } - - allErrs = append(allErrs, ValidateSeccompPodAnnotations(annotations, fldPath)...) - allErrs = append(allErrs, ValidateAppArmorPodAnnotations(annotations, spec, fldPath)...) - - sysctls, err := helper.SysctlsFromPodAnnotation(annotations[api.SysctlsPodAnnotationKey]) - if err != nil { - allErrs = append(allErrs, field.Invalid(fldPath.Key(api.SysctlsPodAnnotationKey), annotations[api.SysctlsPodAnnotationKey], err.Error())) - } else { - allErrs = append(allErrs, validateSysctls(sysctls, fldPath.Key(api.SysctlsPodAnnotationKey))...) - } - unsafeSysctls, err := helper.SysctlsFromPodAnnotation(annotations[api.UnsafeSysctlsPodAnnotationKey]) - if err != nil { - allErrs = append(allErrs, field.Invalid(fldPath.Key(api.UnsafeSysctlsPodAnnotationKey), annotations[api.UnsafeSysctlsPodAnnotationKey], err.Error())) - } else { - allErrs = append(allErrs, validateSysctls(unsafeSysctls, fldPath.Key(api.UnsafeSysctlsPodAnnotationKey))...) - } - inBoth := sysctlIntersection(sysctls, unsafeSysctls) - if len(inBoth) > 0 { - allErrs = append(allErrs, field.Invalid(fldPath.Key(api.UnsafeSysctlsPodAnnotationKey), strings.Join(inBoth, ", "), "can not be safe and unsafe")) - } - - return allErrs -} - -// ValidateTolerationsInPodAnnotations tests that the serialized tolerations in Pod.Annotations has valid data -func ValidateTolerationsInPodAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - tolerations, err := helper.GetTolerationsFromPodAnnotations(annotations) - if err != nil { - allErrs = append(allErrs, field.Invalid(fldPath, api.TolerationsAnnotationKey, err.Error())) - return allErrs - } - - if len(tolerations) > 0 { - allErrs = append(allErrs, ValidateTolerations(tolerations, fldPath.Child(api.TolerationsAnnotationKey))...) - } - - return allErrs -} - -func ValidatePodSpecificAnnotationUpdates(newPod, oldPod *api.Pod, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - newAnnotations := newPod.Annotations - oldAnnotations := oldPod.Annotations - for k, oldVal := range oldAnnotations { - if newVal, exists := newAnnotations[k]; exists && newVal == oldVal { - continue // No change. - } - if strings.HasPrefix(k, apparmor.ContainerAnnotationKeyPrefix) { - allErrs = append(allErrs, field.Forbidden(fldPath.Key(k), "may not remove or update AppArmor annotations")) - } - if k == api.MirrorPodAnnotationKey { - allErrs = append(allErrs, field.Forbidden(fldPath.Key(k), "may not remove or update mirror pod annotation")) - } - } - // Check for additions - for k := range newAnnotations { - if _, ok := oldAnnotations[k]; ok { - continue // No change. - } - if strings.HasPrefix(k, apparmor.ContainerAnnotationKeyPrefix) { - allErrs = append(allErrs, field.Forbidden(fldPath.Key(k), "may not add AppArmor annotations")) - } - if k == api.MirrorPodAnnotationKey { - allErrs = append(allErrs, field.Forbidden(fldPath.Key(k), "may not add mirror pod annotation")) - } - } - allErrs = append(allErrs, ValidatePodSpecificAnnotations(newAnnotations, &newPod.Spec, fldPath)...) - return allErrs -} - -func ValidateEndpointsSpecificAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - return allErrs -} - -func ValidateOwnerReferences(ownerReferences []metav1.OwnerReference, fldPath *field.Path) field.ErrorList { - return genericvalidation.ValidateOwnerReferences(ownerReferences, fldPath) -} - -// ValidateNameFunc validates that the provided name is valid for a given resource type. -// Not all resources have the same validation rules for names. Prefix is true -// if the name will have a value appended to it. If the name is not valid, -// this returns a list of descriptions of individual characteristics of the -// value that were not valid. Otherwise this returns an empty list or nil. -type ValidateNameFunc apimachineryvalidation.ValidateNameFunc - -// maskTrailingDash replaces the final character of a string with a subdomain safe -// value if is a dash. -func maskTrailingDash(name string) string { - if strings.HasSuffix(name, "-") { - return name[:len(name)-2] + "a" - } - return name -} - -// ValidatePodName can be used to check whether the given pod name is valid. -// Prefix indicates this name will be used as part of generation, in which case -// trailing dashes are allowed. -var ValidatePodName = NameIsDNSSubdomain - -// ValidateReplicationControllerName can be used to check whether the given replication -// controller name is valid. -// Prefix indicates this name will be used as part of generation, in which case -// trailing dashes are allowed. -var ValidateReplicationControllerName = NameIsDNSSubdomain - -// ValidateServiceName can be used to check whether the given service name is valid. -// Prefix indicates this name will be used as part of generation, in which case -// trailing dashes are allowed. -var ValidateServiceName = NameIsDNS1035Label - -// ValidateNodeName can be used to check whether the given node name is valid. -// Prefix indicates this name will be used as part of generation, in which case -// trailing dashes are allowed. -var ValidateNodeName = NameIsDNSSubdomain - -// ValidateNamespaceName can be used to check whether the given namespace name is valid. -// Prefix indicates this name will be used as part of generation, in which case -// trailing dashes are allowed. -var ValidateNamespaceName = apimachineryvalidation.ValidateNamespaceName - -// ValidateLimitRangeName can be used to check whether the given limit range name is valid. -// Prefix indicates this name will be used as part of generation, in which case -// trailing dashes are allowed. -var ValidateLimitRangeName = NameIsDNSSubdomain - -// ValidateResourceQuotaName can be used to check whether the given -// resource quota name is valid. -// Prefix indicates this name will be used as part of generation, in which case -// trailing dashes are allowed. -var ValidateResourceQuotaName = NameIsDNSSubdomain - -// ValidateSecretName can be used to check whether the given secret name is valid. -// Prefix indicates this name will be used as part of generation, in which case -// trailing dashes are allowed. -var ValidateSecretName = NameIsDNSSubdomain - -// ValidateServiceAccountName can be used to check whether the given service account name is valid. -// Prefix indicates this name will be used as part of generation, in which case -// trailing dashes are allowed. -var ValidateServiceAccountName = apimachineryvalidation.ValidateServiceAccountName - -// ValidateEndpointsName can be used to check whether the given endpoints name is valid. -// Prefix indicates this name will be used as part of generation, in which case -// trailing dashes are allowed. -var ValidateEndpointsName = NameIsDNSSubdomain - -// ValidateClusterName can be used to check whether the given cluster name is valid. -var ValidateClusterName = genericvalidation.ValidateClusterName - -// ValidateClassName can be used to check whether the given class name is valid. -// It is defined here to avoid import cycle between pkg/apis/storage/validation -// (where it should be) and this file. -var ValidateClassName = NameIsDNSSubdomain - -// ValidatePiorityClassName can be used to check whether the given priority -// class name is valid. -var ValidatePriorityClassName = NameIsDNSSubdomain - -// TODO update all references to these functions to point to the genericvalidation ones -// NameIsDNSSubdomain is a ValidateNameFunc for names that must be a DNS subdomain. -func NameIsDNSSubdomain(name string, prefix bool) []string { - return apimachineryvalidation.NameIsDNSSubdomain(name, prefix) -} - -// NameIsDNSLabel is a ValidateNameFunc for names that must be a DNS 1123 label. -func NameIsDNSLabel(name string, prefix bool) []string { - return apimachineryvalidation.NameIsDNSLabel(name, prefix) -} - -// NameIsDNS1035Label is a ValidateNameFunc for names that must be a DNS 952 label. -func NameIsDNS1035Label(name string, prefix bool) []string { - return apimachineryvalidation.NameIsDNS1035Label(name, prefix) -} - -// Validates that given value is not negative. -func ValidateNonnegativeField(value int64, fldPath *field.Path) field.ErrorList { - return apimachineryvalidation.ValidateNonnegativeField(value, fldPath) -} - -// Validates that a Quantity is not negative -func ValidateNonnegativeQuantity(value resource.Quantity, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if value.Cmp(resource.Quantity{}) < 0 { - allErrs = append(allErrs, field.Invalid(fldPath, value.String(), isNegativeErrorMsg)) - } - return allErrs -} - -func ValidateImmutableField(newVal, oldVal interface{}, fldPath *field.Path) field.ErrorList { - return genericvalidation.ValidateImmutableField(newVal, oldVal, fldPath) -} - -func ValidateImmutableAnnotation(newVal string, oldVal string, annotation string, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - if oldVal != newVal { - allErrs = append(allErrs, field.Invalid(fldPath.Child("annotations", annotation), newVal, fieldImmutableErrorMsg)) - } - return allErrs -} - -// ValidateObjectMeta validates an object's metadata on creation. It expects that name generation has already -// been performed. -// It doesn't return an error for rootscoped resources with namespace, because namespace should already be cleared before. -// TODO: Remove calls to this method scattered in validations of specific resources, e.g., ValidatePodUpdate. -func ValidateObjectMeta(meta *metav1.ObjectMeta, requiresNamespace bool, nameFn ValidateNameFunc, fldPath *field.Path) field.ErrorList { - allErrs := genericvalidation.ValidateObjectMeta(meta, requiresNamespace, apimachineryvalidation.ValidateNameFunc(nameFn), fldPath) - // run additional checks for the finalizer name - for i := range meta.Finalizers { - allErrs = append(allErrs, validateKubeFinalizerName(string(meta.Finalizers[i]), fldPath.Child("finalizers").Index(i))...) - } - return allErrs -} - -// ValidateObjectMetaUpdate validates an object's metadata when updated -func ValidateObjectMetaUpdate(newMeta, oldMeta *metav1.ObjectMeta, fldPath *field.Path) field.ErrorList { - allErrs := genericvalidation.ValidateObjectMetaUpdate(newMeta, oldMeta, fldPath) - // run additional checks for the finalizer name - for i := range newMeta.Finalizers { - allErrs = append(allErrs, validateKubeFinalizerName(string(newMeta.Finalizers[i]), fldPath.Child("finalizers").Index(i))...) - } - - return allErrs -} - -func ValidateNoNewFinalizers(newFinalizers []string, oldFinalizers []string, fldPath *field.Path) field.ErrorList { - return genericvalidation.ValidateNoNewFinalizers(newFinalizers, oldFinalizers, fldPath) -} - -func ValidateVolumes(volumes []api.Volume, fldPath *field.Path) (sets.String, field.ErrorList) { - allErrs := field.ErrorList{} - - allNames := sets.String{} - for i, vol := range volumes { - idxPath := fldPath.Index(i) - namePath := idxPath.Child("name") - el := validateVolumeSource(&vol.VolumeSource, idxPath, vol.Name) - if len(vol.Name) == 0 { - el = append(el, field.Required(namePath, "")) - } else { - el = append(el, ValidateDNS1123Label(vol.Name, namePath)...) - } - if allNames.Has(vol.Name) { - el = append(el, field.Duplicate(namePath, vol.Name)) - } - if len(el) == 0 { - allNames.Insert(vol.Name) - } else { - allErrs = append(allErrs, el...) - } - - } - return allNames, allErrs -} - -func validateVolumeSource(source *api.VolumeSource, fldPath *field.Path, volName string) field.ErrorList { - numVolumes := 0 - allErrs := field.ErrorList{} - if source.EmptyDir != nil { - numVolumes++ - if !utilfeature.DefaultFeatureGate.Enabled(features.LocalStorageCapacityIsolation) { - if source.EmptyDir.SizeLimit != nil && source.EmptyDir.SizeLimit.Cmp(resource.Quantity{}) != 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("emptyDir").Child("sizeLimit"), "SizeLimit field disabled by feature-gate for EmptyDir volumes")) - } - } else { - if source.EmptyDir.SizeLimit != nil && source.EmptyDir.SizeLimit.Cmp(resource.Quantity{}) < 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("emptyDir").Child("sizeLimit"), "SizeLimit field must be a valid resource quantity")) - } - } - if !utilfeature.DefaultFeatureGate.Enabled(features.HugePages) && source.EmptyDir.Medium == api.StorageMediumHugePages { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("emptyDir").Child("medium"), "HugePages medium is disabled by feature-gate for EmptyDir volumes")) - } - } - if source.HostPath != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("hostPath"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateHostPathVolumeSource(source.HostPath, fldPath.Child("hostPath"))...) - } - } - if source.GitRepo != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("gitRepo"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateGitRepoVolumeSource(source.GitRepo, fldPath.Child("gitRepo"))...) - } - } - if source.GCEPersistentDisk != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("gcePersistentDisk"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateGCEPersistentDiskVolumeSource(source.GCEPersistentDisk, fldPath.Child("persistentDisk"))...) - } - } - if source.AWSElasticBlockStore != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("awsElasticBlockStore"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateAWSElasticBlockStoreVolumeSource(source.AWSElasticBlockStore, fldPath.Child("awsElasticBlockStore"))...) - } - } - if source.Secret != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("secret"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateSecretVolumeSource(source.Secret, fldPath.Child("secret"))...) - } - } - if source.NFS != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("nfs"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateNFSVolumeSource(source.NFS, fldPath.Child("nfs"))...) - } - } - if source.ISCSI != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("iscsi"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateISCSIVolumeSource(source.ISCSI, fldPath.Child("iscsi"))...) - } - if source.ISCSI.InitiatorName != nil && len(volName+":"+source.ISCSI.TargetPortal) > 64 { - tooLongErr := "Total length of : must be under 64 characters if iscsi.initiatorName is specified." - allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), volName, tooLongErr)) - } - } - if source.Glusterfs != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("glusterfs"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateGlusterfsVolumeSource(source.Glusterfs, fldPath.Child("glusterfs"))...) - } - } - if source.Flocker != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("flocker"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateFlockerVolumeSource(source.Flocker, fldPath.Child("flocker"))...) - } - } - if source.PersistentVolumeClaim != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("persistentVolumeClaim"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validatePersistentClaimVolumeSource(source.PersistentVolumeClaim, fldPath.Child("persistentVolumeClaim"))...) - } - } - if source.RBD != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("rbd"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateRBDVolumeSource(source.RBD, fldPath.Child("rbd"))...) - } - } - if source.Cinder != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("cinder"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateCinderVolumeSource(source.Cinder, fldPath.Child("cinder"))...) - } - } - if source.CephFS != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("cephFS"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateCephFSVolumeSource(source.CephFS, fldPath.Child("cephfs"))...) - } - } - if source.Quobyte != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("quobyte"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateQuobyteVolumeSource(source.Quobyte, fldPath.Child("quobyte"))...) - } - } - if source.DownwardAPI != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("downwarAPI"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateDownwardAPIVolumeSource(source.DownwardAPI, fldPath.Child("downwardAPI"))...) - } - } - if source.FC != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("fc"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateFCVolumeSource(source.FC, fldPath.Child("fc"))...) - } - } - if source.FlexVolume != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("flexVolume"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateFlexVolumeSource(source.FlexVolume, fldPath.Child("flexVolume"))...) - } - } - if source.ConfigMap != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("configMap"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateConfigMapVolumeSource(source.ConfigMap, fldPath.Child("configMap"))...) - } - } - - if source.AzureFile != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("azureFile"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateAzureFile(source.AzureFile, fldPath.Child("azureFile"))...) - } - } - - if source.VsphereVolume != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("vsphereVolume"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateVsphereVolumeSource(source.VsphereVolume, fldPath.Child("vsphereVolume"))...) - } - } - if source.PhotonPersistentDisk != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("photonPersistentDisk"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validatePhotonPersistentDiskVolumeSource(source.PhotonPersistentDisk, fldPath.Child("photonPersistentDisk"))...) - } - } - if source.PortworxVolume != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("portworxVolume"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validatePortworxVolumeSource(source.PortworxVolume, fldPath.Child("portworxVolume"))...) - } - } - if source.AzureDisk != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("azureDisk"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateAzureDisk(source.AzureDisk, fldPath.Child("azureDisk"))...) - } - } - if source.StorageOS != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("storageos"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateStorageOSVolumeSource(source.StorageOS, fldPath.Child("storageos"))...) - } - } - if source.Projected != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("projected"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateProjectedVolumeSource(source.Projected, fldPath.Child("projected"))...) - } - } - if source.ScaleIO != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("scaleIO"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateScaleIOVolumeSource(source.ScaleIO, fldPath.Child("scaleIO"))...) - } - } - - if numVolumes == 0 { - allErrs = append(allErrs, field.Required(fldPath, "must specify a volume type")) - } - - return allErrs -} - -func validateHostPathVolumeSource(hostPath *api.HostPathVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(hostPath.Path) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("path"), "")) - return allErrs - } - - allErrs = append(allErrs, validatePathNoBacksteps(hostPath.Path, fldPath.Child("path"))...) - allErrs = append(allErrs, validateHostPathType(hostPath.Type, fldPath.Child("type"))...) - return allErrs -} - -func validateGitRepoVolumeSource(gitRepo *api.GitRepoVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(gitRepo.Repository) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("repository"), "")) - } - - pathErrs := validateLocalDescendingPath(gitRepo.Directory, fldPath.Child("directory")) - allErrs = append(allErrs, pathErrs...) - return allErrs -} - -func validateISCSIVolumeSource(iscsi *api.ISCSIVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(iscsi.TargetPortal) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("targetPortal"), "")) - } - if len(iscsi.IQN) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("iqn"), "")) - } else { - if !strings.HasPrefix(iscsi.IQN, "iqn") && !strings.HasPrefix(iscsi.IQN, "eui") && !strings.HasPrefix(iscsi.IQN, "naa") { - allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format")) - } else if strings.HasPrefix(iscsi.IQN, "iqn") && !iscsiInitiatorIqnRegex.MatchString(iscsi.IQN) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format")) - } else if strings.HasPrefix(iscsi.IQN, "eui") && !iscsiInitiatorEuiRegex.MatchString(iscsi.IQN) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format")) - } else if strings.HasPrefix(iscsi.IQN, "naa") && !iscsiInitiatorNaaRegex.MatchString(iscsi.IQN) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format")) - } - } - if iscsi.Lun < 0 || iscsi.Lun > 255 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("lun"), iscsi.Lun, validation.InclusiveRangeError(0, 255))) - } - if (iscsi.DiscoveryCHAPAuth || iscsi.SessionCHAPAuth) && iscsi.SecretRef == nil { - allErrs = append(allErrs, field.Required(fldPath.Child("secretRef"), "")) - } - if iscsi.InitiatorName != nil { - initiator := *iscsi.InitiatorName - if !strings.HasPrefix(initiator, "iqn") && !strings.HasPrefix(initiator, "eui") && !strings.HasPrefix(initiator, "naa") { - allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format")) - } - if strings.HasPrefix(initiator, "iqn") && !iscsiInitiatorIqnRegex.MatchString(initiator) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format")) - } else if strings.HasPrefix(initiator, "eui") && !iscsiInitiatorEuiRegex.MatchString(initiator) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format")) - } else if strings.HasPrefix(initiator, "naa") && !iscsiInitiatorNaaRegex.MatchString(initiator) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format")) - } - } - return allErrs -} - -func validateFCVolumeSource(fc *api.FCVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(fc.TargetWWNs) < 1 && len(fc.WWIDs) < 1 { - allErrs = append(allErrs, field.Required(fldPath.Child("targetWWNs"), "must specify either targetWWNs or wwids, but not both")) - } - - if len(fc.TargetWWNs) != 0 && len(fc.WWIDs) != 0 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("targetWWNs"), fc.TargetWWNs, "targetWWNs and wwids can not be specified simultaneously")) - } - - if len(fc.TargetWWNs) != 0 { - if fc.Lun == nil { - allErrs = append(allErrs, field.Required(fldPath.Child("lun"), "lun is required if targetWWNs is specified")) - } else { - if *fc.Lun < 0 || *fc.Lun > 255 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("lun"), fc.Lun, validation.InclusiveRangeError(0, 255))) - } - } - } - return allErrs -} - -func validateGCEPersistentDiskVolumeSource(pd *api.GCEPersistentDiskVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(pd.PDName) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("pdName"), "")) - } - if pd.Partition < 0 || pd.Partition > 255 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("partition"), pd.Partition, pdPartitionErrorMsg)) - } - return allErrs -} - -func validateAWSElasticBlockStoreVolumeSource(PD *api.AWSElasticBlockStoreVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(PD.VolumeID) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("volumeID"), "")) - } - if PD.Partition < 0 || PD.Partition > 255 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("partition"), PD.Partition, pdPartitionErrorMsg)) - } - return allErrs -} - -func validateSecretVolumeSource(secretSource *api.SecretVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(secretSource.SecretName) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("secretName"), "")) - } - - secretMode := secretSource.DefaultMode - if secretMode != nil && (*secretMode > 0777 || *secretMode < 0) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultMode"), *secretMode, volumeModeErrorMsg)) - } - - itemsPath := fldPath.Child("items") - for i, kp := range secretSource.Items { - itemPath := itemsPath.Index(i) - allErrs = append(allErrs, validateKeyToPath(&kp, itemPath)...) - } - return allErrs -} - -func validateConfigMapVolumeSource(configMapSource *api.ConfigMapVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(configMapSource.Name) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) - } - - configMapMode := configMapSource.DefaultMode - if configMapMode != nil && (*configMapMode > 0777 || *configMapMode < 0) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultMode"), *configMapMode, volumeModeErrorMsg)) - } - - itemsPath := fldPath.Child("items") - for i, kp := range configMapSource.Items { - itemPath := itemsPath.Index(i) - allErrs = append(allErrs, validateKeyToPath(&kp, itemPath)...) - } - return allErrs -} - -func validateKeyToPath(kp *api.KeyToPath, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(kp.Key) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("key"), "")) - } - if len(kp.Path) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("path"), "")) - } - allErrs = append(allErrs, validateLocalNonReservedPath(kp.Path, fldPath.Child("path"))...) - if kp.Mode != nil && (*kp.Mode > 0777 || *kp.Mode < 0) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("mode"), *kp.Mode, volumeModeErrorMsg)) - } - - return allErrs -} - -func validatePersistentClaimVolumeSource(claim *api.PersistentVolumeClaimVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(claim.ClaimName) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("claimName"), "")) - } - return allErrs -} - -func validateNFSVolumeSource(nfs *api.NFSVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(nfs.Server) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("server"), "")) - } - if len(nfs.Path) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("path"), "")) - } - if !path.IsAbs(nfs.Path) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("path"), nfs.Path, "must be an absolute path")) - } - return allErrs -} - -func validateQuobyteVolumeSource(quobyte *api.QuobyteVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(quobyte.Registry) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("registry"), "must be a host:port pair or multiple pairs separated by commas")) - } else { - for _, hostPortPair := range strings.Split(quobyte.Registry, ",") { - if _, _, err := net.SplitHostPort(hostPortPair); err != nil { - allErrs = append(allErrs, field.Invalid(fldPath.Child("registry"), quobyte.Registry, "must be a host:port pair or multiple pairs separated by commas")) - } - } - } - - if len(quobyte.Volume) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("volume"), "")) - } - return allErrs -} - -func validateGlusterfsVolumeSource(glusterfs *api.GlusterfsVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(glusterfs.EndpointsName) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("endpoints"), "")) - } - if len(glusterfs.Path) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("path"), "")) - } - return allErrs -} - -func validateFlockerVolumeSource(flocker *api.FlockerVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(flocker.DatasetName) == 0 && len(flocker.DatasetUUID) == 0 { - //TODO: consider adding a RequiredOneOf() error for this and similar cases - allErrs = append(allErrs, field.Required(fldPath, "one of datasetName and datasetUUID is required")) - } - if len(flocker.DatasetName) != 0 && len(flocker.DatasetUUID) != 0 { - allErrs = append(allErrs, field.Invalid(fldPath, "resource", "datasetName and datasetUUID can not be specified simultaneously")) - } - if strings.Contains(flocker.DatasetName, "/") { - allErrs = append(allErrs, field.Invalid(fldPath.Child("datasetName"), flocker.DatasetName, "must not contain '/'")) - } - return allErrs -} - -var validDownwardAPIFieldPathExpressions = sets.NewString( - "metadata.name", - "metadata.namespace", - "metadata.labels", - "metadata.annotations", - "metadata.uid") - -func validateDownwardAPIVolumeFile(file *api.DownwardAPIVolumeFile, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - if len(file.Path) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("path"), "")) - } - allErrs = append(allErrs, validateLocalNonReservedPath(file.Path, fldPath.Child("path"))...) - if file.FieldRef != nil { - allErrs = append(allErrs, validateObjectFieldSelector(file.FieldRef, &validDownwardAPIFieldPathExpressions, fldPath.Child("fieldRef"))...) - if file.ResourceFieldRef != nil { - allErrs = append(allErrs, field.Invalid(fldPath, "resource", "fieldRef and resourceFieldRef can not be specified simultaneously")) - } - } else if file.ResourceFieldRef != nil { - allErrs = append(allErrs, validateContainerResourceFieldSelector(file.ResourceFieldRef, &validContainerResourceFieldPathExpressions, fldPath.Child("resourceFieldRef"), true)...) - } else { - allErrs = append(allErrs, field.Required(fldPath, "one of fieldRef and resourceFieldRef is required")) - } - if file.Mode != nil && (*file.Mode > 0777 || *file.Mode < 0) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("mode"), *file.Mode, volumeModeErrorMsg)) - } - - return allErrs -} - -func validateDownwardAPIVolumeSource(downwardAPIVolume *api.DownwardAPIVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - downwardAPIMode := downwardAPIVolume.DefaultMode - if downwardAPIMode != nil && (*downwardAPIMode > 0777 || *downwardAPIMode < 0) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultMode"), *downwardAPIMode, volumeModeErrorMsg)) - } - - for _, file := range downwardAPIVolume.Items { - allErrs = append(allErrs, validateDownwardAPIVolumeFile(&file, fldPath)...) - } - return allErrs -} - -func validateProjectionSources(projection *api.ProjectedVolumeSource, projectionMode *int32, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - allPaths := sets.String{} - - for _, source := range projection.Sources { - numSources := 0 - if source.Secret != nil { - if numSources > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("secret"), "may not specify more than 1 volume type")) - } else { - numSources++ - if len(source.Secret.Name) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) - } - itemsPath := fldPath.Child("items") - for i, kp := range source.Secret.Items { - itemPath := itemsPath.Index(i) - allErrs = append(allErrs, validateKeyToPath(&kp, itemPath)...) - if len(kp.Path) > 0 { - curPath := kp.Path - if !allPaths.Has(curPath) { - allPaths.Insert(curPath) - } else { - allErrs = append(allErrs, field.Invalid(fldPath, source.Secret.Name, "conflicting duplicate paths")) - } - } - } - } - } - if source.ConfigMap != nil { - if numSources > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("configMap"), "may not specify more than 1 volume type")) - } else { - numSources++ - if len(source.ConfigMap.Name) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) - } - itemsPath := fldPath.Child("items") - for i, kp := range source.ConfigMap.Items { - itemPath := itemsPath.Index(i) - allErrs = append(allErrs, validateKeyToPath(&kp, itemPath)...) - if len(kp.Path) > 0 { - curPath := kp.Path - if !allPaths.Has(curPath) { - allPaths.Insert(curPath) - } else { - allErrs = append(allErrs, field.Invalid(fldPath, source.ConfigMap.Name, "conflicting duplicate paths")) - } - - } - } - } - } - if source.DownwardAPI != nil { - if numSources > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("downwardAPI"), "may not specify more than 1 volume type")) - } else { - numSources++ - for _, file := range source.DownwardAPI.Items { - allErrs = append(allErrs, validateDownwardAPIVolumeFile(&file, fldPath.Child("downwardAPI"))...) - if len(file.Path) > 0 { - curPath := file.Path - if !allPaths.Has(curPath) { - allPaths.Insert(curPath) - } else { - allErrs = append(allErrs, field.Invalid(fldPath, curPath, "conflicting duplicate paths")) - } - - } - } - } - } - } - return allErrs -} - -func validateProjectedVolumeSource(projection *api.ProjectedVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - projectionMode := projection.DefaultMode - if projectionMode != nil && (*projectionMode > 0777 || *projectionMode < 0) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultMode"), *projectionMode, volumeModeErrorMsg)) - } - - allErrs = append(allErrs, validateProjectionSources(projection, projectionMode, fldPath)...) - return allErrs -} - -var supportedHostPathTypes = sets.NewString( - string(api.HostPathUnset), - string(api.HostPathDirectoryOrCreate), - string(api.HostPathDirectory), - string(api.HostPathFileOrCreate), - string(api.HostPathFile), - string(api.HostPathSocket), - string(api.HostPathCharDev), - string(api.HostPathBlockDev)) - -func validateHostPathType(hostPathType *api.HostPathType, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - if hostPathType != nil && !supportedHostPathTypes.Has(string(*hostPathType)) { - allErrs = append(allErrs, field.NotSupported(fldPath, hostPathType, supportedHostPathTypes.List())) - } - - return allErrs -} - -// This validate will make sure targetPath: -// 1. is not abs path -// 2. does not have any element which is ".." -func validateLocalDescendingPath(targetPath string, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if path.IsAbs(targetPath) { - allErrs = append(allErrs, field.Invalid(fldPath, targetPath, "must be a relative path")) - } - - allErrs = append(allErrs, validatePathNoBacksteps(targetPath, fldPath)...) - - return allErrs -} - -// validatePathNoBacksteps makes sure the targetPath does not have any `..` path elements when split -// -// This assumes the OS of the apiserver and the nodes are the same. The same check should be done -// on the node to ensure there are no backsteps. -func validatePathNoBacksteps(targetPath string, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - parts := strings.Split(filepath.ToSlash(targetPath), "/") - for _, item := range parts { - if item == ".." { - allErrs = append(allErrs, field.Invalid(fldPath, targetPath, "must not contain '..'")) - break // even for `../../..`, one error is sufficient to make the point - } - } - return allErrs -} - -// validateMountPropagation verifies that MountPropagation field is valid and -// allowed for given container. -func validateMountPropagation(mountPropagation *api.MountPropagationMode, container *api.Container, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - if mountPropagation == nil { - return allErrs - } - if !utilfeature.DefaultFeatureGate.Enabled(features.MountPropagation) { - allErrs = append(allErrs, field.Forbidden(fldPath, "mount propagation is disabled by feature-gate")) - return allErrs - } - - supportedMountPropagations := sets.NewString(string(api.MountPropagationBidirectional), string(api.MountPropagationHostToContainer)) - if !supportedMountPropagations.Has(string(*mountPropagation)) { - allErrs = append(allErrs, field.NotSupported(fldPath, *mountPropagation, supportedMountPropagations.List())) - } - - if container == nil { - // The container is not available yet, e.g. during validation of - // PodPreset. Stop validation now, Pod validation will refuse final - // Pods with Bidirectional propagation in non-privileged containers. - return allErrs - } - - privileged := container.SecurityContext != nil && container.SecurityContext.Privileged != nil && *container.SecurityContext.Privileged - if *mountPropagation == api.MountPropagationBidirectional && !privileged { - allErrs = append(allErrs, field.Forbidden(fldPath, "Bidirectional mount propagation is available only to privileged containers")) - } - return allErrs -} - -// This validate will make sure targetPath: -// 1. is not abs path -// 2. does not contain any '..' elements -// 3. does not start with '..' -func validateLocalNonReservedPath(targetPath string, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - allErrs = append(allErrs, validateLocalDescendingPath(targetPath, fldPath)...) - // Don't report this error if the check for .. elements already caught it. - if strings.HasPrefix(targetPath, "..") && !strings.HasPrefix(targetPath, "../") { - allErrs = append(allErrs, field.Invalid(fldPath, targetPath, "must not start with '..'")) - } - return allErrs -} - -func validateRBDVolumeSource(rbd *api.RBDVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(rbd.CephMonitors) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("monitors"), "")) - } - if len(rbd.RBDImage) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("image"), "")) - } - return allErrs -} - -func validateCinderVolumeSource(cd *api.CinderVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(cd.VolumeID) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("volumeID"), "")) - } - return allErrs -} - -func validateCephFSVolumeSource(cephfs *api.CephFSVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(cephfs.Monitors) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("monitors"), "")) - } - return allErrs -} - -func validateCephFSPersistentVolumeSource(cephfs *api.CephFSPersistentVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(cephfs.Monitors) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("monitors"), "")) - } - return allErrs -} - -func validateFlexVolumeSource(fv *api.FlexVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(fv.Driver) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("driver"), "")) - } - - // Make sure user-specified options don't use kubernetes namespaces - for k := range fv.Options { - namespace := k - if parts := strings.SplitN(k, "/", 2); len(parts) == 2 { - namespace = parts[0] - } - normalized := "." + strings.ToLower(namespace) - if strings.HasSuffix(normalized, ".kubernetes.io") || strings.HasSuffix(normalized, ".k8s.io") { - allErrs = append(allErrs, field.Invalid(fldPath.Child("options").Key(k), k, "kubernetes.io and k8s.io namespaces are reserved")) - } - } - - return allErrs -} - -func validateAzureFile(azure *api.AzureFileVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if azure.SecretName == "" { - allErrs = append(allErrs, field.Required(fldPath.Child("secretName"), "")) - } - if azure.ShareName == "" { - allErrs = append(allErrs, field.Required(fldPath.Child("shareName"), "")) - } - return allErrs -} - -func validateAzureFilePV(azure *api.AzureFilePersistentVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if azure.SecretName == "" { - allErrs = append(allErrs, field.Required(fldPath.Child("secretName"), "")) - } - if azure.ShareName == "" { - allErrs = append(allErrs, field.Required(fldPath.Child("shareName"), "")) - } - if azure.SecretNamespace != nil { - if len(*azure.SecretNamespace) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("secretNamespace"), "")) - } - } - return allErrs -} - -func validateAzureDisk(azure *api.AzureDiskVolumeSource, fldPath *field.Path) field.ErrorList { - var supportedCachingModes = sets.NewString(string(api.AzureDataDiskCachingNone), string(api.AzureDataDiskCachingReadOnly), string(api.AzureDataDiskCachingReadWrite)) - var supportedDiskKinds = sets.NewString(string(api.AzureSharedBlobDisk), string(api.AzureDedicatedBlobDisk), string(api.AzureManagedDisk)) - - diskUriSupportedManaged := []string{"/subscriptions/{sub-id}/resourcegroups/{group-name}/providers/microsoft.compute/disks/{disk-id}"} - diskUriSupportedblob := []string{"https://{account-name}.blob.core.windows.net/{container-name}/{disk-name}.vhd"} - - allErrs := field.ErrorList{} - if azure.DiskName == "" { - allErrs = append(allErrs, field.Required(fldPath.Child("diskName"), "")) - } - - if azure.DataDiskURI == "" { - allErrs = append(allErrs, field.Required(fldPath.Child("diskURI"), "")) - } - - if azure.CachingMode != nil && !supportedCachingModes.Has(string(*azure.CachingMode)) { - allErrs = append(allErrs, field.NotSupported(fldPath.Child("cachingMode"), *azure.CachingMode, supportedCachingModes.List())) - } - - if azure.Kind != nil && !supportedDiskKinds.Has(string(*azure.Kind)) { - allErrs = append(allErrs, field.NotSupported(fldPath.Child("kind"), *azure.Kind, supportedDiskKinds.List())) - } - - // validate that DiskUri is the correct format - if azure.Kind != nil && *azure.Kind == api.AzureManagedDisk && strings.Index(azure.DataDiskURI, "/subscriptions/") != 0 { - allErrs = append(allErrs, field.NotSupported(fldPath.Child("diskURI"), azure.DataDiskURI, diskUriSupportedManaged)) - } - - if azure.Kind != nil && *azure.Kind != api.AzureManagedDisk && strings.Index(azure.DataDiskURI, "https://") != 0 { - allErrs = append(allErrs, field.NotSupported(fldPath.Child("diskURI"), azure.DataDiskURI, diskUriSupportedblob)) - } - - return allErrs -} - -func validateVsphereVolumeSource(cd *api.VsphereVirtualDiskVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(cd.VolumePath) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("volumePath"), "")) - } - return allErrs -} - -func validatePhotonPersistentDiskVolumeSource(cd *api.PhotonPersistentDiskVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(cd.PdID) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("pdID"), "")) - } - return allErrs -} - -func validatePortworxVolumeSource(pwx *api.PortworxVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(pwx.VolumeID) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("volumeID"), "")) - } - return allErrs -} - -func validateScaleIOVolumeSource(sio *api.ScaleIOVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if sio.Gateway == "" { - allErrs = append(allErrs, field.Required(fldPath.Child("gateway"), "")) - } - if sio.System == "" { - allErrs = append(allErrs, field.Required(fldPath.Child("system"), "")) - } - if sio.VolumeName == "" { - allErrs = append(allErrs, field.Required(fldPath.Child("volumeName"), "")) - } - return allErrs -} - -func validateLocalVolumeSource(ls *api.LocalVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if ls.Path == "" { - allErrs = append(allErrs, field.Required(fldPath.Child("path"), "")) - return allErrs - } - - allErrs = append(allErrs, validatePathNoBacksteps(ls.Path, fldPath.Child("path"))...) - return allErrs -} - -func validateStorageOSVolumeSource(storageos *api.StorageOSVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(storageos.VolumeName) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("volumeName"), "")) - } else { - allErrs = append(allErrs, ValidateDNS1123Label(storageos.VolumeName, fldPath.Child("volumeName"))...) - } - if len(storageos.VolumeNamespace) > 0 { - allErrs = append(allErrs, ValidateDNS1123Label(storageos.VolumeNamespace, fldPath.Child("volumeNamespace"))...) - } - if storageos.SecretRef != nil { - if len(storageos.SecretRef.Name) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("secretRef", "name"), "")) - } - } - return allErrs -} - -func validateStorageOSPersistentVolumeSource(storageos *api.StorageOSPersistentVolumeSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(storageos.VolumeName) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("volumeName"), "")) - } else { - allErrs = append(allErrs, ValidateDNS1123Label(storageos.VolumeName, fldPath.Child("volumeName"))...) - } - if len(storageos.VolumeNamespace) > 0 { - allErrs = append(allErrs, ValidateDNS1123Label(storageos.VolumeNamespace, fldPath.Child("volumeNamespace"))...) - } - if storageos.SecretRef != nil { - if len(storageos.SecretRef.Name) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("secretRef", "name"), "")) - } - if len(storageos.SecretRef.Namespace) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("secretRef", "namespace"), "")) - } - } - return allErrs -} - -// ValidatePersistentVolumeName checks that a name is appropriate for a -// PersistentVolumeName object. -var ValidatePersistentVolumeName = NameIsDNSSubdomain - -var supportedAccessModes = sets.NewString(string(api.ReadWriteOnce), string(api.ReadOnlyMany), string(api.ReadWriteMany)) - -var supportedReclaimPolicy = sets.NewString(string(api.PersistentVolumeReclaimDelete), string(api.PersistentVolumeReclaimRecycle), string(api.PersistentVolumeReclaimRetain)) - -func ValidatePersistentVolume(pv *api.PersistentVolume) field.ErrorList { - metaPath := field.NewPath("metadata") - allErrs := ValidateObjectMeta(&pv.ObjectMeta, false, ValidatePersistentVolumeName, metaPath) - - specPath := field.NewPath("spec") - if len(pv.Spec.AccessModes) == 0 { - allErrs = append(allErrs, field.Required(specPath.Child("accessModes"), "")) - } - for _, mode := range pv.Spec.AccessModes { - if !supportedAccessModes.Has(string(mode)) { - allErrs = append(allErrs, field.NotSupported(specPath.Child("accessModes"), mode, supportedAccessModes.List())) - } - } - - if len(pv.Spec.Capacity) == 0 { - allErrs = append(allErrs, field.Required(specPath.Child("capacity"), "")) - } - - if _, ok := pv.Spec.Capacity[api.ResourceStorage]; !ok || len(pv.Spec.Capacity) > 1 { - allErrs = append(allErrs, field.NotSupported(specPath.Child("capacity"), pv.Spec.Capacity, []string{string(api.ResourceStorage)})) - } - capPath := specPath.Child("capacity") - for r, qty := range pv.Spec.Capacity { - allErrs = append(allErrs, validateBasicResource(qty, capPath.Key(string(r)))...) - } - if len(string(pv.Spec.PersistentVolumeReclaimPolicy)) > 0 { - if !supportedReclaimPolicy.Has(string(pv.Spec.PersistentVolumeReclaimPolicy)) { - allErrs = append(allErrs, field.NotSupported(specPath.Child("persistentVolumeReclaimPolicy"), pv.Spec.PersistentVolumeReclaimPolicy, supportedReclaimPolicy.List())) - } - } - - nodeAffinitySpecified, errs := validateStorageNodeAffinityAnnotation(pv.ObjectMeta.Annotations, metaPath.Child("annotations")) - allErrs = append(allErrs, errs...) - - numVolumes := 0 - if pv.Spec.HostPath != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(specPath.Child("hostPath"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateHostPathVolumeSource(pv.Spec.HostPath, specPath.Child("hostPath"))...) - } - } - if pv.Spec.GCEPersistentDisk != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(specPath.Child("gcePersistentDisk"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateGCEPersistentDiskVolumeSource(pv.Spec.GCEPersistentDisk, specPath.Child("persistentDisk"))...) - } - } - if pv.Spec.AWSElasticBlockStore != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(specPath.Child("awsElasticBlockStore"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateAWSElasticBlockStoreVolumeSource(pv.Spec.AWSElasticBlockStore, specPath.Child("awsElasticBlockStore"))...) - } - } - if pv.Spec.Glusterfs != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(specPath.Child("glusterfs"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateGlusterfsVolumeSource(pv.Spec.Glusterfs, specPath.Child("glusterfs"))...) - } - } - if pv.Spec.Flocker != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(specPath.Child("flocker"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateFlockerVolumeSource(pv.Spec.Flocker, specPath.Child("flocker"))...) - } - } - if pv.Spec.NFS != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(specPath.Child("nfs"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateNFSVolumeSource(pv.Spec.NFS, specPath.Child("nfs"))...) - } - } - if pv.Spec.RBD != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(specPath.Child("rbd"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateRBDVolumeSource(pv.Spec.RBD, specPath.Child("rbd"))...) - } - } - if pv.Spec.Quobyte != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(specPath.Child("quobyte"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateQuobyteVolumeSource(pv.Spec.Quobyte, specPath.Child("quobyte"))...) - } - } - if pv.Spec.CephFS != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(specPath.Child("cephFS"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateCephFSPersistentVolumeSource(pv.Spec.CephFS, specPath.Child("cephfs"))...) - } - } - if pv.Spec.ISCSI != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(specPath.Child("iscsi"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateISCSIVolumeSource(pv.Spec.ISCSI, specPath.Child("iscsi"))...) - } - if pv.Spec.ISCSI.InitiatorName != nil && len(pv.ObjectMeta.Name+":"+pv.Spec.ISCSI.TargetPortal) > 64 { - tooLongErr := "Total length of : must be under 64 characters if iscsi.initiatorName is specified." - allErrs = append(allErrs, field.Invalid(metaPath.Child("name"), pv.ObjectMeta.Name, tooLongErr)) - } - } - if pv.Spec.Cinder != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(specPath.Child("cinder"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateCinderVolumeSource(pv.Spec.Cinder, specPath.Child("cinder"))...) - } - } - if pv.Spec.FC != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(specPath.Child("fc"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateFCVolumeSource(pv.Spec.FC, specPath.Child("fc"))...) - } - } - if pv.Spec.FlexVolume != nil { - numVolumes++ - allErrs = append(allErrs, validateFlexVolumeSource(pv.Spec.FlexVolume, specPath.Child("flexVolume"))...) - } - if pv.Spec.AzureFile != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(specPath.Child("azureFile"), "may not specify more than 1 volume type")) - - } else { - numVolumes++ - allErrs = append(allErrs, validateAzureFilePV(pv.Spec.AzureFile, specPath.Child("azureFile"))...) - } - } - - if pv.Spec.VsphereVolume != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(specPath.Child("vsphereVolume"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateVsphereVolumeSource(pv.Spec.VsphereVolume, specPath.Child("vsphereVolume"))...) - } - } - if pv.Spec.PhotonPersistentDisk != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(specPath.Child("photonPersistentDisk"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validatePhotonPersistentDiskVolumeSource(pv.Spec.PhotonPersistentDisk, specPath.Child("photonPersistentDisk"))...) - } - } - if pv.Spec.PortworxVolume != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(specPath.Child("portworxVolume"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validatePortworxVolumeSource(pv.Spec.PortworxVolume, specPath.Child("portworxVolume"))...) - } - } - if pv.Spec.AzureDisk != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(specPath.Child("azureDisk"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateAzureDisk(pv.Spec.AzureDisk, specPath.Child("azureDisk"))...) - } - } - if pv.Spec.ScaleIO != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(specPath.Child("scaleIO"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateScaleIOVolumeSource(pv.Spec.ScaleIO, specPath.Child("scaleIO"))...) - } - } - if pv.Spec.Local != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(specPath.Child("local"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - if !utilfeature.DefaultFeatureGate.Enabled(features.PersistentLocalVolumes) { - allErrs = append(allErrs, field.Forbidden(specPath.Child("local"), "Local volumes are disabled by feature-gate")) - } - allErrs = append(allErrs, validateLocalVolumeSource(pv.Spec.Local, specPath.Child("local"))...) - - // NodeAffinity is required - if !nodeAffinitySpecified { - allErrs = append(allErrs, field.Required(metaPath.Child("annotations"), "Local volume requires node affinity")) - } - } - } - if pv.Spec.StorageOS != nil { - if numVolumes > 0 { - allErrs = append(allErrs, field.Forbidden(specPath.Child("storageos"), "may not specify more than 1 volume type")) - } else { - numVolumes++ - allErrs = append(allErrs, validateStorageOSPersistentVolumeSource(pv.Spec.StorageOS, specPath.Child("storageos"))...) - } - } - - if numVolumes == 0 { - allErrs = append(allErrs, field.Required(specPath, "must specify a volume type")) - } - - // do not allow hostPath mounts of '/' to have a 'recycle' reclaim policy - if pv.Spec.HostPath != nil && path.Clean(pv.Spec.HostPath.Path) == "/" && pv.Spec.PersistentVolumeReclaimPolicy == api.PersistentVolumeReclaimRecycle { - allErrs = append(allErrs, field.Forbidden(specPath.Child("persistentVolumeReclaimPolicy"), "may not be 'recycle' for a hostPath mount of '/'")) - } - - if len(pv.Spec.StorageClassName) > 0 { - for _, msg := range ValidateClassName(pv.Spec.StorageClassName, false) { - allErrs = append(allErrs, field.Invalid(specPath.Child("storageClassName"), pv.Spec.StorageClassName, msg)) - } - } - - return allErrs -} - -// ValidatePersistentVolumeUpdate tests to see if the update is legal for an end user to make. -// newPv is updated with fields that cannot be changed. -func ValidatePersistentVolumeUpdate(newPv, oldPv *api.PersistentVolume) field.ErrorList { - allErrs := field.ErrorList{} - allErrs = ValidatePersistentVolume(newPv) - newPv.Status = oldPv.Status - return allErrs -} - -// ValidatePersistentVolumeStatusUpdate tests to see if the status update is legal for an end user to make. -// newPv is updated with fields that cannot be changed. -func ValidatePersistentVolumeStatusUpdate(newPv, oldPv *api.PersistentVolume) field.ErrorList { - allErrs := ValidateObjectMetaUpdate(&newPv.ObjectMeta, &oldPv.ObjectMeta, field.NewPath("metadata")) - if len(newPv.ResourceVersion) == 0 { - allErrs = append(allErrs, field.Required(field.NewPath("resourceVersion"), "")) - } - newPv.Spec = oldPv.Spec - return allErrs -} - -// ValidatePersistentVolumeClaim validates a PersistentVolumeClaim -func ValidatePersistentVolumeClaim(pvc *api.PersistentVolumeClaim) field.ErrorList { - allErrs := ValidateObjectMeta(&pvc.ObjectMeta, true, ValidatePersistentVolumeName, field.NewPath("metadata")) - allErrs = append(allErrs, ValidatePersistentVolumeClaimSpec(&pvc.Spec, field.NewPath("spec"))...) - return allErrs -} - -// ValidatePersistentVolumeClaimSpec validates a PersistentVolumeClaimSpec -func ValidatePersistentVolumeClaimSpec(spec *api.PersistentVolumeClaimSpec, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(spec.AccessModes) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("accessModes"), "at least 1 access mode is required")) - } - if spec.Selector != nil { - allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...) - } - for _, mode := range spec.AccessModes { - if mode != api.ReadWriteOnce && mode != api.ReadOnlyMany && mode != api.ReadWriteMany { - allErrs = append(allErrs, field.NotSupported(fldPath.Child("accessModes"), mode, supportedAccessModes.List())) - } - } - storageValue, ok := spec.Resources.Requests[api.ResourceStorage] - if !ok { - allErrs = append(allErrs, field.Required(fldPath.Child("resources").Key(string(api.ResourceStorage)), "")) - } else { - allErrs = append(allErrs, ValidateResourceQuantityValue(string(api.ResourceStorage), storageValue, fldPath.Child("resources").Key(string(api.ResourceStorage)))...) - } - - if spec.StorageClassName != nil && len(*spec.StorageClassName) > 0 { - for _, msg := range ValidateClassName(*spec.StorageClassName, false) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("storageClassName"), *spec.StorageClassName, msg)) - } - } - return allErrs -} - -// ValidatePersistentVolumeClaimUpdate validates an update to a PersistentVolumeClaim -func ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc *api.PersistentVolumeClaim) field.ErrorList { - allErrs := ValidateObjectMetaUpdate(&newPvc.ObjectMeta, &oldPvc.ObjectMeta, field.NewPath("metadata")) - allErrs = append(allErrs, ValidatePersistentVolumeClaim(newPvc)...) - // PVController needs to update PVC.Spec w/ VolumeName. - // Claims are immutable in order to enforce quota, range limits, etc. without gaming the system. - if len(oldPvc.Spec.VolumeName) == 0 { - // volumeName changes are allowed once. - // Reset back to empty string after equality check - oldPvc.Spec.VolumeName = newPvc.Spec.VolumeName - defer func() { oldPvc.Spec.VolumeName = "" }() - } - - if utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) { - newPVCSpecCopy := newPvc.Spec.DeepCopy() - - // lets make sure storage values are same. - if newPvc.Status.Phase == api.ClaimBound && newPVCSpecCopy.Resources.Requests != nil { - newPVCSpecCopy.Resources.Requests["storage"] = oldPvc.Spec.Resources.Requests["storage"] - } - - oldSize := oldPvc.Spec.Resources.Requests["storage"] - newSize := newPvc.Spec.Resources.Requests["storage"] - - if !apiequality.Semantic.DeepEqual(*newPVCSpecCopy, oldPvc.Spec) { - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "is immutable after creation except resources.requests for bound claims")) - } - if newSize.Cmp(oldSize) < 0 { - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "resources", "requests", "storage"), "field can not be less than previous value")) - } - - } else { - // changes to Spec are not allowed, but updates to label/and some annotations are OK. - // no-op updates pass validation. - if !apiequality.Semantic.DeepEqual(newPvc.Spec, oldPvc.Spec) { - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "field is immutable after creation")) - } - } - - // storageclass annotation should be immutable after creation - // TODO: remove Beta when no longer needed - allErrs = append(allErrs, ValidateImmutableAnnotation(newPvc.ObjectMeta.Annotations[v1.BetaStorageClassAnnotation], oldPvc.ObjectMeta.Annotations[v1.BetaStorageClassAnnotation], v1.BetaStorageClassAnnotation, field.NewPath("metadata"))...) - - newPvc.Status = oldPvc.Status - return allErrs -} - -// ValidatePersistentVolumeClaimStatusUpdate validates an update to status of a PersistentVolumeClaim -func ValidatePersistentVolumeClaimStatusUpdate(newPvc, oldPvc *api.PersistentVolumeClaim) field.ErrorList { - allErrs := ValidateObjectMetaUpdate(&newPvc.ObjectMeta, &oldPvc.ObjectMeta, field.NewPath("metadata")) - if len(newPvc.ResourceVersion) == 0 { - allErrs = append(allErrs, field.Required(field.NewPath("resourceVersion"), "")) - } - if len(newPvc.Spec.AccessModes) == 0 { - allErrs = append(allErrs, field.Required(field.NewPath("Spec", "accessModes"), "")) - } - if !utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) && len(newPvc.Status.Conditions) > 0 { - conditionPath := field.NewPath("status", "conditions") - allErrs = append(allErrs, field.Forbidden(conditionPath, "invalid field")) - } - capPath := field.NewPath("status", "capacity") - for r, qty := range newPvc.Status.Capacity { - allErrs = append(allErrs, validateBasicResource(qty, capPath.Key(string(r)))...) - } - newPvc.Spec = oldPvc.Spec - return allErrs -} - -var supportedPortProtocols = sets.NewString(string(api.ProtocolTCP), string(api.ProtocolUDP)) - -func validateContainerPorts(ports []api.ContainerPort, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - allNames := sets.String{} - for i, port := range ports { - idxPath := fldPath.Index(i) - if len(port.Name) > 0 { - if msgs := validation.IsValidPortName(port.Name); len(msgs) != 0 { - for i = range msgs { - allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), port.Name, msgs[i])) - } - } else if allNames.Has(port.Name) { - allErrs = append(allErrs, field.Duplicate(idxPath.Child("name"), port.Name)) - } else { - allNames.Insert(port.Name) - } - } - if port.ContainerPort == 0 { - allErrs = append(allErrs, field.Required(idxPath.Child("containerPort"), "")) - } else { - for _, msg := range validation.IsValidPortNum(int(port.ContainerPort)) { - allErrs = append(allErrs, field.Invalid(idxPath.Child("containerPort"), port.ContainerPort, msg)) - } - } - if port.HostPort != 0 { - for _, msg := range validation.IsValidPortNum(int(port.HostPort)) { - allErrs = append(allErrs, field.Invalid(idxPath.Child("hostPort"), port.HostPort, msg)) - } - } - if len(port.Protocol) == 0 { - allErrs = append(allErrs, field.Required(idxPath.Child("protocol"), "")) - } else if !supportedPortProtocols.Has(string(port.Protocol)) { - allErrs = append(allErrs, field.NotSupported(idxPath.Child("protocol"), port.Protocol, supportedPortProtocols.List())) - } - } - return allErrs -} - -// ValidateEnv validates env vars -func ValidateEnv(vars []api.EnvVar, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - for i, ev := range vars { - idxPath := fldPath.Index(i) - if len(ev.Name) == 0 { - allErrs = append(allErrs, field.Required(idxPath.Child("name"), "")) - } else { - for _, msg := range validation.IsEnvVarName(ev.Name) { - allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), ev.Name, msg)) - } - } - allErrs = append(allErrs, validateEnvVarValueFrom(ev, idxPath.Child("valueFrom"))...) - } - return allErrs -} - -var validFieldPathExpressionsEnv = sets.NewString("metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP") -var validContainerResourceFieldPathExpressions = sets.NewString("limits.cpu", "limits.memory", "limits.ephemeral-storage", "requests.cpu", "requests.memory", "requests.ephemeral-storage") - -func validateEnvVarValueFrom(ev api.EnvVar, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - if ev.ValueFrom == nil { - return allErrs - } - - numSources := 0 - - if ev.ValueFrom.FieldRef != nil { - numSources++ - allErrs = append(allErrs, validateObjectFieldSelector(ev.ValueFrom.FieldRef, &validFieldPathExpressionsEnv, fldPath.Child("fieldRef"))...) - } - if ev.ValueFrom.ResourceFieldRef != nil { - numSources++ - allErrs = append(allErrs, validateContainerResourceFieldSelector(ev.ValueFrom.ResourceFieldRef, &validContainerResourceFieldPathExpressions, fldPath.Child("resourceFieldRef"), false)...) - } - if ev.ValueFrom.ConfigMapKeyRef != nil { - numSources++ - allErrs = append(allErrs, validateConfigMapKeySelector(ev.ValueFrom.ConfigMapKeyRef, fldPath.Child("configMapKeyRef"))...) - } - if ev.ValueFrom.SecretKeyRef != nil { - numSources++ - allErrs = append(allErrs, validateSecretKeySelector(ev.ValueFrom.SecretKeyRef, fldPath.Child("secretKeyRef"))...) - } - - if numSources == 0 { - allErrs = append(allErrs, field.Invalid(fldPath, "", "must specify one of: `fieldRef`, `resourceFieldRef`, `configMapKeyRef` or `secretKeyRef`")) - } else if len(ev.Value) != 0 { - if numSources != 0 { - allErrs = append(allErrs, field.Invalid(fldPath, "", "may not be specified when `value` is not empty")) - } - } else if numSources > 1 { - allErrs = append(allErrs, field.Invalid(fldPath, "", "may not have more than one field specified at a time")) - } - - return allErrs -} - -func validateObjectFieldSelector(fs *api.ObjectFieldSelector, expressions *sets.String, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - if len(fs.APIVersion) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("apiVersion"), "")) - } else if len(fs.FieldPath) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("fieldPath"), "")) - } else { - internalFieldPath, _, err := api.Scheme.ConvertFieldLabel(fs.APIVersion, "Pod", fs.FieldPath, "") - if err != nil { - allErrs = append(allErrs, field.Invalid(fldPath.Child("fieldPath"), fs.FieldPath, fmt.Sprintf("error converting fieldPath: %v", err))) - } else if !expressions.Has(internalFieldPath) { - allErrs = append(allErrs, field.NotSupported(fldPath.Child("fieldPath"), internalFieldPath, expressions.List())) - } - } - - return allErrs -} - -func fsResourceIsEphemeralStorage(resource string) bool { - if resource == "limits.ephemeral-storage" || resource == "requests.ephemeral-storage" { - return true - } - return false -} - -func validateContainerResourceFieldSelector(fs *api.ResourceFieldSelector, expressions *sets.String, fldPath *field.Path, volume bool) field.ErrorList { - allErrs := field.ErrorList{} - - if volume && len(fs.ContainerName) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("containerName"), "")) - } else if len(fs.Resource) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("resource"), "")) - } else if !expressions.Has(fs.Resource) { - allErrs = append(allErrs, field.NotSupported(fldPath.Child("resource"), fs.Resource, expressions.List())) - } else if fsResourceIsEphemeralStorage(fs.Resource) && !utilfeature.DefaultFeatureGate.Enabled(features.LocalStorageCapacityIsolation) { - allErrs = append(allErrs, field.Forbidden(fldPath, "Containers' ephemeral storage requests/limits disabled by feature-gate for Downward API")) - } - allErrs = append(allErrs, validateContainerResourceDivisor(fs.Resource, fs.Divisor, fldPath)...) - return allErrs -} - -func ValidateEnvFrom(vars []api.EnvFromSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - for i, ev := range vars { - idxPath := fldPath.Index(i) - if len(ev.Prefix) > 0 { - for _, msg := range validation.IsEnvVarName(ev.Prefix) { - allErrs = append(allErrs, field.Invalid(idxPath.Child("prefix"), ev.Prefix, msg)) - } - } - - numSources := 0 - if ev.ConfigMapRef != nil { - numSources++ - allErrs = append(allErrs, validateConfigMapEnvSource(ev.ConfigMapRef, idxPath.Child("configMapRef"))...) - } - if ev.SecretRef != nil { - numSources++ - allErrs = append(allErrs, validateSecretEnvSource(ev.SecretRef, idxPath.Child("secretRef"))...) - } - - if numSources == 0 { - allErrs = append(allErrs, field.Invalid(fldPath, "", "must specify one of: `configMapRef` or `secretRef`")) - } else if numSources > 1 { - allErrs = append(allErrs, field.Invalid(fldPath, "", "may not have more than one field specified at a time")) - } - } - return allErrs -} - -func validateConfigMapEnvSource(configMapSource *api.ConfigMapEnvSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(configMapSource.Name) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) - } else { - for _, msg := range ValidateConfigMapName(configMapSource.Name, true) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), configMapSource.Name, msg)) - } - } - return allErrs -} - -func validateSecretEnvSource(secretSource *api.SecretEnvSource, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(secretSource.Name) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) - } else { - for _, msg := range ValidateSecretName(secretSource.Name, true) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), secretSource.Name, msg)) - } - } - return allErrs -} - -var validContainerResourceDivisorForCPU = sets.NewString("1m", "1") -var validContainerResourceDivisorForMemory = sets.NewString("1", "1k", "1M", "1G", "1T", "1P", "1E", "1Ki", "1Mi", "1Gi", "1Ti", "1Pi", "1Ei") -var validContainerResourceDivisorForEphemeralStorage = sets.NewString("1", "1k", "1M", "1G", "1T", "1P", "1E", "1Ki", "1Mi", "1Gi", "1Ti", "1Pi", "1Ei") - -func validateContainerResourceDivisor(rName string, divisor resource.Quantity, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - unsetDivisor := resource.Quantity{} - if unsetDivisor.Cmp(divisor) == 0 { - return allErrs - } - switch rName { - case "limits.cpu", "requests.cpu": - if !validContainerResourceDivisorForCPU.Has(divisor.String()) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("divisor"), rName, "only divisor's values 1m and 1 are supported with the cpu resource")) - } - case "limits.memory", "requests.memory": - if !validContainerResourceDivisorForMemory.Has(divisor.String()) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("divisor"), rName, "only divisor's values 1, 1k, 1M, 1G, 1T, 1P, 1E, 1Ki, 1Mi, 1Gi, 1Ti, 1Pi, 1Ei are supported with the memory resource")) - } - case "limits.ephemeral-storage", "requests.ephemeral-storage": - if !validContainerResourceDivisorForEphemeralStorage.Has(divisor.String()) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("divisor"), rName, "only divisor's values 1, 1k, 1M, 1G, 1T, 1P, 1E, 1Ki, 1Mi, 1Gi, 1Ti, 1Pi, 1Ei are supported with the local ephemeral storage resource")) - } - } - return allErrs -} - -func validateConfigMapKeySelector(s *api.ConfigMapKeySelector, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - nameFn := ValidateNameFunc(ValidateSecretName) - for _, msg := range nameFn(s.Name, false) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), s.Name, msg)) - } - if len(s.Key) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("key"), "")) - } else { - for _, msg := range validation.IsConfigMapKey(s.Key) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("key"), s.Key, msg)) - } - } - - return allErrs -} - -func validateSecretKeySelector(s *api.SecretKeySelector, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - nameFn := ValidateNameFunc(ValidateSecretName) - for _, msg := range nameFn(s.Name, false) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), s.Name, msg)) - } - if len(s.Key) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("key"), "")) - } else { - for _, msg := range validation.IsConfigMapKey(s.Key) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("key"), s.Key, msg)) - } - } - - return allErrs -} - -func ValidateVolumeMounts(mounts []api.VolumeMount, volumes sets.String, container *api.Container, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - mountpoints := sets.NewString() - - for i, mnt := range mounts { - idxPath := fldPath.Index(i) - if len(mnt.Name) == 0 { - allErrs = append(allErrs, field.Required(idxPath.Child("name"), "")) - } else if !volumes.Has(mnt.Name) { - allErrs = append(allErrs, field.NotFound(idxPath.Child("name"), mnt.Name)) - } - if len(mnt.MountPath) == 0 { - allErrs = append(allErrs, field.Required(idxPath.Child("mountPath"), "")) - } - if mountpoints.Has(mnt.MountPath) { - allErrs = append(allErrs, field.Invalid(idxPath.Child("mountPath"), mnt.MountPath, "must be unique")) - } - if !path.IsAbs(mnt.MountPath) { - allErrs = append(allErrs, field.Invalid(idxPath.Child("mountPath"), mnt.MountPath, "must be an absolute path")) - } - mountpoints.Insert(mnt.MountPath) - if len(mnt.SubPath) > 0 { - allErrs = append(allErrs, validateLocalDescendingPath(mnt.SubPath, fldPath.Child("subPath"))...) - } - - if mnt.MountPropagation != nil { - allErrs = append(allErrs, validateMountPropagation(mnt.MountPropagation, container, fldPath.Child("mountPropagation"))...) - } - } - return allErrs -} - -func validateProbe(probe *api.Probe, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - if probe == nil { - return allErrs - } - allErrs = append(allErrs, validateHandler(&probe.Handler, fldPath)...) - - allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.InitialDelaySeconds), fldPath.Child("initialDelaySeconds"))...) - allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.TimeoutSeconds), fldPath.Child("timeoutSeconds"))...) - allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.PeriodSeconds), fldPath.Child("periodSeconds"))...) - allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.SuccessThreshold), fldPath.Child("successThreshold"))...) - allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.FailureThreshold), fldPath.Child("failureThreshold"))...) - return allErrs -} - -func validateClientIPAffinityConfig(config *api.SessionAffinityConfig, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if config == nil { - allErrs = append(allErrs, field.Required(fldPath, fmt.Sprintf("when session affinity type is %s", api.ServiceAffinityClientIP))) - return allErrs - } - if config.ClientIP == nil { - allErrs = append(allErrs, field.Required(fldPath.Child("clientIP"), fmt.Sprintf("when session affinity type is %s", api.ServiceAffinityClientIP))) - return allErrs - } - if config.ClientIP.TimeoutSeconds == nil { - allErrs = append(allErrs, field.Required(fldPath.Child("clientIP").Child("timeoutSeconds"), fmt.Sprintf("when session affinity type is %s", api.ServiceAffinityClientIP))) - return allErrs - } - allErrs = append(allErrs, validateAffinityTimeout(config.ClientIP.TimeoutSeconds, fldPath.Child("clientIP").Child("timeoutSeconds"))...) - - return allErrs -} - -func validateAffinityTimeout(timeout *int32, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if *timeout <= 0 || *timeout > api.MaxClientIPServiceAffinitySeconds { - allErrs = append(allErrs, field.Invalid(fldPath, timeout, fmt.Sprintf("must be greater than 0 and less than %d", api.MaxClientIPServiceAffinitySeconds))) - } - return allErrs -} - -// AccumulateUniqueHostPorts extracts each HostPort of each Container, -// accumulating the results and returning an error if any ports conflict. -func AccumulateUniqueHostPorts(containers []api.Container, accumulator *sets.String, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - for ci, ctr := range containers { - idxPath := fldPath.Index(ci) - portsPath := idxPath.Child("ports") - for pi := range ctr.Ports { - idxPath := portsPath.Index(pi) - port := ctr.Ports[pi].HostPort - if port == 0 { - continue - } - str := fmt.Sprintf("%s/%s/%d", ctr.Ports[pi].Protocol, ctr.Ports[pi].HostIP, port) - if accumulator.Has(str) { - allErrs = append(allErrs, field.Duplicate(idxPath.Child("hostPort"), str)) - } else { - accumulator.Insert(str) - } - } - } - return allErrs -} - -// checkHostPortConflicts checks for colliding Port.HostPort values across -// a slice of containers. -func checkHostPortConflicts(containers []api.Container, fldPath *field.Path) field.ErrorList { - allPorts := sets.String{} - return AccumulateUniqueHostPorts(containers, &allPorts, fldPath) -} - -func validateExecAction(exec *api.ExecAction, fldPath *field.Path) field.ErrorList { - allErrors := field.ErrorList{} - if len(exec.Command) == 0 { - allErrors = append(allErrors, field.Required(fldPath.Child("command"), "")) - } - return allErrors -} - -var supportedHTTPSchemes = sets.NewString(string(api.URISchemeHTTP), string(api.URISchemeHTTPS)) - -func validateHTTPGetAction(http *api.HTTPGetAction, fldPath *field.Path) field.ErrorList { - allErrors := field.ErrorList{} - if len(http.Path) == 0 { - allErrors = append(allErrors, field.Required(fldPath.Child("path"), "")) - } - allErrors = append(allErrors, ValidatePortNumOrName(http.Port, fldPath.Child("port"))...) - if !supportedHTTPSchemes.Has(string(http.Scheme)) { - allErrors = append(allErrors, field.NotSupported(fldPath.Child("scheme"), http.Scheme, supportedHTTPSchemes.List())) - } - for _, header := range http.HTTPHeaders { - for _, msg := range validation.IsHTTPHeaderName(header.Name) { - allErrors = append(allErrors, field.Invalid(fldPath.Child("httpHeaders"), header.Name, msg)) - } - } - return allErrors -} - -func ValidatePortNumOrName(port intstr.IntOrString, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if port.Type == intstr.Int { - for _, msg := range validation.IsValidPortNum(port.IntValue()) { - allErrs = append(allErrs, field.Invalid(fldPath, port.IntValue(), msg)) - } - } else if port.Type == intstr.String { - for _, msg := range validation.IsValidPortName(port.StrVal) { - allErrs = append(allErrs, field.Invalid(fldPath, port.StrVal, msg)) - } - } else { - allErrs = append(allErrs, field.InternalError(fldPath, fmt.Errorf("unknown type: %v", port.Type))) - } - return allErrs -} - -func validateTCPSocketAction(tcp *api.TCPSocketAction, fldPath *field.Path) field.ErrorList { - return ValidatePortNumOrName(tcp.Port, fldPath.Child("port")) -} - -func validateHandler(handler *api.Handler, fldPath *field.Path) field.ErrorList { - numHandlers := 0 - allErrors := field.ErrorList{} - if handler.Exec != nil { - if numHandlers > 0 { - allErrors = append(allErrors, field.Forbidden(fldPath.Child("exec"), "may not specify more than 1 handler type")) - } else { - numHandlers++ - allErrors = append(allErrors, validateExecAction(handler.Exec, fldPath.Child("exec"))...) - } - } - if handler.HTTPGet != nil { - if numHandlers > 0 { - allErrors = append(allErrors, field.Forbidden(fldPath.Child("httpGet"), "may not specify more than 1 handler type")) - } else { - numHandlers++ - allErrors = append(allErrors, validateHTTPGetAction(handler.HTTPGet, fldPath.Child("httpGet"))...) - } - } - if handler.TCPSocket != nil { - if numHandlers > 0 { - allErrors = append(allErrors, field.Forbidden(fldPath.Child("tcpSocket"), "may not specify more than 1 handler type")) - } else { - numHandlers++ - allErrors = append(allErrors, validateTCPSocketAction(handler.TCPSocket, fldPath.Child("tcpSocket"))...) - } - } - if numHandlers == 0 { - allErrors = append(allErrors, field.Required(fldPath, "must specify a handler type")) - } - return allErrors -} - -func validateLifecycle(lifecycle *api.Lifecycle, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if lifecycle.PostStart != nil { - allErrs = append(allErrs, validateHandler(lifecycle.PostStart, fldPath.Child("postStart"))...) - } - if lifecycle.PreStop != nil { - allErrs = append(allErrs, validateHandler(lifecycle.PreStop, fldPath.Child("preStop"))...) - } - return allErrs -} - -var supportedPullPolicies = sets.NewString(string(api.PullAlways), string(api.PullIfNotPresent), string(api.PullNever)) - -func validatePullPolicy(policy api.PullPolicy, fldPath *field.Path) field.ErrorList { - allErrors := field.ErrorList{} - - switch policy { - case api.PullAlways, api.PullIfNotPresent, api.PullNever: - break - case "": - allErrors = append(allErrors, field.Required(fldPath, "")) - default: - allErrors = append(allErrors, field.NotSupported(fldPath, policy, supportedPullPolicies.List())) - } - - return allErrors -} - -func validateInitContainers(containers, otherContainers []api.Container, volumes sets.String, fldPath *field.Path) field.ErrorList { - var allErrs field.ErrorList - if len(containers) > 0 { - allErrs = append(allErrs, validateContainers(containers, volumes, fldPath)...) - } - - allNames := sets.String{} - for _, ctr := range otherContainers { - allNames.Insert(ctr.Name) - } - for i, ctr := range containers { - idxPath := fldPath.Index(i) - if allNames.Has(ctr.Name) { - allErrs = append(allErrs, field.Duplicate(idxPath.Child("name"), ctr.Name)) - } - if len(ctr.Name) > 0 { - allNames.Insert(ctr.Name) - } - if ctr.Lifecycle != nil { - allErrs = append(allErrs, field.Invalid(idxPath.Child("lifecycle"), ctr.Lifecycle, "must not be set for init containers")) - } - if ctr.LivenessProbe != nil { - allErrs = append(allErrs, field.Invalid(idxPath.Child("livenessProbe"), ctr.LivenessProbe, "must not be set for init containers")) - } - if ctr.ReadinessProbe != nil { - allErrs = append(allErrs, field.Invalid(idxPath.Child("readinessProbe"), ctr.ReadinessProbe, "must not be set for init containers")) - } - } - return allErrs -} - -func validateContainers(containers []api.Container, volumes sets.String, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - if len(containers) == 0 { - return append(allErrs, field.Required(fldPath, "")) - } - - allNames := sets.String{} - for i, ctr := range containers { - idxPath := fldPath.Index(i) - namePath := idxPath.Child("name") - if len(ctr.Name) == 0 { - allErrs = append(allErrs, field.Required(namePath, "")) - } else { - allErrs = append(allErrs, ValidateDNS1123Label(ctr.Name, namePath)...) - } - if allNames.Has(ctr.Name) { - allErrs = append(allErrs, field.Duplicate(namePath, ctr.Name)) - } else { - allNames.Insert(ctr.Name) - } - // TODO: do not validate leading and trailing whitespace to preserve backward compatibility. - // for example: https://github.com/openshift/origin/issues/14659 image = " " is special token in pod template - // others may have done similar - if len(ctr.Image) == 0 { - allErrs = append(allErrs, field.Required(idxPath.Child("image"), "")) - } - if ctr.Lifecycle != nil { - allErrs = append(allErrs, validateLifecycle(ctr.Lifecycle, idxPath.Child("lifecycle"))...) - } - allErrs = append(allErrs, validateProbe(ctr.LivenessProbe, idxPath.Child("livenessProbe"))...) - // Liveness-specific validation - if ctr.LivenessProbe != nil && ctr.LivenessProbe.SuccessThreshold != 1 { - allErrs = append(allErrs, field.Invalid(idxPath.Child("livenessProbe", "successThreshold"), ctr.LivenessProbe.SuccessThreshold, "must be 1")) - } - - switch ctr.TerminationMessagePolicy { - case api.TerminationMessageReadFile, api.TerminationMessageFallbackToLogsOnError: - case "": - allErrs = append(allErrs, field.Required(idxPath.Child("terminationMessagePolicy"), "must be 'File' or 'FallbackToLogsOnError'")) - default: - allErrs = append(allErrs, field.Invalid(idxPath.Child("terminationMessagePolicy"), ctr.TerminationMessagePolicy, "must be 'File' or 'FallbackToLogsOnError'")) - } - - allErrs = append(allErrs, validateProbe(ctr.ReadinessProbe, idxPath.Child("readinessProbe"))...) - allErrs = append(allErrs, validateContainerPorts(ctr.Ports, idxPath.Child("ports"))...) - allErrs = append(allErrs, ValidateEnv(ctr.Env, idxPath.Child("env"))...) - allErrs = append(allErrs, ValidateEnvFrom(ctr.EnvFrom, idxPath.Child("envFrom"))...) - allErrs = append(allErrs, ValidateVolumeMounts(ctr.VolumeMounts, volumes, &ctr, idxPath.Child("volumeMounts"))...) - allErrs = append(allErrs, validatePullPolicy(ctr.ImagePullPolicy, idxPath.Child("imagePullPolicy"))...) - allErrs = append(allErrs, ValidateResourceRequirements(&ctr.Resources, idxPath.Child("resources"))...) - allErrs = append(allErrs, ValidateSecurityContext(ctr.SecurityContext, idxPath.Child("securityContext"))...) - } - // Check for colliding ports across all containers. - allErrs = append(allErrs, checkHostPortConflicts(containers, fldPath)...) - - return allErrs -} - -func validateRestartPolicy(restartPolicy *api.RestartPolicy, fldPath *field.Path) field.ErrorList { - allErrors := field.ErrorList{} - switch *restartPolicy { - case api.RestartPolicyAlways, api.RestartPolicyOnFailure, api.RestartPolicyNever: - break - case "": - allErrors = append(allErrors, field.Required(fldPath, "")) - default: - validValues := []string{string(api.RestartPolicyAlways), string(api.RestartPolicyOnFailure), string(api.RestartPolicyNever)} - allErrors = append(allErrors, field.NotSupported(fldPath, *restartPolicy, validValues)) - } - - return allErrors -} - -func validateDNSPolicy(dnsPolicy *api.DNSPolicy, fldPath *field.Path) field.ErrorList { - allErrors := field.ErrorList{} - switch *dnsPolicy { - case api.DNSClusterFirstWithHostNet, api.DNSClusterFirst, api.DNSDefault: - break - case "": - allErrors = append(allErrors, field.Required(fldPath, "")) - default: - validValues := []string{string(api.DNSClusterFirstWithHostNet), string(api.DNSClusterFirst), string(api.DNSDefault)} - allErrors = append(allErrors, field.NotSupported(fldPath, dnsPolicy, validValues)) - } - return allErrors -} - -func validateHostNetwork(hostNetwork bool, containers []api.Container, fldPath *field.Path) field.ErrorList { - allErrors := field.ErrorList{} - if hostNetwork { - for i, container := range containers { - portsPath := fldPath.Index(i).Child("ports") - for i, port := range container.Ports { - idxPath := portsPath.Index(i) - if port.HostPort != port.ContainerPort { - allErrors = append(allErrors, field.Invalid(idxPath.Child("containerPort"), port.ContainerPort, "must match `hostPort` when `hostNetwork` is true")) - } - } - } - } - return allErrors -} - -// validateImagePullSecrets checks to make sure the pull secrets are well -// formed. Right now, we only expect name to be set (it's the only field). If -// this ever changes and someone decides to set those fields, we'd like to -// know. -func validateImagePullSecrets(imagePullSecrets []api.LocalObjectReference, fldPath *field.Path) field.ErrorList { - allErrors := field.ErrorList{} - for i, currPullSecret := range imagePullSecrets { - idxPath := fldPath.Index(i) - strippedRef := api.LocalObjectReference{Name: currPullSecret.Name} - if !reflect.DeepEqual(strippedRef, currPullSecret) { - allErrors = append(allErrors, field.Invalid(idxPath, currPullSecret, "only name may be set")) - } - } - return allErrors -} - -// validateAffinity checks if given affinities are valid -func validateAffinity(affinity *api.Affinity, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - if affinity != nil { - if na := affinity.NodeAffinity; na != nil { - // TODO: Uncomment the next three lines once RequiredDuringSchedulingRequiredDuringExecution is implemented. - // if na.RequiredDuringSchedulingRequiredDuringExecution != nil { - // allErrs = append(allErrs, ValidateNodeSelector(na.RequiredDuringSchedulingRequiredDuringExecution, fldPath.Child("requiredDuringSchedulingRequiredDuringExecution"))...) - // } - - if na.RequiredDuringSchedulingIgnoredDuringExecution != nil { - allErrs = append(allErrs, ValidateNodeSelector(na.RequiredDuringSchedulingIgnoredDuringExecution, fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...) - } - - if len(na.PreferredDuringSchedulingIgnoredDuringExecution) > 0 { - allErrs = append(allErrs, ValidatePreferredSchedulingTerms(na.PreferredDuringSchedulingIgnoredDuringExecution, fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...) - } - } - if affinity.PodAffinity != nil { - allErrs = append(allErrs, validatePodAffinity(affinity.PodAffinity, fldPath.Child("podAffinity"))...) - } - if affinity.PodAntiAffinity != nil { - allErrs = append(allErrs, validatePodAntiAffinity(affinity.PodAntiAffinity, fldPath.Child("podAntiAffinity"))...) - } - } - - return allErrs -} - -func validateTaintEffect(effect *api.TaintEffect, allowEmpty bool, fldPath *field.Path) field.ErrorList { - if !allowEmpty && len(*effect) == 0 { - return field.ErrorList{field.Required(fldPath, "")} - } - - allErrors := field.ErrorList{} - switch *effect { - // TODO: Replace next line with subsequent commented-out line when implement TaintEffectNoScheduleNoAdmit. - case api.TaintEffectNoSchedule, api.TaintEffectPreferNoSchedule, api.TaintEffectNoExecute: - // case api.TaintEffectNoSchedule, api.TaintEffectPreferNoSchedule, api.TaintEffectNoScheduleNoAdmit, api.TaintEffectNoExecute: - default: - validValues := []string{ - string(api.TaintEffectNoSchedule), - string(api.TaintEffectPreferNoSchedule), - string(api.TaintEffectNoExecute), - // TODO: Uncomment this block when implement TaintEffectNoScheduleNoAdmit. - // string(api.TaintEffectNoScheduleNoAdmit), - } - allErrors = append(allErrors, field.NotSupported(fldPath, effect, validValues)) - } - return allErrors -} - -// validateOnlyAddedTolerations validates updated pod tolerations. -func validateOnlyAddedTolerations(newTolerations []api.Toleration, oldTolerations []api.Toleration, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - for _, old := range oldTolerations { - found := false - old.TolerationSeconds = nil - for _, new := range newTolerations { - new.TolerationSeconds = nil - if reflect.DeepEqual(old, new) { - found = true - break - } - } - if !found { - allErrs = append(allErrs, field.Forbidden(fldPath, "existing toleration can not be modified except its tolerationSeconds")) - return allErrs - } - } - - allErrs = append(allErrs, ValidateTolerations(newTolerations, fldPath)...) - return allErrs -} - -func ValidateHostAliases(hostAliases []api.HostAlias, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - for _, hostAlias := range hostAliases { - if ip := net.ParseIP(hostAlias.IP); ip == nil { - allErrs = append(allErrs, field.Invalid(fldPath.Child("ip"), hostAlias.IP, "must be valid IP address")) - } - for _, hostname := range hostAlias.Hostnames { - allErrs = append(allErrs, ValidateDNS1123Subdomain(hostname, fldPath.Child("hostnames"))...) - } - } - return allErrs -} - -// ValidateTolerations tests if given tolerations have valid data. -func ValidateTolerations(tolerations []api.Toleration, fldPath *field.Path) field.ErrorList { - allErrors := field.ErrorList{} - for i, toleration := range tolerations { - idxPath := fldPath.Index(i) - // validate the toleration key - if len(toleration.Key) > 0 { - allErrors = append(allErrors, unversionedvalidation.ValidateLabelName(toleration.Key, idxPath.Child("key"))...) - } - - // empty toleration key with Exists operator and empty value means match all taints - if len(toleration.Key) == 0 && toleration.Operator != api.TolerationOpExists { - allErrors = append(allErrors, field.Invalid(idxPath.Child("operator"), toleration.Operator, - "operator must be Exists when `key` is empty, which means \"match all values and all keys\"")) - } - - if toleration.TolerationSeconds != nil && toleration.Effect != api.TaintEffectNoExecute { - allErrors = append(allErrors, field.Invalid(idxPath.Child("effect"), toleration.Effect, - "effect must be 'NoExecute' when `tolerationSeconds` is set")) - } - - // validate toleration operator and value - switch toleration.Operator { - // empty operator means Equal - case api.TolerationOpEqual, "": - if errs := validation.IsValidLabelValue(toleration.Value); len(errs) != 0 { - allErrors = append(allErrors, field.Invalid(idxPath.Child("operator"), toleration.Value, strings.Join(errs, ";"))) - } - case api.TolerationOpExists: - if len(toleration.Value) > 0 { - allErrors = append(allErrors, field.Invalid(idxPath.Child("operator"), toleration, "value must be empty when `operator` is 'Exists'")) - } - default: - validValues := []string{string(api.TolerationOpEqual), string(api.TolerationOpExists)} - allErrors = append(allErrors, field.NotSupported(idxPath.Child("operator"), toleration.Operator, validValues)) - } - - // validate toleration effect, empty toleration effect means match all taint effects - if len(toleration.Effect) > 0 { - allErrors = append(allErrors, validateTaintEffect(&toleration.Effect, true, idxPath.Child("effect"))...) - } - } - return allErrors -} - -func toResourceNames(resources api.ResourceList) []api.ResourceName { - result := []api.ResourceName{} - for resourceName := range resources { - result = append(result, resourceName) - } - return result -} - -func toSet(resourceNames []api.ResourceName) sets.String { - result := sets.NewString() - for _, resourceName := range resourceNames { - result.Insert(string(resourceName)) - } - return result -} - -func toContainerResourcesSet(ctr *api.Container) sets.String { - resourceNames := toResourceNames(ctr.Resources.Requests) - resourceNames = append(resourceNames, toResourceNames(ctr.Resources.Limits)...) - return toSet(resourceNames) -} - -// validateContainersOnlyForPod does additional validation for containers on a pod versus a pod template -// it only does additive validation of fields not covered in validateContainers -func validateContainersOnlyForPod(containers []api.Container, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - for i, ctr := range containers { - idxPath := fldPath.Index(i) - if len(ctr.Image) != len(strings.TrimSpace(ctr.Image)) { - allErrs = append(allErrs, field.Invalid(idxPath.Child("image"), ctr.Image, "must not have leading or trailing whitespace")) - } - } - return allErrs -} - -// ValidatePod tests if required fields in the pod are set. -func ValidatePod(pod *api.Pod) field.ErrorList { - fldPath := field.NewPath("metadata") - allErrs := ValidateObjectMeta(&pod.ObjectMeta, true, ValidatePodName, fldPath) - allErrs = append(allErrs, ValidatePodSpecificAnnotations(pod.ObjectMeta.Annotations, &pod.Spec, fldPath.Child("annotations"))...) - allErrs = append(allErrs, ValidatePodSpec(&pod.Spec, field.NewPath("spec"))...) - - // we do additional validation only pertinent for pods and not pod templates - // this was done to preserve backwards compatibility - specPath := field.NewPath("spec") - - allErrs = append(allErrs, validateContainersOnlyForPod(pod.Spec.Containers, specPath.Child("containers"))...) - allErrs = append(allErrs, validateContainersOnlyForPod(pod.Spec.InitContainers, specPath.Child("initContainers"))...) - - if utilfeature.DefaultFeatureGate.Enabled(features.HugePages) { - hugePageResources := sets.NewString() - for i := range pod.Spec.Containers { - resourceSet := toContainerResourcesSet(&pod.Spec.Containers[i]) - for resourceStr := range resourceSet { - if v1helper.IsHugePageResourceName(v1.ResourceName(resourceStr)) { - hugePageResources.Insert(resourceStr) - } - } - } - if len(hugePageResources) > 1 { - allErrs = append(allErrs, field.Invalid(specPath, hugePageResources, "must use a single hugepage size in a pod spec")) - } - } - - return allErrs -} - -// ValidatePodSpec tests that the specified PodSpec has valid data. -// This includes checking formatting and uniqueness. It also canonicalizes the -// structure by setting default values and implementing any backwards-compatibility -// tricks. -func ValidatePodSpec(spec *api.PodSpec, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - allVolumes, vErrs := ValidateVolumes(spec.Volumes, fldPath.Child("volumes")) - allErrs = append(allErrs, vErrs...) - allErrs = append(allErrs, validateContainers(spec.Containers, allVolumes, fldPath.Child("containers"))...) - allErrs = append(allErrs, validateInitContainers(spec.InitContainers, spec.Containers, allVolumes, fldPath.Child("initContainers"))...) - allErrs = append(allErrs, validateRestartPolicy(&spec.RestartPolicy, fldPath.Child("restartPolicy"))...) - allErrs = append(allErrs, validateDNSPolicy(&spec.DNSPolicy, fldPath.Child("dnsPolicy"))...) - allErrs = append(allErrs, unversionedvalidation.ValidateLabels(spec.NodeSelector, fldPath.Child("nodeSelector"))...) - allErrs = append(allErrs, ValidatePodSecurityContext(spec.SecurityContext, spec, fldPath, fldPath.Child("securityContext"))...) - allErrs = append(allErrs, validateImagePullSecrets(spec.ImagePullSecrets, fldPath.Child("imagePullSecrets"))...) - allErrs = append(allErrs, validateAffinity(spec.Affinity, fldPath.Child("affinity"))...) - if len(spec.ServiceAccountName) > 0 { - for _, msg := range ValidateServiceAccountName(spec.ServiceAccountName, false) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("serviceAccountName"), spec.ServiceAccountName, msg)) - } - } - - if len(spec.NodeName) > 0 { - for _, msg := range ValidateNodeName(spec.NodeName, false) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("nodeName"), spec.NodeName, msg)) - } - } - - if spec.ActiveDeadlineSeconds != nil { - value := *spec.ActiveDeadlineSeconds - if value < 1 || value > math.MaxInt32 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("activeDeadlineSeconds"), value, validation.InclusiveRangeError(1, math.MaxInt32))) - } - } - - if len(spec.Hostname) > 0 { - allErrs = append(allErrs, ValidateDNS1123Label(spec.Hostname, fldPath.Child("hostname"))...) - } - - if len(spec.Subdomain) > 0 { - allErrs = append(allErrs, ValidateDNS1123Label(spec.Subdomain, fldPath.Child("subdomain"))...) - } - - if len(spec.Tolerations) > 0 { - allErrs = append(allErrs, ValidateTolerations(spec.Tolerations, fldPath.Child("tolerations"))...) - } - - if len(spec.HostAliases) > 0 { - allErrs = append(allErrs, ValidateHostAliases(spec.HostAliases, fldPath.Child("hostAliases"))...) - } - - if len(spec.PriorityClassName) > 0 { - if !utilfeature.DefaultFeatureGate.Enabled(features.PodPriority) { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("priorityClassName"), "Pod priority is disabled by feature-gate")) - } else { - for _, msg := range ValidatePriorityClassName(spec.PriorityClassName, false) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("priorityClassName"), spec.PriorityClassName, msg)) - } - } - } - - if spec.Priority != nil && !utilfeature.DefaultFeatureGate.Enabled(features.PodPriority) { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("priority"), "Pod priority is disabled by feature-gate")) - } - - return allErrs -} - -// ValidateNodeSelectorRequirement tests that the specified NodeSelectorRequirement fields has valid data -func ValidateNodeSelectorRequirement(rq api.NodeSelectorRequirement, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - switch rq.Operator { - case api.NodeSelectorOpIn, api.NodeSelectorOpNotIn: - if len(rq.Values) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("values"), "must be specified when `operator` is 'In' or 'NotIn'")) - } - case api.NodeSelectorOpExists, api.NodeSelectorOpDoesNotExist: - if len(rq.Values) > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("values"), "may not be specified when `operator` is 'Exists' or 'DoesNotExist'")) - } - - case api.NodeSelectorOpGt, api.NodeSelectorOpLt: - if len(rq.Values) != 1 { - allErrs = append(allErrs, field.Required(fldPath.Child("values"), "must be specified single value when `operator` is 'Lt' or 'Gt'")) - } - default: - allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), rq.Operator, "not a valid selector operator")) - } - allErrs = append(allErrs, unversionedvalidation.ValidateLabelName(rq.Key, fldPath.Child("key"))...) - return allErrs -} - -// ValidateNodeSelectorTerm tests that the specified node selector term has valid data -func ValidateNodeSelectorTerm(term api.NodeSelectorTerm, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - if len(term.MatchExpressions) == 0 { - return append(allErrs, field.Required(fldPath.Child("matchExpressions"), "must have at least one node selector requirement")) - } - for j, req := range term.MatchExpressions { - allErrs = append(allErrs, ValidateNodeSelectorRequirement(req, fldPath.Child("matchExpressions").Index(j))...) - } - return allErrs -} - -// ValidateNodeSelector tests that the specified nodeSelector fields has valid data -func ValidateNodeSelector(nodeSelector *api.NodeSelector, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - termFldPath := fldPath.Child("nodeSelectorTerms") - if len(nodeSelector.NodeSelectorTerms) == 0 { - return append(allErrs, field.Required(termFldPath, "must have at least one node selector term")) - } - - for i, term := range nodeSelector.NodeSelectorTerms { - allErrs = append(allErrs, ValidateNodeSelectorTerm(term, termFldPath.Index(i))...) - } - - return allErrs -} - -// ValidateAvoidPodsInNodeAnnotations tests that the serialized AvoidPods in Node.Annotations has valid data -func ValidateAvoidPodsInNodeAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - v1Avoids, err := v1helper.GetAvoidPodsFromNodeAnnotations(annotations) - if err != nil { - allErrs = append(allErrs, field.Invalid(fldPath.Child("AvoidPods"), api.PreferAvoidPodsAnnotationKey, err.Error())) - return allErrs - } - var avoids api.AvoidPods - if err := k8s_api_v1.Convert_v1_AvoidPods_To_api_AvoidPods(&v1Avoids, &avoids, nil); err != nil { - allErrs = append(allErrs, field.Invalid(fldPath.Child("AvoidPods"), api.PreferAvoidPodsAnnotationKey, err.Error())) - return allErrs - } - - if len(avoids.PreferAvoidPods) != 0 { - for i, pa := range avoids.PreferAvoidPods { - idxPath := fldPath.Child(api.PreferAvoidPodsAnnotationKey).Index(i) - allErrs = append(allErrs, validatePreferAvoidPodsEntry(pa, idxPath)...) - } - } - - return allErrs -} - -// validatePreferAvoidPodsEntry tests if given PreferAvoidPodsEntry has valid data. -func validatePreferAvoidPodsEntry(avoidPodEntry api.PreferAvoidPodsEntry, fldPath *field.Path) field.ErrorList { - allErrors := field.ErrorList{} - if avoidPodEntry.PodSignature.PodController == nil { - allErrors = append(allErrors, field.Required(fldPath.Child("PodSignature"), "")) - } else { - if *(avoidPodEntry.PodSignature.PodController.Controller) != true { - allErrors = append(allErrors, - field.Invalid(fldPath.Child("PodSignature").Child("PodController").Child("Controller"), - *(avoidPodEntry.PodSignature.PodController.Controller), "must point to a controller")) - } - } - return allErrors -} - -// ValidatePreferredSchedulingTerms tests that the specified SoftNodeAffinity fields has valid data -func ValidatePreferredSchedulingTerms(terms []api.PreferredSchedulingTerm, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - for i, term := range terms { - if term.Weight <= 0 || term.Weight > 100 { - allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("weight"), term.Weight, "must be in the range 1-100")) - } - - allErrs = append(allErrs, ValidateNodeSelectorTerm(term.Preference, fldPath.Index(i).Child("preference"))...) - } - return allErrs -} - -// validatePodAffinityTerm tests that the specified podAffinityTerm fields have valid data -func validatePodAffinityTerm(podAffinityTerm api.PodAffinityTerm, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(podAffinityTerm.LabelSelector, fldPath.Child("matchExpressions"))...) - for _, name := range podAffinityTerm.Namespaces { - for _, msg := range ValidateNamespaceName(name, false) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), name, msg)) - } - } - if len(podAffinityTerm.TopologyKey) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("topologyKey"), "can not be empty")) - } - return append(allErrs, unversionedvalidation.ValidateLabelName(podAffinityTerm.TopologyKey, fldPath.Child("topologyKey"))...) -} - -// validatePodAffinityTerms tests that the specified podAffinityTerms fields have valid data -func validatePodAffinityTerms(podAffinityTerms []api.PodAffinityTerm, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - for i, podAffinityTerm := range podAffinityTerms { - allErrs = append(allErrs, validatePodAffinityTerm(podAffinityTerm, fldPath.Index(i))...) - } - return allErrs -} - -// validateWeightedPodAffinityTerms tests that the specified weightedPodAffinityTerms fields have valid data -func validateWeightedPodAffinityTerms(weightedPodAffinityTerms []api.WeightedPodAffinityTerm, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - for j, weightedTerm := range weightedPodAffinityTerms { - if weightedTerm.Weight <= 0 || weightedTerm.Weight > 100 { - allErrs = append(allErrs, field.Invalid(fldPath.Index(j).Child("weight"), weightedTerm.Weight, "must be in the range 1-100")) - } - allErrs = append(allErrs, validatePodAffinityTerm(weightedTerm.PodAffinityTerm, fldPath.Index(j).Child("podAffinityTerm"))...) - } - return allErrs -} - -// validatePodAntiAffinity tests that the specified podAntiAffinity fields have valid data -func validatePodAntiAffinity(podAntiAffinity *api.PodAntiAffinity, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - // TODO:Uncomment below code once RequiredDuringSchedulingRequiredDuringExecution is implemented. - // if podAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution != nil { - // allErrs = append(allErrs, validatePodAffinityTerms(podAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution, false, - // fldPath.Child("requiredDuringSchedulingRequiredDuringExecution"))...) - //} - if podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil { - allErrs = append(allErrs, validatePodAffinityTerms(podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution, - fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...) - } - if podAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil { - allErrs = append(allErrs, validateWeightedPodAffinityTerms(podAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution, - fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...) - } - return allErrs -} - -// validatePodAffinity tests that the specified podAffinity fields have valid data -func validatePodAffinity(podAffinity *api.PodAffinity, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - // TODO:Uncomment below code once RequiredDuringSchedulingRequiredDuringExecution is implemented. - // if podAffinity.RequiredDuringSchedulingRequiredDuringExecution != nil { - // allErrs = append(allErrs, validatePodAffinityTerms(podAffinity.RequiredDuringSchedulingRequiredDuringExecution, false, - // fldPath.Child("requiredDuringSchedulingRequiredDuringExecution"))...) - //} - if podAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil { - allErrs = append(allErrs, validatePodAffinityTerms(podAffinity.RequiredDuringSchedulingIgnoredDuringExecution, - fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...) - } - if podAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil { - allErrs = append(allErrs, validateWeightedPodAffinityTerms(podAffinity.PreferredDuringSchedulingIgnoredDuringExecution, - fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...) - } - return allErrs -} - -func ValidateSeccompProfile(p string, fldPath *field.Path) field.ErrorList { - if p == "docker/default" { - return nil - } - if p == "unconfined" { - return nil - } - if strings.HasPrefix(p, "localhost/") { - return validateLocalDescendingPath(strings.TrimPrefix(p, "localhost/"), fldPath) - } - return field.ErrorList{field.Invalid(fldPath, p, "must be a valid seccomp profile")} -} - -func ValidateSeccompPodAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if p, exists := annotations[api.SeccompPodAnnotationKey]; exists { - allErrs = append(allErrs, ValidateSeccompProfile(p, fldPath.Child(api.SeccompPodAnnotationKey))...) - } - for k, p := range annotations { - if strings.HasPrefix(k, api.SeccompContainerAnnotationKeyPrefix) { - allErrs = append(allErrs, ValidateSeccompProfile(p, fldPath.Child(k))...) - } - } - - return allErrs -} - -func ValidateAppArmorPodAnnotations(annotations map[string]string, spec *api.PodSpec, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - for k, p := range annotations { - if !strings.HasPrefix(k, apparmor.ContainerAnnotationKeyPrefix) { - continue - } - // TODO: this belongs to admission, not general pod validation: - if !utilfeature.DefaultFeatureGate.Enabled(features.AppArmor) { - allErrs = append(allErrs, field.Forbidden(fldPath.Key(k), "AppArmor is disabled by feature-gate")) - continue - } - containerName := strings.TrimPrefix(k, apparmor.ContainerAnnotationKeyPrefix) - if !podSpecHasContainer(spec, containerName) { - allErrs = append(allErrs, field.Invalid(fldPath.Key(k), containerName, "container not found")) - } - - if err := apparmor.ValidateProfileFormat(p); err != nil { - allErrs = append(allErrs, field.Invalid(fldPath.Key(k), p, err.Error())) - } - } - - return allErrs -} - -func podSpecHasContainer(spec *api.PodSpec, containerName string) bool { - for _, c := range spec.InitContainers { - if c.Name == containerName { - return true - } - } - for _, c := range spec.Containers { - if c.Name == containerName { - return true - } - } - return false -} - -const ( - // a sysctl segment regex, concatenated with dots to form a sysctl name - SysctlSegmentFmt string = "[a-z0-9]([-_a-z0-9]*[a-z0-9])?" - - // a sysctl name regex - SysctlFmt string = "(" + SysctlSegmentFmt + "\\.)*" + SysctlSegmentFmt - - // the maximal length of a sysctl name - SysctlMaxLength int = 253 -) - -var sysctlRegexp = regexp.MustCompile("^" + SysctlFmt + "$") - -// IsValidSysctlName checks that the given string is a valid sysctl name, -// i.e. matches SysctlFmt. -func IsValidSysctlName(name string) bool { - if len(name) > SysctlMaxLength { - return false - } - return sysctlRegexp.MatchString(name) -} - -func validateSysctls(sysctls []api.Sysctl, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - for i, s := range sysctls { - if len(s.Name) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("name"), "")) - } else if !IsValidSysctlName(s.Name) { - allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("name"), s.Name, fmt.Sprintf("must have at most %d characters and match regex %s", SysctlMaxLength, SysctlFmt))) - } - } - return allErrs -} - -// ValidatePodSecurityContext test that the specified PodSecurityContext has valid data. -func ValidatePodSecurityContext(securityContext *api.PodSecurityContext, spec *api.PodSpec, specPath, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - if securityContext != nil { - allErrs = append(allErrs, validateHostNetwork(securityContext.HostNetwork, spec.Containers, specPath.Child("containers"))...) - if securityContext.FSGroup != nil { - for _, msg := range validation.IsValidGroupID(*securityContext.FSGroup) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("fsGroup"), *(securityContext.FSGroup), msg)) - } - } - if securityContext.RunAsUser != nil { - for _, msg := range validation.IsValidUserID(*securityContext.RunAsUser) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsUser"), *(securityContext.RunAsUser), msg)) - } - } - for g, gid := range securityContext.SupplementalGroups { - for _, msg := range validation.IsValidGroupID(gid) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("supplementalGroups").Index(g), gid, msg)) - } - } - } - - return allErrs -} - -func ValidateContainerUpdates(newContainers, oldContainers []api.Container, fldPath *field.Path) (allErrs field.ErrorList, stop bool) { - allErrs = field.ErrorList{} - if len(newContainers) != len(oldContainers) { - //TODO: Pinpoint the specific container that causes the invalid error after we have strategic merge diff - allErrs = append(allErrs, field.Forbidden(fldPath, "pod updates may not add or remove containers")) - return allErrs, true - } - - // validate updated container images - for i, ctr := range newContainers { - if len(ctr.Image) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("image"), "")) - } - // this is only called from ValidatePodUpdate so its safe to check leading/trailing whitespace. - if len(strings.TrimSpace(ctr.Image)) != len(ctr.Image) { - allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("image"), ctr.Image, "must not have leading or trailing whitespace")) - } - } - return allErrs, false -} - -// ValidatePodUpdate tests to see if the update is legal for an end user to make. newPod is updated with fields -// that cannot be changed. -func ValidatePodUpdate(newPod, oldPod *api.Pod) field.ErrorList { - fldPath := field.NewPath("metadata") - allErrs := ValidateObjectMetaUpdate(&newPod.ObjectMeta, &oldPod.ObjectMeta, fldPath) - allErrs = append(allErrs, ValidatePodSpecificAnnotationUpdates(newPod, oldPod, fldPath.Child("annotations"))...) - specPath := field.NewPath("spec") - - // validate updateable fields: - // 1. spec.containers[*].image - // 2. spec.initContainers[*].image - // 3. spec.activeDeadlineSeconds - - containerErrs, stop := ValidateContainerUpdates(newPod.Spec.Containers, oldPod.Spec.Containers, specPath.Child("containers")) - allErrs = append(allErrs, containerErrs...) - if stop { - return allErrs - } - containerErrs, stop = ValidateContainerUpdates(newPod.Spec.InitContainers, oldPod.Spec.InitContainers, specPath.Child("initContainers")) - allErrs = append(allErrs, containerErrs...) - if stop { - return allErrs - } - - // validate updated spec.activeDeadlineSeconds. two types of updates are allowed: - // 1. from nil to a positive value - // 2. from a positive value to a lesser, non-negative value - if newPod.Spec.ActiveDeadlineSeconds != nil { - newActiveDeadlineSeconds := *newPod.Spec.ActiveDeadlineSeconds - if newActiveDeadlineSeconds < 0 || newActiveDeadlineSeconds > math.MaxInt32 { - allErrs = append(allErrs, field.Invalid(specPath.Child("activeDeadlineSeconds"), newActiveDeadlineSeconds, validation.InclusiveRangeError(0, math.MaxInt32))) - return allErrs - } - if oldPod.Spec.ActiveDeadlineSeconds != nil { - oldActiveDeadlineSeconds := *oldPod.Spec.ActiveDeadlineSeconds - if oldActiveDeadlineSeconds < newActiveDeadlineSeconds { - allErrs = append(allErrs, field.Invalid(specPath.Child("activeDeadlineSeconds"), newActiveDeadlineSeconds, "must be less than or equal to previous value")) - return allErrs - } - } - } else if oldPod.Spec.ActiveDeadlineSeconds != nil { - allErrs = append(allErrs, field.Invalid(specPath.Child("activeDeadlineSeconds"), newPod.Spec.ActiveDeadlineSeconds, "must not update from a positive integer to nil value")) - } - - // handle updateable fields by munging those fields prior to deep equal comparison. - mungedPod := *newPod - // munge spec.containers[*].image - var newContainers []api.Container - for ix, container := range mungedPod.Spec.Containers { - container.Image = oldPod.Spec.Containers[ix].Image - newContainers = append(newContainers, container) - } - mungedPod.Spec.Containers = newContainers - // munge spec.initContainers[*].image - var newInitContainers []api.Container - for ix, container := range mungedPod.Spec.InitContainers { - container.Image = oldPod.Spec.InitContainers[ix].Image - newInitContainers = append(newInitContainers, container) - } - mungedPod.Spec.InitContainers = newInitContainers - // munge spec.activeDeadlineSeconds - mungedPod.Spec.ActiveDeadlineSeconds = nil - if oldPod.Spec.ActiveDeadlineSeconds != nil { - activeDeadlineSeconds := *oldPod.Spec.ActiveDeadlineSeconds - mungedPod.Spec.ActiveDeadlineSeconds = &activeDeadlineSeconds - } - - // Allow only additions to tolerations updates. - mungedPod.Spec.Tolerations = oldPod.Spec.Tolerations - allErrs = append(allErrs, validateOnlyAddedTolerations(newPod.Spec.Tolerations, oldPod.Spec.Tolerations, specPath.Child("tolerations"))...) - - if !apiequality.Semantic.DeepEqual(mungedPod.Spec, oldPod.Spec) { - // This diff isn't perfect, but it's a helluva lot better an "I'm not going to tell you what the difference is". - //TODO: Pinpoint the specific field that causes the invalid error after we have strategic merge diff - specDiff := diff.ObjectDiff(mungedPod.Spec, oldPod.Spec) - allErrs = append(allErrs, field.Forbidden(specPath, fmt.Sprintf("pod updates may not change fields other than `spec.containers[*].image`, `spec.initContainers[*].image`, `spec.activeDeadlineSeconds` or `spec.tolerations` (only additions to existing tolerations)\n%v", specDiff))) - } - - return allErrs -} - -// ValidatePodStatusUpdate tests to see if the update is legal for an end user to make. newPod is updated with fields -// that cannot be changed. -func ValidatePodStatusUpdate(newPod, oldPod *api.Pod) field.ErrorList { - fldPath := field.NewPath("metadata") - allErrs := ValidateObjectMetaUpdate(&newPod.ObjectMeta, &oldPod.ObjectMeta, fldPath) - allErrs = append(allErrs, ValidatePodSpecificAnnotationUpdates(newPod, oldPod, fldPath.Child("annotations"))...) - - if newPod.Spec.NodeName != oldPod.Spec.NodeName { - allErrs = append(allErrs, field.Forbidden(field.NewPath("status", "nodeName"), "may not be changed directly")) - } - - // For status update we ignore changes to pod spec. - newPod.Spec = oldPod.Spec - - return allErrs -} - -// ValidatePodBinding tests if required fields in the pod binding are legal. -func ValidatePodBinding(binding *api.Binding) field.ErrorList { - allErrs := field.ErrorList{} - - if len(binding.Target.Kind) != 0 && binding.Target.Kind != "Node" { - // TODO: When validation becomes versioned, this gets more complicated. - allErrs = append(allErrs, field.NotSupported(field.NewPath("target", "kind"), binding.Target.Kind, []string{"Node", ""})) - } - if len(binding.Target.Name) == 0 { - // TODO: When validation becomes versioned, this gets more complicated. - allErrs = append(allErrs, field.Required(field.NewPath("target", "name"), "")) - } - - return allErrs -} - -// ValidatePodTemplate tests if required fields in the pod template are set. -func ValidatePodTemplate(pod *api.PodTemplate) field.ErrorList { - allErrs := ValidateObjectMeta(&pod.ObjectMeta, true, ValidatePodName, field.NewPath("metadata")) - allErrs = append(allErrs, ValidatePodTemplateSpec(&pod.Template, field.NewPath("template"))...) - return allErrs -} - -// ValidatePodTemplateUpdate tests to see if the update is legal for an end user to make. newPod is updated with fields -// that cannot be changed. -func ValidatePodTemplateUpdate(newPod, oldPod *api.PodTemplate) field.ErrorList { - allErrs := ValidateObjectMetaUpdate(&oldPod.ObjectMeta, &newPod.ObjectMeta, field.NewPath("metadata")) - allErrs = append(allErrs, ValidatePodTemplateSpec(&newPod.Template, field.NewPath("template"))...) - return allErrs -} - -var supportedSessionAffinityType = sets.NewString(string(api.ServiceAffinityClientIP), string(api.ServiceAffinityNone)) -var supportedServiceType = sets.NewString(string(api.ServiceTypeClusterIP), string(api.ServiceTypeNodePort), - string(api.ServiceTypeLoadBalancer), string(api.ServiceTypeExternalName)) - -// ValidateService tests if required fields/annotations of a Service are valid. -func ValidateService(service *api.Service) field.ErrorList { - allErrs := ValidateObjectMeta(&service.ObjectMeta, true, ValidateServiceName, field.NewPath("metadata")) - - specPath := field.NewPath("spec") - isHeadlessService := service.Spec.ClusterIP == api.ClusterIPNone - if len(service.Spec.Ports) == 0 && !isHeadlessService && service.Spec.Type != api.ServiceTypeExternalName { - allErrs = append(allErrs, field.Required(specPath.Child("ports"), "")) - } - switch service.Spec.Type { - case api.ServiceTypeLoadBalancer: - for ix := range service.Spec.Ports { - port := &service.Spec.Ports[ix] - // This is a workaround for broken cloud environments that - // over-open firewalls. Hopefully it can go away when more clouds - // understand containers better. - if port.Port == 10250 { - portPath := specPath.Child("ports").Index(ix) - allErrs = append(allErrs, field.Invalid(portPath, port.Port, "may not expose port 10250 externally since it is used by kubelet")) - } - } - if service.Spec.ClusterIP == "None" { - allErrs = append(allErrs, field.Invalid(specPath.Child("clusterIP"), service.Spec.ClusterIP, "may not be set to 'None' for LoadBalancer services")) - } - case api.ServiceTypeNodePort: - if service.Spec.ClusterIP == "None" { - allErrs = append(allErrs, field.Invalid(specPath.Child("clusterIP"), service.Spec.ClusterIP, "may not be set to 'None' for NodePort services")) - } - case api.ServiceTypeExternalName: - if service.Spec.ClusterIP != "" { - allErrs = append(allErrs, field.Invalid(specPath.Child("clusterIP"), service.Spec.ClusterIP, "must be empty for ExternalName services")) - } - if len(service.Spec.ExternalName) > 0 { - allErrs = append(allErrs, ValidateDNS1123Subdomain(service.Spec.ExternalName, specPath.Child("externalName"))...) - } else { - allErrs = append(allErrs, field.Required(specPath.Child("externalName"), "")) - } - } - - allPortNames := sets.String{} - portsPath := specPath.Child("ports") - for i := range service.Spec.Ports { - portPath := portsPath.Index(i) - allErrs = append(allErrs, validateServicePort(&service.Spec.Ports[i], len(service.Spec.Ports) > 1, isHeadlessService, &allPortNames, portPath)...) - } - - if service.Spec.Selector != nil { - allErrs = append(allErrs, unversionedvalidation.ValidateLabels(service.Spec.Selector, specPath.Child("selector"))...) - } - - if len(service.Spec.SessionAffinity) == 0 { - allErrs = append(allErrs, field.Required(specPath.Child("sessionAffinity"), "")) - } else if !supportedSessionAffinityType.Has(string(service.Spec.SessionAffinity)) { - allErrs = append(allErrs, field.NotSupported(specPath.Child("sessionAffinity"), service.Spec.SessionAffinity, supportedSessionAffinityType.List())) - } - - if service.Spec.SessionAffinity == api.ServiceAffinityClientIP { - allErrs = append(allErrs, validateClientIPAffinityConfig(service.Spec.SessionAffinityConfig, specPath.Child("sessionAffinityConfig"))...) - } else if service.Spec.SessionAffinity == api.ServiceAffinityNone { - if service.Spec.SessionAffinityConfig != nil { - allErrs = append(allErrs, field.Forbidden(specPath.Child("sessionAffinityConfig"), fmt.Sprintf("must not be set when session affinity is %s", string(api.ServiceAffinityNone)))) - } - } - - if helper.IsServiceIPSet(service) { - if ip := net.ParseIP(service.Spec.ClusterIP); ip == nil { - allErrs = append(allErrs, field.Invalid(specPath.Child("clusterIP"), service.Spec.ClusterIP, "must be empty, 'None', or a valid IP address")) - } - } - - ipPath := specPath.Child("externalIPs") - for i, ip := range service.Spec.ExternalIPs { - idxPath := ipPath.Index(i) - if msgs := validation.IsValidIP(ip); len(msgs) != 0 { - for i := range msgs { - allErrs = append(allErrs, field.Invalid(idxPath, ip, msgs[i])) - } - } else { - allErrs = append(allErrs, validateNonSpecialIP(ip, idxPath)...) - } - } - - if len(service.Spec.Type) == 0 { - allErrs = append(allErrs, field.Required(specPath.Child("type"), "")) - } else if !supportedServiceType.Has(string(service.Spec.Type)) { - allErrs = append(allErrs, field.NotSupported(specPath.Child("type"), service.Spec.Type, supportedServiceType.List())) - } - - if service.Spec.Type == api.ServiceTypeLoadBalancer { - portsPath := specPath.Child("ports") - includeProtocols := sets.NewString() - for i := range service.Spec.Ports { - portPath := portsPath.Index(i) - if !supportedPortProtocols.Has(string(service.Spec.Ports[i].Protocol)) { - allErrs = append(allErrs, field.Invalid(portPath.Child("protocol"), service.Spec.Ports[i].Protocol, "cannot create an external load balancer with non-TCP/UDP ports")) - } else { - includeProtocols.Insert(string(service.Spec.Ports[i].Protocol)) - } - } - if includeProtocols.Len() > 1 { - allErrs = append(allErrs, field.Invalid(portsPath, service.Spec.Ports, "cannot create an external load balancer with mix protocols")) - } - } - - if service.Spec.Type == api.ServiceTypeClusterIP { - portsPath := specPath.Child("ports") - for i := range service.Spec.Ports { - portPath := portsPath.Index(i) - if service.Spec.Ports[i].NodePort != 0 { - allErrs = append(allErrs, field.Invalid(portPath.Child("nodePort"), service.Spec.Ports[i].NodePort, "may not be used when `type` is 'ClusterIP'")) - } - } - } - - // Check for duplicate NodePorts, considering (protocol,port) pairs - portsPath = specPath.Child("ports") - nodePorts := make(map[api.ServicePort]bool) - for i := range service.Spec.Ports { - port := &service.Spec.Ports[i] - if port.NodePort == 0 { - continue - } - portPath := portsPath.Index(i) - var key api.ServicePort - key.Protocol = port.Protocol - key.NodePort = port.NodePort - _, found := nodePorts[key] - if found { - allErrs = append(allErrs, field.Duplicate(portPath.Child("nodePort"), port.NodePort)) - } - nodePorts[key] = true - } - - // Check for duplicate Ports, considering (protocol,port) pairs - portsPath = specPath.Child("ports") - ports := make(map[api.ServicePort]bool) - for i, port := range service.Spec.Ports { - portPath := portsPath.Index(i) - key := api.ServicePort{Protocol: port.Protocol, Port: port.Port} - _, found := ports[key] - if found { - allErrs = append(allErrs, field.Duplicate(portPath, key)) - } - ports[key] = true - } - - // Check for duplicate TargetPort - portsPath = specPath.Child("ports") - targetPorts := make(map[api.ServicePort]bool) - for i, port := range service.Spec.Ports { - if (port.TargetPort.Type == intstr.Int && port.TargetPort.IntVal == 0) || (port.TargetPort.Type == intstr.String && port.TargetPort.StrVal == "") { - continue - } - portPath := portsPath.Index(i) - key := api.ServicePort{Protocol: port.Protocol, TargetPort: port.TargetPort} - _, found := targetPorts[key] - if found { - allErrs = append(allErrs, field.Duplicate(portPath.Child("targetPort"), port.TargetPort)) - } - targetPorts[key] = true - } - - // Validate SourceRange field and annotation - _, ok := service.Annotations[api.AnnotationLoadBalancerSourceRangesKey] - if len(service.Spec.LoadBalancerSourceRanges) > 0 || ok { - var fieldPath *field.Path - var val string - if len(service.Spec.LoadBalancerSourceRanges) > 0 { - fieldPath = specPath.Child("LoadBalancerSourceRanges") - val = fmt.Sprintf("%v", service.Spec.LoadBalancerSourceRanges) - } else { - fieldPath = field.NewPath("metadata", "annotations").Key(api.AnnotationLoadBalancerSourceRangesKey) - val = service.Annotations[api.AnnotationLoadBalancerSourceRangesKey] - } - if service.Spec.Type != api.ServiceTypeLoadBalancer { - allErrs = append(allErrs, field.Invalid(fieldPath, "", "may only be used when `type` is 'LoadBalancer'")) - } - _, err := apiservice.GetLoadBalancerSourceRanges(service) - if err != nil { - allErrs = append(allErrs, field.Invalid(fieldPath, val, "must be a list of IP ranges. For example, 10.240.0.0/24,10.250.0.0/24 ")) - } - } - - allErrs = append(allErrs, validateServiceExternalTrafficFieldsValue(service)...) - - return allErrs -} - -func validateServicePort(sp *api.ServicePort, requireName, isHeadlessService bool, allNames *sets.String, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - if requireName && len(sp.Name) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) - } else if len(sp.Name) != 0 { - allErrs = append(allErrs, ValidateDNS1123Label(sp.Name, fldPath.Child("name"))...) - if allNames.Has(sp.Name) { - allErrs = append(allErrs, field.Duplicate(fldPath.Child("name"), sp.Name)) - } else { - allNames.Insert(sp.Name) - } - } - - for _, msg := range validation.IsValidPortNum(int(sp.Port)) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("port"), sp.Port, msg)) - } - - if len(sp.Protocol) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("protocol"), "")) - } else if !supportedPortProtocols.Has(string(sp.Protocol)) { - allErrs = append(allErrs, field.NotSupported(fldPath.Child("protocol"), sp.Protocol, supportedPortProtocols.List())) - } - - allErrs = append(allErrs, ValidatePortNumOrName(sp.TargetPort, fldPath.Child("targetPort"))...) - - // in the v1 API, targetPorts on headless services were tolerated. - // once we have version-specific validation, we can reject this on newer API versions, but until then, we have to tolerate it for compatibility. - // - // if isHeadlessService { - // if sp.TargetPort.Type == intstr.String || (sp.TargetPort.Type == intstr.Int && sp.Port != sp.TargetPort.IntValue()) { - // allErrs = append(allErrs, field.Invalid(fldPath.Child("targetPort"), sp.TargetPort, "must be equal to the value of 'port' when clusterIP = None")) - // } - // } - - return allErrs -} - -// validateServiceExternalTrafficFieldsValue validates ExternalTraffic related annotations -// have legal value. -func validateServiceExternalTrafficFieldsValue(service *api.Service) field.ErrorList { - allErrs := field.ErrorList{} - - // Check first class fields. - if service.Spec.ExternalTrafficPolicy != "" && - service.Spec.ExternalTrafficPolicy != api.ServiceExternalTrafficPolicyTypeCluster && - service.Spec.ExternalTrafficPolicy != api.ServiceExternalTrafficPolicyTypeLocal { - allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("externalTrafficPolicy"), service.Spec.ExternalTrafficPolicy, - fmt.Sprintf("ExternalTrafficPolicy must be empty, %v or %v", api.ServiceExternalTrafficPolicyTypeCluster, api.ServiceExternalTrafficPolicyTypeLocal))) - } - if service.Spec.HealthCheckNodePort < 0 { - allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("healthCheckNodePort"), service.Spec.HealthCheckNodePort, - "HealthCheckNodePort must be not less than 0")) - } - - return allErrs -} - -// ValidateServiceExternalTrafficFieldsCombination validates if ExternalTrafficPolicy, -// HealthCheckNodePort and Type combination are legal. For update, it should be called -// after clearing externalTraffic related fields for the ease of transitioning between -// different service types. -func ValidateServiceExternalTrafficFieldsCombination(service *api.Service) field.ErrorList { - allErrs := field.ErrorList{} - - if service.Spec.Type != api.ServiceTypeLoadBalancer && - service.Spec.Type != api.ServiceTypeNodePort && - service.Spec.ExternalTrafficPolicy != "" { - allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "externalTrafficPolicy"), service.Spec.ExternalTrafficPolicy, - "ExternalTrafficPolicy can only be set on NodePort and LoadBalancer service")) - } - - if !apiservice.NeedsHealthCheck(service) && - service.Spec.HealthCheckNodePort != 0 { - allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "healthCheckNodePort"), service.Spec.HealthCheckNodePort, - "HealthCheckNodePort can only be set on LoadBalancer service with ExternalTrafficPolicy=Local")) - } - - return allErrs -} - -// ValidateServiceUpdate tests if required fields in the service are set during an update -func ValidateServiceUpdate(service, oldService *api.Service) field.ErrorList { - allErrs := ValidateObjectMetaUpdate(&service.ObjectMeta, &oldService.ObjectMeta, field.NewPath("metadata")) - - // ClusterIP should be immutable for services using it (every type other than ExternalName) - // which do not have ClusterIP assigned yet (empty string value) - if service.Spec.Type != api.ServiceTypeExternalName { - if oldService.Spec.Type != api.ServiceTypeExternalName && oldService.Spec.ClusterIP != "" { - allErrs = append(allErrs, ValidateImmutableField(service.Spec.ClusterIP, oldService.Spec.ClusterIP, field.NewPath("spec", "clusterIP"))...) - } - } - - allErrs = append(allErrs, ValidateService(service)...) - return allErrs -} - -// ValidateServiceStatusUpdate tests if required fields in the Service are set when updating status. -func ValidateServiceStatusUpdate(service, oldService *api.Service) field.ErrorList { - allErrs := ValidateObjectMetaUpdate(&service.ObjectMeta, &oldService.ObjectMeta, field.NewPath("metadata")) - allErrs = append(allErrs, ValidateLoadBalancerStatus(&service.Status.LoadBalancer, field.NewPath("status", "loadBalancer"))...) - return allErrs -} - -// ValidateReplicationController tests if required fields in the replication controller are set. -func ValidateReplicationController(controller *api.ReplicationController) field.ErrorList { - allErrs := ValidateObjectMeta(&controller.ObjectMeta, true, ValidateReplicationControllerName, field.NewPath("metadata")) - allErrs = append(allErrs, ValidateReplicationControllerSpec(&controller.Spec, field.NewPath("spec"))...) - return allErrs -} - -// ValidateReplicationControllerUpdate tests if required fields in the replication controller are set. -func ValidateReplicationControllerUpdate(controller, oldController *api.ReplicationController) field.ErrorList { - allErrs := ValidateObjectMetaUpdate(&controller.ObjectMeta, &oldController.ObjectMeta, field.NewPath("metadata")) - allErrs = append(allErrs, ValidateReplicationControllerSpec(&controller.Spec, field.NewPath("spec"))...) - return allErrs -} - -// ValidateReplicationControllerStatusUpdate tests if required fields in the replication controller are set. -func ValidateReplicationControllerStatusUpdate(controller, oldController *api.ReplicationController) field.ErrorList { - allErrs := ValidateObjectMetaUpdate(&controller.ObjectMeta, &oldController.ObjectMeta, field.NewPath("metadata")) - allErrs = append(allErrs, ValidateReplicationControllerStatus(controller.Status, field.NewPath("status"))...) - return allErrs -} - -func ValidateReplicationControllerStatus(status api.ReplicationControllerStatus, statusPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - allErrs = append(allErrs, ValidateNonnegativeField(int64(status.Replicas), statusPath.Child("replicas"))...) - allErrs = append(allErrs, ValidateNonnegativeField(int64(status.FullyLabeledReplicas), statusPath.Child("fullyLabeledReplicas"))...) - allErrs = append(allErrs, ValidateNonnegativeField(int64(status.ReadyReplicas), statusPath.Child("readyReplicas"))...) - allErrs = append(allErrs, ValidateNonnegativeField(int64(status.AvailableReplicas), statusPath.Child("availableReplicas"))...) - allErrs = append(allErrs, ValidateNonnegativeField(int64(status.ObservedGeneration), statusPath.Child("observedGeneration"))...) - msg := "cannot be greater than status.replicas" - if status.FullyLabeledReplicas > status.Replicas { - allErrs = append(allErrs, field.Invalid(statusPath.Child("fullyLabeledReplicas"), status.FullyLabeledReplicas, msg)) - } - if status.ReadyReplicas > status.Replicas { - allErrs = append(allErrs, field.Invalid(statusPath.Child("readyReplicas"), status.ReadyReplicas, msg)) - } - if status.AvailableReplicas > status.Replicas { - allErrs = append(allErrs, field.Invalid(statusPath.Child("availableReplicas"), status.AvailableReplicas, msg)) - } - if status.AvailableReplicas > status.ReadyReplicas { - allErrs = append(allErrs, field.Invalid(statusPath.Child("availableReplicas"), status.AvailableReplicas, "cannot be greater than readyReplicas")) - } - return allErrs -} - -// Validates that the given selector is non-empty. -func ValidateNonEmptySelector(selectorMap map[string]string, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - selector := labels.Set(selectorMap).AsSelector() - if selector.Empty() { - allErrs = append(allErrs, field.Required(fldPath, "")) - } - return allErrs -} - -// Validates the given template and ensures that it is in accordance with the desired selector and replicas. -func ValidatePodTemplateSpecForRC(template *api.PodTemplateSpec, selectorMap map[string]string, replicas int32, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if template == nil { - allErrs = append(allErrs, field.Required(fldPath, "")) - } else { - selector := labels.Set(selectorMap).AsSelector() - if !selector.Empty() { - // Verify that the RC selector matches the labels in template. - labels := labels.Set(template.Labels) - if !selector.Matches(labels) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("metadata", "labels"), template.Labels, "`selector` does not match template `labels`")) - } - } - allErrs = append(allErrs, ValidatePodTemplateSpec(template, fldPath)...) - if replicas > 1 { - allErrs = append(allErrs, ValidateReadOnlyPersistentDisks(template.Spec.Volumes, fldPath.Child("spec", "volumes"))...) - } - // RestartPolicy has already been first-order validated as per ValidatePodTemplateSpec(). - if template.Spec.RestartPolicy != api.RestartPolicyAlways { - allErrs = append(allErrs, field.NotSupported(fldPath.Child("spec", "restartPolicy"), template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)})) - } - if template.Spec.ActiveDeadlineSeconds != nil { - allErrs = append(allErrs, field.Invalid(fldPath.Child("spec", "activeDeadlineSeconds"), template.Spec.ActiveDeadlineSeconds, "must not be specified")) - } - } - return allErrs -} - -// ValidateReplicationControllerSpec tests if required fields in the replication controller spec are set. -func ValidateReplicationControllerSpec(spec *api.ReplicationControllerSpec, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - allErrs = append(allErrs, ValidateNonnegativeField(int64(spec.MinReadySeconds), fldPath.Child("minReadySeconds"))...) - allErrs = append(allErrs, ValidateNonEmptySelector(spec.Selector, fldPath.Child("selector"))...) - allErrs = append(allErrs, ValidateNonnegativeField(int64(spec.Replicas), fldPath.Child("replicas"))...) - allErrs = append(allErrs, ValidatePodTemplateSpecForRC(spec.Template, spec.Selector, spec.Replicas, fldPath.Child("template"))...) - return allErrs -} - -// ValidatePodTemplateSpec validates the spec of a pod template -func ValidatePodTemplateSpec(spec *api.PodTemplateSpec, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - allErrs = append(allErrs, unversionedvalidation.ValidateLabels(spec.Labels, fldPath.Child("labels"))...) - allErrs = append(allErrs, ValidateAnnotations(spec.Annotations, fldPath.Child("annotations"))...) - allErrs = append(allErrs, ValidatePodSpecificAnnotations(spec.Annotations, &spec.Spec, fldPath.Child("annotations"))...) - allErrs = append(allErrs, ValidatePodSpec(&spec.Spec, fldPath.Child("spec"))...) - return allErrs -} - -func ValidateReadOnlyPersistentDisks(volumes []api.Volume, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - for i := range volumes { - vol := &volumes[i] - idxPath := fldPath.Index(i) - if vol.GCEPersistentDisk != nil { - if vol.GCEPersistentDisk.ReadOnly == false { - allErrs = append(allErrs, field.Invalid(idxPath.Child("gcePersistentDisk", "readOnly"), false, "must be true for replicated pods > 1; GCE PD can only be mounted on multiple machines if it is read-only")) - } - } - // TODO: What to do for AWS? It doesn't support replicas - } - return allErrs -} - -// ValidateTaintsInNodeAnnotations tests that the serialized taints in Node.Annotations has valid data -func ValidateTaintsInNodeAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - taints, err := helper.GetTaintsFromNodeAnnotations(annotations) - if err != nil { - allErrs = append(allErrs, field.Invalid(fldPath, api.TaintsAnnotationKey, err.Error())) - return allErrs - } - - if len(taints) > 0 { - allErrs = append(allErrs, validateNodeTaints(taints, fldPath.Child(api.TaintsAnnotationKey))...) - } - - return allErrs -} - -// validateNodeTaints tests if given taints have valid data. -func validateNodeTaints(taints []api.Taint, fldPath *field.Path) field.ErrorList { - allErrors := field.ErrorList{} - - uniqueTaints := map[api.TaintEffect]sets.String{} - - for i, currTaint := range taints { - idxPath := fldPath.Index(i) - // validate the taint key - allErrors = append(allErrors, unversionedvalidation.ValidateLabelName(currTaint.Key, idxPath.Child("key"))...) - // validate the taint value - if errs := validation.IsValidLabelValue(currTaint.Value); len(errs) != 0 { - allErrors = append(allErrors, field.Invalid(idxPath.Child("value"), currTaint.Value, strings.Join(errs, ";"))) - } - // validate the taint effect - allErrors = append(allErrors, validateTaintEffect(&currTaint.Effect, false, idxPath.Child("effect"))...) - - // validate if taint is unique by - if len(uniqueTaints[currTaint.Effect]) > 0 && uniqueTaints[currTaint.Effect].Has(currTaint.Key) { - duplicatedError := field.Duplicate(idxPath, currTaint) - duplicatedError.Detail = "taints must be unique by key and effect pair" - allErrors = append(allErrors, duplicatedError) - continue - } - - // add taint to existingTaints for uniqueness check - if len(uniqueTaints[currTaint.Effect]) == 0 { - uniqueTaints[currTaint.Effect] = sets.String{} - } - uniqueTaints[currTaint.Effect].Insert(currTaint.Key) - } - return allErrors -} - -func ValidateNodeSpecificAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - if annotations[api.TaintsAnnotationKey] != "" { - allErrs = append(allErrs, ValidateTaintsInNodeAnnotations(annotations, fldPath)...) - } - - if annotations[api.PreferAvoidPodsAnnotationKey] != "" { - allErrs = append(allErrs, ValidateAvoidPodsInNodeAnnotations(annotations, fldPath)...) - } - return allErrs -} - -// ValidateNode tests if required fields in the node are set. -func ValidateNode(node *api.Node) field.ErrorList { - fldPath := field.NewPath("metadata") - allErrs := ValidateObjectMeta(&node.ObjectMeta, false, ValidateNodeName, fldPath) - allErrs = append(allErrs, ValidateNodeSpecificAnnotations(node.ObjectMeta.Annotations, fldPath.Child("annotations"))...) - if len(node.Spec.Taints) > 0 { - allErrs = append(allErrs, validateNodeTaints(node.Spec.Taints, fldPath.Child("taints"))...) - } - - // Only validate spec. - // All status fields are optional and can be updated later. - // That said, if specified, we need to ensure they are valid. - allErrs = append(allErrs, ValidateNodeResources(node)...) - - // external ID is required. - if len(node.Spec.ExternalID) == 0 { - allErrs = append(allErrs, field.Required(field.NewPath("spec", "externalID"), "")) - } - - // Only allow Node.Spec.ConfigSource to be set if the DynamicKubeletConfig feature gate is enabled - if node.Spec.ConfigSource != nil && !utilfeature.DefaultFeatureGate.Enabled(features.DynamicKubeletConfig) { - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "configSource"), "configSource may only be set if the DynamicKubeletConfig feature gate is enabled)")) - } - - // TODO(rjnagal): Ignore PodCIDR till its completely implemented. - return allErrs -} - -// ValidateNodeResources is used to make sure a node has valid capacity and allocatable values. -func ValidateNodeResources(node *api.Node) field.ErrorList { - allErrs := field.ErrorList{} - // Validate resource quantities in capacity. - hugePageSizes := sets.NewString() - for k, v := range node.Status.Capacity { - resPath := field.NewPath("status", "capacity", string(k)) - allErrs = append(allErrs, ValidateResourceQuantityValue(string(k), v, resPath)...) - // track any huge page size that has a positive value - if helper.IsHugePageResourceName(k) && v.Value() > int64(0) { - hugePageSizes.Insert(string(k)) - } - if len(hugePageSizes) > 1 { - allErrs = append(allErrs, field.Invalid(resPath, v, "may not have pre-allocated hugepages for multiple page sizes")) - } - } - // Validate resource quantities in allocatable. - hugePageSizes = sets.NewString() - for k, v := range node.Status.Allocatable { - resPath := field.NewPath("status", "allocatable", string(k)) - allErrs = append(allErrs, ValidateResourceQuantityValue(string(k), v, resPath)...) - // track any huge page size that has a positive value - if helper.IsHugePageResourceName(k) && v.Value() > int64(0) { - hugePageSizes.Insert(string(k)) - } - if len(hugePageSizes) > 1 { - allErrs = append(allErrs, field.Invalid(resPath, v, "may not have pre-allocated hugepages for multiple page sizes")) - } - } - return allErrs -} - -// ValidateNodeUpdate tests to make sure a node update can be applied. Modifies oldNode. -func ValidateNodeUpdate(node, oldNode *api.Node) field.ErrorList { - fldPath := field.NewPath("metadata") - allErrs := ValidateObjectMetaUpdate(&node.ObjectMeta, &oldNode.ObjectMeta, fldPath) - allErrs = append(allErrs, ValidateNodeSpecificAnnotations(node.ObjectMeta.Annotations, fldPath.Child("annotations"))...) - - // TODO: Enable the code once we have better api object.status update model. Currently, - // anyone can update node status. - // if !apiequality.Semantic.DeepEqual(node.Status, api.NodeStatus{}) { - // allErrs = append(allErrs, field.Invalid("status", node.Status, "must be empty")) - // } - - allErrs = append(allErrs, ValidateNodeResources(node)...) - - // Validate no duplicate addresses in node status. - addresses := make(map[api.NodeAddress]bool) - for i, address := range node.Status.Addresses { - if _, ok := addresses[address]; ok { - allErrs = append(allErrs, field.Duplicate(field.NewPath("status", "addresses").Index(i), address)) - } - addresses[address] = true - } - - if len(oldNode.Spec.PodCIDR) == 0 { - // Allow the controller manager to assign a CIDR to a node if it doesn't have one. - oldNode.Spec.PodCIDR = node.Spec.PodCIDR - } else { - if oldNode.Spec.PodCIDR != node.Spec.PodCIDR { - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "podCIDR"), "node updates may not change podCIDR except from \"\" to valid")) - } - } - - // Allow controller manager updating provider ID when not set - if len(oldNode.Spec.ProviderID) == 0 { - oldNode.Spec.ProviderID = node.Spec.ProviderID - } else { - if oldNode.Spec.ProviderID != node.Spec.ProviderID { - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "providerID"), "node updates may not change providerID except from \"\" to valid")) - } - } - - // TODO: move reset function to its own location - // Ignore metadata changes now that they have been tested - oldNode.ObjectMeta = node.ObjectMeta - // Allow users to update capacity - oldNode.Status.Capacity = node.Status.Capacity - // Allow users to unschedule node - oldNode.Spec.Unschedulable = node.Spec.Unschedulable - // Clear status - oldNode.Status = node.Status - - // update taints - if len(node.Spec.Taints) > 0 { - allErrs = append(allErrs, validateNodeTaints(node.Spec.Taints, fldPath.Child("taints"))...) - } - oldNode.Spec.Taints = node.Spec.Taints - - // Allow updates to Node.Spec.ConfigSource if DynamicKubeletConfig feature gate is enabled - if utilfeature.DefaultFeatureGate.Enabled(features.DynamicKubeletConfig) { - oldNode.Spec.ConfigSource = node.Spec.ConfigSource - } - - // We made allowed changes to oldNode, and now we compare oldNode to node. Any remaining differences indicate changes to protected fields. - // TODO: Add a 'real' error type for this error and provide print actual diffs. - if !apiequality.Semantic.DeepEqual(oldNode, node) { - glog.V(4).Infof("Update failed validation %#v vs %#v", oldNode, node) - allErrs = append(allErrs, field.Forbidden(field.NewPath(""), "node updates may only change labels, taints, or capacity (or configSource, if the DynamicKubeletConfig feature gate is enabled)")) - } - - return allErrs -} - -// Validate compute resource typename. -// Refer to docs/design/resources.md for more details. -func validateResourceName(value string, fldPath *field.Path) field.ErrorList { - // Opaque integer resources (OIR) deprecation began in v1.8 - // TODO: Remove warning after OIR deprecation cycle. - if helper.IsOpaqueIntResourceName(api.ResourceName(value)) { - glog.Errorf("DEPRECATION WARNING! Opaque integer resources are deprecated starting with v1.8: %s", value) - } - - allErrs := field.ErrorList{} - for _, msg := range validation.IsQualifiedName(value) { - allErrs = append(allErrs, field.Invalid(fldPath, value, msg)) - } - if len(allErrs) != 0 { - return allErrs - } - - if len(strings.Split(value, "/")) == 1 { - if !helper.IsStandardResourceName(value) { - return append(allErrs, field.Invalid(fldPath, value, "must be a standard resource type or fully qualified")) - } - } - - return allErrs -} - -// Validate container resource name -// Refer to docs/design/resources.md for more details. -func validateContainerResourceName(value string, fldPath *field.Path) field.ErrorList { - allErrs := validateResourceName(value, fldPath) - - if len(strings.Split(value, "/")) == 1 { - if !helper.IsStandardContainerResourceName(value) { - return append(allErrs, field.Invalid(fldPath, value, "must be a standard resource for containers")) - } - } - return allErrs -} - -// isLocalStorageResource checks whether the resource is local ephemeral storage -func isLocalStorageResource(name string) bool { - if name == string(api.ResourceEphemeralStorage) || name == string(api.ResourceRequestsEphemeralStorage) || - name == string(api.ResourceLimitsEphemeralStorage) { - return true - } else { - return false - } -} - -// Validate resource names that can go in a resource quota -// Refer to docs/design/resources.md for more details. -func ValidateResourceQuotaResourceName(value string, fldPath *field.Path) field.ErrorList { - allErrs := validateResourceName(value, fldPath) - if isLocalStorageResource(value) && !utilfeature.DefaultFeatureGate.Enabled(features.LocalStorageCapacityIsolation) { - return append(allErrs, field.Forbidden(fldPath, "ResourceEphemeralStorage field disabled by feature-gate for ResourceQuota")) - } - if len(strings.Split(value, "/")) == 1 { - if !helper.IsStandardQuotaResourceName(value) { - return append(allErrs, field.Invalid(fldPath, value, isInvalidQuotaResource)) - } - } - return allErrs -} - -// Validate limit range types -func validateLimitRangeTypeName(value string, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - for _, msg := range validation.IsQualifiedName(value) { - allErrs = append(allErrs, field.Invalid(fldPath, value, msg)) - } - if len(allErrs) != 0 { - return allErrs - } - - if len(strings.Split(value, "/")) == 1 { - if !helper.IsStandardLimitRangeType(value) { - return append(allErrs, field.Invalid(fldPath, value, "must be a standard limit type or fully qualified")) - } - } - - return allErrs -} - -// Validate limit range resource name -// limit types (other than Pod/Container) could contain storage not just cpu or memory -func validateLimitRangeResourceName(limitType api.LimitType, value string, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if value == string(api.ResourceEphemeralStorage) && !utilfeature.DefaultFeatureGate.Enabled(features.LocalStorageCapacityIsolation) { - return append(allErrs, field.Forbidden(fldPath, "ResourceEphemeralStorage field disabled by feature-gate for Resource LimitRange")) - } - switch limitType { - case api.LimitTypePod, api.LimitTypeContainer: - return validateContainerResourceName(value, fldPath) - default: - return validateResourceName(value, fldPath) - } -} - -// ValidateLimitRange tests if required fields in the LimitRange are set. -func ValidateLimitRange(limitRange *api.LimitRange) field.ErrorList { - allErrs := ValidateObjectMeta(&limitRange.ObjectMeta, true, ValidateLimitRangeName, field.NewPath("metadata")) - - // ensure resource names are properly qualified per docs/design/resources.md - limitTypeSet := map[api.LimitType]bool{} - fldPath := field.NewPath("spec", "limits") - for i := range limitRange.Spec.Limits { - idxPath := fldPath.Index(i) - limit := &limitRange.Spec.Limits[i] - allErrs = append(allErrs, validateLimitRangeTypeName(string(limit.Type), idxPath.Child("type"))...) - - _, found := limitTypeSet[limit.Type] - if found { - allErrs = append(allErrs, field.Duplicate(idxPath.Child("type"), limit.Type)) - } - limitTypeSet[limit.Type] = true - - keys := sets.String{} - min := map[string]resource.Quantity{} - max := map[string]resource.Quantity{} - defaults := map[string]resource.Quantity{} - defaultRequests := map[string]resource.Quantity{} - maxLimitRequestRatios := map[string]resource.Quantity{} - - for k, q := range limit.Max { - allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, string(k), idxPath.Child("max").Key(string(k)))...) - keys.Insert(string(k)) - max[string(k)] = q - } - for k, q := range limit.Min { - allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, string(k), idxPath.Child("min").Key(string(k)))...) - keys.Insert(string(k)) - min[string(k)] = q - } - - if limit.Type == api.LimitTypePod { - if len(limit.Default) > 0 { - allErrs = append(allErrs, field.Forbidden(idxPath.Child("default"), "may not be specified when `type` is 'Pod'")) - } - if len(limit.DefaultRequest) > 0 { - allErrs = append(allErrs, field.Forbidden(idxPath.Child("defaultRequest"), "may not be specified when `type` is 'Pod'")) - } - } else { - for k, q := range limit.Default { - allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, string(k), idxPath.Child("default").Key(string(k)))...) - keys.Insert(string(k)) - defaults[string(k)] = q - } - for k, q := range limit.DefaultRequest { - allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, string(k), idxPath.Child("defaultRequest").Key(string(k)))...) - keys.Insert(string(k)) - defaultRequests[string(k)] = q - } - } - - if limit.Type == api.LimitTypePersistentVolumeClaim { - _, minQuantityFound := limit.Min[api.ResourceStorage] - _, maxQuantityFound := limit.Max[api.ResourceStorage] - if !minQuantityFound && !maxQuantityFound { - allErrs = append(allErrs, field.Required(idxPath.Child("limits"), "either minimum or maximum storage value is required, but neither was provided")) - } - } - - for k, q := range limit.MaxLimitRequestRatio { - allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, string(k), idxPath.Child("maxLimitRequestRatio").Key(string(k)))...) - keys.Insert(string(k)) - maxLimitRequestRatios[string(k)] = q - } - - for k := range keys { - minQuantity, minQuantityFound := min[k] - maxQuantity, maxQuantityFound := max[k] - defaultQuantity, defaultQuantityFound := defaults[k] - defaultRequestQuantity, defaultRequestQuantityFound := defaultRequests[k] - maxRatio, maxRatioFound := maxLimitRequestRatios[k] - - if minQuantityFound && maxQuantityFound && minQuantity.Cmp(maxQuantity) > 0 { - allErrs = append(allErrs, field.Invalid(idxPath.Child("min").Key(string(k)), minQuantity, fmt.Sprintf("min value %s is greater than max value %s", minQuantity.String(), maxQuantity.String()))) - } - - if defaultRequestQuantityFound && minQuantityFound && minQuantity.Cmp(defaultRequestQuantity) > 0 { - allErrs = append(allErrs, field.Invalid(idxPath.Child("defaultRequest").Key(string(k)), defaultRequestQuantity, fmt.Sprintf("min value %s is greater than default request value %s", minQuantity.String(), defaultRequestQuantity.String()))) - } - - if defaultRequestQuantityFound && maxQuantityFound && defaultRequestQuantity.Cmp(maxQuantity) > 0 { - allErrs = append(allErrs, field.Invalid(idxPath.Child("defaultRequest").Key(string(k)), defaultRequestQuantity, fmt.Sprintf("default request value %s is greater than max value %s", defaultRequestQuantity.String(), maxQuantity.String()))) - } - - if defaultRequestQuantityFound && defaultQuantityFound && defaultRequestQuantity.Cmp(defaultQuantity) > 0 { - allErrs = append(allErrs, field.Invalid(idxPath.Child("defaultRequest").Key(string(k)), defaultRequestQuantity, fmt.Sprintf("default request value %s is greater than default limit value %s", defaultRequestQuantity.String(), defaultQuantity.String()))) - } - - if defaultQuantityFound && minQuantityFound && minQuantity.Cmp(defaultQuantity) > 0 { - allErrs = append(allErrs, field.Invalid(idxPath.Child("default").Key(string(k)), minQuantity, fmt.Sprintf("min value %s is greater than default value %s", minQuantity.String(), defaultQuantity.String()))) - } - - if defaultQuantityFound && maxQuantityFound && defaultQuantity.Cmp(maxQuantity) > 0 { - allErrs = append(allErrs, field.Invalid(idxPath.Child("default").Key(string(k)), maxQuantity, fmt.Sprintf("default value %s is greater than max value %s", defaultQuantity.String(), maxQuantity.String()))) - } - if maxRatioFound && maxRatio.Cmp(*resource.NewQuantity(1, resource.DecimalSI)) < 0 { - allErrs = append(allErrs, field.Invalid(idxPath.Child("maxLimitRequestRatio").Key(string(k)), maxRatio, fmt.Sprintf("ratio %s is less than 1", maxRatio.String()))) - } - if maxRatioFound && minQuantityFound && maxQuantityFound { - maxRatioValue := float64(maxRatio.Value()) - minQuantityValue := minQuantity.Value() - maxQuantityValue := maxQuantity.Value() - if maxRatio.Value() < resource.MaxMilliValue && minQuantityValue < resource.MaxMilliValue && maxQuantityValue < resource.MaxMilliValue { - maxRatioValue = float64(maxRatio.MilliValue()) / 1000 - minQuantityValue = minQuantity.MilliValue() - maxQuantityValue = maxQuantity.MilliValue() - } - maxRatioLimit := float64(maxQuantityValue) / float64(minQuantityValue) - if maxRatioValue > maxRatioLimit { - allErrs = append(allErrs, field.Invalid(idxPath.Child("maxLimitRequestRatio").Key(string(k)), maxRatio, fmt.Sprintf("ratio %s is greater than max/min = %f", maxRatio.String(), maxRatioLimit))) - } - } - } - } - - return allErrs -} - -// ValidateServiceAccount tests if required fields in the ServiceAccount are set. -func ValidateServiceAccount(serviceAccount *api.ServiceAccount) field.ErrorList { - allErrs := ValidateObjectMeta(&serviceAccount.ObjectMeta, true, ValidateServiceAccountName, field.NewPath("metadata")) - return allErrs -} - -// ValidateServiceAccountUpdate tests if required fields in the ServiceAccount are set. -func ValidateServiceAccountUpdate(newServiceAccount, oldServiceAccount *api.ServiceAccount) field.ErrorList { - allErrs := ValidateObjectMetaUpdate(&newServiceAccount.ObjectMeta, &oldServiceAccount.ObjectMeta, field.NewPath("metadata")) - allErrs = append(allErrs, ValidateServiceAccount(newServiceAccount)...) - return allErrs -} - -// ValidateSecret tests if required fields in the Secret are set. -func ValidateSecret(secret *api.Secret) field.ErrorList { - allErrs := ValidateObjectMeta(&secret.ObjectMeta, true, ValidateSecretName, field.NewPath("metadata")) - - dataPath := field.NewPath("data") - totalSize := 0 - for key, value := range secret.Data { - for _, msg := range validation.IsConfigMapKey(key) { - allErrs = append(allErrs, field.Invalid(dataPath.Key(key), key, msg)) - } - totalSize += len(value) - } - if totalSize > api.MaxSecretSize { - allErrs = append(allErrs, field.TooLong(dataPath, "", api.MaxSecretSize)) - } - - switch secret.Type { - case api.SecretTypeServiceAccountToken: - // Only require Annotations[kubernetes.io/service-account.name] - // Additional fields (like Annotations[kubernetes.io/service-account.uid] and Data[token]) might be contributed later by a controller loop - if value := secret.Annotations[api.ServiceAccountNameKey]; len(value) == 0 { - allErrs = append(allErrs, field.Required(field.NewPath("metadata", "annotations").Key(api.ServiceAccountNameKey), "")) - } - case api.SecretTypeOpaque, "": - // no-op - case api.SecretTypeDockercfg: - dockercfgBytes, exists := secret.Data[api.DockerConfigKey] - if !exists { - allErrs = append(allErrs, field.Required(dataPath.Key(api.DockerConfigKey), "")) - break - } - - // make sure that the content is well-formed json. - if err := json.Unmarshal(dockercfgBytes, &map[string]interface{}{}); err != nil { - allErrs = append(allErrs, field.Invalid(dataPath.Key(api.DockerConfigKey), "", err.Error())) - } - case api.SecretTypeDockerConfigJson: - dockerConfigJsonBytes, exists := secret.Data[api.DockerConfigJsonKey] - if !exists { - allErrs = append(allErrs, field.Required(dataPath.Key(api.DockerConfigJsonKey), "")) - break - } - - // make sure that the content is well-formed json. - if err := json.Unmarshal(dockerConfigJsonBytes, &map[string]interface{}{}); err != nil { - allErrs = append(allErrs, field.Invalid(dataPath.Key(api.DockerConfigJsonKey), "", err.Error())) - } - case api.SecretTypeBasicAuth: - _, usernameFieldExists := secret.Data[api.BasicAuthUsernameKey] - _, passwordFieldExists := secret.Data[api.BasicAuthPasswordKey] - - // username or password might be empty, but the field must be present - if !usernameFieldExists && !passwordFieldExists { - allErrs = append(allErrs, field.Required(field.NewPath("data[%s]").Key(api.BasicAuthUsernameKey), "")) - allErrs = append(allErrs, field.Required(field.NewPath("data[%s]").Key(api.BasicAuthPasswordKey), "")) - break - } - case api.SecretTypeSSHAuth: - if len(secret.Data[api.SSHAuthPrivateKey]) == 0 { - allErrs = append(allErrs, field.Required(field.NewPath("data[%s]").Key(api.SSHAuthPrivateKey), "")) - break - } - - case api.SecretTypeTLS: - if _, exists := secret.Data[api.TLSCertKey]; !exists { - allErrs = append(allErrs, field.Required(dataPath.Key(api.TLSCertKey), "")) - } - if _, exists := secret.Data[api.TLSPrivateKeyKey]; !exists { - allErrs = append(allErrs, field.Required(dataPath.Key(api.TLSPrivateKeyKey), "")) - } - // TODO: Verify that the key matches the cert. - default: - // no-op - } - - return allErrs -} - -// ValidateSecretUpdate tests if required fields in the Secret are set. -func ValidateSecretUpdate(newSecret, oldSecret *api.Secret) field.ErrorList { - allErrs := ValidateObjectMetaUpdate(&newSecret.ObjectMeta, &oldSecret.ObjectMeta, field.NewPath("metadata")) - - if len(newSecret.Type) == 0 { - newSecret.Type = oldSecret.Type - } - - allErrs = append(allErrs, ValidateImmutableField(newSecret.Type, oldSecret.Type, field.NewPath("type"))...) - - allErrs = append(allErrs, ValidateSecret(newSecret)...) - return allErrs -} - -// ValidateConfigMapName can be used to check whether the given ConfigMap name is valid. -// Prefix indicates this name will be used as part of generation, in which case -// trailing dashes are allowed. -var ValidateConfigMapName = NameIsDNSSubdomain - -// ValidateConfigMap tests whether required fields in the ConfigMap are set. -func ValidateConfigMap(cfg *api.ConfigMap) field.ErrorList { - allErrs := field.ErrorList{} - allErrs = append(allErrs, ValidateObjectMeta(&cfg.ObjectMeta, true, ValidateConfigMapName, field.NewPath("metadata"))...) - - totalSize := 0 - - for key, value := range cfg.Data { - for _, msg := range validation.IsConfigMapKey(key) { - allErrs = append(allErrs, field.Invalid(field.NewPath("data").Key(key), key, msg)) - } - totalSize += len(value) - } - if totalSize > api.MaxSecretSize { - allErrs = append(allErrs, field.TooLong(field.NewPath("data"), "", api.MaxSecretSize)) - } - - return allErrs -} - -// ValidateConfigMapUpdate tests if required fields in the ConfigMap are set. -func ValidateConfigMapUpdate(newCfg, oldCfg *api.ConfigMap) field.ErrorList { - allErrs := field.ErrorList{} - allErrs = append(allErrs, ValidateObjectMetaUpdate(&newCfg.ObjectMeta, &oldCfg.ObjectMeta, field.NewPath("metadata"))...) - allErrs = append(allErrs, ValidateConfigMap(newCfg)...) - - return allErrs -} - -func validateBasicResource(quantity resource.Quantity, fldPath *field.Path) field.ErrorList { - if quantity.Value() < 0 { - return field.ErrorList{field.Invalid(fldPath, quantity.Value(), "must be a valid resource quantity")} - } - return field.ErrorList{} -} - -// Validates resource requirement spec. -func ValidateResourceRequirements(requirements *api.ResourceRequirements, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - limPath := fldPath.Child("limits") - reqPath := fldPath.Child("requests") - for resourceName, quantity := range requirements.Limits { - fldPath := limPath.Key(string(resourceName)) - // Validate resource name. - allErrs = append(allErrs, validateContainerResourceName(string(resourceName), fldPath)...) - - // Validate resource quantity. - allErrs = append(allErrs, ValidateResourceQuantityValue(string(resourceName), quantity, fldPath)...) - - if resourceName == api.ResourceEphemeralStorage && !utilfeature.DefaultFeatureGate.Enabled(features.LocalStorageCapacityIsolation) { - allErrs = append(allErrs, field.Forbidden(limPath, "ResourceEphemeralStorage field disabled by feature-gate for ResourceRequirements")) - } - if helper.IsHugePageResourceName(resourceName) && !utilfeature.DefaultFeatureGate.Enabled(features.HugePages) { - allErrs = append(allErrs, field.Forbidden(limPath, fmt.Sprintf("%s field disabled by feature-gate for ResourceRequirements", resourceName))) - } - - } - for resourceName, quantity := range requirements.Requests { - fldPath := reqPath.Key(string(resourceName)) - // Validate resource name. - allErrs = append(allErrs, validateContainerResourceName(string(resourceName), fldPath)...) - // Validate resource quantity. - allErrs = append(allErrs, ValidateResourceQuantityValue(string(resourceName), quantity, fldPath)...) - - // Check that request <= limit. - limitQuantity, exists := requirements.Limits[resourceName] - if exists { - // For GPUs, not only requests can't exceed limits, they also can't be lower, i.e. must be equal. - if quantity.Cmp(limitQuantity) != 0 && !helper.IsOvercommitAllowed(resourceName) { - allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be equal to %s limit", api.ResourceNvidiaGPU))) - } else if quantity.Cmp(limitQuantity) > 0 { - allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be less than or equal to %s limit", resourceName))) - } - } else if resourceName == api.ResourceNvidiaGPU { - allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be equal to %s request", api.ResourceNvidiaGPU))) - } - } - - return allErrs -} - -// validateResourceQuotaScopes ensures that each enumerated hard resource constraint is valid for set of scopes -func validateResourceQuotaScopes(resourceQuotaSpec *api.ResourceQuotaSpec, fld *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(resourceQuotaSpec.Scopes) == 0 { - return allErrs - } - hardLimits := sets.NewString() - for k := range resourceQuotaSpec.Hard { - hardLimits.Insert(string(k)) - } - fldPath := fld.Child("scopes") - scopeSet := sets.NewString() - for _, scope := range resourceQuotaSpec.Scopes { - if !helper.IsStandardResourceQuotaScope(string(scope)) { - allErrs = append(allErrs, field.Invalid(fldPath, resourceQuotaSpec.Scopes, "unsupported scope")) - } - for _, k := range hardLimits.List() { - if helper.IsStandardQuotaResourceName(k) && !helper.IsResourceQuotaScopeValidForResource(scope, k) { - allErrs = append(allErrs, field.Invalid(fldPath, resourceQuotaSpec.Scopes, "unsupported scope applied to resource")) - } - } - scopeSet.Insert(string(scope)) - } - invalidScopePairs := []sets.String{ - sets.NewString(string(api.ResourceQuotaScopeBestEffort), string(api.ResourceQuotaScopeNotBestEffort)), - sets.NewString(string(api.ResourceQuotaScopeTerminating), string(api.ResourceQuotaScopeNotTerminating)), - } - for _, invalidScopePair := range invalidScopePairs { - if scopeSet.HasAll(invalidScopePair.List()...) { - allErrs = append(allErrs, field.Invalid(fldPath, resourceQuotaSpec.Scopes, "conflicting scopes")) - } - } - return allErrs -} - -// ValidateResourceQuota tests if required fields in the ResourceQuota are set. -func ValidateResourceQuota(resourceQuota *api.ResourceQuota) field.ErrorList { - allErrs := ValidateObjectMeta(&resourceQuota.ObjectMeta, true, ValidateResourceQuotaName, field.NewPath("metadata")) - - allErrs = append(allErrs, ValidateResourceQuotaSpec(&resourceQuota.Spec, field.NewPath("spec"))...) - allErrs = append(allErrs, ValidateResourceQuotaStatus(&resourceQuota.Status, field.NewPath("status"))...) - - return allErrs -} - -func ValidateResourceQuotaStatus(status *api.ResourceQuotaStatus, fld *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - fldPath := fld.Child("hard") - for k, v := range status.Hard { - resPath := fldPath.Key(string(k)) - allErrs = append(allErrs, ValidateResourceQuotaResourceName(string(k), resPath)...) - allErrs = append(allErrs, ValidateResourceQuantityValue(string(k), v, resPath)...) - } - fldPath = fld.Child("used") - for k, v := range status.Used { - resPath := fldPath.Key(string(k)) - allErrs = append(allErrs, ValidateResourceQuotaResourceName(string(k), resPath)...) - allErrs = append(allErrs, ValidateResourceQuantityValue(string(k), v, resPath)...) - } - - return allErrs -} - -func ValidateResourceQuotaSpec(resourceQuotaSpec *api.ResourceQuotaSpec, fld *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - fldPath := fld.Child("hard") - for k, v := range resourceQuotaSpec.Hard { - resPath := fldPath.Key(string(k)) - allErrs = append(allErrs, ValidateResourceQuotaResourceName(string(k), resPath)...) - allErrs = append(allErrs, ValidateResourceQuantityValue(string(k), v, resPath)...) - } - allErrs = append(allErrs, validateResourceQuotaScopes(resourceQuotaSpec, fld)...) - - return allErrs -} - -// ValidateResourceQuantityValue enforces that specified quantity is valid for specified resource -func ValidateResourceQuantityValue(resource string, value resource.Quantity, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - allErrs = append(allErrs, ValidateNonnegativeQuantity(value, fldPath)...) - if helper.IsIntegerResourceName(resource) { - if value.MilliValue()%int64(1000) != int64(0) { - allErrs = append(allErrs, field.Invalid(fldPath, value, isNotIntegerErrorMsg)) - } - } - return allErrs -} - -// ValidateResourceQuotaUpdate tests to see if the update is legal for an end user to make. -// newResourceQuota is updated with fields that cannot be changed. -func ValidateResourceQuotaUpdate(newResourceQuota, oldResourceQuota *api.ResourceQuota) field.ErrorList { - allErrs := ValidateObjectMetaUpdate(&newResourceQuota.ObjectMeta, &oldResourceQuota.ObjectMeta, field.NewPath("metadata")) - allErrs = append(allErrs, ValidateResourceQuotaSpec(&newResourceQuota.Spec, field.NewPath("spec"))...) - - // ensure scopes cannot change, and that resources are still valid for scope - fldPath := field.NewPath("spec", "scopes") - oldScopes := sets.NewString() - newScopes := sets.NewString() - for _, scope := range newResourceQuota.Spec.Scopes { - newScopes.Insert(string(scope)) - } - for _, scope := range oldResourceQuota.Spec.Scopes { - oldScopes.Insert(string(scope)) - } - if !oldScopes.Equal(newScopes) { - allErrs = append(allErrs, field.Invalid(fldPath, newResourceQuota.Spec.Scopes, fieldImmutableErrorMsg)) - } - - newResourceQuota.Status = oldResourceQuota.Status - return allErrs -} - -// ValidateResourceQuotaStatusUpdate tests to see if the status update is legal for an end user to make. -// newResourceQuota is updated with fields that cannot be changed. -func ValidateResourceQuotaStatusUpdate(newResourceQuota, oldResourceQuota *api.ResourceQuota) field.ErrorList { - allErrs := ValidateObjectMetaUpdate(&newResourceQuota.ObjectMeta, &oldResourceQuota.ObjectMeta, field.NewPath("metadata")) - if len(newResourceQuota.ResourceVersion) == 0 { - allErrs = append(allErrs, field.Required(field.NewPath("resourceVersion"), "")) - } - fldPath := field.NewPath("status", "hard") - for k, v := range newResourceQuota.Status.Hard { - resPath := fldPath.Key(string(k)) - allErrs = append(allErrs, ValidateResourceQuotaResourceName(string(k), resPath)...) - allErrs = append(allErrs, ValidateResourceQuantityValue(string(k), v, resPath)...) - } - fldPath = field.NewPath("status", "used") - for k, v := range newResourceQuota.Status.Used { - resPath := fldPath.Key(string(k)) - allErrs = append(allErrs, ValidateResourceQuotaResourceName(string(k), resPath)...) - allErrs = append(allErrs, ValidateResourceQuantityValue(string(k), v, resPath)...) - } - newResourceQuota.Spec = oldResourceQuota.Spec - return allErrs -} - -// ValidateNamespace tests if required fields are set. -func ValidateNamespace(namespace *api.Namespace) field.ErrorList { - allErrs := ValidateObjectMeta(&namespace.ObjectMeta, false, ValidateNamespaceName, field.NewPath("metadata")) - for i := range namespace.Spec.Finalizers { - allErrs = append(allErrs, validateFinalizerName(string(namespace.Spec.Finalizers[i]), field.NewPath("spec", "finalizers"))...) - } - return allErrs -} - -// Validate finalizer names -func validateFinalizerName(stringValue string, fldPath *field.Path) field.ErrorList { - allErrs := genericvalidation.ValidateFinalizerName(stringValue, fldPath) - for _, err := range validateKubeFinalizerName(stringValue, fldPath) { - allErrs = append(allErrs, err) - } - - return allErrs -} - -// validateKubeFinalizerName checks for "standard" names of legacy finalizer -func validateKubeFinalizerName(stringValue string, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(strings.Split(stringValue, "/")) == 1 { - if !helper.IsStandardFinalizerName(stringValue) { - return append(allErrs, field.Invalid(fldPath, stringValue, "name is neither a standard finalizer name nor is it fully qualified")) - } - } - - return allErrs -} - -// ValidateNamespaceUpdate tests to make sure a namespace update can be applied. -// newNamespace is updated with fields that cannot be changed -func ValidateNamespaceUpdate(newNamespace *api.Namespace, oldNamespace *api.Namespace) field.ErrorList { - allErrs := ValidateObjectMetaUpdate(&newNamespace.ObjectMeta, &oldNamespace.ObjectMeta, field.NewPath("metadata")) - newNamespace.Spec.Finalizers = oldNamespace.Spec.Finalizers - newNamespace.Status = oldNamespace.Status - return allErrs -} - -// ValidateNamespaceStatusUpdate tests to see if the update is legal for an end user to make. newNamespace is updated with fields -// that cannot be changed. -func ValidateNamespaceStatusUpdate(newNamespace, oldNamespace *api.Namespace) field.ErrorList { - allErrs := ValidateObjectMetaUpdate(&newNamespace.ObjectMeta, &oldNamespace.ObjectMeta, field.NewPath("metadata")) - newNamespace.Spec = oldNamespace.Spec - if newNamespace.DeletionTimestamp.IsZero() { - if newNamespace.Status.Phase != api.NamespaceActive { - allErrs = append(allErrs, field.Invalid(field.NewPath("status", "Phase"), newNamespace.Status.Phase, "may only be 'Active' if `deletionTimestamp` is empty")) - } - } else { - if newNamespace.Status.Phase != api.NamespaceTerminating { - allErrs = append(allErrs, field.Invalid(field.NewPath("status", "Phase"), newNamespace.Status.Phase, "may only be 'Terminating' if `deletionTimestamp` is not empty")) - } - } - return allErrs -} - -// ValidateNamespaceFinalizeUpdate tests to see if the update is legal for an end user to make. -// newNamespace is updated with fields that cannot be changed. -func ValidateNamespaceFinalizeUpdate(newNamespace, oldNamespace *api.Namespace) field.ErrorList { - allErrs := ValidateObjectMetaUpdate(&newNamespace.ObjectMeta, &oldNamespace.ObjectMeta, field.NewPath("metadata")) - - fldPath := field.NewPath("spec", "finalizers") - for i := range newNamespace.Spec.Finalizers { - idxPath := fldPath.Index(i) - allErrs = append(allErrs, validateFinalizerName(string(newNamespace.Spec.Finalizers[i]), idxPath)...) - } - newNamespace.Status = oldNamespace.Status - return allErrs -} - -// Construct lookup map of old subset IPs to NodeNames. -func updateEpAddrToNodeNameMap(ipToNodeName map[string]string, addresses []api.EndpointAddress) { - for n := range addresses { - if addresses[n].NodeName == nil { - continue - } - ipToNodeName[addresses[n].IP] = *addresses[n].NodeName - } -} - -// Build a map across all subsets of IP -> NodeName -func buildEndpointAddressNodeNameMap(subsets []api.EndpointSubset) map[string]string { - ipToNodeName := make(map[string]string) - for i := range subsets { - updateEpAddrToNodeNameMap(ipToNodeName, subsets[i].Addresses) - updateEpAddrToNodeNameMap(ipToNodeName, subsets[i].NotReadyAddresses) - } - return ipToNodeName -} - -func validateEpAddrNodeNameTransition(addr *api.EndpointAddress, ipToNodeName map[string]string, fldPath *field.Path) field.ErrorList { - errList := field.ErrorList{} - existingNodeName, found := ipToNodeName[addr.IP] - if !found { - return errList - } - if addr.NodeName == nil || *addr.NodeName == existingNodeName { - return errList - } - // NodeName entry found for this endpoint IP, but user is attempting to change NodeName - return append(errList, field.Forbidden(fldPath, fmt.Sprintf("Cannot change NodeName for %s to %s", addr.IP, *addr.NodeName))) -} - -// ValidateEndpoints tests if required fields are set. -func ValidateEndpoints(endpoints *api.Endpoints) field.ErrorList { - allErrs := ValidateObjectMeta(&endpoints.ObjectMeta, true, ValidateEndpointsName, field.NewPath("metadata")) - allErrs = append(allErrs, ValidateEndpointsSpecificAnnotations(endpoints.Annotations, field.NewPath("annotations"))...) - allErrs = append(allErrs, validateEndpointSubsets(endpoints.Subsets, []api.EndpointSubset{}, field.NewPath("subsets"))...) - return allErrs -} - -func validateEndpointSubsets(subsets []api.EndpointSubset, oldSubsets []api.EndpointSubset, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - ipToNodeName := buildEndpointAddressNodeNameMap(oldSubsets) - for i := range subsets { - ss := &subsets[i] - idxPath := fldPath.Index(i) - - // EndpointSubsets must include endpoint address. For headless service, we allow its endpoints not to have ports. - if len(ss.Addresses) == 0 && len(ss.NotReadyAddresses) == 0 { - //TODO: consider adding a RequiredOneOf() error for this and similar cases - allErrs = append(allErrs, field.Required(idxPath, "must specify `addresses` or `notReadyAddresses`")) - } - for addr := range ss.Addresses { - allErrs = append(allErrs, validateEndpointAddress(&ss.Addresses[addr], idxPath.Child("addresses").Index(addr), ipToNodeName)...) - } - for addr := range ss.NotReadyAddresses { - allErrs = append(allErrs, validateEndpointAddress(&ss.NotReadyAddresses[addr], idxPath.Child("notReadyAddresses").Index(addr), ipToNodeName)...) - } - for port := range ss.Ports { - allErrs = append(allErrs, validateEndpointPort(&ss.Ports[port], len(ss.Ports) > 1, idxPath.Child("ports").Index(port))...) - } - } - - return allErrs -} - -func validateEndpointAddress(address *api.EndpointAddress, fldPath *field.Path, ipToNodeName map[string]string) field.ErrorList { - allErrs := field.ErrorList{} - for _, msg := range validation.IsValidIP(address.IP) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("ip"), address.IP, msg)) - } - if len(address.Hostname) > 0 { - allErrs = append(allErrs, ValidateDNS1123Label(address.Hostname, fldPath.Child("hostname"))...) - } - // During endpoint update, verify that NodeName is a DNS subdomain and transition rules allow the update - if address.NodeName != nil { - for _, msg := range ValidateNodeName(*address.NodeName, false) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("nodeName"), *address.NodeName, msg)) - } - } - allErrs = append(allErrs, validateEpAddrNodeNameTransition(address, ipToNodeName, fldPath.Child("nodeName"))...) - if len(allErrs) > 0 { - return allErrs - } - allErrs = append(allErrs, validateNonSpecialIP(address.IP, fldPath.Child("ip"))...) - return allErrs -} - -func validateNonSpecialIP(ipAddress string, fldPath *field.Path) field.ErrorList { - // We disallow some IPs as endpoints or external-ips. Specifically, - // unspecified and loopback addresses are nonsensical and link-local - // addresses tend to be used for node-centric purposes (e.g. metadata - // service). - allErrs := field.ErrorList{} - ip := net.ParseIP(ipAddress) - if ip == nil { - allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, "must be a valid IP address")) - return allErrs - } - if ip.IsUnspecified() { - allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, "may not be unspecified (0.0.0.0)")) - } - if ip.IsLoopback() { - allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, "may not be in the loopback range (127.0.0.0/8)")) - } - if ip.IsLinkLocalUnicast() { - allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, "may not be in the link-local range (169.254.0.0/16)")) - } - if ip.IsLinkLocalMulticast() { - allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, "may not be in the link-local multicast range (224.0.0.0/24)")) - } - return allErrs -} - -func validateEndpointPort(port *api.EndpointPort, requireName bool, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if requireName && len(port.Name) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) - } else if len(port.Name) != 0 { - allErrs = append(allErrs, ValidateDNS1123Label(port.Name, fldPath.Child("name"))...) - } - for _, msg := range validation.IsValidPortNum(int(port.Port)) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("port"), port.Port, msg)) - } - if len(port.Protocol) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("protocol"), "")) - } else if !supportedPortProtocols.Has(string(port.Protocol)) { - allErrs = append(allErrs, field.NotSupported(fldPath.Child("protocol"), port.Protocol, supportedPortProtocols.List())) - } - return allErrs -} - -// ValidateEndpointsUpdate tests to make sure an endpoints update can be applied. -func ValidateEndpointsUpdate(newEndpoints, oldEndpoints *api.Endpoints) field.ErrorList { - allErrs := ValidateObjectMetaUpdate(&newEndpoints.ObjectMeta, &oldEndpoints.ObjectMeta, field.NewPath("metadata")) - allErrs = append(allErrs, validateEndpointSubsets(newEndpoints.Subsets, oldEndpoints.Subsets, field.NewPath("subsets"))...) - allErrs = append(allErrs, ValidateEndpointsSpecificAnnotations(newEndpoints.Annotations, field.NewPath("annotations"))...) - return allErrs -} - -// ValidateSecurityContext ensure the security context contains valid settings -func ValidateSecurityContext(sc *api.SecurityContext, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - //this should only be true for testing since SecurityContext is defaulted by the api - if sc == nil { - return allErrs - } - - if sc.Privileged != nil { - if *sc.Privileged && !capabilities.Get().AllowPrivileged { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("privileged"), "disallowed by cluster policy")) - } - } - - if sc.RunAsUser != nil { - if *sc.RunAsUser < 0 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsUser"), *sc.RunAsUser, isNegativeErrorMsg)) - } - } - - if sc.AllowPrivilegeEscalation != nil && !*sc.AllowPrivilegeEscalation { - if sc.Privileged != nil && *sc.Privileged { - allErrs = append(allErrs, field.Invalid(fldPath, sc, "cannot set `allowPrivilegeEscalation` to false and `privileged` to true")) - } - - if sc.Capabilities != nil { - for _, cap := range sc.Capabilities.Add { - if string(cap) == "CAP_SYS_ADMIN" { - allErrs = append(allErrs, field.Invalid(fldPath, sc, "cannot set `allowPrivilegeEscalation` to false and `capabilities.Add` CAP_SYS_ADMIN")) - } - } - } - } - - return allErrs -} - -func ValidatePodLogOptions(opts *api.PodLogOptions) field.ErrorList { - allErrs := field.ErrorList{} - if opts.TailLines != nil && *opts.TailLines < 0 { - allErrs = append(allErrs, field.Invalid(field.NewPath("tailLines"), *opts.TailLines, isNegativeErrorMsg)) - } - if opts.LimitBytes != nil && *opts.LimitBytes < 1 { - allErrs = append(allErrs, field.Invalid(field.NewPath("limitBytes"), *opts.LimitBytes, "must be greater than 0")) - } - switch { - case opts.SinceSeconds != nil && opts.SinceTime != nil: - allErrs = append(allErrs, field.Forbidden(field.NewPath(""), "at most one of `sinceTime` or `sinceSeconds` may be specified")) - case opts.SinceSeconds != nil: - if *opts.SinceSeconds < 1 { - allErrs = append(allErrs, field.Invalid(field.NewPath("sinceSeconds"), *opts.SinceSeconds, "must be greater than 0")) - } - } - return allErrs -} - -// ValidateLoadBalancerStatus validates required fields on a LoadBalancerStatus -func ValidateLoadBalancerStatus(status *api.LoadBalancerStatus, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - for i, ingress := range status.Ingress { - idxPath := fldPath.Child("ingress").Index(i) - if len(ingress.IP) > 0 { - if isIP := (net.ParseIP(ingress.IP) != nil); !isIP { - allErrs = append(allErrs, field.Invalid(idxPath.Child("ip"), ingress.IP, "must be a valid IP address")) - } - } - if len(ingress.Hostname) > 0 { - for _, msg := range validation.IsDNS1123Subdomain(ingress.Hostname) { - allErrs = append(allErrs, field.Invalid(idxPath.Child("hostname"), ingress.Hostname, msg)) - } - if isIP := (net.ParseIP(ingress.Hostname) != nil); isIP { - allErrs = append(allErrs, field.Invalid(idxPath.Child("hostname"), ingress.Hostname, "must be a DNS name, not an IP address")) - } - } - } - return allErrs -} - -func sysctlIntersection(a []api.Sysctl, b []api.Sysctl) []string { - lookup := make(map[string]struct{}, len(a)) - result := []string{} - for i := range a { - lookup[a[i].Name] = struct{}{} - } - for i := range b { - if _, found := lookup[b[i].Name]; found { - result = append(result, b[i].Name) - } - } - return result -} - -// validateStorageNodeAffinityAnnotation tests that the serialized TopologyConstraints in PersistentVolume.Annotations has valid data -func validateStorageNodeAffinityAnnotation(annotations map[string]string, fldPath *field.Path) (bool, field.ErrorList) { - allErrs := field.ErrorList{} - - na, err := helper.GetStorageNodeAffinityFromAnnotation(annotations) - if err != nil { - allErrs = append(allErrs, field.Invalid(fldPath, api.AlphaStorageNodeAffinityAnnotation, err.Error())) - return false, allErrs - } - if na == nil { - return false, allErrs - } - - if !utilfeature.DefaultFeatureGate.Enabled(features.PersistentLocalVolumes) { - allErrs = append(allErrs, field.Forbidden(fldPath, "Storage node affinity is disabled by feature-gate")) - } - - policySpecified := false - if na.RequiredDuringSchedulingIgnoredDuringExecution != nil { - allErrs = append(allErrs, ValidateNodeSelector(na.RequiredDuringSchedulingIgnoredDuringExecution, fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...) - policySpecified = true - } - - if len(na.PreferredDuringSchedulingIgnoredDuringExecution) > 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("preferredDuringSchedulingIgnoredDuringExection"), "Storage node affinity does not support preferredDuringSchedulingIgnoredDuringExecution")) - } - return policySpecified, allErrs -} diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go deleted file mode 100644 index bb34926bb3c..00000000000 --- a/pkg/api/validation/validation_test.go +++ /dev/null @@ -1,11120 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES 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 ( - "math" - "reflect" - "strings" - "testing" - - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/validation" - "k8s.io/apimachinery/pkg/util/validation/field" - utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper" - _ "k8s.io/kubernetes/pkg/api/testapi" - "k8s.io/kubernetes/pkg/capabilities" - "k8s.io/kubernetes/pkg/security/apparmor" -) - -const ( - dnsLabelErrMsg = "a DNS-1123 label must consist of" - dnsSubdomainLabelErrMsg = "a DNS-1123 subdomain" - envVarNameErrMsg = "a valid environment variable name must consist of" -) - -func newHostPathType(pathType string) *api.HostPathType { - hostPathType := new(api.HostPathType) - *hostPathType = api.HostPathType(pathType) - return hostPathType -} - -func testVolume(name string, namespace string, spec api.PersistentVolumeSpec) *api.PersistentVolume { - objMeta := metav1.ObjectMeta{Name: name} - if namespace != "" { - objMeta.Namespace = namespace - } - - return &api.PersistentVolume{ - ObjectMeta: objMeta, - Spec: spec, - } -} - -func testVolumeWithNodeAffinity(t *testing.T, name string, namespace string, affinity *api.NodeAffinity, spec api.PersistentVolumeSpec) *api.PersistentVolume { - objMeta := metav1.ObjectMeta{Name: name} - if namespace != "" { - objMeta.Namespace = namespace - } - - objMeta.Annotations = map[string]string{} - err := helper.StorageNodeAffinityToAlphaAnnotation(objMeta.Annotations, affinity) - if err != nil { - t.Fatalf("Failed to get node affinity annotation: %v", err) - } - - return &api.PersistentVolume{ - ObjectMeta: objMeta, - Spec: spec, - } -} - -func TestValidatePersistentVolumes(t *testing.T) { - scenarios := map[string]struct { - isExpectedFailure bool - volume *api.PersistentVolume - }{ - "good-volume": { - isExpectedFailure: false, - volume: testVolume("foo", "", api.PersistentVolumeSpec{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, - PersistentVolumeSource: api.PersistentVolumeSource{ - HostPath: &api.HostPathVolumeSource{ - Path: "/foo", - Type: newHostPathType(string(api.HostPathDirectory)), - }, - }, - StorageClassName: "valid", - }), - }, - "good-volume-with-retain-policy": { - isExpectedFailure: false, - volume: testVolume("foo", "", api.PersistentVolumeSpec{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, - PersistentVolumeSource: api.PersistentVolumeSource{ - HostPath: &api.HostPathVolumeSource{ - Path: "/foo", - Type: newHostPathType(string(api.HostPathDirectory)), - }, - }, - PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimRetain, - }), - }, - "invalid-accessmode": { - isExpectedFailure: true, - volume: testVolume("foo", "", api.PersistentVolumeSpec{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - AccessModes: []api.PersistentVolumeAccessMode{"fakemode"}, - PersistentVolumeSource: api.PersistentVolumeSource{ - HostPath: &api.HostPathVolumeSource{ - Path: "/foo", - Type: newHostPathType(string(api.HostPathDirectory)), - }, - }, - }), - }, - "invalid-reclaimpolicy": { - isExpectedFailure: true, - volume: testVolume("foo", "", api.PersistentVolumeSpec{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, - PersistentVolumeSource: api.PersistentVolumeSource{ - HostPath: &api.HostPathVolumeSource{ - Path: "/foo", - Type: newHostPathType(string(api.HostPathDirectory)), - }, - }, - PersistentVolumeReclaimPolicy: "fakeReclaimPolicy", - }), - }, - "unexpected-namespace": { - isExpectedFailure: true, - volume: testVolume("foo", "unexpected-namespace", api.PersistentVolumeSpec{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, - PersistentVolumeSource: api.PersistentVolumeSource{ - HostPath: &api.HostPathVolumeSource{ - Path: "/foo", - Type: newHostPathType(string(api.HostPathDirectory)), - }, - }, - }), - }, - "bad-name": { - isExpectedFailure: true, - volume: testVolume("123*Bad(Name", "unexpected-namespace", api.PersistentVolumeSpec{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, - PersistentVolumeSource: api.PersistentVolumeSource{ - HostPath: &api.HostPathVolumeSource{ - Path: "/foo", - Type: newHostPathType(string(api.HostPathDirectory)), - }, - }, - }), - }, - "missing-name": { - isExpectedFailure: true, - volume: testVolume("", "", api.PersistentVolumeSpec{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, - }), - }, - "missing-capacity": { - isExpectedFailure: true, - volume: testVolume("foo", "", api.PersistentVolumeSpec{}), - }, - "missing-accessmodes": { - isExpectedFailure: true, - volume: testVolume("goodname", "missing-accessmodes", api.PersistentVolumeSpec{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - PersistentVolumeSource: api.PersistentVolumeSource{ - HostPath: &api.HostPathVolumeSource{ - Path: "/foo", - Type: newHostPathType(string(api.HostPathDirectory)), - }, - }, - }), - }, - "too-many-sources": { - isExpectedFailure: true, - volume: testVolume("", "", api.PersistentVolumeSpec{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("5G"), - }, - PersistentVolumeSource: api.PersistentVolumeSource{ - HostPath: &api.HostPathVolumeSource{ - Path: "/foo", - Type: newHostPathType(string(api.HostPathDirectory)), - }, - GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "foo", FSType: "ext4"}, - }, - }), - }, - "host mount of / with recycle reclaim policy": { - isExpectedFailure: true, - volume: testVolume("bad-recycle-do-not-want", "", api.PersistentVolumeSpec{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, - PersistentVolumeSource: api.PersistentVolumeSource{ - HostPath: &api.HostPathVolumeSource{ - Path: "/", - Type: newHostPathType(string(api.HostPathDirectory)), - }, - }, - PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimRecycle, - }), - }, - "host mount of / with recycle reclaim policy 2": { - isExpectedFailure: true, - volume: testVolume("bad-recycle-do-not-want", "", api.PersistentVolumeSpec{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, - PersistentVolumeSource: api.PersistentVolumeSource{ - HostPath: &api.HostPathVolumeSource{ - Path: "/a/..", - Type: newHostPathType(string(api.HostPathDirectory)), - }, - }, - PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimRecycle, - }), - }, - "invalid-storage-class-name": { - isExpectedFailure: true, - volume: testVolume("invalid-storage-class-name", "", api.PersistentVolumeSpec{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, - PersistentVolumeSource: api.PersistentVolumeSource{ - HostPath: &api.HostPathVolumeSource{ - Path: "/foo", - Type: newHostPathType(string(api.HostPathDirectory)), - }, - }, - StorageClassName: "-invalid-", - }), - }, - // LocalVolume alpha feature disabled - // TODO: remove when no longer alpha - "alpha disabled valid local volume": { - isExpectedFailure: true, - volume: testVolumeWithNodeAffinity( - t, - "valid-local-volume", - "", - &api.NodeAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{ - NodeSelectorTerms: []api.NodeSelectorTerm{ - { - MatchExpressions: []api.NodeSelectorRequirement{ - { - Key: "test-label-key", - Operator: api.NodeSelectorOpIn, - Values: []string{"test-label-value"}, - }, - }, - }, - }, - }, - }, - api.PersistentVolumeSpec{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, - PersistentVolumeSource: api.PersistentVolumeSource{ - Local: &api.LocalVolumeSource{ - Path: "/foo", - }, - }, - StorageClassName: "test-storage-class", - }), - }, - "bad-hostpath-volume-backsteps": { - isExpectedFailure: true, - volume: testVolume("foo", "", api.PersistentVolumeSpec{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, - PersistentVolumeSource: api.PersistentVolumeSource{ - HostPath: &api.HostPathVolumeSource{ - Path: "/foo/..", - Type: newHostPathType(string(api.HostPathDirectory)), - }, - }, - StorageClassName: "backstep-hostpath", - }), - }, - "bad-local-volume-backsteps": { - isExpectedFailure: true, - volume: testVolume("foo", "", api.PersistentVolumeSpec{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, - PersistentVolumeSource: api.PersistentVolumeSource{ - Local: &api.LocalVolumeSource{ - Path: "/foo/..", - }, - }, - StorageClassName: "backstep-local", - }), - }, - } - - for name, scenario := range scenarios { - errs := ValidatePersistentVolume(scenario.volume) - if len(errs) == 0 && scenario.isExpectedFailure { - t.Errorf("Unexpected success for scenario: %s", name) - } - if len(errs) > 0 && !scenario.isExpectedFailure { - t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) - } - } - -} - -func TestValidateLocalVolumes(t *testing.T) { - scenarios := map[string]struct { - isExpectedFailure bool - volume *api.PersistentVolume - }{ - "valid local volume": { - isExpectedFailure: false, - volume: testVolumeWithNodeAffinity( - t, - "valid-local-volume", - "", - &api.NodeAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{ - NodeSelectorTerms: []api.NodeSelectorTerm{ - { - MatchExpressions: []api.NodeSelectorRequirement{ - { - Key: "test-label-key", - Operator: api.NodeSelectorOpIn, - Values: []string{"test-label-value"}, - }, - }, - }, - }, - }, - }, - api.PersistentVolumeSpec{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, - PersistentVolumeSource: api.PersistentVolumeSource{ - Local: &api.LocalVolumeSource{ - Path: "/foo", - }, - }, - StorageClassName: "test-storage-class", - }), - }, - "invalid local volume nil annotations": { - isExpectedFailure: true, - volume: testVolume( - "invalid-local-volume-nil-annotations", - "", - api.PersistentVolumeSpec{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, - PersistentVolumeSource: api.PersistentVolumeSource{ - Local: &api.LocalVolumeSource{ - Path: "/foo", - }, - }, - StorageClassName: "test-storage-class", - }), - }, - "invalid local volume empty affinity": { - isExpectedFailure: true, - volume: testVolumeWithNodeAffinity( - t, - "invalid-local-volume-empty-affinity", - "", - &api.NodeAffinity{}, - api.PersistentVolumeSpec{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, - PersistentVolumeSource: api.PersistentVolumeSource{ - Local: &api.LocalVolumeSource{ - Path: "/foo", - }, - }, - StorageClassName: "test-storage-class", - }), - }, - "invalid local volume preferred affinity": { - isExpectedFailure: true, - volume: testVolumeWithNodeAffinity( - t, - "invalid-local-volume-preferred-affinity", - "", - &api.NodeAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{ - NodeSelectorTerms: []api.NodeSelectorTerm{ - { - MatchExpressions: []api.NodeSelectorRequirement{ - { - Key: "test-label-key", - Operator: api.NodeSelectorOpIn, - Values: []string{"test-label-value"}, - }, - }, - }, - }, - }, - PreferredDuringSchedulingIgnoredDuringExecution: []api.PreferredSchedulingTerm{ - { - Weight: 10, - Preference: api.NodeSelectorTerm{ - MatchExpressions: []api.NodeSelectorRequirement{ - { - Key: "test-label-key", - Operator: api.NodeSelectorOpIn, - Values: []string{"test-label-value"}, - }, - }, - }, - }, - }, - }, - api.PersistentVolumeSpec{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, - PersistentVolumeSource: api.PersistentVolumeSource{ - Local: &api.LocalVolumeSource{ - Path: "/foo", - }, - }, - StorageClassName: "test-storage-class", - }), - }, - "invalid local volume empty path": { - isExpectedFailure: true, - volume: testVolumeWithNodeAffinity( - t, - "invalid-local-volume-empty-path", - "", - &api.NodeAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{ - NodeSelectorTerms: []api.NodeSelectorTerm{ - { - MatchExpressions: []api.NodeSelectorRequirement{ - { - Key: "test-label-key", - Operator: api.NodeSelectorOpIn, - Values: []string{"test-label-value"}, - }, - }, - }, - }, - }, - }, - api.PersistentVolumeSpec{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, - PersistentVolumeSource: api.PersistentVolumeSource{ - Local: &api.LocalVolumeSource{}, - }, - StorageClassName: "test-storage-class", - }), - }, - } - - err := utilfeature.DefaultFeatureGate.Set("PersistentLocalVolumes=true") - if err != nil { - t.Errorf("Failed to enable feature gate for LocalPersistentVolumes: %v", err) - return - } - for name, scenario := range scenarios { - errs := ValidatePersistentVolume(scenario.volume) - if len(errs) == 0 && scenario.isExpectedFailure { - t.Errorf("Unexpected success for scenario: %s", name) - } - if len(errs) > 0 && !scenario.isExpectedFailure { - t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) - } - } -} - -func testVolumeClaim(name string, namespace string, spec api.PersistentVolumeClaimSpec) *api.PersistentVolumeClaim { - return &api.PersistentVolumeClaim{ - ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, - Spec: spec, - } -} - -func testVolumeClaimWithStatus( - name, namespace string, - spec api.PersistentVolumeClaimSpec, - status api.PersistentVolumeClaimStatus) *api.PersistentVolumeClaim { - return &api.PersistentVolumeClaim{ - ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, - Spec: spec, - Status: status, - } -} - -func testVolumeClaimStorageClass(name string, namespace string, annval string, spec api.PersistentVolumeClaimSpec) *api.PersistentVolumeClaim { - annotations := map[string]string{ - v1.BetaStorageClassAnnotation: annval, - } - - return &api.PersistentVolumeClaim{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Annotations: annotations, - }, - Spec: spec, - } -} - -func testVolumeClaimAnnotation(name string, namespace string, ann string, annval string, spec api.PersistentVolumeClaimSpec) *api.PersistentVolumeClaim { - annotations := map[string]string{ - ann: annval, - } - - return &api.PersistentVolumeClaim{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Annotations: annotations, - }, - Spec: spec, - } -} - -func TestValidatePersistentVolumeClaim(t *testing.T) { - invalidClassName := "-invalid-" - validClassName := "valid" - scenarios := map[string]struct { - isExpectedFailure bool - claim *api.PersistentVolumeClaim - }{ - "good-claim": { - isExpectedFailure: false, - claim: testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{ - Selector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: "Exists", - }, - }, - }, - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadWriteOnce, - api.ReadOnlyMany, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - }, - StorageClassName: &validClassName, - }), - }, - "invalid-label-selector": { - isExpectedFailure: true, - claim: testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{ - Selector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: "InvalidOp", - Values: []string{"value1", "value2"}, - }, - }, - }, - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadWriteOnce, - api.ReadOnlyMany, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - }, - }), - }, - "invalid-accessmode": { - isExpectedFailure: true, - claim: testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{ - AccessModes: []api.PersistentVolumeAccessMode{"fakemode"}, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - }, - }), - }, - "missing-namespace": { - isExpectedFailure: true, - claim: testVolumeClaim("foo", "", api.PersistentVolumeClaimSpec{ - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadWriteOnce, - api.ReadOnlyMany, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - }, - }), - }, - "no-access-modes": { - isExpectedFailure: true, - claim: testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{ - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - }, - }), - }, - "no-resource-requests": { - isExpectedFailure: true, - claim: testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{ - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadWriteOnce, - }, - }), - }, - "invalid-resource-requests": { - isExpectedFailure: true, - claim: testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{ - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadWriteOnce, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), - }, - }, - }), - }, - "negative-storage-request": { - isExpectedFailure: true, - claim: testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{ - Selector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: "Exists", - }, - }, - }, - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadWriteOnce, - api.ReadOnlyMany, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("-10G"), - }, - }, - }), - }, - "invalid-storage-class-name": { - isExpectedFailure: true, - claim: testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{ - Selector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: "Exists", - }, - }, - }, - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadWriteOnce, - api.ReadOnlyMany, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - }, - StorageClassName: &invalidClassName, - }), - }, - } - - for name, scenario := range scenarios { - errs := ValidatePersistentVolumeClaim(scenario.claim) - if len(errs) == 0 && scenario.isExpectedFailure { - t.Errorf("Unexpected success for scenario: %s", name) - } - if len(errs) > 0 && !scenario.isExpectedFailure { - t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) - } - } -} - -func TestValidatePersistentVolumeClaimUpdate(t *testing.T) { - validClaim := testVolumeClaimWithStatus("foo", "ns", api.PersistentVolumeClaimSpec{ - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadWriteOnce, - api.ReadOnlyMany, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - }, - }, api.PersistentVolumeClaimStatus{ - Phase: api.ClaimBound, - }) - - validClaimStorageClass := testVolumeClaimStorageClass("foo", "ns", "fast", api.PersistentVolumeClaimSpec{ - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadOnlyMany, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - }, - }) - validClaimAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "foo-description", api.PersistentVolumeClaimSpec{ - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadOnlyMany, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - }, - }) - validUpdateClaim := testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{ - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadWriteOnce, - api.ReadOnlyMany, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - }, - VolumeName: "volume", - }) - invalidUpdateClaimResources := testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{ - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadWriteOnce, - api.ReadOnlyMany, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("20G"), - }, - }, - VolumeName: "volume", - }) - invalidUpdateClaimAccessModes := testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{ - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadWriteOnce, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - }, - VolumeName: "volume", - }) - invalidUpdateClaimStorageClass := testVolumeClaimStorageClass("foo", "ns", "fast2", api.PersistentVolumeClaimSpec{ - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadOnlyMany, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - }, - VolumeName: "volume", - }) - validUpdateClaimMutableAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "updated-or-added-foo-description", api.PersistentVolumeClaimSpec{ - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadOnlyMany, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - }, - VolumeName: "volume", - }) - validAddClaimAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "updated-or-added-foo-description", api.PersistentVolumeClaimSpec{ - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadWriteOnce, - api.ReadOnlyMany, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - }, - VolumeName: "volume", - }) - validSizeUpdate := testVolumeClaimWithStatus("foo", "ns", api.PersistentVolumeClaimSpec{ - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadWriteOnce, - api.ReadOnlyMany, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("15G"), - }, - }, - }, api.PersistentVolumeClaimStatus{ - Phase: api.ClaimBound, - }) - - invalidSizeUpdate := testVolumeClaimWithStatus("foo", "ns", api.PersistentVolumeClaimSpec{ - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadWriteOnce, - api.ReadOnlyMany, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("5G"), - }, - }, - }, api.PersistentVolumeClaimStatus{ - Phase: api.ClaimBound, - }) - - unboundSizeUpdate := testVolumeClaimWithStatus("foo", "ns", api.PersistentVolumeClaimSpec{ - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadWriteOnce, - api.ReadOnlyMany, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("12G"), - }, - }, - }, api.PersistentVolumeClaimStatus{ - Phase: api.ClaimPending, - }) - - scenarios := map[string]struct { - isExpectedFailure bool - oldClaim *api.PersistentVolumeClaim - newClaim *api.PersistentVolumeClaim - enableResize bool - }{ - "valid-update-volumeName-only": { - isExpectedFailure: false, - oldClaim: validClaim, - newClaim: validUpdateClaim, - enableResize: false, - }, - "valid-no-op-update": { - isExpectedFailure: false, - oldClaim: validUpdateClaim, - newClaim: validUpdateClaim, - enableResize: false, - }, - "invalid-update-change-resources-on-bound-claim": { - isExpectedFailure: true, - oldClaim: validUpdateClaim, - newClaim: invalidUpdateClaimResources, - enableResize: false, - }, - "invalid-update-change-access-modes-on-bound-claim": { - isExpectedFailure: true, - oldClaim: validUpdateClaim, - newClaim: invalidUpdateClaimAccessModes, - enableResize: false, - }, - "invalid-update-change-storage-class-annotation-after-creation": { - isExpectedFailure: true, - oldClaim: validClaimStorageClass, - newClaim: invalidUpdateClaimStorageClass, - enableResize: false, - }, - "valid-update-mutable-annotation": { - isExpectedFailure: false, - oldClaim: validClaimAnnotation, - newClaim: validUpdateClaimMutableAnnotation, - enableResize: false, - }, - "valid-update-add-annotation": { - isExpectedFailure: false, - oldClaim: validClaim, - newClaim: validAddClaimAnnotation, - enableResize: false, - }, - "valid-size-update-resize-disabled": { - isExpectedFailure: true, - oldClaim: validClaim, - newClaim: validSizeUpdate, - enableResize: false, - }, - "valid-size-update-resize-enabled": { - isExpectedFailure: false, - oldClaim: validClaim, - newClaim: validSizeUpdate, - enableResize: true, - }, - "invalid-size-update-resize-enabled": { - isExpectedFailure: true, - oldClaim: validClaim, - newClaim: invalidSizeUpdate, - enableResize: true, - }, - "unbound-size-update-resize-enabled": { - isExpectedFailure: true, - oldClaim: validClaim, - newClaim: unboundSizeUpdate, - enableResize: true, - }, - } - - for name, scenario := range scenarios { - // ensure we have a resource version specified for updates - togglePVExpandFeature(scenario.enableResize, t) - scenario.oldClaim.ResourceVersion = "1" - scenario.newClaim.ResourceVersion = "1" - errs := ValidatePersistentVolumeClaimUpdate(scenario.newClaim, scenario.oldClaim) - if len(errs) == 0 && scenario.isExpectedFailure { - t.Errorf("Unexpected success for scenario: %s", name) - } - if len(errs) > 0 && !scenario.isExpectedFailure { - t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) - } - } -} - -func togglePVExpandFeature(toggleFlag bool, t *testing.T) { - if toggleFlag { - // Enable alpha feature LocalStorageCapacityIsolation - err := utilfeature.DefaultFeatureGate.Set("ExpandPersistentVolumes=true") - if err != nil { - t.Errorf("Failed to enable feature gate for ExpandPersistentVolumes: %v", err) - return - } - } else { - err := utilfeature.DefaultFeatureGate.Set("ExpandPersistentVolumes=false") - if err != nil { - t.Errorf("Failed to disable feature gate for ExpandPersistentVolumes: %v", err) - return - } - } -} - -func TestValidateKeyToPath(t *testing.T) { - testCases := []struct { - kp api.KeyToPath - ok bool - errtype field.ErrorType - }{ - { - kp: api.KeyToPath{Key: "k", Path: "p"}, - ok: true, - }, - { - kp: api.KeyToPath{Key: "k", Path: "p/p/p/p"}, - ok: true, - }, - { - kp: api.KeyToPath{Key: "k", Path: "p/..p/p../p..p"}, - ok: true, - }, - { - kp: api.KeyToPath{Key: "k", Path: "p", Mode: newInt32(0644)}, - ok: true, - }, - { - kp: api.KeyToPath{Key: "", Path: "p"}, - ok: false, - errtype: field.ErrorTypeRequired, - }, - { - kp: api.KeyToPath{Key: "k", Path: ""}, - ok: false, - errtype: field.ErrorTypeRequired, - }, - { - kp: api.KeyToPath{Key: "k", Path: "..p"}, - ok: false, - errtype: field.ErrorTypeInvalid, - }, - { - kp: api.KeyToPath{Key: "k", Path: "../p"}, - ok: false, - errtype: field.ErrorTypeInvalid, - }, - { - kp: api.KeyToPath{Key: "k", Path: "p/../p"}, - ok: false, - errtype: field.ErrorTypeInvalid, - }, - { - kp: api.KeyToPath{Key: "k", Path: "p/.."}, - ok: false, - errtype: field.ErrorTypeInvalid, - }, - { - kp: api.KeyToPath{Key: "k", Path: "p", Mode: newInt32(01000)}, - ok: false, - errtype: field.ErrorTypeInvalid, - }, - { - kp: api.KeyToPath{Key: "k", Path: "p", Mode: newInt32(-1)}, - ok: false, - errtype: field.ErrorTypeInvalid, - }, - } - - for i, tc := range testCases { - errs := validateKeyToPath(&tc.kp, field.NewPath("field")) - if tc.ok && len(errs) > 0 { - t.Errorf("[%d] unexpected errors: %v", i, errs) - } else if !tc.ok && len(errs) == 0 { - t.Errorf("[%d] expected error type %v", i, tc.errtype) - } else if len(errs) > 1 { - t.Errorf("[%d] expected only one error, got %d", i, len(errs)) - } else if !tc.ok { - if errs[0].Type != tc.errtype { - t.Errorf("[%d] expected error type %v, got %v", i, tc.errtype, errs[0].Type) - } - } - } -} - -func TestValidateNFSVolumeSource(t *testing.T) { - testCases := []struct { - name string - nfs *api.NFSVolumeSource - errtype field.ErrorType - errfield string - errdetail string - }{ - { - name: "missing server", - nfs: &api.NFSVolumeSource{Server: "", Path: "/tmp"}, - errtype: field.ErrorTypeRequired, - errfield: "server", - }, - { - name: "missing path", - nfs: &api.NFSVolumeSource{Server: "my-server", Path: ""}, - errtype: field.ErrorTypeRequired, - errfield: "path", - }, - { - name: "abs path", - nfs: &api.NFSVolumeSource{Server: "my-server", Path: "tmp"}, - errtype: field.ErrorTypeInvalid, - errfield: "path", - errdetail: "must be an absolute path", - }, - } - - for i, tc := range testCases { - errs := validateNFSVolumeSource(tc.nfs, field.NewPath("field")) - - if len(errs) > 0 && tc.errtype == "" { - t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs) - } else if len(errs) == 0 && tc.errtype != "" { - t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype) - } else if len(errs) >= 1 { - if errs[0].Type != tc.errtype { - t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type) - } else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) { - t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field) - } else if !strings.Contains(errs[0].Detail, tc.errdetail) { - t.Errorf("[%d: %q] expected error detail %q, got %q", i, tc.name, tc.errdetail, errs[0].Detail) - } - } - } -} - -func TestValidateGlusterfs(t *testing.T) { - testCases := []struct { - name string - gfs *api.GlusterfsVolumeSource - errtype field.ErrorType - errfield string - }{ - { - name: "missing endpointname", - gfs: &api.GlusterfsVolumeSource{EndpointsName: "", Path: "/tmp"}, - errtype: field.ErrorTypeRequired, - errfield: "endpoints", - }, - { - name: "missing path", - gfs: &api.GlusterfsVolumeSource{EndpointsName: "my-endpoint", Path: ""}, - errtype: field.ErrorTypeRequired, - errfield: "path", - }, - { - name: "missing endpintname and path", - gfs: &api.GlusterfsVolumeSource{EndpointsName: "", Path: ""}, - errtype: field.ErrorTypeRequired, - errfield: "endpoints", - }, - } - - for i, tc := range testCases { - errs := validateGlusterfsVolumeSource(tc.gfs, field.NewPath("field")) - - if len(errs) > 0 && tc.errtype == "" { - t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs) - } else if len(errs) == 0 && tc.errtype != "" { - t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype) - } else if len(errs) >= 1 { - if errs[0].Type != tc.errtype { - t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type) - } else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) { - t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field) - } - } - } -} - -// helper -func newInt32(val int) *int32 { - p := new(int32) - *p = int32(val) - return p -} - -// This test is a little too top-to-bottom. Ideally we would test each volume -// type on its own, but we want to also make sure that the logic works through -// the one-of wrapper, so we just do it all in one place. -func TestValidateVolumes(t *testing.T) { - validInitiatorName := "iqn.2015-02.example.com:init" - invalidInitiatorName := "2015-02.example.com:init" - testCases := []struct { - name string - vol api.Volume - errtype field.ErrorType - errfield string - errdetail string - }{ - // EmptyDir and basic volume names - { - name: "valid alpha name", - vol: api.Volume{ - Name: "empty", - VolumeSource: api.VolumeSource{ - EmptyDir: &api.EmptyDirVolumeSource{}, - }, - }, - }, - { - name: "valid num name", - vol: api.Volume{ - Name: "123", - VolumeSource: api.VolumeSource{ - EmptyDir: &api.EmptyDirVolumeSource{}, - }, - }, - }, - { - name: "valid alphanum name", - vol: api.Volume{ - Name: "empty-123", - VolumeSource: api.VolumeSource{ - EmptyDir: &api.EmptyDirVolumeSource{}, - }, - }, - }, - { - name: "valid numalpha name", - vol: api.Volume{ - Name: "123-empty", - VolumeSource: api.VolumeSource{ - EmptyDir: &api.EmptyDirVolumeSource{}, - }, - }, - }, - { - name: "zero-length name", - vol: api.Volume{ - Name: "", - VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}, - }, - errtype: field.ErrorTypeRequired, - errfield: "name", - }, - { - name: "name > 63 characters", - vol: api.Volume{ - Name: strings.Repeat("a", 64), - VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}, - }, - errtype: field.ErrorTypeInvalid, - errfield: "name", - errdetail: "must be no more than", - }, - { - name: "name not a DNS label", - vol: api.Volume{ - Name: "a.b.c", - VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}, - }, - errtype: field.ErrorTypeInvalid, - errfield: "name", - errdetail: dnsLabelErrMsg, - }, - // More than one source field specified. - { - name: "more than one source", - vol: api.Volume{ - Name: "dups", - VolumeSource: api.VolumeSource{ - EmptyDir: &api.EmptyDirVolumeSource{}, - HostPath: &api.HostPathVolumeSource{ - Path: "/mnt/path", - Type: newHostPathType(string(api.HostPathDirectory)), - }, - }, - }, - errtype: field.ErrorTypeForbidden, - errfield: "hostPath", - errdetail: "may not specify more than 1 volume", - }, - // HostPath Default - { - name: "default HostPath", - vol: api.Volume{ - Name: "hostpath", - VolumeSource: api.VolumeSource{ - HostPath: &api.HostPathVolumeSource{ - Path: "/mnt/path", - Type: newHostPathType(string(api.HostPathDirectory)), - }, - }, - }, - }, - // HostPath Supported - { - name: "valid HostPath", - vol: api.Volume{ - Name: "hostpath", - VolumeSource: api.VolumeSource{ - HostPath: &api.HostPathVolumeSource{ - Path: "/mnt/path", - Type: newHostPathType(string(api.HostPathSocket)), - }, - }, - }, - }, - // HostPath Invalid - { - name: "invalid HostPath", - vol: api.Volume{ - Name: "hostpath", - VolumeSource: api.VolumeSource{ - HostPath: &api.HostPathVolumeSource{ - Path: "/mnt/path", - Type: newHostPathType("invalid"), - }, - }, - }, - errtype: field.ErrorTypeNotSupported, - errfield: "type", - }, - { - name: "invalid HostPath backsteps", - vol: api.Volume{ - Name: "hostpath", - VolumeSource: api.VolumeSource{ - HostPath: &api.HostPathVolumeSource{ - Path: "/mnt/path/..", - Type: newHostPathType(string(api.HostPathDirectory)), - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "path", - errdetail: "must not contain '..'", - }, - // GcePersistentDisk - { - name: "valid GcePersistentDisk", - vol: api.Volume{ - Name: "gce-pd", - VolumeSource: api.VolumeSource{ - GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{ - PDName: "my-PD", - FSType: "ext4", - Partition: 1, - ReadOnly: false, - }, - }, - }, - }, - // AWSElasticBlockStore - { - name: "valid AWSElasticBlockStore", - vol: api.Volume{ - Name: "aws-ebs", - VolumeSource: api.VolumeSource{ - AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{ - VolumeID: "my-PD", - FSType: "ext4", - Partition: 1, - ReadOnly: false, - }, - }, - }, - }, - // GitRepo - { - name: "valid GitRepo", - vol: api.Volume{ - Name: "git-repo", - VolumeSource: api.VolumeSource{ - GitRepo: &api.GitRepoVolumeSource{ - Repository: "my-repo", - Revision: "hashstring", - Directory: "target", - }, - }, - }, - }, - { - name: "valid GitRepo in .", - vol: api.Volume{ - Name: "git-repo-dot", - VolumeSource: api.VolumeSource{ - GitRepo: &api.GitRepoVolumeSource{ - Repository: "my-repo", - Directory: ".", - }, - }, - }, - }, - { - name: "valid GitRepo with .. in name", - vol: api.Volume{ - Name: "git-repo-dot-dot-foo", - VolumeSource: api.VolumeSource{ - GitRepo: &api.GitRepoVolumeSource{ - Repository: "my-repo", - Directory: "..foo", - }, - }, - }, - }, - { - name: "GitRepo starts with ../", - vol: api.Volume{ - Name: "gitrepo", - VolumeSource: api.VolumeSource{ - GitRepo: &api.GitRepoVolumeSource{ - Repository: "foo", - Directory: "../dots/bar", - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "gitRepo.directory", - errdetail: `must not contain '..'`, - }, - { - name: "GitRepo contains ..", - vol: api.Volume{ - Name: "gitrepo", - VolumeSource: api.VolumeSource{ - GitRepo: &api.GitRepoVolumeSource{ - Repository: "foo", - Directory: "dots/../bar", - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "gitRepo.directory", - errdetail: `must not contain '..'`, - }, - { - name: "GitRepo absolute target", - vol: api.Volume{ - Name: "gitrepo", - VolumeSource: api.VolumeSource{ - GitRepo: &api.GitRepoVolumeSource{ - Repository: "foo", - Directory: "/abstarget", - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "gitRepo.directory", - }, - // ISCSI - { - name: "valid ISCSI", - vol: api.Volume{ - Name: "iscsi", - VolumeSource: api.VolumeSource{ - ISCSI: &api.ISCSIVolumeSource{ - TargetPortal: "127.0.0.1", - IQN: "iqn.2015-02.example.com:test", - Lun: 1, - FSType: "ext4", - ReadOnly: false, - }, - }, - }, - }, - { - name: "valid IQN: eui format", - vol: api.Volume{ - Name: "iscsi", - VolumeSource: api.VolumeSource{ - ISCSI: &api.ISCSIVolumeSource{ - TargetPortal: "127.0.0.1", - IQN: "eui.0123456789ABCDEF", - Lun: 1, - FSType: "ext4", - ReadOnly: false, - }, - }, - }, - }, - { - name: "valid IQN: naa format", - vol: api.Volume{ - Name: "iscsi", - VolumeSource: api.VolumeSource{ - ISCSI: &api.ISCSIVolumeSource{ - TargetPortal: "127.0.0.1", - IQN: "naa.62004567BA64678D0123456789ABCDEF", - Lun: 1, - FSType: "ext4", - ReadOnly: false, - }, - }, - }, - }, - { - name: "empty portal", - vol: api.Volume{ - Name: "iscsi", - VolumeSource: api.VolumeSource{ - ISCSI: &api.ISCSIVolumeSource{ - TargetPortal: "", - IQN: "iqn.2015-02.example.com:test", - Lun: 1, - FSType: "ext4", - ReadOnly: false, - }, - }, - }, - errtype: field.ErrorTypeRequired, - errfield: "iscsi.targetPortal", - }, - { - name: "empty iqn", - vol: api.Volume{ - Name: "iscsi", - VolumeSource: api.VolumeSource{ - ISCSI: &api.ISCSIVolumeSource{ - TargetPortal: "127.0.0.1", - IQN: "", - Lun: 1, - FSType: "ext4", - ReadOnly: false, - }, - }, - }, - errtype: field.ErrorTypeRequired, - errfield: "iscsi.iqn", - }, - { - name: "invalid IQN: iqn format", - vol: api.Volume{ - Name: "iscsi", - VolumeSource: api.VolumeSource{ - ISCSI: &api.ISCSIVolumeSource{ - TargetPortal: "127.0.0.1", - IQN: "iqn.2015-02.example.com:test;ls;", - Lun: 1, - FSType: "ext4", - ReadOnly: false, - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "iscsi.iqn", - }, - { - name: "invalid IQN: eui format", - vol: api.Volume{ - Name: "iscsi", - VolumeSource: api.VolumeSource{ - ISCSI: &api.ISCSIVolumeSource{ - TargetPortal: "127.0.0.1", - IQN: "eui.0123456789ABCDEFGHIJ", - Lun: 1, - FSType: "ext4", - ReadOnly: false, - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "iscsi.iqn", - }, - { - name: "invalid IQN: naa format", - vol: api.Volume{ - Name: "iscsi", - VolumeSource: api.VolumeSource{ - ISCSI: &api.ISCSIVolumeSource{ - TargetPortal: "127.0.0.1", - IQN: "naa.62004567BA_4-78D.123456789ABCDEF", - Lun: 1, - FSType: "ext4", - ReadOnly: false, - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "iscsi.iqn", - }, - { - name: "valid initiatorName", - vol: api.Volume{ - Name: "iscsi", - VolumeSource: api.VolumeSource{ - ISCSI: &api.ISCSIVolumeSource{ - TargetPortal: "127.0.0.1", - IQN: "iqn.2015-02.example.com:test", - Lun: 1, - InitiatorName: &validInitiatorName, - FSType: "ext4", - ReadOnly: false, - }, - }, - }, - }, - { - name: "invalid initiatorName", - vol: api.Volume{ - Name: "iscsi", - VolumeSource: api.VolumeSource{ - ISCSI: &api.ISCSIVolumeSource{ - TargetPortal: "127.0.0.1", - IQN: "iqn.2015-02.example.com:test", - Lun: 1, - InitiatorName: &invalidInitiatorName, - FSType: "ext4", - ReadOnly: false, - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "iscsi.initiatorname", - }, - { - name: "empty secret", - vol: api.Volume{ - Name: "iscsi", - VolumeSource: api.VolumeSource{ - ISCSI: &api.ISCSIVolumeSource{ - TargetPortal: "127.0.0.1", - IQN: "iqn.2015-02.example.com:test", - Lun: 1, - FSType: "ext4", - ReadOnly: false, - DiscoveryCHAPAuth: true, - }, - }, - }, - errtype: field.ErrorTypeRequired, - errfield: "iscsi.secretRef", - }, - { - name: "empty secret", - vol: api.Volume{ - Name: "iscsi", - VolumeSource: api.VolumeSource{ - ISCSI: &api.ISCSIVolumeSource{ - TargetPortal: "127.0.0.1", - IQN: "iqn.2015-02.example.com:test", - Lun: 1, - FSType: "ext4", - ReadOnly: false, - SessionCHAPAuth: true, - }, - }, - }, - errtype: field.ErrorTypeRequired, - errfield: "iscsi.secretRef", - }, - // Secret - { - name: "valid Secret", - vol: api.Volume{ - Name: "secret", - VolumeSource: api.VolumeSource{ - Secret: &api.SecretVolumeSource{ - SecretName: "my-secret", - }, - }, - }, - }, - { - name: "valid Secret with defaultMode", - vol: api.Volume{ - Name: "secret", - VolumeSource: api.VolumeSource{ - Secret: &api.SecretVolumeSource{ - SecretName: "my-secret", - DefaultMode: newInt32(0644), - }, - }, - }, - }, - { - name: "valid Secret with projection and mode", - vol: api.Volume{ - Name: "secret", - VolumeSource: api.VolumeSource{ - Secret: &api.SecretVolumeSource{ - SecretName: "my-secret", - Items: []api.KeyToPath{{ - Key: "key", - Path: "filename", - Mode: newInt32(0644), - }}, - }, - }, - }, - }, - { - name: "valid Secret with subdir projection", - vol: api.Volume{ - Name: "secret", - VolumeSource: api.VolumeSource{ - Secret: &api.SecretVolumeSource{ - SecretName: "my-secret", - Items: []api.KeyToPath{{ - Key: "key", - Path: "dir/filename", - }}, - }, - }, - }, - }, - { - name: "secret with missing path", - vol: api.Volume{ - Name: "secret", - VolumeSource: api.VolumeSource{ - Secret: &api.SecretVolumeSource{ - SecretName: "s", - Items: []api.KeyToPath{{Key: "key", Path: ""}}, - }, - }, - }, - errtype: field.ErrorTypeRequired, - errfield: "secret.items[0].path", - }, - { - name: "secret with leading ..", - vol: api.Volume{ - Name: "secret", - VolumeSource: api.VolumeSource{ - Secret: &api.SecretVolumeSource{ - SecretName: "s", - Items: []api.KeyToPath{{Key: "key", Path: "../foo"}}, - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "secret.items[0].path", - }, - { - name: "secret with .. inside", - vol: api.Volume{ - Name: "secret", - VolumeSource: api.VolumeSource{ - Secret: &api.SecretVolumeSource{ - SecretName: "s", - Items: []api.KeyToPath{{Key: "key", Path: "foo/../bar"}}, - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "secret.items[0].path", - }, - { - name: "secret with invalid positive defaultMode", - vol: api.Volume{ - Name: "secret", - VolumeSource: api.VolumeSource{ - Secret: &api.SecretVolumeSource{ - SecretName: "s", - DefaultMode: newInt32(01000), - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "secret.defaultMode", - }, - { - name: "secret with invalid negative defaultMode", - vol: api.Volume{ - Name: "secret", - VolumeSource: api.VolumeSource{ - Secret: &api.SecretVolumeSource{ - SecretName: "s", - DefaultMode: newInt32(-1), - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "secret.defaultMode", - }, - // ConfigMap - { - name: "valid ConfigMap", - vol: api.Volume{ - Name: "cfgmap", - VolumeSource: api.VolumeSource{ - ConfigMap: &api.ConfigMapVolumeSource{ - LocalObjectReference: api.LocalObjectReference{ - Name: "my-cfgmap", - }, - }, - }, - }, - }, - { - name: "valid ConfigMap with defaultMode", - vol: api.Volume{ - Name: "cfgmap", - VolumeSource: api.VolumeSource{ - ConfigMap: &api.ConfigMapVolumeSource{ - LocalObjectReference: api.LocalObjectReference{ - Name: "my-cfgmap", - }, - DefaultMode: newInt32(0644), - }, - }, - }, - }, - { - name: "valid ConfigMap with projection and mode", - vol: api.Volume{ - Name: "cfgmap", - VolumeSource: api.VolumeSource{ - ConfigMap: &api.ConfigMapVolumeSource{ - LocalObjectReference: api.LocalObjectReference{ - Name: "my-cfgmap"}, - Items: []api.KeyToPath{{ - Key: "key", - Path: "filename", - Mode: newInt32(0644), - }}, - }, - }, - }, - }, - { - name: "valid ConfigMap with subdir projection", - vol: api.Volume{ - Name: "cfgmap", - VolumeSource: api.VolumeSource{ - ConfigMap: &api.ConfigMapVolumeSource{ - LocalObjectReference: api.LocalObjectReference{ - Name: "my-cfgmap"}, - Items: []api.KeyToPath{{ - Key: "key", - Path: "dir/filename", - }}, - }, - }, - }, - }, - { - name: "configmap with missing path", - vol: api.Volume{ - Name: "cfgmap", - VolumeSource: api.VolumeSource{ - ConfigMap: &api.ConfigMapVolumeSource{ - LocalObjectReference: api.LocalObjectReference{Name: "c"}, - Items: []api.KeyToPath{{Key: "key", Path: ""}}, - }, - }, - }, - errtype: field.ErrorTypeRequired, - errfield: "configMap.items[0].path", - }, - { - name: "configmap with leading ..", - vol: api.Volume{ - Name: "cfgmap", - VolumeSource: api.VolumeSource{ - ConfigMap: &api.ConfigMapVolumeSource{ - LocalObjectReference: api.LocalObjectReference{Name: "c"}, - Items: []api.KeyToPath{{Key: "key", Path: "../foo"}}, - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "configMap.items[0].path", - }, - { - name: "configmap with .. inside", - vol: api.Volume{ - Name: "cfgmap", - VolumeSource: api.VolumeSource{ - ConfigMap: &api.ConfigMapVolumeSource{ - LocalObjectReference: api.LocalObjectReference{Name: "c"}, - Items: []api.KeyToPath{{Key: "key", Path: "foo/../bar"}}, - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "configMap.items[0].path", - }, - { - name: "configmap with invalid positive defaultMode", - vol: api.Volume{ - Name: "cfgmap", - VolumeSource: api.VolumeSource{ - ConfigMap: &api.ConfigMapVolumeSource{ - LocalObjectReference: api.LocalObjectReference{Name: "c"}, - DefaultMode: newInt32(01000), - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "configMap.defaultMode", - }, - { - name: "configmap with invalid negative defaultMode", - vol: api.Volume{ - Name: "cfgmap", - VolumeSource: api.VolumeSource{ - ConfigMap: &api.ConfigMapVolumeSource{ - LocalObjectReference: api.LocalObjectReference{Name: "c"}, - DefaultMode: newInt32(-1), - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "configMap.defaultMode", - }, - // Glusterfs - { - name: "valid Glusterfs", - vol: api.Volume{ - Name: "glusterfs", - VolumeSource: api.VolumeSource{ - Glusterfs: &api.GlusterfsVolumeSource{ - EndpointsName: "host1", - Path: "path", - ReadOnly: false, - }, - }, - }, - }, - { - name: "empty hosts", - vol: api.Volume{ - Name: "glusterfs", - VolumeSource: api.VolumeSource{ - Glusterfs: &api.GlusterfsVolumeSource{ - EndpointsName: "", - Path: "path", - ReadOnly: false, - }, - }, - }, - errtype: field.ErrorTypeRequired, - errfield: "glusterfs.endpoints", - }, - { - name: "empty path", - vol: api.Volume{ - Name: "glusterfs", - VolumeSource: api.VolumeSource{ - Glusterfs: &api.GlusterfsVolumeSource{ - EndpointsName: "host", - Path: "", - ReadOnly: false, - }, - }, - }, - errtype: field.ErrorTypeRequired, - errfield: "glusterfs.path", - }, - // Flocker - { - name: "valid Flocker -- datasetUUID", - vol: api.Volume{ - Name: "flocker", - VolumeSource: api.VolumeSource{ - Flocker: &api.FlockerVolumeSource{ - DatasetUUID: "d846b09d-223d-43df-ab5b-d6db2206a0e4", - }, - }, - }, - }, - { - name: "valid Flocker -- datasetName", - vol: api.Volume{ - Name: "flocker", - VolumeSource: api.VolumeSource{ - Flocker: &api.FlockerVolumeSource{ - DatasetName: "datasetName", - }, - }, - }, - }, - { - name: "both empty", - vol: api.Volume{ - Name: "flocker", - VolumeSource: api.VolumeSource{ - Flocker: &api.FlockerVolumeSource{ - DatasetName: "", - }, - }, - }, - errtype: field.ErrorTypeRequired, - errfield: "flocker", - }, - { - name: "both specified", - vol: api.Volume{ - Name: "flocker", - VolumeSource: api.VolumeSource{ - Flocker: &api.FlockerVolumeSource{ - DatasetName: "datasetName", - DatasetUUID: "d846b09d-223d-43df-ab5b-d6db2206a0e4", - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "flocker", - }, - { - name: "slash in flocker datasetName", - vol: api.Volume{ - Name: "flocker", - VolumeSource: api.VolumeSource{ - Flocker: &api.FlockerVolumeSource{ - DatasetName: "foo/bar", - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "flocker.datasetName", - errdetail: "must not contain '/'", - }, - // RBD - { - name: "valid RBD", - vol: api.Volume{ - Name: "rbd", - VolumeSource: api.VolumeSource{ - RBD: &api.RBDVolumeSource{ - CephMonitors: []string{"foo"}, - RBDImage: "bar", - FSType: "ext4", - }, - }, - }, - }, - { - name: "empty rbd monitors", - vol: api.Volume{ - Name: "rbd", - VolumeSource: api.VolumeSource{ - RBD: &api.RBDVolumeSource{ - CephMonitors: []string{}, - RBDImage: "bar", - FSType: "ext4", - }, - }, - }, - errtype: field.ErrorTypeRequired, - errfield: "rbd.monitors", - }, - { - name: "empty image", - vol: api.Volume{ - Name: "rbd", - VolumeSource: api.VolumeSource{ - RBD: &api.RBDVolumeSource{ - CephMonitors: []string{"foo"}, - RBDImage: "", - FSType: "ext4", - }, - }, - }, - errtype: field.ErrorTypeRequired, - errfield: "rbd.image", - }, - // Cinder - { - name: "valid Cinder", - vol: api.Volume{ - Name: "cinder", - VolumeSource: api.VolumeSource{ - Cinder: &api.CinderVolumeSource{ - VolumeID: "29ea5088-4f60-4757-962e-dba678767887", - FSType: "ext4", - ReadOnly: false, - }, - }, - }, - }, - // CephFS - { - name: "valid CephFS", - vol: api.Volume{ - Name: "cephfs", - VolumeSource: api.VolumeSource{ - CephFS: &api.CephFSVolumeSource{ - Monitors: []string{"foo"}, - }, - }, - }, - }, - { - name: "empty cephfs monitors", - vol: api.Volume{ - Name: "cephfs", - VolumeSource: api.VolumeSource{ - CephFS: &api.CephFSVolumeSource{ - Monitors: []string{}, - }, - }, - }, - errtype: field.ErrorTypeRequired, - errfield: "cephfs.monitors", - }, - // DownwardAPI - { - name: "valid DownwardAPI", - vol: api.Volume{ - Name: "downwardapi", - VolumeSource: api.VolumeSource{ - DownwardAPI: &api.DownwardAPIVolumeSource{ - Items: []api.DownwardAPIVolumeFile{ - { - Path: "labels", - FieldRef: &api.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.labels", - }, - }, - { - Path: "annotations", - FieldRef: &api.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.annotations", - }, - }, - { - Path: "namespace", - FieldRef: &api.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.namespace", - }, - }, - { - Path: "name", - FieldRef: &api.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.name", - }, - }, - { - Path: "path/with/subdirs", - FieldRef: &api.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.labels", - }, - }, - { - Path: "path/./withdot", - FieldRef: &api.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.labels", - }, - }, - { - Path: "path/with/embedded..dotdot", - FieldRef: &api.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.labels", - }, - }, - { - Path: "path/with/leading/..dotdot", - FieldRef: &api.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.labels", - }, - }, - { - Path: "cpu_limit", - ResourceFieldRef: &api.ResourceFieldSelector{ - ContainerName: "test-container", - Resource: "limits.cpu", - }, - }, - { - Path: "cpu_request", - ResourceFieldRef: &api.ResourceFieldSelector{ - ContainerName: "test-container", - Resource: "requests.cpu", - }, - }, - { - Path: "memory_limit", - ResourceFieldRef: &api.ResourceFieldSelector{ - ContainerName: "test-container", - Resource: "limits.memory", - }, - }, - { - Path: "memory_request", - ResourceFieldRef: &api.ResourceFieldSelector{ - ContainerName: "test-container", - Resource: "requests.memory", - }, - }, - }, - }, - }, - }, - }, - { - name: "downapi valid defaultMode", - vol: api.Volume{ - Name: "downapi", - VolumeSource: api.VolumeSource{ - DownwardAPI: &api.DownwardAPIVolumeSource{ - DefaultMode: newInt32(0644), - }, - }, - }, - }, - { - name: "downapi valid item mode", - vol: api.Volume{ - Name: "downapi", - VolumeSource: api.VolumeSource{ - DownwardAPI: &api.DownwardAPIVolumeSource{ - Items: []api.DownwardAPIVolumeFile{{ - Mode: newInt32(0644), - Path: "path", - FieldRef: &api.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.labels", - }, - }}, - }, - }, - }, - }, - { - name: "downapi invalid positive item mode", - vol: api.Volume{ - Name: "downapi", - VolumeSource: api.VolumeSource{ - DownwardAPI: &api.DownwardAPIVolumeSource{ - Items: []api.DownwardAPIVolumeFile{{ - Mode: newInt32(01000), - Path: "path", - FieldRef: &api.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.labels", - }, - }}, - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "downwardAPI.mode", - }, - { - name: "downapi invalid negative item mode", - vol: api.Volume{ - Name: "downapi", - VolumeSource: api.VolumeSource{ - DownwardAPI: &api.DownwardAPIVolumeSource{ - Items: []api.DownwardAPIVolumeFile{{ - Mode: newInt32(-1), - Path: "path", - FieldRef: &api.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.labels", - }, - }}, - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "downwardAPI.mode", - }, - { - name: "downapi empty metatada path", - vol: api.Volume{ - Name: "downapi", - VolumeSource: api.VolumeSource{ - DownwardAPI: &api.DownwardAPIVolumeSource{ - Items: []api.DownwardAPIVolumeFile{{ - Path: "", - FieldRef: &api.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.labels", - }, - }}, - }, - }, - }, - errtype: field.ErrorTypeRequired, - errfield: "downwardAPI.path", - }, - { - name: "downapi absolute path", - vol: api.Volume{ - Name: "downapi", - VolumeSource: api.VolumeSource{ - DownwardAPI: &api.DownwardAPIVolumeSource{ - Items: []api.DownwardAPIVolumeFile{{ - Path: "/absolutepath", - FieldRef: &api.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.labels", - }, - }}, - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "downwardAPI.path", - }, - { - name: "downapi dot dot path", - vol: api.Volume{ - Name: "downapi", - VolumeSource: api.VolumeSource{ - DownwardAPI: &api.DownwardAPIVolumeSource{ - Items: []api.DownwardAPIVolumeFile{{ - Path: "../../passwd", - FieldRef: &api.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.labels", - }, - }}, - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "downwardAPI.path", - errdetail: `must not contain '..'`, - }, - { - name: "downapi dot dot file name", - vol: api.Volume{ - Name: "downapi", - VolumeSource: api.VolumeSource{ - DownwardAPI: &api.DownwardAPIVolumeSource{ - Items: []api.DownwardAPIVolumeFile{{ - Path: "..badFileName", - FieldRef: &api.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.labels", - }, - }}, - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "downwardAPI.path", - errdetail: `must not start with '..'`, - }, - { - name: "downapi dot dot first level dirent", - vol: api.Volume{ - Name: "downapi", - VolumeSource: api.VolumeSource{ - DownwardAPI: &api.DownwardAPIVolumeSource{ - Items: []api.DownwardAPIVolumeFile{{ - Path: "..badDirName/goodFileName", - FieldRef: &api.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.labels", - }, - }}, - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "downwardAPI.path", - errdetail: `must not start with '..'`, - }, - { - name: "downapi fieldRef and ResourceFieldRef together", - vol: api.Volume{ - Name: "downapi", - VolumeSource: api.VolumeSource{ - DownwardAPI: &api.DownwardAPIVolumeSource{ - Items: []api.DownwardAPIVolumeFile{{ - Path: "test", - FieldRef: &api.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.labels", - }, - ResourceFieldRef: &api.ResourceFieldSelector{ - ContainerName: "test-container", - Resource: "requests.memory", - }, - }}, - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "downwardAPI", - errdetail: "fieldRef and resourceFieldRef can not be specified simultaneously", - }, - { - name: "downapi invalid positive defaultMode", - vol: api.Volume{ - Name: "downapi", - VolumeSource: api.VolumeSource{ - DownwardAPI: &api.DownwardAPIVolumeSource{ - DefaultMode: newInt32(01000), - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "downwardAPI.defaultMode", - }, - { - name: "downapi invalid negative defaultMode", - vol: api.Volume{ - Name: "downapi", - VolumeSource: api.VolumeSource{ - DownwardAPI: &api.DownwardAPIVolumeSource{ - DefaultMode: newInt32(-1), - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "downwardAPI.defaultMode", - }, - // FC - { - name: "FC valid targetWWNs and lun", - vol: api.Volume{ - Name: "fc", - VolumeSource: api.VolumeSource{ - FC: &api.FCVolumeSource{ - TargetWWNs: []string{"some_wwn"}, - Lun: newInt32(1), - FSType: "ext4", - ReadOnly: false, - }, - }, - }, - }, - { - name: "FC valid wwids", - vol: api.Volume{ - Name: "fc", - VolumeSource: api.VolumeSource{ - FC: &api.FCVolumeSource{ - WWIDs: []string{"some_wwid"}, - FSType: "ext4", - ReadOnly: false, - }, - }, - }, - }, - { - name: "FC empty targetWWNs and wwids", - vol: api.Volume{ - Name: "fc", - VolumeSource: api.VolumeSource{ - FC: &api.FCVolumeSource{ - TargetWWNs: []string{}, - Lun: newInt32(1), - WWIDs: []string{}, - FSType: "ext4", - ReadOnly: false, - }, - }, - }, - errtype: field.ErrorTypeRequired, - errfield: "fc.targetWWNs", - errdetail: "must specify either targetWWNs or wwids", - }, - { - name: "FC invalid: both targetWWNs and wwids simultaneously", - vol: api.Volume{ - Name: "fc", - VolumeSource: api.VolumeSource{ - FC: &api.FCVolumeSource{ - TargetWWNs: []string{"some_wwn"}, - Lun: newInt32(1), - WWIDs: []string{"some_wwid"}, - FSType: "ext4", - ReadOnly: false, - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "fc.targetWWNs", - errdetail: "targetWWNs and wwids can not be specified simultaneously", - }, - { - name: "FC valid targetWWNs and empty lun", - vol: api.Volume{ - Name: "fc", - VolumeSource: api.VolumeSource{ - FC: &api.FCVolumeSource{ - TargetWWNs: []string{"wwn"}, - Lun: nil, - FSType: "ext4", - ReadOnly: false, - }, - }, - }, - errtype: field.ErrorTypeRequired, - errfield: "fc.lun", - errdetail: "lun is required if targetWWNs is specified", - }, - { - name: "FC valid targetWWNs and invalid lun", - vol: api.Volume{ - Name: "fc", - VolumeSource: api.VolumeSource{ - FC: &api.FCVolumeSource{ - TargetWWNs: []string{"wwn"}, - Lun: newInt32(256), - FSType: "ext4", - ReadOnly: false, - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "fc.lun", - errdetail: validation.InclusiveRangeError(0, 255), - }, - // FlexVolume - { - name: "valid FlexVolume", - vol: api.Volume{ - Name: "flex-volume", - VolumeSource: api.VolumeSource{ - FlexVolume: &api.FlexVolumeSource{ - Driver: "kubernetes.io/blue", - FSType: "ext4", - }, - }, - }, - }, - // AzureFile - { - name: "valid AzureFile", - vol: api.Volume{ - Name: "azure-file", - VolumeSource: api.VolumeSource{ - AzureFile: &api.AzureFileVolumeSource{ - SecretName: "key", - ShareName: "share", - ReadOnly: false, - }, - }, - }, - }, - { - name: "AzureFile empty secret", - vol: api.Volume{ - Name: "azure-file", - VolumeSource: api.VolumeSource{ - AzureFile: &api.AzureFileVolumeSource{ - SecretName: "", - ShareName: "share", - ReadOnly: false, - }, - }, - }, - errtype: field.ErrorTypeRequired, - errfield: "azureFile.secretName", - }, - { - name: "AzureFile empty share", - vol: api.Volume{ - Name: "azure-file", - VolumeSource: api.VolumeSource{ - AzureFile: &api.AzureFileVolumeSource{ - SecretName: "name", - ShareName: "", - ReadOnly: false, - }, - }, - }, - errtype: field.ErrorTypeRequired, - errfield: "azureFile.shareName", - }, - // Quobyte - { - name: "valid Quobyte", - vol: api.Volume{ - Name: "quobyte", - VolumeSource: api.VolumeSource{ - Quobyte: &api.QuobyteVolumeSource{ - Registry: "registry:7861", - Volume: "volume", - ReadOnly: false, - User: "root", - Group: "root", - }, - }, - }, - }, - { - name: "empty registry quobyte", - vol: api.Volume{ - Name: "quobyte", - VolumeSource: api.VolumeSource{ - Quobyte: &api.QuobyteVolumeSource{ - Volume: "/test", - }, - }, - }, - errtype: field.ErrorTypeRequired, - errfield: "quobyte.registry", - }, - { - name: "wrong format registry quobyte", - vol: api.Volume{ - Name: "quobyte", - VolumeSource: api.VolumeSource{ - Quobyte: &api.QuobyteVolumeSource{ - Registry: "registry7861", - Volume: "/test", - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "quobyte.registry", - }, - { - name: "wrong format multiple registries quobyte", - vol: api.Volume{ - Name: "quobyte", - VolumeSource: api.VolumeSource{ - Quobyte: &api.QuobyteVolumeSource{ - Registry: "registry:7861,reg2", - Volume: "/test", - }, - }, - }, - errtype: field.ErrorTypeInvalid, - errfield: "quobyte.registry", - }, - { - name: "empty volume quobyte", - vol: api.Volume{ - Name: "quobyte", - VolumeSource: api.VolumeSource{ - Quobyte: &api.QuobyteVolumeSource{ - Registry: "registry:7861", - }, - }, - }, - errtype: field.ErrorTypeRequired, - errfield: "quobyte.volume", - }, - // AzureDisk - { - name: "valid AzureDisk", - vol: api.Volume{ - Name: "azure-disk", - VolumeSource: api.VolumeSource{ - AzureDisk: &api.AzureDiskVolumeSource{ - DiskName: "foo", - DataDiskURI: "https://blob/vhds/bar.vhd", - }, - }, - }, - }, - { - name: "AzureDisk empty disk name", - vol: api.Volume{ - Name: "azure-disk", - VolumeSource: api.VolumeSource{ - AzureDisk: &api.AzureDiskVolumeSource{ - DiskName: "", - DataDiskURI: "https://blob/vhds/bar.vhd", - }, - }, - }, - errtype: field.ErrorTypeRequired, - errfield: "azureDisk.diskName", - }, - { - name: "AzureDisk empty disk uri", - vol: api.Volume{ - Name: "azure-disk", - VolumeSource: api.VolumeSource{ - AzureDisk: &api.AzureDiskVolumeSource{ - DiskName: "foo", - DataDiskURI: "", - }, - }, - }, - errtype: field.ErrorTypeRequired, - errfield: "azureDisk.diskURI", - }, - // ScaleIO - { - name: "valid scaleio volume", - vol: api.Volume{ - Name: "scaleio-volume", - VolumeSource: api.VolumeSource{ - ScaleIO: &api.ScaleIOVolumeSource{ - Gateway: "http://abcd/efg", - System: "test-system", - VolumeName: "test-vol-1", - }, - }, - }, - }, - { - name: "ScaleIO with empty name", - vol: api.Volume{ - Name: "scaleio-volume", - VolumeSource: api.VolumeSource{ - ScaleIO: &api.ScaleIOVolumeSource{ - Gateway: "http://abcd/efg", - System: "test-system", - VolumeName: "", - }, - }, - }, - errtype: field.ErrorTypeRequired, - errfield: "scaleIO.volumeName", - }, - { - name: "ScaleIO with empty gateway", - vol: api.Volume{ - Name: "scaleio-volume", - VolumeSource: api.VolumeSource{ - ScaleIO: &api.ScaleIOVolumeSource{ - Gateway: "", - System: "test-system", - VolumeName: "test-vol-1", - }, - }, - }, - errtype: field.ErrorTypeRequired, - errfield: "scaleIO.gateway", - }, - { - name: "ScaleIO with empty system", - vol: api.Volume{ - Name: "scaleio-volume", - VolumeSource: api.VolumeSource{ - ScaleIO: &api.ScaleIOVolumeSource{ - Gateway: "http://agc/efg/gateway", - System: "", - VolumeName: "test-vol-1", - }, - }, - }, - errtype: field.ErrorTypeRequired, - errfield: "scaleIO.system", - }, - } - - for i, tc := range testCases { - names, errs := ValidateVolumes([]api.Volume{tc.vol}, field.NewPath("field")) - if len(errs) > 0 && tc.errtype == "" { - t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs) - } else if len(errs) > 1 { - t.Errorf("[%d: %q] expected 1 error, got %d: %v", i, tc.name, len(errs), errs) - } else if len(errs) == 0 && tc.errtype != "" { - t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype) - } else if len(errs) == 1 { - if errs[0].Type != tc.errtype { - t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type) - } else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) { - t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field) - } else if !strings.Contains(errs[0].Detail, tc.errdetail) { - t.Errorf("[%d: %q] expected error detail %q, got %q", i, tc.name, tc.errdetail, errs[0].Detail) - } - } else { - if len(names) != 1 || !names.Has(tc.vol.Name) { - t.Errorf("[%d: %q] wrong names result: %v", i, tc.name, names) - } - } - } - - dupsCase := []api.Volume{ - {Name: "abc", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}, - {Name: "abc", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}, - } - _, errs := ValidateVolumes(dupsCase, field.NewPath("field")) - if len(errs) == 0 { - t.Errorf("expected error") - } else if len(errs) != 1 { - t.Errorf("expected 1 error, got %d: %v", len(errs), errs) - } else if errs[0].Type != field.ErrorTypeDuplicate { - t.Errorf("expected error type %v, got %v", field.ErrorTypeDuplicate, errs[0].Type) - } - - // Validate HugePages medium type for EmptyDir when HugePages feature is enabled/disabled - hugePagesCase := api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{Medium: api.StorageMediumHugePages}} - - // Enable alpha feature HugePages - err := utilfeature.DefaultFeatureGate.Set("HugePages=true") - if err != nil { - t.Errorf("Failed to enable feature gate for HugePages: %v", err) - } - if errs := validateVolumeSource(&hugePagesCase, field.NewPath("field").Index(0), "working"); len(errs) != 0 { - t.Errorf("Unexpected error when HugePages feature is enabled.") - } - - // Disable alpha feature HugePages - err = utilfeature.DefaultFeatureGate.Set("HugePages=false") - if err != nil { - t.Errorf("Failed to disable feature gate for HugePages: %v", err) - } - if errs := validateVolumeSource(&hugePagesCase, field.NewPath("field").Index(0), "failing"); len(errs) == 0 { - t.Errorf("Expected error when HugePages feature is disabled got nothing.") - } - -} - -func TestAlphaHugePagesIsolation(t *testing.T) { - successCases := []api.Pod{ - { // Basic fields. - ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), - }, - Limits: api.ResourceList{ - api.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), - }, - }, - }, - }, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - }, - } - failureCases := []api.Pod{ - { // Basic fields. - ObjectMeta: metav1.ObjectMeta{Name: "hugepages-shared", Namespace: "ns"}, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), - }, - Limits: api.ResourceList{ - api.ResourceName("hugepages-2Mi"): resource.MustParse("2Gi"), - }, - }, - }, - }, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - }, - { // Basic fields. - ObjectMeta: metav1.ObjectMeta{Name: "hugepages-multiple", Namespace: "ns"}, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), - api.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), - }, - Limits: api.ResourceList{ - api.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), - api.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), - }, - }, - }, - }, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - }, - } - // Enable alpha feature HugePages - err := utilfeature.DefaultFeatureGate.Set("HugePages=true") - if err != nil { - t.Errorf("Failed to enable feature gate for HugePages: %v", err) - return - } - for i := range successCases { - pod := &successCases[i] - if errs := ValidatePod(pod); len(errs) != 0 { - t.Errorf("Unexpected error for case[%d], err: %v", i, errs) - } - } - for i := range failureCases { - pod := &failureCases[i] - if errs := ValidatePod(pod); len(errs) == 0 { - t.Errorf("Expected error for case[%d], pod: %v", i, pod.Name) - } - } - // Disable alpha feature HugePages - err = utilfeature.DefaultFeatureGate.Set("HugePages=false") - if err != nil { - t.Errorf("Failed to disable feature gate for HugePages: %v", err) - return - } - // Disable alpha feature HugePages and ensure all success cases fail - for i := range successCases { - pod := &successCases[i] - if errs := ValidatePod(pod); len(errs) == 0 { - t.Errorf("Expected error for case[%d], pod: %v", i, pod.Name) - } - } -} - -func TestAlphaLocalStorageCapacityIsolation(t *testing.T) { - - testCases := []api.VolumeSource{ - {EmptyDir: &api.EmptyDirVolumeSource{SizeLimit: resource.NewQuantity(int64(5), resource.BinarySI)}}, - } - // Enable alpha feature LocalStorageCapacityIsolation - err := utilfeature.DefaultFeatureGate.Set("LocalStorageCapacityIsolation=true") - if err != nil { - t.Errorf("Failed to enable feature gate for LocalStorageCapacityIsolation: %v", err) - return - } - for _, tc := range testCases { - if errs := validateVolumeSource(&tc, field.NewPath("spec"), "tmpvol"); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - } - // Disable alpha feature LocalStorageCapacityIsolation - err = utilfeature.DefaultFeatureGate.Set("LocalStorageCapacityIsolation=false") - if err != nil { - t.Errorf("Failed to disable feature gate for LocalStorageCapacityIsolation: %v", err) - return - } - for _, tc := range testCases { - if errs := validateVolumeSource(&tc, field.NewPath("spec"), "tmpvol"); len(errs) == 0 { - t.Errorf("expected failure: %v", errs) - } - } - - containerLimitCase := api.ResourceRequirements{ - Limits: api.ResourceList{ - api.ResourceEphemeralStorage: *resource.NewMilliQuantity( - int64(40000), - resource.BinarySI), - }, - } - // Enable alpha feature LocalStorageCapacityIsolation - err = utilfeature.DefaultFeatureGate.Set("LocalStorageCapacityIsolation=true") - if err != nil { - t.Errorf("Failed to enable feature gate for LocalStorageCapacityIsolation: %v", err) - return - } - if errs := ValidateResourceRequirements(&containerLimitCase, field.NewPath("resources")); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - // Disable alpha feature LocalStorageCapacityIsolation - err = utilfeature.DefaultFeatureGate.Set("LocalStorageCapacityIsolation=false") - if err != nil { - t.Errorf("Failed to disable feature gate for LocalStorageCapacityIsolation: %v", err) - return - } - if errs := ValidateResourceRequirements(&containerLimitCase, field.NewPath("resources")); len(errs) == 0 { - t.Errorf("expected failure: %v", errs) - } - -} - -func TestValidateResourceQuotaWithAlphaLocalStorageCapacityIsolation(t *testing.T) { - spec := api.ResourceQuotaSpec{ - Hard: api.ResourceList{ - api.ResourceCPU: resource.MustParse("100"), - api.ResourceMemory: resource.MustParse("10000"), - api.ResourceRequestsCPU: resource.MustParse("100"), - api.ResourceRequestsMemory: resource.MustParse("10000"), - api.ResourceLimitsCPU: resource.MustParse("100"), - api.ResourceLimitsMemory: resource.MustParse("10000"), - api.ResourcePods: resource.MustParse("10"), - api.ResourceServices: resource.MustParse("0"), - api.ResourceReplicationControllers: resource.MustParse("10"), - api.ResourceQuotas: resource.MustParse("10"), - api.ResourceConfigMaps: resource.MustParse("10"), - api.ResourceSecrets: resource.MustParse("10"), - api.ResourceEphemeralStorage: resource.MustParse("10000"), - api.ResourceRequestsEphemeralStorage: resource.MustParse("10000"), - api.ResourceLimitsEphemeralStorage: resource.MustParse("10000"), - }, - } - resourceQuota := &api.ResourceQuota{ - ObjectMeta: metav1.ObjectMeta{ - Name: "abc", - Namespace: "foo", - }, - Spec: spec, - } - - // Enable alpha feature LocalStorageCapacityIsolation - err := utilfeature.DefaultFeatureGate.Set("LocalStorageCapacityIsolation=true") - if err != nil { - t.Errorf("Failed to enable feature gate for LocalStorageCapacityIsolation: %v", err) - return - } - if errs := ValidateResourceQuota(resourceQuota); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - - // Disable alpha feature LocalStorageCapacityIsolation - err = utilfeature.DefaultFeatureGate.Set("LocalStorageCapacityIsolation=false") - if err != nil { - t.Errorf("Failed to disable feature gate for LocalStorageCapacityIsolation: %v", err) - return - } - errs := ValidateResourceQuota(resourceQuota) - if len(errs) == 0 { - t.Errorf("expected failure for %s", resourceQuota.Name) - } - expectedErrMes := "ResourceEphemeralStorage field disabled by feature-gate for ResourceQuota" - for i := range errs { - if !strings.Contains(errs[i].Detail, expectedErrMes) { - t.Errorf("[%s]: expected error detail either empty or %s, got %s", resourceQuota.Name, expectedErrMes, errs[i].Detail) - } - } -} - -func TestValidatePorts(t *testing.T) { - successCase := []api.ContainerPort{ - {Name: "abc", ContainerPort: 80, HostPort: 80, Protocol: "TCP"}, - {Name: "easy", ContainerPort: 82, Protocol: "TCP"}, - {Name: "as", ContainerPort: 83, Protocol: "UDP"}, - {Name: "do-re-me", ContainerPort: 84, Protocol: "UDP"}, - {ContainerPort: 85, Protocol: "TCP"}, - } - if errs := validateContainerPorts(successCase, field.NewPath("field")); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - - nonCanonicalCase := []api.ContainerPort{ - {ContainerPort: 80, Protocol: "TCP"}, - } - if errs := validateContainerPorts(nonCanonicalCase, field.NewPath("field")); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - - errorCases := map[string]struct { - P []api.ContainerPort - T field.ErrorType - F string - D string - }{ - "name > 15 characters": { - []api.ContainerPort{{Name: strings.Repeat("a", 16), ContainerPort: 80, Protocol: "TCP"}}, - field.ErrorTypeInvalid, - "name", "15", - }, - "name contains invalid characters": { - []api.ContainerPort{{Name: "a.b.c", ContainerPort: 80, Protocol: "TCP"}}, - field.ErrorTypeInvalid, - "name", "alpha-numeric", - }, - "name is a number": { - []api.ContainerPort{{Name: "80", ContainerPort: 80, Protocol: "TCP"}}, - field.ErrorTypeInvalid, - "name", "at least one letter", - }, - "name not unique": { - []api.ContainerPort{ - {Name: "abc", ContainerPort: 80, Protocol: "TCP"}, - {Name: "abc", ContainerPort: 81, Protocol: "TCP"}, - }, - field.ErrorTypeDuplicate, - "[1].name", "", - }, - "zero container port": { - []api.ContainerPort{{ContainerPort: 0, Protocol: "TCP"}}, - field.ErrorTypeRequired, - "containerPort", "", - }, - "invalid container port": { - []api.ContainerPort{{ContainerPort: 65536, Protocol: "TCP"}}, - field.ErrorTypeInvalid, - "containerPort", "between", - }, - "invalid host port": { - []api.ContainerPort{{ContainerPort: 80, HostPort: 65536, Protocol: "TCP"}}, - field.ErrorTypeInvalid, - "hostPort", "between", - }, - "invalid protocol case": { - []api.ContainerPort{{ContainerPort: 80, Protocol: "tcp"}}, - field.ErrorTypeNotSupported, - "protocol", `supported values: "TCP", "UDP"`, - }, - "invalid protocol": { - []api.ContainerPort{{ContainerPort: 80, Protocol: "ICMP"}}, - field.ErrorTypeNotSupported, - "protocol", `supported values: "TCP", "UDP"`, - }, - "protocol required": { - []api.ContainerPort{{Name: "abc", ContainerPort: 80}}, - field.ErrorTypeRequired, - "protocol", "", - }, - } - for k, v := range errorCases { - errs := validateContainerPorts(v.P, field.NewPath("field")) - if len(errs) == 0 { - t.Errorf("expected failure for %s", k) - } - for i := range errs { - if errs[i].Type != v.T { - t.Errorf("%s: expected error to have type %q: %q", k, v.T, errs[i].Type) - } - if !strings.Contains(errs[i].Field, v.F) { - t.Errorf("%s: expected error field %q: %q", k, v.F, errs[i].Field) - } - if !strings.Contains(errs[i].Detail, v.D) { - t.Errorf("%s: expected error detail %q, got %q", k, v.D, errs[i].Detail) - } - } - } -} - -func TestLocalStorageEnvWithFeatureGate(t *testing.T) { - testCases := []api.EnvVar{ - { - Name: "ephemeral-storage-limits", - ValueFrom: &api.EnvVarSource{ - ResourceFieldRef: &api.ResourceFieldSelector{ - ContainerName: "test-container", - Resource: "limits.ephemeral-storage", - }, - }, - }, - { - Name: "ephemeral-storage-requests", - ValueFrom: &api.EnvVarSource{ - ResourceFieldRef: &api.ResourceFieldSelector{ - ContainerName: "test-container", - Resource: "requests.ephemeral-storage", - }, - }, - }, - } - // Enable alpha feature LocalStorageCapacityIsolation - err := utilfeature.DefaultFeatureGate.Set("LocalStorageCapacityIsolation=true") - if err != nil { - t.Errorf("Failed to enable feature gate for LocalStorageCapacityIsolation: %v", err) - return - } - for _, testCase := range testCases { - if errs := validateEnvVarValueFrom(testCase, field.NewPath("field")); len(errs) != 0 { - t.Errorf("expected success, got: %v", errs) - } - } - - // Disable alpha feature LocalStorageCapacityIsolation - err = utilfeature.DefaultFeatureGate.Set("LocalStorageCapacityIsolation=false") - if err != nil { - t.Errorf("Failed to disable feature gate for LocalStorageCapacityIsolation: %v", err) - return - } - for _, testCase := range testCases { - if errs := validateEnvVarValueFrom(testCase, field.NewPath("field")); len(errs) == 0 { - t.Errorf("expected failure for %v", testCase.Name) - } - } -} - -func TestValidateEnv(t *testing.T) { - successCase := []api.EnvVar{ - {Name: "abc", Value: "value"}, - {Name: "ABC", Value: "value"}, - {Name: "AbC_123", Value: "value"}, - {Name: "abc", Value: ""}, - {Name: "a.b.c", Value: "value"}, - {Name: "a-b-c", Value: "value"}, - { - Name: "abc", - ValueFrom: &api.EnvVarSource{ - FieldRef: &api.ObjectFieldSelector{ - APIVersion: api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), - FieldPath: "metadata.name", - }, - }, - }, - { - Name: "abc", - ValueFrom: &api.EnvVarSource{ - FieldRef: &api.ObjectFieldSelector{ - APIVersion: api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), - FieldPath: "metadata.namespace", - }, - }, - }, - { - Name: "abc", - ValueFrom: &api.EnvVarSource{ - FieldRef: &api.ObjectFieldSelector{ - APIVersion: api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), - FieldPath: "metadata.uid", - }, - }, - }, - { - Name: "abc", - ValueFrom: &api.EnvVarSource{ - FieldRef: &api.ObjectFieldSelector{ - APIVersion: api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), - FieldPath: "spec.nodeName", - }, - }, - }, - { - Name: "abc", - ValueFrom: &api.EnvVarSource{ - FieldRef: &api.ObjectFieldSelector{ - APIVersion: api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), - FieldPath: "spec.serviceAccountName", - }, - }, - }, - { - Name: "abc", - ValueFrom: &api.EnvVarSource{ - FieldRef: &api.ObjectFieldSelector{ - APIVersion: api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), - FieldPath: "status.hostIP", - }, - }, - }, - { - Name: "abc", - ValueFrom: &api.EnvVarSource{ - FieldRef: &api.ObjectFieldSelector{ - APIVersion: api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), - FieldPath: "status.podIP", - }, - }, - }, - { - Name: "secret_value", - ValueFrom: &api.EnvVarSource{ - SecretKeyRef: &api.SecretKeySelector{ - LocalObjectReference: api.LocalObjectReference{ - Name: "some-secret", - }, - Key: "secret-key", - }, - }, - }, - { - Name: "ENV_VAR_1", - ValueFrom: &api.EnvVarSource{ - ConfigMapKeyRef: &api.ConfigMapKeySelector{ - LocalObjectReference: api.LocalObjectReference{ - Name: "some-config-map", - }, - Key: "some-key", - }, - }, - }, - } - if errs := ValidateEnv(successCase, field.NewPath("field")); len(errs) != 0 { - t.Errorf("expected success, got: %v", errs) - } - - errorCases := []struct { - name string - envs []api.EnvVar - expectedError string - }{ - { - name: "zero-length name", - envs: []api.EnvVar{{Name: ""}}, - expectedError: "[0].name: Required value", - }, - { - name: "illegal character", - envs: []api.EnvVar{{Name: "a!b"}}, - expectedError: `[0].name: Invalid value: "a!b": ` + envVarNameErrMsg, - }, - { - name: "dot only", - envs: []api.EnvVar{{Name: "."}}, - expectedError: `[0].name: Invalid value: ".": must not be`, - }, - { - name: "double dots only", - envs: []api.EnvVar{{Name: ".."}}, - expectedError: `[0].name: Invalid value: "..": must not be`, - }, - { - name: "leading double dots", - envs: []api.EnvVar{{Name: "..abc"}}, - expectedError: `[0].name: Invalid value: "..abc": must not start with`, - }, - { - name: "value and valueFrom specified", - envs: []api.EnvVar{{ - Name: "abc", - Value: "foo", - ValueFrom: &api.EnvVarSource{ - FieldRef: &api.ObjectFieldSelector{ - APIVersion: api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), - FieldPath: "metadata.name", - }, - }, - }}, - expectedError: "[0].valueFrom: Invalid value: \"\": may not be specified when `value` is not empty", - }, - { - name: "valueFrom without a source", - envs: []api.EnvVar{{ - Name: "abc", - ValueFrom: &api.EnvVarSource{}, - }}, - expectedError: "[0].valueFrom: Invalid value: \"\": must specify one of: `fieldRef`, `resourceFieldRef`, `configMapKeyRef` or `secretKeyRef`", - }, - { - name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified", - envs: []api.EnvVar{{ - Name: "abc", - ValueFrom: &api.EnvVarSource{ - FieldRef: &api.ObjectFieldSelector{ - APIVersion: api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), - FieldPath: "metadata.name", - }, - SecretKeyRef: &api.SecretKeySelector{ - LocalObjectReference: api.LocalObjectReference{ - Name: "a-secret", - }, - Key: "a-key", - }, - }, - }}, - expectedError: "[0].valueFrom: Invalid value: \"\": may not have more than one field specified at a time", - }, - { - name: "valueFrom.fieldRef and valueFrom.configMapKeyRef set", - envs: []api.EnvVar{{ - Name: "some_var_name", - ValueFrom: &api.EnvVarSource{ - FieldRef: &api.ObjectFieldSelector{ - APIVersion: api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), - FieldPath: "metadata.name", - }, - ConfigMapKeyRef: &api.ConfigMapKeySelector{ - LocalObjectReference: api.LocalObjectReference{ - Name: "some-config-map", - }, - Key: "some-key", - }, - }, - }}, - expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`, - }, - { - name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified", - envs: []api.EnvVar{{ - Name: "abc", - ValueFrom: &api.EnvVarSource{ - FieldRef: &api.ObjectFieldSelector{ - APIVersion: api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), - FieldPath: "metadata.name", - }, - SecretKeyRef: &api.SecretKeySelector{ - LocalObjectReference: api.LocalObjectReference{ - Name: "a-secret", - }, - Key: "a-key", - }, - ConfigMapKeyRef: &api.ConfigMapKeySelector{ - LocalObjectReference: api.LocalObjectReference{ - Name: "some-config-map", - }, - Key: "some-key", - }, - }, - }}, - expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`, - }, - { - name: "valueFrom.secretKeyRef.name invalid", - envs: []api.EnvVar{{ - Name: "abc", - ValueFrom: &api.EnvVarSource{ - SecretKeyRef: &api.SecretKeySelector{ - LocalObjectReference: api.LocalObjectReference{ - Name: "$%^&*#", - }, - Key: "a-key", - }, - }, - }}, - }, - { - name: "valueFrom.configMapKeyRef.name invalid", - envs: []api.EnvVar{{ - Name: "abc", - ValueFrom: &api.EnvVarSource{ - ConfigMapKeyRef: &api.ConfigMapKeySelector{ - LocalObjectReference: api.LocalObjectReference{ - Name: "$%^&*#", - }, - Key: "some-key", - }, - }, - }}, - }, - { - name: "missing FieldPath on ObjectFieldSelector", - envs: []api.EnvVar{{ - Name: "abc", - ValueFrom: &api.EnvVarSource{ - FieldRef: &api.ObjectFieldSelector{ - APIVersion: api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), - }, - }, - }}, - expectedError: `[0].valueFrom.fieldRef.fieldPath: Required value`, - }, - { - name: "missing APIVersion on ObjectFieldSelector", - envs: []api.EnvVar{{ - Name: "abc", - ValueFrom: &api.EnvVarSource{ - FieldRef: &api.ObjectFieldSelector{ - FieldPath: "metadata.name", - }, - }, - }}, - expectedError: `[0].valueFrom.fieldRef.apiVersion: Required value`, - }, - { - name: "invalid fieldPath", - envs: []api.EnvVar{{ - Name: "abc", - ValueFrom: &api.EnvVarSource{ - FieldRef: &api.ObjectFieldSelector{ - FieldPath: "metadata.whoops", - APIVersion: api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), - }, - }, - }}, - expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.whoops": error converting fieldPath`, - }, - { - name: "invalid fieldPath labels", - envs: []api.EnvVar{{ - Name: "labels", - ValueFrom: &api.EnvVarSource{ - FieldRef: &api.ObjectFieldSelector{ - FieldPath: "metadata.labels", - APIVersion: "v1", - }, - }, - }}, - expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.labels": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP"`, - }, - { - name: "invalid fieldPath annotations", - envs: []api.EnvVar{{ - Name: "abc", - ValueFrom: &api.EnvVarSource{ - FieldRef: &api.ObjectFieldSelector{ - FieldPath: "metadata.annotations", - APIVersion: "v1", - }, - }, - }}, - expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.annotations": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP"`, - }, - { - name: "unsupported fieldPath", - envs: []api.EnvVar{{ - Name: "abc", - ValueFrom: &api.EnvVarSource{ - FieldRef: &api.ObjectFieldSelector{ - FieldPath: "status.phase", - APIVersion: api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), - }, - }, - }}, - expectedError: `valueFrom.fieldRef.fieldPath: Unsupported value: "status.phase": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP"`, - }, - } - for _, tc := range errorCases { - if errs := ValidateEnv(tc.envs, field.NewPath("field")); len(errs) == 0 { - t.Errorf("expected failure for %s", tc.name) - } else { - for i := range errs { - str := errs[i].Error() - if str != "" && !strings.Contains(str, tc.expectedError) { - t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str) - } - } - } - } -} - -func TestValidateEnvFrom(t *testing.T) { - successCase := []api.EnvFromSource{ - { - ConfigMapRef: &api.ConfigMapEnvSource{ - LocalObjectReference: api.LocalObjectReference{Name: "abc"}, - }, - }, - { - Prefix: "pre_", - ConfigMapRef: &api.ConfigMapEnvSource{ - LocalObjectReference: api.LocalObjectReference{Name: "abc"}, - }, - }, - { - Prefix: "a.b", - ConfigMapRef: &api.ConfigMapEnvSource{ - LocalObjectReference: api.LocalObjectReference{Name: "abc"}, - }, - }, - { - SecretRef: &api.SecretEnvSource{ - LocalObjectReference: api.LocalObjectReference{Name: "abc"}, - }, - }, - { - Prefix: "pre_", - SecretRef: &api.SecretEnvSource{ - LocalObjectReference: api.LocalObjectReference{Name: "abc"}, - }, - }, - { - Prefix: "a.b", - SecretRef: &api.SecretEnvSource{ - LocalObjectReference: api.LocalObjectReference{Name: "abc"}, - }, - }, - } - if errs := ValidateEnvFrom(successCase, field.NewPath("field")); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - - errorCases := []struct { - name string - envs []api.EnvFromSource - expectedError string - }{ - { - name: "zero-length name", - envs: []api.EnvFromSource{ - { - ConfigMapRef: &api.ConfigMapEnvSource{ - LocalObjectReference: api.LocalObjectReference{Name: ""}}, - }, - }, - expectedError: "field[0].configMapRef.name: Required value", - }, - { - name: "invalid name", - envs: []api.EnvFromSource{ - { - ConfigMapRef: &api.ConfigMapEnvSource{ - LocalObjectReference: api.LocalObjectReference{Name: "$"}}, - }, - }, - expectedError: "field[0].configMapRef.name: Invalid value", - }, - { - name: "invalid prefix", - envs: []api.EnvFromSource{ - { - Prefix: "a!b", - ConfigMapRef: &api.ConfigMapEnvSource{ - LocalObjectReference: api.LocalObjectReference{Name: "abc"}}, - }, - }, - expectedError: `field[0].prefix: Invalid value: "a!b": ` + envVarNameErrMsg, - }, - { - name: "zero-length name", - envs: []api.EnvFromSource{ - { - SecretRef: &api.SecretEnvSource{ - LocalObjectReference: api.LocalObjectReference{Name: ""}}, - }, - }, - expectedError: "field[0].secretRef.name: Required value", - }, - { - name: "invalid name", - envs: []api.EnvFromSource{ - { - SecretRef: &api.SecretEnvSource{ - LocalObjectReference: api.LocalObjectReference{Name: "&"}}, - }, - }, - expectedError: "field[0].secretRef.name: Invalid value", - }, - { - name: "invalid prefix", - envs: []api.EnvFromSource{ - { - Prefix: "a!b", - SecretRef: &api.SecretEnvSource{ - LocalObjectReference: api.LocalObjectReference{Name: "abc"}}, - }, - }, - expectedError: `field[0].prefix: Invalid value: "a!b": ` + envVarNameErrMsg, - }, - { - name: "no refs", - envs: []api.EnvFromSource{ - {}, - }, - expectedError: "field: Invalid value: \"\": must specify one of: `configMapRef` or `secretRef`", - }, - { - name: "multiple refs", - envs: []api.EnvFromSource{ - { - SecretRef: &api.SecretEnvSource{ - LocalObjectReference: api.LocalObjectReference{Name: "abc"}}, - ConfigMapRef: &api.ConfigMapEnvSource{ - LocalObjectReference: api.LocalObjectReference{Name: "abc"}}, - }, - }, - expectedError: "field: Invalid value: \"\": may not have more than one field specified at a time", - }, - { - name: "invalid secret ref name", - envs: []api.EnvFromSource{ - { - SecretRef: &api.SecretEnvSource{ - LocalObjectReference: api.LocalObjectReference{Name: "$%^&*#"}}, - }, - }, - expectedError: "field[0].secretRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg, - }, - { - name: "invalid config ref name", - envs: []api.EnvFromSource{ - { - ConfigMapRef: &api.ConfigMapEnvSource{ - LocalObjectReference: api.LocalObjectReference{Name: "$%^&*#"}}, - }, - }, - expectedError: "field[0].configMapRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg, - }, - } - for _, tc := range errorCases { - if errs := ValidateEnvFrom(tc.envs, field.NewPath("field")); len(errs) == 0 { - t.Errorf("expected failure for %s", tc.name) - } else { - for i := range errs { - str := errs[i].Error() - if str != "" && !strings.Contains(str, tc.expectedError) { - t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str) - } - } - } - } -} - -func TestValidateVolumeMounts(t *testing.T) { - volumes := sets.NewString("abc", "123", "abc-123") - container := api.Container{ - SecurityContext: nil, - } - propagation := api.MountPropagationBidirectional - - successCase := []api.VolumeMount{ - {Name: "abc", MountPath: "/foo"}, - {Name: "123", MountPath: "/bar"}, - {Name: "abc-123", MountPath: "/baz"}, - {Name: "abc-123", MountPath: "/baa", SubPath: ""}, - {Name: "abc-123", MountPath: "/bab", SubPath: "baz"}, - {Name: "abc-123", MountPath: "/bac", SubPath: ".baz"}, - {Name: "abc-123", MountPath: "/bad", SubPath: "..baz"}, - } - if errs := ValidateVolumeMounts(successCase, volumes, &container, field.NewPath("field")); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - - errorCases := map[string][]api.VolumeMount{ - "empty name": {{Name: "", MountPath: "/foo"}}, - "name not found": {{Name: "", MountPath: "/foo"}}, - "empty mountpath": {{Name: "abc", MountPath: ""}}, - "relative mountpath": {{Name: "abc", MountPath: "bar"}}, - "mountpath collision": {{Name: "foo", MountPath: "/path/a"}, {Name: "bar", MountPath: "/path/a"}}, - "absolute subpath": {{Name: "abc", MountPath: "/bar", SubPath: "/baz"}}, - "subpath in ..": {{Name: "abc", MountPath: "/bar", SubPath: "../baz"}}, - "subpath contains ..": {{Name: "abc", MountPath: "/bar", SubPath: "baz/../bat"}}, - "subpath ends in ..": {{Name: "abc", MountPath: "/bar", SubPath: "./.."}}, - "disabled MountPropagation feature gate": {{Name: "abc", MountPath: "/bar", MountPropagation: &propagation}}, - } - for k, v := range errorCases { - if errs := ValidateVolumeMounts(v, volumes, &container, field.NewPath("field")); len(errs) == 0 { - t.Errorf("expected failure for %s", k) - } - } -} - -func TestValidateMountPropagation(t *testing.T) { - bTrue := true - bFalse := false - privilegedContainer := &api.Container{ - SecurityContext: &api.SecurityContext{ - Privileged: &bTrue, - }, - } - nonPrivilegedContainer := &api.Container{ - SecurityContext: &api.SecurityContext{ - Privileged: &bFalse, - }, - } - defaultContainer := &api.Container{} - - propagationBidirectional := api.MountPropagationBidirectional - propagationHostToContainer := api.MountPropagationHostToContainer - propagationInvalid := api.MountPropagationMode("invalid") - - tests := []struct { - mount api.VolumeMount - container *api.Container - expectError bool - }{ - { - // implicitly non-privileged container + no propagation - api.VolumeMount{Name: "foo", MountPath: "/foo"}, - defaultContainer, - false, - }, - { - // implicitly non-privileged container + HostToContainer - api.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer}, - defaultContainer, - false, - }, - { - // error: implicitly non-privileged container + Bidirectional - api.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional}, - defaultContainer, - true, - }, - { - // explicitly non-privileged container + no propagation - api.VolumeMount{Name: "foo", MountPath: "/foo"}, - nonPrivilegedContainer, - false, - }, - { - // explicitly non-privileged container + HostToContainer - api.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer}, - nonPrivilegedContainer, - false, - }, - { - // explicitly non-privileged container + HostToContainer - api.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional}, - nonPrivilegedContainer, - true, - }, - { - // privileged container + no propagation - api.VolumeMount{Name: "foo", MountPath: "/foo"}, - privilegedContainer, - false, - }, - { - // privileged container + HostToContainer - api.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer}, - privilegedContainer, - false, - }, - { - // privileged container + Bidirectional - api.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional}, - privilegedContainer, - false, - }, - { - // error: privileged container + invalid mount propagation - api.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationInvalid}, - privilegedContainer, - true, - }, - { - // no container + Bidirectional - api.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional}, - nil, - false, - }, - } - - // Enable MountPropagation for this test - priorityEnabled := utilfeature.DefaultFeatureGate.Enabled("MountPropagation") - defer func() { - var err error - // restoring the old value - if priorityEnabled { - err = utilfeature.DefaultFeatureGate.Set("MountPropagation=true") - } else { - err = utilfeature.DefaultFeatureGate.Set("MountPropagation=false") - } - if err != nil { - t.Errorf("Failed to restore feature gate for MountPropagation: %v", err) - } - }() - err := utilfeature.DefaultFeatureGate.Set("MountPropagation=true") - if err != nil { - t.Errorf("Failed to enable feature gate for MountPropagation: %v", err) - return - } - - for i, test := range tests { - volumes := sets.NewString("foo") - errs := ValidateVolumeMounts([]api.VolumeMount{test.mount}, volumes, test.container, field.NewPath("field")) - if test.expectError && len(errs) == 0 { - t.Errorf("test %d expected error, got none", i) - } - if !test.expectError && len(errs) != 0 { - t.Errorf("test %d expected success, got error: %v", i, errs) - } - } - -} - -func TestValidateProbe(t *testing.T) { - handler := api.Handler{Exec: &api.ExecAction{Command: []string{"echo"}}} - // These fields must be positive. - positiveFields := [...]string{"InitialDelaySeconds", "TimeoutSeconds", "PeriodSeconds", "SuccessThreshold", "FailureThreshold"} - successCases := []*api.Probe{nil} - for _, field := range positiveFields { - probe := &api.Probe{Handler: handler} - reflect.ValueOf(probe).Elem().FieldByName(field).SetInt(10) - successCases = append(successCases, probe) - } - - for _, p := range successCases { - if errs := validateProbe(p, field.NewPath("field")); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - } - - errorCases := []*api.Probe{{TimeoutSeconds: 10, InitialDelaySeconds: 10}} - for _, field := range positiveFields { - probe := &api.Probe{Handler: handler} - reflect.ValueOf(probe).Elem().FieldByName(field).SetInt(-10) - errorCases = append(errorCases, probe) - } - for _, p := range errorCases { - if errs := validateProbe(p, field.NewPath("field")); len(errs) == 0 { - t.Errorf("expected failure for %v", p) - } - } -} - -func TestValidateHandler(t *testing.T) { - successCases := []api.Handler{ - {Exec: &api.ExecAction{Command: []string{"echo"}}}, - {HTTPGet: &api.HTTPGetAction{Path: "/", Port: intstr.FromInt(1), Host: "", Scheme: "HTTP"}}, - {HTTPGet: &api.HTTPGetAction{Path: "/foo", Port: intstr.FromInt(65535), Host: "host", Scheme: "HTTP"}}, - {HTTPGet: &api.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP"}}, - {HTTPGet: &api.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []api.HTTPHeader{{Name: "Host", Value: "foo.example.com"}}}}, - {HTTPGet: &api.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []api.HTTPHeader{{Name: "X-Forwarded-For", Value: "1.2.3.4"}, {Name: "X-Forwarded-For", Value: "5.6.7.8"}}}}, - } - for _, h := range successCases { - if errs := validateHandler(&h, field.NewPath("field")); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - } - - errorCases := []api.Handler{ - {}, - {Exec: &api.ExecAction{Command: []string{}}}, - {HTTPGet: &api.HTTPGetAction{Path: "", Port: intstr.FromInt(0), Host: ""}}, - {HTTPGet: &api.HTTPGetAction{Path: "/foo", Port: intstr.FromInt(65536), Host: "host"}}, - {HTTPGet: &api.HTTPGetAction{Path: "", Port: intstr.FromString(""), Host: ""}}, - {HTTPGet: &api.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []api.HTTPHeader{{Name: "Host:", Value: "foo.example.com"}}}}, - {HTTPGet: &api.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []api.HTTPHeader{{Name: "X_Forwarded_For", Value: "foo.example.com"}}}}, - } - for _, h := range errorCases { - if errs := validateHandler(&h, field.NewPath("field")); len(errs) == 0 { - t.Errorf("expected failure for %#v", h) - } - } -} - -func TestValidatePullPolicy(t *testing.T) { - type T struct { - Container api.Container - ExpectedPolicy api.PullPolicy - } - testCases := map[string]T{ - "NotPresent1": { - api.Container{Name: "abc", Image: "image:latest", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, - api.PullIfNotPresent, - }, - "NotPresent2": { - api.Container{Name: "abc1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, - api.PullIfNotPresent, - }, - "Always1": { - api.Container{Name: "123", Image: "image:latest", ImagePullPolicy: "Always"}, - api.PullAlways, - }, - "Always2": { - api.Container{Name: "1234", Image: "image", ImagePullPolicy: "Always"}, - api.PullAlways, - }, - "Never1": { - api.Container{Name: "abc-123", Image: "image:latest", ImagePullPolicy: "Never"}, - api.PullNever, - }, - "Never2": { - api.Container{Name: "abc-1234", Image: "image", ImagePullPolicy: "Never"}, - api.PullNever, - }, - } - for k, v := range testCases { - ctr := &v.Container - errs := validatePullPolicy(ctr.ImagePullPolicy, field.NewPath("field")) - if len(errs) != 0 { - t.Errorf("case[%s] expected success, got %#v", k, errs) - } - if ctr.ImagePullPolicy != v.ExpectedPolicy { - t.Errorf("case[%s] expected policy %v, got %v", k, v.ExpectedPolicy, ctr.ImagePullPolicy) - } - } -} - -func getResourceLimits(cpu, memory string) api.ResourceList { - res := api.ResourceList{} - res[api.ResourceCPU] = resource.MustParse(cpu) - res[api.ResourceMemory] = resource.MustParse(memory) - return res -} - -func TestValidateContainers(t *testing.T) { - volumes := sets.String{} - capabilities.SetForTests(capabilities.Capabilities{ - AllowPrivileged: true, - }) - - successCase := []api.Container{ - {Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, - // backwards compatibility to ensure containers in pod template spec do not check for this - {Name: "def", Image: " ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, - {Name: "ghi", Image: " some ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, - {Name: "123", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, - {Name: "abc-123", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, - { - Name: "life-123", - Image: "image", - Lifecycle: &api.Lifecycle{ - PreStop: &api.Handler{ - Exec: &api.ExecAction{Command: []string{"ls", "-l"}}, - }, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - { - Name: "resources-test", - Image: "image", - Resources: api.ResourceRequirements{ - Limits: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), - api.ResourceName("my.org/resource"): resource.MustParse("10"), - }, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - { - Name: "resources-test-with-gpu-with-request", - Image: "image", - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), - api.ResourceName(api.ResourceNvidiaGPU): resource.MustParse("1"), - }, - Limits: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), - api.ResourceName(api.ResourceNvidiaGPU): resource.MustParse("1"), - }, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - { - Name: "resources-test-with-gpu-without-request", - Image: "image", - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), - }, - Limits: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), - api.ResourceName(api.ResourceNvidiaGPU): resource.MustParse("1"), - }, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - { - Name: "resources-request-limit-simple", - Image: "image", - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("8"), - }, - Limits: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - }, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - { - Name: "resources-request-limit-edge", - Image: "image", - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), - api.ResourceName("my.org/resource"): resource.MustParse("10"), - }, - Limits: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), - api.ResourceName("my.org/resource"): resource.MustParse("10"), - }, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - { - Name: "resources-request-limit-partials", - Image: "image", - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("9.5"), - api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), - }, - Limits: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName("my.org/resource"): resource.MustParse("10"), - }, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - { - Name: "resources-request", - Image: "image", - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("9.5"), - api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), - }, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - { - Name: "same-host-port-different-protocol", - Image: "image", - Ports: []api.ContainerPort{ - {ContainerPort: 80, HostPort: 80, Protocol: "TCP"}, - {ContainerPort: 80, HostPort: 80, Protocol: "UDP"}, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - { - Name: "fallback-to-logs-termination-message", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "FallbackToLogsOnError", - }, - { - Name: "file-termination-message", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - { - Name: "env-from-source", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - EnvFrom: []api.EnvFromSource{ - { - ConfigMapRef: &api.ConfigMapEnvSource{ - LocalObjectReference: api.LocalObjectReference{ - Name: "test", - }, - }, - }, - }, - }, - {Name: "abc-1234", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", SecurityContext: fakeValidSecurityContext(true)}, - } - if errs := validateContainers(successCase, volumes, field.NewPath("field")); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - - capabilities.SetForTests(capabilities.Capabilities{ - AllowPrivileged: false, - }) - errorCases := map[string][]api.Container{ - "zero-length name": {{Name: "", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - "zero-length-image": {{Name: "abc", Image: "", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - "name > 63 characters": {{Name: strings.Repeat("a", 64), Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - "name not a DNS label": {{Name: "a.b.c", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - "name not unique": { - {Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, - {Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, - }, - "zero-length image": {{Name: "abc", Image: "", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - "host port not unique": { - {Name: "abc", Image: "image", Ports: []api.ContainerPort{{ContainerPort: 80, HostPort: 80, Protocol: "TCP"}}, - ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, - {Name: "def", Image: "image", Ports: []api.ContainerPort{{ContainerPort: 81, HostPort: 80, Protocol: "TCP"}}, - ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, - }, - "invalid env var name": { - {Name: "abc", Image: "image", Env: []api.EnvVar{{Name: "ev!1"}}, ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, - }, - "unknown volume name": { - {Name: "abc", Image: "image", VolumeMounts: []api.VolumeMount{{Name: "anything", MountPath: "/foo"}}, - ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, - }, - "invalid lifecycle, no exec command.": { - { - Name: "life-123", - Image: "image", - Lifecycle: &api.Lifecycle{ - PreStop: &api.Handler{ - Exec: &api.ExecAction{}, - }, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - "invalid lifecycle, no http path.": { - { - Name: "life-123", - Image: "image", - Lifecycle: &api.Lifecycle{ - PreStop: &api.Handler{ - HTTPGet: &api.HTTPGetAction{}, - }, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - "invalid lifecycle, no tcp socket port.": { - { - Name: "life-123", - Image: "image", - Lifecycle: &api.Lifecycle{ - PreStop: &api.Handler{ - TCPSocket: &api.TCPSocketAction{}, - }, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - "invalid lifecycle, zero tcp socket port.": { - { - Name: "life-123", - Image: "image", - Lifecycle: &api.Lifecycle{ - PreStop: &api.Handler{ - TCPSocket: &api.TCPSocketAction{ - Port: intstr.FromInt(0), - }, - }, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - "invalid lifecycle, no action.": { - { - Name: "life-123", - Image: "image", - Lifecycle: &api.Lifecycle{ - PreStop: &api.Handler{}, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - "invalid liveness probe, no tcp socket port.": { - { - Name: "life-123", - Image: "image", - LivenessProbe: &api.Probe{ - Handler: api.Handler{ - TCPSocket: &api.TCPSocketAction{}, - }, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - "invalid liveness probe, no action.": { - { - Name: "life-123", - Image: "image", - LivenessProbe: &api.Probe{ - Handler: api.Handler{}, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - "invalid message termination policy": { - { - Name: "life-123", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "Unknown", - }, - }, - "empty message termination policy": { - { - Name: "life-123", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "", - }, - }, - "privilege disabled": { - {Name: "abc", Image: "image", SecurityContext: fakeValidSecurityContext(true)}, - }, - "invalid compute resource": { - { - Name: "abc-123", - Image: "image", - Resources: api.ResourceRequirements{ - Limits: api.ResourceList{ - "disk": resource.MustParse("10G"), - }, - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - "Resource CPU invalid": { - { - Name: "abc-123", - Image: "image", - Resources: api.ResourceRequirements{ - Limits: getResourceLimits("-10", "0"), - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - "Resource Requests CPU invalid": { - { - Name: "abc-123", - Image: "image", - Resources: api.ResourceRequirements{ - Requests: getResourceLimits("-10", "0"), - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - "Resource Memory invalid": { - { - Name: "abc-123", - Image: "image", - Resources: api.ResourceRequirements{ - Limits: getResourceLimits("0", "-10"), - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - "Resource GPU limit must match request": { - { - Name: "gpu-resource-request-limit", - Image: "image", - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), - api.ResourceName(api.ResourceNvidiaGPU): resource.MustParse("0"), - }, - Limits: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), - api.ResourceName(api.ResourceNvidiaGPU): resource.MustParse("1"), - }, - }, - TerminationMessagePolicy: "File", - ImagePullPolicy: "IfNotPresent", - }, - }, - "Resource GPU invalid setting only request": { - { - Name: "gpu-resource-request-limit", - Image: "image", - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), - api.ResourceName(api.ResourceNvidiaGPU): resource.MustParse("1"), - }, - }, - TerminationMessagePolicy: "File", - ImagePullPolicy: "IfNotPresent", - }, - }, - "Request limit simple invalid": { - { - Name: "abc-123", - Image: "image", - Resources: api.ResourceRequirements{ - Limits: getResourceLimits("5", "3"), - Requests: getResourceLimits("6", "3"), - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - "Request limit multiple invalid": { - { - Name: "abc-123", - Image: "image", - Resources: api.ResourceRequirements{ - Limits: getResourceLimits("5", "3"), - Requests: getResourceLimits("6", "4"), - }, - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - }, - }, - "Invalid env from": { - { - Name: "env-from-source", - Image: "image", - ImagePullPolicy: "IfNotPresent", - TerminationMessagePolicy: "File", - EnvFrom: []api.EnvFromSource{ - { - ConfigMapRef: &api.ConfigMapEnvSource{ - LocalObjectReference: api.LocalObjectReference{ - Name: "$%^&*#", - }, - }, - }, - }, - }, - }, - } - for k, v := range errorCases { - if errs := validateContainers(v, volumes, field.NewPath("field")); len(errs) == 0 { - t.Errorf("expected failure for %s", k) - } - } -} - -func TestValidateRestartPolicy(t *testing.T) { - successCases := []api.RestartPolicy{ - api.RestartPolicyAlways, - api.RestartPolicyOnFailure, - api.RestartPolicyNever, - } - for _, policy := range successCases { - if errs := validateRestartPolicy(&policy, field.NewPath("field")); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - } - - errorCases := []api.RestartPolicy{"", "newpolicy"} - - for k, policy := range errorCases { - if errs := validateRestartPolicy(&policy, field.NewPath("field")); len(errs) == 0 { - t.Errorf("expected failure for %d", k) - } - } -} - -func TestValidateDNSPolicy(t *testing.T) { - successCases := []api.DNSPolicy{api.DNSClusterFirst, api.DNSDefault, api.DNSPolicy(api.DNSClusterFirst)} - for _, policy := range successCases { - if errs := validateDNSPolicy(&policy, field.NewPath("field")); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - } - - errorCases := []api.DNSPolicy{api.DNSPolicy("invalid")} - for _, policy := range errorCases { - if errs := validateDNSPolicy(&policy, field.NewPath("field")); len(errs) == 0 { - t.Errorf("expected failure for %v", policy) - } - } -} - -func TestValidatePodSpec(t *testing.T) { - activeDeadlineSeconds := int64(30) - activeDeadlineSecondsMax := int64(math.MaxInt32) - - minUserID := int64(0) - maxUserID := int64(2147483647) - minGroupID := int64(0) - maxGroupID := int64(2147483647) - - priorityEnabled := utilfeature.DefaultFeatureGate.Enabled("PodPriority") - defer func() { - var err error - // restoring the old value - if priorityEnabled { - err = utilfeature.DefaultFeatureGate.Set("PodPriority=true") - } else { - err = utilfeature.DefaultFeatureGate.Set("PodPriority=false") - } - if err != nil { - t.Errorf("Failed to restore feature gate for PodPriority: %v", err) - } - }() - err := utilfeature.DefaultFeatureGate.Set("PodPriority=true") - if err != nil { - t.Errorf("Failed to enable feature gate for PodPriority: %v", err) - return - } - successCases := []api.PodSpec{ - { // Populate basic fields, leave defaults for most. - Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}}, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - { // Populate all fields. - Volumes: []api.Volume{ - {Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}, - }, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - InitContainers: []api.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - NodeSelector: map[string]string{ - "key": "value", - }, - NodeName: "foobar", - DNSPolicy: api.DNSClusterFirst, - ActiveDeadlineSeconds: &activeDeadlineSeconds, - ServiceAccountName: "acct", - }, - { // Populate all fields with larger active deadline. - Volumes: []api.Volume{ - {Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}, - }, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - InitContainers: []api.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - NodeSelector: map[string]string{ - "key": "value", - }, - NodeName: "foobar", - DNSPolicy: api.DNSClusterFirst, - ActiveDeadlineSeconds: &activeDeadlineSecondsMax, - ServiceAccountName: "acct", - }, - { // Populate HostNetwork. - Containers: []api.Container{ - {Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", - Ports: []api.ContainerPort{ - {HostPort: 8080, ContainerPort: 8080, Protocol: "TCP"}}, - }, - }, - SecurityContext: &api.PodSecurityContext{ - HostNetwork: true, - }, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - { // Populate RunAsUser SupplementalGroups FSGroup with minID 0 - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - SecurityContext: &api.PodSecurityContext{ - SupplementalGroups: []int64{minGroupID}, - RunAsUser: &minUserID, - FSGroup: &minGroupID, - }, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - { // Populate RunAsUser SupplementalGroups FSGroup with maxID 2147483647 - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - SecurityContext: &api.PodSecurityContext{ - SupplementalGroups: []int64{maxGroupID}, - RunAsUser: &maxUserID, - FSGroup: &maxGroupID, - }, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - { // Populate HostIPC. - SecurityContext: &api.PodSecurityContext{ - HostIPC: true, - }, - Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}}, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - { // Populate HostPID. - SecurityContext: &api.PodSecurityContext{ - HostPID: true, - }, - Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}}, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - { // Populate Affinity. - Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}}, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - { // Populate HostAliases. - HostAliases: []api.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1", "host2"}}}, - Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}}, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - { // Populate HostAliases with `foo.bar` hostnames. - HostAliases: []api.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1.foo", "host2.bar"}}}, - Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}}, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - { // Populate HostAliases with HostNetwork. - HostAliases: []api.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1.foo", "host2.bar"}}}, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - SecurityContext: &api.PodSecurityContext{ - HostNetwork: true, - }, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - { // Populate PriorityClassName. - Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}}, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - PriorityClassName: "valid-name", - }, - } - for i := range successCases { - if errs := ValidatePodSpec(&successCases[i], field.NewPath("field")); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - } - - activeDeadlineSeconds = int64(0) - activeDeadlineSecondsTooLarge := int64(math.MaxInt32 + 1) - - minUserID = int64(-1) - maxUserID = int64(2147483648) - minGroupID = int64(-1) - maxGroupID = int64(2147483648) - - failureCases := map[string]api.PodSpec{ - "bad volume": { - Volumes: []api.Volume{{}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - }, - "no containers": { - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - "bad container": { - Containers: []api.Container{{}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - "bad init container": { - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - InitContainers: []api.Container{{}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - "bad DNS policy": { - DNSPolicy: api.DNSPolicy("invalid"), - RestartPolicy: api.RestartPolicyAlways, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - }, - "bad service account name": { - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - ServiceAccountName: "invalidName", - }, - "bad restart policy": { - RestartPolicy: "UnknowPolicy", - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - }, - "with hostNetwork hostPort not equal to containerPort": { - Containers: []api.Container{ - {Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", Ports: []api.ContainerPort{ - {HostPort: 8080, ContainerPort: 2600, Protocol: "TCP"}}, - }, - }, - SecurityContext: &api.PodSecurityContext{ - HostNetwork: true, - }, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - "with hostAliases with invalid IP": { - SecurityContext: &api.PodSecurityContext{ - HostNetwork: false, - }, - HostAliases: []api.HostAlias{{IP: "999.999.999.999", Hostnames: []string{"host1", "host2"}}}, - }, - "with hostAliases with invalid hostname": { - SecurityContext: &api.PodSecurityContext{ - HostNetwork: false, - }, - HostAliases: []api.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"@#$^#@#$"}}}, - }, - "bad supplementalGroups large than math.MaxInt32": { - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - SecurityContext: &api.PodSecurityContext{ - HostNetwork: false, - SupplementalGroups: []int64{maxGroupID, 1234}, - }, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - "bad supplementalGroups less than 0": { - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - SecurityContext: &api.PodSecurityContext{ - HostNetwork: false, - SupplementalGroups: []int64{minGroupID, 1234}, - }, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - "bad runAsUser large than math.MaxInt32": { - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - SecurityContext: &api.PodSecurityContext{ - HostNetwork: false, - RunAsUser: &maxUserID, - }, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - "bad runAsUser less than 0": { - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - SecurityContext: &api.PodSecurityContext{ - HostNetwork: false, - RunAsUser: &minUserID, - }, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - "bad fsGroup large than math.MaxInt32": { - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - SecurityContext: &api.PodSecurityContext{ - HostNetwork: false, - FSGroup: &maxGroupID, - }, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - "bad fsGroup less than 0": { - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - SecurityContext: &api.PodSecurityContext{ - HostNetwork: false, - FSGroup: &minGroupID, - }, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - "bad-active-deadline-seconds": { - Volumes: []api.Volume{ - {Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}, - }, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - NodeSelector: map[string]string{ - "key": "value", - }, - NodeName: "foobar", - DNSPolicy: api.DNSClusterFirst, - ActiveDeadlineSeconds: &activeDeadlineSeconds, - }, - "active-deadline-seconds-too-large": { - Volumes: []api.Volume{ - {Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}, - }, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - NodeSelector: map[string]string{ - "key": "value", - }, - NodeName: "foobar", - DNSPolicy: api.DNSClusterFirst, - ActiveDeadlineSeconds: &activeDeadlineSecondsTooLarge, - }, - "bad nodeName": { - NodeName: "node name", - Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}}, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - "bad PriorityClassName": { - Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}}, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - PriorityClassName: "InvalidName", - }, - "with privileged and allowPrivilegeEscalation false": { - Containers: []api.Container{ - { - Name: "ctr", - Image: "image", - ImagePullPolicy: "IfNotPresent", - Ports: []api.ContainerPort{ - {HostPort: 8080, ContainerPort: 2600, Protocol: "TCP"}}, - SecurityContext: &api.SecurityContext{ - Privileged: boolPtr(true), - AllowPrivilegeEscalation: boolPtr(false), - }, - }, - }, - }, - "with CAP_SYS_ADMIN and allowPrivilegeEscalation false": { - Containers: []api.Container{ - { - Name: "ctr", - Image: "image", - ImagePullPolicy: "IfNotPresent", - Ports: []api.ContainerPort{ - {HostPort: 8080, ContainerPort: 2600, Protocol: "TCP"}}, - SecurityContext: &api.SecurityContext{ - Capabilities: &api.Capabilities{ - Add: []api.Capability{"CAP_SYS_ADMIN"}, - }, - AllowPrivilegeEscalation: boolPtr(false), - }, - }, - }, - }, - } - for k, v := range failureCases { - if errs := ValidatePodSpec(&v, field.NewPath("field")); len(errs) == 0 { - t.Errorf("expected failure for %q", k) - } - } - - err = utilfeature.DefaultFeatureGate.Set("PodPriority=false") - if err != nil { - t.Errorf("Failed to disable feature gate for PodPriority: %v", err) - return - } - priority := int32(100) - featuregatedCases := map[string]api.PodSpec{ - "set PriorityClassName": { - Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}}, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - PriorityClassName: "valid-name", - }, - "set Priority": { - Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}}, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - Priority: &priority, - }, - } - for k, v := range featuregatedCases { - if errs := ValidatePodSpec(&v, field.NewPath("field")); len(errs) == 0 { - t.Errorf("expected failure due to gated feature: %q", k) - } - } -} - -func extendPodSpecwithTolerations(in api.PodSpec, tolerations []api.Toleration) api.PodSpec { - var out api.PodSpec - out.Containers = in.Containers - out.RestartPolicy = in.RestartPolicy - out.DNSPolicy = in.DNSPolicy - out.Tolerations = tolerations - return out -} - -func TestValidatePod(t *testing.T) { - validPodSpec := func(affinity *api.Affinity) api.PodSpec { - spec := api.PodSpec{ - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - } - if affinity != nil { - spec.Affinity = affinity - } - return spec - } - - successCases := []api.Pod{ - { // Basic fields. - ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, - Spec: api.PodSpec{ - Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}}, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - }, - { // Just about everything. - ObjectMeta: metav1.ObjectMeta{Name: "abc.123.do-re-mi", Namespace: "ns"}, - Spec: api.PodSpec{ - Volumes: []api.Volume{ - {Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}, - }, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - NodeSelector: map[string]string{ - "key": "value", - }, - NodeName: "foobar", - }, - }, - { // Serialized node affinity requirements. - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - }, - Spec: validPodSpec( - // TODO: Uncomment and move this block and move inside NodeAffinity once - // RequiredDuringSchedulingRequiredDuringExecution is implemented - // RequiredDuringSchedulingRequiredDuringExecution: &api.NodeSelector{ - // NodeSelectorTerms: []api.NodeSelectorTerm{ - // { - // MatchExpressions: []api.NodeSelectorRequirement{ - // { - // Key: "key1", - // Operator: api.NodeSelectorOpExists - // }, - // }, - // }, - // }, - // }, - &api.Affinity{ - NodeAffinity: &api.NodeAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{ - NodeSelectorTerms: []api.NodeSelectorTerm{ - { - MatchExpressions: []api.NodeSelectorRequirement{ - { - Key: "key2", - Operator: api.NodeSelectorOpIn, - Values: []string{"value1", "value2"}, - }, - }, - }, - }, - }, - PreferredDuringSchedulingIgnoredDuringExecution: []api.PreferredSchedulingTerm{ - { - Weight: 10, - Preference: api.NodeSelectorTerm{ - MatchExpressions: []api.NodeSelectorRequirement{ - { - Key: "foo", - Operator: api.NodeSelectorOpIn, - Values: []string{"bar"}, - }, - }, - }, - }, - }, - }, - }, - ), - }, - { // Serialized pod affinity in affinity requirements in annotations. - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - // TODO: Uncomment and move this block into Annotations map once - // RequiredDuringSchedulingRequiredDuringExecution is implemented - // "requiredDuringSchedulingRequiredDuringExecution": [{ - // "labelSelector": { - // "matchExpressions": [{ - // "key": "key2", - // "operator": "In", - // "values": ["value1", "value2"] - // }] - // }, - // "namespaces":["ns"], - // "topologyKey": "zone" - // }] - }, - Spec: validPodSpec(&api.Affinity{ - PodAffinity: &api.PodAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"value1", "value2"}, - }, - }, - }, - TopologyKey: "zone", - Namespaces: []string{"ns"}, - }, - }, - PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ - { - Weight: 10, - PodAffinityTerm: api.PodAffinityTerm{ - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: metav1.LabelSelectorOpNotIn, - Values: []string{"value1", "value2"}, - }, - }, - }, - Namespaces: []string{"ns"}, - TopologyKey: "region", - }, - }, - }, - }, - }), - }, - { // Serialized pod anti affinity with different Label Operators in affinity requirements in annotations. - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - // TODO: Uncomment and move this block into Annotations map once - // RequiredDuringSchedulingRequiredDuringExecution is implemented - // "requiredDuringSchedulingRequiredDuringExecution": [{ - // "labelSelector": { - // "matchExpressions": [{ - // "key": "key2", - // "operator": "In", - // "values": ["value1", "value2"] - // }] - // }, - // "namespaces":["ns"], - // "topologyKey": "zone" - // }] - }, - Spec: validPodSpec(&api.Affinity{ - PodAntiAffinity: &api.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - Namespaces: []string{"ns"}, - }, - }, - PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ - { - Weight: 10, - PodAffinityTerm: api.PodAffinityTerm{ - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: metav1.LabelSelectorOpDoesNotExist, - }, - }, - }, - Namespaces: []string{"ns"}, - TopologyKey: "region", - }, - }, - }, - }, - }), - }, - { // populate forgiveness tolerations with exists operator in annotations. - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - }, - Spec: extendPodSpecwithTolerations(validPodSpec(nil), []api.Toleration{{Key: "foo", Operator: "Exists", Value: "", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}}), - }, - { // populate forgiveness tolerations with equal operator in annotations. - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - }, - Spec: extendPodSpecwithTolerations(validPodSpec(nil), []api.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}}), - }, - { // populate tolerations equal operator in annotations. - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - }, - Spec: extendPodSpecwithTolerations(validPodSpec(nil), []api.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}), - }, - { // populate tolerations exists operator in annotations. - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - }, - Spec: validPodSpec(nil), - }, - { // empty key with Exists operator is OK for toleration, empty toleration key means match all taint keys. - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - }, - Spec: extendPodSpecwithTolerations(validPodSpec(nil), []api.Toleration{{Operator: "Exists", Effect: "NoSchedule"}}), - }, - { // empty operator is OK for toleration, defaults to Equal. - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - }, - Spec: extendPodSpecwithTolerations(validPodSpec(nil), []api.Toleration{{Key: "foo", Value: "bar", Effect: "NoSchedule"}}), - }, - { // empty effect is OK for toleration, empty toleration effect means match all taint effects. - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - }, - Spec: extendPodSpecwithTolerations(validPodSpec(nil), []api.Toleration{{Key: "foo", Operator: "Equal", Value: "bar"}}), - }, - { // negative tolerationSeconds is OK for toleration. - ObjectMeta: metav1.ObjectMeta{ - Name: "pod-forgiveness-invalid", - Namespace: "ns", - }, - Spec: extendPodSpecwithTolerations(validPodSpec(nil), []api.Toleration{{Key: "node.kubernetes.io/not-ready", Operator: "Exists", Effect: "NoExecute", TolerationSeconds: &[]int64{-2}[0]}}), - }, - { // docker default seccomp profile - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - Annotations: map[string]string{ - api.SeccompPodAnnotationKey: "docker/default", - }, - }, - Spec: validPodSpec(nil), - }, - { // unconfined seccomp profile - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - Annotations: map[string]string{ - api.SeccompPodAnnotationKey: "unconfined", - }, - }, - Spec: validPodSpec(nil), - }, - { // localhost seccomp profile - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - Annotations: map[string]string{ - api.SeccompPodAnnotationKey: "localhost/foo", - }, - }, - Spec: validPodSpec(nil), - }, - { // localhost seccomp profile for a container - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - Annotations: map[string]string{ - api.SeccompContainerAnnotationKeyPrefix + "foo": "localhost/foo", - }, - }, - Spec: validPodSpec(nil), - }, - { // default AppArmor profile for a container - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - Annotations: map[string]string{ - apparmor.ContainerAnnotationKeyPrefix + "ctr": apparmor.ProfileRuntimeDefault, - }, - }, - Spec: validPodSpec(nil), - }, - { // default AppArmor profile for an init container - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - Annotations: map[string]string{ - apparmor.ContainerAnnotationKeyPrefix + "init-ctr": apparmor.ProfileRuntimeDefault, - }, - }, - Spec: api.PodSpec{ - InitContainers: []api.Container{{Name: "init-ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - }, - { // localhost AppArmor profile for a container - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - Annotations: map[string]string{ - apparmor.ContainerAnnotationKeyPrefix + "ctr": apparmor.ProfileNamePrefix + "foo", - }, - }, - Spec: validPodSpec(nil), - }, - { // syntactically valid sysctls - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - Annotations: map[string]string{ - api.SysctlsPodAnnotationKey: "kernel.shmmni=32768,kernel.shmmax=1000000000", - api.UnsafeSysctlsPodAnnotationKey: "knet.ipv4.route.min_pmtu=1000", - }, - }, - Spec: validPodSpec(nil), - }, - { // valid opaque integer resources for init container - ObjectMeta: metav1.ObjectMeta{Name: "valid-opaque-int", Namespace: "ns"}, - Spec: api.PodSpec{ - InitContainers: []api.Container{ - { - Name: "valid-opaque-int", - Image: "image", - ImagePullPolicy: "IfNotPresent", - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - helper.OpaqueIntResourceName("A"): resource.MustParse("10"), - }, - Limits: api.ResourceList{ - helper.OpaqueIntResourceName("A"): resource.MustParse("20"), - }, - }, - TerminationMessagePolicy: "File", - }, - }, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - }, - { // valid opaque integer resources for regular container - ObjectMeta: metav1.ObjectMeta{Name: "valid-opaque-int", Namespace: "ns"}, - Spec: api.PodSpec{ - InitContainers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - Containers: []api.Container{ - { - Name: "valid-opaque-int", - Image: "image", - ImagePullPolicy: "IfNotPresent", - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - helper.OpaqueIntResourceName("A"): resource.MustParse("10"), - }, - Limits: api.ResourceList{ - helper.OpaqueIntResourceName("A"): resource.MustParse("20"), - }, - }, - TerminationMessagePolicy: "File", - }, - }, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - }, - } - for _, pod := range successCases { - if errs := ValidatePod(&pod); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - } - - errorCases := map[string]struct { - spec api.Pod - expectedError string - }{ - "bad name": { - expectedError: "metadata.name", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: "ns"}, - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - }, - }, - }, - "image whitespace": { - expectedError: "spec.containers[0].image", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"}, - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "ctr", Image: " ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - }, - }, - }, - "image leading and trailing whitespace": { - expectedError: "spec.containers[0].image", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"}, - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "ctr", Image: " something ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - }, - }, - }, - "bad namespace": { - expectedError: "metadata.namespace", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: ""}, - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - }, - }, - }, - "bad spec": { - expectedError: "spec.containers[0].name", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"}, - Spec: api.PodSpec{ - Containers: []api.Container{{}}, - }, - }, - }, - "bad label": { - expectedError: "NoUppercaseOrSpecialCharsLike=Equals", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "abc", - Namespace: "ns", - Labels: map[string]string{ - "NoUppercaseOrSpecialCharsLike=Equals": "bar", - }, - }, - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - }, - }, - }, - "invalid node selector requirement in node affinity, operator can't be null": { - expectedError: "spec.affinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].operator", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - }, - Spec: validPodSpec(&api.Affinity{ - NodeAffinity: &api.NodeAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{ - NodeSelectorTerms: []api.NodeSelectorTerm{ - { - MatchExpressions: []api.NodeSelectorRequirement{ - { - Key: "key1", - }, - }, - }, - }, - }, - }, - }), - }, - }, - "invalid preferredSchedulingTerm in node affinity, weight should be in range 1-100": { - expectedError: "must be in the range 1-100", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - }, - Spec: validPodSpec(&api.Affinity{ - NodeAffinity: &api.NodeAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []api.PreferredSchedulingTerm{ - { - Weight: 199, - Preference: api.NodeSelectorTerm{ - MatchExpressions: []api.NodeSelectorRequirement{ - { - Key: "foo", - Operator: api.NodeSelectorOpIn, - Values: []string{"bar"}, - }, - }, - }, - }, - }, - }, - }), - }, - }, - "invalid requiredDuringSchedulingIgnoredDuringExecution node selector, nodeSelectorTerms must have at least one term": { - expectedError: "spec.affinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - }, - Spec: validPodSpec(&api.Affinity{ - NodeAffinity: &api.NodeAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{ - NodeSelectorTerms: []api.NodeSelectorTerm{}, - }, - }, - }), - }, - }, - "invalid requiredDuringSchedulingIgnoredDuringExecution node selector term, matchExpressions must have at least one node selector requirement": { - expectedError: "spec.affinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - }, - Spec: validPodSpec(&api.Affinity{ - NodeAffinity: &api.NodeAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{ - NodeSelectorTerms: []api.NodeSelectorTerm{ - { - MatchExpressions: []api.NodeSelectorRequirement{}, - }, - }, - }, - }, - }), - }, - }, - "invalid weight in preferredDuringSchedulingIgnoredDuringExecution in pod affinity annotations, weight should be in range 1-100": { - expectedError: "must be in the range 1-100", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - }, - Spec: validPodSpec(&api.Affinity{ - PodAffinity: &api.PodAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ - { - Weight: 109, - PodAffinityTerm: api.PodAffinityTerm{ - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: metav1.LabelSelectorOpNotIn, - Values: []string{"value1", "value2"}, - }, - }, - }, - Namespaces: []string{"ns"}, - TopologyKey: "region", - }, - }, - }, - }, - }), - }, - }, - "invalid labelSelector in preferredDuringSchedulingIgnoredDuringExecution in podaffinity annotations, values should be empty if the operator is Exists": { - expectedError: "spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.matchExpressions.matchExpressions[0].values", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - }, - Spec: validPodSpec(&api.Affinity{ - PodAntiAffinity: &api.PodAntiAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ - { - Weight: 10, - PodAffinityTerm: api.PodAffinityTerm{ - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: metav1.LabelSelectorOpExists, - Values: []string{"value1", "value2"}, - }, - }, - }, - Namespaces: []string{"ns"}, - TopologyKey: "region", - }, - }, - }, - }, - }), - }, - }, - "invalid name space in preferredDuringSchedulingIgnoredDuringExecution in podaffinity annotations, name space shouldbe valid": { - expectedError: "spec.affinity.podAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.namespace", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - }, - Spec: validPodSpec(&api.Affinity{ - PodAffinity: &api.PodAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ - { - Weight: 10, - PodAffinityTerm: api.PodAffinityTerm{ - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - Namespaces: []string{"INVALID_NAMESPACE"}, - TopologyKey: "region", - }, - }, - }, - }, - }), - }, - }, - "invalid hard pod affinity, empty topologyKey is not allowed for hard pod affinity": { - expectedError: "can not be empty", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - }, - Spec: validPodSpec(&api.Affinity{ - PodAffinity: &api.PodAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"value1", "value2"}, - }, - }, - }, - Namespaces: []string{"ns"}, - }, - }, - }, - }), - }, - }, - "invalid hard pod anti-affinity, empty topologyKey is not allowed for hard pod anti-affinity": { - expectedError: "can not be empty", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - }, - Spec: validPodSpec(&api.Affinity{ - PodAntiAffinity: &api.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"value1", "value2"}, - }, - }, - }, - Namespaces: []string{"ns"}, - }, - }, - }, - }), - }, - }, - "invalid soft pod affinity, empty topologyKey is not allowed for soft pod affinity": { - expectedError: "can not be empty", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - }, - Spec: validPodSpec(&api.Affinity{ - PodAffinity: &api.PodAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ - { - Weight: 10, - PodAffinityTerm: api.PodAffinityTerm{ - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: metav1.LabelSelectorOpNotIn, - Values: []string{"value1", "value2"}, - }, - }, - }, - Namespaces: []string{"ns"}, - }, - }, - }, - }, - }), - }, - }, - "invalid soft pod anti-affinity, empty topologyKey is not allowed for soft pod anti-affinity": { - expectedError: "can not be empty", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - }, - Spec: validPodSpec(&api.Affinity{ - PodAntiAffinity: &api.PodAntiAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ - { - Weight: 10, - PodAffinityTerm: api.PodAffinityTerm{ - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: metav1.LabelSelectorOpNotIn, - Values: []string{"value1", "value2"}, - }, - }, - }, - Namespaces: []string{"ns"}, - }, - }, - }, - }, - }), - }, - }, - "invalid toleration key": { - expectedError: "spec.tolerations[0].key", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - }, - Spec: extendPodSpecwithTolerations(validPodSpec(nil), []api.Toleration{{Key: "nospecialchars^=@", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}), - }, - }, - "invalid toleration operator": { - expectedError: "spec.tolerations[0].operator", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - }, - Spec: extendPodSpecwithTolerations(validPodSpec(nil), []api.Toleration{{Key: "foo", Operator: "In", Value: "bar", Effect: "NoSchedule"}}), - }, - }, - "value must be empty when `operator` is 'Exists'": { - expectedError: "spec.tolerations[0].operator", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - }, - Spec: extendPodSpecwithTolerations(validPodSpec(nil), []api.Toleration{{Key: "foo", Operator: "Exists", Value: "bar", Effect: "NoSchedule"}}), - }, - }, - - "operator must be 'Exists' when `key` is empty": { - expectedError: "spec.tolerations[0].operator", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - }, - Spec: extendPodSpecwithTolerations(validPodSpec(nil), []api.Toleration{{Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}), - }, - }, - "effect must be 'NoExecute' when `TolerationSeconds` is set": { - expectedError: "spec.tolerations[0].effect", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pod-forgiveness-invalid", - Namespace: "ns", - }, - Spec: extendPodSpecwithTolerations(validPodSpec(nil), []api.Toleration{{Key: "node.kubernetes.io/not-ready", Operator: "Exists", Effect: "NoSchedule", TolerationSeconds: &[]int64{20}[0]}}), - }, - }, - "must be a valid pod seccomp profile": { - expectedError: "must be a valid seccomp profile", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - Annotations: map[string]string{ - api.SeccompPodAnnotationKey: "foo", - }, - }, - Spec: validPodSpec(nil), - }, - }, - "must be a valid container seccomp profile": { - expectedError: "must be a valid seccomp profile", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - Annotations: map[string]string{ - api.SeccompContainerAnnotationKeyPrefix + "foo": "foo", - }, - }, - Spec: validPodSpec(nil), - }, - }, - "must be a non-empty container name in seccomp annotation": { - expectedError: "name part must be non-empty", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - Annotations: map[string]string{ - api.SeccompContainerAnnotationKeyPrefix: "foo", - }, - }, - Spec: validPodSpec(nil), - }, - }, - "must be a non-empty container profile in seccomp annotation": { - expectedError: "must be a valid seccomp profile", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - Annotations: map[string]string{ - api.SeccompContainerAnnotationKeyPrefix + "foo": "", - }, - }, - Spec: validPodSpec(nil), - }, - }, - "must be a relative path in a node-local seccomp profile annotation": { - expectedError: "must be a relative path", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - Annotations: map[string]string{ - api.SeccompPodAnnotationKey: "localhost//foo", - }, - }, - Spec: validPodSpec(nil), - }, - }, - "must not start with '../'": { - expectedError: "must not contain '..'", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - Annotations: map[string]string{ - api.SeccompPodAnnotationKey: "localhost/../foo", - }, - }, - Spec: validPodSpec(nil), - }, - }, - "AppArmor profile must apply to a container": { - expectedError: "metadata.annotations[container.apparmor.security.beta.kubernetes.io/fake-ctr]", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - Annotations: map[string]string{ - apparmor.ContainerAnnotationKeyPrefix + "ctr": apparmor.ProfileRuntimeDefault, - apparmor.ContainerAnnotationKeyPrefix + "init-ctr": apparmor.ProfileRuntimeDefault, - apparmor.ContainerAnnotationKeyPrefix + "fake-ctr": apparmor.ProfileRuntimeDefault, - }, - }, - Spec: api.PodSpec{ - InitContainers: []api.Container{{Name: "init-ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - }, - }, - "AppArmor profile format must be valid": { - expectedError: "invalid AppArmor profile name", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - Annotations: map[string]string{ - apparmor.ContainerAnnotationKeyPrefix + "ctr": "bad-name", - }, - }, - Spec: validPodSpec(nil), - }, - }, - "only default AppArmor profile may start with runtime/": { - expectedError: "invalid AppArmor profile name", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - Annotations: map[string]string{ - apparmor.ContainerAnnotationKeyPrefix + "ctr": "runtime/foo", - }, - }, - Spec: validPodSpec(nil), - }, - }, - "invalid sysctl annotation": { - expectedError: "metadata.annotations[security.alpha.kubernetes.io/sysctls]", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - Annotations: map[string]string{ - api.SysctlsPodAnnotationKey: "foo:", - }, - }, - Spec: validPodSpec(nil), - }, - }, - "invalid comma-separated sysctl annotation": { - expectedError: "not of the format sysctl_name=value", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - Annotations: map[string]string{ - api.SysctlsPodAnnotationKey: "kernel.msgmax,", - }, - }, - Spec: validPodSpec(nil), - }, - }, - "invalid unsafe sysctl annotation": { - expectedError: "metadata.annotations[security.alpha.kubernetes.io/sysctls]", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - Annotations: map[string]string{ - api.SysctlsPodAnnotationKey: "foo:", - }, - }, - Spec: validPodSpec(nil), - }, - }, - "intersecting safe sysctls and unsafe sysctls annotations": { - expectedError: "can not be safe and unsafe", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - Annotations: map[string]string{ - api.SysctlsPodAnnotationKey: "kernel.shmmax=10000000", - api.UnsafeSysctlsPodAnnotationKey: "kernel.shmmax=10000000", - }, - }, - Spec: validPodSpec(nil), - }, - }, - "invalid opaque integer resource requirement: request must be <= limit": { - expectedError: "must be less than or equal to pod.alpha.kubernetes.io/opaque-int-resource-A", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Name: "invalid", - Image: "image", - ImagePullPolicy: "IfNotPresent", - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - helper.OpaqueIntResourceName("A"): resource.MustParse("2"), - }, - Limits: api.ResourceList{ - helper.OpaqueIntResourceName("A"): resource.MustParse("1"), - }, - }, - }, - }, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - }, - }, - "invalid fractional opaque integer resource in container request": { - expectedError: "must be an integer", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Name: "invalid", - Image: "image", - ImagePullPolicy: "IfNotPresent", - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - helper.OpaqueIntResourceName("A"): resource.MustParse("500m"), - }, - }, - }, - }, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - }, - }, - "invalid fractional opaque integer resource in init container request": { - expectedError: "must be an integer", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, - Spec: api.PodSpec{ - InitContainers: []api.Container{ - { - Name: "invalid", - Image: "image", - ImagePullPolicy: "IfNotPresent", - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - helper.OpaqueIntResourceName("A"): resource.MustParse("500m"), - }, - }, - }, - }, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - }, - }, - "invalid fractional opaque integer resource in container limit": { - expectedError: "must be an integer", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Name: "invalid", - Image: "image", - ImagePullPolicy: "IfNotPresent", - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - helper.OpaqueIntResourceName("A"): resource.MustParse("5"), - }, - Limits: api.ResourceList{ - helper.OpaqueIntResourceName("A"): resource.MustParse("2.5"), - }, - }, - }, - }, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - }, - }, - "invalid fractional opaque integer resource in init container limit": { - expectedError: "must be an integer", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, - Spec: api.PodSpec{ - InitContainers: []api.Container{ - { - Name: "invalid", - Image: "image", - ImagePullPolicy: "IfNotPresent", - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - helper.OpaqueIntResourceName("A"): resource.MustParse("5"), - }, - Limits: api.ResourceList{ - helper.OpaqueIntResourceName("A"): resource.MustParse("2.5"), - }, - }, - }, - }, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - }, - }, - "mirror-pod present without nodeName": { - expectedError: "mirror", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{api.MirrorPodAnnotationKey: ""}}, - Spec: api.PodSpec{ - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - }, - }, - "mirror-pod populated without nodeName": { - expectedError: "mirror", - spec: api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{api.MirrorPodAnnotationKey: "foo"}}, - Spec: api.PodSpec{ - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - }, - }, - } - for k, v := range errorCases { - if errs := ValidatePod(&v.spec); len(errs) == 0 { - t.Errorf("expected failure for %q", k) - } else if v.expectedError == "" { - t.Errorf("missing expectedError for %q, got %q", k, errs.ToAggregate().Error()) - } else if actualError := errs.ToAggregate().Error(); !strings.Contains(actualError, v.expectedError) { - t.Errorf("expected error for %q to contain %q, got %q", k, v.expectedError, actualError) - } - } -} - -func TestValidatePodUpdate(t *testing.T) { - var ( - activeDeadlineSecondsZero = int64(0) - activeDeadlineSecondsNegative = int64(-30) - activeDeadlineSecondsPositive = int64(30) - activeDeadlineSecondsLarger = int64(31) - - now = metav1.Now() - grace = int64(30) - grace2 = int64(31) - ) - - tests := []struct { - new api.Pod - old api.Pod - err string - test string - }{ - {api.Pod{}, api.Pod{}, "", "nothing"}, - { - api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - }, - api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - }, - "metadata.name", - "ids", - }, - { - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Labels: map[string]string{ - "foo": "bar", - }, - }, - }, - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Labels: map[string]string{ - "bar": "foo", - }, - }, - }, - "", - "labels", - }, - { - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Annotations: map[string]string{ - "foo": "bar", - }, - }, - }, - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Annotations: map[string]string{ - "bar": "foo", - }, - }, - }, - "", - "annotations", - }, - { - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Image: "foo:V1", - }, - }, - }, - }, - api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Image: "foo:V2", - }, - { - Image: "bar:V2", - }, - }, - }, - }, - "may not add or remove containers", - "less containers", - }, - { - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Image: "foo:V1", - }, - { - Image: "bar:V2", - }, - }, - }, - }, - api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Image: "foo:V2", - }, - }, - }, - }, - "may not add or remove containers", - "more containers", - }, - { - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.PodSpec{ - InitContainers: []api.Container{ - { - Image: "foo:V1", - }, - }, - }, - }, - api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: api.PodSpec{ - InitContainers: []api.Container{ - { - Image: "foo:V2", - }, - { - Image: "bar:V2", - }, - }, - }, - }, - "may not add or remove containers", - "more init containers", - }, - { - api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: api.PodSpec{Containers: []api.Container{{Image: "foo:V1"}}}, - }, - api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now}, - Spec: api.PodSpec{Containers: []api.Container{{Image: "foo:V1"}}}, - }, - "", - "deletion timestamp removed", - }, - { - api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now}, - Spec: api.PodSpec{Containers: []api.Container{{Image: "foo:V1"}}}, - }, - api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: api.PodSpec{Containers: []api.Container{{Image: "foo:V1"}}}, - }, - "metadata.deletionTimestamp", - "deletion timestamp added", - }, - { - api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &grace}, - Spec: api.PodSpec{Containers: []api.Container{{Image: "foo:V1"}}}, - }, - api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &grace2}, - Spec: api.PodSpec{Containers: []api.Container{{Image: "foo:V1"}}}, - }, - "metadata.deletionGracePeriodSeconds", - "deletion grace period seconds changed", - }, - { - api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Image: "foo:V1", - }, - }, - }, - }, - api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Image: "foo:V2", - }, - }, - }, - }, - "", - "image change", - }, - { - api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: api.PodSpec{ - InitContainers: []api.Container{ - { - Image: "foo:V1", - }, - }, - }, - }, - api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: api.PodSpec{ - InitContainers: []api.Container{ - { - Image: "foo:V2", - }, - }, - }, - }, - "", - "init container image change", - }, - { - api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: api.PodSpec{ - Containers: []api.Container{ - {}, - }, - }, - }, - api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Image: "foo:V2", - }, - }, - }, - }, - "spec.containers[0].image", - "image change to empty", - }, - { - api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: api.PodSpec{ - InitContainers: []api.Container{ - {}, - }, - }, - }, - api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: api.PodSpec{ - InitContainers: []api.Container{ - { - Image: "foo:V2", - }, - }, - }, - }, - "spec.initContainers[0].image", - "init container image change to empty", - }, - { - api.Pod{ - Spec: api.PodSpec{}, - }, - api.Pod{ - Spec: api.PodSpec{}, - }, - "", - "activeDeadlineSeconds no change, nil", - }, - { - api.Pod{ - Spec: api.PodSpec{ - ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, - }, - }, - api.Pod{ - Spec: api.PodSpec{ - ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, - }, - }, - "", - "activeDeadlineSeconds no change, set", - }, - { - api.Pod{ - Spec: api.PodSpec{ - ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, - }, - }, - api.Pod{}, - "", - "activeDeadlineSeconds change to positive from nil", - }, - { - api.Pod{ - Spec: api.PodSpec{ - ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, - }, - }, - api.Pod{ - Spec: api.PodSpec{ - ActiveDeadlineSeconds: &activeDeadlineSecondsLarger, - }, - }, - "", - "activeDeadlineSeconds change to smaller positive", - }, - { - api.Pod{ - Spec: api.PodSpec{ - ActiveDeadlineSeconds: &activeDeadlineSecondsLarger, - }, - }, - api.Pod{ - Spec: api.PodSpec{ - ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, - }, - }, - "spec.activeDeadlineSeconds", - "activeDeadlineSeconds change to larger positive", - }, - - { - api.Pod{ - Spec: api.PodSpec{ - ActiveDeadlineSeconds: &activeDeadlineSecondsNegative, - }, - }, - api.Pod{}, - "spec.activeDeadlineSeconds", - "activeDeadlineSeconds change to negative from nil", - }, - { - api.Pod{ - Spec: api.PodSpec{ - ActiveDeadlineSeconds: &activeDeadlineSecondsNegative, - }, - }, - api.Pod{ - Spec: api.PodSpec{ - ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, - }, - }, - "spec.activeDeadlineSeconds", - "activeDeadlineSeconds change to negative from positive", - }, - { - api.Pod{ - Spec: api.PodSpec{ - ActiveDeadlineSeconds: &activeDeadlineSecondsZero, - }, - }, - api.Pod{ - Spec: api.PodSpec{ - ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, - }, - }, - "", - "activeDeadlineSeconds change to zero from positive", - }, - { - api.Pod{ - Spec: api.PodSpec{ - ActiveDeadlineSeconds: &activeDeadlineSecondsZero, - }, - }, - api.Pod{}, - "", - "activeDeadlineSeconds change to zero from nil", - }, - { - api.Pod{}, - api.Pod{ - Spec: api.PodSpec{ - ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, - }, - }, - "spec.activeDeadlineSeconds", - "activeDeadlineSeconds change to nil from positive", - }, - { - api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Image: "foo:V1", - Resources: api.ResourceRequirements{ - Limits: getResourceLimits("100m", "0"), - }, - }, - }, - }, - }, - api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Image: "foo:V2", - Resources: api.ResourceRequirements{ - Limits: getResourceLimits("1000m", "0"), - }, - }, - }, - }, - }, - "spec: Forbidden: pod updates may not change fields", - "cpu change", - }, - { - api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Image: "foo:V1", - Ports: []api.ContainerPort{ - {HostPort: 8080, ContainerPort: 80}, - }, - }, - }, - }, - }, - api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Image: "foo:V2", - Ports: []api.ContainerPort{ - {HostPort: 8000, ContainerPort: 80}, - }, - }, - }, - }, - }, - "spec: Forbidden: pod updates may not change fields", - "port change", - }, - { - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Labels: map[string]string{ - "foo": "bar", - }, - }, - }, - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Labels: map[string]string{ - "Bar": "foo", - }, - }, - }, - "", - "bad label change", - }, - { - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.PodSpec{ - NodeName: "node1", - Tolerations: []api.Toleration{{Key: "key1", Value: "value2"}}, - }, - }, - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.PodSpec{ - NodeName: "node1", - Tolerations: []api.Toleration{{Key: "key1", Value: "value1"}}, - }, - }, - "spec.tolerations: Forbidden", - "existing toleration value modified in pod spec updates", - }, - { - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.PodSpec{ - NodeName: "node1", - Tolerations: []api.Toleration{{Key: "key1", Value: "value2", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: nil}}, - }, - }, - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.PodSpec{ - NodeName: "node1", - Tolerations: []api.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}}, - }, - }, - "spec.tolerations: Forbidden", - "existing toleration value modified in pod spec updates with modified tolerationSeconds", - }, - { - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.PodSpec{ - NodeName: "node1", - Tolerations: []api.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}}, - }, - }, - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.PodSpec{ - NodeName: "node1", - Tolerations: []api.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{20}[0]}}, - }}, - "", - "modified tolerationSeconds in existing toleration value in pod spec updates", - }, - { - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.PodSpec{ - Tolerations: []api.Toleration{{Key: "key1", Value: "value2"}}, - }, - }, - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.PodSpec{ - NodeName: "", - Tolerations: []api.Toleration{{Key: "key1", Value: "value1"}}, - }, - }, - "spec.tolerations: Forbidden", - "toleration modified in updates to an unscheduled pod", - }, - { - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.PodSpec{ - NodeName: "node1", - Tolerations: []api.Toleration{{Key: "key1", Value: "value1"}}, - }, - }, - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.PodSpec{ - NodeName: "node1", - Tolerations: []api.Toleration{{Key: "key1", Value: "value1"}}, - }, - }, - "", - "tolerations unmodified in updates to a scheduled pod", - }, - { - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.PodSpec{ - NodeName: "node1", - Tolerations: []api.Toleration{ - {Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{20}[0]}, - {Key: "key2", Value: "value2", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{30}[0]}, - }, - }}, - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.PodSpec{ - NodeName: "node1", - Tolerations: []api.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}}, - }, - }, - "", - "added valid new toleration to existing tolerations in pod spec updates", - }, - { - api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: api.PodSpec{ - NodeName: "node1", - Tolerations: []api.Toleration{ - {Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{20}[0]}, - {Key: "key2", Value: "value2", Operator: "Equal", Effect: "NoSchedule", TolerationSeconds: &[]int64{30}[0]}, - }, - }}, - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.PodSpec{ - NodeName: "node1", Tolerations: []api.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}}, - }}, - "spec.tolerations[1].effect", - "added invalid new toleration to existing tolerations in pod spec updates", - }, - { - api.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: api.PodSpec{NodeName: "foo"}}, - api.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}, - "spec: Forbidden: pod updates may not change fields", - "removed nodeName from pod spec", - }, - { - api.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{api.MirrorPodAnnotationKey: ""}}, Spec: api.PodSpec{NodeName: "foo"}}, - api.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: api.PodSpec{NodeName: "foo"}}, - "metadata.annotations[kubernetes.io/config.mirror]", - "added mirror pod annotation", - }, - { - api.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: api.PodSpec{NodeName: "foo"}}, - api.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{api.MirrorPodAnnotationKey: ""}}, Spec: api.PodSpec{NodeName: "foo"}}, - "metadata.annotations[kubernetes.io/config.mirror]", - "removed mirror pod annotation", - }, - { - api.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{api.MirrorPodAnnotationKey: "foo"}}, Spec: api.PodSpec{NodeName: "foo"}}, - api.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{api.MirrorPodAnnotationKey: "bar"}}, Spec: api.PodSpec{NodeName: "foo"}}, - "metadata.annotations[kubernetes.io/config.mirror]", - "changed mirror pod annotation", - }, - { - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.PodSpec{ - NodeName: "node1", - PriorityClassName: "bar-priority", - }, - }, - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.PodSpec{ - NodeName: "node1", - PriorityClassName: "foo-priority", - }, - }, - "spec: Forbidden: pod updates", - "changed priority class name", - }, - { - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.PodSpec{ - NodeName: "node1", - PriorityClassName: "", - }, - }, - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.PodSpec{ - NodeName: "node1", - PriorityClassName: "foo-priority", - }, - }, - "spec: Forbidden: pod updates", - "removed priority class name", - }, - } - - for _, test := range tests { - test.new.ObjectMeta.ResourceVersion = "1" - test.old.ObjectMeta.ResourceVersion = "1" - errs := ValidatePodUpdate(&test.new, &test.old) - if test.err == "" { - if len(errs) != 0 { - t.Errorf("unexpected invalid: %s (%+v)\nA: %+v\nB: %+v", test.test, errs, test.new, test.old) - } - } else { - if len(errs) == 0 { - t.Errorf("unexpected valid: %s\nA: %+v\nB: %+v", test.test, test.new, test.old) - } else if actualErr := errs.ToAggregate().Error(); !strings.Contains(actualErr, test.err) { - t.Errorf("unexpected error message: %s\nExpected error: %s\nActual error: %s", test.test, test.err, actualErr) - } - } - } -} - -func makeValidService() api.Service { - return api.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "valid", - Namespace: "valid", - Labels: map[string]string{}, - Annotations: map[string]string{}, - ResourceVersion: "1", - }, - Spec: api.ServiceSpec{ - Selector: map[string]string{"key": "val"}, - SessionAffinity: "None", - Type: api.ServiceTypeClusterIP, - Ports: []api.ServicePort{{Name: "p", Protocol: "TCP", Port: 8675, TargetPort: intstr.FromInt(8675)}}, - }, - } -} - -func TestValidateService(t *testing.T) { - testCases := []struct { - name string - tweakSvc func(svc *api.Service) // given a basic valid service, each test case can customize it - numErrs int - }{ - { - name: "missing namespace", - tweakSvc: func(s *api.Service) { - s.Namespace = "" - }, - numErrs: 1, - }, - { - name: "invalid namespace", - tweakSvc: func(s *api.Service) { - s.Namespace = "-123" - }, - numErrs: 1, - }, - { - name: "missing name", - tweakSvc: func(s *api.Service) { - s.Name = "" - }, - numErrs: 1, - }, - { - name: "invalid name", - tweakSvc: func(s *api.Service) { - s.Name = "-123" - }, - numErrs: 1, - }, - { - name: "too long name", - tweakSvc: func(s *api.Service) { - s.Name = strings.Repeat("a", 64) - }, - numErrs: 1, - }, - { - name: "invalid generateName", - tweakSvc: func(s *api.Service) { - s.GenerateName = "-123" - }, - numErrs: 1, - }, - { - name: "too long generateName", - tweakSvc: func(s *api.Service) { - s.GenerateName = strings.Repeat("a", 64) - }, - numErrs: 1, - }, - { - name: "invalid label", - tweakSvc: func(s *api.Service) { - s.Labels["NoUppercaseOrSpecialCharsLike=Equals"] = "bar" - }, - numErrs: 1, - }, - { - name: "invalid annotation", - tweakSvc: func(s *api.Service) { - s.Annotations["NoSpecialCharsLike=Equals"] = "bar" - }, - numErrs: 1, - }, - { - name: "nil selector", - tweakSvc: func(s *api.Service) { - s.Spec.Selector = nil - }, - numErrs: 0, - }, - { - name: "invalid selector", - tweakSvc: func(s *api.Service) { - s.Spec.Selector["NoSpecialCharsLike=Equals"] = "bar" - }, - numErrs: 1, - }, - { - name: "missing session affinity", - tweakSvc: func(s *api.Service) { - s.Spec.SessionAffinity = "" - }, - numErrs: 1, - }, - { - name: "missing type", - tweakSvc: func(s *api.Service) { - s.Spec.Type = "" - }, - numErrs: 1, - }, - { - name: "missing ports", - tweakSvc: func(s *api.Service) { - s.Spec.Ports = nil - }, - numErrs: 1, - }, - { - name: "missing ports but headless", - tweakSvc: func(s *api.Service) { - s.Spec.Ports = nil - s.Spec.ClusterIP = api.ClusterIPNone - }, - numErrs: 0, - }, - { - name: "empty port[0] name", - tweakSvc: func(s *api.Service) { - s.Spec.Ports[0].Name = "" - }, - numErrs: 0, - }, - { - name: "empty port[1] name", - tweakSvc: func(s *api.Service) { - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "", Protocol: "TCP", Port: 12345, TargetPort: intstr.FromInt(12345)}) - }, - numErrs: 1, - }, - { - name: "empty multi-port port[0] name", - tweakSvc: func(s *api.Service) { - s.Spec.Ports[0].Name = "" - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "p", Protocol: "TCP", Port: 12345, TargetPort: intstr.FromInt(12345)}) - }, - numErrs: 1, - }, - { - name: "invalid port name", - tweakSvc: func(s *api.Service) { - s.Spec.Ports[0].Name = "INVALID" - }, - numErrs: 1, - }, - { - name: "missing protocol", - tweakSvc: func(s *api.Service) { - s.Spec.Ports[0].Protocol = "" - }, - numErrs: 1, - }, - { - name: "invalid protocol", - tweakSvc: func(s *api.Service) { - s.Spec.Ports[0].Protocol = "INVALID" - }, - numErrs: 1, - }, - { - name: "invalid cluster ip", - tweakSvc: func(s *api.Service) { - s.Spec.ClusterIP = "invalid" - }, - numErrs: 1, - }, - { - name: "missing port", - tweakSvc: func(s *api.Service) { - s.Spec.Ports[0].Port = 0 - }, - numErrs: 1, - }, - { - name: "invalid port", - tweakSvc: func(s *api.Service) { - s.Spec.Ports[0].Port = 65536 - }, - numErrs: 1, - }, - { - name: "invalid TargetPort int", - tweakSvc: func(s *api.Service) { - s.Spec.Ports[0].TargetPort = intstr.FromInt(65536) - }, - numErrs: 1, - }, - { - name: "valid port headless", - tweakSvc: func(s *api.Service) { - s.Spec.Ports[0].Port = 11722 - s.Spec.Ports[0].TargetPort = intstr.FromInt(11722) - s.Spec.ClusterIP = api.ClusterIPNone - }, - numErrs: 0, - }, - { - name: "invalid port headless 1", - tweakSvc: func(s *api.Service) { - s.Spec.Ports[0].Port = 11722 - s.Spec.Ports[0].TargetPort = intstr.FromInt(11721) - s.Spec.ClusterIP = api.ClusterIPNone - }, - // in the v1 API, targetPorts on headless services were tolerated. - // once we have version-specific validation, we can reject this on newer API versions, but until then, we have to tolerate it for compatibility. - // numErrs: 1, - numErrs: 0, - }, - { - name: "invalid port headless 2", - tweakSvc: func(s *api.Service) { - s.Spec.Ports[0].Port = 11722 - s.Spec.Ports[0].TargetPort = intstr.FromString("target") - s.Spec.ClusterIP = api.ClusterIPNone - }, - // in the v1 API, targetPorts on headless services were tolerated. - // once we have version-specific validation, we can reject this on newer API versions, but until then, we have to tolerate it for compatibility. - // numErrs: 1, - numErrs: 0, - }, - { - name: "invalid publicIPs localhost", - tweakSvc: func(s *api.Service) { - s.Spec.ExternalIPs = []string{"127.0.0.1"} - }, - numErrs: 1, - }, - { - name: "invalid publicIPs unspecified", - tweakSvc: func(s *api.Service) { - s.Spec.ExternalIPs = []string{"0.0.0.0"} - }, - numErrs: 1, - }, - { - name: "invalid publicIPs loopback", - tweakSvc: func(s *api.Service) { - s.Spec.ExternalIPs = []string{"127.0.0.1"} - }, - numErrs: 1, - }, - { - name: "invalid publicIPs host", - tweakSvc: func(s *api.Service) { - s.Spec.ExternalIPs = []string{"myhost.mydomain"} - }, - numErrs: 1, - }, - { - name: "dup port name", - tweakSvc: func(s *api.Service) { - s.Spec.Ports[0].Name = "p" - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "p", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(12345)}) - }, - numErrs: 1, - }, - { - name: "valid load balancer protocol UDP 1", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeLoadBalancer - s.Spec.Ports[0].Protocol = "UDP" - }, - numErrs: 0, - }, - { - name: "valid load balancer protocol UDP 2", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeLoadBalancer - s.Spec.Ports[0] = api.ServicePort{Name: "q", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt(12345)} - }, - numErrs: 0, - }, - { - name: "invalid load balancer with mix protocol", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeLoadBalancer - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "q", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt(12345)}) - }, - numErrs: 1, - }, - { - name: "valid 1", - tweakSvc: func(s *api.Service) { - // do nothing - }, - numErrs: 0, - }, - { - name: "valid 2", - tweakSvc: func(s *api.Service) { - s.Spec.Ports[0].Protocol = "UDP" - s.Spec.Ports[0].TargetPort = intstr.FromInt(12345) - }, - numErrs: 0, - }, - { - name: "valid 3", - tweakSvc: func(s *api.Service) { - s.Spec.Ports[0].TargetPort = intstr.FromString("http") - }, - numErrs: 0, - }, - { - name: "valid cluster ip - none ", - tweakSvc: func(s *api.Service) { - s.Spec.ClusterIP = "None" - }, - numErrs: 0, - }, - { - name: "valid cluster ip - empty", - tweakSvc: func(s *api.Service) { - s.Spec.ClusterIP = "" - s.Spec.Ports[0].TargetPort = intstr.FromString("http") - }, - numErrs: 0, - }, - { - name: "valid type - cluster", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeClusterIP - }, - numErrs: 0, - }, - { - name: "valid type - loadbalancer", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeLoadBalancer - }, - numErrs: 0, - }, - { - name: "valid type loadbalancer 2 ports", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeLoadBalancer - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(12345)}) - }, - numErrs: 0, - }, - { - name: "valid external load balancer 2 ports", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeLoadBalancer - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(12345)}) - }, - numErrs: 0, - }, - { - name: "duplicate nodeports", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeNodePort - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(1)}) - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "r", Port: 2, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(2)}) - }, - numErrs: 1, - }, - { - name: "duplicate nodeports (different protocols)", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeNodePort - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(1)}) - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "r", Port: 2, Protocol: "UDP", NodePort: 1, TargetPort: intstr.FromInt(2)}) - }, - numErrs: 0, - }, - { - name: "invalid duplicate ports (with same protocol)", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeClusterIP - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}) - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "r", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(80)}) - }, - numErrs: 1, - }, - { - name: "valid duplicate ports (with different protocols)", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeClusterIP - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}) - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "r", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt(80)}) - }, - numErrs: 0, - }, - { - name: "invalid duplicate targetports (number with same protocol)", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeClusterIP - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "q", Port: 1, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}) - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "r", Port: 2, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}) - }, - numErrs: 1, - }, - { - name: "invalid duplicate targetports (name with same protocol)", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeClusterIP - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "q", Port: 1, Protocol: "TCP", TargetPort: intstr.FromString("http")}) - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "r", Port: 2, Protocol: "TCP", TargetPort: intstr.FromString("http")}) - }, - numErrs: 1, - }, - { - name: "valid duplicate targetports (number with different protocols)", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeClusterIP - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "q", Port: 1, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}) - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "r", Port: 2, Protocol: "UDP", TargetPort: intstr.FromInt(8080)}) - }, - numErrs: 0, - }, - { - name: "valid duplicate targetports (name with different protocols)", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeClusterIP - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "q", Port: 1, Protocol: "TCP", TargetPort: intstr.FromString("http")}) - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "r", Port: 2, Protocol: "UDP", TargetPort: intstr.FromString("http")}) - }, - numErrs: 0, - }, - { - name: "valid type - cluster", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeClusterIP - }, - numErrs: 0, - }, - { - name: "valid type - nodeport", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeNodePort - }, - numErrs: 0, - }, - { - name: "valid type - loadbalancer", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeLoadBalancer - }, - numErrs: 0, - }, - { - name: "valid type loadbalancer 2 ports", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeLoadBalancer - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(12345)}) - }, - numErrs: 0, - }, - { - name: "valid type loadbalancer with NodePort", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeLoadBalancer - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt(12345)}) - }, - numErrs: 0, - }, - { - name: "valid type=NodePort service with NodePort", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeNodePort - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt(12345)}) - }, - numErrs: 0, - }, - { - name: "valid type=NodePort service without NodePort", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeNodePort - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(12345)}) - }, - numErrs: 0, - }, - { - name: "valid cluster service without NodePort", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeClusterIP - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(12345)}) - }, - numErrs: 0, - }, - { - name: "invalid cluster service with NodePort", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeClusterIP - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt(12345)}) - }, - numErrs: 1, - }, - { - name: "invalid public service with duplicate NodePort", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeNodePort - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "p1", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(1)}) - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "p2", Port: 2, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(2)}) - }, - numErrs: 1, - }, - { - name: "valid type=LoadBalancer", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeLoadBalancer - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(12345)}) - }, - numErrs: 0, - }, - { - // For now we open firewalls, and its insecure if we open 10250, remove this - // when we have better protections in place. - name: "invalid port type=LoadBalancer", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeLoadBalancer - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "kubelet", Port: 10250, Protocol: "TCP", TargetPort: intstr.FromInt(12345)}) - }, - numErrs: 1, - }, - { - name: "valid LoadBalancer source range annotation", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeLoadBalancer - s.Annotations[api.AnnotationLoadBalancerSourceRangesKey] = "1.2.3.4/8, 5.6.7.8/16" - }, - numErrs: 0, - }, - { - name: "empty LoadBalancer source range annotation", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeLoadBalancer - s.Annotations[api.AnnotationLoadBalancerSourceRangesKey] = "" - }, - numErrs: 0, - }, - { - name: "invalid LoadBalancer source range annotation (hostname)", - tweakSvc: func(s *api.Service) { - s.Annotations[api.AnnotationLoadBalancerSourceRangesKey] = "foo.bar" - }, - numErrs: 2, - }, - { - name: "invalid LoadBalancer source range annotation (invalid CIDR)", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeLoadBalancer - s.Annotations[api.AnnotationLoadBalancerSourceRangesKey] = "1.2.3.4/33" - }, - numErrs: 1, - }, - { - name: "invalid source range for non LoadBalancer type service", - tweakSvc: func(s *api.Service) { - s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.4/8", "5.6.7.8/16"} - }, - numErrs: 1, - }, - { - name: "valid LoadBalancer source range", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeLoadBalancer - s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.4/8", "5.6.7.8/16"} - }, - numErrs: 0, - }, - { - name: "empty LoadBalancer source range", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeLoadBalancer - s.Spec.LoadBalancerSourceRanges = []string{" "} - }, - numErrs: 1, - }, - { - name: "invalid LoadBalancer source range", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeLoadBalancer - s.Spec.LoadBalancerSourceRanges = []string{"foo.bar"} - }, - numErrs: 1, - }, - { - name: "valid ExternalName", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeExternalName - s.Spec.ClusterIP = "" - s.Spec.ExternalName = "foo.bar.example.com" - }, - numErrs: 0, - }, - { - name: "invalid ExternalName clusterIP (valid IP)", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeExternalName - s.Spec.ClusterIP = "1.2.3.4" - s.Spec.ExternalName = "foo.bar.example.com" - }, - numErrs: 1, - }, - { - name: "invalid ExternalName clusterIP (None)", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeExternalName - s.Spec.ClusterIP = "None" - s.Spec.ExternalName = "foo.bar.example.com" - }, - numErrs: 1, - }, - { - name: "invalid ExternalName (not a DNS name)", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeExternalName - s.Spec.ClusterIP = "" - s.Spec.ExternalName = "-123" - }, - numErrs: 1, - }, - { - name: "LoadBalancer type cannot have None ClusterIP", - tweakSvc: func(s *api.Service) { - s.Spec.ClusterIP = "None" - s.Spec.Type = api.ServiceTypeLoadBalancer - }, - numErrs: 1, - }, - { - name: "invalid node port with clusterIP None", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeNodePort - s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(1)}) - s.Spec.ClusterIP = "None" - }, - numErrs: 1, - }, - // ESIPP section begins. - { - name: "invalid externalTraffic field", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeLoadBalancer - s.Spec.ExternalTrafficPolicy = "invalid" - }, - numErrs: 1, - }, - { - name: "nagative healthCheckNodePort field", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeLoadBalancer - s.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyTypeLocal - s.Spec.HealthCheckNodePort = -1 - }, - numErrs: 1, - }, - { - name: "nagative healthCheckNodePort field", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeLoadBalancer - s.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyTypeLocal - s.Spec.HealthCheckNodePort = 31100 - }, - numErrs: 0, - }, - // ESIPP section ends. - { - name: "invalid timeoutSeconds field", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeClusterIP - s.Spec.SessionAffinity = api.ServiceAffinityClientIP - s.Spec.SessionAffinityConfig = &api.SessionAffinityConfig{ - ClientIP: &api.ClientIPConfig{ - TimeoutSeconds: newInt32(-1), - }, - } - }, - numErrs: 1, - }, - { - name: "sessionAffinityConfig can't be set when session affinity is None", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeLoadBalancer - s.Spec.SessionAffinity = api.ServiceAffinityNone - s.Spec.SessionAffinityConfig = &api.SessionAffinityConfig{ - ClientIP: &api.ClientIPConfig{ - TimeoutSeconds: newInt32(90), - }, - } - }, - numErrs: 1, - }, - } - - for _, tc := range testCases { - svc := makeValidService() - tc.tweakSvc(&svc) - errs := ValidateService(&svc) - if len(errs) != tc.numErrs { - t.Errorf("Unexpected error list for case %q: %v", tc.name, errs.ToAggregate()) - } - } -} - -func TestValidateServiceExternalTrafficFieldsCombination(t *testing.T) { - testCases := []struct { - name string - tweakSvc func(svc *api.Service) // Given a basic valid service, each test case can customize it. - numErrs int - }{ - { - name: "valid loadBalancer service with externalTrafficPolicy and healthCheckNodePort set", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeLoadBalancer - s.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyTypeLocal - s.Spec.HealthCheckNodePort = 34567 - }, - numErrs: 0, - }, - { - name: "valid nodePort service with externalTrafficPolicy set", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeNodePort - s.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyTypeLocal - }, - numErrs: 0, - }, - { - name: "valid clusterIP service with none of externalTrafficPolicy and healthCheckNodePort set", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeClusterIP - }, - numErrs: 0, - }, - { - name: "cannot set healthCheckNodePort field on loadBalancer service with externalTrafficPolicy!=Local", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeLoadBalancer - s.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyTypeCluster - s.Spec.HealthCheckNodePort = 34567 - }, - numErrs: 1, - }, - { - name: "cannot set healthCheckNodePort field on nodePort service", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeNodePort - s.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyTypeLocal - s.Spec.HealthCheckNodePort = 34567 - }, - numErrs: 1, - }, - { - name: "cannot set externalTrafficPolicy or healthCheckNodePort fields on clusterIP service", - tweakSvc: func(s *api.Service) { - s.Spec.Type = api.ServiceTypeClusterIP - s.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyTypeLocal - s.Spec.HealthCheckNodePort = 34567 - }, - numErrs: 2, - }, - } - - for _, tc := range testCases { - svc := makeValidService() - tc.tweakSvc(&svc) - errs := ValidateServiceExternalTrafficFieldsCombination(&svc) - if len(errs) != tc.numErrs { - t.Errorf("Unexpected error list for case %q: %v", tc.name, errs.ToAggregate()) - } - } -} - -func TestValidateReplicationControllerStatus(t *testing.T) { - tests := []struct { - name string - - replicas int32 - fullyLabeledReplicas int32 - readyReplicas int32 - availableReplicas int32 - observedGeneration int64 - - expectedErr bool - }{ - { - name: "valid status", - replicas: 3, - fullyLabeledReplicas: 3, - readyReplicas: 2, - availableReplicas: 1, - observedGeneration: 2, - expectedErr: false, - }, - { - name: "invalid replicas", - replicas: -1, - fullyLabeledReplicas: 3, - readyReplicas: 2, - availableReplicas: 1, - observedGeneration: 2, - expectedErr: true, - }, - { - name: "invalid fullyLabeledReplicas", - replicas: 3, - fullyLabeledReplicas: -1, - readyReplicas: 2, - availableReplicas: 1, - observedGeneration: 2, - expectedErr: true, - }, - { - name: "invalid readyReplicas", - replicas: 3, - fullyLabeledReplicas: 3, - readyReplicas: -1, - availableReplicas: 1, - observedGeneration: 2, - expectedErr: true, - }, - { - name: "invalid availableReplicas", - replicas: 3, - fullyLabeledReplicas: 3, - readyReplicas: 3, - availableReplicas: -1, - observedGeneration: 2, - expectedErr: true, - }, - { - name: "invalid observedGeneration", - replicas: 3, - fullyLabeledReplicas: 3, - readyReplicas: 3, - availableReplicas: 3, - observedGeneration: -1, - expectedErr: true, - }, - { - name: "fullyLabeledReplicas greater than replicas", - replicas: 3, - fullyLabeledReplicas: 4, - readyReplicas: 3, - availableReplicas: 3, - observedGeneration: 1, - expectedErr: true, - }, - { - name: "readyReplicas greater than replicas", - replicas: 3, - fullyLabeledReplicas: 3, - readyReplicas: 4, - availableReplicas: 3, - observedGeneration: 1, - expectedErr: true, - }, - { - name: "availableReplicas greater than replicas", - replicas: 3, - fullyLabeledReplicas: 3, - readyReplicas: 3, - availableReplicas: 4, - observedGeneration: 1, - expectedErr: true, - }, - { - name: "availableReplicas greater than readyReplicas", - replicas: 3, - fullyLabeledReplicas: 3, - readyReplicas: 2, - availableReplicas: 3, - observedGeneration: 1, - expectedErr: true, - }, - } - - for _, test := range tests { - status := api.ReplicationControllerStatus{ - Replicas: test.replicas, - FullyLabeledReplicas: test.fullyLabeledReplicas, - ReadyReplicas: test.readyReplicas, - AvailableReplicas: test.availableReplicas, - ObservedGeneration: test.observedGeneration, - } - - if hasErr := len(ValidateReplicationControllerStatus(status, field.NewPath("status"))) > 0; hasErr != test.expectedErr { - t.Errorf("%s: expected error: %t, got error: %t", test.name, test.expectedErr, hasErr) - } - } -} - -func TestValidateReplicationControllerStatusUpdate(t *testing.T) { - validSelector := map[string]string{"a": "b"} - validPodTemplate := api.PodTemplate{ - Template: api.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: validSelector, - }, - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - }, - }, - } - type rcUpdateTest struct { - old api.ReplicationController - update api.ReplicationController - } - successCases := []rcUpdateTest{ - { - old: api.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: api.ReplicationControllerSpec{ - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - Status: api.ReplicationControllerStatus{ - Replicas: 2, - }, - }, - update: api.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: api.ReplicationControllerSpec{ - Replicas: 3, - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - Status: api.ReplicationControllerStatus{ - Replicas: 4, - }, - }, - }, - } - for _, successCase := range successCases { - successCase.old.ObjectMeta.ResourceVersion = "1" - successCase.update.ObjectMeta.ResourceVersion = "1" - if errs := ValidateReplicationControllerStatusUpdate(&successCase.update, &successCase.old); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - } - errorCases := map[string]rcUpdateTest{ - "negative replicas": { - old: api.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, - Spec: api.ReplicationControllerSpec{ - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - Status: api.ReplicationControllerStatus{ - Replicas: 3, - }, - }, - update: api.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: api.ReplicationControllerSpec{ - Replicas: 2, - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - Status: api.ReplicationControllerStatus{ - Replicas: -3, - }, - }, - }, - } - for testName, errorCase := range errorCases { - if errs := ValidateReplicationControllerStatusUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 { - t.Errorf("expected failure: %s", testName) - } - } - -} - -func TestValidateReplicationControllerUpdate(t *testing.T) { - validSelector := map[string]string{"a": "b"} - validPodTemplate := api.PodTemplate{ - Template: api.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: validSelector, - }, - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - }, - }, - } - readWriteVolumePodTemplate := api.PodTemplate{ - Template: api.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: validSelector, - }, - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - Volumes: []api.Volume{{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}}, - }, - }, - } - invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} - invalidPodTemplate := api.PodTemplate{ - Template: api.PodTemplateSpec{ - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - ObjectMeta: metav1.ObjectMeta{ - Labels: invalidSelector, - }, - }, - } - type rcUpdateTest struct { - old api.ReplicationController - update api.ReplicationController - } - successCases := []rcUpdateTest{ - { - old: api.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: api.ReplicationControllerSpec{ - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - }, - update: api.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: api.ReplicationControllerSpec{ - Replicas: 3, - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - }, - }, - { - old: api.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: api.ReplicationControllerSpec{ - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - }, - update: api.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: api.ReplicationControllerSpec{ - Replicas: 1, - Selector: validSelector, - Template: &readWriteVolumePodTemplate.Template, - }, - }, - }, - } - for _, successCase := range successCases { - successCase.old.ObjectMeta.ResourceVersion = "1" - successCase.update.ObjectMeta.ResourceVersion = "1" - if errs := ValidateReplicationControllerUpdate(&successCase.update, &successCase.old); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - } - errorCases := map[string]rcUpdateTest{ - "more than one read/write": { - old: api.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, - Spec: api.ReplicationControllerSpec{ - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - }, - update: api.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: api.ReplicationControllerSpec{ - Replicas: 2, - Selector: validSelector, - Template: &readWriteVolumePodTemplate.Template, - }, - }, - }, - "invalid selector": { - old: api.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, - Spec: api.ReplicationControllerSpec{ - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - }, - update: api.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: api.ReplicationControllerSpec{ - Replicas: 2, - Selector: invalidSelector, - Template: &validPodTemplate.Template, - }, - }, - }, - "invalid pod": { - old: api.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, - Spec: api.ReplicationControllerSpec{ - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - }, - update: api.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: api.ReplicationControllerSpec{ - Replicas: 2, - Selector: validSelector, - Template: &invalidPodTemplate.Template, - }, - }, - }, - "negative replicas": { - old: api.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: api.ReplicationControllerSpec{ - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - }, - update: api.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: api.ReplicationControllerSpec{ - Replicas: -1, - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - }, - }, - } - for testName, errorCase := range errorCases { - if errs := ValidateReplicationControllerUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 { - t.Errorf("expected failure: %s", testName) - } - } -} - -func TestValidateReplicationController(t *testing.T) { - validSelector := map[string]string{"a": "b"} - validPodTemplate := api.PodTemplate{ - Template: api.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: validSelector, - }, - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - }, - }, - } - readWriteVolumePodTemplate := api.PodTemplate{ - Template: api.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: validSelector, - }, - Spec: api.PodSpec{ - Volumes: []api.Volume{{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - }, - }, - } - invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} - invalidPodTemplate := api.PodTemplate{ - Template: api.PodTemplateSpec{ - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - ObjectMeta: metav1.ObjectMeta{ - Labels: invalidSelector, - }, - }, - } - successCases := []api.ReplicationController{ - { - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: api.ReplicationControllerSpec{ - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, - Spec: api.ReplicationControllerSpec{ - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, - Spec: api.ReplicationControllerSpec{ - Replicas: 1, - Selector: validSelector, - Template: &readWriteVolumePodTemplate.Template, - }, - }, - } - for _, successCase := range successCases { - if errs := ValidateReplicationController(&successCase); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - } - - errorCases := map[string]api.ReplicationController{ - "zero-length ID": { - ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, - Spec: api.ReplicationControllerSpec{ - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - }, - "missing-namespace": { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123"}, - Spec: api.ReplicationControllerSpec{ - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - }, - "empty selector": { - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: api.ReplicationControllerSpec{ - Template: &validPodTemplate.Template, - }, - }, - "selector_doesnt_match": { - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: api.ReplicationControllerSpec{ - Selector: map[string]string{"foo": "bar"}, - Template: &validPodTemplate.Template, - }, - }, - "invalid manifest": { - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: api.ReplicationControllerSpec{ - Selector: validSelector, - }, - }, - "read-write persistent disk with > 1 pod": { - ObjectMeta: metav1.ObjectMeta{Name: "abc"}, - Spec: api.ReplicationControllerSpec{ - Replicas: 2, - Selector: validSelector, - Template: &readWriteVolumePodTemplate.Template, - }, - }, - "negative_replicas": { - ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, - Spec: api.ReplicationControllerSpec{ - Replicas: -1, - Selector: validSelector, - }, - }, - "invalid_label": { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc-123", - Namespace: metav1.NamespaceDefault, - Labels: map[string]string{ - "NoUppercaseOrSpecialCharsLike=Equals": "bar", - }, - }, - Spec: api.ReplicationControllerSpec{ - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - }, - "invalid_label 2": { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc-123", - Namespace: metav1.NamespaceDefault, - Labels: map[string]string{ - "NoUppercaseOrSpecialCharsLike=Equals": "bar", - }, - }, - Spec: api.ReplicationControllerSpec{ - Template: &invalidPodTemplate.Template, - }, - }, - "invalid_annotation": { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc-123", - Namespace: metav1.NamespaceDefault, - Annotations: map[string]string{ - "NoUppercaseOrSpecialCharsLike=Equals": "bar", - }, - }, - Spec: api.ReplicationControllerSpec{ - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - }, - "invalid restart policy 1": { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc-123", - Namespace: metav1.NamespaceDefault, - }, - Spec: api.ReplicationControllerSpec{ - Selector: validSelector, - Template: &api.PodTemplateSpec{ - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyOnFailure, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - }, - ObjectMeta: metav1.ObjectMeta{ - Labels: validSelector, - }, - }, - }, - }, - "invalid restart policy 2": { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc-123", - Namespace: metav1.NamespaceDefault, - }, - Spec: api.ReplicationControllerSpec{ - Selector: validSelector, - Template: &api.PodTemplateSpec{ - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyNever, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, - }, - ObjectMeta: metav1.ObjectMeta{ - Labels: validSelector, - }, - }, - }, - }, - } - for k, v := range errorCases { - errs := ValidateReplicationController(&v) - if len(errs) == 0 { - t.Errorf("expected failure for %s", k) - } - for i := range errs { - field := errs[i].Field - if !strings.HasPrefix(field, "spec.template.") && - field != "metadata.name" && - field != "metadata.namespace" && - field != "spec.selector" && - field != "spec.template" && - field != "GCEPersistentDisk.ReadOnly" && - field != "spec.replicas" && - field != "spec.template.labels" && - field != "metadata.annotations" && - field != "metadata.labels" && - field != "status.replicas" { - t.Errorf("%s: missing prefix for: %v", k, errs[i]) - } - } - } -} - -func TestValidateNode(t *testing.T) { - validSelector := map[string]string{"a": "b"} - invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} - successCases := []api.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc", - Labels: validSelector, - }, - Status: api.NodeStatus{ - Addresses: []api.NodeAddress{ - {Type: api.NodeExternalIP, Address: "something"}, - }, - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), - api.ResourceName("my.org/gpu"): resource.MustParse("10"), - api.ResourceName("hugepages-2Mi"): resource.MustParse("10Gi"), - api.ResourceName("hugepages-1Gi"): resource.MustParse("0"), - }, - }, - Spec: api.NodeSpec{ - ExternalID: "external", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc", - }, - Status: api.NodeStatus{ - Addresses: []api.NodeAddress{ - {Type: api.NodeExternalIP, Address: "something"}, - }, - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("0"), - }, - }, - Spec: api.NodeSpec{ - ExternalID: "external", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "dedicated-node1", - }, - Status: api.NodeStatus{ - Addresses: []api.NodeAddress{ - {Type: api.NodeExternalIP, Address: "something"}, - }, - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("0"), - }, - }, - Spec: api.NodeSpec{ - ExternalID: "external", - // Add a valid taint to a node - Taints: []api.Taint{{Key: "GPU", Value: "true", Effect: "NoSchedule"}}, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc", - Annotations: map[string]string{ - api.PreferAvoidPodsAnnotationKey: ` - { - "preferAvoidPods": [ - { - "podSignature": { - "podController": { - "apiVersion": "v1", - "kind": "ReplicationController", - "name": "foo", - "uid": "abcdef123456", - "controller": true - } - }, - "reason": "some reason", - "message": "some message" - } - ] - }`, - }, - }, - Status: api.NodeStatus{ - Addresses: []api.NodeAddress{ - {Type: api.NodeExternalIP, Address: "something"}, - }, - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("0"), - }, - }, - Spec: api.NodeSpec{ - ExternalID: "external", - }, - }, - } - for _, successCase := range successCases { - if errs := ValidateNode(&successCase); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - } - - errorCases := map[string]api.Node{ - "zero-length Name": { - ObjectMeta: metav1.ObjectMeta{ - Name: "", - Labels: validSelector, - }, - Status: api.NodeStatus{ - Addresses: []api.NodeAddress{}, - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), - }, - }, - Spec: api.NodeSpec{ - ExternalID: "external", - }, - }, - "invalid-labels": { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc-123", - Labels: invalidSelector, - }, - Status: api.NodeStatus{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), - }, - }, - Spec: api.NodeSpec{ - ExternalID: "external", - }, - }, - "missing-external-id": { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc-123", - Labels: validSelector, - }, - Status: api.NodeStatus{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), - }, - }, - }, - "missing-taint-key": { - ObjectMeta: metav1.ObjectMeta{ - Name: "dedicated-node1", - }, - Spec: api.NodeSpec{ - ExternalID: "external", - // Add a taint with an empty key to a node - Taints: []api.Taint{{Key: "", Value: "special-user-1", Effect: "NoSchedule"}}, - }, - }, - "bad-taint-key": { - ObjectMeta: metav1.ObjectMeta{ - Name: "dedicated-node1", - }, - Spec: api.NodeSpec{ - ExternalID: "external", - // Add a taint with an invalid key to a node - Taints: []api.Taint{{Key: "NoUppercaseOrSpecialCharsLike=Equals", Value: "special-user-1", Effect: "NoSchedule"}}, - }, - }, - "bad-taint-value": { - ObjectMeta: metav1.ObjectMeta{ - Name: "dedicated-node2", - }, - Status: api.NodeStatus{ - Addresses: []api.NodeAddress{ - {Type: api.NodeExternalIP, Address: "something"}, - }, - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("0"), - }, - }, - Spec: api.NodeSpec{ - ExternalID: "external", - // Add a taint with a bad value to a node - Taints: []api.Taint{{Key: "dedicated", Value: "some\\bad\\value", Effect: "NoSchedule"}}, - }, - }, - "missing-taint-effect": { - ObjectMeta: metav1.ObjectMeta{ - Name: "dedicated-node3", - }, - Status: api.NodeStatus{ - Addresses: []api.NodeAddress{ - {Type: api.NodeExternalIP, Address: "something"}, - }, - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("0"), - }, - }, - Spec: api.NodeSpec{ - ExternalID: "external", - // Add a taint with an empty effect to a node - Taints: []api.Taint{{Key: "dedicated", Value: "special-user-3", Effect: ""}}, - }, - }, - "invalid-taint-effect": { - ObjectMeta: metav1.ObjectMeta{ - Name: "dedicated-node3", - }, - Status: api.NodeStatus{ - Addresses: []api.NodeAddress{ - {Type: api.NodeExternalIP, Address: "something"}, - }, - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("0"), - }, - }, - Spec: api.NodeSpec{ - ExternalID: "external", - // Add a taint with NoExecute effect to a node - Taints: []api.Taint{{Key: "dedicated", Value: "special-user-3", Effect: "NoScheduleNoAdmit"}}, - }, - }, - "duplicated-taints-with-same-key-effect": { - ObjectMeta: metav1.ObjectMeta{ - Name: "dedicated-node1", - }, - Spec: api.NodeSpec{ - ExternalID: "external", - // Add two taints to the node with the same key and effect; should be rejected. - Taints: []api.Taint{ - {Key: "dedicated", Value: "special-user-1", Effect: "NoSchedule"}, - {Key: "dedicated", Value: "special-user-2", Effect: "NoSchedule"}, - }, - }, - }, - "missing-podSignature": { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc-123", - Annotations: map[string]string{ - api.PreferAvoidPodsAnnotationKey: ` - { - "preferAvoidPods": [ - { - "reason": "some reason", - "message": "some message" - } - ] - }`, - }, - }, - Status: api.NodeStatus{ - Addresses: []api.NodeAddress{}, - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("0"), - }, - }, - Spec: api.NodeSpec{ - ExternalID: "external", - }, - }, - "invalid-podController": { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc-123", - Annotations: map[string]string{ - api.PreferAvoidPodsAnnotationKey: ` - { - "preferAvoidPods": [ - { - "podSignature": { - "podController": { - "apiVersion": "v1", - "kind": "ReplicationController", - "name": "foo", - "uid": "abcdef123456", - "controller": false - } - }, - "reason": "some reason", - "message": "some message" - } - ] - }`, - }, - }, - Status: api.NodeStatus{ - Addresses: []api.NodeAddress{}, - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("0"), - }, - }, - Spec: api.NodeSpec{ - ExternalID: "external", - }, - }, - "multiple-pre-allocated-hugepages": { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc", - Labels: validSelector, - }, - Status: api.NodeStatus{ - Addresses: []api.NodeAddress{ - {Type: api.NodeExternalIP, Address: "something"}, - }, - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), - api.ResourceName("my.org/gpu"): resource.MustParse("10"), - api.ResourceName("hugepages-2Mi"): resource.MustParse("10Gi"), - api.ResourceName("hugepages-1Gi"): resource.MustParse("10Gi"), - }, - }, - Spec: api.NodeSpec{ - ExternalID: "external", - }, - }, - } - for k, v := range errorCases { - errs := ValidateNode(&v) - if len(errs) == 0 { - t.Errorf("expected failure for %s", k) - } - for i := range errs { - field := errs[i].Field - expectedFields := map[string]bool{ - "metadata.name": true, - "metadata.labels": true, - "metadata.annotations": true, - "metadata.namespace": true, - "spec.externalID": true, - "spec.taints[0].key": true, - "spec.taints[0].value": true, - "spec.taints[0].effect": true, - "metadata.annotations.scheduler.alpha.kubernetes.io/preferAvoidPods[0].PodSignature": true, - "metadata.annotations.scheduler.alpha.kubernetes.io/preferAvoidPods[0].PodSignature.PodController.Controller": true, - } - if val, ok := expectedFields[field]; ok { - if !val { - t.Errorf("%s: missing prefix for: %v", k, errs[i]) - } - } - } - } -} - -func TestValidateNodeUpdate(t *testing.T) { - tests := []struct { - oldNode api.Node - node api.Node - valid bool - }{ - {api.Node{}, api.Node{}, true}, - {api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo"}}, - api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bar"}, - }, false}, - {api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Labels: map[string]string{"foo": "bar"}, - }, - }, api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Labels: map[string]string{"foo": "baz"}, - }, - }, true}, - {api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - }, api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Labels: map[string]string{"foo": "baz"}, - }, - }, true}, - {api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Labels: map[string]string{"bar": "foo"}, - }, - }, api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Labels: map[string]string{"foo": "baz"}, - }, - }, true}, - {api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.NodeSpec{ - PodCIDR: "", - }, - }, api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.NodeSpec{ - PodCIDR: "192.168.0.0/16", - }, - }, true}, - {api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.NodeSpec{ - PodCIDR: "192.123.0.0/16", - }, - }, api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.NodeSpec{ - PodCIDR: "192.168.0.0/16", - }, - }, false}, - {api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Status: api.NodeStatus{ - Capacity: api.ResourceList{ - api.ResourceCPU: resource.MustParse("10000"), - api.ResourceMemory: resource.MustParse("100"), - }, - }, - }, api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Status: api.NodeStatus{ - Capacity: api.ResourceList{ - api.ResourceCPU: resource.MustParse("100"), - api.ResourceMemory: resource.MustParse("10000"), - }, - }, - }, true}, - {api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Labels: map[string]string{"bar": "foo"}, - }, - Status: api.NodeStatus{ - Capacity: api.ResourceList{ - api.ResourceCPU: resource.MustParse("10000"), - api.ResourceMemory: resource.MustParse("100"), - }, - }, - }, api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Labels: map[string]string{"bar": "fooobaz"}, - }, - Status: api.NodeStatus{ - Capacity: api.ResourceList{ - api.ResourceCPU: resource.MustParse("100"), - api.ResourceMemory: resource.MustParse("10000"), - }, - }, - }, true}, - {api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Labels: map[string]string{"bar": "foo"}, - }, - Status: api.NodeStatus{ - Addresses: []api.NodeAddress{ - {Type: api.NodeExternalIP, Address: "1.2.3.4"}, - }, - }, - }, api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Labels: map[string]string{"bar": "fooobaz"}, - }, - }, true}, - {api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Labels: map[string]string{"foo": "baz"}, - }, - }, api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Labels: map[string]string{"Foo": "baz"}, - }, - }, true}, - {api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.NodeSpec{ - Unschedulable: false, - }, - }, api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.NodeSpec{ - Unschedulable: true, - }, - }, true}, - {api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.NodeSpec{ - Unschedulable: false, - }, - }, api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Status: api.NodeStatus{ - Addresses: []api.NodeAddress{ - {Type: api.NodeExternalIP, Address: "1.1.1.1"}, - {Type: api.NodeExternalIP, Address: "1.1.1.1"}, - }, - }, - }, false}, - {api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: api.NodeSpec{ - Unschedulable: false, - }, - }, api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Status: api.NodeStatus{ - Addresses: []api.NodeAddress{ - {Type: api.NodeExternalIP, Address: "1.1.1.1"}, - {Type: api.NodeInternalIP, Address: "10.1.1.1"}, - }, - }, - }, true}, - {api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - }, api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Annotations: map[string]string{ - api.PreferAvoidPodsAnnotationKey: ` - { - "preferAvoidPods": [ - { - "podSignature": { - "podController": { - "apiVersion": "v1", - "kind": "ReplicationController", - "name": "foo", - "uid": "abcdef123456", - "controller": true - } - }, - "reason": "some reason", - "message": "some message" - } - ] - }`, - }, - }, - Spec: api.NodeSpec{ - Unschedulable: false, - }, - }, true}, - {api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - }, api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Annotations: map[string]string{ - api.PreferAvoidPodsAnnotationKey: ` - { - "preferAvoidPods": [ - { - "reason": "some reason", - "message": "some message" - } - ] - }`, - }, - }, - }, false}, - {api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - }, api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Annotations: map[string]string{ - api.PreferAvoidPodsAnnotationKey: ` - { - "preferAvoidPods": [ - { - "podSignature": { - "podController": { - "apiVersion": "v1", - "kind": "ReplicationController", - "name": "foo", - "uid": "abcdef123456", - "controller": false - } - }, - "reason": "some reason", - "message": "some message" - } - ] - }`, - }, - }, - }, false}, - {api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "valid-opaque-int-resources", - }, - }, api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "valid-opaque-int-resources", - }, - Status: api.NodeStatus{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), - helper.OpaqueIntResourceName("A"): resource.MustParse("5"), - helper.OpaqueIntResourceName("B"): resource.MustParse("10"), - }, - }, - }, true}, - {api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "invalid-fractional-opaque-int-capacity", - }, - }, api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "invalid-fractional-opaque-int-capacity", - }, - Status: api.NodeStatus{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), - helper.OpaqueIntResourceName("A"): resource.MustParse("500m"), - }, - }, - }, false}, - {api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "invalid-fractional-opaque-int-allocatable", - }, - }, api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "invalid-fractional-opaque-int-allocatable", - }, - Status: api.NodeStatus{ - Capacity: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), - helper.OpaqueIntResourceName("A"): resource.MustParse("5"), - }, - Allocatable: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("10"), - api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), - helper.OpaqueIntResourceName("A"): resource.MustParse("4.5"), - }, - }, - }, false}, - {api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "update-provider-id-when-not-set", - }, - }, api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "update-provider-id-when-not-set", - }, - Spec: api.NodeSpec{ - ProviderID: "provider:///new", - }, - }, true}, - {api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "update-provider-id-when-set", - }, - Spec: api.NodeSpec{ - ProviderID: "provider:///old", - }, - }, api.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "update-provider-id-when-set", - }, - Spec: api.NodeSpec{ - ProviderID: "provider:///new", - }, - }, false}, - } - for i, test := range tests { - test.oldNode.ObjectMeta.ResourceVersion = "1" - test.node.ObjectMeta.ResourceVersion = "1" - errs := ValidateNodeUpdate(&test.node, &test.oldNode) - if test.valid && len(errs) > 0 { - t.Errorf("%d: Unexpected error: %v", i, errs) - t.Logf("%#v vs %#v", test.oldNode.ObjectMeta, test.node.ObjectMeta) - } - if !test.valid && len(errs) == 0 { - t.Errorf("%d: Unexpected non-error", i) - } - } -} - -func TestValidateServiceUpdate(t *testing.T) { - testCases := []struct { - name string - tweakSvc func(oldSvc, newSvc *api.Service) // given basic valid services, each test case can customize them - numErrs int - }{ - { - name: "no change", - tweakSvc: func(oldSvc, newSvc *api.Service) { - // do nothing - }, - numErrs: 0, - }, - { - name: "change name", - tweakSvc: func(oldSvc, newSvc *api.Service) { - newSvc.Name += "2" - }, - numErrs: 1, - }, - { - name: "change namespace", - tweakSvc: func(oldSvc, newSvc *api.Service) { - newSvc.Namespace += "2" - }, - numErrs: 1, - }, - { - name: "change label valid", - tweakSvc: func(oldSvc, newSvc *api.Service) { - newSvc.Labels["key"] = "other-value" - }, - numErrs: 0, - }, - { - name: "add label", - tweakSvc: func(oldSvc, newSvc *api.Service) { - newSvc.Labels["key2"] = "value2" - }, - numErrs: 0, - }, - { - name: "change cluster IP", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.ClusterIP = "1.2.3.4" - newSvc.Spec.ClusterIP = "8.6.7.5" - }, - numErrs: 1, - }, - { - name: "remove cluster IP", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.ClusterIP = "1.2.3.4" - newSvc.Spec.ClusterIP = "" - }, - numErrs: 1, - }, - { - name: "change affinity", - tweakSvc: func(oldSvc, newSvc *api.Service) { - newSvc.Spec.SessionAffinity = "ClientIP" - newSvc.Spec.SessionAffinityConfig = &api.SessionAffinityConfig{ - ClientIP: &api.ClientIPConfig{ - TimeoutSeconds: newInt32(90), - }, - } - }, - numErrs: 0, - }, - { - name: "remove affinity", - tweakSvc: func(oldSvc, newSvc *api.Service) { - newSvc.Spec.SessionAffinity = "" - }, - numErrs: 1, - }, - { - name: "change type", - tweakSvc: func(oldSvc, newSvc *api.Service) { - newSvc.Spec.Type = api.ServiceTypeLoadBalancer - }, - numErrs: 0, - }, - { - name: "remove type", - tweakSvc: func(oldSvc, newSvc *api.Service) { - newSvc.Spec.Type = "" - }, - numErrs: 1, - }, - { - name: "change type -> nodeport", - tweakSvc: func(oldSvc, newSvc *api.Service) { - newSvc.Spec.Type = api.ServiceTypeNodePort - }, - numErrs: 0, - }, - { - name: "add loadBalancerSourceRanges", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.Type = api.ServiceTypeLoadBalancer - newSvc.Spec.Type = api.ServiceTypeLoadBalancer - newSvc.Spec.LoadBalancerSourceRanges = []string{"10.0.0.0/8"} - }, - numErrs: 0, - }, - { - name: "update loadBalancerSourceRanges", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.Type = api.ServiceTypeLoadBalancer - oldSvc.Spec.LoadBalancerSourceRanges = []string{"10.0.0.0/8"} - newSvc.Spec.Type = api.ServiceTypeLoadBalancer - newSvc.Spec.LoadBalancerSourceRanges = []string{"10.100.0.0/16"} - }, - numErrs: 0, - }, - { - name: "LoadBalancer type cannot have None ClusterIP", - tweakSvc: func(oldSvc, newSvc *api.Service) { - newSvc.Spec.ClusterIP = "None" - newSvc.Spec.Type = api.ServiceTypeLoadBalancer - }, - numErrs: 1, - }, - { - name: "`None` ClusterIP cannot be changed", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.ClusterIP = "None" - newSvc.Spec.ClusterIP = "1.2.3.4" - }, - numErrs: 1, - }, - { - name: "`None` ClusterIP cannot be removed", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.ClusterIP = "None" - newSvc.Spec.ClusterIP = "" - }, - numErrs: 1, - }, - { - name: "Service with ClusterIP type cannot change its set ClusterIP", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.Type = api.ServiceTypeClusterIP - newSvc.Spec.Type = api.ServiceTypeClusterIP - - oldSvc.Spec.ClusterIP = "1.2.3.4" - newSvc.Spec.ClusterIP = "1.2.3.5" - }, - numErrs: 1, - }, - { - name: "Service with ClusterIP type can change its empty ClusterIP", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.Type = api.ServiceTypeClusterIP - newSvc.Spec.Type = api.ServiceTypeClusterIP - - oldSvc.Spec.ClusterIP = "" - newSvc.Spec.ClusterIP = "1.2.3.5" - }, - numErrs: 0, - }, - { - name: "Service with ClusterIP type cannot change its set ClusterIP when changing type to NodePort", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.Type = api.ServiceTypeClusterIP - newSvc.Spec.Type = api.ServiceTypeNodePort - - oldSvc.Spec.ClusterIP = "1.2.3.4" - newSvc.Spec.ClusterIP = "1.2.3.5" - }, - numErrs: 1, - }, - { - name: "Service with ClusterIP type can change its empty ClusterIP when changing type to NodePort", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.Type = api.ServiceTypeClusterIP - newSvc.Spec.Type = api.ServiceTypeNodePort - - oldSvc.Spec.ClusterIP = "" - newSvc.Spec.ClusterIP = "1.2.3.5" - }, - numErrs: 0, - }, - { - name: "Service with ClusterIP type cannot change its ClusterIP when changing type to LoadBalancer", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.Type = api.ServiceTypeClusterIP - newSvc.Spec.Type = api.ServiceTypeLoadBalancer - - oldSvc.Spec.ClusterIP = "1.2.3.4" - newSvc.Spec.ClusterIP = "1.2.3.5" - }, - numErrs: 1, - }, - { - name: "Service with ClusterIP type can change its empty ClusterIP when changing type to LoadBalancer", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.Type = api.ServiceTypeClusterIP - newSvc.Spec.Type = api.ServiceTypeLoadBalancer - - oldSvc.Spec.ClusterIP = "" - newSvc.Spec.ClusterIP = "1.2.3.5" - }, - numErrs: 0, - }, - { - name: "Service with NodePort type cannot change its set ClusterIP", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.Type = api.ServiceTypeNodePort - newSvc.Spec.Type = api.ServiceTypeNodePort - - oldSvc.Spec.ClusterIP = "1.2.3.4" - newSvc.Spec.ClusterIP = "1.2.3.5" - }, - numErrs: 1, - }, - { - name: "Service with NodePort type can change its empty ClusterIP", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.Type = api.ServiceTypeNodePort - newSvc.Spec.Type = api.ServiceTypeNodePort - - oldSvc.Spec.ClusterIP = "" - newSvc.Spec.ClusterIP = "1.2.3.5" - }, - numErrs: 0, - }, - { - name: "Service with NodePort type cannot change its set ClusterIP when changing type to ClusterIP", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.Type = api.ServiceTypeNodePort - newSvc.Spec.Type = api.ServiceTypeClusterIP - - oldSvc.Spec.ClusterIP = "1.2.3.4" - newSvc.Spec.ClusterIP = "1.2.3.5" - }, - numErrs: 1, - }, - { - name: "Service with NodePort type can change its empty ClusterIP when changing type to ClusterIP", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.Type = api.ServiceTypeNodePort - newSvc.Spec.Type = api.ServiceTypeClusterIP - - oldSvc.Spec.ClusterIP = "" - newSvc.Spec.ClusterIP = "1.2.3.5" - }, - numErrs: 0, - }, - { - name: "Service with NodePort type cannot change its set ClusterIP when changing type to LoadBalancer", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.Type = api.ServiceTypeNodePort - newSvc.Spec.Type = api.ServiceTypeLoadBalancer - - oldSvc.Spec.ClusterIP = "1.2.3.4" - newSvc.Spec.ClusterIP = "1.2.3.5" - }, - numErrs: 1, - }, - { - name: "Service with NodePort type can change its empty ClusterIP when changing type to LoadBalancer", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.Type = api.ServiceTypeNodePort - newSvc.Spec.Type = api.ServiceTypeLoadBalancer - - oldSvc.Spec.ClusterIP = "" - newSvc.Spec.ClusterIP = "1.2.3.5" - }, - numErrs: 0, - }, - { - name: "Service with LoadBalancer type cannot change its set ClusterIP", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.Type = api.ServiceTypeLoadBalancer - newSvc.Spec.Type = api.ServiceTypeLoadBalancer - - oldSvc.Spec.ClusterIP = "1.2.3.4" - newSvc.Spec.ClusterIP = "1.2.3.5" - }, - numErrs: 1, - }, - { - name: "Service with LoadBalancer type can change its empty ClusterIP", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.Type = api.ServiceTypeLoadBalancer - newSvc.Spec.Type = api.ServiceTypeLoadBalancer - - oldSvc.Spec.ClusterIP = "" - newSvc.Spec.ClusterIP = "1.2.3.5" - }, - numErrs: 0, - }, - { - name: "Service with LoadBalancer type cannot change its set ClusterIP when changing type to ClusterIP", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.Type = api.ServiceTypeLoadBalancer - newSvc.Spec.Type = api.ServiceTypeClusterIP - - oldSvc.Spec.ClusterIP = "1.2.3.4" - newSvc.Spec.ClusterIP = "1.2.3.5" - }, - numErrs: 1, - }, - { - name: "Service with LoadBalancer type can change its empty ClusterIP when changing type to ClusterIP", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.Type = api.ServiceTypeLoadBalancer - newSvc.Spec.Type = api.ServiceTypeClusterIP - - oldSvc.Spec.ClusterIP = "" - newSvc.Spec.ClusterIP = "1.2.3.5" - }, - numErrs: 0, - }, - { - name: "Service with LoadBalancer type cannot change its set ClusterIP when changing type to NodePort", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.Type = api.ServiceTypeLoadBalancer - newSvc.Spec.Type = api.ServiceTypeNodePort - - oldSvc.Spec.ClusterIP = "1.2.3.4" - newSvc.Spec.ClusterIP = "1.2.3.5" - }, - numErrs: 1, - }, - { - name: "Service with LoadBalancer type can change its empty ClusterIP when changing type to NodePort", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.Type = api.ServiceTypeLoadBalancer - newSvc.Spec.Type = api.ServiceTypeNodePort - - oldSvc.Spec.ClusterIP = "" - newSvc.Spec.ClusterIP = "1.2.3.5" - }, - numErrs: 0, - }, - { - name: "Service with ExternalName type can change its empty ClusterIP when changing type to ClusterIP", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.Type = api.ServiceTypeExternalName - newSvc.Spec.Type = api.ServiceTypeClusterIP - - oldSvc.Spec.ClusterIP = "" - newSvc.Spec.ClusterIP = "1.2.3.5" - }, - numErrs: 0, - }, - { - name: "Service with ExternalName type can change its set ClusterIP when changing type to ClusterIP", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.Type = api.ServiceTypeExternalName - newSvc.Spec.Type = api.ServiceTypeClusterIP - - oldSvc.Spec.ClusterIP = "1.2.3.4" - newSvc.Spec.ClusterIP = "1.2.3.5" - }, - numErrs: 0, - }, - { - name: "invalid node port with clusterIP None", - tweakSvc: func(oldSvc, newSvc *api.Service) { - oldSvc.Spec.Type = api.ServiceTypeNodePort - newSvc.Spec.Type = api.ServiceTypeNodePort - - oldSvc.Spec.Ports = append(oldSvc.Spec.Ports, api.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(1)}) - newSvc.Spec.Ports = append(newSvc.Spec.Ports, api.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(1)}) - - oldSvc.Spec.ClusterIP = "" - newSvc.Spec.ClusterIP = "None" - }, - numErrs: 1, - }, - } - - for _, tc := range testCases { - oldSvc := makeValidService() - newSvc := makeValidService() - tc.tweakSvc(&oldSvc, &newSvc) - errs := ValidateServiceUpdate(&newSvc, &oldSvc) - if len(errs) != tc.numErrs { - t.Errorf("Unexpected error list for case %q: %v", tc.name, errs.ToAggregate()) - } - } -} - -func TestValidateResourceNames(t *testing.T) { - table := []struct { - input string - success bool - expect string - }{ - {"memory", true, ""}, - {"cpu", true, ""}, - {"storage", true, ""}, - {"requests.cpu", true, ""}, - {"requests.memory", true, ""}, - {"requests.storage", true, ""}, - {"limits.cpu", true, ""}, - {"limits.memory", true, ""}, - {"network", false, ""}, - {"disk", false, ""}, - {"", false, ""}, - {".", false, ""}, - {"..", false, ""}, - {"my.favorite.app.co/12345", true, ""}, - {"my.favorite.app.co/_12345", false, ""}, - {"my.favorite.app.co/12345_", false, ""}, - {"kubernetes.io/..", false, ""}, - {"kubernetes.io/" + strings.Repeat("a", 63), true, ""}, - {"kubernetes.io/" + strings.Repeat("a", 64), false, ""}, - {"kubernetes.io//", false, ""}, - {"kubernetes.io", false, ""}, - {"kubernetes.io/will/not/work/", false, ""}, - } - for k, item := range table { - err := validateResourceName(item.input, field.NewPath("field")) - if len(err) != 0 && item.success { - t.Errorf("expected no failure for input %q", item.input) - } else if len(err) == 0 && !item.success { - t.Errorf("expected failure for input %q", item.input) - for i := range err { - detail := err[i].Detail - if detail != "" && !strings.Contains(detail, item.expect) { - t.Errorf("%d: expected error detail either empty or %s, got %s", k, item.expect, detail) - } - } - } - } -} - -func getResourceList(cpu, memory string) api.ResourceList { - res := api.ResourceList{} - if cpu != "" { - res[api.ResourceCPU] = resource.MustParse(cpu) - } - if memory != "" { - res[api.ResourceMemory] = resource.MustParse(memory) - } - return res -} - -func getStorageResourceList(storage string) api.ResourceList { - res := api.ResourceList{} - if storage != "" { - res[api.ResourceStorage] = resource.MustParse(storage) - } - return res -} - -func getLocalStorageResourceList(ephemeralStorage string) api.ResourceList { - res := api.ResourceList{} - if ephemeralStorage != "" { - res[api.ResourceEphemeralStorage] = resource.MustParse(ephemeralStorage) - } - return res -} - -func TestValidateLimitRangeForLocalStorage(t *testing.T) { - testCases := []struct { - name string - spec api.LimitRangeSpec - }{ - { - name: "all-fields-valid", - spec: api.LimitRangeSpec{ - Limits: []api.LimitRangeItem{ - { - Type: api.LimitTypePod, - Max: getLocalStorageResourceList("10000Mi"), - Min: getLocalStorageResourceList("100Mi"), - MaxLimitRequestRatio: getLocalStorageResourceList(""), - }, - { - Type: api.LimitTypeContainer, - Max: getLocalStorageResourceList("10000Mi"), - Min: getLocalStorageResourceList("100Mi"), - Default: getLocalStorageResourceList("500Mi"), - DefaultRequest: getLocalStorageResourceList("200Mi"), - MaxLimitRequestRatio: getLocalStorageResourceList(""), - }, - }, - }, - }, - } - - // Enable alpha feature LocalStorageCapacityIsolation - err := utilfeature.DefaultFeatureGate.Set("LocalStorageCapacityIsolation=true") - if err != nil { - t.Errorf("Failed to enable feature gate for LocalStorageCapacityIsolation: %v", err) - return - } - - for _, testCase := range testCases { - limitRange := &api.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: testCase.name, Namespace: "foo"}, Spec: testCase.spec} - if errs := ValidateLimitRange(limitRange); len(errs) != 0 { - t.Errorf("Case %v, unexpected error: %v", testCase.name, errs) - } - } - - // Disable alpha feature LocalStorageCapacityIsolation - err = utilfeature.DefaultFeatureGate.Set("LocalStorageCapacityIsolation=false") - if err != nil { - t.Errorf("Failed to disable feature gate for LocalStorageCapacityIsolation: %v", err) - return - } - for _, testCase := range testCases { - limitRange := &api.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: testCase.name, Namespace: "foo"}, Spec: testCase.spec} - if errs := ValidateLimitRange(limitRange); len(errs) == 0 { - t.Errorf("Case %v, expected feature gate unable error but actually no error", testCase.name) - } - } - -} - -func TestValidateLimitRange(t *testing.T) { - successCases := []struct { - name string - spec api.LimitRangeSpec - }{ - { - name: "all-fields-valid", - spec: api.LimitRangeSpec{ - Limits: []api.LimitRangeItem{ - { - Type: api.LimitTypePod, - Max: getResourceList("100m", "10000Mi"), - Min: getResourceList("5m", "100Mi"), - MaxLimitRequestRatio: getResourceList("10", ""), - }, - { - Type: api.LimitTypeContainer, - Max: getResourceList("100m", "10000Mi"), - Min: getResourceList("5m", "100Mi"), - Default: getResourceList("50m", "500Mi"), - DefaultRequest: getResourceList("10m", "200Mi"), - MaxLimitRequestRatio: getResourceList("10", ""), - }, - { - Type: api.LimitTypePersistentVolumeClaim, - Max: getStorageResourceList("10Gi"), - Min: getStorageResourceList("5Gi"), - }, - }, - }, - }, - { - name: "pvc-min-only", - spec: api.LimitRangeSpec{ - Limits: []api.LimitRangeItem{ - { - Type: api.LimitTypePersistentVolumeClaim, - Min: getStorageResourceList("5Gi"), - }, - }, - }, - }, - { - name: "pvc-max-only", - spec: api.LimitRangeSpec{ - Limits: []api.LimitRangeItem{ - { - Type: api.LimitTypePersistentVolumeClaim, - Max: getStorageResourceList("10Gi"), - }, - }, - }, - }, - { - name: "all-fields-valid-big-numbers", - spec: api.LimitRangeSpec{ - Limits: []api.LimitRangeItem{ - { - Type: api.LimitTypeContainer, - Max: getResourceList("100m", "10000T"), - Min: getResourceList("5m", "100Mi"), - Default: getResourceList("50m", "500Mi"), - DefaultRequest: getResourceList("10m", "200Mi"), - MaxLimitRequestRatio: getResourceList("10", ""), - }, - }, - }, - }, - { - name: "thirdparty-fields-all-valid-standard-container-resources", - spec: api.LimitRangeSpec{ - Limits: []api.LimitRangeItem{ - { - Type: "thirdparty.com/foo", - Max: getResourceList("100m", "10000T"), - Min: getResourceList("5m", "100Mi"), - Default: getResourceList("50m", "500Mi"), - DefaultRequest: getResourceList("10m", "200Mi"), - MaxLimitRequestRatio: getResourceList("10", ""), - }, - }, - }, - }, - { - name: "thirdparty-fields-all-valid-storage-resources", - spec: api.LimitRangeSpec{ - Limits: []api.LimitRangeItem{ - { - Type: "thirdparty.com/foo", - Max: getStorageResourceList("10000T"), - Min: getStorageResourceList("100Mi"), - Default: getStorageResourceList("500Mi"), - DefaultRequest: getStorageResourceList("200Mi"), - MaxLimitRequestRatio: getStorageResourceList(""), - }, - }, - }, - }, - } - - for _, successCase := range successCases { - limitRange := &api.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: successCase.name, Namespace: "foo"}, Spec: successCase.spec} - if errs := ValidateLimitRange(limitRange); len(errs) != 0 { - t.Errorf("Case %v, unexpected error: %v", successCase.name, errs) - } - } - - errorCases := map[string]struct { - R api.LimitRange - D string - }{ - "zero-length-name": { - api.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: "foo"}, Spec: api.LimitRangeSpec{}}, - "name or generateName is required", - }, - "zero-length-namespace": { - api.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: ""}, Spec: api.LimitRangeSpec{}}, - "", - }, - "invalid-name": { - api.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "^Invalid", Namespace: "foo"}, Spec: api.LimitRangeSpec{}}, - dnsSubdomainLabelErrMsg, - }, - "invalid-namespace": { - api.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "^Invalid"}, Spec: api.LimitRangeSpec{}}, - dnsLabelErrMsg, - }, - "duplicate-limit-type": { - api.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: api.LimitRangeSpec{ - Limits: []api.LimitRangeItem{ - { - Type: api.LimitTypePod, - Max: getResourceList("100m", "10000m"), - Min: getResourceList("0m", "100m"), - }, - { - Type: api.LimitTypePod, - Min: getResourceList("0m", "100m"), - }, - }, - }}, - "", - }, - "default-limit-type-pod": { - api.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: api.LimitRangeSpec{ - Limits: []api.LimitRangeItem{ - { - Type: api.LimitTypePod, - Max: getResourceList("100m", "10000m"), - Min: getResourceList("0m", "100m"), - Default: getResourceList("10m", "100m"), - }, - }, - }}, - "may not be specified when `type` is 'Pod'", - }, - "default-request-limit-type-pod": { - api.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: api.LimitRangeSpec{ - Limits: []api.LimitRangeItem{ - { - Type: api.LimitTypePod, - Max: getResourceList("100m", "10000m"), - Min: getResourceList("0m", "100m"), - DefaultRequest: getResourceList("10m", "100m"), - }, - }, - }}, - "may not be specified when `type` is 'Pod'", - }, - "min value 100m is greater than max value 10m": { - api.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: api.LimitRangeSpec{ - Limits: []api.LimitRangeItem{ - { - Type: api.LimitTypePod, - Max: getResourceList("10m", ""), - Min: getResourceList("100m", ""), - }, - }, - }}, - "min value 100m is greater than max value 10m", - }, - "invalid spec default outside range": { - api.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: api.LimitRangeSpec{ - Limits: []api.LimitRangeItem{ - { - Type: api.LimitTypeContainer, - Max: getResourceList("1", ""), - Min: getResourceList("100m", ""), - Default: getResourceList("2000m", ""), - }, - }, - }}, - "default value 2 is greater than max value 1", - }, - "invalid spec default request outside range": { - api.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: api.LimitRangeSpec{ - Limits: []api.LimitRangeItem{ - { - Type: api.LimitTypeContainer, - Max: getResourceList("1", ""), - Min: getResourceList("100m", ""), - DefaultRequest: getResourceList("2000m", ""), - }, - }, - }}, - "default request value 2 is greater than max value 1", - }, - "invalid spec default request more than default": { - api.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: api.LimitRangeSpec{ - Limits: []api.LimitRangeItem{ - { - Type: api.LimitTypeContainer, - Max: getResourceList("2", ""), - Min: getResourceList("100m", ""), - Default: getResourceList("500m", ""), - DefaultRequest: getResourceList("800m", ""), - }, - }, - }}, - "default request value 800m is greater than default limit value 500m", - }, - "invalid spec maxLimitRequestRatio less than 1": { - api.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: api.LimitRangeSpec{ - Limits: []api.LimitRangeItem{ - { - Type: api.LimitTypePod, - MaxLimitRequestRatio: getResourceList("800m", ""), - }, - }, - }}, - "ratio 800m is less than 1", - }, - "invalid spec maxLimitRequestRatio greater than max/min": { - api.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: api.LimitRangeSpec{ - Limits: []api.LimitRangeItem{ - { - Type: api.LimitTypeContainer, - Max: getResourceList("", "2Gi"), - Min: getResourceList("", "512Mi"), - MaxLimitRequestRatio: getResourceList("", "10"), - }, - }, - }}, - "ratio 10 is greater than max/min = 4.000000", - }, - "invalid non standard limit type": { - api.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: api.LimitRangeSpec{ - Limits: []api.LimitRangeItem{ - { - Type: "foo", - Max: getStorageResourceList("10000T"), - Min: getStorageResourceList("100Mi"), - Default: getStorageResourceList("500Mi"), - DefaultRequest: getStorageResourceList("200Mi"), - MaxLimitRequestRatio: getStorageResourceList(""), - }, - }, - }}, - "must be a standard limit type or fully qualified", - }, - "min and max values missing, one required": { - api.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: api.LimitRangeSpec{ - Limits: []api.LimitRangeItem{ - { - Type: api.LimitTypePersistentVolumeClaim, - }, - }, - }}, - "either minimum or maximum storage value is required, but neither was provided", - }, - "invalid min greater than max": { - api.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: api.LimitRangeSpec{ - Limits: []api.LimitRangeItem{ - { - Type: api.LimitTypePersistentVolumeClaim, - Min: getStorageResourceList("10Gi"), - Max: getStorageResourceList("1Gi"), - }, - }, - }}, - "min value 10Gi is greater than max value 1Gi", - }, - } - - for k, v := range errorCases { - errs := ValidateLimitRange(&v.R) - if len(errs) == 0 { - t.Errorf("expected failure for %s", k) - } - for i := range errs { - detail := errs[i].Detail - if !strings.Contains(detail, v.D) { - t.Errorf("[%s]: expected error detail either empty or %q, got %q", k, v.D, detail) - } - } - } - -} - -func TestValidatePersistentVolumeClaimStatusUpdate(t *testing.T) { - validClaim := testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{ - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadWriteOnce, - api.ReadOnlyMany, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - }, - }) - validConditionUpdate := testVolumeClaimWithStatus("foo", "ns", api.PersistentVolumeClaimSpec{ - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadWriteOnce, - api.ReadOnlyMany, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - }, - }, api.PersistentVolumeClaimStatus{ - Phase: api.ClaimPending, - Conditions: []api.PersistentVolumeClaimCondition{ - {Type: api.PersistentVolumeClaimResizing, Status: api.ConditionTrue}, - }, - }) - scenarios := map[string]struct { - isExpectedFailure bool - oldClaim *api.PersistentVolumeClaim - newClaim *api.PersistentVolumeClaim - enableResize bool - }{ - "condition-update-with-disabled-feature-gate": { - isExpectedFailure: true, - oldClaim: validClaim, - newClaim: validConditionUpdate, - enableResize: false, - }, - "condition-update-with-enabled-feature-gate": { - isExpectedFailure: false, - oldClaim: validClaim, - newClaim: validConditionUpdate, - enableResize: true, - }, - } - for name, scenario := range scenarios { - // ensure we have a resource version specified for updates - togglePVExpandFeature(scenario.enableResize, t) - scenario.oldClaim.ResourceVersion = "1" - scenario.newClaim.ResourceVersion = "1" - errs := ValidatePersistentVolumeClaimStatusUpdate(scenario.newClaim, scenario.oldClaim) - if len(errs) == 0 && scenario.isExpectedFailure { - t.Errorf("Unexpected success for scenario: %s", name) - } - if len(errs) > 0 && !scenario.isExpectedFailure { - t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) - } - } -} - -func TestValidateResourceQuota(t *testing.T) { - spec := api.ResourceQuotaSpec{ - Hard: api.ResourceList{ - api.ResourceCPU: resource.MustParse("100"), - api.ResourceMemory: resource.MustParse("10000"), - api.ResourceRequestsCPU: resource.MustParse("100"), - api.ResourceRequestsMemory: resource.MustParse("10000"), - api.ResourceLimitsCPU: resource.MustParse("100"), - api.ResourceLimitsMemory: resource.MustParse("10000"), - api.ResourcePods: resource.MustParse("10"), - api.ResourceServices: resource.MustParse("0"), - api.ResourceReplicationControllers: resource.MustParse("10"), - api.ResourceQuotas: resource.MustParse("10"), - api.ResourceConfigMaps: resource.MustParse("10"), - api.ResourceSecrets: resource.MustParse("10"), - }, - } - - terminatingSpec := api.ResourceQuotaSpec{ - Hard: api.ResourceList{ - api.ResourceCPU: resource.MustParse("100"), - api.ResourceLimitsCPU: resource.MustParse("200"), - }, - Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeTerminating}, - } - - nonTerminatingSpec := api.ResourceQuotaSpec{ - Hard: api.ResourceList{ - api.ResourceCPU: resource.MustParse("100"), - }, - Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeNotTerminating}, - } - - bestEffortSpec := api.ResourceQuotaSpec{ - Hard: api.ResourceList{ - api.ResourcePods: resource.MustParse("100"), - }, - Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeBestEffort}, - } - - nonBestEffortSpec := api.ResourceQuotaSpec{ - Hard: api.ResourceList{ - api.ResourceCPU: resource.MustParse("100"), - }, - Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeNotBestEffort}, - } - - // storage is not yet supported as a quota tracked resource - invalidQuotaResourceSpec := api.ResourceQuotaSpec{ - Hard: api.ResourceList{ - api.ResourceStorage: resource.MustParse("10"), - }, - } - - negativeSpec := api.ResourceQuotaSpec{ - Hard: api.ResourceList{ - api.ResourceCPU: resource.MustParse("-100"), - api.ResourceMemory: resource.MustParse("-10000"), - api.ResourcePods: resource.MustParse("-10"), - api.ResourceServices: resource.MustParse("-10"), - api.ResourceReplicationControllers: resource.MustParse("-10"), - api.ResourceQuotas: resource.MustParse("-10"), - api.ResourceConfigMaps: resource.MustParse("-10"), - api.ResourceSecrets: resource.MustParse("-10"), - }, - } - - fractionalComputeSpec := api.ResourceQuotaSpec{ - Hard: api.ResourceList{ - api.ResourceCPU: resource.MustParse("100m"), - }, - } - - fractionalPodSpec := api.ResourceQuotaSpec{ - Hard: api.ResourceList{ - api.ResourcePods: resource.MustParse(".1"), - api.ResourceServices: resource.MustParse(".5"), - api.ResourceReplicationControllers: resource.MustParse("1.25"), - api.ResourceQuotas: resource.MustParse("2.5"), - }, - } - - invalidTerminatingScopePairsSpec := api.ResourceQuotaSpec{ - Hard: api.ResourceList{ - api.ResourceCPU: resource.MustParse("100"), - }, - Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeTerminating, api.ResourceQuotaScopeNotTerminating}, - } - - invalidBestEffortScopePairsSpec := api.ResourceQuotaSpec{ - Hard: api.ResourceList{ - api.ResourcePods: resource.MustParse("100"), - }, - Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeBestEffort, api.ResourceQuotaScopeNotBestEffort}, - } - - invalidScopeNameSpec := api.ResourceQuotaSpec{ - Hard: api.ResourceList{ - api.ResourceCPU: resource.MustParse("100"), - }, - Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScope("foo")}, - } - - successCases := []api.ResourceQuota{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc", - Namespace: "foo", - }, - Spec: spec, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc", - Namespace: "foo", - }, - Spec: fractionalComputeSpec, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc", - Namespace: "foo", - }, - Spec: terminatingSpec, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc", - Namespace: "foo", - }, - Spec: nonTerminatingSpec, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc", - Namespace: "foo", - }, - Spec: bestEffortSpec, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "abc", - Namespace: "foo", - }, - Spec: nonBestEffortSpec, - }, - } - - for _, successCase := range successCases { - if errs := ValidateResourceQuota(&successCase); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - } - - errorCases := map[string]struct { - R api.ResourceQuota - D string - }{ - "zero-length Name": { - api.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: "foo"}, Spec: spec}, - "name or generateName is required", - }, - "zero-length Namespace": { - api.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: ""}, Spec: spec}, - "", - }, - "invalid Name": { - api.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "^Invalid", Namespace: "foo"}, Spec: spec}, - dnsSubdomainLabelErrMsg, - }, - "invalid Namespace": { - api.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "^Invalid"}, Spec: spec}, - dnsLabelErrMsg, - }, - "negative-limits": { - api.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: negativeSpec}, - isNegativeErrorMsg, - }, - "fractional-api-resource": { - api.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: fractionalPodSpec}, - isNotIntegerErrorMsg, - }, - "invalid-quota-resource": { - api.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidQuotaResourceSpec}, - isInvalidQuotaResource, - }, - "invalid-quota-terminating-pair": { - api.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidTerminatingScopePairsSpec}, - "conflicting scopes", - }, - "invalid-quota-besteffort-pair": { - api.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidBestEffortScopePairsSpec}, - "conflicting scopes", - }, - "invalid-quota-scope-name": { - api.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidScopeNameSpec}, - "unsupported scope", - }, - } - for k, v := range errorCases { - errs := ValidateResourceQuota(&v.R) - if len(errs) == 0 { - t.Errorf("expected failure for %s", k) - } - for i := range errs { - if !strings.Contains(errs[i].Detail, v.D) { - t.Errorf("[%s]: expected error detail either empty or %s, got %s", k, v.D, errs[i].Detail) - } - } - } -} - -func TestValidateNamespace(t *testing.T) { - validLabels := map[string]string{"a": "b"} - invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} - successCases := []api.Namespace{ - { - ObjectMeta: metav1.ObjectMeta{Name: "abc", Labels: validLabels}, - }, - { - ObjectMeta: metav1.ObjectMeta{Name: "abc-123"}, - Spec: api.NamespaceSpec{ - Finalizers: []api.FinalizerName{"example.com/something", "example.com/other"}, - }, - }, - } - for _, successCase := range successCases { - if errs := ValidateNamespace(&successCase); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - } - errorCases := map[string]struct { - R api.Namespace - D string - }{ - "zero-length name": { - api.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ""}}, - "", - }, - "defined-namespace": { - api.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: "makesnosense"}}, - "", - }, - "invalid-labels": { - api.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "abc", Labels: invalidLabels}}, - "", - }, - } - for k, v := range errorCases { - errs := ValidateNamespace(&v.R) - if len(errs) == 0 { - t.Errorf("expected failure for %s", k) - } - } -} - -func TestValidateNamespaceFinalizeUpdate(t *testing.T) { - tests := []struct { - oldNamespace api.Namespace - namespace api.Namespace - valid bool - }{ - {api.Namespace{}, api.Namespace{}, true}, - {api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo"}}, - api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo"}, - Spec: api.NamespaceSpec{ - Finalizers: []api.FinalizerName{"Foo"}, - }, - }, false}, - {api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo"}, - Spec: api.NamespaceSpec{ - Finalizers: []api.FinalizerName{"foo.com/bar"}, - }, - }, - api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo"}, - Spec: api.NamespaceSpec{ - Finalizers: []api.FinalizerName{"foo.com/bar", "what.com/bar"}, - }, - }, true}, - {api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "fooemptyfinalizer"}, - Spec: api.NamespaceSpec{ - Finalizers: []api.FinalizerName{"foo.com/bar"}, - }, - }, - api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "fooemptyfinalizer"}, - Spec: api.NamespaceSpec{ - Finalizers: []api.FinalizerName{"", "foo.com/bar", "what.com/bar"}, - }, - }, false}, - } - for i, test := range tests { - test.namespace.ObjectMeta.ResourceVersion = "1" - test.oldNamespace.ObjectMeta.ResourceVersion = "1" - errs := ValidateNamespaceFinalizeUpdate(&test.namespace, &test.oldNamespace) - if test.valid && len(errs) > 0 { - t.Errorf("%d: Unexpected error: %v", i, errs) - t.Logf("%#v vs %#v", test.oldNamespace, test.namespace) - } - if !test.valid && len(errs) == 0 { - t.Errorf("%d: Unexpected non-error", i) - } - } -} - -func TestValidateNamespaceStatusUpdate(t *testing.T) { - now := metav1.Now() - - tests := []struct { - oldNamespace api.Namespace - namespace api.Namespace - valid bool - }{ - {api.Namespace{}, api.Namespace{ - Status: api.NamespaceStatus{ - Phase: api.NamespaceActive, - }, - }, true}, - // Cannot set deletionTimestamp via status update - {api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo"}}, - api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - DeletionTimestamp: &now}, - Status: api.NamespaceStatus{ - Phase: api.NamespaceTerminating, - }, - }, false}, - // Can update phase via status update - {api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - DeletionTimestamp: &now}}, - api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - DeletionTimestamp: &now}, - Status: api.NamespaceStatus{ - Phase: api.NamespaceTerminating, - }, - }, true}, - {api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo"}}, - api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo"}, - Status: api.NamespaceStatus{ - Phase: api.NamespaceTerminating, - }, - }, false}, - {api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo"}}, - api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bar"}, - Status: api.NamespaceStatus{ - Phase: api.NamespaceTerminating, - }, - }, false}, - } - for i, test := range tests { - test.namespace.ObjectMeta.ResourceVersion = "1" - test.oldNamespace.ObjectMeta.ResourceVersion = "1" - errs := ValidateNamespaceStatusUpdate(&test.namespace, &test.oldNamespace) - if test.valid && len(errs) > 0 { - t.Errorf("%d: Unexpected error: %v", i, errs) - t.Logf("%#v vs %#v", test.oldNamespace.ObjectMeta, test.namespace.ObjectMeta) - } - if !test.valid && len(errs) == 0 { - t.Errorf("%d: Unexpected non-error", i) - } - } -} - -func TestValidateNamespaceUpdate(t *testing.T) { - tests := []struct { - oldNamespace api.Namespace - namespace api.Namespace - valid bool - }{ - {api.Namespace{}, api.Namespace{}, true}, - {api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo1"}}, - api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bar1"}, - }, false}, - {api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo2", - Labels: map[string]string{"foo": "bar"}, - }, - }, api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo2", - Labels: map[string]string{"foo": "baz"}, - }, - }, true}, - {api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo3", - }, - }, api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo3", - Labels: map[string]string{"foo": "baz"}, - }, - }, true}, - {api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo4", - Labels: map[string]string{"bar": "foo"}, - }, - }, api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo4", - Labels: map[string]string{"foo": "baz"}, - }, - }, true}, - {api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo5", - Labels: map[string]string{"foo": "baz"}, - }, - }, api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo5", - Labels: map[string]string{"Foo": "baz"}, - }, - }, true}, - {api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo6", - Labels: map[string]string{"foo": "baz"}, - }, - }, api.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo6", - Labels: map[string]string{"Foo": "baz"}, - }, - Spec: api.NamespaceSpec{ - Finalizers: []api.FinalizerName{"kubernetes"}, - }, - Status: api.NamespaceStatus{ - Phase: api.NamespaceTerminating, - }, - }, true}, - } - for i, test := range tests { - test.namespace.ObjectMeta.ResourceVersion = "1" - test.oldNamespace.ObjectMeta.ResourceVersion = "1" - errs := ValidateNamespaceUpdate(&test.namespace, &test.oldNamespace) - if test.valid && len(errs) > 0 { - t.Errorf("%d: Unexpected error: %v", i, errs) - t.Logf("%#v vs %#v", test.oldNamespace.ObjectMeta, test.namespace.ObjectMeta) - } - if !test.valid && len(errs) == 0 { - t.Errorf("%d: Unexpected non-error", i) - } - } -} - -func TestValidateSecret(t *testing.T) { - // Opaque secret validation - validSecret := func() api.Secret { - return api.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, - Data: map[string][]byte{ - "data-1": []byte("bar"), - }, - } - } - - var ( - emptyName = validSecret() - invalidName = validSecret() - emptyNs = validSecret() - invalidNs = validSecret() - overMaxSize = validSecret() - invalidKey = validSecret() - leadingDotKey = validSecret() - dotKey = validSecret() - doubleDotKey = validSecret() - ) - - emptyName.Name = "" - invalidName.Name = "NoUppercaseOrSpecialCharsLike=Equals" - emptyNs.Namespace = "" - invalidNs.Namespace = "NoUppercaseOrSpecialCharsLike=Equals" - overMaxSize.Data = map[string][]byte{ - "over": make([]byte, api.MaxSecretSize+1), - } - invalidKey.Data["a*b"] = []byte("whoops") - leadingDotKey.Data[".key"] = []byte("bar") - dotKey.Data["."] = []byte("bar") - doubleDotKey.Data[".."] = []byte("bar") - - // kubernetes.io/service-account-token secret validation - validServiceAccountTokenSecret := func() api.Secret { - return api.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "bar", - Annotations: map[string]string{ - api.ServiceAccountNameKey: "foo", - }, - }, - Type: api.SecretTypeServiceAccountToken, - Data: map[string][]byte{ - "data-1": []byte("bar"), - }, - } - } - - var ( - emptyTokenAnnotation = validServiceAccountTokenSecret() - missingTokenAnnotation = validServiceAccountTokenSecret() - missingTokenAnnotations = validServiceAccountTokenSecret() - ) - emptyTokenAnnotation.Annotations[api.ServiceAccountNameKey] = "" - delete(missingTokenAnnotation.Annotations, api.ServiceAccountNameKey) - missingTokenAnnotations.Annotations = nil - - tests := map[string]struct { - secret api.Secret - valid bool - }{ - "valid": {validSecret(), true}, - "empty name": {emptyName, false}, - "invalid name": {invalidName, false}, - "empty namespace": {emptyNs, false}, - "invalid namespace": {invalidNs, false}, - "over max size": {overMaxSize, false}, - "invalid key": {invalidKey, false}, - "valid service-account-token secret": {validServiceAccountTokenSecret(), true}, - "empty service-account-token annotation": {emptyTokenAnnotation, false}, - "missing service-account-token annotation": {missingTokenAnnotation, false}, - "missing service-account-token annotations": {missingTokenAnnotations, false}, - "leading dot key": {leadingDotKey, true}, - "dot key": {dotKey, false}, - "double dot key": {doubleDotKey, false}, - } - - for name, tc := range tests { - errs := ValidateSecret(&tc.secret) - if tc.valid && len(errs) > 0 { - t.Errorf("%v: Unexpected error: %v", name, errs) - } - if !tc.valid && len(errs) == 0 { - t.Errorf("%v: Unexpected non-error", name) - } - } -} - -func TestValidateDockerConfigSecret(t *testing.T) { - validDockerSecret := func() api.Secret { - return api.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, - Type: api.SecretTypeDockercfg, - Data: map[string][]byte{ - api.DockerConfigKey: []byte(`{"https://index.docker.io/v1/": {"auth": "Y2x1ZWRyb29sZXIwMDAxOnBhc3N3b3Jk","email": "fake@example.com"}}`), - }, - } - } - validDockerSecret2 := func() api.Secret { - return api.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, - Type: api.SecretTypeDockerConfigJson, - Data: map[string][]byte{ - api.DockerConfigJsonKey: []byte(`{"auths":{"https://index.docker.io/v1/": {"auth": "Y2x1ZWRyb29sZXIwMDAxOnBhc3N3b3Jk","email": "fake@example.com"}}}`), - }, - } - } - - var ( - missingDockerConfigKey = validDockerSecret() - emptyDockerConfigKey = validDockerSecret() - invalidDockerConfigKey = validDockerSecret() - missingDockerConfigKey2 = validDockerSecret2() - emptyDockerConfigKey2 = validDockerSecret2() - invalidDockerConfigKey2 = validDockerSecret2() - ) - - delete(missingDockerConfigKey.Data, api.DockerConfigKey) - emptyDockerConfigKey.Data[api.DockerConfigKey] = []byte("") - invalidDockerConfigKey.Data[api.DockerConfigKey] = []byte("bad") - delete(missingDockerConfigKey2.Data, api.DockerConfigJsonKey) - emptyDockerConfigKey2.Data[api.DockerConfigJsonKey] = []byte("") - invalidDockerConfigKey2.Data[api.DockerConfigJsonKey] = []byte("bad") - - tests := map[string]struct { - secret api.Secret - valid bool - }{ - "valid dockercfg": {validDockerSecret(), true}, - "missing dockercfg": {missingDockerConfigKey, false}, - "empty dockercfg": {emptyDockerConfigKey, false}, - "invalid dockercfg": {invalidDockerConfigKey, false}, - "valid config.json": {validDockerSecret2(), true}, - "missing config.json": {missingDockerConfigKey2, false}, - "empty config.json": {emptyDockerConfigKey2, false}, - "invalid config.json": {invalidDockerConfigKey2, false}, - } - - for name, tc := range tests { - errs := ValidateSecret(&tc.secret) - if tc.valid && len(errs) > 0 { - t.Errorf("%v: Unexpected error: %v", name, errs) - } - if !tc.valid && len(errs) == 0 { - t.Errorf("%v: Unexpected non-error", name) - } - } -} - -func TestValidateBasicAuthSecret(t *testing.T) { - validBasicAuthSecret := func() api.Secret { - return api.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, - Type: api.SecretTypeBasicAuth, - Data: map[string][]byte{ - api.BasicAuthUsernameKey: []byte("username"), - api.BasicAuthPasswordKey: []byte("password"), - }, - } - } - - var ( - missingBasicAuthUsernamePasswordKeys = validBasicAuthSecret() - ) - - delete(missingBasicAuthUsernamePasswordKeys.Data, api.BasicAuthUsernameKey) - delete(missingBasicAuthUsernamePasswordKeys.Data, api.BasicAuthPasswordKey) - - tests := map[string]struct { - secret api.Secret - valid bool - }{ - "valid": {validBasicAuthSecret(), true}, - "missing username and password": {missingBasicAuthUsernamePasswordKeys, false}, - } - - for name, tc := range tests { - errs := ValidateSecret(&tc.secret) - if tc.valid && len(errs) > 0 { - t.Errorf("%v: Unexpected error: %v", name, errs) - } - if !tc.valid && len(errs) == 0 { - t.Errorf("%v: Unexpected non-error", name) - } - } -} - -func TestValidateSSHAuthSecret(t *testing.T) { - validSSHAuthSecret := func() api.Secret { - return api.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, - Type: api.SecretTypeSSHAuth, - Data: map[string][]byte{ - api.SSHAuthPrivateKey: []byte("foo-bar-baz"), - }, - } - } - - missingSSHAuthPrivateKey := validSSHAuthSecret() - - delete(missingSSHAuthPrivateKey.Data, api.SSHAuthPrivateKey) - - tests := map[string]struct { - secret api.Secret - valid bool - }{ - "valid": {validSSHAuthSecret(), true}, - "missing private key": {missingSSHAuthPrivateKey, false}, - } - - for name, tc := range tests { - errs := ValidateSecret(&tc.secret) - if tc.valid && len(errs) > 0 { - t.Errorf("%v: Unexpected error: %v", name, errs) - } - if !tc.valid && len(errs) == 0 { - t.Errorf("%v: Unexpected non-error", name) - } - } -} - -func TestValidateEndpoints(t *testing.T) { - successCases := map[string]api.Endpoints{ - "simple endpoint": { - ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{{IP: "10.10.1.1"}, {IP: "10.10.2.2"}}, - Ports: []api.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP"}, {Name: "b", Port: 309, Protocol: "TCP"}}, - }, - { - Addresses: []api.EndpointAddress{{IP: "10.10.3.3"}}, - Ports: []api.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}, {Name: "b", Port: 76, Protocol: "TCP"}}, - }, - }, - }, - "empty subsets": { - ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - }, - "no name required for singleton port": { - ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{{IP: "10.10.1.1"}}, - Ports: []api.EndpointPort{{Port: 8675, Protocol: "TCP"}}, - }, - }, - }, - "empty ports": { - ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{{IP: "10.10.3.3"}}, - }, - }, - }, - } - - for k, v := range successCases { - if errs := ValidateEndpoints(&v); len(errs) != 0 { - t.Errorf("Expected success for %s, got %v", k, errs) - } - } - - errorCases := map[string]struct { - endpoints api.Endpoints - errorType field.ErrorType - errorDetail string - }{ - "missing namespace": { - endpoints: api.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "mysvc"}}, - errorType: "FieldValueRequired", - }, - "missing name": { - endpoints: api.Endpoints{ObjectMeta: metav1.ObjectMeta{Namespace: "namespace"}}, - errorType: "FieldValueRequired", - }, - "invalid namespace": { - endpoints: api.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "no@#invalid.;chars\"allowed"}}, - errorType: "FieldValueInvalid", - errorDetail: dnsLabelErrMsg, - }, - "invalid name": { - endpoints: api.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "-_Invliad^&Characters", Namespace: "namespace"}}, - errorType: "FieldValueInvalid", - errorDetail: dnsSubdomainLabelErrMsg, - }, - "empty addresses": { - endpoints: api.Endpoints{ - ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []api.EndpointSubset{ - { - Ports: []api.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}}, - }, - }, - }, - errorType: "FieldValueRequired", - }, - "invalid IP": { - endpoints: api.Endpoints{ - ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{{IP: "[2001:0db8:85a3:0042:1000:8a2e:0370:7334]"}}, - Ports: []api.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}}, - }, - }, - }, - errorType: "FieldValueInvalid", - errorDetail: "must be a valid IP address", - }, - "Multiple ports, one without name": { - endpoints: api.Endpoints{ - ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{{IP: "10.10.1.1"}}, - Ports: []api.EndpointPort{{Port: 8675, Protocol: "TCP"}, {Name: "b", Port: 309, Protocol: "TCP"}}, - }, - }, - }, - errorType: "FieldValueRequired", - }, - "Invalid port number": { - endpoints: api.Endpoints{ - ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{{IP: "10.10.1.1"}}, - Ports: []api.EndpointPort{{Name: "a", Port: 66000, Protocol: "TCP"}}, - }, - }, - }, - errorType: "FieldValueInvalid", - errorDetail: "between", - }, - "Invalid protocol": { - endpoints: api.Endpoints{ - ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{{IP: "10.10.1.1"}}, - Ports: []api.EndpointPort{{Name: "a", Port: 93, Protocol: "Protocol"}}, - }, - }, - }, - errorType: "FieldValueNotSupported", - }, - "Address missing IP": { - endpoints: api.Endpoints{ - ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{{}}, - Ports: []api.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}}, - }, - }, - }, - errorType: "FieldValueInvalid", - errorDetail: "must be a valid IP address", - }, - "Port missing number": { - endpoints: api.Endpoints{ - ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{{IP: "10.10.1.1"}}, - Ports: []api.EndpointPort{{Name: "a", Protocol: "TCP"}}, - }, - }, - }, - errorType: "FieldValueInvalid", - errorDetail: "between", - }, - "Port missing protocol": { - endpoints: api.Endpoints{ - ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{{IP: "10.10.1.1"}}, - Ports: []api.EndpointPort{{Name: "a", Port: 93}}, - }, - }, - }, - errorType: "FieldValueRequired", - }, - "Address is loopback": { - endpoints: api.Endpoints{ - ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{{IP: "127.0.0.1"}}, - Ports: []api.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}}, - }, - }, - }, - errorType: "FieldValueInvalid", - errorDetail: "loopback", - }, - "Address is link-local": { - endpoints: api.Endpoints{ - ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{{IP: "169.254.169.254"}}, - Ports: []api.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}}, - }, - }, - }, - errorType: "FieldValueInvalid", - errorDetail: "link-local", - }, - "Address is link-local multicast": { - endpoints: api.Endpoints{ - ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{{IP: "224.0.0.1"}}, - Ports: []api.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}}, - }, - }, - }, - errorType: "FieldValueInvalid", - errorDetail: "link-local multicast", - }, - } - - for k, v := range errorCases { - if errs := ValidateEndpoints(&v.endpoints); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) { - t.Errorf("[%s] Expected error type %s with detail %q, got %v", k, v.errorType, v.errorDetail, errs) - } - } -} - -func TestValidateTLSSecret(t *testing.T) { - successCases := map[string]api.Secret{ - "empty certificate chain": { - ObjectMeta: metav1.ObjectMeta{Name: "tls-cert", Namespace: "namespace"}, - Data: map[string][]byte{ - api.TLSCertKey: []byte("public key"), - api.TLSPrivateKeyKey: []byte("private key"), - }, - }, - } - for k, v := range successCases { - if errs := ValidateSecret(&v); len(errs) != 0 { - t.Errorf("Expected success for %s, got %v", k, errs) - } - } - errorCases := map[string]struct { - secrets api.Secret - errorType field.ErrorType - errorDetail string - }{ - "missing public key": { - secrets: api.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "tls-cert"}, - Data: map[string][]byte{ - api.TLSCertKey: []byte("public key"), - }, - }, - errorType: "FieldValueRequired", - }, - "missing private key": { - secrets: api.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "tls-cert"}, - Data: map[string][]byte{ - api.TLSCertKey: []byte("public key"), - }, - }, - errorType: "FieldValueRequired", - }, - } - for k, v := range errorCases { - if errs := ValidateSecret(&v.secrets); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) { - t.Errorf("[%s] Expected error type %s with detail %q, got %v", k, v.errorType, v.errorDetail, errs) - } - } -} - -func TestValidateSecurityContext(t *testing.T) { - priv := false - runAsUser := int64(1) - fullValidSC := func() *api.SecurityContext { - return &api.SecurityContext{ - Privileged: &priv, - Capabilities: &api.Capabilities{ - Add: []api.Capability{"foo"}, - Drop: []api.Capability{"bar"}, - }, - SELinuxOptions: &api.SELinuxOptions{ - User: "user", - Role: "role", - Type: "type", - Level: "level", - }, - RunAsUser: &runAsUser, - } - } - - //setup data - allSettings := fullValidSC() - noCaps := fullValidSC() - noCaps.Capabilities = nil - - noSELinux := fullValidSC() - noSELinux.SELinuxOptions = nil - - noPrivRequest := fullValidSC() - noPrivRequest.Privileged = nil - - noRunAsUser := fullValidSC() - noRunAsUser.RunAsUser = nil - - successCases := map[string]struct { - sc *api.SecurityContext - }{ - "all settings": {allSettings}, - "no capabilities": {noCaps}, - "no selinux": {noSELinux}, - "no priv request": {noPrivRequest}, - "no run as user": {noRunAsUser}, - } - for k, v := range successCases { - if errs := ValidateSecurityContext(v.sc, field.NewPath("field")); len(errs) != 0 { - t.Errorf("[%s] Expected success, got %v", k, errs) - } - } - - privRequestWithGlobalDeny := fullValidSC() - requestPrivileged := true - privRequestWithGlobalDeny.Privileged = &requestPrivileged - - negativeRunAsUser := fullValidSC() - negativeUser := int64(-1) - negativeRunAsUser.RunAsUser = &negativeUser - - errorCases := map[string]struct { - sc *api.SecurityContext - errorType field.ErrorType - errorDetail string - }{ - "request privileged when capabilities forbids": { - sc: privRequestWithGlobalDeny, - errorType: "FieldValueForbidden", - errorDetail: "disallowed by cluster policy", - }, - "negative RunAsUser": { - sc: negativeRunAsUser, - errorType: "FieldValueInvalid", - errorDetail: isNegativeErrorMsg, - }, - } - for k, v := range errorCases { - if errs := ValidateSecurityContext(v.sc, field.NewPath("field")); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) { - t.Errorf("[%s] Expected error type %q with detail %q, got %v", k, v.errorType, v.errorDetail, errs) - } - } -} - -func fakeValidSecurityContext(priv bool) *api.SecurityContext { - return &api.SecurityContext{ - Privileged: &priv, - } -} - -func TestValidPodLogOptions(t *testing.T) { - now := metav1.Now() - negative := int64(-1) - zero := int64(0) - positive := int64(1) - tests := []struct { - opt api.PodLogOptions - errs int - }{ - {api.PodLogOptions{}, 0}, - {api.PodLogOptions{Previous: true}, 0}, - {api.PodLogOptions{Follow: true}, 0}, - {api.PodLogOptions{TailLines: &zero}, 0}, - {api.PodLogOptions{TailLines: &negative}, 1}, - {api.PodLogOptions{TailLines: &positive}, 0}, - {api.PodLogOptions{LimitBytes: &zero}, 1}, - {api.PodLogOptions{LimitBytes: &negative}, 1}, - {api.PodLogOptions{LimitBytes: &positive}, 0}, - {api.PodLogOptions{SinceSeconds: &negative}, 1}, - {api.PodLogOptions{SinceSeconds: &positive}, 0}, - {api.PodLogOptions{SinceSeconds: &zero}, 1}, - {api.PodLogOptions{SinceTime: &now}, 0}, - } - for i, test := range tests { - errs := ValidatePodLogOptions(&test.opt) - if test.errs != len(errs) { - t.Errorf("%d: Unexpected errors: %v", i, errs) - } - } -} - -func TestValidateConfigMap(t *testing.T) { - newConfigMap := func(name, namespace string, data map[string]string) api.ConfigMap { - return api.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Data: data, - } - } - - var ( - validConfigMap = newConfigMap("validname", "validns", map[string]string{"key": "value"}) - maxKeyLength = newConfigMap("validname", "validns", map[string]string{strings.Repeat("a", 253): "value"}) - - emptyName = newConfigMap("", "validns", nil) - invalidName = newConfigMap("NoUppercaseOrSpecialCharsLike=Equals", "validns", nil) - emptyNs = newConfigMap("validname", "", nil) - invalidNs = newConfigMap("validname", "NoUppercaseOrSpecialCharsLike=Equals", nil) - invalidKey = newConfigMap("validname", "validns", map[string]string{"a*b": "value"}) - leadingDotKey = newConfigMap("validname", "validns", map[string]string{".ab": "value"}) - dotKey = newConfigMap("validname", "validns", map[string]string{".": "value"}) - doubleDotKey = newConfigMap("validname", "validns", map[string]string{"..": "value"}) - overMaxKeyLength = newConfigMap("validname", "validns", map[string]string{strings.Repeat("a", 254): "value"}) - overMaxSize = newConfigMap("validname", "validns", map[string]string{"key": strings.Repeat("a", api.MaxSecretSize+1)}) - ) - - tests := map[string]struct { - cfg api.ConfigMap - isValid bool - }{ - "valid": {validConfigMap, true}, - "max key length": {maxKeyLength, true}, - "leading dot key": {leadingDotKey, true}, - "empty name": {emptyName, false}, - "invalid name": {invalidName, false}, - "invalid key": {invalidKey, false}, - "empty namespace": {emptyNs, false}, - "invalid namespace": {invalidNs, false}, - "dot key": {dotKey, false}, - "double dot key": {doubleDotKey, false}, - "over max key length": {overMaxKeyLength, false}, - "over max size": {overMaxSize, false}, - } - - for name, tc := range tests { - errs := ValidateConfigMap(&tc.cfg) - if tc.isValid && len(errs) > 0 { - t.Errorf("%v: unexpected error: %v", name, errs) - } - if !tc.isValid && len(errs) == 0 { - t.Errorf("%v: unexpected non-error", name) - } - } -} - -func TestValidateConfigMapUpdate(t *testing.T) { - newConfigMap := func(version, name, namespace string, data map[string]string) api.ConfigMap { - return api.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - ResourceVersion: version, - }, - Data: data, - } - } - - var ( - validConfigMap = newConfigMap("1", "validname", "validns", map[string]string{"key": "value"}) - noVersion = newConfigMap("", "validname", "validns", map[string]string{"key": "value"}) - ) - - cases := []struct { - name string - newCfg api.ConfigMap - oldCfg api.ConfigMap - isValid bool - }{ - { - name: "valid", - newCfg: validConfigMap, - oldCfg: validConfigMap, - isValid: true, - }, - { - name: "invalid", - newCfg: noVersion, - oldCfg: validConfigMap, - isValid: false, - }, - } - - for _, tc := range cases { - errs := ValidateConfigMapUpdate(&tc.newCfg, &tc.oldCfg) - if tc.isValid && len(errs) > 0 { - t.Errorf("%v: unexpected error: %v", tc.name, errs) - } - if !tc.isValid && len(errs) == 0 { - t.Errorf("%v: unexpected non-error", tc.name) - } - } -} - -func TestValidateHasLabel(t *testing.T) { - successCase := metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - Labels: map[string]string{ - "other": "blah", - "foo": "bar", - }, - } - if errs := ValidateHasLabel(successCase, field.NewPath("field"), "foo", "bar"); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - - missingCase := metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - Labels: map[string]string{ - "other": "blah", - }, - } - if errs := ValidateHasLabel(missingCase, field.NewPath("field"), "foo", "bar"); len(errs) == 0 { - t.Errorf("expected failure") - } - - wrongValueCase := metav1.ObjectMeta{ - Name: "123", - Namespace: "ns", - Labels: map[string]string{ - "other": "blah", - "foo": "notbar", - }, - } - if errs := ValidateHasLabel(wrongValueCase, field.NewPath("field"), "foo", "bar"); len(errs) == 0 { - t.Errorf("expected failure") - } -} - -func TestIsValidSysctlName(t *testing.T) { - valid := []string{ - "a.b.c.d", - "a", - "a_b", - "a-b", - "abc", - "abc.def", - } - invalid := []string{ - "", - "*", - "ä", - "a_", - "_", - "__", - "_a", - "_a._b", - "-", - ".", - "a.", - ".a", - "a.b.", - "a*.b", - "a*b", - "*a", - "a.*", - "*", - "abc*", - "a.abc*", - "a.b.*", - "Abc", - func(n int) string { - x := make([]byte, n) - for i := range x { - x[i] = byte('a') - } - return string(x) - }(256), - } - for _, s := range valid { - if !IsValidSysctlName(s) { - t.Errorf("%q expected to be a valid sysctl name", s) - } - } - for _, s := range invalid { - if IsValidSysctlName(s) { - t.Errorf("%q expected to be an invalid sysctl name", s) - } - } -} - -func TestValidateSysctls(t *testing.T) { - valid := []string{ - "net.foo.bar", - "kernel.shmmax", - } - invalid := []string{ - "i..nvalid", - "_invalid", - } - - sysctls := make([]api.Sysctl, len(valid)) - for i, sysctl := range valid { - sysctls[i].Name = sysctl - } - errs := validateSysctls(sysctls, field.NewPath("foo")) - if len(errs) != 0 { - t.Errorf("unexpected validation errors: %v", errs) - } - - sysctls = make([]api.Sysctl, len(invalid)) - for i, sysctl := range invalid { - sysctls[i].Name = sysctl - } - errs = validateSysctls(sysctls, field.NewPath("foo")) - if len(errs) != 2 { - t.Errorf("expected 2 validation errors. Got: %v", errs) - } else { - if got, expected := errs[0].Error(), "foo"; !strings.Contains(got, expected) { - t.Errorf("unexpected errors: expected=%q, got=%q", expected, got) - } - if got, expected := errs[1].Error(), "foo"; !strings.Contains(got, expected) { - t.Errorf("unexpected errors: expected=%q, got=%q", expected, got) - } - } -} - -func newNodeNameEndpoint(nodeName string) *api.Endpoints { - ep := &api.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: metav1.NamespaceDefault, - ResourceVersion: "1", - }, - Subsets: []api.EndpointSubset{ - { - NotReadyAddresses: []api.EndpointAddress{}, - Ports: []api.EndpointPort{{Name: "https", Port: 443, Protocol: "TCP"}}, - Addresses: []api.EndpointAddress{ - { - IP: "8.8.8.8", - Hostname: "zookeeper1", - NodeName: &nodeName}}}}} - return ep -} - -func TestEndpointAddressNodeNameUpdateRestrictions(t *testing.T) { - oldEndpoint := newNodeNameEndpoint("kubernetes-node-setup-by-backend") - updatedEndpoint := newNodeNameEndpoint("kubernetes-changed-nodename") - // Check that NodeName cannot be changed during update (if already set) - errList := ValidateEndpoints(updatedEndpoint) - errList = append(errList, ValidateEndpointsUpdate(updatedEndpoint, oldEndpoint)...) - if len(errList) == 0 { - t.Error("Endpoint should not allow changing of Subset.Addresses.NodeName on update") - } -} - -func TestEndpointAddressNodeNameInvalidDNSSubdomain(t *testing.T) { - // Check NodeName DNS validation - endpoint := newNodeNameEndpoint("illegal*.nodename") - errList := ValidateEndpoints(endpoint) - if len(errList) == 0 { - t.Error("Endpoint should reject invalid NodeName") - } -} - -func TestEndpointAddressNodeNameCanBeAnIPAddress(t *testing.T) { - endpoint := newNodeNameEndpoint("10.10.1.1") - errList := ValidateEndpoints(endpoint) - if len(errList) != 0 { - t.Error("Endpoint should accept a NodeName that is an IP address") - } -} - -func TestValidateFlexVolumeSource(t *testing.T) { - testcases := map[string]struct { - source *api.FlexVolumeSource - expectedErrs map[string]string - }{ - "valid": { - source: &api.FlexVolumeSource{Driver: "foo"}, - expectedErrs: map[string]string{}, - }, - "valid with options": { - source: &api.FlexVolumeSource{Driver: "foo", Options: map[string]string{"foo": "bar"}}, - expectedErrs: map[string]string{}, - }, - "no driver": { - source: &api.FlexVolumeSource{Driver: ""}, - expectedErrs: map[string]string{"driver": "Required value"}, - }, - "reserved option keys": { - source: &api.FlexVolumeSource{ - Driver: "foo", - Options: map[string]string{ - // valid options - "myns.io": "A", - "myns.io/bar": "A", - "myns.io/kubernetes.io": "A", - - // invalid options - "KUBERNETES.IO": "A", - "kubernetes.io": "A", - "kubernetes.io/": "A", - "kubernetes.io/foo": "A", - - "alpha.kubernetes.io": "A", - "alpha.kubernetes.io/": "A", - "alpha.kubernetes.io/foo": "A", - - "k8s.io": "A", - "k8s.io/": "A", - "k8s.io/foo": "A", - - "alpha.k8s.io": "A", - "alpha.k8s.io/": "A", - "alpha.k8s.io/foo": "A", - }, - }, - expectedErrs: map[string]string{ - "options[KUBERNETES.IO]": "reserved", - "options[kubernetes.io]": "reserved", - "options[kubernetes.io/]": "reserved", - "options[kubernetes.io/foo]": "reserved", - "options[alpha.kubernetes.io]": "reserved", - "options[alpha.kubernetes.io/]": "reserved", - "options[alpha.kubernetes.io/foo]": "reserved", - "options[k8s.io]": "reserved", - "options[k8s.io/]": "reserved", - "options[k8s.io/foo]": "reserved", - "options[alpha.k8s.io]": "reserved", - "options[alpha.k8s.io/]": "reserved", - "options[alpha.k8s.io/foo]": "reserved", - }, - }, - } - - for k, tc := range testcases { - errs := validateFlexVolumeSource(tc.source, nil) - for _, err := range errs { - expectedErr, ok := tc.expectedErrs[err.Field] - if !ok { - t.Errorf("%s: unexpected err on field %s: %v", k, err.Field, err) - continue - } - if !strings.Contains(err.Error(), expectedErr) { - t.Errorf("%s: expected err on field %s to contain '%s', was %v", k, err.Field, expectedErr, err.Error()) - continue - } - } - if len(errs) != len(tc.expectedErrs) { - t.Errorf("%s: expected errs %#v, got %#v", k, tc.expectedErrs, errs) - continue - } - } -} - -func TestValidateOrSetClientIPAffinityConfig(t *testing.T) { - successCases := map[string]*api.SessionAffinityConfig{ - "non-empty config, valid timeout: 1": { - ClientIP: &api.ClientIPConfig{ - TimeoutSeconds: newInt32(1), - }, - }, - "non-empty config, valid timeout: api.MaxClientIPServiceAffinitySeconds-1": { - ClientIP: &api.ClientIPConfig{ - TimeoutSeconds: newInt32(int(api.MaxClientIPServiceAffinitySeconds - 1)), - }, - }, - "non-empty config, valid timeout: api.MaxClientIPServiceAffinitySeconds": { - ClientIP: &api.ClientIPConfig{ - TimeoutSeconds: newInt32(int(api.MaxClientIPServiceAffinitySeconds)), - }, - }, - } - - for name, test := range successCases { - if errs := validateClientIPAffinityConfig(test, field.NewPath("field")); len(errs) != 0 { - t.Errorf("case: %s, expected success: %v", name, errs) - } - } - - errorCases := map[string]*api.SessionAffinityConfig{ - "empty session affinity config": nil, - "empty client IP config": { - ClientIP: nil, - }, - "empty timeoutSeconds": { - ClientIP: &api.ClientIPConfig{ - TimeoutSeconds: nil, - }, - }, - "non-empty config, invalid timeout: api.MaxClientIPServiceAffinitySeconds+1": { - ClientIP: &api.ClientIPConfig{ - TimeoutSeconds: newInt32(int(api.MaxClientIPServiceAffinitySeconds + 1)), - }, - }, - "non-empty config, invalid timeout: -1": { - ClientIP: &api.ClientIPConfig{ - TimeoutSeconds: newInt32(-1), - }, - }, - "non-empty config, invalid timeout: 0": { - ClientIP: &api.ClientIPConfig{ - TimeoutSeconds: newInt32(0), - }, - }, - } - - for name, test := range errorCases { - if errs := validateClientIPAffinityConfig(test, field.NewPath("field")); len(errs) == 0 { - t.Errorf("case: %v, expected failures: %v", name, errs) - } - } -} - -func boolPtr(b bool) *bool { - return &b -} diff --git a/pkg/api/zz_generated.deepcopy.go b/pkg/api/zz_generated.deepcopy.go deleted file mode 100644 index 4929278edc4..00000000000 --- a/pkg/api/zz_generated.deepcopy.go +++ /dev/null @@ -1,6328 +0,0 @@ -// +build !ignore_autogenerated - -/* -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. -*/ - -// This file was autogenerated by deepcopy-gen. Do not edit it manually! - -package api - -import ( - resource "k8s.io/apimachinery/pkg/api/resource" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" - types "k8s.io/apimachinery/pkg/types" - reflect "reflect" -) - -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AWSElasticBlockStoreVolumeSource).DeepCopyInto(out.(*AWSElasticBlockStoreVolumeSource)) - return nil - }, InType: reflect.TypeOf(&AWSElasticBlockStoreVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Affinity).DeepCopyInto(out.(*Affinity)) - return nil - }, InType: reflect.TypeOf(&Affinity{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AttachedVolume).DeepCopyInto(out.(*AttachedVolume)) - return nil - }, InType: reflect.TypeOf(&AttachedVolume{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AvoidPods).DeepCopyInto(out.(*AvoidPods)) - return nil - }, InType: reflect.TypeOf(&AvoidPods{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AzureDiskVolumeSource).DeepCopyInto(out.(*AzureDiskVolumeSource)) - return nil - }, InType: reflect.TypeOf(&AzureDiskVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AzureFilePersistentVolumeSource).DeepCopyInto(out.(*AzureFilePersistentVolumeSource)) - return nil - }, InType: reflect.TypeOf(&AzureFilePersistentVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AzureFileVolumeSource).DeepCopyInto(out.(*AzureFileVolumeSource)) - return nil - }, InType: reflect.TypeOf(&AzureFileVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Binding).DeepCopyInto(out.(*Binding)) - return nil - }, InType: reflect.TypeOf(&Binding{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Capabilities).DeepCopyInto(out.(*Capabilities)) - return nil - }, InType: reflect.TypeOf(&Capabilities{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CephFSPersistentVolumeSource).DeepCopyInto(out.(*CephFSPersistentVolumeSource)) - return nil - }, InType: reflect.TypeOf(&CephFSPersistentVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CephFSVolumeSource).DeepCopyInto(out.(*CephFSVolumeSource)) - return nil - }, InType: reflect.TypeOf(&CephFSVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CinderVolumeSource).DeepCopyInto(out.(*CinderVolumeSource)) - return nil - }, InType: reflect.TypeOf(&CinderVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClientIPConfig).DeepCopyInto(out.(*ClientIPConfig)) - return nil - }, InType: reflect.TypeOf(&ClientIPConfig{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ComponentCondition).DeepCopyInto(out.(*ComponentCondition)) - return nil - }, InType: reflect.TypeOf(&ComponentCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ComponentStatus).DeepCopyInto(out.(*ComponentStatus)) - return nil - }, InType: reflect.TypeOf(&ComponentStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ComponentStatusList).DeepCopyInto(out.(*ComponentStatusList)) - return nil - }, InType: reflect.TypeOf(&ComponentStatusList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ConfigMap).DeepCopyInto(out.(*ConfigMap)) - return nil - }, InType: reflect.TypeOf(&ConfigMap{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ConfigMapEnvSource).DeepCopyInto(out.(*ConfigMapEnvSource)) - return nil - }, InType: reflect.TypeOf(&ConfigMapEnvSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ConfigMapKeySelector).DeepCopyInto(out.(*ConfigMapKeySelector)) - return nil - }, InType: reflect.TypeOf(&ConfigMapKeySelector{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ConfigMapList).DeepCopyInto(out.(*ConfigMapList)) - return nil - }, InType: reflect.TypeOf(&ConfigMapList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ConfigMapProjection).DeepCopyInto(out.(*ConfigMapProjection)) - return nil - }, InType: reflect.TypeOf(&ConfigMapProjection{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ConfigMapVolumeSource).DeepCopyInto(out.(*ConfigMapVolumeSource)) - return nil - }, InType: reflect.TypeOf(&ConfigMapVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Container).DeepCopyInto(out.(*Container)) - return nil - }, InType: reflect.TypeOf(&Container{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ContainerImage).DeepCopyInto(out.(*ContainerImage)) - return nil - }, InType: reflect.TypeOf(&ContainerImage{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ContainerPort).DeepCopyInto(out.(*ContainerPort)) - return nil - }, InType: reflect.TypeOf(&ContainerPort{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ContainerState).DeepCopyInto(out.(*ContainerState)) - return nil - }, InType: reflect.TypeOf(&ContainerState{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ContainerStateRunning).DeepCopyInto(out.(*ContainerStateRunning)) - return nil - }, InType: reflect.TypeOf(&ContainerStateRunning{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ContainerStateTerminated).DeepCopyInto(out.(*ContainerStateTerminated)) - return nil - }, InType: reflect.TypeOf(&ContainerStateTerminated{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ContainerStateWaiting).DeepCopyInto(out.(*ContainerStateWaiting)) - return nil - }, InType: reflect.TypeOf(&ContainerStateWaiting{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ContainerStatus).DeepCopyInto(out.(*ContainerStatus)) - return nil - }, InType: reflect.TypeOf(&ContainerStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DaemonEndpoint).DeepCopyInto(out.(*DaemonEndpoint)) - return nil - }, InType: reflect.TypeOf(&DaemonEndpoint{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeleteOptions).DeepCopyInto(out.(*DeleteOptions)) - return nil - }, InType: reflect.TypeOf(&DeleteOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DownwardAPIProjection).DeepCopyInto(out.(*DownwardAPIProjection)) - return nil - }, InType: reflect.TypeOf(&DownwardAPIProjection{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DownwardAPIVolumeFile).DeepCopyInto(out.(*DownwardAPIVolumeFile)) - return nil - }, InType: reflect.TypeOf(&DownwardAPIVolumeFile{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DownwardAPIVolumeSource).DeepCopyInto(out.(*DownwardAPIVolumeSource)) - return nil - }, InType: reflect.TypeOf(&DownwardAPIVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*EmptyDirVolumeSource).DeepCopyInto(out.(*EmptyDirVolumeSource)) - return nil - }, InType: reflect.TypeOf(&EmptyDirVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*EndpointAddress).DeepCopyInto(out.(*EndpointAddress)) - return nil - }, InType: reflect.TypeOf(&EndpointAddress{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*EndpointPort).DeepCopyInto(out.(*EndpointPort)) - return nil - }, InType: reflect.TypeOf(&EndpointPort{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*EndpointSubset).DeepCopyInto(out.(*EndpointSubset)) - return nil - }, InType: reflect.TypeOf(&EndpointSubset{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Endpoints).DeepCopyInto(out.(*Endpoints)) - return nil - }, InType: reflect.TypeOf(&Endpoints{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*EndpointsList).DeepCopyInto(out.(*EndpointsList)) - return nil - }, InType: reflect.TypeOf(&EndpointsList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*EnvFromSource).DeepCopyInto(out.(*EnvFromSource)) - return nil - }, InType: reflect.TypeOf(&EnvFromSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*EnvVar).DeepCopyInto(out.(*EnvVar)) - return nil - }, InType: reflect.TypeOf(&EnvVar{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*EnvVarSource).DeepCopyInto(out.(*EnvVarSource)) - return nil - }, InType: reflect.TypeOf(&EnvVarSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Event).DeepCopyInto(out.(*Event)) - return nil - }, InType: reflect.TypeOf(&Event{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*EventList).DeepCopyInto(out.(*EventList)) - return nil - }, InType: reflect.TypeOf(&EventList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*EventSource).DeepCopyInto(out.(*EventSource)) - return nil - }, InType: reflect.TypeOf(&EventSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExecAction).DeepCopyInto(out.(*ExecAction)) - return nil - }, InType: reflect.TypeOf(&ExecAction{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*FCVolumeSource).DeepCopyInto(out.(*FCVolumeSource)) - return nil - }, InType: reflect.TypeOf(&FCVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*FlexVolumeSource).DeepCopyInto(out.(*FlexVolumeSource)) - return nil - }, InType: reflect.TypeOf(&FlexVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*FlockerVolumeSource).DeepCopyInto(out.(*FlockerVolumeSource)) - return nil - }, InType: reflect.TypeOf(&FlockerVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*GCEPersistentDiskVolumeSource).DeepCopyInto(out.(*GCEPersistentDiskVolumeSource)) - return nil - }, InType: reflect.TypeOf(&GCEPersistentDiskVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*GitRepoVolumeSource).DeepCopyInto(out.(*GitRepoVolumeSource)) - return nil - }, InType: reflect.TypeOf(&GitRepoVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*GlusterfsVolumeSource).DeepCopyInto(out.(*GlusterfsVolumeSource)) - return nil - }, InType: reflect.TypeOf(&GlusterfsVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HTTPGetAction).DeepCopyInto(out.(*HTTPGetAction)) - return nil - }, InType: reflect.TypeOf(&HTTPGetAction{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HTTPHeader).DeepCopyInto(out.(*HTTPHeader)) - return nil - }, InType: reflect.TypeOf(&HTTPHeader{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Handler).DeepCopyInto(out.(*Handler)) - return nil - }, InType: reflect.TypeOf(&Handler{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HostAlias).DeepCopyInto(out.(*HostAlias)) - return nil - }, InType: reflect.TypeOf(&HostAlias{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HostPathVolumeSource).DeepCopyInto(out.(*HostPathVolumeSource)) - return nil - }, InType: reflect.TypeOf(&HostPathVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ISCSIVolumeSource).DeepCopyInto(out.(*ISCSIVolumeSource)) - return nil - }, InType: reflect.TypeOf(&ISCSIVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KeyToPath).DeepCopyInto(out.(*KeyToPath)) - return nil - }, InType: reflect.TypeOf(&KeyToPath{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Lifecycle).DeepCopyInto(out.(*Lifecycle)) - return nil - }, InType: reflect.TypeOf(&Lifecycle{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LimitRange).DeepCopyInto(out.(*LimitRange)) - return nil - }, InType: reflect.TypeOf(&LimitRange{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LimitRangeItem).DeepCopyInto(out.(*LimitRangeItem)) - return nil - }, InType: reflect.TypeOf(&LimitRangeItem{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LimitRangeList).DeepCopyInto(out.(*LimitRangeList)) - return nil - }, InType: reflect.TypeOf(&LimitRangeList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LimitRangeSpec).DeepCopyInto(out.(*LimitRangeSpec)) - return nil - }, InType: reflect.TypeOf(&LimitRangeSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*List).DeepCopyInto(out.(*List)) - return nil - }, InType: reflect.TypeOf(&List{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ListOptions).DeepCopyInto(out.(*ListOptions)) - return nil - }, InType: reflect.TypeOf(&ListOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LoadBalancerIngress).DeepCopyInto(out.(*LoadBalancerIngress)) - return nil - }, InType: reflect.TypeOf(&LoadBalancerIngress{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LoadBalancerStatus).DeepCopyInto(out.(*LoadBalancerStatus)) - return nil - }, InType: reflect.TypeOf(&LoadBalancerStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LocalObjectReference).DeepCopyInto(out.(*LocalObjectReference)) - return nil - }, InType: reflect.TypeOf(&LocalObjectReference{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LocalVolumeSource).DeepCopyInto(out.(*LocalVolumeSource)) - return nil - }, InType: reflect.TypeOf(&LocalVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NFSVolumeSource).DeepCopyInto(out.(*NFSVolumeSource)) - return nil - }, InType: reflect.TypeOf(&NFSVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Namespace).DeepCopyInto(out.(*Namespace)) - return nil - }, InType: reflect.TypeOf(&Namespace{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NamespaceList).DeepCopyInto(out.(*NamespaceList)) - return nil - }, InType: reflect.TypeOf(&NamespaceList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NamespaceSpec).DeepCopyInto(out.(*NamespaceSpec)) - return nil - }, InType: reflect.TypeOf(&NamespaceSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NamespaceStatus).DeepCopyInto(out.(*NamespaceStatus)) - return nil - }, InType: reflect.TypeOf(&NamespaceStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Node).DeepCopyInto(out.(*Node)) - return nil - }, InType: reflect.TypeOf(&Node{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeAddress).DeepCopyInto(out.(*NodeAddress)) - return nil - }, InType: reflect.TypeOf(&NodeAddress{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeAffinity).DeepCopyInto(out.(*NodeAffinity)) - return nil - }, InType: reflect.TypeOf(&NodeAffinity{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeCondition).DeepCopyInto(out.(*NodeCondition)) - return nil - }, InType: reflect.TypeOf(&NodeCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeConfigSource).DeepCopyInto(out.(*NodeConfigSource)) - return nil - }, InType: reflect.TypeOf(&NodeConfigSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeDaemonEndpoints).DeepCopyInto(out.(*NodeDaemonEndpoints)) - return nil - }, InType: reflect.TypeOf(&NodeDaemonEndpoints{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeList).DeepCopyInto(out.(*NodeList)) - return nil - }, InType: reflect.TypeOf(&NodeList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeProxyOptions).DeepCopyInto(out.(*NodeProxyOptions)) - return nil - }, InType: reflect.TypeOf(&NodeProxyOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeResources).DeepCopyInto(out.(*NodeResources)) - return nil - }, InType: reflect.TypeOf(&NodeResources{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeSelector).DeepCopyInto(out.(*NodeSelector)) - return nil - }, InType: reflect.TypeOf(&NodeSelector{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeSelectorRequirement).DeepCopyInto(out.(*NodeSelectorRequirement)) - return nil - }, InType: reflect.TypeOf(&NodeSelectorRequirement{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeSelectorTerm).DeepCopyInto(out.(*NodeSelectorTerm)) - return nil - }, InType: reflect.TypeOf(&NodeSelectorTerm{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeSpec).DeepCopyInto(out.(*NodeSpec)) - return nil - }, InType: reflect.TypeOf(&NodeSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeStatus).DeepCopyInto(out.(*NodeStatus)) - return nil - }, InType: reflect.TypeOf(&NodeStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeSystemInfo).DeepCopyInto(out.(*NodeSystemInfo)) - return nil - }, InType: reflect.TypeOf(&NodeSystemInfo{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ObjectFieldSelector).DeepCopyInto(out.(*ObjectFieldSelector)) - return nil - }, InType: reflect.TypeOf(&ObjectFieldSelector{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ObjectMeta).DeepCopyInto(out.(*ObjectMeta)) - return nil - }, InType: reflect.TypeOf(&ObjectMeta{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ObjectReference).DeepCopyInto(out.(*ObjectReference)) - return nil - }, InType: reflect.TypeOf(&ObjectReference{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PersistentVolume).DeepCopyInto(out.(*PersistentVolume)) - return nil - }, InType: reflect.TypeOf(&PersistentVolume{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PersistentVolumeClaim).DeepCopyInto(out.(*PersistentVolumeClaim)) - return nil - }, InType: reflect.TypeOf(&PersistentVolumeClaim{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PersistentVolumeClaimCondition).DeepCopyInto(out.(*PersistentVolumeClaimCondition)) - return nil - }, InType: reflect.TypeOf(&PersistentVolumeClaimCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PersistentVolumeClaimList).DeepCopyInto(out.(*PersistentVolumeClaimList)) - return nil - }, InType: reflect.TypeOf(&PersistentVolumeClaimList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PersistentVolumeClaimSpec).DeepCopyInto(out.(*PersistentVolumeClaimSpec)) - return nil - }, InType: reflect.TypeOf(&PersistentVolumeClaimSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PersistentVolumeClaimStatus).DeepCopyInto(out.(*PersistentVolumeClaimStatus)) - return nil - }, InType: reflect.TypeOf(&PersistentVolumeClaimStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PersistentVolumeClaimVolumeSource).DeepCopyInto(out.(*PersistentVolumeClaimVolumeSource)) - return nil - }, InType: reflect.TypeOf(&PersistentVolumeClaimVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PersistentVolumeList).DeepCopyInto(out.(*PersistentVolumeList)) - return nil - }, InType: reflect.TypeOf(&PersistentVolumeList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PersistentVolumeSource).DeepCopyInto(out.(*PersistentVolumeSource)) - return nil - }, InType: reflect.TypeOf(&PersistentVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PersistentVolumeSpec).DeepCopyInto(out.(*PersistentVolumeSpec)) - return nil - }, InType: reflect.TypeOf(&PersistentVolumeSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PersistentVolumeStatus).DeepCopyInto(out.(*PersistentVolumeStatus)) - return nil - }, InType: reflect.TypeOf(&PersistentVolumeStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PhotonPersistentDiskVolumeSource).DeepCopyInto(out.(*PhotonPersistentDiskVolumeSource)) - return nil - }, InType: reflect.TypeOf(&PhotonPersistentDiskVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Pod).DeepCopyInto(out.(*Pod)) - return nil - }, InType: reflect.TypeOf(&Pod{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodAffinity).DeepCopyInto(out.(*PodAffinity)) - return nil - }, InType: reflect.TypeOf(&PodAffinity{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodAffinityTerm).DeepCopyInto(out.(*PodAffinityTerm)) - return nil - }, InType: reflect.TypeOf(&PodAffinityTerm{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodAntiAffinity).DeepCopyInto(out.(*PodAntiAffinity)) - return nil - }, InType: reflect.TypeOf(&PodAntiAffinity{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodAttachOptions).DeepCopyInto(out.(*PodAttachOptions)) - return nil - }, InType: reflect.TypeOf(&PodAttachOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodCondition).DeepCopyInto(out.(*PodCondition)) - return nil - }, InType: reflect.TypeOf(&PodCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodExecOptions).DeepCopyInto(out.(*PodExecOptions)) - return nil - }, InType: reflect.TypeOf(&PodExecOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodList).DeepCopyInto(out.(*PodList)) - return nil - }, InType: reflect.TypeOf(&PodList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodLogOptions).DeepCopyInto(out.(*PodLogOptions)) - return nil - }, InType: reflect.TypeOf(&PodLogOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodPortForwardOptions).DeepCopyInto(out.(*PodPortForwardOptions)) - return nil - }, InType: reflect.TypeOf(&PodPortForwardOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodProxyOptions).DeepCopyInto(out.(*PodProxyOptions)) - return nil - }, InType: reflect.TypeOf(&PodProxyOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodSecurityContext).DeepCopyInto(out.(*PodSecurityContext)) - return nil - }, InType: reflect.TypeOf(&PodSecurityContext{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodSignature).DeepCopyInto(out.(*PodSignature)) - return nil - }, InType: reflect.TypeOf(&PodSignature{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodSpec).DeepCopyInto(out.(*PodSpec)) - return nil - }, InType: reflect.TypeOf(&PodSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodStatus).DeepCopyInto(out.(*PodStatus)) - return nil - }, InType: reflect.TypeOf(&PodStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodStatusResult).DeepCopyInto(out.(*PodStatusResult)) - return nil - }, InType: reflect.TypeOf(&PodStatusResult{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodTemplate).DeepCopyInto(out.(*PodTemplate)) - return nil - }, InType: reflect.TypeOf(&PodTemplate{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodTemplateList).DeepCopyInto(out.(*PodTemplateList)) - return nil - }, InType: reflect.TypeOf(&PodTemplateList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodTemplateSpec).DeepCopyInto(out.(*PodTemplateSpec)) - return nil - }, InType: reflect.TypeOf(&PodTemplateSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PortworxVolumeSource).DeepCopyInto(out.(*PortworxVolumeSource)) - return nil - }, InType: reflect.TypeOf(&PortworxVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Preconditions).DeepCopyInto(out.(*Preconditions)) - return nil - }, InType: reflect.TypeOf(&Preconditions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PreferAvoidPodsEntry).DeepCopyInto(out.(*PreferAvoidPodsEntry)) - return nil - }, InType: reflect.TypeOf(&PreferAvoidPodsEntry{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PreferredSchedulingTerm).DeepCopyInto(out.(*PreferredSchedulingTerm)) - return nil - }, InType: reflect.TypeOf(&PreferredSchedulingTerm{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Probe).DeepCopyInto(out.(*Probe)) - return nil - }, InType: reflect.TypeOf(&Probe{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ProjectedVolumeSource).DeepCopyInto(out.(*ProjectedVolumeSource)) - return nil - }, InType: reflect.TypeOf(&ProjectedVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*QuobyteVolumeSource).DeepCopyInto(out.(*QuobyteVolumeSource)) - return nil - }, InType: reflect.TypeOf(&QuobyteVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RBDVolumeSource).DeepCopyInto(out.(*RBDVolumeSource)) - return nil - }, InType: reflect.TypeOf(&RBDVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RangeAllocation).DeepCopyInto(out.(*RangeAllocation)) - return nil - }, InType: reflect.TypeOf(&RangeAllocation{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicationController).DeepCopyInto(out.(*ReplicationController)) - return nil - }, InType: reflect.TypeOf(&ReplicationController{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicationControllerCondition).DeepCopyInto(out.(*ReplicationControllerCondition)) - return nil - }, InType: reflect.TypeOf(&ReplicationControllerCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicationControllerList).DeepCopyInto(out.(*ReplicationControllerList)) - return nil - }, InType: reflect.TypeOf(&ReplicationControllerList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicationControllerSpec).DeepCopyInto(out.(*ReplicationControllerSpec)) - return nil - }, InType: reflect.TypeOf(&ReplicationControllerSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicationControllerStatus).DeepCopyInto(out.(*ReplicationControllerStatus)) - return nil - }, InType: reflect.TypeOf(&ReplicationControllerStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ResourceFieldSelector).DeepCopyInto(out.(*ResourceFieldSelector)) - return nil - }, InType: reflect.TypeOf(&ResourceFieldSelector{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ResourceQuota).DeepCopyInto(out.(*ResourceQuota)) - return nil - }, InType: reflect.TypeOf(&ResourceQuota{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ResourceQuotaList).DeepCopyInto(out.(*ResourceQuotaList)) - return nil - }, InType: reflect.TypeOf(&ResourceQuotaList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ResourceQuotaSpec).DeepCopyInto(out.(*ResourceQuotaSpec)) - return nil - }, InType: reflect.TypeOf(&ResourceQuotaSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ResourceQuotaStatus).DeepCopyInto(out.(*ResourceQuotaStatus)) - return nil - }, InType: reflect.TypeOf(&ResourceQuotaStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ResourceRequirements).DeepCopyInto(out.(*ResourceRequirements)) - return nil - }, InType: reflect.TypeOf(&ResourceRequirements{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SELinuxOptions).DeepCopyInto(out.(*SELinuxOptions)) - return nil - }, InType: reflect.TypeOf(&SELinuxOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ScaleIOVolumeSource).DeepCopyInto(out.(*ScaleIOVolumeSource)) - return nil - }, InType: reflect.TypeOf(&ScaleIOVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Secret).DeepCopyInto(out.(*Secret)) - return nil - }, InType: reflect.TypeOf(&Secret{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SecretEnvSource).DeepCopyInto(out.(*SecretEnvSource)) - return nil - }, InType: reflect.TypeOf(&SecretEnvSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SecretKeySelector).DeepCopyInto(out.(*SecretKeySelector)) - return nil - }, InType: reflect.TypeOf(&SecretKeySelector{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SecretList).DeepCopyInto(out.(*SecretList)) - return nil - }, InType: reflect.TypeOf(&SecretList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SecretProjection).DeepCopyInto(out.(*SecretProjection)) - return nil - }, InType: reflect.TypeOf(&SecretProjection{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SecretReference).DeepCopyInto(out.(*SecretReference)) - return nil - }, InType: reflect.TypeOf(&SecretReference{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SecretVolumeSource).DeepCopyInto(out.(*SecretVolumeSource)) - return nil - }, InType: reflect.TypeOf(&SecretVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SecurityContext).DeepCopyInto(out.(*SecurityContext)) - return nil - }, InType: reflect.TypeOf(&SecurityContext{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SerializedReference).DeepCopyInto(out.(*SerializedReference)) - return nil - }, InType: reflect.TypeOf(&SerializedReference{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Service).DeepCopyInto(out.(*Service)) - return nil - }, InType: reflect.TypeOf(&Service{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ServiceAccount).DeepCopyInto(out.(*ServiceAccount)) - return nil - }, InType: reflect.TypeOf(&ServiceAccount{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ServiceAccountList).DeepCopyInto(out.(*ServiceAccountList)) - return nil - }, InType: reflect.TypeOf(&ServiceAccountList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ServiceList).DeepCopyInto(out.(*ServiceList)) - return nil - }, InType: reflect.TypeOf(&ServiceList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ServicePort).DeepCopyInto(out.(*ServicePort)) - return nil - }, InType: reflect.TypeOf(&ServicePort{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ServiceProxyOptions).DeepCopyInto(out.(*ServiceProxyOptions)) - return nil - }, InType: reflect.TypeOf(&ServiceProxyOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ServiceSpec).DeepCopyInto(out.(*ServiceSpec)) - return nil - }, InType: reflect.TypeOf(&ServiceSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ServiceStatus).DeepCopyInto(out.(*ServiceStatus)) - return nil - }, InType: reflect.TypeOf(&ServiceStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SessionAffinityConfig).DeepCopyInto(out.(*SessionAffinityConfig)) - return nil - }, InType: reflect.TypeOf(&SessionAffinityConfig{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StorageOSPersistentVolumeSource).DeepCopyInto(out.(*StorageOSPersistentVolumeSource)) - return nil - }, InType: reflect.TypeOf(&StorageOSPersistentVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StorageOSVolumeSource).DeepCopyInto(out.(*StorageOSVolumeSource)) - return nil - }, InType: reflect.TypeOf(&StorageOSVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Sysctl).DeepCopyInto(out.(*Sysctl)) - return nil - }, InType: reflect.TypeOf(&Sysctl{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*TCPSocketAction).DeepCopyInto(out.(*TCPSocketAction)) - return nil - }, InType: reflect.TypeOf(&TCPSocketAction{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Taint).DeepCopyInto(out.(*Taint)) - return nil - }, InType: reflect.TypeOf(&Taint{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Toleration).DeepCopyInto(out.(*Toleration)) - return nil - }, InType: reflect.TypeOf(&Toleration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Volume).DeepCopyInto(out.(*Volume)) - return nil - }, InType: reflect.TypeOf(&Volume{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*VolumeMount).DeepCopyInto(out.(*VolumeMount)) - return nil - }, InType: reflect.TypeOf(&VolumeMount{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*VolumeProjection).DeepCopyInto(out.(*VolumeProjection)) - return nil - }, InType: reflect.TypeOf(&VolumeProjection{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*VolumeSource).DeepCopyInto(out.(*VolumeSource)) - return nil - }, InType: reflect.TypeOf(&VolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*VsphereVirtualDiskVolumeSource).DeepCopyInto(out.(*VsphereVirtualDiskVolumeSource)) - return nil - }, InType: reflect.TypeOf(&VsphereVirtualDiskVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*WeightedPodAffinityTerm).DeepCopyInto(out.(*WeightedPodAffinityTerm)) - return nil - }, InType: reflect.TypeOf(&WeightedPodAffinityTerm{})}, - ) -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AWSElasticBlockStoreVolumeSource) DeepCopyInto(out *AWSElasticBlockStoreVolumeSource) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSElasticBlockStoreVolumeSource. -func (in *AWSElasticBlockStoreVolumeSource) DeepCopy() *AWSElasticBlockStoreVolumeSource { - if in == nil { - return nil - } - out := new(AWSElasticBlockStoreVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Affinity) DeepCopyInto(out *Affinity) { - *out = *in - if in.NodeAffinity != nil { - in, out := &in.NodeAffinity, &out.NodeAffinity - if *in == nil { - *out = nil - } else { - *out = new(NodeAffinity) - (*in).DeepCopyInto(*out) - } - } - if in.PodAffinity != nil { - in, out := &in.PodAffinity, &out.PodAffinity - if *in == nil { - *out = nil - } else { - *out = new(PodAffinity) - (*in).DeepCopyInto(*out) - } - } - if in.PodAntiAffinity != nil { - in, out := &in.PodAntiAffinity, &out.PodAntiAffinity - if *in == nil { - *out = nil - } else { - *out = new(PodAntiAffinity) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Affinity. -func (in *Affinity) DeepCopy() *Affinity { - if in == nil { - return nil - } - out := new(Affinity) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AttachedVolume) DeepCopyInto(out *AttachedVolume) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AttachedVolume. -func (in *AttachedVolume) DeepCopy() *AttachedVolume { - if in == nil { - return nil - } - out := new(AttachedVolume) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AvoidPods) DeepCopyInto(out *AvoidPods) { - *out = *in - if in.PreferAvoidPods != nil { - in, out := &in.PreferAvoidPods, &out.PreferAvoidPods - *out = make([]PreferAvoidPodsEntry, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AvoidPods. -func (in *AvoidPods) DeepCopy() *AvoidPods { - if in == nil { - return nil - } - out := new(AvoidPods) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AzureDiskVolumeSource) DeepCopyInto(out *AzureDiskVolumeSource) { - *out = *in - if in.CachingMode != nil { - in, out := &in.CachingMode, &out.CachingMode - if *in == nil { - *out = nil - } else { - *out = new(AzureDataDiskCachingMode) - **out = **in - } - } - if in.FSType != nil { - in, out := &in.FSType, &out.FSType - if *in == nil { - *out = nil - } else { - *out = new(string) - **out = **in - } - } - if in.ReadOnly != nil { - in, out := &in.ReadOnly, &out.ReadOnly - if *in == nil { - *out = nil - } else { - *out = new(bool) - **out = **in - } - } - if in.Kind != nil { - in, out := &in.Kind, &out.Kind - if *in == nil { - *out = nil - } else { - *out = new(AzureDataDiskKind) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureDiskVolumeSource. -func (in *AzureDiskVolumeSource) DeepCopy() *AzureDiskVolumeSource { - if in == nil { - return nil - } - out := new(AzureDiskVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AzureFilePersistentVolumeSource) DeepCopyInto(out *AzureFilePersistentVolumeSource) { - *out = *in - if in.SecretNamespace != nil { - in, out := &in.SecretNamespace, &out.SecretNamespace - if *in == nil { - *out = nil - } else { - *out = new(string) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureFilePersistentVolumeSource. -func (in *AzureFilePersistentVolumeSource) DeepCopy() *AzureFilePersistentVolumeSource { - if in == nil { - return nil - } - out := new(AzureFilePersistentVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AzureFileVolumeSource) DeepCopyInto(out *AzureFileVolumeSource) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureFileVolumeSource. -func (in *AzureFileVolumeSource) DeepCopy() *AzureFileVolumeSource { - if in == nil { - return nil - } - out := new(AzureFileVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Binding) DeepCopyInto(out *Binding) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Target = in.Target - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Binding. -func (in *Binding) DeepCopy() *Binding { - if in == nil { - return nil - } - out := new(Binding) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Binding) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Capabilities) DeepCopyInto(out *Capabilities) { - *out = *in - if in.Add != nil { - in, out := &in.Add, &out.Add - *out = make([]Capability, len(*in)) - copy(*out, *in) - } - if in.Drop != nil { - in, out := &in.Drop, &out.Drop - *out = make([]Capability, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Capabilities. -func (in *Capabilities) DeepCopy() *Capabilities { - if in == nil { - return nil - } - out := new(Capabilities) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CephFSPersistentVolumeSource) DeepCopyInto(out *CephFSPersistentVolumeSource) { - *out = *in - if in.Monitors != nil { - in, out := &in.Monitors, &out.Monitors - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.SecretRef != nil { - in, out := &in.SecretRef, &out.SecretRef - if *in == nil { - *out = nil - } else { - *out = new(SecretReference) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CephFSPersistentVolumeSource. -func (in *CephFSPersistentVolumeSource) DeepCopy() *CephFSPersistentVolumeSource { - if in == nil { - return nil - } - out := new(CephFSPersistentVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CephFSVolumeSource) DeepCopyInto(out *CephFSVolumeSource) { - *out = *in - if in.Monitors != nil { - in, out := &in.Monitors, &out.Monitors - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.SecretRef != nil { - in, out := &in.SecretRef, &out.SecretRef - if *in == nil { - *out = nil - } else { - *out = new(LocalObjectReference) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CephFSVolumeSource. -func (in *CephFSVolumeSource) DeepCopy() *CephFSVolumeSource { - if in == nil { - return nil - } - out := new(CephFSVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CinderVolumeSource) DeepCopyInto(out *CinderVolumeSource) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CinderVolumeSource. -func (in *CinderVolumeSource) DeepCopy() *CinderVolumeSource { - if in == nil { - return nil - } - out := new(CinderVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClientIPConfig) DeepCopyInto(out *ClientIPConfig) { - *out = *in - if in.TimeoutSeconds != nil { - in, out := &in.TimeoutSeconds, &out.TimeoutSeconds - if *in == nil { - *out = nil - } else { - *out = new(int32) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientIPConfig. -func (in *ClientIPConfig) DeepCopy() *ClientIPConfig { - if in == nil { - return nil - } - out := new(ClientIPConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ComponentCondition) DeepCopyInto(out *ComponentCondition) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentCondition. -func (in *ComponentCondition) DeepCopy() *ComponentCondition { - if in == nil { - return nil - } - out := new(ComponentCondition) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ComponentStatus) DeepCopyInto(out *ComponentStatus) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]ComponentCondition, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentStatus. -func (in *ComponentStatus) DeepCopy() *ComponentStatus { - if in == nil { - return nil - } - out := new(ComponentStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ComponentStatus) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ComponentStatusList) DeepCopyInto(out *ComponentStatusList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ComponentStatus, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentStatusList. -func (in *ComponentStatusList) DeepCopy() *ComponentStatusList { - if in == nil { - return nil - } - out := new(ComponentStatusList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ComponentStatusList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ConfigMap) DeepCopyInto(out *ConfigMap) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.Data != nil { - in, out := &in.Data, &out.Data - *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 ConfigMap. -func (in *ConfigMap) DeepCopy() *ConfigMap { - if in == nil { - return nil - } - out := new(ConfigMap) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ConfigMap) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ConfigMapEnvSource) DeepCopyInto(out *ConfigMapEnvSource) { - *out = *in - out.LocalObjectReference = in.LocalObjectReference - if in.Optional != nil { - in, out := &in.Optional, &out.Optional - if *in == nil { - *out = nil - } else { - *out = new(bool) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapEnvSource. -func (in *ConfigMapEnvSource) DeepCopy() *ConfigMapEnvSource { - if in == nil { - return nil - } - out := new(ConfigMapEnvSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ConfigMapKeySelector) DeepCopyInto(out *ConfigMapKeySelector) { - *out = *in - out.LocalObjectReference = in.LocalObjectReference - if in.Optional != nil { - in, out := &in.Optional, &out.Optional - if *in == nil { - *out = nil - } else { - *out = new(bool) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapKeySelector. -func (in *ConfigMapKeySelector) DeepCopy() *ConfigMapKeySelector { - if in == nil { - return nil - } - out := new(ConfigMapKeySelector) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ConfigMapList) DeepCopyInto(out *ConfigMapList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ConfigMap, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapList. -func (in *ConfigMapList) DeepCopy() *ConfigMapList { - if in == nil { - return nil - } - out := new(ConfigMapList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ConfigMapList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ConfigMapProjection) DeepCopyInto(out *ConfigMapProjection) { - *out = *in - out.LocalObjectReference = in.LocalObjectReference - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]KeyToPath, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Optional != nil { - in, out := &in.Optional, &out.Optional - if *in == nil { - *out = nil - } else { - *out = new(bool) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapProjection. -func (in *ConfigMapProjection) DeepCopy() *ConfigMapProjection { - if in == nil { - return nil - } - out := new(ConfigMapProjection) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ConfigMapVolumeSource) DeepCopyInto(out *ConfigMapVolumeSource) { - *out = *in - out.LocalObjectReference = in.LocalObjectReference - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]KeyToPath, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.DefaultMode != nil { - in, out := &in.DefaultMode, &out.DefaultMode - if *in == nil { - *out = nil - } else { - *out = new(int32) - **out = **in - } - } - if in.Optional != nil { - in, out := &in.Optional, &out.Optional - if *in == nil { - *out = nil - } else { - *out = new(bool) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapVolumeSource. -func (in *ConfigMapVolumeSource) DeepCopy() *ConfigMapVolumeSource { - if in == nil { - return nil - } - out := new(ConfigMapVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Container) DeepCopyInto(out *Container) { - *out = *in - if in.Command != nil { - in, out := &in.Command, &out.Command - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Args != nil { - in, out := &in.Args, &out.Args - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Ports != nil { - in, out := &in.Ports, &out.Ports - *out = make([]ContainerPort, len(*in)) - copy(*out, *in) - } - if in.EnvFrom != nil { - in, out := &in.EnvFrom, &out.EnvFrom - *out = make([]EnvFromSource, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Env != nil { - in, out := &in.Env, &out.Env - *out = make([]EnvVar, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - in.Resources.DeepCopyInto(&out.Resources) - if in.VolumeMounts != nil { - in, out := &in.VolumeMounts, &out.VolumeMounts - *out = make([]VolumeMount, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.LivenessProbe != nil { - in, out := &in.LivenessProbe, &out.LivenessProbe - if *in == nil { - *out = nil - } else { - *out = new(Probe) - (*in).DeepCopyInto(*out) - } - } - if in.ReadinessProbe != nil { - in, out := &in.ReadinessProbe, &out.ReadinessProbe - if *in == nil { - *out = nil - } else { - *out = new(Probe) - (*in).DeepCopyInto(*out) - } - } - if in.Lifecycle != nil { - in, out := &in.Lifecycle, &out.Lifecycle - if *in == nil { - *out = nil - } else { - *out = new(Lifecycle) - (*in).DeepCopyInto(*out) - } - } - if in.SecurityContext != nil { - in, out := &in.SecurityContext, &out.SecurityContext - if *in == nil { - *out = nil - } else { - *out = new(SecurityContext) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Container. -func (in *Container) DeepCopy() *Container { - if in == nil { - return nil - } - out := new(Container) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ContainerImage) DeepCopyInto(out *ContainerImage) { - *out = *in - if in.Names != nil { - in, out := &in.Names, &out.Names - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerImage. -func (in *ContainerImage) DeepCopy() *ContainerImage { - if in == nil { - return nil - } - out := new(ContainerImage) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ContainerPort) DeepCopyInto(out *ContainerPort) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerPort. -func (in *ContainerPort) DeepCopy() *ContainerPort { - if in == nil { - return nil - } - out := new(ContainerPort) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ContainerState) DeepCopyInto(out *ContainerState) { - *out = *in - if in.Waiting != nil { - in, out := &in.Waiting, &out.Waiting - if *in == nil { - *out = nil - } else { - *out = new(ContainerStateWaiting) - **out = **in - } - } - if in.Running != nil { - in, out := &in.Running, &out.Running - if *in == nil { - *out = nil - } else { - *out = new(ContainerStateRunning) - (*in).DeepCopyInto(*out) - } - } - if in.Terminated != nil { - in, out := &in.Terminated, &out.Terminated - if *in == nil { - *out = nil - } else { - *out = new(ContainerStateTerminated) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerState. -func (in *ContainerState) DeepCopy() *ContainerState { - if in == nil { - return nil - } - out := new(ContainerState) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ContainerStateRunning) DeepCopyInto(out *ContainerStateRunning) { - *out = *in - in.StartedAt.DeepCopyInto(&out.StartedAt) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerStateRunning. -func (in *ContainerStateRunning) DeepCopy() *ContainerStateRunning { - if in == nil { - return nil - } - out := new(ContainerStateRunning) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ContainerStateTerminated) DeepCopyInto(out *ContainerStateTerminated) { - *out = *in - in.StartedAt.DeepCopyInto(&out.StartedAt) - in.FinishedAt.DeepCopyInto(&out.FinishedAt) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerStateTerminated. -func (in *ContainerStateTerminated) DeepCopy() *ContainerStateTerminated { - if in == nil { - return nil - } - out := new(ContainerStateTerminated) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ContainerStateWaiting) DeepCopyInto(out *ContainerStateWaiting) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerStateWaiting. -func (in *ContainerStateWaiting) DeepCopy() *ContainerStateWaiting { - if in == nil { - return nil - } - out := new(ContainerStateWaiting) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ContainerStatus) DeepCopyInto(out *ContainerStatus) { - *out = *in - in.State.DeepCopyInto(&out.State) - in.LastTerminationState.DeepCopyInto(&out.LastTerminationState) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerStatus. -func (in *ContainerStatus) DeepCopy() *ContainerStatus { - if in == nil { - return nil - } - out := new(ContainerStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DaemonEndpoint) DeepCopyInto(out *DaemonEndpoint) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DaemonEndpoint. -func (in *DaemonEndpoint) DeepCopy() *DaemonEndpoint { - if in == nil { - return nil - } - out := new(DaemonEndpoint) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DeleteOptions) DeepCopyInto(out *DeleteOptions) { - *out = *in - out.TypeMeta = in.TypeMeta - if in.GracePeriodSeconds != nil { - in, out := &in.GracePeriodSeconds, &out.GracePeriodSeconds - if *in == nil { - *out = nil - } else { - *out = new(int64) - **out = **in - } - } - if in.Preconditions != nil { - in, out := &in.Preconditions, &out.Preconditions - if *in == nil { - *out = nil - } else { - *out = new(Preconditions) - (*in).DeepCopyInto(*out) - } - } - if in.OrphanDependents != nil { - in, out := &in.OrphanDependents, &out.OrphanDependents - if *in == nil { - *out = nil - } else { - *out = new(bool) - **out = **in - } - } - if in.PropagationPolicy != nil { - in, out := &in.PropagationPolicy, &out.PropagationPolicy - if *in == nil { - *out = nil - } else { - *out = new(DeletionPropagation) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeleteOptions. -func (in *DeleteOptions) DeepCopy() *DeleteOptions { - if in == nil { - return nil - } - out := new(DeleteOptions) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *DeleteOptions) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DownwardAPIProjection) DeepCopyInto(out *DownwardAPIProjection) { - *out = *in - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]DownwardAPIVolumeFile, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DownwardAPIProjection. -func (in *DownwardAPIProjection) DeepCopy() *DownwardAPIProjection { - if in == nil { - return nil - } - out := new(DownwardAPIProjection) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DownwardAPIVolumeFile) DeepCopyInto(out *DownwardAPIVolumeFile) { - *out = *in - if in.FieldRef != nil { - in, out := &in.FieldRef, &out.FieldRef - if *in == nil { - *out = nil - } else { - *out = new(ObjectFieldSelector) - **out = **in - } - } - if in.ResourceFieldRef != nil { - in, out := &in.ResourceFieldRef, &out.ResourceFieldRef - if *in == nil { - *out = nil - } else { - *out = new(ResourceFieldSelector) - (*in).DeepCopyInto(*out) - } - } - if in.Mode != nil { - in, out := &in.Mode, &out.Mode - if *in == nil { - *out = nil - } else { - *out = new(int32) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DownwardAPIVolumeFile. -func (in *DownwardAPIVolumeFile) DeepCopy() *DownwardAPIVolumeFile { - if in == nil { - return nil - } - out := new(DownwardAPIVolumeFile) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DownwardAPIVolumeSource) DeepCopyInto(out *DownwardAPIVolumeSource) { - *out = *in - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]DownwardAPIVolumeFile, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.DefaultMode != nil { - in, out := &in.DefaultMode, &out.DefaultMode - if *in == nil { - *out = nil - } else { - *out = new(int32) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DownwardAPIVolumeSource. -func (in *DownwardAPIVolumeSource) DeepCopy() *DownwardAPIVolumeSource { - if in == nil { - return nil - } - out := new(DownwardAPIVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *EmptyDirVolumeSource) DeepCopyInto(out *EmptyDirVolumeSource) { - *out = *in - if in.SizeLimit != nil { - in, out := &in.SizeLimit, &out.SizeLimit - if *in == nil { - *out = nil - } else { - *out = new(resource.Quantity) - **out = (*in).DeepCopy() - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EmptyDirVolumeSource. -func (in *EmptyDirVolumeSource) DeepCopy() *EmptyDirVolumeSource { - if in == nil { - return nil - } - out := new(EmptyDirVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *EndpointAddress) DeepCopyInto(out *EndpointAddress) { - *out = *in - if in.NodeName != nil { - in, out := &in.NodeName, &out.NodeName - if *in == nil { - *out = nil - } else { - *out = new(string) - **out = **in - } - } - if in.TargetRef != nil { - in, out := &in.TargetRef, &out.TargetRef - if *in == nil { - *out = nil - } else { - *out = new(ObjectReference) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointAddress. -func (in *EndpointAddress) DeepCopy() *EndpointAddress { - if in == nil { - return nil - } - out := new(EndpointAddress) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *EndpointPort) DeepCopyInto(out *EndpointPort) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointPort. -func (in *EndpointPort) DeepCopy() *EndpointPort { - if in == nil { - return nil - } - out := new(EndpointPort) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *EndpointSubset) DeepCopyInto(out *EndpointSubset) { - *out = *in - if in.Addresses != nil { - in, out := &in.Addresses, &out.Addresses - *out = make([]EndpointAddress, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.NotReadyAddresses != nil { - in, out := &in.NotReadyAddresses, &out.NotReadyAddresses - *out = make([]EndpointAddress, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Ports != nil { - in, out := &in.Ports, &out.Ports - *out = make([]EndpointPort, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointSubset. -func (in *EndpointSubset) DeepCopy() *EndpointSubset { - if in == nil { - return nil - } - out := new(EndpointSubset) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Endpoints) DeepCopyInto(out *Endpoints) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.Subsets != nil { - in, out := &in.Subsets, &out.Subsets - *out = make([]EndpointSubset, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Endpoints. -func (in *Endpoints) DeepCopy() *Endpoints { - if in == nil { - return nil - } - out := new(Endpoints) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Endpoints) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *EndpointsList) DeepCopyInto(out *EndpointsList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Endpoints, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointsList. -func (in *EndpointsList) DeepCopy() *EndpointsList { - if in == nil { - return nil - } - out := new(EndpointsList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *EndpointsList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *EnvFromSource) DeepCopyInto(out *EnvFromSource) { - *out = *in - if in.ConfigMapRef != nil { - in, out := &in.ConfigMapRef, &out.ConfigMapRef - if *in == nil { - *out = nil - } else { - *out = new(ConfigMapEnvSource) - (*in).DeepCopyInto(*out) - } - } - if in.SecretRef != nil { - in, out := &in.SecretRef, &out.SecretRef - if *in == nil { - *out = nil - } else { - *out = new(SecretEnvSource) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvFromSource. -func (in *EnvFromSource) DeepCopy() *EnvFromSource { - if in == nil { - return nil - } - out := new(EnvFromSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *EnvVar) DeepCopyInto(out *EnvVar) { - *out = *in - if in.ValueFrom != nil { - in, out := &in.ValueFrom, &out.ValueFrom - if *in == nil { - *out = nil - } else { - *out = new(EnvVarSource) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvVar. -func (in *EnvVar) DeepCopy() *EnvVar { - if in == nil { - return nil - } - out := new(EnvVar) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *EnvVarSource) DeepCopyInto(out *EnvVarSource) { - *out = *in - if in.FieldRef != nil { - in, out := &in.FieldRef, &out.FieldRef - if *in == nil { - *out = nil - } else { - *out = new(ObjectFieldSelector) - **out = **in - } - } - if in.ResourceFieldRef != nil { - in, out := &in.ResourceFieldRef, &out.ResourceFieldRef - if *in == nil { - *out = nil - } else { - *out = new(ResourceFieldSelector) - (*in).DeepCopyInto(*out) - } - } - if in.ConfigMapKeyRef != nil { - in, out := &in.ConfigMapKeyRef, &out.ConfigMapKeyRef - if *in == nil { - *out = nil - } else { - *out = new(ConfigMapKeySelector) - (*in).DeepCopyInto(*out) - } - } - if in.SecretKeyRef != nil { - in, out := &in.SecretKeyRef, &out.SecretKeyRef - if *in == nil { - *out = nil - } else { - *out = new(SecretKeySelector) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvVarSource. -func (in *EnvVarSource) DeepCopy() *EnvVarSource { - if in == nil { - return nil - } - out := new(EnvVarSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Event) DeepCopyInto(out *Event) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.InvolvedObject = in.InvolvedObject - out.Source = in.Source - in.FirstTimestamp.DeepCopyInto(&out.FirstTimestamp) - in.LastTimestamp.DeepCopyInto(&out.LastTimestamp) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Event. -func (in *Event) DeepCopy() *Event { - if in == nil { - return nil - } - out := new(Event) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Event) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *EventList) DeepCopyInto(out *EventList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Event, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventList. -func (in *EventList) DeepCopy() *EventList { - if in == nil { - return nil - } - out := new(EventList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *EventList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *EventSource) DeepCopyInto(out *EventSource) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventSource. -func (in *EventSource) DeepCopy() *EventSource { - if in == nil { - return nil - } - out := new(EventSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExecAction) DeepCopyInto(out *ExecAction) { - *out = *in - if in.Command != nil { - in, out := &in.Command, &out.Command - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExecAction. -func (in *ExecAction) DeepCopy() *ExecAction { - if in == nil { - return nil - } - out := new(ExecAction) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FCVolumeSource) DeepCopyInto(out *FCVolumeSource) { - *out = *in - if in.TargetWWNs != nil { - in, out := &in.TargetWWNs, &out.TargetWWNs - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Lun != nil { - in, out := &in.Lun, &out.Lun - if *in == nil { - *out = nil - } else { - *out = new(int32) - **out = **in - } - } - if in.WWIDs != nil { - in, out := &in.WWIDs, &out.WWIDs - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FCVolumeSource. -func (in *FCVolumeSource) DeepCopy() *FCVolumeSource { - if in == nil { - return nil - } - out := new(FCVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FlexVolumeSource) DeepCopyInto(out *FlexVolumeSource) { - *out = *in - if in.SecretRef != nil { - in, out := &in.SecretRef, &out.SecretRef - if *in == nil { - *out = nil - } else { - *out = new(LocalObjectReference) - **out = **in - } - } - if in.Options != nil { - in, out := &in.Options, &out.Options - *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 FlexVolumeSource. -func (in *FlexVolumeSource) DeepCopy() *FlexVolumeSource { - if in == nil { - return nil - } - out := new(FlexVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FlockerVolumeSource) DeepCopyInto(out *FlockerVolumeSource) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlockerVolumeSource. -func (in *FlockerVolumeSource) DeepCopy() *FlockerVolumeSource { - if in == nil { - return nil - } - out := new(FlockerVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GCEPersistentDiskVolumeSource) DeepCopyInto(out *GCEPersistentDiskVolumeSource) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCEPersistentDiskVolumeSource. -func (in *GCEPersistentDiskVolumeSource) DeepCopy() *GCEPersistentDiskVolumeSource { - if in == nil { - return nil - } - out := new(GCEPersistentDiskVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GitRepoVolumeSource) DeepCopyInto(out *GitRepoVolumeSource) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepoVolumeSource. -func (in *GitRepoVolumeSource) DeepCopy() *GitRepoVolumeSource { - if in == nil { - return nil - } - out := new(GitRepoVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GlusterfsVolumeSource) DeepCopyInto(out *GlusterfsVolumeSource) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlusterfsVolumeSource. -func (in *GlusterfsVolumeSource) DeepCopy() *GlusterfsVolumeSource { - if in == nil { - return nil - } - out := new(GlusterfsVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HTTPGetAction) DeepCopyInto(out *HTTPGetAction) { - *out = *in - out.Port = in.Port - if in.HTTPHeaders != nil { - in, out := &in.HTTPHeaders, &out.HTTPHeaders - *out = make([]HTTPHeader, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPGetAction. -func (in *HTTPGetAction) DeepCopy() *HTTPGetAction { - if in == nil { - return nil - } - out := new(HTTPGetAction) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HTTPHeader) DeepCopyInto(out *HTTPHeader) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPHeader. -func (in *HTTPHeader) DeepCopy() *HTTPHeader { - if in == nil { - return nil - } - out := new(HTTPHeader) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Handler) DeepCopyInto(out *Handler) { - *out = *in - if in.Exec != nil { - in, out := &in.Exec, &out.Exec - if *in == nil { - *out = nil - } else { - *out = new(ExecAction) - (*in).DeepCopyInto(*out) - } - } - if in.HTTPGet != nil { - in, out := &in.HTTPGet, &out.HTTPGet - if *in == nil { - *out = nil - } else { - *out = new(HTTPGetAction) - (*in).DeepCopyInto(*out) - } - } - if in.TCPSocket != nil { - in, out := &in.TCPSocket, &out.TCPSocket - if *in == nil { - *out = nil - } else { - *out = new(TCPSocketAction) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Handler. -func (in *Handler) DeepCopy() *Handler { - if in == nil { - return nil - } - out := new(Handler) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HostAlias) DeepCopyInto(out *HostAlias) { - *out = *in - if in.Hostnames != nil { - in, out := &in.Hostnames, &out.Hostnames - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostAlias. -func (in *HostAlias) DeepCopy() *HostAlias { - if in == nil { - return nil - } - out := new(HostAlias) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HostPathVolumeSource) DeepCopyInto(out *HostPathVolumeSource) { - *out = *in - if in.Type != nil { - in, out := &in.Type, &out.Type - if *in == nil { - *out = nil - } else { - *out = new(HostPathType) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostPathVolumeSource. -func (in *HostPathVolumeSource) DeepCopy() *HostPathVolumeSource { - if in == nil { - return nil - } - out := new(HostPathVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ISCSIVolumeSource) DeepCopyInto(out *ISCSIVolumeSource) { - *out = *in - if in.Portals != nil { - in, out := &in.Portals, &out.Portals - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.SecretRef != nil { - in, out := &in.SecretRef, &out.SecretRef - if *in == nil { - *out = nil - } else { - *out = new(LocalObjectReference) - **out = **in - } - } - if in.InitiatorName != nil { - in, out := &in.InitiatorName, &out.InitiatorName - if *in == nil { - *out = nil - } else { - *out = new(string) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ISCSIVolumeSource. -func (in *ISCSIVolumeSource) DeepCopy() *ISCSIVolumeSource { - if in == nil { - return nil - } - out := new(ISCSIVolumeSource) - 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 - if in.Mode != nil { - in, out := &in.Mode, &out.Mode - if *in == nil { - *out = nil - } else { - *out = new(int32) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeyToPath. -func (in *KeyToPath) DeepCopy() *KeyToPath { - if in == nil { - return nil - } - out := new(KeyToPath) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Lifecycle) DeepCopyInto(out *Lifecycle) { - *out = *in - if in.PostStart != nil { - in, out := &in.PostStart, &out.PostStart - if *in == nil { - *out = nil - } else { - *out = new(Handler) - (*in).DeepCopyInto(*out) - } - } - if in.PreStop != nil { - in, out := &in.PreStop, &out.PreStop - if *in == nil { - *out = nil - } else { - *out = new(Handler) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Lifecycle. -func (in *Lifecycle) DeepCopy() *Lifecycle { - if in == nil { - return nil - } - out := new(Lifecycle) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LimitRange) DeepCopyInto(out *LimitRange) { - *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 LimitRange. -func (in *LimitRange) DeepCopy() *LimitRange { - if in == nil { - return nil - } - out := new(LimitRange) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *LimitRange) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LimitRangeItem) DeepCopyInto(out *LimitRangeItem) { - *out = *in - if in.Max != nil { - in, out := &in.Max, &out.Max - *out = make(ResourceList, len(*in)) - for key, val := range *in { - (*out)[key] = val.DeepCopy() - } - } - if in.Min != nil { - in, out := &in.Min, &out.Min - *out = make(ResourceList, len(*in)) - for key, val := range *in { - (*out)[key] = val.DeepCopy() - } - } - if in.Default != nil { - in, out := &in.Default, &out.Default - *out = make(ResourceList, len(*in)) - for key, val := range *in { - (*out)[key] = val.DeepCopy() - } - } - if in.DefaultRequest != nil { - in, out := &in.DefaultRequest, &out.DefaultRequest - *out = make(ResourceList, len(*in)) - for key, val := range *in { - (*out)[key] = val.DeepCopy() - } - } - if in.MaxLimitRequestRatio != nil { - in, out := &in.MaxLimitRequestRatio, &out.MaxLimitRequestRatio - *out = make(ResourceList, len(*in)) - for key, val := range *in { - (*out)[key] = val.DeepCopy() - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LimitRangeItem. -func (in *LimitRangeItem) DeepCopy() *LimitRangeItem { - if in == nil { - return nil - } - out := new(LimitRangeItem) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LimitRangeList) DeepCopyInto(out *LimitRangeList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]LimitRange, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LimitRangeList. -func (in *LimitRangeList) DeepCopy() *LimitRangeList { - if in == nil { - return nil - } - out := new(LimitRangeList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *LimitRangeList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LimitRangeSpec) DeepCopyInto(out *LimitRangeSpec) { - *out = *in - if in.Limits != nil { - in, out := &in.Limits, &out.Limits - *out = make([]LimitRangeItem, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LimitRangeSpec. -func (in *LimitRangeSpec) DeepCopy() *LimitRangeSpec { - if in == nil { - return nil - } - out := new(LimitRangeSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *List) DeepCopyInto(out *List) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]runtime.Object, len(*in)) - for i := range *in { - if (*in)[i] == nil { - (*out)[i] = nil - } else { - (*out)[i] = (*in)[i].DeepCopyObject() - } - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new List. -func (in *List) DeepCopy() *List { - if in == nil { - return nil - } - out := new(List) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *List) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ListOptions) DeepCopyInto(out *ListOptions) { - *out = *in - out.TypeMeta = in.TypeMeta - if in.LabelSelector == nil { - out.LabelSelector = nil - } else { - out.LabelSelector = in.LabelSelector.DeepCopySelector() - } - if in.FieldSelector == nil { - out.FieldSelector = nil - } else { - out.FieldSelector = in.FieldSelector.DeepCopySelector() - } - if in.TimeoutSeconds != nil { - in, out := &in.TimeoutSeconds, &out.TimeoutSeconds - if *in == nil { - *out = nil - } else { - *out = new(int64) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListOptions. -func (in *ListOptions) DeepCopy() *ListOptions { - if in == nil { - return nil - } - out := new(ListOptions) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ListOptions) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LoadBalancerIngress) DeepCopyInto(out *LoadBalancerIngress) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoadBalancerIngress. -func (in *LoadBalancerIngress) DeepCopy() *LoadBalancerIngress { - if in == nil { - return nil - } - out := new(LoadBalancerIngress) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LoadBalancerStatus) DeepCopyInto(out *LoadBalancerStatus) { - *out = *in - if in.Ingress != nil { - in, out := &in.Ingress, &out.Ingress - *out = make([]LoadBalancerIngress, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoadBalancerStatus. -func (in *LoadBalancerStatus) DeepCopy() *LoadBalancerStatus { - if in == nil { - return nil - } - out := new(LoadBalancerStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LocalObjectReference) DeepCopyInto(out *LocalObjectReference) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalObjectReference. -func (in *LocalObjectReference) DeepCopy() *LocalObjectReference { - if in == nil { - return nil - } - out := new(LocalObjectReference) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LocalVolumeSource) DeepCopyInto(out *LocalVolumeSource) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalVolumeSource. -func (in *LocalVolumeSource) DeepCopy() *LocalVolumeSource { - if in == nil { - return nil - } - out := new(LocalVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NFSVolumeSource) DeepCopyInto(out *NFSVolumeSource) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NFSVolumeSource. -func (in *NFSVolumeSource) DeepCopy() *NFSVolumeSource { - if in == nil { - return nil - } - out := new(NFSVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Namespace) DeepCopyInto(out *Namespace) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Namespace. -func (in *Namespace) DeepCopy() *Namespace { - if in == nil { - return nil - } - out := new(Namespace) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Namespace) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NamespaceList) DeepCopyInto(out *NamespaceList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Namespace, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceList. -func (in *NamespaceList) DeepCopy() *NamespaceList { - if in == nil { - return nil - } - out := new(NamespaceList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *NamespaceList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NamespaceSpec) DeepCopyInto(out *NamespaceSpec) { - *out = *in - if in.Finalizers != nil { - in, out := &in.Finalizers, &out.Finalizers - *out = make([]FinalizerName, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceSpec. -func (in *NamespaceSpec) DeepCopy() *NamespaceSpec { - if in == nil { - return nil - } - out := new(NamespaceSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NamespaceStatus) DeepCopyInto(out *NamespaceStatus) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceStatus. -func (in *NamespaceStatus) DeepCopy() *NamespaceStatus { - if in == nil { - return nil - } - out := new(NamespaceStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Node) DeepCopyInto(out *Node) { - *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 Node. -func (in *Node) DeepCopy() *Node { - if in == nil { - return nil - } - out := new(Node) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Node) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NodeAddress) DeepCopyInto(out *NodeAddress) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeAddress. -func (in *NodeAddress) DeepCopy() *NodeAddress { - if in == nil { - return nil - } - out := new(NodeAddress) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NodeAffinity) DeepCopyInto(out *NodeAffinity) { - *out = *in - if in.RequiredDuringSchedulingIgnoredDuringExecution != nil { - in, out := &in.RequiredDuringSchedulingIgnoredDuringExecution, &out.RequiredDuringSchedulingIgnoredDuringExecution - if *in == nil { - *out = nil - } else { - *out = new(NodeSelector) - (*in).DeepCopyInto(*out) - } - } - if in.PreferredDuringSchedulingIgnoredDuringExecution != nil { - in, out := &in.PreferredDuringSchedulingIgnoredDuringExecution, &out.PreferredDuringSchedulingIgnoredDuringExecution - *out = make([]PreferredSchedulingTerm, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeAffinity. -func (in *NodeAffinity) DeepCopy() *NodeAffinity { - if in == nil { - return nil - } - out := new(NodeAffinity) - 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 - in.LastHeartbeatTime.DeepCopyInto(&out.LastHeartbeatTime) - in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeCondition. -func (in *NodeCondition) DeepCopy() *NodeCondition { - if in == nil { - return nil - } - out := new(NodeCondition) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NodeConfigSource) DeepCopyInto(out *NodeConfigSource) { - *out = *in - out.TypeMeta = in.TypeMeta - if in.ConfigMapRef != nil { - in, out := &in.ConfigMapRef, &out.ConfigMapRef - if *in == nil { - *out = nil - } else { - *out = new(ObjectReference) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeConfigSource. -func (in *NodeConfigSource) DeepCopy() *NodeConfigSource { - if in == nil { - return nil - } - out := new(NodeConfigSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *NodeConfigSource) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NodeDaemonEndpoints) DeepCopyInto(out *NodeDaemonEndpoints) { - *out = *in - out.KubeletEndpoint = in.KubeletEndpoint - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeDaemonEndpoints. -func (in *NodeDaemonEndpoints) DeepCopy() *NodeDaemonEndpoints { - if in == nil { - return nil - } - out := new(NodeDaemonEndpoints) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NodeList) DeepCopyInto(out *NodeList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Node, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeList. -func (in *NodeList) DeepCopy() *NodeList { - if in == nil { - return nil - } - out := new(NodeList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *NodeList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NodeProxyOptions) DeepCopyInto(out *NodeProxyOptions) { - *out = *in - out.TypeMeta = in.TypeMeta - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeProxyOptions. -func (in *NodeProxyOptions) DeepCopy() *NodeProxyOptions { - if in == nil { - return nil - } - out := new(NodeProxyOptions) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *NodeProxyOptions) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NodeResources) DeepCopyInto(out *NodeResources) { - *out = *in - if in.Capacity != nil { - in, out := &in.Capacity, &out.Capacity - *out = make(ResourceList, len(*in)) - for key, val := range *in { - (*out)[key] = val.DeepCopy() - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeResources. -func (in *NodeResources) DeepCopy() *NodeResources { - if in == nil { - return nil - } - out := new(NodeResources) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NodeSelector) DeepCopyInto(out *NodeSelector) { - *out = *in - if in.NodeSelectorTerms != nil { - in, out := &in.NodeSelectorTerms, &out.NodeSelectorTerms - *out = make([]NodeSelectorTerm, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeSelector. -func (in *NodeSelector) DeepCopy() *NodeSelector { - if in == nil { - return nil - } - out := new(NodeSelector) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NodeSelectorRequirement) DeepCopyInto(out *NodeSelectorRequirement) { - *out = *in - if in.Values != nil { - in, out := &in.Values, &out.Values - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeSelectorRequirement. -func (in *NodeSelectorRequirement) DeepCopy() *NodeSelectorRequirement { - if in == nil { - return nil - } - out := new(NodeSelectorRequirement) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NodeSelectorTerm) DeepCopyInto(out *NodeSelectorTerm) { - *out = *in - if in.MatchExpressions != nil { - in, out := &in.MatchExpressions, &out.MatchExpressions - *out = make([]NodeSelectorRequirement, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeSelectorTerm. -func (in *NodeSelectorTerm) DeepCopy() *NodeSelectorTerm { - if in == nil { - return nil - } - out := new(NodeSelectorTerm) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NodeSpec) DeepCopyInto(out *NodeSpec) { - *out = *in - if in.Taints != nil { - in, out := &in.Taints, &out.Taints - *out = make([]Taint, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.ConfigSource != nil { - in, out := &in.ConfigSource, &out.ConfigSource - if *in == nil { - *out = nil - } else { - *out = new(NodeConfigSource) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeSpec. -func (in *NodeSpec) DeepCopy() *NodeSpec { - if in == nil { - return nil - } - out := new(NodeSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NodeStatus) DeepCopyInto(out *NodeStatus) { - *out = *in - if in.Capacity != nil { - in, out := &in.Capacity, &out.Capacity - *out = make(ResourceList, len(*in)) - for key, val := range *in { - (*out)[key] = val.DeepCopy() - } - } - if in.Allocatable != nil { - in, out := &in.Allocatable, &out.Allocatable - *out = make(ResourceList, len(*in)) - for key, val := range *in { - (*out)[key] = val.DeepCopy() - } - } - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]NodeCondition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Addresses != nil { - in, out := &in.Addresses, &out.Addresses - *out = make([]NodeAddress, len(*in)) - copy(*out, *in) - } - out.DaemonEndpoints = in.DaemonEndpoints - out.NodeInfo = in.NodeInfo - if in.Images != nil { - in, out := &in.Images, &out.Images - *out = make([]ContainerImage, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.VolumesInUse != nil { - in, out := &in.VolumesInUse, &out.VolumesInUse - *out = make([]UniqueVolumeName, len(*in)) - copy(*out, *in) - } - if in.VolumesAttached != nil { - in, out := &in.VolumesAttached, &out.VolumesAttached - *out = make([]AttachedVolume, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeStatus. -func (in *NodeStatus) DeepCopy() *NodeStatus { - if in == nil { - return nil - } - out := new(NodeStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NodeSystemInfo) DeepCopyInto(out *NodeSystemInfo) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeSystemInfo. -func (in *NodeSystemInfo) DeepCopy() *NodeSystemInfo { - if in == nil { - return nil - } - out := new(NodeSystemInfo) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ObjectFieldSelector) DeepCopyInto(out *ObjectFieldSelector) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectFieldSelector. -func (in *ObjectFieldSelector) DeepCopy() *ObjectFieldSelector { - if in == nil { - return nil - } - out := new(ObjectFieldSelector) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ObjectMeta) DeepCopyInto(out *ObjectMeta) { - *out = *in - in.CreationTimestamp.DeepCopyInto(&out.CreationTimestamp) - if in.DeletionTimestamp != nil { - in, out := &in.DeletionTimestamp, &out.DeletionTimestamp - if *in == nil { - *out = nil - } else { - *out = new(v1.Time) - (*in).DeepCopyInto(*out) - } - } - if in.DeletionGracePeriodSeconds != nil { - in, out := &in.DeletionGracePeriodSeconds, &out.DeletionGracePeriodSeconds - if *in == nil { - *out = nil - } else { - *out = new(int64) - **out = **in - } - } - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Annotations != nil { - in, out := &in.Annotations, &out.Annotations - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.OwnerReferences != nil { - in, out := &in.OwnerReferences, &out.OwnerReferences - *out = make([]v1.OwnerReference, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Initializers != nil { - in, out := &in.Initializers, &out.Initializers - if *in == nil { - *out = nil - } else { - *out = new(v1.Initializers) - (*in).DeepCopyInto(*out) - } - } - if in.Finalizers != nil { - in, out := &in.Finalizers, &out.Finalizers - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectMeta. -func (in *ObjectMeta) DeepCopy() *ObjectMeta { - if in == nil { - return nil - } - out := new(ObjectMeta) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ObjectReference) DeepCopyInto(out *ObjectReference) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectReference. -func (in *ObjectReference) DeepCopy() *ObjectReference { - if in == nil { - return nil - } - out := new(ObjectReference) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ObjectReference) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PersistentVolume) DeepCopyInto(out *PersistentVolume) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistentVolume. -func (in *PersistentVolume) DeepCopy() *PersistentVolume { - if in == nil { - return nil - } - out := new(PersistentVolume) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PersistentVolume) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PersistentVolumeClaim) DeepCopyInto(out *PersistentVolumeClaim) { - *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 PersistentVolumeClaim. -func (in *PersistentVolumeClaim) DeepCopy() *PersistentVolumeClaim { - if in == nil { - return nil - } - out := new(PersistentVolumeClaim) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PersistentVolumeClaim) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PersistentVolumeClaimCondition) DeepCopyInto(out *PersistentVolumeClaimCondition) { - *out = *in - in.LastProbeTime.DeepCopyInto(&out.LastProbeTime) - in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistentVolumeClaimCondition. -func (in *PersistentVolumeClaimCondition) DeepCopy() *PersistentVolumeClaimCondition { - if in == nil { - return nil - } - out := new(PersistentVolumeClaimCondition) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PersistentVolumeClaimList) DeepCopyInto(out *PersistentVolumeClaimList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]PersistentVolumeClaim, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistentVolumeClaimList. -func (in *PersistentVolumeClaimList) DeepCopy() *PersistentVolumeClaimList { - if in == nil { - return nil - } - out := new(PersistentVolumeClaimList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PersistentVolumeClaimList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PersistentVolumeClaimSpec) DeepCopyInto(out *PersistentVolumeClaimSpec) { - *out = *in - if in.AccessModes != nil { - in, out := &in.AccessModes, &out.AccessModes - *out = make([]PersistentVolumeAccessMode, len(*in)) - copy(*out, *in) - } - if in.Selector != nil { - in, out := &in.Selector, &out.Selector - if *in == nil { - *out = nil - } else { - *out = new(v1.LabelSelector) - (*in).DeepCopyInto(*out) - } - } - in.Resources.DeepCopyInto(&out.Resources) - if in.StorageClassName != nil { - in, out := &in.StorageClassName, &out.StorageClassName - if *in == nil { - *out = nil - } else { - *out = new(string) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistentVolumeClaimSpec. -func (in *PersistentVolumeClaimSpec) DeepCopy() *PersistentVolumeClaimSpec { - if in == nil { - return nil - } - out := new(PersistentVolumeClaimSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PersistentVolumeClaimStatus) DeepCopyInto(out *PersistentVolumeClaimStatus) { - *out = *in - if in.AccessModes != nil { - in, out := &in.AccessModes, &out.AccessModes - *out = make([]PersistentVolumeAccessMode, len(*in)) - copy(*out, *in) - } - if in.Capacity != nil { - in, out := &in.Capacity, &out.Capacity - *out = make(ResourceList, len(*in)) - for key, val := range *in { - (*out)[key] = val.DeepCopy() - } - } - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]PersistentVolumeClaimCondition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistentVolumeClaimStatus. -func (in *PersistentVolumeClaimStatus) DeepCopy() *PersistentVolumeClaimStatus { - if in == nil { - return nil - } - out := new(PersistentVolumeClaimStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PersistentVolumeClaimVolumeSource) DeepCopyInto(out *PersistentVolumeClaimVolumeSource) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistentVolumeClaimVolumeSource. -func (in *PersistentVolumeClaimVolumeSource) DeepCopy() *PersistentVolumeClaimVolumeSource { - if in == nil { - return nil - } - out := new(PersistentVolumeClaimVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PersistentVolumeList) DeepCopyInto(out *PersistentVolumeList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]PersistentVolume, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistentVolumeList. -func (in *PersistentVolumeList) DeepCopy() *PersistentVolumeList { - if in == nil { - return nil - } - out := new(PersistentVolumeList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PersistentVolumeList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PersistentVolumeSource) DeepCopyInto(out *PersistentVolumeSource) { - *out = *in - if in.GCEPersistentDisk != nil { - in, out := &in.GCEPersistentDisk, &out.GCEPersistentDisk - if *in == nil { - *out = nil - } else { - *out = new(GCEPersistentDiskVolumeSource) - **out = **in - } - } - if in.AWSElasticBlockStore != nil { - in, out := &in.AWSElasticBlockStore, &out.AWSElasticBlockStore - if *in == nil { - *out = nil - } else { - *out = new(AWSElasticBlockStoreVolumeSource) - **out = **in - } - } - if in.HostPath != nil { - in, out := &in.HostPath, &out.HostPath - if *in == nil { - *out = nil - } else { - *out = new(HostPathVolumeSource) - (*in).DeepCopyInto(*out) - } - } - if in.Glusterfs != nil { - in, out := &in.Glusterfs, &out.Glusterfs - if *in == nil { - *out = nil - } else { - *out = new(GlusterfsVolumeSource) - **out = **in - } - } - if in.NFS != nil { - in, out := &in.NFS, &out.NFS - if *in == nil { - *out = nil - } else { - *out = new(NFSVolumeSource) - **out = **in - } - } - if in.RBD != nil { - in, out := &in.RBD, &out.RBD - if *in == nil { - *out = nil - } else { - *out = new(RBDVolumeSource) - (*in).DeepCopyInto(*out) - } - } - if in.Quobyte != nil { - in, out := &in.Quobyte, &out.Quobyte - if *in == nil { - *out = nil - } else { - *out = new(QuobyteVolumeSource) - **out = **in - } - } - if in.ISCSI != nil { - in, out := &in.ISCSI, &out.ISCSI - if *in == nil { - *out = nil - } else { - *out = new(ISCSIVolumeSource) - (*in).DeepCopyInto(*out) - } - } - if in.FlexVolume != nil { - in, out := &in.FlexVolume, &out.FlexVolume - if *in == nil { - *out = nil - } else { - *out = new(FlexVolumeSource) - (*in).DeepCopyInto(*out) - } - } - if in.Cinder != nil { - in, out := &in.Cinder, &out.Cinder - if *in == nil { - *out = nil - } else { - *out = new(CinderVolumeSource) - **out = **in - } - } - if in.CephFS != nil { - in, out := &in.CephFS, &out.CephFS - if *in == nil { - *out = nil - } else { - *out = new(CephFSPersistentVolumeSource) - (*in).DeepCopyInto(*out) - } - } - if in.FC != nil { - in, out := &in.FC, &out.FC - if *in == nil { - *out = nil - } else { - *out = new(FCVolumeSource) - (*in).DeepCopyInto(*out) - } - } - if in.Flocker != nil { - in, out := &in.Flocker, &out.Flocker - if *in == nil { - *out = nil - } else { - *out = new(FlockerVolumeSource) - **out = **in - } - } - if in.AzureFile != nil { - in, out := &in.AzureFile, &out.AzureFile - if *in == nil { - *out = nil - } else { - *out = new(AzureFilePersistentVolumeSource) - (*in).DeepCopyInto(*out) - } - } - if in.VsphereVolume != nil { - in, out := &in.VsphereVolume, &out.VsphereVolume - if *in == nil { - *out = nil - } else { - *out = new(VsphereVirtualDiskVolumeSource) - **out = **in - } - } - if in.AzureDisk != nil { - in, out := &in.AzureDisk, &out.AzureDisk - if *in == nil { - *out = nil - } else { - *out = new(AzureDiskVolumeSource) - (*in).DeepCopyInto(*out) - } - } - if in.PhotonPersistentDisk != nil { - in, out := &in.PhotonPersistentDisk, &out.PhotonPersistentDisk - if *in == nil { - *out = nil - } else { - *out = new(PhotonPersistentDiskVolumeSource) - **out = **in - } - } - if in.PortworxVolume != nil { - in, out := &in.PortworxVolume, &out.PortworxVolume - if *in == nil { - *out = nil - } else { - *out = new(PortworxVolumeSource) - **out = **in - } - } - if in.ScaleIO != nil { - in, out := &in.ScaleIO, &out.ScaleIO - if *in == nil { - *out = nil - } else { - *out = new(ScaleIOVolumeSource) - (*in).DeepCopyInto(*out) - } - } - if in.Local != nil { - in, out := &in.Local, &out.Local - if *in == nil { - *out = nil - } else { - *out = new(LocalVolumeSource) - **out = **in - } - } - if in.StorageOS != nil { - in, out := &in.StorageOS, &out.StorageOS - if *in == nil { - *out = nil - } else { - *out = new(StorageOSPersistentVolumeSource) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistentVolumeSource. -func (in *PersistentVolumeSource) DeepCopy() *PersistentVolumeSource { - if in == nil { - return nil - } - out := new(PersistentVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PersistentVolumeSpec) DeepCopyInto(out *PersistentVolumeSpec) { - *out = *in - if in.Capacity != nil { - in, out := &in.Capacity, &out.Capacity - *out = make(ResourceList, len(*in)) - for key, val := range *in { - (*out)[key] = val.DeepCopy() - } - } - in.PersistentVolumeSource.DeepCopyInto(&out.PersistentVolumeSource) - if in.AccessModes != nil { - in, out := &in.AccessModes, &out.AccessModes - *out = make([]PersistentVolumeAccessMode, len(*in)) - copy(*out, *in) - } - if in.ClaimRef != nil { - in, out := &in.ClaimRef, &out.ClaimRef - if *in == nil { - *out = nil - } else { - *out = new(ObjectReference) - **out = **in - } - } - if in.MountOptions != nil { - in, out := &in.MountOptions, &out.MountOptions - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistentVolumeSpec. -func (in *PersistentVolumeSpec) DeepCopy() *PersistentVolumeSpec { - if in == nil { - return nil - } - out := new(PersistentVolumeSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PersistentVolumeStatus) DeepCopyInto(out *PersistentVolumeStatus) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistentVolumeStatus. -func (in *PersistentVolumeStatus) DeepCopy() *PersistentVolumeStatus { - if in == nil { - return nil - } - out := new(PersistentVolumeStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PhotonPersistentDiskVolumeSource) DeepCopyInto(out *PhotonPersistentDiskVolumeSource) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PhotonPersistentDiskVolumeSource. -func (in *PhotonPersistentDiskVolumeSource) DeepCopy() *PhotonPersistentDiskVolumeSource { - if in == nil { - return nil - } - out := new(PhotonPersistentDiskVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Pod) DeepCopyInto(out *Pod) { - *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 Pod. -func (in *Pod) DeepCopy() *Pod { - if in == nil { - return nil - } - out := new(Pod) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Pod) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodAffinity) DeepCopyInto(out *PodAffinity) { - *out = *in - if in.RequiredDuringSchedulingIgnoredDuringExecution != nil { - in, out := &in.RequiredDuringSchedulingIgnoredDuringExecution, &out.RequiredDuringSchedulingIgnoredDuringExecution - *out = make([]PodAffinityTerm, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.PreferredDuringSchedulingIgnoredDuringExecution != nil { - in, out := &in.PreferredDuringSchedulingIgnoredDuringExecution, &out.PreferredDuringSchedulingIgnoredDuringExecution - *out = make([]WeightedPodAffinityTerm, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodAffinity. -func (in *PodAffinity) DeepCopy() *PodAffinity { - if in == nil { - return nil - } - out := new(PodAffinity) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodAffinityTerm) DeepCopyInto(out *PodAffinityTerm) { - *out = *in - if in.LabelSelector != nil { - in, out := &in.LabelSelector, &out.LabelSelector - if *in == nil { - *out = nil - } else { - *out = new(v1.LabelSelector) - (*in).DeepCopyInto(*out) - } - } - if in.Namespaces != nil { - in, out := &in.Namespaces, &out.Namespaces - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodAffinityTerm. -func (in *PodAffinityTerm) DeepCopy() *PodAffinityTerm { - if in == nil { - return nil - } - out := new(PodAffinityTerm) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodAntiAffinity) DeepCopyInto(out *PodAntiAffinity) { - *out = *in - if in.RequiredDuringSchedulingIgnoredDuringExecution != nil { - in, out := &in.RequiredDuringSchedulingIgnoredDuringExecution, &out.RequiredDuringSchedulingIgnoredDuringExecution - *out = make([]PodAffinityTerm, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.PreferredDuringSchedulingIgnoredDuringExecution != nil { - in, out := &in.PreferredDuringSchedulingIgnoredDuringExecution, &out.PreferredDuringSchedulingIgnoredDuringExecution - *out = make([]WeightedPodAffinityTerm, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodAntiAffinity. -func (in *PodAntiAffinity) DeepCopy() *PodAntiAffinity { - if in == nil { - return nil - } - out := new(PodAntiAffinity) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodAttachOptions) DeepCopyInto(out *PodAttachOptions) { - *out = *in - out.TypeMeta = in.TypeMeta - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodAttachOptions. -func (in *PodAttachOptions) DeepCopy() *PodAttachOptions { - if in == nil { - return nil - } - out := new(PodAttachOptions) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PodAttachOptions) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodCondition) DeepCopyInto(out *PodCondition) { - *out = *in - in.LastProbeTime.DeepCopyInto(&out.LastProbeTime) - in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodCondition. -func (in *PodCondition) DeepCopy() *PodCondition { - if in == nil { - return nil - } - out := new(PodCondition) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodExecOptions) DeepCopyInto(out *PodExecOptions) { - *out = *in - out.TypeMeta = in.TypeMeta - if in.Command != nil { - in, out := &in.Command, &out.Command - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodExecOptions. -func (in *PodExecOptions) DeepCopy() *PodExecOptions { - if in == nil { - return nil - } - out := new(PodExecOptions) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PodExecOptions) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodList) DeepCopyInto(out *PodList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Pod, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodList. -func (in *PodList) DeepCopy() *PodList { - if in == nil { - return nil - } - out := new(PodList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PodList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodLogOptions) DeepCopyInto(out *PodLogOptions) { - *out = *in - out.TypeMeta = in.TypeMeta - if in.SinceSeconds != nil { - in, out := &in.SinceSeconds, &out.SinceSeconds - if *in == nil { - *out = nil - } else { - *out = new(int64) - **out = **in - } - } - if in.SinceTime != nil { - in, out := &in.SinceTime, &out.SinceTime - if *in == nil { - *out = nil - } else { - *out = new(v1.Time) - (*in).DeepCopyInto(*out) - } - } - if in.TailLines != nil { - in, out := &in.TailLines, &out.TailLines - if *in == nil { - *out = nil - } else { - *out = new(int64) - **out = **in - } - } - if in.LimitBytes != nil { - in, out := &in.LimitBytes, &out.LimitBytes - if *in == nil { - *out = nil - } else { - *out = new(int64) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodLogOptions. -func (in *PodLogOptions) DeepCopy() *PodLogOptions { - if in == nil { - return nil - } - out := new(PodLogOptions) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PodLogOptions) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodPortForwardOptions) DeepCopyInto(out *PodPortForwardOptions) { - *out = *in - out.TypeMeta = in.TypeMeta - if in.Ports != nil { - in, out := &in.Ports, &out.Ports - *out = make([]int32, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodPortForwardOptions. -func (in *PodPortForwardOptions) DeepCopy() *PodPortForwardOptions { - if in == nil { - return nil - } - out := new(PodPortForwardOptions) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PodPortForwardOptions) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodProxyOptions) DeepCopyInto(out *PodProxyOptions) { - *out = *in - out.TypeMeta = in.TypeMeta - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodProxyOptions. -func (in *PodProxyOptions) DeepCopy() *PodProxyOptions { - if in == nil { - return nil - } - out := new(PodProxyOptions) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PodProxyOptions) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// 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 - if in.SELinuxOptions != nil { - in, out := &in.SELinuxOptions, &out.SELinuxOptions - if *in == nil { - *out = nil - } else { - *out = new(SELinuxOptions) - **out = **in - } - } - if in.RunAsUser != nil { - in, out := &in.RunAsUser, &out.RunAsUser - if *in == nil { - *out = nil - } else { - *out = new(int64) - **out = **in - } - } - if in.RunAsNonRoot != nil { - in, out := &in.RunAsNonRoot, &out.RunAsNonRoot - if *in == nil { - *out = nil - } else { - *out = new(bool) - **out = **in - } - } - if in.SupplementalGroups != nil { - in, out := &in.SupplementalGroups, &out.SupplementalGroups - *out = make([]int64, len(*in)) - copy(*out, *in) - } - if in.FSGroup != nil { - in, out := &in.FSGroup, &out.FSGroup - if *in == nil { - *out = nil - } else { - *out = new(int64) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSecurityContext. -func (in *PodSecurityContext) DeepCopy() *PodSecurityContext { - if in == nil { - return nil - } - out := new(PodSecurityContext) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodSignature) DeepCopyInto(out *PodSignature) { - *out = *in - if in.PodController != nil { - in, out := &in.PodController, &out.PodController - if *in == nil { - *out = nil - } else { - *out = new(v1.OwnerReference) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSignature. -func (in *PodSignature) DeepCopy() *PodSignature { - if in == nil { - return nil - } - out := new(PodSignature) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodSpec) DeepCopyInto(out *PodSpec) { - *out = *in - if in.Volumes != nil { - in, out := &in.Volumes, &out.Volumes - *out = make([]Volume, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.InitContainers != nil { - in, out := &in.InitContainers, &out.InitContainers - *out = make([]Container, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Containers != nil { - in, out := &in.Containers, &out.Containers - *out = make([]Container, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.TerminationGracePeriodSeconds != nil { - in, out := &in.TerminationGracePeriodSeconds, &out.TerminationGracePeriodSeconds - if *in == nil { - *out = nil - } else { - *out = new(int64) - **out = **in - } - } - if in.ActiveDeadlineSeconds != nil { - in, out := &in.ActiveDeadlineSeconds, &out.ActiveDeadlineSeconds - if *in == nil { - *out = nil - } else { - *out = new(int64) - **out = **in - } - } - if in.NodeSelector != nil { - in, out := &in.NodeSelector, &out.NodeSelector - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.AutomountServiceAccountToken != nil { - in, out := &in.AutomountServiceAccountToken, &out.AutomountServiceAccountToken - if *in == nil { - *out = nil - } else { - *out = new(bool) - **out = **in - } - } - if in.SecurityContext != nil { - in, out := &in.SecurityContext, &out.SecurityContext - if *in == nil { - *out = nil - } else { - *out = new(PodSecurityContext) - (*in).DeepCopyInto(*out) - } - } - if in.ImagePullSecrets != nil { - in, out := &in.ImagePullSecrets, &out.ImagePullSecrets - *out = make([]LocalObjectReference, len(*in)) - copy(*out, *in) - } - if in.Affinity != nil { - in, out := &in.Affinity, &out.Affinity - if *in == nil { - *out = nil - } else { - *out = new(Affinity) - (*in).DeepCopyInto(*out) - } - } - if in.Tolerations != nil { - in, out := &in.Tolerations, &out.Tolerations - *out = make([]Toleration, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.HostAliases != nil { - in, out := &in.HostAliases, &out.HostAliases - *out = make([]HostAlias, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Priority != nil { - in, out := &in.Priority, &out.Priority - if *in == nil { - *out = nil - } else { - *out = new(int32) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSpec. -func (in *PodSpec) DeepCopy() *PodSpec { - if in == nil { - return nil - } - out := new(PodSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodStatus) DeepCopyInto(out *PodStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]PodCondition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.StartTime != nil { - in, out := &in.StartTime, &out.StartTime - if *in == nil { - *out = nil - } else { - *out = new(v1.Time) - (*in).DeepCopyInto(*out) - } - } - if in.InitContainerStatuses != nil { - in, out := &in.InitContainerStatuses, &out.InitContainerStatuses - *out = make([]ContainerStatus, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.ContainerStatuses != nil { - in, out := &in.ContainerStatuses, &out.ContainerStatuses - *out = make([]ContainerStatus, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodStatus. -func (in *PodStatus) DeepCopy() *PodStatus { - if in == nil { - return nil - } - out := new(PodStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodStatusResult) DeepCopyInto(out *PodStatusResult) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodStatusResult. -func (in *PodStatusResult) DeepCopy() *PodStatusResult { - if in == nil { - return nil - } - out := new(PodStatusResult) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PodStatusResult) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodTemplate) DeepCopyInto(out *PodTemplate) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Template.DeepCopyInto(&out.Template) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodTemplate. -func (in *PodTemplate) DeepCopy() *PodTemplate { - if in == nil { - return nil - } - out := new(PodTemplate) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PodTemplate) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodTemplateList) DeepCopyInto(out *PodTemplateList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]PodTemplate, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodTemplateList. -func (in *PodTemplateList) DeepCopy() *PodTemplateList { - if in == nil { - return nil - } - out := new(PodTemplateList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PodTemplateList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodTemplateSpec) DeepCopyInto(out *PodTemplateSpec) { - *out = *in - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodTemplateSpec. -func (in *PodTemplateSpec) DeepCopy() *PodTemplateSpec { - if in == nil { - return nil - } - out := new(PodTemplateSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PortworxVolumeSource) DeepCopyInto(out *PortworxVolumeSource) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PortworxVolumeSource. -func (in *PortworxVolumeSource) DeepCopy() *PortworxVolumeSource { - if in == nil { - return nil - } - out := new(PortworxVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Preconditions) DeepCopyInto(out *Preconditions) { - *out = *in - if in.UID != nil { - in, out := &in.UID, &out.UID - if *in == nil { - *out = nil - } else { - *out = new(types.UID) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Preconditions. -func (in *Preconditions) DeepCopy() *Preconditions { - if in == nil { - return nil - } - out := new(Preconditions) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PreferAvoidPodsEntry) DeepCopyInto(out *PreferAvoidPodsEntry) { - *out = *in - in.PodSignature.DeepCopyInto(&out.PodSignature) - in.EvictionTime.DeepCopyInto(&out.EvictionTime) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PreferAvoidPodsEntry. -func (in *PreferAvoidPodsEntry) DeepCopy() *PreferAvoidPodsEntry { - if in == nil { - return nil - } - out := new(PreferAvoidPodsEntry) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PreferredSchedulingTerm) DeepCopyInto(out *PreferredSchedulingTerm) { - *out = *in - in.Preference.DeepCopyInto(&out.Preference) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PreferredSchedulingTerm. -func (in *PreferredSchedulingTerm) DeepCopy() *PreferredSchedulingTerm { - if in == nil { - return nil - } - out := new(PreferredSchedulingTerm) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Probe) DeepCopyInto(out *Probe) { - *out = *in - in.Handler.DeepCopyInto(&out.Handler) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Probe. -func (in *Probe) DeepCopy() *Probe { - if in == nil { - return nil - } - out := new(Probe) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ProjectedVolumeSource) DeepCopyInto(out *ProjectedVolumeSource) { - *out = *in - if in.Sources != nil { - in, out := &in.Sources, &out.Sources - *out = make([]VolumeProjection, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.DefaultMode != nil { - in, out := &in.DefaultMode, &out.DefaultMode - if *in == nil { - *out = nil - } else { - *out = new(int32) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProjectedVolumeSource. -func (in *ProjectedVolumeSource) DeepCopy() *ProjectedVolumeSource { - if in == nil { - return nil - } - out := new(ProjectedVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *QuobyteVolumeSource) DeepCopyInto(out *QuobyteVolumeSource) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new QuobyteVolumeSource. -func (in *QuobyteVolumeSource) DeepCopy() *QuobyteVolumeSource { - if in == nil { - return nil - } - out := new(QuobyteVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RBDVolumeSource) DeepCopyInto(out *RBDVolumeSource) { - *out = *in - if in.CephMonitors != nil { - in, out := &in.CephMonitors, &out.CephMonitors - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.SecretRef != nil { - in, out := &in.SecretRef, &out.SecretRef - if *in == nil { - *out = nil - } else { - *out = new(LocalObjectReference) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RBDVolumeSource. -func (in *RBDVolumeSource) DeepCopy() *RBDVolumeSource { - if in == nil { - return nil - } - out := new(RBDVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RangeAllocation) DeepCopyInto(out *RangeAllocation) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.Data != nil { - in, out := &in.Data, &out.Data - *out = make([]byte, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RangeAllocation. -func (in *RangeAllocation) DeepCopy() *RangeAllocation { - if in == nil { - return nil - } - out := new(RangeAllocation) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RangeAllocation) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ReplicationController) DeepCopyInto(out *ReplicationController) { - *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 ReplicationController. -func (in *ReplicationController) DeepCopy() *ReplicationController { - if in == nil { - return nil - } - out := new(ReplicationController) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ReplicationController) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ReplicationControllerCondition) DeepCopyInto(out *ReplicationControllerCondition) { - *out = *in - in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicationControllerCondition. -func (in *ReplicationControllerCondition) DeepCopy() *ReplicationControllerCondition { - if in == nil { - return nil - } - out := new(ReplicationControllerCondition) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ReplicationControllerList) DeepCopyInto(out *ReplicationControllerList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ReplicationController, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicationControllerList. -func (in *ReplicationControllerList) DeepCopy() *ReplicationControllerList { - if in == nil { - return nil - } - out := new(ReplicationControllerList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ReplicationControllerList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ReplicationControllerSpec) DeepCopyInto(out *ReplicationControllerSpec) { - *out = *in - if in.Selector != nil { - in, out := &in.Selector, &out.Selector - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Template != nil { - in, out := &in.Template, &out.Template - if *in == nil { - *out = nil - } else { - *out = new(PodTemplateSpec) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicationControllerSpec. -func (in *ReplicationControllerSpec) DeepCopy() *ReplicationControllerSpec { - if in == nil { - return nil - } - out := new(ReplicationControllerSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ReplicationControllerStatus) DeepCopyInto(out *ReplicationControllerStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]ReplicationControllerCondition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicationControllerStatus. -func (in *ReplicationControllerStatus) DeepCopy() *ReplicationControllerStatus { - if in == nil { - return nil - } - out := new(ReplicationControllerStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResourceFieldSelector) DeepCopyInto(out *ResourceFieldSelector) { - *out = *in - out.Divisor = in.Divisor.DeepCopy() - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceFieldSelector. -func (in *ResourceFieldSelector) DeepCopy() *ResourceFieldSelector { - if in == nil { - return nil - } - out := new(ResourceFieldSelector) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResourceQuota) DeepCopyInto(out *ResourceQuota) { - *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 ResourceQuota. -func (in *ResourceQuota) DeepCopy() *ResourceQuota { - if in == nil { - return nil - } - out := new(ResourceQuota) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ResourceQuota) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResourceQuotaList) DeepCopyInto(out *ResourceQuotaList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ResourceQuota, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceQuotaList. -func (in *ResourceQuotaList) DeepCopy() *ResourceQuotaList { - if in == nil { - return nil - } - out := new(ResourceQuotaList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ResourceQuotaList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResourceQuotaSpec) DeepCopyInto(out *ResourceQuotaSpec) { - *out = *in - if in.Hard != nil { - in, out := &in.Hard, &out.Hard - *out = make(ResourceList, len(*in)) - for key, val := range *in { - (*out)[key] = val.DeepCopy() - } - } - if in.Scopes != nil { - in, out := &in.Scopes, &out.Scopes - *out = make([]ResourceQuotaScope, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceQuotaSpec. -func (in *ResourceQuotaSpec) DeepCopy() *ResourceQuotaSpec { - if in == nil { - return nil - } - out := new(ResourceQuotaSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResourceQuotaStatus) DeepCopyInto(out *ResourceQuotaStatus) { - *out = *in - if in.Hard != nil { - in, out := &in.Hard, &out.Hard - *out = make(ResourceList, len(*in)) - for key, val := range *in { - (*out)[key] = val.DeepCopy() - } - } - if in.Used != nil { - in, out := &in.Used, &out.Used - *out = make(ResourceList, len(*in)) - for key, val := range *in { - (*out)[key] = val.DeepCopy() - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceQuotaStatus. -func (in *ResourceQuotaStatus) DeepCopy() *ResourceQuotaStatus { - if in == nil { - return nil - } - out := new(ResourceQuotaStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResourceRequirements) DeepCopyInto(out *ResourceRequirements) { - *out = *in - if in.Limits != nil { - in, out := &in.Limits, &out.Limits - *out = make(ResourceList, len(*in)) - for key, val := range *in { - (*out)[key] = val.DeepCopy() - } - } - if in.Requests != nil { - in, out := &in.Requests, &out.Requests - *out = make(ResourceList, len(*in)) - for key, val := range *in { - (*out)[key] = val.DeepCopy() - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceRequirements. -func (in *ResourceRequirements) DeepCopy() *ResourceRequirements { - if in == nil { - return nil - } - out := new(ResourceRequirements) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SELinuxOptions) DeepCopyInto(out *SELinuxOptions) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SELinuxOptions. -func (in *SELinuxOptions) DeepCopy() *SELinuxOptions { - if in == nil { - return nil - } - out := new(SELinuxOptions) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ScaleIOVolumeSource) DeepCopyInto(out *ScaleIOVolumeSource) { - *out = *in - if in.SecretRef != nil { - in, out := &in.SecretRef, &out.SecretRef - if *in == nil { - *out = nil - } else { - *out = new(LocalObjectReference) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScaleIOVolumeSource. -func (in *ScaleIOVolumeSource) DeepCopy() *ScaleIOVolumeSource { - if in == nil { - return nil - } - out := new(ScaleIOVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Secret) DeepCopyInto(out *Secret) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.Data != nil { - in, out := &in.Data, &out.Data - *out = make(map[string][]byte, len(*in)) - for key, val := range *in { - if val == nil { - (*out)[key] = nil - } else { - (*out)[key] = make([]byte, len(val)) - copy((*out)[key], val) - } - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Secret. -func (in *Secret) DeepCopy() *Secret { - if in == nil { - return nil - } - out := new(Secret) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Secret) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SecretEnvSource) DeepCopyInto(out *SecretEnvSource) { - *out = *in - out.LocalObjectReference = in.LocalObjectReference - if in.Optional != nil { - in, out := &in.Optional, &out.Optional - if *in == nil { - *out = nil - } else { - *out = new(bool) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretEnvSource. -func (in *SecretEnvSource) DeepCopy() *SecretEnvSource { - if in == nil { - return nil - } - out := new(SecretEnvSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SecretKeySelector) DeepCopyInto(out *SecretKeySelector) { - *out = *in - out.LocalObjectReference = in.LocalObjectReference - if in.Optional != nil { - in, out := &in.Optional, &out.Optional - if *in == nil { - *out = nil - } else { - *out = new(bool) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretKeySelector. -func (in *SecretKeySelector) DeepCopy() *SecretKeySelector { - if in == nil { - return nil - } - out := new(SecretKeySelector) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SecretList) DeepCopyInto(out *SecretList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Secret, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretList. -func (in *SecretList) DeepCopy() *SecretList { - if in == nil { - return nil - } - out := new(SecretList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *SecretList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SecretProjection) DeepCopyInto(out *SecretProjection) { - *out = *in - out.LocalObjectReference = in.LocalObjectReference - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]KeyToPath, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Optional != nil { - in, out := &in.Optional, &out.Optional - if *in == nil { - *out = nil - } else { - *out = new(bool) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretProjection. -func (in *SecretProjection) DeepCopy() *SecretProjection { - if in == nil { - return nil - } - out := new(SecretProjection) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SecretReference) DeepCopyInto(out *SecretReference) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretReference. -func (in *SecretReference) DeepCopy() *SecretReference { - if in == nil { - return nil - } - out := new(SecretReference) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SecretVolumeSource) DeepCopyInto(out *SecretVolumeSource) { - *out = *in - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]KeyToPath, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.DefaultMode != nil { - in, out := &in.DefaultMode, &out.DefaultMode - if *in == nil { - *out = nil - } else { - *out = new(int32) - **out = **in - } - } - if in.Optional != nil { - in, out := &in.Optional, &out.Optional - if *in == nil { - *out = nil - } else { - *out = new(bool) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretVolumeSource. -func (in *SecretVolumeSource) DeepCopy() *SecretVolumeSource { - if in == nil { - return nil - } - out := new(SecretVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SecurityContext) DeepCopyInto(out *SecurityContext) { - *out = *in - if in.Capabilities != nil { - in, out := &in.Capabilities, &out.Capabilities - if *in == nil { - *out = nil - } else { - *out = new(Capabilities) - (*in).DeepCopyInto(*out) - } - } - if in.Privileged != nil { - in, out := &in.Privileged, &out.Privileged - if *in == nil { - *out = nil - } else { - *out = new(bool) - **out = **in - } - } - if in.SELinuxOptions != nil { - in, out := &in.SELinuxOptions, &out.SELinuxOptions - if *in == nil { - *out = nil - } else { - *out = new(SELinuxOptions) - **out = **in - } - } - if in.RunAsUser != nil { - in, out := &in.RunAsUser, &out.RunAsUser - if *in == nil { - *out = nil - } else { - *out = new(int64) - **out = **in - } - } - if in.RunAsNonRoot != nil { - in, out := &in.RunAsNonRoot, &out.RunAsNonRoot - if *in == nil { - *out = nil - } else { - *out = new(bool) - **out = **in - } - } - if in.ReadOnlyRootFilesystem != nil { - in, out := &in.ReadOnlyRootFilesystem, &out.ReadOnlyRootFilesystem - if *in == nil { - *out = nil - } else { - *out = new(bool) - **out = **in - } - } - if in.AllowPrivilegeEscalation != nil { - in, out := &in.AllowPrivilegeEscalation, &out.AllowPrivilegeEscalation - if *in == nil { - *out = nil - } else { - *out = new(bool) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecurityContext. -func (in *SecurityContext) DeepCopy() *SecurityContext { - if in == nil { - return nil - } - out := new(SecurityContext) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SerializedReference) DeepCopyInto(out *SerializedReference) { - *out = *in - out.TypeMeta = in.TypeMeta - out.Reference = in.Reference - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SerializedReference. -func (in *SerializedReference) DeepCopy() *SerializedReference { - if in == nil { - return nil - } - out := new(SerializedReference) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *SerializedReference) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Service) DeepCopyInto(out *Service) { - *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 Service. -func (in *Service) DeepCopy() *Service { - if in == nil { - return nil - } - out := new(Service) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Service) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServiceAccount) DeepCopyInto(out *ServiceAccount) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.Secrets != nil { - in, out := &in.Secrets, &out.Secrets - *out = make([]ObjectReference, len(*in)) - copy(*out, *in) - } - if in.ImagePullSecrets != nil { - in, out := &in.ImagePullSecrets, &out.ImagePullSecrets - *out = make([]LocalObjectReference, len(*in)) - copy(*out, *in) - } - if in.AutomountServiceAccountToken != nil { - in, out := &in.AutomountServiceAccountToken, &out.AutomountServiceAccountToken - if *in == nil { - *out = nil - } else { - *out = new(bool) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccount. -func (in *ServiceAccount) DeepCopy() *ServiceAccount { - if in == nil { - return nil - } - out := new(ServiceAccount) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ServiceAccount) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServiceAccountList) DeepCopyInto(out *ServiceAccountList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ServiceAccount, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountList. -func (in *ServiceAccountList) DeepCopy() *ServiceAccountList { - if in == nil { - return nil - } - out := new(ServiceAccountList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ServiceAccountList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServiceList) DeepCopyInto(out *ServiceList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Service, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceList. -func (in *ServiceList) DeepCopy() *ServiceList { - if in == nil { - return nil - } - out := new(ServiceList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ServiceList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServicePort) DeepCopyInto(out *ServicePort) { - *out = *in - out.TargetPort = in.TargetPort - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServicePort. -func (in *ServicePort) DeepCopy() *ServicePort { - if in == nil { - return nil - } - out := new(ServicePort) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServiceProxyOptions) DeepCopyInto(out *ServiceProxyOptions) { - *out = *in - out.TypeMeta = in.TypeMeta - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceProxyOptions. -func (in *ServiceProxyOptions) DeepCopy() *ServiceProxyOptions { - if in == nil { - return nil - } - out := new(ServiceProxyOptions) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ServiceProxyOptions) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) { - *out = *in - if in.Ports != nil { - in, out := &in.Ports, &out.Ports - *out = make([]ServicePort, len(*in)) - copy(*out, *in) - } - if in.Selector != nil { - in, out := &in.Selector, &out.Selector - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.ExternalIPs != nil { - in, out := &in.ExternalIPs, &out.ExternalIPs - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.SessionAffinityConfig != nil { - in, out := &in.SessionAffinityConfig, &out.SessionAffinityConfig - if *in == nil { - *out = nil - } else { - *out = new(SessionAffinityConfig) - (*in).DeepCopyInto(*out) - } - } - if in.LoadBalancerSourceRanges != nil { - in, out := &in.LoadBalancerSourceRanges, &out.LoadBalancerSourceRanges - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceSpec. -func (in *ServiceSpec) DeepCopy() *ServiceSpec { - if in == nil { - return nil - } - out := new(ServiceSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServiceStatus) DeepCopyInto(out *ServiceStatus) { - *out = *in - in.LoadBalancer.DeepCopyInto(&out.LoadBalancer) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceStatus. -func (in *ServiceStatus) DeepCopy() *ServiceStatus { - if in == nil { - return nil - } - out := new(ServiceStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SessionAffinityConfig) DeepCopyInto(out *SessionAffinityConfig) { - *out = *in - if in.ClientIP != nil { - in, out := &in.ClientIP, &out.ClientIP - if *in == nil { - *out = nil - } else { - *out = new(ClientIPConfig) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SessionAffinityConfig. -func (in *SessionAffinityConfig) DeepCopy() *SessionAffinityConfig { - if in == nil { - return nil - } - out := new(SessionAffinityConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *StorageOSPersistentVolumeSource) DeepCopyInto(out *StorageOSPersistentVolumeSource) { - *out = *in - if in.SecretRef != nil { - in, out := &in.SecretRef, &out.SecretRef - if *in == nil { - *out = nil - } else { - *out = new(ObjectReference) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StorageOSPersistentVolumeSource. -func (in *StorageOSPersistentVolumeSource) DeepCopy() *StorageOSPersistentVolumeSource { - if in == nil { - return nil - } - out := new(StorageOSPersistentVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *StorageOSVolumeSource) DeepCopyInto(out *StorageOSVolumeSource) { - *out = *in - if in.SecretRef != nil { - in, out := &in.SecretRef, &out.SecretRef - if *in == nil { - *out = nil - } else { - *out = new(LocalObjectReference) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StorageOSVolumeSource. -func (in *StorageOSVolumeSource) DeepCopy() *StorageOSVolumeSource { - if in == nil { - return nil - } - out := new(StorageOSVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Sysctl) DeepCopyInto(out *Sysctl) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Sysctl. -func (in *Sysctl) DeepCopy() *Sysctl { - if in == nil { - return nil - } - out := new(Sysctl) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TCPSocketAction) DeepCopyInto(out *TCPSocketAction) { - *out = *in - out.Port = in.Port - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPSocketAction. -func (in *TCPSocketAction) DeepCopy() *TCPSocketAction { - if in == nil { - return nil - } - out := new(TCPSocketAction) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Taint) DeepCopyInto(out *Taint) { - *out = *in - if in.TimeAdded != nil { - in, out := &in.TimeAdded, &out.TimeAdded - if *in == nil { - *out = nil - } else { - *out = new(v1.Time) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Taint. -func (in *Taint) DeepCopy() *Taint { - if in == nil { - return nil - } - out := new(Taint) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Toleration) DeepCopyInto(out *Toleration) { - *out = *in - if in.TolerationSeconds != nil { - in, out := &in.TolerationSeconds, &out.TolerationSeconds - if *in == nil { - *out = nil - } else { - *out = new(int64) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Toleration. -func (in *Toleration) DeepCopy() *Toleration { - if in == nil { - return nil - } - out := new(Toleration) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Volume) DeepCopyInto(out *Volume) { - *out = *in - in.VolumeSource.DeepCopyInto(&out.VolumeSource) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Volume. -func (in *Volume) DeepCopy() *Volume { - if in == nil { - return nil - } - out := new(Volume) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VolumeMount) DeepCopyInto(out *VolumeMount) { - *out = *in - if in.MountPropagation != nil { - in, out := &in.MountPropagation, &out.MountPropagation - if *in == nil { - *out = nil - } else { - *out = new(MountPropagationMode) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeMount. -func (in *VolumeMount) DeepCopy() *VolumeMount { - if in == nil { - return nil - } - out := new(VolumeMount) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VolumeProjection) DeepCopyInto(out *VolumeProjection) { - *out = *in - if in.Secret != nil { - in, out := &in.Secret, &out.Secret - if *in == nil { - *out = nil - } else { - *out = new(SecretProjection) - (*in).DeepCopyInto(*out) - } - } - if in.DownwardAPI != nil { - in, out := &in.DownwardAPI, &out.DownwardAPI - if *in == nil { - *out = nil - } else { - *out = new(DownwardAPIProjection) - (*in).DeepCopyInto(*out) - } - } - if in.ConfigMap != nil { - in, out := &in.ConfigMap, &out.ConfigMap - if *in == nil { - *out = nil - } else { - *out = new(ConfigMapProjection) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeProjection. -func (in *VolumeProjection) DeepCopy() *VolumeProjection { - if in == nil { - return nil - } - out := new(VolumeProjection) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VolumeSource) DeepCopyInto(out *VolumeSource) { - *out = *in - if in.HostPath != nil { - in, out := &in.HostPath, &out.HostPath - if *in == nil { - *out = nil - } else { - *out = new(HostPathVolumeSource) - (*in).DeepCopyInto(*out) - } - } - if in.EmptyDir != nil { - in, out := &in.EmptyDir, &out.EmptyDir - if *in == nil { - *out = nil - } else { - *out = new(EmptyDirVolumeSource) - (*in).DeepCopyInto(*out) - } - } - if in.GCEPersistentDisk != nil { - in, out := &in.GCEPersistentDisk, &out.GCEPersistentDisk - if *in == nil { - *out = nil - } else { - *out = new(GCEPersistentDiskVolumeSource) - **out = **in - } - } - if in.AWSElasticBlockStore != nil { - in, out := &in.AWSElasticBlockStore, &out.AWSElasticBlockStore - if *in == nil { - *out = nil - } else { - *out = new(AWSElasticBlockStoreVolumeSource) - **out = **in - } - } - if in.GitRepo != nil { - in, out := &in.GitRepo, &out.GitRepo - if *in == nil { - *out = nil - } else { - *out = new(GitRepoVolumeSource) - **out = **in - } - } - if in.Secret != nil { - in, out := &in.Secret, &out.Secret - if *in == nil { - *out = nil - } else { - *out = new(SecretVolumeSource) - (*in).DeepCopyInto(*out) - } - } - if in.NFS != nil { - in, out := &in.NFS, &out.NFS - if *in == nil { - *out = nil - } else { - *out = new(NFSVolumeSource) - **out = **in - } - } - if in.ISCSI != nil { - in, out := &in.ISCSI, &out.ISCSI - if *in == nil { - *out = nil - } else { - *out = new(ISCSIVolumeSource) - (*in).DeepCopyInto(*out) - } - } - if in.Glusterfs != nil { - in, out := &in.Glusterfs, &out.Glusterfs - if *in == nil { - *out = nil - } else { - *out = new(GlusterfsVolumeSource) - **out = **in - } - } - if in.PersistentVolumeClaim != nil { - in, out := &in.PersistentVolumeClaim, &out.PersistentVolumeClaim - if *in == nil { - *out = nil - } else { - *out = new(PersistentVolumeClaimVolumeSource) - **out = **in - } - } - if in.RBD != nil { - in, out := &in.RBD, &out.RBD - if *in == nil { - *out = nil - } else { - *out = new(RBDVolumeSource) - (*in).DeepCopyInto(*out) - } - } - if in.Quobyte != nil { - in, out := &in.Quobyte, &out.Quobyte - if *in == nil { - *out = nil - } else { - *out = new(QuobyteVolumeSource) - **out = **in - } - } - if in.FlexVolume != nil { - in, out := &in.FlexVolume, &out.FlexVolume - if *in == nil { - *out = nil - } else { - *out = new(FlexVolumeSource) - (*in).DeepCopyInto(*out) - } - } - if in.Cinder != nil { - in, out := &in.Cinder, &out.Cinder - if *in == nil { - *out = nil - } else { - *out = new(CinderVolumeSource) - **out = **in - } - } - if in.CephFS != nil { - in, out := &in.CephFS, &out.CephFS - if *in == nil { - *out = nil - } else { - *out = new(CephFSVolumeSource) - (*in).DeepCopyInto(*out) - } - } - if in.Flocker != nil { - in, out := &in.Flocker, &out.Flocker - if *in == nil { - *out = nil - } else { - *out = new(FlockerVolumeSource) - **out = **in - } - } - if in.DownwardAPI != nil { - in, out := &in.DownwardAPI, &out.DownwardAPI - if *in == nil { - *out = nil - } else { - *out = new(DownwardAPIVolumeSource) - (*in).DeepCopyInto(*out) - } - } - if in.FC != nil { - in, out := &in.FC, &out.FC - if *in == nil { - *out = nil - } else { - *out = new(FCVolumeSource) - (*in).DeepCopyInto(*out) - } - } - if in.AzureFile != nil { - in, out := &in.AzureFile, &out.AzureFile - if *in == nil { - *out = nil - } else { - *out = new(AzureFileVolumeSource) - **out = **in - } - } - if in.ConfigMap != nil { - in, out := &in.ConfigMap, &out.ConfigMap - if *in == nil { - *out = nil - } else { - *out = new(ConfigMapVolumeSource) - (*in).DeepCopyInto(*out) - } - } - if in.VsphereVolume != nil { - in, out := &in.VsphereVolume, &out.VsphereVolume - if *in == nil { - *out = nil - } else { - *out = new(VsphereVirtualDiskVolumeSource) - **out = **in - } - } - if in.AzureDisk != nil { - in, out := &in.AzureDisk, &out.AzureDisk - if *in == nil { - *out = nil - } else { - *out = new(AzureDiskVolumeSource) - (*in).DeepCopyInto(*out) - } - } - if in.PhotonPersistentDisk != nil { - in, out := &in.PhotonPersistentDisk, &out.PhotonPersistentDisk - if *in == nil { - *out = nil - } else { - *out = new(PhotonPersistentDiskVolumeSource) - **out = **in - } - } - if in.Projected != nil { - in, out := &in.Projected, &out.Projected - if *in == nil { - *out = nil - } else { - *out = new(ProjectedVolumeSource) - (*in).DeepCopyInto(*out) - } - } - if in.PortworxVolume != nil { - in, out := &in.PortworxVolume, &out.PortworxVolume - if *in == nil { - *out = nil - } else { - *out = new(PortworxVolumeSource) - **out = **in - } - } - if in.ScaleIO != nil { - in, out := &in.ScaleIO, &out.ScaleIO - if *in == nil { - *out = nil - } else { - *out = new(ScaleIOVolumeSource) - (*in).DeepCopyInto(*out) - } - } - if in.StorageOS != nil { - in, out := &in.StorageOS, &out.StorageOS - if *in == nil { - *out = nil - } else { - *out = new(StorageOSVolumeSource) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeSource. -func (in *VolumeSource) DeepCopy() *VolumeSource { - if in == nil { - return nil - } - out := new(VolumeSource) - 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 - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VsphereVirtualDiskVolumeSource. -func (in *VsphereVirtualDiskVolumeSource) DeepCopy() *VsphereVirtualDiskVolumeSource { - if in == nil { - return nil - } - out := new(VsphereVirtualDiskVolumeSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *WeightedPodAffinityTerm) DeepCopyInto(out *WeightedPodAffinityTerm) { - *out = *in - in.PodAffinityTerm.DeepCopyInto(&out.PodAffinityTerm) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WeightedPodAffinityTerm. -func (in *WeightedPodAffinityTerm) DeepCopy() *WeightedPodAffinityTerm { - if in == nil { - return nil - } - out := new(WeightedPodAffinityTerm) - in.DeepCopyInto(out) - return out -} diff --git a/pkg/apimachinery/tests/BUILD b/pkg/apimachinery/tests/BUILD deleted file mode 100644 index adbe4ae6907..00000000000 --- a/pkg/apimachinery/tests/BUILD +++ /dev/null @@ -1,30 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = ["api_meta_scheme_test.go"], - importpath = "k8s.io/kubernetes/pkg/apimachinery/tests", - deps = [ - "//pkg/api/testapi:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/pkg/apimachinery/tests/api_meta_scheme_test.go b/pkg/apimachinery/tests/api_meta_scheme_test.go deleted file mode 100644 index 42b2842cd3e..00000000000 --- a/pkg/apimachinery/tests/api_meta_scheme_test.go +++ /dev/null @@ -1,121 +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 tests - -import ( - "fmt" - "reflect" - "strings" - "testing" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api/testapi" -) - -// These types do not follow the list convention as documented in -// docs/devel/api-convention.md -var listTypeExceptions = sets.NewString("APIGroupList", "APIResourceList") - -func validateListType(target reflect.Type) error { - // exceptions - if listTypeExceptions.Has(target.Name()) { - return nil - } - hasListSuffix := strings.HasSuffix(target.Name(), "List") - hasMetadata := false - hasItems := false - for i := 0; i < target.NumField(); i++ { - field := target.Field(i) - tag := field.Tag.Get("json") - switch { - case strings.HasPrefix(tag, "metadata"): - hasMetadata = true - case tag == "items": - hasItems = true - if field.Type.Kind() != reflect.Slice { - return fmt.Errorf("Expected items to be slice, got %s", field.Type.Kind()) - } - } - } - if hasListSuffix && !hasMetadata { - return fmt.Errorf("Expected type %s to contain \"metadata\"", target.Name()) - } - if hasListSuffix && !hasItems { - return fmt.Errorf("Expected type %s to contain \"items\"", target.Name()) - } - // if a type contains field Items with JSON tag "items", its name should end with List. - if !hasListSuffix && hasItems { - return fmt.Errorf("Type %s has Items, its name is expected to end with \"List\"", target.Name()) - } - return nil -} - -// TestListTypes verifies that no external type violates the api convention of -// list types. -func TestListTypes(t *testing.T) { - for groupKey, group := range testapi.Groups { - for kind, target := range group.ExternalTypes() { - t.Logf("working on %v in %v", kind, groupKey) - err := validateListType(target) - if err != nil { - t.Error(err) - } - } - } -} - -type WithoutMetaDataList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta - Items []interface{} `json:"items"` -} - -type WithoutItemsList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` -} - -type WrongItemsJSONTagList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []interface{} `json:"items,omitempty"` -} - -// If a type has Items, its name should end with "List" -type ListWithWrongName struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []interface{} `json:"items"` -} - -// TestValidateListType verifies the validateListType function reports error on -// types that violate the api convention. -func TestValidateListType(t *testing.T) { - var testTypes = []interface{}{ - WithoutMetaDataList{}, - WithoutItemsList{}, - WrongItemsJSONTagList{}, - ListWithWrongName{}, - } - for _, testType := range testTypes { - err := validateListType(reflect.TypeOf(testType)) - if err == nil { - t.Errorf("Expected error") - } - } -} diff --git a/pkg/apis/abac/BUILD b/pkg/apis/abac/BUILD index b2e5925bf96..c2f5e819855 100644 --- a/pkg/apis/abac/BUILD +++ b/pkg/apis/abac/BUILD @@ -16,7 +16,6 @@ go_library( importpath = "k8s.io/kubernetes/pkg/apis/abac", deps = [ "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", diff --git a/pkg/apis/abac/v0/conversion_test.go b/pkg/apis/abac/v0/conversion_test.go index 419005da17d..c71ba1528c3 100644 --- a/pkg/apis/abac/v0/conversion_test.go +++ b/pkg/apis/abac/v0/conversion_test.go @@ -21,64 +21,64 @@ import ( "testing" "k8s.io/apiserver/pkg/authentication/user" - api "k8s.io/kubernetes/pkg/apis/abac" + "k8s.io/kubernetes/pkg/apis/abac" "k8s.io/kubernetes/pkg/apis/abac/v0" ) func TestV0Conversion(t *testing.T) { testcases := map[string]struct { old *v0.Policy - expected *api.Policy + expected *abac.Policy }{ // a completely empty policy rule allows everything to all users "empty": { old: &v0.Policy{}, - expected: &api.Policy{Spec: api.PolicySpec{Group: user.AllAuthenticated, Readonly: false, NonResourcePath: "*", Namespace: "*", Resource: "*", APIGroup: "*"}}, + expected: &abac.Policy{Spec: abac.PolicySpec{Group: user.AllAuthenticated, Readonly: false, NonResourcePath: "*", Namespace: "*", Resource: "*", APIGroup: "*"}}, }, // specifying a user is preserved "user": { old: &v0.Policy{User: "bob"}, - expected: &api.Policy{Spec: api.PolicySpec{User: "bob", Readonly: false, NonResourcePath: "*", Namespace: "*", Resource: "*", APIGroup: "*"}}, + expected: &abac.Policy{Spec: abac.PolicySpec{User: "bob", Readonly: false, NonResourcePath: "*", Namespace: "*", Resource: "*", APIGroup: "*"}}, }, // specifying a group is preserved (and no longer matches all users) "group": { old: &v0.Policy{Group: "mygroup"}, - expected: &api.Policy{Spec: api.PolicySpec{Group: "mygroup", Readonly: false, NonResourcePath: "*", Namespace: "*", Resource: "*", APIGroup: "*"}}, + expected: &abac.Policy{Spec: abac.PolicySpec{Group: "mygroup", Readonly: false, NonResourcePath: "*", Namespace: "*", Resource: "*", APIGroup: "*"}}, }, // specifying * for user or group maps to all authenticated subjects "* user": { old: &v0.Policy{User: "*"}, - expected: &api.Policy{Spec: api.PolicySpec{Group: user.AllAuthenticated, Readonly: false, NonResourcePath: "*", Namespace: "*", Resource: "*", APIGroup: "*"}}, + expected: &abac.Policy{Spec: abac.PolicySpec{Group: user.AllAuthenticated, Readonly: false, NonResourcePath: "*", Namespace: "*", Resource: "*", APIGroup: "*"}}, }, "* group": { old: &v0.Policy{Group: "*"}, - expected: &api.Policy{Spec: api.PolicySpec{Group: user.AllAuthenticated, Readonly: false, NonResourcePath: "*", Namespace: "*", Resource: "*", APIGroup: "*"}}, + expected: &abac.Policy{Spec: abac.PolicySpec{Group: user.AllAuthenticated, Readonly: false, NonResourcePath: "*", Namespace: "*", Resource: "*", APIGroup: "*"}}, }, // specifying a namespace removes the * match on non-resource path "namespace": { old: &v0.Policy{Namespace: "myns"}, - expected: &api.Policy{Spec: api.PolicySpec{Group: user.AllAuthenticated, Readonly: false, NonResourcePath: "", Namespace: "myns", Resource: "*", APIGroup: "*"}}, + expected: &abac.Policy{Spec: abac.PolicySpec{Group: user.AllAuthenticated, Readonly: false, NonResourcePath: "", Namespace: "myns", Resource: "*", APIGroup: "*"}}, }, // specifying a resource removes the * match on non-resource path "resource": { old: &v0.Policy{Resource: "myresource"}, - expected: &api.Policy{Spec: api.PolicySpec{Group: user.AllAuthenticated, Readonly: false, NonResourcePath: "", Namespace: "*", Resource: "myresource", APIGroup: "*"}}, + expected: &abac.Policy{Spec: abac.PolicySpec{Group: user.AllAuthenticated, Readonly: false, NonResourcePath: "", Namespace: "*", Resource: "myresource", APIGroup: "*"}}, }, // specifying a namespace+resource removes the * match on non-resource path "namespace+resource": { old: &v0.Policy{Namespace: "myns", Resource: "myresource"}, - expected: &api.Policy{Spec: api.PolicySpec{Group: user.AllAuthenticated, Readonly: false, NonResourcePath: "", Namespace: "myns", Resource: "myresource", APIGroup: "*"}}, + expected: &abac.Policy{Spec: abac.PolicySpec{Group: user.AllAuthenticated, Readonly: false, NonResourcePath: "", Namespace: "myns", Resource: "myresource", APIGroup: "*"}}, }, } for k, tc := range testcases { - internal := &api.Policy{} - if err := api.Scheme.Convert(tc.old, internal, nil); err != nil { + internal := &abac.Policy{} + if err := abac.Scheme.Convert(tc.old, internal, nil); err != nil { t.Errorf("%s: unexpected error: %v", k, err) } if !reflect.DeepEqual(internal, tc.expected) { diff --git a/pkg/apis/abac/v0/doc.go b/pkg/apis/abac/v0/doc.go index d35c8b86ac2..bd73e3f49a4 100644 --- a/pkg/apis/abac/v0/doc.go +++ b/pkg/apis/abac/v0/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +groupName=abac.authorization.kubernetes.io package v0 // import "k8s.io/kubernetes/pkg/apis/abac/v0" diff --git a/pkg/apis/abac/v0/register.go b/pkg/apis/abac/v0/register.go index 4efcc092961..9a5aa984549 100644 --- a/pkg/apis/abac/v0/register.go +++ b/pkg/apis/abac/v0/register.go @@ -19,7 +19,7 @@ package v0 import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - api "k8s.io/kubernetes/pkg/apis/abac" + "k8s.io/kubernetes/pkg/apis/abac" ) const GroupName = "abac.authorization.kubernetes.io" @@ -29,11 +29,11 @@ var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v0"} func init() { // TODO: Delete this init function, abac should not have its own scheme. - if err := addKnownTypes(api.Scheme); err != nil { + if err := addKnownTypes(abac.Scheme); err != nil { // Programmer error. panic(err) } - if err := addConversionFuncs(api.Scheme); err != nil { + if err := addConversionFuncs(abac.Scheme); err != nil { // Programmer error. panic(err) } diff --git a/pkg/apis/abac/v0/zz_generated.deepcopy.go b/pkg/apis/abac/v0/zz_generated.deepcopy.go index f2b9974b00b..a4154af9825 100644 --- a/pkg/apis/abac/v0/zz_generated.deepcopy.go +++ b/pkg/apis/abac/v0/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,28 +21,9 @@ limitations under the License. package v0 import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Policy).DeepCopyInto(out.(*Policy)) - return nil - }, InType: reflect.TypeOf(&Policy{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Policy) DeepCopyInto(out *Policy) { *out = *in diff --git a/pkg/apis/abac/v1beta1/conversion_test.go b/pkg/apis/abac/v1beta1/conversion_test.go index 0f04c416b9f..814eb3f6142 100644 --- a/pkg/apis/abac/v1beta1/conversion_test.go +++ b/pkg/apis/abac/v1beta1/conversion_test.go @@ -21,40 +21,40 @@ import ( "testing" "k8s.io/apiserver/pkg/authentication/user" - api "k8s.io/kubernetes/pkg/apis/abac" + "k8s.io/kubernetes/pkg/apis/abac" "k8s.io/kubernetes/pkg/apis/abac/v1beta1" ) func TestV1Beta1Conversion(t *testing.T) { testcases := map[string]struct { old *v1beta1.Policy - expected *api.Policy + expected *abac.Policy }{ // specifying a user is preserved "user": { old: &v1beta1.Policy{Spec: v1beta1.PolicySpec{User: "bob"}}, - expected: &api.Policy{Spec: api.PolicySpec{User: "bob"}}, + expected: &abac.Policy{Spec: abac.PolicySpec{User: "bob"}}, }, // specifying a group is preserved "group": { old: &v1beta1.Policy{Spec: v1beta1.PolicySpec{Group: "mygroup"}}, - expected: &api.Policy{Spec: api.PolicySpec{Group: "mygroup"}}, + expected: &abac.Policy{Spec: abac.PolicySpec{Group: "mygroup"}}, }, // specifying * for user or group maps to all authenticated subjects "* user": { old: &v1beta1.Policy{Spec: v1beta1.PolicySpec{User: "*"}}, - expected: &api.Policy{Spec: api.PolicySpec{Group: user.AllAuthenticated}}, + expected: &abac.Policy{Spec: abac.PolicySpec{Group: user.AllAuthenticated}}, }, "* group": { old: &v1beta1.Policy{Spec: v1beta1.PolicySpec{Group: "*"}}, - expected: &api.Policy{Spec: api.PolicySpec{Group: user.AllAuthenticated}}, + expected: &abac.Policy{Spec: abac.PolicySpec{Group: user.AllAuthenticated}}, }, } for k, tc := range testcases { - internal := &api.Policy{} - if err := api.Scheme.Convert(tc.old, internal, nil); err != nil { + internal := &abac.Policy{} + if err := abac.Scheme.Convert(tc.old, internal, nil); err != nil { t.Errorf("%s: unexpected error: %v", k, err) } if !reflect.DeepEqual(internal, tc.expected) { diff --git a/pkg/apis/abac/v1beta1/doc.go b/pkg/apis/abac/v1beta1/doc.go index 5078f2f1237..3fa41e6b849 100644 --- a/pkg/apis/abac/v1beta1/doc.go +++ b/pkg/apis/abac/v1beta1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/abac // +k8s:openapi-gen=true // +k8s:defaulter-gen=TypeMeta diff --git a/pkg/apis/abac/v1beta1/register.go b/pkg/apis/abac/v1beta1/register.go index e157a69aac6..a7fc1158ea0 100644 --- a/pkg/apis/abac/v1beta1/register.go +++ b/pkg/apis/abac/v1beta1/register.go @@ -19,7 +19,7 @@ package v1beta1 import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - api "k8s.io/kubernetes/pkg/apis/abac" + "k8s.io/kubernetes/pkg/apis/abac" ) const GroupName = "abac.authorization.kubernetes.io" @@ -29,11 +29,11 @@ var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1 func init() { // TODO: delete this, abac should not have its own scheme. - if err := addKnownTypes(api.Scheme); err != nil { + if err := addKnownTypes(abac.Scheme); err != nil { // Programmer error. panic(err) } - if err := addConversionFuncs(api.Scheme); err != nil { + if err := addConversionFuncs(abac.Scheme); err != nil { // Programmer error. panic(err) } diff --git a/pkg/apis/abac/v1beta1/zz_generated.conversion.go b/pkg/apis/abac/v1beta1/zz_generated.conversion.go index fe6204d2f59..f1eaf8a678b 100644 --- a/pkg/apis/abac/v1beta1/zz_generated.conversion.go +++ b/pkg/apis/abac/v1beta1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/apis/abac/v1beta1/zz_generated.deepcopy.go b/pkg/apis/abac/v1beta1/zz_generated.deepcopy.go index 84b36e013ee..3af42c83fcf 100644 --- a/pkg/apis/abac/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/abac/v1beta1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,32 +21,9 @@ limitations under the License. package v1beta1 import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Policy).DeepCopyInto(out.(*Policy)) - return nil - }, InType: reflect.TypeOf(&Policy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PolicySpec).DeepCopyInto(out.(*PolicySpec)) - return nil - }, InType: reflect.TypeOf(&PolicySpec{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Policy) DeepCopyInto(out *Policy) { *out = *in diff --git a/pkg/apis/abac/v1beta1/zz_generated.defaults.go b/pkg/apis/abac/v1beta1/zz_generated.defaults.go index e24e70be38b..b61dda74c23 100644 --- a/pkg/apis/abac/v1beta1/zz_generated.defaults.go +++ b/pkg/apis/abac/v1beta1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/apis/abac/zz_generated.deepcopy.go b/pkg/apis/abac/zz_generated.deepcopy.go index 799c0272320..791e9c2c6fd 100644 --- a/pkg/apis/abac/zz_generated.deepcopy.go +++ b/pkg/apis/abac/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,27 +21,9 @@ limitations under the License. package abac import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -// GetGeneratedDeepCopyFuncs returns the generated funcs, since we aren't registering them. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func GetGeneratedDeepCopyFuncs() []conversion.GeneratedDeepCopyFunc { - return []conversion.GeneratedDeepCopyFunc{ - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Policy).DeepCopyInto(out.(*Policy)) - return nil - }, InType: reflect.TypeOf(&Policy{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PolicySpec).DeepCopyInto(out.(*PolicySpec)) - return nil - }, InType: reflect.TypeOf(&PolicySpec{})}, - } -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Policy) DeepCopyInto(out *Policy) { *out = *in diff --git a/pkg/apis/admission/BUILD b/pkg/apis/admission/BUILD index 5d99f310081..1192fb1fa01 100644 --- a/pkg/apis/admission/BUILD +++ b/pkg/apis/admission/BUILD @@ -17,9 +17,9 @@ go_library( deps = [ "//pkg/apis/authentication:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", ], ) @@ -36,7 +36,7 @@ filegroup( ":package-srcs", "//pkg/apis/admission/fuzzer:all-srcs", "//pkg/apis/admission/install:all-srcs", - "//pkg/apis/admission/v1alpha1:all-srcs", + "//pkg/apis/admission/v1beta1:all-srcs", ], tags = ["automanaged"], ) diff --git a/pkg/apis/admission/doc.go b/pkg/apis/admission/doc.go index 4822666798c..64273253a95 100644 --- a/pkg/apis/admission/doc.go +++ b/pkg/apis/admission/doc.go @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +groupName=admission.k8s.io package admission // import "k8s.io/kubernetes/pkg/apis/admission" diff --git a/pkg/apis/admission/install/BUILD b/pkg/apis/admission/install/BUILD index 15d94fadea5..a7f52efeebc 100644 --- a/pkg/apis/admission/install/BUILD +++ b/pkg/apis/admission/install/BUILD @@ -10,9 +10,9 @@ go_library( srcs = ["install.go"], importpath = "k8s.io/kubernetes/pkg/apis/admission/install", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/admission:go_default_library", - "//pkg/apis/admission/v1alpha1:go_default_library", + "//pkg/apis/admission/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/pkg/apis/admission/install/install.go b/pkg/apis/admission/install/install.go index f0b0dcf79bd..404f549c383 100644 --- a/pkg/apis/admission/install/install.go +++ b/pkg/apis/admission/install/install.go @@ -23,13 +23,13 @@ import ( "k8s.io/apimachinery/pkg/apimachinery/registered" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/admission" - "k8s.io/kubernetes/pkg/apis/admission/v1alpha1" + "k8s.io/kubernetes/pkg/apis/admission/v1beta1" ) func init() { - Install(api.GroupFactoryRegistry, api.Registry, api.Scheme) + Install(legacyscheme.GroupFactoryRegistry, legacyscheme.Registry, legacyscheme.Scheme) } // Install registers the API group and adds types to a scheme @@ -37,12 +37,12 @@ func Install(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *r if err := announced.NewGroupMetaFactory( &announced.GroupMetaFactoryArgs{ GroupName: admission.GroupName, - VersionPreferenceOrder: []string{v1alpha1.SchemeGroupVersion.Version}, + VersionPreferenceOrder: []string{v1beta1.SchemeGroupVersion.Version}, RootScopedKinds: sets.NewString("AdmissionReview"), AddInternalObjectsToScheme: admission.AddToScheme, }, announced.VersionToSchemeFunc{ - v1alpha1.SchemeGroupVersion.Version: v1alpha1.AddToScheme, + v1beta1.SchemeGroupVersion.Version: v1beta1.AddToScheme, }, ).Announce(groupFactoryRegistry).RegisterAndEnable(registry, scheme); err != nil { panic(err) diff --git a/pkg/apis/admission/types.go b/pkg/apis/admission/types.go index a816fa8d3fa..37822220e9f 100644 --- a/pkg/apis/admission/types.go +++ b/pkg/apis/admission/types.go @@ -19,38 +19,34 @@ package admission import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "k8s.io/kubernetes/pkg/apis/authentication" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// AdmissionReview describes an admission request. +// AdmissionReview describes an admission review request/response. type AdmissionReview struct { metav1.TypeMeta - // Spec describes the attributes for the admission request. - // Since this admission controller is non-mutating the webhook should avoid setting this in its response to avoid the - // cost of deserializing it. - Spec AdmissionReviewSpec - // Status is filled in by the webhook and indicates whether the admission request should be permitted. - Status AdmissionReviewStatus + // Request describes the attributes for the admission request. + // +optional + Request *AdmissionRequest + + // Response describes the attributes for the admission response. + // +optional + Response *AdmissionResponse } -// AdmissionReviewSpec describes the admission.Attributes for the admission request. -type AdmissionReviewSpec struct { +// AdmissionRequest describes the admission.Attributes for the admission request. +type AdmissionRequest struct { + // UID is an identifier for the individual request/response. It allows us to distinguish instances of requests which are + // 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. + UID types.UID // Kind is the type of object being manipulated. For example: Pod Kind metav1.GroupVersionKind - // Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and - // rely on the server to generate the name. If that is the case, this method will return the empty string. - Name string - // Namespace is the namespace associated with the request (if any). - Namespace string - // Object is the object from the incoming request prior to default values being applied - Object runtime.Object - // OldObject is the existing object. Only populated for UPDATE requests. - OldObject runtime.Object - // Operation is the operation being performed - Operation Operation // Resource is the name of the resource being requested. This is not the kind. For example: pods Resource metav1.GroupVersionResource // SubResource is the name of the subresource being requested. This is a different resource, scoped to the parent @@ -58,21 +54,54 @@ type AdmissionReviewSpec struct { // /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod" (because status operates on // pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource // "binding", and kind "Binding". + // +optional SubResource string + // Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and + // rely on the server to generate the name. If that is the case, this method will return the empty string. + // +optional + Name string + // Namespace is the namespace associated with the request (if any). + // +optional + Namespace string + // Operation is the operation being performed + Operation Operation // UserInfo is information about the requesting user UserInfo authentication.UserInfo + // Object is the object from the incoming request prior to default values being applied + // +optional + Object runtime.Object + // OldObject is the existing object. Only populated for UPDATE requests. + // +optional + OldObject runtime.Object } -// AdmissionReviewStatus describes the status of the admission request. -type AdmissionReviewStatus struct { +// AdmissionResponse describes an admission response. +type AdmissionResponse struct { + // UID is an identifier for the individual request/response. + // This should be copied over from the corresponding AdmissionRequest. + UID types.UID // Allowed indicates whether or not the admission request was permitted. Allowed bool // Result contains extra details into why an admission request was denied. // This field IS NOT consulted in any way if "Allowed" is "true". // +optional Result *metav1.Status + // Patch contains the actual patch. Currently we only support a response in the form of JSONPatch, RFC 6902. + // +optional + Patch []byte + // PatchType indicates the form the Patch will take. Currently we only support "JSONPatch". + // +optional + PatchType *PatchType } +// PatchType is the type of patch being used to represent the mutated object +type PatchType string + +// PatchType constants. +const ( + PatchTypeJSONPatch PatchType = "JSONPatch" +) + // Operation is the type of resource operation being checked for admission control type Operation string diff --git a/pkg/apis/admission/v1alpha1/BUILD b/pkg/apis/admission/v1alpha1/BUILD deleted file mode 100644 index 6671ee78b6f..00000000000 --- a/pkg/apis/admission/v1alpha1/BUILD +++ /dev/null @@ -1,38 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "doc.go", - "register.go", - "zz_generated.conversion.go", - "zz_generated.defaults.go", - ], - importpath = "k8s.io/kubernetes/pkg/apis/admission/v1alpha1", - deps = [ - "//pkg/apis/admission:go_default_library", - "//vendor/k8s.io/api/admission/v1alpha1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/pkg/apis/admission/v1alpha1/doc.go b/pkg/apis/admission/v1alpha1/doc.go deleted file mode 100644 index c2651742708..00000000000 --- a/pkg/apis/admission/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/admission -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/admission/v1alpha1 -// +k8s:defaulter-gen=TypeMeta -// +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/admission/v1alpha1 - -// +groupName=admission.k8s.io -package v1alpha1 // import "k8s.io/kubernetes/pkg/apis/admission/v1alpha1" diff --git a/pkg/apis/admission/v1alpha1/register.go b/pkg/apis/admission/v1alpha1/register.go deleted file mode 100644 index 8e12bcea04b..00000000000 --- a/pkg/apis/admission/v1alpha1/register.go +++ /dev/null @@ -1,45 +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 ( - admissionv1alpha1 "k8s.io/api/admission/v1alpha1" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -// GroupName is the group name for this API. -const GroupName = "admission.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 = &admissionv1alpha1.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(RegisterDefaults) -} diff --git a/pkg/apis/admission/v1alpha1/zz_generated.conversion.go b/pkg/apis/admission/v1alpha1/zz_generated.conversion.go deleted file mode 100644 index c96eb913882..00000000000 --- a/pkg/apis/admission/v1alpha1/zz_generated.conversion.go +++ /dev/null @@ -1,149 +0,0 @@ -// +build !ignore_autogenerated - -/* -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. -*/ - -// This file was autogenerated by conversion-gen. Do not edit it manually! - -package v1alpha1 - -import ( - v1alpha1 "k8s.io/api/admission/v1alpha1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" - admission "k8s.io/kubernetes/pkg/apis/admission" - unsafe "unsafe" -) - -func init() { - localSchemeBuilder.Register(RegisterConversions) -} - -// RegisterConversions adds conversion functions to the given scheme. -// Public to allow building arbitrary schemes. -func RegisterConversions(scheme *runtime.Scheme) error { - return scheme.AddGeneratedConversionFuncs( - Convert_v1alpha1_AdmissionReview_To_admission_AdmissionReview, - Convert_admission_AdmissionReview_To_v1alpha1_AdmissionReview, - Convert_v1alpha1_AdmissionReviewSpec_To_admission_AdmissionReviewSpec, - Convert_admission_AdmissionReviewSpec_To_v1alpha1_AdmissionReviewSpec, - Convert_v1alpha1_AdmissionReviewStatus_To_admission_AdmissionReviewStatus, - Convert_admission_AdmissionReviewStatus_To_v1alpha1_AdmissionReviewStatus, - ) -} - -func autoConvert_v1alpha1_AdmissionReview_To_admission_AdmissionReview(in *v1alpha1.AdmissionReview, out *admission.AdmissionReview, s conversion.Scope) error { - if err := Convert_v1alpha1_AdmissionReviewSpec_To_admission_AdmissionReviewSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1alpha1_AdmissionReviewStatus_To_admission_AdmissionReviewStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha1_AdmissionReview_To_admission_AdmissionReview is an autogenerated conversion function. -func Convert_v1alpha1_AdmissionReview_To_admission_AdmissionReview(in *v1alpha1.AdmissionReview, out *admission.AdmissionReview, s conversion.Scope) error { - return autoConvert_v1alpha1_AdmissionReview_To_admission_AdmissionReview(in, out, s) -} - -func autoConvert_admission_AdmissionReview_To_v1alpha1_AdmissionReview(in *admission.AdmissionReview, out *v1alpha1.AdmissionReview, s conversion.Scope) error { - if err := Convert_admission_AdmissionReviewSpec_To_v1alpha1_AdmissionReviewSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_admission_AdmissionReviewStatus_To_v1alpha1_AdmissionReviewStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_admission_AdmissionReview_To_v1alpha1_AdmissionReview is an autogenerated conversion function. -func Convert_admission_AdmissionReview_To_v1alpha1_AdmissionReview(in *admission.AdmissionReview, out *v1alpha1.AdmissionReview, s conversion.Scope) error { - return autoConvert_admission_AdmissionReview_To_v1alpha1_AdmissionReview(in, out, s) -} - -func autoConvert_v1alpha1_AdmissionReviewSpec_To_admission_AdmissionReviewSpec(in *v1alpha1.AdmissionReviewSpec, out *admission.AdmissionReviewSpec, s conversion.Scope) error { - out.Kind = in.Kind - if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&in.Object, &out.Object, s); err != nil { - return err - } - if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&in.OldObject, &out.OldObject, s); err != nil { - return err - } - out.Operation = admission.Operation(in.Operation) - out.Name = in.Name - out.Namespace = in.Namespace - out.Resource = in.Resource - out.SubResource = in.SubResource - // TODO: Inefficient conversion - can we improve it? - if err := s.Convert(&in.UserInfo, &out.UserInfo, 0); err != nil { - return err - } - return nil -} - -// Convert_v1alpha1_AdmissionReviewSpec_To_admission_AdmissionReviewSpec is an autogenerated conversion function. -func Convert_v1alpha1_AdmissionReviewSpec_To_admission_AdmissionReviewSpec(in *v1alpha1.AdmissionReviewSpec, out *admission.AdmissionReviewSpec, s conversion.Scope) error { - return autoConvert_v1alpha1_AdmissionReviewSpec_To_admission_AdmissionReviewSpec(in, out, s) -} - -func autoConvert_admission_AdmissionReviewSpec_To_v1alpha1_AdmissionReviewSpec(in *admission.AdmissionReviewSpec, out *v1alpha1.AdmissionReviewSpec, s conversion.Scope) error { - out.Kind = in.Kind - out.Name = in.Name - out.Namespace = in.Namespace - if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&in.Object, &out.Object, s); err != nil { - return err - } - if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&in.OldObject, &out.OldObject, s); err != nil { - return err - } - out.Operation = v1alpha1.Operation(in.Operation) - out.Resource = in.Resource - out.SubResource = in.SubResource - // TODO: Inefficient conversion - can we improve it? - if err := s.Convert(&in.UserInfo, &out.UserInfo, 0); err != nil { - return err - } - return nil -} - -// Convert_admission_AdmissionReviewSpec_To_v1alpha1_AdmissionReviewSpec is an autogenerated conversion function. -func Convert_admission_AdmissionReviewSpec_To_v1alpha1_AdmissionReviewSpec(in *admission.AdmissionReviewSpec, out *v1alpha1.AdmissionReviewSpec, s conversion.Scope) error { - return autoConvert_admission_AdmissionReviewSpec_To_v1alpha1_AdmissionReviewSpec(in, out, s) -} - -func autoConvert_v1alpha1_AdmissionReviewStatus_To_admission_AdmissionReviewStatus(in *v1alpha1.AdmissionReviewStatus, out *admission.AdmissionReviewStatus, s conversion.Scope) error { - out.Allowed = in.Allowed - out.Result = (*v1.Status)(unsafe.Pointer(in.Result)) - return nil -} - -// Convert_v1alpha1_AdmissionReviewStatus_To_admission_AdmissionReviewStatus is an autogenerated conversion function. -func Convert_v1alpha1_AdmissionReviewStatus_To_admission_AdmissionReviewStatus(in *v1alpha1.AdmissionReviewStatus, out *admission.AdmissionReviewStatus, s conversion.Scope) error { - return autoConvert_v1alpha1_AdmissionReviewStatus_To_admission_AdmissionReviewStatus(in, out, s) -} - -func autoConvert_admission_AdmissionReviewStatus_To_v1alpha1_AdmissionReviewStatus(in *admission.AdmissionReviewStatus, out *v1alpha1.AdmissionReviewStatus, s conversion.Scope) error { - out.Allowed = in.Allowed - out.Result = (*v1.Status)(unsafe.Pointer(in.Result)) - return nil -} - -// Convert_admission_AdmissionReviewStatus_To_v1alpha1_AdmissionReviewStatus is an autogenerated conversion function. -func Convert_admission_AdmissionReviewStatus_To_v1alpha1_AdmissionReviewStatus(in *admission.AdmissionReviewStatus, out *v1alpha1.AdmissionReviewStatus, s conversion.Scope) error { - return autoConvert_admission_AdmissionReviewStatus_To_v1alpha1_AdmissionReviewStatus(in, out, s) -} diff --git a/pkg/apis/admission/v1alpha1/zz_generated.defaults.go b/pkg/apis/admission/v1alpha1/zz_generated.defaults.go deleted file mode 100644 index 7e6df29d4ae..00000000000 --- a/pkg/apis/admission/v1alpha1/zz_generated.defaults.go +++ /dev/null @@ -1,32 +0,0 @@ -// +build !ignore_autogenerated - -/* -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. -*/ - -// This file was autogenerated by defaulter-gen. Do not edit it manually! - -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/pkg/apis/admission/v1beta1/BUILD b/pkg/apis/admission/v1beta1/BUILD new file mode 100644 index 00000000000..cbe9e893b1d --- /dev/null +++ b/pkg/apis/admission/v1beta1/BUILD @@ -0,0 +1,39 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "register.go", + "zz_generated.conversion.go", + "zz_generated.defaults.go", + ], + importpath = "k8s.io/kubernetes/pkg/apis/admission/v1beta1", + deps = [ + "//pkg/apis/admission:go_default_library", + "//vendor/k8s.io/api/admission/v1beta1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/pkg/apis/admission/v1beta1/doc.go b/pkg/apis/admission/v1beta1/doc.go new file mode 100644 index 00000000000..1bb4d198e48 --- /dev/null +++ b/pkg/apis/admission/v1beta1/doc.go @@ -0,0 +1,23 @@ +/* +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/admission +// +k8s:conversion-gen-external-types=k8s.io/api/admission/v1beta1 +// +k8s:defaulter-gen=TypeMeta +// +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/admission/v1beta1 + +// +groupName=admission.k8s.io +package v1beta1 // import "k8s.io/kubernetes/pkg/apis/admission/v1beta1" diff --git a/pkg/apis/admission/v1beta1/register.go b/pkg/apis/admission/v1beta1/register.go new file mode 100644 index 00000000000..8b3eea5d517 --- /dev/null +++ b/pkg/apis/admission/v1beta1/register.go @@ -0,0 +1,45 @@ +/* +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 v1beta1 + +import ( + admissionv1beta1 "k8s.io/api/admission/v1beta1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName is the group name for this API. +const GroupName = "admission.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + localSchemeBuilder = &admissionv1beta1.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(RegisterDefaults) +} diff --git a/pkg/apis/admission/v1beta1/zz_generated.conversion.go b/pkg/apis/admission/v1beta1/zz_generated.conversion.go new file mode 100644 index 00000000000..971a4849ab2 --- /dev/null +++ b/pkg/apis/admission/v1beta1/zz_generated.conversion.go @@ -0,0 +1,167 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by conversion-gen. Do not edit it manually! + +package v1beta1 + +import ( + unsafe "unsafe" + + v1beta1 "k8s.io/api/admission/v1beta1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + conversion "k8s.io/apimachinery/pkg/conversion" + runtime "k8s.io/apimachinery/pkg/runtime" + types "k8s.io/apimachinery/pkg/types" + admission "k8s.io/kubernetes/pkg/apis/admission" +) + +func init() { + localSchemeBuilder.Register(RegisterConversions) +} + +// RegisterConversions adds conversion functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterConversions(scheme *runtime.Scheme) error { + return scheme.AddGeneratedConversionFuncs( + Convert_v1beta1_AdmissionRequest_To_admission_AdmissionRequest, + Convert_admission_AdmissionRequest_To_v1beta1_AdmissionRequest, + Convert_v1beta1_AdmissionResponse_To_admission_AdmissionResponse, + Convert_admission_AdmissionResponse_To_v1beta1_AdmissionResponse, + Convert_v1beta1_AdmissionReview_To_admission_AdmissionReview, + Convert_admission_AdmissionReview_To_v1beta1_AdmissionReview, + ) +} + +func autoConvert_v1beta1_AdmissionRequest_To_admission_AdmissionRequest(in *v1beta1.AdmissionRequest, out *admission.AdmissionRequest, s conversion.Scope) error { + out.UID = types.UID(in.UID) + out.Kind = in.Kind + out.Resource = in.Resource + out.SubResource = in.SubResource + out.Name = in.Name + out.Namespace = in.Namespace + out.Operation = admission.Operation(in.Operation) + // TODO: Inefficient conversion - can we improve it? + if err := s.Convert(&in.UserInfo, &out.UserInfo, 0); err != nil { + return err + } + if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&in.Object, &out.Object, s); err != nil { + return err + } + if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&in.OldObject, &out.OldObject, s); err != nil { + return err + } + return nil +} + +// Convert_v1beta1_AdmissionRequest_To_admission_AdmissionRequest is an autogenerated conversion function. +func Convert_v1beta1_AdmissionRequest_To_admission_AdmissionRequest(in *v1beta1.AdmissionRequest, out *admission.AdmissionRequest, s conversion.Scope) error { + return autoConvert_v1beta1_AdmissionRequest_To_admission_AdmissionRequest(in, out, s) +} + +func autoConvert_admission_AdmissionRequest_To_v1beta1_AdmissionRequest(in *admission.AdmissionRequest, out *v1beta1.AdmissionRequest, s conversion.Scope) error { + out.UID = types.UID(in.UID) + out.Kind = in.Kind + out.Resource = in.Resource + out.SubResource = in.SubResource + out.Name = in.Name + out.Namespace = in.Namespace + out.Operation = v1beta1.Operation(in.Operation) + // TODO: Inefficient conversion - can we improve it? + if err := s.Convert(&in.UserInfo, &out.UserInfo, 0); err != nil { + return err + } + if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&in.Object, &out.Object, s); err != nil { + return err + } + if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&in.OldObject, &out.OldObject, s); err != nil { + return err + } + return nil +} + +// Convert_admission_AdmissionRequest_To_v1beta1_AdmissionRequest is an autogenerated conversion function. +func Convert_admission_AdmissionRequest_To_v1beta1_AdmissionRequest(in *admission.AdmissionRequest, out *v1beta1.AdmissionRequest, s conversion.Scope) error { + return autoConvert_admission_AdmissionRequest_To_v1beta1_AdmissionRequest(in, out, s) +} + +func autoConvert_v1beta1_AdmissionResponse_To_admission_AdmissionResponse(in *v1beta1.AdmissionResponse, out *admission.AdmissionResponse, s conversion.Scope) error { + out.UID = types.UID(in.UID) + out.Allowed = in.Allowed + out.Result = (*v1.Status)(unsafe.Pointer(in.Result)) + out.Patch = *(*[]byte)(unsafe.Pointer(&in.Patch)) + out.PatchType = (*admission.PatchType)(unsafe.Pointer(in.PatchType)) + return nil +} + +// Convert_v1beta1_AdmissionResponse_To_admission_AdmissionResponse is an autogenerated conversion function. +func Convert_v1beta1_AdmissionResponse_To_admission_AdmissionResponse(in *v1beta1.AdmissionResponse, out *admission.AdmissionResponse, s conversion.Scope) error { + return autoConvert_v1beta1_AdmissionResponse_To_admission_AdmissionResponse(in, out, s) +} + +func autoConvert_admission_AdmissionResponse_To_v1beta1_AdmissionResponse(in *admission.AdmissionResponse, out *v1beta1.AdmissionResponse, s conversion.Scope) error { + out.UID = types.UID(in.UID) + out.Allowed = in.Allowed + out.Result = (*v1.Status)(unsafe.Pointer(in.Result)) + out.Patch = *(*[]byte)(unsafe.Pointer(&in.Patch)) + out.PatchType = (*v1beta1.PatchType)(unsafe.Pointer(in.PatchType)) + return nil +} + +// Convert_admission_AdmissionResponse_To_v1beta1_AdmissionResponse is an autogenerated conversion function. +func Convert_admission_AdmissionResponse_To_v1beta1_AdmissionResponse(in *admission.AdmissionResponse, out *v1beta1.AdmissionResponse, s conversion.Scope) error { + return autoConvert_admission_AdmissionResponse_To_v1beta1_AdmissionResponse(in, out, s) +} + +func autoConvert_v1beta1_AdmissionReview_To_admission_AdmissionReview(in *v1beta1.AdmissionReview, out *admission.AdmissionReview, s conversion.Scope) error { + if in.Request != nil { + in, out := &in.Request, &out.Request + *out = new(admission.AdmissionRequest) + if err := Convert_v1beta1_AdmissionRequest_To_admission_AdmissionRequest(*in, *out, s); err != nil { + return err + } + } else { + out.Request = nil + } + out.Response = (*admission.AdmissionResponse)(unsafe.Pointer(in.Response)) + return nil +} + +// Convert_v1beta1_AdmissionReview_To_admission_AdmissionReview is an autogenerated conversion function. +func Convert_v1beta1_AdmissionReview_To_admission_AdmissionReview(in *v1beta1.AdmissionReview, out *admission.AdmissionReview, s conversion.Scope) error { + return autoConvert_v1beta1_AdmissionReview_To_admission_AdmissionReview(in, out, s) +} + +func autoConvert_admission_AdmissionReview_To_v1beta1_AdmissionReview(in *admission.AdmissionReview, out *v1beta1.AdmissionReview, s conversion.Scope) error { + if in.Request != nil { + in, out := &in.Request, &out.Request + *out = new(v1beta1.AdmissionRequest) + if err := Convert_admission_AdmissionRequest_To_v1beta1_AdmissionRequest(*in, *out, s); err != nil { + return err + } + } else { + out.Request = nil + } + out.Response = (*v1beta1.AdmissionResponse)(unsafe.Pointer(in.Response)) + return nil +} + +// Convert_admission_AdmissionReview_To_v1beta1_AdmissionReview is an autogenerated conversion function. +func Convert_admission_AdmissionReview_To_v1beta1_AdmissionReview(in *admission.AdmissionReview, out *v1beta1.AdmissionReview, s conversion.Scope) error { + return autoConvert_admission_AdmissionReview_To_v1beta1_AdmissionReview(in, out, s) +} diff --git a/pkg/apis/admission/v1beta1/zz_generated.defaults.go b/pkg/apis/admission/v1beta1/zz_generated.defaults.go new file mode 100644 index 00000000000..b61dda74c23 --- /dev/null +++ b/pkg/apis/admission/v1beta1/zz_generated.defaults.go @@ -0,0 +1,32 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by defaulter-gen. Do not edit it manually! + +package v1beta1 + +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/pkg/apis/admission/zz_generated.deepcopy.go b/pkg/apis/admission/zz_generated.deepcopy.go index 22cd49d5e47..84ce688d4b5 100644 --- a/pkg/apis/admission/zz_generated.deepcopy.go +++ b/pkg/apis/admission/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,42 +22,99 @@ package admission import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdmissionRequest) DeepCopyInto(out *AdmissionRequest) { + *out = *in + out.Kind = in.Kind + out.Resource = in.Resource + in.UserInfo.DeepCopyInto(&out.UserInfo) + if in.Object == nil { + out.Object = nil + } else { + out.Object = in.Object.DeepCopyObject() + } + if in.OldObject == nil { + out.OldObject = nil + } else { + out.OldObject = in.OldObject.DeepCopyObject() + } + return } -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AdmissionReview).DeepCopyInto(out.(*AdmissionReview)) - return nil - }, InType: reflect.TypeOf(&AdmissionReview{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AdmissionReviewSpec).DeepCopyInto(out.(*AdmissionReviewSpec)) - return nil - }, InType: reflect.TypeOf(&AdmissionReviewSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AdmissionReviewStatus).DeepCopyInto(out.(*AdmissionReviewStatus)) - return nil - }, InType: reflect.TypeOf(&AdmissionReviewStatus{})}, - ) +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionRequest. +func (in *AdmissionRequest) DeepCopy() *AdmissionRequest { + if in == nil { + return nil + } + out := new(AdmissionRequest) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdmissionResponse) DeepCopyInto(out *AdmissionResponse) { + *out = *in + if in.Result != nil { + in, out := &in.Result, &out.Result + if *in == nil { + *out = nil + } else { + *out = new(v1.Status) + (*in).DeepCopyInto(*out) + } + } + if in.Patch != nil { + in, out := &in.Patch, &out.Patch + *out = make([]byte, len(*in)) + copy(*out, *in) + } + if in.PatchType != nil { + in, out := &in.PatchType, &out.PatchType + if *in == nil { + *out = nil + } else { + *out = new(PatchType) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionResponse. +func (in *AdmissionResponse) DeepCopy() *AdmissionResponse { + if in == nil { + return nil + } + out := new(AdmissionResponse) + in.DeepCopyInto(out) + return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AdmissionReview) DeepCopyInto(out *AdmissionReview) { *out = *in out.TypeMeta = in.TypeMeta - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) + if in.Request != nil { + in, out := &in.Request, &out.Request + if *in == nil { + *out = nil + } else { + *out = new(AdmissionRequest) + (*in).DeepCopyInto(*out) + } + } + if in.Response != nil { + in, out := &in.Response, &out.Response + if *in == nil { + *out = nil + } else { + *out = new(AdmissionResponse) + (*in).DeepCopyInto(*out) + } + } return } @@ -79,57 +136,3 @@ func (in *AdmissionReview) DeepCopyObject() runtime.Object { return nil } } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AdmissionReviewSpec) DeepCopyInto(out *AdmissionReviewSpec) { - *out = *in - out.Kind = in.Kind - if in.Object == nil { - out.Object = nil - } else { - out.Object = in.Object.DeepCopyObject() - } - if in.OldObject == nil { - out.OldObject = nil - } else { - out.OldObject = in.OldObject.DeepCopyObject() - } - out.Resource = in.Resource - in.UserInfo.DeepCopyInto(&out.UserInfo) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionReviewSpec. -func (in *AdmissionReviewSpec) DeepCopy() *AdmissionReviewSpec { - if in == nil { - return nil - } - out := new(AdmissionReviewSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AdmissionReviewStatus) DeepCopyInto(out *AdmissionReviewStatus) { - *out = *in - if in.Result != nil { - in, out := &in.Result, &out.Result - if *in == nil { - *out = nil - } else { - *out = new(v1.Status) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionReviewStatus. -func (in *AdmissionReviewStatus) DeepCopy() *AdmissionReviewStatus { - if in == nil { - return nil - } - out := new(AdmissionReviewStatus) - in.DeepCopyInto(out) - return out -} diff --git a/pkg/apis/admissionregistration/BUILD b/pkg/apis/admissionregistration/BUILD index 9412c20050e..f223ec0a80f 100644 --- a/pkg/apis/admissionregistration/BUILD +++ b/pkg/apis/admissionregistration/BUILD @@ -16,7 +16,6 @@ go_library( importpath = "k8s.io/kubernetes/pkg/apis/admissionregistration", deps = [ "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], @@ -36,6 +35,7 @@ filegroup( "//pkg/apis/admissionregistration/fuzzer:all-srcs", "//pkg/apis/admissionregistration/install:all-srcs", "//pkg/apis/admissionregistration/v1alpha1:all-srcs", + "//pkg/apis/admissionregistration/v1beta1:all-srcs", "//pkg/apis/admissionregistration/validation:all-srcs", ], tags = ["automanaged"], diff --git a/pkg/apis/admissionregistration/doc.go b/pkg/apis/admissionregistration/doc.go index 5dfc4e74e37..7b76bb29b24 100644 --- a/pkg/apis/admissionregistration/doc.go +++ b/pkg/apis/admissionregistration/doc.go @@ -14,11 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // Package admissionregistration is the internal version of the API. // AdmissionConfiguration and AdmissionPluginConfiguration are legacy static admission plugin configuration -// InitializerConfiguration and ExternalAdmissionHookConfiguration is for the +// InitializerConfiguration, ValidatingWebhookConfiguration, and MutatingWebhookConfiguration are for the // new dynamic admission controller configuration. // +groupName=admissionregistration.k8s.io package admissionregistration // import "k8s.io/kubernetes/pkg/apis/admissionregistration" diff --git a/pkg/apis/admissionregistration/fuzzer/fuzzer.go b/pkg/apis/admissionregistration/fuzzer/fuzzer.go index 7c0287f3cc3..4275951b514 100644 --- a/pkg/apis/admissionregistration/fuzzer/fuzzer.go +++ b/pkg/apis/admissionregistration/fuzzer/fuzzer.go @@ -26,7 +26,7 @@ import ( // Funcs returns the fuzzer functions for the admissionregistration api group. var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { return []interface{}{ - func(obj *admissionregistration.ExternalAdmissionHook, c fuzz.Continue) { + func(obj *admissionregistration.Webhook, c fuzz.Continue) { c.FuzzNoCustom(obj) // fuzz self without calling this function again p := admissionregistration.FailurePolicyType("Fail") obj.FailurePolicy = &p diff --git a/pkg/apis/admissionregistration/install/BUILD b/pkg/apis/admissionregistration/install/BUILD index c29b09d3470..04bb06074b4 100644 --- a/pkg/apis/admissionregistration/install/BUILD +++ b/pkg/apis/admissionregistration/install/BUILD @@ -10,9 +10,10 @@ go_library( srcs = ["install.go"], importpath = "k8s.io/kubernetes/pkg/apis/admissionregistration/install", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/admissionregistration:go_default_library", "//pkg/apis/admissionregistration/v1alpha1:go_default_library", + "//pkg/apis/admissionregistration/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/pkg/apis/admissionregistration/install/install.go b/pkg/apis/admissionregistration/install/install.go index b2ff84b3e31..81f4ff2ffc2 100644 --- a/pkg/apis/admissionregistration/install/install.go +++ b/pkg/apis/admissionregistration/install/install.go @@ -21,13 +21,14 @@ import ( "k8s.io/apimachinery/pkg/apimachinery/registered" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/admissionregistration" "k8s.io/kubernetes/pkg/apis/admissionregistration/v1alpha1" + "k8s.io/kubernetes/pkg/apis/admissionregistration/v1beta1" ) func init() { - Install(api.GroupFactoryRegistry, api.Registry, api.Scheme) + Install(legacyscheme.GroupFactoryRegistry, legacyscheme.Registry, legacyscheme.Scheme) } // Install registers the API group and adds types to a scheme @@ -35,12 +36,13 @@ func Install(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *r if err := announced.NewGroupMetaFactory( &announced.GroupMetaFactoryArgs{ GroupName: admissionregistration.GroupName, - RootScopedKinds: sets.NewString("InitializerConfiguration", "ExternalAdmissionHookConfiguration"), - VersionPreferenceOrder: []string{v1alpha1.SchemeGroupVersion.Version}, + RootScopedKinds: sets.NewString("InitializerConfiguration", "ValidatingWebhookConfiguration", "MutatingWebhookConfiguration"), + VersionPreferenceOrder: []string{v1beta1.SchemeGroupVersion.Version, v1alpha1.SchemeGroupVersion.Version}, AddInternalObjectsToScheme: admissionregistration.AddToScheme, }, announced.VersionToSchemeFunc{ v1alpha1.SchemeGroupVersion.Version: v1alpha1.AddToScheme, + v1beta1.SchemeGroupVersion.Version: v1beta1.AddToScheme, }, ).Announce(groupFactoryRegistry).RegisterAndEnable(registry, scheme); err != nil { panic(err) diff --git a/pkg/apis/admissionregistration/register.go b/pkg/apis/admissionregistration/register.go index 1fe291a8f23..941259ce628 100644 --- a/pkg/apis/admissionregistration/register.go +++ b/pkg/apis/admissionregistration/register.go @@ -46,8 +46,10 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &InitializerConfiguration{}, &InitializerConfigurationList{}, - &ExternalAdmissionHookConfiguration{}, - &ExternalAdmissionHookConfigurationList{}, + &ValidatingWebhookConfiguration{}, + &ValidatingWebhookConfigurationList{}, + &MutatingWebhookConfiguration{}, + &MutatingWebhookConfigurationList{}, ) return nil } diff --git a/pkg/apis/admissionregistration/types.go b/pkg/apis/admissionregistration/types.go index b4676560037..091782b81a0 100644 --- a/pkg/apis/admissionregistration/types.go +++ b/pkg/apis/admissionregistration/types.go @@ -106,7 +106,7 @@ type Rule struct { type FailurePolicyType string const ( - // Ignore means the initilizer is removed from the initializers list of an + // Ignore means the initializer is removed from the initializers list of an // object if the initializer is timed out. Ignore FailurePolicyType = "Ignore" // For 1.7, only "Ignore" is allowed. "Fail" will be allowed when the @@ -118,35 +118,61 @@ const ( // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// ExternalAdmissionHookConfiguration describes the configuration of initializers. -type ExternalAdmissionHookConfiguration struct { +// ValidatingWebhookConfiguration describes the configuration of an admission webhook that accepts or rejects and object without changing it. +type ValidatingWebhookConfiguration struct { metav1.TypeMeta // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata. // +optional metav1.ObjectMeta - // ExternalAdmissionHooks is a list of external admission webhooks and the - // affected resources and operations. + // Webhooks is a list of webhooks and the affected resources and operations. // +optional - ExternalAdmissionHooks []ExternalAdmissionHook + Webhooks []Webhook } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// ExternalAdmissionHookConfigurationList is a list of ExternalAdmissionHookConfiguration. -type ExternalAdmissionHookConfigurationList struct { +// ValidatingWebhookConfigurationList is a list of ValidatingWebhookConfiguration. +type ValidatingWebhookConfigurationList struct { metav1.TypeMeta // Standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds // +optional metav1.ListMeta - // List of ExternalAdmissionHookConfiguration. - Items []ExternalAdmissionHookConfiguration + // List of ValidatingWebhookConfigurations. + Items []ValidatingWebhookConfiguration } -// ExternalAdmissionHook describes an external admission webhook and the -// resources and operations it applies to. -type ExternalAdmissionHook struct { - // The name of the external admission webhook. +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// MutatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and may change the object. +type MutatingWebhookConfiguration struct { + metav1.TypeMeta + // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata. + // +optional + metav1.ObjectMeta + // Webhooks is a list of webhooks and the affected resources and operations. + // +optional + Webhooks []Webhook +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// MutatingWebhookConfigurationList is a list of MutatingWebhookConfiguration. +type MutatingWebhookConfigurationList struct { + metav1.TypeMeta + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds + // +optional + metav1.ListMeta + // List of MutatingWebhookConfiguration. + Items []MutatingWebhookConfiguration +} + +// Webhook describes an admission webhook and the resources and operations it applies to. +type Webhook struct { + // 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. @@ -155,7 +181,7 @@ type ExternalAdmissionHook struct { // ClientConfig defines how to communicate with the hook. // Required - ClientConfig AdmissionHookClientConfig + ClientConfig WebhookClientConfig // Rules describes what operations on what resources/subresources the webhook cares about. // The webhook cares about an operation if it matches _any_ Rule. @@ -165,6 +191,52 @@ type ExternalAdmissionHook struct { // allowed values are Ignore or Fail. Defaults to Ignore. // +optional FailurePolicy *FailurePolicyType + + // 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 other cluster scoped resource, + // it is not subjected to the webhook. + // + // For 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": { + // "matchExpressions": [ + // { + // "key": "runlevel", + // "operator": "NotIn", + // "values": [ + // "0", + // "1" + // ] + // } + // ] + // } + // + // If 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": { + // "matchExpressions": [ + // { + // "key": "environment", + // "operator": "In", + // "values": [ + // "prod", + // "staging" + // ] + // } + // ] + // } + // + // See + // https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + // for more examples of label selectors. + // + // Default to the empty LabelSelector, which matches everything. + // +optional + NamespaceSelector *metav1.LabelSelector } // RuleWithOperations is a tuple of Operations and Resources. It is recommended to make @@ -191,25 +263,67 @@ const ( Connect OperationType = "CONNECT" ) -// AdmissionHookClientConfig contains the information to make a TLS +// WebhookClientConfig contains the information to make a TLS // connection with the webhook -type AdmissionHookClientConfig struct { - // Service is a reference to the service for this webhook. If there is only - // one port open for the service, that port will be used. If there are multiple - // ports open, port 443 will be used if it is open, otherwise it is an error. - // Required - Service ServiceReference - // CABundle is a PEM encoded CA bundle which will be used to validate webhook's server certificate. - // Required +type WebhookClientConfig struct { + // `url` gives the location of the webhook, in standard URL form + // (`[scheme://]host:port/path`). Exactly one of `url` or `service` + // must be specified. + // + // The `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. + // + // Please 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. + // + // The scheme must be "https"; the URL must begin with "https://". + // + // A 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. + // + // Attempting to use a user or basic auth e.g. "user:password@" is not + // allowed. Fragments ("#...") and query parameters ("?...") are not + // allowed, either. + // + // +optional + URL *string + + // `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`. + // + // If there is only one port open for the service, that port will be + // used. If there are multiple ports open, port 443 will be used if it + // is open, otherwise it is an error. + // + // +optional + Service *ServiceReference + + // `caBundle` is a PEM encoded CA bundle which will be used to validate + // the webhook's server certificate. + // Required. CABundle []byte } // 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 - // Name is the name of the service + // `name` is the name of the service. // Required Name string + + // `path` is an optional URL path which will be sent in any request to + // this service. + // +optional + Path *string } diff --git a/pkg/apis/admissionregistration/v1alpha1/defaults.go b/pkg/apis/admissionregistration/v1alpha1/defaults.go index 92a37ad1ccd..8949bb87cd7 100644 --- a/pkg/apis/admissionregistration/v1alpha1/defaults.go +++ b/pkg/apis/admissionregistration/v1alpha1/defaults.go @@ -17,17 +17,9 @@ limitations under the License. package v1alpha1 import ( - admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1" "k8s.io/apimachinery/pkg/runtime" ) func addDefaultingFuncs(scheme *runtime.Scheme) error { return RegisterDefaults(scheme) } - -func SetDefaults_ExternalAdmissionHook(obj *admissionregistrationv1alpha1.ExternalAdmissionHook) { - if obj.FailurePolicy == nil { - policy := admissionregistrationv1alpha1.Ignore - obj.FailurePolicy = &policy - } -} diff --git a/pkg/apis/admissionregistration/v1alpha1/doc.go b/pkg/apis/admissionregistration/v1alpha1/doc.go index 50945b2ee45..43fd23dabf6 100644 --- a/pkg/apis/admissionregistration/v1alpha1/doc.go +++ b/pkg/apis/admissionregistration/v1alpha1/doc.go @@ -15,13 +15,13 @@ limitations under the License. */ // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/admissionregistration -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/admissionregistration/v1alpha1 +// +k8s:conversion-gen-external-types=k8s.io/api/admissionregistration/v1alpha1 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/admissionregistration/v1alpha1 // Package v1alpha1 is the v1alpha1 version of the API. // AdmissionConfiguration and AdmissionPluginConfiguration are legacy static admission plugin configuration -// admissionregistrationv1alpha1.InitializerConfiguration and admissionregistrationv1alpha1.ExternalAdmissionHookConfiguration is for the +// InitializerConfiguration, ValidatingWebhookConfiguration, and MutatingWebhookConfiguration are for the // new dynamic admission controller configuration. // +groupName=admissionregistration.k8s.io package v1alpha1 // import "k8s.io/kubernetes/pkg/apis/admissionregistration/v1alpha1" diff --git a/pkg/apis/admissionregistration/v1alpha1/zz_generated.conversion.go b/pkg/apis/admissionregistration/v1alpha1/zz_generated.conversion.go index 86ef143f0ae..49ece4122ff 100644 --- a/pkg/apis/admissionregistration/v1alpha1/zz_generated.conversion.go +++ b/pkg/apis/admissionregistration/v1alpha1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,11 +21,12 @@ limitations under the License. package v1alpha1 import ( + unsafe "unsafe" + v1alpha1 "k8s.io/api/admissionregistration/v1alpha1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" admissionregistration "k8s.io/kubernetes/pkg/apis/admissionregistration" - unsafe "unsafe" ) func init() { @@ -36,14 +37,6 @@ func init() { // Public to allow building arbitrary schemes. func RegisterConversions(scheme *runtime.Scheme) error { return scheme.AddGeneratedConversionFuncs( - Convert_v1alpha1_AdmissionHookClientConfig_To_admissionregistration_AdmissionHookClientConfig, - Convert_admissionregistration_AdmissionHookClientConfig_To_v1alpha1_AdmissionHookClientConfig, - Convert_v1alpha1_ExternalAdmissionHook_To_admissionregistration_ExternalAdmissionHook, - Convert_admissionregistration_ExternalAdmissionHook_To_v1alpha1_ExternalAdmissionHook, - Convert_v1alpha1_ExternalAdmissionHookConfiguration_To_admissionregistration_ExternalAdmissionHookConfiguration, - Convert_admissionregistration_ExternalAdmissionHookConfiguration_To_v1alpha1_ExternalAdmissionHookConfiguration, - Convert_v1alpha1_ExternalAdmissionHookConfigurationList_To_admissionregistration_ExternalAdmissionHookConfigurationList, - Convert_admissionregistration_ExternalAdmissionHookConfigurationList_To_v1alpha1_ExternalAdmissionHookConfigurationList, Convert_v1alpha1_Initializer_To_admissionregistration_Initializer, Convert_admissionregistration_Initializer_To_v1alpha1_Initializer, Convert_v1alpha1_InitializerConfiguration_To_admissionregistration_InitializerConfiguration, @@ -52,113 +45,9 @@ func RegisterConversions(scheme *runtime.Scheme) error { Convert_admissionregistration_InitializerConfigurationList_To_v1alpha1_InitializerConfigurationList, Convert_v1alpha1_Rule_To_admissionregistration_Rule, Convert_admissionregistration_Rule_To_v1alpha1_Rule, - Convert_v1alpha1_RuleWithOperations_To_admissionregistration_RuleWithOperations, - Convert_admissionregistration_RuleWithOperations_To_v1alpha1_RuleWithOperations, - Convert_v1alpha1_ServiceReference_To_admissionregistration_ServiceReference, - Convert_admissionregistration_ServiceReference_To_v1alpha1_ServiceReference, ) } -func autoConvert_v1alpha1_AdmissionHookClientConfig_To_admissionregistration_AdmissionHookClientConfig(in *v1alpha1.AdmissionHookClientConfig, out *admissionregistration.AdmissionHookClientConfig, s conversion.Scope) error { - if err := Convert_v1alpha1_ServiceReference_To_admissionregistration_ServiceReference(&in.Service, &out.Service, s); err != nil { - return err - } - out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle)) - return nil -} - -// Convert_v1alpha1_AdmissionHookClientConfig_To_admissionregistration_AdmissionHookClientConfig is an autogenerated conversion function. -func Convert_v1alpha1_AdmissionHookClientConfig_To_admissionregistration_AdmissionHookClientConfig(in *v1alpha1.AdmissionHookClientConfig, out *admissionregistration.AdmissionHookClientConfig, s conversion.Scope) error { - return autoConvert_v1alpha1_AdmissionHookClientConfig_To_admissionregistration_AdmissionHookClientConfig(in, out, s) -} - -func autoConvert_admissionregistration_AdmissionHookClientConfig_To_v1alpha1_AdmissionHookClientConfig(in *admissionregistration.AdmissionHookClientConfig, out *v1alpha1.AdmissionHookClientConfig, s conversion.Scope) error { - if err := Convert_admissionregistration_ServiceReference_To_v1alpha1_ServiceReference(&in.Service, &out.Service, s); err != nil { - return err - } - out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle)) - return nil -} - -// Convert_admissionregistration_AdmissionHookClientConfig_To_v1alpha1_AdmissionHookClientConfig is an autogenerated conversion function. -func Convert_admissionregistration_AdmissionHookClientConfig_To_v1alpha1_AdmissionHookClientConfig(in *admissionregistration.AdmissionHookClientConfig, out *v1alpha1.AdmissionHookClientConfig, s conversion.Scope) error { - return autoConvert_admissionregistration_AdmissionHookClientConfig_To_v1alpha1_AdmissionHookClientConfig(in, out, s) -} - -func autoConvert_v1alpha1_ExternalAdmissionHook_To_admissionregistration_ExternalAdmissionHook(in *v1alpha1.ExternalAdmissionHook, out *admissionregistration.ExternalAdmissionHook, s conversion.Scope) error { - out.Name = in.Name - if err := Convert_v1alpha1_AdmissionHookClientConfig_To_admissionregistration_AdmissionHookClientConfig(&in.ClientConfig, &out.ClientConfig, s); err != nil { - return err - } - out.Rules = *(*[]admissionregistration.RuleWithOperations)(unsafe.Pointer(&in.Rules)) - out.FailurePolicy = (*admissionregistration.FailurePolicyType)(unsafe.Pointer(in.FailurePolicy)) - return nil -} - -// Convert_v1alpha1_ExternalAdmissionHook_To_admissionregistration_ExternalAdmissionHook is an autogenerated conversion function. -func Convert_v1alpha1_ExternalAdmissionHook_To_admissionregistration_ExternalAdmissionHook(in *v1alpha1.ExternalAdmissionHook, out *admissionregistration.ExternalAdmissionHook, s conversion.Scope) error { - return autoConvert_v1alpha1_ExternalAdmissionHook_To_admissionregistration_ExternalAdmissionHook(in, out, s) -} - -func autoConvert_admissionregistration_ExternalAdmissionHook_To_v1alpha1_ExternalAdmissionHook(in *admissionregistration.ExternalAdmissionHook, out *v1alpha1.ExternalAdmissionHook, s conversion.Scope) error { - out.Name = in.Name - if err := Convert_admissionregistration_AdmissionHookClientConfig_To_v1alpha1_AdmissionHookClientConfig(&in.ClientConfig, &out.ClientConfig, s); err != nil { - return err - } - out.Rules = *(*[]v1alpha1.RuleWithOperations)(unsafe.Pointer(&in.Rules)) - out.FailurePolicy = (*v1alpha1.FailurePolicyType)(unsafe.Pointer(in.FailurePolicy)) - return nil -} - -// Convert_admissionregistration_ExternalAdmissionHook_To_v1alpha1_ExternalAdmissionHook is an autogenerated conversion function. -func Convert_admissionregistration_ExternalAdmissionHook_To_v1alpha1_ExternalAdmissionHook(in *admissionregistration.ExternalAdmissionHook, out *v1alpha1.ExternalAdmissionHook, s conversion.Scope) error { - return autoConvert_admissionregistration_ExternalAdmissionHook_To_v1alpha1_ExternalAdmissionHook(in, out, s) -} - -func autoConvert_v1alpha1_ExternalAdmissionHookConfiguration_To_admissionregistration_ExternalAdmissionHookConfiguration(in *v1alpha1.ExternalAdmissionHookConfiguration, out *admissionregistration.ExternalAdmissionHookConfiguration, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.ExternalAdmissionHooks = *(*[]admissionregistration.ExternalAdmissionHook)(unsafe.Pointer(&in.ExternalAdmissionHooks)) - return nil -} - -// Convert_v1alpha1_ExternalAdmissionHookConfiguration_To_admissionregistration_ExternalAdmissionHookConfiguration is an autogenerated conversion function. -func Convert_v1alpha1_ExternalAdmissionHookConfiguration_To_admissionregistration_ExternalAdmissionHookConfiguration(in *v1alpha1.ExternalAdmissionHookConfiguration, out *admissionregistration.ExternalAdmissionHookConfiguration, s conversion.Scope) error { - return autoConvert_v1alpha1_ExternalAdmissionHookConfiguration_To_admissionregistration_ExternalAdmissionHookConfiguration(in, out, s) -} - -func autoConvert_admissionregistration_ExternalAdmissionHookConfiguration_To_v1alpha1_ExternalAdmissionHookConfiguration(in *admissionregistration.ExternalAdmissionHookConfiguration, out *v1alpha1.ExternalAdmissionHookConfiguration, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.ExternalAdmissionHooks = *(*[]v1alpha1.ExternalAdmissionHook)(unsafe.Pointer(&in.ExternalAdmissionHooks)) - return nil -} - -// Convert_admissionregistration_ExternalAdmissionHookConfiguration_To_v1alpha1_ExternalAdmissionHookConfiguration is an autogenerated conversion function. -func Convert_admissionregistration_ExternalAdmissionHookConfiguration_To_v1alpha1_ExternalAdmissionHookConfiguration(in *admissionregistration.ExternalAdmissionHookConfiguration, out *v1alpha1.ExternalAdmissionHookConfiguration, s conversion.Scope) error { - return autoConvert_admissionregistration_ExternalAdmissionHookConfiguration_To_v1alpha1_ExternalAdmissionHookConfiguration(in, out, s) -} - -func autoConvert_v1alpha1_ExternalAdmissionHookConfigurationList_To_admissionregistration_ExternalAdmissionHookConfigurationList(in *v1alpha1.ExternalAdmissionHookConfigurationList, out *admissionregistration.ExternalAdmissionHookConfigurationList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]admissionregistration.ExternalAdmissionHookConfiguration)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_v1alpha1_ExternalAdmissionHookConfigurationList_To_admissionregistration_ExternalAdmissionHookConfigurationList is an autogenerated conversion function. -func Convert_v1alpha1_ExternalAdmissionHookConfigurationList_To_admissionregistration_ExternalAdmissionHookConfigurationList(in *v1alpha1.ExternalAdmissionHookConfigurationList, out *admissionregistration.ExternalAdmissionHookConfigurationList, s conversion.Scope) error { - return autoConvert_v1alpha1_ExternalAdmissionHookConfigurationList_To_admissionregistration_ExternalAdmissionHookConfigurationList(in, out, s) -} - -func autoConvert_admissionregistration_ExternalAdmissionHookConfigurationList_To_v1alpha1_ExternalAdmissionHookConfigurationList(in *admissionregistration.ExternalAdmissionHookConfigurationList, out *v1alpha1.ExternalAdmissionHookConfigurationList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]v1alpha1.ExternalAdmissionHookConfiguration)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_admissionregistration_ExternalAdmissionHookConfigurationList_To_v1alpha1_ExternalAdmissionHookConfigurationList is an autogenerated conversion function. -func Convert_admissionregistration_ExternalAdmissionHookConfigurationList_To_v1alpha1_ExternalAdmissionHookConfigurationList(in *admissionregistration.ExternalAdmissionHookConfigurationList, out *v1alpha1.ExternalAdmissionHookConfigurationList, s conversion.Scope) error { - return autoConvert_admissionregistration_ExternalAdmissionHookConfigurationList_To_v1alpha1_ExternalAdmissionHookConfigurationList(in, out, s) -} - func autoConvert_v1alpha1_Initializer_To_admissionregistration_Initializer(in *v1alpha1.Initializer, out *admissionregistration.Initializer, s conversion.Scope) error { out.Name = in.Name out.Rules = *(*[]admissionregistration.Rule)(unsafe.Pointer(&in.Rules)) @@ -248,51 +137,3 @@ func autoConvert_admissionregistration_Rule_To_v1alpha1_Rule(in *admissionregist func Convert_admissionregistration_Rule_To_v1alpha1_Rule(in *admissionregistration.Rule, out *v1alpha1.Rule, s conversion.Scope) error { return autoConvert_admissionregistration_Rule_To_v1alpha1_Rule(in, out, s) } - -func autoConvert_v1alpha1_RuleWithOperations_To_admissionregistration_RuleWithOperations(in *v1alpha1.RuleWithOperations, out *admissionregistration.RuleWithOperations, s conversion.Scope) error { - out.Operations = *(*[]admissionregistration.OperationType)(unsafe.Pointer(&in.Operations)) - if err := Convert_v1alpha1_Rule_To_admissionregistration_Rule(&in.Rule, &out.Rule, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha1_RuleWithOperations_To_admissionregistration_RuleWithOperations is an autogenerated conversion function. -func Convert_v1alpha1_RuleWithOperations_To_admissionregistration_RuleWithOperations(in *v1alpha1.RuleWithOperations, out *admissionregistration.RuleWithOperations, s conversion.Scope) error { - return autoConvert_v1alpha1_RuleWithOperations_To_admissionregistration_RuleWithOperations(in, out, s) -} - -func autoConvert_admissionregistration_RuleWithOperations_To_v1alpha1_RuleWithOperations(in *admissionregistration.RuleWithOperations, out *v1alpha1.RuleWithOperations, s conversion.Scope) error { - out.Operations = *(*[]v1alpha1.OperationType)(unsafe.Pointer(&in.Operations)) - if err := Convert_admissionregistration_Rule_To_v1alpha1_Rule(&in.Rule, &out.Rule, s); err != nil { - return err - } - return nil -} - -// Convert_admissionregistration_RuleWithOperations_To_v1alpha1_RuleWithOperations is an autogenerated conversion function. -func Convert_admissionregistration_RuleWithOperations_To_v1alpha1_RuleWithOperations(in *admissionregistration.RuleWithOperations, out *v1alpha1.RuleWithOperations, s conversion.Scope) error { - return autoConvert_admissionregistration_RuleWithOperations_To_v1alpha1_RuleWithOperations(in, out, s) -} - -func autoConvert_v1alpha1_ServiceReference_To_admissionregistration_ServiceReference(in *v1alpha1.ServiceReference, out *admissionregistration.ServiceReference, s conversion.Scope) error { - out.Namespace = in.Namespace - out.Name = in.Name - return nil -} - -// Convert_v1alpha1_ServiceReference_To_admissionregistration_ServiceReference is an autogenerated conversion function. -func Convert_v1alpha1_ServiceReference_To_admissionregistration_ServiceReference(in *v1alpha1.ServiceReference, out *admissionregistration.ServiceReference, s conversion.Scope) error { - return autoConvert_v1alpha1_ServiceReference_To_admissionregistration_ServiceReference(in, out, s) -} - -func autoConvert_admissionregistration_ServiceReference_To_v1alpha1_ServiceReference(in *admissionregistration.ServiceReference, out *v1alpha1.ServiceReference, s conversion.Scope) error { - out.Namespace = in.Namespace - out.Name = in.Name - return nil -} - -// Convert_admissionregistration_ServiceReference_To_v1alpha1_ServiceReference is an autogenerated conversion function. -func Convert_admissionregistration_ServiceReference_To_v1alpha1_ServiceReference(in *admissionregistration.ServiceReference, out *v1alpha1.ServiceReference, s conversion.Scope) error { - return autoConvert_admissionregistration_ServiceReference_To_v1alpha1_ServiceReference(in, out, s) -} diff --git a/pkg/apis/admissionregistration/v1alpha1/zz_generated.defaults.go b/pkg/apis/admissionregistration/v1alpha1/zz_generated.defaults.go index da214c5d95f..5e24d22cacd 100644 --- a/pkg/apis/admissionregistration/v1alpha1/zz_generated.defaults.go +++ b/pkg/apis/admissionregistration/v1alpha1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,7 +21,6 @@ limitations under the License. package v1alpha1 import ( - v1alpha1 "k8s.io/api/admissionregistration/v1alpha1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -29,25 +28,5 @@ 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(&v1alpha1.ExternalAdmissionHookConfiguration{}, func(obj interface{}) { - SetObjectDefaults_ExternalAdmissionHookConfiguration(obj.(*v1alpha1.ExternalAdmissionHookConfiguration)) - }) - scheme.AddTypeDefaultingFunc(&v1alpha1.ExternalAdmissionHookConfigurationList{}, func(obj interface{}) { - SetObjectDefaults_ExternalAdmissionHookConfigurationList(obj.(*v1alpha1.ExternalAdmissionHookConfigurationList)) - }) return nil } - -func SetObjectDefaults_ExternalAdmissionHookConfiguration(in *v1alpha1.ExternalAdmissionHookConfiguration) { - for i := range in.ExternalAdmissionHooks { - a := &in.ExternalAdmissionHooks[i] - SetDefaults_ExternalAdmissionHook(a) - } -} - -func SetObjectDefaults_ExternalAdmissionHookConfigurationList(in *v1alpha1.ExternalAdmissionHookConfigurationList) { - for i := range in.Items { - a := &in.Items[i] - SetObjectDefaults_ExternalAdmissionHookConfiguration(a) - } -} diff --git a/pkg/apis/admissionregistration/v1beta1/BUILD b/pkg/apis/admissionregistration/v1beta1/BUILD new file mode 100644 index 00000000000..414f877488f --- /dev/null +++ b/pkg/apis/admissionregistration/v1beta1/BUILD @@ -0,0 +1,39 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = [ + "defaults.go", + "doc.go", + "register.go", + "zz_generated.conversion.go", + "zz_generated.defaults.go", + ], + importpath = "k8s.io/kubernetes/pkg/apis/admissionregistration/v1beta1", + deps = [ + "//pkg/apis/admissionregistration:go_default_library", + "//vendor/k8s.io/api/admissionregistration/v1beta1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/pkg/apis/admissionregistration/v1beta1/defaults.go b/pkg/apis/admissionregistration/v1beta1/defaults.go new file mode 100644 index 00000000000..907f7d9f31d --- /dev/null +++ b/pkg/apis/admissionregistration/v1beta1/defaults.go @@ -0,0 +1,38 @@ +/* +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 v1beta1 + +import ( + admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func addDefaultingFuncs(scheme *runtime.Scheme) error { + return RegisterDefaults(scheme) +} + +func SetDefaults_Webhook(obj *admissionregistrationv1beta1.Webhook) { + if obj.FailurePolicy == nil { + policy := admissionregistrationv1beta1.Ignore + obj.FailurePolicy = &policy + } + if obj.NamespaceSelector == nil { + selector := metav1.LabelSelector{} + obj.NamespaceSelector = &selector + } +} diff --git a/pkg/apis/admissionregistration/v1beta1/doc.go b/pkg/apis/admissionregistration/v1beta1/doc.go new file mode 100644 index 00000000000..cf03718ed26 --- /dev/null +++ b/pkg/apis/admissionregistration/v1beta1/doc.go @@ -0,0 +1,27 @@ +/* +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/admissionregistration +// +k8s:conversion-gen-external-types=k8s.io/api/admissionregistration/v1beta1 +// +k8s:defaulter-gen=TypeMeta +// +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/admissionregistration/v1beta1 + +// Package v1beta1 is the v1beta1 version of the API. +// AdmissionConfiguration and AdmissionPluginConfiguration are legacy static admission plugin configuration +// InitializerConfiguration, ValidatingWebhookConfiguration, and MutatingWebhookConfiguration are for the +// new dynamic admission controller configuration. +// +groupName=admissionregistration.k8s.io +package v1beta1 // import "k8s.io/kubernetes/pkg/apis/admissionregistration/v1beta1" diff --git a/pkg/apis/admissionregistration/v1beta1/register.go b/pkg/apis/admissionregistration/v1beta1/register.go new file mode 100644 index 00000000000..ff505a577e1 --- /dev/null +++ b/pkg/apis/admissionregistration/v1beta1/register.go @@ -0,0 +1,44 @@ +/* +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 v1beta1 + +import ( + admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +const GroupName = "admissionregistration.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + localSchemeBuilder = &admissionregistrationv1beta1.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/admissionregistration/v1beta1/zz_generated.conversion.go b/pkg/apis/admissionregistration/v1beta1/zz_generated.conversion.go new file mode 100644 index 00000000000..d130633cacb --- /dev/null +++ b/pkg/apis/admissionregistration/v1beta1/zz_generated.conversion.go @@ -0,0 +1,278 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by conversion-gen. Do not edit it manually! + +package v1beta1 + +import ( + unsafe "unsafe" + + v1beta1 "k8s.io/api/admissionregistration/v1beta1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + conversion "k8s.io/apimachinery/pkg/conversion" + runtime "k8s.io/apimachinery/pkg/runtime" + admissionregistration "k8s.io/kubernetes/pkg/apis/admissionregistration" +) + +func init() { + localSchemeBuilder.Register(RegisterConversions) +} + +// RegisterConversions adds conversion functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterConversions(scheme *runtime.Scheme) error { + return scheme.AddGeneratedConversionFuncs( + Convert_v1beta1_MutatingWebhookConfiguration_To_admissionregistration_MutatingWebhookConfiguration, + Convert_admissionregistration_MutatingWebhookConfiguration_To_v1beta1_MutatingWebhookConfiguration, + Convert_v1beta1_MutatingWebhookConfigurationList_To_admissionregistration_MutatingWebhookConfigurationList, + Convert_admissionregistration_MutatingWebhookConfigurationList_To_v1beta1_MutatingWebhookConfigurationList, + Convert_v1beta1_Rule_To_admissionregistration_Rule, + Convert_admissionregistration_Rule_To_v1beta1_Rule, + Convert_v1beta1_RuleWithOperations_To_admissionregistration_RuleWithOperations, + Convert_admissionregistration_RuleWithOperations_To_v1beta1_RuleWithOperations, + Convert_v1beta1_ServiceReference_To_admissionregistration_ServiceReference, + Convert_admissionregistration_ServiceReference_To_v1beta1_ServiceReference, + Convert_v1beta1_ValidatingWebhookConfiguration_To_admissionregistration_ValidatingWebhookConfiguration, + Convert_admissionregistration_ValidatingWebhookConfiguration_To_v1beta1_ValidatingWebhookConfiguration, + Convert_v1beta1_ValidatingWebhookConfigurationList_To_admissionregistration_ValidatingWebhookConfigurationList, + Convert_admissionregistration_ValidatingWebhookConfigurationList_To_v1beta1_ValidatingWebhookConfigurationList, + Convert_v1beta1_Webhook_To_admissionregistration_Webhook, + Convert_admissionregistration_Webhook_To_v1beta1_Webhook, + Convert_v1beta1_WebhookClientConfig_To_admissionregistration_WebhookClientConfig, + Convert_admissionregistration_WebhookClientConfig_To_v1beta1_WebhookClientConfig, + ) +} + +func autoConvert_v1beta1_MutatingWebhookConfiguration_To_admissionregistration_MutatingWebhookConfiguration(in *v1beta1.MutatingWebhookConfiguration, out *admissionregistration.MutatingWebhookConfiguration, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Webhooks = *(*[]admissionregistration.Webhook)(unsafe.Pointer(&in.Webhooks)) + return nil +} + +// Convert_v1beta1_MutatingWebhookConfiguration_To_admissionregistration_MutatingWebhookConfiguration is an autogenerated conversion function. +func Convert_v1beta1_MutatingWebhookConfiguration_To_admissionregistration_MutatingWebhookConfiguration(in *v1beta1.MutatingWebhookConfiguration, out *admissionregistration.MutatingWebhookConfiguration, s conversion.Scope) error { + return autoConvert_v1beta1_MutatingWebhookConfiguration_To_admissionregistration_MutatingWebhookConfiguration(in, out, s) +} + +func autoConvert_admissionregistration_MutatingWebhookConfiguration_To_v1beta1_MutatingWebhookConfiguration(in *admissionregistration.MutatingWebhookConfiguration, out *v1beta1.MutatingWebhookConfiguration, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Webhooks = *(*[]v1beta1.Webhook)(unsafe.Pointer(&in.Webhooks)) + return nil +} + +// Convert_admissionregistration_MutatingWebhookConfiguration_To_v1beta1_MutatingWebhookConfiguration is an autogenerated conversion function. +func Convert_admissionregistration_MutatingWebhookConfiguration_To_v1beta1_MutatingWebhookConfiguration(in *admissionregistration.MutatingWebhookConfiguration, out *v1beta1.MutatingWebhookConfiguration, s conversion.Scope) error { + return autoConvert_admissionregistration_MutatingWebhookConfiguration_To_v1beta1_MutatingWebhookConfiguration(in, out, s) +} + +func autoConvert_v1beta1_MutatingWebhookConfigurationList_To_admissionregistration_MutatingWebhookConfigurationList(in *v1beta1.MutatingWebhookConfigurationList, out *admissionregistration.MutatingWebhookConfigurationList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]admissionregistration.MutatingWebhookConfiguration)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1beta1_MutatingWebhookConfigurationList_To_admissionregistration_MutatingWebhookConfigurationList is an autogenerated conversion function. +func Convert_v1beta1_MutatingWebhookConfigurationList_To_admissionregistration_MutatingWebhookConfigurationList(in *v1beta1.MutatingWebhookConfigurationList, out *admissionregistration.MutatingWebhookConfigurationList, s conversion.Scope) error { + return autoConvert_v1beta1_MutatingWebhookConfigurationList_To_admissionregistration_MutatingWebhookConfigurationList(in, out, s) +} + +func autoConvert_admissionregistration_MutatingWebhookConfigurationList_To_v1beta1_MutatingWebhookConfigurationList(in *admissionregistration.MutatingWebhookConfigurationList, out *v1beta1.MutatingWebhookConfigurationList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]v1beta1.MutatingWebhookConfiguration)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_admissionregistration_MutatingWebhookConfigurationList_To_v1beta1_MutatingWebhookConfigurationList is an autogenerated conversion function. +func Convert_admissionregistration_MutatingWebhookConfigurationList_To_v1beta1_MutatingWebhookConfigurationList(in *admissionregistration.MutatingWebhookConfigurationList, out *v1beta1.MutatingWebhookConfigurationList, s conversion.Scope) error { + return autoConvert_admissionregistration_MutatingWebhookConfigurationList_To_v1beta1_MutatingWebhookConfigurationList(in, out, s) +} + +func autoConvert_v1beta1_Rule_To_admissionregistration_Rule(in *v1beta1.Rule, out *admissionregistration.Rule, s conversion.Scope) error { + out.APIGroups = *(*[]string)(unsafe.Pointer(&in.APIGroups)) + out.APIVersions = *(*[]string)(unsafe.Pointer(&in.APIVersions)) + out.Resources = *(*[]string)(unsafe.Pointer(&in.Resources)) + return nil +} + +// Convert_v1beta1_Rule_To_admissionregistration_Rule is an autogenerated conversion function. +func Convert_v1beta1_Rule_To_admissionregistration_Rule(in *v1beta1.Rule, out *admissionregistration.Rule, s conversion.Scope) error { + return autoConvert_v1beta1_Rule_To_admissionregistration_Rule(in, out, s) +} + +func autoConvert_admissionregistration_Rule_To_v1beta1_Rule(in *admissionregistration.Rule, out *v1beta1.Rule, s conversion.Scope) error { + out.APIGroups = *(*[]string)(unsafe.Pointer(&in.APIGroups)) + out.APIVersions = *(*[]string)(unsafe.Pointer(&in.APIVersions)) + out.Resources = *(*[]string)(unsafe.Pointer(&in.Resources)) + return nil +} + +// Convert_admissionregistration_Rule_To_v1beta1_Rule is an autogenerated conversion function. +func Convert_admissionregistration_Rule_To_v1beta1_Rule(in *admissionregistration.Rule, out *v1beta1.Rule, s conversion.Scope) error { + return autoConvert_admissionregistration_Rule_To_v1beta1_Rule(in, out, s) +} + +func autoConvert_v1beta1_RuleWithOperations_To_admissionregistration_RuleWithOperations(in *v1beta1.RuleWithOperations, out *admissionregistration.RuleWithOperations, s conversion.Scope) error { + out.Operations = *(*[]admissionregistration.OperationType)(unsafe.Pointer(&in.Operations)) + if err := Convert_v1beta1_Rule_To_admissionregistration_Rule(&in.Rule, &out.Rule, s); err != nil { + return err + } + return nil +} + +// Convert_v1beta1_RuleWithOperations_To_admissionregistration_RuleWithOperations is an autogenerated conversion function. +func Convert_v1beta1_RuleWithOperations_To_admissionregistration_RuleWithOperations(in *v1beta1.RuleWithOperations, out *admissionregistration.RuleWithOperations, s conversion.Scope) error { + return autoConvert_v1beta1_RuleWithOperations_To_admissionregistration_RuleWithOperations(in, out, s) +} + +func autoConvert_admissionregistration_RuleWithOperations_To_v1beta1_RuleWithOperations(in *admissionregistration.RuleWithOperations, out *v1beta1.RuleWithOperations, s conversion.Scope) error { + out.Operations = *(*[]v1beta1.OperationType)(unsafe.Pointer(&in.Operations)) + if err := Convert_admissionregistration_Rule_To_v1beta1_Rule(&in.Rule, &out.Rule, s); err != nil { + return err + } + return nil +} + +// Convert_admissionregistration_RuleWithOperations_To_v1beta1_RuleWithOperations is an autogenerated conversion function. +func Convert_admissionregistration_RuleWithOperations_To_v1beta1_RuleWithOperations(in *admissionregistration.RuleWithOperations, out *v1beta1.RuleWithOperations, s conversion.Scope) error { + return autoConvert_admissionregistration_RuleWithOperations_To_v1beta1_RuleWithOperations(in, out, s) +} + +func autoConvert_v1beta1_ServiceReference_To_admissionregistration_ServiceReference(in *v1beta1.ServiceReference, out *admissionregistration.ServiceReference, s conversion.Scope) error { + out.Namespace = in.Namespace + out.Name = in.Name + out.Path = (*string)(unsafe.Pointer(in.Path)) + return nil +} + +// Convert_v1beta1_ServiceReference_To_admissionregistration_ServiceReference is an autogenerated conversion function. +func Convert_v1beta1_ServiceReference_To_admissionregistration_ServiceReference(in *v1beta1.ServiceReference, out *admissionregistration.ServiceReference, s conversion.Scope) error { + return autoConvert_v1beta1_ServiceReference_To_admissionregistration_ServiceReference(in, out, s) +} + +func autoConvert_admissionregistration_ServiceReference_To_v1beta1_ServiceReference(in *admissionregistration.ServiceReference, out *v1beta1.ServiceReference, s conversion.Scope) error { + out.Namespace = in.Namespace + out.Name = in.Name + out.Path = (*string)(unsafe.Pointer(in.Path)) + return nil +} + +// Convert_admissionregistration_ServiceReference_To_v1beta1_ServiceReference is an autogenerated conversion function. +func Convert_admissionregistration_ServiceReference_To_v1beta1_ServiceReference(in *admissionregistration.ServiceReference, out *v1beta1.ServiceReference, s conversion.Scope) error { + return autoConvert_admissionregistration_ServiceReference_To_v1beta1_ServiceReference(in, out, s) +} + +func autoConvert_v1beta1_ValidatingWebhookConfiguration_To_admissionregistration_ValidatingWebhookConfiguration(in *v1beta1.ValidatingWebhookConfiguration, out *admissionregistration.ValidatingWebhookConfiguration, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Webhooks = *(*[]admissionregistration.Webhook)(unsafe.Pointer(&in.Webhooks)) + return nil +} + +// Convert_v1beta1_ValidatingWebhookConfiguration_To_admissionregistration_ValidatingWebhookConfiguration is an autogenerated conversion function. +func Convert_v1beta1_ValidatingWebhookConfiguration_To_admissionregistration_ValidatingWebhookConfiguration(in *v1beta1.ValidatingWebhookConfiguration, out *admissionregistration.ValidatingWebhookConfiguration, s conversion.Scope) error { + return autoConvert_v1beta1_ValidatingWebhookConfiguration_To_admissionregistration_ValidatingWebhookConfiguration(in, out, s) +} + +func autoConvert_admissionregistration_ValidatingWebhookConfiguration_To_v1beta1_ValidatingWebhookConfiguration(in *admissionregistration.ValidatingWebhookConfiguration, out *v1beta1.ValidatingWebhookConfiguration, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Webhooks = *(*[]v1beta1.Webhook)(unsafe.Pointer(&in.Webhooks)) + return nil +} + +// Convert_admissionregistration_ValidatingWebhookConfiguration_To_v1beta1_ValidatingWebhookConfiguration is an autogenerated conversion function. +func Convert_admissionregistration_ValidatingWebhookConfiguration_To_v1beta1_ValidatingWebhookConfiguration(in *admissionregistration.ValidatingWebhookConfiguration, out *v1beta1.ValidatingWebhookConfiguration, s conversion.Scope) error { + return autoConvert_admissionregistration_ValidatingWebhookConfiguration_To_v1beta1_ValidatingWebhookConfiguration(in, out, s) +} + +func autoConvert_v1beta1_ValidatingWebhookConfigurationList_To_admissionregistration_ValidatingWebhookConfigurationList(in *v1beta1.ValidatingWebhookConfigurationList, out *admissionregistration.ValidatingWebhookConfigurationList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]admissionregistration.ValidatingWebhookConfiguration)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1beta1_ValidatingWebhookConfigurationList_To_admissionregistration_ValidatingWebhookConfigurationList is an autogenerated conversion function. +func Convert_v1beta1_ValidatingWebhookConfigurationList_To_admissionregistration_ValidatingWebhookConfigurationList(in *v1beta1.ValidatingWebhookConfigurationList, out *admissionregistration.ValidatingWebhookConfigurationList, s conversion.Scope) error { + return autoConvert_v1beta1_ValidatingWebhookConfigurationList_To_admissionregistration_ValidatingWebhookConfigurationList(in, out, s) +} + +func autoConvert_admissionregistration_ValidatingWebhookConfigurationList_To_v1beta1_ValidatingWebhookConfigurationList(in *admissionregistration.ValidatingWebhookConfigurationList, out *v1beta1.ValidatingWebhookConfigurationList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]v1beta1.ValidatingWebhookConfiguration)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_admissionregistration_ValidatingWebhookConfigurationList_To_v1beta1_ValidatingWebhookConfigurationList is an autogenerated conversion function. +func Convert_admissionregistration_ValidatingWebhookConfigurationList_To_v1beta1_ValidatingWebhookConfigurationList(in *admissionregistration.ValidatingWebhookConfigurationList, out *v1beta1.ValidatingWebhookConfigurationList, s conversion.Scope) error { + return autoConvert_admissionregistration_ValidatingWebhookConfigurationList_To_v1beta1_ValidatingWebhookConfigurationList(in, out, s) +} + +func autoConvert_v1beta1_Webhook_To_admissionregistration_Webhook(in *v1beta1.Webhook, out *admissionregistration.Webhook, s conversion.Scope) error { + out.Name = in.Name + if err := Convert_v1beta1_WebhookClientConfig_To_admissionregistration_WebhookClientConfig(&in.ClientConfig, &out.ClientConfig, s); err != nil { + return err + } + out.Rules = *(*[]admissionregistration.RuleWithOperations)(unsafe.Pointer(&in.Rules)) + out.FailurePolicy = (*admissionregistration.FailurePolicyType)(unsafe.Pointer(in.FailurePolicy)) + out.NamespaceSelector = (*v1.LabelSelector)(unsafe.Pointer(in.NamespaceSelector)) + return nil +} + +// Convert_v1beta1_Webhook_To_admissionregistration_Webhook is an autogenerated conversion function. +func Convert_v1beta1_Webhook_To_admissionregistration_Webhook(in *v1beta1.Webhook, out *admissionregistration.Webhook, s conversion.Scope) error { + return autoConvert_v1beta1_Webhook_To_admissionregistration_Webhook(in, out, s) +} + +func autoConvert_admissionregistration_Webhook_To_v1beta1_Webhook(in *admissionregistration.Webhook, out *v1beta1.Webhook, s conversion.Scope) error { + out.Name = in.Name + if err := Convert_admissionregistration_WebhookClientConfig_To_v1beta1_WebhookClientConfig(&in.ClientConfig, &out.ClientConfig, s); err != nil { + return err + } + out.Rules = *(*[]v1beta1.RuleWithOperations)(unsafe.Pointer(&in.Rules)) + out.FailurePolicy = (*v1beta1.FailurePolicyType)(unsafe.Pointer(in.FailurePolicy)) + out.NamespaceSelector = (*v1.LabelSelector)(unsafe.Pointer(in.NamespaceSelector)) + return nil +} + +// Convert_admissionregistration_Webhook_To_v1beta1_Webhook is an autogenerated conversion function. +func Convert_admissionregistration_Webhook_To_v1beta1_Webhook(in *admissionregistration.Webhook, out *v1beta1.Webhook, s conversion.Scope) error { + return autoConvert_admissionregistration_Webhook_To_v1beta1_Webhook(in, out, s) +} + +func autoConvert_v1beta1_WebhookClientConfig_To_admissionregistration_WebhookClientConfig(in *v1beta1.WebhookClientConfig, out *admissionregistration.WebhookClientConfig, s conversion.Scope) error { + out.URL = (*string)(unsafe.Pointer(in.URL)) + out.Service = (*admissionregistration.ServiceReference)(unsafe.Pointer(in.Service)) + out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle)) + return nil +} + +// Convert_v1beta1_WebhookClientConfig_To_admissionregistration_WebhookClientConfig is an autogenerated conversion function. +func Convert_v1beta1_WebhookClientConfig_To_admissionregistration_WebhookClientConfig(in *v1beta1.WebhookClientConfig, out *admissionregistration.WebhookClientConfig, s conversion.Scope) error { + return autoConvert_v1beta1_WebhookClientConfig_To_admissionregistration_WebhookClientConfig(in, out, s) +} + +func autoConvert_admissionregistration_WebhookClientConfig_To_v1beta1_WebhookClientConfig(in *admissionregistration.WebhookClientConfig, out *v1beta1.WebhookClientConfig, s conversion.Scope) error { + out.URL = (*string)(unsafe.Pointer(in.URL)) + out.Service = (*v1beta1.ServiceReference)(unsafe.Pointer(in.Service)) + out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle)) + return nil +} + +// Convert_admissionregistration_WebhookClientConfig_To_v1beta1_WebhookClientConfig is an autogenerated conversion function. +func Convert_admissionregistration_WebhookClientConfig_To_v1beta1_WebhookClientConfig(in *admissionregistration.WebhookClientConfig, out *v1beta1.WebhookClientConfig, s conversion.Scope) error { + return autoConvert_admissionregistration_WebhookClientConfig_To_v1beta1_WebhookClientConfig(in, out, s) +} diff --git a/pkg/apis/admissionregistration/v1beta1/zz_generated.defaults.go b/pkg/apis/admissionregistration/v1beta1/zz_generated.defaults.go new file mode 100644 index 00000000000..16620a79f1a --- /dev/null +++ b/pkg/apis/admissionregistration/v1beta1/zz_generated.defaults.go @@ -0,0 +1,73 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by defaulter-gen. Do not edit it manually! + +package v1beta1 + +import ( + v1beta1 "k8s.io/api/admissionregistration/v1beta1" + 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(&v1beta1.MutatingWebhookConfiguration{}, func(obj interface{}) { + SetObjectDefaults_MutatingWebhookConfiguration(obj.(*v1beta1.MutatingWebhookConfiguration)) + }) + scheme.AddTypeDefaultingFunc(&v1beta1.MutatingWebhookConfigurationList{}, func(obj interface{}) { + SetObjectDefaults_MutatingWebhookConfigurationList(obj.(*v1beta1.MutatingWebhookConfigurationList)) + }) + scheme.AddTypeDefaultingFunc(&v1beta1.ValidatingWebhookConfiguration{}, func(obj interface{}) { + SetObjectDefaults_ValidatingWebhookConfiguration(obj.(*v1beta1.ValidatingWebhookConfiguration)) + }) + scheme.AddTypeDefaultingFunc(&v1beta1.ValidatingWebhookConfigurationList{}, func(obj interface{}) { + SetObjectDefaults_ValidatingWebhookConfigurationList(obj.(*v1beta1.ValidatingWebhookConfigurationList)) + }) + return nil +} + +func SetObjectDefaults_MutatingWebhookConfiguration(in *v1beta1.MutatingWebhookConfiguration) { + for i := range in.Webhooks { + a := &in.Webhooks[i] + SetDefaults_Webhook(a) + } +} + +func SetObjectDefaults_MutatingWebhookConfigurationList(in *v1beta1.MutatingWebhookConfigurationList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_MutatingWebhookConfiguration(a) + } +} + +func SetObjectDefaults_ValidatingWebhookConfiguration(in *v1beta1.ValidatingWebhookConfiguration) { + for i := range in.Webhooks { + a := &in.Webhooks[i] + SetDefaults_Webhook(a) + } +} + +func SetObjectDefaults_ValidatingWebhookConfigurationList(in *v1beta1.ValidatingWebhookConfigurationList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_ValidatingWebhookConfiguration(a) + } +} diff --git a/pkg/apis/admissionregistration/validation/BUILD b/pkg/apis/admissionregistration/validation/BUILD index ea318376b12..d43f5b59054 100644 --- a/pkg/apis/admissionregistration/validation/BUILD +++ b/pkg/apis/admissionregistration/validation/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["validation_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/apis/admissionregistration/validation", - library = ":go_default_library", deps = [ "//pkg/apis/admissionregistration:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -24,6 +24,7 @@ go_library( deps = [ "//pkg/apis/admissionregistration:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/validation:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", diff --git a/pkg/apis/admissionregistration/validation/validation.go b/pkg/apis/admissionregistration/validation/validation.go index 07c0bb1b198..958ebf44022 100644 --- a/pkg/apis/admissionregistration/validation/validation.go +++ b/pkg/apis/admissionregistration/validation/validation.go @@ -18,9 +18,11 @@ package validation import ( "fmt" + "net/url" "strings" genericvalidation "k8s.io/apimachinery/pkg/api/validation" + metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" @@ -163,15 +165,23 @@ func ValidateInitializerConfigurationUpdate(newIC, oldIC *admissionregistration. return ValidateInitializerConfiguration(newIC) } -func ValidateExternalAdmissionHookConfiguration(e *admissionregistration.ExternalAdmissionHookConfiguration) field.ErrorList { +func ValidateValidatingWebhookConfiguration(e *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList { allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata")) - for i, hook := range e.ExternalAdmissionHooks { - allErrors = append(allErrors, validateExternalAdmissionHook(&hook, field.NewPath("externalAdmissionHooks").Index(i))...) + for i, hook := range e.Webhooks { + allErrors = append(allErrors, validateWebhook(&hook, field.NewPath("webhooks").Index(i))...) } return allErrors } -func validateExternalAdmissionHook(hook *admissionregistration.ExternalAdmissionHook, fldPath *field.Path) field.ErrorList { +func ValidateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebhookConfiguration) field.ErrorList { + allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata")) + for i, hook := range e.Webhooks { + allErrors = append(allErrors, validateWebhook(&hook, field.NewPath("webhooks").Index(i))...) + } + return allErrors +} + +func validateWebhook(hook *admissionregistration.Webhook, fldPath *field.Path) field.ErrorList { var allErrors field.ErrorList // hook.Name must be fully qualified allErrors = append(allErrors, validation.IsFullyQualifiedName(fldPath.Child("name"), hook.Name)...) @@ -179,13 +189,108 @@ func validateExternalAdmissionHook(hook *admissionregistration.ExternalAdmission for i, rule := range hook.Rules { allErrors = append(allErrors, validateRuleWithOperations(&rule, fldPath.Child("rules").Index(i))...) } - // TODO: relax the validation rule when admissionregistration is beta. - if hook.FailurePolicy != nil && *hook.FailurePolicy != admissionregistration.Ignore { - allErrors = append(allErrors, field.NotSupported(fldPath.Child("failurePolicy"), *hook.FailurePolicy, []string{string(admissionregistration.Ignore)})) + if hook.FailurePolicy != nil && !supportedFailurePolicies.Has(string(*hook.FailurePolicy)) { + allErrors = append(allErrors, field.NotSupported(fldPath.Child("failurePolicy"), *hook.FailurePolicy, supportedFailurePolicies.List())) + } + + if hook.NamespaceSelector != nil { + allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.NamespaceSelector, fldPath.Child("namespaceSelector"))...) + } + + allErrors = append(allErrors, validateWebhookClientConfig(fldPath.Child("clientConfig"), &hook.ClientConfig)...) + + return allErrors +} + +func validateWebhookClientConfig(fldPath *field.Path, cc *admissionregistration.WebhookClientConfig) field.ErrorList { + var allErrors field.ErrorList + if (cc.URL == nil) == (cc.Service == nil) { + allErrors = append(allErrors, field.Required(fldPath.Child("url"), "exactly one of url or service is required")) + } + + if cc.URL != nil { + const form = "; desired format: https://host[/path]" + if u, err := url.Parse(*cc.URL); err != nil { + allErrors = append(allErrors, field.Required(fldPath.Child("url"), "url must be a valid URL: "+err.Error()+form)) + } else { + if u.Scheme != "https" { + allErrors = append(allErrors, field.Invalid(fldPath.Child("url"), u.Scheme, "'https' is the only allowed URL scheme"+form)) + } + if len(u.Host) == 0 { + allErrors = append(allErrors, field.Invalid(fldPath.Child("url"), u.Host, "host must be provided"+form)) + } + if u.User != nil { + allErrors = append(allErrors, field.Invalid(fldPath.Child("url"), u.User.String(), "user information is not permitted in the URL")) + } + if len(u.Fragment) != 0 { + allErrors = append(allErrors, field.Invalid(fldPath.Child("url"), u.Fragment, "fragments are not permitted in the URL")) + } + if len(u.RawQuery) != 0 { + allErrors = append(allErrors, field.Invalid(fldPath.Child("url"), u.RawQuery, "query parameters are not permitted in the URL")) + } + } + } + + if cc.Service != nil { + allErrors = append(allErrors, validateWebhookService(fldPath.Child("service"), cc.Service)...) } return allErrors } +func validateWebhookService(fldPath *field.Path, svc *admissionregistration.ServiceReference) field.ErrorList { + var allErrors field.ErrorList + + if len(svc.Name) == 0 { + allErrors = append(allErrors, field.Required(fldPath.Child("name"), "service name is required")) + } + + if len(svc.Namespace) == 0 { + allErrors = append(allErrors, field.Required(fldPath.Child("namespace"), "service namespace is required")) + } + + if svc.Path == nil { + return allErrors + } + + // TODO: replace below with url.Parse + verifying that host is empty? + + urlPath := *svc.Path + if urlPath == "/" || len(urlPath) == 0 { + return allErrors + } + if urlPath == "//" { + allErrors = append(allErrors, field.Invalid(fldPath.Child("path"), urlPath, "segment[0] may not be empty")) + return allErrors + } + + if !strings.HasPrefix(urlPath, "/") { + allErrors = append(allErrors, field.Invalid(fldPath.Child("path"), urlPath, "must start with a '/'")) + } + + urlPathToCheck := urlPath[1:] + if strings.HasSuffix(urlPathToCheck, "/") { + urlPathToCheck = urlPathToCheck[:len(urlPathToCheck)-1] + } + steps := strings.Split(urlPathToCheck, "/") + for i, step := range steps { + if len(step) == 0 { + allErrors = append(allErrors, field.Invalid(fldPath.Child("path"), urlPath, fmt.Sprintf("segment[%d] may not be empty", i))) + continue + } + failures := validation.IsDNS1123Subdomain(step) + for _, failure := range failures { + allErrors = append(allErrors, field.Invalid(fldPath.Child("path"), urlPath, fmt.Sprintf("segment[%d]: %v", i, failure))) + } + } + + return allErrors +} + +var supportedFailurePolicies = sets.NewString( + string(admissionregistration.Ignore), + string(admissionregistration.Fail), +) + var supportedOperations = sets.NewString( string(admissionregistration.OperationAll), string(admissionregistration.Create), @@ -221,6 +326,10 @@ func validateRuleWithOperations(ruleWithOperations *admissionregistration.RuleWi return allErrors } -func ValidateExternalAdmissionHookConfigurationUpdate(newC, oldC *admissionregistration.ExternalAdmissionHookConfiguration) field.ErrorList { - return ValidateExternalAdmissionHookConfiguration(newC) +func ValidateValidatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList { + return ValidateValidatingWebhookConfiguration(newC) +} + +func ValidateMutatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.MutatingWebhookConfiguration) field.ErrorList { + return ValidateMutatingWebhookConfiguration(newC) } diff --git a/pkg/apis/admissionregistration/validation/validation_test.go b/pkg/apis/admissionregistration/validation/validation_test.go index de36f18065c..5dd3fc551b7 100644 --- a/pkg/apis/admissionregistration/validation/validation_test.go +++ b/pkg/apis/admissionregistration/validation/validation_test.go @@ -231,41 +231,51 @@ func TestValidateInitializerConfiguration(t *testing.T) { } } -func getExternalAdmissionHookConfiguration(hooks []admissionregistration.ExternalAdmissionHook) *admissionregistration.ExternalAdmissionHookConfiguration { - return &admissionregistration.ExternalAdmissionHookConfiguration{ +func strPtr(s string) *string { return &s } + +func newValidatingWebhookConfiguration(hooks []admissionregistration.Webhook) *admissionregistration.ValidatingWebhookConfiguration { + return &admissionregistration.ValidatingWebhookConfiguration{ ObjectMeta: metav1.ObjectMeta{ Name: "config", }, - ExternalAdmissionHooks: hooks, + Webhooks: hooks, } } -func TestValidateExternalAdmissionHookConfiguration(t *testing.T) { +// TODO: Add TestValidateMutatingWebhookConfiguration to test validation for mutating webhooks. + +func TestValidateValidatingWebhookConfiguration(t *testing.T) { + validClientConfig := admissionregistration.WebhookClientConfig{ + URL: strPtr("https://example.com"), + } tests := []struct { name string - config *admissionregistration.ExternalAdmissionHookConfiguration + config *admissionregistration.ValidatingWebhookConfiguration expectedError string }{ { - name: "all ExternalAdmissionHook must have a fully qualified name", - config: getExternalAdmissionHookConfiguration( - []admissionregistration.ExternalAdmissionHook{ + name: "all Webhooks must have a fully qualified name", + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ { - Name: "webhook.k8s.io", + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, }, { - Name: "k8s.io", + Name: "k8s.io", + ClientConfig: validClientConfig, }, { - Name: "", + Name: "", + ClientConfig: validClientConfig, }, }), - expectedError: `externalAdmissionHooks[1].name: Invalid value: "k8s.io": should be a domain with at least three segments separated by dots, externalAdmissionHooks[2].name: Required value`, + expectedError: `webhooks[1].name: Invalid value: "k8s.io": should be a domain with at least three segments separated by dots, webhooks[2].name: Required value`, }, { name: "Operations must not be empty or nil", - config: getExternalAdmissionHookConfiguration( - []admissionregistration.ExternalAdmissionHook{ + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ { Name: "webhook.k8s.io", Rules: []admissionregistration.RuleWithOperations{ @@ -288,12 +298,12 @@ func TestValidateExternalAdmissionHookConfiguration(t *testing.T) { }, }, }), - expectedError: `externalAdmissionHooks[0].rules[0].operations: Required value, externalAdmissionHooks[0].rules[1].operations: Required value`, + expectedError: `webhooks[0].rules[0].operations: Required value, webhooks[0].rules[1].operations: Required value`, }, { name: "\"\" is NOT a valid operation", - config: getExternalAdmissionHookConfiguration( - []admissionregistration.ExternalAdmissionHook{ + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ { Name: "webhook.k8s.io", Rules: []admissionregistration.RuleWithOperations{ @@ -312,8 +322,8 @@ func TestValidateExternalAdmissionHookConfiguration(t *testing.T) { }, { name: "operation must be either create/update/delete/connect", - config: getExternalAdmissionHookConfiguration( - []admissionregistration.ExternalAdmissionHook{ + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ { Name: "webhook.k8s.io", Rules: []admissionregistration.RuleWithOperations{ @@ -332,8 +342,8 @@ func TestValidateExternalAdmissionHookConfiguration(t *testing.T) { }, { name: "wildcard operation cannot be mixed with other strings", - config: getExternalAdmissionHookConfiguration( - []admissionregistration.ExternalAdmissionHook{ + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ { Name: "webhook.k8s.io", Rules: []admissionregistration.RuleWithOperations{ @@ -352,10 +362,11 @@ func TestValidateExternalAdmissionHookConfiguration(t *testing.T) { }, { name: `resource "*" can co-exist with resources that have subresources`, - config: getExternalAdmissionHookConfiguration( - []admissionregistration.ExternalAdmissionHook{ + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ { - Name: "webhook.k8s.io", + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, Rules: []admissionregistration.RuleWithOperations{ { Operations: []admissionregistration.OperationType{"CREATE"}, @@ -371,10 +382,11 @@ func TestValidateExternalAdmissionHookConfiguration(t *testing.T) { }, { name: `resource "*" cannot mix with resources that don't have subresources`, - config: getExternalAdmissionHookConfiguration( - []admissionregistration.ExternalAdmissionHook{ + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ { - Name: "webhook.k8s.io", + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, Rules: []admissionregistration.RuleWithOperations{ { Operations: []admissionregistration.OperationType{"CREATE"}, @@ -391,10 +403,11 @@ func TestValidateExternalAdmissionHookConfiguration(t *testing.T) { }, { name: "resource a/* cannot mix with a/x", - config: getExternalAdmissionHookConfiguration( - []admissionregistration.ExternalAdmissionHook{ + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ { - Name: "webhook.k8s.io", + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, Rules: []admissionregistration.RuleWithOperations{ { Operations: []admissionregistration.OperationType{"CREATE"}, @@ -407,14 +420,15 @@ func TestValidateExternalAdmissionHookConfiguration(t *testing.T) { }, }, }), - expectedError: `externalAdmissionHooks[0].rules[0].resources[1]: Invalid value: "a/x": if 'a/*' is present, must not specify a/x`, + expectedError: `webhooks[0].rules[0].resources[1]: Invalid value: "a/x": if 'a/*' is present, must not specify a/x`, }, { name: "resource a/* can mix with a", - config: getExternalAdmissionHookConfiguration( - []admissionregistration.ExternalAdmissionHook{ + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ { - Name: "webhook.k8s.io", + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, Rules: []admissionregistration.RuleWithOperations{ { Operations: []admissionregistration.OperationType{"CREATE"}, @@ -430,10 +444,11 @@ func TestValidateExternalAdmissionHookConfiguration(t *testing.T) { }, { name: "resource */a cannot mix with x/a", - config: getExternalAdmissionHookConfiguration( - []admissionregistration.ExternalAdmissionHook{ + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ { - Name: "webhook.k8s.io", + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, Rules: []admissionregistration.RuleWithOperations{ { Operations: []admissionregistration.OperationType{"CREATE"}, @@ -446,14 +461,15 @@ func TestValidateExternalAdmissionHookConfiguration(t *testing.T) { }, }, }), - expectedError: `externalAdmissionHooks[0].rules[0].resources[1]: Invalid value: "x/a": if '*/a' is present, must not specify x/a`, + expectedError: `webhooks[0].rules[0].resources[1]: Invalid value: "x/a": if '*/a' is present, must not specify x/a`, }, { name: "resource */* cannot mix with other resources", - config: getExternalAdmissionHookConfiguration( - []admissionregistration.ExternalAdmissionHook{ + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ { - Name: "webhook.k8s.io", + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, Rules: []admissionregistration.RuleWithOperations{ { Operations: []admissionregistration.OperationType{"CREATE"}, @@ -466,34 +482,275 @@ func TestValidateExternalAdmissionHookConfiguration(t *testing.T) { }, }, }), - expectedError: `externalAdmissionHooks[0].rules[0].resources: Invalid value: []string{"*/*", "a"}: if '*/*' is present, must not specify other resources`, + expectedError: `webhooks[0].rules[0].resources: Invalid value: []string{"*/*", "a"}: if '*/*' is present, must not specify other resources`, }, { - name: "FailurePolicy can only be \"Ignore\"", - config: getExternalAdmissionHookConfiguration( - []admissionregistration.ExternalAdmissionHook{ + name: "FailurePolicy can only be \"Ignore\" or \"Fail\"", + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ { - Name: "webhook.k8s.io", + Name: "webhook.k8s.io", + ClientConfig: validClientConfig, FailurePolicy: func() *admissionregistration.FailurePolicyType { - r := admissionregistration.Fail + r := admissionregistration.FailurePolicyType("other") return &r }(), }, }), - expectedError: `failurePolicy: Unsupported value: "Fail": supported values: "Ignore"`, + expectedError: `webhooks[0].failurePolicy: Unsupported value: "other": supported values: "Fail", "Ignore"`, + }, + { + name: "both service and URL missing", + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ + { + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{}, + }, + }), + expectedError: `exactly one of`, + }, + { + name: "both service and URL provided", + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ + { + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + }, + URL: strPtr("example.com/k8s/webhook"), + }, + }, + }), + expectedError: `[0].clientConfig.url: Required value: exactly one of url or service is required`, + }, + { + name: "blank URL", + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ + { + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + URL: strPtr(""), + }, + }, + }), + expectedError: `[0].clientConfig.url: Invalid value: "": host must be provided`, + }, + { + name: "wrong scheme", + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ + { + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + URL: strPtr("http://example.com"), + }, + }, + }), + expectedError: `https`, + }, + { + name: "missing host", + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ + { + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + URL: strPtr("https:///fancy/webhook"), + }, + }, + }), + expectedError: `host must be provided`, + }, + { + name: "fragment", + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ + { + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + URL: strPtr("https://example.com/#bookmark"), + }, + }, + }), + expectedError: `"bookmark": fragments are not permitted`, + }, + { + name: "query", + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ + { + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + URL: strPtr("https://example.com?arg=value"), + }, + }, + }), + expectedError: `"arg=value": query parameters are not permitted`, + }, + { + name: "user", + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ + { + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + URL: strPtr("https://harry.potter@example.com/"), + }, + }, + }), + expectedError: `"harry.potter": user information is not permitted`, + }, + { + name: "just totally wrong", + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ + { + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + URL: strPtr("arg#backwards=thisis?html.index/port:host//:https"), + }, + }, + }), + expectedError: `host must be provided`, + }, + { + name: "path must start with slash", + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ + { + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("foo/"), + }, + }, + }, + }), + expectedError: `clientConfig.service.path: Invalid value: "foo/": must start with a '/'`, + }, + { + name: "path accepts slash", + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ + { + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("/"), + }, + }, + }, + }), + expectedError: ``, + }, + { + name: "path accepts no trailing slash", + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ + { + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("/foo"), + }, + }, + }, + }), + expectedError: ``, + }, + { + name: "path fails //", + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ + { + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("//"), + }, + }, + }, + }), + expectedError: `clientConfig.service.path: Invalid value: "//": segment[0] may not be empty`, + }, + { + name: "path no empty step", + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ + { + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("/foo//bar/"), + }, + }, + }, + }), + expectedError: `clientConfig.service.path: Invalid value: "/foo//bar/": segment[1] may not be empty`, + }, { + name: "path no empty step 2", + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ + { + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("/foo/bar//"), + }, + }, + }, + }), + expectedError: `clientConfig.service.path: Invalid value: "/foo/bar//": segment[2] may not be empty`, + }, + { + name: "path no non-subdomain", + config: newValidatingWebhookConfiguration( + []admissionregistration.Webhook{ + { + Name: "webhook.k8s.io", + ClientConfig: admissionregistration.WebhookClientConfig{ + Service: &admissionregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("/apis/foo.bar/v1alpha1/--bad"), + }, + }, + }, + }), + expectedError: `clientConfig.service.path: Invalid value: "/apis/foo.bar/v1alpha1/--bad": segment[3]: a DNS-1123 subdomain`, }, } for _, test := range tests { - errs := ValidateExternalAdmissionHookConfiguration(test.config) - err := errs.ToAggregate() - if err != nil { - if e, a := test.expectedError, err.Error(); !strings.Contains(a, e) || e == "" { - t.Errorf("test case %s, expected to contain %s, got %s", test.name, e, a) + t.Run(test.name, func(t *testing.T) { + errs := ValidateValidatingWebhookConfiguration(test.config) + err := errs.ToAggregate() + if err != nil { + if e, a := test.expectedError, err.Error(); !strings.Contains(a, e) || e == "" { + t.Errorf("expected to contain %s, got %s", e, a) + } + } else { + if test.expectedError != "" { + t.Errorf("unexpected no error, expected to contain %s", test.expectedError) + } } - } else { - if test.expectedError != "" { - t.Errorf("test case %s, unexpected no error, expected to contain %s", test.name, test.expectedError) - } - } + }) + } } diff --git a/pkg/apis/admissionregistration/zz_generated.deepcopy.go b/pkg/apis/admissionregistration/zz_generated.deepcopy.go index 4952a75f791..83562365c20 100644 --- a/pkg/apis/admissionregistration/zz_generated.deepcopy.go +++ b/pkg/apis/admissionregistration/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,187 +21,10 @@ limitations under the License. package admissionregistration import ( - conversion "k8s.io/apimachinery/pkg/conversion" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AdmissionHookClientConfig).DeepCopyInto(out.(*AdmissionHookClientConfig)) - return nil - }, InType: reflect.TypeOf(&AdmissionHookClientConfig{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExternalAdmissionHook).DeepCopyInto(out.(*ExternalAdmissionHook)) - return nil - }, InType: reflect.TypeOf(&ExternalAdmissionHook{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExternalAdmissionHookConfiguration).DeepCopyInto(out.(*ExternalAdmissionHookConfiguration)) - return nil - }, InType: reflect.TypeOf(&ExternalAdmissionHookConfiguration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExternalAdmissionHookConfigurationList).DeepCopyInto(out.(*ExternalAdmissionHookConfigurationList)) - return nil - }, InType: reflect.TypeOf(&ExternalAdmissionHookConfigurationList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Initializer).DeepCopyInto(out.(*Initializer)) - return nil - }, InType: reflect.TypeOf(&Initializer{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*InitializerConfiguration).DeepCopyInto(out.(*InitializerConfiguration)) - return nil - }, InType: reflect.TypeOf(&InitializerConfiguration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*InitializerConfigurationList).DeepCopyInto(out.(*InitializerConfigurationList)) - return nil - }, InType: reflect.TypeOf(&InitializerConfigurationList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Rule).DeepCopyInto(out.(*Rule)) - return nil - }, InType: reflect.TypeOf(&Rule{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RuleWithOperations).DeepCopyInto(out.(*RuleWithOperations)) - return nil - }, InType: reflect.TypeOf(&RuleWithOperations{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ServiceReference).DeepCopyInto(out.(*ServiceReference)) - return nil - }, InType: reflect.TypeOf(&ServiceReference{})}, - ) -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AdmissionHookClientConfig) DeepCopyInto(out *AdmissionHookClientConfig) { - *out = *in - out.Service = in.Service - if in.CABundle != nil { - in, out := &in.CABundle, &out.CABundle - *out = make([]byte, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionHookClientConfig. -func (in *AdmissionHookClientConfig) DeepCopy() *AdmissionHookClientConfig { - if in == nil { - return nil - } - out := new(AdmissionHookClientConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExternalAdmissionHook) DeepCopyInto(out *ExternalAdmissionHook) { - *out = *in - in.ClientConfig.DeepCopyInto(&out.ClientConfig) - if in.Rules != nil { - in, out := &in.Rules, &out.Rules - *out = make([]RuleWithOperations, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.FailurePolicy != nil { - in, out := &in.FailurePolicy, &out.FailurePolicy - if *in == nil { - *out = nil - } else { - *out = new(FailurePolicyType) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalAdmissionHook. -func (in *ExternalAdmissionHook) DeepCopy() *ExternalAdmissionHook { - if in == nil { - return nil - } - out := new(ExternalAdmissionHook) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExternalAdmissionHookConfiguration) DeepCopyInto(out *ExternalAdmissionHookConfiguration) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.ExternalAdmissionHooks != nil { - in, out := &in.ExternalAdmissionHooks, &out.ExternalAdmissionHooks - *out = make([]ExternalAdmissionHook, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalAdmissionHookConfiguration. -func (in *ExternalAdmissionHookConfiguration) DeepCopy() *ExternalAdmissionHookConfiguration { - if in == nil { - return nil - } - out := new(ExternalAdmissionHookConfiguration) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ExternalAdmissionHookConfiguration) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExternalAdmissionHookConfigurationList) DeepCopyInto(out *ExternalAdmissionHookConfigurationList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ExternalAdmissionHookConfiguration, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalAdmissionHookConfigurationList. -func (in *ExternalAdmissionHookConfigurationList) DeepCopy() *ExternalAdmissionHookConfigurationList { - if in == nil { - return nil - } - out := new(ExternalAdmissionHookConfigurationList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ExternalAdmissionHookConfigurationList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Initializer) DeepCopyInto(out *Initializer) { *out = *in @@ -293,6 +116,74 @@ func (in *InitializerConfigurationList) DeepCopyObject() runtime.Object { } } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MutatingWebhookConfiguration) DeepCopyInto(out *MutatingWebhookConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.Webhooks != nil { + in, out := &in.Webhooks, &out.Webhooks + *out = make([]Webhook, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MutatingWebhookConfiguration. +func (in *MutatingWebhookConfiguration) DeepCopy() *MutatingWebhookConfiguration { + if in == nil { + return nil + } + out := new(MutatingWebhookConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MutatingWebhookConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MutatingWebhookConfigurationList) DeepCopyInto(out *MutatingWebhookConfigurationList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]MutatingWebhookConfiguration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MutatingWebhookConfigurationList. +func (in *MutatingWebhookConfigurationList) DeepCopy() *MutatingWebhookConfigurationList { + if in == nil { + return nil + } + out := new(MutatingWebhookConfigurationList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MutatingWebhookConfigurationList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Rule) DeepCopyInto(out *Rule) { *out = *in @@ -349,6 +240,15 @@ func (in *RuleWithOperations) DeepCopy() *RuleWithOperations { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceReference) DeepCopyInto(out *ServiceReference) { *out = *in + if in.Path != nil { + in, out := &in.Path, &out.Path + if *in == nil { + *out = nil + } else { + *out = new(string) + **out = **in + } + } return } @@ -361,3 +261,152 @@ func (in *ServiceReference) DeepCopy() *ServiceReference { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ValidatingWebhookConfiguration) DeepCopyInto(out *ValidatingWebhookConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.Webhooks != nil { + in, out := &in.Webhooks, &out.Webhooks + *out = make([]Webhook, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidatingWebhookConfiguration. +func (in *ValidatingWebhookConfiguration) DeepCopy() *ValidatingWebhookConfiguration { + if in == nil { + return nil + } + out := new(ValidatingWebhookConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ValidatingWebhookConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ValidatingWebhookConfigurationList) DeepCopyInto(out *ValidatingWebhookConfigurationList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ValidatingWebhookConfiguration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidatingWebhookConfigurationList. +func (in *ValidatingWebhookConfigurationList) DeepCopy() *ValidatingWebhookConfigurationList { + if in == nil { + return nil + } + out := new(ValidatingWebhookConfigurationList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ValidatingWebhookConfigurationList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Webhook) DeepCopyInto(out *Webhook) { + *out = *in + in.ClientConfig.DeepCopyInto(&out.ClientConfig) + if in.Rules != nil { + in, out := &in.Rules, &out.Rules + *out = make([]RuleWithOperations, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.FailurePolicy != nil { + in, out := &in.FailurePolicy, &out.FailurePolicy + if *in == nil { + *out = nil + } else { + *out = new(FailurePolicyType) + **out = **in + } + } + if in.NamespaceSelector != nil { + in, out := &in.NamespaceSelector, &out.NamespaceSelector + if *in == nil { + *out = nil + } else { + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Webhook. +func (in *Webhook) DeepCopy() *Webhook { + if in == nil { + return nil + } + out := new(Webhook) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WebhookClientConfig) DeepCopyInto(out *WebhookClientConfig) { + *out = *in + if in.URL != nil { + in, out := &in.URL, &out.URL + if *in == nil { + *out = nil + } else { + *out = new(string) + **out = **in + } + } + if in.Service != nil { + in, out := &in.Service, &out.Service + if *in == nil { + *out = nil + } else { + *out = new(ServiceReference) + (*in).DeepCopyInto(*out) + } + } + if in.CABundle != nil { + in, out := &in.CABundle, &out.CABundle + *out = make([]byte, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookClientConfig. +func (in *WebhookClientConfig) DeepCopy() *WebhookClientConfig { + if in == nil { + return nil + } + out := new(WebhookClientConfig) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/apps/BUILD b/pkg/apis/apps/BUILD index e80860f6db0..8cb47574a19 100644 --- a/pkg/apis/apps/BUILD +++ b/pkg/apis/apps/BUILD @@ -15,10 +15,10 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/apis/apps", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/autoscaling:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/pkg/apis/apps/doc.go b/pkg/apis/apps/doc.go index e216ed6fd8d..1ff549998c0 100644 --- a/pkg/apis/apps/doc.go +++ b/pkg/apis/apps/doc.go @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package package apps // import "k8s.io/kubernetes/pkg/apis/apps" diff --git a/pkg/apis/apps/install/BUILD b/pkg/apis/apps/install/BUILD index 6dfb18cceb4..e33b4cb2422 100644 --- a/pkg/apis/apps/install/BUILD +++ b/pkg/apis/apps/install/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["install.go"], importpath = "k8s.io/kubernetes/pkg/apis/apps/install", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/apps:go_default_library", "//pkg/apis/apps/v1:go_default_library", "//pkg/apis/apps/v1beta1:go_default_library", diff --git a/pkg/apis/apps/install/install.go b/pkg/apis/apps/install/install.go index 2e31da6d9ea..73117127693 100644 --- a/pkg/apis/apps/install/install.go +++ b/pkg/apis/apps/install/install.go @@ -22,7 +22,7 @@ import ( "k8s.io/apimachinery/pkg/apimachinery/announced" "k8s.io/apimachinery/pkg/apimachinery/registered" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/apps/v1" "k8s.io/kubernetes/pkg/apis/apps/v1beta1" @@ -30,7 +30,7 @@ import ( ) func init() { - Install(api.GroupFactoryRegistry, api.Registry, api.Scheme) + Install(legacyscheme.GroupFactoryRegistry, legacyscheme.Registry, legacyscheme.Scheme) } // Install registers the API group and adds types to a scheme diff --git a/pkg/apis/apps/register.go b/pkg/apis/apps/register.go index 77d8445ae14..8b7591807cb 100644 --- a/pkg/apis/apps/register.go +++ b/pkg/apis/apps/register.go @@ -19,6 +19,7 @@ package apps import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kubernetes/pkg/apis/autoscaling" "k8s.io/kubernetes/pkg/apis/extensions" ) @@ -43,7 +44,7 @@ func Resource(resource string) schema.GroupResource { return SchemeGroupVersion.WithResource(resource).GroupResource() } -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { // TODO this will get cleaned up with the scheme types are fixed scheme.AddKnownTypes(SchemeGroupVersion, @@ -52,7 +53,7 @@ func addKnownTypes(scheme *runtime.Scheme) error { &extensions.Deployment{}, &extensions.DeploymentList{}, &extensions.DeploymentRollback{}, - &extensions.Scale{}, + &autoscaling.Scale{}, &StatefulSet{}, &StatefulSetList{}, &ControllerRevision{}, diff --git a/pkg/apis/apps/types.go b/pkg/apis/apps/types.go index 7654b681ab1..850694d7fc2 100644 --- a/pkg/apis/apps/types.go +++ b/pkg/apis/apps/types.go @@ -19,12 +19,12 @@ package apps import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // +genclient -// +genclient:method=GetScale,verb=get,subresource=scale,result=k8s.io/kubernetes/pkg/apis/extensions.Scale -// +genclient:method=UpdateScale,verb=update,subresource=scale,input=k8s.io/kubernetes/pkg/apis/extensions.Scale,result=k8s.io/kubernetes/pkg/apis/extensions.Scale +// +genclient:method=GetScale,verb=get,subresource=scale,result=k8s.io/kubernetes/pkg/apis/autoscaling.Scale +// +genclient:method=UpdateScale,verb=update,subresource=scale,input=k8s.io/kubernetes/pkg/apis/autoscaling.Scale,result=k8s.io/kubernetes/pkg/apis/autoscaling.Scale // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // StatefulSet represents a set of pods with consistent identities. @@ -195,6 +195,27 @@ type StatefulSetStatus struct { // newest ControllerRevision. // +optional CollisionCount *int32 + + // Represents the latest available observations of a statefulset's current state. + Conditions []StatefulSetCondition +} + +type StatefulSetConditionType string + +// TODO: Add valid condition types for Statefulsets. + +// StatefulSetCondition describes the state of a statefulset at a certain point. +type StatefulSetCondition struct { + // Type of statefulset condition. + Type StatefulSetConditionType + // Status of the condition, one of True, False, Unknown. + Status api.ConditionStatus + // The last time this condition was updated. + LastTransitionTime metav1.Time + // The reason for the condition's last transition. + Reason string + // A human readable message indicating details about the transition. + Message string } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/apis/apps/v1/BUILD b/pkg/apis/apps/v1/BUILD index be94c120d4c..3ffdef39f60 100644 --- a/pkg/apis/apps/v1/BUILD +++ b/pkg/apis/apps/v1/BUILD @@ -13,9 +13,12 @@ go_library( importpath = "k8s.io/kubernetes/pkg/apis/apps/v1", visibility = ["//visibility:public"], deps = [ - "//pkg/api/v1:go_default_library", + "//pkg/apis/apps:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/v1:go_default_library", "//pkg/apis/extensions:go_default_library", "//vendor/k8s.io/api/apps/v1:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", @@ -33,13 +36,16 @@ go_test( importpath = "k8s.io/kubernetes/pkg/apis/apps/v1_test", deps = [ ":go_default_library", - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/apps:go_default_library", "//pkg/apis/apps/install:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/install:go_default_library", "//pkg/apis/extensions:go_default_library", "//vendor/k8s.io/api/apps/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", diff --git a/pkg/apis/apps/v1/conversion.go b/pkg/apis/apps/v1/conversion.go index 2e171964736..0908f7fc2bb 100644 --- a/pkg/apis/apps/v1/conversion.go +++ b/pkg/apis/apps/v1/conversion.go @@ -17,13 +17,18 @@ limitations under the License. package v1 import ( + "fmt" "strconv" appsv1 "k8s.io/api/apps/v1" + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" - k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/apis/apps" + api "k8s.io/kubernetes/pkg/apis/core" + k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" "k8s.io/kubernetes/pkg/apis/extensions" ) @@ -33,14 +38,33 @@ func addConversionFuncs(scheme *runtime.Scheme) error { // it, but a plain int32 is more convenient in the internal type. These // functions are the same as the autogenerated ones in every other way. err := scheme.AddConversionFuncs( + Convert_v1_StatefulSetSpec_To_apps_StatefulSetSpec, + Convert_apps_StatefulSetSpec_To_v1_StatefulSetSpec, + Convert_v1_StatefulSetUpdateStrategy_To_apps_StatefulSetUpdateStrategy, + Convert_apps_StatefulSetUpdateStrategy_To_v1_StatefulSetUpdateStrategy, Convert_extensions_RollingUpdateDaemonSet_To_v1_RollingUpdateDaemonSet, Convert_v1_RollingUpdateDaemonSet_To_extensions_RollingUpdateDaemonSet, + Convert_v1_StatefulSetStatus_To_apps_StatefulSetStatus, + Convert_apps_StatefulSetStatus_To_v1_StatefulSetStatus, + Convert_v1_Deployment_To_extensions_Deployment, + Convert_extensions_Deployment_To_v1_Deployment, Convert_extensions_DaemonSet_To_v1_DaemonSet, Convert_v1_DaemonSet_To_extensions_DaemonSet, Convert_extensions_DaemonSetSpec_To_v1_DaemonSetSpec, Convert_v1_DaemonSetSpec_To_extensions_DaemonSetSpec, Convert_extensions_DaemonSetUpdateStrategy_To_v1_DaemonSetUpdateStrategy, Convert_v1_DaemonSetUpdateStrategy_To_extensions_DaemonSetUpdateStrategy, + // extensions + // TODO: below conversions should be dropped in favor of auto-generated + // ones, see https://github.com/kubernetes/kubernetes/issues/39865 + Convert_v1_DeploymentSpec_To_extensions_DeploymentSpec, + Convert_extensions_DeploymentSpec_To_v1_DeploymentSpec, + Convert_v1_DeploymentStrategy_To_extensions_DeploymentStrategy, + Convert_extensions_DeploymentStrategy_To_v1_DeploymentStrategy, + Convert_v1_RollingUpdateDeployment_To_extensions_RollingUpdateDeployment, + Convert_extensions_RollingUpdateDeployment_To_v1_RollingUpdateDeployment, + Convert_extensions_ReplicaSetSpec_To_v1_ReplicaSetSpec, + Convert_v1_ReplicaSetSpec_To_extensions_ReplicaSetSpec, ) if err != nil { return err @@ -48,6 +72,150 @@ func addConversionFuncs(scheme *runtime.Scheme) error { return nil } +func Convert_v1_DeploymentSpec_To_extensions_DeploymentSpec(in *appsv1.DeploymentSpec, out *extensions.DeploymentSpec, s conversion.Scope) error { + if in.Replicas != nil { + out.Replicas = *in.Replicas + } + out.Selector = in.Selector + if err := k8s_api_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err + } + if err := Convert_v1_DeploymentStrategy_To_extensions_DeploymentStrategy(&in.Strategy, &out.Strategy, s); err != nil { + return err + } + out.RevisionHistoryLimit = in.RevisionHistoryLimit + out.MinReadySeconds = in.MinReadySeconds + out.Paused = in.Paused + if in.ProgressDeadlineSeconds != nil { + out.ProgressDeadlineSeconds = new(int32) + *out.ProgressDeadlineSeconds = *in.ProgressDeadlineSeconds + } + return nil +} + +func Convert_extensions_DeploymentSpec_To_v1_DeploymentSpec(in *extensions.DeploymentSpec, out *appsv1.DeploymentSpec, s conversion.Scope) error { + out.Replicas = &in.Replicas + out.Selector = in.Selector + if err := k8s_api_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err + } + if err := Convert_extensions_DeploymentStrategy_To_v1_DeploymentStrategy(&in.Strategy, &out.Strategy, s); err != nil { + return err + } + if in.RevisionHistoryLimit != nil { + out.RevisionHistoryLimit = new(int32) + *out.RevisionHistoryLimit = int32(*in.RevisionHistoryLimit) + } + out.MinReadySeconds = int32(in.MinReadySeconds) + out.Paused = in.Paused + if in.ProgressDeadlineSeconds != nil { + out.ProgressDeadlineSeconds = new(int32) + *out.ProgressDeadlineSeconds = *in.ProgressDeadlineSeconds + } + return nil +} + +func Convert_extensions_DeploymentStrategy_To_v1_DeploymentStrategy(in *extensions.DeploymentStrategy, out *appsv1.DeploymentStrategy, s conversion.Scope) error { + out.Type = appsv1.DeploymentStrategyType(in.Type) + if in.RollingUpdate != nil { + out.RollingUpdate = new(appsv1.RollingUpdateDeployment) + if err := Convert_extensions_RollingUpdateDeployment_To_v1_RollingUpdateDeployment(in.RollingUpdate, out.RollingUpdate, s); err != nil { + return err + } + } else { + out.RollingUpdate = nil + } + return nil +} + +func Convert_v1_DeploymentStrategy_To_extensions_DeploymentStrategy(in *appsv1.DeploymentStrategy, out *extensions.DeploymentStrategy, s conversion.Scope) error { + out.Type = extensions.DeploymentStrategyType(in.Type) + if in.RollingUpdate != nil { + out.RollingUpdate = new(extensions.RollingUpdateDeployment) + if err := Convert_v1_RollingUpdateDeployment_To_extensions_RollingUpdateDeployment(in.RollingUpdate, out.RollingUpdate, s); err != nil { + return err + } + } else { + out.RollingUpdate = nil + } + return nil +} + +func Convert_v1_RollingUpdateDeployment_To_extensions_RollingUpdateDeployment(in *appsv1.RollingUpdateDeployment, out *extensions.RollingUpdateDeployment, s conversion.Scope) error { + if err := s.Convert(in.MaxUnavailable, &out.MaxUnavailable, 0); err != nil { + return err + } + if err := s.Convert(in.MaxSurge, &out.MaxSurge, 0); err != nil { + return err + } + return nil +} + +func Convert_extensions_RollingUpdateDeployment_To_v1_RollingUpdateDeployment(in *extensions.RollingUpdateDeployment, out *appsv1.RollingUpdateDeployment, s conversion.Scope) error { + if out.MaxUnavailable == nil { + out.MaxUnavailable = &intstr.IntOrString{} + } + if err := s.Convert(&in.MaxUnavailable, out.MaxUnavailable, 0); err != nil { + return err + } + if out.MaxSurge == nil { + out.MaxSurge = &intstr.IntOrString{} + } + if err := s.Convert(&in.MaxSurge, out.MaxSurge, 0); err != nil { + return err + } + return nil +} + +func Convert_v1_Deployment_To_extensions_Deployment(in *appsv1.Deployment, out *extensions.Deployment, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_DeploymentSpec_To_extensions_DeploymentSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + + // Copy annotation to deprecated rollbackTo field for roundtrip + // TODO: remove this conversion after we delete extensions/v1beta1 and apps/v1beta1 Deployment + if revision, _ := in.Annotations[appsv1.DeprecatedRollbackTo]; revision != "" { + if revision64, err := strconv.ParseInt(revision, 10, 64); err != nil { + return fmt.Errorf("failed to parse annotation[%s]=%s as int64: %v", appsv1.DeprecatedRollbackTo, revision, err) + } else { + out.Spec.RollbackTo = new(extensions.RollbackConfig) + out.Spec.RollbackTo.Revision = revision64 + } + delete(out.Annotations, appsv1.DeprecatedRollbackTo) + } else { + out.Spec.RollbackTo = nil + } + + if err := Convert_v1_DeploymentStatus_To_extensions_DeploymentStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +func Convert_extensions_Deployment_To_v1_Deployment(in *extensions.Deployment, out *appsv1.Deployment, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_extensions_DeploymentSpec_To_v1_DeploymentSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + + // Copy deprecated rollbackTo field to annotation for roundtrip + // TODO: remove this conversion after we delete extensions/v1beta1 and apps/v1beta1 Deployment + if in.Spec.RollbackTo != nil { + if out.Annotations == nil { + out.Annotations = make(map[string]string) + } + out.Annotations[appsv1.DeprecatedRollbackTo] = strconv.FormatInt(in.Spec.RollbackTo.Revision, 10) + } else { + delete(out.Annotations, appsv1.DeprecatedRollbackTo) + } + + if err := Convert_extensions_DeploymentStatus_To_v1_DeploymentStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + func Convert_extensions_RollingUpdateDaemonSet_To_v1_RollingUpdateDaemonSet(in *extensions.RollingUpdateDaemonSet, out *appsv1.RollingUpdateDaemonSet, s conversion.Scope) error { if out.MaxUnavailable == nil { out.MaxUnavailable = &intstr.IntOrString{} @@ -82,7 +250,7 @@ func Convert_extensions_DaemonSet_To_v1_DaemonSet(in *extensions.DaemonSet, out func Convert_extensions_DaemonSetSpec_To_v1_DaemonSetSpec(in *extensions.DaemonSetSpec, out *appsv1.DaemonSetSpec, s conversion.Scope) error { out.Selector = in.Selector - if err := k8s_api_v1.Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := k8s_api_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if err := Convert_extensions_DaemonSetUpdateStrategy_To_v1_DaemonSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, s); err != nil { @@ -130,7 +298,7 @@ func Convert_v1_DaemonSet_To_extensions_DaemonSet(in *appsv1.DaemonSet, out *ext func Convert_v1_DaemonSetSpec_To_extensions_DaemonSetSpec(in *appsv1.DaemonSetSpec, out *extensions.DaemonSetSpec, s conversion.Scope) error { out.Selector = in.Selector - if err := k8s_api_v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := k8s_api_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if err := Convert_v1_DaemonSetUpdateStrategy_To_extensions_DaemonSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, s); err != nil { @@ -156,3 +324,175 @@ func Convert_v1_DaemonSetUpdateStrategy_To_extensions_DaemonSetUpdateStrategy(in } return nil } + +func Convert_extensions_ReplicaSetSpec_To_v1_ReplicaSetSpec(in *extensions.ReplicaSetSpec, out *appsv1.ReplicaSetSpec, s conversion.Scope) error { + out.Replicas = new(int32) + *out.Replicas = int32(in.Replicas) + out.MinReadySeconds = in.MinReadySeconds + out.Selector = in.Selector + if err := k8s_api_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err + } + return nil +} + +func Convert_v1_ReplicaSetSpec_To_extensions_ReplicaSetSpec(in *appsv1.ReplicaSetSpec, out *extensions.ReplicaSetSpec, s conversion.Scope) error { + if in.Replicas != nil { + out.Replicas = *in.Replicas + } + out.MinReadySeconds = in.MinReadySeconds + out.Selector = in.Selector + if err := k8s_api_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err + } + return nil +} + +func Convert_v1_StatefulSetSpec_To_apps_StatefulSetSpec(in *appsv1.StatefulSetSpec, out *apps.StatefulSetSpec, s conversion.Scope) error { + if in.Replicas != nil { + out.Replicas = *in.Replicas + } + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + *out = new(metav1.LabelSelector) + if err := s.Convert(*in, *out, 0); err != nil { + return err + } + } else { + out.Selector = nil + } + if err := k8s_api_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err + } + if in.VolumeClaimTemplates != nil { + in, out := &in.VolumeClaimTemplates, &out.VolumeClaimTemplates + *out = make([]api.PersistentVolumeClaim, len(*in)) + for i := range *in { + if err := s.Convert(&(*in)[i], &(*out)[i], 0); err != nil { + return err + } + } + } else { + out.VolumeClaimTemplates = nil + } + if err := Convert_v1_StatefulSetUpdateStrategy_To_apps_StatefulSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, s); err != nil { + return err + } + if in.RevisionHistoryLimit != nil { + out.RevisionHistoryLimit = new(int32) + *out.RevisionHistoryLimit = *in.RevisionHistoryLimit + } else { + out.RevisionHistoryLimit = nil + } + out.ServiceName = in.ServiceName + out.PodManagementPolicy = apps.PodManagementPolicyType(in.PodManagementPolicy) + return nil +} + +func Convert_apps_StatefulSetSpec_To_v1_StatefulSetSpec(in *apps.StatefulSetSpec, out *appsv1.StatefulSetSpec, s conversion.Scope) error { + out.Replicas = new(int32) + *out.Replicas = in.Replicas + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + *out = new(metav1.LabelSelector) + if err := s.Convert(*in, *out, 0); err != nil { + return err + } + } else { + out.Selector = nil + } + if err := k8s_api_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err + } + if in.VolumeClaimTemplates != nil { + in, out := &in.VolumeClaimTemplates, &out.VolumeClaimTemplates + *out = make([]v1.PersistentVolumeClaim, len(*in)) + for i := range *in { + if err := s.Convert(&(*in)[i], &(*out)[i], 0); err != nil { + return err + } + } + } else { + out.VolumeClaimTemplates = nil + } + if in.RevisionHistoryLimit != nil { + out.RevisionHistoryLimit = new(int32) + *out.RevisionHistoryLimit = *in.RevisionHistoryLimit + } else { + out.RevisionHistoryLimit = nil + } + out.ServiceName = in.ServiceName + out.PodManagementPolicy = appsv1.PodManagementPolicyType(in.PodManagementPolicy) + if err := Convert_apps_StatefulSetUpdateStrategy_To_v1_StatefulSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, s); err != nil { + return err + } + return nil +} + +func Convert_v1_StatefulSetUpdateStrategy_To_apps_StatefulSetUpdateStrategy(in *appsv1.StatefulSetUpdateStrategy, out *apps.StatefulSetUpdateStrategy, s conversion.Scope) error { + out.Type = apps.StatefulSetUpdateStrategyType(in.Type) + if in.RollingUpdate != nil { + out.RollingUpdate = new(apps.RollingUpdateStatefulSetStrategy) + out.RollingUpdate.Partition = *in.RollingUpdate.Partition + } else { + out.RollingUpdate = nil + } + return nil +} + +func Convert_apps_StatefulSetUpdateStrategy_To_v1_StatefulSetUpdateStrategy(in *apps.StatefulSetUpdateStrategy, out *appsv1.StatefulSetUpdateStrategy, s conversion.Scope) error { + out.Type = appsv1.StatefulSetUpdateStrategyType(in.Type) + if in.RollingUpdate != nil { + out.RollingUpdate = new(appsv1.RollingUpdateStatefulSetStrategy) + out.RollingUpdate.Partition = new(int32) + *out.RollingUpdate.Partition = in.RollingUpdate.Partition + } else { + out.RollingUpdate = nil + } + return nil +} + +func Convert_v1_StatefulSetStatus_To_apps_StatefulSetStatus(in *appsv1.StatefulSetStatus, out *apps.StatefulSetStatus, s conversion.Scope) error { + out.ObservedGeneration = new(int64) + *out.ObservedGeneration = in.ObservedGeneration + out.Replicas = in.Replicas + out.ReadyReplicas = in.ReadyReplicas + out.CurrentReplicas = in.CurrentReplicas + out.UpdatedReplicas = in.UpdatedReplicas + out.CurrentRevision = in.CurrentRevision + out.UpdateRevision = in.UpdateRevision + if in.CollisionCount != nil { + out.CollisionCount = new(int32) + *out.CollisionCount = *in.CollisionCount + } + out.Conditions = make([]apps.StatefulSetCondition, len(in.Conditions)) + for i := range in.Conditions { + if err := Convert_v1_StatefulSetCondition_To_apps_StatefulSetCondition(&in.Conditions[i], &out.Conditions[i], s); err != nil { + return err + } + } + return nil +} + +func Convert_apps_StatefulSetStatus_To_v1_StatefulSetStatus(in *apps.StatefulSetStatus, out *appsv1.StatefulSetStatus, s conversion.Scope) error { + if in.ObservedGeneration != nil { + out.ObservedGeneration = *in.ObservedGeneration + } + out.Replicas = in.Replicas + out.ReadyReplicas = in.ReadyReplicas + out.CurrentReplicas = in.CurrentReplicas + out.UpdatedReplicas = in.UpdatedReplicas + out.CurrentRevision = in.CurrentRevision + out.UpdateRevision = in.UpdateRevision + if in.CollisionCount != nil { + out.CollisionCount = new(int32) + *out.CollisionCount = *in.CollisionCount + } + out.Conditions = make([]appsv1.StatefulSetCondition, len(in.Conditions)) + for i := range in.Conditions { + if err := Convert_apps_StatefulSetCondition_To_v1_StatefulSetCondition(&in.Conditions[i], &out.Conditions[i], s); err != nil { + return err + } + } + return nil +} diff --git a/pkg/apis/apps/v1/conversion_test.go b/pkg/apis/apps/v1/conversion_test.go index ae12f74425c..27e78360c32 100644 --- a/pkg/apis/apps/v1/conversion_test.go +++ b/pkg/apis/apps/v1/conversion_test.go @@ -20,13 +20,210 @@ import ( "testing" appsv1 "k8s.io/api/apps/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/extensions" - + "k8s.io/api/core/v1" apiequality "k8s.io/apimachinery/pkg/api/equality" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/apis/apps" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/extensions" ) +func TestV12StatefulSetSpecConversion(t *testing.T) { + replicas := newInt32(2) + selector := &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}} + appsv1Template := v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: v1.PodSpec{ + RestartPolicy: v1.RestartPolicy("bar"), + SecurityContext: new(v1.PodSecurityContext), + }, + } + apiTemplate := api.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy("bar"), + SecurityContext: new(api.PodSecurityContext), + }, + } + testcases := map[string]struct { + stsSpec1 *apps.StatefulSetSpec + stsSepc2 *appsv1.StatefulSetSpec + }{ + "StatefulSetSpec Conversion 1": { + stsSpec1: &apps.StatefulSetSpec{ + Replicas: *replicas, + Template: apiTemplate, + }, + stsSepc2: &appsv1.StatefulSetSpec{ + Replicas: replicas, + Template: appsv1Template, + }, + }, + "StatefulSetSpec Conversion 2": { + stsSpec1: &apps.StatefulSetSpec{ + Replicas: *replicas, + Selector: selector, + Template: apiTemplate, + ServiceName: "foo", + PodManagementPolicy: apps.PodManagementPolicyType("bar"), + }, + stsSepc2: &appsv1.StatefulSetSpec{ + Replicas: replicas, + Selector: selector, + Template: appsv1Template, + ServiceName: "foo", + PodManagementPolicy: appsv1.PodManagementPolicyType("bar"), + }, + }, + } + + for k, tc := range testcases { + // apps -> appsv1 + internal1 := &appsv1.StatefulSetSpec{} + if err := legacyscheme.Scheme.Convert(tc.stsSpec1, internal1, nil); err != nil { + t.Errorf("%q - %q: unexpected error: %v", k, "from extensions to appsv1", err) + } + + if !apiequality.Semantic.DeepEqual(internal1, tc.stsSepc2) { + t.Errorf("%q - %q: expected\n\t%#v, got \n\t%#v", k, "from extensions to appsv1", tc.stsSepc2, internal1) + } + + // appsv1 -> apps + internal2 := &apps.StatefulSetSpec{} + if err := legacyscheme.Scheme.Convert(tc.stsSepc2, internal2, nil); err != nil { + t.Errorf("%q - %q: unexpected error: %v", k, "from appsv1 to extensions", err) + } + if !apiequality.Semantic.DeepEqual(internal2, tc.stsSpec1) { + t.Errorf("%q- %q: expected\n\t%#v, got \n\t%#v", k, "from appsv1 to extensions", tc.stsSpec1, internal2) + } + } +} + +func TestV1StatefulSetStatusConversion(t *testing.T) { + observedGeneration := new(int64) + *observedGeneration = 2 + collisionCount := new(int32) + *collisionCount = 1 + testcases := map[string]struct { + stsStatus1 *apps.StatefulSetStatus + stsStatus2 *appsv1.StatefulSetStatus + }{ + "StatefulSetStatus Conversion 1": { + stsStatus1: &apps.StatefulSetStatus{ + Replicas: int32(3), + ReadyReplicas: int32(1), + CurrentReplicas: int32(3), + UpdatedReplicas: int32(3), + CurrentRevision: "12345", + UpdateRevision: "23456", + ObservedGeneration: observedGeneration, + }, + stsStatus2: &appsv1.StatefulSetStatus{ + Replicas: int32(3), + ReadyReplicas: int32(1), + CurrentReplicas: int32(3), + UpdatedReplicas: int32(3), + CurrentRevision: "12345", + UpdateRevision: "23456", + ObservedGeneration: *observedGeneration, + }, + }, + "StatefulSetStatus Conversion 2": { + stsStatus1: &apps.StatefulSetStatus{ + ObservedGeneration: observedGeneration, + Replicas: int32(3), + ReadyReplicas: int32(1), + CurrentReplicas: int32(3), + UpdatedReplicas: int32(3), + CurrentRevision: "12345", + UpdateRevision: "23456", + CollisionCount: collisionCount, + }, + stsStatus2: &appsv1.StatefulSetStatus{ + ObservedGeneration: *observedGeneration, + Replicas: int32(3), + ReadyReplicas: int32(1), + CurrentReplicas: int32(3), + UpdatedReplicas: int32(3), + CurrentRevision: "12345", + UpdateRevision: "23456", + CollisionCount: collisionCount, + }, + }, + } + + for k, tc := range testcases { + // apps -> appsv1 + internal1 := &appsv1.StatefulSetStatus{} + if err := legacyscheme.Scheme.Convert(tc.stsStatus1, internal1, nil); err != nil { + t.Errorf("%q - %q: unexpected error: %v", k, "from apps to appsv1", err) + } + + if !apiequality.Semantic.DeepEqual(internal1, tc.stsStatus2) { + t.Errorf("%q - %q: expected\n\t%#v, got \n\t%#v", k, "from apps to appsv1", tc.stsStatus2, internal1) + } + + // appsv1 -> apps + internal2 := &apps.StatefulSetStatus{} + if err := legacyscheme.Scheme.Convert(tc.stsStatus2, internal2, nil); err != nil { + t.Errorf("%q - %q: unexpected error: %v", k, "from appsv1 to apps", err) + } + if !apiequality.Semantic.DeepEqual(internal2, tc.stsStatus1) { + t.Errorf("%q - %q: expected\n\t%#v, got \n\t%#v", k, "from appsv1 to apps", tc.stsStatus1, internal2) + } + } +} + +func TestV1StatefulSetUpdateStrategyConversion(t *testing.T) { + partition := newInt32(2) + appsv1rollingUpdate := new(appsv1.RollingUpdateStatefulSetStrategy) + appsv1rollingUpdate.Partition = partition + appsrollingUpdate := new(apps.RollingUpdateStatefulSetStrategy) + appsrollingUpdate.Partition = *partition + testcases := map[string]struct { + stsUpdateStrategy1 *apps.StatefulSetUpdateStrategy + stsUpdateStrategy2 *appsv1.StatefulSetUpdateStrategy + }{ + "StatefulSetUpdateStrategy Conversion 1": { + stsUpdateStrategy1: &apps.StatefulSetUpdateStrategy{Type: apps.StatefulSetUpdateStrategyType("foo")}, + stsUpdateStrategy2: &appsv1.StatefulSetUpdateStrategy{Type: appsv1.StatefulSetUpdateStrategyType("foo")}, + }, + "StatefulSetUpdateStrategy Conversion 2": { + stsUpdateStrategy1: &apps.StatefulSetUpdateStrategy{ + Type: apps.StatefulSetUpdateStrategyType("foo"), + RollingUpdate: appsrollingUpdate, + }, + stsUpdateStrategy2: &appsv1.StatefulSetUpdateStrategy{ + Type: appsv1.StatefulSetUpdateStrategyType("foo"), + RollingUpdate: appsv1rollingUpdate, + }, + }, + } + + for k, tc := range testcases { + // apps -> appsv1 + internal1 := &appsv1.StatefulSetUpdateStrategy{} + if err := legacyscheme.Scheme.Convert(tc.stsUpdateStrategy1, internal1, nil); err != nil { + t.Errorf("%q - %q: unexpected error: %v", "apps -> appsv1", k, err) + } + + if !apiequality.Semantic.DeepEqual(internal1, tc.stsUpdateStrategy2) { + t.Errorf("%q - %q: expected\n\t%#v, got \n\t%#v", "apps -> appsv1", k, tc.stsUpdateStrategy2, internal1) + } + + // appsv1 -> apps + internal2 := &apps.StatefulSetUpdateStrategy{} + if err := legacyscheme.Scheme.Convert(tc.stsUpdateStrategy2, internal2, nil); err != nil { + t.Errorf("%q - %q: unexpected error: %v", "appsv1 -> apps", k, err) + } + if !apiequality.Semantic.DeepEqual(internal2, tc.stsUpdateStrategy1) { + t.Errorf("%q - %q: expected\n\t%#v, got \n\t%#v", "appsv1 -> apps", k, tc.stsUpdateStrategy1, internal2) + } + } +} + func TestV1RollingUpdateDaemonSetConversion(t *testing.T) { intorstr := intstr.FromInt(1) testcases := map[string]struct { @@ -42,7 +239,7 @@ func TestV1RollingUpdateDaemonSetConversion(t *testing.T) { for k, tc := range testcases { // extensions -> v1 internal1 := &appsv1.RollingUpdateDaemonSet{} - if err := api.Scheme.Convert(tc.rollingUpdateDs1, internal1, nil); err != nil { + if err := legacyscheme.Scheme.Convert(tc.rollingUpdateDs1, internal1, nil); err != nil { t.Errorf("%q - %q: unexpected error: %v", k, "from extensions to v1", err) } if !apiequality.Semantic.DeepEqual(internal1, tc.rollingUpdateDs2) { @@ -51,7 +248,7 @@ func TestV1RollingUpdateDaemonSetConversion(t *testing.T) { // v1 -> extensions internal2 := &extensions.RollingUpdateDaemonSet{} - if err := api.Scheme.Convert(tc.rollingUpdateDs2, internal2, nil); err != nil { + if err := legacyscheme.Scheme.Convert(tc.rollingUpdateDs2, internal2, nil); err != nil { t.Errorf("%q - %q: unexpected error: %v", k, "from v1 to extensions", err) } if !apiequality.Semantic.DeepEqual(internal2, tc.rollingUpdateDs1) { @@ -59,3 +256,344 @@ func TestV1RollingUpdateDaemonSetConversion(t *testing.T) { } } } + +func TestV1DeploymentConversion(t *testing.T) { + replica := newInt32(2) + rollbackTo := new(extensions.RollbackConfig) + rollbackTo.Revision = int64(2) + testcases := map[string]struct { + deployment1 *extensions.Deployment + deployment2 *appsv1.Deployment + }{ + "Deployment Conversion 1": { + deployment1: &extensions.Deployment{ + Spec: extensions.DeploymentSpec{ + Replicas: *replica, + RollbackTo: rollbackTo, + Template: api.PodTemplateSpec{ + Spec: api.PodSpec{ + SecurityContext: new(api.PodSecurityContext), + }, + }, + }, + }, + deployment2: &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{appsv1.DeprecatedRollbackTo: "2"}, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: replica, + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + SecurityContext: new(v1.PodSecurityContext), + }, + }, + }, + }, + }, + "Deployment Conversion 2": { + deployment1: &extensions.Deployment{ + Spec: extensions.DeploymentSpec{ + Replicas: *replica, + Template: api.PodTemplateSpec{ + Spec: api.PodSpec{ + SecurityContext: new(api.PodSecurityContext), + }, + }, + }, + }, + deployment2: &appsv1.Deployment{ + Spec: appsv1.DeploymentSpec{ + Replicas: replica, + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + SecurityContext: new(v1.PodSecurityContext), + }, + }, + }, + }, + }, + } + + for k, tc := range testcases { + // extensions -> v1beta2 + internal1 := &appsv1.Deployment{} + if err := legacyscheme.Scheme.Convert(tc.deployment1, internal1, nil); err != nil { + t.Errorf("%q - %q: unexpected error: %v", k, "from extensions to v1beta2", err) + } + if !apiequality.Semantic.DeepEqual(internal1, tc.deployment2) { + t.Errorf("%q - %q: expected\n\t%#v, got \n\t%#v", k, "from extensions to v1beta2", tc.deployment2, internal1) + } + + // v1beta2 -> extensions + internal2 := &extensions.Deployment{} + if err := legacyscheme.Scheme.Convert(tc.deployment2, internal2, nil); err != nil { + t.Errorf("%q - %q: unexpected error: %v", k, "from v1beta2 to extensions", err) + } + if !apiequality.Semantic.DeepEqual(internal2, tc.deployment1) { + t.Errorf("%q - %q: expected\n\t%#v, got \n\t%#v", k, "from v1beta2 to extensions", tc.deployment1, internal2) + } + } +} + +func TestV1DeploymentSpecConversion(t *testing.T) { + replica := newInt32(2) + revisionHistoryLimit := newInt32(2) + progressDeadlineSeconds := newInt32(2) + + testcases := map[string]struct { + deploymentSpec1 *extensions.DeploymentSpec + deploymentSpec2 *appsv1.DeploymentSpec + }{ + "DeploymentSpec Conversion 1": { + deploymentSpec1: &extensions.DeploymentSpec{ + Replicas: *replica, + Template: api.PodTemplateSpec{ + Spec: api.PodSpec{ + SecurityContext: new(api.PodSecurityContext), + }, + }, + }, + deploymentSpec2: &appsv1.DeploymentSpec{ + Replicas: replica, + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + SecurityContext: new(v1.PodSecurityContext), + }, + }, + }, + }, + "DeploymentSpec Conversion 2": { + deploymentSpec1: &extensions.DeploymentSpec{ + Replicas: *replica, + RevisionHistoryLimit: revisionHistoryLimit, + MinReadySeconds: 2, + Paused: true, + Template: api.PodTemplateSpec{ + Spec: api.PodSpec{ + SecurityContext: new(api.PodSecurityContext), + }, + }, + }, + deploymentSpec2: &appsv1.DeploymentSpec{ + Replicas: replica, + RevisionHistoryLimit: revisionHistoryLimit, + MinReadySeconds: 2, + Paused: true, + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + SecurityContext: new(v1.PodSecurityContext), + }, + }, + }, + }, + "DeploymentSpec Conversion 3": { + deploymentSpec1: &extensions.DeploymentSpec{ + Replicas: *replica, + ProgressDeadlineSeconds: progressDeadlineSeconds, + Template: api.PodTemplateSpec{ + Spec: api.PodSpec{ + SecurityContext: new(api.PodSecurityContext), + }, + }, + }, + deploymentSpec2: &appsv1.DeploymentSpec{ + Replicas: replica, + ProgressDeadlineSeconds: progressDeadlineSeconds, + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + SecurityContext: new(v1.PodSecurityContext), + }, + }, + }, + }, + } + + // extensions -> appsv1 + for k, tc := range testcases { + internal := &appsv1.DeploymentSpec{} + if err := legacyscheme.Scheme.Convert(tc.deploymentSpec1, internal, nil); err != nil { + t.Errorf("%q - %q: unexpected error: %v", "extensions -> appsv1", k, err) + } + + if !apiequality.Semantic.DeepEqual(internal, tc.deploymentSpec2) { + t.Errorf("%q - %q: expected\n\t%+v, got \n\t%+v", "extensions -> appsv1", k, tc.deploymentSpec2, internal) + } + } + + // appsv1 -> extensions + for k, tc := range testcases { + internal := &extensions.DeploymentSpec{} + if err := legacyscheme.Scheme.Convert(tc.deploymentSpec2, internal, nil); err != nil { + t.Errorf("%q - %q: unexpected error: %v", "appsv1 -> extensions", k, err) + } + if !apiequality.Semantic.DeepEqual(internal, tc.deploymentSpec1) { + t.Errorf("%q - %q: expected\n\t%+v, got \n\t%+v", "appsv1 -> extensions", k, tc.deploymentSpec1, internal) + } + } + +} + +func TestV1DeploymentStrategyConversion(t *testing.T) { + maxUnavailable := intstr.FromInt(2) + maxSurge := intstr.FromInt(2) + extensionsRollingUpdate := extensions.RollingUpdateDeployment{MaxUnavailable: maxUnavailable, MaxSurge: maxSurge} + appsv1RollingUpdate := appsv1.RollingUpdateDeployment{MaxUnavailable: &maxUnavailable, MaxSurge: &maxSurge} + testcases := map[string]struct { + deploymentStrategy1 *extensions.DeploymentStrategy + deploymentStrategy2 *appsv1.DeploymentStrategy + }{ + "DeploymentStrategy Conversion 1": { + deploymentStrategy1: &extensions.DeploymentStrategy{Type: extensions.DeploymentStrategyType("foo")}, + deploymentStrategy2: &appsv1.DeploymentStrategy{Type: appsv1.DeploymentStrategyType("foo")}, + }, + "DeploymentStrategy Conversion 2": { + deploymentStrategy1: &extensions.DeploymentStrategy{Type: extensions.DeploymentStrategyType("foo"), RollingUpdate: &extensionsRollingUpdate}, + deploymentStrategy2: &appsv1.DeploymentStrategy{Type: appsv1.DeploymentStrategyType("foo"), RollingUpdate: &appsv1RollingUpdate}, + }, + } + + for k, tc := range testcases { + // extensions -> appsv1 + internal1 := &appsv1.DeploymentStrategy{} + if err := legacyscheme.Scheme.Convert(tc.deploymentStrategy1, internal1, nil); err != nil { + t.Errorf("%q - %q: unexpected error: %v", k, "extensions -> appsv1", err) + } + if !apiequality.Semantic.DeepEqual(internal1, tc.deploymentStrategy2) { + t.Errorf("%q - %q: expected\n\t%#v, got \n\t%#v", k, "extensions -> appsv1", tc.deploymentStrategy2, internal1) + } + + // appsv1 -> extensions + internal2 := &extensions.DeploymentStrategy{} + if err := legacyscheme.Scheme.Convert(tc.deploymentStrategy2, internal2, nil); err != nil { + t.Errorf("%q - %q: unexpected error: %v", k, "appsv1 -> extensions", err) + } + if !apiequality.Semantic.DeepEqual(internal2, tc.deploymentStrategy1) { + t.Errorf("%q - %q: expected\n\t%#v, got \n\t%#v", k, "appsv1 -> extensions", tc.deploymentStrategy1, internal2) + } + } +} + +func TestV1RollingUpdateDeploymentConversion(t *testing.T) { + nilIntStr := intstr.IntOrString{} + maxUnavailable := intstr.FromInt(2) + maxSurge := intstr.FromInt(2) + testcases := map[string]struct { + rollingUpdateDeployment1 *extensions.RollingUpdateDeployment + rollingUpdateDeployment2 *appsv1.RollingUpdateDeployment + }{ + "RollingUpdateDeployment Conversion 1": { + rollingUpdateDeployment1: &extensions.RollingUpdateDeployment{}, + rollingUpdateDeployment2: &appsv1.RollingUpdateDeployment{MaxUnavailable: &nilIntStr, MaxSurge: &nilIntStr}, + }, + "RollingUpdateDeployment Conversion 2": { + rollingUpdateDeployment1: &extensions.RollingUpdateDeployment{MaxUnavailable: maxUnavailable}, + rollingUpdateDeployment2: &appsv1.RollingUpdateDeployment{MaxUnavailable: &maxUnavailable, MaxSurge: &nilIntStr}, + }, + "RollingUpdateDeployment Conversion 3": { + rollingUpdateDeployment1: &extensions.RollingUpdateDeployment{MaxSurge: maxSurge}, + rollingUpdateDeployment2: &appsv1.RollingUpdateDeployment{MaxSurge: &maxSurge, MaxUnavailable: &nilIntStr}, + }, + "RollingUpdateDeployment Conversion 4": { + rollingUpdateDeployment1: &extensions.RollingUpdateDeployment{MaxUnavailable: maxUnavailable, MaxSurge: maxSurge}, + rollingUpdateDeployment2: &appsv1.RollingUpdateDeployment{MaxUnavailable: &maxUnavailable, MaxSurge: &maxSurge}, + }, + } + + for k, tc := range testcases { + // extensions -> appsv1 + internal1 := &appsv1.RollingUpdateDeployment{} + if err := legacyscheme.Scheme.Convert(tc.rollingUpdateDeployment1, internal1, nil); err != nil { + t.Errorf("%q - %q: unexpected error: %v", k, "extensions -> appsv1", err) + } + if !apiequality.Semantic.DeepEqual(internal1, tc.rollingUpdateDeployment2) { + t.Errorf("%q - %q: expected\n\t%#v, got \n\t%#v", k, "extensions -> appsv1", tc.rollingUpdateDeployment2, internal1) + } + + // appsv1 -> extensions + internal2 := &extensions.RollingUpdateDeployment{} + if err := legacyscheme.Scheme.Convert(tc.rollingUpdateDeployment2, internal2, nil); err != nil { + t.Errorf("%q - %q: unexpected error: %v", k, "appsv1 -> extensions", err) + } + if !apiequality.Semantic.DeepEqual(internal2, tc.rollingUpdateDeployment1) { + t.Errorf("%q - %q: expected\n\t%#v, got \n\t%#v", k, "appsv1 -> extensions", tc.rollingUpdateDeployment1, internal2) + } + } +} + +func TestV1ReplicaSetSpecConversion(t *testing.T) { + replicas := new(int32) + *replicas = 2 + matchExpressions := []metav1.LabelSelectorRequirement{ + {Key: "foo", Operator: metav1.LabelSelectorOpIn, Values: []string{"foo"}}, + } + matchLabels := map[string]string{"foo": "bar"} + selector := &metav1.LabelSelector{MatchLabels: matchLabels, MatchExpressions: matchExpressions} + + testcases := map[string]struct { + replicaset1 *extensions.ReplicaSetSpec + replicaset2 *appsv1.ReplicaSetSpec + }{ + "ReplicaSetSpec Conversion 1": { + replicaset1: &extensions.ReplicaSetSpec{ + Replicas: *replicas, + MinReadySeconds: 2, + Template: api.PodTemplateSpec{ + Spec: api.PodSpec{ + SecurityContext: new(api.PodSecurityContext), + }, + }, + }, + replicaset2: &appsv1.ReplicaSetSpec{ + Replicas: replicas, + MinReadySeconds: 2, + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + SecurityContext: new(v1.PodSecurityContext), + }, + }, + }, + }, + "ReplicaSetSpec Conversion 2": { + replicaset1: &extensions.ReplicaSetSpec{ + Replicas: *replicas, + Selector: selector, + Template: api.PodTemplateSpec{ + Spec: api.PodSpec{ + SecurityContext: new(api.PodSecurityContext), + }, + }, + }, + replicaset2: &appsv1.ReplicaSetSpec{ + Replicas: replicas, + Selector: selector, + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + SecurityContext: new(v1.PodSecurityContext), + }, + }, + }, + }, + } + + for k, tc := range testcases { + // extensions -> appsv1 + internal1 := &appsv1.ReplicaSetSpec{} + if err := legacyscheme.Scheme.Convert(tc.replicaset1, internal1, nil); err != nil { + t.Errorf("%q - %q: unexpected error: %v", k, "extensions -> appsv1", err) + } + + if !apiequality.Semantic.DeepEqual(internal1, tc.replicaset2) { + t.Errorf("%q - %q: expected\n\t%+v, got \n\t%+v", k, "extensions -> appsv1", tc.replicaset2, internal1) + } + + // appsv1 -> extensions + internal2 := &extensions.ReplicaSetSpec{} + if err := legacyscheme.Scheme.Convert(tc.replicaset2, internal2, nil); err != nil { + t.Errorf("%q - %q: unexpected error: %v", k, "appsv1 -> extensions", err) + } + if !apiequality.Semantic.DeepEqual(internal2, tc.replicaset1) { + t.Errorf("%q - %q: expected\n\t%+v, got \n\t%+v", k, "appsv1 -> extensions", tc.replicaset1, internal2) + } + } +} diff --git a/pkg/apis/apps/v1/defaults.go b/pkg/apis/apps/v1/defaults.go index 6803d038515..941a0c8e8d8 100644 --- a/pkg/apis/apps/v1/defaults.go +++ b/pkg/apis/apps/v1/defaults.go @@ -26,6 +26,49 @@ func addDefaultingFuncs(scheme *runtime.Scheme) error { return RegisterDefaults(scheme) } +// SetDefaults_Deployment sets additional defaults compared to its counterpart +// in extensions. These addons are: +// - MaxUnavailable during rolling update set to 25% (1 in extensions) +// - MaxSurge value during rolling update set to 25% (1 in extensions) +// - RevisionHistoryLimit set to 10 (not set in extensions) +// - ProgressDeadlineSeconds set to 600s (not set in extensions) +func SetDefaults_Deployment(obj *appsv1.Deployment) { + // Set DeploymentSpec.Replicas to 1 if it is not set. + if obj.Spec.Replicas == nil { + obj.Spec.Replicas = new(int32) + *obj.Spec.Replicas = 1 + } + strategy := &obj.Spec.Strategy + // Set default DeploymentStrategyType as RollingUpdate. + if strategy.Type == "" { + strategy.Type = appsv1.RollingUpdateDeploymentStrategyType + } + if strategy.Type == appsv1.RollingUpdateDeploymentStrategyType { + if strategy.RollingUpdate == nil { + rollingUpdate := appsv1.RollingUpdateDeployment{} + strategy.RollingUpdate = &rollingUpdate + } + if strategy.RollingUpdate.MaxUnavailable == nil { + // Set default MaxUnavailable as 25% by default. + maxUnavailable := intstr.FromString("25%") + strategy.RollingUpdate.MaxUnavailable = &maxUnavailable + } + if strategy.RollingUpdate.MaxSurge == nil { + // Set default MaxSurge as 25% by default. + maxSurge := intstr.FromString("25%") + strategy.RollingUpdate.MaxSurge = &maxSurge + } + } + if obj.Spec.RevisionHistoryLimit == nil { + obj.Spec.RevisionHistoryLimit = new(int32) + *obj.Spec.RevisionHistoryLimit = 10 + } + if obj.Spec.ProgressDeadlineSeconds == nil { + obj.Spec.ProgressDeadlineSeconds = new(int32) + *obj.Spec.ProgressDeadlineSeconds = 600 + } +} + func SetDefaults_DaemonSet(obj *appsv1.DaemonSet) { updateStrategy := &obj.Spec.UpdateStrategy if updateStrategy.Type == "" { @@ -47,3 +90,38 @@ func SetDefaults_DaemonSet(obj *appsv1.DaemonSet) { *obj.Spec.RevisionHistoryLimit = 10 } } + +func SetDefaults_StatefulSet(obj *appsv1.StatefulSet) { + if len(obj.Spec.PodManagementPolicy) == 0 { + obj.Spec.PodManagementPolicy = appsv1.OrderedReadyPodManagement + } + + if obj.Spec.UpdateStrategy.Type == "" { + obj.Spec.UpdateStrategy.Type = appsv1.RollingUpdateStatefulSetStrategyType + + // UpdateStrategy.RollingUpdate will take default values below. + obj.Spec.UpdateStrategy.RollingUpdate = &appsv1.RollingUpdateStatefulSetStrategy{} + } + + if obj.Spec.UpdateStrategy.Type == appsv1.RollingUpdateStatefulSetStrategyType && + obj.Spec.UpdateStrategy.RollingUpdate != nil && + obj.Spec.UpdateStrategy.RollingUpdate.Partition == nil { + obj.Spec.UpdateStrategy.RollingUpdate.Partition = new(int32) + *obj.Spec.UpdateStrategy.RollingUpdate.Partition = 0 + } + + if obj.Spec.Replicas == nil { + obj.Spec.Replicas = new(int32) + *obj.Spec.Replicas = 1 + } + if obj.Spec.RevisionHistoryLimit == nil { + obj.Spec.RevisionHistoryLimit = new(int32) + *obj.Spec.RevisionHistoryLimit = 10 + } +} +func SetDefaults_ReplicaSet(obj *appsv1.ReplicaSet) { + if obj.Spec.Replicas == nil { + obj.Spec.Replicas = new(int32) + *obj.Spec.Replicas = 1 + } +} diff --git a/pkg/apis/apps/v1/defaults_test.go b/pkg/apis/apps/v1/defaults_test.go index 9da68b4f8e8..8050ea4ed61 100644 --- a/pkg/apis/apps/v1/defaults_test.go +++ b/pkg/apis/apps/v1/defaults_test.go @@ -23,13 +23,15 @@ import ( appsv1 "k8s.io/api/apps/v1" "k8s.io/api/core/v1" 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/runtime" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/kubernetes/pkg/api" - _ "k8s.io/kubernetes/pkg/api/install" + "k8s.io/kubernetes/pkg/api/legacyscheme" _ "k8s.io/kubernetes/pkg/apis/apps/install" . "k8s.io/kubernetes/pkg/apis/apps/v1" + api "k8s.io/kubernetes/pkg/apis/core" + _ "k8s.io/kubernetes/pkg/apis/core/install" ) func TestSetDefaultDaemonSetSpec(t *testing.T) { @@ -166,19 +168,395 @@ func TestSetDefaultDaemonSetSpec(t *testing.T) { } } +func TestSetDefaultStatefulSet(t *testing.T) { + defaultLabels := map[string]string{"foo": "bar"} + var defaultPartition int32 = 0 + var defaultReplicas int32 = 1 + + period := int64(v1.DefaultTerminationGracePeriodSeconds) + defaultTemplate := v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + DNSPolicy: v1.DNSClusterFirst, + RestartPolicy: v1.RestartPolicyAlways, + SecurityContext: &v1.PodSecurityContext{}, + TerminationGracePeriodSeconds: &period, + SchedulerName: api.DefaultSchedulerName, + }, + ObjectMeta: metav1.ObjectMeta{ + Labels: defaultLabels, + }, + } + + tests := []struct { + original *appsv1.StatefulSet + expected *appsv1.StatefulSet + }{ + { // labels and default update strategy + original: &appsv1.StatefulSet{ + Spec: appsv1.StatefulSetSpec{ + Template: defaultTemplate, + }, + }, + expected: &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Labels: defaultLabels, + }, + Spec: appsv1.StatefulSetSpec{ + Replicas: &defaultReplicas, + Template: defaultTemplate, + PodManagementPolicy: appsv1.OrderedReadyPodManagement, + UpdateStrategy: appsv1.StatefulSetUpdateStrategy{ + Type: appsv1.RollingUpdateStatefulSetStrategyType, + RollingUpdate: &appsv1.RollingUpdateStatefulSetStrategy{ + Partition: &defaultPartition, + }, + }, + RevisionHistoryLimit: newInt32(10), + }, + }, + }, + { // Alternate update strategy + original: &appsv1.StatefulSet{ + Spec: appsv1.StatefulSetSpec{ + Template: defaultTemplate, + UpdateStrategy: appsv1.StatefulSetUpdateStrategy{ + Type: appsv1.OnDeleteStatefulSetStrategyType, + }, + }, + }, + expected: &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Labels: defaultLabels, + }, + Spec: appsv1.StatefulSetSpec{ + Replicas: &defaultReplicas, + Template: defaultTemplate, + PodManagementPolicy: appsv1.OrderedReadyPodManagement, + UpdateStrategy: appsv1.StatefulSetUpdateStrategy{ + Type: appsv1.OnDeleteStatefulSetStrategyType, + }, + RevisionHistoryLimit: newInt32(10), + }, + }, + }, + { // Parallel pod management policy. + original: &appsv1.StatefulSet{ + Spec: appsv1.StatefulSetSpec{ + Template: defaultTemplate, + PodManagementPolicy: appsv1.ParallelPodManagement, + }, + }, + expected: &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Labels: defaultLabels, + }, + Spec: appsv1.StatefulSetSpec{ + Replicas: &defaultReplicas, + Template: defaultTemplate, + PodManagementPolicy: appsv1.ParallelPodManagement, + UpdateStrategy: appsv1.StatefulSetUpdateStrategy{ + Type: appsv1.RollingUpdateStatefulSetStrategyType, + RollingUpdate: &appsv1.RollingUpdateStatefulSetStrategy{ + Partition: &defaultPartition, + }, + }, + RevisionHistoryLimit: newInt32(10), + }, + }, + }, + } + + for i, test := range tests { + original := test.original + expected := test.expected + obj2 := roundTrip(t, runtime.Object(original)) + got, ok := obj2.(*appsv1.StatefulSet) + if !ok { + t.Errorf("(%d) unexpected object: %v", i, got) + t.FailNow() + } + 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 TestSetDefaultDeployment(t *testing.T) { + defaultIntOrString := intstr.FromString("25%") + differentIntOrString := intstr.FromInt(5) + period := int64(v1.DefaultTerminationGracePeriodSeconds) + defaultTemplate := v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + DNSPolicy: v1.DNSClusterFirst, + RestartPolicy: v1.RestartPolicyAlways, + SecurityContext: &v1.PodSecurityContext{}, + TerminationGracePeriodSeconds: &period, + SchedulerName: api.DefaultSchedulerName, + }, + } + tests := []struct { + original *appsv1.Deployment + expected *appsv1.Deployment + }{ + { + original: &appsv1.Deployment{}, + expected: &appsv1.Deployment{ + Spec: appsv1.DeploymentSpec{ + Replicas: newInt32(1), + Strategy: appsv1.DeploymentStrategy{ + Type: appsv1.RollingUpdateDeploymentStrategyType, + RollingUpdate: &appsv1.RollingUpdateDeployment{ + MaxSurge: &defaultIntOrString, + MaxUnavailable: &defaultIntOrString, + }, + }, + RevisionHistoryLimit: newInt32(10), + ProgressDeadlineSeconds: newInt32(600), + Template: defaultTemplate, + }, + }, + }, + { + original: &appsv1.Deployment{ + Spec: appsv1.DeploymentSpec{ + Replicas: newInt32(5), + Strategy: appsv1.DeploymentStrategy{ + RollingUpdate: &appsv1.RollingUpdateDeployment{ + MaxSurge: &differentIntOrString, + }, + }, + }, + }, + expected: &appsv1.Deployment{ + Spec: appsv1.DeploymentSpec{ + Replicas: newInt32(5), + Strategy: appsv1.DeploymentStrategy{ + Type: appsv1.RollingUpdateDeploymentStrategyType, + RollingUpdate: &appsv1.RollingUpdateDeployment{ + MaxSurge: &differentIntOrString, + MaxUnavailable: &defaultIntOrString, + }, + }, + RevisionHistoryLimit: newInt32(10), + ProgressDeadlineSeconds: newInt32(600), + Template: defaultTemplate, + }, + }, + }, + { + original: &appsv1.Deployment{ + Spec: appsv1.DeploymentSpec{ + Replicas: newInt32(3), + Strategy: appsv1.DeploymentStrategy{ + Type: appsv1.RollingUpdateDeploymentStrategyType, + RollingUpdate: nil, + }, + }, + }, + expected: &appsv1.Deployment{ + Spec: appsv1.DeploymentSpec{ + Replicas: newInt32(3), + Strategy: appsv1.DeploymentStrategy{ + Type: appsv1.RollingUpdateDeploymentStrategyType, + RollingUpdate: &appsv1.RollingUpdateDeployment{ + MaxSurge: &defaultIntOrString, + MaxUnavailable: &defaultIntOrString, + }, + }, + RevisionHistoryLimit: newInt32(10), + ProgressDeadlineSeconds: newInt32(600), + Template: defaultTemplate, + }, + }, + }, + { + original: &appsv1.Deployment{ + Spec: appsv1.DeploymentSpec{ + Replicas: newInt32(5), + Strategy: appsv1.DeploymentStrategy{ + Type: appsv1.RecreateDeploymentStrategyType, + }, + RevisionHistoryLimit: newInt32(0), + }, + }, + expected: &appsv1.Deployment{ + Spec: appsv1.DeploymentSpec{ + Replicas: newInt32(5), + Strategy: appsv1.DeploymentStrategy{ + Type: appsv1.RecreateDeploymentStrategyType, + }, + RevisionHistoryLimit: newInt32(0), + ProgressDeadlineSeconds: newInt32(600), + Template: defaultTemplate, + }, + }, + }, + { + original: &appsv1.Deployment{ + Spec: appsv1.DeploymentSpec{ + Replicas: newInt32(5), + Strategy: appsv1.DeploymentStrategy{ + Type: appsv1.RecreateDeploymentStrategyType, + }, + ProgressDeadlineSeconds: newInt32(30), + RevisionHistoryLimit: newInt32(2), + }, + }, + expected: &appsv1.Deployment{ + Spec: appsv1.DeploymentSpec{ + Replicas: newInt32(5), + Strategy: appsv1.DeploymentStrategy{ + Type: appsv1.RecreateDeploymentStrategyType, + }, + ProgressDeadlineSeconds: newInt32(30), + RevisionHistoryLimit: newInt32(2), + Template: defaultTemplate, + }, + }, + }, + } + + for _, test := range tests { + original := test.original + expected := test.expected + obj2 := roundTrip(t, runtime.Object(original)) + got, ok := obj2.(*appsv1.Deployment) + if !ok { + t.Errorf("unexpected object: %v", got) + t.FailNow() + } + if !apiequality.Semantic.DeepEqual(got.Spec, expected.Spec) { + t.Errorf("object mismatch!\nexpected:\n\t%+v\ngot:\n\t%+v", got.Spec, expected.Spec) + } + } +} + +func TestDefaultDeploymentAvailability(t *testing.T) { + d := roundTrip(t, runtime.Object(&appsv1.Deployment{})).(*appsv1.Deployment) + + maxUnavailable, err := intstr.GetValueFromIntOrPercent(d.Spec.Strategy.RollingUpdate.MaxUnavailable, int(*(d.Spec.Replicas)), false) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if *(d.Spec.Replicas)-int32(maxUnavailable) <= 0 { + t.Fatalf("the default value of maxUnavailable can lead to no active replicas during rolling update") + } +} + +func TestSetDefaultReplicaSetReplicas(t *testing.T) { + tests := []struct { + rs appsv1.ReplicaSet + expectReplicas int32 + }{ + { + rs: appsv1.ReplicaSet{ + Spec: appsv1.ReplicaSetSpec{ + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "foo": "bar", + }, + }, + }, + }, + }, + expectReplicas: 1, + }, + { + rs: appsv1.ReplicaSet{ + Spec: appsv1.ReplicaSetSpec{ + Replicas: newInt32(0), + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "foo": "bar", + }, + }, + }, + }, + }, + expectReplicas: 0, + }, + { + rs: appsv1.ReplicaSet{ + Spec: appsv1.ReplicaSetSpec{ + Replicas: newInt32(3), + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "foo": "bar", + }, + }, + }, + }, + }, + expectReplicas: 3, + }, + } + + for _, test := range tests { + rs := &test.rs + obj2 := roundTrip(t, runtime.Object(rs)) + rs2, ok := obj2.(*appsv1.ReplicaSet) + if !ok { + t.Errorf("unexpected object: %v", rs2) + t.FailNow() + } + if rs2.Spec.Replicas == nil { + t.Errorf("unexpected nil Replicas") + } else if test.expectReplicas != *rs2.Spec.Replicas { + t.Errorf("expected: %d replicas, got: %d", test.expectReplicas, *rs2.Spec.Replicas) + } + } +} + +func TestDefaultRequestIsNotSetForReplicaSet(t *testing.T) { + s := v1.PodSpec{} + s.Containers = []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + }, + }, + }, + } + rs := &appsv1.ReplicaSet{ + Spec: appsv1.ReplicaSetSpec{ + Replicas: newInt32(3), + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "foo": "bar", + }, + }, + Spec: s, + }, + }, + } + output := roundTrip(t, runtime.Object(rs)) + rs2 := output.(*appsv1.ReplicaSet) + defaultRequest := rs2.Spec.Template.Spec.Containers[0].Resources.Requests + requestValue := defaultRequest[v1.ResourceCPU] + if requestValue.String() != "0" { + t.Errorf("Expected 0 request value, got: %s", requestValue.String()) + } +} + func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { - data, err := runtime.Encode(api.Codecs.LegacyCodec(SchemeGroupVersion), obj) + 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(api.Codecs.UniversalDecoder(), data) + 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 = api.Scheme.Convert(obj2, obj3, nil) + err = legacyscheme.Scheme.Convert(obj2, obj3, nil) if err != nil { t.Errorf("%v\nSource: %#v", err, obj2) return nil diff --git a/pkg/apis/apps/v1/doc.go b/pkg/apis/apps/v1/doc.go index 771f2829623..b70ddca6da8 100644 --- a/pkg/apis/apps/v1/doc.go +++ b/pkg/apis/apps/v1/doc.go @@ -16,7 +16,7 @@ limitations under the License. // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/apps // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/extensions -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/apps/v1 +// +k8s:conversion-gen-external-types=k8s.io/api/apps/v1 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/apps/v1 diff --git a/pkg/apis/apps/v1/zz_generated.conversion.go b/pkg/apis/apps/v1/zz_generated.conversion.go index a93ad57d0fd..62aaaf390e5 100644 --- a/pkg/apis/apps/v1/zz_generated.conversion.go +++ b/pkg/apis/apps/v1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,13 +21,17 @@ limitations under the License. package v1 import ( + unsafe "unsafe" + v1 "k8s.io/api/apps/v1" + core_v1 "k8s.io/api/core/v1" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - api_v1 "k8s.io/kubernetes/pkg/api/v1" + apps "k8s.io/kubernetes/pkg/apis/apps" + core "k8s.io/kubernetes/pkg/apis/core" + apis_core_v1 "k8s.io/kubernetes/pkg/apis/core/v1" extensions "k8s.io/kubernetes/pkg/apis/extensions" - unsafe "unsafe" ) func init() { @@ -38,8 +42,14 @@ func init() { // Public to allow building arbitrary schemes. func RegisterConversions(scheme *runtime.Scheme) error { return scheme.AddGeneratedConversionFuncs( + Convert_v1_ControllerRevision_To_apps_ControllerRevision, + Convert_apps_ControllerRevision_To_v1_ControllerRevision, + Convert_v1_ControllerRevisionList_To_apps_ControllerRevisionList, + Convert_apps_ControllerRevisionList_To_v1_ControllerRevisionList, Convert_v1_DaemonSet_To_extensions_DaemonSet, Convert_extensions_DaemonSet_To_v1_DaemonSet, + Convert_v1_DaemonSetCondition_To_extensions_DaemonSetCondition, + Convert_extensions_DaemonSetCondition_To_v1_DaemonSetCondition, Convert_v1_DaemonSetList_To_extensions_DaemonSetList, Convert_extensions_DaemonSetList_To_v1_DaemonSetList, Convert_v1_DaemonSetSpec_To_extensions_DaemonSetSpec, @@ -48,11 +58,119 @@ func RegisterConversions(scheme *runtime.Scheme) error { Convert_extensions_DaemonSetStatus_To_v1_DaemonSetStatus, Convert_v1_DaemonSetUpdateStrategy_To_extensions_DaemonSetUpdateStrategy, Convert_extensions_DaemonSetUpdateStrategy_To_v1_DaemonSetUpdateStrategy, + Convert_v1_Deployment_To_extensions_Deployment, + Convert_extensions_Deployment_To_v1_Deployment, + Convert_v1_DeploymentCondition_To_extensions_DeploymentCondition, + Convert_extensions_DeploymentCondition_To_v1_DeploymentCondition, + Convert_v1_DeploymentList_To_extensions_DeploymentList, + Convert_extensions_DeploymentList_To_v1_DeploymentList, + Convert_v1_DeploymentSpec_To_extensions_DeploymentSpec, + Convert_extensions_DeploymentSpec_To_v1_DeploymentSpec, + Convert_v1_DeploymentStatus_To_extensions_DeploymentStatus, + Convert_extensions_DeploymentStatus_To_v1_DeploymentStatus, + Convert_v1_DeploymentStrategy_To_extensions_DeploymentStrategy, + Convert_extensions_DeploymentStrategy_To_v1_DeploymentStrategy, + Convert_v1_ReplicaSet_To_extensions_ReplicaSet, + Convert_extensions_ReplicaSet_To_v1_ReplicaSet, + Convert_v1_ReplicaSetCondition_To_extensions_ReplicaSetCondition, + Convert_extensions_ReplicaSetCondition_To_v1_ReplicaSetCondition, + Convert_v1_ReplicaSetList_To_extensions_ReplicaSetList, + Convert_extensions_ReplicaSetList_To_v1_ReplicaSetList, + Convert_v1_ReplicaSetSpec_To_extensions_ReplicaSetSpec, + Convert_extensions_ReplicaSetSpec_To_v1_ReplicaSetSpec, + Convert_v1_ReplicaSetStatus_To_extensions_ReplicaSetStatus, + Convert_extensions_ReplicaSetStatus_To_v1_ReplicaSetStatus, Convert_v1_RollingUpdateDaemonSet_To_extensions_RollingUpdateDaemonSet, Convert_extensions_RollingUpdateDaemonSet_To_v1_RollingUpdateDaemonSet, + Convert_v1_RollingUpdateDeployment_To_extensions_RollingUpdateDeployment, + Convert_extensions_RollingUpdateDeployment_To_v1_RollingUpdateDeployment, + Convert_v1_RollingUpdateStatefulSetStrategy_To_apps_RollingUpdateStatefulSetStrategy, + Convert_apps_RollingUpdateStatefulSetStrategy_To_v1_RollingUpdateStatefulSetStrategy, + Convert_v1_StatefulSet_To_apps_StatefulSet, + Convert_apps_StatefulSet_To_v1_StatefulSet, + Convert_v1_StatefulSetCondition_To_apps_StatefulSetCondition, + Convert_apps_StatefulSetCondition_To_v1_StatefulSetCondition, + Convert_v1_StatefulSetList_To_apps_StatefulSetList, + Convert_apps_StatefulSetList_To_v1_StatefulSetList, + Convert_v1_StatefulSetSpec_To_apps_StatefulSetSpec, + Convert_apps_StatefulSetSpec_To_v1_StatefulSetSpec, + Convert_v1_StatefulSetStatus_To_apps_StatefulSetStatus, + Convert_apps_StatefulSetStatus_To_v1_StatefulSetStatus, + Convert_v1_StatefulSetUpdateStrategy_To_apps_StatefulSetUpdateStrategy, + Convert_apps_StatefulSetUpdateStrategy_To_v1_StatefulSetUpdateStrategy, ) } +func autoConvert_v1_ControllerRevision_To_apps_ControllerRevision(in *v1.ControllerRevision, out *apps.ControllerRevision, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&in.Data, &out.Data, s); err != nil { + return err + } + out.Revision = in.Revision + return nil +} + +// Convert_v1_ControllerRevision_To_apps_ControllerRevision is an autogenerated conversion function. +func Convert_v1_ControllerRevision_To_apps_ControllerRevision(in *v1.ControllerRevision, out *apps.ControllerRevision, s conversion.Scope) error { + return autoConvert_v1_ControllerRevision_To_apps_ControllerRevision(in, out, s) +} + +func autoConvert_apps_ControllerRevision_To_v1_ControllerRevision(in *apps.ControllerRevision, out *v1.ControllerRevision, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&in.Data, &out.Data, s); err != nil { + return err + } + out.Revision = in.Revision + return nil +} + +// Convert_apps_ControllerRevision_To_v1_ControllerRevision is an autogenerated conversion function. +func Convert_apps_ControllerRevision_To_v1_ControllerRevision(in *apps.ControllerRevision, out *v1.ControllerRevision, s conversion.Scope) error { + return autoConvert_apps_ControllerRevision_To_v1_ControllerRevision(in, out, s) +} + +func autoConvert_v1_ControllerRevisionList_To_apps_ControllerRevisionList(in *v1.ControllerRevisionList, out *apps.ControllerRevisionList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]apps.ControllerRevision, len(*in)) + for i := range *in { + if err := Convert_v1_ControllerRevision_To_apps_ControllerRevision(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_v1_ControllerRevisionList_To_apps_ControllerRevisionList is an autogenerated conversion function. +func Convert_v1_ControllerRevisionList_To_apps_ControllerRevisionList(in *v1.ControllerRevisionList, out *apps.ControllerRevisionList, s conversion.Scope) error { + return autoConvert_v1_ControllerRevisionList_To_apps_ControllerRevisionList(in, out, s) +} + +func autoConvert_apps_ControllerRevisionList_To_v1_ControllerRevisionList(in *apps.ControllerRevisionList, out *v1.ControllerRevisionList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1.ControllerRevision, len(*in)) + for i := range *in { + if err := Convert_apps_ControllerRevision_To_v1_ControllerRevision(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_apps_ControllerRevisionList_To_v1_ControllerRevisionList is an autogenerated conversion function. +func Convert_apps_ControllerRevisionList_To_v1_ControllerRevisionList(in *apps.ControllerRevisionList, out *v1.ControllerRevisionList, s conversion.Scope) error { + return autoConvert_apps_ControllerRevisionList_To_v1_ControllerRevisionList(in, out, s) +} + func autoConvert_v1_DaemonSet_To_extensions_DaemonSet(in *v1.DaemonSet, out *extensions.DaemonSet, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if err := Convert_v1_DaemonSetSpec_To_extensions_DaemonSetSpec(&in.Spec, &out.Spec, s); err != nil { @@ -75,6 +193,34 @@ func autoConvert_extensions_DaemonSet_To_v1_DaemonSet(in *extensions.DaemonSet, return nil } +func autoConvert_v1_DaemonSetCondition_To_extensions_DaemonSetCondition(in *v1.DaemonSetCondition, out *extensions.DaemonSetCondition, s conversion.Scope) error { + out.Type = extensions.DaemonSetConditionType(in.Type) + out.Status = core.ConditionStatus(in.Status) + out.LastTransitionTime = in.LastTransitionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_v1_DaemonSetCondition_To_extensions_DaemonSetCondition is an autogenerated conversion function. +func Convert_v1_DaemonSetCondition_To_extensions_DaemonSetCondition(in *v1.DaemonSetCondition, out *extensions.DaemonSetCondition, s conversion.Scope) error { + return autoConvert_v1_DaemonSetCondition_To_extensions_DaemonSetCondition(in, out, s) +} + +func autoConvert_extensions_DaemonSetCondition_To_v1_DaemonSetCondition(in *extensions.DaemonSetCondition, out *v1.DaemonSetCondition, s conversion.Scope) error { + out.Type = v1.DaemonSetConditionType(in.Type) + out.Status = core_v1.ConditionStatus(in.Status) + out.LastTransitionTime = in.LastTransitionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_extensions_DaemonSetCondition_To_v1_DaemonSetCondition is an autogenerated conversion function. +func Convert_extensions_DaemonSetCondition_To_v1_DaemonSetCondition(in *extensions.DaemonSetCondition, out *v1.DaemonSetCondition, s conversion.Scope) error { + return autoConvert_extensions_DaemonSetCondition_To_v1_DaemonSetCondition(in, out, s) +} + func autoConvert_v1_DaemonSetList_To_extensions_DaemonSetList(in *v1.DaemonSetList, out *extensions.DaemonSetList, s conversion.Scope) error { out.ListMeta = in.ListMeta if in.Items != nil { @@ -119,7 +265,7 @@ func Convert_extensions_DaemonSetList_To_v1_DaemonSetList(in *extensions.DaemonS func autoConvert_v1_DaemonSetSpec_To_extensions_DaemonSetSpec(in *v1.DaemonSetSpec, out *extensions.DaemonSetSpec, s conversion.Scope) error { out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) - if err := api_v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := apis_core_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if err := Convert_v1_DaemonSetUpdateStrategy_To_extensions_DaemonSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, s); err != nil { @@ -132,7 +278,7 @@ func autoConvert_v1_DaemonSetSpec_To_extensions_DaemonSetSpec(in *v1.DaemonSetSp func autoConvert_extensions_DaemonSetSpec_To_v1_DaemonSetSpec(in *extensions.DaemonSetSpec, out *v1.DaemonSetSpec, s conversion.Scope) error { out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) - if err := api_v1.Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := apis_core_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if err := Convert_extensions_DaemonSetUpdateStrategy_To_v1_DaemonSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, s); err != nil { @@ -154,6 +300,7 @@ func autoConvert_v1_DaemonSetStatus_To_extensions_DaemonSetStatus(in *v1.DaemonS out.NumberAvailable = in.NumberAvailable out.NumberUnavailable = in.NumberUnavailable out.CollisionCount = (*int32)(unsafe.Pointer(in.CollisionCount)) + out.Conditions = *(*[]extensions.DaemonSetCondition)(unsafe.Pointer(&in.Conditions)) return nil } @@ -172,6 +319,7 @@ func autoConvert_extensions_DaemonSetStatus_To_v1_DaemonSetStatus(in *extensions out.NumberAvailable = in.NumberAvailable out.NumberUnavailable = in.NumberUnavailable out.CollisionCount = (*int32)(unsafe.Pointer(in.CollisionCount)) + out.Conditions = *(*[]v1.DaemonSetCondition)(unsafe.Pointer(&in.Conditions)) return nil } @@ -208,6 +356,355 @@ func autoConvert_extensions_DaemonSetUpdateStrategy_To_v1_DaemonSetUpdateStrateg return nil } +func autoConvert_v1_Deployment_To_extensions_Deployment(in *v1.Deployment, out *extensions.Deployment, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_DeploymentSpec_To_extensions_DeploymentSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1_DeploymentStatus_To_extensions_DeploymentStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +func autoConvert_extensions_Deployment_To_v1_Deployment(in *extensions.Deployment, out *v1.Deployment, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_extensions_DeploymentSpec_To_v1_DeploymentSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_extensions_DeploymentStatus_To_v1_DeploymentStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +func autoConvert_v1_DeploymentCondition_To_extensions_DeploymentCondition(in *v1.DeploymentCondition, out *extensions.DeploymentCondition, s conversion.Scope) error { + out.Type = extensions.DeploymentConditionType(in.Type) + out.Status = core.ConditionStatus(in.Status) + out.LastUpdateTime = in.LastUpdateTime + out.LastTransitionTime = in.LastTransitionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_v1_DeploymentCondition_To_extensions_DeploymentCondition is an autogenerated conversion function. +func Convert_v1_DeploymentCondition_To_extensions_DeploymentCondition(in *v1.DeploymentCondition, out *extensions.DeploymentCondition, s conversion.Scope) error { + return autoConvert_v1_DeploymentCondition_To_extensions_DeploymentCondition(in, out, s) +} + +func autoConvert_extensions_DeploymentCondition_To_v1_DeploymentCondition(in *extensions.DeploymentCondition, out *v1.DeploymentCondition, s conversion.Scope) error { + out.Type = v1.DeploymentConditionType(in.Type) + out.Status = core_v1.ConditionStatus(in.Status) + out.LastUpdateTime = in.LastUpdateTime + out.LastTransitionTime = in.LastTransitionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_extensions_DeploymentCondition_To_v1_DeploymentCondition is an autogenerated conversion function. +func Convert_extensions_DeploymentCondition_To_v1_DeploymentCondition(in *extensions.DeploymentCondition, out *v1.DeploymentCondition, s conversion.Scope) error { + return autoConvert_extensions_DeploymentCondition_To_v1_DeploymentCondition(in, out, s) +} + +func autoConvert_v1_DeploymentList_To_extensions_DeploymentList(in *v1.DeploymentList, out *extensions.DeploymentList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]extensions.Deployment, len(*in)) + for i := range *in { + if err := Convert_v1_Deployment_To_extensions_Deployment(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_v1_DeploymentList_To_extensions_DeploymentList is an autogenerated conversion function. +func Convert_v1_DeploymentList_To_extensions_DeploymentList(in *v1.DeploymentList, out *extensions.DeploymentList, s conversion.Scope) error { + return autoConvert_v1_DeploymentList_To_extensions_DeploymentList(in, out, s) +} + +func autoConvert_extensions_DeploymentList_To_v1_DeploymentList(in *extensions.DeploymentList, out *v1.DeploymentList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1.Deployment, len(*in)) + for i := range *in { + if err := Convert_extensions_Deployment_To_v1_Deployment(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_extensions_DeploymentList_To_v1_DeploymentList is an autogenerated conversion function. +func Convert_extensions_DeploymentList_To_v1_DeploymentList(in *extensions.DeploymentList, out *v1.DeploymentList, s conversion.Scope) error { + return autoConvert_extensions_DeploymentList_To_v1_DeploymentList(in, out, s) +} + +func autoConvert_v1_DeploymentSpec_To_extensions_DeploymentSpec(in *v1.DeploymentSpec, out *extensions.DeploymentSpec, s conversion.Scope) error { + if err := meta_v1.Convert_Pointer_int32_To_int32(&in.Replicas, &out.Replicas, s); err != nil { + return err + } + out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) + if err := apis_core_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err + } + if err := Convert_v1_DeploymentStrategy_To_extensions_DeploymentStrategy(&in.Strategy, &out.Strategy, s); err != nil { + return err + } + out.MinReadySeconds = in.MinReadySeconds + out.RevisionHistoryLimit = (*int32)(unsafe.Pointer(in.RevisionHistoryLimit)) + out.Paused = in.Paused + out.ProgressDeadlineSeconds = (*int32)(unsafe.Pointer(in.ProgressDeadlineSeconds)) + return nil +} + +func autoConvert_extensions_DeploymentSpec_To_v1_DeploymentSpec(in *extensions.DeploymentSpec, out *v1.DeploymentSpec, s conversion.Scope) error { + if err := meta_v1.Convert_int32_To_Pointer_int32(&in.Replicas, &out.Replicas, s); err != nil { + return err + } + out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) + if err := apis_core_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err + } + if err := Convert_extensions_DeploymentStrategy_To_v1_DeploymentStrategy(&in.Strategy, &out.Strategy, s); err != nil { + return err + } + out.MinReadySeconds = in.MinReadySeconds + out.RevisionHistoryLimit = (*int32)(unsafe.Pointer(in.RevisionHistoryLimit)) + out.Paused = in.Paused + // WARNING: in.RollbackTo requires manual conversion: does not exist in peer-type + out.ProgressDeadlineSeconds = (*int32)(unsafe.Pointer(in.ProgressDeadlineSeconds)) + return nil +} + +func autoConvert_v1_DeploymentStatus_To_extensions_DeploymentStatus(in *v1.DeploymentStatus, out *extensions.DeploymentStatus, s conversion.Scope) error { + out.ObservedGeneration = in.ObservedGeneration + out.Replicas = in.Replicas + out.UpdatedReplicas = in.UpdatedReplicas + out.ReadyReplicas = in.ReadyReplicas + out.AvailableReplicas = in.AvailableReplicas + out.UnavailableReplicas = in.UnavailableReplicas + out.Conditions = *(*[]extensions.DeploymentCondition)(unsafe.Pointer(&in.Conditions)) + out.CollisionCount = (*int32)(unsafe.Pointer(in.CollisionCount)) + return nil +} + +// Convert_v1_DeploymentStatus_To_extensions_DeploymentStatus is an autogenerated conversion function. +func Convert_v1_DeploymentStatus_To_extensions_DeploymentStatus(in *v1.DeploymentStatus, out *extensions.DeploymentStatus, s conversion.Scope) error { + return autoConvert_v1_DeploymentStatus_To_extensions_DeploymentStatus(in, out, s) +} + +func autoConvert_extensions_DeploymentStatus_To_v1_DeploymentStatus(in *extensions.DeploymentStatus, out *v1.DeploymentStatus, s conversion.Scope) error { + out.ObservedGeneration = in.ObservedGeneration + out.Replicas = in.Replicas + out.UpdatedReplicas = in.UpdatedReplicas + out.ReadyReplicas = in.ReadyReplicas + out.AvailableReplicas = in.AvailableReplicas + out.UnavailableReplicas = in.UnavailableReplicas + out.Conditions = *(*[]v1.DeploymentCondition)(unsafe.Pointer(&in.Conditions)) + out.CollisionCount = (*int32)(unsafe.Pointer(in.CollisionCount)) + return nil +} + +// Convert_extensions_DeploymentStatus_To_v1_DeploymentStatus is an autogenerated conversion function. +func Convert_extensions_DeploymentStatus_To_v1_DeploymentStatus(in *extensions.DeploymentStatus, out *v1.DeploymentStatus, s conversion.Scope) error { + return autoConvert_extensions_DeploymentStatus_To_v1_DeploymentStatus(in, out, s) +} + +func autoConvert_v1_DeploymentStrategy_To_extensions_DeploymentStrategy(in *v1.DeploymentStrategy, out *extensions.DeploymentStrategy, s conversion.Scope) error { + out.Type = extensions.DeploymentStrategyType(in.Type) + if in.RollingUpdate != nil { + in, out := &in.RollingUpdate, &out.RollingUpdate + *out = new(extensions.RollingUpdateDeployment) + if err := Convert_v1_RollingUpdateDeployment_To_extensions_RollingUpdateDeployment(*in, *out, s); err != nil { + return err + } + } else { + out.RollingUpdate = nil + } + return nil +} + +func autoConvert_extensions_DeploymentStrategy_To_v1_DeploymentStrategy(in *extensions.DeploymentStrategy, out *v1.DeploymentStrategy, s conversion.Scope) error { + out.Type = v1.DeploymentStrategyType(in.Type) + if in.RollingUpdate != nil { + in, out := &in.RollingUpdate, &out.RollingUpdate + *out = new(v1.RollingUpdateDeployment) + if err := Convert_extensions_RollingUpdateDeployment_To_v1_RollingUpdateDeployment(*in, *out, s); err != nil { + return err + } + } else { + out.RollingUpdate = nil + } + return nil +} + +func autoConvert_v1_ReplicaSet_To_extensions_ReplicaSet(in *v1.ReplicaSet, out *extensions.ReplicaSet, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_ReplicaSetSpec_To_extensions_ReplicaSetSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1_ReplicaSetStatus_To_extensions_ReplicaSetStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1_ReplicaSet_To_extensions_ReplicaSet is an autogenerated conversion function. +func Convert_v1_ReplicaSet_To_extensions_ReplicaSet(in *v1.ReplicaSet, out *extensions.ReplicaSet, s conversion.Scope) error { + return autoConvert_v1_ReplicaSet_To_extensions_ReplicaSet(in, out, s) +} + +func autoConvert_extensions_ReplicaSet_To_v1_ReplicaSet(in *extensions.ReplicaSet, out *v1.ReplicaSet, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_extensions_ReplicaSetSpec_To_v1_ReplicaSetSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_extensions_ReplicaSetStatus_To_v1_ReplicaSetStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_extensions_ReplicaSet_To_v1_ReplicaSet is an autogenerated conversion function. +func Convert_extensions_ReplicaSet_To_v1_ReplicaSet(in *extensions.ReplicaSet, out *v1.ReplicaSet, s conversion.Scope) error { + return autoConvert_extensions_ReplicaSet_To_v1_ReplicaSet(in, out, s) +} + +func autoConvert_v1_ReplicaSetCondition_To_extensions_ReplicaSetCondition(in *v1.ReplicaSetCondition, out *extensions.ReplicaSetCondition, s conversion.Scope) error { + out.Type = extensions.ReplicaSetConditionType(in.Type) + out.Status = core.ConditionStatus(in.Status) + out.LastTransitionTime = in.LastTransitionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_v1_ReplicaSetCondition_To_extensions_ReplicaSetCondition is an autogenerated conversion function. +func Convert_v1_ReplicaSetCondition_To_extensions_ReplicaSetCondition(in *v1.ReplicaSetCondition, out *extensions.ReplicaSetCondition, s conversion.Scope) error { + return autoConvert_v1_ReplicaSetCondition_To_extensions_ReplicaSetCondition(in, out, s) +} + +func autoConvert_extensions_ReplicaSetCondition_To_v1_ReplicaSetCondition(in *extensions.ReplicaSetCondition, out *v1.ReplicaSetCondition, s conversion.Scope) error { + out.Type = v1.ReplicaSetConditionType(in.Type) + out.Status = core_v1.ConditionStatus(in.Status) + out.LastTransitionTime = in.LastTransitionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_extensions_ReplicaSetCondition_To_v1_ReplicaSetCondition is an autogenerated conversion function. +func Convert_extensions_ReplicaSetCondition_To_v1_ReplicaSetCondition(in *extensions.ReplicaSetCondition, out *v1.ReplicaSetCondition, s conversion.Scope) error { + return autoConvert_extensions_ReplicaSetCondition_To_v1_ReplicaSetCondition(in, out, s) +} + +func autoConvert_v1_ReplicaSetList_To_extensions_ReplicaSetList(in *v1.ReplicaSetList, out *extensions.ReplicaSetList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]extensions.ReplicaSet, len(*in)) + for i := range *in { + if err := Convert_v1_ReplicaSet_To_extensions_ReplicaSet(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_v1_ReplicaSetList_To_extensions_ReplicaSetList is an autogenerated conversion function. +func Convert_v1_ReplicaSetList_To_extensions_ReplicaSetList(in *v1.ReplicaSetList, out *extensions.ReplicaSetList, s conversion.Scope) error { + return autoConvert_v1_ReplicaSetList_To_extensions_ReplicaSetList(in, out, s) +} + +func autoConvert_extensions_ReplicaSetList_To_v1_ReplicaSetList(in *extensions.ReplicaSetList, out *v1.ReplicaSetList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1.ReplicaSet, len(*in)) + for i := range *in { + if err := Convert_extensions_ReplicaSet_To_v1_ReplicaSet(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_extensions_ReplicaSetList_To_v1_ReplicaSetList is an autogenerated conversion function. +func Convert_extensions_ReplicaSetList_To_v1_ReplicaSetList(in *extensions.ReplicaSetList, out *v1.ReplicaSetList, s conversion.Scope) error { + return autoConvert_extensions_ReplicaSetList_To_v1_ReplicaSetList(in, out, s) +} + +func autoConvert_v1_ReplicaSetSpec_To_extensions_ReplicaSetSpec(in *v1.ReplicaSetSpec, out *extensions.ReplicaSetSpec, s conversion.Scope) error { + if err := meta_v1.Convert_Pointer_int32_To_int32(&in.Replicas, &out.Replicas, s); err != nil { + return err + } + out.MinReadySeconds = in.MinReadySeconds + out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) + if err := apis_core_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err + } + return nil +} + +func autoConvert_extensions_ReplicaSetSpec_To_v1_ReplicaSetSpec(in *extensions.ReplicaSetSpec, out *v1.ReplicaSetSpec, s conversion.Scope) error { + if err := meta_v1.Convert_int32_To_Pointer_int32(&in.Replicas, &out.Replicas, s); err != nil { + return err + } + out.MinReadySeconds = in.MinReadySeconds + out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) + if err := apis_core_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err + } + return nil +} + +func autoConvert_v1_ReplicaSetStatus_To_extensions_ReplicaSetStatus(in *v1.ReplicaSetStatus, out *extensions.ReplicaSetStatus, s conversion.Scope) error { + out.Replicas = in.Replicas + out.FullyLabeledReplicas = in.FullyLabeledReplicas + out.ReadyReplicas = in.ReadyReplicas + out.AvailableReplicas = in.AvailableReplicas + out.ObservedGeneration = in.ObservedGeneration + out.Conditions = *(*[]extensions.ReplicaSetCondition)(unsafe.Pointer(&in.Conditions)) + return nil +} + +// Convert_v1_ReplicaSetStatus_To_extensions_ReplicaSetStatus is an autogenerated conversion function. +func Convert_v1_ReplicaSetStatus_To_extensions_ReplicaSetStatus(in *v1.ReplicaSetStatus, out *extensions.ReplicaSetStatus, s conversion.Scope) error { + return autoConvert_v1_ReplicaSetStatus_To_extensions_ReplicaSetStatus(in, out, s) +} + +func autoConvert_extensions_ReplicaSetStatus_To_v1_ReplicaSetStatus(in *extensions.ReplicaSetStatus, out *v1.ReplicaSetStatus, s conversion.Scope) error { + out.Replicas = in.Replicas + out.FullyLabeledReplicas = in.FullyLabeledReplicas + out.ReadyReplicas = in.ReadyReplicas + out.AvailableReplicas = in.AvailableReplicas + out.ObservedGeneration = in.ObservedGeneration + out.Conditions = *(*[]v1.ReplicaSetCondition)(unsafe.Pointer(&in.Conditions)) + return nil +} + +// Convert_extensions_ReplicaSetStatus_To_v1_ReplicaSetStatus is an autogenerated conversion function. +func Convert_extensions_ReplicaSetStatus_To_v1_ReplicaSetStatus(in *extensions.ReplicaSetStatus, out *v1.ReplicaSetStatus, s conversion.Scope) error { + return autoConvert_extensions_ReplicaSetStatus_To_v1_ReplicaSetStatus(in, out, s) +} + func autoConvert_v1_RollingUpdateDaemonSet_To_extensions_RollingUpdateDaemonSet(in *v1.RollingUpdateDaemonSet, out *extensions.RollingUpdateDaemonSet, s conversion.Scope) error { // WARNING: in.MaxUnavailable requires manual conversion: inconvertible types (*k8s.io/apimachinery/pkg/util/intstr.IntOrString vs k8s.io/apimachinery/pkg/util/intstr.IntOrString) return nil @@ -217,3 +714,231 @@ func autoConvert_extensions_RollingUpdateDaemonSet_To_v1_RollingUpdateDaemonSet( // WARNING: in.MaxUnavailable requires manual conversion: inconvertible types (k8s.io/apimachinery/pkg/util/intstr.IntOrString vs *k8s.io/apimachinery/pkg/util/intstr.IntOrString) return nil } + +func autoConvert_v1_RollingUpdateDeployment_To_extensions_RollingUpdateDeployment(in *v1.RollingUpdateDeployment, out *extensions.RollingUpdateDeployment, s conversion.Scope) error { + // WARNING: in.MaxUnavailable requires manual conversion: inconvertible types (*k8s.io/apimachinery/pkg/util/intstr.IntOrString vs k8s.io/apimachinery/pkg/util/intstr.IntOrString) + // WARNING: in.MaxSurge requires manual conversion: inconvertible types (*k8s.io/apimachinery/pkg/util/intstr.IntOrString vs k8s.io/apimachinery/pkg/util/intstr.IntOrString) + return nil +} + +func autoConvert_extensions_RollingUpdateDeployment_To_v1_RollingUpdateDeployment(in *extensions.RollingUpdateDeployment, out *v1.RollingUpdateDeployment, s conversion.Scope) error { + // WARNING: in.MaxUnavailable requires manual conversion: inconvertible types (k8s.io/apimachinery/pkg/util/intstr.IntOrString vs *k8s.io/apimachinery/pkg/util/intstr.IntOrString) + // WARNING: in.MaxSurge requires manual conversion: inconvertible types (k8s.io/apimachinery/pkg/util/intstr.IntOrString vs *k8s.io/apimachinery/pkg/util/intstr.IntOrString) + return nil +} + +func autoConvert_v1_RollingUpdateStatefulSetStrategy_To_apps_RollingUpdateStatefulSetStrategy(in *v1.RollingUpdateStatefulSetStrategy, out *apps.RollingUpdateStatefulSetStrategy, s conversion.Scope) error { + if err := meta_v1.Convert_Pointer_int32_To_int32(&in.Partition, &out.Partition, s); err != nil { + return err + } + return nil +} + +// Convert_v1_RollingUpdateStatefulSetStrategy_To_apps_RollingUpdateStatefulSetStrategy is an autogenerated conversion function. +func Convert_v1_RollingUpdateStatefulSetStrategy_To_apps_RollingUpdateStatefulSetStrategy(in *v1.RollingUpdateStatefulSetStrategy, out *apps.RollingUpdateStatefulSetStrategy, s conversion.Scope) error { + return autoConvert_v1_RollingUpdateStatefulSetStrategy_To_apps_RollingUpdateStatefulSetStrategy(in, out, s) +} + +func autoConvert_apps_RollingUpdateStatefulSetStrategy_To_v1_RollingUpdateStatefulSetStrategy(in *apps.RollingUpdateStatefulSetStrategy, out *v1.RollingUpdateStatefulSetStrategy, s conversion.Scope) error { + if err := meta_v1.Convert_int32_To_Pointer_int32(&in.Partition, &out.Partition, s); err != nil { + return err + } + return nil +} + +// Convert_apps_RollingUpdateStatefulSetStrategy_To_v1_RollingUpdateStatefulSetStrategy is an autogenerated conversion function. +func Convert_apps_RollingUpdateStatefulSetStrategy_To_v1_RollingUpdateStatefulSetStrategy(in *apps.RollingUpdateStatefulSetStrategy, out *v1.RollingUpdateStatefulSetStrategy, s conversion.Scope) error { + return autoConvert_apps_RollingUpdateStatefulSetStrategy_To_v1_RollingUpdateStatefulSetStrategy(in, out, s) +} + +func autoConvert_v1_StatefulSet_To_apps_StatefulSet(in *v1.StatefulSet, out *apps.StatefulSet, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_StatefulSetSpec_To_apps_StatefulSetSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1_StatefulSetStatus_To_apps_StatefulSetStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1_StatefulSet_To_apps_StatefulSet is an autogenerated conversion function. +func Convert_v1_StatefulSet_To_apps_StatefulSet(in *v1.StatefulSet, out *apps.StatefulSet, s conversion.Scope) error { + return autoConvert_v1_StatefulSet_To_apps_StatefulSet(in, out, s) +} + +func autoConvert_apps_StatefulSet_To_v1_StatefulSet(in *apps.StatefulSet, out *v1.StatefulSet, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_apps_StatefulSetSpec_To_v1_StatefulSetSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_apps_StatefulSetStatus_To_v1_StatefulSetStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_apps_StatefulSet_To_v1_StatefulSet is an autogenerated conversion function. +func Convert_apps_StatefulSet_To_v1_StatefulSet(in *apps.StatefulSet, out *v1.StatefulSet, s conversion.Scope) error { + return autoConvert_apps_StatefulSet_To_v1_StatefulSet(in, out, s) +} + +func autoConvert_v1_StatefulSetCondition_To_apps_StatefulSetCondition(in *v1.StatefulSetCondition, out *apps.StatefulSetCondition, s conversion.Scope) error { + out.Type = apps.StatefulSetConditionType(in.Type) + out.Status = core.ConditionStatus(in.Status) + out.LastTransitionTime = in.LastTransitionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_v1_StatefulSetCondition_To_apps_StatefulSetCondition is an autogenerated conversion function. +func Convert_v1_StatefulSetCondition_To_apps_StatefulSetCondition(in *v1.StatefulSetCondition, out *apps.StatefulSetCondition, s conversion.Scope) error { + return autoConvert_v1_StatefulSetCondition_To_apps_StatefulSetCondition(in, out, s) +} + +func autoConvert_apps_StatefulSetCondition_To_v1_StatefulSetCondition(in *apps.StatefulSetCondition, out *v1.StatefulSetCondition, s conversion.Scope) error { + out.Type = v1.StatefulSetConditionType(in.Type) + out.Status = core_v1.ConditionStatus(in.Status) + out.LastTransitionTime = in.LastTransitionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_apps_StatefulSetCondition_To_v1_StatefulSetCondition is an autogenerated conversion function. +func Convert_apps_StatefulSetCondition_To_v1_StatefulSetCondition(in *apps.StatefulSetCondition, out *v1.StatefulSetCondition, s conversion.Scope) error { + return autoConvert_apps_StatefulSetCondition_To_v1_StatefulSetCondition(in, out, s) +} + +func autoConvert_v1_StatefulSetList_To_apps_StatefulSetList(in *v1.StatefulSetList, out *apps.StatefulSetList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]apps.StatefulSet, len(*in)) + for i := range *in { + if err := Convert_v1_StatefulSet_To_apps_StatefulSet(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_v1_StatefulSetList_To_apps_StatefulSetList is an autogenerated conversion function. +func Convert_v1_StatefulSetList_To_apps_StatefulSetList(in *v1.StatefulSetList, out *apps.StatefulSetList, s conversion.Scope) error { + return autoConvert_v1_StatefulSetList_To_apps_StatefulSetList(in, out, s) +} + +func autoConvert_apps_StatefulSetList_To_v1_StatefulSetList(in *apps.StatefulSetList, out *v1.StatefulSetList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1.StatefulSet, len(*in)) + for i := range *in { + if err := Convert_apps_StatefulSet_To_v1_StatefulSet(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_apps_StatefulSetList_To_v1_StatefulSetList is an autogenerated conversion function. +func Convert_apps_StatefulSetList_To_v1_StatefulSetList(in *apps.StatefulSetList, out *v1.StatefulSetList, s conversion.Scope) error { + return autoConvert_apps_StatefulSetList_To_v1_StatefulSetList(in, out, s) +} + +func autoConvert_v1_StatefulSetSpec_To_apps_StatefulSetSpec(in *v1.StatefulSetSpec, out *apps.StatefulSetSpec, s conversion.Scope) error { + if err := meta_v1.Convert_Pointer_int32_To_int32(&in.Replicas, &out.Replicas, s); err != nil { + return err + } + out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) + if err := apis_core_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err + } + out.VolumeClaimTemplates = *(*[]core.PersistentVolumeClaim)(unsafe.Pointer(&in.VolumeClaimTemplates)) + out.ServiceName = in.ServiceName + out.PodManagementPolicy = apps.PodManagementPolicyType(in.PodManagementPolicy) + if err := Convert_v1_StatefulSetUpdateStrategy_To_apps_StatefulSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, s); err != nil { + return err + } + out.RevisionHistoryLimit = (*int32)(unsafe.Pointer(in.RevisionHistoryLimit)) + return nil +} + +func autoConvert_apps_StatefulSetSpec_To_v1_StatefulSetSpec(in *apps.StatefulSetSpec, out *v1.StatefulSetSpec, s conversion.Scope) error { + if err := meta_v1.Convert_int32_To_Pointer_int32(&in.Replicas, &out.Replicas, s); err != nil { + return err + } + out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) + if err := apis_core_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err + } + out.VolumeClaimTemplates = *(*[]core_v1.PersistentVolumeClaim)(unsafe.Pointer(&in.VolumeClaimTemplates)) + out.ServiceName = in.ServiceName + out.PodManagementPolicy = v1.PodManagementPolicyType(in.PodManagementPolicy) + if err := Convert_apps_StatefulSetUpdateStrategy_To_v1_StatefulSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, s); err != nil { + return err + } + out.RevisionHistoryLimit = (*int32)(unsafe.Pointer(in.RevisionHistoryLimit)) + return nil +} + +func autoConvert_v1_StatefulSetStatus_To_apps_StatefulSetStatus(in *v1.StatefulSetStatus, out *apps.StatefulSetStatus, s conversion.Scope) error { + // WARNING: in.ObservedGeneration requires manual conversion: inconvertible types (int64 vs *int64) + out.Replicas = in.Replicas + out.ReadyReplicas = in.ReadyReplicas + out.CurrentReplicas = in.CurrentReplicas + out.UpdatedReplicas = in.UpdatedReplicas + out.CurrentRevision = in.CurrentRevision + out.UpdateRevision = in.UpdateRevision + out.CollisionCount = (*int32)(unsafe.Pointer(in.CollisionCount)) + out.Conditions = *(*[]apps.StatefulSetCondition)(unsafe.Pointer(&in.Conditions)) + return nil +} + +func autoConvert_apps_StatefulSetStatus_To_v1_StatefulSetStatus(in *apps.StatefulSetStatus, out *v1.StatefulSetStatus, s conversion.Scope) error { + // WARNING: in.ObservedGeneration requires manual conversion: inconvertible types (*int64 vs int64) + out.Replicas = in.Replicas + out.ReadyReplicas = in.ReadyReplicas + out.CurrentReplicas = in.CurrentReplicas + out.UpdatedReplicas = in.UpdatedReplicas + out.CurrentRevision = in.CurrentRevision + out.UpdateRevision = in.UpdateRevision + out.CollisionCount = (*int32)(unsafe.Pointer(in.CollisionCount)) + out.Conditions = *(*[]v1.StatefulSetCondition)(unsafe.Pointer(&in.Conditions)) + return nil +} + +func autoConvert_v1_StatefulSetUpdateStrategy_To_apps_StatefulSetUpdateStrategy(in *v1.StatefulSetUpdateStrategy, out *apps.StatefulSetUpdateStrategy, s conversion.Scope) error { + out.Type = apps.StatefulSetUpdateStrategyType(in.Type) + if in.RollingUpdate != nil { + in, out := &in.RollingUpdate, &out.RollingUpdate + *out = new(apps.RollingUpdateStatefulSetStrategy) + if err := Convert_v1_RollingUpdateStatefulSetStrategy_To_apps_RollingUpdateStatefulSetStrategy(*in, *out, s); err != nil { + return err + } + } else { + out.RollingUpdate = nil + } + return nil +} + +func autoConvert_apps_StatefulSetUpdateStrategy_To_v1_StatefulSetUpdateStrategy(in *apps.StatefulSetUpdateStrategy, out *v1.StatefulSetUpdateStrategy, s conversion.Scope) error { + out.Type = v1.StatefulSetUpdateStrategyType(in.Type) + if in.RollingUpdate != nil { + in, out := &in.RollingUpdate, &out.RollingUpdate + *out = new(v1.RollingUpdateStatefulSetStrategy) + if err := Convert_apps_RollingUpdateStatefulSetStrategy_To_v1_RollingUpdateStatefulSetStrategy(*in, *out, s); err != nil { + return err + } + } else { + out.RollingUpdate = nil + } + return nil +} diff --git a/pkg/apis/apps/v1/zz_generated.defaults.go b/pkg/apis/apps/v1/zz_generated.defaults.go index 6cb03199027..8eb29e8d12b 100644 --- a/pkg/apis/apps/v1/zz_generated.defaults.go +++ b/pkg/apis/apps/v1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,7 +23,7 @@ package v1 import ( v1 "k8s.io/api/apps/v1" runtime "k8s.io/apimachinery/pkg/runtime" - api_v1 "k8s.io/kubernetes/pkg/api/v1" + core_v1 "k8s.io/kubernetes/pkg/apis/core/v1" ) // RegisterDefaults adds defaulters functions to the given scheme. @@ -32,140 +32,146 @@ import ( func RegisterDefaults(scheme *runtime.Scheme) error { scheme.AddTypeDefaultingFunc(&v1.DaemonSet{}, func(obj interface{}) { SetObjectDefaults_DaemonSet(obj.(*v1.DaemonSet)) }) scheme.AddTypeDefaultingFunc(&v1.DaemonSetList{}, func(obj interface{}) { SetObjectDefaults_DaemonSetList(obj.(*v1.DaemonSetList)) }) + scheme.AddTypeDefaultingFunc(&v1.Deployment{}, func(obj interface{}) { SetObjectDefaults_Deployment(obj.(*v1.Deployment)) }) + scheme.AddTypeDefaultingFunc(&v1.DeploymentList{}, func(obj interface{}) { SetObjectDefaults_DeploymentList(obj.(*v1.DeploymentList)) }) + scheme.AddTypeDefaultingFunc(&v1.ReplicaSet{}, func(obj interface{}) { SetObjectDefaults_ReplicaSet(obj.(*v1.ReplicaSet)) }) + scheme.AddTypeDefaultingFunc(&v1.ReplicaSetList{}, func(obj interface{}) { SetObjectDefaults_ReplicaSetList(obj.(*v1.ReplicaSetList)) }) + scheme.AddTypeDefaultingFunc(&v1.StatefulSet{}, func(obj interface{}) { SetObjectDefaults_StatefulSet(obj.(*v1.StatefulSet)) }) + scheme.AddTypeDefaultingFunc(&v1.StatefulSetList{}, func(obj interface{}) { SetObjectDefaults_StatefulSetList(obj.(*v1.StatefulSetList)) }) return nil } func SetObjectDefaults_DaemonSet(in *v1.DaemonSet) { SetDefaults_DaemonSet(in) - api_v1.SetDefaults_PodSpec(&in.Spec.Template.Spec) + core_v1.SetDefaults_PodSpec(&in.Spec.Template.Spec) for i := range in.Spec.Template.Spec.Volumes { a := &in.Spec.Template.Spec.Volumes[i] - api_v1.SetDefaults_Volume(a) + core_v1.SetDefaults_Volume(a) if a.VolumeSource.HostPath != nil { - api_v1.SetDefaults_HostPathVolumeSource(a.VolumeSource.HostPath) + core_v1.SetDefaults_HostPathVolumeSource(a.VolumeSource.HostPath) } if a.VolumeSource.Secret != nil { - api_v1.SetDefaults_SecretVolumeSource(a.VolumeSource.Secret) + core_v1.SetDefaults_SecretVolumeSource(a.VolumeSource.Secret) } if a.VolumeSource.ISCSI != nil { - api_v1.SetDefaults_ISCSIVolumeSource(a.VolumeSource.ISCSI) + core_v1.SetDefaults_ISCSIVolumeSource(a.VolumeSource.ISCSI) } if a.VolumeSource.RBD != nil { - api_v1.SetDefaults_RBDVolumeSource(a.VolumeSource.RBD) + core_v1.SetDefaults_RBDVolumeSource(a.VolumeSource.RBD) } if a.VolumeSource.DownwardAPI != nil { - api_v1.SetDefaults_DownwardAPIVolumeSource(a.VolumeSource.DownwardAPI) + core_v1.SetDefaults_DownwardAPIVolumeSource(a.VolumeSource.DownwardAPI) for j := range a.VolumeSource.DownwardAPI.Items { b := &a.VolumeSource.DownwardAPI.Items[j] if b.FieldRef != nil { - api_v1.SetDefaults_ObjectFieldSelector(b.FieldRef) + core_v1.SetDefaults_ObjectFieldSelector(b.FieldRef) } } } if a.VolumeSource.ConfigMap != nil { - api_v1.SetDefaults_ConfigMapVolumeSource(a.VolumeSource.ConfigMap) + core_v1.SetDefaults_ConfigMapVolumeSource(a.VolumeSource.ConfigMap) } if a.VolumeSource.AzureDisk != nil { - api_v1.SetDefaults_AzureDiskVolumeSource(a.VolumeSource.AzureDisk) + core_v1.SetDefaults_AzureDiskVolumeSource(a.VolumeSource.AzureDisk) } if a.VolumeSource.Projected != nil { - api_v1.SetDefaults_ProjectedVolumeSource(a.VolumeSource.Projected) + core_v1.SetDefaults_ProjectedVolumeSource(a.VolumeSource.Projected) for j := range a.VolumeSource.Projected.Sources { b := &a.VolumeSource.Projected.Sources[j] if b.DownwardAPI != nil { for k := range b.DownwardAPI.Items { c := &b.DownwardAPI.Items[k] if c.FieldRef != nil { - api_v1.SetDefaults_ObjectFieldSelector(c.FieldRef) + core_v1.SetDefaults_ObjectFieldSelector(c.FieldRef) } } } } } if a.VolumeSource.ScaleIO != nil { - api_v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) + core_v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) } } for i := range in.Spec.Template.Spec.InitContainers { a := &in.Spec.Template.Spec.InitContainers[i] - api_v1.SetDefaults_Container(a) + core_v1.SetDefaults_Container(a) for j := range a.Ports { b := &a.Ports[j] - api_v1.SetDefaults_ContainerPort(b) + core_v1.SetDefaults_ContainerPort(b) } for j := range a.Env { b := &a.Env[j] if b.ValueFrom != nil { if b.ValueFrom.FieldRef != nil { - api_v1.SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) + core_v1.SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) } } } - api_v1.SetDefaults_ResourceList(&a.Resources.Limits) - api_v1.SetDefaults_ResourceList(&a.Resources.Requests) + core_v1.SetDefaults_ResourceList(&a.Resources.Limits) + core_v1.SetDefaults_ResourceList(&a.Resources.Requests) if a.LivenessProbe != nil { - api_v1.SetDefaults_Probe(a.LivenessProbe) + core_v1.SetDefaults_Probe(a.LivenessProbe) if a.LivenessProbe.Handler.HTTPGet != nil { - api_v1.SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) + core_v1.SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) } } if a.ReadinessProbe != nil { - api_v1.SetDefaults_Probe(a.ReadinessProbe) + core_v1.SetDefaults_Probe(a.ReadinessProbe) if a.ReadinessProbe.Handler.HTTPGet != nil { - api_v1.SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) + core_v1.SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) } } if a.Lifecycle != nil { if a.Lifecycle.PostStart != nil { if a.Lifecycle.PostStart.HTTPGet != nil { - api_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) + core_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) } } if a.Lifecycle.PreStop != nil { if a.Lifecycle.PreStop.HTTPGet != nil { - api_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) + core_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) } } } } for i := range in.Spec.Template.Spec.Containers { a := &in.Spec.Template.Spec.Containers[i] - api_v1.SetDefaults_Container(a) + core_v1.SetDefaults_Container(a) for j := range a.Ports { b := &a.Ports[j] - api_v1.SetDefaults_ContainerPort(b) + core_v1.SetDefaults_ContainerPort(b) } for j := range a.Env { b := &a.Env[j] if b.ValueFrom != nil { if b.ValueFrom.FieldRef != nil { - api_v1.SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) + core_v1.SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) } } } - api_v1.SetDefaults_ResourceList(&a.Resources.Limits) - api_v1.SetDefaults_ResourceList(&a.Resources.Requests) + core_v1.SetDefaults_ResourceList(&a.Resources.Limits) + core_v1.SetDefaults_ResourceList(&a.Resources.Requests) if a.LivenessProbe != nil { - api_v1.SetDefaults_Probe(a.LivenessProbe) + core_v1.SetDefaults_Probe(a.LivenessProbe) if a.LivenessProbe.Handler.HTTPGet != nil { - api_v1.SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) + core_v1.SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) } } if a.ReadinessProbe != nil { - api_v1.SetDefaults_Probe(a.ReadinessProbe) + core_v1.SetDefaults_Probe(a.ReadinessProbe) if a.ReadinessProbe.Handler.HTTPGet != nil { - api_v1.SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) + core_v1.SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) } } if a.Lifecycle != nil { if a.Lifecycle.PostStart != nil { if a.Lifecycle.PostStart.HTTPGet != nil { - api_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) + core_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) } } if a.Lifecycle.PreStop != nil { if a.Lifecycle.PreStop.HTTPGet != nil { - api_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) + core_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) } } } @@ -178,3 +184,442 @@ func SetObjectDefaults_DaemonSetList(in *v1.DaemonSetList) { SetObjectDefaults_DaemonSet(a) } } + +func SetObjectDefaults_Deployment(in *v1.Deployment) { + SetDefaults_Deployment(in) + core_v1.SetDefaults_PodSpec(&in.Spec.Template.Spec) + for i := range in.Spec.Template.Spec.Volumes { + a := &in.Spec.Template.Spec.Volumes[i] + core_v1.SetDefaults_Volume(a) + if a.VolumeSource.HostPath != nil { + core_v1.SetDefaults_HostPathVolumeSource(a.VolumeSource.HostPath) + } + if a.VolumeSource.Secret != nil { + core_v1.SetDefaults_SecretVolumeSource(a.VolumeSource.Secret) + } + if a.VolumeSource.ISCSI != nil { + core_v1.SetDefaults_ISCSIVolumeSource(a.VolumeSource.ISCSI) + } + if a.VolumeSource.RBD != nil { + core_v1.SetDefaults_RBDVolumeSource(a.VolumeSource.RBD) + } + if a.VolumeSource.DownwardAPI != nil { + core_v1.SetDefaults_DownwardAPIVolumeSource(a.VolumeSource.DownwardAPI) + for j := range a.VolumeSource.DownwardAPI.Items { + b := &a.VolumeSource.DownwardAPI.Items[j] + if b.FieldRef != nil { + core_v1.SetDefaults_ObjectFieldSelector(b.FieldRef) + } + } + } + if a.VolumeSource.ConfigMap != nil { + core_v1.SetDefaults_ConfigMapVolumeSource(a.VolumeSource.ConfigMap) + } + if a.VolumeSource.AzureDisk != nil { + core_v1.SetDefaults_AzureDiskVolumeSource(a.VolumeSource.AzureDisk) + } + if a.VolumeSource.Projected != nil { + core_v1.SetDefaults_ProjectedVolumeSource(a.VolumeSource.Projected) + for j := range a.VolumeSource.Projected.Sources { + b := &a.VolumeSource.Projected.Sources[j] + if b.DownwardAPI != nil { + for k := range b.DownwardAPI.Items { + c := &b.DownwardAPI.Items[k] + if c.FieldRef != nil { + core_v1.SetDefaults_ObjectFieldSelector(c.FieldRef) + } + } + } + } + } + if a.VolumeSource.ScaleIO != nil { + core_v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) + } + } + for i := range in.Spec.Template.Spec.InitContainers { + a := &in.Spec.Template.Spec.InitContainers[i] + core_v1.SetDefaults_Container(a) + for j := range a.Ports { + b := &a.Ports[j] + core_v1.SetDefaults_ContainerPort(b) + } + for j := range a.Env { + b := &a.Env[j] + if b.ValueFrom != nil { + if b.ValueFrom.FieldRef != nil { + core_v1.SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) + } + } + } + core_v1.SetDefaults_ResourceList(&a.Resources.Limits) + core_v1.SetDefaults_ResourceList(&a.Resources.Requests) + if a.LivenessProbe != nil { + core_v1.SetDefaults_Probe(a.LivenessProbe) + if a.LivenessProbe.Handler.HTTPGet != nil { + core_v1.SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) + } + } + if a.ReadinessProbe != nil { + core_v1.SetDefaults_Probe(a.ReadinessProbe) + if a.ReadinessProbe.Handler.HTTPGet != nil { + core_v1.SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) + } + } + if a.Lifecycle != nil { + if a.Lifecycle.PostStart != nil { + if a.Lifecycle.PostStart.HTTPGet != nil { + core_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) + } + } + if a.Lifecycle.PreStop != nil { + if a.Lifecycle.PreStop.HTTPGet != nil { + core_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) + } + } + } + } + for i := range in.Spec.Template.Spec.Containers { + a := &in.Spec.Template.Spec.Containers[i] + core_v1.SetDefaults_Container(a) + for j := range a.Ports { + b := &a.Ports[j] + core_v1.SetDefaults_ContainerPort(b) + } + for j := range a.Env { + b := &a.Env[j] + if b.ValueFrom != nil { + if b.ValueFrom.FieldRef != nil { + core_v1.SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) + } + } + } + core_v1.SetDefaults_ResourceList(&a.Resources.Limits) + core_v1.SetDefaults_ResourceList(&a.Resources.Requests) + if a.LivenessProbe != nil { + core_v1.SetDefaults_Probe(a.LivenessProbe) + if a.LivenessProbe.Handler.HTTPGet != nil { + core_v1.SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) + } + } + if a.ReadinessProbe != nil { + core_v1.SetDefaults_Probe(a.ReadinessProbe) + if a.ReadinessProbe.Handler.HTTPGet != nil { + core_v1.SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) + } + } + if a.Lifecycle != nil { + if a.Lifecycle.PostStart != nil { + if a.Lifecycle.PostStart.HTTPGet != nil { + core_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) + } + } + if a.Lifecycle.PreStop != nil { + if a.Lifecycle.PreStop.HTTPGet != nil { + core_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) + } + } + } + } +} + +func SetObjectDefaults_DeploymentList(in *v1.DeploymentList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_Deployment(a) + } +} + +func SetObjectDefaults_ReplicaSet(in *v1.ReplicaSet) { + SetDefaults_ReplicaSet(in) + core_v1.SetDefaults_PodSpec(&in.Spec.Template.Spec) + for i := range in.Spec.Template.Spec.Volumes { + a := &in.Spec.Template.Spec.Volumes[i] + core_v1.SetDefaults_Volume(a) + if a.VolumeSource.HostPath != nil { + core_v1.SetDefaults_HostPathVolumeSource(a.VolumeSource.HostPath) + } + if a.VolumeSource.Secret != nil { + core_v1.SetDefaults_SecretVolumeSource(a.VolumeSource.Secret) + } + if a.VolumeSource.ISCSI != nil { + core_v1.SetDefaults_ISCSIVolumeSource(a.VolumeSource.ISCSI) + } + if a.VolumeSource.RBD != nil { + core_v1.SetDefaults_RBDVolumeSource(a.VolumeSource.RBD) + } + if a.VolumeSource.DownwardAPI != nil { + core_v1.SetDefaults_DownwardAPIVolumeSource(a.VolumeSource.DownwardAPI) + for j := range a.VolumeSource.DownwardAPI.Items { + b := &a.VolumeSource.DownwardAPI.Items[j] + if b.FieldRef != nil { + core_v1.SetDefaults_ObjectFieldSelector(b.FieldRef) + } + } + } + if a.VolumeSource.ConfigMap != nil { + core_v1.SetDefaults_ConfigMapVolumeSource(a.VolumeSource.ConfigMap) + } + if a.VolumeSource.AzureDisk != nil { + core_v1.SetDefaults_AzureDiskVolumeSource(a.VolumeSource.AzureDisk) + } + if a.VolumeSource.Projected != nil { + core_v1.SetDefaults_ProjectedVolumeSource(a.VolumeSource.Projected) + for j := range a.VolumeSource.Projected.Sources { + b := &a.VolumeSource.Projected.Sources[j] + if b.DownwardAPI != nil { + for k := range b.DownwardAPI.Items { + c := &b.DownwardAPI.Items[k] + if c.FieldRef != nil { + core_v1.SetDefaults_ObjectFieldSelector(c.FieldRef) + } + } + } + } + } + if a.VolumeSource.ScaleIO != nil { + core_v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) + } + } + for i := range in.Spec.Template.Spec.InitContainers { + a := &in.Spec.Template.Spec.InitContainers[i] + core_v1.SetDefaults_Container(a) + for j := range a.Ports { + b := &a.Ports[j] + core_v1.SetDefaults_ContainerPort(b) + } + for j := range a.Env { + b := &a.Env[j] + if b.ValueFrom != nil { + if b.ValueFrom.FieldRef != nil { + core_v1.SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) + } + } + } + core_v1.SetDefaults_ResourceList(&a.Resources.Limits) + core_v1.SetDefaults_ResourceList(&a.Resources.Requests) + if a.LivenessProbe != nil { + core_v1.SetDefaults_Probe(a.LivenessProbe) + if a.LivenessProbe.Handler.HTTPGet != nil { + core_v1.SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) + } + } + if a.ReadinessProbe != nil { + core_v1.SetDefaults_Probe(a.ReadinessProbe) + if a.ReadinessProbe.Handler.HTTPGet != nil { + core_v1.SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) + } + } + if a.Lifecycle != nil { + if a.Lifecycle.PostStart != nil { + if a.Lifecycle.PostStart.HTTPGet != nil { + core_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) + } + } + if a.Lifecycle.PreStop != nil { + if a.Lifecycle.PreStop.HTTPGet != nil { + core_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) + } + } + } + } + for i := range in.Spec.Template.Spec.Containers { + a := &in.Spec.Template.Spec.Containers[i] + core_v1.SetDefaults_Container(a) + for j := range a.Ports { + b := &a.Ports[j] + core_v1.SetDefaults_ContainerPort(b) + } + for j := range a.Env { + b := &a.Env[j] + if b.ValueFrom != nil { + if b.ValueFrom.FieldRef != nil { + core_v1.SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) + } + } + } + core_v1.SetDefaults_ResourceList(&a.Resources.Limits) + core_v1.SetDefaults_ResourceList(&a.Resources.Requests) + if a.LivenessProbe != nil { + core_v1.SetDefaults_Probe(a.LivenessProbe) + if a.LivenessProbe.Handler.HTTPGet != nil { + core_v1.SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) + } + } + if a.ReadinessProbe != nil { + core_v1.SetDefaults_Probe(a.ReadinessProbe) + if a.ReadinessProbe.Handler.HTTPGet != nil { + core_v1.SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) + } + } + if a.Lifecycle != nil { + if a.Lifecycle.PostStart != nil { + if a.Lifecycle.PostStart.HTTPGet != nil { + core_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) + } + } + if a.Lifecycle.PreStop != nil { + if a.Lifecycle.PreStop.HTTPGet != nil { + core_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) + } + } + } + } +} + +func SetObjectDefaults_ReplicaSetList(in *v1.ReplicaSetList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_ReplicaSet(a) + } +} + +func SetObjectDefaults_StatefulSet(in *v1.StatefulSet) { + SetDefaults_StatefulSet(in) + core_v1.SetDefaults_PodSpec(&in.Spec.Template.Spec) + for i := range in.Spec.Template.Spec.Volumes { + a := &in.Spec.Template.Spec.Volumes[i] + core_v1.SetDefaults_Volume(a) + if a.VolumeSource.HostPath != nil { + core_v1.SetDefaults_HostPathVolumeSource(a.VolumeSource.HostPath) + } + if a.VolumeSource.Secret != nil { + core_v1.SetDefaults_SecretVolumeSource(a.VolumeSource.Secret) + } + if a.VolumeSource.ISCSI != nil { + core_v1.SetDefaults_ISCSIVolumeSource(a.VolumeSource.ISCSI) + } + if a.VolumeSource.RBD != nil { + core_v1.SetDefaults_RBDVolumeSource(a.VolumeSource.RBD) + } + if a.VolumeSource.DownwardAPI != nil { + core_v1.SetDefaults_DownwardAPIVolumeSource(a.VolumeSource.DownwardAPI) + for j := range a.VolumeSource.DownwardAPI.Items { + b := &a.VolumeSource.DownwardAPI.Items[j] + if b.FieldRef != nil { + core_v1.SetDefaults_ObjectFieldSelector(b.FieldRef) + } + } + } + if a.VolumeSource.ConfigMap != nil { + core_v1.SetDefaults_ConfigMapVolumeSource(a.VolumeSource.ConfigMap) + } + if a.VolumeSource.AzureDisk != nil { + core_v1.SetDefaults_AzureDiskVolumeSource(a.VolumeSource.AzureDisk) + } + if a.VolumeSource.Projected != nil { + core_v1.SetDefaults_ProjectedVolumeSource(a.VolumeSource.Projected) + for j := range a.VolumeSource.Projected.Sources { + b := &a.VolumeSource.Projected.Sources[j] + if b.DownwardAPI != nil { + for k := range b.DownwardAPI.Items { + c := &b.DownwardAPI.Items[k] + if c.FieldRef != nil { + core_v1.SetDefaults_ObjectFieldSelector(c.FieldRef) + } + } + } + } + } + if a.VolumeSource.ScaleIO != nil { + core_v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) + } + } + for i := range in.Spec.Template.Spec.InitContainers { + a := &in.Spec.Template.Spec.InitContainers[i] + core_v1.SetDefaults_Container(a) + for j := range a.Ports { + b := &a.Ports[j] + core_v1.SetDefaults_ContainerPort(b) + } + for j := range a.Env { + b := &a.Env[j] + if b.ValueFrom != nil { + if b.ValueFrom.FieldRef != nil { + core_v1.SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) + } + } + } + core_v1.SetDefaults_ResourceList(&a.Resources.Limits) + core_v1.SetDefaults_ResourceList(&a.Resources.Requests) + if a.LivenessProbe != nil { + core_v1.SetDefaults_Probe(a.LivenessProbe) + if a.LivenessProbe.Handler.HTTPGet != nil { + core_v1.SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) + } + } + if a.ReadinessProbe != nil { + core_v1.SetDefaults_Probe(a.ReadinessProbe) + if a.ReadinessProbe.Handler.HTTPGet != nil { + core_v1.SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) + } + } + if a.Lifecycle != nil { + if a.Lifecycle.PostStart != nil { + if a.Lifecycle.PostStart.HTTPGet != nil { + core_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) + } + } + if a.Lifecycle.PreStop != nil { + if a.Lifecycle.PreStop.HTTPGet != nil { + core_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) + } + } + } + } + for i := range in.Spec.Template.Spec.Containers { + a := &in.Spec.Template.Spec.Containers[i] + core_v1.SetDefaults_Container(a) + for j := range a.Ports { + b := &a.Ports[j] + core_v1.SetDefaults_ContainerPort(b) + } + for j := range a.Env { + b := &a.Env[j] + if b.ValueFrom != nil { + if b.ValueFrom.FieldRef != nil { + core_v1.SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) + } + } + } + core_v1.SetDefaults_ResourceList(&a.Resources.Limits) + core_v1.SetDefaults_ResourceList(&a.Resources.Requests) + if a.LivenessProbe != nil { + core_v1.SetDefaults_Probe(a.LivenessProbe) + if a.LivenessProbe.Handler.HTTPGet != nil { + core_v1.SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) + } + } + if a.ReadinessProbe != nil { + core_v1.SetDefaults_Probe(a.ReadinessProbe) + if a.ReadinessProbe.Handler.HTTPGet != nil { + core_v1.SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) + } + } + if a.Lifecycle != nil { + if a.Lifecycle.PostStart != nil { + if a.Lifecycle.PostStart.HTTPGet != nil { + core_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) + } + } + if a.Lifecycle.PreStop != nil { + if a.Lifecycle.PreStop.HTTPGet != nil { + core_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) + } + } + } + } + for i := range in.Spec.VolumeClaimTemplates { + a := &in.Spec.VolumeClaimTemplates[i] + core_v1.SetDefaults_PersistentVolumeClaim(a) + core_v1.SetDefaults_ResourceList(&a.Spec.Resources.Limits) + core_v1.SetDefaults_ResourceList(&a.Spec.Resources.Requests) + core_v1.SetDefaults_ResourceList(&a.Status.Capacity) + } +} + +func SetObjectDefaults_StatefulSetList(in *v1.StatefulSetList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_StatefulSet(a) + } +} diff --git a/pkg/apis/apps/v1beta1/BUILD b/pkg/apis/apps/v1beta1/BUILD index 7fa96a92aeb..920dcdc49a7 100644 --- a/pkg/apis/apps/v1beta1/BUILD +++ b/pkg/apis/apps/v1beta1/BUILD @@ -18,14 +18,16 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/apis/apps/v1beta1", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/v1:go_default_library", "//pkg/apis/apps:go_default_library", + "//pkg/apis/autoscaling:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/v1:go_default_library", "//pkg/apis/extensions:go_default_library", "//vendor/k8s.io/api/apps/v1beta1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", @@ -51,9 +53,10 @@ go_test( importpath = "k8s.io/kubernetes/pkg/apis/apps/v1beta1_test", deps = [ ":go_default_library", - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/apps/install:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/install:go_default_library", "//vendor/k8s.io/api/apps/v1beta1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", diff --git a/pkg/apis/apps/v1beta1/conversion.go b/pkg/apis/apps/v1beta1/conversion.go index 42d26b86d31..6f4d27cf905 100644 --- a/pkg/apis/apps/v1beta1/conversion.go +++ b/pkg/apis/apps/v1beta1/conversion.go @@ -23,11 +23,13 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/kubernetes/pkg/api" - k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/apis/apps" + "k8s.io/kubernetes/pkg/apis/autoscaling" + api "k8s.io/kubernetes/pkg/apis/core" + k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" "k8s.io/kubernetes/pkg/apis/extensions" ) @@ -44,8 +46,8 @@ func addConversionFuncs(scheme *runtime.Scheme) error { // extensions // TODO: below conversions should be dropped in favor of auto-generated // ones, see https://github.com/kubernetes/kubernetes/issues/39865 - Convert_v1beta1_ScaleStatus_To_extensions_ScaleStatus, - Convert_extensions_ScaleStatus_To_v1beta1_ScaleStatus, + Convert_v1beta1_ScaleStatus_To_autoscaling_ScaleStatus, + Convert_autoscaling_ScaleStatus_To_v1beta1_ScaleStatus, Convert_v1beta1_DeploymentSpec_To_extensions_DeploymentSpec, Convert_extensions_DeploymentSpec_To_v1beta1_DeploymentSpec, Convert_v1beta1_DeploymentStrategy_To_extensions_DeploymentStrategy, @@ -87,7 +89,7 @@ func Convert_v1beta1_StatefulSetSpec_To_apps_StatefulSetSpec(in *appsv1beta1.Sta } else { out.Selector = nil } - if err := k8s_api_v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := k8s_api_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if in.VolumeClaimTemplates != nil { @@ -127,7 +129,7 @@ func Convert_apps_StatefulSetSpec_To_v1beta1_StatefulSetSpec(in *apps.StatefulSe } else { out.Selector = nil } - if err := k8s_api_v1.Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := k8s_api_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if in.VolumeClaimTemplates != nil { @@ -178,48 +180,35 @@ func Convert_apps_StatefulSetUpdateStrategy_To_v1beta1_StatefulSetUpdateStrategy return nil } -func Convert_extensions_ScaleStatus_To_v1beta1_ScaleStatus(in *extensions.ScaleStatus, out *appsv1beta1.ScaleStatus, s conversion.Scope) error { +func Convert_autoscaling_ScaleStatus_To_v1beta1_ScaleStatus(in *autoscaling.ScaleStatus, out *appsv1beta1.ScaleStatus, s conversion.Scope) error { out.Replicas = int32(in.Replicas) + out.TargetSelector = in.Selector out.Selector = nil - out.TargetSelector = "" - if in.Selector != nil { - if in.Selector.MatchExpressions == nil || len(in.Selector.MatchExpressions) == 0 { - out.Selector = in.Selector.MatchLabels - } - - selector, err := metav1.LabelSelectorAsSelector(in.Selector) - if err != nil { - return fmt.Errorf("invalid label selector: %v", err) - } - out.TargetSelector = selector.String() + selector, err := metav1.ParseToLabelSelector(in.Selector) + if err != nil { + return fmt.Errorf("failed to parse selector: %v", err) } + if len(selector.MatchExpressions) == 0 { + out.Selector = selector.MatchLabels + } + return nil } -func Convert_v1beta1_ScaleStatus_To_extensions_ScaleStatus(in *appsv1beta1.ScaleStatus, out *extensions.ScaleStatus, s conversion.Scope) error { +func Convert_v1beta1_ScaleStatus_To_autoscaling_ScaleStatus(in *appsv1beta1.ScaleStatus, out *autoscaling.ScaleStatus, s conversion.Scope) error { out.Replicas = in.Replicas - // Normally when 2 fields map to the same internal value we favor the old field, since - // old clients can't be expected to know about new fields but clients that know about the - // new field can be expected to know about the old field (though that's not quite true, due - // to kubectl apply). However, these fields are readonly, so any non-nil value should work. if in.TargetSelector != "" { - labelSelector, err := metav1.ParseToLabelSelector(in.TargetSelector) - if err != nil { - out.Selector = nil - return fmt.Errorf("failed to parse target selector: %v", err) - } - out.Selector = labelSelector + out.Selector = in.TargetSelector } else if in.Selector != nil { - out.Selector = new(metav1.LabelSelector) - selector := make(map[string]string) + set := labels.Set{} for key, val := range in.Selector { - selector[key] = val + set[key] = val } - out.Selector.MatchLabels = selector + out.Selector = labels.SelectorFromSet(set).String() } else { - out.Selector = nil + out.Selector = "" } return nil } @@ -229,7 +218,7 @@ func Convert_v1beta1_DeploymentSpec_To_extensions_DeploymentSpec(in *appsv1beta1 out.Replicas = *in.Replicas } out.Selector = in.Selector - if err := k8s_api_v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := k8s_api_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if err := Convert_v1beta1_DeploymentStrategy_To_extensions_DeploymentStrategy(&in.Strategy, &out.Strategy, s); err != nil { @@ -254,7 +243,7 @@ func Convert_v1beta1_DeploymentSpec_To_extensions_DeploymentSpec(in *appsv1beta1 func Convert_extensions_DeploymentSpec_To_v1beta1_DeploymentSpec(in *extensions.DeploymentSpec, out *appsv1beta1.DeploymentSpec, s conversion.Scope) error { out.Replicas = &in.Replicas out.Selector = in.Selector - if err := k8s_api_v1.Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := k8s_api_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if err := Convert_extensions_DeploymentStrategy_To_v1beta1_DeploymentStrategy(&in.Strategy, &out.Strategy, s); err != nil { diff --git a/pkg/apis/apps/v1beta1/defaults_test.go b/pkg/apis/apps/v1beta1/defaults_test.go index 6411035db5f..06e0d7b11a2 100644 --- a/pkg/apis/apps/v1beta1/defaults_test.go +++ b/pkg/apis/apps/v1beta1/defaults_test.go @@ -26,10 +26,11 @@ import ( apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/kubernetes/pkg/api" - _ "k8s.io/kubernetes/pkg/api/install" + "k8s.io/kubernetes/pkg/api/legacyscheme" _ "k8s.io/kubernetes/pkg/apis/apps/install" . "k8s.io/kubernetes/pkg/apis/apps/v1beta1" + api "k8s.io/kubernetes/pkg/apis/core" + _ "k8s.io/kubernetes/pkg/apis/core/install" ) func TestSetDefaultDeployment(t *testing.T) { @@ -196,18 +197,18 @@ func TestDefaultDeploymentAvailability(t *testing.T) { } func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { - data, err := runtime.Encode(api.Codecs.LegacyCodec(SchemeGroupVersion), obj) + 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(api.Codecs.UniversalDecoder(), data) + 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 = api.Scheme.Convert(obj2, obj3, nil) + err = legacyscheme.Scheme.Convert(obj2, obj3, nil) if err != nil { t.Errorf("%v\nSource: %#v", err, obj2) return nil diff --git a/pkg/apis/apps/v1beta1/doc.go b/pkg/apis/apps/v1beta1/doc.go index 058dce37f1d..d4672d4f3fd 100644 --- a/pkg/apis/apps/v1beta1/doc.go +++ b/pkg/apis/apps/v1beta1/doc.go @@ -15,8 +15,9 @@ limitations under the License. */ // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/apps +// +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/autoscaling // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/extensions -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/apps/v1beta1 +// +k8s:conversion-gen-external-types=k8s.io/api/apps/v1beta1 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/apps/v1beta1 diff --git a/pkg/apis/apps/v1beta1/zz_generated.conversion.go b/pkg/apis/apps/v1beta1/zz_generated.conversion.go index d3ddaac0333..56639c17dce 100644 --- a/pkg/apis/apps/v1beta1/zz_generated.conversion.go +++ b/pkg/apis/apps/v1beta1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,16 +21,18 @@ limitations under the License. package v1beta1 import ( + unsafe "unsafe" + v1beta1 "k8s.io/api/apps/v1beta1" v1 "k8s.io/api/core/v1" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - api "k8s.io/kubernetes/pkg/api" - api_v1 "k8s.io/kubernetes/pkg/api/v1" apps "k8s.io/kubernetes/pkg/apis/apps" + autoscaling "k8s.io/kubernetes/pkg/apis/autoscaling" + core "k8s.io/kubernetes/pkg/apis/core" + core_v1 "k8s.io/kubernetes/pkg/apis/core/v1" extensions "k8s.io/kubernetes/pkg/apis/extensions" - unsafe "unsafe" ) func init() { @@ -65,14 +67,16 @@ func RegisterConversions(scheme *runtime.Scheme) error { Convert_extensions_RollingUpdateDeployment_To_v1beta1_RollingUpdateDeployment, Convert_v1beta1_RollingUpdateStatefulSetStrategy_To_apps_RollingUpdateStatefulSetStrategy, Convert_apps_RollingUpdateStatefulSetStrategy_To_v1beta1_RollingUpdateStatefulSetStrategy, - Convert_v1beta1_Scale_To_extensions_Scale, - Convert_extensions_Scale_To_v1beta1_Scale, - Convert_v1beta1_ScaleSpec_To_extensions_ScaleSpec, - Convert_extensions_ScaleSpec_To_v1beta1_ScaleSpec, - Convert_v1beta1_ScaleStatus_To_extensions_ScaleStatus, - Convert_extensions_ScaleStatus_To_v1beta1_ScaleStatus, + Convert_v1beta1_Scale_To_autoscaling_Scale, + Convert_autoscaling_Scale_To_v1beta1_Scale, + Convert_v1beta1_ScaleSpec_To_autoscaling_ScaleSpec, + Convert_autoscaling_ScaleSpec_To_v1beta1_ScaleSpec, + Convert_v1beta1_ScaleStatus_To_autoscaling_ScaleStatus, + Convert_autoscaling_ScaleStatus_To_v1beta1_ScaleStatus, Convert_v1beta1_StatefulSet_To_apps_StatefulSet, Convert_apps_StatefulSet_To_v1beta1_StatefulSet, + Convert_v1beta1_StatefulSetCondition_To_apps_StatefulSetCondition, + Convert_apps_StatefulSetCondition_To_v1beta1_StatefulSetCondition, Convert_v1beta1_StatefulSetList_To_apps_StatefulSetList, Convert_apps_StatefulSetList_To_v1beta1_StatefulSetList, Convert_v1beta1_StatefulSetSpec_To_apps_StatefulSetSpec, @@ -188,7 +192,7 @@ func Convert_extensions_Deployment_To_v1beta1_Deployment(in *extensions.Deployme func autoConvert_v1beta1_DeploymentCondition_To_extensions_DeploymentCondition(in *v1beta1.DeploymentCondition, out *extensions.DeploymentCondition, s conversion.Scope) error { out.Type = extensions.DeploymentConditionType(in.Type) - out.Status = api.ConditionStatus(in.Status) + out.Status = core.ConditionStatus(in.Status) out.LastUpdateTime = in.LastUpdateTime out.LastTransitionTime = in.LastTransitionTime out.Reason = in.Reason @@ -291,7 +295,7 @@ func autoConvert_v1beta1_DeploymentSpec_To_extensions_DeploymentSpec(in *v1beta1 return err } out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) - if err := api_v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := core_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if err := Convert_v1beta1_DeploymentStrategy_To_extensions_DeploymentStrategy(&in.Strategy, &out.Strategy, s); err != nil { @@ -310,7 +314,7 @@ func autoConvert_extensions_DeploymentSpec_To_v1beta1_DeploymentSpec(in *extensi return err } out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) - if err := api_v1.Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := core_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if err := Convert_extensions_DeploymentStrategy_To_v1beta1_DeploymentStrategy(&in.Strategy, &out.Strategy, s); err != nil { @@ -442,68 +446,68 @@ func Convert_apps_RollingUpdateStatefulSetStrategy_To_v1beta1_RollingUpdateState return autoConvert_apps_RollingUpdateStatefulSetStrategy_To_v1beta1_RollingUpdateStatefulSetStrategy(in, out, s) } -func autoConvert_v1beta1_Scale_To_extensions_Scale(in *v1beta1.Scale, out *extensions.Scale, s conversion.Scope) error { +func autoConvert_v1beta1_Scale_To_autoscaling_Scale(in *v1beta1.Scale, out *autoscaling.Scale, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta - if err := Convert_v1beta1_ScaleSpec_To_extensions_ScaleSpec(&in.Spec, &out.Spec, s); err != nil { + if err := Convert_v1beta1_ScaleSpec_To_autoscaling_ScaleSpec(&in.Spec, &out.Spec, s); err != nil { return err } - if err := Convert_v1beta1_ScaleStatus_To_extensions_ScaleStatus(&in.Status, &out.Status, s); err != nil { + if err := Convert_v1beta1_ScaleStatus_To_autoscaling_ScaleStatus(&in.Status, &out.Status, s); err != nil { return err } return nil } -// Convert_v1beta1_Scale_To_extensions_Scale is an autogenerated conversion function. -func Convert_v1beta1_Scale_To_extensions_Scale(in *v1beta1.Scale, out *extensions.Scale, s conversion.Scope) error { - return autoConvert_v1beta1_Scale_To_extensions_Scale(in, out, s) +// Convert_v1beta1_Scale_To_autoscaling_Scale is an autogenerated conversion function. +func Convert_v1beta1_Scale_To_autoscaling_Scale(in *v1beta1.Scale, out *autoscaling.Scale, s conversion.Scope) error { + return autoConvert_v1beta1_Scale_To_autoscaling_Scale(in, out, s) } -func autoConvert_extensions_Scale_To_v1beta1_Scale(in *extensions.Scale, out *v1beta1.Scale, s conversion.Scope) error { +func autoConvert_autoscaling_Scale_To_v1beta1_Scale(in *autoscaling.Scale, out *v1beta1.Scale, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta - if err := Convert_extensions_ScaleSpec_To_v1beta1_ScaleSpec(&in.Spec, &out.Spec, s); err != nil { + if err := Convert_autoscaling_ScaleSpec_To_v1beta1_ScaleSpec(&in.Spec, &out.Spec, s); err != nil { return err } - if err := Convert_extensions_ScaleStatus_To_v1beta1_ScaleStatus(&in.Status, &out.Status, s); err != nil { + if err := Convert_autoscaling_ScaleStatus_To_v1beta1_ScaleStatus(&in.Status, &out.Status, s); err != nil { return err } return nil } -// Convert_extensions_Scale_To_v1beta1_Scale is an autogenerated conversion function. -func Convert_extensions_Scale_To_v1beta1_Scale(in *extensions.Scale, out *v1beta1.Scale, s conversion.Scope) error { - return autoConvert_extensions_Scale_To_v1beta1_Scale(in, out, s) +// Convert_autoscaling_Scale_To_v1beta1_Scale is an autogenerated conversion function. +func Convert_autoscaling_Scale_To_v1beta1_Scale(in *autoscaling.Scale, out *v1beta1.Scale, s conversion.Scope) error { + return autoConvert_autoscaling_Scale_To_v1beta1_Scale(in, out, s) } -func autoConvert_v1beta1_ScaleSpec_To_extensions_ScaleSpec(in *v1beta1.ScaleSpec, out *extensions.ScaleSpec, s conversion.Scope) error { +func autoConvert_v1beta1_ScaleSpec_To_autoscaling_ScaleSpec(in *v1beta1.ScaleSpec, out *autoscaling.ScaleSpec, s conversion.Scope) error { out.Replicas = in.Replicas return nil } -// Convert_v1beta1_ScaleSpec_To_extensions_ScaleSpec is an autogenerated conversion function. -func Convert_v1beta1_ScaleSpec_To_extensions_ScaleSpec(in *v1beta1.ScaleSpec, out *extensions.ScaleSpec, s conversion.Scope) error { - return autoConvert_v1beta1_ScaleSpec_To_extensions_ScaleSpec(in, out, s) +// Convert_v1beta1_ScaleSpec_To_autoscaling_ScaleSpec is an autogenerated conversion function. +func Convert_v1beta1_ScaleSpec_To_autoscaling_ScaleSpec(in *v1beta1.ScaleSpec, out *autoscaling.ScaleSpec, s conversion.Scope) error { + return autoConvert_v1beta1_ScaleSpec_To_autoscaling_ScaleSpec(in, out, s) } -func autoConvert_extensions_ScaleSpec_To_v1beta1_ScaleSpec(in *extensions.ScaleSpec, out *v1beta1.ScaleSpec, s conversion.Scope) error { +func autoConvert_autoscaling_ScaleSpec_To_v1beta1_ScaleSpec(in *autoscaling.ScaleSpec, out *v1beta1.ScaleSpec, s conversion.Scope) error { out.Replicas = in.Replicas return nil } -// Convert_extensions_ScaleSpec_To_v1beta1_ScaleSpec is an autogenerated conversion function. -func Convert_extensions_ScaleSpec_To_v1beta1_ScaleSpec(in *extensions.ScaleSpec, out *v1beta1.ScaleSpec, s conversion.Scope) error { - return autoConvert_extensions_ScaleSpec_To_v1beta1_ScaleSpec(in, out, s) +// Convert_autoscaling_ScaleSpec_To_v1beta1_ScaleSpec is an autogenerated conversion function. +func Convert_autoscaling_ScaleSpec_To_v1beta1_ScaleSpec(in *autoscaling.ScaleSpec, out *v1beta1.ScaleSpec, s conversion.Scope) error { + return autoConvert_autoscaling_ScaleSpec_To_v1beta1_ScaleSpec(in, out, s) } -func autoConvert_v1beta1_ScaleStatus_To_extensions_ScaleStatus(in *v1beta1.ScaleStatus, out *extensions.ScaleStatus, s conversion.Scope) error { +func autoConvert_v1beta1_ScaleStatus_To_autoscaling_ScaleStatus(in *v1beta1.ScaleStatus, out *autoscaling.ScaleStatus, s conversion.Scope) error { out.Replicas = in.Replicas - // WARNING: in.Selector requires manual conversion: inconvertible types (map[string]string vs *k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector) + // WARNING: in.Selector requires manual conversion: inconvertible types (map[string]string vs string) // WARNING: in.TargetSelector requires manual conversion: does not exist in peer-type return nil } -func autoConvert_extensions_ScaleStatus_To_v1beta1_ScaleStatus(in *extensions.ScaleStatus, out *v1beta1.ScaleStatus, s conversion.Scope) error { +func autoConvert_autoscaling_ScaleStatus_To_v1beta1_ScaleStatus(in *autoscaling.ScaleStatus, out *v1beta1.ScaleStatus, s conversion.Scope) error { out.Replicas = in.Replicas - // WARNING: in.Selector requires manual conversion: inconvertible types (*k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector vs map[string]string) + // WARNING: in.Selector requires manual conversion: inconvertible types (string vs map[string]string) return nil } @@ -539,6 +543,34 @@ func Convert_apps_StatefulSet_To_v1beta1_StatefulSet(in *apps.StatefulSet, out * return autoConvert_apps_StatefulSet_To_v1beta1_StatefulSet(in, out, s) } +func autoConvert_v1beta1_StatefulSetCondition_To_apps_StatefulSetCondition(in *v1beta1.StatefulSetCondition, out *apps.StatefulSetCondition, s conversion.Scope) error { + out.Type = apps.StatefulSetConditionType(in.Type) + out.Status = core.ConditionStatus(in.Status) + out.LastTransitionTime = in.LastTransitionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_v1beta1_StatefulSetCondition_To_apps_StatefulSetCondition is an autogenerated conversion function. +func Convert_v1beta1_StatefulSetCondition_To_apps_StatefulSetCondition(in *v1beta1.StatefulSetCondition, out *apps.StatefulSetCondition, s conversion.Scope) error { + return autoConvert_v1beta1_StatefulSetCondition_To_apps_StatefulSetCondition(in, out, s) +} + +func autoConvert_apps_StatefulSetCondition_To_v1beta1_StatefulSetCondition(in *apps.StatefulSetCondition, out *v1beta1.StatefulSetCondition, s conversion.Scope) error { + out.Type = v1beta1.StatefulSetConditionType(in.Type) + out.Status = v1.ConditionStatus(in.Status) + out.LastTransitionTime = in.LastTransitionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_apps_StatefulSetCondition_To_v1beta1_StatefulSetCondition is an autogenerated conversion function. +func Convert_apps_StatefulSetCondition_To_v1beta1_StatefulSetCondition(in *apps.StatefulSetCondition, out *v1beta1.StatefulSetCondition, s conversion.Scope) error { + return autoConvert_apps_StatefulSetCondition_To_v1beta1_StatefulSetCondition(in, out, s) +} + func autoConvert_v1beta1_StatefulSetList_To_apps_StatefulSetList(in *v1beta1.StatefulSetList, out *apps.StatefulSetList, s conversion.Scope) error { out.ListMeta = in.ListMeta if in.Items != nil { @@ -586,10 +618,10 @@ func autoConvert_v1beta1_StatefulSetSpec_To_apps_StatefulSetSpec(in *v1beta1.Sta return err } out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) - if err := api_v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := core_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } - out.VolumeClaimTemplates = *(*[]api.PersistentVolumeClaim)(unsafe.Pointer(&in.VolumeClaimTemplates)) + out.VolumeClaimTemplates = *(*[]core.PersistentVolumeClaim)(unsafe.Pointer(&in.VolumeClaimTemplates)) out.ServiceName = in.ServiceName out.PodManagementPolicy = apps.PodManagementPolicyType(in.PodManagementPolicy) if err := Convert_v1beta1_StatefulSetUpdateStrategy_To_apps_StatefulSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, s); err != nil { @@ -604,7 +636,7 @@ func autoConvert_apps_StatefulSetSpec_To_v1beta1_StatefulSetSpec(in *apps.Statef return err } out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) - if err := api_v1.Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := core_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } out.VolumeClaimTemplates = *(*[]v1.PersistentVolumeClaim)(unsafe.Pointer(&in.VolumeClaimTemplates)) @@ -626,6 +658,7 @@ func autoConvert_v1beta1_StatefulSetStatus_To_apps_StatefulSetStatus(in *v1beta1 out.CurrentRevision = in.CurrentRevision out.UpdateRevision = in.UpdateRevision out.CollisionCount = (*int32)(unsafe.Pointer(in.CollisionCount)) + out.Conditions = *(*[]apps.StatefulSetCondition)(unsafe.Pointer(&in.Conditions)) return nil } @@ -643,6 +676,7 @@ func autoConvert_apps_StatefulSetStatus_To_v1beta1_StatefulSetStatus(in *apps.St out.CurrentRevision = in.CurrentRevision out.UpdateRevision = in.UpdateRevision out.CollisionCount = (*int32)(unsafe.Pointer(in.CollisionCount)) + out.Conditions = *(*[]v1beta1.StatefulSetCondition)(unsafe.Pointer(&in.Conditions)) return nil } diff --git a/pkg/apis/apps/v1beta1/zz_generated.defaults.go b/pkg/apis/apps/v1beta1/zz_generated.defaults.go index 26fde73fe2d..b28d0de74fc 100644 --- a/pkg/apis/apps/v1beta1/zz_generated.defaults.go +++ b/pkg/apis/apps/v1beta1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,7 +23,7 @@ package v1beta1 import ( v1beta1 "k8s.io/api/apps/v1beta1" runtime "k8s.io/apimachinery/pkg/runtime" - v1 "k8s.io/kubernetes/pkg/api/v1" + v1 "k8s.io/kubernetes/pkg/apis/core/v1" ) // RegisterDefaults adds defaulters functions to the given scheme. diff --git a/pkg/apis/apps/v1beta2/BUILD b/pkg/apis/apps/v1beta2/BUILD index 5498d62855a..b27e6f362ce 100644 --- a/pkg/apis/apps/v1beta2/BUILD +++ b/pkg/apis/apps/v1beta2/BUILD @@ -18,14 +18,16 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/apis/apps/v1beta2", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/v1:go_default_library", "//pkg/apis/apps:go_default_library", + "//pkg/apis/autoscaling:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/v1:go_default_library", "//pkg/apis/extensions:go_default_library", "//vendor/k8s.io/api/apps/v1beta2:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", @@ -54,10 +56,12 @@ go_test( importpath = "k8s.io/kubernetes/pkg/apis/apps/v1beta2_test", deps = [ ":go_default_library", - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/apps:go_default_library", "//pkg/apis/apps/install:go_default_library", + "//pkg/apis/autoscaling:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/install:go_default_library", "//pkg/apis/extensions:go_default_library", "//vendor/k8s.io/api/apps/v1beta2:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/apis/apps/v1beta2/conversion.go b/pkg/apis/apps/v1beta2/conversion.go index 191c2eb56b4..f71de4f28e7 100644 --- a/pkg/apis/apps/v1beta2/conversion.go +++ b/pkg/apis/apps/v1beta2/conversion.go @@ -24,11 +24,13 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/kubernetes/pkg/api" - k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/apis/apps" + autoscaling "k8s.io/kubernetes/pkg/apis/autoscaling" + api "k8s.io/kubernetes/pkg/apis/core" + k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" "k8s.io/kubernetes/pkg/apis/extensions" ) @@ -57,8 +59,8 @@ func addConversionFuncs(scheme *runtime.Scheme) error { // extensions // TODO: below conversions should be dropped in favor of auto-generated // ones, see https://github.com/kubernetes/kubernetes/issues/39865 - Convert_v1beta2_ScaleStatus_To_extensions_ScaleStatus, - Convert_extensions_ScaleStatus_To_v1beta2_ScaleStatus, + Convert_v1beta2_ScaleStatus_To_autoscaling_ScaleStatus, + Convert_autoscaling_ScaleStatus_To_v1beta2_ScaleStatus, Convert_v1beta2_DeploymentSpec_To_extensions_DeploymentSpec, Convert_extensions_DeploymentSpec_To_v1beta2_DeploymentSpec, Convert_v1beta2_DeploymentStrategy_To_extensions_DeploymentStrategy, @@ -119,7 +121,7 @@ func Convert_v1beta2_StatefulSetSpec_To_apps_StatefulSetSpec(in *appsv1beta2.Sta } else { out.Selector = nil } - if err := k8s_api_v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := k8s_api_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if in.VolumeClaimTemplates != nil { @@ -159,7 +161,7 @@ func Convert_apps_StatefulSetSpec_To_v1beta2_StatefulSetSpec(in *apps.StatefulSe } else { out.Selector = nil } - if err := k8s_api_v1.Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := k8s_api_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if in.VolumeClaimTemplates != nil { @@ -223,6 +225,12 @@ func Convert_v1beta2_StatefulSetStatus_To_apps_StatefulSetStatus(in *appsv1beta2 out.CollisionCount = new(int32) *out.CollisionCount = *in.CollisionCount } + out.Conditions = make([]apps.StatefulSetCondition, len(in.Conditions)) + for i := range in.Conditions { + if err := Convert_v1beta2_StatefulSetCondition_To_apps_StatefulSetCondition(&in.Conditions[i], &out.Conditions[i], s); err != nil { + return err + } + } return nil } @@ -240,29 +248,32 @@ func Convert_apps_StatefulSetStatus_To_v1beta2_StatefulSetStatus(in *apps.Statef out.CollisionCount = new(int32) *out.CollisionCount = *in.CollisionCount } - return nil -} - -func Convert_extensions_ScaleStatus_To_v1beta2_ScaleStatus(in *extensions.ScaleStatus, out *appsv1beta2.ScaleStatus, s conversion.Scope) error { - out.Replicas = int32(in.Replicas) - - out.Selector = nil - out.TargetSelector = "" - if in.Selector != nil { - if in.Selector.MatchExpressions == nil || len(in.Selector.MatchExpressions) == 0 { - out.Selector = in.Selector.MatchLabels + out.Conditions = make([]appsv1beta2.StatefulSetCondition, len(in.Conditions)) + for i := range in.Conditions { + if err := Convert_apps_StatefulSetCondition_To_v1beta2_StatefulSetCondition(&in.Conditions[i], &out.Conditions[i], s); err != nil { + return err } - - selector, err := metav1.LabelSelectorAsSelector(in.Selector) - if err != nil { - return fmt.Errorf("invalid label selector: %v", err) - } - out.TargetSelector = selector.String() } return nil } -func Convert_v1beta2_ScaleStatus_To_extensions_ScaleStatus(in *appsv1beta2.ScaleStatus, out *extensions.ScaleStatus, s conversion.Scope) error { +func Convert_autoscaling_ScaleStatus_To_v1beta2_ScaleStatus(in *autoscaling.ScaleStatus, out *appsv1beta2.ScaleStatus, s conversion.Scope) error { + out.Replicas = int32(in.Replicas) + out.TargetSelector = in.Selector + + out.Selector = nil + selector, err := metav1.ParseToLabelSelector(in.Selector) + if err != nil { + return fmt.Errorf("failed to parse selector: %v", err) + } + if len(selector.MatchExpressions) == 0 { + out.Selector = selector.MatchLabels + } + + return nil +} + +func Convert_v1beta2_ScaleStatus_To_autoscaling_ScaleStatus(in *appsv1beta2.ScaleStatus, out *autoscaling.ScaleStatus, s conversion.Scope) error { out.Replicas = in.Replicas // Normally when 2 fields map to the same internal value we favor the old field, since @@ -270,21 +281,15 @@ func Convert_v1beta2_ScaleStatus_To_extensions_ScaleStatus(in *appsv1beta2.Scale // new field can be expected to know about the old field (though that's not quite true, due // to kubectl apply). However, these fields are readonly, so any non-nil value should work. if in.TargetSelector != "" { - labelSelector, err := metav1.ParseToLabelSelector(in.TargetSelector) - if err != nil { - out.Selector = nil - return fmt.Errorf("failed to parse target selector: %v", err) - } - out.Selector = labelSelector + out.Selector = in.TargetSelector } else if in.Selector != nil { - out.Selector = new(metav1.LabelSelector) - selector := make(map[string]string) + set := labels.Set{} for key, val := range in.Selector { - selector[key] = val + set[key] = val } - out.Selector.MatchLabels = selector + out.Selector = labels.SelectorFromSet(set).String() } else { - out.Selector = nil + out.Selector = "" } return nil } @@ -294,7 +299,7 @@ func Convert_v1beta2_DeploymentSpec_To_extensions_DeploymentSpec(in *appsv1beta2 out.Replicas = *in.Replicas } out.Selector = in.Selector - if err := k8s_api_v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := k8s_api_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if err := Convert_v1beta2_DeploymentStrategy_To_extensions_DeploymentStrategy(&in.Strategy, &out.Strategy, s); err != nil { @@ -313,7 +318,7 @@ func Convert_v1beta2_DeploymentSpec_To_extensions_DeploymentSpec(in *appsv1beta2 func Convert_extensions_DeploymentSpec_To_v1beta2_DeploymentSpec(in *extensions.DeploymentSpec, out *appsv1beta2.DeploymentSpec, s conversion.Scope) error { out.Replicas = &in.Replicas out.Selector = in.Selector - if err := k8s_api_v1.Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := k8s_api_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if err := Convert_extensions_DeploymentStrategy_To_v1beta2_DeploymentStrategy(&in.Strategy, &out.Strategy, s); err != nil { @@ -389,7 +394,7 @@ func Convert_extensions_ReplicaSetSpec_To_v1beta2_ReplicaSetSpec(in *extensions. *out.Replicas = int32(in.Replicas) out.MinReadySeconds = in.MinReadySeconds out.Selector = in.Selector - if err := k8s_api_v1.Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := k8s_api_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } return nil @@ -427,7 +432,7 @@ func Convert_v1beta2_ReplicaSetSpec_To_extensions_ReplicaSetSpec(in *appsv1beta2 } out.MinReadySeconds = in.MinReadySeconds out.Selector = in.Selector - if err := k8s_api_v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := k8s_api_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } return nil @@ -473,7 +478,7 @@ func Convert_extensions_DaemonSet_To_v1beta2_DaemonSet(in *extensions.DaemonSet, func Convert_extensions_DaemonSetSpec_To_v1beta2_DaemonSetSpec(in *extensions.DaemonSetSpec, out *appsv1beta2.DaemonSetSpec, s conversion.Scope) error { out.Selector = in.Selector - if err := k8s_api_v1.Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := k8s_api_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if err := Convert_extensions_DaemonSetUpdateStrategy_To_v1beta2_DaemonSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, s); err != nil { @@ -521,7 +526,7 @@ func Convert_v1beta2_DaemonSet_To_extensions_DaemonSet(in *appsv1beta2.DaemonSet func Convert_v1beta2_DaemonSetSpec_To_extensions_DaemonSetSpec(in *appsv1beta2.DaemonSetSpec, out *extensions.DaemonSetSpec, s conversion.Scope) error { out.Selector = in.Selector - if err := k8s_api_v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := k8s_api_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if err := Convert_v1beta2_DaemonSetUpdateStrategy_To_extensions_DaemonSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, s); err != nil { diff --git a/pkg/apis/apps/v1beta2/conversion_test.go b/pkg/apis/apps/v1beta2/conversion_test.go index 211098897c7..7cf4d11a2f6 100644 --- a/pkg/apis/apps/v1beta2/conversion_test.go +++ b/pkg/apis/apps/v1beta2/conversion_test.go @@ -22,8 +22,10 @@ import ( "k8s.io/api/apps/v1beta2" "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/apps" + "k8s.io/kubernetes/pkg/apis/autoscaling" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" apiequality "k8s.io/apimachinery/pkg/api/equality" @@ -82,7 +84,7 @@ func TestV1beta2StatefulSetSpecConversion(t *testing.T) { for k, tc := range testcases { // apps -> v1beta2 internal1 := &v1beta2.StatefulSetSpec{} - if err := api.Scheme.Convert(tc.stsSpec1, internal1, nil); err != nil { + if err := legacyscheme.Scheme.Convert(tc.stsSpec1, internal1, nil); err != nil { t.Errorf("%q - %q: unexpected error: %v", k, "from extensions to v1beta2", err) } @@ -92,7 +94,7 @@ func TestV1beta2StatefulSetSpecConversion(t *testing.T) { // v1beta2 -> apps internal2 := &apps.StatefulSetSpec{} - if err := api.Scheme.Convert(tc.stsSepc2, internal2, nil); err != nil { + if err := legacyscheme.Scheme.Convert(tc.stsSepc2, internal2, nil); err != nil { t.Errorf("%q - %q: unexpected error: %v", k, "from v1beta2 to extensions", err) } if !apiequality.Semantic.DeepEqual(internal2, tc.stsSpec1) { @@ -130,7 +132,7 @@ func TestV1beta2StatefulSetUpdateStrategyConversion(t *testing.T) { for k, tc := range testcases { // apps -> v1beta2 internal1 := &v1beta2.StatefulSetUpdateStrategy{} - if err := api.Scheme.Convert(tc.stsUpdateStrategy1, internal1, nil); err != nil { + if err := legacyscheme.Scheme.Convert(tc.stsUpdateStrategy1, internal1, nil); err != nil { t.Errorf("%q - %q: unexpected error: %v", "apps -> v1beta2", k, err) } @@ -140,7 +142,7 @@ func TestV1beta2StatefulSetUpdateStrategyConversion(t *testing.T) { // v1beta2 -> apps internal2 := &apps.StatefulSetUpdateStrategy{} - if err := api.Scheme.Convert(tc.stsUpdateStrategy2, internal2, nil); err != nil { + if err := legacyscheme.Scheme.Convert(tc.stsUpdateStrategy2, internal2, nil); err != nil { t.Errorf("%q - %q: unexpected error: %v", "v1beta2 -> apps", k, err) } if !apiequality.Semantic.DeepEqual(internal2, tc.stsUpdateStrategy1) { @@ -164,7 +166,7 @@ func TestV1beta2RollingUpdateDaemonSetConversion(t *testing.T) { for k, tc := range testcases { // extensions -> v1beta2 internal1 := &v1beta2.RollingUpdateDaemonSet{} - if err := api.Scheme.Convert(tc.rollingUpdateDs1, internal1, nil); err != nil { + if err := legacyscheme.Scheme.Convert(tc.rollingUpdateDs1, internal1, nil); err != nil { t.Errorf("%q - %q: unexpected error: %v", k, "from extensions to v1beta2", err) } if !apiequality.Semantic.DeepEqual(internal1, tc.rollingUpdateDs2) { @@ -173,7 +175,7 @@ func TestV1beta2RollingUpdateDaemonSetConversion(t *testing.T) { // v1beta2 -> extensions internal2 := &extensions.RollingUpdateDaemonSet{} - if err := api.Scheme.Convert(tc.rollingUpdateDs2, internal2, nil); err != nil { + if err := legacyscheme.Scheme.Convert(tc.rollingUpdateDs2, internal2, nil); err != nil { t.Errorf("%q - %q: unexpected error: %v", k, "from v1beta2 to extensions", err) } if !apiequality.Semantic.DeepEqual(internal2, tc.rollingUpdateDs1) { @@ -238,7 +240,7 @@ func TestV1beta2StatefulSetStatusConversion(t *testing.T) { for k, tc := range testcases { // apps -> v1beta2 internal1 := &v1beta2.StatefulSetStatus{} - if err := api.Scheme.Convert(tc.stsStatus1, internal1, nil); err != nil { + if err := legacyscheme.Scheme.Convert(tc.stsStatus1, internal1, nil); err != nil { t.Errorf("%q - %q: unexpected error: %v", k, "from apps to v1beta2", err) } @@ -248,7 +250,7 @@ func TestV1beta2StatefulSetStatusConversion(t *testing.T) { // v1beta2 -> apps internal2 := &apps.StatefulSetStatus{} - if err := api.Scheme.Convert(tc.stsStatus2, internal2, nil); err != nil { + if err := legacyscheme.Scheme.Convert(tc.stsStatus2, internal2, nil); err != nil { t.Errorf("%q - %q: unexpected error: %v", k, "from v1beta2 to apps", err) } if !apiequality.Semantic.DeepEqual(internal2, tc.stsStatus1) { @@ -318,7 +320,7 @@ func TestV1beta2DeploymentConversion(t *testing.T) { for k, tc := range testcases { // extensions -> v1beta2 internal1 := &v1beta2.Deployment{} - if err := api.Scheme.Convert(tc.deployment1, internal1, nil); err != nil { + if err := legacyscheme.Scheme.Convert(tc.deployment1, internal1, nil); err != nil { t.Errorf("%q - %q: unexpected error: %v", k, "from extensions to v1beta2", err) } if !apiequality.Semantic.DeepEqual(internal1, tc.deployment2) { @@ -327,7 +329,7 @@ func TestV1beta2DeploymentConversion(t *testing.T) { // v1beta2 -> extensions internal2 := &extensions.Deployment{} - if err := api.Scheme.Convert(tc.deployment2, internal2, nil); err != nil { + if err := legacyscheme.Scheme.Convert(tc.deployment2, internal2, nil); err != nil { t.Errorf("%q - %q: unexpected error: %v", k, "from v1beta2 to extensions", err) } if !apiequality.Semantic.DeepEqual(internal2, tc.deployment1) { @@ -348,41 +350,41 @@ func TestV1beta2ScaleStatusConversion(t *testing.T) { labelsSelector2, _ := metav1.LabelSelectorAsSelector(selector2) testcases := map[string]struct { - scaleStatus1 *extensions.ScaleStatus + scaleStatus1 *autoscaling.ScaleStatus scaleStatus2 *v1beta2.ScaleStatus }{ "ScaleStatus Conversion 1": { - scaleStatus1: &extensions.ScaleStatus{Replicas: 2}, + scaleStatus1: &autoscaling.ScaleStatus{Replicas: 2}, scaleStatus2: &v1beta2.ScaleStatus{Replicas: 2}, }, "ScaleStatus Conversion 2": { - scaleStatus1: &extensions.ScaleStatus{Replicas: 2, Selector: selector1}, + scaleStatus1: &autoscaling.ScaleStatus{Replicas: 2, Selector: labelsSelector1.String()}, scaleStatus2: &v1beta2.ScaleStatus{Replicas: 2, Selector: matchLabels, TargetSelector: labelsSelector1.String()}, }, "ScaleStatus Conversion 3": { - scaleStatus1: &extensions.ScaleStatus{Replicas: 2, Selector: selector2}, + scaleStatus1: &autoscaling.ScaleStatus{Replicas: 2, Selector: labelsSelector2.String()}, scaleStatus2: &v1beta2.ScaleStatus{Replicas: 2, Selector: map[string]string{}, TargetSelector: labelsSelector2.String()}, }, } for k, tc := range testcases { - // extensions -> v1beta2 + // autoscaling -> v1beta2 internal1 := &v1beta2.ScaleStatus{} - if err := api.Scheme.Convert(tc.scaleStatus1, internal1, nil); err != nil { - t.Errorf("%q - %q: unexpected error: %v", k, "extensions -> v1beta2", err) + if err := legacyscheme.Scheme.Convert(tc.scaleStatus1, internal1, nil); err != nil { + t.Errorf("%q - %q: unexpected error: %v", k, "autoscaling -> v1beta2", err) } if !apiequality.Semantic.DeepEqual(internal1, tc.scaleStatus2) { - t.Errorf("%q - %q: expected\n\t%#v, got \n\t%#v", k, "extensions -> v1beta2", tc.scaleStatus2, internal1) + t.Errorf("%q - %q: expected\n\t%#v, got \n\t%#v", k, "autoscaling -> v1beta2", tc.scaleStatus2, internal1) } - // v1beta2 -> extensions - internal2 := &extensions.ScaleStatus{} - if err := api.Scheme.Convert(tc.scaleStatus2, internal2, nil); err != nil { - t.Errorf("%q - %q: unexpected error: %v", k, "v1beta2 -> extensions", err) + // v1beta2 -> autoscaling + internal2 := &autoscaling.ScaleStatus{} + if err := legacyscheme.Scheme.Convert(tc.scaleStatus2, internal2, nil); err != nil { + t.Errorf("%q - %q: unexpected error: %v", k, "v1beta2 -> autoscaling", err) } if !apiequality.Semantic.DeepEqual(internal2, tc.scaleStatus1) { - t.Errorf("%q - %q: expected\n\t%+v, got \n\t%+v", k, "v1beta2 -> extensions", tc.scaleStatus1, internal2) + t.Errorf("%q - %q: expected\n\t%+v, got \n\t%+v", k, "v1beta2 -> autoscaling", tc.scaleStatus1, internal2) } } } @@ -463,7 +465,7 @@ func TestV1beta2DeploymentSpecConversion(t *testing.T) { // extensions -> v1beta2 for k, tc := range testcases { internal := &v1beta2.DeploymentSpec{} - if err := api.Scheme.Convert(tc.deploymentSpec1, internal, nil); err != nil { + if err := legacyscheme.Scheme.Convert(tc.deploymentSpec1, internal, nil); err != nil { t.Errorf("%q - %q: unexpected error: %v", "extensions -> v1beta2", k, err) } @@ -475,7 +477,7 @@ func TestV1beta2DeploymentSpecConversion(t *testing.T) { // v1beta2 -> extensions for k, tc := range testcases { internal := &extensions.DeploymentSpec{} - if err := api.Scheme.Convert(tc.deploymentSpec2, internal, nil); err != nil { + if err := legacyscheme.Scheme.Convert(tc.deploymentSpec2, internal, nil); err != nil { t.Errorf("%q - %q: unexpected error: %v", "v1beta2 -> extensions", k, err) } if !apiequality.Semantic.DeepEqual(internal, tc.deploymentSpec1) { @@ -507,7 +509,7 @@ func TestV1beta2DeploymentStrategyConversion(t *testing.T) { for k, tc := range testcases { // extensions -> v1beta2 internal1 := &v1beta2.DeploymentStrategy{} - if err := api.Scheme.Convert(tc.deploymentStrategy1, internal1, nil); err != nil { + if err := legacyscheme.Scheme.Convert(tc.deploymentStrategy1, internal1, nil); err != nil { t.Errorf("%q - %q: unexpected error: %v", k, "extensions -> v1beta2", err) } if !apiequality.Semantic.DeepEqual(internal1, tc.deploymentStrategy2) { @@ -516,7 +518,7 @@ func TestV1beta2DeploymentStrategyConversion(t *testing.T) { // v1beta2 -> extensions internal2 := &extensions.DeploymentStrategy{} - if err := api.Scheme.Convert(tc.deploymentStrategy2, internal2, nil); err != nil { + if err := legacyscheme.Scheme.Convert(tc.deploymentStrategy2, internal2, nil); err != nil { t.Errorf("%q - %q: unexpected error: %v", k, "v1beta2 -> extensions", err) } if !apiequality.Semantic.DeepEqual(internal2, tc.deploymentStrategy1) { @@ -554,7 +556,7 @@ func TestV1beta2RollingUpdateDeploymentConversion(t *testing.T) { for k, tc := range testcases { // extensions -> v1beta2 internal1 := &v1beta2.RollingUpdateDeployment{} - if err := api.Scheme.Convert(tc.rollingUpdateDeployment1, internal1, nil); err != nil { + if err := legacyscheme.Scheme.Convert(tc.rollingUpdateDeployment1, internal1, nil); err != nil { t.Errorf("%q - %q: unexpected error: %v", k, "extensions -> v1beta2", err) } if !apiequality.Semantic.DeepEqual(internal1, tc.rollingUpdateDeployment2) { @@ -563,7 +565,7 @@ func TestV1beta2RollingUpdateDeploymentConversion(t *testing.T) { // v1beta2 -> extensions internal2 := &extensions.RollingUpdateDeployment{} - if err := api.Scheme.Convert(tc.rollingUpdateDeployment2, internal2, nil); err != nil { + if err := legacyscheme.Scheme.Convert(tc.rollingUpdateDeployment2, internal2, nil); err != nil { t.Errorf("%q - %q: unexpected error: %v", k, "v1beta2 -> extensions", err) } if !apiequality.Semantic.DeepEqual(internal2, tc.rollingUpdateDeployment1) { @@ -630,7 +632,7 @@ func TestV1beta2ReplicaSetSpecConversion(t *testing.T) { for k, tc := range testcases { // extensions -> v1beta2 internal1 := &v1beta2.ReplicaSetSpec{} - if err := api.Scheme.Convert(tc.replicaset1, internal1, nil); err != nil { + if err := legacyscheme.Scheme.Convert(tc.replicaset1, internal1, nil); err != nil { t.Errorf("%q - %q: unexpected error: %v", k, "extensions -> v1beta2", err) } @@ -640,7 +642,7 @@ func TestV1beta2ReplicaSetSpecConversion(t *testing.T) { // v1beta2 -> extensions internal2 := &extensions.ReplicaSetSpec{} - if err := api.Scheme.Convert(tc.replicaset2, internal2, nil); err != nil { + if err := legacyscheme.Scheme.Convert(tc.replicaset2, internal2, nil); err != nil { t.Errorf("%q - %q: unexpected error: %v", k, "v1beta2 -> extensions", err) } if !apiequality.Semantic.DeepEqual(internal2, tc.replicaset1) { diff --git a/pkg/apis/apps/v1beta2/defaults_test.go b/pkg/apis/apps/v1beta2/defaults_test.go index 877ac9538e3..86cec68806b 100644 --- a/pkg/apis/apps/v1beta2/defaults_test.go +++ b/pkg/apis/apps/v1beta2/defaults_test.go @@ -27,10 +27,11 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/kubernetes/pkg/api" - _ "k8s.io/kubernetes/pkg/api/install" + "k8s.io/kubernetes/pkg/api/legacyscheme" _ "k8s.io/kubernetes/pkg/apis/apps/install" . "k8s.io/kubernetes/pkg/apis/apps/v1beta2" + api "k8s.io/kubernetes/pkg/apis/core" + _ "k8s.io/kubernetes/pkg/apis/core/install" ) func TestSetDefaultDaemonSetSpec(t *testing.T) { @@ -544,18 +545,18 @@ func TestDefaultRequestIsNotSetForReplicaSet(t *testing.T) { } func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { - data, err := runtime.Encode(api.Codecs.LegacyCodec(SchemeGroupVersion), obj) + 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(api.Codecs.UniversalDecoder(), data) + 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 = api.Scheme.Convert(obj2, obj3, nil) + err = legacyscheme.Scheme.Convert(obj2, obj3, nil) if err != nil { t.Errorf("%v\nSource: %#v", err, obj2) return nil diff --git a/pkg/apis/apps/v1beta2/doc.go b/pkg/apis/apps/v1beta2/doc.go index 9a0bb581994..cad91c57c3c 100644 --- a/pkg/apis/apps/v1beta2/doc.go +++ b/pkg/apis/apps/v1beta2/doc.go @@ -15,8 +15,9 @@ limitations under the License. */ // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/apps +// +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/autoscaling // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/extensions -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/apps/v1beta2 +// +k8s:conversion-gen-external-types=k8s.io/api/apps/v1beta2 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/apps/v1beta2 diff --git a/pkg/apis/apps/v1beta2/zz_generated.conversion.go b/pkg/apis/apps/v1beta2/zz_generated.conversion.go index 4624fa42bf0..a6325356007 100644 --- a/pkg/apis/apps/v1beta2/zz_generated.conversion.go +++ b/pkg/apis/apps/v1beta2/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,16 +21,18 @@ limitations under the License. package v1beta2 import ( + unsafe "unsafe" + v1beta2 "k8s.io/api/apps/v1beta2" - core_v1 "k8s.io/api/core/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/api/core/v1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - api "k8s.io/kubernetes/pkg/api" - api_v1 "k8s.io/kubernetes/pkg/api/v1" apps "k8s.io/kubernetes/pkg/apis/apps" + autoscaling "k8s.io/kubernetes/pkg/apis/autoscaling" + core "k8s.io/kubernetes/pkg/apis/core" + core_v1 "k8s.io/kubernetes/pkg/apis/core/v1" extensions "k8s.io/kubernetes/pkg/apis/extensions" - unsafe "unsafe" ) func init() { @@ -47,6 +49,8 @@ func RegisterConversions(scheme *runtime.Scheme) error { Convert_apps_ControllerRevisionList_To_v1beta2_ControllerRevisionList, Convert_v1beta2_DaemonSet_To_extensions_DaemonSet, Convert_extensions_DaemonSet_To_v1beta2_DaemonSet, + Convert_v1beta2_DaemonSetCondition_To_extensions_DaemonSetCondition, + Convert_extensions_DaemonSetCondition_To_v1beta2_DaemonSetCondition, Convert_v1beta2_DaemonSetList_To_extensions_DaemonSetList, Convert_extensions_DaemonSetList_To_v1beta2_DaemonSetList, Convert_v1beta2_DaemonSetSpec_To_extensions_DaemonSetSpec, @@ -83,14 +87,16 @@ func RegisterConversions(scheme *runtime.Scheme) error { Convert_extensions_RollingUpdateDeployment_To_v1beta2_RollingUpdateDeployment, Convert_v1beta2_RollingUpdateStatefulSetStrategy_To_apps_RollingUpdateStatefulSetStrategy, Convert_apps_RollingUpdateStatefulSetStrategy_To_v1beta2_RollingUpdateStatefulSetStrategy, - Convert_v1beta2_Scale_To_extensions_Scale, - Convert_extensions_Scale_To_v1beta2_Scale, - Convert_v1beta2_ScaleSpec_To_extensions_ScaleSpec, - Convert_extensions_ScaleSpec_To_v1beta2_ScaleSpec, - Convert_v1beta2_ScaleStatus_To_extensions_ScaleStatus, - Convert_extensions_ScaleStatus_To_v1beta2_ScaleStatus, + Convert_v1beta2_Scale_To_autoscaling_Scale, + Convert_autoscaling_Scale_To_v1beta2_Scale, + Convert_v1beta2_ScaleSpec_To_autoscaling_ScaleSpec, + Convert_autoscaling_ScaleSpec_To_v1beta2_ScaleSpec, + Convert_v1beta2_ScaleStatus_To_autoscaling_ScaleStatus, + Convert_autoscaling_ScaleStatus_To_v1beta2_ScaleStatus, Convert_v1beta2_StatefulSet_To_apps_StatefulSet, Convert_apps_StatefulSet_To_v1beta2_StatefulSet, + Convert_v1beta2_StatefulSetCondition_To_apps_StatefulSetCondition, + Convert_apps_StatefulSetCondition_To_v1beta2_StatefulSetCondition, Convert_v1beta2_StatefulSetList_To_apps_StatefulSetList, Convert_apps_StatefulSetList_To_v1beta2_StatefulSetList, Convert_v1beta2_StatefulSetSpec_To_apps_StatefulSetSpec, @@ -194,6 +200,34 @@ func autoConvert_extensions_DaemonSet_To_v1beta2_DaemonSet(in *extensions.Daemon return nil } +func autoConvert_v1beta2_DaemonSetCondition_To_extensions_DaemonSetCondition(in *v1beta2.DaemonSetCondition, out *extensions.DaemonSetCondition, s conversion.Scope) error { + out.Type = extensions.DaemonSetConditionType(in.Type) + out.Status = core.ConditionStatus(in.Status) + out.LastTransitionTime = in.LastTransitionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_v1beta2_DaemonSetCondition_To_extensions_DaemonSetCondition is an autogenerated conversion function. +func Convert_v1beta2_DaemonSetCondition_To_extensions_DaemonSetCondition(in *v1beta2.DaemonSetCondition, out *extensions.DaemonSetCondition, s conversion.Scope) error { + return autoConvert_v1beta2_DaemonSetCondition_To_extensions_DaemonSetCondition(in, out, s) +} + +func autoConvert_extensions_DaemonSetCondition_To_v1beta2_DaemonSetCondition(in *extensions.DaemonSetCondition, out *v1beta2.DaemonSetCondition, s conversion.Scope) error { + out.Type = v1beta2.DaemonSetConditionType(in.Type) + out.Status = v1.ConditionStatus(in.Status) + out.LastTransitionTime = in.LastTransitionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_extensions_DaemonSetCondition_To_v1beta2_DaemonSetCondition is an autogenerated conversion function. +func Convert_extensions_DaemonSetCondition_To_v1beta2_DaemonSetCondition(in *extensions.DaemonSetCondition, out *v1beta2.DaemonSetCondition, s conversion.Scope) error { + return autoConvert_extensions_DaemonSetCondition_To_v1beta2_DaemonSetCondition(in, out, s) +} + func autoConvert_v1beta2_DaemonSetList_To_extensions_DaemonSetList(in *v1beta2.DaemonSetList, out *extensions.DaemonSetList, s conversion.Scope) error { out.ListMeta = in.ListMeta if in.Items != nil { @@ -237,8 +271,8 @@ func Convert_extensions_DaemonSetList_To_v1beta2_DaemonSetList(in *extensions.Da } func autoConvert_v1beta2_DaemonSetSpec_To_extensions_DaemonSetSpec(in *v1beta2.DaemonSetSpec, out *extensions.DaemonSetSpec, s conversion.Scope) error { - out.Selector = (*v1.LabelSelector)(unsafe.Pointer(in.Selector)) - if err := api_v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) + if err := core_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if err := Convert_v1beta2_DaemonSetUpdateStrategy_To_extensions_DaemonSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, s); err != nil { @@ -250,8 +284,8 @@ func autoConvert_v1beta2_DaemonSetSpec_To_extensions_DaemonSetSpec(in *v1beta2.D } func autoConvert_extensions_DaemonSetSpec_To_v1beta2_DaemonSetSpec(in *extensions.DaemonSetSpec, out *v1beta2.DaemonSetSpec, s conversion.Scope) error { - out.Selector = (*v1.LabelSelector)(unsafe.Pointer(in.Selector)) - if err := api_v1.Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) + if err := core_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if err := Convert_extensions_DaemonSetUpdateStrategy_To_v1beta2_DaemonSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, s); err != nil { @@ -273,6 +307,7 @@ func autoConvert_v1beta2_DaemonSetStatus_To_extensions_DaemonSetStatus(in *v1bet out.NumberAvailable = in.NumberAvailable out.NumberUnavailable = in.NumberUnavailable out.CollisionCount = (*int32)(unsafe.Pointer(in.CollisionCount)) + out.Conditions = *(*[]extensions.DaemonSetCondition)(unsafe.Pointer(&in.Conditions)) return nil } @@ -291,6 +326,7 @@ func autoConvert_extensions_DaemonSetStatus_To_v1beta2_DaemonSetStatus(in *exten out.NumberAvailable = in.NumberAvailable out.NumberUnavailable = in.NumberUnavailable out.CollisionCount = (*int32)(unsafe.Pointer(in.CollisionCount)) + out.Conditions = *(*[]v1beta2.DaemonSetCondition)(unsafe.Pointer(&in.Conditions)) return nil } @@ -351,7 +387,7 @@ func autoConvert_extensions_Deployment_To_v1beta2_Deployment(in *extensions.Depl func autoConvert_v1beta2_DeploymentCondition_To_extensions_DeploymentCondition(in *v1beta2.DeploymentCondition, out *extensions.DeploymentCondition, s conversion.Scope) error { out.Type = extensions.DeploymentConditionType(in.Type) - out.Status = api.ConditionStatus(in.Status) + out.Status = core.ConditionStatus(in.Status) out.LastUpdateTime = in.LastUpdateTime out.LastTransitionTime = in.LastTransitionTime out.Reason = in.Reason @@ -366,7 +402,7 @@ func Convert_v1beta2_DeploymentCondition_To_extensions_DeploymentCondition(in *v func autoConvert_extensions_DeploymentCondition_To_v1beta2_DeploymentCondition(in *extensions.DeploymentCondition, out *v1beta2.DeploymentCondition, s conversion.Scope) error { out.Type = v1beta2.DeploymentConditionType(in.Type) - out.Status = core_v1.ConditionStatus(in.Status) + out.Status = v1.ConditionStatus(in.Status) out.LastUpdateTime = in.LastUpdateTime out.LastTransitionTime = in.LastTransitionTime out.Reason = in.Reason @@ -422,11 +458,11 @@ func Convert_extensions_DeploymentList_To_v1beta2_DeploymentList(in *extensions. } func autoConvert_v1beta2_DeploymentSpec_To_extensions_DeploymentSpec(in *v1beta2.DeploymentSpec, out *extensions.DeploymentSpec, s conversion.Scope) error { - if err := v1.Convert_Pointer_int32_To_int32(&in.Replicas, &out.Replicas, s); err != nil { + if err := meta_v1.Convert_Pointer_int32_To_int32(&in.Replicas, &out.Replicas, s); err != nil { return err } - out.Selector = (*v1.LabelSelector)(unsafe.Pointer(in.Selector)) - if err := api_v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) + if err := core_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if err := Convert_v1beta2_DeploymentStrategy_To_extensions_DeploymentStrategy(&in.Strategy, &out.Strategy, s); err != nil { @@ -440,11 +476,11 @@ func autoConvert_v1beta2_DeploymentSpec_To_extensions_DeploymentSpec(in *v1beta2 } func autoConvert_extensions_DeploymentSpec_To_v1beta2_DeploymentSpec(in *extensions.DeploymentSpec, out *v1beta2.DeploymentSpec, s conversion.Scope) error { - if err := v1.Convert_int32_To_Pointer_int32(&in.Replicas, &out.Replicas, s); err != nil { + if err := meta_v1.Convert_int32_To_Pointer_int32(&in.Replicas, &out.Replicas, s); err != nil { return err } - out.Selector = (*v1.LabelSelector)(unsafe.Pointer(in.Selector)) - if err := api_v1.Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) + if err := core_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if err := Convert_extensions_DeploymentStrategy_To_v1beta2_DeploymentStrategy(&in.Strategy, &out.Strategy, s); err != nil { @@ -554,7 +590,7 @@ func Convert_extensions_ReplicaSet_To_v1beta2_ReplicaSet(in *extensions.ReplicaS func autoConvert_v1beta2_ReplicaSetCondition_To_extensions_ReplicaSetCondition(in *v1beta2.ReplicaSetCondition, out *extensions.ReplicaSetCondition, s conversion.Scope) error { out.Type = extensions.ReplicaSetConditionType(in.Type) - out.Status = api.ConditionStatus(in.Status) + out.Status = core.ConditionStatus(in.Status) out.LastTransitionTime = in.LastTransitionTime out.Reason = in.Reason out.Message = in.Message @@ -568,7 +604,7 @@ func Convert_v1beta2_ReplicaSetCondition_To_extensions_ReplicaSetCondition(in *v func autoConvert_extensions_ReplicaSetCondition_To_v1beta2_ReplicaSetCondition(in *extensions.ReplicaSetCondition, out *v1beta2.ReplicaSetCondition, s conversion.Scope) error { out.Type = v1beta2.ReplicaSetConditionType(in.Type) - out.Status = core_v1.ConditionStatus(in.Status) + out.Status = v1.ConditionStatus(in.Status) out.LastTransitionTime = in.LastTransitionTime out.Reason = in.Reason out.Message = in.Message @@ -623,24 +659,24 @@ func Convert_extensions_ReplicaSetList_To_v1beta2_ReplicaSetList(in *extensions. } func autoConvert_v1beta2_ReplicaSetSpec_To_extensions_ReplicaSetSpec(in *v1beta2.ReplicaSetSpec, out *extensions.ReplicaSetSpec, s conversion.Scope) error { - if err := v1.Convert_Pointer_int32_To_int32(&in.Replicas, &out.Replicas, s); err != nil { + if err := meta_v1.Convert_Pointer_int32_To_int32(&in.Replicas, &out.Replicas, s); err != nil { return err } out.MinReadySeconds = in.MinReadySeconds - out.Selector = (*v1.LabelSelector)(unsafe.Pointer(in.Selector)) - if err := api_v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) + if err := core_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } return nil } func autoConvert_extensions_ReplicaSetSpec_To_v1beta2_ReplicaSetSpec(in *extensions.ReplicaSetSpec, out *v1beta2.ReplicaSetSpec, s conversion.Scope) error { - if err := v1.Convert_int32_To_Pointer_int32(&in.Replicas, &out.Replicas, s); err != nil { + if err := meta_v1.Convert_int32_To_Pointer_int32(&in.Replicas, &out.Replicas, s); err != nil { return err } out.MinReadySeconds = in.MinReadySeconds - out.Selector = (*v1.LabelSelector)(unsafe.Pointer(in.Selector)) - if err := api_v1.Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) + if err := core_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } return nil @@ -699,7 +735,7 @@ func autoConvert_extensions_RollingUpdateDeployment_To_v1beta2_RollingUpdateDepl } func autoConvert_v1beta2_RollingUpdateStatefulSetStrategy_To_apps_RollingUpdateStatefulSetStrategy(in *v1beta2.RollingUpdateStatefulSetStrategy, out *apps.RollingUpdateStatefulSetStrategy, s conversion.Scope) error { - if err := v1.Convert_Pointer_int32_To_int32(&in.Partition, &out.Partition, s); err != nil { + if err := meta_v1.Convert_Pointer_int32_To_int32(&in.Partition, &out.Partition, s); err != nil { return err } return nil @@ -711,7 +747,7 @@ func Convert_v1beta2_RollingUpdateStatefulSetStrategy_To_apps_RollingUpdateState } func autoConvert_apps_RollingUpdateStatefulSetStrategy_To_v1beta2_RollingUpdateStatefulSetStrategy(in *apps.RollingUpdateStatefulSetStrategy, out *v1beta2.RollingUpdateStatefulSetStrategy, s conversion.Scope) error { - if err := v1.Convert_int32_To_Pointer_int32(&in.Partition, &out.Partition, s); err != nil { + if err := meta_v1.Convert_int32_To_Pointer_int32(&in.Partition, &out.Partition, s); err != nil { return err } return nil @@ -722,68 +758,68 @@ func Convert_apps_RollingUpdateStatefulSetStrategy_To_v1beta2_RollingUpdateState return autoConvert_apps_RollingUpdateStatefulSetStrategy_To_v1beta2_RollingUpdateStatefulSetStrategy(in, out, s) } -func autoConvert_v1beta2_Scale_To_extensions_Scale(in *v1beta2.Scale, out *extensions.Scale, s conversion.Scope) error { +func autoConvert_v1beta2_Scale_To_autoscaling_Scale(in *v1beta2.Scale, out *autoscaling.Scale, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta - if err := Convert_v1beta2_ScaleSpec_To_extensions_ScaleSpec(&in.Spec, &out.Spec, s); err != nil { + if err := Convert_v1beta2_ScaleSpec_To_autoscaling_ScaleSpec(&in.Spec, &out.Spec, s); err != nil { return err } - if err := Convert_v1beta2_ScaleStatus_To_extensions_ScaleStatus(&in.Status, &out.Status, s); err != nil { + if err := Convert_v1beta2_ScaleStatus_To_autoscaling_ScaleStatus(&in.Status, &out.Status, s); err != nil { return err } return nil } -// Convert_v1beta2_Scale_To_extensions_Scale is an autogenerated conversion function. -func Convert_v1beta2_Scale_To_extensions_Scale(in *v1beta2.Scale, out *extensions.Scale, s conversion.Scope) error { - return autoConvert_v1beta2_Scale_To_extensions_Scale(in, out, s) +// Convert_v1beta2_Scale_To_autoscaling_Scale is an autogenerated conversion function. +func Convert_v1beta2_Scale_To_autoscaling_Scale(in *v1beta2.Scale, out *autoscaling.Scale, s conversion.Scope) error { + return autoConvert_v1beta2_Scale_To_autoscaling_Scale(in, out, s) } -func autoConvert_extensions_Scale_To_v1beta2_Scale(in *extensions.Scale, out *v1beta2.Scale, s conversion.Scope) error { +func autoConvert_autoscaling_Scale_To_v1beta2_Scale(in *autoscaling.Scale, out *v1beta2.Scale, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta - if err := Convert_extensions_ScaleSpec_To_v1beta2_ScaleSpec(&in.Spec, &out.Spec, s); err != nil { + if err := Convert_autoscaling_ScaleSpec_To_v1beta2_ScaleSpec(&in.Spec, &out.Spec, s); err != nil { return err } - if err := Convert_extensions_ScaleStatus_To_v1beta2_ScaleStatus(&in.Status, &out.Status, s); err != nil { + if err := Convert_autoscaling_ScaleStatus_To_v1beta2_ScaleStatus(&in.Status, &out.Status, s); err != nil { return err } return nil } -// Convert_extensions_Scale_To_v1beta2_Scale is an autogenerated conversion function. -func Convert_extensions_Scale_To_v1beta2_Scale(in *extensions.Scale, out *v1beta2.Scale, s conversion.Scope) error { - return autoConvert_extensions_Scale_To_v1beta2_Scale(in, out, s) +// Convert_autoscaling_Scale_To_v1beta2_Scale is an autogenerated conversion function. +func Convert_autoscaling_Scale_To_v1beta2_Scale(in *autoscaling.Scale, out *v1beta2.Scale, s conversion.Scope) error { + return autoConvert_autoscaling_Scale_To_v1beta2_Scale(in, out, s) } -func autoConvert_v1beta2_ScaleSpec_To_extensions_ScaleSpec(in *v1beta2.ScaleSpec, out *extensions.ScaleSpec, s conversion.Scope) error { +func autoConvert_v1beta2_ScaleSpec_To_autoscaling_ScaleSpec(in *v1beta2.ScaleSpec, out *autoscaling.ScaleSpec, s conversion.Scope) error { out.Replicas = in.Replicas return nil } -// Convert_v1beta2_ScaleSpec_To_extensions_ScaleSpec is an autogenerated conversion function. -func Convert_v1beta2_ScaleSpec_To_extensions_ScaleSpec(in *v1beta2.ScaleSpec, out *extensions.ScaleSpec, s conversion.Scope) error { - return autoConvert_v1beta2_ScaleSpec_To_extensions_ScaleSpec(in, out, s) +// Convert_v1beta2_ScaleSpec_To_autoscaling_ScaleSpec is an autogenerated conversion function. +func Convert_v1beta2_ScaleSpec_To_autoscaling_ScaleSpec(in *v1beta2.ScaleSpec, out *autoscaling.ScaleSpec, s conversion.Scope) error { + return autoConvert_v1beta2_ScaleSpec_To_autoscaling_ScaleSpec(in, out, s) } -func autoConvert_extensions_ScaleSpec_To_v1beta2_ScaleSpec(in *extensions.ScaleSpec, out *v1beta2.ScaleSpec, s conversion.Scope) error { +func autoConvert_autoscaling_ScaleSpec_To_v1beta2_ScaleSpec(in *autoscaling.ScaleSpec, out *v1beta2.ScaleSpec, s conversion.Scope) error { out.Replicas = in.Replicas return nil } -// Convert_extensions_ScaleSpec_To_v1beta2_ScaleSpec is an autogenerated conversion function. -func Convert_extensions_ScaleSpec_To_v1beta2_ScaleSpec(in *extensions.ScaleSpec, out *v1beta2.ScaleSpec, s conversion.Scope) error { - return autoConvert_extensions_ScaleSpec_To_v1beta2_ScaleSpec(in, out, s) +// Convert_autoscaling_ScaleSpec_To_v1beta2_ScaleSpec is an autogenerated conversion function. +func Convert_autoscaling_ScaleSpec_To_v1beta2_ScaleSpec(in *autoscaling.ScaleSpec, out *v1beta2.ScaleSpec, s conversion.Scope) error { + return autoConvert_autoscaling_ScaleSpec_To_v1beta2_ScaleSpec(in, out, s) } -func autoConvert_v1beta2_ScaleStatus_To_extensions_ScaleStatus(in *v1beta2.ScaleStatus, out *extensions.ScaleStatus, s conversion.Scope) error { +func autoConvert_v1beta2_ScaleStatus_To_autoscaling_ScaleStatus(in *v1beta2.ScaleStatus, out *autoscaling.ScaleStatus, s conversion.Scope) error { out.Replicas = in.Replicas - // WARNING: in.Selector requires manual conversion: inconvertible types (map[string]string vs *k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector) + // WARNING: in.Selector requires manual conversion: inconvertible types (map[string]string vs string) // WARNING: in.TargetSelector requires manual conversion: does not exist in peer-type return nil } -func autoConvert_extensions_ScaleStatus_To_v1beta2_ScaleStatus(in *extensions.ScaleStatus, out *v1beta2.ScaleStatus, s conversion.Scope) error { +func autoConvert_autoscaling_ScaleStatus_To_v1beta2_ScaleStatus(in *autoscaling.ScaleStatus, out *v1beta2.ScaleStatus, s conversion.Scope) error { out.Replicas = in.Replicas - // WARNING: in.Selector requires manual conversion: inconvertible types (*k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector vs map[string]string) + // WARNING: in.Selector requires manual conversion: inconvertible types (string vs map[string]string) return nil } @@ -819,6 +855,34 @@ func Convert_apps_StatefulSet_To_v1beta2_StatefulSet(in *apps.StatefulSet, out * return autoConvert_apps_StatefulSet_To_v1beta2_StatefulSet(in, out, s) } +func autoConvert_v1beta2_StatefulSetCondition_To_apps_StatefulSetCondition(in *v1beta2.StatefulSetCondition, out *apps.StatefulSetCondition, s conversion.Scope) error { + out.Type = apps.StatefulSetConditionType(in.Type) + out.Status = core.ConditionStatus(in.Status) + out.LastTransitionTime = in.LastTransitionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_v1beta2_StatefulSetCondition_To_apps_StatefulSetCondition is an autogenerated conversion function. +func Convert_v1beta2_StatefulSetCondition_To_apps_StatefulSetCondition(in *v1beta2.StatefulSetCondition, out *apps.StatefulSetCondition, s conversion.Scope) error { + return autoConvert_v1beta2_StatefulSetCondition_To_apps_StatefulSetCondition(in, out, s) +} + +func autoConvert_apps_StatefulSetCondition_To_v1beta2_StatefulSetCondition(in *apps.StatefulSetCondition, out *v1beta2.StatefulSetCondition, s conversion.Scope) error { + out.Type = v1beta2.StatefulSetConditionType(in.Type) + out.Status = v1.ConditionStatus(in.Status) + out.LastTransitionTime = in.LastTransitionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_apps_StatefulSetCondition_To_v1beta2_StatefulSetCondition is an autogenerated conversion function. +func Convert_apps_StatefulSetCondition_To_v1beta2_StatefulSetCondition(in *apps.StatefulSetCondition, out *v1beta2.StatefulSetCondition, s conversion.Scope) error { + return autoConvert_apps_StatefulSetCondition_To_v1beta2_StatefulSetCondition(in, out, s) +} + func autoConvert_v1beta2_StatefulSetList_To_apps_StatefulSetList(in *v1beta2.StatefulSetList, out *apps.StatefulSetList, s conversion.Scope) error { out.ListMeta = in.ListMeta if in.Items != nil { @@ -862,14 +926,14 @@ func Convert_apps_StatefulSetList_To_v1beta2_StatefulSetList(in *apps.StatefulSe } func autoConvert_v1beta2_StatefulSetSpec_To_apps_StatefulSetSpec(in *v1beta2.StatefulSetSpec, out *apps.StatefulSetSpec, s conversion.Scope) error { - if err := v1.Convert_Pointer_int32_To_int32(&in.Replicas, &out.Replicas, s); err != nil { + if err := meta_v1.Convert_Pointer_int32_To_int32(&in.Replicas, &out.Replicas, s); err != nil { return err } - out.Selector = (*v1.LabelSelector)(unsafe.Pointer(in.Selector)) - if err := api_v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) + if err := core_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } - out.VolumeClaimTemplates = *(*[]api.PersistentVolumeClaim)(unsafe.Pointer(&in.VolumeClaimTemplates)) + out.VolumeClaimTemplates = *(*[]core.PersistentVolumeClaim)(unsafe.Pointer(&in.VolumeClaimTemplates)) out.ServiceName = in.ServiceName out.PodManagementPolicy = apps.PodManagementPolicyType(in.PodManagementPolicy) if err := Convert_v1beta2_StatefulSetUpdateStrategy_To_apps_StatefulSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, s); err != nil { @@ -880,14 +944,14 @@ func autoConvert_v1beta2_StatefulSetSpec_To_apps_StatefulSetSpec(in *v1beta2.Sta } func autoConvert_apps_StatefulSetSpec_To_v1beta2_StatefulSetSpec(in *apps.StatefulSetSpec, out *v1beta2.StatefulSetSpec, s conversion.Scope) error { - if err := v1.Convert_int32_To_Pointer_int32(&in.Replicas, &out.Replicas, s); err != nil { + if err := meta_v1.Convert_int32_To_Pointer_int32(&in.Replicas, &out.Replicas, s); err != nil { return err } - out.Selector = (*v1.LabelSelector)(unsafe.Pointer(in.Selector)) - if err := api_v1.Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) + if err := core_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } - out.VolumeClaimTemplates = *(*[]core_v1.PersistentVolumeClaim)(unsafe.Pointer(&in.VolumeClaimTemplates)) + out.VolumeClaimTemplates = *(*[]v1.PersistentVolumeClaim)(unsafe.Pointer(&in.VolumeClaimTemplates)) out.ServiceName = in.ServiceName out.PodManagementPolicy = v1beta2.PodManagementPolicyType(in.PodManagementPolicy) if err := Convert_apps_StatefulSetUpdateStrategy_To_v1beta2_StatefulSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, s); err != nil { @@ -906,6 +970,7 @@ func autoConvert_v1beta2_StatefulSetStatus_To_apps_StatefulSetStatus(in *v1beta2 out.CurrentRevision = in.CurrentRevision out.UpdateRevision = in.UpdateRevision out.CollisionCount = (*int32)(unsafe.Pointer(in.CollisionCount)) + out.Conditions = *(*[]apps.StatefulSetCondition)(unsafe.Pointer(&in.Conditions)) return nil } @@ -918,6 +983,7 @@ func autoConvert_apps_StatefulSetStatus_To_v1beta2_StatefulSetStatus(in *apps.St out.CurrentRevision = in.CurrentRevision out.UpdateRevision = in.UpdateRevision out.CollisionCount = (*int32)(unsafe.Pointer(in.CollisionCount)) + out.Conditions = *(*[]v1beta2.StatefulSetCondition)(unsafe.Pointer(&in.Conditions)) return nil } diff --git a/pkg/apis/apps/v1beta2/zz_generated.defaults.go b/pkg/apis/apps/v1beta2/zz_generated.defaults.go index 29eabcaf89b..5d79d92d8b1 100644 --- a/pkg/apis/apps/v1beta2/zz_generated.defaults.go +++ b/pkg/apis/apps/v1beta2/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,7 +23,7 @@ package v1beta2 import ( v1beta2 "k8s.io/api/apps/v1beta2" runtime "k8s.io/apimachinery/pkg/runtime" - v1 "k8s.io/kubernetes/pkg/api/v1" + v1 "k8s.io/kubernetes/pkg/apis/core/v1" ) // RegisterDefaults adds defaulters functions to the given scheme. diff --git a/pkg/apis/apps/validation/BUILD b/pkg/apis/apps/validation/BUILD index d02837508c5..2a81a0eb6a4 100644 --- a/pkg/apis/apps/validation/BUILD +++ b/pkg/apis/apps/validation/BUILD @@ -11,9 +11,9 @@ go_library( srcs = ["validation.go"], importpath = "k8s.io/kubernetes/pkg/apis/apps/validation", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/validation:go_default_library", "//pkg/apis/apps:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", @@ -24,11 +24,11 @@ go_library( go_test( name = "go_default_test", srcs = ["validation_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/apis/apps/validation", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", "//pkg/apis/apps:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", diff --git a/pkg/apis/apps/validation/validation.go b/pkg/apis/apps/validation/validation.go index 85bc564ff46..959e7ecb932 100644 --- a/pkg/apis/apps/validation/validation.go +++ b/pkg/apis/apps/validation/validation.go @@ -24,9 +24,9 @@ import ( unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" - apivalidation "k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/apis/apps" + api "k8s.io/kubernetes/pkg/apis/core" + apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" ) // ValidateStatefulSetName can be used to check whether the given StatefulSet name is valid. @@ -94,7 +94,6 @@ func ValidateStatefulSetSpec(spec *apps.StatefulSetSpec, fldPath *field.Path) fi int64(spec.UpdateStrategy.RollingUpdate.Partition), fldPath.Child("updateStrategy").Child("rollingUpdate").Child("partition"))...) } - default: allErrs = append(allErrs, field.Invalid(fldPath.Child("updateStrategy"), spec.UpdateStrategy, @@ -124,7 +123,7 @@ func ValidateStatefulSetSpec(spec *apps.StatefulSetSpec, fldPath *field.Path) fi allErrs = append(allErrs, field.NotSupported(fldPath.Child("template", "spec", "restartPolicy"), spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)})) } if spec.Template.Spec.ActiveDeadlineSeconds != nil { - allErrs = append(allErrs, field.Invalid(fldPath.Child("spec", "activeDeadlineSeconds"), spec.Template.Spec.ActiveDeadlineSeconds, "must not be specified")) + allErrs = append(allErrs, field.Invalid(fldPath.Child("template", "spec", "activeDeadlineSeconds"), spec.Template.Spec.ActiveDeadlineSeconds, "must not be specified")) } return allErrs diff --git a/pkg/apis/apps/validation/validation_test.go b/pkg/apis/apps/validation/validation_test.go index 306fddc7c43..4b8303bb990 100644 --- a/pkg/apis/apps/validation/validation_test.go +++ b/pkg/apis/apps/validation/validation_test.go @@ -17,14 +17,15 @@ limitations under the License. package validation import ( + "strconv" "strings" "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/apps" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestValidateStatefulSet(t *testing.T) { @@ -41,6 +42,7 @@ func TestValidateStatefulSet(t *testing.T) { }, }, } + invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} invalidPodTemplate := api.PodTemplate{ Template: api.PodTemplateSpec{ @@ -53,6 +55,21 @@ func TestValidateStatefulSet(t *testing.T) { }, }, } + + invalidTime := int64(60) + invalidPodTemplate2 := api.PodTemplate{ + Template: api.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"foo": "bar"}, + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyOnFailure, + DNSPolicy: api.DNSClusterFirst, + ActiveDeadlineSeconds: &invalidTime, + }, + }, + } + successCases := []apps.StatefulSet{ { ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, @@ -105,10 +122,13 @@ func TestValidateStatefulSet(t *testing.T) { }, }, } - for _, successCase := range successCases { - if errs := ValidateStatefulSet(&successCase); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } + + for i, successCase := range successCases { + t.Run("success case "+strconv.Itoa(i), func(t *testing.T) { + if errs := ValidateStatefulSet(&successCase); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + }) } errorCases := map[string]apps.StatefulSet{ @@ -260,6 +280,29 @@ func TestValidateStatefulSet(t *testing.T) { UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: "foo"}, }, }, + "empty udpate strategy": { + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + Replicas: 3, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: ""}, + }, + }, + "invalid rolling update": { + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: apps.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + Replicas: 3, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.OnDeleteStatefulSetStrategyType, + RollingUpdate: func() *apps.RollingUpdateStatefulSetStrategy { + return &apps.RollingUpdateStatefulSetStrategy{Partition: 1} + }()}, + }, + }, "negative parition": { ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, Spec: apps.StatefulSetSpec{ @@ -273,31 +316,67 @@ func TestValidateStatefulSet(t *testing.T) { }()}, }, }, + "empty pod management policy": { + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: "", + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + Replicas: 3, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + "invalid pod management policy": { + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: "foo", + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: validPodTemplate.Template, + Replicas: 3, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, + "set active deadline seconds": { + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: apps.StatefulSetSpec{ + PodManagementPolicy: "foo", + Selector: &metav1.LabelSelector{MatchLabels: validLabels}, + Template: invalidPodTemplate2.Template, + Replicas: 3, + UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, + }, + }, } + for k, v := range errorCases { - errs := ValidateStatefulSet(&v) - if len(errs) == 0 { - t.Errorf("expected failure for %s", k) - } - for i := range errs { - field := errs[i].Field - if !strings.HasPrefix(field, "spec.template.") && - field != "metadata.name" && - field != "metadata.namespace" && - field != "spec.selector" && - field != "spec.template" && - field != "GCEPersistentDisk.ReadOnly" && - field != "spec.replicas" && - field != "spec.template.labels" && - field != "metadata.annotations" && - field != "metadata.labels" && - field != "status.replicas" && - field != "spec.updateStrategy" && - field != "spec.updateStrategy.rollingUpdate" && - field != "spec.updateStrategy.rollingUpdate.partition" { - t.Errorf("%s: missing prefix for: %v", k, errs[i]) + t.Run(k, func(t *testing.T) { + errs := ValidateStatefulSet(&v) + if len(errs) == 0 { + t.Errorf("expected failure for %s", k) } - } + + for i := range errs { + field := errs[i].Field + if !strings.HasPrefix(field, "spec.template.") && + field != "metadata.name" && + field != "metadata.namespace" && + field != "spec.selector" && + field != "spec.template" && + field != "GCEPersistentDisk.ReadOnly" && + field != "spec.replicas" && + field != "spec.template.labels" && + field != "metadata.annotations" && + field != "metadata.labels" && + field != "status.replicas" && + field != "spec.updateStrategy" && + field != "spec.updateStrategy.rollingUpdate" && + field != "spec.updateStrategy.rollingUpdate.partition" && + field != "spec.podManagementPolicy" && + field != "spec.template.spec.activeDeadlineSeconds" { + t.Errorf("%s: missing prefix for: %v", k, errs[i]) + } + } + }) } } @@ -399,19 +478,21 @@ func TestValidateStatefulSetStatus(t *testing.T) { } for _, test := range tests { - status := apps.StatefulSetStatus{ - Replicas: test.replicas, - ReadyReplicas: test.readyReplicas, - CurrentReplicas: test.currentReplicas, - UpdatedReplicas: test.updatedReplicas, - ObservedGeneration: test.observedGeneration, - CollisionCount: test.collisionCount, - } + t.Run(test.name, func(t *testing.T) { + status := apps.StatefulSetStatus{ + Replicas: test.replicas, + ReadyReplicas: test.readyReplicas, + CurrentReplicas: test.currentReplicas, + UpdatedReplicas: test.updatedReplicas, + ObservedGeneration: test.observedGeneration, + CollisionCount: test.collisionCount, + } - errs := ValidateStatefulSetStatus(&status, field.NewPath("status")) - if hasErr := len(errs) > 0; hasErr != test.expectedErr { - t.Errorf("%s: expected error: %t, got error: %t\nerrors: %s", test.name, test.expectedErr, hasErr, errs.ToAggregate().Error()) - } + errs := ValidateStatefulSetStatus(&status, field.NewPath("status")) + if hasErr := len(errs) > 0; hasErr != test.expectedErr { + t.Errorf("%s: expected error: %t, got error: %t\nerrors: %s", test.name, test.expectedErr, hasErr, errs.ToAggregate().Error()) + } + }) } } @@ -462,6 +543,7 @@ func TestValidateStatefulSetUpdate(t *testing.T) { }, }, } + type psUpdateTest struct { old apps.StatefulSet update apps.StatefulSet @@ -529,13 +611,17 @@ func TestValidateStatefulSetUpdate(t *testing.T) { }, }, } - for _, successCase := range successCases { - successCase.old.ObjectMeta.ResourceVersion = "1" - successCase.update.ObjectMeta.ResourceVersion = "1" - if errs := ValidateStatefulSetUpdate(&successCase.update, &successCase.old); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } + + for i, successCase := range successCases { + t.Run("success case "+strconv.Itoa(i), func(t *testing.T) { + successCase.old.ObjectMeta.ResourceVersion = "1" + successCase.update.ObjectMeta.ResourceVersion = "1" + if errs := ValidateStatefulSetUpdate(&successCase.update, &successCase.old); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + }) } + errorCases := map[string]psUpdateTest{ "more than one read/write": { old: apps.StatefulSet{ @@ -656,10 +742,13 @@ func TestValidateStatefulSetUpdate(t *testing.T) { }, }, } + for testName, errorCase := range errorCases { - if errs := ValidateStatefulSetUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 { - t.Errorf("expected failure: %s", testName) - } + t.Run(testName, func(t *testing.T) { + if errs := ValidateStatefulSetUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 { + t.Errorf("expected failure: %s", testName) + } + }) } } @@ -715,13 +804,15 @@ func TestValidateControllerRevision(t *testing.T) { } for name, tc := range tests { - errs := ValidateControllerRevision(&tc.history) - if tc.isValid && len(errs) > 0 { - t.Errorf("%v: unexpected error: %v", name, errs) - } - if !tc.isValid && len(errs) == 0 { - t.Errorf("%v: unexpected non-error", name) - } + t.Run(name, func(t *testing.T) { + errs := ValidateControllerRevision(&tc.history) + if tc.isValid && len(errs) > 0 { + t.Errorf("%v: unexpected error: %v", name, errs) + } + if !tc.isValid && len(errs) == 0 { + t.Errorf("%v: unexpected non-error", name) + } + }) } } @@ -809,12 +900,14 @@ func TestValidateControllerRevisionUpdate(t *testing.T) { } for _, tc := range cases { - errs := ValidateControllerRevisionUpdate(&tc.newHistory, &tc.oldHistory) - if tc.isValid && len(errs) > 0 { - t.Errorf("%v: unexpected error: %v", tc.name, errs) - } - if !tc.isValid && len(errs) == 0 { - t.Errorf("%v: unexpected non-error", tc.name) - } + t.Run(tc.name, func(t *testing.T) { + errs := ValidateControllerRevisionUpdate(&tc.newHistory, &tc.oldHistory) + if tc.isValid && len(errs) > 0 { + t.Errorf("%v: unexpected error: %v", tc.name, errs) + } + if !tc.isValid && len(errs) == 0 { + t.Errorf("%v: unexpected non-error", tc.name) + } + }) } } diff --git a/pkg/apis/apps/zz_generated.deepcopy.go b/pkg/apis/apps/zz_generated.deepcopy.go index 983dabf46af..74fab2edd20 100644 --- a/pkg/apis/apps/zz_generated.deepcopy.go +++ b/pkg/apis/apps/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,57 +22,10 @@ package apps import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - api "k8s.io/kubernetes/pkg/api" - reflect "reflect" + core "k8s.io/kubernetes/pkg/apis/core" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ControllerRevision).DeepCopyInto(out.(*ControllerRevision)) - return nil - }, InType: reflect.TypeOf(&ControllerRevision{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ControllerRevisionList).DeepCopyInto(out.(*ControllerRevisionList)) - return nil - }, InType: reflect.TypeOf(&ControllerRevisionList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RollingUpdateStatefulSetStrategy).DeepCopyInto(out.(*RollingUpdateStatefulSetStrategy)) - return nil - }, InType: reflect.TypeOf(&RollingUpdateStatefulSetStrategy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StatefulSet).DeepCopyInto(out.(*StatefulSet)) - return nil - }, InType: reflect.TypeOf(&StatefulSet{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StatefulSetList).DeepCopyInto(out.(*StatefulSetList)) - return nil - }, InType: reflect.TypeOf(&StatefulSetList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StatefulSetSpec).DeepCopyInto(out.(*StatefulSetSpec)) - return nil - }, InType: reflect.TypeOf(&StatefulSetSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StatefulSetStatus).DeepCopyInto(out.(*StatefulSetStatus)) - return nil - }, InType: reflect.TypeOf(&StatefulSetStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StatefulSetUpdateStrategy).DeepCopyInto(out.(*StatefulSetUpdateStrategy)) - return nil - }, InType: reflect.TypeOf(&StatefulSetUpdateStrategy{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ControllerRevision) DeepCopyInto(out *ControllerRevision) { *out = *in @@ -184,6 +137,23 @@ func (in *StatefulSet) DeepCopyObject() runtime.Object { } } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StatefulSetCondition) DeepCopyInto(out *StatefulSetCondition) { + *out = *in + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatefulSetCondition. +func (in *StatefulSetCondition) DeepCopy() *StatefulSetCondition { + if in == nil { + return nil + } + out := new(StatefulSetCondition) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *StatefulSetList) DeepCopyInto(out *StatefulSetList) { *out = *in @@ -233,7 +203,7 @@ func (in *StatefulSetSpec) DeepCopyInto(out *StatefulSetSpec) { in.Template.DeepCopyInto(&out.Template) if in.VolumeClaimTemplates != nil { in, out := &in.VolumeClaimTemplates, &out.VolumeClaimTemplates - *out = make([]api.PersistentVolumeClaim, len(*in)) + *out = make([]core.PersistentVolumeClaim, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -282,6 +252,13 @@ func (in *StatefulSetStatus) DeepCopyInto(out *StatefulSetStatus) { **out = **in } } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]StatefulSetCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } diff --git a/pkg/apis/authentication/BUILD b/pkg/apis/authentication/BUILD index b838290ca57..839995873a2 100644 --- a/pkg/apis/authentication/BUILD +++ b/pkg/apis/authentication/BUILD @@ -16,7 +16,6 @@ go_library( importpath = "k8s.io/kubernetes/pkg/apis/authentication", deps = [ "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/pkg/apis/authentication/doc.go b/pkg/apis/authentication/doc.go index 88bdf625b73..0afbdd3a3a6 100644 --- a/pkg/apis/authentication/doc.go +++ b/pkg/apis/authentication/doc.go @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +groupName=authentication.k8s.io package authentication // import "k8s.io/kubernetes/pkg/apis/authentication" diff --git a/pkg/apis/authentication/install/BUILD b/pkg/apis/authentication/install/BUILD index 23f37eb2913..ec4744b942f 100644 --- a/pkg/apis/authentication/install/BUILD +++ b/pkg/apis/authentication/install/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["install.go"], importpath = "k8s.io/kubernetes/pkg/apis/authentication/install", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/authentication:go_default_library", "//pkg/apis/authentication/v1:go_default_library", "//pkg/apis/authentication/v1beta1:go_default_library", diff --git a/pkg/apis/authentication/install/install.go b/pkg/apis/authentication/install/install.go index 9f1c4d64791..3e1271dcbcb 100644 --- a/pkg/apis/authentication/install/install.go +++ b/pkg/apis/authentication/install/install.go @@ -23,14 +23,14 @@ import ( "k8s.io/apimachinery/pkg/apimachinery/registered" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/authentication" "k8s.io/kubernetes/pkg/apis/authentication/v1" "k8s.io/kubernetes/pkg/apis/authentication/v1beta1" ) func init() { - Install(api.GroupFactoryRegistry, api.Registry, api.Scheme) + Install(legacyscheme.GroupFactoryRegistry, legacyscheme.Registry, legacyscheme.Scheme) } // Install registers the API group and adds types to a scheme diff --git a/pkg/apis/authentication/v1/doc.go b/pkg/apis/authentication/v1/doc.go index 39ad62a8e9e..50ec02077ff 100644 --- a/pkg/apis/authentication/v1/doc.go +++ b/pkg/apis/authentication/v1/doc.go @@ -15,7 +15,7 @@ limitations under the License. */ // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/authentication -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/authentication/v1 +// +k8s:conversion-gen-external-types=k8s.io/api/authentication/v1 // +groupName=authentication.k8s.io // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/authentication/v1 diff --git a/pkg/apis/authentication/v1/zz_generated.conversion.go b/pkg/apis/authentication/v1/zz_generated.conversion.go index 9f335d61457..43acd2c41ed 100644 --- a/pkg/apis/authentication/v1/zz_generated.conversion.go +++ b/pkg/apis/authentication/v1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,11 +21,12 @@ limitations under the License. package v1 import ( + unsafe "unsafe" + v1 "k8s.io/api/authentication/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" authentication "k8s.io/kubernetes/pkg/apis/authentication" - unsafe "unsafe" ) func init() { diff --git a/pkg/apis/authentication/v1/zz_generated.defaults.go b/pkg/apis/authentication/v1/zz_generated.defaults.go index 6df448eb9fd..88d7af085be 100644 --- a/pkg/apis/authentication/v1/zz_generated.defaults.go +++ b/pkg/apis/authentication/v1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/apis/authentication/v1beta1/doc.go b/pkg/apis/authentication/v1beta1/doc.go index 2c477c52c74..7f7a5ffa3be 100644 --- a/pkg/apis/authentication/v1beta1/doc.go +++ b/pkg/apis/authentication/v1beta1/doc.go @@ -15,7 +15,7 @@ limitations under the License. */ // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/authentication -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/authentication/v1beta1 +// +k8s:conversion-gen-external-types=k8s.io/api/authentication/v1beta1 // +groupName=authentication.k8s.io // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/authentication/v1beta1 diff --git a/pkg/apis/authentication/v1beta1/zz_generated.conversion.go b/pkg/apis/authentication/v1beta1/zz_generated.conversion.go index 0fc0bf7ba79..392ea76c011 100644 --- a/pkg/apis/authentication/v1beta1/zz_generated.conversion.go +++ b/pkg/apis/authentication/v1beta1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,11 +21,12 @@ limitations under the License. package v1beta1 import ( + unsafe "unsafe" + v1beta1 "k8s.io/api/authentication/v1beta1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" authentication "k8s.io/kubernetes/pkg/apis/authentication" - unsafe "unsafe" ) func init() { diff --git a/pkg/apis/authentication/v1beta1/zz_generated.defaults.go b/pkg/apis/authentication/v1beta1/zz_generated.defaults.go index e24e70be38b..b61dda74c23 100644 --- a/pkg/apis/authentication/v1beta1/zz_generated.defaults.go +++ b/pkg/apis/authentication/v1beta1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/apis/authentication/zz_generated.deepcopy.go b/pkg/apis/authentication/zz_generated.deepcopy.go index 0efa1497af5..8a7904b19cd 100644 --- a/pkg/apis/authentication/zz_generated.deepcopy.go +++ b/pkg/apis/authentication/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,40 +21,9 @@ limitations under the License. package authentication import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*TokenReview).DeepCopyInto(out.(*TokenReview)) - return nil - }, InType: reflect.TypeOf(&TokenReview{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*TokenReviewSpec).DeepCopyInto(out.(*TokenReviewSpec)) - return nil - }, InType: reflect.TypeOf(&TokenReviewSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*TokenReviewStatus).DeepCopyInto(out.(*TokenReviewStatus)) - return nil - }, InType: reflect.TypeOf(&TokenReviewStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*UserInfo).DeepCopyInto(out.(*UserInfo)) - return nil - }, InType: reflect.TypeOf(&UserInfo{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TokenReview) DeepCopyInto(out *TokenReview) { *out = *in diff --git a/pkg/apis/authorization/BUILD b/pkg/apis/authorization/BUILD index 59a9535768f..30e71c49666 100644 --- a/pkg/apis/authorization/BUILD +++ b/pkg/apis/authorization/BUILD @@ -16,7 +16,6 @@ go_library( importpath = "k8s.io/kubernetes/pkg/apis/authorization", deps = [ "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/pkg/apis/authorization/doc.go b/pkg/apis/authorization/doc.go index 29f9b624203..5cb3094aba8 100644 --- a/pkg/apis/authorization/doc.go +++ b/pkg/apis/authorization/doc.go @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +groupName=authorization.k8s.io package authorization // import "k8s.io/kubernetes/pkg/apis/authorization" diff --git a/pkg/apis/authorization/install/BUILD b/pkg/apis/authorization/install/BUILD index 1862804aab7..9825a5afde3 100644 --- a/pkg/apis/authorization/install/BUILD +++ b/pkg/apis/authorization/install/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["install.go"], importpath = "k8s.io/kubernetes/pkg/apis/authorization/install", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/authorization:go_default_library", "//pkg/apis/authorization/v1:go_default_library", "//pkg/apis/authorization/v1beta1:go_default_library", diff --git a/pkg/apis/authorization/install/install.go b/pkg/apis/authorization/install/install.go index 47b78fe5ec7..2a9cbf6bd2b 100644 --- a/pkg/apis/authorization/install/install.go +++ b/pkg/apis/authorization/install/install.go @@ -23,14 +23,14 @@ import ( "k8s.io/apimachinery/pkg/apimachinery/registered" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/authorization" "k8s.io/kubernetes/pkg/apis/authorization/v1" "k8s.io/kubernetes/pkg/apis/authorization/v1beta1" ) func init() { - Install(api.GroupFactoryRegistry, api.Registry, api.Scheme) + Install(legacyscheme.GroupFactoryRegistry, legacyscheme.Registry, legacyscheme.Scheme) } // Install registers the API group and adds types to a scheme diff --git a/pkg/apis/authorization/types.go b/pkg/apis/authorization/types.go index 4920913c59e..e798ec2123e 100644 --- a/pkg/apis/authorization/types.go +++ b/pkg/apis/authorization/types.go @@ -140,8 +140,13 @@ type SelfSubjectAccessReviewSpec struct { // 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 + // 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. + Denied bool // Reason is optional. It indicates why a request was allowed or denied. Reason string // EvaluationError is an indication that some error occurred during the authorization check. @@ -205,7 +210,8 @@ type ResourceRule struct { // 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. APIGroups []string - // Resources is a list of resources this rule applies to. ResourceAll represents all resources. "*" means all. + // 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. Resources []string // ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. "*" means all. ResourceNames []string diff --git a/pkg/apis/authorization/v1/doc.go b/pkg/apis/authorization/v1/doc.go index 5d3af7912e7..11b7605c898 100644 --- a/pkg/apis/authorization/v1/doc.go +++ b/pkg/apis/authorization/v1/doc.go @@ -15,7 +15,7 @@ limitations under the License. */ // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/authorization -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/authorization/v1 +// +k8s:conversion-gen-external-types=k8s.io/api/authorization/v1 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/authorization/v1 diff --git a/pkg/apis/authorization/v1/zz_generated.conversion.go b/pkg/apis/authorization/v1/zz_generated.conversion.go index 1c06171c6fc..0cab2b3a2a9 100644 --- a/pkg/apis/authorization/v1/zz_generated.conversion.go +++ b/pkg/apis/authorization/v1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,11 +21,12 @@ limitations under the License. package v1 import ( + unsafe "unsafe" + v1 "k8s.io/api/authorization/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" authorization "k8s.io/kubernetes/pkg/apis/authorization" - unsafe "unsafe" ) func init() { @@ -369,6 +370,7 @@ func Convert_authorization_SubjectAccessReviewSpec_To_v1_SubjectAccessReviewSpec func autoConvert_v1_SubjectAccessReviewStatus_To_authorization_SubjectAccessReviewStatus(in *v1.SubjectAccessReviewStatus, out *authorization.SubjectAccessReviewStatus, s conversion.Scope) error { out.Allowed = in.Allowed + out.Denied = in.Denied out.Reason = in.Reason out.EvaluationError = in.EvaluationError return nil @@ -381,6 +383,7 @@ func Convert_v1_SubjectAccessReviewStatus_To_authorization_SubjectAccessReviewSt func autoConvert_authorization_SubjectAccessReviewStatus_To_v1_SubjectAccessReviewStatus(in *authorization.SubjectAccessReviewStatus, out *v1.SubjectAccessReviewStatus, s conversion.Scope) error { out.Allowed = in.Allowed + out.Denied = in.Denied out.Reason = in.Reason out.EvaluationError = in.EvaluationError return nil diff --git a/pkg/apis/authorization/v1/zz_generated.defaults.go b/pkg/apis/authorization/v1/zz_generated.defaults.go index 6df448eb9fd..88d7af085be 100644 --- a/pkg/apis/authorization/v1/zz_generated.defaults.go +++ b/pkg/apis/authorization/v1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/apis/authorization/v1beta1/doc.go b/pkg/apis/authorization/v1beta1/doc.go index 76c8fad1160..a958fa36550 100644 --- a/pkg/apis/authorization/v1beta1/doc.go +++ b/pkg/apis/authorization/v1beta1/doc.go @@ -15,7 +15,7 @@ limitations under the License. */ // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/authorization -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/authorization/v1beta1 +// +k8s:conversion-gen-external-types=k8s.io/api/authorization/v1beta1 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/authorization/v1beta1 diff --git a/pkg/apis/authorization/v1beta1/zz_generated.conversion.go b/pkg/apis/authorization/v1beta1/zz_generated.conversion.go index 4ba9417a61f..c9c1bd5e5ea 100644 --- a/pkg/apis/authorization/v1beta1/zz_generated.conversion.go +++ b/pkg/apis/authorization/v1beta1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,11 +21,12 @@ limitations under the License. package v1beta1 import ( + unsafe "unsafe" + v1beta1 "k8s.io/api/authorization/v1beta1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" authorization "k8s.io/kubernetes/pkg/apis/authorization" - unsafe "unsafe" ) func init() { @@ -369,6 +370,7 @@ func Convert_authorization_SubjectAccessReviewSpec_To_v1beta1_SubjectAccessRevie func autoConvert_v1beta1_SubjectAccessReviewStatus_To_authorization_SubjectAccessReviewStatus(in *v1beta1.SubjectAccessReviewStatus, out *authorization.SubjectAccessReviewStatus, s conversion.Scope) error { out.Allowed = in.Allowed + out.Denied = in.Denied out.Reason = in.Reason out.EvaluationError = in.EvaluationError return nil @@ -381,6 +383,7 @@ func Convert_v1beta1_SubjectAccessReviewStatus_To_authorization_SubjectAccessRev func autoConvert_authorization_SubjectAccessReviewStatus_To_v1beta1_SubjectAccessReviewStatus(in *authorization.SubjectAccessReviewStatus, out *v1beta1.SubjectAccessReviewStatus, s conversion.Scope) error { out.Allowed = in.Allowed + out.Denied = in.Denied out.Reason = in.Reason out.EvaluationError = in.EvaluationError return nil diff --git a/pkg/apis/authorization/v1beta1/zz_generated.defaults.go b/pkg/apis/authorization/v1beta1/zz_generated.defaults.go index e24e70be38b..b61dda74c23 100644 --- a/pkg/apis/authorization/v1beta1/zz_generated.defaults.go +++ b/pkg/apis/authorization/v1beta1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/apis/authorization/validation/BUILD b/pkg/apis/authorization/validation/BUILD index 58559f83da9..c5f76a8be4d 100644 --- a/pkg/apis/authorization/validation/BUILD +++ b/pkg/apis/authorization/validation/BUILD @@ -21,8 +21,8 @@ go_library( go_test( name = "go_default_test", srcs = ["validation_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/apis/authorization/validation", - library = ":go_default_library", deps = [ "//pkg/apis/authorization:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/apis/authorization/zz_generated.deepcopy.go b/pkg/apis/authorization/zz_generated.deepcopy.go index eb121f30885..b35902ecec8 100644 --- a/pkg/apis/authorization/zz_generated.deepcopy.go +++ b/pkg/apis/authorization/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,76 +21,9 @@ limitations under the License. package authorization import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LocalSubjectAccessReview).DeepCopyInto(out.(*LocalSubjectAccessReview)) - return nil - }, InType: reflect.TypeOf(&LocalSubjectAccessReview{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NonResourceAttributes).DeepCopyInto(out.(*NonResourceAttributes)) - return nil - }, InType: reflect.TypeOf(&NonResourceAttributes{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NonResourceRule).DeepCopyInto(out.(*NonResourceRule)) - return nil - }, InType: reflect.TypeOf(&NonResourceRule{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ResourceAttributes).DeepCopyInto(out.(*ResourceAttributes)) - return nil - }, InType: reflect.TypeOf(&ResourceAttributes{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ResourceRule).DeepCopyInto(out.(*ResourceRule)) - return nil - }, InType: reflect.TypeOf(&ResourceRule{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SelfSubjectAccessReview).DeepCopyInto(out.(*SelfSubjectAccessReview)) - return nil - }, InType: reflect.TypeOf(&SelfSubjectAccessReview{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SelfSubjectAccessReviewSpec).DeepCopyInto(out.(*SelfSubjectAccessReviewSpec)) - return nil - }, InType: reflect.TypeOf(&SelfSubjectAccessReviewSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SelfSubjectRulesReview).DeepCopyInto(out.(*SelfSubjectRulesReview)) - return nil - }, InType: reflect.TypeOf(&SelfSubjectRulesReview{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SelfSubjectRulesReviewSpec).DeepCopyInto(out.(*SelfSubjectRulesReviewSpec)) - return nil - }, InType: reflect.TypeOf(&SelfSubjectRulesReviewSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SubjectAccessReview).DeepCopyInto(out.(*SubjectAccessReview)) - return nil - }, InType: reflect.TypeOf(&SubjectAccessReview{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SubjectAccessReviewSpec).DeepCopyInto(out.(*SubjectAccessReviewSpec)) - return nil - }, InType: reflect.TypeOf(&SubjectAccessReviewSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SubjectAccessReviewStatus).DeepCopyInto(out.(*SubjectAccessReviewStatus)) - return nil - }, InType: reflect.TypeOf(&SubjectAccessReviewStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SubjectRulesReviewStatus).DeepCopyInto(out.(*SubjectRulesReviewStatus)) - return nil - }, InType: reflect.TypeOf(&SubjectRulesReviewStatus{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LocalSubjectAccessReview) DeepCopyInto(out *LocalSubjectAccessReview) { *out = *in diff --git a/pkg/apis/autoscaling/BUILD b/pkg/apis/autoscaling/BUILD index edb280ba05d..dcca30c00d5 100644 --- a/pkg/apis/autoscaling/BUILD +++ b/pkg/apis/autoscaling/BUILD @@ -16,10 +16,9 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/apis/autoscaling", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/pkg/apis/autoscaling/doc.go b/pkg/apis/autoscaling/doc.go index d9e11576f92..7c91aac8bb0 100644 --- a/pkg/apis/autoscaling/doc.go +++ b/pkg/apis/autoscaling/doc.go @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package package autoscaling // import "k8s.io/kubernetes/pkg/apis/autoscaling" diff --git a/pkg/apis/autoscaling/fuzzer/BUILD b/pkg/apis/autoscaling/fuzzer/BUILD index 5d1dbca6379..ae20853a342 100644 --- a/pkg/apis/autoscaling/fuzzer/BUILD +++ b/pkg/apis/autoscaling/fuzzer/BUILD @@ -10,10 +10,11 @@ go_library( srcs = ["fuzzer.go"], importpath = "k8s.io/kubernetes/pkg/apis/autoscaling/fuzzer", deps = [ - "//pkg/api:go_default_library", "//pkg/apis/autoscaling:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/github.com/google/gofuzz:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", ], ) diff --git a/pkg/apis/autoscaling/fuzzer/fuzzer.go b/pkg/apis/autoscaling/fuzzer/fuzzer.go index 7133c162735..2c1ea0608ca 100644 --- a/pkg/apis/autoscaling/fuzzer/fuzzer.go +++ b/pkg/apis/autoscaling/fuzzer/fuzzer.go @@ -18,15 +18,26 @@ package fuzzer import ( fuzz "github.com/google/gofuzz" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/autoscaling" + api "k8s.io/kubernetes/pkg/apis/core" ) // Funcs returns the fuzzer functions for the autoscaling api group. var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { return []interface{}{ + func(s *autoscaling.ScaleStatus, c fuzz.Continue) { + c.FuzzNoCustom(s) // fuzz self without calling this function again + + // ensure we have a valid selector + metaSelector := &metav1.LabelSelector{} + c.Fuzz(metaSelector) + labelSelector, _ := metav1.LabelSelectorAsSelector(metaSelector) + s.Selector = labelSelector.String() + }, func(s *autoscaling.HorizontalPodAutoscalerSpec, c fuzz.Continue) { c.FuzzNoCustom(s) // fuzz self without calling this function again minReplicas := int32(c.Rand.Int31()) diff --git a/pkg/apis/autoscaling/install/BUILD b/pkg/apis/autoscaling/install/BUILD index 621c4419870..d1cb4d5dcbf 100644 --- a/pkg/apis/autoscaling/install/BUILD +++ b/pkg/apis/autoscaling/install/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["install.go"], importpath = "k8s.io/kubernetes/pkg/apis/autoscaling/install", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/autoscaling:go_default_library", "//pkg/apis/autoscaling/v1:go_default_library", "//pkg/apis/autoscaling/v2beta1:go_default_library", diff --git a/pkg/apis/autoscaling/install/install.go b/pkg/apis/autoscaling/install/install.go index e7a75f1e5b1..507f2b17f52 100644 --- a/pkg/apis/autoscaling/install/install.go +++ b/pkg/apis/autoscaling/install/install.go @@ -22,14 +22,14 @@ import ( "k8s.io/apimachinery/pkg/apimachinery/announced" "k8s.io/apimachinery/pkg/apimachinery/registered" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/autoscaling" "k8s.io/kubernetes/pkg/apis/autoscaling/v1" "k8s.io/kubernetes/pkg/apis/autoscaling/v2beta1" ) func init() { - Install(api.GroupFactoryRegistry, api.Registry, api.Scheme) + Install(legacyscheme.GroupFactoryRegistry, legacyscheme.Registry, legacyscheme.Scheme) } // Install registers the API group and adds types to a scheme diff --git a/pkg/apis/autoscaling/register.go b/pkg/apis/autoscaling/register.go index 2bcea84b9bf..6c321a3abab 100644 --- a/pkg/apis/autoscaling/register.go +++ b/pkg/apis/autoscaling/register.go @@ -42,7 +42,7 @@ var ( AddToScheme = SchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Scale{}, diff --git a/pkg/apis/autoscaling/types.go b/pkg/apis/autoscaling/types.go index 7c830d9467e..71837b87236 100644 --- a/pkg/apis/autoscaling/types.go +++ b/pkg/apis/autoscaling/types.go @@ -19,7 +19,7 @@ package autoscaling import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -116,7 +116,8 @@ var ( // 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 match one of the fields below. + // Type is the type of metric source. It should be one of "Object", + // "Pods" or "Resource", each mapping to a matching field in the object. Type MetricSourceType // Object refers to a metric describing a single kubernetes object @@ -261,7 +262,8 @@ type HorizontalPodAutoscalerCondition struct { // MetricStatus describes the last-read state of a single metric. type MetricStatus struct { - // Type is the type of metric source. It will match one of the fields below. + // Type is the type of metric source. It will be one of "Object", + // "Pods" or "Resource", each corresponds to a matching field in the object. Type MetricSourceType // Object refers to a metric describing a single kubernetes object diff --git a/pkg/apis/autoscaling/v1/BUILD b/pkg/apis/autoscaling/v1/BUILD index 8a311c1ad4a..4f2490f7dda 100644 --- a/pkg/apis/autoscaling/v1/BUILD +++ b/pkg/apis/autoscaling/v1/BUILD @@ -18,8 +18,8 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/apis/autoscaling/v1", deps = [ - "//pkg/api:go_default_library", "//pkg/apis/autoscaling:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/api/autoscaling/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", @@ -36,9 +36,9 @@ go_test( importpath = "k8s.io/kubernetes/pkg/apis/autoscaling/v1_test", deps = [ ":go_default_library", - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/autoscaling/install:go_default_library", + "//pkg/apis/core/install:go_default_library", "//vendor/k8s.io/api/autoscaling/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", ], diff --git a/pkg/apis/autoscaling/v1/conversion.go b/pkg/apis/autoscaling/v1/conversion.go index 04dc4a2804b..599fa7622d5 100644 --- a/pkg/apis/autoscaling/v1/conversion.go +++ b/pkg/apis/autoscaling/v1/conversion.go @@ -23,8 +23,8 @@ import ( "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/autoscaling" + api "k8s.io/kubernetes/pkg/apis/core" ) func addConversionFuncs(scheme *runtime.Scheme) error { diff --git a/pkg/apis/autoscaling/v1/defaults_test.go b/pkg/apis/autoscaling/v1/defaults_test.go index def10863180..30c4706f2dd 100644 --- a/pkg/apis/autoscaling/v1/defaults_test.go +++ b/pkg/apis/autoscaling/v1/defaults_test.go @@ -23,10 +23,10 @@ import ( autoscalingv1 "k8s.io/api/autoscaling/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" - _ "k8s.io/kubernetes/pkg/api/install" + "k8s.io/kubernetes/pkg/api/legacyscheme" _ "k8s.io/kubernetes/pkg/apis/autoscaling/install" . "k8s.io/kubernetes/pkg/apis/autoscaling/v1" + _ "k8s.io/kubernetes/pkg/apis/core/install" ) func TestSetDefaultHPA(t *testing.T) { @@ -67,18 +67,18 @@ func TestSetDefaultHPA(t *testing.T) { } func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { - data, err := runtime.Encode(api.Codecs.LegacyCodec(SchemeGroupVersion), obj) + 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(api.Codecs.UniversalDecoder(), data) + 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 = api.Scheme.Convert(obj2, obj3, nil) + err = legacyscheme.Scheme.Convert(obj2, obj3, nil) if err != nil { t.Errorf("%v\nSource: %#v", err, obj2) return nil diff --git a/pkg/apis/autoscaling/v1/doc.go b/pkg/apis/autoscaling/v1/doc.go index 461e081a116..4ba5b923633 100644 --- a/pkg/apis/autoscaling/v1/doc.go +++ b/pkg/apis/autoscaling/v1/doc.go @@ -15,7 +15,7 @@ limitations under the License. */ // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/autoscaling -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/autoscaling/v1 +// +k8s:conversion-gen-external-types=k8s.io/api/autoscaling/v1 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/autoscaling/v1 diff --git a/pkg/apis/autoscaling/v1/zz_generated.conversion.go b/pkg/apis/autoscaling/v1/zz_generated.conversion.go index e1f48bdce53..71fac630477 100644 --- a/pkg/apis/autoscaling/v1/zz_generated.conversion.go +++ b/pkg/apis/autoscaling/v1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,15 +21,16 @@ limitations under the License. package v1 import ( + unsafe "unsafe" + v1 "k8s.io/api/autoscaling/v1" core_v1 "k8s.io/api/core/v1" resource "k8s.io/apimachinery/pkg/api/resource" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - api "k8s.io/kubernetes/pkg/api" autoscaling "k8s.io/kubernetes/pkg/apis/autoscaling" - unsafe "unsafe" + core "k8s.io/kubernetes/pkg/apis/core" ) func init() { @@ -385,7 +386,7 @@ func Convert_autoscaling_PodsMetricStatus_To_v1_PodsMetricStatus(in *autoscaling } func autoConvert_v1_ResourceMetricSource_To_autoscaling_ResourceMetricSource(in *v1.ResourceMetricSource, out *autoscaling.ResourceMetricSource, s conversion.Scope) error { - out.Name = api.ResourceName(in.Name) + out.Name = core.ResourceName(in.Name) out.TargetAverageUtilization = (*int32)(unsafe.Pointer(in.TargetAverageUtilization)) out.TargetAverageValue = (*resource.Quantity)(unsafe.Pointer(in.TargetAverageValue)) return nil @@ -409,7 +410,7 @@ func Convert_autoscaling_ResourceMetricSource_To_v1_ResourceMetricSource(in *aut } func autoConvert_v1_ResourceMetricStatus_To_autoscaling_ResourceMetricStatus(in *v1.ResourceMetricStatus, out *autoscaling.ResourceMetricStatus, s conversion.Scope) error { - out.Name = api.ResourceName(in.Name) + out.Name = core.ResourceName(in.Name) out.CurrentAverageUtilization = (*int32)(unsafe.Pointer(in.CurrentAverageUtilization)) out.CurrentAverageValue = in.CurrentAverageValue return nil diff --git a/pkg/apis/autoscaling/v1/zz_generated.defaults.go b/pkg/apis/autoscaling/v1/zz_generated.defaults.go index 192e8f5011c..0cfceabce22 100644 --- a/pkg/apis/autoscaling/v1/zz_generated.defaults.go +++ b/pkg/apis/autoscaling/v1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/apis/autoscaling/v2beta1/BUILD b/pkg/apis/autoscaling/v2beta1/BUILD index 87a5a84dacb..720df24df8a 100644 --- a/pkg/apis/autoscaling/v2beta1/BUILD +++ b/pkg/apis/autoscaling/v2beta1/BUILD @@ -17,8 +17,8 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/apis/autoscaling/v2beta1", deps = [ - "//pkg/api:go_default_library", "//pkg/apis/autoscaling:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/api/autoscaling/v2beta1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", @@ -48,10 +48,10 @@ go_test( importpath = "k8s.io/kubernetes/pkg/apis/autoscaling/v2beta1_test", deps = [ ":go_default_library", - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/autoscaling:go_default_library", "//pkg/apis/autoscaling/install:go_default_library", + "//pkg/apis/core/install:go_default_library", "//vendor/k8s.io/api/autoscaling/v2beta1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", diff --git a/pkg/apis/autoscaling/v2beta1/defaults_test.go b/pkg/apis/autoscaling/v2beta1/defaults_test.go index 3fe496ee485..ec138b4dd1f 100644 --- a/pkg/apis/autoscaling/v2beta1/defaults_test.go +++ b/pkg/apis/autoscaling/v2beta1/defaults_test.go @@ -24,11 +24,11 @@ import ( "k8s.io/api/core/v1" apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" - _ "k8s.io/kubernetes/pkg/api/install" + "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" ) func TestSetDefaultHPA(t *testing.T) { @@ -105,18 +105,18 @@ func TestSetDefaultHPA(t *testing.T) { } func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { - data, err := runtime.Encode(api.Codecs.LegacyCodec(SchemeGroupVersion), obj) + 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(api.Codecs.UniversalDecoder(), data) + 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 = api.Scheme.Convert(obj2, obj3, nil) + err = legacyscheme.Scheme.Convert(obj2, obj3, nil) if err != nil { t.Errorf("%v\nSource: %#v", err, obj2) return nil diff --git a/pkg/apis/autoscaling/v2beta1/doc.go b/pkg/apis/autoscaling/v2beta1/doc.go index 7b8df7617cc..411ef58dcf7 100644 --- a/pkg/apis/autoscaling/v2beta1/doc.go +++ b/pkg/apis/autoscaling/v2beta1/doc.go @@ -15,7 +15,7 @@ limitations under the License. */ // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/autoscaling -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/autoscaling/v2beta1 +// +k8s:conversion-gen-external-types=k8s.io/api/autoscaling/v2beta1 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/autoscaling/v2beta1 diff --git a/pkg/apis/autoscaling/v2beta1/zz_generated.conversion.go b/pkg/apis/autoscaling/v2beta1/zz_generated.conversion.go index af3bb3d8150..2de9ae9b433 100644 --- a/pkg/apis/autoscaling/v2beta1/zz_generated.conversion.go +++ b/pkg/apis/autoscaling/v2beta1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,15 +21,16 @@ limitations under the License. package v2beta1 import ( + unsafe "unsafe" + v2beta1 "k8s.io/api/autoscaling/v2beta1" v1 "k8s.io/api/core/v1" resource "k8s.io/apimachinery/pkg/api/resource" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - api "k8s.io/kubernetes/pkg/api" autoscaling "k8s.io/kubernetes/pkg/apis/autoscaling" - unsafe "unsafe" + core "k8s.io/kubernetes/pkg/apis/core" ) func init() { @@ -390,7 +391,7 @@ func Convert_autoscaling_PodsMetricStatus_To_v2beta1_PodsMetricStatus(in *autosc } func autoConvert_v2beta1_ResourceMetricSource_To_autoscaling_ResourceMetricSource(in *v2beta1.ResourceMetricSource, out *autoscaling.ResourceMetricSource, s conversion.Scope) error { - out.Name = api.ResourceName(in.Name) + out.Name = core.ResourceName(in.Name) out.TargetAverageUtilization = (*int32)(unsafe.Pointer(in.TargetAverageUtilization)) out.TargetAverageValue = (*resource.Quantity)(unsafe.Pointer(in.TargetAverageValue)) return nil @@ -414,7 +415,7 @@ func Convert_autoscaling_ResourceMetricSource_To_v2beta1_ResourceMetricSource(in } func autoConvert_v2beta1_ResourceMetricStatus_To_autoscaling_ResourceMetricStatus(in *v2beta1.ResourceMetricStatus, out *autoscaling.ResourceMetricStatus, s conversion.Scope) error { - out.Name = api.ResourceName(in.Name) + out.Name = core.ResourceName(in.Name) out.CurrentAverageUtilization = (*int32)(unsafe.Pointer(in.CurrentAverageUtilization)) out.CurrentAverageValue = in.CurrentAverageValue return nil diff --git a/pkg/apis/autoscaling/v2beta1/zz_generated.defaults.go b/pkg/apis/autoscaling/v2beta1/zz_generated.defaults.go index 5dfd8218dbe..42787931817 100644 --- a/pkg/apis/autoscaling/v2beta1/zz_generated.defaults.go +++ b/pkg/apis/autoscaling/v2beta1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/apis/autoscaling/validation/BUILD b/pkg/apis/autoscaling/validation/BUILD index f47483fc215..03c70d4c17b 100644 --- a/pkg/apis/autoscaling/validation/BUILD +++ b/pkg/apis/autoscaling/validation/BUILD @@ -11,8 +11,8 @@ go_library( srcs = ["validation.go"], importpath = "k8s.io/kubernetes/pkg/apis/autoscaling/validation", deps = [ - "//pkg/api/validation:go_default_library", "//pkg/apis/autoscaling:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/validation/path:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", @@ -22,11 +22,11 @@ go_library( go_test( name = "go_default_test", srcs = ["validation_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/apis/autoscaling/validation", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", "//pkg/apis/autoscaling:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", ], diff --git a/pkg/apis/autoscaling/validation/validation.go b/pkg/apis/autoscaling/validation/validation.go index e317fed2a59..6338b2a744e 100644 --- a/pkg/apis/autoscaling/validation/validation.go +++ b/pkg/apis/autoscaling/validation/validation.go @@ -22,8 +22,8 @@ import ( pathvalidation "k8s.io/apimachinery/pkg/api/validation/path" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation/field" - apivalidation "k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/apis/autoscaling" + apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" ) func ValidateScale(scale *autoscaling.Scale) field.ErrorList { diff --git a/pkg/apis/autoscaling/validation/validation_test.go b/pkg/apis/autoscaling/validation/validation_test.go index e07442c9e3e..d334a77b3c9 100644 --- a/pkg/apis/autoscaling/validation/validation_test.go +++ b/pkg/apis/autoscaling/validation/validation_test.go @@ -22,8 +22,8 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/autoscaling" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestValidateScale(t *testing.T) { diff --git a/pkg/apis/autoscaling/zz_generated.deepcopy.go b/pkg/apis/autoscaling/zz_generated.deepcopy.go index ec833882e6b..520fa8a577d 100644 --- a/pkg/apis/autoscaling/zz_generated.deepcopy.go +++ b/pkg/apis/autoscaling/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,92 +23,9 @@ package autoscaling import ( resource "k8s.io/apimachinery/pkg/api/resource" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CrossVersionObjectReference).DeepCopyInto(out.(*CrossVersionObjectReference)) - return nil - }, InType: reflect.TypeOf(&CrossVersionObjectReference{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HorizontalPodAutoscaler).DeepCopyInto(out.(*HorizontalPodAutoscaler)) - return nil - }, InType: reflect.TypeOf(&HorizontalPodAutoscaler{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HorizontalPodAutoscalerCondition).DeepCopyInto(out.(*HorizontalPodAutoscalerCondition)) - return nil - }, InType: reflect.TypeOf(&HorizontalPodAutoscalerCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HorizontalPodAutoscalerList).DeepCopyInto(out.(*HorizontalPodAutoscalerList)) - return nil - }, InType: reflect.TypeOf(&HorizontalPodAutoscalerList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HorizontalPodAutoscalerSpec).DeepCopyInto(out.(*HorizontalPodAutoscalerSpec)) - return nil - }, InType: reflect.TypeOf(&HorizontalPodAutoscalerSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HorizontalPodAutoscalerStatus).DeepCopyInto(out.(*HorizontalPodAutoscalerStatus)) - return nil - }, InType: reflect.TypeOf(&HorizontalPodAutoscalerStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*MetricSpec).DeepCopyInto(out.(*MetricSpec)) - return nil - }, InType: reflect.TypeOf(&MetricSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*MetricStatus).DeepCopyInto(out.(*MetricStatus)) - return nil - }, InType: reflect.TypeOf(&MetricStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ObjectMetricSource).DeepCopyInto(out.(*ObjectMetricSource)) - return nil - }, InType: reflect.TypeOf(&ObjectMetricSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ObjectMetricStatus).DeepCopyInto(out.(*ObjectMetricStatus)) - return nil - }, InType: reflect.TypeOf(&ObjectMetricStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodsMetricSource).DeepCopyInto(out.(*PodsMetricSource)) - return nil - }, InType: reflect.TypeOf(&PodsMetricSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodsMetricStatus).DeepCopyInto(out.(*PodsMetricStatus)) - return nil - }, InType: reflect.TypeOf(&PodsMetricStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ResourceMetricSource).DeepCopyInto(out.(*ResourceMetricSource)) - return nil - }, InType: reflect.TypeOf(&ResourceMetricSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ResourceMetricStatus).DeepCopyInto(out.(*ResourceMetricStatus)) - return nil - }, InType: reflect.TypeOf(&ResourceMetricStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Scale).DeepCopyInto(out.(*Scale)) - return nil - }, InType: reflect.TypeOf(&Scale{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ScaleSpec).DeepCopyInto(out.(*ScaleSpec)) - return nil - }, InType: reflect.TypeOf(&ScaleSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ScaleStatus).DeepCopyInto(out.(*ScaleStatus)) - return nil - }, InType: reflect.TypeOf(&ScaleStatus{})}, - ) -} - // 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 diff --git a/pkg/apis/batch/BUILD b/pkg/apis/batch/BUILD index 9c4eaadb051..d8c7452d199 100644 --- a/pkg/apis/batch/BUILD +++ b/pkg/apis/batch/BUILD @@ -15,9 +15,8 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/apis/batch", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/pkg/apis/batch/doc.go b/pkg/apis/batch/doc.go index 9b2b792bd51..a80b3597f23 100644 --- a/pkg/apis/batch/doc.go +++ b/pkg/apis/batch/doc.go @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package package batch // import "k8s.io/kubernetes/pkg/apis/batch" diff --git a/pkg/apis/batch/install/BUILD b/pkg/apis/batch/install/BUILD index 6383a779536..3445e3f35ea 100644 --- a/pkg/apis/batch/install/BUILD +++ b/pkg/apis/batch/install/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["install.go"], importpath = "k8s.io/kubernetes/pkg/apis/batch/install", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/batch:go_default_library", "//pkg/apis/batch/v1:go_default_library", "//pkg/apis/batch/v1beta1:go_default_library", diff --git a/pkg/apis/batch/install/install.go b/pkg/apis/batch/install/install.go index 7df8db201e0..e04f926057a 100644 --- a/pkg/apis/batch/install/install.go +++ b/pkg/apis/batch/install/install.go @@ -22,7 +22,7 @@ import ( "k8s.io/apimachinery/pkg/apimachinery/announced" "k8s.io/apimachinery/pkg/apimachinery/registered" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/batch/v1" "k8s.io/kubernetes/pkg/apis/batch/v1beta1" @@ -30,7 +30,7 @@ import ( ) func init() { - Install(api.GroupFactoryRegistry, api.Registry, api.Scheme) + Install(legacyscheme.GroupFactoryRegistry, legacyscheme.Registry, legacyscheme.Scheme) } // Install registers the API group and adds types to a scheme diff --git a/pkg/apis/batch/register.go b/pkg/apis/batch/register.go index 49414bd3fce..3b4a6d40678 100644 --- a/pkg/apis/batch/register.go +++ b/pkg/apis/batch/register.go @@ -42,7 +42,7 @@ var ( AddToScheme = SchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Job{}, diff --git a/pkg/apis/batch/types.go b/pkg/apis/batch/types.go index be2b692f296..32d921fb6af 100644 --- a/pkg/apis/batch/types.go +++ b/pkg/apis/batch/types.go @@ -18,7 +18,7 @@ package batch import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // +genclient @@ -250,7 +250,10 @@ type CronJobSpec struct { StartingDeadlineSeconds *int64 // Specifies how to treat concurrent executions of a Job. - // Defaults to Allow. + // Valid values are: + // - "Allow" (default): allows CronJobs to run concurrently; + // - "Forbid": forbids concurrent runs, skipping next run if previous run hasn't finished yet; + // - "Replace": cancels currently running job and replaces it with a new one // +optional ConcurrencyPolicy ConcurrencyPolicy diff --git a/pkg/apis/batch/v1/BUILD b/pkg/apis/batch/v1/BUILD index 5c24b3eabf5..2413a15ab25 100644 --- a/pkg/apis/batch/v1/BUILD +++ b/pkg/apis/batch/v1/BUILD @@ -18,9 +18,9 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/apis/batch/v1", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/v1:go_default_library", "//pkg/apis/batch:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/v1:go_default_library", "//vendor/k8s.io/api/batch/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -36,9 +36,9 @@ go_test( importpath = "k8s.io/kubernetes/pkg/apis/batch/v1_test", deps = [ ":go_default_library", - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/batch/install:go_default_library", + "//pkg/apis/core/install:go_default_library", "//vendor/k8s.io/api/batch/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/apis/batch/v1/conversion.go b/pkg/apis/batch/v1/conversion.go index 941b61d4a70..98aae5c276b 100644 --- a/pkg/apis/batch/v1/conversion.go +++ b/pkg/apis/batch/v1/conversion.go @@ -23,8 +23,8 @@ import ( "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/runtime" - k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/apis/batch" + k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" ) func addConversionFuncs(scheme *runtime.Scheme) error { @@ -62,7 +62,7 @@ func Convert_batch_JobSpec_To_v1_JobSpec(in *batch.JobSpec, out *batchv1.JobSpec out.ManualSelector = nil } - if err := k8s_api_v1.Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := k8s_api_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } return nil @@ -81,7 +81,7 @@ func Convert_v1_JobSpec_To_batch_JobSpec(in *batchv1.JobSpec, out *batch.JobSpec out.ManualSelector = nil } - if err := k8s_api_v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := k8s_api_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } return nil diff --git a/pkg/apis/batch/v1/defaults_test.go b/pkg/apis/batch/v1/defaults_test.go index f19ac542a5c..03cfcf4f2f7 100644 --- a/pkg/apis/batch/v1/defaults_test.go +++ b/pkg/apis/batch/v1/defaults_test.go @@ -25,10 +25,10 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" - _ "k8s.io/kubernetes/pkg/api/install" + "k8s.io/kubernetes/pkg/api/legacyscheme" _ "k8s.io/kubernetes/pkg/apis/batch/install" . "k8s.io/kubernetes/pkg/apis/batch/v1" + _ "k8s.io/kubernetes/pkg/apis/core/install" ) func TestSetDefaultJob(t *testing.T) { @@ -226,18 +226,18 @@ func validateDefaultInt32(t *testing.T, name string, field string, actual *int32 } func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { - data, err := runtime.Encode(api.Codecs.LegacyCodec(SchemeGroupVersion), obj) + 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(api.Codecs.UniversalDecoder(), data) + 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 = api.Scheme.Convert(obj2, obj3, nil) + err = legacyscheme.Scheme.Convert(obj2, obj3, nil) if err != nil { t.Errorf("%v\nSource: %#v", err, obj2) return nil diff --git a/pkg/apis/batch/v1/doc.go b/pkg/apis/batch/v1/doc.go index 87d9c2969c3..835d2952969 100644 --- a/pkg/apis/batch/v1/doc.go +++ b/pkg/apis/batch/v1/doc.go @@ -15,7 +15,7 @@ limitations under the License. */ // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/batch -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/batch/v1 +// +k8s:conversion-gen-external-types=k8s.io/api/batch/v1 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/batch/v1 diff --git a/pkg/apis/batch/v1/zz_generated.conversion.go b/pkg/apis/batch/v1/zz_generated.conversion.go index 487b9c63ab8..3852fcd9bbf 100644 --- a/pkg/apis/batch/v1/zz_generated.conversion.go +++ b/pkg/apis/batch/v1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,15 +21,16 @@ limitations under the License. package v1 import ( + unsafe "unsafe" + v1 "k8s.io/api/batch/v1" core_v1 "k8s.io/api/core/v1" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - api "k8s.io/kubernetes/pkg/api" - api_v1 "k8s.io/kubernetes/pkg/api/v1" batch "k8s.io/kubernetes/pkg/apis/batch" - unsafe "unsafe" + core "k8s.io/kubernetes/pkg/apis/core" + apis_core_v1 "k8s.io/kubernetes/pkg/apis/core/v1" ) func init() { @@ -87,7 +88,7 @@ func Convert_batch_Job_To_v1_Job(in *batch.Job, out *v1.Job, s conversion.Scope) func autoConvert_v1_JobCondition_To_batch_JobCondition(in *v1.JobCondition, out *batch.JobCondition, s conversion.Scope) error { out.Type = batch.JobConditionType(in.Type) - out.Status = api.ConditionStatus(in.Status) + out.Status = core.ConditionStatus(in.Status) out.LastProbeTime = in.LastProbeTime out.LastTransitionTime = in.LastTransitionTime out.Reason = in.Reason @@ -164,7 +165,7 @@ func autoConvert_v1_JobSpec_To_batch_JobSpec(in *v1.JobSpec, out *batch.JobSpec, out.BackoffLimit = (*int32)(unsafe.Pointer(in.BackoffLimit)) out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) out.ManualSelector = (*bool)(unsafe.Pointer(in.ManualSelector)) - if err := api_v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := apis_core_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } return nil @@ -177,7 +178,7 @@ func autoConvert_batch_JobSpec_To_v1_JobSpec(in *batch.JobSpec, out *v1.JobSpec, out.BackoffLimit = (*int32)(unsafe.Pointer(in.BackoffLimit)) out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) out.ManualSelector = (*bool)(unsafe.Pointer(in.ManualSelector)) - if err := api_v1.Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := apis_core_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } return nil diff --git a/pkg/apis/batch/v1/zz_generated.defaults.go b/pkg/apis/batch/v1/zz_generated.defaults.go index 18c0cc6d82a..8140c28fa60 100644 --- a/pkg/apis/batch/v1/zz_generated.defaults.go +++ b/pkg/apis/batch/v1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,7 +23,7 @@ package v1 import ( v1 "k8s.io/api/batch/v1" runtime "k8s.io/apimachinery/pkg/runtime" - api_v1 "k8s.io/kubernetes/pkg/api/v1" + core_v1 "k8s.io/kubernetes/pkg/apis/core/v1" ) // RegisterDefaults adds defaulters functions to the given scheme. @@ -37,135 +37,135 @@ func RegisterDefaults(scheme *runtime.Scheme) error { func SetObjectDefaults_Job(in *v1.Job) { SetDefaults_Job(in) - api_v1.SetDefaults_PodSpec(&in.Spec.Template.Spec) + core_v1.SetDefaults_PodSpec(&in.Spec.Template.Spec) for i := range in.Spec.Template.Spec.Volumes { a := &in.Spec.Template.Spec.Volumes[i] - api_v1.SetDefaults_Volume(a) + core_v1.SetDefaults_Volume(a) if a.VolumeSource.HostPath != nil { - api_v1.SetDefaults_HostPathVolumeSource(a.VolumeSource.HostPath) + core_v1.SetDefaults_HostPathVolumeSource(a.VolumeSource.HostPath) } if a.VolumeSource.Secret != nil { - api_v1.SetDefaults_SecretVolumeSource(a.VolumeSource.Secret) + core_v1.SetDefaults_SecretVolumeSource(a.VolumeSource.Secret) } if a.VolumeSource.ISCSI != nil { - api_v1.SetDefaults_ISCSIVolumeSource(a.VolumeSource.ISCSI) + core_v1.SetDefaults_ISCSIVolumeSource(a.VolumeSource.ISCSI) } if a.VolumeSource.RBD != nil { - api_v1.SetDefaults_RBDVolumeSource(a.VolumeSource.RBD) + core_v1.SetDefaults_RBDVolumeSource(a.VolumeSource.RBD) } if a.VolumeSource.DownwardAPI != nil { - api_v1.SetDefaults_DownwardAPIVolumeSource(a.VolumeSource.DownwardAPI) + core_v1.SetDefaults_DownwardAPIVolumeSource(a.VolumeSource.DownwardAPI) for j := range a.VolumeSource.DownwardAPI.Items { b := &a.VolumeSource.DownwardAPI.Items[j] if b.FieldRef != nil { - api_v1.SetDefaults_ObjectFieldSelector(b.FieldRef) + core_v1.SetDefaults_ObjectFieldSelector(b.FieldRef) } } } if a.VolumeSource.ConfigMap != nil { - api_v1.SetDefaults_ConfigMapVolumeSource(a.VolumeSource.ConfigMap) + core_v1.SetDefaults_ConfigMapVolumeSource(a.VolumeSource.ConfigMap) } if a.VolumeSource.AzureDisk != nil { - api_v1.SetDefaults_AzureDiskVolumeSource(a.VolumeSource.AzureDisk) + core_v1.SetDefaults_AzureDiskVolumeSource(a.VolumeSource.AzureDisk) } if a.VolumeSource.Projected != nil { - api_v1.SetDefaults_ProjectedVolumeSource(a.VolumeSource.Projected) + core_v1.SetDefaults_ProjectedVolumeSource(a.VolumeSource.Projected) for j := range a.VolumeSource.Projected.Sources { b := &a.VolumeSource.Projected.Sources[j] if b.DownwardAPI != nil { for k := range b.DownwardAPI.Items { c := &b.DownwardAPI.Items[k] if c.FieldRef != nil { - api_v1.SetDefaults_ObjectFieldSelector(c.FieldRef) + core_v1.SetDefaults_ObjectFieldSelector(c.FieldRef) } } } } } if a.VolumeSource.ScaleIO != nil { - api_v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) + core_v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) } } for i := range in.Spec.Template.Spec.InitContainers { a := &in.Spec.Template.Spec.InitContainers[i] - api_v1.SetDefaults_Container(a) + core_v1.SetDefaults_Container(a) for j := range a.Ports { b := &a.Ports[j] - api_v1.SetDefaults_ContainerPort(b) + core_v1.SetDefaults_ContainerPort(b) } for j := range a.Env { b := &a.Env[j] if b.ValueFrom != nil { if b.ValueFrom.FieldRef != nil { - api_v1.SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) + core_v1.SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) } } } - api_v1.SetDefaults_ResourceList(&a.Resources.Limits) - api_v1.SetDefaults_ResourceList(&a.Resources.Requests) + core_v1.SetDefaults_ResourceList(&a.Resources.Limits) + core_v1.SetDefaults_ResourceList(&a.Resources.Requests) if a.LivenessProbe != nil { - api_v1.SetDefaults_Probe(a.LivenessProbe) + core_v1.SetDefaults_Probe(a.LivenessProbe) if a.LivenessProbe.Handler.HTTPGet != nil { - api_v1.SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) + core_v1.SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) } } if a.ReadinessProbe != nil { - api_v1.SetDefaults_Probe(a.ReadinessProbe) + core_v1.SetDefaults_Probe(a.ReadinessProbe) if a.ReadinessProbe.Handler.HTTPGet != nil { - api_v1.SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) + core_v1.SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) } } if a.Lifecycle != nil { if a.Lifecycle.PostStart != nil { if a.Lifecycle.PostStart.HTTPGet != nil { - api_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) + core_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) } } if a.Lifecycle.PreStop != nil { if a.Lifecycle.PreStop.HTTPGet != nil { - api_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) + core_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) } } } } for i := range in.Spec.Template.Spec.Containers { a := &in.Spec.Template.Spec.Containers[i] - api_v1.SetDefaults_Container(a) + core_v1.SetDefaults_Container(a) for j := range a.Ports { b := &a.Ports[j] - api_v1.SetDefaults_ContainerPort(b) + core_v1.SetDefaults_ContainerPort(b) } for j := range a.Env { b := &a.Env[j] if b.ValueFrom != nil { if b.ValueFrom.FieldRef != nil { - api_v1.SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) + core_v1.SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) } } } - api_v1.SetDefaults_ResourceList(&a.Resources.Limits) - api_v1.SetDefaults_ResourceList(&a.Resources.Requests) + core_v1.SetDefaults_ResourceList(&a.Resources.Limits) + core_v1.SetDefaults_ResourceList(&a.Resources.Requests) if a.LivenessProbe != nil { - api_v1.SetDefaults_Probe(a.LivenessProbe) + core_v1.SetDefaults_Probe(a.LivenessProbe) if a.LivenessProbe.Handler.HTTPGet != nil { - api_v1.SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) + core_v1.SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) } } if a.ReadinessProbe != nil { - api_v1.SetDefaults_Probe(a.ReadinessProbe) + core_v1.SetDefaults_Probe(a.ReadinessProbe) if a.ReadinessProbe.Handler.HTTPGet != nil { - api_v1.SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) + core_v1.SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) } } if a.Lifecycle != nil { if a.Lifecycle.PostStart != nil { if a.Lifecycle.PostStart.HTTPGet != nil { - api_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) + core_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) } } if a.Lifecycle.PreStop != nil { if a.Lifecycle.PreStop.HTTPGet != nil { - api_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) + core_v1.SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) } } } diff --git a/pkg/apis/batch/v1beta1/BUILD b/pkg/apis/batch/v1beta1/BUILD index 82dc4910afb..2985d4e9d07 100644 --- a/pkg/apis/batch/v1beta1/BUILD +++ b/pkg/apis/batch/v1beta1/BUILD @@ -18,10 +18,10 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/apis/batch/v1beta1", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/v1:go_default_library", "//pkg/apis/batch:go_default_library", "//pkg/apis/batch/v1:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/v1:go_default_library", "//vendor/k8s.io/api/batch/v1beta1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -37,9 +37,9 @@ go_test( importpath = "k8s.io/kubernetes/pkg/apis/batch/v1beta1_test", deps = [ ":go_default_library", - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/batch/install:go_default_library", + "//pkg/apis/core/install:go_default_library", "//vendor/k8s.io/api/batch/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", ], diff --git a/pkg/apis/batch/v1beta1/defaults_test.go b/pkg/apis/batch/v1beta1/defaults_test.go index 1f11b0566df..578ea9859ea 100644 --- a/pkg/apis/batch/v1beta1/defaults_test.go +++ b/pkg/apis/batch/v1beta1/defaults_test.go @@ -23,10 +23,10 @@ import ( batchv1beta1 "k8s.io/api/batch/v1beta1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" - _ "k8s.io/kubernetes/pkg/api/install" + "k8s.io/kubernetes/pkg/api/legacyscheme" _ "k8s.io/kubernetes/pkg/apis/batch/install" . "k8s.io/kubernetes/pkg/apis/batch/v1beta1" + _ "k8s.io/kubernetes/pkg/apis/core/install" ) func TestSetDefaultCronJob(t *testing.T) { @@ -90,18 +90,18 @@ func TestSetDefaultCronJob(t *testing.T) { } func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { - data, err := runtime.Encode(api.Codecs.LegacyCodec(SchemeGroupVersion), obj) + 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(api.Codecs.UniversalDecoder(), data) + 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 = api.Scheme.Convert(obj2, obj3, nil) + err = legacyscheme.Scheme.Convert(obj2, obj3, nil) if err != nil { t.Errorf("%v\nSource: %#v", err, obj2) return nil diff --git a/pkg/apis/batch/v1beta1/doc.go b/pkg/apis/batch/v1beta1/doc.go index 6c3e65dcb3d..91d32c5f313 100644 --- a/pkg/apis/batch/v1beta1/doc.go +++ b/pkg/apis/batch/v1beta1/doc.go @@ -15,7 +15,7 @@ limitations under the License. */ // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/batch -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/batch/v1beta1 +// +k8s:conversion-gen-external-types=k8s.io/api/batch/v1beta1 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/batch/v1beta1 diff --git a/pkg/apis/batch/v1beta1/zz_generated.conversion.go b/pkg/apis/batch/v1beta1/zz_generated.conversion.go index f6ae62bd039..8399c047dc3 100644 --- a/pkg/apis/batch/v1beta1/zz_generated.conversion.go +++ b/pkg/apis/batch/v1beta1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,15 +21,16 @@ limitations under the License. package v1beta1 import ( + unsafe "unsafe" + v1beta1 "k8s.io/api/batch/v1beta1" core_v1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - api "k8s.io/kubernetes/pkg/api" batch "k8s.io/kubernetes/pkg/apis/batch" batch_v1 "k8s.io/kubernetes/pkg/apis/batch/v1" - unsafe "unsafe" + core "k8s.io/kubernetes/pkg/apis/core" ) func init() { @@ -166,7 +167,7 @@ func Convert_batch_CronJobSpec_To_v1beta1_CronJobSpec(in *batch.CronJobSpec, out } func autoConvert_v1beta1_CronJobStatus_To_batch_CronJobStatus(in *v1beta1.CronJobStatus, out *batch.CronJobStatus, s conversion.Scope) error { - out.Active = *(*[]api.ObjectReference)(unsafe.Pointer(&in.Active)) + out.Active = *(*[]core.ObjectReference)(unsafe.Pointer(&in.Active)) out.LastScheduleTime = (*v1.Time)(unsafe.Pointer(in.LastScheduleTime)) return nil } diff --git a/pkg/apis/batch/v1beta1/zz_generated.defaults.go b/pkg/apis/batch/v1beta1/zz_generated.defaults.go index e9256b91d07..938bb14eb47 100644 --- a/pkg/apis/batch/v1beta1/zz_generated.defaults.go +++ b/pkg/apis/batch/v1beta1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,7 +23,7 @@ package v1beta1 import ( v1beta1 "k8s.io/api/batch/v1beta1" runtime "k8s.io/apimachinery/pkg/runtime" - v1 "k8s.io/kubernetes/pkg/api/v1" + v1 "k8s.io/kubernetes/pkg/apis/core/v1" ) // RegisterDefaults adds defaulters functions to the given scheme. diff --git a/pkg/apis/batch/v2alpha1/BUILD b/pkg/apis/batch/v2alpha1/BUILD index 88b630d7677..79103911e03 100644 --- a/pkg/apis/batch/v2alpha1/BUILD +++ b/pkg/apis/batch/v2alpha1/BUILD @@ -18,10 +18,10 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/apis/batch/v2alpha1", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/v1:go_default_library", "//pkg/apis/batch:go_default_library", "//pkg/apis/batch/v1:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/v1:go_default_library", "//vendor/k8s.io/api/batch/v2alpha1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -37,9 +37,9 @@ go_test( importpath = "k8s.io/kubernetes/pkg/apis/batch/v2alpha1_test", deps = [ ":go_default_library", - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/batch/install:go_default_library", + "//pkg/apis/core/install:go_default_library", "//vendor/k8s.io/api/batch/v2alpha1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", ], diff --git a/pkg/apis/batch/v2alpha1/defaults_test.go b/pkg/apis/batch/v2alpha1/defaults_test.go index ac76dd46bc1..1864d8c500b 100644 --- a/pkg/apis/batch/v2alpha1/defaults_test.go +++ b/pkg/apis/batch/v2alpha1/defaults_test.go @@ -23,10 +23,10 @@ import ( batchv2alpha1 "k8s.io/api/batch/v2alpha1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" - _ "k8s.io/kubernetes/pkg/api/install" + "k8s.io/kubernetes/pkg/api/legacyscheme" _ "k8s.io/kubernetes/pkg/apis/batch/install" . "k8s.io/kubernetes/pkg/apis/batch/v2alpha1" + _ "k8s.io/kubernetes/pkg/apis/core/install" ) func TestSetDefaultCronJob(t *testing.T) { @@ -78,18 +78,18 @@ func TestSetDefaultCronJob(t *testing.T) { } func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { - data, err := runtime.Encode(api.Codecs.LegacyCodec(SchemeGroupVersion), obj) + 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(api.Codecs.UniversalDecoder(), data) + 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 = api.Scheme.Convert(obj2, obj3, nil) + err = legacyscheme.Scheme.Convert(obj2, obj3, nil) if err != nil { t.Errorf("%v\nSource: %#v", err, obj2) return nil diff --git a/pkg/apis/batch/v2alpha1/doc.go b/pkg/apis/batch/v2alpha1/doc.go index 8fbe1041968..dabf0dc394c 100644 --- a/pkg/apis/batch/v2alpha1/doc.go +++ b/pkg/apis/batch/v2alpha1/doc.go @@ -15,7 +15,7 @@ limitations under the License. */ // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/batch -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/batch/v2alpha1 +// +k8s:conversion-gen-external-types=k8s.io/api/batch/v2alpha1 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/batch/v2alpha1 diff --git a/pkg/apis/batch/v2alpha1/zz_generated.conversion.go b/pkg/apis/batch/v2alpha1/zz_generated.conversion.go index 92c8c8ceb6a..b5d03d747c5 100644 --- a/pkg/apis/batch/v2alpha1/zz_generated.conversion.go +++ b/pkg/apis/batch/v2alpha1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,15 +21,16 @@ limitations under the License. package v2alpha1 import ( + unsafe "unsafe" + v2alpha1 "k8s.io/api/batch/v2alpha1" core_v1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - api "k8s.io/kubernetes/pkg/api" batch "k8s.io/kubernetes/pkg/apis/batch" batch_v1 "k8s.io/kubernetes/pkg/apis/batch/v1" - unsafe "unsafe" + core "k8s.io/kubernetes/pkg/apis/core" ) func init() { @@ -166,7 +167,7 @@ func Convert_batch_CronJobSpec_To_v2alpha1_CronJobSpec(in *batch.CronJobSpec, ou } func autoConvert_v2alpha1_CronJobStatus_To_batch_CronJobStatus(in *v2alpha1.CronJobStatus, out *batch.CronJobStatus, s conversion.Scope) error { - out.Active = *(*[]api.ObjectReference)(unsafe.Pointer(&in.Active)) + out.Active = *(*[]core.ObjectReference)(unsafe.Pointer(&in.Active)) out.LastScheduleTime = (*v1.Time)(unsafe.Pointer(in.LastScheduleTime)) return nil } diff --git a/pkg/apis/batch/v2alpha1/zz_generated.defaults.go b/pkg/apis/batch/v2alpha1/zz_generated.defaults.go index 46e8ca6121d..427ba5cff0f 100644 --- a/pkg/apis/batch/v2alpha1/zz_generated.defaults.go +++ b/pkg/apis/batch/v2alpha1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,7 +23,7 @@ package v2alpha1 import ( v2alpha1 "k8s.io/api/batch/v2alpha1" runtime "k8s.io/apimachinery/pkg/runtime" - v1 "k8s.io/kubernetes/pkg/api/v1" + v1 "k8s.io/kubernetes/pkg/apis/core/v1" ) // RegisterDefaults adds defaulters functions to the given scheme. diff --git a/pkg/apis/batch/validation/BUILD b/pkg/apis/batch/validation/BUILD index ad686162a74..1a6268f9f22 100644 --- a/pkg/apis/batch/validation/BUILD +++ b/pkg/apis/batch/validation/BUILD @@ -11,9 +11,9 @@ go_library( srcs = ["validation.go"], importpath = "k8s.io/kubernetes/pkg/apis/batch/validation", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/validation:go_default_library", "//pkg/apis/batch:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//vendor/github.com/robfig/cron:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/validation:go_default_library", @@ -26,11 +26,11 @@ go_library( go_test( name = "go_default_test", srcs = ["validation_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/apis/batch/validation", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", "//pkg/apis/batch:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", ], diff --git a/pkg/apis/batch/validation/validation.go b/pkg/apis/batch/validation/validation.go index 7f9215d700d..a31bf6416e4 100644 --- a/pkg/apis/batch/validation/validation.go +++ b/pkg/apis/batch/validation/validation.go @@ -24,9 +24,9 @@ import ( "k8s.io/apimachinery/pkg/labels" apimachineryvalidation "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" - apivalidation "k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/apis/batch" + api "k8s.io/kubernetes/pkg/apis/core" + apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" ) // TODO: generalize for other controller objects that will follow the same pattern, such as ReplicaSet and DaemonSet, and diff --git a/pkg/apis/batch/validation/validation_test.go b/pkg/apis/batch/validation/validation_test.go index f40921e42f8..13135e2ea53 100644 --- a/pkg/apis/batch/validation/validation_test.go +++ b/pkg/apis/batch/validation/validation_test.go @@ -22,8 +22,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/batch" + api "k8s.io/kubernetes/pkg/apis/core" ) func getValidManualSelector() *metav1.LabelSelector { diff --git a/pkg/apis/batch/zz_generated.deepcopy.go b/pkg/apis/batch/zz_generated.deepcopy.go index 483452c09e0..27a03db2724 100644 --- a/pkg/apis/batch/zz_generated.deepcopy.go +++ b/pkg/apis/batch/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,69 +22,10 @@ package batch import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - api "k8s.io/kubernetes/pkg/api" - reflect "reflect" + core "k8s.io/kubernetes/pkg/apis/core" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CronJob).DeepCopyInto(out.(*CronJob)) - return nil - }, InType: reflect.TypeOf(&CronJob{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CronJobList).DeepCopyInto(out.(*CronJobList)) - return nil - }, InType: reflect.TypeOf(&CronJobList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CronJobSpec).DeepCopyInto(out.(*CronJobSpec)) - return nil - }, InType: reflect.TypeOf(&CronJobSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CronJobStatus).DeepCopyInto(out.(*CronJobStatus)) - return nil - }, InType: reflect.TypeOf(&CronJobStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Job).DeepCopyInto(out.(*Job)) - return nil - }, InType: reflect.TypeOf(&Job{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*JobCondition).DeepCopyInto(out.(*JobCondition)) - return nil - }, InType: reflect.TypeOf(&JobCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*JobList).DeepCopyInto(out.(*JobList)) - return nil - }, InType: reflect.TypeOf(&JobList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*JobSpec).DeepCopyInto(out.(*JobSpec)) - return nil - }, InType: reflect.TypeOf(&JobSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*JobStatus).DeepCopyInto(out.(*JobStatus)) - return nil - }, InType: reflect.TypeOf(&JobStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*JobTemplate).DeepCopyInto(out.(*JobTemplate)) - return nil - }, InType: reflect.TypeOf(&JobTemplate{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*JobTemplateSpec).DeepCopyInto(out.(*JobTemplateSpec)) - return nil - }, InType: reflect.TypeOf(&JobTemplateSpec{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CronJob) DeepCopyInto(out *CronJob) { *out = *in @@ -206,7 +147,7 @@ func (in *CronJobStatus) DeepCopyInto(out *CronJobStatus) { *out = *in if in.Active != nil { in, out := &in.Active, &out.Active - *out = make([]api.ObjectReference, len(*in)) + *out = make([]core.ObjectReference, len(*in)) copy(*out, *in) } if in.LastScheduleTime != nil { diff --git a/pkg/apis/certificates/BUILD b/pkg/apis/certificates/BUILD index f6fc3073918..cebbfcdea67 100644 --- a/pkg/apis/certificates/BUILD +++ b/pkg/apis/certificates/BUILD @@ -17,7 +17,6 @@ go_library( importpath = "k8s.io/kubernetes/pkg/apis/certificates", deps = [ "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/pkg/apis/certificates/doc.go b/pkg/apis/certificates/doc.go index cdc89bdcc5e..65aad6491eb 100644 --- a/pkg/apis/certificates/doc.go +++ b/pkg/apis/certificates/doc.go @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +groupName=certificates.k8s.io package certificates // import "k8s.io/kubernetes/pkg/apis/certificates" diff --git a/pkg/apis/certificates/install/BUILD b/pkg/apis/certificates/install/BUILD index 7ca4b372ee7..e8962d64056 100644 --- a/pkg/apis/certificates/install/BUILD +++ b/pkg/apis/certificates/install/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["install.go"], importpath = "k8s.io/kubernetes/pkg/apis/certificates/install", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/certificates:go_default_library", "//pkg/apis/certificates/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", diff --git a/pkg/apis/certificates/install/install.go b/pkg/apis/certificates/install/install.go index 34da4b93f3b..a4e9bb39db9 100644 --- a/pkg/apis/certificates/install/install.go +++ b/pkg/apis/certificates/install/install.go @@ -23,13 +23,13 @@ import ( "k8s.io/apimachinery/pkg/apimachinery/registered" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/certificates" "k8s.io/kubernetes/pkg/apis/certificates/v1beta1" ) func init() { - Install(api.GroupFactoryRegistry, api.Registry, api.Scheme) + Install(legacyscheme.GroupFactoryRegistry, legacyscheme.Registry, legacyscheme.Scheme) } // Install registers the API group and adds types to a scheme diff --git a/pkg/apis/certificates/register.go b/pkg/apis/certificates/register.go index 085737583fc..a876251ca43 100644 --- a/pkg/apis/certificates/register.go +++ b/pkg/apis/certificates/register.go @@ -42,7 +42,7 @@ func Resource(resource string) schema.GroupResource { return SchemeGroupVersion.WithResource(resource).GroupResource() } -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &CertificateSigningRequest{}, diff --git a/pkg/apis/certificates/v1beta1/doc.go b/pkg/apis/certificates/v1beta1/doc.go index e2315d387fb..d5f13dfff3a 100644 --- a/pkg/apis/certificates/v1beta1/doc.go +++ b/pkg/apis/certificates/v1beta1/doc.go @@ -15,7 +15,7 @@ limitations under the License. */ // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/certificates -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/certificates/v1beta1 +// +k8s:conversion-gen-external-types=k8s.io/api/certificates/v1beta1 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/certificates/v1beta1 diff --git a/pkg/apis/certificates/v1beta1/zz_generated.conversion.go b/pkg/apis/certificates/v1beta1/zz_generated.conversion.go index 96780e6730d..b1df67c2ec0 100644 --- a/pkg/apis/certificates/v1beta1/zz_generated.conversion.go +++ b/pkg/apis/certificates/v1beta1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,11 +21,12 @@ limitations under the License. package v1beta1 import ( + unsafe "unsafe" + v1beta1 "k8s.io/api/certificates/v1beta1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" certificates "k8s.io/kubernetes/pkg/apis/certificates" - unsafe "unsafe" ) func init() { diff --git a/pkg/apis/certificates/v1beta1/zz_generated.defaults.go b/pkg/apis/certificates/v1beta1/zz_generated.defaults.go index 155ea36921d..edab232d23c 100644 --- a/pkg/apis/certificates/v1beta1/zz_generated.defaults.go +++ b/pkg/apis/certificates/v1beta1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/apis/certificates/validation/BUILD b/pkg/apis/certificates/validation/BUILD index e8f66ab2dfa..788809206c0 100644 --- a/pkg/apis/certificates/validation/BUILD +++ b/pkg/apis/certificates/validation/BUILD @@ -10,8 +10,8 @@ go_library( srcs = ["validation.go"], importpath = "k8s.io/kubernetes/pkg/apis/certificates/validation", deps = [ - "//pkg/api/validation:go_default_library", "//pkg/apis/certificates:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", ], ) diff --git a/pkg/apis/certificates/validation/validation.go b/pkg/apis/certificates/validation/validation.go index 4077e6b0cc5..3b61074bc6b 100644 --- a/pkg/apis/certificates/validation/validation.go +++ b/pkg/apis/certificates/validation/validation.go @@ -20,8 +20,8 @@ import ( "fmt" "k8s.io/apimachinery/pkg/util/validation/field" - apivalidation "k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/apis/certificates" + apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" ) // validateCSR validates the signature and formatting of a base64-wrapped, diff --git a/pkg/apis/certificates/zz_generated.deepcopy.go b/pkg/apis/certificates/zz_generated.deepcopy.go index c1f76f53fae..4b3e1d556da 100644 --- a/pkg/apis/certificates/zz_generated.deepcopy.go +++ b/pkg/apis/certificates/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,44 +21,9 @@ limitations under the License. package certificates import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CertificateSigningRequest).DeepCopyInto(out.(*CertificateSigningRequest)) - return nil - }, InType: reflect.TypeOf(&CertificateSigningRequest{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CertificateSigningRequestCondition).DeepCopyInto(out.(*CertificateSigningRequestCondition)) - return nil - }, InType: reflect.TypeOf(&CertificateSigningRequestCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CertificateSigningRequestList).DeepCopyInto(out.(*CertificateSigningRequestList)) - return nil - }, InType: reflect.TypeOf(&CertificateSigningRequestList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CertificateSigningRequestSpec).DeepCopyInto(out.(*CertificateSigningRequestSpec)) - return nil - }, InType: reflect.TypeOf(&CertificateSigningRequestSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CertificateSigningRequestStatus).DeepCopyInto(out.(*CertificateSigningRequestStatus)) - return nil - }, InType: reflect.TypeOf(&CertificateSigningRequestStatus{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CertificateSigningRequest) DeepCopyInto(out *CertificateSigningRequest) { *out = *in diff --git a/pkg/apis/componentconfig/BUILD b/pkg/apis/componentconfig/BUILD index c1608bc244a..143d8b0223a 100644 --- a/pkg/apis/componentconfig/BUILD +++ b/pkg/apis/componentconfig/BUILD @@ -19,7 +19,6 @@ go_library( deps = [ "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", @@ -29,8 +28,8 @@ go_library( go_test( name = "go_default_test", srcs = ["helpers_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/apis/componentconfig", - library = ":go_default_library", deps = ["//vendor/github.com/spf13/pflag:go_default_library"], ) diff --git a/pkg/apis/componentconfig/doc.go b/pkg/apis/componentconfig/doc.go index 8fe8d52edcb..159d61928b9 100644 --- a/pkg/apis/componentconfig/doc.go +++ b/pkg/apis/componentconfig/doc.go @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package package componentconfig // import "k8s.io/kubernetes/pkg/apis/componentconfig" diff --git a/pkg/apis/componentconfig/helpers.go b/pkg/apis/componentconfig/helpers.go index 39fc082da0d..a39ec138c81 100644 --- a/pkg/apis/componentconfig/helpers.go +++ b/pkg/apis/componentconfig/helpers.go @@ -61,22 +61,6 @@ func (v IPVar) Type() string { return "ip" } -func (m *ProxyMode) Set(s string) error { - *m = ProxyMode(s) - return nil -} - -func (m *ProxyMode) String() string { - if m != nil { - return string(*m) - } - return "" -} - -func (m *ProxyMode) Type() string { - return "ProxyMode" -} - type PortRangeVar struct { Val *string } diff --git a/pkg/apis/componentconfig/install/BUILD b/pkg/apis/componentconfig/install/BUILD index 402204a9c9b..fb58da06e40 100644 --- a/pkg/apis/componentconfig/install/BUILD +++ b/pkg/apis/componentconfig/install/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["install.go"], importpath = "k8s.io/kubernetes/pkg/apis/componentconfig/install", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/componentconfig:go_default_library", "//pkg/apis/componentconfig/v1alpha1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", diff --git a/pkg/apis/componentconfig/install/install.go b/pkg/apis/componentconfig/install/install.go index 53b95f6a531..d5e48363b47 100644 --- a/pkg/apis/componentconfig/install/install.go +++ b/pkg/apis/componentconfig/install/install.go @@ -22,13 +22,13 @@ import ( "k8s.io/apimachinery/pkg/apimachinery/announced" "k8s.io/apimachinery/pkg/apimachinery/registered" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/componentconfig" "k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1" ) func init() { - Install(api.GroupFactoryRegistry, api.Registry, api.Scheme) + Install(legacyscheme.GroupFactoryRegistry, legacyscheme.Registry, legacyscheme.Scheme) } // Install registers the API group and adds types to a scheme diff --git a/pkg/apis/componentconfig/register.go b/pkg/apis/componentconfig/register.go index 53675741352..7ba52b1f68e 100644 --- a/pkg/apis/componentconfig/register.go +++ b/pkg/apis/componentconfig/register.go @@ -45,7 +45,6 @@ func Resource(resource string) schema.GroupResource { func addKnownTypes(scheme *runtime.Scheme) error { // TODO this will get cleaned up with the scheme types are fixed scheme.AddKnownTypes(SchemeGroupVersion, - &KubeProxyConfiguration{}, &KubeSchedulerConfiguration{}, ) return nil diff --git a/pkg/apis/componentconfig/types.go b/pkg/apis/componentconfig/types.go index 6aa64e436dc..4788176be67 100644 --- a/pkg/apis/componentconfig/types.go +++ b/pkg/apis/componentconfig/types.go @@ -30,188 +30,97 @@ type ClientConnectionConfiguration struct { AcceptContentTypes string // contentType is the content type used when sending data to the server from this client. ContentType string - // qps controls the number of queries per second allowed for this connection. + // cps controls the number of queries per second allowed for this connection. QPS float32 // burst allows extra queries to accumulate when a client is exceeding its rate. - Burst int + Burst int32 } -// KubeProxyIPTablesConfiguration contains iptables-related configuration -// details for the Kubernetes proxy server. -type KubeProxyIPTablesConfiguration struct { - // masqueradeBit is the bit of the iptables fwmark space to use for SNAT if using - // the pure iptables proxy mode. Values must be within the range [0, 31]. - MasqueradeBit *int32 - // masqueradeAll tells kube-proxy to SNAT everything if using the pure iptables proxy mode. - MasqueradeAll bool - // syncPeriod is the period that iptables rules are refreshed (e.g. '5s', '1m', - // '2h22m'). Must be greater than 0. - SyncPeriod metav1.Duration - // minSyncPeriod is the minimum period that iptables rules are refreshed (e.g. '5s', '1m', - // '2h22m'). - MinSyncPeriod metav1.Duration +// SchedulerPolicyConfigMapKey defines the key of the element in the +// scheduler's policy ConfigMap that contains scheduler's policy config. +const SchedulerPolicyConfigMapKey string = "policy.cfg" + +// SchedulerPolicySource configures a means to obtain a scheduler Policy. One +// source field must be specified, and source fields are mutually exclusive. +type SchedulerPolicySource struct { + // File is a file policy source. + File *SchedulerPolicyFileSource + // ConfigMap is a config map policy source. + ConfigMap *SchedulerPolicyConfigMapSource } -// KubeProxyIPVSConfiguration contains ipvs-related configuration -// details for the Kubernetes proxy server. -type KubeProxyIPVSConfiguration struct { - // syncPeriod is the period that ipvs rules are refreshed (e.g. '5s', '1m', - // '2h22m'). Must be greater than 0. - SyncPeriod metav1.Duration - // minSyncPeriod is the minimum period that ipvs rules are refreshed (e.g. '5s', '1m', - // '2h22m'). - MinSyncPeriod metav1.Duration - // ipvs scheduler - Scheduler string +// SchedulerPolicyFileSource is a policy serialized to disk and accessed via +// path. +type SchedulerPolicyFileSource struct { + // Path is the location of a serialized policy. + Path string } -// KubeProxyConntrackConfiguration contains conntrack settings for -// the Kubernetes proxy server. -type KubeProxyConntrackConfiguration struct { - // max is the maximum number of NAT connections to track (0 to - // leave as-is). This takes precedence over conntrackMaxPerCore and conntrackMin. - Max int32 - // maxPerCore is the maximum number of NAT connections to track - // per CPU core (0 to leave the limit as-is and ignore conntrackMin). - MaxPerCore int32 - // min is the minimum value of connect-tracking records to allocate, - // regardless of conntrackMaxPerCore (set conntrackMaxPerCore=0 to leave the limit as-is). - Min int32 - // tcpEstablishedTimeout is how long an idle TCP connection will be kept open - // (e.g. '2s'). Must be greater than 0. - TCPEstablishedTimeout metav1.Duration - // tcpCloseWaitTimeout is how long an idle conntrack entry - // in CLOSE_WAIT state will remain in the conntrack - // table. (e.g. '60s'). Must be greater than 0 to set. - TCPCloseWaitTimeout metav1.Duration +// SchedulerPolicyConfigMapSource is a policy serialized into a config map value +// under the SchedulerPolicyConfigMapKey key. +type SchedulerPolicyConfigMapSource struct { + // Namespace is the namespace of the policy config map. + Namespace string + // Name is the name of hte policy config map. + Name string } -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// KubeProxyConfiguration contains everything necessary to configure the -// Kubernetes proxy server. -type KubeProxyConfiguration struct { - metav1.TypeMeta - - // featureGates is a comma-separated list of key=value pairs that control - // which alpha/beta features are enabled. - // - // TODO this really should be a map but that requires refactoring all - // components to use config files because local-up-cluster.sh only supports - // the --feature-gates flag right now, which is comma-separated key=value - // pairs. - FeatureGates string - - // bindAddress is the IP address for the proxy server to serve on (set to 0.0.0.0 - // for all interfaces) - BindAddress string - // healthzBindAddress is the IP address and port for the health check server to serve on, - // defaulting to 0.0.0.0:10256 - HealthzBindAddress string - // metricsBindAddress is the IP address and port for the metrics server to serve on, - // defaulting to 127.0.0.1:10249 (set to 0.0.0.0 for all interfaces) - MetricsBindAddress string - // enableProfiling enables profiling via web interface on /debug/pprof handler. - // Profiling handlers will be handled by metrics server. - EnableProfiling bool - // clusterCIDR is the CIDR range of the pods in the cluster. It is used to - // bridge traffic coming from outside of the cluster. If not provided, - // no off-cluster bridging will be performed. - ClusterCIDR string - // hostnameOverride, if non-empty, will be used as the identity instead of the actual hostname. - HostnameOverride string - // clientConnection specifies the kubeconfig file and client connection settings for the proxy - // server to use when communicating with the apiserver. - ClientConnection ClientConnectionConfiguration - // iptables contains iptables-related configuration options. - IPTables KubeProxyIPTablesConfiguration - // ipvs contains ipvs-related configuration options. - IPVS KubeProxyIPVSConfiguration - // oomScoreAdj is the oom-score-adj value for kube-proxy process. Values must be within - // the range [-1000, 1000] - OOMScoreAdj *int32 - // mode specifies which proxy mode to use. - Mode ProxyMode - // portRange is the range of host ports (beginPort-endPort, inclusive) that may be consumed - // in order to proxy service traffic. If unspecified (0-0) then ports will be randomly chosen. - PortRange string - // resourceContainer is the absolute name of the resource-only container to create and run - // the Kube-proxy in (Default: /kube-proxy). - ResourceContainer string - // udpIdleTimeout is how long an idle UDP connection will be kept open (e.g. '250ms', '2s'). - // Must be greater than 0. Only applicable for proxyMode=userspace. - UDPIdleTimeout metav1.Duration - // conntrack contains conntrack-related configuration options. - Conntrack KubeProxyConntrackConfiguration - // configSyncPeriod is how often configuration from the apiserver is refreshed. Must be greater - // than 0. - ConfigSyncPeriod metav1.Duration +// SchedulerAlgorithmSource is the source of a scheduler algorithm. One source +// field must be specified, and source fields are mutually exclusive. +type SchedulerAlgorithmSource struct { + // Policy is a policy based algorithm source. + Policy *SchedulerPolicySource + // Provider is the name of a scheduling algorithm provider to use. + Provider *string } -// Currently two modes of proxying are available: 'userspace' (older, stable) or 'iptables' -// (newer, faster). If blank, use the best-available proxy (currently iptables, but may -// change in future versions). If the iptables proxy is selected, regardless of how, but -// the system's kernel or iptables versions are insufficient, this always falls back to the -// userspace proxy. -type ProxyMode string - -const ( - ProxyModeUserspace ProxyMode = "userspace" - ProxyModeIPTables ProxyMode = "iptables" -) - // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type KubeSchedulerConfiguration struct { metav1.TypeMeta - // port is the port that the scheduler's http service runs on. - Port int32 - // address is the IP address to serve on. - Address string - // algorithmProvider is the scheduling algorithm provider to use. - AlgorithmProvider string - // policyConfigFile is the filepath to the scheduler policy configuration. - PolicyConfigFile string - // enableProfiling enables profiling via web interface. - EnableProfiling bool - // enableContentionProfiling enables lock contention profiling, if enableProfiling is true. - EnableContentionProfiling bool - // contentType is contentType of requests sent to apiserver. - ContentType string - // kubeAPIQPS is the QPS to use while talking with kubernetes apiserver. - KubeAPIQPS float32 - // kubeAPIBurst is the QPS burst to use while talking with kubernetes apiserver. - KubeAPIBurst int32 // schedulerName is name of the scheduler, used to select which pods // will be processed by this scheduler, based on pod's "spec.SchedulerName". SchedulerName string + // AlgorithmSource specifies the scheduler algorithm source. + AlgorithmSource SchedulerAlgorithmSource // RequiredDuringScheduling affinity is not symmetric, but there is an implicit PreferredDuringScheduling affinity rule // corresponding to every RequiredDuringScheduling affinity rule. // HardPodAffinitySymmetricWeight represents the weight of implicit PreferredDuringScheduling affinity rule, in the range 0-100. - HardPodAffinitySymmetricWeight int + HardPodAffinitySymmetricWeight int32 + + // LeaderElection defines the configuration of leader election client. + LeaderElection KubeSchedulerLeaderElectionConfiguration + + // ClientConnection specifies the kubeconfig file and client connection + // settings for the proxy server to use when communicating with the apiserver. + ClientConnection ClientConnectionConfiguration + // HealthzBindAddress is the IP address and port for the health check server to serve on, + // defaulting to 0.0.0.0:10251 + HealthzBindAddress string + // MetricsBindAddress is the IP address and port for the metrics server to + // serve on, defaulting to 0.0.0.0:10251. + MetricsBindAddress string + // EnableProfiling enables profiling via web interface on /debug/pprof + // handler. Profiling handlers will be handled by metrics server. + EnableProfiling bool + // EnableContentionProfiling enables lock contention profiling, if + // EnableProfiling is true. + EnableContentionProfiling bool + // Indicate the "all topologies" set for empty topologyKey when it's used for PreferredDuringScheduling pod anti-affinity. // DEPRECATED: This is no longer used. FailureDomains string - // leaderElection defines the configuration of leader election client. - LeaderElection LeaderElectionConfiguration +} + +// KubeSchedulerLeaderElectionConfiguration expands LeaderElectionConfiguration +// to include scheduler specific configuration. +type KubeSchedulerLeaderElectionConfiguration struct { + LeaderElectionConfiguration // LockObjectNamespace defines the namespace of the lock object LockObjectNamespace string // LockObjectName defines the lock object name LockObjectName string - // PolicyConfigMapName is the name of the ConfigMap object that specifies - // the scheduler's policy config. If UseLegacyPolicyConfig is true, scheduler - // uses PolicyConfigFile. If UseLegacyPolicyConfig is false and - // PolicyConfigMapName is not empty, the ConfigMap object with this name must - // exist in PolicyConfigMapNamespace before scheduler initialization. - PolicyConfigMapName string - // PolicyConfigMapNamespace is the namespace where the above policy config map - // is located. If none is provided default system namespace ("kube-system") - // will be used. - PolicyConfigMapNamespace string - // UseLegacyPolicyConfig tells the scheduler to ignore Policy ConfigMap and - // to use PolicyConfigFile if available. - UseLegacyPolicyConfig bool } // LeaderElectionConfiguration defines the configuration of leader election @@ -272,6 +181,9 @@ type KubeControllerManagerConfiguration struct { CloudProvider string // cloudConfigFile is the path to the cloud provider configuration file. CloudConfigFile string + // externalCloudVolumePlugin specifies the plugin to use when cloudProvider is "external". + // It is currently used by the in repo cloud providers to handle node and volume control in the KCM. + ExternalCloudVolumePlugin string // run with untagged cloud instances AllowUntaggedCloud bool // concurrentEndpointSyncs is the number of endpoint syncing operations diff --git a/pkg/apis/componentconfig/v1alpha1/BUILD b/pkg/apis/componentconfig/v1alpha1/BUILD index 437864a1f49..74755fbc998 100644 --- a/pkg/apis/componentconfig/v1alpha1/BUILD +++ b/pkg/apis/componentconfig/v1alpha1/BUILD @@ -19,10 +19,9 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1", deps = [ - "//pkg/api:go_default_library", "//pkg/apis/componentconfig:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/kubelet/apis:go_default_library", - "//pkg/kubelet/qos:go_default_library", "//pkg/master/ports:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", @@ -47,7 +46,7 @@ filegroup( go_test( name = "go_default_test", srcs = ["defaults_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1", - library = ":go_default_library", deps = ["//pkg/apis/componentconfig:go_default_library"], ) diff --git a/pkg/apis/componentconfig/v1alpha1/defaults.go b/pkg/apis/componentconfig/v1alpha1/defaults.go index f5bc328e617..0528b6cb6d5 100644 --- a/pkg/apis/componentconfig/v1alpha1/defaults.go +++ b/pkg/apis/componentconfig/v1alpha1/defaults.go @@ -17,15 +17,14 @@ limitations under the License. package v1alpha1 import ( - "fmt" - "strings" + "net" + "strconv" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kruntime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis" - "k8s.io/kubernetes/pkg/kubelet/qos" "k8s.io/kubernetes/pkg/master/ports" ) @@ -33,129 +32,65 @@ func addDefaultingFuncs(scheme *kruntime.Scheme) error { return RegisterDefaults(scheme) } -func SetDefaults_KubeProxyConfiguration(obj *KubeProxyConfiguration) { - if len(obj.BindAddress) == 0 { - obj.BindAddress = "0.0.0.0" +func SetDefaults_KubeSchedulerConfiguration(obj *KubeSchedulerConfiguration) { + if len(obj.SchedulerName) == 0 { + obj.SchedulerName = api.DefaultSchedulerName } - if obj.HealthzBindAddress == "" { - obj.HealthzBindAddress = fmt.Sprintf("0.0.0.0:%v", ports.ProxyHealthzPort) - } else if !strings.Contains(obj.HealthzBindAddress, ":") { - obj.HealthzBindAddress += fmt.Sprintf(":%v", ports.ProxyHealthzPort) + + if obj.HardPodAffinitySymmetricWeight == 0 { + obj.HardPodAffinitySymmetricWeight = api.DefaultHardPodAffinitySymmetricWeight } - if obj.MetricsBindAddress == "" { - obj.MetricsBindAddress = fmt.Sprintf("127.0.0.1:%v", ports.ProxyStatusPort) - } else if !strings.Contains(obj.MetricsBindAddress, ":") { - obj.MetricsBindAddress += fmt.Sprintf(":%v", ports.ProxyStatusPort) + + if obj.AlgorithmSource.Policy == nil && + (obj.AlgorithmSource.Provider == nil || len(*obj.AlgorithmSource.Provider) == 0) { + val := SchedulerDefaultProviderName + obj.AlgorithmSource.Provider = &val } - if obj.OOMScoreAdj == nil { - temp := int32(qos.KubeProxyOOMScoreAdj) - obj.OOMScoreAdj = &temp - } - if obj.ResourceContainer == "" { - obj.ResourceContainer = "/kube-proxy" - } - if obj.IPTables.SyncPeriod.Duration == 0 { - obj.IPTables.SyncPeriod = metav1.Duration{Duration: 30 * time.Second} - } - if obj.IPVS.SyncPeriod.Duration == 0 { - obj.IPVS.SyncPeriod = metav1.Duration{Duration: 30 * time.Second} - } - zero := metav1.Duration{} - if obj.UDPIdleTimeout == zero { - obj.UDPIdleTimeout = metav1.Duration{Duration: 250 * time.Millisecond} - } - // If ConntrackMax is set, respect it. - if obj.Conntrack.Max == 0 { - // If ConntrackMax is *not* set, use per-core scaling. - if obj.Conntrack.MaxPerCore == 0 { - obj.Conntrack.MaxPerCore = 32 * 1024 - } - if obj.Conntrack.Min == 0 { - obj.Conntrack.Min = 128 * 1024 + + if policy := obj.AlgorithmSource.Policy; policy != nil { + if policy.ConfigMap != nil && len(policy.ConfigMap.Namespace) == 0 { + obj.AlgorithmSource.Policy.ConfigMap.Namespace = api.NamespaceSystem } } - if obj.IPTables.MasqueradeBit == nil { - temp := int32(14) - obj.IPTables.MasqueradeBit = &temp + + if host, port, err := net.SplitHostPort(obj.HealthzBindAddress); err == nil { + if len(host) == 0 { + host = "0.0.0.0" + } + obj.HealthzBindAddress = net.JoinHostPort(host, port) + } else { + obj.HealthzBindAddress = net.JoinHostPort("0.0.0.0", strconv.Itoa(ports.SchedulerPort)) } - if obj.Conntrack.TCPEstablishedTimeout == zero { - obj.Conntrack.TCPEstablishedTimeout = metav1.Duration{Duration: 24 * time.Hour} // 1 day (1/5 default) - } - if obj.Conntrack.TCPCloseWaitTimeout == zero { - // See https://github.com/kubernetes/kubernetes/issues/32551. - // - // CLOSE_WAIT conntrack state occurs when the Linux kernel - // sees a FIN from the remote server. Note: this is a half-close - // condition that persists as long as the local side keeps the - // socket open. The condition is rare as it is typical in most - // protocols for both sides to issue a close; this typically - // occurs when the local socket is lazily garbage collected. - // - // If the CLOSE_WAIT conntrack entry expires, then FINs from the - // local socket will not be properly SNAT'd and will not reach the - // remote server (if the connection was subject to SNAT). If the - // remote timeouts for FIN_WAIT* states exceed the CLOSE_WAIT - // timeout, then there will be an inconsistency in the state of - // the connection and a new connection reusing the SNAT (src, - // port) pair may be rejected by the remote side with RST. This - // can cause new calls to connect(2) to return with ECONNREFUSED. - // - // We set CLOSE_WAIT to one hour by default to better match - // typical server timeouts. - obj.Conntrack.TCPCloseWaitTimeout = metav1.Duration{Duration: 1 * time.Hour} - } - if obj.ConfigSyncPeriod.Duration == 0 { - obj.ConfigSyncPeriod.Duration = 15 * time.Minute + + if host, port, err := net.SplitHostPort(obj.MetricsBindAddress); err == nil { + if len(host) == 0 { + host = "0.0.0.0" + } + obj.MetricsBindAddress = net.JoinHostPort(host, port) + } else { + obj.MetricsBindAddress = net.JoinHostPort("0.0.0.0", strconv.Itoa(ports.SchedulerPort)) } if len(obj.ClientConnection.ContentType) == 0 { obj.ClientConnection.ContentType = "application/vnd.kubernetes.protobuf" } if obj.ClientConnection.QPS == 0.0 { - obj.ClientConnection.QPS = 5.0 + obj.ClientConnection.QPS = 50.0 } if obj.ClientConnection.Burst == 0 { - obj.ClientConnection.Burst = 10 + obj.ClientConnection.Burst = 100 } -} -func SetDefaults_KubeSchedulerConfiguration(obj *KubeSchedulerConfiguration) { - if obj.Port == 0 { - obj.Port = ports.SchedulerPort + if len(obj.LeaderElection.LockObjectNamespace) == 0 { + obj.LeaderElection.LockObjectNamespace = SchedulerDefaultLockObjectNamespace } - if obj.Address == "" { - obj.Address = "0.0.0.0" + if len(obj.LeaderElection.LockObjectName) == 0 { + obj.LeaderElection.LockObjectName = SchedulerDefaultLockObjectName } - if obj.AlgorithmProvider == "" { - obj.AlgorithmProvider = "DefaultProvider" - } - if obj.ContentType == "" { - obj.ContentType = "application/vnd.kubernetes.protobuf" - } - if obj.KubeAPIQPS == 0 { - obj.KubeAPIQPS = 50.0 - } - if obj.KubeAPIBurst == 0 { - obj.KubeAPIBurst = 100 - } - if obj.SchedulerName == "" { - obj.SchedulerName = api.DefaultSchedulerName - } - if obj.HardPodAffinitySymmetricWeight == 0 { - obj.HardPodAffinitySymmetricWeight = api.DefaultHardPodAffinitySymmetricWeight - } - if obj.FailureDomains == "" { + + if len(obj.FailureDomains) == 0 { obj.FailureDomains = kubeletapis.DefaultFailureDomains } - if obj.LockObjectNamespace == "" { - obj.LockObjectNamespace = SchedulerDefaultLockObjectNamespace - } - if obj.LockObjectName == "" { - obj.LockObjectName = SchedulerDefaultLockObjectName - } - if obj.PolicyConfigMapNamespace == "" { - obj.PolicyConfigMapNamespace = api.NamespaceSystem - } } func SetDefaults_LeaderElectionConfiguration(obj *LeaderElectionConfiguration) { @@ -174,7 +109,3 @@ func SetDefaults_LeaderElectionConfiguration(obj *LeaderElectionConfiguration) { obj.ResourceLock = "endpoints" } } - -func boolVar(b bool) *bool { - return &b -} diff --git a/pkg/apis/componentconfig/v1alpha1/doc.go b/pkg/apis/componentconfig/v1alpha1/doc.go index f8d91738be8..e3d63571e58 100644 --- a/pkg/apis/componentconfig/v1alpha1/doc.go +++ b/pkg/apis/componentconfig/v1alpha1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/componentconfig // +k8s:openapi-gen=true // +k8s:defaulter-gen=TypeMeta diff --git a/pkg/apis/componentconfig/v1alpha1/register.go b/pkg/apis/componentconfig/v1alpha1/register.go index a75096f20fb..693ff3a3511 100644 --- a/pkg/apis/componentconfig/v1alpha1/register.go +++ b/pkg/apis/componentconfig/v1alpha1/register.go @@ -44,7 +44,6 @@ func init() { func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, - &KubeProxyConfiguration{}, &KubeSchedulerConfiguration{}, ) return nil diff --git a/pkg/apis/componentconfig/v1alpha1/types.go b/pkg/apis/componentconfig/v1alpha1/types.go index 70654ca8c9e..55e99fb4290 100644 --- a/pkg/apis/componentconfig/v1alpha1/types.go +++ b/pkg/apis/componentconfig/v1alpha1/types.go @@ -33,204 +33,81 @@ type ClientConnectionConfiguration struct { // cps controls the number of queries per second allowed for this connection. QPS float32 `json:"qps"` // burst allows extra queries to accumulate when a client is exceeding its rate. - Burst int `json:"burst"` + Burst int32 `json:"burst"` } -// KubeProxyIPTablesConfiguration contains iptables-related configuration -// details for the Kubernetes proxy server. -type KubeProxyIPTablesConfiguration struct { - // masqueradeBit is the bit of the iptables fwmark space to use for SNAT if using - // the pure iptables proxy mode. Values must be within the range [0, 31]. - MasqueradeBit *int32 `json:"masqueradeBit"` - // masqueradeAll tells kube-proxy to SNAT everything if using the pure iptables proxy mode. - MasqueradeAll bool `json:"masqueradeAll"` - // syncPeriod is the period that iptables rules are refreshed (e.g. '5s', '1m', - // '2h22m'). Must be greater than 0. - SyncPeriod metav1.Duration `json:"syncPeriod"` - // minSyncPeriod is the minimum period that iptables rules are refreshed (e.g. '5s', '1m', - // '2h22m'). - MinSyncPeriod metav1.Duration `json:"minSyncPeriod"` +// SchedulerPolicySource configures a means to obtain a scheduler Policy. One +// source field must be specified, and source fields are mutually exclusive. +type SchedulerPolicySource struct { + // File is a file policy source. + File *SchedulerPolicyFileSource `json:"file,omitempty"` + // ConfigMap is a config map policy source. + ConfigMap *SchedulerPolicyConfigMapSource `json:"configMap,omitempty"` } -// KubeProxyIPVSConfiguration contains ipvs-related configuration -// details for the Kubernetes proxy server. -type KubeProxyIPVSConfiguration struct { - // syncPeriod is the period that ipvs rules are refreshed (e.g. '5s', '1m', - // '2h22m'). Must be greater than 0. - SyncPeriod metav1.Duration `json:"syncPeriod"` - // minSyncPeriod is the minimum period that ipvs rules are refreshed (e.g. '5s', '1m', - // '2h22m'). - MinSyncPeriod metav1.Duration `json:"minSyncPeriod"` - // ipvs scheduler - Scheduler string `json:"scheduler"` +// SchedulerPolicyFileSource is a policy serialized to disk and accessed via +// path. +type SchedulerPolicyFileSource struct { + // Path is the location of a serialized policy. + Path string `json:"path"` } -// KubeProxyConntrackConfiguration contains conntrack settings for -// the Kubernetes proxy server. -type KubeProxyConntrackConfiguration struct { - // max is the maximum number of NAT connections to track (0 to - // leave as-is). This takes precedence over conntrackMaxPerCore and conntrackMin. - Max int32 `json:"max"` - // maxPerCore is the maximum number of NAT connections to track - // per CPU core (0 to leave the limit as-is and ignore conntrackMin). - MaxPerCore int32 `json:"maxPerCore"` - // min is the minimum value of connect-tracking records to allocate, - // regardless of conntrackMaxPerCore (set conntrackMaxPerCore=0 to leave the limit as-is). - Min int32 `json:"min"` - // tcpEstablishedTimeout is how long an idle TCP connection will be kept open - // (e.g. '2s'). Must be greater than 0. - TCPEstablishedTimeout metav1.Duration `json:"tcpEstablishedTimeout"` - // tcpCloseWaitTimeout is how long an idle conntrack entry - // in CLOSE_WAIT state will remain in the conntrack - // table. (e.g. '60s'). Must be greater than 0 to set. - TCPCloseWaitTimeout metav1.Duration `json:"tcpCloseWaitTimeout"` +// SchedulerPolicyConfigMapSource is a policy serialized into a config map value +// under the SchedulerPolicyConfigMapKey key. +type SchedulerPolicyConfigMapSource struct { + // Namespace is the namespace of the policy config map. + Namespace string `json:"namespace"` + // Name is the name of hte policy config map. + Name string `json:"name"` } -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// KubeProxyConfiguration contains everything necessary to configure the -// Kubernetes proxy server. -type KubeProxyConfiguration struct { - metav1.TypeMeta `json:",inline"` - - // featureGates is a comma-separated list of key=value pairs that control - // which alpha/beta features are enabled. - // - // TODO this really should be a map but that requires refactoring all - // components to use config files because local-up-cluster.sh only supports - // the --feature-gates flag right now, which is comma-separated key=value - // pairs. - FeatureGates string `json:"featureGates"` - - // bindAddress is the IP address for the proxy server to serve on (set to 0.0.0.0 - // for all interfaces) - BindAddress string `json:"bindAddress"` - // healthzBindAddress is the IP address and port for the health check server to serve on, - // defaulting to 0.0.0.0:10256 - HealthzBindAddress string `json:"healthzBindAddress"` - // metricsBindAddress is the IP address and port for the metrics server to serve on, - // defaulting to 127.0.0.1:10249 (set to 0.0.0.0 for all interfaces) - MetricsBindAddress string `json:"metricsBindAddress"` - // enableProfiling enables profiling via web interface on /debug/pprof handler. - // Profiling handlers will be handled by metrics server. - EnableProfiling bool `json:"enableProfiling"` - // clusterCIDR is the CIDR range of the pods in the cluster. It is used to - // bridge traffic coming from outside of the cluster. If not provided, - // no off-cluster bridging will be performed. - ClusterCIDR string `json:"clusterCIDR"` - // hostnameOverride, if non-empty, will be used as the identity instead of the actual hostname. - HostnameOverride string `json:"hostnameOverride"` - // clientConnection specifies the kubeconfig file and client connection settings for the proxy - // server to use when communicating with the apiserver. - ClientConnection ClientConnectionConfiguration `json:"clientConnection"` - // iptables contains iptables-related configuration options. - IPTables KubeProxyIPTablesConfiguration `json:"iptables"` - // ipvs contains ipvs-related configuration options. - IPVS KubeProxyIPVSConfiguration `json:"ipvs"` - // oomScoreAdj is the oom-score-adj value for kube-proxy process. Values must be within - // the range [-1000, 1000] - OOMScoreAdj *int32 `json:"oomScoreAdj"` - // mode specifies which proxy mode to use. - Mode ProxyMode `json:"mode"` - // portRange is the range of host ports (beginPort-endPort, inclusive) that may be consumed - // in order to proxy service traffic. If unspecified (0-0) then ports will be randomly chosen. - PortRange string `json:"portRange"` - // resourceContainer is the bsolute name of the resource-only container to create and run - // the Kube-proxy in (Default: /kube-proxy). - ResourceContainer string `json:"resourceContainer"` - // udpIdleTimeout is how long an idle UDP connection will be kept open (e.g. '250ms', '2s'). - // Must be greater than 0. Only applicable for proxyMode=userspace. - UDPIdleTimeout metav1.Duration `json:"udpTimeoutMilliseconds"` - // conntrack contains conntrack-related configuration options. - Conntrack KubeProxyConntrackConfiguration `json:"conntrack"` - // configSyncPeriod is how often configuration from the apiserver is refreshed. Must be greater - // than 0. - ConfigSyncPeriod metav1.Duration `json:"configSyncPeriod"` +// SchedulerAlgorithmSource is the source of a scheduler algorithm. One source +// field must be specified, and source fields are mutually exclusive. +type SchedulerAlgorithmSource struct { + // Policy is a policy based algorithm source. + Policy *SchedulerPolicySource `json:"policy,omitempty"` + // Provider is the name of a scheduling algorithm provider to use. + Provider *string `json:"provider,omitempty"` } -// Currently two modes of proxying are available: 'userspace' (older, stable) or 'iptables' -// (newer, faster). If blank, use the best-available proxy (currently iptables, but may -// change in future versions). If the iptables proxy is selected, regardless of how, but -// the system's kernel or iptables versions are insufficient, this always falls back to the -// userspace proxy. -type ProxyMode string - -const ( - ProxyModeUserspace ProxyMode = "userspace" - ProxyModeIPTables ProxyMode = "iptables" -) - // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type KubeSchedulerConfiguration struct { metav1.TypeMeta `json:",inline"` - // port is the port that the scheduler's http service runs on. - Port int `json:"port"` - // address is the IP address to serve on. - Address string `json:"address"` - // algorithmProvider is the scheduling algorithm provider to use. - AlgorithmProvider string `json:"algorithmProvider"` - // policyConfigFile is the filepath to the scheduler policy configuration. - PolicyConfigFile string `json:"policyConfigFile"` - // enableProfiling enables profiling via web interface. - EnableProfiling *bool `json:"enableProfiling"` - // enableContentionProfiling enables lock contention profiling, if enableProfiling is true. - EnableContentionProfiling bool `json:"enableContentionProfiling"` - // contentType is contentType of requests sent to apiserver. - ContentType string `json:"contentType"` - // kubeAPIQPS is the QPS to use while talking with kubernetes apiserver. - KubeAPIQPS float32 `json:"kubeAPIQPS"` - // kubeAPIBurst is the QPS burst to use while talking with kubernetes apiserver. - KubeAPIBurst int `json:"kubeAPIBurst"` - // schedulerName is name of the scheduler, used to select which pods + // SchedulerName is name of the scheduler, used to select which pods // will be processed by this scheduler, based on pod's "spec.SchedulerName". SchedulerName string `json:"schedulerName"` + // AlgorithmSource specifies the scheduler algorithm source. + AlgorithmSource SchedulerAlgorithmSource `json:"algorithmSource"` // RequiredDuringScheduling affinity is not symmetric, but there is an implicit PreferredDuringScheduling affinity rule // corresponding to every RequiredDuringScheduling affinity rule. // HardPodAffinitySymmetricWeight represents the weight of implicit PreferredDuringScheduling affinity rule, in the range 0-100. - HardPodAffinitySymmetricWeight int `json:"hardPodAffinitySymmetricWeight"` + HardPodAffinitySymmetricWeight int32 `json:"hardPodAffinitySymmetricWeight"` + + // LeaderElection defines the configuration of leader election client. + LeaderElection KubeSchedulerLeaderElectionConfiguration `json:"leaderElection"` + + // ClientConnection specifies the kubeconfig file and client connection + // settings for the proxy server to use when communicating with the apiserver. + ClientConnection ClientConnectionConfiguration `json:"clientConnection"` + // HealthzBindAddress is the IP address and port for the health check server to serve on, + // defaulting to 0.0.0.0:10251 + HealthzBindAddress string `json:"healthzBindAddress"` + // MetricsBindAddress is the IP address and port for the metrics server to + // serve on, defaulting to 0.0.0.0:10251. + MetricsBindAddress string `json:"metricsBindAddress"` + // EnableProfiling enables profiling via web interface on /debug/pprof + // handler. Profiling handlers will be handled by metrics server. + EnableProfiling bool `json:"enableProfiling"` + // EnableContentionProfiling enables lock contention profiling, if + // EnableProfiling is true. + EnableContentionProfiling bool `json:"enableContentionProfiling"` + // Indicate the "all topologies" set for empty topologyKey when it's used for PreferredDuringScheduling pod anti-affinity. FailureDomains string `json:"failureDomains"` - // leaderElection defines the configuration of leader election client. - LeaderElection LeaderElectionConfiguration `json:"leaderElection"` - // LockObjectNamespace defines the namespace of the lock object - LockObjectNamespace string `json:"lockObjectNamespace"` - // LockObjectName defines the lock object name - LockObjectName string `json:"lockObjectName"` - // PolicyConfigMapName is the name of the ConfigMap object that specifies - // the scheduler's policy config. If UseLegacyPolicyConfig is true, scheduler - // uses PolicyConfigFile. If UseLegacyPolicyConfig is false and - // PolicyConfigMapName is not empty, the ConfigMap object with this name must - // exist in PolicyConfigMapNamespace before scheduler initialization. - PolicyConfigMapName string `json:"policyConfigMapName"` - // PolicyConfigMapNamespace is the namespace where the above policy config map - // is located. If none is provided default system namespace ("kube-system") - // will be used. - PolicyConfigMapNamespace string `json:"policyConfigMapNamespace"` - // UseLegacyPolicyConfig tells the scheduler to ignore Policy ConfigMap and - // to use PolicyConfigFile if available. - UseLegacyPolicyConfig bool `json:"useLegacyPolicyConfig"` } -// HairpinMode denotes how the kubelet should configure networking to handle -// hairpin packets. -type HairpinMode string - -// Enum settings for different ways to handle hairpin packets. -const ( - // Set the hairpin flag on the veth of containers in the respective - // container runtime. - HairpinVeth = "hairpin-veth" - // Make the container bridge promiscuous. This will force it to accept - // hairpin packets, even if the flag isn't set on ports of the bridge. - PromiscuousBridge = "promiscuous-bridge" - // Neither of the above. If the kubelet is started in this hairpin mode - // and kube-proxy is running in iptables mode, hairpin packets will be - // dropped by the container bridge. - HairpinNone = "none" -) - // LeaderElectionConfiguration defines the configuration of leader election // clients for components that can run with leader election enabled. type LeaderElectionConfiguration struct { @@ -259,10 +136,22 @@ type LeaderElectionConfiguration struct { ResourceLock string `json:"resourceLock"` } +// KubeSchedulerLeaderElectionConfiguration expands LeaderElectionConfiguration +// to include scheduler specific configuration. +type KubeSchedulerLeaderElectionConfiguration struct { + LeaderElectionConfiguration `json:",inline"` + // LockObjectNamespace defines the namespace of the lock object + LockObjectNamespace string `json:"lockObjectNamespace"` + // LockObjectName defines the lock object name + LockObjectName string `json:"lockObjectName"` +} + const ( // "kube-system" is the default scheduler lock object namespace SchedulerDefaultLockObjectNamespace string = "kube-system" // "kube-scheduler" is the default scheduler lock object name SchedulerDefaultLockObjectName = "kube-scheduler" + + SchedulerDefaultProviderName = "DefaultProvider" ) diff --git a/pkg/apis/componentconfig/v1alpha1/zz_generated.conversion.go b/pkg/apis/componentconfig/v1alpha1/zz_generated.conversion.go index 70290c39abd..96555ba9eeb 100644 --- a/pkg/apis/componentconfig/v1alpha1/zz_generated.conversion.go +++ b/pkg/apis/componentconfig/v1alpha1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,11 +21,12 @@ limitations under the License. package v1alpha1 import ( + unsafe "unsafe" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" componentconfig "k8s.io/kubernetes/pkg/apis/componentconfig" - unsafe "unsafe" ) func init() { @@ -38,18 +39,20 @@ func RegisterConversions(scheme *runtime.Scheme) error { return scheme.AddGeneratedConversionFuncs( Convert_v1alpha1_ClientConnectionConfiguration_To_componentconfig_ClientConnectionConfiguration, Convert_componentconfig_ClientConnectionConfiguration_To_v1alpha1_ClientConnectionConfiguration, - Convert_v1alpha1_KubeProxyConfiguration_To_componentconfig_KubeProxyConfiguration, - Convert_componentconfig_KubeProxyConfiguration_To_v1alpha1_KubeProxyConfiguration, - Convert_v1alpha1_KubeProxyConntrackConfiguration_To_componentconfig_KubeProxyConntrackConfiguration, - Convert_componentconfig_KubeProxyConntrackConfiguration_To_v1alpha1_KubeProxyConntrackConfiguration, - Convert_v1alpha1_KubeProxyIPTablesConfiguration_To_componentconfig_KubeProxyIPTablesConfiguration, - Convert_componentconfig_KubeProxyIPTablesConfiguration_To_v1alpha1_KubeProxyIPTablesConfiguration, - Convert_v1alpha1_KubeProxyIPVSConfiguration_To_componentconfig_KubeProxyIPVSConfiguration, - Convert_componentconfig_KubeProxyIPVSConfiguration_To_v1alpha1_KubeProxyIPVSConfiguration, Convert_v1alpha1_KubeSchedulerConfiguration_To_componentconfig_KubeSchedulerConfiguration, Convert_componentconfig_KubeSchedulerConfiguration_To_v1alpha1_KubeSchedulerConfiguration, + Convert_v1alpha1_KubeSchedulerLeaderElectionConfiguration_To_componentconfig_KubeSchedulerLeaderElectionConfiguration, + Convert_componentconfig_KubeSchedulerLeaderElectionConfiguration_To_v1alpha1_KubeSchedulerLeaderElectionConfiguration, Convert_v1alpha1_LeaderElectionConfiguration_To_componentconfig_LeaderElectionConfiguration, Convert_componentconfig_LeaderElectionConfiguration_To_v1alpha1_LeaderElectionConfiguration, + Convert_v1alpha1_SchedulerAlgorithmSource_To_componentconfig_SchedulerAlgorithmSource, + Convert_componentconfig_SchedulerAlgorithmSource_To_v1alpha1_SchedulerAlgorithmSource, + Convert_v1alpha1_SchedulerPolicyConfigMapSource_To_componentconfig_SchedulerPolicyConfigMapSource, + Convert_componentconfig_SchedulerPolicyConfigMapSource_To_v1alpha1_SchedulerPolicyConfigMapSource, + Convert_v1alpha1_SchedulerPolicyFileSource_To_componentconfig_SchedulerPolicyFileSource, + Convert_componentconfig_SchedulerPolicyFileSource_To_v1alpha1_SchedulerPolicyFileSource, + Convert_v1alpha1_SchedulerPolicySource_To_componentconfig_SchedulerPolicySource, + Convert_componentconfig_SchedulerPolicySource_To_v1alpha1_SchedulerPolicySource, ) } @@ -81,175 +84,23 @@ func Convert_componentconfig_ClientConnectionConfiguration_To_v1alpha1_ClientCon return autoConvert_componentconfig_ClientConnectionConfiguration_To_v1alpha1_ClientConnectionConfiguration(in, out, s) } -func autoConvert_v1alpha1_KubeProxyConfiguration_To_componentconfig_KubeProxyConfiguration(in *KubeProxyConfiguration, out *componentconfig.KubeProxyConfiguration, s conversion.Scope) error { - out.FeatureGates = in.FeatureGates - out.BindAddress = in.BindAddress - out.HealthzBindAddress = in.HealthzBindAddress - out.MetricsBindAddress = in.MetricsBindAddress - out.EnableProfiling = in.EnableProfiling - out.ClusterCIDR = in.ClusterCIDR - out.HostnameOverride = in.HostnameOverride +func autoConvert_v1alpha1_KubeSchedulerConfiguration_To_componentconfig_KubeSchedulerConfiguration(in *KubeSchedulerConfiguration, out *componentconfig.KubeSchedulerConfiguration, s conversion.Scope) error { + out.SchedulerName = in.SchedulerName + if err := Convert_v1alpha1_SchedulerAlgorithmSource_To_componentconfig_SchedulerAlgorithmSource(&in.AlgorithmSource, &out.AlgorithmSource, s); err != nil { + return err + } + out.HardPodAffinitySymmetricWeight = in.HardPodAffinitySymmetricWeight + if err := Convert_v1alpha1_KubeSchedulerLeaderElectionConfiguration_To_componentconfig_KubeSchedulerLeaderElectionConfiguration(&in.LeaderElection, &out.LeaderElection, s); err != nil { + return err + } if err := Convert_v1alpha1_ClientConnectionConfiguration_To_componentconfig_ClientConnectionConfiguration(&in.ClientConnection, &out.ClientConnection, s); err != nil { return err } - if err := Convert_v1alpha1_KubeProxyIPTablesConfiguration_To_componentconfig_KubeProxyIPTablesConfiguration(&in.IPTables, &out.IPTables, s); err != nil { - return err - } - if err := Convert_v1alpha1_KubeProxyIPVSConfiguration_To_componentconfig_KubeProxyIPVSConfiguration(&in.IPVS, &out.IPVS, s); err != nil { - return err - } - out.OOMScoreAdj = (*int32)(unsafe.Pointer(in.OOMScoreAdj)) - out.Mode = componentconfig.ProxyMode(in.Mode) - out.PortRange = in.PortRange - out.ResourceContainer = in.ResourceContainer - out.UDPIdleTimeout = in.UDPIdleTimeout - if err := Convert_v1alpha1_KubeProxyConntrackConfiguration_To_componentconfig_KubeProxyConntrackConfiguration(&in.Conntrack, &out.Conntrack, s); err != nil { - return err - } - out.ConfigSyncPeriod = in.ConfigSyncPeriod - return nil -} - -// Convert_v1alpha1_KubeProxyConfiguration_To_componentconfig_KubeProxyConfiguration is an autogenerated conversion function. -func Convert_v1alpha1_KubeProxyConfiguration_To_componentconfig_KubeProxyConfiguration(in *KubeProxyConfiguration, out *componentconfig.KubeProxyConfiguration, s conversion.Scope) error { - return autoConvert_v1alpha1_KubeProxyConfiguration_To_componentconfig_KubeProxyConfiguration(in, out, s) -} - -func autoConvert_componentconfig_KubeProxyConfiguration_To_v1alpha1_KubeProxyConfiguration(in *componentconfig.KubeProxyConfiguration, out *KubeProxyConfiguration, s conversion.Scope) error { - out.FeatureGates = in.FeatureGates - out.BindAddress = in.BindAddress out.HealthzBindAddress = in.HealthzBindAddress out.MetricsBindAddress = in.MetricsBindAddress out.EnableProfiling = in.EnableProfiling - out.ClusterCIDR = in.ClusterCIDR - out.HostnameOverride = in.HostnameOverride - if err := Convert_componentconfig_ClientConnectionConfiguration_To_v1alpha1_ClientConnectionConfiguration(&in.ClientConnection, &out.ClientConnection, s); err != nil { - return err - } - if err := Convert_componentconfig_KubeProxyIPTablesConfiguration_To_v1alpha1_KubeProxyIPTablesConfiguration(&in.IPTables, &out.IPTables, s); err != nil { - return err - } - if err := Convert_componentconfig_KubeProxyIPVSConfiguration_To_v1alpha1_KubeProxyIPVSConfiguration(&in.IPVS, &out.IPVS, s); err != nil { - return err - } - out.OOMScoreAdj = (*int32)(unsafe.Pointer(in.OOMScoreAdj)) - out.Mode = ProxyMode(in.Mode) - out.PortRange = in.PortRange - out.ResourceContainer = in.ResourceContainer - out.UDPIdleTimeout = in.UDPIdleTimeout - if err := Convert_componentconfig_KubeProxyConntrackConfiguration_To_v1alpha1_KubeProxyConntrackConfiguration(&in.Conntrack, &out.Conntrack, s); err != nil { - return err - } - out.ConfigSyncPeriod = in.ConfigSyncPeriod - return nil -} - -// Convert_componentconfig_KubeProxyConfiguration_To_v1alpha1_KubeProxyConfiguration is an autogenerated conversion function. -func Convert_componentconfig_KubeProxyConfiguration_To_v1alpha1_KubeProxyConfiguration(in *componentconfig.KubeProxyConfiguration, out *KubeProxyConfiguration, s conversion.Scope) error { - return autoConvert_componentconfig_KubeProxyConfiguration_To_v1alpha1_KubeProxyConfiguration(in, out, s) -} - -func autoConvert_v1alpha1_KubeProxyConntrackConfiguration_To_componentconfig_KubeProxyConntrackConfiguration(in *KubeProxyConntrackConfiguration, out *componentconfig.KubeProxyConntrackConfiguration, s conversion.Scope) error { - out.Max = in.Max - out.MaxPerCore = in.MaxPerCore - out.Min = in.Min - out.TCPEstablishedTimeout = in.TCPEstablishedTimeout - out.TCPCloseWaitTimeout = in.TCPCloseWaitTimeout - return nil -} - -// Convert_v1alpha1_KubeProxyConntrackConfiguration_To_componentconfig_KubeProxyConntrackConfiguration is an autogenerated conversion function. -func Convert_v1alpha1_KubeProxyConntrackConfiguration_To_componentconfig_KubeProxyConntrackConfiguration(in *KubeProxyConntrackConfiguration, out *componentconfig.KubeProxyConntrackConfiguration, s conversion.Scope) error { - return autoConvert_v1alpha1_KubeProxyConntrackConfiguration_To_componentconfig_KubeProxyConntrackConfiguration(in, out, s) -} - -func autoConvert_componentconfig_KubeProxyConntrackConfiguration_To_v1alpha1_KubeProxyConntrackConfiguration(in *componentconfig.KubeProxyConntrackConfiguration, out *KubeProxyConntrackConfiguration, s conversion.Scope) error { - out.Max = in.Max - out.MaxPerCore = in.MaxPerCore - out.Min = in.Min - out.TCPEstablishedTimeout = in.TCPEstablishedTimeout - out.TCPCloseWaitTimeout = in.TCPCloseWaitTimeout - return nil -} - -// Convert_componentconfig_KubeProxyConntrackConfiguration_To_v1alpha1_KubeProxyConntrackConfiguration is an autogenerated conversion function. -func Convert_componentconfig_KubeProxyConntrackConfiguration_To_v1alpha1_KubeProxyConntrackConfiguration(in *componentconfig.KubeProxyConntrackConfiguration, out *KubeProxyConntrackConfiguration, s conversion.Scope) error { - return autoConvert_componentconfig_KubeProxyConntrackConfiguration_To_v1alpha1_KubeProxyConntrackConfiguration(in, out, s) -} - -func autoConvert_v1alpha1_KubeProxyIPTablesConfiguration_To_componentconfig_KubeProxyIPTablesConfiguration(in *KubeProxyIPTablesConfiguration, out *componentconfig.KubeProxyIPTablesConfiguration, s conversion.Scope) error { - out.MasqueradeBit = (*int32)(unsafe.Pointer(in.MasqueradeBit)) - out.MasqueradeAll = in.MasqueradeAll - out.SyncPeriod = in.SyncPeriod - out.MinSyncPeriod = in.MinSyncPeriod - return nil -} - -// Convert_v1alpha1_KubeProxyIPTablesConfiguration_To_componentconfig_KubeProxyIPTablesConfiguration is an autogenerated conversion function. -func Convert_v1alpha1_KubeProxyIPTablesConfiguration_To_componentconfig_KubeProxyIPTablesConfiguration(in *KubeProxyIPTablesConfiguration, out *componentconfig.KubeProxyIPTablesConfiguration, s conversion.Scope) error { - return autoConvert_v1alpha1_KubeProxyIPTablesConfiguration_To_componentconfig_KubeProxyIPTablesConfiguration(in, out, s) -} - -func autoConvert_componentconfig_KubeProxyIPTablesConfiguration_To_v1alpha1_KubeProxyIPTablesConfiguration(in *componentconfig.KubeProxyIPTablesConfiguration, out *KubeProxyIPTablesConfiguration, s conversion.Scope) error { - out.MasqueradeBit = (*int32)(unsafe.Pointer(in.MasqueradeBit)) - out.MasqueradeAll = in.MasqueradeAll - out.SyncPeriod = in.SyncPeriod - out.MinSyncPeriod = in.MinSyncPeriod - return nil -} - -// Convert_componentconfig_KubeProxyIPTablesConfiguration_To_v1alpha1_KubeProxyIPTablesConfiguration is an autogenerated conversion function. -func Convert_componentconfig_KubeProxyIPTablesConfiguration_To_v1alpha1_KubeProxyIPTablesConfiguration(in *componentconfig.KubeProxyIPTablesConfiguration, out *KubeProxyIPTablesConfiguration, s conversion.Scope) error { - return autoConvert_componentconfig_KubeProxyIPTablesConfiguration_To_v1alpha1_KubeProxyIPTablesConfiguration(in, out, s) -} - -func autoConvert_v1alpha1_KubeProxyIPVSConfiguration_To_componentconfig_KubeProxyIPVSConfiguration(in *KubeProxyIPVSConfiguration, out *componentconfig.KubeProxyIPVSConfiguration, s conversion.Scope) error { - out.SyncPeriod = in.SyncPeriod - out.MinSyncPeriod = in.MinSyncPeriod - out.Scheduler = in.Scheduler - return nil -} - -// Convert_v1alpha1_KubeProxyIPVSConfiguration_To_componentconfig_KubeProxyIPVSConfiguration is an autogenerated conversion function. -func Convert_v1alpha1_KubeProxyIPVSConfiguration_To_componentconfig_KubeProxyIPVSConfiguration(in *KubeProxyIPVSConfiguration, out *componentconfig.KubeProxyIPVSConfiguration, s conversion.Scope) error { - return autoConvert_v1alpha1_KubeProxyIPVSConfiguration_To_componentconfig_KubeProxyIPVSConfiguration(in, out, s) -} - -func autoConvert_componentconfig_KubeProxyIPVSConfiguration_To_v1alpha1_KubeProxyIPVSConfiguration(in *componentconfig.KubeProxyIPVSConfiguration, out *KubeProxyIPVSConfiguration, s conversion.Scope) error { - out.SyncPeriod = in.SyncPeriod - out.MinSyncPeriod = in.MinSyncPeriod - out.Scheduler = in.Scheduler - return nil -} - -// Convert_componentconfig_KubeProxyIPVSConfiguration_To_v1alpha1_KubeProxyIPVSConfiguration is an autogenerated conversion function. -func Convert_componentconfig_KubeProxyIPVSConfiguration_To_v1alpha1_KubeProxyIPVSConfiguration(in *componentconfig.KubeProxyIPVSConfiguration, out *KubeProxyIPVSConfiguration, s conversion.Scope) error { - return autoConvert_componentconfig_KubeProxyIPVSConfiguration_To_v1alpha1_KubeProxyIPVSConfiguration(in, out, s) -} - -func autoConvert_v1alpha1_KubeSchedulerConfiguration_To_componentconfig_KubeSchedulerConfiguration(in *KubeSchedulerConfiguration, out *componentconfig.KubeSchedulerConfiguration, s conversion.Scope) error { - out.Port = int32(in.Port) - out.Address = in.Address - out.AlgorithmProvider = in.AlgorithmProvider - out.PolicyConfigFile = in.PolicyConfigFile - if err := v1.Convert_Pointer_bool_To_bool(&in.EnableProfiling, &out.EnableProfiling, s); err != nil { - return err - } out.EnableContentionProfiling = in.EnableContentionProfiling - out.ContentType = in.ContentType - out.KubeAPIQPS = in.KubeAPIQPS - out.KubeAPIBurst = int32(in.KubeAPIBurst) - out.SchedulerName = in.SchedulerName - out.HardPodAffinitySymmetricWeight = in.HardPodAffinitySymmetricWeight out.FailureDomains = in.FailureDomains - if err := Convert_v1alpha1_LeaderElectionConfiguration_To_componentconfig_LeaderElectionConfiguration(&in.LeaderElection, &out.LeaderElection, s); err != nil { - return err - } - out.LockObjectNamespace = in.LockObjectNamespace - out.LockObjectName = in.LockObjectName - out.PolicyConfigMapName = in.PolicyConfigMapName - out.PolicyConfigMapNamespace = in.PolicyConfigMapNamespace - out.UseLegacyPolicyConfig = in.UseLegacyPolicyConfig return nil } @@ -259,28 +110,22 @@ func Convert_v1alpha1_KubeSchedulerConfiguration_To_componentconfig_KubeSchedule } func autoConvert_componentconfig_KubeSchedulerConfiguration_To_v1alpha1_KubeSchedulerConfiguration(in *componentconfig.KubeSchedulerConfiguration, out *KubeSchedulerConfiguration, s conversion.Scope) error { - out.Port = int(in.Port) - out.Address = in.Address - out.AlgorithmProvider = in.AlgorithmProvider - out.PolicyConfigFile = in.PolicyConfigFile - if err := v1.Convert_bool_To_Pointer_bool(&in.EnableProfiling, &out.EnableProfiling, s); err != nil { - return err - } - out.EnableContentionProfiling = in.EnableContentionProfiling - out.ContentType = in.ContentType - out.KubeAPIQPS = in.KubeAPIQPS - out.KubeAPIBurst = int(in.KubeAPIBurst) out.SchedulerName = in.SchedulerName - out.HardPodAffinitySymmetricWeight = in.HardPodAffinitySymmetricWeight - out.FailureDomains = in.FailureDomains - if err := Convert_componentconfig_LeaderElectionConfiguration_To_v1alpha1_LeaderElectionConfiguration(&in.LeaderElection, &out.LeaderElection, s); err != nil { + if err := Convert_componentconfig_SchedulerAlgorithmSource_To_v1alpha1_SchedulerAlgorithmSource(&in.AlgorithmSource, &out.AlgorithmSource, s); err != nil { return err } - out.LockObjectNamespace = in.LockObjectNamespace - out.LockObjectName = in.LockObjectName - out.PolicyConfigMapName = in.PolicyConfigMapName - out.PolicyConfigMapNamespace = in.PolicyConfigMapNamespace - out.UseLegacyPolicyConfig = in.UseLegacyPolicyConfig + out.HardPodAffinitySymmetricWeight = in.HardPodAffinitySymmetricWeight + if err := Convert_componentconfig_KubeSchedulerLeaderElectionConfiguration_To_v1alpha1_KubeSchedulerLeaderElectionConfiguration(&in.LeaderElection, &out.LeaderElection, s); err != nil { + return err + } + if err := Convert_componentconfig_ClientConnectionConfiguration_To_v1alpha1_ClientConnectionConfiguration(&in.ClientConnection, &out.ClientConnection, s); err != nil { + return err + } + out.HealthzBindAddress = in.HealthzBindAddress + out.MetricsBindAddress = in.MetricsBindAddress + out.EnableProfiling = in.EnableProfiling + out.EnableContentionProfiling = in.EnableContentionProfiling + out.FailureDomains = in.FailureDomains return nil } @@ -289,6 +134,34 @@ func Convert_componentconfig_KubeSchedulerConfiguration_To_v1alpha1_KubeSchedule return autoConvert_componentconfig_KubeSchedulerConfiguration_To_v1alpha1_KubeSchedulerConfiguration(in, out, s) } +func autoConvert_v1alpha1_KubeSchedulerLeaderElectionConfiguration_To_componentconfig_KubeSchedulerLeaderElectionConfiguration(in *KubeSchedulerLeaderElectionConfiguration, out *componentconfig.KubeSchedulerLeaderElectionConfiguration, s conversion.Scope) error { + if err := Convert_v1alpha1_LeaderElectionConfiguration_To_componentconfig_LeaderElectionConfiguration(&in.LeaderElectionConfiguration, &out.LeaderElectionConfiguration, s); err != nil { + return err + } + out.LockObjectNamespace = in.LockObjectNamespace + out.LockObjectName = in.LockObjectName + return nil +} + +// Convert_v1alpha1_KubeSchedulerLeaderElectionConfiguration_To_componentconfig_KubeSchedulerLeaderElectionConfiguration is an autogenerated conversion function. +func Convert_v1alpha1_KubeSchedulerLeaderElectionConfiguration_To_componentconfig_KubeSchedulerLeaderElectionConfiguration(in *KubeSchedulerLeaderElectionConfiguration, out *componentconfig.KubeSchedulerLeaderElectionConfiguration, s conversion.Scope) error { + return autoConvert_v1alpha1_KubeSchedulerLeaderElectionConfiguration_To_componentconfig_KubeSchedulerLeaderElectionConfiguration(in, out, s) +} + +func autoConvert_componentconfig_KubeSchedulerLeaderElectionConfiguration_To_v1alpha1_KubeSchedulerLeaderElectionConfiguration(in *componentconfig.KubeSchedulerLeaderElectionConfiguration, out *KubeSchedulerLeaderElectionConfiguration, s conversion.Scope) error { + if err := Convert_componentconfig_LeaderElectionConfiguration_To_v1alpha1_LeaderElectionConfiguration(&in.LeaderElectionConfiguration, &out.LeaderElectionConfiguration, s); err != nil { + return err + } + out.LockObjectNamespace = in.LockObjectNamespace + out.LockObjectName = in.LockObjectName + return nil +} + +// Convert_componentconfig_KubeSchedulerLeaderElectionConfiguration_To_v1alpha1_KubeSchedulerLeaderElectionConfiguration is an autogenerated conversion function. +func Convert_componentconfig_KubeSchedulerLeaderElectionConfiguration_To_v1alpha1_KubeSchedulerLeaderElectionConfiguration(in *componentconfig.KubeSchedulerLeaderElectionConfiguration, out *KubeSchedulerLeaderElectionConfiguration, s conversion.Scope) error { + return autoConvert_componentconfig_KubeSchedulerLeaderElectionConfiguration_To_v1alpha1_KubeSchedulerLeaderElectionConfiguration(in, out, s) +} + func autoConvert_v1alpha1_LeaderElectionConfiguration_To_componentconfig_LeaderElectionConfiguration(in *LeaderElectionConfiguration, out *componentconfig.LeaderElectionConfiguration, s conversion.Scope) error { if err := v1.Convert_Pointer_bool_To_bool(&in.LeaderElect, &out.LeaderElect, s); err != nil { return err @@ -320,3 +193,89 @@ func autoConvert_componentconfig_LeaderElectionConfiguration_To_v1alpha1_LeaderE func Convert_componentconfig_LeaderElectionConfiguration_To_v1alpha1_LeaderElectionConfiguration(in *componentconfig.LeaderElectionConfiguration, out *LeaderElectionConfiguration, s conversion.Scope) error { return autoConvert_componentconfig_LeaderElectionConfiguration_To_v1alpha1_LeaderElectionConfiguration(in, out, s) } + +func autoConvert_v1alpha1_SchedulerAlgorithmSource_To_componentconfig_SchedulerAlgorithmSource(in *SchedulerAlgorithmSource, out *componentconfig.SchedulerAlgorithmSource, s conversion.Scope) error { + out.Policy = (*componentconfig.SchedulerPolicySource)(unsafe.Pointer(in.Policy)) + out.Provider = (*string)(unsafe.Pointer(in.Provider)) + return nil +} + +// Convert_v1alpha1_SchedulerAlgorithmSource_To_componentconfig_SchedulerAlgorithmSource is an autogenerated conversion function. +func Convert_v1alpha1_SchedulerAlgorithmSource_To_componentconfig_SchedulerAlgorithmSource(in *SchedulerAlgorithmSource, out *componentconfig.SchedulerAlgorithmSource, s conversion.Scope) error { + return autoConvert_v1alpha1_SchedulerAlgorithmSource_To_componentconfig_SchedulerAlgorithmSource(in, out, s) +} + +func autoConvert_componentconfig_SchedulerAlgorithmSource_To_v1alpha1_SchedulerAlgorithmSource(in *componentconfig.SchedulerAlgorithmSource, out *SchedulerAlgorithmSource, s conversion.Scope) error { + out.Policy = (*SchedulerPolicySource)(unsafe.Pointer(in.Policy)) + out.Provider = (*string)(unsafe.Pointer(in.Provider)) + return nil +} + +// Convert_componentconfig_SchedulerAlgorithmSource_To_v1alpha1_SchedulerAlgorithmSource is an autogenerated conversion function. +func Convert_componentconfig_SchedulerAlgorithmSource_To_v1alpha1_SchedulerAlgorithmSource(in *componentconfig.SchedulerAlgorithmSource, out *SchedulerAlgorithmSource, s conversion.Scope) error { + return autoConvert_componentconfig_SchedulerAlgorithmSource_To_v1alpha1_SchedulerAlgorithmSource(in, out, s) +} + +func autoConvert_v1alpha1_SchedulerPolicyConfigMapSource_To_componentconfig_SchedulerPolicyConfigMapSource(in *SchedulerPolicyConfigMapSource, out *componentconfig.SchedulerPolicyConfigMapSource, s conversion.Scope) error { + out.Namespace = in.Namespace + out.Name = in.Name + return nil +} + +// Convert_v1alpha1_SchedulerPolicyConfigMapSource_To_componentconfig_SchedulerPolicyConfigMapSource is an autogenerated conversion function. +func Convert_v1alpha1_SchedulerPolicyConfigMapSource_To_componentconfig_SchedulerPolicyConfigMapSource(in *SchedulerPolicyConfigMapSource, out *componentconfig.SchedulerPolicyConfigMapSource, s conversion.Scope) error { + return autoConvert_v1alpha1_SchedulerPolicyConfigMapSource_To_componentconfig_SchedulerPolicyConfigMapSource(in, out, s) +} + +func autoConvert_componentconfig_SchedulerPolicyConfigMapSource_To_v1alpha1_SchedulerPolicyConfigMapSource(in *componentconfig.SchedulerPolicyConfigMapSource, out *SchedulerPolicyConfigMapSource, s conversion.Scope) error { + out.Namespace = in.Namespace + out.Name = in.Name + return nil +} + +// Convert_componentconfig_SchedulerPolicyConfigMapSource_To_v1alpha1_SchedulerPolicyConfigMapSource is an autogenerated conversion function. +func Convert_componentconfig_SchedulerPolicyConfigMapSource_To_v1alpha1_SchedulerPolicyConfigMapSource(in *componentconfig.SchedulerPolicyConfigMapSource, out *SchedulerPolicyConfigMapSource, s conversion.Scope) error { + return autoConvert_componentconfig_SchedulerPolicyConfigMapSource_To_v1alpha1_SchedulerPolicyConfigMapSource(in, out, s) +} + +func autoConvert_v1alpha1_SchedulerPolicyFileSource_To_componentconfig_SchedulerPolicyFileSource(in *SchedulerPolicyFileSource, out *componentconfig.SchedulerPolicyFileSource, s conversion.Scope) error { + out.Path = in.Path + return nil +} + +// Convert_v1alpha1_SchedulerPolicyFileSource_To_componentconfig_SchedulerPolicyFileSource is an autogenerated conversion function. +func Convert_v1alpha1_SchedulerPolicyFileSource_To_componentconfig_SchedulerPolicyFileSource(in *SchedulerPolicyFileSource, out *componentconfig.SchedulerPolicyFileSource, s conversion.Scope) error { + return autoConvert_v1alpha1_SchedulerPolicyFileSource_To_componentconfig_SchedulerPolicyFileSource(in, out, s) +} + +func autoConvert_componentconfig_SchedulerPolicyFileSource_To_v1alpha1_SchedulerPolicyFileSource(in *componentconfig.SchedulerPolicyFileSource, out *SchedulerPolicyFileSource, s conversion.Scope) error { + out.Path = in.Path + return nil +} + +// Convert_componentconfig_SchedulerPolicyFileSource_To_v1alpha1_SchedulerPolicyFileSource is an autogenerated conversion function. +func Convert_componentconfig_SchedulerPolicyFileSource_To_v1alpha1_SchedulerPolicyFileSource(in *componentconfig.SchedulerPolicyFileSource, out *SchedulerPolicyFileSource, s conversion.Scope) error { + return autoConvert_componentconfig_SchedulerPolicyFileSource_To_v1alpha1_SchedulerPolicyFileSource(in, out, s) +} + +func autoConvert_v1alpha1_SchedulerPolicySource_To_componentconfig_SchedulerPolicySource(in *SchedulerPolicySource, out *componentconfig.SchedulerPolicySource, s conversion.Scope) error { + out.File = (*componentconfig.SchedulerPolicyFileSource)(unsafe.Pointer(in.File)) + out.ConfigMap = (*componentconfig.SchedulerPolicyConfigMapSource)(unsafe.Pointer(in.ConfigMap)) + return nil +} + +// Convert_v1alpha1_SchedulerPolicySource_To_componentconfig_SchedulerPolicySource is an autogenerated conversion function. +func Convert_v1alpha1_SchedulerPolicySource_To_componentconfig_SchedulerPolicySource(in *SchedulerPolicySource, out *componentconfig.SchedulerPolicySource, s conversion.Scope) error { + return autoConvert_v1alpha1_SchedulerPolicySource_To_componentconfig_SchedulerPolicySource(in, out, s) +} + +func autoConvert_componentconfig_SchedulerPolicySource_To_v1alpha1_SchedulerPolicySource(in *componentconfig.SchedulerPolicySource, out *SchedulerPolicySource, s conversion.Scope) error { + out.File = (*SchedulerPolicyFileSource)(unsafe.Pointer(in.File)) + out.ConfigMap = (*SchedulerPolicyConfigMapSource)(unsafe.Pointer(in.ConfigMap)) + return nil +} + +// Convert_componentconfig_SchedulerPolicySource_To_v1alpha1_SchedulerPolicySource is an autogenerated conversion function. +func Convert_componentconfig_SchedulerPolicySource_To_v1alpha1_SchedulerPolicySource(in *componentconfig.SchedulerPolicySource, out *SchedulerPolicySource, s conversion.Scope) error { + return autoConvert_componentconfig_SchedulerPolicySource_To_v1alpha1_SchedulerPolicySource(in, out, s) +} diff --git a/pkg/apis/componentconfig/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/componentconfig/v1alpha1/zz_generated.deepcopy.go index 51344da9e6a..be2509123a4 100644 --- a/pkg/apis/componentconfig/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/componentconfig/v1alpha1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,52 +21,9 @@ limitations under the License. package v1alpha1 import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClientConnectionConfiguration).DeepCopyInto(out.(*ClientConnectionConfiguration)) - return nil - }, InType: reflect.TypeOf(&ClientConnectionConfiguration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KubeProxyConfiguration).DeepCopyInto(out.(*KubeProxyConfiguration)) - return nil - }, InType: reflect.TypeOf(&KubeProxyConfiguration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KubeProxyConntrackConfiguration).DeepCopyInto(out.(*KubeProxyConntrackConfiguration)) - return nil - }, InType: reflect.TypeOf(&KubeProxyConntrackConfiguration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KubeProxyIPTablesConfiguration).DeepCopyInto(out.(*KubeProxyIPTablesConfiguration)) - return nil - }, InType: reflect.TypeOf(&KubeProxyIPTablesConfiguration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KubeProxyIPVSConfiguration).DeepCopyInto(out.(*KubeProxyIPVSConfiguration)) - return nil - }, InType: reflect.TypeOf(&KubeProxyIPVSConfiguration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KubeSchedulerConfiguration).DeepCopyInto(out.(*KubeSchedulerConfiguration)) - return nil - }, InType: reflect.TypeOf(&KubeSchedulerConfiguration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LeaderElectionConfiguration).DeepCopyInto(out.(*LeaderElectionConfiguration)) - return nil - }, InType: reflect.TypeOf(&LeaderElectionConfiguration{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClientConnectionConfiguration) DeepCopyInto(out *ClientConnectionConfiguration) { *out = *in @@ -83,124 +40,13 @@ func (in *ClientConnectionConfiguration) DeepCopy() *ClientConnectionConfigurati return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *KubeProxyConfiguration) DeepCopyInto(out *KubeProxyConfiguration) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ClientConnection = in.ClientConnection - in.IPTables.DeepCopyInto(&out.IPTables) - out.IPVS = in.IPVS - if in.OOMScoreAdj != nil { - in, out := &in.OOMScoreAdj, &out.OOMScoreAdj - if *in == nil { - *out = nil - } else { - *out = new(int32) - **out = **in - } - } - out.UDPIdleTimeout = in.UDPIdleTimeout - out.Conntrack = in.Conntrack - out.ConfigSyncPeriod = in.ConfigSyncPeriod - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeProxyConfiguration. -func (in *KubeProxyConfiguration) DeepCopy() *KubeProxyConfiguration { - if in == nil { - return nil - } - out := new(KubeProxyConfiguration) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *KubeProxyConfiguration) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *KubeProxyConntrackConfiguration) DeepCopyInto(out *KubeProxyConntrackConfiguration) { - *out = *in - out.TCPEstablishedTimeout = in.TCPEstablishedTimeout - out.TCPCloseWaitTimeout = in.TCPCloseWaitTimeout - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeProxyConntrackConfiguration. -func (in *KubeProxyConntrackConfiguration) DeepCopy() *KubeProxyConntrackConfiguration { - if in == nil { - return nil - } - out := new(KubeProxyConntrackConfiguration) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *KubeProxyIPTablesConfiguration) DeepCopyInto(out *KubeProxyIPTablesConfiguration) { - *out = *in - if in.MasqueradeBit != nil { - in, out := &in.MasqueradeBit, &out.MasqueradeBit - if *in == nil { - *out = nil - } else { - *out = new(int32) - **out = **in - } - } - out.SyncPeriod = in.SyncPeriod - out.MinSyncPeriod = in.MinSyncPeriod - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeProxyIPTablesConfiguration. -func (in *KubeProxyIPTablesConfiguration) DeepCopy() *KubeProxyIPTablesConfiguration { - if in == nil { - return nil - } - out := new(KubeProxyIPTablesConfiguration) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *KubeProxyIPVSConfiguration) DeepCopyInto(out *KubeProxyIPVSConfiguration) { - *out = *in - out.SyncPeriod = in.SyncPeriod - out.MinSyncPeriod = in.MinSyncPeriod - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeProxyIPVSConfiguration. -func (in *KubeProxyIPVSConfiguration) DeepCopy() *KubeProxyIPVSConfiguration { - if in == nil { - return nil - } - out := new(KubeProxyIPVSConfiguration) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KubeSchedulerConfiguration) DeepCopyInto(out *KubeSchedulerConfiguration) { *out = *in out.TypeMeta = in.TypeMeta - if in.EnableProfiling != nil { - in, out := &in.EnableProfiling, &out.EnableProfiling - if *in == nil { - *out = nil - } else { - *out = new(bool) - **out = **in - } - } + in.AlgorithmSource.DeepCopyInto(&out.AlgorithmSource) in.LeaderElection.DeepCopyInto(&out.LeaderElection) + out.ClientConnection = in.ClientConnection return } @@ -223,6 +69,23 @@ func (in *KubeSchedulerConfiguration) DeepCopyObject() runtime.Object { } } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeSchedulerLeaderElectionConfiguration) DeepCopyInto(out *KubeSchedulerLeaderElectionConfiguration) { + *out = *in + in.LeaderElectionConfiguration.DeepCopyInto(&out.LeaderElectionConfiguration) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeSchedulerLeaderElectionConfiguration. +func (in *KubeSchedulerLeaderElectionConfiguration) DeepCopy() *KubeSchedulerLeaderElectionConfiguration { + if in == nil { + return nil + } + out := new(KubeSchedulerLeaderElectionConfiguration) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LeaderElectionConfiguration) DeepCopyInto(out *LeaderElectionConfiguration) { *out = *in @@ -250,3 +113,103 @@ func (in *LeaderElectionConfiguration) DeepCopy() *LeaderElectionConfiguration { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SchedulerAlgorithmSource) DeepCopyInto(out *SchedulerAlgorithmSource) { + *out = *in + if in.Policy != nil { + in, out := &in.Policy, &out.Policy + if *in == nil { + *out = nil + } else { + *out = new(SchedulerPolicySource) + (*in).DeepCopyInto(*out) + } + } + if in.Provider != nil { + in, out := &in.Provider, &out.Provider + if *in == nil { + *out = nil + } else { + *out = new(string) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SchedulerAlgorithmSource. +func (in *SchedulerAlgorithmSource) DeepCopy() *SchedulerAlgorithmSource { + if in == nil { + return nil + } + out := new(SchedulerAlgorithmSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SchedulerPolicyConfigMapSource) DeepCopyInto(out *SchedulerPolicyConfigMapSource) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SchedulerPolicyConfigMapSource. +func (in *SchedulerPolicyConfigMapSource) DeepCopy() *SchedulerPolicyConfigMapSource { + if in == nil { + return nil + } + out := new(SchedulerPolicyConfigMapSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SchedulerPolicyFileSource) DeepCopyInto(out *SchedulerPolicyFileSource) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SchedulerPolicyFileSource. +func (in *SchedulerPolicyFileSource) DeepCopy() *SchedulerPolicyFileSource { + if in == nil { + return nil + } + out := new(SchedulerPolicyFileSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SchedulerPolicySource) DeepCopyInto(out *SchedulerPolicySource) { + *out = *in + if in.File != nil { + in, out := &in.File, &out.File + if *in == nil { + *out = nil + } else { + *out = new(SchedulerPolicyFileSource) + **out = **in + } + } + if in.ConfigMap != nil { + in, out := &in.ConfigMap, &out.ConfigMap + if *in == nil { + *out = nil + } else { + *out = new(SchedulerPolicyConfigMapSource) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SchedulerPolicySource. +func (in *SchedulerPolicySource) DeepCopy() *SchedulerPolicySource { + if in == nil { + return nil + } + out := new(SchedulerPolicySource) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/componentconfig/v1alpha1/zz_generated.defaults.go b/pkg/apis/componentconfig/v1alpha1/zz_generated.defaults.go index 72f514af67d..96685c35ba4 100644 --- a/pkg/apis/componentconfig/v1alpha1/zz_generated.defaults.go +++ b/pkg/apis/componentconfig/v1alpha1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -28,16 +28,11 @@ 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(&KubeProxyConfiguration{}, func(obj interface{}) { SetObjectDefaults_KubeProxyConfiguration(obj.(*KubeProxyConfiguration)) }) scheme.AddTypeDefaultingFunc(&KubeSchedulerConfiguration{}, func(obj interface{}) { SetObjectDefaults_KubeSchedulerConfiguration(obj.(*KubeSchedulerConfiguration)) }) return nil } -func SetObjectDefaults_KubeProxyConfiguration(in *KubeProxyConfiguration) { - SetDefaults_KubeProxyConfiguration(in) -} - func SetObjectDefaults_KubeSchedulerConfiguration(in *KubeSchedulerConfiguration) { SetDefaults_KubeSchedulerConfiguration(in) - SetDefaults_LeaderElectionConfiguration(&in.LeaderElection) + SetDefaults_LeaderElectionConfiguration(&in.LeaderElection.LeaderElectionConfiguration) } diff --git a/pkg/apis/componentconfig/zz_generated.deepcopy.go b/pkg/apis/componentconfig/zz_generated.deepcopy.go index d9ee091e54c..31fad659dfc 100644 --- a/pkg/apis/componentconfig/zz_generated.deepcopy.go +++ b/pkg/apis/componentconfig/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,76 +21,9 @@ limitations under the License. package componentconfig import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClientConnectionConfiguration).DeepCopyInto(out.(*ClientConnectionConfiguration)) - return nil - }, InType: reflect.TypeOf(&ClientConnectionConfiguration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*GroupResource).DeepCopyInto(out.(*GroupResource)) - return nil - }, InType: reflect.TypeOf(&GroupResource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*IPVar).DeepCopyInto(out.(*IPVar)) - return nil - }, InType: reflect.TypeOf(&IPVar{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KubeControllerManagerConfiguration).DeepCopyInto(out.(*KubeControllerManagerConfiguration)) - return nil - }, InType: reflect.TypeOf(&KubeControllerManagerConfiguration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KubeProxyConfiguration).DeepCopyInto(out.(*KubeProxyConfiguration)) - return nil - }, InType: reflect.TypeOf(&KubeProxyConfiguration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KubeProxyConntrackConfiguration).DeepCopyInto(out.(*KubeProxyConntrackConfiguration)) - return nil - }, InType: reflect.TypeOf(&KubeProxyConntrackConfiguration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KubeProxyIPTablesConfiguration).DeepCopyInto(out.(*KubeProxyIPTablesConfiguration)) - return nil - }, InType: reflect.TypeOf(&KubeProxyIPTablesConfiguration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KubeProxyIPVSConfiguration).DeepCopyInto(out.(*KubeProxyIPVSConfiguration)) - return nil - }, InType: reflect.TypeOf(&KubeProxyIPVSConfiguration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KubeSchedulerConfiguration).DeepCopyInto(out.(*KubeSchedulerConfiguration)) - return nil - }, InType: reflect.TypeOf(&KubeSchedulerConfiguration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LeaderElectionConfiguration).DeepCopyInto(out.(*LeaderElectionConfiguration)) - return nil - }, InType: reflect.TypeOf(&LeaderElectionConfiguration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PersistentVolumeRecyclerConfiguration).DeepCopyInto(out.(*PersistentVolumeRecyclerConfiguration)) - return nil - }, InType: reflect.TypeOf(&PersistentVolumeRecyclerConfiguration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PortRangeVar).DeepCopyInto(out.(*PortRangeVar)) - return nil - }, InType: reflect.TypeOf(&PortRangeVar{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*VolumeConfiguration).DeepCopyInto(out.(*VolumeConfiguration)) - return nil - }, InType: reflect.TypeOf(&VolumeConfiguration{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClientConnectionConfiguration) DeepCopyInto(out *ClientConnectionConfiguration) { *out = *in @@ -204,115 +137,13 @@ func (in *KubeControllerManagerConfiguration) DeepCopyObject() runtime.Object { } } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *KubeProxyConfiguration) DeepCopyInto(out *KubeProxyConfiguration) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ClientConnection = in.ClientConnection - in.IPTables.DeepCopyInto(&out.IPTables) - out.IPVS = in.IPVS - if in.OOMScoreAdj != nil { - in, out := &in.OOMScoreAdj, &out.OOMScoreAdj - if *in == nil { - *out = nil - } else { - *out = new(int32) - **out = **in - } - } - out.UDPIdleTimeout = in.UDPIdleTimeout - out.Conntrack = in.Conntrack - out.ConfigSyncPeriod = in.ConfigSyncPeriod - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeProxyConfiguration. -func (in *KubeProxyConfiguration) DeepCopy() *KubeProxyConfiguration { - if in == nil { - return nil - } - out := new(KubeProxyConfiguration) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *KubeProxyConfiguration) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *KubeProxyConntrackConfiguration) DeepCopyInto(out *KubeProxyConntrackConfiguration) { - *out = *in - out.TCPEstablishedTimeout = in.TCPEstablishedTimeout - out.TCPCloseWaitTimeout = in.TCPCloseWaitTimeout - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeProxyConntrackConfiguration. -func (in *KubeProxyConntrackConfiguration) DeepCopy() *KubeProxyConntrackConfiguration { - if in == nil { - return nil - } - out := new(KubeProxyConntrackConfiguration) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *KubeProxyIPTablesConfiguration) DeepCopyInto(out *KubeProxyIPTablesConfiguration) { - *out = *in - if in.MasqueradeBit != nil { - in, out := &in.MasqueradeBit, &out.MasqueradeBit - if *in == nil { - *out = nil - } else { - *out = new(int32) - **out = **in - } - } - out.SyncPeriod = in.SyncPeriod - out.MinSyncPeriod = in.MinSyncPeriod - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeProxyIPTablesConfiguration. -func (in *KubeProxyIPTablesConfiguration) DeepCopy() *KubeProxyIPTablesConfiguration { - if in == nil { - return nil - } - out := new(KubeProxyIPTablesConfiguration) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *KubeProxyIPVSConfiguration) DeepCopyInto(out *KubeProxyIPVSConfiguration) { - *out = *in - out.SyncPeriod = in.SyncPeriod - out.MinSyncPeriod = in.MinSyncPeriod - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeProxyIPVSConfiguration. -func (in *KubeProxyIPVSConfiguration) DeepCopy() *KubeProxyIPVSConfiguration { - if in == nil { - return nil - } - out := new(KubeProxyIPVSConfiguration) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KubeSchedulerConfiguration) DeepCopyInto(out *KubeSchedulerConfiguration) { *out = *in out.TypeMeta = in.TypeMeta + in.AlgorithmSource.DeepCopyInto(&out.AlgorithmSource) out.LeaderElection = in.LeaderElection + out.ClientConnection = in.ClientConnection return } @@ -335,6 +166,23 @@ func (in *KubeSchedulerConfiguration) DeepCopyObject() runtime.Object { } } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeSchedulerLeaderElectionConfiguration) DeepCopyInto(out *KubeSchedulerLeaderElectionConfiguration) { + *out = *in + out.LeaderElectionConfiguration = in.LeaderElectionConfiguration + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeSchedulerLeaderElectionConfiguration. +func (in *KubeSchedulerLeaderElectionConfiguration) DeepCopy() *KubeSchedulerLeaderElectionConfiguration { + if in == nil { + return nil + } + out := new(KubeSchedulerLeaderElectionConfiguration) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LeaderElectionConfiguration) DeepCopyInto(out *LeaderElectionConfiguration) { *out = *in @@ -395,6 +243,106 @@ func (in *PortRangeVar) DeepCopy() *PortRangeVar { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SchedulerAlgorithmSource) DeepCopyInto(out *SchedulerAlgorithmSource) { + *out = *in + if in.Policy != nil { + in, out := &in.Policy, &out.Policy + if *in == nil { + *out = nil + } else { + *out = new(SchedulerPolicySource) + (*in).DeepCopyInto(*out) + } + } + if in.Provider != nil { + in, out := &in.Provider, &out.Provider + if *in == nil { + *out = nil + } else { + *out = new(string) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SchedulerAlgorithmSource. +func (in *SchedulerAlgorithmSource) DeepCopy() *SchedulerAlgorithmSource { + if in == nil { + return nil + } + out := new(SchedulerAlgorithmSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SchedulerPolicyConfigMapSource) DeepCopyInto(out *SchedulerPolicyConfigMapSource) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SchedulerPolicyConfigMapSource. +func (in *SchedulerPolicyConfigMapSource) DeepCopy() *SchedulerPolicyConfigMapSource { + if in == nil { + return nil + } + out := new(SchedulerPolicyConfigMapSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SchedulerPolicyFileSource) DeepCopyInto(out *SchedulerPolicyFileSource) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SchedulerPolicyFileSource. +func (in *SchedulerPolicyFileSource) DeepCopy() *SchedulerPolicyFileSource { + if in == nil { + return nil + } + out := new(SchedulerPolicyFileSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SchedulerPolicySource) DeepCopyInto(out *SchedulerPolicySource) { + *out = *in + if in.File != nil { + in, out := &in.File, &out.File + if *in == nil { + *out = nil + } else { + *out = new(SchedulerPolicyFileSource) + **out = **in + } + } + if in.ConfigMap != nil { + in, out := &in.ConfigMap, &out.ConfigMap + if *in == nil { + *out = nil + } else { + *out = new(SchedulerPolicyConfigMapSource) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SchedulerPolicySource. +func (in *SchedulerPolicySource) DeepCopy() *SchedulerPolicySource { + if in == nil { + return nil + } + out := new(SchedulerPolicySource) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VolumeConfiguration) DeepCopyInto(out *VolumeConfiguration) { *out = *in diff --git a/pkg/apis/core/BUILD b/pkg/apis/core/BUILD new file mode 100644 index 00000000000..874383ef18d --- /dev/null +++ b/pkg/apis/core/BUILD @@ -0,0 +1,63 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "annotation_key_constants.go", + "doc.go", + "field_constants.go", + "json.go", + "objectreference.go", + "register.go", + "resource.go", + "taint.go", + "toleration.go", + "types.go", + "zz_generated.deepcopy.go", + ], + importpath = "k8s.io/kubernetes/pkg/apis/core", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "taint_test.go", + "toleration_test.go", + ], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/apis/core", +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//pkg/apis/core/fuzzer:all-srcs", + "//pkg/apis/core/helper:all-srcs", + "//pkg/apis/core/install:all-srcs", + "//pkg/apis/core/pods:all-srcs", + "//pkg/apis/core/v1:all-srcs", + "//pkg/apis/core/validation:all-srcs", + ], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/apis/core/OWNERS b/pkg/apis/core/OWNERS new file mode 100644 index 00000000000..0605b27b2aa --- /dev/null +++ b/pkg/apis/core/OWNERS @@ -0,0 +1,44 @@ +approvers: +- erictune +- lavalamp +- smarterclayton +- thockin +- liggitt +# - bgrant0607 # manual escalations only +reviewers: +- thockin +- lavalamp +- smarterclayton +- wojtek-t +- deads2k +- yujuhong +- brendandburns +- derekwaynecarr +- caesarxuchao +- vishh +- mikedanese +- liggitt +- nikhiljindal +- gmarek +- erictune +- davidopp +- pmorie +- sttts +- dchen1107 +- saad-ali +- zmerlynn +- luxas +- janetkuo +- justinsb +- pwittrock +- roberthbailey +- ncdc +- tallclair +- yifan-gu +- eparis +- mwielgus +- timothysc +- soltysh +- piosz +- jsafrane +- jbeda diff --git a/pkg/api/annotation_key_constants.go b/pkg/apis/core/annotation_key_constants.go similarity index 94% rename from pkg/api/annotation_key_constants.go rename to pkg/apis/core/annotation_key_constants.go index e935424462b..131fdd9905e 100644 --- a/pkg/api/annotation_key_constants.go +++ b/pkg/apis/core/annotation_key_constants.go @@ -16,7 +16,7 @@ limitations under the License. // This file should be consistent with pkg/api/v1/annotation_key_constants.go. -package api +package core const ( // ImagePolicyFailedOpenKey is added to pods created by failing open when the image policy @@ -45,12 +45,6 @@ const ( // to one container of a pod. SeccompContainerAnnotationKeyPrefix string = "container.seccomp.security.alpha.kubernetes.io/" - // CreatedByAnnotation represents the key used to store the spec(json) - // used to create the resource. - // This field is deprecated in favor of ControllerRef (see #44407). - // TODO(#50720): Remove this field in v1.9. - CreatedByAnnotation = "kubernetes.io/created-by" - // PreferAvoidPodsAnnotationKey represents the key of preferAvoidPods data (json serialized) // in the Annotations of a Node. PreferAvoidPodsAnnotationKey string = "scheduler.alpha.kubernetes.io/preferAvoidPods" @@ -74,6 +68,10 @@ const ( // This annotation can be attached to node. ObjectTTLAnnotationKey string = "node.alpha.kubernetes.io/ttl" + // BootstrapCheckpointAnnotationKey represents a Resource (Pod) that should be checkpointed by + // the kubelet prior to running + BootstrapCheckpointAnnotationKey string = "node.kubernetes.io/bootstrap-checkpoint" + // annotation key prefix used to identify non-convertible json paths. NonConvertibleAnnotationPrefix = "non-convertible.kubernetes.io" diff --git a/pkg/apis/core/doc.go b/pkg/apis/core/doc.go new file mode 100644 index 00000000000..6017bfdab1b --- /dev/null +++ b/pkg/apis/core/doc.go @@ -0,0 +1,24 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 api contains the latest (or "internal") version of the +// Kubernetes API objects. This is the API objects as represented in memory. +// The contract presented to clients is located in the versioned packages, +// which are sub-directories. The first one is "v1". Those packages +// describe how a particular version is serialized to storage/network. +package core // import "k8s.io/kubernetes/pkg/apis/core" diff --git a/pkg/api/field_constants.go b/pkg/apis/core/field_constants.go similarity index 92% rename from pkg/api/field_constants.go rename to pkg/apis/core/field_constants.go index 5ead0f13feb..a26f80568c0 100644 --- a/pkg/api/field_constants.go +++ b/pkg/apis/core/field_constants.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package api +package core // Field path constants that are specific to the internal API // representation. @@ -25,8 +25,8 @@ const ( PodStatusField = "status.phase" SecretTypeField = "type" - EventReasonField = "reason" - EventSourceField = "source" + EventReasonField = "action" + EventSourceField = "reportingComponent" EventTypeField = "type" EventInvolvedKindField = "involvedObject.kind" EventInvolvedNamespaceField = "involvedObject.namespace" diff --git a/pkg/apis/core/fuzzer/BUILD b/pkg/apis/core/fuzzer/BUILD new file mode 100644 index 00000000000..dc09845478c --- /dev/null +++ b/pkg/apis/core/fuzzer/BUILD @@ -0,0 +1,37 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = ["fuzzer.go"], + importpath = "k8s.io/kubernetes/pkg/apis/core/fuzzer", + deps = [ + "//pkg/apis/core:go_default_library", + "//vendor/github.com/google/gofuzz:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/pkg/apis/core/fuzzer/fuzzer.go b/pkg/apis/core/fuzzer/fuzzer.go new file mode 100644 index 00000000000..e979584723e --- /dev/null +++ b/pkg/apis/core/fuzzer/fuzzer.go @@ -0,0 +1,496 @@ +/* +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 fuzzer + +import ( + "reflect" + "strconv" + "time" + + fuzz "github.com/google/gofuzz" + + "k8s.io/apimachinery/pkg/api/resource" + 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" + runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/kubernetes/pkg/apis/core" +) + +// Funcs returns the fuzzer functions for the core core group. +var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { + return []interface{}{ + func(q *resource.Quantity, c fuzz.Continue) { + *q = *resource.NewQuantity(c.Int63n(1000), resource.DecimalExponent) + }, + func(j *core.ObjectReference, c fuzz.Continue) { + // We have to customize the randomization of TypeMetas because their + // APIVersion and Kind must remain blank in memory. + j.APIVersion = c.RandString() + j.Kind = c.RandString() + j.Namespace = c.RandString() + j.Name = c.RandString() + j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10) + j.FieldPath = c.RandString() + }, + func(j *core.ListOptions, c fuzz.Continue) { + label, _ := labels.Parse("a=b") + j.LabelSelector = label + field, _ := fields.ParseSelector("a=b") + j.FieldSelector = field + }, + func(j *core.PodExecOptions, c fuzz.Continue) { + j.Stdout = true + j.Stderr = true + }, + func(j *core.PodAttachOptions, c fuzz.Continue) { + j.Stdout = true + j.Stderr = true + }, + func(j *core.PodPortForwardOptions, c fuzz.Continue) { + if c.RandBool() { + j.Ports = make([]int32, c.Intn(10)) + for i := range j.Ports { + j.Ports[i] = c.Int31n(65535) + } + } + }, + func(s *core.PodSpec, c fuzz.Continue) { + c.FuzzNoCustom(s) + // has a default value + ttl := int64(30) + if c.RandBool() { + ttl = int64(c.Uint32()) + } + s.TerminationGracePeriodSeconds = &ttl + + c.Fuzz(s.SecurityContext) + + if s.SecurityContext == nil { + s.SecurityContext = new(core.PodSecurityContext) + } + if s.Affinity == nil { + s.Affinity = new(core.Affinity) + } + if s.SchedulerName == "" { + s.SchedulerName = core.DefaultSchedulerName + } + }, + func(j *core.PodPhase, c fuzz.Continue) { + statuses := []core.PodPhase{core.PodPending, core.PodRunning, core.PodFailed, core.PodUnknown} + *j = statuses[c.Rand.Intn(len(statuses))] + }, + func(j *core.Binding, c fuzz.Continue) { + c.Fuzz(&j.ObjectMeta) + j.Target.Name = c.RandString() + }, + func(j *core.ReplicationControllerSpec, c fuzz.Continue) { + c.FuzzNoCustom(j) // fuzz self without calling this function again + //j.TemplateRef = nil // this is required for round trip + }, + func(j *core.List, c fuzz.Continue) { + c.FuzzNoCustom(j) // fuzz self without calling this function again + // TODO: uncomment when round trip starts from a versioned object + if false { //j.Items == nil { + j.Items = []runtime.Object{} + } + }, + func(q *core.ResourceRequirements, c fuzz.Continue) { + randomQuantity := func() resource.Quantity { + var q resource.Quantity + c.Fuzz(&q) + // precalc the string for benchmarking purposes + _ = q.String() + return q + } + q.Limits = make(core.ResourceList) + q.Requests = make(core.ResourceList) + cpuLimit := randomQuantity() + q.Limits[core.ResourceCPU] = *cpuLimit.Copy() + q.Requests[core.ResourceCPU] = *cpuLimit.Copy() + memoryLimit := randomQuantity() + q.Limits[core.ResourceMemory] = *memoryLimit.Copy() + q.Requests[core.ResourceMemory] = *memoryLimit.Copy() + storageLimit := randomQuantity() + q.Limits[core.ResourceStorage] = *storageLimit.Copy() + q.Requests[core.ResourceStorage] = *storageLimit.Copy() + }, + func(q *core.LimitRangeItem, c fuzz.Continue) { + var cpuLimit resource.Quantity + c.Fuzz(&cpuLimit) + + q.Type = core.LimitTypeContainer + q.Default = make(core.ResourceList) + q.Default[core.ResourceCPU] = *(cpuLimit.Copy()) + + q.DefaultRequest = make(core.ResourceList) + q.DefaultRequest[core.ResourceCPU] = *(cpuLimit.Copy()) + + q.Max = make(core.ResourceList) + q.Max[core.ResourceCPU] = *(cpuLimit.Copy()) + + q.Min = make(core.ResourceList) + q.Min[core.ResourceCPU] = *(cpuLimit.Copy()) + + q.MaxLimitRequestRatio = make(core.ResourceList) + q.MaxLimitRequestRatio[core.ResourceCPU] = resource.MustParse("10") + }, + func(p *core.PullPolicy, c fuzz.Continue) { + policies := []core.PullPolicy{core.PullAlways, core.PullNever, core.PullIfNotPresent} + *p = policies[c.Rand.Intn(len(policies))] + }, + func(rp *core.RestartPolicy, c fuzz.Continue) { + policies := []core.RestartPolicy{core.RestartPolicyAlways, core.RestartPolicyNever, core.RestartPolicyOnFailure} + *rp = policies[c.Rand.Intn(len(policies))] + }, + // core.DownwardAPIVolumeFile needs to have a specific func since FieldRef has to be + // defaulted to a version otherwise roundtrip will fail + func(m *core.DownwardAPIVolumeFile, c fuzz.Continue) { + m.Path = c.RandString() + versions := []string{"v1"} + m.FieldRef = &core.ObjectFieldSelector{} + m.FieldRef.APIVersion = versions[c.Rand.Intn(len(versions))] + m.FieldRef.FieldPath = c.RandString() + c.Fuzz(m.Mode) + if m.Mode != nil { + *m.Mode &= 0777 + } + }, + func(s *core.SecretVolumeSource, c fuzz.Continue) { + c.FuzzNoCustom(s) // fuzz self without calling this function again + + if c.RandBool() { + opt := c.RandBool() + s.Optional = &opt + } + // DefaultMode should always be set, it has a default + // value and it is expected to be between 0 and 0777 + var mode int32 + c.Fuzz(&mode) + mode &= 0777 + s.DefaultMode = &mode + }, + func(cm *core.ConfigMapVolumeSource, c fuzz.Continue) { + c.FuzzNoCustom(cm) // fuzz self without calling this function again + + if c.RandBool() { + opt := c.RandBool() + cm.Optional = &opt + } + // DefaultMode should always be set, it has a default + // value and it is expected to be between 0 and 0777 + var mode int32 + c.Fuzz(&mode) + mode &= 0777 + cm.DefaultMode = &mode + }, + func(d *core.DownwardAPIVolumeSource, c fuzz.Continue) { + c.FuzzNoCustom(d) // fuzz self without calling this function again + + // DefaultMode should always be set, it has a default + // value and it is expected to be between 0 and 0777 + var mode int32 + c.Fuzz(&mode) + mode &= 0777 + d.DefaultMode = &mode + }, + func(s *core.ProjectedVolumeSource, c fuzz.Continue) { + c.FuzzNoCustom(s) // fuzz self without calling this function again + + // DefaultMode should always be set, it has a default + // value and it is expected to be between 0 and 0777 + var mode int32 + c.Fuzz(&mode) + mode &= 0777 + s.DefaultMode = &mode + }, + func(k *core.KeyToPath, c fuzz.Continue) { + c.FuzzNoCustom(k) // fuzz self without calling this function again + k.Key = c.RandString() + k.Path = c.RandString() + + // Mode is not mandatory, but if it is set, it should be + // a value between 0 and 0777 + if k.Mode != nil { + *k.Mode &= 0777 + } + }, + func(vs *core.VolumeSource, c fuzz.Continue) { + // Exactly one of the fields must be set. + v := reflect.ValueOf(vs).Elem() + i := int(c.RandUint64() % uint64(v.NumField())) + t := v.Field(i).Addr() + for v.Field(i).IsNil() { + c.Fuzz(t.Interface()) + } + }, + func(i *core.ISCSIVolumeSource, c fuzz.Continue) { + i.ISCSIInterface = c.RandString() + if i.ISCSIInterface == "" { + i.ISCSIInterface = "default" + } + }, + func(i *core.ISCSIPersistentVolumeSource, c fuzz.Continue) { + i.ISCSIInterface = c.RandString() + if i.ISCSIInterface == "" { + i.ISCSIInterface = "default" + } + }, + func(d *core.DNSPolicy, c fuzz.Continue) { + policies := []core.DNSPolicy{core.DNSClusterFirst, core.DNSDefault} + *d = policies[c.Rand.Intn(len(policies))] + }, + func(p *core.Protocol, c fuzz.Continue) { + protocols := []core.Protocol{core.ProtocolTCP, core.ProtocolUDP} + *p = protocols[c.Rand.Intn(len(protocols))] + }, + func(p *core.ServiceAffinity, c fuzz.Continue) { + types := []core.ServiceAffinity{core.ServiceAffinityClientIP, core.ServiceAffinityNone} + *p = types[c.Rand.Intn(len(types))] + }, + func(p *core.ServiceType, c fuzz.Continue) { + types := []core.ServiceType{core.ServiceTypeClusterIP, core.ServiceTypeNodePort, core.ServiceTypeLoadBalancer} + *p = types[c.Rand.Intn(len(types))] + }, + func(p *core.ServiceExternalTrafficPolicyType, c fuzz.Continue) { + types := []core.ServiceExternalTrafficPolicyType{core.ServiceExternalTrafficPolicyTypeCluster, core.ServiceExternalTrafficPolicyTypeLocal} + *p = types[c.Rand.Intn(len(types))] + }, + func(ct *core.Container, c fuzz.Continue) { + c.FuzzNoCustom(ct) // fuzz self without calling this function again + ct.TerminationMessagePath = "/" + ct.TerminationMessagePath // Must be non-empty + ct.TerminationMessagePolicy = "File" + }, + func(p *core.Probe, c fuzz.Continue) { + c.FuzzNoCustom(p) + // These fields have default values. + intFieldsWithDefaults := [...]string{"TimeoutSeconds", "PeriodSeconds", "SuccessThreshold", "FailureThreshold"} + v := reflect.ValueOf(p).Elem() + for _, field := range intFieldsWithDefaults { + f := v.FieldByName(field) + if f.Int() == 0 { + f.SetInt(1) + } + } + }, + func(ev *core.EnvVar, c fuzz.Continue) { + ev.Name = c.RandString() + if c.RandBool() { + ev.Value = c.RandString() + } else { + ev.ValueFrom = &core.EnvVarSource{} + ev.ValueFrom.FieldRef = &core.ObjectFieldSelector{} + + versions := []schema.GroupVersion{ + {Group: "admission.k8s.io", Version: "v1alpha1"}, + {Group: "apps", Version: "v1beta1"}, + {Group: "apps", Version: "v1beta2"}, + {Group: "foo", Version: "v42"}, + } + + ev.ValueFrom.FieldRef.APIVersion = versions[c.Rand.Intn(len(versions))].String() + ev.ValueFrom.FieldRef.FieldPath = c.RandString() + } + }, + func(ev *core.EnvFromSource, c fuzz.Continue) { + if c.RandBool() { + ev.Prefix = "p_" + } + if c.RandBool() { + c.Fuzz(&ev.ConfigMapRef) + } else { + c.Fuzz(&ev.SecretRef) + } + }, + func(cm *core.ConfigMapEnvSource, c fuzz.Continue) { + c.FuzzNoCustom(cm) // fuzz self without calling this function again + if c.RandBool() { + opt := c.RandBool() + cm.Optional = &opt + } + }, + func(s *core.SecretEnvSource, c fuzz.Continue) { + c.FuzzNoCustom(s) // fuzz self without calling this function again + }, + func(sc *core.SecurityContext, c fuzz.Continue) { + c.FuzzNoCustom(sc) // fuzz self without calling this function again + if c.RandBool() { + priv := c.RandBool() + sc.Privileged = &priv + } + + if c.RandBool() { + sc.Capabilities = &core.Capabilities{ + Add: make([]core.Capability, 0), + Drop: make([]core.Capability, 0), + } + c.Fuzz(&sc.Capabilities.Add) + c.Fuzz(&sc.Capabilities.Drop) + } + }, + func(s *core.Secret, c fuzz.Continue) { + c.FuzzNoCustom(s) // fuzz self without calling this function again + s.Type = core.SecretTypeOpaque + }, + func(r *core.RBDVolumeSource, c fuzz.Continue) { + r.RBDPool = c.RandString() + if r.RBDPool == "" { + r.RBDPool = "rbd" + } + r.RadosUser = c.RandString() + if r.RadosUser == "" { + r.RadosUser = "admin" + } + r.Keyring = c.RandString() + if r.Keyring == "" { + r.Keyring = "/etc/ceph/keyring" + } + }, + func(r *core.RBDPersistentVolumeSource, c fuzz.Continue) { + r.RBDPool = c.RandString() + if r.RBDPool == "" { + r.RBDPool = "rbd" + } + r.RadosUser = c.RandString() + if r.RadosUser == "" { + r.RadosUser = "admin" + } + r.Keyring = c.RandString() + if r.Keyring == "" { + r.Keyring = "/etc/ceph/keyring" + } + }, + func(obj *core.HostPathVolumeSource, c fuzz.Continue) { + c.FuzzNoCustom(obj) + types := []core.HostPathType{core.HostPathUnset, core.HostPathDirectoryOrCreate, core.HostPathDirectory, + core.HostPathFileOrCreate, core.HostPathFile, core.HostPathSocket, core.HostPathCharDev, core.HostPathBlockDev} + typeVol := types[c.Rand.Intn(len(types))] + if obj.Type == nil { + obj.Type = &typeVol + } + }, + func(pv *core.PersistentVolume, c fuzz.Continue) { + c.FuzzNoCustom(pv) // fuzz self without calling this function again + types := []core.PersistentVolumePhase{core.VolumeAvailable, core.VolumePending, core.VolumeBound, core.VolumeReleased, core.VolumeFailed} + pv.Status.Phase = types[c.Rand.Intn(len(types))] + pv.Status.Message = c.RandString() + reclamationPolicies := []core.PersistentVolumeReclaimPolicy{core.PersistentVolumeReclaimRecycle, core.PersistentVolumeReclaimRetain} + pv.Spec.PersistentVolumeReclaimPolicy = reclamationPolicies[c.Rand.Intn(len(reclamationPolicies))] + }, + func(pvc *core.PersistentVolumeClaim, c fuzz.Continue) { + c.FuzzNoCustom(pvc) // fuzz self without calling this function again + types := []core.PersistentVolumeClaimPhase{core.ClaimBound, core.ClaimPending, core.ClaimLost} + pvc.Status.Phase = types[c.Rand.Intn(len(types))] + }, + func(obj *core.AzureDiskVolumeSource, c fuzz.Continue) { + if obj.CachingMode == nil { + obj.CachingMode = new(core.AzureDataDiskCachingMode) + *obj.CachingMode = core.AzureDataDiskCachingReadWrite + } + if obj.Kind == nil { + obj.Kind = new(core.AzureDataDiskKind) + *obj.Kind = core.AzureSharedBlobDisk + } + if obj.FSType == nil { + obj.FSType = new(string) + *obj.FSType = "ext4" + } + if obj.ReadOnly == nil { + obj.ReadOnly = new(bool) + *obj.ReadOnly = false + } + }, + func(sio *core.ScaleIOVolumeSource, c fuzz.Continue) { + sio.StorageMode = c.RandString() + if sio.StorageMode == "" { + sio.StorageMode = "ThinProvisioned" + } + sio.FSType = c.RandString() + if sio.FSType == "" { + sio.FSType = "xfs" + } + }, + func(sio *core.ScaleIOPersistentVolumeSource, c fuzz.Continue) { + sio.StorageMode = c.RandString() + if sio.StorageMode == "" { + sio.StorageMode = "ThinProvisioned" + } + sio.FSType = c.RandString() + if sio.FSType == "" { + sio.FSType = "xfs" + } + }, + func(s *core.NamespaceSpec, c fuzz.Continue) { + s.Finalizers = []core.FinalizerName{core.FinalizerKubernetes} + }, + func(s *core.NamespaceStatus, c fuzz.Continue) { + s.Phase = core.NamespaceActive + }, + func(http *core.HTTPGetAction, c fuzz.Continue) { + c.FuzzNoCustom(http) // fuzz self without calling this function again + http.Path = "/" + http.Path // can't be blank + http.Scheme = "x" + http.Scheme // can't be blank + }, + func(ss *core.ServiceSpec, c fuzz.Continue) { + c.FuzzNoCustom(ss) // fuzz self without calling this function again + if len(ss.Ports) == 0 { + // There must be at least 1 port. + ss.Ports = append(ss.Ports, core.ServicePort{}) + c.Fuzz(&ss.Ports[0]) + } + for i := range ss.Ports { + switch ss.Ports[i].TargetPort.Type { + case intstr.Int: + ss.Ports[i].TargetPort.IntVal = 1 + ss.Ports[i].TargetPort.IntVal%65535 // non-zero + case intstr.String: + ss.Ports[i].TargetPort.StrVal = "x" + ss.Ports[i].TargetPort.StrVal // non-empty + } + } + types := []core.ServiceAffinity{core.ServiceAffinityNone, core.ServiceAffinityClientIP} + ss.SessionAffinity = types[c.Rand.Intn(len(types))] + switch ss.SessionAffinity { + case core.ServiceAffinityClientIP: + timeoutSeconds := int32(c.Rand.Intn(int(core.MaxClientIPServiceAffinitySeconds))) + ss.SessionAffinityConfig = &core.SessionAffinityConfig{ + ClientIP: &core.ClientIPConfig{ + TimeoutSeconds: &timeoutSeconds, + }, + } + case core.ServiceAffinityNone: + ss.SessionAffinityConfig = nil + } + }, + func(n *core.Node, c fuzz.Continue) { + c.FuzzNoCustom(n) + n.Spec.ExternalID = "external" + }, + func(s *core.NodeStatus, c fuzz.Continue) { + c.FuzzNoCustom(s) + s.Allocatable = s.Capacity + }, + func(e *core.Event, c fuzz.Continue) { + c.FuzzNoCustom(e) + e.EventTime = metav1.MicroTime{Time: time.Unix(1, 1000)} + if e.Series != nil { + e.Series.LastObservedTime = metav1.MicroTime{Time: time.Unix(3, 3000)} + } + }, + } +} diff --git a/pkg/apis/core/helper/BUILD b/pkg/apis/core/helper/BUILD new file mode 100644 index 00000000000..7f4ec626543 --- /dev/null +++ b/pkg/apis/core/helper/BUILD @@ -0,0 +1,51 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_test( + name = "go_default_test", + srcs = ["helpers_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/apis/core/helper", + deps = [ + "//pkg/apis/core:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + ], +) + +go_library( + name = "go_default_library", + srcs = ["helpers.go"], + importpath = "k8s.io/kubernetes/pkg/apis/core/helper", + deps = [ + "//pkg/apis/core:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/selection:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//pkg/apis/core/helper/qos:all-srcs", + ], + tags = ["automanaged"], +) diff --git a/pkg/apis/core/helper/helpers.go b/pkg/apis/core/helper/helpers.go new file mode 100644 index 00000000000..d0c004cf997 --- /dev/null +++ b/pkg/apis/core/helper/helpers.go @@ -0,0 +1,585 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES 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 ( + "encoding/json" + "fmt" + "strings" + + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/kubernetes/pkg/apis/core" +) + +// IsHugePageResourceName returns true if the resource name has the huge page +// resource prefix. +func IsHugePageResourceName(name core.ResourceName) bool { + return strings.HasPrefix(string(name), core.ResourceHugePagesPrefix) +} + +// IsQuotaHugePageResourceName returns true if the resource name has the quota +// related huge page resource prefix. +func IsQuotaHugePageResourceName(name core.ResourceName) bool { + return strings.HasPrefix(string(name), core.ResourceHugePagesPrefix) || strings.HasPrefix(string(name), core.ResourceRequestsHugePagesPrefix) +} + +// HugePageResourceName returns a ResourceName with the canonical hugepage +// prefix prepended for the specified page size. The page size is converted +// to its canonical representation. +func HugePageResourceName(pageSize resource.Quantity) core.ResourceName { + return core.ResourceName(fmt.Sprintf("%s%s", core.ResourceHugePagesPrefix, pageSize.String())) +} + +// HugePageSizeFromResourceName returns the page size for the specified huge page +// resource name. If the specified input is not a valid huge page resource name +// an error is returned. +func HugePageSizeFromResourceName(name core.ResourceName) (resource.Quantity, error) { + if !IsHugePageResourceName(name) { + return resource.Quantity{}, fmt.Errorf("resource name: %s is not valid hugepage name", name) + } + pageSize := strings.TrimPrefix(string(name), core.ResourceHugePagesPrefix) + return resource.ParseQuantity(pageSize) +} + +// NonConvertibleFields iterates over the provided map and filters out all but +// any keys with the "non-convertible.kubernetes.io" prefix. +func NonConvertibleFields(annotations map[string]string) map[string]string { + nonConvertibleKeys := map[string]string{} + for key, value := range annotations { + if strings.HasPrefix(key, core.NonConvertibleAnnotationPrefix) { + nonConvertibleKeys[key] = value + } + } + return nonConvertibleKeys +} + +// Semantic can do semantic deep equality checks for core objects. +// Example: apiequality.Semantic.DeepEqual(aPod, aPodWithNonNilButEmptyMaps) == true +var Semantic = conversion.EqualitiesOrDie( + func(a, b resource.Quantity) bool { + // Ignore formatting, only care that numeric value stayed the same. + // TODO: if we decide it's important, it should be safe to start comparing the format. + // + // Uninitialized quantities are equivalent to 0 quantities. + return a.Cmp(b) == 0 + }, + func(a, b metav1.MicroTime) bool { + return a.UTC() == b.UTC() + }, + func(a, b metav1.Time) bool { + return a.UTC() == b.UTC() + }, + func(a, b labels.Selector) bool { + return a.String() == b.String() + }, + func(a, b fields.Selector) bool { + return a.String() == b.String() + }, +) + +var standardResourceQuotaScopes = sets.NewString( + string(core.ResourceQuotaScopeTerminating), + string(core.ResourceQuotaScopeNotTerminating), + string(core.ResourceQuotaScopeBestEffort), + string(core.ResourceQuotaScopeNotBestEffort), +) + +// IsStandardResourceQuotaScope returns true if the scope is a standard value +func IsStandardResourceQuotaScope(str string) bool { + return standardResourceQuotaScopes.Has(str) +} + +var podObjectCountQuotaResources = sets.NewString( + string(core.ResourcePods), +) + +var podComputeQuotaResources = sets.NewString( + string(core.ResourceCPU), + string(core.ResourceMemory), + string(core.ResourceLimitsCPU), + string(core.ResourceLimitsMemory), + string(core.ResourceRequestsCPU), + string(core.ResourceRequestsMemory), +) + +// IsResourceQuotaScopeValidForResource returns true if the resource applies to the specified scope +func IsResourceQuotaScopeValidForResource(scope core.ResourceQuotaScope, resource string) bool { + switch scope { + case core.ResourceQuotaScopeTerminating, core.ResourceQuotaScopeNotTerminating, core.ResourceQuotaScopeNotBestEffort: + return podObjectCountQuotaResources.Has(resource) || podComputeQuotaResources.Has(resource) + case core.ResourceQuotaScopeBestEffort: + return podObjectCountQuotaResources.Has(resource) + default: + return true + } +} + +var standardContainerResources = sets.NewString( + string(core.ResourceCPU), + string(core.ResourceMemory), + string(core.ResourceEphemeralStorage), +) + +// IsStandardContainerResourceName returns true if the container can make a resource request +// for the specified resource +func IsStandardContainerResourceName(str string) bool { + return standardContainerResources.Has(str) || IsHugePageResourceName(core.ResourceName(str)) +} + +// IsExtendedResourceName returns true if the resource name is not in the +// default namespace. +func IsExtendedResourceName(name core.ResourceName) bool { + return !IsDefaultNamespaceResource(name) +} + +// IsDefaultNamespaceResource returns true if the resource name is in the +// *kubernetes.io/ namespace. Partially-qualified (unprefixed) names are +// implicitly in the kubernetes.io/ namespace. +func IsDefaultNamespaceResource(name core.ResourceName) bool { + return !strings.Contains(string(name), "/") || + strings.Contains(string(name), core.ResourceDefaultNamespacePrefix) +} + +var overcommitBlacklist = sets.NewString(string(core.ResourceNvidiaGPU)) + +// IsOvercommitAllowed returns true if the resource is in the default +// namespace and not blacklisted. +func IsOvercommitAllowed(name core.ResourceName) bool { + return IsDefaultNamespaceResource(name) && + !IsHugePageResourceName(name) && + !overcommitBlacklist.Has(string(name)) +} + +var standardLimitRangeTypes = sets.NewString( + string(core.LimitTypePod), + string(core.LimitTypeContainer), + string(core.LimitTypePersistentVolumeClaim), +) + +// IsStandardLimitRangeType returns true if the type is Pod or Container +func IsStandardLimitRangeType(str string) bool { + return standardLimitRangeTypes.Has(str) +} + +var standardQuotaResources = sets.NewString( + string(core.ResourceCPU), + string(core.ResourceMemory), + string(core.ResourceEphemeralStorage), + string(core.ResourceRequestsCPU), + string(core.ResourceRequestsMemory), + string(core.ResourceRequestsStorage), + string(core.ResourceRequestsEphemeralStorage), + string(core.ResourceLimitsCPU), + string(core.ResourceLimitsMemory), + string(core.ResourceLimitsEphemeralStorage), + string(core.ResourcePods), + string(core.ResourceQuotas), + string(core.ResourceServices), + string(core.ResourceReplicationControllers), + string(core.ResourceSecrets), + string(core.ResourcePersistentVolumeClaims), + string(core.ResourceConfigMaps), + string(core.ResourceServicesNodePorts), + string(core.ResourceServicesLoadBalancers), +) + +// IsStandardQuotaResourceName returns true if the resource is known to +// the quota tracking system +func IsStandardQuotaResourceName(str string) bool { + return standardQuotaResources.Has(str) || IsQuotaHugePageResourceName(core.ResourceName(str)) +} + +var standardResources = sets.NewString( + string(core.ResourceCPU), + string(core.ResourceMemory), + string(core.ResourceEphemeralStorage), + string(core.ResourceRequestsCPU), + string(core.ResourceRequestsMemory), + string(core.ResourceRequestsEphemeralStorage), + string(core.ResourceLimitsCPU), + string(core.ResourceLimitsMemory), + string(core.ResourceLimitsEphemeralStorage), + string(core.ResourcePods), + string(core.ResourceQuotas), + string(core.ResourceServices), + string(core.ResourceReplicationControllers), + string(core.ResourceSecrets), + string(core.ResourceConfigMaps), + string(core.ResourcePersistentVolumeClaims), + string(core.ResourceStorage), + string(core.ResourceRequestsStorage), + string(core.ResourceServicesNodePorts), + string(core.ResourceServicesLoadBalancers), +) + +// IsStandardResourceName returns true if the resource is known to the system +func IsStandardResourceName(str string) bool { + return standardResources.Has(str) || IsQuotaHugePageResourceName(core.ResourceName(str)) +} + +var integerResources = sets.NewString( + string(core.ResourcePods), + string(core.ResourceQuotas), + string(core.ResourceServices), + string(core.ResourceReplicationControllers), + string(core.ResourceSecrets), + string(core.ResourceConfigMaps), + string(core.ResourcePersistentVolumeClaims), + string(core.ResourceServicesNodePorts), + string(core.ResourceServicesLoadBalancers), +) + +// IsIntegerResourceName returns true if the resource is measured in integer values +func IsIntegerResourceName(str string) bool { + return integerResources.Has(str) || IsExtendedResourceName(core.ResourceName(str)) +} + +// this function aims to check if the service's ClusterIP is set or not +// the objective is not to perform validation here +func IsServiceIPSet(service *core.Service) bool { + return service.Spec.ClusterIP != core.ClusterIPNone && service.Spec.ClusterIP != "" +} + +var standardFinalizers = sets.NewString( + string(core.FinalizerKubernetes), + metav1.FinalizerOrphanDependents, + metav1.FinalizerDeleteDependents, +) + +func IsStandardFinalizerName(str string) bool { + return standardFinalizers.Has(str) +} + +// AddToNodeAddresses appends the NodeAddresses to the passed-by-pointer slice, +// only if they do not already exist +func AddToNodeAddresses(addresses *[]core.NodeAddress, addAddresses ...core.NodeAddress) { + for _, add := range addAddresses { + exists := false + for _, existing := range *addresses { + if existing.Address == add.Address && existing.Type == add.Type { + exists = true + break + } + } + if !exists { + *addresses = append(*addresses, add) + } + } +} + +// TODO: make method on LoadBalancerStatus? +func LoadBalancerStatusEqual(l, r *core.LoadBalancerStatus) bool { + return ingressSliceEqual(l.Ingress, r.Ingress) +} + +func ingressSliceEqual(lhs, rhs []core.LoadBalancerIngress) bool { + if len(lhs) != len(rhs) { + return false + } + for i := range lhs { + if !ingressEqual(&lhs[i], &rhs[i]) { + return false + } + } + return true +} + +func ingressEqual(lhs, rhs *core.LoadBalancerIngress) bool { + if lhs.IP != rhs.IP { + return false + } + if lhs.Hostname != rhs.Hostname { + return false + } + return true +} + +// TODO: make method on LoadBalancerStatus? +func LoadBalancerStatusDeepCopy(lb *core.LoadBalancerStatus) *core.LoadBalancerStatus { + c := &core.LoadBalancerStatus{} + c.Ingress = make([]core.LoadBalancerIngress, len(lb.Ingress)) + for i := range lb.Ingress { + c.Ingress[i] = lb.Ingress[i] + } + return c +} + +// GetAccessModesAsString returns a string representation of an array of access modes. +// modes, when present, are always in the same order: RWO,ROX,RWX. +func GetAccessModesAsString(modes []core.PersistentVolumeAccessMode) string { + modes = removeDuplicateAccessModes(modes) + modesStr := []string{} + if containsAccessMode(modes, core.ReadWriteOnce) { + modesStr = append(modesStr, "RWO") + } + if containsAccessMode(modes, core.ReadOnlyMany) { + modesStr = append(modesStr, "ROX") + } + if containsAccessMode(modes, core.ReadWriteMany) { + modesStr = append(modesStr, "RWX") + } + return strings.Join(modesStr, ",") +} + +// GetAccessModesAsString returns an array of AccessModes from a string created by GetAccessModesAsString +func GetAccessModesFromString(modes string) []core.PersistentVolumeAccessMode { + strmodes := strings.Split(modes, ",") + accessModes := []core.PersistentVolumeAccessMode{} + for _, s := range strmodes { + s = strings.Trim(s, " ") + switch { + case s == "RWO": + accessModes = append(accessModes, core.ReadWriteOnce) + case s == "ROX": + accessModes = append(accessModes, core.ReadOnlyMany) + case s == "RWX": + accessModes = append(accessModes, core.ReadWriteMany) + } + } + return accessModes +} + +// removeDuplicateAccessModes returns an array of access modes without any duplicates +func removeDuplicateAccessModes(modes []core.PersistentVolumeAccessMode) []core.PersistentVolumeAccessMode { + accessModes := []core.PersistentVolumeAccessMode{} + for _, m := range modes { + if !containsAccessMode(accessModes, m) { + accessModes = append(accessModes, m) + } + } + return accessModes +} + +func containsAccessMode(modes []core.PersistentVolumeAccessMode, mode core.PersistentVolumeAccessMode) bool { + for _, m := range modes { + if m == mode { + return true + } + } + return false +} + +// NodeSelectorRequirementsAsSelector converts the []NodeSelectorRequirement core type into a struct that implements +// labels.Selector. +func NodeSelectorRequirementsAsSelector(nsm []core.NodeSelectorRequirement) (labels.Selector, error) { + if len(nsm) == 0 { + return labels.Nothing(), nil + } + selector := labels.NewSelector() + for _, expr := range nsm { + var op selection.Operator + switch expr.Operator { + case core.NodeSelectorOpIn: + op = selection.In + case core.NodeSelectorOpNotIn: + op = selection.NotIn + case core.NodeSelectorOpExists: + op = selection.Exists + case core.NodeSelectorOpDoesNotExist: + op = selection.DoesNotExist + case core.NodeSelectorOpGt: + op = selection.GreaterThan + case core.NodeSelectorOpLt: + op = selection.LessThan + default: + return nil, fmt.Errorf("%q is not a valid node selector operator", expr.Operator) + } + r, err := labels.NewRequirement(expr.Key, op, expr.Values) + if err != nil { + return nil, err + } + selector = selector.Add(*r) + } + return selector, nil +} + +// GetTolerationsFromPodAnnotations gets the json serialized tolerations data from Pod.Annotations +// and converts it to the []Toleration type in core. +func GetTolerationsFromPodAnnotations(annotations map[string]string) ([]core.Toleration, error) { + var tolerations []core.Toleration + if len(annotations) > 0 && annotations[core.TolerationsAnnotationKey] != "" { + err := json.Unmarshal([]byte(annotations[core.TolerationsAnnotationKey]), &tolerations) + if err != nil { + return tolerations, err + } + } + return tolerations, nil +} + +// AddOrUpdateTolerationInPod tries to add a toleration to the pod's toleration list. +// Returns true if something was updated, false otherwise. +func AddOrUpdateTolerationInPod(pod *core.Pod, toleration *core.Toleration) bool { + podTolerations := pod.Spec.Tolerations + + var newTolerations []core.Toleration + updated := false + for i := range podTolerations { + if toleration.MatchToleration(&podTolerations[i]) { + if Semantic.DeepEqual(toleration, podTolerations[i]) { + return false + } + newTolerations = append(newTolerations, *toleration) + updated = true + continue + } + + newTolerations = append(newTolerations, podTolerations[i]) + } + + if !updated { + newTolerations = append(newTolerations, *toleration) + } + + pod.Spec.Tolerations = newTolerations + return true +} + +// GetTaintsFromNodeAnnotations gets the json serialized taints data from Pod.Annotations +// and converts it to the []Taint type in core. +func GetTaintsFromNodeAnnotations(annotations map[string]string) ([]core.Taint, error) { + var taints []core.Taint + if len(annotations) > 0 && annotations[core.TaintsAnnotationKey] != "" { + err := json.Unmarshal([]byte(annotations[core.TaintsAnnotationKey]), &taints) + if err != nil { + return []core.Taint{}, err + } + } + return taints, nil +} + +// SysctlsFromPodAnnotations parses the sysctl annotations into a slice of safe Sysctls +// and a slice of unsafe Sysctls. This is only a convenience wrapper around +// SysctlsFromPodAnnotation. +func SysctlsFromPodAnnotations(a map[string]string) ([]core.Sysctl, []core.Sysctl, error) { + safe, err := SysctlsFromPodAnnotation(a[core.SysctlsPodAnnotationKey]) + if err != nil { + return nil, nil, err + } + unsafe, err := SysctlsFromPodAnnotation(a[core.UnsafeSysctlsPodAnnotationKey]) + if err != nil { + return nil, nil, err + } + + return safe, unsafe, nil +} + +// SysctlsFromPodAnnotation parses an annotation value into a slice of Sysctls. +func SysctlsFromPodAnnotation(annotation string) ([]core.Sysctl, error) { + if len(annotation) == 0 { + return nil, nil + } + + kvs := strings.Split(annotation, ",") + sysctls := make([]core.Sysctl, len(kvs)) + for i, kv := range kvs { + cs := strings.Split(kv, "=") + if len(cs) != 2 || len(cs[0]) == 0 { + return nil, fmt.Errorf("sysctl %q not of the format sysctl_name=value", kv) + } + sysctls[i].Name = cs[0] + sysctls[i].Value = cs[1] + } + return sysctls, nil +} + +// PodAnnotationsFromSysctls creates an annotation value for a slice of Sysctls. +func PodAnnotationsFromSysctls(sysctls []core.Sysctl) string { + if len(sysctls) == 0 { + return "" + } + + kvs := make([]string, len(sysctls)) + for i := range sysctls { + kvs[i] = fmt.Sprintf("%s=%s", sysctls[i].Name, sysctls[i].Value) + } + return strings.Join(kvs, ",") +} + +// GetPersistentVolumeClass returns StorageClassName. +func GetPersistentVolumeClass(volume *core.PersistentVolume) string { + // Use beta annotation first + if class, found := volume.Annotations[core.BetaStorageClassAnnotation]; found { + return class + } + + return volume.Spec.StorageClassName +} + +// GetPersistentVolumeClaimClass returns StorageClassName. If no storage class was +// requested, it returns "". +func GetPersistentVolumeClaimClass(claim *core.PersistentVolumeClaim) string { + // Use beta annotation first + if class, found := claim.Annotations[core.BetaStorageClassAnnotation]; found { + return class + } + + if claim.Spec.StorageClassName != nil { + return *claim.Spec.StorageClassName + } + + return "" +} + +// PersistentVolumeClaimHasClass returns true if given claim has set StorageClassName field. +func PersistentVolumeClaimHasClass(claim *core.PersistentVolumeClaim) bool { + // Use beta annotation first + if _, found := claim.Annotations[core.BetaStorageClassAnnotation]; found { + return true + } + + if claim.Spec.StorageClassName != nil { + return true + } + + return false +} + +// GetStorageNodeAffinityFromAnnotation gets the json serialized data from PersistentVolume.Annotations +// and converts it to the NodeAffinity type in core. +// TODO: update when storage node affinity graduates to beta +func GetStorageNodeAffinityFromAnnotation(annotations map[string]string) (*core.NodeAffinity, error) { + if len(annotations) > 0 && annotations[core.AlphaStorageNodeAffinityAnnotation] != "" { + var affinity core.NodeAffinity + err := json.Unmarshal([]byte(annotations[core.AlphaStorageNodeAffinityAnnotation]), &affinity) + if err != nil { + return nil, err + } + return &affinity, nil + } + return nil, nil +} + +// Converts NodeAffinity type to Alpha annotation for use in PersistentVolumes +// TODO: update when storage node affinity graduates to beta +func StorageNodeAffinityToAlphaAnnotation(annotations map[string]string, affinity *core.NodeAffinity) error { + if affinity == nil { + return nil + } + + json, err := json.Marshal(*affinity) + if err != nil { + return err + } + annotations[core.AlphaStorageNodeAffinityAnnotation] = string(json) + return nil +} diff --git a/pkg/apis/core/helper/helpers_test.go b/pkg/apis/core/helper/helpers_test.go new file mode 100644 index 00000000000..03e01f8083e --- /dev/null +++ b/pkg/apis/core/helper/helpers_test.go @@ -0,0 +1,491 @@ +/* +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 helper + +import ( + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/kubernetes/pkg/apis/core" +) + +func TestSemantic(t *testing.T) { + table := []struct { + a, b interface{} + shouldEqual bool + }{ + {resource.MustParse("0"), resource.Quantity{}, true}, + {resource.Quantity{}, resource.MustParse("0"), true}, + {resource.Quantity{}, resource.MustParse("1m"), false}, + { + resource.NewQuantity(5, resource.BinarySI), + resource.NewQuantity(5, resource.DecimalSI), + true, + }, + {resource.MustParse("2m"), resource.MustParse("1m"), false}, + } + + for index, item := range table { + if e, a := item.shouldEqual, Semantic.DeepEqual(item.a, item.b); e != a { + t.Errorf("case[%d], expected %v, got %v.", index, e, a) + } + } +} + +func TestIsStandardResource(t *testing.T) { + testCases := []struct { + input string + output bool + }{ + {"cpu", true}, + {"memory", true}, + {"disk", false}, + {"blah", false}, + {"x.y.z", false}, + {"hugepages-2Mi", true}, + {"requests.hugepages-2Mi", true}, + } + for i, tc := range testCases { + if IsStandardResourceName(tc.input) != tc.output { + t.Errorf("case[%d], input: %s, expected: %t, got: %t", i, tc.input, tc.output, !tc.output) + } + } +} + +func TestIsStandardContainerResource(t *testing.T) { + testCases := []struct { + input string + output bool + }{ + {"cpu", true}, + {"memory", true}, + {"disk", false}, + {"hugepages-2Mi", true}, + } + for i, tc := range testCases { + if IsStandardContainerResourceName(tc.input) != tc.output { + t.Errorf("case[%d], input: %s, expected: %t, got: %t", i, tc.input, tc.output, !tc.output) + } + } +} + +func TestAddToNodeAddresses(t *testing.T) { + testCases := []struct { + existing []core.NodeAddress + toAdd []core.NodeAddress + expected []core.NodeAddress + }{ + { + existing: []core.NodeAddress{}, + toAdd: []core.NodeAddress{}, + expected: []core.NodeAddress{}, + }, + { + existing: []core.NodeAddress{}, + toAdd: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "1.1.1.1"}, + {Type: core.NodeHostName, Address: "localhost"}, + }, + expected: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "1.1.1.1"}, + {Type: core.NodeHostName, Address: "localhost"}, + }, + }, + { + existing: []core.NodeAddress{}, + toAdd: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "1.1.1.1"}, + {Type: core.NodeExternalIP, Address: "1.1.1.1"}, + }, + expected: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "1.1.1.1"}, + }, + }, + { + existing: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "1.1.1.1"}, + {Type: core.NodeInternalIP, Address: "10.1.1.1"}, + }, + toAdd: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "1.1.1.1"}, + {Type: core.NodeHostName, Address: "localhost"}, + }, + expected: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "1.1.1.1"}, + {Type: core.NodeInternalIP, Address: "10.1.1.1"}, + {Type: core.NodeHostName, Address: "localhost"}, + }, + }, + } + + for i, tc := range testCases { + AddToNodeAddresses(&tc.existing, tc.toAdd...) + if !Semantic.DeepEqual(tc.expected, tc.existing) { + t.Errorf("case[%d], expected: %v, got: %v", i, tc.expected, tc.existing) + } + } +} + +func TestGetAccessModesFromString(t *testing.T) { + modes := GetAccessModesFromString("ROX") + if !containsAccessMode(modes, core.ReadOnlyMany) { + t.Errorf("Expected mode %s, but got %+v", core.ReadOnlyMany, modes) + } + + modes = GetAccessModesFromString("ROX,RWX") + if !containsAccessMode(modes, core.ReadOnlyMany) { + t.Errorf("Expected mode %s, but got %+v", core.ReadOnlyMany, modes) + } + if !containsAccessMode(modes, core.ReadWriteMany) { + t.Errorf("Expected mode %s, but got %+v", core.ReadWriteMany, modes) + } + + modes = GetAccessModesFromString("RWO,ROX,RWX") + if !containsAccessMode(modes, core.ReadOnlyMany) { + t.Errorf("Expected mode %s, but got %+v", core.ReadOnlyMany, modes) + } + if !containsAccessMode(modes, core.ReadWriteMany) { + t.Errorf("Expected mode %s, but got %+v", core.ReadWriteMany, modes) + } +} + +func TestRemoveDuplicateAccessModes(t *testing.T) { + modes := []core.PersistentVolumeAccessMode{ + core.ReadWriteOnce, core.ReadOnlyMany, core.ReadOnlyMany, core.ReadOnlyMany, + } + modes = removeDuplicateAccessModes(modes) + if len(modes) != 2 { + t.Errorf("Expected 2 distinct modes in set but found %v", len(modes)) + } +} + +func TestNodeSelectorRequirementsAsSelector(t *testing.T) { + matchExpressions := []core.NodeSelectorRequirement{{ + Key: "foo", + Operator: core.NodeSelectorOpIn, + Values: []string{"bar", "baz"}, + }} + mustParse := func(s string) labels.Selector { + out, e := labels.Parse(s) + if e != nil { + panic(e) + } + return out + } + tc := []struct { + in []core.NodeSelectorRequirement + out labels.Selector + expectErr bool + }{ + {in: nil, out: labels.Nothing()}, + {in: []core.NodeSelectorRequirement{}, out: labels.Nothing()}, + { + in: matchExpressions, + out: mustParse("foo in (baz,bar)"), + }, + { + in: []core.NodeSelectorRequirement{{ + Key: "foo", + Operator: core.NodeSelectorOpExists, + Values: []string{"bar", "baz"}, + }}, + expectErr: true, + }, + { + in: []core.NodeSelectorRequirement{{ + Key: "foo", + Operator: core.NodeSelectorOpGt, + Values: []string{"1"}, + }}, + out: mustParse("foo>1"), + }, + { + in: []core.NodeSelectorRequirement{{ + Key: "bar", + Operator: core.NodeSelectorOpLt, + Values: []string{"7"}, + }}, + out: mustParse("bar<7"), + }, + } + + for i, tc := range tc { + out, err := NodeSelectorRequirementsAsSelector(tc.in) + if err == nil && tc.expectErr { + t.Errorf("[%v]expected error but got none.", i) + } + if err != nil && !tc.expectErr { + t.Errorf("[%v]did not expect error but got: %v", i, err) + } + if !reflect.DeepEqual(out, tc.out) { + t.Errorf("[%v]expected:\n\t%+v\nbut got:\n\t%+v", i, tc.out, out) + } + } +} + +func TestSysctlsFromPodAnnotation(t *testing.T) { + type Test struct { + annotation string + expectValue []core.Sysctl + expectErr bool + } + for i, test := range []Test{ + { + annotation: "", + expectValue: nil, + }, + { + annotation: "foo.bar", + expectErr: true, + }, + { + annotation: "=123", + expectErr: true, + }, + { + annotation: "foo.bar=", + expectValue: []core.Sysctl{{Name: "foo.bar", Value: ""}}, + }, + { + annotation: "foo.bar=42", + expectValue: []core.Sysctl{{Name: "foo.bar", Value: "42"}}, + }, + { + annotation: "foo.bar=42,", + expectErr: true, + }, + { + annotation: "foo.bar=42,abc.def=1", + expectValue: []core.Sysctl{{Name: "foo.bar", Value: "42"}, {Name: "abc.def", Value: "1"}}, + }, + } { + sysctls, err := SysctlsFromPodAnnotation(test.annotation) + if test.expectErr && err == nil { + t.Errorf("[%v]expected error but got none", i) + } else if !test.expectErr && err != nil { + t.Errorf("[%v]did not expect error but got: %v", i, err) + } else if !reflect.DeepEqual(sysctls, test.expectValue) { + t.Errorf("[%v]expect value %v but got %v", i, test.expectValue, sysctls) + } + } +} + +// TODO: remove when alpha support for topology constraints is removed +func TestGetNodeAffinityFromAnnotations(t *testing.T) { + testCases := []struct { + annotations map[string]string + expectErr bool + }{ + { + annotations: nil, + expectErr: false, + }, + { + annotations: map[string]string{}, + expectErr: false, + }, + { + annotations: map[string]string{ + core.AlphaStorageNodeAffinityAnnotation: `{ + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { "matchExpressions": [ + { "key": "test-key1", + "operator": "In", + "values": ["test-value1", "test-value2"] + }, + { "key": "test-key2", + "operator": "In", + "values": ["test-value1", "test-value2"] + } + ]} + ]} + }`, + }, + expectErr: false, + }, + { + annotations: map[string]string{ + core.AlphaStorageNodeAffinityAnnotation: `[{ + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { "matchExpressions": [ + { "key": "test-key1", + "operator": "In", + "values": ["test-value1", "test-value2"] + }, + { "key": "test-key2", + "operator": "In", + "values": ["test-value1", "test-value2"] + } + ]} + ]} + }]`, + }, + expectErr: true, + }, + { + annotations: map[string]string{ + core.AlphaStorageNodeAffinityAnnotation: `{ + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": + "matchExpressions": [ + { "key": "test-key1", + "operator": "In", + "values": ["test-value1", "test-value2"] + }, + { "key": "test-key2", + "operator": "In", + "values": ["test-value1", "test-value2"] + } + ]} + } + }`, + }, + expectErr: true, + }, + } + + for i, tc := range testCases { + _, err := GetStorageNodeAffinityFromAnnotation(tc.annotations) + if err == nil && tc.expectErr { + t.Errorf("[%v]expected error but got none.", i) + } + if err != nil && !tc.expectErr { + t.Errorf("[%v]did not expect error but got: %v", i, err) + } + } +} + +func TestIsHugePageResourceName(t *testing.T) { + testCases := []struct { + name core.ResourceName + result bool + }{ + { + name: core.ResourceName("hugepages-2Mi"), + result: true, + }, + { + name: core.ResourceName("hugepages-1Gi"), + result: true, + }, + { + name: core.ResourceName("cpu"), + result: false, + }, + { + name: core.ResourceName("memory"), + result: false, + }, + } + for _, testCase := range testCases { + if testCase.result != IsHugePageResourceName(testCase.name) { + t.Errorf("resource: %v expected result: %v", testCase.name, testCase.result) + } + } +} + +func TestHugePageResourceName(t *testing.T) { + testCases := []struct { + pageSize resource.Quantity + name core.ResourceName + }{ + { + pageSize: resource.MustParse("2Mi"), + name: core.ResourceName("hugepages-2Mi"), + }, + { + pageSize: resource.MustParse("1Gi"), + name: core.ResourceName("hugepages-1Gi"), + }, + { + // verify we do not regress our canonical representation + pageSize: *resource.NewQuantity(int64(2097152), resource.BinarySI), + name: core.ResourceName("hugepages-2Mi"), + }, + } + for _, testCase := range testCases { + if result := HugePageResourceName(testCase.pageSize); result != testCase.name { + t.Errorf("pageSize: %v, expected: %v, but got: %v", testCase.pageSize.String(), testCase.name, result.String()) + } + } +} + +func TestHugePageSizeFromResourceName(t *testing.T) { + testCases := []struct { + name core.ResourceName + expectErr bool + pageSize resource.Quantity + }{ + { + name: core.ResourceName("hugepages-2Mi"), + pageSize: resource.MustParse("2Mi"), + expectErr: false, + }, + { + name: core.ResourceName("hugepages-1Gi"), + pageSize: resource.MustParse("1Gi"), + expectErr: false, + }, + { + name: core.ResourceName("hugepages-bad"), + expectErr: true, + }, + } + for _, testCase := range testCases { + value, err := HugePageSizeFromResourceName(testCase.name) + if testCase.expectErr && err == nil { + t.Errorf("Expected an error for %v", testCase.name) + } else if !testCase.expectErr && err != nil { + t.Errorf("Unexpected error for %v, got %v", testCase.name, err) + } else if testCase.pageSize.Value() != value.Value() { + t.Errorf("Unexpected pageSize for resource %v got %v", testCase.name, value.String()) + } + } +} + +func TestIsOvercommitAllowed(t *testing.T) { + testCases := []struct { + name core.ResourceName + allowed bool + }{ + { + name: core.ResourceCPU, + allowed: true, + }, + { + name: core.ResourceMemory, + allowed: true, + }, + { + name: core.ResourceNvidiaGPU, + allowed: false, + }, + { + name: HugePageResourceName(resource.MustParse("2Mi")), + allowed: false, + }, + } + for _, testCase := range testCases { + if testCase.allowed != IsOvercommitAllowed(testCase.name) { + t.Errorf("Unexpected result for %v", testCase.name) + } + } +} diff --git a/pkg/apis/core/helper/qos/BUILD b/pkg/apis/core/helper/qos/BUILD new file mode 100644 index 00000000000..c2a1e9010b8 --- /dev/null +++ b/pkg/apis/core/helper/qos/BUILD @@ -0,0 +1,30 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = ["qos.go"], + importpath = "k8s.io/kubernetes/pkg/apis/core/helper/qos", + deps = [ + "//pkg/apis/core:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/pkg/apis/core/helper/qos/qos.go b/pkg/apis/core/helper/qos/qos.go new file mode 100644 index 00000000000..fad6fb24074 --- /dev/null +++ b/pkg/apis/core/helper/qos/qos.go @@ -0,0 +1,97 @@ +/* +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. +*/ + +// NOTE: DO NOT use those helper functions through client-go, the +// package path will be changed in the future. +package qos + +import ( + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/kubernetes/pkg/apis/core" +) + +var supportedQoSComputeResources = sets.NewString(string(core.ResourceCPU), string(core.ResourceMemory)) + +func isSupportedQoSComputeResource(name core.ResourceName) bool { + return supportedQoSComputeResources.Has(string(name)) +} + +// GetPodQOS returns the QoS class of a pod. +// A pod is besteffort if none of its containers have specified any requests or limits. +// A pod is guaranteed only when requests and limits are specified for all the containers and they are equal. +// A pod is burstable if limits and requests do not match across all containers. +func GetPodQOS(pod *core.Pod) core.PodQOSClass { + requests := core.ResourceList{} + limits := core.ResourceList{} + zeroQuantity := resource.MustParse("0") + isGuaranteed := true + for _, container := range pod.Spec.Containers { + // process requests + for name, quantity := range container.Resources.Requests { + if !isSupportedQoSComputeResource(name) { + continue + } + if quantity.Cmp(zeroQuantity) == 1 { + delta := quantity.Copy() + if _, exists := requests[name]; !exists { + requests[name] = *delta + } else { + delta.Add(requests[name]) + requests[name] = *delta + } + } + } + // process limits + qosLimitsFound := sets.NewString() + for name, quantity := range container.Resources.Limits { + if !isSupportedQoSComputeResource(name) { + continue + } + if quantity.Cmp(zeroQuantity) == 1 { + qosLimitsFound.Insert(string(name)) + delta := quantity.Copy() + if _, exists := limits[name]; !exists { + limits[name] = *delta + } else { + delta.Add(limits[name]) + limits[name] = *delta + } + } + } + + if !qosLimitsFound.HasAll(string(core.ResourceMemory), string(core.ResourceCPU)) { + isGuaranteed = false + } + } + if len(requests) == 0 && len(limits) == 0 { + return core.PodQOSBestEffort + } + // Check is requests match limits for all resources. + if isGuaranteed { + for name, req := range requests { + if lim, exists := limits[name]; !exists || lim.Cmp(req) != 0 { + isGuaranteed = false + break + } + } + } + if isGuaranteed && + len(requests) == len(limits) { + return core.PodQOSGuaranteed + } + return core.PodQOSBurstable +} diff --git a/pkg/apis/core/install/BUILD b/pkg/apis/core/install/BUILD new file mode 100644 index 00000000000..1e760dbe8ea --- /dev/null +++ b/pkg/apis/core/install/BUILD @@ -0,0 +1,50 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_library( + name = "go_default_library", + srcs = ["install.go"], + importpath = "k8s.io/kubernetes/pkg/apis/core/install", + deps = [ + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["install_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/apis/core/install", + deps = [ + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/pkg/api/install/OWNERS b/pkg/apis/core/install/OWNERS similarity index 100% rename from pkg/api/install/OWNERS rename to pkg/apis/core/install/OWNERS diff --git a/pkg/apis/core/install/install.go b/pkg/apis/core/install/install.go new file mode 100644 index 00000000000..cae514ec7b0 --- /dev/null +++ b/pkg/apis/core/install/install.go @@ -0,0 +1,67 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES 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 v1 monolithic api, making it available as an +// option to all of the API encoding/decoding machinery. +package install + +import ( + "k8s.io/apimachinery/pkg/apimachinery/announced" + "k8s.io/apimachinery/pkg/apimachinery/registered" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/v1" +) + +func init() { + Install(legacyscheme.GroupFactoryRegistry, legacyscheme.Registry, legacyscheme.Scheme) +} + +// Install registers the API group and adds types to a scheme +func Install(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *registered.APIRegistrationManager, scheme *runtime.Scheme) { + if err := announced.NewGroupMetaFactory( + &announced.GroupMetaFactoryArgs{ + GroupName: core.GroupName, + VersionPreferenceOrder: []string{v1.SchemeGroupVersion.Version}, + AddInternalObjectsToScheme: core.AddToScheme, + RootScopedKinds: sets.NewString( + "Node", + "Namespace", + "PersistentVolume", + "ComponentStatus", + ), + IgnoredKinds: sets.NewString( + "ListOptions", + "DeleteOptions", + "Status", + "PodLogOptions", + "PodExecOptions", + "PodAttachOptions", + "PodPortForwardOptions", + "PodProxyOptions", + "NodeProxyOptions", + "ServiceProxyOptions", + ), + }, + announced.VersionToSchemeFunc{ + v1.SchemeGroupVersion.Version: v1.AddToScheme, + }, + ).Announce(groupFactoryRegistry).RegisterAndEnable(registry, scheme); err != nil { + panic(err) + } +} diff --git a/pkg/apis/core/install/install_test.go b/pkg/apis/core/install/install_test.go new file mode 100644 index 00000000000..b580c56c4fe --- /dev/null +++ b/pkg/apis/core/install/install_test.go @@ -0,0 +1,140 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES 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 + +import ( + "encoding/json" + "reflect" + "testing" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kubernetes/pkg/api/legacyscheme" + internal "k8s.io/kubernetes/pkg/apis/core" +) + +func TestResourceVersioner(t *testing.T) { + g, err := legacyscheme.Registry.Group(v1.GroupName) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + intf, err := g.DefaultInterfacesFor(v1.SchemeGroupVersion) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + accessor := intf.MetadataAccessor + + pod := internal.Pod{ObjectMeta: metav1.ObjectMeta{ResourceVersion: "10"}} + version, err := accessor.ResourceVersion(&pod) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if version != "10" { + t.Errorf("unexpected version %v", version) + } + + podList := internal.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}} + version, err = accessor.ResourceVersion(&podList) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if version != "10" { + t.Errorf("unexpected version %v", version) + } +} + +func TestCodec(t *testing.T) { + pod := internal.Pod{} + // We do want to use package registered rather than testapi here, because we + // want to test if the package install and package registered work as expected. + data, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(legacyscheme.Registry.GroupOrDie(internal.GroupName).GroupVersion), &pod) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + other := internal.Pod{} + if err := json.Unmarshal(data, &other); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if other.APIVersion != legacyscheme.Registry.GroupOrDie(internal.GroupName).GroupVersion.Version || other.Kind != "Pod" { + t.Errorf("unexpected unmarshalled object %#v", other) + } +} + +func TestInterfacesFor(t *testing.T) { + if _, err := legacyscheme.Registry.GroupOrDie(internal.GroupName).InterfacesFor(internal.SchemeGroupVersion); err == nil { + t.Fatalf("unexpected non-error: %v", err) + } + for i, version := range legacyscheme.Registry.GroupOrDie(internal.GroupName).GroupVersions { + if vi, err := legacyscheme.Registry.GroupOrDie(internal.GroupName).InterfacesFor(version); err != nil || vi == nil { + t.Fatalf("%d: unexpected result: %v", i, err) + } + } +} + +func TestRESTMapper(t *testing.T) { + gv := schema.GroupVersion{Group: "", Version: "v1"} + rcGVK := gv.WithKind("ReplicationController") + podTemplateGVK := gv.WithKind("PodTemplate") + + if gvk, err := legacyscheme.Registry.RESTMapper().KindFor(internal.SchemeGroupVersion.WithResource("replicationcontrollers")); err != nil || gvk != rcGVK { + t.Errorf("unexpected version mapping: %v %v", gvk, err) + } + + if m, err := legacyscheme.Registry.GroupOrDie(internal.GroupName).RESTMapper.RESTMapping(podTemplateGVK.GroupKind(), ""); err != nil || m.GroupVersionKind != podTemplateGVK || m.Resource != "podtemplates" { + t.Errorf("unexpected version mapping: %#v %v", m, err) + } + + for _, version := range legacyscheme.Registry.GroupOrDie(internal.GroupName).GroupVersions { + mapping, err := legacyscheme.Registry.GroupOrDie(internal.GroupName).RESTMapper.RESTMapping(rcGVK.GroupKind(), version.Version) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if mapping.Resource != "replicationControllers" && mapping.Resource != "replicationcontrollers" { + t.Errorf("incorrect resource name: %#v", mapping) + } + if mapping.GroupVersionKind.GroupVersion() != version { + t.Errorf("incorrect version: %v", mapping) + } + + interfaces, _ := legacyscheme.Registry.GroupOrDie(internal.GroupName).InterfacesFor(version) + if mapping.ObjectConvertor != interfaces.ObjectConvertor { + t.Errorf("unexpected: %#v, expected: %#v", mapping, interfaces) + } + + rc := &internal.ReplicationController{ObjectMeta: metav1.ObjectMeta{Name: "foo"}} + name, err := mapping.MetadataAccessor.Name(rc) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if name != "foo" { + t.Errorf("unable to retrieve object meta with: %v", mapping.MetadataAccessor) + } + } +} + +func TestUnversioned(t *testing.T) { + for _, obj := range []runtime.Object{ + &metav1.Status{}, + } { + if unversioned, ok := legacyscheme.Scheme.IsUnversioned(obj); !unversioned || !ok { + t.Errorf("%v is expected to be unversioned", reflect.TypeOf(obj)) + } + } +} diff --git a/pkg/api/json.go b/pkg/apis/core/json.go similarity index 98% rename from pkg/api/json.go rename to pkg/apis/core/json.go index 3a6e04c182f..937cd056c01 100644 --- a/pkg/api/json.go +++ b/pkg/apis/core/json.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package api +package core import "encoding/json" diff --git a/pkg/api/objectreference.go b/pkg/apis/core/objectreference.go similarity index 98% rename from pkg/api/objectreference.go rename to pkg/apis/core/objectreference.go index b36ecab27ff..55b27f30b6f 100644 --- a/pkg/api/objectreference.go +++ b/pkg/apis/core/objectreference.go @@ -17,7 +17,7 @@ limitations under the License. //TODO: consider making these methods functions, because we don't want helper //functions in the k8s.io/api repo. -package api +package core import ( "k8s.io/apimachinery/pkg/runtime/schema" diff --git a/pkg/apis/core/pods/BUILD b/pkg/apis/core/pods/BUILD new file mode 100644 index 00000000000..34e8b8f3644 --- /dev/null +++ b/pkg/apis/core/pods/BUILD @@ -0,0 +1,30 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["helpers.go"], + importpath = "k8s.io/kubernetes/pkg/apis/core/pods", + visibility = ["//visibility:public"], + deps = ["//pkg/fieldpath:go_default_library"], +) + +go_test( + name = "go_default_test", + srcs = ["helpers_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/apis/core/pods", +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/apis/core/pods/helpers.go b/pkg/apis/core/pods/helpers.go new file mode 100644 index 00000000000..cf199cee737 --- /dev/null +++ b/pkg/apis/core/pods/helpers.go @@ -0,0 +1,63 @@ +/* +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 pods + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/fieldpath" +) + +// ConvertDownwardAPIFieldLabel converts the specified downward API field label +// and its value in the pod of the specified version to the internal version, +// and returns the converted label and value. This function returns an error if +// the conversion fails. +func ConvertDownwardAPIFieldLabel(version, label, value string) (string, string, error) { + if version != "v1" { + return "", "", fmt.Errorf("unsupported pod version: %s", version) + } + + if path, _, ok := fieldpath.SplitMaybeSubscriptedPath(label); ok { + switch path { + case "metadata.annotations", "metadata.labels": + return label, value, nil + default: + return "", "", fmt.Errorf("field label does not support subscript: %s", label) + } + } + + switch label { + case "metadata.annotations", + "metadata.labels", + "metadata.name", + "metadata.namespace", + "metadata.uid", + "spec.nodeName", + "spec.restartPolicy", + "spec.serviceAccountName", + "spec.schedulerName", + "status.phase", + "status.hostIP", + "status.podIP": + return label, value, nil + // This is for backwards compatibility with old v1 clients which send spec.host + case "spec.host": + return "spec.nodeName", value, nil + default: + return "", "", fmt.Errorf("field label not supported: %s", label) + } +} diff --git a/pkg/apis/core/pods/helpers_test.go b/pkg/apis/core/pods/helpers_test.go new file mode 100644 index 00000000000..9db95a014ba --- /dev/null +++ b/pkg/apis/core/pods/helpers_test.go @@ -0,0 +1,87 @@ +/* +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 pods + +import ( + "testing" +) + +func TestConvertDownwardAPIFieldLabel(t *testing.T) { + testCases := []struct { + version string + label string + value string + expectedErr bool + expectedLabel string + expectedValue string + }{ + { + version: "v2", + label: "metadata.name", + value: "test-pod", + expectedErr: true, + }, + { + version: "v1", + label: "invalid-label", + value: "value", + expectedErr: true, + }, + { + version: "v1", + label: "metadata.name", + value: "test-pod", + expectedLabel: "metadata.name", + expectedValue: "test-pod", + }, + { + version: "v1", + label: "metadata.annotations", + value: "myValue", + expectedLabel: "metadata.annotations", + expectedValue: "myValue", + }, + { + version: "v1", + label: "metadata.annotations['myKey']", + value: "myValue", + expectedLabel: "metadata.annotations['myKey']", + expectedValue: "myValue", + }, + { + version: "v1", + label: "spec.host", + value: "127.0.0.1", + expectedLabel: "spec.nodeName", + expectedValue: "127.0.0.1", + }, + } + for _, tc := range testCases { + label, value, err := ConvertDownwardAPIFieldLabel(tc.version, tc.label, tc.value) + if err != nil { + if tc.expectedErr { + continue + } + t.Errorf("ConvertDownwardAPIFieldLabel(%s, %s, %s) failed: %s", + tc.version, tc.label, tc.value, err) + } + if tc.expectedLabel != label || tc.expectedValue != value { + t.Errorf("ConvertDownwardAPIFieldLabel(%s, %s, %s) = (%s, %s, nil), expected (%s, %s, nil)", + tc.version, tc.label, tc.value, label, value, tc.expectedLabel, tc.expectedValue) + } + } +} diff --git a/pkg/apis/core/register.go b/pkg/apis/core/register.go new file mode 100644 index 00000000000..2784cbe157e --- /dev/null +++ b/pkg/apis/core/register.go @@ -0,0 +1,99 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package core + +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 = "" + +// 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() +} + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +func addKnownTypes(scheme *runtime.Scheme) error { + if err := scheme.AddIgnoredConversionType(&metav1.TypeMeta{}, &metav1.TypeMeta{}); err != nil { + return err + } + scheme.AddKnownTypes(SchemeGroupVersion, + &Pod{}, + &PodList{}, + &PodStatusResult{}, + &PodTemplate{}, + &PodTemplateList{}, + &ReplicationControllerList{}, + &ReplicationController{}, + &ServiceList{}, + &Service{}, + &ServiceProxyOptions{}, + &NodeList{}, + &Node{}, + &NodeConfigSource{}, + &NodeProxyOptions{}, + &Endpoints{}, + &EndpointsList{}, + &Binding{}, + &Event{}, + &EventList{}, + &List{}, + &LimitRange{}, + &LimitRangeList{}, + &ResourceQuota{}, + &ResourceQuotaList{}, + &Namespace{}, + &NamespaceList{}, + &ServiceAccount{}, + &ServiceAccountList{}, + &Secret{}, + &SecretList{}, + &PersistentVolume{}, + &PersistentVolumeList{}, + &PersistentVolumeClaim{}, + &PersistentVolumeClaimList{}, + &PodAttachOptions{}, + &PodLogOptions{}, + &PodExecOptions{}, + &PodPortForwardOptions{}, + &PodProxyOptions{}, + &ComponentStatus{}, + &ComponentStatusList{}, + &SerializedReference{}, + &RangeAllocation{}, + &ConfigMap{}, + &ConfigMapList{}, + ) + + return nil +} diff --git a/pkg/api/resource.go b/pkg/apis/core/resource.go similarity index 99% rename from pkg/api/resource.go rename to pkg/apis/core/resource.go index ce1747d8e6b..1910cd921d9 100644 --- a/pkg/api/resource.go +++ b/pkg/apis/core/resource.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package api +package core import ( "k8s.io/apimachinery/pkg/api/resource" diff --git a/pkg/api/taint.go b/pkg/apis/core/taint.go similarity index 98% rename from pkg/api/taint.go rename to pkg/apis/core/taint.go index 173e4e60161..ae1feb74d7e 100644 --- a/pkg/api/taint.go +++ b/pkg/apis/core/taint.go @@ -17,7 +17,7 @@ limitations under the License. //TODO: consider making these methods functions, because we don't want helper //functions in the k8s.io/api repo. -package api +package core import "fmt" diff --git a/pkg/api/taint_test.go b/pkg/apis/core/taint_test.go similarity index 99% rename from pkg/api/taint_test.go rename to pkg/apis/core/taint_test.go index a52582b3543..baa9404e084 100644 --- a/pkg/api/taint_test.go +++ b/pkg/apis/core/taint_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package api +package core import "testing" diff --git a/pkg/api/toleration.go b/pkg/apis/core/toleration.go similarity index 98% rename from pkg/api/toleration.go rename to pkg/apis/core/toleration.go index edb73b74e1a..1dfbc9f1bb2 100644 --- a/pkg/api/toleration.go +++ b/pkg/apis/core/toleration.go @@ -17,7 +17,7 @@ limitations under the License. //TODO: consider making these methods functions, because we don't want helper //functions in the k8s.io/api repo. -package api +package core // MatchToleration checks if the toleration matches tolerationToMatch. Tolerations are unique by , // if the two tolerations have same combination, regard as they match. diff --git a/pkg/apis/core/toleration_test.go b/pkg/apis/core/toleration_test.go new file mode 100644 index 00000000000..ec7a8dec13d --- /dev/null +++ b/pkg/apis/core/toleration_test.go @@ -0,0 +1,136 @@ +/* +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 core + +import "testing" + +func TestMatchToleration(t *testing.T) { + + tolerationSeconds := int64(5) + tolerationToMatchSeconds := int64(3) + testCases := []struct { + description string + toleration *Toleration + tolerationToMatch *Toleration + expectMatch bool + }{ + { + description: "two taints with the same key,operator,value,effect should match", + toleration: &Toleration{ + Key: "foo", + Operator: "Exists", + Value: "bar", + Effect: TaintEffectNoSchedule, + }, + tolerationToMatch: &Toleration{ + Key: "foo", + Operator: "Exists", + Value: "bar", + Effect: TaintEffectNoSchedule, + }, + expectMatch: true, + }, + { + description: "two taints with the different key cannot match", + toleration: &Toleration{ + Key: "foo", + Operator: "Exists", + Value: "bar", + Effect: TaintEffectNoSchedule, + }, + tolerationToMatch: &Toleration{ + Key: "different-key", + Operator: "Exists", + Value: "bar", + Effect: TaintEffectNoSchedule, + }, + expectMatch: false, + }, + { + description: "two taints with the different operator cannot match", + toleration: &Toleration{ + Key: "foo", + Operator: "Exists", + Value: "bar", + Effect: TaintEffectNoSchedule, + }, + tolerationToMatch: &Toleration{ + Key: "foo", + Operator: "different-operator", + Value: "bar", + Effect: TaintEffectNoSchedule, + }, + expectMatch: false, + }, + { + description: "two taints with the different value cannot match", + toleration: &Toleration{ + Key: "foo", + Operator: "Exists", + Value: "bar", + Effect: TaintEffectNoSchedule, + }, + tolerationToMatch: &Toleration{ + Key: "foo", + Operator: "Exists", + Value: "different-value", + Effect: TaintEffectNoSchedule, + }, + expectMatch: false, + }, + { + description: "two taints with the different effect cannot match", + toleration: &Toleration{ + Key: "foo", + Operator: "Exists", + Value: "bar", + Effect: TaintEffectNoSchedule, + }, + tolerationToMatch: &Toleration{ + Key: "foo", + Operator: "Exists", + Value: "bar", + Effect: TaintEffectPreferNoSchedule, + }, + expectMatch: false, + }, + { + description: "two taints with the different tolerationSeconds should match", + toleration: &Toleration{ + Key: "foo", + Operator: "Exists", + Value: "bar", + Effect: TaintEffectNoSchedule, + TolerationSeconds: &tolerationSeconds, + }, + tolerationToMatch: &Toleration{ + Key: "foo", + Operator: "Exists", + Value: "bar", + Effect: TaintEffectNoSchedule, + TolerationSeconds: &tolerationToMatchSeconds, + }, + expectMatch: true, + }, + } + + for _, tc := range testCases { + if actual := tc.toleration.MatchToleration(tc.tolerationToMatch); actual != tc.expectMatch { + t.Errorf("[%s] expect: %v , got: %v", tc.description, tc.expectMatch, !tc.expectMatch) + } + } +} diff --git a/pkg/apis/core/types.go b/pkg/apis/core/types.go new file mode 100644 index 00000000000..e036d0721b9 --- /dev/null +++ b/pkg/apis/core/types.go @@ -0,0 +1,4577 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package core + +import ( + "k8s.io/apimachinery/pkg/api/resource" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" +) + +// Common string formats +// --------------------- +// Many fields in this API have formatting requirements. The commonly used +// formats are defined here. +// +// C_IDENTIFIER: This is a string that conforms to the definition of an "identifier" +// in the C language. This is captured by the following regex: +// [A-Za-z_][A-Za-z0-9_]* +// This defines the format, but not the length restriction, which should be +// specified at the definition of any field of this type. +// +// DNS_LABEL: This is a string, no more than 63 characters long, that conforms +// to the definition of a "label" in RFCs 1035 and 1123. This is captured +// by the following regex: +// [a-z0-9]([-a-z0-9]*[a-z0-9])? +// +// DNS_SUBDOMAIN: This is a string, no more than 253 characters long, that conforms +// to the definition of a "subdomain" in RFCs 1035 and 1123. This is captured +// by the following regex: +// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* +// or more simply: +// DNS_LABEL(\.DNS_LABEL)* +// +// IANA_SVC_NAME: This is a string, no more than 15 characters long, that +// conforms to the definition of IANA service name in RFC 6335. +// It must contains at least one letter [a-z] and it must contains only [a-z0-9-]. +// Hypens ('-') cannot be leading or trailing character of the string +// and cannot be adjacent to other hyphens. + +// ObjectMeta is metadata that all persisted resources must have, which includes all objects +// users must create. +// DEPRECATED: Use k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta instead - this type will be removed soon. +type ObjectMeta struct { + // Name is unique within a namespace. Name 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. + // +optional + Name string + + // GenerateName indicates that the name should be made unique by the server prior to persisting + // it. A non-empty value for the field indicates the name will be made unique (and the name + // returned to the client will be different than the name passed). The value of this field will + // be combined with a unique suffix on the server if the Name field has not been provided. + // The provided value must be valid within the rules for Name, and may be truncated by the length + // of the suffix required to make the value unique on the server. + // + // If this field is specified, and Name is not present, the server will NOT return a 409 if the + // generated name exists - instead, it will either return 201 Created or 500 with Reason + // ServerTimeout indicating a unique name could not be found in the time allotted, and the client + // should retry (optionally after the time indicated in the Retry-After header). + // +optional + GenerateName string + + // Namespace defines the space within which 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. + // +optional + Namespace string + + // SelfLink is a URL representing this object. + // +optional + SelfLink string + + // 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. + // +optional + UID types.UID + + // An opaque value that represents the version of this resource. 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 values may only be valid for a particular + // resource or set of resources. Only servers will generate resource versions. + // +optional + ResourceVersion string + + // A sequence number representing a specific generation of the desired state. + // Populated by the system. Read-only. + // +optional + Generation int64 + + // 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. + // +optional + CreationTimestamp metav1.Time + + // 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 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. + // + // Populated by the system when a graceful deletion is requested. + // Read-only. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + // +optional + DeletionTimestamp *metav1.Time + + // DeletionGracePeriodSeconds records the graceful deletion value set when graceful deletion + // was requested. Represents the most recent grace period, and may only be shortened once set. + // +optional + DeletionGracePeriodSeconds *int64 + + // Labels are key value pairs that may be used to scope and select individual resources. + // Label keys are of the form: + // label-key ::= prefixed-name | name + // prefixed-name ::= prefix '/' name + // prefix ::= DNS_SUBDOMAIN + // name ::= DNS_LABEL + // The prefix is optional. If the prefix is not specified, the key is assumed to be private + // to the user. Other system components that wish to use labels must specify a prefix. The + // "kubernetes.io/" prefix is reserved for use by kubernetes components. + // +optional + Labels map[string]string + + // Annotations are unstructured key value data stored with a resource that may be set by + // external tooling. They are not queryable and should be preserved when modifying + // objects. Annotation keys have the same formatting restrictions as Label keys. See the + // comments on Labels for details. + // +optional + Annotations map[string]string + + // 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. + // +optional + OwnerReferences []metav1.OwnerReference + + // An initializer is a controller which enforces some system invariant at object creation time. + // This field is a list of initializers that have not yet acted on this object. If nil or empty, + // this object has been completely initialized. Otherwise, the object is considered uninitialized + // and is hidden (in list/watch and get calls) from clients that haven't explicitly asked to + // observe uninitialized objects. + // + // When an object is created, the system will populate this list with the current set of initializers. + // Only privileged users may set or modify this list. Once it is empty, it may not be modified further + // by any user. + Initializers *metav1.Initializers + + // 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. + // +optional + Finalizers []string + + // The name of the cluster which the object belongs to. + // This is used to distinguish resources with same name and namespace in different clusters. + // This field is not set anywhere right now and apiserver is going to ignore it if set in create or update request. + // +optional + ClusterName string +} + +const ( + // NamespaceDefault means the object is in the default namespace which is applied when not specified by clients + NamespaceDefault string = "default" + // NamespaceAll is the default argument to specify on a context when you want to list or filter resources across all namespaces + NamespaceAll string = "" + // NamespaceNone is the argument for a context when there is no namespace. + NamespaceNone string = "" + // NamespaceSystem is the system namespace where we place system components. + NamespaceSystem string = "kube-system" + // NamespacePublic is the namespace where we place public info (ConfigMaps) + NamespacePublic string = "kube-public" + // TerminationMessagePathDefault means the default path to capture the application termination message running in a container + TerminationMessagePathDefault string = "/dev/termination-log" +) + +// Volume represents a named volume in a pod that may be accessed by any containers in the pod. +type Volume struct { + // Required: This must be a DNS_LABEL. Each volume in a pod must have + // a unique name. + Name string + // The VolumeSource represents the location and type of a volume to mount. + // This is optional for now. If not specified, the Volume is implied to be an EmptyDir. + // This implied behavior is deprecated and will be removed in a future version. + // +optional + VolumeSource +} + +// VolumeSource represents the source location of a volume to mount. +// Only one of its members may be specified. +type VolumeSource struct { + // HostPath represents file or directory on the host machine that is + // directly exposed to the container. This is generally used for system + // agents or other privileged things that are allowed to see the host + // machine. Most containers will NOT need this. + // --- + // TODO(jonesdl) We need to restrict who can use host directory mounts and who can/can not + // mount host directories as read/write. + // +optional + HostPath *HostPathVolumeSource + // EmptyDir represents a temporary directory that shares a pod's lifetime. + // +optional + EmptyDir *EmptyDirVolumeSource + // GCEPersistentDisk represents a GCE Disk resource that is attached to a + // kubelet's host machine and then exposed to the pod. + // +optional + GCEPersistentDisk *GCEPersistentDiskVolumeSource + // AWSElasticBlockStore represents an AWS EBS disk that is attached to a + // kubelet's host machine and then exposed to the pod. + // +optional + AWSElasticBlockStore *AWSElasticBlockStoreVolumeSource + // GitRepo represents a git repository at a particular revision. + // +optional + GitRepo *GitRepoVolumeSource + // Secret represents a secret that should populate this volume. + // +optional + Secret *SecretVolumeSource + // NFS represents an NFS mount on the host that shares a pod's lifetime + // +optional + NFS *NFSVolumeSource + // ISCSIVolumeSource represents an ISCSI Disk resource that is attached to a + // kubelet's host machine and then exposed to the pod. + // +optional + ISCSI *ISCSIVolumeSource + // Glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime + // +optional + Glusterfs *GlusterfsVolumeSource + // PersistentVolumeClaimVolumeSource represents a reference to a PersistentVolumeClaim in the same namespace + // +optional + PersistentVolumeClaim *PersistentVolumeClaimVolumeSource + // RBD represents a Rados Block Device mount on the host that shares a pod's lifetime + // +optional + RBD *RBDVolumeSource + + // Quobyte represents a Quobyte mount on the host that shares a pod's lifetime + // +optional + Quobyte *QuobyteVolumeSource + + // FlexVolume represents a generic volume resource that is + // provisioned/attached using an exec based plugin. + // +optional + FlexVolume *FlexVolumeSource + + // Cinder represents a cinder volume attached and mounted on kubelets host machine + // +optional + Cinder *CinderVolumeSource + + // CephFS represents a Cephfs mount on the host that shares a pod's lifetime + // +optional + CephFS *CephFSVolumeSource + + // Flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running + // +optional + Flocker *FlockerVolumeSource + + // DownwardAPI represents metadata about the pod that should populate this volume + // +optional + DownwardAPI *DownwardAPIVolumeSource + // FC represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod. + // +optional + FC *FCVolumeSource + // AzureFile represents an Azure File Service mount on the host and bind mount to the pod. + // +optional + AzureFile *AzureFileVolumeSource + // ConfigMap represents a configMap that should populate this volume + // +optional + ConfigMap *ConfigMapVolumeSource + // VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine + // +optional + VsphereVolume *VsphereVirtualDiskVolumeSource + // AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. + // +optional + AzureDisk *AzureDiskVolumeSource + // PhotonPersistentDisk represents a Photon Controller persistent disk attached and mounted on kubelets host machine + PhotonPersistentDisk *PhotonPersistentDiskVolumeSource + // Items for all in one resources secrets, configmaps, and downward API + Projected *ProjectedVolumeSource + // PortworxVolume represents a portworx volume attached and mounted on kubelets host machine + // +optional + PortworxVolume *PortworxVolumeSource + // ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. + // +optional + ScaleIO *ScaleIOVolumeSource + // StorageOS represents a StorageOS volume that is attached to the kubelet's host machine and mounted into the pod + // +optional + StorageOS *StorageOSVolumeSource +} + +// Similar to VolumeSource but meant for the administrator who creates PVs. +// Exactly one of its members must be set. +type PersistentVolumeSource struct { + // GCEPersistentDisk represents a GCE Disk resource that is attached to a + // kubelet's host machine and then exposed to the pod. + // +optional + GCEPersistentDisk *GCEPersistentDiskVolumeSource + // AWSElasticBlockStore represents an AWS EBS disk that is attached to a + // kubelet's host machine and then exposed to the pod. + // +optional + AWSElasticBlockStore *AWSElasticBlockStoreVolumeSource + // HostPath represents a directory on the host. + // Provisioned by a developer or tester. + // This is useful for single-node development and testing only! + // On-host storage is not supported in any way and WILL NOT WORK in a multi-node cluster. + // +optional + HostPath *HostPathVolumeSource + // Glusterfs represents a Glusterfs volume that is attached to a host and exposed to the pod + // +optional + Glusterfs *GlusterfsVolumeSource + // NFS represents an NFS mount on the host that shares a pod's lifetime + // +optional + NFS *NFSVolumeSource + // RBD represents a Rados Block Device mount on the host that shares a pod's lifetime + // +optional + RBD *RBDPersistentVolumeSource + // Quobyte represents a Quobyte mount on the host that shares a pod's lifetime + // +optional + Quobyte *QuobyteVolumeSource + // ISCSIPersistentVolumeSource represents an ISCSI resource that is attached to a + // kubelet's host machine and then exposed to the pod. + // +optional + ISCSI *ISCSIPersistentVolumeSource + // FlexVolume represents a generic volume resource that is + // provisioned/attached using an exec based plugin. + // +optional + FlexVolume *FlexPersistentVolumeSource + // Cinder represents a cinder volume attached and mounted on kubelets host machine + // +optional + Cinder *CinderVolumeSource + // CephFS represents a Ceph FS mount on the host that shares a pod's lifetime + // +optional + CephFS *CephFSPersistentVolumeSource + // FC represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod. + // +optional + FC *FCVolumeSource + // Flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running + // +optional + Flocker *FlockerVolumeSource + // AzureFile represents an Azure File Service mount on the host and bind mount to the pod. + // +optional + AzureFile *AzureFilePersistentVolumeSource + // VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine + // +optional + VsphereVolume *VsphereVirtualDiskVolumeSource + // AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. + // +optional + AzureDisk *AzureDiskVolumeSource + // PhotonPersistentDisk represents a Photon Controller persistent disk attached and mounted on kubelets host machine + PhotonPersistentDisk *PhotonPersistentDiskVolumeSource + // PortworxVolume represents a portworx volume attached and mounted on kubelets host machine + // +optional + PortworxVolume *PortworxVolumeSource + // ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. + // +optional + ScaleIO *ScaleIOPersistentVolumeSource + // Local represents directly-attached storage with node affinity + // +optional + Local *LocalVolumeSource + // StorageOS represents a StorageOS volume that is attached to the kubelet's host machine and mounted into the pod + // More info: https://releases.k8s.io/HEAD/examples/volumes/storageos/README.md + // +optional + StorageOS *StorageOSPersistentVolumeSource + // CSI (Container Storage Interface) represents storage that handled by an external CSI driver + // +optional + CSI *CSIPersistentVolumeSource +} + +type PersistentVolumeClaimVolumeSource struct { + // ClaimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume + ClaimName string + // Optional: Defaults to false (read/write). ReadOnly here + // will force the ReadOnly setting in VolumeMounts + // +optional + ReadOnly bool +} + +const ( + // BetaStorageClassAnnotation represents the beta/previous StorageClass annotation. + // It's deprecated and will be removed in a future release. (#51440) + BetaStorageClassAnnotation = "volume.beta.kubernetes.io/storage-class" + + // MountOptionAnnotation defines mount option annotation used in PVs + MountOptionAnnotation = "volume.beta.kubernetes.io/mount-options" + + // AlphaStorageNodeAffinityAnnotation defines node affinity policies for a PersistentVolume. + // Value is a string of the json representation of type NodeAffinity + AlphaStorageNodeAffinityAnnotation = "volume.alpha.kubernetes.io/node-affinity" +) + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type PersistentVolume struct { + metav1.TypeMeta + // +optional + metav1.ObjectMeta + + //Spec defines a persistent volume owned by the cluster + // +optional + Spec PersistentVolumeSpec + + // Status represents the current information about persistent volume. + // +optional + Status PersistentVolumeStatus +} + +type PersistentVolumeSpec struct { + // Resources represents the actual resources of the volume + Capacity ResourceList + // Source represents the location and type of a volume to mount. + PersistentVolumeSource + // AccessModes contains all ways the volume can be mounted + // +optional + AccessModes []PersistentVolumeAccessMode + // ClaimRef is part of a bi-directional binding between PersistentVolume and PersistentVolumeClaim. + // ClaimRef is expected to be non-nil when bound. + // claim.VolumeName is the authoritative bind between PV and PVC. + // When set to non-nil value, PVC.Spec.Selector of the referenced PVC is + // ignored, i.e. labels of this PV do not need to match PVC selector. + // +optional + ClaimRef *ObjectReference + // Optional: what happens to a persistent volume when released from its claim. + // +optional + PersistentVolumeReclaimPolicy PersistentVolumeReclaimPolicy + // Name of StorageClass to which this persistent volume belongs. Empty value + // means that this volume does not belong to any StorageClass. + // +optional + StorageClassName string + // A list of mount options, e.g. ["ro", "soft"]. Not validated - mount will + // simply fail if one is invalid. + // +optional + MountOptions []string + // volumeMode defines if a volume is intended to be used with a formatted filesystem + // or to remain in raw block state. Value of Filesystem is implied when not included in spec. + // This is an alpha feature and may change in the future. + // +optional + VolumeMode *PersistentVolumeMode +} + +// PersistentVolumeReclaimPolicy describes a policy for end-of-life maintenance of persistent volumes +type PersistentVolumeReclaimPolicy string + +const ( + // PersistentVolumeReclaimRecycle means the volume will be recycled back into the pool of unbound persistent volumes on release from its claim. + // The volume plugin must support Recycling. + PersistentVolumeReclaimRecycle PersistentVolumeReclaimPolicy = "Recycle" + // PersistentVolumeReclaimDelete means the volume will be deleted from Kubernetes on release from its claim. + // The volume plugin must support Deletion. + PersistentVolumeReclaimDelete PersistentVolumeReclaimPolicy = "Delete" + // PersistentVolumeReclaimRetain means the volume will be left in its current phase (Released) for manual reclamation by the administrator. + // The default policy is Retain. + PersistentVolumeReclaimRetain PersistentVolumeReclaimPolicy = "Retain" +) + +// PersistentVolumeMode describes how a volume is intended to be consumed, either Block or Filesystem. +type PersistentVolumeMode string + +const ( + // PersistentVolumeBlock means the volume will not be formatted with a filesystem and will remain a raw block device. + PersistentVolumeBlock PersistentVolumeMode = "Block" + // PersistentVolumeFilesystem means the volume will be or is formatted with a filesystem. + PersistentVolumeFilesystem PersistentVolumeMode = "Filesystem" +) + +type PersistentVolumeStatus struct { + // Phase indicates if a volume is available, bound to a claim, or released by a claim + // +optional + Phase PersistentVolumePhase + // A human-readable message indicating details about why the volume is in this state. + // +optional + Message string + // Reason is a brief CamelCase string that describes any failure and is meant for machine parsing and tidy display in the CLI + // +optional + Reason string +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type PersistentVolumeList struct { + metav1.TypeMeta + // +optional + metav1.ListMeta + Items []PersistentVolume +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PersistentVolumeClaim is a user's request for and claim to a persistent volume +type PersistentVolumeClaim struct { + metav1.TypeMeta + // +optional + metav1.ObjectMeta + + // Spec defines the volume requested by a pod author + // +optional + Spec PersistentVolumeClaimSpec + + // Status represents the current information about a claim + // +optional + Status PersistentVolumeClaimStatus +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type PersistentVolumeClaimList struct { + metav1.TypeMeta + // +optional + metav1.ListMeta + Items []PersistentVolumeClaim +} + +// PersistentVolumeClaimSpec describes the common attributes of storage devices +// and allows a Source for provider-specific attributes +type PersistentVolumeClaimSpec struct { + // Contains the types of access modes required + // +optional + AccessModes []PersistentVolumeAccessMode + // A label query over volumes to consider for binding. This selector is + // ignored when VolumeName is set + // +optional + Selector *metav1.LabelSelector + // Resources represents the minimum resources required + // +optional + Resources ResourceRequirements + // VolumeName is the binding reference to the PersistentVolume backing this + // claim. When set to non-empty value Selector is not evaluated + // +optional + VolumeName string + // Name of the StorageClass required by the claim. + // More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#class-1 + // +optional + StorageClassName *string + // volumeMode defines what type of volume is required by the claim. + // Value of Filesystem is implied when not included in claim spec. + // This is an alpha feature and may change in the future. + // +optional + VolumeMode *PersistentVolumeMode +} + +type PersistentVolumeClaimConditionType string + +// These are valid conditions of Pvc +const ( + // An user trigger resize of pvc has been started + PersistentVolumeClaimResizing PersistentVolumeClaimConditionType = "Resizing" +) + +type PersistentVolumeClaimCondition struct { + Type PersistentVolumeClaimConditionType + Status ConditionStatus + // +optional + LastProbeTime metav1.Time + // +optional + LastTransitionTime metav1.Time + // +optional + Reason string + // +optional + Message string +} + +type PersistentVolumeClaimStatus struct { + // Phase represents the current phase of PersistentVolumeClaim + // +optional + Phase PersistentVolumeClaimPhase + // AccessModes contains all ways the volume backing the PVC can be mounted + // +optional + AccessModes []PersistentVolumeAccessMode + // Represents the actual resources of the underlying volume + // +optional + Capacity ResourceList + // +optional + Conditions []PersistentVolumeClaimCondition +} + +type PersistentVolumeAccessMode string + +const ( + // can be mounted read/write mode to exactly 1 host + ReadWriteOnce PersistentVolumeAccessMode = "ReadWriteOnce" + // can be mounted in read-only mode to many hosts + ReadOnlyMany PersistentVolumeAccessMode = "ReadOnlyMany" + // can be mounted in read/write mode to many hosts + ReadWriteMany PersistentVolumeAccessMode = "ReadWriteMany" +) + +type PersistentVolumePhase string + +const ( + // used for PersistentVolumes that are not available + VolumePending PersistentVolumePhase = "Pending" + // used for PersistentVolumes that are not yet bound + // Available volumes are held by the binder and matched to PersistentVolumeClaims + VolumeAvailable PersistentVolumePhase = "Available" + // used for PersistentVolumes that are bound + VolumeBound PersistentVolumePhase = "Bound" + // used for PersistentVolumes where the bound PersistentVolumeClaim was deleted + // released volumes must be recycled before becoming available again + // this phase is used by the persistent volume claim binder to signal to another process to reclaim the resource + VolumeReleased PersistentVolumePhase = "Released" + // used for PersistentVolumes that failed to be correctly recycled or deleted after being released from a claim + VolumeFailed PersistentVolumePhase = "Failed" +) + +type PersistentVolumeClaimPhase string + +const ( + // used for PersistentVolumeClaims that are not yet bound + ClaimPending PersistentVolumeClaimPhase = "Pending" + // used for PersistentVolumeClaims that are bound + ClaimBound PersistentVolumeClaimPhase = "Bound" + // used for PersistentVolumeClaims that lost their underlying + // PersistentVolume. The claim was bound to a PersistentVolume and this + // volume does not exist any longer and all data on it was lost. + ClaimLost PersistentVolumeClaimPhase = "Lost" +) + +type HostPathType string + +const ( + // For backwards compatible, leave it empty if unset + HostPathUnset HostPathType = "" + // If nothing exists at the given path, an empty directory will be created there + // as needed with file mode 0755, having the same group and ownership with Kubelet. + HostPathDirectoryOrCreate HostPathType = "DirectoryOrCreate" + // A directory must exist at the given path + HostPathDirectory HostPathType = "Directory" + // If nothing exists at the given path, an empty file will be created there + // as needed with file mode 0644, having the same group and ownership with Kubelet. + HostPathFileOrCreate HostPathType = "FileOrCreate" + // A file must exist at the given path + HostPathFile HostPathType = "File" + // A UNIX socket must exist at the given path + HostPathSocket HostPathType = "Socket" + // A character device must exist at the given path + HostPathCharDev HostPathType = "CharDevice" + // A block device must exist at the given path + HostPathBlockDev HostPathType = "BlockDevice" +) + +// Represents a host path mapped into a pod. +// Host path volumes do not support ownership management or SELinux relabeling. +type HostPathVolumeSource struct { + // If the path is a symlink, it will follow the link to the real path. + Path string + // Defaults to "" + Type *HostPathType +} + +// Represents an empty directory for a pod. +// Empty directory volumes support ownership management and SELinux relabeling. +type EmptyDirVolumeSource struct { + // TODO: Longer term we want to represent the selection of underlying + // media more like a scheduling problem - user says what traits they + // need, we give them a backing store that satisfies that. For now + // this will cover the most common needs. + // Optional: what type of storage medium should back this directory. + // The default is "" which means to use the node's default medium. + // +optional + Medium StorageMedium + // Total amount of local storage required for this EmptyDir volume. + // The size limit is also applicable for memory medium. + // The maximum usage on memory medium EmptyDir would be the minimum value between + // the SizeLimit specified here and the sum of memory limits of all containers in a pod. + // The default is nil which means that the limit is undefined. + // More info: http://kubernetes.io/docs/user-guide/volumes#emptydir + // +optional + SizeLimit *resource.Quantity +} + +// StorageMedium defines ways that storage can be allocated to a volume. +type StorageMedium string + +const ( + StorageMediumDefault StorageMedium = "" // use whatever the default is for the node + StorageMediumMemory StorageMedium = "Memory" // use memory (tmpfs) + StorageMediumHugePages StorageMedium = "HugePages" // use hugepages +) + +// Protocol defines network protocols supported for things like container ports. +type Protocol string + +const ( + // ProtocolTCP is the TCP protocol. + ProtocolTCP Protocol = "TCP" + // ProtocolUDP is the UDP protocol. + ProtocolUDP Protocol = "UDP" +) + +// Represents a Persistent Disk resource in Google Compute Engine. +// +// A GCE PD must exist before mounting to a container. The disk must +// also be in the same GCE project and zone as the kubelet. A GCE PD +// can only be mounted as read/write once or read-only many times. GCE +// PDs support ownership management and SELinux relabeling. +type GCEPersistentDiskVolumeSource struct { + // Unique name of the PD resource. Used to identify the disk in GCE + PDName string + // Filesystem type to mount. + // Must be a filesystem type supported by the host operating system. + // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + // TODO: how do we prevent errors in the filesystem from compromising the machine + // +optional + FSType string + // Optional: Partition on the disk to mount. + // If omitted, kubelet will attempt to mount the device name. + // Ex. For /dev/sda1, this field is "1", for /dev/sda, this field is 0 or empty. + // +optional + Partition int32 + // Optional: Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + ReadOnly bool +} + +// Represents an ISCSI disk. +// ISCSI volumes can only be mounted as read/write once. +// ISCSI volumes support ownership management and SELinux relabeling. +type ISCSIVolumeSource struct { + // Required: iSCSI target portal + // the portal is either an IP or ip_addr:port if port is other than default (typically TCP ports 860 and 3260) + // +optional + TargetPortal string + // Required: target iSCSI Qualified Name + // +optional + IQN string + // Required: iSCSI target lun number + // +optional + Lun int32 + // Optional: Defaults to 'default' (tcp). iSCSI interface name that uses an iSCSI transport. + // +optional + ISCSIInterface string + // Filesystem type to mount. + // Must be a filesystem type supported by the host operating system. + // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + // TODO: how do we prevent errors in the filesystem from compromising the machine + // +optional + FSType string + // Optional: Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + ReadOnly bool + // Optional: list of iSCSI target portal ips for high availability. + // the portal is either an IP or ip_addr:port if port is other than default (typically TCP ports 860 and 3260) + // +optional + Portals []string + // Optional: whether support iSCSI Discovery CHAP authentication + // +optional + DiscoveryCHAPAuth bool + // Optional: whether support iSCSI Session CHAP authentication + // +optional + SessionCHAPAuth bool + // Optional: CHAP secret for iSCSI target and initiator authentication. + // The secret is used if either DiscoveryCHAPAuth or SessionCHAPAuth is true + // +optional + SecretRef *LocalObjectReference + // Optional: Custom initiator name per volume. + // If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface + // : will be created for the connection. + // +optional + InitiatorName *string +} + +// ISCSIPersistentVolumeSource represents an ISCSI disk. +// ISCSI volumes can only be mounted as read/write once. +// ISCSI volumes support ownership management and SELinux relabeling. +type ISCSIPersistentVolumeSource struct { + // Required: iSCSI target portal + // the portal is either an IP or ip_addr:port if port is other than default (typically TCP ports 860 and 3260) + // +optional + TargetPortal string + // Required: target iSCSI Qualified Name + // +optional + IQN string + // Required: iSCSI target lun number + // +optional + Lun int32 + // Optional: Defaults to 'default' (tcp). iSCSI interface name that uses an iSCSI transport. + // +optional + ISCSIInterface string + // Filesystem type to mount. + // Must be a filesystem type supported by the host operating system. + // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + // TODO: how do we prevent errors in the filesystem from compromising the machine + // +optional + FSType string + // Optional: Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + ReadOnly bool + // Optional: list of iSCSI target portal ips for high availability. + // the portal is either an IP or ip_addr:port if port is other than default (typically TCP ports 860 and 3260) + // +optional + Portals []string + // Optional: whether support iSCSI Discovery CHAP authentication + // +optional + DiscoveryCHAPAuth bool + // Optional: whether support iSCSI Session CHAP authentication + // +optional + SessionCHAPAuth bool + // Optional: CHAP secret for iSCSI target and initiator authentication. + // The secret is used if either DiscoveryCHAPAuth or SessionCHAPAuth is true + // +optional + SecretRef *SecretReference + // Optional: Custom initiator name per volume. + // If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface + // : will be created for the connection. + // +optional + InitiatorName *string +} + +// Represents a Fibre Channel volume. +// Fibre Channel volumes can only be mounted as read/write once. +// Fibre Channel volumes support ownership management and SELinux relabeling. +type FCVolumeSource struct { + // Optional: FC target worldwide names (WWNs) + // +optional + TargetWWNs []string + // Optional: FC target lun number + // +optional + Lun *int32 + // Filesystem type to mount. + // Must be a filesystem type supported by the host operating system. + // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + // TODO: how do we prevent errors in the filesystem from compromising the machine + // +optional + FSType string + // Optional: Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + ReadOnly bool + // Optional: FC volume World Wide Identifiers (WWIDs) + // Either WWIDs or TargetWWNs and Lun must be set, but not both simultaneously. + // +optional + WWIDs []string +} + +// FlexPersistentVolumeSource represents a generic persistent volume resource that is +// provisioned/attached using an exec based plugin. +type FlexPersistentVolumeSource struct { + // Driver is the name of the driver to use for this volume. + Driver string + // Filesystem type to mount. + // Must be a filesystem type supported by the host operating system. + // Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + // +optional + FSType string + // Optional: SecretRef is reference to the secret object containing + // sensitive information to pass to the plugin scripts. This may be + // empty if no secret object is specified. If the secret object + // contains more than one secret, all secrets are passed to the plugin + // scripts. + // +optional + SecretRef *SecretReference + // Optional: Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + ReadOnly bool + // Optional: Extra driver options if any. + // +optional + Options map[string]string +} + +// FlexVolume represents a generic volume resource that is +// provisioned/attached using an exec based plugin. +type FlexVolumeSource struct { + // Driver is the name of the driver to use for this volume. + Driver string + // Filesystem type to mount. + // Must be a filesystem type supported by the host operating system. + // Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + // +optional + FSType string + // Optional: SecretRef is reference to the secret object containing + // sensitive information to pass to the plugin scripts. This may be + // empty if no secret object is specified. If the secret object + // contains more than one secret, all secrets are passed to the plugin + // scripts. + // +optional + SecretRef *LocalObjectReference + // Optional: Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + ReadOnly bool + // Optional: Extra driver options if any. + // +optional + Options map[string]string +} + +// Represents a Persistent Disk resource in AWS. +// +// An AWS EBS disk must exist before mounting to a container. The disk +// must also be in the same AWS zone as the kubelet. An AWS EBS disk +// can only be mounted as read/write once. AWS EBS volumes support +// ownership management and SELinux relabeling. +type AWSElasticBlockStoreVolumeSource struct { + // Unique id of the persistent disk resource. Used to identify the disk in AWS + VolumeID string + // Filesystem type to mount. + // Must be a filesystem type supported by the host operating system. + // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + // TODO: how do we prevent errors in the filesystem from compromising the machine + // +optional + FSType string + // Optional: Partition on the disk to mount. + // If omitted, kubelet will attempt to mount the device name. + // Ex. For /dev/sda1, this field is "1", for /dev/sda, this field is 0 or empty. + // +optional + Partition int32 + // Optional: Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + ReadOnly bool +} + +// Represents a volume that is populated with the contents of a git repository. +// Git repo volumes do not support ownership management. +// Git repo volumes support SELinux relabeling. +type GitRepoVolumeSource struct { + // Repository URL + Repository string + // Commit hash, this is optional + // +optional + Revision string + // Clone target, this is optional + // Must not contain or start with '..'. If '.' is supplied, the volume directory will be the + // git repository. Otherwise, if specified, the volume will contain the git repository in + // the subdirectory with the given name. + // +optional + Directory string + // TODO: Consider credentials here. +} + +// Adapts a Secret into a volume. +// +// The contents of the target Secret's Data field will be presented in a volume +// as files using the keys in the Data field as the file names. +// Secret volumes support ownership management and SELinux relabeling. +type SecretVolumeSource struct { + // Name of the secret in the pod's namespace to use. + // +optional + SecretName string + // If unspecified, each key-value pair in the Data field of the referenced + // Secret will be projected into the volume as a file whose name is the + // key and content is the value. If specified, the listed keys will be + // projected into the specified paths, and unlisted keys will not be + // present. If a key is specified which is not present in the Secret, + // the volume setup will error unless it is marked optional. Paths must be + // relative and may not contain the '..' path or start with '..'. + // +optional + Items []KeyToPath + // Mode bits to use on created files by default. Must be a value between + // 0 and 0777. + // Directories within the path are not affected by this setting. + // This might be in conflict with other options that affect the file + // mode, like fsGroup, and the result can be other mode bits set. + // +optional + DefaultMode *int32 + // Specify whether the Secret or its key must be defined + // +optional + Optional *bool +} + +// Adapts a secret into a projected volume. +// +// The contents of the target Secret's Data field will be presented in a +// projected volume as files using the keys in the Data field as the file names. +// Note that this is identical to a secret volume source without the default +// mode. +type SecretProjection struct { + LocalObjectReference + // If unspecified, each key-value pair in the Data field of the referenced + // Secret will be projected into the volume as a file whose name is the + // key and content is the value. If specified, the listed keys will be + // projected into the specified paths, and unlisted keys will not be + // present. If a key is specified which is not present in the Secret, + // the volume setup will error unless it is marked optional. Paths must be + // relative and may not contain the '..' path or start with '..'. + // +optional + Items []KeyToPath + // Specify whether the Secret or its key must be defined + // +optional + Optional *bool +} + +// Represents an NFS mount that lasts the lifetime of a pod. +// NFS volumes do not support ownership management or SELinux relabeling. +type NFSVolumeSource struct { + // Server is the hostname or IP address of the NFS server + Server string + + // Path is the exported NFS share + Path string + + // Optional: Defaults to false (read/write). ReadOnly here will force + // the NFS export to be mounted with read-only permissions + // +optional + ReadOnly bool +} + +// Represents a Quobyte mount that lasts the lifetime of a pod. +// Quobyte volumes do not support ownership management or SELinux relabeling. +type QuobyteVolumeSource struct { + // Registry represents a single or multiple Quobyte Registry services + // specified as a string as host:port pair (multiple entries are separated with commas) + // which acts as the central registry for volumes + Registry string + + // Volume is a string that references an already created Quobyte volume by name. + Volume string + + // Defaults to false (read/write). ReadOnly here will force + // the Quobyte to be mounted with read-only permissions + // +optional + ReadOnly bool + + // User to map volume access to + // Defaults to the root user + // +optional + User string + + // Group to map volume access to + // Default is no group + // +optional + Group string +} + +// Represents a Glusterfs mount that lasts the lifetime of a pod. +// Glusterfs volumes do not support ownership management or SELinux relabeling. +type GlusterfsVolumeSource struct { + // Required: EndpointsName is the endpoint name that details Glusterfs topology + EndpointsName string + + // Required: Path is the Glusterfs volume path + Path string + + // Optional: Defaults to false (read/write). ReadOnly here will force + // the Glusterfs to be mounted with read-only permissions + // +optional + ReadOnly bool +} + +// Represents a Rados Block Device mount that lasts the lifetime of a pod. +// RBD volumes support ownership management and SELinux relabeling. +type RBDVolumeSource struct { + // Required: CephMonitors is a collection of Ceph monitors + CephMonitors []string + // Required: RBDImage is the rados image name + RBDImage string + // Filesystem type to mount. + // Must be a filesystem type supported by the host operating system. + // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + // TODO: how do we prevent errors in the filesystem from compromising the machine + // +optional + FSType string + // Optional: RadosPool is the rados pool name,default is rbd + // +optional + RBDPool string + // Optional: RBDUser is the rados user name, default is admin + // +optional + RadosUser string + // Optional: Keyring is the path to key ring for RBDUser, default is /etc/ceph/keyring + // +optional + Keyring string + // Optional: SecretRef is name of the authentication secret for RBDUser, default is nil. + // +optional + SecretRef *LocalObjectReference + // Optional: Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + ReadOnly bool +} + +// Represents a Rados Block Device mount that lasts the lifetime of a pod. +// RBD volumes support ownership management and SELinux relabeling. +type RBDPersistentVolumeSource struct { + // Required: CephMonitors is a collection of Ceph monitors + CephMonitors []string + // Required: RBDImage is the rados image name + RBDImage string + // Filesystem type to mount. + // Must be a filesystem type supported by the host operating system. + // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + // TODO: how do we prevent errors in the filesystem from compromising the machine + // +optional + FSType string + // Optional: RadosPool is the rados pool name,default is rbd + // +optional + RBDPool string + // Optional: RBDUser is the rados user name, default is admin + // +optional + RadosUser string + // Optional: Keyring is the path to key ring for RBDUser, default is /etc/ceph/keyring + // +optional + Keyring string + // Optional: SecretRef is reference to the authentication secret for User, default is empty. + // +optional + SecretRef *SecretReference + // Optional: Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + ReadOnly bool +} + +// Represents a cinder volume resource in Openstack. A Cinder volume +// must exist before mounting to a container. The volume must also be +// in the same region as the kubelet. Cinder volumes support ownership +// management and SELinux relabeling. +type CinderVolumeSource struct { + // Unique id of the volume used to identify the cinder volume + VolumeID string + // Filesystem type to mount. + // Must be a filesystem type supported by the host operating system. + // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + // +optional + FSType string + // Optional: Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + ReadOnly bool +} + +// Represents a Ceph Filesystem mount that lasts the lifetime of a pod +// Cephfs volumes do not support ownership management or SELinux relabeling. +type CephFSVolumeSource struct { + // Required: Monitors is a collection of Ceph monitors + Monitors []string + // Optional: Used as the mounted root, rather than the full Ceph tree, default is / + // +optional + Path string + // Optional: User is the rados user name, default is admin + // +optional + User string + // Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret + // +optional + SecretFile string + // Optional: SecretRef is reference to the authentication secret for User, default is empty. + // +optional + SecretRef *LocalObjectReference + // Optional: Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + ReadOnly bool +} + +// SecretReference represents a Secret Reference. It has enough information to retrieve secret +// in any namespace +type SecretReference struct { + // Name is unique within a namespace to reference a secret resource. + // +optional + Name string + // Namespace defines the space within which the secret name must be unique. + // +optional + Namespace string +} + +// Represents a Ceph Filesystem mount that lasts the lifetime of a pod +// Cephfs volumes do not support ownership management or SELinux relabeling. +type CephFSPersistentVolumeSource struct { + // Required: Monitors is a collection of Ceph monitors + Monitors []string + // Optional: Used as the mounted root, rather than the full Ceph tree, default is / + // +optional + Path string + // Optional: User is the rados user name, default is admin + // +optional + User string + // Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret + // +optional + SecretFile string + // Optional: SecretRef is reference to the authentication secret for User, default is empty. + // +optional + SecretRef *SecretReference + // Optional: Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + ReadOnly bool +} + +// Represents a Flocker volume mounted by the Flocker agent. +// One and only one of datasetName and datasetUUID should be set. +// Flocker volumes do not support ownership management or SELinux relabeling. +type FlockerVolumeSource struct { + // Name of the dataset stored as metadata -> name on the dataset for Flocker + // should be considered as deprecated + // +optional + DatasetName string + // UUID of the dataset. This is unique identifier of a Flocker dataset + // +optional + DatasetUUID string +} + +// Represents a volume containing downward API info. +// Downward API volumes support ownership management and SELinux relabeling. +type DownwardAPIVolumeSource struct { + // Items is a list of DownwardAPIVolume file + // +optional + Items []DownwardAPIVolumeFile + // Mode bits to use on created files by default. Must be a value between + // 0 and 0777. + // Directories within the path are not affected by this setting. + // This might be in conflict with other options that affect the file + // mode, like fsGroup, and the result can be other mode bits set. + // +optional + DefaultMode *int32 +} + +// Represents a single file containing information from the downward API +type DownwardAPIVolumeFile struct { + // Required: Path is the relative path name of the file to be created. Must not be absolute or contain the '..' path. Must be utf-8 encoded. The first item of the relative path must not start with '..' + Path string + // Required: Selects a field of the pod: only annotations, labels, name, namespace and uid are supported. + // +optional + FieldRef *ObjectFieldSelector + // Selects a resource of the container: only resources limits and requests + // (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + // +optional + ResourceFieldRef *ResourceFieldSelector + // Optional: mode bits to use on this file, must be a value between 0 + // and 0777. If not specified, the volume defaultMode will be used. + // This might be in conflict with other options that affect the file + // mode, like fsGroup, and the result can be other mode bits set. + // +optional + Mode *int32 +} + +// Represents downward API info for projecting into a projected volume. +// Note that this is identical to a downwardAPI volume source without the default +// mode. +type DownwardAPIProjection struct { + // Items is a list of DownwardAPIVolume file + // +optional + Items []DownwardAPIVolumeFile +} + +// AzureFile represents an Azure File Service mount on the host and bind mount to the pod. +type AzureFileVolumeSource struct { + // the name of secret that contains Azure Storage Account Name and Key + SecretName string + // Share Name + ShareName string + // Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + ReadOnly bool +} + +// AzureFile represents an Azure File Service mount on the host and bind mount to the pod. +type AzureFilePersistentVolumeSource struct { + // the name of secret that contains Azure Storage Account Name and Key + SecretName string + // Share Name + ShareName string + // Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + ReadOnly bool + // the namespace of the secret that contains Azure Storage Account Name and Key + // default is the same as the Pod + // +optional + SecretNamespace *string +} + +// Represents a vSphere volume resource. +type VsphereVirtualDiskVolumeSource struct { + // Path that identifies vSphere volume vmdk + VolumePath string + // Filesystem type to mount. + // Must be a filesystem type supported by the host operating system. + // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + // +optional + FSType string + // Storage Policy Based Management (SPBM) profile name. + // +optional + StoragePolicyName string + // Storage Policy Based Management (SPBM) profile ID associated with the StoragePolicyName. + // +optional + StoragePolicyID string +} + +// Represents a Photon Controller persistent disk resource. +type PhotonPersistentDiskVolumeSource struct { + // ID that identifies Photon Controller persistent disk + PdID string + // Filesystem type to mount. + // Must be a filesystem type supported by the host operating system. + // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + FSType string +} + +// PortworxVolumeSource represents a Portworx volume resource. +type PortworxVolumeSource struct { + // VolumeID uniquely identifies a Portworx volume + VolumeID string + // FSType represents the filesystem type to mount + // Must be a filesystem type supported by the host operating system. + // Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. + // +optional + FSType string + // Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + ReadOnly bool +} + +type AzureDataDiskCachingMode string +type AzureDataDiskKind string + +const ( + AzureDataDiskCachingNone AzureDataDiskCachingMode = "None" + AzureDataDiskCachingReadOnly AzureDataDiskCachingMode = "ReadOnly" + AzureDataDiskCachingReadWrite AzureDataDiskCachingMode = "ReadWrite" + + AzureSharedBlobDisk AzureDataDiskKind = "Shared" + AzureDedicatedBlobDisk AzureDataDiskKind = "Dedicated" + AzureManagedDisk AzureDataDiskKind = "Managed" +) + +// AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. +type AzureDiskVolumeSource struct { + // The Name of the data disk in the blob storage + DiskName string + // The URI of the data disk in the blob storage + DataDiskURI string + // Host Caching mode: None, Read Only, Read Write. + // +optional + CachingMode *AzureDataDiskCachingMode + // Filesystem type to mount. + // Must be a filesystem type supported by the host operating system. + // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + // +optional + FSType *string + // Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + ReadOnly *bool + // Expected values Shared: multiple blob disks per storage account Dedicated: single blob disk per storage account Managed: azure managed data disk (only in managed availability set). defaults to shared + Kind *AzureDataDiskKind +} + +// ScaleIOVolumeSource represents a persistent ScaleIO volume +type ScaleIOVolumeSource struct { + // The host address of the ScaleIO API Gateway. + Gateway string + // The name of the storage system as configured in ScaleIO. + System string + // SecretRef references to the secret for ScaleIO user and other + // sensitive information. If this is not provided, Login operation will fail. + SecretRef *LocalObjectReference + // Flag to enable/disable SSL communication with Gateway, default false + // +optional + SSLEnabled bool + // The name of the ScaleIO Protection Domain for the configured storage. + // +optional + ProtectionDomain string + // The ScaleIO Storage Pool associated with the protection domain. + // +optional + StoragePool string + // Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. + // +optional + StorageMode string + // The name of a volume already created in the ScaleIO system + // that is associated with this volume source. + VolumeName string + // Filesystem type to mount. + // Must be a filesystem type supported by the host operating system. + // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + // +optional + FSType string + // Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + ReadOnly bool +} + +// ScaleIOPersistentVolumeSource represents a persistent ScaleIO volume that can be defined +// by a an admin via a storage class, for instance. +type ScaleIOPersistentVolumeSource struct { + // The host address of the ScaleIO API Gateway. + Gateway string + // The name of the storage system as configured in ScaleIO. + System string + // SecretRef references to the secret for ScaleIO user and other + // sensitive information. If this is not provided, Login operation will fail. + SecretRef *SecretReference + // Flag to enable/disable SSL communication with Gateway, default false + // +optional + SSLEnabled bool + // The name of the ScaleIO Protection Domain for the configured storage. + // +optional + ProtectionDomain string + // The ScaleIO Storage Pool associated with the protection domain. + // +optional + StoragePool string + // Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. + // +optional + StorageMode string + // The name of a volume created in the ScaleIO system + // that is associated with this volume source. + VolumeName string + // Filesystem type to mount. + // Must be a filesystem type supported by the host operating system. + // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + // +optional + FSType string + // Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + ReadOnly bool +} + +// Represents a StorageOS persistent volume resource. +type StorageOSVolumeSource struct { + // VolumeName is the human-readable name of the StorageOS volume. Volume + // names are only unique within a namespace. + VolumeName string + // VolumeNamespace specifies the scope of the volume within StorageOS. If no + // namespace is specified then the Pod's namespace will be used. This allows the + // Kubernetes name scoping to be mirrored within StorageOS for tighter integration. + // Set VolumeName to any name to override the default behaviour. + // Set to "default" if you are not using namespaces within StorageOS. + // Namespaces that do not pre-exist within StorageOS will be created. + // +optional + VolumeNamespace string + // Filesystem type to mount. + // Must be a filesystem type supported by the host operating system. + // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + // +optional + FSType string + // Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + ReadOnly bool + // SecretRef specifies the secret to use for obtaining the StorageOS API + // credentials. If not specified, default values will be attempted. + // +optional + SecretRef *LocalObjectReference +} + +// Represents a StorageOS persistent volume resource. +type StorageOSPersistentVolumeSource struct { + // VolumeName is the human-readable name of the StorageOS volume. Volume + // names are only unique within a namespace. + VolumeName string + // VolumeNamespace specifies the scope of the volume within StorageOS. If no + // namespace is specified then the Pod's namespace will be used. This allows the + // Kubernetes name scoping to be mirrored within StorageOS for tighter integration. + // Set VolumeName to any name to override the default behaviour. + // Set to "default" if you are not using namespaces within StorageOS. + // Namespaces that do not pre-exist within StorageOS will be created. + // +optional + VolumeNamespace string + // Filesystem type to mount. + // Must be a filesystem type supported by the host operating system. + // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + // +optional + FSType string + // Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + ReadOnly bool + // SecretRef specifies the secret to use for obtaining the StorageOS API + // credentials. If not specified, default values will be attempted. + // +optional + SecretRef *ObjectReference +} + +// Adapts a ConfigMap into a volume. +// +// The contents of the target ConfigMap's Data field will be presented in a +// volume as files using the keys in the Data field as the file names, unless +// the items element is populated with specific mappings of keys to paths. +// ConfigMap volumes support ownership management and SELinux relabeling. +type ConfigMapVolumeSource struct { + LocalObjectReference + // If unspecified, each key-value pair in the Data field of the referenced + // ConfigMap will be projected into the volume as a file whose name is the + // key and content is the value. If specified, the listed keys will be + // projected into the specified paths, and unlisted keys will not be + // present. If a key is specified which is not present in the ConfigMap, + // the volume setup will error unless it is marked optional. Paths must be + // relative and may not contain the '..' path or start with '..'. + // +optional + Items []KeyToPath + // Mode bits to use on created files by default. Must be a value between + // 0 and 0777. + // Directories within the path are not affected by this setting. + // This might be in conflict with other options that affect the file + // mode, like fsGroup, and the result can be other mode bits set. + // +optional + DefaultMode *int32 + // Specify whether the ConfigMap or it's keys must be defined + // +optional + Optional *bool +} + +// Adapts a ConfigMap into a projected volume. +// +// The contents of the target ConfigMap's Data field will be presented in a +// projected volume as files using the keys in the Data field as the file names, +// unless the items element is populated with specific mappings of keys to paths. +// Note that this is identical to a configmap volume source without the default +// mode. +type ConfigMapProjection struct { + LocalObjectReference + // If unspecified, each key-value pair in the Data field of the referenced + // ConfigMap will be projected into the volume as a file whose name is the + // key and content is the value. If specified, the listed keys will be + // projected into the specified paths, and unlisted keys will not be + // present. If a key is specified which is not present in the ConfigMap, + // the volume setup will error unless it is marked optional. Paths must be + // relative and may not contain the '..' path or start with '..'. + // +optional + Items []KeyToPath + // Specify whether the ConfigMap or it's keys must be defined + // +optional + Optional *bool +} + +// Represents a projected volume source +type ProjectedVolumeSource struct { + // list of volume projections + Sources []VolumeProjection + // Mode bits to use on created files by default. Must be a value between + // 0 and 0777. + // Directories within the path are not affected by this setting. + // This might be in conflict with other options that affect the file + // mode, like fsGroup, and the result can be other mode bits set. + // +optional + DefaultMode *int32 +} + +// Projection that may be projected along with other supported volume types +type VolumeProjection struct { + // all types below are the supported types for projection into the same volume + + // information about the secret data to project + Secret *SecretProjection + // information about the downwardAPI data to project + DownwardAPI *DownwardAPIProjection + // information about the configMap data to project + ConfigMap *ConfigMapProjection +} + +// Maps a string key to a path within a volume. +type KeyToPath struct { + // The key to project. + Key string + + // The relative path of the file to map the key to. + // May not be an absolute path. + // May not contain the path element '..'. + // May not start with the string '..'. + Path string + // Optional: mode bits to use on this file, should be a value between 0 + // and 0777. If not specified, the volume defaultMode will be used. + // This might be in conflict with other options that affect the file + // mode, like fsGroup, and the result can be other mode bits set. + // +optional + Mode *int32 +} + +// Local represents directly-attached storage with node affinity +type LocalVolumeSource struct { + // The full path to the volume on the node + // For alpha, this path must be a directory + // Once block as a source is supported, then this path can point to a block device + Path string +} + +// Represents storage that is managed by an external CSI volume driver +type CSIPersistentVolumeSource struct { + // Driver is the name of the driver to use for this volume. + // Required. + Driver string + + // VolumeHandle is the unique volume name returned by the CSI volume + // plugin’s CreateVolume to refer to the volume on all subsequent calls. + // Required. + VolumeHandle string + + // Optional: The value to pass to ControllerPublishVolumeRequest. + // Defaults to false (read/write). + // +optional + ReadOnly bool +} + +// ContainerPort represents a network port in a single container +type ContainerPort struct { + // Optional: If specified, this must be an IANA_SVC_NAME Each named port + // in a pod must have a unique name. + // +optional + Name string + // Optional: If specified, this must be a valid port number, 0 < x < 65536. + // If HostNetwork is specified, this must match ContainerPort. + // +optional + HostPort int32 + // Required: This must be a valid port number, 0 < x < 65536. + ContainerPort int32 + // Required: Supports "TCP" and "UDP". + // +optional + Protocol Protocol + // Optional: What host IP to bind the external port to. + // +optional + HostIP string +} + +// VolumeMount describes a mounting of a Volume within a container. +type VolumeMount struct { + // Required: This must match the Name of a Volume [above]. + Name string + // Optional: Defaults to false (read-write). + // +optional + ReadOnly bool + // Required. If the path is not an absolute path (e.g. some/path) it + // will be prepended with the appropriate root prefix for the operating + // system. On Linux this is '/', on Windows this is 'C:\'. + MountPath string + // Path within the volume from which the container's volume should be mounted. + // Defaults to "" (volume's root). + // +optional + SubPath string + // mountPropagation determines how mounts are propagated from the host + // to container and the other way around. + // When not set, MountPropagationHostToContainer is used. + // This field is alpha in 1.8 and can be reworked or removed in a future + // release. + // +optional + MountPropagation *MountPropagationMode +} + +// MountPropagationMode describes mount propagation. +type MountPropagationMode string + +const ( + // MountPropagationHostToContainer means that the volume in a container will + // receive new mounts from the host or other containers, but filesystems + // mounted inside the container won't be propagated to the host or other + // containers. + // Note that this mode is recursively applied to all mounts in the volume + // ("rslave" in Linux terminology). + MountPropagationHostToContainer MountPropagationMode = "HostToContainer" + // MountPropagationBidirectional means that the volume in a container will + // receive new mounts from the host or other containers, and its own mounts + // will be propagated from the container to the host or other containers. + // Note that this mode is recursively applied to all mounts in the volume + // ("rshared" in Linux terminology). + MountPropagationBidirectional MountPropagationMode = "Bidirectional" +) + +// VolumeDevice describes a mapping of a raw block device within a container. +type VolumeDevice struct { + // name must match the name of a persistentVolumeClaim in the pod + Name string + // devicePath is the path inside of the container that the device will be mapped to. + DevicePath string +} + +// EnvVar represents an environment variable present in a Container. +type EnvVar struct { + // Required: This must be a C_IDENTIFIER. + Name string + // Optional: no more than one of the following may be specified. + // Optional: Defaults to ""; variable references $(VAR_NAME) are expanded + // using the previous defined environment variables in the container and + // any service environment variables. If a variable cannot be resolved, + // the reference in the input string will be unchanged. The $(VAR_NAME) + // syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped + // references will never be expanded, regardless of whether the variable + // exists or not. + // +optional + Value string + // Optional: Specifies a source the value of this var should come from. + // +optional + ValueFrom *EnvVarSource +} + +// EnvVarSource represents a source for the value of an EnvVar. +// Only one of its fields may be set. +type EnvVarSource struct { + // Selects a field of the pod: supports metadata.name, metadata.namespace, metadata.labels, metadata.annotations, + // metadata.uid, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP. + // +optional + FieldRef *ObjectFieldSelector + // Selects a resource of the container: only resources limits and requests + // (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + // +optional + ResourceFieldRef *ResourceFieldSelector + // Selects a key of a ConfigMap. + // +optional + ConfigMapKeyRef *ConfigMapKeySelector + // Selects a key of a secret in the pod's namespace. + // +optional + SecretKeyRef *SecretKeySelector +} + +// ObjectFieldSelector selects an APIVersioned field of an object. +type ObjectFieldSelector struct { + // Required: Version of the schema the FieldPath is written in terms of. + // If no value is specified, it will be defaulted to the APIVersion of the + // enclosing object. + APIVersion string + // Required: Path of the field to select in the specified API version + FieldPath string +} + +// ResourceFieldSelector represents container resources (cpu, memory) and their output format +type ResourceFieldSelector struct { + // Container name: required for volumes, optional for env vars + // +optional + ContainerName string + // Required: resource to select + Resource string + // Specifies the output format of the exposed resources, defaults to "1" + // +optional + Divisor resource.Quantity +} + +// Selects a key from a ConfigMap. +type ConfigMapKeySelector struct { + // The ConfigMap to select from. + LocalObjectReference + // The key to select. + Key string + // Specify whether the ConfigMap or it's key must be defined + // +optional + Optional *bool +} + +// SecretKeySelector selects a key of a Secret. +type SecretKeySelector struct { + // The name of the secret in the pod's namespace to select from. + LocalObjectReference + // The key of the secret to select from. Must be a valid secret key. + Key string + // Specify whether the Secret or it's key must be defined + // +optional + Optional *bool +} + +// EnvFromSource represents the source of a set of ConfigMaps +type EnvFromSource struct { + // An optional identifier to prepend to each key in the ConfigMap. + // +optional + Prefix string + // The ConfigMap to select from. + //+optional + ConfigMapRef *ConfigMapEnvSource + // The Secret to select from. + //+optional + SecretRef *SecretEnvSource +} + +// ConfigMapEnvSource selects a ConfigMap to populate the environment +// variables with. +// +// The contents of the target ConfigMap's Data field will represent the +// key-value pairs as environment variables. +type ConfigMapEnvSource struct { + // The ConfigMap to select from. + LocalObjectReference + // Specify whether the ConfigMap must be defined + // +optional + Optional *bool +} + +// SecretEnvSource selects a Secret to populate the environment +// variables with. +// +// The contents of the target Secret's Data field will represent the +// key-value pairs as environment variables. +type SecretEnvSource struct { + // The Secret to select from. + LocalObjectReference + // Specify whether the Secret must be defined + // +optional + Optional *bool +} + +// HTTPHeader describes a custom header to be used in HTTP probes +type HTTPHeader struct { + // The header field name + Name string + // The header field value + Value string +} + +// HTTPGetAction describes an action based on HTTP Get requests. +type HTTPGetAction struct { + // Optional: Path to access on the HTTP server. + // +optional + Path string + // Required: Name or number of the port to access on the container. + // +optional + Port intstr.IntOrString + // Optional: Host name to connect to, defaults to the pod IP. You + // probably want to set "Host" in httpHeaders instead. + // +optional + Host string + // Optional: Scheme to use for connecting to the host, defaults to HTTP. + // +optional + Scheme URIScheme + // Optional: Custom headers to set in the request. HTTP allows repeated headers. + // +optional + HTTPHeaders []HTTPHeader +} + +// URIScheme identifies the scheme used for connection to a host for Get actions +type URIScheme string + +const ( + // URISchemeHTTP means that the scheme used will be http:// + URISchemeHTTP URIScheme = "HTTP" + // URISchemeHTTPS means that the scheme used will be https:// + URISchemeHTTPS URIScheme = "HTTPS" +) + +// TCPSocketAction describes an action based on opening a socket +type TCPSocketAction struct { + // Required: Port to connect to. + // +optional + Port intstr.IntOrString + // Optional: Host name to connect to, defaults to the pod IP. + // +optional + Host string +} + +// ExecAction describes a "run in container" action. +type ExecAction struct { + // Command is the command line to execute inside the container, the working directory for the + // command is root ('/') in the container's filesystem. The command is simply exec'd, it is + // not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + // a shell, you need to explicitly call out to that shell. + // +optional + Command []string +} + +// Probe describes a health check to be performed against a container to determine whether it is +// alive or ready to receive traffic. +type Probe struct { + // The action taken to determine the health of a container + Handler + // Length of time before health checking is activated. In seconds. + // +optional + InitialDelaySeconds int32 + // Length of time before health checking times out. In seconds. + // +optional + TimeoutSeconds int32 + // How often (in seconds) to perform the probe. + // +optional + PeriodSeconds int32 + // Minimum consecutive successes for the probe to be considered successful after having failed. + // Must be 1 for liveness. + // +optional + SuccessThreshold int32 + // Minimum consecutive failures for the probe to be considered failed after having succeeded. + // +optional + FailureThreshold int32 +} + +// PullPolicy describes a policy for if/when to pull a container image +type PullPolicy string + +const ( + // PullAlways means that kubelet always attempts to pull the latest image. Container will fail If the pull fails. + PullAlways PullPolicy = "Always" + // PullNever means that kubelet never pulls an image, but only uses a local image. Container will fail if the image isn't present + PullNever PullPolicy = "Never" + // PullIfNotPresent means that kubelet pulls if the image isn't present on disk. Container will fail if the image isn't present and the pull fails. + PullIfNotPresent PullPolicy = "IfNotPresent" +) + +// TerminationMessagePolicy describes how termination messages are retrieved from a container. +type TerminationMessagePolicy string + +const ( + // TerminationMessageReadFile is the default behavior and will set the container status message to + // the contents of the container's terminationMessagePath when the container exits. + TerminationMessageReadFile TerminationMessagePolicy = "File" + // TerminationMessageFallbackToLogsOnError will read the most recent contents of the container logs + // for the container status message when the container exits with an error and the + // terminationMessagePath has no contents. + TerminationMessageFallbackToLogsOnError TerminationMessagePolicy = "FallbackToLogsOnError" +) + +// Capability represent POSIX capabilities type +type Capability string + +// Capabilities represent POSIX capabilities that can be added or removed to a running container. +type Capabilities struct { + // Added capabilities + // +optional + Add []Capability + // Removed capabilities + // +optional + Drop []Capability +} + +// ResourceRequirements describes the compute resource requirements. +type ResourceRequirements struct { + // Limits describes the maximum amount of compute resources allowed. + // +optional + Limits ResourceList + // Requests describes the minimum amount of compute resources required. + // If Request is omitted for a container, it defaults to Limits if that is explicitly specified, + // otherwise to an implementation-defined value + // +optional + Requests ResourceList +} + +// Container represents a single container that is expected to be run on the host. +type Container struct { + // Required: This must be a DNS_LABEL. Each container in a pod must + // have a unique name. + Name string + // Required. + Image string + // Optional: The docker image's entrypoint is used if this is not provided; cannot be updated. + // Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + // cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax + // can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, + // regardless of whether the variable exists or not. + // +optional + Command []string + // Optional: The docker image's cmd is used if this is not provided; cannot be updated. + // Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + // cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax + // can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, + // regardless of whether the variable exists or not. + // +optional + Args []string + // Optional: Defaults to Docker's default. + // +optional + WorkingDir string + // +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 + // 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. + // Cannot be updated. + // +optional + EnvFrom []EnvFromSource + // +optional + Env []EnvVar + // Compute resource requirements. + // +optional + Resources ResourceRequirements + // +optional + VolumeMounts []VolumeMount + // volumeDevices is the list of block devices to be used by the container. + // This is an alpha feature and may change in the future. + // +optional + VolumeDevices []VolumeDevice + // +optional + LivenessProbe *Probe + // +optional + ReadinessProbe *Probe + // +optional + Lifecycle *Lifecycle + // Required. + // +optional + TerminationMessagePath string + // +optional + TerminationMessagePolicy TerminationMessagePolicy + // Required: Policy for pulling images for this container + ImagePullPolicy PullPolicy + // Optional: SecurityContext defines the security options the container should be run with. + // If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext. + // +optional + SecurityContext *SecurityContext + + // Variables for interactive containers, these have very specialized use-cases (e.g. debugging) + // and shouldn't be used for general purpose containers. + // +optional + Stdin bool + // +optional + StdinOnce bool + // +optional + TTY bool +} + +// Handler defines a specific action that should be taken +// TODO: pass structured data to these actions, and document that data here. +type Handler struct { + // One and only one of the following should be specified. + // Exec specifies the action to take. + // +optional + Exec *ExecAction + // HTTPGet specifies the http request to perform. + // +optional + HTTPGet *HTTPGetAction + // TCPSocket specifies an action involving a TCP port. + // TODO: implement a realistic TCP lifecycle hook + // +optional + TCPSocket *TCPSocketAction +} + +// Lifecycle describes actions that the management system should take in response to container lifecycle +// events. For the PostStart and PreStop lifecycle handlers, management of the container blocks +// until the action is complete, unless the container process fails, in which case the handler is aborted. +type Lifecycle struct { + // PostStart is called immediately after a container is created. If the handler fails, the container + // is terminated and restarted. + // +optional + PostStart *Handler + // PreStop is called immediately before a container is terminated. The reason for termination is + // passed to the handler. Regardless of the outcome of the handler, the container is eventually terminated. + // +optional + PreStop *Handler +} + +// The below types are used by kube_client and api_server. + +type ConditionStatus string + +// These are valid condition statuses. "ConditionTrue" means a resource is in the condition; +// "ConditionFalse" means a resource is not in the condition; "ConditionUnknown" means kubernetes +// can't decide if a resource is in the condition or not. In the future, we could add other +// intermediate conditions, e.g. ConditionDegraded. +const ( + ConditionTrue ConditionStatus = "True" + ConditionFalse ConditionStatus = "False" + ConditionUnknown ConditionStatus = "Unknown" +) + +type ContainerStateWaiting struct { + // A brief CamelCase string indicating details about why the container is in waiting state. + // +optional + Reason string + // A human-readable message indicating details about why the container is in waiting state. + // +optional + Message string +} + +type ContainerStateRunning struct { + // +optional + StartedAt metav1.Time +} + +type ContainerStateTerminated struct { + ExitCode int32 + // +optional + Signal int32 + // +optional + Reason string + // +optional + Message string + // +optional + StartedAt metav1.Time + // +optional + FinishedAt metav1.Time + // +optional + ContainerID string +} + +// ContainerState holds a possible state of container. +// Only one of its members may be specified. +// If none of them is specified, the default one is ContainerStateWaiting. +type ContainerState struct { + // +optional + Waiting *ContainerStateWaiting + // +optional + Running *ContainerStateRunning + // +optional + Terminated *ContainerStateTerminated +} + +type ContainerStatus struct { + // Each container in a pod must have a unique name. + Name string + // +optional + State ContainerState + // +optional + LastTerminationState ContainerState + // Ready specifies whether the container has passed its readiness check. + Ready bool + // Note that this is calculated from dead containers. But those containers are subject to + // garbage collection. This value will get capped at 5 by GC. + RestartCount int32 + Image string + ImageID string + // +optional + ContainerID string +} + +// PodPhase is a label for the condition of a pod at the current time. +type PodPhase string + +// These are the valid statuses of pods. +const ( + // PodPending means the pod has been accepted by the system, but one or more of the containers + // has not been started. This includes time before being bound to a node, as well as time spent + // pulling images onto the host. + PodPending PodPhase = "Pending" + // PodRunning means the pod has been bound to a node and all of the containers have been started. + // At least one container is still running or is in the process of being restarted. + PodRunning PodPhase = "Running" + // PodSucceeded means that all containers in the pod have voluntarily terminated + // with a container exit code of 0, and the system is not going to restart any of these containers. + PodSucceeded PodPhase = "Succeeded" + // PodFailed means that all containers in the pod have terminated, and at least one container has + // terminated in a failure (exited with a non-zero exit code or was stopped by the system). + PodFailed PodPhase = "Failed" + // PodUnknown means that 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. + PodUnknown PodPhase = "Unknown" +) + +type PodConditionType string + +// These are valid conditions of pod. +const ( + // PodScheduled represents status of the scheduling process for this pod. + PodScheduled PodConditionType = "PodScheduled" + // PodReady means the pod is able to service requests and should be added to the + // load balancing pools of all matching services. + PodReady PodConditionType = "Ready" + // PodInitialized means that all init containers in the pod have started successfully. + PodInitialized PodConditionType = "Initialized" + // PodReasonUnschedulable reason in PodScheduled PodCondition means that the scheduler + // can't schedule the pod right now, for example due to insufficient resources in the cluster. + PodReasonUnschedulable = "Unschedulable" +) + +type PodCondition struct { + Type PodConditionType + Status ConditionStatus + // +optional + LastProbeTime metav1.Time + // +optional + LastTransitionTime metav1.Time + // +optional + Reason string + // +optional + Message string +} + +// RestartPolicy describes how the container should be restarted. +// Only one of the following restart policies may be specified. +// If none of the following policies is specified, the default one +// is RestartPolicyAlways. +type RestartPolicy string + +const ( + RestartPolicyAlways RestartPolicy = "Always" + RestartPolicyOnFailure RestartPolicy = "OnFailure" + RestartPolicyNever RestartPolicy = "Never" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PodList is a list of Pods. +type PodList struct { + metav1.TypeMeta + // +optional + metav1.ListMeta + + Items []Pod +} + +// DNSPolicy defines how a pod's DNS will be configured. +type DNSPolicy string + +const ( + // DNSClusterFirstWithHostNet indicates that the pod should use cluster DNS + // first, if it is available, then fall back on the default + // (as determined by kubelet) DNS settings. + DNSClusterFirstWithHostNet DNSPolicy = "ClusterFirstWithHostNet" + + // DNSClusterFirst indicates that the pod should use cluster DNS + // first unless hostNetwork is true, if it is available, then + // fall back on the default (as determined by kubelet) DNS settings. + DNSClusterFirst DNSPolicy = "ClusterFirst" + + // DNSDefault indicates that the pod should use the default (as + // determined by kubelet) DNS settings. + DNSDefault DNSPolicy = "Default" + + // DNSNone indicates that the pod should use empty DNS settings. DNS + // parameters such as nameservers and search paths should be defined via + // DNSConfig. + DNSNone DNSPolicy = "None" +) + +// A node selector represents the union of the results of one or more label queries +// over a set of nodes; that is, it represents the OR of the selectors represented +// by the node selector terms. +type NodeSelector struct { + //Required. A list of node selector terms. The terms are ORed. + NodeSelectorTerms []NodeSelectorTerm +} + +// A null or empty node selector term matches no objects. +type NodeSelectorTerm struct { + //Required. A list of node selector requirements. The requirements are ANDed. + MatchExpressions []NodeSelectorRequirement +} + +// A node selector requirement is a selector that contains values, a key, and an operator +// that relates the key and values. +type NodeSelectorRequirement struct { + // The label key that the selector applies to. + Key string + // Represents a key's relationship to a set of values. + // Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + Operator NodeSelectorOperator + // An array of string values. If the operator is In or NotIn, + // the values array must be non-empty. If the operator is Exists or DoesNotExist, + // the values array must be empty. If the operator is Gt or Lt, the values + // array must have a single element, which will be interpreted as an integer. + // This array is replaced during a strategic merge patch. + // +optional + Values []string +} + +// A node selector operator is the set of operators that can be used in +// a node selector requirement. +type NodeSelectorOperator string + +const ( + NodeSelectorOpIn NodeSelectorOperator = "In" + NodeSelectorOpNotIn NodeSelectorOperator = "NotIn" + NodeSelectorOpExists NodeSelectorOperator = "Exists" + NodeSelectorOpDoesNotExist NodeSelectorOperator = "DoesNotExist" + NodeSelectorOpGt NodeSelectorOperator = "Gt" + NodeSelectorOpLt NodeSelectorOperator = "Lt" +) + +// Affinity is a group of affinity scheduling rules. +type Affinity struct { + // Describes node affinity scheduling rules for the pod. + // +optional + NodeAffinity *NodeAffinity + // Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). + // +optional + PodAffinity *PodAffinity + // Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). + // +optional + PodAntiAffinity *PodAntiAffinity +} + +// Pod affinity is a group of inter pod affinity scheduling rules. +type PodAffinity struct { + // NOT YET IMPLEMENTED. TODO: Uncomment field once it is implemented. + // If the affinity requirements specified by this field are not met at + // scheduling time, the pod will not be scheduled onto the node. + // If the affinity requirements specified by this field cease to be met + // at some point during pod execution (e.g. due to a pod label update), the + // system will try to eventually evict the pod from its node. + // When there are multiple elements, the lists of nodes corresponding to each + // podAffinityTerm are intersected, i.e. all terms must be satisfied. + // +optional + // RequiredDuringSchedulingRequiredDuringExecution []PodAffinityTerm + + // If the affinity requirements specified by this field are not met at + // scheduling time, the pod will not be scheduled onto the node. + // If the affinity requirements specified by this field cease to be met + // at some point during pod execution (e.g. due to a pod label update), the + // system may or may not try to eventually evict the pod from its node. + // When there are multiple elements, the lists of nodes corresponding to each + // podAffinityTerm are intersected, i.e. all terms must be satisfied. + // +optional + RequiredDuringSchedulingIgnoredDuringExecution []PodAffinityTerm + // The scheduler will prefer to schedule pods to nodes that satisfy + // the affinity expressions specified by this field, but it may choose + // a node that violates one or more of the expressions. The node that is + // most preferred is the one with the greatest sum of weights, i.e. + // for each node that meets all of the scheduling requirements (resource + // request, requiredDuringScheduling affinity expressions, etc.), + // compute a sum by iterating through the elements of this field and adding + // "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + // node(s) with the highest sum are the most preferred. + // +optional + PreferredDuringSchedulingIgnoredDuringExecution []WeightedPodAffinityTerm +} + +// Pod anti affinity is a group of inter pod anti affinity scheduling rules. +type PodAntiAffinity struct { + // NOT YET IMPLEMENTED. TODO: Uncomment field once it is implemented. + // If the anti-affinity requirements specified by this field are not met at + // scheduling time, the pod will not be scheduled onto the node. + // If the anti-affinity requirements specified by this field cease to be met + // at some point during pod execution (e.g. due to a pod label update), the + // system will try to eventually evict the pod from its node. + // When there are multiple elements, the lists of nodes corresponding to each + // podAffinityTerm are intersected, i.e. all terms must be satisfied. + // +optional + // RequiredDuringSchedulingRequiredDuringExecution []PodAffinityTerm + + // If the anti-affinity requirements specified by this field are not met at + // scheduling time, the pod will not be scheduled onto the node. + // If the anti-affinity requirements specified by this field cease to be met + // at some point during pod execution (e.g. due to a pod label update), the + // system may or may not try to eventually evict the pod from its node. + // When there are multiple elements, the lists of nodes corresponding to each + // podAffinityTerm are intersected, i.e. all terms must be satisfied. + // +optional + RequiredDuringSchedulingIgnoredDuringExecution []PodAffinityTerm + // The scheduler will prefer to schedule pods to nodes that satisfy + // the anti-affinity expressions specified by this field, but it may choose + // a node that violates one or more of the expressions. The node that is + // most preferred is the one with the greatest sum of weights, i.e. + // for each node that meets all of the scheduling requirements (resource + // request, requiredDuringScheduling anti-affinity expressions, etc.), + // compute a sum by iterating through the elements of this field and adding + // "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + // node(s) with the highest sum are the most preferred. + // +optional + PreferredDuringSchedulingIgnoredDuringExecution []WeightedPodAffinityTerm +} + +// The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) +type WeightedPodAffinityTerm struct { + // weight associated with matching the corresponding podAffinityTerm, + // in the range 1-100. + Weight int32 + // Required. A pod affinity term, associated with the corresponding weight. + PodAffinityTerm PodAffinityTerm +} + +// Defines a set of pods (namely those matching the labelSelector +// relative to the given namespace(s)) that this pod should be +// co-located (affinity) or not co-located (anti-affinity) with, +// where co-located is defined as running on a node whose value of +// the label with key matches that of any node on which +// a pod of the set of pods is running. +type PodAffinityTerm struct { + // A label query over a set of resources, in this case pods. + // +optional + LabelSelector *metav1.LabelSelector + // namespaces specifies which namespaces the labelSelector applies to (matches against); + // null or empty list means "this pod's namespace" + // +optional + Namespaces []string + // This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + // the labelSelector in the specified namespaces, where co-located is defined as running on a node + // whose value of the label with key topologyKey matches that of any node on which any of the + // selected pods is running. + // Empty topologyKey is not allowed. + TopologyKey string +} + +// Node affinity is a group of node affinity scheduling rules. +type NodeAffinity struct { + // NOT YET IMPLEMENTED. TODO: Uncomment field once it is implemented. + // If the affinity requirements specified by this field are not met at + // scheduling time, the pod will not be scheduled onto the node. + // If the affinity requirements specified by this field cease to be met + // at some point during pod execution (e.g. due to an update), the system + // will try to eventually evict the pod from its node. + // +optional + // RequiredDuringSchedulingRequiredDuringExecution *NodeSelector + + // If the affinity requirements specified by this field are not met at + // scheduling time, the pod will not be scheduled onto the node. + // If the affinity requirements specified by this field cease to be met + // at some point during pod execution (e.g. due to an update), the system + // may or may not try to eventually evict the pod from its node. + // +optional + RequiredDuringSchedulingIgnoredDuringExecution *NodeSelector + // The scheduler will prefer to schedule pods to nodes that satisfy + // the affinity expressions specified by this field, but it may choose + // a node that violates one or more of the expressions. The node that is + // most preferred is the one with the greatest sum of weights, i.e. + // for each node that meets all of the scheduling requirements (resource + // request, requiredDuringScheduling affinity expressions, etc.), + // compute a sum by iterating through the elements of this field and adding + // "weight" to the sum if the node matches the corresponding matchExpressions; the + // node(s) with the highest sum are the most preferred. + // +optional + PreferredDuringSchedulingIgnoredDuringExecution []PreferredSchedulingTerm +} + +// An empty preferred scheduling term matches all objects with implicit weight 0 +// (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). +type PreferredSchedulingTerm struct { + // Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. + Weight int32 + // A node selector term, associated with the corresponding weight. + Preference NodeSelectorTerm +} + +// The node this Taint is attached to has the "effect" on +// any pod that does not tolerate the Taint. +type Taint struct { + // Required. The taint key to be applied to a node. + Key string + // Required. The taint value corresponding to the taint key. + // +optional + Value string + // Required. The effect of the taint on pods + // that do not tolerate the taint. + // Valid effects are NoSchedule, PreferNoSchedule and NoExecute. + Effect TaintEffect + // TimeAdded represents the time at which the taint was added. + // It is only written for NoExecute taints. + // +optional + TimeAdded *metav1.Time +} + +type TaintEffect string + +const ( + // Do not allow new pods to schedule onto the node unless they tolerate the taint, + // but allow all pods submitted to Kubelet without going through the scheduler + // to start, and allow all already-running pods to continue running. + // Enforced by the scheduler. + TaintEffectNoSchedule TaintEffect = "NoSchedule" + // Like TaintEffectNoSchedule, but the scheduler tries not to schedule + // new pods onto the node, rather than prohibiting new pods from scheduling + // onto the node entirely. Enforced by the scheduler. + TaintEffectPreferNoSchedule TaintEffect = "PreferNoSchedule" + // NOT YET IMPLEMENTED. TODO: Uncomment field once it is implemented. + // Like TaintEffectNoSchedule, but additionally do not allow pods submitted to + // Kubelet without going through the scheduler to start. + // Enforced by Kubelet and the scheduler. + // TaintEffectNoScheduleNoAdmit TaintEffect = "NoScheduleNoAdmit" + + // Evict any already-running pods that do not tolerate the taint. + // Currently enforced by NodeController. + TaintEffectNoExecute TaintEffect = "NoExecute" +) + +// The pod this Toleration is attached to tolerates any taint that matches +// the triple using the matching operator . +type Toleration struct { + // 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. + // +optional + Key string + // Operator represents a key's relationship to the value. + // Valid operators are Exists and Equal. Defaults to Equal. + // Exists is equivalent to wildcard for value, so that a pod can + // tolerate all taints of a particular category. + // +optional + Operator TolerationOperator + // Value is the taint value the toleration matches to. + // If the operator is Exists, the value should be empty, otherwise just a regular string. + // +optional + Value string + // Effect indicates the taint effect to match. Empty means match all taint effects. + // When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + // +optional + Effect TaintEffect + // TolerationSeconds represents the period of time the toleration (which must be + // of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + // it is not set, which means tolerate the taint forever (do not evict). Zero and + // negative values will be treated as 0 (evict immediately) by the system. + // +optional + TolerationSeconds *int64 +} + +// A toleration operator is the set of operators that can be used in a toleration. +type TolerationOperator string + +const ( + TolerationOpExists TolerationOperator = "Exists" + TolerationOpEqual TolerationOperator = "Equal" +) + +// PodSpec is a description of a pod +type PodSpec struct { + Volumes []Volume + // List of initialization containers belonging to the pod. + InitContainers []Container + // List of containers belonging to the pod. + Containers []Container + // +optional + RestartPolicy RestartPolicy + // Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. + // Value must be non-negative integer. The value zero indicates delete immediately. + // If this value is nil, the default grace period will be used instead. + // The grace period is the duration in seconds after the processes running in the pod are sent + // a termination signal and the time when the processes are forcibly halted with a kill signal. + // Set this value longer than the expected cleanup time for your process. + // +optional + TerminationGracePeriodSeconds *int64 + // Optional duration in seconds relative to the StartTime that the pod may be active on a node + // before the system actively tries to terminate the pod; value must be positive integer + // +optional + ActiveDeadlineSeconds *int64 + // Set DNS policy for the pod. + // Defaults to "ClusterFirst". + // Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. + // DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. + // To have DNS options set along with hostNetwork, you have to specify DNS policy + // explicitly to 'ClusterFirstWithHostNet'. + // +optional + DNSPolicy DNSPolicy + // NodeSelector is a selector which must be true for the pod to fit on a node + // +optional + NodeSelector map[string]string + + // ServiceAccountName is the name of the ServiceAccount to use to run this pod + // The pod will be allowed to use secrets referenced by the ServiceAccount + ServiceAccountName string + // AutomountServiceAccountToken indicates whether a service account token should be automatically mounted. + // +optional + AutomountServiceAccountToken *bool + + // NodeName is a request to schedule this pod onto a specific node. If it is non-empty, + // the scheduler simply schedules this pod onto that node, assuming that it fits resource + // requirements. + // +optional + NodeName string + // SecurityContext holds pod-level security attributes and common container settings. + // Optional: Defaults to empty. See type description for default values of each field. + // +optional + SecurityContext *PodSecurityContext + // ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. + // If specified, these secrets will be passed to individual puller implementations for them to use. For example, + // in the case of docker, only DockerConfig type secrets are honored. + // +optional + ImagePullSecrets []LocalObjectReference + // Specifies the hostname of the Pod. + // If not specified, the pod's hostname will be set to a system-defined value. + // +optional + Hostname string + // If specified, the fully qualified Pod hostname will be "...svc.". + // If not specified, the pod will not have a domainname at all. + // +optional + Subdomain string + // If specified, the pod's scheduling constraints + // +optional + Affinity *Affinity + // If specified, the pod will be dispatched by specified scheduler. + // If not specified, the pod will be dispatched by default scheduler. + // +optional + SchedulerName string + // If specified, the pod's tolerations. + // +optional + Tolerations []Toleration + // HostAliases is an optional list of hosts and IPs that will be injected into the pod's hosts + // file if specified. This is only valid for non-hostNetwork pods. + // +optional + HostAliases []HostAlias + // If specified, indicates the pod's priority. "SYSTEM" is a special keyword + // which indicates the highest priority. Any other name must be defined by + // creating a PriorityClass object with that name. + // If not specified, the pod priority will be default or zero if there is no + // default. + // +optional + PriorityClassName string + // The priority value. Various system components use this field to find the + // priority of the pod. 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. + // +optional + Priority *int32 + // Specifies the DNS parameters of a pod. + // Parameters specified here will be merged to the generated DNS + // configuration based on DNSPolicy. + // +optional + DNSConfig *PodDNSConfig +} + +// HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the +// pod's hosts file. +type HostAlias struct { + IP string + Hostnames []string +} + +// Sysctl defines a kernel parameter to be set +type Sysctl struct { + // Name of a property to set + Name string + // Value of a property to set + Value 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. +type PodSecurityContext struct { + // Use the host's network namespace. If this option is set, the ports that will be + // used must be specified. + // Optional: Default to false + // +k8s:conversion-gen=false + // +optional + HostNetwork bool + // Use the host's pid namespace. + // Optional: Default to false. + // +k8s:conversion-gen=false + // +optional + HostPID bool + // Use the host's ipc namespace. + // Optional: Default to false. + // +k8s:conversion-gen=false + // +optional + HostIPC bool + // 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. + // +optional + SELinuxOptions *SELinuxOptions + // The UID to run the entrypoint of the container process. + // Defaults to user specified in image metadata if unspecified. + // May also be set in SecurityContext. If set in both SecurityContext and + // PodSecurityContext, the value specified in SecurityContext takes precedence + // for that container. + // +optional + RunAsUser *int64 + // 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 SecurityContext. If set in both SecurityContext and + // PodSecurityContext, the value specified in SecurityContext takes precedence. + // +optional + RunAsNonRoot *bool + // A list of groups applied to the first process run in each container, in addition + // to the container's primary GID. If unspecified, no groups will be added to + // any container. + // +optional + SupplementalGroups []int64 + // A special supplemental group that applies to all containers in a pod. + // Some volume types allow the Kubelet to change the ownership of that volume + // to be owned by the pod: + // + // 1. The owning GID will be the FSGroup + // 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + // 3. The permission bits are OR'd with rw-rw---- + // + // If unset, the Kubelet will not modify the ownership and permissions of any volume. + // +optional + FSGroup *int64 +} + +// PodQOSClass defines the supported qos classes of Pods. +type PodQOSClass string + +const ( + // PodQOSGuaranteed is the Guaranteed qos class. + PodQOSGuaranteed PodQOSClass = "Guaranteed" + // PodQOSBurstable is the Burstable qos class. + PodQOSBurstable PodQOSClass = "Burstable" + // PodQOSBestEffort is the BestEffort qos class. + PodQOSBestEffort PodQOSClass = "BestEffort" +) + +// PodDNSConfig defines the DNS parameters of a pod in addition to +// those generated from DNSPolicy. +type PodDNSConfig struct { + // A list of DNS name server IP addresses. + // This will be appended to the base nameservers generated from DNSPolicy. + // Duplicated nameservers will be removed. + // +optional + Nameservers []string + // A list of DNS search domains for host-name lookup. + // This will be appended to the base search paths generated from DNSPolicy. + // Duplicated search paths will be removed. + // +optional + Searches []string + // A list of DNS resolver options. + // This will be merged with the base options generated from DNSPolicy. + // Duplicated entries will be removed. Resolution options given in Options + // will override those that appear in the base DNSPolicy. + // +optional + Options []PodDNSConfigOption +} + +// PodDNSConfigOption defines DNS resolver options of a pod. +type PodDNSConfigOption struct { + // Required. + Name string + // +optional + Value *string +} + +// PodStatus represents information about the status of a pod. Status may trail the actual +// state of a system. +type PodStatus struct { + // +optional + Phase PodPhase + // +optional + Conditions []PodCondition + // A human readable message indicating details about why the pod is in this state. + // +optional + Message string + // A brief CamelCase message indicating details about why the pod is in this state. e.g. 'Evicted' + // +optional + Reason string + + // +optional + HostIP string + // +optional + PodIP string + + // 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. + // +optional + StartTime *metav1.Time + // +optional + QOSClass PodQOSClass + + // The list has one entry per init container in the manifest. The most recent successful + // init container will have ready = true, the most recently started container will have + // startTime set. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-and-container-status + InitContainerStatuses []ContainerStatus + // The list has one entry per container in the manifest. Each entry is + // currently the output of `docker inspect`. This output format is *not* + // final and should not be relied upon. + // TODO: Make real decisions about what our info should look like. Re-enable fuzz test + // when we have done this. + // +optional + ContainerStatuses []ContainerStatus +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PodStatusResult is a wrapper for PodStatus returned by kubelet that can be encode/decoded +type PodStatusResult struct { + metav1.TypeMeta + // +optional + metav1.ObjectMeta + // Status represents the current information about a pod. This data may not be up + // to date. + // +optional + Status PodStatus +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Pod is a collection of containers, used as either input (create, update) or as output (list, get). +type Pod struct { + metav1.TypeMeta + // +optional + metav1.ObjectMeta + + // Spec defines the behavior of a pod. + // +optional + Spec PodSpec + + // Status represents the current information about a pod. This data may not be up + // to date. + // +optional + Status PodStatus +} + +// PodTemplateSpec describes the data a pod should have when created from a template +type PodTemplateSpec struct { + // Metadata of the pods created from this template. + // +optional + metav1.ObjectMeta + + // Spec defines the behavior of a pod. + // +optional + Spec PodSpec +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PodTemplate describes a template for creating copies of a predefined pod. +type PodTemplate struct { + metav1.TypeMeta + // +optional + metav1.ObjectMeta + + // Template defines the pods that will be created from this pod template + // +optional + Template PodTemplateSpec +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PodTemplateList is a list of PodTemplates. +type PodTemplateList struct { + metav1.TypeMeta + // +optional + metav1.ListMeta + + Items []PodTemplate +} + +// ReplicationControllerSpec is the specification of a replication controller. +// As the internal representation of a replication controller, it may have either +// a TemplateRef or a Template set. +type ReplicationControllerSpec struct { + // Replicas is the number of desired replicas. + Replicas int32 + + // 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 + MinReadySeconds int32 + + // Selector is a label query over pods that should match the Replicas count. + Selector map[string]string + + // TemplateRef is a reference to an object that describes the pod that will be created if + // insufficient replicas are detected. This reference is ignored if a Template is set. + // Must be set before converting to a versioned API object + // +optional + //TemplateRef *ObjectReference + + // Template is the object that describes the pod that will be created if + // insufficient replicas are detected. Internally, this takes precedence over a + // TemplateRef. + // +optional + Template *PodTemplateSpec +} + +// ReplicationControllerStatus represents the current status of a replication +// controller. +type ReplicationControllerStatus struct { + // Replicas is the number of actual replicas. + Replicas int32 + + // The number of pods that have labels matching the labels of the pod template of the replication controller. + // +optional + FullyLabeledReplicas int32 + + // The number of ready replicas for this replication controller. + // +optional + ReadyReplicas int32 + + // The number of available replicas (ready for at least minReadySeconds) for this replication controller. + // +optional + AvailableReplicas int32 + + // ObservedGeneration is the most recent generation observed by the controller. + // +optional + ObservedGeneration int64 + + // Represents the latest available observations of a replication controller's current state. + // +optional + Conditions []ReplicationControllerCondition +} + +type ReplicationControllerConditionType string + +// These are valid conditions of a replication controller. +const ( + // ReplicationControllerReplicaFailure is added in a replication controller when one of its pods + // fails to be created due to insufficient quota, limit ranges, pod security policy, node selectors, + // etc. or deleted due to kubelet being down or finalizers are failing. + ReplicationControllerReplicaFailure ReplicationControllerConditionType = "ReplicaFailure" +) + +// ReplicationControllerCondition describes the state of a replication controller at a certain point. +type ReplicationControllerCondition struct { + // Type of replication controller condition. + Type ReplicationControllerConditionType + // Status of the condition, one of True, False, Unknown. + Status ConditionStatus + // The last time the condition transitioned from one status to another. + // +optional + LastTransitionTime metav1.Time + // The reason for the condition's last transition. + // +optional + Reason string + // A human readable message indicating details about the transition. + // +optional + Message string +} + +// +genclient +// +genclient:method=GetScale,verb=get,subresource=scale,result=k8s.io/kubernetes/pkg/apis/autoscaling.Scale +// +genclient:method=UpdateScale,verb=update,subresource=scale,input=k8s.io/kubernetes/pkg/apis/autoscaling.Scale,result=k8s.io/kubernetes/pkg/apis/autoscaling.Scale +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ReplicationController represents the configuration of a replication controller. +type ReplicationController struct { + metav1.TypeMeta + // +optional + metav1.ObjectMeta + + // Spec defines the desired behavior of this replication controller. + // +optional + Spec ReplicationControllerSpec + + // Status is the current status of this replication controller. This data may be + // out of date by some window of time. + // +optional + Status ReplicationControllerStatus +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ReplicationControllerList is a collection of replication controllers. +type ReplicationControllerList struct { + metav1.TypeMeta + // +optional + metav1.ListMeta + + Items []ReplicationController +} + +const ( + // ClusterIPNone - do not assign a cluster IP + // no proxying required and no environment variables should be created for pods + ClusterIPNone = "None" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ServiceList holds a list of services. +type ServiceList struct { + metav1.TypeMeta + // +optional + metav1.ListMeta + + Items []Service +} + +// Session Affinity Type string +type ServiceAffinity string + +const ( + // ServiceAffinityClientIP is the Client IP based. + ServiceAffinityClientIP ServiceAffinity = "ClientIP" + + // ServiceAffinityNone - no session affinity. + ServiceAffinityNone ServiceAffinity = "None" +) + +const ( + // DefaultClientIPServiceAffinitySeconds is the default timeout seconds + // of Client IP based session affinity - 3 hours. + DefaultClientIPServiceAffinitySeconds int32 = 10800 + // MaxClientIPServiceAffinitySeconds is the max timeout seconds + // of Client IP based session affinity - 1 day. + MaxClientIPServiceAffinitySeconds int32 = 86400 +) + +// SessionAffinityConfig represents the configurations of session affinity. +type SessionAffinityConfig struct { + // clientIP contains the configurations of Client IP based session affinity. + // +optional + ClientIP *ClientIPConfig +} + +// ClientIPConfig represents the configurations of Client IP based session affinity. +type ClientIPConfig struct { + // timeoutSeconds specifies the seconds of ClientIP type session sticky time. + // The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + // Default value is 10800(for 3 hours). + // +optional + TimeoutSeconds *int32 +} + +// Service Type string describes ingress methods for a service +type ServiceType string + +const ( + // ServiceTypeClusterIP means a service will only be accessible inside the + // cluster, via the ClusterIP. + ServiceTypeClusterIP ServiceType = "ClusterIP" + + // ServiceTypeNodePort means a service will be exposed on one port of + // every node, in addition to 'ClusterIP' type. + ServiceTypeNodePort ServiceType = "NodePort" + + // ServiceTypeLoadBalancer means a service will be exposed via an + // external load balancer (if the cloud provider supports it), in addition + // to 'NodePort' type. + ServiceTypeLoadBalancer ServiceType = "LoadBalancer" + + // ServiceTypeExternalName means a service consists of only a reference to + // an external name that kubedns or equivalent will return as a CNAME + // record, with no exposing or proxying of any pods involved. + ServiceTypeExternalName ServiceType = "ExternalName" +) + +// Service External Traffic Policy Type string +type ServiceExternalTrafficPolicyType string + +const ( + // ServiceExternalTrafficPolicyTypeLocal specifies node-local endpoints behavior. + ServiceExternalTrafficPolicyTypeLocal ServiceExternalTrafficPolicyType = "Local" + // ServiceExternalTrafficPolicyTypeCluster specifies cluster-wide (legacy) behavior. + ServiceExternalTrafficPolicyTypeCluster ServiceExternalTrafficPolicyType = "Cluster" +) + +// ServiceStatus represents the current status of a service +type ServiceStatus struct { + // LoadBalancer contains the current status of the load-balancer, + // if one is present. + // +optional + LoadBalancer LoadBalancerStatus +} + +// LoadBalancerStatus represents the status of a load-balancer +type LoadBalancerStatus struct { + // Ingress is a list containing ingress points for the load-balancer; + // traffic intended for the service should be sent to these ingress points. + // +optional + Ingress []LoadBalancerIngress +} + +// LoadBalancerIngress represents the status of a load-balancer ingress point: +// traffic intended for the service should be sent to an ingress point. +type LoadBalancerIngress struct { + // IP is set for load-balancer ingress points that are IP based + // (typically GCE or OpenStack load-balancers) + // +optional + IP string + + // Hostname is set for load-balancer ingress points that are DNS based + // (typically AWS load-balancers) + // +optional + Hostname string +} + +// ServiceSpec describes the attributes that a user creates on a service +type ServiceSpec struct { + // Type determines how the Service is exposed. Defaults to ClusterIP. Valid + // options are ExternalName, ClusterIP, NodePort, and LoadBalancer. + // "ExternalName" maps to the specified externalName. + // "ClusterIP" allocates a cluster-internal IP address for load-balancing to + // endpoints. Endpoints are determined by the selector or if that is not + // specified, by manual construction of an Endpoints object. If clusterIP is + // "None", no virtual IP is allocated and the endpoints are published as a + // set of endpoints rather than a stable IP. + // "NodePort" builds on ClusterIP and allocates a port on every node which + // routes to the clusterIP. + // "LoadBalancer" builds on NodePort and creates an + // external load-balancer (if supported in the current cloud) which routes + // to the clusterIP. + // More info: https://kubernetes.io/docs/concepts/services-networking/service/ + // +optional + Type ServiceType + + // Required: The list of ports that are exposed by this service. + Ports []ServicePort + + // Route service traffic to pods with label keys and values matching this + // selector. If empty or not present, the service is assumed to have an + // external process managing its endpoints, which Kubernetes will not + // modify. Only applies to types ClusterIP, NodePort, and LoadBalancer. + // Ignored if type is ExternalName. + // More info: https://kubernetes.io/docs/concepts/services-networking/service/ + Selector map[string]string + + // ClusterIP is the IP address of the service and is usually assigned + // randomly by the master. If an address is specified manually and is not in + // use by others, it will be allocated to the service; otherwise, creation + // of the service will fail. This field can not be changed through updates. + // Valid values are "None", empty string (""), or a valid IP address. "None" + // can be specified for headless services when proxying is not required. + // Only applies to types ClusterIP, NodePort, and LoadBalancer. Ignored if + // type is ExternalName. + // More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies + // +optional + ClusterIP string + + // ExternalName is the external reference that kubedns or equivalent will + // return as a CNAME record for this service. No proxying will be involved. + // Must be a valid RFC-1123 hostname (https://tools.ietf.org/html/rfc1123) + // and requires Type to be ExternalName. + ExternalName string + + // ExternalIPs are used by external load balancers, or can be set by + // users to handle external traffic that arrives at a node. + // +optional + ExternalIPs []string + + // Only applies to Service Type: LoadBalancer + // LoadBalancer will get created with the IP specified in this field. + // This feature depends on whether the underlying cloud-provider supports specifying + // the loadBalancerIP when a load balancer is created. + // This field will be ignored if the cloud-provider does not support the feature. + // +optional + LoadBalancerIP string + + // Optional: Supports "ClientIP" and "None". Used to maintain session affinity. + // +optional + SessionAffinity ServiceAffinity + + // sessionAffinityConfig contains the configurations of session affinity. + // +optional + SessionAffinityConfig *SessionAffinityConfig + + // Optional: If specified and supported by the platform, this will restrict traffic through the cloud-provider + // load-balancer will be restricted to the specified client IPs. This field will be ignored if the + // cloud-provider does not support the feature." + // +optional + LoadBalancerSourceRanges []string + + // externalTrafficPolicy denotes if this Service desires to route external + // traffic to node-local or cluster-wide endpoints. "Local" preserves the + // client source IP and avoids a second hop for LoadBalancer and Nodeport + // type services, but risks potentially imbalanced traffic spreading. + // "Cluster" obscures the client source IP and may cause a second hop to + // another node, but should have good overall load-spreading. + // +optional + ExternalTrafficPolicy ServiceExternalTrafficPolicyType + + // healthCheckNodePort specifies the healthcheck nodePort for the service. + // If not specified, HealthCheckNodePort is created by the service api + // backend with the allocated nodePort. Will use user-specified nodePort value + // if specified by the client. Only effects when Type is set to LoadBalancer + // and ExternalTrafficPolicy is set to Local. + // +optional + HealthCheckNodePort int32 + + // publishNotReadyAddresses, when set to true, indicates that DNS implementations + // must publish the notReadyAddresses of subsets for the Endpoints associated with + // the Service. The default value is false. + // The primary use case for setting this field is to use a StatefulSet's Headless Service + // to propagate SRV records for its Pods without respect to their readiness for purpose + // of peer discovery. + // This field will replace the service.alpha.kubernetes.io/tolerate-unready-endpoints + // when that annotation is deprecated and all clients have been converted to use this + // field. + // +optional + PublishNotReadyAddresses bool +} + +type ServicePort struct { + // Optional if only one ServicePort is defined on this service: The + // name of this port within the service. This must be a DNS_LABEL. + // All ports within a ServiceSpec must have unique names. This maps to + // the 'Name' field in EndpointPort objects. + Name string + + // The IP protocol for this port. Supports "TCP" and "UDP". + Protocol Protocol + + // The port that will be exposed on the service. + Port int32 + + // Optional: The target port on pods selected by this service. If this + // is a string, it will be looked up as a named port in the target + // Pod's container ports. If this is not specified, the value + // of the 'port' field is used (an identity map). + // This field is ignored for services with clusterIP=None, and should be + // omitted or set equal to the 'port' field. + TargetPort intstr.IntOrString + + // The port on each node on which this service is exposed. + // Default is to auto-allocate a port if the ServiceType of this Service requires one. + NodePort int32 +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Service is a named abstraction of software service (for example, mysql) consisting of local port +// (for example 3306) that the proxy listens on, and the selector that determines which pods +// will answer requests sent through the proxy. +type Service struct { + metav1.TypeMeta + // +optional + metav1.ObjectMeta + + // Spec defines the behavior of a service. + // +optional + Spec ServiceSpec + + // Status represents the current status of a service. + // +optional + Status ServiceStatus +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ServiceAccount binds together: +// * a name, understood by users, and perhaps by peripheral systems, for an identity +// * a principal that can be authenticated and authorized +// * a set of secrets +type ServiceAccount struct { + metav1.TypeMeta + // +optional + metav1.ObjectMeta + + // Secrets is the list of secrets allowed to be used by pods running using this ServiceAccount + Secrets []ObjectReference + + // ImagePullSecrets is a list of references to secrets in the same namespace to use for pulling any images + // in pods that reference this ServiceAccount. ImagePullSecrets are distinct from Secrets because Secrets + // can be mounted in the pod, but ImagePullSecrets are only accessed by the kubelet. + // +optional + ImagePullSecrets []LocalObjectReference + + // AutomountServiceAccountToken indicates whether pods running as this service account should have an API token automatically mounted. + // Can be overridden at the pod level. + // +optional + AutomountServiceAccountToken *bool +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ServiceAccountList is a list of ServiceAccount objects +type ServiceAccountList struct { + metav1.TypeMeta + // +optional + metav1.ListMeta + + Items []ServiceAccount +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Endpoints is a collection of endpoints that implement the actual service. Example: +// Name: "mysvc", +// Subsets: [ +// { +// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}], +// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}] +// }, +// { +// Addresses: [{"ip": "10.10.3.3"}], +// Ports: [{"name": "a", "port": 93}, {"name": "b", "port": 76}] +// }, +// ] +type Endpoints struct { + metav1.TypeMeta + // +optional + metav1.ObjectMeta + + // The set of all endpoints is the union of all subsets. + Subsets []EndpointSubset +} + +// EndpointSubset is a group of addresses with a common set of ports. The +// expanded set of endpoints is the Cartesian product of Addresses x Ports. +// For example, given: +// { +// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}], +// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}] +// } +// The resulting set of endpoints can be viewed as: +// a: [ 10.10.1.1:8675, 10.10.2.2:8675 ], +// b: [ 10.10.1.1:309, 10.10.2.2:309 ] +type EndpointSubset struct { + Addresses []EndpointAddress + NotReadyAddresses []EndpointAddress + Ports []EndpointPort +} + +// EndpointAddress is a tuple that describes single IP address. +type EndpointAddress struct { + // The IP of this endpoint. + // IPv6 is also accepted but not fully supported on all platforms. Also, certain + // kubernetes components, like kube-proxy, are not IPv6 ready. + // TODO: This should allow hostname or IP, see #4447. + IP string + // Optional: Hostname of this endpoint + // Meant to be used by DNS servers etc. + // +optional + Hostname string + // Optional: Node hosting this endpoint. This can be used to determine endpoints local to a node. + // +optional + NodeName *string + // Optional: The kubernetes object related to the entry point. + TargetRef *ObjectReference +} + +// EndpointPort is a tuple that describes a single port. +type EndpointPort struct { + // The name of this port (corresponds to ServicePort.Name). Optional + // if only one port is defined. Must be a DNS_LABEL. + Name string + + // The port number. + Port int32 + + // The IP protocol for this port. + Protocol Protocol +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// EndpointsList is a list of endpoints. +type EndpointsList struct { + metav1.TypeMeta + // +optional + metav1.ListMeta + + Items []Endpoints +} + +// NodeSpec describes the attributes that a node is created with. +type NodeSpec struct { + // PodCIDR represents the pod IP range assigned to the node + // Note: assigning IP ranges to nodes might need to be revisited when we support migratable IPs. + // +optional + PodCIDR string + + // External ID of the node assigned by some machine database (e.g. a cloud provider) + // +optional + ExternalID string + + // ID of the node assigned by the cloud provider + // Note: format is "://" + // +optional + ProviderID string + + // Unschedulable controls node schedulability of new pods. By default node is schedulable. + // +optional + Unschedulable bool + + // If specified, the node's taints. + // +optional + Taints []Taint + + // If specified, the source to get node configuration from + // The DynamicKubeletConfig feature gate must be enabled for the Kubelet to use this field + // +optional + ConfigSource *NodeConfigSource +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// NodeConfigSource specifies a source of node configuration. Exactly one subfield must be non-nil. +type NodeConfigSource struct { + metav1.TypeMeta + ConfigMapRef *ObjectReference +} + +// DaemonEndpoint contains information about a single Daemon endpoint. +type DaemonEndpoint struct { + /* + The port tag was not properly in quotes in earlier releases, so it must be + uppercase for backwards compatibility (since it was falling back to var name of + 'Port'). + */ + + // Port number of the given endpoint. + Port int32 +} + +// NodeDaemonEndpoints lists ports opened by daemons running on the Node. +type NodeDaemonEndpoints struct { + // Endpoint on which Kubelet is listening. + // +optional + KubeletEndpoint DaemonEndpoint +} + +// NodeSystemInfo is a set of ids/uuids to uniquely identify the node. +type NodeSystemInfo struct { + // MachineID reported by the node. For unique machine identification + // in the cluster this field is preferred. Learn more from man(5) + // machine-id: http://man7.org/linux/man-pages/man5/machine-id.5.html + MachineID string + // SystemUUID reported by the node. For unique machine identification + // MachineID is preferred. This field is specific to Red Hat hosts + // https://access.redhat.com/documentation/en-US/Red_Hat_Subscription_Management/1/html/RHSM/getting-system-uuid.html + SystemUUID string + // Boot ID reported by the node. + BootID string + // Kernel Version reported by the node. + KernelVersion string + // OS Image reported by the node. + OSImage string + // ContainerRuntime Version reported by the node. + ContainerRuntimeVersion string + // Kubelet Version reported by the node. + KubeletVersion string + // KubeProxy Version reported by the node. + KubeProxyVersion string + // The Operating System reported by the node + OperatingSystem string + // The Architecture reported by the node + Architecture string +} + +// NodeStatus is information about the current status of a node. +type NodeStatus struct { + // Capacity represents the total resources of a node. + // +optional + Capacity ResourceList + // Allocatable represents the resources of a node that are available for scheduling. + // +optional + Allocatable ResourceList + // NodePhase is the current lifecycle phase of the node. + // +optional + Phase NodePhase + // Conditions is an array of current node conditions. + // +optional + Conditions []NodeCondition + // Queried from cloud provider, if available. + // +optional + Addresses []NodeAddress + // Endpoints of daemons running on the Node. + // +optional + DaemonEndpoints NodeDaemonEndpoints + // Set of ids/uuids to uniquely identify the node. + // +optional + NodeInfo NodeSystemInfo + // List of container images on this node + // +optional + Images []ContainerImage + // List of attachable volumes in use (mounted) by the node. + // +optional + VolumesInUse []UniqueVolumeName + // List of volumes that are attached to the node. + // +optional + VolumesAttached []AttachedVolume +} + +type UniqueVolumeName string + +// AttachedVolume describes a volume attached to a node +type AttachedVolume struct { + // Name of the attached volume + Name UniqueVolumeName + + // DevicePath represents the device path where the volume should be available + DevicePath string +} + +// AvoidPods describes pods that should avoid this node. This is the value for a +// Node annotation with key scheduler.alpha.kubernetes.io/preferAvoidPods and +// will eventually become a field of NodeStatus. +type AvoidPods struct { + // Bounded-sized list of signatures of pods that should avoid this node, sorted + // in timestamp order from oldest to newest. Size of the slice is unspecified. + // +optional + PreferAvoidPods []PreferAvoidPodsEntry +} + +// Describes a class of pods that should avoid this node. +type PreferAvoidPodsEntry struct { + // The class of pods. + PodSignature PodSignature + // Time at which this entry was added to the list. + // +optional + EvictionTime metav1.Time + // (brief) reason why this entry was added to the list. + // +optional + Reason string + // Human readable message indicating why this entry was added to the list. + // +optional + Message string +} + +// Describes the class of pods that should avoid this node. +// Exactly one field should be set. +type PodSignature struct { + // Reference to controller whose pods should avoid this node. + // +optional + PodController *metav1.OwnerReference +} + +// Describe a container image +type ContainerImage struct { + // Names by which this image is known. + Names []string + // The size of the image in bytes. + // +optional + SizeBytes int64 +} + +type NodePhase string + +// These are the valid phases of node. +const ( + // NodePending means the node has been created/added by the system, but not configured. + NodePending NodePhase = "Pending" + // NodeRunning means the node has been configured and has Kubernetes components running. + NodeRunning NodePhase = "Running" + // NodeTerminated means the node has been removed from the cluster. + NodeTerminated NodePhase = "Terminated" +) + +type NodeConditionType string + +// These are valid conditions of node. Currently, we don't have enough information to decide +// node condition. In the future, we will add more. The proposed set of conditions are: +// NodeReady, NodeReachable +const ( + // NodeReady means kubelet is healthy and ready to accept pods. + NodeReady NodeConditionType = "Ready" + // NodeOutOfDisk means the kubelet will not accept new pods due to insufficient free disk + // space on the node. + NodeOutOfDisk NodeConditionType = "OutOfDisk" + // NodeMemoryPressure means the kubelet is under pressure due to insufficient available memory. + NodeMemoryPressure NodeConditionType = "MemoryPressure" + // NodeDiskPressure means the kubelet is under pressure due to insufficient available disk. + NodeDiskPressure NodeConditionType = "DiskPressure" + // NodeNetworkUnavailable means that network for the node is not correctly configured. + NodeNetworkUnavailable NodeConditionType = "NetworkUnavailable" + // NodeConfigOK indicates whether the kubelet is correctly configured + NodeConfigOK NodeConditionType = "ConfigOK" +) + +type NodeCondition struct { + Type NodeConditionType + Status ConditionStatus + // +optional + LastHeartbeatTime metav1.Time + // +optional + LastTransitionTime metav1.Time + // +optional + Reason string + // +optional + Message string +} + +type NodeAddressType string + +const ( + NodeHostName NodeAddressType = "Hostname" + NodeExternalIP NodeAddressType = "ExternalIP" + NodeInternalIP NodeAddressType = "InternalIP" + NodeExternalDNS NodeAddressType = "ExternalDNS" + NodeInternalDNS NodeAddressType = "InternalDNS" +) + +type NodeAddress struct { + Type NodeAddressType + Address string +} + +// NodeResources is an object for conveying resource information about a node. +// see http://releases.k8s.io/HEAD/docs/design/resources.md for more details. +type NodeResources struct { + // Capacity represents the available resources of a node + // +optional + Capacity ResourceList +} + +// ResourceName is the name identifying various resources in a ResourceList. +type ResourceName string + +// Resource names must be not more than 63 characters, consisting of upper- or lower-case alphanumeric characters, +// with the -, _, and . characters allowed anywhere, except the first or last character. +// The default convention, matching that for annotations, is to use lower-case names, with dashes, rather than +// camel case, separating compound words. +// Fully-qualified resource typenames are constructed from a DNS-style subdomain, followed by a slash `/` and a name. +const ( + // CPU, in cores. (500m = .5 cores) + ResourceCPU ResourceName = "cpu" + // Memory, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) + ResourceMemory ResourceName = "memory" + // Volume size, in bytes (e,g. 5Gi = 5GiB = 5 * 1024 * 1024 * 1024) + ResourceStorage ResourceName = "storage" + // Local ephemeral storage, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) + // The resource name for ResourceEphemeralStorage is alpha and it can change across releases. + ResourceEphemeralStorage ResourceName = "ephemeral-storage" + // NVIDIA GPU, in devices. Alpha, might change: although fractional and allowing values >1, only one whole device per node is assigned. + ResourceNvidiaGPU ResourceName = "alpha.kubernetes.io/nvidia-gpu" +) + +const ( + // Default namespace prefix. + ResourceDefaultNamespacePrefix = "kubernetes.io/" + // Name prefix for huge page resources (alpha). + ResourceHugePagesPrefix = "hugepages-" +) + +// ResourceList is a set of (resource name, quantity) pairs. +type ResourceList map[ResourceName]resource.Quantity + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Node is a worker node in Kubernetes +// The name of the node according to etcd is in ObjectMeta.Name. +type Node struct { + metav1.TypeMeta + // +optional + metav1.ObjectMeta + + // Spec defines the behavior of a node. + // +optional + Spec NodeSpec + + // Status describes the current status of a Node + // +optional + Status NodeStatus +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// NodeList is a list of nodes. +type NodeList struct { + metav1.TypeMeta + // +optional + metav1.ListMeta + + Items []Node +} + +// NamespaceSpec describes the attributes on a Namespace +type NamespaceSpec struct { + // Finalizers is an opaque list of values that must be empty to permanently remove object from storage + Finalizers []FinalizerName +} + +// FinalizerName is the name identifying a finalizer during namespace lifecycle. +type FinalizerName string + +// These are internal finalizer values to Kubernetes, must be qualified name unless defined here or +// in metav1. +const ( + FinalizerKubernetes FinalizerName = "kubernetes" +) + +// NamespaceStatus is information about the current status of a Namespace. +type NamespaceStatus struct { + // Phase is the current lifecycle phase of the namespace. + // +optional + Phase NamespacePhase +} + +type NamespacePhase string + +// These are the valid phases of a namespace. +const ( + // NamespaceActive means the namespace is available for use in the system + NamespaceActive NamespacePhase = "Active" + // NamespaceTerminating means the namespace is undergoing graceful termination + NamespaceTerminating NamespacePhase = "Terminating" +) + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// A namespace provides a scope for Names. +// Use of multiple namespaces is optional +type Namespace struct { + metav1.TypeMeta + // +optional + metav1.ObjectMeta + + // Spec defines the behavior of the Namespace. + // +optional + Spec NamespaceSpec + + // Status describes the current status of a Namespace + // +optional + Status NamespaceStatus +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// NamespaceList is a list of Namespaces. +type NamespaceList struct { + metav1.TypeMeta + // +optional + metav1.ListMeta + + Items []Namespace +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Binding ties one object to another; for example, a pod is bound to a node by a scheduler. +// Deprecated in 1.7, please use the bindings subresource of pods instead. +type Binding struct { + metav1.TypeMeta + // ObjectMeta describes the object that is being bound. + // +optional + metav1.ObjectMeta + + // Target is the object to bind to. + Target ObjectReference +} + +// Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out. +type Preconditions struct { + // Specifies the target UID. + // +optional + UID *types.UID +} + +// DeletionPropagation decides whether and how garbage collection will be performed. +type DeletionPropagation string + +const ( + // Orphans the dependents. + DeletePropagationOrphan DeletionPropagation = "Orphan" + // Deletes the object from the key-value store, the garbage collector will delete the dependents in the background. + DeletePropagationBackground DeletionPropagation = "Background" + // The object exists in the key-value store until the garbage collector deletes all the dependents whose ownerReference.blockOwnerDeletion=true from the key-value store. + // API sever will put the "DeletingDependents" finalizer on the object, and sets its deletionTimestamp. + // This policy is cascading, i.e., the dependents will be deleted with Foreground. + DeletePropagationForeground DeletionPropagation = "Foreground" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// DeleteOptions may be provided when deleting an API object +// DEPRECATED: This type has been moved to meta/v1 and will be removed soon. +type DeleteOptions struct { + metav1.TypeMeta + + // Optional 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. + // +optional + GracePeriodSeconds *int64 + + // Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be + // returned. + // +optional + Preconditions *Preconditions + + // 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. + // +optional + OrphanDependents *bool + + // 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. + // +optional + PropagationPolicy *DeletionPropagation +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ListOptions is the query options to a standard REST list call, and has future support for +// watch calls. +// DEPRECATED: This type has been moved to meta/v1 and will be removed soon. +type ListOptions struct { + metav1.TypeMeta + + // A selector based on labels + LabelSelector labels.Selector + // A selector based on fields + FieldSelector fields.Selector + + // If true, partially initialized resources are included in the response. + IncludeUninitialized bool + + // If true, watch for changes to this list + Watch bool + // When specified with a watch call, shows changes that occur after that particular version of a resource. + // Defaults to changes from the beginning of history. + // When specified for list: + // - if unset, then the result is returned from remote storage based on quorum-read flag; + // - if it's 0, then we simply return what we currently have in cache, no guarantee; + // - if set to non zero, then the result is at least as fresh as given rv. + ResourceVersion string + // Timeout for the list/watch call. + TimeoutSeconds *int64 +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PodLogOptions is the query options for a Pod's logs REST call +type PodLogOptions struct { + metav1.TypeMeta + + // Container for which to return logs + Container string + // If true, follow the logs for the pod + Follow bool + // If true, return previous terminated container logs + Previous bool + // 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. + SinceSeconds *int64 + // An RFC3339 timestamp 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. + SinceTime *metav1.Time + // If true, add an RFC3339 or RFC3339Nano timestamp at the beginning of every line + // of log output. + Timestamps bool + // If set, the number of lines from the end of the logs to show. If not specified, + // logs are shown from the creation of the container or sinceSeconds or sinceTime + TailLines *int64 + // If set, the number of bytes to read from the server before terminating the + // log output. This may not display a complete final line of logging, and may return + // slightly more or slightly less than the specified limit. + LimitBytes *int64 +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PodAttachOptions is the query options to a Pod's remote attach call +// TODO: merge w/ PodExecOptions below for stdin, stdout, etc +type PodAttachOptions struct { + metav1.TypeMeta + + // Stdin if true indicates that stdin is to be redirected for the attach call + // +optional + Stdin bool + + // Stdout if true indicates that stdout is to be redirected for the attach call + // +optional + Stdout bool + + // Stderr if true indicates that stderr is to be redirected for the attach call + // +optional + Stderr bool + + // TTY if true indicates that a tty will be allocated for the attach call + // +optional + TTY bool + + // Container to attach to. + // +optional + Container string +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PodExecOptions is the query options to a Pod's remote exec call +type PodExecOptions struct { + metav1.TypeMeta + + // Stdin if true indicates that stdin is to be redirected for the exec call + Stdin bool + + // Stdout if true indicates that stdout is to be redirected for the exec call + Stdout bool + + // Stderr if true indicates that stderr is to be redirected for the exec call + Stderr bool + + // TTY if true indicates that a tty will be allocated for the exec call + TTY bool + + // Container in which to execute the command. + Container string + + // Command is the remote command to execute; argv array; not executed within a shell. + Command []string +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PodPortForwardOptions is the query options to a Pod's port forward call +type PodPortForwardOptions struct { + metav1.TypeMeta + + // The list of ports to forward + // +optional + Ports []int32 +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PodProxyOptions is the query options to a Pod's proxy call +type PodProxyOptions struct { + metav1.TypeMeta + + // Path is the URL path to use for the current proxy request + Path string +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// NodeProxyOptions is the query options to a Node's proxy call +type NodeProxyOptions struct { + metav1.TypeMeta + + // Path is the URL path to use for the current proxy request + Path string +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ServiceProxyOptions is the query options to a Service's proxy call. +type ServiceProxyOptions struct { + metav1.TypeMeta + + // Path is the part of URLs that include service endpoints, suffixes, + // and parameters to use for the current proxy request to service. + // For example, the whole request URL is + // http://localhost/api/v1/namespaces/kube-system/services/elasticsearch-logging/_search?q=user:kimchy. + // Path is _search?q=user:kimchy. + Path string +} + +// ObjectReference contains enough information to let you inspect or modify the referred object. +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ObjectReference struct { + // +optional + Kind string + // +optional + Namespace string + // +optional + Name string + // +optional + UID types.UID + // +optional + APIVersion string + // +optional + ResourceVersion string + + // Optional. If referring to a piece of an object instead of an entire object, this string + // should contain information to identify the sub-object. For example, if the object + // reference is to a container within a pod, this would take on a value like: + // "spec.containers{name}" (where "name" refers to the name of the container that triggered + // the event) or if no container name is specified "spec.containers[2]" (container with + // index 2 in this pod). This syntax is chosen only to have some well-defined way of + // referencing a part of an object. + // TODO: this design is not final and this field is subject to change in the future. + // +optional + FieldPath string +} + +// LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace. +type LocalObjectReference struct { + //TODO: Add other useful fields. apiVersion, kind, uid? + Name string +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type SerializedReference struct { + metav1.TypeMeta + // +optional + Reference ObjectReference +} + +type EventSource struct { + // Component from which the event is generated. + // +optional + Component string + // Node name on which the event is generated. + // +optional + Host string +} + +// Valid values for event types (new types could be added in future) +const ( + // Information only and will not cause any problems + EventTypeNormal string = "Normal" + // These events are to warn that something might go wrong + EventTypeWarning string = "Warning" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Event is a report of an event somewhere in the cluster. +// TODO: Decide whether to store these separately or with the object they apply to. +type Event struct { + metav1.TypeMeta + // +optional + metav1.ObjectMeta + + // Required. The object that this event is about. Mapped to events.Event.regarding + // +optional + InvolvedObject ObjectReference + + // Optional; this should be a short, machine understandable string that gives the reason + // for this event being generated. For example, if the event is reporting that a container + // can't start, the Reason might be "ImageNotFound". + // TODO: provide exact specification for format. + // +optional + Reason string + + // Optional. A human-readable description of the status of this operation. + // TODO: decide on maximum length. Mapped to events.Event.note + // +optional + Message string + + // Optional. The component reporting this event. Should be a short machine understandable string. + // +optional + Source EventSource + + // The time at which the event was first recorded. (Time of server receipt is in TypeMeta.) + // +optional + FirstTimestamp metav1.Time + + // The time at which the most recent occurrence of this event was recorded. + // +optional + LastTimestamp metav1.Time + + // The number of times this event has occurred. + // +optional + Count int32 + + // Type of this event (Normal, Warning), new types could be added in the future. + // +optional + Type string + + // Time when this Event was first observed. + // +optional + EventTime metav1.MicroTime + + // Data about the Event series this event represents or nil if it's a singleton Event. + // +optional + Series *EventSeries + + // What action was taken/failed regarding to the Regarding object. + // +optional + Action string + + // Optional secondary object for more complex actions. + // +optional + Related *ObjectReference + + // Name of the controller that emitted this Event, e.g. `kubernetes.io/kubelet`. + // +optional + ReportingController string + + // ID of the controller instance, e.g. `kubelet-xyzf`. + // +optional + ReportingInstance string +} + +type EventSeries struct { + // Number of occurrences in this series up to the last heartbeat time + Count int32 + // Time of the last occurence observed + LastObservedTime metav1.MicroTime + // State of this Series: Ongoing or Finished + State EventSeriesState +} + +type EventSeriesState string + +const ( + EventSeriesStateOngoing EventSeriesState = "Ongoing" + EventSeriesStateFinished EventSeriesState = "Finished" + EventSeriesStateUnknown EventSeriesState = "Unknown" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// EventList is a list of events. +type EventList struct { + metav1.TypeMeta + // +optional + metav1.ListMeta + + Items []Event +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// List holds a list of objects, which may not be known by the server. +type List metainternalversion.List + +// A type of object that is limited +type LimitType string + +const ( + // Limit that applies to all pods in a namespace + LimitTypePod LimitType = "Pod" + // Limit that applies to all containers in a namespace + LimitTypeContainer LimitType = "Container" + // Limit that applies to all persistent volume claims in a namespace + LimitTypePersistentVolumeClaim LimitType = "PersistentVolumeClaim" +) + +// LimitRangeItem defines a min/max usage limit for any resource that matches on kind +type LimitRangeItem struct { + // Type of resource that this limit applies to + // +optional + Type LimitType + // Max usage constraints on this kind by resource name + // +optional + Max ResourceList + // Min usage constraints on this kind by resource name + // +optional + Min ResourceList + // Default resource requirement limit value by resource name. + // +optional + Default ResourceList + // DefaultRequest resource requirement request value by resource name. + // +optional + DefaultRequest ResourceList + // MaxLimitRequestRatio represents the max burst value for the named resource + // +optional + MaxLimitRequestRatio ResourceList +} + +// LimitRangeSpec defines a min/max usage limit for resources that match on kind +type LimitRangeSpec struct { + // Limits is the list of LimitRangeItem objects that are enforced + Limits []LimitRangeItem +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// LimitRange sets resource usage limits for each kind of resource in a Namespace +type LimitRange struct { + metav1.TypeMeta + // +optional + metav1.ObjectMeta + + // Spec defines the limits enforced + // +optional + Spec LimitRangeSpec +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// LimitRangeList is a list of LimitRange items. +type LimitRangeList struct { + metav1.TypeMeta + // +optional + metav1.ListMeta + + // Items is a list of LimitRange objects + Items []LimitRange +} + +// The following identify resource constants for Kubernetes object types +const ( + // Pods, number + ResourcePods ResourceName = "pods" + // Services, number + ResourceServices ResourceName = "services" + // ReplicationControllers, number + ResourceReplicationControllers ResourceName = "replicationcontrollers" + // ResourceQuotas, number + ResourceQuotas ResourceName = "resourcequotas" + // ResourceSecrets, number + ResourceSecrets ResourceName = "secrets" + // ResourceConfigMaps, number + ResourceConfigMaps ResourceName = "configmaps" + // ResourcePersistentVolumeClaims, number + ResourcePersistentVolumeClaims ResourceName = "persistentvolumeclaims" + // ResourceServicesNodePorts, number + ResourceServicesNodePorts ResourceName = "services.nodeports" + // ResourceServicesLoadBalancers, number + ResourceServicesLoadBalancers ResourceName = "services.loadbalancers" + // CPU request, in cores. (500m = .5 cores) + ResourceRequestsCPU ResourceName = "requests.cpu" + // Memory request, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) + ResourceRequestsMemory ResourceName = "requests.memory" + // Storage request, in bytes + ResourceRequestsStorage ResourceName = "requests.storage" + // Local ephemeral storage request, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) + ResourceRequestsEphemeralStorage ResourceName = "requests.ephemeral-storage" + // CPU limit, in cores. (500m = .5 cores) + ResourceLimitsCPU ResourceName = "limits.cpu" + // Memory limit, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) + ResourceLimitsMemory ResourceName = "limits.memory" + // Local ephemeral storage limit, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) + ResourceLimitsEphemeralStorage ResourceName = "limits.ephemeral-storage" +) + +// The following identify resource prefix for Kubernetes object types +const ( + // HugePages request, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) + // As burst is not supported for HugePages, we would only quota its request, and ignore the limit. + ResourceRequestsHugePagesPrefix = "requests.hugepages-" +) + +// A ResourceQuotaScope defines a filter that must match each object tracked by a quota +type ResourceQuotaScope string + +const ( + // Match all pod objects where spec.activeDeadlineSeconds + ResourceQuotaScopeTerminating ResourceQuotaScope = "Terminating" + // Match all pod objects where !spec.activeDeadlineSeconds + ResourceQuotaScopeNotTerminating ResourceQuotaScope = "NotTerminating" + // Match all pod objects that have best effort quality of service + ResourceQuotaScopeBestEffort ResourceQuotaScope = "BestEffort" + // Match all pod objects that do not have best effort quality of service + ResourceQuotaScopeNotBestEffort ResourceQuotaScope = "NotBestEffort" +) + +// ResourceQuotaSpec defines the desired hard limits to enforce for Quota +type ResourceQuotaSpec struct { + // Hard is the set of desired hard limits for each named resource + // +optional + Hard ResourceList + // A collection of filters that must match each object tracked by a quota. + // If not specified, the quota matches all objects. + // +optional + Scopes []ResourceQuotaScope +} + +// ResourceQuotaStatus defines the enforced hard limits and observed use +type ResourceQuotaStatus struct { + // Hard is the set of enforced hard limits for each named resource + // +optional + Hard ResourceList + // Used is the current observed total usage of the resource in the namespace + // +optional + Used ResourceList +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ResourceQuota sets aggregate quota restrictions enforced per namespace +type ResourceQuota struct { + metav1.TypeMeta + // +optional + metav1.ObjectMeta + + // Spec defines the desired quota + // +optional + Spec ResourceQuotaSpec + + // Status defines the actual enforced quota and its current usage + // +optional + Status ResourceQuotaStatus +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ResourceQuotaList is a list of ResourceQuota items +type ResourceQuotaList struct { + metav1.TypeMeta + // +optional + metav1.ListMeta + + // Items is a list of ResourceQuota objects + Items []ResourceQuota +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Secret holds secret data of a certain type. The total bytes of the values in +// the Data field must be less than MaxSecretSize bytes. +type Secret struct { + metav1.TypeMeta + // +optional + metav1.ObjectMeta + + // Data contains the secret data. Each key must consist of alphanumeric + // characters, '-', '_' or '.'. The serialized form of the secret data is a + // base64 encoded string, representing the arbitrary (possibly non-string) + // data value here. + // +optional + Data map[string][]byte + + // Used to facilitate programmatic handling of secret data. + // +optional + Type SecretType +} + +const MaxSecretSize = 1 * 1024 * 1024 + +type SecretType string + +const ( + // SecretTypeOpaque is the default; arbitrary user-defined data + SecretTypeOpaque SecretType = "Opaque" + + // SecretTypeServiceAccountToken contains a token that identifies a service account to the API + // + // Required fields: + // - Secret.Annotations["kubernetes.io/service-account.name"] - the name of the ServiceAccount the token identifies + // - Secret.Annotations["kubernetes.io/service-account.uid"] - the UID of the ServiceAccount the token identifies + // - Secret.Data["token"] - a token that identifies the service account to the API + SecretTypeServiceAccountToken SecretType = "kubernetes.io/service-account-token" + + // ServiceAccountNameKey is the key of the required annotation for SecretTypeServiceAccountToken secrets + ServiceAccountNameKey = "kubernetes.io/service-account.name" + // ServiceAccountUIDKey is the key of the required annotation for SecretTypeServiceAccountToken secrets + ServiceAccountUIDKey = "kubernetes.io/service-account.uid" + // ServiceAccountTokenKey is the key of the required data for SecretTypeServiceAccountToken secrets + ServiceAccountTokenKey = "token" + // ServiceAccountKubeconfigKey is the key of the optional kubeconfig data for SecretTypeServiceAccountToken secrets + ServiceAccountKubeconfigKey = "kubernetes.kubeconfig" + // ServiceAccountRootCAKey is the key of the optional root certificate authority for SecretTypeServiceAccountToken secrets + ServiceAccountRootCAKey = "ca.crt" + // ServiceAccountNamespaceKey is the key of the optional namespace to use as the default for namespaced API calls + ServiceAccountNamespaceKey = "namespace" + + // SecretTypeDockercfg contains a dockercfg file that follows the same format rules as ~/.dockercfg + // + // Required fields: + // - Secret.Data[".dockercfg"] - a serialized ~/.dockercfg file + SecretTypeDockercfg SecretType = "kubernetes.io/dockercfg" + + // DockerConfigKey is the key of the required data for SecretTypeDockercfg secrets + DockerConfigKey = ".dockercfg" + + // SecretTypeDockerConfigJson contains a dockercfg file that follows the same format rules as ~/.docker/config.json + // + // Required fields: + // - Secret.Data[".dockerconfigjson"] - a serialized ~/.docker/config.json file + SecretTypeDockerConfigJson SecretType = "kubernetes.io/dockerconfigjson" + + // DockerConfigJsonKey is the key of the required data for SecretTypeDockerConfigJson secrets + DockerConfigJsonKey = ".dockerconfigjson" + + // SecretTypeBasicAuth contains data needed for basic authentication. + // + // Required at least one of fields: + // - Secret.Data["username"] - username used for authentication + // - Secret.Data["password"] - password or token needed for authentication + SecretTypeBasicAuth SecretType = "kubernetes.io/basic-auth" + + // BasicAuthUsernameKey is the key of the username for SecretTypeBasicAuth secrets + BasicAuthUsernameKey = "username" + // BasicAuthPasswordKey is the key of the password or token for SecretTypeBasicAuth secrets + BasicAuthPasswordKey = "password" + + // SecretTypeSSHAuth contains data needed for SSH authentication. + // + // Required field: + // - Secret.Data["ssh-privatekey"] - private SSH key needed for authentication + SecretTypeSSHAuth SecretType = "kubernetes.io/ssh-auth" + + // SSHAuthPrivateKey is the key of the required SSH private key for SecretTypeSSHAuth secrets + SSHAuthPrivateKey = "ssh-privatekey" + + // SecretTypeTLS contains information about a TLS client or server secret. It + // is primarily used with TLS termination of the Ingress resource, but may be + // used in other types. + // + // Required fields: + // - Secret.Data["tls.key"] - TLS private key. + // Secret.Data["tls.crt"] - TLS certificate. + // TODO: Consider supporting different formats, specifying CA/destinationCA. + SecretTypeTLS SecretType = "kubernetes.io/tls" + + // TLSCertKey is the key for tls certificates in a TLS secret. + TLSCertKey = "tls.crt" + // TLSPrivateKeyKey is the key for the private key field in a TLS secret. + TLSPrivateKeyKey = "tls.key" + // SecretTypeBootstrapToken is used during the automated bootstrap process (first + // implemented by kubeadm). It stores tokens that are used to sign well known + // ConfigMaps. They are used for authn. + SecretTypeBootstrapToken SecretType = "bootstrap.kubernetes.io/token" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type SecretList struct { + metav1.TypeMeta + // +optional + metav1.ListMeta + + Items []Secret +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ConfigMap holds configuration data for components or applications to consume. +type ConfigMap struct { + metav1.TypeMeta + // +optional + metav1.ObjectMeta + + // Data contains the configuration data. + // Each key must consist of alphanumeric characters, '-', '_' or '.'. + // +optional + Data map[string]string +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ConfigMapList is a resource containing a list of ConfigMap objects. +type ConfigMapList struct { + metav1.TypeMeta + // +optional + metav1.ListMeta + + // Items is the list of ConfigMaps. + Items []ConfigMap +} + +// These constants are for remote command execution and port forwarding and are +// used by both the client side and server side components. +// +// This is probably not the ideal place for them, but it didn't seem worth it +// to create pkg/exec and pkg/portforward just to contain a single file with +// constants in it. Suggestions for more appropriate alternatives are +// definitely welcome! +const ( + // Enable stdin for remote command execution + ExecStdinParam = "input" + // Enable stdout for remote command execution + ExecStdoutParam = "output" + // Enable stderr for remote command execution + ExecStderrParam = "error" + // Enable TTY for remote command execution + ExecTTYParam = "tty" + // Command to run for remote command execution + ExecCommandParam = "command" + + // Name of header that specifies stream type + StreamType = "streamType" + // Value for streamType header for stdin stream + StreamTypeStdin = "stdin" + // Value for streamType header for stdout stream + StreamTypeStdout = "stdout" + // Value for streamType header for stderr stream + StreamTypeStderr = "stderr" + // Value for streamType header for data stream + StreamTypeData = "data" + // Value for streamType header for error stream + StreamTypeError = "error" + // Value for streamType header for terminal resize stream + StreamTypeResize = "resize" + + // Name of header that specifies the port being forwarded + PortHeader = "port" + // Name of header that specifies a request ID used to associate the error + // and data streams for a single forwarded connection + PortForwardRequestIDHeader = "requestID" +) + +// Type and constants for component health validation. +type ComponentConditionType string + +// These are the valid conditions for the component. +const ( + ComponentHealthy ComponentConditionType = "Healthy" +) + +type ComponentCondition struct { + Type ComponentConditionType + Status ConditionStatus + // +optional + Message string + // +optional + Error string +} + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ComponentStatus (and ComponentStatusList) holds the cluster validation info. +type ComponentStatus struct { + metav1.TypeMeta + // +optional + metav1.ObjectMeta + + // +optional + Conditions []ComponentCondition +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type ComponentStatusList struct { + metav1.TypeMeta + // +optional + metav1.ListMeta + + Items []ComponentStatus +} + +// SecurityContext holds security configuration that will be applied to a container. +// Some fields are present in both SecurityContext and PodSecurityContext. When both +// are set, the values in SecurityContext take precedence. +type SecurityContext struct { + // The capabilities to add/drop when running containers. + // Defaults to the default set of capabilities granted by the container runtime. + // +optional + Capabilities *Capabilities + // Run container in privileged mode. + // Processes in privileged containers are essentially equivalent to root on the host. + // Defaults to false. + // +optional + Privileged *bool + // The SELinux context to be applied to the container. + // If unspecified, the container runtime will allocate a random SELinux context for each + // container. May also be set in PodSecurityContext. If set in both SecurityContext and + // PodSecurityContext, the value specified in SecurityContext takes precedence. + // +optional + SELinuxOptions *SELinuxOptions + // The UID to run the entrypoint of the container process. + // Defaults to user specified in image metadata if unspecified. + // May also be set in PodSecurityContext. If set in both SecurityContext and + // PodSecurityContext, the value specified in SecurityContext takes precedence. + // +optional + RunAsUser *int64 + // 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. + // +optional + RunAsNonRoot *bool + // The read-only root filesystem allows you to restrict the locations that an application can write + // files to, ensuring the persistent data can only be written to mounts. + // +optional + ReadOnlyRootFilesystem *bool + // 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. + // +optional + AllowPrivilegeEscalation *bool +} + +// SELinuxOptions are the labels to be applied to the container. +type SELinuxOptions struct { + // SELinux user label + // +optional + User string + // SELinux role label + // +optional + Role string + // SELinux type label + // +optional + Type string + // SELinux level label. + // +optional + Level string +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// RangeAllocation is an opaque API object (not exposed to end users) that can be persisted to record +// the global allocation state of the cluster. The schema of Range and Data generic, in that Range +// should be a string representation of the inputs to a range (for instance, for IP allocation it +// might be a CIDR) and Data is an opaque blob understood by an allocator which is typically a +// binary range. Consumers should use annotations to record additional information (schema version, +// data encoding hints). A range allocation should *ALWAYS* be recreatable at any time by observation +// of the cluster, thus the object is less strongly typed than most. +type RangeAllocation struct { + metav1.TypeMeta + // +optional + metav1.ObjectMeta + // A string representing a unique label for a range of resources, such as a CIDR "10.0.0.0/8" or + // port range "10000-30000". Range is not strongly schema'd here. The Range is expected to define + // a start and end unless there is an implicit end. + Range string + // A byte array representing the serialized state of a range allocation. Additional clarifiers on + // the type or format of data should be represented with annotations. For IP allocations, this is + // represented as a bit array starting at the base IP of the CIDR in Range, with each bit representing + // a single allocated address (the fifth bit on CIDR 10.0.0.0/8 is 10.0.0.4). + Data []byte +} + +const ( + // "default-scheduler" is the name of default scheduler. + DefaultSchedulerName = "default-scheduler" + + // RequiredDuringScheduling affinity is not symmetric, but there is an implicit PreferredDuringScheduling affinity rule + // corresponding to every RequiredDuringScheduling affinity rule. + // When the --hard-pod-affinity-weight scheduler flag is not specified, + // DefaultHardPodAffinityWeight defines the weight of the implicit PreferredDuringScheduling affinity rule. + DefaultHardPodAffinitySymmetricWeight int32 = 1 +) diff --git a/pkg/apis/core/v1/BUILD b/pkg/apis/core/v1/BUILD new file mode 100644 index 00000000000..5796c614076 --- /dev/null +++ b/pkg/apis/core/v1/BUILD @@ -0,0 +1,79 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "conversion.go", + "defaults.go", + "doc.go", + "register.go", + "zz_generated.conversion.go", + "zz_generated.defaults.go", + ], + importpath = "k8s.io/kubernetes/pkg/apis/core/v1", + visibility = ["//visibility:public"], + deps = [ + "//pkg/apis/core:go_default_library", + "//pkg/apis/extensions:go_default_library", + "//pkg/features:go_default_library", + "//pkg/util/parsers:go_default_library", + "//pkg/util/pointer:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + ], +) + +go_test( + name = "go_default_xtest", + srcs = [ + "conversion_test.go", + "defaults_test.go", + ], + importpath = "k8s.io/kubernetes/pkg/apis/core/v1_test", + deps = [ + ":go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/api/testapi:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/fuzzer:go_default_library", + "//pkg/apis/extensions:go_default_library", + "//pkg/util/pointer:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/testing/fuzzer:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/fuzzer:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//pkg/apis/core/v1/helper:all-srcs", + "//pkg/apis/core/v1/validation:all-srcs", + ], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/apis/core/v1/OWNERS b/pkg/apis/core/v1/OWNERS new file mode 100755 index 00000000000..c627c33f878 --- /dev/null +++ b/pkg/apis/core/v1/OWNERS @@ -0,0 +1,39 @@ +reviewers: +- thockin +- lavalamp +- smarterclayton +- wojtek-t +- deads2k +- yujuhong +- brendandburns +- derekwaynecarr +- caesarxuchao +- vishh +- mikedanese +- liggitt +- nikhiljindal +- gmarek +- erictune +- davidopp +- pmorie +- sttts +- dchen1107 +- saad-ali +- zmerlynn +- luxas +- janetkuo +- justinsb +- roberthbailey +- ncdc +- tallclair +- eparis +- timothysc +- piosz +- jsafrane +- dims +- errordeveloper +- madhusudancs +- krousey +- jayunit100 +- rootfs +- markturansky diff --git a/pkg/apis/core/v1/conversion.go b/pkg/apis/core/v1/conversion.go new file mode 100644 index 00000000000..8f6e09b673e --- /dev/null +++ b/pkg/apis/core/v1/conversion.go @@ -0,0 +1,571 @@ +/* +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 v1 + +import ( + "fmt" + "reflect" + + "k8s.io/api/core/v1" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/extensions" +) + +// This is a "fast-path" that avoids reflection for common types. It focuses on the objects that are +// converted the most in the cluster. +// TODO: generate one of these for every external API group - this is to prove the impact +func addFastPathConversionFuncs(scheme *runtime.Scheme) error { + scheme.AddGenericConversionFunc(func(objA, objB interface{}, s conversion.Scope) (bool, error) { + switch a := objA.(type) { + case *v1.Pod: + switch b := objB.(type) { + case *core.Pod: + return true, Convert_v1_Pod_To_core_Pod(a, b, s) + } + case *core.Pod: + switch b := objB.(type) { + case *v1.Pod: + return true, Convert_core_Pod_To_v1_Pod(a, b, s) + } + + case *v1.Event: + switch b := objB.(type) { + case *core.Event: + return true, Convert_v1_Event_To_core_Event(a, b, s) + } + case *core.Event: + switch b := objB.(type) { + case *v1.Event: + return true, Convert_core_Event_To_v1_Event(a, b, s) + } + + case *v1.ReplicationController: + switch b := objB.(type) { + case *core.ReplicationController: + return true, Convert_v1_ReplicationController_To_core_ReplicationController(a, b, s) + } + case *core.ReplicationController: + switch b := objB.(type) { + case *v1.ReplicationController: + return true, Convert_core_ReplicationController_To_v1_ReplicationController(a, b, s) + } + + case *v1.Node: + switch b := objB.(type) { + case *core.Node: + return true, Convert_v1_Node_To_core_Node(a, b, s) + } + case *core.Node: + switch b := objB.(type) { + case *v1.Node: + return true, Convert_core_Node_To_v1_Node(a, b, s) + } + + case *v1.Namespace: + switch b := objB.(type) { + case *core.Namespace: + return true, Convert_v1_Namespace_To_core_Namespace(a, b, s) + } + case *core.Namespace: + switch b := objB.(type) { + case *v1.Namespace: + return true, Convert_core_Namespace_To_v1_Namespace(a, b, s) + } + + case *v1.Service: + switch b := objB.(type) { + case *core.Service: + return true, Convert_v1_Service_To_core_Service(a, b, s) + } + case *core.Service: + switch b := objB.(type) { + case *v1.Service: + return true, Convert_core_Service_To_v1_Service(a, b, s) + } + + case *v1.Endpoints: + switch b := objB.(type) { + case *core.Endpoints: + return true, Convert_v1_Endpoints_To_core_Endpoints(a, b, s) + } + case *core.Endpoints: + switch b := objB.(type) { + case *v1.Endpoints: + return true, Convert_core_Endpoints_To_v1_Endpoints(a, b, s) + } + + case *metav1.WatchEvent: + switch b := objB.(type) { + case *metav1.InternalEvent: + return true, metav1.Convert_versioned_Event_to_versioned_InternalEvent(a, b, s) + } + case *metav1.InternalEvent: + switch b := objB.(type) { + case *metav1.WatchEvent: + return true, metav1.Convert_versioned_InternalEvent_to_versioned_Event(a, b, s) + } + } + return false, nil + }) + return nil +} + +func addConversionFuncs(scheme *runtime.Scheme) error { + // Add non-generated conversion functions + err := scheme.AddConversionFuncs( + Convert_core_Pod_To_v1_Pod, + Convert_core_PodSpec_To_v1_PodSpec, + Convert_core_ReplicationControllerSpec_To_v1_ReplicationControllerSpec, + Convert_core_ServiceSpec_To_v1_ServiceSpec, + Convert_v1_Pod_To_core_Pod, + Convert_v1_PodSpec_To_core_PodSpec, + Convert_v1_ReplicationControllerSpec_To_core_ReplicationControllerSpec, + Convert_v1_Secret_To_core_Secret, + Convert_v1_ServiceSpec_To_core_ServiceSpec, + Convert_v1_ResourceList_To_core_ResourceList, + Convert_v1_ReplicationController_to_extensions_ReplicaSet, + Convert_v1_ReplicationControllerSpec_to_extensions_ReplicaSetSpec, + Convert_v1_ReplicationControllerStatus_to_extensions_ReplicaSetStatus, + Convert_extensions_ReplicaSet_to_v1_ReplicationController, + Convert_extensions_ReplicaSetSpec_to_v1_ReplicationControllerSpec, + Convert_extensions_ReplicaSetStatus_to_v1_ReplicationControllerStatus, + ) + if err != nil { + return err + } + + // Add field conversion funcs. + err = scheme.AddFieldLabelConversionFunc("v1", "Pod", + func(label, value string) (string, string, error) { + switch label { + case "metadata.name", + "metadata.namespace", + "spec.nodeName", + "spec.restartPolicy", + "spec.schedulerName", + "status.phase", + "status.podIP": + return label, value, nil + // This is for backwards compatibility with old v1 clients which send spec.host + case "spec.host": + return "spec.nodeName", value, nil + default: + return "", "", fmt.Errorf("field label not supported: %s", label) + } + }, + ) + if err != nil { + return err + } + err = scheme.AddFieldLabelConversionFunc("v1", "Node", + func(label, value string) (string, string, error) { + switch label { + case "metadata.name": + return label, value, nil + case "spec.unschedulable": + return label, value, nil + default: + return "", "", fmt.Errorf("field label not supported: %s", label) + } + }, + ) + if err != nil { + return err + } + err = scheme.AddFieldLabelConversionFunc("v1", "ReplicationController", + func(label, value string) (string, string, error) { + switch label { + case "metadata.name", + "metadata.namespace", + "status.replicas": + return label, value, nil + default: + return "", "", fmt.Errorf("field label not supported: %s", label) + } + }) + if err != nil { + return err + } + if err := AddFieldLabelConversionsForEvent(scheme); err != nil { + return err + } + if err := AddFieldLabelConversionsForNamespace(scheme); err != nil { + return err + } + if err := AddFieldLabelConversionsForSecret(scheme); err != nil { + return err + } + return nil +} + +func Convert_v1_ReplicationController_to_extensions_ReplicaSet(in *v1.ReplicationController, out *extensions.ReplicaSet, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_ReplicationControllerSpec_to_extensions_ReplicaSetSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1_ReplicationControllerStatus_to_extensions_ReplicaSetStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +func Convert_v1_ReplicationControllerSpec_to_extensions_ReplicaSetSpec(in *v1.ReplicationControllerSpec, out *extensions.ReplicaSetSpec, s conversion.Scope) error { + out.Replicas = *in.Replicas + out.MinReadySeconds = in.MinReadySeconds + if in.Selector != nil { + out.Selector = new(metav1.LabelSelector) + metav1.Convert_map_to_unversioned_LabelSelector(&in.Selector, out.Selector, s) + } + if in.Template != nil { + if err := Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(in.Template, &out.Template, s); err != nil { + return err + } + } + return nil +} + +func Convert_v1_ReplicationControllerStatus_to_extensions_ReplicaSetStatus(in *v1.ReplicationControllerStatus, out *extensions.ReplicaSetStatus, s conversion.Scope) error { + out.Replicas = in.Replicas + out.FullyLabeledReplicas = in.FullyLabeledReplicas + out.ReadyReplicas = in.ReadyReplicas + out.AvailableReplicas = in.AvailableReplicas + out.ObservedGeneration = in.ObservedGeneration + for _, cond := range in.Conditions { + out.Conditions = append(out.Conditions, extensions.ReplicaSetCondition{ + Type: extensions.ReplicaSetConditionType(cond.Type), + Status: core.ConditionStatus(cond.Status), + LastTransitionTime: cond.LastTransitionTime, + Reason: cond.Reason, + Message: cond.Message, + }) + } + return nil +} + +func Convert_extensions_ReplicaSet_to_v1_ReplicationController(in *extensions.ReplicaSet, out *v1.ReplicationController, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_extensions_ReplicaSetSpec_to_v1_ReplicationControllerSpec(&in.Spec, &out.Spec, s); err != nil { + fieldErr, ok := err.(*field.Error) + if !ok { + return err + } + if out.Annotations == nil { + out.Annotations = make(map[string]string) + } + out.Annotations[v1.NonConvertibleAnnotationPrefix+"/"+fieldErr.Field] = reflect.ValueOf(fieldErr.BadValue).String() + } + if err := Convert_extensions_ReplicaSetStatus_to_v1_ReplicationControllerStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +func Convert_extensions_ReplicaSetSpec_to_v1_ReplicationControllerSpec(in *extensions.ReplicaSetSpec, out *v1.ReplicationControllerSpec, s conversion.Scope) error { + out.Replicas = new(int32) + *out.Replicas = in.Replicas + out.MinReadySeconds = in.MinReadySeconds + var invalidErr error + if in.Selector != nil { + invalidErr = metav1.Convert_unversioned_LabelSelector_to_map(in.Selector, &out.Selector, s) + } + out.Template = new(v1.PodTemplateSpec) + if err := Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, out.Template, s); err != nil { + return err + } + return invalidErr +} + +func Convert_extensions_ReplicaSetStatus_to_v1_ReplicationControllerStatus(in *extensions.ReplicaSetStatus, out *v1.ReplicationControllerStatus, s conversion.Scope) error { + out.Replicas = in.Replicas + out.FullyLabeledReplicas = in.FullyLabeledReplicas + out.ReadyReplicas = in.ReadyReplicas + out.AvailableReplicas = in.AvailableReplicas + out.ObservedGeneration = in.ObservedGeneration + for _, cond := range in.Conditions { + out.Conditions = append(out.Conditions, v1.ReplicationControllerCondition{ + Type: v1.ReplicationControllerConditionType(cond.Type), + Status: v1.ConditionStatus(cond.Status), + LastTransitionTime: cond.LastTransitionTime, + Reason: cond.Reason, + Message: cond.Message, + }) + } + return nil +} + +func Convert_core_ReplicationControllerSpec_To_v1_ReplicationControllerSpec(in *core.ReplicationControllerSpec, out *v1.ReplicationControllerSpec, s conversion.Scope) error { + out.Replicas = &in.Replicas + out.MinReadySeconds = in.MinReadySeconds + out.Selector = in.Selector + if in.Template != nil { + out.Template = new(v1.PodTemplateSpec) + if err := Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(in.Template, out.Template, s); err != nil { + return err + } + } else { + out.Template = nil + } + return nil +} + +func Convert_v1_ReplicationControllerSpec_To_core_ReplicationControllerSpec(in *v1.ReplicationControllerSpec, out *core.ReplicationControllerSpec, s conversion.Scope) error { + if in.Replicas != nil { + out.Replicas = *in.Replicas + } + out.MinReadySeconds = in.MinReadySeconds + out.Selector = in.Selector + if in.Template != nil { + out.Template = new(core.PodTemplateSpec) + if err := Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(in.Template, out.Template, s); err != nil { + return err + } + } else { + out.Template = nil + } + return nil +} + +func Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(in *core.PodTemplateSpec, out *v1.PodTemplateSpec, s conversion.Scope) error { + if err := autoConvert_core_PodTemplateSpec_To_v1_PodTemplateSpec(in, out, s); err != nil { + return err + } + + return nil +} + +func Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(in *v1.PodTemplateSpec, out *core.PodTemplateSpec, s conversion.Scope) error { + if err := autoConvert_v1_PodTemplateSpec_To_core_PodTemplateSpec(in, out, s); err != nil { + return err + } + + return nil +} + +// The following two v1.PodSpec conversions are done here to support v1.ServiceAccount +// as an alias for ServiceAccountName. +func Convert_core_PodSpec_To_v1_PodSpec(in *core.PodSpec, out *v1.PodSpec, s conversion.Scope) error { + if err := autoConvert_core_PodSpec_To_v1_PodSpec(in, out, s); err != nil { + return err + } + + // DeprecatedServiceAccount is an alias for ServiceAccountName. + out.DeprecatedServiceAccount = in.ServiceAccountName + + if in.SecurityContext != nil { + // the host namespace fields have to be handled here for backward compatibility + // with v1.0.0 + out.HostPID = in.SecurityContext.HostPID + out.HostNetwork = in.SecurityContext.HostNetwork + out.HostIPC = in.SecurityContext.HostIPC + } + + return nil +} + +func Convert_v1_PodSpec_To_core_PodSpec(in *v1.PodSpec, out *core.PodSpec, s conversion.Scope) error { + if err := autoConvert_v1_PodSpec_To_core_PodSpec(in, out, s); err != nil { + return err + } + + // We support DeprecatedServiceAccount as an alias for ServiceAccountName. + // If both are specified, ServiceAccountName (the new field) wins. + if in.ServiceAccountName == "" { + out.ServiceAccountName = in.DeprecatedServiceAccount + } + + // the host namespace fields have to be handled specially for backward compatibility + // with v1.0.0 + if out.SecurityContext == nil { + out.SecurityContext = new(core.PodSecurityContext) + } + out.SecurityContext.HostNetwork = in.HostNetwork + out.SecurityContext.HostPID = in.HostPID + out.SecurityContext.HostIPC = in.HostIPC + + return nil +} + +func Convert_core_Pod_To_v1_Pod(in *core.Pod, out *v1.Pod, s conversion.Scope) error { + if err := autoConvert_core_Pod_To_v1_Pod(in, out, s); err != nil { + return err + } + + // drop init container annotations so they don't take effect on legacy kubelets. + // remove this once the oldest supported kubelet no longer honors the annotations over the field. + if len(out.Annotations) > 0 { + old := out.Annotations + out.Annotations = make(map[string]string, len(old)) + for k, v := range old { + out.Annotations[k] = v + } + delete(out.Annotations, "pod.beta.kubernetes.io/init-containers") + delete(out.Annotations, "pod.alpha.kubernetes.io/init-containers") + delete(out.Annotations, "pod.beta.kubernetes.io/init-container-statuses") + delete(out.Annotations, "pod.alpha.kubernetes.io/init-container-statuses") + } + + return nil +} + +func Convert_v1_Secret_To_core_Secret(in *v1.Secret, out *core.Secret, s conversion.Scope) error { + if err := autoConvert_v1_Secret_To_core_Secret(in, out, s); err != nil { + return err + } + + // StringData overwrites Data + if len(in.StringData) > 0 { + if out.Data == nil { + out.Data = map[string][]byte{} + } + for k, v := range in.StringData { + out.Data[k] = []byte(v) + } + } + + return nil +} +func Convert_core_SecurityContext_To_v1_SecurityContext(in *core.SecurityContext, out *v1.SecurityContext, s conversion.Scope) error { + if in.Capabilities != nil { + out.Capabilities = new(v1.Capabilities) + if err := Convert_core_Capabilities_To_v1_Capabilities(in.Capabilities, out.Capabilities, s); err != nil { + return err + } + } else { + out.Capabilities = nil + } + out.Privileged = in.Privileged + if in.SELinuxOptions != nil { + out.SELinuxOptions = new(v1.SELinuxOptions) + if err := Convert_core_SELinuxOptions_To_v1_SELinuxOptions(in.SELinuxOptions, out.SELinuxOptions, s); err != nil { + return err + } + } else { + out.SELinuxOptions = nil + } + out.RunAsUser = in.RunAsUser + out.RunAsNonRoot = in.RunAsNonRoot + out.ReadOnlyRootFilesystem = in.ReadOnlyRootFilesystem + out.AllowPrivilegeEscalation = in.AllowPrivilegeEscalation + return nil +} + +func Convert_core_PodSecurityContext_To_v1_PodSecurityContext(in *core.PodSecurityContext, out *v1.PodSecurityContext, s conversion.Scope) error { + out.SupplementalGroups = in.SupplementalGroups + if in.SELinuxOptions != nil { + out.SELinuxOptions = new(v1.SELinuxOptions) + if err := Convert_core_SELinuxOptions_To_v1_SELinuxOptions(in.SELinuxOptions, out.SELinuxOptions, s); err != nil { + return err + } + } else { + out.SELinuxOptions = nil + } + out.RunAsUser = in.RunAsUser + out.RunAsNonRoot = in.RunAsNonRoot + out.FSGroup = in.FSGroup + return nil +} + +func Convert_v1_PodSecurityContext_To_core_PodSecurityContext(in *v1.PodSecurityContext, out *core.PodSecurityContext, s conversion.Scope) error { + out.SupplementalGroups = in.SupplementalGroups + if in.SELinuxOptions != nil { + out.SELinuxOptions = new(core.SELinuxOptions) + if err := Convert_v1_SELinuxOptions_To_core_SELinuxOptions(in.SELinuxOptions, out.SELinuxOptions, s); err != nil { + return err + } + } else { + out.SELinuxOptions = nil + } + out.RunAsUser = in.RunAsUser + out.RunAsNonRoot = in.RunAsNonRoot + out.FSGroup = in.FSGroup + return nil +} + +// +k8s:conversion-fn=copy-only +func Convert_v1_ResourceList_To_core_ResourceList(in *v1.ResourceList, out *core.ResourceList, s conversion.Scope) error { + if *in == nil { + return nil + } + if *out == nil { + *out = make(core.ResourceList, len(*in)) + } + for key, val := range *in { + // Moved to defaults + // TODO(#18538): We round up resource values to milli scale to maintain API compatibility. + // In the future, we should instead reject values that need rounding. + // const milliScale = -3 + // val.RoundUp(milliScale) + + (*out)[core.ResourceName(key)] = val + } + return nil +} + +func AddFieldLabelConversionsForEvent(scheme *runtime.Scheme) error { + return scheme.AddFieldLabelConversionFunc("v1", "Event", + func(label, value string) (string, string, error) { + switch label { + case "involvedObject.kind", + "involvedObject.namespace", + "involvedObject.name", + "involvedObject.uid", + "involvedObject.apiVersion", + "involvedObject.resourceVersion", + "involvedObject.fieldPath", + "reason", + "source", + "type", + "metadata.namespace", + "metadata.name": + return label, value, nil + default: + return "", "", fmt.Errorf("field label not supported: %s", label) + } + }) +} + +func AddFieldLabelConversionsForNamespace(scheme *runtime.Scheme) error { + return scheme.AddFieldLabelConversionFunc("v1", "Namespace", + func(label, value string) (string, string, error) { + switch label { + case "status.phase", + "metadata.name": + return label, value, nil + default: + return "", "", fmt.Errorf("field label not supported: %s", label) + } + }) +} + +func AddFieldLabelConversionsForSecret(scheme *runtime.Scheme) error { + return scheme.AddFieldLabelConversionFunc("v1", "Secret", + func(label, value string) (string, string, error) { + switch label { + case "type", + "metadata.namespace", + "metadata.name": + return label, value, nil + default: + return "", "", fmt.Errorf("field label not supported: %s", label) + } + }) +} diff --git a/pkg/apis/core/v1/conversion_test.go b/pkg/apis/core/v1/conversion_test.go new file mode 100644 index 00000000000..e6d2bbdc56e --- /dev/null +++ b/pkg/apis/core/v1/conversion_test.go @@ -0,0 +1,348 @@ +/* +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 v1_test + +import ( + "encoding/json" + "math/rand" + "net/url" + "reflect" + "testing" + "time" + + "k8s.io/api/core/v1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/api/testing/fuzzer" + metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/diff" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/apis/core" + corefuzzer "k8s.io/kubernetes/pkg/apis/core/fuzzer" + corev1 "k8s.io/kubernetes/pkg/apis/core/v1" + "k8s.io/kubernetes/pkg/apis/extensions" + utilpointer "k8s.io/kubernetes/pkg/util/pointer" + + // enforce that all types are installed + _ "k8s.io/kubernetes/pkg/api/testapi" +) + +func TestPodLogOptions(t *testing.T) { + sinceSeconds := int64(1) + sinceTime := metav1.NewTime(time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC).Local()) + tailLines := int64(2) + limitBytes := int64(3) + + versionedLogOptions := &v1.PodLogOptions{ + Container: "mycontainer", + Follow: true, + Previous: true, + SinceSeconds: &sinceSeconds, + SinceTime: &sinceTime, + Timestamps: true, + TailLines: &tailLines, + LimitBytes: &limitBytes, + } + unversionedLogOptions := &core.PodLogOptions{ + Container: "mycontainer", + Follow: true, + Previous: true, + SinceSeconds: &sinceSeconds, + SinceTime: &sinceTime, + Timestamps: true, + TailLines: &tailLines, + LimitBytes: &limitBytes, + } + expectedParameters := url.Values{ + "container": {"mycontainer"}, + "follow": {"true"}, + "previous": {"true"}, + "sinceSeconds": {"1"}, + "sinceTime": {"2000-01-01T12:34:56Z"}, + "timestamps": {"true"}, + "tailLines": {"2"}, + "limitBytes": {"3"}, + } + + codec := runtime.NewParameterCodec(legacyscheme.Scheme) + + // unversioned -> query params + { + actualParameters, err := codec.EncodeParameters(unversionedLogOptions, v1.SchemeGroupVersion) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(actualParameters, expectedParameters) { + t.Fatalf("Expected\n%#v\ngot\n%#v", expectedParameters, actualParameters) + } + } + + // versioned -> query params + { + actualParameters, err := codec.EncodeParameters(versionedLogOptions, v1.SchemeGroupVersion) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(actualParameters, expectedParameters) { + t.Fatalf("Expected\n%#v\ngot\n%#v", expectedParameters, actualParameters) + } + } + + // query params -> versioned + { + convertedLogOptions := &v1.PodLogOptions{} + err := codec.DecodeParameters(expectedParameters, v1.SchemeGroupVersion, convertedLogOptions) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(convertedLogOptions, versionedLogOptions) { + t.Fatalf("Unexpected deserialization:\n%s", diff.ObjectGoPrintSideBySide(versionedLogOptions, convertedLogOptions)) + } + } + + // query params -> unversioned + { + convertedLogOptions := &core.PodLogOptions{} + err := codec.DecodeParameters(expectedParameters, v1.SchemeGroupVersion, convertedLogOptions) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(convertedLogOptions, unversionedLogOptions) { + t.Fatalf("Unexpected deserialization:\n%s", diff.ObjectGoPrintSideBySide(unversionedLogOptions, convertedLogOptions)) + } + } +} + +// TestPodSpecConversion tests that v1.ServiceAccount is an alias for +// ServiceAccountName. +func TestPodSpecConversion(t *testing.T) { + name, other := "foo", "bar" + + // Test internal -> v1. Should have both alias (DeprecatedServiceAccount) + // and new field (ServiceAccountName). + i := &core.PodSpec{ + ServiceAccountName: name, + } + v := v1.PodSpec{} + if err := legacyscheme.Scheme.Convert(i, &v, nil); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if v.ServiceAccountName != name { + t.Fatalf("want v1.ServiceAccountName %q, got %q", name, v.ServiceAccountName) + } + if v.DeprecatedServiceAccount != name { + t.Fatalf("want v1.DeprecatedServiceAccount %q, got %q", name, v.DeprecatedServiceAccount) + } + + // Test v1 -> internal. Either DeprecatedServiceAccount, ServiceAccountName, + // or both should translate to ServiceAccountName. ServiceAccountName wins + // if both are set. + testCases := []*v1.PodSpec{ + // New + {ServiceAccountName: name}, + // Alias + {DeprecatedServiceAccount: name}, + // Both: same + {ServiceAccountName: name, DeprecatedServiceAccount: name}, + // Both: different + {ServiceAccountName: name, DeprecatedServiceAccount: other}, + } + for k, v := range testCases { + got := core.PodSpec{} + err := legacyscheme.Scheme.Convert(v, &got, nil) + if err != nil { + t.Fatalf("unexpected error for case %d: %v", k, err) + } + if got.ServiceAccountName != name { + t.Fatalf("want core.ServiceAccountName %q, got %q", name, got.ServiceAccountName) + } + } +} + +func TestResourceListConversion(t *testing.T) { + bigMilliQuantity := resource.NewQuantity(resource.MaxMilliValue, resource.DecimalSI) + bigMilliQuantity.Add(resource.MustParse("12345m")) + + tests := []struct { + input v1.ResourceList + expected core.ResourceList + }{ + { // No changes necessary. + input: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("30M"), + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceStorage: resource.MustParse("1G"), + }, + expected: core.ResourceList{ + core.ResourceMemory: resource.MustParse("30M"), + core.ResourceCPU: resource.MustParse("100m"), + core.ResourceStorage: resource.MustParse("1G"), + }, + }, + { // Nano-scale values should be rounded up to milli-scale. + input: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("3.000023m"), + v1.ResourceMemory: resource.MustParse("500.000050m"), + }, + expected: core.ResourceList{ + core.ResourceCPU: resource.MustParse("4m"), + core.ResourceMemory: resource.MustParse("501m"), + }, + }, + { // Large values should still be accurate. + input: v1.ResourceList{ + v1.ResourceCPU: *bigMilliQuantity.Copy(), + v1.ResourceStorage: *bigMilliQuantity.Copy(), + }, + expected: core.ResourceList{ + core.ResourceCPU: *bigMilliQuantity.Copy(), + core.ResourceStorage: *bigMilliQuantity.Copy(), + }, + }, + } + + for i, test := range tests { + output := core.ResourceList{} + + // defaulting is a separate step from conversion that is applied when reading from the API or from etcd. + // perform that step explicitly. + corev1.SetDefaults_ResourceList(&test.input) + + err := legacyscheme.Scheme.Convert(&test.input, &output, nil) + if err != nil { + t.Fatalf("unexpected error for case %d: %v", i, err) + } + if !apiequality.Semantic.DeepEqual(test.expected, output) { + t.Errorf("unexpected conversion for case %d: Expected\n%+v;\nGot\n%+v", i, test.expected, output) + } + } +} + +func TestReplicationControllerConversion(t *testing.T) { + // If we start with a RC, we should always have round-trip fidelity. + inputs := []*v1.ReplicationController{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + Namespace: "namespace", + }, + Spec: v1.ReplicationControllerSpec{ + Replicas: utilpointer.Int32Ptr(1), + MinReadySeconds: 32, + Selector: map[string]string{"foo": "bar", "bar": "foo"}, + Template: &v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"foo": "bar", "bar": "foo"}, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "container", + Image: "image", + }, + }, + }, + }, + }, + Status: v1.ReplicationControllerStatus{ + Replicas: 1, + FullyLabeledReplicas: 2, + ReadyReplicas: 3, + AvailableReplicas: 4, + ObservedGeneration: 5, + Conditions: []v1.ReplicationControllerCondition{ + { + Type: v1.ReplicationControllerReplicaFailure, + Status: v1.ConditionTrue, + LastTransitionTime: metav1.NewTime(time.Unix(123456789, 0)), + Reason: "Reason", + Message: "Message", + }, + }, + }, + }, + } + + // Add some fuzzed RCs. + apiObjectFuzzer := fuzzer.FuzzerFor(fuzzer.MergeFuzzerFuncs(metafuzzer.Funcs, corefuzzer.Funcs), rand.NewSource(152), legacyscheme.Codecs) + for i := 0; i < 100; i++ { + rc := &v1.ReplicationController{} + apiObjectFuzzer.Fuzz(rc) + // Sometimes the fuzzer decides to leave Spec.Template nil. + // We can't support that because Spec.Template is not a pointer in RS, + // so it will round-trip as non-nil but empty. + if rc.Spec.Template == nil { + rc.Spec.Template = &v1.PodTemplateSpec{} + } + // Sometimes the fuzzer decides to insert an empty label key. + // This doesn't round-trip properly because it's invalid. + if rc.Spec.Selector != nil { + delete(rc.Spec.Selector, "") + } + inputs = append(inputs, rc) + } + + // Round-trip the input RCs before converting to RS. + for i := range inputs { + inputs[i] = roundTrip(t, inputs[i]).(*v1.ReplicationController) + } + + for _, in := range inputs { + rs := &extensions.ReplicaSet{} + // Use in.DeepCopy() to avoid sharing pointers with `in`. + if err := corev1.Convert_v1_ReplicationController_to_extensions_ReplicaSet(in.DeepCopy(), rs, nil); err != nil { + t.Errorf("can't convert RC to RS: %v", err) + continue + } + // Round-trip RS before converting back to RC. + rs = roundTripRS(t, rs) + out := &v1.ReplicationController{} + if err := corev1.Convert_extensions_ReplicaSet_to_v1_ReplicationController(rs, out, nil); err != nil { + t.Errorf("can't convert RS to RC: %v", err) + continue + } + if !apiequality.Semantic.DeepEqual(in, out) { + instr, _ := json.MarshalIndent(in, "", " ") + outstr, _ := json.MarshalIndent(out, "", " ") + t.Errorf("RC-RS conversion round-trip failed:\nin:\n%s\nout:\n%s", instr, outstr) + } + } +} + +func roundTripRS(t *testing.T, rs *extensions.ReplicaSet) *extensions.ReplicaSet { + codec := legacyscheme.Codecs.LegacyCodec(extensionsv1beta1.SchemeGroupVersion) + data, err := runtime.Encode(codec, rs) + if err != nil { + t.Errorf("%v\n %#v", err, rs) + return nil + } + obj2, err := runtime.Decode(codec, data) + if err != nil { + t.Errorf("%v\nData: %s\nSource: %#v", err, string(data), rs) + return nil + } + obj3 := &extensions.ReplicaSet{} + 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/core/v1/defaults.go b/pkg/apis/core/v1/defaults.go new file mode 100644 index 00000000000..c2aeafc3d2f --- /dev/null +++ b/pkg/apis/core/v1/defaults.go @@ -0,0 +1,413 @@ +/* +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 v1 + +import ( + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/features" + "k8s.io/kubernetes/pkg/util/parsers" + utilpointer "k8s.io/kubernetes/pkg/util/pointer" +) + +func addDefaultingFuncs(scheme *runtime.Scheme) error { + return RegisterDefaults(scheme) +} + +func SetDefaults_ResourceList(obj *v1.ResourceList) { + for key, val := range *obj { + // TODO(#18538): We round up resource values to milli scale to maintain API compatibility. + // In the future, we should instead reject values that need rounding. + const milliScale = -3 + val.RoundUp(milliScale) + + (*obj)[v1.ResourceName(key)] = val + } +} + +func SetDefaults_ReplicationController(obj *v1.ReplicationController) { + var labels map[string]string + if obj.Spec.Template != nil { + labels = obj.Spec.Template.Labels + } + // TODO: support templates defined elsewhere when we support them in the API + if labels != nil { + if len(obj.Spec.Selector) == 0 { + obj.Spec.Selector = labels + } + if len(obj.Labels) == 0 { + obj.Labels = labels + } + } + if obj.Spec.Replicas == nil { + obj.Spec.Replicas = new(int32) + *obj.Spec.Replicas = 1 + } +} +func SetDefaults_Volume(obj *v1.Volume) { + if utilpointer.AllPtrFieldsNil(&obj.VolumeSource) { + obj.VolumeSource = v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + } + } +} +func SetDefaults_ContainerPort(obj *v1.ContainerPort) { + if obj.Protocol == "" { + obj.Protocol = v1.ProtocolTCP + } +} +func SetDefaults_Container(obj *v1.Container) { + if obj.ImagePullPolicy == "" { + // Ignore error and assume it has been validated elsewhere + _, tag, _, _ := parsers.ParseImageName(obj.Image) + + // Check image tag + if tag == "latest" { + obj.ImagePullPolicy = v1.PullAlways + } else { + obj.ImagePullPolicy = v1.PullIfNotPresent + } + } + if obj.TerminationMessagePath == "" { + obj.TerminationMessagePath = v1.TerminationMessagePathDefault + } + if obj.TerminationMessagePolicy == "" { + obj.TerminationMessagePolicy = v1.TerminationMessageReadFile + } +} +func SetDefaults_Service(obj *v1.Service) { + if obj.Spec.SessionAffinity == "" { + obj.Spec.SessionAffinity = v1.ServiceAffinityNone + } + if obj.Spec.SessionAffinity == v1.ServiceAffinityNone { + obj.Spec.SessionAffinityConfig = nil + } + if obj.Spec.SessionAffinity == v1.ServiceAffinityClientIP { + if obj.Spec.SessionAffinityConfig == nil || obj.Spec.SessionAffinityConfig.ClientIP == nil || obj.Spec.SessionAffinityConfig.ClientIP.TimeoutSeconds == nil { + timeoutSeconds := v1.DefaultClientIPServiceAffinitySeconds + obj.Spec.SessionAffinityConfig = &v1.SessionAffinityConfig{ + ClientIP: &v1.ClientIPConfig{ + TimeoutSeconds: &timeoutSeconds, + }, + } + } + } + if obj.Spec.Type == "" { + obj.Spec.Type = v1.ServiceTypeClusterIP + } + for i := range obj.Spec.Ports { + sp := &obj.Spec.Ports[i] + if sp.Protocol == "" { + sp.Protocol = v1.ProtocolTCP + } + if sp.TargetPort == intstr.FromInt(0) || sp.TargetPort == intstr.FromString("") { + sp.TargetPort = intstr.FromInt(int(sp.Port)) + } + } + // Defaults ExternalTrafficPolicy field for NodePort / LoadBalancer service + // to Global for consistency. + if (obj.Spec.Type == v1.ServiceTypeNodePort || + obj.Spec.Type == v1.ServiceTypeLoadBalancer) && + obj.Spec.ExternalTrafficPolicy == "" { + obj.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyTypeCluster + } +} +func SetDefaults_Pod(obj *v1.Pod) { + // If limits are specified, but requests are not, default requests to limits + // This is done here rather than a more specific defaulting pass on v1.ResourceRequirements + // because we only want this defaulting semantic to take place on a v1.Pod and not a v1.PodTemplate + for i := range obj.Spec.Containers { + // set requests to limits if requests are not specified, but limits are + if obj.Spec.Containers[i].Resources.Limits != nil { + if obj.Spec.Containers[i].Resources.Requests == nil { + obj.Spec.Containers[i].Resources.Requests = make(v1.ResourceList) + } + for key, value := range obj.Spec.Containers[i].Resources.Limits { + if _, exists := obj.Spec.Containers[i].Resources.Requests[key]; !exists { + obj.Spec.Containers[i].Resources.Requests[key] = *(value.Copy()) + } + } + } + } + for i := range obj.Spec.InitContainers { + if obj.Spec.InitContainers[i].Resources.Limits != nil { + if obj.Spec.InitContainers[i].Resources.Requests == nil { + obj.Spec.InitContainers[i].Resources.Requests = make(v1.ResourceList) + } + for key, value := range obj.Spec.InitContainers[i].Resources.Limits { + if _, exists := obj.Spec.InitContainers[i].Resources.Requests[key]; !exists { + obj.Spec.InitContainers[i].Resources.Requests[key] = *(value.Copy()) + } + } + } + } +} +func SetDefaults_PodSpec(obj *v1.PodSpec) { + if obj.DNSPolicy == "" { + obj.DNSPolicy = v1.DNSClusterFirst + } + if obj.RestartPolicy == "" { + obj.RestartPolicy = v1.RestartPolicyAlways + } + if obj.HostNetwork { + defaultHostNetworkPorts(&obj.Containers) + defaultHostNetworkPorts(&obj.InitContainers) + } + if obj.SecurityContext == nil { + obj.SecurityContext = &v1.PodSecurityContext{} + } + if obj.TerminationGracePeriodSeconds == nil { + period := int64(v1.DefaultTerminationGracePeriodSeconds) + obj.TerminationGracePeriodSeconds = &period + } + if obj.SchedulerName == "" { + obj.SchedulerName = v1.DefaultSchedulerName + } +} +func SetDefaults_Probe(obj *v1.Probe) { + if obj.TimeoutSeconds == 0 { + obj.TimeoutSeconds = 1 + } + if obj.PeriodSeconds == 0 { + obj.PeriodSeconds = 10 + } + if obj.SuccessThreshold == 0 { + obj.SuccessThreshold = 1 + } + if obj.FailureThreshold == 0 { + obj.FailureThreshold = 3 + } +} +func SetDefaults_SecretVolumeSource(obj *v1.SecretVolumeSource) { + if obj.DefaultMode == nil { + perm := int32(v1.SecretVolumeSourceDefaultMode) + obj.DefaultMode = &perm + } +} +func SetDefaults_ConfigMapVolumeSource(obj *v1.ConfigMapVolumeSource) { + if obj.DefaultMode == nil { + perm := int32(v1.ConfigMapVolumeSourceDefaultMode) + obj.DefaultMode = &perm + } +} +func SetDefaults_DownwardAPIVolumeSource(obj *v1.DownwardAPIVolumeSource) { + if obj.DefaultMode == nil { + perm := int32(v1.DownwardAPIVolumeSourceDefaultMode) + obj.DefaultMode = &perm + } +} +func SetDefaults_Secret(obj *v1.Secret) { + if obj.Type == "" { + obj.Type = v1.SecretTypeOpaque + } +} +func SetDefaults_ProjectedVolumeSource(obj *v1.ProjectedVolumeSource) { + if obj.DefaultMode == nil { + perm := int32(v1.ProjectedVolumeSourceDefaultMode) + obj.DefaultMode = &perm + } +} +func SetDefaults_PersistentVolume(obj *v1.PersistentVolume) { + if obj.Status.Phase == "" { + obj.Status.Phase = v1.VolumePending + } + if obj.Spec.PersistentVolumeReclaimPolicy == "" { + obj.Spec.PersistentVolumeReclaimPolicy = v1.PersistentVolumeReclaimRetain + } + if obj.Spec.VolumeMode == nil && utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + obj.Spec.VolumeMode = new(v1.PersistentVolumeMode) + *obj.Spec.VolumeMode = v1.PersistentVolumeFilesystem + } +} +func SetDefaults_PersistentVolumeClaim(obj *v1.PersistentVolumeClaim) { + if obj.Status.Phase == "" { + obj.Status.Phase = v1.ClaimPending + } + if obj.Spec.VolumeMode == nil && utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + obj.Spec.VolumeMode = new(v1.PersistentVolumeMode) + *obj.Spec.VolumeMode = v1.PersistentVolumeFilesystem + } +} +func SetDefaults_ISCSIVolumeSource(obj *v1.ISCSIVolumeSource) { + if obj.ISCSIInterface == "" { + obj.ISCSIInterface = "default" + } +} +func SetDefaults_ISCSIPersistentVolumeSource(obj *v1.ISCSIPersistentVolumeSource) { + if obj.ISCSIInterface == "" { + obj.ISCSIInterface = "default" + } +} +func SetDefaults_AzureDiskVolumeSource(obj *v1.AzureDiskVolumeSource) { + if obj.CachingMode == nil { + obj.CachingMode = new(v1.AzureDataDiskCachingMode) + *obj.CachingMode = v1.AzureDataDiskCachingReadWrite + } + if obj.Kind == nil { + obj.Kind = new(v1.AzureDataDiskKind) + *obj.Kind = v1.AzureSharedBlobDisk + } + if obj.FSType == nil { + obj.FSType = new(string) + *obj.FSType = "ext4" + } + if obj.ReadOnly == nil { + obj.ReadOnly = new(bool) + *obj.ReadOnly = false + } +} +func SetDefaults_Endpoints(obj *v1.Endpoints) { + for i := range obj.Subsets { + ss := &obj.Subsets[i] + for i := range ss.Ports { + ep := &ss.Ports[i] + if ep.Protocol == "" { + ep.Protocol = v1.ProtocolTCP + } + } + } +} +func SetDefaults_HTTPGetAction(obj *v1.HTTPGetAction) { + if obj.Path == "" { + obj.Path = "/" + } + if obj.Scheme == "" { + obj.Scheme = v1.URISchemeHTTP + } +} +func SetDefaults_NamespaceStatus(obj *v1.NamespaceStatus) { + if obj.Phase == "" { + obj.Phase = v1.NamespaceActive + } +} +func SetDefaults_Node(obj *v1.Node) { + if obj.Spec.ExternalID == "" { + obj.Spec.ExternalID = obj.Name + } +} +func SetDefaults_NodeStatus(obj *v1.NodeStatus) { + if obj.Allocatable == nil && obj.Capacity != nil { + obj.Allocatable = make(v1.ResourceList, len(obj.Capacity)) + for key, value := range obj.Capacity { + obj.Allocatable[key] = *(value.Copy()) + } + obj.Allocatable = obj.Capacity + } +} +func SetDefaults_ObjectFieldSelector(obj *v1.ObjectFieldSelector) { + if obj.APIVersion == "" { + obj.APIVersion = "v1" + } +} +func SetDefaults_LimitRangeItem(obj *v1.LimitRangeItem) { + // for container limits, we apply default values + if obj.Type == v1.LimitTypeContainer { + + if obj.Default == nil { + obj.Default = make(v1.ResourceList) + } + if obj.DefaultRequest == nil { + obj.DefaultRequest = make(v1.ResourceList) + } + + // If a default limit is unspecified, but the max is specified, default the limit to the max + for key, value := range obj.Max { + if _, exists := obj.Default[key]; !exists { + obj.Default[key] = *(value.Copy()) + } + } + // If a default limit is specified, but the default request is not, default request to limit + for key, value := range obj.Default { + if _, exists := obj.DefaultRequest[key]; !exists { + obj.DefaultRequest[key] = *(value.Copy()) + } + } + // If a default request is not specified, but the min is provided, default request to the min + for key, value := range obj.Min { + if _, exists := obj.DefaultRequest[key]; !exists { + obj.DefaultRequest[key] = *(value.Copy()) + } + } + } +} +func SetDefaults_ConfigMap(obj *v1.ConfigMap) { + if obj.Data == nil { + obj.Data = make(map[string]string) + } +} + +// With host networking default all container ports to host ports. +func defaultHostNetworkPorts(containers *[]v1.Container) { + for i := range *containers { + for j := range (*containers)[i].Ports { + if (*containers)[i].Ports[j].HostPort == 0 { + (*containers)[i].Ports[j].HostPort = (*containers)[i].Ports[j].ContainerPort + } + } + } +} + +func SetDefaults_RBDVolumeSource(obj *v1.RBDVolumeSource) { + if obj.RBDPool == "" { + obj.RBDPool = "rbd" + } + if obj.RadosUser == "" { + obj.RadosUser = "admin" + } + if obj.Keyring == "" { + obj.Keyring = "/etc/ceph/keyring" + } +} + +func SetDefaults_RBDPersistentVolumeSource(obj *v1.RBDPersistentVolumeSource) { + if obj.RBDPool == "" { + obj.RBDPool = "rbd" + } + if obj.RadosUser == "" { + obj.RadosUser = "admin" + } + if obj.Keyring == "" { + obj.Keyring = "/etc/ceph/keyring" + } +} + +func SetDefaults_ScaleIOVolumeSource(obj *v1.ScaleIOVolumeSource) { + if obj.StorageMode == "" { + obj.StorageMode = "ThinProvisioned" + } + if obj.FSType == "" { + obj.FSType = "xfs" + } +} + +func SetDefaults_ScaleIOPersistentVolumeSource(obj *v1.ScaleIOPersistentVolumeSource) { + if obj.StorageMode == "" { + obj.StorageMode = "ThinProvisioned" + } + if obj.FSType == "" { + obj.FSType = "xfs" + } +} + +func SetDefaults_HostPathVolumeSource(obj *v1.HostPathVolumeSource) { + typeVol := v1.HostPathUnset + if obj.Type == nil { + obj.Type = &typeVol + } +} diff --git a/pkg/apis/core/v1/defaults_test.go b/pkg/apis/core/v1/defaults_test.go new file mode 100644 index 00000000000..9a071f38ae9 --- /dev/null +++ b/pkg/apis/core/v1/defaults_test.go @@ -0,0 +1,1391 @@ +/* +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 v1_test + +import ( + "fmt" + "reflect" + "testing" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/api/legacyscheme" + corev1 "k8s.io/kubernetes/pkg/apis/core/v1" + + // enforce that all types are installed + _ "k8s.io/kubernetes/pkg/api/testapi" +) + +func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { + codec := legacyscheme.Codecs.LegacyCodec(corev1.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 TestSetDefaultReplicationController(t *testing.T) { + tests := []struct { + rc *v1.ReplicationController + expectLabels bool + expectSelector bool + }{ + { + rc: &v1.ReplicationController{ + Spec: v1.ReplicationControllerSpec{ + Template: &v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "foo": "bar", + }, + }, + }, + }, + }, + expectLabels: true, + expectSelector: true, + }, + { + rc: &v1.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "bar": "foo", + }, + }, + Spec: v1.ReplicationControllerSpec{ + Template: &v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "foo": "bar", + }, + }, + }, + }, + }, + expectLabels: false, + expectSelector: true, + }, + { + rc: &v1.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "bar": "foo", + }, + }, + Spec: v1.ReplicationControllerSpec{ + Selector: map[string]string{ + "some": "other", + }, + Template: &v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "foo": "bar", + }, + }, + }, + }, + }, + expectLabels: false, + expectSelector: false, + }, + { + rc: &v1.ReplicationController{ + Spec: v1.ReplicationControllerSpec{ + Selector: map[string]string{ + "some": "other", + }, + Template: &v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "foo": "bar", + }, + }, + }, + }, + }, + expectLabels: true, + expectSelector: false, + }, + } + + for _, test := range tests { + rc := test.rc + obj2 := roundTrip(t, runtime.Object(rc)) + rc2, ok := obj2.(*v1.ReplicationController) + if !ok { + t.Errorf("unexpected object: %v", rc2) + t.FailNow() + } + if test.expectSelector != reflect.DeepEqual(rc2.Spec.Selector, rc2.Spec.Template.Labels) { + if test.expectSelector { + t.Errorf("expected: %v, got: %v", rc2.Spec.Template.Labels, rc2.Spec.Selector) + } else { + t.Errorf("unexpected equality: %v", rc.Spec.Selector) + } + } + if test.expectLabels != reflect.DeepEqual(rc2.Labels, rc2.Spec.Template.Labels) { + if test.expectLabels { + t.Errorf("expected: %v, got: %v", rc2.Spec.Template.Labels, rc2.Labels) + } else { + t.Errorf("unexpected equality: %v", rc.Labels) + } + } + } +} + +func newInt(val int32) *int32 { + p := new(int32) + *p = val + return p +} + +func TestSetDefaultReplicationControllerReplicas(t *testing.T) { + tests := []struct { + rc v1.ReplicationController + expectReplicas int32 + }{ + { + rc: v1.ReplicationController{ + Spec: v1.ReplicationControllerSpec{ + Template: &v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "foo": "bar", + }, + }, + }, + }, + }, + expectReplicas: 1, + }, + { + rc: v1.ReplicationController{ + Spec: v1.ReplicationControllerSpec{ + Replicas: newInt(0), + Template: &v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "foo": "bar", + }, + }, + }, + }, + }, + expectReplicas: 0, + }, + { + rc: v1.ReplicationController{ + Spec: v1.ReplicationControllerSpec{ + Replicas: newInt(3), + Template: &v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "foo": "bar", + }, + }, + }, + }, + }, + expectReplicas: 3, + }, + } + + for _, test := range tests { + rc := &test.rc + obj2 := roundTrip(t, runtime.Object(rc)) + rc2, ok := obj2.(*v1.ReplicationController) + if !ok { + t.Errorf("unexpected object: %v", rc2) + t.FailNow() + } + if rc2.Spec.Replicas == nil { + t.Errorf("unexpected nil Replicas") + } else if test.expectReplicas != *rc2.Spec.Replicas { + t.Errorf("expected: %d replicas, got: %d", test.expectReplicas, *rc2.Spec.Replicas) + } + } +} + +type InitContainerValidator func(got, expected *v1.Container) error + +func TestSetDefaultReplicationControllerInitContainers(t *testing.T) { + assertEnvFieldRef := func(got, expected *v1.Container) error { + if len(got.Env) != len(expected.Env) { + return fmt.Errorf("different number of env: got <%v>, expected <%v>", len(got.Env), len(expected.Env)) + } + + for j := range got.Env { + ge := &got.Env[j] + ee := &expected.Env[j] + + if ge.Name != ee.Name { + return fmt.Errorf("different name of env: got <%v>, expected <%v>", ge.Name, ee.Name) + } + + if ge.ValueFrom.FieldRef.APIVersion != ee.ValueFrom.FieldRef.APIVersion { + return fmt.Errorf("different api version of FieldRef <%v>: got <%v>, expected <%v>", + ge.Name, ge.ValueFrom.FieldRef.APIVersion, ee.ValueFrom.FieldRef.APIVersion) + } + } + return nil + } + + assertImagePullPolicy := func(got, expected *v1.Container) error { + if got.ImagePullPolicy != expected.ImagePullPolicy { + return fmt.Errorf("different image pull poicy: got <%v>, expected <%v>", got.ImagePullPolicy, expected.ImagePullPolicy) + } + return nil + } + + assertContainerPort := func(got, expected *v1.Container) error { + if len(got.Ports) != len(expected.Ports) { + return fmt.Errorf("different number of ports: got <%v>, expected <%v>", len(got.Ports), len(expected.Ports)) + } + + for i := range got.Ports { + gp := &got.Ports[i] + ep := &expected.Ports[i] + + if gp.Name != ep.Name { + return fmt.Errorf("different name of port: got <%v>, expected <%v>", gp.Name, ep.Name) + } + + if gp.Protocol != ep.Protocol { + return fmt.Errorf("different port protocol <%v>: got <%v>, expected <%v>", gp.Name, gp.Protocol, ep.Protocol) + } + } + + return nil + } + + assertResource := func(got, expected *v1.Container) error { + if len(got.Resources.Limits) != len(expected.Resources.Limits) { + return fmt.Errorf("different number of resources.Limits: got <%v>, expected <%v>", len(got.Resources.Limits), (expected.Resources.Limits)) + } + + for k, v := range got.Resources.Limits { + if ev, found := expected.Resources.Limits[v1.ResourceName(k)]; !found { + return fmt.Errorf("failed to find resource <%v> in expected resources.Limits.", k) + } else { + if ev.Value() != v.Value() { + return fmt.Errorf("different resource.Limits: got <%v>, expected <%v>.", v.Value(), ev.Value()) + } + } + } + + if len(got.Resources.Requests) != len(expected.Resources.Requests) { + return fmt.Errorf("different number of resources.Requests: got <%v>, expected <%v>", len(got.Resources.Requests), (expected.Resources.Requests)) + } + + for k, v := range got.Resources.Requests { + if ev, found := expected.Resources.Requests[v1.ResourceName(k)]; !found { + return fmt.Errorf("failed to find resource <%v> in expected resources.Requests.", k) + } else { + if ev.Value() != v.Value() { + return fmt.Errorf("different resource.Requests: got <%v>, expected <%v>.", v.Value(), ev.Value()) + } + } + } + + return nil + } + + assertProb := func(got, expected *v1.Container) error { + // Assert LivenessProbe + if got.LivenessProbe.Handler.HTTPGet.Path != expected.LivenessProbe.Handler.HTTPGet.Path || + got.LivenessProbe.Handler.HTTPGet.Scheme != expected.LivenessProbe.Handler.HTTPGet.Scheme || + got.LivenessProbe.FailureThreshold != expected.LivenessProbe.FailureThreshold || + got.LivenessProbe.SuccessThreshold != expected.LivenessProbe.SuccessThreshold || + got.LivenessProbe.PeriodSeconds != expected.LivenessProbe.PeriodSeconds || + got.LivenessProbe.TimeoutSeconds != expected.LivenessProbe.TimeoutSeconds { + return fmt.Errorf("different LivenessProbe: got <%v>, expected <%v>", got.LivenessProbe, expected.LivenessProbe) + } + + // Assert ReadinessProbe + if got.ReadinessProbe.Handler.HTTPGet.Path != expected.ReadinessProbe.Handler.HTTPGet.Path || + got.ReadinessProbe.Handler.HTTPGet.Scheme != expected.ReadinessProbe.Handler.HTTPGet.Scheme || + got.ReadinessProbe.FailureThreshold != expected.ReadinessProbe.FailureThreshold || + got.ReadinessProbe.SuccessThreshold != expected.ReadinessProbe.SuccessThreshold || + got.ReadinessProbe.PeriodSeconds != expected.ReadinessProbe.PeriodSeconds || + got.ReadinessProbe.TimeoutSeconds != expected.ReadinessProbe.TimeoutSeconds { + return fmt.Errorf("different ReadinessProbe: got <%v>, expected <%v>", got.ReadinessProbe, expected.ReadinessProbe) + } + + return nil + } + + assertLifeCycle := func(got, expected *v1.Container) error { + if got.Lifecycle.PostStart.HTTPGet.Path != expected.Lifecycle.PostStart.HTTPGet.Path || + got.Lifecycle.PostStart.HTTPGet.Scheme != expected.Lifecycle.PostStart.HTTPGet.Scheme { + return fmt.Errorf("different LifeCycle: got <%v>, expected <%v>", got.Lifecycle, expected.Lifecycle) + } + + return nil + } + + cpu, _ := resource.ParseQuantity("100m") + mem, _ := resource.ParseQuantity("100Mi") + + tests := []struct { + name string + rc v1.ReplicationController + expected []v1.Container + validators []InitContainerValidator + }{ + { + name: "imagePullIPolicy", + rc: v1.ReplicationController{ + Spec: v1.ReplicationControllerSpec{ + Template: &v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + InitContainers: []v1.Container{ + { + Name: "install", + Image: "busybox", + }, + }, + }, + }, + }, + }, + expected: []v1.Container{ + { + ImagePullPolicy: v1.PullAlways, + }, + }, + validators: []InitContainerValidator{assertImagePullPolicy}, + }, + { + name: "FieldRef", + rc: v1.ReplicationController{ + Spec: v1.ReplicationControllerSpec{ + Template: &v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + InitContainers: []v1.Container{ + { + Name: "fun", + Image: "alpine", + Env: []v1.EnvVar{ + { + Name: "MY_POD_IP", + ValueFrom: &v1.EnvVarSource{ + FieldRef: &v1.ObjectFieldSelector{ + APIVersion: "", + FieldPath: "status.podIP", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + expected: []v1.Container{ + { + Env: []v1.EnvVar{ + { + Name: "MY_POD_IP", + ValueFrom: &v1.EnvVarSource{ + FieldRef: &v1.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "status.podIP", + }, + }, + }, + }, + }, + }, + validators: []InitContainerValidator{assertEnvFieldRef}, + }, + { + name: "ContainerPort", + rc: v1.ReplicationController{ + Spec: v1.ReplicationControllerSpec{ + Template: &v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + InitContainers: []v1.Container{ + { + Name: "fun", + Image: "alpine", + Ports: []v1.ContainerPort{ + { + Name: "default", + }, + }, + }, + }, + }, + }, + }, + }, + expected: []v1.Container{ + { + Ports: []v1.ContainerPort{ + { + Name: "default", + Protocol: v1.ProtocolTCP, + }, + }, + }, + }, + validators: []InitContainerValidator{assertContainerPort}, + }, + { + name: "Resources", + rc: v1.ReplicationController{ + Spec: v1.ReplicationControllerSpec{ + Template: &v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + InitContainers: []v1.Container{ + { + Name: "fun", + Image: "alpine", + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + }, + }, + }, + }, + }, + }, + }, + expected: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: cpu, + v1.ResourceMemory: mem, + }, + Requests: v1.ResourceList{ + v1.ResourceCPU: cpu, + v1.ResourceMemory: mem, + }, + }, + }, + }, + validators: []InitContainerValidator{assertResource}, + }, + { + name: "Probe", + rc: v1.ReplicationController{ + Spec: v1.ReplicationControllerSpec{ + Template: &v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + InitContainers: []v1.Container{ + { + Name: "fun", + Image: "alpine", + LivenessProbe: &v1.Probe{ + Handler: v1.Handler{ + HTTPGet: &v1.HTTPGetAction{ + Host: "localhost", + }, + }, + }, + ReadinessProbe: &v1.Probe{ + Handler: v1.Handler{ + HTTPGet: &v1.HTTPGetAction{ + Host: "localhost", + }, + }, + }, + }, + }, + }, + }, + }, + }, + expected: []v1.Container{ + { + LivenessProbe: &v1.Probe{ + Handler: v1.Handler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/", + Scheme: v1.URISchemeHTTP, + }, + }, + TimeoutSeconds: 1, + PeriodSeconds: 10, + SuccessThreshold: 1, + FailureThreshold: 3, + }, + ReadinessProbe: &v1.Probe{ + Handler: v1.Handler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/", + Scheme: v1.URISchemeHTTP, + }, + }, + TimeoutSeconds: 1, + PeriodSeconds: 10, + SuccessThreshold: 1, + FailureThreshold: 3, + }, + }, + }, + validators: []InitContainerValidator{assertProb}, + }, + { + name: "LifeCycle", + rc: v1.ReplicationController{ + Spec: v1.ReplicationControllerSpec{ + Template: &v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + InitContainers: []v1.Container{ + { + Name: "fun", + Image: "alpine", + Ports: []v1.ContainerPort{ + { + Name: "default", + }, + }, + Lifecycle: &v1.Lifecycle{ + PostStart: &v1.Handler{ + HTTPGet: &v1.HTTPGetAction{ + Host: "localhost", + }, + }, + PreStop: &v1.Handler{ + HTTPGet: &v1.HTTPGetAction{ + Host: "localhost", + }, + }, + }, + }, + }, + }, + }, + }, + }, + expected: []v1.Container{ + { + Lifecycle: &v1.Lifecycle{ + PostStart: &v1.Handler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/", + Scheme: v1.URISchemeHTTP, + }, + }, + PreStop: &v1.Handler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/", + Scheme: v1.URISchemeHTTP, + }, + }, + }, + }, + }, + validators: []InitContainerValidator{assertLifeCycle}, + }, + } + + assertInitContainers := func(got, expected []v1.Container, validators []InitContainerValidator) error { + if len(got) != len(expected) { + return fmt.Errorf("different number of init container: got <%d>, expected <%d>", + len(got), len(expected)) + } + + for i := range got { + g := &got[i] + e := &expected[i] + + for _, validator := range validators { + if err := validator(g, e); err != nil { + return err + } + } + } + + return nil + } + + for _, test := range tests { + rc := &test.rc + obj2 := roundTrip(t, runtime.Object(rc)) + rc2, ok := obj2.(*v1.ReplicationController) + if !ok { + t.Errorf("unexpected object: %v", rc2) + t.FailNow() + } + + if err := assertInitContainers(rc2.Spec.Template.Spec.InitContainers, test.expected, test.validators); err != nil { + t.Errorf("test %v failed: %v", test.name, err) + } + } +} + +func TestSetDefaultService(t *testing.T) { + svc := &v1.Service{} + obj2 := roundTrip(t, runtime.Object(svc)) + svc2 := obj2.(*v1.Service) + if svc2.Spec.SessionAffinity != v1.ServiceAffinityNone { + t.Errorf("Expected default session affinity type:%s, got: %s", v1.ServiceAffinityNone, svc2.Spec.SessionAffinity) + } + if svc2.Spec.SessionAffinityConfig != nil { + t.Errorf("Expected empty session affinity config when session affinity type: %s, got: %v", v1.ServiceAffinityNone, svc2.Spec.SessionAffinityConfig) + } + if svc2.Spec.Type != v1.ServiceTypeClusterIP { + t.Errorf("Expected default type:%s, got: %s", v1.ServiceTypeClusterIP, svc2.Spec.Type) + } +} + +func TestSetDefaultServiceSessionAffinityConfig(t *testing.T) { + testCases := map[string]v1.Service{ + "SessionAffinityConfig is empty": { + Spec: v1.ServiceSpec{ + SessionAffinity: v1.ServiceAffinityClientIP, + SessionAffinityConfig: nil, + }, + }, + "ClientIP is empty": { + Spec: v1.ServiceSpec{ + SessionAffinity: v1.ServiceAffinityClientIP, + SessionAffinityConfig: &v1.SessionAffinityConfig{ + ClientIP: nil, + }, + }, + }, + "TimeoutSeconds is empty": { + Spec: v1.ServiceSpec{ + SessionAffinity: v1.ServiceAffinityClientIP, + SessionAffinityConfig: &v1.SessionAffinityConfig{ + ClientIP: &v1.ClientIPConfig{ + TimeoutSeconds: nil, + }, + }, + }, + }, + } + for name, test := range testCases { + obj2 := roundTrip(t, runtime.Object(&test)) + svc2 := obj2.(*v1.Service) + if svc2.Spec.SessionAffinityConfig == nil || svc2.Spec.SessionAffinityConfig.ClientIP == nil || svc2.Spec.SessionAffinityConfig.ClientIP.TimeoutSeconds == nil { + t.Fatalf("Case: %s, unexpected empty SessionAffinityConfig/ClientIP/TimeoutSeconds when session affinity type: %s, got: %v", name, v1.ServiceAffinityClientIP, svc2.Spec.SessionAffinityConfig) + } + if *svc2.Spec.SessionAffinityConfig.ClientIP.TimeoutSeconds != v1.DefaultClientIPServiceAffinitySeconds { + t.Errorf("Case: %s, default TimeoutSeconds should be %d when session affinity type: %s, got: %d", name, v1.DefaultClientIPServiceAffinitySeconds, v1.ServiceAffinityClientIP, *svc2.Spec.SessionAffinityConfig.ClientIP.TimeoutSeconds) + } + } +} + +func TestSetDefaultSecretVolumeSource(t *testing.T) { + s := v1.PodSpec{} + s.Volumes = []v1.Volume{ + { + VolumeSource: v1.VolumeSource{ + Secret: &v1.SecretVolumeSource{}, + }, + }, + } + pod := &v1.Pod{ + Spec: s, + } + output := roundTrip(t, runtime.Object(pod)) + pod2 := output.(*v1.Pod) + defaultMode := pod2.Spec.Volumes[0].VolumeSource.Secret.DefaultMode + expectedMode := v1.SecretVolumeSourceDefaultMode + + if defaultMode == nil || *defaultMode != expectedMode { + t.Errorf("Expected secret DefaultMode %v, got %v", expectedMode, defaultMode) + } +} + +func TestSetDefaultConfigMapVolumeSource(t *testing.T) { + s := v1.PodSpec{} + s.Volumes = []v1.Volume{ + { + VolumeSource: v1.VolumeSource{ + ConfigMap: &v1.ConfigMapVolumeSource{}, + }, + }, + } + pod := &v1.Pod{ + Spec: s, + } + output := roundTrip(t, runtime.Object(pod)) + pod2 := output.(*v1.Pod) + defaultMode := pod2.Spec.Volumes[0].VolumeSource.ConfigMap.DefaultMode + expectedMode := v1.ConfigMapVolumeSourceDefaultMode + + if defaultMode == nil || *defaultMode != expectedMode { + t.Errorf("Expected v1.ConfigMap DefaultMode %v, got %v", expectedMode, defaultMode) + } +} + +func TestSetDefaultDownwardAPIVolumeSource(t *testing.T) { + s := v1.PodSpec{} + s.Volumes = []v1.Volume{ + { + VolumeSource: v1.VolumeSource{ + DownwardAPI: &v1.DownwardAPIVolumeSource{}, + }, + }, + } + pod := &v1.Pod{ + Spec: s, + } + output := roundTrip(t, runtime.Object(pod)) + pod2 := output.(*v1.Pod) + defaultMode := pod2.Spec.Volumes[0].VolumeSource.DownwardAPI.DefaultMode + expectedMode := v1.DownwardAPIVolumeSourceDefaultMode + + if defaultMode == nil || *defaultMode != expectedMode { + t.Errorf("Expected DownwardAPI DefaultMode %v, got %v", expectedMode, defaultMode) + } +} + +func TestSetDefaultProjectedVolumeSource(t *testing.T) { + s := v1.PodSpec{} + s.Volumes = []v1.Volume{ + { + VolumeSource: v1.VolumeSource{ + Projected: &v1.ProjectedVolumeSource{}, + }, + }, + } + pod := &v1.Pod{ + Spec: s, + } + output := roundTrip(t, runtime.Object(pod)) + pod2 := output.(*v1.Pod) + defaultMode := pod2.Spec.Volumes[0].VolumeSource.Projected.DefaultMode + expectedMode := v1.ProjectedVolumeSourceDefaultMode + + if defaultMode == nil || *defaultMode != expectedMode { + t.Errorf("Expected v1.ProjectedVolumeSource DefaultMode %v, got %v", expectedMode, defaultMode) + } +} + +func TestSetDefaultSecret(t *testing.T) { + s := &v1.Secret{} + obj2 := roundTrip(t, runtime.Object(s)) + s2 := obj2.(*v1.Secret) + + if s2.Type != v1.SecretTypeOpaque { + t.Errorf("Expected secret type %v, got %v", v1.SecretTypeOpaque, s2.Type) + } +} + +func TestSetDefaultPersistentVolume(t *testing.T) { + pv := &v1.PersistentVolume{} + obj2 := roundTrip(t, runtime.Object(pv)) + pv2 := obj2.(*v1.PersistentVolume) + + if pv2.Status.Phase != v1.VolumePending { + t.Errorf("Expected volume phase %v, got %v", v1.VolumePending, pv2.Status.Phase) + } + if pv2.Spec.PersistentVolumeReclaimPolicy != v1.PersistentVolumeReclaimRetain { + t.Errorf("Expected pv reclaim policy %v, got %v", v1.PersistentVolumeReclaimRetain, pv2.Spec.PersistentVolumeReclaimPolicy) + } + + // When feature gate is disabled, field should not be defaulted + defaultMode := v1.PersistentVolumeFilesystem + outputMode := pv2.Spec.VolumeMode + if outputMode != nil { + t.Errorf("Expected VolumeMode to not be defaulted, got: %+v", outputMode) + } + + // When feature gate is enabled, field should be defaulted + err := utilfeature.DefaultFeatureGate.Set("BlockVolume=true") + if err != nil { + t.Fatalf("Failed to enable feature gate for BlockVolume: %v", err) + } + obj3 := roundTrip(t, runtime.Object(pv)).(*v1.PersistentVolume) + outputMode3 := obj3.Spec.VolumeMode + + if outputMode3 == nil { + t.Errorf("Expected VolumeMode to be defaulted to: %+v, got: nil", defaultMode) + } else if *outputMode3 != defaultMode { + t.Errorf("Expected VolumeMode to be defaulted to: %+v, got: %+v", defaultMode, outputMode3) + } + + err = utilfeature.DefaultFeatureGate.Set("BlockVolume=false") + if err != nil { + t.Fatalf("Failed to disable feature gate for BlockVolume: %v", err) + } + +} + +func TestSetDefaultPersistentVolumeClaim(t *testing.T) { + pvc := &v1.PersistentVolumeClaim{} + obj2 := roundTrip(t, runtime.Object(pvc)) + pvc2 := obj2.(*v1.PersistentVolumeClaim) + + if pvc2.Status.Phase != v1.ClaimPending { + t.Errorf("Expected claim phase %v, got %v", v1.ClaimPending, pvc2.Status.Phase) + } + + // When feature gate is disabled, field should not be defaulted + defaultMode := v1.PersistentVolumeFilesystem + outputMode := pvc2.Spec.VolumeMode + if outputMode != nil { + t.Errorf("Expected VolumeMode to not be defaulted, got: %+v", outputMode) + } + + // When feature gate is enabled, field should be defaulted + err := utilfeature.DefaultFeatureGate.Set("BlockVolume=true") + if err != nil { + t.Fatalf("Failed to enable feature gate for BlockVolume: %v", err) + } + obj3 := roundTrip(t, runtime.Object(pvc)).(*v1.PersistentVolumeClaim) + outputMode3 := obj3.Spec.VolumeMode + + if outputMode3 == nil { + t.Errorf("Expected VolumeMode to be defaulted to: %+v, got: nil", defaultMode) + } else if *outputMode3 != defaultMode { + t.Errorf("Expected VolumeMode to be defaulted to: %+v, got: %+v", defaultMode, outputMode3) + } + + err = utilfeature.DefaultFeatureGate.Set("BlockVolume=false") + if err != nil { + t.Fatalf("Failed to disable feature gate for BlockVolume: %v", err) + } +} + +func TestSetDefaulEndpointsProtocol(t *testing.T) { + in := &v1.Endpoints{Subsets: []v1.EndpointSubset{ + {Ports: []v1.EndpointPort{{}, {Protocol: "UDP"}, {}}}, + }} + obj := roundTrip(t, runtime.Object(in)) + out := obj.(*v1.Endpoints) + + for i := range out.Subsets { + for j := range out.Subsets[i].Ports { + if in.Subsets[i].Ports[j].Protocol == "" { + if out.Subsets[i].Ports[j].Protocol != v1.ProtocolTCP { + t.Errorf("Expected protocol %s, got %s", v1.ProtocolTCP, out.Subsets[i].Ports[j].Protocol) + } + } else { + if out.Subsets[i].Ports[j].Protocol != in.Subsets[i].Ports[j].Protocol { + t.Errorf("Expected protocol %s, got %s", in.Subsets[i].Ports[j].Protocol, out.Subsets[i].Ports[j].Protocol) + } + } + } + } +} + +func TestSetDefaulServiceTargetPort(t *testing.T) { + in := &v1.Service{Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1234}}}} + obj := roundTrip(t, runtime.Object(in)) + out := obj.(*v1.Service) + if out.Spec.Ports[0].TargetPort != intstr.FromInt(1234) { + t.Errorf("Expected TargetPort to be defaulted, got %v", out.Spec.Ports[0].TargetPort) + } + + in = &v1.Service{Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1234, TargetPort: intstr.FromInt(5678)}}}} + obj = roundTrip(t, runtime.Object(in)) + out = obj.(*v1.Service) + if out.Spec.Ports[0].TargetPort != intstr.FromInt(5678) { + t.Errorf("Expected TargetPort to be unchanged, got %v", out.Spec.Ports[0].TargetPort) + } +} + +func TestSetDefaultServicePort(t *testing.T) { + // Unchanged if set. + in := &v1.Service{Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{ + {Protocol: "UDP", Port: 9376, TargetPort: intstr.FromString("p")}, + {Protocol: "UDP", Port: 8675, TargetPort: intstr.FromInt(309)}, + }, + }} + out := roundTrip(t, runtime.Object(in)).(*v1.Service) + if out.Spec.Ports[0].Protocol != v1.ProtocolUDP { + t.Errorf("Expected protocol %s, got %s", v1.ProtocolUDP, out.Spec.Ports[0].Protocol) + } + if out.Spec.Ports[0].TargetPort != intstr.FromString("p") { + t.Errorf("Expected port %v, got %v", in.Spec.Ports[0].Port, out.Spec.Ports[0].TargetPort) + } + if out.Spec.Ports[1].Protocol != v1.ProtocolUDP { + t.Errorf("Expected protocol %s, got %s", v1.ProtocolUDP, out.Spec.Ports[1].Protocol) + } + if out.Spec.Ports[1].TargetPort != intstr.FromInt(309) { + t.Errorf("Expected port %v, got %v", in.Spec.Ports[1].Port, out.Spec.Ports[1].TargetPort) + } + + // Defaulted. + in = &v1.Service{Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{ + {Protocol: "", Port: 9376, TargetPort: intstr.FromString("")}, + {Protocol: "", Port: 8675, TargetPort: intstr.FromInt(0)}, + }, + }} + out = roundTrip(t, runtime.Object(in)).(*v1.Service) + if out.Spec.Ports[0].Protocol != v1.ProtocolTCP { + t.Errorf("Expected protocol %s, got %s", v1.ProtocolTCP, out.Spec.Ports[0].Protocol) + } + if out.Spec.Ports[0].TargetPort != intstr.FromInt(int(in.Spec.Ports[0].Port)) { + t.Errorf("Expected port %v, got %v", in.Spec.Ports[0].Port, out.Spec.Ports[0].TargetPort) + } + if out.Spec.Ports[1].Protocol != v1.ProtocolTCP { + t.Errorf("Expected protocol %s, got %s", v1.ProtocolTCP, out.Spec.Ports[1].Protocol) + } + if out.Spec.Ports[1].TargetPort != intstr.FromInt(int(in.Spec.Ports[1].Port)) { + t.Errorf("Expected port %v, got %v", in.Spec.Ports[1].Port, out.Spec.Ports[1].TargetPort) + } +} + +func TestSetDefaulServiceExternalTraffic(t *testing.T) { + in := &v1.Service{} + obj := roundTrip(t, runtime.Object(in)) + out := obj.(*v1.Service) + if out.Spec.ExternalTrafficPolicy != "" { + t.Errorf("Expected ExternalTrafficPolicy to be empty, got %v", out.Spec.ExternalTrafficPolicy) + } + + in = &v1.Service{Spec: v1.ServiceSpec{Type: v1.ServiceTypeNodePort}} + obj = roundTrip(t, runtime.Object(in)) + out = obj.(*v1.Service) + if out.Spec.ExternalTrafficPolicy != v1.ServiceExternalTrafficPolicyTypeCluster { + t.Errorf("Expected ExternalTrafficPolicy to be %v, got %v", v1.ServiceExternalTrafficPolicyTypeCluster, out.Spec.ExternalTrafficPolicy) + } + + in = &v1.Service{Spec: v1.ServiceSpec{Type: v1.ServiceTypeLoadBalancer}} + obj = roundTrip(t, runtime.Object(in)) + out = obj.(*v1.Service) + if out.Spec.ExternalTrafficPolicy != v1.ServiceExternalTrafficPolicyTypeCluster { + t.Errorf("Expected ExternalTrafficPolicy to be %v, got %v", v1.ServiceExternalTrafficPolicyTypeCluster, out.Spec.ExternalTrafficPolicy) + } +} + +func TestSetDefaultNamespace(t *testing.T) { + s := &v1.Namespace{} + obj2 := roundTrip(t, runtime.Object(s)) + s2 := obj2.(*v1.Namespace) + + if s2.Status.Phase != v1.NamespaceActive { + t.Errorf("Expected phase %v, got %v", v1.NamespaceActive, s2.Status.Phase) + } +} + +func TestSetDefaultPodSpecHostNetwork(t *testing.T) { + portNum := int32(8080) + s := v1.PodSpec{} + s.HostNetwork = true + s.Containers = []v1.Container{ + { + Ports: []v1.ContainerPort{ + { + ContainerPort: portNum, + }, + }, + }, + } + s.InitContainers = []v1.Container{ + { + Ports: []v1.ContainerPort{ + { + ContainerPort: portNum, + }, + }, + }, + } + pod := &v1.Pod{ + Spec: s, + } + obj2 := roundTrip(t, runtime.Object(pod)) + pod2 := obj2.(*v1.Pod) + s2 := pod2.Spec + + hostPortNum := s2.Containers[0].Ports[0].HostPort + if hostPortNum != portNum { + t.Errorf("Expected container port to be defaulted, was made %d instead of %d", hostPortNum, portNum) + } + + hostPortNum = s2.InitContainers[0].Ports[0].HostPort + if hostPortNum != portNum { + t.Errorf("Expected container port to be defaulted, was made %d instead of %d", hostPortNum, portNum) + } +} + +func TestSetDefaultNodeExternalID(t *testing.T) { + name := "node0" + n := &v1.Node{} + n.Name = name + obj2 := roundTrip(t, runtime.Object(n)) + n2 := obj2.(*v1.Node) + if n2.Spec.ExternalID != name { + t.Errorf("Expected default External ID: %s, got: %s", name, n2.Spec.ExternalID) + } + if n2.Spec.ProviderID != "" { + t.Errorf("Expected empty default Cloud Provider ID, got: %s", n2.Spec.ProviderID) + } +} + +func TestSetDefaultNodeStatusAllocatable(t *testing.T) { + capacity := v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1000m"), + v1.ResourceMemory: resource.MustParse("10G"), + } + allocatable := v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("500m"), + v1.ResourceMemory: resource.MustParse("5G"), + } + tests := []struct { + capacity v1.ResourceList + allocatable v1.ResourceList + expectedAllocatable v1.ResourceList + }{{ // Everything set, no defaulting. + capacity: capacity, + allocatable: allocatable, + expectedAllocatable: allocatable, + }, { // Allocatable set, no defaulting. + capacity: nil, + allocatable: allocatable, + expectedAllocatable: allocatable, + }, { // Capacity set, allocatable defaults to capacity. + capacity: capacity, + allocatable: nil, + expectedAllocatable: capacity, + }, { // Nothing set, allocatable "defaults" to capacity. + capacity: nil, + allocatable: nil, + expectedAllocatable: nil, + }} + + copyResourceList := func(rl v1.ResourceList) v1.ResourceList { + if rl == nil { + return nil + } + copy := make(v1.ResourceList, len(rl)) + for k, v := range rl { + copy[k] = *v.Copy() + } + return copy + } + + resourceListsEqual := func(a v1.ResourceList, b v1.ResourceList) bool { + if len(a) != len(b) { + return false + } + for k, v := range a { + vb, found := b[k] + if !found { + return false + } + if v.Cmp(vb) != 0 { + return false + } + } + return true + } + + for i, testcase := range tests { + node := v1.Node{ + Status: v1.NodeStatus{ + Capacity: copyResourceList(testcase.capacity), + Allocatable: copyResourceList(testcase.allocatable), + }, + } + node2 := roundTrip(t, runtime.Object(&node)).(*v1.Node) + actual := node2.Status.Allocatable + expected := testcase.expectedAllocatable + if !resourceListsEqual(expected, actual) { + t.Errorf("[%d] Expected v1.NodeStatus.Allocatable: %+v; Got: %+v", i, expected, actual) + } + } +} + +func TestSetDefaultObjectFieldSelectorAPIVersion(t *testing.T) { + s := v1.PodSpec{ + Containers: []v1.Container{ + { + Env: []v1.EnvVar{ + { + ValueFrom: &v1.EnvVarSource{ + FieldRef: &v1.ObjectFieldSelector{}, + }, + }, + }, + }, + }, + } + pod := &v1.Pod{ + Spec: s, + } + obj2 := roundTrip(t, runtime.Object(pod)) + pod2 := obj2.(*v1.Pod) + s2 := pod2.Spec + + apiVersion := s2.Containers[0].Env[0].ValueFrom.FieldRef.APIVersion + if apiVersion != "v1" { + t.Errorf("Expected default APIVersion v1, got: %v", apiVersion) + } +} + +func TestSetMinimumScalePod(t *testing.T) { + // verify we default if limits are specified (and that request=0 is preserved) + s := v1.PodSpec{} + s.Containers = []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("1n"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2n"), + }, + }, + }, + } + s.InitContainers = []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("1n"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2n"), + }, + }, + }, + } + pod := &v1.Pod{ + Spec: s, + } + corev1.SetObjectDefaults_Pod(pod) + + if expect := resource.MustParse("1m"); expect.Cmp(pod.Spec.Containers[0].Resources.Requests[v1.ResourceMemory]) != 0 { + t.Errorf("did not round resources: %#v", pod.Spec.Containers[0].Resources) + } + if expect := resource.MustParse("1m"); expect.Cmp(pod.Spec.InitContainers[0].Resources.Requests[v1.ResourceMemory]) != 0 { + t.Errorf("did not round resources: %#v", pod.Spec.InitContainers[0].Resources) + } +} + +func TestSetDefaultRequestsPod(t *testing.T) { + // verify we default if limits are specified (and that request=0 is preserved) + s := v1.PodSpec{} + s.Containers = []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("0"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("1Gi"), + }, + }, + }, + } + s.InitContainers = []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("0"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("1Gi"), + }, + }, + }, + } + pod := &v1.Pod{ + Spec: s, + } + output := roundTrip(t, runtime.Object(pod)) + pod2 := output.(*v1.Pod) + defaultRequest := pod2.Spec.Containers[0].Resources.Requests + if requestValue := defaultRequest[v1.ResourceCPU]; requestValue.String() != "100m" { + t.Errorf("Expected request cpu: %s, got: %s", "100m", requestValue.String()) + } + if requestValue := defaultRequest[v1.ResourceMemory]; requestValue.String() != "0" { + t.Errorf("Expected request memory: %s, got: %s", "0", requestValue.String()) + } + defaultRequest = pod2.Spec.InitContainers[0].Resources.Requests + if requestValue := defaultRequest[v1.ResourceCPU]; requestValue.String() != "100m" { + t.Errorf("Expected request cpu: %s, got: %s", "100m", requestValue.String()) + } + if requestValue := defaultRequest[v1.ResourceMemory]; requestValue.String() != "0" { + t.Errorf("Expected request memory: %s, got: %s", "0", requestValue.String()) + } + + // verify we do nothing if no limits are specified + s = v1.PodSpec{} + s.Containers = []v1.Container{{}} + s.InitContainers = []v1.Container{{}} + pod = &v1.Pod{ + Spec: s, + } + output = roundTrip(t, runtime.Object(pod)) + pod2 = output.(*v1.Pod) + defaultRequest = pod2.Spec.Containers[0].Resources.Requests + if requestValue := defaultRequest[v1.ResourceCPU]; requestValue.String() != "0" { + t.Errorf("Expected 0 request value, got: %s", requestValue.String()) + } + defaultRequest = pod2.Spec.InitContainers[0].Resources.Requests + if requestValue := defaultRequest[v1.ResourceCPU]; requestValue.String() != "0" { + t.Errorf("Expected 0 request value, got: %s", requestValue.String()) + } +} + +func TestDefaultRequestIsNotSetForReplicationController(t *testing.T) { + s := v1.PodSpec{} + s.Containers = []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + }, + }, + }, + } + rc := &v1.ReplicationController{ + Spec: v1.ReplicationControllerSpec{ + Replicas: newInt(3), + Template: &v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "foo": "bar", + }, + }, + Spec: s, + }, + }, + } + output := roundTrip(t, runtime.Object(rc)) + rc2 := output.(*v1.ReplicationController) + defaultRequest := rc2.Spec.Template.Spec.Containers[0].Resources.Requests + requestValue := defaultRequest[v1.ResourceCPU] + if requestValue.String() != "0" { + t.Errorf("Expected 0 request value, got: %s", requestValue.String()) + } +} + +func TestSetDefaultLimitRangeItem(t *testing.T) { + limitRange := &v1.LimitRange{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-defaults", + }, + Spec: v1.LimitRangeSpec{ + Limits: []v1.LimitRangeItem{{ + Type: v1.LimitTypeContainer, + Max: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + }, + Min: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Default: v1.ResourceList{}, + DefaultRequest: v1.ResourceList{}, + }}, + }, + } + + output := roundTrip(t, runtime.Object(limitRange)) + limitRange2 := output.(*v1.LimitRange) + defaultLimit := limitRange2.Spec.Limits[0].Default + defaultRequest := limitRange2.Spec.Limits[0].DefaultRequest + + // verify that default cpu was set to the max + defaultValue := defaultLimit[v1.ResourceCPU] + if defaultValue.String() != "100m" { + t.Errorf("Expected default cpu: %s, got: %s", "100m", defaultValue.String()) + } + // verify that default request was set to the limit + requestValue := defaultRequest[v1.ResourceCPU] + if requestValue.String() != "100m" { + t.Errorf("Expected request cpu: %s, got: %s", "100m", requestValue.String()) + } + // verify that if a min is provided, it will be the default if no limit is specified + requestMinValue := defaultRequest[v1.ResourceMemory] + if requestMinValue.String() != "100Mi" { + t.Errorf("Expected request memory: %s, got: %s", "100Mi", requestMinValue.String()) + } +} + +func TestSetDefaultProbe(t *testing.T) { + originalProbe := v1.Probe{} + expectedProbe := v1.Probe{ + InitialDelaySeconds: 0, + TimeoutSeconds: 1, + PeriodSeconds: 10, + SuccessThreshold: 1, + FailureThreshold: 3, + } + + pod := &v1.Pod{ + Spec: v1.PodSpec{ + Containers: []v1.Container{{LivenessProbe: &originalProbe}}, + }, + } + + output := roundTrip(t, runtime.Object(pod)).(*v1.Pod) + actualProbe := *output.Spec.Containers[0].LivenessProbe + if actualProbe != expectedProbe { + t.Errorf("Expected probe: %+v\ngot: %+v\n", expectedProbe, actualProbe) + } +} + +func TestSetDefaultSchedulerName(t *testing.T) { + pod := &v1.Pod{} + + output := roundTrip(t, runtime.Object(pod)).(*v1.Pod) + if output.Spec.SchedulerName != v1.DefaultSchedulerName { + t.Errorf("Expected scheduler name: %+v\ngot: %+v\n", v1.DefaultSchedulerName, output.Spec.SchedulerName) + } +} + +func TestSetDefaultHostPathVolumeSource(t *testing.T) { + s := v1.PodSpec{} + s.Volumes = []v1.Volume{ + { + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{Path: "foo"}, + }, + }, + } + pod := &v1.Pod{ + Spec: s, + } + output := roundTrip(t, runtime.Object(pod)) + pod2 := output.(*v1.Pod) + defaultType := pod2.Spec.Volumes[0].VolumeSource.HostPath.Type + expectedType := v1.HostPathUnset + + if defaultType == nil || *defaultType != expectedType { + t.Errorf("Expected v1.HostPathVolumeSource default type %v, got %v", expectedType, defaultType) + } +} diff --git a/pkg/apis/core/v1/doc.go b/pkg/apis/core/v1/doc.go new file mode 100644 index 00000000000..454e3018316 --- /dev/null +++ b/pkg/apis/core/v1/doc.go @@ -0,0 +1,23 @@ +/* +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. +*/ + +// +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/core +// +k8s:conversion-gen-external-types=k8s.io/api/core/v1 +// +k8s:defaulter-gen=TypeMeta +// +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/core/v1 + +// Package v1 is the v1 version of the API. +package v1 // import "k8s.io/kubernetes/pkg/apis/core/v1" diff --git a/pkg/apis/core/v1/helper/BUILD b/pkg/apis/core/v1/helper/BUILD new file mode 100644 index 00000000000..fec9c1baaf0 --- /dev/null +++ b/pkg/apis/core/v1/helper/BUILD @@ -0,0 +1,51 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_test( + name = "go_default_test", + srcs = ["helpers_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/apis/core/v1/helper", + deps = [ + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + ], +) + +go_library( + name = "go_default_library", + srcs = ["helpers.go"], + importpath = "k8s.io/kubernetes/pkg/apis/core/v1/helper", + deps = [ + "//pkg/apis/core/helper:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/selection:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//pkg/apis/core/v1/helper/qos:all-srcs", + ], + tags = ["automanaged"], +) diff --git a/pkg/apis/core/v1/helper/helpers.go b/pkg/apis/core/v1/helper/helpers.go new file mode 100644 index 00000000000..b90a6de1f92 --- /dev/null +++ b/pkg/apis/core/v1/helper/helpers.go @@ -0,0 +1,438 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES 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 ( + "encoding/json" + "fmt" + "strings" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/kubernetes/pkg/apis/core/helper" +) + +// IsExtendedResourceName returns true if the resource name is not in the +// default namespace. +func IsExtendedResourceName(name v1.ResourceName) bool { + return !IsDefaultNamespaceResource(name) +} + +// IsDefaultNamespaceResource returns true if the resource name is in the +// *kubernetes.io/ namespace. Partially-qualified (unprefixed) names are +// implicitly in the kubernetes.io/ namespace. +func IsDefaultNamespaceResource(name v1.ResourceName) bool { + return !strings.Contains(string(name), "/") || + strings.Contains(string(name), v1.ResourceDefaultNamespacePrefix) + +} + +// IsHugePageResourceName returns true if the resource name has the huge page +// resource prefix. +func IsHugePageResourceName(name v1.ResourceName) bool { + return strings.HasPrefix(string(name), v1.ResourceHugePagesPrefix) +} + +// HugePageResourceName returns a ResourceName with the canonical hugepage +// prefix prepended for the specified page size. The page size is converted +// to its canonical representation. +func HugePageResourceName(pageSize resource.Quantity) v1.ResourceName { + return v1.ResourceName(fmt.Sprintf("%s%s", v1.ResourceHugePagesPrefix, pageSize.String())) +} + +// HugePageSizeFromResourceName returns the page size for the specified huge page +// resource name. If the specified input is not a valid huge page resource name +// an error is returned. +func HugePageSizeFromResourceName(name v1.ResourceName) (resource.Quantity, error) { + if !IsHugePageResourceName(name) { + return resource.Quantity{}, fmt.Errorf("resource name: %s is not valid hugepage name", name) + } + pageSize := strings.TrimPrefix(string(name), v1.ResourceHugePagesPrefix) + return resource.ParseQuantity(pageSize) +} + +var overcommitBlacklist = sets.NewString(string(v1.ResourceNvidiaGPU)) + +// IsOvercommitAllowed returns true if the resource is in the default +// namespace and not blacklisted and is not hugepages. +func IsOvercommitAllowed(name v1.ResourceName) bool { + return IsDefaultNamespaceResource(name) && + !IsHugePageResourceName(name) && + !overcommitBlacklist.Has(string(name)) +} + +// Extended and Hugepages resources +func IsScalarResourceName(name v1.ResourceName) bool { + return IsExtendedResourceName(name) || IsHugePageResourceName(name) +} + +// this function aims to check if the service's ClusterIP is set or not +// the objective is not to perform validation here +func IsServiceIPSet(service *v1.Service) bool { + return service.Spec.ClusterIP != v1.ClusterIPNone && service.Spec.ClusterIP != "" +} + +// AddToNodeAddresses appends the NodeAddresses to the passed-by-pointer slice, +// only if they do not already exist +func AddToNodeAddresses(addresses *[]v1.NodeAddress, addAddresses ...v1.NodeAddress) { + for _, add := range addAddresses { + exists := false + for _, existing := range *addresses { + if existing.Address == add.Address && existing.Type == add.Type { + exists = true + break + } + } + if !exists { + *addresses = append(*addresses, add) + } + } +} + +// TODO: make method on LoadBalancerStatus? +func LoadBalancerStatusEqual(l, r *v1.LoadBalancerStatus) bool { + return ingressSliceEqual(l.Ingress, r.Ingress) +} + +func ingressSliceEqual(lhs, rhs []v1.LoadBalancerIngress) bool { + if len(lhs) != len(rhs) { + return false + } + for i := range lhs { + if !ingressEqual(&lhs[i], &rhs[i]) { + return false + } + } + return true +} + +func ingressEqual(lhs, rhs *v1.LoadBalancerIngress) bool { + if lhs.IP != rhs.IP { + return false + } + if lhs.Hostname != rhs.Hostname { + return false + } + return true +} + +// TODO: make method on LoadBalancerStatus? +func LoadBalancerStatusDeepCopy(lb *v1.LoadBalancerStatus) *v1.LoadBalancerStatus { + c := &v1.LoadBalancerStatus{} + c.Ingress = make([]v1.LoadBalancerIngress, len(lb.Ingress)) + for i := range lb.Ingress { + c.Ingress[i] = lb.Ingress[i] + } + return c +} + +// GetAccessModesAsString returns a string representation of an array of access modes. +// modes, when present, are always in the same order: RWO,ROX,RWX. +func GetAccessModesAsString(modes []v1.PersistentVolumeAccessMode) string { + modes = removeDuplicateAccessModes(modes) + modesStr := []string{} + if containsAccessMode(modes, v1.ReadWriteOnce) { + modesStr = append(modesStr, "RWO") + } + if containsAccessMode(modes, v1.ReadOnlyMany) { + modesStr = append(modesStr, "ROX") + } + if containsAccessMode(modes, v1.ReadWriteMany) { + modesStr = append(modesStr, "RWX") + } + return strings.Join(modesStr, ",") +} + +// GetAccessModesAsString returns an array of AccessModes from a string created by GetAccessModesAsString +func GetAccessModesFromString(modes string) []v1.PersistentVolumeAccessMode { + strmodes := strings.Split(modes, ",") + accessModes := []v1.PersistentVolumeAccessMode{} + for _, s := range strmodes { + s = strings.Trim(s, " ") + switch { + case s == "RWO": + accessModes = append(accessModes, v1.ReadWriteOnce) + case s == "ROX": + accessModes = append(accessModes, v1.ReadOnlyMany) + case s == "RWX": + accessModes = append(accessModes, v1.ReadWriteMany) + } + } + return accessModes +} + +// removeDuplicateAccessModes returns an array of access modes without any duplicates +func removeDuplicateAccessModes(modes []v1.PersistentVolumeAccessMode) []v1.PersistentVolumeAccessMode { + accessModes := []v1.PersistentVolumeAccessMode{} + for _, m := range modes { + if !containsAccessMode(accessModes, m) { + accessModes = append(accessModes, m) + } + } + return accessModes +} + +func containsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool { + for _, m := range modes { + if m == mode { + return true + } + } + return false +} + +// NodeSelectorRequirementsAsSelector converts the []NodeSelectorRequirement api type into a struct that implements +// labels.Selector. +func NodeSelectorRequirementsAsSelector(nsm []v1.NodeSelectorRequirement) (labels.Selector, error) { + if len(nsm) == 0 { + return labels.Nothing(), nil + } + selector := labels.NewSelector() + for _, expr := range nsm { + var op selection.Operator + switch expr.Operator { + case v1.NodeSelectorOpIn: + op = selection.In + case v1.NodeSelectorOpNotIn: + op = selection.NotIn + case v1.NodeSelectorOpExists: + op = selection.Exists + case v1.NodeSelectorOpDoesNotExist: + op = selection.DoesNotExist + case v1.NodeSelectorOpGt: + op = selection.GreaterThan + case v1.NodeSelectorOpLt: + op = selection.LessThan + default: + return nil, fmt.Errorf("%q is not a valid node selector operator", expr.Operator) + } + r, err := labels.NewRequirement(expr.Key, op, expr.Values) + if err != nil { + return nil, err + } + selector = selector.Add(*r) + } + return selector, nil +} + +// AddOrUpdateTolerationInPodSpec tries to add a toleration to the toleration list in PodSpec. +// Returns true if something was updated, false otherwise. +func AddOrUpdateTolerationInPodSpec(spec *v1.PodSpec, toleration *v1.Toleration) bool { + podTolerations := spec.Tolerations + + var newTolerations []v1.Toleration + updated := false + for i := range podTolerations { + if toleration.MatchToleration(&podTolerations[i]) { + if helper.Semantic.DeepEqual(toleration, podTolerations[i]) { + return false + } + newTolerations = append(newTolerations, *toleration) + updated = true + continue + } + + newTolerations = append(newTolerations, podTolerations[i]) + } + + if !updated { + newTolerations = append(newTolerations, *toleration) + } + + spec.Tolerations = newTolerations + return true +} + +// AddOrUpdateTolerationInPod tries to add a toleration to the pod's toleration list. +// Returns true if something was updated, false otherwise. +func AddOrUpdateTolerationInPod(pod *v1.Pod, toleration *v1.Toleration) bool { + return AddOrUpdateTolerationInPodSpec(&pod.Spec, toleration) +} + +// TolerationsTolerateTaint checks if taint is tolerated by any of the tolerations. +func TolerationsTolerateTaint(tolerations []v1.Toleration, taint *v1.Taint) bool { + for i := range tolerations { + if tolerations[i].ToleratesTaint(taint) { + return true + } + } + return false +} + +type taintsFilterFunc func(*v1.Taint) bool + +// TolerationsTolerateTaintsWithFilter checks if given tolerations tolerates +// all the taints that apply to the filter in given taint list. +func TolerationsTolerateTaintsWithFilter(tolerations []v1.Toleration, taints []v1.Taint, applyFilter taintsFilterFunc) bool { + if len(taints) == 0 { + return true + } + + for i := range taints { + if applyFilter != nil && !applyFilter(&taints[i]) { + continue + } + + if !TolerationsTolerateTaint(tolerations, &taints[i]) { + return false + } + } + + return true +} + +// Returns true and list of Tolerations matching all Taints if all are tolerated, or false otherwise. +func GetMatchingTolerations(taints []v1.Taint, tolerations []v1.Toleration) (bool, []v1.Toleration) { + if len(taints) == 0 { + return true, []v1.Toleration{} + } + if len(tolerations) == 0 && len(taints) > 0 { + return false, []v1.Toleration{} + } + result := []v1.Toleration{} + for i := range taints { + tolerated := false + for j := range tolerations { + if tolerations[j].ToleratesTaint(&taints[i]) { + result = append(result, tolerations[j]) + tolerated = true + break + } + } + if !tolerated { + return false, []v1.Toleration{} + } + } + return true, result +} + +func GetAvoidPodsFromNodeAnnotations(annotations map[string]string) (v1.AvoidPods, error) { + var avoidPods v1.AvoidPods + if len(annotations) > 0 && annotations[v1.PreferAvoidPodsAnnotationKey] != "" { + err := json.Unmarshal([]byte(annotations[v1.PreferAvoidPodsAnnotationKey]), &avoidPods) + if err != nil { + return avoidPods, err + } + } + return avoidPods, nil +} + +// SysctlsFromPodAnnotations parses the sysctl annotations into a slice of safe Sysctls +// and a slice of unsafe Sysctls. This is only a convenience wrapper around +// SysctlsFromPodAnnotation. +func SysctlsFromPodAnnotations(a map[string]string) ([]v1.Sysctl, []v1.Sysctl, error) { + safe, err := SysctlsFromPodAnnotation(a[v1.SysctlsPodAnnotationKey]) + if err != nil { + return nil, nil, err + } + unsafe, err := SysctlsFromPodAnnotation(a[v1.UnsafeSysctlsPodAnnotationKey]) + if err != nil { + return nil, nil, err + } + + return safe, unsafe, nil +} + +// SysctlsFromPodAnnotation parses an annotation value into a slice of Sysctls. +func SysctlsFromPodAnnotation(annotation string) ([]v1.Sysctl, error) { + if len(annotation) == 0 { + return nil, nil + } + + kvs := strings.Split(annotation, ",") + sysctls := make([]v1.Sysctl, len(kvs)) + for i, kv := range kvs { + cs := strings.Split(kv, "=") + if len(cs) != 2 || len(cs[0]) == 0 { + return nil, fmt.Errorf("sysctl %q not of the format sysctl_name=value", kv) + } + sysctls[i].Name = cs[0] + sysctls[i].Value = cs[1] + } + return sysctls, nil +} + +// PodAnnotationsFromSysctls creates an annotation value for a slice of Sysctls. +func PodAnnotationsFromSysctls(sysctls []v1.Sysctl) string { + if len(sysctls) == 0 { + return "" + } + + kvs := make([]string, len(sysctls)) + for i := range sysctls { + kvs[i] = fmt.Sprintf("%s=%s", sysctls[i].Name, sysctls[i].Value) + } + return strings.Join(kvs, ",") +} + +// GetPersistentVolumeClass returns StorageClassName. +func GetPersistentVolumeClass(volume *v1.PersistentVolume) string { + // Use beta annotation first + if class, found := volume.Annotations[v1.BetaStorageClassAnnotation]; found { + return class + } + + return volume.Spec.StorageClassName +} + +// GetPersistentVolumeClaimClass returns StorageClassName. If no storage class was +// requested, it returns "". +func GetPersistentVolumeClaimClass(claim *v1.PersistentVolumeClaim) string { + // Use beta annotation first + if class, found := claim.Annotations[v1.BetaStorageClassAnnotation]; found { + return class + } + + if claim.Spec.StorageClassName != nil { + return *claim.Spec.StorageClassName + } + + return "" +} + +// GetStorageNodeAffinityFromAnnotation gets the json serialized data from PersistentVolume.Annotations +// and converts it to the NodeAffinity type in api. +// TODO: update when storage node affinity graduates to beta +func GetStorageNodeAffinityFromAnnotation(annotations map[string]string) (*v1.NodeAffinity, error) { + if len(annotations) > 0 && annotations[v1.AlphaStorageNodeAffinityAnnotation] != "" { + var affinity v1.NodeAffinity + err := json.Unmarshal([]byte(annotations[v1.AlphaStorageNodeAffinityAnnotation]), &affinity) + if err != nil { + return nil, err + } + return &affinity, nil + } + return nil, nil +} + +// Converts NodeAffinity type to Alpha annotation for use in PersistentVolumes +// TODO: update when storage node affinity graduates to beta +func StorageNodeAffinityToAlphaAnnotation(annotations map[string]string, affinity *v1.NodeAffinity) error { + if affinity == nil { + return nil + } + + json, err := json.Marshal(*affinity) + if err != nil { + return err + } + annotations[v1.AlphaStorageNodeAffinityAnnotation] = string(json) + return nil +} diff --git a/pkg/apis/core/v1/helper/helpers_test.go b/pkg/apis/core/v1/helper/helpers_test.go new file mode 100644 index 00000000000..9baeb2ba351 --- /dev/null +++ b/pkg/apis/core/v1/helper/helpers_test.go @@ -0,0 +1,658 @@ +/* +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 helper + +import ( + "fmt" + "reflect" + "testing" + + "k8s.io/api/core/v1" + 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/labels" +) + +func TestIsDefaultNamespaceResource(t *testing.T) { + testCases := []struct { + resourceName v1.ResourceName + expectVal bool + }{ + { + resourceName: "pod.alpha.kubernetes.io/opaque-int-resource-foo", + expectVal: true, + }, + { + resourceName: "kubernetes.io/resource-foo", + expectVal: true, + }, + { + resourceName: "foo", + expectVal: true, + }, + { + resourceName: "a/b", + expectVal: false, + }, + { + resourceName: "", + expectVal: true, + }, + } + + 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 := IsDefaultNamespaceResource(tc.resourceName) + if v != tc.expectVal { + t.Errorf("Got %v but expected %v", v, tc.expectVal) + } + }) + } +} + +func TestHugePageSizeFromResourceName(t *testing.T) { + expected100m, _ := resource.ParseQuantity("100m") + testCases := []struct { + resourceName v1.ResourceName + expectVal resource.Quantity + expectErr bool + }{ + { + resourceName: "pod.alpha.kubernetes.io/opaque-int-resource-foo", + expectVal: resource.Quantity{}, + expectErr: true, + }, + { + resourceName: "hugepages-", + expectVal: resource.Quantity{}, + expectErr: true, + }, + { + resourceName: "hugepages-100m", + expectVal: expected100m, + expectErr: false, + }, + { + resourceName: "", + expectVal: resource.Quantity{}, + expectErr: true, + }, + } + + for i, 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, err := HugePageSizeFromResourceName(tc.resourceName) + if err == nil && tc.expectErr { + t.Errorf("[%v]expected error but got none.", i) + } + if err != nil && !tc.expectErr { + t.Errorf("[%v]did not expect error but got: %v", i, err) + } + if v != tc.expectVal { + t.Errorf("Got %v but expected %v", v, tc.expectVal) + } + }) + } +} + +func TestIsOvercommitAllowed(t *testing.T) { + testCases := []struct { + resourceName v1.ResourceName + expectVal bool + }{ + { + resourceName: "pod.alpha.kubernetes.io/opaque-int-resource-foo", + expectVal: true, + }, + { + resourceName: "kubernetes.io/resource-foo", + expectVal: true, + }, + { + resourceName: "alpha.kubernetes.io/nvidia-gpu", + expectVal: false, + }, + { + resourceName: "hugepages-100m", + expectVal: false, + }, + { + resourceName: "", + expectVal: true, + }, + } + + 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) + if v != tc.expectVal { + t.Errorf("Got %v but expected %v", v, tc.expectVal) + } + }) + } +} + +func TestAddToNodeAddresses(t *testing.T) { + testCases := []struct { + existing []v1.NodeAddress + toAdd []v1.NodeAddress + expected []v1.NodeAddress + }{ + { + existing: []v1.NodeAddress{}, + toAdd: []v1.NodeAddress{}, + expected: []v1.NodeAddress{}, + }, + { + existing: []v1.NodeAddress{}, + toAdd: []v1.NodeAddress{ + {Type: v1.NodeExternalIP, Address: "1.1.1.1"}, + {Type: v1.NodeHostName, Address: "localhost"}, + }, + expected: []v1.NodeAddress{ + {Type: v1.NodeExternalIP, Address: "1.1.1.1"}, + {Type: v1.NodeHostName, Address: "localhost"}, + }, + }, + { + existing: []v1.NodeAddress{}, + toAdd: []v1.NodeAddress{ + {Type: v1.NodeExternalIP, Address: "1.1.1.1"}, + {Type: v1.NodeExternalIP, Address: "1.1.1.1"}, + }, + expected: []v1.NodeAddress{ + {Type: v1.NodeExternalIP, Address: "1.1.1.1"}, + }, + }, + { + existing: []v1.NodeAddress{ + {Type: v1.NodeExternalIP, Address: "1.1.1.1"}, + {Type: v1.NodeInternalIP, Address: "10.1.1.1"}, + }, + toAdd: []v1.NodeAddress{ + {Type: v1.NodeExternalIP, Address: "1.1.1.1"}, + {Type: v1.NodeHostName, Address: "localhost"}, + }, + expected: []v1.NodeAddress{ + {Type: v1.NodeExternalIP, Address: "1.1.1.1"}, + {Type: v1.NodeInternalIP, Address: "10.1.1.1"}, + {Type: v1.NodeHostName, Address: "localhost"}, + }, + }, + } + + for i, tc := range testCases { + AddToNodeAddresses(&tc.existing, tc.toAdd...) + if !apiequality.Semantic.DeepEqual(tc.expected, tc.existing) { + t.Errorf("case[%d], expected: %v, got: %v", i, tc.expected, tc.existing) + } + } +} + +func TestGetAccessModesFromString(t *testing.T) { + modes := GetAccessModesFromString("ROX") + if !containsAccessMode(modes, v1.ReadOnlyMany) { + t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes) + } + + modes = GetAccessModesFromString("ROX,RWX") + if !containsAccessMode(modes, v1.ReadOnlyMany) { + t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes) + } + if !containsAccessMode(modes, v1.ReadWriteMany) { + t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteMany, modes) + } + + modes = GetAccessModesFromString("RWO,ROX,RWX") + if !containsAccessMode(modes, v1.ReadOnlyMany) { + t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes) + } + if !containsAccessMode(modes, v1.ReadWriteMany) { + t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteMany, modes) + } +} + +func TestRemoveDuplicateAccessModes(t *testing.T) { + modes := []v1.PersistentVolumeAccessMode{ + v1.ReadWriteOnce, v1.ReadOnlyMany, v1.ReadOnlyMany, v1.ReadOnlyMany, + } + modes = removeDuplicateAccessModes(modes) + if len(modes) != 2 { + t.Errorf("Expected 2 distinct modes in set but found %v", len(modes)) + } +} + +func TestNodeSelectorRequirementsAsSelector(t *testing.T) { + matchExpressions := []v1.NodeSelectorRequirement{{ + Key: "foo", + Operator: v1.NodeSelectorOpIn, + Values: []string{"bar", "baz"}, + }} + mustParse := func(s string) labels.Selector { + out, e := labels.Parse(s) + if e != nil { + panic(e) + } + return out + } + tc := []struct { + in []v1.NodeSelectorRequirement + out labels.Selector + expectErr bool + }{ + {in: nil, out: labels.Nothing()}, + {in: []v1.NodeSelectorRequirement{}, out: labels.Nothing()}, + { + in: matchExpressions, + out: mustParse("foo in (baz,bar)"), + }, + { + in: []v1.NodeSelectorRequirement{{ + Key: "foo", + Operator: v1.NodeSelectorOpExists, + Values: []string{"bar", "baz"}, + }}, + expectErr: true, + }, + { + in: []v1.NodeSelectorRequirement{{ + Key: "foo", + Operator: v1.NodeSelectorOpGt, + Values: []string{"1"}, + }}, + out: mustParse("foo>1"), + }, + { + in: []v1.NodeSelectorRequirement{{ + Key: "bar", + Operator: v1.NodeSelectorOpLt, + Values: []string{"7"}, + }}, + out: mustParse("bar<7"), + }, + } + + for i, tc := range tc { + out, err := NodeSelectorRequirementsAsSelector(tc.in) + if err == nil && tc.expectErr { + t.Errorf("[%v]expected error but got none.", i) + } + if err != nil && !tc.expectErr { + t.Errorf("[%v]did not expect error but got: %v", i, err) + } + if !reflect.DeepEqual(out, tc.out) { + t.Errorf("[%v]expected:\n\t%+v\nbut got:\n\t%+v", i, tc.out, out) + } + } +} + +func TestTolerationsTolerateTaintsWithFilter(t *testing.T) { + testCases := []struct { + description string + tolerations []v1.Toleration + taints []v1.Taint + applyFilter taintsFilterFunc + expectTolerated bool + }{ + { + description: "empty tolerations tolerate empty taints", + tolerations: []v1.Toleration{}, + taints: []v1.Taint{}, + applyFilter: func(t *v1.Taint) bool { return true }, + expectTolerated: true, + }, + { + description: "non-empty tolerations tolerate empty taints", + tolerations: []v1.Toleration{ + { + Key: "foo", + Operator: "Exists", + Effect: v1.TaintEffectNoSchedule, + }, + }, + taints: []v1.Taint{}, + applyFilter: func(t *v1.Taint) bool { return true }, + expectTolerated: true, + }, + { + description: "tolerations match all taints, expect tolerated", + tolerations: []v1.Toleration{ + { + Key: "foo", + Operator: "Exists", + Effect: v1.TaintEffectNoSchedule, + }, + }, + taints: []v1.Taint{ + { + Key: "foo", + Effect: v1.TaintEffectNoSchedule, + }, + }, + applyFilter: func(t *v1.Taint) bool { return true }, + expectTolerated: true, + }, + { + description: "tolerations don't match taints, but no taints apply to the filter, expect tolerated", + tolerations: []v1.Toleration{ + { + Key: "foo", + Operator: "Exists", + Effect: v1.TaintEffectNoSchedule, + }, + }, + taints: []v1.Taint{ + { + Key: "bar", + Effect: v1.TaintEffectNoSchedule, + }, + }, + applyFilter: func(t *v1.Taint) bool { return false }, + expectTolerated: true, + }, + { + description: "no filterFunc indicated, means all taints apply to the filter, tolerations don't match taints, expect untolerated", + tolerations: []v1.Toleration{ + { + Key: "foo", + Operator: "Exists", + Effect: v1.TaintEffectNoSchedule, + }, + }, + taints: []v1.Taint{ + { + Key: "bar", + Effect: v1.TaintEffectNoSchedule, + }, + }, + applyFilter: nil, + expectTolerated: false, + }, + { + description: "tolerations match taints, expect tolerated", + tolerations: []v1.Toleration{ + { + Key: "foo", + Operator: "Exists", + Effect: v1.TaintEffectNoExecute, + }, + }, + taints: []v1.Taint{ + { + Key: "foo", + Effect: v1.TaintEffectNoExecute, + }, + { + Key: "bar", + Effect: v1.TaintEffectNoSchedule, + }, + }, + applyFilter: func(t *v1.Taint) bool { return t.Effect == v1.TaintEffectNoExecute }, + expectTolerated: true, + }, + } + + for _, tc := range testCases { + if tc.expectTolerated != TolerationsTolerateTaintsWithFilter(tc.tolerations, tc.taints, tc.applyFilter) { + filteredTaints := []v1.Taint{} + for _, taint := range tc.taints { + if tc.applyFilter != nil && !tc.applyFilter(&taint) { + continue + } + filteredTaints = append(filteredTaints, taint) + } + t.Errorf("[%s] expect tolerations %+v tolerate filtered taints %+v in taints %+v", tc.description, tc.tolerations, filteredTaints, tc.taints) + } + } +} + +func TestGetAvoidPodsFromNode(t *testing.T) { + controllerFlag := true + testCases := []struct { + node *v1.Node + expectValue v1.AvoidPods + expectErr bool + }{ + { + node: &v1.Node{}, + expectValue: v1.AvoidPods{}, + expectErr: false, + }, + { + node: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + v1.PreferAvoidPodsAnnotationKey: ` + { + "preferAvoidPods": [ + { + "podSignature": { + "podController": { + "apiVersion": "v1", + "kind": "ReplicationController", + "name": "foo", + "uid": "abcdef123456", + "controller": true + } + }, + "reason": "some reason", + "message": "some message" + } + ] + }`, + }, + }, + }, + expectValue: v1.AvoidPods{ + PreferAvoidPods: []v1.PreferAvoidPodsEntry{ + { + PodSignature: v1.PodSignature{ + PodController: &metav1.OwnerReference{ + APIVersion: "v1", + Kind: "ReplicationController", + Name: "foo", + UID: "abcdef123456", + Controller: &controllerFlag, + }, + }, + Reason: "some reason", + Message: "some message", + }, + }, + }, + expectErr: false, + }, + { + node: &v1.Node{ + // Missing end symbol of "podController" and "podSignature" + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + v1.PreferAvoidPodsAnnotationKey: ` + { + "preferAvoidPods": [ + { + "podSignature": { + "podController": { + "kind": "ReplicationController", + "apiVersion": "v1" + "reason": "some reason", + "message": "some message" + } + ] + }`, + }, + }, + }, + expectValue: v1.AvoidPods{}, + expectErr: true, + }, + } + + for i, tc := range testCases { + v, err := GetAvoidPodsFromNodeAnnotations(tc.node.Annotations) + if err == nil && tc.expectErr { + t.Errorf("[%v]expected error but got none.", i) + } + if err != nil && !tc.expectErr { + t.Errorf("[%v]did not expect error but got: %v", i, err) + } + if !reflect.DeepEqual(tc.expectValue, v) { + t.Errorf("[%v]expect value %v but got %v with %v", i, tc.expectValue, v, v.PreferAvoidPods[0].PodSignature.PodController.Controller) + } + } +} + +func TestSysctlsFromPodAnnotation(t *testing.T) { + type Test struct { + annotation string + expectValue []v1.Sysctl + expectErr bool + } + for i, test := range []Test{ + { + annotation: "", + expectValue: nil, + }, + { + annotation: "foo.bar", + expectErr: true, + }, + { + annotation: "=123", + expectErr: true, + }, + { + annotation: "foo.bar=", + expectValue: []v1.Sysctl{{Name: "foo.bar", Value: ""}}, + }, + { + annotation: "foo.bar=42", + expectValue: []v1.Sysctl{{Name: "foo.bar", Value: "42"}}, + }, + { + annotation: "foo.bar=42,", + expectErr: true, + }, + { + annotation: "foo.bar=42,abc.def=1", + expectValue: []v1.Sysctl{{Name: "foo.bar", Value: "42"}, {Name: "abc.def", Value: "1"}}, + }, + } { + sysctls, err := SysctlsFromPodAnnotation(test.annotation) + if test.expectErr && err == nil { + t.Errorf("[%v]expected error but got none", i) + } else if !test.expectErr && err != nil { + t.Errorf("[%v]did not expect error but got: %v", i, err) + } else if !reflect.DeepEqual(sysctls, test.expectValue) { + t.Errorf("[%v]expect value %v but got %v", i, test.expectValue, sysctls) + } + } +} + +// TODO: remove when alpha support for topology constraints is removed +func TestGetNodeAffinityFromAnnotations(t *testing.T) { + testCases := []struct { + annotations map[string]string + expectErr bool + }{ + { + annotations: nil, + expectErr: false, + }, + { + annotations: map[string]string{}, + expectErr: false, + }, + { + annotations: map[string]string{ + v1.AlphaStorageNodeAffinityAnnotation: `{ + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { "matchExpressions": [ + { "key": "test-key1", + "operator": "In", + "values": ["test-value1", "test-value2"] + }, + { "key": "test-key2", + "operator": "In", + "values": ["test-value1", "test-value2"] + } + ]} + ]} + }`, + }, + expectErr: false, + }, + { + annotations: map[string]string{ + v1.AlphaStorageNodeAffinityAnnotation: `[{ + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { "matchExpressions": [ + { "key": "test-key1", + "operator": "In", + "values": ["test-value1", "test-value2"] + }, + { "key": "test-key2", + "operator": "In", + "values": ["test-value1", "test-value2"] + } + ]} + ]} + }]`, + }, + expectErr: true, + }, + { + annotations: map[string]string{ + v1.AlphaStorageNodeAffinityAnnotation: `{ + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": + "matchExpressions": [ + { "key": "test-key1", + "operator": "In", + "values": ["test-value1", "test-value2"] + }, + { "key": "test-key2", + "operator": "In", + "values": ["test-value1", "test-value2"] + } + ]} + } + }`, + }, + expectErr: true, + }, + } + + for i, tc := range testCases { + _, err := GetStorageNodeAffinityFromAnnotation(tc.annotations) + if err == nil && tc.expectErr { + t.Errorf("[%v]expected error but got none.", i) + } + if err != nil && !tc.expectErr { + t.Errorf("[%v]did not expect error but got: %v", i, err) + } + } +} diff --git a/pkg/apis/core/v1/helper/qos/BUILD b/pkg/apis/core/v1/helper/qos/BUILD new file mode 100644 index 00000000000..57ab07f7252 --- /dev/null +++ b/pkg/apis/core/v1/helper/qos/BUILD @@ -0,0 +1,47 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_test( + name = "go_default_test", + srcs = ["qos_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos", + deps = [ + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper/qos:go_default_library", + "//pkg/apis/core/v1:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + ], +) + +go_library( + name = "go_default_library", + srcs = ["qos.go"], + importpath = "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos", + deps = [ + "//pkg/apis/core:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/pkg/apis/core/v1/helper/qos/qos.go b/pkg/apis/core/v1/helper/qos/qos.go new file mode 100644 index 00000000000..426f054efa6 --- /dev/null +++ b/pkg/apis/core/v1/helper/qos/qos.go @@ -0,0 +1,99 @@ +/* +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 qos + +import ( + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/kubernetes/pkg/apis/core" +) + +var supportedQoSComputeResources = sets.NewString(string(core.ResourceCPU), string(core.ResourceMemory)) + +// QOSList is a set of (resource name, QoS class) pairs. +type QOSList map[v1.ResourceName]v1.PodQOSClass + +func isSupportedQoSComputeResource(name v1.ResourceName) bool { + return supportedQoSComputeResources.Has(string(name)) +} + +// GetPodQOS returns the QoS class of a pod. +// A pod is besteffort if none of its containers have specified any requests or limits. +// A pod is guaranteed only when requests and limits are specified for all the containers and they are equal. +// A pod is burstable if limits and requests do not match across all containers. +func GetPodQOS(pod *v1.Pod) v1.PodQOSClass { + requests := v1.ResourceList{} + limits := v1.ResourceList{} + zeroQuantity := resource.MustParse("0") + isGuaranteed := true + for _, container := range pod.Spec.Containers { + // process requests + for name, quantity := range container.Resources.Requests { + if !isSupportedQoSComputeResource(name) { + continue + } + if quantity.Cmp(zeroQuantity) == 1 { + delta := quantity.Copy() + if _, exists := requests[name]; !exists { + requests[name] = *delta + } else { + delta.Add(requests[name]) + requests[name] = *delta + } + } + } + // process limits + qosLimitsFound := sets.NewString() + for name, quantity := range container.Resources.Limits { + if !isSupportedQoSComputeResource(name) { + continue + } + if quantity.Cmp(zeroQuantity) == 1 { + qosLimitsFound.Insert(string(name)) + delta := quantity.Copy() + if _, exists := limits[name]; !exists { + limits[name] = *delta + } else { + delta.Add(limits[name]) + limits[name] = *delta + } + } + } + + if !qosLimitsFound.HasAll(string(v1.ResourceMemory), string(v1.ResourceCPU)) { + isGuaranteed = false + } + } + if len(requests) == 0 && len(limits) == 0 { + return v1.PodQOSBestEffort + } + // Check is requests match limits for all resources. + if isGuaranteed { + for name, req := range requests { + if lim, exists := limits[name]; !exists || lim.Cmp(req) != 0 { + isGuaranteed = false + break + } + } + } + if isGuaranteed && + len(requests) == len(limits) { + return v1.PodQOSGuaranteed + } + return v1.PodQOSBurstable +} diff --git a/pkg/api/v1/helper/qos/qos_test.go b/pkg/apis/core/v1/helper/qos/qos_test.go similarity index 90% rename from pkg/api/v1/helper/qos/qos_test.go rename to pkg/apis/core/v1/helper/qos/qos_test.go index a48387c3ded..0685d4e6559 100644 --- a/pkg/api/v1/helper/qos/qos_test.go +++ b/pkg/apis/core/v1/helper/qos/qos_test.go @@ -22,9 +22,9 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper/qos" - k8sv1 "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper/qos" + corev1 "k8s.io/kubernetes/pkg/apis/core/v1" ) func TestGetPodQOS(t *testing.T) { @@ -131,10 +131,10 @@ func TestGetPodQOS(t *testing.T) { expected: v1.PodQOSBurstable, }, { - pod: newPod("burstable-hugepages", []v1.Container{ - newContainer("burstable", addResource("hugepages-2Mi", "1Gi", getResourceList("0", "0")), addResource("hugepages-2Mi", "1Gi", getResourceList("0", "0"))), + pod: newPod("best-effort-hugepages", []v1.Container{ + newContainer("best-effort", addResource("hugepages-2Mi", "1Gi", getResourceList("0", "0")), addResource("hugepages-2Mi", "1Gi", getResourceList("0", "0"))), }), - expected: v1.PodQOSBurstable, + expected: v1.PodQOSBestEffort, }, } for id, testCase := range testCases { @@ -142,11 +142,11 @@ func TestGetPodQOS(t *testing.T) { t.Errorf("[%d]: invalid qos pod %s, expected: %s, actual: %s", id, testCase.pod.Name, testCase.expected, actual) } - // Convert v1.Pod to api.Pod, and then check against `api.helper.GetPodQOS`. - pod := api.Pod{} - k8sv1.Convert_v1_Pod_To_api_Pod(testCase.pod, &pod, nil) + // Convert v1.Pod to core.Pod, and then check against `core.helper.GetPodQOS`. + pod := core.Pod{} + corev1.Convert_v1_Pod_To_core_Pod(testCase.pod, &pod, nil) - if actual := qos.GetPodQOS(&pod); api.PodQOSClass(testCase.expected) != actual { + if actual := qos.GetPodQOS(&pod); core.PodQOSClass(testCase.expected) != actual { t.Errorf("[%d]: conversion invalid qos pod %s, expected: %s, actual: %s", id, testCase.pod.Name, testCase.expected, actual) } } diff --git a/pkg/api/v1/register.go b/pkg/apis/core/v1/register.go similarity index 100% rename from pkg/api/v1/register.go rename to pkg/apis/core/v1/register.go diff --git a/pkg/apis/core/v1/validation/BUILD b/pkg/apis/core/v1/validation/BUILD new file mode 100644 index 00000000000..21cf28f303f --- /dev/null +++ b/pkg/apis/core/v1/validation/BUILD @@ -0,0 +1,43 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["validation.go"], + importpath = "k8s.io/kubernetes/pkg/apis/core/v1/validation", + visibility = ["//visibility:public"], + deps = [ + "//pkg/apis/core/helper:go_default_library", + "//pkg/apis/core/v1/helper:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["validation_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/apis/core/v1/validation", + deps = [ + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/apis/core/v1/validation/validation.go b/pkg/apis/core/v1/validation/validation.go new file mode 100644 index 00000000000..8e41b6c9632 --- /dev/null +++ b/pkg/apis/core/v1/validation/validation.go @@ -0,0 +1,163 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES 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/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/kubernetes/pkg/apis/core/helper" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" +) + +const isNegativeErrorMsg string = `must be greater than or equal to 0` +const isNotIntegerErrorMsg string = `must be an integer` + +func ValidateResourceRequirements(requirements *v1.ResourceRequirements, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + limPath := fldPath.Child("limits") + reqPath := fldPath.Child("requests") + for resourceName, quantity := range requirements.Limits { + fldPath := limPath.Key(string(resourceName)) + // Validate resource name. + allErrs = append(allErrs, validateContainerResourceName(string(resourceName), fldPath)...) + + // Validate resource quantity. + allErrs = append(allErrs, ValidateResourceQuantityValue(string(resourceName), quantity, fldPath)...) + + } + for resourceName, quantity := range requirements.Requests { + fldPath := reqPath.Key(string(resourceName)) + // Validate resource name. + allErrs = append(allErrs, validateContainerResourceName(string(resourceName), fldPath)...) + // Validate resource quantity. + allErrs = append(allErrs, ValidateResourceQuantityValue(string(resourceName), quantity, fldPath)...) + + // Check that request <= limit. + limitQuantity, exists := requirements.Limits[resourceName] + if exists { + // For GPUs, not only requests can't exceed limits, they also can't be lower, i.e. must be equal. + if quantity.Cmp(limitQuantity) != 0 && !v1helper.IsOvercommitAllowed(resourceName) { + allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be equal to %s limit", resourceName))) + } else if quantity.Cmp(limitQuantity) > 0 { + allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be less than or equal to %s limit", resourceName))) + } + } else if resourceName == v1.ResourceNvidiaGPU { + allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be equal to %s request", v1.ResourceNvidiaGPU))) + } + } + + return allErrs +} + +func validateContainerResourceName(value string, fldPath *field.Path) field.ErrorList { + allErrs := validateResourceName(value, fldPath) + if len(strings.Split(value, "/")) == 1 { + if !helper.IsStandardContainerResourceName(value) { + return append(allErrs, field.Invalid(fldPath, value, "must be a standard resource for containers")) + } + } + return allErrs +} + +// ValidateResourceQuantityValue enforces that specified quantity is valid for specified resource +func ValidateResourceQuantityValue(resource string, value resource.Quantity, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, ValidateNonnegativeQuantity(value, fldPath)...) + if helper.IsIntegerResourceName(resource) { + if value.MilliValue()%int64(1000) != int64(0) { + allErrs = append(allErrs, field.Invalid(fldPath, value, isNotIntegerErrorMsg)) + } + } + return allErrs +} + +// Validates that a Quantity is not negative +func ValidateNonnegativeQuantity(value resource.Quantity, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if value.Cmp(resource.Quantity{}) < 0 { + allErrs = append(allErrs, field.Invalid(fldPath, value.String(), isNegativeErrorMsg)) + } + return allErrs +} + +// Validate compute resource typename. +// Refer to docs/design/resources.md for more details. +func validateResourceName(value string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for _, msg := range validation.IsQualifiedName(value) { + allErrs = append(allErrs, field.Invalid(fldPath, value, msg)) + } + if len(allErrs) != 0 { + return allErrs + } + + if len(strings.Split(value, "/")) == 1 { + if !helper.IsStandardResourceName(value) { + return append(allErrs, field.Invalid(fldPath, value, "must be a standard resource type or fully qualified")) + } + } + + return allErrs +} + +func ValidatePodLogOptions(opts *v1.PodLogOptions) field.ErrorList { + allErrs := field.ErrorList{} + if opts.TailLines != nil && *opts.TailLines < 0 { + allErrs = append(allErrs, field.Invalid(field.NewPath("tailLines"), *opts.TailLines, isNegativeErrorMsg)) + } + if opts.LimitBytes != nil && *opts.LimitBytes < 1 { + allErrs = append(allErrs, field.Invalid(field.NewPath("limitBytes"), *opts.LimitBytes, "must be greater than 0")) + } + switch { + case opts.SinceSeconds != nil && opts.SinceTime != nil: + allErrs = append(allErrs, field.Forbidden(field.NewPath(""), "at most one of `sinceTime` or `sinceSeconds` may be specified")) + case opts.SinceSeconds != nil: + if *opts.SinceSeconds < 1 { + allErrs = append(allErrs, field.Invalid(field.NewPath("sinceSeconds"), *opts.SinceSeconds, "must be greater than 0")) + } + } + return allErrs +} + +func AccumulateUniqueHostPorts(containers []v1.Container, accumulator *sets.String, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + for ci, ctr := range containers { + idxPath := fldPath.Index(ci) + portsPath := idxPath.Child("ports") + for pi := range ctr.Ports { + idxPath := portsPath.Index(pi) + port := ctr.Ports[pi].HostPort + if port == 0 { + continue + } + str := fmt.Sprintf("%d/%s", port, ctr.Ports[pi].Protocol) + if accumulator.Has(str) { + allErrs = append(allErrs, field.Duplicate(idxPath.Child("hostPort"), str)) + } else { + accumulator.Insert(str) + } + } + } + return allErrs +} diff --git a/pkg/api/v1/validation/validation_test.go b/pkg/apis/core/v1/validation/validation_test.go similarity index 100% rename from pkg/api/v1/validation/validation_test.go rename to pkg/apis/core/v1/validation/validation_test.go diff --git a/pkg/apis/core/v1/zz_generated.conversion.go b/pkg/apis/core/v1/zz_generated.conversion.go new file mode 100644 index 00000000000..e8ddfac91fb --- /dev/null +++ b/pkg/apis/core/v1/zz_generated.conversion.go @@ -0,0 +1,5651 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by conversion-gen. Do not edit it manually! + +package v1 + +import ( + unsafe "unsafe" + + v1 "k8s.io/api/core/v1" + resource "k8s.io/apimachinery/pkg/api/resource" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + conversion "k8s.io/apimachinery/pkg/conversion" + runtime "k8s.io/apimachinery/pkg/runtime" + types "k8s.io/apimachinery/pkg/types" + 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(scheme *runtime.Scheme) error { + return scheme.AddGeneratedConversionFuncs( + Convert_v1_AWSElasticBlockStoreVolumeSource_To_core_AWSElasticBlockStoreVolumeSource, + Convert_core_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource, + Convert_v1_Affinity_To_core_Affinity, + Convert_core_Affinity_To_v1_Affinity, + Convert_v1_AttachedVolume_To_core_AttachedVolume, + Convert_core_AttachedVolume_To_v1_AttachedVolume, + Convert_v1_AvoidPods_To_core_AvoidPods, + Convert_core_AvoidPods_To_v1_AvoidPods, + Convert_v1_AzureDiskVolumeSource_To_core_AzureDiskVolumeSource, + Convert_core_AzureDiskVolumeSource_To_v1_AzureDiskVolumeSource, + Convert_v1_AzureFilePersistentVolumeSource_To_core_AzureFilePersistentVolumeSource, + Convert_core_AzureFilePersistentVolumeSource_To_v1_AzureFilePersistentVolumeSource, + Convert_v1_AzureFileVolumeSource_To_core_AzureFileVolumeSource, + Convert_core_AzureFileVolumeSource_To_v1_AzureFileVolumeSource, + Convert_v1_Binding_To_core_Binding, + Convert_core_Binding_To_v1_Binding, + Convert_v1_CSIPersistentVolumeSource_To_core_CSIPersistentVolumeSource, + Convert_core_CSIPersistentVolumeSource_To_v1_CSIPersistentVolumeSource, + Convert_v1_Capabilities_To_core_Capabilities, + Convert_core_Capabilities_To_v1_Capabilities, + Convert_v1_CephFSPersistentVolumeSource_To_core_CephFSPersistentVolumeSource, + Convert_core_CephFSPersistentVolumeSource_To_v1_CephFSPersistentVolumeSource, + Convert_v1_CephFSVolumeSource_To_core_CephFSVolumeSource, + Convert_core_CephFSVolumeSource_To_v1_CephFSVolumeSource, + Convert_v1_CinderVolumeSource_To_core_CinderVolumeSource, + Convert_core_CinderVolumeSource_To_v1_CinderVolumeSource, + Convert_v1_ClientIPConfig_To_core_ClientIPConfig, + Convert_core_ClientIPConfig_To_v1_ClientIPConfig, + Convert_v1_ComponentCondition_To_core_ComponentCondition, + Convert_core_ComponentCondition_To_v1_ComponentCondition, + Convert_v1_ComponentStatus_To_core_ComponentStatus, + Convert_core_ComponentStatus_To_v1_ComponentStatus, + Convert_v1_ComponentStatusList_To_core_ComponentStatusList, + Convert_core_ComponentStatusList_To_v1_ComponentStatusList, + Convert_v1_ConfigMap_To_core_ConfigMap, + Convert_core_ConfigMap_To_v1_ConfigMap, + Convert_v1_ConfigMapEnvSource_To_core_ConfigMapEnvSource, + Convert_core_ConfigMapEnvSource_To_v1_ConfigMapEnvSource, + Convert_v1_ConfigMapKeySelector_To_core_ConfigMapKeySelector, + Convert_core_ConfigMapKeySelector_To_v1_ConfigMapKeySelector, + Convert_v1_ConfigMapList_To_core_ConfigMapList, + Convert_core_ConfigMapList_To_v1_ConfigMapList, + Convert_v1_ConfigMapProjection_To_core_ConfigMapProjection, + Convert_core_ConfigMapProjection_To_v1_ConfigMapProjection, + Convert_v1_ConfigMapVolumeSource_To_core_ConfigMapVolumeSource, + Convert_core_ConfigMapVolumeSource_To_v1_ConfigMapVolumeSource, + Convert_v1_Container_To_core_Container, + Convert_core_Container_To_v1_Container, + Convert_v1_ContainerImage_To_core_ContainerImage, + Convert_core_ContainerImage_To_v1_ContainerImage, + Convert_v1_ContainerPort_To_core_ContainerPort, + Convert_core_ContainerPort_To_v1_ContainerPort, + Convert_v1_ContainerState_To_core_ContainerState, + Convert_core_ContainerState_To_v1_ContainerState, + Convert_v1_ContainerStateRunning_To_core_ContainerStateRunning, + Convert_core_ContainerStateRunning_To_v1_ContainerStateRunning, + Convert_v1_ContainerStateTerminated_To_core_ContainerStateTerminated, + Convert_core_ContainerStateTerminated_To_v1_ContainerStateTerminated, + Convert_v1_ContainerStateWaiting_To_core_ContainerStateWaiting, + Convert_core_ContainerStateWaiting_To_v1_ContainerStateWaiting, + Convert_v1_ContainerStatus_To_core_ContainerStatus, + Convert_core_ContainerStatus_To_v1_ContainerStatus, + Convert_v1_DaemonEndpoint_To_core_DaemonEndpoint, + Convert_core_DaemonEndpoint_To_v1_DaemonEndpoint, + Convert_v1_DeleteOptions_To_core_DeleteOptions, + Convert_core_DeleteOptions_To_v1_DeleteOptions, + Convert_v1_DownwardAPIProjection_To_core_DownwardAPIProjection, + Convert_core_DownwardAPIProjection_To_v1_DownwardAPIProjection, + Convert_v1_DownwardAPIVolumeFile_To_core_DownwardAPIVolumeFile, + Convert_core_DownwardAPIVolumeFile_To_v1_DownwardAPIVolumeFile, + Convert_v1_DownwardAPIVolumeSource_To_core_DownwardAPIVolumeSource, + Convert_core_DownwardAPIVolumeSource_To_v1_DownwardAPIVolumeSource, + Convert_v1_EmptyDirVolumeSource_To_core_EmptyDirVolumeSource, + Convert_core_EmptyDirVolumeSource_To_v1_EmptyDirVolumeSource, + Convert_v1_EndpointAddress_To_core_EndpointAddress, + Convert_core_EndpointAddress_To_v1_EndpointAddress, + Convert_v1_EndpointPort_To_core_EndpointPort, + Convert_core_EndpointPort_To_v1_EndpointPort, + Convert_v1_EndpointSubset_To_core_EndpointSubset, + Convert_core_EndpointSubset_To_v1_EndpointSubset, + Convert_v1_Endpoints_To_core_Endpoints, + Convert_core_Endpoints_To_v1_Endpoints, + Convert_v1_EndpointsList_To_core_EndpointsList, + Convert_core_EndpointsList_To_v1_EndpointsList, + Convert_v1_EnvFromSource_To_core_EnvFromSource, + Convert_core_EnvFromSource_To_v1_EnvFromSource, + Convert_v1_EnvVar_To_core_EnvVar, + Convert_core_EnvVar_To_v1_EnvVar, + Convert_v1_EnvVarSource_To_core_EnvVarSource, + Convert_core_EnvVarSource_To_v1_EnvVarSource, + Convert_v1_Event_To_core_Event, + Convert_core_Event_To_v1_Event, + Convert_v1_EventList_To_core_EventList, + Convert_core_EventList_To_v1_EventList, + Convert_v1_EventSeries_To_core_EventSeries, + Convert_core_EventSeries_To_v1_EventSeries, + Convert_v1_EventSource_To_core_EventSource, + Convert_core_EventSource_To_v1_EventSource, + Convert_v1_ExecAction_To_core_ExecAction, + Convert_core_ExecAction_To_v1_ExecAction, + Convert_v1_FCVolumeSource_To_core_FCVolumeSource, + Convert_core_FCVolumeSource_To_v1_FCVolumeSource, + Convert_v1_FlexPersistentVolumeSource_To_core_FlexPersistentVolumeSource, + Convert_core_FlexPersistentVolumeSource_To_v1_FlexPersistentVolumeSource, + Convert_v1_FlexVolumeSource_To_core_FlexVolumeSource, + Convert_core_FlexVolumeSource_To_v1_FlexVolumeSource, + Convert_v1_FlockerVolumeSource_To_core_FlockerVolumeSource, + Convert_core_FlockerVolumeSource_To_v1_FlockerVolumeSource, + Convert_v1_GCEPersistentDiskVolumeSource_To_core_GCEPersistentDiskVolumeSource, + Convert_core_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource, + Convert_v1_GitRepoVolumeSource_To_core_GitRepoVolumeSource, + Convert_core_GitRepoVolumeSource_To_v1_GitRepoVolumeSource, + Convert_v1_GlusterfsVolumeSource_To_core_GlusterfsVolumeSource, + Convert_core_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource, + Convert_v1_HTTPGetAction_To_core_HTTPGetAction, + Convert_core_HTTPGetAction_To_v1_HTTPGetAction, + Convert_v1_HTTPHeader_To_core_HTTPHeader, + Convert_core_HTTPHeader_To_v1_HTTPHeader, + Convert_v1_Handler_To_core_Handler, + Convert_core_Handler_To_v1_Handler, + Convert_v1_HostAlias_To_core_HostAlias, + Convert_core_HostAlias_To_v1_HostAlias, + Convert_v1_HostPathVolumeSource_To_core_HostPathVolumeSource, + Convert_core_HostPathVolumeSource_To_v1_HostPathVolumeSource, + Convert_v1_ISCSIPersistentVolumeSource_To_core_ISCSIPersistentVolumeSource, + Convert_core_ISCSIPersistentVolumeSource_To_v1_ISCSIPersistentVolumeSource, + Convert_v1_ISCSIVolumeSource_To_core_ISCSIVolumeSource, + Convert_core_ISCSIVolumeSource_To_v1_ISCSIVolumeSource, + Convert_v1_KeyToPath_To_core_KeyToPath, + Convert_core_KeyToPath_To_v1_KeyToPath, + Convert_v1_Lifecycle_To_core_Lifecycle, + Convert_core_Lifecycle_To_v1_Lifecycle, + Convert_v1_LimitRange_To_core_LimitRange, + Convert_core_LimitRange_To_v1_LimitRange, + Convert_v1_LimitRangeItem_To_core_LimitRangeItem, + Convert_core_LimitRangeItem_To_v1_LimitRangeItem, + Convert_v1_LimitRangeList_To_core_LimitRangeList, + Convert_core_LimitRangeList_To_v1_LimitRangeList, + Convert_v1_LimitRangeSpec_To_core_LimitRangeSpec, + Convert_core_LimitRangeSpec_To_v1_LimitRangeSpec, + Convert_v1_List_To_core_List, + Convert_core_List_To_v1_List, + Convert_v1_ListOptions_To_core_ListOptions, + Convert_core_ListOptions_To_v1_ListOptions, + Convert_v1_LoadBalancerIngress_To_core_LoadBalancerIngress, + Convert_core_LoadBalancerIngress_To_v1_LoadBalancerIngress, + Convert_v1_LoadBalancerStatus_To_core_LoadBalancerStatus, + Convert_core_LoadBalancerStatus_To_v1_LoadBalancerStatus, + Convert_v1_LocalObjectReference_To_core_LocalObjectReference, + Convert_core_LocalObjectReference_To_v1_LocalObjectReference, + Convert_v1_LocalVolumeSource_To_core_LocalVolumeSource, + Convert_core_LocalVolumeSource_To_v1_LocalVolumeSource, + Convert_v1_NFSVolumeSource_To_core_NFSVolumeSource, + Convert_core_NFSVolumeSource_To_v1_NFSVolumeSource, + Convert_v1_Namespace_To_core_Namespace, + Convert_core_Namespace_To_v1_Namespace, + Convert_v1_NamespaceList_To_core_NamespaceList, + Convert_core_NamespaceList_To_v1_NamespaceList, + Convert_v1_NamespaceSpec_To_core_NamespaceSpec, + Convert_core_NamespaceSpec_To_v1_NamespaceSpec, + Convert_v1_NamespaceStatus_To_core_NamespaceStatus, + Convert_core_NamespaceStatus_To_v1_NamespaceStatus, + Convert_v1_Node_To_core_Node, + Convert_core_Node_To_v1_Node, + Convert_v1_NodeAddress_To_core_NodeAddress, + Convert_core_NodeAddress_To_v1_NodeAddress, + Convert_v1_NodeAffinity_To_core_NodeAffinity, + Convert_core_NodeAffinity_To_v1_NodeAffinity, + Convert_v1_NodeCondition_To_core_NodeCondition, + Convert_core_NodeCondition_To_v1_NodeCondition, + Convert_v1_NodeConfigSource_To_core_NodeConfigSource, + Convert_core_NodeConfigSource_To_v1_NodeConfigSource, + Convert_v1_NodeDaemonEndpoints_To_core_NodeDaemonEndpoints, + Convert_core_NodeDaemonEndpoints_To_v1_NodeDaemonEndpoints, + Convert_v1_NodeList_To_core_NodeList, + Convert_core_NodeList_To_v1_NodeList, + Convert_v1_NodeProxyOptions_To_core_NodeProxyOptions, + Convert_core_NodeProxyOptions_To_v1_NodeProxyOptions, + Convert_v1_NodeResources_To_core_NodeResources, + Convert_core_NodeResources_To_v1_NodeResources, + Convert_v1_NodeSelector_To_core_NodeSelector, + Convert_core_NodeSelector_To_v1_NodeSelector, + Convert_v1_NodeSelectorRequirement_To_core_NodeSelectorRequirement, + Convert_core_NodeSelectorRequirement_To_v1_NodeSelectorRequirement, + Convert_v1_NodeSelectorTerm_To_core_NodeSelectorTerm, + Convert_core_NodeSelectorTerm_To_v1_NodeSelectorTerm, + Convert_v1_NodeSpec_To_core_NodeSpec, + Convert_core_NodeSpec_To_v1_NodeSpec, + Convert_v1_NodeStatus_To_core_NodeStatus, + Convert_core_NodeStatus_To_v1_NodeStatus, + Convert_v1_NodeSystemInfo_To_core_NodeSystemInfo, + Convert_core_NodeSystemInfo_To_v1_NodeSystemInfo, + Convert_v1_ObjectFieldSelector_To_core_ObjectFieldSelector, + Convert_core_ObjectFieldSelector_To_v1_ObjectFieldSelector, + Convert_v1_ObjectMeta_To_core_ObjectMeta, + Convert_core_ObjectMeta_To_v1_ObjectMeta, + Convert_v1_ObjectReference_To_core_ObjectReference, + Convert_core_ObjectReference_To_v1_ObjectReference, + Convert_v1_PersistentVolume_To_core_PersistentVolume, + Convert_core_PersistentVolume_To_v1_PersistentVolume, + Convert_v1_PersistentVolumeClaim_To_core_PersistentVolumeClaim, + Convert_core_PersistentVolumeClaim_To_v1_PersistentVolumeClaim, + Convert_v1_PersistentVolumeClaimCondition_To_core_PersistentVolumeClaimCondition, + Convert_core_PersistentVolumeClaimCondition_To_v1_PersistentVolumeClaimCondition, + Convert_v1_PersistentVolumeClaimList_To_core_PersistentVolumeClaimList, + Convert_core_PersistentVolumeClaimList_To_v1_PersistentVolumeClaimList, + Convert_v1_PersistentVolumeClaimSpec_To_core_PersistentVolumeClaimSpec, + Convert_core_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec, + Convert_v1_PersistentVolumeClaimStatus_To_core_PersistentVolumeClaimStatus, + Convert_core_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus, + Convert_v1_PersistentVolumeClaimVolumeSource_To_core_PersistentVolumeClaimVolumeSource, + Convert_core_PersistentVolumeClaimVolumeSource_To_v1_PersistentVolumeClaimVolumeSource, + Convert_v1_PersistentVolumeList_To_core_PersistentVolumeList, + Convert_core_PersistentVolumeList_To_v1_PersistentVolumeList, + Convert_v1_PersistentVolumeSource_To_core_PersistentVolumeSource, + Convert_core_PersistentVolumeSource_To_v1_PersistentVolumeSource, + Convert_v1_PersistentVolumeSpec_To_core_PersistentVolumeSpec, + Convert_core_PersistentVolumeSpec_To_v1_PersistentVolumeSpec, + Convert_v1_PersistentVolumeStatus_To_core_PersistentVolumeStatus, + Convert_core_PersistentVolumeStatus_To_v1_PersistentVolumeStatus, + Convert_v1_PhotonPersistentDiskVolumeSource_To_core_PhotonPersistentDiskVolumeSource, + Convert_core_PhotonPersistentDiskVolumeSource_To_v1_PhotonPersistentDiskVolumeSource, + Convert_v1_Pod_To_core_Pod, + Convert_core_Pod_To_v1_Pod, + Convert_v1_PodAffinity_To_core_PodAffinity, + Convert_core_PodAffinity_To_v1_PodAffinity, + Convert_v1_PodAffinityTerm_To_core_PodAffinityTerm, + Convert_core_PodAffinityTerm_To_v1_PodAffinityTerm, + Convert_v1_PodAntiAffinity_To_core_PodAntiAffinity, + Convert_core_PodAntiAffinity_To_v1_PodAntiAffinity, + Convert_v1_PodAttachOptions_To_core_PodAttachOptions, + Convert_core_PodAttachOptions_To_v1_PodAttachOptions, + Convert_v1_PodCondition_To_core_PodCondition, + Convert_core_PodCondition_To_v1_PodCondition, + Convert_v1_PodDNSConfig_To_core_PodDNSConfig, + Convert_core_PodDNSConfig_To_v1_PodDNSConfig, + Convert_v1_PodDNSConfigOption_To_core_PodDNSConfigOption, + Convert_core_PodDNSConfigOption_To_v1_PodDNSConfigOption, + Convert_v1_PodExecOptions_To_core_PodExecOptions, + Convert_core_PodExecOptions_To_v1_PodExecOptions, + Convert_v1_PodList_To_core_PodList, + Convert_core_PodList_To_v1_PodList, + Convert_v1_PodLogOptions_To_core_PodLogOptions, + Convert_core_PodLogOptions_To_v1_PodLogOptions, + Convert_v1_PodPortForwardOptions_To_core_PodPortForwardOptions, + Convert_core_PodPortForwardOptions_To_v1_PodPortForwardOptions, + Convert_v1_PodProxyOptions_To_core_PodProxyOptions, + Convert_core_PodProxyOptions_To_v1_PodProxyOptions, + Convert_v1_PodSecurityContext_To_core_PodSecurityContext, + Convert_core_PodSecurityContext_To_v1_PodSecurityContext, + Convert_v1_PodSignature_To_core_PodSignature, + Convert_core_PodSignature_To_v1_PodSignature, + Convert_v1_PodSpec_To_core_PodSpec, + Convert_core_PodSpec_To_v1_PodSpec, + Convert_v1_PodStatus_To_core_PodStatus, + Convert_core_PodStatus_To_v1_PodStatus, + Convert_v1_PodStatusResult_To_core_PodStatusResult, + Convert_core_PodStatusResult_To_v1_PodStatusResult, + Convert_v1_PodTemplate_To_core_PodTemplate, + Convert_core_PodTemplate_To_v1_PodTemplate, + Convert_v1_PodTemplateList_To_core_PodTemplateList, + Convert_core_PodTemplateList_To_v1_PodTemplateList, + Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec, + Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec, + Convert_v1_PortworxVolumeSource_To_core_PortworxVolumeSource, + Convert_core_PortworxVolumeSource_To_v1_PortworxVolumeSource, + Convert_v1_Preconditions_To_core_Preconditions, + Convert_core_Preconditions_To_v1_Preconditions, + Convert_v1_PreferAvoidPodsEntry_To_core_PreferAvoidPodsEntry, + Convert_core_PreferAvoidPodsEntry_To_v1_PreferAvoidPodsEntry, + Convert_v1_PreferredSchedulingTerm_To_core_PreferredSchedulingTerm, + Convert_core_PreferredSchedulingTerm_To_v1_PreferredSchedulingTerm, + Convert_v1_Probe_To_core_Probe, + Convert_core_Probe_To_v1_Probe, + Convert_v1_ProjectedVolumeSource_To_core_ProjectedVolumeSource, + Convert_core_ProjectedVolumeSource_To_v1_ProjectedVolumeSource, + Convert_v1_QuobyteVolumeSource_To_core_QuobyteVolumeSource, + Convert_core_QuobyteVolumeSource_To_v1_QuobyteVolumeSource, + Convert_v1_RBDPersistentVolumeSource_To_core_RBDPersistentVolumeSource, + Convert_core_RBDPersistentVolumeSource_To_v1_RBDPersistentVolumeSource, + Convert_v1_RBDVolumeSource_To_core_RBDVolumeSource, + Convert_core_RBDVolumeSource_To_v1_RBDVolumeSource, + Convert_v1_RangeAllocation_To_core_RangeAllocation, + Convert_core_RangeAllocation_To_v1_RangeAllocation, + Convert_v1_ReplicationController_To_core_ReplicationController, + Convert_core_ReplicationController_To_v1_ReplicationController, + Convert_v1_ReplicationControllerCondition_To_core_ReplicationControllerCondition, + Convert_core_ReplicationControllerCondition_To_v1_ReplicationControllerCondition, + Convert_v1_ReplicationControllerList_To_core_ReplicationControllerList, + Convert_core_ReplicationControllerList_To_v1_ReplicationControllerList, + Convert_v1_ReplicationControllerSpec_To_core_ReplicationControllerSpec, + Convert_core_ReplicationControllerSpec_To_v1_ReplicationControllerSpec, + Convert_v1_ReplicationControllerStatus_To_core_ReplicationControllerStatus, + Convert_core_ReplicationControllerStatus_To_v1_ReplicationControllerStatus, + Convert_v1_ResourceFieldSelector_To_core_ResourceFieldSelector, + Convert_core_ResourceFieldSelector_To_v1_ResourceFieldSelector, + Convert_v1_ResourceQuota_To_core_ResourceQuota, + Convert_core_ResourceQuota_To_v1_ResourceQuota, + Convert_v1_ResourceQuotaList_To_core_ResourceQuotaList, + Convert_core_ResourceQuotaList_To_v1_ResourceQuotaList, + Convert_v1_ResourceQuotaSpec_To_core_ResourceQuotaSpec, + Convert_core_ResourceQuotaSpec_To_v1_ResourceQuotaSpec, + Convert_v1_ResourceQuotaStatus_To_core_ResourceQuotaStatus, + Convert_core_ResourceQuotaStatus_To_v1_ResourceQuotaStatus, + Convert_v1_ResourceRequirements_To_core_ResourceRequirements, + Convert_core_ResourceRequirements_To_v1_ResourceRequirements, + Convert_v1_SELinuxOptions_To_core_SELinuxOptions, + Convert_core_SELinuxOptions_To_v1_SELinuxOptions, + Convert_v1_ScaleIOPersistentVolumeSource_To_core_ScaleIOPersistentVolumeSource, + Convert_core_ScaleIOPersistentVolumeSource_To_v1_ScaleIOPersistentVolumeSource, + Convert_v1_ScaleIOVolumeSource_To_core_ScaleIOVolumeSource, + Convert_core_ScaleIOVolumeSource_To_v1_ScaleIOVolumeSource, + Convert_v1_Secret_To_core_Secret, + Convert_core_Secret_To_v1_Secret, + Convert_v1_SecretEnvSource_To_core_SecretEnvSource, + Convert_core_SecretEnvSource_To_v1_SecretEnvSource, + Convert_v1_SecretKeySelector_To_core_SecretKeySelector, + Convert_core_SecretKeySelector_To_v1_SecretKeySelector, + Convert_v1_SecretList_To_core_SecretList, + Convert_core_SecretList_To_v1_SecretList, + Convert_v1_SecretProjection_To_core_SecretProjection, + Convert_core_SecretProjection_To_v1_SecretProjection, + Convert_v1_SecretReference_To_core_SecretReference, + Convert_core_SecretReference_To_v1_SecretReference, + Convert_v1_SecretVolumeSource_To_core_SecretVolumeSource, + Convert_core_SecretVolumeSource_To_v1_SecretVolumeSource, + Convert_v1_SecurityContext_To_core_SecurityContext, + Convert_core_SecurityContext_To_v1_SecurityContext, + Convert_v1_SerializedReference_To_core_SerializedReference, + Convert_core_SerializedReference_To_v1_SerializedReference, + Convert_v1_Service_To_core_Service, + Convert_core_Service_To_v1_Service, + Convert_v1_ServiceAccount_To_core_ServiceAccount, + Convert_core_ServiceAccount_To_v1_ServiceAccount, + Convert_v1_ServiceAccountList_To_core_ServiceAccountList, + Convert_core_ServiceAccountList_To_v1_ServiceAccountList, + Convert_v1_ServiceList_To_core_ServiceList, + Convert_core_ServiceList_To_v1_ServiceList, + Convert_v1_ServicePort_To_core_ServicePort, + Convert_core_ServicePort_To_v1_ServicePort, + Convert_v1_ServiceProxyOptions_To_core_ServiceProxyOptions, + Convert_core_ServiceProxyOptions_To_v1_ServiceProxyOptions, + Convert_v1_ServiceSpec_To_core_ServiceSpec, + Convert_core_ServiceSpec_To_v1_ServiceSpec, + Convert_v1_ServiceStatus_To_core_ServiceStatus, + Convert_core_ServiceStatus_To_v1_ServiceStatus, + Convert_v1_SessionAffinityConfig_To_core_SessionAffinityConfig, + Convert_core_SessionAffinityConfig_To_v1_SessionAffinityConfig, + Convert_v1_StorageOSPersistentVolumeSource_To_core_StorageOSPersistentVolumeSource, + Convert_core_StorageOSPersistentVolumeSource_To_v1_StorageOSPersistentVolumeSource, + Convert_v1_StorageOSVolumeSource_To_core_StorageOSVolumeSource, + Convert_core_StorageOSVolumeSource_To_v1_StorageOSVolumeSource, + Convert_v1_Sysctl_To_core_Sysctl, + Convert_core_Sysctl_To_v1_Sysctl, + Convert_v1_TCPSocketAction_To_core_TCPSocketAction, + Convert_core_TCPSocketAction_To_v1_TCPSocketAction, + Convert_v1_Taint_To_core_Taint, + Convert_core_Taint_To_v1_Taint, + Convert_v1_Toleration_To_core_Toleration, + Convert_core_Toleration_To_v1_Toleration, + Convert_v1_Volume_To_core_Volume, + Convert_core_Volume_To_v1_Volume, + Convert_v1_VolumeDevice_To_core_VolumeDevice, + Convert_core_VolumeDevice_To_v1_VolumeDevice, + Convert_v1_VolumeMount_To_core_VolumeMount, + Convert_core_VolumeMount_To_v1_VolumeMount, + Convert_v1_VolumeProjection_To_core_VolumeProjection, + Convert_core_VolumeProjection_To_v1_VolumeProjection, + Convert_v1_VolumeSource_To_core_VolumeSource, + Convert_core_VolumeSource_To_v1_VolumeSource, + Convert_v1_VsphereVirtualDiskVolumeSource_To_core_VsphereVirtualDiskVolumeSource, + Convert_core_VsphereVirtualDiskVolumeSource_To_v1_VsphereVirtualDiskVolumeSource, + Convert_v1_WeightedPodAffinityTerm_To_core_WeightedPodAffinityTerm, + Convert_core_WeightedPodAffinityTerm_To_v1_WeightedPodAffinityTerm, + ) +} + +func autoConvert_v1_AWSElasticBlockStoreVolumeSource_To_core_AWSElasticBlockStoreVolumeSource(in *v1.AWSElasticBlockStoreVolumeSource, out *core.AWSElasticBlockStoreVolumeSource, s conversion.Scope) error { + out.VolumeID = in.VolumeID + out.FSType = in.FSType + out.Partition = in.Partition + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_v1_AWSElasticBlockStoreVolumeSource_To_core_AWSElasticBlockStoreVolumeSource is an autogenerated conversion function. +func Convert_v1_AWSElasticBlockStoreVolumeSource_To_core_AWSElasticBlockStoreVolumeSource(in *v1.AWSElasticBlockStoreVolumeSource, out *core.AWSElasticBlockStoreVolumeSource, s conversion.Scope) error { + return autoConvert_v1_AWSElasticBlockStoreVolumeSource_To_core_AWSElasticBlockStoreVolumeSource(in, out, s) +} + +func autoConvert_core_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource(in *core.AWSElasticBlockStoreVolumeSource, out *v1.AWSElasticBlockStoreVolumeSource, s conversion.Scope) error { + out.VolumeID = in.VolumeID + out.FSType = in.FSType + out.Partition = in.Partition + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_core_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource is an autogenerated conversion function. +func Convert_core_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource(in *core.AWSElasticBlockStoreVolumeSource, out *v1.AWSElasticBlockStoreVolumeSource, s conversion.Scope) error { + return autoConvert_core_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource(in, out, s) +} + +func autoConvert_v1_Affinity_To_core_Affinity(in *v1.Affinity, out *core.Affinity, s conversion.Scope) error { + out.NodeAffinity = (*core.NodeAffinity)(unsafe.Pointer(in.NodeAffinity)) + out.PodAffinity = (*core.PodAffinity)(unsafe.Pointer(in.PodAffinity)) + out.PodAntiAffinity = (*core.PodAntiAffinity)(unsafe.Pointer(in.PodAntiAffinity)) + return nil +} + +// Convert_v1_Affinity_To_core_Affinity is an autogenerated conversion function. +func Convert_v1_Affinity_To_core_Affinity(in *v1.Affinity, out *core.Affinity, s conversion.Scope) error { + return autoConvert_v1_Affinity_To_core_Affinity(in, out, s) +} + +func autoConvert_core_Affinity_To_v1_Affinity(in *core.Affinity, out *v1.Affinity, s conversion.Scope) error { + out.NodeAffinity = (*v1.NodeAffinity)(unsafe.Pointer(in.NodeAffinity)) + out.PodAffinity = (*v1.PodAffinity)(unsafe.Pointer(in.PodAffinity)) + out.PodAntiAffinity = (*v1.PodAntiAffinity)(unsafe.Pointer(in.PodAntiAffinity)) + return nil +} + +// Convert_core_Affinity_To_v1_Affinity is an autogenerated conversion function. +func Convert_core_Affinity_To_v1_Affinity(in *core.Affinity, out *v1.Affinity, s conversion.Scope) error { + return autoConvert_core_Affinity_To_v1_Affinity(in, out, s) +} + +func autoConvert_v1_AttachedVolume_To_core_AttachedVolume(in *v1.AttachedVolume, out *core.AttachedVolume, s conversion.Scope) error { + out.Name = core.UniqueVolumeName(in.Name) + out.DevicePath = in.DevicePath + return nil +} + +// Convert_v1_AttachedVolume_To_core_AttachedVolume is an autogenerated conversion function. +func Convert_v1_AttachedVolume_To_core_AttachedVolume(in *v1.AttachedVolume, out *core.AttachedVolume, s conversion.Scope) error { + return autoConvert_v1_AttachedVolume_To_core_AttachedVolume(in, out, s) +} + +func autoConvert_core_AttachedVolume_To_v1_AttachedVolume(in *core.AttachedVolume, out *v1.AttachedVolume, s conversion.Scope) error { + out.Name = v1.UniqueVolumeName(in.Name) + out.DevicePath = in.DevicePath + return nil +} + +// Convert_core_AttachedVolume_To_v1_AttachedVolume is an autogenerated conversion function. +func Convert_core_AttachedVolume_To_v1_AttachedVolume(in *core.AttachedVolume, out *v1.AttachedVolume, s conversion.Scope) error { + return autoConvert_core_AttachedVolume_To_v1_AttachedVolume(in, out, s) +} + +func autoConvert_v1_AvoidPods_To_core_AvoidPods(in *v1.AvoidPods, out *core.AvoidPods, s conversion.Scope) error { + out.PreferAvoidPods = *(*[]core.PreferAvoidPodsEntry)(unsafe.Pointer(&in.PreferAvoidPods)) + return nil +} + +// Convert_v1_AvoidPods_To_core_AvoidPods is an autogenerated conversion function. +func Convert_v1_AvoidPods_To_core_AvoidPods(in *v1.AvoidPods, out *core.AvoidPods, s conversion.Scope) error { + return autoConvert_v1_AvoidPods_To_core_AvoidPods(in, out, s) +} + +func autoConvert_core_AvoidPods_To_v1_AvoidPods(in *core.AvoidPods, out *v1.AvoidPods, s conversion.Scope) error { + out.PreferAvoidPods = *(*[]v1.PreferAvoidPodsEntry)(unsafe.Pointer(&in.PreferAvoidPods)) + return nil +} + +// Convert_core_AvoidPods_To_v1_AvoidPods is an autogenerated conversion function. +func Convert_core_AvoidPods_To_v1_AvoidPods(in *core.AvoidPods, out *v1.AvoidPods, s conversion.Scope) error { + return autoConvert_core_AvoidPods_To_v1_AvoidPods(in, out, s) +} + +func autoConvert_v1_AzureDiskVolumeSource_To_core_AzureDiskVolumeSource(in *v1.AzureDiskVolumeSource, out *core.AzureDiskVolumeSource, s conversion.Scope) error { + out.DiskName = in.DiskName + out.DataDiskURI = in.DataDiskURI + out.CachingMode = (*core.AzureDataDiskCachingMode)(unsafe.Pointer(in.CachingMode)) + out.FSType = (*string)(unsafe.Pointer(in.FSType)) + out.ReadOnly = (*bool)(unsafe.Pointer(in.ReadOnly)) + out.Kind = (*core.AzureDataDiskKind)(unsafe.Pointer(in.Kind)) + return nil +} + +// Convert_v1_AzureDiskVolumeSource_To_core_AzureDiskVolumeSource is an autogenerated conversion function. +func Convert_v1_AzureDiskVolumeSource_To_core_AzureDiskVolumeSource(in *v1.AzureDiskVolumeSource, out *core.AzureDiskVolumeSource, s conversion.Scope) error { + return autoConvert_v1_AzureDiskVolumeSource_To_core_AzureDiskVolumeSource(in, out, s) +} + +func autoConvert_core_AzureDiskVolumeSource_To_v1_AzureDiskVolumeSource(in *core.AzureDiskVolumeSource, out *v1.AzureDiskVolumeSource, s conversion.Scope) error { + out.DiskName = in.DiskName + out.DataDiskURI = in.DataDiskURI + out.CachingMode = (*v1.AzureDataDiskCachingMode)(unsafe.Pointer(in.CachingMode)) + out.FSType = (*string)(unsafe.Pointer(in.FSType)) + out.ReadOnly = (*bool)(unsafe.Pointer(in.ReadOnly)) + out.Kind = (*v1.AzureDataDiskKind)(unsafe.Pointer(in.Kind)) + return nil +} + +// Convert_core_AzureDiskVolumeSource_To_v1_AzureDiskVolumeSource is an autogenerated conversion function. +func Convert_core_AzureDiskVolumeSource_To_v1_AzureDiskVolumeSource(in *core.AzureDiskVolumeSource, out *v1.AzureDiskVolumeSource, s conversion.Scope) error { + return autoConvert_core_AzureDiskVolumeSource_To_v1_AzureDiskVolumeSource(in, out, s) +} + +func autoConvert_v1_AzureFilePersistentVolumeSource_To_core_AzureFilePersistentVolumeSource(in *v1.AzureFilePersistentVolumeSource, out *core.AzureFilePersistentVolumeSource, s conversion.Scope) error { + out.SecretName = in.SecretName + out.ShareName = in.ShareName + out.ReadOnly = in.ReadOnly + out.SecretNamespace = (*string)(unsafe.Pointer(in.SecretNamespace)) + return nil +} + +// Convert_v1_AzureFilePersistentVolumeSource_To_core_AzureFilePersistentVolumeSource is an autogenerated conversion function. +func Convert_v1_AzureFilePersistentVolumeSource_To_core_AzureFilePersistentVolumeSource(in *v1.AzureFilePersistentVolumeSource, out *core.AzureFilePersistentVolumeSource, s conversion.Scope) error { + return autoConvert_v1_AzureFilePersistentVolumeSource_To_core_AzureFilePersistentVolumeSource(in, out, s) +} + +func autoConvert_core_AzureFilePersistentVolumeSource_To_v1_AzureFilePersistentVolumeSource(in *core.AzureFilePersistentVolumeSource, out *v1.AzureFilePersistentVolumeSource, s conversion.Scope) error { + out.SecretName = in.SecretName + out.ShareName = in.ShareName + out.ReadOnly = in.ReadOnly + out.SecretNamespace = (*string)(unsafe.Pointer(in.SecretNamespace)) + return nil +} + +// Convert_core_AzureFilePersistentVolumeSource_To_v1_AzureFilePersistentVolumeSource is an autogenerated conversion function. +func Convert_core_AzureFilePersistentVolumeSource_To_v1_AzureFilePersistentVolumeSource(in *core.AzureFilePersistentVolumeSource, out *v1.AzureFilePersistentVolumeSource, s conversion.Scope) error { + return autoConvert_core_AzureFilePersistentVolumeSource_To_v1_AzureFilePersistentVolumeSource(in, out, s) +} + +func autoConvert_v1_AzureFileVolumeSource_To_core_AzureFileVolumeSource(in *v1.AzureFileVolumeSource, out *core.AzureFileVolumeSource, s conversion.Scope) error { + out.SecretName = in.SecretName + out.ShareName = in.ShareName + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_v1_AzureFileVolumeSource_To_core_AzureFileVolumeSource is an autogenerated conversion function. +func Convert_v1_AzureFileVolumeSource_To_core_AzureFileVolumeSource(in *v1.AzureFileVolumeSource, out *core.AzureFileVolumeSource, s conversion.Scope) error { + return autoConvert_v1_AzureFileVolumeSource_To_core_AzureFileVolumeSource(in, out, s) +} + +func autoConvert_core_AzureFileVolumeSource_To_v1_AzureFileVolumeSource(in *core.AzureFileVolumeSource, out *v1.AzureFileVolumeSource, s conversion.Scope) error { + out.SecretName = in.SecretName + out.ShareName = in.ShareName + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_core_AzureFileVolumeSource_To_v1_AzureFileVolumeSource is an autogenerated conversion function. +func Convert_core_AzureFileVolumeSource_To_v1_AzureFileVolumeSource(in *core.AzureFileVolumeSource, out *v1.AzureFileVolumeSource, s conversion.Scope) error { + return autoConvert_core_AzureFileVolumeSource_To_v1_AzureFileVolumeSource(in, out, s) +} + +func autoConvert_v1_Binding_To_core_Binding(in *v1.Binding, out *core.Binding, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_ObjectReference_To_core_ObjectReference(&in.Target, &out.Target, s); err != nil { + return err + } + return nil +} + +// Convert_v1_Binding_To_core_Binding is an autogenerated conversion function. +func Convert_v1_Binding_To_core_Binding(in *v1.Binding, out *core.Binding, s conversion.Scope) error { + return autoConvert_v1_Binding_To_core_Binding(in, out, s) +} + +func autoConvert_core_Binding_To_v1_Binding(in *core.Binding, out *v1.Binding, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_core_ObjectReference_To_v1_ObjectReference(&in.Target, &out.Target, s); err != nil { + return err + } + return nil +} + +// Convert_core_Binding_To_v1_Binding is an autogenerated conversion function. +func Convert_core_Binding_To_v1_Binding(in *core.Binding, out *v1.Binding, s conversion.Scope) error { + return autoConvert_core_Binding_To_v1_Binding(in, out, s) +} + +func autoConvert_v1_CSIPersistentVolumeSource_To_core_CSIPersistentVolumeSource(in *v1.CSIPersistentVolumeSource, out *core.CSIPersistentVolumeSource, s conversion.Scope) error { + out.Driver = in.Driver + out.VolumeHandle = in.VolumeHandle + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_v1_CSIPersistentVolumeSource_To_core_CSIPersistentVolumeSource is an autogenerated conversion function. +func Convert_v1_CSIPersistentVolumeSource_To_core_CSIPersistentVolumeSource(in *v1.CSIPersistentVolumeSource, out *core.CSIPersistentVolumeSource, s conversion.Scope) error { + return autoConvert_v1_CSIPersistentVolumeSource_To_core_CSIPersistentVolumeSource(in, out, s) +} + +func autoConvert_core_CSIPersistentVolumeSource_To_v1_CSIPersistentVolumeSource(in *core.CSIPersistentVolumeSource, out *v1.CSIPersistentVolumeSource, s conversion.Scope) error { + out.Driver = in.Driver + out.VolumeHandle = in.VolumeHandle + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_core_CSIPersistentVolumeSource_To_v1_CSIPersistentVolumeSource is an autogenerated conversion function. +func Convert_core_CSIPersistentVolumeSource_To_v1_CSIPersistentVolumeSource(in *core.CSIPersistentVolumeSource, out *v1.CSIPersistentVolumeSource, s conversion.Scope) error { + return autoConvert_core_CSIPersistentVolumeSource_To_v1_CSIPersistentVolumeSource(in, out, s) +} + +func autoConvert_v1_Capabilities_To_core_Capabilities(in *v1.Capabilities, out *core.Capabilities, s conversion.Scope) error { + out.Add = *(*[]core.Capability)(unsafe.Pointer(&in.Add)) + out.Drop = *(*[]core.Capability)(unsafe.Pointer(&in.Drop)) + return nil +} + +// Convert_v1_Capabilities_To_core_Capabilities is an autogenerated conversion function. +func Convert_v1_Capabilities_To_core_Capabilities(in *v1.Capabilities, out *core.Capabilities, s conversion.Scope) error { + return autoConvert_v1_Capabilities_To_core_Capabilities(in, out, s) +} + +func autoConvert_core_Capabilities_To_v1_Capabilities(in *core.Capabilities, out *v1.Capabilities, s conversion.Scope) error { + out.Add = *(*[]v1.Capability)(unsafe.Pointer(&in.Add)) + out.Drop = *(*[]v1.Capability)(unsafe.Pointer(&in.Drop)) + return nil +} + +// Convert_core_Capabilities_To_v1_Capabilities is an autogenerated conversion function. +func Convert_core_Capabilities_To_v1_Capabilities(in *core.Capabilities, out *v1.Capabilities, s conversion.Scope) error { + return autoConvert_core_Capabilities_To_v1_Capabilities(in, out, s) +} + +func autoConvert_v1_CephFSPersistentVolumeSource_To_core_CephFSPersistentVolumeSource(in *v1.CephFSPersistentVolumeSource, out *core.CephFSPersistentVolumeSource, s conversion.Scope) error { + out.Monitors = *(*[]string)(unsafe.Pointer(&in.Monitors)) + out.Path = in.Path + out.User = in.User + out.SecretFile = in.SecretFile + out.SecretRef = (*core.SecretReference)(unsafe.Pointer(in.SecretRef)) + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_v1_CephFSPersistentVolumeSource_To_core_CephFSPersistentVolumeSource is an autogenerated conversion function. +func Convert_v1_CephFSPersistentVolumeSource_To_core_CephFSPersistentVolumeSource(in *v1.CephFSPersistentVolumeSource, out *core.CephFSPersistentVolumeSource, s conversion.Scope) error { + return autoConvert_v1_CephFSPersistentVolumeSource_To_core_CephFSPersistentVolumeSource(in, out, s) +} + +func autoConvert_core_CephFSPersistentVolumeSource_To_v1_CephFSPersistentVolumeSource(in *core.CephFSPersistentVolumeSource, out *v1.CephFSPersistentVolumeSource, s conversion.Scope) error { + out.Monitors = *(*[]string)(unsafe.Pointer(&in.Monitors)) + out.Path = in.Path + out.User = in.User + out.SecretFile = in.SecretFile + out.SecretRef = (*v1.SecretReference)(unsafe.Pointer(in.SecretRef)) + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_core_CephFSPersistentVolumeSource_To_v1_CephFSPersistentVolumeSource is an autogenerated conversion function. +func Convert_core_CephFSPersistentVolumeSource_To_v1_CephFSPersistentVolumeSource(in *core.CephFSPersistentVolumeSource, out *v1.CephFSPersistentVolumeSource, s conversion.Scope) error { + return autoConvert_core_CephFSPersistentVolumeSource_To_v1_CephFSPersistentVolumeSource(in, out, s) +} + +func autoConvert_v1_CephFSVolumeSource_To_core_CephFSVolumeSource(in *v1.CephFSVolumeSource, out *core.CephFSVolumeSource, s conversion.Scope) error { + out.Monitors = *(*[]string)(unsafe.Pointer(&in.Monitors)) + out.Path = in.Path + out.User = in.User + out.SecretFile = in.SecretFile + out.SecretRef = (*core.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_v1_CephFSVolumeSource_To_core_CephFSVolumeSource is an autogenerated conversion function. +func Convert_v1_CephFSVolumeSource_To_core_CephFSVolumeSource(in *v1.CephFSVolumeSource, out *core.CephFSVolumeSource, s conversion.Scope) error { + return autoConvert_v1_CephFSVolumeSource_To_core_CephFSVolumeSource(in, out, s) +} + +func autoConvert_core_CephFSVolumeSource_To_v1_CephFSVolumeSource(in *core.CephFSVolumeSource, out *v1.CephFSVolumeSource, s conversion.Scope) error { + out.Monitors = *(*[]string)(unsafe.Pointer(&in.Monitors)) + out.Path = in.Path + out.User = in.User + out.SecretFile = in.SecretFile + out.SecretRef = (*v1.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_core_CephFSVolumeSource_To_v1_CephFSVolumeSource is an autogenerated conversion function. +func Convert_core_CephFSVolumeSource_To_v1_CephFSVolumeSource(in *core.CephFSVolumeSource, out *v1.CephFSVolumeSource, s conversion.Scope) error { + return autoConvert_core_CephFSVolumeSource_To_v1_CephFSVolumeSource(in, out, s) +} + +func autoConvert_v1_CinderVolumeSource_To_core_CinderVolumeSource(in *v1.CinderVolumeSource, out *core.CinderVolumeSource, s conversion.Scope) error { + out.VolumeID = in.VolumeID + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_v1_CinderVolumeSource_To_core_CinderVolumeSource is an autogenerated conversion function. +func Convert_v1_CinderVolumeSource_To_core_CinderVolumeSource(in *v1.CinderVolumeSource, out *core.CinderVolumeSource, s conversion.Scope) error { + return autoConvert_v1_CinderVolumeSource_To_core_CinderVolumeSource(in, out, s) +} + +func autoConvert_core_CinderVolumeSource_To_v1_CinderVolumeSource(in *core.CinderVolumeSource, out *v1.CinderVolumeSource, s conversion.Scope) error { + out.VolumeID = in.VolumeID + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_core_CinderVolumeSource_To_v1_CinderVolumeSource is an autogenerated conversion function. +func Convert_core_CinderVolumeSource_To_v1_CinderVolumeSource(in *core.CinderVolumeSource, out *v1.CinderVolumeSource, s conversion.Scope) error { + return autoConvert_core_CinderVolumeSource_To_v1_CinderVolumeSource(in, out, s) +} + +func autoConvert_v1_ClientIPConfig_To_core_ClientIPConfig(in *v1.ClientIPConfig, out *core.ClientIPConfig, s conversion.Scope) error { + out.TimeoutSeconds = (*int32)(unsafe.Pointer(in.TimeoutSeconds)) + return nil +} + +// Convert_v1_ClientIPConfig_To_core_ClientIPConfig is an autogenerated conversion function. +func Convert_v1_ClientIPConfig_To_core_ClientIPConfig(in *v1.ClientIPConfig, out *core.ClientIPConfig, s conversion.Scope) error { + return autoConvert_v1_ClientIPConfig_To_core_ClientIPConfig(in, out, s) +} + +func autoConvert_core_ClientIPConfig_To_v1_ClientIPConfig(in *core.ClientIPConfig, out *v1.ClientIPConfig, s conversion.Scope) error { + out.TimeoutSeconds = (*int32)(unsafe.Pointer(in.TimeoutSeconds)) + return nil +} + +// Convert_core_ClientIPConfig_To_v1_ClientIPConfig is an autogenerated conversion function. +func Convert_core_ClientIPConfig_To_v1_ClientIPConfig(in *core.ClientIPConfig, out *v1.ClientIPConfig, s conversion.Scope) error { + return autoConvert_core_ClientIPConfig_To_v1_ClientIPConfig(in, out, s) +} + +func autoConvert_v1_ComponentCondition_To_core_ComponentCondition(in *v1.ComponentCondition, out *core.ComponentCondition, s conversion.Scope) error { + out.Type = core.ComponentConditionType(in.Type) + out.Status = core.ConditionStatus(in.Status) + out.Message = in.Message + out.Error = in.Error + return nil +} + +// Convert_v1_ComponentCondition_To_core_ComponentCondition is an autogenerated conversion function. +func Convert_v1_ComponentCondition_To_core_ComponentCondition(in *v1.ComponentCondition, out *core.ComponentCondition, s conversion.Scope) error { + return autoConvert_v1_ComponentCondition_To_core_ComponentCondition(in, out, s) +} + +func autoConvert_core_ComponentCondition_To_v1_ComponentCondition(in *core.ComponentCondition, out *v1.ComponentCondition, s conversion.Scope) error { + out.Type = v1.ComponentConditionType(in.Type) + out.Status = v1.ConditionStatus(in.Status) + out.Message = in.Message + out.Error = in.Error + return nil +} + +// Convert_core_ComponentCondition_To_v1_ComponentCondition is an autogenerated conversion function. +func Convert_core_ComponentCondition_To_v1_ComponentCondition(in *core.ComponentCondition, out *v1.ComponentCondition, s conversion.Scope) error { + return autoConvert_core_ComponentCondition_To_v1_ComponentCondition(in, out, s) +} + +func autoConvert_v1_ComponentStatus_To_core_ComponentStatus(in *v1.ComponentStatus, out *core.ComponentStatus, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Conditions = *(*[]core.ComponentCondition)(unsafe.Pointer(&in.Conditions)) + return nil +} + +// Convert_v1_ComponentStatus_To_core_ComponentStatus is an autogenerated conversion function. +func Convert_v1_ComponentStatus_To_core_ComponentStatus(in *v1.ComponentStatus, out *core.ComponentStatus, s conversion.Scope) error { + return autoConvert_v1_ComponentStatus_To_core_ComponentStatus(in, out, s) +} + +func autoConvert_core_ComponentStatus_To_v1_ComponentStatus(in *core.ComponentStatus, out *v1.ComponentStatus, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Conditions = *(*[]v1.ComponentCondition)(unsafe.Pointer(&in.Conditions)) + return nil +} + +// Convert_core_ComponentStatus_To_v1_ComponentStatus is an autogenerated conversion function. +func Convert_core_ComponentStatus_To_v1_ComponentStatus(in *core.ComponentStatus, out *v1.ComponentStatus, s conversion.Scope) error { + return autoConvert_core_ComponentStatus_To_v1_ComponentStatus(in, out, s) +} + +func autoConvert_v1_ComponentStatusList_To_core_ComponentStatusList(in *v1.ComponentStatusList, out *core.ComponentStatusList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]core.ComponentStatus)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1_ComponentStatusList_To_core_ComponentStatusList is an autogenerated conversion function. +func Convert_v1_ComponentStatusList_To_core_ComponentStatusList(in *v1.ComponentStatusList, out *core.ComponentStatusList, s conversion.Scope) error { + return autoConvert_v1_ComponentStatusList_To_core_ComponentStatusList(in, out, s) +} + +func autoConvert_core_ComponentStatusList_To_v1_ComponentStatusList(in *core.ComponentStatusList, out *v1.ComponentStatusList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]v1.ComponentStatus)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_core_ComponentStatusList_To_v1_ComponentStatusList is an autogenerated conversion function. +func Convert_core_ComponentStatusList_To_v1_ComponentStatusList(in *core.ComponentStatusList, out *v1.ComponentStatusList, s conversion.Scope) error { + return autoConvert_core_ComponentStatusList_To_v1_ComponentStatusList(in, out, s) +} + +func autoConvert_v1_ConfigMap_To_core_ConfigMap(in *v1.ConfigMap, out *core.ConfigMap, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Data = *(*map[string]string)(unsafe.Pointer(&in.Data)) + return nil +} + +// Convert_v1_ConfigMap_To_core_ConfigMap is an autogenerated conversion function. +func Convert_v1_ConfigMap_To_core_ConfigMap(in *v1.ConfigMap, out *core.ConfigMap, s conversion.Scope) error { + return autoConvert_v1_ConfigMap_To_core_ConfigMap(in, out, s) +} + +func autoConvert_core_ConfigMap_To_v1_ConfigMap(in *core.ConfigMap, out *v1.ConfigMap, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Data = *(*map[string]string)(unsafe.Pointer(&in.Data)) + return nil +} + +// Convert_core_ConfigMap_To_v1_ConfigMap is an autogenerated conversion function. +func Convert_core_ConfigMap_To_v1_ConfigMap(in *core.ConfigMap, out *v1.ConfigMap, s conversion.Scope) error { + return autoConvert_core_ConfigMap_To_v1_ConfigMap(in, out, s) +} + +func autoConvert_v1_ConfigMapEnvSource_To_core_ConfigMapEnvSource(in *v1.ConfigMapEnvSource, out *core.ConfigMapEnvSource, s conversion.Scope) error { + if err := Convert_v1_LocalObjectReference_To_core_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { + return err + } + out.Optional = (*bool)(unsafe.Pointer(in.Optional)) + return nil +} + +// Convert_v1_ConfigMapEnvSource_To_core_ConfigMapEnvSource is an autogenerated conversion function. +func Convert_v1_ConfigMapEnvSource_To_core_ConfigMapEnvSource(in *v1.ConfigMapEnvSource, out *core.ConfigMapEnvSource, s conversion.Scope) error { + return autoConvert_v1_ConfigMapEnvSource_To_core_ConfigMapEnvSource(in, out, s) +} + +func autoConvert_core_ConfigMapEnvSource_To_v1_ConfigMapEnvSource(in *core.ConfigMapEnvSource, out *v1.ConfigMapEnvSource, s conversion.Scope) error { + if err := Convert_core_LocalObjectReference_To_v1_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { + return err + } + out.Optional = (*bool)(unsafe.Pointer(in.Optional)) + return nil +} + +// Convert_core_ConfigMapEnvSource_To_v1_ConfigMapEnvSource is an autogenerated conversion function. +func Convert_core_ConfigMapEnvSource_To_v1_ConfigMapEnvSource(in *core.ConfigMapEnvSource, out *v1.ConfigMapEnvSource, s conversion.Scope) error { + return autoConvert_core_ConfigMapEnvSource_To_v1_ConfigMapEnvSource(in, out, s) +} + +func autoConvert_v1_ConfigMapKeySelector_To_core_ConfigMapKeySelector(in *v1.ConfigMapKeySelector, out *core.ConfigMapKeySelector, s conversion.Scope) error { + if err := Convert_v1_LocalObjectReference_To_core_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { + return err + } + out.Key = in.Key + out.Optional = (*bool)(unsafe.Pointer(in.Optional)) + return nil +} + +// Convert_v1_ConfigMapKeySelector_To_core_ConfigMapKeySelector is an autogenerated conversion function. +func Convert_v1_ConfigMapKeySelector_To_core_ConfigMapKeySelector(in *v1.ConfigMapKeySelector, out *core.ConfigMapKeySelector, s conversion.Scope) error { + return autoConvert_v1_ConfigMapKeySelector_To_core_ConfigMapKeySelector(in, out, s) +} + +func autoConvert_core_ConfigMapKeySelector_To_v1_ConfigMapKeySelector(in *core.ConfigMapKeySelector, out *v1.ConfigMapKeySelector, s conversion.Scope) error { + if err := Convert_core_LocalObjectReference_To_v1_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { + return err + } + out.Key = in.Key + out.Optional = (*bool)(unsafe.Pointer(in.Optional)) + return nil +} + +// Convert_core_ConfigMapKeySelector_To_v1_ConfigMapKeySelector is an autogenerated conversion function. +func Convert_core_ConfigMapKeySelector_To_v1_ConfigMapKeySelector(in *core.ConfigMapKeySelector, out *v1.ConfigMapKeySelector, s conversion.Scope) error { + return autoConvert_core_ConfigMapKeySelector_To_v1_ConfigMapKeySelector(in, out, s) +} + +func autoConvert_v1_ConfigMapList_To_core_ConfigMapList(in *v1.ConfigMapList, out *core.ConfigMapList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]core.ConfigMap)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1_ConfigMapList_To_core_ConfigMapList is an autogenerated conversion function. +func Convert_v1_ConfigMapList_To_core_ConfigMapList(in *v1.ConfigMapList, out *core.ConfigMapList, s conversion.Scope) error { + return autoConvert_v1_ConfigMapList_To_core_ConfigMapList(in, out, s) +} + +func autoConvert_core_ConfigMapList_To_v1_ConfigMapList(in *core.ConfigMapList, out *v1.ConfigMapList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]v1.ConfigMap)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_core_ConfigMapList_To_v1_ConfigMapList is an autogenerated conversion function. +func Convert_core_ConfigMapList_To_v1_ConfigMapList(in *core.ConfigMapList, out *v1.ConfigMapList, s conversion.Scope) error { + return autoConvert_core_ConfigMapList_To_v1_ConfigMapList(in, out, s) +} + +func autoConvert_v1_ConfigMapProjection_To_core_ConfigMapProjection(in *v1.ConfigMapProjection, out *core.ConfigMapProjection, s conversion.Scope) error { + if err := Convert_v1_LocalObjectReference_To_core_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { + return err + } + out.Items = *(*[]core.KeyToPath)(unsafe.Pointer(&in.Items)) + out.Optional = (*bool)(unsafe.Pointer(in.Optional)) + return nil +} + +// Convert_v1_ConfigMapProjection_To_core_ConfigMapProjection is an autogenerated conversion function. +func Convert_v1_ConfigMapProjection_To_core_ConfigMapProjection(in *v1.ConfigMapProjection, out *core.ConfigMapProjection, s conversion.Scope) error { + return autoConvert_v1_ConfigMapProjection_To_core_ConfigMapProjection(in, out, s) +} + +func autoConvert_core_ConfigMapProjection_To_v1_ConfigMapProjection(in *core.ConfigMapProjection, out *v1.ConfigMapProjection, s conversion.Scope) error { + if err := Convert_core_LocalObjectReference_To_v1_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { + return err + } + out.Items = *(*[]v1.KeyToPath)(unsafe.Pointer(&in.Items)) + out.Optional = (*bool)(unsafe.Pointer(in.Optional)) + return nil +} + +// Convert_core_ConfigMapProjection_To_v1_ConfigMapProjection is an autogenerated conversion function. +func Convert_core_ConfigMapProjection_To_v1_ConfigMapProjection(in *core.ConfigMapProjection, out *v1.ConfigMapProjection, s conversion.Scope) error { + return autoConvert_core_ConfigMapProjection_To_v1_ConfigMapProjection(in, out, s) +} + +func autoConvert_v1_ConfigMapVolumeSource_To_core_ConfigMapVolumeSource(in *v1.ConfigMapVolumeSource, out *core.ConfigMapVolumeSource, s conversion.Scope) error { + if err := Convert_v1_LocalObjectReference_To_core_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { + return err + } + out.Items = *(*[]core.KeyToPath)(unsafe.Pointer(&in.Items)) + out.DefaultMode = (*int32)(unsafe.Pointer(in.DefaultMode)) + out.Optional = (*bool)(unsafe.Pointer(in.Optional)) + return nil +} + +// Convert_v1_ConfigMapVolumeSource_To_core_ConfigMapVolumeSource is an autogenerated conversion function. +func Convert_v1_ConfigMapVolumeSource_To_core_ConfigMapVolumeSource(in *v1.ConfigMapVolumeSource, out *core.ConfigMapVolumeSource, s conversion.Scope) error { + return autoConvert_v1_ConfigMapVolumeSource_To_core_ConfigMapVolumeSource(in, out, s) +} + +func autoConvert_core_ConfigMapVolumeSource_To_v1_ConfigMapVolumeSource(in *core.ConfigMapVolumeSource, out *v1.ConfigMapVolumeSource, s conversion.Scope) error { + if err := Convert_core_LocalObjectReference_To_v1_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { + return err + } + out.Items = *(*[]v1.KeyToPath)(unsafe.Pointer(&in.Items)) + out.DefaultMode = (*int32)(unsafe.Pointer(in.DefaultMode)) + out.Optional = (*bool)(unsafe.Pointer(in.Optional)) + return nil +} + +// Convert_core_ConfigMapVolumeSource_To_v1_ConfigMapVolumeSource is an autogenerated conversion function. +func Convert_core_ConfigMapVolumeSource_To_v1_ConfigMapVolumeSource(in *core.ConfigMapVolumeSource, out *v1.ConfigMapVolumeSource, s conversion.Scope) error { + return autoConvert_core_ConfigMapVolumeSource_To_v1_ConfigMapVolumeSource(in, out, s) +} + +func autoConvert_v1_Container_To_core_Container(in *v1.Container, out *core.Container, s conversion.Scope) error { + out.Name = in.Name + out.Image = in.Image + out.Command = *(*[]string)(unsafe.Pointer(&in.Command)) + out.Args = *(*[]string)(unsafe.Pointer(&in.Args)) + out.WorkingDir = in.WorkingDir + out.Ports = *(*[]core.ContainerPort)(unsafe.Pointer(&in.Ports)) + out.EnvFrom = *(*[]core.EnvFromSource)(unsafe.Pointer(&in.EnvFrom)) + out.Env = *(*[]core.EnvVar)(unsafe.Pointer(&in.Env)) + if err := Convert_v1_ResourceRequirements_To_core_ResourceRequirements(&in.Resources, &out.Resources, s); err != nil { + return err + } + out.VolumeMounts = *(*[]core.VolumeMount)(unsafe.Pointer(&in.VolumeMounts)) + out.VolumeDevices = *(*[]core.VolumeDevice)(unsafe.Pointer(&in.VolumeDevices)) + out.LivenessProbe = (*core.Probe)(unsafe.Pointer(in.LivenessProbe)) + out.ReadinessProbe = (*core.Probe)(unsafe.Pointer(in.ReadinessProbe)) + out.Lifecycle = (*core.Lifecycle)(unsafe.Pointer(in.Lifecycle)) + out.TerminationMessagePath = in.TerminationMessagePath + out.TerminationMessagePolicy = core.TerminationMessagePolicy(in.TerminationMessagePolicy) + out.ImagePullPolicy = core.PullPolicy(in.ImagePullPolicy) + if in.SecurityContext != nil { + in, out := &in.SecurityContext, &out.SecurityContext + *out = new(core.SecurityContext) + if err := Convert_v1_SecurityContext_To_core_SecurityContext(*in, *out, s); err != nil { + return err + } + } else { + out.SecurityContext = nil + } + out.Stdin = in.Stdin + out.StdinOnce = in.StdinOnce + out.TTY = in.TTY + return nil +} + +// Convert_v1_Container_To_core_Container is an autogenerated conversion function. +func Convert_v1_Container_To_core_Container(in *v1.Container, out *core.Container, s conversion.Scope) error { + return autoConvert_v1_Container_To_core_Container(in, out, s) +} + +func autoConvert_core_Container_To_v1_Container(in *core.Container, out *v1.Container, s conversion.Scope) error { + out.Name = in.Name + out.Image = in.Image + out.Command = *(*[]string)(unsafe.Pointer(&in.Command)) + out.Args = *(*[]string)(unsafe.Pointer(&in.Args)) + out.WorkingDir = in.WorkingDir + out.Ports = *(*[]v1.ContainerPort)(unsafe.Pointer(&in.Ports)) + out.EnvFrom = *(*[]v1.EnvFromSource)(unsafe.Pointer(&in.EnvFrom)) + out.Env = *(*[]v1.EnvVar)(unsafe.Pointer(&in.Env)) + if err := Convert_core_ResourceRequirements_To_v1_ResourceRequirements(&in.Resources, &out.Resources, s); err != nil { + return err + } + out.VolumeMounts = *(*[]v1.VolumeMount)(unsafe.Pointer(&in.VolumeMounts)) + out.VolumeDevices = *(*[]v1.VolumeDevice)(unsafe.Pointer(&in.VolumeDevices)) + out.LivenessProbe = (*v1.Probe)(unsafe.Pointer(in.LivenessProbe)) + out.ReadinessProbe = (*v1.Probe)(unsafe.Pointer(in.ReadinessProbe)) + out.Lifecycle = (*v1.Lifecycle)(unsafe.Pointer(in.Lifecycle)) + out.TerminationMessagePath = in.TerminationMessagePath + out.TerminationMessagePolicy = v1.TerminationMessagePolicy(in.TerminationMessagePolicy) + out.ImagePullPolicy = v1.PullPolicy(in.ImagePullPolicy) + if in.SecurityContext != nil { + in, out := &in.SecurityContext, &out.SecurityContext + *out = new(v1.SecurityContext) + if err := Convert_core_SecurityContext_To_v1_SecurityContext(*in, *out, s); err != nil { + return err + } + } else { + out.SecurityContext = nil + } + out.Stdin = in.Stdin + out.StdinOnce = in.StdinOnce + out.TTY = in.TTY + return nil +} + +// Convert_core_Container_To_v1_Container is an autogenerated conversion function. +func Convert_core_Container_To_v1_Container(in *core.Container, out *v1.Container, s conversion.Scope) error { + return autoConvert_core_Container_To_v1_Container(in, out, s) +} + +func autoConvert_v1_ContainerImage_To_core_ContainerImage(in *v1.ContainerImage, out *core.ContainerImage, s conversion.Scope) error { + out.Names = *(*[]string)(unsafe.Pointer(&in.Names)) + out.SizeBytes = in.SizeBytes + return nil +} + +// Convert_v1_ContainerImage_To_core_ContainerImage is an autogenerated conversion function. +func Convert_v1_ContainerImage_To_core_ContainerImage(in *v1.ContainerImage, out *core.ContainerImage, s conversion.Scope) error { + return autoConvert_v1_ContainerImage_To_core_ContainerImage(in, out, s) +} + +func autoConvert_core_ContainerImage_To_v1_ContainerImage(in *core.ContainerImage, out *v1.ContainerImage, s conversion.Scope) error { + out.Names = *(*[]string)(unsafe.Pointer(&in.Names)) + out.SizeBytes = in.SizeBytes + return nil +} + +// Convert_core_ContainerImage_To_v1_ContainerImage is an autogenerated conversion function. +func Convert_core_ContainerImage_To_v1_ContainerImage(in *core.ContainerImage, out *v1.ContainerImage, s conversion.Scope) error { + return autoConvert_core_ContainerImage_To_v1_ContainerImage(in, out, s) +} + +func autoConvert_v1_ContainerPort_To_core_ContainerPort(in *v1.ContainerPort, out *core.ContainerPort, s conversion.Scope) error { + out.Name = in.Name + out.HostPort = in.HostPort + out.ContainerPort = in.ContainerPort + out.Protocol = core.Protocol(in.Protocol) + out.HostIP = in.HostIP + return nil +} + +// Convert_v1_ContainerPort_To_core_ContainerPort is an autogenerated conversion function. +func Convert_v1_ContainerPort_To_core_ContainerPort(in *v1.ContainerPort, out *core.ContainerPort, s conversion.Scope) error { + return autoConvert_v1_ContainerPort_To_core_ContainerPort(in, out, s) +} + +func autoConvert_core_ContainerPort_To_v1_ContainerPort(in *core.ContainerPort, out *v1.ContainerPort, s conversion.Scope) error { + out.Name = in.Name + out.HostPort = in.HostPort + out.ContainerPort = in.ContainerPort + out.Protocol = v1.Protocol(in.Protocol) + out.HostIP = in.HostIP + return nil +} + +// Convert_core_ContainerPort_To_v1_ContainerPort is an autogenerated conversion function. +func Convert_core_ContainerPort_To_v1_ContainerPort(in *core.ContainerPort, out *v1.ContainerPort, s conversion.Scope) error { + return autoConvert_core_ContainerPort_To_v1_ContainerPort(in, out, s) +} + +func autoConvert_v1_ContainerState_To_core_ContainerState(in *v1.ContainerState, out *core.ContainerState, s conversion.Scope) error { + out.Waiting = (*core.ContainerStateWaiting)(unsafe.Pointer(in.Waiting)) + out.Running = (*core.ContainerStateRunning)(unsafe.Pointer(in.Running)) + out.Terminated = (*core.ContainerStateTerminated)(unsafe.Pointer(in.Terminated)) + return nil +} + +// Convert_v1_ContainerState_To_core_ContainerState is an autogenerated conversion function. +func Convert_v1_ContainerState_To_core_ContainerState(in *v1.ContainerState, out *core.ContainerState, s conversion.Scope) error { + return autoConvert_v1_ContainerState_To_core_ContainerState(in, out, s) +} + +func autoConvert_core_ContainerState_To_v1_ContainerState(in *core.ContainerState, out *v1.ContainerState, s conversion.Scope) error { + out.Waiting = (*v1.ContainerStateWaiting)(unsafe.Pointer(in.Waiting)) + out.Running = (*v1.ContainerStateRunning)(unsafe.Pointer(in.Running)) + out.Terminated = (*v1.ContainerStateTerminated)(unsafe.Pointer(in.Terminated)) + return nil +} + +// Convert_core_ContainerState_To_v1_ContainerState is an autogenerated conversion function. +func Convert_core_ContainerState_To_v1_ContainerState(in *core.ContainerState, out *v1.ContainerState, s conversion.Scope) error { + return autoConvert_core_ContainerState_To_v1_ContainerState(in, out, s) +} + +func autoConvert_v1_ContainerStateRunning_To_core_ContainerStateRunning(in *v1.ContainerStateRunning, out *core.ContainerStateRunning, s conversion.Scope) error { + out.StartedAt = in.StartedAt + return nil +} + +// Convert_v1_ContainerStateRunning_To_core_ContainerStateRunning is an autogenerated conversion function. +func Convert_v1_ContainerStateRunning_To_core_ContainerStateRunning(in *v1.ContainerStateRunning, out *core.ContainerStateRunning, s conversion.Scope) error { + return autoConvert_v1_ContainerStateRunning_To_core_ContainerStateRunning(in, out, s) +} + +func autoConvert_core_ContainerStateRunning_To_v1_ContainerStateRunning(in *core.ContainerStateRunning, out *v1.ContainerStateRunning, s conversion.Scope) error { + out.StartedAt = in.StartedAt + return nil +} + +// Convert_core_ContainerStateRunning_To_v1_ContainerStateRunning is an autogenerated conversion function. +func Convert_core_ContainerStateRunning_To_v1_ContainerStateRunning(in *core.ContainerStateRunning, out *v1.ContainerStateRunning, s conversion.Scope) error { + return autoConvert_core_ContainerStateRunning_To_v1_ContainerStateRunning(in, out, s) +} + +func autoConvert_v1_ContainerStateTerminated_To_core_ContainerStateTerminated(in *v1.ContainerStateTerminated, out *core.ContainerStateTerminated, s conversion.Scope) error { + out.ExitCode = in.ExitCode + out.Signal = in.Signal + out.Reason = in.Reason + out.Message = in.Message + out.StartedAt = in.StartedAt + out.FinishedAt = in.FinishedAt + out.ContainerID = in.ContainerID + return nil +} + +// Convert_v1_ContainerStateTerminated_To_core_ContainerStateTerminated is an autogenerated conversion function. +func Convert_v1_ContainerStateTerminated_To_core_ContainerStateTerminated(in *v1.ContainerStateTerminated, out *core.ContainerStateTerminated, s conversion.Scope) error { + return autoConvert_v1_ContainerStateTerminated_To_core_ContainerStateTerminated(in, out, s) +} + +func autoConvert_core_ContainerStateTerminated_To_v1_ContainerStateTerminated(in *core.ContainerStateTerminated, out *v1.ContainerStateTerminated, s conversion.Scope) error { + out.ExitCode = in.ExitCode + out.Signal = in.Signal + out.Reason = in.Reason + out.Message = in.Message + out.StartedAt = in.StartedAt + out.FinishedAt = in.FinishedAt + out.ContainerID = in.ContainerID + return nil +} + +// Convert_core_ContainerStateTerminated_To_v1_ContainerStateTerminated is an autogenerated conversion function. +func Convert_core_ContainerStateTerminated_To_v1_ContainerStateTerminated(in *core.ContainerStateTerminated, out *v1.ContainerStateTerminated, s conversion.Scope) error { + return autoConvert_core_ContainerStateTerminated_To_v1_ContainerStateTerminated(in, out, s) +} + +func autoConvert_v1_ContainerStateWaiting_To_core_ContainerStateWaiting(in *v1.ContainerStateWaiting, out *core.ContainerStateWaiting, s conversion.Scope) error { + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_v1_ContainerStateWaiting_To_core_ContainerStateWaiting is an autogenerated conversion function. +func Convert_v1_ContainerStateWaiting_To_core_ContainerStateWaiting(in *v1.ContainerStateWaiting, out *core.ContainerStateWaiting, s conversion.Scope) error { + return autoConvert_v1_ContainerStateWaiting_To_core_ContainerStateWaiting(in, out, s) +} + +func autoConvert_core_ContainerStateWaiting_To_v1_ContainerStateWaiting(in *core.ContainerStateWaiting, out *v1.ContainerStateWaiting, s conversion.Scope) error { + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_core_ContainerStateWaiting_To_v1_ContainerStateWaiting is an autogenerated conversion function. +func Convert_core_ContainerStateWaiting_To_v1_ContainerStateWaiting(in *core.ContainerStateWaiting, out *v1.ContainerStateWaiting, s conversion.Scope) error { + return autoConvert_core_ContainerStateWaiting_To_v1_ContainerStateWaiting(in, out, s) +} + +func autoConvert_v1_ContainerStatus_To_core_ContainerStatus(in *v1.ContainerStatus, out *core.ContainerStatus, s conversion.Scope) error { + out.Name = in.Name + if err := Convert_v1_ContainerState_To_core_ContainerState(&in.State, &out.State, s); err != nil { + return err + } + if err := Convert_v1_ContainerState_To_core_ContainerState(&in.LastTerminationState, &out.LastTerminationState, s); err != nil { + return err + } + out.Ready = in.Ready + out.RestartCount = in.RestartCount + out.Image = in.Image + out.ImageID = in.ImageID + out.ContainerID = in.ContainerID + return nil +} + +// Convert_v1_ContainerStatus_To_core_ContainerStatus is an autogenerated conversion function. +func Convert_v1_ContainerStatus_To_core_ContainerStatus(in *v1.ContainerStatus, out *core.ContainerStatus, s conversion.Scope) error { + return autoConvert_v1_ContainerStatus_To_core_ContainerStatus(in, out, s) +} + +func autoConvert_core_ContainerStatus_To_v1_ContainerStatus(in *core.ContainerStatus, out *v1.ContainerStatus, s conversion.Scope) error { + out.Name = in.Name + if err := Convert_core_ContainerState_To_v1_ContainerState(&in.State, &out.State, s); err != nil { + return err + } + if err := Convert_core_ContainerState_To_v1_ContainerState(&in.LastTerminationState, &out.LastTerminationState, s); err != nil { + return err + } + out.Ready = in.Ready + out.RestartCount = in.RestartCount + out.Image = in.Image + out.ImageID = in.ImageID + out.ContainerID = in.ContainerID + return nil +} + +// Convert_core_ContainerStatus_To_v1_ContainerStatus is an autogenerated conversion function. +func Convert_core_ContainerStatus_To_v1_ContainerStatus(in *core.ContainerStatus, out *v1.ContainerStatus, s conversion.Scope) error { + return autoConvert_core_ContainerStatus_To_v1_ContainerStatus(in, out, s) +} + +func autoConvert_v1_DaemonEndpoint_To_core_DaemonEndpoint(in *v1.DaemonEndpoint, out *core.DaemonEndpoint, s conversion.Scope) error { + out.Port = in.Port + return nil +} + +// Convert_v1_DaemonEndpoint_To_core_DaemonEndpoint is an autogenerated conversion function. +func Convert_v1_DaemonEndpoint_To_core_DaemonEndpoint(in *v1.DaemonEndpoint, out *core.DaemonEndpoint, s conversion.Scope) error { + return autoConvert_v1_DaemonEndpoint_To_core_DaemonEndpoint(in, out, s) +} + +func autoConvert_core_DaemonEndpoint_To_v1_DaemonEndpoint(in *core.DaemonEndpoint, out *v1.DaemonEndpoint, s conversion.Scope) error { + out.Port = in.Port + return nil +} + +// Convert_core_DaemonEndpoint_To_v1_DaemonEndpoint is an autogenerated conversion function. +func Convert_core_DaemonEndpoint_To_v1_DaemonEndpoint(in *core.DaemonEndpoint, out *v1.DaemonEndpoint, s conversion.Scope) error { + return autoConvert_core_DaemonEndpoint_To_v1_DaemonEndpoint(in, out, s) +} + +func autoConvert_v1_DeleteOptions_To_core_DeleteOptions(in *v1.DeleteOptions, out *core.DeleteOptions, s conversion.Scope) error { + out.GracePeriodSeconds = (*int64)(unsafe.Pointer(in.GracePeriodSeconds)) + out.Preconditions = (*core.Preconditions)(unsafe.Pointer(in.Preconditions)) + out.OrphanDependents = (*bool)(unsafe.Pointer(in.OrphanDependents)) + out.PropagationPolicy = (*core.DeletionPropagation)(unsafe.Pointer(in.PropagationPolicy)) + return nil +} + +// Convert_v1_DeleteOptions_To_core_DeleteOptions is an autogenerated conversion function. +func Convert_v1_DeleteOptions_To_core_DeleteOptions(in *v1.DeleteOptions, out *core.DeleteOptions, s conversion.Scope) error { + return autoConvert_v1_DeleteOptions_To_core_DeleteOptions(in, out, s) +} + +func autoConvert_core_DeleteOptions_To_v1_DeleteOptions(in *core.DeleteOptions, out *v1.DeleteOptions, s conversion.Scope) error { + out.GracePeriodSeconds = (*int64)(unsafe.Pointer(in.GracePeriodSeconds)) + out.Preconditions = (*v1.Preconditions)(unsafe.Pointer(in.Preconditions)) + out.OrphanDependents = (*bool)(unsafe.Pointer(in.OrphanDependents)) + out.PropagationPolicy = (*v1.DeletionPropagation)(unsafe.Pointer(in.PropagationPolicy)) + return nil +} + +// Convert_core_DeleteOptions_To_v1_DeleteOptions is an autogenerated conversion function. +func Convert_core_DeleteOptions_To_v1_DeleteOptions(in *core.DeleteOptions, out *v1.DeleteOptions, s conversion.Scope) error { + return autoConvert_core_DeleteOptions_To_v1_DeleteOptions(in, out, s) +} + +func autoConvert_v1_DownwardAPIProjection_To_core_DownwardAPIProjection(in *v1.DownwardAPIProjection, out *core.DownwardAPIProjection, s conversion.Scope) error { + out.Items = *(*[]core.DownwardAPIVolumeFile)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1_DownwardAPIProjection_To_core_DownwardAPIProjection is an autogenerated conversion function. +func Convert_v1_DownwardAPIProjection_To_core_DownwardAPIProjection(in *v1.DownwardAPIProjection, out *core.DownwardAPIProjection, s conversion.Scope) error { + return autoConvert_v1_DownwardAPIProjection_To_core_DownwardAPIProjection(in, out, s) +} + +func autoConvert_core_DownwardAPIProjection_To_v1_DownwardAPIProjection(in *core.DownwardAPIProjection, out *v1.DownwardAPIProjection, s conversion.Scope) error { + out.Items = *(*[]v1.DownwardAPIVolumeFile)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_core_DownwardAPIProjection_To_v1_DownwardAPIProjection is an autogenerated conversion function. +func Convert_core_DownwardAPIProjection_To_v1_DownwardAPIProjection(in *core.DownwardAPIProjection, out *v1.DownwardAPIProjection, s conversion.Scope) error { + return autoConvert_core_DownwardAPIProjection_To_v1_DownwardAPIProjection(in, out, s) +} + +func autoConvert_v1_DownwardAPIVolumeFile_To_core_DownwardAPIVolumeFile(in *v1.DownwardAPIVolumeFile, out *core.DownwardAPIVolumeFile, s conversion.Scope) error { + out.Path = in.Path + out.FieldRef = (*core.ObjectFieldSelector)(unsafe.Pointer(in.FieldRef)) + out.ResourceFieldRef = (*core.ResourceFieldSelector)(unsafe.Pointer(in.ResourceFieldRef)) + out.Mode = (*int32)(unsafe.Pointer(in.Mode)) + return nil +} + +// Convert_v1_DownwardAPIVolumeFile_To_core_DownwardAPIVolumeFile is an autogenerated conversion function. +func Convert_v1_DownwardAPIVolumeFile_To_core_DownwardAPIVolumeFile(in *v1.DownwardAPIVolumeFile, out *core.DownwardAPIVolumeFile, s conversion.Scope) error { + return autoConvert_v1_DownwardAPIVolumeFile_To_core_DownwardAPIVolumeFile(in, out, s) +} + +func autoConvert_core_DownwardAPIVolumeFile_To_v1_DownwardAPIVolumeFile(in *core.DownwardAPIVolumeFile, out *v1.DownwardAPIVolumeFile, s conversion.Scope) error { + out.Path = in.Path + out.FieldRef = (*v1.ObjectFieldSelector)(unsafe.Pointer(in.FieldRef)) + out.ResourceFieldRef = (*v1.ResourceFieldSelector)(unsafe.Pointer(in.ResourceFieldRef)) + out.Mode = (*int32)(unsafe.Pointer(in.Mode)) + return nil +} + +// Convert_core_DownwardAPIVolumeFile_To_v1_DownwardAPIVolumeFile is an autogenerated conversion function. +func Convert_core_DownwardAPIVolumeFile_To_v1_DownwardAPIVolumeFile(in *core.DownwardAPIVolumeFile, out *v1.DownwardAPIVolumeFile, s conversion.Scope) error { + return autoConvert_core_DownwardAPIVolumeFile_To_v1_DownwardAPIVolumeFile(in, out, s) +} + +func autoConvert_v1_DownwardAPIVolumeSource_To_core_DownwardAPIVolumeSource(in *v1.DownwardAPIVolumeSource, out *core.DownwardAPIVolumeSource, s conversion.Scope) error { + out.Items = *(*[]core.DownwardAPIVolumeFile)(unsafe.Pointer(&in.Items)) + out.DefaultMode = (*int32)(unsafe.Pointer(in.DefaultMode)) + return nil +} + +// Convert_v1_DownwardAPIVolumeSource_To_core_DownwardAPIVolumeSource is an autogenerated conversion function. +func Convert_v1_DownwardAPIVolumeSource_To_core_DownwardAPIVolumeSource(in *v1.DownwardAPIVolumeSource, out *core.DownwardAPIVolumeSource, s conversion.Scope) error { + return autoConvert_v1_DownwardAPIVolumeSource_To_core_DownwardAPIVolumeSource(in, out, s) +} + +func autoConvert_core_DownwardAPIVolumeSource_To_v1_DownwardAPIVolumeSource(in *core.DownwardAPIVolumeSource, out *v1.DownwardAPIVolumeSource, s conversion.Scope) error { + out.Items = *(*[]v1.DownwardAPIVolumeFile)(unsafe.Pointer(&in.Items)) + out.DefaultMode = (*int32)(unsafe.Pointer(in.DefaultMode)) + return nil +} + +// Convert_core_DownwardAPIVolumeSource_To_v1_DownwardAPIVolumeSource is an autogenerated conversion function. +func Convert_core_DownwardAPIVolumeSource_To_v1_DownwardAPIVolumeSource(in *core.DownwardAPIVolumeSource, out *v1.DownwardAPIVolumeSource, s conversion.Scope) error { + return autoConvert_core_DownwardAPIVolumeSource_To_v1_DownwardAPIVolumeSource(in, out, s) +} + +func autoConvert_v1_EmptyDirVolumeSource_To_core_EmptyDirVolumeSource(in *v1.EmptyDirVolumeSource, out *core.EmptyDirVolumeSource, s conversion.Scope) error { + out.Medium = core.StorageMedium(in.Medium) + out.SizeLimit = (*resource.Quantity)(unsafe.Pointer(in.SizeLimit)) + return nil +} + +// Convert_v1_EmptyDirVolumeSource_To_core_EmptyDirVolumeSource is an autogenerated conversion function. +func Convert_v1_EmptyDirVolumeSource_To_core_EmptyDirVolumeSource(in *v1.EmptyDirVolumeSource, out *core.EmptyDirVolumeSource, s conversion.Scope) error { + return autoConvert_v1_EmptyDirVolumeSource_To_core_EmptyDirVolumeSource(in, out, s) +} + +func autoConvert_core_EmptyDirVolumeSource_To_v1_EmptyDirVolumeSource(in *core.EmptyDirVolumeSource, out *v1.EmptyDirVolumeSource, s conversion.Scope) error { + out.Medium = v1.StorageMedium(in.Medium) + out.SizeLimit = (*resource.Quantity)(unsafe.Pointer(in.SizeLimit)) + return nil +} + +// Convert_core_EmptyDirVolumeSource_To_v1_EmptyDirVolumeSource is an autogenerated conversion function. +func Convert_core_EmptyDirVolumeSource_To_v1_EmptyDirVolumeSource(in *core.EmptyDirVolumeSource, out *v1.EmptyDirVolumeSource, s conversion.Scope) error { + return autoConvert_core_EmptyDirVolumeSource_To_v1_EmptyDirVolumeSource(in, out, s) +} + +func autoConvert_v1_EndpointAddress_To_core_EndpointAddress(in *v1.EndpointAddress, out *core.EndpointAddress, s conversion.Scope) error { + out.IP = in.IP + out.Hostname = in.Hostname + out.NodeName = (*string)(unsafe.Pointer(in.NodeName)) + out.TargetRef = (*core.ObjectReference)(unsafe.Pointer(in.TargetRef)) + return nil +} + +// Convert_v1_EndpointAddress_To_core_EndpointAddress is an autogenerated conversion function. +func Convert_v1_EndpointAddress_To_core_EndpointAddress(in *v1.EndpointAddress, out *core.EndpointAddress, s conversion.Scope) error { + return autoConvert_v1_EndpointAddress_To_core_EndpointAddress(in, out, s) +} + +func autoConvert_core_EndpointAddress_To_v1_EndpointAddress(in *core.EndpointAddress, out *v1.EndpointAddress, s conversion.Scope) error { + out.IP = in.IP + out.Hostname = in.Hostname + out.NodeName = (*string)(unsafe.Pointer(in.NodeName)) + out.TargetRef = (*v1.ObjectReference)(unsafe.Pointer(in.TargetRef)) + return nil +} + +// Convert_core_EndpointAddress_To_v1_EndpointAddress is an autogenerated conversion function. +func Convert_core_EndpointAddress_To_v1_EndpointAddress(in *core.EndpointAddress, out *v1.EndpointAddress, s conversion.Scope) error { + return autoConvert_core_EndpointAddress_To_v1_EndpointAddress(in, out, s) +} + +func autoConvert_v1_EndpointPort_To_core_EndpointPort(in *v1.EndpointPort, out *core.EndpointPort, s conversion.Scope) error { + out.Name = in.Name + out.Port = in.Port + out.Protocol = core.Protocol(in.Protocol) + return nil +} + +// Convert_v1_EndpointPort_To_core_EndpointPort is an autogenerated conversion function. +func Convert_v1_EndpointPort_To_core_EndpointPort(in *v1.EndpointPort, out *core.EndpointPort, s conversion.Scope) error { + return autoConvert_v1_EndpointPort_To_core_EndpointPort(in, out, s) +} + +func autoConvert_core_EndpointPort_To_v1_EndpointPort(in *core.EndpointPort, out *v1.EndpointPort, s conversion.Scope) error { + out.Name = in.Name + out.Port = in.Port + out.Protocol = v1.Protocol(in.Protocol) + return nil +} + +// Convert_core_EndpointPort_To_v1_EndpointPort is an autogenerated conversion function. +func Convert_core_EndpointPort_To_v1_EndpointPort(in *core.EndpointPort, out *v1.EndpointPort, s conversion.Scope) error { + return autoConvert_core_EndpointPort_To_v1_EndpointPort(in, out, s) +} + +func autoConvert_v1_EndpointSubset_To_core_EndpointSubset(in *v1.EndpointSubset, out *core.EndpointSubset, s conversion.Scope) error { + out.Addresses = *(*[]core.EndpointAddress)(unsafe.Pointer(&in.Addresses)) + out.NotReadyAddresses = *(*[]core.EndpointAddress)(unsafe.Pointer(&in.NotReadyAddresses)) + out.Ports = *(*[]core.EndpointPort)(unsafe.Pointer(&in.Ports)) + return nil +} + +// Convert_v1_EndpointSubset_To_core_EndpointSubset is an autogenerated conversion function. +func Convert_v1_EndpointSubset_To_core_EndpointSubset(in *v1.EndpointSubset, out *core.EndpointSubset, s conversion.Scope) error { + return autoConvert_v1_EndpointSubset_To_core_EndpointSubset(in, out, s) +} + +func autoConvert_core_EndpointSubset_To_v1_EndpointSubset(in *core.EndpointSubset, out *v1.EndpointSubset, s conversion.Scope) error { + out.Addresses = *(*[]v1.EndpointAddress)(unsafe.Pointer(&in.Addresses)) + out.NotReadyAddresses = *(*[]v1.EndpointAddress)(unsafe.Pointer(&in.NotReadyAddresses)) + out.Ports = *(*[]v1.EndpointPort)(unsafe.Pointer(&in.Ports)) + return nil +} + +// Convert_core_EndpointSubset_To_v1_EndpointSubset is an autogenerated conversion function. +func Convert_core_EndpointSubset_To_v1_EndpointSubset(in *core.EndpointSubset, out *v1.EndpointSubset, s conversion.Scope) error { + return autoConvert_core_EndpointSubset_To_v1_EndpointSubset(in, out, s) +} + +func autoConvert_v1_Endpoints_To_core_Endpoints(in *v1.Endpoints, out *core.Endpoints, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Subsets = *(*[]core.EndpointSubset)(unsafe.Pointer(&in.Subsets)) + return nil +} + +// Convert_v1_Endpoints_To_core_Endpoints is an autogenerated conversion function. +func Convert_v1_Endpoints_To_core_Endpoints(in *v1.Endpoints, out *core.Endpoints, s conversion.Scope) error { + return autoConvert_v1_Endpoints_To_core_Endpoints(in, out, s) +} + +func autoConvert_core_Endpoints_To_v1_Endpoints(in *core.Endpoints, out *v1.Endpoints, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Subsets = *(*[]v1.EndpointSubset)(unsafe.Pointer(&in.Subsets)) + return nil +} + +// Convert_core_Endpoints_To_v1_Endpoints is an autogenerated conversion function. +func Convert_core_Endpoints_To_v1_Endpoints(in *core.Endpoints, out *v1.Endpoints, s conversion.Scope) error { + return autoConvert_core_Endpoints_To_v1_Endpoints(in, out, s) +} + +func autoConvert_v1_EndpointsList_To_core_EndpointsList(in *v1.EndpointsList, out *core.EndpointsList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]core.Endpoints)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1_EndpointsList_To_core_EndpointsList is an autogenerated conversion function. +func Convert_v1_EndpointsList_To_core_EndpointsList(in *v1.EndpointsList, out *core.EndpointsList, s conversion.Scope) error { + return autoConvert_v1_EndpointsList_To_core_EndpointsList(in, out, s) +} + +func autoConvert_core_EndpointsList_To_v1_EndpointsList(in *core.EndpointsList, out *v1.EndpointsList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]v1.Endpoints)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_core_EndpointsList_To_v1_EndpointsList is an autogenerated conversion function. +func Convert_core_EndpointsList_To_v1_EndpointsList(in *core.EndpointsList, out *v1.EndpointsList, s conversion.Scope) error { + return autoConvert_core_EndpointsList_To_v1_EndpointsList(in, out, s) +} + +func autoConvert_v1_EnvFromSource_To_core_EnvFromSource(in *v1.EnvFromSource, out *core.EnvFromSource, s conversion.Scope) error { + out.Prefix = in.Prefix + out.ConfigMapRef = (*core.ConfigMapEnvSource)(unsafe.Pointer(in.ConfigMapRef)) + out.SecretRef = (*core.SecretEnvSource)(unsafe.Pointer(in.SecretRef)) + return nil +} + +// Convert_v1_EnvFromSource_To_core_EnvFromSource is an autogenerated conversion function. +func Convert_v1_EnvFromSource_To_core_EnvFromSource(in *v1.EnvFromSource, out *core.EnvFromSource, s conversion.Scope) error { + return autoConvert_v1_EnvFromSource_To_core_EnvFromSource(in, out, s) +} + +func autoConvert_core_EnvFromSource_To_v1_EnvFromSource(in *core.EnvFromSource, out *v1.EnvFromSource, s conversion.Scope) error { + out.Prefix = in.Prefix + out.ConfigMapRef = (*v1.ConfigMapEnvSource)(unsafe.Pointer(in.ConfigMapRef)) + out.SecretRef = (*v1.SecretEnvSource)(unsafe.Pointer(in.SecretRef)) + return nil +} + +// Convert_core_EnvFromSource_To_v1_EnvFromSource is an autogenerated conversion function. +func Convert_core_EnvFromSource_To_v1_EnvFromSource(in *core.EnvFromSource, out *v1.EnvFromSource, s conversion.Scope) error { + return autoConvert_core_EnvFromSource_To_v1_EnvFromSource(in, out, s) +} + +func autoConvert_v1_EnvVar_To_core_EnvVar(in *v1.EnvVar, out *core.EnvVar, s conversion.Scope) error { + out.Name = in.Name + out.Value = in.Value + out.ValueFrom = (*core.EnvVarSource)(unsafe.Pointer(in.ValueFrom)) + return nil +} + +// Convert_v1_EnvVar_To_core_EnvVar is an autogenerated conversion function. +func Convert_v1_EnvVar_To_core_EnvVar(in *v1.EnvVar, out *core.EnvVar, s conversion.Scope) error { + return autoConvert_v1_EnvVar_To_core_EnvVar(in, out, s) +} + +func autoConvert_core_EnvVar_To_v1_EnvVar(in *core.EnvVar, out *v1.EnvVar, s conversion.Scope) error { + out.Name = in.Name + out.Value = in.Value + out.ValueFrom = (*v1.EnvVarSource)(unsafe.Pointer(in.ValueFrom)) + return nil +} + +// Convert_core_EnvVar_To_v1_EnvVar is an autogenerated conversion function. +func Convert_core_EnvVar_To_v1_EnvVar(in *core.EnvVar, out *v1.EnvVar, s conversion.Scope) error { + return autoConvert_core_EnvVar_To_v1_EnvVar(in, out, s) +} + +func autoConvert_v1_EnvVarSource_To_core_EnvVarSource(in *v1.EnvVarSource, out *core.EnvVarSource, s conversion.Scope) error { + out.FieldRef = (*core.ObjectFieldSelector)(unsafe.Pointer(in.FieldRef)) + out.ResourceFieldRef = (*core.ResourceFieldSelector)(unsafe.Pointer(in.ResourceFieldRef)) + out.ConfigMapKeyRef = (*core.ConfigMapKeySelector)(unsafe.Pointer(in.ConfigMapKeyRef)) + out.SecretKeyRef = (*core.SecretKeySelector)(unsafe.Pointer(in.SecretKeyRef)) + return nil +} + +// Convert_v1_EnvVarSource_To_core_EnvVarSource is an autogenerated conversion function. +func Convert_v1_EnvVarSource_To_core_EnvVarSource(in *v1.EnvVarSource, out *core.EnvVarSource, s conversion.Scope) error { + return autoConvert_v1_EnvVarSource_To_core_EnvVarSource(in, out, s) +} + +func autoConvert_core_EnvVarSource_To_v1_EnvVarSource(in *core.EnvVarSource, out *v1.EnvVarSource, s conversion.Scope) error { + out.FieldRef = (*v1.ObjectFieldSelector)(unsafe.Pointer(in.FieldRef)) + out.ResourceFieldRef = (*v1.ResourceFieldSelector)(unsafe.Pointer(in.ResourceFieldRef)) + out.ConfigMapKeyRef = (*v1.ConfigMapKeySelector)(unsafe.Pointer(in.ConfigMapKeyRef)) + out.SecretKeyRef = (*v1.SecretKeySelector)(unsafe.Pointer(in.SecretKeyRef)) + return nil +} + +// Convert_core_EnvVarSource_To_v1_EnvVarSource is an autogenerated conversion function. +func Convert_core_EnvVarSource_To_v1_EnvVarSource(in *core.EnvVarSource, out *v1.EnvVarSource, s conversion.Scope) error { + return autoConvert_core_EnvVarSource_To_v1_EnvVarSource(in, out, s) +} + +func autoConvert_v1_Event_To_core_Event(in *v1.Event, out *core.Event, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_ObjectReference_To_core_ObjectReference(&in.InvolvedObject, &out.InvolvedObject, s); err != nil { + return err + } + out.Reason = in.Reason + out.Message = in.Message + if err := Convert_v1_EventSource_To_core_EventSource(&in.Source, &out.Source, s); err != nil { + return err + } + out.FirstTimestamp = in.FirstTimestamp + out.LastTimestamp = in.LastTimestamp + out.Count = in.Count + out.Type = in.Type + out.EventTime = in.EventTime + out.Series = (*core.EventSeries)(unsafe.Pointer(in.Series)) + out.Action = in.Action + out.Related = (*core.ObjectReference)(unsafe.Pointer(in.Related)) + out.ReportingController = in.ReportingController + out.ReportingInstance = in.ReportingInstance + return nil +} + +// Convert_v1_Event_To_core_Event is an autogenerated conversion function. +func Convert_v1_Event_To_core_Event(in *v1.Event, out *core.Event, s conversion.Scope) error { + return autoConvert_v1_Event_To_core_Event(in, out, s) +} + +func autoConvert_core_Event_To_v1_Event(in *core.Event, out *v1.Event, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_core_ObjectReference_To_v1_ObjectReference(&in.InvolvedObject, &out.InvolvedObject, s); err != nil { + return err + } + out.Reason = in.Reason + out.Message = in.Message + if err := Convert_core_EventSource_To_v1_EventSource(&in.Source, &out.Source, s); err != nil { + return err + } + out.FirstTimestamp = in.FirstTimestamp + out.LastTimestamp = in.LastTimestamp + out.Count = in.Count + out.Type = in.Type + out.EventTime = in.EventTime + out.Series = (*v1.EventSeries)(unsafe.Pointer(in.Series)) + out.Action = in.Action + out.Related = (*v1.ObjectReference)(unsafe.Pointer(in.Related)) + out.ReportingController = in.ReportingController + out.ReportingInstance = in.ReportingInstance + return nil +} + +// Convert_core_Event_To_v1_Event is an autogenerated conversion function. +func Convert_core_Event_To_v1_Event(in *core.Event, out *v1.Event, s conversion.Scope) error { + return autoConvert_core_Event_To_v1_Event(in, out, s) +} + +func autoConvert_v1_EventList_To_core_EventList(in *v1.EventList, out *core.EventList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]core.Event)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1_EventList_To_core_EventList is an autogenerated conversion function. +func Convert_v1_EventList_To_core_EventList(in *v1.EventList, out *core.EventList, s conversion.Scope) error { + return autoConvert_v1_EventList_To_core_EventList(in, out, s) +} + +func autoConvert_core_EventList_To_v1_EventList(in *core.EventList, out *v1.EventList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]v1.Event)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_core_EventList_To_v1_EventList is an autogenerated conversion function. +func Convert_core_EventList_To_v1_EventList(in *core.EventList, out *v1.EventList, s conversion.Scope) error { + return autoConvert_core_EventList_To_v1_EventList(in, out, s) +} + +func autoConvert_v1_EventSeries_To_core_EventSeries(in *v1.EventSeries, out *core.EventSeries, s conversion.Scope) error { + out.Count = in.Count + out.LastObservedTime = in.LastObservedTime + out.State = core.EventSeriesState(in.State) + return nil +} + +// Convert_v1_EventSeries_To_core_EventSeries is an autogenerated conversion function. +func Convert_v1_EventSeries_To_core_EventSeries(in *v1.EventSeries, out *core.EventSeries, s conversion.Scope) error { + return autoConvert_v1_EventSeries_To_core_EventSeries(in, out, s) +} + +func autoConvert_core_EventSeries_To_v1_EventSeries(in *core.EventSeries, out *v1.EventSeries, s conversion.Scope) error { + out.Count = in.Count + out.LastObservedTime = in.LastObservedTime + out.State = v1.EventSeriesState(in.State) + return nil +} + +// Convert_core_EventSeries_To_v1_EventSeries is an autogenerated conversion function. +func Convert_core_EventSeries_To_v1_EventSeries(in *core.EventSeries, out *v1.EventSeries, s conversion.Scope) error { + return autoConvert_core_EventSeries_To_v1_EventSeries(in, out, s) +} + +func autoConvert_v1_EventSource_To_core_EventSource(in *v1.EventSource, out *core.EventSource, s conversion.Scope) error { + out.Component = in.Component + out.Host = in.Host + return nil +} + +// Convert_v1_EventSource_To_core_EventSource is an autogenerated conversion function. +func Convert_v1_EventSource_To_core_EventSource(in *v1.EventSource, out *core.EventSource, s conversion.Scope) error { + return autoConvert_v1_EventSource_To_core_EventSource(in, out, s) +} + +func autoConvert_core_EventSource_To_v1_EventSource(in *core.EventSource, out *v1.EventSource, s conversion.Scope) error { + out.Component = in.Component + out.Host = in.Host + return nil +} + +// Convert_core_EventSource_To_v1_EventSource is an autogenerated conversion function. +func Convert_core_EventSource_To_v1_EventSource(in *core.EventSource, out *v1.EventSource, s conversion.Scope) error { + return autoConvert_core_EventSource_To_v1_EventSource(in, out, s) +} + +func autoConvert_v1_ExecAction_To_core_ExecAction(in *v1.ExecAction, out *core.ExecAction, s conversion.Scope) error { + out.Command = *(*[]string)(unsafe.Pointer(&in.Command)) + return nil +} + +// Convert_v1_ExecAction_To_core_ExecAction is an autogenerated conversion function. +func Convert_v1_ExecAction_To_core_ExecAction(in *v1.ExecAction, out *core.ExecAction, s conversion.Scope) error { + return autoConvert_v1_ExecAction_To_core_ExecAction(in, out, s) +} + +func autoConvert_core_ExecAction_To_v1_ExecAction(in *core.ExecAction, out *v1.ExecAction, s conversion.Scope) error { + out.Command = *(*[]string)(unsafe.Pointer(&in.Command)) + return nil +} + +// Convert_core_ExecAction_To_v1_ExecAction is an autogenerated conversion function. +func Convert_core_ExecAction_To_v1_ExecAction(in *core.ExecAction, out *v1.ExecAction, s conversion.Scope) error { + return autoConvert_core_ExecAction_To_v1_ExecAction(in, out, s) +} + +func autoConvert_v1_FCVolumeSource_To_core_FCVolumeSource(in *v1.FCVolumeSource, out *core.FCVolumeSource, s conversion.Scope) error { + out.TargetWWNs = *(*[]string)(unsafe.Pointer(&in.TargetWWNs)) + out.Lun = (*int32)(unsafe.Pointer(in.Lun)) + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + out.WWIDs = *(*[]string)(unsafe.Pointer(&in.WWIDs)) + return nil +} + +// Convert_v1_FCVolumeSource_To_core_FCVolumeSource is an autogenerated conversion function. +func Convert_v1_FCVolumeSource_To_core_FCVolumeSource(in *v1.FCVolumeSource, out *core.FCVolumeSource, s conversion.Scope) error { + return autoConvert_v1_FCVolumeSource_To_core_FCVolumeSource(in, out, s) +} + +func autoConvert_core_FCVolumeSource_To_v1_FCVolumeSource(in *core.FCVolumeSource, out *v1.FCVolumeSource, s conversion.Scope) error { + out.TargetWWNs = *(*[]string)(unsafe.Pointer(&in.TargetWWNs)) + out.Lun = (*int32)(unsafe.Pointer(in.Lun)) + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + out.WWIDs = *(*[]string)(unsafe.Pointer(&in.WWIDs)) + return nil +} + +// Convert_core_FCVolumeSource_To_v1_FCVolumeSource is an autogenerated conversion function. +func Convert_core_FCVolumeSource_To_v1_FCVolumeSource(in *core.FCVolumeSource, out *v1.FCVolumeSource, s conversion.Scope) error { + return autoConvert_core_FCVolumeSource_To_v1_FCVolumeSource(in, out, s) +} + +func autoConvert_v1_FlexPersistentVolumeSource_To_core_FlexPersistentVolumeSource(in *v1.FlexPersistentVolumeSource, out *core.FlexPersistentVolumeSource, s conversion.Scope) error { + out.Driver = in.Driver + out.FSType = in.FSType + out.SecretRef = (*core.SecretReference)(unsafe.Pointer(in.SecretRef)) + out.ReadOnly = in.ReadOnly + out.Options = *(*map[string]string)(unsafe.Pointer(&in.Options)) + return nil +} + +// Convert_v1_FlexPersistentVolumeSource_To_core_FlexPersistentVolumeSource is an autogenerated conversion function. +func Convert_v1_FlexPersistentVolumeSource_To_core_FlexPersistentVolumeSource(in *v1.FlexPersistentVolumeSource, out *core.FlexPersistentVolumeSource, s conversion.Scope) error { + return autoConvert_v1_FlexPersistentVolumeSource_To_core_FlexPersistentVolumeSource(in, out, s) +} + +func autoConvert_core_FlexPersistentVolumeSource_To_v1_FlexPersistentVolumeSource(in *core.FlexPersistentVolumeSource, out *v1.FlexPersistentVolumeSource, s conversion.Scope) error { + out.Driver = in.Driver + out.FSType = in.FSType + out.SecretRef = (*v1.SecretReference)(unsafe.Pointer(in.SecretRef)) + out.ReadOnly = in.ReadOnly + out.Options = *(*map[string]string)(unsafe.Pointer(&in.Options)) + return nil +} + +// Convert_core_FlexPersistentVolumeSource_To_v1_FlexPersistentVolumeSource is an autogenerated conversion function. +func Convert_core_FlexPersistentVolumeSource_To_v1_FlexPersistentVolumeSource(in *core.FlexPersistentVolumeSource, out *v1.FlexPersistentVolumeSource, s conversion.Scope) error { + return autoConvert_core_FlexPersistentVolumeSource_To_v1_FlexPersistentVolumeSource(in, out, s) +} + +func autoConvert_v1_FlexVolumeSource_To_core_FlexVolumeSource(in *v1.FlexVolumeSource, out *core.FlexVolumeSource, s conversion.Scope) error { + out.Driver = in.Driver + out.FSType = in.FSType + out.SecretRef = (*core.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) + out.ReadOnly = in.ReadOnly + out.Options = *(*map[string]string)(unsafe.Pointer(&in.Options)) + return nil +} + +// Convert_v1_FlexVolumeSource_To_core_FlexVolumeSource is an autogenerated conversion function. +func Convert_v1_FlexVolumeSource_To_core_FlexVolumeSource(in *v1.FlexVolumeSource, out *core.FlexVolumeSource, s conversion.Scope) error { + return autoConvert_v1_FlexVolumeSource_To_core_FlexVolumeSource(in, out, s) +} + +func autoConvert_core_FlexVolumeSource_To_v1_FlexVolumeSource(in *core.FlexVolumeSource, out *v1.FlexVolumeSource, s conversion.Scope) error { + out.Driver = in.Driver + out.FSType = in.FSType + out.SecretRef = (*v1.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) + out.ReadOnly = in.ReadOnly + out.Options = *(*map[string]string)(unsafe.Pointer(&in.Options)) + return nil +} + +// Convert_core_FlexVolumeSource_To_v1_FlexVolumeSource is an autogenerated conversion function. +func Convert_core_FlexVolumeSource_To_v1_FlexVolumeSource(in *core.FlexVolumeSource, out *v1.FlexVolumeSource, s conversion.Scope) error { + return autoConvert_core_FlexVolumeSource_To_v1_FlexVolumeSource(in, out, s) +} + +func autoConvert_v1_FlockerVolumeSource_To_core_FlockerVolumeSource(in *v1.FlockerVolumeSource, out *core.FlockerVolumeSource, s conversion.Scope) error { + out.DatasetName = in.DatasetName + out.DatasetUUID = in.DatasetUUID + return nil +} + +// Convert_v1_FlockerVolumeSource_To_core_FlockerVolumeSource is an autogenerated conversion function. +func Convert_v1_FlockerVolumeSource_To_core_FlockerVolumeSource(in *v1.FlockerVolumeSource, out *core.FlockerVolumeSource, s conversion.Scope) error { + return autoConvert_v1_FlockerVolumeSource_To_core_FlockerVolumeSource(in, out, s) +} + +func autoConvert_core_FlockerVolumeSource_To_v1_FlockerVolumeSource(in *core.FlockerVolumeSource, out *v1.FlockerVolumeSource, s conversion.Scope) error { + out.DatasetName = in.DatasetName + out.DatasetUUID = in.DatasetUUID + return nil +} + +// Convert_core_FlockerVolumeSource_To_v1_FlockerVolumeSource is an autogenerated conversion function. +func Convert_core_FlockerVolumeSource_To_v1_FlockerVolumeSource(in *core.FlockerVolumeSource, out *v1.FlockerVolumeSource, s conversion.Scope) error { + return autoConvert_core_FlockerVolumeSource_To_v1_FlockerVolumeSource(in, out, s) +} + +func autoConvert_v1_GCEPersistentDiskVolumeSource_To_core_GCEPersistentDiskVolumeSource(in *v1.GCEPersistentDiskVolumeSource, out *core.GCEPersistentDiskVolumeSource, s conversion.Scope) error { + out.PDName = in.PDName + out.FSType = in.FSType + out.Partition = in.Partition + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_v1_GCEPersistentDiskVolumeSource_To_core_GCEPersistentDiskVolumeSource is an autogenerated conversion function. +func Convert_v1_GCEPersistentDiskVolumeSource_To_core_GCEPersistentDiskVolumeSource(in *v1.GCEPersistentDiskVolumeSource, out *core.GCEPersistentDiskVolumeSource, s conversion.Scope) error { + return autoConvert_v1_GCEPersistentDiskVolumeSource_To_core_GCEPersistentDiskVolumeSource(in, out, s) +} + +func autoConvert_core_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource(in *core.GCEPersistentDiskVolumeSource, out *v1.GCEPersistentDiskVolumeSource, s conversion.Scope) error { + out.PDName = in.PDName + out.FSType = in.FSType + out.Partition = in.Partition + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_core_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource is an autogenerated conversion function. +func Convert_core_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource(in *core.GCEPersistentDiskVolumeSource, out *v1.GCEPersistentDiskVolumeSource, s conversion.Scope) error { + return autoConvert_core_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource(in, out, s) +} + +func autoConvert_v1_GitRepoVolumeSource_To_core_GitRepoVolumeSource(in *v1.GitRepoVolumeSource, out *core.GitRepoVolumeSource, s conversion.Scope) error { + out.Repository = in.Repository + out.Revision = in.Revision + out.Directory = in.Directory + return nil +} + +// Convert_v1_GitRepoVolumeSource_To_core_GitRepoVolumeSource is an autogenerated conversion function. +func Convert_v1_GitRepoVolumeSource_To_core_GitRepoVolumeSource(in *v1.GitRepoVolumeSource, out *core.GitRepoVolumeSource, s conversion.Scope) error { + return autoConvert_v1_GitRepoVolumeSource_To_core_GitRepoVolumeSource(in, out, s) +} + +func autoConvert_core_GitRepoVolumeSource_To_v1_GitRepoVolumeSource(in *core.GitRepoVolumeSource, out *v1.GitRepoVolumeSource, s conversion.Scope) error { + out.Repository = in.Repository + out.Revision = in.Revision + out.Directory = in.Directory + return nil +} + +// Convert_core_GitRepoVolumeSource_To_v1_GitRepoVolumeSource is an autogenerated conversion function. +func Convert_core_GitRepoVolumeSource_To_v1_GitRepoVolumeSource(in *core.GitRepoVolumeSource, out *v1.GitRepoVolumeSource, s conversion.Scope) error { + return autoConvert_core_GitRepoVolumeSource_To_v1_GitRepoVolumeSource(in, out, s) +} + +func autoConvert_v1_GlusterfsVolumeSource_To_core_GlusterfsVolumeSource(in *v1.GlusterfsVolumeSource, out *core.GlusterfsVolumeSource, s conversion.Scope) error { + out.EndpointsName = in.EndpointsName + out.Path = in.Path + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_v1_GlusterfsVolumeSource_To_core_GlusterfsVolumeSource is an autogenerated conversion function. +func Convert_v1_GlusterfsVolumeSource_To_core_GlusterfsVolumeSource(in *v1.GlusterfsVolumeSource, out *core.GlusterfsVolumeSource, s conversion.Scope) error { + return autoConvert_v1_GlusterfsVolumeSource_To_core_GlusterfsVolumeSource(in, out, s) +} + +func autoConvert_core_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource(in *core.GlusterfsVolumeSource, out *v1.GlusterfsVolumeSource, s conversion.Scope) error { + out.EndpointsName = in.EndpointsName + out.Path = in.Path + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_core_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource is an autogenerated conversion function. +func Convert_core_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource(in *core.GlusterfsVolumeSource, out *v1.GlusterfsVolumeSource, s conversion.Scope) error { + return autoConvert_core_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource(in, out, s) +} + +func autoConvert_v1_HTTPGetAction_To_core_HTTPGetAction(in *v1.HTTPGetAction, out *core.HTTPGetAction, s conversion.Scope) error { + out.Path = in.Path + out.Port = in.Port + out.Host = in.Host + out.Scheme = core.URIScheme(in.Scheme) + out.HTTPHeaders = *(*[]core.HTTPHeader)(unsafe.Pointer(&in.HTTPHeaders)) + return nil +} + +// Convert_v1_HTTPGetAction_To_core_HTTPGetAction is an autogenerated conversion function. +func Convert_v1_HTTPGetAction_To_core_HTTPGetAction(in *v1.HTTPGetAction, out *core.HTTPGetAction, s conversion.Scope) error { + return autoConvert_v1_HTTPGetAction_To_core_HTTPGetAction(in, out, s) +} + +func autoConvert_core_HTTPGetAction_To_v1_HTTPGetAction(in *core.HTTPGetAction, out *v1.HTTPGetAction, s conversion.Scope) error { + out.Path = in.Path + out.Port = in.Port + out.Host = in.Host + out.Scheme = v1.URIScheme(in.Scheme) + out.HTTPHeaders = *(*[]v1.HTTPHeader)(unsafe.Pointer(&in.HTTPHeaders)) + return nil +} + +// Convert_core_HTTPGetAction_To_v1_HTTPGetAction is an autogenerated conversion function. +func Convert_core_HTTPGetAction_To_v1_HTTPGetAction(in *core.HTTPGetAction, out *v1.HTTPGetAction, s conversion.Scope) error { + return autoConvert_core_HTTPGetAction_To_v1_HTTPGetAction(in, out, s) +} + +func autoConvert_v1_HTTPHeader_To_core_HTTPHeader(in *v1.HTTPHeader, out *core.HTTPHeader, s conversion.Scope) error { + out.Name = in.Name + out.Value = in.Value + return nil +} + +// Convert_v1_HTTPHeader_To_core_HTTPHeader is an autogenerated conversion function. +func Convert_v1_HTTPHeader_To_core_HTTPHeader(in *v1.HTTPHeader, out *core.HTTPHeader, s conversion.Scope) error { + return autoConvert_v1_HTTPHeader_To_core_HTTPHeader(in, out, s) +} + +func autoConvert_core_HTTPHeader_To_v1_HTTPHeader(in *core.HTTPHeader, out *v1.HTTPHeader, s conversion.Scope) error { + out.Name = in.Name + out.Value = in.Value + return nil +} + +// Convert_core_HTTPHeader_To_v1_HTTPHeader is an autogenerated conversion function. +func Convert_core_HTTPHeader_To_v1_HTTPHeader(in *core.HTTPHeader, out *v1.HTTPHeader, s conversion.Scope) error { + return autoConvert_core_HTTPHeader_To_v1_HTTPHeader(in, out, s) +} + +func autoConvert_v1_Handler_To_core_Handler(in *v1.Handler, out *core.Handler, s conversion.Scope) error { + out.Exec = (*core.ExecAction)(unsafe.Pointer(in.Exec)) + out.HTTPGet = (*core.HTTPGetAction)(unsafe.Pointer(in.HTTPGet)) + out.TCPSocket = (*core.TCPSocketAction)(unsafe.Pointer(in.TCPSocket)) + return nil +} + +// Convert_v1_Handler_To_core_Handler is an autogenerated conversion function. +func Convert_v1_Handler_To_core_Handler(in *v1.Handler, out *core.Handler, s conversion.Scope) error { + return autoConvert_v1_Handler_To_core_Handler(in, out, s) +} + +func autoConvert_core_Handler_To_v1_Handler(in *core.Handler, out *v1.Handler, s conversion.Scope) error { + out.Exec = (*v1.ExecAction)(unsafe.Pointer(in.Exec)) + out.HTTPGet = (*v1.HTTPGetAction)(unsafe.Pointer(in.HTTPGet)) + out.TCPSocket = (*v1.TCPSocketAction)(unsafe.Pointer(in.TCPSocket)) + return nil +} + +// Convert_core_Handler_To_v1_Handler is an autogenerated conversion function. +func Convert_core_Handler_To_v1_Handler(in *core.Handler, out *v1.Handler, s conversion.Scope) error { + return autoConvert_core_Handler_To_v1_Handler(in, out, s) +} + +func autoConvert_v1_HostAlias_To_core_HostAlias(in *v1.HostAlias, out *core.HostAlias, s conversion.Scope) error { + out.IP = in.IP + out.Hostnames = *(*[]string)(unsafe.Pointer(&in.Hostnames)) + return nil +} + +// Convert_v1_HostAlias_To_core_HostAlias is an autogenerated conversion function. +func Convert_v1_HostAlias_To_core_HostAlias(in *v1.HostAlias, out *core.HostAlias, s conversion.Scope) error { + return autoConvert_v1_HostAlias_To_core_HostAlias(in, out, s) +} + +func autoConvert_core_HostAlias_To_v1_HostAlias(in *core.HostAlias, out *v1.HostAlias, s conversion.Scope) error { + out.IP = in.IP + out.Hostnames = *(*[]string)(unsafe.Pointer(&in.Hostnames)) + return nil +} + +// Convert_core_HostAlias_To_v1_HostAlias is an autogenerated conversion function. +func Convert_core_HostAlias_To_v1_HostAlias(in *core.HostAlias, out *v1.HostAlias, s conversion.Scope) error { + return autoConvert_core_HostAlias_To_v1_HostAlias(in, out, s) +} + +func autoConvert_v1_HostPathVolumeSource_To_core_HostPathVolumeSource(in *v1.HostPathVolumeSource, out *core.HostPathVolumeSource, s conversion.Scope) error { + out.Path = in.Path + out.Type = (*core.HostPathType)(unsafe.Pointer(in.Type)) + return nil +} + +// Convert_v1_HostPathVolumeSource_To_core_HostPathVolumeSource is an autogenerated conversion function. +func Convert_v1_HostPathVolumeSource_To_core_HostPathVolumeSource(in *v1.HostPathVolumeSource, out *core.HostPathVolumeSource, s conversion.Scope) error { + return autoConvert_v1_HostPathVolumeSource_To_core_HostPathVolumeSource(in, out, s) +} + +func autoConvert_core_HostPathVolumeSource_To_v1_HostPathVolumeSource(in *core.HostPathVolumeSource, out *v1.HostPathVolumeSource, s conversion.Scope) error { + out.Path = in.Path + out.Type = (*v1.HostPathType)(unsafe.Pointer(in.Type)) + return nil +} + +// Convert_core_HostPathVolumeSource_To_v1_HostPathVolumeSource is an autogenerated conversion function. +func Convert_core_HostPathVolumeSource_To_v1_HostPathVolumeSource(in *core.HostPathVolumeSource, out *v1.HostPathVolumeSource, s conversion.Scope) error { + return autoConvert_core_HostPathVolumeSource_To_v1_HostPathVolumeSource(in, out, s) +} + +func autoConvert_v1_ISCSIPersistentVolumeSource_To_core_ISCSIPersistentVolumeSource(in *v1.ISCSIPersistentVolumeSource, out *core.ISCSIPersistentVolumeSource, s conversion.Scope) error { + out.TargetPortal = in.TargetPortal + out.IQN = in.IQN + out.Lun = in.Lun + out.ISCSIInterface = in.ISCSIInterface + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + out.Portals = *(*[]string)(unsafe.Pointer(&in.Portals)) + out.DiscoveryCHAPAuth = in.DiscoveryCHAPAuth + out.SessionCHAPAuth = in.SessionCHAPAuth + out.SecretRef = (*core.SecretReference)(unsafe.Pointer(in.SecretRef)) + out.InitiatorName = (*string)(unsafe.Pointer(in.InitiatorName)) + return nil +} + +// Convert_v1_ISCSIPersistentVolumeSource_To_core_ISCSIPersistentVolumeSource is an autogenerated conversion function. +func Convert_v1_ISCSIPersistentVolumeSource_To_core_ISCSIPersistentVolumeSource(in *v1.ISCSIPersistentVolumeSource, out *core.ISCSIPersistentVolumeSource, s conversion.Scope) error { + return autoConvert_v1_ISCSIPersistentVolumeSource_To_core_ISCSIPersistentVolumeSource(in, out, s) +} + +func autoConvert_core_ISCSIPersistentVolumeSource_To_v1_ISCSIPersistentVolumeSource(in *core.ISCSIPersistentVolumeSource, out *v1.ISCSIPersistentVolumeSource, s conversion.Scope) error { + out.TargetPortal = in.TargetPortal + out.IQN = in.IQN + out.Lun = in.Lun + out.ISCSIInterface = in.ISCSIInterface + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + out.Portals = *(*[]string)(unsafe.Pointer(&in.Portals)) + out.DiscoveryCHAPAuth = in.DiscoveryCHAPAuth + out.SessionCHAPAuth = in.SessionCHAPAuth + out.SecretRef = (*v1.SecretReference)(unsafe.Pointer(in.SecretRef)) + out.InitiatorName = (*string)(unsafe.Pointer(in.InitiatorName)) + return nil +} + +// Convert_core_ISCSIPersistentVolumeSource_To_v1_ISCSIPersistentVolumeSource is an autogenerated conversion function. +func Convert_core_ISCSIPersistentVolumeSource_To_v1_ISCSIPersistentVolumeSource(in *core.ISCSIPersistentVolumeSource, out *v1.ISCSIPersistentVolumeSource, s conversion.Scope) error { + return autoConvert_core_ISCSIPersistentVolumeSource_To_v1_ISCSIPersistentVolumeSource(in, out, s) +} + +func autoConvert_v1_ISCSIVolumeSource_To_core_ISCSIVolumeSource(in *v1.ISCSIVolumeSource, out *core.ISCSIVolumeSource, s conversion.Scope) error { + out.TargetPortal = in.TargetPortal + out.IQN = in.IQN + out.Lun = in.Lun + out.ISCSIInterface = in.ISCSIInterface + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + out.Portals = *(*[]string)(unsafe.Pointer(&in.Portals)) + out.DiscoveryCHAPAuth = in.DiscoveryCHAPAuth + out.SessionCHAPAuth = in.SessionCHAPAuth + out.SecretRef = (*core.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) + out.InitiatorName = (*string)(unsafe.Pointer(in.InitiatorName)) + return nil +} + +// Convert_v1_ISCSIVolumeSource_To_core_ISCSIVolumeSource is an autogenerated conversion function. +func Convert_v1_ISCSIVolumeSource_To_core_ISCSIVolumeSource(in *v1.ISCSIVolumeSource, out *core.ISCSIVolumeSource, s conversion.Scope) error { + return autoConvert_v1_ISCSIVolumeSource_To_core_ISCSIVolumeSource(in, out, s) +} + +func autoConvert_core_ISCSIVolumeSource_To_v1_ISCSIVolumeSource(in *core.ISCSIVolumeSource, out *v1.ISCSIVolumeSource, s conversion.Scope) error { + out.TargetPortal = in.TargetPortal + out.IQN = in.IQN + out.Lun = in.Lun + out.ISCSIInterface = in.ISCSIInterface + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + out.Portals = *(*[]string)(unsafe.Pointer(&in.Portals)) + out.DiscoveryCHAPAuth = in.DiscoveryCHAPAuth + out.SessionCHAPAuth = in.SessionCHAPAuth + out.SecretRef = (*v1.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) + out.InitiatorName = (*string)(unsafe.Pointer(in.InitiatorName)) + return nil +} + +// Convert_core_ISCSIVolumeSource_To_v1_ISCSIVolumeSource is an autogenerated conversion function. +func Convert_core_ISCSIVolumeSource_To_v1_ISCSIVolumeSource(in *core.ISCSIVolumeSource, out *v1.ISCSIVolumeSource, s conversion.Scope) error { + return autoConvert_core_ISCSIVolumeSource_To_v1_ISCSIVolumeSource(in, out, s) +} + +func autoConvert_v1_KeyToPath_To_core_KeyToPath(in *v1.KeyToPath, out *core.KeyToPath, s conversion.Scope) error { + out.Key = in.Key + out.Path = in.Path + out.Mode = (*int32)(unsafe.Pointer(in.Mode)) + return nil +} + +// Convert_v1_KeyToPath_To_core_KeyToPath is an autogenerated conversion function. +func Convert_v1_KeyToPath_To_core_KeyToPath(in *v1.KeyToPath, out *core.KeyToPath, s conversion.Scope) error { + return autoConvert_v1_KeyToPath_To_core_KeyToPath(in, out, s) +} + +func autoConvert_core_KeyToPath_To_v1_KeyToPath(in *core.KeyToPath, out *v1.KeyToPath, s conversion.Scope) error { + out.Key = in.Key + out.Path = in.Path + out.Mode = (*int32)(unsafe.Pointer(in.Mode)) + return nil +} + +// Convert_core_KeyToPath_To_v1_KeyToPath is an autogenerated conversion function. +func Convert_core_KeyToPath_To_v1_KeyToPath(in *core.KeyToPath, out *v1.KeyToPath, s conversion.Scope) error { + return autoConvert_core_KeyToPath_To_v1_KeyToPath(in, out, s) +} + +func autoConvert_v1_Lifecycle_To_core_Lifecycle(in *v1.Lifecycle, out *core.Lifecycle, s conversion.Scope) error { + out.PostStart = (*core.Handler)(unsafe.Pointer(in.PostStart)) + out.PreStop = (*core.Handler)(unsafe.Pointer(in.PreStop)) + return nil +} + +// Convert_v1_Lifecycle_To_core_Lifecycle is an autogenerated conversion function. +func Convert_v1_Lifecycle_To_core_Lifecycle(in *v1.Lifecycle, out *core.Lifecycle, s conversion.Scope) error { + return autoConvert_v1_Lifecycle_To_core_Lifecycle(in, out, s) +} + +func autoConvert_core_Lifecycle_To_v1_Lifecycle(in *core.Lifecycle, out *v1.Lifecycle, s conversion.Scope) error { + out.PostStart = (*v1.Handler)(unsafe.Pointer(in.PostStart)) + out.PreStop = (*v1.Handler)(unsafe.Pointer(in.PreStop)) + return nil +} + +// Convert_core_Lifecycle_To_v1_Lifecycle is an autogenerated conversion function. +func Convert_core_Lifecycle_To_v1_Lifecycle(in *core.Lifecycle, out *v1.Lifecycle, s conversion.Scope) error { + return autoConvert_core_Lifecycle_To_v1_Lifecycle(in, out, s) +} + +func autoConvert_v1_LimitRange_To_core_LimitRange(in *v1.LimitRange, out *core.LimitRange, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_LimitRangeSpec_To_core_LimitRangeSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + return nil +} + +// Convert_v1_LimitRange_To_core_LimitRange is an autogenerated conversion function. +func Convert_v1_LimitRange_To_core_LimitRange(in *v1.LimitRange, out *core.LimitRange, s conversion.Scope) error { + return autoConvert_v1_LimitRange_To_core_LimitRange(in, out, s) +} + +func autoConvert_core_LimitRange_To_v1_LimitRange(in *core.LimitRange, out *v1.LimitRange, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_core_LimitRangeSpec_To_v1_LimitRangeSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + return nil +} + +// Convert_core_LimitRange_To_v1_LimitRange is an autogenerated conversion function. +func Convert_core_LimitRange_To_v1_LimitRange(in *core.LimitRange, out *v1.LimitRange, s conversion.Scope) error { + return autoConvert_core_LimitRange_To_v1_LimitRange(in, out, s) +} + +func autoConvert_v1_LimitRangeItem_To_core_LimitRangeItem(in *v1.LimitRangeItem, out *core.LimitRangeItem, s conversion.Scope) error { + out.Type = core.LimitType(in.Type) + out.Max = *(*core.ResourceList)(unsafe.Pointer(&in.Max)) + out.Min = *(*core.ResourceList)(unsafe.Pointer(&in.Min)) + out.Default = *(*core.ResourceList)(unsafe.Pointer(&in.Default)) + out.DefaultRequest = *(*core.ResourceList)(unsafe.Pointer(&in.DefaultRequest)) + out.MaxLimitRequestRatio = *(*core.ResourceList)(unsafe.Pointer(&in.MaxLimitRequestRatio)) + return nil +} + +// Convert_v1_LimitRangeItem_To_core_LimitRangeItem is an autogenerated conversion function. +func Convert_v1_LimitRangeItem_To_core_LimitRangeItem(in *v1.LimitRangeItem, out *core.LimitRangeItem, s conversion.Scope) error { + return autoConvert_v1_LimitRangeItem_To_core_LimitRangeItem(in, out, s) +} + +func autoConvert_core_LimitRangeItem_To_v1_LimitRangeItem(in *core.LimitRangeItem, out *v1.LimitRangeItem, s conversion.Scope) error { + out.Type = v1.LimitType(in.Type) + out.Max = *(*v1.ResourceList)(unsafe.Pointer(&in.Max)) + out.Min = *(*v1.ResourceList)(unsafe.Pointer(&in.Min)) + out.Default = *(*v1.ResourceList)(unsafe.Pointer(&in.Default)) + out.DefaultRequest = *(*v1.ResourceList)(unsafe.Pointer(&in.DefaultRequest)) + out.MaxLimitRequestRatio = *(*v1.ResourceList)(unsafe.Pointer(&in.MaxLimitRequestRatio)) + return nil +} + +// Convert_core_LimitRangeItem_To_v1_LimitRangeItem is an autogenerated conversion function. +func Convert_core_LimitRangeItem_To_v1_LimitRangeItem(in *core.LimitRangeItem, out *v1.LimitRangeItem, s conversion.Scope) error { + return autoConvert_core_LimitRangeItem_To_v1_LimitRangeItem(in, out, s) +} + +func autoConvert_v1_LimitRangeList_To_core_LimitRangeList(in *v1.LimitRangeList, out *core.LimitRangeList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]core.LimitRange)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1_LimitRangeList_To_core_LimitRangeList is an autogenerated conversion function. +func Convert_v1_LimitRangeList_To_core_LimitRangeList(in *v1.LimitRangeList, out *core.LimitRangeList, s conversion.Scope) error { + return autoConvert_v1_LimitRangeList_To_core_LimitRangeList(in, out, s) +} + +func autoConvert_core_LimitRangeList_To_v1_LimitRangeList(in *core.LimitRangeList, out *v1.LimitRangeList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]v1.LimitRange)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_core_LimitRangeList_To_v1_LimitRangeList is an autogenerated conversion function. +func Convert_core_LimitRangeList_To_v1_LimitRangeList(in *core.LimitRangeList, out *v1.LimitRangeList, s conversion.Scope) error { + return autoConvert_core_LimitRangeList_To_v1_LimitRangeList(in, out, s) +} + +func autoConvert_v1_LimitRangeSpec_To_core_LimitRangeSpec(in *v1.LimitRangeSpec, out *core.LimitRangeSpec, s conversion.Scope) error { + out.Limits = *(*[]core.LimitRangeItem)(unsafe.Pointer(&in.Limits)) + return nil +} + +// Convert_v1_LimitRangeSpec_To_core_LimitRangeSpec is an autogenerated conversion function. +func Convert_v1_LimitRangeSpec_To_core_LimitRangeSpec(in *v1.LimitRangeSpec, out *core.LimitRangeSpec, s conversion.Scope) error { + return autoConvert_v1_LimitRangeSpec_To_core_LimitRangeSpec(in, out, s) +} + +func autoConvert_core_LimitRangeSpec_To_v1_LimitRangeSpec(in *core.LimitRangeSpec, out *v1.LimitRangeSpec, s conversion.Scope) error { + out.Limits = *(*[]v1.LimitRangeItem)(unsafe.Pointer(&in.Limits)) + return nil +} + +// Convert_core_LimitRangeSpec_To_v1_LimitRangeSpec is an autogenerated conversion function. +func Convert_core_LimitRangeSpec_To_v1_LimitRangeSpec(in *core.LimitRangeSpec, out *v1.LimitRangeSpec, s conversion.Scope) error { + return autoConvert_core_LimitRangeSpec_To_v1_LimitRangeSpec(in, out, s) +} + +func autoConvert_v1_List_To_core_List(in *v1.List, out *core.List, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]runtime.Object, len(*in)) + for i := range *in { + if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_v1_List_To_core_List is an autogenerated conversion function. +func Convert_v1_List_To_core_List(in *v1.List, out *core.List, s conversion.Scope) error { + return autoConvert_v1_List_To_core_List(in, out, s) +} + +func autoConvert_core_List_To_v1_List(in *core.List, out *v1.List, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]runtime.RawExtension, len(*in)) + for i := range *in { + if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_core_List_To_v1_List is an autogenerated conversion function. +func Convert_core_List_To_v1_List(in *core.List, out *v1.List, s conversion.Scope) error { + return autoConvert_core_List_To_v1_List(in, out, s) +} + +func autoConvert_v1_ListOptions_To_core_ListOptions(in *v1.ListOptions, out *core.ListOptions, s conversion.Scope) error { + if err := meta_v1.Convert_string_To_labels_Selector(&in.LabelSelector, &out.LabelSelector, s); err != nil { + return err + } + if err := meta_v1.Convert_string_To_fields_Selector(&in.FieldSelector, &out.FieldSelector, s); err != nil { + return err + } + out.IncludeUninitialized = in.IncludeUninitialized + out.Watch = in.Watch + out.ResourceVersion = in.ResourceVersion + out.TimeoutSeconds = (*int64)(unsafe.Pointer(in.TimeoutSeconds)) + return nil +} + +// Convert_v1_ListOptions_To_core_ListOptions is an autogenerated conversion function. +func Convert_v1_ListOptions_To_core_ListOptions(in *v1.ListOptions, out *core.ListOptions, s conversion.Scope) error { + return autoConvert_v1_ListOptions_To_core_ListOptions(in, out, s) +} + +func autoConvert_core_ListOptions_To_v1_ListOptions(in *core.ListOptions, out *v1.ListOptions, s conversion.Scope) error { + if err := meta_v1.Convert_labels_Selector_To_string(&in.LabelSelector, &out.LabelSelector, s); err != nil { + return err + } + if err := meta_v1.Convert_fields_Selector_To_string(&in.FieldSelector, &out.FieldSelector, s); err != nil { + return err + } + out.IncludeUninitialized = in.IncludeUninitialized + out.Watch = in.Watch + out.ResourceVersion = in.ResourceVersion + out.TimeoutSeconds = (*int64)(unsafe.Pointer(in.TimeoutSeconds)) + return nil +} + +// Convert_core_ListOptions_To_v1_ListOptions is an autogenerated conversion function. +func Convert_core_ListOptions_To_v1_ListOptions(in *core.ListOptions, out *v1.ListOptions, s conversion.Scope) error { + return autoConvert_core_ListOptions_To_v1_ListOptions(in, out, s) +} + +func autoConvert_v1_LoadBalancerIngress_To_core_LoadBalancerIngress(in *v1.LoadBalancerIngress, out *core.LoadBalancerIngress, s conversion.Scope) error { + out.IP = in.IP + out.Hostname = in.Hostname + return nil +} + +// Convert_v1_LoadBalancerIngress_To_core_LoadBalancerIngress is an autogenerated conversion function. +func Convert_v1_LoadBalancerIngress_To_core_LoadBalancerIngress(in *v1.LoadBalancerIngress, out *core.LoadBalancerIngress, s conversion.Scope) error { + return autoConvert_v1_LoadBalancerIngress_To_core_LoadBalancerIngress(in, out, s) +} + +func autoConvert_core_LoadBalancerIngress_To_v1_LoadBalancerIngress(in *core.LoadBalancerIngress, out *v1.LoadBalancerIngress, s conversion.Scope) error { + out.IP = in.IP + out.Hostname = in.Hostname + return nil +} + +// Convert_core_LoadBalancerIngress_To_v1_LoadBalancerIngress is an autogenerated conversion function. +func Convert_core_LoadBalancerIngress_To_v1_LoadBalancerIngress(in *core.LoadBalancerIngress, out *v1.LoadBalancerIngress, s conversion.Scope) error { + return autoConvert_core_LoadBalancerIngress_To_v1_LoadBalancerIngress(in, out, s) +} + +func autoConvert_v1_LoadBalancerStatus_To_core_LoadBalancerStatus(in *v1.LoadBalancerStatus, out *core.LoadBalancerStatus, s conversion.Scope) error { + out.Ingress = *(*[]core.LoadBalancerIngress)(unsafe.Pointer(&in.Ingress)) + return nil +} + +// Convert_v1_LoadBalancerStatus_To_core_LoadBalancerStatus is an autogenerated conversion function. +func Convert_v1_LoadBalancerStatus_To_core_LoadBalancerStatus(in *v1.LoadBalancerStatus, out *core.LoadBalancerStatus, s conversion.Scope) error { + return autoConvert_v1_LoadBalancerStatus_To_core_LoadBalancerStatus(in, out, s) +} + +func autoConvert_core_LoadBalancerStatus_To_v1_LoadBalancerStatus(in *core.LoadBalancerStatus, out *v1.LoadBalancerStatus, s conversion.Scope) error { + out.Ingress = *(*[]v1.LoadBalancerIngress)(unsafe.Pointer(&in.Ingress)) + return nil +} + +// Convert_core_LoadBalancerStatus_To_v1_LoadBalancerStatus is an autogenerated conversion function. +func Convert_core_LoadBalancerStatus_To_v1_LoadBalancerStatus(in *core.LoadBalancerStatus, out *v1.LoadBalancerStatus, s conversion.Scope) error { + return autoConvert_core_LoadBalancerStatus_To_v1_LoadBalancerStatus(in, out, s) +} + +func autoConvert_v1_LocalObjectReference_To_core_LocalObjectReference(in *v1.LocalObjectReference, out *core.LocalObjectReference, s conversion.Scope) error { + out.Name = in.Name + return nil +} + +// Convert_v1_LocalObjectReference_To_core_LocalObjectReference is an autogenerated conversion function. +func Convert_v1_LocalObjectReference_To_core_LocalObjectReference(in *v1.LocalObjectReference, out *core.LocalObjectReference, s conversion.Scope) error { + return autoConvert_v1_LocalObjectReference_To_core_LocalObjectReference(in, out, s) +} + +func autoConvert_core_LocalObjectReference_To_v1_LocalObjectReference(in *core.LocalObjectReference, out *v1.LocalObjectReference, s conversion.Scope) error { + out.Name = in.Name + return nil +} + +// Convert_core_LocalObjectReference_To_v1_LocalObjectReference is an autogenerated conversion function. +func Convert_core_LocalObjectReference_To_v1_LocalObjectReference(in *core.LocalObjectReference, out *v1.LocalObjectReference, s conversion.Scope) error { + return autoConvert_core_LocalObjectReference_To_v1_LocalObjectReference(in, out, s) +} + +func autoConvert_v1_LocalVolumeSource_To_core_LocalVolumeSource(in *v1.LocalVolumeSource, out *core.LocalVolumeSource, s conversion.Scope) error { + out.Path = in.Path + return nil +} + +// Convert_v1_LocalVolumeSource_To_core_LocalVolumeSource is an autogenerated conversion function. +func Convert_v1_LocalVolumeSource_To_core_LocalVolumeSource(in *v1.LocalVolumeSource, out *core.LocalVolumeSource, s conversion.Scope) error { + return autoConvert_v1_LocalVolumeSource_To_core_LocalVolumeSource(in, out, s) +} + +func autoConvert_core_LocalVolumeSource_To_v1_LocalVolumeSource(in *core.LocalVolumeSource, out *v1.LocalVolumeSource, s conversion.Scope) error { + out.Path = in.Path + return nil +} + +// Convert_core_LocalVolumeSource_To_v1_LocalVolumeSource is an autogenerated conversion function. +func Convert_core_LocalVolumeSource_To_v1_LocalVolumeSource(in *core.LocalVolumeSource, out *v1.LocalVolumeSource, s conversion.Scope) error { + return autoConvert_core_LocalVolumeSource_To_v1_LocalVolumeSource(in, out, s) +} + +func autoConvert_v1_NFSVolumeSource_To_core_NFSVolumeSource(in *v1.NFSVolumeSource, out *core.NFSVolumeSource, s conversion.Scope) error { + out.Server = in.Server + out.Path = in.Path + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_v1_NFSVolumeSource_To_core_NFSVolumeSource is an autogenerated conversion function. +func Convert_v1_NFSVolumeSource_To_core_NFSVolumeSource(in *v1.NFSVolumeSource, out *core.NFSVolumeSource, s conversion.Scope) error { + return autoConvert_v1_NFSVolumeSource_To_core_NFSVolumeSource(in, out, s) +} + +func autoConvert_core_NFSVolumeSource_To_v1_NFSVolumeSource(in *core.NFSVolumeSource, out *v1.NFSVolumeSource, s conversion.Scope) error { + out.Server = in.Server + out.Path = in.Path + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_core_NFSVolumeSource_To_v1_NFSVolumeSource is an autogenerated conversion function. +func Convert_core_NFSVolumeSource_To_v1_NFSVolumeSource(in *core.NFSVolumeSource, out *v1.NFSVolumeSource, s conversion.Scope) error { + return autoConvert_core_NFSVolumeSource_To_v1_NFSVolumeSource(in, out, s) +} + +func autoConvert_v1_Namespace_To_core_Namespace(in *v1.Namespace, out *core.Namespace, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_NamespaceSpec_To_core_NamespaceSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1_NamespaceStatus_To_core_NamespaceStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1_Namespace_To_core_Namespace is an autogenerated conversion function. +func Convert_v1_Namespace_To_core_Namespace(in *v1.Namespace, out *core.Namespace, s conversion.Scope) error { + return autoConvert_v1_Namespace_To_core_Namespace(in, out, s) +} + +func autoConvert_core_Namespace_To_v1_Namespace(in *core.Namespace, out *v1.Namespace, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_core_NamespaceSpec_To_v1_NamespaceSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_core_NamespaceStatus_To_v1_NamespaceStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_core_Namespace_To_v1_Namespace is an autogenerated conversion function. +func Convert_core_Namespace_To_v1_Namespace(in *core.Namespace, out *v1.Namespace, s conversion.Scope) error { + return autoConvert_core_Namespace_To_v1_Namespace(in, out, s) +} + +func autoConvert_v1_NamespaceList_To_core_NamespaceList(in *v1.NamespaceList, out *core.NamespaceList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]core.Namespace)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1_NamespaceList_To_core_NamespaceList is an autogenerated conversion function. +func Convert_v1_NamespaceList_To_core_NamespaceList(in *v1.NamespaceList, out *core.NamespaceList, s conversion.Scope) error { + return autoConvert_v1_NamespaceList_To_core_NamespaceList(in, out, s) +} + +func autoConvert_core_NamespaceList_To_v1_NamespaceList(in *core.NamespaceList, out *v1.NamespaceList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]v1.Namespace)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_core_NamespaceList_To_v1_NamespaceList is an autogenerated conversion function. +func Convert_core_NamespaceList_To_v1_NamespaceList(in *core.NamespaceList, out *v1.NamespaceList, s conversion.Scope) error { + return autoConvert_core_NamespaceList_To_v1_NamespaceList(in, out, s) +} + +func autoConvert_v1_NamespaceSpec_To_core_NamespaceSpec(in *v1.NamespaceSpec, out *core.NamespaceSpec, s conversion.Scope) error { + out.Finalizers = *(*[]core.FinalizerName)(unsafe.Pointer(&in.Finalizers)) + return nil +} + +// Convert_v1_NamespaceSpec_To_core_NamespaceSpec is an autogenerated conversion function. +func Convert_v1_NamespaceSpec_To_core_NamespaceSpec(in *v1.NamespaceSpec, out *core.NamespaceSpec, s conversion.Scope) error { + return autoConvert_v1_NamespaceSpec_To_core_NamespaceSpec(in, out, s) +} + +func autoConvert_core_NamespaceSpec_To_v1_NamespaceSpec(in *core.NamespaceSpec, out *v1.NamespaceSpec, s conversion.Scope) error { + out.Finalizers = *(*[]v1.FinalizerName)(unsafe.Pointer(&in.Finalizers)) + return nil +} + +// Convert_core_NamespaceSpec_To_v1_NamespaceSpec is an autogenerated conversion function. +func Convert_core_NamespaceSpec_To_v1_NamespaceSpec(in *core.NamespaceSpec, out *v1.NamespaceSpec, s conversion.Scope) error { + return autoConvert_core_NamespaceSpec_To_v1_NamespaceSpec(in, out, s) +} + +func autoConvert_v1_NamespaceStatus_To_core_NamespaceStatus(in *v1.NamespaceStatus, out *core.NamespaceStatus, s conversion.Scope) error { + out.Phase = core.NamespacePhase(in.Phase) + return nil +} + +// Convert_v1_NamespaceStatus_To_core_NamespaceStatus is an autogenerated conversion function. +func Convert_v1_NamespaceStatus_To_core_NamespaceStatus(in *v1.NamespaceStatus, out *core.NamespaceStatus, s conversion.Scope) error { + return autoConvert_v1_NamespaceStatus_To_core_NamespaceStatus(in, out, s) +} + +func autoConvert_core_NamespaceStatus_To_v1_NamespaceStatus(in *core.NamespaceStatus, out *v1.NamespaceStatus, s conversion.Scope) error { + out.Phase = v1.NamespacePhase(in.Phase) + return nil +} + +// Convert_core_NamespaceStatus_To_v1_NamespaceStatus is an autogenerated conversion function. +func Convert_core_NamespaceStatus_To_v1_NamespaceStatus(in *core.NamespaceStatus, out *v1.NamespaceStatus, s conversion.Scope) error { + return autoConvert_core_NamespaceStatus_To_v1_NamespaceStatus(in, out, s) +} + +func autoConvert_v1_Node_To_core_Node(in *v1.Node, out *core.Node, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_NodeSpec_To_core_NodeSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1_NodeStatus_To_core_NodeStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1_Node_To_core_Node is an autogenerated conversion function. +func Convert_v1_Node_To_core_Node(in *v1.Node, out *core.Node, s conversion.Scope) error { + return autoConvert_v1_Node_To_core_Node(in, out, s) +} + +func autoConvert_core_Node_To_v1_Node(in *core.Node, out *v1.Node, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_core_NodeSpec_To_v1_NodeSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_core_NodeStatus_To_v1_NodeStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_core_Node_To_v1_Node is an autogenerated conversion function. +func Convert_core_Node_To_v1_Node(in *core.Node, out *v1.Node, s conversion.Scope) error { + return autoConvert_core_Node_To_v1_Node(in, out, s) +} + +func autoConvert_v1_NodeAddress_To_core_NodeAddress(in *v1.NodeAddress, out *core.NodeAddress, s conversion.Scope) error { + out.Type = core.NodeAddressType(in.Type) + out.Address = in.Address + return nil +} + +// Convert_v1_NodeAddress_To_core_NodeAddress is an autogenerated conversion function. +func Convert_v1_NodeAddress_To_core_NodeAddress(in *v1.NodeAddress, out *core.NodeAddress, s conversion.Scope) error { + return autoConvert_v1_NodeAddress_To_core_NodeAddress(in, out, s) +} + +func autoConvert_core_NodeAddress_To_v1_NodeAddress(in *core.NodeAddress, out *v1.NodeAddress, s conversion.Scope) error { + out.Type = v1.NodeAddressType(in.Type) + out.Address = in.Address + return nil +} + +// Convert_core_NodeAddress_To_v1_NodeAddress is an autogenerated conversion function. +func Convert_core_NodeAddress_To_v1_NodeAddress(in *core.NodeAddress, out *v1.NodeAddress, s conversion.Scope) error { + return autoConvert_core_NodeAddress_To_v1_NodeAddress(in, out, s) +} + +func autoConvert_v1_NodeAffinity_To_core_NodeAffinity(in *v1.NodeAffinity, out *core.NodeAffinity, s conversion.Scope) error { + out.RequiredDuringSchedulingIgnoredDuringExecution = (*core.NodeSelector)(unsafe.Pointer(in.RequiredDuringSchedulingIgnoredDuringExecution)) + out.PreferredDuringSchedulingIgnoredDuringExecution = *(*[]core.PreferredSchedulingTerm)(unsafe.Pointer(&in.PreferredDuringSchedulingIgnoredDuringExecution)) + return nil +} + +// Convert_v1_NodeAffinity_To_core_NodeAffinity is an autogenerated conversion function. +func Convert_v1_NodeAffinity_To_core_NodeAffinity(in *v1.NodeAffinity, out *core.NodeAffinity, s conversion.Scope) error { + return autoConvert_v1_NodeAffinity_To_core_NodeAffinity(in, out, s) +} + +func autoConvert_core_NodeAffinity_To_v1_NodeAffinity(in *core.NodeAffinity, out *v1.NodeAffinity, s conversion.Scope) error { + out.RequiredDuringSchedulingIgnoredDuringExecution = (*v1.NodeSelector)(unsafe.Pointer(in.RequiredDuringSchedulingIgnoredDuringExecution)) + out.PreferredDuringSchedulingIgnoredDuringExecution = *(*[]v1.PreferredSchedulingTerm)(unsafe.Pointer(&in.PreferredDuringSchedulingIgnoredDuringExecution)) + return nil +} + +// Convert_core_NodeAffinity_To_v1_NodeAffinity is an autogenerated conversion function. +func Convert_core_NodeAffinity_To_v1_NodeAffinity(in *core.NodeAffinity, out *v1.NodeAffinity, s conversion.Scope) error { + return autoConvert_core_NodeAffinity_To_v1_NodeAffinity(in, out, s) +} + +func autoConvert_v1_NodeCondition_To_core_NodeCondition(in *v1.NodeCondition, out *core.NodeCondition, s conversion.Scope) error { + out.Type = core.NodeConditionType(in.Type) + out.Status = core.ConditionStatus(in.Status) + out.LastHeartbeatTime = in.LastHeartbeatTime + out.LastTransitionTime = in.LastTransitionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_v1_NodeCondition_To_core_NodeCondition is an autogenerated conversion function. +func Convert_v1_NodeCondition_To_core_NodeCondition(in *v1.NodeCondition, out *core.NodeCondition, s conversion.Scope) error { + return autoConvert_v1_NodeCondition_To_core_NodeCondition(in, out, s) +} + +func autoConvert_core_NodeCondition_To_v1_NodeCondition(in *core.NodeCondition, out *v1.NodeCondition, s conversion.Scope) error { + out.Type = v1.NodeConditionType(in.Type) + out.Status = v1.ConditionStatus(in.Status) + out.LastHeartbeatTime = in.LastHeartbeatTime + out.LastTransitionTime = in.LastTransitionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_core_NodeCondition_To_v1_NodeCondition is an autogenerated conversion function. +func Convert_core_NodeCondition_To_v1_NodeCondition(in *core.NodeCondition, out *v1.NodeCondition, s conversion.Scope) error { + return autoConvert_core_NodeCondition_To_v1_NodeCondition(in, out, s) +} + +func autoConvert_v1_NodeConfigSource_To_core_NodeConfigSource(in *v1.NodeConfigSource, out *core.NodeConfigSource, s conversion.Scope) error { + out.ConfigMapRef = (*core.ObjectReference)(unsafe.Pointer(in.ConfigMapRef)) + return nil +} + +// Convert_v1_NodeConfigSource_To_core_NodeConfigSource is an autogenerated conversion function. +func Convert_v1_NodeConfigSource_To_core_NodeConfigSource(in *v1.NodeConfigSource, out *core.NodeConfigSource, s conversion.Scope) error { + return autoConvert_v1_NodeConfigSource_To_core_NodeConfigSource(in, out, s) +} + +func autoConvert_core_NodeConfigSource_To_v1_NodeConfigSource(in *core.NodeConfigSource, out *v1.NodeConfigSource, s conversion.Scope) error { + out.ConfigMapRef = (*v1.ObjectReference)(unsafe.Pointer(in.ConfigMapRef)) + return nil +} + +// Convert_core_NodeConfigSource_To_v1_NodeConfigSource is an autogenerated conversion function. +func Convert_core_NodeConfigSource_To_v1_NodeConfigSource(in *core.NodeConfigSource, out *v1.NodeConfigSource, s conversion.Scope) error { + return autoConvert_core_NodeConfigSource_To_v1_NodeConfigSource(in, out, s) +} + +func autoConvert_v1_NodeDaemonEndpoints_To_core_NodeDaemonEndpoints(in *v1.NodeDaemonEndpoints, out *core.NodeDaemonEndpoints, s conversion.Scope) error { + if err := Convert_v1_DaemonEndpoint_To_core_DaemonEndpoint(&in.KubeletEndpoint, &out.KubeletEndpoint, s); err != nil { + return err + } + return nil +} + +// Convert_v1_NodeDaemonEndpoints_To_core_NodeDaemonEndpoints is an autogenerated conversion function. +func Convert_v1_NodeDaemonEndpoints_To_core_NodeDaemonEndpoints(in *v1.NodeDaemonEndpoints, out *core.NodeDaemonEndpoints, s conversion.Scope) error { + return autoConvert_v1_NodeDaemonEndpoints_To_core_NodeDaemonEndpoints(in, out, s) +} + +func autoConvert_core_NodeDaemonEndpoints_To_v1_NodeDaemonEndpoints(in *core.NodeDaemonEndpoints, out *v1.NodeDaemonEndpoints, s conversion.Scope) error { + if err := Convert_core_DaemonEndpoint_To_v1_DaemonEndpoint(&in.KubeletEndpoint, &out.KubeletEndpoint, s); err != nil { + return err + } + return nil +} + +// Convert_core_NodeDaemonEndpoints_To_v1_NodeDaemonEndpoints is an autogenerated conversion function. +func Convert_core_NodeDaemonEndpoints_To_v1_NodeDaemonEndpoints(in *core.NodeDaemonEndpoints, out *v1.NodeDaemonEndpoints, s conversion.Scope) error { + return autoConvert_core_NodeDaemonEndpoints_To_v1_NodeDaemonEndpoints(in, out, s) +} + +func autoConvert_v1_NodeList_To_core_NodeList(in *v1.NodeList, out *core.NodeList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]core.Node)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1_NodeList_To_core_NodeList is an autogenerated conversion function. +func Convert_v1_NodeList_To_core_NodeList(in *v1.NodeList, out *core.NodeList, s conversion.Scope) error { + return autoConvert_v1_NodeList_To_core_NodeList(in, out, s) +} + +func autoConvert_core_NodeList_To_v1_NodeList(in *core.NodeList, out *v1.NodeList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]v1.Node)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_core_NodeList_To_v1_NodeList is an autogenerated conversion function. +func Convert_core_NodeList_To_v1_NodeList(in *core.NodeList, out *v1.NodeList, s conversion.Scope) error { + return autoConvert_core_NodeList_To_v1_NodeList(in, out, s) +} + +func autoConvert_v1_NodeProxyOptions_To_core_NodeProxyOptions(in *v1.NodeProxyOptions, out *core.NodeProxyOptions, s conversion.Scope) error { + out.Path = in.Path + return nil +} + +// Convert_v1_NodeProxyOptions_To_core_NodeProxyOptions is an autogenerated conversion function. +func Convert_v1_NodeProxyOptions_To_core_NodeProxyOptions(in *v1.NodeProxyOptions, out *core.NodeProxyOptions, s conversion.Scope) error { + return autoConvert_v1_NodeProxyOptions_To_core_NodeProxyOptions(in, out, s) +} + +func autoConvert_core_NodeProxyOptions_To_v1_NodeProxyOptions(in *core.NodeProxyOptions, out *v1.NodeProxyOptions, s conversion.Scope) error { + out.Path = in.Path + return nil +} + +// Convert_core_NodeProxyOptions_To_v1_NodeProxyOptions is an autogenerated conversion function. +func Convert_core_NodeProxyOptions_To_v1_NodeProxyOptions(in *core.NodeProxyOptions, out *v1.NodeProxyOptions, s conversion.Scope) error { + return autoConvert_core_NodeProxyOptions_To_v1_NodeProxyOptions(in, out, s) +} + +func autoConvert_v1_NodeResources_To_core_NodeResources(in *v1.NodeResources, out *core.NodeResources, s conversion.Scope) error { + out.Capacity = *(*core.ResourceList)(unsafe.Pointer(&in.Capacity)) + return nil +} + +// Convert_v1_NodeResources_To_core_NodeResources is an autogenerated conversion function. +func Convert_v1_NodeResources_To_core_NodeResources(in *v1.NodeResources, out *core.NodeResources, s conversion.Scope) error { + return autoConvert_v1_NodeResources_To_core_NodeResources(in, out, s) +} + +func autoConvert_core_NodeResources_To_v1_NodeResources(in *core.NodeResources, out *v1.NodeResources, s conversion.Scope) error { + out.Capacity = *(*v1.ResourceList)(unsafe.Pointer(&in.Capacity)) + return nil +} + +// Convert_core_NodeResources_To_v1_NodeResources is an autogenerated conversion function. +func Convert_core_NodeResources_To_v1_NodeResources(in *core.NodeResources, out *v1.NodeResources, s conversion.Scope) error { + return autoConvert_core_NodeResources_To_v1_NodeResources(in, out, s) +} + +func autoConvert_v1_NodeSelector_To_core_NodeSelector(in *v1.NodeSelector, out *core.NodeSelector, s conversion.Scope) error { + out.NodeSelectorTerms = *(*[]core.NodeSelectorTerm)(unsafe.Pointer(&in.NodeSelectorTerms)) + return nil +} + +// Convert_v1_NodeSelector_To_core_NodeSelector is an autogenerated conversion function. +func Convert_v1_NodeSelector_To_core_NodeSelector(in *v1.NodeSelector, out *core.NodeSelector, s conversion.Scope) error { + return autoConvert_v1_NodeSelector_To_core_NodeSelector(in, out, s) +} + +func autoConvert_core_NodeSelector_To_v1_NodeSelector(in *core.NodeSelector, out *v1.NodeSelector, s conversion.Scope) error { + out.NodeSelectorTerms = *(*[]v1.NodeSelectorTerm)(unsafe.Pointer(&in.NodeSelectorTerms)) + return nil +} + +// Convert_core_NodeSelector_To_v1_NodeSelector is an autogenerated conversion function. +func Convert_core_NodeSelector_To_v1_NodeSelector(in *core.NodeSelector, out *v1.NodeSelector, s conversion.Scope) error { + return autoConvert_core_NodeSelector_To_v1_NodeSelector(in, out, s) +} + +func autoConvert_v1_NodeSelectorRequirement_To_core_NodeSelectorRequirement(in *v1.NodeSelectorRequirement, out *core.NodeSelectorRequirement, s conversion.Scope) error { + out.Key = in.Key + out.Operator = core.NodeSelectorOperator(in.Operator) + out.Values = *(*[]string)(unsafe.Pointer(&in.Values)) + return nil +} + +// Convert_v1_NodeSelectorRequirement_To_core_NodeSelectorRequirement is an autogenerated conversion function. +func Convert_v1_NodeSelectorRequirement_To_core_NodeSelectorRequirement(in *v1.NodeSelectorRequirement, out *core.NodeSelectorRequirement, s conversion.Scope) error { + return autoConvert_v1_NodeSelectorRequirement_To_core_NodeSelectorRequirement(in, out, s) +} + +func autoConvert_core_NodeSelectorRequirement_To_v1_NodeSelectorRequirement(in *core.NodeSelectorRequirement, out *v1.NodeSelectorRequirement, s conversion.Scope) error { + out.Key = in.Key + out.Operator = v1.NodeSelectorOperator(in.Operator) + out.Values = *(*[]string)(unsafe.Pointer(&in.Values)) + return nil +} + +// Convert_core_NodeSelectorRequirement_To_v1_NodeSelectorRequirement is an autogenerated conversion function. +func Convert_core_NodeSelectorRequirement_To_v1_NodeSelectorRequirement(in *core.NodeSelectorRequirement, out *v1.NodeSelectorRequirement, s conversion.Scope) error { + return autoConvert_core_NodeSelectorRequirement_To_v1_NodeSelectorRequirement(in, out, s) +} + +func autoConvert_v1_NodeSelectorTerm_To_core_NodeSelectorTerm(in *v1.NodeSelectorTerm, out *core.NodeSelectorTerm, s conversion.Scope) error { + out.MatchExpressions = *(*[]core.NodeSelectorRequirement)(unsafe.Pointer(&in.MatchExpressions)) + return nil +} + +// Convert_v1_NodeSelectorTerm_To_core_NodeSelectorTerm is an autogenerated conversion function. +func Convert_v1_NodeSelectorTerm_To_core_NodeSelectorTerm(in *v1.NodeSelectorTerm, out *core.NodeSelectorTerm, s conversion.Scope) error { + return autoConvert_v1_NodeSelectorTerm_To_core_NodeSelectorTerm(in, out, s) +} + +func autoConvert_core_NodeSelectorTerm_To_v1_NodeSelectorTerm(in *core.NodeSelectorTerm, out *v1.NodeSelectorTerm, s conversion.Scope) error { + out.MatchExpressions = *(*[]v1.NodeSelectorRequirement)(unsafe.Pointer(&in.MatchExpressions)) + return nil +} + +// Convert_core_NodeSelectorTerm_To_v1_NodeSelectorTerm is an autogenerated conversion function. +func Convert_core_NodeSelectorTerm_To_v1_NodeSelectorTerm(in *core.NodeSelectorTerm, out *v1.NodeSelectorTerm, s conversion.Scope) error { + return autoConvert_core_NodeSelectorTerm_To_v1_NodeSelectorTerm(in, out, s) +} + +func autoConvert_v1_NodeSpec_To_core_NodeSpec(in *v1.NodeSpec, out *core.NodeSpec, s conversion.Scope) error { + out.PodCIDR = in.PodCIDR + out.ExternalID = in.ExternalID + out.ProviderID = in.ProviderID + out.Unschedulable = in.Unschedulable + out.Taints = *(*[]core.Taint)(unsafe.Pointer(&in.Taints)) + out.ConfigSource = (*core.NodeConfigSource)(unsafe.Pointer(in.ConfigSource)) + return nil +} + +// Convert_v1_NodeSpec_To_core_NodeSpec is an autogenerated conversion function. +func Convert_v1_NodeSpec_To_core_NodeSpec(in *v1.NodeSpec, out *core.NodeSpec, s conversion.Scope) error { + return autoConvert_v1_NodeSpec_To_core_NodeSpec(in, out, s) +} + +func autoConvert_core_NodeSpec_To_v1_NodeSpec(in *core.NodeSpec, out *v1.NodeSpec, s conversion.Scope) error { + out.PodCIDR = in.PodCIDR + out.ExternalID = in.ExternalID + out.ProviderID = in.ProviderID + out.Unschedulable = in.Unschedulable + out.Taints = *(*[]v1.Taint)(unsafe.Pointer(&in.Taints)) + out.ConfigSource = (*v1.NodeConfigSource)(unsafe.Pointer(in.ConfigSource)) + return nil +} + +// Convert_core_NodeSpec_To_v1_NodeSpec is an autogenerated conversion function. +func Convert_core_NodeSpec_To_v1_NodeSpec(in *core.NodeSpec, out *v1.NodeSpec, s conversion.Scope) error { + return autoConvert_core_NodeSpec_To_v1_NodeSpec(in, out, s) +} + +func autoConvert_v1_NodeStatus_To_core_NodeStatus(in *v1.NodeStatus, out *core.NodeStatus, s conversion.Scope) error { + out.Capacity = *(*core.ResourceList)(unsafe.Pointer(&in.Capacity)) + out.Allocatable = *(*core.ResourceList)(unsafe.Pointer(&in.Allocatable)) + out.Phase = core.NodePhase(in.Phase) + out.Conditions = *(*[]core.NodeCondition)(unsafe.Pointer(&in.Conditions)) + out.Addresses = *(*[]core.NodeAddress)(unsafe.Pointer(&in.Addresses)) + if err := Convert_v1_NodeDaemonEndpoints_To_core_NodeDaemonEndpoints(&in.DaemonEndpoints, &out.DaemonEndpoints, s); err != nil { + return err + } + if err := Convert_v1_NodeSystemInfo_To_core_NodeSystemInfo(&in.NodeInfo, &out.NodeInfo, s); err != nil { + return err + } + out.Images = *(*[]core.ContainerImage)(unsafe.Pointer(&in.Images)) + out.VolumesInUse = *(*[]core.UniqueVolumeName)(unsafe.Pointer(&in.VolumesInUse)) + out.VolumesAttached = *(*[]core.AttachedVolume)(unsafe.Pointer(&in.VolumesAttached)) + return nil +} + +// Convert_v1_NodeStatus_To_core_NodeStatus is an autogenerated conversion function. +func Convert_v1_NodeStatus_To_core_NodeStatus(in *v1.NodeStatus, out *core.NodeStatus, s conversion.Scope) error { + return autoConvert_v1_NodeStatus_To_core_NodeStatus(in, out, s) +} + +func autoConvert_core_NodeStatus_To_v1_NodeStatus(in *core.NodeStatus, out *v1.NodeStatus, s conversion.Scope) error { + out.Capacity = *(*v1.ResourceList)(unsafe.Pointer(&in.Capacity)) + out.Allocatable = *(*v1.ResourceList)(unsafe.Pointer(&in.Allocatable)) + out.Phase = v1.NodePhase(in.Phase) + out.Conditions = *(*[]v1.NodeCondition)(unsafe.Pointer(&in.Conditions)) + out.Addresses = *(*[]v1.NodeAddress)(unsafe.Pointer(&in.Addresses)) + if err := Convert_core_NodeDaemonEndpoints_To_v1_NodeDaemonEndpoints(&in.DaemonEndpoints, &out.DaemonEndpoints, s); err != nil { + return err + } + if err := Convert_core_NodeSystemInfo_To_v1_NodeSystemInfo(&in.NodeInfo, &out.NodeInfo, s); err != nil { + return err + } + out.Images = *(*[]v1.ContainerImage)(unsafe.Pointer(&in.Images)) + out.VolumesInUse = *(*[]v1.UniqueVolumeName)(unsafe.Pointer(&in.VolumesInUse)) + out.VolumesAttached = *(*[]v1.AttachedVolume)(unsafe.Pointer(&in.VolumesAttached)) + return nil +} + +// Convert_core_NodeStatus_To_v1_NodeStatus is an autogenerated conversion function. +func Convert_core_NodeStatus_To_v1_NodeStatus(in *core.NodeStatus, out *v1.NodeStatus, s conversion.Scope) error { + return autoConvert_core_NodeStatus_To_v1_NodeStatus(in, out, s) +} + +func autoConvert_v1_NodeSystemInfo_To_core_NodeSystemInfo(in *v1.NodeSystemInfo, out *core.NodeSystemInfo, s conversion.Scope) error { + out.MachineID = in.MachineID + out.SystemUUID = in.SystemUUID + out.BootID = in.BootID + out.KernelVersion = in.KernelVersion + out.OSImage = in.OSImage + out.ContainerRuntimeVersion = in.ContainerRuntimeVersion + out.KubeletVersion = in.KubeletVersion + out.KubeProxyVersion = in.KubeProxyVersion + out.OperatingSystem = in.OperatingSystem + out.Architecture = in.Architecture + return nil +} + +// Convert_v1_NodeSystemInfo_To_core_NodeSystemInfo is an autogenerated conversion function. +func Convert_v1_NodeSystemInfo_To_core_NodeSystemInfo(in *v1.NodeSystemInfo, out *core.NodeSystemInfo, s conversion.Scope) error { + return autoConvert_v1_NodeSystemInfo_To_core_NodeSystemInfo(in, out, s) +} + +func autoConvert_core_NodeSystemInfo_To_v1_NodeSystemInfo(in *core.NodeSystemInfo, out *v1.NodeSystemInfo, s conversion.Scope) error { + out.MachineID = in.MachineID + out.SystemUUID = in.SystemUUID + out.BootID = in.BootID + out.KernelVersion = in.KernelVersion + out.OSImage = in.OSImage + out.ContainerRuntimeVersion = in.ContainerRuntimeVersion + out.KubeletVersion = in.KubeletVersion + out.KubeProxyVersion = in.KubeProxyVersion + out.OperatingSystem = in.OperatingSystem + out.Architecture = in.Architecture + return nil +} + +// Convert_core_NodeSystemInfo_To_v1_NodeSystemInfo is an autogenerated conversion function. +func Convert_core_NodeSystemInfo_To_v1_NodeSystemInfo(in *core.NodeSystemInfo, out *v1.NodeSystemInfo, s conversion.Scope) error { + return autoConvert_core_NodeSystemInfo_To_v1_NodeSystemInfo(in, out, s) +} + +func autoConvert_v1_ObjectFieldSelector_To_core_ObjectFieldSelector(in *v1.ObjectFieldSelector, out *core.ObjectFieldSelector, s conversion.Scope) error { + out.APIVersion = in.APIVersion + out.FieldPath = in.FieldPath + return nil +} + +// Convert_v1_ObjectFieldSelector_To_core_ObjectFieldSelector is an autogenerated conversion function. +func Convert_v1_ObjectFieldSelector_To_core_ObjectFieldSelector(in *v1.ObjectFieldSelector, out *core.ObjectFieldSelector, s conversion.Scope) error { + return autoConvert_v1_ObjectFieldSelector_To_core_ObjectFieldSelector(in, out, s) +} + +func autoConvert_core_ObjectFieldSelector_To_v1_ObjectFieldSelector(in *core.ObjectFieldSelector, out *v1.ObjectFieldSelector, s conversion.Scope) error { + out.APIVersion = in.APIVersion + out.FieldPath = in.FieldPath + return nil +} + +// Convert_core_ObjectFieldSelector_To_v1_ObjectFieldSelector is an autogenerated conversion function. +func Convert_core_ObjectFieldSelector_To_v1_ObjectFieldSelector(in *core.ObjectFieldSelector, out *v1.ObjectFieldSelector, s conversion.Scope) error { + return autoConvert_core_ObjectFieldSelector_To_v1_ObjectFieldSelector(in, out, s) +} + +func autoConvert_v1_ObjectMeta_To_core_ObjectMeta(in *v1.ObjectMeta, out *core.ObjectMeta, s conversion.Scope) error { + out.Name = in.Name + out.GenerateName = in.GenerateName + out.Namespace = in.Namespace + out.SelfLink = in.SelfLink + out.UID = types.UID(in.UID) + out.ResourceVersion = in.ResourceVersion + out.Generation = in.Generation + out.CreationTimestamp = in.CreationTimestamp + out.DeletionTimestamp = (*meta_v1.Time)(unsafe.Pointer(in.DeletionTimestamp)) + out.DeletionGracePeriodSeconds = (*int64)(unsafe.Pointer(in.DeletionGracePeriodSeconds)) + out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) + out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) + out.OwnerReferences = *(*[]meta_v1.OwnerReference)(unsafe.Pointer(&in.OwnerReferences)) + out.Initializers = (*meta_v1.Initializers)(unsafe.Pointer(in.Initializers)) + out.Finalizers = *(*[]string)(unsafe.Pointer(&in.Finalizers)) + out.ClusterName = in.ClusterName + return nil +} + +// Convert_v1_ObjectMeta_To_core_ObjectMeta is an autogenerated conversion function. +func Convert_v1_ObjectMeta_To_core_ObjectMeta(in *v1.ObjectMeta, out *core.ObjectMeta, s conversion.Scope) error { + return autoConvert_v1_ObjectMeta_To_core_ObjectMeta(in, out, s) +} + +func autoConvert_core_ObjectMeta_To_v1_ObjectMeta(in *core.ObjectMeta, out *v1.ObjectMeta, s conversion.Scope) error { + out.Name = in.Name + out.GenerateName = in.GenerateName + out.Namespace = in.Namespace + out.SelfLink = in.SelfLink + out.UID = types.UID(in.UID) + out.ResourceVersion = in.ResourceVersion + out.Generation = in.Generation + out.CreationTimestamp = in.CreationTimestamp + out.DeletionTimestamp = (*meta_v1.Time)(unsafe.Pointer(in.DeletionTimestamp)) + out.DeletionGracePeriodSeconds = (*int64)(unsafe.Pointer(in.DeletionGracePeriodSeconds)) + out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) + out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) + out.OwnerReferences = *(*[]meta_v1.OwnerReference)(unsafe.Pointer(&in.OwnerReferences)) + out.Initializers = (*meta_v1.Initializers)(unsafe.Pointer(in.Initializers)) + out.Finalizers = *(*[]string)(unsafe.Pointer(&in.Finalizers)) + out.ClusterName = in.ClusterName + return nil +} + +// Convert_core_ObjectMeta_To_v1_ObjectMeta is an autogenerated conversion function. +func Convert_core_ObjectMeta_To_v1_ObjectMeta(in *core.ObjectMeta, out *v1.ObjectMeta, s conversion.Scope) error { + return autoConvert_core_ObjectMeta_To_v1_ObjectMeta(in, out, s) +} + +func autoConvert_v1_ObjectReference_To_core_ObjectReference(in *v1.ObjectReference, out *core.ObjectReference, s conversion.Scope) error { + out.Kind = in.Kind + out.Namespace = in.Namespace + out.Name = in.Name + out.UID = types.UID(in.UID) + out.APIVersion = in.APIVersion + out.ResourceVersion = in.ResourceVersion + out.FieldPath = in.FieldPath + return nil +} + +// Convert_v1_ObjectReference_To_core_ObjectReference is an autogenerated conversion function. +func Convert_v1_ObjectReference_To_core_ObjectReference(in *v1.ObjectReference, out *core.ObjectReference, s conversion.Scope) error { + return autoConvert_v1_ObjectReference_To_core_ObjectReference(in, out, s) +} + +func autoConvert_core_ObjectReference_To_v1_ObjectReference(in *core.ObjectReference, out *v1.ObjectReference, s conversion.Scope) error { + out.Kind = in.Kind + out.Namespace = in.Namespace + out.Name = in.Name + out.UID = types.UID(in.UID) + out.APIVersion = in.APIVersion + out.ResourceVersion = in.ResourceVersion + out.FieldPath = in.FieldPath + return nil +} + +// Convert_core_ObjectReference_To_v1_ObjectReference is an autogenerated conversion function. +func Convert_core_ObjectReference_To_v1_ObjectReference(in *core.ObjectReference, out *v1.ObjectReference, s conversion.Scope) error { + return autoConvert_core_ObjectReference_To_v1_ObjectReference(in, out, s) +} + +func autoConvert_v1_PersistentVolume_To_core_PersistentVolume(in *v1.PersistentVolume, out *core.PersistentVolume, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_PersistentVolumeSpec_To_core_PersistentVolumeSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1_PersistentVolumeStatus_To_core_PersistentVolumeStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1_PersistentVolume_To_core_PersistentVolume is an autogenerated conversion function. +func Convert_v1_PersistentVolume_To_core_PersistentVolume(in *v1.PersistentVolume, out *core.PersistentVolume, s conversion.Scope) error { + return autoConvert_v1_PersistentVolume_To_core_PersistentVolume(in, out, s) +} + +func autoConvert_core_PersistentVolume_To_v1_PersistentVolume(in *core.PersistentVolume, out *v1.PersistentVolume, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_core_PersistentVolumeSpec_To_v1_PersistentVolumeSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_core_PersistentVolumeStatus_To_v1_PersistentVolumeStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_core_PersistentVolume_To_v1_PersistentVolume is an autogenerated conversion function. +func Convert_core_PersistentVolume_To_v1_PersistentVolume(in *core.PersistentVolume, out *v1.PersistentVolume, s conversion.Scope) error { + return autoConvert_core_PersistentVolume_To_v1_PersistentVolume(in, out, s) +} + +func autoConvert_v1_PersistentVolumeClaim_To_core_PersistentVolumeClaim(in *v1.PersistentVolumeClaim, out *core.PersistentVolumeClaim, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_PersistentVolumeClaimSpec_To_core_PersistentVolumeClaimSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1_PersistentVolumeClaimStatus_To_core_PersistentVolumeClaimStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1_PersistentVolumeClaim_To_core_PersistentVolumeClaim is an autogenerated conversion function. +func Convert_v1_PersistentVolumeClaim_To_core_PersistentVolumeClaim(in *v1.PersistentVolumeClaim, out *core.PersistentVolumeClaim, s conversion.Scope) error { + return autoConvert_v1_PersistentVolumeClaim_To_core_PersistentVolumeClaim(in, out, s) +} + +func autoConvert_core_PersistentVolumeClaim_To_v1_PersistentVolumeClaim(in *core.PersistentVolumeClaim, out *v1.PersistentVolumeClaim, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_core_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_core_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_core_PersistentVolumeClaim_To_v1_PersistentVolumeClaim is an autogenerated conversion function. +func Convert_core_PersistentVolumeClaim_To_v1_PersistentVolumeClaim(in *core.PersistentVolumeClaim, out *v1.PersistentVolumeClaim, s conversion.Scope) error { + return autoConvert_core_PersistentVolumeClaim_To_v1_PersistentVolumeClaim(in, out, s) +} + +func autoConvert_v1_PersistentVolumeClaimCondition_To_core_PersistentVolumeClaimCondition(in *v1.PersistentVolumeClaimCondition, out *core.PersistentVolumeClaimCondition, s conversion.Scope) error { + out.Type = core.PersistentVolumeClaimConditionType(in.Type) + out.Status = core.ConditionStatus(in.Status) + out.LastProbeTime = in.LastProbeTime + out.LastTransitionTime = in.LastTransitionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_v1_PersistentVolumeClaimCondition_To_core_PersistentVolumeClaimCondition is an autogenerated conversion function. +func Convert_v1_PersistentVolumeClaimCondition_To_core_PersistentVolumeClaimCondition(in *v1.PersistentVolumeClaimCondition, out *core.PersistentVolumeClaimCondition, s conversion.Scope) error { + return autoConvert_v1_PersistentVolumeClaimCondition_To_core_PersistentVolumeClaimCondition(in, out, s) +} + +func autoConvert_core_PersistentVolumeClaimCondition_To_v1_PersistentVolumeClaimCondition(in *core.PersistentVolumeClaimCondition, out *v1.PersistentVolumeClaimCondition, s conversion.Scope) error { + out.Type = v1.PersistentVolumeClaimConditionType(in.Type) + out.Status = v1.ConditionStatus(in.Status) + out.LastProbeTime = in.LastProbeTime + out.LastTransitionTime = in.LastTransitionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_core_PersistentVolumeClaimCondition_To_v1_PersistentVolumeClaimCondition is an autogenerated conversion function. +func Convert_core_PersistentVolumeClaimCondition_To_v1_PersistentVolumeClaimCondition(in *core.PersistentVolumeClaimCondition, out *v1.PersistentVolumeClaimCondition, s conversion.Scope) error { + return autoConvert_core_PersistentVolumeClaimCondition_To_v1_PersistentVolumeClaimCondition(in, out, s) +} + +func autoConvert_v1_PersistentVolumeClaimList_To_core_PersistentVolumeClaimList(in *v1.PersistentVolumeClaimList, out *core.PersistentVolumeClaimList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]core.PersistentVolumeClaim)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1_PersistentVolumeClaimList_To_core_PersistentVolumeClaimList is an autogenerated conversion function. +func Convert_v1_PersistentVolumeClaimList_To_core_PersistentVolumeClaimList(in *v1.PersistentVolumeClaimList, out *core.PersistentVolumeClaimList, s conversion.Scope) error { + return autoConvert_v1_PersistentVolumeClaimList_To_core_PersistentVolumeClaimList(in, out, s) +} + +func autoConvert_core_PersistentVolumeClaimList_To_v1_PersistentVolumeClaimList(in *core.PersistentVolumeClaimList, out *v1.PersistentVolumeClaimList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]v1.PersistentVolumeClaim)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_core_PersistentVolumeClaimList_To_v1_PersistentVolumeClaimList is an autogenerated conversion function. +func Convert_core_PersistentVolumeClaimList_To_v1_PersistentVolumeClaimList(in *core.PersistentVolumeClaimList, out *v1.PersistentVolumeClaimList, s conversion.Scope) error { + return autoConvert_core_PersistentVolumeClaimList_To_v1_PersistentVolumeClaimList(in, out, s) +} + +func autoConvert_v1_PersistentVolumeClaimSpec_To_core_PersistentVolumeClaimSpec(in *v1.PersistentVolumeClaimSpec, out *core.PersistentVolumeClaimSpec, s conversion.Scope) error { + out.AccessModes = *(*[]core.PersistentVolumeAccessMode)(unsafe.Pointer(&in.AccessModes)) + out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) + if err := Convert_v1_ResourceRequirements_To_core_ResourceRequirements(&in.Resources, &out.Resources, s); err != nil { + return err + } + out.VolumeName = in.VolumeName + out.StorageClassName = (*string)(unsafe.Pointer(in.StorageClassName)) + out.VolumeMode = (*core.PersistentVolumeMode)(unsafe.Pointer(in.VolumeMode)) + return nil +} + +// Convert_v1_PersistentVolumeClaimSpec_To_core_PersistentVolumeClaimSpec is an autogenerated conversion function. +func Convert_v1_PersistentVolumeClaimSpec_To_core_PersistentVolumeClaimSpec(in *v1.PersistentVolumeClaimSpec, out *core.PersistentVolumeClaimSpec, s conversion.Scope) error { + return autoConvert_v1_PersistentVolumeClaimSpec_To_core_PersistentVolumeClaimSpec(in, out, s) +} + +func autoConvert_core_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec(in *core.PersistentVolumeClaimSpec, out *v1.PersistentVolumeClaimSpec, s conversion.Scope) error { + out.AccessModes = *(*[]v1.PersistentVolumeAccessMode)(unsafe.Pointer(&in.AccessModes)) + out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) + if err := Convert_core_ResourceRequirements_To_v1_ResourceRequirements(&in.Resources, &out.Resources, s); err != nil { + return err + } + out.VolumeName = in.VolumeName + out.StorageClassName = (*string)(unsafe.Pointer(in.StorageClassName)) + out.VolumeMode = (*v1.PersistentVolumeMode)(unsafe.Pointer(in.VolumeMode)) + return nil +} + +// Convert_core_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec is an autogenerated conversion function. +func Convert_core_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec(in *core.PersistentVolumeClaimSpec, out *v1.PersistentVolumeClaimSpec, s conversion.Scope) error { + return autoConvert_core_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec(in, out, s) +} + +func autoConvert_v1_PersistentVolumeClaimStatus_To_core_PersistentVolumeClaimStatus(in *v1.PersistentVolumeClaimStatus, out *core.PersistentVolumeClaimStatus, s conversion.Scope) error { + out.Phase = core.PersistentVolumeClaimPhase(in.Phase) + out.AccessModes = *(*[]core.PersistentVolumeAccessMode)(unsafe.Pointer(&in.AccessModes)) + out.Capacity = *(*core.ResourceList)(unsafe.Pointer(&in.Capacity)) + out.Conditions = *(*[]core.PersistentVolumeClaimCondition)(unsafe.Pointer(&in.Conditions)) + return nil +} + +// Convert_v1_PersistentVolumeClaimStatus_To_core_PersistentVolumeClaimStatus is an autogenerated conversion function. +func Convert_v1_PersistentVolumeClaimStatus_To_core_PersistentVolumeClaimStatus(in *v1.PersistentVolumeClaimStatus, out *core.PersistentVolumeClaimStatus, s conversion.Scope) error { + return autoConvert_v1_PersistentVolumeClaimStatus_To_core_PersistentVolumeClaimStatus(in, out, s) +} + +func autoConvert_core_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus(in *core.PersistentVolumeClaimStatus, out *v1.PersistentVolumeClaimStatus, s conversion.Scope) error { + out.Phase = v1.PersistentVolumeClaimPhase(in.Phase) + out.AccessModes = *(*[]v1.PersistentVolumeAccessMode)(unsafe.Pointer(&in.AccessModes)) + out.Capacity = *(*v1.ResourceList)(unsafe.Pointer(&in.Capacity)) + out.Conditions = *(*[]v1.PersistentVolumeClaimCondition)(unsafe.Pointer(&in.Conditions)) + return nil +} + +// Convert_core_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus is an autogenerated conversion function. +func Convert_core_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus(in *core.PersistentVolumeClaimStatus, out *v1.PersistentVolumeClaimStatus, s conversion.Scope) error { + return autoConvert_core_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus(in, out, s) +} + +func autoConvert_v1_PersistentVolumeClaimVolumeSource_To_core_PersistentVolumeClaimVolumeSource(in *v1.PersistentVolumeClaimVolumeSource, out *core.PersistentVolumeClaimVolumeSource, s conversion.Scope) error { + out.ClaimName = in.ClaimName + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_v1_PersistentVolumeClaimVolumeSource_To_core_PersistentVolumeClaimVolumeSource is an autogenerated conversion function. +func Convert_v1_PersistentVolumeClaimVolumeSource_To_core_PersistentVolumeClaimVolumeSource(in *v1.PersistentVolumeClaimVolumeSource, out *core.PersistentVolumeClaimVolumeSource, s conversion.Scope) error { + return autoConvert_v1_PersistentVolumeClaimVolumeSource_To_core_PersistentVolumeClaimVolumeSource(in, out, s) +} + +func autoConvert_core_PersistentVolumeClaimVolumeSource_To_v1_PersistentVolumeClaimVolumeSource(in *core.PersistentVolumeClaimVolumeSource, out *v1.PersistentVolumeClaimVolumeSource, s conversion.Scope) error { + out.ClaimName = in.ClaimName + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_core_PersistentVolumeClaimVolumeSource_To_v1_PersistentVolumeClaimVolumeSource is an autogenerated conversion function. +func Convert_core_PersistentVolumeClaimVolumeSource_To_v1_PersistentVolumeClaimVolumeSource(in *core.PersistentVolumeClaimVolumeSource, out *v1.PersistentVolumeClaimVolumeSource, s conversion.Scope) error { + return autoConvert_core_PersistentVolumeClaimVolumeSource_To_v1_PersistentVolumeClaimVolumeSource(in, out, s) +} + +func autoConvert_v1_PersistentVolumeList_To_core_PersistentVolumeList(in *v1.PersistentVolumeList, out *core.PersistentVolumeList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]core.PersistentVolume, len(*in)) + for i := range *in { + if err := Convert_v1_PersistentVolume_To_core_PersistentVolume(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_v1_PersistentVolumeList_To_core_PersistentVolumeList is an autogenerated conversion function. +func Convert_v1_PersistentVolumeList_To_core_PersistentVolumeList(in *v1.PersistentVolumeList, out *core.PersistentVolumeList, s conversion.Scope) error { + return autoConvert_v1_PersistentVolumeList_To_core_PersistentVolumeList(in, out, s) +} + +func autoConvert_core_PersistentVolumeList_To_v1_PersistentVolumeList(in *core.PersistentVolumeList, out *v1.PersistentVolumeList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1.PersistentVolume, len(*in)) + for i := range *in { + if err := Convert_core_PersistentVolume_To_v1_PersistentVolume(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_core_PersistentVolumeList_To_v1_PersistentVolumeList is an autogenerated conversion function. +func Convert_core_PersistentVolumeList_To_v1_PersistentVolumeList(in *core.PersistentVolumeList, out *v1.PersistentVolumeList, s conversion.Scope) error { + return autoConvert_core_PersistentVolumeList_To_v1_PersistentVolumeList(in, out, s) +} + +func autoConvert_v1_PersistentVolumeSource_To_core_PersistentVolumeSource(in *v1.PersistentVolumeSource, out *core.PersistentVolumeSource, s conversion.Scope) error { + out.GCEPersistentDisk = (*core.GCEPersistentDiskVolumeSource)(unsafe.Pointer(in.GCEPersistentDisk)) + out.AWSElasticBlockStore = (*core.AWSElasticBlockStoreVolumeSource)(unsafe.Pointer(in.AWSElasticBlockStore)) + out.HostPath = (*core.HostPathVolumeSource)(unsafe.Pointer(in.HostPath)) + out.Glusterfs = (*core.GlusterfsVolumeSource)(unsafe.Pointer(in.Glusterfs)) + out.NFS = (*core.NFSVolumeSource)(unsafe.Pointer(in.NFS)) + out.RBD = (*core.RBDPersistentVolumeSource)(unsafe.Pointer(in.RBD)) + out.ISCSI = (*core.ISCSIPersistentVolumeSource)(unsafe.Pointer(in.ISCSI)) + out.Cinder = (*core.CinderVolumeSource)(unsafe.Pointer(in.Cinder)) + out.CephFS = (*core.CephFSPersistentVolumeSource)(unsafe.Pointer(in.CephFS)) + out.FC = (*core.FCVolumeSource)(unsafe.Pointer(in.FC)) + out.Flocker = (*core.FlockerVolumeSource)(unsafe.Pointer(in.Flocker)) + out.FlexVolume = (*core.FlexPersistentVolumeSource)(unsafe.Pointer(in.FlexVolume)) + out.AzureFile = (*core.AzureFilePersistentVolumeSource)(unsafe.Pointer(in.AzureFile)) + out.VsphereVolume = (*core.VsphereVirtualDiskVolumeSource)(unsafe.Pointer(in.VsphereVolume)) + out.Quobyte = (*core.QuobyteVolumeSource)(unsafe.Pointer(in.Quobyte)) + out.AzureDisk = (*core.AzureDiskVolumeSource)(unsafe.Pointer(in.AzureDisk)) + out.PhotonPersistentDisk = (*core.PhotonPersistentDiskVolumeSource)(unsafe.Pointer(in.PhotonPersistentDisk)) + out.PortworxVolume = (*core.PortworxVolumeSource)(unsafe.Pointer(in.PortworxVolume)) + out.ScaleIO = (*core.ScaleIOPersistentVolumeSource)(unsafe.Pointer(in.ScaleIO)) + out.Local = (*core.LocalVolumeSource)(unsafe.Pointer(in.Local)) + out.StorageOS = (*core.StorageOSPersistentVolumeSource)(unsafe.Pointer(in.StorageOS)) + out.CSI = (*core.CSIPersistentVolumeSource)(unsafe.Pointer(in.CSI)) + return nil +} + +// Convert_v1_PersistentVolumeSource_To_core_PersistentVolumeSource is an autogenerated conversion function. +func Convert_v1_PersistentVolumeSource_To_core_PersistentVolumeSource(in *v1.PersistentVolumeSource, out *core.PersistentVolumeSource, s conversion.Scope) error { + return autoConvert_v1_PersistentVolumeSource_To_core_PersistentVolumeSource(in, out, s) +} + +func autoConvert_core_PersistentVolumeSource_To_v1_PersistentVolumeSource(in *core.PersistentVolumeSource, out *v1.PersistentVolumeSource, s conversion.Scope) error { + out.GCEPersistentDisk = (*v1.GCEPersistentDiskVolumeSource)(unsafe.Pointer(in.GCEPersistentDisk)) + out.AWSElasticBlockStore = (*v1.AWSElasticBlockStoreVolumeSource)(unsafe.Pointer(in.AWSElasticBlockStore)) + out.HostPath = (*v1.HostPathVolumeSource)(unsafe.Pointer(in.HostPath)) + out.Glusterfs = (*v1.GlusterfsVolumeSource)(unsafe.Pointer(in.Glusterfs)) + out.NFS = (*v1.NFSVolumeSource)(unsafe.Pointer(in.NFS)) + out.RBD = (*v1.RBDPersistentVolumeSource)(unsafe.Pointer(in.RBD)) + out.Quobyte = (*v1.QuobyteVolumeSource)(unsafe.Pointer(in.Quobyte)) + out.ISCSI = (*v1.ISCSIPersistentVolumeSource)(unsafe.Pointer(in.ISCSI)) + out.FlexVolume = (*v1.FlexPersistentVolumeSource)(unsafe.Pointer(in.FlexVolume)) + out.Cinder = (*v1.CinderVolumeSource)(unsafe.Pointer(in.Cinder)) + out.CephFS = (*v1.CephFSPersistentVolumeSource)(unsafe.Pointer(in.CephFS)) + out.FC = (*v1.FCVolumeSource)(unsafe.Pointer(in.FC)) + out.Flocker = (*v1.FlockerVolumeSource)(unsafe.Pointer(in.Flocker)) + out.AzureFile = (*v1.AzureFilePersistentVolumeSource)(unsafe.Pointer(in.AzureFile)) + out.VsphereVolume = (*v1.VsphereVirtualDiskVolumeSource)(unsafe.Pointer(in.VsphereVolume)) + out.AzureDisk = (*v1.AzureDiskVolumeSource)(unsafe.Pointer(in.AzureDisk)) + out.PhotonPersistentDisk = (*v1.PhotonPersistentDiskVolumeSource)(unsafe.Pointer(in.PhotonPersistentDisk)) + out.PortworxVolume = (*v1.PortworxVolumeSource)(unsafe.Pointer(in.PortworxVolume)) + out.ScaleIO = (*v1.ScaleIOPersistentVolumeSource)(unsafe.Pointer(in.ScaleIO)) + out.Local = (*v1.LocalVolumeSource)(unsafe.Pointer(in.Local)) + out.StorageOS = (*v1.StorageOSPersistentVolumeSource)(unsafe.Pointer(in.StorageOS)) + out.CSI = (*v1.CSIPersistentVolumeSource)(unsafe.Pointer(in.CSI)) + return nil +} + +// Convert_core_PersistentVolumeSource_To_v1_PersistentVolumeSource is an autogenerated conversion function. +func Convert_core_PersistentVolumeSource_To_v1_PersistentVolumeSource(in *core.PersistentVolumeSource, out *v1.PersistentVolumeSource, s conversion.Scope) error { + return autoConvert_core_PersistentVolumeSource_To_v1_PersistentVolumeSource(in, out, s) +} + +func autoConvert_v1_PersistentVolumeSpec_To_core_PersistentVolumeSpec(in *v1.PersistentVolumeSpec, out *core.PersistentVolumeSpec, s conversion.Scope) error { + out.Capacity = *(*core.ResourceList)(unsafe.Pointer(&in.Capacity)) + if err := Convert_v1_PersistentVolumeSource_To_core_PersistentVolumeSource(&in.PersistentVolumeSource, &out.PersistentVolumeSource, s); err != nil { + return err + } + out.AccessModes = *(*[]core.PersistentVolumeAccessMode)(unsafe.Pointer(&in.AccessModes)) + out.ClaimRef = (*core.ObjectReference)(unsafe.Pointer(in.ClaimRef)) + out.PersistentVolumeReclaimPolicy = core.PersistentVolumeReclaimPolicy(in.PersistentVolumeReclaimPolicy) + out.StorageClassName = in.StorageClassName + out.MountOptions = *(*[]string)(unsafe.Pointer(&in.MountOptions)) + out.VolumeMode = (*core.PersistentVolumeMode)(unsafe.Pointer(in.VolumeMode)) + return nil +} + +// Convert_v1_PersistentVolumeSpec_To_core_PersistentVolumeSpec is an autogenerated conversion function. +func Convert_v1_PersistentVolumeSpec_To_core_PersistentVolumeSpec(in *v1.PersistentVolumeSpec, out *core.PersistentVolumeSpec, s conversion.Scope) error { + return autoConvert_v1_PersistentVolumeSpec_To_core_PersistentVolumeSpec(in, out, s) +} + +func autoConvert_core_PersistentVolumeSpec_To_v1_PersistentVolumeSpec(in *core.PersistentVolumeSpec, out *v1.PersistentVolumeSpec, s conversion.Scope) error { + out.Capacity = *(*v1.ResourceList)(unsafe.Pointer(&in.Capacity)) + if err := Convert_core_PersistentVolumeSource_To_v1_PersistentVolumeSource(&in.PersistentVolumeSource, &out.PersistentVolumeSource, s); err != nil { + return err + } + out.AccessModes = *(*[]v1.PersistentVolumeAccessMode)(unsafe.Pointer(&in.AccessModes)) + out.ClaimRef = (*v1.ObjectReference)(unsafe.Pointer(in.ClaimRef)) + out.PersistentVolumeReclaimPolicy = v1.PersistentVolumeReclaimPolicy(in.PersistentVolumeReclaimPolicy) + out.StorageClassName = in.StorageClassName + out.MountOptions = *(*[]string)(unsafe.Pointer(&in.MountOptions)) + out.VolumeMode = (*v1.PersistentVolumeMode)(unsafe.Pointer(in.VolumeMode)) + return nil +} + +// Convert_core_PersistentVolumeSpec_To_v1_PersistentVolumeSpec is an autogenerated conversion function. +func Convert_core_PersistentVolumeSpec_To_v1_PersistentVolumeSpec(in *core.PersistentVolumeSpec, out *v1.PersistentVolumeSpec, s conversion.Scope) error { + return autoConvert_core_PersistentVolumeSpec_To_v1_PersistentVolumeSpec(in, out, s) +} + +func autoConvert_v1_PersistentVolumeStatus_To_core_PersistentVolumeStatus(in *v1.PersistentVolumeStatus, out *core.PersistentVolumeStatus, s conversion.Scope) error { + out.Phase = core.PersistentVolumePhase(in.Phase) + out.Message = in.Message + out.Reason = in.Reason + return nil +} + +// Convert_v1_PersistentVolumeStatus_To_core_PersistentVolumeStatus is an autogenerated conversion function. +func Convert_v1_PersistentVolumeStatus_To_core_PersistentVolumeStatus(in *v1.PersistentVolumeStatus, out *core.PersistentVolumeStatus, s conversion.Scope) error { + return autoConvert_v1_PersistentVolumeStatus_To_core_PersistentVolumeStatus(in, out, s) +} + +func autoConvert_core_PersistentVolumeStatus_To_v1_PersistentVolumeStatus(in *core.PersistentVolumeStatus, out *v1.PersistentVolumeStatus, s conversion.Scope) error { + out.Phase = v1.PersistentVolumePhase(in.Phase) + out.Message = in.Message + out.Reason = in.Reason + return nil +} + +// Convert_core_PersistentVolumeStatus_To_v1_PersistentVolumeStatus is an autogenerated conversion function. +func Convert_core_PersistentVolumeStatus_To_v1_PersistentVolumeStatus(in *core.PersistentVolumeStatus, out *v1.PersistentVolumeStatus, s conversion.Scope) error { + return autoConvert_core_PersistentVolumeStatus_To_v1_PersistentVolumeStatus(in, out, s) +} + +func autoConvert_v1_PhotonPersistentDiskVolumeSource_To_core_PhotonPersistentDiskVolumeSource(in *v1.PhotonPersistentDiskVolumeSource, out *core.PhotonPersistentDiskVolumeSource, s conversion.Scope) error { + out.PdID = in.PdID + out.FSType = in.FSType + return nil +} + +// Convert_v1_PhotonPersistentDiskVolumeSource_To_core_PhotonPersistentDiskVolumeSource is an autogenerated conversion function. +func Convert_v1_PhotonPersistentDiskVolumeSource_To_core_PhotonPersistentDiskVolumeSource(in *v1.PhotonPersistentDiskVolumeSource, out *core.PhotonPersistentDiskVolumeSource, s conversion.Scope) error { + return autoConvert_v1_PhotonPersistentDiskVolumeSource_To_core_PhotonPersistentDiskVolumeSource(in, out, s) +} + +func autoConvert_core_PhotonPersistentDiskVolumeSource_To_v1_PhotonPersistentDiskVolumeSource(in *core.PhotonPersistentDiskVolumeSource, out *v1.PhotonPersistentDiskVolumeSource, s conversion.Scope) error { + out.PdID = in.PdID + out.FSType = in.FSType + return nil +} + +// Convert_core_PhotonPersistentDiskVolumeSource_To_v1_PhotonPersistentDiskVolumeSource is an autogenerated conversion function. +func Convert_core_PhotonPersistentDiskVolumeSource_To_v1_PhotonPersistentDiskVolumeSource(in *core.PhotonPersistentDiskVolumeSource, out *v1.PhotonPersistentDiskVolumeSource, s conversion.Scope) error { + return autoConvert_core_PhotonPersistentDiskVolumeSource_To_v1_PhotonPersistentDiskVolumeSource(in, out, s) +} + +func autoConvert_v1_Pod_To_core_Pod(in *v1.Pod, out *core.Pod, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_PodSpec_To_core_PodSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1_PodStatus_To_core_PodStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1_Pod_To_core_Pod is an autogenerated conversion function. +func Convert_v1_Pod_To_core_Pod(in *v1.Pod, out *core.Pod, s conversion.Scope) error { + return autoConvert_v1_Pod_To_core_Pod(in, out, s) +} + +func autoConvert_core_Pod_To_v1_Pod(in *core.Pod, out *v1.Pod, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_core_PodSpec_To_v1_PodSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_core_PodStatus_To_v1_PodStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +func autoConvert_v1_PodAffinity_To_core_PodAffinity(in *v1.PodAffinity, out *core.PodAffinity, s conversion.Scope) error { + out.RequiredDuringSchedulingIgnoredDuringExecution = *(*[]core.PodAffinityTerm)(unsafe.Pointer(&in.RequiredDuringSchedulingIgnoredDuringExecution)) + out.PreferredDuringSchedulingIgnoredDuringExecution = *(*[]core.WeightedPodAffinityTerm)(unsafe.Pointer(&in.PreferredDuringSchedulingIgnoredDuringExecution)) + return nil +} + +// Convert_v1_PodAffinity_To_core_PodAffinity is an autogenerated conversion function. +func Convert_v1_PodAffinity_To_core_PodAffinity(in *v1.PodAffinity, out *core.PodAffinity, s conversion.Scope) error { + return autoConvert_v1_PodAffinity_To_core_PodAffinity(in, out, s) +} + +func autoConvert_core_PodAffinity_To_v1_PodAffinity(in *core.PodAffinity, out *v1.PodAffinity, s conversion.Scope) error { + out.RequiredDuringSchedulingIgnoredDuringExecution = *(*[]v1.PodAffinityTerm)(unsafe.Pointer(&in.RequiredDuringSchedulingIgnoredDuringExecution)) + out.PreferredDuringSchedulingIgnoredDuringExecution = *(*[]v1.WeightedPodAffinityTerm)(unsafe.Pointer(&in.PreferredDuringSchedulingIgnoredDuringExecution)) + return nil +} + +// Convert_core_PodAffinity_To_v1_PodAffinity is an autogenerated conversion function. +func Convert_core_PodAffinity_To_v1_PodAffinity(in *core.PodAffinity, out *v1.PodAffinity, s conversion.Scope) error { + return autoConvert_core_PodAffinity_To_v1_PodAffinity(in, out, s) +} + +func autoConvert_v1_PodAffinityTerm_To_core_PodAffinityTerm(in *v1.PodAffinityTerm, out *core.PodAffinityTerm, s conversion.Scope) error { + out.LabelSelector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.LabelSelector)) + out.Namespaces = *(*[]string)(unsafe.Pointer(&in.Namespaces)) + out.TopologyKey = in.TopologyKey + return nil +} + +// Convert_v1_PodAffinityTerm_To_core_PodAffinityTerm is an autogenerated conversion function. +func Convert_v1_PodAffinityTerm_To_core_PodAffinityTerm(in *v1.PodAffinityTerm, out *core.PodAffinityTerm, s conversion.Scope) error { + return autoConvert_v1_PodAffinityTerm_To_core_PodAffinityTerm(in, out, s) +} + +func autoConvert_core_PodAffinityTerm_To_v1_PodAffinityTerm(in *core.PodAffinityTerm, out *v1.PodAffinityTerm, s conversion.Scope) error { + out.LabelSelector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.LabelSelector)) + out.Namespaces = *(*[]string)(unsafe.Pointer(&in.Namespaces)) + out.TopologyKey = in.TopologyKey + return nil +} + +// Convert_core_PodAffinityTerm_To_v1_PodAffinityTerm is an autogenerated conversion function. +func Convert_core_PodAffinityTerm_To_v1_PodAffinityTerm(in *core.PodAffinityTerm, out *v1.PodAffinityTerm, s conversion.Scope) error { + return autoConvert_core_PodAffinityTerm_To_v1_PodAffinityTerm(in, out, s) +} + +func autoConvert_v1_PodAntiAffinity_To_core_PodAntiAffinity(in *v1.PodAntiAffinity, out *core.PodAntiAffinity, s conversion.Scope) error { + out.RequiredDuringSchedulingIgnoredDuringExecution = *(*[]core.PodAffinityTerm)(unsafe.Pointer(&in.RequiredDuringSchedulingIgnoredDuringExecution)) + out.PreferredDuringSchedulingIgnoredDuringExecution = *(*[]core.WeightedPodAffinityTerm)(unsafe.Pointer(&in.PreferredDuringSchedulingIgnoredDuringExecution)) + return nil +} + +// Convert_v1_PodAntiAffinity_To_core_PodAntiAffinity is an autogenerated conversion function. +func Convert_v1_PodAntiAffinity_To_core_PodAntiAffinity(in *v1.PodAntiAffinity, out *core.PodAntiAffinity, s conversion.Scope) error { + return autoConvert_v1_PodAntiAffinity_To_core_PodAntiAffinity(in, out, s) +} + +func autoConvert_core_PodAntiAffinity_To_v1_PodAntiAffinity(in *core.PodAntiAffinity, out *v1.PodAntiAffinity, s conversion.Scope) error { + out.RequiredDuringSchedulingIgnoredDuringExecution = *(*[]v1.PodAffinityTerm)(unsafe.Pointer(&in.RequiredDuringSchedulingIgnoredDuringExecution)) + out.PreferredDuringSchedulingIgnoredDuringExecution = *(*[]v1.WeightedPodAffinityTerm)(unsafe.Pointer(&in.PreferredDuringSchedulingIgnoredDuringExecution)) + return nil +} + +// Convert_core_PodAntiAffinity_To_v1_PodAntiAffinity is an autogenerated conversion function. +func Convert_core_PodAntiAffinity_To_v1_PodAntiAffinity(in *core.PodAntiAffinity, out *v1.PodAntiAffinity, s conversion.Scope) error { + return autoConvert_core_PodAntiAffinity_To_v1_PodAntiAffinity(in, out, s) +} + +func autoConvert_v1_PodAttachOptions_To_core_PodAttachOptions(in *v1.PodAttachOptions, out *core.PodAttachOptions, s conversion.Scope) error { + out.Stdin = in.Stdin + out.Stdout = in.Stdout + out.Stderr = in.Stderr + out.TTY = in.TTY + out.Container = in.Container + return nil +} + +// Convert_v1_PodAttachOptions_To_core_PodAttachOptions is an autogenerated conversion function. +func Convert_v1_PodAttachOptions_To_core_PodAttachOptions(in *v1.PodAttachOptions, out *core.PodAttachOptions, s conversion.Scope) error { + return autoConvert_v1_PodAttachOptions_To_core_PodAttachOptions(in, out, s) +} + +func autoConvert_core_PodAttachOptions_To_v1_PodAttachOptions(in *core.PodAttachOptions, out *v1.PodAttachOptions, s conversion.Scope) error { + out.Stdin = in.Stdin + out.Stdout = in.Stdout + out.Stderr = in.Stderr + out.TTY = in.TTY + out.Container = in.Container + return nil +} + +// Convert_core_PodAttachOptions_To_v1_PodAttachOptions is an autogenerated conversion function. +func Convert_core_PodAttachOptions_To_v1_PodAttachOptions(in *core.PodAttachOptions, out *v1.PodAttachOptions, s conversion.Scope) error { + return autoConvert_core_PodAttachOptions_To_v1_PodAttachOptions(in, out, s) +} + +func autoConvert_v1_PodCondition_To_core_PodCondition(in *v1.PodCondition, out *core.PodCondition, s conversion.Scope) error { + out.Type = core.PodConditionType(in.Type) + out.Status = core.ConditionStatus(in.Status) + out.LastProbeTime = in.LastProbeTime + out.LastTransitionTime = in.LastTransitionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_v1_PodCondition_To_core_PodCondition is an autogenerated conversion function. +func Convert_v1_PodCondition_To_core_PodCondition(in *v1.PodCondition, out *core.PodCondition, s conversion.Scope) error { + return autoConvert_v1_PodCondition_To_core_PodCondition(in, out, s) +} + +func autoConvert_core_PodCondition_To_v1_PodCondition(in *core.PodCondition, out *v1.PodCondition, s conversion.Scope) error { + out.Type = v1.PodConditionType(in.Type) + out.Status = v1.ConditionStatus(in.Status) + out.LastProbeTime = in.LastProbeTime + out.LastTransitionTime = in.LastTransitionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_core_PodCondition_To_v1_PodCondition is an autogenerated conversion function. +func Convert_core_PodCondition_To_v1_PodCondition(in *core.PodCondition, out *v1.PodCondition, s conversion.Scope) error { + return autoConvert_core_PodCondition_To_v1_PodCondition(in, out, s) +} + +func autoConvert_v1_PodDNSConfig_To_core_PodDNSConfig(in *v1.PodDNSConfig, out *core.PodDNSConfig, s conversion.Scope) error { + out.Nameservers = *(*[]string)(unsafe.Pointer(&in.Nameservers)) + out.Searches = *(*[]string)(unsafe.Pointer(&in.Searches)) + out.Options = *(*[]core.PodDNSConfigOption)(unsafe.Pointer(&in.Options)) + return nil +} + +// Convert_v1_PodDNSConfig_To_core_PodDNSConfig is an autogenerated conversion function. +func Convert_v1_PodDNSConfig_To_core_PodDNSConfig(in *v1.PodDNSConfig, out *core.PodDNSConfig, s conversion.Scope) error { + return autoConvert_v1_PodDNSConfig_To_core_PodDNSConfig(in, out, s) +} + +func autoConvert_core_PodDNSConfig_To_v1_PodDNSConfig(in *core.PodDNSConfig, out *v1.PodDNSConfig, s conversion.Scope) error { + out.Nameservers = *(*[]string)(unsafe.Pointer(&in.Nameservers)) + out.Searches = *(*[]string)(unsafe.Pointer(&in.Searches)) + out.Options = *(*[]v1.PodDNSConfigOption)(unsafe.Pointer(&in.Options)) + return nil +} + +// Convert_core_PodDNSConfig_To_v1_PodDNSConfig is an autogenerated conversion function. +func Convert_core_PodDNSConfig_To_v1_PodDNSConfig(in *core.PodDNSConfig, out *v1.PodDNSConfig, s conversion.Scope) error { + return autoConvert_core_PodDNSConfig_To_v1_PodDNSConfig(in, out, s) +} + +func autoConvert_v1_PodDNSConfigOption_To_core_PodDNSConfigOption(in *v1.PodDNSConfigOption, out *core.PodDNSConfigOption, s conversion.Scope) error { + out.Name = in.Name + out.Value = (*string)(unsafe.Pointer(in.Value)) + return nil +} + +// Convert_v1_PodDNSConfigOption_To_core_PodDNSConfigOption is an autogenerated conversion function. +func Convert_v1_PodDNSConfigOption_To_core_PodDNSConfigOption(in *v1.PodDNSConfigOption, out *core.PodDNSConfigOption, s conversion.Scope) error { + return autoConvert_v1_PodDNSConfigOption_To_core_PodDNSConfigOption(in, out, s) +} + +func autoConvert_core_PodDNSConfigOption_To_v1_PodDNSConfigOption(in *core.PodDNSConfigOption, out *v1.PodDNSConfigOption, s conversion.Scope) error { + out.Name = in.Name + out.Value = (*string)(unsafe.Pointer(in.Value)) + return nil +} + +// Convert_core_PodDNSConfigOption_To_v1_PodDNSConfigOption is an autogenerated conversion function. +func Convert_core_PodDNSConfigOption_To_v1_PodDNSConfigOption(in *core.PodDNSConfigOption, out *v1.PodDNSConfigOption, s conversion.Scope) error { + return autoConvert_core_PodDNSConfigOption_To_v1_PodDNSConfigOption(in, out, s) +} + +func autoConvert_v1_PodExecOptions_To_core_PodExecOptions(in *v1.PodExecOptions, out *core.PodExecOptions, s conversion.Scope) error { + out.Stdin = in.Stdin + out.Stdout = in.Stdout + out.Stderr = in.Stderr + out.TTY = in.TTY + out.Container = in.Container + out.Command = *(*[]string)(unsafe.Pointer(&in.Command)) + return nil +} + +// Convert_v1_PodExecOptions_To_core_PodExecOptions is an autogenerated conversion function. +func Convert_v1_PodExecOptions_To_core_PodExecOptions(in *v1.PodExecOptions, out *core.PodExecOptions, s conversion.Scope) error { + return autoConvert_v1_PodExecOptions_To_core_PodExecOptions(in, out, s) +} + +func autoConvert_core_PodExecOptions_To_v1_PodExecOptions(in *core.PodExecOptions, out *v1.PodExecOptions, s conversion.Scope) error { + out.Stdin = in.Stdin + out.Stdout = in.Stdout + out.Stderr = in.Stderr + out.TTY = in.TTY + out.Container = in.Container + out.Command = *(*[]string)(unsafe.Pointer(&in.Command)) + return nil +} + +// Convert_core_PodExecOptions_To_v1_PodExecOptions is an autogenerated conversion function. +func Convert_core_PodExecOptions_To_v1_PodExecOptions(in *core.PodExecOptions, out *v1.PodExecOptions, s conversion.Scope) error { + return autoConvert_core_PodExecOptions_To_v1_PodExecOptions(in, out, s) +} + +func autoConvert_v1_PodList_To_core_PodList(in *v1.PodList, out *core.PodList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]core.Pod, len(*in)) + for i := range *in { + if err := Convert_v1_Pod_To_core_Pod(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_v1_PodList_To_core_PodList is an autogenerated conversion function. +func Convert_v1_PodList_To_core_PodList(in *v1.PodList, out *core.PodList, s conversion.Scope) error { + return autoConvert_v1_PodList_To_core_PodList(in, out, s) +} + +func autoConvert_core_PodList_To_v1_PodList(in *core.PodList, out *v1.PodList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1.Pod, len(*in)) + for i := range *in { + if err := Convert_core_Pod_To_v1_Pod(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_core_PodList_To_v1_PodList is an autogenerated conversion function. +func Convert_core_PodList_To_v1_PodList(in *core.PodList, out *v1.PodList, s conversion.Scope) error { + return autoConvert_core_PodList_To_v1_PodList(in, out, s) +} + +func autoConvert_v1_PodLogOptions_To_core_PodLogOptions(in *v1.PodLogOptions, out *core.PodLogOptions, s conversion.Scope) error { + out.Container = in.Container + out.Follow = in.Follow + out.Previous = in.Previous + out.SinceSeconds = (*int64)(unsafe.Pointer(in.SinceSeconds)) + out.SinceTime = (*meta_v1.Time)(unsafe.Pointer(in.SinceTime)) + out.Timestamps = in.Timestamps + out.TailLines = (*int64)(unsafe.Pointer(in.TailLines)) + out.LimitBytes = (*int64)(unsafe.Pointer(in.LimitBytes)) + return nil +} + +// Convert_v1_PodLogOptions_To_core_PodLogOptions is an autogenerated conversion function. +func Convert_v1_PodLogOptions_To_core_PodLogOptions(in *v1.PodLogOptions, out *core.PodLogOptions, s conversion.Scope) error { + return autoConvert_v1_PodLogOptions_To_core_PodLogOptions(in, out, s) +} + +func autoConvert_core_PodLogOptions_To_v1_PodLogOptions(in *core.PodLogOptions, out *v1.PodLogOptions, s conversion.Scope) error { + out.Container = in.Container + out.Follow = in.Follow + out.Previous = in.Previous + out.SinceSeconds = (*int64)(unsafe.Pointer(in.SinceSeconds)) + out.SinceTime = (*meta_v1.Time)(unsafe.Pointer(in.SinceTime)) + out.Timestamps = in.Timestamps + out.TailLines = (*int64)(unsafe.Pointer(in.TailLines)) + out.LimitBytes = (*int64)(unsafe.Pointer(in.LimitBytes)) + return nil +} + +// Convert_core_PodLogOptions_To_v1_PodLogOptions is an autogenerated conversion function. +func Convert_core_PodLogOptions_To_v1_PodLogOptions(in *core.PodLogOptions, out *v1.PodLogOptions, s conversion.Scope) error { + return autoConvert_core_PodLogOptions_To_v1_PodLogOptions(in, out, s) +} + +func autoConvert_v1_PodPortForwardOptions_To_core_PodPortForwardOptions(in *v1.PodPortForwardOptions, out *core.PodPortForwardOptions, s conversion.Scope) error { + out.Ports = *(*[]int32)(unsafe.Pointer(&in.Ports)) + return nil +} + +// Convert_v1_PodPortForwardOptions_To_core_PodPortForwardOptions is an autogenerated conversion function. +func Convert_v1_PodPortForwardOptions_To_core_PodPortForwardOptions(in *v1.PodPortForwardOptions, out *core.PodPortForwardOptions, s conversion.Scope) error { + return autoConvert_v1_PodPortForwardOptions_To_core_PodPortForwardOptions(in, out, s) +} + +func autoConvert_core_PodPortForwardOptions_To_v1_PodPortForwardOptions(in *core.PodPortForwardOptions, out *v1.PodPortForwardOptions, s conversion.Scope) error { + out.Ports = *(*[]int32)(unsafe.Pointer(&in.Ports)) + return nil +} + +// Convert_core_PodPortForwardOptions_To_v1_PodPortForwardOptions is an autogenerated conversion function. +func Convert_core_PodPortForwardOptions_To_v1_PodPortForwardOptions(in *core.PodPortForwardOptions, out *v1.PodPortForwardOptions, s conversion.Scope) error { + return autoConvert_core_PodPortForwardOptions_To_v1_PodPortForwardOptions(in, out, s) +} + +func autoConvert_v1_PodProxyOptions_To_core_PodProxyOptions(in *v1.PodProxyOptions, out *core.PodProxyOptions, s conversion.Scope) error { + out.Path = in.Path + return nil +} + +// Convert_v1_PodProxyOptions_To_core_PodProxyOptions is an autogenerated conversion function. +func Convert_v1_PodProxyOptions_To_core_PodProxyOptions(in *v1.PodProxyOptions, out *core.PodProxyOptions, s conversion.Scope) error { + return autoConvert_v1_PodProxyOptions_To_core_PodProxyOptions(in, out, s) +} + +func autoConvert_core_PodProxyOptions_To_v1_PodProxyOptions(in *core.PodProxyOptions, out *v1.PodProxyOptions, s conversion.Scope) error { + out.Path = in.Path + return nil +} + +// Convert_core_PodProxyOptions_To_v1_PodProxyOptions is an autogenerated conversion function. +func Convert_core_PodProxyOptions_To_v1_PodProxyOptions(in *core.PodProxyOptions, out *v1.PodProxyOptions, s conversion.Scope) error { + return autoConvert_core_PodProxyOptions_To_v1_PodProxyOptions(in, out, s) +} + +func autoConvert_v1_PodSecurityContext_To_core_PodSecurityContext(in *v1.PodSecurityContext, out *core.PodSecurityContext, s conversion.Scope) error { + out.SELinuxOptions = (*core.SELinuxOptions)(unsafe.Pointer(in.SELinuxOptions)) + out.RunAsUser = (*int64)(unsafe.Pointer(in.RunAsUser)) + out.RunAsNonRoot = (*bool)(unsafe.Pointer(in.RunAsNonRoot)) + out.SupplementalGroups = *(*[]int64)(unsafe.Pointer(&in.SupplementalGroups)) + out.FSGroup = (*int64)(unsafe.Pointer(in.FSGroup)) + return nil +} + +func autoConvert_core_PodSecurityContext_To_v1_PodSecurityContext(in *core.PodSecurityContext, out *v1.PodSecurityContext, s conversion.Scope) error { + // INFO: in.HostNetwork opted out of conversion generation + // INFO: in.HostPID opted out of conversion generation + // INFO: in.HostIPC opted out of conversion generation + out.SELinuxOptions = (*v1.SELinuxOptions)(unsafe.Pointer(in.SELinuxOptions)) + out.RunAsUser = (*int64)(unsafe.Pointer(in.RunAsUser)) + out.RunAsNonRoot = (*bool)(unsafe.Pointer(in.RunAsNonRoot)) + out.SupplementalGroups = *(*[]int64)(unsafe.Pointer(&in.SupplementalGroups)) + out.FSGroup = (*int64)(unsafe.Pointer(in.FSGroup)) + return nil +} + +func autoConvert_v1_PodSignature_To_core_PodSignature(in *v1.PodSignature, out *core.PodSignature, s conversion.Scope) error { + out.PodController = (*meta_v1.OwnerReference)(unsafe.Pointer(in.PodController)) + return nil +} + +// Convert_v1_PodSignature_To_core_PodSignature is an autogenerated conversion function. +func Convert_v1_PodSignature_To_core_PodSignature(in *v1.PodSignature, out *core.PodSignature, s conversion.Scope) error { + return autoConvert_v1_PodSignature_To_core_PodSignature(in, out, s) +} + +func autoConvert_core_PodSignature_To_v1_PodSignature(in *core.PodSignature, out *v1.PodSignature, s conversion.Scope) error { + out.PodController = (*meta_v1.OwnerReference)(unsafe.Pointer(in.PodController)) + return nil +} + +// Convert_core_PodSignature_To_v1_PodSignature is an autogenerated conversion function. +func Convert_core_PodSignature_To_v1_PodSignature(in *core.PodSignature, out *v1.PodSignature, s conversion.Scope) error { + return autoConvert_core_PodSignature_To_v1_PodSignature(in, out, s) +} + +func autoConvert_v1_PodSpec_To_core_PodSpec(in *v1.PodSpec, out *core.PodSpec, s conversion.Scope) error { + if in.Volumes != nil { + in, out := &in.Volumes, &out.Volumes + *out = make([]core.Volume, len(*in)) + for i := range *in { + if err := Convert_v1_Volume_To_core_Volume(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Volumes = nil + } + if in.InitContainers != nil { + in, out := &in.InitContainers, &out.InitContainers + *out = make([]core.Container, len(*in)) + for i := range *in { + if err := Convert_v1_Container_To_core_Container(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.InitContainers = nil + } + if in.Containers != nil { + in, out := &in.Containers, &out.Containers + *out = make([]core.Container, len(*in)) + for i := range *in { + if err := Convert_v1_Container_To_core_Container(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Containers = nil + } + out.RestartPolicy = core.RestartPolicy(in.RestartPolicy) + out.TerminationGracePeriodSeconds = (*int64)(unsafe.Pointer(in.TerminationGracePeriodSeconds)) + out.ActiveDeadlineSeconds = (*int64)(unsafe.Pointer(in.ActiveDeadlineSeconds)) + out.DNSPolicy = core.DNSPolicy(in.DNSPolicy) + out.NodeSelector = *(*map[string]string)(unsafe.Pointer(&in.NodeSelector)) + out.ServiceAccountName = in.ServiceAccountName + // INFO: in.DeprecatedServiceAccount opted out of conversion generation + out.AutomountServiceAccountToken = (*bool)(unsafe.Pointer(in.AutomountServiceAccountToken)) + out.NodeName = in.NodeName + // INFO: in.HostNetwork opted out of conversion generation + // INFO: in.HostPID opted out of conversion generation + // INFO: in.HostIPC opted out of conversion generation + if in.SecurityContext != nil { + in, out := &in.SecurityContext, &out.SecurityContext + *out = new(core.PodSecurityContext) + if err := Convert_v1_PodSecurityContext_To_core_PodSecurityContext(*in, *out, s); err != nil { + return err + } + } else { + out.SecurityContext = nil + } + out.ImagePullSecrets = *(*[]core.LocalObjectReference)(unsafe.Pointer(&in.ImagePullSecrets)) + out.Hostname = in.Hostname + out.Subdomain = in.Subdomain + out.Affinity = (*core.Affinity)(unsafe.Pointer(in.Affinity)) + out.SchedulerName = in.SchedulerName + out.Tolerations = *(*[]core.Toleration)(unsafe.Pointer(&in.Tolerations)) + out.HostAliases = *(*[]core.HostAlias)(unsafe.Pointer(&in.HostAliases)) + out.PriorityClassName = in.PriorityClassName + out.Priority = (*int32)(unsafe.Pointer(in.Priority)) + out.DNSConfig = (*core.PodDNSConfig)(unsafe.Pointer(in.DNSConfig)) + return nil +} + +func autoConvert_core_PodSpec_To_v1_PodSpec(in *core.PodSpec, out *v1.PodSpec, s conversion.Scope) error { + if in.Volumes != nil { + in, out := &in.Volumes, &out.Volumes + *out = make([]v1.Volume, len(*in)) + for i := range *in { + if err := Convert_core_Volume_To_v1_Volume(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Volumes = nil + } + if in.InitContainers != nil { + in, out := &in.InitContainers, &out.InitContainers + *out = make([]v1.Container, len(*in)) + for i := range *in { + if err := Convert_core_Container_To_v1_Container(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.InitContainers = nil + } + if in.Containers != nil { + in, out := &in.Containers, &out.Containers + *out = make([]v1.Container, len(*in)) + for i := range *in { + if err := Convert_core_Container_To_v1_Container(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Containers = nil + } + out.RestartPolicy = v1.RestartPolicy(in.RestartPolicy) + out.TerminationGracePeriodSeconds = (*int64)(unsafe.Pointer(in.TerminationGracePeriodSeconds)) + out.ActiveDeadlineSeconds = (*int64)(unsafe.Pointer(in.ActiveDeadlineSeconds)) + out.DNSPolicy = v1.DNSPolicy(in.DNSPolicy) + out.NodeSelector = *(*map[string]string)(unsafe.Pointer(&in.NodeSelector)) + out.ServiceAccountName = in.ServiceAccountName + out.AutomountServiceAccountToken = (*bool)(unsafe.Pointer(in.AutomountServiceAccountToken)) + out.NodeName = in.NodeName + if in.SecurityContext != nil { + in, out := &in.SecurityContext, &out.SecurityContext + *out = new(v1.PodSecurityContext) + if err := Convert_core_PodSecurityContext_To_v1_PodSecurityContext(*in, *out, s); err != nil { + return err + } + } else { + out.SecurityContext = nil + } + out.ImagePullSecrets = *(*[]v1.LocalObjectReference)(unsafe.Pointer(&in.ImagePullSecrets)) + out.Hostname = in.Hostname + out.Subdomain = in.Subdomain + out.Affinity = (*v1.Affinity)(unsafe.Pointer(in.Affinity)) + out.SchedulerName = in.SchedulerName + out.Tolerations = *(*[]v1.Toleration)(unsafe.Pointer(&in.Tolerations)) + out.HostAliases = *(*[]v1.HostAlias)(unsafe.Pointer(&in.HostAliases)) + out.PriorityClassName = in.PriorityClassName + out.Priority = (*int32)(unsafe.Pointer(in.Priority)) + out.DNSConfig = (*v1.PodDNSConfig)(unsafe.Pointer(in.DNSConfig)) + return nil +} + +func autoConvert_v1_PodStatus_To_core_PodStatus(in *v1.PodStatus, out *core.PodStatus, s conversion.Scope) error { + out.Phase = core.PodPhase(in.Phase) + out.Conditions = *(*[]core.PodCondition)(unsafe.Pointer(&in.Conditions)) + out.Message = in.Message + out.Reason = in.Reason + out.HostIP = in.HostIP + out.PodIP = in.PodIP + out.StartTime = (*meta_v1.Time)(unsafe.Pointer(in.StartTime)) + out.InitContainerStatuses = *(*[]core.ContainerStatus)(unsafe.Pointer(&in.InitContainerStatuses)) + out.ContainerStatuses = *(*[]core.ContainerStatus)(unsafe.Pointer(&in.ContainerStatuses)) + out.QOSClass = core.PodQOSClass(in.QOSClass) + return nil +} + +// Convert_v1_PodStatus_To_core_PodStatus is an autogenerated conversion function. +func Convert_v1_PodStatus_To_core_PodStatus(in *v1.PodStatus, out *core.PodStatus, s conversion.Scope) error { + return autoConvert_v1_PodStatus_To_core_PodStatus(in, out, s) +} + +func autoConvert_core_PodStatus_To_v1_PodStatus(in *core.PodStatus, out *v1.PodStatus, s conversion.Scope) error { + out.Phase = v1.PodPhase(in.Phase) + out.Conditions = *(*[]v1.PodCondition)(unsafe.Pointer(&in.Conditions)) + out.Message = in.Message + out.Reason = in.Reason + out.HostIP = in.HostIP + out.PodIP = in.PodIP + out.StartTime = (*meta_v1.Time)(unsafe.Pointer(in.StartTime)) + out.QOSClass = v1.PodQOSClass(in.QOSClass) + out.InitContainerStatuses = *(*[]v1.ContainerStatus)(unsafe.Pointer(&in.InitContainerStatuses)) + out.ContainerStatuses = *(*[]v1.ContainerStatus)(unsafe.Pointer(&in.ContainerStatuses)) + return nil +} + +// Convert_core_PodStatus_To_v1_PodStatus is an autogenerated conversion function. +func Convert_core_PodStatus_To_v1_PodStatus(in *core.PodStatus, out *v1.PodStatus, s conversion.Scope) error { + return autoConvert_core_PodStatus_To_v1_PodStatus(in, out, s) +} + +func autoConvert_v1_PodStatusResult_To_core_PodStatusResult(in *v1.PodStatusResult, out *core.PodStatusResult, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_PodStatus_To_core_PodStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1_PodStatusResult_To_core_PodStatusResult is an autogenerated conversion function. +func Convert_v1_PodStatusResult_To_core_PodStatusResult(in *v1.PodStatusResult, out *core.PodStatusResult, s conversion.Scope) error { + return autoConvert_v1_PodStatusResult_To_core_PodStatusResult(in, out, s) +} + +func autoConvert_core_PodStatusResult_To_v1_PodStatusResult(in *core.PodStatusResult, out *v1.PodStatusResult, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_core_PodStatus_To_v1_PodStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_core_PodStatusResult_To_v1_PodStatusResult is an autogenerated conversion function. +func Convert_core_PodStatusResult_To_v1_PodStatusResult(in *core.PodStatusResult, out *v1.PodStatusResult, s conversion.Scope) error { + return autoConvert_core_PodStatusResult_To_v1_PodStatusResult(in, out, s) +} + +func autoConvert_v1_PodTemplate_To_core_PodTemplate(in *v1.PodTemplate, out *core.PodTemplate, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err + } + return nil +} + +// Convert_v1_PodTemplate_To_core_PodTemplate is an autogenerated conversion function. +func Convert_v1_PodTemplate_To_core_PodTemplate(in *v1.PodTemplate, out *core.PodTemplate, s conversion.Scope) error { + return autoConvert_v1_PodTemplate_To_core_PodTemplate(in, out, s) +} + +func autoConvert_core_PodTemplate_To_v1_PodTemplate(in *core.PodTemplate, out *v1.PodTemplate, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err + } + return nil +} + +// Convert_core_PodTemplate_To_v1_PodTemplate is an autogenerated conversion function. +func Convert_core_PodTemplate_To_v1_PodTemplate(in *core.PodTemplate, out *v1.PodTemplate, s conversion.Scope) error { + return autoConvert_core_PodTemplate_To_v1_PodTemplate(in, out, s) +} + +func autoConvert_v1_PodTemplateList_To_core_PodTemplateList(in *v1.PodTemplateList, out *core.PodTemplateList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]core.PodTemplate, len(*in)) + for i := range *in { + if err := Convert_v1_PodTemplate_To_core_PodTemplate(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_v1_PodTemplateList_To_core_PodTemplateList is an autogenerated conversion function. +func Convert_v1_PodTemplateList_To_core_PodTemplateList(in *v1.PodTemplateList, out *core.PodTemplateList, s conversion.Scope) error { + return autoConvert_v1_PodTemplateList_To_core_PodTemplateList(in, out, s) +} + +func autoConvert_core_PodTemplateList_To_v1_PodTemplateList(in *core.PodTemplateList, out *v1.PodTemplateList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1.PodTemplate, len(*in)) + for i := range *in { + if err := Convert_core_PodTemplate_To_v1_PodTemplate(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_core_PodTemplateList_To_v1_PodTemplateList is an autogenerated conversion function. +func Convert_core_PodTemplateList_To_v1_PodTemplateList(in *core.PodTemplateList, out *v1.PodTemplateList, s conversion.Scope) error { + return autoConvert_core_PodTemplateList_To_v1_PodTemplateList(in, out, s) +} + +func autoConvert_v1_PodTemplateSpec_To_core_PodTemplateSpec(in *v1.PodTemplateSpec, out *core.PodTemplateSpec, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_PodSpec_To_core_PodSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + return nil +} + +func autoConvert_core_PodTemplateSpec_To_v1_PodTemplateSpec(in *core.PodTemplateSpec, out *v1.PodTemplateSpec, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_core_PodSpec_To_v1_PodSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + return nil +} + +func autoConvert_v1_PortworxVolumeSource_To_core_PortworxVolumeSource(in *v1.PortworxVolumeSource, out *core.PortworxVolumeSource, s conversion.Scope) error { + out.VolumeID = in.VolumeID + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_v1_PortworxVolumeSource_To_core_PortworxVolumeSource is an autogenerated conversion function. +func Convert_v1_PortworxVolumeSource_To_core_PortworxVolumeSource(in *v1.PortworxVolumeSource, out *core.PortworxVolumeSource, s conversion.Scope) error { + return autoConvert_v1_PortworxVolumeSource_To_core_PortworxVolumeSource(in, out, s) +} + +func autoConvert_core_PortworxVolumeSource_To_v1_PortworxVolumeSource(in *core.PortworxVolumeSource, out *v1.PortworxVolumeSource, s conversion.Scope) error { + out.VolumeID = in.VolumeID + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_core_PortworxVolumeSource_To_v1_PortworxVolumeSource is an autogenerated conversion function. +func Convert_core_PortworxVolumeSource_To_v1_PortworxVolumeSource(in *core.PortworxVolumeSource, out *v1.PortworxVolumeSource, s conversion.Scope) error { + return autoConvert_core_PortworxVolumeSource_To_v1_PortworxVolumeSource(in, out, s) +} + +func autoConvert_v1_Preconditions_To_core_Preconditions(in *v1.Preconditions, out *core.Preconditions, s conversion.Scope) error { + out.UID = (*types.UID)(unsafe.Pointer(in.UID)) + return nil +} + +// Convert_v1_Preconditions_To_core_Preconditions is an autogenerated conversion function. +func Convert_v1_Preconditions_To_core_Preconditions(in *v1.Preconditions, out *core.Preconditions, s conversion.Scope) error { + return autoConvert_v1_Preconditions_To_core_Preconditions(in, out, s) +} + +func autoConvert_core_Preconditions_To_v1_Preconditions(in *core.Preconditions, out *v1.Preconditions, s conversion.Scope) error { + out.UID = (*types.UID)(unsafe.Pointer(in.UID)) + return nil +} + +// Convert_core_Preconditions_To_v1_Preconditions is an autogenerated conversion function. +func Convert_core_Preconditions_To_v1_Preconditions(in *core.Preconditions, out *v1.Preconditions, s conversion.Scope) error { + return autoConvert_core_Preconditions_To_v1_Preconditions(in, out, s) +} + +func autoConvert_v1_PreferAvoidPodsEntry_To_core_PreferAvoidPodsEntry(in *v1.PreferAvoidPodsEntry, out *core.PreferAvoidPodsEntry, s conversion.Scope) error { + if err := Convert_v1_PodSignature_To_core_PodSignature(&in.PodSignature, &out.PodSignature, s); err != nil { + return err + } + out.EvictionTime = in.EvictionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_v1_PreferAvoidPodsEntry_To_core_PreferAvoidPodsEntry is an autogenerated conversion function. +func Convert_v1_PreferAvoidPodsEntry_To_core_PreferAvoidPodsEntry(in *v1.PreferAvoidPodsEntry, out *core.PreferAvoidPodsEntry, s conversion.Scope) error { + return autoConvert_v1_PreferAvoidPodsEntry_To_core_PreferAvoidPodsEntry(in, out, s) +} + +func autoConvert_core_PreferAvoidPodsEntry_To_v1_PreferAvoidPodsEntry(in *core.PreferAvoidPodsEntry, out *v1.PreferAvoidPodsEntry, s conversion.Scope) error { + if err := Convert_core_PodSignature_To_v1_PodSignature(&in.PodSignature, &out.PodSignature, s); err != nil { + return err + } + out.EvictionTime = in.EvictionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_core_PreferAvoidPodsEntry_To_v1_PreferAvoidPodsEntry is an autogenerated conversion function. +func Convert_core_PreferAvoidPodsEntry_To_v1_PreferAvoidPodsEntry(in *core.PreferAvoidPodsEntry, out *v1.PreferAvoidPodsEntry, s conversion.Scope) error { + return autoConvert_core_PreferAvoidPodsEntry_To_v1_PreferAvoidPodsEntry(in, out, s) +} + +func autoConvert_v1_PreferredSchedulingTerm_To_core_PreferredSchedulingTerm(in *v1.PreferredSchedulingTerm, out *core.PreferredSchedulingTerm, s conversion.Scope) error { + out.Weight = in.Weight + if err := Convert_v1_NodeSelectorTerm_To_core_NodeSelectorTerm(&in.Preference, &out.Preference, s); err != nil { + return err + } + return nil +} + +// Convert_v1_PreferredSchedulingTerm_To_core_PreferredSchedulingTerm is an autogenerated conversion function. +func Convert_v1_PreferredSchedulingTerm_To_core_PreferredSchedulingTerm(in *v1.PreferredSchedulingTerm, out *core.PreferredSchedulingTerm, s conversion.Scope) error { + return autoConvert_v1_PreferredSchedulingTerm_To_core_PreferredSchedulingTerm(in, out, s) +} + +func autoConvert_core_PreferredSchedulingTerm_To_v1_PreferredSchedulingTerm(in *core.PreferredSchedulingTerm, out *v1.PreferredSchedulingTerm, s conversion.Scope) error { + out.Weight = in.Weight + if err := Convert_core_NodeSelectorTerm_To_v1_NodeSelectorTerm(&in.Preference, &out.Preference, s); err != nil { + return err + } + return nil +} + +// Convert_core_PreferredSchedulingTerm_To_v1_PreferredSchedulingTerm is an autogenerated conversion function. +func Convert_core_PreferredSchedulingTerm_To_v1_PreferredSchedulingTerm(in *core.PreferredSchedulingTerm, out *v1.PreferredSchedulingTerm, s conversion.Scope) error { + return autoConvert_core_PreferredSchedulingTerm_To_v1_PreferredSchedulingTerm(in, out, s) +} + +func autoConvert_v1_Probe_To_core_Probe(in *v1.Probe, out *core.Probe, s conversion.Scope) error { + if err := Convert_v1_Handler_To_core_Handler(&in.Handler, &out.Handler, s); err != nil { + return err + } + out.InitialDelaySeconds = in.InitialDelaySeconds + out.TimeoutSeconds = in.TimeoutSeconds + out.PeriodSeconds = in.PeriodSeconds + out.SuccessThreshold = in.SuccessThreshold + out.FailureThreshold = in.FailureThreshold + return nil +} + +// Convert_v1_Probe_To_core_Probe is an autogenerated conversion function. +func Convert_v1_Probe_To_core_Probe(in *v1.Probe, out *core.Probe, s conversion.Scope) error { + return autoConvert_v1_Probe_To_core_Probe(in, out, s) +} + +func autoConvert_core_Probe_To_v1_Probe(in *core.Probe, out *v1.Probe, s conversion.Scope) error { + if err := Convert_core_Handler_To_v1_Handler(&in.Handler, &out.Handler, s); err != nil { + return err + } + out.InitialDelaySeconds = in.InitialDelaySeconds + out.TimeoutSeconds = in.TimeoutSeconds + out.PeriodSeconds = in.PeriodSeconds + out.SuccessThreshold = in.SuccessThreshold + out.FailureThreshold = in.FailureThreshold + return nil +} + +// Convert_core_Probe_To_v1_Probe is an autogenerated conversion function. +func Convert_core_Probe_To_v1_Probe(in *core.Probe, out *v1.Probe, s conversion.Scope) error { + return autoConvert_core_Probe_To_v1_Probe(in, out, s) +} + +func autoConvert_v1_ProjectedVolumeSource_To_core_ProjectedVolumeSource(in *v1.ProjectedVolumeSource, out *core.ProjectedVolumeSource, s conversion.Scope) error { + out.Sources = *(*[]core.VolumeProjection)(unsafe.Pointer(&in.Sources)) + out.DefaultMode = (*int32)(unsafe.Pointer(in.DefaultMode)) + return nil +} + +// Convert_v1_ProjectedVolumeSource_To_core_ProjectedVolumeSource is an autogenerated conversion function. +func Convert_v1_ProjectedVolumeSource_To_core_ProjectedVolumeSource(in *v1.ProjectedVolumeSource, out *core.ProjectedVolumeSource, s conversion.Scope) error { + return autoConvert_v1_ProjectedVolumeSource_To_core_ProjectedVolumeSource(in, out, s) +} + +func autoConvert_core_ProjectedVolumeSource_To_v1_ProjectedVolumeSource(in *core.ProjectedVolumeSource, out *v1.ProjectedVolumeSource, s conversion.Scope) error { + out.Sources = *(*[]v1.VolumeProjection)(unsafe.Pointer(&in.Sources)) + out.DefaultMode = (*int32)(unsafe.Pointer(in.DefaultMode)) + return nil +} + +// Convert_core_ProjectedVolumeSource_To_v1_ProjectedVolumeSource is an autogenerated conversion function. +func Convert_core_ProjectedVolumeSource_To_v1_ProjectedVolumeSource(in *core.ProjectedVolumeSource, out *v1.ProjectedVolumeSource, s conversion.Scope) error { + return autoConvert_core_ProjectedVolumeSource_To_v1_ProjectedVolumeSource(in, out, s) +} + +func autoConvert_v1_QuobyteVolumeSource_To_core_QuobyteVolumeSource(in *v1.QuobyteVolumeSource, out *core.QuobyteVolumeSource, s conversion.Scope) error { + out.Registry = in.Registry + out.Volume = in.Volume + out.ReadOnly = in.ReadOnly + out.User = in.User + out.Group = in.Group + return nil +} + +// Convert_v1_QuobyteVolumeSource_To_core_QuobyteVolumeSource is an autogenerated conversion function. +func Convert_v1_QuobyteVolumeSource_To_core_QuobyteVolumeSource(in *v1.QuobyteVolumeSource, out *core.QuobyteVolumeSource, s conversion.Scope) error { + return autoConvert_v1_QuobyteVolumeSource_To_core_QuobyteVolumeSource(in, out, s) +} + +func autoConvert_core_QuobyteVolumeSource_To_v1_QuobyteVolumeSource(in *core.QuobyteVolumeSource, out *v1.QuobyteVolumeSource, s conversion.Scope) error { + out.Registry = in.Registry + out.Volume = in.Volume + out.ReadOnly = in.ReadOnly + out.User = in.User + out.Group = in.Group + return nil +} + +// Convert_core_QuobyteVolumeSource_To_v1_QuobyteVolumeSource is an autogenerated conversion function. +func Convert_core_QuobyteVolumeSource_To_v1_QuobyteVolumeSource(in *core.QuobyteVolumeSource, out *v1.QuobyteVolumeSource, s conversion.Scope) error { + return autoConvert_core_QuobyteVolumeSource_To_v1_QuobyteVolumeSource(in, out, s) +} + +func autoConvert_v1_RBDPersistentVolumeSource_To_core_RBDPersistentVolumeSource(in *v1.RBDPersistentVolumeSource, out *core.RBDPersistentVolumeSource, s conversion.Scope) error { + out.CephMonitors = *(*[]string)(unsafe.Pointer(&in.CephMonitors)) + out.RBDImage = in.RBDImage + out.FSType = in.FSType + out.RBDPool = in.RBDPool + out.RadosUser = in.RadosUser + out.Keyring = in.Keyring + out.SecretRef = (*core.SecretReference)(unsafe.Pointer(in.SecretRef)) + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_v1_RBDPersistentVolumeSource_To_core_RBDPersistentVolumeSource is an autogenerated conversion function. +func Convert_v1_RBDPersistentVolumeSource_To_core_RBDPersistentVolumeSource(in *v1.RBDPersistentVolumeSource, out *core.RBDPersistentVolumeSource, s conversion.Scope) error { + return autoConvert_v1_RBDPersistentVolumeSource_To_core_RBDPersistentVolumeSource(in, out, s) +} + +func autoConvert_core_RBDPersistentVolumeSource_To_v1_RBDPersistentVolumeSource(in *core.RBDPersistentVolumeSource, out *v1.RBDPersistentVolumeSource, s conversion.Scope) error { + out.CephMonitors = *(*[]string)(unsafe.Pointer(&in.CephMonitors)) + out.RBDImage = in.RBDImage + out.FSType = in.FSType + out.RBDPool = in.RBDPool + out.RadosUser = in.RadosUser + out.Keyring = in.Keyring + out.SecretRef = (*v1.SecretReference)(unsafe.Pointer(in.SecretRef)) + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_core_RBDPersistentVolumeSource_To_v1_RBDPersistentVolumeSource is an autogenerated conversion function. +func Convert_core_RBDPersistentVolumeSource_To_v1_RBDPersistentVolumeSource(in *core.RBDPersistentVolumeSource, out *v1.RBDPersistentVolumeSource, s conversion.Scope) error { + return autoConvert_core_RBDPersistentVolumeSource_To_v1_RBDPersistentVolumeSource(in, out, s) +} + +func autoConvert_v1_RBDVolumeSource_To_core_RBDVolumeSource(in *v1.RBDVolumeSource, out *core.RBDVolumeSource, s conversion.Scope) error { + out.CephMonitors = *(*[]string)(unsafe.Pointer(&in.CephMonitors)) + out.RBDImage = in.RBDImage + out.FSType = in.FSType + out.RBDPool = in.RBDPool + out.RadosUser = in.RadosUser + out.Keyring = in.Keyring + out.SecretRef = (*core.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_v1_RBDVolumeSource_To_core_RBDVolumeSource is an autogenerated conversion function. +func Convert_v1_RBDVolumeSource_To_core_RBDVolumeSource(in *v1.RBDVolumeSource, out *core.RBDVolumeSource, s conversion.Scope) error { + return autoConvert_v1_RBDVolumeSource_To_core_RBDVolumeSource(in, out, s) +} + +func autoConvert_core_RBDVolumeSource_To_v1_RBDVolumeSource(in *core.RBDVolumeSource, out *v1.RBDVolumeSource, s conversion.Scope) error { + out.CephMonitors = *(*[]string)(unsafe.Pointer(&in.CephMonitors)) + out.RBDImage = in.RBDImage + out.FSType = in.FSType + out.RBDPool = in.RBDPool + out.RadosUser = in.RadosUser + out.Keyring = in.Keyring + out.SecretRef = (*v1.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_core_RBDVolumeSource_To_v1_RBDVolumeSource is an autogenerated conversion function. +func Convert_core_RBDVolumeSource_To_v1_RBDVolumeSource(in *core.RBDVolumeSource, out *v1.RBDVolumeSource, s conversion.Scope) error { + return autoConvert_core_RBDVolumeSource_To_v1_RBDVolumeSource(in, out, s) +} + +func autoConvert_v1_RangeAllocation_To_core_RangeAllocation(in *v1.RangeAllocation, out *core.RangeAllocation, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Range = in.Range + out.Data = *(*[]byte)(unsafe.Pointer(&in.Data)) + return nil +} + +// Convert_v1_RangeAllocation_To_core_RangeAllocation is an autogenerated conversion function. +func Convert_v1_RangeAllocation_To_core_RangeAllocation(in *v1.RangeAllocation, out *core.RangeAllocation, s conversion.Scope) error { + return autoConvert_v1_RangeAllocation_To_core_RangeAllocation(in, out, s) +} + +func autoConvert_core_RangeAllocation_To_v1_RangeAllocation(in *core.RangeAllocation, out *v1.RangeAllocation, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Range = in.Range + out.Data = *(*[]byte)(unsafe.Pointer(&in.Data)) + return nil +} + +// Convert_core_RangeAllocation_To_v1_RangeAllocation is an autogenerated conversion function. +func Convert_core_RangeAllocation_To_v1_RangeAllocation(in *core.RangeAllocation, out *v1.RangeAllocation, s conversion.Scope) error { + return autoConvert_core_RangeAllocation_To_v1_RangeAllocation(in, out, s) +} + +func autoConvert_v1_ReplicationController_To_core_ReplicationController(in *v1.ReplicationController, out *core.ReplicationController, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_ReplicationControllerSpec_To_core_ReplicationControllerSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1_ReplicationControllerStatus_To_core_ReplicationControllerStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1_ReplicationController_To_core_ReplicationController is an autogenerated conversion function. +func Convert_v1_ReplicationController_To_core_ReplicationController(in *v1.ReplicationController, out *core.ReplicationController, s conversion.Scope) error { + return autoConvert_v1_ReplicationController_To_core_ReplicationController(in, out, s) +} + +func autoConvert_core_ReplicationController_To_v1_ReplicationController(in *core.ReplicationController, out *v1.ReplicationController, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_core_ReplicationControllerSpec_To_v1_ReplicationControllerSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_core_ReplicationControllerStatus_To_v1_ReplicationControllerStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_core_ReplicationController_To_v1_ReplicationController is an autogenerated conversion function. +func Convert_core_ReplicationController_To_v1_ReplicationController(in *core.ReplicationController, out *v1.ReplicationController, s conversion.Scope) error { + return autoConvert_core_ReplicationController_To_v1_ReplicationController(in, out, s) +} + +func autoConvert_v1_ReplicationControllerCondition_To_core_ReplicationControllerCondition(in *v1.ReplicationControllerCondition, out *core.ReplicationControllerCondition, s conversion.Scope) error { + out.Type = core.ReplicationControllerConditionType(in.Type) + out.Status = core.ConditionStatus(in.Status) + out.LastTransitionTime = in.LastTransitionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_v1_ReplicationControllerCondition_To_core_ReplicationControllerCondition is an autogenerated conversion function. +func Convert_v1_ReplicationControllerCondition_To_core_ReplicationControllerCondition(in *v1.ReplicationControllerCondition, out *core.ReplicationControllerCondition, s conversion.Scope) error { + return autoConvert_v1_ReplicationControllerCondition_To_core_ReplicationControllerCondition(in, out, s) +} + +func autoConvert_core_ReplicationControllerCondition_To_v1_ReplicationControllerCondition(in *core.ReplicationControllerCondition, out *v1.ReplicationControllerCondition, s conversion.Scope) error { + out.Type = v1.ReplicationControllerConditionType(in.Type) + out.Status = v1.ConditionStatus(in.Status) + out.LastTransitionTime = in.LastTransitionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_core_ReplicationControllerCondition_To_v1_ReplicationControllerCondition is an autogenerated conversion function. +func Convert_core_ReplicationControllerCondition_To_v1_ReplicationControllerCondition(in *core.ReplicationControllerCondition, out *v1.ReplicationControllerCondition, s conversion.Scope) error { + return autoConvert_core_ReplicationControllerCondition_To_v1_ReplicationControllerCondition(in, out, s) +} + +func autoConvert_v1_ReplicationControllerList_To_core_ReplicationControllerList(in *v1.ReplicationControllerList, out *core.ReplicationControllerList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]core.ReplicationController, len(*in)) + for i := range *in { + if err := Convert_v1_ReplicationController_To_core_ReplicationController(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_v1_ReplicationControllerList_To_core_ReplicationControllerList is an autogenerated conversion function. +func Convert_v1_ReplicationControllerList_To_core_ReplicationControllerList(in *v1.ReplicationControllerList, out *core.ReplicationControllerList, s conversion.Scope) error { + return autoConvert_v1_ReplicationControllerList_To_core_ReplicationControllerList(in, out, s) +} + +func autoConvert_core_ReplicationControllerList_To_v1_ReplicationControllerList(in *core.ReplicationControllerList, out *v1.ReplicationControllerList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1.ReplicationController, len(*in)) + for i := range *in { + if err := Convert_core_ReplicationController_To_v1_ReplicationController(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_core_ReplicationControllerList_To_v1_ReplicationControllerList is an autogenerated conversion function. +func Convert_core_ReplicationControllerList_To_v1_ReplicationControllerList(in *core.ReplicationControllerList, out *v1.ReplicationControllerList, s conversion.Scope) error { + return autoConvert_core_ReplicationControllerList_To_v1_ReplicationControllerList(in, out, s) +} + +func autoConvert_v1_ReplicationControllerSpec_To_core_ReplicationControllerSpec(in *v1.ReplicationControllerSpec, out *core.ReplicationControllerSpec, s conversion.Scope) error { + if err := meta_v1.Convert_Pointer_int32_To_int32(&in.Replicas, &out.Replicas, s); err != nil { + return err + } + out.MinReadySeconds = in.MinReadySeconds + out.Selector = *(*map[string]string)(unsafe.Pointer(&in.Selector)) + if in.Template != nil { + in, out := &in.Template, &out.Template + *out = new(core.PodTemplateSpec) + if err := Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(*in, *out, s); err != nil { + return err + } + } else { + out.Template = nil + } + return nil +} + +func autoConvert_core_ReplicationControllerSpec_To_v1_ReplicationControllerSpec(in *core.ReplicationControllerSpec, out *v1.ReplicationControllerSpec, s conversion.Scope) error { + if err := meta_v1.Convert_int32_To_Pointer_int32(&in.Replicas, &out.Replicas, s); err != nil { + return err + } + out.MinReadySeconds = in.MinReadySeconds + out.Selector = *(*map[string]string)(unsafe.Pointer(&in.Selector)) + if in.Template != nil { + in, out := &in.Template, &out.Template + *out = new(v1.PodTemplateSpec) + if err := Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(*in, *out, s); err != nil { + return err + } + } else { + out.Template = nil + } + return nil +} + +func autoConvert_v1_ReplicationControllerStatus_To_core_ReplicationControllerStatus(in *v1.ReplicationControllerStatus, out *core.ReplicationControllerStatus, s conversion.Scope) error { + out.Replicas = in.Replicas + out.FullyLabeledReplicas = in.FullyLabeledReplicas + out.ReadyReplicas = in.ReadyReplicas + out.AvailableReplicas = in.AvailableReplicas + out.ObservedGeneration = in.ObservedGeneration + out.Conditions = *(*[]core.ReplicationControllerCondition)(unsafe.Pointer(&in.Conditions)) + return nil +} + +// Convert_v1_ReplicationControllerStatus_To_core_ReplicationControllerStatus is an autogenerated conversion function. +func Convert_v1_ReplicationControllerStatus_To_core_ReplicationControllerStatus(in *v1.ReplicationControllerStatus, out *core.ReplicationControllerStatus, s conversion.Scope) error { + return autoConvert_v1_ReplicationControllerStatus_To_core_ReplicationControllerStatus(in, out, s) +} + +func autoConvert_core_ReplicationControllerStatus_To_v1_ReplicationControllerStatus(in *core.ReplicationControllerStatus, out *v1.ReplicationControllerStatus, s conversion.Scope) error { + out.Replicas = in.Replicas + out.FullyLabeledReplicas = in.FullyLabeledReplicas + out.ReadyReplicas = in.ReadyReplicas + out.AvailableReplicas = in.AvailableReplicas + out.ObservedGeneration = in.ObservedGeneration + out.Conditions = *(*[]v1.ReplicationControllerCondition)(unsafe.Pointer(&in.Conditions)) + return nil +} + +// Convert_core_ReplicationControllerStatus_To_v1_ReplicationControllerStatus is an autogenerated conversion function. +func Convert_core_ReplicationControllerStatus_To_v1_ReplicationControllerStatus(in *core.ReplicationControllerStatus, out *v1.ReplicationControllerStatus, s conversion.Scope) error { + return autoConvert_core_ReplicationControllerStatus_To_v1_ReplicationControllerStatus(in, out, s) +} + +func autoConvert_v1_ResourceFieldSelector_To_core_ResourceFieldSelector(in *v1.ResourceFieldSelector, out *core.ResourceFieldSelector, s conversion.Scope) error { + out.ContainerName = in.ContainerName + out.Resource = in.Resource + out.Divisor = in.Divisor + return nil +} + +// Convert_v1_ResourceFieldSelector_To_core_ResourceFieldSelector is an autogenerated conversion function. +func Convert_v1_ResourceFieldSelector_To_core_ResourceFieldSelector(in *v1.ResourceFieldSelector, out *core.ResourceFieldSelector, s conversion.Scope) error { + return autoConvert_v1_ResourceFieldSelector_To_core_ResourceFieldSelector(in, out, s) +} + +func autoConvert_core_ResourceFieldSelector_To_v1_ResourceFieldSelector(in *core.ResourceFieldSelector, out *v1.ResourceFieldSelector, s conversion.Scope) error { + out.ContainerName = in.ContainerName + out.Resource = in.Resource + out.Divisor = in.Divisor + return nil +} + +// Convert_core_ResourceFieldSelector_To_v1_ResourceFieldSelector is an autogenerated conversion function. +func Convert_core_ResourceFieldSelector_To_v1_ResourceFieldSelector(in *core.ResourceFieldSelector, out *v1.ResourceFieldSelector, s conversion.Scope) error { + return autoConvert_core_ResourceFieldSelector_To_v1_ResourceFieldSelector(in, out, s) +} + +func autoConvert_v1_ResourceQuota_To_core_ResourceQuota(in *v1.ResourceQuota, out *core.ResourceQuota, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_ResourceQuotaSpec_To_core_ResourceQuotaSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1_ResourceQuotaStatus_To_core_ResourceQuotaStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1_ResourceQuota_To_core_ResourceQuota is an autogenerated conversion function. +func Convert_v1_ResourceQuota_To_core_ResourceQuota(in *v1.ResourceQuota, out *core.ResourceQuota, s conversion.Scope) error { + return autoConvert_v1_ResourceQuota_To_core_ResourceQuota(in, out, s) +} + +func autoConvert_core_ResourceQuota_To_v1_ResourceQuota(in *core.ResourceQuota, out *v1.ResourceQuota, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_core_ResourceQuotaSpec_To_v1_ResourceQuotaSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_core_ResourceQuotaStatus_To_v1_ResourceQuotaStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_core_ResourceQuota_To_v1_ResourceQuota is an autogenerated conversion function. +func Convert_core_ResourceQuota_To_v1_ResourceQuota(in *core.ResourceQuota, out *v1.ResourceQuota, s conversion.Scope) error { + return autoConvert_core_ResourceQuota_To_v1_ResourceQuota(in, out, s) +} + +func autoConvert_v1_ResourceQuotaList_To_core_ResourceQuotaList(in *v1.ResourceQuotaList, out *core.ResourceQuotaList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]core.ResourceQuota)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1_ResourceQuotaList_To_core_ResourceQuotaList is an autogenerated conversion function. +func Convert_v1_ResourceQuotaList_To_core_ResourceQuotaList(in *v1.ResourceQuotaList, out *core.ResourceQuotaList, s conversion.Scope) error { + return autoConvert_v1_ResourceQuotaList_To_core_ResourceQuotaList(in, out, s) +} + +func autoConvert_core_ResourceQuotaList_To_v1_ResourceQuotaList(in *core.ResourceQuotaList, out *v1.ResourceQuotaList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]v1.ResourceQuota)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_core_ResourceQuotaList_To_v1_ResourceQuotaList is an autogenerated conversion function. +func Convert_core_ResourceQuotaList_To_v1_ResourceQuotaList(in *core.ResourceQuotaList, out *v1.ResourceQuotaList, s conversion.Scope) error { + return autoConvert_core_ResourceQuotaList_To_v1_ResourceQuotaList(in, out, s) +} + +func autoConvert_v1_ResourceQuotaSpec_To_core_ResourceQuotaSpec(in *v1.ResourceQuotaSpec, out *core.ResourceQuotaSpec, s conversion.Scope) error { + out.Hard = *(*core.ResourceList)(unsafe.Pointer(&in.Hard)) + out.Scopes = *(*[]core.ResourceQuotaScope)(unsafe.Pointer(&in.Scopes)) + return nil +} + +// Convert_v1_ResourceQuotaSpec_To_core_ResourceQuotaSpec is an autogenerated conversion function. +func Convert_v1_ResourceQuotaSpec_To_core_ResourceQuotaSpec(in *v1.ResourceQuotaSpec, out *core.ResourceQuotaSpec, s conversion.Scope) error { + return autoConvert_v1_ResourceQuotaSpec_To_core_ResourceQuotaSpec(in, out, s) +} + +func autoConvert_core_ResourceQuotaSpec_To_v1_ResourceQuotaSpec(in *core.ResourceQuotaSpec, out *v1.ResourceQuotaSpec, s conversion.Scope) error { + out.Hard = *(*v1.ResourceList)(unsafe.Pointer(&in.Hard)) + out.Scopes = *(*[]v1.ResourceQuotaScope)(unsafe.Pointer(&in.Scopes)) + return nil +} + +// Convert_core_ResourceQuotaSpec_To_v1_ResourceQuotaSpec is an autogenerated conversion function. +func Convert_core_ResourceQuotaSpec_To_v1_ResourceQuotaSpec(in *core.ResourceQuotaSpec, out *v1.ResourceQuotaSpec, s conversion.Scope) error { + return autoConvert_core_ResourceQuotaSpec_To_v1_ResourceQuotaSpec(in, out, s) +} + +func autoConvert_v1_ResourceQuotaStatus_To_core_ResourceQuotaStatus(in *v1.ResourceQuotaStatus, out *core.ResourceQuotaStatus, s conversion.Scope) error { + out.Hard = *(*core.ResourceList)(unsafe.Pointer(&in.Hard)) + out.Used = *(*core.ResourceList)(unsafe.Pointer(&in.Used)) + return nil +} + +// Convert_v1_ResourceQuotaStatus_To_core_ResourceQuotaStatus is an autogenerated conversion function. +func Convert_v1_ResourceQuotaStatus_To_core_ResourceQuotaStatus(in *v1.ResourceQuotaStatus, out *core.ResourceQuotaStatus, s conversion.Scope) error { + return autoConvert_v1_ResourceQuotaStatus_To_core_ResourceQuotaStatus(in, out, s) +} + +func autoConvert_core_ResourceQuotaStatus_To_v1_ResourceQuotaStatus(in *core.ResourceQuotaStatus, out *v1.ResourceQuotaStatus, s conversion.Scope) error { + out.Hard = *(*v1.ResourceList)(unsafe.Pointer(&in.Hard)) + out.Used = *(*v1.ResourceList)(unsafe.Pointer(&in.Used)) + return nil +} + +// Convert_core_ResourceQuotaStatus_To_v1_ResourceQuotaStatus is an autogenerated conversion function. +func Convert_core_ResourceQuotaStatus_To_v1_ResourceQuotaStatus(in *core.ResourceQuotaStatus, out *v1.ResourceQuotaStatus, s conversion.Scope) error { + return autoConvert_core_ResourceQuotaStatus_To_v1_ResourceQuotaStatus(in, out, s) +} + +func autoConvert_v1_ResourceRequirements_To_core_ResourceRequirements(in *v1.ResourceRequirements, out *core.ResourceRequirements, s conversion.Scope) error { + out.Limits = *(*core.ResourceList)(unsafe.Pointer(&in.Limits)) + out.Requests = *(*core.ResourceList)(unsafe.Pointer(&in.Requests)) + return nil +} + +// Convert_v1_ResourceRequirements_To_core_ResourceRequirements is an autogenerated conversion function. +func Convert_v1_ResourceRequirements_To_core_ResourceRequirements(in *v1.ResourceRequirements, out *core.ResourceRequirements, s conversion.Scope) error { + return autoConvert_v1_ResourceRequirements_To_core_ResourceRequirements(in, out, s) +} + +func autoConvert_core_ResourceRequirements_To_v1_ResourceRequirements(in *core.ResourceRequirements, out *v1.ResourceRequirements, s conversion.Scope) error { + out.Limits = *(*v1.ResourceList)(unsafe.Pointer(&in.Limits)) + out.Requests = *(*v1.ResourceList)(unsafe.Pointer(&in.Requests)) + return nil +} + +// Convert_core_ResourceRequirements_To_v1_ResourceRequirements is an autogenerated conversion function. +func Convert_core_ResourceRequirements_To_v1_ResourceRequirements(in *core.ResourceRequirements, out *v1.ResourceRequirements, s conversion.Scope) error { + return autoConvert_core_ResourceRequirements_To_v1_ResourceRequirements(in, out, s) +} + +func autoConvert_v1_SELinuxOptions_To_core_SELinuxOptions(in *v1.SELinuxOptions, out *core.SELinuxOptions, s conversion.Scope) error { + out.User = in.User + out.Role = in.Role + out.Type = in.Type + out.Level = in.Level + return nil +} + +// Convert_v1_SELinuxOptions_To_core_SELinuxOptions is an autogenerated conversion function. +func Convert_v1_SELinuxOptions_To_core_SELinuxOptions(in *v1.SELinuxOptions, out *core.SELinuxOptions, s conversion.Scope) error { + return autoConvert_v1_SELinuxOptions_To_core_SELinuxOptions(in, out, s) +} + +func autoConvert_core_SELinuxOptions_To_v1_SELinuxOptions(in *core.SELinuxOptions, out *v1.SELinuxOptions, s conversion.Scope) error { + out.User = in.User + out.Role = in.Role + out.Type = in.Type + out.Level = in.Level + return nil +} + +// Convert_core_SELinuxOptions_To_v1_SELinuxOptions is an autogenerated conversion function. +func Convert_core_SELinuxOptions_To_v1_SELinuxOptions(in *core.SELinuxOptions, out *v1.SELinuxOptions, s conversion.Scope) error { + return autoConvert_core_SELinuxOptions_To_v1_SELinuxOptions(in, out, s) +} + +func autoConvert_v1_ScaleIOPersistentVolumeSource_To_core_ScaleIOPersistentVolumeSource(in *v1.ScaleIOPersistentVolumeSource, out *core.ScaleIOPersistentVolumeSource, s conversion.Scope) error { + out.Gateway = in.Gateway + out.System = in.System + out.SecretRef = (*core.SecretReference)(unsafe.Pointer(in.SecretRef)) + out.SSLEnabled = in.SSLEnabled + out.ProtectionDomain = in.ProtectionDomain + out.StoragePool = in.StoragePool + out.StorageMode = in.StorageMode + out.VolumeName = in.VolumeName + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_v1_ScaleIOPersistentVolumeSource_To_core_ScaleIOPersistentVolumeSource is an autogenerated conversion function. +func Convert_v1_ScaleIOPersistentVolumeSource_To_core_ScaleIOPersistentVolumeSource(in *v1.ScaleIOPersistentVolumeSource, out *core.ScaleIOPersistentVolumeSource, s conversion.Scope) error { + return autoConvert_v1_ScaleIOPersistentVolumeSource_To_core_ScaleIOPersistentVolumeSource(in, out, s) +} + +func autoConvert_core_ScaleIOPersistentVolumeSource_To_v1_ScaleIOPersistentVolumeSource(in *core.ScaleIOPersistentVolumeSource, out *v1.ScaleIOPersistentVolumeSource, s conversion.Scope) error { + out.Gateway = in.Gateway + out.System = in.System + out.SecretRef = (*v1.SecretReference)(unsafe.Pointer(in.SecretRef)) + out.SSLEnabled = in.SSLEnabled + out.ProtectionDomain = in.ProtectionDomain + out.StoragePool = in.StoragePool + out.StorageMode = in.StorageMode + out.VolumeName = in.VolumeName + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_core_ScaleIOPersistentVolumeSource_To_v1_ScaleIOPersistentVolumeSource is an autogenerated conversion function. +func Convert_core_ScaleIOPersistentVolumeSource_To_v1_ScaleIOPersistentVolumeSource(in *core.ScaleIOPersistentVolumeSource, out *v1.ScaleIOPersistentVolumeSource, s conversion.Scope) error { + return autoConvert_core_ScaleIOPersistentVolumeSource_To_v1_ScaleIOPersistentVolumeSource(in, out, s) +} + +func autoConvert_v1_ScaleIOVolumeSource_To_core_ScaleIOVolumeSource(in *v1.ScaleIOVolumeSource, out *core.ScaleIOVolumeSource, s conversion.Scope) error { + out.Gateway = in.Gateway + out.System = in.System + out.SecretRef = (*core.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) + out.SSLEnabled = in.SSLEnabled + out.ProtectionDomain = in.ProtectionDomain + out.StoragePool = in.StoragePool + out.StorageMode = in.StorageMode + out.VolumeName = in.VolumeName + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_v1_ScaleIOVolumeSource_To_core_ScaleIOVolumeSource is an autogenerated conversion function. +func Convert_v1_ScaleIOVolumeSource_To_core_ScaleIOVolumeSource(in *v1.ScaleIOVolumeSource, out *core.ScaleIOVolumeSource, s conversion.Scope) error { + return autoConvert_v1_ScaleIOVolumeSource_To_core_ScaleIOVolumeSource(in, out, s) +} + +func autoConvert_core_ScaleIOVolumeSource_To_v1_ScaleIOVolumeSource(in *core.ScaleIOVolumeSource, out *v1.ScaleIOVolumeSource, s conversion.Scope) error { + out.Gateway = in.Gateway + out.System = in.System + out.SecretRef = (*v1.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) + out.SSLEnabled = in.SSLEnabled + out.ProtectionDomain = in.ProtectionDomain + out.StoragePool = in.StoragePool + out.StorageMode = in.StorageMode + out.VolumeName = in.VolumeName + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + return nil +} + +// Convert_core_ScaleIOVolumeSource_To_v1_ScaleIOVolumeSource is an autogenerated conversion function. +func Convert_core_ScaleIOVolumeSource_To_v1_ScaleIOVolumeSource(in *core.ScaleIOVolumeSource, out *v1.ScaleIOVolumeSource, s conversion.Scope) error { + return autoConvert_core_ScaleIOVolumeSource_To_v1_ScaleIOVolumeSource(in, out, s) +} + +func autoConvert_v1_Secret_To_core_Secret(in *v1.Secret, out *core.Secret, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Data = *(*map[string][]byte)(unsafe.Pointer(&in.Data)) + // INFO: in.StringData opted out of conversion generation + out.Type = core.SecretType(in.Type) + return nil +} + +func autoConvert_core_Secret_To_v1_Secret(in *core.Secret, out *v1.Secret, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Data = *(*map[string][]byte)(unsafe.Pointer(&in.Data)) + out.Type = v1.SecretType(in.Type) + return nil +} + +// Convert_core_Secret_To_v1_Secret is an autogenerated conversion function. +func Convert_core_Secret_To_v1_Secret(in *core.Secret, out *v1.Secret, s conversion.Scope) error { + return autoConvert_core_Secret_To_v1_Secret(in, out, s) +} + +func autoConvert_v1_SecretEnvSource_To_core_SecretEnvSource(in *v1.SecretEnvSource, out *core.SecretEnvSource, s conversion.Scope) error { + if err := Convert_v1_LocalObjectReference_To_core_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { + return err + } + out.Optional = (*bool)(unsafe.Pointer(in.Optional)) + return nil +} + +// Convert_v1_SecretEnvSource_To_core_SecretEnvSource is an autogenerated conversion function. +func Convert_v1_SecretEnvSource_To_core_SecretEnvSource(in *v1.SecretEnvSource, out *core.SecretEnvSource, s conversion.Scope) error { + return autoConvert_v1_SecretEnvSource_To_core_SecretEnvSource(in, out, s) +} + +func autoConvert_core_SecretEnvSource_To_v1_SecretEnvSource(in *core.SecretEnvSource, out *v1.SecretEnvSource, s conversion.Scope) error { + if err := Convert_core_LocalObjectReference_To_v1_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { + return err + } + out.Optional = (*bool)(unsafe.Pointer(in.Optional)) + return nil +} + +// Convert_core_SecretEnvSource_To_v1_SecretEnvSource is an autogenerated conversion function. +func Convert_core_SecretEnvSource_To_v1_SecretEnvSource(in *core.SecretEnvSource, out *v1.SecretEnvSource, s conversion.Scope) error { + return autoConvert_core_SecretEnvSource_To_v1_SecretEnvSource(in, out, s) +} + +func autoConvert_v1_SecretKeySelector_To_core_SecretKeySelector(in *v1.SecretKeySelector, out *core.SecretKeySelector, s conversion.Scope) error { + if err := Convert_v1_LocalObjectReference_To_core_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { + return err + } + out.Key = in.Key + out.Optional = (*bool)(unsafe.Pointer(in.Optional)) + return nil +} + +// Convert_v1_SecretKeySelector_To_core_SecretKeySelector is an autogenerated conversion function. +func Convert_v1_SecretKeySelector_To_core_SecretKeySelector(in *v1.SecretKeySelector, out *core.SecretKeySelector, s conversion.Scope) error { + return autoConvert_v1_SecretKeySelector_To_core_SecretKeySelector(in, out, s) +} + +func autoConvert_core_SecretKeySelector_To_v1_SecretKeySelector(in *core.SecretKeySelector, out *v1.SecretKeySelector, s conversion.Scope) error { + if err := Convert_core_LocalObjectReference_To_v1_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { + return err + } + out.Key = in.Key + out.Optional = (*bool)(unsafe.Pointer(in.Optional)) + return nil +} + +// Convert_core_SecretKeySelector_To_v1_SecretKeySelector is an autogenerated conversion function. +func Convert_core_SecretKeySelector_To_v1_SecretKeySelector(in *core.SecretKeySelector, out *v1.SecretKeySelector, s conversion.Scope) error { + return autoConvert_core_SecretKeySelector_To_v1_SecretKeySelector(in, out, s) +} + +func autoConvert_v1_SecretList_To_core_SecretList(in *v1.SecretList, out *core.SecretList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]core.Secret, len(*in)) + for i := range *in { + if err := Convert_v1_Secret_To_core_Secret(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_v1_SecretList_To_core_SecretList is an autogenerated conversion function. +func Convert_v1_SecretList_To_core_SecretList(in *v1.SecretList, out *core.SecretList, s conversion.Scope) error { + return autoConvert_v1_SecretList_To_core_SecretList(in, out, s) +} + +func autoConvert_core_SecretList_To_v1_SecretList(in *core.SecretList, out *v1.SecretList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1.Secret, len(*in)) + for i := range *in { + if err := Convert_core_Secret_To_v1_Secret(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_core_SecretList_To_v1_SecretList is an autogenerated conversion function. +func Convert_core_SecretList_To_v1_SecretList(in *core.SecretList, out *v1.SecretList, s conversion.Scope) error { + return autoConvert_core_SecretList_To_v1_SecretList(in, out, s) +} + +func autoConvert_v1_SecretProjection_To_core_SecretProjection(in *v1.SecretProjection, out *core.SecretProjection, s conversion.Scope) error { + if err := Convert_v1_LocalObjectReference_To_core_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { + return err + } + out.Items = *(*[]core.KeyToPath)(unsafe.Pointer(&in.Items)) + out.Optional = (*bool)(unsafe.Pointer(in.Optional)) + return nil +} + +// Convert_v1_SecretProjection_To_core_SecretProjection is an autogenerated conversion function. +func Convert_v1_SecretProjection_To_core_SecretProjection(in *v1.SecretProjection, out *core.SecretProjection, s conversion.Scope) error { + return autoConvert_v1_SecretProjection_To_core_SecretProjection(in, out, s) +} + +func autoConvert_core_SecretProjection_To_v1_SecretProjection(in *core.SecretProjection, out *v1.SecretProjection, s conversion.Scope) error { + if err := Convert_core_LocalObjectReference_To_v1_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { + return err + } + out.Items = *(*[]v1.KeyToPath)(unsafe.Pointer(&in.Items)) + out.Optional = (*bool)(unsafe.Pointer(in.Optional)) + return nil +} + +// Convert_core_SecretProjection_To_v1_SecretProjection is an autogenerated conversion function. +func Convert_core_SecretProjection_To_v1_SecretProjection(in *core.SecretProjection, out *v1.SecretProjection, s conversion.Scope) error { + return autoConvert_core_SecretProjection_To_v1_SecretProjection(in, out, s) +} + +func autoConvert_v1_SecretReference_To_core_SecretReference(in *v1.SecretReference, out *core.SecretReference, s conversion.Scope) error { + out.Name = in.Name + out.Namespace = in.Namespace + return nil +} + +// Convert_v1_SecretReference_To_core_SecretReference is an autogenerated conversion function. +func Convert_v1_SecretReference_To_core_SecretReference(in *v1.SecretReference, out *core.SecretReference, s conversion.Scope) error { + return autoConvert_v1_SecretReference_To_core_SecretReference(in, out, s) +} + +func autoConvert_core_SecretReference_To_v1_SecretReference(in *core.SecretReference, out *v1.SecretReference, s conversion.Scope) error { + out.Name = in.Name + out.Namespace = in.Namespace + return nil +} + +// Convert_core_SecretReference_To_v1_SecretReference is an autogenerated conversion function. +func Convert_core_SecretReference_To_v1_SecretReference(in *core.SecretReference, out *v1.SecretReference, s conversion.Scope) error { + return autoConvert_core_SecretReference_To_v1_SecretReference(in, out, s) +} + +func autoConvert_v1_SecretVolumeSource_To_core_SecretVolumeSource(in *v1.SecretVolumeSource, out *core.SecretVolumeSource, s conversion.Scope) error { + out.SecretName = in.SecretName + out.Items = *(*[]core.KeyToPath)(unsafe.Pointer(&in.Items)) + out.DefaultMode = (*int32)(unsafe.Pointer(in.DefaultMode)) + out.Optional = (*bool)(unsafe.Pointer(in.Optional)) + return nil +} + +// Convert_v1_SecretVolumeSource_To_core_SecretVolumeSource is an autogenerated conversion function. +func Convert_v1_SecretVolumeSource_To_core_SecretVolumeSource(in *v1.SecretVolumeSource, out *core.SecretVolumeSource, s conversion.Scope) error { + return autoConvert_v1_SecretVolumeSource_To_core_SecretVolumeSource(in, out, s) +} + +func autoConvert_core_SecretVolumeSource_To_v1_SecretVolumeSource(in *core.SecretVolumeSource, out *v1.SecretVolumeSource, s conversion.Scope) error { + out.SecretName = in.SecretName + out.Items = *(*[]v1.KeyToPath)(unsafe.Pointer(&in.Items)) + out.DefaultMode = (*int32)(unsafe.Pointer(in.DefaultMode)) + out.Optional = (*bool)(unsafe.Pointer(in.Optional)) + return nil +} + +// Convert_core_SecretVolumeSource_To_v1_SecretVolumeSource is an autogenerated conversion function. +func Convert_core_SecretVolumeSource_To_v1_SecretVolumeSource(in *core.SecretVolumeSource, out *v1.SecretVolumeSource, s conversion.Scope) error { + return autoConvert_core_SecretVolumeSource_To_v1_SecretVolumeSource(in, out, s) +} + +func autoConvert_v1_SecurityContext_To_core_SecurityContext(in *v1.SecurityContext, out *core.SecurityContext, s conversion.Scope) error { + out.Capabilities = (*core.Capabilities)(unsafe.Pointer(in.Capabilities)) + out.Privileged = (*bool)(unsafe.Pointer(in.Privileged)) + out.SELinuxOptions = (*core.SELinuxOptions)(unsafe.Pointer(in.SELinuxOptions)) + out.RunAsUser = (*int64)(unsafe.Pointer(in.RunAsUser)) + out.RunAsNonRoot = (*bool)(unsafe.Pointer(in.RunAsNonRoot)) + out.ReadOnlyRootFilesystem = (*bool)(unsafe.Pointer(in.ReadOnlyRootFilesystem)) + out.AllowPrivilegeEscalation = (*bool)(unsafe.Pointer(in.AllowPrivilegeEscalation)) + return nil +} + +// Convert_v1_SecurityContext_To_core_SecurityContext is an autogenerated conversion function. +func Convert_v1_SecurityContext_To_core_SecurityContext(in *v1.SecurityContext, out *core.SecurityContext, s conversion.Scope) error { + return autoConvert_v1_SecurityContext_To_core_SecurityContext(in, out, s) +} + +func autoConvert_core_SecurityContext_To_v1_SecurityContext(in *core.SecurityContext, out *v1.SecurityContext, s conversion.Scope) error { + out.Capabilities = (*v1.Capabilities)(unsafe.Pointer(in.Capabilities)) + out.Privileged = (*bool)(unsafe.Pointer(in.Privileged)) + out.SELinuxOptions = (*v1.SELinuxOptions)(unsafe.Pointer(in.SELinuxOptions)) + out.RunAsUser = (*int64)(unsafe.Pointer(in.RunAsUser)) + out.RunAsNonRoot = (*bool)(unsafe.Pointer(in.RunAsNonRoot)) + out.ReadOnlyRootFilesystem = (*bool)(unsafe.Pointer(in.ReadOnlyRootFilesystem)) + out.AllowPrivilegeEscalation = (*bool)(unsafe.Pointer(in.AllowPrivilegeEscalation)) + return nil +} + +func autoConvert_v1_SerializedReference_To_core_SerializedReference(in *v1.SerializedReference, out *core.SerializedReference, s conversion.Scope) error { + if err := Convert_v1_ObjectReference_To_core_ObjectReference(&in.Reference, &out.Reference, s); err != nil { + return err + } + return nil +} + +// Convert_v1_SerializedReference_To_core_SerializedReference is an autogenerated conversion function. +func Convert_v1_SerializedReference_To_core_SerializedReference(in *v1.SerializedReference, out *core.SerializedReference, s conversion.Scope) error { + return autoConvert_v1_SerializedReference_To_core_SerializedReference(in, out, s) +} + +func autoConvert_core_SerializedReference_To_v1_SerializedReference(in *core.SerializedReference, out *v1.SerializedReference, s conversion.Scope) error { + if err := Convert_core_ObjectReference_To_v1_ObjectReference(&in.Reference, &out.Reference, s); err != nil { + return err + } + return nil +} + +// Convert_core_SerializedReference_To_v1_SerializedReference is an autogenerated conversion function. +func Convert_core_SerializedReference_To_v1_SerializedReference(in *core.SerializedReference, out *v1.SerializedReference, s conversion.Scope) error { + return autoConvert_core_SerializedReference_To_v1_SerializedReference(in, out, s) +} + +func autoConvert_v1_Service_To_core_Service(in *v1.Service, out *core.Service, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_ServiceSpec_To_core_ServiceSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1_ServiceStatus_To_core_ServiceStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1_Service_To_core_Service is an autogenerated conversion function. +func Convert_v1_Service_To_core_Service(in *v1.Service, out *core.Service, s conversion.Scope) error { + return autoConvert_v1_Service_To_core_Service(in, out, s) +} + +func autoConvert_core_Service_To_v1_Service(in *core.Service, out *v1.Service, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_core_ServiceSpec_To_v1_ServiceSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_core_ServiceStatus_To_v1_ServiceStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_core_Service_To_v1_Service is an autogenerated conversion function. +func Convert_core_Service_To_v1_Service(in *core.Service, out *v1.Service, s conversion.Scope) error { + return autoConvert_core_Service_To_v1_Service(in, out, s) +} + +func autoConvert_v1_ServiceAccount_To_core_ServiceAccount(in *v1.ServiceAccount, out *core.ServiceAccount, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Secrets = *(*[]core.ObjectReference)(unsafe.Pointer(&in.Secrets)) + out.ImagePullSecrets = *(*[]core.LocalObjectReference)(unsafe.Pointer(&in.ImagePullSecrets)) + out.AutomountServiceAccountToken = (*bool)(unsafe.Pointer(in.AutomountServiceAccountToken)) + return nil +} + +// Convert_v1_ServiceAccount_To_core_ServiceAccount is an autogenerated conversion function. +func Convert_v1_ServiceAccount_To_core_ServiceAccount(in *v1.ServiceAccount, out *core.ServiceAccount, s conversion.Scope) error { + return autoConvert_v1_ServiceAccount_To_core_ServiceAccount(in, out, s) +} + +func autoConvert_core_ServiceAccount_To_v1_ServiceAccount(in *core.ServiceAccount, out *v1.ServiceAccount, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Secrets = *(*[]v1.ObjectReference)(unsafe.Pointer(&in.Secrets)) + out.ImagePullSecrets = *(*[]v1.LocalObjectReference)(unsafe.Pointer(&in.ImagePullSecrets)) + out.AutomountServiceAccountToken = (*bool)(unsafe.Pointer(in.AutomountServiceAccountToken)) + return nil +} + +// Convert_core_ServiceAccount_To_v1_ServiceAccount is an autogenerated conversion function. +func Convert_core_ServiceAccount_To_v1_ServiceAccount(in *core.ServiceAccount, out *v1.ServiceAccount, s conversion.Scope) error { + return autoConvert_core_ServiceAccount_To_v1_ServiceAccount(in, out, s) +} + +func autoConvert_v1_ServiceAccountList_To_core_ServiceAccountList(in *v1.ServiceAccountList, out *core.ServiceAccountList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]core.ServiceAccount)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1_ServiceAccountList_To_core_ServiceAccountList is an autogenerated conversion function. +func Convert_v1_ServiceAccountList_To_core_ServiceAccountList(in *v1.ServiceAccountList, out *core.ServiceAccountList, s conversion.Scope) error { + return autoConvert_v1_ServiceAccountList_To_core_ServiceAccountList(in, out, s) +} + +func autoConvert_core_ServiceAccountList_To_v1_ServiceAccountList(in *core.ServiceAccountList, out *v1.ServiceAccountList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]v1.ServiceAccount)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_core_ServiceAccountList_To_v1_ServiceAccountList is an autogenerated conversion function. +func Convert_core_ServiceAccountList_To_v1_ServiceAccountList(in *core.ServiceAccountList, out *v1.ServiceAccountList, s conversion.Scope) error { + return autoConvert_core_ServiceAccountList_To_v1_ServiceAccountList(in, out, s) +} + +func autoConvert_v1_ServiceList_To_core_ServiceList(in *v1.ServiceList, out *core.ServiceList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]core.Service, len(*in)) + for i := range *in { + if err := Convert_v1_Service_To_core_Service(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_v1_ServiceList_To_core_ServiceList is an autogenerated conversion function. +func Convert_v1_ServiceList_To_core_ServiceList(in *v1.ServiceList, out *core.ServiceList, s conversion.Scope) error { + return autoConvert_v1_ServiceList_To_core_ServiceList(in, out, s) +} + +func autoConvert_core_ServiceList_To_v1_ServiceList(in *core.ServiceList, out *v1.ServiceList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1.Service, len(*in)) + for i := range *in { + if err := Convert_core_Service_To_v1_Service(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_core_ServiceList_To_v1_ServiceList is an autogenerated conversion function. +func Convert_core_ServiceList_To_v1_ServiceList(in *core.ServiceList, out *v1.ServiceList, s conversion.Scope) error { + return autoConvert_core_ServiceList_To_v1_ServiceList(in, out, s) +} + +func autoConvert_v1_ServicePort_To_core_ServicePort(in *v1.ServicePort, out *core.ServicePort, s conversion.Scope) error { + out.Name = in.Name + out.Protocol = core.Protocol(in.Protocol) + out.Port = in.Port + out.TargetPort = in.TargetPort + out.NodePort = in.NodePort + return nil +} + +// Convert_v1_ServicePort_To_core_ServicePort is an autogenerated conversion function. +func Convert_v1_ServicePort_To_core_ServicePort(in *v1.ServicePort, out *core.ServicePort, s conversion.Scope) error { + return autoConvert_v1_ServicePort_To_core_ServicePort(in, out, s) +} + +func autoConvert_core_ServicePort_To_v1_ServicePort(in *core.ServicePort, out *v1.ServicePort, s conversion.Scope) error { + out.Name = in.Name + out.Protocol = v1.Protocol(in.Protocol) + out.Port = in.Port + out.TargetPort = in.TargetPort + out.NodePort = in.NodePort + return nil +} + +// Convert_core_ServicePort_To_v1_ServicePort is an autogenerated conversion function. +func Convert_core_ServicePort_To_v1_ServicePort(in *core.ServicePort, out *v1.ServicePort, s conversion.Scope) error { + return autoConvert_core_ServicePort_To_v1_ServicePort(in, out, s) +} + +func autoConvert_v1_ServiceProxyOptions_To_core_ServiceProxyOptions(in *v1.ServiceProxyOptions, out *core.ServiceProxyOptions, s conversion.Scope) error { + out.Path = in.Path + return nil +} + +// Convert_v1_ServiceProxyOptions_To_core_ServiceProxyOptions is an autogenerated conversion function. +func Convert_v1_ServiceProxyOptions_To_core_ServiceProxyOptions(in *v1.ServiceProxyOptions, out *core.ServiceProxyOptions, s conversion.Scope) error { + return autoConvert_v1_ServiceProxyOptions_To_core_ServiceProxyOptions(in, out, s) +} + +func autoConvert_core_ServiceProxyOptions_To_v1_ServiceProxyOptions(in *core.ServiceProxyOptions, out *v1.ServiceProxyOptions, s conversion.Scope) error { + out.Path = in.Path + return nil +} + +// Convert_core_ServiceProxyOptions_To_v1_ServiceProxyOptions is an autogenerated conversion function. +func Convert_core_ServiceProxyOptions_To_v1_ServiceProxyOptions(in *core.ServiceProxyOptions, out *v1.ServiceProxyOptions, s conversion.Scope) error { + return autoConvert_core_ServiceProxyOptions_To_v1_ServiceProxyOptions(in, out, s) +} + +func autoConvert_v1_ServiceSpec_To_core_ServiceSpec(in *v1.ServiceSpec, out *core.ServiceSpec, s conversion.Scope) error { + out.Ports = *(*[]core.ServicePort)(unsafe.Pointer(&in.Ports)) + out.Selector = *(*map[string]string)(unsafe.Pointer(&in.Selector)) + out.ClusterIP = in.ClusterIP + out.Type = core.ServiceType(in.Type) + out.ExternalIPs = *(*[]string)(unsafe.Pointer(&in.ExternalIPs)) + out.SessionAffinity = core.ServiceAffinity(in.SessionAffinity) + out.LoadBalancerIP = in.LoadBalancerIP + out.LoadBalancerSourceRanges = *(*[]string)(unsafe.Pointer(&in.LoadBalancerSourceRanges)) + out.ExternalName = in.ExternalName + out.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyType(in.ExternalTrafficPolicy) + out.HealthCheckNodePort = in.HealthCheckNodePort + out.PublishNotReadyAddresses = in.PublishNotReadyAddresses + out.SessionAffinityConfig = (*core.SessionAffinityConfig)(unsafe.Pointer(in.SessionAffinityConfig)) + return nil +} + +// Convert_v1_ServiceSpec_To_core_ServiceSpec is an autogenerated conversion function. +func Convert_v1_ServiceSpec_To_core_ServiceSpec(in *v1.ServiceSpec, out *core.ServiceSpec, s conversion.Scope) error { + return autoConvert_v1_ServiceSpec_To_core_ServiceSpec(in, out, s) +} + +func autoConvert_core_ServiceSpec_To_v1_ServiceSpec(in *core.ServiceSpec, out *v1.ServiceSpec, s conversion.Scope) error { + out.Type = v1.ServiceType(in.Type) + out.Ports = *(*[]v1.ServicePort)(unsafe.Pointer(&in.Ports)) + out.Selector = *(*map[string]string)(unsafe.Pointer(&in.Selector)) + out.ClusterIP = in.ClusterIP + out.ExternalName = in.ExternalName + out.ExternalIPs = *(*[]string)(unsafe.Pointer(&in.ExternalIPs)) + out.LoadBalancerIP = in.LoadBalancerIP + out.SessionAffinity = v1.ServiceAffinity(in.SessionAffinity) + out.SessionAffinityConfig = (*v1.SessionAffinityConfig)(unsafe.Pointer(in.SessionAffinityConfig)) + out.LoadBalancerSourceRanges = *(*[]string)(unsafe.Pointer(&in.LoadBalancerSourceRanges)) + out.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyType(in.ExternalTrafficPolicy) + out.HealthCheckNodePort = in.HealthCheckNodePort + out.PublishNotReadyAddresses = in.PublishNotReadyAddresses + return nil +} + +// Convert_core_ServiceSpec_To_v1_ServiceSpec is an autogenerated conversion function. +func Convert_core_ServiceSpec_To_v1_ServiceSpec(in *core.ServiceSpec, out *v1.ServiceSpec, s conversion.Scope) error { + return autoConvert_core_ServiceSpec_To_v1_ServiceSpec(in, out, s) +} + +func autoConvert_v1_ServiceStatus_To_core_ServiceStatus(in *v1.ServiceStatus, out *core.ServiceStatus, s conversion.Scope) error { + if err := Convert_v1_LoadBalancerStatus_To_core_LoadBalancerStatus(&in.LoadBalancer, &out.LoadBalancer, s); err != nil { + return err + } + return nil +} + +// Convert_v1_ServiceStatus_To_core_ServiceStatus is an autogenerated conversion function. +func Convert_v1_ServiceStatus_To_core_ServiceStatus(in *v1.ServiceStatus, out *core.ServiceStatus, s conversion.Scope) error { + return autoConvert_v1_ServiceStatus_To_core_ServiceStatus(in, out, s) +} + +func autoConvert_core_ServiceStatus_To_v1_ServiceStatus(in *core.ServiceStatus, out *v1.ServiceStatus, s conversion.Scope) error { + if err := Convert_core_LoadBalancerStatus_To_v1_LoadBalancerStatus(&in.LoadBalancer, &out.LoadBalancer, s); err != nil { + return err + } + return nil +} + +// Convert_core_ServiceStatus_To_v1_ServiceStatus is an autogenerated conversion function. +func Convert_core_ServiceStatus_To_v1_ServiceStatus(in *core.ServiceStatus, out *v1.ServiceStatus, s conversion.Scope) error { + return autoConvert_core_ServiceStatus_To_v1_ServiceStatus(in, out, s) +} + +func autoConvert_v1_SessionAffinityConfig_To_core_SessionAffinityConfig(in *v1.SessionAffinityConfig, out *core.SessionAffinityConfig, s conversion.Scope) error { + out.ClientIP = (*core.ClientIPConfig)(unsafe.Pointer(in.ClientIP)) + return nil +} + +// Convert_v1_SessionAffinityConfig_To_core_SessionAffinityConfig is an autogenerated conversion function. +func Convert_v1_SessionAffinityConfig_To_core_SessionAffinityConfig(in *v1.SessionAffinityConfig, out *core.SessionAffinityConfig, s conversion.Scope) error { + return autoConvert_v1_SessionAffinityConfig_To_core_SessionAffinityConfig(in, out, s) +} + +func autoConvert_core_SessionAffinityConfig_To_v1_SessionAffinityConfig(in *core.SessionAffinityConfig, out *v1.SessionAffinityConfig, s conversion.Scope) error { + out.ClientIP = (*v1.ClientIPConfig)(unsafe.Pointer(in.ClientIP)) + return nil +} + +// Convert_core_SessionAffinityConfig_To_v1_SessionAffinityConfig is an autogenerated conversion function. +func Convert_core_SessionAffinityConfig_To_v1_SessionAffinityConfig(in *core.SessionAffinityConfig, out *v1.SessionAffinityConfig, s conversion.Scope) error { + return autoConvert_core_SessionAffinityConfig_To_v1_SessionAffinityConfig(in, out, s) +} + +func autoConvert_v1_StorageOSPersistentVolumeSource_To_core_StorageOSPersistentVolumeSource(in *v1.StorageOSPersistentVolumeSource, out *core.StorageOSPersistentVolumeSource, s conversion.Scope) error { + out.VolumeName = in.VolumeName + out.VolumeNamespace = in.VolumeNamespace + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + out.SecretRef = (*core.ObjectReference)(unsafe.Pointer(in.SecretRef)) + return nil +} + +// Convert_v1_StorageOSPersistentVolumeSource_To_core_StorageOSPersistentVolumeSource is an autogenerated conversion function. +func Convert_v1_StorageOSPersistentVolumeSource_To_core_StorageOSPersistentVolumeSource(in *v1.StorageOSPersistentVolumeSource, out *core.StorageOSPersistentVolumeSource, s conversion.Scope) error { + return autoConvert_v1_StorageOSPersistentVolumeSource_To_core_StorageOSPersistentVolumeSource(in, out, s) +} + +func autoConvert_core_StorageOSPersistentVolumeSource_To_v1_StorageOSPersistentVolumeSource(in *core.StorageOSPersistentVolumeSource, out *v1.StorageOSPersistentVolumeSource, s conversion.Scope) error { + out.VolumeName = in.VolumeName + out.VolumeNamespace = in.VolumeNamespace + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + out.SecretRef = (*v1.ObjectReference)(unsafe.Pointer(in.SecretRef)) + return nil +} + +// Convert_core_StorageOSPersistentVolumeSource_To_v1_StorageOSPersistentVolumeSource is an autogenerated conversion function. +func Convert_core_StorageOSPersistentVolumeSource_To_v1_StorageOSPersistentVolumeSource(in *core.StorageOSPersistentVolumeSource, out *v1.StorageOSPersistentVolumeSource, s conversion.Scope) error { + return autoConvert_core_StorageOSPersistentVolumeSource_To_v1_StorageOSPersistentVolumeSource(in, out, s) +} + +func autoConvert_v1_StorageOSVolumeSource_To_core_StorageOSVolumeSource(in *v1.StorageOSVolumeSource, out *core.StorageOSVolumeSource, s conversion.Scope) error { + out.VolumeName = in.VolumeName + out.VolumeNamespace = in.VolumeNamespace + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + out.SecretRef = (*core.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) + return nil +} + +// Convert_v1_StorageOSVolumeSource_To_core_StorageOSVolumeSource is an autogenerated conversion function. +func Convert_v1_StorageOSVolumeSource_To_core_StorageOSVolumeSource(in *v1.StorageOSVolumeSource, out *core.StorageOSVolumeSource, s conversion.Scope) error { + return autoConvert_v1_StorageOSVolumeSource_To_core_StorageOSVolumeSource(in, out, s) +} + +func autoConvert_core_StorageOSVolumeSource_To_v1_StorageOSVolumeSource(in *core.StorageOSVolumeSource, out *v1.StorageOSVolumeSource, s conversion.Scope) error { + out.VolumeName = in.VolumeName + out.VolumeNamespace = in.VolumeNamespace + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + out.SecretRef = (*v1.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) + return nil +} + +// Convert_core_StorageOSVolumeSource_To_v1_StorageOSVolumeSource is an autogenerated conversion function. +func Convert_core_StorageOSVolumeSource_To_v1_StorageOSVolumeSource(in *core.StorageOSVolumeSource, out *v1.StorageOSVolumeSource, s conversion.Scope) error { + return autoConvert_core_StorageOSVolumeSource_To_v1_StorageOSVolumeSource(in, out, s) +} + +func autoConvert_v1_Sysctl_To_core_Sysctl(in *v1.Sysctl, out *core.Sysctl, s conversion.Scope) error { + out.Name = in.Name + out.Value = in.Value + return nil +} + +// Convert_v1_Sysctl_To_core_Sysctl is an autogenerated conversion function. +func Convert_v1_Sysctl_To_core_Sysctl(in *v1.Sysctl, out *core.Sysctl, s conversion.Scope) error { + return autoConvert_v1_Sysctl_To_core_Sysctl(in, out, s) +} + +func autoConvert_core_Sysctl_To_v1_Sysctl(in *core.Sysctl, out *v1.Sysctl, s conversion.Scope) error { + out.Name = in.Name + out.Value = in.Value + return nil +} + +// Convert_core_Sysctl_To_v1_Sysctl is an autogenerated conversion function. +func Convert_core_Sysctl_To_v1_Sysctl(in *core.Sysctl, out *v1.Sysctl, s conversion.Scope) error { + return autoConvert_core_Sysctl_To_v1_Sysctl(in, out, s) +} + +func autoConvert_v1_TCPSocketAction_To_core_TCPSocketAction(in *v1.TCPSocketAction, out *core.TCPSocketAction, s conversion.Scope) error { + out.Port = in.Port + out.Host = in.Host + return nil +} + +// Convert_v1_TCPSocketAction_To_core_TCPSocketAction is an autogenerated conversion function. +func Convert_v1_TCPSocketAction_To_core_TCPSocketAction(in *v1.TCPSocketAction, out *core.TCPSocketAction, s conversion.Scope) error { + return autoConvert_v1_TCPSocketAction_To_core_TCPSocketAction(in, out, s) +} + +func autoConvert_core_TCPSocketAction_To_v1_TCPSocketAction(in *core.TCPSocketAction, out *v1.TCPSocketAction, s conversion.Scope) error { + out.Port = in.Port + out.Host = in.Host + return nil +} + +// Convert_core_TCPSocketAction_To_v1_TCPSocketAction is an autogenerated conversion function. +func Convert_core_TCPSocketAction_To_v1_TCPSocketAction(in *core.TCPSocketAction, out *v1.TCPSocketAction, s conversion.Scope) error { + return autoConvert_core_TCPSocketAction_To_v1_TCPSocketAction(in, out, s) +} + +func autoConvert_v1_Taint_To_core_Taint(in *v1.Taint, out *core.Taint, s conversion.Scope) error { + out.Key = in.Key + out.Value = in.Value + out.Effect = core.TaintEffect(in.Effect) + out.TimeAdded = (*meta_v1.Time)(unsafe.Pointer(in.TimeAdded)) + return nil +} + +// Convert_v1_Taint_To_core_Taint is an autogenerated conversion function. +func Convert_v1_Taint_To_core_Taint(in *v1.Taint, out *core.Taint, s conversion.Scope) error { + return autoConvert_v1_Taint_To_core_Taint(in, out, s) +} + +func autoConvert_core_Taint_To_v1_Taint(in *core.Taint, out *v1.Taint, s conversion.Scope) error { + out.Key = in.Key + out.Value = in.Value + out.Effect = v1.TaintEffect(in.Effect) + out.TimeAdded = (*meta_v1.Time)(unsafe.Pointer(in.TimeAdded)) + return nil +} + +// Convert_core_Taint_To_v1_Taint is an autogenerated conversion function. +func Convert_core_Taint_To_v1_Taint(in *core.Taint, out *v1.Taint, s conversion.Scope) error { + return autoConvert_core_Taint_To_v1_Taint(in, out, s) +} + +func autoConvert_v1_Toleration_To_core_Toleration(in *v1.Toleration, out *core.Toleration, s conversion.Scope) error { + out.Key = in.Key + out.Operator = core.TolerationOperator(in.Operator) + out.Value = in.Value + out.Effect = core.TaintEffect(in.Effect) + out.TolerationSeconds = (*int64)(unsafe.Pointer(in.TolerationSeconds)) + return nil +} + +// Convert_v1_Toleration_To_core_Toleration is an autogenerated conversion function. +func Convert_v1_Toleration_To_core_Toleration(in *v1.Toleration, out *core.Toleration, s conversion.Scope) error { + return autoConvert_v1_Toleration_To_core_Toleration(in, out, s) +} + +func autoConvert_core_Toleration_To_v1_Toleration(in *core.Toleration, out *v1.Toleration, s conversion.Scope) error { + out.Key = in.Key + out.Operator = v1.TolerationOperator(in.Operator) + out.Value = in.Value + out.Effect = v1.TaintEffect(in.Effect) + out.TolerationSeconds = (*int64)(unsafe.Pointer(in.TolerationSeconds)) + return nil +} + +// Convert_core_Toleration_To_v1_Toleration is an autogenerated conversion function. +func Convert_core_Toleration_To_v1_Toleration(in *core.Toleration, out *v1.Toleration, s conversion.Scope) error { + return autoConvert_core_Toleration_To_v1_Toleration(in, out, s) +} + +func autoConvert_v1_Volume_To_core_Volume(in *v1.Volume, out *core.Volume, s conversion.Scope) error { + out.Name = in.Name + if err := Convert_v1_VolumeSource_To_core_VolumeSource(&in.VolumeSource, &out.VolumeSource, s); err != nil { + return err + } + return nil +} + +// Convert_v1_Volume_To_core_Volume is an autogenerated conversion function. +func Convert_v1_Volume_To_core_Volume(in *v1.Volume, out *core.Volume, s conversion.Scope) error { + return autoConvert_v1_Volume_To_core_Volume(in, out, s) +} + +func autoConvert_core_Volume_To_v1_Volume(in *core.Volume, out *v1.Volume, s conversion.Scope) error { + out.Name = in.Name + if err := Convert_core_VolumeSource_To_v1_VolumeSource(&in.VolumeSource, &out.VolumeSource, s); err != nil { + return err + } + return nil +} + +// Convert_core_Volume_To_v1_Volume is an autogenerated conversion function. +func Convert_core_Volume_To_v1_Volume(in *core.Volume, out *v1.Volume, s conversion.Scope) error { + return autoConvert_core_Volume_To_v1_Volume(in, out, s) +} + +func autoConvert_v1_VolumeDevice_To_core_VolumeDevice(in *v1.VolumeDevice, out *core.VolumeDevice, s conversion.Scope) error { + out.Name = in.Name + out.DevicePath = in.DevicePath + return nil +} + +// Convert_v1_VolumeDevice_To_core_VolumeDevice is an autogenerated conversion function. +func Convert_v1_VolumeDevice_To_core_VolumeDevice(in *v1.VolumeDevice, out *core.VolumeDevice, s conversion.Scope) error { + return autoConvert_v1_VolumeDevice_To_core_VolumeDevice(in, out, s) +} + +func autoConvert_core_VolumeDevice_To_v1_VolumeDevice(in *core.VolumeDevice, out *v1.VolumeDevice, s conversion.Scope) error { + out.Name = in.Name + out.DevicePath = in.DevicePath + return nil +} + +// Convert_core_VolumeDevice_To_v1_VolumeDevice is an autogenerated conversion function. +func Convert_core_VolumeDevice_To_v1_VolumeDevice(in *core.VolumeDevice, out *v1.VolumeDevice, s conversion.Scope) error { + return autoConvert_core_VolumeDevice_To_v1_VolumeDevice(in, out, s) +} + +func autoConvert_v1_VolumeMount_To_core_VolumeMount(in *v1.VolumeMount, out *core.VolumeMount, s conversion.Scope) error { + out.Name = in.Name + out.ReadOnly = in.ReadOnly + out.MountPath = in.MountPath + out.SubPath = in.SubPath + out.MountPropagation = (*core.MountPropagationMode)(unsafe.Pointer(in.MountPropagation)) + return nil +} + +// Convert_v1_VolumeMount_To_core_VolumeMount is an autogenerated conversion function. +func Convert_v1_VolumeMount_To_core_VolumeMount(in *v1.VolumeMount, out *core.VolumeMount, s conversion.Scope) error { + return autoConvert_v1_VolumeMount_To_core_VolumeMount(in, out, s) +} + +func autoConvert_core_VolumeMount_To_v1_VolumeMount(in *core.VolumeMount, out *v1.VolumeMount, s conversion.Scope) error { + out.Name = in.Name + out.ReadOnly = in.ReadOnly + out.MountPath = in.MountPath + out.SubPath = in.SubPath + out.MountPropagation = (*v1.MountPropagationMode)(unsafe.Pointer(in.MountPropagation)) + return nil +} + +// Convert_core_VolumeMount_To_v1_VolumeMount is an autogenerated conversion function. +func Convert_core_VolumeMount_To_v1_VolumeMount(in *core.VolumeMount, out *v1.VolumeMount, s conversion.Scope) error { + return autoConvert_core_VolumeMount_To_v1_VolumeMount(in, out, s) +} + +func autoConvert_v1_VolumeProjection_To_core_VolumeProjection(in *v1.VolumeProjection, out *core.VolumeProjection, s conversion.Scope) error { + out.Secret = (*core.SecretProjection)(unsafe.Pointer(in.Secret)) + out.DownwardAPI = (*core.DownwardAPIProjection)(unsafe.Pointer(in.DownwardAPI)) + out.ConfigMap = (*core.ConfigMapProjection)(unsafe.Pointer(in.ConfigMap)) + return nil +} + +// Convert_v1_VolumeProjection_To_core_VolumeProjection is an autogenerated conversion function. +func Convert_v1_VolumeProjection_To_core_VolumeProjection(in *v1.VolumeProjection, out *core.VolumeProjection, s conversion.Scope) error { + return autoConvert_v1_VolumeProjection_To_core_VolumeProjection(in, out, s) +} + +func autoConvert_core_VolumeProjection_To_v1_VolumeProjection(in *core.VolumeProjection, out *v1.VolumeProjection, s conversion.Scope) error { + out.Secret = (*v1.SecretProjection)(unsafe.Pointer(in.Secret)) + out.DownwardAPI = (*v1.DownwardAPIProjection)(unsafe.Pointer(in.DownwardAPI)) + out.ConfigMap = (*v1.ConfigMapProjection)(unsafe.Pointer(in.ConfigMap)) + return nil +} + +// Convert_core_VolumeProjection_To_v1_VolumeProjection is an autogenerated conversion function. +func Convert_core_VolumeProjection_To_v1_VolumeProjection(in *core.VolumeProjection, out *v1.VolumeProjection, s conversion.Scope) error { + return autoConvert_core_VolumeProjection_To_v1_VolumeProjection(in, out, s) +} + +func autoConvert_v1_VolumeSource_To_core_VolumeSource(in *v1.VolumeSource, out *core.VolumeSource, s conversion.Scope) error { + out.HostPath = (*core.HostPathVolumeSource)(unsafe.Pointer(in.HostPath)) + out.EmptyDir = (*core.EmptyDirVolumeSource)(unsafe.Pointer(in.EmptyDir)) + out.GCEPersistentDisk = (*core.GCEPersistentDiskVolumeSource)(unsafe.Pointer(in.GCEPersistentDisk)) + out.AWSElasticBlockStore = (*core.AWSElasticBlockStoreVolumeSource)(unsafe.Pointer(in.AWSElasticBlockStore)) + out.GitRepo = (*core.GitRepoVolumeSource)(unsafe.Pointer(in.GitRepo)) + out.Secret = (*core.SecretVolumeSource)(unsafe.Pointer(in.Secret)) + out.NFS = (*core.NFSVolumeSource)(unsafe.Pointer(in.NFS)) + out.ISCSI = (*core.ISCSIVolumeSource)(unsafe.Pointer(in.ISCSI)) + out.Glusterfs = (*core.GlusterfsVolumeSource)(unsafe.Pointer(in.Glusterfs)) + out.PersistentVolumeClaim = (*core.PersistentVolumeClaimVolumeSource)(unsafe.Pointer(in.PersistentVolumeClaim)) + out.RBD = (*core.RBDVolumeSource)(unsafe.Pointer(in.RBD)) + out.FlexVolume = (*core.FlexVolumeSource)(unsafe.Pointer(in.FlexVolume)) + out.Cinder = (*core.CinderVolumeSource)(unsafe.Pointer(in.Cinder)) + out.CephFS = (*core.CephFSVolumeSource)(unsafe.Pointer(in.CephFS)) + out.Flocker = (*core.FlockerVolumeSource)(unsafe.Pointer(in.Flocker)) + out.DownwardAPI = (*core.DownwardAPIVolumeSource)(unsafe.Pointer(in.DownwardAPI)) + out.FC = (*core.FCVolumeSource)(unsafe.Pointer(in.FC)) + out.AzureFile = (*core.AzureFileVolumeSource)(unsafe.Pointer(in.AzureFile)) + out.ConfigMap = (*core.ConfigMapVolumeSource)(unsafe.Pointer(in.ConfigMap)) + out.VsphereVolume = (*core.VsphereVirtualDiskVolumeSource)(unsafe.Pointer(in.VsphereVolume)) + out.Quobyte = (*core.QuobyteVolumeSource)(unsafe.Pointer(in.Quobyte)) + out.AzureDisk = (*core.AzureDiskVolumeSource)(unsafe.Pointer(in.AzureDisk)) + out.PhotonPersistentDisk = (*core.PhotonPersistentDiskVolumeSource)(unsafe.Pointer(in.PhotonPersistentDisk)) + out.Projected = (*core.ProjectedVolumeSource)(unsafe.Pointer(in.Projected)) + out.PortworxVolume = (*core.PortworxVolumeSource)(unsafe.Pointer(in.PortworxVolume)) + out.ScaleIO = (*core.ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO)) + out.StorageOS = (*core.StorageOSVolumeSource)(unsafe.Pointer(in.StorageOS)) + return nil +} + +// Convert_v1_VolumeSource_To_core_VolumeSource is an autogenerated conversion function. +func Convert_v1_VolumeSource_To_core_VolumeSource(in *v1.VolumeSource, out *core.VolumeSource, s conversion.Scope) error { + return autoConvert_v1_VolumeSource_To_core_VolumeSource(in, out, s) +} + +func autoConvert_core_VolumeSource_To_v1_VolumeSource(in *core.VolumeSource, out *v1.VolumeSource, s conversion.Scope) error { + out.HostPath = (*v1.HostPathVolumeSource)(unsafe.Pointer(in.HostPath)) + out.EmptyDir = (*v1.EmptyDirVolumeSource)(unsafe.Pointer(in.EmptyDir)) + out.GCEPersistentDisk = (*v1.GCEPersistentDiskVolumeSource)(unsafe.Pointer(in.GCEPersistentDisk)) + out.AWSElasticBlockStore = (*v1.AWSElasticBlockStoreVolumeSource)(unsafe.Pointer(in.AWSElasticBlockStore)) + out.GitRepo = (*v1.GitRepoVolumeSource)(unsafe.Pointer(in.GitRepo)) + out.Secret = (*v1.SecretVolumeSource)(unsafe.Pointer(in.Secret)) + out.NFS = (*v1.NFSVolumeSource)(unsafe.Pointer(in.NFS)) + out.ISCSI = (*v1.ISCSIVolumeSource)(unsafe.Pointer(in.ISCSI)) + out.Glusterfs = (*v1.GlusterfsVolumeSource)(unsafe.Pointer(in.Glusterfs)) + out.PersistentVolumeClaim = (*v1.PersistentVolumeClaimVolumeSource)(unsafe.Pointer(in.PersistentVolumeClaim)) + out.RBD = (*v1.RBDVolumeSource)(unsafe.Pointer(in.RBD)) + out.Quobyte = (*v1.QuobyteVolumeSource)(unsafe.Pointer(in.Quobyte)) + out.FlexVolume = (*v1.FlexVolumeSource)(unsafe.Pointer(in.FlexVolume)) + out.Cinder = (*v1.CinderVolumeSource)(unsafe.Pointer(in.Cinder)) + out.CephFS = (*v1.CephFSVolumeSource)(unsafe.Pointer(in.CephFS)) + out.Flocker = (*v1.FlockerVolumeSource)(unsafe.Pointer(in.Flocker)) + out.DownwardAPI = (*v1.DownwardAPIVolumeSource)(unsafe.Pointer(in.DownwardAPI)) + out.FC = (*v1.FCVolumeSource)(unsafe.Pointer(in.FC)) + out.AzureFile = (*v1.AzureFileVolumeSource)(unsafe.Pointer(in.AzureFile)) + out.ConfigMap = (*v1.ConfigMapVolumeSource)(unsafe.Pointer(in.ConfigMap)) + out.VsphereVolume = (*v1.VsphereVirtualDiskVolumeSource)(unsafe.Pointer(in.VsphereVolume)) + out.AzureDisk = (*v1.AzureDiskVolumeSource)(unsafe.Pointer(in.AzureDisk)) + out.PhotonPersistentDisk = (*v1.PhotonPersistentDiskVolumeSource)(unsafe.Pointer(in.PhotonPersistentDisk)) + out.Projected = (*v1.ProjectedVolumeSource)(unsafe.Pointer(in.Projected)) + out.PortworxVolume = (*v1.PortworxVolumeSource)(unsafe.Pointer(in.PortworxVolume)) + out.ScaleIO = (*v1.ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO)) + out.StorageOS = (*v1.StorageOSVolumeSource)(unsafe.Pointer(in.StorageOS)) + return nil +} + +// Convert_core_VolumeSource_To_v1_VolumeSource is an autogenerated conversion function. +func Convert_core_VolumeSource_To_v1_VolumeSource(in *core.VolumeSource, out *v1.VolumeSource, s conversion.Scope) error { + return autoConvert_core_VolumeSource_To_v1_VolumeSource(in, out, s) +} + +func autoConvert_v1_VsphereVirtualDiskVolumeSource_To_core_VsphereVirtualDiskVolumeSource(in *v1.VsphereVirtualDiskVolumeSource, out *core.VsphereVirtualDiskVolumeSource, s conversion.Scope) error { + out.VolumePath = in.VolumePath + out.FSType = in.FSType + out.StoragePolicyName = in.StoragePolicyName + out.StoragePolicyID = in.StoragePolicyID + return nil +} + +// Convert_v1_VsphereVirtualDiskVolumeSource_To_core_VsphereVirtualDiskVolumeSource is an autogenerated conversion function. +func Convert_v1_VsphereVirtualDiskVolumeSource_To_core_VsphereVirtualDiskVolumeSource(in *v1.VsphereVirtualDiskVolumeSource, out *core.VsphereVirtualDiskVolumeSource, s conversion.Scope) error { + return autoConvert_v1_VsphereVirtualDiskVolumeSource_To_core_VsphereVirtualDiskVolumeSource(in, out, s) +} + +func autoConvert_core_VsphereVirtualDiskVolumeSource_To_v1_VsphereVirtualDiskVolumeSource(in *core.VsphereVirtualDiskVolumeSource, out *v1.VsphereVirtualDiskVolumeSource, s conversion.Scope) error { + out.VolumePath = in.VolumePath + out.FSType = in.FSType + out.StoragePolicyName = in.StoragePolicyName + out.StoragePolicyID = in.StoragePolicyID + return nil +} + +// Convert_core_VsphereVirtualDiskVolumeSource_To_v1_VsphereVirtualDiskVolumeSource is an autogenerated conversion function. +func Convert_core_VsphereVirtualDiskVolumeSource_To_v1_VsphereVirtualDiskVolumeSource(in *core.VsphereVirtualDiskVolumeSource, out *v1.VsphereVirtualDiskVolumeSource, s conversion.Scope) error { + return autoConvert_core_VsphereVirtualDiskVolumeSource_To_v1_VsphereVirtualDiskVolumeSource(in, out, s) +} + +func autoConvert_v1_WeightedPodAffinityTerm_To_core_WeightedPodAffinityTerm(in *v1.WeightedPodAffinityTerm, out *core.WeightedPodAffinityTerm, s conversion.Scope) error { + out.Weight = in.Weight + if err := Convert_v1_PodAffinityTerm_To_core_PodAffinityTerm(&in.PodAffinityTerm, &out.PodAffinityTerm, s); err != nil { + return err + } + return nil +} + +// Convert_v1_WeightedPodAffinityTerm_To_core_WeightedPodAffinityTerm is an autogenerated conversion function. +func Convert_v1_WeightedPodAffinityTerm_To_core_WeightedPodAffinityTerm(in *v1.WeightedPodAffinityTerm, out *core.WeightedPodAffinityTerm, s conversion.Scope) error { + return autoConvert_v1_WeightedPodAffinityTerm_To_core_WeightedPodAffinityTerm(in, out, s) +} + +func autoConvert_core_WeightedPodAffinityTerm_To_v1_WeightedPodAffinityTerm(in *core.WeightedPodAffinityTerm, out *v1.WeightedPodAffinityTerm, s conversion.Scope) error { + out.Weight = in.Weight + if err := Convert_core_PodAffinityTerm_To_v1_PodAffinityTerm(&in.PodAffinityTerm, &out.PodAffinityTerm, s); err != nil { + return err + } + return nil +} + +// Convert_core_WeightedPodAffinityTerm_To_v1_WeightedPodAffinityTerm is an autogenerated conversion function. +func Convert_core_WeightedPodAffinityTerm_To_v1_WeightedPodAffinityTerm(in *core.WeightedPodAffinityTerm, out *v1.WeightedPodAffinityTerm, s conversion.Scope) error { + return autoConvert_core_WeightedPodAffinityTerm_To_v1_WeightedPodAffinityTerm(in, out, s) +} diff --git a/pkg/apis/core/v1/zz_generated.defaults.go b/pkg/apis/core/v1/zz_generated.defaults.go new file mode 100644 index 00000000000..468969442e0 --- /dev/null +++ b/pkg/apis/core/v1/zz_generated.defaults.go @@ -0,0 +1,638 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by defaulter-gen. Do not edit it manually! + +package v1 + +import ( + v1 "k8s.io/api/core/v1" + 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(&v1.ConfigMap{}, func(obj interface{}) { SetObjectDefaults_ConfigMap(obj.(*v1.ConfigMap)) }) + scheme.AddTypeDefaultingFunc(&v1.ConfigMapList{}, func(obj interface{}) { SetObjectDefaults_ConfigMapList(obj.(*v1.ConfigMapList)) }) + scheme.AddTypeDefaultingFunc(&v1.Endpoints{}, func(obj interface{}) { SetObjectDefaults_Endpoints(obj.(*v1.Endpoints)) }) + scheme.AddTypeDefaultingFunc(&v1.EndpointsList{}, func(obj interface{}) { SetObjectDefaults_EndpointsList(obj.(*v1.EndpointsList)) }) + scheme.AddTypeDefaultingFunc(&v1.LimitRange{}, func(obj interface{}) { SetObjectDefaults_LimitRange(obj.(*v1.LimitRange)) }) + scheme.AddTypeDefaultingFunc(&v1.LimitRangeList{}, func(obj interface{}) { SetObjectDefaults_LimitRangeList(obj.(*v1.LimitRangeList)) }) + scheme.AddTypeDefaultingFunc(&v1.Namespace{}, func(obj interface{}) { SetObjectDefaults_Namespace(obj.(*v1.Namespace)) }) + scheme.AddTypeDefaultingFunc(&v1.NamespaceList{}, func(obj interface{}) { SetObjectDefaults_NamespaceList(obj.(*v1.NamespaceList)) }) + scheme.AddTypeDefaultingFunc(&v1.Node{}, func(obj interface{}) { SetObjectDefaults_Node(obj.(*v1.Node)) }) + scheme.AddTypeDefaultingFunc(&v1.NodeList{}, func(obj interface{}) { SetObjectDefaults_NodeList(obj.(*v1.NodeList)) }) + scheme.AddTypeDefaultingFunc(&v1.PersistentVolume{}, func(obj interface{}) { SetObjectDefaults_PersistentVolume(obj.(*v1.PersistentVolume)) }) + scheme.AddTypeDefaultingFunc(&v1.PersistentVolumeClaim{}, func(obj interface{}) { SetObjectDefaults_PersistentVolumeClaim(obj.(*v1.PersistentVolumeClaim)) }) + scheme.AddTypeDefaultingFunc(&v1.PersistentVolumeClaimList{}, func(obj interface{}) { + SetObjectDefaults_PersistentVolumeClaimList(obj.(*v1.PersistentVolumeClaimList)) + }) + scheme.AddTypeDefaultingFunc(&v1.PersistentVolumeList{}, func(obj interface{}) { SetObjectDefaults_PersistentVolumeList(obj.(*v1.PersistentVolumeList)) }) + scheme.AddTypeDefaultingFunc(&v1.Pod{}, func(obj interface{}) { SetObjectDefaults_Pod(obj.(*v1.Pod)) }) + scheme.AddTypeDefaultingFunc(&v1.PodList{}, func(obj interface{}) { SetObjectDefaults_PodList(obj.(*v1.PodList)) }) + scheme.AddTypeDefaultingFunc(&v1.PodTemplate{}, func(obj interface{}) { SetObjectDefaults_PodTemplate(obj.(*v1.PodTemplate)) }) + scheme.AddTypeDefaultingFunc(&v1.PodTemplateList{}, func(obj interface{}) { SetObjectDefaults_PodTemplateList(obj.(*v1.PodTemplateList)) }) + scheme.AddTypeDefaultingFunc(&v1.ReplicationController{}, func(obj interface{}) { SetObjectDefaults_ReplicationController(obj.(*v1.ReplicationController)) }) + scheme.AddTypeDefaultingFunc(&v1.ReplicationControllerList{}, func(obj interface{}) { + SetObjectDefaults_ReplicationControllerList(obj.(*v1.ReplicationControllerList)) + }) + scheme.AddTypeDefaultingFunc(&v1.ResourceQuota{}, func(obj interface{}) { SetObjectDefaults_ResourceQuota(obj.(*v1.ResourceQuota)) }) + scheme.AddTypeDefaultingFunc(&v1.ResourceQuotaList{}, func(obj interface{}) { SetObjectDefaults_ResourceQuotaList(obj.(*v1.ResourceQuotaList)) }) + scheme.AddTypeDefaultingFunc(&v1.Secret{}, func(obj interface{}) { SetObjectDefaults_Secret(obj.(*v1.Secret)) }) + scheme.AddTypeDefaultingFunc(&v1.SecretList{}, func(obj interface{}) { SetObjectDefaults_SecretList(obj.(*v1.SecretList)) }) + scheme.AddTypeDefaultingFunc(&v1.Service{}, func(obj interface{}) { SetObjectDefaults_Service(obj.(*v1.Service)) }) + scheme.AddTypeDefaultingFunc(&v1.ServiceList{}, func(obj interface{}) { SetObjectDefaults_ServiceList(obj.(*v1.ServiceList)) }) + return nil +} + +func SetObjectDefaults_ConfigMap(in *v1.ConfigMap) { + SetDefaults_ConfigMap(in) +} + +func SetObjectDefaults_ConfigMapList(in *v1.ConfigMapList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_ConfigMap(a) + } +} + +func SetObjectDefaults_Endpoints(in *v1.Endpoints) { + SetDefaults_Endpoints(in) +} + +func SetObjectDefaults_EndpointsList(in *v1.EndpointsList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_Endpoints(a) + } +} + +func SetObjectDefaults_LimitRange(in *v1.LimitRange) { + for i := range in.Spec.Limits { + a := &in.Spec.Limits[i] + SetDefaults_LimitRangeItem(a) + SetDefaults_ResourceList(&a.Max) + SetDefaults_ResourceList(&a.Min) + SetDefaults_ResourceList(&a.Default) + SetDefaults_ResourceList(&a.DefaultRequest) + SetDefaults_ResourceList(&a.MaxLimitRequestRatio) + } +} + +func SetObjectDefaults_LimitRangeList(in *v1.LimitRangeList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_LimitRange(a) + } +} + +func SetObjectDefaults_Namespace(in *v1.Namespace) { + SetDefaults_NamespaceStatus(&in.Status) +} + +func SetObjectDefaults_NamespaceList(in *v1.NamespaceList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_Namespace(a) + } +} + +func SetObjectDefaults_Node(in *v1.Node) { + SetDefaults_Node(in) + SetDefaults_NodeStatus(&in.Status) + SetDefaults_ResourceList(&in.Status.Capacity) + SetDefaults_ResourceList(&in.Status.Allocatable) +} + +func SetObjectDefaults_NodeList(in *v1.NodeList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_Node(a) + } +} + +func SetObjectDefaults_PersistentVolume(in *v1.PersistentVolume) { + SetDefaults_PersistentVolume(in) + SetDefaults_ResourceList(&in.Spec.Capacity) + if in.Spec.PersistentVolumeSource.HostPath != nil { + SetDefaults_HostPathVolumeSource(in.Spec.PersistentVolumeSource.HostPath) + } + if in.Spec.PersistentVolumeSource.RBD != nil { + SetDefaults_RBDPersistentVolumeSource(in.Spec.PersistentVolumeSource.RBD) + } + if in.Spec.PersistentVolumeSource.ISCSI != nil { + SetDefaults_ISCSIPersistentVolumeSource(in.Spec.PersistentVolumeSource.ISCSI) + } + if in.Spec.PersistentVolumeSource.AzureDisk != nil { + SetDefaults_AzureDiskVolumeSource(in.Spec.PersistentVolumeSource.AzureDisk) + } + if in.Spec.PersistentVolumeSource.ScaleIO != nil { + SetDefaults_ScaleIOPersistentVolumeSource(in.Spec.PersistentVolumeSource.ScaleIO) + } +} + +func SetObjectDefaults_PersistentVolumeClaim(in *v1.PersistentVolumeClaim) { + SetDefaults_PersistentVolumeClaim(in) + SetDefaults_ResourceList(&in.Spec.Resources.Limits) + SetDefaults_ResourceList(&in.Spec.Resources.Requests) + SetDefaults_ResourceList(&in.Status.Capacity) +} + +func SetObjectDefaults_PersistentVolumeClaimList(in *v1.PersistentVolumeClaimList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_PersistentVolumeClaim(a) + } +} + +func SetObjectDefaults_PersistentVolumeList(in *v1.PersistentVolumeList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_PersistentVolume(a) + } +} + +func SetObjectDefaults_Pod(in *v1.Pod) { + SetDefaults_Pod(in) + SetDefaults_PodSpec(&in.Spec) + for i := range in.Spec.Volumes { + a := &in.Spec.Volumes[i] + SetDefaults_Volume(a) + if a.VolumeSource.HostPath != nil { + SetDefaults_HostPathVolumeSource(a.VolumeSource.HostPath) + } + if a.VolumeSource.Secret != nil { + SetDefaults_SecretVolumeSource(a.VolumeSource.Secret) + } + if a.VolumeSource.ISCSI != nil { + SetDefaults_ISCSIVolumeSource(a.VolumeSource.ISCSI) + } + if a.VolumeSource.RBD != nil { + SetDefaults_RBDVolumeSource(a.VolumeSource.RBD) + } + if a.VolumeSource.DownwardAPI != nil { + SetDefaults_DownwardAPIVolumeSource(a.VolumeSource.DownwardAPI) + for j := range a.VolumeSource.DownwardAPI.Items { + b := &a.VolumeSource.DownwardAPI.Items[j] + if b.FieldRef != nil { + SetDefaults_ObjectFieldSelector(b.FieldRef) + } + } + } + if a.VolumeSource.ConfigMap != nil { + SetDefaults_ConfigMapVolumeSource(a.VolumeSource.ConfigMap) + } + if a.VolumeSource.AzureDisk != nil { + SetDefaults_AzureDiskVolumeSource(a.VolumeSource.AzureDisk) + } + if a.VolumeSource.Projected != nil { + SetDefaults_ProjectedVolumeSource(a.VolumeSource.Projected) + for j := range a.VolumeSource.Projected.Sources { + b := &a.VolumeSource.Projected.Sources[j] + if b.DownwardAPI != nil { + for k := range b.DownwardAPI.Items { + c := &b.DownwardAPI.Items[k] + if c.FieldRef != nil { + SetDefaults_ObjectFieldSelector(c.FieldRef) + } + } + } + } + } + if a.VolumeSource.ScaleIO != nil { + SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) + } + } + for i := range in.Spec.InitContainers { + a := &in.Spec.InitContainers[i] + SetDefaults_Container(a) + for j := range a.Ports { + b := &a.Ports[j] + SetDefaults_ContainerPort(b) + } + for j := range a.Env { + b := &a.Env[j] + if b.ValueFrom != nil { + if b.ValueFrom.FieldRef != nil { + SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) + } + } + } + SetDefaults_ResourceList(&a.Resources.Limits) + SetDefaults_ResourceList(&a.Resources.Requests) + if a.LivenessProbe != nil { + SetDefaults_Probe(a.LivenessProbe) + if a.LivenessProbe.Handler.HTTPGet != nil { + SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) + } + } + if a.ReadinessProbe != nil { + SetDefaults_Probe(a.ReadinessProbe) + if a.ReadinessProbe.Handler.HTTPGet != nil { + SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) + } + } + if a.Lifecycle != nil { + if a.Lifecycle.PostStart != nil { + if a.Lifecycle.PostStart.HTTPGet != nil { + SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) + } + } + if a.Lifecycle.PreStop != nil { + if a.Lifecycle.PreStop.HTTPGet != nil { + SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) + } + } + } + } + for i := range in.Spec.Containers { + a := &in.Spec.Containers[i] + SetDefaults_Container(a) + for j := range a.Ports { + b := &a.Ports[j] + SetDefaults_ContainerPort(b) + } + for j := range a.Env { + b := &a.Env[j] + if b.ValueFrom != nil { + if b.ValueFrom.FieldRef != nil { + SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) + } + } + } + SetDefaults_ResourceList(&a.Resources.Limits) + SetDefaults_ResourceList(&a.Resources.Requests) + if a.LivenessProbe != nil { + SetDefaults_Probe(a.LivenessProbe) + if a.LivenessProbe.Handler.HTTPGet != nil { + SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) + } + } + if a.ReadinessProbe != nil { + SetDefaults_Probe(a.ReadinessProbe) + if a.ReadinessProbe.Handler.HTTPGet != nil { + SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) + } + } + if a.Lifecycle != nil { + if a.Lifecycle.PostStart != nil { + if a.Lifecycle.PostStart.HTTPGet != nil { + SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) + } + } + if a.Lifecycle.PreStop != nil { + if a.Lifecycle.PreStop.HTTPGet != nil { + SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) + } + } + } + } +} + +func SetObjectDefaults_PodList(in *v1.PodList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_Pod(a) + } +} + +func SetObjectDefaults_PodTemplate(in *v1.PodTemplate) { + SetDefaults_PodSpec(&in.Template.Spec) + for i := range in.Template.Spec.Volumes { + a := &in.Template.Spec.Volumes[i] + SetDefaults_Volume(a) + if a.VolumeSource.HostPath != nil { + SetDefaults_HostPathVolumeSource(a.VolumeSource.HostPath) + } + if a.VolumeSource.Secret != nil { + SetDefaults_SecretVolumeSource(a.VolumeSource.Secret) + } + if a.VolumeSource.ISCSI != nil { + SetDefaults_ISCSIVolumeSource(a.VolumeSource.ISCSI) + } + if a.VolumeSource.RBD != nil { + SetDefaults_RBDVolumeSource(a.VolumeSource.RBD) + } + if a.VolumeSource.DownwardAPI != nil { + SetDefaults_DownwardAPIVolumeSource(a.VolumeSource.DownwardAPI) + for j := range a.VolumeSource.DownwardAPI.Items { + b := &a.VolumeSource.DownwardAPI.Items[j] + if b.FieldRef != nil { + SetDefaults_ObjectFieldSelector(b.FieldRef) + } + } + } + if a.VolumeSource.ConfigMap != nil { + SetDefaults_ConfigMapVolumeSource(a.VolumeSource.ConfigMap) + } + if a.VolumeSource.AzureDisk != nil { + SetDefaults_AzureDiskVolumeSource(a.VolumeSource.AzureDisk) + } + if a.VolumeSource.Projected != nil { + SetDefaults_ProjectedVolumeSource(a.VolumeSource.Projected) + for j := range a.VolumeSource.Projected.Sources { + b := &a.VolumeSource.Projected.Sources[j] + if b.DownwardAPI != nil { + for k := range b.DownwardAPI.Items { + c := &b.DownwardAPI.Items[k] + if c.FieldRef != nil { + SetDefaults_ObjectFieldSelector(c.FieldRef) + } + } + } + } + } + if a.VolumeSource.ScaleIO != nil { + SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) + } + } + for i := range in.Template.Spec.InitContainers { + a := &in.Template.Spec.InitContainers[i] + SetDefaults_Container(a) + for j := range a.Ports { + b := &a.Ports[j] + SetDefaults_ContainerPort(b) + } + for j := range a.Env { + b := &a.Env[j] + if b.ValueFrom != nil { + if b.ValueFrom.FieldRef != nil { + SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) + } + } + } + SetDefaults_ResourceList(&a.Resources.Limits) + SetDefaults_ResourceList(&a.Resources.Requests) + if a.LivenessProbe != nil { + SetDefaults_Probe(a.LivenessProbe) + if a.LivenessProbe.Handler.HTTPGet != nil { + SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) + } + } + if a.ReadinessProbe != nil { + SetDefaults_Probe(a.ReadinessProbe) + if a.ReadinessProbe.Handler.HTTPGet != nil { + SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) + } + } + if a.Lifecycle != nil { + if a.Lifecycle.PostStart != nil { + if a.Lifecycle.PostStart.HTTPGet != nil { + SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) + } + } + if a.Lifecycle.PreStop != nil { + if a.Lifecycle.PreStop.HTTPGet != nil { + SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) + } + } + } + } + for i := range in.Template.Spec.Containers { + a := &in.Template.Spec.Containers[i] + SetDefaults_Container(a) + for j := range a.Ports { + b := &a.Ports[j] + SetDefaults_ContainerPort(b) + } + for j := range a.Env { + b := &a.Env[j] + if b.ValueFrom != nil { + if b.ValueFrom.FieldRef != nil { + SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) + } + } + } + SetDefaults_ResourceList(&a.Resources.Limits) + SetDefaults_ResourceList(&a.Resources.Requests) + if a.LivenessProbe != nil { + SetDefaults_Probe(a.LivenessProbe) + if a.LivenessProbe.Handler.HTTPGet != nil { + SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) + } + } + if a.ReadinessProbe != nil { + SetDefaults_Probe(a.ReadinessProbe) + if a.ReadinessProbe.Handler.HTTPGet != nil { + SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) + } + } + if a.Lifecycle != nil { + if a.Lifecycle.PostStart != nil { + if a.Lifecycle.PostStart.HTTPGet != nil { + SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) + } + } + if a.Lifecycle.PreStop != nil { + if a.Lifecycle.PreStop.HTTPGet != nil { + SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) + } + } + } + } +} + +func SetObjectDefaults_PodTemplateList(in *v1.PodTemplateList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_PodTemplate(a) + } +} + +func SetObjectDefaults_ReplicationController(in *v1.ReplicationController) { + SetDefaults_ReplicationController(in) + if in.Spec.Template != nil { + SetDefaults_PodSpec(&in.Spec.Template.Spec) + for i := range in.Spec.Template.Spec.Volumes { + a := &in.Spec.Template.Spec.Volumes[i] + SetDefaults_Volume(a) + if a.VolumeSource.HostPath != nil { + SetDefaults_HostPathVolumeSource(a.VolumeSource.HostPath) + } + if a.VolumeSource.Secret != nil { + SetDefaults_SecretVolumeSource(a.VolumeSource.Secret) + } + if a.VolumeSource.ISCSI != nil { + SetDefaults_ISCSIVolumeSource(a.VolumeSource.ISCSI) + } + if a.VolumeSource.RBD != nil { + SetDefaults_RBDVolumeSource(a.VolumeSource.RBD) + } + if a.VolumeSource.DownwardAPI != nil { + SetDefaults_DownwardAPIVolumeSource(a.VolumeSource.DownwardAPI) + for j := range a.VolumeSource.DownwardAPI.Items { + b := &a.VolumeSource.DownwardAPI.Items[j] + if b.FieldRef != nil { + SetDefaults_ObjectFieldSelector(b.FieldRef) + } + } + } + if a.VolumeSource.ConfigMap != nil { + SetDefaults_ConfigMapVolumeSource(a.VolumeSource.ConfigMap) + } + if a.VolumeSource.AzureDisk != nil { + SetDefaults_AzureDiskVolumeSource(a.VolumeSource.AzureDisk) + } + if a.VolumeSource.Projected != nil { + SetDefaults_ProjectedVolumeSource(a.VolumeSource.Projected) + for j := range a.VolumeSource.Projected.Sources { + b := &a.VolumeSource.Projected.Sources[j] + if b.DownwardAPI != nil { + for k := range b.DownwardAPI.Items { + c := &b.DownwardAPI.Items[k] + if c.FieldRef != nil { + SetDefaults_ObjectFieldSelector(c.FieldRef) + } + } + } + } + } + if a.VolumeSource.ScaleIO != nil { + SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) + } + } + for i := range in.Spec.Template.Spec.InitContainers { + a := &in.Spec.Template.Spec.InitContainers[i] + SetDefaults_Container(a) + for j := range a.Ports { + b := &a.Ports[j] + SetDefaults_ContainerPort(b) + } + for j := range a.Env { + b := &a.Env[j] + if b.ValueFrom != nil { + if b.ValueFrom.FieldRef != nil { + SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) + } + } + } + SetDefaults_ResourceList(&a.Resources.Limits) + SetDefaults_ResourceList(&a.Resources.Requests) + if a.LivenessProbe != nil { + SetDefaults_Probe(a.LivenessProbe) + if a.LivenessProbe.Handler.HTTPGet != nil { + SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) + } + } + if a.ReadinessProbe != nil { + SetDefaults_Probe(a.ReadinessProbe) + if a.ReadinessProbe.Handler.HTTPGet != nil { + SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) + } + } + if a.Lifecycle != nil { + if a.Lifecycle.PostStart != nil { + if a.Lifecycle.PostStart.HTTPGet != nil { + SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) + } + } + if a.Lifecycle.PreStop != nil { + if a.Lifecycle.PreStop.HTTPGet != nil { + SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) + } + } + } + } + for i := range in.Spec.Template.Spec.Containers { + a := &in.Spec.Template.Spec.Containers[i] + SetDefaults_Container(a) + for j := range a.Ports { + b := &a.Ports[j] + SetDefaults_ContainerPort(b) + } + for j := range a.Env { + b := &a.Env[j] + if b.ValueFrom != nil { + if b.ValueFrom.FieldRef != nil { + SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef) + } + } + } + SetDefaults_ResourceList(&a.Resources.Limits) + SetDefaults_ResourceList(&a.Resources.Requests) + if a.LivenessProbe != nil { + SetDefaults_Probe(a.LivenessProbe) + if a.LivenessProbe.Handler.HTTPGet != nil { + SetDefaults_HTTPGetAction(a.LivenessProbe.Handler.HTTPGet) + } + } + if a.ReadinessProbe != nil { + SetDefaults_Probe(a.ReadinessProbe) + if a.ReadinessProbe.Handler.HTTPGet != nil { + SetDefaults_HTTPGetAction(a.ReadinessProbe.Handler.HTTPGet) + } + } + if a.Lifecycle != nil { + if a.Lifecycle.PostStart != nil { + if a.Lifecycle.PostStart.HTTPGet != nil { + SetDefaults_HTTPGetAction(a.Lifecycle.PostStart.HTTPGet) + } + } + if a.Lifecycle.PreStop != nil { + if a.Lifecycle.PreStop.HTTPGet != nil { + SetDefaults_HTTPGetAction(a.Lifecycle.PreStop.HTTPGet) + } + } + } + } + } +} + +func SetObjectDefaults_ReplicationControllerList(in *v1.ReplicationControllerList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_ReplicationController(a) + } +} + +func SetObjectDefaults_ResourceQuota(in *v1.ResourceQuota) { + SetDefaults_ResourceList(&in.Spec.Hard) + SetDefaults_ResourceList(&in.Status.Hard) + SetDefaults_ResourceList(&in.Status.Used) +} + +func SetObjectDefaults_ResourceQuotaList(in *v1.ResourceQuotaList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_ResourceQuota(a) + } +} + +func SetObjectDefaults_Secret(in *v1.Secret) { + SetDefaults_Secret(in) +} + +func SetObjectDefaults_SecretList(in *v1.SecretList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_Secret(a) + } +} + +func SetObjectDefaults_Service(in *v1.Service) { + SetDefaults_Service(in) +} + +func SetObjectDefaults_ServiceList(in *v1.ServiceList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_Service(a) + } +} diff --git a/pkg/apis/core/validation/BUILD b/pkg/apis/core/validation/BUILD new file mode 100644 index 00000000000..69e50541a98 --- /dev/null +++ b/pkg/apis/core/validation/BUILD @@ -0,0 +1,84 @@ +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "events.go", + "validation.go", + ], + importpath = "k8s.io/kubernetes/pkg/apis/core/validation", + visibility = ["//visibility:public"], + deps = [ + "//pkg/api/legacyscheme:go_default_library", + "//pkg/api/service:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", + "//pkg/apis/core/pods:go_default_library", + "//pkg/apis/core/v1:go_default_library", + "//pkg/apis/core/v1/helper:go_default_library", + "//pkg/capabilities:go_default_library", + "//pkg/features:go_default_library", + "//pkg/fieldpath:go_default_library", + "//pkg/master/ports:go_default_library", + "//pkg/security/apparmor:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/validation:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/validation:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "events_test.go", + "validation_test.go", + ], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/apis/core/validation", + deps = [ + "//pkg/api/legacyscheme:go_default_library", + "//pkg/api/testapi:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", + "//pkg/capabilities:go_default_library", + "//pkg/security/apparmor:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/api/validation/OWNERS b/pkg/apis/core/validation/OWNERS similarity index 100% rename from pkg/api/validation/OWNERS rename to pkg/apis/core/validation/OWNERS diff --git a/pkg/apis/core/validation/doc.go b/pkg/apis/core/validation/doc.go new file mode 100644 index 00000000000..0c1cfaab5a7 --- /dev/null +++ b/pkg/apis/core/validation/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES 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 has functions for validating the correctness of api +// objects and explaining what is wrong with them when they aren't valid. +package validation // import "k8s.io/kubernetes/pkg/apis/core/validation" diff --git a/pkg/apis/core/validation/events.go b/pkg/apis/core/validation/events.go new file mode 100644 index 00000000000..ab265bd7644 --- /dev/null +++ b/pkg/apis/core/validation/events.go @@ -0,0 +1,129 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES 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" + "time" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/apis/core" +) + +const ( + ReportingInstanceLengthLimit = 128 + ActionLengthLimit = 128 + ReasonLengthLimit = 128 + NoteLengthLimit = 1024 +) + +// ValidateEvent makes sure that the event makes sense. +func ValidateEvent(event *core.Event) field.ErrorList { + allErrs := field.ErrorList{} + // Because go + zeroTime := time.Time{} + + // "New" Events need to have EventTime set, so it's validating old object. + if event.EventTime.Time == zeroTime { + // Make sure event.Namespace and the involvedInvolvedObject.Namespace agree + if len(event.InvolvedObject.Namespace) == 0 { + // event.Namespace must also be empty (or "default", for compatibility with old clients) + if event.Namespace != metav1.NamespaceNone && event.Namespace != metav1.NamespaceDefault { + allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match event.namespace")) + } + } else { + // event namespace must match + if event.Namespace != event.InvolvedObject.Namespace { + allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match event.namespace")) + } + } + + } else { + if len(event.InvolvedObject.Namespace) == 0 && event.Namespace != metav1.NamespaceSystem { + allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match event.namespace")) + } + if len(event.ReportingController) == 0 { + allErrs = append(allErrs, field.Required(field.NewPath("reportingController"), "")) + } + for _, msg := range validation.IsQualifiedName(event.ReportingController) { + allErrs = append(allErrs, field.Invalid(field.NewPath("reportingController"), event.ReportingController, msg)) + } + if len(event.ReportingInstance) == 0 { + allErrs = append(allErrs, field.Required(field.NewPath("reportingInstance"), "")) + } + if len(event.ReportingInstance) > ReportingInstanceLengthLimit { + allErrs = append(allErrs, field.Invalid(field.NewPath("repotingIntance"), "", fmt.Sprintf("can have at most %v characters", ReportingInstanceLengthLimit))) + } + if len(event.Action) == 0 { + allErrs = append(allErrs, field.Required(field.NewPath("action"), "")) + } + if len(event.Action) > ActionLengthLimit { + allErrs = append(allErrs, field.Invalid(field.NewPath("action"), "", fmt.Sprintf("can have at most %v characters", ActionLengthLimit))) + } + if len(event.Reason) == 0 { + allErrs = append(allErrs, field.Required(field.NewPath("reason"), "")) + } + if len(event.Reason) > ReasonLengthLimit { + allErrs = append(allErrs, field.Invalid(field.NewPath("reason"), "", fmt.Sprintf("can have at most %v characters", ReasonLengthLimit))) + } + if len(event.Message) > NoteLengthLimit { + allErrs = append(allErrs, field.Invalid(field.NewPath("message"), "", fmt.Sprintf("can have at most %v characters", NoteLengthLimit))) + } + } + + // For kinds we recognize, make sure InvolvedObject.Namespace is set for namespaced kinds + if namespaced, err := isNamespacedKind(event.InvolvedObject.Kind, event.InvolvedObject.APIVersion); err == nil { + if namespaced && len(event.InvolvedObject.Namespace) == 0 { + allErrs = append(allErrs, field.Required(field.NewPath("involvedObject", "namespace"), fmt.Sprintf("required for kind %s", event.InvolvedObject.Kind))) + } + if !namespaced && len(event.InvolvedObject.Namespace) > 0 { + allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, fmt.Sprintf("not allowed for kind %s", event.InvolvedObject.Kind))) + } + } + + for _, msg := range validation.IsDNS1123Subdomain(event.Namespace) { + allErrs = append(allErrs, field.Invalid(field.NewPath("namespace"), event.Namespace, msg)) + } + return allErrs +} + +// Check whether the kind in groupVersion is scoped at the root of the api hierarchy +func isNamespacedKind(kind, groupVersion string) (bool, error) { + gv, err := schema.ParseGroupVersion(groupVersion) + if err != nil { + return false, err + } + g, err := legacyscheme.Registry.Group(gv.Group) + if err != nil { + return false, err + } + + restMapping, err := g.RESTMapper.RESTMapping(schema.GroupKind{Group: gv.Group, Kind: kind}, gv.Version) + if err != nil { + return false, err + } + scopeName := restMapping.Scope.Name() + if scopeName == meta.RESTScopeNameNamespace { + return true, nil + } + return false, nil +} diff --git a/pkg/apis/core/validation/events_test.go b/pkg/apis/core/validation/events_test.go new file mode 100644 index 00000000000..1db7463f971 --- /dev/null +++ b/pkg/apis/core/validation/events_test.go @@ -0,0 +1,392 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES 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" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/pkg/apis/core" +) + +func TestValidateEvent(t *testing.T) { + table := []struct { + *core.Event + valid bool + }{ + { + &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test1", + Namespace: "foo", + }, + InvolvedObject: core.ObjectReference{ + Namespace: "bar", + Kind: "Pod", + }, + }, + false, + }, { + &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test2", + Namespace: "aoeu-_-aoeu", + }, + InvolvedObject: core.ObjectReference{ + Namespace: "aoeu-_-aoeu", + Kind: "Pod", + }, + }, + false, + }, { + &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test3", + Namespace: metav1.NamespaceDefault, + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "v1", + Kind: "Node", + }, + }, + true, + }, { + &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test4", + Namespace: metav1.NamespaceDefault, + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "v1", + Kind: "Namespace", + }, + }, + true, + }, { + &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test5", + Namespace: metav1.NamespaceDefault, + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "extensions/v1beta1", + Kind: "NoKind", + Namespace: metav1.NamespaceDefault, + }, + }, + true, + }, { + &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test6", + Namespace: metav1.NamespaceDefault, + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "extensions/v1beta1", + Kind: "Job", + Namespace: "foo", + }, + }, + false, + }, { + &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test7", + Namespace: metav1.NamespaceDefault, + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "extensions/v1beta1", + Kind: "Job", + Namespace: metav1.NamespaceDefault, + }, + }, + true, + }, { + &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test8", + Namespace: metav1.NamespaceDefault, + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "other/v1beta1", + Kind: "Job", + Namespace: "foo", + }, + }, + false, + }, { + &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test9", + Namespace: "foo", + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "other/v1beta1", + Kind: "Job", + Namespace: "foo", + }, + }, + true, + }, { + &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test10", + Namespace: metav1.NamespaceDefault, + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "extensions", + Kind: "Job", + Namespace: "foo", + }, + }, + false, + }, { + &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test11", + Namespace: "foo", + }, + InvolvedObject: core.ObjectReference{ + // must register in v1beta1 to be true + APIVersion: "extensions/v1beta1", + Kind: "Job", + Namespace: "foo", + }, + }, + true, + }, + { + &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test12", + Namespace: "foo", + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "other/v1beta1", + Kind: "FooBar", + Namespace: "bar", + }, + }, + false, + }, + { + &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test13", + Namespace: "", + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "other/v1beta1", + Kind: "FooBar", + Namespace: "bar", + }, + }, + false, + }, + { + &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test14", + Namespace: "foo", + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "other/v1beta1", + Kind: "FooBar", + Namespace: "", + }, + }, + false, + }, + } + + for _, item := range table { + if e, a := item.valid, len(ValidateEvent(item.Event)) == 0; e != a { + t.Errorf("%v: expected %v, got %v: %v", item.Event.Name, e, a, ValidateEvent(item.Event)) + } + } +} + +func TestValidateNewEvent(t *testing.T) { + someTime := metav1.MicroTime{Time: time.Unix(1505828956, 0)} + table := []struct { + *core.Event + valid bool + msg string + }{ + { + Event: &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: metav1.NamespaceDefault, + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "v1", + Kind: "Node", + }, + EventTime: someTime, + }, + valid: false, + msg: "Old Event with EventTime should trigger new validation and fail", + }, + { + Event: &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: metav1.NamespaceSystem, + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "v1", + Kind: "Node", + }, + EventTime: someTime, + ReportingController: "k8s.io/my-controller", + ReportingInstance: "node-xyz", + Action: "Do", + Reason: "Because", + }, + valid: true, + msg: "Valid new Event", + }, + { + Event: &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: metav1.NamespaceSystem, + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "v1", + Kind: "Node", + }, + EventTime: someTime, + ReportingController: "my-contr@ller", + ReportingInstance: "node-xyz", + Action: "Do", + Reason: "Because", + }, + valid: false, + msg: "not qualified reportingController", + }, + { + Event: &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: metav1.NamespaceSystem, + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "v1", + Kind: "Node", + }, + EventTime: someTime, + ReportingController: "k8s.io/my-controller", + ReportingInstance: "node-xyzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", + Action: "Do", + Reason: "Because", + }, + valid: false, + msg: "too long reporting instance", + }, + { + Event: &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: metav1.NamespaceSystem, + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "v1", + Kind: "Node", + }, + EventTime: someTime, + ReportingController: "k8s.io/my-controller", + ReportingInstance: "node-xyz", + Action: "Do", + }, + valid: false, + msg: "missing reason", + }, + { + Event: &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: metav1.NamespaceSystem, + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "v1", + Kind: "Node", + }, + EventTime: someTime, + ReportingController: "k8s.io/my-controller", + ReportingInstance: "node-xyz", + Reason: "Because", + }, + valid: false, + msg: "missing action", + }, + { + Event: &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "v1", + Kind: "Node", + }, + EventTime: someTime, + ReportingController: "k8s.io/my-controller", + ReportingInstance: "node-xyz", + Reason: "Because", + }, + valid: false, + msg: "missing namespace", + }, + { + Event: &core.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + InvolvedObject: core.ObjectReference{ + APIVersion: "v1", + Kind: "Node", + }, + EventTime: someTime, + ReportingController: "k8s.io/my-controller", + ReportingInstance: "node-xyz", + Action: "Do", + Reason: "Because", + Message: `zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz`, + }, + valid: false, + msg: "too long message", + }, + } + + for _, item := range table { + if e, a := item.valid, len(ValidateEvent(item.Event)) == 0; e != a { + t.Errorf("%v: expected %v, got %v: %v", item.msg, e, a, ValidateEvent(item.Event)) + } + } +} diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go new file mode 100644 index 00000000000..eda651fd575 --- /dev/null +++ b/pkg/apis/core/validation/validation.go @@ -0,0 +1,4924 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES 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 ( + "encoding/json" + "fmt" + "math" + "net" + "path" + "path/filepath" + "reflect" + "regexp" + "strings" + + "github.com/golang/glog" + + "k8s.io/api/core/v1" + apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/resource" + apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/diff" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/apimachinery/pkg/util/validation/field" + utilfeature "k8s.io/apiserver/pkg/util/feature" + apiservice "k8s.io/kubernetes/pkg/api/service" + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" + 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" + "k8s.io/kubernetes/pkg/master/ports" + "k8s.io/kubernetes/pkg/security/apparmor" +) + +// TODO: delete this global variable when we enable the validation of common +// fields by default. +var RepairMalformedUpdates bool = apimachineryvalidation.RepairMalformedUpdates + +const isNegativeErrorMsg string = apimachineryvalidation.IsNegativeErrorMsg +const isInvalidQuotaResource string = `must be a standard resource for quota` +const fieldImmutableErrorMsg string = apimachineryvalidation.FieldImmutableErrorMsg +const isNotIntegerErrorMsg string = `must be an integer` +const isNotPositiveErrorMsg string = `must be greater than zero` + +var pdPartitionErrorMsg string = validation.InclusiveRangeError(1, 255) +var fileModeErrorMsg string = "must be a number between 0 and 0777 (octal), both inclusive" + +// BannedOwners is a black list of object that are not allowed to be owners. +var BannedOwners = apimachineryvalidation.BannedOwners + +var iscsiInitiatorIqnRegex = regexp.MustCompile(`iqn\.\d{4}-\d{2}\.([[:alnum:]-.]+)(:[^,;*&$|\s]+)$`) +var iscsiInitiatorEuiRegex = regexp.MustCompile(`^eui.[[:alnum:]]{16}$`) +var iscsiInitiatorNaaRegex = regexp.MustCompile(`^naa.[[:alnum:]]{32}$`) + +// ValidateHasLabel requires that metav1.ObjectMeta has a Label with key and expectedValue +func ValidateHasLabel(meta metav1.ObjectMeta, fldPath *field.Path, key, expectedValue string) field.ErrorList { + allErrs := field.ErrorList{} + actualValue, found := meta.Labels[key] + if !found { + allErrs = append(allErrs, field.Required(fldPath.Child("labels").Key(key), + fmt.Sprintf("must be '%s'", expectedValue))) + return allErrs + } + if actualValue != expectedValue { + allErrs = append(allErrs, field.Invalid(fldPath.Child("labels").Key(key), meta.Labels, + fmt.Sprintf("must be '%s'", expectedValue))) + } + return allErrs +} + +// ValidateAnnotations validates that a set of annotations are correctly defined. +func ValidateAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { + return apimachineryvalidation.ValidateAnnotations(annotations, fldPath) +} + +func ValidateDNS1123Label(value string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for _, msg := range validation.IsDNS1123Label(value) { + allErrs = append(allErrs, field.Invalid(fldPath, value, msg)) + } + return allErrs +} + +// ValidateDNS1123Subdomain validates that a name is a proper DNS subdomain. +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)) + } + return allErrs +} + +func ValidatePodSpecificAnnotations(annotations map[string]string, spec *core.PodSpec, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if value, isMirror := annotations[core.MirrorPodAnnotationKey]; isMirror { + if len(spec.NodeName) == 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Key(core.MirrorPodAnnotationKey), value, "must set spec.nodeName if mirror pod annotation is set")) + } + } + + if annotations[core.TolerationsAnnotationKey] != "" { + allErrs = append(allErrs, ValidateTolerationsInPodAnnotations(annotations, fldPath)...) + } + + allErrs = append(allErrs, ValidateSeccompPodAnnotations(annotations, fldPath)...) + allErrs = append(allErrs, ValidateAppArmorPodAnnotations(annotations, spec, fldPath)...) + + sysctls, err := helper.SysctlsFromPodAnnotation(annotations[core.SysctlsPodAnnotationKey]) + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Key(core.SysctlsPodAnnotationKey), annotations[core.SysctlsPodAnnotationKey], err.Error())) + } else { + allErrs = append(allErrs, validateSysctls(sysctls, fldPath.Key(core.SysctlsPodAnnotationKey))...) + } + unsafeSysctls, err := helper.SysctlsFromPodAnnotation(annotations[core.UnsafeSysctlsPodAnnotationKey]) + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Key(core.UnsafeSysctlsPodAnnotationKey), annotations[core.UnsafeSysctlsPodAnnotationKey], err.Error())) + } else { + allErrs = append(allErrs, validateSysctls(unsafeSysctls, fldPath.Key(core.UnsafeSysctlsPodAnnotationKey))...) + } + inBoth := sysctlIntersection(sysctls, unsafeSysctls) + if len(inBoth) > 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Key(core.UnsafeSysctlsPodAnnotationKey), strings.Join(inBoth, ", "), "can not be safe and unsafe")) + } + + return allErrs +} + +// ValidateTolerationsInPodAnnotations tests that the serialized tolerations in Pod.Annotations has valid data +func ValidateTolerationsInPodAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + tolerations, err := helper.GetTolerationsFromPodAnnotations(annotations) + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath, core.TolerationsAnnotationKey, err.Error())) + return allErrs + } + + if len(tolerations) > 0 { + allErrs = append(allErrs, ValidateTolerations(tolerations, fldPath.Child(core.TolerationsAnnotationKey))...) + } + + return allErrs +} + +func ValidatePodSpecificAnnotationUpdates(newPod, oldPod *core.Pod, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + newAnnotations := newPod.Annotations + oldAnnotations := oldPod.Annotations + for k, oldVal := range oldAnnotations { + if newVal, exists := newAnnotations[k]; exists && newVal == oldVal { + continue // No change. + } + if strings.HasPrefix(k, apparmor.ContainerAnnotationKeyPrefix) { + allErrs = append(allErrs, field.Forbidden(fldPath.Key(k), "may not remove or update AppArmor annotations")) + } + if k == core.MirrorPodAnnotationKey { + allErrs = append(allErrs, field.Forbidden(fldPath.Key(k), "may not remove or update mirror pod annotation")) + } + } + // Check for additions + for k := range newAnnotations { + if _, ok := oldAnnotations[k]; ok { + continue // No change. + } + if strings.HasPrefix(k, apparmor.ContainerAnnotationKeyPrefix) { + allErrs = append(allErrs, field.Forbidden(fldPath.Key(k), "may not add AppArmor annotations")) + } + if k == core.MirrorPodAnnotationKey { + allErrs = append(allErrs, field.Forbidden(fldPath.Key(k), "may not add mirror pod annotation")) + } + } + allErrs = append(allErrs, ValidatePodSpecificAnnotations(newAnnotations, &newPod.Spec, fldPath)...) + return allErrs +} + +func ValidateEndpointsSpecificAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + return allErrs +} + +func ValidateOwnerReferences(ownerReferences []metav1.OwnerReference, fldPath *field.Path) field.ErrorList { + return apimachineryvalidation.ValidateOwnerReferences(ownerReferences, fldPath) +} + +// ValidateNameFunc validates that the provided name is valid for a given resource type. +// Not all resources have the same validation rules for names. Prefix is true +// if the name will have a value appended to it. If the name is not valid, +// this returns a list of descriptions of individual characteristics of the +// value that were not valid. Otherwise this returns an empty list or nil. +type ValidateNameFunc apimachineryvalidation.ValidateNameFunc + +// ValidatePodName can be used to check whether the given pod name is valid. +// Prefix indicates this name will be used as part of generation, in which case +// trailing dashes are allowed. +var ValidatePodName = NameIsDNSSubdomain + +// ValidateReplicationControllerName can be used to check whether the given replication +// controller name is valid. +// Prefix indicates this name will be used as part of generation, in which case +// trailing dashes are allowed. +var ValidateReplicationControllerName = NameIsDNSSubdomain + +// ValidateServiceName can be used to check whether the given service name is valid. +// Prefix indicates this name will be used as part of generation, in which case +// trailing dashes are allowed. +var ValidateServiceName = NameIsDNS1035Label + +// ValidateNodeName can be used to check whether the given node name is valid. +// Prefix indicates this name will be used as part of generation, in which case +// trailing dashes are allowed. +var ValidateNodeName = NameIsDNSSubdomain + +// ValidateNamespaceName can be used to check whether the given namespace name is valid. +// Prefix indicates this name will be used as part of generation, in which case +// trailing dashes are allowed. +var ValidateNamespaceName = apimachineryvalidation.ValidateNamespaceName + +// ValidateLimitRangeName can be used to check whether the given limit range name is valid. +// Prefix indicates this name will be used as part of generation, in which case +// trailing dashes are allowed. +var ValidateLimitRangeName = NameIsDNSSubdomain + +// ValidateResourceQuotaName can be used to check whether the given +// resource quota name is valid. +// Prefix indicates this name will be used as part of generation, in which case +// trailing dashes are allowed. +var ValidateResourceQuotaName = NameIsDNSSubdomain + +// ValidateSecretName can be used to check whether the given secret name is valid. +// Prefix indicates this name will be used as part of generation, in which case +// trailing dashes are allowed. +var ValidateSecretName = NameIsDNSSubdomain + +// ValidateServiceAccountName can be used to check whether the given service account name is valid. +// Prefix indicates this name will be used as part of generation, in which case +// trailing dashes are allowed. +var ValidateServiceAccountName = apimachineryvalidation.ValidateServiceAccountName + +// ValidateEndpointsName can be used to check whether the given endpoints name is valid. +// Prefix indicates this name will be used as part of generation, in which case +// trailing dashes are allowed. +var ValidateEndpointsName = NameIsDNSSubdomain + +// ValidateClusterName can be used to check whether the given cluster name is valid. +var ValidateClusterName = apimachineryvalidation.ValidateClusterName + +// ValidateClassName can be used to check whether the given class name is valid. +// It is defined here to avoid import cycle between pkg/apis/storage/validation +// (where it should be) and this file. +var ValidateClassName = NameIsDNSSubdomain + +// ValidatePiorityClassName can be used to check whether the given priority +// class name is valid. +var ValidatePriorityClassName = NameIsDNSSubdomain + +// TODO update all references to these functions to point to the apimachineryvalidation ones +// NameIsDNSSubdomain is a ValidateNameFunc for names that must be a DNS subdomain. +func NameIsDNSSubdomain(name string, prefix bool) []string { + return apimachineryvalidation.NameIsDNSSubdomain(name, prefix) +} + +// NameIsDNSLabel is a ValidateNameFunc for names that must be a DNS 1123 label. +func NameIsDNSLabel(name string, prefix bool) []string { + return apimachineryvalidation.NameIsDNSLabel(name, prefix) +} + +// NameIsDNS1035Label is a ValidateNameFunc for names that must be a DNS 952 label. +func NameIsDNS1035Label(name string, prefix bool) []string { + return apimachineryvalidation.NameIsDNS1035Label(name, prefix) +} + +// Validates that given value is not negative. +func ValidateNonnegativeField(value int64, fldPath *field.Path) field.ErrorList { + return apimachineryvalidation.ValidateNonnegativeField(value, fldPath) +} + +// Validates that a Quantity is not negative +func ValidateNonnegativeQuantity(value resource.Quantity, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if value.Cmp(resource.Quantity{}) < 0 { + allErrs = append(allErrs, field.Invalid(fldPath, value.String(), isNegativeErrorMsg)) + } + return allErrs +} + +// Validates that a Quantity is positive +func ValidatePositiveQuantityValue(value resource.Quantity, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if value.Cmp(resource.Quantity{}) <= 0 { + allErrs = append(allErrs, field.Invalid(fldPath, value.String(), isNotPositiveErrorMsg)) + } + return allErrs +} + +func ValidateImmutableField(newVal, oldVal interface{}, fldPath *field.Path) field.ErrorList { + return apimachineryvalidation.ValidateImmutableField(newVal, oldVal, fldPath) +} + +func ValidateImmutableAnnotation(newVal string, oldVal string, annotation string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if oldVal != newVal { + allErrs = append(allErrs, field.Invalid(fldPath.Child("annotations", annotation), newVal, fieldImmutableErrorMsg)) + } + return allErrs +} + +// ValidateObjectMeta validates an object's metadata on creation. It expects that name generation has already +// been performed. +// It doesn't return an error for rootscoped resources with namespace, because namespace should already be cleared before. +// TODO: Remove calls to this method scattered in validations of specific resources, e.g., ValidatePodUpdate. +func ValidateObjectMeta(meta *metav1.ObjectMeta, requiresNamespace bool, nameFn ValidateNameFunc, fldPath *field.Path) field.ErrorList { + allErrs := apimachineryvalidation.ValidateObjectMeta(meta, requiresNamespace, apimachineryvalidation.ValidateNameFunc(nameFn), fldPath) + // run additional checks for the finalizer name + for i := range meta.Finalizers { + allErrs = append(allErrs, validateKubeFinalizerName(string(meta.Finalizers[i]), fldPath.Child("finalizers").Index(i))...) + } + return allErrs +} + +// ValidateObjectMetaUpdate validates an object's metadata when updated +func ValidateObjectMetaUpdate(newMeta, oldMeta *metav1.ObjectMeta, fldPath *field.Path) field.ErrorList { + allErrs := apimachineryvalidation.ValidateObjectMetaUpdate(newMeta, oldMeta, fldPath) + // run additional checks for the finalizer name + for i := range newMeta.Finalizers { + allErrs = append(allErrs, validateKubeFinalizerName(string(newMeta.Finalizers[i]), fldPath.Child("finalizers").Index(i))...) + } + + return allErrs +} + +func ValidateNoNewFinalizers(newFinalizers []string, oldFinalizers []string, fldPath *field.Path) field.ErrorList { + return apimachineryvalidation.ValidateNoNewFinalizers(newFinalizers, oldFinalizers, fldPath) +} + +func ValidateVolumes(volumes []core.Volume, fldPath *field.Path) (map[string]core.VolumeSource, field.ErrorList) { + allErrs := field.ErrorList{} + + allNames := sets.String{} + vols := make(map[string]core.VolumeSource) + for i, vol := range volumes { + idxPath := fldPath.Index(i) + namePath := idxPath.Child("name") + el := validateVolumeSource(&vol.VolumeSource, idxPath, vol.Name) + if len(vol.Name) == 0 { + el = append(el, field.Required(namePath, "")) + } else { + el = append(el, ValidateDNS1123Label(vol.Name, namePath)...) + } + if allNames.Has(vol.Name) { + el = append(el, field.Duplicate(namePath, vol.Name)) + } + if len(el) == 0 { + allNames.Insert(vol.Name) + vols[vol.Name] = vol.VolumeSource + } else { + allErrs = append(allErrs, el...) + } + + } + return vols, allErrs +} + +func IsMatchedVolume(name string, volumes map[string]core.VolumeSource) bool { + if _, ok := volumes[name]; ok { + return true + } else { + return false + } +} + +func isMatchedDevice(name string, volumes map[string]core.VolumeSource) (bool, bool) { + if source, ok := volumes[name]; ok { + if source.PersistentVolumeClaim != nil { + return true, true + } else { + return true, false + } + } else { + return false, false + } +} + +func mountNameAlreadyExists(name string, devices map[string]string) bool { + if _, ok := devices[name]; ok { + return true + } else { + return false + } +} + +func mountPathAlreadyExists(mountPath string, devices map[string]string) bool { + for _, devPath := range devices { + if mountPath == devPath { + return true + } + } + + return false +} + +func deviceNameAlreadyExists(name string, mounts map[string]string) bool { + if _, ok := mounts[name]; ok { + return true + } else { + return false + } +} + +func devicePathAlreadyExists(devicePath string, mounts map[string]string) bool { + for _, mountPath := range mounts { + if mountPath == devicePath { + return true + } + } + + return false +} + +func validateVolumeSource(source *core.VolumeSource, fldPath *field.Path, volName string) field.ErrorList { + numVolumes := 0 + allErrs := field.ErrorList{} + if source.EmptyDir != nil { + numVolumes++ + if !utilfeature.DefaultFeatureGate.Enabled(features.LocalStorageCapacityIsolation) { + if source.EmptyDir.SizeLimit != nil && source.EmptyDir.SizeLimit.Cmp(resource.Quantity{}) != 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("emptyDir").Child("sizeLimit"), "SizeLimit field disabled by feature-gate for EmptyDir volumes")) + } + } else { + if source.EmptyDir.SizeLimit != nil && source.EmptyDir.SizeLimit.Cmp(resource.Quantity{}) < 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("emptyDir").Child("sizeLimit"), "SizeLimit field must be a valid resource quantity")) + } + } + if !utilfeature.DefaultFeatureGate.Enabled(features.HugePages) && source.EmptyDir.Medium == core.StorageMediumHugePages { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("emptyDir").Child("medium"), "HugePages medium is disabled by feature-gate for EmptyDir volumes")) + } + } + if source.HostPath != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("hostPath"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateHostPathVolumeSource(source.HostPath, fldPath.Child("hostPath"))...) + } + } + if source.GitRepo != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("gitRepo"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateGitRepoVolumeSource(source.GitRepo, fldPath.Child("gitRepo"))...) + } + } + if source.GCEPersistentDisk != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("gcePersistentDisk"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateGCEPersistentDiskVolumeSource(source.GCEPersistentDisk, fldPath.Child("persistentDisk"))...) + } + } + if source.AWSElasticBlockStore != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("awsElasticBlockStore"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateAWSElasticBlockStoreVolumeSource(source.AWSElasticBlockStore, fldPath.Child("awsElasticBlockStore"))...) + } + } + if source.Secret != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("secret"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateSecretVolumeSource(source.Secret, fldPath.Child("secret"))...) + } + } + if source.NFS != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("nfs"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateNFSVolumeSource(source.NFS, fldPath.Child("nfs"))...) + } + } + if source.ISCSI != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("iscsi"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateISCSIVolumeSource(source.ISCSI, fldPath.Child("iscsi"))...) + } + if source.ISCSI.InitiatorName != nil && len(volName+":"+source.ISCSI.TargetPortal) > 64 { + tooLongErr := "Total length of : must be under 64 characters if iscsi.initiatorName is specified." + allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), volName, tooLongErr)) + } + } + if source.Glusterfs != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("glusterfs"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateGlusterfsVolumeSource(source.Glusterfs, fldPath.Child("glusterfs"))...) + } + } + if source.Flocker != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("flocker"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateFlockerVolumeSource(source.Flocker, fldPath.Child("flocker"))...) + } + } + if source.PersistentVolumeClaim != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("persistentVolumeClaim"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validatePersistentClaimVolumeSource(source.PersistentVolumeClaim, fldPath.Child("persistentVolumeClaim"))...) + } + } + if source.RBD != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("rbd"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateRBDVolumeSource(source.RBD, fldPath.Child("rbd"))...) + } + } + if source.Cinder != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("cinder"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateCinderVolumeSource(source.Cinder, fldPath.Child("cinder"))...) + } + } + if source.CephFS != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("cephFS"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateCephFSVolumeSource(source.CephFS, fldPath.Child("cephfs"))...) + } + } + if source.Quobyte != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("quobyte"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateQuobyteVolumeSource(source.Quobyte, fldPath.Child("quobyte"))...) + } + } + if source.DownwardAPI != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("downwarAPI"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateDownwardAPIVolumeSource(source.DownwardAPI, fldPath.Child("downwardAPI"))...) + } + } + if source.FC != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("fc"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateFCVolumeSource(source.FC, fldPath.Child("fc"))...) + } + } + if source.FlexVolume != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("flexVolume"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateFlexVolumeSource(source.FlexVolume, fldPath.Child("flexVolume"))...) + } + } + if source.ConfigMap != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("configMap"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateConfigMapVolumeSource(source.ConfigMap, fldPath.Child("configMap"))...) + } + } + + if source.AzureFile != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("azureFile"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateAzureFile(source.AzureFile, fldPath.Child("azureFile"))...) + } + } + + if source.VsphereVolume != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("vsphereVolume"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateVsphereVolumeSource(source.VsphereVolume, fldPath.Child("vsphereVolume"))...) + } + } + if source.PhotonPersistentDisk != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("photonPersistentDisk"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validatePhotonPersistentDiskVolumeSource(source.PhotonPersistentDisk, fldPath.Child("photonPersistentDisk"))...) + } + } + if source.PortworxVolume != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("portworxVolume"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validatePortworxVolumeSource(source.PortworxVolume, fldPath.Child("portworxVolume"))...) + } + } + if source.AzureDisk != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("azureDisk"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateAzureDisk(source.AzureDisk, fldPath.Child("azureDisk"))...) + } + } + if source.StorageOS != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("storageos"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateStorageOSVolumeSource(source.StorageOS, fldPath.Child("storageos"))...) + } + } + if source.Projected != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("projected"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateProjectedVolumeSource(source.Projected, fldPath.Child("projected"))...) + } + } + if source.ScaleIO != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("scaleIO"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateScaleIOVolumeSource(source.ScaleIO, fldPath.Child("scaleIO"))...) + } + } + + if numVolumes == 0 { + allErrs = append(allErrs, field.Required(fldPath, "must specify a volume type")) + } + + return allErrs +} + +func validateHostPathVolumeSource(hostPath *core.HostPathVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(hostPath.Path) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("path"), "")) + return allErrs + } + + allErrs = append(allErrs, validatePathNoBacksteps(hostPath.Path, fldPath.Child("path"))...) + allErrs = append(allErrs, validateHostPathType(hostPath.Type, fldPath.Child("type"))...) + return allErrs +} + +func validateGitRepoVolumeSource(gitRepo *core.GitRepoVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(gitRepo.Repository) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("repository"), "")) + } + + pathErrs := validateLocalDescendingPath(gitRepo.Directory, fldPath.Child("directory")) + allErrs = append(allErrs, pathErrs...) + return allErrs +} + +func validateISCSIVolumeSource(iscsi *core.ISCSIVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(iscsi.TargetPortal) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("targetPortal"), "")) + } + if len(iscsi.IQN) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("iqn"), "")) + } else { + if !strings.HasPrefix(iscsi.IQN, "iqn") && !strings.HasPrefix(iscsi.IQN, "eui") && !strings.HasPrefix(iscsi.IQN, "naa") { + allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format starting with iqn, eui, or naa")) + } else if strings.HasPrefix(iscsi.IQN, "iqn") && !iscsiInitiatorIqnRegex.MatchString(iscsi.IQN) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format")) + } else if strings.HasPrefix(iscsi.IQN, "eui") && !iscsiInitiatorEuiRegex.MatchString(iscsi.IQN) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format")) + } else if strings.HasPrefix(iscsi.IQN, "naa") && !iscsiInitiatorNaaRegex.MatchString(iscsi.IQN) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format")) + } + } + if iscsi.Lun < 0 || iscsi.Lun > 255 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("lun"), iscsi.Lun, validation.InclusiveRangeError(0, 255))) + } + if (iscsi.DiscoveryCHAPAuth || iscsi.SessionCHAPAuth) && iscsi.SecretRef == nil { + allErrs = append(allErrs, field.Required(fldPath.Child("secretRef"), "")) + } + if iscsi.InitiatorName != nil { + initiator := *iscsi.InitiatorName + if !strings.HasPrefix(initiator, "iqn") && !strings.HasPrefix(initiator, "eui") && !strings.HasPrefix(initiator, "naa") { + allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format starting with iqn, eui, or naa")) + } + if strings.HasPrefix(initiator, "iqn") && !iscsiInitiatorIqnRegex.MatchString(initiator) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format")) + } else if strings.HasPrefix(initiator, "eui") && !iscsiInitiatorEuiRegex.MatchString(initiator) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format")) + } else if strings.HasPrefix(initiator, "naa") && !iscsiInitiatorNaaRegex.MatchString(initiator) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format")) + } + } + return allErrs +} + +func validateISCSIPersistentVolumeSource(iscsi *core.ISCSIPersistentVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(iscsi.TargetPortal) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("targetPortal"), "")) + } + if len(iscsi.IQN) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("iqn"), "")) + } else { + if !strings.HasPrefix(iscsi.IQN, "iqn") && !strings.HasPrefix(iscsi.IQN, "eui") && !strings.HasPrefix(iscsi.IQN, "naa") { + allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format")) + } else if strings.HasPrefix(iscsi.IQN, "iqn") && !iscsiInitiatorIqnRegex.MatchString(iscsi.IQN) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format")) + } else if strings.HasPrefix(iscsi.IQN, "eui") && !iscsiInitiatorEuiRegex.MatchString(iscsi.IQN) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format")) + } else if strings.HasPrefix(iscsi.IQN, "naa") && !iscsiInitiatorNaaRegex.MatchString(iscsi.IQN) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format")) + } + } + if iscsi.Lun < 0 || iscsi.Lun > 255 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("lun"), iscsi.Lun, validation.InclusiveRangeError(0, 255))) + } + if (iscsi.DiscoveryCHAPAuth || iscsi.SessionCHAPAuth) && iscsi.SecretRef == nil { + allErrs = append(allErrs, field.Required(fldPath.Child("secretRef"), "")) + } + if iscsi.SecretRef != nil { + if len(iscsi.SecretRef.Name) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("secretRef", "name"), "")) + } + } + if iscsi.InitiatorName != nil { + initiator := *iscsi.InitiatorName + if !strings.HasPrefix(initiator, "iqn") && !strings.HasPrefix(initiator, "eui") && !strings.HasPrefix(initiator, "naa") { + allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format")) + } + if strings.HasPrefix(initiator, "iqn") && !iscsiInitiatorIqnRegex.MatchString(initiator) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format")) + } else if strings.HasPrefix(initiator, "eui") && !iscsiInitiatorEuiRegex.MatchString(initiator) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format")) + } else if strings.HasPrefix(initiator, "naa") && !iscsiInitiatorNaaRegex.MatchString(initiator) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format")) + } + } + return allErrs +} + +func validateFCVolumeSource(fc *core.FCVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(fc.TargetWWNs) < 1 && len(fc.WWIDs) < 1 { + allErrs = append(allErrs, field.Required(fldPath.Child("targetWWNs"), "must specify either targetWWNs or wwids, but not both")) + } + + if len(fc.TargetWWNs) != 0 && len(fc.WWIDs) != 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("targetWWNs"), fc.TargetWWNs, "targetWWNs and wwids can not be specified simultaneously")) + } + + if len(fc.TargetWWNs) != 0 { + if fc.Lun == nil { + allErrs = append(allErrs, field.Required(fldPath.Child("lun"), "lun is required if targetWWNs is specified")) + } else { + if *fc.Lun < 0 || *fc.Lun > 255 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("lun"), fc.Lun, validation.InclusiveRangeError(0, 255))) + } + } + } + return allErrs +} + +func validateGCEPersistentDiskVolumeSource(pd *core.GCEPersistentDiskVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(pd.PDName) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("pdName"), "")) + } + if pd.Partition < 0 || pd.Partition > 255 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("partition"), pd.Partition, pdPartitionErrorMsg)) + } + return allErrs +} + +func validateAWSElasticBlockStoreVolumeSource(PD *core.AWSElasticBlockStoreVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(PD.VolumeID) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("volumeID"), "")) + } + if PD.Partition < 0 || PD.Partition > 255 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("partition"), PD.Partition, pdPartitionErrorMsg)) + } + return allErrs +} + +func validateSecretVolumeSource(secretSource *core.SecretVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(secretSource.SecretName) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("secretName"), "")) + } + + secretMode := secretSource.DefaultMode + if secretMode != nil && (*secretMode > 0777 || *secretMode < 0) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultMode"), *secretMode, fileModeErrorMsg)) + } + + itemsPath := fldPath.Child("items") + for i, kp := range secretSource.Items { + itemPath := itemsPath.Index(i) + allErrs = append(allErrs, validateKeyToPath(&kp, itemPath)...) + } + return allErrs +} + +func validateConfigMapVolumeSource(configMapSource *core.ConfigMapVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(configMapSource.Name) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) + } + + configMapMode := configMapSource.DefaultMode + if configMapMode != nil && (*configMapMode > 0777 || *configMapMode < 0) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultMode"), *configMapMode, fileModeErrorMsg)) + } + + itemsPath := fldPath.Child("items") + for i, kp := range configMapSource.Items { + itemPath := itemsPath.Index(i) + allErrs = append(allErrs, validateKeyToPath(&kp, itemPath)...) + } + return allErrs +} + +func validateKeyToPath(kp *core.KeyToPath, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(kp.Key) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("key"), "")) + } + if len(kp.Path) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("path"), "")) + } + allErrs = append(allErrs, validateLocalNonReservedPath(kp.Path, fldPath.Child("path"))...) + if kp.Mode != nil && (*kp.Mode > 0777 || *kp.Mode < 0) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("mode"), *kp.Mode, fileModeErrorMsg)) + } + + return allErrs +} + +func validatePersistentClaimVolumeSource(claim *core.PersistentVolumeClaimVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(claim.ClaimName) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("claimName"), "")) + } + return allErrs +} + +func validateNFSVolumeSource(nfs *core.NFSVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(nfs.Server) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("server"), "")) + } + if len(nfs.Path) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("path"), "")) + } + if !path.IsAbs(nfs.Path) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("path"), nfs.Path, "must be an absolute path")) + } + return allErrs +} + +func validateQuobyteVolumeSource(quobyte *core.QuobyteVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(quobyte.Registry) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("registry"), "must be a host:port pair or multiple pairs separated by commas")) + } else { + for _, hostPortPair := range strings.Split(quobyte.Registry, ",") { + if _, _, err := net.SplitHostPort(hostPortPair); err != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("registry"), quobyte.Registry, "must be a host:port pair or multiple pairs separated by commas")) + } + } + } + + if len(quobyte.Volume) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("volume"), "")) + } + return allErrs +} + +func validateGlusterfsVolumeSource(glusterfs *core.GlusterfsVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(glusterfs.EndpointsName) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("endpoints"), "")) + } + if len(glusterfs.Path) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("path"), "")) + } + return allErrs +} + +func validateFlockerVolumeSource(flocker *core.FlockerVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(flocker.DatasetName) == 0 && len(flocker.DatasetUUID) == 0 { + //TODO: consider adding a RequiredOneOf() error for this and similar cases + allErrs = append(allErrs, field.Required(fldPath, "one of datasetName and datasetUUID is required")) + } + if len(flocker.DatasetName) != 0 && len(flocker.DatasetUUID) != 0 { + allErrs = append(allErrs, field.Invalid(fldPath, "resource", "datasetName and datasetUUID can not be specified simultaneously")) + } + if strings.Contains(flocker.DatasetName, "/") { + allErrs = append(allErrs, field.Invalid(fldPath.Child("datasetName"), flocker.DatasetName, "must not contain '/'")) + } + return allErrs +} + +var validVolumeDownwardAPIFieldPathExpressions = sets.NewString( + "metadata.name", + "metadata.namespace", + "metadata.labels", + "metadata.annotations", + "metadata.uid") + +func validateDownwardAPIVolumeFile(file *core.DownwardAPIVolumeFile, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if len(file.Path) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("path"), "")) + } + allErrs = append(allErrs, validateLocalNonReservedPath(file.Path, fldPath.Child("path"))...) + if file.FieldRef != nil { + allErrs = append(allErrs, validateObjectFieldSelector(file.FieldRef, &validVolumeDownwardAPIFieldPathExpressions, fldPath.Child("fieldRef"))...) + if file.ResourceFieldRef != nil { + allErrs = append(allErrs, field.Invalid(fldPath, "resource", "fieldRef and resourceFieldRef can not be specified simultaneously")) + } + } else if file.ResourceFieldRef != nil { + allErrs = append(allErrs, validateContainerResourceFieldSelector(file.ResourceFieldRef, &validContainerResourceFieldPathExpressions, fldPath.Child("resourceFieldRef"), true)...) + } else { + allErrs = append(allErrs, field.Required(fldPath, "one of fieldRef and resourceFieldRef is required")) + } + if file.Mode != nil && (*file.Mode > 0777 || *file.Mode < 0) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("mode"), *file.Mode, fileModeErrorMsg)) + } + + return allErrs +} + +func validateDownwardAPIVolumeSource(downwardAPIVolume *core.DownwardAPIVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + downwardAPIMode := downwardAPIVolume.DefaultMode + if downwardAPIMode != nil && (*downwardAPIMode > 0777 || *downwardAPIMode < 0) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultMode"), *downwardAPIMode, fileModeErrorMsg)) + } + + for _, file := range downwardAPIVolume.Items { + allErrs = append(allErrs, validateDownwardAPIVolumeFile(&file, fldPath)...) + } + return allErrs +} + +func validateProjectionSources(projection *core.ProjectedVolumeSource, projectionMode *int32, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + allPaths := sets.String{} + + for _, source := range projection.Sources { + numSources := 0 + if source.Secret != nil { + if numSources > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("secret"), "may not specify more than 1 volume type")) + } else { + numSources++ + if len(source.Secret.Name) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) + } + itemsPath := fldPath.Child("items") + for i, kp := range source.Secret.Items { + itemPath := itemsPath.Index(i) + allErrs = append(allErrs, validateKeyToPath(&kp, itemPath)...) + if len(kp.Path) > 0 { + curPath := kp.Path + if !allPaths.Has(curPath) { + allPaths.Insert(curPath) + } else { + allErrs = append(allErrs, field.Invalid(fldPath, source.Secret.Name, "conflicting duplicate paths")) + } + } + } + } + } + if source.ConfigMap != nil { + if numSources > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("configMap"), "may not specify more than 1 volume type")) + } else { + numSources++ + if len(source.ConfigMap.Name) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) + } + itemsPath := fldPath.Child("items") + for i, kp := range source.ConfigMap.Items { + itemPath := itemsPath.Index(i) + allErrs = append(allErrs, validateKeyToPath(&kp, itemPath)...) + if len(kp.Path) > 0 { + curPath := kp.Path + if !allPaths.Has(curPath) { + allPaths.Insert(curPath) + } else { + allErrs = append(allErrs, field.Invalid(fldPath, source.ConfigMap.Name, "conflicting duplicate paths")) + } + + } + } + } + } + if source.DownwardAPI != nil { + if numSources > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("downwardAPI"), "may not specify more than 1 volume type")) + } else { + numSources++ + for _, file := range source.DownwardAPI.Items { + allErrs = append(allErrs, validateDownwardAPIVolumeFile(&file, fldPath.Child("downwardAPI"))...) + if len(file.Path) > 0 { + curPath := file.Path + if !allPaths.Has(curPath) { + allPaths.Insert(curPath) + } else { + allErrs = append(allErrs, field.Invalid(fldPath, curPath, "conflicting duplicate paths")) + } + + } + } + } + } + } + return allErrs +} + +func validateProjectedVolumeSource(projection *core.ProjectedVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + projectionMode := projection.DefaultMode + if projectionMode != nil && (*projectionMode > 0777 || *projectionMode < 0) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultMode"), *projectionMode, fileModeErrorMsg)) + } + + allErrs = append(allErrs, validateProjectionSources(projection, projectionMode, fldPath)...) + return allErrs +} + +var supportedHostPathTypes = sets.NewString( + string(core.HostPathUnset), + string(core.HostPathDirectoryOrCreate), + string(core.HostPathDirectory), + string(core.HostPathFileOrCreate), + string(core.HostPathFile), + string(core.HostPathSocket), + string(core.HostPathCharDev), + string(core.HostPathBlockDev)) + +func validateHostPathType(hostPathType *core.HostPathType, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if hostPathType != nil && !supportedHostPathTypes.Has(string(*hostPathType)) { + allErrs = append(allErrs, field.NotSupported(fldPath, hostPathType, supportedHostPathTypes.List())) + } + + return allErrs +} + +// This validate will make sure targetPath: +// 1. is not abs path +// 2. does not have any element which is ".." +func validateLocalDescendingPath(targetPath string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if path.IsAbs(targetPath) { + allErrs = append(allErrs, field.Invalid(fldPath, targetPath, "must be a relative path")) + } + + allErrs = append(allErrs, validatePathNoBacksteps(targetPath, fldPath)...) + + return allErrs +} + +// validatePathNoBacksteps makes sure the targetPath does not have any `..` path elements when split +// +// This assumes the OS of the apiserver and the nodes are the same. The same check should be done +// on the node to ensure there are no backsteps. +func validatePathNoBacksteps(targetPath string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + parts := strings.Split(filepath.ToSlash(targetPath), "/") + for _, item := range parts { + if item == ".." { + allErrs = append(allErrs, field.Invalid(fldPath, targetPath, "must not contain '..'")) + break // even for `../../..`, one error is sufficient to make the point + } + } + return allErrs +} + +// validateMountPropagation verifies that MountPropagation field is valid and +// allowed for given container. +func validateMountPropagation(mountPropagation *core.MountPropagationMode, container *core.Container, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if mountPropagation == nil { + return allErrs + } + if !utilfeature.DefaultFeatureGate.Enabled(features.MountPropagation) { + allErrs = append(allErrs, field.Forbidden(fldPath, "mount propagation is disabled by feature-gate")) + return allErrs + } + + supportedMountPropagations := sets.NewString(string(core.MountPropagationBidirectional), string(core.MountPropagationHostToContainer)) + if !supportedMountPropagations.Has(string(*mountPropagation)) { + allErrs = append(allErrs, field.NotSupported(fldPath, *mountPropagation, supportedMountPropagations.List())) + } + + if container == nil { + // The container is not available yet, e.g. during validation of + // PodPreset. Stop validation now, Pod validation will refuse final + // Pods with Bidirectional propagation in non-privileged containers. + return allErrs + } + + privileged := container.SecurityContext != nil && container.SecurityContext.Privileged != nil && *container.SecurityContext.Privileged + if *mountPropagation == core.MountPropagationBidirectional && !privileged { + allErrs = append(allErrs, field.Forbidden(fldPath, "Bidirectional mount propagation is available only to privileged containers")) + } + return allErrs +} + +// This validate will make sure targetPath: +// 1. is not abs path +// 2. does not contain any '..' elements +// 3. does not start with '..' +func validateLocalNonReservedPath(targetPath string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, validateLocalDescendingPath(targetPath, fldPath)...) + // Don't report this error if the check for .. elements already caught it. + if strings.HasPrefix(targetPath, "..") && !strings.HasPrefix(targetPath, "../") { + allErrs = append(allErrs, field.Invalid(fldPath, targetPath, "must not start with '..'")) + } + return allErrs +} + +func validateRBDVolumeSource(rbd *core.RBDVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(rbd.CephMonitors) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("monitors"), "")) + } + if len(rbd.RBDImage) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("image"), "")) + } + return allErrs +} + +func validateRBDPersistentVolumeSource(rbd *core.RBDPersistentVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(rbd.CephMonitors) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("monitors"), "")) + } + if len(rbd.RBDImage) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("image"), "")) + } + return allErrs +} + +func validateCinderVolumeSource(cd *core.CinderVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(cd.VolumeID) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("volumeID"), "")) + } + return allErrs +} + +func validateCephFSVolumeSource(cephfs *core.CephFSVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(cephfs.Monitors) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("monitors"), "")) + } + return allErrs +} + +func validateCephFSPersistentVolumeSource(cephfs *core.CephFSPersistentVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(cephfs.Monitors) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("monitors"), "")) + } + return allErrs +} + +func validateFlexVolumeSource(fv *core.FlexVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(fv.Driver) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("driver"), "")) + } + + // Make sure user-specified options don't use kubernetes namespaces + for k := range fv.Options { + namespace := k + if parts := strings.SplitN(k, "/", 2); len(parts) == 2 { + namespace = parts[0] + } + normalized := "." + strings.ToLower(namespace) + if strings.HasSuffix(normalized, ".kubernetes.io") || strings.HasSuffix(normalized, ".k8s.io") { + allErrs = append(allErrs, field.Invalid(fldPath.Child("options").Key(k), k, "kubernetes.io and k8s.io namespaces are reserved")) + } + } + + return allErrs +} + +func validateFlexPersistentVolumeSource(fv *core.FlexPersistentVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(fv.Driver) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("driver"), "")) + } + + // Make sure user-specified options don't use kubernetes namespaces + for k := range fv.Options { + namespace := k + if parts := strings.SplitN(k, "/", 2); len(parts) == 2 { + namespace = parts[0] + } + normalized := "." + strings.ToLower(namespace) + if strings.HasSuffix(normalized, ".kubernetes.io") || strings.HasSuffix(normalized, ".k8s.io") { + allErrs = append(allErrs, field.Invalid(fldPath.Child("options").Key(k), k, "kubernetes.io and k8s.io namespaces are reserved")) + } + } + + return allErrs +} + +func validateAzureFile(azure *core.AzureFileVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if azure.SecretName == "" { + allErrs = append(allErrs, field.Required(fldPath.Child("secretName"), "")) + } + if azure.ShareName == "" { + allErrs = append(allErrs, field.Required(fldPath.Child("shareName"), "")) + } + return allErrs +} + +func validateAzureFilePV(azure *core.AzureFilePersistentVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if azure.SecretName == "" { + allErrs = append(allErrs, field.Required(fldPath.Child("secretName"), "")) + } + if azure.ShareName == "" { + allErrs = append(allErrs, field.Required(fldPath.Child("shareName"), "")) + } + if azure.SecretNamespace != nil { + if len(*azure.SecretNamespace) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("secretNamespace"), "")) + } + } + return allErrs +} + +func validateAzureDisk(azure *core.AzureDiskVolumeSource, fldPath *field.Path) field.ErrorList { + var supportedCachingModes = sets.NewString(string(core.AzureDataDiskCachingNone), string(core.AzureDataDiskCachingReadOnly), string(core.AzureDataDiskCachingReadWrite)) + var supportedDiskKinds = sets.NewString(string(core.AzureSharedBlobDisk), string(core.AzureDedicatedBlobDisk), string(core.AzureManagedDisk)) + + diskUriSupportedManaged := []string{"/subscriptions/{sub-id}/resourcegroups/{group-name}/providers/microsoft.compute/disks/{disk-id}"} + diskUriSupportedblob := []string{"https://{account-name}.blob.core.windows.net/{container-name}/{disk-name}.vhd"} + + allErrs := field.ErrorList{} + if azure.DiskName == "" { + allErrs = append(allErrs, field.Required(fldPath.Child("diskName"), "")) + } + + if azure.DataDiskURI == "" { + allErrs = append(allErrs, field.Required(fldPath.Child("diskURI"), "")) + } + + if azure.CachingMode != nil && !supportedCachingModes.Has(string(*azure.CachingMode)) { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("cachingMode"), *azure.CachingMode, supportedCachingModes.List())) + } + + if azure.Kind != nil && !supportedDiskKinds.Has(string(*azure.Kind)) { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("kind"), *azure.Kind, supportedDiskKinds.List())) + } + + // validate that DiskUri is the correct format + if azure.Kind != nil && *azure.Kind == core.AzureManagedDisk && strings.Index(azure.DataDiskURI, "/subscriptions/") != 0 { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("diskURI"), azure.DataDiskURI, diskUriSupportedManaged)) + } + + if azure.Kind != nil && *azure.Kind != core.AzureManagedDisk && strings.Index(azure.DataDiskURI, "https://") != 0 { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("diskURI"), azure.DataDiskURI, diskUriSupportedblob)) + } + + return allErrs +} + +func validateVsphereVolumeSource(cd *core.VsphereVirtualDiskVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(cd.VolumePath) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("volumePath"), "")) + } + return allErrs +} + +func validatePhotonPersistentDiskVolumeSource(cd *core.PhotonPersistentDiskVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(cd.PdID) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("pdID"), "")) + } + return allErrs +} + +func validatePortworxVolumeSource(pwx *core.PortworxVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(pwx.VolumeID) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("volumeID"), "")) + } + return allErrs +} + +func validateScaleIOVolumeSource(sio *core.ScaleIOVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if sio.Gateway == "" { + allErrs = append(allErrs, field.Required(fldPath.Child("gateway"), "")) + } + if sio.System == "" { + allErrs = append(allErrs, field.Required(fldPath.Child("system"), "")) + } + if sio.VolumeName == "" { + allErrs = append(allErrs, field.Required(fldPath.Child("volumeName"), "")) + } + return allErrs +} + +func validateScaleIOPersistentVolumeSource(sio *core.ScaleIOPersistentVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if sio.Gateway == "" { + allErrs = append(allErrs, field.Required(fldPath.Child("gateway"), "")) + } + if sio.System == "" { + allErrs = append(allErrs, field.Required(fldPath.Child("system"), "")) + } + if sio.VolumeName == "" { + allErrs = append(allErrs, field.Required(fldPath.Child("volumeName"), "")) + } + return allErrs +} + +func validateLocalVolumeSource(ls *core.LocalVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if ls.Path == "" { + allErrs = append(allErrs, field.Required(fldPath.Child("path"), "")) + return allErrs + } + + allErrs = append(allErrs, validatePathNoBacksteps(ls.Path, fldPath.Child("path"))...) + return allErrs +} + +func validateStorageOSVolumeSource(storageos *core.StorageOSVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(storageos.VolumeName) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("volumeName"), "")) + } else { + allErrs = append(allErrs, ValidateDNS1123Label(storageos.VolumeName, fldPath.Child("volumeName"))...) + } + if len(storageos.VolumeNamespace) > 0 { + allErrs = append(allErrs, ValidateDNS1123Label(storageos.VolumeNamespace, fldPath.Child("volumeNamespace"))...) + } + if storageos.SecretRef != nil { + if len(storageos.SecretRef.Name) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("secretRef", "name"), "")) + } + } + return allErrs +} + +func validateStorageOSPersistentVolumeSource(storageos *core.StorageOSPersistentVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(storageos.VolumeName) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("volumeName"), "")) + } else { + allErrs = append(allErrs, ValidateDNS1123Label(storageos.VolumeName, fldPath.Child("volumeName"))...) + } + if len(storageos.VolumeNamespace) > 0 { + allErrs = append(allErrs, ValidateDNS1123Label(storageos.VolumeNamespace, fldPath.Child("volumeNamespace"))...) + } + if storageos.SecretRef != nil { + if len(storageos.SecretRef.Name) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("secretRef", "name"), "")) + } + if len(storageos.SecretRef.Namespace) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("secretRef", "namespace"), "")) + } + } + return allErrs +} + +func validateCSIPersistentVolumeSource(csi *core.CSIPersistentVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if !utilfeature.DefaultFeatureGate.Enabled(features.CSIPersistentVolume) { + allErrs = append(allErrs, field.Forbidden(fldPath, "CSIPersistentVolume disabled by feature-gate")) + } + + if len(csi.Driver) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("driver"), "")) + } + + if len(csi.VolumeHandle) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("volumeHandle"), "")) + } + + return allErrs +} + +// ValidatePersistentVolumeName checks that a name is appropriate for a +// PersistentVolumeName object. +var ValidatePersistentVolumeName = NameIsDNSSubdomain + +var supportedAccessModes = sets.NewString(string(core.ReadWriteOnce), string(core.ReadOnlyMany), string(core.ReadWriteMany)) + +var supportedReclaimPolicy = sets.NewString(string(core.PersistentVolumeReclaimDelete), string(core.PersistentVolumeReclaimRecycle), string(core.PersistentVolumeReclaimRetain)) + +var supportedVolumeModes = sets.NewString(string(core.PersistentVolumeBlock), string(core.PersistentVolumeFilesystem)) + +func ValidatePersistentVolume(pv *core.PersistentVolume) field.ErrorList { + metaPath := field.NewPath("metadata") + allErrs := ValidateObjectMeta(&pv.ObjectMeta, false, ValidatePersistentVolumeName, metaPath) + + specPath := field.NewPath("spec") + if len(pv.Spec.AccessModes) == 0 { + allErrs = append(allErrs, field.Required(specPath.Child("accessModes"), "")) + } + for _, mode := range pv.Spec.AccessModes { + if !supportedAccessModes.Has(string(mode)) { + allErrs = append(allErrs, field.NotSupported(specPath.Child("accessModes"), mode, supportedAccessModes.List())) + } + } + + if len(pv.Spec.Capacity) == 0 { + allErrs = append(allErrs, field.Required(specPath.Child("capacity"), "")) + } + + if _, ok := pv.Spec.Capacity[core.ResourceStorage]; !ok || len(pv.Spec.Capacity) > 1 { + allErrs = append(allErrs, field.NotSupported(specPath.Child("capacity"), pv.Spec.Capacity, []string{string(core.ResourceStorage)})) + } + capPath := specPath.Child("capacity") + for r, qty := range pv.Spec.Capacity { + allErrs = append(allErrs, validateBasicResource(qty, capPath.Key(string(r)))...) + allErrs = append(allErrs, ValidatePositiveQuantityValue(qty, capPath.Key(string(r)))...) + } + if len(string(pv.Spec.PersistentVolumeReclaimPolicy)) > 0 { + if !supportedReclaimPolicy.Has(string(pv.Spec.PersistentVolumeReclaimPolicy)) { + allErrs = append(allErrs, field.NotSupported(specPath.Child("persistentVolumeReclaimPolicy"), pv.Spec.PersistentVolumeReclaimPolicy, supportedReclaimPolicy.List())) + } + } + + nodeAffinitySpecified, errs := validateStorageNodeAffinityAnnotation(pv.ObjectMeta.Annotations, metaPath.Child("annotations")) + allErrs = append(allErrs, errs...) + + numVolumes := 0 + if pv.Spec.HostPath != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(specPath.Child("hostPath"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateHostPathVolumeSource(pv.Spec.HostPath, specPath.Child("hostPath"))...) + } + } + if pv.Spec.GCEPersistentDisk != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(specPath.Child("gcePersistentDisk"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateGCEPersistentDiskVolumeSource(pv.Spec.GCEPersistentDisk, specPath.Child("persistentDisk"))...) + } + } + if pv.Spec.AWSElasticBlockStore != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(specPath.Child("awsElasticBlockStore"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateAWSElasticBlockStoreVolumeSource(pv.Spec.AWSElasticBlockStore, specPath.Child("awsElasticBlockStore"))...) + } + } + if pv.Spec.Glusterfs != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(specPath.Child("glusterfs"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateGlusterfsVolumeSource(pv.Spec.Glusterfs, specPath.Child("glusterfs"))...) + } + } + if pv.Spec.Flocker != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(specPath.Child("flocker"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateFlockerVolumeSource(pv.Spec.Flocker, specPath.Child("flocker"))...) + } + } + if pv.Spec.NFS != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(specPath.Child("nfs"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateNFSVolumeSource(pv.Spec.NFS, specPath.Child("nfs"))...) + } + } + if pv.Spec.RBD != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(specPath.Child("rbd"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateRBDPersistentVolumeSource(pv.Spec.RBD, specPath.Child("rbd"))...) + } + } + if pv.Spec.Quobyte != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(specPath.Child("quobyte"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateQuobyteVolumeSource(pv.Spec.Quobyte, specPath.Child("quobyte"))...) + } + } + if pv.Spec.CephFS != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(specPath.Child("cephFS"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateCephFSPersistentVolumeSource(pv.Spec.CephFS, specPath.Child("cephfs"))...) + } + } + if pv.Spec.ISCSI != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(specPath.Child("iscsi"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateISCSIPersistentVolumeSource(pv.Spec.ISCSI, specPath.Child("iscsi"))...) + } + if pv.Spec.ISCSI.InitiatorName != nil && len(pv.ObjectMeta.Name+":"+pv.Spec.ISCSI.TargetPortal) > 64 { + tooLongErr := "Total length of : must be under 64 characters if iscsi.initiatorName is specified." + allErrs = append(allErrs, field.Invalid(metaPath.Child("name"), pv.ObjectMeta.Name, tooLongErr)) + } + } + if pv.Spec.Cinder != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(specPath.Child("cinder"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateCinderVolumeSource(pv.Spec.Cinder, specPath.Child("cinder"))...) + } + } + if pv.Spec.FC != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(specPath.Child("fc"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateFCVolumeSource(pv.Spec.FC, specPath.Child("fc"))...) + } + } + if pv.Spec.FlexVolume != nil { + numVolumes++ + allErrs = append(allErrs, validateFlexPersistentVolumeSource(pv.Spec.FlexVolume, specPath.Child("flexVolume"))...) + } + if pv.Spec.AzureFile != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(specPath.Child("azureFile"), "may not specify more than 1 volume type")) + + } else { + numVolumes++ + allErrs = append(allErrs, validateAzureFilePV(pv.Spec.AzureFile, specPath.Child("azureFile"))...) + } + } + + if pv.Spec.VsphereVolume != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(specPath.Child("vsphereVolume"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateVsphereVolumeSource(pv.Spec.VsphereVolume, specPath.Child("vsphereVolume"))...) + } + } + if pv.Spec.PhotonPersistentDisk != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(specPath.Child("photonPersistentDisk"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validatePhotonPersistentDiskVolumeSource(pv.Spec.PhotonPersistentDisk, specPath.Child("photonPersistentDisk"))...) + } + } + if pv.Spec.PortworxVolume != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(specPath.Child("portworxVolume"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validatePortworxVolumeSource(pv.Spec.PortworxVolume, specPath.Child("portworxVolume"))...) + } + } + if pv.Spec.AzureDisk != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(specPath.Child("azureDisk"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateAzureDisk(pv.Spec.AzureDisk, specPath.Child("azureDisk"))...) + } + } + if pv.Spec.ScaleIO != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(specPath.Child("scaleIO"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateScaleIOPersistentVolumeSource(pv.Spec.ScaleIO, specPath.Child("scaleIO"))...) + } + } + if pv.Spec.Local != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(specPath.Child("local"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + if !utilfeature.DefaultFeatureGate.Enabled(features.PersistentLocalVolumes) { + allErrs = append(allErrs, field.Forbidden(specPath.Child("local"), "Local volumes are disabled by feature-gate")) + } + allErrs = append(allErrs, validateLocalVolumeSource(pv.Spec.Local, specPath.Child("local"))...) + + // NodeAffinity is required + if !nodeAffinitySpecified { + allErrs = append(allErrs, field.Required(metaPath.Child("annotations"), "Local volume requires node affinity")) + } + } + } + if pv.Spec.StorageOS != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(specPath.Child("storageos"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateStorageOSPersistentVolumeSource(pv.Spec.StorageOS, specPath.Child("storageos"))...) + } + } + + if pv.Spec.CSI != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(specPath.Child("csi"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateCSIPersistentVolumeSource(pv.Spec.CSI, specPath.Child("csi"))...) + } + } + + if numVolumes == 0 { + allErrs = append(allErrs, field.Required(specPath, "must specify a volume type")) + } + + // do not allow hostPath mounts of '/' to have a 'recycle' reclaim policy + if pv.Spec.HostPath != nil && path.Clean(pv.Spec.HostPath.Path) == "/" && pv.Spec.PersistentVolumeReclaimPolicy == core.PersistentVolumeReclaimRecycle { + allErrs = append(allErrs, field.Forbidden(specPath.Child("persistentVolumeReclaimPolicy"), "may not be 'recycle' for a hostPath mount of '/'")) + } + + if len(pv.Spec.StorageClassName) > 0 { + for _, msg := range ValidateClassName(pv.Spec.StorageClassName, false) { + allErrs = append(allErrs, field.Invalid(specPath.Child("storageClassName"), pv.Spec.StorageClassName, msg)) + } + } + if pv.Spec.VolumeMode != nil && !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + allErrs = append(allErrs, field.Forbidden(specPath.Child("volumeMode"), "PersistentVolume volumeMode is disabled by feature-gate")) + } else if pv.Spec.VolumeMode != nil && !supportedVolumeModes.Has(string(*pv.Spec.VolumeMode)) { + allErrs = append(allErrs, field.NotSupported(specPath.Child("volumeMode"), *pv.Spec.VolumeMode, supportedVolumeModes.List())) + } + return allErrs +} + +// ValidatePersistentVolumeUpdate tests to see if the update is legal for an end user to make. +// newPv is updated with fields that cannot be changed. +func ValidatePersistentVolumeUpdate(newPv, oldPv *core.PersistentVolume) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = ValidatePersistentVolume(newPv) + + // PersistentVolumeSource should be immutable after creation. + if !apiequality.Semantic.DeepEqual(newPv.Spec.PersistentVolumeSource, oldPv.Spec.PersistentVolumeSource) { + allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "persistentvolumesource"), "is immutable after creation")) + } + + newPv.Status = oldPv.Status + + if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + allErrs = append(allErrs, ValidateImmutableField(newPv.Spec.VolumeMode, oldPv.Spec.VolumeMode, field.NewPath("volumeMode"))...) + } + + return allErrs +} + +// ValidatePersistentVolumeStatusUpdate tests to see if the status update is legal for an end user to make. +// newPv is updated with fields that cannot be changed. +func ValidatePersistentVolumeStatusUpdate(newPv, oldPv *core.PersistentVolume) field.ErrorList { + allErrs := ValidateObjectMetaUpdate(&newPv.ObjectMeta, &oldPv.ObjectMeta, field.NewPath("metadata")) + if len(newPv.ResourceVersion) == 0 { + allErrs = append(allErrs, field.Required(field.NewPath("resourceVersion"), "")) + } + newPv.Spec = oldPv.Spec + return allErrs +} + +// ValidatePersistentVolumeClaim validates a PersistentVolumeClaim +func ValidatePersistentVolumeClaim(pvc *core.PersistentVolumeClaim) field.ErrorList { + allErrs := ValidateObjectMeta(&pvc.ObjectMeta, true, ValidatePersistentVolumeName, field.NewPath("metadata")) + allErrs = append(allErrs, ValidatePersistentVolumeClaimSpec(&pvc.Spec, field.NewPath("spec"))...) + return allErrs +} + +// ValidatePersistentVolumeClaimSpec validates a PersistentVolumeClaimSpec +func ValidatePersistentVolumeClaimSpec(spec *core.PersistentVolumeClaimSpec, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(spec.AccessModes) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("accessModes"), "at least 1 access mode is required")) + } + if spec.Selector != nil { + allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...) + } + for _, mode := range spec.AccessModes { + if mode != core.ReadWriteOnce && mode != core.ReadOnlyMany && mode != core.ReadWriteMany { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("accessModes"), mode, supportedAccessModes.List())) + } + } + storageValue, ok := spec.Resources.Requests[core.ResourceStorage] + if !ok { + allErrs = append(allErrs, field.Required(fldPath.Child("resources").Key(string(core.ResourceStorage)), "")) + } else { + allErrs = append(allErrs, ValidateResourceQuantityValue(string(core.ResourceStorage), storageValue, fldPath.Child("resources").Key(string(core.ResourceStorage)))...) + allErrs = append(allErrs, ValidatePositiveQuantityValue(storageValue, fldPath.Child("resources").Key(string(core.ResourceStorage)))...) + } + + if spec.StorageClassName != nil && len(*spec.StorageClassName) > 0 { + for _, msg := range ValidateClassName(*spec.StorageClassName, false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("storageClassName"), *spec.StorageClassName, msg)) + } + } + if spec.VolumeMode != nil && !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("volumeMode"), "PersistentVolumeClaim volumeMode is disabled by feature-gate")) + } else if spec.VolumeMode != nil && !supportedVolumeModes.Has(string(*spec.VolumeMode)) { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("volumeMode"), *spec.VolumeMode, supportedVolumeModes.List())) + } + return allErrs +} + +// ValidatePersistentVolumeClaimUpdate validates an update to a PersistentVolumeClaim +func ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc *core.PersistentVolumeClaim) field.ErrorList { + allErrs := ValidateObjectMetaUpdate(&newPvc.ObjectMeta, &oldPvc.ObjectMeta, field.NewPath("metadata")) + allErrs = append(allErrs, ValidatePersistentVolumeClaim(newPvc)...) + newPvcClone := newPvc.DeepCopy() + oldPvcClone := oldPvc.DeepCopy() + + // PVController needs to update PVC.Spec w/ VolumeName. + // Claims are immutable in order to enforce quota, range limits, etc. without gaming the system. + if len(oldPvc.Spec.VolumeName) == 0 { + // volumeName changes are allowed once. + oldPvcClone.Spec.VolumeName = newPvcClone.Spec.VolumeName + } + + if utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) { + // lets make sure storage values are same. + if newPvc.Status.Phase == core.ClaimBound && newPvcClone.Spec.Resources.Requests != nil { + newPvcClone.Spec.Resources.Requests["storage"] = oldPvc.Spec.Resources.Requests["storage"] + } + + oldSize := oldPvc.Spec.Resources.Requests["storage"] + newSize := newPvc.Spec.Resources.Requests["storage"] + + if !apiequality.Semantic.DeepEqual(newPvcClone.Spec, oldPvcClone.Spec) { + allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "is immutable after creation except resources.requests for bound claims")) + } + if newSize.Cmp(oldSize) < 0 { + allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "resources", "requests", "storage"), "field can not be less than previous value")) + } + + } else { + // changes to Spec are not allowed, but updates to label/and some annotations are OK. + // no-op updates pass validation. + if !apiequality.Semantic.DeepEqual(newPvcClone.Spec, oldPvcClone.Spec) { + allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "field is immutable after creation")) + } + } + + // storageclass annotation should be immutable after creation + // TODO: remove Beta when no longer needed + allErrs = append(allErrs, ValidateImmutableAnnotation(newPvc.ObjectMeta.Annotations[v1.BetaStorageClassAnnotation], oldPvc.ObjectMeta.Annotations[v1.BetaStorageClassAnnotation], v1.BetaStorageClassAnnotation, field.NewPath("metadata"))...) + + if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + allErrs = append(allErrs, ValidateImmutableField(newPvc.Spec.VolumeMode, oldPvc.Spec.VolumeMode, field.NewPath("volumeMode"))...) + } + return allErrs +} + +// ValidatePersistentVolumeClaimStatusUpdate validates an update to status of a PersistentVolumeClaim +func ValidatePersistentVolumeClaimStatusUpdate(newPvc, oldPvc *core.PersistentVolumeClaim) field.ErrorList { + allErrs := ValidateObjectMetaUpdate(&newPvc.ObjectMeta, &oldPvc.ObjectMeta, field.NewPath("metadata")) + if len(newPvc.ResourceVersion) == 0 { + allErrs = append(allErrs, field.Required(field.NewPath("resourceVersion"), "")) + } + if len(newPvc.Spec.AccessModes) == 0 { + allErrs = append(allErrs, field.Required(field.NewPath("Spec", "accessModes"), "")) + } + if !utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) && len(newPvc.Status.Conditions) > 0 { + conditionPath := field.NewPath("status", "conditions") + allErrs = append(allErrs, field.Forbidden(conditionPath, "invalid field")) + } + capPath := field.NewPath("status", "capacity") + for r, qty := range newPvc.Status.Capacity { + allErrs = append(allErrs, validateBasicResource(qty, capPath.Key(string(r)))...) + } + newPvc.Spec = oldPvc.Spec + return allErrs +} + +var supportedPortProtocols = sets.NewString(string(core.ProtocolTCP), string(core.ProtocolUDP)) + +func validateContainerPorts(ports []core.ContainerPort, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + allNames := sets.String{} + for i, port := range ports { + idxPath := fldPath.Index(i) + if len(port.Name) > 0 { + if msgs := validation.IsValidPortName(port.Name); len(msgs) != 0 { + for i = range msgs { + allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), port.Name, msgs[i])) + } + } else if allNames.Has(port.Name) { + allErrs = append(allErrs, field.Duplicate(idxPath.Child("name"), port.Name)) + } else { + allNames.Insert(port.Name) + } + } + if port.ContainerPort == 0 { + allErrs = append(allErrs, field.Required(idxPath.Child("containerPort"), "")) + } else { + for _, msg := range validation.IsValidPortNum(int(port.ContainerPort)) { + allErrs = append(allErrs, field.Invalid(idxPath.Child("containerPort"), port.ContainerPort, msg)) + } + } + if port.HostPort != 0 { + for _, msg := range validation.IsValidPortNum(int(port.HostPort)) { + allErrs = append(allErrs, field.Invalid(idxPath.Child("hostPort"), port.HostPort, msg)) + } + } + if len(port.Protocol) == 0 { + allErrs = append(allErrs, field.Required(idxPath.Child("protocol"), "")) + } else if !supportedPortProtocols.Has(string(port.Protocol)) { + allErrs = append(allErrs, field.NotSupported(idxPath.Child("protocol"), port.Protocol, supportedPortProtocols.List())) + } + } + return allErrs +} + +// ValidateEnv validates env vars +func ValidateEnv(vars []core.EnvVar, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + for i, ev := range vars { + idxPath := fldPath.Index(i) + if len(ev.Name) == 0 { + allErrs = append(allErrs, field.Required(idxPath.Child("name"), "")) + } else { + for _, msg := range validation.IsEnvVarName(ev.Name) { + allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), ev.Name, msg)) + } + } + allErrs = append(allErrs, validateEnvVarValueFrom(ev, idxPath.Child("valueFrom"))...) + } + return allErrs +} + +var validEnvDownwardAPIFieldPathExpressions = sets.NewString( + "metadata.name", + "metadata.namespace", + "metadata.uid", + "spec.nodeName", + "spec.serviceAccountName", + "status.hostIP", + "status.podIP") +var validContainerResourceFieldPathExpressions = sets.NewString("limits.cpu", "limits.memory", "limits.ephemeral-storage", "requests.cpu", "requests.memory", "requests.ephemeral-storage") + +func validateEnvVarValueFrom(ev core.EnvVar, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if ev.ValueFrom == nil { + return allErrs + } + + numSources := 0 + + if ev.ValueFrom.FieldRef != nil { + numSources++ + allErrs = append(allErrs, validateObjectFieldSelector(ev.ValueFrom.FieldRef, &validEnvDownwardAPIFieldPathExpressions, fldPath.Child("fieldRef"))...) + } + if ev.ValueFrom.ResourceFieldRef != nil { + numSources++ + allErrs = append(allErrs, validateContainerResourceFieldSelector(ev.ValueFrom.ResourceFieldRef, &validContainerResourceFieldPathExpressions, fldPath.Child("resourceFieldRef"), false)...) + } + if ev.ValueFrom.ConfigMapKeyRef != nil { + numSources++ + allErrs = append(allErrs, validateConfigMapKeySelector(ev.ValueFrom.ConfigMapKeyRef, fldPath.Child("configMapKeyRef"))...) + } + if ev.ValueFrom.SecretKeyRef != nil { + numSources++ + allErrs = append(allErrs, validateSecretKeySelector(ev.ValueFrom.SecretKeyRef, fldPath.Child("secretKeyRef"))...) + } + + if numSources == 0 { + allErrs = append(allErrs, field.Invalid(fldPath, "", "must specify one of: `fieldRef`, `resourceFieldRef`, `configMapKeyRef` or `secretKeyRef`")) + } else if len(ev.Value) != 0 { + if numSources != 0 { + allErrs = append(allErrs, field.Invalid(fldPath, "", "may not be specified when `value` is not empty")) + } + } else if numSources > 1 { + allErrs = append(allErrs, field.Invalid(fldPath, "", "may not have more than one field specified at a time")) + } + + return allErrs +} + +func validateObjectFieldSelector(fs *core.ObjectFieldSelector, expressions *sets.String, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if len(fs.APIVersion) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("apiVersion"), "")) + return allErrs + } + if len(fs.FieldPath) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("fieldPath"), "")) + return allErrs + } + + internalFieldPath, _, err := podshelper.ConvertDownwardAPIFieldLabel(fs.APIVersion, fs.FieldPath, "") + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("fieldPath"), fs.FieldPath, fmt.Sprintf("error converting fieldPath: %v", err))) + return allErrs + } + + if path, subscript, ok := fieldpath.SplitMaybeSubscriptedPath(internalFieldPath); ok { + switch path { + case "metadata.annotations": + for _, msg := range validation.IsQualifiedName(strings.ToLower(subscript)) { + allErrs = append(allErrs, field.Invalid(fldPath, subscript, msg)) + } + case "metadata.labels": + for _, msg := range validation.IsQualifiedName(subscript) { + allErrs = append(allErrs, field.Invalid(fldPath, subscript, msg)) + } + default: + allErrs = append(allErrs, field.Invalid(fldPath, path, "does not support subscript")) + } + } else if !expressions.Has(path) { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("fieldPath"), path, expressions.List())) + return allErrs + } + + return allErrs +} + +func fsResourceIsEphemeralStorage(resource string) bool { + if resource == "limits.ephemeral-storage" || resource == "requests.ephemeral-storage" { + return true + } + return false +} + +func validateContainerResourceFieldSelector(fs *core.ResourceFieldSelector, expressions *sets.String, fldPath *field.Path, volume bool) field.ErrorList { + allErrs := field.ErrorList{} + + if volume && len(fs.ContainerName) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("containerName"), "")) + } else if len(fs.Resource) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("resource"), "")) + } else if !expressions.Has(fs.Resource) { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("resource"), fs.Resource, expressions.List())) + } else if fsResourceIsEphemeralStorage(fs.Resource) && !utilfeature.DefaultFeatureGate.Enabled(features.LocalStorageCapacityIsolation) { + allErrs = append(allErrs, field.Forbidden(fldPath, "Containers' ephemeral storage requests/limits disabled by feature-gate for Downward API")) + } + allErrs = append(allErrs, validateContainerResourceDivisor(fs.Resource, fs.Divisor, fldPath)...) + return allErrs +} + +func ValidateEnvFrom(vars []core.EnvFromSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for i, ev := range vars { + idxPath := fldPath.Index(i) + if len(ev.Prefix) > 0 { + for _, msg := range validation.IsEnvVarName(ev.Prefix) { + allErrs = append(allErrs, field.Invalid(idxPath.Child("prefix"), ev.Prefix, msg)) + } + } + + numSources := 0 + if ev.ConfigMapRef != nil { + numSources++ + allErrs = append(allErrs, validateConfigMapEnvSource(ev.ConfigMapRef, idxPath.Child("configMapRef"))...) + } + if ev.SecretRef != nil { + numSources++ + allErrs = append(allErrs, validateSecretEnvSource(ev.SecretRef, idxPath.Child("secretRef"))...) + } + + if numSources == 0 { + allErrs = append(allErrs, field.Invalid(fldPath, "", "must specify one of: `configMapRef` or `secretRef`")) + } else if numSources > 1 { + allErrs = append(allErrs, field.Invalid(fldPath, "", "may not have more than one field specified at a time")) + } + } + return allErrs +} + +func validateConfigMapEnvSource(configMapSource *core.ConfigMapEnvSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(configMapSource.Name) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) + } else { + for _, msg := range ValidateConfigMapName(configMapSource.Name, true) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), configMapSource.Name, msg)) + } + } + return allErrs +} + +func validateSecretEnvSource(secretSource *core.SecretEnvSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(secretSource.Name) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) + } else { + for _, msg := range ValidateSecretName(secretSource.Name, true) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), secretSource.Name, msg)) + } + } + return allErrs +} + +var validContainerResourceDivisorForCPU = sets.NewString("1m", "1") +var validContainerResourceDivisorForMemory = sets.NewString("1", "1k", "1M", "1G", "1T", "1P", "1E", "1Ki", "1Mi", "1Gi", "1Ti", "1Pi", "1Ei") +var validContainerResourceDivisorForEphemeralStorage = sets.NewString("1", "1k", "1M", "1G", "1T", "1P", "1E", "1Ki", "1Mi", "1Gi", "1Ti", "1Pi", "1Ei") + +func validateContainerResourceDivisor(rName string, divisor resource.Quantity, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + unsetDivisor := resource.Quantity{} + if unsetDivisor.Cmp(divisor) == 0 { + return allErrs + } + switch rName { + case "limits.cpu", "requests.cpu": + if !validContainerResourceDivisorForCPU.Has(divisor.String()) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("divisor"), rName, "only divisor's values 1m and 1 are supported with the cpu resource")) + } + case "limits.memory", "requests.memory": + if !validContainerResourceDivisorForMemory.Has(divisor.String()) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("divisor"), rName, "only divisor's values 1, 1k, 1M, 1G, 1T, 1P, 1E, 1Ki, 1Mi, 1Gi, 1Ti, 1Pi, 1Ei are supported with the memory resource")) + } + case "limits.ephemeral-storage", "requests.ephemeral-storage": + if !validContainerResourceDivisorForEphemeralStorage.Has(divisor.String()) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("divisor"), rName, "only divisor's values 1, 1k, 1M, 1G, 1T, 1P, 1E, 1Ki, 1Mi, 1Gi, 1Ti, 1Pi, 1Ei are supported with the local ephemeral storage resource")) + } + } + return allErrs +} + +func validateConfigMapKeySelector(s *core.ConfigMapKeySelector, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + nameFn := ValidateNameFunc(ValidateSecretName) + for _, msg := range nameFn(s.Name, false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), s.Name, msg)) + } + if len(s.Key) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("key"), "")) + } else { + for _, msg := range validation.IsConfigMapKey(s.Key) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("key"), s.Key, msg)) + } + } + + return allErrs +} + +func validateSecretKeySelector(s *core.SecretKeySelector, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + nameFn := ValidateNameFunc(ValidateSecretName) + for _, msg := range nameFn(s.Name, false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), s.Name, msg)) + } + if len(s.Key) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("key"), "")) + } else { + for _, msg := range validation.IsConfigMapKey(s.Key) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("key"), s.Key, msg)) + } + } + + return allErrs +} + +func GetVolumeMountMap(mounts []core.VolumeMount) map[string]string { + volmounts := make(map[string]string) + + for _, mnt := range mounts { + volmounts[mnt.Name] = mnt.MountPath + } + + return volmounts +} + +func GetVolumeDeviceMap(devices []core.VolumeDevice) map[string]string { + voldevices := make(map[string]string) + + for _, dev := range devices { + voldevices[dev.Name] = dev.DevicePath + } + + return voldevices +} + +func ValidateVolumeMounts(mounts []core.VolumeMount, voldevices map[string]string, volumes map[string]core.VolumeSource, container *core.Container, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + mountpoints := sets.NewString() + + for i, mnt := range mounts { + idxPath := fldPath.Index(i) + if len(mnt.Name) == 0 { + allErrs = append(allErrs, field.Required(idxPath.Child("name"), "")) + } + if !IsMatchedVolume(mnt.Name, volumes) { + allErrs = append(allErrs, field.NotFound(idxPath.Child("name"), mnt.Name)) + } + if len(mnt.MountPath) == 0 { + allErrs = append(allErrs, field.Required(idxPath.Child("mountPath"), "")) + } + if mountpoints.Has(mnt.MountPath) { + allErrs = append(allErrs, field.Invalid(idxPath.Child("mountPath"), mnt.MountPath, "must be unique")) + } + mountpoints.Insert(mnt.MountPath) + + // check for overlap with VolumeDevice + if mountNameAlreadyExists(mnt.Name, voldevices) { + allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), mnt.Name, "must not already exist in volumeDevices")) + } + if mountPathAlreadyExists(mnt.MountPath, voldevices) { + allErrs = append(allErrs, field.Invalid(idxPath.Child("mountPath"), mnt.MountPath, "must not already exist as a path in volumeDevices")) + } + + if len(mnt.SubPath) > 0 { + allErrs = append(allErrs, validateLocalDescendingPath(mnt.SubPath, fldPath.Child("subPath"))...) + } + + if mnt.MountPropagation != nil { + allErrs = append(allErrs, validateMountPropagation(mnt.MountPropagation, container, fldPath.Child("mountPropagation"))...) + } + } + return allErrs +} + +func ValidateVolumeDevices(devices []core.VolumeDevice, volmounts map[string]string, volumes map[string]core.VolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + devicepath := sets.NewString() + devicename := sets.NewString() + + if devices != nil && !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("volumeDevices"), "Container volumeDevices is disabled by feature-gate")) + return allErrs + } + if devices != nil { + for i, dev := range devices { + idxPath := fldPath.Index(i) + devName := dev.Name + devPath := dev.DevicePath + didMatch, isPVC := isMatchedDevice(devName, volumes) + if len(devName) == 0 { + allErrs = append(allErrs, field.Required(idxPath.Child("name"), "")) + } + if devicename.Has(devName) { + allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), devName, "must be unique")) + } + // Must be PersistentVolumeClaim volume source + if didMatch && !isPVC { + allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), devName, "can only use volume source type of PersistentVolumeClaim for block mode")) + } + if !didMatch { + allErrs = append(allErrs, field.NotFound(idxPath.Child("name"), devName)) + } + if len(devPath) == 0 { + allErrs = append(allErrs, field.Required(idxPath.Child("devicePath"), "")) + } + if devicepath.Has(devPath) { + allErrs = append(allErrs, field.Invalid(idxPath.Child("devicePath"), devPath, "must be unique")) + } + if len(devPath) > 0 && len(validatePathNoBacksteps(devPath, fldPath.Child("devicePath"))) > 0 { + allErrs = append(allErrs, field.Invalid(idxPath.Child("devicePath"), devPath, "can not contain backsteps ('..')")) + } else { + devicepath.Insert(devPath) + } + // check for overlap with VolumeMount + if deviceNameAlreadyExists(devName, volmounts) { + allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), devName, "must not already exist in volumeMounts")) + } + if devicePathAlreadyExists(devPath, volmounts) { + allErrs = append(allErrs, field.Invalid(idxPath.Child("devicePath"), devPath, "must not already exist as a path in volumeMounts")) + } + if len(devName) > 0 { + devicename.Insert(devName) + } + } + } + return allErrs +} + +func validateProbe(probe *core.Probe, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if probe == nil { + return allErrs + } + allErrs = append(allErrs, validateHandler(&probe.Handler, fldPath)...) + + allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.InitialDelaySeconds), fldPath.Child("initialDelaySeconds"))...) + allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.TimeoutSeconds), fldPath.Child("timeoutSeconds"))...) + allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.PeriodSeconds), fldPath.Child("periodSeconds"))...) + allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.SuccessThreshold), fldPath.Child("successThreshold"))...) + allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.FailureThreshold), fldPath.Child("failureThreshold"))...) + return allErrs +} + +func validateClientIPAffinityConfig(config *core.SessionAffinityConfig, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if config == nil { + allErrs = append(allErrs, field.Required(fldPath, fmt.Sprintf("when session affinity type is %s", core.ServiceAffinityClientIP))) + return allErrs + } + if config.ClientIP == nil { + allErrs = append(allErrs, field.Required(fldPath.Child("clientIP"), fmt.Sprintf("when session affinity type is %s", core.ServiceAffinityClientIP))) + return allErrs + } + if config.ClientIP.TimeoutSeconds == nil { + allErrs = append(allErrs, field.Required(fldPath.Child("clientIP").Child("timeoutSeconds"), fmt.Sprintf("when session affinity type is %s", core.ServiceAffinityClientIP))) + return allErrs + } + allErrs = append(allErrs, validateAffinityTimeout(config.ClientIP.TimeoutSeconds, fldPath.Child("clientIP").Child("timeoutSeconds"))...) + + return allErrs +} + +func validateAffinityTimeout(timeout *int32, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if *timeout <= 0 || *timeout > core.MaxClientIPServiceAffinitySeconds { + allErrs = append(allErrs, field.Invalid(fldPath, timeout, fmt.Sprintf("must be greater than 0 and less than %d", core.MaxClientIPServiceAffinitySeconds))) + } + return allErrs +} + +// AccumulateUniqueHostPorts extracts each HostPort of each Container, +// accumulating the results and returning an error if any ports conflict. +func AccumulateUniqueHostPorts(containers []core.Container, accumulator *sets.String, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + for ci, ctr := range containers { + idxPath := fldPath.Index(ci) + portsPath := idxPath.Child("ports") + for pi := range ctr.Ports { + idxPath := portsPath.Index(pi) + port := ctr.Ports[pi].HostPort + if port == 0 { + continue + } + str := fmt.Sprintf("%s/%s/%d", ctr.Ports[pi].Protocol, ctr.Ports[pi].HostIP, port) + if accumulator.Has(str) { + allErrs = append(allErrs, field.Duplicate(idxPath.Child("hostPort"), str)) + } else { + accumulator.Insert(str) + } + } + } + return allErrs +} + +// checkHostPortConflicts checks for colliding Port.HostPort values across +// a slice of containers. +func checkHostPortConflicts(containers []core.Container, fldPath *field.Path) field.ErrorList { + allPorts := sets.String{} + return AccumulateUniqueHostPorts(containers, &allPorts, fldPath) +} + +func validateExecAction(exec *core.ExecAction, fldPath *field.Path) field.ErrorList { + allErrors := field.ErrorList{} + if len(exec.Command) == 0 { + allErrors = append(allErrors, field.Required(fldPath.Child("command"), "")) + } + return allErrors +} + +var supportedHTTPSchemes = sets.NewString(string(core.URISchemeHTTP), string(core.URISchemeHTTPS)) + +func validateHTTPGetAction(http *core.HTTPGetAction, fldPath *field.Path) field.ErrorList { + allErrors := field.ErrorList{} + if len(http.Path) == 0 { + allErrors = append(allErrors, field.Required(fldPath.Child("path"), "")) + } + allErrors = append(allErrors, ValidatePortNumOrName(http.Port, fldPath.Child("port"))...) + if !supportedHTTPSchemes.Has(string(http.Scheme)) { + allErrors = append(allErrors, field.NotSupported(fldPath.Child("scheme"), http.Scheme, supportedHTTPSchemes.List())) + } + for _, header := range http.HTTPHeaders { + for _, msg := range validation.IsHTTPHeaderName(header.Name) { + allErrors = append(allErrors, field.Invalid(fldPath.Child("httpHeaders"), header.Name, msg)) + } + } + return allErrors +} + +func ValidatePortNumOrName(port intstr.IntOrString, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if port.Type == intstr.Int { + for _, msg := range validation.IsValidPortNum(port.IntValue()) { + allErrs = append(allErrs, field.Invalid(fldPath, port.IntValue(), msg)) + } + } else if port.Type == intstr.String { + for _, msg := range validation.IsValidPortName(port.StrVal) { + allErrs = append(allErrs, field.Invalid(fldPath, port.StrVal, msg)) + } + } else { + allErrs = append(allErrs, field.InternalError(fldPath, fmt.Errorf("unknown type: %v", port.Type))) + } + return allErrs +} + +func validateTCPSocketAction(tcp *core.TCPSocketAction, fldPath *field.Path) field.ErrorList { + return ValidatePortNumOrName(tcp.Port, fldPath.Child("port")) +} + +func validateHandler(handler *core.Handler, fldPath *field.Path) field.ErrorList { + numHandlers := 0 + allErrors := field.ErrorList{} + if handler.Exec != nil { + if numHandlers > 0 { + allErrors = append(allErrors, field.Forbidden(fldPath.Child("exec"), "may not specify more than 1 handler type")) + } else { + numHandlers++ + allErrors = append(allErrors, validateExecAction(handler.Exec, fldPath.Child("exec"))...) + } + } + if handler.HTTPGet != nil { + if numHandlers > 0 { + allErrors = append(allErrors, field.Forbidden(fldPath.Child("httpGet"), "may not specify more than 1 handler type")) + } else { + numHandlers++ + allErrors = append(allErrors, validateHTTPGetAction(handler.HTTPGet, fldPath.Child("httpGet"))...) + } + } + if handler.TCPSocket != nil { + if numHandlers > 0 { + allErrors = append(allErrors, field.Forbidden(fldPath.Child("tcpSocket"), "may not specify more than 1 handler type")) + } else { + numHandlers++ + allErrors = append(allErrors, validateTCPSocketAction(handler.TCPSocket, fldPath.Child("tcpSocket"))...) + } + } + if numHandlers == 0 { + allErrors = append(allErrors, field.Required(fldPath, "must specify a handler type")) + } + return allErrors +} + +func validateLifecycle(lifecycle *core.Lifecycle, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if lifecycle.PostStart != nil { + allErrs = append(allErrs, validateHandler(lifecycle.PostStart, fldPath.Child("postStart"))...) + } + if lifecycle.PreStop != nil { + allErrs = append(allErrs, validateHandler(lifecycle.PreStop, fldPath.Child("preStop"))...) + } + return allErrs +} + +var supportedPullPolicies = sets.NewString(string(core.PullAlways), string(core.PullIfNotPresent), string(core.PullNever)) + +func validatePullPolicy(policy core.PullPolicy, fldPath *field.Path) field.ErrorList { + allErrors := field.ErrorList{} + + switch policy { + case core.PullAlways, core.PullIfNotPresent, core.PullNever: + break + case "": + allErrors = append(allErrors, field.Required(fldPath, "")) + default: + allErrors = append(allErrors, field.NotSupported(fldPath, policy, supportedPullPolicies.List())) + } + + return allErrors +} + +func validateInitContainers(containers, otherContainers []core.Container, deviceVolumes map[string]core.VolumeSource, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + if len(containers) > 0 { + allErrs = append(allErrs, validateContainers(containers, deviceVolumes, fldPath)...) + } + + allNames := sets.String{} + for _, ctr := range otherContainers { + allNames.Insert(ctr.Name) + } + for i, ctr := range containers { + idxPath := fldPath.Index(i) + if allNames.Has(ctr.Name) { + allErrs = append(allErrs, field.Duplicate(idxPath.Child("name"), ctr.Name)) + } + if len(ctr.Name) > 0 { + allNames.Insert(ctr.Name) + } + if ctr.Lifecycle != nil { + allErrs = append(allErrs, field.Invalid(idxPath.Child("lifecycle"), ctr.Lifecycle, "must not be set for init containers")) + } + if ctr.LivenessProbe != nil { + allErrs = append(allErrs, field.Invalid(idxPath.Child("livenessProbe"), ctr.LivenessProbe, "must not be set for init containers")) + } + if ctr.ReadinessProbe != nil { + allErrs = append(allErrs, field.Invalid(idxPath.Child("readinessProbe"), ctr.ReadinessProbe, "must not be set for init containers")) + } + } + return allErrs +} + +func validateContainers(containers []core.Container, volumes map[string]core.VolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if len(containers) == 0 { + return append(allErrs, field.Required(fldPath, "")) + } + + allNames := sets.String{} + for i, ctr := range containers { + idxPath := fldPath.Index(i) + namePath := idxPath.Child("name") + volMounts := GetVolumeMountMap(ctr.VolumeMounts) + volDevices := GetVolumeDeviceMap(ctr.VolumeDevices) + + if len(ctr.Name) == 0 { + allErrs = append(allErrs, field.Required(namePath, "")) + } else { + allErrs = append(allErrs, ValidateDNS1123Label(ctr.Name, namePath)...) + } + if allNames.Has(ctr.Name) { + allErrs = append(allErrs, field.Duplicate(namePath, ctr.Name)) + } else { + allNames.Insert(ctr.Name) + } + // TODO: do not validate leading and trailing whitespace to preserve backward compatibility. + // for example: https://github.com/openshift/origin/issues/14659 image = " " is special token in pod template + // others may have done similar + if len(ctr.Image) == 0 { + allErrs = append(allErrs, field.Required(idxPath.Child("image"), "")) + } + if ctr.Lifecycle != nil { + allErrs = append(allErrs, validateLifecycle(ctr.Lifecycle, idxPath.Child("lifecycle"))...) + } + allErrs = append(allErrs, validateProbe(ctr.LivenessProbe, idxPath.Child("livenessProbe"))...) + // Liveness-specific validation + if ctr.LivenessProbe != nil && ctr.LivenessProbe.SuccessThreshold != 1 { + allErrs = append(allErrs, field.Invalid(idxPath.Child("livenessProbe", "successThreshold"), ctr.LivenessProbe.SuccessThreshold, "must be 1")) + } + + switch ctr.TerminationMessagePolicy { + case core.TerminationMessageReadFile, core.TerminationMessageFallbackToLogsOnError: + case "": + allErrs = append(allErrs, field.Required(idxPath.Child("terminationMessagePolicy"), "must be 'File' or 'FallbackToLogsOnError'")) + default: + allErrs = append(allErrs, field.Invalid(idxPath.Child("terminationMessagePolicy"), ctr.TerminationMessagePolicy, "must be 'File' or 'FallbackToLogsOnError'")) + } + + allErrs = append(allErrs, validateProbe(ctr.ReadinessProbe, idxPath.Child("readinessProbe"))...) + allErrs = append(allErrs, validateContainerPorts(ctr.Ports, idxPath.Child("ports"))...) + allErrs = append(allErrs, ValidateEnv(ctr.Env, idxPath.Child("env"))...) + allErrs = append(allErrs, ValidateEnvFrom(ctr.EnvFrom, idxPath.Child("envFrom"))...) + allErrs = append(allErrs, ValidateVolumeMounts(ctr.VolumeMounts, volDevices, volumes, &ctr, idxPath.Child("volumeMounts"))...) + allErrs = append(allErrs, ValidateVolumeDevices(ctr.VolumeDevices, volMounts, volumes, idxPath.Child("volumeDevices"))...) + allErrs = append(allErrs, validatePullPolicy(ctr.ImagePullPolicy, idxPath.Child("imagePullPolicy"))...) + allErrs = append(allErrs, ValidateResourceRequirements(&ctr.Resources, idxPath.Child("resources"))...) + allErrs = append(allErrs, ValidateSecurityContext(ctr.SecurityContext, idxPath.Child("securityContext"))...) + } + // Check for colliding ports across all containers. + allErrs = append(allErrs, checkHostPortConflicts(containers, fldPath)...) + + return allErrs +} + +func validateRestartPolicy(restartPolicy *core.RestartPolicy, fldPath *field.Path) field.ErrorList { + allErrors := field.ErrorList{} + switch *restartPolicy { + case core.RestartPolicyAlways, core.RestartPolicyOnFailure, core.RestartPolicyNever: + break + case "": + allErrors = append(allErrors, field.Required(fldPath, "")) + default: + validValues := []string{string(core.RestartPolicyAlways), string(core.RestartPolicyOnFailure), string(core.RestartPolicyNever)} + allErrors = append(allErrors, field.NotSupported(fldPath, *restartPolicy, validValues)) + } + + return allErrors +} + +func validateDNSPolicy(dnsPolicy *core.DNSPolicy, fldPath *field.Path) field.ErrorList { + allErrors := field.ErrorList{} + switch *dnsPolicy { + case core.DNSClusterFirstWithHostNet, core.DNSClusterFirst, core.DNSDefault: + case core.DNSNone: + if !utilfeature.DefaultFeatureGate.Enabled(features.CustomPodDNS) { + allErrors = append(allErrors, field.Invalid(fldPath, dnsPolicy, "DNSPolicy: can not use 'None', custom pod DNS is disabled by feature gate")) + } + case "": + allErrors = append(allErrors, field.Required(fldPath, "")) + default: + validValues := []string{string(core.DNSClusterFirstWithHostNet), string(core.DNSClusterFirst), string(core.DNSDefault)} + if utilfeature.DefaultFeatureGate.Enabled(features.CustomPodDNS) { + validValues = append(validValues, string(core.DNSNone)) + } + allErrors = append(allErrors, field.NotSupported(fldPath, dnsPolicy, validValues)) + } + return allErrors +} + +const ( + // Limits on various DNS parameters. These are derived from + // restrictions in Linux libc name resolution handling. + // Max number of DNS name servers. + MaxDNSNameservers = 3 + // Max number of domains in search path. + MaxDNSSearchPaths = 6 + // Max number of characters in search path. + MaxDNSSearchListChars = 256 +) + +func validatePodDNSConfig(dnsConfig *core.PodDNSConfig, dnsPolicy *core.DNSPolicy, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + // Validate DNSNone case. Must provide at least one DNS name server. + if utilfeature.DefaultFeatureGate.Enabled(features.CustomPodDNS) && dnsPolicy != nil && *dnsPolicy == core.DNSNone { + if dnsConfig == nil { + return append(allErrs, field.Required(fldPath, fmt.Sprintf("must provide `dnsConfig` when `dnsPolicy` is %s", core.DNSNone))) + } + if len(dnsConfig.Nameservers) == 0 { + return append(allErrs, field.Required(fldPath.Child("nameservers"), fmt.Sprintf("must provide at least one DNS nameserver when `dnsPolicy` is %s", core.DNSNone))) + } + } + + if dnsConfig != nil { + if !utilfeature.DefaultFeatureGate.Enabled(features.CustomPodDNS) { + return append(allErrs, field.Forbidden(fldPath, "DNSConfig: custom pod DNS is disabled by feature gate")) + } + + // Validate nameservers. + if len(dnsConfig.Nameservers) > MaxDNSNameservers { + allErrs = append(allErrs, field.Invalid(fldPath.Child("nameservers"), dnsConfig.Nameservers, fmt.Sprintf("must not have more than %v nameservers", MaxDNSNameservers))) + } + for i, ns := range dnsConfig.Nameservers { + if ip := net.ParseIP(ns); ip == nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("nameservers").Index(i), ns, "must be valid IP address")) + } + } + // Validate searches. + if len(dnsConfig.Searches) > MaxDNSSearchPaths { + allErrs = append(allErrs, field.Invalid(fldPath.Child("searches"), dnsConfig.Searches, fmt.Sprintf("must not have more than %v search paths", MaxDNSSearchPaths))) + } + // Include the space between search paths. + if len(strings.Join(dnsConfig.Searches, " ")) > MaxDNSSearchListChars { + allErrs = append(allErrs, field.Invalid(fldPath.Child("searches"), dnsConfig.Searches, "must not have more than 256 characters (including spaces) in the search list")) + } + for i, search := range dnsConfig.Searches { + allErrs = append(allErrs, ValidateDNS1123Subdomain(search, fldPath.Child("searches").Index(i))...) + } + // Validate options. + for i, option := range dnsConfig.Options { + if len(option.Name) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("options").Index(i), "must not be empty")) + } + } + } + return allErrs +} + +func validateHostNetwork(hostNetwork bool, containers []core.Container, fldPath *field.Path) field.ErrorList { + allErrors := field.ErrorList{} + if hostNetwork { + for i, container := range containers { + portsPath := fldPath.Index(i).Child("ports") + for i, port := range container.Ports { + idxPath := portsPath.Index(i) + if port.HostPort != port.ContainerPort { + allErrors = append(allErrors, field.Invalid(idxPath.Child("containerPort"), port.ContainerPort, "must match `hostPort` when `hostNetwork` is true")) + } + } + } + } + return allErrors +} + +// validateImagePullSecrets checks to make sure the pull secrets are well +// formed. Right now, we only expect name to be set (it's the only field). If +// this ever changes and someone decides to set those fields, we'd like to +// know. +func validateImagePullSecrets(imagePullSecrets []core.LocalObjectReference, fldPath *field.Path) field.ErrorList { + allErrors := field.ErrorList{} + for i, currPullSecret := range imagePullSecrets { + idxPath := fldPath.Index(i) + strippedRef := core.LocalObjectReference{Name: currPullSecret.Name} + if !reflect.DeepEqual(strippedRef, currPullSecret) { + allErrors = append(allErrors, field.Invalid(idxPath, currPullSecret, "only name may be set")) + } + } + return allErrors +} + +// validateAffinity checks if given affinities are valid +func validateAffinity(affinity *core.Affinity, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if affinity != nil { + if na := affinity.NodeAffinity; na != nil { + // TODO: Uncomment the next three lines once RequiredDuringSchedulingRequiredDuringExecution is implemented. + // if na.RequiredDuringSchedulingRequiredDuringExecution != nil { + // allErrs = append(allErrs, ValidateNodeSelector(na.RequiredDuringSchedulingRequiredDuringExecution, fldPath.Child("requiredDuringSchedulingRequiredDuringExecution"))...) + // } + + if na.RequiredDuringSchedulingIgnoredDuringExecution != nil { + allErrs = append(allErrs, ValidateNodeSelector(na.RequiredDuringSchedulingIgnoredDuringExecution, fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...) + } + + if len(na.PreferredDuringSchedulingIgnoredDuringExecution) > 0 { + allErrs = append(allErrs, ValidatePreferredSchedulingTerms(na.PreferredDuringSchedulingIgnoredDuringExecution, fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...) + } + } + if affinity.PodAffinity != nil { + allErrs = append(allErrs, validatePodAffinity(affinity.PodAffinity, fldPath.Child("podAffinity"))...) + } + if affinity.PodAntiAffinity != nil { + allErrs = append(allErrs, validatePodAntiAffinity(affinity.PodAntiAffinity, fldPath.Child("podAntiAffinity"))...) + } + } + + return allErrs +} + +func validateTaintEffect(effect *core.TaintEffect, allowEmpty bool, fldPath *field.Path) field.ErrorList { + if !allowEmpty && len(*effect) == 0 { + return field.ErrorList{field.Required(fldPath, "")} + } + + allErrors := field.ErrorList{} + switch *effect { + // TODO: Replace next line with subsequent commented-out line when implement TaintEffectNoScheduleNoAdmit. + case core.TaintEffectNoSchedule, core.TaintEffectPreferNoSchedule, core.TaintEffectNoExecute: + // case core.TaintEffectNoSchedule, core.TaintEffectPreferNoSchedule, core.TaintEffectNoScheduleNoAdmit, core.TaintEffectNoExecute: + default: + validValues := []string{ + string(core.TaintEffectNoSchedule), + string(core.TaintEffectPreferNoSchedule), + string(core.TaintEffectNoExecute), + // TODO: Uncomment this block when implement TaintEffectNoScheduleNoAdmit. + // string(core.TaintEffectNoScheduleNoAdmit), + } + allErrors = append(allErrors, field.NotSupported(fldPath, effect, validValues)) + } + return allErrors +} + +// validateOnlyAddedTolerations validates updated pod tolerations. +func validateOnlyAddedTolerations(newTolerations []core.Toleration, oldTolerations []core.Toleration, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for _, old := range oldTolerations { + found := false + old.TolerationSeconds = nil + for _, new := range newTolerations { + new.TolerationSeconds = nil + if reflect.DeepEqual(old, new) { + found = true + break + } + } + if !found { + allErrs = append(allErrs, field.Forbidden(fldPath, "existing toleration can not be modified except its tolerationSeconds")) + return allErrs + } + } + + allErrs = append(allErrs, ValidateTolerations(newTolerations, fldPath)...) + return allErrs +} + +func ValidateHostAliases(hostAliases []core.HostAlias, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for _, hostAlias := range hostAliases { + if ip := net.ParseIP(hostAlias.IP); ip == nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("ip"), hostAlias.IP, "must be valid IP address")) + } + for _, hostname := range hostAlias.Hostnames { + allErrs = append(allErrs, ValidateDNS1123Subdomain(hostname, fldPath.Child("hostnames"))...) + } + } + return allErrs +} + +// ValidateTolerations tests if given tolerations have valid data. +func ValidateTolerations(tolerations []core.Toleration, fldPath *field.Path) field.ErrorList { + allErrors := field.ErrorList{} + for i, toleration := range tolerations { + idxPath := fldPath.Index(i) + // validate the toleration key + if len(toleration.Key) > 0 { + allErrors = append(allErrors, unversionedvalidation.ValidateLabelName(toleration.Key, idxPath.Child("key"))...) + } + + // empty toleration key with Exists operator and empty value means match all taints + if len(toleration.Key) == 0 && toleration.Operator != core.TolerationOpExists { + allErrors = append(allErrors, field.Invalid(idxPath.Child("operator"), toleration.Operator, + "operator must be Exists when `key` is empty, which means \"match all values and all keys\"")) + } + + if toleration.TolerationSeconds != nil && toleration.Effect != core.TaintEffectNoExecute { + allErrors = append(allErrors, field.Invalid(idxPath.Child("effect"), toleration.Effect, + "effect must be 'NoExecute' when `tolerationSeconds` is set")) + } + + // validate toleration operator and value + switch toleration.Operator { + // empty operator means Equal + case core.TolerationOpEqual, "": + if errs := validation.IsValidLabelValue(toleration.Value); len(errs) != 0 { + allErrors = append(allErrors, field.Invalid(idxPath.Child("operator"), toleration.Value, strings.Join(errs, ";"))) + } + case core.TolerationOpExists: + if len(toleration.Value) > 0 { + allErrors = append(allErrors, field.Invalid(idxPath.Child("operator"), toleration, "value must be empty when `operator` is 'Exists'")) + } + default: + validValues := []string{string(core.TolerationOpEqual), string(core.TolerationOpExists)} + allErrors = append(allErrors, field.NotSupported(idxPath.Child("operator"), toleration.Operator, validValues)) + } + + // validate toleration effect, empty toleration effect means match all taint effects + if len(toleration.Effect) > 0 { + allErrors = append(allErrors, validateTaintEffect(&toleration.Effect, true, idxPath.Child("effect"))...) + } + } + return allErrors +} + +func toResourceNames(resources core.ResourceList) []core.ResourceName { + result := []core.ResourceName{} + for resourceName := range resources { + result = append(result, resourceName) + } + return result +} + +func toSet(resourceNames []core.ResourceName) sets.String { + result := sets.NewString() + for _, resourceName := range resourceNames { + result.Insert(string(resourceName)) + } + return result +} + +func toContainerResourcesSet(ctr *core.Container) sets.String { + resourceNames := toResourceNames(ctr.Resources.Requests) + resourceNames = append(resourceNames, toResourceNames(ctr.Resources.Limits)...) + return toSet(resourceNames) +} + +// validateContainersOnlyForPod does additional validation for containers on a pod versus a pod template +// it only does additive validation of fields not covered in validateContainers +func validateContainersOnlyForPod(containers []core.Container, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for i, ctr := range containers { + idxPath := fldPath.Index(i) + if len(ctr.Image) != len(strings.TrimSpace(ctr.Image)) { + allErrs = append(allErrs, field.Invalid(idxPath.Child("image"), ctr.Image, "must not have leading or trailing whitespace")) + } + } + return allErrs +} + +// ValidatePod tests if required fields in the pod are set. +func ValidatePod(pod *core.Pod) field.ErrorList { + fldPath := field.NewPath("metadata") + allErrs := ValidateObjectMeta(&pod.ObjectMeta, true, ValidatePodName, fldPath) + allErrs = append(allErrs, ValidatePodSpecificAnnotations(pod.ObjectMeta.Annotations, &pod.Spec, fldPath.Child("annotations"))...) + allErrs = append(allErrs, ValidatePodSpec(&pod.Spec, field.NewPath("spec"))...) + + // we do additional validation only pertinent for pods and not pod templates + // this was done to preserve backwards compatibility + specPath := field.NewPath("spec") + + allErrs = append(allErrs, validateContainersOnlyForPod(pod.Spec.Containers, specPath.Child("containers"))...) + allErrs = append(allErrs, validateContainersOnlyForPod(pod.Spec.InitContainers, specPath.Child("initContainers"))...) + + if utilfeature.DefaultFeatureGate.Enabled(features.HugePages) { + hugePageResources := sets.NewString() + for i := range pod.Spec.Containers { + resourceSet := toContainerResourcesSet(&pod.Spec.Containers[i]) + for resourceStr := range resourceSet { + if v1helper.IsHugePageResourceName(v1.ResourceName(resourceStr)) { + hugePageResources.Insert(resourceStr) + } + } + } + if len(hugePageResources) > 1 { + allErrs = append(allErrs, field.Invalid(specPath, hugePageResources, "must use a single hugepage size in a pod spec")) + } + } + + return allErrs +} + +// ValidatePodSpec tests that the specified PodSpec has valid data. +// This includes checking formatting and uniqueness. It also canonicalizes the +// structure by setting default values and implementing any backwards-compatibility +// tricks. +func ValidatePodSpec(spec *core.PodSpec, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + vols, vErrs := ValidateVolumes(spec.Volumes, fldPath.Child("volumes")) + allErrs = append(allErrs, vErrs...) + allErrs = append(allErrs, validateContainers(spec.Containers, vols, fldPath.Child("containers"))...) + allErrs = append(allErrs, validateInitContainers(spec.InitContainers, spec.Containers, vols, fldPath.Child("initContainers"))...) + allErrs = append(allErrs, validateRestartPolicy(&spec.RestartPolicy, fldPath.Child("restartPolicy"))...) + allErrs = append(allErrs, validateDNSPolicy(&spec.DNSPolicy, fldPath.Child("dnsPolicy"))...) + allErrs = append(allErrs, unversionedvalidation.ValidateLabels(spec.NodeSelector, fldPath.Child("nodeSelector"))...) + allErrs = append(allErrs, ValidatePodSecurityContext(spec.SecurityContext, spec, fldPath, fldPath.Child("securityContext"))...) + allErrs = append(allErrs, validateImagePullSecrets(spec.ImagePullSecrets, fldPath.Child("imagePullSecrets"))...) + allErrs = append(allErrs, validateAffinity(spec.Affinity, fldPath.Child("affinity"))...) + allErrs = append(allErrs, validatePodDNSConfig(spec.DNSConfig, &spec.DNSPolicy, fldPath.Child("dnsConfig"))...) + if len(spec.ServiceAccountName) > 0 { + for _, msg := range ValidateServiceAccountName(spec.ServiceAccountName, false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("serviceAccountName"), spec.ServiceAccountName, msg)) + } + } + + if len(spec.NodeName) > 0 { + for _, msg := range ValidateNodeName(spec.NodeName, false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("nodeName"), spec.NodeName, msg)) + } + } + + if spec.ActiveDeadlineSeconds != nil { + value := *spec.ActiveDeadlineSeconds + if value < 1 || value > math.MaxInt32 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("activeDeadlineSeconds"), value, validation.InclusiveRangeError(1, math.MaxInt32))) + } + } + + if len(spec.Hostname) > 0 { + allErrs = append(allErrs, ValidateDNS1123Label(spec.Hostname, fldPath.Child("hostname"))...) + } + + if len(spec.Subdomain) > 0 { + allErrs = append(allErrs, ValidateDNS1123Label(spec.Subdomain, fldPath.Child("subdomain"))...) + } + + if len(spec.Tolerations) > 0 { + allErrs = append(allErrs, ValidateTolerations(spec.Tolerations, fldPath.Child("tolerations"))...) + } + + if len(spec.HostAliases) > 0 { + allErrs = append(allErrs, ValidateHostAliases(spec.HostAliases, fldPath.Child("hostAliases"))...) + } + + if len(spec.PriorityClassName) > 0 { + if !utilfeature.DefaultFeatureGate.Enabled(features.PodPriority) { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("priorityClassName"), "Pod priority is disabled by feature-gate")) + } else { + for _, msg := range ValidatePriorityClassName(spec.PriorityClassName, false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("priorityClassName"), spec.PriorityClassName, msg)) + } + } + } + + if spec.Priority != nil && !utilfeature.DefaultFeatureGate.Enabled(features.PodPriority) { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("priority"), "Pod priority is disabled by feature-gate")) + } + + return allErrs +} + +// ValidateNodeSelectorRequirement tests that the specified NodeSelectorRequirement fields has valid data +func ValidateNodeSelectorRequirement(rq core.NodeSelectorRequirement, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + switch rq.Operator { + case core.NodeSelectorOpIn, core.NodeSelectorOpNotIn: + if len(rq.Values) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("values"), "must be specified when `operator` is 'In' or 'NotIn'")) + } + case core.NodeSelectorOpExists, core.NodeSelectorOpDoesNotExist: + if len(rq.Values) > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("values"), "may not be specified when `operator` is 'Exists' or 'DoesNotExist'")) + } + + case core.NodeSelectorOpGt, core.NodeSelectorOpLt: + if len(rq.Values) != 1 { + allErrs = append(allErrs, field.Required(fldPath.Child("values"), "must be specified single value when `operator` is 'Lt' or 'Gt'")) + } + default: + allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), rq.Operator, "not a valid selector operator")) + } + allErrs = append(allErrs, unversionedvalidation.ValidateLabelName(rq.Key, fldPath.Child("key"))...) + return allErrs +} + +// ValidateNodeSelectorTerm tests that the specified node selector term has valid data +func ValidateNodeSelectorTerm(term core.NodeSelectorTerm, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if len(term.MatchExpressions) == 0 { + return append(allErrs, field.Required(fldPath.Child("matchExpressions"), "must have at least one node selector requirement")) + } + for j, req := range term.MatchExpressions { + allErrs = append(allErrs, ValidateNodeSelectorRequirement(req, fldPath.Child("matchExpressions").Index(j))...) + } + return allErrs +} + +// ValidateNodeSelector tests that the specified nodeSelector fields has valid data +func ValidateNodeSelector(nodeSelector *core.NodeSelector, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + termFldPath := fldPath.Child("nodeSelectorTerms") + if len(nodeSelector.NodeSelectorTerms) == 0 { + return append(allErrs, field.Required(termFldPath, "must have at least one node selector term")) + } + + for i, term := range nodeSelector.NodeSelectorTerms { + allErrs = append(allErrs, ValidateNodeSelectorTerm(term, termFldPath.Index(i))...) + } + + return allErrs +} + +// ValidateAvoidPodsInNodeAnnotations tests that the serialized AvoidPods in Node.Annotations has valid data +func ValidateAvoidPodsInNodeAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + v1Avoids, err := v1helper.GetAvoidPodsFromNodeAnnotations(annotations) + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("AvoidPods"), core.PreferAvoidPodsAnnotationKey, err.Error())) + return allErrs + } + var avoids core.AvoidPods + if err := corev1.Convert_v1_AvoidPods_To_core_AvoidPods(&v1Avoids, &avoids, nil); err != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("AvoidPods"), core.PreferAvoidPodsAnnotationKey, err.Error())) + return allErrs + } + + if len(avoids.PreferAvoidPods) != 0 { + for i, pa := range avoids.PreferAvoidPods { + idxPath := fldPath.Child(core.PreferAvoidPodsAnnotationKey).Index(i) + allErrs = append(allErrs, validatePreferAvoidPodsEntry(pa, idxPath)...) + } + } + + return allErrs +} + +// validatePreferAvoidPodsEntry tests if given PreferAvoidPodsEntry has valid data. +func validatePreferAvoidPodsEntry(avoidPodEntry core.PreferAvoidPodsEntry, fldPath *field.Path) field.ErrorList { + allErrors := field.ErrorList{} + if avoidPodEntry.PodSignature.PodController == nil { + allErrors = append(allErrors, field.Required(fldPath.Child("PodSignature"), "")) + } else { + if *(avoidPodEntry.PodSignature.PodController.Controller) != true { + allErrors = append(allErrors, + field.Invalid(fldPath.Child("PodSignature").Child("PodController").Child("Controller"), + *(avoidPodEntry.PodSignature.PodController.Controller), "must point to a controller")) + } + } + return allErrors +} + +// ValidatePreferredSchedulingTerms tests that the specified SoftNodeAffinity fields has valid data +func ValidatePreferredSchedulingTerms(terms []core.PreferredSchedulingTerm, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + for i, term := range terms { + if term.Weight <= 0 || term.Weight > 100 { + allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("weight"), term.Weight, "must be in the range 1-100")) + } + + allErrs = append(allErrs, ValidateNodeSelectorTerm(term.Preference, fldPath.Index(i).Child("preference"))...) + } + return allErrs +} + +// validatePodAffinityTerm tests that the specified podAffinityTerm fields have valid data +func validatePodAffinityTerm(podAffinityTerm core.PodAffinityTerm, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(podAffinityTerm.LabelSelector, fldPath.Child("matchExpressions"))...) + for _, name := range podAffinityTerm.Namespaces { + for _, msg := range ValidateNamespaceName(name, false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), name, msg)) + } + } + if len(podAffinityTerm.TopologyKey) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("topologyKey"), "can not be empty")) + } + return append(allErrs, unversionedvalidation.ValidateLabelName(podAffinityTerm.TopologyKey, fldPath.Child("topologyKey"))...) +} + +// validatePodAffinityTerms tests that the specified podAffinityTerms fields have valid data +func validatePodAffinityTerms(podAffinityTerms []core.PodAffinityTerm, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for i, podAffinityTerm := range podAffinityTerms { + allErrs = append(allErrs, validatePodAffinityTerm(podAffinityTerm, fldPath.Index(i))...) + } + return allErrs +} + +// validateWeightedPodAffinityTerms tests that the specified weightedPodAffinityTerms fields have valid data +func validateWeightedPodAffinityTerms(weightedPodAffinityTerms []core.WeightedPodAffinityTerm, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for j, weightedTerm := range weightedPodAffinityTerms { + if weightedTerm.Weight <= 0 || weightedTerm.Weight > 100 { + allErrs = append(allErrs, field.Invalid(fldPath.Index(j).Child("weight"), weightedTerm.Weight, "must be in the range 1-100")) + } + allErrs = append(allErrs, validatePodAffinityTerm(weightedTerm.PodAffinityTerm, fldPath.Index(j).Child("podAffinityTerm"))...) + } + return allErrs +} + +// validatePodAntiAffinity tests that the specified podAntiAffinity fields have valid data +func validatePodAntiAffinity(podAntiAffinity *core.PodAntiAffinity, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + // TODO:Uncomment below code once RequiredDuringSchedulingRequiredDuringExecution is implemented. + // if podAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution != nil { + // allErrs = append(allErrs, validatePodAffinityTerms(podAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution, false, + // fldPath.Child("requiredDuringSchedulingRequiredDuringExecution"))...) + //} + if podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil { + allErrs = append(allErrs, validatePodAffinityTerms(podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution, + fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...) + } + if podAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil { + allErrs = append(allErrs, validateWeightedPodAffinityTerms(podAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution, + fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...) + } + return allErrs +} + +// validatePodAffinity tests that the specified podAffinity fields have valid data +func validatePodAffinity(podAffinity *core.PodAffinity, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + // TODO:Uncomment below code once RequiredDuringSchedulingRequiredDuringExecution is implemented. + // if podAffinity.RequiredDuringSchedulingRequiredDuringExecution != nil { + // allErrs = append(allErrs, validatePodAffinityTerms(podAffinity.RequiredDuringSchedulingRequiredDuringExecution, false, + // fldPath.Child("requiredDuringSchedulingRequiredDuringExecution"))...) + //} + if podAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil { + allErrs = append(allErrs, validatePodAffinityTerms(podAffinity.RequiredDuringSchedulingIgnoredDuringExecution, + fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...) + } + if podAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil { + allErrs = append(allErrs, validateWeightedPodAffinityTerms(podAffinity.PreferredDuringSchedulingIgnoredDuringExecution, + fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...) + } + return allErrs +} + +func ValidateSeccompProfile(p string, fldPath *field.Path) field.ErrorList { + if p == "docker/default" { + return nil + } + if p == "unconfined" { + return nil + } + if strings.HasPrefix(p, "localhost/") { + return validateLocalDescendingPath(strings.TrimPrefix(p, "localhost/"), fldPath) + } + return field.ErrorList{field.Invalid(fldPath, p, "must be a valid seccomp profile")} +} + +func ValidateSeccompPodAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if p, exists := annotations[core.SeccompPodAnnotationKey]; exists { + allErrs = append(allErrs, ValidateSeccompProfile(p, fldPath.Child(core.SeccompPodAnnotationKey))...) + } + for k, p := range annotations { + if strings.HasPrefix(k, core.SeccompContainerAnnotationKeyPrefix) { + allErrs = append(allErrs, ValidateSeccompProfile(p, fldPath.Child(k))...) + } + } + + return allErrs +} + +func ValidateAppArmorPodAnnotations(annotations map[string]string, spec *core.PodSpec, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for k, p := range annotations { + if !strings.HasPrefix(k, apparmor.ContainerAnnotationKeyPrefix) { + continue + } + // TODO: this belongs to admission, not general pod validation: + if !utilfeature.DefaultFeatureGate.Enabled(features.AppArmor) { + allErrs = append(allErrs, field.Forbidden(fldPath.Key(k), "AppArmor is disabled by feature-gate")) + continue + } + containerName := strings.TrimPrefix(k, apparmor.ContainerAnnotationKeyPrefix) + if !podSpecHasContainer(spec, containerName) { + allErrs = append(allErrs, field.Invalid(fldPath.Key(k), containerName, "container not found")) + } + + if err := apparmor.ValidateProfileFormat(p); err != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Key(k), p, err.Error())) + } + } + + return allErrs +} + +func podSpecHasContainer(spec *core.PodSpec, containerName string) bool { + for _, c := range spec.InitContainers { + if c.Name == containerName { + return true + } + } + for _, c := range spec.Containers { + if c.Name == containerName { + return true + } + } + return false +} + +const ( + // a sysctl segment regex, concatenated with dots to form a sysctl name + SysctlSegmentFmt string = "[a-z0-9]([-_a-z0-9]*[a-z0-9])?" + + // a sysctl name regex + SysctlFmt string = "(" + SysctlSegmentFmt + "\\.)*" + SysctlSegmentFmt + + // the maximal length of a sysctl name + SysctlMaxLength int = 253 +) + +var sysctlRegexp = regexp.MustCompile("^" + SysctlFmt + "$") + +// IsValidSysctlName checks that the given string is a valid sysctl name, +// i.e. matches SysctlFmt. +func IsValidSysctlName(name string) bool { + if len(name) > SysctlMaxLength { + return false + } + return sysctlRegexp.MatchString(name) +} + +func validateSysctls(sysctls []core.Sysctl, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for i, s := range sysctls { + if len(s.Name) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("name"), "")) + } else if !IsValidSysctlName(s.Name) { + allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("name"), s.Name, fmt.Sprintf("must have at most %d characters and match regex %s", SysctlMaxLength, SysctlFmt))) + } + } + return allErrs +} + +// ValidatePodSecurityContext test that the specified PodSecurityContext has valid data. +func ValidatePodSecurityContext(securityContext *core.PodSecurityContext, spec *core.PodSpec, specPath, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if securityContext != nil { + allErrs = append(allErrs, validateHostNetwork(securityContext.HostNetwork, spec.Containers, specPath.Child("containers"))...) + if securityContext.FSGroup != nil { + for _, msg := range validation.IsValidGroupID(*securityContext.FSGroup) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("fsGroup"), *(securityContext.FSGroup), msg)) + } + } + if securityContext.RunAsUser != nil { + for _, msg := range validation.IsValidUserID(*securityContext.RunAsUser) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsUser"), *(securityContext.RunAsUser), msg)) + } + } + for g, gid := range securityContext.SupplementalGroups { + for _, msg := range validation.IsValidGroupID(gid) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("supplementalGroups").Index(g), gid, msg)) + } + } + } + + return allErrs +} + +func ValidateContainerUpdates(newContainers, oldContainers []core.Container, fldPath *field.Path) (allErrs field.ErrorList, stop bool) { + allErrs = field.ErrorList{} + if len(newContainers) != len(oldContainers) { + //TODO: Pinpoint the specific container that causes the invalid error after we have strategic merge diff + allErrs = append(allErrs, field.Forbidden(fldPath, "pod updates may not add or remove containers")) + return allErrs, true + } + + // validate updated container images + for i, ctr := range newContainers { + if len(ctr.Image) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("image"), "")) + } + // this is only called from ValidatePodUpdate so its safe to check leading/trailing whitespace. + if len(strings.TrimSpace(ctr.Image)) != len(ctr.Image) { + allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("image"), ctr.Image, "must not have leading or trailing whitespace")) + } + } + return allErrs, false +} + +// ValidatePodUpdate tests to see if the update is legal for an end user to make. newPod is updated with fields +// that cannot be changed. +func ValidatePodUpdate(newPod, oldPod *core.Pod) field.ErrorList { + fldPath := field.NewPath("metadata") + allErrs := ValidateObjectMetaUpdate(&newPod.ObjectMeta, &oldPod.ObjectMeta, fldPath) + allErrs = append(allErrs, ValidatePodSpecificAnnotationUpdates(newPod, oldPod, fldPath.Child("annotations"))...) + specPath := field.NewPath("spec") + + // validate updateable fields: + // 1. spec.containers[*].image + // 2. spec.initContainers[*].image + // 3. spec.activeDeadlineSeconds + + containerErrs, stop := ValidateContainerUpdates(newPod.Spec.Containers, oldPod.Spec.Containers, specPath.Child("containers")) + allErrs = append(allErrs, containerErrs...) + if stop { + return allErrs + } + containerErrs, stop = ValidateContainerUpdates(newPod.Spec.InitContainers, oldPod.Spec.InitContainers, specPath.Child("initContainers")) + allErrs = append(allErrs, containerErrs...) + if stop { + return allErrs + } + + // validate updated spec.activeDeadlineSeconds. two types of updates are allowed: + // 1. from nil to a positive value + // 2. from a positive value to a lesser, non-negative value + if newPod.Spec.ActiveDeadlineSeconds != nil { + newActiveDeadlineSeconds := *newPod.Spec.ActiveDeadlineSeconds + if newActiveDeadlineSeconds < 0 || newActiveDeadlineSeconds > math.MaxInt32 { + allErrs = append(allErrs, field.Invalid(specPath.Child("activeDeadlineSeconds"), newActiveDeadlineSeconds, validation.InclusiveRangeError(0, math.MaxInt32))) + return allErrs + } + if oldPod.Spec.ActiveDeadlineSeconds != nil { + oldActiveDeadlineSeconds := *oldPod.Spec.ActiveDeadlineSeconds + if oldActiveDeadlineSeconds < newActiveDeadlineSeconds { + allErrs = append(allErrs, field.Invalid(specPath.Child("activeDeadlineSeconds"), newActiveDeadlineSeconds, "must be less than or equal to previous value")) + return allErrs + } + } + } else if oldPod.Spec.ActiveDeadlineSeconds != nil { + allErrs = append(allErrs, field.Invalid(specPath.Child("activeDeadlineSeconds"), newPod.Spec.ActiveDeadlineSeconds, "must not update from a positive integer to nil value")) + } + + // handle updateable fields by munging those fields prior to deep equal comparison. + mungedPod := *newPod + // munge spec.containers[*].image + var newContainers []core.Container + for ix, container := range mungedPod.Spec.Containers { + container.Image = oldPod.Spec.Containers[ix].Image + newContainers = append(newContainers, container) + } + mungedPod.Spec.Containers = newContainers + // munge spec.initContainers[*].image + var newInitContainers []core.Container + for ix, container := range mungedPod.Spec.InitContainers { + container.Image = oldPod.Spec.InitContainers[ix].Image + newInitContainers = append(newInitContainers, container) + } + mungedPod.Spec.InitContainers = newInitContainers + // munge spec.activeDeadlineSeconds + mungedPod.Spec.ActiveDeadlineSeconds = nil + if oldPod.Spec.ActiveDeadlineSeconds != nil { + activeDeadlineSeconds := *oldPod.Spec.ActiveDeadlineSeconds + mungedPod.Spec.ActiveDeadlineSeconds = &activeDeadlineSeconds + } + + // Allow only additions to tolerations updates. + mungedPod.Spec.Tolerations = oldPod.Spec.Tolerations + allErrs = append(allErrs, validateOnlyAddedTolerations(newPod.Spec.Tolerations, oldPod.Spec.Tolerations, specPath.Child("tolerations"))...) + + if !apiequality.Semantic.DeepEqual(mungedPod.Spec, oldPod.Spec) { + // This diff isn't perfect, but it's a helluva lot better an "I'm not going to tell you what the difference is". + //TODO: Pinpoint the specific field that causes the invalid error after we have strategic merge diff + specDiff := diff.ObjectDiff(mungedPod.Spec, oldPod.Spec) + allErrs = append(allErrs, field.Forbidden(specPath, fmt.Sprintf("pod updates may not change fields other than `spec.containers[*].image`, `spec.initContainers[*].image`, `spec.activeDeadlineSeconds` or `spec.tolerations` (only additions to existing tolerations)\n%v", specDiff))) + } + + return allErrs +} + +// ValidateContainerStateTransition test to if any illegal container state transitions are being attempted +func ValidateContainerStateTransition(newStatuses, oldStatuses []core.ContainerStatus, fldpath *field.Path, restartPolicy core.RestartPolicy) field.ErrorList { + allErrs := field.ErrorList{} + // If we should always restart, containers are allowed to leave the terminated state + if restartPolicy == core.RestartPolicyAlways { + return allErrs + } + for i, oldStatus := range oldStatuses { + // Skip any container that is not terminated + if oldStatus.State.Terminated == nil { + continue + } + // Skip any container that failed but is allowed to restart + if oldStatus.State.Terminated.ExitCode != 0 && restartPolicy == core.RestartPolicyOnFailure { + continue + } + for _, newStatus := range newStatuses { + if oldStatus.Name == newStatus.Name && newStatus.State.Terminated == nil { + allErrs = append(allErrs, field.Forbidden(fldpath.Index(i).Child("state"), "may not be transitioned to non-terminated state")) + } + } + } + return allErrs +} + +// ValidatePodStatusUpdate tests to see if the update is legal for an end user to make. newPod is updated with fields +// that cannot be changed. +func ValidatePodStatusUpdate(newPod, oldPod *core.Pod) field.ErrorList { + fldPath := field.NewPath("metadata") + allErrs := ValidateObjectMetaUpdate(&newPod.ObjectMeta, &oldPod.ObjectMeta, fldPath) + allErrs = append(allErrs, ValidatePodSpecificAnnotationUpdates(newPod, oldPod, fldPath.Child("annotations"))...) + + fldPath = field.NewPath("status") + if newPod.Spec.NodeName != oldPod.Spec.NodeName { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("nodeName"), "may not be changed directly")) + } + + // If pod should not restart, make sure the status update does not transition + // any terminated containers to a non-terminated state. + allErrs = append(allErrs, ValidateContainerStateTransition(newPod.Status.ContainerStatuses, oldPod.Status.ContainerStatuses, fldPath.Child("containerStatuses"), oldPod.Spec.RestartPolicy)...) + allErrs = append(allErrs, ValidateContainerStateTransition(newPod.Status.InitContainerStatuses, oldPod.Status.InitContainerStatuses, fldPath.Child("initContainerStatuses"), oldPod.Spec.RestartPolicy)...) + + // For status update we ignore changes to pod spec. + newPod.Spec = oldPod.Spec + + return allErrs +} + +// ValidatePodBinding tests if required fields in the pod binding are legal. +func ValidatePodBinding(binding *core.Binding) field.ErrorList { + allErrs := field.ErrorList{} + + if len(binding.Target.Kind) != 0 && binding.Target.Kind != "Node" { + // TODO: When validation becomes versioned, this gets more complicated. + allErrs = append(allErrs, field.NotSupported(field.NewPath("target", "kind"), binding.Target.Kind, []string{"Node", ""})) + } + if len(binding.Target.Name) == 0 { + // TODO: When validation becomes versioned, this gets more complicated. + allErrs = append(allErrs, field.Required(field.NewPath("target", "name"), "")) + } + + return allErrs +} + +// ValidatePodTemplate tests if required fields in the pod template are set. +func ValidatePodTemplate(pod *core.PodTemplate) field.ErrorList { + allErrs := ValidateObjectMeta(&pod.ObjectMeta, true, ValidatePodName, field.NewPath("metadata")) + allErrs = append(allErrs, ValidatePodTemplateSpec(&pod.Template, field.NewPath("template"))...) + return allErrs +} + +// ValidatePodTemplateUpdate tests to see if the update is legal for an end user to make. newPod is updated with fields +// that cannot be changed. +func ValidatePodTemplateUpdate(newPod, oldPod *core.PodTemplate) field.ErrorList { + allErrs := ValidateObjectMetaUpdate(&oldPod.ObjectMeta, &newPod.ObjectMeta, field.NewPath("metadata")) + allErrs = append(allErrs, ValidatePodTemplateSpec(&newPod.Template, field.NewPath("template"))...) + return allErrs +} + +var supportedSessionAffinityType = sets.NewString(string(core.ServiceAffinityClientIP), string(core.ServiceAffinityNone)) +var supportedServiceType = sets.NewString(string(core.ServiceTypeClusterIP), string(core.ServiceTypeNodePort), + string(core.ServiceTypeLoadBalancer), string(core.ServiceTypeExternalName)) + +// ValidateService tests if required fields/annotations of a Service are valid. +func ValidateService(service *core.Service) field.ErrorList { + allErrs := ValidateObjectMeta(&service.ObjectMeta, true, ValidateServiceName, field.NewPath("metadata")) + + specPath := field.NewPath("spec") + isHeadlessService := service.Spec.ClusterIP == core.ClusterIPNone + if len(service.Spec.Ports) == 0 && !isHeadlessService && service.Spec.Type != core.ServiceTypeExternalName { + allErrs = append(allErrs, field.Required(specPath.Child("ports"), "")) + } + switch service.Spec.Type { + case core.ServiceTypeLoadBalancer: + for ix := range service.Spec.Ports { + port := &service.Spec.Ports[ix] + // This is a workaround for broken cloud environments that + // over-open firewalls. Hopefully it can go away when more clouds + // understand containers better. + if port.Port == ports.KubeletPort { + portPath := specPath.Child("ports").Index(ix) + allErrs = append(allErrs, field.Invalid(portPath, port.Port, fmt.Sprintf("may not expose port %v externally since it is used by kubelet", ports.KubeletPort))) + } + } + if service.Spec.ClusterIP == "None" { + allErrs = append(allErrs, field.Invalid(specPath.Child("clusterIP"), service.Spec.ClusterIP, "may not be set to 'None' for LoadBalancer services")) + } + case core.ServiceTypeNodePort: + if service.Spec.ClusterIP == "None" { + allErrs = append(allErrs, field.Invalid(specPath.Child("clusterIP"), service.Spec.ClusterIP, "may not be set to 'None' for NodePort services")) + } + case core.ServiceTypeExternalName: + if service.Spec.ClusterIP != "" { + allErrs = append(allErrs, field.Forbidden(specPath.Child("clusterIP"), "must be empty for ExternalName services")) + } + if len(service.Spec.ExternalName) > 0 { + allErrs = append(allErrs, ValidateDNS1123Subdomain(service.Spec.ExternalName, specPath.Child("externalName"))...) + } else { + allErrs = append(allErrs, field.Required(specPath.Child("externalName"), "")) + } + } + + allPortNames := sets.String{} + portsPath := specPath.Child("ports") + for i := range service.Spec.Ports { + portPath := portsPath.Index(i) + allErrs = append(allErrs, validateServicePort(&service.Spec.Ports[i], len(service.Spec.Ports) > 1, isHeadlessService, &allPortNames, portPath)...) + } + + if service.Spec.Selector != nil { + allErrs = append(allErrs, unversionedvalidation.ValidateLabels(service.Spec.Selector, specPath.Child("selector"))...) + } + + if len(service.Spec.SessionAffinity) == 0 { + allErrs = append(allErrs, field.Required(specPath.Child("sessionAffinity"), "")) + } else if !supportedSessionAffinityType.Has(string(service.Spec.SessionAffinity)) { + allErrs = append(allErrs, field.NotSupported(specPath.Child("sessionAffinity"), service.Spec.SessionAffinity, supportedSessionAffinityType.List())) + } + + if service.Spec.SessionAffinity == core.ServiceAffinityClientIP { + allErrs = append(allErrs, validateClientIPAffinityConfig(service.Spec.SessionAffinityConfig, specPath.Child("sessionAffinityConfig"))...) + } else if service.Spec.SessionAffinity == core.ServiceAffinityNone { + if service.Spec.SessionAffinityConfig != nil { + allErrs = append(allErrs, field.Forbidden(specPath.Child("sessionAffinityConfig"), fmt.Sprintf("must not be set when session affinity is %s", string(core.ServiceAffinityNone)))) + } + } + + if helper.IsServiceIPSet(service) { + if ip := net.ParseIP(service.Spec.ClusterIP); ip == nil { + allErrs = append(allErrs, field.Invalid(specPath.Child("clusterIP"), service.Spec.ClusterIP, "must be empty, 'None', or a valid IP address")) + } + } + + ipPath := specPath.Child("externalIPs") + for i, ip := range service.Spec.ExternalIPs { + idxPath := ipPath.Index(i) + if msgs := validation.IsValidIP(ip); len(msgs) != 0 { + for i := range msgs { + allErrs = append(allErrs, field.Invalid(idxPath, ip, msgs[i])) + } + } else { + allErrs = append(allErrs, validateNonSpecialIP(ip, idxPath)...) + } + } + + if len(service.Spec.Type) == 0 { + allErrs = append(allErrs, field.Required(specPath.Child("type"), "")) + } else if !supportedServiceType.Has(string(service.Spec.Type)) { + allErrs = append(allErrs, field.NotSupported(specPath.Child("type"), service.Spec.Type, supportedServiceType.List())) + } + + if service.Spec.Type == core.ServiceTypeLoadBalancer { + portsPath := specPath.Child("ports") + includeProtocols := sets.NewString() + for i := range service.Spec.Ports { + portPath := portsPath.Index(i) + if !supportedPortProtocols.Has(string(service.Spec.Ports[i].Protocol)) { + allErrs = append(allErrs, field.Invalid(portPath.Child("protocol"), service.Spec.Ports[i].Protocol, "cannot create an external load balancer with non-TCP/UDP ports")) + } else { + includeProtocols.Insert(string(service.Spec.Ports[i].Protocol)) + } + } + if includeProtocols.Len() > 1 { + allErrs = append(allErrs, field.Invalid(portsPath, service.Spec.Ports, "cannot create an external load balancer with mix protocols")) + } + } + + if service.Spec.Type == core.ServiceTypeClusterIP { + portsPath := specPath.Child("ports") + for i := range service.Spec.Ports { + portPath := portsPath.Index(i) + if service.Spec.Ports[i].NodePort != 0 { + allErrs = append(allErrs, field.Forbidden(portPath.Child("nodePort"), "may not be used when `type` is 'ClusterIP'")) + } + } + } + + // Check for duplicate NodePorts, considering (protocol,port) pairs + portsPath = specPath.Child("ports") + nodePorts := make(map[core.ServicePort]bool) + for i := range service.Spec.Ports { + port := &service.Spec.Ports[i] + if port.NodePort == 0 { + continue + } + portPath := portsPath.Index(i) + var key core.ServicePort + key.Protocol = port.Protocol + key.NodePort = port.NodePort + _, found := nodePorts[key] + if found { + allErrs = append(allErrs, field.Duplicate(portPath.Child("nodePort"), port.NodePort)) + } + nodePorts[key] = true + } + + // Check for duplicate Ports, considering (protocol,port) pairs + portsPath = specPath.Child("ports") + ports := make(map[core.ServicePort]bool) + for i, port := range service.Spec.Ports { + portPath := portsPath.Index(i) + key := core.ServicePort{Protocol: port.Protocol, Port: port.Port} + _, found := ports[key] + if found { + allErrs = append(allErrs, field.Duplicate(portPath, key)) + } + ports[key] = true + } + + // Validate SourceRange field and annotation + _, ok := service.Annotations[core.AnnotationLoadBalancerSourceRangesKey] + if len(service.Spec.LoadBalancerSourceRanges) > 0 || ok { + var fieldPath *field.Path + var val string + if len(service.Spec.LoadBalancerSourceRanges) > 0 { + fieldPath = specPath.Child("LoadBalancerSourceRanges") + val = fmt.Sprintf("%v", service.Spec.LoadBalancerSourceRanges) + } else { + fieldPath = field.NewPath("metadata", "annotations").Key(core.AnnotationLoadBalancerSourceRangesKey) + val = service.Annotations[core.AnnotationLoadBalancerSourceRangesKey] + } + if service.Spec.Type != core.ServiceTypeLoadBalancer { + allErrs = append(allErrs, field.Forbidden(fieldPath, "may only be used when `type` is 'LoadBalancer'")) + } + _, err := apiservice.GetLoadBalancerSourceRanges(service) + if err != nil { + allErrs = append(allErrs, field.Invalid(fieldPath, val, "must be a list of IP ranges. For example, 10.240.0.0/24,10.250.0.0/24 ")) + } + } + + allErrs = append(allErrs, validateServiceExternalTrafficFieldsValue(service)...) + + return allErrs +} + +func validateServicePort(sp *core.ServicePort, requireName, isHeadlessService bool, allNames *sets.String, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if requireName && len(sp.Name) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) + } else if len(sp.Name) != 0 { + allErrs = append(allErrs, ValidateDNS1123Label(sp.Name, fldPath.Child("name"))...) + if allNames.Has(sp.Name) { + allErrs = append(allErrs, field.Duplicate(fldPath.Child("name"), sp.Name)) + } else { + allNames.Insert(sp.Name) + } + } + + for _, msg := range validation.IsValidPortNum(int(sp.Port)) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("port"), sp.Port, msg)) + } + + if len(sp.Protocol) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("protocol"), "")) + } else if !supportedPortProtocols.Has(string(sp.Protocol)) { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("protocol"), sp.Protocol, supportedPortProtocols.List())) + } + + allErrs = append(allErrs, ValidatePortNumOrName(sp.TargetPort, fldPath.Child("targetPort"))...) + + // in the v1 API, targetPorts on headless services were tolerated. + // once we have version-specific validation, we can reject this on newer API versions, but until then, we have to tolerate it for compatibility. + // + // if isHeadlessService { + // if sp.TargetPort.Type == intstr.String || (sp.TargetPort.Type == intstr.Int && sp.Port != sp.TargetPort.IntValue()) { + // allErrs = append(allErrs, field.Invalid(fldPath.Child("targetPort"), sp.TargetPort, "must be equal to the value of 'port' when clusterIP = None")) + // } + // } + + return allErrs +} + +// validateServiceExternalTrafficFieldsValue validates ExternalTraffic related annotations +// have legal value. +func validateServiceExternalTrafficFieldsValue(service *core.Service) field.ErrorList { + allErrs := field.ErrorList{} + + // Check first class fields. + if service.Spec.ExternalTrafficPolicy != "" && + service.Spec.ExternalTrafficPolicy != core.ServiceExternalTrafficPolicyTypeCluster && + service.Spec.ExternalTrafficPolicy != core.ServiceExternalTrafficPolicyTypeLocal { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("externalTrafficPolicy"), service.Spec.ExternalTrafficPolicy, + fmt.Sprintf("ExternalTrafficPolicy must be empty, %v or %v", core.ServiceExternalTrafficPolicyTypeCluster, core.ServiceExternalTrafficPolicyTypeLocal))) + } + if service.Spec.HealthCheckNodePort < 0 { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("healthCheckNodePort"), service.Spec.HealthCheckNodePort, + "HealthCheckNodePort must be not less than 0")) + } + + return allErrs +} + +// ValidateServiceExternalTrafficFieldsCombination validates if ExternalTrafficPolicy, +// HealthCheckNodePort and Type combination are legal. For update, it should be called +// after clearing externalTraffic related fields for the ease of transitioning between +// different service types. +func ValidateServiceExternalTrafficFieldsCombination(service *core.Service) field.ErrorList { + allErrs := field.ErrorList{} + + if service.Spec.Type != core.ServiceTypeLoadBalancer && + service.Spec.Type != core.ServiceTypeNodePort && + service.Spec.ExternalTrafficPolicy != "" { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "externalTrafficPolicy"), service.Spec.ExternalTrafficPolicy, + "ExternalTrafficPolicy can only be set on NodePort and LoadBalancer service")) + } + + if !apiservice.NeedsHealthCheck(service) && + service.Spec.HealthCheckNodePort != 0 { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "healthCheckNodePort"), service.Spec.HealthCheckNodePort, + "HealthCheckNodePort can only be set on LoadBalancer service with ExternalTrafficPolicy=Local")) + } + + return allErrs +} + +// ValidateServiceUpdate tests if required fields in the service are set during an update +func ValidateServiceUpdate(service, oldService *core.Service) field.ErrorList { + allErrs := ValidateObjectMetaUpdate(&service.ObjectMeta, &oldService.ObjectMeta, field.NewPath("metadata")) + + // ClusterIP should be immutable for services using it (every type other than ExternalName) + // which do not have ClusterIP assigned yet (empty string value) + if service.Spec.Type != core.ServiceTypeExternalName { + if oldService.Spec.Type != core.ServiceTypeExternalName && oldService.Spec.ClusterIP != "" { + allErrs = append(allErrs, ValidateImmutableField(service.Spec.ClusterIP, oldService.Spec.ClusterIP, field.NewPath("spec", "clusterIP"))...) + } + } + + allErrs = append(allErrs, ValidateService(service)...) + return allErrs +} + +// ValidateServiceStatusUpdate tests if required fields in the Service are set when updating status. +func ValidateServiceStatusUpdate(service, oldService *core.Service) field.ErrorList { + allErrs := ValidateObjectMetaUpdate(&service.ObjectMeta, &oldService.ObjectMeta, field.NewPath("metadata")) + allErrs = append(allErrs, ValidateLoadBalancerStatus(&service.Status.LoadBalancer, field.NewPath("status", "loadBalancer"))...) + return allErrs +} + +// ValidateReplicationController tests if required fields in the replication controller are set. +func ValidateReplicationController(controller *core.ReplicationController) field.ErrorList { + allErrs := ValidateObjectMeta(&controller.ObjectMeta, true, ValidateReplicationControllerName, field.NewPath("metadata")) + allErrs = append(allErrs, ValidateReplicationControllerSpec(&controller.Spec, field.NewPath("spec"))...) + return allErrs +} + +// ValidateReplicationControllerUpdate tests if required fields in the replication controller are set. +func ValidateReplicationControllerUpdate(controller, oldController *core.ReplicationController) field.ErrorList { + allErrs := ValidateObjectMetaUpdate(&controller.ObjectMeta, &oldController.ObjectMeta, field.NewPath("metadata")) + allErrs = append(allErrs, ValidateReplicationControllerSpec(&controller.Spec, field.NewPath("spec"))...) + return allErrs +} + +// ValidateReplicationControllerStatusUpdate tests if required fields in the replication controller are set. +func ValidateReplicationControllerStatusUpdate(controller, oldController *core.ReplicationController) field.ErrorList { + allErrs := ValidateObjectMetaUpdate(&controller.ObjectMeta, &oldController.ObjectMeta, field.NewPath("metadata")) + allErrs = append(allErrs, ValidateReplicationControllerStatus(controller.Status, field.NewPath("status"))...) + return allErrs +} + +func ValidateReplicationControllerStatus(status core.ReplicationControllerStatus, statusPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, ValidateNonnegativeField(int64(status.Replicas), statusPath.Child("replicas"))...) + allErrs = append(allErrs, ValidateNonnegativeField(int64(status.FullyLabeledReplicas), statusPath.Child("fullyLabeledReplicas"))...) + allErrs = append(allErrs, ValidateNonnegativeField(int64(status.ReadyReplicas), statusPath.Child("readyReplicas"))...) + allErrs = append(allErrs, ValidateNonnegativeField(int64(status.AvailableReplicas), statusPath.Child("availableReplicas"))...) + allErrs = append(allErrs, ValidateNonnegativeField(int64(status.ObservedGeneration), statusPath.Child("observedGeneration"))...) + msg := "cannot be greater than status.replicas" + if status.FullyLabeledReplicas > status.Replicas { + allErrs = append(allErrs, field.Invalid(statusPath.Child("fullyLabeledReplicas"), status.FullyLabeledReplicas, msg)) + } + if status.ReadyReplicas > status.Replicas { + allErrs = append(allErrs, field.Invalid(statusPath.Child("readyReplicas"), status.ReadyReplicas, msg)) + } + if status.AvailableReplicas > status.Replicas { + allErrs = append(allErrs, field.Invalid(statusPath.Child("availableReplicas"), status.AvailableReplicas, msg)) + } + if status.AvailableReplicas > status.ReadyReplicas { + allErrs = append(allErrs, field.Invalid(statusPath.Child("availableReplicas"), status.AvailableReplicas, "cannot be greater than readyReplicas")) + } + return allErrs +} + +// Validates that the given selector is non-empty. +func ValidateNonEmptySelector(selectorMap map[string]string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + selector := labels.Set(selectorMap).AsSelector() + if selector.Empty() { + allErrs = append(allErrs, field.Required(fldPath, "")) + } + return allErrs +} + +// Validates the given template and ensures that it is in accordance with the desired selector and replicas. +func ValidatePodTemplateSpecForRC(template *core.PodTemplateSpec, selectorMap map[string]string, replicas int32, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if template == nil { + allErrs = append(allErrs, field.Required(fldPath, "")) + } else { + selector := labels.Set(selectorMap).AsSelector() + if !selector.Empty() { + // Verify that the RC selector matches the labels in template. + labels := labels.Set(template.Labels) + if !selector.Matches(labels) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("metadata", "labels"), template.Labels, "`selector` does not match template `labels`")) + } + } + allErrs = append(allErrs, ValidatePodTemplateSpec(template, fldPath)...) + if replicas > 1 { + allErrs = append(allErrs, ValidateReadOnlyPersistentDisks(template.Spec.Volumes, fldPath.Child("spec", "volumes"))...) + } + // RestartPolicy has already been first-order validated as per ValidatePodTemplateSpec(). + if template.Spec.RestartPolicy != core.RestartPolicyAlways { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("spec", "restartPolicy"), template.Spec.RestartPolicy, []string{string(core.RestartPolicyAlways)})) + } + if template.Spec.ActiveDeadlineSeconds != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("spec", "activeDeadlineSeconds"), template.Spec.ActiveDeadlineSeconds, "must not be specified")) + } + } + return allErrs +} + +// ValidateReplicationControllerSpec tests if required fields in the replication controller spec are set. +func ValidateReplicationControllerSpec(spec *core.ReplicationControllerSpec, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, ValidateNonnegativeField(int64(spec.MinReadySeconds), fldPath.Child("minReadySeconds"))...) + allErrs = append(allErrs, ValidateNonEmptySelector(spec.Selector, fldPath.Child("selector"))...) + allErrs = append(allErrs, ValidateNonnegativeField(int64(spec.Replicas), fldPath.Child("replicas"))...) + allErrs = append(allErrs, ValidatePodTemplateSpecForRC(spec.Template, spec.Selector, spec.Replicas, fldPath.Child("template"))...) + return allErrs +} + +// ValidatePodTemplateSpec validates the spec of a pod template +func ValidatePodTemplateSpec(spec *core.PodTemplateSpec, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, unversionedvalidation.ValidateLabels(spec.Labels, fldPath.Child("labels"))...) + allErrs = append(allErrs, ValidateAnnotations(spec.Annotations, fldPath.Child("annotations"))...) + allErrs = append(allErrs, ValidatePodSpecificAnnotations(spec.Annotations, &spec.Spec, fldPath.Child("annotations"))...) + allErrs = append(allErrs, ValidatePodSpec(&spec.Spec, fldPath.Child("spec"))...) + return allErrs +} + +func ValidateReadOnlyPersistentDisks(volumes []core.Volume, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for i := range volumes { + vol := &volumes[i] + idxPath := fldPath.Index(i) + if vol.GCEPersistentDisk != nil { + if vol.GCEPersistentDisk.ReadOnly == false { + allErrs = append(allErrs, field.Invalid(idxPath.Child("gcePersistentDisk", "readOnly"), false, "must be true for replicated pods > 1; GCE PD can only be mounted on multiple machines if it is read-only")) + } + } + // TODO: What to do for AWS? It doesn't support replicas + } + return allErrs +} + +// ValidateTaintsInNodeAnnotations tests that the serialized taints in Node.Annotations has valid data +func ValidateTaintsInNodeAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + taints, err := helper.GetTaintsFromNodeAnnotations(annotations) + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath, core.TaintsAnnotationKey, err.Error())) + return allErrs + } + + if len(taints) > 0 { + allErrs = append(allErrs, validateNodeTaints(taints, fldPath.Child(core.TaintsAnnotationKey))...) + } + + return allErrs +} + +// validateNodeTaints tests if given taints have valid data. +func validateNodeTaints(taints []core.Taint, fldPath *field.Path) field.ErrorList { + allErrors := field.ErrorList{} + + uniqueTaints := map[core.TaintEffect]sets.String{} + + for i, currTaint := range taints { + idxPath := fldPath.Index(i) + // validate the taint key + allErrors = append(allErrors, unversionedvalidation.ValidateLabelName(currTaint.Key, idxPath.Child("key"))...) + // validate the taint value + if errs := validation.IsValidLabelValue(currTaint.Value); len(errs) != 0 { + allErrors = append(allErrors, field.Invalid(idxPath.Child("value"), currTaint.Value, strings.Join(errs, ";"))) + } + // validate the taint effect + allErrors = append(allErrors, validateTaintEffect(&currTaint.Effect, false, idxPath.Child("effect"))...) + + // validate if taint is unique by + if len(uniqueTaints[currTaint.Effect]) > 0 && uniqueTaints[currTaint.Effect].Has(currTaint.Key) { + duplicatedError := field.Duplicate(idxPath, currTaint) + duplicatedError.Detail = "taints must be unique by key and effect pair" + allErrors = append(allErrors, duplicatedError) + continue + } + + // add taint to existingTaints for uniqueness check + if len(uniqueTaints[currTaint.Effect]) == 0 { + uniqueTaints[currTaint.Effect] = sets.String{} + } + uniqueTaints[currTaint.Effect].Insert(currTaint.Key) + } + return allErrors +} + +func ValidateNodeSpecificAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if annotations[core.TaintsAnnotationKey] != "" { + allErrs = append(allErrs, ValidateTaintsInNodeAnnotations(annotations, fldPath)...) + } + + if annotations[core.PreferAvoidPodsAnnotationKey] != "" { + allErrs = append(allErrs, ValidateAvoidPodsInNodeAnnotations(annotations, fldPath)...) + } + return allErrs +} + +// ValidateNode tests if required fields in the node are set. +func ValidateNode(node *core.Node) field.ErrorList { + fldPath := field.NewPath("metadata") + allErrs := ValidateObjectMeta(&node.ObjectMeta, false, ValidateNodeName, fldPath) + allErrs = append(allErrs, ValidateNodeSpecificAnnotations(node.ObjectMeta.Annotations, fldPath.Child("annotations"))...) + if len(node.Spec.Taints) > 0 { + allErrs = append(allErrs, validateNodeTaints(node.Spec.Taints, fldPath.Child("taints"))...) + } + + // Only validate spec. + // All status fields are optional and can be updated later. + // That said, if specified, we need to ensure they are valid. + allErrs = append(allErrs, ValidateNodeResources(node)...) + + // external ID is required. + if len(node.Spec.ExternalID) == 0 { + allErrs = append(allErrs, field.Required(field.NewPath("spec", "externalID"), "")) + } + + // Only allow Node.Spec.ConfigSource to be set if the DynamicKubeletConfig feature gate is enabled + if node.Spec.ConfigSource != nil && !utilfeature.DefaultFeatureGate.Enabled(features.DynamicKubeletConfig) { + allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "configSource"), "configSource may only be set if the DynamicKubeletConfig feature gate is enabled)")) + } + + if len(node.Spec.PodCIDR) != 0 { + _, err := ValidateCIDR(node.Spec.PodCIDR) + if err != nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "podCIDR"), node.Spec.PodCIDR, "not a valid CIDR")) + } + } + return allErrs +} + +// ValidateNodeResources is used to make sure a node has valid capacity and allocatable values. +func ValidateNodeResources(node *core.Node) field.ErrorList { + allErrs := field.ErrorList{} + // Validate resource quantities in capacity. + hugePageSizes := sets.NewString() + for k, v := range node.Status.Capacity { + resPath := field.NewPath("status", "capacity", string(k)) + allErrs = append(allErrs, ValidateResourceQuantityValue(string(k), v, resPath)...) + // track any huge page size that has a positive value + if helper.IsHugePageResourceName(k) && v.Value() > int64(0) { + hugePageSizes.Insert(string(k)) + } + if len(hugePageSizes) > 1 { + allErrs = append(allErrs, field.Invalid(resPath, v, "may not have pre-allocated hugepages for multiple page sizes")) + } + } + // Validate resource quantities in allocatable. + hugePageSizes = sets.NewString() + for k, v := range node.Status.Allocatable { + resPath := field.NewPath("status", "allocatable", string(k)) + allErrs = append(allErrs, ValidateResourceQuantityValue(string(k), v, resPath)...) + // track any huge page size that has a positive value + if helper.IsHugePageResourceName(k) && v.Value() > int64(0) { + hugePageSizes.Insert(string(k)) + } + if len(hugePageSizes) > 1 { + allErrs = append(allErrs, field.Invalid(resPath, v, "may not have pre-allocated hugepages for multiple page sizes")) + } + } + return allErrs +} + +// ValidateNodeUpdate tests to make sure a node update can be applied. Modifies oldNode. +func ValidateNodeUpdate(node, oldNode *core.Node) field.ErrorList { + fldPath := field.NewPath("metadata") + allErrs := ValidateObjectMetaUpdate(&node.ObjectMeta, &oldNode.ObjectMeta, fldPath) + allErrs = append(allErrs, ValidateNodeSpecificAnnotations(node.ObjectMeta.Annotations, fldPath.Child("annotations"))...) + + // TODO: Enable the code once we have better core object.status update model. Currently, + // anyone can update node status. + // if !apiequality.Semantic.DeepEqual(node.Status, core.NodeStatus{}) { + // allErrs = append(allErrs, field.Invalid("status", node.Status, "must be empty")) + // } + + allErrs = append(allErrs, ValidateNodeResources(node)...) + + // Validate no duplicate addresses in node status. + addresses := make(map[core.NodeAddress]bool) + for i, address := range node.Status.Addresses { + if _, ok := addresses[address]; ok { + allErrs = append(allErrs, field.Duplicate(field.NewPath("status", "addresses").Index(i), address)) + } + addresses[address] = true + } + + if len(oldNode.Spec.PodCIDR) == 0 { + // Allow the controller manager to assign a CIDR to a node if it doesn't have one. + oldNode.Spec.PodCIDR = node.Spec.PodCIDR + } else { + if oldNode.Spec.PodCIDR != node.Spec.PodCIDR { + allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "podCIDR"), "node updates may not change podCIDR except from \"\" to valid")) + } + } + + // Allow controller manager updating provider ID when not set + if len(oldNode.Spec.ProviderID) == 0 { + oldNode.Spec.ProviderID = node.Spec.ProviderID + } else { + if oldNode.Spec.ProviderID != node.Spec.ProviderID { + allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "providerID"), "node updates may not change providerID except from \"\" to valid")) + } + } + + // TODO: move reset function to its own location + // Ignore metadata changes now that they have been tested + oldNode.ObjectMeta = node.ObjectMeta + // Allow users to update capacity + oldNode.Status.Capacity = node.Status.Capacity + // Allow users to unschedule node + oldNode.Spec.Unschedulable = node.Spec.Unschedulable + // Clear status + oldNode.Status = node.Status + + // update taints + if len(node.Spec.Taints) > 0 { + allErrs = append(allErrs, validateNodeTaints(node.Spec.Taints, fldPath.Child("taints"))...) + } + oldNode.Spec.Taints = node.Spec.Taints + + // Allow updates to Node.Spec.ConfigSource if DynamicKubeletConfig feature gate is enabled + if utilfeature.DefaultFeatureGate.Enabled(features.DynamicKubeletConfig) { + oldNode.Spec.ConfigSource = node.Spec.ConfigSource + } + + // We made allowed changes to oldNode, and now we compare oldNode to node. Any remaining differences indicate changes to protected fields. + // TODO: Add a 'real' error type for this error and provide print actual diffs. + if !apiequality.Semantic.DeepEqual(oldNode, node) { + glog.V(4).Infof("Update failed validation %#v vs %#v", oldNode, node) + allErrs = append(allErrs, field.Forbidden(field.NewPath(""), "node updates may only change labels, taints, or capacity (or configSource, if the DynamicKubeletConfig feature gate is enabled)")) + } + + return allErrs +} + +// Validate compute resource typename. +// Refer to docs/design/resources.md for more details. +func validateResourceName(value string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for _, msg := range validation.IsQualifiedName(value) { + allErrs = append(allErrs, field.Invalid(fldPath, value, msg)) + } + if len(allErrs) != 0 { + return allErrs + } + + if len(strings.Split(value, "/")) == 1 { + if !helper.IsStandardResourceName(value) { + return append(allErrs, field.Invalid(fldPath, value, "must be a standard resource type or fully qualified")) + } + } + + return allErrs +} + +// Validate container resource name +// Refer to docs/design/resources.md for more details. +func validateContainerResourceName(value string, fldPath *field.Path) field.ErrorList { + allErrs := validateResourceName(value, fldPath) + + if len(strings.Split(value, "/")) == 1 { + if !helper.IsStandardContainerResourceName(value) { + return append(allErrs, field.Invalid(fldPath, value, "must be a standard resource for containers")) + } + } + return allErrs +} + +// isLocalStorageResource checks whether the resource is local ephemeral storage +func isLocalStorageResource(name string) bool { + if name == string(core.ResourceEphemeralStorage) || name == string(core.ResourceRequestsEphemeralStorage) || + name == string(core.ResourceLimitsEphemeralStorage) { + return true + } else { + return false + } +} + +// Validate resource names that can go in a resource quota +// Refer to docs/design/resources.md for more details. +func ValidateResourceQuotaResourceName(value string, fldPath *field.Path) field.ErrorList { + allErrs := validateResourceName(value, fldPath) + if isLocalStorageResource(value) && !utilfeature.DefaultFeatureGate.Enabled(features.LocalStorageCapacityIsolation) { + return append(allErrs, field.Forbidden(fldPath, "ResourceEphemeralStorage field disabled by feature-gate for ResourceQuota")) + } + if len(strings.Split(value, "/")) == 1 { + if !helper.IsStandardQuotaResourceName(value) { + return append(allErrs, field.Invalid(fldPath, value, isInvalidQuotaResource)) + } + } + return allErrs +} + +// Validate limit range types +func validateLimitRangeTypeName(value string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for _, msg := range validation.IsQualifiedName(value) { + allErrs = append(allErrs, field.Invalid(fldPath, value, msg)) + } + if len(allErrs) != 0 { + return allErrs + } + + if len(strings.Split(value, "/")) == 1 { + if !helper.IsStandardLimitRangeType(value) { + return append(allErrs, field.Invalid(fldPath, value, "must be a standard limit type or fully qualified")) + } + } + + return allErrs +} + +// Validate limit range resource name +// limit types (other than Pod/Container) could contain storage not just cpu or memory +func validateLimitRangeResourceName(limitType core.LimitType, value string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if value == string(core.ResourceEphemeralStorage) && !utilfeature.DefaultFeatureGate.Enabled(features.LocalStorageCapacityIsolation) { + return append(allErrs, field.Forbidden(fldPath, "ResourceEphemeralStorage field disabled by feature-gate for Resource LimitRange")) + } + switch limitType { + case core.LimitTypePod, core.LimitTypeContainer: + return validateContainerResourceName(value, fldPath) + default: + return validateResourceName(value, fldPath) + } +} + +// ValidateLimitRange tests if required fields in the LimitRange are set. +func ValidateLimitRange(limitRange *core.LimitRange) field.ErrorList { + allErrs := ValidateObjectMeta(&limitRange.ObjectMeta, true, ValidateLimitRangeName, field.NewPath("metadata")) + + // ensure resource names are properly qualified per docs/design/resources.md + limitTypeSet := map[core.LimitType]bool{} + fldPath := field.NewPath("spec", "limits") + for i := range limitRange.Spec.Limits { + idxPath := fldPath.Index(i) + limit := &limitRange.Spec.Limits[i] + allErrs = append(allErrs, validateLimitRangeTypeName(string(limit.Type), idxPath.Child("type"))...) + + _, found := limitTypeSet[limit.Type] + if found { + allErrs = append(allErrs, field.Duplicate(idxPath.Child("type"), limit.Type)) + } + limitTypeSet[limit.Type] = true + + keys := sets.String{} + min := map[string]resource.Quantity{} + max := map[string]resource.Quantity{} + defaults := map[string]resource.Quantity{} + defaultRequests := map[string]resource.Quantity{} + maxLimitRequestRatios := map[string]resource.Quantity{} + + for k, q := range limit.Max { + allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, string(k), idxPath.Child("max").Key(string(k)))...) + keys.Insert(string(k)) + max[string(k)] = q + } + for k, q := range limit.Min { + allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, string(k), idxPath.Child("min").Key(string(k)))...) + keys.Insert(string(k)) + min[string(k)] = q + } + + if limit.Type == core.LimitTypePod { + if len(limit.Default) > 0 { + allErrs = append(allErrs, field.Forbidden(idxPath.Child("default"), "may not be specified when `type` is 'Pod'")) + } + if len(limit.DefaultRequest) > 0 { + allErrs = append(allErrs, field.Forbidden(idxPath.Child("defaultRequest"), "may not be specified when `type` is 'Pod'")) + } + } else { + for k, q := range limit.Default { + allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, string(k), idxPath.Child("default").Key(string(k)))...) + keys.Insert(string(k)) + defaults[string(k)] = q + } + for k, q := range limit.DefaultRequest { + allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, string(k), idxPath.Child("defaultRequest").Key(string(k)))...) + keys.Insert(string(k)) + defaultRequests[string(k)] = q + } + } + + if limit.Type == core.LimitTypePersistentVolumeClaim { + _, minQuantityFound := limit.Min[core.ResourceStorage] + _, maxQuantityFound := limit.Max[core.ResourceStorage] + if !minQuantityFound && !maxQuantityFound { + allErrs = append(allErrs, field.Required(idxPath.Child("limits"), "either minimum or maximum storage value is required, but neither was provided")) + } + } + + for k, q := range limit.MaxLimitRequestRatio { + allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, string(k), idxPath.Child("maxLimitRequestRatio").Key(string(k)))...) + keys.Insert(string(k)) + maxLimitRequestRatios[string(k)] = q + } + + for k := range keys { + minQuantity, minQuantityFound := min[k] + maxQuantity, maxQuantityFound := max[k] + defaultQuantity, defaultQuantityFound := defaults[k] + defaultRequestQuantity, defaultRequestQuantityFound := defaultRequests[k] + maxRatio, maxRatioFound := maxLimitRequestRatios[k] + + if minQuantityFound && maxQuantityFound && minQuantity.Cmp(maxQuantity) > 0 { + allErrs = append(allErrs, field.Invalid(idxPath.Child("min").Key(string(k)), minQuantity, fmt.Sprintf("min value %s is greater than max value %s", minQuantity.String(), maxQuantity.String()))) + } + + if defaultRequestQuantityFound && minQuantityFound && minQuantity.Cmp(defaultRequestQuantity) > 0 { + allErrs = append(allErrs, field.Invalid(idxPath.Child("defaultRequest").Key(string(k)), defaultRequestQuantity, fmt.Sprintf("min value %s is greater than default request value %s", minQuantity.String(), defaultRequestQuantity.String()))) + } + + if defaultRequestQuantityFound && maxQuantityFound && defaultRequestQuantity.Cmp(maxQuantity) > 0 { + allErrs = append(allErrs, field.Invalid(idxPath.Child("defaultRequest").Key(string(k)), defaultRequestQuantity, fmt.Sprintf("default request value %s is greater than max value %s", defaultRequestQuantity.String(), maxQuantity.String()))) + } + + if defaultRequestQuantityFound && defaultQuantityFound && defaultRequestQuantity.Cmp(defaultQuantity) > 0 { + allErrs = append(allErrs, field.Invalid(idxPath.Child("defaultRequest").Key(string(k)), defaultRequestQuantity, fmt.Sprintf("default request value %s is greater than default limit value %s", defaultRequestQuantity.String(), defaultQuantity.String()))) + } + + if defaultQuantityFound && minQuantityFound && minQuantity.Cmp(defaultQuantity) > 0 { + allErrs = append(allErrs, field.Invalid(idxPath.Child("default").Key(string(k)), minQuantity, fmt.Sprintf("min value %s is greater than default value %s", minQuantity.String(), defaultQuantity.String()))) + } + + if defaultQuantityFound && maxQuantityFound && defaultQuantity.Cmp(maxQuantity) > 0 { + allErrs = append(allErrs, field.Invalid(idxPath.Child("default").Key(string(k)), maxQuantity, fmt.Sprintf("default value %s is greater than max value %s", defaultQuantity.String(), maxQuantity.String()))) + } + if maxRatioFound && maxRatio.Cmp(*resource.NewQuantity(1, resource.DecimalSI)) < 0 { + allErrs = append(allErrs, field.Invalid(idxPath.Child("maxLimitRequestRatio").Key(string(k)), maxRatio, fmt.Sprintf("ratio %s is less than 1", maxRatio.String()))) + } + if maxRatioFound && minQuantityFound && maxQuantityFound { + maxRatioValue := float64(maxRatio.Value()) + minQuantityValue := minQuantity.Value() + maxQuantityValue := maxQuantity.Value() + if maxRatio.Value() < resource.MaxMilliValue && minQuantityValue < resource.MaxMilliValue && maxQuantityValue < resource.MaxMilliValue { + maxRatioValue = float64(maxRatio.MilliValue()) / 1000 + minQuantityValue = minQuantity.MilliValue() + maxQuantityValue = maxQuantity.MilliValue() + } + maxRatioLimit := float64(maxQuantityValue) / float64(minQuantityValue) + if maxRatioValue > maxRatioLimit { + allErrs = append(allErrs, field.Invalid(idxPath.Child("maxLimitRequestRatio").Key(string(k)), maxRatio, fmt.Sprintf("ratio %s is greater than max/min = %f", maxRatio.String(), maxRatioLimit))) + } + } + + // for GPU and hugepages, the default value and defaultRequest value must match if both are specified + if !helper.IsOvercommitAllowed(core.ResourceName(k)) && defaultQuantityFound && defaultRequestQuantityFound && defaultQuantity.Cmp(defaultRequestQuantity) != 0 { + allErrs = append(allErrs, field.Invalid(idxPath.Child("defaultRequest").Key(string(k)), defaultRequestQuantity, fmt.Sprintf("default value %s must equal to defaultRequest value %s in %s", defaultQuantity.String(), defaultRequestQuantity.String(), k))) + } + } + } + + return allErrs +} + +// ValidateServiceAccount tests if required fields in the ServiceAccount are set. +func ValidateServiceAccount(serviceAccount *core.ServiceAccount) field.ErrorList { + allErrs := ValidateObjectMeta(&serviceAccount.ObjectMeta, true, ValidateServiceAccountName, field.NewPath("metadata")) + return allErrs +} + +// ValidateServiceAccountUpdate tests if required fields in the ServiceAccount are set. +func ValidateServiceAccountUpdate(newServiceAccount, oldServiceAccount *core.ServiceAccount) field.ErrorList { + allErrs := ValidateObjectMetaUpdate(&newServiceAccount.ObjectMeta, &oldServiceAccount.ObjectMeta, field.NewPath("metadata")) + allErrs = append(allErrs, ValidateServiceAccount(newServiceAccount)...) + return allErrs +} + +// ValidateSecret tests if required fields in the Secret are set. +func ValidateSecret(secret *core.Secret) field.ErrorList { + allErrs := ValidateObjectMeta(&secret.ObjectMeta, true, ValidateSecretName, field.NewPath("metadata")) + + dataPath := field.NewPath("data") + totalSize := 0 + for key, value := range secret.Data { + for _, msg := range validation.IsConfigMapKey(key) { + allErrs = append(allErrs, field.Invalid(dataPath.Key(key), key, msg)) + } + totalSize += len(value) + } + if totalSize > core.MaxSecretSize { + allErrs = append(allErrs, field.TooLong(dataPath, "", core.MaxSecretSize)) + } + + switch secret.Type { + case core.SecretTypeServiceAccountToken: + // Only require Annotations[kubernetes.io/service-account.name] + // Additional fields (like Annotations[kubernetes.io/service-account.uid] and Data[token]) might be contributed later by a controller loop + if value := secret.Annotations[core.ServiceAccountNameKey]; len(value) == 0 { + allErrs = append(allErrs, field.Required(field.NewPath("metadata", "annotations").Key(core.ServiceAccountNameKey), "")) + } + case core.SecretTypeOpaque, "": + // no-op + case core.SecretTypeDockercfg: + dockercfgBytes, exists := secret.Data[core.DockerConfigKey] + if !exists { + allErrs = append(allErrs, field.Required(dataPath.Key(core.DockerConfigKey), "")) + break + } + + // make sure that the content is well-formed json. + if err := json.Unmarshal(dockercfgBytes, &map[string]interface{}{}); err != nil { + allErrs = append(allErrs, field.Invalid(dataPath.Key(core.DockerConfigKey), "", err.Error())) + } + case core.SecretTypeDockerConfigJson: + dockerConfigJsonBytes, exists := secret.Data[core.DockerConfigJsonKey] + if !exists { + allErrs = append(allErrs, field.Required(dataPath.Key(core.DockerConfigJsonKey), "")) + break + } + + // make sure that the content is well-formed json. + if err := json.Unmarshal(dockerConfigJsonBytes, &map[string]interface{}{}); err != nil { + allErrs = append(allErrs, field.Invalid(dataPath.Key(core.DockerConfigJsonKey), "", err.Error())) + } + case core.SecretTypeBasicAuth: + _, usernameFieldExists := secret.Data[core.BasicAuthUsernameKey] + _, passwordFieldExists := secret.Data[core.BasicAuthPasswordKey] + + // username or password might be empty, but the field must be present + if !usernameFieldExists && !passwordFieldExists { + allErrs = append(allErrs, field.Required(field.NewPath("data[%s]").Key(core.BasicAuthUsernameKey), "")) + allErrs = append(allErrs, field.Required(field.NewPath("data[%s]").Key(core.BasicAuthPasswordKey), "")) + break + } + case core.SecretTypeSSHAuth: + if len(secret.Data[core.SSHAuthPrivateKey]) == 0 { + allErrs = append(allErrs, field.Required(field.NewPath("data[%s]").Key(core.SSHAuthPrivateKey), "")) + break + } + + case core.SecretTypeTLS: + if _, exists := secret.Data[core.TLSCertKey]; !exists { + allErrs = append(allErrs, field.Required(dataPath.Key(core.TLSCertKey), "")) + } + if _, exists := secret.Data[core.TLSPrivateKeyKey]; !exists { + allErrs = append(allErrs, field.Required(dataPath.Key(core.TLSPrivateKeyKey), "")) + } + // TODO: Verify that the key matches the cert. + default: + // no-op + } + + return allErrs +} + +// ValidateSecretUpdate tests if required fields in the Secret are set. +func ValidateSecretUpdate(newSecret, oldSecret *core.Secret) field.ErrorList { + allErrs := ValidateObjectMetaUpdate(&newSecret.ObjectMeta, &oldSecret.ObjectMeta, field.NewPath("metadata")) + + if len(newSecret.Type) == 0 { + newSecret.Type = oldSecret.Type + } + + allErrs = append(allErrs, ValidateImmutableField(newSecret.Type, oldSecret.Type, field.NewPath("type"))...) + + allErrs = append(allErrs, ValidateSecret(newSecret)...) + return allErrs +} + +// ValidateConfigMapName can be used to check whether the given ConfigMap name is valid. +// Prefix indicates this name will be used as part of generation, in which case +// trailing dashes are allowed. +var ValidateConfigMapName = NameIsDNSSubdomain + +// ValidateConfigMap tests whether required fields in the ConfigMap are set. +func ValidateConfigMap(cfg *core.ConfigMap) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, ValidateObjectMeta(&cfg.ObjectMeta, true, ValidateConfigMapName, field.NewPath("metadata"))...) + + totalSize := 0 + + for key, value := range cfg.Data { + for _, msg := range validation.IsConfigMapKey(key) { + allErrs = append(allErrs, field.Invalid(field.NewPath("data").Key(key), key, msg)) + } + totalSize += len(value) + } + if totalSize > core.MaxSecretSize { + allErrs = append(allErrs, field.TooLong(field.NewPath("data"), "", core.MaxSecretSize)) + } + + return allErrs +} + +// ValidateConfigMapUpdate tests if required fields in the ConfigMap are set. +func ValidateConfigMapUpdate(newCfg, oldCfg *core.ConfigMap) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, ValidateObjectMetaUpdate(&newCfg.ObjectMeta, &oldCfg.ObjectMeta, field.NewPath("metadata"))...) + allErrs = append(allErrs, ValidateConfigMap(newCfg)...) + + return allErrs +} + +func validateBasicResource(quantity resource.Quantity, fldPath *field.Path) field.ErrorList { + if quantity.Value() < 0 { + return field.ErrorList{field.Invalid(fldPath, quantity.Value(), "must be a valid resource quantity")} + } + return field.ErrorList{} +} + +// Validates resource requirement spec. +func ValidateResourceRequirements(requirements *core.ResourceRequirements, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + limPath := fldPath.Child("limits") + reqPath := fldPath.Child("requests") + limContainsCpuOrMemory := false + reqContainsCpuOrMemory := false + limContainsHugePages := false + reqContainsHugePages := false + supportedQoSComputeResources := sets.NewString(string(core.ResourceCPU), string(core.ResourceMemory)) + for resourceName, quantity := range requirements.Limits { + + fldPath := limPath.Key(string(resourceName)) + // Validate resource name. + allErrs = append(allErrs, validateContainerResourceName(string(resourceName), fldPath)...) + + // Validate resource quantity. + allErrs = append(allErrs, ValidateResourceQuantityValue(string(resourceName), quantity, fldPath)...) + + if resourceName == core.ResourceEphemeralStorage && !utilfeature.DefaultFeatureGate.Enabled(features.LocalStorageCapacityIsolation) { + allErrs = append(allErrs, field.Forbidden(limPath, "ResourceEphemeralStorage field disabled by feature-gate for ResourceRequirements")) + } + if helper.IsHugePageResourceName(resourceName) { + if !utilfeature.DefaultFeatureGate.Enabled(features.HugePages) { + allErrs = append(allErrs, field.Forbidden(limPath, fmt.Sprintf("%s field disabled by feature-gate for ResourceRequirements", resourceName))) + } else { + limContainsHugePages = true + } + } + + if supportedQoSComputeResources.Has(string(resourceName)) { + limContainsCpuOrMemory = true + } + } + for resourceName, quantity := range requirements.Requests { + fldPath := reqPath.Key(string(resourceName)) + // Validate resource name. + allErrs = append(allErrs, validateContainerResourceName(string(resourceName), fldPath)...) + // Validate resource quantity. + allErrs = append(allErrs, ValidateResourceQuantityValue(string(resourceName), quantity, fldPath)...) + + // Check that request <= limit. + limitQuantity, exists := requirements.Limits[resourceName] + if exists { + // For non overcommitable resources, not only requests can't exceed limits, they also can't be lower, i.e. must be equal. + if quantity.Cmp(limitQuantity) != 0 && !helper.IsOvercommitAllowed(resourceName) { + allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be equal to %s limit", resourceName))) + } else if quantity.Cmp(limitQuantity) > 0 { + allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be less than or equal to %s limit", resourceName))) + } + } else if !helper.IsOvercommitAllowed(resourceName) { + allErrs = append(allErrs, field.Required(limPath, "Limit must be set for non overcommitable resources")) + } + if helper.IsHugePageResourceName(resourceName) { + reqContainsHugePages = true + } + if supportedQoSComputeResources.Has(string(resourceName)) { + reqContainsCpuOrMemory = true + } + + } + if !limContainsCpuOrMemory && !reqContainsCpuOrMemory && (reqContainsHugePages || limContainsHugePages) { + allErrs = append(allErrs, field.Forbidden(fldPath, fmt.Sprintf("HugePages require cpu or memory"))) + } + + return allErrs +} + +// validateResourceQuotaScopes ensures that each enumerated hard resource constraint is valid for set of scopes +func validateResourceQuotaScopes(resourceQuotaSpec *core.ResourceQuotaSpec, fld *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(resourceQuotaSpec.Scopes) == 0 { + return allErrs + } + hardLimits := sets.NewString() + for k := range resourceQuotaSpec.Hard { + hardLimits.Insert(string(k)) + } + fldPath := fld.Child("scopes") + scopeSet := sets.NewString() + for _, scope := range resourceQuotaSpec.Scopes { + if !helper.IsStandardResourceQuotaScope(string(scope)) { + allErrs = append(allErrs, field.Invalid(fldPath, resourceQuotaSpec.Scopes, "unsupported scope")) + } + for _, k := range hardLimits.List() { + if helper.IsStandardQuotaResourceName(k) && !helper.IsResourceQuotaScopeValidForResource(scope, k) { + allErrs = append(allErrs, field.Invalid(fldPath, resourceQuotaSpec.Scopes, "unsupported scope applied to resource")) + } + } + scopeSet.Insert(string(scope)) + } + invalidScopePairs := []sets.String{ + sets.NewString(string(core.ResourceQuotaScopeBestEffort), string(core.ResourceQuotaScopeNotBestEffort)), + sets.NewString(string(core.ResourceQuotaScopeTerminating), string(core.ResourceQuotaScopeNotTerminating)), + } + for _, invalidScopePair := range invalidScopePairs { + if scopeSet.HasAll(invalidScopePair.List()...) { + allErrs = append(allErrs, field.Invalid(fldPath, resourceQuotaSpec.Scopes, "conflicting scopes")) + } + } + return allErrs +} + +// ValidateResourceQuota tests if required fields in the ResourceQuota are set. +func ValidateResourceQuota(resourceQuota *core.ResourceQuota) field.ErrorList { + allErrs := ValidateObjectMeta(&resourceQuota.ObjectMeta, true, ValidateResourceQuotaName, field.NewPath("metadata")) + + allErrs = append(allErrs, ValidateResourceQuotaSpec(&resourceQuota.Spec, field.NewPath("spec"))...) + allErrs = append(allErrs, ValidateResourceQuotaStatus(&resourceQuota.Status, field.NewPath("status"))...) + + return allErrs +} + +func ValidateResourceQuotaStatus(status *core.ResourceQuotaStatus, fld *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + fldPath := fld.Child("hard") + for k, v := range status.Hard { + resPath := fldPath.Key(string(k)) + allErrs = append(allErrs, ValidateResourceQuotaResourceName(string(k), resPath)...) + allErrs = append(allErrs, ValidateResourceQuantityValue(string(k), v, resPath)...) + } + fldPath = fld.Child("used") + for k, v := range status.Used { + resPath := fldPath.Key(string(k)) + allErrs = append(allErrs, ValidateResourceQuotaResourceName(string(k), resPath)...) + allErrs = append(allErrs, ValidateResourceQuantityValue(string(k), v, resPath)...) + } + + return allErrs +} + +func ValidateResourceQuotaSpec(resourceQuotaSpec *core.ResourceQuotaSpec, fld *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + fldPath := fld.Child("hard") + for k, v := range resourceQuotaSpec.Hard { + resPath := fldPath.Key(string(k)) + allErrs = append(allErrs, ValidateResourceQuotaResourceName(string(k), resPath)...) + allErrs = append(allErrs, ValidateResourceQuantityValue(string(k), v, resPath)...) + } + allErrs = append(allErrs, validateResourceQuotaScopes(resourceQuotaSpec, fld)...) + + return allErrs +} + +// ValidateResourceQuantityValue enforces that specified quantity is valid for specified resource +func ValidateResourceQuantityValue(resource string, value resource.Quantity, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, ValidateNonnegativeQuantity(value, fldPath)...) + if helper.IsIntegerResourceName(resource) { + if value.MilliValue()%int64(1000) != int64(0) { + allErrs = append(allErrs, field.Invalid(fldPath, value, isNotIntegerErrorMsg)) + } + } + return allErrs +} + +// ValidateResourceQuotaUpdate tests to see if the update is legal for an end user to make. +// newResourceQuota is updated with fields that cannot be changed. +func ValidateResourceQuotaUpdate(newResourceQuota, oldResourceQuota *core.ResourceQuota) field.ErrorList { + allErrs := ValidateObjectMetaUpdate(&newResourceQuota.ObjectMeta, &oldResourceQuota.ObjectMeta, field.NewPath("metadata")) + allErrs = append(allErrs, ValidateResourceQuotaSpec(&newResourceQuota.Spec, field.NewPath("spec"))...) + + // ensure scopes cannot change, and that resources are still valid for scope + fldPath := field.NewPath("spec", "scopes") + oldScopes := sets.NewString() + newScopes := sets.NewString() + for _, scope := range newResourceQuota.Spec.Scopes { + newScopes.Insert(string(scope)) + } + for _, scope := range oldResourceQuota.Spec.Scopes { + oldScopes.Insert(string(scope)) + } + if !oldScopes.Equal(newScopes) { + allErrs = append(allErrs, field.Invalid(fldPath, newResourceQuota.Spec.Scopes, fieldImmutableErrorMsg)) + } + + newResourceQuota.Status = oldResourceQuota.Status + return allErrs +} + +// ValidateResourceQuotaStatusUpdate tests to see if the status update is legal for an end user to make. +// newResourceQuota is updated with fields that cannot be changed. +func ValidateResourceQuotaStatusUpdate(newResourceQuota, oldResourceQuota *core.ResourceQuota) field.ErrorList { + allErrs := ValidateObjectMetaUpdate(&newResourceQuota.ObjectMeta, &oldResourceQuota.ObjectMeta, field.NewPath("metadata")) + if len(newResourceQuota.ResourceVersion) == 0 { + allErrs = append(allErrs, field.Required(field.NewPath("resourceVersion"), "")) + } + fldPath := field.NewPath("status", "hard") + for k, v := range newResourceQuota.Status.Hard { + resPath := fldPath.Key(string(k)) + allErrs = append(allErrs, ValidateResourceQuotaResourceName(string(k), resPath)...) + allErrs = append(allErrs, ValidateResourceQuantityValue(string(k), v, resPath)...) + } + fldPath = field.NewPath("status", "used") + for k, v := range newResourceQuota.Status.Used { + resPath := fldPath.Key(string(k)) + allErrs = append(allErrs, ValidateResourceQuotaResourceName(string(k), resPath)...) + allErrs = append(allErrs, ValidateResourceQuantityValue(string(k), v, resPath)...) + } + newResourceQuota.Spec = oldResourceQuota.Spec + return allErrs +} + +// ValidateNamespace tests if required fields are set. +func ValidateNamespace(namespace *core.Namespace) field.ErrorList { + allErrs := ValidateObjectMeta(&namespace.ObjectMeta, false, ValidateNamespaceName, field.NewPath("metadata")) + for i := range namespace.Spec.Finalizers { + allErrs = append(allErrs, validateFinalizerName(string(namespace.Spec.Finalizers[i]), field.NewPath("spec", "finalizers"))...) + } + return allErrs +} + +// Validate finalizer names +func validateFinalizerName(stringValue string, fldPath *field.Path) field.ErrorList { + allErrs := apimachineryvalidation.ValidateFinalizerName(stringValue, fldPath) + for _, err := range validateKubeFinalizerName(stringValue, fldPath) { + allErrs = append(allErrs, err) + } + + return allErrs +} + +// validateKubeFinalizerName checks for "standard" names of legacy finalizer +func validateKubeFinalizerName(stringValue string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(strings.Split(stringValue, "/")) == 1 { + if !helper.IsStandardFinalizerName(stringValue) { + return append(allErrs, field.Invalid(fldPath, stringValue, "name is neither a standard finalizer name nor is it fully qualified")) + } + } + + return allErrs +} + +// ValidateNamespaceUpdate tests to make sure a namespace update can be applied. +// newNamespace is updated with fields that cannot be changed +func ValidateNamespaceUpdate(newNamespace *core.Namespace, oldNamespace *core.Namespace) field.ErrorList { + allErrs := ValidateObjectMetaUpdate(&newNamespace.ObjectMeta, &oldNamespace.ObjectMeta, field.NewPath("metadata")) + newNamespace.Spec.Finalizers = oldNamespace.Spec.Finalizers + newNamespace.Status = oldNamespace.Status + return allErrs +} + +// ValidateNamespaceStatusUpdate tests to see if the update is legal for an end user to make. newNamespace is updated with fields +// that cannot be changed. +func ValidateNamespaceStatusUpdate(newNamespace, oldNamespace *core.Namespace) field.ErrorList { + allErrs := ValidateObjectMetaUpdate(&newNamespace.ObjectMeta, &oldNamespace.ObjectMeta, field.NewPath("metadata")) + newNamespace.Spec = oldNamespace.Spec + if newNamespace.DeletionTimestamp.IsZero() { + if newNamespace.Status.Phase != core.NamespaceActive { + allErrs = append(allErrs, field.Invalid(field.NewPath("status", "Phase"), newNamespace.Status.Phase, "may only be 'Active' if `deletionTimestamp` is empty")) + } + } else { + if newNamespace.Status.Phase != core.NamespaceTerminating { + allErrs = append(allErrs, field.Invalid(field.NewPath("status", "Phase"), newNamespace.Status.Phase, "may only be 'Terminating' if `deletionTimestamp` is not empty")) + } + } + return allErrs +} + +// ValidateNamespaceFinalizeUpdate tests to see if the update is legal for an end user to make. +// newNamespace is updated with fields that cannot be changed. +func ValidateNamespaceFinalizeUpdate(newNamespace, oldNamespace *core.Namespace) field.ErrorList { + allErrs := ValidateObjectMetaUpdate(&newNamespace.ObjectMeta, &oldNamespace.ObjectMeta, field.NewPath("metadata")) + + fldPath := field.NewPath("spec", "finalizers") + for i := range newNamespace.Spec.Finalizers { + idxPath := fldPath.Index(i) + allErrs = append(allErrs, validateFinalizerName(string(newNamespace.Spec.Finalizers[i]), idxPath)...) + } + newNamespace.Status = oldNamespace.Status + return allErrs +} + +// Construct lookup map of old subset IPs to NodeNames. +func updateEpAddrToNodeNameMap(ipToNodeName map[string]string, addresses []core.EndpointAddress) { + for n := range addresses { + if addresses[n].NodeName == nil { + continue + } + ipToNodeName[addresses[n].IP] = *addresses[n].NodeName + } +} + +// Build a map across all subsets of IP -> NodeName +func buildEndpointAddressNodeNameMap(subsets []core.EndpointSubset) map[string]string { + ipToNodeName := make(map[string]string) + for i := range subsets { + updateEpAddrToNodeNameMap(ipToNodeName, subsets[i].Addresses) + updateEpAddrToNodeNameMap(ipToNodeName, subsets[i].NotReadyAddresses) + } + return ipToNodeName +} + +func validateEpAddrNodeNameTransition(addr *core.EndpointAddress, ipToNodeName map[string]string, fldPath *field.Path) field.ErrorList { + errList := field.ErrorList{} + existingNodeName, found := ipToNodeName[addr.IP] + if !found { + return errList + } + if addr.NodeName == nil || *addr.NodeName == existingNodeName { + return errList + } + // NodeName entry found for this endpoint IP, but user is attempting to change NodeName + return append(errList, field.Forbidden(fldPath, fmt.Sprintf("Cannot change NodeName for %s to %s", addr.IP, *addr.NodeName))) +} + +// ValidateEndpoints tests if required fields are set. +func ValidateEndpoints(endpoints *core.Endpoints) field.ErrorList { + allErrs := ValidateObjectMeta(&endpoints.ObjectMeta, true, ValidateEndpointsName, field.NewPath("metadata")) + allErrs = append(allErrs, ValidateEndpointsSpecificAnnotations(endpoints.Annotations, field.NewPath("annotations"))...) + allErrs = append(allErrs, validateEndpointSubsets(endpoints.Subsets, []core.EndpointSubset{}, field.NewPath("subsets"))...) + return allErrs +} + +func validateEndpointSubsets(subsets []core.EndpointSubset, oldSubsets []core.EndpointSubset, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + ipToNodeName := buildEndpointAddressNodeNameMap(oldSubsets) + for i := range subsets { + ss := &subsets[i] + idxPath := fldPath.Index(i) + + // EndpointSubsets must include endpoint address. For headless service, we allow its endpoints not to have ports. + if len(ss.Addresses) == 0 && len(ss.NotReadyAddresses) == 0 { + //TODO: consider adding a RequiredOneOf() error for this and similar cases + allErrs = append(allErrs, field.Required(idxPath, "must specify `addresses` or `notReadyAddresses`")) + } + for addr := range ss.Addresses { + allErrs = append(allErrs, validateEndpointAddress(&ss.Addresses[addr], idxPath.Child("addresses").Index(addr), ipToNodeName)...) + } + for addr := range ss.NotReadyAddresses { + allErrs = append(allErrs, validateEndpointAddress(&ss.NotReadyAddresses[addr], idxPath.Child("notReadyAddresses").Index(addr), ipToNodeName)...) + } + for port := range ss.Ports { + allErrs = append(allErrs, validateEndpointPort(&ss.Ports[port], len(ss.Ports) > 1, idxPath.Child("ports").Index(port))...) + } + } + + return allErrs +} + +func validateEndpointAddress(address *core.EndpointAddress, fldPath *field.Path, ipToNodeName map[string]string) field.ErrorList { + allErrs := field.ErrorList{} + for _, msg := range validation.IsValidIP(address.IP) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("ip"), address.IP, msg)) + } + if len(address.Hostname) > 0 { + allErrs = append(allErrs, ValidateDNS1123Label(address.Hostname, fldPath.Child("hostname"))...) + } + // During endpoint update, verify that NodeName is a DNS subdomain and transition rules allow the update + if address.NodeName != nil { + for _, msg := range ValidateNodeName(*address.NodeName, false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("nodeName"), *address.NodeName, msg)) + } + } + allErrs = append(allErrs, validateEpAddrNodeNameTransition(address, ipToNodeName, fldPath.Child("nodeName"))...) + if len(allErrs) > 0 { + return allErrs + } + allErrs = append(allErrs, validateNonSpecialIP(address.IP, fldPath.Child("ip"))...) + return allErrs +} + +func validateNonSpecialIP(ipAddress string, fldPath *field.Path) field.ErrorList { + // We disallow some IPs as endpoints or external-ips. Specifically, + // unspecified and loopback addresses are nonsensical and link-local + // addresses tend to be used for node-centric purposes (e.g. metadata + // service). + allErrs := field.ErrorList{} + ip := net.ParseIP(ipAddress) + if ip == nil { + allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, "must be a valid IP address")) + return allErrs + } + if ip.IsUnspecified() { + allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, "may not be unspecified (0.0.0.0)")) + } + if ip.IsLoopback() { + allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, "may not be in the loopback range (127.0.0.0/8)")) + } + if ip.IsLinkLocalUnicast() { + allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, "may not be in the link-local range (169.254.0.0/16)")) + } + if ip.IsLinkLocalMulticast() { + allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, "may not be in the link-local multicast range (224.0.0.0/24)")) + } + return allErrs +} + +func validateEndpointPort(port *core.EndpointPort, requireName bool, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if requireName && len(port.Name) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) + } else if len(port.Name) != 0 { + allErrs = append(allErrs, ValidateDNS1123Label(port.Name, fldPath.Child("name"))...) + } + for _, msg := range validation.IsValidPortNum(int(port.Port)) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("port"), port.Port, msg)) + } + if len(port.Protocol) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("protocol"), "")) + } else if !supportedPortProtocols.Has(string(port.Protocol)) { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("protocol"), port.Protocol, supportedPortProtocols.List())) + } + return allErrs +} + +// ValidateEndpointsUpdate tests to make sure an endpoints update can be applied. +func ValidateEndpointsUpdate(newEndpoints, oldEndpoints *core.Endpoints) field.ErrorList { + allErrs := ValidateObjectMetaUpdate(&newEndpoints.ObjectMeta, &oldEndpoints.ObjectMeta, field.NewPath("metadata")) + allErrs = append(allErrs, validateEndpointSubsets(newEndpoints.Subsets, oldEndpoints.Subsets, field.NewPath("subsets"))...) + allErrs = append(allErrs, ValidateEndpointsSpecificAnnotations(newEndpoints.Annotations, field.NewPath("annotations"))...) + return allErrs +} + +// ValidateSecurityContext ensure the security context contains valid settings +func ValidateSecurityContext(sc *core.SecurityContext, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + //this should only be true for testing since SecurityContext is defaulted by the core + if sc == nil { + return allErrs + } + + if sc.Privileged != nil { + if *sc.Privileged && !capabilities.Get().AllowPrivileged { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("privileged"), "disallowed by cluster policy")) + } + } + + if sc.RunAsUser != nil { + if *sc.RunAsUser < 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsUser"), *sc.RunAsUser, isNegativeErrorMsg)) + } + } + + if sc.AllowPrivilegeEscalation != nil && !*sc.AllowPrivilegeEscalation { + if sc.Privileged != nil && *sc.Privileged { + allErrs = append(allErrs, field.Invalid(fldPath, sc, "cannot set `allowPrivilegeEscalation` to false and `privileged` to true")) + } + + if sc.Capabilities != nil { + for _, cap := range sc.Capabilities.Add { + if string(cap) == "CAP_SYS_ADMIN" { + allErrs = append(allErrs, field.Invalid(fldPath, sc, "cannot set `allowPrivilegeEscalation` to false and `capabilities.Add` CAP_SYS_ADMIN")) + } + } + } + } + + return allErrs +} + +func ValidatePodLogOptions(opts *core.PodLogOptions) field.ErrorList { + allErrs := field.ErrorList{} + if opts.TailLines != nil && *opts.TailLines < 0 { + allErrs = append(allErrs, field.Invalid(field.NewPath("tailLines"), *opts.TailLines, isNegativeErrorMsg)) + } + if opts.LimitBytes != nil && *opts.LimitBytes < 1 { + allErrs = append(allErrs, field.Invalid(field.NewPath("limitBytes"), *opts.LimitBytes, "must be greater than 0")) + } + switch { + case opts.SinceSeconds != nil && opts.SinceTime != nil: + allErrs = append(allErrs, field.Forbidden(field.NewPath(""), "at most one of `sinceTime` or `sinceSeconds` may be specified")) + case opts.SinceSeconds != nil: + if *opts.SinceSeconds < 1 { + allErrs = append(allErrs, field.Invalid(field.NewPath("sinceSeconds"), *opts.SinceSeconds, "must be greater than 0")) + } + } + return allErrs +} + +// ValidateLoadBalancerStatus validates required fields on a LoadBalancerStatus +func ValidateLoadBalancerStatus(status *core.LoadBalancerStatus, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for i, ingress := range status.Ingress { + idxPath := fldPath.Child("ingress").Index(i) + if len(ingress.IP) > 0 { + if isIP := (net.ParseIP(ingress.IP) != nil); !isIP { + allErrs = append(allErrs, field.Invalid(idxPath.Child("ip"), ingress.IP, "must be a valid IP address")) + } + } + if len(ingress.Hostname) > 0 { + for _, msg := range validation.IsDNS1123Subdomain(ingress.Hostname) { + allErrs = append(allErrs, field.Invalid(idxPath.Child("hostname"), ingress.Hostname, msg)) + } + if isIP := (net.ParseIP(ingress.Hostname) != nil); isIP { + allErrs = append(allErrs, field.Invalid(idxPath.Child("hostname"), ingress.Hostname, "must be a DNS name, not an IP address")) + } + } + } + return allErrs +} + +func sysctlIntersection(a []core.Sysctl, b []core.Sysctl) []string { + lookup := make(map[string]struct{}, len(a)) + result := []string{} + for i := range a { + lookup[a[i].Name] = struct{}{} + } + for i := range b { + if _, found := lookup[b[i].Name]; found { + result = append(result, b[i].Name) + } + } + return result +} + +// validateStorageNodeAffinityAnnotation tests that the serialized TopologyConstraints in PersistentVolume.Annotations has valid data +func validateStorageNodeAffinityAnnotation(annotations map[string]string, fldPath *field.Path) (bool, field.ErrorList) { + allErrs := field.ErrorList{} + + na, err := helper.GetStorageNodeAffinityFromAnnotation(annotations) + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath, core.AlphaStorageNodeAffinityAnnotation, err.Error())) + return false, allErrs + } + if na == nil { + return false, allErrs + } + + if !utilfeature.DefaultFeatureGate.Enabled(features.PersistentLocalVolumes) { + allErrs = append(allErrs, field.Forbidden(fldPath, "Storage node affinity is disabled by feature-gate")) + } + + policySpecified := false + if na.RequiredDuringSchedulingIgnoredDuringExecution != nil { + allErrs = append(allErrs, ValidateNodeSelector(na.RequiredDuringSchedulingIgnoredDuringExecution, fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...) + policySpecified = true + } + + if len(na.PreferredDuringSchedulingIgnoredDuringExecution) > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("preferredDuringSchedulingIgnoredDuringExection"), "Storage node affinity does not support preferredDuringSchedulingIgnoredDuringExecution")) + } + return policySpecified, allErrs +} + +// ValidateCIDR validates whether a CIDR matches the conventions expected by net.ParseCIDR +func ValidateCIDR(cidr string) (*net.IPNet, error) { + _, net, err := net.ParseCIDR(cidr) + if err != nil { + return nil, err + } + return net, nil +} diff --git a/pkg/apis/core/validation/validation_test.go b/pkg/apis/core/validation/validation_test.go new file mode 100644 index 00000000000..092aae51dda --- /dev/null +++ b/pkg/apis/core/validation/validation_test.go @@ -0,0 +1,12222 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES 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" + "math" + "reflect" + "strings" + "testing" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/apimachinery/pkg/util/validation/field" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/api/legacyscheme" + _ "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" + "k8s.io/kubernetes/pkg/capabilities" + "k8s.io/kubernetes/pkg/security/apparmor" +) + +const ( + dnsLabelErrMsg = "a DNS-1123 label must consist of" + dnsSubdomainLabelErrMsg = "a DNS-1123 subdomain" + envVarNameErrMsg = "a valid environment variable name must consist of" +) + +func newHostPathType(pathType string) *core.HostPathType { + hostPathType := new(core.HostPathType) + *hostPathType = core.HostPathType(pathType) + return hostPathType +} + +func testVolume(name string, namespace string, spec core.PersistentVolumeSpec) *core.PersistentVolume { + objMeta := metav1.ObjectMeta{Name: name} + if namespace != "" { + objMeta.Namespace = namespace + } + + return &core.PersistentVolume{ + ObjectMeta: objMeta, + Spec: spec, + } +} + +func testVolumeWithNodeAffinity(t *testing.T, name string, namespace string, affinity *core.NodeAffinity, spec core.PersistentVolumeSpec) *core.PersistentVolume { + objMeta := metav1.ObjectMeta{Name: name} + if namespace != "" { + objMeta.Namespace = namespace + } + + objMeta.Annotations = map[string]string{} + err := helper.StorageNodeAffinityToAlphaAnnotation(objMeta.Annotations, affinity) + if err != nil { + t.Fatalf("Failed to get node affinity annotation: %v", err) + } + + return &core.PersistentVolume{ + ObjectMeta: objMeta, + Spec: spec, + } +} + +func TestValidatePersistentVolumes(t *testing.T) { + validMode := core.PersistentVolumeFilesystem + scenarios := map[string]struct { + isExpectedFailure bool + volume *core.PersistentVolume + }{ + "good-volume": { + isExpectedFailure: false, + volume: testVolume("foo", "", core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/foo", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + }, + }), + }, + "good-volume-with-capacity-unit": { + isExpectedFailure: false, + volume: testVolume("foo", "", core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10Gi"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/foo", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + }, + }), + }, + "good-volume-without-capacity-unit": { + isExpectedFailure: false, + volume: testVolume("foo", "", core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/foo", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + }, + }), + }, + "good-volume-with-storage-class": { + isExpectedFailure: false, + volume: testVolume("foo", "", core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/foo", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + }, + StorageClassName: "valid", + }), + }, + "good-volume-with-retain-policy": { + isExpectedFailure: false, + volume: testVolume("foo", "", core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/foo", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + }, + PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRetain, + }), + }, + "invalid-accessmode": { + isExpectedFailure: true, + volume: testVolume("foo", "", core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + AccessModes: []core.PersistentVolumeAccessMode{"fakemode"}, + PersistentVolumeSource: core.PersistentVolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/foo", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + }, + }), + }, + "invalid-reclaimpolicy": { + isExpectedFailure: true, + volume: testVolume("foo", "", core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/foo", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + }, + PersistentVolumeReclaimPolicy: "fakeReclaimPolicy", + }), + }, + "unexpected-namespace": { + isExpectedFailure: true, + volume: testVolume("foo", "unexpected-namespace", core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/foo", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + }, + }), + }, + "missing-volume-source": { + isExpectedFailure: true, + volume: testVolume("foo", "", core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + }), + }, + "bad-name": { + isExpectedFailure: true, + volume: testVolume("123*Bad(Name", "unexpected-namespace", core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/foo", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + }, + }), + }, + "missing-name": { + isExpectedFailure: true, + volume: testVolume("", "", core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/foo", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + }, + }), + }, + "missing-capacity": { + isExpectedFailure: true, + volume: testVolume("foo", "", core.PersistentVolumeSpec{ + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/foo", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + }, + }), + }, + "bad-volume-zero-capacity": { + isExpectedFailure: true, + volume: testVolume("foo", "", core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("0"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/foo", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + }, + }), + }, + "missing-accessmodes": { + isExpectedFailure: true, + volume: testVolume("goodname", "missing-accessmodes", core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + PersistentVolumeSource: core.PersistentVolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/foo", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + }, + }), + }, + "too-many-sources": { + isExpectedFailure: true, + volume: testVolume("foo", "", core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("5G"), + }, + PersistentVolumeSource: core.PersistentVolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/foo", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{PDName: "foo", FSType: "ext4"}, + }, + }), + }, + "host mount of / with recycle reclaim policy": { + isExpectedFailure: true, + volume: testVolume("bad-recycle-do-not-want", "", core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + }, + PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRecycle, + }), + }, + "host mount of / with recycle reclaim policy 2": { + isExpectedFailure: true, + volume: testVolume("bad-recycle-do-not-want", "", core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/a/..", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + }, + PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRecycle, + }), + }, + "invalid-storage-class-name": { + isExpectedFailure: true, + volume: testVolume("invalid-storage-class-name", "", core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/foo", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + }, + StorageClassName: "-invalid-", + }), + }, + // VolumeMode alpha feature disabled + // TODO: remove when no longer alpha + "alpha disabled valid volume mode": { + isExpectedFailure: true, + volume: testVolume("foo", "", core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/foo", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + }, + StorageClassName: "valid", + VolumeMode: &validMode, + }), + }, + // LocalVolume alpha feature disabled + // TODO: remove when no longer alpha + "alpha disabled valid local volume": { + isExpectedFailure: true, + volume: testVolumeWithNodeAffinity( + t, + "valid-local-volume", + "", + &core.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ + NodeSelectorTerms: []core.NodeSelectorTerm{ + { + MatchExpressions: []core.NodeSelectorRequirement{ + { + Key: "test-label-key", + Operator: core.NodeSelectorOpIn, + Values: []string{"test-label-value"}, + }, + }, + }, + }, + }, + }, + core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + Local: &core.LocalVolumeSource{ + Path: "/foo", + }, + }, + StorageClassName: "test-storage-class", + }), + }, + "bad-hostpath-volume-backsteps": { + isExpectedFailure: true, + volume: testVolume("foo", "", core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/foo/..", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + }, + StorageClassName: "backstep-hostpath", + }), + }, + "bad-local-volume-backsteps": { + isExpectedFailure: true, + volume: testVolume("foo", "", core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + Local: &core.LocalVolumeSource{ + Path: "/foo/..", + }, + }, + StorageClassName: "backstep-local", + }), + }, + } + + for name, scenario := range scenarios { + errs := ValidatePersistentVolume(scenario.volume) + if len(errs) == 0 && scenario.isExpectedFailure { + t.Errorf("Unexpected success for scenario: %s", name) + } + if len(errs) > 0 && !scenario.isExpectedFailure { + t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) + } + } + +} + +func TestValidatePersistentVolumeSourceUpdate(t *testing.T) { + validVolume := testVolume("foo", "", core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("1G"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/foo", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + }, + StorageClassName: "valid", + }) + validPvSourceNoUpdate := validVolume.DeepCopy() + invalidPvSourceUpdateType := validVolume.DeepCopy() + invalidPvSourceUpdateType.Spec.PersistentVolumeSource = core.PersistentVolumeSource{ + FlexVolume: &core.FlexPersistentVolumeSource{ + Driver: "kubernetes.io/blue", + FSType: "ext4", + }, + } + invalidPvSourceUpdateDeep := validVolume.DeepCopy() + invalidPvSourceUpdateDeep.Spec.PersistentVolumeSource = core.PersistentVolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/updated", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + } + scenarios := map[string]struct { + isExpectedFailure bool + oldVolume *core.PersistentVolume + newVolume *core.PersistentVolume + }{ + "condition-no-update": { + isExpectedFailure: false, + oldVolume: validVolume, + newVolume: validPvSourceNoUpdate, + }, + "condition-update-source-type": { + isExpectedFailure: true, + oldVolume: validVolume, + newVolume: invalidPvSourceUpdateType, + }, + "condition-update-source-deep": { + isExpectedFailure: true, + oldVolume: validVolume, + newVolume: invalidPvSourceUpdateDeep, + }, + } + for name, scenario := range scenarios { + errs := ValidatePersistentVolumeUpdate(scenario.newVolume, scenario.oldVolume) + if len(errs) == 0 && scenario.isExpectedFailure { + t.Errorf("Unexpected success for scenario: %s", name) + } + if len(errs) > 0 && !scenario.isExpectedFailure { + t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) + } + } +} + +func TestValidateLocalVolumes(t *testing.T) { + scenarios := map[string]struct { + isExpectedFailure bool + volume *core.PersistentVolume + }{ + "valid local volume": { + isExpectedFailure: false, + volume: testVolumeWithNodeAffinity( + t, + "valid-local-volume", + "", + &core.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ + NodeSelectorTerms: []core.NodeSelectorTerm{ + { + MatchExpressions: []core.NodeSelectorRequirement{ + { + Key: "test-label-key", + Operator: core.NodeSelectorOpIn, + Values: []string{"test-label-value"}, + }, + }, + }, + }, + }, + }, + core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + Local: &core.LocalVolumeSource{ + Path: "/foo", + }, + }, + StorageClassName: "test-storage-class", + }), + }, + "invalid local volume nil annotations": { + isExpectedFailure: true, + volume: testVolume( + "invalid-local-volume-nil-annotations", + "", + core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + Local: &core.LocalVolumeSource{ + Path: "/foo", + }, + }, + StorageClassName: "test-storage-class", + }), + }, + "invalid local volume empty affinity": { + isExpectedFailure: true, + volume: testVolumeWithNodeAffinity( + t, + "invalid-local-volume-empty-affinity", + "", + &core.NodeAffinity{}, + core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + Local: &core.LocalVolumeSource{ + Path: "/foo", + }, + }, + StorageClassName: "test-storage-class", + }), + }, + "invalid local volume preferred affinity": { + isExpectedFailure: true, + volume: testVolumeWithNodeAffinity( + t, + "invalid-local-volume-preferred-affinity", + "", + &core.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ + NodeSelectorTerms: []core.NodeSelectorTerm{ + { + MatchExpressions: []core.NodeSelectorRequirement{ + { + Key: "test-label-key", + Operator: core.NodeSelectorOpIn, + Values: []string{"test-label-value"}, + }, + }, + }, + }, + }, + PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{ + { + Weight: 10, + Preference: core.NodeSelectorTerm{ + MatchExpressions: []core.NodeSelectorRequirement{ + { + Key: "test-label-key", + Operator: core.NodeSelectorOpIn, + Values: []string{"test-label-value"}, + }, + }, + }, + }, + }, + }, + core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + Local: &core.LocalVolumeSource{ + Path: "/foo", + }, + }, + StorageClassName: "test-storage-class", + }), + }, + "invalid local volume empty path": { + isExpectedFailure: true, + volume: testVolumeWithNodeAffinity( + t, + "invalid-local-volume-empty-path", + "", + &core.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ + NodeSelectorTerms: []core.NodeSelectorTerm{ + { + MatchExpressions: []core.NodeSelectorRequirement{ + { + Key: "test-label-key", + Operator: core.NodeSelectorOpIn, + Values: []string{"test-label-value"}, + }, + }, + }, + }, + }, + }, + core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + Local: &core.LocalVolumeSource{}, + }, + StorageClassName: "test-storage-class", + }), + }, + } + + err := utilfeature.DefaultFeatureGate.Set("PersistentLocalVolumes=true") + if err != nil { + t.Errorf("Failed to enable feature gate for LocalPersistentVolumes: %v", err) + return + } + for name, scenario := range scenarios { + errs := ValidatePersistentVolume(scenario.volume) + if len(errs) == 0 && scenario.isExpectedFailure { + t.Errorf("Unexpected success for scenario: %s", name) + } + if len(errs) > 0 && !scenario.isExpectedFailure { + t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) + } + } +} + +func testVolumeClaim(name string, namespace string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim { + return &core.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, + Spec: spec, + } +} + +func testVolumeClaimWithStatus( + name, namespace string, + spec core.PersistentVolumeClaimSpec, + status core.PersistentVolumeClaimStatus) *core.PersistentVolumeClaim { + return &core.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, + Spec: spec, + Status: status, + } +} + +func testVolumeClaimStorageClass(name string, namespace string, annval string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim { + annotations := map[string]string{ + v1.BetaStorageClassAnnotation: annval, + } + + return &core.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: annotations, + }, + Spec: spec, + } +} + +func testVolumeClaimAnnotation(name string, namespace string, ann string, annval string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim { + annotations := map[string]string{ + ann: annval, + } + + return &core.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: annotations, + }, + Spec: spec, + } +} + +func TestValidatePersistentVolumeClaim(t *testing.T) { + invalidClassName := "-invalid-" + validClassName := "valid" + validMode := core.PersistentVolumeFilesystem + scenarios := map[string]struct { + isExpectedFailure bool + claim *core.PersistentVolumeClaim + }{ + "good-claim": { + isExpectedFailure: false, + claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ + Selector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key2", + Operator: "Exists", + }, + }, + }, + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadWriteOnce, + core.ReadOnlyMany, + }, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + }, + StorageClassName: &validClassName, + }), + }, + "invalid-claim-zero-capacity": { + isExpectedFailure: true, + claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ + Selector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key2", + Operator: "Exists", + }, + }, + }, + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadWriteOnce, + core.ReadOnlyMany, + }, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("0G"), + }, + }, + StorageClassName: &validClassName, + }), + }, + "invalid-label-selector": { + isExpectedFailure: true, + claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ + Selector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key2", + Operator: "InvalidOp", + Values: []string{"value1", "value2"}, + }, + }, + }, + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadWriteOnce, + core.ReadOnlyMany, + }, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + }, + }), + }, + "invalid-accessmode": { + isExpectedFailure: true, + claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ + AccessModes: []core.PersistentVolumeAccessMode{"fakemode"}, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + }, + }), + }, + "missing-namespace": { + isExpectedFailure: true, + claim: testVolumeClaim("foo", "", core.PersistentVolumeClaimSpec{ + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadWriteOnce, + core.ReadOnlyMany, + }, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + }, + }), + }, + "no-access-modes": { + isExpectedFailure: true, + claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + }, + }), + }, + "no-resource-requests": { + isExpectedFailure: true, + claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadWriteOnce, + }, + }), + }, + "invalid-resource-requests": { + isExpectedFailure: true, + claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadWriteOnce, + }, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + }, + }, + }), + }, + "negative-storage-request": { + isExpectedFailure: true, + claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ + Selector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key2", + Operator: "Exists", + }, + }, + }, + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadWriteOnce, + core.ReadOnlyMany, + }, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("-10G"), + }, + }, + }), + }, + "zero-storage-request": { + isExpectedFailure: true, + claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ + Selector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key2", + Operator: "Exists", + }, + }, + }, + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadWriteOnce, + core.ReadOnlyMany, + }, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("0G"), + }, + }, + }), + }, + "invalid-storage-class-name": { + isExpectedFailure: true, + claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ + Selector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key2", + Operator: "Exists", + }, + }, + }, + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadWriteOnce, + core.ReadOnlyMany, + }, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + }, + StorageClassName: &invalidClassName, + }), + }, + // VolumeMode alpha feature disabled + // TODO: remove when no longer alpha + "disabled alpha valid volume mode": { + isExpectedFailure: true, + claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ + Selector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key2", + Operator: "Exists", + }, + }, + }, + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadWriteOnce, + core.ReadOnlyMany, + }, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + }, + StorageClassName: &validClassName, + VolumeMode: &validMode, + }), + }, + } + + for name, scenario := range scenarios { + errs := ValidatePersistentVolumeClaim(scenario.claim) + if len(errs) == 0 && scenario.isExpectedFailure { + t.Errorf("Unexpected success for scenario: %s", name) + } + if len(errs) > 0 && !scenario.isExpectedFailure { + t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) + } + } +} + +func TestAlphaPVVolumeModeUpdate(t *testing.T) { + block := core.PersistentVolumeBlock + file := core.PersistentVolumeFilesystem + + scenarios := map[string]struct { + isExpectedFailure bool + oldPV *core.PersistentVolume + newPV *core.PersistentVolume + enableBlock bool + }{ + "valid-update-volume-mode-block-to-block": { + isExpectedFailure: false, + oldPV: createTestVolModePV(&block), + newPV: createTestVolModePV(&block), + enableBlock: true, + }, + "valid-update-volume-mode-file-to-file": { + isExpectedFailure: false, + oldPV: createTestVolModePV(&file), + newPV: createTestVolModePV(&file), + enableBlock: true, + }, + "invalid-update-volume-mode-to-block": { + isExpectedFailure: true, + oldPV: createTestVolModePV(&file), + newPV: createTestVolModePV(&block), + enableBlock: true, + }, + "invalid-update-volume-mode-to-file": { + isExpectedFailure: true, + oldPV: createTestVolModePV(&block), + newPV: createTestVolModePV(&file), + enableBlock: true, + }, + "invalid-update-blocksupport-disabled": { + isExpectedFailure: true, + oldPV: createTestVolModePV(&block), + newPV: createTestVolModePV(&block), + enableBlock: false, + }, + "invalid-update-volume-mode-nil-to-file": { + isExpectedFailure: true, + oldPV: createTestVolModePV(nil), + newPV: createTestVolModePV(&file), + enableBlock: true, + }, + "invalid-update-volume-mode-nil-to-block": { + isExpectedFailure: true, + oldPV: createTestVolModePV(nil), + newPV: createTestVolModePV(&block), + enableBlock: true, + }, + "invalid-update-volume-mode-file-to-nil": { + isExpectedFailure: true, + oldPV: createTestVolModePV(&file), + newPV: createTestVolModePV(nil), + enableBlock: true, + }, + "invalid-update-volume-mode-block-to-nil": { + isExpectedFailure: true, + oldPV: createTestVolModePV(&block), + newPV: createTestVolModePV(nil), + enableBlock: true, + }, + "invalid-update-volume-mode-nil-to-nil": { + isExpectedFailure: false, + oldPV: createTestVolModePV(nil), + newPV: createTestVolModePV(nil), + enableBlock: true, + }, + "invalid-update-volume-mode-empty-to-mode": { + isExpectedFailure: true, + oldPV: createTestPV(), + newPV: createTestVolModePV(&block), + enableBlock: true, + }, + } + + for name, scenario := range scenarios { + // ensure we have a resource version specified for updates + toggleBlockVolumeFeature(scenario.enableBlock, t) + errs := ValidatePersistentVolumeUpdate(scenario.newPV, scenario.oldPV) + if len(errs) == 0 && scenario.isExpectedFailure { + t.Errorf("Unexpected success for scenario: %s", name) + } + if len(errs) > 0 && !scenario.isExpectedFailure { + t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) + } + } +} + +func TestValidatePersistentVolumeClaimUpdate(t *testing.T) { + block := core.PersistentVolumeBlock + file := core.PersistentVolumeFilesystem + + validClaim := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadWriteOnce, + core.ReadOnlyMany, + }, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + }, + }, core.PersistentVolumeClaimStatus{ + Phase: core.ClaimBound, + }) + + validClaimStorageClass := testVolumeClaimStorageClass("foo", "ns", "fast", core.PersistentVolumeClaimSpec{ + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadOnlyMany, + }, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + }, + }) + validClaimAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "foo-description", core.PersistentVolumeClaimSpec{ + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadOnlyMany, + }, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + }, + }) + validUpdateClaim := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadWriteOnce, + core.ReadOnlyMany, + }, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + }, + VolumeName: "volume", + }) + invalidUpdateClaimResources := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadWriteOnce, + core.ReadOnlyMany, + }, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("20G"), + }, + }, + VolumeName: "volume", + }) + invalidUpdateClaimAccessModes := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadWriteOnce, + }, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + }, + VolumeName: "volume", + }) + validClaimVolumeModeFile := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadWriteOnce, + }, + VolumeMode: &file, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + }, + VolumeName: "volume", + }) + validClaimVolumeModeBlock := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadWriteOnce, + }, + VolumeMode: &block, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + }, + VolumeName: "volume", + }) + invalidClaimVolumeModeNil := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadWriteOnce, + }, + VolumeMode: nil, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + }, + VolumeName: "volume", + }) + invalidUpdateClaimStorageClass := testVolumeClaimStorageClass("foo", "ns", "fast2", core.PersistentVolumeClaimSpec{ + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadOnlyMany, + }, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + }, + VolumeName: "volume", + }) + validUpdateClaimMutableAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "updated-or-added-foo-description", core.PersistentVolumeClaimSpec{ + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadOnlyMany, + }, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + }, + VolumeName: "volume", + }) + validAddClaimAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "updated-or-added-foo-description", core.PersistentVolumeClaimSpec{ + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadWriteOnce, + core.ReadOnlyMany, + }, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + }, + VolumeName: "volume", + }) + validSizeUpdate := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadWriteOnce, + core.ReadOnlyMany, + }, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("15G"), + }, + }, + }, core.PersistentVolumeClaimStatus{ + Phase: core.ClaimBound, + }) + + invalidSizeUpdate := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadWriteOnce, + core.ReadOnlyMany, + }, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("5G"), + }, + }, + }, core.PersistentVolumeClaimStatus{ + Phase: core.ClaimBound, + }) + + unboundSizeUpdate := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadWriteOnce, + core.ReadOnlyMany, + }, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("12G"), + }, + }, + }, core.PersistentVolumeClaimStatus{ + Phase: core.ClaimPending, + }) + + scenarios := map[string]struct { + isExpectedFailure bool + oldClaim *core.PersistentVolumeClaim + newClaim *core.PersistentVolumeClaim + enableResize bool + enableBlock bool + }{ + "valid-update-volumeName-only": { + isExpectedFailure: false, + oldClaim: validClaim, + newClaim: validUpdateClaim, + enableResize: false, + enableBlock: false, + }, + "valid-no-op-update": { + isExpectedFailure: false, + oldClaim: validUpdateClaim, + newClaim: validUpdateClaim, + enableResize: false, + enableBlock: false, + }, + "invalid-update-change-resources-on-bound-claim": { + isExpectedFailure: true, + oldClaim: validUpdateClaim, + newClaim: invalidUpdateClaimResources, + enableResize: false, + enableBlock: false, + }, + "invalid-update-change-access-modes-on-bound-claim": { + isExpectedFailure: true, + oldClaim: validUpdateClaim, + newClaim: invalidUpdateClaimAccessModes, + enableResize: false, + enableBlock: false, + }, + "valid-update-volume-mode-block-to-block": { + isExpectedFailure: false, + oldClaim: validClaimVolumeModeBlock, + newClaim: validClaimVolumeModeBlock, + enableResize: false, + enableBlock: true, + }, + "valid-update-volume-mode-file-to-file": { + isExpectedFailure: false, + oldClaim: validClaimVolumeModeFile, + newClaim: validClaimVolumeModeFile, + enableResize: false, + enableBlock: true, + }, + "invalid-update-volume-mode-to-block": { + isExpectedFailure: true, + oldClaim: validClaimVolumeModeFile, + newClaim: validClaimVolumeModeBlock, + enableResize: false, + enableBlock: true, + }, + "invalid-update-volume-mode-to-file": { + isExpectedFailure: true, + oldClaim: validClaimVolumeModeBlock, + newClaim: validClaimVolumeModeFile, + enableResize: false, + enableBlock: true, + }, + "invalid-update-volume-mode-nil-to-file": { + isExpectedFailure: true, + oldClaim: invalidClaimVolumeModeNil, + newClaim: validClaimVolumeModeFile, + enableResize: false, + enableBlock: true, + }, + "invalid-update-volume-mode-nil-to-block": { + isExpectedFailure: true, + oldClaim: invalidClaimVolumeModeNil, + newClaim: validClaimVolumeModeBlock, + enableResize: false, + enableBlock: true, + }, + "invalid-update-volume-mode-block-to-nil": { + isExpectedFailure: true, + oldClaim: validClaimVolumeModeBlock, + newClaim: invalidClaimVolumeModeNil, + enableResize: false, + enableBlock: true, + }, + "invalid-update-volume-mode-file-to-nil": { + isExpectedFailure: true, + oldClaim: validClaimVolumeModeFile, + newClaim: invalidClaimVolumeModeNil, + enableResize: false, + enableBlock: true, + }, + "invalid-update-volume-mode-empty-to-mode": { + isExpectedFailure: true, + oldClaim: validClaim, + newClaim: validClaimVolumeModeBlock, + enableResize: false, + enableBlock: true, + }, + "invalid-update-volume-mode-mode-to-empty": { + isExpectedFailure: true, + oldClaim: validClaimVolumeModeBlock, + newClaim: validClaim, + enableResize: false, + enableBlock: true, + }, + "invalid-update-blocksupport-disabled": { + isExpectedFailure: true, + oldClaim: validClaimVolumeModeFile, + newClaim: validClaimVolumeModeFile, + enableResize: false, + enableBlock: false, + }, + "invalid-update-change-storage-class-annotation-after-creation": { + isExpectedFailure: true, + oldClaim: validClaimStorageClass, + newClaim: invalidUpdateClaimStorageClass, + enableResize: false, + enableBlock: false, + }, + "valid-update-mutable-annotation": { + isExpectedFailure: false, + oldClaim: validClaimAnnotation, + newClaim: validUpdateClaimMutableAnnotation, + enableResize: false, + enableBlock: false, + }, + "valid-update-add-annotation": { + isExpectedFailure: false, + oldClaim: validClaim, + newClaim: validAddClaimAnnotation, + enableResize: false, + enableBlock: false, + }, + "valid-size-update-resize-disabled": { + isExpectedFailure: true, + oldClaim: validClaim, + newClaim: validSizeUpdate, + enableResize: false, + enableBlock: false, + }, + "valid-size-update-resize-enabled": { + isExpectedFailure: false, + oldClaim: validClaim, + newClaim: validSizeUpdate, + enableResize: true, + enableBlock: false, + }, + "invalid-size-update-resize-enabled": { + isExpectedFailure: true, + oldClaim: validClaim, + newClaim: invalidSizeUpdate, + enableResize: true, + enableBlock: false, + }, + "unbound-size-update-resize-enabled": { + isExpectedFailure: true, + oldClaim: validClaim, + newClaim: unboundSizeUpdate, + enableResize: true, + enableBlock: false, + }, + } + + for name, scenario := range scenarios { + // ensure we have a resource version specified for updates + togglePVExpandFeature(scenario.enableResize, t) + toggleBlockVolumeFeature(scenario.enableBlock, t) + scenario.oldClaim.ResourceVersion = "1" + scenario.newClaim.ResourceVersion = "1" + errs := ValidatePersistentVolumeClaimUpdate(scenario.newClaim, scenario.oldClaim) + if len(errs) == 0 && scenario.isExpectedFailure { + t.Errorf("Unexpected success for scenario: %s", name) + } + if len(errs) > 0 && !scenario.isExpectedFailure { + t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) + } + } +} + +func toggleBlockVolumeFeature(toggleFlag bool, t *testing.T) { + if toggleFlag { + // Enable alpha feature BlockVolume + err := utilfeature.DefaultFeatureGate.Set("BlockVolume=true") + if err != nil { + t.Errorf("Failed to enable feature gate for BlockVolume: %v", err) + return + } + } else { + err := utilfeature.DefaultFeatureGate.Set("BlockVolume=false") + if err != nil { + t.Errorf("Failed to disable feature gate for BlockVolume: %v", err) + return + } + } +} + +func togglePVExpandFeature(toggleFlag bool, t *testing.T) { + if toggleFlag { + // Enable alpha feature LocalStorageCapacityIsolation + err := utilfeature.DefaultFeatureGate.Set("ExpandPersistentVolumes=true") + if err != nil { + t.Errorf("Failed to enable feature gate for ExpandPersistentVolumes: %v", err) + return + } + } else { + err := utilfeature.DefaultFeatureGate.Set("ExpandPersistentVolumes=false") + if err != nil { + t.Errorf("Failed to disable feature gate for ExpandPersistentVolumes: %v", err) + return + } + } +} + +func TestValidateKeyToPath(t *testing.T) { + testCases := []struct { + kp core.KeyToPath + ok bool + errtype field.ErrorType + }{ + { + kp: core.KeyToPath{Key: "k", Path: "p"}, + ok: true, + }, + { + kp: core.KeyToPath{Key: "k", Path: "p/p/p/p"}, + ok: true, + }, + { + kp: core.KeyToPath{Key: "k", Path: "p/..p/p../p..p"}, + ok: true, + }, + { + kp: core.KeyToPath{Key: "k", Path: "p", Mode: newInt32(0644)}, + ok: true, + }, + { + kp: core.KeyToPath{Key: "", Path: "p"}, + ok: false, + errtype: field.ErrorTypeRequired, + }, + { + kp: core.KeyToPath{Key: "k", Path: ""}, + ok: false, + errtype: field.ErrorTypeRequired, + }, + { + kp: core.KeyToPath{Key: "k", Path: "..p"}, + ok: false, + errtype: field.ErrorTypeInvalid, + }, + { + kp: core.KeyToPath{Key: "k", Path: "../p"}, + ok: false, + errtype: field.ErrorTypeInvalid, + }, + { + kp: core.KeyToPath{Key: "k", Path: "p/../p"}, + ok: false, + errtype: field.ErrorTypeInvalid, + }, + { + kp: core.KeyToPath{Key: "k", Path: "p/.."}, + ok: false, + errtype: field.ErrorTypeInvalid, + }, + { + kp: core.KeyToPath{Key: "k", Path: "p", Mode: newInt32(01000)}, + ok: false, + errtype: field.ErrorTypeInvalid, + }, + { + kp: core.KeyToPath{Key: "k", Path: "p", Mode: newInt32(-1)}, + ok: false, + errtype: field.ErrorTypeInvalid, + }, + } + + for i, tc := range testCases { + errs := validateKeyToPath(&tc.kp, field.NewPath("field")) + if tc.ok && len(errs) > 0 { + t.Errorf("[%d] unexpected errors: %v", i, errs) + } else if !tc.ok && len(errs) == 0 { + t.Errorf("[%d] expected error type %v", i, tc.errtype) + } else if len(errs) > 1 { + t.Errorf("[%d] expected only one error, got %d", i, len(errs)) + } else if !tc.ok { + if errs[0].Type != tc.errtype { + t.Errorf("[%d] expected error type %v, got %v", i, tc.errtype, errs[0].Type) + } + } + } +} + +func TestValidateNFSVolumeSource(t *testing.T) { + testCases := []struct { + name string + nfs *core.NFSVolumeSource + errtype field.ErrorType + errfield string + errdetail string + }{ + { + name: "missing server", + nfs: &core.NFSVolumeSource{Server: "", Path: "/tmp"}, + errtype: field.ErrorTypeRequired, + errfield: "server", + }, + { + name: "missing path", + nfs: &core.NFSVolumeSource{Server: "my-server", Path: ""}, + errtype: field.ErrorTypeRequired, + errfield: "path", + }, + { + name: "abs path", + nfs: &core.NFSVolumeSource{Server: "my-server", Path: "tmp"}, + errtype: field.ErrorTypeInvalid, + errfield: "path", + errdetail: "must be an absolute path", + }, + } + + for i, tc := range testCases { + errs := validateNFSVolumeSource(tc.nfs, field.NewPath("field")) + + if len(errs) > 0 && tc.errtype == "" { + t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs) + } else if len(errs) == 0 && tc.errtype != "" { + t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype) + } else if len(errs) >= 1 { + if errs[0].Type != tc.errtype { + t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type) + } else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) { + t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field) + } else if !strings.Contains(errs[0].Detail, tc.errdetail) { + t.Errorf("[%d: %q] expected error detail %q, got %q", i, tc.name, tc.errdetail, errs[0].Detail) + } + } + } +} + +func TestValidateGlusterfs(t *testing.T) { + testCases := []struct { + name string + gfs *core.GlusterfsVolumeSource + errtype field.ErrorType + errfield string + }{ + { + name: "missing endpointname", + gfs: &core.GlusterfsVolumeSource{EndpointsName: "", Path: "/tmp"}, + errtype: field.ErrorTypeRequired, + errfield: "endpoints", + }, + { + name: "missing path", + gfs: &core.GlusterfsVolumeSource{EndpointsName: "my-endpoint", Path: ""}, + errtype: field.ErrorTypeRequired, + errfield: "path", + }, + { + name: "missing endpintname and path", + gfs: &core.GlusterfsVolumeSource{EndpointsName: "", Path: ""}, + errtype: field.ErrorTypeRequired, + errfield: "endpoints", + }, + } + + for i, tc := range testCases { + errs := validateGlusterfsVolumeSource(tc.gfs, field.NewPath("field")) + + if len(errs) > 0 && tc.errtype == "" { + t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs) + } else if len(errs) == 0 && tc.errtype != "" { + t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype) + } else if len(errs) >= 1 { + if errs[0].Type != tc.errtype { + t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type) + } else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) { + t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field) + } + } + } +} + +func TestValidateCSIVolumeSource(t *testing.T) { + testCases := []struct { + name string + csi *core.CSIPersistentVolumeSource + errtype field.ErrorType + errfield string + }{ + { + name: "all required fields ok", + csi: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true}, + }, + { + name: "with default values ok", + csi: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123"}, + }, + { + name: "missing driver name", + csi: &core.CSIPersistentVolumeSource{VolumeHandle: "test-123"}, + errtype: field.ErrorTypeRequired, + errfield: "driver", + }, + { + name: "missing volume handle", + csi: &core.CSIPersistentVolumeSource{Driver: "my-driver"}, + errtype: field.ErrorTypeRequired, + errfield: "volumeHandle", + }, + } + + err := utilfeature.DefaultFeatureGate.Set("CSIPersistentVolume=true") + if err != nil { + t.Errorf("Failed to enable feature gate for CSIPersistentVolumes: %v", err) + return + } + + for i, tc := range testCases { + errs := validateCSIPersistentVolumeSource(tc.csi, field.NewPath("field")) + + if len(errs) > 0 && tc.errtype == "" { + t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs) + } else if len(errs) == 0 && tc.errtype != "" { + t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype) + } else if len(errs) >= 1 { + if errs[0].Type != tc.errtype { + t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type) + } else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) { + t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field) + } + } + } + err = utilfeature.DefaultFeatureGate.Set("CSIPersistentVolume=false") + if err != nil { + t.Errorf("Failed to disable feature gate for CSIPersistentVolumes: %v", err) + return + } + +} + +// helper +func newInt32(val int) *int32 { + p := new(int32) + *p = int32(val) + return p +} + +// This test is a little too top-to-bottom. Ideally we would test each volume +// type on its own, but we want to also make sure that the logic works through +// the one-of wrapper, so we just do it all in one place. +func TestValidateVolumes(t *testing.T) { + validInitiatorName := "iqn.2015-02.example.com:init" + invalidInitiatorName := "2015-02.example.com:init" + testCases := []struct { + name string + vol core.Volume + errtype field.ErrorType + errfield string + errdetail string + }{ + // EmptyDir and basic volume names + { + name: "valid alpha name", + vol: core.Volume{ + Name: "empty", + VolumeSource: core.VolumeSource{ + EmptyDir: &core.EmptyDirVolumeSource{}, + }, + }, + }, + { + name: "valid num name", + vol: core.Volume{ + Name: "123", + VolumeSource: core.VolumeSource{ + EmptyDir: &core.EmptyDirVolumeSource{}, + }, + }, + }, + { + name: "valid alphanum name", + vol: core.Volume{ + Name: "empty-123", + VolumeSource: core.VolumeSource{ + EmptyDir: &core.EmptyDirVolumeSource{}, + }, + }, + }, + { + name: "valid numalpha name", + vol: core.Volume{ + Name: "123-empty", + VolumeSource: core.VolumeSource{ + EmptyDir: &core.EmptyDirVolumeSource{}, + }, + }, + }, + { + name: "zero-length name", + vol: core.Volume{ + Name: "", + VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}, + }, + errtype: field.ErrorTypeRequired, + errfield: "name", + }, + { + name: "name > 63 characters", + vol: core.Volume{ + Name: strings.Repeat("a", 64), + VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}, + }, + errtype: field.ErrorTypeInvalid, + errfield: "name", + errdetail: "must be no more than", + }, + { + name: "name not a DNS label", + vol: core.Volume{ + Name: "a.b.c", + VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}, + }, + errtype: field.ErrorTypeInvalid, + errfield: "name", + errdetail: dnsLabelErrMsg, + }, + // More than one source field specified. + { + name: "more than one source", + vol: core.Volume{ + Name: "dups", + VolumeSource: core.VolumeSource{ + EmptyDir: &core.EmptyDirVolumeSource{}, + HostPath: &core.HostPathVolumeSource{ + Path: "/mnt/path", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + }, + }, + errtype: field.ErrorTypeForbidden, + errfield: "hostPath", + errdetail: "may not specify more than 1 volume", + }, + // HostPath Default + { + name: "default HostPath", + vol: core.Volume{ + Name: "hostpath", + VolumeSource: core.VolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/mnt/path", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + }, + }, + }, + // HostPath Supported + { + name: "valid HostPath", + vol: core.Volume{ + Name: "hostpath", + VolumeSource: core.VolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/mnt/path", + Type: newHostPathType(string(core.HostPathSocket)), + }, + }, + }, + }, + // HostPath Invalid + { + name: "invalid HostPath", + vol: core.Volume{ + Name: "hostpath", + VolumeSource: core.VolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/mnt/path", + Type: newHostPathType("invalid"), + }, + }, + }, + errtype: field.ErrorTypeNotSupported, + errfield: "type", + }, + { + name: "invalid HostPath backsteps", + vol: core.Volume{ + Name: "hostpath", + VolumeSource: core.VolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/mnt/path/..", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "path", + errdetail: "must not contain '..'", + }, + // GcePersistentDisk + { + name: "valid GcePersistentDisk", + vol: core.Volume{ + Name: "gce-pd", + VolumeSource: core.VolumeSource{ + GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ + PDName: "my-PD", + FSType: "ext4", + Partition: 1, + ReadOnly: false, + }, + }, + }, + }, + // AWSElasticBlockStore + { + name: "valid AWSElasticBlockStore", + vol: core.Volume{ + Name: "aws-ebs", + VolumeSource: core.VolumeSource{ + AWSElasticBlockStore: &core.AWSElasticBlockStoreVolumeSource{ + VolumeID: "my-PD", + FSType: "ext4", + Partition: 1, + ReadOnly: false, + }, + }, + }, + }, + // GitRepo + { + name: "valid GitRepo", + vol: core.Volume{ + Name: "git-repo", + VolumeSource: core.VolumeSource{ + GitRepo: &core.GitRepoVolumeSource{ + Repository: "my-repo", + Revision: "hashstring", + Directory: "target", + }, + }, + }, + }, + { + name: "valid GitRepo in .", + vol: core.Volume{ + Name: "git-repo-dot", + VolumeSource: core.VolumeSource{ + GitRepo: &core.GitRepoVolumeSource{ + Repository: "my-repo", + Directory: ".", + }, + }, + }, + }, + { + name: "valid GitRepo with .. in name", + vol: core.Volume{ + Name: "git-repo-dot-dot-foo", + VolumeSource: core.VolumeSource{ + GitRepo: &core.GitRepoVolumeSource{ + Repository: "my-repo", + Directory: "..foo", + }, + }, + }, + }, + { + name: "GitRepo starts with ../", + vol: core.Volume{ + Name: "gitrepo", + VolumeSource: core.VolumeSource{ + GitRepo: &core.GitRepoVolumeSource{ + Repository: "foo", + Directory: "../dots/bar", + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "gitRepo.directory", + errdetail: `must not contain '..'`, + }, + { + name: "GitRepo contains ..", + vol: core.Volume{ + Name: "gitrepo", + VolumeSource: core.VolumeSource{ + GitRepo: &core.GitRepoVolumeSource{ + Repository: "foo", + Directory: "dots/../bar", + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "gitRepo.directory", + errdetail: `must not contain '..'`, + }, + { + name: "GitRepo absolute target", + vol: core.Volume{ + Name: "gitrepo", + VolumeSource: core.VolumeSource{ + GitRepo: &core.GitRepoVolumeSource{ + Repository: "foo", + Directory: "/abstarget", + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "gitRepo.directory", + }, + // ISCSI + { + name: "valid ISCSI", + vol: core.Volume{ + Name: "iscsi", + VolumeSource: core.VolumeSource{ + ISCSI: &core.ISCSIVolumeSource{ + TargetPortal: "127.0.0.1", + IQN: "iqn.2015-02.example.com:test", + Lun: 1, + FSType: "ext4", + ReadOnly: false, + }, + }, + }, + }, + { + name: "valid IQN: eui format", + vol: core.Volume{ + Name: "iscsi", + VolumeSource: core.VolumeSource{ + ISCSI: &core.ISCSIVolumeSource{ + TargetPortal: "127.0.0.1", + IQN: "eui.0123456789ABCDEF", + Lun: 1, + FSType: "ext4", + ReadOnly: false, + }, + }, + }, + }, + { + name: "valid IQN: naa format", + vol: core.Volume{ + Name: "iscsi", + VolumeSource: core.VolumeSource{ + ISCSI: &core.ISCSIVolumeSource{ + TargetPortal: "127.0.0.1", + IQN: "naa.62004567BA64678D0123456789ABCDEF", + Lun: 1, + FSType: "ext4", + ReadOnly: false, + }, + }, + }, + }, + { + name: "empty portal", + vol: core.Volume{ + Name: "iscsi", + VolumeSource: core.VolumeSource{ + ISCSI: &core.ISCSIVolumeSource{ + TargetPortal: "", + IQN: "iqn.2015-02.example.com:test", + Lun: 1, + FSType: "ext4", + ReadOnly: false, + }, + }, + }, + errtype: field.ErrorTypeRequired, + errfield: "iscsi.targetPortal", + }, + { + name: "empty iqn", + vol: core.Volume{ + Name: "iscsi", + VolumeSource: core.VolumeSource{ + ISCSI: &core.ISCSIVolumeSource{ + TargetPortal: "127.0.0.1", + IQN: "", + Lun: 1, + FSType: "ext4", + ReadOnly: false, + }, + }, + }, + errtype: field.ErrorTypeRequired, + errfield: "iscsi.iqn", + }, + { + name: "invalid IQN: iqn format", + vol: core.Volume{ + Name: "iscsi", + VolumeSource: core.VolumeSource{ + ISCSI: &core.ISCSIVolumeSource{ + TargetPortal: "127.0.0.1", + IQN: "iqn.2015-02.example.com:test;ls;", + Lun: 1, + FSType: "ext4", + ReadOnly: false, + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "iscsi.iqn", + }, + { + name: "invalid IQN: eui format", + vol: core.Volume{ + Name: "iscsi", + VolumeSource: core.VolumeSource{ + ISCSI: &core.ISCSIVolumeSource{ + TargetPortal: "127.0.0.1", + IQN: "eui.0123456789ABCDEFGHIJ", + Lun: 1, + FSType: "ext4", + ReadOnly: false, + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "iscsi.iqn", + }, + { + name: "invalid IQN: naa format", + vol: core.Volume{ + Name: "iscsi", + VolumeSource: core.VolumeSource{ + ISCSI: &core.ISCSIVolumeSource{ + TargetPortal: "127.0.0.1", + IQN: "naa.62004567BA_4-78D.123456789ABCDEF", + Lun: 1, + FSType: "ext4", + ReadOnly: false, + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "iscsi.iqn", + }, + { + name: "valid initiatorName", + vol: core.Volume{ + Name: "iscsi", + VolumeSource: core.VolumeSource{ + ISCSI: &core.ISCSIVolumeSource{ + TargetPortal: "127.0.0.1", + IQN: "iqn.2015-02.example.com:test", + Lun: 1, + InitiatorName: &validInitiatorName, + FSType: "ext4", + ReadOnly: false, + }, + }, + }, + }, + { + name: "invalid initiatorName", + vol: core.Volume{ + Name: "iscsi", + VolumeSource: core.VolumeSource{ + ISCSI: &core.ISCSIVolumeSource{ + TargetPortal: "127.0.0.1", + IQN: "iqn.2015-02.example.com:test", + Lun: 1, + InitiatorName: &invalidInitiatorName, + FSType: "ext4", + ReadOnly: false, + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "iscsi.initiatorname", + }, + { + name: "empty secret", + vol: core.Volume{ + Name: "iscsi", + VolumeSource: core.VolumeSource{ + ISCSI: &core.ISCSIVolumeSource{ + TargetPortal: "127.0.0.1", + IQN: "iqn.2015-02.example.com:test", + Lun: 1, + FSType: "ext4", + ReadOnly: false, + DiscoveryCHAPAuth: true, + }, + }, + }, + errtype: field.ErrorTypeRequired, + errfield: "iscsi.secretRef", + }, + { + name: "empty secret", + vol: core.Volume{ + Name: "iscsi", + VolumeSource: core.VolumeSource{ + ISCSI: &core.ISCSIVolumeSource{ + TargetPortal: "127.0.0.1", + IQN: "iqn.2015-02.example.com:test", + Lun: 1, + FSType: "ext4", + ReadOnly: false, + SessionCHAPAuth: true, + }, + }, + }, + errtype: field.ErrorTypeRequired, + errfield: "iscsi.secretRef", + }, + // Secret + { + name: "valid Secret", + vol: core.Volume{ + Name: "secret", + VolumeSource: core.VolumeSource{ + Secret: &core.SecretVolumeSource{ + SecretName: "my-secret", + }, + }, + }, + }, + { + name: "valid Secret with defaultMode", + vol: core.Volume{ + Name: "secret", + VolumeSource: core.VolumeSource{ + Secret: &core.SecretVolumeSource{ + SecretName: "my-secret", + DefaultMode: newInt32(0644), + }, + }, + }, + }, + { + name: "valid Secret with projection and mode", + vol: core.Volume{ + Name: "secret", + VolumeSource: core.VolumeSource{ + Secret: &core.SecretVolumeSource{ + SecretName: "my-secret", + Items: []core.KeyToPath{{ + Key: "key", + Path: "filename", + Mode: newInt32(0644), + }}, + }, + }, + }, + }, + { + name: "valid Secret with subdir projection", + vol: core.Volume{ + Name: "secret", + VolumeSource: core.VolumeSource{ + Secret: &core.SecretVolumeSource{ + SecretName: "my-secret", + Items: []core.KeyToPath{{ + Key: "key", + Path: "dir/filename", + }}, + }, + }, + }, + }, + { + name: "secret with missing path", + vol: core.Volume{ + Name: "secret", + VolumeSource: core.VolumeSource{ + Secret: &core.SecretVolumeSource{ + SecretName: "s", + Items: []core.KeyToPath{{Key: "key", Path: ""}}, + }, + }, + }, + errtype: field.ErrorTypeRequired, + errfield: "secret.items[0].path", + }, + { + name: "secret with leading ..", + vol: core.Volume{ + Name: "secret", + VolumeSource: core.VolumeSource{ + Secret: &core.SecretVolumeSource{ + SecretName: "s", + Items: []core.KeyToPath{{Key: "key", Path: "../foo"}}, + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "secret.items[0].path", + }, + { + name: "secret with .. inside", + vol: core.Volume{ + Name: "secret", + VolumeSource: core.VolumeSource{ + Secret: &core.SecretVolumeSource{ + SecretName: "s", + Items: []core.KeyToPath{{Key: "key", Path: "foo/../bar"}}, + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "secret.items[0].path", + }, + { + name: "secret with invalid positive defaultMode", + vol: core.Volume{ + Name: "secret", + VolumeSource: core.VolumeSource{ + Secret: &core.SecretVolumeSource{ + SecretName: "s", + DefaultMode: newInt32(01000), + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "secret.defaultMode", + }, + { + name: "secret with invalid negative defaultMode", + vol: core.Volume{ + Name: "secret", + VolumeSource: core.VolumeSource{ + Secret: &core.SecretVolumeSource{ + SecretName: "s", + DefaultMode: newInt32(-1), + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "secret.defaultMode", + }, + // ConfigMap + { + name: "valid ConfigMap", + vol: core.Volume{ + Name: "cfgmap", + VolumeSource: core.VolumeSource{ + ConfigMap: &core.ConfigMapVolumeSource{ + LocalObjectReference: core.LocalObjectReference{ + Name: "my-cfgmap", + }, + }, + }, + }, + }, + { + name: "valid ConfigMap with defaultMode", + vol: core.Volume{ + Name: "cfgmap", + VolumeSource: core.VolumeSource{ + ConfigMap: &core.ConfigMapVolumeSource{ + LocalObjectReference: core.LocalObjectReference{ + Name: "my-cfgmap", + }, + DefaultMode: newInt32(0644), + }, + }, + }, + }, + { + name: "valid ConfigMap with projection and mode", + vol: core.Volume{ + Name: "cfgmap", + VolumeSource: core.VolumeSource{ + ConfigMap: &core.ConfigMapVolumeSource{ + LocalObjectReference: core.LocalObjectReference{ + Name: "my-cfgmap"}, + Items: []core.KeyToPath{{ + Key: "key", + Path: "filename", + Mode: newInt32(0644), + }}, + }, + }, + }, + }, + { + name: "valid ConfigMap with subdir projection", + vol: core.Volume{ + Name: "cfgmap", + VolumeSource: core.VolumeSource{ + ConfigMap: &core.ConfigMapVolumeSource{ + LocalObjectReference: core.LocalObjectReference{ + Name: "my-cfgmap"}, + Items: []core.KeyToPath{{ + Key: "key", + Path: "dir/filename", + }}, + }, + }, + }, + }, + { + name: "configmap with missing path", + vol: core.Volume{ + Name: "cfgmap", + VolumeSource: core.VolumeSource{ + ConfigMap: &core.ConfigMapVolumeSource{ + LocalObjectReference: core.LocalObjectReference{Name: "c"}, + Items: []core.KeyToPath{{Key: "key", Path: ""}}, + }, + }, + }, + errtype: field.ErrorTypeRequired, + errfield: "configMap.items[0].path", + }, + { + name: "configmap with leading ..", + vol: core.Volume{ + Name: "cfgmap", + VolumeSource: core.VolumeSource{ + ConfigMap: &core.ConfigMapVolumeSource{ + LocalObjectReference: core.LocalObjectReference{Name: "c"}, + Items: []core.KeyToPath{{Key: "key", Path: "../foo"}}, + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "configMap.items[0].path", + }, + { + name: "configmap with .. inside", + vol: core.Volume{ + Name: "cfgmap", + VolumeSource: core.VolumeSource{ + ConfigMap: &core.ConfigMapVolumeSource{ + LocalObjectReference: core.LocalObjectReference{Name: "c"}, + Items: []core.KeyToPath{{Key: "key", Path: "foo/../bar"}}, + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "configMap.items[0].path", + }, + { + name: "configmap with invalid positive defaultMode", + vol: core.Volume{ + Name: "cfgmap", + VolumeSource: core.VolumeSource{ + ConfigMap: &core.ConfigMapVolumeSource{ + LocalObjectReference: core.LocalObjectReference{Name: "c"}, + DefaultMode: newInt32(01000), + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "configMap.defaultMode", + }, + { + name: "configmap with invalid negative defaultMode", + vol: core.Volume{ + Name: "cfgmap", + VolumeSource: core.VolumeSource{ + ConfigMap: &core.ConfigMapVolumeSource{ + LocalObjectReference: core.LocalObjectReference{Name: "c"}, + DefaultMode: newInt32(-1), + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "configMap.defaultMode", + }, + // Glusterfs + { + name: "valid Glusterfs", + vol: core.Volume{ + Name: "glusterfs", + VolumeSource: core.VolumeSource{ + Glusterfs: &core.GlusterfsVolumeSource{ + EndpointsName: "host1", + Path: "path", + ReadOnly: false, + }, + }, + }, + }, + { + name: "empty hosts", + vol: core.Volume{ + Name: "glusterfs", + VolumeSource: core.VolumeSource{ + Glusterfs: &core.GlusterfsVolumeSource{ + EndpointsName: "", + Path: "path", + ReadOnly: false, + }, + }, + }, + errtype: field.ErrorTypeRequired, + errfield: "glusterfs.endpoints", + }, + { + name: "empty path", + vol: core.Volume{ + Name: "glusterfs", + VolumeSource: core.VolumeSource{ + Glusterfs: &core.GlusterfsVolumeSource{ + EndpointsName: "host", + Path: "", + ReadOnly: false, + }, + }, + }, + errtype: field.ErrorTypeRequired, + errfield: "glusterfs.path", + }, + // Flocker + { + name: "valid Flocker -- datasetUUID", + vol: core.Volume{ + Name: "flocker", + VolumeSource: core.VolumeSource{ + Flocker: &core.FlockerVolumeSource{ + DatasetUUID: "d846b09d-223d-43df-ab5b-d6db2206a0e4", + }, + }, + }, + }, + { + name: "valid Flocker -- datasetName", + vol: core.Volume{ + Name: "flocker", + VolumeSource: core.VolumeSource{ + Flocker: &core.FlockerVolumeSource{ + DatasetName: "datasetName", + }, + }, + }, + }, + { + name: "both empty", + vol: core.Volume{ + Name: "flocker", + VolumeSource: core.VolumeSource{ + Flocker: &core.FlockerVolumeSource{ + DatasetName: "", + }, + }, + }, + errtype: field.ErrorTypeRequired, + errfield: "flocker", + }, + { + name: "both specified", + vol: core.Volume{ + Name: "flocker", + VolumeSource: core.VolumeSource{ + Flocker: &core.FlockerVolumeSource{ + DatasetName: "datasetName", + DatasetUUID: "d846b09d-223d-43df-ab5b-d6db2206a0e4", + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "flocker", + }, + { + name: "slash in flocker datasetName", + vol: core.Volume{ + Name: "flocker", + VolumeSource: core.VolumeSource{ + Flocker: &core.FlockerVolumeSource{ + DatasetName: "foo/bar", + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "flocker.datasetName", + errdetail: "must not contain '/'", + }, + // RBD + { + name: "valid RBD", + vol: core.Volume{ + Name: "rbd", + VolumeSource: core.VolumeSource{ + RBD: &core.RBDVolumeSource{ + CephMonitors: []string{"foo"}, + RBDImage: "bar", + FSType: "ext4", + }, + }, + }, + }, + { + name: "empty rbd monitors", + vol: core.Volume{ + Name: "rbd", + VolumeSource: core.VolumeSource{ + RBD: &core.RBDVolumeSource{ + CephMonitors: []string{}, + RBDImage: "bar", + FSType: "ext4", + }, + }, + }, + errtype: field.ErrorTypeRequired, + errfield: "rbd.monitors", + }, + { + name: "empty image", + vol: core.Volume{ + Name: "rbd", + VolumeSource: core.VolumeSource{ + RBD: &core.RBDVolumeSource{ + CephMonitors: []string{"foo"}, + RBDImage: "", + FSType: "ext4", + }, + }, + }, + errtype: field.ErrorTypeRequired, + errfield: "rbd.image", + }, + // Cinder + { + name: "valid Cinder", + vol: core.Volume{ + Name: "cinder", + VolumeSource: core.VolumeSource{ + Cinder: &core.CinderVolumeSource{ + VolumeID: "29ea5088-4f60-4757-962e-dba678767887", + FSType: "ext4", + ReadOnly: false, + }, + }, + }, + }, + // CephFS + { + name: "valid CephFS", + vol: core.Volume{ + Name: "cephfs", + VolumeSource: core.VolumeSource{ + CephFS: &core.CephFSVolumeSource{ + Monitors: []string{"foo"}, + }, + }, + }, + }, + { + name: "empty cephfs monitors", + vol: core.Volume{ + Name: "cephfs", + VolumeSource: core.VolumeSource{ + CephFS: &core.CephFSVolumeSource{ + Monitors: []string{}, + }, + }, + }, + errtype: field.ErrorTypeRequired, + errfield: "cephfs.monitors", + }, + // DownwardAPI + { + name: "valid DownwardAPI", + vol: core.Volume{ + Name: "downwardapi", + VolumeSource: core.VolumeSource{ + DownwardAPI: &core.DownwardAPIVolumeSource{ + Items: []core.DownwardAPIVolumeFile{ + { + Path: "labels", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.labels", + }, + }, + { + Path: "labels with subscript", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.labels['key']", + }, + }, + { + Path: "labels with complex subscript", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.labels['test.example.com/key']", + }, + }, + { + Path: "annotations", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.annotations", + }, + }, + { + Path: "annotations with subscript", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.annotations['key']", + }, + }, + { + Path: "annotations with complex subscript", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.annotations['TEST.EXAMPLE.COM/key']", + }, + }, + { + Path: "namespace", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.namespace", + }, + }, + { + Path: "name", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.name", + }, + }, + { + Path: "path/with/subdirs", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.labels", + }, + }, + { + Path: "path/./withdot", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.labels", + }, + }, + { + Path: "path/with/embedded..dotdot", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.labels", + }, + }, + { + Path: "path/with/leading/..dotdot", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.labels", + }, + }, + { + Path: "cpu_limit", + ResourceFieldRef: &core.ResourceFieldSelector{ + ContainerName: "test-container", + Resource: "limits.cpu", + }, + }, + { + Path: "cpu_request", + ResourceFieldRef: &core.ResourceFieldSelector{ + ContainerName: "test-container", + Resource: "requests.cpu", + }, + }, + { + Path: "memory_limit", + ResourceFieldRef: &core.ResourceFieldSelector{ + ContainerName: "test-container", + Resource: "limits.memory", + }, + }, + { + Path: "memory_request", + ResourceFieldRef: &core.ResourceFieldSelector{ + ContainerName: "test-container", + Resource: "requests.memory", + }, + }, + }, + }, + }, + }, + }, + { + name: "downapi valid defaultMode", + vol: core.Volume{ + Name: "downapi", + VolumeSource: core.VolumeSource{ + DownwardAPI: &core.DownwardAPIVolumeSource{ + DefaultMode: newInt32(0644), + }, + }, + }, + }, + { + name: "downapi valid item mode", + vol: core.Volume{ + Name: "downapi", + VolumeSource: core.VolumeSource{ + DownwardAPI: &core.DownwardAPIVolumeSource{ + Items: []core.DownwardAPIVolumeFile{{ + Mode: newInt32(0644), + Path: "path", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.labels", + }, + }}, + }, + }, + }, + }, + { + name: "downapi invalid positive item mode", + vol: core.Volume{ + Name: "downapi", + VolumeSource: core.VolumeSource{ + DownwardAPI: &core.DownwardAPIVolumeSource{ + Items: []core.DownwardAPIVolumeFile{{ + Mode: newInt32(01000), + Path: "path", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.labels", + }, + }}, + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "downwardAPI.mode", + }, + { + name: "downapi invalid negative item mode", + vol: core.Volume{ + Name: "downapi", + VolumeSource: core.VolumeSource{ + DownwardAPI: &core.DownwardAPIVolumeSource{ + Items: []core.DownwardAPIVolumeFile{{ + Mode: newInt32(-1), + Path: "path", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.labels", + }, + }}, + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "downwardAPI.mode", + }, + { + name: "downapi empty metatada path", + vol: core.Volume{ + Name: "downapi", + VolumeSource: core.VolumeSource{ + DownwardAPI: &core.DownwardAPIVolumeSource{ + Items: []core.DownwardAPIVolumeFile{{ + Path: "", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.labels", + }, + }}, + }, + }, + }, + errtype: field.ErrorTypeRequired, + errfield: "downwardAPI.path", + }, + { + name: "downapi absolute path", + vol: core.Volume{ + Name: "downapi", + VolumeSource: core.VolumeSource{ + DownwardAPI: &core.DownwardAPIVolumeSource{ + Items: []core.DownwardAPIVolumeFile{{ + Path: "/absolutepath", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.labels", + }, + }}, + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "downwardAPI.path", + }, + { + name: "downapi dot dot path", + vol: core.Volume{ + Name: "downapi", + VolumeSource: core.VolumeSource{ + DownwardAPI: &core.DownwardAPIVolumeSource{ + Items: []core.DownwardAPIVolumeFile{{ + Path: "../../passwd", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.labels", + }, + }}, + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "downwardAPI.path", + errdetail: `must not contain '..'`, + }, + { + name: "downapi dot dot file name", + vol: core.Volume{ + Name: "downapi", + VolumeSource: core.VolumeSource{ + DownwardAPI: &core.DownwardAPIVolumeSource{ + Items: []core.DownwardAPIVolumeFile{{ + Path: "..badFileName", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.labels", + }, + }}, + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "downwardAPI.path", + errdetail: `must not start with '..'`, + }, + { + name: "downapi dot dot first level dirent", + vol: core.Volume{ + Name: "downapi", + VolumeSource: core.VolumeSource{ + DownwardAPI: &core.DownwardAPIVolumeSource{ + Items: []core.DownwardAPIVolumeFile{{ + Path: "..badDirName/goodFileName", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.labels", + }, + }}, + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "downwardAPI.path", + errdetail: `must not start with '..'`, + }, + { + name: "downapi fieldRef and ResourceFieldRef together", + vol: core.Volume{ + Name: "downapi", + VolumeSource: core.VolumeSource{ + DownwardAPI: &core.DownwardAPIVolumeSource{ + Items: []core.DownwardAPIVolumeFile{{ + Path: "test", + FieldRef: &core.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.labels", + }, + ResourceFieldRef: &core.ResourceFieldSelector{ + ContainerName: "test-container", + Resource: "requests.memory", + }, + }}, + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "downwardAPI", + errdetail: "fieldRef and resourceFieldRef can not be specified simultaneously", + }, + { + name: "downapi invalid positive defaultMode", + vol: core.Volume{ + Name: "downapi", + VolumeSource: core.VolumeSource{ + DownwardAPI: &core.DownwardAPIVolumeSource{ + DefaultMode: newInt32(01000), + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "downwardAPI.defaultMode", + }, + { + name: "downapi invalid negative defaultMode", + vol: core.Volume{ + Name: "downapi", + VolumeSource: core.VolumeSource{ + DownwardAPI: &core.DownwardAPIVolumeSource{ + DefaultMode: newInt32(-1), + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "downwardAPI.defaultMode", + }, + // FC + { + name: "FC valid targetWWNs and lun", + vol: core.Volume{ + Name: "fc", + VolumeSource: core.VolumeSource{ + FC: &core.FCVolumeSource{ + TargetWWNs: []string{"some_wwn"}, + Lun: newInt32(1), + FSType: "ext4", + ReadOnly: false, + }, + }, + }, + }, + { + name: "FC valid wwids", + vol: core.Volume{ + Name: "fc", + VolumeSource: core.VolumeSource{ + FC: &core.FCVolumeSource{ + WWIDs: []string{"some_wwid"}, + FSType: "ext4", + ReadOnly: false, + }, + }, + }, + }, + { + name: "FC empty targetWWNs and wwids", + vol: core.Volume{ + Name: "fc", + VolumeSource: core.VolumeSource{ + FC: &core.FCVolumeSource{ + TargetWWNs: []string{}, + Lun: newInt32(1), + WWIDs: []string{}, + FSType: "ext4", + ReadOnly: false, + }, + }, + }, + errtype: field.ErrorTypeRequired, + errfield: "fc.targetWWNs", + errdetail: "must specify either targetWWNs or wwids", + }, + { + name: "FC invalid: both targetWWNs and wwids simultaneously", + vol: core.Volume{ + Name: "fc", + VolumeSource: core.VolumeSource{ + FC: &core.FCVolumeSource{ + TargetWWNs: []string{"some_wwn"}, + Lun: newInt32(1), + WWIDs: []string{"some_wwid"}, + FSType: "ext4", + ReadOnly: false, + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "fc.targetWWNs", + errdetail: "targetWWNs and wwids can not be specified simultaneously", + }, + { + name: "FC valid targetWWNs and empty lun", + vol: core.Volume{ + Name: "fc", + VolumeSource: core.VolumeSource{ + FC: &core.FCVolumeSource{ + TargetWWNs: []string{"wwn"}, + Lun: nil, + FSType: "ext4", + ReadOnly: false, + }, + }, + }, + errtype: field.ErrorTypeRequired, + errfield: "fc.lun", + errdetail: "lun is required if targetWWNs is specified", + }, + { + name: "FC valid targetWWNs and invalid lun", + vol: core.Volume{ + Name: "fc", + VolumeSource: core.VolumeSource{ + FC: &core.FCVolumeSource{ + TargetWWNs: []string{"wwn"}, + Lun: newInt32(256), + FSType: "ext4", + ReadOnly: false, + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "fc.lun", + errdetail: validation.InclusiveRangeError(0, 255), + }, + // FlexVolume + { + name: "valid FlexVolume", + vol: core.Volume{ + Name: "flex-volume", + VolumeSource: core.VolumeSource{ + FlexVolume: &core.FlexVolumeSource{ + Driver: "kubernetes.io/blue", + FSType: "ext4", + }, + }, + }, + }, + // AzureFile + { + name: "valid AzureFile", + vol: core.Volume{ + Name: "azure-file", + VolumeSource: core.VolumeSource{ + AzureFile: &core.AzureFileVolumeSource{ + SecretName: "key", + ShareName: "share", + ReadOnly: false, + }, + }, + }, + }, + { + name: "AzureFile empty secret", + vol: core.Volume{ + Name: "azure-file", + VolumeSource: core.VolumeSource{ + AzureFile: &core.AzureFileVolumeSource{ + SecretName: "", + ShareName: "share", + ReadOnly: false, + }, + }, + }, + errtype: field.ErrorTypeRequired, + errfield: "azureFile.secretName", + }, + { + name: "AzureFile empty share", + vol: core.Volume{ + Name: "azure-file", + VolumeSource: core.VolumeSource{ + AzureFile: &core.AzureFileVolumeSource{ + SecretName: "name", + ShareName: "", + ReadOnly: false, + }, + }, + }, + errtype: field.ErrorTypeRequired, + errfield: "azureFile.shareName", + }, + // Quobyte + { + name: "valid Quobyte", + vol: core.Volume{ + Name: "quobyte", + VolumeSource: core.VolumeSource{ + Quobyte: &core.QuobyteVolumeSource{ + Registry: "registry:7861", + Volume: "volume", + ReadOnly: false, + User: "root", + Group: "root", + }, + }, + }, + }, + { + name: "empty registry quobyte", + vol: core.Volume{ + Name: "quobyte", + VolumeSource: core.VolumeSource{ + Quobyte: &core.QuobyteVolumeSource{ + Volume: "/test", + }, + }, + }, + errtype: field.ErrorTypeRequired, + errfield: "quobyte.registry", + }, + { + name: "wrong format registry quobyte", + vol: core.Volume{ + Name: "quobyte", + VolumeSource: core.VolumeSource{ + Quobyte: &core.QuobyteVolumeSource{ + Registry: "registry7861", + Volume: "/test", + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "quobyte.registry", + }, + { + name: "wrong format multiple registries quobyte", + vol: core.Volume{ + Name: "quobyte", + VolumeSource: core.VolumeSource{ + Quobyte: &core.QuobyteVolumeSource{ + Registry: "registry:7861,reg2", + Volume: "/test", + }, + }, + }, + errtype: field.ErrorTypeInvalid, + errfield: "quobyte.registry", + }, + { + name: "empty volume quobyte", + vol: core.Volume{ + Name: "quobyte", + VolumeSource: core.VolumeSource{ + Quobyte: &core.QuobyteVolumeSource{ + Registry: "registry:7861", + }, + }, + }, + errtype: field.ErrorTypeRequired, + errfield: "quobyte.volume", + }, + // AzureDisk + { + name: "valid AzureDisk", + vol: core.Volume{ + Name: "azure-disk", + VolumeSource: core.VolumeSource{ + AzureDisk: &core.AzureDiskVolumeSource{ + DiskName: "foo", + DataDiskURI: "https://blob/vhds/bar.vhd", + }, + }, + }, + }, + { + name: "AzureDisk empty disk name", + vol: core.Volume{ + Name: "azure-disk", + VolumeSource: core.VolumeSource{ + AzureDisk: &core.AzureDiskVolumeSource{ + DiskName: "", + DataDiskURI: "https://blob/vhds/bar.vhd", + }, + }, + }, + errtype: field.ErrorTypeRequired, + errfield: "azureDisk.diskName", + }, + { + name: "AzureDisk empty disk uri", + vol: core.Volume{ + Name: "azure-disk", + VolumeSource: core.VolumeSource{ + AzureDisk: &core.AzureDiskVolumeSource{ + DiskName: "foo", + DataDiskURI: "", + }, + }, + }, + errtype: field.ErrorTypeRequired, + errfield: "azureDisk.diskURI", + }, + // ScaleIO + { + name: "valid scaleio volume", + vol: core.Volume{ + Name: "scaleio-volume", + VolumeSource: core.VolumeSource{ + ScaleIO: &core.ScaleIOVolumeSource{ + Gateway: "http://abcd/efg", + System: "test-system", + VolumeName: "test-vol-1", + }, + }, + }, + }, + { + name: "ScaleIO with empty name", + vol: core.Volume{ + Name: "scaleio-volume", + VolumeSource: core.VolumeSource{ + ScaleIO: &core.ScaleIOVolumeSource{ + Gateway: "http://abcd/efg", + System: "test-system", + VolumeName: "", + }, + }, + }, + errtype: field.ErrorTypeRequired, + errfield: "scaleIO.volumeName", + }, + { + name: "ScaleIO with empty gateway", + vol: core.Volume{ + Name: "scaleio-volume", + VolumeSource: core.VolumeSource{ + ScaleIO: &core.ScaleIOVolumeSource{ + Gateway: "", + System: "test-system", + VolumeName: "test-vol-1", + }, + }, + }, + errtype: field.ErrorTypeRequired, + errfield: "scaleIO.gateway", + }, + { + name: "ScaleIO with empty system", + vol: core.Volume{ + Name: "scaleio-volume", + VolumeSource: core.VolumeSource{ + ScaleIO: &core.ScaleIOVolumeSource{ + Gateway: "http://agc/efg/gateway", + System: "", + VolumeName: "test-vol-1", + }, + }, + }, + errtype: field.ErrorTypeRequired, + errfield: "scaleIO.system", + }, + } + + for i, tc := range testCases { + names, errs := ValidateVolumes([]core.Volume{tc.vol}, field.NewPath("field")) + if len(errs) > 0 && tc.errtype == "" { + t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs) + } else if len(errs) > 1 { + t.Errorf("[%d: %q] expected 1 error, got %d: %v", i, tc.name, len(errs), errs) + } else if len(errs) == 0 && tc.errtype != "" { + t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype) + } else if len(errs) == 1 { + if errs[0].Type != tc.errtype { + t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type) + } else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) { + t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field) + } else if !strings.Contains(errs[0].Detail, tc.errdetail) { + t.Errorf("[%d: %q] expected error detail %q, got %q", i, tc.name, tc.errdetail, errs[0].Detail) + } + } else { + if len(names) != 1 || !IsMatchedVolume(tc.vol.Name, names) { + t.Errorf("[%d: %q] wrong names result: %v", i, tc.name, names) + } + } + } + + dupsCase := []core.Volume{ + {Name: "abc", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}, + {Name: "abc", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}, + } + _, errs := ValidateVolumes(dupsCase, field.NewPath("field")) + if len(errs) == 0 { + t.Errorf("expected error") + } else if len(errs) != 1 { + t.Errorf("expected 1 error, got %d: %v", len(errs), errs) + } else if errs[0].Type != field.ErrorTypeDuplicate { + t.Errorf("expected error type %v, got %v", field.ErrorTypeDuplicate, errs[0].Type) + } + + // Validate HugePages medium type for EmptyDir when HugePages feature is enabled/disabled + hugePagesCase := core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{Medium: core.StorageMediumHugePages}} + + // Enable alpha feature HugePages + err := utilfeature.DefaultFeatureGate.Set("HugePages=true") + if err != nil { + t.Errorf("Failed to enable feature gate for HugePages: %v", err) + } + if errs := validateVolumeSource(&hugePagesCase, field.NewPath("field").Index(0), "working"); len(errs) != 0 { + t.Errorf("Unexpected error when HugePages feature is enabled.") + } + + // Disable alpha feature HugePages + err = utilfeature.DefaultFeatureGate.Set("HugePages=false") + if err != nil { + t.Errorf("Failed to disable feature gate for HugePages: %v", err) + } + if errs := validateVolumeSource(&hugePagesCase, field.NewPath("field").Index(0), "failing"); len(errs) == 0 { + t.Errorf("Expected error when HugePages feature is disabled got nothing.") + } + +} + +func TestAlphaHugePagesIsolation(t *testing.T) { + successCases := []core.Pod{ + { // Basic fields. + ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, + Spec: core.PodSpec{ + Containers: []core.Container{ + { + Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), + }, + Limits: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), + }, + }, + }, + }, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + }, + } + failureCases := []core.Pod{ + { // Basic fields. + ObjectMeta: metav1.ObjectMeta{Name: "hugepages-requireCpuOrMemory", Namespace: "ns"}, + Spec: core.PodSpec{ + Containers: []core.Container{ + { + Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), + }, + Limits: core.ResourceList{ + core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), + }, + }, + }, + }, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + }, + { // Basic fields. + ObjectMeta: metav1.ObjectMeta{Name: "hugepages-shared", Namespace: "ns"}, + Spec: core.PodSpec{ + Containers: []core.Container{ + { + Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), + }, + Limits: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName("hugepages-2Mi"): resource.MustParse("2Gi"), + }, + }, + }, + }, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + }, + { // Basic fields. + ObjectMeta: metav1.ObjectMeta{Name: "hugepages-multiple", Namespace: "ns"}, + Spec: core.PodSpec{ + Containers: []core.Container{ + { + Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), + }, + Limits: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), + core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), + }, + }, + }, + }, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + }, + } + // Enable alpha feature HugePages + err := utilfeature.DefaultFeatureGate.Set("HugePages=true") + if err != nil { + t.Errorf("Failed to enable feature gate for HugePages: %v", err) + return + } + for i := range successCases { + pod := &successCases[i] + if errs := ValidatePod(pod); len(errs) != 0 { + t.Errorf("Unexpected error for case[%d], err: %v", i, errs) + } + } + for i := range failureCases { + pod := &failureCases[i] + if errs := ValidatePod(pod); len(errs) == 0 { + t.Errorf("Expected error for case[%d], pod: %v", i, pod.Name) + } + } + // Disable alpha feature HugePages + err = utilfeature.DefaultFeatureGate.Set("HugePages=false") + if err != nil { + t.Errorf("Failed to disable feature gate for HugePages: %v", err) + return + } + // Disable alpha feature HugePages and ensure all success cases fail + for i := range successCases { + pod := &successCases[i] + if errs := ValidatePod(pod); len(errs) == 0 { + t.Errorf("Expected error for case[%d], pod: %v", i, pod.Name) + } + } +} + +func TestAlphaPVCVolumeMode(t *testing.T) { + // Enable alpha feature BlockVolume for PVC + err := utilfeature.DefaultFeatureGate.Set("BlockVolume=true") + if err != nil { + t.Errorf("Failed to enable feature gate for BlockVolume: %v", err) + return + } + + block := core.PersistentVolumeBlock + file := core.PersistentVolumeFilesystem + fake := core.PersistentVolumeMode("fake") + empty := core.PersistentVolumeMode("") + + // Success Cases + successCasesPVC := map[string]*core.PersistentVolumeClaim{ + "valid block value": createTestVolModePVC(&block), + "valid filesystem value": createTestVolModePVC(&file), + "valid nil value": createTestVolModePVC(nil), + } + for k, v := range successCasesPVC { + if errs := ValidatePersistentVolumeClaim(v); len(errs) != 0 { + t.Errorf("expected success for %s", k) + } + } + + // Error Cases + errorCasesPVC := map[string]*core.PersistentVolumeClaim{ + "invalid value": createTestVolModePVC(&fake), + "empty value": createTestVolModePVC(&empty), + } + for k, v := range errorCasesPVC { + if errs := ValidatePersistentVolumeClaim(v); len(errs) == 0 { + t.Errorf("expected failure for %s", k) + } + } +} + +func TestAlphaPVVolumeMode(t *testing.T) { + // Enable alpha feature BlockVolume for PV + err := utilfeature.DefaultFeatureGate.Set("BlockVolume=true") + if err != nil { + t.Errorf("Failed to enable feature gate for BlockVolume: %v", err) + return + } + + block := core.PersistentVolumeBlock + file := core.PersistentVolumeFilesystem + fake := core.PersistentVolumeMode("fake") + empty := core.PersistentVolumeMode("") + + // Success Cases + successCasesPV := map[string]*core.PersistentVolume{ + "valid block value": createTestVolModePV(&block), + "valid filesystem value": createTestVolModePV(&file), + "valid nil value": createTestVolModePV(nil), + } + for k, v := range successCasesPV { + if errs := ValidatePersistentVolume(v); len(errs) != 0 { + t.Errorf("expected success for %s", k) + } + } + + // Error Cases + errorCasesPV := map[string]*core.PersistentVolume{ + "invalid value": createTestVolModePV(&fake), + "empty value": createTestVolModePV(&empty), + } + for k, v := range errorCasesPV { + if errs := ValidatePersistentVolume(v); len(errs) == 0 { + t.Errorf("expected failure for %s", k) + } + } +} + +func createTestVolModePVC(vmode *core.PersistentVolumeMode) *core.PersistentVolumeClaim { + validName := "valid-storage-class" + + pvc := core.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "default", + }, + Spec: core.PersistentVolumeClaimSpec{ + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + StorageClassName: &validName, + VolumeMode: vmode, + }, + } + return &pvc +} + +func createTestVolModePV(vmode *core.PersistentVolumeMode) *core.PersistentVolume { + + // PersistentVolume with VolumeMode set (valid and invalid) + pv := core.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "", + }, + Spec: core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/foo", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + }, + StorageClassName: "test-storage-class", + VolumeMode: vmode, + }, + } + return &pv +} + +func createTestPV() *core.PersistentVolume { + + // PersistentVolume with VolumeMode set (valid and invalid) + pv := core.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "", + }, + Spec: core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, + PersistentVolumeSource: core.PersistentVolumeSource{ + HostPath: &core.HostPathVolumeSource{ + Path: "/foo", + Type: newHostPathType(string(core.HostPathDirectory)), + }, + }, + StorageClassName: "test-storage-class", + }, + } + return &pv +} + +func TestAlphaLocalStorageCapacityIsolation(t *testing.T) { + + testCases := []core.VolumeSource{ + {EmptyDir: &core.EmptyDirVolumeSource{SizeLimit: resource.NewQuantity(int64(5), resource.BinarySI)}}, + } + // Enable alpha feature LocalStorageCapacityIsolation + err := utilfeature.DefaultFeatureGate.Set("LocalStorageCapacityIsolation=true") + if err != nil { + t.Errorf("Failed to enable feature gate for LocalStorageCapacityIsolation: %v", err) + return + } + for _, tc := range testCases { + if errs := validateVolumeSource(&tc, field.NewPath("spec"), "tmpvol"); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + // Disable alpha feature LocalStorageCapacityIsolation + err = utilfeature.DefaultFeatureGate.Set("LocalStorageCapacityIsolation=false") + if err != nil { + t.Errorf("Failed to disable feature gate for LocalStorageCapacityIsolation: %v", err) + return + } + for _, tc := range testCases { + if errs := validateVolumeSource(&tc, field.NewPath("spec"), "tmpvol"); len(errs) == 0 { + t.Errorf("expected failure: %v", errs) + } + } + + containerLimitCase := core.ResourceRequirements{ + Limits: core.ResourceList{ + core.ResourceEphemeralStorage: *resource.NewMilliQuantity( + int64(40000), + resource.BinarySI), + }, + } + // Enable alpha feature LocalStorageCapacityIsolation + err = utilfeature.DefaultFeatureGate.Set("LocalStorageCapacityIsolation=true") + if err != nil { + t.Errorf("Failed to enable feature gate for LocalStorageCapacityIsolation: %v", err) + return + } + if errs := ValidateResourceRequirements(&containerLimitCase, field.NewPath("resources")); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + // Disable alpha feature LocalStorageCapacityIsolation + err = utilfeature.DefaultFeatureGate.Set("LocalStorageCapacityIsolation=false") + if err != nil { + t.Errorf("Failed to disable feature gate for LocalStorageCapacityIsolation: %v", err) + return + } + if errs := ValidateResourceRequirements(&containerLimitCase, field.NewPath("resources")); len(errs) == 0 { + t.Errorf("expected failure: %v", errs) + } + +} + +func TestValidateResourceQuotaWithAlphaLocalStorageCapacityIsolation(t *testing.T) { + spec := core.ResourceQuotaSpec{ + Hard: core.ResourceList{ + core.ResourceCPU: resource.MustParse("100"), + core.ResourceMemory: resource.MustParse("10000"), + core.ResourceRequestsCPU: resource.MustParse("100"), + core.ResourceRequestsMemory: resource.MustParse("10000"), + core.ResourceLimitsCPU: resource.MustParse("100"), + core.ResourceLimitsMemory: resource.MustParse("10000"), + core.ResourcePods: resource.MustParse("10"), + core.ResourceServices: resource.MustParse("0"), + core.ResourceReplicationControllers: resource.MustParse("10"), + core.ResourceQuotas: resource.MustParse("10"), + core.ResourceConfigMaps: resource.MustParse("10"), + core.ResourceSecrets: resource.MustParse("10"), + core.ResourceEphemeralStorage: resource.MustParse("10000"), + core.ResourceRequestsEphemeralStorage: resource.MustParse("10000"), + core.ResourceLimitsEphemeralStorage: resource.MustParse("10000"), + }, + } + resourceQuota := &core.ResourceQuota{ + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + Namespace: "foo", + }, + Spec: spec, + } + + // Enable alpha feature LocalStorageCapacityIsolation + err := utilfeature.DefaultFeatureGate.Set("LocalStorageCapacityIsolation=true") + if err != nil { + t.Errorf("Failed to enable feature gate for LocalStorageCapacityIsolation: %v", err) + return + } + if errs := ValidateResourceQuota(resourceQuota); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + + // Disable alpha feature LocalStorageCapacityIsolation + err = utilfeature.DefaultFeatureGate.Set("LocalStorageCapacityIsolation=false") + if err != nil { + t.Errorf("Failed to disable feature gate for LocalStorageCapacityIsolation: %v", err) + return + } + errs := ValidateResourceQuota(resourceQuota) + if len(errs) == 0 { + t.Errorf("expected failure for %s", resourceQuota.Name) + } + expectedErrMes := "ResourceEphemeralStorage field disabled by feature-gate for ResourceQuota" + for i := range errs { + if !strings.Contains(errs[i].Detail, expectedErrMes) { + t.Errorf("[%s]: expected error detail either empty or %s, got %s", resourceQuota.Name, expectedErrMes, errs[i].Detail) + } + } +} + +func TestValidatePorts(t *testing.T) { + successCase := []core.ContainerPort{ + {Name: "abc", ContainerPort: 80, HostPort: 80, Protocol: "TCP"}, + {Name: "easy", ContainerPort: 82, Protocol: "TCP"}, + {Name: "as", ContainerPort: 83, Protocol: "UDP"}, + {Name: "do-re-me", ContainerPort: 84, Protocol: "UDP"}, + {ContainerPort: 85, Protocol: "TCP"}, + } + if errs := validateContainerPorts(successCase, field.NewPath("field")); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + + nonCanonicalCase := []core.ContainerPort{ + {ContainerPort: 80, Protocol: "TCP"}, + } + if errs := validateContainerPorts(nonCanonicalCase, field.NewPath("field")); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + + errorCases := map[string]struct { + P []core.ContainerPort + T field.ErrorType + F string + D string + }{ + "name > 15 characters": { + []core.ContainerPort{{Name: strings.Repeat("a", 16), ContainerPort: 80, Protocol: "TCP"}}, + field.ErrorTypeInvalid, + "name", "15", + }, + "name contains invalid characters": { + []core.ContainerPort{{Name: "a.b.c", ContainerPort: 80, Protocol: "TCP"}}, + field.ErrorTypeInvalid, + "name", "alpha-numeric", + }, + "name is a number": { + []core.ContainerPort{{Name: "80", ContainerPort: 80, Protocol: "TCP"}}, + field.ErrorTypeInvalid, + "name", "at least one letter", + }, + "name not unique": { + []core.ContainerPort{ + {Name: "abc", ContainerPort: 80, Protocol: "TCP"}, + {Name: "abc", ContainerPort: 81, Protocol: "TCP"}, + }, + field.ErrorTypeDuplicate, + "[1].name", "", + }, + "zero container port": { + []core.ContainerPort{{ContainerPort: 0, Protocol: "TCP"}}, + field.ErrorTypeRequired, + "containerPort", "", + }, + "invalid container port": { + []core.ContainerPort{{ContainerPort: 65536, Protocol: "TCP"}}, + field.ErrorTypeInvalid, + "containerPort", "between", + }, + "invalid host port": { + []core.ContainerPort{{ContainerPort: 80, HostPort: 65536, Protocol: "TCP"}}, + field.ErrorTypeInvalid, + "hostPort", "between", + }, + "invalid protocol case": { + []core.ContainerPort{{ContainerPort: 80, Protocol: "tcp"}}, + field.ErrorTypeNotSupported, + "protocol", `supported values: "TCP", "UDP"`, + }, + "invalid protocol": { + []core.ContainerPort{{ContainerPort: 80, Protocol: "ICMP"}}, + field.ErrorTypeNotSupported, + "protocol", `supported values: "TCP", "UDP"`, + }, + "protocol required": { + []core.ContainerPort{{Name: "abc", ContainerPort: 80}}, + field.ErrorTypeRequired, + "protocol", "", + }, + } + for k, v := range errorCases { + errs := validateContainerPorts(v.P, field.NewPath("field")) + if len(errs) == 0 { + t.Errorf("expected failure for %s", k) + } + for i := range errs { + if errs[i].Type != v.T { + t.Errorf("%s: expected error to have type %q: %q", k, v.T, errs[i].Type) + } + if !strings.Contains(errs[i].Field, v.F) { + t.Errorf("%s: expected error field %q: %q", k, v.F, errs[i].Field) + } + if !strings.Contains(errs[i].Detail, v.D) { + t.Errorf("%s: expected error detail %q, got %q", k, v.D, errs[i].Detail) + } + } + } +} + +func TestLocalStorageEnvWithFeatureGate(t *testing.T) { + testCases := []core.EnvVar{ + { + Name: "ephemeral-storage-limits", + ValueFrom: &core.EnvVarSource{ + ResourceFieldRef: &core.ResourceFieldSelector{ + ContainerName: "test-container", + Resource: "limits.ephemeral-storage", + }, + }, + }, + { + Name: "ephemeral-storage-requests", + ValueFrom: &core.EnvVarSource{ + ResourceFieldRef: &core.ResourceFieldSelector{ + ContainerName: "test-container", + Resource: "requests.ephemeral-storage", + }, + }, + }, + } + // Enable alpha feature LocalStorageCapacityIsolation + err := utilfeature.DefaultFeatureGate.Set("LocalStorageCapacityIsolation=true") + if err != nil { + t.Errorf("Failed to enable feature gate for LocalStorageCapacityIsolation: %v", err) + return + } + for _, testCase := range testCases { + if errs := validateEnvVarValueFrom(testCase, field.NewPath("field")); len(errs) != 0 { + t.Errorf("expected success, got: %v", errs) + } + } + + // Disable alpha feature LocalStorageCapacityIsolation + err = utilfeature.DefaultFeatureGate.Set("LocalStorageCapacityIsolation=false") + if err != nil { + t.Errorf("Failed to disable feature gate for LocalStorageCapacityIsolation: %v", err) + return + } + for _, testCase := range testCases { + if errs := validateEnvVarValueFrom(testCase, field.NewPath("field")); len(errs) == 0 { + t.Errorf("expected failure for %v", testCase.Name) + } + } +} + +func TestValidateEnv(t *testing.T) { + successCase := []core.EnvVar{ + {Name: "abc", Value: "value"}, + {Name: "ABC", Value: "value"}, + {Name: "AbC_123", Value: "value"}, + {Name: "abc", Value: ""}, + {Name: "a.b.c", Value: "value"}, + {Name: "a-b-c", Value: "value"}, + { + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + APIVersion: legacyscheme.Registry.GroupOrDie(core.GroupName).GroupVersion.String(), + FieldPath: "metadata.annotations['key']", + }, + }, + }, + { + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + APIVersion: legacyscheme.Registry.GroupOrDie(core.GroupName).GroupVersion.String(), + FieldPath: "metadata.labels['key']", + }, + }, + }, + { + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + APIVersion: legacyscheme.Registry.GroupOrDie(core.GroupName).GroupVersion.String(), + FieldPath: "metadata.name", + }, + }, + }, + { + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + APIVersion: legacyscheme.Registry.GroupOrDie(core.GroupName).GroupVersion.String(), + FieldPath: "metadata.namespace", + }, + }, + }, + { + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + APIVersion: legacyscheme.Registry.GroupOrDie(core.GroupName).GroupVersion.String(), + FieldPath: "metadata.uid", + }, + }, + }, + { + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + APIVersion: legacyscheme.Registry.GroupOrDie(core.GroupName).GroupVersion.String(), + FieldPath: "spec.nodeName", + }, + }, + }, + { + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + APIVersion: legacyscheme.Registry.GroupOrDie(core.GroupName).GroupVersion.String(), + FieldPath: "spec.serviceAccountName", + }, + }, + }, + { + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + APIVersion: legacyscheme.Registry.GroupOrDie(core.GroupName).GroupVersion.String(), + FieldPath: "status.hostIP", + }, + }, + }, + { + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + APIVersion: legacyscheme.Registry.GroupOrDie(core.GroupName).GroupVersion.String(), + FieldPath: "status.podIP", + }, + }, + }, + { + Name: "secret_value", + ValueFrom: &core.EnvVarSource{ + SecretKeyRef: &core.SecretKeySelector{ + LocalObjectReference: core.LocalObjectReference{ + Name: "some-secret", + }, + Key: "secret-key", + }, + }, + }, + { + Name: "ENV_VAR_1", + ValueFrom: &core.EnvVarSource{ + ConfigMapKeyRef: &core.ConfigMapKeySelector{ + LocalObjectReference: core.LocalObjectReference{ + Name: "some-config-map", + }, + Key: "some-key", + }, + }, + }, + } + if errs := ValidateEnv(successCase, field.NewPath("field")); len(errs) != 0 { + t.Errorf("expected success, got: %v", errs) + } + + errorCases := []struct { + name string + envs []core.EnvVar + expectedError string + }{ + { + name: "zero-length name", + envs: []core.EnvVar{{Name: ""}}, + expectedError: "[0].name: Required value", + }, + { + name: "illegal character", + envs: []core.EnvVar{{Name: "a!b"}}, + expectedError: `[0].name: Invalid value: "a!b": ` + envVarNameErrMsg, + }, + { + name: "dot only", + envs: []core.EnvVar{{Name: "."}}, + expectedError: `[0].name: Invalid value: ".": must not be`, + }, + { + name: "double dots only", + envs: []core.EnvVar{{Name: ".."}}, + expectedError: `[0].name: Invalid value: "..": must not be`, + }, + { + name: "leading double dots", + envs: []core.EnvVar{{Name: "..abc"}}, + expectedError: `[0].name: Invalid value: "..abc": must not start with`, + }, + { + name: "value and valueFrom specified", + envs: []core.EnvVar{{ + Name: "abc", + Value: "foo", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + APIVersion: legacyscheme.Registry.GroupOrDie(core.GroupName).GroupVersion.String(), + FieldPath: "metadata.name", + }, + }, + }}, + expectedError: "[0].valueFrom: Invalid value: \"\": may not be specified when `value` is not empty", + }, + { + name: "valueFrom without a source", + envs: []core.EnvVar{{ + Name: "abc", + ValueFrom: &core.EnvVarSource{}, + }}, + expectedError: "[0].valueFrom: Invalid value: \"\": must specify one of: `fieldRef`, `resourceFieldRef`, `configMapKeyRef` or `secretKeyRef`", + }, + { + name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified", + envs: []core.EnvVar{{ + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + APIVersion: legacyscheme.Registry.GroupOrDie(core.GroupName).GroupVersion.String(), + FieldPath: "metadata.name", + }, + SecretKeyRef: &core.SecretKeySelector{ + LocalObjectReference: core.LocalObjectReference{ + Name: "a-secret", + }, + Key: "a-key", + }, + }, + }}, + expectedError: "[0].valueFrom: Invalid value: \"\": may not have more than one field specified at a time", + }, + { + name: "valueFrom.fieldRef and valueFrom.configMapKeyRef set", + envs: []core.EnvVar{{ + Name: "some_var_name", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + APIVersion: legacyscheme.Registry.GroupOrDie(core.GroupName).GroupVersion.String(), + FieldPath: "metadata.name", + }, + ConfigMapKeyRef: &core.ConfigMapKeySelector{ + LocalObjectReference: core.LocalObjectReference{ + Name: "some-config-map", + }, + Key: "some-key", + }, + }, + }}, + expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`, + }, + { + name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified", + envs: []core.EnvVar{{ + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + APIVersion: legacyscheme.Registry.GroupOrDie(core.GroupName).GroupVersion.String(), + FieldPath: "metadata.name", + }, + SecretKeyRef: &core.SecretKeySelector{ + LocalObjectReference: core.LocalObjectReference{ + Name: "a-secret", + }, + Key: "a-key", + }, + ConfigMapKeyRef: &core.ConfigMapKeySelector{ + LocalObjectReference: core.LocalObjectReference{ + Name: "some-config-map", + }, + Key: "some-key", + }, + }, + }}, + expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`, + }, + { + name: "valueFrom.secretKeyRef.name invalid", + envs: []core.EnvVar{{ + Name: "abc", + ValueFrom: &core.EnvVarSource{ + SecretKeyRef: &core.SecretKeySelector{ + LocalObjectReference: core.LocalObjectReference{ + Name: "$%^&*#", + }, + Key: "a-key", + }, + }, + }}, + }, + { + name: "valueFrom.configMapKeyRef.name invalid", + envs: []core.EnvVar{{ + Name: "abc", + ValueFrom: &core.EnvVarSource{ + ConfigMapKeyRef: &core.ConfigMapKeySelector{ + LocalObjectReference: core.LocalObjectReference{ + Name: "$%^&*#", + }, + Key: "some-key", + }, + }, + }}, + }, + { + name: "missing FieldPath on ObjectFieldSelector", + envs: []core.EnvVar{{ + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + APIVersion: legacyscheme.Registry.GroupOrDie(core.GroupName).GroupVersion.String(), + }, + }, + }}, + expectedError: `[0].valueFrom.fieldRef.fieldPath: Required value`, + }, + { + name: "missing APIVersion on ObjectFieldSelector", + envs: []core.EnvVar{{ + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + FieldPath: "metadata.name", + }, + }, + }}, + expectedError: `[0].valueFrom.fieldRef.apiVersion: Required value`, + }, + { + name: "invalid fieldPath", + envs: []core.EnvVar{{ + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + FieldPath: "metadata.whoops", + APIVersion: legacyscheme.Registry.GroupOrDie(core.GroupName).GroupVersion.String(), + }, + }, + }}, + expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.whoops": error converting fieldPath`, + }, + { + name: "metadata.name with subscript", + envs: []core.EnvVar{{ + Name: "labels", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + FieldPath: "metadata.name['key']", + APIVersion: "v1", + }, + }, + }}, + expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.name['key']": error converting fieldPath: field label does not support subscript`, + }, + { + name: "metadata.labels without subscript", + envs: []core.EnvVar{{ + Name: "labels", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + FieldPath: "metadata.labels", + APIVersion: "v1", + }, + }, + }}, + expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.labels": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP"`, + }, + { + name: "metadata.annotations without subscript", + envs: []core.EnvVar{{ + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + FieldPath: "metadata.annotations", + APIVersion: "v1", + }, + }, + }}, + expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.annotations": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP"`, + }, + { + name: "metadata.annotations with invalid key", + envs: []core.EnvVar{{ + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + FieldPath: "metadata.annotations['invalid~key']", + APIVersion: "v1", + }, + }, + }}, + expectedError: `field[0].valueFrom.fieldRef: Invalid value: "invalid~key"`, + }, + { + name: "metadata.labels with invalid key", + envs: []core.EnvVar{{ + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + FieldPath: "metadata.labels['Www.k8s.io/test']", + APIVersion: "v1", + }, + }, + }}, + expectedError: `field[0].valueFrom.fieldRef: Invalid value: "Www.k8s.io/test"`, + }, + { + name: "unsupported fieldPath", + envs: []core.EnvVar{{ + Name: "abc", + ValueFrom: &core.EnvVarSource{ + FieldRef: &core.ObjectFieldSelector{ + FieldPath: "status.phase", + APIVersion: legacyscheme.Registry.GroupOrDie(core.GroupName).GroupVersion.String(), + }, + }, + }}, + expectedError: `valueFrom.fieldRef.fieldPath: Unsupported value: "status.phase": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP"`, + }, + } + for _, tc := range errorCases { + if errs := ValidateEnv(tc.envs, field.NewPath("field")); len(errs) == 0 { + t.Errorf("expected failure for %s", tc.name) + } else { + for i := range errs { + str := errs[i].Error() + if str != "" && !strings.Contains(str, tc.expectedError) { + t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str) + } + } + } + } +} + +func TestValidateEnvFrom(t *testing.T) { + successCase := []core.EnvFromSource{ + { + ConfigMapRef: &core.ConfigMapEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "abc"}, + }, + }, + { + Prefix: "pre_", + ConfigMapRef: &core.ConfigMapEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "abc"}, + }, + }, + { + Prefix: "a.b", + ConfigMapRef: &core.ConfigMapEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "abc"}, + }, + }, + { + SecretRef: &core.SecretEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "abc"}, + }, + }, + { + Prefix: "pre_", + SecretRef: &core.SecretEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "abc"}, + }, + }, + { + Prefix: "a.b", + SecretRef: &core.SecretEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "abc"}, + }, + }, + } + if errs := ValidateEnvFrom(successCase, field.NewPath("field")); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + + errorCases := []struct { + name string + envs []core.EnvFromSource + expectedError string + }{ + { + name: "zero-length name", + envs: []core.EnvFromSource{ + { + ConfigMapRef: &core.ConfigMapEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: ""}}, + }, + }, + expectedError: "field[0].configMapRef.name: Required value", + }, + { + name: "invalid name", + envs: []core.EnvFromSource{ + { + ConfigMapRef: &core.ConfigMapEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "$"}}, + }, + }, + expectedError: "field[0].configMapRef.name: Invalid value", + }, + { + name: "invalid prefix", + envs: []core.EnvFromSource{ + { + Prefix: "a!b", + ConfigMapRef: &core.ConfigMapEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "abc"}}, + }, + }, + expectedError: `field[0].prefix: Invalid value: "a!b": ` + envVarNameErrMsg, + }, + { + name: "zero-length name", + envs: []core.EnvFromSource{ + { + SecretRef: &core.SecretEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: ""}}, + }, + }, + expectedError: "field[0].secretRef.name: Required value", + }, + { + name: "invalid name", + envs: []core.EnvFromSource{ + { + SecretRef: &core.SecretEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "&"}}, + }, + }, + expectedError: "field[0].secretRef.name: Invalid value", + }, + { + name: "invalid prefix", + envs: []core.EnvFromSource{ + { + Prefix: "a!b", + SecretRef: &core.SecretEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "abc"}}, + }, + }, + expectedError: `field[0].prefix: Invalid value: "a!b": ` + envVarNameErrMsg, + }, + { + name: "no refs", + envs: []core.EnvFromSource{ + {}, + }, + expectedError: "field: Invalid value: \"\": must specify one of: `configMapRef` or `secretRef`", + }, + { + name: "multiple refs", + envs: []core.EnvFromSource{ + { + SecretRef: &core.SecretEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "abc"}}, + ConfigMapRef: &core.ConfigMapEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "abc"}}, + }, + }, + expectedError: "field: Invalid value: \"\": may not have more than one field specified at a time", + }, + { + name: "invalid secret ref name", + envs: []core.EnvFromSource{ + { + SecretRef: &core.SecretEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "$%^&*#"}}, + }, + }, + expectedError: "field[0].secretRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg, + }, + { + name: "invalid config ref name", + envs: []core.EnvFromSource{ + { + ConfigMapRef: &core.ConfigMapEnvSource{ + LocalObjectReference: core.LocalObjectReference{Name: "$%^&*#"}}, + }, + }, + expectedError: "field[0].configMapRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg, + }, + } + for _, tc := range errorCases { + if errs := ValidateEnvFrom(tc.envs, field.NewPath("field")); len(errs) == 0 { + t.Errorf("expected failure for %s", tc.name) + } else { + for i := range errs { + str := errs[i].Error() + if str != "" && !strings.Contains(str, tc.expectedError) { + t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str) + } + } + } + } +} + +func TestValidateVolumeMounts(t *testing.T) { + volumes := []core.Volume{ + {Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}}, + {Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}}, + {Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}}, + } + vols, v1err := ValidateVolumes(volumes, field.NewPath("field")) + if len(v1err) > 0 { + t.Errorf("Invalid test volume - expected success %v", v1err) + return + } + container := core.Container{ + SecurityContext: nil, + } + propagation := core.MountPropagationBidirectional + + successCase := []core.VolumeMount{ + {Name: "abc", MountPath: "/foo"}, + {Name: "123", MountPath: "/bar"}, + {Name: "abc-123", MountPath: "/baz"}, + {Name: "abc-123", MountPath: "/baa", SubPath: ""}, + {Name: "abc-123", MountPath: "/bab", SubPath: "baz"}, + {Name: "abc-123", MountPath: "d:", SubPath: ""}, + {Name: "abc-123", MountPath: "F:", SubPath: ""}, + {Name: "abc-123", MountPath: "G:\\mount", SubPath: ""}, + {Name: "abc-123", MountPath: "/bac", SubPath: ".baz"}, + {Name: "abc-123", MountPath: "/bad", SubPath: "..baz"}, + } + goodVolumeDevices := []core.VolumeDevice{ + {Name: "xyz", DevicePath: "/foofoo"}, + {Name: "uvw", DevicePath: "/foofoo/share/test"}, + } + if errs := ValidateVolumeMounts(successCase, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field")); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + + errorCases := map[string][]core.VolumeMount{ + "empty name": {{Name: "", MountPath: "/foo"}}, + "name not found": {{Name: "", MountPath: "/foo"}}, + "empty mountpath": {{Name: "abc", MountPath: ""}}, + "mountpath collision": {{Name: "foo", MountPath: "/path/a"}, {Name: "bar", MountPath: "/path/a"}}, + "absolute subpath": {{Name: "abc", MountPath: "/bar", SubPath: "/baz"}}, + "subpath in ..": {{Name: "abc", MountPath: "/bar", SubPath: "../baz"}}, + "subpath contains ..": {{Name: "abc", MountPath: "/bar", SubPath: "baz/../bat"}}, + "subpath ends in ..": {{Name: "abc", MountPath: "/bar", SubPath: "./.."}}, + "disabled MountPropagation feature gate": {{Name: "abc", MountPath: "/bar", MountPropagation: &propagation}}, + "name exists in volumeDevice": {{Name: "xyz", MountPath: "/bar"}}, + "mountpath exists in volumeDevice": {{Name: "uvw", MountPath: "/mnt/exists"}}, + "both exist in volumeDevice": {{Name: "xyz", MountPath: "/mnt/exists"}}, + } + badVolumeDevice := []core.VolumeDevice{ + {Name: "xyz", DevicePath: "/mnt/exists"}, + } + + for k, v := range errorCases { + if errs := ValidateVolumeMounts(v, GetVolumeDeviceMap(badVolumeDevice), vols, &container, field.NewPath("field")); len(errs) == 0 { + t.Errorf("expected failure for %s", k) + } + } +} + +func TestValidateMountPropagation(t *testing.T) { + bTrue := true + bFalse := false + privilegedContainer := &core.Container{ + SecurityContext: &core.SecurityContext{ + Privileged: &bTrue, + }, + } + nonPrivilegedContainer := &core.Container{ + SecurityContext: &core.SecurityContext{ + Privileged: &bFalse, + }, + } + defaultContainer := &core.Container{} + + propagationBidirectional := core.MountPropagationBidirectional + propagationHostToContainer := core.MountPropagationHostToContainer + propagationInvalid := core.MountPropagationMode("invalid") + + tests := []struct { + mount core.VolumeMount + container *core.Container + expectError bool + }{ + { + // implicitly non-privileged container + no propagation + core.VolumeMount{Name: "foo", MountPath: "/foo"}, + defaultContainer, + false, + }, + { + // implicitly non-privileged container + HostToContainer + core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer}, + defaultContainer, + false, + }, + { + // error: implicitly non-privileged container + Bidirectional + core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional}, + defaultContainer, + true, + }, + { + // explicitly non-privileged container + no propagation + core.VolumeMount{Name: "foo", MountPath: "/foo"}, + nonPrivilegedContainer, + false, + }, + { + // explicitly non-privileged container + HostToContainer + core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer}, + nonPrivilegedContainer, + false, + }, + { + // explicitly non-privileged container + HostToContainer + core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional}, + nonPrivilegedContainer, + true, + }, + { + // privileged container + no propagation + core.VolumeMount{Name: "foo", MountPath: "/foo"}, + privilegedContainer, + false, + }, + { + // privileged container + HostToContainer + core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer}, + privilegedContainer, + false, + }, + { + // privileged container + Bidirectional + core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional}, + privilegedContainer, + false, + }, + { + // error: privileged container + invalid mount propagation + core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationInvalid}, + privilegedContainer, + true, + }, + { + // no container + Bidirectional + core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional}, + nil, + false, + }, + } + + // Enable MountPropagation for this test + priorityEnabled := utilfeature.DefaultFeatureGate.Enabled("MountPropagation") + defer func() { + var err error + // restoring the old value + if priorityEnabled { + err = utilfeature.DefaultFeatureGate.Set("MountPropagation=true") + } else { + err = utilfeature.DefaultFeatureGate.Set("MountPropagation=false") + } + if err != nil { + t.Errorf("Failed to restore feature gate for MountPropagation: %v", err) + } + }() + err := utilfeature.DefaultFeatureGate.Set("MountPropagation=true") + if err != nil { + t.Errorf("Failed to enable feature gate for MountPropagation: %v", err) + return + } + + volumes := []core.Volume{ + {Name: "foo", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}}, + } + vols2, v2err := ValidateVolumes(volumes, field.NewPath("field")) + if len(v2err) > 0 { + t.Errorf("Invalid test volume - expected success %v", v2err) + return + } + for i, test := range tests { + errs := ValidateVolumeMounts([]core.VolumeMount{test.mount}, nil, vols2, test.container, field.NewPath("field")) + if test.expectError && len(errs) == 0 { + t.Errorf("test %d expected error, got none", i) + } + if !test.expectError && len(errs) != 0 { + t.Errorf("test %d expected success, got error: %v", i, errs) + } + } +} + +func TestAlphaValidateVolumeDevices(t *testing.T) { + volumes := []core.Volume{ + {Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}}, + {Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}}, + {Name: "def", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}}, + } + + vols, v1err := ValidateVolumes(volumes, field.NewPath("field")) + if len(v1err) > 0 { + t.Errorf("Invalid test volumes - expected success %v", v1err) + return + } + + disabledAlphaVolDevice := []core.VolumeDevice{ + {Name: "abc", DevicePath: "/foo"}, + } + + successCase := []core.VolumeDevice{ + {Name: "abc", DevicePath: "/foo"}, + {Name: "abc-123", DevicePath: "/usr/share/test"}, + } + goodVolumeMounts := []core.VolumeMount{ + {Name: "xyz", MountPath: "/foofoo"}, + {Name: "ghi", MountPath: "/foo/usr/share/test"}, + } + + errorCases := map[string][]core.VolumeDevice{ + "empty name": {{Name: "", DevicePath: "/foo"}}, + "duplicate name": {{Name: "abc", DevicePath: "/foo"}, {Name: "abc", DevicePath: "/foo/bar"}}, + "name not found": {{Name: "not-found", DevicePath: "/usr/share/test"}}, + "name found but invalid source": {{Name: "def", DevicePath: "/usr/share/test"}}, + "empty devicepath": {{Name: "abc", DevicePath: ""}}, + "relative devicepath": {{Name: "abc-123", DevicePath: "baz"}}, + "duplicate devicepath": {{Name: "abc", DevicePath: "/foo"}, {Name: "abc-123", DevicePath: "/foo"}}, + "no backsteps": {{Name: "def", DevicePath: "/baz/../"}}, + "name exists in volumemounts": {{Name: "abc", DevicePath: "/baz/../"}}, + "path exists in volumemounts": {{Name: "xyz", DevicePath: "/this/path/exists"}}, + "both exist in volumemounts": {{Name: "abc", DevicePath: "/this/path/exists"}}, + } + badVolumeMounts := []core.VolumeMount{ + {Name: "abc", MountPath: "/foo"}, + {Name: "abc-123", MountPath: "/this/path/exists"}, + } + + // enable Alpha BlockVolume + err1 := utilfeature.DefaultFeatureGate.Set("BlockVolume=true") + if err1 != nil { + t.Errorf("Failed to enable feature gate for BlockVolume: %v", err1) + return + } + // Success Cases: + // Validate normal success cases - only PVC volumeSource + if errs := ValidateVolumeDevices(successCase, GetVolumeMountMap(goodVolumeMounts), vols, field.NewPath("field")); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + + // Error Cases: + // Validate normal error cases - only PVC volumeSource + for k, v := range errorCases { + if errs := ValidateVolumeDevices(v, GetVolumeMountMap(badVolumeMounts), vols, field.NewPath("field")); len(errs) == 0 { + t.Errorf("expected failure for %s", k) + } + } + + // disable Alpha BlockVolume + err2 := utilfeature.DefaultFeatureGate.Set("BlockVolume=false") + if err2 != nil { + t.Errorf("Failed to disable feature gate for BlockVolume: %v", err2) + return + } + if errs := ValidateVolumeDevices(disabledAlphaVolDevice, GetVolumeMountMap(goodVolumeMounts), vols, field.NewPath("field")); len(errs) == 0 { + t.Errorf("expected failure: %v", errs) + } +} + +func TestValidateProbe(t *testing.T) { + handler := core.Handler{Exec: &core.ExecAction{Command: []string{"echo"}}} + // These fields must be positive. + positiveFields := [...]string{"InitialDelaySeconds", "TimeoutSeconds", "PeriodSeconds", "SuccessThreshold", "FailureThreshold"} + successCases := []*core.Probe{nil} + for _, field := range positiveFields { + probe := &core.Probe{Handler: handler} + reflect.ValueOf(probe).Elem().FieldByName(field).SetInt(10) + successCases = append(successCases, probe) + } + + for _, p := range successCases { + if errs := validateProbe(p, field.NewPath("field")); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + + errorCases := []*core.Probe{{TimeoutSeconds: 10, InitialDelaySeconds: 10}} + for _, field := range positiveFields { + probe := &core.Probe{Handler: handler} + reflect.ValueOf(probe).Elem().FieldByName(field).SetInt(-10) + errorCases = append(errorCases, probe) + } + for _, p := range errorCases { + if errs := validateProbe(p, field.NewPath("field")); len(errs) == 0 { + t.Errorf("expected failure for %v", p) + } + } +} + +func TestValidateHandler(t *testing.T) { + successCases := []core.Handler{ + {Exec: &core.ExecAction{Command: []string{"echo"}}}, + {HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromInt(1), Host: "", Scheme: "HTTP"}}, + {HTTPGet: &core.HTTPGetAction{Path: "/foo", Port: intstr.FromInt(65535), Host: "host", Scheme: "HTTP"}}, + {HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP"}}, + {HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "Host", Value: "foo.example.com"}}}}, + {HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "X-Forwarded-For", Value: "1.2.3.4"}, {Name: "X-Forwarded-For", Value: "5.6.7.8"}}}}, + } + for _, h := range successCases { + if errs := validateHandler(&h, field.NewPath("field")); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + + errorCases := []core.Handler{ + {}, + {Exec: &core.ExecAction{Command: []string{}}}, + {HTTPGet: &core.HTTPGetAction{Path: "", Port: intstr.FromInt(0), Host: ""}}, + {HTTPGet: &core.HTTPGetAction{Path: "/foo", Port: intstr.FromInt(65536), Host: "host"}}, + {HTTPGet: &core.HTTPGetAction{Path: "", Port: intstr.FromString(""), Host: ""}}, + {HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "Host:", Value: "foo.example.com"}}}}, + {HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "X_Forwarded_For", Value: "foo.example.com"}}}}, + } + for _, h := range errorCases { + if errs := validateHandler(&h, field.NewPath("field")); len(errs) == 0 { + t.Errorf("expected failure for %#v", h) + } + } +} + +func TestValidatePullPolicy(t *testing.T) { + type T struct { + Container core.Container + ExpectedPolicy core.PullPolicy + } + testCases := map[string]T{ + "NotPresent1": { + core.Container{Name: "abc", Image: "image:latest", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, + core.PullIfNotPresent, + }, + "NotPresent2": { + core.Container{Name: "abc1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, + core.PullIfNotPresent, + }, + "Always1": { + core.Container{Name: "123", Image: "image:latest", ImagePullPolicy: "Always"}, + core.PullAlways, + }, + "Always2": { + core.Container{Name: "1234", Image: "image", ImagePullPolicy: "Always"}, + core.PullAlways, + }, + "Never1": { + core.Container{Name: "abc-123", Image: "image:latest", ImagePullPolicy: "Never"}, + core.PullNever, + }, + "Never2": { + core.Container{Name: "abc-1234", Image: "image", ImagePullPolicy: "Never"}, + core.PullNever, + }, + } + for k, v := range testCases { + ctr := &v.Container + errs := validatePullPolicy(ctr.ImagePullPolicy, field.NewPath("field")) + if len(errs) != 0 { + t.Errorf("case[%s] expected success, got %#v", k, errs) + } + if ctr.ImagePullPolicy != v.ExpectedPolicy { + t.Errorf("case[%s] expected policy %v, got %v", k, v.ExpectedPolicy, ctr.ImagePullPolicy) + } + } +} + +func getResourceLimits(cpu, memory string) core.ResourceList { + res := core.ResourceList{} + res[core.ResourceCPU] = resource.MustParse(cpu) + res[core.ResourceMemory] = resource.MustParse(memory) + return res +} + +func TestValidateContainers(t *testing.T) { + volumeDevices := make(map[string]core.VolumeSource) + capabilities.SetForTests(capabilities.Capabilities{ + AllowPrivileged: true, + }) + + successCase := []core.Container{ + {Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, + // backwards compatibility to ensure containers in pod template spec do not check for this + {Name: "def", Image: " ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, + {Name: "ghi", Image: " some ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, + {Name: "123", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, + {Name: "abc-123", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, + { + Name: "life-123", + Image: "image", + Lifecycle: &core.Lifecycle{ + PreStop: &core.Handler{ + Exec: &core.ExecAction{Command: []string{"ls", "-l"}}, + }, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + { + Name: "resources-test", + Image: "image", + Resources: core.ResourceRequirements{ + Limits: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName("my.org/resource"): resource.MustParse("10"), + }, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + { + Name: "resources-test-with-gpu-with-request", + Image: "image", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName(core.ResourceNvidiaGPU): resource.MustParse("1"), + }, + Limits: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName(core.ResourceNvidiaGPU): resource.MustParse("1"), + }, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + { + Name: "resources-test-with-gpu-without-request", + Image: "image", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + }, + Limits: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName(core.ResourceNvidiaGPU): resource.MustParse("1"), + }, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + { + Name: "resources-request-limit-simple", + Image: "image", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("8"), + }, + Limits: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + }, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + { + Name: "resources-request-limit-edge", + Image: "image", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName("my.org/resource"): resource.MustParse("10"), + }, + Limits: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName("my.org/resource"): resource.MustParse("10"), + }, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + { + Name: "resources-request-limit-partials", + Image: "image", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("9.5"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + }, + Limits: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName("my.org/resource"): resource.MustParse("10"), + }, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + { + Name: "resources-request", + Image: "image", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("9.5"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + }, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + { + Name: "same-host-port-different-protocol", + Image: "image", + Ports: []core.ContainerPort{ + {ContainerPort: 80, HostPort: 80, Protocol: "TCP"}, + {ContainerPort: 80, HostPort: 80, Protocol: "UDP"}, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + { + Name: "fallback-to-logs-termination-message", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "FallbackToLogsOnError", + }, + { + Name: "file-termination-message", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + { + Name: "env-from-source", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + EnvFrom: []core.EnvFromSource{ + { + ConfigMapRef: &core.ConfigMapEnvSource{ + LocalObjectReference: core.LocalObjectReference{ + Name: "test", + }, + }, + }, + }, + }, + {Name: "abc-1234", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", SecurityContext: fakeValidSecurityContext(true)}, + } + if errs := validateContainers(successCase, volumeDevices, field.NewPath("field")); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + + capabilities.SetForTests(capabilities.Capabilities{ + AllowPrivileged: false, + }) + errorCases := map[string][]core.Container{ + "zero-length name": {{Name: "", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + "zero-length-image": {{Name: "abc", Image: "", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + "name > 63 characters": {{Name: strings.Repeat("a", 64), Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + "name not a DNS label": {{Name: "a.b.c", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + "name not unique": { + {Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, + {Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, + }, + "zero-length image": {{Name: "abc", Image: "", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + "host port not unique": { + {Name: "abc", Image: "image", Ports: []core.ContainerPort{{ContainerPort: 80, HostPort: 80, Protocol: "TCP"}}, + ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, + {Name: "def", Image: "image", Ports: []core.ContainerPort{{ContainerPort: 81, HostPort: 80, Protocol: "TCP"}}, + ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, + }, + "invalid env var name": { + {Name: "abc", Image: "image", Env: []core.EnvVar{{Name: "ev!1"}}, ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, + }, + "unknown volume name": { + {Name: "abc", Image: "image", VolumeMounts: []core.VolumeMount{{Name: "anything", MountPath: "/foo"}}, + ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, + }, + "invalid lifecycle, no exec command.": { + { + Name: "life-123", + Image: "image", + Lifecycle: &core.Lifecycle{ + PreStop: &core.Handler{ + Exec: &core.ExecAction{}, + }, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }, + "invalid lifecycle, no http path.": { + { + Name: "life-123", + Image: "image", + Lifecycle: &core.Lifecycle{ + PreStop: &core.Handler{ + HTTPGet: &core.HTTPGetAction{}, + }, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }, + "invalid lifecycle, no tcp socket port.": { + { + Name: "life-123", + Image: "image", + Lifecycle: &core.Lifecycle{ + PreStop: &core.Handler{ + TCPSocket: &core.TCPSocketAction{}, + }, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }, + "invalid lifecycle, zero tcp socket port.": { + { + Name: "life-123", + Image: "image", + Lifecycle: &core.Lifecycle{ + PreStop: &core.Handler{ + TCPSocket: &core.TCPSocketAction{ + Port: intstr.FromInt(0), + }, + }, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }, + "invalid lifecycle, no action.": { + { + Name: "life-123", + Image: "image", + Lifecycle: &core.Lifecycle{ + PreStop: &core.Handler{}, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }, + "invalid liveness probe, no tcp socket port.": { + { + Name: "life-123", + Image: "image", + LivenessProbe: &core.Probe{ + Handler: core.Handler{ + TCPSocket: &core.TCPSocketAction{}, + }, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }, + "invalid liveness probe, no action.": { + { + Name: "life-123", + Image: "image", + LivenessProbe: &core.Probe{ + Handler: core.Handler{}, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }, + "invalid message termination policy": { + { + Name: "life-123", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "Unknown", + }, + }, + "empty message termination policy": { + { + Name: "life-123", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "", + }, + }, + "privilege disabled": { + {Name: "abc", Image: "image", SecurityContext: fakeValidSecurityContext(true)}, + }, + "invalid compute resource": { + { + Name: "abc-123", + Image: "image", + Resources: core.ResourceRequirements{ + Limits: core.ResourceList{ + "disk": resource.MustParse("10G"), + }, + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }, + "Resource CPU invalid": { + { + Name: "abc-123", + Image: "image", + Resources: core.ResourceRequirements{ + Limits: getResourceLimits("-10", "0"), + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }, + "Resource Requests CPU invalid": { + { + Name: "abc-123", + Image: "image", + Resources: core.ResourceRequirements{ + Requests: getResourceLimits("-10", "0"), + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }, + "Resource Memory invalid": { + { + Name: "abc-123", + Image: "image", + Resources: core.ResourceRequirements{ + Limits: getResourceLimits("0", "-10"), + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }, + "Resource GPU limit must match request": { + { + Name: "gpu-resource-request-limit", + Image: "image", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName(core.ResourceNvidiaGPU): resource.MustParse("0"), + }, + Limits: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName(core.ResourceNvidiaGPU): resource.MustParse("1"), + }, + }, + TerminationMessagePolicy: "File", + ImagePullPolicy: "IfNotPresent", + }, + }, + "Resource GPU invalid setting only request": { + { + Name: "gpu-resource-request-limit", + Image: "image", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName(core.ResourceNvidiaGPU): resource.MustParse("1"), + }, + }, + TerminationMessagePolicy: "File", + ImagePullPolicy: "IfNotPresent", + }, + }, + "Request limit simple invalid": { + { + Name: "abc-123", + Image: "image", + Resources: core.ResourceRequirements{ + Limits: getResourceLimits("5", "3"), + Requests: getResourceLimits("6", "3"), + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }, + "Request limit multiple invalid": { + { + Name: "abc-123", + Image: "image", + Resources: core.ResourceRequirements{ + Limits: getResourceLimits("5", "3"), + Requests: getResourceLimits("6", "4"), + }, + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + }, + }, + "Invalid env from": { + { + Name: "env-from-source", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + EnvFrom: []core.EnvFromSource{ + { + ConfigMapRef: &core.ConfigMapEnvSource{ + LocalObjectReference: core.LocalObjectReference{ + Name: "$%^&*#", + }, + }, + }, + }, + }, + }, + } + for k, v := range errorCases { + if errs := validateContainers(v, volumeDevices, field.NewPath("field")); len(errs) == 0 { + t.Errorf("expected failure for %s", k) + } + } +} + +func TestValidateRestartPolicy(t *testing.T) { + successCases := []core.RestartPolicy{ + core.RestartPolicyAlways, + core.RestartPolicyOnFailure, + core.RestartPolicyNever, + } + for _, policy := range successCases { + if errs := validateRestartPolicy(&policy, field.NewPath("field")); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + + errorCases := []core.RestartPolicy{"", "newpolicy"} + + for k, policy := range errorCases { + if errs := validateRestartPolicy(&policy, field.NewPath("field")); len(errs) == 0 { + t.Errorf("expected failure for %d", k) + } + } +} + +func TestValidateDNSPolicy(t *testing.T) { + customDNSEnabled := utilfeature.DefaultFeatureGate.Enabled("CustomPodDNS") + defer func() { + // Restoring the old value. + if err := utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("CustomPodDNS=%v", customDNSEnabled)); err != nil { + t.Errorf("Failed to restore CustomPodDNS feature gate: %v", err) + } + }() + if err := utilfeature.DefaultFeatureGate.Set("CustomPodDNS=true"); err != nil { + t.Errorf("Failed to enable CustomPodDNS feature gate: %v", err) + } + + successCases := []core.DNSPolicy{core.DNSClusterFirst, core.DNSDefault, core.DNSPolicy(core.DNSClusterFirst), core.DNSNone} + for _, policy := range successCases { + if errs := validateDNSPolicy(&policy, field.NewPath("field")); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + + errorCases := []core.DNSPolicy{core.DNSPolicy("invalid")} + for _, policy := range errorCases { + if errs := validateDNSPolicy(&policy, field.NewPath("field")); len(errs) == 0 { + t.Errorf("expected failure for %v", policy) + } + } +} + +func TestValidatePodDNSConfig(t *testing.T) { + customDNSEnabled := utilfeature.DefaultFeatureGate.Enabled("CustomPodDNS") + defer func() { + // Restoring the old value. + if err := utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("CustomPodDNS=%v", customDNSEnabled)); err != nil { + t.Errorf("Failed to restore CustomPodDNS feature gate: %v", err) + } + }() + if err := utilfeature.DefaultFeatureGate.Set("CustomPodDNS=true"); err != nil { + t.Errorf("Failed to enable CustomPodDNS feature gate: %v", err) + } + + generateTestSearchPathFunc := func(numChars int) string { + res := "" + for i := 0; i < numChars; i++ { + res = res + "a" + } + return res + } + testOptionValue := "2" + testDNSNone := core.DNSNone + testDNSClusterFirst := core.DNSClusterFirst + + testCases := []struct { + desc string + dnsConfig *core.PodDNSConfig + dnsPolicy *core.DNSPolicy + expectedError bool + }{ + { + desc: "valid: empty DNSConfig", + dnsConfig: &core.PodDNSConfig{}, + expectedError: false, + }, + { + desc: "valid: 1 option", + dnsConfig: &core.PodDNSConfig{ + Options: []core.PodDNSConfigOption{ + {Name: "ndots", Value: &testOptionValue}, + }, + }, + expectedError: false, + }, + { + desc: "valid: 1 nameserver", + dnsConfig: &core.PodDNSConfig{ + Nameservers: []string{"127.0.0.1"}, + }, + expectedError: false, + }, + { + desc: "valid: DNSNone with 1 nameserver", + dnsConfig: &core.PodDNSConfig{ + Nameservers: []string{"127.0.0.1"}, + }, + dnsPolicy: &testDNSNone, + expectedError: false, + }, + { + desc: "valid: 1 search path", + dnsConfig: &core.PodDNSConfig{ + Searches: []string{"custom"}, + }, + expectedError: false, + }, + { + desc: "valid: 3 nameservers and 6 search paths", + dnsConfig: &core.PodDNSConfig{ + Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"}, + Searches: []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local"}, + }, + expectedError: false, + }, + { + desc: "valid: 256 characters in search path list", + dnsConfig: &core.PodDNSConfig{ + // We can have 256 - (6 - 1) = 251 characters in total for 6 search paths. + Searches: []string{ + generateTestSearchPathFunc(1), + generateTestSearchPathFunc(50), + generateTestSearchPathFunc(50), + generateTestSearchPathFunc(50), + generateTestSearchPathFunc(50), + generateTestSearchPathFunc(50), + }, + }, + expectedError: false, + }, + { + desc: "valid: ipv6 nameserver", + dnsConfig: &core.PodDNSConfig{ + Nameservers: []string{"FE80::0202:B3FF:FE1E:8329"}, + }, + expectedError: false, + }, + { + desc: "invalid: 4 nameservers", + dnsConfig: &core.PodDNSConfig{ + Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8", "1.2.3.4"}, + }, + expectedError: true, + }, + { + desc: "invalid: 7 search paths", + dnsConfig: &core.PodDNSConfig{ + Searches: []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local", "exceeded"}, + }, + expectedError: true, + }, + { + desc: "invalid: 257 characters in search path list", + dnsConfig: &core.PodDNSConfig{ + // We can have 256 - (6 - 1) = 251 characters in total for 6 search paths. + Searches: []string{ + generateTestSearchPathFunc(2), + generateTestSearchPathFunc(50), + generateTestSearchPathFunc(50), + generateTestSearchPathFunc(50), + generateTestSearchPathFunc(50), + generateTestSearchPathFunc(50), + }, + }, + expectedError: true, + }, + { + desc: "invalid search path", + dnsConfig: &core.PodDNSConfig{ + Searches: []string{"custom?"}, + }, + expectedError: true, + }, + { + desc: "invalid nameserver", + dnsConfig: &core.PodDNSConfig{ + Nameservers: []string{"invalid"}, + }, + expectedError: true, + }, + { + desc: "invalid empty option name", + dnsConfig: &core.PodDNSConfig{ + Options: []core.PodDNSConfigOption{ + {Value: &testOptionValue}, + }, + }, + expectedError: true, + }, + { + desc: "invalid: DNSNone with 0 nameserver", + dnsConfig: &core.PodDNSConfig{ + Searches: []string{"custom"}, + }, + dnsPolicy: &testDNSNone, + expectedError: true, + }, + } + + for _, tc := range testCases { + if tc.dnsPolicy == nil { + tc.dnsPolicy = &testDNSClusterFirst + } + + errs := validatePodDNSConfig(tc.dnsConfig, tc.dnsPolicy, field.NewPath("dnsConfig")) + if len(errs) != 0 && !tc.expectedError { + t.Errorf("%v: validatePodDNSConfig(%v) = %v, want nil", tc.desc, tc.dnsConfig, errs) + } else if len(errs) == 0 && tc.expectedError { + t.Errorf("%v: validatePodDNSConfig(%v) = nil, want error", tc.desc, tc.dnsConfig) + } + } +} + +func TestValidatePodSpec(t *testing.T) { + activeDeadlineSeconds := int64(30) + activeDeadlineSecondsMax := int64(math.MaxInt32) + + minUserID := int64(0) + maxUserID := int64(2147483647) + minGroupID := int64(0) + maxGroupID := int64(2147483647) + + priorityEnabled := utilfeature.DefaultFeatureGate.Enabled("PodPriority") + defer func() { + var err error + // restoring the old value + if priorityEnabled { + err = utilfeature.DefaultFeatureGate.Set("PodPriority=true") + } else { + err = utilfeature.DefaultFeatureGate.Set("PodPriority=false") + } + if err != nil { + t.Errorf("Failed to restore feature gate for PodPriority: %v", err) + } + }() + err := utilfeature.DefaultFeatureGate.Set("PodPriority=true") + if err != nil { + t.Errorf("Failed to enable feature gate for PodPriority: %v", err) + return + } + successCases := []core.PodSpec{ + { // Populate basic fields, leave defaults for most. + Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + { // Populate all fields. + Volumes: []core.Volume{ + {Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}, + }, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + InitContainers: []core.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + NodeSelector: map[string]string{ + "key": "value", + }, + NodeName: "foobar", + DNSPolicy: core.DNSClusterFirst, + ActiveDeadlineSeconds: &activeDeadlineSeconds, + ServiceAccountName: "acct", + }, + { // Populate all fields with larger active deadline. + Volumes: []core.Volume{ + {Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}, + }, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + InitContainers: []core.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + NodeSelector: map[string]string{ + "key": "value", + }, + NodeName: "foobar", + DNSPolicy: core.DNSClusterFirst, + ActiveDeadlineSeconds: &activeDeadlineSecondsMax, + ServiceAccountName: "acct", + }, + { // Populate HostNetwork. + Containers: []core.Container{ + {Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", + Ports: []core.ContainerPort{ + {HostPort: 8080, ContainerPort: 8080, Protocol: "TCP"}}, + }, + }, + SecurityContext: &core.PodSecurityContext{ + HostNetwork: true, + }, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + { // Populate RunAsUser SupplementalGroups FSGroup with minID 0 + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + SecurityContext: &core.PodSecurityContext{ + SupplementalGroups: []int64{minGroupID}, + RunAsUser: &minUserID, + FSGroup: &minGroupID, + }, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + { // Populate RunAsUser SupplementalGroups FSGroup with maxID 2147483647 + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + SecurityContext: &core.PodSecurityContext{ + SupplementalGroups: []int64{maxGroupID}, + RunAsUser: &maxUserID, + FSGroup: &maxGroupID, + }, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + { // Populate HostIPC. + SecurityContext: &core.PodSecurityContext{ + HostIPC: true, + }, + Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + { // Populate HostPID. + SecurityContext: &core.PodSecurityContext{ + HostPID: true, + }, + Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + { // Populate Affinity. + Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + { // Populate HostAliases. + HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1", "host2"}}}, + Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + { // Populate HostAliases with `foo.bar` hostnames. + HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1.foo", "host2.bar"}}}, + Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + { // Populate HostAliases with HostNetwork. + HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1.foo", "host2.bar"}}}, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + SecurityContext: &core.PodSecurityContext{ + HostNetwork: true, + }, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + { // Populate PriorityClassName. + Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + PriorityClassName: "valid-name", + }, + } + for i := range successCases { + if errs := ValidatePodSpec(&successCases[i], field.NewPath("field")); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + + activeDeadlineSeconds = int64(0) + activeDeadlineSecondsTooLarge := int64(math.MaxInt32 + 1) + + minUserID = int64(-1) + maxUserID = int64(2147483648) + minGroupID = int64(-1) + maxGroupID = int64(2147483648) + + failureCases := map[string]core.PodSpec{ + "bad volume": { + Volumes: []core.Volume{{}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + }, + "no containers": { + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + "bad container": { + Containers: []core.Container{{}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + "bad init container": { + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + InitContainers: []core.Container{{}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + "bad DNS policy": { + DNSPolicy: core.DNSPolicy("invalid"), + RestartPolicy: core.RestartPolicyAlways, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + }, + "bad service account name": { + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + ServiceAccountName: "invalidName", + }, + "bad restart policy": { + RestartPolicy: "UnknowPolicy", + DNSPolicy: core.DNSClusterFirst, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + }, + "with hostNetwork hostPort not equal to containerPort": { + Containers: []core.Container{ + {Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", Ports: []core.ContainerPort{ + {HostPort: 8080, ContainerPort: 2600, Protocol: "TCP"}}, + }, + }, + SecurityContext: &core.PodSecurityContext{ + HostNetwork: true, + }, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + "with hostAliases with invalid IP": { + SecurityContext: &core.PodSecurityContext{ + HostNetwork: false, + }, + HostAliases: []core.HostAlias{{IP: "999.999.999.999", Hostnames: []string{"host1", "host2"}}}, + }, + "with hostAliases with invalid hostname": { + SecurityContext: &core.PodSecurityContext{ + HostNetwork: false, + }, + HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"@#$^#@#$"}}}, + }, + "bad supplementalGroups large than math.MaxInt32": { + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + SecurityContext: &core.PodSecurityContext{ + HostNetwork: false, + SupplementalGroups: []int64{maxGroupID, 1234}, + }, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + "bad supplementalGroups less than 0": { + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + SecurityContext: &core.PodSecurityContext{ + HostNetwork: false, + SupplementalGroups: []int64{minGroupID, 1234}, + }, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + "bad runAsUser large than math.MaxInt32": { + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + SecurityContext: &core.PodSecurityContext{ + HostNetwork: false, + RunAsUser: &maxUserID, + }, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + "bad runAsUser less than 0": { + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + SecurityContext: &core.PodSecurityContext{ + HostNetwork: false, + RunAsUser: &minUserID, + }, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + "bad fsGroup large than math.MaxInt32": { + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + SecurityContext: &core.PodSecurityContext{ + HostNetwork: false, + FSGroup: &maxGroupID, + }, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + "bad fsGroup less than 0": { + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + SecurityContext: &core.PodSecurityContext{ + HostNetwork: false, + FSGroup: &minGroupID, + }, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + "bad-active-deadline-seconds": { + Volumes: []core.Volume{ + {Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}, + }, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + NodeSelector: map[string]string{ + "key": "value", + }, + NodeName: "foobar", + DNSPolicy: core.DNSClusterFirst, + ActiveDeadlineSeconds: &activeDeadlineSeconds, + }, + "active-deadline-seconds-too-large": { + Volumes: []core.Volume{ + {Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}, + }, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + NodeSelector: map[string]string{ + "key": "value", + }, + NodeName: "foobar", + DNSPolicy: core.DNSClusterFirst, + ActiveDeadlineSeconds: &activeDeadlineSecondsTooLarge, + }, + "bad nodeName": { + NodeName: "node name", + Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + "bad PriorityClassName": { + Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + PriorityClassName: "InvalidName", + }, + "with privileged and allowPrivilegeEscalation false": { + Containers: []core.Container{ + { + Name: "ctr", + Image: "image", + ImagePullPolicy: "IfNotPresent", + Ports: []core.ContainerPort{ + {HostPort: 8080, ContainerPort: 2600, Protocol: "TCP"}}, + SecurityContext: &core.SecurityContext{ + Privileged: boolPtr(true), + AllowPrivilegeEscalation: boolPtr(false), + }, + }, + }, + }, + "with CAP_SYS_ADMIN and allowPrivilegeEscalation false": { + Containers: []core.Container{ + { + Name: "ctr", + Image: "image", + ImagePullPolicy: "IfNotPresent", + Ports: []core.ContainerPort{ + {HostPort: 8080, ContainerPort: 2600, Protocol: "TCP"}}, + SecurityContext: &core.SecurityContext{ + Capabilities: &core.Capabilities{ + Add: []core.Capability{"CAP_SYS_ADMIN"}, + }, + AllowPrivilegeEscalation: boolPtr(false), + }, + }, + }, + }, + } + for k, v := range failureCases { + if errs := ValidatePodSpec(&v, field.NewPath("field")); len(errs) == 0 { + t.Errorf("expected failure for %q", k) + } + } + + err = utilfeature.DefaultFeatureGate.Set("PodPriority=false") + if err != nil { + t.Errorf("Failed to disable feature gate for PodPriority: %v", err) + return + } + priority := int32(100) + featuregatedCases := map[string]core.PodSpec{ + "set PriorityClassName": { + Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + PriorityClassName: "valid-name", + }, + "set Priority": { + Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + Priority: &priority, + }, + } + for k, v := range featuregatedCases { + if errs := ValidatePodSpec(&v, field.NewPath("field")); len(errs) == 0 { + t.Errorf("expected failure due to gated feature: %q", k) + } + } +} + +func extendPodSpecwithTolerations(in core.PodSpec, tolerations []core.Toleration) core.PodSpec { + var out core.PodSpec + out.Containers = in.Containers + out.RestartPolicy = in.RestartPolicy + out.DNSPolicy = in.DNSPolicy + out.Tolerations = tolerations + return out +} + +func TestValidatePod(t *testing.T) { + validPodSpec := func(affinity *core.Affinity) core.PodSpec { + spec := core.PodSpec{ + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + } + if affinity != nil { + spec.Affinity = affinity + } + return spec + } + + successCases := []core.Pod{ + { // Basic fields. + ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, + Spec: core.PodSpec{ + Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + }, + { // Just about everything. + ObjectMeta: metav1.ObjectMeta{Name: "abc.123.do-re-mi", Namespace: "ns"}, + Spec: core.PodSpec{ + Volumes: []core.Volume{ + {Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}, + }, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + NodeSelector: map[string]string{ + "key": "value", + }, + NodeName: "foobar", + }, + }, + { // Serialized node affinity requirements. + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + }, + Spec: validPodSpec( + // TODO: Uncomment and move this block and move inside NodeAffinity once + // RequiredDuringSchedulingRequiredDuringExecution is implemented + // RequiredDuringSchedulingRequiredDuringExecution: &core.NodeSelector{ + // NodeSelectorTerms: []core.NodeSelectorTerm{ + // { + // MatchExpressions: []core.NodeSelectorRequirement{ + // { + // Key: "key1", + // Operator: core.NodeSelectorOpExists + // }, + // }, + // }, + // }, + // }, + &core.Affinity{ + NodeAffinity: &core.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ + NodeSelectorTerms: []core.NodeSelectorTerm{ + { + MatchExpressions: []core.NodeSelectorRequirement{ + { + Key: "key2", + Operator: core.NodeSelectorOpIn, + Values: []string{"value1", "value2"}, + }, + }, + }, + }, + }, + PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{ + { + Weight: 10, + Preference: core.NodeSelectorTerm{ + MatchExpressions: []core.NodeSelectorRequirement{ + { + Key: "foo", + Operator: core.NodeSelectorOpIn, + Values: []string{"bar"}, + }, + }, + }, + }, + }, + }, + }, + ), + }, + { // Serialized pod affinity in affinity requirements in annotations. + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + // TODO: Uncomment and move this block into Annotations map once + // RequiredDuringSchedulingRequiredDuringExecution is implemented + // "requiredDuringSchedulingRequiredDuringExecution": [{ + // "labelSelector": { + // "matchExpressions": [{ + // "key": "key2", + // "operator": "In", + // "values": ["value1", "value2"] + // }] + // }, + // "namespaces":["ns"], + // "topologyKey": "zone" + // }] + }, + Spec: validPodSpec(&core.Affinity{ + PodAffinity: &core.PodAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{ + { + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key2", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"value1", "value2"}, + }, + }, + }, + TopologyKey: "zone", + Namespaces: []string{"ns"}, + }, + }, + PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ + { + Weight: 10, + PodAffinityTerm: core.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key2", + Operator: metav1.LabelSelectorOpNotIn, + Values: []string{"value1", "value2"}, + }, + }, + }, + Namespaces: []string{"ns"}, + TopologyKey: "region", + }, + }, + }, + }, + }), + }, + { // Serialized pod anti affinity with different Label Operators in affinity requirements in annotations. + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + // TODO: Uncomment and move this block into Annotations map once + // RequiredDuringSchedulingRequiredDuringExecution is implemented + // "requiredDuringSchedulingRequiredDuringExecution": [{ + // "labelSelector": { + // "matchExpressions": [{ + // "key": "key2", + // "operator": "In", + // "values": ["value1", "value2"] + // }] + // }, + // "namespaces":["ns"], + // "topologyKey": "zone" + // }] + }, + Spec: validPodSpec(&core.Affinity{ + PodAntiAffinity: &core.PodAntiAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{ + { + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key2", + Operator: metav1.LabelSelectorOpExists, + }, + }, + }, + TopologyKey: "zone", + Namespaces: []string{"ns"}, + }, + }, + PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ + { + Weight: 10, + PodAffinityTerm: core.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key2", + Operator: metav1.LabelSelectorOpDoesNotExist, + }, + }, + }, + Namespaces: []string{"ns"}, + TopologyKey: "region", + }, + }, + }, + }, + }), + }, + { // populate forgiveness tolerations with exists operator in annotations. + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + }, + Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Exists", Value: "", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}}), + }, + { // populate forgiveness tolerations with equal operator in annotations. + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + }, + Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}}), + }, + { // populate tolerations equal operator in annotations. + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + }, + Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}), + }, + { // populate tolerations exists operator in annotations. + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + }, + Spec: validPodSpec(nil), + }, + { // empty key with Exists operator is OK for toleration, empty toleration key means match all taint keys. + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + }, + Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Operator: "Exists", Effect: "NoSchedule"}}), + }, + { // empty operator is OK for toleration, defaults to Equal. + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + }, + Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Value: "bar", Effect: "NoSchedule"}}), + }, + { // empty effect is OK for toleration, empty toleration effect means match all taint effects. + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + }, + Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar"}}), + }, + { // negative tolerationSeconds is OK for toleration. + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-forgiveness-invalid", + Namespace: "ns", + }, + Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "node.kubernetes.io/not-ready", Operator: "Exists", Effect: "NoExecute", TolerationSeconds: &[]int64{-2}[0]}}), + }, + { // docker default seccomp profile + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + core.SeccompPodAnnotationKey: "docker/default", + }, + }, + Spec: validPodSpec(nil), + }, + { // unconfined seccomp profile + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + core.SeccompPodAnnotationKey: "unconfined", + }, + }, + Spec: validPodSpec(nil), + }, + { // localhost seccomp profile + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + core.SeccompPodAnnotationKey: "localhost/foo", + }, + }, + Spec: validPodSpec(nil), + }, + { // localhost seccomp profile for a container + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + core.SeccompContainerAnnotationKeyPrefix + "foo": "localhost/foo", + }, + }, + Spec: validPodSpec(nil), + }, + { // default AppArmor profile for a container + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + apparmor.ContainerAnnotationKeyPrefix + "ctr": apparmor.ProfileRuntimeDefault, + }, + }, + Spec: validPodSpec(nil), + }, + { // default AppArmor profile for an init container + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + apparmor.ContainerAnnotationKeyPrefix + "init-ctr": apparmor.ProfileRuntimeDefault, + }, + }, + Spec: core.PodSpec{ + InitContainers: []core.Container{{Name: "init-ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + }, + { // localhost AppArmor profile for a container + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + apparmor.ContainerAnnotationKeyPrefix + "ctr": apparmor.ProfileNamePrefix + "foo", + }, + }, + Spec: validPodSpec(nil), + }, + { // syntactically valid sysctls + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + core.SysctlsPodAnnotationKey: "kernel.shmmni=32768,kernel.shmmax=1000000000", + core.UnsafeSysctlsPodAnnotationKey: "knet.ipv4.route.min_pmtu=1000", + }, + }, + Spec: validPodSpec(nil), + }, + { // valid extended resources for init container + ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"}, + Spec: core.PodSpec{ + InitContainers: []core.Container{ + { + Name: "valid-extended", + Image: "image", + ImagePullPolicy: "IfNotPresent", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("10"), + }, + Limits: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("10"), + }, + }, + TerminationMessagePolicy: "File", + }, + }, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + }, + { // valid extended resources for regular container + ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"}, + Spec: core.PodSpec{ + InitContainers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + Containers: []core.Container{ + { + Name: "valid-extended", + Image: "image", + ImagePullPolicy: "IfNotPresent", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("10"), + }, + Limits: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("10"), + }, + }, + TerminationMessagePolicy: "File", + }, + }, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + }, + } + for _, pod := range successCases { + if errs := ValidatePod(&pod); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + + errorCases := map[string]struct { + spec core.Pod + expectedError string + }{ + "bad name": { + expectedError: "metadata.name", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: "ns"}, + Spec: core.PodSpec{ + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + }, + }, + }, + "image whitespace": { + expectedError: "spec.containers[0].image", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"}, + Spec: core.PodSpec{ + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + Containers: []core.Container{{Name: "ctr", Image: " ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + }, + }, + }, + "image leading and trailing whitespace": { + expectedError: "spec.containers[0].image", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"}, + Spec: core.PodSpec{ + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + Containers: []core.Container{{Name: "ctr", Image: " something ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + }, + }, + }, + "bad namespace": { + expectedError: "metadata.namespace", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: ""}, + Spec: core.PodSpec{ + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + }, + }, + }, + "bad spec": { + expectedError: "spec.containers[0].name", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"}, + Spec: core.PodSpec{ + Containers: []core.Container{{}}, + }, + }, + }, + "bad label": { + expectedError: "NoUppercaseOrSpecialCharsLike=Equals", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + Namespace: "ns", + Labels: map[string]string{ + "NoUppercaseOrSpecialCharsLike=Equals": "bar", + }, + }, + Spec: core.PodSpec{ + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + }, + }, + }, + "invalid node selector requirement in node affinity, operator can't be null": { + expectedError: "spec.affinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].operator", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + }, + Spec: validPodSpec(&core.Affinity{ + NodeAffinity: &core.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ + NodeSelectorTerms: []core.NodeSelectorTerm{ + { + MatchExpressions: []core.NodeSelectorRequirement{ + { + Key: "key1", + }, + }, + }, + }, + }, + }, + }), + }, + }, + "invalid preferredSchedulingTerm in node affinity, weight should be in range 1-100": { + expectedError: "must be in the range 1-100", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + }, + Spec: validPodSpec(&core.Affinity{ + NodeAffinity: &core.NodeAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{ + { + Weight: 199, + Preference: core.NodeSelectorTerm{ + MatchExpressions: []core.NodeSelectorRequirement{ + { + Key: "foo", + Operator: core.NodeSelectorOpIn, + Values: []string{"bar"}, + }, + }, + }, + }, + }, + }, + }), + }, + }, + "invalid requiredDuringSchedulingIgnoredDuringExecution node selector, nodeSelectorTerms must have at least one term": { + expectedError: "spec.affinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + }, + Spec: validPodSpec(&core.Affinity{ + NodeAffinity: &core.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ + NodeSelectorTerms: []core.NodeSelectorTerm{}, + }, + }, + }), + }, + }, + "invalid requiredDuringSchedulingIgnoredDuringExecution node selector term, matchExpressions must have at least one node selector requirement": { + expectedError: "spec.affinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + }, + Spec: validPodSpec(&core.Affinity{ + NodeAffinity: &core.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ + NodeSelectorTerms: []core.NodeSelectorTerm{ + { + MatchExpressions: []core.NodeSelectorRequirement{}, + }, + }, + }, + }, + }), + }, + }, + "invalid weight in preferredDuringSchedulingIgnoredDuringExecution in pod affinity annotations, weight should be in range 1-100": { + expectedError: "must be in the range 1-100", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + }, + Spec: validPodSpec(&core.Affinity{ + PodAffinity: &core.PodAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ + { + Weight: 109, + PodAffinityTerm: core.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key2", + Operator: metav1.LabelSelectorOpNotIn, + Values: []string{"value1", "value2"}, + }, + }, + }, + Namespaces: []string{"ns"}, + TopologyKey: "region", + }, + }, + }, + }, + }), + }, + }, + "invalid labelSelector in preferredDuringSchedulingIgnoredDuringExecution in podaffinity annotations, values should be empty if the operator is Exists": { + expectedError: "spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.matchExpressions.matchExpressions[0].values", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + }, + Spec: validPodSpec(&core.Affinity{ + PodAntiAffinity: &core.PodAntiAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ + { + Weight: 10, + PodAffinityTerm: core.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key2", + Operator: metav1.LabelSelectorOpExists, + Values: []string{"value1", "value2"}, + }, + }, + }, + Namespaces: []string{"ns"}, + TopologyKey: "region", + }, + }, + }, + }, + }), + }, + }, + "invalid name space in preferredDuringSchedulingIgnoredDuringExecution in podaffinity annotations, name space shouldbe valid": { + expectedError: "spec.affinity.podAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.namespace", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + }, + Spec: validPodSpec(&core.Affinity{ + PodAffinity: &core.PodAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ + { + Weight: 10, + PodAffinityTerm: core.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key2", + Operator: metav1.LabelSelectorOpExists, + }, + }, + }, + Namespaces: []string{"INVALID_NAMESPACE"}, + TopologyKey: "region", + }, + }, + }, + }, + }), + }, + }, + "invalid hard pod affinity, empty topologyKey is not allowed for hard pod affinity": { + expectedError: "can not be empty", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + }, + Spec: validPodSpec(&core.Affinity{ + PodAffinity: &core.PodAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{ + { + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key2", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"value1", "value2"}, + }, + }, + }, + Namespaces: []string{"ns"}, + }, + }, + }, + }), + }, + }, + "invalid hard pod anti-affinity, empty topologyKey is not allowed for hard pod anti-affinity": { + expectedError: "can not be empty", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + }, + Spec: validPodSpec(&core.Affinity{ + PodAntiAffinity: &core.PodAntiAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{ + { + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key2", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"value1", "value2"}, + }, + }, + }, + Namespaces: []string{"ns"}, + }, + }, + }, + }), + }, + }, + "invalid soft pod affinity, empty topologyKey is not allowed for soft pod affinity": { + expectedError: "can not be empty", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + }, + Spec: validPodSpec(&core.Affinity{ + PodAffinity: &core.PodAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ + { + Weight: 10, + PodAffinityTerm: core.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key2", + Operator: metav1.LabelSelectorOpNotIn, + Values: []string{"value1", "value2"}, + }, + }, + }, + Namespaces: []string{"ns"}, + }, + }, + }, + }, + }), + }, + }, + "invalid soft pod anti-affinity, empty topologyKey is not allowed for soft pod anti-affinity": { + expectedError: "can not be empty", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + }, + Spec: validPodSpec(&core.Affinity{ + PodAntiAffinity: &core.PodAntiAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ + { + Weight: 10, + PodAffinityTerm: core.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key2", + Operator: metav1.LabelSelectorOpNotIn, + Values: []string{"value1", "value2"}, + }, + }, + }, + Namespaces: []string{"ns"}, + }, + }, + }, + }, + }), + }, + }, + "invalid toleration key": { + expectedError: "spec.tolerations[0].key", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + }, + Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "nospecialchars^=@", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}), + }, + }, + "invalid toleration operator": { + expectedError: "spec.tolerations[0].operator", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + }, + Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "In", Value: "bar", Effect: "NoSchedule"}}), + }, + }, + "value must be empty when `operator` is 'Exists'": { + expectedError: "spec.tolerations[0].operator", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + }, + Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Exists", Value: "bar", Effect: "NoSchedule"}}), + }, + }, + + "operator must be 'Exists' when `key` is empty": { + expectedError: "spec.tolerations[0].operator", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + }, + Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}), + }, + }, + "effect must be 'NoExecute' when `TolerationSeconds` is set": { + expectedError: "spec.tolerations[0].effect", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-forgiveness-invalid", + Namespace: "ns", + }, + Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "node.kubernetes.io/not-ready", Operator: "Exists", Effect: "NoSchedule", TolerationSeconds: &[]int64{20}[0]}}), + }, + }, + "must be a valid pod seccomp profile": { + expectedError: "must be a valid seccomp profile", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + core.SeccompPodAnnotationKey: "foo", + }, + }, + Spec: validPodSpec(nil), + }, + }, + "must be a valid container seccomp profile": { + expectedError: "must be a valid seccomp profile", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + core.SeccompContainerAnnotationKeyPrefix + "foo": "foo", + }, + }, + Spec: validPodSpec(nil), + }, + }, + "must be a non-empty container name in seccomp annotation": { + expectedError: "name part must be non-empty", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + core.SeccompContainerAnnotationKeyPrefix: "foo", + }, + }, + Spec: validPodSpec(nil), + }, + }, + "must be a non-empty container profile in seccomp annotation": { + expectedError: "must be a valid seccomp profile", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + core.SeccompContainerAnnotationKeyPrefix + "foo": "", + }, + }, + Spec: validPodSpec(nil), + }, + }, + "must be a relative path in a node-local seccomp profile annotation": { + expectedError: "must be a relative path", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + core.SeccompPodAnnotationKey: "localhost//foo", + }, + }, + Spec: validPodSpec(nil), + }, + }, + "must not start with '../'": { + expectedError: "must not contain '..'", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + core.SeccompPodAnnotationKey: "localhost/../foo", + }, + }, + Spec: validPodSpec(nil), + }, + }, + "AppArmor profile must apply to a container": { + expectedError: "metadata.annotations[container.apparmor.security.beta.kubernetes.io/fake-ctr]", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + apparmor.ContainerAnnotationKeyPrefix + "ctr": apparmor.ProfileRuntimeDefault, + apparmor.ContainerAnnotationKeyPrefix + "init-ctr": apparmor.ProfileRuntimeDefault, + apparmor.ContainerAnnotationKeyPrefix + "fake-ctr": apparmor.ProfileRuntimeDefault, + }, + }, + Spec: core.PodSpec{ + InitContainers: []core.Container{{Name: "init-ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + }, + }, + "AppArmor profile format must be valid": { + expectedError: "invalid AppArmor profile name", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + apparmor.ContainerAnnotationKeyPrefix + "ctr": "bad-name", + }, + }, + Spec: validPodSpec(nil), + }, + }, + "only default AppArmor profile may start with runtime/": { + expectedError: "invalid AppArmor profile name", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + apparmor.ContainerAnnotationKeyPrefix + "ctr": "runtime/foo", + }, + }, + Spec: validPodSpec(nil), + }, + }, + "invalid sysctl annotation": { + expectedError: "metadata.annotations[security.alpha.kubernetes.io/sysctls]", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + core.SysctlsPodAnnotationKey: "foo:", + }, + }, + Spec: validPodSpec(nil), + }, + }, + "invalid comma-separated sysctl annotation": { + expectedError: "not of the format sysctl_name=value", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + core.SysctlsPodAnnotationKey: "kernel.msgmax,", + }, + }, + Spec: validPodSpec(nil), + }, + }, + "invalid unsafe sysctl annotation": { + expectedError: "metadata.annotations[security.alpha.kubernetes.io/sysctls]", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + core.SysctlsPodAnnotationKey: "foo:", + }, + }, + Spec: validPodSpec(nil), + }, + }, + "intersecting safe sysctls and unsafe sysctls annotations": { + expectedError: "can not be safe and unsafe", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + Annotations: map[string]string{ + core.SysctlsPodAnnotationKey: "kernel.shmmax=10000000", + core.UnsafeSysctlsPodAnnotationKey: "kernel.shmmax=10000000", + }, + }, + Spec: validPodSpec(nil), + }, + }, + "invalid extended resource requirement: request must be == limit": { + expectedError: "must be equal to example.com/a", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, + Spec: core.PodSpec{ + Containers: []core.Container{ + { + Name: "invalid", + Image: "image", + ImagePullPolicy: "IfNotPresent", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("2"), + }, + Limits: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("1"), + }, + }, + }, + }, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + }, + }, + "invalid extended resource requirement without limit": { + expectedError: "Limit must be set", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, + Spec: core.PodSpec{ + Containers: []core.Container{ + { + Name: "invalid", + Image: "image", + ImagePullPolicy: "IfNotPresent", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("2"), + }, + }, + }, + }, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + }, + }, + "invalid fractional extended resource in container request": { + expectedError: "must be an integer", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, + Spec: core.PodSpec{ + Containers: []core.Container{ + { + Name: "invalid", + Image: "image", + ImagePullPolicy: "IfNotPresent", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("500m"), + }, + }, + }, + }, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + }, + }, + "invalid fractional extended resource in init container request": { + expectedError: "must be an integer", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, + Spec: core.PodSpec{ + InitContainers: []core.Container{ + { + Name: "invalid", + Image: "image", + ImagePullPolicy: "IfNotPresent", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("500m"), + }, + }, + }, + }, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + }, + }, + "invalid fractional extended resource in container limit": { + expectedError: "must be an integer", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, + Spec: core.PodSpec{ + Containers: []core.Container{ + { + Name: "invalid", + Image: "image", + ImagePullPolicy: "IfNotPresent", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("5"), + }, + Limits: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("2.5"), + }, + }, + }, + }, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + }, + }, + "invalid fractional extended resource in init container limit": { + expectedError: "must be an integer", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, + Spec: core.PodSpec{ + InitContainers: []core.Container{ + { + Name: "invalid", + Image: "image", + ImagePullPolicy: "IfNotPresent", + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("2.5"), + }, + Limits: core.ResourceList{ + core.ResourceName("example.com/a"): resource.MustParse("2.5"), + }, + }, + }, + }, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + }, + }, + "mirror-pod present without nodeName": { + expectedError: "mirror", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.MirrorPodAnnotationKey: ""}}, + Spec: core.PodSpec{ + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + }, + }, + "mirror-pod populated without nodeName": { + expectedError: "mirror", + spec: core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.MirrorPodAnnotationKey: "foo"}}, + Spec: core.PodSpec{ + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + }, + }, + } + for k, v := range errorCases { + if errs := ValidatePod(&v.spec); len(errs) == 0 { + t.Errorf("expected failure for %q", k) + } else if v.expectedError == "" { + t.Errorf("missing expectedError for %q, got %q", k, errs.ToAggregate().Error()) + } else if actualError := errs.ToAggregate().Error(); !strings.Contains(actualError, v.expectedError) { + t.Errorf("expected error for %q to contain %q, got %q", k, v.expectedError, actualError) + } + } +} + +func TestValidatePodUpdate(t *testing.T) { + var ( + activeDeadlineSecondsZero = int64(0) + activeDeadlineSecondsNegative = int64(-30) + activeDeadlineSecondsPositive = int64(30) + activeDeadlineSecondsLarger = int64(31) + + now = metav1.Now() + grace = int64(30) + grace2 = int64(31) + ) + + tests := []struct { + new core.Pod + old core.Pod + err string + test string + }{ + {core.Pod{}, core.Pod{}, "", "nothing"}, + { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "bar"}, + }, + "metadata.name", + "ids", + }, + { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Labels: map[string]string{ + "foo": "bar", + }, + }, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Labels: map[string]string{ + "bar": "foo", + }, + }, + }, + "", + "labels", + }, + { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Annotations: map[string]string{ + "foo": "bar", + }, + }, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Annotations: map[string]string{ + "bar": "foo", + }, + }, + }, + "", + "annotations", + }, + { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.PodSpec{ + Containers: []core.Container{ + { + Image: "foo:V1", + }, + }, + }, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: core.PodSpec{ + Containers: []core.Container{ + { + Image: "foo:V2", + }, + { + Image: "bar:V2", + }, + }, + }, + }, + "may not add or remove containers", + "less containers", + }, + { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.PodSpec{ + Containers: []core.Container{ + { + Image: "foo:V1", + }, + { + Image: "bar:V2", + }, + }, + }, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: core.PodSpec{ + Containers: []core.Container{ + { + Image: "foo:V2", + }, + }, + }, + }, + "may not add or remove containers", + "more containers", + }, + { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.PodSpec{ + InitContainers: []core.Container{ + { + Image: "foo:V1", + }, + }, + }, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: core.PodSpec{ + InitContainers: []core.Container{ + { + Image: "foo:V2", + }, + { + Image: "bar:V2", + }, + }, + }, + }, + "may not add or remove containers", + "more init containers", + }, + { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}}, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now}, + Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}}, + }, + "", + "deletion timestamp removed", + }, + { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now}, + Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}}, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}}, + }, + "metadata.deletionTimestamp", + "deletion timestamp added", + }, + { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &grace}, + Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}}, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &grace2}, + Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}}, + }, + "metadata.deletionGracePeriodSeconds", + "deletion grace period seconds changed", + }, + { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: core.PodSpec{ + Containers: []core.Container{ + { + Image: "foo:V1", + }, + }, + }, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: core.PodSpec{ + Containers: []core.Container{ + { + Image: "foo:V2", + }, + }, + }, + }, + "", + "image change", + }, + { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: core.PodSpec{ + InitContainers: []core.Container{ + { + Image: "foo:V1", + }, + }, + }, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: core.PodSpec{ + InitContainers: []core.Container{ + { + Image: "foo:V2", + }, + }, + }, + }, + "", + "init container image change", + }, + { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: core.PodSpec{ + Containers: []core.Container{ + {}, + }, + }, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: core.PodSpec{ + Containers: []core.Container{ + { + Image: "foo:V2", + }, + }, + }, + }, + "spec.containers[0].image", + "image change to empty", + }, + { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: core.PodSpec{ + InitContainers: []core.Container{ + {}, + }, + }, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: core.PodSpec{ + InitContainers: []core.Container{ + { + Image: "foo:V2", + }, + }, + }, + }, + "spec.initContainers[0].image", + "init container image change to empty", + }, + { + core.Pod{ + Spec: core.PodSpec{}, + }, + core.Pod{ + Spec: core.PodSpec{}, + }, + "", + "activeDeadlineSeconds no change, nil", + }, + { + core.Pod{ + Spec: core.PodSpec{ + ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, + }, + }, + core.Pod{ + Spec: core.PodSpec{ + ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, + }, + }, + "", + "activeDeadlineSeconds no change, set", + }, + { + core.Pod{ + Spec: core.PodSpec{ + ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, + }, + }, + core.Pod{}, + "", + "activeDeadlineSeconds change to positive from nil", + }, + { + core.Pod{ + Spec: core.PodSpec{ + ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, + }, + }, + core.Pod{ + Spec: core.PodSpec{ + ActiveDeadlineSeconds: &activeDeadlineSecondsLarger, + }, + }, + "", + "activeDeadlineSeconds change to smaller positive", + }, + { + core.Pod{ + Spec: core.PodSpec{ + ActiveDeadlineSeconds: &activeDeadlineSecondsLarger, + }, + }, + core.Pod{ + Spec: core.PodSpec{ + ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, + }, + }, + "spec.activeDeadlineSeconds", + "activeDeadlineSeconds change to larger positive", + }, + + { + core.Pod{ + Spec: core.PodSpec{ + ActiveDeadlineSeconds: &activeDeadlineSecondsNegative, + }, + }, + core.Pod{}, + "spec.activeDeadlineSeconds", + "activeDeadlineSeconds change to negative from nil", + }, + { + core.Pod{ + Spec: core.PodSpec{ + ActiveDeadlineSeconds: &activeDeadlineSecondsNegative, + }, + }, + core.Pod{ + Spec: core.PodSpec{ + ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, + }, + }, + "spec.activeDeadlineSeconds", + "activeDeadlineSeconds change to negative from positive", + }, + { + core.Pod{ + Spec: core.PodSpec{ + ActiveDeadlineSeconds: &activeDeadlineSecondsZero, + }, + }, + core.Pod{ + Spec: core.PodSpec{ + ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, + }, + }, + "", + "activeDeadlineSeconds change to zero from positive", + }, + { + core.Pod{ + Spec: core.PodSpec{ + ActiveDeadlineSeconds: &activeDeadlineSecondsZero, + }, + }, + core.Pod{}, + "", + "activeDeadlineSeconds change to zero from nil", + }, + { + core.Pod{}, + core.Pod{ + Spec: core.PodSpec{ + ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, + }, + }, + "spec.activeDeadlineSeconds", + "activeDeadlineSeconds change to nil from positive", + }, + { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: core.PodSpec{ + Containers: []core.Container{ + { + Image: "foo:V1", + Resources: core.ResourceRequirements{ + Limits: getResourceLimits("100m", "0"), + }, + }, + }, + }, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: core.PodSpec{ + Containers: []core.Container{ + { + Image: "foo:V2", + Resources: core.ResourceRequirements{ + Limits: getResourceLimits("1000m", "0"), + }, + }, + }, + }, + }, + "spec: Forbidden: pod updates may not change fields", + "cpu change", + }, + { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: core.PodSpec{ + Containers: []core.Container{ + { + Image: "foo:V1", + Ports: []core.ContainerPort{ + {HostPort: 8080, ContainerPort: 80}, + }, + }, + }, + }, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: core.PodSpec{ + Containers: []core.Container{ + { + Image: "foo:V2", + Ports: []core.ContainerPort{ + {HostPort: 8000, ContainerPort: 80}, + }, + }, + }, + }, + }, + "spec: Forbidden: pod updates may not change fields", + "port change", + }, + { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Labels: map[string]string{ + "foo": "bar", + }, + }, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Labels: map[string]string{ + "Bar": "foo", + }, + }, + }, + "", + "bad label change", + }, + { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.PodSpec{ + NodeName: "node1", + Tolerations: []core.Toleration{{Key: "key1", Value: "value2"}}, + }, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.PodSpec{ + NodeName: "node1", + Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}}, + }, + }, + "spec.tolerations: Forbidden", + "existing toleration value modified in pod spec updates", + }, + { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.PodSpec{ + NodeName: "node1", + Tolerations: []core.Toleration{{Key: "key1", Value: "value2", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: nil}}, + }, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.PodSpec{ + NodeName: "node1", + Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}}, + }, + }, + "spec.tolerations: Forbidden", + "existing toleration value modified in pod spec updates with modified tolerationSeconds", + }, + { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.PodSpec{ + NodeName: "node1", + Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}}, + }, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.PodSpec{ + NodeName: "node1", + Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{20}[0]}}, + }}, + "", + "modified tolerationSeconds in existing toleration value in pod spec updates", + }, + { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.PodSpec{ + Tolerations: []core.Toleration{{Key: "key1", Value: "value2"}}, + }, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.PodSpec{ + NodeName: "", + Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}}, + }, + }, + "spec.tolerations: Forbidden", + "toleration modified in updates to an unscheduled pod", + }, + { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.PodSpec{ + NodeName: "node1", + Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}}, + }, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.PodSpec{ + NodeName: "node1", + Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}}, + }, + }, + "", + "tolerations unmodified in updates to a scheduled pod", + }, + { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.PodSpec{ + NodeName: "node1", + Tolerations: []core.Toleration{ + {Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{20}[0]}, + {Key: "key2", Value: "value2", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{30}[0]}, + }, + }}, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.PodSpec{ + NodeName: "node1", + Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}}, + }, + }, + "", + "added valid new toleration to existing tolerations in pod spec updates", + }, + { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{ + NodeName: "node1", + Tolerations: []core.Toleration{ + {Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{20}[0]}, + {Key: "key2", Value: "value2", Operator: "Equal", Effect: "NoSchedule", TolerationSeconds: &[]int64{30}[0]}, + }, + }}, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.PodSpec{ + NodeName: "node1", Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}}, + }}, + "spec.tolerations[1].effect", + "added invalid new toleration to existing tolerations in pod spec updates", + }, + { + core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{NodeName: "foo"}}, + core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}, + "spec: Forbidden: pod updates may not change fields", + "removed nodeName from pod spec", + }, + { + core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: ""}}, Spec: core.PodSpec{NodeName: "foo"}}, + core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{NodeName: "foo"}}, + "metadata.annotations[kubernetes.io/config.mirror]", + "added mirror pod annotation", + }, + { + core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{NodeName: "foo"}}, + core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: ""}}, Spec: core.PodSpec{NodeName: "foo"}}, + "metadata.annotations[kubernetes.io/config.mirror]", + "removed mirror pod annotation", + }, + { + core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: "foo"}}, Spec: core.PodSpec{NodeName: "foo"}}, + core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: "bar"}}, Spec: core.PodSpec{NodeName: "foo"}}, + "metadata.annotations[kubernetes.io/config.mirror]", + "changed mirror pod annotation", + }, + { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.PodSpec{ + NodeName: "node1", + PriorityClassName: "bar-priority", + }, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.PodSpec{ + NodeName: "node1", + PriorityClassName: "foo-priority", + }, + }, + "spec: Forbidden: pod updates", + "changed priority class name", + }, + { + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.PodSpec{ + NodeName: "node1", + PriorityClassName: "", + }, + }, + core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.PodSpec{ + NodeName: "node1", + PriorityClassName: "foo-priority", + }, + }, + "spec: Forbidden: pod updates", + "removed priority class name", + }, + } + + for _, test := range tests { + test.new.ObjectMeta.ResourceVersion = "1" + test.old.ObjectMeta.ResourceVersion = "1" + errs := ValidatePodUpdate(&test.new, &test.old) + if test.err == "" { + if len(errs) != 0 { + t.Errorf("unexpected invalid: %s (%+v)\nA: %+v\nB: %+v", test.test, errs, test.new, test.old) + } + } else { + if len(errs) == 0 { + t.Errorf("unexpected valid: %s\nA: %+v\nB: %+v", test.test, test.new, test.old) + } else if actualErr := errs.ToAggregate().Error(); !strings.Contains(actualErr, test.err) { + t.Errorf("unexpected error message: %s\nExpected error: %s\nActual error: %s", test.test, test.err, actualErr) + } + } + } +} + +func makeValidService() core.Service { + return core.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid", + Namespace: "valid", + Labels: map[string]string{}, + Annotations: map[string]string{}, + ResourceVersion: "1", + }, + Spec: core.ServiceSpec{ + Selector: map[string]string{"key": "val"}, + SessionAffinity: "None", + Type: core.ServiceTypeClusterIP, + Ports: []core.ServicePort{{Name: "p", Protocol: "TCP", Port: 8675, TargetPort: intstr.FromInt(8675)}}, + }, + } +} + +func TestValidateService(t *testing.T) { + testCases := []struct { + name string + tweakSvc func(svc *core.Service) // given a basic valid service, each test case can customize it + numErrs int + }{ + { + name: "missing namespace", + tweakSvc: func(s *core.Service) { + s.Namespace = "" + }, + numErrs: 1, + }, + { + name: "invalid namespace", + tweakSvc: func(s *core.Service) { + s.Namespace = "-123" + }, + numErrs: 1, + }, + { + name: "missing name", + tweakSvc: func(s *core.Service) { + s.Name = "" + }, + numErrs: 1, + }, + { + name: "invalid name", + tweakSvc: func(s *core.Service) { + s.Name = "-123" + }, + numErrs: 1, + }, + { + name: "too long name", + tweakSvc: func(s *core.Service) { + s.Name = strings.Repeat("a", 64) + }, + numErrs: 1, + }, + { + name: "invalid generateName", + tweakSvc: func(s *core.Service) { + s.GenerateName = "-123" + }, + numErrs: 1, + }, + { + name: "too long generateName", + tweakSvc: func(s *core.Service) { + s.GenerateName = strings.Repeat("a", 64) + }, + numErrs: 1, + }, + { + name: "invalid label", + tweakSvc: func(s *core.Service) { + s.Labels["NoUppercaseOrSpecialCharsLike=Equals"] = "bar" + }, + numErrs: 1, + }, + { + name: "invalid annotation", + tweakSvc: func(s *core.Service) { + s.Annotations["NoSpecialCharsLike=Equals"] = "bar" + }, + numErrs: 1, + }, + { + name: "nil selector", + tweakSvc: func(s *core.Service) { + s.Spec.Selector = nil + }, + numErrs: 0, + }, + { + name: "invalid selector", + tweakSvc: func(s *core.Service) { + s.Spec.Selector["NoSpecialCharsLike=Equals"] = "bar" + }, + numErrs: 1, + }, + { + name: "missing session affinity", + tweakSvc: func(s *core.Service) { + s.Spec.SessionAffinity = "" + }, + numErrs: 1, + }, + { + name: "missing type", + tweakSvc: func(s *core.Service) { + s.Spec.Type = "" + }, + numErrs: 1, + }, + { + name: "missing ports", + tweakSvc: func(s *core.Service) { + s.Spec.Ports = nil + }, + numErrs: 1, + }, + { + name: "missing ports but headless", + tweakSvc: func(s *core.Service) { + s.Spec.Ports = nil + s.Spec.ClusterIP = core.ClusterIPNone + }, + numErrs: 0, + }, + { + name: "empty port[0] name", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].Name = "" + }, + numErrs: 0, + }, + { + name: "empty port[1] name", + tweakSvc: func(s *core.Service) { + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "", Protocol: "TCP", Port: 12345, TargetPort: intstr.FromInt(12345)}) + }, + numErrs: 1, + }, + { + name: "empty multi-port port[0] name", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].Name = "" + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p", Protocol: "TCP", Port: 12345, TargetPort: intstr.FromInt(12345)}) + }, + numErrs: 1, + }, + { + name: "invalid port name", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].Name = "INVALID" + }, + numErrs: 1, + }, + { + name: "missing protocol", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].Protocol = "" + }, + numErrs: 1, + }, + { + name: "invalid protocol", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].Protocol = "INVALID" + }, + numErrs: 1, + }, + { + name: "invalid cluster ip", + tweakSvc: func(s *core.Service) { + s.Spec.ClusterIP = "invalid" + }, + numErrs: 1, + }, + { + name: "missing port", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].Port = 0 + }, + numErrs: 1, + }, + { + name: "invalid port", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].Port = 65536 + }, + numErrs: 1, + }, + { + name: "invalid TargetPort int", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].TargetPort = intstr.FromInt(65536) + }, + numErrs: 1, + }, + { + name: "valid port headless", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].Port = 11722 + s.Spec.Ports[0].TargetPort = intstr.FromInt(11722) + s.Spec.ClusterIP = core.ClusterIPNone + }, + numErrs: 0, + }, + { + name: "invalid port headless 1", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].Port = 11722 + s.Spec.Ports[0].TargetPort = intstr.FromInt(11721) + s.Spec.ClusterIP = core.ClusterIPNone + }, + // in the v1 API, targetPorts on headless services were tolerated. + // once we have version-specific validation, we can reject this on newer API versions, but until then, we have to tolerate it for compatibility. + // numErrs: 1, + numErrs: 0, + }, + { + name: "invalid port headless 2", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].Port = 11722 + s.Spec.Ports[0].TargetPort = intstr.FromString("target") + s.Spec.ClusterIP = core.ClusterIPNone + }, + // in the v1 API, targetPorts on headless services were tolerated. + // once we have version-specific validation, we can reject this on newer API versions, but until then, we have to tolerate it for compatibility. + // numErrs: 1, + numErrs: 0, + }, + { + name: "invalid publicIPs localhost", + tweakSvc: func(s *core.Service) { + s.Spec.ExternalIPs = []string{"127.0.0.1"} + }, + numErrs: 1, + }, + { + name: "invalid publicIPs unspecified", + tweakSvc: func(s *core.Service) { + s.Spec.ExternalIPs = []string{"0.0.0.0"} + }, + numErrs: 1, + }, + { + name: "invalid publicIPs loopback", + tweakSvc: func(s *core.Service) { + s.Spec.ExternalIPs = []string{"127.0.0.1"} + }, + numErrs: 1, + }, + { + name: "invalid publicIPs host", + tweakSvc: func(s *core.Service) { + s.Spec.ExternalIPs = []string{"myhost.mydomain"} + }, + numErrs: 1, + }, + { + name: "dup port name", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].Name = "p" + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(12345)}) + }, + numErrs: 1, + }, + { + name: "valid load balancer protocol UDP 1", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.Ports[0].Protocol = "UDP" + }, + numErrs: 0, + }, + { + name: "valid load balancer protocol UDP 2", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.Ports[0] = core.ServicePort{Name: "q", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt(12345)} + }, + numErrs: 0, + }, + { + name: "invalid load balancer with mix protocol", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt(12345)}) + }, + numErrs: 1, + }, + { + name: "valid 1", + tweakSvc: func(s *core.Service) { + // do nothing + }, + numErrs: 0, + }, + { + name: "valid 2", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].Protocol = "UDP" + s.Spec.Ports[0].TargetPort = intstr.FromInt(12345) + }, + numErrs: 0, + }, + { + name: "valid 3", + tweakSvc: func(s *core.Service) { + s.Spec.Ports[0].TargetPort = intstr.FromString("http") + }, + numErrs: 0, + }, + { + name: "valid cluster ip - none ", + tweakSvc: func(s *core.Service) { + s.Spec.ClusterIP = "None" + }, + numErrs: 0, + }, + { + name: "valid cluster ip - empty", + tweakSvc: func(s *core.Service) { + s.Spec.ClusterIP = "" + s.Spec.Ports[0].TargetPort = intstr.FromString("http") + }, + numErrs: 0, + }, + { + name: "valid type - cluster", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeClusterIP + }, + numErrs: 0, + }, + { + name: "valid type - loadbalancer", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + }, + numErrs: 0, + }, + { + name: "valid type loadbalancer 2 ports", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(12345)}) + }, + numErrs: 0, + }, + { + name: "valid external load balancer 2 ports", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(12345)}) + }, + numErrs: 0, + }, + { + name: "duplicate nodeports", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeNodePort + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(1)}) + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 2, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(2)}) + }, + numErrs: 1, + }, + { + name: "duplicate nodeports (different protocols)", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeNodePort + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(1)}) + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 2, Protocol: "UDP", NodePort: 1, TargetPort: intstr.FromInt(2)}) + }, + numErrs: 0, + }, + { + name: "invalid duplicate ports (with same protocol)", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeClusterIP + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}) + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(80)}) + }, + numErrs: 1, + }, + { + name: "valid duplicate ports (with different protocols)", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeClusterIP + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}) + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt(80)}) + }, + numErrs: 0, + }, + { + name: "valid type - cluster", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeClusterIP + }, + numErrs: 0, + }, + { + name: "valid type - nodeport", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeNodePort + }, + numErrs: 0, + }, + { + name: "valid type - loadbalancer", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + }, + numErrs: 0, + }, + { + name: "valid type loadbalancer 2 ports", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(12345)}) + }, + numErrs: 0, + }, + { + name: "valid type loadbalancer with NodePort", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt(12345)}) + }, + numErrs: 0, + }, + { + name: "valid type=NodePort service with NodePort", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeNodePort + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt(12345)}) + }, + numErrs: 0, + }, + { + name: "valid type=NodePort service without NodePort", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeNodePort + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(12345)}) + }, + numErrs: 0, + }, + { + name: "valid cluster service without NodePort", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeClusterIP + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(12345)}) + }, + numErrs: 0, + }, + { + name: "invalid cluster service with NodePort", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeClusterIP + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt(12345)}) + }, + numErrs: 1, + }, + { + name: "invalid public service with duplicate NodePort", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeNodePort + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p1", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(1)}) + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p2", Port: 2, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(2)}) + }, + numErrs: 1, + }, + { + name: "valid type=LoadBalancer", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(12345)}) + }, + numErrs: 0, + }, + { + // For now we open firewalls, and its insecure if we open 10250, remove this + // when we have better protections in place. + name: "invalid port type=LoadBalancer", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "kubelet", Port: 10250, Protocol: "TCP", TargetPort: intstr.FromInt(12345)}) + }, + numErrs: 1, + }, + { + name: "valid LoadBalancer source range annotation", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "1.2.3.4/8, 5.6.7.8/16" + }, + numErrs: 0, + }, + { + name: "empty LoadBalancer source range annotation", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "" + }, + numErrs: 0, + }, + { + name: "invalid LoadBalancer source range annotation (hostname)", + tweakSvc: func(s *core.Service) { + s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "foo.bar" + }, + numErrs: 2, + }, + { + name: "invalid LoadBalancer source range annotation (invalid CIDR)", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "1.2.3.4/33" + }, + numErrs: 1, + }, + { + name: "invalid source range for non LoadBalancer type service", + tweakSvc: func(s *core.Service) { + s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.4/8", "5.6.7.8/16"} + }, + numErrs: 1, + }, + { + name: "valid LoadBalancer source range", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.4/8", "5.6.7.8/16"} + }, + numErrs: 0, + }, + { + name: "empty LoadBalancer source range", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.LoadBalancerSourceRanges = []string{" "} + }, + numErrs: 1, + }, + { + name: "invalid LoadBalancer source range", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.LoadBalancerSourceRanges = []string{"foo.bar"} + }, + numErrs: 1, + }, + { + name: "valid ExternalName", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeExternalName + s.Spec.ClusterIP = "" + s.Spec.ExternalName = "foo.bar.example.com" + }, + numErrs: 0, + }, + { + name: "invalid ExternalName clusterIP (valid IP)", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeExternalName + s.Spec.ClusterIP = "1.2.3.4" + s.Spec.ExternalName = "foo.bar.example.com" + }, + numErrs: 1, + }, + { + name: "invalid ExternalName clusterIP (None)", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeExternalName + s.Spec.ClusterIP = "None" + s.Spec.ExternalName = "foo.bar.example.com" + }, + numErrs: 1, + }, + { + name: "invalid ExternalName (not a DNS name)", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeExternalName + s.Spec.ClusterIP = "" + s.Spec.ExternalName = "-123" + }, + numErrs: 1, + }, + { + name: "LoadBalancer type cannot have None ClusterIP", + tweakSvc: func(s *core.Service) { + s.Spec.ClusterIP = "None" + s.Spec.Type = core.ServiceTypeLoadBalancer + }, + numErrs: 1, + }, + { + name: "invalid node port with clusterIP None", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeNodePort + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(1)}) + s.Spec.ClusterIP = "None" + }, + numErrs: 1, + }, + // ESIPP section begins. + { + name: "invalid externalTraffic field", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.ExternalTrafficPolicy = "invalid" + }, + numErrs: 1, + }, + { + name: "nagative healthCheckNodePort field", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyTypeLocal + s.Spec.HealthCheckNodePort = -1 + }, + numErrs: 1, + }, + { + name: "nagative healthCheckNodePort field", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyTypeLocal + s.Spec.HealthCheckNodePort = 31100 + }, + numErrs: 0, + }, + // ESIPP section ends. + { + name: "invalid timeoutSeconds field", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeClusterIP + s.Spec.SessionAffinity = core.ServiceAffinityClientIP + s.Spec.SessionAffinityConfig = &core.SessionAffinityConfig{ + ClientIP: &core.ClientIPConfig{ + TimeoutSeconds: newInt32(-1), + }, + } + }, + numErrs: 1, + }, + { + name: "sessionAffinityConfig can't be set when session affinity is None", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.SessionAffinity = core.ServiceAffinityNone + s.Spec.SessionAffinityConfig = &core.SessionAffinityConfig{ + ClientIP: &core.ClientIPConfig{ + TimeoutSeconds: newInt32(90), + }, + } + }, + numErrs: 1, + }, + } + + for _, tc := range testCases { + svc := makeValidService() + tc.tweakSvc(&svc) + errs := ValidateService(&svc) + if len(errs) != tc.numErrs { + t.Errorf("Unexpected error list for case %q: %v", tc.name, errs.ToAggregate()) + } + } +} + +func TestValidateServiceExternalTrafficFieldsCombination(t *testing.T) { + testCases := []struct { + name string + tweakSvc func(svc *core.Service) // Given a basic valid service, each test case can customize it. + numErrs int + }{ + { + name: "valid loadBalancer service with externalTrafficPolicy and healthCheckNodePort set", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyTypeLocal + s.Spec.HealthCheckNodePort = 34567 + }, + numErrs: 0, + }, + { + name: "valid nodePort service with externalTrafficPolicy set", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeNodePort + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyTypeLocal + }, + numErrs: 0, + }, + { + name: "valid clusterIP service with none of externalTrafficPolicy and healthCheckNodePort set", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeClusterIP + }, + numErrs: 0, + }, + { + name: "cannot set healthCheckNodePort field on loadBalancer service with externalTrafficPolicy!=Local", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeLoadBalancer + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyTypeCluster + s.Spec.HealthCheckNodePort = 34567 + }, + numErrs: 1, + }, + { + name: "cannot set healthCheckNodePort field on nodePort service", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeNodePort + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyTypeLocal + s.Spec.HealthCheckNodePort = 34567 + }, + numErrs: 1, + }, + { + name: "cannot set externalTrafficPolicy or healthCheckNodePort fields on clusterIP service", + tweakSvc: func(s *core.Service) { + s.Spec.Type = core.ServiceTypeClusterIP + s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyTypeLocal + s.Spec.HealthCheckNodePort = 34567 + }, + numErrs: 2, + }, + } + + for _, tc := range testCases { + svc := makeValidService() + tc.tweakSvc(&svc) + errs := ValidateServiceExternalTrafficFieldsCombination(&svc) + if len(errs) != tc.numErrs { + t.Errorf("Unexpected error list for case %q: %v", tc.name, errs.ToAggregate()) + } + } +} + +func TestValidateReplicationControllerStatus(t *testing.T) { + tests := []struct { + name string + + replicas int32 + fullyLabeledReplicas int32 + readyReplicas int32 + availableReplicas int32 + observedGeneration int64 + + expectedErr bool + }{ + { + name: "valid status", + replicas: 3, + fullyLabeledReplicas: 3, + readyReplicas: 2, + availableReplicas: 1, + observedGeneration: 2, + expectedErr: false, + }, + { + name: "invalid replicas", + replicas: -1, + fullyLabeledReplicas: 3, + readyReplicas: 2, + availableReplicas: 1, + observedGeneration: 2, + expectedErr: true, + }, + { + name: "invalid fullyLabeledReplicas", + replicas: 3, + fullyLabeledReplicas: -1, + readyReplicas: 2, + availableReplicas: 1, + observedGeneration: 2, + expectedErr: true, + }, + { + name: "invalid readyReplicas", + replicas: 3, + fullyLabeledReplicas: 3, + readyReplicas: -1, + availableReplicas: 1, + observedGeneration: 2, + expectedErr: true, + }, + { + name: "invalid availableReplicas", + replicas: 3, + fullyLabeledReplicas: 3, + readyReplicas: 3, + availableReplicas: -1, + observedGeneration: 2, + expectedErr: true, + }, + { + name: "invalid observedGeneration", + replicas: 3, + fullyLabeledReplicas: 3, + readyReplicas: 3, + availableReplicas: 3, + observedGeneration: -1, + expectedErr: true, + }, + { + name: "fullyLabeledReplicas greater than replicas", + replicas: 3, + fullyLabeledReplicas: 4, + readyReplicas: 3, + availableReplicas: 3, + observedGeneration: 1, + expectedErr: true, + }, + { + name: "readyReplicas greater than replicas", + replicas: 3, + fullyLabeledReplicas: 3, + readyReplicas: 4, + availableReplicas: 3, + observedGeneration: 1, + expectedErr: true, + }, + { + name: "availableReplicas greater than replicas", + replicas: 3, + fullyLabeledReplicas: 3, + readyReplicas: 3, + availableReplicas: 4, + observedGeneration: 1, + expectedErr: true, + }, + { + name: "availableReplicas greater than readyReplicas", + replicas: 3, + fullyLabeledReplicas: 3, + readyReplicas: 2, + availableReplicas: 3, + observedGeneration: 1, + expectedErr: true, + }, + } + + for _, test := range tests { + status := core.ReplicationControllerStatus{ + Replicas: test.replicas, + FullyLabeledReplicas: test.fullyLabeledReplicas, + ReadyReplicas: test.readyReplicas, + AvailableReplicas: test.availableReplicas, + ObservedGeneration: test.observedGeneration, + } + + if hasErr := len(ValidateReplicationControllerStatus(status, field.NewPath("status"))) > 0; hasErr != test.expectedErr { + t.Errorf("%s: expected error: %t, got error: %t", test.name, test.expectedErr, hasErr) + } + } +} + +func TestValidateReplicationControllerStatusUpdate(t *testing.T) { + validSelector := map[string]string{"a": "b"} + validPodTemplate := core.PodTemplate{ + Template: core.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: validSelector, + }, + Spec: core.PodSpec{ + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + Containers: []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + }, + }, + } + type rcUpdateTest struct { + old core.ReplicationController + update core.ReplicationController + } + successCases := []rcUpdateTest{ + { + old: core.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + Status: core.ReplicationControllerStatus{ + Replicas: 2, + }, + }, + update: core.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Replicas: 3, + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + Status: core.ReplicationControllerStatus{ + Replicas: 4, + }, + }, + }, + } + for _, successCase := range successCases { + successCase.old.ObjectMeta.ResourceVersion = "1" + successCase.update.ObjectMeta.ResourceVersion = "1" + if errs := ValidateReplicationControllerStatusUpdate(&successCase.update, &successCase.old); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + errorCases := map[string]rcUpdateTest{ + "negative replicas": { + old: core.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + Status: core.ReplicationControllerStatus{ + Replicas: 3, + }, + }, + update: core.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Replicas: 2, + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + Status: core.ReplicationControllerStatus{ + Replicas: -3, + }, + }, + }, + } + for testName, errorCase := range errorCases { + if errs := ValidateReplicationControllerStatusUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 { + t.Errorf("expected failure: %s", testName) + } + } + +} + +func TestValidateReplicationControllerUpdate(t *testing.T) { + validSelector := map[string]string{"a": "b"} + validPodTemplate := core.PodTemplate{ + Template: core.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: validSelector, + }, + Spec: core.PodSpec{ + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + Containers: []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + }, + }, + } + readWriteVolumePodTemplate := core.PodTemplate{ + Template: core.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: validSelector, + }, + Spec: core.PodSpec{ + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + Containers: []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + Volumes: []core.Volume{{Name: "gcepd", VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}}, + }, + }, + } + invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} + invalidPodTemplate := core.PodTemplate{ + Template: core.PodTemplateSpec{ + Spec: core.PodSpec{ + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + ObjectMeta: metav1.ObjectMeta{ + Labels: invalidSelector, + }, + }, + } + type rcUpdateTest struct { + old core.ReplicationController + update core.ReplicationController + } + successCases := []rcUpdateTest{ + { + old: core.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + }, + update: core.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Replicas: 3, + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + }, + }, + { + old: core.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + }, + update: core.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Replicas: 1, + Selector: validSelector, + Template: &readWriteVolumePodTemplate.Template, + }, + }, + }, + } + for _, successCase := range successCases { + successCase.old.ObjectMeta.ResourceVersion = "1" + successCase.update.ObjectMeta.ResourceVersion = "1" + if errs := ValidateReplicationControllerUpdate(&successCase.update, &successCase.old); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + errorCases := map[string]rcUpdateTest{ + "more than one read/write": { + old: core.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + }, + update: core.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Replicas: 2, + Selector: validSelector, + Template: &readWriteVolumePodTemplate.Template, + }, + }, + }, + "invalid selector": { + old: core.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + }, + update: core.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Replicas: 2, + Selector: invalidSelector, + Template: &validPodTemplate.Template, + }, + }, + }, + "invalid pod": { + old: core.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + }, + update: core.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Replicas: 2, + Selector: validSelector, + Template: &invalidPodTemplate.Template, + }, + }, + }, + "negative replicas": { + old: core.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + }, + update: core.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Replicas: -1, + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + }, + }, + } + for testName, errorCase := range errorCases { + if errs := ValidateReplicationControllerUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 { + t.Errorf("expected failure: %s", testName) + } + } +} + +func TestValidateReplicationController(t *testing.T) { + validSelector := map[string]string{"a": "b"} + validPodTemplate := core.PodTemplate{ + Template: core.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: validSelector, + }, + Spec: core.PodSpec{ + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + Containers: []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + }, + }, + } + readWriteVolumePodTemplate := core.PodTemplate{ + Template: core.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: validSelector, + }, + Spec: core.PodSpec{ + Volumes: []core.Volume{{Name: "gcepd", VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + Containers: []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + }, + }, + } + invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} + invalidPodTemplate := core.PodTemplate{ + Template: core.PodTemplateSpec{ + Spec: core.PodSpec{ + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + }, + ObjectMeta: metav1.ObjectMeta{ + Labels: invalidSelector, + }, + }, + } + successCases := []core.ReplicationController{ + { + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Replicas: 1, + Selector: validSelector, + Template: &readWriteVolumePodTemplate.Template, + }, + }, + } + for _, successCase := range successCases { + if errs := ValidateReplicationController(&successCase); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + + errorCases := map[string]core.ReplicationController{ + "zero-length ID": { + ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + }, + "missing-namespace": { + ObjectMeta: metav1.ObjectMeta{Name: "abc-123"}, + Spec: core.ReplicationControllerSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + }, + "empty selector": { + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Template: &validPodTemplate.Template, + }, + }, + "selector_doesnt_match": { + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Selector: map[string]string{"foo": "bar"}, + Template: &validPodTemplate.Template, + }, + }, + "invalid manifest": { + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Selector: validSelector, + }, + }, + "read-write persistent disk with > 1 pod": { + ObjectMeta: metav1.ObjectMeta{Name: "abc"}, + Spec: core.ReplicationControllerSpec{ + Replicas: 2, + Selector: validSelector, + Template: &readWriteVolumePodTemplate.Template, + }, + }, + "negative_replicas": { + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, + Spec: core.ReplicationControllerSpec{ + Replicas: -1, + Selector: validSelector, + }, + }, + "invalid_label": { + ObjectMeta: metav1.ObjectMeta{ + Name: "abc-123", + Namespace: metav1.NamespaceDefault, + Labels: map[string]string{ + "NoUppercaseOrSpecialCharsLike=Equals": "bar", + }, + }, + Spec: core.ReplicationControllerSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + }, + "invalid_label 2": { + ObjectMeta: metav1.ObjectMeta{ + Name: "abc-123", + Namespace: metav1.NamespaceDefault, + Labels: map[string]string{ + "NoUppercaseOrSpecialCharsLike=Equals": "bar", + }, + }, + Spec: core.ReplicationControllerSpec{ + Template: &invalidPodTemplate.Template, + }, + }, + "invalid_annotation": { + ObjectMeta: metav1.ObjectMeta{ + Name: "abc-123", + Namespace: metav1.NamespaceDefault, + Annotations: map[string]string{ + "NoUppercaseOrSpecialCharsLike=Equals": "bar", + }, + }, + Spec: core.ReplicationControllerSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + }, + "invalid restart policy 1": { + ObjectMeta: metav1.ObjectMeta{ + Name: "abc-123", + Namespace: metav1.NamespaceDefault, + }, + Spec: core.ReplicationControllerSpec{ + Selector: validSelector, + Template: &core.PodTemplateSpec{ + Spec: core.PodSpec{ + RestartPolicy: core.RestartPolicyOnFailure, + DNSPolicy: core.DNSClusterFirst, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + }, + ObjectMeta: metav1.ObjectMeta{ + Labels: validSelector, + }, + }, + }, + }, + "invalid restart policy 2": { + ObjectMeta: metav1.ObjectMeta{ + Name: "abc-123", + Namespace: metav1.NamespaceDefault, + }, + Spec: core.ReplicationControllerSpec{ + Selector: validSelector, + Template: &core.PodTemplateSpec{ + Spec: core.PodSpec{ + RestartPolicy: core.RestartPolicyNever, + DNSPolicy: core.DNSClusterFirst, + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + }, + ObjectMeta: metav1.ObjectMeta{ + Labels: validSelector, + }, + }, + }, + }, + } + for k, v := range errorCases { + errs := ValidateReplicationController(&v) + if len(errs) == 0 { + t.Errorf("expected failure for %s", k) + } + for i := range errs { + field := errs[i].Field + if !strings.HasPrefix(field, "spec.template.") && + field != "metadata.name" && + field != "metadata.namespace" && + field != "spec.selector" && + field != "spec.template" && + field != "GCEPersistentDisk.ReadOnly" && + field != "spec.replicas" && + field != "spec.template.labels" && + field != "metadata.annotations" && + field != "metadata.labels" && + field != "status.replicas" { + t.Errorf("%s: missing prefix for: %v", k, errs[i]) + } + } + } +} + +func TestValidateNode(t *testing.T) { + validSelector := map[string]string{"a": "b"} + invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} + successCases := []core.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + Labels: validSelector, + }, + Status: core.NodeStatus{ + Addresses: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "something"}, + }, + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName("my.org/gpu"): resource.MustParse("10"), + core.ResourceName("hugepages-2Mi"): resource.MustParse("10Gi"), + core.ResourceName("hugepages-1Gi"): resource.MustParse("0"), + }, + }, + Spec: core.NodeSpec{ + ExternalID: "external", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + }, + Status: core.NodeStatus{ + Addresses: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "something"}, + }, + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("0"), + }, + }, + Spec: core.NodeSpec{ + ExternalID: "external", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "dedicated-node1", + }, + Status: core.NodeStatus{ + Addresses: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "something"}, + }, + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("0"), + }, + }, + Spec: core.NodeSpec{ + ExternalID: "external", + // Add a valid taint to a node + Taints: []core.Taint{{Key: "GPU", Value: "true", Effect: "NoSchedule"}}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + Annotations: map[string]string{ + core.PreferAvoidPodsAnnotationKey: ` + { + "preferAvoidPods": [ + { + "podSignature": { + "podController": { + "apiVersion": "v1", + "kind": "ReplicationController", + "name": "foo", + "uid": "abcdef123456", + "controller": true + } + }, + "reason": "some reason", + "message": "some message" + } + ] + }`, + }, + }, + Status: core.NodeStatus{ + Addresses: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "something"}, + }, + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("0"), + }, + }, + Spec: core.NodeSpec{ + ExternalID: "external", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + }, + Status: core.NodeStatus{ + Addresses: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "something"}, + }, + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("0"), + }, + }, + Spec: core.NodeSpec{ + ExternalID: "external", + PodCIDR: "192.168.0.0/16", + }, + }, + } + for _, successCase := range successCases { + if errs := ValidateNode(&successCase); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + + errorCases := map[string]core.Node{ + "zero-length Name": { + ObjectMeta: metav1.ObjectMeta{ + Name: "", + Labels: validSelector, + }, + Status: core.NodeStatus{ + Addresses: []core.NodeAddress{}, + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + }, + }, + Spec: core.NodeSpec{ + ExternalID: "external", + }, + }, + "invalid-labels": { + ObjectMeta: metav1.ObjectMeta{ + Name: "abc-123", + Labels: invalidSelector, + }, + Status: core.NodeStatus{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + }, + }, + Spec: core.NodeSpec{ + ExternalID: "external", + }, + }, + "missing-external-id": { + ObjectMeta: metav1.ObjectMeta{ + Name: "abc-123", + Labels: validSelector, + }, + Status: core.NodeStatus{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + }, + }, + }, + "missing-taint-key": { + ObjectMeta: metav1.ObjectMeta{ + Name: "dedicated-node1", + }, + Spec: core.NodeSpec{ + ExternalID: "external", + // Add a taint with an empty key to a node + Taints: []core.Taint{{Key: "", Value: "special-user-1", Effect: "NoSchedule"}}, + }, + }, + "bad-taint-key": { + ObjectMeta: metav1.ObjectMeta{ + Name: "dedicated-node1", + }, + Spec: core.NodeSpec{ + ExternalID: "external", + // Add a taint with an invalid key to a node + Taints: []core.Taint{{Key: "NoUppercaseOrSpecialCharsLike=Equals", Value: "special-user-1", Effect: "NoSchedule"}}, + }, + }, + "bad-taint-value": { + ObjectMeta: metav1.ObjectMeta{ + Name: "dedicated-node2", + }, + Status: core.NodeStatus{ + Addresses: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "something"}, + }, + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("0"), + }, + }, + Spec: core.NodeSpec{ + ExternalID: "external", + // Add a taint with a bad value to a node + Taints: []core.Taint{{Key: "dedicated", Value: "some\\bad\\value", Effect: "NoSchedule"}}, + }, + }, + "missing-taint-effect": { + ObjectMeta: metav1.ObjectMeta{ + Name: "dedicated-node3", + }, + Status: core.NodeStatus{ + Addresses: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "something"}, + }, + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("0"), + }, + }, + Spec: core.NodeSpec{ + ExternalID: "external", + // Add a taint with an empty effect to a node + Taints: []core.Taint{{Key: "dedicated", Value: "special-user-3", Effect: ""}}, + }, + }, + "invalid-taint-effect": { + ObjectMeta: metav1.ObjectMeta{ + Name: "dedicated-node3", + }, + Status: core.NodeStatus{ + Addresses: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "something"}, + }, + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("0"), + }, + }, + Spec: core.NodeSpec{ + ExternalID: "external", + // Add a taint with NoExecute effect to a node + Taints: []core.Taint{{Key: "dedicated", Value: "special-user-3", Effect: "NoScheduleNoAdmit"}}, + }, + }, + "duplicated-taints-with-same-key-effect": { + ObjectMeta: metav1.ObjectMeta{ + Name: "dedicated-node1", + }, + Spec: core.NodeSpec{ + ExternalID: "external", + // Add two taints to the node with the same key and effect; should be rejected. + Taints: []core.Taint{ + {Key: "dedicated", Value: "special-user-1", Effect: "NoSchedule"}, + {Key: "dedicated", Value: "special-user-2", Effect: "NoSchedule"}, + }, + }, + }, + "missing-podSignature": { + ObjectMeta: metav1.ObjectMeta{ + Name: "abc-123", + Annotations: map[string]string{ + core.PreferAvoidPodsAnnotationKey: ` + { + "preferAvoidPods": [ + { + "reason": "some reason", + "message": "some message" + } + ] + }`, + }, + }, + Status: core.NodeStatus{ + Addresses: []core.NodeAddress{}, + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("0"), + }, + }, + Spec: core.NodeSpec{ + ExternalID: "external", + }, + }, + "invalid-podController": { + ObjectMeta: metav1.ObjectMeta{ + Name: "abc-123", + Annotations: map[string]string{ + core.PreferAvoidPodsAnnotationKey: ` + { + "preferAvoidPods": [ + { + "podSignature": { + "podController": { + "apiVersion": "v1", + "kind": "ReplicationController", + "name": "foo", + "uid": "abcdef123456", + "controller": false + } + }, + "reason": "some reason", + "message": "some message" + } + ] + }`, + }, + }, + Status: core.NodeStatus{ + Addresses: []core.NodeAddress{}, + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("0"), + }, + }, + Spec: core.NodeSpec{ + ExternalID: "external", + }, + }, + "multiple-pre-allocated-hugepages": { + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + Labels: validSelector, + }, + Status: core.NodeStatus{ + Addresses: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "something"}, + }, + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName("my.org/gpu"): resource.MustParse("10"), + core.ResourceName("hugepages-2Mi"): resource.MustParse("10Gi"), + core.ResourceName("hugepages-1Gi"): resource.MustParse("10Gi"), + }, + }, + Spec: core.NodeSpec{ + ExternalID: "external", + }, + }, + "invalid-pod-cidr": { + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + }, + Status: core.NodeStatus{ + Addresses: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "something"}, + }, + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("0"), + }, + }, + Spec: core.NodeSpec{ + ExternalID: "external", + PodCIDR: "192.168.0.0", + }, + }, + } + for k, v := range errorCases { + errs := ValidateNode(&v) + if len(errs) == 0 { + t.Errorf("expected failure for %s", k) + } + for i := range errs { + field := errs[i].Field + expectedFields := map[string]bool{ + "metadata.name": true, + "metadata.labels": true, + "metadata.annotations": true, + "metadata.namespace": true, + "spec.externalID": true, + "spec.taints[0].key": true, + "spec.taints[0].value": true, + "spec.taints[0].effect": true, + "metadata.annotations.scheduler.alpha.kubernetes.io/preferAvoidPods[0].PodSignature": true, + "metadata.annotations.scheduler.alpha.kubernetes.io/preferAvoidPods[0].PodSignature.PodController.Controller": true, + } + if val, ok := expectedFields[field]; ok { + if !val { + t.Errorf("%s: missing prefix for: %v", k, errs[i]) + } + } + } + } +} + +func TestValidateNodeUpdate(t *testing.T) { + tests := []struct { + oldNode core.Node + node core.Node + valid bool + }{ + {core.Node{}, core.Node{}, true}, + {core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo"}}, + core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bar"}, + }, false}, + {core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Labels: map[string]string{"foo": "bar"}, + }, + }, core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Labels: map[string]string{"foo": "baz"}, + }, + }, true}, + {core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + }, core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Labels: map[string]string{"foo": "baz"}, + }, + }, true}, + {core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Labels: map[string]string{"bar": "foo"}, + }, + }, core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Labels: map[string]string{"foo": "baz"}, + }, + }, true}, + {core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.NodeSpec{ + PodCIDR: "", + }, + }, core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.NodeSpec{ + PodCIDR: "192.168.0.0/16", + }, + }, true}, + {core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.NodeSpec{ + PodCIDR: "192.123.0.0/16", + }, + }, core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.NodeSpec{ + PodCIDR: "192.168.0.0/16", + }, + }, false}, + {core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: core.NodeStatus{ + Capacity: core.ResourceList{ + core.ResourceCPU: resource.MustParse("10000"), + core.ResourceMemory: resource.MustParse("100"), + }, + }, + }, core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: core.NodeStatus{ + Capacity: core.ResourceList{ + core.ResourceCPU: resource.MustParse("100"), + core.ResourceMemory: resource.MustParse("10000"), + }, + }, + }, true}, + {core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Labels: map[string]string{"bar": "foo"}, + }, + Status: core.NodeStatus{ + Capacity: core.ResourceList{ + core.ResourceCPU: resource.MustParse("10000"), + core.ResourceMemory: resource.MustParse("100"), + }, + }, + }, core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Labels: map[string]string{"bar": "fooobaz"}, + }, + Status: core.NodeStatus{ + Capacity: core.ResourceList{ + core.ResourceCPU: resource.MustParse("100"), + core.ResourceMemory: resource.MustParse("10000"), + }, + }, + }, true}, + {core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Labels: map[string]string{"bar": "foo"}, + }, + Status: core.NodeStatus{ + Addresses: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "1.2.3.4"}, + }, + }, + }, core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Labels: map[string]string{"bar": "fooobaz"}, + }, + }, true}, + {core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Labels: map[string]string{"foo": "baz"}, + }, + }, core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Labels: map[string]string{"Foo": "baz"}, + }, + }, true}, + {core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.NodeSpec{ + Unschedulable: false, + }, + }, core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.NodeSpec{ + Unschedulable: true, + }, + }, true}, + {core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.NodeSpec{ + Unschedulable: false, + }, + }, core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: core.NodeStatus{ + Addresses: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "1.1.1.1"}, + {Type: core.NodeExternalIP, Address: "1.1.1.1"}, + }, + }, + }, false}, + {core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: core.NodeSpec{ + Unschedulable: false, + }, + }, core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: core.NodeStatus{ + Addresses: []core.NodeAddress{ + {Type: core.NodeExternalIP, Address: "1.1.1.1"}, + {Type: core.NodeInternalIP, Address: "10.1.1.1"}, + }, + }, + }, true}, + {core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + }, core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Annotations: map[string]string{ + core.PreferAvoidPodsAnnotationKey: ` + { + "preferAvoidPods": [ + { + "podSignature": { + "podController": { + "apiVersion": "v1", + "kind": "ReplicationController", + "name": "foo", + "uid": "abcdef123456", + "controller": true + } + }, + "reason": "some reason", + "message": "some message" + } + ] + }`, + }, + }, + Spec: core.NodeSpec{ + Unschedulable: false, + }, + }, true}, + {core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + }, core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Annotations: map[string]string{ + core.PreferAvoidPodsAnnotationKey: ` + { + "preferAvoidPods": [ + { + "reason": "some reason", + "message": "some message" + } + ] + }`, + }, + }, + }, false}, + {core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + }, core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Annotations: map[string]string{ + core.PreferAvoidPodsAnnotationKey: ` + { + "preferAvoidPods": [ + { + "podSignature": { + "podController": { + "apiVersion": "v1", + "kind": "ReplicationController", + "name": "foo", + "uid": "abcdef123456", + "controller": false + } + }, + "reason": "some reason", + "message": "some message" + } + ] + }`, + }, + }, + }, false}, + {core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-extended-resources", + }, + }, core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-extended-resources", + }, + Status: core.NodeStatus{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName("example.com/a"): resource.MustParse("5"), + core.ResourceName("example.com/b"): resource.MustParse("10"), + }, + }, + }, true}, + {core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "invalid-fractional-extended-capacity", + }, + }, core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "invalid-fractional-extended-capacity", + }, + Status: core.NodeStatus{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName("example.com/a"): resource.MustParse("500m"), + }, + }, + }, false}, + {core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "invalid-fractional-extended-allocatable", + }, + }, core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "invalid-fractional-extended-allocatable", + }, + Status: core.NodeStatus{ + Capacity: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName("example.com/a"): resource.MustParse("5"), + }, + Allocatable: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + core.ResourceName("example.com/a"): resource.MustParse("4.5"), + }, + }, + }, false}, + {core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "update-provider-id-when-not-set", + }, + }, core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "update-provider-id-when-not-set", + }, + Spec: core.NodeSpec{ + ProviderID: "provider:///new", + }, + }, true}, + {core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "update-provider-id-when-set", + }, + Spec: core.NodeSpec{ + ProviderID: "provider:///old", + }, + }, core.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "update-provider-id-when-set", + }, + Spec: core.NodeSpec{ + ProviderID: "provider:///new", + }, + }, false}, + } + for i, test := range tests { + test.oldNode.ObjectMeta.ResourceVersion = "1" + test.node.ObjectMeta.ResourceVersion = "1" + errs := ValidateNodeUpdate(&test.node, &test.oldNode) + if test.valid && len(errs) > 0 { + t.Errorf("%d: Unexpected error: %v", i, errs) + t.Logf("%#v vs %#v", test.oldNode.ObjectMeta, test.node.ObjectMeta) + } + if !test.valid && len(errs) == 0 { + t.Errorf("%d: Unexpected non-error", i) + } + } +} + +func TestValidateServiceUpdate(t *testing.T) { + testCases := []struct { + name string + tweakSvc func(oldSvc, newSvc *core.Service) // given basic valid services, each test case can customize them + numErrs int + }{ + { + name: "no change", + tweakSvc: func(oldSvc, newSvc *core.Service) { + // do nothing + }, + numErrs: 0, + }, + { + name: "change name", + tweakSvc: func(oldSvc, newSvc *core.Service) { + newSvc.Name += "2" + }, + numErrs: 1, + }, + { + name: "change namespace", + tweakSvc: func(oldSvc, newSvc *core.Service) { + newSvc.Namespace += "2" + }, + numErrs: 1, + }, + { + name: "change label valid", + tweakSvc: func(oldSvc, newSvc *core.Service) { + newSvc.Labels["key"] = "other-value" + }, + numErrs: 0, + }, + { + name: "add label", + tweakSvc: func(oldSvc, newSvc *core.Service) { + newSvc.Labels["key2"] = "value2" + }, + numErrs: 0, + }, + { + name: "change cluster IP", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.ClusterIP = "1.2.3.4" + newSvc.Spec.ClusterIP = "8.6.7.5" + }, + numErrs: 1, + }, + { + name: "remove cluster IP", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.ClusterIP = "1.2.3.4" + newSvc.Spec.ClusterIP = "" + }, + numErrs: 1, + }, + { + name: "change affinity", + tweakSvc: func(oldSvc, newSvc *core.Service) { + newSvc.Spec.SessionAffinity = "ClientIP" + newSvc.Spec.SessionAffinityConfig = &core.SessionAffinityConfig{ + ClientIP: &core.ClientIPConfig{ + TimeoutSeconds: newInt32(90), + }, + } + }, + numErrs: 0, + }, + { + name: "remove affinity", + tweakSvc: func(oldSvc, newSvc *core.Service) { + newSvc.Spec.SessionAffinity = "" + }, + numErrs: 1, + }, + { + name: "change type", + tweakSvc: func(oldSvc, newSvc *core.Service) { + newSvc.Spec.Type = core.ServiceTypeLoadBalancer + }, + numErrs: 0, + }, + { + name: "remove type", + tweakSvc: func(oldSvc, newSvc *core.Service) { + newSvc.Spec.Type = "" + }, + numErrs: 1, + }, + { + name: "change type -> nodeport", + tweakSvc: func(oldSvc, newSvc *core.Service) { + newSvc.Spec.Type = core.ServiceTypeNodePort + }, + numErrs: 0, + }, + { + name: "add loadBalancerSourceRanges", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeLoadBalancer + newSvc.Spec.Type = core.ServiceTypeLoadBalancer + newSvc.Spec.LoadBalancerSourceRanges = []string{"10.0.0.0/8"} + }, + numErrs: 0, + }, + { + name: "update loadBalancerSourceRanges", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeLoadBalancer + oldSvc.Spec.LoadBalancerSourceRanges = []string{"10.0.0.0/8"} + newSvc.Spec.Type = core.ServiceTypeLoadBalancer + newSvc.Spec.LoadBalancerSourceRanges = []string{"10.100.0.0/16"} + }, + numErrs: 0, + }, + { + name: "LoadBalancer type cannot have None ClusterIP", + tweakSvc: func(oldSvc, newSvc *core.Service) { + newSvc.Spec.ClusterIP = "None" + newSvc.Spec.Type = core.ServiceTypeLoadBalancer + }, + numErrs: 1, + }, + { + name: "`None` ClusterIP cannot be changed", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.ClusterIP = "None" + newSvc.Spec.ClusterIP = "1.2.3.4" + }, + numErrs: 1, + }, + { + name: "`None` ClusterIP cannot be removed", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.ClusterIP = "None" + newSvc.Spec.ClusterIP = "" + }, + numErrs: 1, + }, + { + name: "Service with ClusterIP type cannot change its set ClusterIP", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeClusterIP + newSvc.Spec.Type = core.ServiceTypeClusterIP + + oldSvc.Spec.ClusterIP = "1.2.3.4" + newSvc.Spec.ClusterIP = "1.2.3.5" + }, + numErrs: 1, + }, + { + name: "Service with ClusterIP type can change its empty ClusterIP", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeClusterIP + newSvc.Spec.Type = core.ServiceTypeClusterIP + + oldSvc.Spec.ClusterIP = "" + newSvc.Spec.ClusterIP = "1.2.3.5" + }, + numErrs: 0, + }, + { + name: "Service with ClusterIP type cannot change its set ClusterIP when changing type to NodePort", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeClusterIP + newSvc.Spec.Type = core.ServiceTypeNodePort + + oldSvc.Spec.ClusterIP = "1.2.3.4" + newSvc.Spec.ClusterIP = "1.2.3.5" + }, + numErrs: 1, + }, + { + name: "Service with ClusterIP type can change its empty ClusterIP when changing type to NodePort", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeClusterIP + newSvc.Spec.Type = core.ServiceTypeNodePort + + oldSvc.Spec.ClusterIP = "" + newSvc.Spec.ClusterIP = "1.2.3.5" + }, + numErrs: 0, + }, + { + name: "Service with ClusterIP type cannot change its ClusterIP when changing type to LoadBalancer", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeClusterIP + newSvc.Spec.Type = core.ServiceTypeLoadBalancer + + oldSvc.Spec.ClusterIP = "1.2.3.4" + newSvc.Spec.ClusterIP = "1.2.3.5" + }, + numErrs: 1, + }, + { + name: "Service with ClusterIP type can change its empty ClusterIP when changing type to LoadBalancer", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeClusterIP + newSvc.Spec.Type = core.ServiceTypeLoadBalancer + + oldSvc.Spec.ClusterIP = "" + newSvc.Spec.ClusterIP = "1.2.3.5" + }, + numErrs: 0, + }, + { + name: "Service with NodePort type cannot change its set ClusterIP", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeNodePort + newSvc.Spec.Type = core.ServiceTypeNodePort + + oldSvc.Spec.ClusterIP = "1.2.3.4" + newSvc.Spec.ClusterIP = "1.2.3.5" + }, + numErrs: 1, + }, + { + name: "Service with NodePort type can change its empty ClusterIP", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeNodePort + newSvc.Spec.Type = core.ServiceTypeNodePort + + oldSvc.Spec.ClusterIP = "" + newSvc.Spec.ClusterIP = "1.2.3.5" + }, + numErrs: 0, + }, + { + name: "Service with NodePort type cannot change its set ClusterIP when changing type to ClusterIP", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeNodePort + newSvc.Spec.Type = core.ServiceTypeClusterIP + + oldSvc.Spec.ClusterIP = "1.2.3.4" + newSvc.Spec.ClusterIP = "1.2.3.5" + }, + numErrs: 1, + }, + { + name: "Service with NodePort type can change its empty ClusterIP when changing type to ClusterIP", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeNodePort + newSvc.Spec.Type = core.ServiceTypeClusterIP + + oldSvc.Spec.ClusterIP = "" + newSvc.Spec.ClusterIP = "1.2.3.5" + }, + numErrs: 0, + }, + { + name: "Service with NodePort type cannot change its set ClusterIP when changing type to LoadBalancer", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeNodePort + newSvc.Spec.Type = core.ServiceTypeLoadBalancer + + oldSvc.Spec.ClusterIP = "1.2.3.4" + newSvc.Spec.ClusterIP = "1.2.3.5" + }, + numErrs: 1, + }, + { + name: "Service with NodePort type can change its empty ClusterIP when changing type to LoadBalancer", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeNodePort + newSvc.Spec.Type = core.ServiceTypeLoadBalancer + + oldSvc.Spec.ClusterIP = "" + newSvc.Spec.ClusterIP = "1.2.3.5" + }, + numErrs: 0, + }, + { + name: "Service with LoadBalancer type cannot change its set ClusterIP", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeLoadBalancer + newSvc.Spec.Type = core.ServiceTypeLoadBalancer + + oldSvc.Spec.ClusterIP = "1.2.3.4" + newSvc.Spec.ClusterIP = "1.2.3.5" + }, + numErrs: 1, + }, + { + name: "Service with LoadBalancer type can change its empty ClusterIP", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeLoadBalancer + newSvc.Spec.Type = core.ServiceTypeLoadBalancer + + oldSvc.Spec.ClusterIP = "" + newSvc.Spec.ClusterIP = "1.2.3.5" + }, + numErrs: 0, + }, + { + name: "Service with LoadBalancer type cannot change its set ClusterIP when changing type to ClusterIP", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeLoadBalancer + newSvc.Spec.Type = core.ServiceTypeClusterIP + + oldSvc.Spec.ClusterIP = "1.2.3.4" + newSvc.Spec.ClusterIP = "1.2.3.5" + }, + numErrs: 1, + }, + { + name: "Service with LoadBalancer type can change its empty ClusterIP when changing type to ClusterIP", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeLoadBalancer + newSvc.Spec.Type = core.ServiceTypeClusterIP + + oldSvc.Spec.ClusterIP = "" + newSvc.Spec.ClusterIP = "1.2.3.5" + }, + numErrs: 0, + }, + { + name: "Service with LoadBalancer type cannot change its set ClusterIP when changing type to NodePort", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeLoadBalancer + newSvc.Spec.Type = core.ServiceTypeNodePort + + oldSvc.Spec.ClusterIP = "1.2.3.4" + newSvc.Spec.ClusterIP = "1.2.3.5" + }, + numErrs: 1, + }, + { + name: "Service with LoadBalancer type can change its empty ClusterIP when changing type to NodePort", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeLoadBalancer + newSvc.Spec.Type = core.ServiceTypeNodePort + + oldSvc.Spec.ClusterIP = "" + newSvc.Spec.ClusterIP = "1.2.3.5" + }, + numErrs: 0, + }, + { + name: "Service with ExternalName type can change its empty ClusterIP when changing type to ClusterIP", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeExternalName + newSvc.Spec.Type = core.ServiceTypeClusterIP + + oldSvc.Spec.ClusterIP = "" + newSvc.Spec.ClusterIP = "1.2.3.5" + }, + numErrs: 0, + }, + { + name: "Service with ExternalName type can change its set ClusterIP when changing type to ClusterIP", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeExternalName + newSvc.Spec.Type = core.ServiceTypeClusterIP + + oldSvc.Spec.ClusterIP = "1.2.3.4" + newSvc.Spec.ClusterIP = "1.2.3.5" + }, + numErrs: 0, + }, + { + name: "invalid node port with clusterIP None", + tweakSvc: func(oldSvc, newSvc *core.Service) { + oldSvc.Spec.Type = core.ServiceTypeNodePort + newSvc.Spec.Type = core.ServiceTypeNodePort + + oldSvc.Spec.Ports = append(oldSvc.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(1)}) + newSvc.Spec.Ports = append(newSvc.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(1)}) + + oldSvc.Spec.ClusterIP = "" + newSvc.Spec.ClusterIP = "None" + }, + numErrs: 1, + }, + } + + for _, tc := range testCases { + oldSvc := makeValidService() + newSvc := makeValidService() + tc.tweakSvc(&oldSvc, &newSvc) + errs := ValidateServiceUpdate(&newSvc, &oldSvc) + if len(errs) != tc.numErrs { + t.Errorf("Unexpected error list for case %q: %v", tc.name, errs.ToAggregate()) + } + } +} + +func TestValidateResourceNames(t *testing.T) { + table := []struct { + input string + success bool + expect string + }{ + {"memory", true, ""}, + {"cpu", true, ""}, + {"storage", true, ""}, + {"requests.cpu", true, ""}, + {"requests.memory", true, ""}, + {"requests.storage", true, ""}, + {"limits.cpu", true, ""}, + {"limits.memory", true, ""}, + {"network", false, ""}, + {"disk", false, ""}, + {"", false, ""}, + {".", false, ""}, + {"..", false, ""}, + {"my.favorite.app.co/12345", true, ""}, + {"my.favorite.app.co/_12345", false, ""}, + {"my.favorite.app.co/12345_", false, ""}, + {"kubernetes.io/..", false, ""}, + {"kubernetes.io/" + strings.Repeat("a", 63), true, ""}, + {"kubernetes.io/" + strings.Repeat("a", 64), false, ""}, + {"kubernetes.io//", false, ""}, + {"kubernetes.io", false, ""}, + {"kubernetes.io/will/not/work/", false, ""}, + } + for k, item := range table { + err := validateResourceName(item.input, field.NewPath("field")) + if len(err) != 0 && item.success { + t.Errorf("expected no failure for input %q", item.input) + } else if len(err) == 0 && !item.success { + t.Errorf("expected failure for input %q", item.input) + for i := range err { + detail := err[i].Detail + if detail != "" && !strings.Contains(detail, item.expect) { + t.Errorf("%d: expected error detail either empty or %s, got %s", k, item.expect, detail) + } + } + } + } +} + +func getResourceList(cpu, memory string) core.ResourceList { + res := core.ResourceList{} + if cpu != "" { + res[core.ResourceCPU] = resource.MustParse(cpu) + } + if memory != "" { + res[core.ResourceMemory] = resource.MustParse(memory) + } + return res +} + +func getStorageResourceList(storage string) core.ResourceList { + res := core.ResourceList{} + if storage != "" { + res[core.ResourceStorage] = resource.MustParse(storage) + } + return res +} + +func getLocalStorageResourceList(ephemeralStorage string) core.ResourceList { + res := core.ResourceList{} + if ephemeralStorage != "" { + res[core.ResourceEphemeralStorage] = resource.MustParse(ephemeralStorage) + } + return res +} + +func TestValidateLimitRangeForLocalStorage(t *testing.T) { + testCases := []struct { + name string + spec core.LimitRangeSpec + }{ + { + name: "all-fields-valid", + spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{ + { + Type: core.LimitTypePod, + Max: getLocalStorageResourceList("10000Mi"), + Min: getLocalStorageResourceList("100Mi"), + MaxLimitRequestRatio: getLocalStorageResourceList(""), + }, + { + Type: core.LimitTypeContainer, + Max: getLocalStorageResourceList("10000Mi"), + Min: getLocalStorageResourceList("100Mi"), + Default: getLocalStorageResourceList("500Mi"), + DefaultRequest: getLocalStorageResourceList("200Mi"), + MaxLimitRequestRatio: getLocalStorageResourceList(""), + }, + }, + }, + }, + } + + // Enable alpha feature LocalStorageCapacityIsolation + err := utilfeature.DefaultFeatureGate.Set("LocalStorageCapacityIsolation=true") + if err != nil { + t.Errorf("Failed to enable feature gate for LocalStorageCapacityIsolation: %v", err) + return + } + + for _, testCase := range testCases { + limitRange := &core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: testCase.name, Namespace: "foo"}, Spec: testCase.spec} + if errs := ValidateLimitRange(limitRange); len(errs) != 0 { + t.Errorf("Case %v, unexpected error: %v", testCase.name, errs) + } + } + + // Disable alpha feature LocalStorageCapacityIsolation + err = utilfeature.DefaultFeatureGate.Set("LocalStorageCapacityIsolation=false") + if err != nil { + t.Errorf("Failed to disable feature gate for LocalStorageCapacityIsolation: %v", err) + return + } + for _, testCase := range testCases { + limitRange := &core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: testCase.name, Namespace: "foo"}, Spec: testCase.spec} + if errs := ValidateLimitRange(limitRange); len(errs) == 0 { + t.Errorf("Case %v, expected feature gate unable error but actually no error", testCase.name) + } + } + +} + +func TestValidateLimitRange(t *testing.T) { + successCases := []struct { + name string + spec core.LimitRangeSpec + }{ + { + name: "all-fields-valid", + spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{ + { + Type: core.LimitTypePod, + Max: getResourceList("100m", "10000Mi"), + Min: getResourceList("5m", "100Mi"), + MaxLimitRequestRatio: getResourceList("10", ""), + }, + { + Type: core.LimitTypeContainer, + Max: getResourceList("100m", "10000Mi"), + Min: getResourceList("5m", "100Mi"), + Default: getResourceList("50m", "500Mi"), + DefaultRequest: getResourceList("10m", "200Mi"), + MaxLimitRequestRatio: getResourceList("10", ""), + }, + { + Type: core.LimitTypePersistentVolumeClaim, + Max: getStorageResourceList("10Gi"), + Min: getStorageResourceList("5Gi"), + }, + }, + }, + }, + { + name: "pvc-min-only", + spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{ + { + Type: core.LimitTypePersistentVolumeClaim, + Min: getStorageResourceList("5Gi"), + }, + }, + }, + }, + { + name: "pvc-max-only", + spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{ + { + Type: core.LimitTypePersistentVolumeClaim, + Max: getStorageResourceList("10Gi"), + }, + }, + }, + }, + { + name: "all-fields-valid-big-numbers", + spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{ + { + Type: core.LimitTypeContainer, + Max: getResourceList("100m", "10000T"), + Min: getResourceList("5m", "100Mi"), + Default: getResourceList("50m", "500Mi"), + DefaultRequest: getResourceList("10m", "200Mi"), + MaxLimitRequestRatio: getResourceList("10", ""), + }, + }, + }, + }, + { + name: "thirdparty-fields-all-valid-standard-container-resources", + spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{ + { + Type: "thirdparty.com/foo", + Max: getResourceList("100m", "10000T"), + Min: getResourceList("5m", "100Mi"), + Default: getResourceList("50m", "500Mi"), + DefaultRequest: getResourceList("10m", "200Mi"), + MaxLimitRequestRatio: getResourceList("10", ""), + }, + }, + }, + }, + { + name: "thirdparty-fields-all-valid-storage-resources", + spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{ + { + Type: "thirdparty.com/foo", + Max: getStorageResourceList("10000T"), + Min: getStorageResourceList("100Mi"), + Default: getStorageResourceList("500Mi"), + DefaultRequest: getStorageResourceList("200Mi"), + MaxLimitRequestRatio: getStorageResourceList(""), + }, + }, + }, + }, + } + + for _, successCase := range successCases { + limitRange := &core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: successCase.name, Namespace: "foo"}, Spec: successCase.spec} + if errs := ValidateLimitRange(limitRange); len(errs) != 0 { + t.Errorf("Case %v, unexpected error: %v", successCase.name, errs) + } + } + + errorCases := map[string]struct { + R core.LimitRange + D string + }{ + "zero-length-name": { + core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: "foo"}, Spec: core.LimitRangeSpec{}}, + "name or generateName is required", + }, + "zero-length-namespace": { + core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: ""}, Spec: core.LimitRangeSpec{}}, + "", + }, + "invalid-name": { + core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "^Invalid", Namespace: "foo"}, Spec: core.LimitRangeSpec{}}, + dnsSubdomainLabelErrMsg, + }, + "invalid-namespace": { + core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "^Invalid"}, Spec: core.LimitRangeSpec{}}, + dnsLabelErrMsg, + }, + "duplicate-limit-type": { + core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{ + { + Type: core.LimitTypePod, + Max: getResourceList("100m", "10000m"), + Min: getResourceList("0m", "100m"), + }, + { + Type: core.LimitTypePod, + Min: getResourceList("0m", "100m"), + }, + }, + }}, + "", + }, + "default-limit-type-pod": { + core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{ + { + Type: core.LimitTypePod, + Max: getResourceList("100m", "10000m"), + Min: getResourceList("0m", "100m"), + Default: getResourceList("10m", "100m"), + }, + }, + }}, + "may not be specified when `type` is 'Pod'", + }, + "default-request-limit-type-pod": { + core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{ + { + Type: core.LimitTypePod, + Max: getResourceList("100m", "10000m"), + Min: getResourceList("0m", "100m"), + DefaultRequest: getResourceList("10m", "100m"), + }, + }, + }}, + "may not be specified when `type` is 'Pod'", + }, + "min value 100m is greater than max value 10m": { + core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{ + { + Type: core.LimitTypePod, + Max: getResourceList("10m", ""), + Min: getResourceList("100m", ""), + }, + }, + }}, + "min value 100m is greater than max value 10m", + }, + "invalid spec default outside range": { + core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{ + { + Type: core.LimitTypeContainer, + Max: getResourceList("1", ""), + Min: getResourceList("100m", ""), + Default: getResourceList("2000m", ""), + }, + }, + }}, + "default value 2 is greater than max value 1", + }, + "invalid spec default request outside range": { + core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{ + { + Type: core.LimitTypeContainer, + Max: getResourceList("1", ""), + Min: getResourceList("100m", ""), + DefaultRequest: getResourceList("2000m", ""), + }, + }, + }}, + "default request value 2 is greater than max value 1", + }, + "invalid spec default request more than default": { + core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{ + { + Type: core.LimitTypeContainer, + Max: getResourceList("2", ""), + Min: getResourceList("100m", ""), + Default: getResourceList("500m", ""), + DefaultRequest: getResourceList("800m", ""), + }, + }, + }}, + "default request value 800m is greater than default limit value 500m", + }, + "invalid spec maxLimitRequestRatio less than 1": { + core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{ + { + Type: core.LimitTypePod, + MaxLimitRequestRatio: getResourceList("800m", ""), + }, + }, + }}, + "ratio 800m is less than 1", + }, + "invalid spec maxLimitRequestRatio greater than max/min": { + core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{ + { + Type: core.LimitTypeContainer, + Max: getResourceList("", "2Gi"), + Min: getResourceList("", "512Mi"), + MaxLimitRequestRatio: getResourceList("", "10"), + }, + }, + }}, + "ratio 10 is greater than max/min = 4.000000", + }, + "invalid non standard limit type": { + core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{ + { + Type: "foo", + Max: getStorageResourceList("10000T"), + Min: getStorageResourceList("100Mi"), + Default: getStorageResourceList("500Mi"), + DefaultRequest: getStorageResourceList("200Mi"), + MaxLimitRequestRatio: getStorageResourceList(""), + }, + }, + }}, + "must be a standard limit type or fully qualified", + }, + "min and max values missing, one required": { + core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{ + { + Type: core.LimitTypePersistentVolumeClaim, + }, + }, + }}, + "either minimum or maximum storage value is required, but neither was provided", + }, + "invalid min greater than max": { + core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ + Limits: []core.LimitRangeItem{ + { + Type: core.LimitTypePersistentVolumeClaim, + Min: getStorageResourceList("10Gi"), + Max: getStorageResourceList("1Gi"), + }, + }, + }}, + "min value 10Gi is greater than max value 1Gi", + }, + } + + for k, v := range errorCases { + errs := ValidateLimitRange(&v.R) + if len(errs) == 0 { + t.Errorf("expected failure for %s", k) + } + for i := range errs { + detail := errs[i].Detail + if !strings.Contains(detail, v.D) { + t.Errorf("[%s]: expected error detail either empty or %q, got %q", k, v.D, detail) + } + } + } + +} + +func TestValidatePersistentVolumeClaimStatusUpdate(t *testing.T) { + validClaim := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadWriteOnce, + core.ReadOnlyMany, + }, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + }, + }) + validConditionUpdate := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadWriteOnce, + core.ReadOnlyMany, + }, + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), + }, + }, + }, core.PersistentVolumeClaimStatus{ + Phase: core.ClaimPending, + Conditions: []core.PersistentVolumeClaimCondition{ + {Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue}, + }, + }) + scenarios := map[string]struct { + isExpectedFailure bool + oldClaim *core.PersistentVolumeClaim + newClaim *core.PersistentVolumeClaim + enableResize bool + }{ + "condition-update-with-disabled-feature-gate": { + isExpectedFailure: true, + oldClaim: validClaim, + newClaim: validConditionUpdate, + enableResize: false, + }, + "condition-update-with-enabled-feature-gate": { + isExpectedFailure: false, + oldClaim: validClaim, + newClaim: validConditionUpdate, + enableResize: true, + }, + } + for name, scenario := range scenarios { + // ensure we have a resource version specified for updates + togglePVExpandFeature(scenario.enableResize, t) + scenario.oldClaim.ResourceVersion = "1" + scenario.newClaim.ResourceVersion = "1" + errs := ValidatePersistentVolumeClaimStatusUpdate(scenario.newClaim, scenario.oldClaim) + if len(errs) == 0 && scenario.isExpectedFailure { + t.Errorf("Unexpected success for scenario: %s", name) + } + if len(errs) > 0 && !scenario.isExpectedFailure { + t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) + } + } +} + +func TestValidateResourceQuota(t *testing.T) { + spec := core.ResourceQuotaSpec{ + Hard: core.ResourceList{ + core.ResourceCPU: resource.MustParse("100"), + core.ResourceMemory: resource.MustParse("10000"), + core.ResourceRequestsCPU: resource.MustParse("100"), + core.ResourceRequestsMemory: resource.MustParse("10000"), + core.ResourceLimitsCPU: resource.MustParse("100"), + core.ResourceLimitsMemory: resource.MustParse("10000"), + core.ResourcePods: resource.MustParse("10"), + core.ResourceServices: resource.MustParse("0"), + core.ResourceReplicationControllers: resource.MustParse("10"), + core.ResourceQuotas: resource.MustParse("10"), + core.ResourceConfigMaps: resource.MustParse("10"), + core.ResourceSecrets: resource.MustParse("10"), + }, + } + + terminatingSpec := core.ResourceQuotaSpec{ + Hard: core.ResourceList{ + core.ResourceCPU: resource.MustParse("100"), + core.ResourceLimitsCPU: resource.MustParse("200"), + }, + Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeTerminating}, + } + + nonTerminatingSpec := core.ResourceQuotaSpec{ + Hard: core.ResourceList{ + core.ResourceCPU: resource.MustParse("100"), + }, + Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeNotTerminating}, + } + + bestEffortSpec := core.ResourceQuotaSpec{ + Hard: core.ResourceList{ + core.ResourcePods: resource.MustParse("100"), + }, + Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeBestEffort}, + } + + nonBestEffortSpec := core.ResourceQuotaSpec{ + Hard: core.ResourceList{ + core.ResourceCPU: resource.MustParse("100"), + }, + Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeNotBestEffort}, + } + + // storage is not yet supported as a quota tracked resource + invalidQuotaResourceSpec := core.ResourceQuotaSpec{ + Hard: core.ResourceList{ + core.ResourceStorage: resource.MustParse("10"), + }, + } + + negativeSpec := core.ResourceQuotaSpec{ + Hard: core.ResourceList{ + core.ResourceCPU: resource.MustParse("-100"), + core.ResourceMemory: resource.MustParse("-10000"), + core.ResourcePods: resource.MustParse("-10"), + core.ResourceServices: resource.MustParse("-10"), + core.ResourceReplicationControllers: resource.MustParse("-10"), + core.ResourceQuotas: resource.MustParse("-10"), + core.ResourceConfigMaps: resource.MustParse("-10"), + core.ResourceSecrets: resource.MustParse("-10"), + }, + } + + fractionalComputeSpec := core.ResourceQuotaSpec{ + Hard: core.ResourceList{ + core.ResourceCPU: resource.MustParse("100m"), + }, + } + + fractionalPodSpec := core.ResourceQuotaSpec{ + Hard: core.ResourceList{ + core.ResourcePods: resource.MustParse(".1"), + core.ResourceServices: resource.MustParse(".5"), + core.ResourceReplicationControllers: resource.MustParse("1.25"), + core.ResourceQuotas: resource.MustParse("2.5"), + }, + } + + invalidTerminatingScopePairsSpec := core.ResourceQuotaSpec{ + Hard: core.ResourceList{ + core.ResourceCPU: resource.MustParse("100"), + }, + Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeTerminating, core.ResourceQuotaScopeNotTerminating}, + } + + invalidBestEffortScopePairsSpec := core.ResourceQuotaSpec{ + Hard: core.ResourceList{ + core.ResourcePods: resource.MustParse("100"), + }, + Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeBestEffort, core.ResourceQuotaScopeNotBestEffort}, + } + + invalidScopeNameSpec := core.ResourceQuotaSpec{ + Hard: core.ResourceList{ + core.ResourceCPU: resource.MustParse("100"), + }, + Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScope("foo")}, + } + + successCases := []core.ResourceQuota{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + Namespace: "foo", + }, + Spec: spec, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + Namespace: "foo", + }, + Spec: fractionalComputeSpec, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + Namespace: "foo", + }, + Spec: terminatingSpec, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + Namespace: "foo", + }, + Spec: nonTerminatingSpec, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + Namespace: "foo", + }, + Spec: bestEffortSpec, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + Namespace: "foo", + }, + Spec: nonBestEffortSpec, + }, + } + + for _, successCase := range successCases { + if errs := ValidateResourceQuota(&successCase); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + + errorCases := map[string]struct { + R core.ResourceQuota + D string + }{ + "zero-length Name": { + core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: "foo"}, Spec: spec}, + "name or generateName is required", + }, + "zero-length Namespace": { + core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: ""}, Spec: spec}, + "", + }, + "invalid Name": { + core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "^Invalid", Namespace: "foo"}, Spec: spec}, + dnsSubdomainLabelErrMsg, + }, + "invalid Namespace": { + core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "^Invalid"}, Spec: spec}, + dnsLabelErrMsg, + }, + "negative-limits": { + core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: negativeSpec}, + isNegativeErrorMsg, + }, + "fractional-api-resource": { + core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: fractionalPodSpec}, + isNotIntegerErrorMsg, + }, + "invalid-quota-resource": { + core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidQuotaResourceSpec}, + isInvalidQuotaResource, + }, + "invalid-quota-terminating-pair": { + core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidTerminatingScopePairsSpec}, + "conflicting scopes", + }, + "invalid-quota-besteffort-pair": { + core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidBestEffortScopePairsSpec}, + "conflicting scopes", + }, + "invalid-quota-scope-name": { + core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidScopeNameSpec}, + "unsupported scope", + }, + } + for k, v := range errorCases { + errs := ValidateResourceQuota(&v.R) + if len(errs) == 0 { + t.Errorf("expected failure for %s", k) + } + for i := range errs { + if !strings.Contains(errs[i].Detail, v.D) { + t.Errorf("[%s]: expected error detail either empty or %s, got %s", k, v.D, errs[i].Detail) + } + } + } +} + +func TestValidateNamespace(t *testing.T) { + validLabels := map[string]string{"a": "b"} + invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} + successCases := []core.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{Name: "abc", Labels: validLabels}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "abc-123"}, + Spec: core.NamespaceSpec{ + Finalizers: []core.FinalizerName{"example.com/something", "example.com/other"}, + }, + }, + } + for _, successCase := range successCases { + if errs := ValidateNamespace(&successCase); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + errorCases := map[string]struct { + R core.Namespace + D string + }{ + "zero-length name": { + core.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ""}}, + "", + }, + "defined-namespace": { + core.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: "makesnosense"}}, + "", + }, + "invalid-labels": { + core.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "abc", Labels: invalidLabels}}, + "", + }, + } + for k, v := range errorCases { + errs := ValidateNamespace(&v.R) + if len(errs) == 0 { + t.Errorf("expected failure for %s", k) + } + } +} + +func TestValidateNamespaceFinalizeUpdate(t *testing.T) { + tests := []struct { + oldNamespace core.Namespace + namespace core.Namespace + valid bool + }{ + {core.Namespace{}, core.Namespace{}, true}, + {core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo"}}, + core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo"}, + Spec: core.NamespaceSpec{ + Finalizers: []core.FinalizerName{"Foo"}, + }, + }, false}, + {core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo"}, + Spec: core.NamespaceSpec{ + Finalizers: []core.FinalizerName{"foo.com/bar"}, + }, + }, + core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo"}, + Spec: core.NamespaceSpec{ + Finalizers: []core.FinalizerName{"foo.com/bar", "what.com/bar"}, + }, + }, true}, + {core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fooemptyfinalizer"}, + Spec: core.NamespaceSpec{ + Finalizers: []core.FinalizerName{"foo.com/bar"}, + }, + }, + core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fooemptyfinalizer"}, + Spec: core.NamespaceSpec{ + Finalizers: []core.FinalizerName{"", "foo.com/bar", "what.com/bar"}, + }, + }, false}, + } + for i, test := range tests { + test.namespace.ObjectMeta.ResourceVersion = "1" + test.oldNamespace.ObjectMeta.ResourceVersion = "1" + errs := ValidateNamespaceFinalizeUpdate(&test.namespace, &test.oldNamespace) + if test.valid && len(errs) > 0 { + t.Errorf("%d: Unexpected error: %v", i, errs) + t.Logf("%#v vs %#v", test.oldNamespace, test.namespace) + } + if !test.valid && len(errs) == 0 { + t.Errorf("%d: Unexpected non-error", i) + } + } +} + +func TestValidateNamespaceStatusUpdate(t *testing.T) { + now := metav1.Now() + + tests := []struct { + oldNamespace core.Namespace + namespace core.Namespace + valid bool + }{ + {core.Namespace{}, core.Namespace{ + Status: core.NamespaceStatus{ + Phase: core.NamespaceActive, + }, + }, true}, + // Cannot set deletionTimestamp via status update + {core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo"}}, + core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + DeletionTimestamp: &now}, + Status: core.NamespaceStatus{ + Phase: core.NamespaceTerminating, + }, + }, false}, + // Can update phase via status update + {core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + DeletionTimestamp: &now}}, + core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + DeletionTimestamp: &now}, + Status: core.NamespaceStatus{ + Phase: core.NamespaceTerminating, + }, + }, true}, + {core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo"}}, + core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo"}, + Status: core.NamespaceStatus{ + Phase: core.NamespaceTerminating, + }, + }, false}, + {core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo"}}, + core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bar"}, + Status: core.NamespaceStatus{ + Phase: core.NamespaceTerminating, + }, + }, false}, + } + for i, test := range tests { + test.namespace.ObjectMeta.ResourceVersion = "1" + test.oldNamespace.ObjectMeta.ResourceVersion = "1" + errs := ValidateNamespaceStatusUpdate(&test.namespace, &test.oldNamespace) + if test.valid && len(errs) > 0 { + t.Errorf("%d: Unexpected error: %v", i, errs) + t.Logf("%#v vs %#v", test.oldNamespace.ObjectMeta, test.namespace.ObjectMeta) + } + if !test.valid && len(errs) == 0 { + t.Errorf("%d: Unexpected non-error", i) + } + } +} + +func TestValidateNamespaceUpdate(t *testing.T) { + tests := []struct { + oldNamespace core.Namespace + namespace core.Namespace + valid bool + }{ + {core.Namespace{}, core.Namespace{}, true}, + {core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo1"}}, + core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bar1"}, + }, false}, + {core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo2", + Labels: map[string]string{"foo": "bar"}, + }, + }, core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo2", + Labels: map[string]string{"foo": "baz"}, + }, + }, true}, + {core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo3", + }, + }, core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo3", + Labels: map[string]string{"foo": "baz"}, + }, + }, true}, + {core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo4", + Labels: map[string]string{"bar": "foo"}, + }, + }, core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo4", + Labels: map[string]string{"foo": "baz"}, + }, + }, true}, + {core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo5", + Labels: map[string]string{"foo": "baz"}, + }, + }, core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo5", + Labels: map[string]string{"Foo": "baz"}, + }, + }, true}, + {core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo6", + Labels: map[string]string{"foo": "baz"}, + }, + }, core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo6", + Labels: map[string]string{"Foo": "baz"}, + }, + Spec: core.NamespaceSpec{ + Finalizers: []core.FinalizerName{"kubernetes"}, + }, + Status: core.NamespaceStatus{ + Phase: core.NamespaceTerminating, + }, + }, true}, + } + for i, test := range tests { + test.namespace.ObjectMeta.ResourceVersion = "1" + test.oldNamespace.ObjectMeta.ResourceVersion = "1" + errs := ValidateNamespaceUpdate(&test.namespace, &test.oldNamespace) + if test.valid && len(errs) > 0 { + t.Errorf("%d: Unexpected error: %v", i, errs) + t.Logf("%#v vs %#v", test.oldNamespace.ObjectMeta, test.namespace.ObjectMeta) + } + if !test.valid && len(errs) == 0 { + t.Errorf("%d: Unexpected non-error", i) + } + } +} + +func TestValidateSecret(t *testing.T) { + // Opaque secret validation + validSecret := func() core.Secret { + return core.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, + Data: map[string][]byte{ + "data-1": []byte("bar"), + }, + } + } + + var ( + emptyName = validSecret() + invalidName = validSecret() + emptyNs = validSecret() + invalidNs = validSecret() + overMaxSize = validSecret() + invalidKey = validSecret() + leadingDotKey = validSecret() + dotKey = validSecret() + doubleDotKey = validSecret() + ) + + emptyName.Name = "" + invalidName.Name = "NoUppercaseOrSpecialCharsLike=Equals" + emptyNs.Namespace = "" + invalidNs.Namespace = "NoUppercaseOrSpecialCharsLike=Equals" + overMaxSize.Data = map[string][]byte{ + "over": make([]byte, core.MaxSecretSize+1), + } + invalidKey.Data["a*b"] = []byte("whoops") + leadingDotKey.Data[".key"] = []byte("bar") + dotKey.Data["."] = []byte("bar") + doubleDotKey.Data[".."] = []byte("bar") + + // kubernetes.io/service-account-token secret validation + validServiceAccountTokenSecret := func() core.Secret { + return core.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "bar", + Annotations: map[string]string{ + core.ServiceAccountNameKey: "foo", + }, + }, + Type: core.SecretTypeServiceAccountToken, + Data: map[string][]byte{ + "data-1": []byte("bar"), + }, + } + } + + var ( + emptyTokenAnnotation = validServiceAccountTokenSecret() + missingTokenAnnotation = validServiceAccountTokenSecret() + missingTokenAnnotations = validServiceAccountTokenSecret() + ) + emptyTokenAnnotation.Annotations[core.ServiceAccountNameKey] = "" + delete(missingTokenAnnotation.Annotations, core.ServiceAccountNameKey) + missingTokenAnnotations.Annotations = nil + + tests := map[string]struct { + secret core.Secret + valid bool + }{ + "valid": {validSecret(), true}, + "empty name": {emptyName, false}, + "invalid name": {invalidName, false}, + "empty namespace": {emptyNs, false}, + "invalid namespace": {invalidNs, false}, + "over max size": {overMaxSize, false}, + "invalid key": {invalidKey, false}, + "valid service-account-token secret": {validServiceAccountTokenSecret(), true}, + "empty service-account-token annotation": {emptyTokenAnnotation, false}, + "missing service-account-token annotation": {missingTokenAnnotation, false}, + "missing service-account-token annotations": {missingTokenAnnotations, false}, + "leading dot key": {leadingDotKey, true}, + "dot key": {dotKey, false}, + "double dot key": {doubleDotKey, false}, + } + + for name, tc := range tests { + errs := ValidateSecret(&tc.secret) + if tc.valid && len(errs) > 0 { + t.Errorf("%v: Unexpected error: %v", name, errs) + } + if !tc.valid && len(errs) == 0 { + t.Errorf("%v: Unexpected non-error", name) + } + } +} + +func TestValidateDockerConfigSecret(t *testing.T) { + validDockerSecret := func() core.Secret { + return core.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, + Type: core.SecretTypeDockercfg, + Data: map[string][]byte{ + core.DockerConfigKey: []byte(`{"https://index.docker.io/v1/": {"auth": "Y2x1ZWRyb29sZXIwMDAxOnBhc3N3b3Jk","email": "fake@example.com"}}`), + }, + } + } + validDockerSecret2 := func() core.Secret { + return core.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, + Type: core.SecretTypeDockerConfigJson, + Data: map[string][]byte{ + core.DockerConfigJsonKey: []byte(`{"auths":{"https://index.docker.io/v1/": {"auth": "Y2x1ZWRyb29sZXIwMDAxOnBhc3N3b3Jk","email": "fake@example.com"}}}`), + }, + } + } + + var ( + missingDockerConfigKey = validDockerSecret() + emptyDockerConfigKey = validDockerSecret() + invalidDockerConfigKey = validDockerSecret() + missingDockerConfigKey2 = validDockerSecret2() + emptyDockerConfigKey2 = validDockerSecret2() + invalidDockerConfigKey2 = validDockerSecret2() + ) + + delete(missingDockerConfigKey.Data, core.DockerConfigKey) + emptyDockerConfigKey.Data[core.DockerConfigKey] = []byte("") + invalidDockerConfigKey.Data[core.DockerConfigKey] = []byte("bad") + delete(missingDockerConfigKey2.Data, core.DockerConfigJsonKey) + emptyDockerConfigKey2.Data[core.DockerConfigJsonKey] = []byte("") + invalidDockerConfigKey2.Data[core.DockerConfigJsonKey] = []byte("bad") + + tests := map[string]struct { + secret core.Secret + valid bool + }{ + "valid dockercfg": {validDockerSecret(), true}, + "missing dockercfg": {missingDockerConfigKey, false}, + "empty dockercfg": {emptyDockerConfigKey, false}, + "invalid dockercfg": {invalidDockerConfigKey, false}, + "valid config.json": {validDockerSecret2(), true}, + "missing config.json": {missingDockerConfigKey2, false}, + "empty config.json": {emptyDockerConfigKey2, false}, + "invalid config.json": {invalidDockerConfigKey2, false}, + } + + for name, tc := range tests { + errs := ValidateSecret(&tc.secret) + if tc.valid && len(errs) > 0 { + t.Errorf("%v: Unexpected error: %v", name, errs) + } + if !tc.valid && len(errs) == 0 { + t.Errorf("%v: Unexpected non-error", name) + } + } +} + +func TestValidateBasicAuthSecret(t *testing.T) { + validBasicAuthSecret := func() core.Secret { + return core.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, + Type: core.SecretTypeBasicAuth, + Data: map[string][]byte{ + core.BasicAuthUsernameKey: []byte("username"), + core.BasicAuthPasswordKey: []byte("password"), + }, + } + } + + var ( + missingBasicAuthUsernamePasswordKeys = validBasicAuthSecret() + ) + + delete(missingBasicAuthUsernamePasswordKeys.Data, core.BasicAuthUsernameKey) + delete(missingBasicAuthUsernamePasswordKeys.Data, core.BasicAuthPasswordKey) + + tests := map[string]struct { + secret core.Secret + valid bool + }{ + "valid": {validBasicAuthSecret(), true}, + "missing username and password": {missingBasicAuthUsernamePasswordKeys, false}, + } + + for name, tc := range tests { + errs := ValidateSecret(&tc.secret) + if tc.valid && len(errs) > 0 { + t.Errorf("%v: Unexpected error: %v", name, errs) + } + if !tc.valid && len(errs) == 0 { + t.Errorf("%v: Unexpected non-error", name) + } + } +} + +func TestValidateSSHAuthSecret(t *testing.T) { + validSSHAuthSecret := func() core.Secret { + return core.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, + Type: core.SecretTypeSSHAuth, + Data: map[string][]byte{ + core.SSHAuthPrivateKey: []byte("foo-bar-baz"), + }, + } + } + + missingSSHAuthPrivateKey := validSSHAuthSecret() + + delete(missingSSHAuthPrivateKey.Data, core.SSHAuthPrivateKey) + + tests := map[string]struct { + secret core.Secret + valid bool + }{ + "valid": {validSSHAuthSecret(), true}, + "missing private key": {missingSSHAuthPrivateKey, false}, + } + + for name, tc := range tests { + errs := ValidateSecret(&tc.secret) + if tc.valid && len(errs) > 0 { + t.Errorf("%v: Unexpected error: %v", name, errs) + } + if !tc.valid && len(errs) == 0 { + t.Errorf("%v: Unexpected non-error", name) + } + } +} + +func TestValidateEndpoints(t *testing.T) { + successCases := map[string]core.Endpoints{ + "simple endpoint": { + ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, + Subsets: []core.EndpointSubset{ + { + Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}, {IP: "10.10.2.2"}}, + Ports: []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP"}, {Name: "b", Port: 309, Protocol: "TCP"}}, + }, + { + Addresses: []core.EndpointAddress{{IP: "10.10.3.3"}}, + Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}, {Name: "b", Port: 76, Protocol: "TCP"}}, + }, + }, + }, + "empty subsets": { + ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, + }, + "no name required for singleton port": { + ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, + Subsets: []core.EndpointSubset{ + { + Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, + Ports: []core.EndpointPort{{Port: 8675, Protocol: "TCP"}}, + }, + }, + }, + "empty ports": { + ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, + Subsets: []core.EndpointSubset{ + { + Addresses: []core.EndpointAddress{{IP: "10.10.3.3"}}, + }, + }, + }, + } + + for k, v := range successCases { + if errs := ValidateEndpoints(&v); len(errs) != 0 { + t.Errorf("Expected success for %s, got %v", k, errs) + } + } + + errorCases := map[string]struct { + endpoints core.Endpoints + errorType field.ErrorType + errorDetail string + }{ + "missing namespace": { + endpoints: core.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "mysvc"}}, + errorType: "FieldValueRequired", + }, + "missing name": { + endpoints: core.Endpoints{ObjectMeta: metav1.ObjectMeta{Namespace: "namespace"}}, + errorType: "FieldValueRequired", + }, + "invalid namespace": { + endpoints: core.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "no@#invalid.;chars\"allowed"}}, + errorType: "FieldValueInvalid", + errorDetail: dnsLabelErrMsg, + }, + "invalid name": { + endpoints: core.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "-_Invliad^&Characters", Namespace: "namespace"}}, + errorType: "FieldValueInvalid", + errorDetail: dnsSubdomainLabelErrMsg, + }, + "empty addresses": { + endpoints: core.Endpoints{ + ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, + Subsets: []core.EndpointSubset{ + { + Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}}, + }, + }, + }, + errorType: "FieldValueRequired", + }, + "invalid IP": { + endpoints: core.Endpoints{ + ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, + Subsets: []core.EndpointSubset{ + { + Addresses: []core.EndpointAddress{{IP: "[2001:0db8:85a3:0042:1000:8a2e:0370:7334]"}}, + Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}}, + }, + }, + }, + errorType: "FieldValueInvalid", + errorDetail: "must be a valid IP address", + }, + "Multiple ports, one without name": { + endpoints: core.Endpoints{ + ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, + Subsets: []core.EndpointSubset{ + { + Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, + Ports: []core.EndpointPort{{Port: 8675, Protocol: "TCP"}, {Name: "b", Port: 309, Protocol: "TCP"}}, + }, + }, + }, + errorType: "FieldValueRequired", + }, + "Invalid port number": { + endpoints: core.Endpoints{ + ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, + Subsets: []core.EndpointSubset{ + { + Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, + Ports: []core.EndpointPort{{Name: "a", Port: 66000, Protocol: "TCP"}}, + }, + }, + }, + errorType: "FieldValueInvalid", + errorDetail: "between", + }, + "Invalid protocol": { + endpoints: core.Endpoints{ + ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, + Subsets: []core.EndpointSubset{ + { + Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, + Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "Protocol"}}, + }, + }, + }, + errorType: "FieldValueNotSupported", + }, + "Address missing IP": { + endpoints: core.Endpoints{ + ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, + Subsets: []core.EndpointSubset{ + { + Addresses: []core.EndpointAddress{{}}, + Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}}, + }, + }, + }, + errorType: "FieldValueInvalid", + errorDetail: "must be a valid IP address", + }, + "Port missing number": { + endpoints: core.Endpoints{ + ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, + Subsets: []core.EndpointSubset{ + { + Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, + Ports: []core.EndpointPort{{Name: "a", Protocol: "TCP"}}, + }, + }, + }, + errorType: "FieldValueInvalid", + errorDetail: "between", + }, + "Port missing protocol": { + endpoints: core.Endpoints{ + ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, + Subsets: []core.EndpointSubset{ + { + Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, + Ports: []core.EndpointPort{{Name: "a", Port: 93}}, + }, + }, + }, + errorType: "FieldValueRequired", + }, + "Address is loopback": { + endpoints: core.Endpoints{ + ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, + Subsets: []core.EndpointSubset{ + { + Addresses: []core.EndpointAddress{{IP: "127.0.0.1"}}, + Ports: []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}}, + }, + }, + }, + errorType: "FieldValueInvalid", + errorDetail: "loopback", + }, + "Address is link-local": { + endpoints: core.Endpoints{ + ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, + Subsets: []core.EndpointSubset{ + { + Addresses: []core.EndpointAddress{{IP: "169.254.169.254"}}, + Ports: []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}}, + }, + }, + }, + errorType: "FieldValueInvalid", + errorDetail: "link-local", + }, + "Address is link-local multicast": { + endpoints: core.Endpoints{ + ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, + Subsets: []core.EndpointSubset{ + { + Addresses: []core.EndpointAddress{{IP: "224.0.0.1"}}, + Ports: []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}}, + }, + }, + }, + errorType: "FieldValueInvalid", + errorDetail: "link-local multicast", + }, + } + + for k, v := range errorCases { + if errs := ValidateEndpoints(&v.endpoints); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) { + t.Errorf("[%s] Expected error type %s with detail %q, got %v", k, v.errorType, v.errorDetail, errs) + } + } +} + +func TestValidateTLSSecret(t *testing.T) { + successCases := map[string]core.Secret{ + "empty certificate chain": { + ObjectMeta: metav1.ObjectMeta{Name: "tls-cert", Namespace: "namespace"}, + Data: map[string][]byte{ + core.TLSCertKey: []byte("public key"), + core.TLSPrivateKeyKey: []byte("private key"), + }, + }, + } + for k, v := range successCases { + if errs := ValidateSecret(&v); len(errs) != 0 { + t.Errorf("Expected success for %s, got %v", k, errs) + } + } + errorCases := map[string]struct { + secrets core.Secret + errorType field.ErrorType + errorDetail string + }{ + "missing public key": { + secrets: core.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "tls-cert"}, + Data: map[string][]byte{ + core.TLSCertKey: []byte("public key"), + }, + }, + errorType: "FieldValueRequired", + }, + "missing private key": { + secrets: core.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "tls-cert"}, + Data: map[string][]byte{ + core.TLSCertKey: []byte("public key"), + }, + }, + errorType: "FieldValueRequired", + }, + } + for k, v := range errorCases { + if errs := ValidateSecret(&v.secrets); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) { + t.Errorf("[%s] Expected error type %s with detail %q, got %v", k, v.errorType, v.errorDetail, errs) + } + } +} + +func TestValidateSecurityContext(t *testing.T) { + priv := false + runAsUser := int64(1) + fullValidSC := func() *core.SecurityContext { + return &core.SecurityContext{ + Privileged: &priv, + Capabilities: &core.Capabilities{ + Add: []core.Capability{"foo"}, + Drop: []core.Capability{"bar"}, + }, + SELinuxOptions: &core.SELinuxOptions{ + User: "user", + Role: "role", + Type: "type", + Level: "level", + }, + RunAsUser: &runAsUser, + } + } + + //setup data + allSettings := fullValidSC() + noCaps := fullValidSC() + noCaps.Capabilities = nil + + noSELinux := fullValidSC() + noSELinux.SELinuxOptions = nil + + noPrivRequest := fullValidSC() + noPrivRequest.Privileged = nil + + noRunAsUser := fullValidSC() + noRunAsUser.RunAsUser = nil + + successCases := map[string]struct { + sc *core.SecurityContext + }{ + "all settings": {allSettings}, + "no capabilities": {noCaps}, + "no selinux": {noSELinux}, + "no priv request": {noPrivRequest}, + "no run as user": {noRunAsUser}, + } + for k, v := range successCases { + if errs := ValidateSecurityContext(v.sc, field.NewPath("field")); len(errs) != 0 { + t.Errorf("[%s] Expected success, got %v", k, errs) + } + } + + privRequestWithGlobalDeny := fullValidSC() + requestPrivileged := true + privRequestWithGlobalDeny.Privileged = &requestPrivileged + + negativeRunAsUser := fullValidSC() + negativeUser := int64(-1) + negativeRunAsUser.RunAsUser = &negativeUser + + errorCases := map[string]struct { + sc *core.SecurityContext + errorType field.ErrorType + errorDetail string + }{ + "request privileged when capabilities forbids": { + sc: privRequestWithGlobalDeny, + errorType: "FieldValueForbidden", + errorDetail: "disallowed by cluster policy", + }, + "negative RunAsUser": { + sc: negativeRunAsUser, + errorType: "FieldValueInvalid", + errorDetail: isNegativeErrorMsg, + }, + } + for k, v := range errorCases { + if errs := ValidateSecurityContext(v.sc, field.NewPath("field")); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) { + t.Errorf("[%s] Expected error type %q with detail %q, got %v", k, v.errorType, v.errorDetail, errs) + } + } +} + +func fakeValidSecurityContext(priv bool) *core.SecurityContext { + return &core.SecurityContext{ + Privileged: &priv, + } +} + +func TestValidPodLogOptions(t *testing.T) { + now := metav1.Now() + negative := int64(-1) + zero := int64(0) + positive := int64(1) + tests := []struct { + opt core.PodLogOptions + errs int + }{ + {core.PodLogOptions{}, 0}, + {core.PodLogOptions{Previous: true}, 0}, + {core.PodLogOptions{Follow: true}, 0}, + {core.PodLogOptions{TailLines: &zero}, 0}, + {core.PodLogOptions{TailLines: &negative}, 1}, + {core.PodLogOptions{TailLines: &positive}, 0}, + {core.PodLogOptions{LimitBytes: &zero}, 1}, + {core.PodLogOptions{LimitBytes: &negative}, 1}, + {core.PodLogOptions{LimitBytes: &positive}, 0}, + {core.PodLogOptions{SinceSeconds: &negative}, 1}, + {core.PodLogOptions{SinceSeconds: &positive}, 0}, + {core.PodLogOptions{SinceSeconds: &zero}, 1}, + {core.PodLogOptions{SinceTime: &now}, 0}, + } + for i, test := range tests { + errs := ValidatePodLogOptions(&test.opt) + if test.errs != len(errs) { + t.Errorf("%d: Unexpected errors: %v", i, errs) + } + } +} + +func TestValidateConfigMap(t *testing.T) { + newConfigMap := func(name, namespace string, data map[string]string) core.ConfigMap { + return core.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Data: data, + } + } + + var ( + validConfigMap = newConfigMap("validname", "validns", map[string]string{"key": "value"}) + maxKeyLength = newConfigMap("validname", "validns", map[string]string{strings.Repeat("a", 253): "value"}) + + emptyName = newConfigMap("", "validns", nil) + invalidName = newConfigMap("NoUppercaseOrSpecialCharsLike=Equals", "validns", nil) + emptyNs = newConfigMap("validname", "", nil) + invalidNs = newConfigMap("validname", "NoUppercaseOrSpecialCharsLike=Equals", nil) + invalidKey = newConfigMap("validname", "validns", map[string]string{"a*b": "value"}) + leadingDotKey = newConfigMap("validname", "validns", map[string]string{".ab": "value"}) + dotKey = newConfigMap("validname", "validns", map[string]string{".": "value"}) + doubleDotKey = newConfigMap("validname", "validns", map[string]string{"..": "value"}) + overMaxKeyLength = newConfigMap("validname", "validns", map[string]string{strings.Repeat("a", 254): "value"}) + overMaxSize = newConfigMap("validname", "validns", map[string]string{"key": strings.Repeat("a", core.MaxSecretSize+1)}) + ) + + tests := map[string]struct { + cfg core.ConfigMap + isValid bool + }{ + "valid": {validConfigMap, true}, + "max key length": {maxKeyLength, true}, + "leading dot key": {leadingDotKey, true}, + "empty name": {emptyName, false}, + "invalid name": {invalidName, false}, + "invalid key": {invalidKey, false}, + "empty namespace": {emptyNs, false}, + "invalid namespace": {invalidNs, false}, + "dot key": {dotKey, false}, + "double dot key": {doubleDotKey, false}, + "over max key length": {overMaxKeyLength, false}, + "over max size": {overMaxSize, false}, + } + + for name, tc := range tests { + errs := ValidateConfigMap(&tc.cfg) + if tc.isValid && len(errs) > 0 { + t.Errorf("%v: unexpected error: %v", name, errs) + } + if !tc.isValid && len(errs) == 0 { + t.Errorf("%v: unexpected non-error", name) + } + } +} + +func TestValidateConfigMapUpdate(t *testing.T) { + newConfigMap := func(version, name, namespace string, data map[string]string) core.ConfigMap { + return core.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + ResourceVersion: version, + }, + Data: data, + } + } + + var ( + validConfigMap = newConfigMap("1", "validname", "validns", map[string]string{"key": "value"}) + noVersion = newConfigMap("", "validname", "validns", map[string]string{"key": "value"}) + ) + + cases := []struct { + name string + newCfg core.ConfigMap + oldCfg core.ConfigMap + isValid bool + }{ + { + name: "valid", + newCfg: validConfigMap, + oldCfg: validConfigMap, + isValid: true, + }, + { + name: "invalid", + newCfg: noVersion, + oldCfg: validConfigMap, + isValid: false, + }, + } + + for _, tc := range cases { + errs := ValidateConfigMapUpdate(&tc.newCfg, &tc.oldCfg) + if tc.isValid && len(errs) > 0 { + t.Errorf("%v: unexpected error: %v", tc.name, errs) + } + if !tc.isValid && len(errs) == 0 { + t.Errorf("%v: unexpected non-error", tc.name) + } + } +} + +func TestValidateHasLabel(t *testing.T) { + successCase := metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + Labels: map[string]string{ + "other": "blah", + "foo": "bar", + }, + } + if errs := ValidateHasLabel(successCase, field.NewPath("field"), "foo", "bar"); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + + missingCase := metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + Labels: map[string]string{ + "other": "blah", + }, + } + if errs := ValidateHasLabel(missingCase, field.NewPath("field"), "foo", "bar"); len(errs) == 0 { + t.Errorf("expected failure") + } + + wrongValueCase := metav1.ObjectMeta{ + Name: "123", + Namespace: "ns", + Labels: map[string]string{ + "other": "blah", + "foo": "notbar", + }, + } + if errs := ValidateHasLabel(wrongValueCase, field.NewPath("field"), "foo", "bar"); len(errs) == 0 { + t.Errorf("expected failure") + } +} + +func TestIsValidSysctlName(t *testing.T) { + valid := []string{ + "a.b.c.d", + "a", + "a_b", + "a-b", + "abc", + "abc.def", + } + invalid := []string{ + "", + "*", + "ä", + "a_", + "_", + "__", + "_a", + "_a._b", + "-", + ".", + "a.", + ".a", + "a.b.", + "a*.b", + "a*b", + "*a", + "a.*", + "*", + "abc*", + "a.abc*", + "a.b.*", + "Abc", + func(n int) string { + x := make([]byte, n) + for i := range x { + x[i] = byte('a') + } + return string(x) + }(256), + } + for _, s := range valid { + if !IsValidSysctlName(s) { + t.Errorf("%q expected to be a valid sysctl name", s) + } + } + for _, s := range invalid { + if IsValidSysctlName(s) { + t.Errorf("%q expected to be an invalid sysctl name", s) + } + } +} + +func TestValidateSysctls(t *testing.T) { + valid := []string{ + "net.foo.bar", + "kernel.shmmax", + } + invalid := []string{ + "i..nvalid", + "_invalid", + } + + sysctls := make([]core.Sysctl, len(valid)) + for i, sysctl := range valid { + sysctls[i].Name = sysctl + } + errs := validateSysctls(sysctls, field.NewPath("foo")) + if len(errs) != 0 { + t.Errorf("unexpected validation errors: %v", errs) + } + + sysctls = make([]core.Sysctl, len(invalid)) + for i, sysctl := range invalid { + sysctls[i].Name = sysctl + } + errs = validateSysctls(sysctls, field.NewPath("foo")) + if len(errs) != 2 { + t.Errorf("expected 2 validation errors. Got: %v", errs) + } else { + if got, expected := errs[0].Error(), "foo"; !strings.Contains(got, expected) { + t.Errorf("unexpected errors: expected=%q, got=%q", expected, got) + } + if got, expected := errs[1].Error(), "foo"; !strings.Contains(got, expected) { + t.Errorf("unexpected errors: expected=%q, got=%q", expected, got) + } + } +} + +func newNodeNameEndpoint(nodeName string) *core.Endpoints { + ep := &core.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: metav1.NamespaceDefault, + ResourceVersion: "1", + }, + Subsets: []core.EndpointSubset{ + { + NotReadyAddresses: []core.EndpointAddress{}, + Ports: []core.EndpointPort{{Name: "https", Port: 443, Protocol: "TCP"}}, + Addresses: []core.EndpointAddress{ + { + IP: "8.8.8.8", + Hostname: "zookeeper1", + NodeName: &nodeName}}}}} + return ep +} + +func TestEndpointAddressNodeNameUpdateRestrictions(t *testing.T) { + oldEndpoint := newNodeNameEndpoint("kubernetes-node-setup-by-backend") + updatedEndpoint := newNodeNameEndpoint("kubernetes-changed-nodename") + // Check that NodeName cannot be changed during update (if already set) + errList := ValidateEndpoints(updatedEndpoint) + errList = append(errList, ValidateEndpointsUpdate(updatedEndpoint, oldEndpoint)...) + if len(errList) == 0 { + t.Error("Endpoint should not allow changing of Subset.Addresses.NodeName on update") + } +} + +func TestEndpointAddressNodeNameInvalidDNSSubdomain(t *testing.T) { + // Check NodeName DNS validation + endpoint := newNodeNameEndpoint("illegal*.nodename") + errList := ValidateEndpoints(endpoint) + if len(errList) == 0 { + t.Error("Endpoint should reject invalid NodeName") + } +} + +func TestEndpointAddressNodeNameCanBeAnIPAddress(t *testing.T) { + endpoint := newNodeNameEndpoint("10.10.1.1") + errList := ValidateEndpoints(endpoint) + if len(errList) != 0 { + t.Error("Endpoint should accept a NodeName that is an IP address") + } +} + +func TestValidateFlexVolumeSource(t *testing.T) { + testcases := map[string]struct { + source *core.FlexVolumeSource + expectedErrs map[string]string + }{ + "valid": { + source: &core.FlexVolumeSource{Driver: "foo"}, + expectedErrs: map[string]string{}, + }, + "valid with options": { + source: &core.FlexVolumeSource{Driver: "foo", Options: map[string]string{"foo": "bar"}}, + expectedErrs: map[string]string{}, + }, + "no driver": { + source: &core.FlexVolumeSource{Driver: ""}, + expectedErrs: map[string]string{"driver": "Required value"}, + }, + "reserved option keys": { + source: &core.FlexVolumeSource{ + Driver: "foo", + Options: map[string]string{ + // valid options + "myns.io": "A", + "myns.io/bar": "A", + "myns.io/kubernetes.io": "A", + + // invalid options + "KUBERNETES.IO": "A", + "kubernetes.io": "A", + "kubernetes.io/": "A", + "kubernetes.io/foo": "A", + + "alpha.kubernetes.io": "A", + "alpha.kubernetes.io/": "A", + "alpha.kubernetes.io/foo": "A", + + "k8s.io": "A", + "k8s.io/": "A", + "k8s.io/foo": "A", + + "alpha.k8s.io": "A", + "alpha.k8s.io/": "A", + "alpha.k8s.io/foo": "A", + }, + }, + expectedErrs: map[string]string{ + "options[KUBERNETES.IO]": "reserved", + "options[kubernetes.io]": "reserved", + "options[kubernetes.io/]": "reserved", + "options[kubernetes.io/foo]": "reserved", + "options[alpha.kubernetes.io]": "reserved", + "options[alpha.kubernetes.io/]": "reserved", + "options[alpha.kubernetes.io/foo]": "reserved", + "options[k8s.io]": "reserved", + "options[k8s.io/]": "reserved", + "options[k8s.io/foo]": "reserved", + "options[alpha.k8s.io]": "reserved", + "options[alpha.k8s.io/]": "reserved", + "options[alpha.k8s.io/foo]": "reserved", + }, + }, + } + + for k, tc := range testcases { + errs := validateFlexVolumeSource(tc.source, nil) + for _, err := range errs { + expectedErr, ok := tc.expectedErrs[err.Field] + if !ok { + t.Errorf("%s: unexpected err on field %s: %v", k, err.Field, err) + continue + } + if !strings.Contains(err.Error(), expectedErr) { + t.Errorf("%s: expected err on field %s to contain '%s', was %v", k, err.Field, expectedErr, err.Error()) + continue + } + } + if len(errs) != len(tc.expectedErrs) { + t.Errorf("%s: expected errs %#v, got %#v", k, tc.expectedErrs, errs) + continue + } + } +} + +func TestValidateOrSetClientIPAffinityConfig(t *testing.T) { + successCases := map[string]*core.SessionAffinityConfig{ + "non-empty config, valid timeout: 1": { + ClientIP: &core.ClientIPConfig{ + TimeoutSeconds: newInt32(1), + }, + }, + "non-empty config, valid timeout: core.MaxClientIPServiceAffinitySeconds-1": { + ClientIP: &core.ClientIPConfig{ + TimeoutSeconds: newInt32(int(core.MaxClientIPServiceAffinitySeconds - 1)), + }, + }, + "non-empty config, valid timeout: core.MaxClientIPServiceAffinitySeconds": { + ClientIP: &core.ClientIPConfig{ + TimeoutSeconds: newInt32(int(core.MaxClientIPServiceAffinitySeconds)), + }, + }, + } + + for name, test := range successCases { + if errs := validateClientIPAffinityConfig(test, field.NewPath("field")); len(errs) != 0 { + t.Errorf("case: %s, expected success: %v", name, errs) + } + } + + errorCases := map[string]*core.SessionAffinityConfig{ + "empty session affinity config": nil, + "empty client IP config": { + ClientIP: nil, + }, + "empty timeoutSeconds": { + ClientIP: &core.ClientIPConfig{ + TimeoutSeconds: nil, + }, + }, + "non-empty config, invalid timeout: core.MaxClientIPServiceAffinitySeconds+1": { + ClientIP: &core.ClientIPConfig{ + TimeoutSeconds: newInt32(int(core.MaxClientIPServiceAffinitySeconds + 1)), + }, + }, + "non-empty config, invalid timeout: -1": { + ClientIP: &core.ClientIPConfig{ + TimeoutSeconds: newInt32(-1), + }, + }, + "non-empty config, invalid timeout: 0": { + ClientIP: &core.ClientIPConfig{ + TimeoutSeconds: newInt32(0), + }, + }, + } + + for name, test := range errorCases { + if errs := validateClientIPAffinityConfig(test, field.NewPath("field")); len(errs) == 0 { + t.Errorf("case: %v, expected failures: %v", name, errs) + } + } +} + +func boolPtr(b bool) *bool { + return &b +} diff --git a/pkg/apis/core/zz_generated.deepcopy.go b/pkg/apis/core/zz_generated.deepcopy.go new file mode 100644 index 00000000000..22913309a3e --- /dev/null +++ b/pkg/apis/core/zz_generated.deepcopy.go @@ -0,0 +1,5894 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +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" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AWSElasticBlockStoreVolumeSource) DeepCopyInto(out *AWSElasticBlockStoreVolumeSource) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSElasticBlockStoreVolumeSource. +func (in *AWSElasticBlockStoreVolumeSource) DeepCopy() *AWSElasticBlockStoreVolumeSource { + if in == nil { + return nil + } + out := new(AWSElasticBlockStoreVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Affinity) DeepCopyInto(out *Affinity) { + *out = *in + if in.NodeAffinity != nil { + in, out := &in.NodeAffinity, &out.NodeAffinity + if *in == nil { + *out = nil + } else { + *out = new(NodeAffinity) + (*in).DeepCopyInto(*out) + } + } + if in.PodAffinity != nil { + in, out := &in.PodAffinity, &out.PodAffinity + if *in == nil { + *out = nil + } else { + *out = new(PodAffinity) + (*in).DeepCopyInto(*out) + } + } + if in.PodAntiAffinity != nil { + in, out := &in.PodAntiAffinity, &out.PodAntiAffinity + if *in == nil { + *out = nil + } else { + *out = new(PodAntiAffinity) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Affinity. +func (in *Affinity) DeepCopy() *Affinity { + if in == nil { + return nil + } + out := new(Affinity) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AttachedVolume) DeepCopyInto(out *AttachedVolume) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AttachedVolume. +func (in *AttachedVolume) DeepCopy() *AttachedVolume { + if in == nil { + return nil + } + out := new(AttachedVolume) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AvoidPods) DeepCopyInto(out *AvoidPods) { + *out = *in + if in.PreferAvoidPods != nil { + in, out := &in.PreferAvoidPods, &out.PreferAvoidPods + *out = make([]PreferAvoidPodsEntry, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AvoidPods. +func (in *AvoidPods) DeepCopy() *AvoidPods { + if in == nil { + return nil + } + out := new(AvoidPods) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AzureDiskVolumeSource) DeepCopyInto(out *AzureDiskVolumeSource) { + *out = *in + if in.CachingMode != nil { + in, out := &in.CachingMode, &out.CachingMode + if *in == nil { + *out = nil + } else { + *out = new(AzureDataDiskCachingMode) + **out = **in + } + } + if in.FSType != nil { + in, out := &in.FSType, &out.FSType + if *in == nil { + *out = nil + } else { + *out = new(string) + **out = **in + } + } + if in.ReadOnly != nil { + in, out := &in.ReadOnly, &out.ReadOnly + if *in == nil { + *out = nil + } else { + *out = new(bool) + **out = **in + } + } + if in.Kind != nil { + in, out := &in.Kind, &out.Kind + if *in == nil { + *out = nil + } else { + *out = new(AzureDataDiskKind) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureDiskVolumeSource. +func (in *AzureDiskVolumeSource) DeepCopy() *AzureDiskVolumeSource { + if in == nil { + return nil + } + out := new(AzureDiskVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AzureFilePersistentVolumeSource) DeepCopyInto(out *AzureFilePersistentVolumeSource) { + *out = *in + if in.SecretNamespace != nil { + in, out := &in.SecretNamespace, &out.SecretNamespace + if *in == nil { + *out = nil + } else { + *out = new(string) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureFilePersistentVolumeSource. +func (in *AzureFilePersistentVolumeSource) DeepCopy() *AzureFilePersistentVolumeSource { + if in == nil { + return nil + } + out := new(AzureFilePersistentVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AzureFileVolumeSource) DeepCopyInto(out *AzureFileVolumeSource) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureFileVolumeSource. +func (in *AzureFileVolumeSource) DeepCopy() *AzureFileVolumeSource { + if in == nil { + return nil + } + out := new(AzureFileVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Binding) DeepCopyInto(out *Binding) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Target = in.Target + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Binding. +func (in *Binding) DeepCopy() *Binding { + if in == nil { + return nil + } + out := new(Binding) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Binding) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CSIPersistentVolumeSource) DeepCopyInto(out *CSIPersistentVolumeSource) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CSIPersistentVolumeSource. +func (in *CSIPersistentVolumeSource) DeepCopy() *CSIPersistentVolumeSource { + if in == nil { + return nil + } + out := new(CSIPersistentVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Capabilities) DeepCopyInto(out *Capabilities) { + *out = *in + if in.Add != nil { + in, out := &in.Add, &out.Add + *out = make([]Capability, len(*in)) + copy(*out, *in) + } + if in.Drop != nil { + in, out := &in.Drop, &out.Drop + *out = make([]Capability, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Capabilities. +func (in *Capabilities) DeepCopy() *Capabilities { + if in == nil { + return nil + } + out := new(Capabilities) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CephFSPersistentVolumeSource) DeepCopyInto(out *CephFSPersistentVolumeSource) { + *out = *in + if in.Monitors != nil { + in, out := &in.Monitors, &out.Monitors + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + if *in == nil { + *out = nil + } else { + *out = new(SecretReference) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CephFSPersistentVolumeSource. +func (in *CephFSPersistentVolumeSource) DeepCopy() *CephFSPersistentVolumeSource { + if in == nil { + return nil + } + out := new(CephFSPersistentVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CephFSVolumeSource) DeepCopyInto(out *CephFSVolumeSource) { + *out = *in + if in.Monitors != nil { + in, out := &in.Monitors, &out.Monitors + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + if *in == nil { + *out = nil + } else { + *out = new(LocalObjectReference) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CephFSVolumeSource. +func (in *CephFSVolumeSource) DeepCopy() *CephFSVolumeSource { + if in == nil { + return nil + } + out := new(CephFSVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CinderVolumeSource) DeepCopyInto(out *CinderVolumeSource) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CinderVolumeSource. +func (in *CinderVolumeSource) DeepCopy() *CinderVolumeSource { + if in == nil { + return nil + } + out := new(CinderVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClientIPConfig) DeepCopyInto(out *ClientIPConfig) { + *out = *in + if in.TimeoutSeconds != nil { + in, out := &in.TimeoutSeconds, &out.TimeoutSeconds + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientIPConfig. +func (in *ClientIPConfig) DeepCopy() *ClientIPConfig { + if in == nil { + return nil + } + out := new(ClientIPConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ComponentCondition) DeepCopyInto(out *ComponentCondition) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentCondition. +func (in *ComponentCondition) DeepCopy() *ComponentCondition { + if in == nil { + return nil + } + out := new(ComponentCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ComponentStatus) DeepCopyInto(out *ComponentStatus) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]ComponentCondition, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentStatus. +func (in *ComponentStatus) DeepCopy() *ComponentStatus { + if in == nil { + return nil + } + out := new(ComponentStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ComponentStatus) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ComponentStatusList) DeepCopyInto(out *ComponentStatusList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ComponentStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentStatusList. +func (in *ComponentStatusList) DeepCopy() *ComponentStatusList { + if in == nil { + return nil + } + out := new(ComponentStatusList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ComponentStatusList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConfigMap) DeepCopyInto(out *ConfigMap) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.Data != nil { + in, out := &in.Data, &out.Data + *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 ConfigMap. +func (in *ConfigMap) DeepCopy() *ConfigMap { + if in == nil { + return nil + } + out := new(ConfigMap) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ConfigMap) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConfigMapEnvSource) DeepCopyInto(out *ConfigMapEnvSource) { + *out = *in + out.LocalObjectReference = in.LocalObjectReference + if in.Optional != nil { + in, out := &in.Optional, &out.Optional + if *in == nil { + *out = nil + } else { + *out = new(bool) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapEnvSource. +func (in *ConfigMapEnvSource) DeepCopy() *ConfigMapEnvSource { + if in == nil { + return nil + } + out := new(ConfigMapEnvSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConfigMapKeySelector) DeepCopyInto(out *ConfigMapKeySelector) { + *out = *in + out.LocalObjectReference = in.LocalObjectReference + if in.Optional != nil { + in, out := &in.Optional, &out.Optional + if *in == nil { + *out = nil + } else { + *out = new(bool) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapKeySelector. +func (in *ConfigMapKeySelector) DeepCopy() *ConfigMapKeySelector { + if in == nil { + return nil + } + out := new(ConfigMapKeySelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConfigMapList) DeepCopyInto(out *ConfigMapList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ConfigMap, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapList. +func (in *ConfigMapList) DeepCopy() *ConfigMapList { + if in == nil { + return nil + } + out := new(ConfigMapList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ConfigMapList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConfigMapProjection) DeepCopyInto(out *ConfigMapProjection) { + *out = *in + out.LocalObjectReference = in.LocalObjectReference + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]KeyToPath, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Optional != nil { + in, out := &in.Optional, &out.Optional + if *in == nil { + *out = nil + } else { + *out = new(bool) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapProjection. +func (in *ConfigMapProjection) DeepCopy() *ConfigMapProjection { + if in == nil { + return nil + } + out := new(ConfigMapProjection) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConfigMapVolumeSource) DeepCopyInto(out *ConfigMapVolumeSource) { + *out = *in + out.LocalObjectReference = in.LocalObjectReference + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]KeyToPath, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.DefaultMode != nil { + in, out := &in.DefaultMode, &out.DefaultMode + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + if in.Optional != nil { + in, out := &in.Optional, &out.Optional + if *in == nil { + *out = nil + } else { + *out = new(bool) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapVolumeSource. +func (in *ConfigMapVolumeSource) DeepCopy() *ConfigMapVolumeSource { + if in == nil { + return nil + } + out := new(ConfigMapVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Container) DeepCopyInto(out *Container) { + *out = *in + if in.Command != nil { + in, out := &in.Command, &out.Command + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Args != nil { + in, out := &in.Args, &out.Args + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Ports != nil { + in, out := &in.Ports, &out.Ports + *out = make([]ContainerPort, len(*in)) + copy(*out, *in) + } + if in.EnvFrom != nil { + in, out := &in.EnvFrom, &out.EnvFrom + *out = make([]EnvFromSource, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Env != nil { + in, out := &in.Env, &out.Env + *out = make([]EnvVar, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + in.Resources.DeepCopyInto(&out.Resources) + if in.VolumeMounts != nil { + in, out := &in.VolumeMounts, &out.VolumeMounts + *out = make([]VolumeMount, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.VolumeDevices != nil { + in, out := &in.VolumeDevices, &out.VolumeDevices + *out = make([]VolumeDevice, len(*in)) + copy(*out, *in) + } + if in.LivenessProbe != nil { + in, out := &in.LivenessProbe, &out.LivenessProbe + if *in == nil { + *out = nil + } else { + *out = new(Probe) + (*in).DeepCopyInto(*out) + } + } + if in.ReadinessProbe != nil { + in, out := &in.ReadinessProbe, &out.ReadinessProbe + if *in == nil { + *out = nil + } else { + *out = new(Probe) + (*in).DeepCopyInto(*out) + } + } + if in.Lifecycle != nil { + in, out := &in.Lifecycle, &out.Lifecycle + if *in == nil { + *out = nil + } else { + *out = new(Lifecycle) + (*in).DeepCopyInto(*out) + } + } + if in.SecurityContext != nil { + in, out := &in.SecurityContext, &out.SecurityContext + if *in == nil { + *out = nil + } else { + *out = new(SecurityContext) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Container. +func (in *Container) DeepCopy() *Container { + if in == nil { + return nil + } + out := new(Container) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ContainerImage) DeepCopyInto(out *ContainerImage) { + *out = *in + if in.Names != nil { + in, out := &in.Names, &out.Names + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerImage. +func (in *ContainerImage) DeepCopy() *ContainerImage { + if in == nil { + return nil + } + out := new(ContainerImage) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ContainerPort) DeepCopyInto(out *ContainerPort) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerPort. +func (in *ContainerPort) DeepCopy() *ContainerPort { + if in == nil { + return nil + } + out := new(ContainerPort) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ContainerState) DeepCopyInto(out *ContainerState) { + *out = *in + if in.Waiting != nil { + in, out := &in.Waiting, &out.Waiting + if *in == nil { + *out = nil + } else { + *out = new(ContainerStateWaiting) + **out = **in + } + } + if in.Running != nil { + in, out := &in.Running, &out.Running + if *in == nil { + *out = nil + } else { + *out = new(ContainerStateRunning) + (*in).DeepCopyInto(*out) + } + } + if in.Terminated != nil { + in, out := &in.Terminated, &out.Terminated + if *in == nil { + *out = nil + } else { + *out = new(ContainerStateTerminated) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerState. +func (in *ContainerState) DeepCopy() *ContainerState { + if in == nil { + return nil + } + out := new(ContainerState) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ContainerStateRunning) DeepCopyInto(out *ContainerStateRunning) { + *out = *in + in.StartedAt.DeepCopyInto(&out.StartedAt) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerStateRunning. +func (in *ContainerStateRunning) DeepCopy() *ContainerStateRunning { + if in == nil { + return nil + } + out := new(ContainerStateRunning) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ContainerStateTerminated) DeepCopyInto(out *ContainerStateTerminated) { + *out = *in + in.StartedAt.DeepCopyInto(&out.StartedAt) + in.FinishedAt.DeepCopyInto(&out.FinishedAt) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerStateTerminated. +func (in *ContainerStateTerminated) DeepCopy() *ContainerStateTerminated { + if in == nil { + return nil + } + out := new(ContainerStateTerminated) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ContainerStateWaiting) DeepCopyInto(out *ContainerStateWaiting) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerStateWaiting. +func (in *ContainerStateWaiting) DeepCopy() *ContainerStateWaiting { + if in == nil { + return nil + } + out := new(ContainerStateWaiting) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ContainerStatus) DeepCopyInto(out *ContainerStatus) { + *out = *in + in.State.DeepCopyInto(&out.State) + in.LastTerminationState.DeepCopyInto(&out.LastTerminationState) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerStatus. +func (in *ContainerStatus) DeepCopy() *ContainerStatus { + if in == nil { + return nil + } + out := new(ContainerStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DaemonEndpoint) DeepCopyInto(out *DaemonEndpoint) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DaemonEndpoint. +func (in *DaemonEndpoint) DeepCopy() *DaemonEndpoint { + if in == nil { + return nil + } + out := new(DaemonEndpoint) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeleteOptions) DeepCopyInto(out *DeleteOptions) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.GracePeriodSeconds != nil { + in, out := &in.GracePeriodSeconds, &out.GracePeriodSeconds + if *in == nil { + *out = nil + } else { + *out = new(int64) + **out = **in + } + } + if in.Preconditions != nil { + in, out := &in.Preconditions, &out.Preconditions + if *in == nil { + *out = nil + } else { + *out = new(Preconditions) + (*in).DeepCopyInto(*out) + } + } + if in.OrphanDependents != nil { + in, out := &in.OrphanDependents, &out.OrphanDependents + if *in == nil { + *out = nil + } else { + *out = new(bool) + **out = **in + } + } + if in.PropagationPolicy != nil { + in, out := &in.PropagationPolicy, &out.PropagationPolicy + if *in == nil { + *out = nil + } else { + *out = new(DeletionPropagation) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeleteOptions. +func (in *DeleteOptions) DeepCopy() *DeleteOptions { + if in == nil { + return nil + } + out := new(DeleteOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DeleteOptions) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DownwardAPIProjection) DeepCopyInto(out *DownwardAPIProjection) { + *out = *in + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]DownwardAPIVolumeFile, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DownwardAPIProjection. +func (in *DownwardAPIProjection) DeepCopy() *DownwardAPIProjection { + if in == nil { + return nil + } + out := new(DownwardAPIProjection) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DownwardAPIVolumeFile) DeepCopyInto(out *DownwardAPIVolumeFile) { + *out = *in + if in.FieldRef != nil { + in, out := &in.FieldRef, &out.FieldRef + if *in == nil { + *out = nil + } else { + *out = new(ObjectFieldSelector) + **out = **in + } + } + if in.ResourceFieldRef != nil { + in, out := &in.ResourceFieldRef, &out.ResourceFieldRef + if *in == nil { + *out = nil + } else { + *out = new(ResourceFieldSelector) + (*in).DeepCopyInto(*out) + } + } + if in.Mode != nil { + in, out := &in.Mode, &out.Mode + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DownwardAPIVolumeFile. +func (in *DownwardAPIVolumeFile) DeepCopy() *DownwardAPIVolumeFile { + if in == nil { + return nil + } + out := new(DownwardAPIVolumeFile) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DownwardAPIVolumeSource) DeepCopyInto(out *DownwardAPIVolumeSource) { + *out = *in + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]DownwardAPIVolumeFile, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.DefaultMode != nil { + in, out := &in.DefaultMode, &out.DefaultMode + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DownwardAPIVolumeSource. +func (in *DownwardAPIVolumeSource) DeepCopy() *DownwardAPIVolumeSource { + if in == nil { + return nil + } + out := new(DownwardAPIVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EmptyDirVolumeSource) DeepCopyInto(out *EmptyDirVolumeSource) { + *out = *in + if in.SizeLimit != nil { + in, out := &in.SizeLimit, &out.SizeLimit + if *in == nil { + *out = nil + } else { + *out = new(resource.Quantity) + **out = (*in).DeepCopy() + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EmptyDirVolumeSource. +func (in *EmptyDirVolumeSource) DeepCopy() *EmptyDirVolumeSource { + if in == nil { + return nil + } + out := new(EmptyDirVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EndpointAddress) DeepCopyInto(out *EndpointAddress) { + *out = *in + if in.NodeName != nil { + in, out := &in.NodeName, &out.NodeName + if *in == nil { + *out = nil + } else { + *out = new(string) + **out = **in + } + } + if in.TargetRef != nil { + in, out := &in.TargetRef, &out.TargetRef + if *in == nil { + *out = nil + } else { + *out = new(ObjectReference) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointAddress. +func (in *EndpointAddress) DeepCopy() *EndpointAddress { + if in == nil { + return nil + } + out := new(EndpointAddress) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EndpointPort) DeepCopyInto(out *EndpointPort) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointPort. +func (in *EndpointPort) DeepCopy() *EndpointPort { + if in == nil { + return nil + } + out := new(EndpointPort) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EndpointSubset) DeepCopyInto(out *EndpointSubset) { + *out = *in + if in.Addresses != nil { + in, out := &in.Addresses, &out.Addresses + *out = make([]EndpointAddress, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.NotReadyAddresses != nil { + in, out := &in.NotReadyAddresses, &out.NotReadyAddresses + *out = make([]EndpointAddress, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Ports != nil { + in, out := &in.Ports, &out.Ports + *out = make([]EndpointPort, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointSubset. +func (in *EndpointSubset) DeepCopy() *EndpointSubset { + if in == nil { + return nil + } + out := new(EndpointSubset) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Endpoints) DeepCopyInto(out *Endpoints) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.Subsets != nil { + in, out := &in.Subsets, &out.Subsets + *out = make([]EndpointSubset, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Endpoints. +func (in *Endpoints) DeepCopy() *Endpoints { + if in == nil { + return nil + } + out := new(Endpoints) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Endpoints) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EndpointsList) DeepCopyInto(out *EndpointsList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Endpoints, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointsList. +func (in *EndpointsList) DeepCopy() *EndpointsList { + if in == nil { + return nil + } + out := new(EndpointsList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *EndpointsList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EnvFromSource) DeepCopyInto(out *EnvFromSource) { + *out = *in + if in.ConfigMapRef != nil { + in, out := &in.ConfigMapRef, &out.ConfigMapRef + if *in == nil { + *out = nil + } else { + *out = new(ConfigMapEnvSource) + (*in).DeepCopyInto(*out) + } + } + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + if *in == nil { + *out = nil + } else { + *out = new(SecretEnvSource) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvFromSource. +func (in *EnvFromSource) DeepCopy() *EnvFromSource { + if in == nil { + return nil + } + out := new(EnvFromSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EnvVar) DeepCopyInto(out *EnvVar) { + *out = *in + if in.ValueFrom != nil { + in, out := &in.ValueFrom, &out.ValueFrom + if *in == nil { + *out = nil + } else { + *out = new(EnvVarSource) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvVar. +func (in *EnvVar) DeepCopy() *EnvVar { + if in == nil { + return nil + } + out := new(EnvVar) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EnvVarSource) DeepCopyInto(out *EnvVarSource) { + *out = *in + if in.FieldRef != nil { + in, out := &in.FieldRef, &out.FieldRef + if *in == nil { + *out = nil + } else { + *out = new(ObjectFieldSelector) + **out = **in + } + } + if in.ResourceFieldRef != nil { + in, out := &in.ResourceFieldRef, &out.ResourceFieldRef + if *in == nil { + *out = nil + } else { + *out = new(ResourceFieldSelector) + (*in).DeepCopyInto(*out) + } + } + if in.ConfigMapKeyRef != nil { + in, out := &in.ConfigMapKeyRef, &out.ConfigMapKeyRef + if *in == nil { + *out = nil + } else { + *out = new(ConfigMapKeySelector) + (*in).DeepCopyInto(*out) + } + } + if in.SecretKeyRef != nil { + in, out := &in.SecretKeyRef, &out.SecretKeyRef + if *in == nil { + *out = nil + } else { + *out = new(SecretKeySelector) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvVarSource. +func (in *EnvVarSource) DeepCopy() *EnvVarSource { + if in == nil { + return nil + } + out := new(EnvVarSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Event) DeepCopyInto(out *Event) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.InvolvedObject = in.InvolvedObject + out.Source = in.Source + in.FirstTimestamp.DeepCopyInto(&out.FirstTimestamp) + in.LastTimestamp.DeepCopyInto(&out.LastTimestamp) + in.EventTime.DeepCopyInto(&out.EventTime) + if in.Series != nil { + in, out := &in.Series, &out.Series + if *in == nil { + *out = nil + } else { + *out = new(EventSeries) + (*in).DeepCopyInto(*out) + } + } + if in.Related != nil { + in, out := &in.Related, &out.Related + if *in == nil { + *out = nil + } else { + *out = new(ObjectReference) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Event. +func (in *Event) DeepCopy() *Event { + if in == nil { + return nil + } + out := new(Event) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Event) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EventList) DeepCopyInto(out *EventList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Event, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventList. +func (in *EventList) DeepCopy() *EventList { + if in == nil { + return nil + } + out := new(EventList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *EventList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EventSeries) DeepCopyInto(out *EventSeries) { + *out = *in + in.LastObservedTime.DeepCopyInto(&out.LastObservedTime) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventSeries. +func (in *EventSeries) DeepCopy() *EventSeries { + if in == nil { + return nil + } + out := new(EventSeries) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EventSource) DeepCopyInto(out *EventSource) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventSource. +func (in *EventSource) DeepCopy() *EventSource { + if in == nil { + return nil + } + out := new(EventSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExecAction) DeepCopyInto(out *ExecAction) { + *out = *in + if in.Command != nil { + in, out := &in.Command, &out.Command + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExecAction. +func (in *ExecAction) DeepCopy() *ExecAction { + if in == nil { + return nil + } + out := new(ExecAction) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FCVolumeSource) DeepCopyInto(out *FCVolumeSource) { + *out = *in + if in.TargetWWNs != nil { + in, out := &in.TargetWWNs, &out.TargetWWNs + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Lun != nil { + in, out := &in.Lun, &out.Lun + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + if in.WWIDs != nil { + in, out := &in.WWIDs, &out.WWIDs + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FCVolumeSource. +func (in *FCVolumeSource) DeepCopy() *FCVolumeSource { + if in == nil { + return nil + } + out := new(FCVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FlexPersistentVolumeSource) DeepCopyInto(out *FlexPersistentVolumeSource) { + *out = *in + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + if *in == nil { + *out = nil + } else { + *out = new(SecretReference) + **out = **in + } + } + if in.Options != nil { + in, out := &in.Options, &out.Options + *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 FlexPersistentVolumeSource. +func (in *FlexPersistentVolumeSource) DeepCopy() *FlexPersistentVolumeSource { + if in == nil { + return nil + } + out := new(FlexPersistentVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FlexVolumeSource) DeepCopyInto(out *FlexVolumeSource) { + *out = *in + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + if *in == nil { + *out = nil + } else { + *out = new(LocalObjectReference) + **out = **in + } + } + if in.Options != nil { + in, out := &in.Options, &out.Options + *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 FlexVolumeSource. +func (in *FlexVolumeSource) DeepCopy() *FlexVolumeSource { + if in == nil { + return nil + } + out := new(FlexVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FlockerVolumeSource) DeepCopyInto(out *FlockerVolumeSource) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlockerVolumeSource. +func (in *FlockerVolumeSource) DeepCopy() *FlockerVolumeSource { + if in == nil { + return nil + } + out := new(FlockerVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GCEPersistentDiskVolumeSource) DeepCopyInto(out *GCEPersistentDiskVolumeSource) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCEPersistentDiskVolumeSource. +func (in *GCEPersistentDiskVolumeSource) DeepCopy() *GCEPersistentDiskVolumeSource { + if in == nil { + return nil + } + out := new(GCEPersistentDiskVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitRepoVolumeSource) DeepCopyInto(out *GitRepoVolumeSource) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepoVolumeSource. +func (in *GitRepoVolumeSource) DeepCopy() *GitRepoVolumeSource { + if in == nil { + return nil + } + out := new(GitRepoVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GlusterfsVolumeSource) DeepCopyInto(out *GlusterfsVolumeSource) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlusterfsVolumeSource. +func (in *GlusterfsVolumeSource) DeepCopy() *GlusterfsVolumeSource { + if in == nil { + return nil + } + out := new(GlusterfsVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTPGetAction) DeepCopyInto(out *HTTPGetAction) { + *out = *in + out.Port = in.Port + if in.HTTPHeaders != nil { + in, out := &in.HTTPHeaders, &out.HTTPHeaders + *out = make([]HTTPHeader, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPGetAction. +func (in *HTTPGetAction) DeepCopy() *HTTPGetAction { + if in == nil { + return nil + } + out := new(HTTPGetAction) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTPHeader) DeepCopyInto(out *HTTPHeader) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPHeader. +func (in *HTTPHeader) DeepCopy() *HTTPHeader { + if in == nil { + return nil + } + out := new(HTTPHeader) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Handler) DeepCopyInto(out *Handler) { + *out = *in + if in.Exec != nil { + in, out := &in.Exec, &out.Exec + if *in == nil { + *out = nil + } else { + *out = new(ExecAction) + (*in).DeepCopyInto(*out) + } + } + if in.HTTPGet != nil { + in, out := &in.HTTPGet, &out.HTTPGet + if *in == nil { + *out = nil + } else { + *out = new(HTTPGetAction) + (*in).DeepCopyInto(*out) + } + } + if in.TCPSocket != nil { + in, out := &in.TCPSocket, &out.TCPSocket + if *in == nil { + *out = nil + } else { + *out = new(TCPSocketAction) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Handler. +func (in *Handler) DeepCopy() *Handler { + if in == nil { + return nil + } + out := new(Handler) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HostAlias) DeepCopyInto(out *HostAlias) { + *out = *in + if in.Hostnames != nil { + in, out := &in.Hostnames, &out.Hostnames + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostAlias. +func (in *HostAlias) DeepCopy() *HostAlias { + if in == nil { + return nil + } + out := new(HostAlias) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HostPathVolumeSource) DeepCopyInto(out *HostPathVolumeSource) { + *out = *in + if in.Type != nil { + in, out := &in.Type, &out.Type + if *in == nil { + *out = nil + } else { + *out = new(HostPathType) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostPathVolumeSource. +func (in *HostPathVolumeSource) DeepCopy() *HostPathVolumeSource { + if in == nil { + return nil + } + out := new(HostPathVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ISCSIPersistentVolumeSource) DeepCopyInto(out *ISCSIPersistentVolumeSource) { + *out = *in + if in.Portals != nil { + in, out := &in.Portals, &out.Portals + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + if *in == nil { + *out = nil + } else { + *out = new(SecretReference) + **out = **in + } + } + if in.InitiatorName != nil { + in, out := &in.InitiatorName, &out.InitiatorName + if *in == nil { + *out = nil + } else { + *out = new(string) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ISCSIPersistentVolumeSource. +func (in *ISCSIPersistentVolumeSource) DeepCopy() *ISCSIPersistentVolumeSource { + if in == nil { + return nil + } + out := new(ISCSIPersistentVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ISCSIVolumeSource) DeepCopyInto(out *ISCSIVolumeSource) { + *out = *in + if in.Portals != nil { + in, out := &in.Portals, &out.Portals + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + if *in == nil { + *out = nil + } else { + *out = new(LocalObjectReference) + **out = **in + } + } + if in.InitiatorName != nil { + in, out := &in.InitiatorName, &out.InitiatorName + if *in == nil { + *out = nil + } else { + *out = new(string) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ISCSIVolumeSource. +func (in *ISCSIVolumeSource) DeepCopy() *ISCSIVolumeSource { + if in == nil { + return nil + } + out := new(ISCSIVolumeSource) + 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 + if in.Mode != nil { + in, out := &in.Mode, &out.Mode + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeyToPath. +func (in *KeyToPath) DeepCopy() *KeyToPath { + if in == nil { + return nil + } + out := new(KeyToPath) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Lifecycle) DeepCopyInto(out *Lifecycle) { + *out = *in + if in.PostStart != nil { + in, out := &in.PostStart, &out.PostStart + if *in == nil { + *out = nil + } else { + *out = new(Handler) + (*in).DeepCopyInto(*out) + } + } + if in.PreStop != nil { + in, out := &in.PreStop, &out.PreStop + if *in == nil { + *out = nil + } else { + *out = new(Handler) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Lifecycle. +func (in *Lifecycle) DeepCopy() *Lifecycle { + if in == nil { + return nil + } + out := new(Lifecycle) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LimitRange) DeepCopyInto(out *LimitRange) { + *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 LimitRange. +func (in *LimitRange) DeepCopy() *LimitRange { + if in == nil { + return nil + } + out := new(LimitRange) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LimitRange) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LimitRangeItem) DeepCopyInto(out *LimitRangeItem) { + *out = *in + if in.Max != nil { + in, out := &in.Max, &out.Max + *out = make(ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + if in.Min != nil { + in, out := &in.Min, &out.Min + *out = make(ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + if in.Default != nil { + in, out := &in.Default, &out.Default + *out = make(ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + if in.DefaultRequest != nil { + in, out := &in.DefaultRequest, &out.DefaultRequest + *out = make(ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + if in.MaxLimitRequestRatio != nil { + in, out := &in.MaxLimitRequestRatio, &out.MaxLimitRequestRatio + *out = make(ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LimitRangeItem. +func (in *LimitRangeItem) DeepCopy() *LimitRangeItem { + if in == nil { + return nil + } + out := new(LimitRangeItem) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LimitRangeList) DeepCopyInto(out *LimitRangeList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]LimitRange, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LimitRangeList. +func (in *LimitRangeList) DeepCopy() *LimitRangeList { + if in == nil { + return nil + } + out := new(LimitRangeList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LimitRangeList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LimitRangeSpec) DeepCopyInto(out *LimitRangeSpec) { + *out = *in + if in.Limits != nil { + in, out := &in.Limits, &out.Limits + *out = make([]LimitRangeItem, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LimitRangeSpec. +func (in *LimitRangeSpec) DeepCopy() *LimitRangeSpec { + if in == nil { + return nil + } + out := new(LimitRangeSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *List) DeepCopyInto(out *List) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]runtime.Object, len(*in)) + for i := range *in { + if (*in)[i] == nil { + (*out)[i] = nil + } else { + (*out)[i] = (*in)[i].DeepCopyObject() + } + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new List. +func (in *List) DeepCopy() *List { + if in == nil { + return nil + } + out := new(List) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *List) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ListOptions) DeepCopyInto(out *ListOptions) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.LabelSelector == nil { + out.LabelSelector = nil + } else { + out.LabelSelector = in.LabelSelector.DeepCopySelector() + } + if in.FieldSelector == nil { + out.FieldSelector = nil + } else { + out.FieldSelector = in.FieldSelector.DeepCopySelector() + } + if in.TimeoutSeconds != nil { + in, out := &in.TimeoutSeconds, &out.TimeoutSeconds + if *in == nil { + *out = nil + } else { + *out = new(int64) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListOptions. +func (in *ListOptions) DeepCopy() *ListOptions { + if in == nil { + return nil + } + out := new(ListOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ListOptions) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LoadBalancerIngress) DeepCopyInto(out *LoadBalancerIngress) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoadBalancerIngress. +func (in *LoadBalancerIngress) DeepCopy() *LoadBalancerIngress { + if in == nil { + return nil + } + out := new(LoadBalancerIngress) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LoadBalancerStatus) DeepCopyInto(out *LoadBalancerStatus) { + *out = *in + if in.Ingress != nil { + in, out := &in.Ingress, &out.Ingress + *out = make([]LoadBalancerIngress, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoadBalancerStatus. +func (in *LoadBalancerStatus) DeepCopy() *LoadBalancerStatus { + if in == nil { + return nil + } + out := new(LoadBalancerStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LocalObjectReference) DeepCopyInto(out *LocalObjectReference) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalObjectReference. +func (in *LocalObjectReference) DeepCopy() *LocalObjectReference { + if in == nil { + return nil + } + out := new(LocalObjectReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LocalVolumeSource) DeepCopyInto(out *LocalVolumeSource) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalVolumeSource. +func (in *LocalVolumeSource) DeepCopy() *LocalVolumeSource { + if in == nil { + return nil + } + out := new(LocalVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NFSVolumeSource) DeepCopyInto(out *NFSVolumeSource) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NFSVolumeSource. +func (in *NFSVolumeSource) DeepCopy() *NFSVolumeSource { + if in == nil { + return nil + } + out := new(NFSVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Namespace) DeepCopyInto(out *Namespace) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Namespace. +func (in *Namespace) DeepCopy() *Namespace { + if in == nil { + return nil + } + out := new(Namespace) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Namespace) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespaceList) DeepCopyInto(out *NamespaceList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Namespace, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceList. +func (in *NamespaceList) DeepCopy() *NamespaceList { + if in == nil { + return nil + } + out := new(NamespaceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NamespaceList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespaceSpec) DeepCopyInto(out *NamespaceSpec) { + *out = *in + if in.Finalizers != nil { + in, out := &in.Finalizers, &out.Finalizers + *out = make([]FinalizerName, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceSpec. +func (in *NamespaceSpec) DeepCopy() *NamespaceSpec { + if in == nil { + return nil + } + out := new(NamespaceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespaceStatus) DeepCopyInto(out *NamespaceStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceStatus. +func (in *NamespaceStatus) DeepCopy() *NamespaceStatus { + if in == nil { + return nil + } + out := new(NamespaceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Node) DeepCopyInto(out *Node) { + *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 Node. +func (in *Node) DeepCopy() *Node { + if in == nil { + return nil + } + out := new(Node) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Node) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeAddress) DeepCopyInto(out *NodeAddress) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeAddress. +func (in *NodeAddress) DeepCopy() *NodeAddress { + if in == nil { + return nil + } + out := new(NodeAddress) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeAffinity) DeepCopyInto(out *NodeAffinity) { + *out = *in + if in.RequiredDuringSchedulingIgnoredDuringExecution != nil { + in, out := &in.RequiredDuringSchedulingIgnoredDuringExecution, &out.RequiredDuringSchedulingIgnoredDuringExecution + if *in == nil { + *out = nil + } else { + *out = new(NodeSelector) + (*in).DeepCopyInto(*out) + } + } + if in.PreferredDuringSchedulingIgnoredDuringExecution != nil { + in, out := &in.PreferredDuringSchedulingIgnoredDuringExecution, &out.PreferredDuringSchedulingIgnoredDuringExecution + *out = make([]PreferredSchedulingTerm, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeAffinity. +func (in *NodeAffinity) DeepCopy() *NodeAffinity { + if in == nil { + return nil + } + out := new(NodeAffinity) + 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 + in.LastHeartbeatTime.DeepCopyInto(&out.LastHeartbeatTime) + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeCondition. +func (in *NodeCondition) DeepCopy() *NodeCondition { + if in == nil { + return nil + } + out := new(NodeCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeConfigSource) DeepCopyInto(out *NodeConfigSource) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.ConfigMapRef != nil { + in, out := &in.ConfigMapRef, &out.ConfigMapRef + if *in == nil { + *out = nil + } else { + *out = new(ObjectReference) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeConfigSource. +func (in *NodeConfigSource) DeepCopy() *NodeConfigSource { + if in == nil { + return nil + } + out := new(NodeConfigSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NodeConfigSource) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeDaemonEndpoints) DeepCopyInto(out *NodeDaemonEndpoints) { + *out = *in + out.KubeletEndpoint = in.KubeletEndpoint + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeDaemonEndpoints. +func (in *NodeDaemonEndpoints) DeepCopy() *NodeDaemonEndpoints { + if in == nil { + return nil + } + out := new(NodeDaemonEndpoints) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeList) DeepCopyInto(out *NodeList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Node, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeList. +func (in *NodeList) DeepCopy() *NodeList { + if in == nil { + return nil + } + out := new(NodeList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NodeList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeProxyOptions) DeepCopyInto(out *NodeProxyOptions) { + *out = *in + out.TypeMeta = in.TypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeProxyOptions. +func (in *NodeProxyOptions) DeepCopy() *NodeProxyOptions { + if in == nil { + return nil + } + out := new(NodeProxyOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NodeProxyOptions) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeResources) DeepCopyInto(out *NodeResources) { + *out = *in + if in.Capacity != nil { + in, out := &in.Capacity, &out.Capacity + *out = make(ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeResources. +func (in *NodeResources) DeepCopy() *NodeResources { + if in == nil { + return nil + } + out := new(NodeResources) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeSelector) DeepCopyInto(out *NodeSelector) { + *out = *in + if in.NodeSelectorTerms != nil { + in, out := &in.NodeSelectorTerms, &out.NodeSelectorTerms + *out = make([]NodeSelectorTerm, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeSelector. +func (in *NodeSelector) DeepCopy() *NodeSelector { + if in == nil { + return nil + } + out := new(NodeSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeSelectorRequirement) DeepCopyInto(out *NodeSelectorRequirement) { + *out = *in + if in.Values != nil { + in, out := &in.Values, &out.Values + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeSelectorRequirement. +func (in *NodeSelectorRequirement) DeepCopy() *NodeSelectorRequirement { + if in == nil { + return nil + } + out := new(NodeSelectorRequirement) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeSelectorTerm) DeepCopyInto(out *NodeSelectorTerm) { + *out = *in + if in.MatchExpressions != nil { + in, out := &in.MatchExpressions, &out.MatchExpressions + *out = make([]NodeSelectorRequirement, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeSelectorTerm. +func (in *NodeSelectorTerm) DeepCopy() *NodeSelectorTerm { + if in == nil { + return nil + } + out := new(NodeSelectorTerm) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeSpec) DeepCopyInto(out *NodeSpec) { + *out = *in + if in.Taints != nil { + in, out := &in.Taints, &out.Taints + *out = make([]Taint, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ConfigSource != nil { + in, out := &in.ConfigSource, &out.ConfigSource + if *in == nil { + *out = nil + } else { + *out = new(NodeConfigSource) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeSpec. +func (in *NodeSpec) DeepCopy() *NodeSpec { + if in == nil { + return nil + } + out := new(NodeSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeStatus) DeepCopyInto(out *NodeStatus) { + *out = *in + if in.Capacity != nil { + in, out := &in.Capacity, &out.Capacity + *out = make(ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + if in.Allocatable != nil { + in, out := &in.Allocatable, &out.Allocatable + *out = make(ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]NodeCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Addresses != nil { + in, out := &in.Addresses, &out.Addresses + *out = make([]NodeAddress, len(*in)) + copy(*out, *in) + } + out.DaemonEndpoints = in.DaemonEndpoints + out.NodeInfo = in.NodeInfo + if in.Images != nil { + in, out := &in.Images, &out.Images + *out = make([]ContainerImage, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.VolumesInUse != nil { + in, out := &in.VolumesInUse, &out.VolumesInUse + *out = make([]UniqueVolumeName, len(*in)) + copy(*out, *in) + } + if in.VolumesAttached != nil { + in, out := &in.VolumesAttached, &out.VolumesAttached + *out = make([]AttachedVolume, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeStatus. +func (in *NodeStatus) DeepCopy() *NodeStatus { + if in == nil { + return nil + } + out := new(NodeStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeSystemInfo) DeepCopyInto(out *NodeSystemInfo) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeSystemInfo. +func (in *NodeSystemInfo) DeepCopy() *NodeSystemInfo { + if in == nil { + return nil + } + out := new(NodeSystemInfo) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectFieldSelector) DeepCopyInto(out *ObjectFieldSelector) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectFieldSelector. +func (in *ObjectFieldSelector) DeepCopy() *ObjectFieldSelector { + if in == nil { + return nil + } + out := new(ObjectFieldSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectMeta) DeepCopyInto(out *ObjectMeta) { + *out = *in + in.CreationTimestamp.DeepCopyInto(&out.CreationTimestamp) + if in.DeletionTimestamp != nil { + in, out := &in.DeletionTimestamp, &out.DeletionTimestamp + if *in == nil { + *out = nil + } else { + *out = new(v1.Time) + (*in).DeepCopyInto(*out) + } + } + if in.DeletionGracePeriodSeconds != nil { + in, out := &in.DeletionGracePeriodSeconds, &out.DeletionGracePeriodSeconds + if *in == nil { + *out = nil + } else { + *out = new(int64) + **out = **in + } + } + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.OwnerReferences != nil { + in, out := &in.OwnerReferences, &out.OwnerReferences + *out = make([]v1.OwnerReference, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Initializers != nil { + in, out := &in.Initializers, &out.Initializers + if *in == nil { + *out = nil + } else { + *out = new(v1.Initializers) + (*in).DeepCopyInto(*out) + } + } + if in.Finalizers != nil { + in, out := &in.Finalizers, &out.Finalizers + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectMeta. +func (in *ObjectMeta) DeepCopy() *ObjectMeta { + if in == nil { + return nil + } + out := new(ObjectMeta) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectReference) DeepCopyInto(out *ObjectReference) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectReference. +func (in *ObjectReference) DeepCopy() *ObjectReference { + if in == nil { + return nil + } + out := new(ObjectReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ObjectReference) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PersistentVolume) DeepCopyInto(out *PersistentVolume) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistentVolume. +func (in *PersistentVolume) DeepCopy() *PersistentVolume { + if in == nil { + return nil + } + out := new(PersistentVolume) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PersistentVolume) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PersistentVolumeClaim) DeepCopyInto(out *PersistentVolumeClaim) { + *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 PersistentVolumeClaim. +func (in *PersistentVolumeClaim) DeepCopy() *PersistentVolumeClaim { + if in == nil { + return nil + } + out := new(PersistentVolumeClaim) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PersistentVolumeClaim) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PersistentVolumeClaimCondition) DeepCopyInto(out *PersistentVolumeClaimCondition) { + *out = *in + in.LastProbeTime.DeepCopyInto(&out.LastProbeTime) + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistentVolumeClaimCondition. +func (in *PersistentVolumeClaimCondition) DeepCopy() *PersistentVolumeClaimCondition { + if in == nil { + return nil + } + out := new(PersistentVolumeClaimCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PersistentVolumeClaimList) DeepCopyInto(out *PersistentVolumeClaimList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]PersistentVolumeClaim, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistentVolumeClaimList. +func (in *PersistentVolumeClaimList) DeepCopy() *PersistentVolumeClaimList { + if in == nil { + return nil + } + out := new(PersistentVolumeClaimList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PersistentVolumeClaimList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PersistentVolumeClaimSpec) DeepCopyInto(out *PersistentVolumeClaimSpec) { + *out = *in + if in.AccessModes != nil { + in, out := &in.AccessModes, &out.AccessModes + *out = make([]PersistentVolumeAccessMode, len(*in)) + copy(*out, *in) + } + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + if *in == nil { + *out = nil + } else { + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + } + in.Resources.DeepCopyInto(&out.Resources) + if in.StorageClassName != nil { + in, out := &in.StorageClassName, &out.StorageClassName + if *in == nil { + *out = nil + } else { + *out = new(string) + **out = **in + } + } + if in.VolumeMode != nil { + in, out := &in.VolumeMode, &out.VolumeMode + if *in == nil { + *out = nil + } else { + *out = new(PersistentVolumeMode) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistentVolumeClaimSpec. +func (in *PersistentVolumeClaimSpec) DeepCopy() *PersistentVolumeClaimSpec { + if in == nil { + return nil + } + out := new(PersistentVolumeClaimSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PersistentVolumeClaimStatus) DeepCopyInto(out *PersistentVolumeClaimStatus) { + *out = *in + if in.AccessModes != nil { + in, out := &in.AccessModes, &out.AccessModes + *out = make([]PersistentVolumeAccessMode, len(*in)) + copy(*out, *in) + } + if in.Capacity != nil { + in, out := &in.Capacity, &out.Capacity + *out = make(ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]PersistentVolumeClaimCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistentVolumeClaimStatus. +func (in *PersistentVolumeClaimStatus) DeepCopy() *PersistentVolumeClaimStatus { + if in == nil { + return nil + } + out := new(PersistentVolumeClaimStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PersistentVolumeClaimVolumeSource) DeepCopyInto(out *PersistentVolumeClaimVolumeSource) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistentVolumeClaimVolumeSource. +func (in *PersistentVolumeClaimVolumeSource) DeepCopy() *PersistentVolumeClaimVolumeSource { + if in == nil { + return nil + } + out := new(PersistentVolumeClaimVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PersistentVolumeList) DeepCopyInto(out *PersistentVolumeList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]PersistentVolume, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistentVolumeList. +func (in *PersistentVolumeList) DeepCopy() *PersistentVolumeList { + if in == nil { + return nil + } + out := new(PersistentVolumeList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PersistentVolumeList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PersistentVolumeSource) DeepCopyInto(out *PersistentVolumeSource) { + *out = *in + if in.GCEPersistentDisk != nil { + in, out := &in.GCEPersistentDisk, &out.GCEPersistentDisk + if *in == nil { + *out = nil + } else { + *out = new(GCEPersistentDiskVolumeSource) + **out = **in + } + } + if in.AWSElasticBlockStore != nil { + in, out := &in.AWSElasticBlockStore, &out.AWSElasticBlockStore + if *in == nil { + *out = nil + } else { + *out = new(AWSElasticBlockStoreVolumeSource) + **out = **in + } + } + if in.HostPath != nil { + in, out := &in.HostPath, &out.HostPath + if *in == nil { + *out = nil + } else { + *out = new(HostPathVolumeSource) + (*in).DeepCopyInto(*out) + } + } + if in.Glusterfs != nil { + in, out := &in.Glusterfs, &out.Glusterfs + if *in == nil { + *out = nil + } else { + *out = new(GlusterfsVolumeSource) + **out = **in + } + } + if in.NFS != nil { + in, out := &in.NFS, &out.NFS + if *in == nil { + *out = nil + } else { + *out = new(NFSVolumeSource) + **out = **in + } + } + if in.RBD != nil { + in, out := &in.RBD, &out.RBD + if *in == nil { + *out = nil + } else { + *out = new(RBDPersistentVolumeSource) + (*in).DeepCopyInto(*out) + } + } + if in.Quobyte != nil { + in, out := &in.Quobyte, &out.Quobyte + if *in == nil { + *out = nil + } else { + *out = new(QuobyteVolumeSource) + **out = **in + } + } + if in.ISCSI != nil { + in, out := &in.ISCSI, &out.ISCSI + if *in == nil { + *out = nil + } else { + *out = new(ISCSIPersistentVolumeSource) + (*in).DeepCopyInto(*out) + } + } + if in.FlexVolume != nil { + in, out := &in.FlexVolume, &out.FlexVolume + if *in == nil { + *out = nil + } else { + *out = new(FlexPersistentVolumeSource) + (*in).DeepCopyInto(*out) + } + } + if in.Cinder != nil { + in, out := &in.Cinder, &out.Cinder + if *in == nil { + *out = nil + } else { + *out = new(CinderVolumeSource) + **out = **in + } + } + if in.CephFS != nil { + in, out := &in.CephFS, &out.CephFS + if *in == nil { + *out = nil + } else { + *out = new(CephFSPersistentVolumeSource) + (*in).DeepCopyInto(*out) + } + } + if in.FC != nil { + in, out := &in.FC, &out.FC + if *in == nil { + *out = nil + } else { + *out = new(FCVolumeSource) + (*in).DeepCopyInto(*out) + } + } + if in.Flocker != nil { + in, out := &in.Flocker, &out.Flocker + if *in == nil { + *out = nil + } else { + *out = new(FlockerVolumeSource) + **out = **in + } + } + if in.AzureFile != nil { + in, out := &in.AzureFile, &out.AzureFile + if *in == nil { + *out = nil + } else { + *out = new(AzureFilePersistentVolumeSource) + (*in).DeepCopyInto(*out) + } + } + if in.VsphereVolume != nil { + in, out := &in.VsphereVolume, &out.VsphereVolume + if *in == nil { + *out = nil + } else { + *out = new(VsphereVirtualDiskVolumeSource) + **out = **in + } + } + if in.AzureDisk != nil { + in, out := &in.AzureDisk, &out.AzureDisk + if *in == nil { + *out = nil + } else { + *out = new(AzureDiskVolumeSource) + (*in).DeepCopyInto(*out) + } + } + if in.PhotonPersistentDisk != nil { + in, out := &in.PhotonPersistentDisk, &out.PhotonPersistentDisk + if *in == nil { + *out = nil + } else { + *out = new(PhotonPersistentDiskVolumeSource) + **out = **in + } + } + if in.PortworxVolume != nil { + in, out := &in.PortworxVolume, &out.PortworxVolume + if *in == nil { + *out = nil + } else { + *out = new(PortworxVolumeSource) + **out = **in + } + } + if in.ScaleIO != nil { + in, out := &in.ScaleIO, &out.ScaleIO + if *in == nil { + *out = nil + } else { + *out = new(ScaleIOPersistentVolumeSource) + (*in).DeepCopyInto(*out) + } + } + if in.Local != nil { + in, out := &in.Local, &out.Local + if *in == nil { + *out = nil + } else { + *out = new(LocalVolumeSource) + **out = **in + } + } + if in.StorageOS != nil { + in, out := &in.StorageOS, &out.StorageOS + if *in == nil { + *out = nil + } else { + *out = new(StorageOSPersistentVolumeSource) + (*in).DeepCopyInto(*out) + } + } + if in.CSI != nil { + in, out := &in.CSI, &out.CSI + if *in == nil { + *out = nil + } else { + *out = new(CSIPersistentVolumeSource) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistentVolumeSource. +func (in *PersistentVolumeSource) DeepCopy() *PersistentVolumeSource { + if in == nil { + return nil + } + out := new(PersistentVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PersistentVolumeSpec) DeepCopyInto(out *PersistentVolumeSpec) { + *out = *in + if in.Capacity != nil { + in, out := &in.Capacity, &out.Capacity + *out = make(ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + in.PersistentVolumeSource.DeepCopyInto(&out.PersistentVolumeSource) + if in.AccessModes != nil { + in, out := &in.AccessModes, &out.AccessModes + *out = make([]PersistentVolumeAccessMode, len(*in)) + copy(*out, *in) + } + if in.ClaimRef != nil { + in, out := &in.ClaimRef, &out.ClaimRef + if *in == nil { + *out = nil + } else { + *out = new(ObjectReference) + **out = **in + } + } + if in.MountOptions != nil { + in, out := &in.MountOptions, &out.MountOptions + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.VolumeMode != nil { + in, out := &in.VolumeMode, &out.VolumeMode + if *in == nil { + *out = nil + } else { + *out = new(PersistentVolumeMode) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistentVolumeSpec. +func (in *PersistentVolumeSpec) DeepCopy() *PersistentVolumeSpec { + if in == nil { + return nil + } + out := new(PersistentVolumeSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PersistentVolumeStatus) DeepCopyInto(out *PersistentVolumeStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistentVolumeStatus. +func (in *PersistentVolumeStatus) DeepCopy() *PersistentVolumeStatus { + if in == nil { + return nil + } + out := new(PersistentVolumeStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PhotonPersistentDiskVolumeSource) DeepCopyInto(out *PhotonPersistentDiskVolumeSource) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PhotonPersistentDiskVolumeSource. +func (in *PhotonPersistentDiskVolumeSource) DeepCopy() *PhotonPersistentDiskVolumeSource { + if in == nil { + return nil + } + out := new(PhotonPersistentDiskVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Pod) DeepCopyInto(out *Pod) { + *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 Pod. +func (in *Pod) DeepCopy() *Pod { + if in == nil { + return nil + } + out := new(Pod) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Pod) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodAffinity) DeepCopyInto(out *PodAffinity) { + *out = *in + if in.RequiredDuringSchedulingIgnoredDuringExecution != nil { + in, out := &in.RequiredDuringSchedulingIgnoredDuringExecution, &out.RequiredDuringSchedulingIgnoredDuringExecution + *out = make([]PodAffinityTerm, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.PreferredDuringSchedulingIgnoredDuringExecution != nil { + in, out := &in.PreferredDuringSchedulingIgnoredDuringExecution, &out.PreferredDuringSchedulingIgnoredDuringExecution + *out = make([]WeightedPodAffinityTerm, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodAffinity. +func (in *PodAffinity) DeepCopy() *PodAffinity { + if in == nil { + return nil + } + out := new(PodAffinity) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodAffinityTerm) DeepCopyInto(out *PodAffinityTerm) { + *out = *in + if in.LabelSelector != nil { + in, out := &in.LabelSelector, &out.LabelSelector + if *in == nil { + *out = nil + } else { + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + } + if in.Namespaces != nil { + in, out := &in.Namespaces, &out.Namespaces + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodAffinityTerm. +func (in *PodAffinityTerm) DeepCopy() *PodAffinityTerm { + if in == nil { + return nil + } + out := new(PodAffinityTerm) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodAntiAffinity) DeepCopyInto(out *PodAntiAffinity) { + *out = *in + if in.RequiredDuringSchedulingIgnoredDuringExecution != nil { + in, out := &in.RequiredDuringSchedulingIgnoredDuringExecution, &out.RequiredDuringSchedulingIgnoredDuringExecution + *out = make([]PodAffinityTerm, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.PreferredDuringSchedulingIgnoredDuringExecution != nil { + in, out := &in.PreferredDuringSchedulingIgnoredDuringExecution, &out.PreferredDuringSchedulingIgnoredDuringExecution + *out = make([]WeightedPodAffinityTerm, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodAntiAffinity. +func (in *PodAntiAffinity) DeepCopy() *PodAntiAffinity { + if in == nil { + return nil + } + out := new(PodAntiAffinity) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodAttachOptions) DeepCopyInto(out *PodAttachOptions) { + *out = *in + out.TypeMeta = in.TypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodAttachOptions. +func (in *PodAttachOptions) DeepCopy() *PodAttachOptions { + if in == nil { + return nil + } + out := new(PodAttachOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PodAttachOptions) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodCondition) DeepCopyInto(out *PodCondition) { + *out = *in + in.LastProbeTime.DeepCopyInto(&out.LastProbeTime) + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodCondition. +func (in *PodCondition) DeepCopy() *PodCondition { + if in == nil { + return nil + } + out := new(PodCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodDNSConfig) DeepCopyInto(out *PodDNSConfig) { + *out = *in + if in.Nameservers != nil { + in, out := &in.Nameservers, &out.Nameservers + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Searches != nil { + in, out := &in.Searches, &out.Searches + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Options != nil { + in, out := &in.Options, &out.Options + *out = make([]PodDNSConfigOption, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodDNSConfig. +func (in *PodDNSConfig) DeepCopy() *PodDNSConfig { + if in == nil { + return nil + } + out := new(PodDNSConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodDNSConfigOption) DeepCopyInto(out *PodDNSConfigOption) { + *out = *in + if in.Value != nil { + in, out := &in.Value, &out.Value + if *in == nil { + *out = nil + } else { + *out = new(string) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodDNSConfigOption. +func (in *PodDNSConfigOption) DeepCopy() *PodDNSConfigOption { + if in == nil { + return nil + } + out := new(PodDNSConfigOption) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodExecOptions) DeepCopyInto(out *PodExecOptions) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Command != nil { + in, out := &in.Command, &out.Command + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodExecOptions. +func (in *PodExecOptions) DeepCopy() *PodExecOptions { + if in == nil { + return nil + } + out := new(PodExecOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PodExecOptions) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodList) DeepCopyInto(out *PodList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Pod, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodList. +func (in *PodList) DeepCopy() *PodList { + if in == nil { + return nil + } + out := new(PodList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PodList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodLogOptions) DeepCopyInto(out *PodLogOptions) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.SinceSeconds != nil { + in, out := &in.SinceSeconds, &out.SinceSeconds + if *in == nil { + *out = nil + } else { + *out = new(int64) + **out = **in + } + } + if in.SinceTime != nil { + in, out := &in.SinceTime, &out.SinceTime + if *in == nil { + *out = nil + } else { + *out = new(v1.Time) + (*in).DeepCopyInto(*out) + } + } + if in.TailLines != nil { + in, out := &in.TailLines, &out.TailLines + if *in == nil { + *out = nil + } else { + *out = new(int64) + **out = **in + } + } + if in.LimitBytes != nil { + in, out := &in.LimitBytes, &out.LimitBytes + if *in == nil { + *out = nil + } else { + *out = new(int64) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodLogOptions. +func (in *PodLogOptions) DeepCopy() *PodLogOptions { + if in == nil { + return nil + } + out := new(PodLogOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PodLogOptions) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodPortForwardOptions) DeepCopyInto(out *PodPortForwardOptions) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Ports != nil { + in, out := &in.Ports, &out.Ports + *out = make([]int32, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodPortForwardOptions. +func (in *PodPortForwardOptions) DeepCopy() *PodPortForwardOptions { + if in == nil { + return nil + } + out := new(PodPortForwardOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PodPortForwardOptions) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodProxyOptions) DeepCopyInto(out *PodProxyOptions) { + *out = *in + out.TypeMeta = in.TypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodProxyOptions. +func (in *PodProxyOptions) DeepCopy() *PodProxyOptions { + if in == nil { + return nil + } + out := new(PodProxyOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PodProxyOptions) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// 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 + if in.SELinuxOptions != nil { + in, out := &in.SELinuxOptions, &out.SELinuxOptions + if *in == nil { + *out = nil + } else { + *out = new(SELinuxOptions) + **out = **in + } + } + if in.RunAsUser != nil { + in, out := &in.RunAsUser, &out.RunAsUser + if *in == nil { + *out = nil + } else { + *out = new(int64) + **out = **in + } + } + if in.RunAsNonRoot != nil { + in, out := &in.RunAsNonRoot, &out.RunAsNonRoot + if *in == nil { + *out = nil + } else { + *out = new(bool) + **out = **in + } + } + if in.SupplementalGroups != nil { + in, out := &in.SupplementalGroups, &out.SupplementalGroups + *out = make([]int64, len(*in)) + copy(*out, *in) + } + if in.FSGroup != nil { + in, out := &in.FSGroup, &out.FSGroup + if *in == nil { + *out = nil + } else { + *out = new(int64) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSecurityContext. +func (in *PodSecurityContext) DeepCopy() *PodSecurityContext { + if in == nil { + return nil + } + out := new(PodSecurityContext) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodSignature) DeepCopyInto(out *PodSignature) { + *out = *in + if in.PodController != nil { + in, out := &in.PodController, &out.PodController + if *in == nil { + *out = nil + } else { + *out = new(v1.OwnerReference) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSignature. +func (in *PodSignature) DeepCopy() *PodSignature { + if in == nil { + return nil + } + out := new(PodSignature) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodSpec) DeepCopyInto(out *PodSpec) { + *out = *in + if in.Volumes != nil { + in, out := &in.Volumes, &out.Volumes + *out = make([]Volume, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.InitContainers != nil { + in, out := &in.InitContainers, &out.InitContainers + *out = make([]Container, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Containers != nil { + in, out := &in.Containers, &out.Containers + *out = make([]Container, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.TerminationGracePeriodSeconds != nil { + in, out := &in.TerminationGracePeriodSeconds, &out.TerminationGracePeriodSeconds + if *in == nil { + *out = nil + } else { + *out = new(int64) + **out = **in + } + } + if in.ActiveDeadlineSeconds != nil { + in, out := &in.ActiveDeadlineSeconds, &out.ActiveDeadlineSeconds + if *in == nil { + *out = nil + } else { + *out = new(int64) + **out = **in + } + } + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.AutomountServiceAccountToken != nil { + in, out := &in.AutomountServiceAccountToken, &out.AutomountServiceAccountToken + if *in == nil { + *out = nil + } else { + *out = new(bool) + **out = **in + } + } + if in.SecurityContext != nil { + in, out := &in.SecurityContext, &out.SecurityContext + if *in == nil { + *out = nil + } else { + *out = new(PodSecurityContext) + (*in).DeepCopyInto(*out) + } + } + if in.ImagePullSecrets != nil { + in, out := &in.ImagePullSecrets, &out.ImagePullSecrets + *out = make([]LocalObjectReference, len(*in)) + copy(*out, *in) + } + if in.Affinity != nil { + in, out := &in.Affinity, &out.Affinity + if *in == nil { + *out = nil + } else { + *out = new(Affinity) + (*in).DeepCopyInto(*out) + } + } + if in.Tolerations != nil { + in, out := &in.Tolerations, &out.Tolerations + *out = make([]Toleration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.HostAliases != nil { + in, out := &in.HostAliases, &out.HostAliases + *out = make([]HostAlias, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Priority != nil { + in, out := &in.Priority, &out.Priority + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + if in.DNSConfig != nil { + in, out := &in.DNSConfig, &out.DNSConfig + if *in == nil { + *out = nil + } else { + *out = new(PodDNSConfig) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSpec. +func (in *PodSpec) DeepCopy() *PodSpec { + if in == nil { + return nil + } + out := new(PodSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodStatus) DeepCopyInto(out *PodStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]PodCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.StartTime != nil { + in, out := &in.StartTime, &out.StartTime + if *in == nil { + *out = nil + } else { + *out = new(v1.Time) + (*in).DeepCopyInto(*out) + } + } + if in.InitContainerStatuses != nil { + in, out := &in.InitContainerStatuses, &out.InitContainerStatuses + *out = make([]ContainerStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ContainerStatuses != nil { + in, out := &in.ContainerStatuses, &out.ContainerStatuses + *out = make([]ContainerStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodStatus. +func (in *PodStatus) DeepCopy() *PodStatus { + if in == nil { + return nil + } + out := new(PodStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodStatusResult) DeepCopyInto(out *PodStatusResult) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodStatusResult. +func (in *PodStatusResult) DeepCopy() *PodStatusResult { + if in == nil { + return nil + } + out := new(PodStatusResult) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PodStatusResult) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodTemplate) DeepCopyInto(out *PodTemplate) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Template.DeepCopyInto(&out.Template) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodTemplate. +func (in *PodTemplate) DeepCopy() *PodTemplate { + if in == nil { + return nil + } + out := new(PodTemplate) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PodTemplate) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodTemplateList) DeepCopyInto(out *PodTemplateList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]PodTemplate, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodTemplateList. +func (in *PodTemplateList) DeepCopy() *PodTemplateList { + if in == nil { + return nil + } + out := new(PodTemplateList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PodTemplateList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodTemplateSpec) DeepCopyInto(out *PodTemplateSpec) { + *out = *in + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodTemplateSpec. +func (in *PodTemplateSpec) DeepCopy() *PodTemplateSpec { + if in == nil { + return nil + } + out := new(PodTemplateSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PortworxVolumeSource) DeepCopyInto(out *PortworxVolumeSource) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PortworxVolumeSource. +func (in *PortworxVolumeSource) DeepCopy() *PortworxVolumeSource { + if in == nil { + return nil + } + out := new(PortworxVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Preconditions) DeepCopyInto(out *Preconditions) { + *out = *in + if in.UID != nil { + in, out := &in.UID, &out.UID + if *in == nil { + *out = nil + } else { + *out = new(types.UID) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Preconditions. +func (in *Preconditions) DeepCopy() *Preconditions { + if in == nil { + return nil + } + out := new(Preconditions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PreferAvoidPodsEntry) DeepCopyInto(out *PreferAvoidPodsEntry) { + *out = *in + in.PodSignature.DeepCopyInto(&out.PodSignature) + in.EvictionTime.DeepCopyInto(&out.EvictionTime) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PreferAvoidPodsEntry. +func (in *PreferAvoidPodsEntry) DeepCopy() *PreferAvoidPodsEntry { + if in == nil { + return nil + } + out := new(PreferAvoidPodsEntry) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PreferredSchedulingTerm) DeepCopyInto(out *PreferredSchedulingTerm) { + *out = *in + in.Preference.DeepCopyInto(&out.Preference) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PreferredSchedulingTerm. +func (in *PreferredSchedulingTerm) DeepCopy() *PreferredSchedulingTerm { + if in == nil { + return nil + } + out := new(PreferredSchedulingTerm) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Probe) DeepCopyInto(out *Probe) { + *out = *in + in.Handler.DeepCopyInto(&out.Handler) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Probe. +func (in *Probe) DeepCopy() *Probe { + if in == nil { + return nil + } + out := new(Probe) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProjectedVolumeSource) DeepCopyInto(out *ProjectedVolumeSource) { + *out = *in + if in.Sources != nil { + in, out := &in.Sources, &out.Sources + *out = make([]VolumeProjection, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.DefaultMode != nil { + in, out := &in.DefaultMode, &out.DefaultMode + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProjectedVolumeSource. +func (in *ProjectedVolumeSource) DeepCopy() *ProjectedVolumeSource { + if in == nil { + return nil + } + out := new(ProjectedVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *QuobyteVolumeSource) DeepCopyInto(out *QuobyteVolumeSource) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new QuobyteVolumeSource. +func (in *QuobyteVolumeSource) DeepCopy() *QuobyteVolumeSource { + if in == nil { + return nil + } + out := new(QuobyteVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RBDPersistentVolumeSource) DeepCopyInto(out *RBDPersistentVolumeSource) { + *out = *in + if in.CephMonitors != nil { + in, out := &in.CephMonitors, &out.CephMonitors + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + if *in == nil { + *out = nil + } else { + *out = new(SecretReference) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RBDPersistentVolumeSource. +func (in *RBDPersistentVolumeSource) DeepCopy() *RBDPersistentVolumeSource { + if in == nil { + return nil + } + out := new(RBDPersistentVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RBDVolumeSource) DeepCopyInto(out *RBDVolumeSource) { + *out = *in + if in.CephMonitors != nil { + in, out := &in.CephMonitors, &out.CephMonitors + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + if *in == nil { + *out = nil + } else { + *out = new(LocalObjectReference) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RBDVolumeSource. +func (in *RBDVolumeSource) DeepCopy() *RBDVolumeSource { + if in == nil { + return nil + } + out := new(RBDVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RangeAllocation) DeepCopyInto(out *RangeAllocation) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.Data != nil { + in, out := &in.Data, &out.Data + *out = make([]byte, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RangeAllocation. +func (in *RangeAllocation) DeepCopy() *RangeAllocation { + if in == nil { + return nil + } + out := new(RangeAllocation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RangeAllocation) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplicationController) DeepCopyInto(out *ReplicationController) { + *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 ReplicationController. +func (in *ReplicationController) DeepCopy() *ReplicationController { + if in == nil { + return nil + } + out := new(ReplicationController) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ReplicationController) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplicationControllerCondition) DeepCopyInto(out *ReplicationControllerCondition) { + *out = *in + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicationControllerCondition. +func (in *ReplicationControllerCondition) DeepCopy() *ReplicationControllerCondition { + if in == nil { + return nil + } + out := new(ReplicationControllerCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplicationControllerList) DeepCopyInto(out *ReplicationControllerList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ReplicationController, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicationControllerList. +func (in *ReplicationControllerList) DeepCopy() *ReplicationControllerList { + if in == nil { + return nil + } + out := new(ReplicationControllerList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ReplicationControllerList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplicationControllerSpec) DeepCopyInto(out *ReplicationControllerSpec) { + *out = *in + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Template != nil { + in, out := &in.Template, &out.Template + if *in == nil { + *out = nil + } else { + *out = new(PodTemplateSpec) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicationControllerSpec. +func (in *ReplicationControllerSpec) DeepCopy() *ReplicationControllerSpec { + if in == nil { + return nil + } + out := new(ReplicationControllerSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplicationControllerStatus) DeepCopyInto(out *ReplicationControllerStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]ReplicationControllerCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicationControllerStatus. +func (in *ReplicationControllerStatus) DeepCopy() *ReplicationControllerStatus { + if in == nil { + return nil + } + out := new(ReplicationControllerStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceFieldSelector) DeepCopyInto(out *ResourceFieldSelector) { + *out = *in + out.Divisor = in.Divisor.DeepCopy() + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceFieldSelector. +func (in *ResourceFieldSelector) DeepCopy() *ResourceFieldSelector { + if in == nil { + return nil + } + out := new(ResourceFieldSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceQuota) DeepCopyInto(out *ResourceQuota) { + *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 ResourceQuota. +func (in *ResourceQuota) DeepCopy() *ResourceQuota { + if in == nil { + return nil + } + out := new(ResourceQuota) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ResourceQuota) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceQuotaList) DeepCopyInto(out *ResourceQuotaList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ResourceQuota, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceQuotaList. +func (in *ResourceQuotaList) DeepCopy() *ResourceQuotaList { + if in == nil { + return nil + } + out := new(ResourceQuotaList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ResourceQuotaList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceQuotaSpec) DeepCopyInto(out *ResourceQuotaSpec) { + *out = *in + if in.Hard != nil { + in, out := &in.Hard, &out.Hard + *out = make(ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + if in.Scopes != nil { + in, out := &in.Scopes, &out.Scopes + *out = make([]ResourceQuotaScope, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceQuotaSpec. +func (in *ResourceQuotaSpec) DeepCopy() *ResourceQuotaSpec { + if in == nil { + return nil + } + out := new(ResourceQuotaSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceQuotaStatus) DeepCopyInto(out *ResourceQuotaStatus) { + *out = *in + if in.Hard != nil { + in, out := &in.Hard, &out.Hard + *out = make(ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + if in.Used != nil { + in, out := &in.Used, &out.Used + *out = make(ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceQuotaStatus. +func (in *ResourceQuotaStatus) DeepCopy() *ResourceQuotaStatus { + if in == nil { + return nil + } + out := new(ResourceQuotaStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceRequirements) DeepCopyInto(out *ResourceRequirements) { + *out = *in + if in.Limits != nil { + in, out := &in.Limits, &out.Limits + *out = make(ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + if in.Requests != nil { + in, out := &in.Requests, &out.Requests + *out = make(ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceRequirements. +func (in *ResourceRequirements) DeepCopy() *ResourceRequirements { + if in == nil { + return nil + } + out := new(ResourceRequirements) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SELinuxOptions) DeepCopyInto(out *SELinuxOptions) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SELinuxOptions. +func (in *SELinuxOptions) DeepCopy() *SELinuxOptions { + if in == nil { + return nil + } + out := new(SELinuxOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ScaleIOPersistentVolumeSource) DeepCopyInto(out *ScaleIOPersistentVolumeSource) { + *out = *in + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + if *in == nil { + *out = nil + } else { + *out = new(SecretReference) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScaleIOPersistentVolumeSource. +func (in *ScaleIOPersistentVolumeSource) DeepCopy() *ScaleIOPersistentVolumeSource { + if in == nil { + return nil + } + out := new(ScaleIOPersistentVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ScaleIOVolumeSource) DeepCopyInto(out *ScaleIOVolumeSource) { + *out = *in + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + if *in == nil { + *out = nil + } else { + *out = new(LocalObjectReference) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScaleIOVolumeSource. +func (in *ScaleIOVolumeSource) DeepCopy() *ScaleIOVolumeSource { + if in == nil { + return nil + } + out := new(ScaleIOVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Secret) DeepCopyInto(out *Secret) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.Data != nil { + in, out := &in.Data, &out.Data + *out = make(map[string][]byte, len(*in)) + for key, val := range *in { + if val == nil { + (*out)[key] = nil + } else { + (*out)[key] = make([]byte, len(val)) + copy((*out)[key], val) + } + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Secret. +func (in *Secret) DeepCopy() *Secret { + if in == nil { + return nil + } + out := new(Secret) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Secret) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretEnvSource) DeepCopyInto(out *SecretEnvSource) { + *out = *in + out.LocalObjectReference = in.LocalObjectReference + if in.Optional != nil { + in, out := &in.Optional, &out.Optional + if *in == nil { + *out = nil + } else { + *out = new(bool) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretEnvSource. +func (in *SecretEnvSource) DeepCopy() *SecretEnvSource { + if in == nil { + return nil + } + out := new(SecretEnvSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretKeySelector) DeepCopyInto(out *SecretKeySelector) { + *out = *in + out.LocalObjectReference = in.LocalObjectReference + if in.Optional != nil { + in, out := &in.Optional, &out.Optional + if *in == nil { + *out = nil + } else { + *out = new(bool) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretKeySelector. +func (in *SecretKeySelector) DeepCopy() *SecretKeySelector { + if in == nil { + return nil + } + out := new(SecretKeySelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretList) DeepCopyInto(out *SecretList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Secret, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretList. +func (in *SecretList) DeepCopy() *SecretList { + if in == nil { + return nil + } + out := new(SecretList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SecretList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretProjection) DeepCopyInto(out *SecretProjection) { + *out = *in + out.LocalObjectReference = in.LocalObjectReference + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]KeyToPath, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Optional != nil { + in, out := &in.Optional, &out.Optional + if *in == nil { + *out = nil + } else { + *out = new(bool) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretProjection. +func (in *SecretProjection) DeepCopy() *SecretProjection { + if in == nil { + return nil + } + out := new(SecretProjection) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretReference) DeepCopyInto(out *SecretReference) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretReference. +func (in *SecretReference) DeepCopy() *SecretReference { + if in == nil { + return nil + } + out := new(SecretReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretVolumeSource) DeepCopyInto(out *SecretVolumeSource) { + *out = *in + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]KeyToPath, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.DefaultMode != nil { + in, out := &in.DefaultMode, &out.DefaultMode + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + if in.Optional != nil { + in, out := &in.Optional, &out.Optional + if *in == nil { + *out = nil + } else { + *out = new(bool) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretVolumeSource. +func (in *SecretVolumeSource) DeepCopy() *SecretVolumeSource { + if in == nil { + return nil + } + out := new(SecretVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecurityContext) DeepCopyInto(out *SecurityContext) { + *out = *in + if in.Capabilities != nil { + in, out := &in.Capabilities, &out.Capabilities + if *in == nil { + *out = nil + } else { + *out = new(Capabilities) + (*in).DeepCopyInto(*out) + } + } + if in.Privileged != nil { + in, out := &in.Privileged, &out.Privileged + if *in == nil { + *out = nil + } else { + *out = new(bool) + **out = **in + } + } + if in.SELinuxOptions != nil { + in, out := &in.SELinuxOptions, &out.SELinuxOptions + if *in == nil { + *out = nil + } else { + *out = new(SELinuxOptions) + **out = **in + } + } + if in.RunAsUser != nil { + in, out := &in.RunAsUser, &out.RunAsUser + if *in == nil { + *out = nil + } else { + *out = new(int64) + **out = **in + } + } + if in.RunAsNonRoot != nil { + in, out := &in.RunAsNonRoot, &out.RunAsNonRoot + if *in == nil { + *out = nil + } else { + *out = new(bool) + **out = **in + } + } + if in.ReadOnlyRootFilesystem != nil { + in, out := &in.ReadOnlyRootFilesystem, &out.ReadOnlyRootFilesystem + if *in == nil { + *out = nil + } else { + *out = new(bool) + **out = **in + } + } + if in.AllowPrivilegeEscalation != nil { + in, out := &in.AllowPrivilegeEscalation, &out.AllowPrivilegeEscalation + if *in == nil { + *out = nil + } else { + *out = new(bool) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecurityContext. +func (in *SecurityContext) DeepCopy() *SecurityContext { + if in == nil { + return nil + } + out := new(SecurityContext) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SerializedReference) DeepCopyInto(out *SerializedReference) { + *out = *in + out.TypeMeta = in.TypeMeta + out.Reference = in.Reference + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SerializedReference. +func (in *SerializedReference) DeepCopy() *SerializedReference { + if in == nil { + return nil + } + out := new(SerializedReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SerializedReference) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Service) DeepCopyInto(out *Service) { + *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 Service. +func (in *Service) DeepCopy() *Service { + if in == nil { + return nil + } + out := new(Service) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Service) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceAccount) DeepCopyInto(out *ServiceAccount) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.Secrets != nil { + in, out := &in.Secrets, &out.Secrets + *out = make([]ObjectReference, len(*in)) + copy(*out, *in) + } + if in.ImagePullSecrets != nil { + in, out := &in.ImagePullSecrets, &out.ImagePullSecrets + *out = make([]LocalObjectReference, len(*in)) + copy(*out, *in) + } + if in.AutomountServiceAccountToken != nil { + in, out := &in.AutomountServiceAccountToken, &out.AutomountServiceAccountToken + if *in == nil { + *out = nil + } else { + *out = new(bool) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccount. +func (in *ServiceAccount) DeepCopy() *ServiceAccount { + if in == nil { + return nil + } + out := new(ServiceAccount) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ServiceAccount) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceAccountList) DeepCopyInto(out *ServiceAccountList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ServiceAccount, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountList. +func (in *ServiceAccountList) DeepCopy() *ServiceAccountList { + if in == nil { + return nil + } + out := new(ServiceAccountList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ServiceAccountList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceList) DeepCopyInto(out *ServiceList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Service, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceList. +func (in *ServiceList) DeepCopy() *ServiceList { + if in == nil { + return nil + } + out := new(ServiceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ServiceList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServicePort) DeepCopyInto(out *ServicePort) { + *out = *in + out.TargetPort = in.TargetPort + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServicePort. +func (in *ServicePort) DeepCopy() *ServicePort { + if in == nil { + return nil + } + out := new(ServicePort) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceProxyOptions) DeepCopyInto(out *ServiceProxyOptions) { + *out = *in + out.TypeMeta = in.TypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceProxyOptions. +func (in *ServiceProxyOptions) DeepCopy() *ServiceProxyOptions { + if in == nil { + return nil + } + out := new(ServiceProxyOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ServiceProxyOptions) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) { + *out = *in + if in.Ports != nil { + in, out := &in.Ports, &out.Ports + *out = make([]ServicePort, len(*in)) + copy(*out, *in) + } + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.ExternalIPs != nil { + in, out := &in.ExternalIPs, &out.ExternalIPs + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SessionAffinityConfig != nil { + in, out := &in.SessionAffinityConfig, &out.SessionAffinityConfig + if *in == nil { + *out = nil + } else { + *out = new(SessionAffinityConfig) + (*in).DeepCopyInto(*out) + } + } + if in.LoadBalancerSourceRanges != nil { + in, out := &in.LoadBalancerSourceRanges, &out.LoadBalancerSourceRanges + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceSpec. +func (in *ServiceSpec) DeepCopy() *ServiceSpec { + if in == nil { + return nil + } + out := new(ServiceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceStatus) DeepCopyInto(out *ServiceStatus) { + *out = *in + in.LoadBalancer.DeepCopyInto(&out.LoadBalancer) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceStatus. +func (in *ServiceStatus) DeepCopy() *ServiceStatus { + if in == nil { + return nil + } + out := new(ServiceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SessionAffinityConfig) DeepCopyInto(out *SessionAffinityConfig) { + *out = *in + if in.ClientIP != nil { + in, out := &in.ClientIP, &out.ClientIP + if *in == nil { + *out = nil + } else { + *out = new(ClientIPConfig) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SessionAffinityConfig. +func (in *SessionAffinityConfig) DeepCopy() *SessionAffinityConfig { + if in == nil { + return nil + } + out := new(SessionAffinityConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StorageOSPersistentVolumeSource) DeepCopyInto(out *StorageOSPersistentVolumeSource) { + *out = *in + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + if *in == nil { + *out = nil + } else { + *out = new(ObjectReference) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StorageOSPersistentVolumeSource. +func (in *StorageOSPersistentVolumeSource) DeepCopy() *StorageOSPersistentVolumeSource { + if in == nil { + return nil + } + out := new(StorageOSPersistentVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StorageOSVolumeSource) DeepCopyInto(out *StorageOSVolumeSource) { + *out = *in + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + if *in == nil { + *out = nil + } else { + *out = new(LocalObjectReference) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StorageOSVolumeSource. +func (in *StorageOSVolumeSource) DeepCopy() *StorageOSVolumeSource { + if in == nil { + return nil + } + out := new(StorageOSVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Sysctl) DeepCopyInto(out *Sysctl) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Sysctl. +func (in *Sysctl) DeepCopy() *Sysctl { + if in == nil { + return nil + } + out := new(Sysctl) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TCPSocketAction) DeepCopyInto(out *TCPSocketAction) { + *out = *in + out.Port = in.Port + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPSocketAction. +func (in *TCPSocketAction) DeepCopy() *TCPSocketAction { + if in == nil { + return nil + } + out := new(TCPSocketAction) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Taint) DeepCopyInto(out *Taint) { + *out = *in + if in.TimeAdded != nil { + in, out := &in.TimeAdded, &out.TimeAdded + if *in == nil { + *out = nil + } else { + *out = new(v1.Time) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Taint. +func (in *Taint) DeepCopy() *Taint { + if in == nil { + return nil + } + out := new(Taint) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Toleration) DeepCopyInto(out *Toleration) { + *out = *in + if in.TolerationSeconds != nil { + in, out := &in.TolerationSeconds, &out.TolerationSeconds + if *in == nil { + *out = nil + } else { + *out = new(int64) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Toleration. +func (in *Toleration) DeepCopy() *Toleration { + if in == nil { + return nil + } + out := new(Toleration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Volume) DeepCopyInto(out *Volume) { + *out = *in + in.VolumeSource.DeepCopyInto(&out.VolumeSource) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Volume. +func (in *Volume) DeepCopy() *Volume { + if in == nil { + return nil + } + out := new(Volume) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VolumeDevice) DeepCopyInto(out *VolumeDevice) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeDevice. +func (in *VolumeDevice) DeepCopy() *VolumeDevice { + if in == nil { + return nil + } + out := new(VolumeDevice) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VolumeMount) DeepCopyInto(out *VolumeMount) { + *out = *in + if in.MountPropagation != nil { + in, out := &in.MountPropagation, &out.MountPropagation + if *in == nil { + *out = nil + } else { + *out = new(MountPropagationMode) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeMount. +func (in *VolumeMount) DeepCopy() *VolumeMount { + if in == nil { + return nil + } + out := new(VolumeMount) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VolumeProjection) DeepCopyInto(out *VolumeProjection) { + *out = *in + if in.Secret != nil { + in, out := &in.Secret, &out.Secret + if *in == nil { + *out = nil + } else { + *out = new(SecretProjection) + (*in).DeepCopyInto(*out) + } + } + if in.DownwardAPI != nil { + in, out := &in.DownwardAPI, &out.DownwardAPI + if *in == nil { + *out = nil + } else { + *out = new(DownwardAPIProjection) + (*in).DeepCopyInto(*out) + } + } + if in.ConfigMap != nil { + in, out := &in.ConfigMap, &out.ConfigMap + if *in == nil { + *out = nil + } else { + *out = new(ConfigMapProjection) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeProjection. +func (in *VolumeProjection) DeepCopy() *VolumeProjection { + if in == nil { + return nil + } + out := new(VolumeProjection) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VolumeSource) DeepCopyInto(out *VolumeSource) { + *out = *in + if in.HostPath != nil { + in, out := &in.HostPath, &out.HostPath + if *in == nil { + *out = nil + } else { + *out = new(HostPathVolumeSource) + (*in).DeepCopyInto(*out) + } + } + if in.EmptyDir != nil { + in, out := &in.EmptyDir, &out.EmptyDir + if *in == nil { + *out = nil + } else { + *out = new(EmptyDirVolumeSource) + (*in).DeepCopyInto(*out) + } + } + if in.GCEPersistentDisk != nil { + in, out := &in.GCEPersistentDisk, &out.GCEPersistentDisk + if *in == nil { + *out = nil + } else { + *out = new(GCEPersistentDiskVolumeSource) + **out = **in + } + } + if in.AWSElasticBlockStore != nil { + in, out := &in.AWSElasticBlockStore, &out.AWSElasticBlockStore + if *in == nil { + *out = nil + } else { + *out = new(AWSElasticBlockStoreVolumeSource) + **out = **in + } + } + if in.GitRepo != nil { + in, out := &in.GitRepo, &out.GitRepo + if *in == nil { + *out = nil + } else { + *out = new(GitRepoVolumeSource) + **out = **in + } + } + if in.Secret != nil { + in, out := &in.Secret, &out.Secret + if *in == nil { + *out = nil + } else { + *out = new(SecretVolumeSource) + (*in).DeepCopyInto(*out) + } + } + if in.NFS != nil { + in, out := &in.NFS, &out.NFS + if *in == nil { + *out = nil + } else { + *out = new(NFSVolumeSource) + **out = **in + } + } + if in.ISCSI != nil { + in, out := &in.ISCSI, &out.ISCSI + if *in == nil { + *out = nil + } else { + *out = new(ISCSIVolumeSource) + (*in).DeepCopyInto(*out) + } + } + if in.Glusterfs != nil { + in, out := &in.Glusterfs, &out.Glusterfs + if *in == nil { + *out = nil + } else { + *out = new(GlusterfsVolumeSource) + **out = **in + } + } + if in.PersistentVolumeClaim != nil { + in, out := &in.PersistentVolumeClaim, &out.PersistentVolumeClaim + if *in == nil { + *out = nil + } else { + *out = new(PersistentVolumeClaimVolumeSource) + **out = **in + } + } + if in.RBD != nil { + in, out := &in.RBD, &out.RBD + if *in == nil { + *out = nil + } else { + *out = new(RBDVolumeSource) + (*in).DeepCopyInto(*out) + } + } + if in.Quobyte != nil { + in, out := &in.Quobyte, &out.Quobyte + if *in == nil { + *out = nil + } else { + *out = new(QuobyteVolumeSource) + **out = **in + } + } + if in.FlexVolume != nil { + in, out := &in.FlexVolume, &out.FlexVolume + if *in == nil { + *out = nil + } else { + *out = new(FlexVolumeSource) + (*in).DeepCopyInto(*out) + } + } + if in.Cinder != nil { + in, out := &in.Cinder, &out.Cinder + if *in == nil { + *out = nil + } else { + *out = new(CinderVolumeSource) + **out = **in + } + } + if in.CephFS != nil { + in, out := &in.CephFS, &out.CephFS + if *in == nil { + *out = nil + } else { + *out = new(CephFSVolumeSource) + (*in).DeepCopyInto(*out) + } + } + if in.Flocker != nil { + in, out := &in.Flocker, &out.Flocker + if *in == nil { + *out = nil + } else { + *out = new(FlockerVolumeSource) + **out = **in + } + } + if in.DownwardAPI != nil { + in, out := &in.DownwardAPI, &out.DownwardAPI + if *in == nil { + *out = nil + } else { + *out = new(DownwardAPIVolumeSource) + (*in).DeepCopyInto(*out) + } + } + if in.FC != nil { + in, out := &in.FC, &out.FC + if *in == nil { + *out = nil + } else { + *out = new(FCVolumeSource) + (*in).DeepCopyInto(*out) + } + } + if in.AzureFile != nil { + in, out := &in.AzureFile, &out.AzureFile + if *in == nil { + *out = nil + } else { + *out = new(AzureFileVolumeSource) + **out = **in + } + } + if in.ConfigMap != nil { + in, out := &in.ConfigMap, &out.ConfigMap + if *in == nil { + *out = nil + } else { + *out = new(ConfigMapVolumeSource) + (*in).DeepCopyInto(*out) + } + } + if in.VsphereVolume != nil { + in, out := &in.VsphereVolume, &out.VsphereVolume + if *in == nil { + *out = nil + } else { + *out = new(VsphereVirtualDiskVolumeSource) + **out = **in + } + } + if in.AzureDisk != nil { + in, out := &in.AzureDisk, &out.AzureDisk + if *in == nil { + *out = nil + } else { + *out = new(AzureDiskVolumeSource) + (*in).DeepCopyInto(*out) + } + } + if in.PhotonPersistentDisk != nil { + in, out := &in.PhotonPersistentDisk, &out.PhotonPersistentDisk + if *in == nil { + *out = nil + } else { + *out = new(PhotonPersistentDiskVolumeSource) + **out = **in + } + } + if in.Projected != nil { + in, out := &in.Projected, &out.Projected + if *in == nil { + *out = nil + } else { + *out = new(ProjectedVolumeSource) + (*in).DeepCopyInto(*out) + } + } + if in.PortworxVolume != nil { + in, out := &in.PortworxVolume, &out.PortworxVolume + if *in == nil { + *out = nil + } else { + *out = new(PortworxVolumeSource) + **out = **in + } + } + if in.ScaleIO != nil { + in, out := &in.ScaleIO, &out.ScaleIO + if *in == nil { + *out = nil + } else { + *out = new(ScaleIOVolumeSource) + (*in).DeepCopyInto(*out) + } + } + if in.StorageOS != nil { + in, out := &in.StorageOS, &out.StorageOS + if *in == nil { + *out = nil + } else { + *out = new(StorageOSVolumeSource) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeSource. +func (in *VolumeSource) DeepCopy() *VolumeSource { + if in == nil { + return nil + } + out := new(VolumeSource) + 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 + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VsphereVirtualDiskVolumeSource. +func (in *VsphereVirtualDiskVolumeSource) DeepCopy() *VsphereVirtualDiskVolumeSource { + if in == nil { + return nil + } + out := new(VsphereVirtualDiskVolumeSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WeightedPodAffinityTerm) DeepCopyInto(out *WeightedPodAffinityTerm) { + *out = *in + in.PodAffinityTerm.DeepCopyInto(&out.PodAffinityTerm) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WeightedPodAffinityTerm. +func (in *WeightedPodAffinityTerm) DeepCopy() *WeightedPodAffinityTerm { + if in == nil { + return nil + } + out := new(WeightedPodAffinityTerm) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/events/BUILD b/pkg/apis/events/BUILD new file mode 100644 index 00000000000..13bf1079d83 --- /dev/null +++ b/pkg/apis/events/BUILD @@ -0,0 +1,34 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "register.go", + ], + importpath = "k8s.io/kubernetes/pkg/apis/events", + visibility = ["//visibility:public"], + deps = [ + "//pkg/apis/core:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//pkg/apis/events/install:all-srcs", + "//pkg/apis/events/v1beta1:all-srcs", + ], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/apis/events/doc.go b/pkg/apis/events/doc.go new file mode 100644 index 00000000000..15095ad3a29 --- /dev/null +++ b/pkg/apis/events/doc.go @@ -0,0 +1,18 @@ +/* +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. +*/ + +// +groupName=events.k8s.io +package events // import "k8s.io/kubernetes/pkg/apis/events" diff --git a/pkg/apis/events/install/BUILD b/pkg/apis/events/install/BUILD new file mode 100644 index 00000000000..01c67e2ed5a --- /dev/null +++ b/pkg/apis/events/install/BUILD @@ -0,0 +1,30 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["install.go"], + importpath = "k8s.io/kubernetes/pkg/apis/events/install", + visibility = ["//visibility:public"], + deps = [ + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/events:go_default_library", + "//pkg/apis/events/v1beta1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/apis/events/install/install.go b/pkg/apis/events/install/install.go new file mode 100644 index 00000000000..9dbc1b4d47d --- /dev/null +++ b/pkg/apis/events/install/install.go @@ -0,0 +1,48 @@ +/* +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 install installs the events API group, making it available as +// an option to all of the API encoding/decoding machinery. +package install + +import ( + "k8s.io/apimachinery/pkg/apimachinery/announced" + "k8s.io/apimachinery/pkg/apimachinery/registered" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/apis/events" + "k8s.io/kubernetes/pkg/apis/events/v1beta1" +) + +func init() { + Install(legacyscheme.GroupFactoryRegistry, legacyscheme.Registry, legacyscheme.Scheme) +} + +// Install registers the API group and adds types to a scheme +func Install(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *registered.APIRegistrationManager, scheme *runtime.Scheme) { + if err := announced.NewGroupMetaFactory( + &announced.GroupMetaFactoryArgs{ + GroupName: events.GroupName, + VersionPreferenceOrder: []string{v1beta1.SchemeGroupVersion.Version}, + AddInternalObjectsToScheme: events.AddToScheme, + }, + announced.VersionToSchemeFunc{ + v1beta1.SchemeGroupVersion.Version: v1beta1.AddToScheme, + }, + ).Announce(groupFactoryRegistry).RegisterAndEnable(registry, scheme); err != nil { + panic(err) + } +} diff --git a/pkg/apis/events/register.go b/pkg/apis/events/register.go new file mode 100644 index 00000000000..6156e800688 --- /dev/null +++ b/pkg/apis/events/register.go @@ -0,0 +1,52 @@ +/* +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 events + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kubernetes/pkg/apis/core" +) + +// GroupName is the group name use in this package +const GroupName = "events.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() +} + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &core.Event{}, + &core.EventList{}, + ) + return nil +} diff --git a/pkg/apis/events/v1beta1/BUILD b/pkg/apis/events/v1beta1/BUILD new file mode 100644 index 00000000000..bef76b25529 --- /dev/null +++ b/pkg/apis/events/v1beta1/BUILD @@ -0,0 +1,37 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "conversion.go", + "doc.go", + "register.go", + "zz_generated.conversion.go", + "zz_generated.defaults.go", + ], + importpath = "k8s.io/kubernetes/pkg/apis/events/v1beta1", + visibility = ["//visibility:public"], + deps = [ + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/v1:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/events/v1beta1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/apis/events/v1beta1/conversion.go b/pkg/apis/events/v1beta1/conversion.go new file mode 100644 index 00000000000..3a2be8e3168 --- /dev/null +++ b/pkg/apis/events/v1beta1/conversion.go @@ -0,0 +1,58 @@ +/* +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 v1beta1 + +import ( + v1beta1 "k8s.io/api/events/v1beta1" + conversion "k8s.io/apimachinery/pkg/conversion" + k8s_api "k8s.io/kubernetes/pkg/apis/core" + k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" +) + +func Convert_v1beta1_Event_To_core_Event(in *v1beta1.Event, out *k8s_api.Event, s conversion.Scope) error { + if err := autoConvert_v1beta1_Event_To_core_Event(in, out, s); err != nil { + return err + } + if err := k8s_api_v1.Convert_v1_ObjectReference_To_core_ObjectReference(&in.Regarding, &out.InvolvedObject, s); err != nil { + return err + } + if err := k8s_api_v1.Convert_v1_EventSource_To_core_EventSource(&in.DeprecatedSource, &out.Source, s); err != nil { + return err + } + out.Message = in.Note + out.FirstTimestamp = in.DeprecatedFirstTimestamp + out.LastTimestamp = in.DeprecatedLastTimestamp + out.Count = in.DeprecatedCount + return nil +} + +func Convert_core_Event_To_v1beta1_Event(in *k8s_api.Event, out *v1beta1.Event, s conversion.Scope) error { + if err := autoConvert_core_Event_To_v1beta1_Event(in, out, s); err != nil { + return err + } + if err := k8s_api_v1.Convert_core_ObjectReference_To_v1_ObjectReference(&in.InvolvedObject, &out.Regarding, s); err != nil { + return err + } + if err := k8s_api_v1.Convert_core_EventSource_To_v1_EventSource(&in.Source, &out.DeprecatedSource, s); err != nil { + return err + } + out.Note = in.Message + out.DeprecatedFirstTimestamp = in.FirstTimestamp + out.DeprecatedLastTimestamp = in.LastTimestamp + out.DeprecatedCount = in.Count + return nil +} diff --git a/pkg/apis/events/v1beta1/doc.go b/pkg/apis/events/v1beta1/doc.go new file mode 100644 index 00000000000..174190914fa --- /dev/null +++ b/pkg/apis/events/v1beta1/doc.go @@ -0,0 +1,23 @@ +/* +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/events +// +k8s:conversion-gen-external-types=k8s.io/api/events/v1beta1 +// +k8s:defaulter-gen=TypeMeta +// +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/events/v1beta1 + +// +groupName=events.k8s.io +package v1beta1 // import "k8s.io/kubernetes/pkg/apis/events/v1beta1" diff --git a/pkg/apis/events/v1beta1/register.go b/pkg/apis/events/v1beta1/register.go new file mode 100644 index 00000000000..7ff9379602f --- /dev/null +++ b/pkg/apis/events/v1beta1/register.go @@ -0,0 +1,45 @@ +/* +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 v1beta1 + +import ( + eventsv1beta1 "k8s.io/api/events/v1beta1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName is the group name use in this package +const GroupName = "events.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + localSchemeBuilder = &eventsv1beta1.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(RegisterDefaults) +} diff --git a/pkg/apis/events/v1beta1/zz_generated.conversion.go b/pkg/apis/events/v1beta1/zz_generated.conversion.go new file mode 100644 index 00000000000..e73b2081338 --- /dev/null +++ b/pkg/apis/events/v1beta1/zz_generated.conversion.go @@ -0,0 +1,152 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by conversion-gen. Do not edit it manually! + +package v1beta1 + +import ( + unsafe "unsafe" + + v1 "k8s.io/api/core/v1" + v1beta1 "k8s.io/api/events/v1beta1" + conversion "k8s.io/apimachinery/pkg/conversion" + runtime "k8s.io/apimachinery/pkg/runtime" + 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(scheme *runtime.Scheme) error { + return scheme.AddGeneratedConversionFuncs( + Convert_v1beta1_Event_To_core_Event, + Convert_core_Event_To_v1beta1_Event, + Convert_v1beta1_EventList_To_core_EventList, + Convert_core_EventList_To_v1beta1_EventList, + Convert_v1beta1_EventSeries_To_core_EventSeries, + Convert_core_EventSeries_To_v1beta1_EventSeries, + ) +} + +func autoConvert_v1beta1_Event_To_core_Event(in *v1beta1.Event, out *core.Event, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.EventTime = in.EventTime + out.Series = (*core.EventSeries)(unsafe.Pointer(in.Series)) + out.ReportingController = in.ReportingController + out.ReportingInstance = in.ReportingInstance + out.Action = in.Action + out.Reason = in.Reason + // WARNING: in.Regarding requires manual conversion: does not exist in peer-type + out.Related = (*core.ObjectReference)(unsafe.Pointer(in.Related)) + // WARNING: in.Note requires manual conversion: does not exist in peer-type + out.Type = in.Type + // WARNING: in.DeprecatedSource requires manual conversion: does not exist in peer-type + // WARNING: in.DeprecatedFirstTimestamp requires manual conversion: does not exist in peer-type + // WARNING: in.DeprecatedLastTimestamp requires manual conversion: does not exist in peer-type + // WARNING: in.DeprecatedCount requires manual conversion: does not exist in peer-type + return nil +} + +func autoConvert_core_Event_To_v1beta1_Event(in *core.Event, out *v1beta1.Event, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + // WARNING: in.InvolvedObject requires manual conversion: does not exist in peer-type + out.Reason = in.Reason + // WARNING: in.Message requires manual conversion: does not exist in peer-type + // WARNING: in.Source requires manual conversion: does not exist in peer-type + // WARNING: in.FirstTimestamp requires manual conversion: does not exist in peer-type + // WARNING: in.LastTimestamp requires manual conversion: does not exist in peer-type + // WARNING: in.Count requires manual conversion: does not exist in peer-type + out.Type = in.Type + out.EventTime = in.EventTime + out.Series = (*v1beta1.EventSeries)(unsafe.Pointer(in.Series)) + out.Action = in.Action + out.Related = (*v1.ObjectReference)(unsafe.Pointer(in.Related)) + out.ReportingController = in.ReportingController + out.ReportingInstance = in.ReportingInstance + return nil +} + +func autoConvert_v1beta1_EventList_To_core_EventList(in *v1beta1.EventList, out *core.EventList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]core.Event, len(*in)) + for i := range *in { + if err := Convert_v1beta1_Event_To_core_Event(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_v1beta1_EventList_To_core_EventList is an autogenerated conversion function. +func Convert_v1beta1_EventList_To_core_EventList(in *v1beta1.EventList, out *core.EventList, s conversion.Scope) error { + return autoConvert_v1beta1_EventList_To_core_EventList(in, out, s) +} + +func autoConvert_core_EventList_To_v1beta1_EventList(in *core.EventList, out *v1beta1.EventList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1beta1.Event, len(*in)) + for i := range *in { + if err := Convert_core_Event_To_v1beta1_Event(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_core_EventList_To_v1beta1_EventList is an autogenerated conversion function. +func Convert_core_EventList_To_v1beta1_EventList(in *core.EventList, out *v1beta1.EventList, s conversion.Scope) error { + return autoConvert_core_EventList_To_v1beta1_EventList(in, out, s) +} + +func autoConvert_v1beta1_EventSeries_To_core_EventSeries(in *v1beta1.EventSeries, out *core.EventSeries, s conversion.Scope) error { + out.Count = in.Count + out.LastObservedTime = in.LastObservedTime + out.State = core.EventSeriesState(in.State) + return nil +} + +// Convert_v1beta1_EventSeries_To_core_EventSeries is an autogenerated conversion function. +func Convert_v1beta1_EventSeries_To_core_EventSeries(in *v1beta1.EventSeries, out *core.EventSeries, s conversion.Scope) error { + return autoConvert_v1beta1_EventSeries_To_core_EventSeries(in, out, s) +} + +func autoConvert_core_EventSeries_To_v1beta1_EventSeries(in *core.EventSeries, out *v1beta1.EventSeries, s conversion.Scope) error { + out.Count = in.Count + out.LastObservedTime = in.LastObservedTime + out.State = v1beta1.EventSeriesState(in.State) + return nil +} + +// Convert_core_EventSeries_To_v1beta1_EventSeries is an autogenerated conversion function. +func Convert_core_EventSeries_To_v1beta1_EventSeries(in *core.EventSeries, out *v1beta1.EventSeries, s conversion.Scope) error { + return autoConvert_core_EventSeries_To_v1beta1_EventSeries(in, out, s) +} diff --git a/pkg/apis/events/v1beta1/zz_generated.defaults.go b/pkg/apis/events/v1beta1/zz_generated.defaults.go new file mode 100644 index 00000000000..b61dda74c23 --- /dev/null +++ b/pkg/apis/events/v1beta1/zz_generated.defaults.go @@ -0,0 +1,32 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by defaulter-gen. Do not edit it manually! + +package v1beta1 + +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/pkg/apis/extensions/BUILD b/pkg/apis/extensions/BUILD index 5cdded17f15..17076d4720e 100644 --- a/pkg/apis/extensions/BUILD +++ b/pkg/apis/extensions/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["helpers_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/apis/extensions", - library = ":go_default_library", ) go_library( @@ -24,11 +24,11 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/apis/extensions", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/autoscaling:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/networking:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", diff --git a/pkg/apis/extensions/doc.go b/pkg/apis/extensions/doc.go index fbce8ee707d..d97cffdbcb2 100644 --- a/pkg/apis/extensions/doc.go +++ b/pkg/apis/extensions/doc.go @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package package extensions // import "k8s.io/kubernetes/pkg/apis/extensions" diff --git a/pkg/apis/extensions/fuzzer/BUILD b/pkg/apis/extensions/fuzzer/BUILD index 4f4bf9cce0f..712d0f4e681 100644 --- a/pkg/apis/extensions/fuzzer/BUILD +++ b/pkg/apis/extensions/fuzzer/BUILD @@ -12,7 +12,6 @@ go_library( deps = [ "//pkg/apis/extensions:go_default_library", "//vendor/github.com/google/gofuzz:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", ], diff --git a/pkg/apis/extensions/fuzzer/fuzzer.go b/pkg/apis/extensions/fuzzer/fuzzer.go index f8f3b3d374a..006d04f9f9c 100644 --- a/pkg/apis/extensions/fuzzer/fuzzer.go +++ b/pkg/apis/extensions/fuzzer/fuzzer.go @@ -21,7 +21,6 @@ import ( fuzz "github.com/google/gofuzz" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/kubernetes/pkg/apis/extensions" @@ -83,25 +82,6 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { } psp.FSGroup.Rule = fsGroupRules[c.Rand.Intn(len(fsGroupRules))] }, - func(s *extensions.Scale, c fuzz.Continue) { - c.FuzzNoCustom(s) // fuzz self without calling this function again - // TODO: Implement a fuzzer to generate valid keys, values and operators for - // selector requirements. - if s.Status.Selector != nil { - s.Status.Selector = &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "testlabelkey": "testlabelval", - }, - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "testkey", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"val1", "val2", "val3"}, - }, - }, - } - } - }, func(j *extensions.DaemonSetSpec, c fuzz.Continue) { c.FuzzNoCustom(j) // fuzz self without calling this function again rhl := int32(c.Rand.Int31()) diff --git a/pkg/apis/extensions/install/BUILD b/pkg/apis/extensions/install/BUILD index 971b44e30cd..886ce927a26 100644 --- a/pkg/apis/extensions/install/BUILD +++ b/pkg/apis/extensions/install/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["install.go"], importpath = "k8s.io/kubernetes/pkg/apis/extensions/install", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/apis/extensions/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", diff --git a/pkg/apis/extensions/install/install.go b/pkg/apis/extensions/install/install.go index 7e8cc9c2169..dfa89910120 100644 --- a/pkg/apis/extensions/install/install.go +++ b/pkg/apis/extensions/install/install.go @@ -23,13 +23,13 @@ import ( "k8s.io/apimachinery/pkg/apimachinery/registered" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" ) func init() { - Install(api.GroupFactoryRegistry, api.Registry, api.Scheme) + Install(legacyscheme.GroupFactoryRegistry, legacyscheme.Registry, legacyscheme.Scheme) } // Install registers the API group and adds types to a scheme @@ -38,7 +38,7 @@ func Install(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *r &announced.GroupMetaFactoryArgs{ GroupName: extensions.GroupName, VersionPreferenceOrder: []string{v1beta1.SchemeGroupVersion.Version}, - RootScopedKinds: sets.NewString("PodSecurityPolicy", "ThirdPartyResource"), + RootScopedKinds: sets.NewString("PodSecurityPolicy"), AddInternalObjectsToScheme: extensions.AddToScheme, }, announced.VersionToSchemeFunc{ diff --git a/pkg/apis/extensions/register.go b/pkg/apis/extensions/register.go index 780f58dc747..48137fc6962 100644 --- a/pkg/apis/extensions/register.go +++ b/pkg/apis/extensions/register.go @@ -19,6 +19,7 @@ package extensions import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kubernetes/pkg/apis/autoscaling" "k8s.io/kubernetes/pkg/apis/networking" ) @@ -43,7 +44,7 @@ var ( AddToScheme = SchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { // TODO this gets cleaned up when the types are fixed scheme.AddKnownTypes(SchemeGroupVersion, @@ -51,19 +52,15 @@ func addKnownTypes(scheme *runtime.Scheme) error { &DeploymentList{}, &DeploymentRollback{}, &ReplicationControllerDummy{}, - &Scale{}, - &ThirdPartyResource{}, - &ThirdPartyResourceList{}, &DaemonSetList{}, &DaemonSet{}, - &ThirdPartyResourceData{}, - &ThirdPartyResourceDataList{}, &Ingress{}, &IngressList{}, &ReplicaSet{}, &ReplicaSetList{}, &PodSecurityPolicy{}, &PodSecurityPolicyList{}, + &autoscaling.Scale{}, &networking.NetworkPolicy{}, &networking.NetworkPolicyList{}, ) diff --git a/pkg/apis/extensions/types.go b/pkg/apis/extensions/types.go index 3abc8f8f30c..e36972846bc 100644 --- a/pkg/apis/extensions/types.go +++ b/pkg/apis/extensions/types.go @@ -32,7 +32,7 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) const ( @@ -42,44 +42,6 @@ const ( SysctlsPodSecurityPolicyAnnotationKey string = "security.alpha.kubernetes.io/sysctls" ) -// describes the attributes of a scale subresource -type ScaleSpec struct { - // desired number of instances for the scaled object. - // +optional - Replicas int32 -} - -// represents the current status of a scale subresource. -type ScaleStatus struct { - // actual number of observed instances of the scaled object. - Replicas int32 - - // label query over pods that should match the replicas count. - // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors - // +optional - Selector *metav1.LabelSelector -} - -// +genclient -// +genclient:noVerbs -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// represents a scaling request for a resource. -type Scale struct { - metav1.TypeMeta - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata. - // +optional - metav1.ObjectMeta - - // defines the behavior of the scale. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status. - // +optional - Spec ScaleSpec - - // current status of the scale. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status. Read-only. - // +optional - Status ScaleStatus -} - // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // Dummy definition @@ -111,63 +73,8 @@ type CustomMetricCurrentStatusList struct { } // +genclient -// +genclient:nonNamespaced -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// A ThirdPartyResource is a generic representation of a resource, it is used by add-ons and plugins to add new resource -// types to the API. It consists of one or more Versions of the api. -type ThirdPartyResource struct { - metav1.TypeMeta - - // Standard object metadata - // +optional - metav1.ObjectMeta - - // Description is the description of this object. - // +optional - Description string - - // Versions are versions for this third party object - Versions []APIVersion -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -type ThirdPartyResourceList struct { - metav1.TypeMeta - - // Standard list metadata. - // +optional - metav1.ListMeta - - // Items is the list of horizontal pod autoscalers. - Items []ThirdPartyResource -} - -// An APIVersion represents a single concrete version of an object model. -// TODO: we should consider merge this struct with GroupVersion in metav1.go -type APIVersion struct { - // Name of this version (e.g. 'v1'). - Name string -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// An internal object, used for versioned storage in etcd. Not exposed to the end user. -type ThirdPartyResourceData struct { - metav1.TypeMeta - // Standard object metadata. - // +optional - metav1.ObjectMeta - - // Data is the raw JSON data for this data. - // +optional - Data []byte -} - -// +genclient -// +genclient:method=GetScale,verb=get,subresource=scale,result=Scale -// +genclient:method=UpdateScale,verb=update,subresource=scale,input=Scale,result=Scale +// +genclient:method=GetScale,verb=get,subresource=scale,result=k8s.io/kubernetes/pkg/apis/autoscaling.Scale +// +genclient:method=UpdateScale,verb=update,subresource=scale,input=k8s.io/kubernetes/pkg/apis/autoscaling.Scale,result=k8s.io/kubernetes/pkg/apis/autoscaling.Scale // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type Deployment struct { @@ -524,6 +431,27 @@ type DaemonSetStatus struct { // create the name for the newest ControllerRevision. // +optional CollisionCount *int32 + + // Represents the latest available observations of a DaemonSet's current state. + Conditions []DaemonSetCondition +} + +type DaemonSetConditionType string + +// TODO: Add valid condition types of a DaemonSet. + +// DaemonSetCondition describes the state of a DaemonSet at a certain point. +type DaemonSetCondition struct { + // Type of DaemonSet condition. + Type DaemonSetConditionType + // Status of the condition, one of True, False, Unknown. + Status api.ConditionStatus + // Last time the condition transitioned from one status to another. + LastTransitionTime metav1.Time + // The reason for the condition's last transition. + Reason string + // A human readable message indicating details about the transition. + Message string } // +genclient @@ -573,18 +501,6 @@ type DaemonSetList struct { Items []DaemonSet } -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -type ThirdPartyResourceDataList struct { - metav1.TypeMeta - // Standard list metadata - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata - // +optional - metav1.ListMeta - // Items is a list of third party objects - Items []ThirdPartyResourceData -} - // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -755,8 +671,8 @@ type IngressBackend struct { } // +genclient -// +genclient:method=GetScale,verb=get,subresource=scale,result=Scale -// +genclient:method=UpdateScale,verb=update,subresource=scale,input=Scale,result=Scale +// +genclient:method=GetScale,verb=get,subresource=scale,result=k8s.io/kubernetes/pkg/apis/autoscaling.Scale +// +genclient:method=UpdateScale,verb=update,subresource=scale,input=k8s.io/kubernetes/pkg/apis/autoscaling.Scale,result=k8s.io/kubernetes/pkg/apis/autoscaling.Scale // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // ReplicaSet ensures that a specified number of pod replicas are running at any given time. @@ -888,7 +804,8 @@ type PodSecurityPolicySpec struct { Privileged bool // DefaultAddCapabilities is the default set of capabilities that will be added to the container // unless the pod spec specifically drops the capability. You may not list a capability in both - // DefaultAddCapabilities and RequiredDropCapabilities. + // DefaultAddCapabilities and RequiredDropCapabilities. Capabilities added here are implicitly + // allowed, and need not be included in the AllowedCapabilities list. // +optional DefaultAddCapabilities []api.Capability // RequiredDropCapabilities are the capabilities that will be dropped from the container. These @@ -943,6 +860,11 @@ type PodSecurityPolicySpec struct { // AllowedHostPaths is a white list of allowed host paths. Empty indicates that all host paths may be used. // +optional AllowedHostPaths []AllowedHostPath + // AllowedFlexVolumes is a whitelist of allowed Flexvolumes. Empty or nil indicates that all + // Flexvolumes may be used. This parameter is effective only when the usage of the Flexvolumes + // is allowed in the "Volumes" field. + // +optional + AllowedFlexVolumes []AllowedFlexVolume } // AllowedHostPath defines the host volume conditions that will be enabled by a policy @@ -962,9 +884,9 @@ type AllowedHostPath struct { // for pods to use. It requires both the start and end to be defined. type HostPortRange struct { // Min is the start of the range, inclusive. - Min int + Min int32 // Max is the end of the range, inclusive. - Max int + Max int32 } // AllowAllCapabilities can be used as a value for the PodSecurityPolicy.AllowAllCapabilities @@ -1002,15 +924,22 @@ var ( Projected FSType = "projected" PortworxVolume FSType = "portworxVolume" ScaleIO FSType = "scaleIO" + CSI FSType = "csi" All FSType = "*" ) +// AllowedFlexVolume represents a single Flexvolume that is allowed to be used. +type AllowedFlexVolume struct { + // Driver is the name of the Flexvolume driver. + Driver string +} + // SELinuxStrategyOptions defines the strategy type and any options used to create the strategy. type SELinuxStrategyOptions struct { // Rule is the strategy that will dictate the allowable labels that may be set. Rule SELinuxStrategy // seLinuxOptions required to run as; required for MustRunAs - // More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md + // More info: https://kubernetes.io/docs/concepts/policy/pod-security-policy/#selinux // +optional SELinuxOptions *api.SELinuxOptions } diff --git a/pkg/apis/extensions/v1beta1/BUILD b/pkg/apis/extensions/v1beta1/BUILD index a8540165605..51d6bf1c77a 100644 --- a/pkg/apis/extensions/v1beta1/BUILD +++ b/pkg/apis/extensions/v1beta1/BUILD @@ -18,14 +18,16 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/apis/extensions/v1beta1", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/v1:go_default_library", + "//pkg/apis/autoscaling:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/v1:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/apis/networking:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", @@ -38,8 +40,9 @@ go_test( importpath = "k8s.io/kubernetes/pkg/apis/extensions/v1beta1_test", deps = [ ":go_default_library", - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/install:go_default_library", "//pkg/apis/extensions/install:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", diff --git a/pkg/apis/extensions/v1beta1/conversion.go b/pkg/apis/extensions/v1beta1/conversion.go index 04aa3a316e0..d236a415e13 100644 --- a/pkg/apis/extensions/v1beta1/conversion.go +++ b/pkg/apis/extensions/v1beta1/conversion.go @@ -24,10 +24,12 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/kubernetes/pkg/api" - k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/apis/autoscaling" + api "k8s.io/kubernetes/pkg/apis/core" + k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/networking" ) @@ -35,8 +37,8 @@ import ( func addConversionFuncs(scheme *runtime.Scheme) error { // Add non-generated conversion functions err := scheme.AddConversionFuncs( - Convert_extensions_ScaleStatus_To_v1beta1_ScaleStatus, - Convert_v1beta1_ScaleStatus_To_extensions_ScaleStatus, + Convert_autoscaling_ScaleStatus_To_v1beta1_ScaleStatus, + Convert_v1beta1_ScaleStatus_To_autoscaling_ScaleStatus, Convert_extensions_DeploymentSpec_To_v1beta1_DeploymentSpec, Convert_v1beta1_DeploymentSpec_To_extensions_DeploymentSpec, Convert_extensions_DeploymentStrategy_To_v1beta1_DeploymentStrategy, @@ -72,48 +74,35 @@ func addConversionFuncs(scheme *runtime.Scheme) error { return nil } -func Convert_extensions_ScaleStatus_To_v1beta1_ScaleStatus(in *extensions.ScaleStatus, out *extensionsv1beta1.ScaleStatus, s conversion.Scope) error { +func Convert_autoscaling_ScaleStatus_To_v1beta1_ScaleStatus(in *autoscaling.ScaleStatus, out *extensionsv1beta1.ScaleStatus, s conversion.Scope) error { out.Replicas = int32(in.Replicas) + out.TargetSelector = in.Selector out.Selector = nil - out.TargetSelector = "" - if in.Selector != nil { - if in.Selector.MatchExpressions == nil || len(in.Selector.MatchExpressions) == 0 { - out.Selector = in.Selector.MatchLabels - } - - selector, err := metav1.LabelSelectorAsSelector(in.Selector) - if err != nil { - return fmt.Errorf("invalid label selector: %v", err) - } - out.TargetSelector = selector.String() + selector, err := metav1.ParseToLabelSelector(in.Selector) + if err != nil { + return fmt.Errorf("failed to parse selector: %v", err) } + if len(selector.MatchExpressions) == 0 { + out.Selector = selector.MatchLabels + } + return nil } -func Convert_v1beta1_ScaleStatus_To_extensions_ScaleStatus(in *extensionsv1beta1.ScaleStatus, out *extensions.ScaleStatus, s conversion.Scope) error { +func Convert_v1beta1_ScaleStatus_To_autoscaling_ScaleStatus(in *extensionsv1beta1.ScaleStatus, out *autoscaling.ScaleStatus, s conversion.Scope) error { out.Replicas = in.Replicas - // Normally when 2 fields map to the same internal value we favor the old field, since - // old clients can't be expected to know about new fields but clients that know about the - // new field can be expected to know about the old field (though that's not quite true, due - // to kubectl apply). However, these fields are readonly, so any non-nil value should work. if in.TargetSelector != "" { - labelSelector, err := metav1.ParseToLabelSelector(in.TargetSelector) - if err != nil { - out.Selector = nil - return fmt.Errorf("failed to parse target selector: %v", err) - } - out.Selector = labelSelector + out.Selector = in.TargetSelector } else if in.Selector != nil { - out.Selector = new(metav1.LabelSelector) - selector := make(map[string]string) + set := labels.Set{} for key, val := range in.Selector { - selector[key] = val + set[key] = val } - out.Selector.MatchLabels = selector + out.Selector = labels.SelectorFromSet(set).String() } else { - out.Selector = nil + out.Selector = "" } return nil } @@ -121,7 +110,7 @@ func Convert_v1beta1_ScaleStatus_To_extensions_ScaleStatus(in *extensionsv1beta1 func Convert_extensions_DeploymentSpec_To_v1beta1_DeploymentSpec(in *extensions.DeploymentSpec, out *extensionsv1beta1.DeploymentSpec, s conversion.Scope) error { out.Replicas = &in.Replicas out.Selector = in.Selector - if err := k8s_api_v1.Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := k8s_api_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if err := Convert_extensions_DeploymentStrategy_To_v1beta1_DeploymentStrategy(&in.Strategy, &out.Strategy, s); err != nil { @@ -151,7 +140,7 @@ func Convert_v1beta1_DeploymentSpec_To_extensions_DeploymentSpec(in *extensionsv out.Replicas = *in.Replicas } out.Selector = in.Selector - if err := k8s_api_v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := k8s_api_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if err := Convert_v1beta1_DeploymentStrategy_To_extensions_DeploymentStrategy(&in.Strategy, &out.Strategy, s); err != nil { @@ -247,7 +236,7 @@ func Convert_extensions_ReplicaSetSpec_To_v1beta1_ReplicaSetSpec(in *extensions. *out.Replicas = int32(in.Replicas) out.MinReadySeconds = in.MinReadySeconds out.Selector = in.Selector - if err := k8s_api_v1.Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := k8s_api_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } return nil @@ -259,7 +248,7 @@ func Convert_v1beta1_ReplicaSetSpec_To_extensions_ReplicaSetSpec(in *extensionsv } out.MinReadySeconds = in.MinReadySeconds out.Selector = in.Selector - if err := k8s_api_v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + if err := k8s_api_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } return nil @@ -338,10 +327,12 @@ func Convert_v1beta1_NetworkPolicyIngressRule_To_networking_NetworkPolicyIngress return err } } - out.From = make([]networking.NetworkPolicyPeer, len(in.From)) - for i := range in.From { - if err := Convert_v1beta1_NetworkPolicyPeer_To_networking_NetworkPolicyPeer(&in.From[i], &out.From[i], s); err != nil { - return err + if in.From != nil { + out.From = make([]networking.NetworkPolicyPeer, len(in.From)) + for i := range in.From { + if err := Convert_v1beta1_NetworkPolicyPeer_To_networking_NetworkPolicyPeer(&in.From[i], &out.From[i], s); err != nil { + return err + } } } return nil @@ -354,10 +345,12 @@ func Convert_networking_NetworkPolicyIngressRule_To_v1beta1_NetworkPolicyIngress return err } } - out.From = make([]extensionsv1beta1.NetworkPolicyPeer, len(in.From)) - for i := range in.From { - if err := Convert_networking_NetworkPolicyPeer_To_v1beta1_NetworkPolicyPeer(&in.From[i], &out.From[i], s); err != nil { - return err + if in.From != nil { + out.From = make([]extensionsv1beta1.NetworkPolicyPeer, len(in.From)) + for i := range in.From { + if err := Convert_networking_NetworkPolicyPeer_To_v1beta1_NetworkPolicyPeer(&in.From[i], &out.From[i], s); err != nil { + return err + } } } return nil diff --git a/pkg/apis/extensions/v1beta1/defaults_test.go b/pkg/apis/extensions/v1beta1/defaults_test.go index e1c2b527d3b..a35b76f19b5 100644 --- a/pkg/apis/extensions/v1beta1/defaults_test.go +++ b/pkg/apis/extensions/v1beta1/defaults_test.go @@ -28,8 +28,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/kubernetes/pkg/api" - _ "k8s.io/kubernetes/pkg/api/install" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" + _ "k8s.io/kubernetes/pkg/apis/core/install" _ "k8s.io/kubernetes/pkg/apis/extensions/install" . "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" ) @@ -723,18 +724,18 @@ func TestSetDefaultNetworkPolicy(t *testing.T) { } func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { - data, err := runtime.Encode(api.Codecs.LegacyCodec(SchemeGroupVersion), obj) + 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(api.Codecs.UniversalDecoder(), data) + 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 = api.Scheme.Convert(obj2, obj3, nil) + err = legacyscheme.Scheme.Convert(obj2, obj3, nil) if err != nil { t.Errorf("%v\nSource: %#v", err, obj2) return nil diff --git a/pkg/apis/extensions/v1beta1/doc.go b/pkg/apis/extensions/v1beta1/doc.go index fb27eae221a..da219b9d221 100644 --- a/pkg/apis/extensions/v1beta1/doc.go +++ b/pkg/apis/extensions/v1beta1/doc.go @@ -15,7 +15,8 @@ limitations under the License. */ // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/extensions -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/extensions/v1beta1 +// +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/autoscaling +// +k8s:conversion-gen-external-types=k8s.io/api/extensions/v1beta1 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/extensions/v1beta1 diff --git a/pkg/apis/extensions/v1beta1/zz_generated.conversion.go b/pkg/apis/extensions/v1beta1/zz_generated.conversion.go index 95b006f05b5..7053558193e 100644 --- a/pkg/apis/extensions/v1beta1/zz_generated.conversion.go +++ b/pkg/apis/extensions/v1beta1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,15 +21,17 @@ limitations under the License. package v1beta1 import ( - core_v1 "k8s.io/api/core/v1" + unsafe "unsafe" + + v1 "k8s.io/api/core/v1" v1beta1 "k8s.io/api/extensions/v1beta1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - api "k8s.io/kubernetes/pkg/api" - api_v1 "k8s.io/kubernetes/pkg/api/v1" + autoscaling "k8s.io/kubernetes/pkg/apis/autoscaling" + core "k8s.io/kubernetes/pkg/apis/core" + core_v1 "k8s.io/kubernetes/pkg/apis/core/v1" extensions "k8s.io/kubernetes/pkg/apis/extensions" - unsafe "unsafe" ) func init() { @@ -40,8 +42,8 @@ func init() { // Public to allow building arbitrary schemes. func RegisterConversions(scheme *runtime.Scheme) error { return scheme.AddGeneratedConversionFuncs( - Convert_v1beta1_APIVersion_To_extensions_APIVersion, - Convert_extensions_APIVersion_To_v1beta1_APIVersion, + Convert_v1beta1_AllowedFlexVolume_To_extensions_AllowedFlexVolume, + Convert_extensions_AllowedFlexVolume_To_v1beta1_AllowedFlexVolume, Convert_v1beta1_AllowedHostPath_To_extensions_AllowedHostPath, Convert_extensions_AllowedHostPath_To_v1beta1_AllowedHostPath, Convert_v1beta1_CustomMetricCurrentStatus_To_extensions_CustomMetricCurrentStatus, @@ -54,6 +56,8 @@ func RegisterConversions(scheme *runtime.Scheme) error { Convert_extensions_CustomMetricTargetList_To_v1beta1_CustomMetricTargetList, Convert_v1beta1_DaemonSet_To_extensions_DaemonSet, Convert_extensions_DaemonSet_To_v1beta1_DaemonSet, + Convert_v1beta1_DaemonSetCondition_To_extensions_DaemonSetCondition, + Convert_extensions_DaemonSetCondition_To_v1beta1_DaemonSetCondition, Convert_v1beta1_DaemonSetList_To_extensions_DaemonSetList, Convert_extensions_DaemonSetList_To_v1beta1_DaemonSetList, Convert_v1beta1_DaemonSetSpec_To_extensions_DaemonSetSpec, @@ -128,43 +132,35 @@ func RegisterConversions(scheme *runtime.Scheme) error { Convert_extensions_RunAsUserStrategyOptions_To_v1beta1_RunAsUserStrategyOptions, Convert_v1beta1_SELinuxStrategyOptions_To_extensions_SELinuxStrategyOptions, Convert_extensions_SELinuxStrategyOptions_To_v1beta1_SELinuxStrategyOptions, - Convert_v1beta1_Scale_To_extensions_Scale, - Convert_extensions_Scale_To_v1beta1_Scale, - Convert_v1beta1_ScaleSpec_To_extensions_ScaleSpec, - Convert_extensions_ScaleSpec_To_v1beta1_ScaleSpec, - Convert_v1beta1_ScaleStatus_To_extensions_ScaleStatus, - Convert_extensions_ScaleStatus_To_v1beta1_ScaleStatus, + Convert_v1beta1_Scale_To_autoscaling_Scale, + Convert_autoscaling_Scale_To_v1beta1_Scale, + Convert_v1beta1_ScaleSpec_To_autoscaling_ScaleSpec, + Convert_autoscaling_ScaleSpec_To_v1beta1_ScaleSpec, + Convert_v1beta1_ScaleStatus_To_autoscaling_ScaleStatus, + Convert_autoscaling_ScaleStatus_To_v1beta1_ScaleStatus, Convert_v1beta1_SupplementalGroupsStrategyOptions_To_extensions_SupplementalGroupsStrategyOptions, Convert_extensions_SupplementalGroupsStrategyOptions_To_v1beta1_SupplementalGroupsStrategyOptions, - Convert_v1beta1_ThirdPartyResource_To_extensions_ThirdPartyResource, - Convert_extensions_ThirdPartyResource_To_v1beta1_ThirdPartyResource, - Convert_v1beta1_ThirdPartyResourceData_To_extensions_ThirdPartyResourceData, - Convert_extensions_ThirdPartyResourceData_To_v1beta1_ThirdPartyResourceData, - Convert_v1beta1_ThirdPartyResourceDataList_To_extensions_ThirdPartyResourceDataList, - Convert_extensions_ThirdPartyResourceDataList_To_v1beta1_ThirdPartyResourceDataList, - Convert_v1beta1_ThirdPartyResourceList_To_extensions_ThirdPartyResourceList, - Convert_extensions_ThirdPartyResourceList_To_v1beta1_ThirdPartyResourceList, ) } -func autoConvert_v1beta1_APIVersion_To_extensions_APIVersion(in *v1beta1.APIVersion, out *extensions.APIVersion, s conversion.Scope) error { - out.Name = in.Name +func autoConvert_v1beta1_AllowedFlexVolume_To_extensions_AllowedFlexVolume(in *v1beta1.AllowedFlexVolume, out *extensions.AllowedFlexVolume, s conversion.Scope) error { + out.Driver = in.Driver return nil } -// Convert_v1beta1_APIVersion_To_extensions_APIVersion is an autogenerated conversion function. -func Convert_v1beta1_APIVersion_To_extensions_APIVersion(in *v1beta1.APIVersion, out *extensions.APIVersion, s conversion.Scope) error { - return autoConvert_v1beta1_APIVersion_To_extensions_APIVersion(in, out, s) +// Convert_v1beta1_AllowedFlexVolume_To_extensions_AllowedFlexVolume is an autogenerated conversion function. +func Convert_v1beta1_AllowedFlexVolume_To_extensions_AllowedFlexVolume(in *v1beta1.AllowedFlexVolume, out *extensions.AllowedFlexVolume, s conversion.Scope) error { + return autoConvert_v1beta1_AllowedFlexVolume_To_extensions_AllowedFlexVolume(in, out, s) } -func autoConvert_extensions_APIVersion_To_v1beta1_APIVersion(in *extensions.APIVersion, out *v1beta1.APIVersion, s conversion.Scope) error { - out.Name = in.Name +func autoConvert_extensions_AllowedFlexVolume_To_v1beta1_AllowedFlexVolume(in *extensions.AllowedFlexVolume, out *v1beta1.AllowedFlexVolume, s conversion.Scope) error { + out.Driver = in.Driver return nil } -// Convert_extensions_APIVersion_To_v1beta1_APIVersion is an autogenerated conversion function. -func Convert_extensions_APIVersion_To_v1beta1_APIVersion(in *extensions.APIVersion, out *v1beta1.APIVersion, s conversion.Scope) error { - return autoConvert_extensions_APIVersion_To_v1beta1_APIVersion(in, out, s) +// Convert_extensions_AllowedFlexVolume_To_v1beta1_AllowedFlexVolume is an autogenerated conversion function. +func Convert_extensions_AllowedFlexVolume_To_v1beta1_AllowedFlexVolume(in *extensions.AllowedFlexVolume, out *v1beta1.AllowedFlexVolume, s conversion.Scope) error { + return autoConvert_extensions_AllowedFlexVolume_To_v1beta1_AllowedFlexVolume(in, out, s) } func autoConvert_v1beta1_AllowedHostPath_To_extensions_AllowedHostPath(in *v1beta1.AllowedHostPath, out *extensions.AllowedHostPath, s conversion.Scope) error { @@ -303,6 +299,34 @@ func Convert_extensions_DaemonSet_To_v1beta1_DaemonSet(in *extensions.DaemonSet, return autoConvert_extensions_DaemonSet_To_v1beta1_DaemonSet(in, out, s) } +func autoConvert_v1beta1_DaemonSetCondition_To_extensions_DaemonSetCondition(in *v1beta1.DaemonSetCondition, out *extensions.DaemonSetCondition, s conversion.Scope) error { + out.Type = extensions.DaemonSetConditionType(in.Type) + out.Status = core.ConditionStatus(in.Status) + out.LastTransitionTime = in.LastTransitionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_v1beta1_DaemonSetCondition_To_extensions_DaemonSetCondition is an autogenerated conversion function. +func Convert_v1beta1_DaemonSetCondition_To_extensions_DaemonSetCondition(in *v1beta1.DaemonSetCondition, out *extensions.DaemonSetCondition, s conversion.Scope) error { + return autoConvert_v1beta1_DaemonSetCondition_To_extensions_DaemonSetCondition(in, out, s) +} + +func autoConvert_extensions_DaemonSetCondition_To_v1beta1_DaemonSetCondition(in *extensions.DaemonSetCondition, out *v1beta1.DaemonSetCondition, s conversion.Scope) error { + out.Type = v1beta1.DaemonSetConditionType(in.Type) + out.Status = v1.ConditionStatus(in.Status) + out.LastTransitionTime = in.LastTransitionTime + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +// Convert_extensions_DaemonSetCondition_To_v1beta1_DaemonSetCondition is an autogenerated conversion function. +func Convert_extensions_DaemonSetCondition_To_v1beta1_DaemonSetCondition(in *extensions.DaemonSetCondition, out *v1beta1.DaemonSetCondition, s conversion.Scope) error { + return autoConvert_extensions_DaemonSetCondition_To_v1beta1_DaemonSetCondition(in, out, s) +} + func autoConvert_v1beta1_DaemonSetList_To_extensions_DaemonSetList(in *v1beta1.DaemonSetList, out *extensions.DaemonSetList, s conversion.Scope) error { out.ListMeta = in.ListMeta if in.Items != nil { @@ -346,8 +370,8 @@ func Convert_extensions_DaemonSetList_To_v1beta1_DaemonSetList(in *extensions.Da } func autoConvert_v1beta1_DaemonSetSpec_To_extensions_DaemonSetSpec(in *v1beta1.DaemonSetSpec, out *extensions.DaemonSetSpec, s conversion.Scope) error { - out.Selector = (*v1.LabelSelector)(unsafe.Pointer(in.Selector)) - if err := api_v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) + if err := core_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if err := Convert_v1beta1_DaemonSetUpdateStrategy_To_extensions_DaemonSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, s); err != nil { @@ -365,8 +389,8 @@ func Convert_v1beta1_DaemonSetSpec_To_extensions_DaemonSetSpec(in *v1beta1.Daemo } func autoConvert_extensions_DaemonSetSpec_To_v1beta1_DaemonSetSpec(in *extensions.DaemonSetSpec, out *v1beta1.DaemonSetSpec, s conversion.Scope) error { - out.Selector = (*v1.LabelSelector)(unsafe.Pointer(in.Selector)) - if err := api_v1.Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) + if err := core_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if err := Convert_extensions_DaemonSetUpdateStrategy_To_v1beta1_DaemonSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, s); err != nil { @@ -393,6 +417,7 @@ func autoConvert_v1beta1_DaemonSetStatus_To_extensions_DaemonSetStatus(in *v1bet out.NumberAvailable = in.NumberAvailable out.NumberUnavailable = in.NumberUnavailable out.CollisionCount = (*int32)(unsafe.Pointer(in.CollisionCount)) + out.Conditions = *(*[]extensions.DaemonSetCondition)(unsafe.Pointer(&in.Conditions)) return nil } @@ -411,6 +436,7 @@ func autoConvert_extensions_DaemonSetStatus_To_v1beta1_DaemonSetStatus(in *exten out.NumberAvailable = in.NumberAvailable out.NumberUnavailable = in.NumberUnavailable out.CollisionCount = (*int32)(unsafe.Pointer(in.CollisionCount)) + out.Conditions = *(*[]v1beta1.DaemonSetCondition)(unsafe.Pointer(&in.Conditions)) return nil } @@ -491,7 +517,7 @@ func Convert_extensions_Deployment_To_v1beta1_Deployment(in *extensions.Deployme func autoConvert_v1beta1_DeploymentCondition_To_extensions_DeploymentCondition(in *v1beta1.DeploymentCondition, out *extensions.DeploymentCondition, s conversion.Scope) error { out.Type = extensions.DeploymentConditionType(in.Type) - out.Status = api.ConditionStatus(in.Status) + out.Status = core.ConditionStatus(in.Status) out.LastUpdateTime = in.LastUpdateTime out.LastTransitionTime = in.LastTransitionTime out.Reason = in.Reason @@ -506,7 +532,7 @@ func Convert_v1beta1_DeploymentCondition_To_extensions_DeploymentCondition(in *v func autoConvert_extensions_DeploymentCondition_To_v1beta1_DeploymentCondition(in *extensions.DeploymentCondition, out *v1beta1.DeploymentCondition, s conversion.Scope) error { out.Type = v1beta1.DeploymentConditionType(in.Type) - out.Status = core_v1.ConditionStatus(in.Status) + out.Status = v1.ConditionStatus(in.Status) out.LastUpdateTime = in.LastUpdateTime out.LastTransitionTime = in.LastTransitionTime out.Reason = in.Reason @@ -590,11 +616,11 @@ func Convert_extensions_DeploymentRollback_To_v1beta1_DeploymentRollback(in *ext } func autoConvert_v1beta1_DeploymentSpec_To_extensions_DeploymentSpec(in *v1beta1.DeploymentSpec, out *extensions.DeploymentSpec, s conversion.Scope) error { - if err := v1.Convert_Pointer_int32_To_int32(&in.Replicas, &out.Replicas, s); err != nil { + if err := meta_v1.Convert_Pointer_int32_To_int32(&in.Replicas, &out.Replicas, s); err != nil { return err } - out.Selector = (*v1.LabelSelector)(unsafe.Pointer(in.Selector)) - if err := api_v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) + if err := core_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if err := Convert_v1beta1_DeploymentStrategy_To_extensions_DeploymentStrategy(&in.Strategy, &out.Strategy, s); err != nil { @@ -609,11 +635,11 @@ func autoConvert_v1beta1_DeploymentSpec_To_extensions_DeploymentSpec(in *v1beta1 } func autoConvert_extensions_DeploymentSpec_To_v1beta1_DeploymentSpec(in *extensions.DeploymentSpec, out *v1beta1.DeploymentSpec, s conversion.Scope) error { - if err := v1.Convert_int32_To_Pointer_int32(&in.Replicas, &out.Replicas, s); err != nil { + if err := meta_v1.Convert_int32_To_Pointer_int32(&in.Replicas, &out.Replicas, s); err != nil { return err } - out.Selector = (*v1.LabelSelector)(unsafe.Pointer(in.Selector)) - if err := api_v1.Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) + if err := core_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } if err := Convert_extensions_DeploymentStrategy_To_v1beta1_DeploymentStrategy(&in.Strategy, &out.Strategy, s); err != nil { @@ -758,8 +784,8 @@ func Convert_extensions_HTTPIngressRuleValue_To_v1beta1_HTTPIngressRuleValue(in } func autoConvert_v1beta1_HostPortRange_To_extensions_HostPortRange(in *v1beta1.HostPortRange, out *extensions.HostPortRange, s conversion.Scope) error { - out.Min = int(in.Min) - out.Max = int(in.Max) + out.Min = in.Min + out.Max = in.Max return nil } @@ -769,8 +795,8 @@ func Convert_v1beta1_HostPortRange_To_extensions_HostPortRange(in *v1beta1.HostP } func autoConvert_extensions_HostPortRange_To_v1beta1_HostPortRange(in *extensions.HostPortRange, out *v1beta1.HostPortRange, s conversion.Scope) error { - out.Min = int32(in.Min) - out.Max = int32(in.Max) + out.Min = in.Min + out.Max = in.Max return nil } @@ -1043,22 +1069,12 @@ func Convert_extensions_PodSecurityPolicyList_To_v1beta1_PodSecurityPolicyList(i func autoConvert_v1beta1_PodSecurityPolicySpec_To_extensions_PodSecurityPolicySpec(in *v1beta1.PodSecurityPolicySpec, out *extensions.PodSecurityPolicySpec, s conversion.Scope) error { out.Privileged = in.Privileged - out.DefaultAddCapabilities = *(*[]api.Capability)(unsafe.Pointer(&in.DefaultAddCapabilities)) - out.RequiredDropCapabilities = *(*[]api.Capability)(unsafe.Pointer(&in.RequiredDropCapabilities)) - out.AllowedCapabilities = *(*[]api.Capability)(unsafe.Pointer(&in.AllowedCapabilities)) + out.DefaultAddCapabilities = *(*[]core.Capability)(unsafe.Pointer(&in.DefaultAddCapabilities)) + out.RequiredDropCapabilities = *(*[]core.Capability)(unsafe.Pointer(&in.RequiredDropCapabilities)) + out.AllowedCapabilities = *(*[]core.Capability)(unsafe.Pointer(&in.AllowedCapabilities)) out.Volumes = *(*[]extensions.FSType)(unsafe.Pointer(&in.Volumes)) out.HostNetwork = in.HostNetwork - if in.HostPorts != nil { - in, out := &in.HostPorts, &out.HostPorts - *out = make([]extensions.HostPortRange, len(*in)) - for i := range *in { - if err := Convert_v1beta1_HostPortRange_To_extensions_HostPortRange(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.HostPorts = nil - } + out.HostPorts = *(*[]extensions.HostPortRange)(unsafe.Pointer(&in.HostPorts)) out.HostPID = in.HostPID out.HostIPC = in.HostIPC if err := Convert_v1beta1_SELinuxStrategyOptions_To_extensions_SELinuxStrategyOptions(&in.SELinux, &out.SELinux, s); err != nil { @@ -1075,10 +1091,11 @@ func autoConvert_v1beta1_PodSecurityPolicySpec_To_extensions_PodSecurityPolicySp } out.ReadOnlyRootFilesystem = in.ReadOnlyRootFilesystem out.DefaultAllowPrivilegeEscalation = (*bool)(unsafe.Pointer(in.DefaultAllowPrivilegeEscalation)) - if err := v1.Convert_Pointer_bool_To_bool(&in.AllowPrivilegeEscalation, &out.AllowPrivilegeEscalation, s); err != nil { + if err := meta_v1.Convert_Pointer_bool_To_bool(&in.AllowPrivilegeEscalation, &out.AllowPrivilegeEscalation, s); err != nil { return err } out.AllowedHostPaths = *(*[]extensions.AllowedHostPath)(unsafe.Pointer(&in.AllowedHostPaths)) + out.AllowedFlexVolumes = *(*[]extensions.AllowedFlexVolume)(unsafe.Pointer(&in.AllowedFlexVolumes)) return nil } @@ -1089,22 +1106,12 @@ func Convert_v1beta1_PodSecurityPolicySpec_To_extensions_PodSecurityPolicySpec(i func autoConvert_extensions_PodSecurityPolicySpec_To_v1beta1_PodSecurityPolicySpec(in *extensions.PodSecurityPolicySpec, out *v1beta1.PodSecurityPolicySpec, s conversion.Scope) error { out.Privileged = in.Privileged - out.DefaultAddCapabilities = *(*[]core_v1.Capability)(unsafe.Pointer(&in.DefaultAddCapabilities)) - out.RequiredDropCapabilities = *(*[]core_v1.Capability)(unsafe.Pointer(&in.RequiredDropCapabilities)) - out.AllowedCapabilities = *(*[]core_v1.Capability)(unsafe.Pointer(&in.AllowedCapabilities)) + out.DefaultAddCapabilities = *(*[]v1.Capability)(unsafe.Pointer(&in.DefaultAddCapabilities)) + out.RequiredDropCapabilities = *(*[]v1.Capability)(unsafe.Pointer(&in.RequiredDropCapabilities)) + out.AllowedCapabilities = *(*[]v1.Capability)(unsafe.Pointer(&in.AllowedCapabilities)) out.Volumes = *(*[]v1beta1.FSType)(unsafe.Pointer(&in.Volumes)) out.HostNetwork = in.HostNetwork - if in.HostPorts != nil { - in, out := &in.HostPorts, &out.HostPorts - *out = make([]v1beta1.HostPortRange, len(*in)) - for i := range *in { - if err := Convert_extensions_HostPortRange_To_v1beta1_HostPortRange(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.HostPorts = nil - } + out.HostPorts = *(*[]v1beta1.HostPortRange)(unsafe.Pointer(&in.HostPorts)) out.HostPID = in.HostPID out.HostIPC = in.HostIPC if err := Convert_extensions_SELinuxStrategyOptions_To_v1beta1_SELinuxStrategyOptions(&in.SELinux, &out.SELinux, s); err != nil { @@ -1121,10 +1128,11 @@ func autoConvert_extensions_PodSecurityPolicySpec_To_v1beta1_PodSecurityPolicySp } out.ReadOnlyRootFilesystem = in.ReadOnlyRootFilesystem out.DefaultAllowPrivilegeEscalation = (*bool)(unsafe.Pointer(in.DefaultAllowPrivilegeEscalation)) - if err := v1.Convert_bool_To_Pointer_bool(&in.AllowPrivilegeEscalation, &out.AllowPrivilegeEscalation, s); err != nil { + if err := meta_v1.Convert_bool_To_Pointer_bool(&in.AllowPrivilegeEscalation, &out.AllowPrivilegeEscalation, s); err != nil { return err } out.AllowedHostPaths = *(*[]v1beta1.AllowedHostPath)(unsafe.Pointer(&in.AllowedHostPaths)) + out.AllowedFlexVolumes = *(*[]v1beta1.AllowedFlexVolume)(unsafe.Pointer(&in.AllowedFlexVolumes)) return nil } @@ -1162,7 +1170,7 @@ func Convert_extensions_ReplicaSet_To_v1beta1_ReplicaSet(in *extensions.ReplicaS func autoConvert_v1beta1_ReplicaSetCondition_To_extensions_ReplicaSetCondition(in *v1beta1.ReplicaSetCondition, out *extensions.ReplicaSetCondition, s conversion.Scope) error { out.Type = extensions.ReplicaSetConditionType(in.Type) - out.Status = api.ConditionStatus(in.Status) + out.Status = core.ConditionStatus(in.Status) out.LastTransitionTime = in.LastTransitionTime out.Reason = in.Reason out.Message = in.Message @@ -1176,7 +1184,7 @@ func Convert_v1beta1_ReplicaSetCondition_To_extensions_ReplicaSetCondition(in *v func autoConvert_extensions_ReplicaSetCondition_To_v1beta1_ReplicaSetCondition(in *extensions.ReplicaSetCondition, out *v1beta1.ReplicaSetCondition, s conversion.Scope) error { out.Type = v1beta1.ReplicaSetConditionType(in.Type) - out.Status = core_v1.ConditionStatus(in.Status) + out.Status = v1.ConditionStatus(in.Status) out.LastTransitionTime = in.LastTransitionTime out.Reason = in.Reason out.Message = in.Message @@ -1231,24 +1239,24 @@ func Convert_extensions_ReplicaSetList_To_v1beta1_ReplicaSetList(in *extensions. } func autoConvert_v1beta1_ReplicaSetSpec_To_extensions_ReplicaSetSpec(in *v1beta1.ReplicaSetSpec, out *extensions.ReplicaSetSpec, s conversion.Scope) error { - if err := v1.Convert_Pointer_int32_To_int32(&in.Replicas, &out.Replicas, s); err != nil { + if err := meta_v1.Convert_Pointer_int32_To_int32(&in.Replicas, &out.Replicas, s); err != nil { return err } out.MinReadySeconds = in.MinReadySeconds - out.Selector = (*v1.LabelSelector)(unsafe.Pointer(in.Selector)) - if err := api_v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) + if err := core_v1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } return nil } func autoConvert_extensions_ReplicaSetSpec_To_v1beta1_ReplicaSetSpec(in *extensions.ReplicaSetSpec, out *v1beta1.ReplicaSetSpec, s conversion.Scope) error { - if err := v1.Convert_int32_To_Pointer_int32(&in.Replicas, &out.Replicas, s); err != nil { + if err := meta_v1.Convert_int32_To_Pointer_int32(&in.Replicas, &out.Replicas, s); err != nil { return err } out.MinReadySeconds = in.MinReadySeconds - out.Selector = (*v1.LabelSelector)(unsafe.Pointer(in.Selector)) - if err := api_v1.Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + out.Selector = (*meta_v1.LabelSelector)(unsafe.Pointer(in.Selector)) + if err := core_v1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } return nil @@ -1368,7 +1376,7 @@ func Convert_extensions_RunAsUserStrategyOptions_To_v1beta1_RunAsUserStrategyOpt func autoConvert_v1beta1_SELinuxStrategyOptions_To_extensions_SELinuxStrategyOptions(in *v1beta1.SELinuxStrategyOptions, out *extensions.SELinuxStrategyOptions, s conversion.Scope) error { out.Rule = extensions.SELinuxStrategy(in.Rule) - out.SELinuxOptions = (*api.SELinuxOptions)(unsafe.Pointer(in.SELinuxOptions)) + out.SELinuxOptions = (*core.SELinuxOptions)(unsafe.Pointer(in.SELinuxOptions)) return nil } @@ -1379,7 +1387,7 @@ func Convert_v1beta1_SELinuxStrategyOptions_To_extensions_SELinuxStrategyOptions func autoConvert_extensions_SELinuxStrategyOptions_To_v1beta1_SELinuxStrategyOptions(in *extensions.SELinuxStrategyOptions, out *v1beta1.SELinuxStrategyOptions, s conversion.Scope) error { out.Rule = v1beta1.SELinuxStrategy(in.Rule) - out.SELinuxOptions = (*core_v1.SELinuxOptions)(unsafe.Pointer(in.SELinuxOptions)) + out.SELinuxOptions = (*v1.SELinuxOptions)(unsafe.Pointer(in.SELinuxOptions)) return nil } @@ -1388,68 +1396,68 @@ func Convert_extensions_SELinuxStrategyOptions_To_v1beta1_SELinuxStrategyOptions return autoConvert_extensions_SELinuxStrategyOptions_To_v1beta1_SELinuxStrategyOptions(in, out, s) } -func autoConvert_v1beta1_Scale_To_extensions_Scale(in *v1beta1.Scale, out *extensions.Scale, s conversion.Scope) error { +func autoConvert_v1beta1_Scale_To_autoscaling_Scale(in *v1beta1.Scale, out *autoscaling.Scale, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta - if err := Convert_v1beta1_ScaleSpec_To_extensions_ScaleSpec(&in.Spec, &out.Spec, s); err != nil { + if err := Convert_v1beta1_ScaleSpec_To_autoscaling_ScaleSpec(&in.Spec, &out.Spec, s); err != nil { return err } - if err := Convert_v1beta1_ScaleStatus_To_extensions_ScaleStatus(&in.Status, &out.Status, s); err != nil { + if err := Convert_v1beta1_ScaleStatus_To_autoscaling_ScaleStatus(&in.Status, &out.Status, s); err != nil { return err } return nil } -// Convert_v1beta1_Scale_To_extensions_Scale is an autogenerated conversion function. -func Convert_v1beta1_Scale_To_extensions_Scale(in *v1beta1.Scale, out *extensions.Scale, s conversion.Scope) error { - return autoConvert_v1beta1_Scale_To_extensions_Scale(in, out, s) +// Convert_v1beta1_Scale_To_autoscaling_Scale is an autogenerated conversion function. +func Convert_v1beta1_Scale_To_autoscaling_Scale(in *v1beta1.Scale, out *autoscaling.Scale, s conversion.Scope) error { + return autoConvert_v1beta1_Scale_To_autoscaling_Scale(in, out, s) } -func autoConvert_extensions_Scale_To_v1beta1_Scale(in *extensions.Scale, out *v1beta1.Scale, s conversion.Scope) error { +func autoConvert_autoscaling_Scale_To_v1beta1_Scale(in *autoscaling.Scale, out *v1beta1.Scale, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta - if err := Convert_extensions_ScaleSpec_To_v1beta1_ScaleSpec(&in.Spec, &out.Spec, s); err != nil { + if err := Convert_autoscaling_ScaleSpec_To_v1beta1_ScaleSpec(&in.Spec, &out.Spec, s); err != nil { return err } - if err := Convert_extensions_ScaleStatus_To_v1beta1_ScaleStatus(&in.Status, &out.Status, s); err != nil { + if err := Convert_autoscaling_ScaleStatus_To_v1beta1_ScaleStatus(&in.Status, &out.Status, s); err != nil { return err } return nil } -// Convert_extensions_Scale_To_v1beta1_Scale is an autogenerated conversion function. -func Convert_extensions_Scale_To_v1beta1_Scale(in *extensions.Scale, out *v1beta1.Scale, s conversion.Scope) error { - return autoConvert_extensions_Scale_To_v1beta1_Scale(in, out, s) +// Convert_autoscaling_Scale_To_v1beta1_Scale is an autogenerated conversion function. +func Convert_autoscaling_Scale_To_v1beta1_Scale(in *autoscaling.Scale, out *v1beta1.Scale, s conversion.Scope) error { + return autoConvert_autoscaling_Scale_To_v1beta1_Scale(in, out, s) } -func autoConvert_v1beta1_ScaleSpec_To_extensions_ScaleSpec(in *v1beta1.ScaleSpec, out *extensions.ScaleSpec, s conversion.Scope) error { +func autoConvert_v1beta1_ScaleSpec_To_autoscaling_ScaleSpec(in *v1beta1.ScaleSpec, out *autoscaling.ScaleSpec, s conversion.Scope) error { out.Replicas = in.Replicas return nil } -// Convert_v1beta1_ScaleSpec_To_extensions_ScaleSpec is an autogenerated conversion function. -func Convert_v1beta1_ScaleSpec_To_extensions_ScaleSpec(in *v1beta1.ScaleSpec, out *extensions.ScaleSpec, s conversion.Scope) error { - return autoConvert_v1beta1_ScaleSpec_To_extensions_ScaleSpec(in, out, s) +// Convert_v1beta1_ScaleSpec_To_autoscaling_ScaleSpec is an autogenerated conversion function. +func Convert_v1beta1_ScaleSpec_To_autoscaling_ScaleSpec(in *v1beta1.ScaleSpec, out *autoscaling.ScaleSpec, s conversion.Scope) error { + return autoConvert_v1beta1_ScaleSpec_To_autoscaling_ScaleSpec(in, out, s) } -func autoConvert_extensions_ScaleSpec_To_v1beta1_ScaleSpec(in *extensions.ScaleSpec, out *v1beta1.ScaleSpec, s conversion.Scope) error { +func autoConvert_autoscaling_ScaleSpec_To_v1beta1_ScaleSpec(in *autoscaling.ScaleSpec, out *v1beta1.ScaleSpec, s conversion.Scope) error { out.Replicas = in.Replicas return nil } -// Convert_extensions_ScaleSpec_To_v1beta1_ScaleSpec is an autogenerated conversion function. -func Convert_extensions_ScaleSpec_To_v1beta1_ScaleSpec(in *extensions.ScaleSpec, out *v1beta1.ScaleSpec, s conversion.Scope) error { - return autoConvert_extensions_ScaleSpec_To_v1beta1_ScaleSpec(in, out, s) +// Convert_autoscaling_ScaleSpec_To_v1beta1_ScaleSpec is an autogenerated conversion function. +func Convert_autoscaling_ScaleSpec_To_v1beta1_ScaleSpec(in *autoscaling.ScaleSpec, out *v1beta1.ScaleSpec, s conversion.Scope) error { + return autoConvert_autoscaling_ScaleSpec_To_v1beta1_ScaleSpec(in, out, s) } -func autoConvert_v1beta1_ScaleStatus_To_extensions_ScaleStatus(in *v1beta1.ScaleStatus, out *extensions.ScaleStatus, s conversion.Scope) error { +func autoConvert_v1beta1_ScaleStatus_To_autoscaling_ScaleStatus(in *v1beta1.ScaleStatus, out *autoscaling.ScaleStatus, s conversion.Scope) error { out.Replicas = in.Replicas - // WARNING: in.Selector requires manual conversion: inconvertible types (map[string]string vs *k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector) + // WARNING: in.Selector requires manual conversion: inconvertible types (map[string]string vs string) // WARNING: in.TargetSelector requires manual conversion: does not exist in peer-type return nil } -func autoConvert_extensions_ScaleStatus_To_v1beta1_ScaleStatus(in *extensions.ScaleStatus, out *v1beta1.ScaleStatus, s conversion.Scope) error { +func autoConvert_autoscaling_ScaleStatus_To_v1beta1_ScaleStatus(in *autoscaling.ScaleStatus, out *v1beta1.ScaleStatus, s conversion.Scope) error { out.Replicas = in.Replicas - // WARNING: in.Selector requires manual conversion: inconvertible types (*k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector vs map[string]string) + // WARNING: in.Selector requires manual conversion: inconvertible types (string vs map[string]string) return nil } @@ -1474,93 +1482,3 @@ func autoConvert_extensions_SupplementalGroupsStrategyOptions_To_v1beta1_Supplem func Convert_extensions_SupplementalGroupsStrategyOptions_To_v1beta1_SupplementalGroupsStrategyOptions(in *extensions.SupplementalGroupsStrategyOptions, out *v1beta1.SupplementalGroupsStrategyOptions, s conversion.Scope) error { return autoConvert_extensions_SupplementalGroupsStrategyOptions_To_v1beta1_SupplementalGroupsStrategyOptions(in, out, s) } - -func autoConvert_v1beta1_ThirdPartyResource_To_extensions_ThirdPartyResource(in *v1beta1.ThirdPartyResource, out *extensions.ThirdPartyResource, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Description = in.Description - out.Versions = *(*[]extensions.APIVersion)(unsafe.Pointer(&in.Versions)) - return nil -} - -// Convert_v1beta1_ThirdPartyResource_To_extensions_ThirdPartyResource is an autogenerated conversion function. -func Convert_v1beta1_ThirdPartyResource_To_extensions_ThirdPartyResource(in *v1beta1.ThirdPartyResource, out *extensions.ThirdPartyResource, s conversion.Scope) error { - return autoConvert_v1beta1_ThirdPartyResource_To_extensions_ThirdPartyResource(in, out, s) -} - -func autoConvert_extensions_ThirdPartyResource_To_v1beta1_ThirdPartyResource(in *extensions.ThirdPartyResource, out *v1beta1.ThirdPartyResource, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Description = in.Description - out.Versions = *(*[]v1beta1.APIVersion)(unsafe.Pointer(&in.Versions)) - return nil -} - -// Convert_extensions_ThirdPartyResource_To_v1beta1_ThirdPartyResource is an autogenerated conversion function. -func Convert_extensions_ThirdPartyResource_To_v1beta1_ThirdPartyResource(in *extensions.ThirdPartyResource, out *v1beta1.ThirdPartyResource, s conversion.Scope) error { - return autoConvert_extensions_ThirdPartyResource_To_v1beta1_ThirdPartyResource(in, out, s) -} - -func autoConvert_v1beta1_ThirdPartyResourceData_To_extensions_ThirdPartyResourceData(in *v1beta1.ThirdPartyResourceData, out *extensions.ThirdPartyResourceData, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Data = *(*[]byte)(unsafe.Pointer(&in.Data)) - return nil -} - -// Convert_v1beta1_ThirdPartyResourceData_To_extensions_ThirdPartyResourceData is an autogenerated conversion function. -func Convert_v1beta1_ThirdPartyResourceData_To_extensions_ThirdPartyResourceData(in *v1beta1.ThirdPartyResourceData, out *extensions.ThirdPartyResourceData, s conversion.Scope) error { - return autoConvert_v1beta1_ThirdPartyResourceData_To_extensions_ThirdPartyResourceData(in, out, s) -} - -func autoConvert_extensions_ThirdPartyResourceData_To_v1beta1_ThirdPartyResourceData(in *extensions.ThirdPartyResourceData, out *v1beta1.ThirdPartyResourceData, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Data = *(*[]byte)(unsafe.Pointer(&in.Data)) - return nil -} - -// Convert_extensions_ThirdPartyResourceData_To_v1beta1_ThirdPartyResourceData is an autogenerated conversion function. -func Convert_extensions_ThirdPartyResourceData_To_v1beta1_ThirdPartyResourceData(in *extensions.ThirdPartyResourceData, out *v1beta1.ThirdPartyResourceData, s conversion.Scope) error { - return autoConvert_extensions_ThirdPartyResourceData_To_v1beta1_ThirdPartyResourceData(in, out, s) -} - -func autoConvert_v1beta1_ThirdPartyResourceDataList_To_extensions_ThirdPartyResourceDataList(in *v1beta1.ThirdPartyResourceDataList, out *extensions.ThirdPartyResourceDataList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]extensions.ThirdPartyResourceData)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_v1beta1_ThirdPartyResourceDataList_To_extensions_ThirdPartyResourceDataList is an autogenerated conversion function. -func Convert_v1beta1_ThirdPartyResourceDataList_To_extensions_ThirdPartyResourceDataList(in *v1beta1.ThirdPartyResourceDataList, out *extensions.ThirdPartyResourceDataList, s conversion.Scope) error { - return autoConvert_v1beta1_ThirdPartyResourceDataList_To_extensions_ThirdPartyResourceDataList(in, out, s) -} - -func autoConvert_extensions_ThirdPartyResourceDataList_To_v1beta1_ThirdPartyResourceDataList(in *extensions.ThirdPartyResourceDataList, out *v1beta1.ThirdPartyResourceDataList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]v1beta1.ThirdPartyResourceData)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_extensions_ThirdPartyResourceDataList_To_v1beta1_ThirdPartyResourceDataList is an autogenerated conversion function. -func Convert_extensions_ThirdPartyResourceDataList_To_v1beta1_ThirdPartyResourceDataList(in *extensions.ThirdPartyResourceDataList, out *v1beta1.ThirdPartyResourceDataList, s conversion.Scope) error { - return autoConvert_extensions_ThirdPartyResourceDataList_To_v1beta1_ThirdPartyResourceDataList(in, out, s) -} - -func autoConvert_v1beta1_ThirdPartyResourceList_To_extensions_ThirdPartyResourceList(in *v1beta1.ThirdPartyResourceList, out *extensions.ThirdPartyResourceList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]extensions.ThirdPartyResource)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_v1beta1_ThirdPartyResourceList_To_extensions_ThirdPartyResourceList is an autogenerated conversion function. -func Convert_v1beta1_ThirdPartyResourceList_To_extensions_ThirdPartyResourceList(in *v1beta1.ThirdPartyResourceList, out *extensions.ThirdPartyResourceList, s conversion.Scope) error { - return autoConvert_v1beta1_ThirdPartyResourceList_To_extensions_ThirdPartyResourceList(in, out, s) -} - -func autoConvert_extensions_ThirdPartyResourceList_To_v1beta1_ThirdPartyResourceList(in *extensions.ThirdPartyResourceList, out *v1beta1.ThirdPartyResourceList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]v1beta1.ThirdPartyResource)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_extensions_ThirdPartyResourceList_To_v1beta1_ThirdPartyResourceList is an autogenerated conversion function. -func Convert_extensions_ThirdPartyResourceList_To_v1beta1_ThirdPartyResourceList(in *extensions.ThirdPartyResourceList, out *v1beta1.ThirdPartyResourceList, s conversion.Scope) error { - return autoConvert_extensions_ThirdPartyResourceList_To_v1beta1_ThirdPartyResourceList(in, out, s) -} diff --git a/pkg/apis/extensions/v1beta1/zz_generated.defaults.go b/pkg/apis/extensions/v1beta1/zz_generated.defaults.go index 11bd8e64018..6eb0bbedc54 100644 --- a/pkg/apis/extensions/v1beta1/zz_generated.defaults.go +++ b/pkg/apis/extensions/v1beta1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,7 +23,7 @@ package v1beta1 import ( v1beta1 "k8s.io/api/extensions/v1beta1" runtime "k8s.io/apimachinery/pkg/runtime" - v1 "k8s.io/kubernetes/pkg/api/v1" + v1 "k8s.io/kubernetes/pkg/apis/core/v1" ) // RegisterDefaults adds defaulters functions to the given scheme. diff --git a/pkg/apis/extensions/validation/BUILD b/pkg/apis/extensions/validation/BUILD index 797acbd579b..b9439bfac6d 100644 --- a/pkg/apis/extensions/validation/BUILD +++ b/pkg/apis/extensions/validation/BUILD @@ -11,8 +11,8 @@ go_library( srcs = ["validation.go"], importpath = "k8s.io/kubernetes/pkg/apis/extensions/validation", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/security/apparmor:go_default_library", "//pkg/security/podsecuritypolicy/seccomp:go_default_library", @@ -31,10 +31,10 @@ go_library( go_test( name = "go_default_test", srcs = ["validation_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/apis/extensions/validation", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/security/apparmor:go_default_library", "//pkg/security/podsecuritypolicy/seccomp:go_default_library", diff --git a/pkg/apis/extensions/validation/validation.go b/pkg/apis/extensions/validation/validation.go index 1206feedb7b..20cc279e8fd 100644 --- a/pkg/apis/extensions/validation/validation.go +++ b/pkg/apis/extensions/validation/validation.go @@ -33,8 +33,8 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" - apivalidation "k8s.io/kubernetes/pkg/api/validation" + api "k8s.io/kubernetes/pkg/apis/core" + apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/security/apparmor" "k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp" @@ -325,9 +325,7 @@ func ValidateDeploymentStatus(status *extensions.DeploymentStatus, fldPath *fiel if status.AvailableReplicas > status.Replicas { allErrs = append(allErrs, field.Invalid(fldPath.Child("availableReplicas"), status.AvailableReplicas, msg)) } - // TODO: ReadyReplicas is introduced in 1.6 and this check breaks the Deployment controller when pre-1.6 clusters get upgraded. - // Remove the comparison to zero once we stop supporting upgrades from 1.5. - if status.ReadyReplicas > 0 && status.AvailableReplicas > status.ReadyReplicas { + if status.AvailableReplicas > status.ReadyReplicas { allErrs = append(allErrs, field.Invalid(fldPath.Child("availableReplicas"), status.AvailableReplicas, "cannot be greater than readyReplicas")) } return allErrs @@ -353,7 +351,7 @@ func ValidateDeploymentStatusUpdate(update, old *extensions.Deployment) field.Er return allErrs } -// TODO: Move in "k8s.io/kubernetes/pkg/api/validation" +// TODO: Move in "k8s.io/kubernetes/pkg/apis/core/validation" func isDecremented(update, old *int32) bool { if update == nil && old != nil { return true @@ -522,17 +520,6 @@ func validateIngressBackend(backend *extensions.IngressBackend, fldPath *field.P return allErrs } -func ValidateScale(scale *extensions.Scale) field.ErrorList { - allErrs := field.ErrorList{} - allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&scale.ObjectMeta, true, apivalidation.NameIsDNSSubdomain, field.NewPath("metadata"))...) - - if scale.Spec.Replicas < 0 { - allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "replicas"), scale.Spec.Replicas, "must be greater than or equal to 0")) - } - - return allErrs -} - // ValidateReplicaSetName can be used to check whether the given ReplicaSet // name is valid. // Prefix indicates this name will be used as part of generation, in which case @@ -668,6 +655,7 @@ func ValidatePodSecurityPolicySpec(spec *extensions.PodSecurityPolicySpec, fldPa allErrs = append(allErrs, validatePSPCapsAgainstDrops(spec.RequiredDropCapabilities, spec.AllowedCapabilities, field.NewPath("allowedCapabilities"))...) allErrs = append(allErrs, validatePSPDefaultAllowPrivilegeEscalation(fldPath.Child("defaultAllowPrivilegeEscalation"), spec.DefaultAllowPrivilegeEscalation, spec.AllowPrivilegeEscalation)...) allErrs = append(allErrs, validatePSPAllowedHostPaths(fldPath.Child("allowedHostPaths"), spec.AllowedHostPaths)...) + allErrs = append(allErrs, validatePSPAllowedFlexVolumes(fldPath.Child("allowedFlexVolumes"), spec.AllowedFlexVolumes)...) return allErrs } @@ -734,6 +722,20 @@ func validatePSPAllowedHostPaths(fldPath *field.Path, allowedHostPaths []extensi return allErrs } +// validatePSPAllowedFlexVolumes +func validatePSPAllowedFlexVolumes(fldPath *field.Path, flexVolumes []extensions.AllowedFlexVolume) field.ErrorList { + allErrs := field.ErrorList{} + if len(flexVolumes) > 0 { + for idx, fv := range flexVolumes { + if len(fv.Driver) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("allowedFlexVolumes").Index(idx).Child("driver"), + "must specify a driver")) + } + } + } + return allErrs +} + // validatePSPSELinux validates the SELinux fields of PodSecurityPolicy. func validatePSPSELinux(fldPath *field.Path, seLinux *extensions.SELinuxStrategyOptions) field.ErrorList { allErrs := field.ErrorList{} @@ -815,7 +817,6 @@ func validatePodSecurityPolicyVolumes(fldPath *field.Path, volumes []extensions. allErrs = append(allErrs, field.NotSupported(fldPath.Child("volumes"), v, allowed.List())) } } - return allErrs } diff --git a/pkg/apis/extensions/validation/validation_test.go b/pkg/apis/extensions/validation/validation_test.go index 980a6f47254..d3a0d0fce1d 100644 --- a/pkg/apis/extensions/validation/validation_test.go +++ b/pkg/apis/extensions/validation/validation_test.go @@ -26,7 +26,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/security/apparmor" "k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp" @@ -1232,11 +1232,6 @@ func TestValidateDeployment(t *testing.T) { } } -func int64p(i int) *int64 { - i64 := int64(i) - return &i64 -} - func TestValidateDeploymentStatus(t *testing.T) { collisionCount := int32(-3) tests := []struct { @@ -1335,15 +1330,6 @@ func TestValidateDeploymentStatus(t *testing.T) { observedGeneration: 1, expectedErr: true, }, - // TODO: Remove the following test case once we stop supporting upgrades from 1.5. - { - name: "don't validate readyReplicas when it's zero", - replicas: 3, - readyReplicas: 0, - availableReplicas: 3, - observedGeneration: 1, - expectedErr: false, - }, { name: "invalid collisionCount", replicas: 3, @@ -1482,8 +1468,6 @@ func TestValidateDeploymentRollback(t *testing.T) { } } -type ingressRules map[string]string - func TestValidateIngress(t *testing.T) { defaultBackend := extensions.IngressBackend{ ServiceName: "default-backend", @@ -1750,70 +1734,6 @@ func TestValidateIngressStatusUpdate(t *testing.T) { } } -func TestValidateScale(t *testing.T) { - successCases := []extensions.Scale{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "frontend", - Namespace: metav1.NamespaceDefault, - }, - Spec: extensions.ScaleSpec{ - Replicas: 1, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "frontend", - Namespace: metav1.NamespaceDefault, - }, - Spec: extensions.ScaleSpec{ - Replicas: 10, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "frontend", - Namespace: metav1.NamespaceDefault, - }, - Spec: extensions.ScaleSpec{ - Replicas: 0, - }, - }, - } - - for _, successCase := range successCases { - if errs := ValidateScale(&successCase); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - } - - errorCases := []struct { - scale extensions.Scale - msg string - }{ - { - scale: extensions.Scale{ - ObjectMeta: metav1.ObjectMeta{ - Name: "frontend", - Namespace: metav1.NamespaceDefault, - }, - Spec: extensions.ScaleSpec{ - Replicas: -1, - }, - }, - msg: "must be greater than or equal to 0", - }, - } - - for _, c := range errorCases { - if errs := ValidateScale(&c.scale); len(errs) == 0 { - t.Errorf("expected failure for %s", c.msg) - } else if !strings.Contains(errs[0].Error(), c.msg) { - t.Errorf("unexpected error: %v, expected: %s", errs[0], c.msg) - } - } -} - func TestValidateReplicaSetStatus(t *testing.T) { tests := []struct { name string @@ -2523,6 +2443,13 @@ func TestValidatePodSecurityPolicy(t *testing.T) { pe := true invalidDefaultAllowPrivilegeEscalation.Spec.DefaultAllowPrivilegeEscalation = &pe + emptyFlexDriver := validPSP() + emptyFlexDriver.Spec.Volumes = []extensions.FSType{extensions.FlexVolume} + emptyFlexDriver.Spec.AllowedFlexVolumes = []extensions.AllowedFlexVolume{{}} + + nonEmptyFlexVolumes := validPSP() + nonEmptyFlexVolumes.Spec.AllowedFlexVolumes = []extensions.AllowedFlexVolume{{Driver: "example/driver"}} + type testCase struct { psp *extensions.PodSecurityPolicy errorType field.ErrorType @@ -2654,6 +2581,11 @@ func TestValidatePodSecurityPolicy(t *testing.T) { errorType: field.ErrorTypeInvalid, errorDetail: "must not contain '..'", }, + "empty flex volume driver": { + psp: emptyFlexDriver, + errorType: field.ErrorTypeRequired, + errorDetail: "must specify a driver", + }, } for k, v := range errorCases { @@ -2733,6 +2665,17 @@ func TestValidatePodSecurityPolicy(t *testing.T) { validDefaultAllowPrivilegeEscalation.Spec.DefaultAllowPrivilegeEscalation = &pe validDefaultAllowPrivilegeEscalation.Spec.AllowPrivilegeEscalation = true + flexvolumeWhenFlexVolumesAllowed := validPSP() + flexvolumeWhenFlexVolumesAllowed.Spec.Volumes = []extensions.FSType{extensions.FlexVolume} + flexvolumeWhenFlexVolumesAllowed.Spec.AllowedFlexVolumes = []extensions.AllowedFlexVolume{ + {Driver: "example/driver1"}, + } + + flexvolumeWhenAllVolumesAllowed := validPSP() + flexvolumeWhenAllVolumesAllowed.Spec.Volumes = []extensions.FSType{extensions.All} + flexvolumeWhenAllVolumesAllowed.Spec.AllowedFlexVolumes = []extensions.AllowedFlexVolume{ + {Driver: "example/driver2"}, + } successCases := map[string]struct { psp *extensions.PodSecurityPolicy }{ @@ -2763,6 +2706,12 @@ func TestValidatePodSecurityPolicy(t *testing.T) { "valid defaultAllowPrivilegeEscalation as true": { psp: validDefaultAllowPrivilegeEscalation, }, + "allow white-listed flexVolume when flex volumes are allowed": { + psp: flexvolumeWhenFlexVolumesAllowed, + }, + "allow white-listed flexVolume when all volumes are allowed": { + psp: flexvolumeWhenAllVolumesAllowed, + }, } for k, v := range successCases { diff --git a/pkg/apis/extensions/zz_generated.deepcopy.go b/pkg/apis/extensions/zz_generated.deepcopy.go index d41048a5a88..852d4f09a9c 100644 --- a/pkg/apis/extensions/zz_generated.deepcopy.go +++ b/pkg/apis/extensions/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,253 +22,22 @@ package extensions import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - api "k8s.io/kubernetes/pkg/api" - reflect "reflect" + core "k8s.io/kubernetes/pkg/apis/core" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*APIVersion).DeepCopyInto(out.(*APIVersion)) - return nil - }, InType: reflect.TypeOf(&APIVersion{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AllowedHostPath).DeepCopyInto(out.(*AllowedHostPath)) - return nil - }, InType: reflect.TypeOf(&AllowedHostPath{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CustomMetricCurrentStatus).DeepCopyInto(out.(*CustomMetricCurrentStatus)) - return nil - }, InType: reflect.TypeOf(&CustomMetricCurrentStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CustomMetricCurrentStatusList).DeepCopyInto(out.(*CustomMetricCurrentStatusList)) - return nil - }, InType: reflect.TypeOf(&CustomMetricCurrentStatusList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CustomMetricTarget).DeepCopyInto(out.(*CustomMetricTarget)) - return nil - }, InType: reflect.TypeOf(&CustomMetricTarget{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CustomMetricTargetList).DeepCopyInto(out.(*CustomMetricTargetList)) - return nil - }, InType: reflect.TypeOf(&CustomMetricTargetList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DaemonSet).DeepCopyInto(out.(*DaemonSet)) - return nil - }, InType: reflect.TypeOf(&DaemonSet{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DaemonSetList).DeepCopyInto(out.(*DaemonSetList)) - return nil - }, InType: reflect.TypeOf(&DaemonSetList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DaemonSetSpec).DeepCopyInto(out.(*DaemonSetSpec)) - return nil - }, InType: reflect.TypeOf(&DaemonSetSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DaemonSetStatus).DeepCopyInto(out.(*DaemonSetStatus)) - return nil - }, InType: reflect.TypeOf(&DaemonSetStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DaemonSetUpdateStrategy).DeepCopyInto(out.(*DaemonSetUpdateStrategy)) - return nil - }, InType: reflect.TypeOf(&DaemonSetUpdateStrategy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Deployment).DeepCopyInto(out.(*Deployment)) - return nil - }, InType: reflect.TypeOf(&Deployment{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeploymentCondition).DeepCopyInto(out.(*DeploymentCondition)) - return nil - }, InType: reflect.TypeOf(&DeploymentCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeploymentList).DeepCopyInto(out.(*DeploymentList)) - return nil - }, InType: reflect.TypeOf(&DeploymentList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeploymentRollback).DeepCopyInto(out.(*DeploymentRollback)) - return nil - }, InType: reflect.TypeOf(&DeploymentRollback{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeploymentSpec).DeepCopyInto(out.(*DeploymentSpec)) - return nil - }, InType: reflect.TypeOf(&DeploymentSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeploymentStatus).DeepCopyInto(out.(*DeploymentStatus)) - return nil - }, InType: reflect.TypeOf(&DeploymentStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeploymentStrategy).DeepCopyInto(out.(*DeploymentStrategy)) - return nil - }, InType: reflect.TypeOf(&DeploymentStrategy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*FSGroupStrategyOptions).DeepCopyInto(out.(*FSGroupStrategyOptions)) - return nil - }, InType: reflect.TypeOf(&FSGroupStrategyOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*GroupIDRange).DeepCopyInto(out.(*GroupIDRange)) - return nil - }, InType: reflect.TypeOf(&GroupIDRange{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HTTPIngressPath).DeepCopyInto(out.(*HTTPIngressPath)) - return nil - }, InType: reflect.TypeOf(&HTTPIngressPath{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HTTPIngressRuleValue).DeepCopyInto(out.(*HTTPIngressRuleValue)) - return nil - }, InType: reflect.TypeOf(&HTTPIngressRuleValue{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HostPortRange).DeepCopyInto(out.(*HostPortRange)) - return nil - }, InType: reflect.TypeOf(&HostPortRange{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Ingress).DeepCopyInto(out.(*Ingress)) - return nil - }, InType: reflect.TypeOf(&Ingress{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*IngressBackend).DeepCopyInto(out.(*IngressBackend)) - return nil - }, InType: reflect.TypeOf(&IngressBackend{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*IngressList).DeepCopyInto(out.(*IngressList)) - return nil - }, InType: reflect.TypeOf(&IngressList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*IngressRule).DeepCopyInto(out.(*IngressRule)) - return nil - }, InType: reflect.TypeOf(&IngressRule{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*IngressRuleValue).DeepCopyInto(out.(*IngressRuleValue)) - return nil - }, InType: reflect.TypeOf(&IngressRuleValue{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*IngressSpec).DeepCopyInto(out.(*IngressSpec)) - return nil - }, InType: reflect.TypeOf(&IngressSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*IngressStatus).DeepCopyInto(out.(*IngressStatus)) - return nil - }, InType: reflect.TypeOf(&IngressStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*IngressTLS).DeepCopyInto(out.(*IngressTLS)) - return nil - }, InType: reflect.TypeOf(&IngressTLS{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodSecurityPolicy).DeepCopyInto(out.(*PodSecurityPolicy)) - return nil - }, InType: reflect.TypeOf(&PodSecurityPolicy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodSecurityPolicyList).DeepCopyInto(out.(*PodSecurityPolicyList)) - return nil - }, InType: reflect.TypeOf(&PodSecurityPolicyList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodSecurityPolicySpec).DeepCopyInto(out.(*PodSecurityPolicySpec)) - return nil - }, InType: reflect.TypeOf(&PodSecurityPolicySpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicaSet).DeepCopyInto(out.(*ReplicaSet)) - return nil - }, InType: reflect.TypeOf(&ReplicaSet{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicaSetCondition).DeepCopyInto(out.(*ReplicaSetCondition)) - return nil - }, InType: reflect.TypeOf(&ReplicaSetCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicaSetList).DeepCopyInto(out.(*ReplicaSetList)) - return nil - }, InType: reflect.TypeOf(&ReplicaSetList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicaSetSpec).DeepCopyInto(out.(*ReplicaSetSpec)) - return nil - }, InType: reflect.TypeOf(&ReplicaSetSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicaSetStatus).DeepCopyInto(out.(*ReplicaSetStatus)) - return nil - }, InType: reflect.TypeOf(&ReplicaSetStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicationControllerDummy).DeepCopyInto(out.(*ReplicationControllerDummy)) - return nil - }, InType: reflect.TypeOf(&ReplicationControllerDummy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RollbackConfig).DeepCopyInto(out.(*RollbackConfig)) - return nil - }, InType: reflect.TypeOf(&RollbackConfig{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RollingUpdateDaemonSet).DeepCopyInto(out.(*RollingUpdateDaemonSet)) - return nil - }, InType: reflect.TypeOf(&RollingUpdateDaemonSet{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RollingUpdateDeployment).DeepCopyInto(out.(*RollingUpdateDeployment)) - return nil - }, InType: reflect.TypeOf(&RollingUpdateDeployment{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RunAsUserStrategyOptions).DeepCopyInto(out.(*RunAsUserStrategyOptions)) - return nil - }, InType: reflect.TypeOf(&RunAsUserStrategyOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SELinuxStrategyOptions).DeepCopyInto(out.(*SELinuxStrategyOptions)) - return nil - }, InType: reflect.TypeOf(&SELinuxStrategyOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Scale).DeepCopyInto(out.(*Scale)) - return nil - }, InType: reflect.TypeOf(&Scale{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ScaleSpec).DeepCopyInto(out.(*ScaleSpec)) - return nil - }, InType: reflect.TypeOf(&ScaleSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ScaleStatus).DeepCopyInto(out.(*ScaleStatus)) - return nil - }, InType: reflect.TypeOf(&ScaleStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SupplementalGroupsStrategyOptions).DeepCopyInto(out.(*SupplementalGroupsStrategyOptions)) - return nil - }, InType: reflect.TypeOf(&SupplementalGroupsStrategyOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ThirdPartyResource).DeepCopyInto(out.(*ThirdPartyResource)) - return nil - }, InType: reflect.TypeOf(&ThirdPartyResource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ThirdPartyResourceData).DeepCopyInto(out.(*ThirdPartyResourceData)) - return nil - }, InType: reflect.TypeOf(&ThirdPartyResourceData{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ThirdPartyResourceDataList).DeepCopyInto(out.(*ThirdPartyResourceDataList)) - return nil - }, InType: reflect.TypeOf(&ThirdPartyResourceDataList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ThirdPartyResourceList).DeepCopyInto(out.(*ThirdPartyResourceList)) - return nil - }, InType: reflect.TypeOf(&ThirdPartyResourceList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*UserIDRange).DeepCopyInto(out.(*UserIDRange)) - return nil - }, InType: reflect.TypeOf(&UserIDRange{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *APIVersion) DeepCopyInto(out *APIVersion) { +func (in *AllowedFlexVolume) DeepCopyInto(out *AllowedFlexVolume) { *out = *in return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIVersion. -func (in *APIVersion) DeepCopy() *APIVersion { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedFlexVolume. +func (in *AllowedFlexVolume) DeepCopy() *AllowedFlexVolume { if in == nil { return nil } - out := new(APIVersion) + out := new(AllowedFlexVolume) in.DeepCopyInto(out) return out } @@ -398,6 +167,23 @@ func (in *DaemonSet) DeepCopyObject() runtime.Object { } } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DaemonSetCondition) DeepCopyInto(out *DaemonSetCondition) { + *out = *in + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DaemonSetCondition. +func (in *DaemonSetCondition) DeepCopy() *DaemonSetCondition { + if in == nil { + return nil + } + out := new(DaemonSetCondition) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DaemonSetList) DeepCopyInto(out *DaemonSetList) { *out = *in @@ -480,6 +266,13 @@ func (in *DaemonSetStatus) DeepCopyInto(out *DaemonSetStatus) { **out = **in } } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]DaemonSetCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -1101,17 +894,17 @@ func (in *PodSecurityPolicySpec) DeepCopyInto(out *PodSecurityPolicySpec) { *out = *in if in.DefaultAddCapabilities != nil { in, out := &in.DefaultAddCapabilities, &out.DefaultAddCapabilities - *out = make([]api.Capability, len(*in)) + *out = make([]core.Capability, len(*in)) copy(*out, *in) } if in.RequiredDropCapabilities != nil { in, out := &in.RequiredDropCapabilities, &out.RequiredDropCapabilities - *out = make([]api.Capability, len(*in)) + *out = make([]core.Capability, len(*in)) copy(*out, *in) } if in.AllowedCapabilities != nil { in, out := &in.AllowedCapabilities, &out.AllowedCapabilities - *out = make([]api.Capability, len(*in)) + *out = make([]core.Capability, len(*in)) copy(*out, *in) } if in.Volumes != nil { @@ -1142,6 +935,11 @@ func (in *PodSecurityPolicySpec) DeepCopyInto(out *PodSecurityPolicySpec) { *out = make([]AllowedHostPath, len(*in)) copy(*out, *in) } + if in.AllowedFlexVolumes != nil { + in, out := &in.AllowedFlexVolumes, &out.AllowedFlexVolumes + *out = make([]AllowedFlexVolume, len(*in)) + copy(*out, *in) + } return } @@ -1390,7 +1188,7 @@ func (in *SELinuxStrategyOptions) DeepCopyInto(out *SELinuxStrategyOptions) { if *in == nil { *out = nil } else { - *out = new(api.SELinuxOptions) + *out = new(core.SELinuxOptions) **out = **in } } @@ -1407,76 +1205,6 @@ func (in *SELinuxStrategyOptions) DeepCopy() *SELinuxStrategyOptions { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Scale) DeepCopyInto(out *Scale) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Scale. -func (in *Scale) DeepCopy() *Scale { - if in == nil { - return nil - } - out := new(Scale) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Scale) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ScaleSpec) DeepCopyInto(out *ScaleSpec) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScaleSpec. -func (in *ScaleSpec) DeepCopy() *ScaleSpec { - if in == nil { - return nil - } - out := new(ScaleSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ScaleStatus) DeepCopyInto(out *ScaleStatus) { - *out = *in - if in.Selector != nil { - in, out := &in.Selector, &out.Selector - if *in == nil { - *out = nil - } else { - *out = new(v1.LabelSelector) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScaleStatus. -func (in *ScaleStatus) DeepCopy() *ScaleStatus { - if in == nil { - return nil - } - out := new(ScaleStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SupplementalGroupsStrategyOptions) DeepCopyInto(out *SupplementalGroupsStrategyOptions) { *out = *in @@ -1498,138 +1226,6 @@ func (in *SupplementalGroupsStrategyOptions) DeepCopy() *SupplementalGroupsStrat return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ThirdPartyResource) DeepCopyInto(out *ThirdPartyResource) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.Versions != nil { - in, out := &in.Versions, &out.Versions - *out = make([]APIVersion, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ThirdPartyResource. -func (in *ThirdPartyResource) DeepCopy() *ThirdPartyResource { - if in == nil { - return nil - } - out := new(ThirdPartyResource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ThirdPartyResource) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ThirdPartyResourceData) DeepCopyInto(out *ThirdPartyResourceData) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.Data != nil { - in, out := &in.Data, &out.Data - *out = make([]byte, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ThirdPartyResourceData. -func (in *ThirdPartyResourceData) DeepCopy() *ThirdPartyResourceData { - if in == nil { - return nil - } - out := new(ThirdPartyResourceData) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ThirdPartyResourceData) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ThirdPartyResourceDataList) DeepCopyInto(out *ThirdPartyResourceDataList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ThirdPartyResourceData, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ThirdPartyResourceDataList. -func (in *ThirdPartyResourceDataList) DeepCopy() *ThirdPartyResourceDataList { - if in == nil { - return nil - } - out := new(ThirdPartyResourceDataList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ThirdPartyResourceDataList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ThirdPartyResourceList) DeepCopyInto(out *ThirdPartyResourceList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ThirdPartyResource, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ThirdPartyResourceList. -func (in *ThirdPartyResourceList) DeepCopy() *ThirdPartyResourceList { - if in == nil { - return nil - } - out := new(ThirdPartyResourceList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ThirdPartyResourceList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *UserIDRange) DeepCopyInto(out *UserIDRange) { *out = *in diff --git a/pkg/apis/imagepolicy/BUILD b/pkg/apis/imagepolicy/BUILD index 67e196967d1..9dd53f7d33c 100644 --- a/pkg/apis/imagepolicy/BUILD +++ b/pkg/apis/imagepolicy/BUILD @@ -16,7 +16,6 @@ go_library( importpath = "k8s.io/kubernetes/pkg/apis/imagepolicy", deps = [ "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/pkg/apis/imagepolicy/doc.go b/pkg/apis/imagepolicy/doc.go index e69aee39738..a0af1868e13 100644 --- a/pkg/apis/imagepolicy/doc.go +++ b/pkg/apis/imagepolicy/doc.go @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +groupName=imagepolicy.k8s.io package imagepolicy // import "k8s.io/kubernetes/pkg/apis/imagepolicy" diff --git a/pkg/apis/imagepolicy/install/BUILD b/pkg/apis/imagepolicy/install/BUILD index bcb06b88302..77b3a196e84 100644 --- a/pkg/apis/imagepolicy/install/BUILD +++ b/pkg/apis/imagepolicy/install/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["install.go"], importpath = "k8s.io/kubernetes/pkg/apis/imagepolicy/install", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/imagepolicy:go_default_library", "//pkg/apis/imagepolicy/v1alpha1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", diff --git a/pkg/apis/imagepolicy/install/install.go b/pkg/apis/imagepolicy/install/install.go index cf03fd483c2..75f5ba1f44e 100644 --- a/pkg/apis/imagepolicy/install/install.go +++ b/pkg/apis/imagepolicy/install/install.go @@ -23,13 +23,13 @@ import ( "k8s.io/apimachinery/pkg/apimachinery/registered" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/imagepolicy" "k8s.io/kubernetes/pkg/apis/imagepolicy/v1alpha1" ) func init() { - Install(api.GroupFactoryRegistry, api.Registry, api.Scheme) + Install(legacyscheme.GroupFactoryRegistry, legacyscheme.Registry, legacyscheme.Scheme) } // Install registers the API group and adds types to a scheme diff --git a/pkg/apis/imagepolicy/v1alpha1/doc.go b/pkg/apis/imagepolicy/v1alpha1/doc.go index dbd99ca4862..b517ce45438 100644 --- a/pkg/apis/imagepolicy/v1alpha1/doc.go +++ b/pkg/apis/imagepolicy/v1alpha1/doc.go @@ -15,7 +15,7 @@ limitations under the License. */ // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/imagepolicy -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/imagepolicy/v1alpha1 +// +k8s:conversion-gen-external-types=k8s.io/api/imagepolicy/v1alpha1 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/imagepolicy/v1alpha1 diff --git a/pkg/apis/imagepolicy/v1alpha1/zz_generated.conversion.go b/pkg/apis/imagepolicy/v1alpha1/zz_generated.conversion.go index 3cf6299140a..ad1a808f71c 100644 --- a/pkg/apis/imagepolicy/v1alpha1/zz_generated.conversion.go +++ b/pkg/apis/imagepolicy/v1alpha1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,11 +21,12 @@ limitations under the License. package v1alpha1 import ( + unsafe "unsafe" + v1alpha1 "k8s.io/api/imagepolicy/v1alpha1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" imagepolicy "k8s.io/kubernetes/pkg/apis/imagepolicy" - unsafe "unsafe" ) func init() { diff --git a/pkg/apis/imagepolicy/v1alpha1/zz_generated.defaults.go b/pkg/apis/imagepolicy/v1alpha1/zz_generated.defaults.go index 7e6df29d4ae..5e24d22cacd 100644 --- a/pkg/apis/imagepolicy/v1alpha1/zz_generated.defaults.go +++ b/pkg/apis/imagepolicy/v1alpha1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/apis/imagepolicy/zz_generated.deepcopy.go b/pkg/apis/imagepolicy/zz_generated.deepcopy.go index aa752870e5a..790256b218f 100644 --- a/pkg/apis/imagepolicy/zz_generated.deepcopy.go +++ b/pkg/apis/imagepolicy/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,40 +21,9 @@ limitations under the License. package imagepolicy import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ImageReview).DeepCopyInto(out.(*ImageReview)) - return nil - }, InType: reflect.TypeOf(&ImageReview{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ImageReviewContainerSpec).DeepCopyInto(out.(*ImageReviewContainerSpec)) - return nil - }, InType: reflect.TypeOf(&ImageReviewContainerSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ImageReviewSpec).DeepCopyInto(out.(*ImageReviewSpec)) - return nil - }, InType: reflect.TypeOf(&ImageReviewSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ImageReviewStatus).DeepCopyInto(out.(*ImageReviewStatus)) - return nil - }, InType: reflect.TypeOf(&ImageReviewStatus{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ImageReview) DeepCopyInto(out *ImageReview) { *out = *in diff --git a/pkg/apis/meta/v1/BUILD b/pkg/apis/meta/v1/BUILD deleted file mode 100644 index 1ceec85713b..00000000000 --- a/pkg/apis/meta/v1/BUILD +++ /dev/null @@ -1,30 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["time.go"], - importpath = "k8s.io/kubernetes/pkg/apis/meta/v1", - deps = [ - "//vendor/github.com/go-openapi/spec:go_default_library", - "//vendor/github.com/google/gofuzz:go_default_library", - "//vendor/k8s.io/kube-openapi/pkg/common:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/pkg/apis/meta/v1/time.go b/pkg/apis/meta/v1/time.go deleted file mode 100644 index c73d7ca6335..00000000000 --- a/pkg/apis/meta/v1/time.go +++ /dev/null @@ -1,180 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES 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 ( - "encoding/json" - "time" - - openapi "k8s.io/kube-openapi/pkg/common" - - "github.com/go-openapi/spec" - "github.com/google/gofuzz" -) - -// 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. -// -// +protobuf.options.marshal=false -// +protobuf.as=Timestamp -// +protobuf.options.(gogoproto.goproto_stringer)=false -type Time struct { - time.Time `protobuf:"-"` -} - -// DeepCopyInto creates a deep-copy of the Time value. The underlying time.Time -// type is effectively immutable in the time API, so it is safe to -// copy-by-assign, despite the presence of (unexported) Pointer fields. -func (t *Time) DeepCopyInto(out *Time) { - *out = *t -} - -// String returns the representation of the time. -func (t Time) String() string { - return t.Time.String() -} - -// NewTime returns a wrapped instance of the provided time -func NewTime(time time.Time) Time { - return Time{time} -} - -// Date returns the Time corresponding to the supplied parameters -// by wrapping time.Date. -func Date(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) Time { - return Time{time.Date(year, month, day, hour, min, sec, nsec, loc)} -} - -// Now returns the current local time. -func Now() Time { - return Time{time.Now()} -} - -// IsZero returns true if the value is nil or time is zero. -func (t *Time) IsZero() bool { - if t == nil { - return true - } - return t.Time.IsZero() -} - -// Before reports whether the time instant t is before u. -func (t Time) Before(u Time) bool { - return t.Time.Before(u.Time) -} - -// Equal reports whether the time instant t is equal to u. -func (t Time) Equal(u Time) bool { - return t.Time.Equal(u.Time) -} - -// Unix returns the local time corresponding to the given Unix time -// by wrapping time.Unix. -func Unix(sec int64, nsec int64) Time { - return Time{time.Unix(sec, nsec)} -} - -// Rfc3339Copy returns a copy of the Time at second-level precision. -func (t Time) Rfc3339Copy() Time { - copied, _ := time.Parse(time.RFC3339, t.Format(time.RFC3339)) - return Time{copied} -} - -// UnmarshalJSON implements the json.Unmarshaller interface. -func (t *Time) UnmarshalJSON(b []byte) error { - if len(b) == 4 && string(b) == "null" { - t.Time = time.Time{} - return nil - } - - var str string - json.Unmarshal(b, &str) - - pt, err := time.Parse(time.RFC3339, str) - if err != nil { - return err - } - - t.Time = pt.Local() - return nil -} - -// UnmarshalQueryParameter converts from a URL query parameter value to an object -func (t *Time) UnmarshalQueryParameter(str string) error { - if len(str) == 0 { - t.Time = time.Time{} - return nil - } - // Tolerate requests from older clients that used JSON serialization to build query params - if len(str) == 4 && str == "null" { - t.Time = time.Time{} - return nil - } - - pt, err := time.Parse(time.RFC3339, str) - if err != nil { - return err - } - - t.Time = pt.Local() - return nil -} - -// MarshalJSON implements the json.Marshaler interface. -func (t Time) MarshalJSON() ([]byte, error) { - if t.IsZero() { - // Encode unset/nil objects as JSON's "null". - return []byte("null"), nil - } - - return json.Marshal(t.UTC().Format(time.RFC3339)) -} - -func (_ Time) OpenAPIDefinition() openapi.OpenAPIDefinition { - return openapi.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "date-time", - }, - }, - } -} - -// MarshalQueryParameter converts to a URL query parameter value -func (t Time) MarshalQueryParameter() (string, error) { - if t.IsZero() { - // Encode unset/nil objects as an empty string - return "", nil - } - - return t.UTC().Format(time.RFC3339), nil -} - -// Fuzz satisfies fuzz.Interface. -func (t *Time) Fuzz(c fuzz.Continue) { - if t == nil { - return - } - // Allow for about 1000 years of randomness. Leave off nanoseconds - // because JSON doesn't represent them so they can't round-trip - // properly. - t.Time = time.Unix(c.Rand.Int63n(1000*365*24*60*60), 0) -} - -var _ fuzz.Interface = &Time{} diff --git a/pkg/apis/networking/BUILD b/pkg/apis/networking/BUILD index 5d62efcc51b..4dff82d1ba3 100644 --- a/pkg/apis/networking/BUILD +++ b/pkg/apis/networking/BUILD @@ -15,9 +15,8 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/apis/networking", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", diff --git a/pkg/apis/networking/doc.go b/pkg/apis/networking/doc.go index b583051ca4c..8b013e34867 100644 --- a/pkg/apis/networking/doc.go +++ b/pkg/apis/networking/doc.go @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +groupName=networking.k8s.io package networking // import "k8s.io/kubernetes/pkg/apis/networking" diff --git a/pkg/apis/networking/install/BUILD b/pkg/apis/networking/install/BUILD index 504ea757baa..a5ce49004d4 100644 --- a/pkg/apis/networking/install/BUILD +++ b/pkg/apis/networking/install/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["install.go"], importpath = "k8s.io/kubernetes/pkg/apis/networking/install", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/networking:go_default_library", "//pkg/apis/networking/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", diff --git a/pkg/apis/networking/install/install.go b/pkg/apis/networking/install/install.go index 85c7991bcf0..59a3d043c57 100644 --- a/pkg/apis/networking/install/install.go +++ b/pkg/apis/networking/install/install.go @@ -22,13 +22,13 @@ import ( "k8s.io/apimachinery/pkg/apimachinery/announced" "k8s.io/apimachinery/pkg/apimachinery/registered" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/networking" "k8s.io/kubernetes/pkg/apis/networking/v1" ) func init() { - Install(api.GroupFactoryRegistry, api.Registry, api.Scheme) + Install(legacyscheme.GroupFactoryRegistry, legacyscheme.Registry, legacyscheme.Scheme) } // Install registers the API group and adds types to a scheme diff --git a/pkg/apis/networking/types.go b/pkg/apis/networking/types.go index 2109c374526..ae37fcd5e26 100644 --- a/pkg/apis/networking/types.go +++ b/pkg/apis/networking/types.go @@ -19,7 +19,7 @@ package networking import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // +genclient diff --git a/pkg/apis/networking/v1/BUILD b/pkg/apis/networking/v1/BUILD index 8cfe08308a4..a6237c850cf 100644 --- a/pkg/apis/networking/v1/BUILD +++ b/pkg/apis/networking/v1/BUILD @@ -17,7 +17,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/apis/networking/v1", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/networking:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/networking/v1:go_default_library", @@ -48,8 +48,8 @@ go_test( importpath = "k8s.io/kubernetes/pkg/apis/networking/v1_test", deps = [ ":go_default_library", - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core/install:go_default_library", "//pkg/apis/networking/install:go_default_library", "//vendor/k8s.io/api/networking/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", diff --git a/pkg/apis/networking/v1/defaults_test.go b/pkg/apis/networking/v1/defaults_test.go index 3a86610d736..7bc2a68339d 100644 --- a/pkg/apis/networking/v1/defaults_test.go +++ b/pkg/apis/networking/v1/defaults_test.go @@ -25,8 +25,8 @@ import ( 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" - _ "k8s.io/kubernetes/pkg/api/install" + "k8s.io/kubernetes/pkg/api/legacyscheme" + _ "k8s.io/kubernetes/pkg/apis/core/install" _ "k8s.io/kubernetes/pkg/apis/networking/install" . "k8s.io/kubernetes/pkg/apis/networking/v1" ) @@ -236,18 +236,18 @@ func TestSetDefaultNetworkPolicy(t *testing.T) { } func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { - data, err := runtime.Encode(api.Codecs.LegacyCodec(SchemeGroupVersion), obj) + 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(api.Codecs.UniversalDecoder(), data) + 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 = api.Scheme.Convert(obj2, obj3, nil) + err = legacyscheme.Scheme.Convert(obj2, obj3, nil) if err != nil { t.Errorf("%v\nSource: %#v", err, obj2) return nil diff --git a/pkg/apis/networking/v1/doc.go b/pkg/apis/networking/v1/doc.go index d069022f53d..f53cbf3dc65 100644 --- a/pkg/apis/networking/v1/doc.go +++ b/pkg/apis/networking/v1/doc.go @@ -15,7 +15,7 @@ limitations under the License. */ // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/networking -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/networking/v1 +// +k8s:conversion-gen-external-types=k8s.io/api/networking/v1 // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/extensions // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/networking/v1 diff --git a/pkg/apis/networking/v1/zz_generated.conversion.go b/pkg/apis/networking/v1/zz_generated.conversion.go index 67132df2c11..df0a508f2aa 100644 --- a/pkg/apis/networking/v1/zz_generated.conversion.go +++ b/pkg/apis/networking/v1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,15 +21,16 @@ limitations under the License. package v1 import ( + unsafe "unsafe" + core_v1 "k8s.io/api/core/v1" v1 "k8s.io/api/networking/v1" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" intstr "k8s.io/apimachinery/pkg/util/intstr" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" networking "k8s.io/kubernetes/pkg/apis/networking" - unsafe "unsafe" ) func init() { @@ -198,7 +199,7 @@ func Convert_networking_NetworkPolicyPeer_To_v1_NetworkPolicyPeer(in *networking } func autoConvert_v1_NetworkPolicyPort_To_networking_NetworkPolicyPort(in *v1.NetworkPolicyPort, out *networking.NetworkPolicyPort, s conversion.Scope) error { - out.Protocol = (*api.Protocol)(unsafe.Pointer(in.Protocol)) + out.Protocol = (*core.Protocol)(unsafe.Pointer(in.Protocol)) out.Port = (*intstr.IntOrString)(unsafe.Pointer(in.Port)) return nil } diff --git a/pkg/apis/networking/v1/zz_generated.defaults.go b/pkg/apis/networking/v1/zz_generated.defaults.go index 0c7b575db98..5843b99ed54 100644 --- a/pkg/apis/networking/v1/zz_generated.defaults.go +++ b/pkg/apis/networking/v1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/apis/networking/validation/BUILD b/pkg/apis/networking/validation/BUILD index f43264234e0..44a0d65766b 100644 --- a/pkg/apis/networking/validation/BUILD +++ b/pkg/apis/networking/validation/BUILD @@ -9,10 +9,10 @@ load( go_test( name = "go_default_test", srcs = ["validation_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/apis/networking/validation", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/networking:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", @@ -24,8 +24,8 @@ go_library( srcs = ["validation.go"], importpath = "k8s.io/kubernetes/pkg/apis/networking/validation", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//pkg/apis/networking:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", diff --git a/pkg/apis/networking/validation/validation.go b/pkg/apis/networking/validation/validation.go index 49f638f91f0..9f0a04c81b2 100644 --- a/pkg/apis/networking/validation/validation.go +++ b/pkg/apis/networking/validation/validation.go @@ -17,15 +17,13 @@ limitations under the License. package validation import ( - "net" - unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" - apivalidation "k8s.io/kubernetes/pkg/api/validation" + api "k8s.io/kubernetes/pkg/apis/core" + apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" "k8s.io/kubernetes/pkg/apis/networking" ) @@ -163,7 +161,7 @@ func ValidateIPBlock(ipb *networking.IPBlock, fldPath *field.Path) field.ErrorLi allErrs = append(allErrs, field.Required(fldPath.Child("cidr"), "")) return allErrs } - cidrIPNet, err := validateCIDR(ipb.CIDR) + cidrIPNet, err := apivalidation.ValidateCIDR(ipb.CIDR) if err != nil { allErrs = append(allErrs, field.Invalid(fldPath.Child("cidr"), ipb.CIDR, "not a valid CIDR")) return allErrs @@ -171,7 +169,7 @@ func ValidateIPBlock(ipb *networking.IPBlock, fldPath *field.Path) field.ErrorLi exceptCIDR := ipb.Except for i, exceptIP := range exceptCIDR { exceptPath := fldPath.Child("except").Index(i) - exceptCIDR, err := validateCIDR(exceptIP) + exceptCIDR, err := apivalidation.ValidateCIDR(exceptIP) if err != nil { allErrs = append(allErrs, field.Invalid(exceptPath, exceptIP, "not a valid CIDR")) return allErrs @@ -182,12 +180,3 @@ func ValidateIPBlock(ipb *networking.IPBlock, fldPath *field.Path) field.ErrorLi } return allErrs } - -// validateCIDR validates whether a CIDR matches the conventions expected by net.ParseCIDR -func validateCIDR(cidr string) (*net.IPNet, error) { - _, net, err := net.ParseCIDR(cidr) - if err != nil { - return nil, err - } - return net, nil -} diff --git a/pkg/apis/networking/validation/validation_test.go b/pkg/apis/networking/validation/validation_test.go index 401eb43b6c7..3af353d8ce2 100644 --- a/pkg/apis/networking/validation/validation_test.go +++ b/pkg/apis/networking/validation/validation_test.go @@ -21,7 +21,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/networking" ) @@ -215,6 +215,36 @@ func TestValidateNetworkPolicy(t *testing.T) { PolicyTypes: []networking.PolicyType{networking.PolicyTypeIngress, networking.PolicyTypeEgress}, }, }, + { + ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, + Spec: networking.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + Egress: []networking.NetworkPolicyEgressRule{ + { + Ports: []networking.NetworkPolicyPort{ + { + Protocol: nil, + Port: &intstr.IntOrString{Type: intstr.Int, IntVal: 80}, + }, + { + Protocol: &protocolTCP, + Port: nil, + }, + { + Protocol: &protocolTCP, + Port: &intstr.IntOrString{Type: intstr.Int, IntVal: 443}, + }, + { + Protocol: &protocolUDP, + Port: &intstr.IntOrString{Type: intstr.String, StrVal: "dns"}, + }, + }, + }, + }, + }, + }, } // Success cases are expected to pass validation. @@ -246,6 +276,38 @@ func TestValidateNetworkPolicy(t *testing.T) { }, }, }, + Egress: []networking.NetworkPolicyEgressRule{ + { + To: []networking.NetworkPolicyPeer{ + { + PodSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"c": "d"}, + }, + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"c": "d"}, + }, + }, + }, + }, + }, + }, + }, + "missing from and to type": { + ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, + Spec: networking.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + Ingress: []networking.NetworkPolicyIngressRule{ + { + From: []networking.NetworkPolicyPeer{{}}, + }, + }, + Egress: []networking.NetworkPolicyEgressRule{ + { + To: []networking.NetworkPolicyPeer{{}}, + }, + }, }, }, "invalid spec.podSelector": { @@ -349,6 +411,54 @@ func TestValidateNetworkPolicy(t *testing.T) { }, }, }, + "invalid egress.ports.protocol": { + ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, + Spec: networking.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{}, + Egress: []networking.NetworkPolicyEgressRule{ + { + Ports: []networking.NetworkPolicyPort{ + { + Protocol: &protocolICMP, + Port: &intstr.IntOrString{Type: intstr.Int, IntVal: 80}, + }, + }, + }, + }, + }, + }, + "invalid egress.ports.port (int)": { + ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, + Spec: networking.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{}, + Egress: []networking.NetworkPolicyEgressRule{ + { + Ports: []networking.NetworkPolicyPort{ + { + Protocol: &protocolTCP, + Port: &intstr.IntOrString{Type: intstr.Int, IntVal: 123456789}, + }, + }, + }, + }, + }, + }, + "invalid egress.ports.port (str)": { + ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, + Spec: networking.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{}, + Egress: []networking.NetworkPolicyEgressRule{ + { + Ports: []networking.NetworkPolicyPort{ + { + Protocol: &protocolTCP, + Port: &intstr.IntOrString{Type: intstr.String, StrVal: "!@#$"}, + }, + }, + }, + }, + }, + }, "invalid ingress.from.namespaceSelector": { ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, Spec: networking.NetworkPolicySpec{ diff --git a/pkg/apis/networking/zz_generated.deepcopy.go b/pkg/apis/networking/zz_generated.deepcopy.go index da38fb5e8db..8ab8e33ec18 100644 --- a/pkg/apis/networking/zz_generated.deepcopy.go +++ b/pkg/apis/networking/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,58 +22,11 @@ package networking import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" intstr "k8s.io/apimachinery/pkg/util/intstr" - api "k8s.io/kubernetes/pkg/api" - reflect "reflect" + core "k8s.io/kubernetes/pkg/apis/core" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*IPBlock).DeepCopyInto(out.(*IPBlock)) - return nil - }, InType: reflect.TypeOf(&IPBlock{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NetworkPolicy).DeepCopyInto(out.(*NetworkPolicy)) - return nil - }, InType: reflect.TypeOf(&NetworkPolicy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NetworkPolicyEgressRule).DeepCopyInto(out.(*NetworkPolicyEgressRule)) - return nil - }, InType: reflect.TypeOf(&NetworkPolicyEgressRule{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NetworkPolicyIngressRule).DeepCopyInto(out.(*NetworkPolicyIngressRule)) - return nil - }, InType: reflect.TypeOf(&NetworkPolicyIngressRule{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NetworkPolicyList).DeepCopyInto(out.(*NetworkPolicyList)) - return nil - }, InType: reflect.TypeOf(&NetworkPolicyList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NetworkPolicyPeer).DeepCopyInto(out.(*NetworkPolicyPeer)) - return nil - }, InType: reflect.TypeOf(&NetworkPolicyPeer{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NetworkPolicyPort).DeepCopyInto(out.(*NetworkPolicyPort)) - return nil - }, InType: reflect.TypeOf(&NetworkPolicyPort{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NetworkPolicySpec).DeepCopyInto(out.(*NetworkPolicySpec)) - return nil - }, InType: reflect.TypeOf(&NetworkPolicySpec{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IPBlock) DeepCopyInto(out *IPBlock) { *out = *in @@ -268,7 +221,7 @@ func (in *NetworkPolicyPort) DeepCopyInto(out *NetworkPolicyPort) { if *in == nil { *out = nil } else { - *out = new(api.Protocol) + *out = new(core.Protocol) **out = **in } } diff --git a/pkg/apis/policy/BUILD b/pkg/apis/policy/BUILD index 8396aae0f60..86f104cff8d 100644 --- a/pkg/apis/policy/BUILD +++ b/pkg/apis/policy/BUILD @@ -16,7 +16,6 @@ go_library( importpath = "k8s.io/kubernetes/pkg/apis/policy", deps = [ "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", @@ -36,7 +35,6 @@ filegroup( ":package-srcs", "//pkg/apis/policy/fuzzer:all-srcs", "//pkg/apis/policy/install:all-srcs", - "//pkg/apis/policy/v1alpha1:all-srcs", "//pkg/apis/policy/v1beta1:all-srcs", "//pkg/apis/policy/validation:all-srcs", ], diff --git a/pkg/apis/policy/doc.go b/pkg/apis/policy/doc.go index 86f4cd56079..e8e081e7a3c 100644 --- a/pkg/apis/policy/doc.go +++ b/pkg/apis/policy/doc.go @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package package policy // import "k8s.io/kubernetes/pkg/apis/policy" diff --git a/pkg/apis/policy/install/BUILD b/pkg/apis/policy/install/BUILD index b063e72592a..6cecb39790c 100644 --- a/pkg/apis/policy/install/BUILD +++ b/pkg/apis/policy/install/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["install.go"], importpath = "k8s.io/kubernetes/pkg/apis/policy/install", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/policy:go_default_library", "//pkg/apis/policy/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", diff --git a/pkg/apis/policy/install/install.go b/pkg/apis/policy/install/install.go index cf133df6b6b..6c5703195b9 100644 --- a/pkg/apis/policy/install/install.go +++ b/pkg/apis/policy/install/install.go @@ -22,13 +22,13 @@ import ( "k8s.io/apimachinery/pkg/apimachinery/announced" "k8s.io/apimachinery/pkg/apimachinery/registered" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/policy" "k8s.io/kubernetes/pkg/apis/policy/v1beta1" ) func init() { - Install(api.GroupFactoryRegistry, api.Registry, api.Scheme) + Install(legacyscheme.GroupFactoryRegistry, legacyscheme.Registry, legacyscheme.Scheme) } // Install registers the API group and adds types to a scheme diff --git a/pkg/apis/policy/register.go b/pkg/apis/policy/register.go index 5aadc3f1b8e..80e834b385d 100644 --- a/pkg/apis/policy/register.go +++ b/pkg/apis/policy/register.go @@ -42,7 +42,7 @@ var ( AddToScheme = SchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { // TODO this gets cleaned up when the types are fixed scheme.AddKnownTypes(SchemeGroupVersion, diff --git a/pkg/apis/policy/v1alpha1/BUILD b/pkg/apis/policy/v1alpha1/BUILD deleted file mode 100644 index 9818297b0f8..00000000000 --- a/pkg/apis/policy/v1alpha1/BUILD +++ /dev/null @@ -1,38 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "doc.go", - "register.go", - "types.go", - "zz_generated.deepcopy.go", - "zz_generated.defaults.go", - ], - importpath = "k8s.io/kubernetes/pkg/apis/policy/v1alpha1", - deps = [ - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/pkg/apis/policy/v1alpha1/doc.go b/pkg/apis/policy/v1alpha1/doc.go deleted file mode 100644 index e5b6777ce26..00000000000 --- a/pkg/apis/policy/v1alpha1/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:defaulter-gen=TypeMeta -// +k8s:deepcopy-gen=package,register - -// Package policy is for any kind of policy object. Suitable examples, even if -// they aren't all here, are PodDisruptionBudget, PodSecurityPolicy, -// NetworkPolicy, etc. -package v1alpha1 // import "k8s.io/kubernetes/pkg/apis/policy/v1alpha1" diff --git a/pkg/apis/policy/v1alpha1/register.go b/pkg/apis/policy/v1alpha1/register.go deleted file mode 100644 index 78092cecf5c..00000000000 --- a/pkg/apis/policy/v1alpha1/register.go +++ /dev/null @@ -1,61 +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 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 = "policy" - -// 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.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, RegisterDefaults) -} - -// Adds the list of known types to api.Scheme. -func addKnownTypes(scheme *runtime.Scheme) error { - scheme.AddKnownTypes(SchemeGroupVersion, - &PodDisruptionBudget{}, - &PodDisruptionBudgetList{}, - &Eviction{}, - ) - // Add the watch version that applies - metav1.AddToGroupVersion(scheme, SchemeGroupVersion) - return nil -} diff --git a/pkg/apis/policy/v1alpha1/types.go b/pkg/apis/policy/v1alpha1/types.go deleted file mode 100644 index 7232e8b91fa..00000000000 --- a/pkg/apis/policy/v1alpha1/types.go +++ /dev/null @@ -1,97 +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 v1alpha1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" -) - -// PodDisruptionBudgetSpec is a description of a PodDisruptionBudget. -type PodDisruptionBudgetSpec struct { - // An eviction is allowed if at least "minAvailable" pods selected by - // "selector" will still be available after the eviction, i.e. even in the - // absence of the evicted pod. So for example you can prevent all voluntary - // evictions by specifying "100%". - // +optional - MinAvailable intstr.IntOrString `json:"minAvailable,omitempty" protobuf:"bytes,1,opt,name=minAvailable"` - - // Label query over pods whose evictions are managed by the disruption - // budget. - // +optional - Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,2,opt,name=selector"` -} - -// PodDisruptionBudgetStatus represents information about the status of a -// PodDisruptionBudget. Status may trail the actual state of a system. -type PodDisruptionBudgetStatus struct { - // Whether or not a disruption is currently allowed. - PodDisruptionAllowed bool `json:"disruptionAllowed" protobuf:"varint,1,opt,name=disruptionAllowed"` - - // current number of healthy pods - CurrentHealthy int32 `json:"currentHealthy" protobuf:"varint,2,opt,name=currentHealthy"` - - // minimum desired number of healthy pods - DesiredHealthy int32 `json:"desiredHealthy" protobuf:"varint,3,opt,name=desiredHealthy"` - - // total number of pods counted by this disruption budget - ExpectedPods int32 `json:"expectedPods" protobuf:"varint,4,opt,name=expectedPods"` -} - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// PodDisruptionBudget is an object to define the max disruption that can be caused to a collection of pods -type PodDisruptionBudget struct { - metav1.TypeMeta `json:",inline"` - // +optional - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // Specification of the desired behavior of the PodDisruptionBudget. - // +optional - Spec PodDisruptionBudgetSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` - // Most recently observed status of the PodDisruptionBudget. - // +optional - Status PodDisruptionBudgetStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// PodDisruptionBudgetList is a collection of PodDisruptionBudgets. -type PodDisruptionBudgetList struct { - metav1.TypeMeta `json:",inline"` - // +optional - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - Items []PodDisruptionBudget `json:"items" protobuf:"bytes,2,rep,name=items"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// 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//eviction. -type Eviction struct { - metav1.TypeMeta `json:",inline"` - - // ObjectMeta describes the pod that is being evicted. - // +optional - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // DeleteOptions may be provided - // +optional - DeleteOptions *metav1.DeleteOptions `json:"deleteOptions,omitempty" protobuf:"bytes,2,opt,name=deleteOptions"` -} diff --git a/pkg/apis/policy/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/policy/v1alpha1/zz_generated.deepcopy.go deleted file mode 100644 index d4c2628057d..00000000000 --- a/pkg/apis/policy/v1alpha1/zz_generated.deepcopy.go +++ /dev/null @@ -1,202 +0,0 @@ -// +build !ignore_autogenerated - -/* -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. -*/ - -// This file was autogenerated by deepcopy-gen. Do not edit it manually! - -package v1alpha1 - -import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" -) - -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Eviction).DeepCopyInto(out.(*Eviction)) - return nil - }, InType: reflect.TypeOf(&Eviction{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodDisruptionBudget).DeepCopyInto(out.(*PodDisruptionBudget)) - return nil - }, InType: reflect.TypeOf(&PodDisruptionBudget{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodDisruptionBudgetList).DeepCopyInto(out.(*PodDisruptionBudgetList)) - return nil - }, InType: reflect.TypeOf(&PodDisruptionBudgetList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodDisruptionBudgetSpec).DeepCopyInto(out.(*PodDisruptionBudgetSpec)) - return nil - }, InType: reflect.TypeOf(&PodDisruptionBudgetSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodDisruptionBudgetStatus).DeepCopyInto(out.(*PodDisruptionBudgetStatus)) - return nil - }, InType: reflect.TypeOf(&PodDisruptionBudgetStatus{})}, - ) -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Eviction) DeepCopyInto(out *Eviction) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.DeleteOptions != nil { - in, out := &in.DeleteOptions, &out.DeleteOptions - if *in == nil { - *out = nil - } else { - *out = new(v1.DeleteOptions) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Eviction. -func (in *Eviction) DeepCopy() *Eviction { - if in == nil { - return nil - } - out := new(Eviction) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Eviction) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodDisruptionBudget) DeepCopyInto(out *PodDisruptionBudget) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodDisruptionBudget. -func (in *PodDisruptionBudget) DeepCopy() *PodDisruptionBudget { - if in == nil { - return nil - } - out := new(PodDisruptionBudget) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PodDisruptionBudget) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodDisruptionBudgetList) DeepCopyInto(out *PodDisruptionBudgetList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]PodDisruptionBudget, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodDisruptionBudgetList. -func (in *PodDisruptionBudgetList) DeepCopy() *PodDisruptionBudgetList { - if in == nil { - return nil - } - out := new(PodDisruptionBudgetList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PodDisruptionBudgetList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodDisruptionBudgetSpec) DeepCopyInto(out *PodDisruptionBudgetSpec) { - *out = *in - out.MinAvailable = in.MinAvailable - if in.Selector != nil { - in, out := &in.Selector, &out.Selector - if *in == nil { - *out = nil - } else { - *out = new(v1.LabelSelector) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodDisruptionBudgetSpec. -func (in *PodDisruptionBudgetSpec) DeepCopy() *PodDisruptionBudgetSpec { - if in == nil { - return nil - } - out := new(PodDisruptionBudgetSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodDisruptionBudgetStatus) DeepCopyInto(out *PodDisruptionBudgetStatus) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodDisruptionBudgetStatus. -func (in *PodDisruptionBudgetStatus) DeepCopy() *PodDisruptionBudgetStatus { - if in == nil { - return nil - } - out := new(PodDisruptionBudgetStatus) - in.DeepCopyInto(out) - return out -} diff --git a/pkg/apis/policy/v1alpha1/zz_generated.defaults.go b/pkg/apis/policy/v1alpha1/zz_generated.defaults.go deleted file mode 100644 index 7e6df29d4ae..00000000000 --- a/pkg/apis/policy/v1alpha1/zz_generated.defaults.go +++ /dev/null @@ -1,32 +0,0 @@ -// +build !ignore_autogenerated - -/* -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. -*/ - -// This file was autogenerated by defaulter-gen. Do not edit it manually! - -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/pkg/apis/policy/v1beta1/doc.go b/pkg/apis/policy/v1beta1/doc.go index f5500bb4b9f..9629da3fcd4 100644 --- a/pkg/apis/policy/v1beta1/doc.go +++ b/pkg/apis/policy/v1beta1/doc.go @@ -15,7 +15,7 @@ limitations under the License. */ // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/policy -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/policy/v1beta1 +// +k8s:conversion-gen-external-types=k8s.io/api/policy/v1beta1 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/policy/v1beta1 diff --git a/pkg/apis/policy/v1beta1/zz_generated.conversion.go b/pkg/apis/policy/v1beta1/zz_generated.conversion.go index aa07a7bd7d5..7c897efb1b6 100644 --- a/pkg/apis/policy/v1beta1/zz_generated.conversion.go +++ b/pkg/apis/policy/v1beta1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,13 +21,14 @@ limitations under the License. package v1beta1 import ( + unsafe "unsafe" + v1beta1 "k8s.io/api/policy/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" intstr "k8s.io/apimachinery/pkg/util/intstr" policy "k8s.io/kubernetes/pkg/apis/policy" - unsafe "unsafe" ) func init() { diff --git a/pkg/apis/policy/v1beta1/zz_generated.defaults.go b/pkg/apis/policy/v1beta1/zz_generated.defaults.go index e24e70be38b..b61dda74c23 100644 --- a/pkg/apis/policy/v1beta1/zz_generated.defaults.go +++ b/pkg/apis/policy/v1beta1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/apis/policy/validation/BUILD b/pkg/apis/policy/validation/BUILD index 20cf1223ca3..692d0ca0c15 100644 --- a/pkg/apis/policy/validation/BUILD +++ b/pkg/apis/policy/validation/BUILD @@ -11,7 +11,7 @@ go_library( srcs = ["validation.go"], importpath = "k8s.io/kubernetes/pkg/apis/policy/validation", deps = [ - "//pkg/api/validation:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//pkg/apis/extensions/validation:go_default_library", "//pkg/apis/policy:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/validation:go_default_library", @@ -22,8 +22,8 @@ go_library( go_test( name = "go_default_test", srcs = ["validation_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/apis/policy/validation", - library = ":go_default_library", deps = [ "//pkg/apis/policy:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", diff --git a/pkg/apis/policy/validation/validation.go b/pkg/apis/policy/validation/validation.go index cb0e0506d74..cae1609de6e 100644 --- a/pkg/apis/policy/validation/validation.go +++ b/pkg/apis/policy/validation/validation.go @@ -21,7 +21,7 @@ import ( unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" "k8s.io/apimachinery/pkg/util/validation/field" - apivalidation "k8s.io/kubernetes/pkg/api/validation" + apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" extensionsvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation" "k8s.io/kubernetes/pkg/apis/policy" ) diff --git a/pkg/apis/policy/zz_generated.deepcopy.go b/pkg/apis/policy/zz_generated.deepcopy.go index 46dfdd8f4f0..539854524ca 100644 --- a/pkg/apis/policy/zz_generated.deepcopy.go +++ b/pkg/apis/policy/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,45 +22,10 @@ package policy import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" intstr "k8s.io/apimachinery/pkg/util/intstr" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Eviction).DeepCopyInto(out.(*Eviction)) - return nil - }, InType: reflect.TypeOf(&Eviction{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodDisruptionBudget).DeepCopyInto(out.(*PodDisruptionBudget)) - return nil - }, InType: reflect.TypeOf(&PodDisruptionBudget{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodDisruptionBudgetList).DeepCopyInto(out.(*PodDisruptionBudgetList)) - return nil - }, InType: reflect.TypeOf(&PodDisruptionBudgetList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodDisruptionBudgetSpec).DeepCopyInto(out.(*PodDisruptionBudgetSpec)) - return nil - }, InType: reflect.TypeOf(&PodDisruptionBudgetSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodDisruptionBudgetStatus).DeepCopyInto(out.(*PodDisruptionBudgetStatus)) - return nil - }, InType: reflect.TypeOf(&PodDisruptionBudgetStatus{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Eviction) DeepCopyInto(out *Eviction) { *out = *in diff --git a/pkg/apis/rbac/BUILD b/pkg/apis/rbac/BUILD index 5e305a6ab93..ef634f03a34 100644 --- a/pkg/apis/rbac/BUILD +++ b/pkg/apis/rbac/BUILD @@ -18,7 +18,6 @@ go_library( importpath = "k8s.io/kubernetes/pkg/apis/rbac", deps = [ "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", @@ -52,7 +51,7 @@ go_test( importpath = "k8s.io/kubernetes/pkg/apis/rbac_test", deps = [ ":go_default_library", - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/rbac/install:go_default_library", "//pkg/apis/rbac/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/pkg/apis/rbac/doc.go b/pkg/apis/rbac/doc.go index b264fb9a02e..bebcb771d12 100644 --- a/pkg/apis/rbac/doc.go +++ b/pkg/apis/rbac/doc.go @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +groupName=rbac.authorization.k8s.io package rbac // import "k8s.io/kubernetes/pkg/apis/rbac" diff --git a/pkg/apis/rbac/helpers.go b/pkg/apis/rbac/helpers.go index efc4c61c569..373711500b1 100644 --- a/pkg/apis/rbac/helpers.go +++ b/pkg/apis/rbac/helpers.go @@ -55,14 +55,29 @@ func APIGroupMatches(rule *PolicyRule, requestedGroup string) bool { return false } -func ResourceMatches(rule *PolicyRule, requestedResource string) bool { +func ResourceMatches(rule *PolicyRule, combinedRequestedResource, requestedSubresource string) bool { for _, ruleResource := range rule.Resources { + // if everything is allowed, we match if ruleResource == ResourceAll { return true } - if ruleResource == requestedResource { + // if we have an exact match, we match + if ruleResource == combinedRequestedResource { return true } + + // We can also match a */subresource. + // if there isn't a subresource, then continue + if len(requestedSubresource) == 0 { + continue + } + // if the rule isn't in the format */subresource, then we don't match, continue + if len(ruleResource) == len(requestedSubresource)+2 && + strings.HasPrefix(ruleResource, "*/") && + strings.HasSuffix(ruleResource, requestedSubresource) { + return true + + } } return false @@ -132,6 +147,10 @@ func (r PolicyRule) String() string { func (r PolicyRule) CompactString() string { formatStringParts := []string{} formatArgs := []interface{}{} + if len(r.APIGroups) > 0 { + formatStringParts = append(formatStringParts, "APIGroups:%q") + formatArgs = append(formatArgs, r.APIGroups) + } if len(r.Resources) > 0 { formatStringParts = append(formatStringParts, "Resources:%q") formatArgs = append(formatArgs, r.Resources) @@ -144,10 +163,6 @@ func (r PolicyRule) CompactString() string { formatStringParts = append(formatStringParts, "ResourceNames:%q") formatArgs = append(formatArgs, r.ResourceNames) } - if len(r.APIGroups) > 0 { - formatStringParts = append(formatStringParts, "APIGroups:%q") - formatArgs = append(formatArgs, r.APIGroups) - } if len(r.Verbs) > 0 { formatStringParts = append(formatStringParts, "Verbs:%q") formatArgs = append(formatArgs, r.Verbs) diff --git a/pkg/apis/rbac/helpers_test.go b/pkg/apis/rbac/helpers_test.go index de9f24a090a..8723796e84d 100644 --- a/pkg/apis/rbac/helpers_test.go +++ b/pkg/apis/rbac/helpers_test.go @@ -22,7 +22,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/diff" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/rbac" "k8s.io/kubernetes/pkg/apis/rbac/v1" @@ -53,13 +53,13 @@ func TestHelpersRoundTrip(t *testing.T) { } for _, internalObj := range []runtime.Object{&rb, &rbcr, &crb, role, clusterRole} { - v1Obj, err := api.Scheme.ConvertToVersion(internalObj, v1.SchemeGroupVersion) + v1Obj, err := legacyscheme.Scheme.ConvertToVersion(internalObj, v1.SchemeGroupVersion) if err != nil { t.Errorf("err on %T: %v", internalObj, err) continue } - api.Scheme.Default(v1Obj) - roundTrippedObj, err := api.Scheme.ConvertToVersion(v1Obj, rbac.SchemeGroupVersion) + legacyscheme.Scheme.Default(v1Obj) + roundTrippedObj, err := legacyscheme.Scheme.ConvertToVersion(v1Obj, rbac.SchemeGroupVersion) if err != nil { t.Errorf("err on %T: %v", internalObj, err) continue @@ -70,3 +70,108 @@ func TestHelpersRoundTrip(t *testing.T) { } } } + +func TestResourceMatches(t *testing.T) { + tests := []struct { + name string + ruleResources []string + combinedRequestedResource string + requestedSubresource string + expected bool + }{ + { + name: "all matches 01", + ruleResources: []string{"*"}, + combinedRequestedResource: "foo", + expected: true, + }, + { + name: "checks all rules", + ruleResources: []string{"doesn't match", "*"}, + combinedRequestedResource: "foo", + expected: true, + }, + { + name: "matches exact rule", + ruleResources: []string{"foo/bar"}, + combinedRequestedResource: "foo/bar", + requestedSubresource: "bar", + expected: true, + }, + { + name: "matches exact rule 02", + ruleResources: []string{"foo/bar"}, + combinedRequestedResource: "foo", + expected: false, + }, + { + name: "matches subresource", + ruleResources: []string{"*/scale"}, + combinedRequestedResource: "foo/scale", + requestedSubresource: "scale", + expected: true, + }, + { + name: "doesn't match partial subresource hit", + ruleResources: []string{"foo/bar", "*/other"}, + combinedRequestedResource: "foo/other/segment", + requestedSubresource: "other/segment", + expected: false, + }, + { + name: "matches subresource with multiple slashes", + ruleResources: []string{"*/other/segment"}, + combinedRequestedResource: "foo/other/segment", + requestedSubresource: "other/segment", + expected: true, + }, + { + name: "doesn't fail on empty", + ruleResources: []string{""}, + combinedRequestedResource: "foo/other/segment", + requestedSubresource: "other/segment", + expected: false, + }, + { + name: "doesn't fail on slash", + ruleResources: []string{"/"}, + combinedRequestedResource: "foo/other/segment", + requestedSubresource: "other/segment", + expected: false, + }, + { + name: "doesn't fail on missing subresource", + ruleResources: []string{"*/"}, + combinedRequestedResource: "foo/other/segment", + requestedSubresource: "other/segment", + expected: false, + }, + { + name: "doesn't match on not star", + ruleResources: []string{"*something/other/segment"}, + combinedRequestedResource: "foo/other/segment", + requestedSubresource: "other/segment", + expected: false, + }, + { + name: "doesn't match on something else", + ruleResources: []string{"something/other/segment"}, + combinedRequestedResource: "foo/other/segment", + requestedSubresource: "other/segment", + expected: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + rule := &rbac.PolicyRule{ + Resources: tc.ruleResources, + } + actual := rbac.ResourceMatches(rule, tc.combinedRequestedResource, tc.requestedSubresource) + if tc.expected != actual { + t.Errorf("expected %v, got %v", tc.expected, actual) + } + + }) + } +} diff --git a/pkg/apis/rbac/install/BUILD b/pkg/apis/rbac/install/BUILD index ff8ca7c4e5e..5c699a006c6 100644 --- a/pkg/apis/rbac/install/BUILD +++ b/pkg/apis/rbac/install/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["install.go"], importpath = "k8s.io/kubernetes/pkg/apis/rbac/install", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/rbac:go_default_library", "//pkg/apis/rbac/v1:go_default_library", "//pkg/apis/rbac/v1alpha1:go_default_library", diff --git a/pkg/apis/rbac/install/install.go b/pkg/apis/rbac/install/install.go index 84be4c1866c..b03d8475cd4 100644 --- a/pkg/apis/rbac/install/install.go +++ b/pkg/apis/rbac/install/install.go @@ -23,7 +23,7 @@ import ( "k8s.io/apimachinery/pkg/apimachinery/registered" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/rbac" "k8s.io/kubernetes/pkg/apis/rbac/v1" "k8s.io/kubernetes/pkg/apis/rbac/v1alpha1" @@ -31,7 +31,7 @@ import ( ) func init() { - Install(api.GroupFactoryRegistry, api.Registry, api.Scheme) + Install(legacyscheme.GroupFactoryRegistry, legacyscheme.Registry, legacyscheme.Scheme) } // Install registers the API group and adds types to a scheme diff --git a/pkg/apis/rbac/register.go b/pkg/apis/rbac/register.go index f4a838bd89a..4f232951512 100644 --- a/pkg/apis/rbac/register.go +++ b/pkg/apis/rbac/register.go @@ -41,7 +41,7 @@ var ( AddToScheme = SchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Role{}, diff --git a/pkg/apis/rbac/types.go b/pkg/apis/rbac/types.go index 6e5ce60fb88..6fdd486d242 100644 --- a/pkg/apis/rbac/types.go +++ b/pkg/apis/rbac/types.go @@ -48,7 +48,8 @@ type PolicyRule struct { // 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. APIGroups []string - // Resources is a list of resources this rule applies to. ResourceAll represents all resources. + // Resources is a list of resources this rule applies to. '*' represents all resources in the specified apiGroups. + // '*/foo' represents the subresource 'foo' for all resources in the specified apiGroups. Resources []string // ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. ResourceNames []string @@ -154,6 +155,18 @@ type ClusterRole struct { // Rules holds all the PolicyRules for this ClusterRole Rules []PolicyRule + + // 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 + // stomped by the controller. + AggregationRule *AggregationRule +} + +// AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole +type AggregationRule struct { + // ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. + // If any of the selectors match, then the ClusterRole's permissions will be added + ClusterRoleSelectors []metav1.LabelSelector } // +genclient diff --git a/pkg/apis/rbac/v1/doc.go b/pkg/apis/rbac/v1/doc.go index 6dec982b781..1668eabe3b8 100644 --- a/pkg/apis/rbac/v1/doc.go +++ b/pkg/apis/rbac/v1/doc.go @@ -15,7 +15,7 @@ limitations under the License. */ // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/rbac -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/rbac/v1 +// +k8s:conversion-gen-external-types=k8s.io/api/rbac/v1 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/rbac/v1 diff --git a/pkg/apis/rbac/v1/zz_generated.conversion.go b/pkg/apis/rbac/v1/zz_generated.conversion.go index e5e668811e2..39e0c380e62 100644 --- a/pkg/apis/rbac/v1/zz_generated.conversion.go +++ b/pkg/apis/rbac/v1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,11 +21,13 @@ limitations under the License. package v1 import ( + unsafe "unsafe" + v1 "k8s.io/api/rbac/v1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" rbac "k8s.io/kubernetes/pkg/apis/rbac" - unsafe "unsafe" ) func init() { @@ -36,6 +38,8 @@ func init() { // Public to allow building arbitrary schemes. func RegisterConversions(scheme *runtime.Scheme) error { return scheme.AddGeneratedConversionFuncs( + Convert_v1_AggregationRule_To_rbac_AggregationRule, + Convert_rbac_AggregationRule_To_v1_AggregationRule, Convert_v1_ClusterRole_To_rbac_ClusterRole, Convert_rbac_ClusterRole_To_v1_ClusterRole, Convert_v1_ClusterRoleBinding_To_rbac_ClusterRoleBinding, @@ -61,9 +65,30 @@ func RegisterConversions(scheme *runtime.Scheme) error { ) } +func autoConvert_v1_AggregationRule_To_rbac_AggregationRule(in *v1.AggregationRule, out *rbac.AggregationRule, s conversion.Scope) error { + out.ClusterRoleSelectors = *(*[]meta_v1.LabelSelector)(unsafe.Pointer(&in.ClusterRoleSelectors)) + return nil +} + +// Convert_v1_AggregationRule_To_rbac_AggregationRule is an autogenerated conversion function. +func Convert_v1_AggregationRule_To_rbac_AggregationRule(in *v1.AggregationRule, out *rbac.AggregationRule, s conversion.Scope) error { + return autoConvert_v1_AggregationRule_To_rbac_AggregationRule(in, out, s) +} + +func autoConvert_rbac_AggregationRule_To_v1_AggregationRule(in *rbac.AggregationRule, out *v1.AggregationRule, s conversion.Scope) error { + out.ClusterRoleSelectors = *(*[]meta_v1.LabelSelector)(unsafe.Pointer(&in.ClusterRoleSelectors)) + return nil +} + +// Convert_rbac_AggregationRule_To_v1_AggregationRule is an autogenerated conversion function. +func Convert_rbac_AggregationRule_To_v1_AggregationRule(in *rbac.AggregationRule, out *v1.AggregationRule, s conversion.Scope) error { + return autoConvert_rbac_AggregationRule_To_v1_AggregationRule(in, out, s) +} + func autoConvert_v1_ClusterRole_To_rbac_ClusterRole(in *v1.ClusterRole, out *rbac.ClusterRole, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta out.Rules = *(*[]rbac.PolicyRule)(unsafe.Pointer(&in.Rules)) + out.AggregationRule = (*rbac.AggregationRule)(unsafe.Pointer(in.AggregationRule)) return nil } @@ -75,6 +100,7 @@ func Convert_v1_ClusterRole_To_rbac_ClusterRole(in *v1.ClusterRole, out *rbac.Cl func autoConvert_rbac_ClusterRole_To_v1_ClusterRole(in *rbac.ClusterRole, out *v1.ClusterRole, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta out.Rules = *(*[]v1.PolicyRule)(unsafe.Pointer(&in.Rules)) + out.AggregationRule = (*v1.AggregationRule)(unsafe.Pointer(in.AggregationRule)) return nil } diff --git a/pkg/apis/rbac/v1/zz_generated.defaults.go b/pkg/apis/rbac/v1/zz_generated.defaults.go index 756d155b33b..32fae3fa9f0 100644 --- a/pkg/apis/rbac/v1/zz_generated.defaults.go +++ b/pkg/apis/rbac/v1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/apis/rbac/v1alpha1/BUILD b/pkg/apis/rbac/v1alpha1/BUILD index 6df936ab46f..fed81967eb7 100644 --- a/pkg/apis/rbac/v1alpha1/BUILD +++ b/pkg/apis/rbac/v1alpha1/BUILD @@ -33,7 +33,7 @@ go_test( srcs = ["conversion_test.go"], importpath = "k8s.io/kubernetes/pkg/apis/rbac/v1alpha1_test", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/rbac:go_default_library", "//pkg/apis/rbac/install:go_default_library", "//vendor/k8s.io/api/rbac/v1alpha1:go_default_library", diff --git a/pkg/apis/rbac/v1alpha1/conversion_test.go b/pkg/apis/rbac/v1alpha1/conversion_test.go index ca436d530e3..2eea7804109 100644 --- a/pkg/apis/rbac/v1alpha1/conversion_test.go +++ b/pkg/apis/rbac/v1alpha1/conversion_test.go @@ -21,7 +21,7 @@ import ( "testing" "k8s.io/api/rbac/v1alpha1" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" rbacapi "k8s.io/kubernetes/pkg/apis/rbac" _ "k8s.io/kubernetes/pkg/apis/rbac/install" ) @@ -96,7 +96,7 @@ func TestConversion(t *testing.T) { } for k, tc := range testcases { internal := &rbacapi.RoleBinding{} - if err := api.Scheme.Convert(tc.old, internal, nil); err != nil { + if err := legacyscheme.Scheme.Convert(tc.old, internal, nil); err != nil { t.Errorf("%s: unexpected error: %v", k, err) } if !reflect.DeepEqual(internal, tc.expected) { diff --git a/pkg/apis/rbac/v1alpha1/doc.go b/pkg/apis/rbac/v1alpha1/doc.go index fa6dbc5cdc8..365f3881436 100644 --- a/pkg/apis/rbac/v1alpha1/doc.go +++ b/pkg/apis/rbac/v1alpha1/doc.go @@ -15,7 +15,7 @@ limitations under the License. */ // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/rbac -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/rbac/v1alpha1 +// +k8s:conversion-gen-external-types=k8s.io/api/rbac/v1alpha1 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/rbac/v1alpha1 diff --git a/pkg/apis/rbac/v1alpha1/zz_generated.conversion.go b/pkg/apis/rbac/v1alpha1/zz_generated.conversion.go index 4c52c5a7839..6bd6c4e76e6 100644 --- a/pkg/apis/rbac/v1alpha1/zz_generated.conversion.go +++ b/pkg/apis/rbac/v1alpha1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,11 +21,13 @@ limitations under the License. package v1alpha1 import ( + unsafe "unsafe" + v1alpha1 "k8s.io/api/rbac/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" rbac "k8s.io/kubernetes/pkg/apis/rbac" - unsafe "unsafe" ) func init() { @@ -36,6 +38,8 @@ func init() { // Public to allow building arbitrary schemes. func RegisterConversions(scheme *runtime.Scheme) error { return scheme.AddGeneratedConversionFuncs( + Convert_v1alpha1_AggregationRule_To_rbac_AggregationRule, + Convert_rbac_AggregationRule_To_v1alpha1_AggregationRule, Convert_v1alpha1_ClusterRole_To_rbac_ClusterRole, Convert_rbac_ClusterRole_To_v1alpha1_ClusterRole, Convert_v1alpha1_ClusterRoleBinding_To_rbac_ClusterRoleBinding, @@ -61,9 +65,30 @@ func RegisterConversions(scheme *runtime.Scheme) error { ) } +func autoConvert_v1alpha1_AggregationRule_To_rbac_AggregationRule(in *v1alpha1.AggregationRule, out *rbac.AggregationRule, s conversion.Scope) error { + out.ClusterRoleSelectors = *(*[]v1.LabelSelector)(unsafe.Pointer(&in.ClusterRoleSelectors)) + return nil +} + +// Convert_v1alpha1_AggregationRule_To_rbac_AggregationRule is an autogenerated conversion function. +func Convert_v1alpha1_AggregationRule_To_rbac_AggregationRule(in *v1alpha1.AggregationRule, out *rbac.AggregationRule, s conversion.Scope) error { + return autoConvert_v1alpha1_AggregationRule_To_rbac_AggregationRule(in, out, s) +} + +func autoConvert_rbac_AggregationRule_To_v1alpha1_AggregationRule(in *rbac.AggregationRule, out *v1alpha1.AggregationRule, s conversion.Scope) error { + out.ClusterRoleSelectors = *(*[]v1.LabelSelector)(unsafe.Pointer(&in.ClusterRoleSelectors)) + return nil +} + +// Convert_rbac_AggregationRule_To_v1alpha1_AggregationRule is an autogenerated conversion function. +func Convert_rbac_AggregationRule_To_v1alpha1_AggregationRule(in *rbac.AggregationRule, out *v1alpha1.AggregationRule, s conversion.Scope) error { + return autoConvert_rbac_AggregationRule_To_v1alpha1_AggregationRule(in, out, s) +} + func autoConvert_v1alpha1_ClusterRole_To_rbac_ClusterRole(in *v1alpha1.ClusterRole, out *rbac.ClusterRole, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta out.Rules = *(*[]rbac.PolicyRule)(unsafe.Pointer(&in.Rules)) + out.AggregationRule = (*rbac.AggregationRule)(unsafe.Pointer(in.AggregationRule)) return nil } @@ -75,6 +100,7 @@ func Convert_v1alpha1_ClusterRole_To_rbac_ClusterRole(in *v1alpha1.ClusterRole, func autoConvert_rbac_ClusterRole_To_v1alpha1_ClusterRole(in *rbac.ClusterRole, out *v1alpha1.ClusterRole, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta out.Rules = *(*[]v1alpha1.PolicyRule)(unsafe.Pointer(&in.Rules)) + out.AggregationRule = (*v1alpha1.AggregationRule)(unsafe.Pointer(in.AggregationRule)) return nil } diff --git a/pkg/apis/rbac/v1alpha1/zz_generated.defaults.go b/pkg/apis/rbac/v1alpha1/zz_generated.defaults.go index 123ba49e915..ee2303e6554 100644 --- a/pkg/apis/rbac/v1alpha1/zz_generated.defaults.go +++ b/pkg/apis/rbac/v1alpha1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/apis/rbac/v1beta1/doc.go b/pkg/apis/rbac/v1beta1/doc.go index 3ae3e51d512..7ba759013a2 100644 --- a/pkg/apis/rbac/v1beta1/doc.go +++ b/pkg/apis/rbac/v1beta1/doc.go @@ -15,7 +15,7 @@ limitations under the License. */ // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/rbac -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/rbac/v1beta1 +// +k8s:conversion-gen-external-types=k8s.io/api/rbac/v1beta1 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/rbac/v1beta1 diff --git a/pkg/apis/rbac/v1beta1/zz_generated.conversion.go b/pkg/apis/rbac/v1beta1/zz_generated.conversion.go index d06b9265aa1..88803f86fb6 100644 --- a/pkg/apis/rbac/v1beta1/zz_generated.conversion.go +++ b/pkg/apis/rbac/v1beta1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,11 +21,13 @@ limitations under the License. package v1beta1 import ( + unsafe "unsafe" + v1beta1 "k8s.io/api/rbac/v1beta1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" rbac "k8s.io/kubernetes/pkg/apis/rbac" - unsafe "unsafe" ) func init() { @@ -36,6 +38,8 @@ func init() { // Public to allow building arbitrary schemes. func RegisterConversions(scheme *runtime.Scheme) error { return scheme.AddGeneratedConversionFuncs( + Convert_v1beta1_AggregationRule_To_rbac_AggregationRule, + Convert_rbac_AggregationRule_To_v1beta1_AggregationRule, Convert_v1beta1_ClusterRole_To_rbac_ClusterRole, Convert_rbac_ClusterRole_To_v1beta1_ClusterRole, Convert_v1beta1_ClusterRoleBinding_To_rbac_ClusterRoleBinding, @@ -61,9 +65,30 @@ func RegisterConversions(scheme *runtime.Scheme) error { ) } +func autoConvert_v1beta1_AggregationRule_To_rbac_AggregationRule(in *v1beta1.AggregationRule, out *rbac.AggregationRule, s conversion.Scope) error { + out.ClusterRoleSelectors = *(*[]v1.LabelSelector)(unsafe.Pointer(&in.ClusterRoleSelectors)) + return nil +} + +// Convert_v1beta1_AggregationRule_To_rbac_AggregationRule is an autogenerated conversion function. +func Convert_v1beta1_AggregationRule_To_rbac_AggregationRule(in *v1beta1.AggregationRule, out *rbac.AggregationRule, s conversion.Scope) error { + return autoConvert_v1beta1_AggregationRule_To_rbac_AggregationRule(in, out, s) +} + +func autoConvert_rbac_AggregationRule_To_v1beta1_AggregationRule(in *rbac.AggregationRule, out *v1beta1.AggregationRule, s conversion.Scope) error { + out.ClusterRoleSelectors = *(*[]v1.LabelSelector)(unsafe.Pointer(&in.ClusterRoleSelectors)) + return nil +} + +// Convert_rbac_AggregationRule_To_v1beta1_AggregationRule is an autogenerated conversion function. +func Convert_rbac_AggregationRule_To_v1beta1_AggregationRule(in *rbac.AggregationRule, out *v1beta1.AggregationRule, s conversion.Scope) error { + return autoConvert_rbac_AggregationRule_To_v1beta1_AggregationRule(in, out, s) +} + func autoConvert_v1beta1_ClusterRole_To_rbac_ClusterRole(in *v1beta1.ClusterRole, out *rbac.ClusterRole, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta out.Rules = *(*[]rbac.PolicyRule)(unsafe.Pointer(&in.Rules)) + out.AggregationRule = (*rbac.AggregationRule)(unsafe.Pointer(in.AggregationRule)) return nil } @@ -75,6 +100,7 @@ func Convert_v1beta1_ClusterRole_To_rbac_ClusterRole(in *v1beta1.ClusterRole, ou func autoConvert_rbac_ClusterRole_To_v1beta1_ClusterRole(in *rbac.ClusterRole, out *v1beta1.ClusterRole, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta out.Rules = *(*[]v1beta1.PolicyRule)(unsafe.Pointer(&in.Rules)) + out.AggregationRule = (*v1beta1.AggregationRule)(unsafe.Pointer(in.AggregationRule)) return nil } diff --git a/pkg/apis/rbac/v1beta1/zz_generated.defaults.go b/pkg/apis/rbac/v1beta1/zz_generated.defaults.go index 08ff0c71480..8486ab7a4bc 100644 --- a/pkg/apis/rbac/v1beta1/zz_generated.defaults.go +++ b/pkg/apis/rbac/v1beta1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/apis/rbac/validation/BUILD b/pkg/apis/rbac/validation/BUILD index d3a74c673cc..8b7d835d3a4 100644 --- a/pkg/apis/rbac/validation/BUILD +++ b/pkg/apis/rbac/validation/BUILD @@ -11,9 +11,11 @@ go_library( srcs = ["validation.go"], importpath = "k8s.io/kubernetes/pkg/apis/rbac/validation", deps = [ - "//pkg/api/validation:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//pkg/apis/rbac:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/validation/path:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", ], ) @@ -21,8 +23,8 @@ go_library( go_test( name = "go_default_test", srcs = ["validation_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/apis/rbac/validation", - library = ":go_default_library", deps = [ "//pkg/apis/rbac:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/apis/rbac/validation/validation.go b/pkg/apis/rbac/validation/validation.go index 0fc3cb1fbc3..92fc5bdb15b 100644 --- a/pkg/apis/rbac/validation/validation.go +++ b/pkg/apis/rbac/validation/validation.go @@ -18,8 +18,10 @@ package validation import ( "k8s.io/apimachinery/pkg/api/validation/path" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/apis/core/validation" "k8s.io/kubernetes/pkg/apis/rbac" ) @@ -61,6 +63,22 @@ func ValidateClusterRole(role *rbac.ClusterRole) field.ErrorList { allErrs = append(allErrs, err...) } } + + if role.AggregationRule != nil { + if len(role.AggregationRule.ClusterRoleSelectors) == 0 { + allErrs = append(allErrs, field.Required(field.NewPath("aggregationRule", "clusterRoleSelectors"), "at least one clusterRoleSelector required if aggregationRule is non-nil")) + } + for i, selector := range role.AggregationRule.ClusterRoleSelectors { + fieldPath := field.NewPath("aggregationRule", "clusterRoleSelectors").Index(i) + allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(&selector, fieldPath)...) + + selector, err := metav1.LabelSelectorAsSelector(&selector) + if err != nil { + allErrs = append(allErrs, field.Invalid(fieldPath, selector, "invalid label selector.")) + } + } + } + if len(allErrs) != 0 { return allErrs } diff --git a/pkg/apis/rbac/zz_generated.deepcopy.go b/pkg/apis/rbac/zz_generated.deepcopy.go index ce11037427a..2d84b8bbeb6 100644 --- a/pkg/apis/rbac/zz_generated.deepcopy.go +++ b/pkg/apis/rbac/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,66 +21,31 @@ limitations under the License. package rbac import ( - conversion "k8s.io/apimachinery/pkg/conversion" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AggregationRule) DeepCopyInto(out *AggregationRule) { + *out = *in + if in.ClusterRoleSelectors != nil { + in, out := &in.ClusterRoleSelectors, &out.ClusterRoleSelectors + *out = make([]v1.LabelSelector, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return } -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterRole).DeepCopyInto(out.(*ClusterRole)) - return nil - }, InType: reflect.TypeOf(&ClusterRole{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterRoleBinding).DeepCopyInto(out.(*ClusterRoleBinding)) - return nil - }, InType: reflect.TypeOf(&ClusterRoleBinding{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterRoleBindingList).DeepCopyInto(out.(*ClusterRoleBindingList)) - return nil - }, InType: reflect.TypeOf(&ClusterRoleBindingList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterRoleList).DeepCopyInto(out.(*ClusterRoleList)) - return nil - }, InType: reflect.TypeOf(&ClusterRoleList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PolicyRule).DeepCopyInto(out.(*PolicyRule)) - return nil - }, InType: reflect.TypeOf(&PolicyRule{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Role).DeepCopyInto(out.(*Role)) - return nil - }, InType: reflect.TypeOf(&Role{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RoleBinding).DeepCopyInto(out.(*RoleBinding)) - return nil - }, InType: reflect.TypeOf(&RoleBinding{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RoleBindingList).DeepCopyInto(out.(*RoleBindingList)) - return nil - }, InType: reflect.TypeOf(&RoleBindingList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RoleList).DeepCopyInto(out.(*RoleList)) - return nil - }, InType: reflect.TypeOf(&RoleList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RoleRef).DeepCopyInto(out.(*RoleRef)) - return nil - }, InType: reflect.TypeOf(&RoleRef{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Subject).DeepCopyInto(out.(*Subject)) - return nil - }, InType: reflect.TypeOf(&Subject{})}, - ) +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AggregationRule. +func (in *AggregationRule) DeepCopy() *AggregationRule { + if in == nil { + return nil + } + out := new(AggregationRule) + in.DeepCopyInto(out) + return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -95,6 +60,15 @@ func (in *ClusterRole) DeepCopyInto(out *ClusterRole) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.AggregationRule != nil { + in, out := &in.AggregationRule, &out.AggregationRule + if *in == nil { + *out = nil + } else { + *out = new(AggregationRule) + (*in).DeepCopyInto(*out) + } + } return } diff --git a/pkg/apis/scheduling/BUILD b/pkg/apis/scheduling/BUILD index bae1489d2eb..58811a51216 100644 --- a/pkg/apis/scheduling/BUILD +++ b/pkg/apis/scheduling/BUILD @@ -16,7 +16,6 @@ go_library( importpath = "k8s.io/kubernetes/pkg/apis/scheduling", deps = [ "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/pkg/apis/scheduling/doc.go b/pkg/apis/scheduling/doc.go index 782bf3fbd00..f2745c227e2 100644 --- a/pkg/apis/scheduling/doc.go +++ b/pkg/apis/scheduling/doc.go @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +groupName=scheduling.k8s.io package scheduling // import "k8s.io/kubernetes/pkg/apis/scheduling" diff --git a/pkg/apis/scheduling/install/BUILD b/pkg/apis/scheduling/install/BUILD index c0cf8f9ea4c..b151648488f 100644 --- a/pkg/apis/scheduling/install/BUILD +++ b/pkg/apis/scheduling/install/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["install.go"], importpath = "k8s.io/kubernetes/pkg/apis/scheduling/install", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/scheduling:go_default_library", "//pkg/apis/scheduling/v1alpha1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", diff --git a/pkg/apis/scheduling/install/install.go b/pkg/apis/scheduling/install/install.go index d35d4705843..0a47a8815ce 100644 --- a/pkg/apis/scheduling/install/install.go +++ b/pkg/apis/scheduling/install/install.go @@ -23,13 +23,13 @@ import ( "k8s.io/apimachinery/pkg/apimachinery/registered" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/scheduling" "k8s.io/kubernetes/pkg/apis/scheduling/v1alpha1" ) func init() { - Install(api.GroupFactoryRegistry, api.Registry, api.Scheme) + Install(legacyscheme.GroupFactoryRegistry, legacyscheme.Registry, legacyscheme.Scheme) } // Install registers the API group and adds types to a scheme diff --git a/pkg/apis/scheduling/v1alpha1/doc.go b/pkg/apis/scheduling/v1alpha1/doc.go index 115597cc3b8..e2bf21c7697 100644 --- a/pkg/apis/scheduling/v1alpha1/doc.go +++ b/pkg/apis/scheduling/v1alpha1/doc.go @@ -15,7 +15,7 @@ limitations under the License. */ // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/scheduling -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/scheduling/v1alpha1 +// +k8s:conversion-gen-external-types=k8s.io/api/scheduling/v1alpha1 // +groupName=scheduling.k8s.io // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/scheduling/v1alpha1 diff --git a/pkg/apis/scheduling/v1alpha1/zz_generated.conversion.go b/pkg/apis/scheduling/v1alpha1/zz_generated.conversion.go index 6a99b58a6e5..e3a3602bca5 100644 --- a/pkg/apis/scheduling/v1alpha1/zz_generated.conversion.go +++ b/pkg/apis/scheduling/v1alpha1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,11 +21,12 @@ limitations under the License. package v1alpha1 import ( + unsafe "unsafe" + v1alpha1 "k8s.io/api/scheduling/v1alpha1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" scheduling "k8s.io/kubernetes/pkg/apis/scheduling" - unsafe "unsafe" ) func init() { diff --git a/pkg/apis/scheduling/v1alpha1/zz_generated.defaults.go b/pkg/apis/scheduling/v1alpha1/zz_generated.defaults.go index 7e6df29d4ae..5e24d22cacd 100644 --- a/pkg/apis/scheduling/v1alpha1/zz_generated.defaults.go +++ b/pkg/apis/scheduling/v1alpha1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/apis/scheduling/validation/BUILD b/pkg/apis/scheduling/validation/BUILD index 5056a08c1b4..875e4f75cfd 100644 --- a/pkg/apis/scheduling/validation/BUILD +++ b/pkg/apis/scheduling/validation/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["validation_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/apis/scheduling/validation", - library = ":go_default_library", deps = [ "//pkg/apis/scheduling:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -23,7 +23,7 @@ go_library( srcs = ["validation.go"], importpath = "k8s.io/kubernetes/pkg/apis/scheduling/validation", deps = [ - "//pkg/api/validation:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//pkg/apis/scheduling:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", ], diff --git a/pkg/apis/scheduling/validation/validation.go b/pkg/apis/scheduling/validation/validation.go index 0c32fe2513d..f4fac9d9cea 100644 --- a/pkg/apis/scheduling/validation/validation.go +++ b/pkg/apis/scheduling/validation/validation.go @@ -18,7 +18,7 @@ package validation import ( "k8s.io/apimachinery/pkg/util/validation/field" - apivalidation "k8s.io/kubernetes/pkg/api/validation" + apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" "k8s.io/kubernetes/pkg/apis/scheduling" ) diff --git a/pkg/apis/scheduling/zz_generated.deepcopy.go b/pkg/apis/scheduling/zz_generated.deepcopy.go index 9eb362fad5f..93b95da65d2 100644 --- a/pkg/apis/scheduling/zz_generated.deepcopy.go +++ b/pkg/apis/scheduling/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,32 +21,9 @@ limitations under the License. package scheduling import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PriorityClass).DeepCopyInto(out.(*PriorityClass)) - return nil - }, InType: reflect.TypeOf(&PriorityClass{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PriorityClassList).DeepCopyInto(out.(*PriorityClassList)) - return nil - }, InType: reflect.TypeOf(&PriorityClassList{})}, - ) -} - // 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 diff --git a/pkg/apis/settings/BUILD b/pkg/apis/settings/BUILD index cb4d216f5b6..2e9f40d277a 100644 --- a/pkg/apis/settings/BUILD +++ b/pkg/apis/settings/BUILD @@ -15,9 +15,8 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/apis/settings", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/pkg/apis/settings/doc.go b/pkg/apis/settings/doc.go index 292cb6b244f..6093e3e8241 100644 --- a/pkg/apis/settings/doc.go +++ b/pkg/apis/settings/doc.go @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +groupName=settings.k8s.io package settings // import "k8s.io/kubernetes/pkg/apis/settings" diff --git a/pkg/apis/settings/install/BUILD b/pkg/apis/settings/install/BUILD index 9b304daa2c2..9a6ffe726ac 100644 --- a/pkg/apis/settings/install/BUILD +++ b/pkg/apis/settings/install/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["install.go"], importpath = "k8s.io/kubernetes/pkg/apis/settings/install", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/settings:go_default_library", "//pkg/apis/settings/v1alpha1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", diff --git a/pkg/apis/settings/install/install.go b/pkg/apis/settings/install/install.go index fdaf7d2c37e..bf4ae5c5796 100644 --- a/pkg/apis/settings/install/install.go +++ b/pkg/apis/settings/install/install.go @@ -22,13 +22,13 @@ import ( "k8s.io/apimachinery/pkg/apimachinery/announced" "k8s.io/apimachinery/pkg/apimachinery/registered" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/settings" "k8s.io/kubernetes/pkg/apis/settings/v1alpha1" ) func init() { - Install(api.GroupFactoryRegistry, api.Registry, api.Scheme) + Install(legacyscheme.GroupFactoryRegistry, legacyscheme.Registry, legacyscheme.Scheme) } // Install registers the API group and adds types to a scheme diff --git a/pkg/apis/settings/register.go b/pkg/apis/settings/register.go index 85847012793..7b144b7dfc8 100644 --- a/pkg/apis/settings/register.go +++ b/pkg/apis/settings/register.go @@ -42,7 +42,7 @@ func Resource(resource string) schema.GroupResource { return SchemeGroupVersion.WithResource(resource).GroupResource() } -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &PodPreset{}, diff --git a/pkg/apis/settings/types.go b/pkg/apis/settings/types.go index 1087f00f805..876c8025532 100644 --- a/pkg/apis/settings/types.go +++ b/pkg/apis/settings/types.go @@ -18,7 +18,7 @@ package settings import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // +genclient diff --git a/pkg/apis/settings/v1alpha1/BUILD b/pkg/apis/settings/v1alpha1/BUILD index 923fd9e5794..7869c4fad8e 100644 --- a/pkg/apis/settings/v1alpha1/BUILD +++ b/pkg/apis/settings/v1alpha1/BUILD @@ -15,8 +15,8 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/apis/settings/v1alpha1", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/v1:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/v1:go_default_library", "//pkg/apis/settings:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/settings/v1alpha1:go_default_library", diff --git a/pkg/apis/settings/v1alpha1/doc.go b/pkg/apis/settings/v1alpha1/doc.go index cdc4161eb0e..4422bb3e77b 100644 --- a/pkg/apis/settings/v1alpha1/doc.go +++ b/pkg/apis/settings/v1alpha1/doc.go @@ -15,7 +15,7 @@ limitations under the License. */ // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/settings -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/settings/v1alpha1 +// +k8s:conversion-gen-external-types=k8s.io/api/settings/v1alpha1 // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/settings/v1alpha1 diff --git a/pkg/apis/settings/v1alpha1/zz_generated.conversion.go b/pkg/apis/settings/v1alpha1/zz_generated.conversion.go index 396102ecd84..08ab0049f66 100644 --- a/pkg/apis/settings/v1alpha1/zz_generated.conversion.go +++ b/pkg/apis/settings/v1alpha1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,13 +21,14 @@ limitations under the License. package v1alpha1 import ( + unsafe "unsafe" + v1 "k8s.io/api/core/v1" v1alpha1 "k8s.io/api/settings/v1alpha1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" settings "k8s.io/kubernetes/pkg/apis/settings" - unsafe "unsafe" ) func init() { @@ -117,11 +118,11 @@ func Convert_settings_PodPresetList_To_v1alpha1_PodPresetList(in *settings.PodPr func autoConvert_v1alpha1_PodPresetSpec_To_settings_PodPresetSpec(in *v1alpha1.PodPresetSpec, out *settings.PodPresetSpec, s conversion.Scope) error { out.Selector = in.Selector - out.Env = *(*[]api.EnvVar)(unsafe.Pointer(&in.Env)) - out.EnvFrom = *(*[]api.EnvFromSource)(unsafe.Pointer(&in.EnvFrom)) + out.Env = *(*[]core.EnvVar)(unsafe.Pointer(&in.Env)) + out.EnvFrom = *(*[]core.EnvFromSource)(unsafe.Pointer(&in.EnvFrom)) if in.Volumes != nil { in, out := &in.Volumes, &out.Volumes - *out = make([]api.Volume, len(*in)) + *out = make([]core.Volume, len(*in)) for i := range *in { // TODO: Inefficient conversion - can we improve it? if err := s.Convert(&(*in)[i], &(*out)[i], 0); err != nil { @@ -131,7 +132,7 @@ func autoConvert_v1alpha1_PodPresetSpec_To_settings_PodPresetSpec(in *v1alpha1.P } else { out.Volumes = nil } - out.VolumeMounts = *(*[]api.VolumeMount)(unsafe.Pointer(&in.VolumeMounts)) + out.VolumeMounts = *(*[]core.VolumeMount)(unsafe.Pointer(&in.VolumeMounts)) return nil } diff --git a/pkg/apis/settings/v1alpha1/zz_generated.defaults.go b/pkg/apis/settings/v1alpha1/zz_generated.defaults.go index b2fa4af7d3e..ef7e61652fa 100644 --- a/pkg/apis/settings/v1alpha1/zz_generated.defaults.go +++ b/pkg/apis/settings/v1alpha1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,7 +23,7 @@ package v1alpha1 import ( v1alpha1 "k8s.io/api/settings/v1alpha1" runtime "k8s.io/apimachinery/pkg/runtime" - v1 "k8s.io/kubernetes/pkg/api/v1" + v1 "k8s.io/kubernetes/pkg/apis/core/v1" ) // RegisterDefaults adds defaulters functions to the given scheme. diff --git a/pkg/apis/settings/validation/BUILD b/pkg/apis/settings/validation/BUILD index 9b1ab0c1e78..74813488201 100644 --- a/pkg/apis/settings/validation/BUILD +++ b/pkg/apis/settings/validation/BUILD @@ -9,10 +9,10 @@ load( go_test( name = "go_default_test", srcs = ["validation_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/apis/settings/validation", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/settings:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", ], @@ -23,7 +23,7 @@ go_library( srcs = ["validation.go"], importpath = "k8s.io/kubernetes/pkg/apis/settings/validation", deps = [ - "//pkg/api/validation:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//pkg/apis/settings:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", diff --git a/pkg/apis/settings/validation/validation.go b/pkg/apis/settings/validation/validation.go index 6f02781ff49..50ac75aab44 100644 --- a/pkg/apis/settings/validation/validation.go +++ b/pkg/apis/settings/validation/validation.go @@ -19,7 +19,7 @@ package validation import ( unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" "k8s.io/apimachinery/pkg/util/validation/field" - apivalidation "k8s.io/kubernetes/pkg/api/validation" + apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" "k8s.io/kubernetes/pkg/apis/settings" ) @@ -43,11 +43,11 @@ func ValidatePodPresetSpec(spec *settings.PodPresetSpec, fldPath *field.Path) fi allErrs = append(allErrs, field.Required(fldPath.Child("volumes", "env", "envFrom", "volumeMounts"), "must specify at least one")) } - volumes, vErrs := apivalidation.ValidateVolumes(spec.Volumes, fldPath.Child("volumes")) + vols, vErrs := apivalidation.ValidateVolumes(spec.Volumes, fldPath.Child("volumes")) allErrs = append(allErrs, vErrs...) allErrs = append(allErrs, apivalidation.ValidateEnv(spec.Env, fldPath.Child("env"))...) allErrs = append(allErrs, apivalidation.ValidateEnvFrom(spec.EnvFrom, fldPath.Child("envFrom"))...) - allErrs = append(allErrs, apivalidation.ValidateVolumeMounts(spec.VolumeMounts, volumes, nil, fldPath.Child("volumeMounts"))...) + allErrs = append(allErrs, apivalidation.ValidateVolumeMounts(spec.VolumeMounts, nil, vols, nil, fldPath.Child("volumeMounts"))...) return allErrs } diff --git a/pkg/apis/settings/validation/validation_test.go b/pkg/apis/settings/validation/validation_test.go index 343f6465c0c..884b15f2b7a 100644 --- a/pkg/apis/settings/validation/validation_test.go +++ b/pkg/apis/settings/validation/validation_test.go @@ -21,7 +21,7 @@ import ( "testing" "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/settings" ) diff --git a/pkg/apis/settings/zz_generated.deepcopy.go b/pkg/apis/settings/zz_generated.deepcopy.go index a6d188a3986..90ec26ed20c 100644 --- a/pkg/apis/settings/zz_generated.deepcopy.go +++ b/pkg/apis/settings/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,37 +21,10 @@ limitations under the License. package settings import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - api "k8s.io/kubernetes/pkg/api" - reflect "reflect" + core "k8s.io/kubernetes/pkg/apis/core" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodPreset).DeepCopyInto(out.(*PodPreset)) - return nil - }, InType: reflect.TypeOf(&PodPreset{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodPresetList).DeepCopyInto(out.(*PodPresetList)) - return nil - }, InType: reflect.TypeOf(&PodPresetList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodPresetSpec).DeepCopyInto(out.(*PodPresetSpec)) - return nil - }, InType: reflect.TypeOf(&PodPresetSpec{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PodPreset) DeepCopyInto(out *PodPreset) { *out = *in @@ -120,28 +93,28 @@ func (in *PodPresetSpec) DeepCopyInto(out *PodPresetSpec) { in.Selector.DeepCopyInto(&out.Selector) if in.Env != nil { in, out := &in.Env, &out.Env - *out = make([]api.EnvVar, len(*in)) + *out = make([]core.EnvVar, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } if in.EnvFrom != nil { in, out := &in.EnvFrom, &out.EnvFrom - *out = make([]api.EnvFromSource, len(*in)) + *out = make([]core.EnvFromSource, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } if in.Volumes != nil { in, out := &in.Volumes, &out.Volumes - *out = make([]api.Volume, len(*in)) + *out = make([]core.Volume, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } if in.VolumeMounts != nil { in, out := &in.VolumeMounts, &out.VolumeMounts - *out = make([]api.VolumeMount, len(*in)) + *out = make([]core.VolumeMount, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } diff --git a/pkg/apis/storage/BUILD b/pkg/apis/storage/BUILD index aa487dde151..b8d3d38ef27 100644 --- a/pkg/apis/storage/BUILD +++ b/pkg/apis/storage/BUILD @@ -15,9 +15,8 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/apis/storage", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], @@ -38,6 +37,7 @@ filegroup( "//pkg/apis/storage/install:all-srcs", "//pkg/apis/storage/util:all-srcs", "//pkg/apis/storage/v1:all-srcs", + "//pkg/apis/storage/v1alpha1:all-srcs", "//pkg/apis/storage/v1beta1:all-srcs", "//pkg/apis/storage/validation:all-srcs", ], diff --git a/pkg/apis/storage/doc.go b/pkg/apis/storage/doc.go index 51dbb344c74..5220b981e47 100644 --- a/pkg/apis/storage/doc.go +++ b/pkg/apis/storage/doc.go @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +groupName=storage.k8s.io package storage // import "k8s.io/kubernetes/pkg/apis/storage" diff --git a/pkg/apis/storage/fuzzer/BUILD b/pkg/apis/storage/fuzzer/BUILD index 150483ad356..fcec240ec87 100644 --- a/pkg/apis/storage/fuzzer/BUILD +++ b/pkg/apis/storage/fuzzer/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["fuzzer.go"], importpath = "k8s.io/kubernetes/pkg/apis/storage/fuzzer", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/storage:go_default_library", "//vendor/github.com/google/gofuzz:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", diff --git a/pkg/apis/storage/fuzzer/fuzzer.go b/pkg/apis/storage/fuzzer/fuzzer.go index e8a36514947..ea35b74a5ac 100644 --- a/pkg/apis/storage/fuzzer/fuzzer.go +++ b/pkg/apis/storage/fuzzer/fuzzer.go @@ -20,7 +20,7 @@ import ( fuzz "github.com/google/gofuzz" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/storage" ) diff --git a/pkg/apis/storage/install/BUILD b/pkg/apis/storage/install/BUILD index 91b5cfef424..798341862f4 100644 --- a/pkg/apis/storage/install/BUILD +++ b/pkg/apis/storage/install/BUILD @@ -10,9 +10,10 @@ go_library( srcs = ["install.go"], importpath = "k8s.io/kubernetes/pkg/apis/storage/install", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/storage:go_default_library", "//pkg/apis/storage/v1:go_default_library", + "//pkg/apis/storage/v1alpha1:go_default_library", "//pkg/apis/storage/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library", diff --git a/pkg/apis/storage/install/install.go b/pkg/apis/storage/install/install.go index 6c645feb634..28fa7865968 100644 --- a/pkg/apis/storage/install/install.go +++ b/pkg/apis/storage/install/install.go @@ -23,28 +23,33 @@ import ( "k8s.io/apimachinery/pkg/apimachinery/registered" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/storage" "k8s.io/kubernetes/pkg/apis/storage/v1" + "k8s.io/kubernetes/pkg/apis/storage/v1alpha1" "k8s.io/kubernetes/pkg/apis/storage/v1beta1" ) func init() { - Install(api.GroupFactoryRegistry, api.Registry, api.Scheme) + Install(legacyscheme.GroupFactoryRegistry, legacyscheme.Registry, legacyscheme.Scheme) } // Install registers the API group and adds types to a scheme func Install(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *registered.APIRegistrationManager, scheme *runtime.Scheme) { if err := announced.NewGroupMetaFactory( &announced.GroupMetaFactoryArgs{ - GroupName: storage.GroupName, - VersionPreferenceOrder: []string{v1.SchemeGroupVersion.Version, v1beta1.SchemeGroupVersion.Version}, - RootScopedKinds: sets.NewString("StorageClass"), + GroupName: storage.GroupName, + VersionPreferenceOrder: []string{v1.SchemeGroupVersion.Version, v1beta1.SchemeGroupVersion.Version, v1alpha1.SchemeGroupVersion.Version}, + RootScopedKinds: sets.NewString( + "StorageClass", + "VolumeAttachment", + ), AddInternalObjectsToScheme: storage.AddToScheme, }, announced.VersionToSchemeFunc{ - v1.SchemeGroupVersion.Version: v1.AddToScheme, - v1beta1.SchemeGroupVersion.Version: v1beta1.AddToScheme, + v1.SchemeGroupVersion.Version: v1.AddToScheme, + v1beta1.SchemeGroupVersion.Version: v1beta1.AddToScheme, + v1alpha1.SchemeGroupVersion.Version: v1alpha1.AddToScheme, }, ).Announce(groupFactoryRegistry).RegisterAndEnable(registry, scheme); err != nil { panic(err) diff --git a/pkg/apis/storage/register.go b/pkg/apis/storage/register.go index aaa619b4d97..7ae2f3efe18 100644 --- a/pkg/apis/storage/register.go +++ b/pkg/apis/storage/register.go @@ -46,6 +46,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &StorageClass{}, &StorageClassList{}, + &VolumeAttachment{}, + &VolumeAttachmentList{}, ) return nil } diff --git a/pkg/apis/storage/types.go b/pkg/apis/storage/types.go index 1af94b2d745..e44b327d381 100644 --- a/pkg/apis/storage/types.go +++ b/pkg/apis/storage/types.go @@ -18,7 +18,7 @@ package storage import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // +genclient @@ -65,6 +65,13 @@ type StorageClass struct { // for all PVs created from this storageclass. // +optional AllowVolumeExpansion *bool + + // VolumeBindingMode indicates how PersistentVolumeClaims should be + // provisioned and bound. When unset, VolumeBindingImmediate is used. + // This field is alpha-level and is only honored by servers that enable + // the VolumeScheduling feature. + // +optional + VolumeBindingMode *VolumeBindingMode } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -80,3 +87,125 @@ type StorageClassList struct { // Items is the list of StorageClasses Items []StorageClass } + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Captures the intent to attach or detach the specified volume to/from +// the specified node. +// +// VolumeAttachment objects are non-namespaced. +type VolumeAttachment struct { + metav1.TypeMeta + + // Standard object metadata. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + // +optional + metav1.ObjectMeta + + // Specification of the desired attach/detach volume behavior. + // Populated by the Kubernetes system. + Spec VolumeAttachmentSpec + + // Status of the VolumeAttachment request. + // Populated by the entity completing the attach or detach + // operation, i.e. the external-attacher. + // +optional + Status VolumeAttachmentStatus +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// VolumeAttachmentList is a collection of VolumeAttachment objects. +type VolumeAttachmentList struct { + metav1.TypeMeta + // Standard list metadata + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + // +optional + metav1.ListMeta + + // Items is the list of VolumeAttachments + Items []VolumeAttachment +} + +// The specification of a VolumeAttachment request. +type VolumeAttachmentSpec struct { + // Attacher indicates the name of the volume driver that MUST handle this + // request. This is the name returned by GetPluginName(). + Attacher string + + // Source represents the volume that should be attached. + Source VolumeAttachmentSource + + // The node that the volume should be attached to. + NodeName string +} + +// VolumeAttachmentSource represents a volume that should be attached. +// Right now only PersistenVolumes can be attached via external attacher, +// in future we may allow also inline volumes in pods. +// Exactly one member can be set. +type VolumeAttachmentSource struct { + // Name of the persistent volume to attach. + // +optional + PersistentVolumeName *string + + // Placeholder for *VolumeSource to accommodate inline volumes in pods. +} + +// The status of a VolumeAttachment request. +type VolumeAttachmentStatus struct { + // Indicates the volume is successfully attached. + // This field must only be set by the entity completing the attach + // operation, i.e. the external-attacher. + Attached bool + + // Upon successful attach, this field is populated with any + // information returned by the attach operation that must be passed + // into subsequent WaitForAttach or Mount calls. + // This field must only be set by the entity completing the attach + // operation, i.e. the external-attacher. + // +optional + AttachmentMetadata map[string]string + + // The last error encountered during attach operation, if any. + // This field must only be set by the entity completing the attach + // operation, i.e. the external-attacher. + // +optional + AttachError *VolumeError + + // The last error encountered during detach operation, if any. + // This field must only be set by the entity completing the detach + // operation, i.e. the external-attacher. + // +optional + DetachError *VolumeError +} + +// Captures an error encountered during a volume operation. +type VolumeError struct { + // Time the error was encountered. + // +optional + Time metav1.Time + + // String detailing the error encountered during Attach or Detach operation. + // This string maybe logged, so it should not contain sensitive + // information. + // +optional + Message string +} + +// VolumeBindingMode indicates how PersistentVolumeClaims should be bound. +type VolumeBindingMode string + +const ( + // VolumeBindingImmediate indicates that PersistentVolumeClaims should be + // immediately provisioned and bound. + VolumeBindingImmediate VolumeBindingMode = "Immediate" + + // VolumeBindingWaitForFirstConsumer indicates that PersistentVolumeClaims + // should not be provisioned and bound until the first Pod is created that + // references the PeristentVolumeClaim. The volume provisioning and + // binding will occur during Pod scheduing. + VolumeBindingWaitForFirstConsumer VolumeBindingMode = "WaitForFirstConsumer" +) diff --git a/pkg/apis/storage/util/BUILD b/pkg/apis/storage/util/BUILD index 297c1df9b02..3e8c883b105 100644 --- a/pkg/apis/storage/util/BUILD +++ b/pkg/apis/storage/util/BUILD @@ -3,13 +3,22 @@ package(default_visibility = ["//visibility:public"]) load( "@io_bazel_rules_go//go:def.bzl", "go_library", + "go_test", ) go_library( name = "go_default_library", - srcs = ["helpers.go"], + srcs = [ + "helpers.go", + "util.go", + ], importpath = "k8s.io/kubernetes/pkg/apis/storage/util", - deps = ["//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library"], + deps = [ + "//pkg/apis/storage:go_default_library", + "//pkg/features:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + ], ) filegroup( @@ -24,3 +33,14 @@ filegroup( srcs = [":package-srcs"], tags = ["automanaged"], ) + +go_test( + name = "go_default_test", + srcs = ["util_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/apis/storage/util", + deps = [ + "//pkg/apis/storage:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + ], +) diff --git a/pkg/apis/storage/util/util.go b/pkg/apis/storage/util/util.go new file mode 100644 index 00000000000..9d134043563 --- /dev/null +++ b/pkg/apis/storage/util/util.go @@ -0,0 +1,30 @@ +/* +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 util + +import ( + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/apis/storage" + "k8s.io/kubernetes/pkg/features" +) + +// DropDisabledAlphaFields removes disabled fields from the StorageClass object. +func DropDisabledAlphaFields(class *storage.StorageClass) { + if !utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) { + class.VolumeBindingMode = nil + } +} diff --git a/pkg/apis/storage/util/util_test.go b/pkg/apis/storage/util/util_test.go new file mode 100644 index 00000000000..d75b2455865 --- /dev/null +++ b/pkg/apis/storage/util/util_test.go @@ -0,0 +1,52 @@ +/* +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 util + +import ( + "testing" + + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/apis/storage" +) + +func TestDropAlphaFields(t *testing.T) { + bindingMode := storage.VolumeBindingWaitForFirstConsumer + + // Test that field gets dropped when feature gate is not set + class := &storage.StorageClass{ + VolumeBindingMode: &bindingMode, + } + DropDisabledAlphaFields(class) + if class.VolumeBindingMode != nil { + t.Errorf("VolumeBindingMode field didn't get dropped: %+v", class.VolumeBindingMode) + } + + // Test that field does not get dropped when feature gate is set + class = &storage.StorageClass{ + VolumeBindingMode: &bindingMode, + } + if err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true"); err != nil { + t.Fatalf("Failed to set feature gate for VolumeScheduling: %v", err) + } + DropDisabledAlphaFields(class) + if class.VolumeBindingMode != &bindingMode { + t.Errorf("VolumeBindingMode field got unexpectantly modified: %+v", class.VolumeBindingMode) + } + if err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false"); err != nil { + t.Fatalf("Failed to disable feature gate for VolumeScheduling: %v", err) + } +} diff --git a/pkg/apis/storage/v1/BUILD b/pkg/apis/storage/v1/BUILD index f630ecc27ca..aeb7e17d2a7 100644 --- a/pkg/apis/storage/v1/BUILD +++ b/pkg/apis/storage/v1/BUILD @@ -3,6 +3,7 @@ package(default_visibility = ["//visibility:public"]) load( "@io_bazel_rules_go//go:def.bzl", "go_library", + "go_test", ) go_library( @@ -16,13 +17,15 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/apis/storage/v1", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/storage:go_default_library", + "//pkg/features:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/storage/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", ], ) @@ -41,3 +44,16 @@ filegroup( ], tags = ["automanaged"], ) + +go_test( + name = "go_default_xtest", + srcs = ["defaults_test.go"], + importpath = "k8s.io/kubernetes/pkg/apis/storage/v1_test", + deps = [ + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/storage/install:go_default_library", + "//vendor/k8s.io/api/storage/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + ], +) diff --git a/pkg/apis/storage/v1/defaults.go b/pkg/apis/storage/v1/defaults.go index 2e7c51c632e..6f574f4867c 100644 --- a/pkg/apis/storage/v1/defaults.go +++ b/pkg/apis/storage/v1/defaults.go @@ -20,6 +20,8 @@ import ( "k8s.io/api/core/v1" storagev1 "k8s.io/api/storage/v1" "k8s.io/apimachinery/pkg/runtime" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/features" ) func addDefaultingFuncs(scheme *runtime.Scheme) error { @@ -31,4 +33,9 @@ func SetDefaults_StorageClass(obj *storagev1.StorageClass) { obj.ReclaimPolicy = new(v1.PersistentVolumeReclaimPolicy) *obj.ReclaimPolicy = v1.PersistentVolumeReclaimDelete } + + if obj.VolumeBindingMode == nil && utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) { + obj.VolumeBindingMode = new(storagev1.VolumeBindingMode) + *obj.VolumeBindingMode = storagev1.VolumeBindingImmediate + } } diff --git a/pkg/apis/storage/v1/defaults_test.go b/pkg/apis/storage/v1/defaults_test.go new file mode 100644 index 00000000000..4fb6304f773 --- /dev/null +++ b/pkg/apis/storage/v1/defaults_test.go @@ -0,0 +1,81 @@ +/* +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 v1_test + +import ( + "reflect" + "testing" + + storagev1 "k8s.io/api/storage/v1" + "k8s.io/apimachinery/pkg/runtime" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/api/legacyscheme" + _ "k8s.io/kubernetes/pkg/apis/storage/install" +) + +func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { + codec := legacyscheme.Codecs.LegacyCodec(storagev1.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 TestSetDefaultVolumeBindingMode(t *testing.T) { + class := &storagev1.StorageClass{} + + // When feature gate is disabled, field should not be defaulted + output := roundTrip(t, runtime.Object(class)).(*storagev1.StorageClass) + if output.VolumeBindingMode != nil { + t.Errorf("Expected VolumeBindingMode to not be defaulted, got: %+v", output.VolumeBindingMode) + } + + class = &storagev1.StorageClass{} + + err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true") + if err != nil { + t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err) + } + + // When feature gate is enabled, field should be defaulted + defaultMode := storagev1.VolumeBindingImmediate + output = roundTrip(t, runtime.Object(class)).(*storagev1.StorageClass) + outMode := output.VolumeBindingMode + if outMode == nil { + t.Errorf("Expected VolumeBindingMode to be defaulted to: %+v, got: nil", defaultMode) + } else if *outMode != defaultMode { + t.Errorf("Expected VolumeBindingMode to be defaulted to: %+v, got: %+v", defaultMode, outMode) + } + + err = utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false") + if err != nil { + t.Fatalf("Failed to disable feature gate for VolumeScheduling: %v", err) + } +} diff --git a/pkg/apis/storage/v1/doc.go b/pkg/apis/storage/v1/doc.go index 3a5b7ac2dd1..617aa14c1aa 100644 --- a/pkg/apis/storage/v1/doc.go +++ b/pkg/apis/storage/v1/doc.go @@ -15,7 +15,7 @@ limitations under the License. */ // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/storage -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/storage/v1 +// +k8s:conversion-gen-external-types=k8s.io/api/storage/v1 // +groupName=storage.k8s.io // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/storage/v1 diff --git a/pkg/apis/storage/v1/util/helpers.go b/pkg/apis/storage/v1/util/helpers.go index 62c27ccfc19..d0d5ad7ba99 100644 --- a/pkg/apis/storage/v1/util/helpers.go +++ b/pkg/apis/storage/v1/util/helpers.go @@ -16,7 +16,9 @@ limitations under the License. package util -import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) // IsDefaultStorageClassAnnotation represents a StorageClass annotation that // marks a class as the default StorageClass diff --git a/pkg/apis/storage/v1/zz_generated.conversion.go b/pkg/apis/storage/v1/zz_generated.conversion.go index 59594d26519..cc86dc53505 100644 --- a/pkg/apis/storage/v1/zz_generated.conversion.go +++ b/pkg/apis/storage/v1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,13 +21,14 @@ limitations under the License. package v1 import ( + unsafe "unsafe" + core_v1 "k8s.io/api/core/v1" v1 "k8s.io/api/storage/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" storage "k8s.io/kubernetes/pkg/apis/storage" - unsafe "unsafe" ) func init() { @@ -49,9 +50,10 @@ func autoConvert_v1_StorageClass_To_storage_StorageClass(in *v1.StorageClass, ou out.ObjectMeta = in.ObjectMeta out.Provisioner = in.Provisioner out.Parameters = *(*map[string]string)(unsafe.Pointer(&in.Parameters)) - out.ReclaimPolicy = (*api.PersistentVolumeReclaimPolicy)(unsafe.Pointer(in.ReclaimPolicy)) + out.ReclaimPolicy = (*core.PersistentVolumeReclaimPolicy)(unsafe.Pointer(in.ReclaimPolicy)) out.MountOptions = *(*[]string)(unsafe.Pointer(&in.MountOptions)) out.AllowVolumeExpansion = (*bool)(unsafe.Pointer(in.AllowVolumeExpansion)) + out.VolumeBindingMode = (*storage.VolumeBindingMode)(unsafe.Pointer(in.VolumeBindingMode)) return nil } @@ -67,6 +69,7 @@ func autoConvert_storage_StorageClass_To_v1_StorageClass(in *storage.StorageClas out.ReclaimPolicy = (*core_v1.PersistentVolumeReclaimPolicy)(unsafe.Pointer(in.ReclaimPolicy)) out.MountOptions = *(*[]string)(unsafe.Pointer(&in.MountOptions)) out.AllowVolumeExpansion = (*bool)(unsafe.Pointer(in.AllowVolumeExpansion)) + out.VolumeBindingMode = (*v1.VolumeBindingMode)(unsafe.Pointer(in.VolumeBindingMode)) return nil } diff --git a/pkg/apis/storage/v1/zz_generated.defaults.go b/pkg/apis/storage/v1/zz_generated.defaults.go index 4db23e8cfba..3c2083f7f1f 100644 --- a/pkg/apis/storage/v1/zz_generated.defaults.go +++ b/pkg/apis/storage/v1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/apis/storage/v1alpha1/BUILD b/pkg/apis/storage/v1alpha1/BUILD new file mode 100644 index 00000000000..6617d6a97ea --- /dev/null +++ b/pkg/apis/storage/v1alpha1/BUILD @@ -0,0 +1,34 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "register.go", + "zz_generated.conversion.go", + "zz_generated.defaults.go", + ], + importpath = "k8s.io/kubernetes/pkg/apis/storage/v1alpha1", + visibility = ["//visibility:public"], + deps = [ + "//pkg/apis/storage:go_default_library", + "//vendor/k8s.io/api/storage/v1alpha1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/apis/storage/v1alpha1/doc.go b/pkg/apis/storage/v1alpha1/doc.go new file mode 100644 index 00000000000..07c766c3a28 --- /dev/null +++ b/pkg/apis/storage/v1alpha1/doc.go @@ -0,0 +1,22 @@ +/* +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/storage +// +k8s:conversion-gen-external-types=k8s.io/api/storage/v1alpha1 +// +groupName=storage.k8s.io +// +k8s:defaulter-gen=TypeMeta +// +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/storage/v1alpha1 +package v1alpha1 // import "k8s.io/kubernetes/pkg/apis/storage/v1alpha1" diff --git a/pkg/apis/storage/v1alpha1/register.go b/pkg/apis/storage/v1alpha1/register.go new file mode 100644 index 00000000000..699fab12d5b --- /dev/null +++ b/pkg/apis/storage/v1alpha1/register.go @@ -0,0 +1,38 @@ +/* +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 ( + storagev1alpha1 "k8s.io/api/storage/v1alpha1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName is the group name use in this package +const GroupName = "storage.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 = &storagev1alpha1.SchemeBuilder + AddToScheme = localSchemeBuilder.AddToScheme +) diff --git a/pkg/apis/storage/v1alpha1/zz_generated.conversion.go b/pkg/apis/storage/v1alpha1/zz_generated.conversion.go new file mode 100644 index 00000000000..d1a73f48ae1 --- /dev/null +++ b/pkg/apis/storage/v1alpha1/zz_generated.conversion.go @@ -0,0 +1,203 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by conversion-gen. Do not edit it manually! + +package v1alpha1 + +import ( + unsafe "unsafe" + + v1alpha1 "k8s.io/api/storage/v1alpha1" + conversion "k8s.io/apimachinery/pkg/conversion" + runtime "k8s.io/apimachinery/pkg/runtime" + storage "k8s.io/kubernetes/pkg/apis/storage" +) + +func init() { + localSchemeBuilder.Register(RegisterConversions) +} + +// RegisterConversions adds conversion functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterConversions(scheme *runtime.Scheme) error { + return scheme.AddGeneratedConversionFuncs( + Convert_v1alpha1_VolumeAttachment_To_storage_VolumeAttachment, + Convert_storage_VolumeAttachment_To_v1alpha1_VolumeAttachment, + Convert_v1alpha1_VolumeAttachmentList_To_storage_VolumeAttachmentList, + Convert_storage_VolumeAttachmentList_To_v1alpha1_VolumeAttachmentList, + Convert_v1alpha1_VolumeAttachmentSource_To_storage_VolumeAttachmentSource, + Convert_storage_VolumeAttachmentSource_To_v1alpha1_VolumeAttachmentSource, + Convert_v1alpha1_VolumeAttachmentSpec_To_storage_VolumeAttachmentSpec, + Convert_storage_VolumeAttachmentSpec_To_v1alpha1_VolumeAttachmentSpec, + Convert_v1alpha1_VolumeAttachmentStatus_To_storage_VolumeAttachmentStatus, + Convert_storage_VolumeAttachmentStatus_To_v1alpha1_VolumeAttachmentStatus, + Convert_v1alpha1_VolumeError_To_storage_VolumeError, + Convert_storage_VolumeError_To_v1alpha1_VolumeError, + ) +} + +func autoConvert_v1alpha1_VolumeAttachment_To_storage_VolumeAttachment(in *v1alpha1.VolumeAttachment, out *storage.VolumeAttachment, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1alpha1_VolumeAttachmentSpec_To_storage_VolumeAttachmentSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1alpha1_VolumeAttachmentStatus_To_storage_VolumeAttachmentStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1alpha1_VolumeAttachment_To_storage_VolumeAttachment is an autogenerated conversion function. +func Convert_v1alpha1_VolumeAttachment_To_storage_VolumeAttachment(in *v1alpha1.VolumeAttachment, out *storage.VolumeAttachment, s conversion.Scope) error { + return autoConvert_v1alpha1_VolumeAttachment_To_storage_VolumeAttachment(in, out, s) +} + +func autoConvert_storage_VolumeAttachment_To_v1alpha1_VolumeAttachment(in *storage.VolumeAttachment, out *v1alpha1.VolumeAttachment, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_storage_VolumeAttachmentSpec_To_v1alpha1_VolumeAttachmentSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_storage_VolumeAttachmentStatus_To_v1alpha1_VolumeAttachmentStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_storage_VolumeAttachment_To_v1alpha1_VolumeAttachment is an autogenerated conversion function. +func Convert_storage_VolumeAttachment_To_v1alpha1_VolumeAttachment(in *storage.VolumeAttachment, out *v1alpha1.VolumeAttachment, s conversion.Scope) error { + return autoConvert_storage_VolumeAttachment_To_v1alpha1_VolumeAttachment(in, out, s) +} + +func autoConvert_v1alpha1_VolumeAttachmentList_To_storage_VolumeAttachmentList(in *v1alpha1.VolumeAttachmentList, out *storage.VolumeAttachmentList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]storage.VolumeAttachment)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1alpha1_VolumeAttachmentList_To_storage_VolumeAttachmentList is an autogenerated conversion function. +func Convert_v1alpha1_VolumeAttachmentList_To_storage_VolumeAttachmentList(in *v1alpha1.VolumeAttachmentList, out *storage.VolumeAttachmentList, s conversion.Scope) error { + return autoConvert_v1alpha1_VolumeAttachmentList_To_storage_VolumeAttachmentList(in, out, s) +} + +func autoConvert_storage_VolumeAttachmentList_To_v1alpha1_VolumeAttachmentList(in *storage.VolumeAttachmentList, out *v1alpha1.VolumeAttachmentList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]v1alpha1.VolumeAttachment)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_storage_VolumeAttachmentList_To_v1alpha1_VolumeAttachmentList is an autogenerated conversion function. +func Convert_storage_VolumeAttachmentList_To_v1alpha1_VolumeAttachmentList(in *storage.VolumeAttachmentList, out *v1alpha1.VolumeAttachmentList, s conversion.Scope) error { + return autoConvert_storage_VolumeAttachmentList_To_v1alpha1_VolumeAttachmentList(in, out, s) +} + +func autoConvert_v1alpha1_VolumeAttachmentSource_To_storage_VolumeAttachmentSource(in *v1alpha1.VolumeAttachmentSource, out *storage.VolumeAttachmentSource, s conversion.Scope) error { + out.PersistentVolumeName = (*string)(unsafe.Pointer(in.PersistentVolumeName)) + return nil +} + +// Convert_v1alpha1_VolumeAttachmentSource_To_storage_VolumeAttachmentSource is an autogenerated conversion function. +func Convert_v1alpha1_VolumeAttachmentSource_To_storage_VolumeAttachmentSource(in *v1alpha1.VolumeAttachmentSource, out *storage.VolumeAttachmentSource, s conversion.Scope) error { + return autoConvert_v1alpha1_VolumeAttachmentSource_To_storage_VolumeAttachmentSource(in, out, s) +} + +func autoConvert_storage_VolumeAttachmentSource_To_v1alpha1_VolumeAttachmentSource(in *storage.VolumeAttachmentSource, out *v1alpha1.VolumeAttachmentSource, s conversion.Scope) error { + out.PersistentVolumeName = (*string)(unsafe.Pointer(in.PersistentVolumeName)) + return nil +} + +// Convert_storage_VolumeAttachmentSource_To_v1alpha1_VolumeAttachmentSource is an autogenerated conversion function. +func Convert_storage_VolumeAttachmentSource_To_v1alpha1_VolumeAttachmentSource(in *storage.VolumeAttachmentSource, out *v1alpha1.VolumeAttachmentSource, s conversion.Scope) error { + return autoConvert_storage_VolumeAttachmentSource_To_v1alpha1_VolumeAttachmentSource(in, out, s) +} + +func autoConvert_v1alpha1_VolumeAttachmentSpec_To_storage_VolumeAttachmentSpec(in *v1alpha1.VolumeAttachmentSpec, out *storage.VolumeAttachmentSpec, s conversion.Scope) error { + out.Attacher = in.Attacher + if err := Convert_v1alpha1_VolumeAttachmentSource_To_storage_VolumeAttachmentSource(&in.Source, &out.Source, s); err != nil { + return err + } + out.NodeName = in.NodeName + return nil +} + +// Convert_v1alpha1_VolumeAttachmentSpec_To_storage_VolumeAttachmentSpec is an autogenerated conversion function. +func Convert_v1alpha1_VolumeAttachmentSpec_To_storage_VolumeAttachmentSpec(in *v1alpha1.VolumeAttachmentSpec, out *storage.VolumeAttachmentSpec, s conversion.Scope) error { + return autoConvert_v1alpha1_VolumeAttachmentSpec_To_storage_VolumeAttachmentSpec(in, out, s) +} + +func autoConvert_storage_VolumeAttachmentSpec_To_v1alpha1_VolumeAttachmentSpec(in *storage.VolumeAttachmentSpec, out *v1alpha1.VolumeAttachmentSpec, s conversion.Scope) error { + out.Attacher = in.Attacher + if err := Convert_storage_VolumeAttachmentSource_To_v1alpha1_VolumeAttachmentSource(&in.Source, &out.Source, s); err != nil { + return err + } + out.NodeName = in.NodeName + return nil +} + +// Convert_storage_VolumeAttachmentSpec_To_v1alpha1_VolumeAttachmentSpec is an autogenerated conversion function. +func Convert_storage_VolumeAttachmentSpec_To_v1alpha1_VolumeAttachmentSpec(in *storage.VolumeAttachmentSpec, out *v1alpha1.VolumeAttachmentSpec, s conversion.Scope) error { + return autoConvert_storage_VolumeAttachmentSpec_To_v1alpha1_VolumeAttachmentSpec(in, out, s) +} + +func autoConvert_v1alpha1_VolumeAttachmentStatus_To_storage_VolumeAttachmentStatus(in *v1alpha1.VolumeAttachmentStatus, out *storage.VolumeAttachmentStatus, s conversion.Scope) error { + out.Attached = in.Attached + out.AttachmentMetadata = *(*map[string]string)(unsafe.Pointer(&in.AttachmentMetadata)) + out.AttachError = (*storage.VolumeError)(unsafe.Pointer(in.AttachError)) + out.DetachError = (*storage.VolumeError)(unsafe.Pointer(in.DetachError)) + return nil +} + +// Convert_v1alpha1_VolumeAttachmentStatus_To_storage_VolumeAttachmentStatus is an autogenerated conversion function. +func Convert_v1alpha1_VolumeAttachmentStatus_To_storage_VolumeAttachmentStatus(in *v1alpha1.VolumeAttachmentStatus, out *storage.VolumeAttachmentStatus, s conversion.Scope) error { + return autoConvert_v1alpha1_VolumeAttachmentStatus_To_storage_VolumeAttachmentStatus(in, out, s) +} + +func autoConvert_storage_VolumeAttachmentStatus_To_v1alpha1_VolumeAttachmentStatus(in *storage.VolumeAttachmentStatus, out *v1alpha1.VolumeAttachmentStatus, s conversion.Scope) error { + out.Attached = in.Attached + out.AttachmentMetadata = *(*map[string]string)(unsafe.Pointer(&in.AttachmentMetadata)) + out.AttachError = (*v1alpha1.VolumeError)(unsafe.Pointer(in.AttachError)) + out.DetachError = (*v1alpha1.VolumeError)(unsafe.Pointer(in.DetachError)) + return nil +} + +// Convert_storage_VolumeAttachmentStatus_To_v1alpha1_VolumeAttachmentStatus is an autogenerated conversion function. +func Convert_storage_VolumeAttachmentStatus_To_v1alpha1_VolumeAttachmentStatus(in *storage.VolumeAttachmentStatus, out *v1alpha1.VolumeAttachmentStatus, s conversion.Scope) error { + return autoConvert_storage_VolumeAttachmentStatus_To_v1alpha1_VolumeAttachmentStatus(in, out, s) +} + +func autoConvert_v1alpha1_VolumeError_To_storage_VolumeError(in *v1alpha1.VolumeError, out *storage.VolumeError, s conversion.Scope) error { + out.Time = in.Time + out.Message = in.Message + return nil +} + +// Convert_v1alpha1_VolumeError_To_storage_VolumeError is an autogenerated conversion function. +func Convert_v1alpha1_VolumeError_To_storage_VolumeError(in *v1alpha1.VolumeError, out *storage.VolumeError, s conversion.Scope) error { + return autoConvert_v1alpha1_VolumeError_To_storage_VolumeError(in, out, s) +} + +func autoConvert_storage_VolumeError_To_v1alpha1_VolumeError(in *storage.VolumeError, out *v1alpha1.VolumeError, s conversion.Scope) error { + out.Time = in.Time + out.Message = in.Message + return nil +} + +// Convert_storage_VolumeError_To_v1alpha1_VolumeError is an autogenerated conversion function. +func Convert_storage_VolumeError_To_v1alpha1_VolumeError(in *storage.VolumeError, out *v1alpha1.VolumeError, s conversion.Scope) error { + return autoConvert_storage_VolumeError_To_v1alpha1_VolumeError(in, out, s) +} diff --git a/pkg/apis/storage/v1alpha1/zz_generated.defaults.go b/pkg/apis/storage/v1alpha1/zz_generated.defaults.go new file mode 100644 index 00000000000..5e24d22cacd --- /dev/null +++ b/pkg/apis/storage/v1alpha1/zz_generated.defaults.go @@ -0,0 +1,32 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by defaulter-gen. Do not edit it manually! + +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/pkg/apis/storage/v1beta1/BUILD b/pkg/apis/storage/v1beta1/BUILD index 247f4ff047a..368302812eb 100644 --- a/pkg/apis/storage/v1beta1/BUILD +++ b/pkg/apis/storage/v1beta1/BUILD @@ -3,6 +3,7 @@ package(default_visibility = ["//visibility:public"]) load( "@io_bazel_rules_go//go:def.bzl", "go_library", + "go_test", ) go_library( @@ -16,13 +17,15 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/apis/storage/v1beta1", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/storage:go_default_library", + "//pkg/features:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/storage/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", ], ) @@ -41,3 +44,16 @@ filegroup( ], tags = ["automanaged"], ) + +go_test( + name = "go_default_xtest", + srcs = ["defaults_test.go"], + importpath = "k8s.io/kubernetes/pkg/apis/storage/v1beta1_test", + deps = [ + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/storage/install:go_default_library", + "//vendor/k8s.io/api/storage/v1beta1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + ], +) diff --git a/pkg/apis/storage/v1beta1/defaults.go b/pkg/apis/storage/v1beta1/defaults.go index e50599bf273..97dbff2f378 100644 --- a/pkg/apis/storage/v1beta1/defaults.go +++ b/pkg/apis/storage/v1beta1/defaults.go @@ -20,6 +20,8 @@ import ( "k8s.io/api/core/v1" storagev1beta1 "k8s.io/api/storage/v1beta1" "k8s.io/apimachinery/pkg/runtime" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/features" ) func addDefaultingFuncs(scheme *runtime.Scheme) error { @@ -31,4 +33,9 @@ func SetDefaults_StorageClass(obj *storagev1beta1.StorageClass) { obj.ReclaimPolicy = new(v1.PersistentVolumeReclaimPolicy) *obj.ReclaimPolicy = v1.PersistentVolumeReclaimDelete } + + if obj.VolumeBindingMode == nil && utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) { + obj.VolumeBindingMode = new(storagev1beta1.VolumeBindingMode) + *obj.VolumeBindingMode = storagev1beta1.VolumeBindingImmediate + } } diff --git a/pkg/apis/storage/v1beta1/defaults_test.go b/pkg/apis/storage/v1beta1/defaults_test.go new file mode 100644 index 00000000000..34aff74b8e0 --- /dev/null +++ b/pkg/apis/storage/v1beta1/defaults_test.go @@ -0,0 +1,81 @@ +/* +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 v1beta1_test + +import ( + "reflect" + "testing" + + storagev1beta1 "k8s.io/api/storage/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/api/legacyscheme" + _ "k8s.io/kubernetes/pkg/apis/storage/install" +) + +func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { + codec := legacyscheme.Codecs.LegacyCodec(storagev1beta1.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 TestSetDefaultVolumeBindingMode(t *testing.T) { + class := &storagev1beta1.StorageClass{} + + // When feature gate is disabled, field should not be defaulted + output := roundTrip(t, runtime.Object(class)).(*storagev1beta1.StorageClass) + if output.VolumeBindingMode != nil { + t.Errorf("Expected VolumeBindingMode to not be defaulted, got: %+v", output.VolumeBindingMode) + } + + class = &storagev1beta1.StorageClass{} + + err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true") + if err != nil { + t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err) + } + + // When feature gate is enabled, field should be defaulted + defaultMode := storagev1beta1.VolumeBindingImmediate + output = roundTrip(t, runtime.Object(class)).(*storagev1beta1.StorageClass) + outMode := output.VolumeBindingMode + if outMode == nil { + t.Errorf("Expected VolumeBindingMode to be defaulted to: %+v, got: nil", defaultMode) + } else if *outMode != defaultMode { + t.Errorf("Expected VolumeBindingMode to be defaulted to: %+v, got: %+v", defaultMode, outMode) + } + + err = utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false") + if err != nil { + t.Fatalf("Failed to disable feature gate for VolumeScheduling: %v", err) + } +} diff --git a/pkg/apis/storage/v1beta1/doc.go b/pkg/apis/storage/v1beta1/doc.go index b47a5044921..a5b0ca68ab8 100644 --- a/pkg/apis/storage/v1beta1/doc.go +++ b/pkg/apis/storage/v1beta1/doc.go @@ -15,7 +15,7 @@ limitations under the License. */ // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/storage -// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/storage/v1beta1 +// +k8s:conversion-gen-external-types=k8s.io/api/storage/v1beta1 // +groupName=storage.k8s.io // +k8s:defaulter-gen=TypeMeta // +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/storage/v1beta1 diff --git a/pkg/apis/storage/v1beta1/zz_generated.conversion.go b/pkg/apis/storage/v1beta1/zz_generated.conversion.go index d340530abbc..5b72dda9f7a 100644 --- a/pkg/apis/storage/v1beta1/zz_generated.conversion.go +++ b/pkg/apis/storage/v1beta1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,13 +21,14 @@ limitations under the License. package v1beta1 import ( + unsafe "unsafe" + v1 "k8s.io/api/core/v1" v1beta1 "k8s.io/api/storage/v1beta1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" storage "k8s.io/kubernetes/pkg/apis/storage" - unsafe "unsafe" ) func init() { @@ -49,9 +50,10 @@ func autoConvert_v1beta1_StorageClass_To_storage_StorageClass(in *v1beta1.Storag out.ObjectMeta = in.ObjectMeta out.Provisioner = in.Provisioner out.Parameters = *(*map[string]string)(unsafe.Pointer(&in.Parameters)) - out.ReclaimPolicy = (*api.PersistentVolumeReclaimPolicy)(unsafe.Pointer(in.ReclaimPolicy)) + out.ReclaimPolicy = (*core.PersistentVolumeReclaimPolicy)(unsafe.Pointer(in.ReclaimPolicy)) out.MountOptions = *(*[]string)(unsafe.Pointer(&in.MountOptions)) out.AllowVolumeExpansion = (*bool)(unsafe.Pointer(in.AllowVolumeExpansion)) + out.VolumeBindingMode = (*storage.VolumeBindingMode)(unsafe.Pointer(in.VolumeBindingMode)) return nil } @@ -67,6 +69,7 @@ func autoConvert_storage_StorageClass_To_v1beta1_StorageClass(in *storage.Storag out.ReclaimPolicy = (*v1.PersistentVolumeReclaimPolicy)(unsafe.Pointer(in.ReclaimPolicy)) out.MountOptions = *(*[]string)(unsafe.Pointer(&in.MountOptions)) out.AllowVolumeExpansion = (*bool)(unsafe.Pointer(in.AllowVolumeExpansion)) + out.VolumeBindingMode = (*v1beta1.VolumeBindingMode)(unsafe.Pointer(in.VolumeBindingMode)) return nil } diff --git a/pkg/apis/storage/v1beta1/zz_generated.defaults.go b/pkg/apis/storage/v1beta1/zz_generated.defaults.go index 1200af6b071..07847aebdfd 100644 --- a/pkg/apis/storage/v1beta1/zz_generated.defaults.go +++ b/pkg/apis/storage/v1beta1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/apis/storage/validation/BUILD b/pkg/apis/storage/validation/BUILD index c637aa9c591..b345c86a906 100644 --- a/pkg/apis/storage/validation/BUILD +++ b/pkg/apis/storage/validation/BUILD @@ -11,10 +11,11 @@ go_library( srcs = ["validation.go"], importpath = "k8s.io/kubernetes/pkg/apis/storage/validation", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//pkg/apis/storage:go_default_library", "//pkg/features:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", @@ -25,10 +26,10 @@ go_library( go_test( name = "go_default_test", srcs = ["validation_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/apis/storage/validation", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/storage:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", diff --git a/pkg/apis/storage/validation/validation.go b/pkg/apis/storage/validation/validation.go index 38b7915167e..e9f66e938e8 100644 --- a/pkg/apis/storage/validation/validation.go +++ b/pkg/apis/storage/validation/validation.go @@ -20,16 +20,25 @@ import ( "reflect" "strings" + apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/kubernetes/pkg/api" - apivalidation "k8s.io/kubernetes/pkg/api/validation" + api "k8s.io/kubernetes/pkg/apis/core" + apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" "k8s.io/kubernetes/pkg/apis/storage" "k8s.io/kubernetes/pkg/features" ) +const ( + maxProvisionerParameterSize = 256 * (1 << 10) // 256 kB + maxProvisionerParameterLen = 512 + + maxAttachedVolumeMetadataSize = 256 * (1 << 10) // 256 kB + maxVolumeErrorMessageSize = 1024 +) + // ValidateStorageClass validates a StorageClass. func ValidateStorageClass(storageClass *storage.StorageClass) field.ErrorList { allErrs := apivalidation.ValidateObjectMeta(&storageClass.ObjectMeta, false, apivalidation.ValidateClassName, field.NewPath("metadata")) @@ -37,6 +46,7 @@ func ValidateStorageClass(storageClass *storage.StorageClass) field.ErrorList { allErrs = append(allErrs, validateParameters(storageClass.Parameters, field.NewPath("parameters"))...) allErrs = append(allErrs, validateReclaimPolicy(storageClass.ReclaimPolicy, field.NewPath("reclaimPolicy"))...) allErrs = append(allErrs, validateAllowVolumeExpansion(storageClass.AllowVolumeExpansion, field.NewPath("allowVolumeExpansion"))...) + allErrs = append(allErrs, validateVolumeBindingMode(storageClass.VolumeBindingMode, field.NewPath("volumeBindingMode"))...) return allErrs } @@ -55,6 +65,8 @@ func ValidateStorageClassUpdate(storageClass, oldStorageClass *storage.StorageCl if *storageClass.ReclaimPolicy != *oldStorageClass.ReclaimPolicy { allErrs = append(allErrs, field.Forbidden(field.NewPath("reclaimPolicy"), "updates to reclaimPolicy are forbidden.")) } + + allErrs = append(allErrs, apivalidation.ValidateImmutableField(storageClass.VolumeBindingMode, oldStorageClass.VolumeBindingMode, field.NewPath("volumeBindingMode"))...) return allErrs } @@ -72,9 +84,6 @@ func validateProvisioner(provisioner string, fldPath *field.Path) field.ErrorLis return allErrs } -const maxProvisionerParameterSize = 256 * (1 << 10) // 256 kB -const maxProvisionerParameterLen = 512 - // validateParameters tests that keys are qualified names and that provisionerParameter are < 256kB. func validateParameters(params map[string]string, fldPath *field.Path) field.ErrorList { var totalSize int64 @@ -121,3 +130,112 @@ func validateAllowVolumeExpansion(allowExpand *bool, fldPath *field.Path) field. } return allErrs } + +// ValidateVolumeAttachment validates a VolumeAttachment. +func ValidateVolumeAttachment(volumeAttachment *storage.VolumeAttachment) field.ErrorList { + 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 +} + +// ValidateVolumeAttachmentSpec tests that the specified VolumeAttachmentSpec +// 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 = 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{} + if source.PersistentVolumeName == nil || len(*source.PersistentVolumeName) == 0 { + allErrs = append(allErrs, field.Required(fldPath, "")) + } + return allErrs +} + +// validateNodeName tests if the nodeName is valid for VolumeAttachment. +func validateNodeName(nodeName string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for _, msg := range apivalidation.ValidateNodeName(nodeName, false /* prefix */) { + allErrs = append(allErrs, field.Invalid(fldPath, nodeName, msg)) + } + return allErrs +} + +// validaVolumeAttachmentStatus tests if volumeAttachmentStatus is valid. +func validateVolumeAttachmentStatus(status *storage.VolumeAttachmentStatus, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, validateAttachmentMetadata(status.AttachmentMetadata, fldPath.Child("attachmentMetadata"))...) + allErrs = append(allErrs, validateVolumeError(status.AttachError, fldPath.Child("attachError"))...) + allErrs = append(allErrs, validateVolumeError(status.DetachError, fldPath.Child("detachError"))...) + return allErrs +} + +func validateAttachmentMetadata(metadata map[string]string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + var size int64 + for k, v := range metadata { + size += (int64)(len(k)) + (int64)(len(v)) + } + if size > maxAttachedVolumeMetadataSize { + allErrs = append(allErrs, field.TooLong(fldPath, metadata, maxAttachedVolumeMetadataSize)) + } + return allErrs +} + +func validateVolumeError(e *storage.VolumeError, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if e == nil { + return allErrs + } + if len(e.Message) > maxVolumeErrorMessageSize { + allErrs = append(allErrs, field.TooLong(fldPath.Child("message"), e.Message, maxAttachedVolumeMetadataSize)) + } + return allErrs +} + +// ValidateVolumeAttachmentUpdate validates a VolumeAttachment. +func ValidateVolumeAttachmentUpdate(new, old *storage.VolumeAttachment) field.ErrorList { + allErrs := ValidateVolumeAttachment(new) + + // Spec is read-only + if !apiequality.Semantic.DeepEqual(old.Spec, new.Spec) { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec"), new.Spec, "field is immutable")) + } + return allErrs +} + +var supportedVolumeBindingModes = sets.NewString(string(storage.VolumeBindingImmediate), string(storage.VolumeBindingWaitForFirstConsumer)) + +// validateVolumeBindingMode tests that VolumeBindingMode specifies valid values. +func validateVolumeBindingMode(mode *storage.VolumeBindingMode, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) { + if mode == nil { + allErrs = append(allErrs, field.Required(fldPath, "")) + } else if !supportedVolumeBindingModes.Has(string(*mode)) { + allErrs = append(allErrs, field.NotSupported(fldPath, mode, supportedVolumeBindingModes.List())) + } + } else if mode != nil { + allErrs = append(allErrs, field.Forbidden(fldPath, "field is disabled by feature-gate VolumeScheduling")) + } + + return allErrs +} diff --git a/pkg/apis/storage/validation/validation_test.go b/pkg/apis/storage/validation/validation_test.go index 8e8e9f499a2..2cb0a61a414 100644 --- a/pkg/apis/storage/validation/validation_test.go +++ b/pkg/apis/storage/validation/validation_test.go @@ -18,14 +18,23 @@ package validation import ( "fmt" + "strings" "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/storage" ) +var ( + deleteReclaimPolicy = api.PersistentVolumeReclaimDelete + immediateMode1 = storage.VolumeBindingImmediate + immediateMode2 = storage.VolumeBindingImmediate + waitingMode = storage.VolumeBindingWaitForFirstConsumer + invalidMode = storage.VolumeBindingMode("foo") +) + func TestValidateStorageClass(t *testing.T) { deleteReclaimPolicy := api.PersistentVolumeReclaimPolicy("Delete") retainReclaimPolicy := api.PersistentVolumeReclaimPolicy("Retain") @@ -157,3 +166,419 @@ func TestAlphaExpandPersistentVolumesFeatureValidation(t *testing.T) { } } + +func TestVolumeAttachmentValidation(t *testing.T) { + volumeName := "pv-name" + empty := "" + successCases := []storage.VolumeAttachment{ + { + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &volumeName, + }, + NodeName: "mynode", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "foo-with-status"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &volumeName, + }, + NodeName: "mynode", + }, + Status: storage.VolumeAttachmentStatus{ + Attached: true, + AttachmentMetadata: map[string]string{ + "foo": "bar", + }, + AttachError: &storage.VolumeError{ + Time: metav1.Time{}, + Message: "hello world", + }, + DetachError: &storage.VolumeError{ + Time: metav1.Time{}, + Message: "hello world", + }, + }, + }, + } + + for _, volumeAttachment := range successCases { + if errs := ValidateVolumeAttachment(&volumeAttachment); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + errorCases := []storage.VolumeAttachment{ + { + // Empty attacher name + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "", + NodeName: "mynode", + }, + }, + { + // Invalid attacher name + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "invalid!@#$%^&*()", + NodeName: "mynode", + }, + }, + { + // Empty node name + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + NodeName: "", + }, + }, + { + // No volume name + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + NodeName: "node", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: nil, + }, + }, + }, + { + // Empty volume name + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + NodeName: "node", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &empty, + }, + }, + }, + { + // Too long error message + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + NodeName: "node", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &volumeName, + }, + }, + Status: storage.VolumeAttachmentStatus{ + Attached: true, + AttachmentMetadata: map[string]string{ + "foo": "bar", + }, + AttachError: &storage.VolumeError{ + Time: metav1.Time{}, + Message: "hello world", + }, + DetachError: &storage.VolumeError{ + Time: metav1.Time{}, + Message: strings.Repeat("a", maxVolumeErrorMessageSize+1), + }, + }, + }, + { + // Too long metadata + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + NodeName: "node", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &volumeName, + }, + }, + Status: storage.VolumeAttachmentStatus{ + Attached: true, + AttachmentMetadata: map[string]string{ + "foo": strings.Repeat("a", maxAttachedVolumeMetadataSize), + }, + AttachError: &storage.VolumeError{ + Time: metav1.Time{}, + Message: "hello world", + }, + DetachError: &storage.VolumeError{ + Time: metav1.Time{}, + Message: "hello world", + }, + }, + }, + } + + for _, volumeAttachment := range errorCases { + if errs := ValidateVolumeAttachment(&volumeAttachment); len(errs) == 0 { + t.Errorf("Expected failure for test: %v", volumeAttachment) + } + } +} + +func TestVolumeAttachmentUpdateValidation(t *testing.T) { + volumeName := "foo" + newVolumeName := "bar" + + old := storage.VolumeAttachment{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &volumeName, + }, + NodeName: "mynode", + }, + } + successCases := []storage.VolumeAttachment{ + { + // no change + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &volumeName, + }, + NodeName: "mynode", + }, + }, + { + // modify status + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &volumeName, + }, + NodeName: "mynode", + }, + Status: storage.VolumeAttachmentStatus{ + Attached: true, + AttachmentMetadata: map[string]string{ + "foo": "bar", + }, + AttachError: &storage.VolumeError{ + Time: metav1.Time{}, + Message: "hello world", + }, + DetachError: &storage.VolumeError{ + Time: metav1.Time{}, + Message: "hello world", + }, + }, + }, + } + + for _, volumeAttachment := range successCases { + if errs := ValidateVolumeAttachmentUpdate(&volumeAttachment, &old); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + + errorCases := []storage.VolumeAttachment{ + { + // change attacher + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "another-attacher", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &volumeName, + }, + NodeName: "mynode", + }, + }, + { + // change volume + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &newVolumeName, + }, + NodeName: "mynode", + }, + }, + { + // change node + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &volumeName, + }, + NodeName: "anothernode", + }, + }, + { + // add invalid status + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "myattacher", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &volumeName, + }, + NodeName: "mynode", + }, + Status: storage.VolumeAttachmentStatus{ + Attached: true, + AttachmentMetadata: map[string]string{ + "foo": "bar", + }, + AttachError: &storage.VolumeError{ + Time: metav1.Time{}, + Message: strings.Repeat("a", maxAttachedVolumeMetadataSize), + }, + DetachError: &storage.VolumeError{ + Time: metav1.Time{}, + Message: "hello world", + }, + }, + }, + } + + for _, volumeAttachment := range errorCases { + if errs := ValidateVolumeAttachmentUpdate(&volumeAttachment, &old); len(errs) == 0 { + t.Errorf("Expected failure for test: %v", volumeAttachment) + } + } +} + +func makeClassWithBinding(mode *storage.VolumeBindingMode) *storage.StorageClass { + return &storage.StorageClass{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "foo"}, + Provisioner: "kubernetes.io/foo-provisioner", + ReclaimPolicy: &deleteReclaimPolicy, + VolumeBindingMode: mode, + } +} + +// TODO: Remove these tests once feature gate is not required +func TestValidateVolumeBindingModeAlphaDisabled(t *testing.T) { + errorCases := map[string]*storage.StorageClass{ + "immediate mode": makeClassWithBinding(&immediateMode1), + "waiting mode": makeClassWithBinding(&waitingMode), + "invalid mode": makeClassWithBinding(&invalidMode), + } + + for testName, storageClass := range errorCases { + if errs := ValidateStorageClass(storageClass); len(errs) == 0 { + t.Errorf("Expected failure for test: %v", testName) + } + } +} + +type bindingTest struct { + class *storage.StorageClass + shouldSucceed bool +} + +func TestValidateVolumeBindingMode(t *testing.T) { + cases := map[string]bindingTest{ + "no mode": { + class: makeClassWithBinding(nil), + shouldSucceed: false, + }, + "immediate mode": { + class: makeClassWithBinding(&immediateMode1), + shouldSucceed: true, + }, + "waiting mode": { + class: makeClassWithBinding(&waitingMode), + shouldSucceed: true, + }, + "invalid mode": { + class: makeClassWithBinding(&invalidMode), + shouldSucceed: false, + }, + } + + // TODO: remove when feature gate not required + err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true") + if err != nil { + t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err) + } + + for testName, testCase := range cases { + errs := ValidateStorageClass(testCase.class) + if testCase.shouldSucceed && len(errs) != 0 { + t.Errorf("Expected success for test %q, got %v", testName, errs) + } + if !testCase.shouldSucceed && len(errs) == 0 { + t.Errorf("Expected failure for test %q, got success", testName) + } + } + + err = utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false") + if err != nil { + t.Fatalf("Failed to disable feature gate for VolumeScheduling: %v", err) + } +} + +type updateTest struct { + oldClass *storage.StorageClass + newClass *storage.StorageClass + shouldSucceed bool +} + +func TestValidateUpdateVolumeBindingMode(t *testing.T) { + noBinding := makeClassWithBinding(nil) + immediateBinding1 := makeClassWithBinding(&immediateMode1) + immediateBinding2 := makeClassWithBinding(&immediateMode2) + waitBinding := makeClassWithBinding(&waitingMode) + + cases := map[string]updateTest{ + "old and new no mode": { + oldClass: noBinding, + newClass: noBinding, + shouldSucceed: true, + }, + "old and new same mode ptr": { + oldClass: immediateBinding1, + newClass: immediateBinding1, + shouldSucceed: true, + }, + "old and new same mode value": { + oldClass: immediateBinding1, + newClass: immediateBinding2, + shouldSucceed: true, + }, + "old no mode, new mode": { + oldClass: noBinding, + newClass: waitBinding, + shouldSucceed: false, + }, + "old mode, new no mode": { + oldClass: waitBinding, + newClass: noBinding, + shouldSucceed: false, + }, + "old and new different modes": { + oldClass: waitBinding, + newClass: immediateBinding1, + shouldSucceed: false, + }, + } + + // TODO: remove when feature gate not required + err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true") + if err != nil { + t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err) + } + + for testName, testCase := range cases { + errs := ValidateStorageClassUpdate(testCase.newClass, testCase.oldClass) + if testCase.shouldSucceed && len(errs) != 0 { + t.Errorf("Expected success for %v, got %v", testName, errs) + } + if !testCase.shouldSucceed && len(errs) == 0 { + t.Errorf("Expected failure for %v, got success", testName) + } + } + + err = utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false") + if err != nil { + t.Fatalf("Failed to disable feature gate for VolumeScheduling: %v", err) + } +} diff --git a/pkg/apis/storage/zz_generated.deepcopy.go b/pkg/apis/storage/zz_generated.deepcopy.go index b6564ce03ad..61ced04a4c4 100644 --- a/pkg/apis/storage/zz_generated.deepcopy.go +++ b/pkg/apis/storage/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,33 +21,10 @@ limitations under the License. package storage import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - api "k8s.io/kubernetes/pkg/api" - reflect "reflect" + core "k8s.io/kubernetes/pkg/apis/core" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StorageClass).DeepCopyInto(out.(*StorageClass)) - return nil - }, InType: reflect.TypeOf(&StorageClass{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StorageClassList).DeepCopyInto(out.(*StorageClassList)) - return nil - }, InType: reflect.TypeOf(&StorageClassList{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *StorageClass) DeepCopyInto(out *StorageClass) { *out = *in @@ -65,7 +42,7 @@ func (in *StorageClass) DeepCopyInto(out *StorageClass) { if *in == nil { *out = nil } else { - *out = new(api.PersistentVolumeReclaimPolicy) + *out = new(core.PersistentVolumeReclaimPolicy) **out = **in } } @@ -83,6 +60,15 @@ func (in *StorageClass) DeepCopyInto(out *StorageClass) { **out = **in } } + if in.VolumeBindingMode != nil { + in, out := &in.VolumeBindingMode, &out.VolumeBindingMode + if *in == nil { + *out = nil + } else { + *out = new(VolumeBindingMode) + **out = **in + } + } return } @@ -138,3 +124,166 @@ func (in *StorageClassList) DeepCopyObject() runtime.Object { return nil } } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VolumeAttachment) DeepCopyInto(out *VolumeAttachment) { + *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 VolumeAttachment. +func (in *VolumeAttachment) DeepCopy() *VolumeAttachment { + if in == nil { + return nil + } + out := new(VolumeAttachment) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VolumeAttachment) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VolumeAttachmentList) DeepCopyInto(out *VolumeAttachmentList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]VolumeAttachment, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeAttachmentList. +func (in *VolumeAttachmentList) DeepCopy() *VolumeAttachmentList { + if in == nil { + return nil + } + out := new(VolumeAttachmentList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VolumeAttachmentList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VolumeAttachmentSource) DeepCopyInto(out *VolumeAttachmentSource) { + *out = *in + if in.PersistentVolumeName != nil { + in, out := &in.PersistentVolumeName, &out.PersistentVolumeName + if *in == nil { + *out = nil + } else { + *out = new(string) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeAttachmentSource. +func (in *VolumeAttachmentSource) DeepCopy() *VolumeAttachmentSource { + if in == nil { + return nil + } + out := new(VolumeAttachmentSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VolumeAttachmentSpec) DeepCopyInto(out *VolumeAttachmentSpec) { + *out = *in + in.Source.DeepCopyInto(&out.Source) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeAttachmentSpec. +func (in *VolumeAttachmentSpec) DeepCopy() *VolumeAttachmentSpec { + if in == nil { + return nil + } + out := new(VolumeAttachmentSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VolumeAttachmentStatus) DeepCopyInto(out *VolumeAttachmentStatus) { + *out = *in + if in.AttachmentMetadata != nil { + in, out := &in.AttachmentMetadata, &out.AttachmentMetadata + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.AttachError != nil { + in, out := &in.AttachError, &out.AttachError + if *in == nil { + *out = nil + } else { + *out = new(VolumeError) + (*in).DeepCopyInto(*out) + } + } + if in.DetachError != nil { + in, out := &in.DetachError, &out.DetachError + if *in == nil { + *out = nil + } else { + *out = new(VolumeError) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeAttachmentStatus. +func (in *VolumeAttachmentStatus) DeepCopy() *VolumeAttachmentStatus { + if in == nil { + return nil + } + out := new(VolumeAttachmentStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VolumeError) DeepCopyInto(out *VolumeError) { + *out = *in + in.Time.DeepCopyInto(&out.Time) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeError. +func (in *VolumeError) DeepCopy() *VolumeError { + if in == nil { + return nil + } + out := new(VolumeError) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/auth/authenticator/OWNERS b/pkg/auth/authenticator/OWNERS deleted file mode 100755 index 94487992079..00000000000 --- a/pkg/auth/authenticator/OWNERS +++ /dev/null @@ -1 +0,0 @@ -reviewers: diff --git a/pkg/auth/authorizer/abac/BUILD b/pkg/auth/authorizer/abac/BUILD index efbea8e2568..f61659c62ac 100644 --- a/pkg/auth/authorizer/abac/BUILD +++ b/pkg/auth/authorizer/abac/BUILD @@ -35,8 +35,8 @@ go_test( data = [ ":example_policy", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/auth/authorizer/abac", - library = ":go_default_library", deps = [ "//pkg/apis/abac:go_default_library", "//pkg/apis/abac/v0:go_default_library", diff --git a/pkg/auth/authorizer/abac/abac.go b/pkg/auth/authorizer/abac/abac.go index 5e56c19ba9c..f3132740d2d 100644 --- a/pkg/auth/authorizer/abac/abac.go +++ b/pkg/auth/authorizer/abac/abac.go @@ -30,7 +30,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" - api "k8s.io/kubernetes/pkg/apis/abac" + "k8s.io/kubernetes/pkg/apis/abac" _ "k8s.io/kubernetes/pkg/apis/abac/latest" "k8s.io/kubernetes/pkg/apis/abac/v0" ) @@ -49,7 +49,7 @@ func (p policyLoadError) Error() string { return fmt.Sprintf("error reading policy file %s: %v", p.path, p.err) } -type policyList []*api.Policy +type policyList []*abac.Policy // TODO: Have policies be created via an API call and stored in REST storage. func NewFromFile(path string) (policyList, error) { @@ -64,13 +64,13 @@ func NewFromFile(path string) (policyList, error) { scanner := bufio.NewScanner(file) pl := make(policyList, 0) - decoder := api.Codecs.UniversalDecoder() + decoder := abac.Codecs.UniversalDecoder() i := 0 unversionedLines := 0 for scanner.Scan() { i++ - p := &api.Policy{} + p := &abac.Policy{} b := scanner.Bytes() // skip comment lines and blank lines @@ -90,14 +90,14 @@ func NewFromFile(path string) (policyList, error) { if err := runtime.DecodeInto(decoder, b, oldPolicy); err != nil { return nil, policyLoadError{path, i, b, err} } - if err := api.Scheme.Convert(oldPolicy, p, nil); err != nil { + if err := abac.Scheme.Convert(oldPolicy, p, nil); err != nil { return nil, policyLoadError{path, i, b, err} } pl = append(pl, p) continue } - decodedPolicy, ok := decodedObj.(*api.Policy) + decodedPolicy, ok := decodedObj.(*abac.Policy) if !ok { return nil, policyLoadError{path, i, b, fmt.Errorf("unrecognized object: %#v", decodedObj)} } @@ -114,7 +114,7 @@ func NewFromFile(path string) (policyList, error) { return pl, nil } -func matches(p api.Policy, a authorizer.Attributes) bool { +func matches(p abac.Policy, a authorizer.Attributes) bool { if subjectMatches(p, a.GetUser()) { if verbMatches(p, a) { // Resource and non-resource requests are mutually exclusive, at most one will match a policy @@ -130,7 +130,7 @@ func matches(p api.Policy, a authorizer.Attributes) bool { } // subjectMatches returns true if specified user and group properties in the policy match the attributes -func subjectMatches(p api.Policy, user user.Info) bool { +func subjectMatches(p abac.Policy, user user.Info) bool { matched := false if user == nil { @@ -171,7 +171,7 @@ func subjectMatches(p api.Policy, user user.Info) bool { return matched } -func verbMatches(p api.Policy, a authorizer.Attributes) bool { +func verbMatches(p abac.Policy, a authorizer.Attributes) bool { // TODO: match on verb // All policies allow read only requests @@ -187,7 +187,7 @@ func verbMatches(p api.Policy, a authorizer.Attributes) bool { return false } -func nonResourceMatches(p api.Policy, a authorizer.Attributes) bool { +func nonResourceMatches(p abac.Policy, a authorizer.Attributes) bool { // A non-resource policy cannot match a resource request if !a.IsResourceRequest() { // Allow wildcard match @@ -206,7 +206,7 @@ func nonResourceMatches(p api.Policy, a authorizer.Attributes) bool { return false } -func resourceMatches(p api.Policy, a authorizer.Attributes) bool { +func resourceMatches(p abac.Policy, a authorizer.Attributes) bool { // A resource policy cannot match a non-resource request if a.IsResourceRequest() { if p.Spec.Namespace == "*" || p.Spec.Namespace == a.GetNamespace() { @@ -221,13 +221,13 @@ func resourceMatches(p api.Policy, a authorizer.Attributes) bool { } // Authorizer implements authorizer.Authorize -func (pl policyList) Authorize(a authorizer.Attributes) (bool, string, error) { +func (pl policyList) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) { for _, p := range pl { if matches(*p, a) { - return true, "", nil + return authorizer.DecisionAllow, "", nil } } - return false, "No policy matched.", nil + return authorizer.DecisionNoOpinion, "No policy matched.", nil // TODO: Benchmark how much time policy matching takes with a medium size // policy file, compared to other steps such as encoding/decoding. // Then, add Caching only if needed. diff --git a/pkg/auth/authorizer/abac/abac_test.go b/pkg/auth/authorizer/abac/abac_test.go index b8df86bb806..6d732399201 100644 --- a/pkg/auth/authorizer/abac/abac_test.go +++ b/pkg/auth/authorizer/abac/abac_test.go @@ -25,7 +25,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" - api "k8s.io/kubernetes/pkg/apis/abac" + "k8s.io/kubernetes/pkg/apis/abac" "k8s.io/kubernetes/pkg/apis/abac/v0" "k8s.io/kubernetes/pkg/apis/abac/v1beta1" ) @@ -81,46 +81,46 @@ func TestAuthorizeV0(t *testing.T) { uChuck := user.DefaultInfo{Name: "chuck", UID: "uid5", Groups: authenticatedGroup} testCases := []struct { - User user.DefaultInfo - Verb string - Resource string - NS string - APIGroup string - Path string - ExpectAllow bool + User user.DefaultInfo + Verb string + Resource string + NS string + APIGroup string + Path string + ExpectDecision authorizer.Decision }{ // Scheduler can read pods - {User: uScheduler, Verb: "list", Resource: "pods", NS: "ns1", ExpectAllow: true}, - {User: uScheduler, Verb: "list", Resource: "pods", NS: "", ExpectAllow: true}, + {User: uScheduler, Verb: "list", Resource: "pods", NS: "ns1", ExpectDecision: authorizer.DecisionAllow}, + {User: uScheduler, Verb: "list", Resource: "pods", NS: "", ExpectDecision: authorizer.DecisionAllow}, // Scheduler cannot write pods - {User: uScheduler, Verb: "create", Resource: "pods", NS: "ns1", ExpectAllow: false}, - {User: uScheduler, Verb: "create", Resource: "pods", NS: "", ExpectAllow: false}, + {User: uScheduler, Verb: "create", Resource: "pods", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion}, + {User: uScheduler, Verb: "create", Resource: "pods", NS: "", ExpectDecision: authorizer.DecisionNoOpinion}, // Scheduler can write bindings - {User: uScheduler, Verb: "get", Resource: "bindings", NS: "ns1", ExpectAllow: true}, - {User: uScheduler, Verb: "get", Resource: "bindings", NS: "", ExpectAllow: true}, + {User: uScheduler, Verb: "get", Resource: "bindings", NS: "ns1", ExpectDecision: authorizer.DecisionAllow}, + {User: uScheduler, Verb: "get", Resource: "bindings", NS: "", ExpectDecision: authorizer.DecisionAllow}, // Alice can read and write anything in the right namespace. - {User: uAlice, Verb: "get", Resource: "pods", NS: "projectCaribou", ExpectAllow: true}, - {User: uAlice, Verb: "get", Resource: "widgets", NS: "projectCaribou", ExpectAllow: true}, - {User: uAlice, Verb: "get", Resource: "", NS: "projectCaribou", ExpectAllow: true}, - {User: uAlice, Verb: "update", Resource: "pods", NS: "projectCaribou", ExpectAllow: true}, - {User: uAlice, Verb: "update", Resource: "widgets", NS: "projectCaribou", ExpectAllow: true}, - {User: uAlice, Verb: "update", Resource: "", NS: "projectCaribou", ExpectAllow: true}, - {User: uAlice, Verb: "update", Resource: "foo", NS: "projectCaribou", APIGroup: "bar", ExpectAllow: true}, + {User: uAlice, Verb: "get", Resource: "pods", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow}, + {User: uAlice, Verb: "get", Resource: "widgets", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow}, + {User: uAlice, Verb: "get", Resource: "", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow}, + {User: uAlice, Verb: "update", Resource: "pods", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow}, + {User: uAlice, Verb: "update", Resource: "widgets", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow}, + {User: uAlice, Verb: "update", Resource: "", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow}, + {User: uAlice, Verb: "update", Resource: "foo", NS: "projectCaribou", APIGroup: "bar", ExpectDecision: authorizer.DecisionAllow}, // .. but not the wrong namespace. - {User: uAlice, Verb: "get", Resource: "pods", NS: "ns1", ExpectAllow: false}, - {User: uAlice, Verb: "get", Resource: "widgets", NS: "ns1", ExpectAllow: false}, - {User: uAlice, Verb: "get", Resource: "", NS: "ns1", ExpectAllow: false}, + {User: uAlice, Verb: "get", Resource: "pods", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion}, + {User: uAlice, Verb: "get", Resource: "widgets", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion}, + {User: uAlice, Verb: "get", Resource: "", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion}, // Chuck can read events, since anyone can. - {User: uChuck, Verb: "get", Resource: "events", NS: "ns1", ExpectAllow: true}, - {User: uChuck, Verb: "get", Resource: "events", NS: "", ExpectAllow: true}, + {User: uChuck, Verb: "get", Resource: "events", NS: "ns1", ExpectDecision: authorizer.DecisionAllow}, + {User: uChuck, Verb: "get", Resource: "events", NS: "", ExpectDecision: authorizer.DecisionAllow}, // Chuck can't do other things. - {User: uChuck, Verb: "update", Resource: "events", NS: "ns1", ExpectAllow: false}, - {User: uChuck, Verb: "get", Resource: "pods", NS: "ns1", ExpectAllow: false}, - {User: uChuck, Verb: "get", Resource: "floop", NS: "ns1", ExpectAllow: false}, + {User: uChuck, Verb: "update", Resource: "events", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion}, + {User: uChuck, Verb: "get", Resource: "pods", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion}, + {User: uChuck, Verb: "get", Resource: "floop", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion}, // Chunk can't access things with no kind or namespace - {User: uChuck, Verb: "get", Path: "/", Resource: "", NS: "", ExpectAllow: false}, + {User: uChuck, Verb: "get", Path: "/", Resource: "", NS: "", ExpectDecision: authorizer.DecisionNoOpinion}, } for i, tc := range testCases { attr := authorizer.AttributesRecord{ @@ -133,11 +133,11 @@ func TestAuthorizeV0(t *testing.T) { ResourceRequest: len(tc.NS) > 0 || len(tc.Resource) > 0, } - authorized, _, _ := a.Authorize(attr) - if tc.ExpectAllow != authorized { + decision, _, _ := a.Authorize(attr) + if tc.ExpectDecision != decision { t.Logf("tc: %v -> attr %v", tc, attr) t.Errorf("%d: Expected allowed=%v but actually allowed=%v\n\t%v", - i, tc.ExpectAllow, authorized, tc) + i, tc.ExpectDecision, decision, tc) } } } @@ -373,72 +373,72 @@ func TestAuthorizeV1beta1(t *testing.T) { uAPIGroup := user.DefaultInfo{Name: "apigroupuser", UID: "uid8", Groups: authenticatedGroup} testCases := []struct { - User user.DefaultInfo - Verb string - Resource string - APIGroup string - NS string - Path string - ExpectAllow bool + User user.DefaultInfo + Verb string + Resource string + APIGroup string + NS string + Path string + ExpectDecision authorizer.Decision }{ // Scheduler can read pods - {User: uScheduler, Verb: "list", Resource: "pods", NS: "ns1", ExpectAllow: true}, - {User: uScheduler, Verb: "list", Resource: "pods", NS: "", ExpectAllow: true}, + {User: uScheduler, Verb: "list", Resource: "pods", NS: "ns1", ExpectDecision: authorizer.DecisionAllow}, + {User: uScheduler, Verb: "list", Resource: "pods", NS: "", ExpectDecision: authorizer.DecisionAllow}, // Scheduler cannot write pods - {User: uScheduler, Verb: "create", Resource: "pods", NS: "ns1", ExpectAllow: false}, - {User: uScheduler, Verb: "create", Resource: "pods", NS: "", ExpectAllow: false}, + {User: uScheduler, Verb: "create", Resource: "pods", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion}, + {User: uScheduler, Verb: "create", Resource: "pods", NS: "", ExpectDecision: authorizer.DecisionNoOpinion}, // Scheduler can write bindings - {User: uScheduler, Verb: "get", Resource: "bindings", NS: "ns1", ExpectAllow: true}, - {User: uScheduler, Verb: "get", Resource: "bindings", NS: "", ExpectAllow: true}, + {User: uScheduler, Verb: "get", Resource: "bindings", NS: "ns1", ExpectDecision: authorizer.DecisionAllow}, + {User: uScheduler, Verb: "get", Resource: "bindings", NS: "", ExpectDecision: authorizer.DecisionAllow}, // Alice can read and write anything in the right namespace. - {User: uAlice, Verb: "get", Resource: "pods", NS: "projectCaribou", ExpectAllow: true}, - {User: uAlice, Verb: "get", Resource: "widgets", NS: "projectCaribou", ExpectAllow: true}, - {User: uAlice, Verb: "get", Resource: "", NS: "projectCaribou", ExpectAllow: true}, - {User: uAlice, Verb: "update", Resource: "pods", NS: "projectCaribou", ExpectAllow: true}, - {User: uAlice, Verb: "update", Resource: "widgets", NS: "projectCaribou", ExpectAllow: true}, - {User: uAlice, Verb: "update", Resource: "", NS: "projectCaribou", ExpectAllow: true}, + {User: uAlice, Verb: "get", Resource: "pods", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow}, + {User: uAlice, Verb: "get", Resource: "widgets", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow}, + {User: uAlice, Verb: "get", Resource: "", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow}, + {User: uAlice, Verb: "update", Resource: "pods", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow}, + {User: uAlice, Verb: "update", Resource: "widgets", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow}, + {User: uAlice, Verb: "update", Resource: "", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow}, // .. but not the wrong namespace. - {User: uAlice, Verb: "get", Resource: "pods", NS: "ns1", ExpectAllow: false}, - {User: uAlice, Verb: "get", Resource: "widgets", NS: "ns1", ExpectAllow: false}, - {User: uAlice, Verb: "get", Resource: "", NS: "ns1", ExpectAllow: false}, + {User: uAlice, Verb: "get", Resource: "pods", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion}, + {User: uAlice, Verb: "get", Resource: "widgets", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion}, + {User: uAlice, Verb: "get", Resource: "", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion}, // Debbie can write to pods in the right namespace - {User: uDebbie, Verb: "update", Resource: "pods", NS: "projectCaribou", ExpectAllow: true}, + {User: uDebbie, Verb: "update", Resource: "pods", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow}, // Chuck can read events, since anyone can. - {User: uChuck, Verb: "get", Resource: "events", NS: "ns1", ExpectAllow: true}, - {User: uChuck, Verb: "get", Resource: "events", NS: "", ExpectAllow: true}, + {User: uChuck, Verb: "get", Resource: "events", NS: "ns1", ExpectDecision: authorizer.DecisionAllow}, + {User: uChuck, Verb: "get", Resource: "events", NS: "", ExpectDecision: authorizer.DecisionAllow}, // Chuck can't do other things. - {User: uChuck, Verb: "update", Resource: "events", NS: "ns1", ExpectAllow: false}, - {User: uChuck, Verb: "get", Resource: "pods", NS: "ns1", ExpectAllow: false}, - {User: uChuck, Verb: "get", Resource: "floop", NS: "ns1", ExpectAllow: false}, + {User: uChuck, Verb: "update", Resource: "events", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion}, + {User: uChuck, Verb: "get", Resource: "pods", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion}, + {User: uChuck, Verb: "get", Resource: "floop", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion}, // Chuck can't access things with no resource or namespace - {User: uChuck, Verb: "get", Path: "/", Resource: "", NS: "", ExpectAllow: false}, + {User: uChuck, Verb: "get", Path: "/", Resource: "", NS: "", ExpectDecision: authorizer.DecisionNoOpinion}, // but can access /api - {User: uChuck, Verb: "get", Path: "/api", Resource: "", NS: "", ExpectAllow: true}, + {User: uChuck, Verb: "get", Path: "/api", Resource: "", NS: "", ExpectDecision: authorizer.DecisionAllow}, // though he cannot write to it - {User: uChuck, Verb: "create", Path: "/api", Resource: "", NS: "", ExpectAllow: false}, + {User: uChuck, Verb: "create", Path: "/api", Resource: "", NS: "", ExpectDecision: authorizer.DecisionNoOpinion}, // while he can write to /custom - {User: uChuck, Verb: "update", Path: "/custom", Resource: "", NS: "", ExpectAllow: true}, + {User: uChuck, Verb: "update", Path: "/custom", Resource: "", NS: "", ExpectDecision: authorizer.DecisionAllow}, // he cannot get "/root" - {User: uChuck, Verb: "get", Path: "/root", Resource: "", NS: "", ExpectAllow: false}, + {User: uChuck, Verb: "get", Path: "/root", Resource: "", NS: "", ExpectDecision: authorizer.DecisionNoOpinion}, // but can get any subpath - {User: uChuck, Verb: "get", Path: "/root/", Resource: "", NS: "", ExpectAllow: true}, - {User: uChuck, Verb: "get", Path: "/root/test/1/2/3", Resource: "", NS: "", ExpectAllow: true}, + {User: uChuck, Verb: "get", Path: "/root/", Resource: "", NS: "", ExpectDecision: authorizer.DecisionAllow}, + {User: uChuck, Verb: "get", Path: "/root/test/1/2/3", Resource: "", NS: "", ExpectDecision: authorizer.DecisionAllow}, // the user "noresource" can get any non-resource request - {User: uNoResource, Verb: "get", Path: "", Resource: "", NS: "", ExpectAllow: true}, - {User: uNoResource, Verb: "get", Path: "/", Resource: "", NS: "", ExpectAllow: true}, - {User: uNoResource, Verb: "get", Path: "/foo/bar/baz", Resource: "", NS: "", ExpectAllow: true}, + {User: uNoResource, Verb: "get", Path: "", Resource: "", NS: "", ExpectDecision: authorizer.DecisionAllow}, + {User: uNoResource, Verb: "get", Path: "/", Resource: "", NS: "", ExpectDecision: authorizer.DecisionAllow}, + {User: uNoResource, Verb: "get", Path: "/foo/bar/baz", Resource: "", NS: "", ExpectDecision: authorizer.DecisionAllow}, // but cannot get any request where IsResourceRequest() == true - {User: uNoResource, Verb: "get", Path: "/", Resource: "", NS: "bar", ExpectAllow: false}, - {User: uNoResource, Verb: "get", Path: "/foo/bar/baz", Resource: "foo", NS: "bar", ExpectAllow: false}, + {User: uNoResource, Verb: "get", Path: "/", Resource: "", NS: "bar", ExpectDecision: authorizer.DecisionNoOpinion}, + {User: uNoResource, Verb: "get", Path: "/foo/bar/baz", Resource: "foo", NS: "bar", ExpectDecision: authorizer.DecisionNoOpinion}, // Test APIGroup matching - {User: uAPIGroup, Verb: "get", APIGroup: "x", Resource: "foo", NS: "projectAnyGroup", ExpectAllow: true}, - {User: uAPIGroup, Verb: "get", APIGroup: "x", Resource: "foo", NS: "projectEmptyGroup", ExpectAllow: false}, - {User: uAPIGroup, Verb: "get", APIGroup: "x", Resource: "foo", NS: "projectXGroup", ExpectAllow: true}, + {User: uAPIGroup, Verb: "get", APIGroup: "x", Resource: "foo", NS: "projectAnyGroup", ExpectDecision: authorizer.DecisionAllow}, + {User: uAPIGroup, Verb: "get", APIGroup: "x", Resource: "foo", NS: "projectEmptyGroup", ExpectDecision: authorizer.DecisionNoOpinion}, + {User: uAPIGroup, Verb: "get", APIGroup: "x", Resource: "foo", NS: "projectXGroup", ExpectDecision: authorizer.DecisionAllow}, } for i, tc := range testCases { attr := authorizer.AttributesRecord{ @@ -451,10 +451,10 @@ func TestAuthorizeV1beta1(t *testing.T) { Path: tc.Path, } // t.Logf("tc %2v: %v -> attr %v", i, tc, attr) - authorized, _, _ := a.Authorize(attr) - if tc.ExpectAllow != authorized { + decision, _, _ := a.Authorize(attr) + if tc.ExpectDecision != decision { t.Errorf("%d: Expected allowed=%v but actually allowed=%v, for case %+v & %+v", - i, tc.ExpectAllow, authorized, tc, attr) + i, tc.ExpectDecision, decision, tc, attr) } } } @@ -799,8 +799,8 @@ func TestSubjectMatches(t *testing.T) { } for k, tc := range testCases { - policy := &api.Policy{} - if err := api.Scheme.Convert(tc.Policy, policy, nil); err != nil { + policy := &abac.Policy{} + if err := abac.Scheme.Convert(tc.Policy, policy, nil); err != nil { t.Errorf("%s: error converting: %v", k, err) continue } @@ -1254,8 +1254,8 @@ func TestPolicy(t *testing.T) { }, } for _, test := range tests { - policy := &api.Policy{} - if err := api.Scheme.Convert(test.policy, policy, nil); err != nil { + policy := &abac.Policy{} + if err := abac.Scheme.Convert(test.policy, policy, nil); err != nil { t.Errorf("%s: error converting: %v", test.name, err) continue } diff --git a/pkg/auth/group/OWNERS b/pkg/auth/group/OWNERS deleted file mode 100755 index 94487992079..00000000000 --- a/pkg/auth/group/OWNERS +++ /dev/null @@ -1 +0,0 @@ -reviewers: diff --git a/pkg/auth/handlers/OWNERS b/pkg/auth/handlers/OWNERS deleted file mode 100755 index 94487992079..00000000000 --- a/pkg/auth/handlers/OWNERS +++ /dev/null @@ -1 +0,0 @@ -reviewers: diff --git a/pkg/auth/nodeidentifier/BUILD b/pkg/auth/nodeidentifier/BUILD index f51a1dc6119..a573310579c 100644 --- a/pkg/auth/nodeidentifier/BUILD +++ b/pkg/auth/nodeidentifier/BUILD @@ -19,8 +19,8 @@ go_library( go_test( name = "go_default_test", srcs = ["default_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/auth/nodeidentifier", - library = ":go_default_library", deps = ["//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library"], ) diff --git a/pkg/bootstrap/api/BUILD b/pkg/bootstrap/api/BUILD index f38e43841d2..f72517262a5 100644 --- a/pkg/bootstrap/api/BUILD +++ b/pkg/bootstrap/api/BUILD @@ -14,7 +14,10 @@ go_library( "types.go", ], importpath = "k8s.io/kubernetes/pkg/bootstrap/api", - deps = ["//vendor/k8s.io/api/core/v1:go_default_library"], + deps = [ + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + ], ) filegroup( @@ -33,6 +36,6 @@ filegroup( go_test( name = "go_default_test", srcs = ["helpers_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/bootstrap/api", - library = ":go_default_library", ) diff --git a/pkg/bootstrap/api/helpers.go b/pkg/bootstrap/api/helpers.go index 639d61a33c3..01859bc37fe 100644 --- a/pkg/bootstrap/api/helpers.go +++ b/pkg/bootstrap/api/helpers.go @@ -18,7 +18,9 @@ package api import ( "fmt" + "k8s.io/apimachinery/pkg/util/sets" "regexp" + "strings" ) var bootstrapGroupRegexp = regexp.MustCompile(`\A` + BootstrapGroupPattern + `\z`) @@ -32,3 +34,19 @@ func ValidateBootstrapGroupName(name string) error { } return fmt.Errorf("bootstrap group %q is invalid (must match %s)", name, BootstrapGroupPattern) } + +// ValidateUsages validates that the passed in string are valid usage strings for bootstrap tokens. +func ValidateUsages(usages []string) error { + usageAuthentication := strings.TrimPrefix(BootstrapTokenUsageAuthentication, BootstrapTokenUsagePrefix) + usageSigning := strings.TrimPrefix(BootstrapTokenUsageSigningKey, BootstrapTokenUsagePrefix) + invalidUsages := sets.NewString() + for _, usage := range usages { + if usage != usageAuthentication && usage != usageSigning { + invalidUsages.Insert(usage) + } + } + if len(invalidUsages) > 0 { + return fmt.Errorf("invalide bootstrap token usage string: %s, valid usage option: %s, %s", strings.Join(invalidUsages.List(), ","), usageAuthentication, usageSigning) + } + return nil +} diff --git a/pkg/bootstrap/api/helpers_test.go b/pkg/bootstrap/api/helpers_test.go index 177687150c5..d1575f60840 100644 --- a/pkg/bootstrap/api/helpers_test.go +++ b/pkg/bootstrap/api/helpers_test.go @@ -50,3 +50,27 @@ func TestValidateBootstrapGroupName(t *testing.T) { } } } + +func TestValidateUsages(t *testing.T) { + tests := []struct { + name string + input []string + valid bool + }{ + {"valid of signing", []string{"signing"}, true}, + {"valid of authentication", []string{"authentication"}, true}, + {"all valid", []string{"authentication", "signing"}, true}, + {"single invalid", []string{"authentication", "foo"}, false}, + {"all invalid", []string{"foo", "bar"}, false}, + } + + for _, test := range tests { + err := ValidateUsages(test.input) + if err != nil && test.valid { + t.Errorf("test %q: ValidateUsages(%v) returned unexpected error: %v", test.name, test.input, err) + } + if err == nil && !test.valid { + t.Errorf("test %q: ValidateUsages(%v) was supposed to return an error but didn't", test.name, test.input) + } + } +} diff --git a/pkg/capabilities/BUILD b/pkg/capabilities/BUILD index 6ab539698cf..f011d7aa17d 100644 --- a/pkg/capabilities/BUILD +++ b/pkg/capabilities/BUILD @@ -18,8 +18,8 @@ go_library( go_test( name = "go_default_test", srcs = ["capabilities_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/capabilities", - library = ":go_default_library", ) filegroup( diff --git a/pkg/client/chaosclient/BUILD b/pkg/client/chaosclient/BUILD index 8ba5b300d30..81179c06740 100644 --- a/pkg/client/chaosclient/BUILD +++ b/pkg/client/chaosclient/BUILD @@ -16,8 +16,8 @@ go_library( go_test( name = "go_default_test", srcs = ["chaosclient_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/client/chaosclient", - library = ":go_default_library", ) filegroup( diff --git a/pkg/client/clientset_generated/internalclientset/BUILD b/pkg/client/clientset_generated/internalclientset/BUILD index 7fcbea15e10..219323e9e5e 100644 --- a/pkg/client/clientset_generated/internalclientset/BUILD +++ b/pkg/client/clientset_generated/internalclientset/BUILD @@ -21,6 +21,7 @@ go_library( "//pkg/client/clientset_generated/internalclientset/typed/batch/internalversion:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library", + "//pkg/client/clientset_generated/internalclientset/typed/events/internalversion:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/networking/internalversion:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/policy/internalversion:go_default_library", @@ -56,6 +57,7 @@ filegroup( "//pkg/client/clientset_generated/internalclientset/typed/batch/internalversion:all-srcs", "//pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion:all-srcs", "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:all-srcs", + "//pkg/client/clientset_generated/internalclientset/typed/events/internalversion:all-srcs", "//pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion:all-srcs", "//pkg/client/clientset_generated/internalclientset/typed/networking/internalversion:all-srcs", "//pkg/client/clientset_generated/internalclientset/typed/policy/internalversion:all-srcs", diff --git a/pkg/client/clientset_generated/internalclientset/clientset.go b/pkg/client/clientset_generated/internalclientset/clientset.go index c7eb9d437ca..77314b2e714 100644 --- a/pkg/client/clientset_generated/internalclientset/clientset.go +++ b/pkg/client/clientset_generated/internalclientset/clientset.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -29,6 +29,7 @@ import ( batchinternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion" certificatesinternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion" coreinternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" + eventsinternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/events/internalversion" extensionsinternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion" networkinginternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion" policyinternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion" @@ -48,6 +49,7 @@ type Interface interface { Autoscaling() autoscalinginternalversion.AutoscalingInterface Batch() batchinternalversion.BatchInterface Certificates() certificatesinternalversion.CertificatesInterface + Events() eventsinternalversion.EventsInterface Extensions() extensionsinternalversion.ExtensionsInterface Networking() networkinginternalversion.NetworkingInterface Policy() policyinternalversion.PolicyInterface @@ -69,6 +71,7 @@ type Clientset struct { autoscaling *autoscalinginternalversion.AutoscalingClient batch *batchinternalversion.BatchClient certificates *certificatesinternalversion.CertificatesClient + events *eventsinternalversion.EventsClient extensions *extensionsinternalversion.ExtensionsClient networking *networkinginternalversion.NetworkingClient policy *policyinternalversion.PolicyClient @@ -118,6 +121,11 @@ func (c *Clientset) Certificates() certificatesinternalversion.CertificatesInter return c.certificates } +// Events retrieves the EventsClient +func (c *Clientset) Events() eventsinternalversion.EventsInterface { + return c.events +} + // Extensions retrieves the ExtensionsClient func (c *Clientset) Extensions() extensionsinternalversion.ExtensionsInterface { return c.extensions @@ -201,6 +209,10 @@ func NewForConfig(c *rest.Config) (*Clientset, error) { if err != nil { return nil, err } + cs.events, err = eventsinternalversion.NewForConfig(&configShallowCopy) + if err != nil { + return nil, err + } cs.extensions, err = extensionsinternalversion.NewForConfig(&configShallowCopy) if err != nil { return nil, err @@ -250,6 +262,7 @@ func NewForConfigOrDie(c *rest.Config) *Clientset { cs.autoscaling = autoscalinginternalversion.NewForConfigOrDie(c) cs.batch = batchinternalversion.NewForConfigOrDie(c) cs.certificates = certificatesinternalversion.NewForConfigOrDie(c) + cs.events = eventsinternalversion.NewForConfigOrDie(c) cs.extensions = extensionsinternalversion.NewForConfigOrDie(c) cs.networking = networkinginternalversion.NewForConfigOrDie(c) cs.policy = policyinternalversion.NewForConfigOrDie(c) @@ -273,6 +286,7 @@ func New(c rest.Interface) *Clientset { cs.autoscaling = autoscalinginternalversion.New(c) cs.batch = batchinternalversion.New(c) cs.certificates = certificatesinternalversion.New(c) + cs.events = eventsinternalversion.New(c) cs.extensions = extensionsinternalversion.New(c) cs.networking = networkinginternalversion.New(c) cs.policy = policyinternalversion.New(c) diff --git a/pkg/client/clientset_generated/internalclientset/doc.go b/pkg/client/clientset_generated/internalclientset/doc.go index b667dd5157a..4ede718dee1 100644 --- a/pkg/client/clientset_generated/internalclientset/doc.go +++ b/pkg/client/clientset_generated/internalclientset/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/fake/BUILD b/pkg/client/clientset_generated/internalclientset/fake/BUILD index 7b7ef98d9e2..321fd0cdbb8 100644 --- a/pkg/client/clientset_generated/internalclientset/fake/BUILD +++ b/pkg/client/clientset_generated/internalclientset/fake/BUILD @@ -14,7 +14,6 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake", deps = [ - "//pkg/api:go_default_library", "//pkg/apis/admissionregistration:go_default_library", "//pkg/apis/apps:go_default_library", "//pkg/apis/authentication:go_default_library", @@ -22,6 +21,8 @@ go_library( "//pkg/apis/autoscaling:go_default_library", "//pkg/apis/batch:go_default_library", "//pkg/apis/certificates:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/events:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/apis/networking:go_default_library", "//pkg/apis/policy:go_default_library", @@ -46,6 +47,8 @@ go_library( "//pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/fake:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake:go_default_library", + "//pkg/client/clientset_generated/internalclientset/typed/events/internalversion:go_default_library", + "//pkg/client/clientset_generated/internalclientset/typed/events/internalversion/fake:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/networking/internalversion:go_default_library", diff --git a/pkg/client/clientset_generated/internalclientset/fake/clientset_generated.go b/pkg/client/clientset_generated/internalclientset/fake/clientset_generated.go index bd30e8ae181..65bf12a7c04 100644 --- a/pkg/client/clientset_generated/internalclientset/fake/clientset_generated.go +++ b/pkg/client/clientset_generated/internalclientset/fake/clientset_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -39,6 +39,8 @@ import ( fakecertificatesinternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/fake" coreinternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" fakecoreinternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake" + eventsinternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/events/internalversion" + fakeeventsinternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/events/internalversion/fake" extensionsinternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion" fakeextensionsinternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake" networkinginternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion" @@ -128,6 +130,11 @@ func (c *Clientset) Certificates() certificatesinternalversion.CertificatesInter return &fakecertificatesinternalversion.FakeCertificates{Fake: &c.Fake} } +// Events retrieves the EventsClient +func (c *Clientset) Events() eventsinternalversion.EventsInterface { + return &fakeeventsinternalversion.FakeEvents{Fake: &c.Fake} +} + // Extensions retrieves the ExtensionsClient func (c *Clientset) Extensions() extensionsinternalversion.ExtensionsInterface { return &fakeextensionsinternalversion.FakeExtensions{Fake: &c.Fake} diff --git a/pkg/client/clientset_generated/internalclientset/fake/doc.go b/pkg/client/clientset_generated/internalclientset/fake/doc.go index 3fd8e1e2cdc..8a3101e3981 100644 --- a/pkg/client/clientset_generated/internalclientset/fake/doc.go +++ b/pkg/client/clientset_generated/internalclientset/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/fake/register.go b/pkg/client/clientset_generated/internalclientset/fake/register.go index 279688bc764..b5d85f76975 100644 --- a/pkg/client/clientset_generated/internalclientset/fake/register.go +++ b/pkg/client/clientset_generated/internalclientset/fake/register.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,7 +21,6 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" serializer "k8s.io/apimachinery/pkg/runtime/serializer" - coreinternalversion "k8s.io/kubernetes/pkg/api" admissionregistrationinternalversion "k8s.io/kubernetes/pkg/apis/admissionregistration" appsinternalversion "k8s.io/kubernetes/pkg/apis/apps" authenticationinternalversion "k8s.io/kubernetes/pkg/apis/authentication" @@ -29,6 +28,8 @@ import ( autoscalinginternalversion "k8s.io/kubernetes/pkg/apis/autoscaling" batchinternalversion "k8s.io/kubernetes/pkg/apis/batch" certificatesinternalversion "k8s.io/kubernetes/pkg/apis/certificates" + coreinternalversion "k8s.io/kubernetes/pkg/apis/core" + eventsinternalversion "k8s.io/kubernetes/pkg/apis/events" extensionsinternalversion "k8s.io/kubernetes/pkg/apis/extensions" networkinginternalversion "k8s.io/kubernetes/pkg/apis/networking" policyinternalversion "k8s.io/kubernetes/pkg/apis/policy" @@ -70,6 +71,7 @@ func AddToScheme(scheme *runtime.Scheme) { autoscalinginternalversion.AddToScheme(scheme) batchinternalversion.AddToScheme(scheme) certificatesinternalversion.AddToScheme(scheme) + eventsinternalversion.AddToScheme(scheme) extensionsinternalversion.AddToScheme(scheme) networkinginternalversion.AddToScheme(scheme) policyinternalversion.AddToScheme(scheme) diff --git a/pkg/client/clientset_generated/internalclientset/scheme/BUILD b/pkg/client/clientset_generated/internalclientset/scheme/BUILD index 8b94589bf81..97e20c749e7 100644 --- a/pkg/client/clientset_generated/internalclientset/scheme/BUILD +++ b/pkg/client/clientset_generated/internalclientset/scheme/BUILD @@ -14,7 +14,6 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme", deps = [ - "//pkg/api/install:go_default_library", "//pkg/apis/admissionregistration/install:go_default_library", "//pkg/apis/apps/install:go_default_library", "//pkg/apis/authentication/install:go_default_library", @@ -23,6 +22,8 @@ go_library( "//pkg/apis/batch/install:go_default_library", "//pkg/apis/certificates/install:go_default_library", "//pkg/apis/componentconfig/install:go_default_library", + "//pkg/apis/core/install:go_default_library", + "//pkg/apis/events/install:go_default_library", "//pkg/apis/extensions/install:go_default_library", "//pkg/apis/networking/install:go_default_library", "//pkg/apis/policy/install:go_default_library", diff --git a/pkg/client/clientset_generated/internalclientset/scheme/doc.go b/pkg/client/clientset_generated/internalclientset/scheme/doc.go index 3ec2200d099..3d3ab5f4edf 100644 --- a/pkg/client/clientset_generated/internalclientset/scheme/doc.go +++ b/pkg/client/clientset_generated/internalclientset/scheme/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/scheme/register.go b/pkg/client/clientset_generated/internalclientset/scheme/register.go index 500d8da9fa2..69fb9e80a90 100644 --- a/pkg/client/clientset_generated/internalclientset/scheme/register.go +++ b/pkg/client/clientset_generated/internalclientset/scheme/register.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -17,13 +17,14 @@ limitations under the License. package scheme import ( + os "os" + announced "k8s.io/apimachinery/pkg/apimachinery/announced" registered "k8s.io/apimachinery/pkg/apimachinery/registered" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" serializer "k8s.io/apimachinery/pkg/runtime/serializer" - core "k8s.io/kubernetes/pkg/api/install" admissionregistration "k8s.io/kubernetes/pkg/apis/admissionregistration/install" apps "k8s.io/kubernetes/pkg/apis/apps/install" authentication "k8s.io/kubernetes/pkg/apis/authentication/install" @@ -31,6 +32,8 @@ import ( autoscaling "k8s.io/kubernetes/pkg/apis/autoscaling/install" batch "k8s.io/kubernetes/pkg/apis/batch/install" certificates "k8s.io/kubernetes/pkg/apis/certificates/install" + core "k8s.io/kubernetes/pkg/apis/core/install" + events "k8s.io/kubernetes/pkg/apis/events/install" extensions "k8s.io/kubernetes/pkg/apis/extensions/install" networking "k8s.io/kubernetes/pkg/apis/networking/install" policy "k8s.io/kubernetes/pkg/apis/policy/install" @@ -38,7 +41,6 @@ import ( scheduling "k8s.io/kubernetes/pkg/apis/scheduling/install" settings "k8s.io/kubernetes/pkg/apis/settings/install" storage "k8s.io/kubernetes/pkg/apis/storage/install" - os "os" ) var Scheme = runtime.NewScheme() @@ -63,6 +65,7 @@ func Install(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *r autoscaling.Install(groupFactoryRegistry, registry, scheme) batch.Install(groupFactoryRegistry, registry, scheme) certificates.Install(groupFactoryRegistry, registry, scheme) + events.Install(groupFactoryRegistry, registry, scheme) extensions.Install(groupFactoryRegistry, registry, scheme) networking.Install(groupFactoryRegistry, registry, scheme) policy.Install(groupFactoryRegistry, registry, scheme) diff --git a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/BUILD b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/BUILD index 6d1e815c9e9..74333876b72 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/BUILD +++ b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/BUILD @@ -10,9 +10,10 @@ go_library( srcs = [ "admissionregistration_client.go", "doc.go", - "externaladmissionhookconfiguration.go", "generated_expansion.go", "initializerconfiguration.go", + "mutatingwebhookconfiguration.go", + "validatingwebhookconfiguration.go", ], importpath = "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion", deps = [ diff --git a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/admissionregistration_client.go b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/admissionregistration_client.go index e64ad0d1375..b2a30fa0d5c 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/admissionregistration_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/admissionregistration_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,8 +23,9 @@ import ( type AdmissionregistrationInterface interface { RESTClient() rest.Interface - ExternalAdmissionHookConfigurationsGetter InitializerConfigurationsGetter + MutatingWebhookConfigurationsGetter + ValidatingWebhookConfigurationsGetter } // AdmissionregistrationClient is used to interact with features provided by the admissionregistration.k8s.io group. @@ -32,14 +33,18 @@ type AdmissionregistrationClient struct { restClient rest.Interface } -func (c *AdmissionregistrationClient) ExternalAdmissionHookConfigurations() ExternalAdmissionHookConfigurationInterface { - return newExternalAdmissionHookConfigurations(c) -} - func (c *AdmissionregistrationClient) InitializerConfigurations() InitializerConfigurationInterface { return newInitializerConfigurations(c) } +func (c *AdmissionregistrationClient) MutatingWebhookConfigurations() MutatingWebhookConfigurationInterface { + return newMutatingWebhookConfigurations(c) +} + +func (c *AdmissionregistrationClient) ValidatingWebhookConfigurations() ValidatingWebhookConfigurationInterface { + return newValidatingWebhookConfigurations(c) +} + // NewForConfig creates a new AdmissionregistrationClient for the given config. func NewForConfig(c *rest.Config) (*AdmissionregistrationClient, error) { config := *c diff --git a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/doc.go b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/doc.go index 3adf06d8934..8615019757d 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/externaladmissionhookconfiguration.go b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/externaladmissionhookconfiguration.go deleted file mode 100644 index 09b2b383aa7..00000000000 --- a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/externaladmissionhookconfiguration.go +++ /dev/null @@ -1,145 +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 internalversion - -import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" - admissionregistration "k8s.io/kubernetes/pkg/apis/admissionregistration" - scheme "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme" -) - -// ExternalAdmissionHookConfigurationsGetter has a method to return a ExternalAdmissionHookConfigurationInterface. -// A group's client should implement this interface. -type ExternalAdmissionHookConfigurationsGetter interface { - ExternalAdmissionHookConfigurations() ExternalAdmissionHookConfigurationInterface -} - -// ExternalAdmissionHookConfigurationInterface has methods to work with ExternalAdmissionHookConfiguration resources. -type ExternalAdmissionHookConfigurationInterface interface { - Create(*admissionregistration.ExternalAdmissionHookConfiguration) (*admissionregistration.ExternalAdmissionHookConfiguration, error) - Update(*admissionregistration.ExternalAdmissionHookConfiguration) (*admissionregistration.ExternalAdmissionHookConfiguration, error) - Delete(name string, options *v1.DeleteOptions) error - DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*admissionregistration.ExternalAdmissionHookConfiguration, error) - List(opts v1.ListOptions) (*admissionregistration.ExternalAdmissionHookConfigurationList, error) - Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *admissionregistration.ExternalAdmissionHookConfiguration, err error) - ExternalAdmissionHookConfigurationExpansion -} - -// externalAdmissionHookConfigurations implements ExternalAdmissionHookConfigurationInterface -type externalAdmissionHookConfigurations struct { - client rest.Interface -} - -// newExternalAdmissionHookConfigurations returns a ExternalAdmissionHookConfigurations -func newExternalAdmissionHookConfigurations(c *AdmissionregistrationClient) *externalAdmissionHookConfigurations { - return &externalAdmissionHookConfigurations{ - client: c.RESTClient(), - } -} - -// Get takes name of the externalAdmissionHookConfiguration, and returns the corresponding externalAdmissionHookConfiguration object, and an error if there is any. -func (c *externalAdmissionHookConfigurations) Get(name string, options v1.GetOptions) (result *admissionregistration.ExternalAdmissionHookConfiguration, err error) { - result = &admissionregistration.ExternalAdmissionHookConfiguration{} - err = c.client.Get(). - Resource("externaladmissionhookconfigurations"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of ExternalAdmissionHookConfigurations that match those selectors. -func (c *externalAdmissionHookConfigurations) List(opts v1.ListOptions) (result *admissionregistration.ExternalAdmissionHookConfigurationList, err error) { - result = &admissionregistration.ExternalAdmissionHookConfigurationList{} - err = c.client.Get(). - Resource("externaladmissionhookconfigurations"). - VersionedParams(&opts, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested externalAdmissionHookConfigurations. -func (c *externalAdmissionHookConfigurations) Watch(opts v1.ListOptions) (watch.Interface, error) { - opts.Watch = true - return c.client.Get(). - Resource("externaladmissionhookconfigurations"). - VersionedParams(&opts, scheme.ParameterCodec). - Watch() -} - -// Create takes the representation of a externalAdmissionHookConfiguration and creates it. Returns the server's representation of the externalAdmissionHookConfiguration, and an error, if there is any. -func (c *externalAdmissionHookConfigurations) Create(externalAdmissionHookConfiguration *admissionregistration.ExternalAdmissionHookConfiguration) (result *admissionregistration.ExternalAdmissionHookConfiguration, err error) { - result = &admissionregistration.ExternalAdmissionHookConfiguration{} - err = c.client.Post(). - Resource("externaladmissionhookconfigurations"). - Body(externalAdmissionHookConfiguration). - Do(). - Into(result) - return -} - -// Update takes the representation of a externalAdmissionHookConfiguration and updates it. Returns the server's representation of the externalAdmissionHookConfiguration, and an error, if there is any. -func (c *externalAdmissionHookConfigurations) Update(externalAdmissionHookConfiguration *admissionregistration.ExternalAdmissionHookConfiguration) (result *admissionregistration.ExternalAdmissionHookConfiguration, err error) { - result = &admissionregistration.ExternalAdmissionHookConfiguration{} - err = c.client.Put(). - Resource("externaladmissionhookconfigurations"). - Name(externalAdmissionHookConfiguration.Name). - Body(externalAdmissionHookConfiguration). - Do(). - Into(result) - return -} - -// Delete takes name of the externalAdmissionHookConfiguration and deletes it. Returns an error if one occurs. -func (c *externalAdmissionHookConfigurations) Delete(name string, options *v1.DeleteOptions) error { - return c.client.Delete(). - Resource("externaladmissionhookconfigurations"). - Name(name). - Body(options). - Do(). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *externalAdmissionHookConfigurations) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - return c.client.Delete(). - Resource("externaladmissionhookconfigurations"). - VersionedParams(&listOptions, scheme.ParameterCodec). - Body(options). - Do(). - Error() -} - -// Patch applies the patch and returns the patched externalAdmissionHookConfiguration. -func (c *externalAdmissionHookConfigurations) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *admissionregistration.ExternalAdmissionHookConfiguration, err error) { - result = &admissionregistration.ExternalAdmissionHookConfiguration{} - err = c.client.Patch(pt). - Resource("externaladmissionhookconfigurations"). - SubResource(subresources...). - Name(name). - Body(data). - Do(). - Into(result) - return -} diff --git a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake/BUILD b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake/BUILD index 8d32bc24966..9fd39c1ad32 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake/BUILD +++ b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake/BUILD @@ -10,8 +10,9 @@ go_library( srcs = [ "doc.go", "fake_admissionregistration_client.go", - "fake_externaladmissionhookconfiguration.go", "fake_initializerconfiguration.go", + "fake_mutatingwebhookconfiguration.go", + "fake_validatingwebhookconfiguration.go", ], importpath = "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake", deps = [ diff --git a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake/doc.go b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake/doc.go index c58fac35e4b..63e2c8a0821 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake/fake_admissionregistration_client.go b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake/fake_admissionregistration_client.go index b0e7b835720..097363daad6 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake/fake_admissionregistration_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake/fake_admissionregistration_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -26,14 +26,18 @@ type FakeAdmissionregistration struct { *testing.Fake } -func (c *FakeAdmissionregistration) ExternalAdmissionHookConfigurations() internalversion.ExternalAdmissionHookConfigurationInterface { - return &FakeExternalAdmissionHookConfigurations{c} -} - func (c *FakeAdmissionregistration) InitializerConfigurations() internalversion.InitializerConfigurationInterface { return &FakeInitializerConfigurations{c} } +func (c *FakeAdmissionregistration) MutatingWebhookConfigurations() internalversion.MutatingWebhookConfigurationInterface { + return &FakeMutatingWebhookConfigurations{c} +} + +func (c *FakeAdmissionregistration) ValidatingWebhookConfigurations() internalversion.ValidatingWebhookConfigurationInterface { + return &FakeValidatingWebhookConfigurations{c} +} + // RESTClient returns a RESTClient that is used to communicate // with API server by this client implementation. func (c *FakeAdmissionregistration) RESTClient() rest.Interface { diff --git a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake/fake_externaladmissionhookconfiguration.go b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake/fake_externaladmissionhookconfiguration.go deleted file mode 100644 index be6b42e2ede..00000000000 --- a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake/fake_externaladmissionhookconfiguration.go +++ /dev/null @@ -1,118 +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 fake - -import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" - admissionregistration "k8s.io/kubernetes/pkg/apis/admissionregistration" -) - -// FakeExternalAdmissionHookConfigurations implements ExternalAdmissionHookConfigurationInterface -type FakeExternalAdmissionHookConfigurations struct { - Fake *FakeAdmissionregistration -} - -var externaladmissionhookconfigurationsResource = schema.GroupVersionResource{Group: "admissionregistration.k8s.io", Version: "", Resource: "externaladmissionhookconfigurations"} - -var externaladmissionhookconfigurationsKind = schema.GroupVersionKind{Group: "admissionregistration.k8s.io", Version: "", Kind: "ExternalAdmissionHookConfiguration"} - -// Get takes name of the externalAdmissionHookConfiguration, and returns the corresponding externalAdmissionHookConfiguration object, and an error if there is any. -func (c *FakeExternalAdmissionHookConfigurations) Get(name string, options v1.GetOptions) (result *admissionregistration.ExternalAdmissionHookConfiguration, err error) { - obj, err := c.Fake. - Invokes(testing.NewRootGetAction(externaladmissionhookconfigurationsResource, name), &admissionregistration.ExternalAdmissionHookConfiguration{}) - if obj == nil { - return nil, err - } - return obj.(*admissionregistration.ExternalAdmissionHookConfiguration), err -} - -// List takes label and field selectors, and returns the list of ExternalAdmissionHookConfigurations that match those selectors. -func (c *FakeExternalAdmissionHookConfigurations) List(opts v1.ListOptions) (result *admissionregistration.ExternalAdmissionHookConfigurationList, err error) { - obj, err := c.Fake. - Invokes(testing.NewRootListAction(externaladmissionhookconfigurationsResource, externaladmissionhookconfigurationsKind, opts), &admissionregistration.ExternalAdmissionHookConfigurationList{}) - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &admissionregistration.ExternalAdmissionHookConfigurationList{} - for _, item := range obj.(*admissionregistration.ExternalAdmissionHookConfigurationList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested externalAdmissionHookConfigurations. -func (c *FakeExternalAdmissionHookConfigurations) Watch(opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewRootWatchAction(externaladmissionhookconfigurationsResource, opts)) -} - -// Create takes the representation of a externalAdmissionHookConfiguration and creates it. Returns the server's representation of the externalAdmissionHookConfiguration, and an error, if there is any. -func (c *FakeExternalAdmissionHookConfigurations) Create(externalAdmissionHookConfiguration *admissionregistration.ExternalAdmissionHookConfiguration) (result *admissionregistration.ExternalAdmissionHookConfiguration, err error) { - obj, err := c.Fake. - Invokes(testing.NewRootCreateAction(externaladmissionhookconfigurationsResource, externalAdmissionHookConfiguration), &admissionregistration.ExternalAdmissionHookConfiguration{}) - if obj == nil { - return nil, err - } - return obj.(*admissionregistration.ExternalAdmissionHookConfiguration), err -} - -// Update takes the representation of a externalAdmissionHookConfiguration and updates it. Returns the server's representation of the externalAdmissionHookConfiguration, and an error, if there is any. -func (c *FakeExternalAdmissionHookConfigurations) Update(externalAdmissionHookConfiguration *admissionregistration.ExternalAdmissionHookConfiguration) (result *admissionregistration.ExternalAdmissionHookConfiguration, err error) { - obj, err := c.Fake. - Invokes(testing.NewRootUpdateAction(externaladmissionhookconfigurationsResource, externalAdmissionHookConfiguration), &admissionregistration.ExternalAdmissionHookConfiguration{}) - if obj == nil { - return nil, err - } - return obj.(*admissionregistration.ExternalAdmissionHookConfiguration), err -} - -// Delete takes name of the externalAdmissionHookConfiguration and deletes it. Returns an error if one occurs. -func (c *FakeExternalAdmissionHookConfigurations) Delete(name string, options *v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewRootDeleteAction(externaladmissionhookconfigurationsResource, name), &admissionregistration.ExternalAdmissionHookConfiguration{}) - return err -} - -// DeleteCollection deletes a collection of objects. -func (c *FakeExternalAdmissionHookConfigurations) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - action := testing.NewRootDeleteCollectionAction(externaladmissionhookconfigurationsResource, listOptions) - - _, err := c.Fake.Invokes(action, &admissionregistration.ExternalAdmissionHookConfigurationList{}) - return err -} - -// Patch applies the patch and returns the patched externalAdmissionHookConfiguration. -func (c *FakeExternalAdmissionHookConfigurations) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *admissionregistration.ExternalAdmissionHookConfiguration, err error) { - obj, err := c.Fake. - Invokes(testing.NewRootPatchSubresourceAction(externaladmissionhookconfigurationsResource, name, data, subresources...), &admissionregistration.ExternalAdmissionHookConfiguration{}) - if obj == nil { - return nil, err - } - return obj.(*admissionregistration.ExternalAdmissionHookConfiguration), err -} diff --git a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake/fake_initializerconfiguration.go b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake/fake_initializerconfiguration.go index 5d0d153491a..b69df0f6eb7 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake/fake_initializerconfiguration.go +++ b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake/fake_initializerconfiguration.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake/fake_mutatingwebhookconfiguration.go b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake/fake_mutatingwebhookconfiguration.go new file mode 100644 index 00000000000..ac62029d39b --- /dev/null +++ b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake/fake_mutatingwebhookconfiguration.go @@ -0,0 +1,118 @@ +/* +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 fake + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" + admissionregistration "k8s.io/kubernetes/pkg/apis/admissionregistration" +) + +// FakeMutatingWebhookConfigurations implements MutatingWebhookConfigurationInterface +type FakeMutatingWebhookConfigurations struct { + Fake *FakeAdmissionregistration +} + +var mutatingwebhookconfigurationsResource = schema.GroupVersionResource{Group: "admissionregistration.k8s.io", Version: "", Resource: "mutatingwebhookconfigurations"} + +var mutatingwebhookconfigurationsKind = schema.GroupVersionKind{Group: "admissionregistration.k8s.io", Version: "", Kind: "MutatingWebhookConfiguration"} + +// Get takes name of the mutatingWebhookConfiguration, and returns the corresponding mutatingWebhookConfiguration object, and an error if there is any. +func (c *FakeMutatingWebhookConfigurations) Get(name string, options v1.GetOptions) (result *admissionregistration.MutatingWebhookConfiguration, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootGetAction(mutatingwebhookconfigurationsResource, name), &admissionregistration.MutatingWebhookConfiguration{}) + if obj == nil { + return nil, err + } + return obj.(*admissionregistration.MutatingWebhookConfiguration), err +} + +// List takes label and field selectors, and returns the list of MutatingWebhookConfigurations that match those selectors. +func (c *FakeMutatingWebhookConfigurations) List(opts v1.ListOptions) (result *admissionregistration.MutatingWebhookConfigurationList, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootListAction(mutatingwebhookconfigurationsResource, mutatingwebhookconfigurationsKind, opts), &admissionregistration.MutatingWebhookConfigurationList{}) + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &admissionregistration.MutatingWebhookConfigurationList{} + for _, item := range obj.(*admissionregistration.MutatingWebhookConfigurationList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested mutatingWebhookConfigurations. +func (c *FakeMutatingWebhookConfigurations) Watch(opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewRootWatchAction(mutatingwebhookconfigurationsResource, opts)) +} + +// Create takes the representation of a mutatingWebhookConfiguration and creates it. Returns the server's representation of the mutatingWebhookConfiguration, and an error, if there is any. +func (c *FakeMutatingWebhookConfigurations) Create(mutatingWebhookConfiguration *admissionregistration.MutatingWebhookConfiguration) (result *admissionregistration.MutatingWebhookConfiguration, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootCreateAction(mutatingwebhookconfigurationsResource, mutatingWebhookConfiguration), &admissionregistration.MutatingWebhookConfiguration{}) + if obj == nil { + return nil, err + } + return obj.(*admissionregistration.MutatingWebhookConfiguration), err +} + +// Update takes the representation of a mutatingWebhookConfiguration and updates it. Returns the server's representation of the mutatingWebhookConfiguration, and an error, if there is any. +func (c *FakeMutatingWebhookConfigurations) Update(mutatingWebhookConfiguration *admissionregistration.MutatingWebhookConfiguration) (result *admissionregistration.MutatingWebhookConfiguration, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateAction(mutatingwebhookconfigurationsResource, mutatingWebhookConfiguration), &admissionregistration.MutatingWebhookConfiguration{}) + if obj == nil { + return nil, err + } + return obj.(*admissionregistration.MutatingWebhookConfiguration), err +} + +// Delete takes name of the mutatingWebhookConfiguration and deletes it. Returns an error if one occurs. +func (c *FakeMutatingWebhookConfigurations) Delete(name string, options *v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewRootDeleteAction(mutatingwebhookconfigurationsResource, name), &admissionregistration.MutatingWebhookConfiguration{}) + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeMutatingWebhookConfigurations) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + action := testing.NewRootDeleteCollectionAction(mutatingwebhookconfigurationsResource, listOptions) + + _, err := c.Fake.Invokes(action, &admissionregistration.MutatingWebhookConfigurationList{}) + return err +} + +// Patch applies the patch and returns the patched mutatingWebhookConfiguration. +func (c *FakeMutatingWebhookConfigurations) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *admissionregistration.MutatingWebhookConfiguration, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootPatchSubresourceAction(mutatingwebhookconfigurationsResource, name, data, subresources...), &admissionregistration.MutatingWebhookConfiguration{}) + if obj == nil { + return nil, err + } + return obj.(*admissionregistration.MutatingWebhookConfiguration), err +} diff --git a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake/fake_validatingwebhookconfiguration.go b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake/fake_validatingwebhookconfiguration.go new file mode 100644 index 00000000000..a78bbfdd25c --- /dev/null +++ b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake/fake_validatingwebhookconfiguration.go @@ -0,0 +1,118 @@ +/* +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 fake + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" + admissionregistration "k8s.io/kubernetes/pkg/apis/admissionregistration" +) + +// FakeValidatingWebhookConfigurations implements ValidatingWebhookConfigurationInterface +type FakeValidatingWebhookConfigurations struct { + Fake *FakeAdmissionregistration +} + +var validatingwebhookconfigurationsResource = schema.GroupVersionResource{Group: "admissionregistration.k8s.io", Version: "", Resource: "validatingwebhookconfigurations"} + +var validatingwebhookconfigurationsKind = schema.GroupVersionKind{Group: "admissionregistration.k8s.io", Version: "", Kind: "ValidatingWebhookConfiguration"} + +// Get takes name of the validatingWebhookConfiguration, and returns the corresponding validatingWebhookConfiguration object, and an error if there is any. +func (c *FakeValidatingWebhookConfigurations) Get(name string, options v1.GetOptions) (result *admissionregistration.ValidatingWebhookConfiguration, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootGetAction(validatingwebhookconfigurationsResource, name), &admissionregistration.ValidatingWebhookConfiguration{}) + if obj == nil { + return nil, err + } + return obj.(*admissionregistration.ValidatingWebhookConfiguration), err +} + +// List takes label and field selectors, and returns the list of ValidatingWebhookConfigurations that match those selectors. +func (c *FakeValidatingWebhookConfigurations) List(opts v1.ListOptions) (result *admissionregistration.ValidatingWebhookConfigurationList, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootListAction(validatingwebhookconfigurationsResource, validatingwebhookconfigurationsKind, opts), &admissionregistration.ValidatingWebhookConfigurationList{}) + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &admissionregistration.ValidatingWebhookConfigurationList{} + for _, item := range obj.(*admissionregistration.ValidatingWebhookConfigurationList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested validatingWebhookConfigurations. +func (c *FakeValidatingWebhookConfigurations) Watch(opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewRootWatchAction(validatingwebhookconfigurationsResource, opts)) +} + +// Create takes the representation of a validatingWebhookConfiguration and creates it. Returns the server's representation of the validatingWebhookConfiguration, and an error, if there is any. +func (c *FakeValidatingWebhookConfigurations) Create(validatingWebhookConfiguration *admissionregistration.ValidatingWebhookConfiguration) (result *admissionregistration.ValidatingWebhookConfiguration, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootCreateAction(validatingwebhookconfigurationsResource, validatingWebhookConfiguration), &admissionregistration.ValidatingWebhookConfiguration{}) + if obj == nil { + return nil, err + } + return obj.(*admissionregistration.ValidatingWebhookConfiguration), err +} + +// Update takes the representation of a validatingWebhookConfiguration and updates it. Returns the server's representation of the validatingWebhookConfiguration, and an error, if there is any. +func (c *FakeValidatingWebhookConfigurations) Update(validatingWebhookConfiguration *admissionregistration.ValidatingWebhookConfiguration) (result *admissionregistration.ValidatingWebhookConfiguration, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateAction(validatingwebhookconfigurationsResource, validatingWebhookConfiguration), &admissionregistration.ValidatingWebhookConfiguration{}) + if obj == nil { + return nil, err + } + return obj.(*admissionregistration.ValidatingWebhookConfiguration), err +} + +// Delete takes name of the validatingWebhookConfiguration and deletes it. Returns an error if one occurs. +func (c *FakeValidatingWebhookConfigurations) Delete(name string, options *v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewRootDeleteAction(validatingwebhookconfigurationsResource, name), &admissionregistration.ValidatingWebhookConfiguration{}) + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeValidatingWebhookConfigurations) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + action := testing.NewRootDeleteCollectionAction(validatingwebhookconfigurationsResource, listOptions) + + _, err := c.Fake.Invokes(action, &admissionregistration.ValidatingWebhookConfigurationList{}) + return err +} + +// Patch applies the patch and returns the patched validatingWebhookConfiguration. +func (c *FakeValidatingWebhookConfigurations) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *admissionregistration.ValidatingWebhookConfiguration, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootPatchSubresourceAction(validatingwebhookconfigurationsResource, name, data, subresources...), &admissionregistration.ValidatingWebhookConfiguration{}) + if obj == nil { + return nil, err + } + return obj.(*admissionregistration.ValidatingWebhookConfiguration), err +} diff --git a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/generated_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/generated_expansion.go index 631817dea3f..06f4a44cf86 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/generated_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/generated_expansion.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -16,6 +16,8 @@ limitations under the License. package internalversion -type ExternalAdmissionHookConfigurationExpansion interface{} - type InitializerConfigurationExpansion interface{} + +type MutatingWebhookConfigurationExpansion interface{} + +type ValidatingWebhookConfigurationExpansion interface{} diff --git a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/initializerconfiguration.go b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/initializerconfiguration.go index 365bfdb4344..45e63241186 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/initializerconfiguration.go +++ b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/initializerconfiguration.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/mutatingwebhookconfiguration.go b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/mutatingwebhookconfiguration.go new file mode 100644 index 00000000000..06f3a5f0b38 --- /dev/null +++ b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/mutatingwebhookconfiguration.go @@ -0,0 +1,145 @@ +/* +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 internalversion + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" + admissionregistration "k8s.io/kubernetes/pkg/apis/admissionregistration" + scheme "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme" +) + +// MutatingWebhookConfigurationsGetter has a method to return a MutatingWebhookConfigurationInterface. +// A group's client should implement this interface. +type MutatingWebhookConfigurationsGetter interface { + MutatingWebhookConfigurations() MutatingWebhookConfigurationInterface +} + +// MutatingWebhookConfigurationInterface has methods to work with MutatingWebhookConfiguration resources. +type MutatingWebhookConfigurationInterface interface { + Create(*admissionregistration.MutatingWebhookConfiguration) (*admissionregistration.MutatingWebhookConfiguration, error) + Update(*admissionregistration.MutatingWebhookConfiguration) (*admissionregistration.MutatingWebhookConfiguration, error) + Delete(name string, options *v1.DeleteOptions) error + DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error + Get(name string, options v1.GetOptions) (*admissionregistration.MutatingWebhookConfiguration, error) + List(opts v1.ListOptions) (*admissionregistration.MutatingWebhookConfigurationList, error) + Watch(opts v1.ListOptions) (watch.Interface, error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *admissionregistration.MutatingWebhookConfiguration, err error) + MutatingWebhookConfigurationExpansion +} + +// mutatingWebhookConfigurations implements MutatingWebhookConfigurationInterface +type mutatingWebhookConfigurations struct { + client rest.Interface +} + +// newMutatingWebhookConfigurations returns a MutatingWebhookConfigurations +func newMutatingWebhookConfigurations(c *AdmissionregistrationClient) *mutatingWebhookConfigurations { + return &mutatingWebhookConfigurations{ + client: c.RESTClient(), + } +} + +// Get takes name of the mutatingWebhookConfiguration, and returns the corresponding mutatingWebhookConfiguration object, and an error if there is any. +func (c *mutatingWebhookConfigurations) Get(name string, options v1.GetOptions) (result *admissionregistration.MutatingWebhookConfiguration, err error) { + result = &admissionregistration.MutatingWebhookConfiguration{} + err = c.client.Get(). + Resource("mutatingwebhookconfigurations"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of MutatingWebhookConfigurations that match those selectors. +func (c *mutatingWebhookConfigurations) List(opts v1.ListOptions) (result *admissionregistration.MutatingWebhookConfigurationList, err error) { + result = &admissionregistration.MutatingWebhookConfigurationList{} + err = c.client.Get(). + Resource("mutatingwebhookconfigurations"). + VersionedParams(&opts, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested mutatingWebhookConfigurations. +func (c *mutatingWebhookConfigurations) Watch(opts v1.ListOptions) (watch.Interface, error) { + opts.Watch = true + return c.client.Get(). + Resource("mutatingwebhookconfigurations"). + VersionedParams(&opts, scheme.ParameterCodec). + Watch() +} + +// Create takes the representation of a mutatingWebhookConfiguration and creates it. Returns the server's representation of the mutatingWebhookConfiguration, and an error, if there is any. +func (c *mutatingWebhookConfigurations) Create(mutatingWebhookConfiguration *admissionregistration.MutatingWebhookConfiguration) (result *admissionregistration.MutatingWebhookConfiguration, err error) { + result = &admissionregistration.MutatingWebhookConfiguration{} + err = c.client.Post(). + Resource("mutatingwebhookconfigurations"). + Body(mutatingWebhookConfiguration). + Do(). + Into(result) + return +} + +// Update takes the representation of a mutatingWebhookConfiguration and updates it. Returns the server's representation of the mutatingWebhookConfiguration, and an error, if there is any. +func (c *mutatingWebhookConfigurations) Update(mutatingWebhookConfiguration *admissionregistration.MutatingWebhookConfiguration) (result *admissionregistration.MutatingWebhookConfiguration, err error) { + result = &admissionregistration.MutatingWebhookConfiguration{} + err = c.client.Put(). + Resource("mutatingwebhookconfigurations"). + Name(mutatingWebhookConfiguration.Name). + Body(mutatingWebhookConfiguration). + Do(). + Into(result) + return +} + +// Delete takes name of the mutatingWebhookConfiguration and deletes it. Returns an error if one occurs. +func (c *mutatingWebhookConfigurations) Delete(name string, options *v1.DeleteOptions) error { + return c.client.Delete(). + Resource("mutatingwebhookconfigurations"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *mutatingWebhookConfigurations) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + return c.client.Delete(). + Resource("mutatingwebhookconfigurations"). + VersionedParams(&listOptions, scheme.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Patch applies the patch and returns the patched mutatingWebhookConfiguration. +func (c *mutatingWebhookConfigurations) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *admissionregistration.MutatingWebhookConfiguration, err error) { + result = &admissionregistration.MutatingWebhookConfiguration{} + err = c.client.Patch(pt). + Resource("mutatingwebhookconfigurations"). + SubResource(subresources...). + Name(name). + Body(data). + Do(). + Into(result) + return +} diff --git a/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/validatingwebhookconfiguration.go b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/validatingwebhookconfiguration.go new file mode 100644 index 00000000000..9a57c9a8174 --- /dev/null +++ b/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/validatingwebhookconfiguration.go @@ -0,0 +1,145 @@ +/* +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 internalversion + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" + admissionregistration "k8s.io/kubernetes/pkg/apis/admissionregistration" + scheme "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme" +) + +// ValidatingWebhookConfigurationsGetter has a method to return a ValidatingWebhookConfigurationInterface. +// A group's client should implement this interface. +type ValidatingWebhookConfigurationsGetter interface { + ValidatingWebhookConfigurations() ValidatingWebhookConfigurationInterface +} + +// ValidatingWebhookConfigurationInterface has methods to work with ValidatingWebhookConfiguration resources. +type ValidatingWebhookConfigurationInterface interface { + Create(*admissionregistration.ValidatingWebhookConfiguration) (*admissionregistration.ValidatingWebhookConfiguration, error) + Update(*admissionregistration.ValidatingWebhookConfiguration) (*admissionregistration.ValidatingWebhookConfiguration, error) + Delete(name string, options *v1.DeleteOptions) error + DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error + Get(name string, options v1.GetOptions) (*admissionregistration.ValidatingWebhookConfiguration, error) + List(opts v1.ListOptions) (*admissionregistration.ValidatingWebhookConfigurationList, error) + Watch(opts v1.ListOptions) (watch.Interface, error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *admissionregistration.ValidatingWebhookConfiguration, err error) + ValidatingWebhookConfigurationExpansion +} + +// validatingWebhookConfigurations implements ValidatingWebhookConfigurationInterface +type validatingWebhookConfigurations struct { + client rest.Interface +} + +// newValidatingWebhookConfigurations returns a ValidatingWebhookConfigurations +func newValidatingWebhookConfigurations(c *AdmissionregistrationClient) *validatingWebhookConfigurations { + return &validatingWebhookConfigurations{ + client: c.RESTClient(), + } +} + +// Get takes name of the validatingWebhookConfiguration, and returns the corresponding validatingWebhookConfiguration object, and an error if there is any. +func (c *validatingWebhookConfigurations) Get(name string, options v1.GetOptions) (result *admissionregistration.ValidatingWebhookConfiguration, err error) { + result = &admissionregistration.ValidatingWebhookConfiguration{} + err = c.client.Get(). + Resource("validatingwebhookconfigurations"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ValidatingWebhookConfigurations that match those selectors. +func (c *validatingWebhookConfigurations) List(opts v1.ListOptions) (result *admissionregistration.ValidatingWebhookConfigurationList, err error) { + result = &admissionregistration.ValidatingWebhookConfigurationList{} + err = c.client.Get(). + Resource("validatingwebhookconfigurations"). + VersionedParams(&opts, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested validatingWebhookConfigurations. +func (c *validatingWebhookConfigurations) Watch(opts v1.ListOptions) (watch.Interface, error) { + opts.Watch = true + return c.client.Get(). + Resource("validatingwebhookconfigurations"). + VersionedParams(&opts, scheme.ParameterCodec). + Watch() +} + +// Create takes the representation of a validatingWebhookConfiguration and creates it. Returns the server's representation of the validatingWebhookConfiguration, and an error, if there is any. +func (c *validatingWebhookConfigurations) Create(validatingWebhookConfiguration *admissionregistration.ValidatingWebhookConfiguration) (result *admissionregistration.ValidatingWebhookConfiguration, err error) { + result = &admissionregistration.ValidatingWebhookConfiguration{} + err = c.client.Post(). + Resource("validatingwebhookconfigurations"). + Body(validatingWebhookConfiguration). + Do(). + Into(result) + return +} + +// Update takes the representation of a validatingWebhookConfiguration and updates it. Returns the server's representation of the validatingWebhookConfiguration, and an error, if there is any. +func (c *validatingWebhookConfigurations) Update(validatingWebhookConfiguration *admissionregistration.ValidatingWebhookConfiguration) (result *admissionregistration.ValidatingWebhookConfiguration, err error) { + result = &admissionregistration.ValidatingWebhookConfiguration{} + err = c.client.Put(). + Resource("validatingwebhookconfigurations"). + Name(validatingWebhookConfiguration.Name). + Body(validatingWebhookConfiguration). + Do(). + Into(result) + return +} + +// Delete takes name of the validatingWebhookConfiguration and deletes it. Returns an error if one occurs. +func (c *validatingWebhookConfigurations) Delete(name string, options *v1.DeleteOptions) error { + return c.client.Delete(). + Resource("validatingwebhookconfigurations"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *validatingWebhookConfigurations) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + return c.client.Delete(). + Resource("validatingwebhookconfigurations"). + VersionedParams(&listOptions, scheme.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Patch applies the patch and returns the patched validatingWebhookConfiguration. +func (c *validatingWebhookConfigurations) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *admissionregistration.ValidatingWebhookConfiguration, err error) { + result = &admissionregistration.ValidatingWebhookConfiguration{} + err = c.client.Patch(pt). + Resource("validatingwebhookconfigurations"). + SubResource(subresources...). + Name(name). + Body(data). + Do(). + Into(result) + return +} diff --git a/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/BUILD b/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/BUILD index 9435fcd5131..e3fad87bee9 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/BUILD +++ b/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/BUILD @@ -17,7 +17,7 @@ go_library( importpath = "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion", deps = [ "//pkg/apis/apps:go_default_library", - "//pkg/apis/extensions:go_default_library", + "//pkg/apis/autoscaling:go_default_library", "//pkg/client/clientset_generated/internalclientset/scheme:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", diff --git a/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/apps_client.go b/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/apps_client.go index 718e82c478c..184ebd38968 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/apps_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/apps_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/controllerrevision.go b/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/controllerrevision.go index 1cec65d626d..120a56cfd36 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/controllerrevision.go +++ b/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/controllerrevision.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/doc.go b/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/doc.go index 3adf06d8934..8615019757d 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/fake/BUILD b/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/fake/BUILD index b124ea57859..ecee8ae0d4a 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/fake/BUILD +++ b/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/fake/BUILD @@ -16,7 +16,7 @@ go_library( importpath = "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/fake", deps = [ "//pkg/apis/apps:go_default_library", - "//pkg/apis/extensions:go_default_library", + "//pkg/apis/autoscaling:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/apps/internalversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", diff --git a/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/fake/doc.go b/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/fake/doc.go index c58fac35e4b..63e2c8a0821 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/fake/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/fake/fake_apps_client.go b/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/fake/fake_apps_client.go index e677f9d283a..52151df20fa 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/fake/fake_apps_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/fake/fake_apps_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/fake/fake_controllerrevision.go b/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/fake/fake_controllerrevision.go index f3ea25bb53d..9ca9e8cab23 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/fake/fake_controllerrevision.go +++ b/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/fake/fake_controllerrevision.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/fake/fake_statefulset.go b/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/fake/fake_statefulset.go index df41c42aa99..612832c9395 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/fake/fake_statefulset.go +++ b/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/fake/fake_statefulset.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -24,7 +24,7 @@ import ( watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" apps "k8s.io/kubernetes/pkg/apis/apps" - extensions "k8s.io/kubernetes/pkg/apis/extensions" + autoscaling "k8s.io/kubernetes/pkg/apis/autoscaling" ) // FakeStatefulSets implements StatefulSetInterface @@ -139,23 +139,23 @@ func (c *FakeStatefulSets) Patch(name string, pt types.PatchType, data []byte, s } // GetScale takes name of the statefulSet, and returns the corresponding scale object, and an error if there is any. -func (c *FakeStatefulSets) GetScale(statefulSetName string, options v1.GetOptions) (result *extensions.Scale, err error) { +func (c *FakeStatefulSets) GetScale(statefulSetName string, options v1.GetOptions) (result *autoscaling.Scale, err error) { obj, err := c.Fake. - Invokes(testing.NewGetSubresourceAction(statefulsetsResource, c.ns, "scale", statefulSetName), &extensions.Scale{}) + Invokes(testing.NewGetSubresourceAction(statefulsetsResource, c.ns, "scale", statefulSetName), &autoscaling.Scale{}) if obj == nil { return nil, err } - return obj.(*extensions.Scale), err + return obj.(*autoscaling.Scale), err } // UpdateScale takes the representation of a scale and updates it. Returns the server's representation of the scale, and an error, if there is any. -func (c *FakeStatefulSets) UpdateScale(statefulSetName string, scale *extensions.Scale) (result *extensions.Scale, err error) { +func (c *FakeStatefulSets) UpdateScale(statefulSetName string, scale *autoscaling.Scale) (result *autoscaling.Scale, err error) { obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(statefulsetsResource, "scale", c.ns, scale), &extensions.Scale{}) + Invokes(testing.NewUpdateSubresourceAction(statefulsetsResource, "scale", c.ns, scale), &autoscaling.Scale{}) if obj == nil { return nil, err } - return obj.(*extensions.Scale), err + return obj.(*autoscaling.Scale), err } diff --git a/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/generated_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/generated_expansion.go index 3b3f37336a7..7fd67b1f712 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/generated_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/generated_expansion.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/statefulset.go b/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/statefulset.go index 85ee4bd86aa..cb496b53a89 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/statefulset.go +++ b/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/statefulset.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,7 +22,7 @@ import ( watch "k8s.io/apimachinery/pkg/watch" rest "k8s.io/client-go/rest" apps "k8s.io/kubernetes/pkg/apis/apps" - extensions "k8s.io/kubernetes/pkg/apis/extensions" + autoscaling "k8s.io/kubernetes/pkg/apis/autoscaling" scheme "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme" ) @@ -43,8 +43,8 @@ type StatefulSetInterface interface { List(opts v1.ListOptions) (*apps.StatefulSetList, error) Watch(opts v1.ListOptions) (watch.Interface, error) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *apps.StatefulSet, err error) - GetScale(statefulSetName string, options v1.GetOptions) (*extensions.Scale, error) - UpdateScale(statefulSetName string, scale *extensions.Scale) (*extensions.Scale, error) + GetScale(statefulSetName string, options v1.GetOptions) (*autoscaling.Scale, error) + UpdateScale(statefulSetName string, scale *autoscaling.Scale) (*autoscaling.Scale, error) StatefulSetExpansion } @@ -175,9 +175,9 @@ func (c *statefulSets) Patch(name string, pt types.PatchType, data []byte, subre return } -// GetScale takes name of the statefulSet, and returns the corresponding extensions.Scale object, and an error if there is any. -func (c *statefulSets) GetScale(statefulSetName string, options v1.GetOptions) (result *extensions.Scale, err error) { - result = &extensions.Scale{} +// GetScale takes name of the statefulSet, and returns the corresponding autoscaling.Scale object, and an error if there is any. +func (c *statefulSets) GetScale(statefulSetName string, options v1.GetOptions) (result *autoscaling.Scale, err error) { + result = &autoscaling.Scale{} err = c.client.Get(). Namespace(c.ns). Resource("statefulsets"). @@ -190,8 +190,8 @@ func (c *statefulSets) GetScale(statefulSetName string, options v1.GetOptions) ( } // UpdateScale takes the top resource name and the representation of a scale and updates it. Returns the server's representation of the scale, and an error, if there is any. -func (c *statefulSets) UpdateScale(statefulSetName string, scale *extensions.Scale) (result *extensions.Scale, err error) { - result = &extensions.Scale{} +func (c *statefulSets) UpdateScale(statefulSetName string, scale *autoscaling.Scale) (result *autoscaling.Scale, err error) { + result = &autoscaling.Scale{} err = c.client.Put(). Namespace(c.ns). Resource("statefulsets"). diff --git a/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/authentication_client.go b/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/authentication_client.go index 709621a10da..68c3efadc47 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/authentication_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/authentication_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/doc.go b/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/doc.go index 3adf06d8934..8615019757d 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/fake/doc.go b/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/fake/doc.go index c58fac35e4b..63e2c8a0821 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/fake/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/fake/fake_authentication_client.go b/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/fake/fake_authentication_client.go index ec72845a061..d92fa512a4f 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/fake/fake_authentication_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/fake/fake_authentication_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/fake/fake_tokenreview.go b/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/fake/fake_tokenreview.go index f88556a649b..1854aed7e88 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/fake/fake_tokenreview.go +++ b/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/fake/fake_tokenreview.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/generated_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/generated_expansion.go index b0f76eeede5..36261d29dad 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/generated_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/generated_expansion.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/tokenreview.go b/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/tokenreview.go index c0880edb874..16cbd1aaf6d 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/tokenreview.go +++ b/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/tokenreview.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/authorization_client.go b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/authorization_client.go index a5aeb4831ec..3da3b1f5277 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/authorization_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/authorization_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/doc.go b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/doc.go index 3adf06d8934..8615019757d 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake/doc.go b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake/doc.go index c58fac35e4b..63e2c8a0821 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake/fake_authorization_client.go b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake/fake_authorization_client.go index 65689a76490..d5be9aba1da 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake/fake_authorization_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake/fake_authorization_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake/fake_localsubjectaccessreview.go b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake/fake_localsubjectaccessreview.go index ddc53b8dd6c..e7f649e8d4c 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake/fake_localsubjectaccessreview.go +++ b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake/fake_localsubjectaccessreview.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake/fake_selfsubjectaccessreview.go b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake/fake_selfsubjectaccessreview.go index 7af01048ec7..4f5f5269277 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake/fake_selfsubjectaccessreview.go +++ b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake/fake_selfsubjectaccessreview.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake/fake_selfsubjectrulesreview.go b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake/fake_selfsubjectrulesreview.go index 59841af9dbd..4c1f7f02858 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake/fake_selfsubjectrulesreview.go +++ b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake/fake_selfsubjectrulesreview.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake/fake_subjectaccessreview.go b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake/fake_subjectaccessreview.go index e0a92c737a8..e799e0e7ee1 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake/fake_subjectaccessreview.go +++ b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake/fake_subjectaccessreview.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/generated_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/generated_expansion.go index b0f76eeede5..36261d29dad 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/generated_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/generated_expansion.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/localsubjectaccessreview.go b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/localsubjectaccessreview.go index bf02fad768f..5a2916aa82b 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/localsubjectaccessreview.go +++ b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/localsubjectaccessreview.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/selfsubjectaccessreview.go b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/selfsubjectaccessreview.go index cb19357c487..dd3120e199c 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/selfsubjectaccessreview.go +++ b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/selfsubjectaccessreview.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/selfsubjectrulesreview.go b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/selfsubjectrulesreview.go index de80117b57b..89015541d51 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/selfsubjectrulesreview.go +++ b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/selfsubjectrulesreview.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/subjectaccessreview.go b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/subjectaccessreview.go index 456b469b196..67a2d9304ef 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/subjectaccessreview.go +++ b/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/subjectaccessreview.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/autoscaling_client.go b/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/autoscaling_client.go index e0752ab5ff5..4f7d5ab377c 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/autoscaling_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/autoscaling_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/doc.go b/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/doc.go index 3adf06d8934..8615019757d 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/fake/doc.go b/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/fake/doc.go index c58fac35e4b..63e2c8a0821 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/fake/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/fake/fake_autoscaling_client.go b/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/fake/fake_autoscaling_client.go index a6098274bcd..1da546a7d0f 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/fake/fake_autoscaling_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/fake/fake_autoscaling_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/fake/fake_horizontalpodautoscaler.go b/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/fake/fake_horizontalpodautoscaler.go index 519a50971e7..81afddd06d3 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/fake/fake_horizontalpodautoscaler.go +++ b/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/fake/fake_horizontalpodautoscaler.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/generated_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/generated_expansion.go index bf7296c51a1..413f0db67f9 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/generated_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/generated_expansion.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/horizontalpodautoscaler.go b/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/horizontalpodautoscaler.go index 4fb58f612fb..9657f0408b1 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/horizontalpodautoscaler.go +++ b/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/horizontalpodautoscaler.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/batch_client.go b/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/batch_client.go index 97be023a7ba..152ab445681 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/batch_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/batch_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/cronjob.go b/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/cronjob.go index fb0737ef811..12ef15132bb 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/cronjob.go +++ b/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/cronjob.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/doc.go b/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/doc.go index 3adf06d8934..8615019757d 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/fake/doc.go b/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/fake/doc.go index c58fac35e4b..63e2c8a0821 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/fake/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/fake/fake_batch_client.go b/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/fake/fake_batch_client.go index 34e3f16e40d..770887f60f6 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/fake/fake_batch_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/fake/fake_batch_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/fake/fake_cronjob.go b/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/fake/fake_cronjob.go index 8da7ded25db..6b91892c1ee 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/fake/fake_cronjob.go +++ b/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/fake/fake_cronjob.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/fake/fake_job.go b/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/fake/fake_job.go index 6e10cfd1384..154e2932932 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/fake/fake_job.go +++ b/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/fake/fake_job.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/generated_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/generated_expansion.go index 28a62926cd0..9fbcb660708 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/generated_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/generated_expansion.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/job.go b/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/job.go index 8098046f4f5..a1a97718d51 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/job.go +++ b/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/job.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/certificates_client.go b/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/certificates_client.go index 53e6142d0c0..4ccb172fa74 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/certificates_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/certificates_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/certificatesigningrequest.go b/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/certificatesigningrequest.go index 0dcbe23f8b2..51232f9f5c6 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/certificatesigningrequest.go +++ b/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/certificatesigningrequest.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/doc.go b/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/doc.go index 3adf06d8934..8615019757d 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/fake/doc.go b/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/fake/doc.go index c58fac35e4b..63e2c8a0821 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/fake/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/fake/fake_certificates_client.go b/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/fake/fake_certificates_client.go index e38333579cf..f3a423f9609 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/fake/fake_certificates_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/fake/fake_certificates_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/fake/fake_certificatesigningrequest.go b/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/fake/fake_certificatesigningrequest.go index ad1379ddb39..d02bc624b74 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/fake/fake_certificatesigningrequest.go +++ b/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/fake/fake_certificatesigningrequest.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/generated_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/generated_expansion.go index b0f76eeede5..36261d29dad 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/generated_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/generated_expansion.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/BUILD b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/BUILD index 8a2308628a5..ad0e9695a54 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/BUILD +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/BUILD @@ -35,10 +35,11 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/ref:go_default_library", - "//pkg/api/v1:go_default_library", - "//pkg/apis/extensions:go_default_library", + "//pkg/apis/autoscaling:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/v1:go_default_library", "//pkg/client/clientset_generated/internalclientset/scheme:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/componentstatus.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/componentstatus.go index dfd6969a91f..290ee6ebba6 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/componentstatus.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/componentstatus.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,7 +21,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" rest "k8s.io/client-go/rest" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" scheme "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme" ) @@ -33,14 +33,14 @@ type ComponentStatusesGetter interface { // ComponentStatusInterface has methods to work with ComponentStatus resources. type ComponentStatusInterface interface { - Create(*api.ComponentStatus) (*api.ComponentStatus, error) - Update(*api.ComponentStatus) (*api.ComponentStatus, error) + Create(*core.ComponentStatus) (*core.ComponentStatus, error) + Update(*core.ComponentStatus) (*core.ComponentStatus, error) Delete(name string, options *v1.DeleteOptions) error DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*api.ComponentStatus, error) - List(opts v1.ListOptions) (*api.ComponentStatusList, error) + Get(name string, options v1.GetOptions) (*core.ComponentStatus, error) + List(opts v1.ListOptions) (*core.ComponentStatusList, error) Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.ComponentStatus, err error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.ComponentStatus, err error) ComponentStatusExpansion } @@ -57,8 +57,8 @@ func newComponentStatuses(c *CoreClient) *componentStatuses { } // Get takes name of the componentStatus, and returns the corresponding componentStatus object, and an error if there is any. -func (c *componentStatuses) Get(name string, options v1.GetOptions) (result *api.ComponentStatus, err error) { - result = &api.ComponentStatus{} +func (c *componentStatuses) Get(name string, options v1.GetOptions) (result *core.ComponentStatus, err error) { + result = &core.ComponentStatus{} err = c.client.Get(). Resource("componentstatuses"). Name(name). @@ -69,8 +69,8 @@ func (c *componentStatuses) Get(name string, options v1.GetOptions) (result *api } // List takes label and field selectors, and returns the list of ComponentStatuses that match those selectors. -func (c *componentStatuses) List(opts v1.ListOptions) (result *api.ComponentStatusList, err error) { - result = &api.ComponentStatusList{} +func (c *componentStatuses) List(opts v1.ListOptions) (result *core.ComponentStatusList, err error) { + result = &core.ComponentStatusList{} err = c.client.Get(). Resource("componentstatuses"). VersionedParams(&opts, scheme.ParameterCodec). @@ -89,8 +89,8 @@ func (c *componentStatuses) Watch(opts v1.ListOptions) (watch.Interface, error) } // Create takes the representation of a componentStatus and creates it. Returns the server's representation of the componentStatus, and an error, if there is any. -func (c *componentStatuses) Create(componentStatus *api.ComponentStatus) (result *api.ComponentStatus, err error) { - result = &api.ComponentStatus{} +func (c *componentStatuses) Create(componentStatus *core.ComponentStatus) (result *core.ComponentStatus, err error) { + result = &core.ComponentStatus{} err = c.client.Post(). Resource("componentstatuses"). Body(componentStatus). @@ -100,8 +100,8 @@ func (c *componentStatuses) Create(componentStatus *api.ComponentStatus) (result } // Update takes the representation of a componentStatus and updates it. Returns the server's representation of the componentStatus, and an error, if there is any. -func (c *componentStatuses) Update(componentStatus *api.ComponentStatus) (result *api.ComponentStatus, err error) { - result = &api.ComponentStatus{} +func (c *componentStatuses) Update(componentStatus *core.ComponentStatus) (result *core.ComponentStatus, err error) { + result = &core.ComponentStatus{} err = c.client.Put(). Resource("componentstatuses"). Name(componentStatus.Name). @@ -132,8 +132,8 @@ func (c *componentStatuses) DeleteCollection(options *v1.DeleteOptions, listOpti } // Patch applies the patch and returns the patched componentStatus. -func (c *componentStatuses) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.ComponentStatus, err error) { - result = &api.ComponentStatus{} +func (c *componentStatuses) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.ComponentStatus, err error) { + result = &core.ComponentStatus{} err = c.client.Patch(pt). Resource("componentstatuses"). SubResource(subresources...). diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/configmap.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/configmap.go index 53ed9fed80d..84f39b2ffb0 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/configmap.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/configmap.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,7 +21,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" rest "k8s.io/client-go/rest" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" scheme "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme" ) @@ -33,14 +33,14 @@ type ConfigMapsGetter interface { // ConfigMapInterface has methods to work with ConfigMap resources. type ConfigMapInterface interface { - Create(*api.ConfigMap) (*api.ConfigMap, error) - Update(*api.ConfigMap) (*api.ConfigMap, error) + Create(*core.ConfigMap) (*core.ConfigMap, error) + Update(*core.ConfigMap) (*core.ConfigMap, error) Delete(name string, options *v1.DeleteOptions) error DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*api.ConfigMap, error) - List(opts v1.ListOptions) (*api.ConfigMapList, error) + Get(name string, options v1.GetOptions) (*core.ConfigMap, error) + List(opts v1.ListOptions) (*core.ConfigMapList, error) Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.ConfigMap, err error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.ConfigMap, err error) ConfigMapExpansion } @@ -59,8 +59,8 @@ func newConfigMaps(c *CoreClient, namespace string) *configMaps { } // Get takes name of the configMap, and returns the corresponding configMap object, and an error if there is any. -func (c *configMaps) Get(name string, options v1.GetOptions) (result *api.ConfigMap, err error) { - result = &api.ConfigMap{} +func (c *configMaps) Get(name string, options v1.GetOptions) (result *core.ConfigMap, err error) { + result = &core.ConfigMap{} err = c.client.Get(). Namespace(c.ns). Resource("configmaps"). @@ -72,8 +72,8 @@ func (c *configMaps) Get(name string, options v1.GetOptions) (result *api.Config } // List takes label and field selectors, and returns the list of ConfigMaps that match those selectors. -func (c *configMaps) List(opts v1.ListOptions) (result *api.ConfigMapList, err error) { - result = &api.ConfigMapList{} +func (c *configMaps) List(opts v1.ListOptions) (result *core.ConfigMapList, err error) { + result = &core.ConfigMapList{} err = c.client.Get(). Namespace(c.ns). Resource("configmaps"). @@ -94,8 +94,8 @@ func (c *configMaps) Watch(opts v1.ListOptions) (watch.Interface, error) { } // Create takes the representation of a configMap and creates it. Returns the server's representation of the configMap, and an error, if there is any. -func (c *configMaps) Create(configMap *api.ConfigMap) (result *api.ConfigMap, err error) { - result = &api.ConfigMap{} +func (c *configMaps) Create(configMap *core.ConfigMap) (result *core.ConfigMap, err error) { + result = &core.ConfigMap{} err = c.client.Post(). Namespace(c.ns). Resource("configmaps"). @@ -106,8 +106,8 @@ func (c *configMaps) Create(configMap *api.ConfigMap) (result *api.ConfigMap, er } // Update takes the representation of a configMap and updates it. Returns the server's representation of the configMap, and an error, if there is any. -func (c *configMaps) Update(configMap *api.ConfigMap) (result *api.ConfigMap, err error) { - result = &api.ConfigMap{} +func (c *configMaps) Update(configMap *core.ConfigMap) (result *core.ConfigMap, err error) { + result = &core.ConfigMap{} err = c.client.Put(). Namespace(c.ns). Resource("configmaps"). @@ -141,8 +141,8 @@ func (c *configMaps) DeleteCollection(options *v1.DeleteOptions, listOptions v1. } // Patch applies the patch and returns the patched configMap. -func (c *configMaps) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.ConfigMap, err error) { - result = &api.ConfigMap{} +func (c *configMaps) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.ConfigMap, err error) { + result = &core.ConfigMap{} err = c.client.Patch(pt). Namespace(c.ns). Resource("configmaps"). diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/core_client.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/core_client.go index 10313c3cb62..593b8872abf 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/core_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/core_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/doc.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/doc.go index 3adf06d8934..8615019757d 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/endpoints.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/endpoints.go index 39fe3d44c1a..4f1a2b76776 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/endpoints.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/endpoints.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,7 +21,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" rest "k8s.io/client-go/rest" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" scheme "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme" ) @@ -33,14 +33,14 @@ type EndpointsGetter interface { // EndpointsInterface has methods to work with Endpoints resources. type EndpointsInterface interface { - Create(*api.Endpoints) (*api.Endpoints, error) - Update(*api.Endpoints) (*api.Endpoints, error) + Create(*core.Endpoints) (*core.Endpoints, error) + Update(*core.Endpoints) (*core.Endpoints, error) Delete(name string, options *v1.DeleteOptions) error DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*api.Endpoints, error) - List(opts v1.ListOptions) (*api.EndpointsList, error) + Get(name string, options v1.GetOptions) (*core.Endpoints, error) + List(opts v1.ListOptions) (*core.EndpointsList, error) Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.Endpoints, err error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.Endpoints, err error) EndpointsExpansion } @@ -59,8 +59,8 @@ func newEndpoints(c *CoreClient, namespace string) *endpoints { } // Get takes name of the endpoints, and returns the corresponding endpoints object, and an error if there is any. -func (c *endpoints) Get(name string, options v1.GetOptions) (result *api.Endpoints, err error) { - result = &api.Endpoints{} +func (c *endpoints) Get(name string, options v1.GetOptions) (result *core.Endpoints, err error) { + result = &core.Endpoints{} err = c.client.Get(). Namespace(c.ns). Resource("endpoints"). @@ -72,8 +72,8 @@ func (c *endpoints) Get(name string, options v1.GetOptions) (result *api.Endpoin } // List takes label and field selectors, and returns the list of Endpoints that match those selectors. -func (c *endpoints) List(opts v1.ListOptions) (result *api.EndpointsList, err error) { - result = &api.EndpointsList{} +func (c *endpoints) List(opts v1.ListOptions) (result *core.EndpointsList, err error) { + result = &core.EndpointsList{} err = c.client.Get(). Namespace(c.ns). Resource("endpoints"). @@ -94,8 +94,8 @@ func (c *endpoints) Watch(opts v1.ListOptions) (watch.Interface, error) { } // Create takes the representation of a endpoints and creates it. Returns the server's representation of the endpoints, and an error, if there is any. -func (c *endpoints) Create(endpoints *api.Endpoints) (result *api.Endpoints, err error) { - result = &api.Endpoints{} +func (c *endpoints) Create(endpoints *core.Endpoints) (result *core.Endpoints, err error) { + result = &core.Endpoints{} err = c.client.Post(). Namespace(c.ns). Resource("endpoints"). @@ -106,8 +106,8 @@ func (c *endpoints) Create(endpoints *api.Endpoints) (result *api.Endpoints, err } // Update takes the representation of a endpoints and updates it. Returns the server's representation of the endpoints, and an error, if there is any. -func (c *endpoints) Update(endpoints *api.Endpoints) (result *api.Endpoints, err error) { - result = &api.Endpoints{} +func (c *endpoints) Update(endpoints *core.Endpoints) (result *core.Endpoints, err error) { + result = &core.Endpoints{} err = c.client.Put(). Namespace(c.ns). Resource("endpoints"). @@ -141,8 +141,8 @@ func (c *endpoints) DeleteCollection(options *v1.DeleteOptions, listOptions v1.L } // Patch applies the patch and returns the patched endpoints. -func (c *endpoints) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.Endpoints, err error) { - result = &api.Endpoints{} +func (c *endpoints) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.Endpoints, err error) { + result = &core.Endpoints{} err = c.client.Patch(pt). Namespace(c.ns). Resource("endpoints"). diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/event.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/event.go index 143099b6706..0d7e427a295 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/event.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/event.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,7 +21,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" rest "k8s.io/client-go/rest" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" scheme "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme" ) @@ -33,14 +33,14 @@ type EventsGetter interface { // EventInterface has methods to work with Event resources. type EventInterface interface { - Create(*api.Event) (*api.Event, error) - Update(*api.Event) (*api.Event, error) + Create(*core.Event) (*core.Event, error) + Update(*core.Event) (*core.Event, error) Delete(name string, options *v1.DeleteOptions) error DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*api.Event, error) - List(opts v1.ListOptions) (*api.EventList, error) + Get(name string, options v1.GetOptions) (*core.Event, error) + List(opts v1.ListOptions) (*core.EventList, error) Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.Event, err error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.Event, err error) EventExpansion } @@ -59,8 +59,8 @@ func newEvents(c *CoreClient, namespace string) *events { } // Get takes name of the event, and returns the corresponding event object, and an error if there is any. -func (c *events) Get(name string, options v1.GetOptions) (result *api.Event, err error) { - result = &api.Event{} +func (c *events) Get(name string, options v1.GetOptions) (result *core.Event, err error) { + result = &core.Event{} err = c.client.Get(). Namespace(c.ns). Resource("events"). @@ -72,8 +72,8 @@ func (c *events) Get(name string, options v1.GetOptions) (result *api.Event, err } // List takes label and field selectors, and returns the list of Events that match those selectors. -func (c *events) List(opts v1.ListOptions) (result *api.EventList, err error) { - result = &api.EventList{} +func (c *events) List(opts v1.ListOptions) (result *core.EventList, err error) { + result = &core.EventList{} err = c.client.Get(). Namespace(c.ns). Resource("events"). @@ -94,8 +94,8 @@ func (c *events) Watch(opts v1.ListOptions) (watch.Interface, error) { } // Create takes the representation of a event and creates it. Returns the server's representation of the event, and an error, if there is any. -func (c *events) Create(event *api.Event) (result *api.Event, err error) { - result = &api.Event{} +func (c *events) Create(event *core.Event) (result *core.Event, err error) { + result = &core.Event{} err = c.client.Post(). Namespace(c.ns). Resource("events"). @@ -106,8 +106,8 @@ func (c *events) Create(event *api.Event) (result *api.Event, err error) { } // Update takes the representation of a event and updates it. Returns the server's representation of the event, and an error, if there is any. -func (c *events) Update(event *api.Event) (result *api.Event, err error) { - result = &api.Event{} +func (c *events) Update(event *core.Event) (result *core.Event, err error) { + result = &core.Event{} err = c.client.Put(). Namespace(c.ns). Resource("events"). @@ -141,8 +141,8 @@ func (c *events) DeleteCollection(options *v1.DeleteOptions, listOptions v1.List } // Patch applies the patch and returns the patched event. -func (c *events) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.Event, err error) { - result = &api.Event{} +func (c *events) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.Event, err error) { + result = &core.Event{} err = c.client.Patch(pt). Namespace(c.ns). Resource("events"). diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/event_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/event_expansion.go index d9ac64fdb63..5f1ebb89cc9 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/event_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/event_expansion.go @@ -24,9 +24,9 @@ import ( "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/ref" - k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" + api "k8s.io/kubernetes/pkg/apis/core" + k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" ) // The EventExpansion interface allows manually adding extra methods to the EventInterface. @@ -155,7 +155,7 @@ type EventSinkImpl struct { func (e *EventSinkImpl) Create(event *v1.Event) (*v1.Event, error) { internalEvent := &api.Event{} - err := k8s_api_v1.Convert_v1_Event_To_api_Event(event, internalEvent, nil) + err := k8s_api_v1.Convert_v1_Event_To_core_Event(event, internalEvent, nil) if err != nil { return nil, err } @@ -168,7 +168,7 @@ func (e *EventSinkImpl) Create(event *v1.Event) (*v1.Event, error) { func (e *EventSinkImpl) Update(event *v1.Event) (*v1.Event, error) { internalEvent := &api.Event{} - err := k8s_api_v1.Convert_v1_Event_To_api_Event(event, internalEvent, nil) + err := k8s_api_v1.Convert_v1_Event_To_core_Event(event, internalEvent, nil) if err != nil { return nil, err } @@ -181,7 +181,7 @@ func (e *EventSinkImpl) Update(event *v1.Event) (*v1.Event, error) { func (e *EventSinkImpl) Patch(event *v1.Event, data []byte) (*v1.Event, error) { internalEvent := &api.Event{} - err := k8s_api_v1.Convert_v1_Event_To_api_Event(event, internalEvent, nil) + err := k8s_api_v1.Convert_v1_Event_To_core_Event(event, internalEvent, nil) if err != nil { return nil, err } @@ -190,7 +190,7 @@ func (e *EventSinkImpl) Patch(event *v1.Event, data []byte) (*v1.Event, error) { return nil, err } externalEvent := &v1.Event{} - err = k8s_api_v1.Convert_api_Event_To_v1_Event(internalEvent, externalEvent, nil) + err = k8s_api_v1.Convert_core_Event_To_v1_Event(internalEvent, externalEvent, nil) if err != nil { // Patch succeeded, no need to report the failed conversion return event, nil diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/BUILD b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/BUILD index 33bd6660e41..e1c87b0aa70 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/BUILD +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/BUILD @@ -34,8 +34,8 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake", deps = [ - "//pkg/api:go_default_library", - "//pkg/apis/extensions:go_default_library", + "//pkg/apis/autoscaling:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/doc.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/doc.go index c58fac35e4b..63e2c8a0821 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_componentstatus.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_componentstatus.go index 9d2bdd39e55..d6171dce0d4 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_componentstatus.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_componentstatus.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,7 +23,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // FakeComponentStatuses implements ComponentStatusInterface @@ -36,19 +36,19 @@ var componentstatusesResource = schema.GroupVersionResource{Group: "", Version: var componentstatusesKind = schema.GroupVersionKind{Group: "", Version: "", Kind: "ComponentStatus"} // Get takes name of the componentStatus, and returns the corresponding componentStatus object, and an error if there is any. -func (c *FakeComponentStatuses) Get(name string, options v1.GetOptions) (result *api.ComponentStatus, err error) { +func (c *FakeComponentStatuses) Get(name string, options v1.GetOptions) (result *core.ComponentStatus, err error) { obj, err := c.Fake. - Invokes(testing.NewRootGetAction(componentstatusesResource, name), &api.ComponentStatus{}) + Invokes(testing.NewRootGetAction(componentstatusesResource, name), &core.ComponentStatus{}) if obj == nil { return nil, err } - return obj.(*api.ComponentStatus), err + return obj.(*core.ComponentStatus), err } // List takes label and field selectors, and returns the list of ComponentStatuses that match those selectors. -func (c *FakeComponentStatuses) List(opts v1.ListOptions) (result *api.ComponentStatusList, err error) { +func (c *FakeComponentStatuses) List(opts v1.ListOptions) (result *core.ComponentStatusList, err error) { obj, err := c.Fake. - Invokes(testing.NewRootListAction(componentstatusesResource, componentstatusesKind, opts), &api.ComponentStatusList{}) + Invokes(testing.NewRootListAction(componentstatusesResource, componentstatusesKind, opts), &core.ComponentStatusList{}) if obj == nil { return nil, err } @@ -57,8 +57,8 @@ func (c *FakeComponentStatuses) List(opts v1.ListOptions) (result *api.Component if label == nil { label = labels.Everything() } - list := &api.ComponentStatusList{} - for _, item := range obj.(*api.ComponentStatusList).Items { + list := &core.ComponentStatusList{} + for _, item := range obj.(*core.ComponentStatusList).Items { if label.Matches(labels.Set(item.Labels)) { list.Items = append(list.Items, item) } @@ -73,29 +73,29 @@ func (c *FakeComponentStatuses) Watch(opts v1.ListOptions) (watch.Interface, err } // Create takes the representation of a componentStatus and creates it. Returns the server's representation of the componentStatus, and an error, if there is any. -func (c *FakeComponentStatuses) Create(componentStatus *api.ComponentStatus) (result *api.ComponentStatus, err error) { +func (c *FakeComponentStatuses) Create(componentStatus *core.ComponentStatus) (result *core.ComponentStatus, err error) { obj, err := c.Fake. - Invokes(testing.NewRootCreateAction(componentstatusesResource, componentStatus), &api.ComponentStatus{}) + Invokes(testing.NewRootCreateAction(componentstatusesResource, componentStatus), &core.ComponentStatus{}) if obj == nil { return nil, err } - return obj.(*api.ComponentStatus), err + return obj.(*core.ComponentStatus), err } // Update takes the representation of a componentStatus and updates it. Returns the server's representation of the componentStatus, and an error, if there is any. -func (c *FakeComponentStatuses) Update(componentStatus *api.ComponentStatus) (result *api.ComponentStatus, err error) { +func (c *FakeComponentStatuses) Update(componentStatus *core.ComponentStatus) (result *core.ComponentStatus, err error) { obj, err := c.Fake. - Invokes(testing.NewRootUpdateAction(componentstatusesResource, componentStatus), &api.ComponentStatus{}) + Invokes(testing.NewRootUpdateAction(componentstatusesResource, componentStatus), &core.ComponentStatus{}) if obj == nil { return nil, err } - return obj.(*api.ComponentStatus), err + return obj.(*core.ComponentStatus), err } // Delete takes name of the componentStatus and deletes it. Returns an error if one occurs. func (c *FakeComponentStatuses) Delete(name string, options *v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewRootDeleteAction(componentstatusesResource, name), &api.ComponentStatus{}) + Invokes(testing.NewRootDeleteAction(componentstatusesResource, name), &core.ComponentStatus{}) return err } @@ -103,16 +103,16 @@ func (c *FakeComponentStatuses) Delete(name string, options *v1.DeleteOptions) e func (c *FakeComponentStatuses) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { action := testing.NewRootDeleteCollectionAction(componentstatusesResource, listOptions) - _, err := c.Fake.Invokes(action, &api.ComponentStatusList{}) + _, err := c.Fake.Invokes(action, &core.ComponentStatusList{}) return err } // Patch applies the patch and returns the patched componentStatus. -func (c *FakeComponentStatuses) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.ComponentStatus, err error) { +func (c *FakeComponentStatuses) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.ComponentStatus, err error) { obj, err := c.Fake. - Invokes(testing.NewRootPatchSubresourceAction(componentstatusesResource, name, data, subresources...), &api.ComponentStatus{}) + Invokes(testing.NewRootPatchSubresourceAction(componentstatusesResource, name, data, subresources...), &core.ComponentStatus{}) if obj == nil { return nil, err } - return obj.(*api.ComponentStatus), err + return obj.(*core.ComponentStatus), err } diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_configmap.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_configmap.go index cf5ac3e4716..798efa92308 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_configmap.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_configmap.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,7 +23,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // FakeConfigMaps implements ConfigMapInterface @@ -37,20 +37,20 @@ var configmapsResource = schema.GroupVersionResource{Group: "", Version: "", Res var configmapsKind = schema.GroupVersionKind{Group: "", Version: "", Kind: "ConfigMap"} // Get takes name of the configMap, and returns the corresponding configMap object, and an error if there is any. -func (c *FakeConfigMaps) Get(name string, options v1.GetOptions) (result *api.ConfigMap, err error) { +func (c *FakeConfigMaps) Get(name string, options v1.GetOptions) (result *core.ConfigMap, err error) { obj, err := c.Fake. - Invokes(testing.NewGetAction(configmapsResource, c.ns, name), &api.ConfigMap{}) + Invokes(testing.NewGetAction(configmapsResource, c.ns, name), &core.ConfigMap{}) if obj == nil { return nil, err } - return obj.(*api.ConfigMap), err + return obj.(*core.ConfigMap), err } // List takes label and field selectors, and returns the list of ConfigMaps that match those selectors. -func (c *FakeConfigMaps) List(opts v1.ListOptions) (result *api.ConfigMapList, err error) { +func (c *FakeConfigMaps) List(opts v1.ListOptions) (result *core.ConfigMapList, err error) { obj, err := c.Fake. - Invokes(testing.NewListAction(configmapsResource, configmapsKind, c.ns, opts), &api.ConfigMapList{}) + Invokes(testing.NewListAction(configmapsResource, configmapsKind, c.ns, opts), &core.ConfigMapList{}) if obj == nil { return nil, err @@ -60,8 +60,8 @@ func (c *FakeConfigMaps) List(opts v1.ListOptions) (result *api.ConfigMapList, e if label == nil { label = labels.Everything() } - list := &api.ConfigMapList{} - for _, item := range obj.(*api.ConfigMapList).Items { + list := &core.ConfigMapList{} + for _, item := range obj.(*core.ConfigMapList).Items { if label.Matches(labels.Set(item.Labels)) { list.Items = append(list.Items, item) } @@ -77,31 +77,31 @@ func (c *FakeConfigMaps) Watch(opts v1.ListOptions) (watch.Interface, error) { } // Create takes the representation of a configMap and creates it. Returns the server's representation of the configMap, and an error, if there is any. -func (c *FakeConfigMaps) Create(configMap *api.ConfigMap) (result *api.ConfigMap, err error) { +func (c *FakeConfigMaps) Create(configMap *core.ConfigMap) (result *core.ConfigMap, err error) { obj, err := c.Fake. - Invokes(testing.NewCreateAction(configmapsResource, c.ns, configMap), &api.ConfigMap{}) + Invokes(testing.NewCreateAction(configmapsResource, c.ns, configMap), &core.ConfigMap{}) if obj == nil { return nil, err } - return obj.(*api.ConfigMap), err + return obj.(*core.ConfigMap), err } // Update takes the representation of a configMap and updates it. Returns the server's representation of the configMap, and an error, if there is any. -func (c *FakeConfigMaps) Update(configMap *api.ConfigMap) (result *api.ConfigMap, err error) { +func (c *FakeConfigMaps) Update(configMap *core.ConfigMap) (result *core.ConfigMap, err error) { obj, err := c.Fake. - Invokes(testing.NewUpdateAction(configmapsResource, c.ns, configMap), &api.ConfigMap{}) + Invokes(testing.NewUpdateAction(configmapsResource, c.ns, configMap), &core.ConfigMap{}) if obj == nil { return nil, err } - return obj.(*api.ConfigMap), err + return obj.(*core.ConfigMap), err } // Delete takes name of the configMap and deletes it. Returns an error if one occurs. func (c *FakeConfigMaps) Delete(name string, options *v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewDeleteAction(configmapsResource, c.ns, name), &api.ConfigMap{}) + Invokes(testing.NewDeleteAction(configmapsResource, c.ns, name), &core.ConfigMap{}) return err } @@ -110,17 +110,17 @@ func (c *FakeConfigMaps) Delete(name string, options *v1.DeleteOptions) error { func (c *FakeConfigMaps) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { action := testing.NewDeleteCollectionAction(configmapsResource, c.ns, listOptions) - _, err := c.Fake.Invokes(action, &api.ConfigMapList{}) + _, err := c.Fake.Invokes(action, &core.ConfigMapList{}) return err } // Patch applies the patch and returns the patched configMap. -func (c *FakeConfigMaps) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.ConfigMap, err error) { +func (c *FakeConfigMaps) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.ConfigMap, err error) { obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(configmapsResource, c.ns, name, data, subresources...), &api.ConfigMap{}) + Invokes(testing.NewPatchSubresourceAction(configmapsResource, c.ns, name, data, subresources...), &core.ConfigMap{}) if obj == nil { return nil, err } - return obj.(*api.ConfigMap), err + return obj.(*core.ConfigMap), err } diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_core_client.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_core_client.go index 511cb36dbd1..6575e13da10 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_core_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_core_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_endpoints.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_endpoints.go index 4e31fdcddcb..4918baee871 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_endpoints.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_endpoints.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,7 +23,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // FakeEndpoints implements EndpointsInterface @@ -37,20 +37,20 @@ var endpointsResource = schema.GroupVersionResource{Group: "", Version: "", Reso var endpointsKind = schema.GroupVersionKind{Group: "", Version: "", Kind: "Endpoints"} // Get takes name of the endpoints, and returns the corresponding endpoints object, and an error if there is any. -func (c *FakeEndpoints) Get(name string, options v1.GetOptions) (result *api.Endpoints, err error) { +func (c *FakeEndpoints) Get(name string, options v1.GetOptions) (result *core.Endpoints, err error) { obj, err := c.Fake. - Invokes(testing.NewGetAction(endpointsResource, c.ns, name), &api.Endpoints{}) + Invokes(testing.NewGetAction(endpointsResource, c.ns, name), &core.Endpoints{}) if obj == nil { return nil, err } - return obj.(*api.Endpoints), err + return obj.(*core.Endpoints), err } // List takes label and field selectors, and returns the list of Endpoints that match those selectors. -func (c *FakeEndpoints) List(opts v1.ListOptions) (result *api.EndpointsList, err error) { +func (c *FakeEndpoints) List(opts v1.ListOptions) (result *core.EndpointsList, err error) { obj, err := c.Fake. - Invokes(testing.NewListAction(endpointsResource, endpointsKind, c.ns, opts), &api.EndpointsList{}) + Invokes(testing.NewListAction(endpointsResource, endpointsKind, c.ns, opts), &core.EndpointsList{}) if obj == nil { return nil, err @@ -60,8 +60,8 @@ func (c *FakeEndpoints) List(opts v1.ListOptions) (result *api.EndpointsList, er if label == nil { label = labels.Everything() } - list := &api.EndpointsList{} - for _, item := range obj.(*api.EndpointsList).Items { + list := &core.EndpointsList{} + for _, item := range obj.(*core.EndpointsList).Items { if label.Matches(labels.Set(item.Labels)) { list.Items = append(list.Items, item) } @@ -77,31 +77,31 @@ func (c *FakeEndpoints) Watch(opts v1.ListOptions) (watch.Interface, error) { } // Create takes the representation of a endpoints and creates it. Returns the server's representation of the endpoints, and an error, if there is any. -func (c *FakeEndpoints) Create(endpoints *api.Endpoints) (result *api.Endpoints, err error) { +func (c *FakeEndpoints) Create(endpoints *core.Endpoints) (result *core.Endpoints, err error) { obj, err := c.Fake. - Invokes(testing.NewCreateAction(endpointsResource, c.ns, endpoints), &api.Endpoints{}) + Invokes(testing.NewCreateAction(endpointsResource, c.ns, endpoints), &core.Endpoints{}) if obj == nil { return nil, err } - return obj.(*api.Endpoints), err + return obj.(*core.Endpoints), err } // Update takes the representation of a endpoints and updates it. Returns the server's representation of the endpoints, and an error, if there is any. -func (c *FakeEndpoints) Update(endpoints *api.Endpoints) (result *api.Endpoints, err error) { +func (c *FakeEndpoints) Update(endpoints *core.Endpoints) (result *core.Endpoints, err error) { obj, err := c.Fake. - Invokes(testing.NewUpdateAction(endpointsResource, c.ns, endpoints), &api.Endpoints{}) + Invokes(testing.NewUpdateAction(endpointsResource, c.ns, endpoints), &core.Endpoints{}) if obj == nil { return nil, err } - return obj.(*api.Endpoints), err + return obj.(*core.Endpoints), err } // Delete takes name of the endpoints and deletes it. Returns an error if one occurs. func (c *FakeEndpoints) Delete(name string, options *v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewDeleteAction(endpointsResource, c.ns, name), &api.Endpoints{}) + Invokes(testing.NewDeleteAction(endpointsResource, c.ns, name), &core.Endpoints{}) return err } @@ -110,17 +110,17 @@ func (c *FakeEndpoints) Delete(name string, options *v1.DeleteOptions) error { func (c *FakeEndpoints) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { action := testing.NewDeleteCollectionAction(endpointsResource, c.ns, listOptions) - _, err := c.Fake.Invokes(action, &api.EndpointsList{}) + _, err := c.Fake.Invokes(action, &core.EndpointsList{}) return err } // Patch applies the patch and returns the patched endpoints. -func (c *FakeEndpoints) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.Endpoints, err error) { +func (c *FakeEndpoints) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.Endpoints, err error) { obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(endpointsResource, c.ns, name, data, subresources...), &api.Endpoints{}) + Invokes(testing.NewPatchSubresourceAction(endpointsResource, c.ns, name, data, subresources...), &core.Endpoints{}) if obj == nil { return nil, err } - return obj.(*api.Endpoints), err + return obj.(*core.Endpoints), err } diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_event.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_event.go index 3242483dfa0..04240b59f92 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_event.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_event.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,7 +23,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // FakeEvents implements EventInterface @@ -37,20 +37,20 @@ var eventsResource = schema.GroupVersionResource{Group: "", Version: "", Resourc var eventsKind = schema.GroupVersionKind{Group: "", Version: "", Kind: "Event"} // Get takes name of the event, and returns the corresponding event object, and an error if there is any. -func (c *FakeEvents) Get(name string, options v1.GetOptions) (result *api.Event, err error) { +func (c *FakeEvents) Get(name string, options v1.GetOptions) (result *core.Event, err error) { obj, err := c.Fake. - Invokes(testing.NewGetAction(eventsResource, c.ns, name), &api.Event{}) + Invokes(testing.NewGetAction(eventsResource, c.ns, name), &core.Event{}) if obj == nil { return nil, err } - return obj.(*api.Event), err + return obj.(*core.Event), err } // List takes label and field selectors, and returns the list of Events that match those selectors. -func (c *FakeEvents) List(opts v1.ListOptions) (result *api.EventList, err error) { +func (c *FakeEvents) List(opts v1.ListOptions) (result *core.EventList, err error) { obj, err := c.Fake. - Invokes(testing.NewListAction(eventsResource, eventsKind, c.ns, opts), &api.EventList{}) + Invokes(testing.NewListAction(eventsResource, eventsKind, c.ns, opts), &core.EventList{}) if obj == nil { return nil, err @@ -60,8 +60,8 @@ func (c *FakeEvents) List(opts v1.ListOptions) (result *api.EventList, err error if label == nil { label = labels.Everything() } - list := &api.EventList{} - for _, item := range obj.(*api.EventList).Items { + list := &core.EventList{} + for _, item := range obj.(*core.EventList).Items { if label.Matches(labels.Set(item.Labels)) { list.Items = append(list.Items, item) } @@ -77,31 +77,31 @@ func (c *FakeEvents) Watch(opts v1.ListOptions) (watch.Interface, error) { } // Create takes the representation of a event and creates it. Returns the server's representation of the event, and an error, if there is any. -func (c *FakeEvents) Create(event *api.Event) (result *api.Event, err error) { +func (c *FakeEvents) Create(event *core.Event) (result *core.Event, err error) { obj, err := c.Fake. - Invokes(testing.NewCreateAction(eventsResource, c.ns, event), &api.Event{}) + Invokes(testing.NewCreateAction(eventsResource, c.ns, event), &core.Event{}) if obj == nil { return nil, err } - return obj.(*api.Event), err + return obj.(*core.Event), err } // Update takes the representation of a event and updates it. Returns the server's representation of the event, and an error, if there is any. -func (c *FakeEvents) Update(event *api.Event) (result *api.Event, err error) { +func (c *FakeEvents) Update(event *core.Event) (result *core.Event, err error) { obj, err := c.Fake. - Invokes(testing.NewUpdateAction(eventsResource, c.ns, event), &api.Event{}) + Invokes(testing.NewUpdateAction(eventsResource, c.ns, event), &core.Event{}) if obj == nil { return nil, err } - return obj.(*api.Event), err + return obj.(*core.Event), err } // Delete takes name of the event and deletes it. Returns an error if one occurs. func (c *FakeEvents) Delete(name string, options *v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewDeleteAction(eventsResource, c.ns, name), &api.Event{}) + Invokes(testing.NewDeleteAction(eventsResource, c.ns, name), &core.Event{}) return err } @@ -110,17 +110,17 @@ func (c *FakeEvents) Delete(name string, options *v1.DeleteOptions) error { func (c *FakeEvents) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { action := testing.NewDeleteCollectionAction(eventsResource, c.ns, listOptions) - _, err := c.Fake.Invokes(action, &api.EventList{}) + _, err := c.Fake.Invokes(action, &core.EventList{}) return err } // Patch applies the patch and returns the patched event. -func (c *FakeEvents) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.Event, err error) { +func (c *FakeEvents) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.Event, err error) { obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(eventsResource, c.ns, name, data, subresources...), &api.Event{}) + Invokes(testing.NewPatchSubresourceAction(eventsResource, c.ns, name, data, subresources...), &core.Event{}) if obj == nil { return nil, err } - return obj.(*api.Event), err + return obj.(*core.Event), err } diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_event_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_event_expansion.go index 283578a58cc..3adbbac7026 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_event_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_event_expansion.go @@ -21,7 +21,7 @@ import ( "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime" core "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) func (c *FakeEvents) CreateWithEventNamespace(event *api.Event) (*api.Event, error) { diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_limitrange.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_limitrange.go index 7323825c633..75c2516dd53 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_limitrange.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_limitrange.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,7 +23,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // FakeLimitRanges implements LimitRangeInterface @@ -37,20 +37,20 @@ var limitrangesResource = schema.GroupVersionResource{Group: "", Version: "", Re var limitrangesKind = schema.GroupVersionKind{Group: "", Version: "", Kind: "LimitRange"} // Get takes name of the limitRange, and returns the corresponding limitRange object, and an error if there is any. -func (c *FakeLimitRanges) Get(name string, options v1.GetOptions) (result *api.LimitRange, err error) { +func (c *FakeLimitRanges) Get(name string, options v1.GetOptions) (result *core.LimitRange, err error) { obj, err := c.Fake. - Invokes(testing.NewGetAction(limitrangesResource, c.ns, name), &api.LimitRange{}) + Invokes(testing.NewGetAction(limitrangesResource, c.ns, name), &core.LimitRange{}) if obj == nil { return nil, err } - return obj.(*api.LimitRange), err + return obj.(*core.LimitRange), err } // List takes label and field selectors, and returns the list of LimitRanges that match those selectors. -func (c *FakeLimitRanges) List(opts v1.ListOptions) (result *api.LimitRangeList, err error) { +func (c *FakeLimitRanges) List(opts v1.ListOptions) (result *core.LimitRangeList, err error) { obj, err := c.Fake. - Invokes(testing.NewListAction(limitrangesResource, limitrangesKind, c.ns, opts), &api.LimitRangeList{}) + Invokes(testing.NewListAction(limitrangesResource, limitrangesKind, c.ns, opts), &core.LimitRangeList{}) if obj == nil { return nil, err @@ -60,8 +60,8 @@ func (c *FakeLimitRanges) List(opts v1.ListOptions) (result *api.LimitRangeList, if label == nil { label = labels.Everything() } - list := &api.LimitRangeList{} - for _, item := range obj.(*api.LimitRangeList).Items { + list := &core.LimitRangeList{} + for _, item := range obj.(*core.LimitRangeList).Items { if label.Matches(labels.Set(item.Labels)) { list.Items = append(list.Items, item) } @@ -77,31 +77,31 @@ func (c *FakeLimitRanges) Watch(opts v1.ListOptions) (watch.Interface, error) { } // Create takes the representation of a limitRange and creates it. Returns the server's representation of the limitRange, and an error, if there is any. -func (c *FakeLimitRanges) Create(limitRange *api.LimitRange) (result *api.LimitRange, err error) { +func (c *FakeLimitRanges) Create(limitRange *core.LimitRange) (result *core.LimitRange, err error) { obj, err := c.Fake. - Invokes(testing.NewCreateAction(limitrangesResource, c.ns, limitRange), &api.LimitRange{}) + Invokes(testing.NewCreateAction(limitrangesResource, c.ns, limitRange), &core.LimitRange{}) if obj == nil { return nil, err } - return obj.(*api.LimitRange), err + return obj.(*core.LimitRange), err } // Update takes the representation of a limitRange and updates it. Returns the server's representation of the limitRange, and an error, if there is any. -func (c *FakeLimitRanges) Update(limitRange *api.LimitRange) (result *api.LimitRange, err error) { +func (c *FakeLimitRanges) Update(limitRange *core.LimitRange) (result *core.LimitRange, err error) { obj, err := c.Fake. - Invokes(testing.NewUpdateAction(limitrangesResource, c.ns, limitRange), &api.LimitRange{}) + Invokes(testing.NewUpdateAction(limitrangesResource, c.ns, limitRange), &core.LimitRange{}) if obj == nil { return nil, err } - return obj.(*api.LimitRange), err + return obj.(*core.LimitRange), err } // Delete takes name of the limitRange and deletes it. Returns an error if one occurs. func (c *FakeLimitRanges) Delete(name string, options *v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewDeleteAction(limitrangesResource, c.ns, name), &api.LimitRange{}) + Invokes(testing.NewDeleteAction(limitrangesResource, c.ns, name), &core.LimitRange{}) return err } @@ -110,17 +110,17 @@ func (c *FakeLimitRanges) Delete(name string, options *v1.DeleteOptions) error { func (c *FakeLimitRanges) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { action := testing.NewDeleteCollectionAction(limitrangesResource, c.ns, listOptions) - _, err := c.Fake.Invokes(action, &api.LimitRangeList{}) + _, err := c.Fake.Invokes(action, &core.LimitRangeList{}) return err } // Patch applies the patch and returns the patched limitRange. -func (c *FakeLimitRanges) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.LimitRange, err error) { +func (c *FakeLimitRanges) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.LimitRange, err error) { obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(limitrangesResource, c.ns, name, data, subresources...), &api.LimitRange{}) + Invokes(testing.NewPatchSubresourceAction(limitrangesResource, c.ns, name, data, subresources...), &core.LimitRange{}) if obj == nil { return nil, err } - return obj.(*api.LimitRange), err + return obj.(*core.LimitRange), err } diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_namespace.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_namespace.go index be812a4b85a..c58b99c6c67 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_namespace.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_namespace.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,7 +23,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // FakeNamespaces implements NamespaceInterface @@ -36,19 +36,19 @@ var namespacesResource = schema.GroupVersionResource{Group: "", Version: "", Res var namespacesKind = schema.GroupVersionKind{Group: "", Version: "", Kind: "Namespace"} // Get takes name of the namespace, and returns the corresponding namespace object, and an error if there is any. -func (c *FakeNamespaces) Get(name string, options v1.GetOptions) (result *api.Namespace, err error) { +func (c *FakeNamespaces) Get(name string, options v1.GetOptions) (result *core.Namespace, err error) { obj, err := c.Fake. - Invokes(testing.NewRootGetAction(namespacesResource, name), &api.Namespace{}) + Invokes(testing.NewRootGetAction(namespacesResource, name), &core.Namespace{}) if obj == nil { return nil, err } - return obj.(*api.Namespace), err + return obj.(*core.Namespace), err } // List takes label and field selectors, and returns the list of Namespaces that match those selectors. -func (c *FakeNamespaces) List(opts v1.ListOptions) (result *api.NamespaceList, err error) { +func (c *FakeNamespaces) List(opts v1.ListOptions) (result *core.NamespaceList, err error) { obj, err := c.Fake. - Invokes(testing.NewRootListAction(namespacesResource, namespacesKind, opts), &api.NamespaceList{}) + Invokes(testing.NewRootListAction(namespacesResource, namespacesKind, opts), &core.NamespaceList{}) if obj == nil { return nil, err } @@ -57,8 +57,8 @@ func (c *FakeNamespaces) List(opts v1.ListOptions) (result *api.NamespaceList, e if label == nil { label = labels.Everything() } - list := &api.NamespaceList{} - for _, item := range obj.(*api.NamespaceList).Items { + list := &core.NamespaceList{} + for _, item := range obj.(*core.NamespaceList).Items { if label.Matches(labels.Set(item.Labels)) { list.Items = append(list.Items, item) } @@ -73,40 +73,40 @@ func (c *FakeNamespaces) Watch(opts v1.ListOptions) (watch.Interface, error) { } // Create takes the representation of a namespace and creates it. Returns the server's representation of the namespace, and an error, if there is any. -func (c *FakeNamespaces) Create(namespace *api.Namespace) (result *api.Namespace, err error) { +func (c *FakeNamespaces) Create(namespace *core.Namespace) (result *core.Namespace, err error) { obj, err := c.Fake. - Invokes(testing.NewRootCreateAction(namespacesResource, namespace), &api.Namespace{}) + Invokes(testing.NewRootCreateAction(namespacesResource, namespace), &core.Namespace{}) if obj == nil { return nil, err } - return obj.(*api.Namespace), err + return obj.(*core.Namespace), err } // Update takes the representation of a namespace and updates it. Returns the server's representation of the namespace, and an error, if there is any. -func (c *FakeNamespaces) Update(namespace *api.Namespace) (result *api.Namespace, err error) { +func (c *FakeNamespaces) Update(namespace *core.Namespace) (result *core.Namespace, err error) { obj, err := c.Fake. - Invokes(testing.NewRootUpdateAction(namespacesResource, namespace), &api.Namespace{}) + Invokes(testing.NewRootUpdateAction(namespacesResource, namespace), &core.Namespace{}) if obj == nil { return nil, err } - return obj.(*api.Namespace), err + return obj.(*core.Namespace), err } // UpdateStatus was generated because the type contains a Status member. // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeNamespaces) UpdateStatus(namespace *api.Namespace) (*api.Namespace, error) { +func (c *FakeNamespaces) UpdateStatus(namespace *core.Namespace) (*core.Namespace, error) { obj, err := c.Fake. - Invokes(testing.NewRootUpdateSubresourceAction(namespacesResource, "status", namespace), &api.Namespace{}) + Invokes(testing.NewRootUpdateSubresourceAction(namespacesResource, "status", namespace), &core.Namespace{}) if obj == nil { return nil, err } - return obj.(*api.Namespace), err + return obj.(*core.Namespace), err } // Delete takes name of the namespace and deletes it. Returns an error if one occurs. func (c *FakeNamespaces) Delete(name string, options *v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewRootDeleteAction(namespacesResource, name), &api.Namespace{}) + Invokes(testing.NewRootDeleteAction(namespacesResource, name), &core.Namespace{}) return err } @@ -114,16 +114,16 @@ func (c *FakeNamespaces) Delete(name string, options *v1.DeleteOptions) error { func (c *FakeNamespaces) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { action := testing.NewRootDeleteCollectionAction(namespacesResource, listOptions) - _, err := c.Fake.Invokes(action, &api.NamespaceList{}) + _, err := c.Fake.Invokes(action, &core.NamespaceList{}) return err } // Patch applies the patch and returns the patched namespace. -func (c *FakeNamespaces) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.Namespace, err error) { +func (c *FakeNamespaces) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.Namespace, err error) { obj, err := c.Fake. - Invokes(testing.NewRootPatchSubresourceAction(namespacesResource, name, data, subresources...), &api.Namespace{}) + Invokes(testing.NewRootPatchSubresourceAction(namespacesResource, name, data, subresources...), &core.Namespace{}) if obj == nil { return nil, err } - return obj.(*api.Namespace), err + return obj.(*core.Namespace), err } diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_namespace_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_namespace_expansion.go index dd425cf8c12..77200c72fba 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_namespace_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_namespace_expansion.go @@ -18,7 +18,7 @@ package fake import ( core "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) func (c *FakeNamespaces) Finalize(namespace *api.Namespace) (*api.Namespace, error) { diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_node.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_node.go index 55be99ece21..fa049258100 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_node.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_node.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,7 +23,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // FakeNodes implements NodeInterface @@ -36,19 +36,19 @@ var nodesResource = schema.GroupVersionResource{Group: "", Version: "", Resource var nodesKind = schema.GroupVersionKind{Group: "", Version: "", Kind: "Node"} // Get takes name of the node, and returns the corresponding node object, and an error if there is any. -func (c *FakeNodes) Get(name string, options v1.GetOptions) (result *api.Node, err error) { +func (c *FakeNodes) Get(name string, options v1.GetOptions) (result *core.Node, err error) { obj, err := c.Fake. - Invokes(testing.NewRootGetAction(nodesResource, name), &api.Node{}) + Invokes(testing.NewRootGetAction(nodesResource, name), &core.Node{}) if obj == nil { return nil, err } - return obj.(*api.Node), err + return obj.(*core.Node), err } // List takes label and field selectors, and returns the list of Nodes that match those selectors. -func (c *FakeNodes) List(opts v1.ListOptions) (result *api.NodeList, err error) { +func (c *FakeNodes) List(opts v1.ListOptions) (result *core.NodeList, err error) { obj, err := c.Fake. - Invokes(testing.NewRootListAction(nodesResource, nodesKind, opts), &api.NodeList{}) + Invokes(testing.NewRootListAction(nodesResource, nodesKind, opts), &core.NodeList{}) if obj == nil { return nil, err } @@ -57,8 +57,8 @@ func (c *FakeNodes) List(opts v1.ListOptions) (result *api.NodeList, err error) if label == nil { label = labels.Everything() } - list := &api.NodeList{} - for _, item := range obj.(*api.NodeList).Items { + list := &core.NodeList{} + for _, item := range obj.(*core.NodeList).Items { if label.Matches(labels.Set(item.Labels)) { list.Items = append(list.Items, item) } @@ -73,40 +73,40 @@ func (c *FakeNodes) Watch(opts v1.ListOptions) (watch.Interface, error) { } // Create takes the representation of a node and creates it. Returns the server's representation of the node, and an error, if there is any. -func (c *FakeNodes) Create(node *api.Node) (result *api.Node, err error) { +func (c *FakeNodes) Create(node *core.Node) (result *core.Node, err error) { obj, err := c.Fake. - Invokes(testing.NewRootCreateAction(nodesResource, node), &api.Node{}) + Invokes(testing.NewRootCreateAction(nodesResource, node), &core.Node{}) if obj == nil { return nil, err } - return obj.(*api.Node), err + return obj.(*core.Node), err } // Update takes the representation of a node and updates it. Returns the server's representation of the node, and an error, if there is any. -func (c *FakeNodes) Update(node *api.Node) (result *api.Node, err error) { +func (c *FakeNodes) Update(node *core.Node) (result *core.Node, err error) { obj, err := c.Fake. - Invokes(testing.NewRootUpdateAction(nodesResource, node), &api.Node{}) + Invokes(testing.NewRootUpdateAction(nodesResource, node), &core.Node{}) if obj == nil { return nil, err } - return obj.(*api.Node), err + return obj.(*core.Node), err } // UpdateStatus was generated because the type contains a Status member. // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeNodes) UpdateStatus(node *api.Node) (*api.Node, error) { +func (c *FakeNodes) UpdateStatus(node *core.Node) (*core.Node, error) { obj, err := c.Fake. - Invokes(testing.NewRootUpdateSubresourceAction(nodesResource, "status", node), &api.Node{}) + Invokes(testing.NewRootUpdateSubresourceAction(nodesResource, "status", node), &core.Node{}) if obj == nil { return nil, err } - return obj.(*api.Node), err + return obj.(*core.Node), err } // Delete takes name of the node and deletes it. Returns an error if one occurs. func (c *FakeNodes) Delete(name string, options *v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewRootDeleteAction(nodesResource, name), &api.Node{}) + Invokes(testing.NewRootDeleteAction(nodesResource, name), &core.Node{}) return err } @@ -114,16 +114,16 @@ func (c *FakeNodes) Delete(name string, options *v1.DeleteOptions) error { func (c *FakeNodes) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { action := testing.NewRootDeleteCollectionAction(nodesResource, listOptions) - _, err := c.Fake.Invokes(action, &api.NodeList{}) + _, err := c.Fake.Invokes(action, &core.NodeList{}) return err } // Patch applies the patch and returns the patched node. -func (c *FakeNodes) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.Node, err error) { +func (c *FakeNodes) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.Node, err error) { obj, err := c.Fake. - Invokes(testing.NewRootPatchSubresourceAction(nodesResource, name, data, subresources...), &api.Node{}) + Invokes(testing.NewRootPatchSubresourceAction(nodesResource, name, data, subresources...), &core.Node{}) if obj == nil { return nil, err } - return obj.(*api.Node), err + return obj.(*core.Node), err } diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_node_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_node_expansion.go index d119e95a454..6d5df8dc745 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_node_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_node_expansion.go @@ -18,7 +18,7 @@ package fake import ( core "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) func (c *FakeNodes) PatchStatus(nodeName string, data []byte) (*api.Node, error) { diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_persistentvolume.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_persistentvolume.go index b55c8cd6def..046e83d6843 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_persistentvolume.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_persistentvolume.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,7 +23,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // FakePersistentVolumes implements PersistentVolumeInterface @@ -36,19 +36,19 @@ var persistentvolumesResource = schema.GroupVersionResource{Group: "", Version: var persistentvolumesKind = schema.GroupVersionKind{Group: "", Version: "", Kind: "PersistentVolume"} // Get takes name of the persistentVolume, and returns the corresponding persistentVolume object, and an error if there is any. -func (c *FakePersistentVolumes) Get(name string, options v1.GetOptions) (result *api.PersistentVolume, err error) { +func (c *FakePersistentVolumes) Get(name string, options v1.GetOptions) (result *core.PersistentVolume, err error) { obj, err := c.Fake. - Invokes(testing.NewRootGetAction(persistentvolumesResource, name), &api.PersistentVolume{}) + Invokes(testing.NewRootGetAction(persistentvolumesResource, name), &core.PersistentVolume{}) if obj == nil { return nil, err } - return obj.(*api.PersistentVolume), err + return obj.(*core.PersistentVolume), err } // List takes label and field selectors, and returns the list of PersistentVolumes that match those selectors. -func (c *FakePersistentVolumes) List(opts v1.ListOptions) (result *api.PersistentVolumeList, err error) { +func (c *FakePersistentVolumes) List(opts v1.ListOptions) (result *core.PersistentVolumeList, err error) { obj, err := c.Fake. - Invokes(testing.NewRootListAction(persistentvolumesResource, persistentvolumesKind, opts), &api.PersistentVolumeList{}) + Invokes(testing.NewRootListAction(persistentvolumesResource, persistentvolumesKind, opts), &core.PersistentVolumeList{}) if obj == nil { return nil, err } @@ -57,8 +57,8 @@ func (c *FakePersistentVolumes) List(opts v1.ListOptions) (result *api.Persisten if label == nil { label = labels.Everything() } - list := &api.PersistentVolumeList{} - for _, item := range obj.(*api.PersistentVolumeList).Items { + list := &core.PersistentVolumeList{} + for _, item := range obj.(*core.PersistentVolumeList).Items { if label.Matches(labels.Set(item.Labels)) { list.Items = append(list.Items, item) } @@ -73,40 +73,40 @@ func (c *FakePersistentVolumes) Watch(opts v1.ListOptions) (watch.Interface, err } // Create takes the representation of a persistentVolume and creates it. Returns the server's representation of the persistentVolume, and an error, if there is any. -func (c *FakePersistentVolumes) Create(persistentVolume *api.PersistentVolume) (result *api.PersistentVolume, err error) { +func (c *FakePersistentVolumes) Create(persistentVolume *core.PersistentVolume) (result *core.PersistentVolume, err error) { obj, err := c.Fake. - Invokes(testing.NewRootCreateAction(persistentvolumesResource, persistentVolume), &api.PersistentVolume{}) + Invokes(testing.NewRootCreateAction(persistentvolumesResource, persistentVolume), &core.PersistentVolume{}) if obj == nil { return nil, err } - return obj.(*api.PersistentVolume), err + return obj.(*core.PersistentVolume), err } // Update takes the representation of a persistentVolume and updates it. Returns the server's representation of the persistentVolume, and an error, if there is any. -func (c *FakePersistentVolumes) Update(persistentVolume *api.PersistentVolume) (result *api.PersistentVolume, err error) { +func (c *FakePersistentVolumes) Update(persistentVolume *core.PersistentVolume) (result *core.PersistentVolume, err error) { obj, err := c.Fake. - Invokes(testing.NewRootUpdateAction(persistentvolumesResource, persistentVolume), &api.PersistentVolume{}) + Invokes(testing.NewRootUpdateAction(persistentvolumesResource, persistentVolume), &core.PersistentVolume{}) if obj == nil { return nil, err } - return obj.(*api.PersistentVolume), err + return obj.(*core.PersistentVolume), err } // UpdateStatus was generated because the type contains a Status member. // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakePersistentVolumes) UpdateStatus(persistentVolume *api.PersistentVolume) (*api.PersistentVolume, error) { +func (c *FakePersistentVolumes) UpdateStatus(persistentVolume *core.PersistentVolume) (*core.PersistentVolume, error) { obj, err := c.Fake. - Invokes(testing.NewRootUpdateSubresourceAction(persistentvolumesResource, "status", persistentVolume), &api.PersistentVolume{}) + Invokes(testing.NewRootUpdateSubresourceAction(persistentvolumesResource, "status", persistentVolume), &core.PersistentVolume{}) if obj == nil { return nil, err } - return obj.(*api.PersistentVolume), err + return obj.(*core.PersistentVolume), err } // Delete takes name of the persistentVolume and deletes it. Returns an error if one occurs. func (c *FakePersistentVolumes) Delete(name string, options *v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewRootDeleteAction(persistentvolumesResource, name), &api.PersistentVolume{}) + Invokes(testing.NewRootDeleteAction(persistentvolumesResource, name), &core.PersistentVolume{}) return err } @@ -114,16 +114,16 @@ func (c *FakePersistentVolumes) Delete(name string, options *v1.DeleteOptions) e func (c *FakePersistentVolumes) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { action := testing.NewRootDeleteCollectionAction(persistentvolumesResource, listOptions) - _, err := c.Fake.Invokes(action, &api.PersistentVolumeList{}) + _, err := c.Fake.Invokes(action, &core.PersistentVolumeList{}) return err } // Patch applies the patch and returns the patched persistentVolume. -func (c *FakePersistentVolumes) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.PersistentVolume, err error) { +func (c *FakePersistentVolumes) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.PersistentVolume, err error) { obj, err := c.Fake. - Invokes(testing.NewRootPatchSubresourceAction(persistentvolumesResource, name, data, subresources...), &api.PersistentVolume{}) + Invokes(testing.NewRootPatchSubresourceAction(persistentvolumesResource, name, data, subresources...), &core.PersistentVolume{}) if obj == nil { return nil, err } - return obj.(*api.PersistentVolume), err + return obj.(*core.PersistentVolume), err } diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_persistentvolumeclaim.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_persistentvolumeclaim.go index 34727f80abf..141cca4bbfe 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_persistentvolumeclaim.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_persistentvolumeclaim.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,7 +23,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // FakePersistentVolumeClaims implements PersistentVolumeClaimInterface @@ -37,20 +37,20 @@ var persistentvolumeclaimsResource = schema.GroupVersionResource{Group: "", Vers var persistentvolumeclaimsKind = schema.GroupVersionKind{Group: "", Version: "", Kind: "PersistentVolumeClaim"} // Get takes name of the persistentVolumeClaim, and returns the corresponding persistentVolumeClaim object, and an error if there is any. -func (c *FakePersistentVolumeClaims) Get(name string, options v1.GetOptions) (result *api.PersistentVolumeClaim, err error) { +func (c *FakePersistentVolumeClaims) Get(name string, options v1.GetOptions) (result *core.PersistentVolumeClaim, err error) { obj, err := c.Fake. - Invokes(testing.NewGetAction(persistentvolumeclaimsResource, c.ns, name), &api.PersistentVolumeClaim{}) + Invokes(testing.NewGetAction(persistentvolumeclaimsResource, c.ns, name), &core.PersistentVolumeClaim{}) if obj == nil { return nil, err } - return obj.(*api.PersistentVolumeClaim), err + return obj.(*core.PersistentVolumeClaim), err } // List takes label and field selectors, and returns the list of PersistentVolumeClaims that match those selectors. -func (c *FakePersistentVolumeClaims) List(opts v1.ListOptions) (result *api.PersistentVolumeClaimList, err error) { +func (c *FakePersistentVolumeClaims) List(opts v1.ListOptions) (result *core.PersistentVolumeClaimList, err error) { obj, err := c.Fake. - Invokes(testing.NewListAction(persistentvolumeclaimsResource, persistentvolumeclaimsKind, c.ns, opts), &api.PersistentVolumeClaimList{}) + Invokes(testing.NewListAction(persistentvolumeclaimsResource, persistentvolumeclaimsKind, c.ns, opts), &core.PersistentVolumeClaimList{}) if obj == nil { return nil, err @@ -60,8 +60,8 @@ func (c *FakePersistentVolumeClaims) List(opts v1.ListOptions) (result *api.Pers if label == nil { label = labels.Everything() } - list := &api.PersistentVolumeClaimList{} - for _, item := range obj.(*api.PersistentVolumeClaimList).Items { + list := &core.PersistentVolumeClaimList{} + for _, item := range obj.(*core.PersistentVolumeClaimList).Items { if label.Matches(labels.Set(item.Labels)) { list.Items = append(list.Items, item) } @@ -77,43 +77,43 @@ func (c *FakePersistentVolumeClaims) Watch(opts v1.ListOptions) (watch.Interface } // Create takes the representation of a persistentVolumeClaim and creates it. Returns the server's representation of the persistentVolumeClaim, and an error, if there is any. -func (c *FakePersistentVolumeClaims) Create(persistentVolumeClaim *api.PersistentVolumeClaim) (result *api.PersistentVolumeClaim, err error) { +func (c *FakePersistentVolumeClaims) Create(persistentVolumeClaim *core.PersistentVolumeClaim) (result *core.PersistentVolumeClaim, err error) { obj, err := c.Fake. - Invokes(testing.NewCreateAction(persistentvolumeclaimsResource, c.ns, persistentVolumeClaim), &api.PersistentVolumeClaim{}) + Invokes(testing.NewCreateAction(persistentvolumeclaimsResource, c.ns, persistentVolumeClaim), &core.PersistentVolumeClaim{}) if obj == nil { return nil, err } - return obj.(*api.PersistentVolumeClaim), err + return obj.(*core.PersistentVolumeClaim), err } // Update takes the representation of a persistentVolumeClaim and updates it. Returns the server's representation of the persistentVolumeClaim, and an error, if there is any. -func (c *FakePersistentVolumeClaims) Update(persistentVolumeClaim *api.PersistentVolumeClaim) (result *api.PersistentVolumeClaim, err error) { +func (c *FakePersistentVolumeClaims) Update(persistentVolumeClaim *core.PersistentVolumeClaim) (result *core.PersistentVolumeClaim, err error) { obj, err := c.Fake. - Invokes(testing.NewUpdateAction(persistentvolumeclaimsResource, c.ns, persistentVolumeClaim), &api.PersistentVolumeClaim{}) + Invokes(testing.NewUpdateAction(persistentvolumeclaimsResource, c.ns, persistentVolumeClaim), &core.PersistentVolumeClaim{}) if obj == nil { return nil, err } - return obj.(*api.PersistentVolumeClaim), err + return obj.(*core.PersistentVolumeClaim), err } // UpdateStatus was generated because the type contains a Status member. // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakePersistentVolumeClaims) UpdateStatus(persistentVolumeClaim *api.PersistentVolumeClaim) (*api.PersistentVolumeClaim, error) { +func (c *FakePersistentVolumeClaims) UpdateStatus(persistentVolumeClaim *core.PersistentVolumeClaim) (*core.PersistentVolumeClaim, error) { obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(persistentvolumeclaimsResource, "status", c.ns, persistentVolumeClaim), &api.PersistentVolumeClaim{}) + Invokes(testing.NewUpdateSubresourceAction(persistentvolumeclaimsResource, "status", c.ns, persistentVolumeClaim), &core.PersistentVolumeClaim{}) if obj == nil { return nil, err } - return obj.(*api.PersistentVolumeClaim), err + return obj.(*core.PersistentVolumeClaim), err } // Delete takes name of the persistentVolumeClaim and deletes it. Returns an error if one occurs. func (c *FakePersistentVolumeClaims) Delete(name string, options *v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewDeleteAction(persistentvolumeclaimsResource, c.ns, name), &api.PersistentVolumeClaim{}) + Invokes(testing.NewDeleteAction(persistentvolumeclaimsResource, c.ns, name), &core.PersistentVolumeClaim{}) return err } @@ -122,17 +122,17 @@ func (c *FakePersistentVolumeClaims) Delete(name string, options *v1.DeleteOptio func (c *FakePersistentVolumeClaims) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { action := testing.NewDeleteCollectionAction(persistentvolumeclaimsResource, c.ns, listOptions) - _, err := c.Fake.Invokes(action, &api.PersistentVolumeClaimList{}) + _, err := c.Fake.Invokes(action, &core.PersistentVolumeClaimList{}) return err } // Patch applies the patch and returns the patched persistentVolumeClaim. -func (c *FakePersistentVolumeClaims) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.PersistentVolumeClaim, err error) { +func (c *FakePersistentVolumeClaims) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.PersistentVolumeClaim, err error) { obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(persistentvolumeclaimsResource, c.ns, name, data, subresources...), &api.PersistentVolumeClaim{}) + Invokes(testing.NewPatchSubresourceAction(persistentvolumeclaimsResource, c.ns, name, data, subresources...), &core.PersistentVolumeClaim{}) if obj == nil { return nil, err } - return obj.(*api.PersistentVolumeClaim), err + return obj.(*core.PersistentVolumeClaim), err } diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_pod.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_pod.go index 1fe43bd8bdc..7af6f9dc8ae 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_pod.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_pod.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,7 +23,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // FakePods implements PodInterface @@ -37,20 +37,20 @@ var podsResource = schema.GroupVersionResource{Group: "", Version: "", Resource: var podsKind = schema.GroupVersionKind{Group: "", Version: "", Kind: "Pod"} // Get takes name of the pod, and returns the corresponding pod object, and an error if there is any. -func (c *FakePods) Get(name string, options v1.GetOptions) (result *api.Pod, err error) { +func (c *FakePods) Get(name string, options v1.GetOptions) (result *core.Pod, err error) { obj, err := c.Fake. - Invokes(testing.NewGetAction(podsResource, c.ns, name), &api.Pod{}) + Invokes(testing.NewGetAction(podsResource, c.ns, name), &core.Pod{}) if obj == nil { return nil, err } - return obj.(*api.Pod), err + return obj.(*core.Pod), err } // List takes label and field selectors, and returns the list of Pods that match those selectors. -func (c *FakePods) List(opts v1.ListOptions) (result *api.PodList, err error) { +func (c *FakePods) List(opts v1.ListOptions) (result *core.PodList, err error) { obj, err := c.Fake. - Invokes(testing.NewListAction(podsResource, podsKind, c.ns, opts), &api.PodList{}) + Invokes(testing.NewListAction(podsResource, podsKind, c.ns, opts), &core.PodList{}) if obj == nil { return nil, err @@ -60,8 +60,8 @@ func (c *FakePods) List(opts v1.ListOptions) (result *api.PodList, err error) { if label == nil { label = labels.Everything() } - list := &api.PodList{} - for _, item := range obj.(*api.PodList).Items { + list := &core.PodList{} + for _, item := range obj.(*core.PodList).Items { if label.Matches(labels.Set(item.Labels)) { list.Items = append(list.Items, item) } @@ -77,43 +77,43 @@ func (c *FakePods) Watch(opts v1.ListOptions) (watch.Interface, error) { } // Create takes the representation of a pod and creates it. Returns the server's representation of the pod, and an error, if there is any. -func (c *FakePods) Create(pod *api.Pod) (result *api.Pod, err error) { +func (c *FakePods) Create(pod *core.Pod) (result *core.Pod, err error) { obj, err := c.Fake. - Invokes(testing.NewCreateAction(podsResource, c.ns, pod), &api.Pod{}) + Invokes(testing.NewCreateAction(podsResource, c.ns, pod), &core.Pod{}) if obj == nil { return nil, err } - return obj.(*api.Pod), err + return obj.(*core.Pod), err } // Update takes the representation of a pod and updates it. Returns the server's representation of the pod, and an error, if there is any. -func (c *FakePods) Update(pod *api.Pod) (result *api.Pod, err error) { +func (c *FakePods) Update(pod *core.Pod) (result *core.Pod, err error) { obj, err := c.Fake. - Invokes(testing.NewUpdateAction(podsResource, c.ns, pod), &api.Pod{}) + Invokes(testing.NewUpdateAction(podsResource, c.ns, pod), &core.Pod{}) if obj == nil { return nil, err } - return obj.(*api.Pod), err + return obj.(*core.Pod), err } // UpdateStatus was generated because the type contains a Status member. // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakePods) UpdateStatus(pod *api.Pod) (*api.Pod, error) { +func (c *FakePods) UpdateStatus(pod *core.Pod) (*core.Pod, error) { obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(podsResource, "status", c.ns, pod), &api.Pod{}) + Invokes(testing.NewUpdateSubresourceAction(podsResource, "status", c.ns, pod), &core.Pod{}) if obj == nil { return nil, err } - return obj.(*api.Pod), err + return obj.(*core.Pod), err } // Delete takes name of the pod and deletes it. Returns an error if one occurs. func (c *FakePods) Delete(name string, options *v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewDeleteAction(podsResource, c.ns, name), &api.Pod{}) + Invokes(testing.NewDeleteAction(podsResource, c.ns, name), &core.Pod{}) return err } @@ -122,17 +122,17 @@ func (c *FakePods) Delete(name string, options *v1.DeleteOptions) error { func (c *FakePods) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { action := testing.NewDeleteCollectionAction(podsResource, c.ns, listOptions) - _, err := c.Fake.Invokes(action, &api.PodList{}) + _, err := c.Fake.Invokes(action, &core.PodList{}) return err } // Patch applies the patch and returns the patched pod. -func (c *FakePods) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.Pod, err error) { +func (c *FakePods) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.Pod, err error) { obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(podsResource, c.ns, name, data, subresources...), &api.Pod{}) + Invokes(testing.NewPatchSubresourceAction(podsResource, c.ns, name, data, subresources...), &core.Pod{}) if obj == nil { return nil, err } - return obj.(*api.Pod), err + return obj.(*core.Pod), err } diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_pod_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_pod_expansion.go index 8e94319ea8d..8853f9eee19 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_pod_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_pod_expansion.go @@ -19,7 +19,7 @@ package fake import ( restclient "k8s.io/client-go/rest" core "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) func (c *FakePods) Bind(binding *api.Binding) error { diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_podtemplate.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_podtemplate.go index 770ca16d83b..6de3800bb65 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_podtemplate.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_podtemplate.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,7 +23,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // FakePodTemplates implements PodTemplateInterface @@ -37,20 +37,20 @@ var podtemplatesResource = schema.GroupVersionResource{Group: "", Version: "", R var podtemplatesKind = schema.GroupVersionKind{Group: "", Version: "", Kind: "PodTemplate"} // Get takes name of the podTemplate, and returns the corresponding podTemplate object, and an error if there is any. -func (c *FakePodTemplates) Get(name string, options v1.GetOptions) (result *api.PodTemplate, err error) { +func (c *FakePodTemplates) Get(name string, options v1.GetOptions) (result *core.PodTemplate, err error) { obj, err := c.Fake. - Invokes(testing.NewGetAction(podtemplatesResource, c.ns, name), &api.PodTemplate{}) + Invokes(testing.NewGetAction(podtemplatesResource, c.ns, name), &core.PodTemplate{}) if obj == nil { return nil, err } - return obj.(*api.PodTemplate), err + return obj.(*core.PodTemplate), err } // List takes label and field selectors, and returns the list of PodTemplates that match those selectors. -func (c *FakePodTemplates) List(opts v1.ListOptions) (result *api.PodTemplateList, err error) { +func (c *FakePodTemplates) List(opts v1.ListOptions) (result *core.PodTemplateList, err error) { obj, err := c.Fake. - Invokes(testing.NewListAction(podtemplatesResource, podtemplatesKind, c.ns, opts), &api.PodTemplateList{}) + Invokes(testing.NewListAction(podtemplatesResource, podtemplatesKind, c.ns, opts), &core.PodTemplateList{}) if obj == nil { return nil, err @@ -60,8 +60,8 @@ func (c *FakePodTemplates) List(opts v1.ListOptions) (result *api.PodTemplateLis if label == nil { label = labels.Everything() } - list := &api.PodTemplateList{} - for _, item := range obj.(*api.PodTemplateList).Items { + list := &core.PodTemplateList{} + for _, item := range obj.(*core.PodTemplateList).Items { if label.Matches(labels.Set(item.Labels)) { list.Items = append(list.Items, item) } @@ -77,31 +77,31 @@ func (c *FakePodTemplates) Watch(opts v1.ListOptions) (watch.Interface, error) { } // Create takes the representation of a podTemplate and creates it. Returns the server's representation of the podTemplate, and an error, if there is any. -func (c *FakePodTemplates) Create(podTemplate *api.PodTemplate) (result *api.PodTemplate, err error) { +func (c *FakePodTemplates) Create(podTemplate *core.PodTemplate) (result *core.PodTemplate, err error) { obj, err := c.Fake. - Invokes(testing.NewCreateAction(podtemplatesResource, c.ns, podTemplate), &api.PodTemplate{}) + Invokes(testing.NewCreateAction(podtemplatesResource, c.ns, podTemplate), &core.PodTemplate{}) if obj == nil { return nil, err } - return obj.(*api.PodTemplate), err + return obj.(*core.PodTemplate), err } // Update takes the representation of a podTemplate and updates it. Returns the server's representation of the podTemplate, and an error, if there is any. -func (c *FakePodTemplates) Update(podTemplate *api.PodTemplate) (result *api.PodTemplate, err error) { +func (c *FakePodTemplates) Update(podTemplate *core.PodTemplate) (result *core.PodTemplate, err error) { obj, err := c.Fake. - Invokes(testing.NewUpdateAction(podtemplatesResource, c.ns, podTemplate), &api.PodTemplate{}) + Invokes(testing.NewUpdateAction(podtemplatesResource, c.ns, podTemplate), &core.PodTemplate{}) if obj == nil { return nil, err } - return obj.(*api.PodTemplate), err + return obj.(*core.PodTemplate), err } // Delete takes name of the podTemplate and deletes it. Returns an error if one occurs. func (c *FakePodTemplates) Delete(name string, options *v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewDeleteAction(podtemplatesResource, c.ns, name), &api.PodTemplate{}) + Invokes(testing.NewDeleteAction(podtemplatesResource, c.ns, name), &core.PodTemplate{}) return err } @@ -110,17 +110,17 @@ func (c *FakePodTemplates) Delete(name string, options *v1.DeleteOptions) error func (c *FakePodTemplates) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { action := testing.NewDeleteCollectionAction(podtemplatesResource, c.ns, listOptions) - _, err := c.Fake.Invokes(action, &api.PodTemplateList{}) + _, err := c.Fake.Invokes(action, &core.PodTemplateList{}) return err } // Patch applies the patch and returns the patched podTemplate. -func (c *FakePodTemplates) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.PodTemplate, err error) { +func (c *FakePodTemplates) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.PodTemplate, err error) { obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(podtemplatesResource, c.ns, name, data, subresources...), &api.PodTemplate{}) + Invokes(testing.NewPatchSubresourceAction(podtemplatesResource, c.ns, name, data, subresources...), &core.PodTemplate{}) if obj == nil { return nil, err } - return obj.(*api.PodTemplate), err + return obj.(*core.PodTemplate), err } diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_replicationcontroller.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_replicationcontroller.go index 80e7de10fef..8d0eb00074e 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_replicationcontroller.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_replicationcontroller.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,8 +23,8 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" - api "k8s.io/kubernetes/pkg/api" - extensions "k8s.io/kubernetes/pkg/apis/extensions" + autoscaling "k8s.io/kubernetes/pkg/apis/autoscaling" + core "k8s.io/kubernetes/pkg/apis/core" ) // FakeReplicationControllers implements ReplicationControllerInterface @@ -38,20 +38,20 @@ var replicationcontrollersResource = schema.GroupVersionResource{Group: "", Vers var replicationcontrollersKind = schema.GroupVersionKind{Group: "", Version: "", Kind: "ReplicationController"} // Get takes name of the replicationController, and returns the corresponding replicationController object, and an error if there is any. -func (c *FakeReplicationControllers) Get(name string, options v1.GetOptions) (result *api.ReplicationController, err error) { +func (c *FakeReplicationControllers) Get(name string, options v1.GetOptions) (result *core.ReplicationController, err error) { obj, err := c.Fake. - Invokes(testing.NewGetAction(replicationcontrollersResource, c.ns, name), &api.ReplicationController{}) + Invokes(testing.NewGetAction(replicationcontrollersResource, c.ns, name), &core.ReplicationController{}) if obj == nil { return nil, err } - return obj.(*api.ReplicationController), err + return obj.(*core.ReplicationController), err } // List takes label and field selectors, and returns the list of ReplicationControllers that match those selectors. -func (c *FakeReplicationControllers) List(opts v1.ListOptions) (result *api.ReplicationControllerList, err error) { +func (c *FakeReplicationControllers) List(opts v1.ListOptions) (result *core.ReplicationControllerList, err error) { obj, err := c.Fake. - Invokes(testing.NewListAction(replicationcontrollersResource, replicationcontrollersKind, c.ns, opts), &api.ReplicationControllerList{}) + Invokes(testing.NewListAction(replicationcontrollersResource, replicationcontrollersKind, c.ns, opts), &core.ReplicationControllerList{}) if obj == nil { return nil, err @@ -61,8 +61,8 @@ func (c *FakeReplicationControllers) List(opts v1.ListOptions) (result *api.Repl if label == nil { label = labels.Everything() } - list := &api.ReplicationControllerList{} - for _, item := range obj.(*api.ReplicationControllerList).Items { + list := &core.ReplicationControllerList{} + for _, item := range obj.(*core.ReplicationControllerList).Items { if label.Matches(labels.Set(item.Labels)) { list.Items = append(list.Items, item) } @@ -78,43 +78,43 @@ func (c *FakeReplicationControllers) Watch(opts v1.ListOptions) (watch.Interface } // Create takes the representation of a replicationController and creates it. Returns the server's representation of the replicationController, and an error, if there is any. -func (c *FakeReplicationControllers) Create(replicationController *api.ReplicationController) (result *api.ReplicationController, err error) { +func (c *FakeReplicationControllers) Create(replicationController *core.ReplicationController) (result *core.ReplicationController, err error) { obj, err := c.Fake. - Invokes(testing.NewCreateAction(replicationcontrollersResource, c.ns, replicationController), &api.ReplicationController{}) + Invokes(testing.NewCreateAction(replicationcontrollersResource, c.ns, replicationController), &core.ReplicationController{}) if obj == nil { return nil, err } - return obj.(*api.ReplicationController), err + return obj.(*core.ReplicationController), err } // Update takes the representation of a replicationController and updates it. Returns the server's representation of the replicationController, and an error, if there is any. -func (c *FakeReplicationControllers) Update(replicationController *api.ReplicationController) (result *api.ReplicationController, err error) { +func (c *FakeReplicationControllers) Update(replicationController *core.ReplicationController) (result *core.ReplicationController, err error) { obj, err := c.Fake. - Invokes(testing.NewUpdateAction(replicationcontrollersResource, c.ns, replicationController), &api.ReplicationController{}) + Invokes(testing.NewUpdateAction(replicationcontrollersResource, c.ns, replicationController), &core.ReplicationController{}) if obj == nil { return nil, err } - return obj.(*api.ReplicationController), err + return obj.(*core.ReplicationController), err } // UpdateStatus was generated because the type contains a Status member. // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeReplicationControllers) UpdateStatus(replicationController *api.ReplicationController) (*api.ReplicationController, error) { +func (c *FakeReplicationControllers) UpdateStatus(replicationController *core.ReplicationController) (*core.ReplicationController, error) { obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(replicationcontrollersResource, "status", c.ns, replicationController), &api.ReplicationController{}) + Invokes(testing.NewUpdateSubresourceAction(replicationcontrollersResource, "status", c.ns, replicationController), &core.ReplicationController{}) if obj == nil { return nil, err } - return obj.(*api.ReplicationController), err + return obj.(*core.ReplicationController), err } // Delete takes name of the replicationController and deletes it. Returns an error if one occurs. func (c *FakeReplicationControllers) Delete(name string, options *v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewDeleteAction(replicationcontrollersResource, c.ns, name), &api.ReplicationController{}) + Invokes(testing.NewDeleteAction(replicationcontrollersResource, c.ns, name), &core.ReplicationController{}) return err } @@ -123,39 +123,39 @@ func (c *FakeReplicationControllers) Delete(name string, options *v1.DeleteOptio func (c *FakeReplicationControllers) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { action := testing.NewDeleteCollectionAction(replicationcontrollersResource, c.ns, listOptions) - _, err := c.Fake.Invokes(action, &api.ReplicationControllerList{}) + _, err := c.Fake.Invokes(action, &core.ReplicationControllerList{}) return err } // Patch applies the patch and returns the patched replicationController. -func (c *FakeReplicationControllers) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.ReplicationController, err error) { +func (c *FakeReplicationControllers) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.ReplicationController, err error) { obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(replicationcontrollersResource, c.ns, name, data, subresources...), &api.ReplicationController{}) + Invokes(testing.NewPatchSubresourceAction(replicationcontrollersResource, c.ns, name, data, subresources...), &core.ReplicationController{}) if obj == nil { return nil, err } - return obj.(*api.ReplicationController), err + return obj.(*core.ReplicationController), err } // GetScale takes name of the replicationController, and returns the corresponding scale object, and an error if there is any. -func (c *FakeReplicationControllers) GetScale(replicationControllerName string, options v1.GetOptions) (result *extensions.Scale, err error) { +func (c *FakeReplicationControllers) GetScale(replicationControllerName string, options v1.GetOptions) (result *autoscaling.Scale, err error) { obj, err := c.Fake. - Invokes(testing.NewGetSubresourceAction(replicationcontrollersResource, c.ns, "scale", replicationControllerName), &extensions.Scale{}) + Invokes(testing.NewGetSubresourceAction(replicationcontrollersResource, c.ns, "scale", replicationControllerName), &autoscaling.Scale{}) if obj == nil { return nil, err } - return obj.(*extensions.Scale), err + return obj.(*autoscaling.Scale), err } // UpdateScale takes the representation of a scale and updates it. Returns the server's representation of the scale, and an error, if there is any. -func (c *FakeReplicationControllers) UpdateScale(replicationControllerName string, scale *extensions.Scale) (result *extensions.Scale, err error) { +func (c *FakeReplicationControllers) UpdateScale(replicationControllerName string, scale *autoscaling.Scale) (result *autoscaling.Scale, err error) { obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(replicationcontrollersResource, "scale", c.ns, scale), &extensions.Scale{}) + Invokes(testing.NewUpdateSubresourceAction(replicationcontrollersResource, "scale", c.ns, scale), &autoscaling.Scale{}) if obj == nil { return nil, err } - return obj.(*extensions.Scale), err + return obj.(*autoscaling.Scale), err } diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_resourcequota.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_resourcequota.go index 610f24c1bda..3db010b7651 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_resourcequota.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_resourcequota.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,7 +23,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // FakeResourceQuotas implements ResourceQuotaInterface @@ -37,20 +37,20 @@ var resourcequotasResource = schema.GroupVersionResource{Group: "", Version: "", var resourcequotasKind = schema.GroupVersionKind{Group: "", Version: "", Kind: "ResourceQuota"} // Get takes name of the resourceQuota, and returns the corresponding resourceQuota object, and an error if there is any. -func (c *FakeResourceQuotas) Get(name string, options v1.GetOptions) (result *api.ResourceQuota, err error) { +func (c *FakeResourceQuotas) Get(name string, options v1.GetOptions) (result *core.ResourceQuota, err error) { obj, err := c.Fake. - Invokes(testing.NewGetAction(resourcequotasResource, c.ns, name), &api.ResourceQuota{}) + Invokes(testing.NewGetAction(resourcequotasResource, c.ns, name), &core.ResourceQuota{}) if obj == nil { return nil, err } - return obj.(*api.ResourceQuota), err + return obj.(*core.ResourceQuota), err } // List takes label and field selectors, and returns the list of ResourceQuotas that match those selectors. -func (c *FakeResourceQuotas) List(opts v1.ListOptions) (result *api.ResourceQuotaList, err error) { +func (c *FakeResourceQuotas) List(opts v1.ListOptions) (result *core.ResourceQuotaList, err error) { obj, err := c.Fake. - Invokes(testing.NewListAction(resourcequotasResource, resourcequotasKind, c.ns, opts), &api.ResourceQuotaList{}) + Invokes(testing.NewListAction(resourcequotasResource, resourcequotasKind, c.ns, opts), &core.ResourceQuotaList{}) if obj == nil { return nil, err @@ -60,8 +60,8 @@ func (c *FakeResourceQuotas) List(opts v1.ListOptions) (result *api.ResourceQuot if label == nil { label = labels.Everything() } - list := &api.ResourceQuotaList{} - for _, item := range obj.(*api.ResourceQuotaList).Items { + list := &core.ResourceQuotaList{} + for _, item := range obj.(*core.ResourceQuotaList).Items { if label.Matches(labels.Set(item.Labels)) { list.Items = append(list.Items, item) } @@ -77,43 +77,43 @@ func (c *FakeResourceQuotas) Watch(opts v1.ListOptions) (watch.Interface, error) } // Create takes the representation of a resourceQuota and creates it. Returns the server's representation of the resourceQuota, and an error, if there is any. -func (c *FakeResourceQuotas) Create(resourceQuota *api.ResourceQuota) (result *api.ResourceQuota, err error) { +func (c *FakeResourceQuotas) Create(resourceQuota *core.ResourceQuota) (result *core.ResourceQuota, err error) { obj, err := c.Fake. - Invokes(testing.NewCreateAction(resourcequotasResource, c.ns, resourceQuota), &api.ResourceQuota{}) + Invokes(testing.NewCreateAction(resourcequotasResource, c.ns, resourceQuota), &core.ResourceQuota{}) if obj == nil { return nil, err } - return obj.(*api.ResourceQuota), err + return obj.(*core.ResourceQuota), err } // Update takes the representation of a resourceQuota and updates it. Returns the server's representation of the resourceQuota, and an error, if there is any. -func (c *FakeResourceQuotas) Update(resourceQuota *api.ResourceQuota) (result *api.ResourceQuota, err error) { +func (c *FakeResourceQuotas) Update(resourceQuota *core.ResourceQuota) (result *core.ResourceQuota, err error) { obj, err := c.Fake. - Invokes(testing.NewUpdateAction(resourcequotasResource, c.ns, resourceQuota), &api.ResourceQuota{}) + Invokes(testing.NewUpdateAction(resourcequotasResource, c.ns, resourceQuota), &core.ResourceQuota{}) if obj == nil { return nil, err } - return obj.(*api.ResourceQuota), err + return obj.(*core.ResourceQuota), err } // UpdateStatus was generated because the type contains a Status member. // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeResourceQuotas) UpdateStatus(resourceQuota *api.ResourceQuota) (*api.ResourceQuota, error) { +func (c *FakeResourceQuotas) UpdateStatus(resourceQuota *core.ResourceQuota) (*core.ResourceQuota, error) { obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(resourcequotasResource, "status", c.ns, resourceQuota), &api.ResourceQuota{}) + Invokes(testing.NewUpdateSubresourceAction(resourcequotasResource, "status", c.ns, resourceQuota), &core.ResourceQuota{}) if obj == nil { return nil, err } - return obj.(*api.ResourceQuota), err + return obj.(*core.ResourceQuota), err } // Delete takes name of the resourceQuota and deletes it. Returns an error if one occurs. func (c *FakeResourceQuotas) Delete(name string, options *v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewDeleteAction(resourcequotasResource, c.ns, name), &api.ResourceQuota{}) + Invokes(testing.NewDeleteAction(resourcequotasResource, c.ns, name), &core.ResourceQuota{}) return err } @@ -122,17 +122,17 @@ func (c *FakeResourceQuotas) Delete(name string, options *v1.DeleteOptions) erro func (c *FakeResourceQuotas) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { action := testing.NewDeleteCollectionAction(resourcequotasResource, c.ns, listOptions) - _, err := c.Fake.Invokes(action, &api.ResourceQuotaList{}) + _, err := c.Fake.Invokes(action, &core.ResourceQuotaList{}) return err } // Patch applies the patch and returns the patched resourceQuota. -func (c *FakeResourceQuotas) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.ResourceQuota, err error) { +func (c *FakeResourceQuotas) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.ResourceQuota, err error) { obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(resourcequotasResource, c.ns, name, data, subresources...), &api.ResourceQuota{}) + Invokes(testing.NewPatchSubresourceAction(resourcequotasResource, c.ns, name, data, subresources...), &core.ResourceQuota{}) if obj == nil { return nil, err } - return obj.(*api.ResourceQuota), err + return obj.(*core.ResourceQuota), err } diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_secret.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_secret.go index 22f9cf836b0..13a47e4e748 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_secret.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_secret.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,7 +23,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // FakeSecrets implements SecretInterface @@ -37,20 +37,20 @@ var secretsResource = schema.GroupVersionResource{Group: "", Version: "", Resour var secretsKind = schema.GroupVersionKind{Group: "", Version: "", Kind: "Secret"} // Get takes name of the secret, and returns the corresponding secret object, and an error if there is any. -func (c *FakeSecrets) Get(name string, options v1.GetOptions) (result *api.Secret, err error) { +func (c *FakeSecrets) Get(name string, options v1.GetOptions) (result *core.Secret, err error) { obj, err := c.Fake. - Invokes(testing.NewGetAction(secretsResource, c.ns, name), &api.Secret{}) + Invokes(testing.NewGetAction(secretsResource, c.ns, name), &core.Secret{}) if obj == nil { return nil, err } - return obj.(*api.Secret), err + return obj.(*core.Secret), err } // List takes label and field selectors, and returns the list of Secrets that match those selectors. -func (c *FakeSecrets) List(opts v1.ListOptions) (result *api.SecretList, err error) { +func (c *FakeSecrets) List(opts v1.ListOptions) (result *core.SecretList, err error) { obj, err := c.Fake. - Invokes(testing.NewListAction(secretsResource, secretsKind, c.ns, opts), &api.SecretList{}) + Invokes(testing.NewListAction(secretsResource, secretsKind, c.ns, opts), &core.SecretList{}) if obj == nil { return nil, err @@ -60,8 +60,8 @@ func (c *FakeSecrets) List(opts v1.ListOptions) (result *api.SecretList, err err if label == nil { label = labels.Everything() } - list := &api.SecretList{} - for _, item := range obj.(*api.SecretList).Items { + list := &core.SecretList{} + for _, item := range obj.(*core.SecretList).Items { if label.Matches(labels.Set(item.Labels)) { list.Items = append(list.Items, item) } @@ -77,31 +77,31 @@ func (c *FakeSecrets) Watch(opts v1.ListOptions) (watch.Interface, error) { } // Create takes the representation of a secret and creates it. Returns the server's representation of the secret, and an error, if there is any. -func (c *FakeSecrets) Create(secret *api.Secret) (result *api.Secret, err error) { +func (c *FakeSecrets) Create(secret *core.Secret) (result *core.Secret, err error) { obj, err := c.Fake. - Invokes(testing.NewCreateAction(secretsResource, c.ns, secret), &api.Secret{}) + Invokes(testing.NewCreateAction(secretsResource, c.ns, secret), &core.Secret{}) if obj == nil { return nil, err } - return obj.(*api.Secret), err + return obj.(*core.Secret), err } // Update takes the representation of a secret and updates it. Returns the server's representation of the secret, and an error, if there is any. -func (c *FakeSecrets) Update(secret *api.Secret) (result *api.Secret, err error) { +func (c *FakeSecrets) Update(secret *core.Secret) (result *core.Secret, err error) { obj, err := c.Fake. - Invokes(testing.NewUpdateAction(secretsResource, c.ns, secret), &api.Secret{}) + Invokes(testing.NewUpdateAction(secretsResource, c.ns, secret), &core.Secret{}) if obj == nil { return nil, err } - return obj.(*api.Secret), err + return obj.(*core.Secret), err } // Delete takes name of the secret and deletes it. Returns an error if one occurs. func (c *FakeSecrets) Delete(name string, options *v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewDeleteAction(secretsResource, c.ns, name), &api.Secret{}) + Invokes(testing.NewDeleteAction(secretsResource, c.ns, name), &core.Secret{}) return err } @@ -110,17 +110,17 @@ func (c *FakeSecrets) Delete(name string, options *v1.DeleteOptions) error { func (c *FakeSecrets) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { action := testing.NewDeleteCollectionAction(secretsResource, c.ns, listOptions) - _, err := c.Fake.Invokes(action, &api.SecretList{}) + _, err := c.Fake.Invokes(action, &core.SecretList{}) return err } // Patch applies the patch and returns the patched secret. -func (c *FakeSecrets) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.Secret, err error) { +func (c *FakeSecrets) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.Secret, err error) { obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(secretsResource, c.ns, name, data, subresources...), &api.Secret{}) + Invokes(testing.NewPatchSubresourceAction(secretsResource, c.ns, name, data, subresources...), &core.Secret{}) if obj == nil { return nil, err } - return obj.(*api.Secret), err + return obj.(*core.Secret), err } diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_service.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_service.go index 337b3b34dba..8bc747d7465 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_service.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_service.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,7 +23,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // FakeServices implements ServiceInterface @@ -37,20 +37,20 @@ var servicesResource = schema.GroupVersionResource{Group: "", Version: "", Resou var servicesKind = schema.GroupVersionKind{Group: "", Version: "", Kind: "Service"} // Get takes name of the service, and returns the corresponding service object, and an error if there is any. -func (c *FakeServices) Get(name string, options v1.GetOptions) (result *api.Service, err error) { +func (c *FakeServices) Get(name string, options v1.GetOptions) (result *core.Service, err error) { obj, err := c.Fake. - Invokes(testing.NewGetAction(servicesResource, c.ns, name), &api.Service{}) + Invokes(testing.NewGetAction(servicesResource, c.ns, name), &core.Service{}) if obj == nil { return nil, err } - return obj.(*api.Service), err + return obj.(*core.Service), err } // List takes label and field selectors, and returns the list of Services that match those selectors. -func (c *FakeServices) List(opts v1.ListOptions) (result *api.ServiceList, err error) { +func (c *FakeServices) List(opts v1.ListOptions) (result *core.ServiceList, err error) { obj, err := c.Fake. - Invokes(testing.NewListAction(servicesResource, servicesKind, c.ns, opts), &api.ServiceList{}) + Invokes(testing.NewListAction(servicesResource, servicesKind, c.ns, opts), &core.ServiceList{}) if obj == nil { return nil, err @@ -60,8 +60,8 @@ func (c *FakeServices) List(opts v1.ListOptions) (result *api.ServiceList, err e if label == nil { label = labels.Everything() } - list := &api.ServiceList{} - for _, item := range obj.(*api.ServiceList).Items { + list := &core.ServiceList{} + for _, item := range obj.(*core.ServiceList).Items { if label.Matches(labels.Set(item.Labels)) { list.Items = append(list.Items, item) } @@ -77,43 +77,43 @@ func (c *FakeServices) Watch(opts v1.ListOptions) (watch.Interface, error) { } // Create takes the representation of a service and creates it. Returns the server's representation of the service, and an error, if there is any. -func (c *FakeServices) Create(service *api.Service) (result *api.Service, err error) { +func (c *FakeServices) Create(service *core.Service) (result *core.Service, err error) { obj, err := c.Fake. - Invokes(testing.NewCreateAction(servicesResource, c.ns, service), &api.Service{}) + Invokes(testing.NewCreateAction(servicesResource, c.ns, service), &core.Service{}) if obj == nil { return nil, err } - return obj.(*api.Service), err + return obj.(*core.Service), err } // Update takes the representation of a service and updates it. Returns the server's representation of the service, and an error, if there is any. -func (c *FakeServices) Update(service *api.Service) (result *api.Service, err error) { +func (c *FakeServices) Update(service *core.Service) (result *core.Service, err error) { obj, err := c.Fake. - Invokes(testing.NewUpdateAction(servicesResource, c.ns, service), &api.Service{}) + Invokes(testing.NewUpdateAction(servicesResource, c.ns, service), &core.Service{}) if obj == nil { return nil, err } - return obj.(*api.Service), err + return obj.(*core.Service), err } // UpdateStatus was generated because the type contains a Status member. // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeServices) UpdateStatus(service *api.Service) (*api.Service, error) { +func (c *FakeServices) UpdateStatus(service *core.Service) (*core.Service, error) { obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(servicesResource, "status", c.ns, service), &api.Service{}) + Invokes(testing.NewUpdateSubresourceAction(servicesResource, "status", c.ns, service), &core.Service{}) if obj == nil { return nil, err } - return obj.(*api.Service), err + return obj.(*core.Service), err } // Delete takes name of the service and deletes it. Returns an error if one occurs. func (c *FakeServices) Delete(name string, options *v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewDeleteAction(servicesResource, c.ns, name), &api.Service{}) + Invokes(testing.NewDeleteAction(servicesResource, c.ns, name), &core.Service{}) return err } @@ -122,17 +122,17 @@ func (c *FakeServices) Delete(name string, options *v1.DeleteOptions) error { func (c *FakeServices) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { action := testing.NewDeleteCollectionAction(servicesResource, c.ns, listOptions) - _, err := c.Fake.Invokes(action, &api.ServiceList{}) + _, err := c.Fake.Invokes(action, &core.ServiceList{}) return err } // Patch applies the patch and returns the patched service. -func (c *FakeServices) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.Service, err error) { +func (c *FakeServices) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.Service, err error) { obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(servicesResource, c.ns, name, data, subresources...), &api.Service{}) + Invokes(testing.NewPatchSubresourceAction(servicesResource, c.ns, name, data, subresources...), &core.Service{}) if obj == nil { return nil, err } - return obj.(*api.Service), err + return obj.(*core.Service), err } diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_serviceaccount.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_serviceaccount.go index 07b893b0d69..d71ad3e4816 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_serviceaccount.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake/fake_serviceaccount.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,7 +23,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // FakeServiceAccounts implements ServiceAccountInterface @@ -37,20 +37,20 @@ var serviceaccountsResource = schema.GroupVersionResource{Group: "", Version: "" var serviceaccountsKind = schema.GroupVersionKind{Group: "", Version: "", Kind: "ServiceAccount"} // Get takes name of the serviceAccount, and returns the corresponding serviceAccount object, and an error if there is any. -func (c *FakeServiceAccounts) Get(name string, options v1.GetOptions) (result *api.ServiceAccount, err error) { +func (c *FakeServiceAccounts) Get(name string, options v1.GetOptions) (result *core.ServiceAccount, err error) { obj, err := c.Fake. - Invokes(testing.NewGetAction(serviceaccountsResource, c.ns, name), &api.ServiceAccount{}) + Invokes(testing.NewGetAction(serviceaccountsResource, c.ns, name), &core.ServiceAccount{}) if obj == nil { return nil, err } - return obj.(*api.ServiceAccount), err + return obj.(*core.ServiceAccount), err } // List takes label and field selectors, and returns the list of ServiceAccounts that match those selectors. -func (c *FakeServiceAccounts) List(opts v1.ListOptions) (result *api.ServiceAccountList, err error) { +func (c *FakeServiceAccounts) List(opts v1.ListOptions) (result *core.ServiceAccountList, err error) { obj, err := c.Fake. - Invokes(testing.NewListAction(serviceaccountsResource, serviceaccountsKind, c.ns, opts), &api.ServiceAccountList{}) + Invokes(testing.NewListAction(serviceaccountsResource, serviceaccountsKind, c.ns, opts), &core.ServiceAccountList{}) if obj == nil { return nil, err @@ -60,8 +60,8 @@ func (c *FakeServiceAccounts) List(opts v1.ListOptions) (result *api.ServiceAcco if label == nil { label = labels.Everything() } - list := &api.ServiceAccountList{} - for _, item := range obj.(*api.ServiceAccountList).Items { + list := &core.ServiceAccountList{} + for _, item := range obj.(*core.ServiceAccountList).Items { if label.Matches(labels.Set(item.Labels)) { list.Items = append(list.Items, item) } @@ -77,31 +77,31 @@ func (c *FakeServiceAccounts) Watch(opts v1.ListOptions) (watch.Interface, error } // Create takes the representation of a serviceAccount and creates it. Returns the server's representation of the serviceAccount, and an error, if there is any. -func (c *FakeServiceAccounts) Create(serviceAccount *api.ServiceAccount) (result *api.ServiceAccount, err error) { +func (c *FakeServiceAccounts) Create(serviceAccount *core.ServiceAccount) (result *core.ServiceAccount, err error) { obj, err := c.Fake. - Invokes(testing.NewCreateAction(serviceaccountsResource, c.ns, serviceAccount), &api.ServiceAccount{}) + Invokes(testing.NewCreateAction(serviceaccountsResource, c.ns, serviceAccount), &core.ServiceAccount{}) if obj == nil { return nil, err } - return obj.(*api.ServiceAccount), err + return obj.(*core.ServiceAccount), err } // Update takes the representation of a serviceAccount and updates it. Returns the server's representation of the serviceAccount, and an error, if there is any. -func (c *FakeServiceAccounts) Update(serviceAccount *api.ServiceAccount) (result *api.ServiceAccount, err error) { +func (c *FakeServiceAccounts) Update(serviceAccount *core.ServiceAccount) (result *core.ServiceAccount, err error) { obj, err := c.Fake. - Invokes(testing.NewUpdateAction(serviceaccountsResource, c.ns, serviceAccount), &api.ServiceAccount{}) + Invokes(testing.NewUpdateAction(serviceaccountsResource, c.ns, serviceAccount), &core.ServiceAccount{}) if obj == nil { return nil, err } - return obj.(*api.ServiceAccount), err + return obj.(*core.ServiceAccount), err } // Delete takes name of the serviceAccount and deletes it. Returns an error if one occurs. func (c *FakeServiceAccounts) Delete(name string, options *v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewDeleteAction(serviceaccountsResource, c.ns, name), &api.ServiceAccount{}) + Invokes(testing.NewDeleteAction(serviceaccountsResource, c.ns, name), &core.ServiceAccount{}) return err } @@ -110,17 +110,17 @@ func (c *FakeServiceAccounts) Delete(name string, options *v1.DeleteOptions) err func (c *FakeServiceAccounts) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { action := testing.NewDeleteCollectionAction(serviceaccountsResource, c.ns, listOptions) - _, err := c.Fake.Invokes(action, &api.ServiceAccountList{}) + _, err := c.Fake.Invokes(action, &core.ServiceAccountList{}) return err } // Patch applies the patch and returns the patched serviceAccount. -func (c *FakeServiceAccounts) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.ServiceAccount, err error) { +func (c *FakeServiceAccounts) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.ServiceAccount, err error) { obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(serviceaccountsResource, c.ns, name, data, subresources...), &api.ServiceAccount{}) + Invokes(testing.NewPatchSubresourceAction(serviceaccountsResource, c.ns, name, data, subresources...), &core.ServiceAccount{}) if obj == nil { return nil, err } - return obj.(*api.ServiceAccount), err + return obj.(*core.ServiceAccount), err } diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/generated_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/generated_expansion.go index 6591a3af0da..bba3e73d108 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/generated_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/generated_expansion.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/limitrange.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/limitrange.go index f809d04cb0f..41ab70187b6 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/limitrange.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/limitrange.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,7 +21,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" rest "k8s.io/client-go/rest" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" scheme "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme" ) @@ -33,14 +33,14 @@ type LimitRangesGetter interface { // LimitRangeInterface has methods to work with LimitRange resources. type LimitRangeInterface interface { - Create(*api.LimitRange) (*api.LimitRange, error) - Update(*api.LimitRange) (*api.LimitRange, error) + Create(*core.LimitRange) (*core.LimitRange, error) + Update(*core.LimitRange) (*core.LimitRange, error) Delete(name string, options *v1.DeleteOptions) error DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*api.LimitRange, error) - List(opts v1.ListOptions) (*api.LimitRangeList, error) + Get(name string, options v1.GetOptions) (*core.LimitRange, error) + List(opts v1.ListOptions) (*core.LimitRangeList, error) Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.LimitRange, err error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.LimitRange, err error) LimitRangeExpansion } @@ -59,8 +59,8 @@ func newLimitRanges(c *CoreClient, namespace string) *limitRanges { } // Get takes name of the limitRange, and returns the corresponding limitRange object, and an error if there is any. -func (c *limitRanges) Get(name string, options v1.GetOptions) (result *api.LimitRange, err error) { - result = &api.LimitRange{} +func (c *limitRanges) Get(name string, options v1.GetOptions) (result *core.LimitRange, err error) { + result = &core.LimitRange{} err = c.client.Get(). Namespace(c.ns). Resource("limitranges"). @@ -72,8 +72,8 @@ func (c *limitRanges) Get(name string, options v1.GetOptions) (result *api.Limit } // List takes label and field selectors, and returns the list of LimitRanges that match those selectors. -func (c *limitRanges) List(opts v1.ListOptions) (result *api.LimitRangeList, err error) { - result = &api.LimitRangeList{} +func (c *limitRanges) List(opts v1.ListOptions) (result *core.LimitRangeList, err error) { + result = &core.LimitRangeList{} err = c.client.Get(). Namespace(c.ns). Resource("limitranges"). @@ -94,8 +94,8 @@ func (c *limitRanges) Watch(opts v1.ListOptions) (watch.Interface, error) { } // Create takes the representation of a limitRange and creates it. Returns the server's representation of the limitRange, and an error, if there is any. -func (c *limitRanges) Create(limitRange *api.LimitRange) (result *api.LimitRange, err error) { - result = &api.LimitRange{} +func (c *limitRanges) Create(limitRange *core.LimitRange) (result *core.LimitRange, err error) { + result = &core.LimitRange{} err = c.client.Post(). Namespace(c.ns). Resource("limitranges"). @@ -106,8 +106,8 @@ func (c *limitRanges) Create(limitRange *api.LimitRange) (result *api.LimitRange } // Update takes the representation of a limitRange and updates it. Returns the server's representation of the limitRange, and an error, if there is any. -func (c *limitRanges) Update(limitRange *api.LimitRange) (result *api.LimitRange, err error) { - result = &api.LimitRange{} +func (c *limitRanges) Update(limitRange *core.LimitRange) (result *core.LimitRange, err error) { + result = &core.LimitRange{} err = c.client.Put(). Namespace(c.ns). Resource("limitranges"). @@ -141,8 +141,8 @@ func (c *limitRanges) DeleteCollection(options *v1.DeleteOptions, listOptions v1 } // Patch applies the patch and returns the patched limitRange. -func (c *limitRanges) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.LimitRange, err error) { - result = &api.LimitRange{} +func (c *limitRanges) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.LimitRange, err error) { + result = &core.LimitRange{} err = c.client.Patch(pt). Namespace(c.ns). Resource("limitranges"). diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/namespace.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/namespace.go index fff41e42061..9e2e54b1ffe 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/namespace.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/namespace.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,7 +21,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" rest "k8s.io/client-go/rest" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" scheme "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme" ) @@ -33,15 +33,15 @@ type NamespacesGetter interface { // NamespaceInterface has methods to work with Namespace resources. type NamespaceInterface interface { - Create(*api.Namespace) (*api.Namespace, error) - Update(*api.Namespace) (*api.Namespace, error) - UpdateStatus(*api.Namespace) (*api.Namespace, error) + Create(*core.Namespace) (*core.Namespace, error) + Update(*core.Namespace) (*core.Namespace, error) + UpdateStatus(*core.Namespace) (*core.Namespace, error) Delete(name string, options *v1.DeleteOptions) error DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*api.Namespace, error) - List(opts v1.ListOptions) (*api.NamespaceList, error) + Get(name string, options v1.GetOptions) (*core.Namespace, error) + List(opts v1.ListOptions) (*core.NamespaceList, error) Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.Namespace, err error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.Namespace, err error) NamespaceExpansion } @@ -58,8 +58,8 @@ func newNamespaces(c *CoreClient) *namespaces { } // Get takes name of the namespace, and returns the corresponding namespace object, and an error if there is any. -func (c *namespaces) Get(name string, options v1.GetOptions) (result *api.Namespace, err error) { - result = &api.Namespace{} +func (c *namespaces) Get(name string, options v1.GetOptions) (result *core.Namespace, err error) { + result = &core.Namespace{} err = c.client.Get(). Resource("namespaces"). Name(name). @@ -70,8 +70,8 @@ func (c *namespaces) Get(name string, options v1.GetOptions) (result *api.Namesp } // List takes label and field selectors, and returns the list of Namespaces that match those selectors. -func (c *namespaces) List(opts v1.ListOptions) (result *api.NamespaceList, err error) { - result = &api.NamespaceList{} +func (c *namespaces) List(opts v1.ListOptions) (result *core.NamespaceList, err error) { + result = &core.NamespaceList{} err = c.client.Get(). Resource("namespaces"). VersionedParams(&opts, scheme.ParameterCodec). @@ -90,8 +90,8 @@ func (c *namespaces) Watch(opts v1.ListOptions) (watch.Interface, error) { } // Create takes the representation of a namespace and creates it. Returns the server's representation of the namespace, and an error, if there is any. -func (c *namespaces) Create(namespace *api.Namespace) (result *api.Namespace, err error) { - result = &api.Namespace{} +func (c *namespaces) Create(namespace *core.Namespace) (result *core.Namespace, err error) { + result = &core.Namespace{} err = c.client.Post(). Resource("namespaces"). Body(namespace). @@ -101,8 +101,8 @@ func (c *namespaces) Create(namespace *api.Namespace) (result *api.Namespace, er } // Update takes the representation of a namespace and updates it. Returns the server's representation of the namespace, and an error, if there is any. -func (c *namespaces) Update(namespace *api.Namespace) (result *api.Namespace, err error) { - result = &api.Namespace{} +func (c *namespaces) Update(namespace *core.Namespace) (result *core.Namespace, err error) { + result = &core.Namespace{} err = c.client.Put(). Resource("namespaces"). Name(namespace.Name). @@ -115,8 +115,8 @@ func (c *namespaces) Update(namespace *api.Namespace) (result *api.Namespace, er // UpdateStatus was generated because the type contains a Status member. // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *namespaces) UpdateStatus(namespace *api.Namespace) (result *api.Namespace, err error) { - result = &api.Namespace{} +func (c *namespaces) UpdateStatus(namespace *core.Namespace) (result *core.Namespace, err error) { + result = &core.Namespace{} err = c.client.Put(). Resource("namespaces"). Name(namespace.Name). @@ -148,8 +148,8 @@ func (c *namespaces) DeleteCollection(options *v1.DeleteOptions, listOptions v1. } // Patch applies the patch and returns the patched namespace. -func (c *namespaces) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.Namespace, err error) { - result = &api.Namespace{} +func (c *namespaces) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.Namespace, err error) { + result = &core.Namespace{} err = c.client.Patch(pt). Resource("namespaces"). SubResource(subresources...). diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/namespace_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/namespace_expansion.go index 456de1cdfcd..29c7bcf2e24 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/namespace_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/namespace_expansion.go @@ -16,7 +16,9 @@ limitations under the License. package internalversion -import "k8s.io/kubernetes/pkg/api" +import ( + api "k8s.io/kubernetes/pkg/apis/core" +) // The NamespaceExpansion interface allows manually adding extra methods to the NamespaceInterface. type NamespaceExpansion interface { diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/node.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/node.go index ee11691b8a7..a315e0f5c68 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/node.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/node.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,7 +21,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" rest "k8s.io/client-go/rest" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" scheme "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme" ) @@ -33,15 +33,15 @@ type NodesGetter interface { // NodeInterface has methods to work with Node resources. type NodeInterface interface { - Create(*api.Node) (*api.Node, error) - Update(*api.Node) (*api.Node, error) - UpdateStatus(*api.Node) (*api.Node, error) + Create(*core.Node) (*core.Node, error) + Update(*core.Node) (*core.Node, error) + UpdateStatus(*core.Node) (*core.Node, error) Delete(name string, options *v1.DeleteOptions) error DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*api.Node, error) - List(opts v1.ListOptions) (*api.NodeList, error) + Get(name string, options v1.GetOptions) (*core.Node, error) + List(opts v1.ListOptions) (*core.NodeList, error) Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.Node, err error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.Node, err error) NodeExpansion } @@ -58,8 +58,8 @@ func newNodes(c *CoreClient) *nodes { } // Get takes name of the node, and returns the corresponding node object, and an error if there is any. -func (c *nodes) Get(name string, options v1.GetOptions) (result *api.Node, err error) { - result = &api.Node{} +func (c *nodes) Get(name string, options v1.GetOptions) (result *core.Node, err error) { + result = &core.Node{} err = c.client.Get(). Resource("nodes"). Name(name). @@ -70,8 +70,8 @@ func (c *nodes) Get(name string, options v1.GetOptions) (result *api.Node, err e } // List takes label and field selectors, and returns the list of Nodes that match those selectors. -func (c *nodes) List(opts v1.ListOptions) (result *api.NodeList, err error) { - result = &api.NodeList{} +func (c *nodes) List(opts v1.ListOptions) (result *core.NodeList, err error) { + result = &core.NodeList{} err = c.client.Get(). Resource("nodes"). VersionedParams(&opts, scheme.ParameterCodec). @@ -90,8 +90,8 @@ func (c *nodes) Watch(opts v1.ListOptions) (watch.Interface, error) { } // Create takes the representation of a node and creates it. Returns the server's representation of the node, and an error, if there is any. -func (c *nodes) Create(node *api.Node) (result *api.Node, err error) { - result = &api.Node{} +func (c *nodes) Create(node *core.Node) (result *core.Node, err error) { + result = &core.Node{} err = c.client.Post(). Resource("nodes"). Body(node). @@ -101,8 +101,8 @@ func (c *nodes) Create(node *api.Node) (result *api.Node, err error) { } // Update takes the representation of a node and updates it. Returns the server's representation of the node, and an error, if there is any. -func (c *nodes) Update(node *api.Node) (result *api.Node, err error) { - result = &api.Node{} +func (c *nodes) Update(node *core.Node) (result *core.Node, err error) { + result = &core.Node{} err = c.client.Put(). Resource("nodes"). Name(node.Name). @@ -115,8 +115,8 @@ func (c *nodes) Update(node *api.Node) (result *api.Node, err error) { // UpdateStatus was generated because the type contains a Status member. // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *nodes) UpdateStatus(node *api.Node) (result *api.Node, err error) { - result = &api.Node{} +func (c *nodes) UpdateStatus(node *core.Node) (result *core.Node, err error) { + result = &core.Node{} err = c.client.Put(). Resource("nodes"). Name(node.Name). @@ -148,8 +148,8 @@ func (c *nodes) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListO } // Patch applies the patch and returns the patched node. -func (c *nodes) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.Node, err error) { - result = &api.Node{} +func (c *nodes) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.Node, err error) { + result = &core.Node{} err = c.client.Patch(pt). Resource("nodes"). SubResource(subresources...). diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/node_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/node_expansion.go index b02fa083ba6..8e29d5f1ef0 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/node_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/node_expansion.go @@ -18,7 +18,7 @@ package internalversion import ( "k8s.io/apimachinery/pkg/types" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // The NodeExpansion interface allows manually adding extra methods to the NodeInterface. diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/persistentvolume.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/persistentvolume.go index 92a2657d451..7ea769f1a0d 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/persistentvolume.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/persistentvolume.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,7 +21,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" rest "k8s.io/client-go/rest" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" scheme "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme" ) @@ -33,15 +33,15 @@ type PersistentVolumesGetter interface { // PersistentVolumeInterface has methods to work with PersistentVolume resources. type PersistentVolumeInterface interface { - Create(*api.PersistentVolume) (*api.PersistentVolume, error) - Update(*api.PersistentVolume) (*api.PersistentVolume, error) - UpdateStatus(*api.PersistentVolume) (*api.PersistentVolume, error) + Create(*core.PersistentVolume) (*core.PersistentVolume, error) + Update(*core.PersistentVolume) (*core.PersistentVolume, error) + UpdateStatus(*core.PersistentVolume) (*core.PersistentVolume, error) Delete(name string, options *v1.DeleteOptions) error DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*api.PersistentVolume, error) - List(opts v1.ListOptions) (*api.PersistentVolumeList, error) + Get(name string, options v1.GetOptions) (*core.PersistentVolume, error) + List(opts v1.ListOptions) (*core.PersistentVolumeList, error) Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.PersistentVolume, err error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.PersistentVolume, err error) PersistentVolumeExpansion } @@ -58,8 +58,8 @@ func newPersistentVolumes(c *CoreClient) *persistentVolumes { } // Get takes name of the persistentVolume, and returns the corresponding persistentVolume object, and an error if there is any. -func (c *persistentVolumes) Get(name string, options v1.GetOptions) (result *api.PersistentVolume, err error) { - result = &api.PersistentVolume{} +func (c *persistentVolumes) Get(name string, options v1.GetOptions) (result *core.PersistentVolume, err error) { + result = &core.PersistentVolume{} err = c.client.Get(). Resource("persistentvolumes"). Name(name). @@ -70,8 +70,8 @@ func (c *persistentVolumes) Get(name string, options v1.GetOptions) (result *api } // List takes label and field selectors, and returns the list of PersistentVolumes that match those selectors. -func (c *persistentVolumes) List(opts v1.ListOptions) (result *api.PersistentVolumeList, err error) { - result = &api.PersistentVolumeList{} +func (c *persistentVolumes) List(opts v1.ListOptions) (result *core.PersistentVolumeList, err error) { + result = &core.PersistentVolumeList{} err = c.client.Get(). Resource("persistentvolumes"). VersionedParams(&opts, scheme.ParameterCodec). @@ -90,8 +90,8 @@ func (c *persistentVolumes) Watch(opts v1.ListOptions) (watch.Interface, error) } // Create takes the representation of a persistentVolume and creates it. Returns the server's representation of the persistentVolume, and an error, if there is any. -func (c *persistentVolumes) Create(persistentVolume *api.PersistentVolume) (result *api.PersistentVolume, err error) { - result = &api.PersistentVolume{} +func (c *persistentVolumes) Create(persistentVolume *core.PersistentVolume) (result *core.PersistentVolume, err error) { + result = &core.PersistentVolume{} err = c.client.Post(). Resource("persistentvolumes"). Body(persistentVolume). @@ -101,8 +101,8 @@ func (c *persistentVolumes) Create(persistentVolume *api.PersistentVolume) (resu } // Update takes the representation of a persistentVolume and updates it. Returns the server's representation of the persistentVolume, and an error, if there is any. -func (c *persistentVolumes) Update(persistentVolume *api.PersistentVolume) (result *api.PersistentVolume, err error) { - result = &api.PersistentVolume{} +func (c *persistentVolumes) Update(persistentVolume *core.PersistentVolume) (result *core.PersistentVolume, err error) { + result = &core.PersistentVolume{} err = c.client.Put(). Resource("persistentvolumes"). Name(persistentVolume.Name). @@ -115,8 +115,8 @@ func (c *persistentVolumes) Update(persistentVolume *api.PersistentVolume) (resu // UpdateStatus was generated because the type contains a Status member. // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *persistentVolumes) UpdateStatus(persistentVolume *api.PersistentVolume) (result *api.PersistentVolume, err error) { - result = &api.PersistentVolume{} +func (c *persistentVolumes) UpdateStatus(persistentVolume *core.PersistentVolume) (result *core.PersistentVolume, err error) { + result = &core.PersistentVolume{} err = c.client.Put(). Resource("persistentvolumes"). Name(persistentVolume.Name). @@ -148,8 +148,8 @@ func (c *persistentVolumes) DeleteCollection(options *v1.DeleteOptions, listOpti } // Patch applies the patch and returns the patched persistentVolume. -func (c *persistentVolumes) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.PersistentVolume, err error) { - result = &api.PersistentVolume{} +func (c *persistentVolumes) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.PersistentVolume, err error) { + result = &core.PersistentVolume{} err = c.client.Patch(pt). Resource("persistentvolumes"). SubResource(subresources...). diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/persistentvolumeclaim.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/persistentvolumeclaim.go index d0db131d77f..8c94f646554 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/persistentvolumeclaim.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/persistentvolumeclaim.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,7 +21,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" rest "k8s.io/client-go/rest" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" scheme "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme" ) @@ -33,15 +33,15 @@ type PersistentVolumeClaimsGetter interface { // PersistentVolumeClaimInterface has methods to work with PersistentVolumeClaim resources. type PersistentVolumeClaimInterface interface { - Create(*api.PersistentVolumeClaim) (*api.PersistentVolumeClaim, error) - Update(*api.PersistentVolumeClaim) (*api.PersistentVolumeClaim, error) - UpdateStatus(*api.PersistentVolumeClaim) (*api.PersistentVolumeClaim, error) + Create(*core.PersistentVolumeClaim) (*core.PersistentVolumeClaim, error) + Update(*core.PersistentVolumeClaim) (*core.PersistentVolumeClaim, error) + UpdateStatus(*core.PersistentVolumeClaim) (*core.PersistentVolumeClaim, error) Delete(name string, options *v1.DeleteOptions) error DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*api.PersistentVolumeClaim, error) - List(opts v1.ListOptions) (*api.PersistentVolumeClaimList, error) + Get(name string, options v1.GetOptions) (*core.PersistentVolumeClaim, error) + List(opts v1.ListOptions) (*core.PersistentVolumeClaimList, error) Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.PersistentVolumeClaim, err error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.PersistentVolumeClaim, err error) PersistentVolumeClaimExpansion } @@ -60,8 +60,8 @@ func newPersistentVolumeClaims(c *CoreClient, namespace string) *persistentVolum } // Get takes name of the persistentVolumeClaim, and returns the corresponding persistentVolumeClaim object, and an error if there is any. -func (c *persistentVolumeClaims) Get(name string, options v1.GetOptions) (result *api.PersistentVolumeClaim, err error) { - result = &api.PersistentVolumeClaim{} +func (c *persistentVolumeClaims) Get(name string, options v1.GetOptions) (result *core.PersistentVolumeClaim, err error) { + result = &core.PersistentVolumeClaim{} err = c.client.Get(). Namespace(c.ns). Resource("persistentvolumeclaims"). @@ -73,8 +73,8 @@ func (c *persistentVolumeClaims) Get(name string, options v1.GetOptions) (result } // List takes label and field selectors, and returns the list of PersistentVolumeClaims that match those selectors. -func (c *persistentVolumeClaims) List(opts v1.ListOptions) (result *api.PersistentVolumeClaimList, err error) { - result = &api.PersistentVolumeClaimList{} +func (c *persistentVolumeClaims) List(opts v1.ListOptions) (result *core.PersistentVolumeClaimList, err error) { + result = &core.PersistentVolumeClaimList{} err = c.client.Get(). Namespace(c.ns). Resource("persistentvolumeclaims"). @@ -95,8 +95,8 @@ func (c *persistentVolumeClaims) Watch(opts v1.ListOptions) (watch.Interface, er } // Create takes the representation of a persistentVolumeClaim and creates it. Returns the server's representation of the persistentVolumeClaim, and an error, if there is any. -func (c *persistentVolumeClaims) Create(persistentVolumeClaim *api.PersistentVolumeClaim) (result *api.PersistentVolumeClaim, err error) { - result = &api.PersistentVolumeClaim{} +func (c *persistentVolumeClaims) Create(persistentVolumeClaim *core.PersistentVolumeClaim) (result *core.PersistentVolumeClaim, err error) { + result = &core.PersistentVolumeClaim{} err = c.client.Post(). Namespace(c.ns). Resource("persistentvolumeclaims"). @@ -107,8 +107,8 @@ func (c *persistentVolumeClaims) Create(persistentVolumeClaim *api.PersistentVol } // Update takes the representation of a persistentVolumeClaim and updates it. Returns the server's representation of the persistentVolumeClaim, and an error, if there is any. -func (c *persistentVolumeClaims) Update(persistentVolumeClaim *api.PersistentVolumeClaim) (result *api.PersistentVolumeClaim, err error) { - result = &api.PersistentVolumeClaim{} +func (c *persistentVolumeClaims) Update(persistentVolumeClaim *core.PersistentVolumeClaim) (result *core.PersistentVolumeClaim, err error) { + result = &core.PersistentVolumeClaim{} err = c.client.Put(). Namespace(c.ns). Resource("persistentvolumeclaims"). @@ -122,8 +122,8 @@ func (c *persistentVolumeClaims) Update(persistentVolumeClaim *api.PersistentVol // UpdateStatus was generated because the type contains a Status member. // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *persistentVolumeClaims) UpdateStatus(persistentVolumeClaim *api.PersistentVolumeClaim) (result *api.PersistentVolumeClaim, err error) { - result = &api.PersistentVolumeClaim{} +func (c *persistentVolumeClaims) UpdateStatus(persistentVolumeClaim *core.PersistentVolumeClaim) (result *core.PersistentVolumeClaim, err error) { + result = &core.PersistentVolumeClaim{} err = c.client.Put(). Namespace(c.ns). Resource("persistentvolumeclaims"). @@ -158,8 +158,8 @@ func (c *persistentVolumeClaims) DeleteCollection(options *v1.DeleteOptions, lis } // Patch applies the patch and returns the patched persistentVolumeClaim. -func (c *persistentVolumeClaims) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.PersistentVolumeClaim, err error) { - result = &api.PersistentVolumeClaim{} +func (c *persistentVolumeClaims) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.PersistentVolumeClaim, err error) { + result = &core.PersistentVolumeClaim{} err = c.client.Patch(pt). Namespace(c.ns). Resource("persistentvolumeclaims"). diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/pod.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/pod.go index b662689cf50..e7f6a4099bd 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/pod.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/pod.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,7 +21,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" rest "k8s.io/client-go/rest" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" scheme "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme" ) @@ -33,15 +33,15 @@ type PodsGetter interface { // PodInterface has methods to work with Pod resources. type PodInterface interface { - Create(*api.Pod) (*api.Pod, error) - Update(*api.Pod) (*api.Pod, error) - UpdateStatus(*api.Pod) (*api.Pod, error) + Create(*core.Pod) (*core.Pod, error) + Update(*core.Pod) (*core.Pod, error) + UpdateStatus(*core.Pod) (*core.Pod, error) Delete(name string, options *v1.DeleteOptions) error DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*api.Pod, error) - List(opts v1.ListOptions) (*api.PodList, error) + Get(name string, options v1.GetOptions) (*core.Pod, error) + List(opts v1.ListOptions) (*core.PodList, error) Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.Pod, err error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.Pod, err error) PodExpansion } @@ -60,8 +60,8 @@ func newPods(c *CoreClient, namespace string) *pods { } // Get takes name of the pod, and returns the corresponding pod object, and an error if there is any. -func (c *pods) Get(name string, options v1.GetOptions) (result *api.Pod, err error) { - result = &api.Pod{} +func (c *pods) Get(name string, options v1.GetOptions) (result *core.Pod, err error) { + result = &core.Pod{} err = c.client.Get(). Namespace(c.ns). Resource("pods"). @@ -73,8 +73,8 @@ func (c *pods) Get(name string, options v1.GetOptions) (result *api.Pod, err err } // List takes label and field selectors, and returns the list of Pods that match those selectors. -func (c *pods) List(opts v1.ListOptions) (result *api.PodList, err error) { - result = &api.PodList{} +func (c *pods) List(opts v1.ListOptions) (result *core.PodList, err error) { + result = &core.PodList{} err = c.client.Get(). Namespace(c.ns). Resource("pods"). @@ -95,8 +95,8 @@ func (c *pods) Watch(opts v1.ListOptions) (watch.Interface, error) { } // Create takes the representation of a pod and creates it. Returns the server's representation of the pod, and an error, if there is any. -func (c *pods) Create(pod *api.Pod) (result *api.Pod, err error) { - result = &api.Pod{} +func (c *pods) Create(pod *core.Pod) (result *core.Pod, err error) { + result = &core.Pod{} err = c.client.Post(). Namespace(c.ns). Resource("pods"). @@ -107,8 +107,8 @@ func (c *pods) Create(pod *api.Pod) (result *api.Pod, err error) { } // Update takes the representation of a pod and updates it. Returns the server's representation of the pod, and an error, if there is any. -func (c *pods) Update(pod *api.Pod) (result *api.Pod, err error) { - result = &api.Pod{} +func (c *pods) Update(pod *core.Pod) (result *core.Pod, err error) { + result = &core.Pod{} err = c.client.Put(). Namespace(c.ns). Resource("pods"). @@ -122,8 +122,8 @@ func (c *pods) Update(pod *api.Pod) (result *api.Pod, err error) { // UpdateStatus was generated because the type contains a Status member. // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *pods) UpdateStatus(pod *api.Pod) (result *api.Pod, err error) { - result = &api.Pod{} +func (c *pods) UpdateStatus(pod *core.Pod) (result *core.Pod, err error) { + result = &core.Pod{} err = c.client.Put(). Namespace(c.ns). Resource("pods"). @@ -158,8 +158,8 @@ func (c *pods) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOp } // Patch applies the patch and returns the patched pod. -func (c *pods) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.Pod, err error) { - result = &api.Pod{} +func (c *pods) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.Pod, err error) { + result = &core.Pod{} err = c.client.Patch(pt). Namespace(c.ns). Resource("pods"). diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/pod_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/pod_expansion.go index d6ea139a46b..86fead7cced 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/pod_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/pod_expansion.go @@ -18,7 +18,8 @@ package internalversion import ( restclient "k8s.io/client-go/rest" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" ) // The PodExpansion interface allows manually adding extra methods to the PodInterface. @@ -34,5 +35,5 @@ func (c *pods) Bind(binding *api.Binding) error { // Get constructs a request for getting the logs for a pod func (c *pods) GetLogs(name string, opts *api.PodLogOptions) *restclient.Request { - return c.client.Get().Namespace(c.ns).Name(name).Resource("pods").SubResource("log").VersionedParams(opts, api.ParameterCodec) + return c.client.Get().Namespace(c.ns).Name(name).Resource("pods").SubResource("log").VersionedParams(opts, legacyscheme.ParameterCodec) } diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/podtemplate.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/podtemplate.go index 8a940bf6b72..0919e64081d 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/podtemplate.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/podtemplate.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,7 +21,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" rest "k8s.io/client-go/rest" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" scheme "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme" ) @@ -33,14 +33,14 @@ type PodTemplatesGetter interface { // PodTemplateInterface has methods to work with PodTemplate resources. type PodTemplateInterface interface { - Create(*api.PodTemplate) (*api.PodTemplate, error) - Update(*api.PodTemplate) (*api.PodTemplate, error) + Create(*core.PodTemplate) (*core.PodTemplate, error) + Update(*core.PodTemplate) (*core.PodTemplate, error) Delete(name string, options *v1.DeleteOptions) error DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*api.PodTemplate, error) - List(opts v1.ListOptions) (*api.PodTemplateList, error) + Get(name string, options v1.GetOptions) (*core.PodTemplate, error) + List(opts v1.ListOptions) (*core.PodTemplateList, error) Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.PodTemplate, err error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.PodTemplate, err error) PodTemplateExpansion } @@ -59,8 +59,8 @@ func newPodTemplates(c *CoreClient, namespace string) *podTemplates { } // Get takes name of the podTemplate, and returns the corresponding podTemplate object, and an error if there is any. -func (c *podTemplates) Get(name string, options v1.GetOptions) (result *api.PodTemplate, err error) { - result = &api.PodTemplate{} +func (c *podTemplates) Get(name string, options v1.GetOptions) (result *core.PodTemplate, err error) { + result = &core.PodTemplate{} err = c.client.Get(). Namespace(c.ns). Resource("podtemplates"). @@ -72,8 +72,8 @@ func (c *podTemplates) Get(name string, options v1.GetOptions) (result *api.PodT } // List takes label and field selectors, and returns the list of PodTemplates that match those selectors. -func (c *podTemplates) List(opts v1.ListOptions) (result *api.PodTemplateList, err error) { - result = &api.PodTemplateList{} +func (c *podTemplates) List(opts v1.ListOptions) (result *core.PodTemplateList, err error) { + result = &core.PodTemplateList{} err = c.client.Get(). Namespace(c.ns). Resource("podtemplates"). @@ -94,8 +94,8 @@ func (c *podTemplates) Watch(opts v1.ListOptions) (watch.Interface, error) { } // Create takes the representation of a podTemplate and creates it. Returns the server's representation of the podTemplate, and an error, if there is any. -func (c *podTemplates) Create(podTemplate *api.PodTemplate) (result *api.PodTemplate, err error) { - result = &api.PodTemplate{} +func (c *podTemplates) Create(podTemplate *core.PodTemplate) (result *core.PodTemplate, err error) { + result = &core.PodTemplate{} err = c.client.Post(). Namespace(c.ns). Resource("podtemplates"). @@ -106,8 +106,8 @@ func (c *podTemplates) Create(podTemplate *api.PodTemplate) (result *api.PodTemp } // Update takes the representation of a podTemplate and updates it. Returns the server's representation of the podTemplate, and an error, if there is any. -func (c *podTemplates) Update(podTemplate *api.PodTemplate) (result *api.PodTemplate, err error) { - result = &api.PodTemplate{} +func (c *podTemplates) Update(podTemplate *core.PodTemplate) (result *core.PodTemplate, err error) { + result = &core.PodTemplate{} err = c.client.Put(). Namespace(c.ns). Resource("podtemplates"). @@ -141,8 +141,8 @@ func (c *podTemplates) DeleteCollection(options *v1.DeleteOptions, listOptions v } // Patch applies the patch and returns the patched podTemplate. -func (c *podTemplates) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.PodTemplate, err error) { - result = &api.PodTemplate{} +func (c *podTemplates) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.PodTemplate, err error) { + result = &core.PodTemplate{} err = c.client.Patch(pt). Namespace(c.ns). Resource("podtemplates"). diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/replicationcontroller.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/replicationcontroller.go index 3be51c316fe..9302f18f51a 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/replicationcontroller.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/replicationcontroller.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,8 +21,8 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" rest "k8s.io/client-go/rest" - api "k8s.io/kubernetes/pkg/api" - extensions "k8s.io/kubernetes/pkg/apis/extensions" + autoscaling "k8s.io/kubernetes/pkg/apis/autoscaling" + core "k8s.io/kubernetes/pkg/apis/core" scheme "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme" ) @@ -34,17 +34,17 @@ type ReplicationControllersGetter interface { // ReplicationControllerInterface has methods to work with ReplicationController resources. type ReplicationControllerInterface interface { - Create(*api.ReplicationController) (*api.ReplicationController, error) - Update(*api.ReplicationController) (*api.ReplicationController, error) - UpdateStatus(*api.ReplicationController) (*api.ReplicationController, error) + Create(*core.ReplicationController) (*core.ReplicationController, error) + Update(*core.ReplicationController) (*core.ReplicationController, error) + UpdateStatus(*core.ReplicationController) (*core.ReplicationController, error) Delete(name string, options *v1.DeleteOptions) error DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*api.ReplicationController, error) - List(opts v1.ListOptions) (*api.ReplicationControllerList, error) + Get(name string, options v1.GetOptions) (*core.ReplicationController, error) + List(opts v1.ListOptions) (*core.ReplicationControllerList, error) Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.ReplicationController, err error) - GetScale(replicationControllerName string, options v1.GetOptions) (*extensions.Scale, error) - UpdateScale(replicationControllerName string, scale *extensions.Scale) (*extensions.Scale, error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.ReplicationController, err error) + GetScale(replicationControllerName string, options v1.GetOptions) (*autoscaling.Scale, error) + UpdateScale(replicationControllerName string, scale *autoscaling.Scale) (*autoscaling.Scale, error) ReplicationControllerExpansion } @@ -64,8 +64,8 @@ func newReplicationControllers(c *CoreClient, namespace string) *replicationCont } // Get takes name of the replicationController, and returns the corresponding replicationController object, and an error if there is any. -func (c *replicationControllers) Get(name string, options v1.GetOptions) (result *api.ReplicationController, err error) { - result = &api.ReplicationController{} +func (c *replicationControllers) Get(name string, options v1.GetOptions) (result *core.ReplicationController, err error) { + result = &core.ReplicationController{} err = c.client.Get(). Namespace(c.ns). Resource("replicationcontrollers"). @@ -77,8 +77,8 @@ func (c *replicationControllers) Get(name string, options v1.GetOptions) (result } // List takes label and field selectors, and returns the list of ReplicationControllers that match those selectors. -func (c *replicationControllers) List(opts v1.ListOptions) (result *api.ReplicationControllerList, err error) { - result = &api.ReplicationControllerList{} +func (c *replicationControllers) List(opts v1.ListOptions) (result *core.ReplicationControllerList, err error) { + result = &core.ReplicationControllerList{} err = c.client.Get(). Namespace(c.ns). Resource("replicationcontrollers"). @@ -99,8 +99,8 @@ func (c *replicationControllers) Watch(opts v1.ListOptions) (watch.Interface, er } // Create takes the representation of a replicationController and creates it. Returns the server's representation of the replicationController, and an error, if there is any. -func (c *replicationControllers) Create(replicationController *api.ReplicationController) (result *api.ReplicationController, err error) { - result = &api.ReplicationController{} +func (c *replicationControllers) Create(replicationController *core.ReplicationController) (result *core.ReplicationController, err error) { + result = &core.ReplicationController{} err = c.client.Post(). Namespace(c.ns). Resource("replicationcontrollers"). @@ -111,8 +111,8 @@ func (c *replicationControllers) Create(replicationController *api.ReplicationCo } // Update takes the representation of a replicationController and updates it. Returns the server's representation of the replicationController, and an error, if there is any. -func (c *replicationControllers) Update(replicationController *api.ReplicationController) (result *api.ReplicationController, err error) { - result = &api.ReplicationController{} +func (c *replicationControllers) Update(replicationController *core.ReplicationController) (result *core.ReplicationController, err error) { + result = &core.ReplicationController{} err = c.client.Put(). Namespace(c.ns). Resource("replicationcontrollers"). @@ -126,8 +126,8 @@ func (c *replicationControllers) Update(replicationController *api.ReplicationCo // UpdateStatus was generated because the type contains a Status member. // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *replicationControllers) UpdateStatus(replicationController *api.ReplicationController) (result *api.ReplicationController, err error) { - result = &api.ReplicationController{} +func (c *replicationControllers) UpdateStatus(replicationController *core.ReplicationController) (result *core.ReplicationController, err error) { + result = &core.ReplicationController{} err = c.client.Put(). Namespace(c.ns). Resource("replicationcontrollers"). @@ -162,8 +162,8 @@ func (c *replicationControllers) DeleteCollection(options *v1.DeleteOptions, lis } // Patch applies the patch and returns the patched replicationController. -func (c *replicationControllers) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.ReplicationController, err error) { - result = &api.ReplicationController{} +func (c *replicationControllers) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.ReplicationController, err error) { + result = &core.ReplicationController{} err = c.client.Patch(pt). Namespace(c.ns). Resource("replicationcontrollers"). @@ -175,9 +175,9 @@ func (c *replicationControllers) Patch(name string, pt types.PatchType, data []b return } -// GetScale takes name of the replicationController, and returns the corresponding extensions.Scale object, and an error if there is any. -func (c *replicationControllers) GetScale(replicationControllerName string, options v1.GetOptions) (result *extensions.Scale, err error) { - result = &extensions.Scale{} +// GetScale takes name of the replicationController, and returns the corresponding autoscaling.Scale object, and an error if there is any. +func (c *replicationControllers) GetScale(replicationControllerName string, options v1.GetOptions) (result *autoscaling.Scale, err error) { + result = &autoscaling.Scale{} err = c.client.Get(). Namespace(c.ns). Resource("replicationcontrollers"). @@ -190,8 +190,8 @@ func (c *replicationControllers) GetScale(replicationControllerName string, opti } // UpdateScale takes the top resource name and the representation of a scale and updates it. Returns the server's representation of the scale, and an error, if there is any. -func (c *replicationControllers) UpdateScale(replicationControllerName string, scale *extensions.Scale) (result *extensions.Scale, err error) { - result = &extensions.Scale{} +func (c *replicationControllers) UpdateScale(replicationControllerName string, scale *autoscaling.Scale) (result *autoscaling.Scale, err error) { + result = &autoscaling.Scale{} err = c.client.Put(). Namespace(c.ns). Resource("replicationcontrollers"). diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/resourcequota.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/resourcequota.go index 169f3219afc..20e3ed8e9bd 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/resourcequota.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/resourcequota.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,7 +21,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" rest "k8s.io/client-go/rest" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" scheme "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme" ) @@ -33,15 +33,15 @@ type ResourceQuotasGetter interface { // ResourceQuotaInterface has methods to work with ResourceQuota resources. type ResourceQuotaInterface interface { - Create(*api.ResourceQuota) (*api.ResourceQuota, error) - Update(*api.ResourceQuota) (*api.ResourceQuota, error) - UpdateStatus(*api.ResourceQuota) (*api.ResourceQuota, error) + Create(*core.ResourceQuota) (*core.ResourceQuota, error) + Update(*core.ResourceQuota) (*core.ResourceQuota, error) + UpdateStatus(*core.ResourceQuota) (*core.ResourceQuota, error) Delete(name string, options *v1.DeleteOptions) error DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*api.ResourceQuota, error) - List(opts v1.ListOptions) (*api.ResourceQuotaList, error) + Get(name string, options v1.GetOptions) (*core.ResourceQuota, error) + List(opts v1.ListOptions) (*core.ResourceQuotaList, error) Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.ResourceQuota, err error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.ResourceQuota, err error) ResourceQuotaExpansion } @@ -60,8 +60,8 @@ func newResourceQuotas(c *CoreClient, namespace string) *resourceQuotas { } // Get takes name of the resourceQuota, and returns the corresponding resourceQuota object, and an error if there is any. -func (c *resourceQuotas) Get(name string, options v1.GetOptions) (result *api.ResourceQuota, err error) { - result = &api.ResourceQuota{} +func (c *resourceQuotas) Get(name string, options v1.GetOptions) (result *core.ResourceQuota, err error) { + result = &core.ResourceQuota{} err = c.client.Get(). Namespace(c.ns). Resource("resourcequotas"). @@ -73,8 +73,8 @@ func (c *resourceQuotas) Get(name string, options v1.GetOptions) (result *api.Re } // List takes label and field selectors, and returns the list of ResourceQuotas that match those selectors. -func (c *resourceQuotas) List(opts v1.ListOptions) (result *api.ResourceQuotaList, err error) { - result = &api.ResourceQuotaList{} +func (c *resourceQuotas) List(opts v1.ListOptions) (result *core.ResourceQuotaList, err error) { + result = &core.ResourceQuotaList{} err = c.client.Get(). Namespace(c.ns). Resource("resourcequotas"). @@ -95,8 +95,8 @@ func (c *resourceQuotas) Watch(opts v1.ListOptions) (watch.Interface, error) { } // Create takes the representation of a resourceQuota and creates it. Returns the server's representation of the resourceQuota, and an error, if there is any. -func (c *resourceQuotas) Create(resourceQuota *api.ResourceQuota) (result *api.ResourceQuota, err error) { - result = &api.ResourceQuota{} +func (c *resourceQuotas) Create(resourceQuota *core.ResourceQuota) (result *core.ResourceQuota, err error) { + result = &core.ResourceQuota{} err = c.client.Post(). Namespace(c.ns). Resource("resourcequotas"). @@ -107,8 +107,8 @@ func (c *resourceQuotas) Create(resourceQuota *api.ResourceQuota) (result *api.R } // Update takes the representation of a resourceQuota and updates it. Returns the server's representation of the resourceQuota, and an error, if there is any. -func (c *resourceQuotas) Update(resourceQuota *api.ResourceQuota) (result *api.ResourceQuota, err error) { - result = &api.ResourceQuota{} +func (c *resourceQuotas) Update(resourceQuota *core.ResourceQuota) (result *core.ResourceQuota, err error) { + result = &core.ResourceQuota{} err = c.client.Put(). Namespace(c.ns). Resource("resourcequotas"). @@ -122,8 +122,8 @@ func (c *resourceQuotas) Update(resourceQuota *api.ResourceQuota) (result *api.R // UpdateStatus was generated because the type contains a Status member. // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *resourceQuotas) UpdateStatus(resourceQuota *api.ResourceQuota) (result *api.ResourceQuota, err error) { - result = &api.ResourceQuota{} +func (c *resourceQuotas) UpdateStatus(resourceQuota *core.ResourceQuota) (result *core.ResourceQuota, err error) { + result = &core.ResourceQuota{} err = c.client.Put(). Namespace(c.ns). Resource("resourcequotas"). @@ -158,8 +158,8 @@ func (c *resourceQuotas) DeleteCollection(options *v1.DeleteOptions, listOptions } // Patch applies the patch and returns the patched resourceQuota. -func (c *resourceQuotas) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.ResourceQuota, err error) { - result = &api.ResourceQuota{} +func (c *resourceQuotas) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.ResourceQuota, err error) { + result = &core.ResourceQuota{} err = c.client.Patch(pt). Namespace(c.ns). Resource("resourcequotas"). diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/secret.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/secret.go index 628fa147171..478430f51f8 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/secret.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/secret.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,7 +21,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" rest "k8s.io/client-go/rest" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" scheme "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme" ) @@ -33,14 +33,14 @@ type SecretsGetter interface { // SecretInterface has methods to work with Secret resources. type SecretInterface interface { - Create(*api.Secret) (*api.Secret, error) - Update(*api.Secret) (*api.Secret, error) + Create(*core.Secret) (*core.Secret, error) + Update(*core.Secret) (*core.Secret, error) Delete(name string, options *v1.DeleteOptions) error DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*api.Secret, error) - List(opts v1.ListOptions) (*api.SecretList, error) + Get(name string, options v1.GetOptions) (*core.Secret, error) + List(opts v1.ListOptions) (*core.SecretList, error) Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.Secret, err error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.Secret, err error) SecretExpansion } @@ -59,8 +59,8 @@ func newSecrets(c *CoreClient, namespace string) *secrets { } // Get takes name of the secret, and returns the corresponding secret object, and an error if there is any. -func (c *secrets) Get(name string, options v1.GetOptions) (result *api.Secret, err error) { - result = &api.Secret{} +func (c *secrets) Get(name string, options v1.GetOptions) (result *core.Secret, err error) { + result = &core.Secret{} err = c.client.Get(). Namespace(c.ns). Resource("secrets"). @@ -72,8 +72,8 @@ func (c *secrets) Get(name string, options v1.GetOptions) (result *api.Secret, e } // List takes label and field selectors, and returns the list of Secrets that match those selectors. -func (c *secrets) List(opts v1.ListOptions) (result *api.SecretList, err error) { - result = &api.SecretList{} +func (c *secrets) List(opts v1.ListOptions) (result *core.SecretList, err error) { + result = &core.SecretList{} err = c.client.Get(). Namespace(c.ns). Resource("secrets"). @@ -94,8 +94,8 @@ func (c *secrets) Watch(opts v1.ListOptions) (watch.Interface, error) { } // Create takes the representation of a secret and creates it. Returns the server's representation of the secret, and an error, if there is any. -func (c *secrets) Create(secret *api.Secret) (result *api.Secret, err error) { - result = &api.Secret{} +func (c *secrets) Create(secret *core.Secret) (result *core.Secret, err error) { + result = &core.Secret{} err = c.client.Post(). Namespace(c.ns). Resource("secrets"). @@ -106,8 +106,8 @@ func (c *secrets) Create(secret *api.Secret) (result *api.Secret, err error) { } // Update takes the representation of a secret and updates it. Returns the server's representation of the secret, and an error, if there is any. -func (c *secrets) Update(secret *api.Secret) (result *api.Secret, err error) { - result = &api.Secret{} +func (c *secrets) Update(secret *core.Secret) (result *core.Secret, err error) { + result = &core.Secret{} err = c.client.Put(). Namespace(c.ns). Resource("secrets"). @@ -141,8 +141,8 @@ func (c *secrets) DeleteCollection(options *v1.DeleteOptions, listOptions v1.Lis } // Patch applies the patch and returns the patched secret. -func (c *secrets) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.Secret, err error) { - result = &api.Secret{} +func (c *secrets) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.Secret, err error) { + result = &core.Secret{} err = c.client.Patch(pt). Namespace(c.ns). Resource("secrets"). diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/service.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/service.go index 05bd7855dcf..239feca1f43 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/service.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/service.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,7 +21,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" rest "k8s.io/client-go/rest" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" scheme "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme" ) @@ -33,15 +33,15 @@ type ServicesGetter interface { // ServiceInterface has methods to work with Service resources. type ServiceInterface interface { - Create(*api.Service) (*api.Service, error) - Update(*api.Service) (*api.Service, error) - UpdateStatus(*api.Service) (*api.Service, error) + Create(*core.Service) (*core.Service, error) + Update(*core.Service) (*core.Service, error) + UpdateStatus(*core.Service) (*core.Service, error) Delete(name string, options *v1.DeleteOptions) error DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*api.Service, error) - List(opts v1.ListOptions) (*api.ServiceList, error) + Get(name string, options v1.GetOptions) (*core.Service, error) + List(opts v1.ListOptions) (*core.ServiceList, error) Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.Service, err error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.Service, err error) ServiceExpansion } @@ -60,8 +60,8 @@ func newServices(c *CoreClient, namespace string) *services { } // Get takes name of the service, and returns the corresponding service object, and an error if there is any. -func (c *services) Get(name string, options v1.GetOptions) (result *api.Service, err error) { - result = &api.Service{} +func (c *services) Get(name string, options v1.GetOptions) (result *core.Service, err error) { + result = &core.Service{} err = c.client.Get(). Namespace(c.ns). Resource("services"). @@ -73,8 +73,8 @@ func (c *services) Get(name string, options v1.GetOptions) (result *api.Service, } // List takes label and field selectors, and returns the list of Services that match those selectors. -func (c *services) List(opts v1.ListOptions) (result *api.ServiceList, err error) { - result = &api.ServiceList{} +func (c *services) List(opts v1.ListOptions) (result *core.ServiceList, err error) { + result = &core.ServiceList{} err = c.client.Get(). Namespace(c.ns). Resource("services"). @@ -95,8 +95,8 @@ func (c *services) Watch(opts v1.ListOptions) (watch.Interface, error) { } // Create takes the representation of a service and creates it. Returns the server's representation of the service, and an error, if there is any. -func (c *services) Create(service *api.Service) (result *api.Service, err error) { - result = &api.Service{} +func (c *services) Create(service *core.Service) (result *core.Service, err error) { + result = &core.Service{} err = c.client.Post(). Namespace(c.ns). Resource("services"). @@ -107,8 +107,8 @@ func (c *services) Create(service *api.Service) (result *api.Service, err error) } // Update takes the representation of a service and updates it. Returns the server's representation of the service, and an error, if there is any. -func (c *services) Update(service *api.Service) (result *api.Service, err error) { - result = &api.Service{} +func (c *services) Update(service *core.Service) (result *core.Service, err error) { + result = &core.Service{} err = c.client.Put(). Namespace(c.ns). Resource("services"). @@ -122,8 +122,8 @@ func (c *services) Update(service *api.Service) (result *api.Service, err error) // UpdateStatus was generated because the type contains a Status member. // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *services) UpdateStatus(service *api.Service) (result *api.Service, err error) { - result = &api.Service{} +func (c *services) UpdateStatus(service *core.Service) (result *core.Service, err error) { + result = &core.Service{} err = c.client.Put(). Namespace(c.ns). Resource("services"). @@ -158,8 +158,8 @@ func (c *services) DeleteCollection(options *v1.DeleteOptions, listOptions v1.Li } // Patch applies the patch and returns the patched service. -func (c *services) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.Service, err error) { - result = &api.Service{} +func (c *services) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.Service, err error) { + result = &core.Service{} err = c.client.Patch(pt). Namespace(c.ns). Resource("services"). diff --git a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/serviceaccount.go b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/serviceaccount.go index 3a7bf04aaf3..d405955d172 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/serviceaccount.go +++ b/pkg/client/clientset_generated/internalclientset/typed/core/internalversion/serviceaccount.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,7 +21,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" rest "k8s.io/client-go/rest" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" scheme "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme" ) @@ -33,14 +33,14 @@ type ServiceAccountsGetter interface { // ServiceAccountInterface has methods to work with ServiceAccount resources. type ServiceAccountInterface interface { - Create(*api.ServiceAccount) (*api.ServiceAccount, error) - Update(*api.ServiceAccount) (*api.ServiceAccount, error) + Create(*core.ServiceAccount) (*core.ServiceAccount, error) + Update(*core.ServiceAccount) (*core.ServiceAccount, error) Delete(name string, options *v1.DeleteOptions) error DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*api.ServiceAccount, error) - List(opts v1.ListOptions) (*api.ServiceAccountList, error) + Get(name string, options v1.GetOptions) (*core.ServiceAccount, error) + List(opts v1.ListOptions) (*core.ServiceAccountList, error) Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.ServiceAccount, err error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.ServiceAccount, err error) ServiceAccountExpansion } @@ -59,8 +59,8 @@ func newServiceAccounts(c *CoreClient, namespace string) *serviceAccounts { } // Get takes name of the serviceAccount, and returns the corresponding serviceAccount object, and an error if there is any. -func (c *serviceAccounts) Get(name string, options v1.GetOptions) (result *api.ServiceAccount, err error) { - result = &api.ServiceAccount{} +func (c *serviceAccounts) Get(name string, options v1.GetOptions) (result *core.ServiceAccount, err error) { + result = &core.ServiceAccount{} err = c.client.Get(). Namespace(c.ns). Resource("serviceaccounts"). @@ -72,8 +72,8 @@ func (c *serviceAccounts) Get(name string, options v1.GetOptions) (result *api.S } // List takes label and field selectors, and returns the list of ServiceAccounts that match those selectors. -func (c *serviceAccounts) List(opts v1.ListOptions) (result *api.ServiceAccountList, err error) { - result = &api.ServiceAccountList{} +func (c *serviceAccounts) List(opts v1.ListOptions) (result *core.ServiceAccountList, err error) { + result = &core.ServiceAccountList{} err = c.client.Get(). Namespace(c.ns). Resource("serviceaccounts"). @@ -94,8 +94,8 @@ func (c *serviceAccounts) Watch(opts v1.ListOptions) (watch.Interface, error) { } // Create takes the representation of a serviceAccount and creates it. Returns the server's representation of the serviceAccount, and an error, if there is any. -func (c *serviceAccounts) Create(serviceAccount *api.ServiceAccount) (result *api.ServiceAccount, err error) { - result = &api.ServiceAccount{} +func (c *serviceAccounts) Create(serviceAccount *core.ServiceAccount) (result *core.ServiceAccount, err error) { + result = &core.ServiceAccount{} err = c.client.Post(). Namespace(c.ns). Resource("serviceaccounts"). @@ -106,8 +106,8 @@ func (c *serviceAccounts) Create(serviceAccount *api.ServiceAccount) (result *ap } // Update takes the representation of a serviceAccount and updates it. Returns the server's representation of the serviceAccount, and an error, if there is any. -func (c *serviceAccounts) Update(serviceAccount *api.ServiceAccount) (result *api.ServiceAccount, err error) { - result = &api.ServiceAccount{} +func (c *serviceAccounts) Update(serviceAccount *core.ServiceAccount) (result *core.ServiceAccount, err error) { + result = &core.ServiceAccount{} err = c.client.Put(). Namespace(c.ns). Resource("serviceaccounts"). @@ -141,8 +141,8 @@ func (c *serviceAccounts) DeleteCollection(options *v1.DeleteOptions, listOption } // Patch applies the patch and returns the patched serviceAccount. -func (c *serviceAccounts) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *api.ServiceAccount, err error) { - result = &api.ServiceAccount{} +func (c *serviceAccounts) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *core.ServiceAccount, err error) { + result = &core.ServiceAccount{} err = c.client.Patch(pt). Namespace(c.ns). Resource("serviceaccounts"). diff --git a/pkg/client/clientset_generated/internalclientset/typed/events/internalversion/BUILD b/pkg/client/clientset_generated/internalclientset/typed/events/internalversion/BUILD new file mode 100644 index 00000000000..cca3da1e72a --- /dev/null +++ b/pkg/client/clientset_generated/internalclientset/typed/events/internalversion/BUILD @@ -0,0 +1,33 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "events_client.go", + "generated_expansion.go", + ], + importpath = "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/events/internalversion", + visibility = ["//visibility:public"], + deps = [ + "//pkg/client/clientset_generated/internalclientset/scheme:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//pkg/client/clientset_generated/internalclientset/typed/events/internalversion/fake:all-srcs", + ], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/client/clientset_generated/internalclientset/typed/events/internalversion/doc.go b/pkg/client/clientset_generated/internalclientset/typed/events/internalversion/doc.go new file mode 100644 index 00000000000..8615019757d --- /dev/null +++ b/pkg/client/clientset_generated/internalclientset/typed/events/internalversion/doc.go @@ -0,0 +1,18 @@ +/* +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. +*/ + +// This package has the automatically generated typed clients. +package internalversion diff --git a/pkg/client/clientset_generated/internalclientset/typed/events/internalversion/events_client.go b/pkg/client/clientset_generated/internalclientset/typed/events/internalversion/events_client.go new file mode 100644 index 00000000000..dcadcff69ea --- /dev/null +++ b/pkg/client/clientset_generated/internalclientset/typed/events/internalversion/events_client.go @@ -0,0 +1,94 @@ +/* +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 internalversion + +import ( + rest "k8s.io/client-go/rest" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme" +) + +type EventsInterface interface { + RESTClient() rest.Interface +} + +// EventsClient is used to interact with features provided by the events.k8s.io group. +type EventsClient struct { + restClient rest.Interface +} + +// NewForConfig creates a new EventsClient for the given config. +func NewForConfig(c *rest.Config) (*EventsClient, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientFor(&config) + if err != nil { + return nil, err + } + return &EventsClient{client}, nil +} + +// NewForConfigOrDie creates a new EventsClient for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *EventsClient { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new EventsClient for the given RESTClient. +func New(c rest.Interface) *EventsClient { + return &EventsClient{c} +} + +func setConfigDefaults(config *rest.Config) error { + g, err := scheme.Registry.Group("events.k8s.io") + if err != nil { + return err + } + + config.APIPath = "/apis" + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + if config.GroupVersion == nil || config.GroupVersion.Group != g.GroupVersion.Group { + gv := g.GroupVersion + config.GroupVersion = &gv + } + config.NegotiatedSerializer = scheme.Codecs + + if config.QPS == 0 { + config.QPS = 5 + } + if config.Burst == 0 { + config.Burst = 10 + } + + return nil +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *EventsClient) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/pkg/client/clientset_generated/internalclientset/typed/events/internalversion/fake/BUILD b/pkg/client/clientset_generated/internalclientset/typed/events/internalversion/fake/BUILD new file mode 100644 index 00000000000..1f7e701c813 --- /dev/null +++ b/pkg/client/clientset_generated/internalclientset/typed/events/internalversion/fake/BUILD @@ -0,0 +1,29 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "fake_events_client.go", + ], + importpath = "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/events/internalversion/fake", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/client-go/rest:go_default_library", + "//vendor/k8s.io/client-go/testing:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/client/clientset_generated/internalclientset/typed/events/internalversion/fake/doc.go b/pkg/client/clientset_generated/internalclientset/typed/events/internalversion/fake/doc.go new file mode 100644 index 00000000000..63e2c8a0821 --- /dev/null +++ b/pkg/client/clientset_generated/internalclientset/typed/events/internalversion/fake/doc.go @@ -0,0 +1,18 @@ +/* +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 fake has the automatically generated clients. +package fake diff --git a/pkg/client/clientset_generated/internalclientset/typed/events/internalversion/fake/fake_events_client.go b/pkg/client/clientset_generated/internalclientset/typed/events/internalversion/fake/fake_events_client.go new file mode 100644 index 00000000000..0bcfe38d006 --- /dev/null +++ b/pkg/client/clientset_generated/internalclientset/typed/events/internalversion/fake/fake_events_client.go @@ -0,0 +1,33 @@ +/* +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 fake + +import ( + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" +) + +type FakeEvents struct { + *testing.Fake +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeEvents) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/pkg/client/clientset_generated/internalclientset/typed/events/internalversion/generated_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/events/internalversion/generated_expansion.go new file mode 100644 index 00000000000..36261d29dad --- /dev/null +++ b/pkg/client/clientset_generated/internalclientset/typed/events/internalversion/generated_expansion.go @@ -0,0 +1,17 @@ +/* +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 internalversion diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/BUILD b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/BUILD index 7d2f3aa3d4d..85d27b16516 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/BUILD +++ b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/BUILD @@ -17,17 +17,13 @@ go_library( "ingress.go", "podsecuritypolicy.go", "replicaset.go", - "scale.go", - "scale_expansion.go", - "thirdpartyresource.go", ], importpath = "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion", deps = [ + "//pkg/apis/autoscaling:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/client/clientset_generated/internalclientset/scheme:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/daemonset.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/daemonset.go index c97e59d31a1..567e2d8d446 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/daemonset.go +++ b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/daemonset.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/deployment.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/deployment.go index 32213751782..4078b837dd1 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/deployment.go +++ b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/deployment.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,6 +21,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" rest "k8s.io/client-go/rest" + autoscaling "k8s.io/kubernetes/pkg/apis/autoscaling" extensions "k8s.io/kubernetes/pkg/apis/extensions" scheme "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme" ) @@ -42,8 +43,8 @@ type DeploymentInterface interface { List(opts v1.ListOptions) (*extensions.DeploymentList, error) Watch(opts v1.ListOptions) (watch.Interface, error) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *extensions.Deployment, err error) - GetScale(deploymentName string, options v1.GetOptions) (*extensions.Scale, error) - UpdateScale(deploymentName string, scale *extensions.Scale) (*extensions.Scale, error) + GetScale(deploymentName string, options v1.GetOptions) (*autoscaling.Scale, error) + UpdateScale(deploymentName string, scale *autoscaling.Scale) (*autoscaling.Scale, error) DeploymentExpansion } @@ -174,9 +175,9 @@ func (c *deployments) Patch(name string, pt types.PatchType, data []byte, subres return } -// GetScale takes name of the deployment, and returns the corresponding extensions.Scale object, and an error if there is any. -func (c *deployments) GetScale(deploymentName string, options v1.GetOptions) (result *extensions.Scale, err error) { - result = &extensions.Scale{} +// GetScale takes name of the deployment, and returns the corresponding autoscaling.Scale object, and an error if there is any. +func (c *deployments) GetScale(deploymentName string, options v1.GetOptions) (result *autoscaling.Scale, err error) { + result = &autoscaling.Scale{} err = c.client.Get(). Namespace(c.ns). Resource("deployments"). @@ -189,8 +190,8 @@ func (c *deployments) GetScale(deploymentName string, options v1.GetOptions) (re } // UpdateScale takes the top resource name and the representation of a scale and updates it. Returns the server's representation of the scale, and an error, if there is any. -func (c *deployments) UpdateScale(deploymentName string, scale *extensions.Scale) (result *extensions.Scale, err error) { - result = &extensions.Scale{} +func (c *deployments) UpdateScale(deploymentName string, scale *autoscaling.Scale) (result *autoscaling.Scale, err error) { + result = &autoscaling.Scale{} err = c.client.Put(). Namespace(c.ns). Resource("deployments"). diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/doc.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/doc.go index 3adf06d8934..8615019757d 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/extensions_client.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/extensions_client.go index a89a8371913..48f3fef1ea8 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/extensions_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/extensions_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -28,8 +28,6 @@ type ExtensionsInterface interface { IngressesGetter PodSecurityPoliciesGetter ReplicaSetsGetter - ScalesGetter - ThirdPartyResourcesGetter } // ExtensionsClient is used to interact with features provided by the extensions group. @@ -57,14 +55,6 @@ func (c *ExtensionsClient) ReplicaSets(namespace string) ReplicaSetInterface { return newReplicaSets(c, namespace) } -func (c *ExtensionsClient) Scales(namespace string) ScaleInterface { - return newScales(c, namespace) -} - -func (c *ExtensionsClient) ThirdPartyResources() ThirdPartyResourceInterface { - return newThirdPartyResources(c) -} - // NewForConfig creates a new ExtensionsClient for the given config. func NewForConfig(c *rest.Config) (*ExtensionsClient, error) { config := *c diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/BUILD b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/BUILD index 44971327d3a..91755daa631 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/BUILD +++ b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/BUILD @@ -16,12 +16,10 @@ go_library( "fake_ingress.go", "fake_podsecuritypolicy.go", "fake_replicaset.go", - "fake_scale.go", - "fake_scale_expansion.go", - "fake_thirdpartyresource.go", ], importpath = "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake", deps = [ + "//pkg/apis/autoscaling:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/doc.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/doc.go index c58fac35e4b..63e2c8a0821 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_daemonset.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_daemonset.go index c2c4de2516b..9605d275d98 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_daemonset.go +++ b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_daemonset.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_deployment.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_deployment.go index be65113bbaf..69dc3ae4e8a 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_deployment.go +++ b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_deployment.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,6 +23,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" + autoscaling "k8s.io/kubernetes/pkg/apis/autoscaling" extensions "k8s.io/kubernetes/pkg/apis/extensions" ) @@ -138,23 +139,23 @@ func (c *FakeDeployments) Patch(name string, pt types.PatchType, data []byte, su } // GetScale takes name of the deployment, and returns the corresponding scale object, and an error if there is any. -func (c *FakeDeployments) GetScale(deploymentName string, options v1.GetOptions) (result *extensions.Scale, err error) { +func (c *FakeDeployments) GetScale(deploymentName string, options v1.GetOptions) (result *autoscaling.Scale, err error) { obj, err := c.Fake. - Invokes(testing.NewGetSubresourceAction(deploymentsResource, c.ns, "scale", deploymentName), &extensions.Scale{}) + Invokes(testing.NewGetSubresourceAction(deploymentsResource, c.ns, "scale", deploymentName), &autoscaling.Scale{}) if obj == nil { return nil, err } - return obj.(*extensions.Scale), err + return obj.(*autoscaling.Scale), err } // UpdateScale takes the representation of a scale and updates it. Returns the server's representation of the scale, and an error, if there is any. -func (c *FakeDeployments) UpdateScale(deploymentName string, scale *extensions.Scale) (result *extensions.Scale, err error) { +func (c *FakeDeployments) UpdateScale(deploymentName string, scale *autoscaling.Scale) (result *autoscaling.Scale, err error) { obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(deploymentsResource, "scale", c.ns, scale), &extensions.Scale{}) + Invokes(testing.NewUpdateSubresourceAction(deploymentsResource, "scale", c.ns, scale), &autoscaling.Scale{}) if obj == nil { return nil, err } - return obj.(*extensions.Scale), err + return obj.(*autoscaling.Scale), err } diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_extensions_client.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_extensions_client.go index 82ec8abfa26..144418c738b 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_extensions_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_extensions_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -46,14 +46,6 @@ func (c *FakeExtensions) ReplicaSets(namespace string) internalversion.ReplicaSe return &FakeReplicaSets{c, namespace} } -func (c *FakeExtensions) Scales(namespace string) internalversion.ScaleInterface { - return &FakeScales{c, namespace} -} - -func (c *FakeExtensions) ThirdPartyResources() internalversion.ThirdPartyResourceInterface { - return &FakeThirdPartyResources{c} -} - // RESTClient returns a RESTClient that is used to communicate // with API server by this client implementation. func (c *FakeExtensions) RESTClient() rest.Interface { diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_ingress.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_ingress.go index f6293c03943..07ad3f67a3b 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_ingress.go +++ b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_ingress.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_podsecuritypolicy.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_podsecuritypolicy.go index 1e75456f141..c58bbf27a4b 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_podsecuritypolicy.go +++ b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_podsecuritypolicy.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_replicaset.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_replicaset.go index 37ed3661ad7..588d06e2ea4 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_replicaset.go +++ b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_replicaset.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,6 +23,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" + autoscaling "k8s.io/kubernetes/pkg/apis/autoscaling" extensions "k8s.io/kubernetes/pkg/apis/extensions" ) @@ -138,23 +139,23 @@ func (c *FakeReplicaSets) Patch(name string, pt types.PatchType, data []byte, su } // GetScale takes name of the replicaSet, and returns the corresponding scale object, and an error if there is any. -func (c *FakeReplicaSets) GetScale(replicaSetName string, options v1.GetOptions) (result *extensions.Scale, err error) { +func (c *FakeReplicaSets) GetScale(replicaSetName string, options v1.GetOptions) (result *autoscaling.Scale, err error) { obj, err := c.Fake. - Invokes(testing.NewGetSubresourceAction(replicasetsResource, c.ns, "scale", replicaSetName), &extensions.Scale{}) + Invokes(testing.NewGetSubresourceAction(replicasetsResource, c.ns, "scale", replicaSetName), &autoscaling.Scale{}) if obj == nil { return nil, err } - return obj.(*extensions.Scale), err + return obj.(*autoscaling.Scale), err } // UpdateScale takes the representation of a scale and updates it. Returns the server's representation of the scale, and an error, if there is any. -func (c *FakeReplicaSets) UpdateScale(replicaSetName string, scale *extensions.Scale) (result *extensions.Scale, err error) { +func (c *FakeReplicaSets) UpdateScale(replicaSetName string, scale *autoscaling.Scale) (result *autoscaling.Scale, err error) { obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(replicasetsResource, "scale", c.ns, scale), &extensions.Scale{}) + Invokes(testing.NewUpdateSubresourceAction(replicasetsResource, "scale", c.ns, scale), &autoscaling.Scale{}) if obj == nil { return nil, err } - return obj.(*extensions.Scale), err + return obj.(*autoscaling.Scale), err } diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_scale.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_scale.go deleted file mode 100644 index 2d2eb4f6e43..00000000000 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_scale.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. -*/ - -package fake - -// FakeScales implements ScaleInterface -type FakeScales struct { - Fake *FakeExtensions - ns string -} diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_scale_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_scale_expansion.go deleted file mode 100644 index 4b9413322a0..00000000000 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_scale_expansion.go +++ /dev/null @@ -1,47 +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 fake - -import ( - "k8s.io/apimachinery/pkg/runtime/schema" - core "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/apis/extensions" -) - -func (c *FakeScales) Get(kind string, name string) (result *extensions.Scale, err error) { - action := core.GetActionImpl{} - action.Verb = "get" - action.Namespace = c.ns - action.Resource = schema.GroupVersionResource{Resource: kind} - action.Subresource = "scale" - action.Name = name - obj, err := c.Fake.Invokes(action, &extensions.Scale{}) - result = obj.(*extensions.Scale) - return -} - -func (c *FakeScales) Update(kind string, scale *extensions.Scale) (result *extensions.Scale, err error) { - action := core.UpdateActionImpl{} - action.Verb = "update" - action.Namespace = c.ns - action.Resource = schema.GroupVersionResource{Resource: kind} - action.Subresource = "scale" - action.Object = scale - obj, err := c.Fake.Invokes(action, scale) - result = obj.(*extensions.Scale) - return -} diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_thirdpartyresource.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_thirdpartyresource.go deleted file mode 100644 index 2ff85611c82..00000000000 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake/fake_thirdpartyresource.go +++ /dev/null @@ -1,118 +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 fake - -import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" - extensions "k8s.io/kubernetes/pkg/apis/extensions" -) - -// FakeThirdPartyResources implements ThirdPartyResourceInterface -type FakeThirdPartyResources struct { - Fake *FakeExtensions -} - -var thirdpartyresourcesResource = schema.GroupVersionResource{Group: "extensions", Version: "", Resource: "thirdpartyresources"} - -var thirdpartyresourcesKind = schema.GroupVersionKind{Group: "extensions", Version: "", Kind: "ThirdPartyResource"} - -// Get takes name of the thirdPartyResource, and returns the corresponding thirdPartyResource object, and an error if there is any. -func (c *FakeThirdPartyResources) Get(name string, options v1.GetOptions) (result *extensions.ThirdPartyResource, err error) { - obj, err := c.Fake. - Invokes(testing.NewRootGetAction(thirdpartyresourcesResource, name), &extensions.ThirdPartyResource{}) - if obj == nil { - return nil, err - } - return obj.(*extensions.ThirdPartyResource), err -} - -// List takes label and field selectors, and returns the list of ThirdPartyResources that match those selectors. -func (c *FakeThirdPartyResources) List(opts v1.ListOptions) (result *extensions.ThirdPartyResourceList, err error) { - obj, err := c.Fake. - Invokes(testing.NewRootListAction(thirdpartyresourcesResource, thirdpartyresourcesKind, opts), &extensions.ThirdPartyResourceList{}) - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &extensions.ThirdPartyResourceList{} - for _, item := range obj.(*extensions.ThirdPartyResourceList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested thirdPartyResources. -func (c *FakeThirdPartyResources) Watch(opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewRootWatchAction(thirdpartyresourcesResource, opts)) -} - -// Create takes the representation of a thirdPartyResource and creates it. Returns the server's representation of the thirdPartyResource, and an error, if there is any. -func (c *FakeThirdPartyResources) Create(thirdPartyResource *extensions.ThirdPartyResource) (result *extensions.ThirdPartyResource, err error) { - obj, err := c.Fake. - Invokes(testing.NewRootCreateAction(thirdpartyresourcesResource, thirdPartyResource), &extensions.ThirdPartyResource{}) - if obj == nil { - return nil, err - } - return obj.(*extensions.ThirdPartyResource), err -} - -// Update takes the representation of a thirdPartyResource and updates it. Returns the server's representation of the thirdPartyResource, and an error, if there is any. -func (c *FakeThirdPartyResources) Update(thirdPartyResource *extensions.ThirdPartyResource) (result *extensions.ThirdPartyResource, err error) { - obj, err := c.Fake. - Invokes(testing.NewRootUpdateAction(thirdpartyresourcesResource, thirdPartyResource), &extensions.ThirdPartyResource{}) - if obj == nil { - return nil, err - } - return obj.(*extensions.ThirdPartyResource), err -} - -// Delete takes name of the thirdPartyResource and deletes it. Returns an error if one occurs. -func (c *FakeThirdPartyResources) Delete(name string, options *v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewRootDeleteAction(thirdpartyresourcesResource, name), &extensions.ThirdPartyResource{}) - return err -} - -// DeleteCollection deletes a collection of objects. -func (c *FakeThirdPartyResources) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - action := testing.NewRootDeleteCollectionAction(thirdpartyresourcesResource, listOptions) - - _, err := c.Fake.Invokes(action, &extensions.ThirdPartyResourceList{}) - return err -} - -// Patch applies the patch and returns the patched thirdPartyResource. -func (c *FakeThirdPartyResources) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *extensions.ThirdPartyResource, err error) { - obj, err := c.Fake. - Invokes(testing.NewRootPatchSubresourceAction(thirdpartyresourcesResource, name, data, subresources...), &extensions.ThirdPartyResource{}) - if obj == nil { - return nil, err - } - return obj.(*extensions.ThirdPartyResource), err -} diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/generated_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/generated_expansion.go index d59199eb50c..de02b2c1a4b 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/generated_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/generated_expansion.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,5 +23,3 @@ type IngressExpansion interface{} type PodSecurityPolicyExpansion interface{} type ReplicaSetExpansion interface{} - -type ThirdPartyResourceExpansion interface{} diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/ingress.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/ingress.go index 1b7babfe88d..6a7fb410b2c 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/ingress.go +++ b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/ingress.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/podsecuritypolicy.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/podsecuritypolicy.go index 2dc679c35c3..14995cd4a7e 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/podsecuritypolicy.go +++ b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/podsecuritypolicy.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/replicaset.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/replicaset.go index d6debe65fc2..231826f1ac7 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/replicaset.go +++ b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/replicaset.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,6 +21,7 @@ import ( types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" rest "k8s.io/client-go/rest" + autoscaling "k8s.io/kubernetes/pkg/apis/autoscaling" extensions "k8s.io/kubernetes/pkg/apis/extensions" scheme "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme" ) @@ -42,8 +43,8 @@ type ReplicaSetInterface interface { List(opts v1.ListOptions) (*extensions.ReplicaSetList, error) Watch(opts v1.ListOptions) (watch.Interface, error) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *extensions.ReplicaSet, err error) - GetScale(replicaSetName string, options v1.GetOptions) (*extensions.Scale, error) - UpdateScale(replicaSetName string, scale *extensions.Scale) (*extensions.Scale, error) + GetScale(replicaSetName string, options v1.GetOptions) (*autoscaling.Scale, error) + UpdateScale(replicaSetName string, scale *autoscaling.Scale) (*autoscaling.Scale, error) ReplicaSetExpansion } @@ -174,9 +175,9 @@ func (c *replicaSets) Patch(name string, pt types.PatchType, data []byte, subres return } -// GetScale takes name of the replicaSet, and returns the corresponding extensions.Scale object, and an error if there is any. -func (c *replicaSets) GetScale(replicaSetName string, options v1.GetOptions) (result *extensions.Scale, err error) { - result = &extensions.Scale{} +// GetScale takes name of the replicaSet, and returns the corresponding autoscaling.Scale object, and an error if there is any. +func (c *replicaSets) GetScale(replicaSetName string, options v1.GetOptions) (result *autoscaling.Scale, err error) { + result = &autoscaling.Scale{} err = c.client.Get(). Namespace(c.ns). Resource("replicasets"). @@ -189,8 +190,8 @@ func (c *replicaSets) GetScale(replicaSetName string, options v1.GetOptions) (re } // UpdateScale takes the top resource name and the representation of a scale and updates it. Returns the server's representation of the scale, and an error, if there is any. -func (c *replicaSets) UpdateScale(replicaSetName string, scale *extensions.Scale) (result *extensions.Scale, err error) { - result = &extensions.Scale{} +func (c *replicaSets) UpdateScale(replicaSetName string, scale *autoscaling.Scale) (result *autoscaling.Scale, err error) { + result = &autoscaling.Scale{} err = c.client.Put(). Namespace(c.ns). Resource("replicasets"). diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/scale.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/scale.go deleted file mode 100644 index ad1d91b015b..00000000000 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/scale.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 internalversion - -import ( - rest "k8s.io/client-go/rest" -) - -// ScalesGetter has a method to return a ScaleInterface. -// A group's client should implement this interface. -type ScalesGetter interface { - Scales(namespace string) ScaleInterface -} - -// ScaleInterface has methods to work with Scale resources. -type ScaleInterface interface { - ScaleExpansion -} - -// scales implements ScaleInterface -type scales struct { - client rest.Interface - ns string -} - -// newScales returns a Scales -func newScales(c *ExtensionsClient, namespace string) *scales { - return &scales{ - client: c.RESTClient(), - ns: namespace, - } -} diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/scale_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/scale_expansion.go deleted file mode 100644 index e5f2242a8d5..00000000000 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/scale_expansion.go +++ /dev/null @@ -1,65 +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 internalversion - -import ( - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/kubernetes/pkg/apis/extensions" -) - -// The ScaleExpansion interface allows manually adding extra methods to the ScaleInterface. -type ScaleExpansion interface { - Get(kind string, name string) (*extensions.Scale, error) - Update(kind string, scale *extensions.Scale) (*extensions.Scale, error) -} - -// Get takes the reference to scale subresource and returns the subresource or error, if one occurs. -func (c *scales) Get(kind string, name string) (result *extensions.Scale, err error) { - result = &extensions.Scale{} - - // TODO this method needs to take a proper unambiguous kind - fullyQualifiedKind := schema.GroupVersionKind{Kind: kind} - resource, _ := meta.UnsafeGuessKindToResource(fullyQualifiedKind) - - err = c.client.Get(). - Namespace(c.ns). - Resource(resource.Resource). - Name(name). - SubResource("scale"). - Do(). - Into(result) - return -} - -func (c *scales) Update(kind string, scale *extensions.Scale) (result *extensions.Scale, err error) { - result = &extensions.Scale{} - - // TODO this method needs to take a proper unambiguous kind - fullyQualifiedKind := schema.GroupVersionKind{Kind: kind} - resource, _ := meta.UnsafeGuessKindToResource(fullyQualifiedKind) - - err = c.client.Put(). - Namespace(scale.Namespace). - Resource(resource.Resource). - Name(scale.Name). - SubResource("scale"). - Body(scale). - Do(). - Into(result) - return -} diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/thirdpartyresource.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/thirdpartyresource.go deleted file mode 100644 index cd814b6e578..00000000000 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/thirdpartyresource.go +++ /dev/null @@ -1,145 +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 internalversion - -import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" - extensions "k8s.io/kubernetes/pkg/apis/extensions" - scheme "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme" -) - -// ThirdPartyResourcesGetter has a method to return a ThirdPartyResourceInterface. -// A group's client should implement this interface. -type ThirdPartyResourcesGetter interface { - ThirdPartyResources() ThirdPartyResourceInterface -} - -// ThirdPartyResourceInterface has methods to work with ThirdPartyResource resources. -type ThirdPartyResourceInterface interface { - Create(*extensions.ThirdPartyResource) (*extensions.ThirdPartyResource, error) - Update(*extensions.ThirdPartyResource) (*extensions.ThirdPartyResource, error) - Delete(name string, options *v1.DeleteOptions) error - DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*extensions.ThirdPartyResource, error) - List(opts v1.ListOptions) (*extensions.ThirdPartyResourceList, error) - Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *extensions.ThirdPartyResource, err error) - ThirdPartyResourceExpansion -} - -// thirdPartyResources implements ThirdPartyResourceInterface -type thirdPartyResources struct { - client rest.Interface -} - -// newThirdPartyResources returns a ThirdPartyResources -func newThirdPartyResources(c *ExtensionsClient) *thirdPartyResources { - return &thirdPartyResources{ - client: c.RESTClient(), - } -} - -// Get takes name of the thirdPartyResource, and returns the corresponding thirdPartyResource object, and an error if there is any. -func (c *thirdPartyResources) Get(name string, options v1.GetOptions) (result *extensions.ThirdPartyResource, err error) { - result = &extensions.ThirdPartyResource{} - err = c.client.Get(). - Resource("thirdpartyresources"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of ThirdPartyResources that match those selectors. -func (c *thirdPartyResources) List(opts v1.ListOptions) (result *extensions.ThirdPartyResourceList, err error) { - result = &extensions.ThirdPartyResourceList{} - err = c.client.Get(). - Resource("thirdpartyresources"). - VersionedParams(&opts, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested thirdPartyResources. -func (c *thirdPartyResources) Watch(opts v1.ListOptions) (watch.Interface, error) { - opts.Watch = true - return c.client.Get(). - Resource("thirdpartyresources"). - VersionedParams(&opts, scheme.ParameterCodec). - Watch() -} - -// Create takes the representation of a thirdPartyResource and creates it. Returns the server's representation of the thirdPartyResource, and an error, if there is any. -func (c *thirdPartyResources) Create(thirdPartyResource *extensions.ThirdPartyResource) (result *extensions.ThirdPartyResource, err error) { - result = &extensions.ThirdPartyResource{} - err = c.client.Post(). - Resource("thirdpartyresources"). - Body(thirdPartyResource). - Do(). - Into(result) - return -} - -// Update takes the representation of a thirdPartyResource and updates it. Returns the server's representation of the thirdPartyResource, and an error, if there is any. -func (c *thirdPartyResources) Update(thirdPartyResource *extensions.ThirdPartyResource) (result *extensions.ThirdPartyResource, err error) { - result = &extensions.ThirdPartyResource{} - err = c.client.Put(). - Resource("thirdpartyresources"). - Name(thirdPartyResource.Name). - Body(thirdPartyResource). - Do(). - Into(result) - return -} - -// Delete takes name of the thirdPartyResource and deletes it. Returns an error if one occurs. -func (c *thirdPartyResources) Delete(name string, options *v1.DeleteOptions) error { - return c.client.Delete(). - Resource("thirdpartyresources"). - Name(name). - Body(options). - Do(). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *thirdPartyResources) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - return c.client.Delete(). - Resource("thirdpartyresources"). - VersionedParams(&listOptions, scheme.ParameterCodec). - Body(options). - Do(). - Error() -} - -// Patch applies the patch and returns the patched thirdPartyResource. -func (c *thirdPartyResources) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *extensions.ThirdPartyResource, err error) { - result = &extensions.ThirdPartyResource{} - err = c.client.Patch(pt). - Resource("thirdpartyresources"). - SubResource(subresources...). - Name(name). - Body(data). - Do(). - Into(result) - return -} diff --git a/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/doc.go b/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/doc.go index 3adf06d8934..8615019757d 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/fake/doc.go b/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/fake/doc.go index c58fac35e4b..63e2c8a0821 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/fake/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/fake/fake_networking_client.go b/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/fake/fake_networking_client.go index 9c0941a3108..6be9641fa81 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/fake/fake_networking_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/fake/fake_networking_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/fake/fake_networkpolicy.go b/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/fake/fake_networkpolicy.go index 5585a50e3fb..357ebb82f02 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/fake/fake_networkpolicy.go +++ b/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/fake/fake_networkpolicy.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/generated_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/generated_expansion.go index c2c8709d691..343fed42cb4 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/generated_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/generated_expansion.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/networking_client.go b/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/networking_client.go index 4469af17165..53a927adf41 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/networking_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/networking_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/networkpolicy.go b/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/networkpolicy.go index 23cf7bcf649..e6aa62cc13e 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/networkpolicy.go +++ b/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/networkpolicy.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/doc.go b/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/doc.go index 3adf06d8934..8615019757d 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/eviction.go b/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/eviction.go index a43dabbb19d..42760568b35 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/eviction.go +++ b/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/eviction.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/fake/doc.go b/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/fake/doc.go index c58fac35e4b..63e2c8a0821 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/fake/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/fake/fake_eviction.go b/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/fake/fake_eviction.go index 52abe617c5d..1d699bd1959 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/fake/fake_eviction.go +++ b/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/fake/fake_eviction.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/fake/fake_poddisruptionbudget.go b/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/fake/fake_poddisruptionbudget.go index e6e411e7ddc..d8d34ed6f4d 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/fake/fake_poddisruptionbudget.go +++ b/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/fake/fake_poddisruptionbudget.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/fake/fake_policy_client.go b/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/fake/fake_policy_client.go index 75012322141..6f8f5901af1 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/fake/fake_policy_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/fake/fake_policy_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/generated_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/generated_expansion.go index d323ea6c626..a5d8910e60e 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/generated_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/generated_expansion.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/poddisruptionbudget.go b/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/poddisruptionbudget.go index 232a5508209..2ae24ea5620 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/poddisruptionbudget.go +++ b/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/poddisruptionbudget.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/policy_client.go b/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/policy_client.go index 04c7b36e352..86eebbdf86e 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/policy_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/policy_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/clusterrole.go b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/clusterrole.go index 245d36d9309..2d9c34ffe07 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/clusterrole.go +++ b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/clusterrole.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/clusterrolebinding.go b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/clusterrolebinding.go index cdad43a63a3..103133da3af 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/clusterrolebinding.go +++ b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/clusterrolebinding.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/doc.go b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/doc.go index 3adf06d8934..8615019757d 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake/doc.go b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake/doc.go index c58fac35e4b..63e2c8a0821 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake/fake_clusterrole.go b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake/fake_clusterrole.go index cf1cea4f220..3fe3b2c16a3 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake/fake_clusterrole.go +++ b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake/fake_clusterrole.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake/fake_clusterrolebinding.go b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake/fake_clusterrolebinding.go index 6dfbde29a6e..819dd653a28 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake/fake_clusterrolebinding.go +++ b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake/fake_clusterrolebinding.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake/fake_rbac_client.go b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake/fake_rbac_client.go index ab1f8b30a86..e6431ed0f6f 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake/fake_rbac_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake/fake_rbac_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake/fake_role.go b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake/fake_role.go index 6f67a77766c..8361b08a877 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake/fake_role.go +++ b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake/fake_role.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake/fake_rolebinding.go b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake/fake_rolebinding.go index f92aafdd2bd..4bbf33bbea5 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake/fake_rolebinding.go +++ b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake/fake_rolebinding.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/generated_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/generated_expansion.go index 749e7d2ca82..35e44fd40cd 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/generated_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/generated_expansion.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/rbac_client.go b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/rbac_client.go index 6e5a608cf32..c1a5bce5691 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/rbac_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/rbac_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/role.go b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/role.go index c94637bbd86..46f2d54922c 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/role.go +++ b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/role.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/rolebinding.go b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/rolebinding.go index 1248a99f7ce..6fff7a16405 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/rolebinding.go +++ b/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/rolebinding.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/doc.go b/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/doc.go index 3adf06d8934..8615019757d 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/fake/doc.go b/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/fake/doc.go index c58fac35e4b..63e2c8a0821 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/fake/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/fake/fake_priorityclass.go b/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/fake/fake_priorityclass.go index b39c6994de8..fe59ea5a445 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/fake/fake_priorityclass.go +++ b/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/fake/fake_priorityclass.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/fake/fake_scheduling_client.go b/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/fake/fake_scheduling_client.go index 0452e4f7a2f..6618b268725 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/fake/fake_scheduling_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/fake/fake_scheduling_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/generated_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/generated_expansion.go index 83f1102b3d6..8a32bee4ef4 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/generated_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/generated_expansion.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/priorityclass.go b/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/priorityclass.go index 20014824544..094a77c48e2 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/priorityclass.go +++ b/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/priorityclass.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/scheduling_client.go b/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/scheduling_client.go index c17b733f894..5500c19a22a 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/scheduling_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/scheduling_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/doc.go b/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/doc.go index 3adf06d8934..8615019757d 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/fake/doc.go b/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/fake/doc.go index c58fac35e4b..63e2c8a0821 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/fake/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/fake/fake_podpreset.go b/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/fake/fake_podpreset.go index 76d13d6a997..ef3bd6d2740 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/fake/fake_podpreset.go +++ b/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/fake/fake_podpreset.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/fake/fake_settings_client.go b/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/fake/fake_settings_client.go index 04776e59dda..cefb9e443cc 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/fake/fake_settings_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/fake/fake_settings_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/generated_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/generated_expansion.go index b82b5f75184..5cc4c47d171 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/generated_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/generated_expansion.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/podpreset.go b/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/podpreset.go index c2b4220a592..c230b097417 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/podpreset.go +++ b/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/podpreset.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/settings_client.go b/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/settings_client.go index 448280d54b4..797355725d4 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/settings_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/settings_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/BUILD b/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/BUILD index 9c2c76bc771..cd8e7a8121a 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/BUILD +++ b/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/BUILD @@ -12,6 +12,7 @@ go_library( "generated_expansion.go", "storage_client.go", "storageclass.go", + "volumeattachment.go", ], importpath = "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion", deps = [ diff --git a/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/doc.go b/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/doc.go index 3adf06d8934..8615019757d 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/fake/BUILD b/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/fake/BUILD index 9acf4577afc..dbe29585720 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/fake/BUILD +++ b/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/fake/BUILD @@ -11,6 +11,7 @@ go_library( "doc.go", "fake_storage_client.go", "fake_storageclass.go", + "fake_volumeattachment.go", ], importpath = "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/fake", deps = [ diff --git a/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/fake/doc.go b/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/fake/doc.go index c58fac35e4b..63e2c8a0821 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/fake/doc.go +++ b/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/fake/fake_storage_client.go b/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/fake/fake_storage_client.go index 2bccdbd249d..3b64d29d68f 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/fake/fake_storage_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/fake/fake_storage_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -30,6 +30,10 @@ func (c *FakeStorage) StorageClasses() internalversion.StorageClassInterface { return &FakeStorageClasses{c} } +func (c *FakeStorage) VolumeAttachments() internalversion.VolumeAttachmentInterface { + return &FakeVolumeAttachments{c} +} + // RESTClient returns a RESTClient that is used to communicate // with API server by this client implementation. func (c *FakeStorage) RESTClient() rest.Interface { diff --git a/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/fake/fake_storageclass.go b/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/fake/fake_storageclass.go index f076b9ef1d0..4c52ec24faa 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/fake/fake_storageclass.go +++ b/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/fake/fake_storageclass.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/fake/fake_volumeattachment.go b/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/fake/fake_volumeattachment.go new file mode 100644 index 00000000000..0218d1dd92f --- /dev/null +++ b/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/fake/fake_volumeattachment.go @@ -0,0 +1,129 @@ +/* +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 fake + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" + storage "k8s.io/kubernetes/pkg/apis/storage" +) + +// FakeVolumeAttachments implements VolumeAttachmentInterface +type FakeVolumeAttachments struct { + Fake *FakeStorage +} + +var volumeattachmentsResource = schema.GroupVersionResource{Group: "storage.k8s.io", Version: "", Resource: "volumeattachments"} + +var volumeattachmentsKind = schema.GroupVersionKind{Group: "storage.k8s.io", Version: "", Kind: "VolumeAttachment"} + +// Get takes name of the volumeAttachment, and returns the corresponding volumeAttachment object, and an error if there is any. +func (c *FakeVolumeAttachments) Get(name string, options v1.GetOptions) (result *storage.VolumeAttachment, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootGetAction(volumeattachmentsResource, name), &storage.VolumeAttachment{}) + if obj == nil { + return nil, err + } + return obj.(*storage.VolumeAttachment), err +} + +// List takes label and field selectors, and returns the list of VolumeAttachments that match those selectors. +func (c *FakeVolumeAttachments) List(opts v1.ListOptions) (result *storage.VolumeAttachmentList, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootListAction(volumeattachmentsResource, volumeattachmentsKind, opts), &storage.VolumeAttachmentList{}) + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &storage.VolumeAttachmentList{} + for _, item := range obj.(*storage.VolumeAttachmentList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested volumeAttachments. +func (c *FakeVolumeAttachments) Watch(opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewRootWatchAction(volumeattachmentsResource, opts)) +} + +// Create takes the representation of a volumeAttachment and creates it. Returns the server's representation of the volumeAttachment, and an error, if there is any. +func (c *FakeVolumeAttachments) Create(volumeAttachment *storage.VolumeAttachment) (result *storage.VolumeAttachment, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootCreateAction(volumeattachmentsResource, volumeAttachment), &storage.VolumeAttachment{}) + if obj == nil { + return nil, err + } + return obj.(*storage.VolumeAttachment), err +} + +// Update takes the representation of a volumeAttachment and updates it. Returns the server's representation of the volumeAttachment, and an error, if there is any. +func (c *FakeVolumeAttachments) Update(volumeAttachment *storage.VolumeAttachment) (result *storage.VolumeAttachment, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateAction(volumeattachmentsResource, volumeAttachment), &storage.VolumeAttachment{}) + if obj == nil { + return nil, err + } + return obj.(*storage.VolumeAttachment), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeVolumeAttachments) UpdateStatus(volumeAttachment *storage.VolumeAttachment) (*storage.VolumeAttachment, error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateSubresourceAction(volumeattachmentsResource, "status", volumeAttachment), &storage.VolumeAttachment{}) + if obj == nil { + return nil, err + } + return obj.(*storage.VolumeAttachment), err +} + +// Delete takes name of the volumeAttachment and deletes it. Returns an error if one occurs. +func (c *FakeVolumeAttachments) Delete(name string, options *v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewRootDeleteAction(volumeattachmentsResource, name), &storage.VolumeAttachment{}) + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeVolumeAttachments) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + action := testing.NewRootDeleteCollectionAction(volumeattachmentsResource, listOptions) + + _, err := c.Fake.Invokes(action, &storage.VolumeAttachmentList{}) + return err +} + +// Patch applies the patch and returns the patched volumeAttachment. +func (c *FakeVolumeAttachments) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *storage.VolumeAttachment, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootPatchSubresourceAction(volumeattachmentsResource, name, data, subresources...), &storage.VolumeAttachment{}) + if obj == nil { + return nil, err + } + return obj.(*storage.VolumeAttachment), err +} diff --git a/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/generated_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/generated_expansion.go index 2fe8e8d7edd..17366dbd688 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/generated_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/generated_expansion.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -17,3 +17,5 @@ limitations under the License. package internalversion type StorageClassExpansion interface{} + +type VolumeAttachmentExpansion interface{} diff --git a/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/storage_client.go b/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/storage_client.go index 9eebff01a4c..b482df23b10 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/storage_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/storage_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -24,6 +24,7 @@ import ( type StorageInterface interface { RESTClient() rest.Interface StorageClassesGetter + VolumeAttachmentsGetter } // StorageClient is used to interact with features provided by the storage.k8s.io group. @@ -35,6 +36,10 @@ func (c *StorageClient) StorageClasses() StorageClassInterface { return newStorageClasses(c) } +func (c *StorageClient) VolumeAttachments() VolumeAttachmentInterface { + return newVolumeAttachments(c) +} + // NewForConfig creates a new StorageClient for the given config. func NewForConfig(c *rest.Config) (*StorageClient, error) { config := *c diff --git a/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/storageclass.go b/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/storageclass.go index a23b7b0082e..704036ad7bd 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/storageclass.go +++ b/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/storageclass.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/volumeattachment.go b/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/volumeattachment.go new file mode 100644 index 00000000000..b3dd6f6b5cc --- /dev/null +++ b/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/volumeattachment.go @@ -0,0 +1,161 @@ +/* +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 internalversion + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" + storage "k8s.io/kubernetes/pkg/apis/storage" + scheme "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme" +) + +// VolumeAttachmentsGetter has a method to return a VolumeAttachmentInterface. +// A group's client should implement this interface. +type VolumeAttachmentsGetter interface { + VolumeAttachments() VolumeAttachmentInterface +} + +// VolumeAttachmentInterface has methods to work with VolumeAttachment resources. +type VolumeAttachmentInterface interface { + Create(*storage.VolumeAttachment) (*storage.VolumeAttachment, error) + Update(*storage.VolumeAttachment) (*storage.VolumeAttachment, error) + UpdateStatus(*storage.VolumeAttachment) (*storage.VolumeAttachment, error) + Delete(name string, options *v1.DeleteOptions) error + DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error + Get(name string, options v1.GetOptions) (*storage.VolumeAttachment, error) + List(opts v1.ListOptions) (*storage.VolumeAttachmentList, error) + Watch(opts v1.ListOptions) (watch.Interface, error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *storage.VolumeAttachment, err error) + VolumeAttachmentExpansion +} + +// volumeAttachments implements VolumeAttachmentInterface +type volumeAttachments struct { + client rest.Interface +} + +// newVolumeAttachments returns a VolumeAttachments +func newVolumeAttachments(c *StorageClient) *volumeAttachments { + return &volumeAttachments{ + client: c.RESTClient(), + } +} + +// Get takes name of the volumeAttachment, and returns the corresponding volumeAttachment object, and an error if there is any. +func (c *volumeAttachments) Get(name string, options v1.GetOptions) (result *storage.VolumeAttachment, err error) { + result = &storage.VolumeAttachment{} + err = c.client.Get(). + Resource("volumeattachments"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of VolumeAttachments that match those selectors. +func (c *volumeAttachments) List(opts v1.ListOptions) (result *storage.VolumeAttachmentList, err error) { + result = &storage.VolumeAttachmentList{} + err = c.client.Get(). + Resource("volumeattachments"). + VersionedParams(&opts, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested volumeAttachments. +func (c *volumeAttachments) Watch(opts v1.ListOptions) (watch.Interface, error) { + opts.Watch = true + return c.client.Get(). + Resource("volumeattachments"). + VersionedParams(&opts, scheme.ParameterCodec). + Watch() +} + +// Create takes the representation of a volumeAttachment and creates it. Returns the server's representation of the volumeAttachment, and an error, if there is any. +func (c *volumeAttachments) Create(volumeAttachment *storage.VolumeAttachment) (result *storage.VolumeAttachment, err error) { + result = &storage.VolumeAttachment{} + err = c.client.Post(). + Resource("volumeattachments"). + Body(volumeAttachment). + Do(). + Into(result) + return +} + +// Update takes the representation of a volumeAttachment and updates it. Returns the server's representation of the volumeAttachment, and an error, if there is any. +func (c *volumeAttachments) Update(volumeAttachment *storage.VolumeAttachment) (result *storage.VolumeAttachment, err error) { + result = &storage.VolumeAttachment{} + err = c.client.Put(). + Resource("volumeattachments"). + Name(volumeAttachment.Name). + Body(volumeAttachment). + Do(). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + +func (c *volumeAttachments) UpdateStatus(volumeAttachment *storage.VolumeAttachment) (result *storage.VolumeAttachment, err error) { + result = &storage.VolumeAttachment{} + err = c.client.Put(). + Resource("volumeattachments"). + Name(volumeAttachment.Name). + SubResource("status"). + Body(volumeAttachment). + Do(). + Into(result) + return +} + +// Delete takes name of the volumeAttachment and deletes it. Returns an error if one occurs. +func (c *volumeAttachments) Delete(name string, options *v1.DeleteOptions) error { + return c.client.Delete(). + Resource("volumeattachments"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *volumeAttachments) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + return c.client.Delete(). + Resource("volumeattachments"). + VersionedParams(&listOptions, scheme.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Patch applies the patch and returns the patched volumeAttachment. +func (c *volumeAttachments) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *storage.VolumeAttachment, err error) { + result = &storage.VolumeAttachment{} + err = c.client.Patch(pt). + Resource("volumeattachments"). + SubResource(subresources...). + Name(name). + Body(data). + Do(). + Into(result) + return +} diff --git a/pkg/client/conditions/BUILD b/pkg/client/conditions/BUILD index 5c381c4e119..a475e8f1cbd 100644 --- a/pkg/client/conditions/BUILD +++ b/pkg/client/conditions/BUILD @@ -10,7 +10,6 @@ go_library( srcs = ["conditions.go"], importpath = "k8s.io/kubernetes/pkg/client/conditions", deps = [ - "//pkg/api/v1/pod:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", diff --git a/pkg/client/conditions/conditions.go b/pkg/client/conditions/conditions.go index be4c0bfbf36..16255119545 100644 --- a/pkg/client/conditions/conditions.go +++ b/pkg/client/conditions/conditions.go @@ -23,17 +23,12 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/watch" - podutil "k8s.io/kubernetes/pkg/api/v1/pod" ) // ErrPodCompleted is returned by PodRunning or PodContainerRunning to indicate that // the pod has already reached completed state. var ErrPodCompleted = fmt.Errorf("pod ran to completion") -// ErrContainerTerminated is returned by PodContainerRunning in the intermediate -// state where the pod indicates it's still running, but its container is already terminated -var ErrContainerTerminated = fmt.Errorf("container terminated") - // PodRunning returns true if the pod is running, false if the pod has not yet reached running state, // returns ErrPodCompleted if the pod has run to completion, or an error in any other case. func PodRunning(event watch.Event) (bool, error) { @@ -70,86 +65,6 @@ func PodCompleted(event watch.Event) (bool, error) { return false, nil } -// PodRunningAndReady returns true if the pod is running and ready, false if the pod has not -// yet reached those states, returns ErrPodCompleted if the pod has run to completion, or -// an error in any other case. -func PodRunningAndReady(event watch.Event) (bool, error) { - switch event.Type { - case watch.Deleted: - return false, errors.NewNotFound(schema.GroupResource{Resource: "pods"}, "") - } - switch t := event.Object.(type) { - case *v1.Pod: - switch t.Status.Phase { - case v1.PodFailed, v1.PodSucceeded: - return false, ErrPodCompleted - case v1.PodRunning: - return podutil.IsPodReady(t), nil - } - } - return false, nil -} - -// PodNotPending returns true if the pod has left the pending state, false if it has not, -// or an error in any other case (such as if the pod was deleted). -func PodNotPending(event watch.Event) (bool, error) { - switch event.Type { - case watch.Deleted: - return false, errors.NewNotFound(schema.GroupResource{Resource: "pods"}, "") - } - switch t := event.Object.(type) { - case *v1.Pod: - switch t.Status.Phase { - case v1.PodPending: - return false, nil - default: - return true, nil - } - } - return false, nil -} - -// PodContainerRunning returns false until the named container has ContainerStatus running (at least once), -// and will return an error if the pod is deleted, runs to completion, or the container pod is not available. -func PodContainerRunning(containerName string) watch.ConditionFunc { - return func(event watch.Event) (bool, error) { - switch event.Type { - case watch.Deleted: - return false, errors.NewNotFound(schema.GroupResource{Resource: "pods"}, "") - } - switch t := event.Object.(type) { - case *v1.Pod: - switch t.Status.Phase { - case v1.PodRunning, v1.PodPending: - case v1.PodFailed, v1.PodSucceeded: - return false, ErrPodCompleted - default: - return false, nil - } - for _, s := range t.Status.ContainerStatuses { - if s.Name != containerName { - continue - } - if s.State.Terminated != nil { - return false, ErrContainerTerminated - } - return s.State.Running != nil, nil - } - for _, s := range t.Status.InitContainerStatuses { - if s.Name != containerName { - continue - } - if s.State.Terminated != nil { - return false, ErrContainerTerminated - } - return s.State.Running != nil, nil - } - return false, nil - } - return false, nil - } -} - // ServiceAccountHasSecrets returns true if the service account has at least one secret, // false if it does not, or an error. func ServiceAccountHasSecrets(event watch.Event) (bool, error) { diff --git a/pkg/client/informers/informers_generated/internalversion/BUILD b/pkg/client/informers/informers_generated/internalversion/BUILD index fea81e07cdc..91495dc5d9d 100644 --- a/pkg/client/informers/informers_generated/internalversion/BUILD +++ b/pkg/client/informers/informers_generated/internalversion/BUILD @@ -13,12 +13,12 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion", deps = [ - "//pkg/api:go_default_library", "//pkg/apis/admissionregistration:go_default_library", "//pkg/apis/apps:go_default_library", "//pkg/apis/autoscaling:go_default_library", "//pkg/apis/batch:go_default_library", "//pkg/apis/certificates:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/apis/networking:go_default_library", "//pkg/apis/policy:go_default_library", @@ -41,6 +41,7 @@ go_library( "//pkg/client/informers/informers_generated/internalversion/scheduling:go_default_library", "//pkg/client/informers/informers_generated/internalversion/settings:go_default_library", "//pkg/client/informers/informers_generated/internalversion/storage:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", diff --git a/pkg/client/informers/informers_generated/internalversion/admissionregistration/interface.go b/pkg/client/informers/informers_generated/internalversion/admissionregistration/interface.go index 036ae9754b6..1a2aad20530 100644 --- a/pkg/client/informers/informers_generated/internalversion/admissionregistration/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/admissionregistration/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -30,15 +30,17 @@ type Interface interface { } type group struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &group{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // InternalVersion returns a new internalversion.Interface. func (g *group) InternalVersion() internalversion.Interface { - return internalversion.New(g.SharedInformerFactory) + return internalversion.New(g.factory, g.namespace, g.tweakListOptions) } diff --git a/pkg/client/informers/informers_generated/internalversion/admissionregistration/internalversion/BUILD b/pkg/client/informers/informers_generated/internalversion/admissionregistration/internalversion/BUILD index 19467f1ed5c..ca1c234c013 100644 --- a/pkg/client/informers/informers_generated/internalversion/admissionregistration/internalversion/BUILD +++ b/pkg/client/informers/informers_generated/internalversion/admissionregistration/internalversion/BUILD @@ -8,9 +8,10 @@ load( go_library( name = "go_default_library", srcs = [ - "externaladmissionhookconfiguration.go", "initializerconfiguration.go", "interface.go", + "mutatingwebhookconfiguration.go", + "validatingwebhookconfiguration.go", ], importpath = "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/admissionregistration/internalversion", deps = [ diff --git a/pkg/client/informers/informers_generated/internalversion/admissionregistration/internalversion/externaladmissionhookconfiguration.go b/pkg/client/informers/informers_generated/internalversion/admissionregistration/internalversion/externaladmissionhookconfiguration.go deleted file mode 100644 index 6972004733d..00000000000 --- a/pkg/client/informers/informers_generated/internalversion/admissionregistration/internalversion/externaladmissionhookconfiguration.go +++ /dev/null @@ -1,73 +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. -*/ - -// This file was automatically generated by informer-gen - -package internalversion - -import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" - watch "k8s.io/apimachinery/pkg/watch" - cache "k8s.io/client-go/tools/cache" - admissionregistration "k8s.io/kubernetes/pkg/apis/admissionregistration" - internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" - internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" - internalversion "k8s.io/kubernetes/pkg/client/listers/admissionregistration/internalversion" - time "time" -) - -// ExternalAdmissionHookConfigurationInformer provides access to a shared informer and lister for -// ExternalAdmissionHookConfigurations. -type ExternalAdmissionHookConfigurationInformer interface { - Informer() cache.SharedIndexInformer - Lister() internalversion.ExternalAdmissionHookConfigurationLister -} - -type externalAdmissionHookConfigurationInformer struct { - factory internalinterfaces.SharedInformerFactory -} - -// NewExternalAdmissionHookConfigurationInformer constructs a new informer for ExternalAdmissionHookConfiguration 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 NewExternalAdmissionHookConfigurationInformer(client internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( - &cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { - return client.Admissionregistration().ExternalAdmissionHookConfigurations().List(options) - }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { - return client.Admissionregistration().ExternalAdmissionHookConfigurations().Watch(options) - }, - }, - &admissionregistration.ExternalAdmissionHookConfiguration{}, - resyncPeriod, - indexers, - ) -} - -func defaultExternalAdmissionHookConfigurationInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewExternalAdmissionHookConfigurationInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) -} - -func (f *externalAdmissionHookConfigurationInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&admissionregistration.ExternalAdmissionHookConfiguration{}, defaultExternalAdmissionHookConfigurationInformer) -} - -func (f *externalAdmissionHookConfigurationInformer) Lister() internalversion.ExternalAdmissionHookConfigurationLister { - return internalversion.NewExternalAdmissionHookConfigurationLister(f.Informer().GetIndexer()) -} diff --git a/pkg/client/informers/informers_generated/internalversion/admissionregistration/internalversion/initializerconfiguration.go b/pkg/client/informers/informers_generated/internalversion/admissionregistration/internalversion/initializerconfiguration.go index 70f869291d5..b71f19f8e35 100644 --- a/pkg/client/informers/informers_generated/internalversion/admissionregistration/internalversion/initializerconfiguration.go +++ b/pkg/client/informers/informers_generated/internalversion/admissionregistration/internalversion/initializerconfiguration.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,6 +19,8 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -27,7 +29,6 @@ import ( internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/admissionregistration/internalversion" - time "time" ) // InitializerConfigurationInformer provides access to a shared informer and lister for @@ -38,19 +39,33 @@ type InitializerConfigurationInformer interface { } type initializerConfigurationInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc } // NewInitializerConfigurationInformer constructs a new informer for InitializerConfiguration 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 NewInitializerConfigurationInformer(client internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredInitializerConfigurationInformer(client, resyncPeriod, indexers, nil) +} + +// NewFilteredInitializerConfigurationInformer constructs a new informer for InitializerConfiguration 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 NewFilteredInitializerConfigurationInformer(client internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Admissionregistration().InitializerConfigurations().List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Admissionregistration().InitializerConfigurations().Watch(options) }, }, @@ -60,12 +75,12 @@ func NewInitializerConfigurationInformer(client internalclientset.Interface, res ) } -func defaultInitializerConfigurationInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewInitializerConfigurationInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *initializerConfigurationInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredInitializerConfigurationInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *initializerConfigurationInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&admissionregistration.InitializerConfiguration{}, defaultInitializerConfigurationInformer) + return f.factory.InformerFor(&admissionregistration.InitializerConfiguration{}, f.defaultInformer) } func (f *initializerConfigurationInformer) Lister() internalversion.InitializerConfigurationLister { diff --git a/pkg/client/informers/informers_generated/internalversion/admissionregistration/internalversion/interface.go b/pkg/client/informers/informers_generated/internalversion/admissionregistration/internalversion/interface.go index bd0803e2759..4afa5cd9d1c 100644 --- a/pkg/client/informers/informers_generated/internalversion/admissionregistration/internalversion/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/admissionregistration/internalversion/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -24,27 +24,36 @@ import ( // Interface provides access to all the informers in this group version. type Interface interface { - // ExternalAdmissionHookConfigurations returns a ExternalAdmissionHookConfigurationInformer. - ExternalAdmissionHookConfigurations() ExternalAdmissionHookConfigurationInformer // InitializerConfigurations returns a InitializerConfigurationInformer. InitializerConfigurations() InitializerConfigurationInformer + // MutatingWebhookConfigurations returns a MutatingWebhookConfigurationInformer. + MutatingWebhookConfigurations() MutatingWebhookConfigurationInformer + // ValidatingWebhookConfigurations returns a ValidatingWebhookConfigurationInformer. + ValidatingWebhookConfigurations() ValidatingWebhookConfigurationInformer } type version struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &version{f} -} - -// ExternalAdmissionHookConfigurations returns a ExternalAdmissionHookConfigurationInformer. -func (v *version) ExternalAdmissionHookConfigurations() ExternalAdmissionHookConfigurationInformer { - return &externalAdmissionHookConfigurationInformer{factory: v.SharedInformerFactory} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // InitializerConfigurations returns a InitializerConfigurationInformer. func (v *version) InitializerConfigurations() InitializerConfigurationInformer { - return &initializerConfigurationInformer{factory: v.SharedInformerFactory} + return &initializerConfigurationInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} + +// MutatingWebhookConfigurations returns a MutatingWebhookConfigurationInformer. +func (v *version) MutatingWebhookConfigurations() MutatingWebhookConfigurationInformer { + return &mutatingWebhookConfigurationInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} + +// ValidatingWebhookConfigurations returns a ValidatingWebhookConfigurationInformer. +func (v *version) ValidatingWebhookConfigurations() ValidatingWebhookConfigurationInformer { + return &validatingWebhookConfigurationInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } diff --git a/pkg/client/informers/informers_generated/internalversion/admissionregistration/internalversion/mutatingwebhookconfiguration.go b/pkg/client/informers/informers_generated/internalversion/admissionregistration/internalversion/mutatingwebhookconfiguration.go new file mode 100644 index 00000000000..cca3c29d54c --- /dev/null +++ b/pkg/client/informers/informers_generated/internalversion/admissionregistration/internalversion/mutatingwebhookconfiguration.go @@ -0,0 +1,88 @@ +/* +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. +*/ + +// This file was automatically generated by informer-gen + +package internalversion + +import ( + time "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" + admissionregistration "k8s.io/kubernetes/pkg/apis/admissionregistration" + internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" + internalversion "k8s.io/kubernetes/pkg/client/listers/admissionregistration/internalversion" +) + +// MutatingWebhookConfigurationInformer provides access to a shared informer and lister for +// MutatingWebhookConfigurations. +type MutatingWebhookConfigurationInformer interface { + Informer() cache.SharedIndexInformer + Lister() internalversion.MutatingWebhookConfigurationLister +} + +type mutatingWebhookConfigurationInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// NewMutatingWebhookConfigurationInformer 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 NewMutatingWebhookConfigurationInformer(client internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredMutatingWebhookConfigurationInformer(client, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.Admissionregistration().MutatingWebhookConfigurations().List(options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.Admissionregistration().MutatingWebhookConfigurations().Watch(options) + }, + }, + &admissionregistration.MutatingWebhookConfiguration{}, + resyncPeriod, + indexers, + ) +} + +func (f *mutatingWebhookConfigurationInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredMutatingWebhookConfigurationInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *mutatingWebhookConfigurationInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&admissionregistration.MutatingWebhookConfiguration{}, f.defaultInformer) +} + +func (f *mutatingWebhookConfigurationInformer) Lister() internalversion.MutatingWebhookConfigurationLister { + return internalversion.NewMutatingWebhookConfigurationLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/informers_generated/internalversion/admissionregistration/internalversion/validatingwebhookconfiguration.go b/pkg/client/informers/informers_generated/internalversion/admissionregistration/internalversion/validatingwebhookconfiguration.go new file mode 100644 index 00000000000..f523b07c4ed --- /dev/null +++ b/pkg/client/informers/informers_generated/internalversion/admissionregistration/internalversion/validatingwebhookconfiguration.go @@ -0,0 +1,88 @@ +/* +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. +*/ + +// This file was automatically generated by informer-gen + +package internalversion + +import ( + time "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" + admissionregistration "k8s.io/kubernetes/pkg/apis/admissionregistration" + internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" + internalversion "k8s.io/kubernetes/pkg/client/listers/admissionregistration/internalversion" +) + +// ValidatingWebhookConfigurationInformer provides access to a shared informer and lister for +// ValidatingWebhookConfigurations. +type ValidatingWebhookConfigurationInformer interface { + Informer() cache.SharedIndexInformer + Lister() internalversion.ValidatingWebhookConfigurationLister +} + +type validatingWebhookConfigurationInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// NewValidatingWebhookConfigurationInformer 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 NewValidatingWebhookConfigurationInformer(client internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredValidatingWebhookConfigurationInformer(client, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.Admissionregistration().ValidatingWebhookConfigurations().List(options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.Admissionregistration().ValidatingWebhookConfigurations().Watch(options) + }, + }, + &admissionregistration.ValidatingWebhookConfiguration{}, + resyncPeriod, + indexers, + ) +} + +func (f *validatingWebhookConfigurationInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredValidatingWebhookConfigurationInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *validatingWebhookConfigurationInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&admissionregistration.ValidatingWebhookConfiguration{}, f.defaultInformer) +} + +func (f *validatingWebhookConfigurationInformer) Lister() internalversion.ValidatingWebhookConfigurationLister { + return internalversion.NewValidatingWebhookConfigurationLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/informers_generated/internalversion/apps/interface.go b/pkg/client/informers/informers_generated/internalversion/apps/interface.go index 720ab8c11e2..a847b158afd 100644 --- a/pkg/client/informers/informers_generated/internalversion/apps/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/apps/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -30,15 +30,17 @@ type Interface interface { } type group struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &group{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // InternalVersion returns a new internalversion.Interface. func (g *group) InternalVersion() internalversion.Interface { - return internalversion.New(g.SharedInformerFactory) + return internalversion.New(g.factory, g.namespace, g.tweakListOptions) } diff --git a/pkg/client/informers/informers_generated/internalversion/apps/internalversion/controllerrevision.go b/pkg/client/informers/informers_generated/internalversion/apps/internalversion/controllerrevision.go index 11e381b537b..19b79bce962 100644 --- a/pkg/client/informers/informers_generated/internalversion/apps/internalversion/controllerrevision.go +++ b/pkg/client/informers/informers_generated/internalversion/apps/internalversion/controllerrevision.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,6 +19,8 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -27,7 +29,6 @@ import ( internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/apps/internalversion" - time "time" ) // ControllerRevisionInformer provides access to a shared informer and lister for @@ -38,19 +39,34 @@ type ControllerRevisionInformer interface { } type controllerRevisionInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } // NewControllerRevisionInformer 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 NewControllerRevisionInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredControllerRevisionInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Apps().ControllerRevisions(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Apps().ControllerRevisions(namespace).Watch(options) }, }, @@ -60,12 +76,12 @@ func NewControllerRevisionInformer(client internalclientset.Interface, namespace ) } -func defaultControllerRevisionInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewControllerRevisionInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *controllerRevisionInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredControllerRevisionInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *controllerRevisionInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&apps.ControllerRevision{}, defaultControllerRevisionInformer) + return f.factory.InformerFor(&apps.ControllerRevision{}, f.defaultInformer) } func (f *controllerRevisionInformer) Lister() internalversion.ControllerRevisionLister { diff --git a/pkg/client/informers/informers_generated/internalversion/apps/internalversion/interface.go b/pkg/client/informers/informers_generated/internalversion/apps/internalversion/interface.go index bd5ec23b152..1c75f780edb 100644 --- a/pkg/client/informers/informers_generated/internalversion/apps/internalversion/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/apps/internalversion/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -31,20 +31,22 @@ type Interface interface { } type version struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &version{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // ControllerRevisions returns a ControllerRevisionInformer. func (v *version) ControllerRevisions() ControllerRevisionInformer { - return &controllerRevisionInformer{factory: v.SharedInformerFactory} + return &controllerRevisionInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } // StatefulSets returns a StatefulSetInformer. func (v *version) StatefulSets() StatefulSetInformer { - return &statefulSetInformer{factory: v.SharedInformerFactory} + return &statefulSetInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } diff --git a/pkg/client/informers/informers_generated/internalversion/apps/internalversion/statefulset.go b/pkg/client/informers/informers_generated/internalversion/apps/internalversion/statefulset.go index e52b64661bd..a3db402fb53 100644 --- a/pkg/client/informers/informers_generated/internalversion/apps/internalversion/statefulset.go +++ b/pkg/client/informers/informers_generated/internalversion/apps/internalversion/statefulset.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,6 +19,8 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -27,7 +29,6 @@ import ( internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/apps/internalversion" - time "time" ) // StatefulSetInformer provides access to a shared informer and lister for @@ -38,19 +39,34 @@ type StatefulSetInformer interface { } type statefulSetInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } // NewStatefulSetInformer 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 NewStatefulSetInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredStatefulSetInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Apps().StatefulSets(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Apps().StatefulSets(namespace).Watch(options) }, }, @@ -60,12 +76,12 @@ func NewStatefulSetInformer(client internalclientset.Interface, namespace string ) } -func defaultStatefulSetInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewStatefulSetInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *statefulSetInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredStatefulSetInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *statefulSetInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&apps.StatefulSet{}, defaultStatefulSetInformer) + return f.factory.InformerFor(&apps.StatefulSet{}, f.defaultInformer) } func (f *statefulSetInformer) Lister() internalversion.StatefulSetLister { diff --git a/pkg/client/informers/informers_generated/internalversion/autoscaling/interface.go b/pkg/client/informers/informers_generated/internalversion/autoscaling/interface.go index 52a595766d9..930736e5bc7 100644 --- a/pkg/client/informers/informers_generated/internalversion/autoscaling/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/autoscaling/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -30,15 +30,17 @@ type Interface interface { } type group struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &group{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // InternalVersion returns a new internalversion.Interface. func (g *group) InternalVersion() internalversion.Interface { - return internalversion.New(g.SharedInformerFactory) + return internalversion.New(g.factory, g.namespace, g.tweakListOptions) } diff --git a/pkg/client/informers/informers_generated/internalversion/autoscaling/internalversion/horizontalpodautoscaler.go b/pkg/client/informers/informers_generated/internalversion/autoscaling/internalversion/horizontalpodautoscaler.go index cc8e7997bc9..a604fd981a5 100644 --- a/pkg/client/informers/informers_generated/internalversion/autoscaling/internalversion/horizontalpodautoscaler.go +++ b/pkg/client/informers/informers_generated/internalversion/autoscaling/internalversion/horizontalpodautoscaler.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,6 +19,8 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -27,7 +29,6 @@ import ( internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/autoscaling/internalversion" - time "time" ) // HorizontalPodAutoscalerInformer provides access to a shared informer and lister for @@ -38,19 +39,34 @@ type HorizontalPodAutoscalerInformer interface { } type horizontalPodAutoscalerInformer struct { - factory internalinterfaces.SharedInformerFactory + 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 internalclientset.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 internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Autoscaling().HorizontalPodAutoscalers(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Autoscaling().HorizontalPodAutoscalers(namespace).Watch(options) }, }, @@ -60,12 +76,12 @@ func NewHorizontalPodAutoscalerInformer(client internalclientset.Interface, name ) } -func defaultHorizontalPodAutoscalerInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewHorizontalPodAutoscalerInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *horizontalPodAutoscalerInformer) defaultInformer(client internalclientset.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(&autoscaling.HorizontalPodAutoscaler{}, defaultHorizontalPodAutoscalerInformer) + return f.factory.InformerFor(&autoscaling.HorizontalPodAutoscaler{}, f.defaultInformer) } func (f *horizontalPodAutoscalerInformer) Lister() internalversion.HorizontalPodAutoscalerLister { diff --git a/pkg/client/informers/informers_generated/internalversion/autoscaling/internalversion/interface.go b/pkg/client/informers/informers_generated/internalversion/autoscaling/internalversion/interface.go index e442cfb1b4d..37690fd99a3 100644 --- a/pkg/client/informers/informers_generated/internalversion/autoscaling/internalversion/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/autoscaling/internalversion/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -29,15 +29,17 @@ type Interface interface { } type version struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &version{f} +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.SharedInformerFactory} + return &horizontalPodAutoscalerInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } diff --git a/pkg/client/informers/informers_generated/internalversion/batch/interface.go b/pkg/client/informers/informers_generated/internalversion/batch/interface.go index 72bb5e72912..afb31334d9f 100644 --- a/pkg/client/informers/informers_generated/internalversion/batch/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/batch/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -30,15 +30,17 @@ type Interface interface { } type group struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &group{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // InternalVersion returns a new internalversion.Interface. func (g *group) InternalVersion() internalversion.Interface { - return internalversion.New(g.SharedInformerFactory) + return internalversion.New(g.factory, g.namespace, g.tweakListOptions) } diff --git a/pkg/client/informers/informers_generated/internalversion/batch/internalversion/cronjob.go b/pkg/client/informers/informers_generated/internalversion/batch/internalversion/cronjob.go index d0362f436a7..f47bfb24c30 100644 --- a/pkg/client/informers/informers_generated/internalversion/batch/internalversion/cronjob.go +++ b/pkg/client/informers/informers_generated/internalversion/batch/internalversion/cronjob.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,6 +19,8 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -27,7 +29,6 @@ import ( internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/batch/internalversion" - time "time" ) // CronJobInformer provides access to a shared informer and lister for @@ -38,19 +39,34 @@ type CronJobInformer interface { } type cronJobInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } // NewCronJobInformer 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 NewCronJobInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredCronJobInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Batch().CronJobs(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Batch().CronJobs(namespace).Watch(options) }, }, @@ -60,12 +76,12 @@ func NewCronJobInformer(client internalclientset.Interface, namespace string, re ) } -func defaultCronJobInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewCronJobInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *cronJobInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredCronJobInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *cronJobInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&batch.CronJob{}, defaultCronJobInformer) + return f.factory.InformerFor(&batch.CronJob{}, f.defaultInformer) } func (f *cronJobInformer) Lister() internalversion.CronJobLister { diff --git a/pkg/client/informers/informers_generated/internalversion/batch/internalversion/interface.go b/pkg/client/informers/informers_generated/internalversion/batch/internalversion/interface.go index e02c25aafb0..7064bd64c77 100644 --- a/pkg/client/informers/informers_generated/internalversion/batch/internalversion/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/batch/internalversion/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -31,20 +31,22 @@ type Interface interface { } type version struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &version{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // CronJobs returns a CronJobInformer. func (v *version) CronJobs() CronJobInformer { - return &cronJobInformer{factory: v.SharedInformerFactory} + return &cronJobInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } // Jobs returns a JobInformer. func (v *version) Jobs() JobInformer { - return &jobInformer{factory: v.SharedInformerFactory} + return &jobInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } diff --git a/pkg/client/informers/informers_generated/internalversion/batch/internalversion/job.go b/pkg/client/informers/informers_generated/internalversion/batch/internalversion/job.go index 56039f740a1..3ec10ba6a16 100644 --- a/pkg/client/informers/informers_generated/internalversion/batch/internalversion/job.go +++ b/pkg/client/informers/informers_generated/internalversion/batch/internalversion/job.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,6 +19,8 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -27,7 +29,6 @@ import ( internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/batch/internalversion" - time "time" ) // JobInformer provides access to a shared informer and lister for @@ -38,19 +39,34 @@ type JobInformer interface { } type jobInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } // NewJobInformer 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 NewJobInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredJobInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Batch().Jobs(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Batch().Jobs(namespace).Watch(options) }, }, @@ -60,12 +76,12 @@ func NewJobInformer(client internalclientset.Interface, namespace string, resync ) } -func defaultJobInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewJobInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *jobInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredJobInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *jobInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&batch.Job{}, defaultJobInformer) + return f.factory.InformerFor(&batch.Job{}, f.defaultInformer) } func (f *jobInformer) Lister() internalversion.JobLister { diff --git a/pkg/client/informers/informers_generated/internalversion/certificates/interface.go b/pkg/client/informers/informers_generated/internalversion/certificates/interface.go index eda8f815382..8aa1b6eb878 100644 --- a/pkg/client/informers/informers_generated/internalversion/certificates/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/certificates/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -30,15 +30,17 @@ type Interface interface { } type group struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &group{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // InternalVersion returns a new internalversion.Interface. func (g *group) InternalVersion() internalversion.Interface { - return internalversion.New(g.SharedInformerFactory) + return internalversion.New(g.factory, g.namespace, g.tweakListOptions) } diff --git a/pkg/client/informers/informers_generated/internalversion/certificates/internalversion/certificatesigningrequest.go b/pkg/client/informers/informers_generated/internalversion/certificates/internalversion/certificatesigningrequest.go index 88bda731beb..703bd632bc1 100644 --- a/pkg/client/informers/informers_generated/internalversion/certificates/internalversion/certificatesigningrequest.go +++ b/pkg/client/informers/informers_generated/internalversion/certificates/internalversion/certificatesigningrequest.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,6 +19,8 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -27,7 +29,6 @@ import ( internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/certificates/internalversion" - time "time" ) // CertificateSigningRequestInformer provides access to a shared informer and lister for @@ -38,19 +39,33 @@ type CertificateSigningRequestInformer interface { } type certificateSigningRequestInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc } // NewCertificateSigningRequestInformer 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 NewCertificateSigningRequestInformer(client internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredCertificateSigningRequestInformer(client, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Certificates().CertificateSigningRequests().List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Certificates().CertificateSigningRequests().Watch(options) }, }, @@ -60,12 +75,12 @@ func NewCertificateSigningRequestInformer(client internalclientset.Interface, re ) } -func defaultCertificateSigningRequestInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewCertificateSigningRequestInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *certificateSigningRequestInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredCertificateSigningRequestInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *certificateSigningRequestInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&certificates.CertificateSigningRequest{}, defaultCertificateSigningRequestInformer) + return f.factory.InformerFor(&certificates.CertificateSigningRequest{}, f.defaultInformer) } func (f *certificateSigningRequestInformer) Lister() internalversion.CertificateSigningRequestLister { diff --git a/pkg/client/informers/informers_generated/internalversion/certificates/internalversion/interface.go b/pkg/client/informers/informers_generated/internalversion/certificates/internalversion/interface.go index 6dcbb450300..b319d89c87c 100644 --- a/pkg/client/informers/informers_generated/internalversion/certificates/internalversion/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/certificates/internalversion/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -29,15 +29,17 @@ type Interface interface { } type version struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &version{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // CertificateSigningRequests returns a CertificateSigningRequestInformer. func (v *version) CertificateSigningRequests() CertificateSigningRequestInformer { - return &certificateSigningRequestInformer{factory: v.SharedInformerFactory} + return &certificateSigningRequestInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } diff --git a/pkg/client/informers/informers_generated/internalversion/core/interface.go b/pkg/client/informers/informers_generated/internalversion/core/interface.go index 329d6b529e1..5be46b908f7 100644 --- a/pkg/client/informers/informers_generated/internalversion/core/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/core/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -30,15 +30,17 @@ type Interface interface { } type group struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &group{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // InternalVersion returns a new internalversion.Interface. func (g *group) InternalVersion() internalversion.Interface { - return internalversion.New(g.SharedInformerFactory) + return internalversion.New(g.factory, g.namespace, g.tweakListOptions) } diff --git a/pkg/client/informers/informers_generated/internalversion/core/internalversion/BUILD b/pkg/client/informers/informers_generated/internalversion/core/internalversion/BUILD index 5d703c4a2ed..6724faa6a86 100644 --- a/pkg/client/informers/informers_generated/internalversion/core/internalversion/BUILD +++ b/pkg/client/informers/informers_generated/internalversion/core/internalversion/BUILD @@ -28,7 +28,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/core/internalversion", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/informers/informers_generated/internalversion/internalinterfaces:go_default_library", "//pkg/client/listers/core/internalversion:go_default_library", diff --git a/pkg/client/informers/informers_generated/internalversion/core/internalversion/componentstatus.go b/pkg/client/informers/informers_generated/internalversion/core/internalversion/componentstatus.go index 4044e1abad3..f61b6a082bf 100644 --- a/pkg/client/informers/informers_generated/internalversion/core/internalversion/componentstatus.go +++ b/pkg/client/informers/informers_generated/internalversion/core/internalversion/componentstatus.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,15 +19,16 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" cache "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/core/internalversion" - time "time" ) // ComponentStatusInformer provides access to a shared informer and lister for @@ -38,34 +39,48 @@ type ComponentStatusInformer interface { } type componentStatusInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc } // NewComponentStatusInformer 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 NewComponentStatusInformer(client internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredComponentStatusInformer(client, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().ComponentStatuses().List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().ComponentStatuses().Watch(options) }, }, - &api.ComponentStatus{}, + &core.ComponentStatus{}, resyncPeriod, indexers, ) } -func defaultComponentStatusInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewComponentStatusInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *componentStatusInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredComponentStatusInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *componentStatusInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&api.ComponentStatus{}, defaultComponentStatusInformer) + return f.factory.InformerFor(&core.ComponentStatus{}, f.defaultInformer) } func (f *componentStatusInformer) Lister() internalversion.ComponentStatusLister { diff --git a/pkg/client/informers/informers_generated/internalversion/core/internalversion/configmap.go b/pkg/client/informers/informers_generated/internalversion/core/internalversion/configmap.go index bfd2af64b27..2f1e3837b64 100644 --- a/pkg/client/informers/informers_generated/internalversion/core/internalversion/configmap.go +++ b/pkg/client/informers/informers_generated/internalversion/core/internalversion/configmap.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,15 +19,16 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" cache "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/core/internalversion" - time "time" ) // ConfigMapInformer provides access to a shared informer and lister for @@ -38,34 +39,49 @@ type ConfigMapInformer interface { } type configMapInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } // NewConfigMapInformer 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 NewConfigMapInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredConfigMapInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().ConfigMaps(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().ConfigMaps(namespace).Watch(options) }, }, - &api.ConfigMap{}, + &core.ConfigMap{}, resyncPeriod, indexers, ) } -func defaultConfigMapInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewConfigMapInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *configMapInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredConfigMapInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *configMapInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&api.ConfigMap{}, defaultConfigMapInformer) + return f.factory.InformerFor(&core.ConfigMap{}, f.defaultInformer) } func (f *configMapInformer) Lister() internalversion.ConfigMapLister { diff --git a/pkg/client/informers/informers_generated/internalversion/core/internalversion/endpoints.go b/pkg/client/informers/informers_generated/internalversion/core/internalversion/endpoints.go index bb999125686..fa400679836 100644 --- a/pkg/client/informers/informers_generated/internalversion/core/internalversion/endpoints.go +++ b/pkg/client/informers/informers_generated/internalversion/core/internalversion/endpoints.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,15 +19,16 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" cache "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/core/internalversion" - time "time" ) // EndpointsInformer provides access to a shared informer and lister for @@ -38,34 +39,49 @@ type EndpointsInformer interface { } type endpointsInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } // NewEndpointsInformer 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 NewEndpointsInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredEndpointsInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().Endpoints(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().Endpoints(namespace).Watch(options) }, }, - &api.Endpoints{}, + &core.Endpoints{}, resyncPeriod, indexers, ) } -func defaultEndpointsInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewEndpointsInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *endpointsInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredEndpointsInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *endpointsInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&api.Endpoints{}, defaultEndpointsInformer) + return f.factory.InformerFor(&core.Endpoints{}, f.defaultInformer) } func (f *endpointsInformer) Lister() internalversion.EndpointsLister { diff --git a/pkg/client/informers/informers_generated/internalversion/core/internalversion/event.go b/pkg/client/informers/informers_generated/internalversion/core/internalversion/event.go index 7b7108b61da..87a3288956b 100644 --- a/pkg/client/informers/informers_generated/internalversion/core/internalversion/event.go +++ b/pkg/client/informers/informers_generated/internalversion/core/internalversion/event.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,15 +19,16 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" cache "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/core/internalversion" - time "time" ) // EventInformer provides access to a shared informer and lister for @@ -38,34 +39,49 @@ type EventInformer interface { } type eventInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } // NewEventInformer 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 NewEventInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredEventInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().Events(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().Events(namespace).Watch(options) }, }, - &api.Event{}, + &core.Event{}, resyncPeriod, indexers, ) } -func defaultEventInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewEventInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *eventInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredEventInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *eventInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&api.Event{}, defaultEventInformer) + return f.factory.InformerFor(&core.Event{}, f.defaultInformer) } func (f *eventInformer) Lister() internalversion.EventLister { diff --git a/pkg/client/informers/informers_generated/internalversion/core/internalversion/interface.go b/pkg/client/informers/informers_generated/internalversion/core/internalversion/interface.go index a9f5f16335e..48a4e4a25a6 100644 --- a/pkg/client/informers/informers_generated/internalversion/core/internalversion/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/core/internalversion/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -59,90 +59,92 @@ type Interface interface { } type version struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &version{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // ComponentStatuses returns a ComponentStatusInformer. func (v *version) ComponentStatuses() ComponentStatusInformer { - return &componentStatusInformer{factory: v.SharedInformerFactory} + return &componentStatusInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } // ConfigMaps returns a ConfigMapInformer. func (v *version) ConfigMaps() ConfigMapInformer { - return &configMapInformer{factory: v.SharedInformerFactory} + return &configMapInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } // Endpoints returns a EndpointsInformer. func (v *version) Endpoints() EndpointsInformer { - return &endpointsInformer{factory: v.SharedInformerFactory} + return &endpointsInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } // Events returns a EventInformer. func (v *version) Events() EventInformer { - return &eventInformer{factory: v.SharedInformerFactory} + return &eventInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } // LimitRanges returns a LimitRangeInformer. func (v *version) LimitRanges() LimitRangeInformer { - return &limitRangeInformer{factory: v.SharedInformerFactory} + return &limitRangeInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } // Namespaces returns a NamespaceInformer. func (v *version) Namespaces() NamespaceInformer { - return &namespaceInformer{factory: v.SharedInformerFactory} + return &namespaceInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } // Nodes returns a NodeInformer. func (v *version) Nodes() NodeInformer { - return &nodeInformer{factory: v.SharedInformerFactory} + return &nodeInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } // PersistentVolumes returns a PersistentVolumeInformer. func (v *version) PersistentVolumes() PersistentVolumeInformer { - return &persistentVolumeInformer{factory: v.SharedInformerFactory} + return &persistentVolumeInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } // PersistentVolumeClaims returns a PersistentVolumeClaimInformer. func (v *version) PersistentVolumeClaims() PersistentVolumeClaimInformer { - return &persistentVolumeClaimInformer{factory: v.SharedInformerFactory} + return &persistentVolumeClaimInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } // Pods returns a PodInformer. func (v *version) Pods() PodInformer { - return &podInformer{factory: v.SharedInformerFactory} + return &podInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } // PodTemplates returns a PodTemplateInformer. func (v *version) PodTemplates() PodTemplateInformer { - return &podTemplateInformer{factory: v.SharedInformerFactory} + return &podTemplateInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } // ReplicationControllers returns a ReplicationControllerInformer. func (v *version) ReplicationControllers() ReplicationControllerInformer { - return &replicationControllerInformer{factory: v.SharedInformerFactory} + return &replicationControllerInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } // ResourceQuotas returns a ResourceQuotaInformer. func (v *version) ResourceQuotas() ResourceQuotaInformer { - return &resourceQuotaInformer{factory: v.SharedInformerFactory} + return &resourceQuotaInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } // Secrets returns a SecretInformer. func (v *version) Secrets() SecretInformer { - return &secretInformer{factory: v.SharedInformerFactory} + return &secretInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } // Services returns a ServiceInformer. func (v *version) Services() ServiceInformer { - return &serviceInformer{factory: v.SharedInformerFactory} + return &serviceInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } // ServiceAccounts returns a ServiceAccountInformer. func (v *version) ServiceAccounts() ServiceAccountInformer { - return &serviceAccountInformer{factory: v.SharedInformerFactory} + return &serviceAccountInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } diff --git a/pkg/client/informers/informers_generated/internalversion/core/internalversion/limitrange.go b/pkg/client/informers/informers_generated/internalversion/core/internalversion/limitrange.go index 8eea5642fc7..843754a3744 100644 --- a/pkg/client/informers/informers_generated/internalversion/core/internalversion/limitrange.go +++ b/pkg/client/informers/informers_generated/internalversion/core/internalversion/limitrange.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,15 +19,16 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" cache "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/core/internalversion" - time "time" ) // LimitRangeInformer provides access to a shared informer and lister for @@ -38,34 +39,49 @@ type LimitRangeInformer interface { } type limitRangeInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } // NewLimitRangeInformer 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 NewLimitRangeInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredLimitRangeInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().LimitRanges(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().LimitRanges(namespace).Watch(options) }, }, - &api.LimitRange{}, + &core.LimitRange{}, resyncPeriod, indexers, ) } -func defaultLimitRangeInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewLimitRangeInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *limitRangeInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredLimitRangeInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *limitRangeInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&api.LimitRange{}, defaultLimitRangeInformer) + return f.factory.InformerFor(&core.LimitRange{}, f.defaultInformer) } func (f *limitRangeInformer) Lister() internalversion.LimitRangeLister { diff --git a/pkg/client/informers/informers_generated/internalversion/core/internalversion/namespace.go b/pkg/client/informers/informers_generated/internalversion/core/internalversion/namespace.go index 8610b66c787..668c5186210 100644 --- a/pkg/client/informers/informers_generated/internalversion/core/internalversion/namespace.go +++ b/pkg/client/informers/informers_generated/internalversion/core/internalversion/namespace.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,15 +19,16 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" cache "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/core/internalversion" - time "time" ) // NamespaceInformer provides access to a shared informer and lister for @@ -38,34 +39,48 @@ type NamespaceInformer interface { } type namespaceInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc } // NewNamespaceInformer 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 NewNamespaceInformer(client internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredNamespaceInformer(client, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().Namespaces().List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().Namespaces().Watch(options) }, }, - &api.Namespace{}, + &core.Namespace{}, resyncPeriod, indexers, ) } -func defaultNamespaceInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewNamespaceInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *namespaceInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredNamespaceInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *namespaceInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&api.Namespace{}, defaultNamespaceInformer) + return f.factory.InformerFor(&core.Namespace{}, f.defaultInformer) } func (f *namespaceInformer) Lister() internalversion.NamespaceLister { diff --git a/pkg/client/informers/informers_generated/internalversion/core/internalversion/node.go b/pkg/client/informers/informers_generated/internalversion/core/internalversion/node.go index bc8432c3a7a..fddcf541b5c 100644 --- a/pkg/client/informers/informers_generated/internalversion/core/internalversion/node.go +++ b/pkg/client/informers/informers_generated/internalversion/core/internalversion/node.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,15 +19,16 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" cache "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/core/internalversion" - time "time" ) // NodeInformer provides access to a shared informer and lister for @@ -38,34 +39,48 @@ type NodeInformer interface { } type nodeInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc } // NewNodeInformer 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 NewNodeInformer(client internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredNodeInformer(client, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().Nodes().List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().Nodes().Watch(options) }, }, - &api.Node{}, + &core.Node{}, resyncPeriod, indexers, ) } -func defaultNodeInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewNodeInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *nodeInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredNodeInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *nodeInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&api.Node{}, defaultNodeInformer) + return f.factory.InformerFor(&core.Node{}, f.defaultInformer) } func (f *nodeInformer) Lister() internalversion.NodeLister { diff --git a/pkg/client/informers/informers_generated/internalversion/core/internalversion/persistentvolume.go b/pkg/client/informers/informers_generated/internalversion/core/internalversion/persistentvolume.go index 7f344f1fd8c..64a6014480d 100644 --- a/pkg/client/informers/informers_generated/internalversion/core/internalversion/persistentvolume.go +++ b/pkg/client/informers/informers_generated/internalversion/core/internalversion/persistentvolume.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,15 +19,16 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" cache "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/core/internalversion" - time "time" ) // PersistentVolumeInformer provides access to a shared informer and lister for @@ -38,34 +39,48 @@ type PersistentVolumeInformer interface { } type persistentVolumeInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc } // NewPersistentVolumeInformer 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 NewPersistentVolumeInformer(client internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredPersistentVolumeInformer(client, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().PersistentVolumes().List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().PersistentVolumes().Watch(options) }, }, - &api.PersistentVolume{}, + &core.PersistentVolume{}, resyncPeriod, indexers, ) } -func defaultPersistentVolumeInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewPersistentVolumeInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *persistentVolumeInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredPersistentVolumeInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *persistentVolumeInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&api.PersistentVolume{}, defaultPersistentVolumeInformer) + return f.factory.InformerFor(&core.PersistentVolume{}, f.defaultInformer) } func (f *persistentVolumeInformer) Lister() internalversion.PersistentVolumeLister { diff --git a/pkg/client/informers/informers_generated/internalversion/core/internalversion/persistentvolumeclaim.go b/pkg/client/informers/informers_generated/internalversion/core/internalversion/persistentvolumeclaim.go index e32130ba3b8..3d4092a37cf 100644 --- a/pkg/client/informers/informers_generated/internalversion/core/internalversion/persistentvolumeclaim.go +++ b/pkg/client/informers/informers_generated/internalversion/core/internalversion/persistentvolumeclaim.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,15 +19,16 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" cache "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/core/internalversion" - time "time" ) // PersistentVolumeClaimInformer provides access to a shared informer and lister for @@ -38,34 +39,49 @@ type PersistentVolumeClaimInformer interface { } type persistentVolumeClaimInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } // NewPersistentVolumeClaimInformer 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 NewPersistentVolumeClaimInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredPersistentVolumeClaimInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().PersistentVolumeClaims(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().PersistentVolumeClaims(namespace).Watch(options) }, }, - &api.PersistentVolumeClaim{}, + &core.PersistentVolumeClaim{}, resyncPeriod, indexers, ) } -func defaultPersistentVolumeClaimInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewPersistentVolumeClaimInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *persistentVolumeClaimInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredPersistentVolumeClaimInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *persistentVolumeClaimInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&api.PersistentVolumeClaim{}, defaultPersistentVolumeClaimInformer) + return f.factory.InformerFor(&core.PersistentVolumeClaim{}, f.defaultInformer) } func (f *persistentVolumeClaimInformer) Lister() internalversion.PersistentVolumeClaimLister { diff --git a/pkg/client/informers/informers_generated/internalversion/core/internalversion/pod.go b/pkg/client/informers/informers_generated/internalversion/core/internalversion/pod.go index a5bf7be529e..ec324fef2d7 100644 --- a/pkg/client/informers/informers_generated/internalversion/core/internalversion/pod.go +++ b/pkg/client/informers/informers_generated/internalversion/core/internalversion/pod.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,15 +19,16 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" cache "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/core/internalversion" - time "time" ) // PodInformer provides access to a shared informer and lister for @@ -38,34 +39,49 @@ type PodInformer interface { } type podInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } // NewPodInformer 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 NewPodInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredPodInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().Pods(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().Pods(namespace).Watch(options) }, }, - &api.Pod{}, + &core.Pod{}, resyncPeriod, indexers, ) } -func defaultPodInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewPodInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *podInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredPodInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *podInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&api.Pod{}, defaultPodInformer) + return f.factory.InformerFor(&core.Pod{}, f.defaultInformer) } func (f *podInformer) Lister() internalversion.PodLister { diff --git a/pkg/client/informers/informers_generated/internalversion/core/internalversion/podtemplate.go b/pkg/client/informers/informers_generated/internalversion/core/internalversion/podtemplate.go index 295fdc174c8..69e51de17d9 100644 --- a/pkg/client/informers/informers_generated/internalversion/core/internalversion/podtemplate.go +++ b/pkg/client/informers/informers_generated/internalversion/core/internalversion/podtemplate.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,15 +19,16 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" cache "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/core/internalversion" - time "time" ) // PodTemplateInformer provides access to a shared informer and lister for @@ -38,34 +39,49 @@ type PodTemplateInformer interface { } type podTemplateInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } // NewPodTemplateInformer 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 NewPodTemplateInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredPodTemplateInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().PodTemplates(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().PodTemplates(namespace).Watch(options) }, }, - &api.PodTemplate{}, + &core.PodTemplate{}, resyncPeriod, indexers, ) } -func defaultPodTemplateInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewPodTemplateInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *podTemplateInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredPodTemplateInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *podTemplateInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&api.PodTemplate{}, defaultPodTemplateInformer) + return f.factory.InformerFor(&core.PodTemplate{}, f.defaultInformer) } func (f *podTemplateInformer) Lister() internalversion.PodTemplateLister { diff --git a/pkg/client/informers/informers_generated/internalversion/core/internalversion/replicationcontroller.go b/pkg/client/informers/informers_generated/internalversion/core/internalversion/replicationcontroller.go index 592d6bcc472..d6bae5f9107 100644 --- a/pkg/client/informers/informers_generated/internalversion/core/internalversion/replicationcontroller.go +++ b/pkg/client/informers/informers_generated/internalversion/core/internalversion/replicationcontroller.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,15 +19,16 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" cache "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/core/internalversion" - time "time" ) // ReplicationControllerInformer provides access to a shared informer and lister for @@ -38,34 +39,49 @@ type ReplicationControllerInformer interface { } type replicationControllerInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } // NewReplicationControllerInformer 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 NewReplicationControllerInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredReplicationControllerInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().ReplicationControllers(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().ReplicationControllers(namespace).Watch(options) }, }, - &api.ReplicationController{}, + &core.ReplicationController{}, resyncPeriod, indexers, ) } -func defaultReplicationControllerInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewReplicationControllerInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *replicationControllerInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredReplicationControllerInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *replicationControllerInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&api.ReplicationController{}, defaultReplicationControllerInformer) + return f.factory.InformerFor(&core.ReplicationController{}, f.defaultInformer) } func (f *replicationControllerInformer) Lister() internalversion.ReplicationControllerLister { diff --git a/pkg/client/informers/informers_generated/internalversion/core/internalversion/resourcequota.go b/pkg/client/informers/informers_generated/internalversion/core/internalversion/resourcequota.go index 04327b479ed..c43cbdc6db9 100644 --- a/pkg/client/informers/informers_generated/internalversion/core/internalversion/resourcequota.go +++ b/pkg/client/informers/informers_generated/internalversion/core/internalversion/resourcequota.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,15 +19,16 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" cache "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/core/internalversion" - time "time" ) // ResourceQuotaInformer provides access to a shared informer and lister for @@ -38,34 +39,49 @@ type ResourceQuotaInformer interface { } type resourceQuotaInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } // NewResourceQuotaInformer 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 NewResourceQuotaInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredResourceQuotaInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().ResourceQuotas(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().ResourceQuotas(namespace).Watch(options) }, }, - &api.ResourceQuota{}, + &core.ResourceQuota{}, resyncPeriod, indexers, ) } -func defaultResourceQuotaInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewResourceQuotaInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *resourceQuotaInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredResourceQuotaInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *resourceQuotaInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&api.ResourceQuota{}, defaultResourceQuotaInformer) + return f.factory.InformerFor(&core.ResourceQuota{}, f.defaultInformer) } func (f *resourceQuotaInformer) Lister() internalversion.ResourceQuotaLister { diff --git a/pkg/client/informers/informers_generated/internalversion/core/internalversion/secret.go b/pkg/client/informers/informers_generated/internalversion/core/internalversion/secret.go index 1dae5677090..3e306f79b2e 100644 --- a/pkg/client/informers/informers_generated/internalversion/core/internalversion/secret.go +++ b/pkg/client/informers/informers_generated/internalversion/core/internalversion/secret.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,15 +19,16 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" cache "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/core/internalversion" - time "time" ) // SecretInformer provides access to a shared informer and lister for @@ -38,34 +39,49 @@ type SecretInformer interface { } type secretInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } // NewSecretInformer 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 NewSecretInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredSecretInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().Secrets(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().Secrets(namespace).Watch(options) }, }, - &api.Secret{}, + &core.Secret{}, resyncPeriod, indexers, ) } -func defaultSecretInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewSecretInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *secretInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredSecretInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *secretInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&api.Secret{}, defaultSecretInformer) + return f.factory.InformerFor(&core.Secret{}, f.defaultInformer) } func (f *secretInformer) Lister() internalversion.SecretLister { diff --git a/pkg/client/informers/informers_generated/internalversion/core/internalversion/service.go b/pkg/client/informers/informers_generated/internalversion/core/internalversion/service.go index 0e61757540e..be412ab982b 100644 --- a/pkg/client/informers/informers_generated/internalversion/core/internalversion/service.go +++ b/pkg/client/informers/informers_generated/internalversion/core/internalversion/service.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,15 +19,16 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" cache "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/core/internalversion" - time "time" ) // ServiceInformer provides access to a shared informer and lister for @@ -38,34 +39,49 @@ type ServiceInformer interface { } type serviceInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } // NewServiceInformer 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 NewServiceInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredServiceInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().Services(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().Services(namespace).Watch(options) }, }, - &api.Service{}, + &core.Service{}, resyncPeriod, indexers, ) } -func defaultServiceInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewServiceInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *serviceInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredServiceInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *serviceInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&api.Service{}, defaultServiceInformer) + return f.factory.InformerFor(&core.Service{}, f.defaultInformer) } func (f *serviceInformer) Lister() internalversion.ServiceLister { diff --git a/pkg/client/informers/informers_generated/internalversion/core/internalversion/serviceaccount.go b/pkg/client/informers/informers_generated/internalversion/core/internalversion/serviceaccount.go index dce710de768..a3247a566e1 100644 --- a/pkg/client/informers/informers_generated/internalversion/core/internalversion/serviceaccount.go +++ b/pkg/client/informers/informers_generated/internalversion/core/internalversion/serviceaccount.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,15 +19,16 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" cache "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/core/internalversion" - time "time" ) // ServiceAccountInformer provides access to a shared informer and lister for @@ -38,34 +39,49 @@ type ServiceAccountInformer interface { } type serviceAccountInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } // NewServiceAccountInformer 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 NewServiceAccountInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredServiceAccountInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().ServiceAccounts(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Core().ServiceAccounts(namespace).Watch(options) }, }, - &api.ServiceAccount{}, + &core.ServiceAccount{}, resyncPeriod, indexers, ) } -func defaultServiceAccountInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewServiceAccountInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *serviceAccountInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredServiceAccountInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *serviceAccountInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&api.ServiceAccount{}, defaultServiceAccountInformer) + return f.factory.InformerFor(&core.ServiceAccount{}, f.defaultInformer) } func (f *serviceAccountInformer) Lister() internalversion.ServiceAccountLister { diff --git a/pkg/client/informers/informers_generated/internalversion/extensions/interface.go b/pkg/client/informers/informers_generated/internalversion/extensions/interface.go index acc422e7989..55b3aa788b7 100644 --- a/pkg/client/informers/informers_generated/internalversion/extensions/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/extensions/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -30,15 +30,17 @@ type Interface interface { } type group struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &group{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // InternalVersion returns a new internalversion.Interface. func (g *group) InternalVersion() internalversion.Interface { - return internalversion.New(g.SharedInformerFactory) + return internalversion.New(g.factory, g.namespace, g.tweakListOptions) } diff --git a/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/BUILD b/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/BUILD index d0b14d962f2..4b31f38ce9b 100644 --- a/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/BUILD +++ b/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/BUILD @@ -14,7 +14,6 @@ go_library( "interface.go", "podsecuritypolicy.go", "replicaset.go", - "thirdpartyresource.go", ], importpath = "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/extensions/internalversion", deps = [ diff --git a/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/daemonset.go b/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/daemonset.go index 401a08551f5..7a6ad9f2570 100644 --- a/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/daemonset.go +++ b/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/daemonset.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,6 +19,8 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -27,7 +29,6 @@ import ( internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/extensions/internalversion" - time "time" ) // DaemonSetInformer provides access to a shared informer and lister for @@ -38,19 +39,34 @@ type DaemonSetInformer interface { } type daemonSetInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } // NewDaemonSetInformer 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 NewDaemonSetInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredDaemonSetInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Extensions().DaemonSets(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Extensions().DaemonSets(namespace).Watch(options) }, }, @@ -60,12 +76,12 @@ func NewDaemonSetInformer(client internalclientset.Interface, namespace string, ) } -func defaultDaemonSetInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewDaemonSetInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *daemonSetInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredDaemonSetInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *daemonSetInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&extensions.DaemonSet{}, defaultDaemonSetInformer) + return f.factory.InformerFor(&extensions.DaemonSet{}, f.defaultInformer) } func (f *daemonSetInformer) Lister() internalversion.DaemonSetLister { diff --git a/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/deployment.go b/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/deployment.go index 872c211e00a..c9d09f0a3c9 100644 --- a/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/deployment.go +++ b/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/deployment.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,6 +19,8 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -27,7 +29,6 @@ import ( internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/extensions/internalversion" - time "time" ) // DeploymentInformer provides access to a shared informer and lister for @@ -38,19 +39,34 @@ type DeploymentInformer interface { } type deploymentInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } // NewDeploymentInformer 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 NewDeploymentInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredDeploymentInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Extensions().Deployments(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Extensions().Deployments(namespace).Watch(options) }, }, @@ -60,12 +76,12 @@ func NewDeploymentInformer(client internalclientset.Interface, namespace string, ) } -func defaultDeploymentInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewDeploymentInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *deploymentInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredDeploymentInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *deploymentInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&extensions.Deployment{}, defaultDeploymentInformer) + return f.factory.InformerFor(&extensions.Deployment{}, f.defaultInformer) } func (f *deploymentInformer) Lister() internalversion.DeploymentLister { diff --git a/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/ingress.go b/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/ingress.go index 8c1e22d71cf..5f12d307f5e 100644 --- a/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/ingress.go +++ b/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/ingress.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,6 +19,8 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -27,7 +29,6 @@ import ( internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/extensions/internalversion" - time "time" ) // IngressInformer provides access to a shared informer and lister for @@ -38,19 +39,34 @@ type IngressInformer interface { } type ingressInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } // NewIngressInformer 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 NewIngressInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredIngressInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Extensions().Ingresses(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Extensions().Ingresses(namespace).Watch(options) }, }, @@ -60,12 +76,12 @@ func NewIngressInformer(client internalclientset.Interface, namespace string, re ) } -func defaultIngressInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewIngressInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *ingressInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredIngressInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *ingressInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&extensions.Ingress{}, defaultIngressInformer) + return f.factory.InformerFor(&extensions.Ingress{}, f.defaultInformer) } func (f *ingressInformer) Lister() internalversion.IngressLister { diff --git a/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/interface.go b/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/interface.go index 04e131c807b..7d5d8573584 100644 --- a/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -34,45 +34,40 @@ type Interface interface { PodSecurityPolicies() PodSecurityPolicyInformer // ReplicaSets returns a ReplicaSetInformer. ReplicaSets() ReplicaSetInformer - // ThirdPartyResources returns a ThirdPartyResourceInformer. - ThirdPartyResources() ThirdPartyResourceInformer } type version struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &version{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // DaemonSets returns a DaemonSetInformer. func (v *version) DaemonSets() DaemonSetInformer { - return &daemonSetInformer{factory: v.SharedInformerFactory} + return &daemonSetInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } // Deployments returns a DeploymentInformer. func (v *version) Deployments() DeploymentInformer { - return &deploymentInformer{factory: v.SharedInformerFactory} + return &deploymentInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } // Ingresses returns a IngressInformer. func (v *version) Ingresses() IngressInformer { - return &ingressInformer{factory: v.SharedInformerFactory} + return &ingressInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } // PodSecurityPolicies returns a PodSecurityPolicyInformer. func (v *version) PodSecurityPolicies() PodSecurityPolicyInformer { - return &podSecurityPolicyInformer{factory: v.SharedInformerFactory} + return &podSecurityPolicyInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } // ReplicaSets returns a ReplicaSetInformer. func (v *version) ReplicaSets() ReplicaSetInformer { - return &replicaSetInformer{factory: v.SharedInformerFactory} -} - -// ThirdPartyResources returns a ThirdPartyResourceInformer. -func (v *version) ThirdPartyResources() ThirdPartyResourceInformer { - return &thirdPartyResourceInformer{factory: v.SharedInformerFactory} + return &replicaSetInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } diff --git a/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/podsecuritypolicy.go b/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/podsecuritypolicy.go index 07d30d0dd28..02248fda6fe 100644 --- a/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/podsecuritypolicy.go +++ b/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/podsecuritypolicy.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,6 +19,8 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -27,7 +29,6 @@ import ( internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/extensions/internalversion" - time "time" ) // PodSecurityPolicyInformer provides access to a shared informer and lister for @@ -38,19 +39,33 @@ type PodSecurityPolicyInformer interface { } type podSecurityPolicyInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc } // NewPodSecurityPolicyInformer constructs a new informer for PodSecurityPolicy 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 NewPodSecurityPolicyInformer(client internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredPodSecurityPolicyInformer(client, resyncPeriod, indexers, nil) +} + +// NewFilteredPodSecurityPolicyInformer constructs a new informer for PodSecurityPolicy 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 NewFilteredPodSecurityPolicyInformer(client internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Extensions().PodSecurityPolicies().List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Extensions().PodSecurityPolicies().Watch(options) }, }, @@ -60,12 +75,12 @@ func NewPodSecurityPolicyInformer(client internalclientset.Interface, resyncPeri ) } -func defaultPodSecurityPolicyInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewPodSecurityPolicyInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *podSecurityPolicyInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredPodSecurityPolicyInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *podSecurityPolicyInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&extensions.PodSecurityPolicy{}, defaultPodSecurityPolicyInformer) + return f.factory.InformerFor(&extensions.PodSecurityPolicy{}, f.defaultInformer) } func (f *podSecurityPolicyInformer) Lister() internalversion.PodSecurityPolicyLister { diff --git a/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/replicaset.go b/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/replicaset.go index c5deda6ce61..fa51fc575cb 100644 --- a/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/replicaset.go +++ b/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/replicaset.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,6 +19,8 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -27,7 +29,6 @@ import ( internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/extensions/internalversion" - time "time" ) // ReplicaSetInformer provides access to a shared informer and lister for @@ -38,19 +39,34 @@ type ReplicaSetInformer interface { } type replicaSetInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } // NewReplicaSetInformer 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 NewReplicaSetInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredReplicaSetInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Extensions().ReplicaSets(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Extensions().ReplicaSets(namespace).Watch(options) }, }, @@ -60,12 +76,12 @@ func NewReplicaSetInformer(client internalclientset.Interface, namespace string, ) } -func defaultReplicaSetInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewReplicaSetInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *replicaSetInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredReplicaSetInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *replicaSetInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&extensions.ReplicaSet{}, defaultReplicaSetInformer) + return f.factory.InformerFor(&extensions.ReplicaSet{}, f.defaultInformer) } func (f *replicaSetInformer) Lister() internalversion.ReplicaSetLister { diff --git a/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/thirdpartyresource.go b/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/thirdpartyresource.go deleted file mode 100644 index 7b121bd4c50..00000000000 --- a/pkg/client/informers/informers_generated/internalversion/extensions/internalversion/thirdpartyresource.go +++ /dev/null @@ -1,73 +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. -*/ - -// This file was automatically generated by informer-gen - -package internalversion - -import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" - watch "k8s.io/apimachinery/pkg/watch" - cache "k8s.io/client-go/tools/cache" - extensions "k8s.io/kubernetes/pkg/apis/extensions" - internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" - internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" - internalversion "k8s.io/kubernetes/pkg/client/listers/extensions/internalversion" - time "time" -) - -// ThirdPartyResourceInformer provides access to a shared informer and lister for -// ThirdPartyResources. -type ThirdPartyResourceInformer interface { - Informer() cache.SharedIndexInformer - Lister() internalversion.ThirdPartyResourceLister -} - -type thirdPartyResourceInformer struct { - factory internalinterfaces.SharedInformerFactory -} - -// NewThirdPartyResourceInformer constructs a new informer for ThirdPartyResource 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 NewThirdPartyResourceInformer(client internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( - &cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { - return client.Extensions().ThirdPartyResources().List(options) - }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { - return client.Extensions().ThirdPartyResources().Watch(options) - }, - }, - &extensions.ThirdPartyResource{}, - resyncPeriod, - indexers, - ) -} - -func defaultThirdPartyResourceInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewThirdPartyResourceInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) -} - -func (f *thirdPartyResourceInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&extensions.ThirdPartyResource{}, defaultThirdPartyResourceInformer) -} - -func (f *thirdPartyResourceInformer) Lister() internalversion.ThirdPartyResourceLister { - return internalversion.NewThirdPartyResourceLister(f.Informer().GetIndexer()) -} diff --git a/pkg/client/informers/informers_generated/internalversion/factory.go b/pkg/client/informers/informers_generated/internalversion/factory.go index c5dba5cc025..4b8394e9b15 100644 --- a/pkg/client/informers/informers_generated/internalversion/factory.go +++ b/pkg/client/informers/informers_generated/internalversion/factory.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,6 +19,11 @@ limitations under the License. package internalversion import ( + reflect "reflect" + sync "sync" + time "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" cache "k8s.io/client-go/tools/cache" @@ -37,15 +42,14 @@ import ( scheduling "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/scheduling" settings "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/settings" storage "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/storage" - reflect "reflect" - sync "sync" - time "time" ) type sharedInformerFactory struct { - client internalclientset.Interface - lock sync.Mutex - defaultResync time.Duration + client internalclientset.Interface + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc + lock sync.Mutex + defaultResync time.Duration informers map[reflect.Type]cache.SharedIndexInformer // startedInformers is used for tracking which informers have been started. @@ -55,8 +59,17 @@ type sharedInformerFactory struct { // NewSharedInformerFactory constructs a new instance of sharedInformerFactory func NewSharedInformerFactory(client internalclientset.Interface, defaultResync time.Duration) SharedInformerFactory { + return NewFilteredSharedInformerFactory(client, defaultResync, v1.NamespaceAll, nil) +} + +// NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory. +// Listers obtained via this SharedInformerFactory will be subject to the same filters +// as specified here. +func NewFilteredSharedInformerFactory(client internalclientset.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory { return &sharedInformerFactory{ client: client, + namespace: namespace, + tweakListOptions: tweakListOptions, defaultResync: defaultResync, informers: make(map[reflect.Type]cache.SharedIndexInformer), startedInformers: make(map[reflect.Type]bool), @@ -138,53 +151,53 @@ type SharedInformerFactory interface { } func (f *sharedInformerFactory) Admissionregistration() admissionregistration.Interface { - return admissionregistration.New(f) + return admissionregistration.New(f, f.namespace, f.tweakListOptions) } func (f *sharedInformerFactory) Apps() apps.Interface { - return apps.New(f) + return apps.New(f, f.namespace, f.tweakListOptions) } func (f *sharedInformerFactory) Autoscaling() autoscaling.Interface { - return autoscaling.New(f) + return autoscaling.New(f, f.namespace, f.tweakListOptions) } func (f *sharedInformerFactory) Batch() batch.Interface { - return batch.New(f) + return batch.New(f, f.namespace, f.tweakListOptions) } func (f *sharedInformerFactory) Certificates() certificates.Interface { - return certificates.New(f) + return certificates.New(f, f.namespace, f.tweakListOptions) } func (f *sharedInformerFactory) Core() core.Interface { - return core.New(f) + return core.New(f, f.namespace, f.tweakListOptions) } func (f *sharedInformerFactory) Extensions() extensions.Interface { - return extensions.New(f) + return extensions.New(f, f.namespace, f.tweakListOptions) } func (f *sharedInformerFactory) Networking() networking.Interface { - return networking.New(f) + return networking.New(f, f.namespace, f.tweakListOptions) } func (f *sharedInformerFactory) Policy() policy.Interface { - return policy.New(f) + return policy.New(f, f.namespace, f.tweakListOptions) } func (f *sharedInformerFactory) Rbac() rbac.Interface { - return rbac.New(f) + return rbac.New(f, f.namespace, f.tweakListOptions) } func (f *sharedInformerFactory) Scheduling() scheduling.Interface { - return scheduling.New(f) + return scheduling.New(f, f.namespace, f.tweakListOptions) } func (f *sharedInformerFactory) Settings() settings.Interface { - return settings.New(f) + return settings.New(f, f.namespace, f.tweakListOptions) } func (f *sharedInformerFactory) Storage() storage.Interface { - return storage.New(f) + return storage.New(f, f.namespace, f.tweakListOptions) } diff --git a/pkg/client/informers/informers_generated/internalversion/generic.go b/pkg/client/informers/informers_generated/internalversion/generic.go index 8fa88584b7f..31370e7861c 100644 --- a/pkg/client/informers/informers_generated/internalversion/generic.go +++ b/pkg/client/informers/informers_generated/internalversion/generic.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -20,14 +20,15 @@ package internalversion import ( "fmt" + schema "k8s.io/apimachinery/pkg/runtime/schema" cache "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" admissionregistration "k8s.io/kubernetes/pkg/apis/admissionregistration" apps "k8s.io/kubernetes/pkg/apis/apps" autoscaling "k8s.io/kubernetes/pkg/apis/autoscaling" batch "k8s.io/kubernetes/pkg/apis/batch" certificates "k8s.io/kubernetes/pkg/apis/certificates" + core "k8s.io/kubernetes/pkg/apis/core" extensions "k8s.io/kubernetes/pkg/apis/extensions" networking "k8s.io/kubernetes/pkg/apis/networking" policy "k8s.io/kubernetes/pkg/apis/policy" @@ -63,67 +64,69 @@ func (f *genericInformer) Lister() cache.GenericLister { // TODO extend this to unknown resources with a client pool func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { switch resource { - // Group=Admissionregistration, Version=InternalVersion - case admissionregistration.SchemeGroupVersion.WithResource("externaladmissionhookconfigurations"): - return &genericInformer{resource: resource.GroupResource(), informer: f.Admissionregistration().InternalVersion().ExternalAdmissionHookConfigurations().Informer()}, nil + // Group=admissionregistration.k8s.io, Version=internalVersion case admissionregistration.SchemeGroupVersion.WithResource("initializerconfigurations"): return &genericInformer{resource: resource.GroupResource(), informer: f.Admissionregistration().InternalVersion().InitializerConfigurations().Informer()}, nil + case admissionregistration.SchemeGroupVersion.WithResource("mutatingwebhookconfigurations"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Admissionregistration().InternalVersion().MutatingWebhookConfigurations().Informer()}, nil + case admissionregistration.SchemeGroupVersion.WithResource("validatingwebhookconfigurations"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Admissionregistration().InternalVersion().ValidatingWebhookConfigurations().Informer()}, nil - // Group=Apps, Version=InternalVersion + // Group=apps, Version=internalVersion case apps.SchemeGroupVersion.WithResource("controllerrevisions"): return &genericInformer{resource: resource.GroupResource(), informer: f.Apps().InternalVersion().ControllerRevisions().Informer()}, nil case apps.SchemeGroupVersion.WithResource("statefulsets"): return &genericInformer{resource: resource.GroupResource(), informer: f.Apps().InternalVersion().StatefulSets().Informer()}, nil - // Group=Autoscaling, Version=InternalVersion + // Group=autoscaling, Version=internalVersion case autoscaling.SchemeGroupVersion.WithResource("horizontalpodautoscalers"): return &genericInformer{resource: resource.GroupResource(), informer: f.Autoscaling().InternalVersion().HorizontalPodAutoscalers().Informer()}, nil - // Group=Batch, Version=InternalVersion + // Group=batch, Version=internalVersion case batch.SchemeGroupVersion.WithResource("cronjobs"): return &genericInformer{resource: resource.GroupResource(), informer: f.Batch().InternalVersion().CronJobs().Informer()}, nil case batch.SchemeGroupVersion.WithResource("jobs"): return &genericInformer{resource: resource.GroupResource(), informer: f.Batch().InternalVersion().Jobs().Informer()}, nil - // Group=Certificates, Version=InternalVersion + // Group=certificates.k8s.io, Version=internalVersion case certificates.SchemeGroupVersion.WithResource("certificatesigningrequests"): return &genericInformer{resource: resource.GroupResource(), informer: f.Certificates().InternalVersion().CertificateSigningRequests().Informer()}, nil - // Group=Core, Version=InternalVersion - case api.SchemeGroupVersion.WithResource("componentstatuses"): + // Group=core, Version=internalVersion + case core.SchemeGroupVersion.WithResource("componentstatuses"): return &genericInformer{resource: resource.GroupResource(), informer: f.Core().InternalVersion().ComponentStatuses().Informer()}, nil - case api.SchemeGroupVersion.WithResource("configmaps"): + case core.SchemeGroupVersion.WithResource("configmaps"): return &genericInformer{resource: resource.GroupResource(), informer: f.Core().InternalVersion().ConfigMaps().Informer()}, nil - case api.SchemeGroupVersion.WithResource("endpoints"): + case core.SchemeGroupVersion.WithResource("endpoints"): return &genericInformer{resource: resource.GroupResource(), informer: f.Core().InternalVersion().Endpoints().Informer()}, nil - case api.SchemeGroupVersion.WithResource("events"): + case core.SchemeGroupVersion.WithResource("events"): return &genericInformer{resource: resource.GroupResource(), informer: f.Core().InternalVersion().Events().Informer()}, nil - case api.SchemeGroupVersion.WithResource("limitranges"): + case core.SchemeGroupVersion.WithResource("limitranges"): return &genericInformer{resource: resource.GroupResource(), informer: f.Core().InternalVersion().LimitRanges().Informer()}, nil - case api.SchemeGroupVersion.WithResource("namespaces"): + case core.SchemeGroupVersion.WithResource("namespaces"): return &genericInformer{resource: resource.GroupResource(), informer: f.Core().InternalVersion().Namespaces().Informer()}, nil - case api.SchemeGroupVersion.WithResource("nodes"): + case core.SchemeGroupVersion.WithResource("nodes"): return &genericInformer{resource: resource.GroupResource(), informer: f.Core().InternalVersion().Nodes().Informer()}, nil - case api.SchemeGroupVersion.WithResource("persistentvolumes"): + case core.SchemeGroupVersion.WithResource("persistentvolumes"): return &genericInformer{resource: resource.GroupResource(), informer: f.Core().InternalVersion().PersistentVolumes().Informer()}, nil - case api.SchemeGroupVersion.WithResource("persistentvolumeclaims"): + case core.SchemeGroupVersion.WithResource("persistentvolumeclaims"): return &genericInformer{resource: resource.GroupResource(), informer: f.Core().InternalVersion().PersistentVolumeClaims().Informer()}, nil - case api.SchemeGroupVersion.WithResource("pods"): + case core.SchemeGroupVersion.WithResource("pods"): return &genericInformer{resource: resource.GroupResource(), informer: f.Core().InternalVersion().Pods().Informer()}, nil - case api.SchemeGroupVersion.WithResource("podtemplates"): + case core.SchemeGroupVersion.WithResource("podtemplates"): return &genericInformer{resource: resource.GroupResource(), informer: f.Core().InternalVersion().PodTemplates().Informer()}, nil - case api.SchemeGroupVersion.WithResource("replicationcontrollers"): + case core.SchemeGroupVersion.WithResource("replicationcontrollers"): return &genericInformer{resource: resource.GroupResource(), informer: f.Core().InternalVersion().ReplicationControllers().Informer()}, nil - case api.SchemeGroupVersion.WithResource("resourcequotas"): + case core.SchemeGroupVersion.WithResource("resourcequotas"): return &genericInformer{resource: resource.GroupResource(), informer: f.Core().InternalVersion().ResourceQuotas().Informer()}, nil - case api.SchemeGroupVersion.WithResource("secrets"): + case core.SchemeGroupVersion.WithResource("secrets"): return &genericInformer{resource: resource.GroupResource(), informer: f.Core().InternalVersion().Secrets().Informer()}, nil - case api.SchemeGroupVersion.WithResource("services"): + case core.SchemeGroupVersion.WithResource("services"): return &genericInformer{resource: resource.GroupResource(), informer: f.Core().InternalVersion().Services().Informer()}, nil - case api.SchemeGroupVersion.WithResource("serviceaccounts"): + case core.SchemeGroupVersion.WithResource("serviceaccounts"): return &genericInformer{resource: resource.GroupResource(), informer: f.Core().InternalVersion().ServiceAccounts().Informer()}, nil - // Group=Extensions, Version=InternalVersion + // Group=extensions, Version=internalVersion case extensions.SchemeGroupVersion.WithResource("daemonsets"): return &genericInformer{resource: resource.GroupResource(), informer: f.Extensions().InternalVersion().DaemonSets().Informer()}, nil case extensions.SchemeGroupVersion.WithResource("deployments"): @@ -134,18 +137,16 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Extensions().InternalVersion().PodSecurityPolicies().Informer()}, nil case extensions.SchemeGroupVersion.WithResource("replicasets"): return &genericInformer{resource: resource.GroupResource(), informer: f.Extensions().InternalVersion().ReplicaSets().Informer()}, nil - case extensions.SchemeGroupVersion.WithResource("thirdpartyresources"): - return &genericInformer{resource: resource.GroupResource(), informer: f.Extensions().InternalVersion().ThirdPartyResources().Informer()}, nil - // Group=Networking, Version=InternalVersion + // Group=networking.k8s.io, Version=internalVersion case networking.SchemeGroupVersion.WithResource("networkpolicies"): return &genericInformer{resource: resource.GroupResource(), informer: f.Networking().InternalVersion().NetworkPolicies().Informer()}, nil - // Group=Policy, Version=InternalVersion + // Group=policy, Version=internalVersion case policy.SchemeGroupVersion.WithResource("poddisruptionbudgets"): return &genericInformer{resource: resource.GroupResource(), informer: f.Policy().InternalVersion().PodDisruptionBudgets().Informer()}, nil - // Group=Rbac, Version=InternalVersion + // Group=rbac.authorization.k8s.io, Version=internalVersion case rbac.SchemeGroupVersion.WithResource("clusterroles"): return &genericInformer{resource: resource.GroupResource(), informer: f.Rbac().InternalVersion().ClusterRoles().Informer()}, nil case rbac.SchemeGroupVersion.WithResource("clusterrolebindings"): @@ -155,17 +156,19 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource case rbac.SchemeGroupVersion.WithResource("rolebindings"): return &genericInformer{resource: resource.GroupResource(), informer: f.Rbac().InternalVersion().RoleBindings().Informer()}, nil - // Group=Scheduling, Version=InternalVersion + // Group=scheduling.k8s.io, Version=internalVersion case scheduling.SchemeGroupVersion.WithResource("priorityclasses"): return &genericInformer{resource: resource.GroupResource(), informer: f.Scheduling().InternalVersion().PriorityClasses().Informer()}, nil - // Group=Settings, Version=InternalVersion + // Group=settings.k8s.io, Version=internalVersion case settings.SchemeGroupVersion.WithResource("podpresets"): return &genericInformer{resource: resource.GroupResource(), informer: f.Settings().InternalVersion().PodPresets().Informer()}, nil - // Group=Storage, Version=InternalVersion + // Group=storage.k8s.io, Version=internalVersion case storage.SchemeGroupVersion.WithResource("storageclasses"): return &genericInformer{resource: resource.GroupResource(), informer: f.Storage().InternalVersion().StorageClasses().Informer()}, nil + case storage.SchemeGroupVersion.WithResource("volumeattachments"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Storage().InternalVersion().VolumeAttachments().Informer()}, nil } diff --git a/pkg/client/informers/informers_generated/internalversion/internalinterfaces/BUILD b/pkg/client/informers/informers_generated/internalversion/internalinterfaces/BUILD index 05f5fa18194..a87900962d2 100644 --- a/pkg/client/informers/informers_generated/internalversion/internalinterfaces/BUILD +++ b/pkg/client/informers/informers_generated/internalversion/internalinterfaces/BUILD @@ -11,6 +11,7 @@ go_library( importpath = "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces", deps = [ "//pkg/client/clientset_generated/internalclientset:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", ], diff --git a/pkg/client/informers/informers_generated/internalversion/internalinterfaces/factory_interfaces.go b/pkg/client/informers/informers_generated/internalversion/internalinterfaces/factory_interfaces.go index 5b168b5919e..bf129d9217d 100644 --- a/pkg/client/informers/informers_generated/internalversion/internalinterfaces/factory_interfaces.go +++ b/pkg/client/informers/informers_generated/internalversion/internalinterfaces/factory_interfaces.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,10 +19,12 @@ limitations under the License. package internalinterfaces import ( + time "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" cache "k8s.io/client-go/tools/cache" internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" - time "time" ) type NewInformerFunc func(internalclientset.Interface, time.Duration) cache.SharedIndexInformer @@ -32,3 +34,5 @@ type SharedInformerFactory interface { Start(stopCh <-chan struct{}) InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer } + +type TweakListOptionsFunc func(*v1.ListOptions) diff --git a/pkg/client/informers/informers_generated/internalversion/networking/interface.go b/pkg/client/informers/informers_generated/internalversion/networking/interface.go index bae69a50414..bf6917bb85d 100644 --- a/pkg/client/informers/informers_generated/internalversion/networking/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/networking/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -30,15 +30,17 @@ type Interface interface { } type group struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &group{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // InternalVersion returns a new internalversion.Interface. func (g *group) InternalVersion() internalversion.Interface { - return internalversion.New(g.SharedInformerFactory) + return internalversion.New(g.factory, g.namespace, g.tweakListOptions) } diff --git a/pkg/client/informers/informers_generated/internalversion/networking/internalversion/interface.go b/pkg/client/informers/informers_generated/internalversion/networking/internalversion/interface.go index e3eb5fa8181..48b5b4852cc 100644 --- a/pkg/client/informers/informers_generated/internalversion/networking/internalversion/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/networking/internalversion/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -29,15 +29,17 @@ type Interface interface { } type version struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &version{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // NetworkPolicies returns a NetworkPolicyInformer. func (v *version) NetworkPolicies() NetworkPolicyInformer { - return &networkPolicyInformer{factory: v.SharedInformerFactory} + return &networkPolicyInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } diff --git a/pkg/client/informers/informers_generated/internalversion/networking/internalversion/networkpolicy.go b/pkg/client/informers/informers_generated/internalversion/networking/internalversion/networkpolicy.go index aea32f716b5..5220eabd7ef 100644 --- a/pkg/client/informers/informers_generated/internalversion/networking/internalversion/networkpolicy.go +++ b/pkg/client/informers/informers_generated/internalversion/networking/internalversion/networkpolicy.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,6 +19,8 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -27,7 +29,6 @@ import ( internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/networking/internalversion" - time "time" ) // NetworkPolicyInformer provides access to a shared informer and lister for @@ -38,19 +39,34 @@ type NetworkPolicyInformer interface { } type networkPolicyInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } // NewNetworkPolicyInformer 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 NewNetworkPolicyInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredNetworkPolicyInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Networking().NetworkPolicies(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Networking().NetworkPolicies(namespace).Watch(options) }, }, @@ -60,12 +76,12 @@ func NewNetworkPolicyInformer(client internalclientset.Interface, namespace stri ) } -func defaultNetworkPolicyInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewNetworkPolicyInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *networkPolicyInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredNetworkPolicyInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *networkPolicyInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&networking.NetworkPolicy{}, defaultNetworkPolicyInformer) + return f.factory.InformerFor(&networking.NetworkPolicy{}, f.defaultInformer) } func (f *networkPolicyInformer) Lister() internalversion.NetworkPolicyLister { diff --git a/pkg/client/informers/informers_generated/internalversion/policy/interface.go b/pkg/client/informers/informers_generated/internalversion/policy/interface.go index 82977c9ce9f..93b04cf43f3 100644 --- a/pkg/client/informers/informers_generated/internalversion/policy/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/policy/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -30,15 +30,17 @@ type Interface interface { } type group struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &group{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // InternalVersion returns a new internalversion.Interface. func (g *group) InternalVersion() internalversion.Interface { - return internalversion.New(g.SharedInformerFactory) + return internalversion.New(g.factory, g.namespace, g.tweakListOptions) } diff --git a/pkg/client/informers/informers_generated/internalversion/policy/internalversion/interface.go b/pkg/client/informers/informers_generated/internalversion/policy/internalversion/interface.go index 258654df2ec..38239a69eb2 100644 --- a/pkg/client/informers/informers_generated/internalversion/policy/internalversion/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/policy/internalversion/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -29,15 +29,17 @@ type Interface interface { } type version struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &version{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // PodDisruptionBudgets returns a PodDisruptionBudgetInformer. func (v *version) PodDisruptionBudgets() PodDisruptionBudgetInformer { - return &podDisruptionBudgetInformer{factory: v.SharedInformerFactory} + return &podDisruptionBudgetInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } diff --git a/pkg/client/informers/informers_generated/internalversion/policy/internalversion/poddisruptionbudget.go b/pkg/client/informers/informers_generated/internalversion/policy/internalversion/poddisruptionbudget.go index 6be99a7120b..db2fe0410a6 100644 --- a/pkg/client/informers/informers_generated/internalversion/policy/internalversion/poddisruptionbudget.go +++ b/pkg/client/informers/informers_generated/internalversion/policy/internalversion/poddisruptionbudget.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,6 +19,8 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -27,7 +29,6 @@ import ( internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/policy/internalversion" - time "time" ) // PodDisruptionBudgetInformer provides access to a shared informer and lister for @@ -38,19 +39,34 @@ type PodDisruptionBudgetInformer interface { } type podDisruptionBudgetInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } // NewPodDisruptionBudgetInformer 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 NewPodDisruptionBudgetInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredPodDisruptionBudgetInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Policy().PodDisruptionBudgets(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Policy().PodDisruptionBudgets(namespace).Watch(options) }, }, @@ -60,12 +76,12 @@ func NewPodDisruptionBudgetInformer(client internalclientset.Interface, namespac ) } -func defaultPodDisruptionBudgetInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewPodDisruptionBudgetInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *podDisruptionBudgetInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredPodDisruptionBudgetInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *podDisruptionBudgetInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&policy.PodDisruptionBudget{}, defaultPodDisruptionBudgetInformer) + return f.factory.InformerFor(&policy.PodDisruptionBudget{}, f.defaultInformer) } func (f *podDisruptionBudgetInformer) Lister() internalversion.PodDisruptionBudgetLister { diff --git a/pkg/client/informers/informers_generated/internalversion/rbac/interface.go b/pkg/client/informers/informers_generated/internalversion/rbac/interface.go index 47dad0522d3..4d6cec593f4 100644 --- a/pkg/client/informers/informers_generated/internalversion/rbac/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/rbac/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -30,15 +30,17 @@ type Interface interface { } type group struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &group{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // InternalVersion returns a new internalversion.Interface. func (g *group) InternalVersion() internalversion.Interface { - return internalversion.New(g.SharedInformerFactory) + return internalversion.New(g.factory, g.namespace, g.tweakListOptions) } diff --git a/pkg/client/informers/informers_generated/internalversion/rbac/internalversion/clusterrole.go b/pkg/client/informers/informers_generated/internalversion/rbac/internalversion/clusterrole.go index 6f3d575ce02..2d7e68d9520 100644 --- a/pkg/client/informers/informers_generated/internalversion/rbac/internalversion/clusterrole.go +++ b/pkg/client/informers/informers_generated/internalversion/rbac/internalversion/clusterrole.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,6 +19,8 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -27,7 +29,6 @@ import ( internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/rbac/internalversion" - time "time" ) // ClusterRoleInformer provides access to a shared informer and lister for @@ -38,19 +39,33 @@ type ClusterRoleInformer interface { } type clusterRoleInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc } // NewClusterRoleInformer 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 NewClusterRoleInformer(client internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredClusterRoleInformer(client, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Rbac().ClusterRoles().List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Rbac().ClusterRoles().Watch(options) }, }, @@ -60,12 +75,12 @@ func NewClusterRoleInformer(client internalclientset.Interface, resyncPeriod tim ) } -func defaultClusterRoleInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewClusterRoleInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *clusterRoleInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredClusterRoleInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *clusterRoleInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&rbac.ClusterRole{}, defaultClusterRoleInformer) + return f.factory.InformerFor(&rbac.ClusterRole{}, f.defaultInformer) } func (f *clusterRoleInformer) Lister() internalversion.ClusterRoleLister { diff --git a/pkg/client/informers/informers_generated/internalversion/rbac/internalversion/clusterrolebinding.go b/pkg/client/informers/informers_generated/internalversion/rbac/internalversion/clusterrolebinding.go index e0c5160a732..11f05913eaf 100644 --- a/pkg/client/informers/informers_generated/internalversion/rbac/internalversion/clusterrolebinding.go +++ b/pkg/client/informers/informers_generated/internalversion/rbac/internalversion/clusterrolebinding.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,6 +19,8 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -27,7 +29,6 @@ import ( internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/rbac/internalversion" - time "time" ) // ClusterRoleBindingInformer provides access to a shared informer and lister for @@ -38,19 +39,33 @@ type ClusterRoleBindingInformer interface { } type clusterRoleBindingInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc } // NewClusterRoleBindingInformer 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 NewClusterRoleBindingInformer(client internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredClusterRoleBindingInformer(client, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Rbac().ClusterRoleBindings().List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Rbac().ClusterRoleBindings().Watch(options) }, }, @@ -60,12 +75,12 @@ func NewClusterRoleBindingInformer(client internalclientset.Interface, resyncPer ) } -func defaultClusterRoleBindingInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewClusterRoleBindingInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *clusterRoleBindingInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredClusterRoleBindingInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *clusterRoleBindingInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&rbac.ClusterRoleBinding{}, defaultClusterRoleBindingInformer) + return f.factory.InformerFor(&rbac.ClusterRoleBinding{}, f.defaultInformer) } func (f *clusterRoleBindingInformer) Lister() internalversion.ClusterRoleBindingLister { diff --git a/pkg/client/informers/informers_generated/internalversion/rbac/internalversion/interface.go b/pkg/client/informers/informers_generated/internalversion/rbac/internalversion/interface.go index fc85041c4a4..920ebda64dc 100644 --- a/pkg/client/informers/informers_generated/internalversion/rbac/internalversion/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/rbac/internalversion/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -35,30 +35,32 @@ type Interface interface { } type version struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &version{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // ClusterRoles returns a ClusterRoleInformer. func (v *version) ClusterRoles() ClusterRoleInformer { - return &clusterRoleInformer{factory: v.SharedInformerFactory} + return &clusterRoleInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } // ClusterRoleBindings returns a ClusterRoleBindingInformer. func (v *version) ClusterRoleBindings() ClusterRoleBindingInformer { - return &clusterRoleBindingInformer{factory: v.SharedInformerFactory} + return &clusterRoleBindingInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } // Roles returns a RoleInformer. func (v *version) Roles() RoleInformer { - return &roleInformer{factory: v.SharedInformerFactory} + return &roleInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } // RoleBindings returns a RoleBindingInformer. func (v *version) RoleBindings() RoleBindingInformer { - return &roleBindingInformer{factory: v.SharedInformerFactory} + return &roleBindingInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } diff --git a/pkg/client/informers/informers_generated/internalversion/rbac/internalversion/role.go b/pkg/client/informers/informers_generated/internalversion/rbac/internalversion/role.go index 5d61c9c28e7..687bf1dc4aa 100644 --- a/pkg/client/informers/informers_generated/internalversion/rbac/internalversion/role.go +++ b/pkg/client/informers/informers_generated/internalversion/rbac/internalversion/role.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,6 +19,8 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -27,7 +29,6 @@ import ( internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/rbac/internalversion" - time "time" ) // RoleInformer provides access to a shared informer and lister for @@ -38,19 +39,34 @@ type RoleInformer interface { } type roleInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } // NewRoleInformer 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 NewRoleInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredRoleInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Rbac().Roles(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Rbac().Roles(namespace).Watch(options) }, }, @@ -60,12 +76,12 @@ func NewRoleInformer(client internalclientset.Interface, namespace string, resyn ) } -func defaultRoleInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewRoleInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *roleInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredRoleInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *roleInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&rbac.Role{}, defaultRoleInformer) + return f.factory.InformerFor(&rbac.Role{}, f.defaultInformer) } func (f *roleInformer) Lister() internalversion.RoleLister { diff --git a/pkg/client/informers/informers_generated/internalversion/rbac/internalversion/rolebinding.go b/pkg/client/informers/informers_generated/internalversion/rbac/internalversion/rolebinding.go index 752fba074e3..7b7163a1082 100644 --- a/pkg/client/informers/informers_generated/internalversion/rbac/internalversion/rolebinding.go +++ b/pkg/client/informers/informers_generated/internalversion/rbac/internalversion/rolebinding.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,6 +19,8 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -27,7 +29,6 @@ import ( internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/rbac/internalversion" - time "time" ) // RoleBindingInformer provides access to a shared informer and lister for @@ -38,19 +39,34 @@ type RoleBindingInformer interface { } type roleBindingInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } // NewRoleBindingInformer 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 NewRoleBindingInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredRoleBindingInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Rbac().RoleBindings(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Rbac().RoleBindings(namespace).Watch(options) }, }, @@ -60,12 +76,12 @@ func NewRoleBindingInformer(client internalclientset.Interface, namespace string ) } -func defaultRoleBindingInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewRoleBindingInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *roleBindingInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredRoleBindingInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *roleBindingInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&rbac.RoleBinding{}, defaultRoleBindingInformer) + return f.factory.InformerFor(&rbac.RoleBinding{}, f.defaultInformer) } func (f *roleBindingInformer) Lister() internalversion.RoleBindingLister { diff --git a/pkg/client/informers/informers_generated/internalversion/scheduling/interface.go b/pkg/client/informers/informers_generated/internalversion/scheduling/interface.go index 3fb2db10b10..e9f36dc1e02 100644 --- a/pkg/client/informers/informers_generated/internalversion/scheduling/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/scheduling/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -30,15 +30,17 @@ type Interface interface { } type group struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &group{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // InternalVersion returns a new internalversion.Interface. func (g *group) InternalVersion() internalversion.Interface { - return internalversion.New(g.SharedInformerFactory) + return internalversion.New(g.factory, g.namespace, g.tweakListOptions) } diff --git a/pkg/client/informers/informers_generated/internalversion/scheduling/internalversion/interface.go b/pkg/client/informers/informers_generated/internalversion/scheduling/internalversion/interface.go index fe670796c4a..4e856b0b671 100644 --- a/pkg/client/informers/informers_generated/internalversion/scheduling/internalversion/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/scheduling/internalversion/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -29,15 +29,17 @@ type Interface interface { } type version struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &version{f} +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.SharedInformerFactory} + return &priorityClassInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } diff --git a/pkg/client/informers/informers_generated/internalversion/scheduling/internalversion/priorityclass.go b/pkg/client/informers/informers_generated/internalversion/scheduling/internalversion/priorityclass.go index 551ba23b2e2..40b1437ba51 100644 --- a/pkg/client/informers/informers_generated/internalversion/scheduling/internalversion/priorityclass.go +++ b/pkg/client/informers/informers_generated/internalversion/scheduling/internalversion/priorityclass.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,6 +19,8 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -27,7 +29,6 @@ import ( internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/scheduling/internalversion" - time "time" ) // PriorityClassInformer provides access to a shared informer and lister for @@ -38,19 +39,33 @@ type PriorityClassInformer interface { } type priorityClassInformer struct { - factory internalinterfaces.SharedInformerFactory + 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 internalclientset.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 internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Scheduling().PriorityClasses().List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Scheduling().PriorityClasses().Watch(options) }, }, @@ -60,12 +75,12 @@ func NewPriorityClassInformer(client internalclientset.Interface, resyncPeriod t ) } -func defaultPriorityClassInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewPriorityClassInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *priorityClassInformer) defaultInformer(client internalclientset.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(&scheduling.PriorityClass{}, defaultPriorityClassInformer) + return f.factory.InformerFor(&scheduling.PriorityClass{}, f.defaultInformer) } func (f *priorityClassInformer) Lister() internalversion.PriorityClassLister { diff --git a/pkg/client/informers/informers_generated/internalversion/settings/interface.go b/pkg/client/informers/informers_generated/internalversion/settings/interface.go index 2c4321439a2..7e3ce2132c9 100644 --- a/pkg/client/informers/informers_generated/internalversion/settings/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/settings/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -30,15 +30,17 @@ type Interface interface { } type group struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &group{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // InternalVersion returns a new internalversion.Interface. func (g *group) InternalVersion() internalversion.Interface { - return internalversion.New(g.SharedInformerFactory) + return internalversion.New(g.factory, g.namespace, g.tweakListOptions) } diff --git a/pkg/client/informers/informers_generated/internalversion/settings/internalversion/interface.go b/pkg/client/informers/informers_generated/internalversion/settings/internalversion/interface.go index d8f7c341867..b518c14f2f4 100644 --- a/pkg/client/informers/informers_generated/internalversion/settings/internalversion/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/settings/internalversion/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -29,15 +29,17 @@ type Interface interface { } type version struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &version{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // PodPresets returns a PodPresetInformer. func (v *version) PodPresets() PodPresetInformer { - return &podPresetInformer{factory: v.SharedInformerFactory} + return &podPresetInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } diff --git a/pkg/client/informers/informers_generated/internalversion/settings/internalversion/podpreset.go b/pkg/client/informers/informers_generated/internalversion/settings/internalversion/podpreset.go index df812b4219c..b4a0f89b9f2 100644 --- a/pkg/client/informers/informers_generated/internalversion/settings/internalversion/podpreset.go +++ b/pkg/client/informers/informers_generated/internalversion/settings/internalversion/podpreset.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,6 +19,8 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -27,7 +29,6 @@ import ( internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/settings/internalversion" - time "time" ) // PodPresetInformer provides access to a shared informer and lister for @@ -38,19 +39,34 @@ type PodPresetInformer interface { } type podPresetInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } // NewPodPresetInformer constructs a new informer for PodPreset 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 NewPodPresetInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredPodPresetInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredPodPresetInformer constructs a new informer for PodPreset 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 NewFilteredPodPresetInformer(client internalclientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Settings().PodPresets(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Settings().PodPresets(namespace).Watch(options) }, }, @@ -60,12 +76,12 @@ func NewPodPresetInformer(client internalclientset.Interface, namespace string, ) } -func defaultPodPresetInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewPodPresetInformer(client, v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *podPresetInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredPodPresetInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *podPresetInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&settings.PodPreset{}, defaultPodPresetInformer) + return f.factory.InformerFor(&settings.PodPreset{}, f.defaultInformer) } func (f *podPresetInformer) Lister() internalversion.PodPresetLister { diff --git a/pkg/client/informers/informers_generated/internalversion/storage/interface.go b/pkg/client/informers/informers_generated/internalversion/storage/interface.go index d4f366fed74..2bffda1951c 100644 --- a/pkg/client/informers/informers_generated/internalversion/storage/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/storage/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -30,15 +30,17 @@ type Interface interface { } type group struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &group{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // InternalVersion returns a new internalversion.Interface. func (g *group) InternalVersion() internalversion.Interface { - return internalversion.New(g.SharedInformerFactory) + return internalversion.New(g.factory, g.namespace, g.tweakListOptions) } diff --git a/pkg/client/informers/informers_generated/internalversion/storage/internalversion/BUILD b/pkg/client/informers/informers_generated/internalversion/storage/internalversion/BUILD index 98539bc2179..f4ffc7da14d 100644 --- a/pkg/client/informers/informers_generated/internalversion/storage/internalversion/BUILD +++ b/pkg/client/informers/informers_generated/internalversion/storage/internalversion/BUILD @@ -10,6 +10,7 @@ go_library( srcs = [ "interface.go", "storageclass.go", + "volumeattachment.go", ], importpath = "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/storage/internalversion", deps = [ diff --git a/pkg/client/informers/informers_generated/internalversion/storage/internalversion/interface.go b/pkg/client/informers/informers_generated/internalversion/storage/internalversion/interface.go index 86cf3676356..71960e41431 100644 --- a/pkg/client/informers/informers_generated/internalversion/storage/internalversion/interface.go +++ b/pkg/client/informers/informers_generated/internalversion/storage/internalversion/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -26,18 +26,27 @@ import ( type Interface interface { // StorageClasses returns a StorageClassInformer. StorageClasses() StorageClassInformer + // VolumeAttachments returns a VolumeAttachmentInformer. + VolumeAttachments() VolumeAttachmentInformer } type version struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &version{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // StorageClasses returns a StorageClassInformer. func (v *version) StorageClasses() StorageClassInformer { - return &storageClassInformer{factory: v.SharedInformerFactory} + return &storageClassInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} + +// VolumeAttachments returns a VolumeAttachmentInformer. +func (v *version) VolumeAttachments() VolumeAttachmentInformer { + return &volumeAttachmentInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } diff --git a/pkg/client/informers/informers_generated/internalversion/storage/internalversion/storageclass.go b/pkg/client/informers/informers_generated/internalversion/storage/internalversion/storageclass.go index eb7761a80a6..dbcb0c74f78 100644 --- a/pkg/client/informers/informers_generated/internalversion/storage/internalversion/storageclass.go +++ b/pkg/client/informers/informers_generated/internalversion/storage/internalversion/storageclass.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,6 +19,8 @@ limitations under the License. package internalversion import ( + time "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -27,7 +29,6 @@ import ( internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" internalversion "k8s.io/kubernetes/pkg/client/listers/storage/internalversion" - time "time" ) // StorageClassInformer provides access to a shared informer and lister for @@ -38,19 +39,33 @@ type StorageClassInformer interface { } type storageClassInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc } // NewStorageClassInformer 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 NewStorageClassInformer(client internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredStorageClassInformer(client, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Storage().StorageClasses().List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Storage().StorageClasses().Watch(options) }, }, @@ -60,12 +75,12 @@ func NewStorageClassInformer(client internalclientset.Interface, resyncPeriod ti ) } -func defaultStorageClassInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewStorageClassInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *storageClassInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredStorageClassInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *storageClassInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&storage.StorageClass{}, defaultStorageClassInformer) + return f.factory.InformerFor(&storage.StorageClass{}, f.defaultInformer) } func (f *storageClassInformer) Lister() internalversion.StorageClassLister { diff --git a/pkg/client/informers/informers_generated/internalversion/storage/internalversion/volumeattachment.go b/pkg/client/informers/informers_generated/internalversion/storage/internalversion/volumeattachment.go new file mode 100644 index 00000000000..fc8e809742f --- /dev/null +++ b/pkg/client/informers/informers_generated/internalversion/storage/internalversion/volumeattachment.go @@ -0,0 +1,88 @@ +/* +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. +*/ + +// This file was automatically generated by informer-gen + +package internalversion + +import ( + time "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" + storage "k8s.io/kubernetes/pkg/apis/storage" + internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/internalinterfaces" + internalversion "k8s.io/kubernetes/pkg/client/listers/storage/internalversion" +) + +// VolumeAttachmentInformer provides access to a shared informer and lister for +// VolumeAttachments. +type VolumeAttachmentInformer interface { + Informer() cache.SharedIndexInformer + Lister() internalversion.VolumeAttachmentLister +} + +type volumeAttachmentInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// NewVolumeAttachmentInformer 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 NewVolumeAttachmentInformer(client internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredVolumeAttachmentInformer(client, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.Storage().VolumeAttachments().List(options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.Storage().VolumeAttachments().Watch(options) + }, + }, + &storage.VolumeAttachment{}, + resyncPeriod, + indexers, + ) +} + +func (f *volumeAttachmentInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredVolumeAttachmentInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *volumeAttachmentInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&storage.VolumeAttachment{}, f.defaultInformer) +} + +func (f *volumeAttachmentInformer) Lister() internalversion.VolumeAttachmentLister { + return internalversion.NewVolumeAttachmentLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/listers/admissionregistration/internalversion/BUILD b/pkg/client/listers/admissionregistration/internalversion/BUILD index 16b67f74c03..1afdec477e3 100644 --- a/pkg/client/listers/admissionregistration/internalversion/BUILD +++ b/pkg/client/listers/admissionregistration/internalversion/BUILD @@ -9,14 +9,14 @@ go_library( name = "go_default_library", srcs = [ "expansion_generated.go", - "externaladmissionhookconfiguration.go", "initializerconfiguration.go", + "mutatingwebhookconfiguration.go", + "validatingwebhookconfiguration.go", ], importpath = "k8s.io/kubernetes/pkg/client/listers/admissionregistration/internalversion", deps = [ "//pkg/apis/admissionregistration:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", ], diff --git a/pkg/client/listers/admissionregistration/internalversion/expansion_generated.go b/pkg/client/listers/admissionregistration/internalversion/expansion_generated.go index 278e929fb4f..c8cb4b85ea4 100644 --- a/pkg/client/listers/admissionregistration/internalversion/expansion_generated.go +++ b/pkg/client/listers/admissionregistration/internalversion/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -18,10 +18,14 @@ limitations under the License. package internalversion -// ExternalAdmissionHookConfigurationListerExpansion allows custom methods to be added to -// ExternalAdmissionHookConfigurationLister. -type ExternalAdmissionHookConfigurationListerExpansion interface{} - // InitializerConfigurationListerExpansion allows custom methods to be added to // InitializerConfigurationLister. type InitializerConfigurationListerExpansion interface{} + +// MutatingWebhookConfigurationListerExpansion allows custom methods to be added to +// MutatingWebhookConfigurationLister. +type MutatingWebhookConfigurationListerExpansion interface{} + +// ValidatingWebhookConfigurationListerExpansion allows custom methods to be added to +// ValidatingWebhookConfigurationLister. +type ValidatingWebhookConfigurationListerExpansion interface{} diff --git a/pkg/client/listers/admissionregistration/internalversion/externaladmissionhookconfiguration.go b/pkg/client/listers/admissionregistration/internalversion/externaladmissionhookconfiguration.go deleted file mode 100644 index ac200d55291..00000000000 --- a/pkg/client/listers/admissionregistration/internalversion/externaladmissionhookconfiguration.go +++ /dev/null @@ -1,67 +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. -*/ - -// This file was automatically generated by lister-gen - -package internalversion - -import ( - "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/tools/cache" - admissionregistration "k8s.io/kubernetes/pkg/apis/admissionregistration" -) - -// ExternalAdmissionHookConfigurationLister helps list ExternalAdmissionHookConfigurations. -type ExternalAdmissionHookConfigurationLister interface { - // List lists all ExternalAdmissionHookConfigurations in the indexer. - List(selector labels.Selector) (ret []*admissionregistration.ExternalAdmissionHookConfiguration, err error) - // Get retrieves the ExternalAdmissionHookConfiguration from the index for a given name. - Get(name string) (*admissionregistration.ExternalAdmissionHookConfiguration, error) - ExternalAdmissionHookConfigurationListerExpansion -} - -// externalAdmissionHookConfigurationLister implements the ExternalAdmissionHookConfigurationLister interface. -type externalAdmissionHookConfigurationLister struct { - indexer cache.Indexer -} - -// NewExternalAdmissionHookConfigurationLister returns a new ExternalAdmissionHookConfigurationLister. -func NewExternalAdmissionHookConfigurationLister(indexer cache.Indexer) ExternalAdmissionHookConfigurationLister { - return &externalAdmissionHookConfigurationLister{indexer: indexer} -} - -// List lists all ExternalAdmissionHookConfigurations in the indexer. -func (s *externalAdmissionHookConfigurationLister) List(selector labels.Selector) (ret []*admissionregistration.ExternalAdmissionHookConfiguration, err error) { - err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*admissionregistration.ExternalAdmissionHookConfiguration)) - }) - return ret, err -} - -// Get retrieves the ExternalAdmissionHookConfiguration from the index for a given name. -func (s *externalAdmissionHookConfigurationLister) Get(name string) (*admissionregistration.ExternalAdmissionHookConfiguration, error) { - key := &admissionregistration.ExternalAdmissionHookConfiguration{ObjectMeta: v1.ObjectMeta{Name: name}} - obj, exists, err := s.indexer.Get(key) - if err != nil { - return nil, err - } - if !exists { - return nil, errors.NewNotFound(admissionregistration.Resource("externaladmissionhookconfiguration"), name) - } - return obj.(*admissionregistration.ExternalAdmissionHookConfiguration), nil -} diff --git a/pkg/client/listers/admissionregistration/internalversion/initializerconfiguration.go b/pkg/client/listers/admissionregistration/internalversion/initializerconfiguration.go index 8fe394f0eaa..34641e94dcb 100644 --- a/pkg/client/listers/admissionregistration/internalversion/initializerconfiguration.go +++ b/pkg/client/listers/admissionregistration/internalversion/initializerconfiguration.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -20,7 +20,6 @@ package internalversion import ( "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" admissionregistration "k8s.io/kubernetes/pkg/apis/admissionregistration" @@ -55,8 +54,7 @@ func (s *initializerConfigurationLister) List(selector labels.Selector) (ret []* // Get retrieves the InitializerConfiguration from the index for a given name. func (s *initializerConfigurationLister) Get(name string) (*admissionregistration.InitializerConfiguration, error) { - key := &admissionregistration.InitializerConfiguration{ObjectMeta: v1.ObjectMeta{Name: name}} - obj, exists, err := s.indexer.Get(key) + obj, exists, err := s.indexer.GetByKey(name) if err != nil { return nil, err } diff --git a/pkg/client/listers/admissionregistration/internalversion/mutatingwebhookconfiguration.go b/pkg/client/listers/admissionregistration/internalversion/mutatingwebhookconfiguration.go new file mode 100644 index 00000000000..0f69668f5bb --- /dev/null +++ b/pkg/client/listers/admissionregistration/internalversion/mutatingwebhookconfiguration.go @@ -0,0 +1,65 @@ +/* +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. +*/ + +// This file was automatically generated by lister-gen + +package internalversion + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + admissionregistration "k8s.io/kubernetes/pkg/apis/admissionregistration" +) + +// MutatingWebhookConfigurationLister helps list MutatingWebhookConfigurations. +type MutatingWebhookConfigurationLister interface { + // List lists all MutatingWebhookConfigurations in the indexer. + List(selector labels.Selector) (ret []*admissionregistration.MutatingWebhookConfiguration, err error) + // Get retrieves the MutatingWebhookConfiguration from the index for a given name. + Get(name string) (*admissionregistration.MutatingWebhookConfiguration, error) + MutatingWebhookConfigurationListerExpansion +} + +// mutatingWebhookConfigurationLister implements the MutatingWebhookConfigurationLister interface. +type mutatingWebhookConfigurationLister struct { + indexer cache.Indexer +} + +// NewMutatingWebhookConfigurationLister returns a new MutatingWebhookConfigurationLister. +func NewMutatingWebhookConfigurationLister(indexer cache.Indexer) MutatingWebhookConfigurationLister { + return &mutatingWebhookConfigurationLister{indexer: indexer} +} + +// List lists all MutatingWebhookConfigurations in the indexer. +func (s *mutatingWebhookConfigurationLister) List(selector labels.Selector) (ret []*admissionregistration.MutatingWebhookConfiguration, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*admissionregistration.MutatingWebhookConfiguration)) + }) + return ret, err +} + +// Get retrieves the MutatingWebhookConfiguration from the index for a given name. +func (s *mutatingWebhookConfigurationLister) Get(name string) (*admissionregistration.MutatingWebhookConfiguration, error) { + obj, exists, err := s.indexer.GetByKey(name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(admissionregistration.Resource("mutatingwebhookconfiguration"), name) + } + return obj.(*admissionregistration.MutatingWebhookConfiguration), nil +} diff --git a/pkg/client/listers/admissionregistration/internalversion/validatingwebhookconfiguration.go b/pkg/client/listers/admissionregistration/internalversion/validatingwebhookconfiguration.go new file mode 100644 index 00000000000..e17c0bec989 --- /dev/null +++ b/pkg/client/listers/admissionregistration/internalversion/validatingwebhookconfiguration.go @@ -0,0 +1,65 @@ +/* +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. +*/ + +// This file was automatically generated by lister-gen + +package internalversion + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + admissionregistration "k8s.io/kubernetes/pkg/apis/admissionregistration" +) + +// ValidatingWebhookConfigurationLister helps list ValidatingWebhookConfigurations. +type ValidatingWebhookConfigurationLister interface { + // List lists all ValidatingWebhookConfigurations in the indexer. + List(selector labels.Selector) (ret []*admissionregistration.ValidatingWebhookConfiguration, err error) + // Get retrieves the ValidatingWebhookConfiguration from the index for a given name. + Get(name string) (*admissionregistration.ValidatingWebhookConfiguration, error) + ValidatingWebhookConfigurationListerExpansion +} + +// validatingWebhookConfigurationLister implements the ValidatingWebhookConfigurationLister interface. +type validatingWebhookConfigurationLister struct { + indexer cache.Indexer +} + +// NewValidatingWebhookConfigurationLister returns a new ValidatingWebhookConfigurationLister. +func NewValidatingWebhookConfigurationLister(indexer cache.Indexer) ValidatingWebhookConfigurationLister { + return &validatingWebhookConfigurationLister{indexer: indexer} +} + +// List lists all ValidatingWebhookConfigurations in the indexer. +func (s *validatingWebhookConfigurationLister) List(selector labels.Selector) (ret []*admissionregistration.ValidatingWebhookConfiguration, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*admissionregistration.ValidatingWebhookConfiguration)) + }) + return ret, err +} + +// Get retrieves the ValidatingWebhookConfiguration from the index for a given name. +func (s *validatingWebhookConfigurationLister) Get(name string) (*admissionregistration.ValidatingWebhookConfiguration, error) { + obj, exists, err := s.indexer.GetByKey(name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(admissionregistration.Resource("validatingwebhookconfiguration"), name) + } + return obj.(*admissionregistration.ValidatingWebhookConfiguration), nil +} diff --git a/pkg/client/listers/apis/admissionregistration/BUILD b/pkg/client/listers/apis/admissionregistration/BUILD new file mode 100644 index 00000000000..abefdf365c8 --- /dev/null +++ b/pkg/client/listers/apis/admissionregistration/BUILD @@ -0,0 +1,31 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "expansion_generated.go", + "initializerconfiguration.go", + ], + importpath = "k8s.io/kubernetes/pkg/client/listers/apis/admissionregistration", + visibility = ["//visibility:public"], + deps = [ + "//pkg/apis/admissionregistration:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/client-go/tools/cache:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/client/listers/apis/admissionregistration/expansion_generated.go b/pkg/client/listers/apis/admissionregistration/expansion_generated.go new file mode 100644 index 00000000000..8142f2712b9 --- /dev/null +++ b/pkg/client/listers/apis/admissionregistration/expansion_generated.go @@ -0,0 +1,27 @@ +/* +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. +*/ + +// This file was automatically generated by lister-gen + +package admissionregistration + +// InitializerConfigurationListerExpansion allows custom methods to be added to +// InitializerConfigurationLister. +type InitializerConfigurationListerExpansion interface{} + +// ValidatingWebhookConfigurationListerExpansion allows custom methods to be added to +// ValidatingWebhookConfigurationLister. +type ValidatingWebhookConfigurationListerExpansion interface{} diff --git a/pkg/client/listers/apis/admissionregistration/initializerconfiguration.go b/pkg/client/listers/apis/admissionregistration/initializerconfiguration.go new file mode 100644 index 00000000000..a935f624efa --- /dev/null +++ b/pkg/client/listers/apis/admissionregistration/initializerconfiguration.go @@ -0,0 +1,65 @@ +/* +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. +*/ + +// This file was automatically generated by lister-gen + +package admissionregistration + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + admissionregistration "k8s.io/kubernetes/pkg/apis/admissionregistration" +) + +// InitializerConfigurationLister helps list InitializerConfigurations. +type InitializerConfigurationLister interface { + // List lists all InitializerConfigurations in the indexer. + List(selector labels.Selector) (ret []*admissionregistration.InitializerConfiguration, err error) + // Get retrieves the InitializerConfiguration from the index for a given name. + Get(name string) (*admissionregistration.InitializerConfiguration, error) + InitializerConfigurationListerExpansion +} + +// initializerConfigurationLister implements the InitializerConfigurationLister interface. +type initializerConfigurationLister struct { + indexer cache.Indexer +} + +// NewInitializerConfigurationLister returns a new InitializerConfigurationLister. +func NewInitializerConfigurationLister(indexer cache.Indexer) InitializerConfigurationLister { + return &initializerConfigurationLister{indexer: indexer} +} + +// List lists all InitializerConfigurations in the indexer. +func (s *initializerConfigurationLister) List(selector labels.Selector) (ret []*admissionregistration.InitializerConfiguration, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*admissionregistration.InitializerConfiguration)) + }) + return ret, err +} + +// Get retrieves the InitializerConfiguration from the index for a given name. +func (s *initializerConfigurationLister) Get(name string) (*admissionregistration.InitializerConfiguration, error) { + obj, exists, err := s.indexer.GetByKey(name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(admissionregistration.Resource("initializerconfiguration"), name) + } + return obj.(*admissionregistration.InitializerConfiguration), nil +} diff --git a/pkg/client/listers/apps/internalversion/BUILD b/pkg/client/listers/apps/internalversion/BUILD index bd5e13960c7..73334dd2e19 100644 --- a/pkg/client/listers/apps/internalversion/BUILD +++ b/pkg/client/listers/apps/internalversion/BUILD @@ -15,8 +15,8 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/client/listers/apps/internalversion", deps = [ - "//pkg/api:go_default_library", "//pkg/apis/apps:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", diff --git a/pkg/client/listers/apps/internalversion/controllerrevision.go b/pkg/client/listers/apps/internalversion/controllerrevision.go index 89efbf8c8cb..9d7be2425ae 100644 --- a/pkg/client/listers/apps/internalversion/controllerrevision.go +++ b/pkg/client/listers/apps/internalversion/controllerrevision.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/apps/internalversion/expansion_generated.go b/pkg/client/listers/apps/internalversion/expansion_generated.go index 9aa9ff54cfd..caae093af0d 100644 --- a/pkg/client/listers/apps/internalversion/expansion_generated.go +++ b/pkg/client/listers/apps/internalversion/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/apps/internalversion/statefulset.go b/pkg/client/listers/apps/internalversion/statefulset.go index 0548d14b0ed..b43156f59e3 100644 --- a/pkg/client/listers/apps/internalversion/statefulset.go +++ b/pkg/client/listers/apps/internalversion/statefulset.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/apps/internalversion/statefulset_expansion.go b/pkg/client/listers/apps/internalversion/statefulset_expansion.go index c98c700b43f..8798f7249bc 100644 --- a/pkg/client/listers/apps/internalversion/statefulset_expansion.go +++ b/pkg/client/listers/apps/internalversion/statefulset_expansion.go @@ -21,8 +21,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/apps" + api "k8s.io/kubernetes/pkg/apis/core" ) // StatefulSetListerExpansion allows custom methods to be added to diff --git a/pkg/client/listers/authentication/internalversion/BUILD b/pkg/client/listers/authentication/internalversion/BUILD index b5ea7c5e630..8a3d4bf56c5 100644 --- a/pkg/client/listers/authentication/internalversion/BUILD +++ b/pkg/client/listers/authentication/internalversion/BUILD @@ -15,7 +15,6 @@ go_library( deps = [ "//pkg/apis/authentication:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", ], diff --git a/pkg/client/listers/authentication/internalversion/expansion_generated.go b/pkg/client/listers/authentication/internalversion/expansion_generated.go index 18211cd78de..bf33d4c5df4 100644 --- a/pkg/client/listers/authentication/internalversion/expansion_generated.go +++ b/pkg/client/listers/authentication/internalversion/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/authentication/internalversion/tokenreview.go b/pkg/client/listers/authentication/internalversion/tokenreview.go index df92d2fb20e..312161ba729 100644 --- a/pkg/client/listers/authentication/internalversion/tokenreview.go +++ b/pkg/client/listers/authentication/internalversion/tokenreview.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -20,7 +20,6 @@ package internalversion import ( "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" authentication "k8s.io/kubernetes/pkg/apis/authentication" @@ -55,8 +54,7 @@ func (s *tokenReviewLister) List(selector labels.Selector) (ret []*authenticatio // Get retrieves the TokenReview from the index for a given name. func (s *tokenReviewLister) Get(name string) (*authentication.TokenReview, error) { - key := &authentication.TokenReview{ObjectMeta: v1.ObjectMeta{Name: name}} - obj, exists, err := s.indexer.Get(key) + obj, exists, err := s.indexer.GetByKey(name) if err != nil { return nil, err } diff --git a/pkg/client/listers/authorization/internalversion/BUILD b/pkg/client/listers/authorization/internalversion/BUILD index 765f66ffb06..25fc1d63da7 100644 --- a/pkg/client/listers/authorization/internalversion/BUILD +++ b/pkg/client/listers/authorization/internalversion/BUILD @@ -18,7 +18,6 @@ go_library( deps = [ "//pkg/apis/authorization:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", ], diff --git a/pkg/client/listers/authorization/internalversion/expansion_generated.go b/pkg/client/listers/authorization/internalversion/expansion_generated.go index 7715a480aec..985fe34a01e 100644 --- a/pkg/client/listers/authorization/internalversion/expansion_generated.go +++ b/pkg/client/listers/authorization/internalversion/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/authorization/internalversion/localsubjectaccessreview.go b/pkg/client/listers/authorization/internalversion/localsubjectaccessreview.go index 41e780e80e3..dbf71c0faa3 100644 --- a/pkg/client/listers/authorization/internalversion/localsubjectaccessreview.go +++ b/pkg/client/listers/authorization/internalversion/localsubjectaccessreview.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/authorization/internalversion/selfsubjectaccessreview.go b/pkg/client/listers/authorization/internalversion/selfsubjectaccessreview.go index c968fa6d5c5..d6fc6ec032d 100644 --- a/pkg/client/listers/authorization/internalversion/selfsubjectaccessreview.go +++ b/pkg/client/listers/authorization/internalversion/selfsubjectaccessreview.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -20,7 +20,6 @@ package internalversion import ( "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" authorization "k8s.io/kubernetes/pkg/apis/authorization" @@ -55,8 +54,7 @@ func (s *selfSubjectAccessReviewLister) List(selector labels.Selector) (ret []*a // Get retrieves the SelfSubjectAccessReview from the index for a given name. func (s *selfSubjectAccessReviewLister) Get(name string) (*authorization.SelfSubjectAccessReview, error) { - key := &authorization.SelfSubjectAccessReview{ObjectMeta: v1.ObjectMeta{Name: name}} - obj, exists, err := s.indexer.Get(key) + obj, exists, err := s.indexer.GetByKey(name) if err != nil { return nil, err } diff --git a/pkg/client/listers/authorization/internalversion/selfsubjectrulesreview.go b/pkg/client/listers/authorization/internalversion/selfsubjectrulesreview.go index 999956a5a6c..e34695af6fa 100644 --- a/pkg/client/listers/authorization/internalversion/selfsubjectrulesreview.go +++ b/pkg/client/listers/authorization/internalversion/selfsubjectrulesreview.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -20,7 +20,6 @@ package internalversion import ( "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" authorization "k8s.io/kubernetes/pkg/apis/authorization" @@ -55,8 +54,7 @@ func (s *selfSubjectRulesReviewLister) List(selector labels.Selector) (ret []*au // Get retrieves the SelfSubjectRulesReview from the index for a given name. func (s *selfSubjectRulesReviewLister) Get(name string) (*authorization.SelfSubjectRulesReview, error) { - key := &authorization.SelfSubjectRulesReview{ObjectMeta: v1.ObjectMeta{Name: name}} - obj, exists, err := s.indexer.Get(key) + obj, exists, err := s.indexer.GetByKey(name) if err != nil { return nil, err } diff --git a/pkg/client/listers/authorization/internalversion/subjectaccessreview.go b/pkg/client/listers/authorization/internalversion/subjectaccessreview.go index fbb6f404458..f8ef9460cee 100644 --- a/pkg/client/listers/authorization/internalversion/subjectaccessreview.go +++ b/pkg/client/listers/authorization/internalversion/subjectaccessreview.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -20,7 +20,6 @@ package internalversion import ( "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" authorization "k8s.io/kubernetes/pkg/apis/authorization" @@ -55,8 +54,7 @@ func (s *subjectAccessReviewLister) List(selector labels.Selector) (ret []*autho // Get retrieves the SubjectAccessReview from the index for a given name. func (s *subjectAccessReviewLister) Get(name string) (*authorization.SubjectAccessReview, error) { - key := &authorization.SubjectAccessReview{ObjectMeta: v1.ObjectMeta{Name: name}} - obj, exists, err := s.indexer.Get(key) + obj, exists, err := s.indexer.GetByKey(name) if err != nil { return nil, err } diff --git a/pkg/client/listers/autoscaling/internalversion/expansion_generated.go b/pkg/client/listers/autoscaling/internalversion/expansion_generated.go index 5f8b6b6d5ba..c1756faa9eb 100644 --- a/pkg/client/listers/autoscaling/internalversion/expansion_generated.go +++ b/pkg/client/listers/autoscaling/internalversion/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/autoscaling/internalversion/horizontalpodautoscaler.go b/pkg/client/listers/autoscaling/internalversion/horizontalpodautoscaler.go index 280424c881b..69269d12fc4 100644 --- a/pkg/client/listers/autoscaling/internalversion/horizontalpodautoscaler.go +++ b/pkg/client/listers/autoscaling/internalversion/horizontalpodautoscaler.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/batch/internalversion/BUILD b/pkg/client/listers/batch/internalversion/BUILD index 1d09a951231..c0f5a4f8b27 100644 --- a/pkg/client/listers/batch/internalversion/BUILD +++ b/pkg/client/listers/batch/internalversion/BUILD @@ -16,8 +16,8 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/client/listers/batch/internalversion", deps = [ - "//pkg/api:go_default_library", "//pkg/apis/batch:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", @@ -28,11 +28,11 @@ go_library( go_test( name = "go_default_test", srcs = ["job_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/client/listers/batch/internalversion", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", "//pkg/apis/batch:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", diff --git a/pkg/client/listers/batch/internalversion/cronjob.go b/pkg/client/listers/batch/internalversion/cronjob.go index 8e5dcb8982b..3df69bf9932 100644 --- a/pkg/client/listers/batch/internalversion/cronjob.go +++ b/pkg/client/listers/batch/internalversion/cronjob.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/batch/internalversion/expansion_generated.go b/pkg/client/listers/batch/internalversion/expansion_generated.go index 5499876f60b..5334bff9279 100644 --- a/pkg/client/listers/batch/internalversion/expansion_generated.go +++ b/pkg/client/listers/batch/internalversion/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/batch/internalversion/job.go b/pkg/client/listers/batch/internalversion/job.go index e02319336bb..139f794f23b 100644 --- a/pkg/client/listers/batch/internalversion/job.go +++ b/pkg/client/listers/batch/internalversion/job.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/batch/internalversion/job_expansion.go b/pkg/client/listers/batch/internalversion/job_expansion.go index 1687f88198f..083453d90f1 100644 --- a/pkg/client/listers/batch/internalversion/job_expansion.go +++ b/pkg/client/listers/batch/internalversion/job_expansion.go @@ -21,8 +21,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/batch" + api "k8s.io/kubernetes/pkg/apis/core" ) // JobListerExpansion allows custom methods to be added to diff --git a/pkg/client/listers/batch/internalversion/job_test.go b/pkg/client/listers/batch/internalversion/job_test.go index c4785934524..b4c60de3e9e 100644 --- a/pkg/client/listers/batch/internalversion/job_test.go +++ b/pkg/client/listers/batch/internalversion/job_test.go @@ -23,8 +23,8 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/tools/cache" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/batch" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestJobLister(t *testing.T) { diff --git a/pkg/client/listers/certificates/internalversion/BUILD b/pkg/client/listers/certificates/internalversion/BUILD index f158bd430a3..afc655b9eee 100644 --- a/pkg/client/listers/certificates/internalversion/BUILD +++ b/pkg/client/listers/certificates/internalversion/BUILD @@ -15,7 +15,6 @@ go_library( deps = [ "//pkg/apis/certificates:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", ], diff --git a/pkg/client/listers/certificates/internalversion/certificatesigningrequest.go b/pkg/client/listers/certificates/internalversion/certificatesigningrequest.go index 3ba28efa5d6..6b3d96bb6b0 100644 --- a/pkg/client/listers/certificates/internalversion/certificatesigningrequest.go +++ b/pkg/client/listers/certificates/internalversion/certificatesigningrequest.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -20,7 +20,6 @@ package internalversion import ( "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" certificates "k8s.io/kubernetes/pkg/apis/certificates" @@ -55,8 +54,7 @@ func (s *certificateSigningRequestLister) List(selector labels.Selector) (ret [] // Get retrieves the CertificateSigningRequest from the index for a given name. func (s *certificateSigningRequestLister) Get(name string) (*certificates.CertificateSigningRequest, error) { - key := &certificates.CertificateSigningRequest{ObjectMeta: v1.ObjectMeta{Name: name}} - obj, exists, err := s.indexer.Get(key) + obj, exists, err := s.indexer.GetByKey(name) if err != nil { return nil, err } diff --git a/pkg/client/listers/certificates/internalversion/expansion_generated.go b/pkg/client/listers/certificates/internalversion/expansion_generated.go index c13b24a7846..9d8c9b52e85 100644 --- a/pkg/client/listers/certificates/internalversion/expansion_generated.go +++ b/pkg/client/listers/certificates/internalversion/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/core/internalversion/BUILD b/pkg/client/listers/core/internalversion/BUILD index 9f54d487886..8daff542f4c 100644 --- a/pkg/client/listers/core/internalversion/BUILD +++ b/pkg/client/listers/core/internalversion/BUILD @@ -31,9 +31,8 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/client/listers/core/internalversion", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", ], diff --git a/pkg/client/listers/core/internalversion/componentstatus.go b/pkg/client/listers/core/internalversion/componentstatus.go index 8f0a0a71f09..34672845411 100644 --- a/pkg/client/listers/core/internalversion/componentstatus.go +++ b/pkg/client/listers/core/internalversion/componentstatus.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -20,18 +20,17 @@ package internalversion import ( "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // ComponentStatusLister helps list ComponentStatuses. type ComponentStatusLister interface { // List lists all ComponentStatuses in the indexer. - List(selector labels.Selector) (ret []*api.ComponentStatus, err error) + List(selector labels.Selector) (ret []*core.ComponentStatus, err error) // Get retrieves the ComponentStatus from the index for a given name. - Get(name string) (*api.ComponentStatus, error) + Get(name string) (*core.ComponentStatus, error) ComponentStatusListerExpansion } @@ -46,22 +45,21 @@ func NewComponentStatusLister(indexer cache.Indexer) ComponentStatusLister { } // List lists all ComponentStatuses in the indexer. -func (s *componentStatusLister) List(selector labels.Selector) (ret []*api.ComponentStatus, err error) { +func (s *componentStatusLister) List(selector labels.Selector) (ret []*core.ComponentStatus, err error) { err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*api.ComponentStatus)) + ret = append(ret, m.(*core.ComponentStatus)) }) return ret, err } // Get retrieves the ComponentStatus from the index for a given name. -func (s *componentStatusLister) Get(name string) (*api.ComponentStatus, error) { - key := &api.ComponentStatus{ObjectMeta: v1.ObjectMeta{Name: name}} - obj, exists, err := s.indexer.Get(key) +func (s *componentStatusLister) Get(name string) (*core.ComponentStatus, error) { + obj, exists, err := s.indexer.GetByKey(name) if err != nil { return nil, err } if !exists { - return nil, errors.NewNotFound(api.Resource("componentstatus"), name) + return nil, errors.NewNotFound(core.Resource("componentstatus"), name) } - return obj.(*api.ComponentStatus), nil + return obj.(*core.ComponentStatus), nil } diff --git a/pkg/client/listers/core/internalversion/configmap.go b/pkg/client/listers/core/internalversion/configmap.go index 373447625c1..e3cb54f6896 100644 --- a/pkg/client/listers/core/internalversion/configmap.go +++ b/pkg/client/listers/core/internalversion/configmap.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,13 +22,13 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // ConfigMapLister helps list ConfigMaps. type ConfigMapLister interface { // List lists all ConfigMaps in the indexer. - List(selector labels.Selector) (ret []*api.ConfigMap, err error) + List(selector labels.Selector) (ret []*core.ConfigMap, err error) // ConfigMaps returns an object that can list and get ConfigMaps. ConfigMaps(namespace string) ConfigMapNamespaceLister ConfigMapListerExpansion @@ -45,9 +45,9 @@ func NewConfigMapLister(indexer cache.Indexer) ConfigMapLister { } // List lists all ConfigMaps in the indexer. -func (s *configMapLister) List(selector labels.Selector) (ret []*api.ConfigMap, err error) { +func (s *configMapLister) List(selector labels.Selector) (ret []*core.ConfigMap, err error) { err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*api.ConfigMap)) + ret = append(ret, m.(*core.ConfigMap)) }) return ret, err } @@ -60,9 +60,9 @@ func (s *configMapLister) ConfigMaps(namespace string) ConfigMapNamespaceLister // ConfigMapNamespaceLister helps list and get ConfigMaps. type ConfigMapNamespaceLister interface { // List lists all ConfigMaps in the indexer for a given namespace. - List(selector labels.Selector) (ret []*api.ConfigMap, err error) + List(selector labels.Selector) (ret []*core.ConfigMap, err error) // Get retrieves the ConfigMap from the indexer for a given namespace and name. - Get(name string) (*api.ConfigMap, error) + Get(name string) (*core.ConfigMap, error) ConfigMapNamespaceListerExpansion } @@ -74,21 +74,21 @@ type configMapNamespaceLister struct { } // List lists all ConfigMaps in the indexer for a given namespace. -func (s configMapNamespaceLister) List(selector labels.Selector) (ret []*api.ConfigMap, err error) { +func (s configMapNamespaceLister) List(selector labels.Selector) (ret []*core.ConfigMap, err error) { err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*api.ConfigMap)) + ret = append(ret, m.(*core.ConfigMap)) }) return ret, err } // Get retrieves the ConfigMap from the indexer for a given namespace and name. -func (s configMapNamespaceLister) Get(name string) (*api.ConfigMap, error) { +func (s configMapNamespaceLister) Get(name string) (*core.ConfigMap, error) { obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) if err != nil { return nil, err } if !exists { - return nil, errors.NewNotFound(api.Resource("configmap"), name) + return nil, errors.NewNotFound(core.Resource("configmap"), name) } - return obj.(*api.ConfigMap), nil + return obj.(*core.ConfigMap), nil } diff --git a/pkg/client/listers/core/internalversion/endpoints.go b/pkg/client/listers/core/internalversion/endpoints.go index efea139faa5..181f2ef52de 100644 --- a/pkg/client/listers/core/internalversion/endpoints.go +++ b/pkg/client/listers/core/internalversion/endpoints.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,13 +22,13 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // EndpointsLister helps list Endpoints. type EndpointsLister interface { // List lists all Endpoints in the indexer. - List(selector labels.Selector) (ret []*api.Endpoints, err error) + List(selector labels.Selector) (ret []*core.Endpoints, err error) // Endpoints returns an object that can list and get Endpoints. Endpoints(namespace string) EndpointsNamespaceLister EndpointsListerExpansion @@ -45,9 +45,9 @@ func NewEndpointsLister(indexer cache.Indexer) EndpointsLister { } // List lists all Endpoints in the indexer. -func (s *endpointsLister) List(selector labels.Selector) (ret []*api.Endpoints, err error) { +func (s *endpointsLister) List(selector labels.Selector) (ret []*core.Endpoints, err error) { err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*api.Endpoints)) + ret = append(ret, m.(*core.Endpoints)) }) return ret, err } @@ -60,9 +60,9 @@ func (s *endpointsLister) Endpoints(namespace string) EndpointsNamespaceLister { // EndpointsNamespaceLister helps list and get Endpoints. type EndpointsNamespaceLister interface { // List lists all Endpoints in the indexer for a given namespace. - List(selector labels.Selector) (ret []*api.Endpoints, err error) + List(selector labels.Selector) (ret []*core.Endpoints, err error) // Get retrieves the Endpoints from the indexer for a given namespace and name. - Get(name string) (*api.Endpoints, error) + Get(name string) (*core.Endpoints, error) EndpointsNamespaceListerExpansion } @@ -74,21 +74,21 @@ type endpointsNamespaceLister struct { } // List lists all Endpoints in the indexer for a given namespace. -func (s endpointsNamespaceLister) List(selector labels.Selector) (ret []*api.Endpoints, err error) { +func (s endpointsNamespaceLister) List(selector labels.Selector) (ret []*core.Endpoints, err error) { err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*api.Endpoints)) + ret = append(ret, m.(*core.Endpoints)) }) return ret, err } // Get retrieves the Endpoints from the indexer for a given namespace and name. -func (s endpointsNamespaceLister) Get(name string) (*api.Endpoints, error) { +func (s endpointsNamespaceLister) Get(name string) (*core.Endpoints, error) { obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) if err != nil { return nil, err } if !exists { - return nil, errors.NewNotFound(api.Resource("endpoints"), name) + return nil, errors.NewNotFound(core.Resource("endpoints"), name) } - return obj.(*api.Endpoints), nil + return obj.(*core.Endpoints), nil } diff --git a/pkg/client/listers/core/internalversion/event.go b/pkg/client/listers/core/internalversion/event.go index e66ec202a29..0f029d19744 100644 --- a/pkg/client/listers/core/internalversion/event.go +++ b/pkg/client/listers/core/internalversion/event.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,13 +22,13 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // EventLister helps list Events. type EventLister interface { // List lists all Events in the indexer. - List(selector labels.Selector) (ret []*api.Event, err error) + List(selector labels.Selector) (ret []*core.Event, err error) // Events returns an object that can list and get Events. Events(namespace string) EventNamespaceLister EventListerExpansion @@ -45,9 +45,9 @@ func NewEventLister(indexer cache.Indexer) EventLister { } // List lists all Events in the indexer. -func (s *eventLister) List(selector labels.Selector) (ret []*api.Event, err error) { +func (s *eventLister) List(selector labels.Selector) (ret []*core.Event, err error) { err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*api.Event)) + ret = append(ret, m.(*core.Event)) }) return ret, err } @@ -60,9 +60,9 @@ func (s *eventLister) Events(namespace string) EventNamespaceLister { // EventNamespaceLister helps list and get Events. type EventNamespaceLister interface { // List lists all Events in the indexer for a given namespace. - List(selector labels.Selector) (ret []*api.Event, err error) + List(selector labels.Selector) (ret []*core.Event, err error) // Get retrieves the Event from the indexer for a given namespace and name. - Get(name string) (*api.Event, error) + Get(name string) (*core.Event, error) EventNamespaceListerExpansion } @@ -74,21 +74,21 @@ type eventNamespaceLister struct { } // List lists all Events in the indexer for a given namespace. -func (s eventNamespaceLister) List(selector labels.Selector) (ret []*api.Event, err error) { +func (s eventNamespaceLister) List(selector labels.Selector) (ret []*core.Event, err error) { err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*api.Event)) + ret = append(ret, m.(*core.Event)) }) return ret, err } // Get retrieves the Event from the indexer for a given namespace and name. -func (s eventNamespaceLister) Get(name string) (*api.Event, error) { +func (s eventNamespaceLister) Get(name string) (*core.Event, error) { obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) if err != nil { return nil, err } if !exists { - return nil, errors.NewNotFound(api.Resource("event"), name) + return nil, errors.NewNotFound(core.Resource("event"), name) } - return obj.(*api.Event), nil + return obj.(*core.Event), nil } diff --git a/pkg/client/listers/core/internalversion/expansion_generated.go b/pkg/client/listers/core/internalversion/expansion_generated.go index d184d4aa34f..506f5a84ab7 100644 --- a/pkg/client/listers/core/internalversion/expansion_generated.go +++ b/pkg/client/listers/core/internalversion/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/core/internalversion/limitrange.go b/pkg/client/listers/core/internalversion/limitrange.go index 1fa3bea66b8..caf17946c6a 100644 --- a/pkg/client/listers/core/internalversion/limitrange.go +++ b/pkg/client/listers/core/internalversion/limitrange.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,13 +22,13 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // LimitRangeLister helps list LimitRanges. type LimitRangeLister interface { // List lists all LimitRanges in the indexer. - List(selector labels.Selector) (ret []*api.LimitRange, err error) + List(selector labels.Selector) (ret []*core.LimitRange, err error) // LimitRanges returns an object that can list and get LimitRanges. LimitRanges(namespace string) LimitRangeNamespaceLister LimitRangeListerExpansion @@ -45,9 +45,9 @@ func NewLimitRangeLister(indexer cache.Indexer) LimitRangeLister { } // List lists all LimitRanges in the indexer. -func (s *limitRangeLister) List(selector labels.Selector) (ret []*api.LimitRange, err error) { +func (s *limitRangeLister) List(selector labels.Selector) (ret []*core.LimitRange, err error) { err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*api.LimitRange)) + ret = append(ret, m.(*core.LimitRange)) }) return ret, err } @@ -60,9 +60,9 @@ func (s *limitRangeLister) LimitRanges(namespace string) LimitRangeNamespaceList // LimitRangeNamespaceLister helps list and get LimitRanges. type LimitRangeNamespaceLister interface { // List lists all LimitRanges in the indexer for a given namespace. - List(selector labels.Selector) (ret []*api.LimitRange, err error) + List(selector labels.Selector) (ret []*core.LimitRange, err error) // Get retrieves the LimitRange from the indexer for a given namespace and name. - Get(name string) (*api.LimitRange, error) + Get(name string) (*core.LimitRange, error) LimitRangeNamespaceListerExpansion } @@ -74,21 +74,21 @@ type limitRangeNamespaceLister struct { } // List lists all LimitRanges in the indexer for a given namespace. -func (s limitRangeNamespaceLister) List(selector labels.Selector) (ret []*api.LimitRange, err error) { +func (s limitRangeNamespaceLister) List(selector labels.Selector) (ret []*core.LimitRange, err error) { err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*api.LimitRange)) + ret = append(ret, m.(*core.LimitRange)) }) return ret, err } // Get retrieves the LimitRange from the indexer for a given namespace and name. -func (s limitRangeNamespaceLister) Get(name string) (*api.LimitRange, error) { +func (s limitRangeNamespaceLister) Get(name string) (*core.LimitRange, error) { obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) if err != nil { return nil, err } if !exists { - return nil, errors.NewNotFound(api.Resource("limitrange"), name) + return nil, errors.NewNotFound(core.Resource("limitrange"), name) } - return obj.(*api.LimitRange), nil + return obj.(*core.LimitRange), nil } diff --git a/pkg/client/listers/core/internalversion/namespace.go b/pkg/client/listers/core/internalversion/namespace.go index c925f652373..db1b724bf5c 100644 --- a/pkg/client/listers/core/internalversion/namespace.go +++ b/pkg/client/listers/core/internalversion/namespace.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -20,18 +20,17 @@ package internalversion import ( "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // NamespaceLister helps list Namespaces. type NamespaceLister interface { // List lists all Namespaces in the indexer. - List(selector labels.Selector) (ret []*api.Namespace, err error) + List(selector labels.Selector) (ret []*core.Namespace, err error) // Get retrieves the Namespace from the index for a given name. - Get(name string) (*api.Namespace, error) + Get(name string) (*core.Namespace, error) NamespaceListerExpansion } @@ -46,22 +45,21 @@ func NewNamespaceLister(indexer cache.Indexer) NamespaceLister { } // List lists all Namespaces in the indexer. -func (s *namespaceLister) List(selector labels.Selector) (ret []*api.Namespace, err error) { +func (s *namespaceLister) List(selector labels.Selector) (ret []*core.Namespace, err error) { err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*api.Namespace)) + ret = append(ret, m.(*core.Namespace)) }) return ret, err } // Get retrieves the Namespace from the index for a given name. -func (s *namespaceLister) Get(name string) (*api.Namespace, error) { - key := &api.Namespace{ObjectMeta: v1.ObjectMeta{Name: name}} - obj, exists, err := s.indexer.Get(key) +func (s *namespaceLister) Get(name string) (*core.Namespace, error) { + obj, exists, err := s.indexer.GetByKey(name) if err != nil { return nil, err } if !exists { - return nil, errors.NewNotFound(api.Resource("namespace"), name) + return nil, errors.NewNotFound(core.Resource("namespace"), name) } - return obj.(*api.Namespace), nil + return obj.(*core.Namespace), nil } diff --git a/pkg/client/listers/core/internalversion/node.go b/pkg/client/listers/core/internalversion/node.go index ba33d64896d..3e870fa437b 100644 --- a/pkg/client/listers/core/internalversion/node.go +++ b/pkg/client/listers/core/internalversion/node.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -20,18 +20,17 @@ package internalversion import ( "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // NodeLister helps list Nodes. type NodeLister interface { // List lists all Nodes in the indexer. - List(selector labels.Selector) (ret []*api.Node, err error) + List(selector labels.Selector) (ret []*core.Node, err error) // Get retrieves the Node from the index for a given name. - Get(name string) (*api.Node, error) + Get(name string) (*core.Node, error) NodeListerExpansion } @@ -46,22 +45,21 @@ func NewNodeLister(indexer cache.Indexer) NodeLister { } // List lists all Nodes in the indexer. -func (s *nodeLister) List(selector labels.Selector) (ret []*api.Node, err error) { +func (s *nodeLister) List(selector labels.Selector) (ret []*core.Node, err error) { err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*api.Node)) + ret = append(ret, m.(*core.Node)) }) return ret, err } // Get retrieves the Node from the index for a given name. -func (s *nodeLister) Get(name string) (*api.Node, error) { - key := &api.Node{ObjectMeta: v1.ObjectMeta{Name: name}} - obj, exists, err := s.indexer.Get(key) +func (s *nodeLister) Get(name string) (*core.Node, error) { + obj, exists, err := s.indexer.GetByKey(name) if err != nil { return nil, err } if !exists { - return nil, errors.NewNotFound(api.Resource("node"), name) + return nil, errors.NewNotFound(core.Resource("node"), name) } - return obj.(*api.Node), nil + return obj.(*core.Node), nil } diff --git a/pkg/client/listers/core/internalversion/node_expansion.go b/pkg/client/listers/core/internalversion/node_expansion.go index 465c59e3ba2..3abca2d58ed 100644 --- a/pkg/client/listers/core/internalversion/node_expansion.go +++ b/pkg/client/listers/core/internalversion/node_expansion.go @@ -18,7 +18,7 @@ package internalversion import ( "k8s.io/apimachinery/pkg/labels" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // NodeConditionPredicate is a function that indicates whether the given node's conditions meet diff --git a/pkg/client/listers/core/internalversion/persistentvolume.go b/pkg/client/listers/core/internalversion/persistentvolume.go index 040058df20f..a4acc918165 100644 --- a/pkg/client/listers/core/internalversion/persistentvolume.go +++ b/pkg/client/listers/core/internalversion/persistentvolume.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -20,18 +20,17 @@ package internalversion import ( "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // PersistentVolumeLister helps list PersistentVolumes. type PersistentVolumeLister interface { // List lists all PersistentVolumes in the indexer. - List(selector labels.Selector) (ret []*api.PersistentVolume, err error) + List(selector labels.Selector) (ret []*core.PersistentVolume, err error) // Get retrieves the PersistentVolume from the index for a given name. - Get(name string) (*api.PersistentVolume, error) + Get(name string) (*core.PersistentVolume, error) PersistentVolumeListerExpansion } @@ -46,22 +45,21 @@ func NewPersistentVolumeLister(indexer cache.Indexer) PersistentVolumeLister { } // List lists all PersistentVolumes in the indexer. -func (s *persistentVolumeLister) List(selector labels.Selector) (ret []*api.PersistentVolume, err error) { +func (s *persistentVolumeLister) List(selector labels.Selector) (ret []*core.PersistentVolume, err error) { err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*api.PersistentVolume)) + ret = append(ret, m.(*core.PersistentVolume)) }) return ret, err } // Get retrieves the PersistentVolume from the index for a given name. -func (s *persistentVolumeLister) Get(name string) (*api.PersistentVolume, error) { - key := &api.PersistentVolume{ObjectMeta: v1.ObjectMeta{Name: name}} - obj, exists, err := s.indexer.Get(key) +func (s *persistentVolumeLister) Get(name string) (*core.PersistentVolume, error) { + obj, exists, err := s.indexer.GetByKey(name) if err != nil { return nil, err } if !exists { - return nil, errors.NewNotFound(api.Resource("persistentvolume"), name) + return nil, errors.NewNotFound(core.Resource("persistentvolume"), name) } - return obj.(*api.PersistentVolume), nil + return obj.(*core.PersistentVolume), nil } diff --git a/pkg/client/listers/core/internalversion/persistentvolumeclaim.go b/pkg/client/listers/core/internalversion/persistentvolumeclaim.go index f2e482269e3..0e844e2a6e7 100644 --- a/pkg/client/listers/core/internalversion/persistentvolumeclaim.go +++ b/pkg/client/listers/core/internalversion/persistentvolumeclaim.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,13 +22,13 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // PersistentVolumeClaimLister helps list PersistentVolumeClaims. type PersistentVolumeClaimLister interface { // List lists all PersistentVolumeClaims in the indexer. - List(selector labels.Selector) (ret []*api.PersistentVolumeClaim, err error) + List(selector labels.Selector) (ret []*core.PersistentVolumeClaim, err error) // PersistentVolumeClaims returns an object that can list and get PersistentVolumeClaims. PersistentVolumeClaims(namespace string) PersistentVolumeClaimNamespaceLister PersistentVolumeClaimListerExpansion @@ -45,9 +45,9 @@ func NewPersistentVolumeClaimLister(indexer cache.Indexer) PersistentVolumeClaim } // List lists all PersistentVolumeClaims in the indexer. -func (s *persistentVolumeClaimLister) List(selector labels.Selector) (ret []*api.PersistentVolumeClaim, err error) { +func (s *persistentVolumeClaimLister) List(selector labels.Selector) (ret []*core.PersistentVolumeClaim, err error) { err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*api.PersistentVolumeClaim)) + ret = append(ret, m.(*core.PersistentVolumeClaim)) }) return ret, err } @@ -60,9 +60,9 @@ func (s *persistentVolumeClaimLister) PersistentVolumeClaims(namespace string) P // PersistentVolumeClaimNamespaceLister helps list and get PersistentVolumeClaims. type PersistentVolumeClaimNamespaceLister interface { // List lists all PersistentVolumeClaims in the indexer for a given namespace. - List(selector labels.Selector) (ret []*api.PersistentVolumeClaim, err error) + List(selector labels.Selector) (ret []*core.PersistentVolumeClaim, err error) // Get retrieves the PersistentVolumeClaim from the indexer for a given namespace and name. - Get(name string) (*api.PersistentVolumeClaim, error) + Get(name string) (*core.PersistentVolumeClaim, error) PersistentVolumeClaimNamespaceListerExpansion } @@ -74,21 +74,21 @@ type persistentVolumeClaimNamespaceLister struct { } // List lists all PersistentVolumeClaims in the indexer for a given namespace. -func (s persistentVolumeClaimNamespaceLister) List(selector labels.Selector) (ret []*api.PersistentVolumeClaim, err error) { +func (s persistentVolumeClaimNamespaceLister) List(selector labels.Selector) (ret []*core.PersistentVolumeClaim, err error) { err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*api.PersistentVolumeClaim)) + ret = append(ret, m.(*core.PersistentVolumeClaim)) }) return ret, err } // Get retrieves the PersistentVolumeClaim from the indexer for a given namespace and name. -func (s persistentVolumeClaimNamespaceLister) Get(name string) (*api.PersistentVolumeClaim, error) { +func (s persistentVolumeClaimNamespaceLister) Get(name string) (*core.PersistentVolumeClaim, error) { obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) if err != nil { return nil, err } if !exists { - return nil, errors.NewNotFound(api.Resource("persistentvolumeclaim"), name) + return nil, errors.NewNotFound(core.Resource("persistentvolumeclaim"), name) } - return obj.(*api.PersistentVolumeClaim), nil + return obj.(*core.PersistentVolumeClaim), nil } diff --git a/pkg/client/listers/core/internalversion/pod.go b/pkg/client/listers/core/internalversion/pod.go index 2785db1c55d..50c423cc761 100644 --- a/pkg/client/listers/core/internalversion/pod.go +++ b/pkg/client/listers/core/internalversion/pod.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,13 +22,13 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // PodLister helps list Pods. type PodLister interface { // List lists all Pods in the indexer. - List(selector labels.Selector) (ret []*api.Pod, err error) + List(selector labels.Selector) (ret []*core.Pod, err error) // Pods returns an object that can list and get Pods. Pods(namespace string) PodNamespaceLister PodListerExpansion @@ -45,9 +45,9 @@ func NewPodLister(indexer cache.Indexer) PodLister { } // List lists all Pods in the indexer. -func (s *podLister) List(selector labels.Selector) (ret []*api.Pod, err error) { +func (s *podLister) List(selector labels.Selector) (ret []*core.Pod, err error) { err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*api.Pod)) + ret = append(ret, m.(*core.Pod)) }) return ret, err } @@ -60,9 +60,9 @@ func (s *podLister) Pods(namespace string) PodNamespaceLister { // PodNamespaceLister helps list and get Pods. type PodNamespaceLister interface { // List lists all Pods in the indexer for a given namespace. - List(selector labels.Selector) (ret []*api.Pod, err error) + List(selector labels.Selector) (ret []*core.Pod, err error) // Get retrieves the Pod from the indexer for a given namespace and name. - Get(name string) (*api.Pod, error) + Get(name string) (*core.Pod, error) PodNamespaceListerExpansion } @@ -74,21 +74,21 @@ type podNamespaceLister struct { } // List lists all Pods in the indexer for a given namespace. -func (s podNamespaceLister) List(selector labels.Selector) (ret []*api.Pod, err error) { +func (s podNamespaceLister) List(selector labels.Selector) (ret []*core.Pod, err error) { err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*api.Pod)) + ret = append(ret, m.(*core.Pod)) }) return ret, err } // Get retrieves the Pod from the indexer for a given namespace and name. -func (s podNamespaceLister) Get(name string) (*api.Pod, error) { +func (s podNamespaceLister) Get(name string) (*core.Pod, error) { obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) if err != nil { return nil, err } if !exists { - return nil, errors.NewNotFound(api.Resource("pod"), name) + return nil, errors.NewNotFound(core.Resource("pod"), name) } - return obj.(*api.Pod), nil + return obj.(*core.Pod), nil } diff --git a/pkg/client/listers/core/internalversion/podtemplate.go b/pkg/client/listers/core/internalversion/podtemplate.go index becc5848552..b9107bae96e 100644 --- a/pkg/client/listers/core/internalversion/podtemplate.go +++ b/pkg/client/listers/core/internalversion/podtemplate.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,13 +22,13 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // PodTemplateLister helps list PodTemplates. type PodTemplateLister interface { // List lists all PodTemplates in the indexer. - List(selector labels.Selector) (ret []*api.PodTemplate, err error) + List(selector labels.Selector) (ret []*core.PodTemplate, err error) // PodTemplates returns an object that can list and get PodTemplates. PodTemplates(namespace string) PodTemplateNamespaceLister PodTemplateListerExpansion @@ -45,9 +45,9 @@ func NewPodTemplateLister(indexer cache.Indexer) PodTemplateLister { } // List lists all PodTemplates in the indexer. -func (s *podTemplateLister) List(selector labels.Selector) (ret []*api.PodTemplate, err error) { +func (s *podTemplateLister) List(selector labels.Selector) (ret []*core.PodTemplate, err error) { err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*api.PodTemplate)) + ret = append(ret, m.(*core.PodTemplate)) }) return ret, err } @@ -60,9 +60,9 @@ func (s *podTemplateLister) PodTemplates(namespace string) PodTemplateNamespaceL // PodTemplateNamespaceLister helps list and get PodTemplates. type PodTemplateNamespaceLister interface { // List lists all PodTemplates in the indexer for a given namespace. - List(selector labels.Selector) (ret []*api.PodTemplate, err error) + List(selector labels.Selector) (ret []*core.PodTemplate, err error) // Get retrieves the PodTemplate from the indexer for a given namespace and name. - Get(name string) (*api.PodTemplate, error) + Get(name string) (*core.PodTemplate, error) PodTemplateNamespaceListerExpansion } @@ -74,21 +74,21 @@ type podTemplateNamespaceLister struct { } // List lists all PodTemplates in the indexer for a given namespace. -func (s podTemplateNamespaceLister) List(selector labels.Selector) (ret []*api.PodTemplate, err error) { +func (s podTemplateNamespaceLister) List(selector labels.Selector) (ret []*core.PodTemplate, err error) { err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*api.PodTemplate)) + ret = append(ret, m.(*core.PodTemplate)) }) return ret, err } // Get retrieves the PodTemplate from the indexer for a given namespace and name. -func (s podTemplateNamespaceLister) Get(name string) (*api.PodTemplate, error) { +func (s podTemplateNamespaceLister) Get(name string) (*core.PodTemplate, error) { obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) if err != nil { return nil, err } if !exists { - return nil, errors.NewNotFound(api.Resource("podtemplate"), name) + return nil, errors.NewNotFound(core.Resource("podtemplate"), name) } - return obj.(*api.PodTemplate), nil + return obj.(*core.PodTemplate), nil } diff --git a/pkg/client/listers/core/internalversion/replicationcontroller.go b/pkg/client/listers/core/internalversion/replicationcontroller.go index b9728c249e6..acd50236613 100644 --- a/pkg/client/listers/core/internalversion/replicationcontroller.go +++ b/pkg/client/listers/core/internalversion/replicationcontroller.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,13 +22,13 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // ReplicationControllerLister helps list ReplicationControllers. type ReplicationControllerLister interface { // List lists all ReplicationControllers in the indexer. - List(selector labels.Selector) (ret []*api.ReplicationController, err error) + List(selector labels.Selector) (ret []*core.ReplicationController, err error) // ReplicationControllers returns an object that can list and get ReplicationControllers. ReplicationControllers(namespace string) ReplicationControllerNamespaceLister ReplicationControllerListerExpansion @@ -45,9 +45,9 @@ func NewReplicationControllerLister(indexer cache.Indexer) ReplicationController } // List lists all ReplicationControllers in the indexer. -func (s *replicationControllerLister) List(selector labels.Selector) (ret []*api.ReplicationController, err error) { +func (s *replicationControllerLister) List(selector labels.Selector) (ret []*core.ReplicationController, err error) { err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*api.ReplicationController)) + ret = append(ret, m.(*core.ReplicationController)) }) return ret, err } @@ -60,9 +60,9 @@ func (s *replicationControllerLister) ReplicationControllers(namespace string) R // ReplicationControllerNamespaceLister helps list and get ReplicationControllers. type ReplicationControllerNamespaceLister interface { // List lists all ReplicationControllers in the indexer for a given namespace. - List(selector labels.Selector) (ret []*api.ReplicationController, err error) + List(selector labels.Selector) (ret []*core.ReplicationController, err error) // Get retrieves the ReplicationController from the indexer for a given namespace and name. - Get(name string) (*api.ReplicationController, error) + Get(name string) (*core.ReplicationController, error) ReplicationControllerNamespaceListerExpansion } @@ -74,21 +74,21 @@ type replicationControllerNamespaceLister struct { } // List lists all ReplicationControllers in the indexer for a given namespace. -func (s replicationControllerNamespaceLister) List(selector labels.Selector) (ret []*api.ReplicationController, err error) { +func (s replicationControllerNamespaceLister) List(selector labels.Selector) (ret []*core.ReplicationController, err error) { err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*api.ReplicationController)) + ret = append(ret, m.(*core.ReplicationController)) }) return ret, err } // Get retrieves the ReplicationController from the indexer for a given namespace and name. -func (s replicationControllerNamespaceLister) Get(name string) (*api.ReplicationController, error) { +func (s replicationControllerNamespaceLister) Get(name string) (*core.ReplicationController, error) { obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) if err != nil { return nil, err } if !exists { - return nil, errors.NewNotFound(api.Resource("replicationcontroller"), name) + return nil, errors.NewNotFound(core.Resource("replicationcontroller"), name) } - return obj.(*api.ReplicationController), nil + return obj.(*core.ReplicationController), nil } diff --git a/pkg/client/listers/core/internalversion/replicationcontroller_expansion.go b/pkg/client/listers/core/internalversion/replicationcontroller_expansion.go index c08d38d2d82..bd9029160da 100644 --- a/pkg/client/listers/core/internalversion/replicationcontroller_expansion.go +++ b/pkg/client/listers/core/internalversion/replicationcontroller_expansion.go @@ -20,7 +20,7 @@ import ( "fmt" "k8s.io/apimachinery/pkg/labels" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // ReplicationControllerListerExpansion allows custom methods to be added to diff --git a/pkg/client/listers/core/internalversion/resourcequota.go b/pkg/client/listers/core/internalversion/resourcequota.go index d0015df76eb..af9f1af09cf 100644 --- a/pkg/client/listers/core/internalversion/resourcequota.go +++ b/pkg/client/listers/core/internalversion/resourcequota.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,13 +22,13 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // ResourceQuotaLister helps list ResourceQuotas. type ResourceQuotaLister interface { // List lists all ResourceQuotas in the indexer. - List(selector labels.Selector) (ret []*api.ResourceQuota, err error) + List(selector labels.Selector) (ret []*core.ResourceQuota, err error) // ResourceQuotas returns an object that can list and get ResourceQuotas. ResourceQuotas(namespace string) ResourceQuotaNamespaceLister ResourceQuotaListerExpansion @@ -45,9 +45,9 @@ func NewResourceQuotaLister(indexer cache.Indexer) ResourceQuotaLister { } // List lists all ResourceQuotas in the indexer. -func (s *resourceQuotaLister) List(selector labels.Selector) (ret []*api.ResourceQuota, err error) { +func (s *resourceQuotaLister) List(selector labels.Selector) (ret []*core.ResourceQuota, err error) { err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*api.ResourceQuota)) + ret = append(ret, m.(*core.ResourceQuota)) }) return ret, err } @@ -60,9 +60,9 @@ func (s *resourceQuotaLister) ResourceQuotas(namespace string) ResourceQuotaName // ResourceQuotaNamespaceLister helps list and get ResourceQuotas. type ResourceQuotaNamespaceLister interface { // List lists all ResourceQuotas in the indexer for a given namespace. - List(selector labels.Selector) (ret []*api.ResourceQuota, err error) + List(selector labels.Selector) (ret []*core.ResourceQuota, err error) // Get retrieves the ResourceQuota from the indexer for a given namespace and name. - Get(name string) (*api.ResourceQuota, error) + Get(name string) (*core.ResourceQuota, error) ResourceQuotaNamespaceListerExpansion } @@ -74,21 +74,21 @@ type resourceQuotaNamespaceLister struct { } // List lists all ResourceQuotas in the indexer for a given namespace. -func (s resourceQuotaNamespaceLister) List(selector labels.Selector) (ret []*api.ResourceQuota, err error) { +func (s resourceQuotaNamespaceLister) List(selector labels.Selector) (ret []*core.ResourceQuota, err error) { err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*api.ResourceQuota)) + ret = append(ret, m.(*core.ResourceQuota)) }) return ret, err } // Get retrieves the ResourceQuota from the indexer for a given namespace and name. -func (s resourceQuotaNamespaceLister) Get(name string) (*api.ResourceQuota, error) { +func (s resourceQuotaNamespaceLister) Get(name string) (*core.ResourceQuota, error) { obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) if err != nil { return nil, err } if !exists { - return nil, errors.NewNotFound(api.Resource("resourcequota"), name) + return nil, errors.NewNotFound(core.Resource("resourcequota"), name) } - return obj.(*api.ResourceQuota), nil + return obj.(*core.ResourceQuota), nil } diff --git a/pkg/client/listers/core/internalversion/secret.go b/pkg/client/listers/core/internalversion/secret.go index 820fc421970..18cd65735d0 100644 --- a/pkg/client/listers/core/internalversion/secret.go +++ b/pkg/client/listers/core/internalversion/secret.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,13 +22,13 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // SecretLister helps list Secrets. type SecretLister interface { // List lists all Secrets in the indexer. - List(selector labels.Selector) (ret []*api.Secret, err error) + List(selector labels.Selector) (ret []*core.Secret, err error) // Secrets returns an object that can list and get Secrets. Secrets(namespace string) SecretNamespaceLister SecretListerExpansion @@ -45,9 +45,9 @@ func NewSecretLister(indexer cache.Indexer) SecretLister { } // List lists all Secrets in the indexer. -func (s *secretLister) List(selector labels.Selector) (ret []*api.Secret, err error) { +func (s *secretLister) List(selector labels.Selector) (ret []*core.Secret, err error) { err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*api.Secret)) + ret = append(ret, m.(*core.Secret)) }) return ret, err } @@ -60,9 +60,9 @@ func (s *secretLister) Secrets(namespace string) SecretNamespaceLister { // SecretNamespaceLister helps list and get Secrets. type SecretNamespaceLister interface { // List lists all Secrets in the indexer for a given namespace. - List(selector labels.Selector) (ret []*api.Secret, err error) + List(selector labels.Selector) (ret []*core.Secret, err error) // Get retrieves the Secret from the indexer for a given namespace and name. - Get(name string) (*api.Secret, error) + Get(name string) (*core.Secret, error) SecretNamespaceListerExpansion } @@ -74,21 +74,21 @@ type secretNamespaceLister struct { } // List lists all Secrets in the indexer for a given namespace. -func (s secretNamespaceLister) List(selector labels.Selector) (ret []*api.Secret, err error) { +func (s secretNamespaceLister) List(selector labels.Selector) (ret []*core.Secret, err error) { err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*api.Secret)) + ret = append(ret, m.(*core.Secret)) }) return ret, err } // Get retrieves the Secret from the indexer for a given namespace and name. -func (s secretNamespaceLister) Get(name string) (*api.Secret, error) { +func (s secretNamespaceLister) Get(name string) (*core.Secret, error) { obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) if err != nil { return nil, err } if !exists { - return nil, errors.NewNotFound(api.Resource("secret"), name) + return nil, errors.NewNotFound(core.Resource("secret"), name) } - return obj.(*api.Secret), nil + return obj.(*core.Secret), nil } diff --git a/pkg/client/listers/core/internalversion/service.go b/pkg/client/listers/core/internalversion/service.go index 06afb1b504d..a2d0644af48 100644 --- a/pkg/client/listers/core/internalversion/service.go +++ b/pkg/client/listers/core/internalversion/service.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,13 +22,13 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // ServiceLister helps list Services. type ServiceLister interface { // List lists all Services in the indexer. - List(selector labels.Selector) (ret []*api.Service, err error) + List(selector labels.Selector) (ret []*core.Service, err error) // Services returns an object that can list and get Services. Services(namespace string) ServiceNamespaceLister ServiceListerExpansion @@ -45,9 +45,9 @@ func NewServiceLister(indexer cache.Indexer) ServiceLister { } // List lists all Services in the indexer. -func (s *serviceLister) List(selector labels.Selector) (ret []*api.Service, err error) { +func (s *serviceLister) List(selector labels.Selector) (ret []*core.Service, err error) { err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*api.Service)) + ret = append(ret, m.(*core.Service)) }) return ret, err } @@ -60,9 +60,9 @@ func (s *serviceLister) Services(namespace string) ServiceNamespaceLister { // ServiceNamespaceLister helps list and get Services. type ServiceNamespaceLister interface { // List lists all Services in the indexer for a given namespace. - List(selector labels.Selector) (ret []*api.Service, err error) + List(selector labels.Selector) (ret []*core.Service, err error) // Get retrieves the Service from the indexer for a given namespace and name. - Get(name string) (*api.Service, error) + Get(name string) (*core.Service, error) ServiceNamespaceListerExpansion } @@ -74,21 +74,21 @@ type serviceNamespaceLister struct { } // List lists all Services in the indexer for a given namespace. -func (s serviceNamespaceLister) List(selector labels.Selector) (ret []*api.Service, err error) { +func (s serviceNamespaceLister) List(selector labels.Selector) (ret []*core.Service, err error) { err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*api.Service)) + ret = append(ret, m.(*core.Service)) }) return ret, err } // Get retrieves the Service from the indexer for a given namespace and name. -func (s serviceNamespaceLister) Get(name string) (*api.Service, error) { +func (s serviceNamespaceLister) Get(name string) (*core.Service, error) { obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) if err != nil { return nil, err } if !exists { - return nil, errors.NewNotFound(api.Resource("service"), name) + return nil, errors.NewNotFound(core.Resource("service"), name) } - return obj.(*api.Service), nil + return obj.(*core.Service), nil } diff --git a/pkg/client/listers/core/internalversion/service_expansion.go b/pkg/client/listers/core/internalversion/service_expansion.go index 1bbc89c5c7d..6a951cabfd3 100644 --- a/pkg/client/listers/core/internalversion/service_expansion.go +++ b/pkg/client/listers/core/internalversion/service_expansion.go @@ -18,7 +18,7 @@ package internalversion import ( "k8s.io/apimachinery/pkg/labels" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // ServiceListerExpansion allows custom methods to be added to diff --git a/pkg/client/listers/core/internalversion/serviceaccount.go b/pkg/client/listers/core/internalversion/serviceaccount.go index 5b07ee039fd..78e8e96e42b 100644 --- a/pkg/client/listers/core/internalversion/serviceaccount.go +++ b/pkg/client/listers/core/internalversion/serviceaccount.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,13 +22,13 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" ) // ServiceAccountLister helps list ServiceAccounts. type ServiceAccountLister interface { // List lists all ServiceAccounts in the indexer. - List(selector labels.Selector) (ret []*api.ServiceAccount, err error) + List(selector labels.Selector) (ret []*core.ServiceAccount, err error) // ServiceAccounts returns an object that can list and get ServiceAccounts. ServiceAccounts(namespace string) ServiceAccountNamespaceLister ServiceAccountListerExpansion @@ -45,9 +45,9 @@ func NewServiceAccountLister(indexer cache.Indexer) ServiceAccountLister { } // List lists all ServiceAccounts in the indexer. -func (s *serviceAccountLister) List(selector labels.Selector) (ret []*api.ServiceAccount, err error) { +func (s *serviceAccountLister) List(selector labels.Selector) (ret []*core.ServiceAccount, err error) { err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*api.ServiceAccount)) + ret = append(ret, m.(*core.ServiceAccount)) }) return ret, err } @@ -60,9 +60,9 @@ func (s *serviceAccountLister) ServiceAccounts(namespace string) ServiceAccountN // ServiceAccountNamespaceLister helps list and get ServiceAccounts. type ServiceAccountNamespaceLister interface { // List lists all ServiceAccounts in the indexer for a given namespace. - List(selector labels.Selector) (ret []*api.ServiceAccount, err error) + List(selector labels.Selector) (ret []*core.ServiceAccount, err error) // Get retrieves the ServiceAccount from the indexer for a given namespace and name. - Get(name string) (*api.ServiceAccount, error) + Get(name string) (*core.ServiceAccount, error) ServiceAccountNamespaceListerExpansion } @@ -74,21 +74,21 @@ type serviceAccountNamespaceLister struct { } // List lists all ServiceAccounts in the indexer for a given namespace. -func (s serviceAccountNamespaceLister) List(selector labels.Selector) (ret []*api.ServiceAccount, err error) { +func (s serviceAccountNamespaceLister) List(selector labels.Selector) (ret []*core.ServiceAccount, err error) { err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*api.ServiceAccount)) + ret = append(ret, m.(*core.ServiceAccount)) }) return ret, err } // Get retrieves the ServiceAccount from the indexer for a given namespace and name. -func (s serviceAccountNamespaceLister) Get(name string) (*api.ServiceAccount, error) { +func (s serviceAccountNamespaceLister) Get(name string) (*core.ServiceAccount, error) { obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) if err != nil { return nil, err } if !exists { - return nil, errors.NewNotFound(api.Resource("serviceaccount"), name) + return nil, errors.NewNotFound(core.Resource("serviceaccount"), name) } - return obj.(*api.ServiceAccount), nil + return obj.(*core.ServiceAccount), nil } diff --git a/pkg/client/listers/extensions/internalversion/BUILD b/pkg/client/listers/extensions/internalversion/BUILD index 6981d11e94e..f521b0c0df6 100644 --- a/pkg/client/listers/extensions/internalversion/BUILD +++ b/pkg/client/listers/extensions/internalversion/BUILD @@ -18,12 +18,10 @@ go_library( "podsecuritypolicy.go", "replicaset.go", "replicaset_expansion.go", - "scale.go", - "thirdpartyresource.go", ], importpath = "k8s.io/kubernetes/pkg/client/listers/extensions/internalversion", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -48,10 +46,10 @@ filegroup( go_test( name = "go_default_test", srcs = ["daemonset_expansion_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/client/listers/extensions/internalversion", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", diff --git a/pkg/client/listers/extensions/internalversion/daemonset.go b/pkg/client/listers/extensions/internalversion/daemonset.go index a9716d75f3b..36f6b1e3918 100644 --- a/pkg/client/listers/extensions/internalversion/daemonset.go +++ b/pkg/client/listers/extensions/internalversion/daemonset.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/extensions/internalversion/daemonset_expansion.go b/pkg/client/listers/extensions/internalversion/daemonset_expansion.go index d6c55ec49a5..dd9eee74311 100644 --- a/pkg/client/listers/extensions/internalversion/daemonset_expansion.go +++ b/pkg/client/listers/extensions/internalversion/daemonset_expansion.go @@ -21,7 +21,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" ) diff --git a/pkg/client/listers/extensions/internalversion/daemonset_expansion_test.go b/pkg/client/listers/extensions/internalversion/daemonset_expansion_test.go index b3a596c07e7..2455b5271ab 100644 --- a/pkg/client/listers/extensions/internalversion/daemonset_expansion_test.go +++ b/pkg/client/listers/extensions/internalversion/daemonset_expansion_test.go @@ -23,7 +23,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/tools/cache" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" ) diff --git a/pkg/client/listers/extensions/internalversion/deployment.go b/pkg/client/listers/extensions/internalversion/deployment.go index b393add6882..da8b8efd0c0 100644 --- a/pkg/client/listers/extensions/internalversion/deployment.go +++ b/pkg/client/listers/extensions/internalversion/deployment.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/extensions/internalversion/expansion_generated.go b/pkg/client/listers/extensions/internalversion/expansion_generated.go index 1faa9b74946..6b63294a7e1 100644 --- a/pkg/client/listers/extensions/internalversion/expansion_generated.go +++ b/pkg/client/listers/extensions/internalversion/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -29,15 +29,3 @@ type IngressNamespaceListerExpansion interface{} // PodSecurityPolicyListerExpansion allows custom methods to be added to // PodSecurityPolicyLister. type PodSecurityPolicyListerExpansion interface{} - -// ScaleListerExpansion allows custom methods to be added to -// ScaleLister. -type ScaleListerExpansion interface{} - -// ScaleNamespaceListerExpansion allows custom methods to be added to -// ScaleNamespaceLister. -type ScaleNamespaceListerExpansion interface{} - -// ThirdPartyResourceListerExpansion allows custom methods to be added to -// ThirdPartyResourceLister. -type ThirdPartyResourceListerExpansion interface{} diff --git a/pkg/client/listers/extensions/internalversion/ingress.go b/pkg/client/listers/extensions/internalversion/ingress.go index 8e295804a11..79892903afc 100644 --- a/pkg/client/listers/extensions/internalversion/ingress.go +++ b/pkg/client/listers/extensions/internalversion/ingress.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/extensions/internalversion/podsecuritypolicy.go b/pkg/client/listers/extensions/internalversion/podsecuritypolicy.go index 03a976bad28..8db690522cd 100644 --- a/pkg/client/listers/extensions/internalversion/podsecuritypolicy.go +++ b/pkg/client/listers/extensions/internalversion/podsecuritypolicy.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -20,7 +20,6 @@ package internalversion import ( "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" extensions "k8s.io/kubernetes/pkg/apis/extensions" @@ -55,8 +54,7 @@ func (s *podSecurityPolicyLister) List(selector labels.Selector) (ret []*extensi // Get retrieves the PodSecurityPolicy from the index for a given name. func (s *podSecurityPolicyLister) Get(name string) (*extensions.PodSecurityPolicy, error) { - key := &extensions.PodSecurityPolicy{ObjectMeta: v1.ObjectMeta{Name: name}} - obj, exists, err := s.indexer.Get(key) + obj, exists, err := s.indexer.GetByKey(name) if err != nil { return nil, err } diff --git a/pkg/client/listers/extensions/internalversion/replicaset.go b/pkg/client/listers/extensions/internalversion/replicaset.go index 03d53d77c1e..a6d0f4c26e9 100644 --- a/pkg/client/listers/extensions/internalversion/replicaset.go +++ b/pkg/client/listers/extensions/internalversion/replicaset.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/extensions/internalversion/replicaset_expansion.go b/pkg/client/listers/extensions/internalversion/replicaset_expansion.go index 44808306b4d..3b3cfc361f4 100644 --- a/pkg/client/listers/extensions/internalversion/replicaset_expansion.go +++ b/pkg/client/listers/extensions/internalversion/replicaset_expansion.go @@ -21,7 +21,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" ) diff --git a/pkg/client/listers/extensions/internalversion/scale.go b/pkg/client/listers/extensions/internalversion/scale.go deleted file mode 100644 index b33c2a156f0..00000000000 --- a/pkg/client/listers/extensions/internalversion/scale.go +++ /dev/null @@ -1,94 +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. -*/ - -// This file was automatically generated by lister-gen - -package internalversion - -import ( - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/tools/cache" - extensions "k8s.io/kubernetes/pkg/apis/extensions" -) - -// ScaleLister helps list Scales. -type ScaleLister interface { - // List lists all Scales in the indexer. - List(selector labels.Selector) (ret []*extensions.Scale, err error) - // Scales returns an object that can list and get Scales. - Scales(namespace string) ScaleNamespaceLister - ScaleListerExpansion -} - -// scaleLister implements the ScaleLister interface. -type scaleLister struct { - indexer cache.Indexer -} - -// NewScaleLister returns a new ScaleLister. -func NewScaleLister(indexer cache.Indexer) ScaleLister { - return &scaleLister{indexer: indexer} -} - -// List lists all Scales in the indexer. -func (s *scaleLister) List(selector labels.Selector) (ret []*extensions.Scale, err error) { - err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*extensions.Scale)) - }) - return ret, err -} - -// Scales returns an object that can list and get Scales. -func (s *scaleLister) Scales(namespace string) ScaleNamespaceLister { - return scaleNamespaceLister{indexer: s.indexer, namespace: namespace} -} - -// ScaleNamespaceLister helps list and get Scales. -type ScaleNamespaceLister interface { - // List lists all Scales in the indexer for a given namespace. - List(selector labels.Selector) (ret []*extensions.Scale, err error) - // Get retrieves the Scale from the indexer for a given namespace and name. - Get(name string) (*extensions.Scale, error) - ScaleNamespaceListerExpansion -} - -// scaleNamespaceLister implements the ScaleNamespaceLister -// interface. -type scaleNamespaceLister struct { - indexer cache.Indexer - namespace string -} - -// List lists all Scales in the indexer for a given namespace. -func (s scaleNamespaceLister) List(selector labels.Selector) (ret []*extensions.Scale, err error) { - err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*extensions.Scale)) - }) - return ret, err -} - -// Get retrieves the Scale from the indexer for a given namespace and name. -func (s scaleNamespaceLister) Get(name string) (*extensions.Scale, error) { - obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) - if err != nil { - return nil, err - } - if !exists { - return nil, errors.NewNotFound(extensions.Resource("scale"), name) - } - return obj.(*extensions.Scale), nil -} diff --git a/pkg/client/listers/extensions/internalversion/thirdpartyresource.go b/pkg/client/listers/extensions/internalversion/thirdpartyresource.go deleted file mode 100644 index 6177bf5f55a..00000000000 --- a/pkg/client/listers/extensions/internalversion/thirdpartyresource.go +++ /dev/null @@ -1,67 +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. -*/ - -// This file was automatically generated by lister-gen - -package internalversion - -import ( - "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/tools/cache" - extensions "k8s.io/kubernetes/pkg/apis/extensions" -) - -// ThirdPartyResourceLister helps list ThirdPartyResources. -type ThirdPartyResourceLister interface { - // List lists all ThirdPartyResources in the indexer. - List(selector labels.Selector) (ret []*extensions.ThirdPartyResource, err error) - // Get retrieves the ThirdPartyResource from the index for a given name. - Get(name string) (*extensions.ThirdPartyResource, error) - ThirdPartyResourceListerExpansion -} - -// thirdPartyResourceLister implements the ThirdPartyResourceLister interface. -type thirdPartyResourceLister struct { - indexer cache.Indexer -} - -// NewThirdPartyResourceLister returns a new ThirdPartyResourceLister. -func NewThirdPartyResourceLister(indexer cache.Indexer) ThirdPartyResourceLister { - return &thirdPartyResourceLister{indexer: indexer} -} - -// List lists all ThirdPartyResources in the indexer. -func (s *thirdPartyResourceLister) List(selector labels.Selector) (ret []*extensions.ThirdPartyResource, err error) { - err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*extensions.ThirdPartyResource)) - }) - return ret, err -} - -// Get retrieves the ThirdPartyResource from the index for a given name. -func (s *thirdPartyResourceLister) Get(name string) (*extensions.ThirdPartyResource, error) { - key := &extensions.ThirdPartyResource{ObjectMeta: v1.ObjectMeta{Name: name}} - obj, exists, err := s.indexer.Get(key) - if err != nil { - return nil, err - } - if !exists { - return nil, errors.NewNotFound(extensions.Resource("thirdpartyresource"), name) - } - return obj.(*extensions.ThirdPartyResource), nil -} diff --git a/pkg/client/listers/imagepolicy/internalversion/BUILD b/pkg/client/listers/imagepolicy/internalversion/BUILD index af588cda286..22a9151e0cf 100644 --- a/pkg/client/listers/imagepolicy/internalversion/BUILD +++ b/pkg/client/listers/imagepolicy/internalversion/BUILD @@ -15,7 +15,6 @@ go_library( deps = [ "//pkg/apis/imagepolicy:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", ], diff --git a/pkg/client/listers/imagepolicy/internalversion/expansion_generated.go b/pkg/client/listers/imagepolicy/internalversion/expansion_generated.go index 1beaad497da..42ff47094a6 100644 --- a/pkg/client/listers/imagepolicy/internalversion/expansion_generated.go +++ b/pkg/client/listers/imagepolicy/internalversion/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/imagepolicy/internalversion/imagereview.go b/pkg/client/listers/imagepolicy/internalversion/imagereview.go index 98cfe57b9fb..e51f20c8de7 100644 --- a/pkg/client/listers/imagepolicy/internalversion/imagereview.go +++ b/pkg/client/listers/imagepolicy/internalversion/imagereview.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -20,7 +20,6 @@ package internalversion import ( "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" imagepolicy "k8s.io/kubernetes/pkg/apis/imagepolicy" @@ -55,8 +54,7 @@ func (s *imageReviewLister) List(selector labels.Selector) (ret []*imagepolicy.I // Get retrieves the ImageReview from the index for a given name. func (s *imageReviewLister) Get(name string) (*imagepolicy.ImageReview, error) { - key := &imagepolicy.ImageReview{ObjectMeta: v1.ObjectMeta{Name: name}} - obj, exists, err := s.indexer.Get(key) + obj, exists, err := s.indexer.GetByKey(name) if err != nil { return nil, err } diff --git a/pkg/client/listers/networking/internalversion/expansion_generated.go b/pkg/client/listers/networking/internalversion/expansion_generated.go index 085ac329ce1..55b40392ba6 100644 --- a/pkg/client/listers/networking/internalversion/expansion_generated.go +++ b/pkg/client/listers/networking/internalversion/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/networking/internalversion/networkpolicy.go b/pkg/client/listers/networking/internalversion/networkpolicy.go index 3b7a618e398..c1abebedc18 100644 --- a/pkg/client/listers/networking/internalversion/networkpolicy.go +++ b/pkg/client/listers/networking/internalversion/networkpolicy.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/policy/internalversion/BUILD b/pkg/client/listers/policy/internalversion/BUILD index 26f5cfc1466..c120deb3b46 100644 --- a/pkg/client/listers/policy/internalversion/BUILD +++ b/pkg/client/listers/policy/internalversion/BUILD @@ -15,7 +15,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/client/listers/policy/internalversion", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/policy:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", diff --git a/pkg/client/listers/policy/internalversion/eviction.go b/pkg/client/listers/policy/internalversion/eviction.go index 38e15234a7e..743271fda85 100644 --- a/pkg/client/listers/policy/internalversion/eviction.go +++ b/pkg/client/listers/policy/internalversion/eviction.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/policy/internalversion/expansion_generated.go b/pkg/client/listers/policy/internalversion/expansion_generated.go index 65e4ddd42b0..7591e3eb6bd 100644 --- a/pkg/client/listers/policy/internalversion/expansion_generated.go +++ b/pkg/client/listers/policy/internalversion/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/policy/internalversion/poddisruptionbudget.go b/pkg/client/listers/policy/internalversion/poddisruptionbudget.go index b1828e57be7..9ea20e4f728 100644 --- a/pkg/client/listers/policy/internalversion/poddisruptionbudget.go +++ b/pkg/client/listers/policy/internalversion/poddisruptionbudget.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/policy/internalversion/poddisruptionbudget_expansion.go b/pkg/client/listers/policy/internalversion/poddisruptionbudget_expansion.go index 025358920f4..fcbc88bc8d0 100644 --- a/pkg/client/listers/policy/internalversion/poddisruptionbudget_expansion.go +++ b/pkg/client/listers/policy/internalversion/poddisruptionbudget_expansion.go @@ -22,7 +22,7 @@ import ( "github.com/golang/glog" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/policy" ) diff --git a/pkg/client/listers/rbac/internalversion/BUILD b/pkg/client/listers/rbac/internalversion/BUILD index f0b816c06a3..1530995394e 100644 --- a/pkg/client/listers/rbac/internalversion/BUILD +++ b/pkg/client/listers/rbac/internalversion/BUILD @@ -18,7 +18,6 @@ go_library( deps = [ "//pkg/apis/rbac:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", ], diff --git a/pkg/client/listers/rbac/internalversion/clusterrole.go b/pkg/client/listers/rbac/internalversion/clusterrole.go index eda1cc3acfe..c18f546052b 100644 --- a/pkg/client/listers/rbac/internalversion/clusterrole.go +++ b/pkg/client/listers/rbac/internalversion/clusterrole.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -20,7 +20,6 @@ package internalversion import ( "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" rbac "k8s.io/kubernetes/pkg/apis/rbac" @@ -55,8 +54,7 @@ func (s *clusterRoleLister) List(selector labels.Selector) (ret []*rbac.ClusterR // Get retrieves the ClusterRole from the index for a given name. func (s *clusterRoleLister) Get(name string) (*rbac.ClusterRole, error) { - key := &rbac.ClusterRole{ObjectMeta: v1.ObjectMeta{Name: name}} - obj, exists, err := s.indexer.Get(key) + obj, exists, err := s.indexer.GetByKey(name) if err != nil { return nil, err } diff --git a/pkg/client/listers/rbac/internalversion/clusterrolebinding.go b/pkg/client/listers/rbac/internalversion/clusterrolebinding.go index fed2b2e9f36..53e6ba1fd8a 100644 --- a/pkg/client/listers/rbac/internalversion/clusterrolebinding.go +++ b/pkg/client/listers/rbac/internalversion/clusterrolebinding.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -20,7 +20,6 @@ package internalversion import ( "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" rbac "k8s.io/kubernetes/pkg/apis/rbac" @@ -55,8 +54,7 @@ func (s *clusterRoleBindingLister) List(selector labels.Selector) (ret []*rbac.C // Get retrieves the ClusterRoleBinding from the index for a given name. func (s *clusterRoleBindingLister) Get(name string) (*rbac.ClusterRoleBinding, error) { - key := &rbac.ClusterRoleBinding{ObjectMeta: v1.ObjectMeta{Name: name}} - obj, exists, err := s.indexer.Get(key) + obj, exists, err := s.indexer.GetByKey(name) if err != nil { return nil, err } diff --git a/pkg/client/listers/rbac/internalversion/expansion_generated.go b/pkg/client/listers/rbac/internalversion/expansion_generated.go index 2e88685425f..a4ddf2437d9 100644 --- a/pkg/client/listers/rbac/internalversion/expansion_generated.go +++ b/pkg/client/listers/rbac/internalversion/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/rbac/internalversion/role.go b/pkg/client/listers/rbac/internalversion/role.go index 8d4ee98b79a..2a1355214f5 100644 --- a/pkg/client/listers/rbac/internalversion/role.go +++ b/pkg/client/listers/rbac/internalversion/role.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/rbac/internalversion/rolebinding.go b/pkg/client/listers/rbac/internalversion/rolebinding.go index ad9a3d4785c..02392a00661 100644 --- a/pkg/client/listers/rbac/internalversion/rolebinding.go +++ b/pkg/client/listers/rbac/internalversion/rolebinding.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/scheduling/internalversion/BUILD b/pkg/client/listers/scheduling/internalversion/BUILD index 2dd68118549..5c2f02bafe7 100644 --- a/pkg/client/listers/scheduling/internalversion/BUILD +++ b/pkg/client/listers/scheduling/internalversion/BUILD @@ -15,7 +15,6 @@ go_library( deps = [ "//pkg/apis/scheduling:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", ], diff --git a/pkg/client/listers/scheduling/internalversion/expansion_generated.go b/pkg/client/listers/scheduling/internalversion/expansion_generated.go index a919cca03fd..e846b204bea 100644 --- a/pkg/client/listers/scheduling/internalversion/expansion_generated.go +++ b/pkg/client/listers/scheduling/internalversion/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/scheduling/internalversion/priorityclass.go b/pkg/client/listers/scheduling/internalversion/priorityclass.go index b219677dd2f..e2d510b1247 100644 --- a/pkg/client/listers/scheduling/internalversion/priorityclass.go +++ b/pkg/client/listers/scheduling/internalversion/priorityclass.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -20,7 +20,6 @@ package internalversion import ( "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" scheduling "k8s.io/kubernetes/pkg/apis/scheduling" @@ -55,8 +54,7 @@ func (s *priorityClassLister) List(selector labels.Selector) (ret []*scheduling. // Get retrieves the PriorityClass from the index for a given name. func (s *priorityClassLister) Get(name string) (*scheduling.PriorityClass, error) { - key := &scheduling.PriorityClass{ObjectMeta: v1.ObjectMeta{Name: name}} - obj, exists, err := s.indexer.Get(key) + obj, exists, err := s.indexer.GetByKey(name) if err != nil { return nil, err } diff --git a/pkg/client/listers/settings/internalversion/expansion_generated.go b/pkg/client/listers/settings/internalversion/expansion_generated.go index c28f8d51958..169001b1738 100644 --- a/pkg/client/listers/settings/internalversion/expansion_generated.go +++ b/pkg/client/listers/settings/internalversion/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/settings/internalversion/podpreset.go b/pkg/client/listers/settings/internalversion/podpreset.go index 024b599625b..afecfe1ddfe 100644 --- a/pkg/client/listers/settings/internalversion/podpreset.go +++ b/pkg/client/listers/settings/internalversion/podpreset.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/client/listers/storage/internalversion/BUILD b/pkg/client/listers/storage/internalversion/BUILD index 4733b25a4ac..ff223c8b8cd 100644 --- a/pkg/client/listers/storage/internalversion/BUILD +++ b/pkg/client/listers/storage/internalversion/BUILD @@ -10,12 +10,12 @@ go_library( srcs = [ "expansion_generated.go", "storageclass.go", + "volumeattachment.go", ], importpath = "k8s.io/kubernetes/pkg/client/listers/storage/internalversion", deps = [ "//pkg/apis/storage:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", ], diff --git a/pkg/client/listers/storage/internalversion/expansion_generated.go b/pkg/client/listers/storage/internalversion/expansion_generated.go index 858ece21cbd..7b5bff6ba32 100644 --- a/pkg/client/listers/storage/internalversion/expansion_generated.go +++ b/pkg/client/listers/storage/internalversion/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,3 +21,7 @@ package internalversion // StorageClassListerExpansion allows custom methods to be added to // StorageClassLister. type StorageClassListerExpansion interface{} + +// VolumeAttachmentListerExpansion allows custom methods to be added to +// VolumeAttachmentLister. +type VolumeAttachmentListerExpansion interface{} diff --git a/pkg/client/listers/storage/internalversion/storageclass.go b/pkg/client/listers/storage/internalversion/storageclass.go index f1898a98f7f..a1f2109bc01 100644 --- a/pkg/client/listers/storage/internalversion/storageclass.go +++ b/pkg/client/listers/storage/internalversion/storageclass.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -20,7 +20,6 @@ package internalversion import ( "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" storage "k8s.io/kubernetes/pkg/apis/storage" @@ -55,8 +54,7 @@ func (s *storageClassLister) List(selector labels.Selector) (ret []*storage.Stor // Get retrieves the StorageClass from the index for a given name. func (s *storageClassLister) Get(name string) (*storage.StorageClass, error) { - key := &storage.StorageClass{ObjectMeta: v1.ObjectMeta{Name: name}} - obj, exists, err := s.indexer.Get(key) + obj, exists, err := s.indexer.GetByKey(name) if err != nil { return nil, err } diff --git a/pkg/client/listers/storage/internalversion/volumeattachment.go b/pkg/client/listers/storage/internalversion/volumeattachment.go new file mode 100644 index 00000000000..c0d91fdbc90 --- /dev/null +++ b/pkg/client/listers/storage/internalversion/volumeattachment.go @@ -0,0 +1,65 @@ +/* +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. +*/ + +// This file was automatically generated by lister-gen + +package internalversion + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + storage "k8s.io/kubernetes/pkg/apis/storage" +) + +// VolumeAttachmentLister helps list VolumeAttachments. +type VolumeAttachmentLister interface { + // List lists all VolumeAttachments in the indexer. + List(selector labels.Selector) (ret []*storage.VolumeAttachment, err error) + // Get retrieves the VolumeAttachment from the index for a given name. + Get(name string) (*storage.VolumeAttachment, error) + VolumeAttachmentListerExpansion +} + +// volumeAttachmentLister implements the VolumeAttachmentLister interface. +type volumeAttachmentLister struct { + indexer cache.Indexer +} + +// NewVolumeAttachmentLister returns a new VolumeAttachmentLister. +func NewVolumeAttachmentLister(indexer cache.Indexer) VolumeAttachmentLister { + return &volumeAttachmentLister{indexer: indexer} +} + +// List lists all VolumeAttachments in the indexer. +func (s *volumeAttachmentLister) List(selector labels.Selector) (ret []*storage.VolumeAttachment, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*storage.VolumeAttachment)) + }) + return ret, err +} + +// Get retrieves the VolumeAttachment from the index for a given name. +func (s *volumeAttachmentLister) Get(name string) (*storage.VolumeAttachment, error) { + obj, exists, err := s.indexer.GetByKey(name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(storage.Resource("volumeattachment"), name) + } + return obj.(*storage.VolumeAttachment), nil +} diff --git a/pkg/client/tests/BUILD b/pkg/client/tests/BUILD index aa3cb29bb1e..d22b547f68f 100644 --- a/pkg/client/tests/BUILD +++ b/pkg/client/tests/BUILD @@ -14,12 +14,13 @@ go_test( "portfoward_test.go", "remotecommand_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/client/tests", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/testapi:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/install:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", "//pkg/kubelet/server/portforward:go_default_library", diff --git a/pkg/client/tests/fake_client_test.go b/pkg/client/tests/fake_client_test.go index 25b3dc03de5..731e334e766 100644 --- a/pkg/client/tests/fake_client_test.go +++ b/pkg/client/tests/fake_client_test.go @@ -20,10 +20,10 @@ import ( "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" clientsetfake "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" - _ "k8s.io/kubernetes/pkg/api/install" + _ "k8s.io/kubernetes/pkg/apis/core/install" ) func TestFakeClientSetFiltering(t *testing.T) { diff --git a/pkg/client/tests/listwatch_test.go b/pkg/client/tests/listwatch_test.go index b3fd42d619c..a4b6c211840 100644 --- a/pkg/client/tests/listwatch_test.go +++ b/pkg/client/tests/listwatch_test.go @@ -30,7 +30,7 @@ import ( restclient "k8s.io/client-go/rest" . "k8s.io/client-go/tools/cache" utiltesting "k8s.io/client-go/util/testing" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/testapi" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" ) @@ -61,7 +61,7 @@ func buildLocation(resourcePath string, query url.Values) string { } func TestListWatchesCanList(t *testing.T) { - fieldSelectorQueryParamName := metav1.FieldSelectorQueryParam(api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()) + fieldSelectorQueryParamName := metav1.FieldSelectorQueryParam(legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()) table := []struct { location string resource string @@ -102,7 +102,7 @@ func TestListWatchesCanList(t *testing.T) { } server := httptest.NewServer(&handler) defer server.Close() - client := clientset.NewForConfigOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + client := clientset.NewForConfigOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) lw := NewListWatchFromClient(client.Core().RESTClient(), item.resource, item.namespace, item.fieldSelector) lw.DisableChunking = true // This test merely tests that the correct request is made. @@ -112,7 +112,7 @@ func TestListWatchesCanList(t *testing.T) { } func TestListWatchesCanWatch(t *testing.T) { - fieldSelectorQueryParamName := metav1.FieldSelectorQueryParam(api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()) + fieldSelectorQueryParamName := metav1.FieldSelectorQueryParam(legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()) table := []struct { rv string location string @@ -169,7 +169,7 @@ func TestListWatchesCanWatch(t *testing.T) { } server := httptest.NewServer(&handler) defer server.Close() - client := clientset.NewForConfigOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + client := clientset.NewForConfigOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) lw := NewListWatchFromClient(client.Core().RESTClient(), item.resource, item.namespace, item.fieldSelector) // This test merely tests that the correct request is made. lw.Watch(metav1.ListOptions{ResourceVersion: item.rv}) diff --git a/pkg/client/tests/remotecommand_test.go b/pkg/client/tests/remotecommand_test.go index 6ea8a96ecb1..285a8a29ee1 100644 --- a/pkg/client/tests/remotecommand_test.go +++ b/pkg/client/tests/remotecommand_test.go @@ -38,8 +38,8 @@ import ( restclient "k8s.io/client-go/rest" remoteclient "k8s.io/client-go/tools/remotecommand" "k8s.io/client-go/transport/spdy" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/kubelet/server/remotecommand" ) @@ -256,7 +256,12 @@ func TestStream(t *testing.T) { conf := &restclient.Config{ Host: server.URL, } - e, err := remoteclient.NewSPDYExecutorForProtocols(conf, "POST", req.URL(), testCase.ClientProtocols...) + transport, upgradeTransport, err := spdy.RoundTripperFor(conf) + if err != nil { + t.Errorf("%s: unexpected error: %v", name, err) + continue + } + e, err := remoteclient.NewSPDYExecutorForProtocols(transport, upgradeTransport, "POST", req.URL(), testCase.ClientProtocols...) if err != nil { t.Errorf("%s: unexpected error: %v", name, err) continue diff --git a/pkg/client/unversioned/BUILD b/pkg/client/unversioned/BUILD index 02e53cb56b1..0d97be77f39 100644 --- a/pkg/client/unversioned/BUILD +++ b/pkg/client/unversioned/BUILD @@ -14,10 +14,11 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/client/unversioned", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/pod:go_default_library", "//pkg/apis/apps:go_default_library", "//pkg/apis/batch:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/apps/internalversion:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/batch/internalversion:go_default_library", @@ -36,11 +37,12 @@ go_library( go_test( name = "go_default_test", srcs = ["helper_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/client/unversioned", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/testapi:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", diff --git a/pkg/client/unversioned/conditions.go b/pkg/client/unversioned/conditions.go index a65c8475085..042d13a6fa8 100644 --- a/pkg/client/unversioned/conditions.go +++ b/pkg/client/unversioned/conditions.go @@ -24,10 +24,10 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/watch" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/pod" "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/batch" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" appsclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion" batchclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion" diff --git a/pkg/client/unversioned/helper.go b/pkg/client/unversioned/helper.go index 74baf75d0d5..ca0fc2c70d4 100644 --- a/pkg/client/unversioned/helper.go +++ b/pkg/client/unversioned/helper.go @@ -19,7 +19,7 @@ package unversioned import ( "k8s.io/apimachinery/pkg/runtime/schema" restclient "k8s.io/client-go/rest" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" // Import solely to initialize client auth plugins. _ "k8s.io/client-go/plugin/pkg/client/auth" ) @@ -41,7 +41,7 @@ func SetKubernetesDefaults(config *restclient.Config) error { config.GroupVersion = &schema.GroupVersion{} } if config.NegotiatedSerializer == nil { - config.NegotiatedSerializer = api.Codecs + config.NegotiatedSerializer = legacyscheme.Codecs } return restclient.SetKubernetesDefaults(config) } @@ -52,7 +52,7 @@ func setGroupDefaults(groupName string, config *restclient.Config) error { config.UserAgent = restclient.DefaultKubernetesUserAgent() } if config.GroupVersion == nil || config.GroupVersion.Group != groupName { - g, err := api.Registry.Group(groupName) + g, err := legacyscheme.Registry.Group(groupName) if err != nil { return err } @@ -60,7 +60,7 @@ func setGroupDefaults(groupName string, config *restclient.Config) error { config.GroupVersion = ©GroupVersion } if config.NegotiatedSerializer == nil { - config.NegotiatedSerializer = api.Codecs + config.NegotiatedSerializer = legacyscheme.Codecs } return nil } diff --git a/pkg/client/unversioned/helper_test.go b/pkg/client/unversioned/helper_test.go index 52966101591..4b62c3bfc83 100644 --- a/pkg/client/unversioned/helper_test.go +++ b/pkg/client/unversioned/helper_test.go @@ -27,8 +27,9 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" restclient "k8s.io/client-go/rest" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/testapi" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestSetKubernetesDefaults(t *testing.T) { @@ -140,9 +141,9 @@ func TestSetsCodec(t *testing.T) { Prefix string NegotiatedSerializer runtime.NegotiatedSerializer }{ - api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version: { + legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.Version: { Err: false, - Prefix: "/api/" + api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version, + Prefix: "/api/" + legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.Version, NegotiatedSerializer: testapi.Default.NegotiatedSerializer(), }, // Add this test back when we fixed config and SetKubernetesDefaults diff --git a/pkg/client/unversioned/testclient/simple/BUILD b/pkg/client/unversioned/testclient/simple/BUILD index 781454671a0..788e07aeec4 100644 --- a/pkg/client/unversioned/testclient/simple/BUILD +++ b/pkg/client/unversioned/testclient/simple/BUILD @@ -10,8 +10,9 @@ go_library( srcs = ["simple_testclient.go"], importpath = "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/testapi:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/client/unversioned/testclient/simple/simple_testclient.go b/pkg/client/unversioned/testclient/simple/simple_testclient.go index 34649d0f0de..17160aa3f29 100644 --- a/pkg/client/unversioned/testclient/simple/simple_testclient.go +++ b/pkg/client/unversioned/testclient/simple/simple_testclient.go @@ -31,8 +31,9 @@ import ( "k8s.io/apimachinery/pkg/runtime" restclient "k8s.io/client-go/rest" utiltesting "k8s.io/client-go/util/testing" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/testapi" + api "k8s.io/kubernetes/pkg/apis/core" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" ) @@ -141,9 +142,9 @@ func (c *Client) ValidateCommon(t *testing.T, err error) { validator, ok := c.QueryValidator[key] if !ok { switch key { - case metav1.LabelSelectorQueryParam(api.Registry.GroupOrDie(api.GroupName).GroupVersion.String()): + case metav1.LabelSelectorQueryParam(legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.String()): validator = ValidateLabels - case metav1.FieldSelectorQueryParam(api.Registry.GroupOrDie(api.GroupName).GroupVersion.String()): + case metav1.FieldSelectorQueryParam(legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.String()): validator = validateFields default: validator = func(a, b string) bool { return a == b } @@ -199,7 +200,7 @@ func validateFields(a, b string) bool { func (c *Client) body(t *testing.T, obj runtime.Object, raw *string) *string { if obj != nil { - fqKinds, _, err := api.Scheme.ObjectKinds(obj) + fqKinds, _, err := legacyscheme.Scheme.ObjectKinds(obj) if err != nil { t.Errorf("unexpected encoding error: %v", err) } diff --git a/pkg/cloudprovider/BUILD b/pkg/cloudprovider/BUILD index 9f780833582..7c38feae3fe 100644 --- a/pkg/cloudprovider/BUILD +++ b/pkg/cloudprovider/BUILD @@ -18,6 +18,7 @@ go_library( "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/client-go/informers:go_default_library", ], ) diff --git a/pkg/cloudprovider/cloud.go b/pkg/cloudprovider/cloud.go index 00479e9c5a0..2f5cbbf1a1a 100644 --- a/pkg/cloudprovider/cloud.go +++ b/pkg/cloudprovider/cloud.go @@ -23,6 +23,7 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/informers" "k8s.io/kubernetes/pkg/controller" ) @@ -43,12 +44,15 @@ type Interface interface { Routes() (Routes, bool) // ProviderName returns the cloud provider ID. ProviderName() string - // ScrubDNS provides an opportunity for cloud-provider-specific code to process DNS settings for pods. - ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) // HasClusterID returns true if a ClusterID is required and set HasClusterID() bool } +type InformerUser interface { + // SetInformers sets the informer on the cloud object. + SetInformers(informerFactory informers.SharedInformerFactory) +} + // Clusters is an abstract, pluggable interface for clusters of containers. type Clusters interface { // ListClusters lists the names of the available clusters. diff --git a/pkg/cloudprovider/plugins.go b/pkg/cloudprovider/plugins.go index 0fc41f5eaf8..888532717aa 100644 --- a/pkg/cloudprovider/plugins.go +++ b/pkg/cloudprovider/plugins.go @@ -60,18 +60,6 @@ func IsCloudProvider(name string) bool { return found } -// CloudProviders returns the name of all registered cloud providers in a -// string slice -func CloudProviders() []string { - names := []string{} - providersMutex.Lock() - defer providersMutex.Unlock() - for name := range providers { - names = append(names, name) - } - return names -} - // GetCloudProvider creates an instance of the named cloud provider, or nil if // the name is unknown. The error return is only used if the named provider // was known but failed to initialize. The config parameter specifies the diff --git a/pkg/cloudprovider/providers/aws/BUILD b/pkg/cloudprovider/providers/aws/BUILD index a475be11eba..7fb2d5e4eb5 100644 --- a/pkg/cloudprovider/providers/aws/BUILD +++ b/pkg/cloudprovider/providers/aws/BUILD @@ -44,14 +44,21 @@ go_library( "//vendor/github.com/aws/aws-sdk-go/service/autoscaling:go_default_library", "//vendor/github.com/aws/aws-sdk-go/service/ec2:go_default_library", "//vendor/github.com/aws/aws-sdk-go/service/elb:go_default_library", + "//vendor/github.com/aws/aws-sdk-go/service/elbv2:go_default_library", "//vendor/github.com/aws/aws-sdk-go/service/kms:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", "//vendor/gopkg.in/gcfg.v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", + "//vendor/k8s.io/client-go/tools/record:go_default_library", ], ) @@ -66,8 +73,8 @@ go_test( "retry_handler_test.go", "tags_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/aws", - library = ":go_default_library", deps = [ "//pkg/kubelet/apis:go_default_library", "//vendor/github.com/aws/aws-sdk-go/aws:go_default_library", @@ -75,6 +82,7 @@ go_test( "//vendor/github.com/aws/aws-sdk-go/service/elb:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/github.com/stretchr/testify/mock:go_default_library", + "//vendor/github.com/stretchr/testify/require:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", diff --git a/pkg/cloudprovider/providers/aws/OWNERS b/pkg/cloudprovider/providers/aws/OWNERS index 294b5c5eb55..905c5f972a2 100644 --- a/pkg/cloudprovider/providers/aws/OWNERS +++ b/pkg/cloudprovider/providers/aws/OWNERS @@ -6,3 +6,4 @@ reviewers: - jsafrane - justinsb - zmerlynn +- chrislovecnm diff --git a/pkg/cloudprovider/providers/aws/aws.go b/pkg/cloudprovider/providers/aws/aws.go index 65c360f7e09..2c2a953f117 100644 --- a/pkg/cloudprovider/providers/aws/aws.go +++ b/pkg/cloudprovider/providers/aws/aws.go @@ -38,16 +38,23 @@ import ( "github.com/aws/aws-sdk-go/service/autoscaling" "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/elb" + "github.com/aws/aws-sdk-go/service/elbv2" "github.com/aws/aws-sdk-go/service/kms" "github.com/golang/glog" "github.com/prometheus/client_golang/prometheus" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/record" "path" "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/kubernetes/scheme" + v1core "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/kubernetes/pkg/api/v1/service" "k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/controller" @@ -56,6 +63,18 @@ import ( volumeutil "k8s.io/kubernetes/pkg/volume/util" ) +// NLBHealthCheckRuleDescription is the comment used on a security group rule to +// indicate that it is used for health checks +const NLBHealthCheckRuleDescription = "kubernetes.io/rule/nlb/health" + +// NLBClientRuleDescription is the comment used on a security group rule to +// indicate that it is used for client traffic +const NLBClientRuleDescription = "kubernetes.io/rule/nlb/client" + +// NLBMtuDiscoveryRuleDescription is the comment used on a security group rule +// to indicate that it is used for mtu discovery +const NLBMtuDiscoveryRuleDescription = "kubernetes.io/rule/nlb/mtu" + // ProviderName is the name of this cloud provider. const ProviderName = "aws" @@ -71,6 +90,11 @@ const TagNameSubnetInternalELB = "kubernetes.io/role/internal-elb" // it should be used for internet ELBs const TagNameSubnetPublicELB = "kubernetes.io/role/elb" +// ServiceAnnotationLoadBalancerType is the annotation used on the service +// to indicate what type of Load Balancer we want. Right now, the only accepted +// value is "nlb" +const ServiceAnnotationLoadBalancerType = "service.beta.kubernetes.io/aws-load-balancer-type" + // ServiceAnnotationLoadBalancerInternal is the annotation used on the service // to indicate that we want an internal ELB. const ServiceAnnotationLoadBalancerInternal = "service.beta.kubernetes.io/aws-load-balancer-internal" @@ -129,6 +153,11 @@ const ServiceAnnotationLoadBalancerCertificate = "service.beta.kubernetes.io/aws // listeners. Defaults to '*' (all). const ServiceAnnotationLoadBalancerSSLPorts = "service.beta.kubernetes.io/aws-load-balancer-ssl-ports" +// ServiceAnnotationLoadBalancerSSLNegotiationPolicy is the annotation used on +// the service to specify a SSL negotiation settings for the HTTPS/SSL listeners +// of your load balancer. Defaults to AWS's default +const ServiceAnnotationLoadBalancerSSLNegotiationPolicy = "service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy" + // ServiceAnnotationLoadBalancerBEProtocol is the annotation used on the service // to specify the protocol spoken by the backend (pod) behind a listener. // If `http` (default) or `https`, an HTTPS listener that terminates the @@ -144,6 +173,31 @@ const ServiceAnnotationLoadBalancerBEProtocol = "service.beta.kubernetes.io/aws- // For example: "Key1=Val1,Key2=Val2,KeyNoVal1=,KeyNoVal2" const ServiceAnnotationLoadBalancerAdditionalTags = "service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags" +// ServiceAnnotationLoadBalancerHCHealthyThreshold is the annotation used on +// the service to specify the number of successive successful health checks +// required for a backend to be considered healthy for traffic. +const ServiceAnnotationLoadBalancerHCHealthyThreshold = "service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold" + +// ServiceAnnotationLoadBalancerHCUnhealthyThreshold is the annotation used +// on the service to specify the number of unsuccessful health checks +// required for a backend to be considered unhealthy for traffic +const ServiceAnnotationLoadBalancerHCUnhealthyThreshold = "service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold" + +// ServiceAnnotationLoadBalancerHCTimeout is the annotation used on the +// service to specify, in seconds, how long to wait before marking a health +// check as failed. +const ServiceAnnotationLoadBalancerHCTimeout = "service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout" + +// ServiceAnnotationLoadBalancerHCInterval is the annotation used on the +// service to specify, in seconds, the interval between health checks. +const ServiceAnnotationLoadBalancerHCInterval = "service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval" + +// Event key when a volume is stuck on attaching state when being attached to a volume +const volumeAttachmentStuck = "VolumeAttachmentStuck" + +// Indicates that a node has volumes stuck in attaching state and hence it is not fit for scheduling more pods +const nodeWithImpairedVolumes = "NodeWithImpairedVolumes" + const ( // volumeAttachmentConsecutiveErrorLimit is the number of consecutive errors we will ignore when waiting for a volume to attach/detach volumeAttachmentStatusConsecutiveErrorLimit = 10 @@ -193,11 +247,6 @@ const MaxReadThenCreateRetries = 30 // need hardcoded defaults. const DefaultVolumeType = "gp2" -// DefaultMaxEBSVolumes is the limit for volumes attached to an instance. -// Amazon recommends no more than 40; the system root volume uses at least one. -// See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/volume_limits.html#linux-specific-volume-limits -const DefaultMaxEBSVolumes = 39 - // Used to call RecognizeWellKnownRegions just once var once sync.Once @@ -208,6 +257,7 @@ var _ cloudprovider.PVLabeler = (*Cloud)(nil) type Services interface { Compute(region string) (EC2, error) LoadBalancing(region string) (ELB, error) + LoadBalancingV2(region string) (ELBV2, error) Autoscaling(region string) (ASG, error) Metadata() (EC2Metadata, error) KeyManagement(region string) (KMS, error) @@ -231,6 +281,10 @@ type EC2 interface { // Delete an EBS volume DeleteVolume(*ec2.DeleteVolumeInput) (*ec2.DeleteVolumeOutput, error) + ModifyVolume(*ec2.ModifyVolumeInput) (*ec2.ModifyVolumeOutput, error) + + DescribeVolumeModifications(*ec2.DescribeVolumesModificationsInput) ([]*ec2.VolumeModification, error) + DescribeSecurityGroups(request *ec2.DescribeSecurityGroupsInput) ([]*ec2.SecurityGroup, error) CreateSecurityGroup(*ec2.CreateSecurityGroupInput) (*ec2.CreateSecurityGroupOutput, error) @@ -248,6 +302,8 @@ type EC2 interface { DeleteRoute(request *ec2.DeleteRouteInput) (*ec2.DeleteRouteOutput, error) ModifyInstanceAttribute(request *ec2.ModifyInstanceAttributeInput) (*ec2.ModifyInstanceAttributeOutput, error) + + DescribeVpcs(input *ec2.DescribeVpcsInput) (*ec2.DescribeVpcsOutput, error) } // ELB is a simple pass-through of AWS' ELB client interface, which allows for testing @@ -255,10 +311,13 @@ type ELB interface { CreateLoadBalancer(*elb.CreateLoadBalancerInput) (*elb.CreateLoadBalancerOutput, error) DeleteLoadBalancer(*elb.DeleteLoadBalancerInput) (*elb.DeleteLoadBalancerOutput, error) DescribeLoadBalancers(*elb.DescribeLoadBalancersInput) (*elb.DescribeLoadBalancersOutput, error) + AddTags(*elb.AddTagsInput) (*elb.AddTagsOutput, error) RegisterInstancesWithLoadBalancer(*elb.RegisterInstancesWithLoadBalancerInput) (*elb.RegisterInstancesWithLoadBalancerOutput, error) DeregisterInstancesFromLoadBalancer(*elb.DeregisterInstancesFromLoadBalancerInput) (*elb.DeregisterInstancesFromLoadBalancerOutput, error) CreateLoadBalancerPolicy(*elb.CreateLoadBalancerPolicyInput) (*elb.CreateLoadBalancerPolicyOutput, error) SetLoadBalancerPoliciesForBackendServer(*elb.SetLoadBalancerPoliciesForBackendServerInput) (*elb.SetLoadBalancerPoliciesForBackendServerOutput, error) + SetLoadBalancerPoliciesOfListener(input *elb.SetLoadBalancerPoliciesOfListenerInput) (*elb.SetLoadBalancerPoliciesOfListenerOutput, error) + DescribeLoadBalancerPolicies(input *elb.DescribeLoadBalancerPoliciesInput) (*elb.DescribeLoadBalancerPoliciesOutput, error) DetachLoadBalancerFromSubnets(*elb.DetachLoadBalancerFromSubnetsInput) (*elb.DetachLoadBalancerFromSubnetsOutput, error) AttachLoadBalancerToSubnets(*elb.AttachLoadBalancerToSubnetsInput) (*elb.AttachLoadBalancerToSubnetsOutput, error) @@ -274,6 +333,38 @@ type ELB interface { ModifyLoadBalancerAttributes(*elb.ModifyLoadBalancerAttributesInput) (*elb.ModifyLoadBalancerAttributesOutput, error) } +// ELBV2 is a simple pass-through of AWS' ELBV2 client interface, which allows for testing +type ELBV2 interface { + AddTags(input *elbv2.AddTagsInput) (*elbv2.AddTagsOutput, error) + + CreateLoadBalancer(*elbv2.CreateLoadBalancerInput) (*elbv2.CreateLoadBalancerOutput, error) + DescribeLoadBalancers(*elbv2.DescribeLoadBalancersInput) (*elbv2.DescribeLoadBalancersOutput, error) + DeleteLoadBalancer(*elbv2.DeleteLoadBalancerInput) (*elbv2.DeleteLoadBalancerOutput, error) + + ModifyLoadBalancerAttributes(*elbv2.ModifyLoadBalancerAttributesInput) (*elbv2.ModifyLoadBalancerAttributesOutput, error) + DescribeLoadBalancerAttributes(*elbv2.DescribeLoadBalancerAttributesInput) (*elbv2.DescribeLoadBalancerAttributesOutput, error) + + CreateTargetGroup(*elbv2.CreateTargetGroupInput) (*elbv2.CreateTargetGroupOutput, error) + DescribeTargetGroups(*elbv2.DescribeTargetGroupsInput) (*elbv2.DescribeTargetGroupsOutput, error) + ModifyTargetGroup(*elbv2.ModifyTargetGroupInput) (*elbv2.ModifyTargetGroupOutput, error) + DeleteTargetGroup(*elbv2.DeleteTargetGroupInput) (*elbv2.DeleteTargetGroupOutput, error) + + DescribeTargetHealth(input *elbv2.DescribeTargetHealthInput) (*elbv2.DescribeTargetHealthOutput, error) + + DescribeTargetGroupAttributes(*elbv2.DescribeTargetGroupAttributesInput) (*elbv2.DescribeTargetGroupAttributesOutput, error) + ModifyTargetGroupAttributes(*elbv2.ModifyTargetGroupAttributesInput) (*elbv2.ModifyTargetGroupAttributesOutput, error) + + RegisterTargets(*elbv2.RegisterTargetsInput) (*elbv2.RegisterTargetsOutput, error) + DeregisterTargets(*elbv2.DeregisterTargetsInput) (*elbv2.DeregisterTargetsOutput, error) + + CreateListener(*elbv2.CreateListenerInput) (*elbv2.CreateListenerOutput, error) + DescribeListeners(*elbv2.DescribeListenersInput) (*elbv2.DescribeListenersOutput, error) + DeleteListener(*elbv2.DeleteListenerInput) (*elbv2.DeleteListenerOutput, error) + ModifyListener(*elbv2.ModifyListenerInput) (*elbv2.ModifyListenerOutput, error) + + WaitUntilLoadBalancersDeleted(*elbv2.DescribeLoadBalancersInput) error +} + // ASG is a simple pass-through of the Autoscaling client interface, which // allows for testing. type ASG interface { @@ -362,6 +453,9 @@ type Volumes interface { // Check if disks specified in argument map are still attached to their respective nodes. DisksAreAttached(map[types.NodeName][]KubernetesVolumeID) (map[types.NodeName]map[KubernetesVolumeID]bool, error) + + // Expand the disk to new size + ResizeDisk(diskName KubernetesVolumeID, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) } // InstanceGroups is an interface for managing cloud-managed instance groups / autoscaling instance groups @@ -383,6 +477,7 @@ type InstanceGroupInfo interface { type Cloud struct { ec2 EC2 elb ELB + elbv2 ELBV2 asg ASG kms KMS metadata EC2Metadata @@ -398,6 +493,11 @@ type Cloud struct { instanceCache instanceCache + clientBuilder controller.ControllerClientBuilder + kubeClient clientset.Interface + eventBroadcaster record.EventBroadcaster + eventRecorder record.EventRecorder + // We keep an active list of devices we have assigned but not yet // attached, to avoid a race condition where we assign a device mapping // and then get a second request before we attach the volume @@ -563,6 +663,20 @@ func (p *awsSDKProvider) LoadBalancing(regionName string) (ELB, error) { return elbClient, nil } +func (p *awsSDKProvider) LoadBalancingV2(regionName string) (ELBV2, error) { + awsConfig := &aws.Config{ + Region: ®ionName, + Credentials: p.creds, + } + awsConfig = awsConfig.WithCredentialsChainVerboseErrors(true) + + elbClient := elbv2.New(session.New(awsConfig)) + + p.addHandlers(regionName, &elbClient.Handlers) + + return elbClient, nil +} + func (p *awsSDKProvider) Autoscaling(regionName string) (ASG, error) { awsConfig := &aws.Config{ Region: ®ionName, @@ -722,6 +836,36 @@ func (s *awsSdkEC2) DeleteVolume(request *ec2.DeleteVolumeInput) (*ec2.DeleteVol return resp, err } +func (s *awsSdkEC2) ModifyVolume(request *ec2.ModifyVolumeInput) (*ec2.ModifyVolumeOutput, error) { + requestTime := time.Now() + resp, err := s.ec2.ModifyVolume(request) + timeTaken := time.Since(requestTime).Seconds() + recordAwsMetric("modify_volume", timeTaken, err) + return resp, err +} + +func (s *awsSdkEC2) DescribeVolumeModifications(request *ec2.DescribeVolumesModificationsInput) ([]*ec2.VolumeModification, error) { + requestTime := time.Now() + results := []*ec2.VolumeModification{} + var nextToken *string + for { + resp, err := s.ec2.DescribeVolumesModifications(request) + if err != nil { + recordAwsMetric("describe_volume_modification", 0, err) + return nil, fmt.Errorf("error listing volume modifictions : %v", err) + } + results = append(results, resp.VolumesModifications...) + nextToken = resp.NextToken + if aws.StringValue(nextToken) == "" { + break + } + request.NextToken = nextToken + } + timeTaken := time.Since(requestTime).Seconds() + recordAwsMetric("describe_volume_modification", timeTaken, nil) + return results, nil +} + func (s *awsSdkEC2) DescribeSubnets(request *ec2.DescribeSubnetsInput) ([]*ec2.Subnet, error) { // Subnets are not paged response, err := s.ec2.DescribeSubnets(request) @@ -776,6 +920,10 @@ func (s *awsSdkEC2) ModifyInstanceAttribute(request *ec2.ModifyInstanceAttribute return s.ec2.ModifyInstanceAttribute(request) } +func (s *awsSdkEC2) DescribeVpcs(request *ec2.DescribeVpcsInput) (*ec2.DescribeVpcsOutput, error) { + return s.ec2.DescribeVpcs(request) +} + func init() { registerMetrics() cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) { @@ -889,6 +1037,11 @@ func newAWSCloud(config io.Reader, awsServices Services) (*Cloud, error) { return nil, fmt.Errorf("error creating AWS ELB client: %v", err) } + elbv2, err := awsServices.LoadBalancingV2(regionName) + if err != nil { + return nil, fmt.Errorf("error creating AWS ELBV2 client: %v", err) + } + asg, err := awsServices.Autoscaling(regionName) if err != nil { return nil, fmt.Errorf("error creating AWS autoscaling client: %v", err) @@ -902,6 +1055,7 @@ func newAWSCloud(config io.Reader, awsServices Services) (*Cloud, error) { awsCloud := &Cloud{ ec2: ec2, elb: elb, + elbv2: elbv2, asg: asg, metadata: metadata, kms: kms, @@ -957,7 +1111,14 @@ func newAWSCloud(config io.Reader, awsServices Services) (*Cloud, error) { } // Initialize passes a Kubernetes clientBuilder interface to the cloud provider -func (c *Cloud) Initialize(clientBuilder controller.ControllerClientBuilder) {} +func (c *Cloud) Initialize(clientBuilder controller.ControllerClientBuilder) { + c.clientBuilder = clientBuilder + c.kubeClient = clientBuilder.ClientOrDie("aws-cloud-provider") + c.eventBroadcaster = record.NewBroadcaster() + c.eventBroadcaster.StartLogging(glog.Infof) + c.eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(c.kubeClient.CoreV1().RESTClient()).Events("")}) + c.eventRecorder = c.eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "aws-cloud-provider"}) +} // Clusters returns the list of clusters. func (c *Cloud) Clusters() (cloudprovider.Clusters, bool) { @@ -969,11 +1130,6 @@ func (c *Cloud) ProviderName() string { return ProviderName } -// ScrubDNS filters DNS settings for pods. -func (c *Cloud) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) { - return nameservers, searches -} - // LoadBalancer returns an implementation of LoadBalancer for Amazon Web Services. func (c *Cloud) LoadBalancer() (cloudprovider.LoadBalancer, bool) { return c, true @@ -1039,7 +1195,7 @@ func (c *Cloud) NodeAddresses(name types.NodeName) ([]v1.NodeAddress, error) { if err != nil || len(internalDNS) == 0 { //TODO: It would be nice to be able to determine the reason for the failure, // but the AWS client masks all failures with the same error description. - glog.V(2).Info("Could not determine private DNS from AWS metadata.") + glog.V(4).Info("Could not determine private DNS from AWS metadata.") } else { addresses = append(addresses, v1.NodeAddress{Type: v1.NodeInternalDNS, Address: internalDNS}) } @@ -1048,7 +1204,7 @@ func (c *Cloud) NodeAddresses(name types.NodeName) ([]v1.NodeAddress, error) { if err != nil || len(externalDNS) == 0 { //TODO: It would be nice to be able to determine the reason for the failure, // but the AWS client masks all failures with the same error description. - glog.V(2).Info("Could not determine public DNS from AWS metadata.") + glog.V(4).Info("Could not determine public DNS from AWS metadata.") } else { addresses = append(addresses, v1.NodeAddress{Type: v1.NodeExternalDNS, Address: externalDNS}) } @@ -1152,7 +1308,33 @@ func (c *Cloud) ExternalID(nodeName types.NodeName) (string, error) { // InstanceExistsByProviderID returns true if the instance with the given provider id still exists and is running. // If false is returned with no error, the instance will be immediately deleted by the cloud controller manager. func (c *Cloud) InstanceExistsByProviderID(providerID string) (bool, error) { - return false, cloudprovider.NotImplemented + instanceID, err := kubernetesInstanceID(providerID).mapToAWSInstanceID() + if err != nil { + return false, err + } + + request := &ec2.DescribeInstancesInput{ + InstanceIds: []*string{instanceID.awsString()}, + } + + instances, err := c.ec2.DescribeInstances(request) + if err != nil { + return false, err + } + if len(instances) == 0 { + return false, nil + } + if len(instances) > 1 { + return false, fmt.Errorf("multiple instances found for instance: %s", instanceID) + } + + state := instances[0].State.Name + if *state != "running" { + glog.Warningf("the instance %s is not running", instanceID) + return false, nil + } + + return true, nil } // InstanceID returns the cloud provider ID of the node with the specified nodeName. @@ -1470,7 +1652,7 @@ type awsDisk struct { } func newAWSDisk(aws *Cloud, name KubernetesVolumeID) (*awsDisk, error) { - awsID, err := name.mapToAWSVolumeID() + awsID, err := name.MapToAWSVolumeID() if err != nil { return nil, err } @@ -1499,6 +1681,87 @@ func (d *awsDisk) describeVolume() (*ec2.Volume, error) { return volumes[0], nil } +func (d *awsDisk) describeVolumeModification() (*ec2.VolumeModification, error) { + volumeID := d.awsID + request := &ec2.DescribeVolumesModificationsInput{ + VolumeIds: []*string{volumeID.awsString()}, + } + volumeMods, err := d.ec2.DescribeVolumeModifications(request) + + if err != nil { + return nil, fmt.Errorf("error describing volume modification %s with %v", volumeID, err) + } + + if len(volumeMods) == 0 { + return nil, fmt.Errorf("no volume modifications found for %s", volumeID) + } + lastIndex := len(volumeMods) - 1 + return volumeMods[lastIndex], nil +} + +func (d *awsDisk) modifyVolume(requestGiB int64) (int64, error) { + volumeID := d.awsID + + request := &ec2.ModifyVolumeInput{ + VolumeId: volumeID.awsString(), + Size: aws.Int64(requestGiB), + } + output, err := d.ec2.ModifyVolume(request) + if err != nil { + modifyError := fmt.Errorf("AWS modifyVolume failed for %s with %v", volumeID, err) + return requestGiB, modifyError + } + + volumeModification := output.VolumeModification + + if aws.StringValue(volumeModification.ModificationState) == ec2.VolumeModificationStateCompleted { + return aws.Int64Value(volumeModification.TargetSize), nil + } + + backoff := wait.Backoff{ + Duration: 1 * time.Second, + Factor: 2, + Steps: 10, + } + + checkForResize := func() (bool, error) { + volumeModification, err := d.describeVolumeModification() + + if err != nil { + return false, err + } + + if aws.StringValue(volumeModification.ModificationState) == ec2.VolumeModificationStateCompleted { + return true, nil + } + return false, nil + } + waitWithErr := wait.ExponentialBackoff(backoff, checkForResize) + return requestGiB, waitWithErr +} + +// applyUnSchedulableTaint applies a unschedulable taint to a node after verifying +// if node has become unusable because of volumes getting stuck in attaching state. +func (c *Cloud) applyUnSchedulableTaint(nodeName types.NodeName, reason string) { + node, fetchErr := c.kubeClient.CoreV1().Nodes().Get(string(nodeName), metav1.GetOptions{}) + if fetchErr != nil { + glog.Errorf("Error fetching node %s with %v", nodeName, fetchErr) + return + } + + taint := &v1.Taint{ + Key: nodeWithImpairedVolumes, + Value: "true", + Effect: v1.TaintEffectNoSchedule, + } + err := controller.AddOrUpdateTaintOnNode(c.kubeClient, string(nodeName), taint) + if err != nil { + glog.Errorf("Error applying taint to node %s with error %v", nodeName, err) + return + } + c.eventRecorder.Eventf(node, v1.EventTypeWarning, volumeAttachmentStuck, reason) +} + // waitForAttachmentStatus polls until the attachment status is the expected value // On success, it returns the last attachment state. func (d *awsDisk) waitForAttachmentStatus(status string) (*ec2.VolumeAttachment, error) { @@ -1690,6 +1953,15 @@ func (c *Cloud) AttachDisk(diskName KubernetesVolumeID, nodeName types.NodeName, ec2Device := "/dev/xvd" + string(mountDevice) if !alreadyAttached { + available, err := c.checkIfAvailable(disk, "attaching", awsInstance.awsID) + if err != nil { + glog.Error(err) + } + + if !available { + attachEnded = true + return "", err + } request := &ec2.AttachVolumeInput{ Device: aws.String(ec2Device), InstanceId: aws.String(awsInstance.awsID), @@ -1709,7 +1981,11 @@ func (c *Cloud) AttachDisk(diskName KubernetesVolumeID, nodeName types.NodeName, } attachment, err := disk.waitForAttachmentStatus("attached") + if err != nil { + if err == wait.ErrWaitTimeout { + c.applyUnSchedulableTaint(nodeName, "Volume stuck in attaching state - node needs reboot to fix impaired state.") + } return "", err } @@ -1735,26 +2011,23 @@ func (c *Cloud) AttachDisk(diskName KubernetesVolumeID, nodeName types.NodeName, // DetachDisk implements Volumes.DetachDisk func (c *Cloud) DetachDisk(diskName KubernetesVolumeID, nodeName types.NodeName) (string, error) { - disk, err := newAWSDisk(c, diskName) - if err != nil { + diskInfo, attached, err := c.checkIfAttachedToNode(diskName, nodeName) + if diskInfo == nil { return "", err } - awsInstance, info, err := c.getFullInstance(nodeName) - if err != nil { - if err == cloudprovider.InstanceNotFound { - // If instance no longer exists, safe to assume volume is not attached. - glog.Warningf( - "Instance %q does not exist. DetachDisk will assume disk %q is not attached to it.", - nodeName, - diskName) - return "", nil - } - - return "", err + if !attached && diskInfo.ec2Instance != nil { + glog.Warningf("DetachDisk %s called for node %s but volume is attached to node %s", diskName, nodeName, diskInfo.nodeName) + return "", nil } - mountDevice, alreadyAttached, err := c.getMountDevice(awsInstance, info, disk.awsID, false) + if !attached { + return "", nil + } + + awsInstance := newAWSInstance(c.ec2, diskInfo.ec2Instance) + + mountDevice, alreadyAttached, err := c.getMountDevice(awsInstance, diskInfo.ec2Instance, diskInfo.disk.awsID, false) if err != nil { return "", err } @@ -1766,18 +2039,19 @@ func (c *Cloud) DetachDisk(diskName KubernetesVolumeID, nodeName types.NodeName) request := ec2.DetachVolumeInput{ InstanceId: &awsInstance.awsID, - VolumeId: disk.awsID.awsString(), + VolumeId: diskInfo.disk.awsID.awsString(), } response, err := c.ec2.DetachVolume(&request) if err != nil { - return "", fmt.Errorf("error detaching EBS volume %q from %q: %q", disk.awsID, awsInstance.awsID, err) + return "", fmt.Errorf("error detaching EBS volume %q from %q: %q", diskInfo.disk.awsID, awsInstance.awsID, err) } + if response == nil { return "", errors.New("no response from DetachVolume") } - attachment, err := disk.waitForAttachmentStatus("detached") + attachment, err := diskInfo.disk.waitForAttachmentStatus("detached") if err != nil { return "", err } @@ -1790,7 +2064,7 @@ func (c *Cloud) DetachDisk(diskName KubernetesVolumeID, nodeName types.NodeName) } if mountDevice != "" { - c.endAttaching(awsInstance, disk.awsID, mountDevice) + c.endAttaching(awsInstance, diskInfo.disk.awsID, mountDevice) // We don't check the return value - we don't really expect the attachment to have been // in progress, though it might have been } @@ -1922,9 +2196,59 @@ func (c *Cloud) DeleteDisk(volumeName KubernetesVolumeID) (bool, error) { if err != nil { return false, err } + available, err := c.checkIfAvailable(awsDisk, "deleting", "") + if err != nil { + glog.Error(err) + } + + if !available { + return false, err + } + return awsDisk.deleteVolume() } +func (c *Cloud) checkIfAvailable(disk *awsDisk, opName string, instance string) (bool, error) { + info, err := disk.describeVolume() + + if err != nil { + glog.Errorf("Error describing volume %q: %q", disk.awsID, err) + // if for some reason we can not describe volume we will return error + return false, err + } + + volumeState := aws.StringValue(info.State) + opError := fmt.Sprintf("Error %s EBS volume %q", opName, disk.awsID) + if len(instance) != 0 { + opError = fmt.Sprintf("%q to instance %q", opError, instance) + } + + // Only available volumes can be attached or deleted + if volumeState != "available" { + // Volume is attached somewhere else and we can not attach it here + if len(info.Attachments) > 0 { + attachment := info.Attachments[0] + instanceId := aws.StringValue(attachment.InstanceId) + attachedInstance, ierr := c.getInstanceByID(instanceId) + attachErr := fmt.Sprintf("%s since volume is currently attached to %q", opError, instanceId) + if ierr != nil { + glog.Error(attachErr) + return false, errors.New(attachErr) + } + devicePath := aws.StringValue(attachment.Device) + nodeName := mapInstanceToNodeName(attachedInstance) + + danglingErr := volumeutil.NewDanglingError(attachErr, nodeName, devicePath) + return false, danglingErr + } + + attachErr := fmt.Errorf("%s since volume is in %q state", opError, volumeState) + return false, attachErr + } + + return true, nil +} + func (c *Cloud) GetLabelsForVolume(pv *v1.PersistentVolume) (map[string]string, error) { // Ignore any volumes that are being provisioned if pv.Spec.AWSElasticBlockStore.VolumeID == volume.ProvisionedVolumeName { @@ -1984,32 +2308,12 @@ func (c *Cloud) GetDiskPath(volumeName KubernetesVolumeID) (string, error) { // DiskIsAttached implements Volumes.DiskIsAttached func (c *Cloud) DiskIsAttached(diskName KubernetesVolumeID, nodeName types.NodeName) (bool, error) { - _, instance, err := c.getFullInstance(nodeName) - if err != nil { - if err == cloudprovider.InstanceNotFound { - // If instance no longer exists, safe to assume volume is not attached. - glog.Warningf( - "Instance %q does not exist. DiskIsAttached will assume disk %q is not attached to it.", - nodeName, - diskName) - return false, nil - } - - return false, err + diskInfo, attached, err := c.checkIfAttachedToNode(diskName, nodeName) + if diskInfo == nil { + return true, err } - diskID, err := diskName.mapToAWSVolumeID() - if err != nil { - return false, fmt.Errorf("error mapping volume spec %q to aws id: %v", diskName, err) - } - - for _, blockDevice := range instance.BlockDeviceMappings { - id := awsVolumeID(aws.StringValue(blockDevice.Ebs.VolumeId)) - if id == diskID { - return true, nil - } - } - return false, nil + return attached, nil } func (c *Cloud) DisksAreAttached(nodeDisks map[types.NodeName][]KubernetesVolumeID) (map[types.NodeName]map[KubernetesVolumeID]bool, error) { @@ -2062,7 +2366,7 @@ func (c *Cloud) DisksAreAttached(nodeDisks map[types.NodeName][]KubernetesVolume idToDiskName := make(map[awsVolumeID]KubernetesVolumeID) for _, diskName := range diskNames { - volumeID, err := diskName.mapToAWSVolumeID() + volumeID, err := diskName.MapToAWSVolumeID() if err != nil { return nil, fmt.Errorf("error mapping volume spec %q to aws id: %v", diskName, err) } @@ -2082,6 +2386,37 @@ func (c *Cloud) DisksAreAttached(nodeDisks map[types.NodeName][]KubernetesVolume return attached, nil } +func (c *Cloud) ResizeDisk( + diskName KubernetesVolumeID, + oldSize resource.Quantity, + newSize resource.Quantity) (resource.Quantity, error) { + awsDisk, err := newAWSDisk(c, diskName) + if err != nil { + return oldSize, err + } + + volumeInfo, err := awsDisk.describeVolume() + if err != nil { + descErr := fmt.Errorf("AWS.ResizeDisk Error describing volume %s with %v", diskName, err) + return oldSize, descErr + } + requestBytes := newSize.Value() + // AWS resizes in chunks of GiB (not GB) + requestGiB := volume.RoundUpSize(requestBytes, 1024*1024*1024) + newSizeQuant := resource.MustParse(fmt.Sprintf("%dGi", requestGiB)) + + // If disk already if of greater or equal size than requested we return + if aws.Int64Value(volumeInfo.Size) >= requestGiB { + return newSizeQuant, nil + } + _, err = awsDisk.modifyVolume(requestGiB) + + if err != nil { + return oldSize, err + } + return newSizeQuant, nil +} + // Gets the current load balancer state func (c *Cloud) describeLoadBalancer(name string) (*elb.LoadBalancerDescription, error) { request := &elb.DescribeLoadBalancersInput{} @@ -2107,6 +2442,53 @@ func (c *Cloud) describeLoadBalancer(name string) (*elb.LoadBalancerDescription, return ret, nil } +func (c *Cloud) addLoadBalancerTags(loadBalancerName string, requested map[string]string) error { + var tags []*elb.Tag + for k, v := range requested { + tag := &elb.Tag{ + Key: aws.String(k), + Value: aws.String(v), + } + tags = append(tags, tag) + } + + request := &elb.AddTagsInput{} + request.LoadBalancerNames = []*string{&loadBalancerName} + request.Tags = tags + + _, err := c.elb.AddTags(request) + if err != nil { + return fmt.Errorf("error adding tags to load balancer: %v", err) + } + return nil +} + +// Gets the current load balancer state +func (c *Cloud) describeLoadBalancerv2(name string) (*elbv2.LoadBalancer, error) { + request := &elbv2.DescribeLoadBalancersInput{ + Names: []*string{aws.String(name)}, + } + + response, err := c.elbv2.DescribeLoadBalancers(request) + if err != nil { + if awsError, ok := err.(awserr.Error); ok { + if awsError.Code() == elbv2.ErrCodeLoadBalancerNotFoundException { + return nil, nil + } + } + return nil, fmt.Errorf("Error describing load balancer: %q", err) + } + + // AWS will not return 2 load balancers with the same name _and_ type. + for i := range response.LoadBalancers { + if aws.StringValue(response.LoadBalancers[i].Type) == elbv2.LoadBalancerTypeEnumNetwork { + return response.LoadBalancers[i], nil + } + } + + return nil, fmt.Errorf("NLB '%s' could not be found", name) +} + // Retrieves instance's vpc id from metadata func (c *Cloud) findVPCID() (string, error) { macs, err := c.metadata.GetMetadata("network/interfaces/macs/") @@ -2796,6 +3178,8 @@ func (c *Cloud) EnsureLoadBalancer(clusterName string, apiService *v1.Service, n // Figure out what mappings we want on the load balancer listeners := []*elb.Listener{} + v2Mappings := []nlbPortMapping{} + portList := getPortSets(annotations[ServiceAnnotationLoadBalancerSSLPorts]) for _, port := range apiService.Spec.Ports { if port.Protocol != v1.ProtocolTCP { @@ -2805,6 +3189,17 @@ func (c *Cloud) EnsureLoadBalancer(clusterName string, apiService *v1.Service, n glog.Errorf("Ignoring port without NodePort defined: %v", port) continue } + + if isNLB(annotations) { + v2Mappings = append(v2Mappings, nlbPortMapping{ + FrontendPort: int64(port.Port), + TrafficPort: int64(port.NodePort), + // if externalTrafficPolicy == "Local", we'll override the + // health check later + HealthCheckPort: int64(port.NodePort), + HealthCheckProtocol: elbv2.ProtocolEnumTcp, + }) + } listener, err := buildListener(port, annotations, portList) if err != nil { return nil, err @@ -2833,6 +3228,69 @@ func (c *Cloud) EnsureLoadBalancer(clusterName string, apiService *v1.Service, n internalELB = true } + if isNLB(annotations) { + + if path, healthCheckNodePort := service.GetServiceHealthCheckPathPort(apiService); path != "" { + for i := range v2Mappings { + v2Mappings[i].HealthCheckPort = int64(healthCheckNodePort) + v2Mappings[i].HealthCheckPath = path + v2Mappings[i].HealthCheckProtocol = elbv2.ProtocolEnumHttp + } + } + + // Find the subnets that the ELB will live in + subnetIDs, err := c.findELBSubnets(internalELB) + if err != nil { + glog.Errorf("Error listing subnets in VPC: %q", err) + return nil, err + } + // Bail out early if there are no subnets + if len(subnetIDs) == 0 { + return nil, fmt.Errorf("could not find any suitable subnets for creating the ELB") + } + + loadBalancerName := cloudprovider.GetLoadBalancerName(apiService) + serviceName := types.NamespacedName{Namespace: apiService.Namespace, Name: apiService.Name} + + instanceIDs := []string{} + for id := range instances { + instanceIDs = append(instanceIDs, string(id)) + } + + v2LoadBalancer, err := c.ensureLoadBalancerv2( + serviceName, + loadBalancerName, + v2Mappings, + instanceIDs, + subnetIDs, + internalELB, + annotations, + ) + if err != nil { + return nil, err + } + + sourceRangeCidrs := []string{} + for cidr := range sourceRanges { + sourceRangeCidrs = append(sourceRangeCidrs, cidr) + } + if len(sourceRangeCidrs) == 0 { + sourceRangeCidrs = append(sourceRangeCidrs, "0.0.0.0/0") + } + + err = c.updateInstanceSecurityGroupsForNLB(v2Mappings, instances, loadBalancerName, sourceRangeCidrs) + if err != nil { + glog.Warningf("Error opening ingress rules for the load balancer to the instances: %q", err) + return nil, err + } + + // We don't have an `ensureLoadBalancerInstances()` function for elbv2 + // because `ensureLoadBalancerv2()` requires instance Ids + + // TODO: Wait for creation? + return v2toStatus(v2LoadBalancer), nil + } + // Determine if we need to set the Proxy protocol policy proxyProtocol := false proxyProtocolAnnotation := apiService.Annotations[ServiceAnnotationLoadBalancerProxyProtocol] @@ -3016,9 +3474,23 @@ func (c *Cloud) EnsureLoadBalancer(clusterName string, apiService *v1.Service, n return nil, err } + if sslPolicyName, ok := annotations[ServiceAnnotationLoadBalancerSSLNegotiationPolicy]; ok { + err := c.ensureSSLNegotiationPolicy(loadBalancer, sslPolicyName) + if err != nil { + return nil, err + } + + for _, port := range c.getLoadBalancerTLSPorts(loadBalancer) { + err := c.setSSLNegotiationPolicy(loadBalancerName, sslPolicyName, port) + if err != nil { + return nil, err + } + } + } + if path, healthCheckNodePort := service.GetServiceHealthCheckPathPort(apiService); path != "" { glog.V(4).Infof("service %v (%v) needs health checks on :%d%s)", apiService.Name, loadBalancerName, healthCheckNodePort, path) - err = c.ensureLoadBalancerHealthCheck(loadBalancer, "HTTP", healthCheckNodePort, path) + err = c.ensureLoadBalancerHealthCheck(loadBalancer, "HTTP", healthCheckNodePort, path, annotations) if err != nil { return nil, fmt.Errorf("Failed to ensure health check for localized service %v on node port %v: %q", loadBalancerName, healthCheckNodePort, err) } @@ -3034,7 +3506,7 @@ func (c *Cloud) EnsureLoadBalancer(clusterName string, apiService *v1.Service, n break } // there must be no path on TCP health check - err = c.ensureLoadBalancerHealthCheck(loadBalancer, "TCP", tcpHealthCheckPort, "") + err = c.ensureLoadBalancerHealthCheck(loadBalancer, "TCP", tcpHealthCheckPort, "", annotations) if err != nil { return nil, err } @@ -3063,6 +3535,18 @@ func (c *Cloud) EnsureLoadBalancer(clusterName string, apiService *v1.Service, n // GetLoadBalancer is an implementation of LoadBalancer.GetLoadBalancer func (c *Cloud) GetLoadBalancer(clusterName string, service *v1.Service) (*v1.LoadBalancerStatus, bool, error) { loadBalancerName := cloudprovider.GetLoadBalancerName(service) + + if isNLB(service.Annotations) { + lb, err := c.describeLoadBalancerv2(loadBalancerName) + if err != nil { + return nil, false, err + } + if lb == nil { + return nil, false, nil + } + return v2toStatus(lb), true, nil + } + lb, err := c.describeLoadBalancer(loadBalancerName) if err != nil { return nil, false, err @@ -3088,6 +3572,24 @@ func toStatus(lb *elb.LoadBalancerDescription) *v1.LoadBalancerStatus { return status } +func v2toStatus(lb *elbv2.LoadBalancer) *v1.LoadBalancerStatus { + status := &v1.LoadBalancerStatus{} + if lb == nil { + glog.Error("[BUG] v2toStatus got nil input, this is a Kubernetes bug, please report") + return status + } + + // We check for Active or Provisioning, the only successful statuses + if aws.StringValue(lb.DNSName) != "" && (aws.StringValue(lb.State.Code) == elbv2.LoadBalancerStateEnumActive || + aws.StringValue(lb.State.Code) == elbv2.LoadBalancerStateEnumProvisioning) { + var ingress v1.LoadBalancerIngress + ingress.Hostname = aws.StringValue(lb.DNSName) + status.Ingress = []v1.LoadBalancerIngress{ingress} + } + + return status +} + // Returns the first security group for an instance, or nil // We only create instances with one security group, so we don't expect multiple security groups. // However, if there are multiple security groups, we will choose the one tagged with our cluster filter. @@ -3293,6 +3795,139 @@ func (c *Cloud) updateInstanceSecurityGroupsForLoadBalancer(lb *elb.LoadBalancer // EnsureLoadBalancerDeleted implements LoadBalancer.EnsureLoadBalancerDeleted. func (c *Cloud) EnsureLoadBalancerDeleted(clusterName string, service *v1.Service) error { loadBalancerName := cloudprovider.GetLoadBalancerName(service) + + if isNLB(service.Annotations) { + lb, err := c.describeLoadBalancerv2(loadBalancerName) + if err != nil { + return err + } + if lb == nil { + glog.Info("Load balancer already deleted: ", loadBalancerName) + return nil + } + + // Delete the LoadBalancer and target groups + // + // Deleting a target group while associated with a load balancer will + // fail. We delete the loadbalancer first. This does leave the + // possibility of zombie target groups if DeleteLoadBalancer() fails + // + // * Get target groups for NLB + // * Delete Load Balancer + // * Delete target groups + // * Clean up SecurityGroupRules + { + + targetGroups, err := c.elbv2.DescribeTargetGroups( + &elbv2.DescribeTargetGroupsInput{LoadBalancerArn: lb.LoadBalancerArn}, + ) + if err != nil { + return fmt.Errorf("Error listing target groups before deleting load balancer: %q", err) + } + + _, err = c.elbv2.DeleteLoadBalancer( + &elbv2.DeleteLoadBalancerInput{LoadBalancerArn: lb.LoadBalancerArn}, + ) + if err != nil { + return fmt.Errorf("Error deleting load balancer %q: %v", loadBalancerName, err) + } + + for _, group := range targetGroups.TargetGroups { + _, err := c.elbv2.DeleteTargetGroup( + &elbv2.DeleteTargetGroupInput{TargetGroupArn: group.TargetGroupArn}, + ) + if err != nil { + return fmt.Errorf("Error deleting target groups after deleting load balancer: %q", err) + } + } + } + + { + var matchingGroups []*ec2.SecurityGroup + { + // Server side filter + describeRequest := &ec2.DescribeSecurityGroupsInput{} + filters := []*ec2.Filter{ + newEc2Filter("ip-permission.protocol", "tcp"), + } + describeRequest.Filters = c.tagging.addFilters(filters) + response, err := c.ec2.DescribeSecurityGroups(describeRequest) + if err != nil { + return fmt.Errorf("Error querying security groups for NLB: %q", err) + } + for _, sg := range response { + if !c.tagging.hasClusterTag(sg.Tags) { + continue + } + matchingGroups = append(matchingGroups, sg) + } + + // client-side filter out groups that don't have IP Rules we've + // annotated for this service + matchingGroups = filterForIPRangeDescription(matchingGroups, loadBalancerName) + } + + { + clientRule := fmt.Sprintf("%s=%s", NLBClientRuleDescription, loadBalancerName) + mtuRule := fmt.Sprintf("%s=%s", NLBMtuDiscoveryRuleDescription, loadBalancerName) + healthRule := fmt.Sprintf("%s=%s", NLBHealthCheckRuleDescription, loadBalancerName) + + for i := range matchingGroups { + removes := []*ec2.IpPermission{} + for j := range matchingGroups[i].IpPermissions { + + v4rangesToRemove := []*ec2.IpRange{} + v6rangesToRemove := []*ec2.Ipv6Range{} + + // Find IpPermission that contains k8s description + // If we removed the whole IpPermission, it could contain other non-k8s specified ranges + for k := range matchingGroups[i].IpPermissions[j].IpRanges { + description := aws.StringValue(matchingGroups[i].IpPermissions[j].IpRanges[k].Description) + if description == clientRule || description == mtuRule || description == healthRule { + v4rangesToRemove = append(v4rangesToRemove, matchingGroups[i].IpPermissions[j].IpRanges[k]) + } + } + + // Find IpPermission that contains k8s description + // If we removed the whole IpPermission, it could contain other non-k8s specified rangesk + for k := range matchingGroups[i].IpPermissions[j].Ipv6Ranges { + description := aws.StringValue(matchingGroups[i].IpPermissions[j].Ipv6Ranges[k].Description) + if description == clientRule || description == mtuRule || description == healthRule { + v6rangesToRemove = append(v6rangesToRemove, matchingGroups[i].IpPermissions[j].Ipv6Ranges[k]) + } + } + + if len(v4rangesToRemove) > 0 || len(v6rangesToRemove) > 0 { + // create a new *IpPermission to not accidentally remove UserIdGroupPairs + removedPermission := &ec2.IpPermission{ + FromPort: matchingGroups[i].IpPermissions[j].FromPort, + IpProtocol: matchingGroups[i].IpPermissions[j].IpProtocol, + IpRanges: v4rangesToRemove, + Ipv6Ranges: v6rangesToRemove, + ToPort: matchingGroups[i].IpPermissions[j].ToPort, + } + removes = append(removes, removedPermission) + } + + } + if len(removes) > 0 { + changed, err := c.removeSecurityGroupIngress(aws.StringValue(matchingGroups[i].GroupId), removes) + if err != nil { + return err + } + if !changed { + glog.Warning("Revoking ingress was not needed; concurrent change? groupId=", *matchingGroups[i].GroupId) + } + } + + } + + } + + } + return nil + } + lb, err := c.describeLoadBalancer(loadBalancerName) if err != nil { return err @@ -3398,6 +4033,17 @@ func (c *Cloud) UpdateLoadBalancer(clusterName string, service *v1.Service, node } loadBalancerName := cloudprovider.GetLoadBalancerName(service) + if isNLB(service.Annotations) { + lb, err := c.describeLoadBalancerv2(loadBalancerName) + if err != nil { + return err + } + if lb == nil { + return fmt.Errorf("Load balancer not found") + } + _, err = c.EnsureLoadBalancer(clusterName, service, nodes) + return err + } lb, err := c.describeLoadBalancer(loadBalancerName) if err != nil { return err @@ -3407,6 +4053,19 @@ func (c *Cloud) UpdateLoadBalancer(clusterName string, service *v1.Service, node return fmt.Errorf("Load balancer not found") } + if sslPolicyName, ok := service.Annotations[ServiceAnnotationLoadBalancerSSLNegotiationPolicy]; ok { + err := c.ensureSSLNegotiationPolicy(lb, sslPolicyName) + if err != nil { + return err + } + for _, port := range c.getLoadBalancerTLSPorts(lb) { + err := c.setSSLNegotiationPolicy(loadBalancerName, sslPolicyName, port) + if err != nil { + return err + } + } + } + err = c.ensureLoadBalancerInstances(aws.StringValue(lb.LoadBalancerName), lb.Instances, instances) if err != nil { return nil diff --git a/pkg/cloudprovider/providers/aws/aws_fakes.go b/pkg/cloudprovider/providers/aws/aws_fakes.go index 069da837db4..77436327230 100644 --- a/pkg/cloudprovider/providers/aws/aws_fakes.go +++ b/pkg/cloudprovider/providers/aws/aws_fakes.go @@ -23,6 +23,7 @@ import ( "github.com/aws/aws-sdk-go/service/autoscaling" "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/elb" + "github.com/aws/aws-sdk-go/service/elbv2" "github.com/aws/aws-sdk-go/service/kms" "github.com/golang/glog" @@ -38,6 +39,7 @@ type FakeAWSServices struct { ec2 FakeEC2 elb ELB + elbv2 ELBV2 asg *FakeASG metadata *FakeMetadata kms *FakeKMS @@ -48,6 +50,7 @@ func NewFakeAWSServices(clusterId string) *FakeAWSServices { s.region = "us-east-1" s.ec2 = &FakeEC2Impl{aws: s} s.elb = &FakeELB{aws: s} + s.elbv2 = &FakeELBV2{aws: s} s.asg = &FakeASG{aws: s} s.metadata = &FakeMetadata{aws: s} s.kms = &FakeKMS{aws: s} @@ -90,6 +93,10 @@ func (s *FakeAWSServices) LoadBalancing(region string) (ELB, error) { return s.elb, nil } +func (s *FakeAWSServices) LoadBalancingV2(region string) (ELBV2, error) { + return s.elbv2, nil +} + func (s *FakeAWSServices) Autoscaling(region string) (ASG, error) { return s.asg, nil } @@ -196,6 +203,14 @@ func (ec2i *FakeEC2Impl) RevokeSecurityGroupIngress(*ec2.RevokeSecurityGroupIngr panic("Not implemented") } +func (ec2i *FakeEC2Impl) DescribeVolumeModifications(*ec2.DescribeVolumesModificationsInput) ([]*ec2.VolumeModification, error) { + panic("Not implemented") +} + +func (ec2i *FakeEC2Impl) ModifyVolume(*ec2.ModifyVolumeInput) (*ec2.ModifyVolumeOutput, error) { + panic("Not implemented") +} + func (ec2i *FakeEC2Impl) CreateSubnet(request *ec2.Subnet) (*ec2.CreateSubnetOutput, error) { ec2i.Subnets = append(ec2i.Subnets, request) response := &ec2.CreateSubnetOutput{ @@ -246,6 +261,10 @@ func (ec2i *FakeEC2Impl) ModifyInstanceAttribute(request *ec2.ModifyInstanceAttr panic("Not implemented") } +func (ec2i *FakeEC2Impl) DescribeVpcs(request *ec2.DescribeVpcsInput) (*ec2.DescribeVpcsOutput, error) { + return &ec2.DescribeVpcsOutput{Vpcs: []*ec2.Vpc{{CidrBlock: aws.String("172.20.0.0/16")}}}, nil +} + type FakeMetadata struct { aws *FakeAWSServices } @@ -312,6 +331,10 @@ func (elb *FakeELB) DescribeLoadBalancers(input *elb.DescribeLoadBalancersInput) panic("Not implemented") } +func (elb *FakeELB) AddTags(input *elb.AddTagsInput) (*elb.AddTagsOutput, error) { + panic("Not implemented") +} + func (elb *FakeELB) RegisterInstancesWithLoadBalancer(*elb.RegisterInstancesWithLoadBalancerInput) (*elb.RegisterInstancesWithLoadBalancerOutput, error) { panic("Not implemented") } @@ -352,6 +375,14 @@ func (elb *FakeELB) SetLoadBalancerPoliciesForBackendServer(*elb.SetLoadBalancer panic("Not implemented") } +func (elb *FakeELB) SetLoadBalancerPoliciesOfListener(input *elb.SetLoadBalancerPoliciesOfListenerInput) (*elb.SetLoadBalancerPoliciesOfListenerOutput, error) { + panic("Not implemented") +} + +func (elb *FakeELB) DescribeLoadBalancerPolicies(input *elb.DescribeLoadBalancerPoliciesInput) (*elb.DescribeLoadBalancerPoliciesOutput, error) { + panic("Not implemented") +} + func (elb *FakeELB) DescribeLoadBalancerAttributes(*elb.DescribeLoadBalancerAttributesInput) (*elb.DescribeLoadBalancerAttributesOutput, error) { panic("Not implemented") } @@ -364,6 +395,79 @@ func (self *FakeELB) expectDescribeLoadBalancers(loadBalancerName string) { panic("Not implemented") } +type FakeELBV2 struct { + aws *FakeAWSServices +} + +func (self *FakeELBV2) AddTags(input *elbv2.AddTagsInput) (*elbv2.AddTagsOutput, error) { + panic("Not implemented") +} + +func (self *FakeELBV2) CreateLoadBalancer(*elbv2.CreateLoadBalancerInput) (*elbv2.CreateLoadBalancerOutput, error) { + panic("Not implemented") +} +func (self *FakeELBV2) DescribeLoadBalancers(*elbv2.DescribeLoadBalancersInput) (*elbv2.DescribeLoadBalancersOutput, error) { + panic("Not implemented") +} +func (self *FakeELBV2) DeleteLoadBalancer(*elbv2.DeleteLoadBalancerInput) (*elbv2.DeleteLoadBalancerOutput, error) { + panic("Not implemented") +} + +func (self *FakeELBV2) ModifyLoadBalancerAttributes(*elbv2.ModifyLoadBalancerAttributesInput) (*elbv2.ModifyLoadBalancerAttributesOutput, error) { + panic("Not implemented") +} +func (self *FakeELBV2) DescribeLoadBalancerAttributes(*elbv2.DescribeLoadBalancerAttributesInput) (*elbv2.DescribeLoadBalancerAttributesOutput, error) { + panic("Not implemented") +} + +func (self *FakeELBV2) CreateTargetGroup(*elbv2.CreateTargetGroupInput) (*elbv2.CreateTargetGroupOutput, error) { + panic("Not implemented") +} +func (self *FakeELBV2) DescribeTargetGroups(*elbv2.DescribeTargetGroupsInput) (*elbv2.DescribeTargetGroupsOutput, error) { + panic("Not implemented") +} +func (self *FakeELBV2) ModifyTargetGroup(*elbv2.ModifyTargetGroupInput) (*elbv2.ModifyTargetGroupOutput, error) { + panic("Not implemented") +} +func (self *FakeELBV2) DeleteTargetGroup(*elbv2.DeleteTargetGroupInput) (*elbv2.DeleteTargetGroupOutput, error) { + panic("Not implemented") +} + +func (self *FakeELBV2) DescribeTargetHealth(input *elbv2.DescribeTargetHealthInput) (*elbv2.DescribeTargetHealthOutput, error) { + panic("Not implemented") +} + +func (self *FakeELBV2) DescribeTargetGroupAttributes(*elbv2.DescribeTargetGroupAttributesInput) (*elbv2.DescribeTargetGroupAttributesOutput, error) { + panic("Not implemented") +} +func (self *FakeELBV2) ModifyTargetGroupAttributes(*elbv2.ModifyTargetGroupAttributesInput) (*elbv2.ModifyTargetGroupAttributesOutput, error) { + panic("Not implemented") +} + +func (self *FakeELBV2) RegisterTargets(*elbv2.RegisterTargetsInput) (*elbv2.RegisterTargetsOutput, error) { + panic("Not implemented") +} +func (self *FakeELBV2) DeregisterTargets(*elbv2.DeregisterTargetsInput) (*elbv2.DeregisterTargetsOutput, error) { + panic("Not implemented") +} + +func (self *FakeELBV2) CreateListener(*elbv2.CreateListenerInput) (*elbv2.CreateListenerOutput, error) { + panic("Not implemented") +} +func (self *FakeELBV2) DescribeListeners(*elbv2.DescribeListenersInput) (*elbv2.DescribeListenersOutput, error) { + panic("Not implemented") +} +func (self *FakeELBV2) DeleteListener(*elbv2.DeleteListenerInput) (*elbv2.DeleteListenerOutput, error) { + panic("Not implemented") +} +func (self *FakeELBV2) ModifyListener(*elbv2.ModifyListenerInput) (*elbv2.ModifyListenerOutput, error) { + panic("Not implemented") +} + +func (self *FakeELBV2) WaitUntilLoadBalancersDeleted(*elbv2.DescribeLoadBalancersInput) error { + panic("Not implemented") +} + type FakeASG struct { aws *FakeAWSServices } diff --git a/pkg/cloudprovider/providers/aws/aws_loadbalancer.go b/pkg/cloudprovider/providers/aws/aws_loadbalancer.go index 03eefb296e0..6baaca2ac6c 100644 --- a/pkg/cloudprovider/providers/aws/aws_loadbalancer.go +++ b/pkg/cloudprovider/providers/aws/aws_loadbalancer.go @@ -17,21 +17,53 @@ limitations under the License. package aws import ( + "crypto/sha1" "fmt" "reflect" "strconv" "strings" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/elb" + "github.com/aws/aws-sdk-go/service/elbv2" "github.com/golang/glog" "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" ) -const ProxyProtocolPolicyName = "k8s-proxyprotocol-enabled" +const ( + ProxyProtocolPolicyName = "k8s-proxyprotocol-enabled" + + SSLNegotiationPolicyNameFormat = "k8s-SSLNegotiationPolicy-%s" +) + +var ( + // Defaults for ELB Healthcheck + defaultHCHealthyThreshold = int64(2) + defaultHCUnhealthyThreshold = int64(6) + defaultHCTimeout = int64(5) + defaultHCInterval = int64(10) +) + +func isNLB(annotations map[string]string) bool { + if annotations[ServiceAnnotationLoadBalancerType] == "nlb" { + return true + } + return false +} + +type nlbPortMapping struct { + FrontendPort int64 + TrafficPort int64 + ClientCIDR string + + HealthCheckPort int64 + HealthCheckPath string + HealthCheckProtocol string +} // getLoadBalancerAdditionalTags converts the comma separated list of key-value // pairs in the ServiceAnnotationLoadBalancerAdditionalTags annotation and returns @@ -62,6 +94,760 @@ func getLoadBalancerAdditionalTags(annotations map[string]string) map[string]str return additionalTags } +// ensureLoadBalancerv2 ensures a v2 load balancer is created +func (c *Cloud) ensureLoadBalancerv2(namespacedName types.NamespacedName, loadBalancerName string, mappings []nlbPortMapping, instanceIDs, subnetIDs []string, internalELB bool, annotations map[string]string) (*elbv2.LoadBalancer, error) { + loadBalancer, err := c.describeLoadBalancerv2(loadBalancerName) + if err != nil { + return nil, err + } + + dirty := false + + // Get additional tags set by the user + tags := getLoadBalancerAdditionalTags(annotations) + // Add default tags + tags[TagNameKubernetesService] = namespacedName.String() + tags = c.tagging.buildTags(ResourceLifecycleOwned, tags) + + if loadBalancer == nil { + // Create the LB + createRequest := &elbv2.CreateLoadBalancerInput{ + Type: aws.String(elbv2.LoadBalancerTypeEnumNetwork), + Name: aws.String(loadBalancerName), + } + if internalELB { + createRequest.Scheme = aws.String("internal") + } + + // We are supposed to specify one subnet per AZ. + // TODO: What happens if we have more than one subnet per AZ? + createRequest.SubnetMappings = createSubnetMappings(subnetIDs) + + for k, v := range tags { + createRequest.Tags = append(createRequest.Tags, &elbv2.Tag{ + Key: aws.String(k), Value: aws.String(v), + }) + } + + glog.Infof("Creating load balancer for %v with name: %s", namespacedName, loadBalancerName) + createResponse, err := c.elbv2.CreateLoadBalancer(createRequest) + if err != nil { + return nil, fmt.Errorf("Error creating load balancer: %q", err) + } + + loadBalancer = createResponse.LoadBalancers[0] + + // Create Target Groups + addTagsInput := &elbv2.AddTagsInput{ + ResourceArns: []*string{}, + Tags: []*elbv2.Tag{}, + } + + for i := range mappings { + // It is easier to keep track of updates by having possibly + // duplicate target groups where the backend port is the same + _, targetGroupArn, err := c.createListenerV2(createResponse.LoadBalancers[0].LoadBalancerArn, mappings[i], namespacedName, instanceIDs, *createResponse.LoadBalancers[0].VpcId) + if err != nil { + return nil, fmt.Errorf("Error creating listener: %q", err) + } + addTagsInput.ResourceArns = append(addTagsInput.ResourceArns, targetGroupArn) + + } + + // Add tags to targets + for k, v := range tags { + addTagsInput.Tags = append(addTagsInput.Tags, &elbv2.Tag{ + Key: aws.String(k), Value: aws.String(v), + }) + } + if len(addTagsInput.ResourceArns) > 0 && len(addTagsInput.Tags) > 0 { + _, err = c.elbv2.AddTags(addTagsInput) + if err != nil { + return nil, fmt.Errorf("Error adding tags after creating Load Balancer: %q", err) + } + } + } else { + // TODO: Sync internal vs non-internal + + // sync mappings + { + listenerDescriptions, err := c.elbv2.DescribeListeners( + &elbv2.DescribeListenersInput{ + LoadBalancerArn: loadBalancer.LoadBalancerArn, + }, + ) + if err != nil { + return nil, fmt.Errorf("Error describing listeners: %q", err) + } + + // actual maps FrontendPort to an elbv2.Listener + actual := map[int64]*elbv2.Listener{} + for _, listener := range listenerDescriptions.Listeners { + actual[*listener.Port] = listener + } + + actualTargetGroups, err := c.elbv2.DescribeTargetGroups( + &elbv2.DescribeTargetGroupsInput{ + LoadBalancerArn: loadBalancer.LoadBalancerArn, + }, + ) + if err != nil { + return nil, fmt.Errorf("Error listing target groups: %q", err) + } + + nodePortTargetGroup := map[int64]*elbv2.TargetGroup{} + for _, targetGroup := range actualTargetGroups.TargetGroups { + nodePortTargetGroup[*targetGroup.Port] = targetGroup + } + + // Create Target Groups + addTagsInput := &elbv2.AddTagsInput{ + ResourceArns: []*string{}, + Tags: []*elbv2.Tag{}, + } + + // Handle additions/modifications + for _, mapping := range mappings { + frontendPort := mapping.FrontendPort + nodePort := mapping.TrafficPort + + // modifications + if listener, ok := actual[frontendPort]; ok { + // nodePort must have changed, we'll need to delete old TG + // and recreate + if targetGroup, ok := nodePortTargetGroup[nodePort]; !ok { + // Create new Target group + targetName := createTargetName(namespacedName, frontendPort, nodePort) + targetGroup, err = c.ensureTargetGroup( + nil, + mapping, + instanceIDs, + targetName, + *loadBalancer.VpcId, + ) + if err != nil { + return nil, err + } + + // Associate new target group to LB + _, err := c.elbv2.ModifyListener(&elbv2.ModifyListenerInput{ + ListenerArn: listener.ListenerArn, + Port: aws.Int64(frontendPort), + Protocol: aws.String("TCP"), + DefaultActions: []*elbv2.Action{{ + TargetGroupArn: targetGroup.TargetGroupArn, + Type: aws.String("forward"), + }}, + }) + if err != nil { + return nil, fmt.Errorf("Error updating load balancer listener: %q", err) + } + + // Delete old target group + _, err = c.elbv2.DeleteTargetGroup(&elbv2.DeleteTargetGroupInput{ + TargetGroupArn: listener.DefaultActions[0].TargetGroupArn, + }) + if err != nil { + return nil, fmt.Errorf("Error deleting old target group: %q", err) + } + + } else { + // Run ensureTargetGroup to make sure instances in service are up-to-date + targetName := createTargetName(namespacedName, frontendPort, nodePort) + _, err = c.ensureTargetGroup( + targetGroup, + mapping, + instanceIDs, + targetName, + *loadBalancer.VpcId, + ) + if err != nil { + return nil, err + } + } + dirty = true + continue + } + + // Additions + _, targetGroupArn, err := c.createListenerV2(loadBalancer.LoadBalancerArn, mapping, namespacedName, instanceIDs, *loadBalancer.VpcId) + if err != nil { + return nil, err + } + addTagsInput.ResourceArns = append(addTagsInput.ResourceArns, targetGroupArn) + dirty = true + } + + frontEndPorts := map[int64]bool{} + for i := range mappings { + frontEndPorts[mappings[i].FrontendPort] = true + } + + // handle deletions + for port, listener := range actual { + if _, ok := frontEndPorts[port]; !ok { + err := c.deleteListenerV2(listener) + if err != nil { + return nil, err + } + dirty = true + } + } + + // Add tags to new targets + for k, v := range tags { + addTagsInput.Tags = append(addTagsInput.Tags, &elbv2.Tag{ + Key: aws.String(k), Value: aws.String(v), + }) + } + if len(addTagsInput.ResourceArns) > 0 && len(addTagsInput.Tags) > 0 { + _, err = c.elbv2.AddTags(addTagsInput) + if err != nil { + return nil, fmt.Errorf("Error adding tags after modifying load balancer targets: %q", err) + } + } + } + + // Subnets cannot be modified on NLBs + if dirty { + loadBalancers, err := c.elbv2.DescribeLoadBalancers( + &elbv2.DescribeLoadBalancersInput{ + LoadBalancerArns: []*string{ + loadBalancer.LoadBalancerArn, + }, + }, + ) + if err != nil { + return nil, fmt.Errorf("Error retrieving load balancer after update: %q", err) + } + loadBalancer = loadBalancers.LoadBalancers[0] + } + } + return loadBalancer, nil +} + +// create a valid target group name - ensure name is not over 32 characters +func createTargetName(namespacedName types.NamespacedName, frontendPort, nodePort int64) string { + sha := fmt.Sprintf("%x", sha1.Sum([]byte(namespacedName.String())))[:13] + return fmt.Sprintf("k8s-tg-%s-%d-%d", sha, frontendPort, nodePort) +} + +func (c *Cloud) createListenerV2(loadBalancerArn *string, mapping nlbPortMapping, namespacedName types.NamespacedName, instanceIDs []string, vpcID string) (listener *elbv2.Listener, targetGroupArn *string, err error) { + targetName := createTargetName(namespacedName, mapping.FrontendPort, mapping.TrafficPort) + + glog.Infof("Creating load balancer target group for %v with name: %s", namespacedName, targetName) + target, err := c.ensureTargetGroup( + nil, + mapping, + instanceIDs, + targetName, + vpcID, + ) + if err != nil { + return nil, aws.String(""), err + } + + createListernerInput := &elbv2.CreateListenerInput{ + LoadBalancerArn: loadBalancerArn, + Port: aws.Int64(mapping.FrontendPort), + Protocol: aws.String("TCP"), + DefaultActions: []*elbv2.Action{{ + TargetGroupArn: target.TargetGroupArn, + Type: aws.String(elbv2.ActionTypeEnumForward), + }}, + } + glog.Infof("Creating load balancer listener for %v", namespacedName) + createListenerOutput, err := c.elbv2.CreateListener(createListernerInput) + if err != nil { + return nil, aws.String(""), fmt.Errorf("Error creating load balancer listener: %q", err) + } + return createListenerOutput.Listeners[0], target.TargetGroupArn, nil +} + +// cleans up listener and corresponding target group +func (c *Cloud) deleteListenerV2(listener *elbv2.Listener) error { + _, err := c.elbv2.DeleteListener(&elbv2.DeleteListenerInput{ListenerArn: listener.ListenerArn}) + if err != nil { + return fmt.Errorf("Error deleting load balancer listener: %q", err) + } + _, err = c.elbv2.DeleteTargetGroup(&elbv2.DeleteTargetGroupInput{TargetGroupArn: listener.DefaultActions[0].TargetGroupArn}) + if err != nil { + return fmt.Errorf("Error deleting load balancer target group: %q", err) + } + return nil +} + +// ensureTargetGroup creates a target group with a set of instances +func (c *Cloud) ensureTargetGroup(targetGroup *elbv2.TargetGroup, mapping nlbPortMapping, instances []string, name string, vpcID string) (*elbv2.TargetGroup, error) { + dirty := false + if targetGroup == nil { + + input := &elbv2.CreateTargetGroupInput{ + VpcId: aws.String(vpcID), + Name: aws.String(name), + Port: aws.Int64(mapping.TrafficPort), + Protocol: aws.String("TCP"), + TargetType: aws.String("instance"), + HealthCheckIntervalSeconds: aws.Int64(30), + HealthCheckPort: aws.String("traffic-port"), + HealthCheckProtocol: aws.String("TCP"), + HealthyThresholdCount: aws.Int64(3), + UnhealthyThresholdCount: aws.Int64(3), + } + + input.HealthCheckProtocol = aws.String(mapping.HealthCheckProtocol) + if mapping.HealthCheckProtocol != elbv2.ProtocolEnumTcp { + input.HealthCheckPath = aws.String(mapping.HealthCheckPath) + } + + // Account for externalTrafficPolicy = "Local" + if mapping.HealthCheckPort != mapping.TrafficPort { + input.HealthCheckPort = aws.String(strconv.Itoa(int(mapping.HealthCheckPort))) + } + + result, err := c.elbv2.CreateTargetGroup(input) + if err != nil { + return nil, fmt.Errorf("Error creating load balancer target group: %q", err) + } + if len(result.TargetGroups) != 1 { + return nil, fmt.Errorf("Expected only one target group on CreateTargetGroup, got %d groups", len(result.TargetGroups)) + } + + registerInput := &elbv2.RegisterTargetsInput{ + TargetGroupArn: result.TargetGroups[0].TargetGroupArn, + Targets: []*elbv2.TargetDescription{}, + } + for _, instanceID := range instances { + registerInput.Targets = append(registerInput.Targets, &elbv2.TargetDescription{ + Id: aws.String(string(instanceID)), + Port: aws.Int64(mapping.TrafficPort), + }) + } + + _, err = c.elbv2.RegisterTargets(registerInput) + if err != nil { + return nil, fmt.Errorf("Error registering targets for load balancer: %q", err) + } + + return result.TargetGroups[0], nil + } + + // handle instances in service + { + healthResponse, err := c.elbv2.DescribeTargetHealth(&elbv2.DescribeTargetHealthInput{TargetGroupArn: targetGroup.TargetGroupArn}) + if err != nil { + return nil, fmt.Errorf("Error describing target group health: %q", err) + } + actualIDs := []string{} + for _, healthDescription := range healthResponse.TargetHealthDescriptions { + if healthDescription.TargetHealth.Reason != nil { + switch aws.StringValue(healthDescription.TargetHealth.Reason) { + case elbv2.TargetHealthReasonEnumTargetDeregistrationInProgress: + // We don't need to count this instance in service if it is + // on its way out + default: + actualIDs = append(actualIDs, *healthDescription.Target.Id) + } + } + } + + actual := sets.NewString(actualIDs...) + expected := sets.NewString(instances...) + + additions := expected.Difference(actual) + removals := actual.Difference(expected) + + if len(additions) > 0 { + registerInput := &elbv2.RegisterTargetsInput{ + TargetGroupArn: targetGroup.TargetGroupArn, + Targets: []*elbv2.TargetDescription{}, + } + for instanceID := range additions { + registerInput.Targets = append(registerInput.Targets, &elbv2.TargetDescription{ + Id: aws.String(instanceID), + Port: aws.Int64(mapping.TrafficPort), + }) + } + _, err := c.elbv2.RegisterTargets(registerInput) + if err != nil { + return nil, fmt.Errorf("Error registering new targets in target group: %q", err) + } + dirty = true + } + + if len(removals) > 0 { + deregisterInput := &elbv2.DeregisterTargetsInput{ + TargetGroupArn: targetGroup.TargetGroupArn, + Targets: []*elbv2.TargetDescription{}, + } + for instanceID := range removals { + deregisterInput.Targets = append(deregisterInput.Targets, &elbv2.TargetDescription{ + Id: aws.String(instanceID), + Port: aws.Int64(mapping.TrafficPort), + }) + } + _, err := c.elbv2.DeregisterTargets(deregisterInput) + if err != nil { + return nil, fmt.Errorf("Error trying to deregister targets in target group: %q", err) + } + dirty = true + } + } + + // ensure the health check is correct + { + dirtyHealthCheck := false + + input := &elbv2.ModifyTargetGroupInput{ + TargetGroupArn: targetGroup.TargetGroupArn, + } + + if aws.StringValue(targetGroup.HealthCheckProtocol) != mapping.HealthCheckProtocol { + input.HealthCheckProtocol = aws.String(mapping.HealthCheckProtocol) + dirtyHealthCheck = true + } + if aws.StringValue(targetGroup.HealthCheckPort) != strconv.Itoa(int(mapping.HealthCheckPort)) { + input.HealthCheckPort = aws.String(strconv.Itoa(int(mapping.HealthCheckPort))) + dirtyHealthCheck = true + } + if mapping.HealthCheckPath != "" && mapping.HealthCheckProtocol != elbv2.ProtocolEnumTcp { + input.HealthCheckPath = aws.String(mapping.HealthCheckPath) + dirtyHealthCheck = true + } + + if dirtyHealthCheck { + _, err := c.elbv2.ModifyTargetGroup(input) + if err != nil { + return nil, fmt.Errorf("Error modifying target group health check: %q", err) + } + + dirty = true + } + } + + if dirty { + result, err := c.elbv2.DescribeTargetGroups(&elbv2.DescribeTargetGroupsInput{ + Names: []*string{aws.String(name)}, + }) + if err != nil { + return nil, fmt.Errorf("Error retrieving target group after creation/update: %q", err) + } + targetGroup = result.TargetGroups[0] + } + + return targetGroup, nil +} + +func portsForNLB(lbName string, sg *ec2.SecurityGroup, clientTraffic bool) sets.Int64 { + response := sets.NewInt64() + var annotation string + if clientTraffic { + annotation = fmt.Sprintf("%s=%s", NLBClientRuleDescription, lbName) + } else { + annotation = fmt.Sprintf("%s=%s", NLBHealthCheckRuleDescription, lbName) + } + + for i := range sg.IpPermissions { + for j := range sg.IpPermissions[i].IpRanges { + description := aws.StringValue(sg.IpPermissions[i].IpRanges[j].Description) + if description == annotation { + // TODO should probably check FromPort == ToPort + response.Insert(aws.Int64Value(sg.IpPermissions[i].FromPort)) + } + } + } + return response +} + +// filterForIPRangeDescription filters in security groups that have IpRange Descriptions that match a loadBalancerName +func filterForIPRangeDescription(securityGroups []*ec2.SecurityGroup, lbName string) []*ec2.SecurityGroup { + response := []*ec2.SecurityGroup{} + clientRule := fmt.Sprintf("%s=%s", NLBClientRuleDescription, lbName) + healthRule := fmt.Sprintf("%s=%s", NLBHealthCheckRuleDescription, lbName) + for i := range securityGroups { + for j := range securityGroups[i].IpPermissions { + for k := range securityGroups[i].IpPermissions[j].IpRanges { + description := aws.StringValue(securityGroups[i].IpPermissions[j].IpRanges[k].Description) + if description == clientRule || description == healthRule { + response = append(response, securityGroups[i]) + } + } + } + } + return response +} + +func (c *Cloud) getVpcCidrBlock() (*string, error) { + vpcs, err := c.ec2.DescribeVpcs(&ec2.DescribeVpcsInput{ + VpcIds: []*string{aws.String(c.vpcID)}, + }) + if err != nil { + return nil, fmt.Errorf("Error querying VPC for ELB: %q", err) + } + if len(vpcs.Vpcs) != 1 { + return nil, fmt.Errorf("Error querying VPC for ELB, got %d vpcs for %s", len(vpcs.Vpcs), c.vpcID) + } + return vpcs.Vpcs[0].CidrBlock, nil +} + +// abstraction for updating SG rules +// if clientTraffic is false, then only update HealthCheck rules +func (c *Cloud) updateInstanceSecurityGroupsForNLBTraffic(actualGroups []*ec2.SecurityGroup, desiredSgIds []string, ports []int64, lbName string, clientCidrs []string, clientTraffic bool) error { + + // Map containing the groups we want to make changes on; the ports to make + // changes on; and whether to add or remove it. true to add, false to remove + portChanges := map[string]map[int64]bool{} + + for _, id := range desiredSgIds { + // consider everything an addition for now + if _, ok := portChanges[id]; !ok { + portChanges[id] = make(map[int64]bool) + } + for _, port := range ports { + portChanges[id][port] = true + } + } + + // Compare to actual groups + for _, actualGroup := range actualGroups { + actualGroupID := aws.StringValue(actualGroup.GroupId) + if actualGroupID == "" { + glog.Warning("Ignoring group without ID: ", actualGroup) + continue + } + + addingMap, ok := portChanges[actualGroupID] + if ok { + desiredSet := sets.NewInt64() + for port := range addingMap { + desiredSet.Insert(port) + } + existingSet := portsForNLB(lbName, actualGroup, clientTraffic) + + // remove from portChanges ports that are already allowed + if intersection := desiredSet.Intersection(existingSet); intersection.Len() > 0 { + for p := range intersection { + delete(portChanges[actualGroupID], p) + } + } + + // allowed ports that need to be removed + if difference := existingSet.Difference(desiredSet); difference.Len() > 0 { + for p := range difference { + portChanges[actualGroupID][p] = false + } + } + } + } + + // Make changes we've planned on + for instanceSecurityGroupID, portMap := range portChanges { + adds := []*ec2.IpPermission{} + removes := []*ec2.IpPermission{} + for port, add := range portMap { + if add { + if clientTraffic { + glog.V(2).Infof("Adding rule for client MTU discovery from the network load balancer (%s) to instances (%s)", clientCidrs, instanceSecurityGroupID) + glog.V(2).Infof("Adding rule for client traffic from the network load balancer (%s) to instances (%s)", clientCidrs, instanceSecurityGroupID) + } else { + glog.V(2).Infof("Adding rule for health check traffic from the network load balancer (%s) to instances (%s)", clientCidrs, instanceSecurityGroupID) + } + } else { + if clientTraffic { + glog.V(2).Infof("Removing rule for client MTU discovery from the network load balancer (%s) to instances (%s)", clientCidrs, instanceSecurityGroupID) + glog.V(2).Infof("Removing rule for client traffic from the network load balancer (%s) to instance (%s)", clientCidrs, instanceSecurityGroupID) + } + glog.V(2).Infof("Removing rule for health check traffic from the network load balancer (%s) to instance (%s)", clientCidrs, instanceSecurityGroupID) + } + + if clientTraffic { + clientRuleAnnotation := fmt.Sprintf("%s=%s", NLBClientRuleDescription, lbName) + mtuRuleAnnotation := fmt.Sprintf("%s=%s", NLBMtuDiscoveryRuleDescription, lbName) + // Client Traffic + permission := &ec2.IpPermission{ + FromPort: aws.Int64(port), + ToPort: aws.Int64(port), + IpProtocol: aws.String("tcp"), + } + ranges := []*ec2.IpRange{} + for _, cidr := range clientCidrs { + ranges = append(ranges, &ec2.IpRange{ + CidrIp: aws.String(cidr), + Description: aws.String(clientRuleAnnotation), + }) + } + permission.IpRanges = ranges + if add { + adds = append(adds, permission) + } else { + removes = append(removes, permission) + } + + // MTU discovery + permission = &ec2.IpPermission{ + IpProtocol: aws.String("icmp"), + FromPort: aws.Int64(3), + ToPort: aws.Int64(4), + } + ranges = []*ec2.IpRange{} + for _, cidr := range clientCidrs { + ranges = append(ranges, &ec2.IpRange{ + CidrIp: aws.String(cidr), + Description: aws.String(mtuRuleAnnotation), + }) + } + permission.IpRanges = ranges + if add { + adds = append(adds, permission) + } else { + removes = append(removes, permission) + } + } else { + healthRuleAnnotation := fmt.Sprintf("%s=%s", NLBHealthCheckRuleDescription, lbName) + + // NLB HealthCheck + permission := &ec2.IpPermission{ + FromPort: aws.Int64(port), + ToPort: aws.Int64(port), + IpProtocol: aws.String("tcp"), + } + ranges := []*ec2.IpRange{} + for _, cidr := range clientCidrs { + ranges = append(ranges, &ec2.IpRange{ + CidrIp: aws.String(cidr), + Description: aws.String(healthRuleAnnotation), + }) + } + permission.IpRanges = ranges + if add { + adds = append(adds, permission) + } else { + removes = append(removes, permission) + } + } + + } + if len(adds) > 0 { + changed, err := c.addSecurityGroupIngress(instanceSecurityGroupID, adds) + if err != nil { + return err + } + if !changed { + glog.Warning("Allowing ingress was not needed; concurrent change? groupId=", instanceSecurityGroupID) + } + } + if len(removes) > 0 { + changed, err := c.removeSecurityGroupIngress(instanceSecurityGroupID, removes) + if err != nil { + return err + } + if !changed { + glog.Warning("Revoking ingress was not needed; concurrent change? groupId=", instanceSecurityGroupID) + } + } + } + return nil +} + +// Add SG rules for a given NLB +func (c *Cloud) updateInstanceSecurityGroupsForNLB(mappings []nlbPortMapping, instances map[awsInstanceID]*ec2.Instance, lbName string, clientCidrs []string) error { + if c.cfg.Global.DisableSecurityGroupIngress { + return nil + } + + vpcCidr, err := c.getVpcCidrBlock() + if err != nil { + return err + } + + // Unlike the classic ELB, NLB does not have a security group that we can + // filter against all existing groups to see if they allow access. Instead + // we use the IpRange.Description field to annotate NLB health check and + // client traffic rules + + // Get the actual list of groups that allow ingress for the load-balancer + var actualGroups []*ec2.SecurityGroup + { + // Server side filter + describeRequest := &ec2.DescribeSecurityGroupsInput{} + filters := []*ec2.Filter{ + newEc2Filter("ip-permission.protocol", "tcp"), + } + describeRequest.Filters = c.tagging.addFilters(filters) + response, err := c.ec2.DescribeSecurityGroups(describeRequest) + if err != nil { + return fmt.Errorf("Error querying security groups for NLB: %q", err) + } + for _, sg := range response { + if !c.tagging.hasClusterTag(sg.Tags) { + continue + } + actualGroups = append(actualGroups, sg) + } + + // client-side filter + // Filter out groups that don't have IP Rules we've annotated for this service + actualGroups = filterForIPRangeDescription(actualGroups, lbName) + } + + taggedSecurityGroups, err := c.getTaggedSecurityGroups() + if err != nil { + return fmt.Errorf("Error querying for tagged security groups: %q", err) + } + + externalTrafficPolicyIsLocal := false + trafficPorts := []int64{} + for i := range mappings { + trafficPorts = append(trafficPorts, mappings[i].TrafficPort) + if mappings[i].TrafficPort != mappings[i].HealthCheckPort { + externalTrafficPolicyIsLocal = true + } + } + + healthCheckPorts := trafficPorts + // if externalTrafficPolicy is Local, all listeners use the same health + // check port + if externalTrafficPolicyIsLocal && len(mappings) > 0 { + healthCheckPorts = []int64{mappings[0].HealthCheckPort} + } + + desiredGroupIds := []string{} + // Scan instances for groups we want open + for _, instance := range instances { + securityGroup, err := findSecurityGroupForInstance(instance, taggedSecurityGroups) + if err != nil { + return err + } + + if securityGroup == nil { + glog.Warningf("Ignoring instance without security group: %s", aws.StringValue(instance.InstanceId)) + continue + } + + id := aws.StringValue(securityGroup.GroupId) + if id == "" { + glog.Warningf("found security group without id: %v", securityGroup) + continue + } + + desiredGroupIds = append(desiredGroupIds, id) + } + + // Run once for Client traffic + err = c.updateInstanceSecurityGroupsForNLBTraffic(actualGroups, desiredGroupIds, trafficPorts, lbName, clientCidrs, true) + if err != nil { + return err + } + + // Run once for health check traffic + err = c.updateInstanceSecurityGroupsForNLBTraffic(actualGroups, desiredGroupIds, healthCheckPorts, lbName, []string{aws.StringValue(vpcCidr)}, false) + if err != nil { + return err + } + + return nil +} + func (c *Cloud) ensureLoadBalancer(namespacedName types.NamespacedName, loadBalancerName string, listeners []*elb.Listener, subnetIDs []string, securityGroupIDs []string, internalELB, proxyProtocol bool, loadBalancerAttributes *elb.LoadBalancerAttributes, annotations map[string]string) (*elb.LoadBalancerDescription, error) { loadBalancer, err := c.describeLoadBalancer(loadBalancerName) if err != nil { @@ -314,6 +1100,18 @@ func (c *Cloud) ensureLoadBalancer(namespacedName types.NamespacedName, loadBala } } } + + { + // Add additional tags + glog.V(2).Infof("Creating additional load balancer tags for %s", loadBalancerName) + tags := getLoadBalancerAdditionalTags(annotations) + if len(tags) > 0 { + err := c.addLoadBalancerTags(loadBalancerName, tags) + if err != nil { + return nil, fmt.Errorf("unable to create additional load balancer tags: %v", err) + } + } + } } // Whether the ELB was new or existing, sync attributes regardless. This accounts for things @@ -356,6 +1154,17 @@ func (c *Cloud) ensureLoadBalancer(namespacedName types.NamespacedName, loadBala return loadBalancer, nil } +func createSubnetMappings(subnetIDs []string) []*elbv2.SubnetMapping { + response := []*elbv2.SubnetMapping{} + + for _, id := range subnetIDs { + // Ignore AllocationId for now + response = append(response, &elbv2.SubnetMapping{SubnetId: aws.String(id)}) + } + + return response +} + // elbProtocolsAreEqual checks if two ELB protocol strings are considered the same // Comparison is case insensitive func elbProtocolsAreEqual(l, r *string) bool { @@ -374,44 +1183,72 @@ func awsArnEquals(l, r *string) bool { return strings.EqualFold(aws.StringValue(l), aws.StringValue(r)) } +// getExpectedHealthCheck returns an elb.Healthcheck for the provided target +// and using either sensible defaults or overrides via Service annotations +func (c *Cloud) getExpectedHealthCheck(target string, annotations map[string]string) (*elb.HealthCheck, error) { + healthcheck := &elb.HealthCheck{Target: &target} + getOrDefault := func(annotation string, defaultValue int64) (*int64, error) { + i64 := defaultValue + var err error + if s, ok := annotations[annotation]; ok { + i64, err = strconv.ParseInt(s, 10, 0) + if err != nil { + return nil, fmt.Errorf("failed parsing health check annotation value: %v", err) + } + } + return &i64, nil + } + var err error + healthcheck.HealthyThreshold, err = getOrDefault(ServiceAnnotationLoadBalancerHCHealthyThreshold, defaultHCHealthyThreshold) + if err != nil { + return nil, err + } + healthcheck.UnhealthyThreshold, err = getOrDefault(ServiceAnnotationLoadBalancerHCUnhealthyThreshold, defaultHCUnhealthyThreshold) + if err != nil { + return nil, err + } + healthcheck.Timeout, err = getOrDefault(ServiceAnnotationLoadBalancerHCTimeout, defaultHCTimeout) + if err != nil { + return nil, err + } + healthcheck.Interval, err = getOrDefault(ServiceAnnotationLoadBalancerHCInterval, defaultHCInterval) + if err != nil { + return nil, err + } + if err = healthcheck.Validate(); err != nil { + return nil, fmt.Errorf("some of the load balancer health check parameters are invalid: %v", err) + } + return healthcheck, nil +} + // Makes sure that the health check for an ELB matches the configured health check node port -func (c *Cloud) ensureLoadBalancerHealthCheck(loadBalancer *elb.LoadBalancerDescription, protocol string, port int32, path string) error { +func (c *Cloud) ensureLoadBalancerHealthCheck(loadBalancer *elb.LoadBalancerDescription, protocol string, port int32, path string, annotations map[string]string) error { name := aws.StringValue(loadBalancer.LoadBalancerName) actual := loadBalancer.HealthCheck - - // Default AWS settings - expectedHealthyThreshold := int64(2) - expectedUnhealthyThreshold := int64(6) - expectedTimeout := int64(5) - expectedInterval := int64(10) - expectedTarget := protocol + ":" + strconv.FormatInt(int64(port), 10) + path + expected, err := c.getExpectedHealthCheck(expectedTarget, annotations) + if err != nil { + return fmt.Errorf("cannot update health check for load balancer %q: %q", name, err) + } - if expectedTarget == aws.StringValue(actual.Target) && - expectedHealthyThreshold == orZero(actual.HealthyThreshold) && - expectedUnhealthyThreshold == orZero(actual.UnhealthyThreshold) && - expectedTimeout == orZero(actual.Timeout) && - expectedInterval == orZero(actual.Interval) { + // comparing attributes 1 by 1 to avoid breakage in case a new field is + // added to the HC which breaks the equality + if aws.StringValue(expected.Target) == aws.StringValue(actual.Target) && + aws.Int64Value(expected.HealthyThreshold) == aws.Int64Value(actual.HealthyThreshold) && + aws.Int64Value(expected.UnhealthyThreshold) == aws.Int64Value(actual.UnhealthyThreshold) && + aws.Int64Value(expected.Interval) == aws.Int64Value(actual.Interval) && + aws.Int64Value(expected.Timeout) == aws.Int64Value(actual.Timeout) { return nil } - glog.V(2).Infof("Updating load-balancer health-check for %q", name) - - healthCheck := &elb.HealthCheck{} - healthCheck.HealthyThreshold = &expectedHealthyThreshold - healthCheck.UnhealthyThreshold = &expectedUnhealthyThreshold - healthCheck.Timeout = &expectedTimeout - healthCheck.Interval = &expectedInterval - healthCheck.Target = &expectedTarget - request := &elb.ConfigureHealthCheckInput{} - request.HealthCheck = healthCheck + request.HealthCheck = expected request.LoadBalancerName = loadBalancer.LoadBalancerName - _, err := c.elb.ConfigureHealthCheck(request) + _, err = c.elb.ConfigureHealthCheck(request) if err != nil { - return fmt.Errorf("error configuring load-balancer health-check for %q: %q", name, err) + return fmt.Errorf("error configuring load balancer health check for %q: %q", name, err) } return nil @@ -471,6 +1308,78 @@ func (c *Cloud) ensureLoadBalancerInstances(loadBalancerName string, lbInstances return nil } +func (c *Cloud) getLoadBalancerTLSPorts(loadBalancer *elb.LoadBalancerDescription) []int64 { + ports := []int64{} + + for _, listenerDescription := range loadBalancer.ListenerDescriptions { + protocol := aws.StringValue(listenerDescription.Listener.Protocol) + if protocol == "SSL" || protocol == "HTTPS" { + ports = append(ports, aws.Int64Value(listenerDescription.Listener.LoadBalancerPort)) + } + } + return ports +} + +func (c *Cloud) ensureSSLNegotiationPolicy(loadBalancer *elb.LoadBalancerDescription, policyName string) error { + glog.V(2).Info("Describing load balancer policies on load balancer") + result, err := c.elb.DescribeLoadBalancerPolicies(&elb.DescribeLoadBalancerPoliciesInput{ + LoadBalancerName: loadBalancer.LoadBalancerName, + PolicyNames: []*string{ + aws.String(fmt.Sprintf(SSLNegotiationPolicyNameFormat, policyName)), + }, + }) + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + switch aerr.Code() { + case "PolicyNotFound": + // TODO change from string to `elb.ErrCodePolicyNotFoundException` once the AWS SDK is updated + default: + return fmt.Errorf("error describing security policies on load balancer: %q", err) + } + } + } + + if len(result.PolicyDescriptions) > 0 { + return nil + } + + glog.V(2).Infof("Creating SSL negotiation policy '%s' on load balancer", fmt.Sprintf(SSLNegotiationPolicyNameFormat, policyName)) + // there is an upper limit of 98 policies on an ELB, we're pretty safe from + // running into it + _, err = c.elb.CreateLoadBalancerPolicy(&elb.CreateLoadBalancerPolicyInput{ + LoadBalancerName: loadBalancer.LoadBalancerName, + PolicyName: aws.String(fmt.Sprintf(SSLNegotiationPolicyNameFormat, policyName)), + PolicyTypeName: aws.String("SSLNegotiationPolicyType"), + PolicyAttributes: []*elb.PolicyAttribute{ + { + AttributeName: aws.String("Reference-Security-Policy"), + AttributeValue: aws.String(policyName), + }, + }, + }) + if err != nil { + return fmt.Errorf("error creating security policy on load balancer: %q", err) + } + return nil +} + +func (c *Cloud) setSSLNegotiationPolicy(loadBalancerName, sslPolicyName string, port int64) error { + policyName := fmt.Sprintf(SSLNegotiationPolicyNameFormat, sslPolicyName) + request := &elb.SetLoadBalancerPoliciesOfListenerInput{ + LoadBalancerName: aws.String(loadBalancerName), + LoadBalancerPort: aws.Int64(port), + PolicyNames: []*string{ + aws.String(policyName), + }, + } + glog.V(2).Infof("Setting SSL negotiation policy '%s' on load balancer", policyName) + _, err := c.elb.SetLoadBalancerPoliciesOfListener(request) + if err != nil { + return fmt.Errorf("error setting SSL negotiation policy '%s' on load balancer: %q", policyName, err) + } + return nil +} + func (c *Cloud) createProxyProtocolPolicy(loadBalancerName string) error { request := &elb.CreateLoadBalancerPolicyInput{ LoadBalancerName: aws.String(loadBalancerName), diff --git a/pkg/cloudprovider/providers/aws/aws_loadbalancer_test.go b/pkg/cloudprovider/providers/aws/aws_loadbalancer_test.go index fb1cb12fbdc..6141de5ebbd 100644 --- a/pkg/cloudprovider/providers/aws/aws_loadbalancer_test.go +++ b/pkg/cloudprovider/providers/aws/aws_loadbalancer_test.go @@ -125,3 +125,37 @@ func TestAWSARNEquals(t *testing.T) { } } } + +func TestIsNLB(t *testing.T) { + tests := []struct { + name string + + annotations map[string]string + want bool + }{ + { + "NLB annotation provided", + map[string]string{"service.beta.kubernetes.io/aws-load-balancer-type": "nlb"}, + true, + }, + { + "NLB annotation has invalid value", + map[string]string{"service.beta.kubernetes.io/aws-load-balancer-type": "elb"}, + false, + }, + { + "NLB annotation absent", + map[string]string{}, + false, + }, + } + + for _, test := range tests { + t.Logf("Running test case %s", test.name) + got := isNLB(test.annotations) + + if got != test.want { + t.Errorf("Incorrect value for isNLB() case %s. Got %t, expected %t.", test.name, got, test.want) + } + } +} diff --git a/pkg/cloudprovider/providers/aws/aws_test.go b/pkg/cloudprovider/providers/aws/aws_test.go index 6609638e8b2..ded45896a65 100644 --- a/pkg/cloudprovider/providers/aws/aws_test.go +++ b/pkg/cloudprovider/providers/aws/aws_test.go @@ -32,6 +32,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/types" kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis" ) @@ -82,6 +83,29 @@ func (m *MockedFakeELB) expectDescribeLoadBalancers(loadBalancerName string) { }) } +func (m *MockedFakeELB) AddTags(input *elb.AddTagsInput) (*elb.AddTagsOutput, error) { + args := m.Called(input) + return args.Get(0).(*elb.AddTagsOutput), nil +} + +func (m *MockedFakeELB) ConfigureHealthCheck(input *elb.ConfigureHealthCheckInput) (*elb.ConfigureHealthCheckOutput, error) { + args := m.Called(input) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*elb.ConfigureHealthCheckOutput), args.Error(1) +} + +func (m *MockedFakeELB) expectConfigureHealthCheck(loadBalancerName *string, expectedHC *elb.HealthCheck, returnErr error) { + expected := &elb.ConfigureHealthCheckInput{HealthCheck: expectedHC, LoadBalancerName: loadBalancerName} + call := m.On("ConfigureHealthCheck", expected) + if returnErr != nil { + call.Return(nil, returnErr) + } else { + call.Return(&elb.ConfigureHealthCheckOutput{}, nil) + } +} + func TestReadAWSCloudConfig(t *testing.T) { tests := []struct { name string @@ -1130,6 +1154,146 @@ func TestLBExtraSecurityGroupsAnnotation(t *testing.T) { } } +// Test that we can add a load balancer tag +func TestAddLoadBalancerTags(t *testing.T) { + loadBalancerName := "test-elb" + awsServices := newMockedFakeAWSServices(TestClusterId) + c, _ := newAWSCloud(strings.NewReader("[global]"), awsServices) + + want := make(map[string]string) + want["tag1"] = "val1" + + expectedAddTagsRequest := &elb.AddTagsInput{ + LoadBalancerNames: []*string{&loadBalancerName}, + Tags: []*elb.Tag{ + { + Key: aws.String("tag1"), + Value: aws.String("val1"), + }, + }, + } + awsServices.elb.(*MockedFakeELB).On("AddTags", expectedAddTagsRequest).Return(&elb.AddTagsOutput{}) + + err := c.addLoadBalancerTags(loadBalancerName, want) + assert.Nil(t, err, "Error adding load balancer tags: %v", err) + awsServices.elb.(*MockedFakeELB).AssertExpectations(t) +} + +func TestEnsureLoadBalancerHealthCheck(t *testing.T) { + + tests := []struct { + name string + annotations map[string]string + overriddenFieldName string + overriddenValue int64 + }{ + {"falls back to HC defaults", map[string]string{}, "", int64(0)}, + {"healthy threshold override", map[string]string{ServiceAnnotationLoadBalancerHCHealthyThreshold: "7"}, "HealthyThreshold", int64(7)}, + {"unhealthy threshold override", map[string]string{ServiceAnnotationLoadBalancerHCUnhealthyThreshold: "7"}, "UnhealthyThreshold", int64(7)}, + {"timeout override", map[string]string{ServiceAnnotationLoadBalancerHCTimeout: "7"}, "Timeout", int64(7)}, + {"interval override", map[string]string{ServiceAnnotationLoadBalancerHCInterval: "7"}, "Interval", int64(7)}, + } + lbName := "myLB" + // this HC will always differ from the expected HC and thus it is expected an + // API call will be made to update it + currentHC := &elb.HealthCheck{} + elbDesc := &elb.LoadBalancerDescription{LoadBalancerName: &lbName, HealthCheck: currentHC} + defaultHealthyThreshold := int64(2) + defaultUnhealthyThreshold := int64(6) + defaultTimeout := int64(5) + defaultInterval := int64(10) + protocol, path, port := "tcp", "", int32(8080) + target := "tcp:8080" + defaultHC := &elb.HealthCheck{ + HealthyThreshold: &defaultHealthyThreshold, + UnhealthyThreshold: &defaultUnhealthyThreshold, + Timeout: &defaultTimeout, + Interval: &defaultInterval, + Target: &target, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + awsServices := newMockedFakeAWSServices(TestClusterId) + c, err := newAWSCloud(strings.NewReader("[global]"), awsServices) + assert.Nil(t, err, "Error building aws cloud: %v", err) + expectedHC := *defaultHC + if test.overriddenFieldName != "" { // cater for test case with no overrides + value := reflect.ValueOf(&test.overriddenValue) + reflect.ValueOf(&expectedHC).Elem().FieldByName(test.overriddenFieldName).Set(value) + } + awsServices.elb.(*MockedFakeELB).expectConfigureHealthCheck(&lbName, &expectedHC, nil) + + err = c.ensureLoadBalancerHealthCheck(elbDesc, protocol, port, path, test.annotations) + + require.Nil(t, err) + awsServices.elb.(*MockedFakeELB).AssertExpectations(t) + }) + } + + t.Run("does not make an API call if the current health check is the same", func(t *testing.T) { + awsServices := newMockedFakeAWSServices(TestClusterId) + c, err := newAWSCloud(strings.NewReader("[global]"), awsServices) + assert.Nil(t, err, "Error building aws cloud: %v", err) + expectedHC := *defaultHC + timeout := int64(3) + expectedHC.Timeout = &timeout + annotations := map[string]string{ServiceAnnotationLoadBalancerHCTimeout: "3"} + var currentHC elb.HealthCheck + currentHC = expectedHC + + // NOTE no call expectations are set on the ELB mock + // test default HC + elbDesc := &elb.LoadBalancerDescription{LoadBalancerName: &lbName, HealthCheck: defaultHC} + err = c.ensureLoadBalancerHealthCheck(elbDesc, protocol, port, path, map[string]string{}) + assert.Nil(t, err) + // test HC with override + elbDesc = &elb.LoadBalancerDescription{LoadBalancerName: &lbName, HealthCheck: ¤tHC} + err = c.ensureLoadBalancerHealthCheck(elbDesc, protocol, port, path, annotations) + assert.Nil(t, err) + }) + + t.Run("validates resulting expected health check before making an API call", func(t *testing.T) { + awsServices := newMockedFakeAWSServices(TestClusterId) + c, err := newAWSCloud(strings.NewReader("[global]"), awsServices) + assert.Nil(t, err, "Error building aws cloud: %v", err) + expectedHC := *defaultHC + invalidThreshold := int64(1) + expectedHC.HealthyThreshold = &invalidThreshold + require.Error(t, expectedHC.Validate()) // confirm test precondition + annotations := map[string]string{ServiceAnnotationLoadBalancerHCTimeout: "1"} + + // NOTE no call expectations are set on the ELB mock + err = c.ensureLoadBalancerHealthCheck(elbDesc, protocol, port, path, annotations) + + require.Error(t, err) + }) + + t.Run("handles invalid override values", func(t *testing.T) { + awsServices := newMockedFakeAWSServices(TestClusterId) + c, err := newAWSCloud(strings.NewReader("[global]"), awsServices) + assert.Nil(t, err, "Error building aws cloud: %v", err) + annotations := map[string]string{ServiceAnnotationLoadBalancerHCTimeout: "3.3"} + + // NOTE no call expectations are set on the ELB mock + err = c.ensureLoadBalancerHealthCheck(elbDesc, protocol, port, path, annotations) + + require.Error(t, err) + }) + + t.Run("returns error when updating the health check fails", func(t *testing.T) { + awsServices := newMockedFakeAWSServices(TestClusterId) + c, err := newAWSCloud(strings.NewReader("[global]"), awsServices) + assert.Nil(t, err, "Error building aws cloud: %v", err) + returnErr := fmt.Errorf("throttling error") + awsServices.elb.(*MockedFakeELB).expectConfigureHealthCheck(&lbName, defaultHC, returnErr) + + err = c.ensureLoadBalancerHealthCheck(elbDesc, protocol, port, path, map[string]string{}) + + require.Error(t, err) + awsServices.elb.(*MockedFakeELB).AssertExpectations(t) + }) +} + func newMockedFakeAWSServices(id string) *FakeAWSServices { s := NewFakeAWSServices(id) s.ec2 = &MockedFakeEC2{FakeEC2Impl: s.ec2.(*FakeEC2Impl)} diff --git a/pkg/cloudprovider/providers/aws/instances.go b/pkg/cloudprovider/providers/aws/instances.go index 3fa778e2e62..a42834415dd 100644 --- a/pkg/cloudprovider/providers/aws/instances.go +++ b/pkg/cloudprovider/providers/aws/instances.go @@ -25,10 +25,14 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" "github.com/golang/glog" "k8s.io/api/core/v1" + "regexp" "sync" "time" ) +// awsInstanceRegMatch represents Regex Match for AWS instance. +var awsInstanceRegMatch = regexp.MustCompile("^i-[^/]*$") + // awsInstanceID represents the ID of the instance in the AWS API, e.g. i-12345678 // The "traditional" format is "i-12345678" // A new longer format is also being introduced: "i-12345678abcdef01" @@ -76,8 +80,7 @@ func (name kubernetesInstanceID) mapToAWSInstanceID() (awsInstanceID, error) { // We sanity check the resulting volume; the two known formats are // i-12345678 and i-12345678abcdef01 - // TODO: Regex match? - if awsID == "" || strings.Contains(awsID, "/") || !strings.HasPrefix(awsID, "i-") { + if awsID == "" || !awsInstanceRegMatch.MatchString(awsID) { return "", fmt.Errorf("Invalid format for AWS instance (%s)", name) } diff --git a/pkg/cloudprovider/providers/aws/retry_handler.go b/pkg/cloudprovider/providers/aws/retry_handler.go index 49a50ef5c50..8c063f8e8a4 100644 --- a/pkg/cloudprovider/providers/aws/retry_handler.go +++ b/pkg/cloudprovider/providers/aws/retry_handler.go @@ -21,6 +21,7 @@ import ( "sync" "time" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/request" "github.com/golang/glog" @@ -53,7 +54,15 @@ func (c *CrossRequestRetryDelay) BeforeSign(r *request.Request) { if delay > 0 { glog.Warningf("Inserting delay before AWS request (%s) to avoid RequestLimitExceeded: %s", describeRequest(r), delay.String()) - r.Config.SleepDelay(delay) + + if sleepFn := r.Config.SleepDelay; sleepFn != nil { + // Support SleepDelay for backwards compatibility + sleepFn(delay) + } else if err := aws.SleepWithContext(r.Context(), delay); err != nil { + r.Error = awserr.New(request.CanceledErrorCode, "request context canceled", err) + r.Retryable = aws.Bool(false) + return + } // Avoid clock skew problems r.Time = now diff --git a/pkg/cloudprovider/providers/aws/volumes.go b/pkg/cloudprovider/providers/aws/volumes.go index 8ff342199d8..2ac932d43d7 100644 --- a/pkg/cloudprovider/providers/aws/volumes.go +++ b/pkg/cloudprovider/providers/aws/volumes.go @@ -19,11 +19,18 @@ package aws import ( "fmt" "net/url" + "regexp" "strings" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/golang/glog" + "k8s.io/apimachinery/pkg/types" ) +// awsVolumeRegMatch represents Regex Match for AWS volume. +var awsVolumeRegMatch = regexp.MustCompile("^vol-[^/]*$") + // awsVolumeID represents the ID of the volume in the AWS API, e.g. vol-12345678 // The "traditional" format is "vol-12345678" // A new longer format is also being introduced: "vol-12345678abcdef01" @@ -42,8 +49,18 @@ func (i awsVolumeID) awsString() *string { // * type KubernetesVolumeID string -// mapToAWSVolumeID extracts the awsVolumeID from the KubernetesVolumeID -func (name KubernetesVolumeID) mapToAWSVolumeID() (awsVolumeID, error) { +// DiskInfo returns aws disk information in easy to use manner +type diskInfo struct { + ec2Instance *ec2.Instance + nodeName types.NodeName + volumeState string + attachmentState string + hasAttachment bool + disk *awsDisk +} + +// MapToAWSVolumeID extracts the awsVolumeID from the KubernetesVolumeID +func (name KubernetesVolumeID) MapToAWSVolumeID() (awsVolumeID, error) { // name looks like aws://availability-zone/awsVolumeId // The original idea of the URL-style name was to put the AZ into the @@ -75,10 +92,61 @@ func (name KubernetesVolumeID) mapToAWSVolumeID() (awsVolumeID, error) { // We sanity check the resulting volume; the two known formats are // vol-12345678 and vol-12345678abcdef01 - // TODO: Regex match? - if strings.Contains(awsID, "/") || !strings.HasPrefix(awsID, "vol-") { + if !awsVolumeRegMatch.MatchString(awsID) { return "", fmt.Errorf("Invalid format for AWS volume (%s)", name) } return awsVolumeID(awsID), nil } + +func GetAWSVolumeID(kubeVolumeID string) (string, error) { + kid := KubernetesVolumeID(kubeVolumeID) + awsID, err := kid.MapToAWSVolumeID() + return string(awsID), err +} + +func (c *Cloud) checkIfAttachedToNode(diskName KubernetesVolumeID, nodeName types.NodeName) (*diskInfo, bool, error) { + disk, err := newAWSDisk(c, diskName) + + if err != nil { + return nil, true, err + } + + awsDiskInfo := &diskInfo{ + disk: disk, + } + + info, err := disk.describeVolume() + + if err != nil { + describeError := fmt.Errorf("Error describing volume %s with %v", diskName, err) + glog.Warning(describeError) + awsDiskInfo.volumeState = "unknown" + return awsDiskInfo, false, describeError + } + + awsDiskInfo.volumeState = aws.StringValue(info.State) + + if len(info.Attachments) > 0 { + attachment := info.Attachments[0] + awsDiskInfo.attachmentState = aws.StringValue(attachment.State) + instanceID := aws.StringValue(attachment.InstanceId) + instanceInfo, err := c.getInstanceByID(instanceID) + + // This should never happen but if it does it could mean there was a race and instance + // has been deleted + if err != nil { + fetchErr := fmt.Errorf("Error fetching instance %s for volume %s", instanceID, diskName) + glog.Warning(fetchErr) + return awsDiskInfo, false, fetchErr + } + + awsDiskInfo.ec2Instance = instanceInfo + awsDiskInfo.nodeName = mapInstanceToNodeName(instanceInfo) + awsDiskInfo.hasAttachment = true + if awsDiskInfo.nodeName == nodeName { + return awsDiskInfo, true, nil + } + } + return awsDiskInfo, false, nil +} diff --git a/pkg/cloudprovider/providers/azure/BUILD b/pkg/cloudprovider/providers/azure/BUILD index 8aa8da13c15..fe131367e0c 100644 --- a/pkg/cloudprovider/providers/azure/BUILD +++ b/pkg/cloudprovider/providers/azure/BUILD @@ -13,8 +13,8 @@ go_library( "azure_backoff.go", "azure_blobDiskController.go", "azure_controllerCommon.go", + "azure_fakes.go", "azure_file.go", - "azure_instance_metadata.go", "azure_instances.go", "azure_loadbalancer.go", "azure_managedDiskController.go", @@ -22,6 +22,9 @@ go_library( "azure_storage.go", "azure_storageaccount.go", "azure_util.go", + "azure_util_cache.go", + "azure_util_vmss.go", + "azure_vmsets.go", "azure_wrap.go", "azure_zones.go", ], @@ -29,6 +32,7 @@ go_library( deps = [ "//pkg/api/v1/service:go_default_library", "//pkg/cloudprovider:go_default_library", + "//pkg/cloudprovider/providers/azure/auth:go_default_library", "//pkg/controller:go_default_library", "//pkg/version:go_default_library", "//pkg/volume:go_default_library", @@ -38,32 +42,45 @@ go_library( "//vendor/github.com/Azure/azure-sdk-for-go/arm/storage:go_default_library", "//vendor/github.com/Azure/azure-sdk-for-go/storage:go_default_library", "//vendor/github.com/Azure/go-autorest/autorest:go_default_library", - "//vendor/github.com/Azure/go-autorest/autorest/adal:go_default_library", "//vendor/github.com/Azure/go-autorest/autorest/azure:go_default_library", "//vendor/github.com/Azure/go-autorest/autorest/to:go_default_library", "//vendor/github.com/ghodss/yaml:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/rubiojr/go-vhd/vhd:go_default_library", - "//vendor/golang.org/x/crypto/pkcs12:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/client-go/tools/cache:go_default_library", "//vendor/k8s.io/client-go/util/flowcontrol:go_default_library", ], ) go_test( name = "go_default_test", - srcs = ["azure_test.go"], + srcs = [ + "azure_loadbalancer_test.go", + "azure_test.go", + "azure_util_cache_test.go", + "azure_util_test.go", + "azure_wrap_test.go", + ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/azure", - library = ":go_default_library", deps = [ "//pkg/api/v1/service:go_default_library", + "//pkg/cloudprovider/providers/azure/auth:go_default_library", + "//pkg/kubelet/apis:go_default_library", + "//vendor/github.com/Azure/azure-sdk-for-go/arm/compute:go_default_library", "//vendor/github.com/Azure/azure-sdk-for-go/arm/network:go_default_library", + "//vendor/github.com/Azure/go-autorest/autorest:go_default_library", "//vendor/github.com/Azure/go-autorest/autorest/to:go_default_library", + "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/client-go/util/flowcontrol:go_default_library", ], ) @@ -76,6 +93,9 @@ filegroup( filegroup( name = "all-srcs", - srcs = [":package-srcs"], + srcs = [ + ":package-srcs", + "//pkg/cloudprovider/providers/azure/auth:all-srcs", + ], tags = ["automanaged"], ) diff --git a/pkg/cloudprovider/providers/azure/OWNERS b/pkg/cloudprovider/providers/azure/OWNERS index 9e1337493fe..535c3ff2dce 100644 --- a/pkg/cloudprovider/providers/azure/OWNERS +++ b/pkg/cloudprovider/providers/azure/OWNERS @@ -1,4 +1,16 @@ approvers: +- andyzhangx - brendandburns - colemickens +- feiskyer - jdumars +- karataliu +- khenidak +reviewers: +- andyzhangx +- brendandburns +- colemickens +- feiskyer +- jdumars +- karataliu +- khenidak diff --git a/pkg/cloudprovider/providers/azure/auth/BUILD b/pkg/cloudprovider/providers/azure/auth/BUILD new file mode 100644 index 00000000000..cc733d385aa --- /dev/null +++ b/pkg/cloudprovider/providers/azure/auth/BUILD @@ -0,0 +1,28 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["azure_auth.go"], + importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/azure/auth", + visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/Azure/go-autorest/autorest/adal:go_default_library", + "//vendor/github.com/Azure/go-autorest/autorest/azure:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/golang.org/x/crypto/pkcs12:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/cloudprovider/providers/azure/auth/azure_auth.go b/pkg/cloudprovider/providers/azure/auth/azure_auth.go new file mode 100644 index 00000000000..948206f8eb6 --- /dev/null +++ b/pkg/cloudprovider/providers/azure/auth/azure_auth.go @@ -0,0 +1,124 @@ +/* +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 auth + +import ( + "crypto/rsa" + "crypto/x509" + "fmt" + "io/ioutil" + + "github.com/Azure/go-autorest/autorest/adal" + "github.com/Azure/go-autorest/autorest/azure" + "github.com/golang/glog" + "golang.org/x/crypto/pkcs12" +) + +// AzureAuthConfig holds auth related part of cloud config +type AzureAuthConfig struct { + // The cloud environment identifier. Takes values from https://github.com/Azure/go-autorest/blob/ec5f4903f77ed9927ac95b19ab8e44ada64c1356/autorest/azure/environments.go#L13 + Cloud string `json:"cloud" yaml:"cloud"` + // The AAD Tenant ID for the Subscription that the cluster is deployed in + TenantID string `json:"tenantId" yaml:"tenantId"` + // The ClientID for an AAD application with RBAC access to talk to Azure RM APIs + AADClientID string `json:"aadClientId" yaml:"aadClientId"` + // The ClientSecret for an AAD application with RBAC access to talk to Azure RM APIs + AADClientSecret string `json:"aadClientSecret" yaml:"aadClientSecret"` + // The path of a client certificate for an AAD application with RBAC access to talk to Azure RM APIs + AADClientCertPath string `json:"aadClientCertPath" yaml:"aadClientCertPath"` + // The password of the client certificate for an AAD application with RBAC access to talk to Azure RM APIs + AADClientCertPassword string `json:"aadClientCertPassword" yaml:"aadClientCertPassword"` + // Use managed service identity for the virtual machine to access Azure ARM APIs + UseManagedIdentityExtension bool `json:"useManagedIdentityExtension"` + // The ID of the Azure Subscription that the cluster is deployed in + SubscriptionID string `json:"subscriptionId" yaml:"subscriptionId"` +} + +// GetServicePrincipalToken creates a new service principal token based on the configuration +func GetServicePrincipalToken(config *AzureAuthConfig, env *azure.Environment) (*adal.ServicePrincipalToken, error) { + oauthConfig, err := adal.NewOAuthConfig(env.ActiveDirectoryEndpoint, config.TenantID) + if err != nil { + return nil, fmt.Errorf("creating the OAuth config: %v", err) + } + + if config.UseManagedIdentityExtension { + glog.V(2).Infoln("azure: using managed identity extension to retrieve access token") + msiEndpoint, err := adal.GetMSIVMEndpoint() + if err != nil { + return nil, fmt.Errorf("Getting the managed service identity endpoint: %v", err) + } + return adal.NewServicePrincipalTokenFromMSI( + msiEndpoint, + env.ServiceManagementEndpoint) + } + + if len(config.AADClientSecret) > 0 { + glog.V(2).Infoln("azure: using client_id+client_secret to retrieve access token") + return adal.NewServicePrincipalToken( + *oauthConfig, + config.AADClientID, + config.AADClientSecret, + env.ServiceManagementEndpoint) + } + + if len(config.AADClientCertPath) > 0 && len(config.AADClientCertPassword) > 0 { + glog.V(2).Infoln("azure: using jwt client_assertion (client_cert+client_private_key) to retrieve access token") + certData, err := ioutil.ReadFile(config.AADClientCertPath) + if err != nil { + return nil, fmt.Errorf("reading the client certificate from file %s: %v", config.AADClientCertPath, err) + } + certificate, privateKey, err := decodePkcs12(certData, config.AADClientCertPassword) + if err != nil { + return nil, fmt.Errorf("decoding the client certificate: %v", err) + } + return adal.NewServicePrincipalTokenFromCertificate( + *oauthConfig, + config.AADClientID, + certificate, + privateKey, + env.ServiceManagementEndpoint) + } + + return nil, fmt.Errorf("No credentials provided for AAD application %s", config.AADClientID) +} + +// ParseAzureEnvironment returns azure environment by name +func ParseAzureEnvironment(cloudName string) (*azure.Environment, error) { + var env azure.Environment + var err error + if cloudName == "" { + env = azure.PublicCloud + } else { + env, err = azure.EnvironmentFromName(cloudName) + } + return &env, err +} + +// decodePkcs12 decodes a PKCS#12 client certificate by extracting the public certificate and +// the private RSA key +func decodePkcs12(pkcs []byte, password string) (*x509.Certificate, *rsa.PrivateKey, error) { + privateKey, certificate, err := pkcs12.Decode(pkcs, password) + if err != nil { + return nil, nil, fmt.Errorf("decoding the PKCS#12 client certificate: %v", err) + } + rsaPrivateKey, isRsaKey := privateKey.(*rsa.PrivateKey) + if !isRsaKey { + return nil, nil, fmt.Errorf("PKCS#12 certificate must contain a RSA private key") + } + + return certificate, rsaPrivateKey, nil +} diff --git a/pkg/cloudprovider/providers/azure/azure.go b/pkg/cloudprovider/providers/azure/azure.go index 44a91bb8790..5435036e6b8 100644 --- a/pkg/cloudprovider/providers/azure/azure.go +++ b/pkg/cloudprovider/providers/azure/azure.go @@ -17,15 +17,16 @@ limitations under the License. package azure import ( - "crypto/rsa" - "crypto/x509" "fmt" "io" "io/ioutil" + "strings" "time" + "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/util/flowcontrol" "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/cloudprovider/providers/azure/auth" "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/version" @@ -34,34 +35,31 @@ import ( "github.com/Azure/azure-sdk-for-go/arm/network" "github.com/Azure/azure-sdk-for-go/arm/storage" "github.com/Azure/go-autorest/autorest" - "github.com/Azure/go-autorest/autorest/adal" "github.com/Azure/go-autorest/autorest/azure" "github.com/ghodss/yaml" "github.com/golang/glog" - "golang.org/x/crypto/pkcs12" - "k8s.io/apimachinery/pkg/util/wait" ) const ( // CloudProviderName is the value used for the --cloud-provider flag - CloudProviderName = "azure" - rateLimitQPSDefault = 1.0 - rateLimitBucketDefault = 5 - backoffRetriesDefault = 6 - backoffExponentDefault = 1.5 - backoffDurationDefault = 5 // in seconds - backoffJitterDefault = 1.0 + CloudProviderName = "azure" + rateLimitQPSDefault = 1.0 + rateLimitBucketDefault = 5 + backoffRetriesDefault = 6 + backoffExponentDefault = 1.5 + backoffDurationDefault = 5 // in seconds + backoffJitterDefault = 1.0 + maximumLoadBalancerRuleCount = 148 // According to Azure LB rule default limit + + vmTypeVMSS = "vmss" + vmTypeStandard = "standard" ) // Config holds the configuration parsed from the --cloud-config flag // All fields are required unless otherwise specified type Config struct { - // The cloud environment identifier. Takes values from https://github.com/Azure/go-autorest/blob/ec5f4903f77ed9927ac95b19ab8e44ada64c1356/autorest/azure/environments.go#L13 - Cloud string `json:"cloud" yaml:"cloud"` - // The AAD Tenant ID for the Subscription that the cluster is deployed in - TenantID string `json:"tenantId" yaml:"tenantId"` - // The ID of the Azure Subscription that the cluster is deployed in - SubscriptionID string `json:"subscriptionId" yaml:"subscriptionId"` + auth.AzureAuthConfig + // The name of the resource group that the cluster is deployed in ResourceGroup string `json:"resourceGroup" yaml:"resourceGroup"` // The location of the resource group that the cluster is deployed in @@ -82,15 +80,15 @@ type Config struct { // the cloudprovider will try to add all nodes to a single backend pool which is forbidden. // In other words, if you use multiple agent pools (availability sets), you MUST set this field. PrimaryAvailabilitySetName string `json:"primaryAvailabilitySetName" yaml:"primaryAvailabilitySetName"` - - // The ClientID for an AAD application with RBAC access to talk to Azure RM APIs - AADClientID string `json:"aadClientId" yaml:"aadClientId"` - // The ClientSecret for an AAD application with RBAC access to talk to Azure RM APIs - AADClientSecret string `json:"aadClientSecret" yaml:"aadClientSecret"` - // The path of a client certificate for an AAD application with RBAC access to talk to Azure RM APIs - AADClientCertPath string `json:"aadClientCertPath" yaml:"aadClientCertPath"` - // The password of the client certificate for an AAD application with RBAC access to talk to Azure RM APIs - AADClientCertPassword string `json:"aadClientCertPassword" yaml:"aadClientCertPassword"` + // The type of azure nodes. Candidate valudes are: vmss and standard. + // If not set, it will be default to standard. + VMType string `json:"vmType" yaml:"vmType"` + // The name of the scale set that should be used as the load balancer backend. + // If this is set, the Azure cloudprovider will only add nodes from that scale set to the load + // balancer backend pool. If this is not set, and multiple agent pools (scale sets) are used, then + // the cloudprovider will try to add all nodes to a single backend pool which is forbidden. + // In other words, if you use multiple agent pools (scale sets), you MUST set this field. + PrimaryScaleSetName string `json:"primaryScaleSetName" yaml:"primaryScaleSetName"` // Enable exponential backoff to manage resource request retries CloudProviderBackoff bool `json:"cloudProviderBackoff" yaml:"cloudProviderBackoff"` // Backoff retry limit @@ -108,30 +106,125 @@ type Config struct { // Rate limit Bucket Size CloudProviderRateLimitBucket int `json:"cloudProviderRateLimitBucket" yaml:"cloudProviderRateLimitBucket"` - // Use instance metadata service where possible - UseInstanceMetadata bool `json:"useInstanceMetadata" yaml:"useInstanceMetadata"` + // Maximum allowed LoadBalancer Rule Count is the limit enforced by Azure Load balancer + MaximumLoadBalancerRuleCount int `json:"maximumLoadBalancerRuleCount"` +} - // Use managed service identity for the virtual machine to access Azure ARM APIs - UseManagedIdentityExtension bool `json:"useManagedIdentityExtension"` +// VirtualMachinesClient defines needed functions for azure compute.VirtualMachinesClient +type VirtualMachinesClient interface { + CreateOrUpdate(resourceGroupName string, VMName string, parameters compute.VirtualMachine, cancel <-chan struct{}) (<-chan compute.VirtualMachine, <-chan error) + Get(resourceGroupName string, VMName string, expand compute.InstanceViewTypes) (result compute.VirtualMachine, err error) + List(resourceGroupName string) (result compute.VirtualMachineListResult, err error) + ListNextResults(lastResults compute.VirtualMachineListResult) (result compute.VirtualMachineListResult, err error) +} + +// InterfacesClient defines needed functions for azure network.InterfacesClient +type InterfacesClient interface { + CreateOrUpdate(resourceGroupName string, networkInterfaceName string, parameters network.Interface, cancel <-chan struct{}) (<-chan network.Interface, <-chan error) + Get(resourceGroupName string, networkInterfaceName string, expand string) (result network.Interface, err error) + GetVirtualMachineScaleSetNetworkInterface(resourceGroupName string, virtualMachineScaleSetName string, virtualmachineIndex string, networkInterfaceName string, expand string) (result network.Interface, err error) +} + +// LoadBalancersClient defines needed functions for azure network.LoadBalancersClient +type LoadBalancersClient interface { + CreateOrUpdate(resourceGroupName string, loadBalancerName string, parameters network.LoadBalancer, cancel <-chan struct{}) (<-chan network.LoadBalancer, <-chan error) + Delete(resourceGroupName string, loadBalancerName string, cancel <-chan struct{}) (<-chan autorest.Response, <-chan error) + Get(resourceGroupName string, loadBalancerName string, expand string) (result network.LoadBalancer, err error) + List(resourceGroupName string) (result network.LoadBalancerListResult, err error) + ListNextResults(lastResult network.LoadBalancerListResult) (result network.LoadBalancerListResult, err error) +} + +// PublicIPAddressesClient defines needed functions for azure network.PublicIPAddressesClient +type PublicIPAddressesClient interface { + CreateOrUpdate(resourceGroupName string, publicIPAddressName string, parameters network.PublicIPAddress, cancel <-chan struct{}) (<-chan network.PublicIPAddress, <-chan error) + Delete(resourceGroupName string, publicIPAddressName string, cancel <-chan struct{}) (<-chan autorest.Response, <-chan error) + Get(resourceGroupName string, publicIPAddressName string, expand string) (result network.PublicIPAddress, err error) + List(resourceGroupName string) (result network.PublicIPAddressListResult, err error) + ListNextResults(lastResults network.PublicIPAddressListResult) (result network.PublicIPAddressListResult, err error) +} + +// SubnetsClient defines needed functions for azure network.SubnetsClient +type SubnetsClient interface { + CreateOrUpdate(resourceGroupName string, virtualNetworkName string, subnetName string, subnetParameters network.Subnet, cancel <-chan struct{}) (<-chan network.Subnet, <-chan error) + Delete(resourceGroupName string, virtualNetworkName string, subnetName string, cancel <-chan struct{}) (<-chan autorest.Response, <-chan error) + Get(resourceGroupName string, virtualNetworkName string, subnetName string, expand string) (result network.Subnet, err error) + List(resourceGroupName string, virtualNetworkName string) (result network.SubnetListResult, err error) +} + +// SecurityGroupsClient defines needed functions for azure network.SecurityGroupsClient +type SecurityGroupsClient interface { + CreateOrUpdate(resourceGroupName string, networkSecurityGroupName string, parameters network.SecurityGroup, cancel <-chan struct{}) (<-chan network.SecurityGroup, <-chan error) + Delete(resourceGroupName string, networkSecurityGroupName string, cancel <-chan struct{}) (<-chan autorest.Response, <-chan error) + Get(resourceGroupName string, networkSecurityGroupName string, expand string) (result network.SecurityGroup, err error) + List(resourceGroupName string) (result network.SecurityGroupListResult, err error) +} + +// VirtualMachineScaleSetsClient defines needed functions for azure compute.VirtualMachineScaleSetsClient +type VirtualMachineScaleSetsClient interface { + CreateOrUpdate(resourceGroupName string, VMScaleSetName string, parameters compute.VirtualMachineScaleSet, cancel <-chan struct{}) (<-chan compute.VirtualMachineScaleSet, <-chan error) + Get(resourceGroupName string, VMScaleSetName string) (result compute.VirtualMachineScaleSet, err error) + List(resourceGroupName string) (result compute.VirtualMachineScaleSetListResult, err error) + ListNextResults(lastResults compute.VirtualMachineScaleSetListResult) (result compute.VirtualMachineScaleSetListResult, err error) + UpdateInstances(resourceGroupName string, VMScaleSetName string, VMInstanceIDs compute.VirtualMachineScaleSetVMInstanceRequiredIDs, cancel <-chan struct{}) (<-chan compute.OperationStatusResponse, <-chan error) +} + +// VirtualMachineScaleSetVMsClient defines needed functions for azure compute.VirtualMachineScaleSetVMsClient +type VirtualMachineScaleSetVMsClient interface { + Get(resourceGroupName string, VMScaleSetName string, instanceID string) (result compute.VirtualMachineScaleSetVM, err error) + GetInstanceView(resourceGroupName string, VMScaleSetName string, instanceID string) (result compute.VirtualMachineScaleSetVMInstanceView, err error) + List(resourceGroupName string, virtualMachineScaleSetName string, filter string, selectParameter string, expand string) (result compute.VirtualMachineScaleSetVMListResult, err error) + ListNextResults(lastResults compute.VirtualMachineScaleSetVMListResult) (result compute.VirtualMachineScaleSetVMListResult, err error) +} + +// RoutesClient defines needed functions for azure network.RoutesClient +type RoutesClient interface { + CreateOrUpdate(resourceGroupName string, routeTableName string, routeName string, routeParameters network.Route, cancel <-chan struct{}) (<-chan network.Route, <-chan error) + Delete(resourceGroupName string, routeTableName string, routeName string, cancel <-chan struct{}) (<-chan autorest.Response, <-chan error) +} + +// RouteTablesClient defines needed functions for azure network.RouteTablesClient +type RouteTablesClient interface { + CreateOrUpdate(resourceGroupName string, routeTableName string, parameters network.RouteTable, cancel <-chan struct{}) (<-chan network.RouteTable, <-chan error) + Get(resourceGroupName string, routeTableName string, expand string) (result network.RouteTable, err error) +} + +// StorageAccountClient defines needed functions for azure storage.AccountsClient +type StorageAccountClient interface { + Create(resourceGroupName string, accountName string, parameters storage.AccountCreateParameters, cancel <-chan struct{}) (<-chan storage.Account, <-chan error) + Delete(resourceGroupName string, accountName string) (result autorest.Response, err error) + ListKeys(resourceGroupName string, accountName string) (result storage.AccountListKeysResult, err error) + ListByResourceGroup(resourceGroupName string) (result storage.AccountListResult, err error) + GetProperties(resourceGroupName string, accountName string) (result storage.Account, err error) +} + +// DisksClient defines needed functions for azure disk.DisksClient +type DisksClient interface { + CreateOrUpdate(resourceGroupName string, diskName string, diskParameter disk.Model, cancel <-chan struct{}) (<-chan disk.Model, <-chan error) + Delete(resourceGroupName string, diskName string, cancel <-chan struct{}) (<-chan disk.OperationStatusResponse, <-chan error) + Get(resourceGroupName string, diskName string) (result disk.Model, err error) } // Cloud holds the config and clients type Cloud struct { Config Environment azure.Environment - RoutesClient network.RoutesClient - SubnetsClient network.SubnetsClient - InterfacesClient network.InterfacesClient - RouteTablesClient network.RouteTablesClient - LoadBalancerClient network.LoadBalancersClient - PublicIPAddressesClient network.PublicIPAddressesClient - SecurityGroupsClient network.SecurityGroupsClient - VirtualMachinesClient compute.VirtualMachinesClient - StorageAccountClient storage.AccountsClient - DisksClient disk.DisksClient + RoutesClient RoutesClient + SubnetsClient SubnetsClient + InterfacesClient InterfacesClient + RouteTablesClient RouteTablesClient + LoadBalancerClient LoadBalancersClient + PublicIPAddressesClient PublicIPAddressesClient + SecurityGroupsClient SecurityGroupsClient + VirtualMachinesClient VirtualMachinesClient + StorageAccountClient StorageAccountClient + DisksClient DisksClient operationPollRateLimiter flowcontrol.RateLimiter resourceRequestBackoff wait.Backoff - metadata *InstanceMetadata + vmSet VMSet + + // Clients for vmss. + VirtualMachineScaleSetsClient VirtualMachineScaleSetsClient + VirtualMachineScaleSetVMsClient VirtualMachineScaleSetVMsClient *BlobDiskController *ManagedDiskController @@ -142,136 +235,111 @@ func init() { cloudprovider.RegisterCloudProvider(CloudProviderName, NewCloud) } -// decodePkcs12 decodes a PKCS#12 client certificate by extracting the public certificate and -// the private RSA key -func decodePkcs12(pkcs []byte, password string) (*x509.Certificate, *rsa.PrivateKey, error) { - privateKey, certificate, err := pkcs12.Decode(pkcs, password) - if err != nil { - return nil, nil, fmt.Errorf("decoding the PKCS#12 client certificate: %v", err) - } - rsaPrivateKey, isRsaKey := privateKey.(*rsa.PrivateKey) - if !isRsaKey { - return nil, nil, fmt.Errorf("PKCS#12 certificate must contain a RSA private key") - } - - return certificate, rsaPrivateKey, nil -} - -// GetServicePrincipalToken creates a new service principal token based on the configuration -func GetServicePrincipalToken(config *Config, env *azure.Environment) (*adal.ServicePrincipalToken, error) { - oauthConfig, err := adal.NewOAuthConfig(env.ActiveDirectoryEndpoint, config.TenantID) - if err != nil { - return nil, fmt.Errorf("creating the OAuth config: %v", err) - } - - if config.UseManagedIdentityExtension { - glog.V(2).Infoln("azure: using managed identity extension to retrieve access token") - return adal.NewServicePrincipalTokenFromMSI( - *oauthConfig, - env.ServiceManagementEndpoint) - } - - if len(config.AADClientSecret) > 0 { - glog.V(2).Infoln("azure: using client_id+client_secret to retrieve access token") - return adal.NewServicePrincipalToken( - *oauthConfig, - config.AADClientID, - config.AADClientSecret, - env.ServiceManagementEndpoint) - } - - if len(config.AADClientCertPath) > 0 && len(config.AADClientCertPassword) > 0 { - glog.V(2).Infoln("azure: using jwt client_assertion (client_cert+client_private_key) to retrieve access token") - certData, err := ioutil.ReadFile(config.AADClientCertPath) - if err != nil { - return nil, fmt.Errorf("reading the client certificate from file %s: %v", config.AADClientCertPath, err) - } - certificate, privateKey, err := decodePkcs12(certData, config.AADClientCertPassword) - if err != nil { - return nil, fmt.Errorf("decoding the client certificate: %v", err) - } - return adal.NewServicePrincipalTokenFromCertificate( - *oauthConfig, - config.AADClientID, - certificate, - privateKey, - env.ServiceManagementEndpoint) - } - - return nil, fmt.Errorf("No credentials provided for AAD application %s", config.AADClientID) -} - // NewCloud returns a Cloud with initialized clients func NewCloud(configReader io.Reader) (cloudprovider.Interface, error) { - config, env, err := ParseConfig(configReader) + config, err := parseConfig(configReader) if err != nil { return nil, err } + + env, err := auth.ParseAzureEnvironment(config.Cloud) + if err != nil { + return nil, err + } + az := Cloud{ Config: *config, Environment: *env, } - servicePrincipalToken, err := GetServicePrincipalToken(config, env) + servicePrincipalToken, err := auth.GetServicePrincipalToken(&config.AzureAuthConfig, env) if err != nil { return nil, err } - az.SubnetsClient = network.NewSubnetsClient(az.SubscriptionID) - az.SubnetsClient.BaseURI = az.Environment.ResourceManagerEndpoint - az.SubnetsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) - az.SubnetsClient.PollingDelay = 5 * time.Second - configureUserAgent(&az.SubnetsClient.Client) + subnetsClient := network.NewSubnetsClient(az.SubscriptionID) + subnetsClient.BaseURI = az.Environment.ResourceManagerEndpoint + subnetsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) + subnetsClient.PollingDelay = 5 * time.Second + configureUserAgent(&subnetsClient.Client) + az.SubnetsClient = subnetsClient - az.RouteTablesClient = network.NewRouteTablesClient(az.SubscriptionID) - az.RouteTablesClient.BaseURI = az.Environment.ResourceManagerEndpoint - az.RouteTablesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) - az.RouteTablesClient.PollingDelay = 5 * time.Second - configureUserAgent(&az.RouteTablesClient.Client) + routeTablesClient := network.NewRouteTablesClient(az.SubscriptionID) + routeTablesClient.BaseURI = az.Environment.ResourceManagerEndpoint + routeTablesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) + routeTablesClient.PollingDelay = 5 * time.Second + configureUserAgent(&routeTablesClient.Client) + az.RouteTablesClient = routeTablesClient - az.RoutesClient = network.NewRoutesClient(az.SubscriptionID) - az.RoutesClient.BaseURI = az.Environment.ResourceManagerEndpoint - az.RoutesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) - az.RoutesClient.PollingDelay = 5 * time.Second - configureUserAgent(&az.RoutesClient.Client) + routesClient := network.NewRoutesClient(az.SubscriptionID) + routesClient.BaseURI = az.Environment.ResourceManagerEndpoint + routesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) + routesClient.PollingDelay = 5 * time.Second + configureUserAgent(&routesClient.Client) + az.RoutesClient = routesClient - az.InterfacesClient = network.NewInterfacesClient(az.SubscriptionID) - az.InterfacesClient.BaseURI = az.Environment.ResourceManagerEndpoint - az.InterfacesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) - az.InterfacesClient.PollingDelay = 5 * time.Second - configureUserAgent(&az.InterfacesClient.Client) + interfacesClient := network.NewInterfacesClient(az.SubscriptionID) + interfacesClient.BaseURI = az.Environment.ResourceManagerEndpoint + interfacesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) + interfacesClient.PollingDelay = 5 * time.Second + configureUserAgent(&interfacesClient.Client) + az.InterfacesClient = interfacesClient - az.LoadBalancerClient = network.NewLoadBalancersClient(az.SubscriptionID) - az.LoadBalancerClient.BaseURI = az.Environment.ResourceManagerEndpoint - az.LoadBalancerClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) - az.LoadBalancerClient.PollingDelay = 5 * time.Second - configureUserAgent(&az.LoadBalancerClient.Client) + loadBalancerClient := network.NewLoadBalancersClient(az.SubscriptionID) + loadBalancerClient.BaseURI = az.Environment.ResourceManagerEndpoint + loadBalancerClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) + loadBalancerClient.PollingDelay = 5 * time.Second + configureUserAgent(&loadBalancerClient.Client) + az.LoadBalancerClient = loadBalancerClient - az.VirtualMachinesClient = compute.NewVirtualMachinesClient(az.SubscriptionID) - az.VirtualMachinesClient.BaseURI = az.Environment.ResourceManagerEndpoint - az.VirtualMachinesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) - az.VirtualMachinesClient.PollingDelay = 5 * time.Second - configureUserAgent(&az.VirtualMachinesClient.Client) + virtualMachinesClient := compute.NewVirtualMachinesClient(az.SubscriptionID) + virtualMachinesClient.BaseURI = az.Environment.ResourceManagerEndpoint + virtualMachinesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) + virtualMachinesClient.PollingDelay = 5 * time.Second + configureUserAgent(&virtualMachinesClient.Client) + az.VirtualMachinesClient = virtualMachinesClient - az.PublicIPAddressesClient = network.NewPublicIPAddressesClient(az.SubscriptionID) - az.PublicIPAddressesClient.BaseURI = az.Environment.ResourceManagerEndpoint - az.PublicIPAddressesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) - az.PublicIPAddressesClient.PollingDelay = 5 * time.Second - configureUserAgent(&az.PublicIPAddressesClient.Client) + publicIPAddressClient := network.NewPublicIPAddressesClient(az.SubscriptionID) + publicIPAddressClient.BaseURI = az.Environment.ResourceManagerEndpoint + publicIPAddressClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) + publicIPAddressClient.PollingDelay = 5 * time.Second + configureUserAgent(&publicIPAddressClient.Client) + az.PublicIPAddressesClient = publicIPAddressClient - az.SecurityGroupsClient = network.NewSecurityGroupsClient(az.SubscriptionID) - az.SecurityGroupsClient.BaseURI = az.Environment.ResourceManagerEndpoint - az.SecurityGroupsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) - az.SecurityGroupsClient.PollingDelay = 5 * time.Second - configureUserAgent(&az.SecurityGroupsClient.Client) + securityGroupsClient := network.NewSecurityGroupsClient(az.SubscriptionID) + securityGroupsClient.BaseURI = az.Environment.ResourceManagerEndpoint + securityGroupsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) + securityGroupsClient.PollingDelay = 5 * time.Second + configureUserAgent(&securityGroupsClient.Client) + az.SecurityGroupsClient = securityGroupsClient - az.StorageAccountClient = storage.NewAccountsClientWithBaseURI(az.Environment.ResourceManagerEndpoint, az.SubscriptionID) - az.StorageAccountClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) - configureUserAgent(&az.StorageAccountClient.Client) + virtualMachineScaleSetVMsClient := compute.NewVirtualMachineScaleSetVMsClient(az.SubscriptionID) + virtualMachineScaleSetVMsClient.BaseURI = az.Environment.ResourceManagerEndpoint + virtualMachineScaleSetVMsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) + virtualMachineScaleSetVMsClient.PollingDelay = 5 * time.Second + configureUserAgent(&virtualMachineScaleSetVMsClient.Client) + az.VirtualMachineScaleSetVMsClient = virtualMachineScaleSetVMsClient - az.DisksClient = disk.NewDisksClientWithBaseURI(az.Environment.ResourceManagerEndpoint, az.SubscriptionID) - az.DisksClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) - configureUserAgent(&az.DisksClient.Client) + virtualMachineScaleSetsClient := compute.NewVirtualMachineScaleSetsClient(az.SubscriptionID) + virtualMachineScaleSetsClient.BaseURI = az.Environment.ResourceManagerEndpoint + virtualMachineScaleSetsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) + virtualMachineScaleSetsClient.PollingDelay = 5 * time.Second + configureUserAgent(&virtualMachineScaleSetsClient.Client) + az.VirtualMachineScaleSetsClient = virtualMachineScaleSetsClient + + storageAccountClient := storage.NewAccountsClientWithBaseURI(az.Environment.ResourceManagerEndpoint, az.SubscriptionID) + storageAccountClient.BaseURI = az.Environment.ResourceManagerEndpoint + storageAccountClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) + storageAccountClient.PollingDelay = 5 * time.Second + configureUserAgent(&storageAccountClient.Client) + az.StorageAccountClient = storageAccountClient + + disksClient := disk.NewDisksClientWithBaseURI(az.Environment.ResourceManagerEndpoint, az.SubscriptionID) + disksClient.BaseURI = az.Environment.ResourceManagerEndpoint + disksClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) + disksClient.PollingDelay = 5 * time.Second + configureUserAgent(&disksClient.Client) + az.DisksClient = disksClient // Conditionally configure rate limits if az.CloudProviderRateLimit { @@ -321,7 +389,15 @@ func NewCloud(configReader io.Reader) (cloudprovider.Interface, error) { az.CloudProviderBackoffJitter) } - az.metadata = NewInstanceMetadata() + if az.MaximumLoadBalancerRuleCount == 0 { + az.MaximumLoadBalancerRuleCount = maximumLoadBalancerRuleCount + } + + if strings.EqualFold(vmTypeVMSS, az.Config.VMType) { + az.vmSet = newScaleSet(&az) + } else { + az.vmSet = newAvailabilitySet(&az) + } if err := initDiskControllers(&az); err != nil { return nil, err @@ -329,33 +405,24 @@ func NewCloud(configReader io.Reader) (cloudprovider.Interface, error) { return &az, nil } -// ParseConfig returns a parsed configuration and azure.Environment for an Azure cloudprovider config file -func ParseConfig(configReader io.Reader) (*Config, *azure.Environment, error) { +// parseConfig returns a parsed configuration for an Azure cloudprovider config file +func parseConfig(configReader io.Reader) (*Config, error) { var config Config - var env azure.Environment if configReader == nil { - return &config, &env, nil + return &config, nil } configContents, err := ioutil.ReadAll(configReader) if err != nil { - return nil, nil, err + return nil, err } err = yaml.Unmarshal(configContents, &config) if err != nil { - return nil, nil, err + return nil, err } - if config.Cloud == "" { - env = azure.PublicCloud - } else { - env, err = azure.EnvironmentFromName(config.Cloud) - if err != nil { - return nil, nil, err - } - } - return &config, &env, nil + return &config, nil } // Initialize passes a Kubernetes clientBuilder interface to the cloud provider @@ -386,11 +453,6 @@ func (az *Cloud) Routes() (cloudprovider.Routes, bool) { return az, true } -// ScrubDNS provides an opportunity for cloud-provider-specific code to process DNS settings for pods. -func (az *Cloud) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) { - return nameservers, searches -} - // HasClusterID returns true if the cluster has a clusterID func (az *Cloud) HasClusterID() bool { return true @@ -422,7 +484,6 @@ func initDiskControllers(az *Cloud) error { storageEndpointSuffix: az.Environment.StorageEndpointSuffix, managementEndpoint: az.Environment.ResourceManagerEndpoint, resourceGroup: az.ResourceGroup, - tenantID: az.TenantID, tokenEndPoint: az.Environment.ActiveDirectoryEndpoint, subscriptionID: az.SubscriptionID, cloud: az, diff --git a/pkg/cloudprovider/providers/azure/azure_backoff.go b/pkg/cloudprovider/providers/azure/azure_backoff.go index b30b1da38b7..099fea81fe1 100644 --- a/pkg/cloudprovider/providers/azure/azure_backoff.go +++ b/pkg/cloudprovider/providers/azure/azure_backoff.go @@ -26,29 +26,27 @@ import ( "k8s.io/apimachinery/pkg/types" ) -// GetVirtualMachineWithRetry invokes az.getVirtualMachine with exponential backoff retry -func (az *Cloud) GetVirtualMachineWithRetry(name types.NodeName) (compute.VirtualMachine, bool, error) { - var machine compute.VirtualMachine - var exists bool - err := wait.ExponentialBackoff(az.resourceRequestBackoff, func() (bool, error) { - var retryErr error - machine, exists, retryErr = az.getVirtualMachine(name) - if retryErr != nil { - glog.Errorf("backoff: failure, will retry,err=%v", retryErr) - return false, nil - } - glog.V(2).Infof("backoff: success") - return true, nil - }) - return machine, exists, err +// requestBackoff if backoff is disabled in cloud provider it +// returns a new Backoff object steps = 1 +// This is to make sure that the requested command executes +// at least once +func (az *Cloud) requestBackoff() (resourceRequestBackoff wait.Backoff) { + if az.CloudProviderBackoff { + return az.resourceRequestBackoff + } + resourceRequestBackoff = wait.Backoff{ + Steps: 1, + } + + return resourceRequestBackoff } -// VirtualMachineClientGetWithRetry invokes az.VirtualMachinesClient.Get with exponential backoff retry -func (az *Cloud) VirtualMachineClientGetWithRetry(resourceGroup, vmName string, types compute.InstanceViewTypes) (compute.VirtualMachine, error) { +// GetVirtualMachineWithRetry invokes az.getVirtualMachine with exponential backoff retry +func (az *Cloud) GetVirtualMachineWithRetry(name types.NodeName) (compute.VirtualMachine, error) { var machine compute.VirtualMachine - err := wait.ExponentialBackoff(az.resourceRequestBackoff, func() (bool, error) { - var retryErr error - machine, retryErr = az.VirtualMachinesClient.Get(resourceGroup, vmName, types) + var retryErr error + err := wait.ExponentialBackoff(az.requestBackoff(), func() (bool, error) { + machine, retryErr = az.getVirtualMachine(name) if retryErr != nil { glog.Errorf("backoff: failure, will retry,err=%v", retryErr) return false, nil @@ -56,13 +54,70 @@ func (az *Cloud) VirtualMachineClientGetWithRetry(resourceGroup, vmName string, glog.V(2).Infof("backoff: success") return true, nil }) + if err == wait.ErrWaitTimeout { + err = retryErr + } + return machine, err } +// VirtualMachineClientListWithRetry invokes az.VirtualMachinesClient.List with exponential backoff retry +func (az *Cloud) VirtualMachineClientListWithRetry() ([]compute.VirtualMachine, error) { + allNodes := []compute.VirtualMachine{} + var result compute.VirtualMachineListResult + err := wait.ExponentialBackoff(az.requestBackoff(), func() (bool, error) { + var retryErr error + az.operationPollRateLimiter.Accept() + glog.V(10).Infof("VirtualMachinesClient.List(%v): start", az.ResourceGroup) + result, retryErr = az.VirtualMachinesClient.List(az.ResourceGroup) + glog.V(10).Infof("VirtualMachinesClient.List(%v): end", az.ResourceGroup) + if retryErr != nil { + glog.Errorf("VirtualMachinesClient.List(%v) - backoff: failure, will retry,err=%v", + az.ResourceGroup, + retryErr) + return false, retryErr + } + glog.V(2).Infof("VirtualMachinesClient.List(%v) - backoff: success", az.ResourceGroup) + return true, nil + }) + if err != nil { + return nil, err + } + + appendResults := (result.Value != nil && len(*result.Value) > 0) + for appendResults { + allNodes = append(allNodes, *result.Value...) + appendResults = false + // follow the next link to get all the vms for resource group + if result.NextLink != nil { + err := wait.ExponentialBackoff(az.requestBackoff(), func() (bool, error) { + var retryErr error + az.operationPollRateLimiter.Accept() + glog.V(10).Infof("VirtualMachinesClient.ListNextResults(%v): start", az.ResourceGroup) + result, retryErr = az.VirtualMachinesClient.ListNextResults(result) + glog.V(10).Infof("VirtualMachinesClient.ListNextResults(%v): end", az.ResourceGroup) + if retryErr != nil { + glog.Errorf("VirtualMachinesClient.ListNextResults(%v) - backoff: failure, will retry,err=%v", + az.ResourceGroup, retryErr) + return false, retryErr + } + glog.V(2).Infof("VirtualMachinesClient.ListNextResults(%v): success", az.ResourceGroup) + return true, nil + }) + if err != nil { + return allNodes, err + } + appendResults = (result.Value != nil && len(*result.Value) > 0) + } + } + + return allNodes, err +} + // GetIPForMachineWithRetry invokes az.getIPForMachine with exponential backoff retry func (az *Cloud) GetIPForMachineWithRetry(name types.NodeName) (string, error) { var ip string - err := wait.ExponentialBackoff(az.resourceRequestBackoff, func() (bool, error) { + err := wait.ExponentialBackoff(az.requestBackoff(), func() (bool, error) { var retryErr error ip, retryErr = az.getIPForMachine(name) if retryErr != nil { @@ -77,7 +132,7 @@ func (az *Cloud) GetIPForMachineWithRetry(name types.NodeName) (string, error) { // CreateOrUpdateSGWithRetry invokes az.SecurityGroupsClient.CreateOrUpdate with exponential backoff retry func (az *Cloud) CreateOrUpdateSGWithRetry(sg network.SecurityGroup) error { - return wait.ExponentialBackoff(az.resourceRequestBackoff, func() (bool, error) { + return wait.ExponentialBackoff(az.requestBackoff(), func() (bool, error) { az.operationPollRateLimiter.Accept() glog.V(10).Infof("SecurityGroupsClient.CreateOrUpdate(%s): start", *sg.Name) respChan, errChan := az.SecurityGroupsClient.CreateOrUpdate(az.ResourceGroup, *sg.Name, sg, nil) @@ -90,7 +145,7 @@ func (az *Cloud) CreateOrUpdateSGWithRetry(sg network.SecurityGroup) error { // CreateOrUpdateLBWithRetry invokes az.LoadBalancerClient.CreateOrUpdate with exponential backoff retry func (az *Cloud) CreateOrUpdateLBWithRetry(lb network.LoadBalancer) error { - return wait.ExponentialBackoff(az.resourceRequestBackoff, func() (bool, error) { + return wait.ExponentialBackoff(az.requestBackoff(), func() (bool, error) { az.operationPollRateLimiter.Accept() glog.V(10).Infof("LoadBalancerClient.CreateOrUpdate(%s): start", *lb.Name) respChan, errChan := az.LoadBalancerClient.CreateOrUpdate(az.ResourceGroup, *lb.Name, lb, nil) @@ -101,22 +156,133 @@ func (az *Cloud) CreateOrUpdateLBWithRetry(lb network.LoadBalancer) error { }) } -// CreateOrUpdatePIPWithRetry invokes az.PublicIPAddressesClient.CreateOrUpdate with exponential backoff retry -func (az *Cloud) CreateOrUpdatePIPWithRetry(pip network.PublicIPAddress) error { - return wait.ExponentialBackoff(az.resourceRequestBackoff, func() (bool, error) { +// ListLBWithRetry invokes az.LoadBalancerClient.List with exponential backoff retry +func (az *Cloud) ListLBWithRetry() ([]network.LoadBalancer, error) { + allLBs := []network.LoadBalancer{} + var result network.LoadBalancerListResult + + err := wait.ExponentialBackoff(az.requestBackoff(), func() (bool, error) { + var retryErr error az.operationPollRateLimiter.Accept() - glog.V(10).Infof("PublicIPAddressesClient.CreateOrUpdate(%s): start", *pip.Name) - respChan, errChan := az.PublicIPAddressesClient.CreateOrUpdate(az.ResourceGroup, *pip.Name, pip, nil) + glog.V(10).Infof("LoadBalancerClient.List(%v): start", az.ResourceGroup) + result, retryErr = az.LoadBalancerClient.List(az.ResourceGroup) + glog.V(10).Infof("LoadBalancerClient.List(%v): end", az.ResourceGroup) + if retryErr != nil { + glog.Errorf("LoadBalancerClient.List(%v) - backoff: failure, will retry,err=%v", + az.ResourceGroup, + retryErr) + return false, retryErr + } + glog.V(2).Infof("LoadBalancerClient.List(%v) - backoff: success", az.ResourceGroup) + return true, nil + }) + if err != nil { + return nil, err + } + + appendResults := (result.Value != nil && len(*result.Value) > 0) + for appendResults { + allLBs = append(allLBs, *result.Value...) + appendResults = false + + // follow the next link to get all the vms for resource group + if result.NextLink != nil { + err := wait.ExponentialBackoff(az.requestBackoff(), func() (bool, error) { + var retryErr error + az.operationPollRateLimiter.Accept() + glog.V(10).Infof("LoadBalancerClient.ListNextResults(%v): start", az.ResourceGroup) + result, retryErr = az.LoadBalancerClient.ListNextResults(result) + glog.V(10).Infof("LoadBalancerClient.ListNextResults(%v): end", az.ResourceGroup) + if retryErr != nil { + glog.Errorf("LoadBalancerClient.ListNextResults(%v) - backoff: failure, will retry,err=%v", + az.ResourceGroup, + retryErr) + return false, retryErr + } + glog.V(2).Infof("LoadBalancerClient.ListNextResults(%v) - backoff: success", az.ResourceGroup) + return true, nil + }) + if err != nil { + return allLBs, err + } + appendResults = (result.Value != nil && len(*result.Value) > 0) + } + } + + return allLBs, nil +} + +// ListPIPWithRetry list the PIP resources in the given resource group +func (az *Cloud) ListPIPWithRetry(pipResourceGroup string) ([]network.PublicIPAddress, error) { + allPIPs := []network.PublicIPAddress{} + var result network.PublicIPAddressListResult + err := wait.ExponentialBackoff(az.requestBackoff(), func() (bool, error) { + var retryErr error + az.operationPollRateLimiter.Accept() + glog.V(10).Infof("PublicIPAddressesClient.List(%v): start", pipResourceGroup) + result, retryErr = az.PublicIPAddressesClient.List(pipResourceGroup) + glog.V(10).Infof("PublicIPAddressesClient.List(%v): end", pipResourceGroup) + if retryErr != nil { + glog.Errorf("PublicIPAddressesClient.List(%v) - backoff: failure, will retry,err=%v", + pipResourceGroup, + retryErr) + return false, retryErr + } + glog.V(2).Infof("PublicIPAddressesClient.List(%v) - backoff: success", pipResourceGroup) + return true, nil + }) + if err != nil { + return nil, err + } + + appendResults := (result.Value != nil && len(*result.Value) > 0) + for appendResults { + allPIPs = append(allPIPs, *result.Value...) + appendResults = false + + // follow the next link to get all the pip resources for resource group + if result.NextLink != nil { + err := wait.ExponentialBackoff(az.requestBackoff(), func() (bool, error) { + var retryErr error + az.operationPollRateLimiter.Accept() + glog.V(10).Infof("PublicIPAddressesClient.ListNextResults(%v): start", pipResourceGroup) + result, retryErr = az.PublicIPAddressesClient.ListNextResults(result) + glog.V(10).Infof("PublicIPAddressesClient.ListNextResults(%v): end", pipResourceGroup) + if retryErr != nil { + glog.Errorf("PublicIPAddressesClient.ListNextResults(%v) - backoff: failure, will retry,err=%v", + pipResourceGroup, + retryErr) + return false, retryErr + } + glog.V(2).Infof("PublicIPAddressesClient.ListNextResults(%v) - backoff: success", pipResourceGroup) + return true, nil + }) + if err != nil { + return allPIPs, err + } + appendResults = (result.Value != nil && len(*result.Value) > 0) + } + } + + return allPIPs, nil +} + +// CreateOrUpdatePIPWithRetry invokes az.PublicIPAddressesClient.CreateOrUpdate with exponential backoff retry +func (az *Cloud) CreateOrUpdatePIPWithRetry(pipResourceGroup string, pip network.PublicIPAddress) error { + return wait.ExponentialBackoff(az.requestBackoff(), func() (bool, error) { + az.operationPollRateLimiter.Accept() + glog.V(10).Infof("PublicIPAddressesClient.CreateOrUpdate(%s, %s): start", pipResourceGroup, *pip.Name) + respChan, errChan := az.PublicIPAddressesClient.CreateOrUpdate(pipResourceGroup, *pip.Name, pip, nil) resp := <-respChan err := <-errChan - glog.V(10).Infof("PublicIPAddressesClient.CreateOrUpdate(%s): end", *pip.Name) + glog.V(10).Infof("PublicIPAddressesClient.CreateOrUpdate(%s, %s): end", pipResourceGroup, *pip.Name) return processRetryResponse(resp.Response, err) }) } // CreateOrUpdateInterfaceWithRetry invokes az.PublicIPAddressesClient.CreateOrUpdate with exponential backoff retry func (az *Cloud) CreateOrUpdateInterfaceWithRetry(nic network.Interface) error { - return wait.ExponentialBackoff(az.resourceRequestBackoff, func() (bool, error) { + return wait.ExponentialBackoff(az.requestBackoff(), func() (bool, error) { az.operationPollRateLimiter.Accept() glog.V(10).Infof("InterfacesClient.CreateOrUpdate(%s): start", *nic.Name) respChan, errChan := az.InterfacesClient.CreateOrUpdate(az.ResourceGroup, *nic.Name, nic, nil) @@ -128,21 +294,21 @@ func (az *Cloud) CreateOrUpdateInterfaceWithRetry(nic network.Interface) error { } // DeletePublicIPWithRetry invokes az.PublicIPAddressesClient.Delete with exponential backoff retry -func (az *Cloud) DeletePublicIPWithRetry(pipName string) error { - return wait.ExponentialBackoff(az.resourceRequestBackoff, func() (bool, error) { +func (az *Cloud) DeletePublicIPWithRetry(pipResourceGroup string, pipName string) error { + return wait.ExponentialBackoff(az.requestBackoff(), func() (bool, error) { az.operationPollRateLimiter.Accept() - glog.V(10).Infof("PublicIPAddressesClient.Delete(%s): start", pipName) - respChan, errChan := az.PublicIPAddressesClient.Delete(az.ResourceGroup, pipName, nil) + glog.V(10).Infof("PublicIPAddressesClient.Delete(%s, %s): start", pipResourceGroup, pipName) + respChan, errChan := az.PublicIPAddressesClient.Delete(pipResourceGroup, pipName, nil) resp := <-respChan err := <-errChan - glog.V(10).Infof("PublicIPAddressesClient.Delete(%s): end", pipName) + glog.V(10).Infof("PublicIPAddressesClient.Delete(%s, %s): end", pipResourceGroup, pipName) return processRetryResponse(resp, err) }) } // DeleteLBWithRetry invokes az.LoadBalancerClient.Delete with exponential backoff retry func (az *Cloud) DeleteLBWithRetry(lbName string) error { - return wait.ExponentialBackoff(az.resourceRequestBackoff, func() (bool, error) { + return wait.ExponentialBackoff(az.requestBackoff(), func() (bool, error) { az.operationPollRateLimiter.Accept() glog.V(10).Infof("LoadBalancerClient.Delete(%s): start", lbName) respChan, errChan := az.LoadBalancerClient.Delete(az.ResourceGroup, lbName, nil) @@ -155,7 +321,7 @@ func (az *Cloud) DeleteLBWithRetry(lbName string) error { // CreateOrUpdateRouteTableWithRetry invokes az.RouteTablesClient.CreateOrUpdate with exponential backoff retry func (az *Cloud) CreateOrUpdateRouteTableWithRetry(routeTable network.RouteTable) error { - return wait.ExponentialBackoff(az.resourceRequestBackoff, func() (bool, error) { + return wait.ExponentialBackoff(az.requestBackoff(), func() (bool, error) { az.operationPollRateLimiter.Accept() glog.V(10).Infof("RouteTablesClient.CreateOrUpdate(%s): start", *routeTable.Name) respChan, errChan := az.RouteTablesClient.CreateOrUpdate(az.ResourceGroup, az.RouteTableName, routeTable, nil) @@ -168,7 +334,7 @@ func (az *Cloud) CreateOrUpdateRouteTableWithRetry(routeTable network.RouteTable // CreateOrUpdateRouteWithRetry invokes az.RoutesClient.CreateOrUpdate with exponential backoff retry func (az *Cloud) CreateOrUpdateRouteWithRetry(route network.Route) error { - return wait.ExponentialBackoff(az.resourceRequestBackoff, func() (bool, error) { + return wait.ExponentialBackoff(az.requestBackoff(), func() (bool, error) { az.operationPollRateLimiter.Accept() glog.V(10).Infof("RoutesClient.CreateOrUpdate(%s): start", *route.Name) respChan, errChan := az.RoutesClient.CreateOrUpdate(az.ResourceGroup, az.RouteTableName, *route.Name, route, nil) @@ -181,7 +347,7 @@ func (az *Cloud) CreateOrUpdateRouteWithRetry(route network.Route) error { // DeleteRouteWithRetry invokes az.RoutesClient.Delete with exponential backoff retry func (az *Cloud) DeleteRouteWithRetry(routeName string) error { - return wait.ExponentialBackoff(az.resourceRequestBackoff, func() (bool, error) { + return wait.ExponentialBackoff(az.requestBackoff(), func() (bool, error) { az.operationPollRateLimiter.Accept() glog.V(10).Infof("RoutesClient.Delete(%s): start", az.RouteTableName) respChan, errChan := az.RoutesClient.Delete(az.ResourceGroup, az.RouteTableName, routeName, nil) @@ -194,7 +360,7 @@ func (az *Cloud) DeleteRouteWithRetry(routeName string) error { // CreateOrUpdateVMWithRetry invokes az.VirtualMachinesClient.CreateOrUpdate with exponential backoff retry func (az *Cloud) CreateOrUpdateVMWithRetry(vmName string, newVM compute.VirtualMachine) error { - return wait.ExponentialBackoff(az.resourceRequestBackoff, func() (bool, error) { + return wait.ExponentialBackoff(az.requestBackoff(), func() (bool, error) { az.operationPollRateLimiter.Accept() glog.V(10).Infof("VirtualMachinesClient.CreateOrUpdate(%s): start", vmName) respChan, errChan := az.VirtualMachinesClient.CreateOrUpdate(az.ResourceGroup, vmName, newVM, nil) diff --git a/pkg/cloudprovider/providers/azure/azure_blobDiskController.go b/pkg/cloudprovider/providers/azure/azure_blobDiskController.go index d738599eabc..e68c23b896c 100644 --- a/pkg/cloudprovider/providers/azure/azure_blobDiskController.go +++ b/pkg/cloudprovider/providers/azure/azure_blobDiskController.go @@ -20,9 +20,7 @@ import ( "bytes" "encoding/binary" "fmt" - "math" "net/url" - "os" "regexp" "sync" @@ -61,70 +59,71 @@ type BlobDiskController struct { accounts map[string]*storageAccountState } -var defaultContainerName = "" -var storageAccountNamePrefix = "" -var storageAccountNameMatch = "" -var initFlag int64 - -var accountsLock = &sync.Mutex{} +var ( + defaultContainerName = "" + storageAccountNamePrefix = "" + storageAccountNameMatch = "" + accountsLock = &sync.Mutex{} +) func newBlobDiskController(common *controllerCommon) (*BlobDiskController, error) { c := BlobDiskController{common: common} - err := c.init() + c.setUniqueStrings() + // get accounts + accounts, err := c.getAllStorageAccounts() if err != nil { - return nil, err + glog.Errorf("azureDisk - getAllStorageAccounts error: %v", err) + c.accounts = make(map[string]*storageAccountState) + return &c, nil } - + c.accounts = accounts return &c, nil } -// CreateVolume creates a VHD blob in a given storage account, will create the given storage account if it does not exist in current resource group -func (c *BlobDiskController) CreateVolume(name, storageAccount string, storageAccountType storage.SkuName, location string, requestGB int) (string, string, int, error) { - key, err := c.common.cloud.getStorageAccesskey(storageAccount) - if err != nil { - glog.V(2).Infof("azureDisk - no key found for storage account %s in resource group %s, begin to create a new storage account", storageAccount, c.common.resourceGroup) - - cp := storage.AccountCreateParameters{ - Sku: &storage.Sku{Name: storageAccountType}, - Tags: &map[string]*string{"created-by": to.StringPtr("azure-dd")}, - Location: &location} - cancel := make(chan struct{}) - - _, errchan := c.common.cloud.StorageAccountClient.Create(c.common.resourceGroup, storageAccount, cp, cancel) - err = <-errchan +// CreateVolume creates a VHD blob in a storage account that has storageType and location using the given storage account. +// If no storage account is given, search all the storage accounts associated with the resource group and pick one that +// fits storage type and location. +func (c *BlobDiskController) CreateVolume(name, storageAccount, storageAccountType, location string, requestGB int) (string, string, int, error) { + var err error + accounts := []accountWithLocation{} + if len(storageAccount) > 0 { + accounts = append(accounts, accountWithLocation{Name: storageAccount}) + } else { + // find a storage account + accounts, err = c.common.cloud.getStorageAccounts() if err != nil { - return "", "", 0, fmt.Errorf(fmt.Sprintf("Create Storage Account %s, error: %s", storageAccount, err)) + // TODO: create a storage account and container + return "", "", 0, err } + } + for _, account := range accounts { + glog.V(4).Infof("account %s type %s location %s", account.Name, account.StorageType, account.Location) + if (storageAccountType == "" || account.StorageType == storageAccountType) && (location == "" || account.Location == location) || len(storageAccount) > 0 { + // find the access key with this account + key, err := c.common.cloud.getStorageAccesskey(account.Name) + if err != nil { + glog.V(2).Infof("no key found for storage account %s", account.Name) + continue + } - key, err = c.common.cloud.getStorageAccesskey(storageAccount) - if err != nil { - return "", "", 0, fmt.Errorf("no key found for storage account %s even after creating a new storage account", storageAccount) + client, err := azstorage.NewBasicClientOnSovereignCloud(account.Name, key, c.common.cloud.Environment) + if err != nil { + return "", "", 0, err + } + blobClient := client.GetBlobService() + + // create a page blob in this account's vhd container + diskName, diskURI, err := c.createVHDBlobDisk(blobClient, account.Name, name, vhdContainerName, int64(requestGB)) + if err != nil { + return "", "", 0, err + } + + glog.V(4).Infof("azureDisk - created vhd blob uri: %s", diskURI) + return diskName, diskURI, requestGB, err } - - glog.Errorf("no key found for storage account %s in resource group %s", storageAccount, c.common.resourceGroup) - return "", "", 0, err } - - client, err := azstorage.NewBasicClientOnSovereignCloud(storageAccount, key, c.common.cloud.Environment) - if err != nil { - return "", "", 0, err - } - blobClient := client.GetBlobService() - - container := blobClient.GetContainerReference(vhdContainerName) - _, err = container.CreateIfNotExists(&azstorage.CreateContainerOptions{Access: azstorage.ContainerAccessTypePrivate}) - if err != nil { - return "", "", 0, err - } - - diskName, diskURI, err := c.createVHDBlobDisk(blobClient, storageAccount, name, vhdContainerName, int64(requestGB)) - if err != nil { - return "", "", 0, err - } - - glog.V(4).Infof("azureDisk - created vhd blob uri: %s", diskURI) - return diskName, diskURI, requestGB, err + return "", "", 0, fmt.Errorf("failed to find a matching storage account") } // DeleteVolume deletes a VHD blob @@ -172,11 +171,6 @@ func (c *BlobDiskController) getBlobNameAndAccountFromURI(diskURI string) (strin func (c *BlobDiskController) createVHDBlobDisk(blobClient azstorage.BlobStorageClient, accountName, vhdName, containerName string, sizeGB int64) (string, string, error) { container := blobClient.GetContainerReference(containerName) - _, err := container.CreateIfNotExists(&azstorage.CreateContainerOptions{Access: azstorage.ContainerAccessTypePrivate}) - if err != nil { - return "", "", err - } - size := 1024 * 1024 * 1024 * sizeGB vhdSize := size + vhd.VHD_HEADER_SIZE /* header size */ // Blob name in URL must end with '.vhd' extension. @@ -189,7 +183,17 @@ func (c *BlobDiskController) createVHDBlobDisk(blobClient azstorage.BlobStorageC blob := container.GetBlobReference(vhdName) blob.Properties.ContentLength = vhdSize blob.Metadata = tags - err = blob.PutPageBlob(nil) + err := blob.PutPageBlob(nil) + if err != nil { + // if container doesn't exist, create one and retry PutPageBlob + detail := err.Error() + if strings.Contains(detail, errContainerNotFound) { + err = container.Create(&azstorage.CreateContainerOptions{Access: azstorage.ContainerAccessTypePrivate}) + if err == nil { + err = blob.PutPageBlob(nil) + } + } + } if err != nil { return "", "", fmt.Errorf("failed to put page blob %s in container %s: %v", vhdName, containerName, err) } @@ -235,24 +239,12 @@ func (c *BlobDiskController) deleteVhdBlob(accountName, accountKey, blobName str } //CreateBlobDisk : create a blob disk in a node -func (c *BlobDiskController) CreateBlobDisk(dataDiskName string, storageAccountType storage.SkuName, sizeGB int, forceStandAlone bool) (string, error) { - glog.V(4).Infof("azureDisk - creating blob data disk named:%s on StorageAccountType:%s StandAlone:%v", dataDiskName, storageAccountType, forceStandAlone) +func (c *BlobDiskController) CreateBlobDisk(dataDiskName string, storageAccountType storage.SkuName, sizeGB int) (string, error) { + glog.V(4).Infof("azureDisk - creating blob data disk named:%s on StorageAccountType:%s", dataDiskName, storageAccountType) - var storageAccountName = "" - var err error - - if forceStandAlone { - // we have to wait until the storage account is is created - storageAccountName = "p" + MakeCRC32(c.common.subscriptionID+c.common.resourceGroup+dataDiskName) - err = c.createStorageAccount(storageAccountName, storageAccountType, c.common.location, false) - if err != nil { - return "", err - } - } else { - storageAccountName, err = c.findSANameForDisk(storageAccountType) - if err != nil { - return "", err - } + storageAccountName, err := c.findSANameForDisk(storageAccountType) + if err != nil { + return "", err } blobClient, err := c.getBlobSvcClient(storageAccountName) @@ -265,15 +257,13 @@ func (c *BlobDiskController) CreateBlobDisk(dataDiskName string, storageAccountT return "", err } - if !forceStandAlone { - atomic.AddInt32(&c.accounts[storageAccountName].diskCount, 1) - } + atomic.AddInt32(&c.accounts[storageAccountName].diskCount, 1) return diskURI, nil } //DeleteBlobDisk : delete a blob disk from a node -func (c *BlobDiskController) DeleteBlobDisk(diskURI string, wasForced bool) error { +func (c *BlobDiskController) DeleteBlobDisk(diskURI string) error { storageAccountName, vhdName, err := diskNameandSANameFromURI(diskURI) if err != nil { return err @@ -285,11 +275,6 @@ func (c *BlobDiskController) DeleteBlobDisk(diskURI string, wasForced bool) erro glog.V(4).Infof("azureDisk - deleting volume %s", diskURI) return c.DeleteVolume(diskURI) } - // if forced (as in one disk = one storage account) - // delete the account completely - if wasForced { - return c.deleteStorageAccount(storageAccountName) - } blobSvc, err := c.getBlobSvcClient(storageAccountName) if err != nil { @@ -314,65 +299,11 @@ func (c *BlobDiskController) DeleteBlobDisk(diskURI string, wasForced bool) erro return err } -// Init tries best effort to ensure that 2 accounts standard/premium were created -// to be used by shared blob disks. This to increase the speed pvc provisioning (in most of cases) -func (c *BlobDiskController) init() error { - if !c.shouldInit() { - return nil - } - - c.setUniqueStrings() - - // get accounts - accounts, err := c.getAllStorageAccounts() - if err != nil { - return err - } - c.accounts = accounts - - if len(c.accounts) == 0 { - counter := 1 - for counter <= storageAccountsCountInit { - - accountType := storage.PremiumLRS - if n := math.Mod(float64(counter), 2); n == 0 { - accountType = storage.StandardLRS - } - - // We don't really care if these calls failed - // at this stage, we are trying to ensure 2 accounts (Standard/Premium) - // are there ready for PVC creation - - // if we failed here, the accounts will be created in the process - // of creating PVC - - // nor do we care if they were partially created, as the entire - // account creation process is idempotent - go func(thisNext int) { - newAccountName := getAccountNameForNum(thisNext) - - glog.Infof("azureDisk - BlobDiskController init process will create new storageAccount:%s type:%s", newAccountName, accountType) - err := c.createStorageAccount(newAccountName, accountType, c.common.location, true) - // TODO return created and error from - if err != nil { - glog.Infof("azureDisk - BlobDiskController init: create account %s with error:%s", newAccountName, err.Error()) - - } else { - glog.Infof("azureDisk - BlobDiskController init: created account %s", newAccountName) - } - }(counter) - counter = counter + 1 - } - } - - return nil -} - //Sets unique strings to be used as accountnames && || blob containers names func (c *BlobDiskController) setUniqueStrings() { uniqueString := c.common.resourceGroup + c.common.location + c.common.subscriptionID hash := MakeCRC32(uniqueString) - //used to generate a unqie container name used by this cluster PVC + //used to generate a unique container name used by this cluster PVC defaultContainerName = hash storageAccountNamePrefix = fmt.Sprintf(storageAccountNameTemplate, hash) @@ -428,13 +359,13 @@ func (c *BlobDiskController) ensureDefaultContainer(storageAccountName string) e var err error var blobSvc azstorage.BlobStorageClient - // short circut the check via local cache + // short circuit the check via local cache // we are forgiving the fact that account may not be in cache yet if v, ok := c.accounts[storageAccountName]; ok && v.defaultContainerCreated { return nil } - // not cached, check existance and readiness + // not cached, check existence and readiness bExist, provisionState, _ := c.getStorageAccountState(storageAccountName) // account does not exist @@ -461,7 +392,7 @@ func (c *BlobDiskController) ensureDefaultContainer(storageAccountName string) e c.accounts[storageAccountName].isValidating = 0 }() - // short circut the check again. + // short circuit the check again. if v, ok := c.accounts[storageAccountName]; ok && v.defaultContainerCreated { return nil } @@ -539,21 +470,8 @@ func (c *BlobDiskController) getDiskCount(SAName string) (int, error) { return int(c.accounts[SAName].diskCount), nil } -// shouldInit ensures that we only init the plugin once -// and we only do that in the controller - -func (c *BlobDiskController) shouldInit() bool { - if os.Args[0] == "kube-controller-manager" || (os.Args[0] == "/hyperkube" && os.Args[1] == "controller-manager") { - swapped := atomic.CompareAndSwapInt64(&initFlag, 0, 1) - if swapped { - return true - } - } - return false -} - func (c *BlobDiskController) getAllStorageAccounts() (map[string]*storageAccountState, error) { - accountListResult, err := c.common.cloud.StorageAccountClient.List() + accountListResult, err := c.common.cloud.StorageAccountClient.ListByResourceGroup(c.common.resourceGroup) if err != nil { return nil, err } @@ -601,7 +519,7 @@ func (c *BlobDiskController) createStorageAccount(storageAccountName string, sto return fmt.Errorf("azureDisk - can not create new storage account, current storage accounts count:%v Max is:%v", len(c.accounts), maxStorageAccounts) } - glog.V(2).Infof("azureDisk - Creating storage account %s type %s \n", storageAccountName, string(storageAccountType)) + glog.V(2).Infof("azureDisk - Creating storage account %s type %s", storageAccountName, string(storageAccountType)) cp := storage.AccountCreateParameters{ Sku: &storage.Sku{Name: storageAccountType}, @@ -624,14 +542,6 @@ func (c *BlobDiskController) createStorageAccount(storageAccountName string, sto c.addAccountState(storageAccountName, newAccountState) } - if !bExist { - // SA Accounts takes time to be provisioned - // so if this account was just created allow it sometime - // before polling - glog.V(2).Infof("azureDisk - storage account %s was just created, allowing time before polling status", storageAccountName) - time.Sleep(25 * time.Second) // as observed 25 is the average time for SA to be provisioned - } - // finally, make sure that we default container is created // before handing it back over return c.ensureDefaultContainer(storageAccountName) @@ -649,9 +559,9 @@ func (c *BlobDiskController) findSANameForDisk(storageAccountType storage.SkuNam continue } - // note: we compute avge stratified by type. - // this to enable user to grow per SA type to avoid low - //avg utilization on one account type skewing all data. + // note: we compute avg stratified by type. + // this is to enable user to grow per SA type to avoid low + // avg utilization on one account type skewing all data. if v.saType == storageAccountType { // compute average @@ -664,7 +574,7 @@ func (c *BlobDiskController) findSANameForDisk(storageAccountType storage.SkuNam // empty account if dCount == 0 { glog.V(2).Infof("azureDisk - account %s identified for a new disk is because it has 0 allocated disks", v.name) - return v.name, nil // shortcircut, avg is good and no need to adjust + return v.name, nil // short circuit, avg is good and no need to adjust } // if this account is less allocated if dCount < maxDiskCount { @@ -690,7 +600,7 @@ func (c *BlobDiskController) findSANameForDisk(storageAccountType storage.SkuNam avgUtilization := float64(disksAfter) / float64(countAccounts*maxDisksPerStorageAccounts) aboveAvg := (avgUtilization > storageAccountUtilizationBeforeGrowing) - // avg are not create and we should craete more accounts if we can + // avg are not create and we should create more accounts if we can if aboveAvg && countAccounts < maxStorageAccounts { glog.V(2).Infof("azureDisk - shared storageAccounts utilzation(%v) > grow-at-avg-utilization (%v). New storage account will be created", avgUtilization, storageAccountUtilizationBeforeGrowing) SAName = getAccountNameForNum(c.getNextAccountNum()) @@ -701,7 +611,7 @@ func (c *BlobDiskController) findSANameForDisk(storageAccountType storage.SkuNam return SAName, nil } - // avergates are not ok and we are at capacity(max storage accounts allowed) + // averages are not ok and we are at capacity (max storage accounts allowed) if aboveAvg && countAccounts == maxStorageAccounts { glog.Infof("azureDisk - shared storageAccounts utilzation(%v) > grow-at-avg-utilization (%v). But k8s maxed on SAs for PVC(%v). k8s will now exceed grow-at-avg-utilization without adding accounts", avgUtilization, storageAccountUtilizationBeforeGrowing, maxStorageAccounts) diff --git a/pkg/cloudprovider/providers/azure/azure_controllerCommon.go b/pkg/cloudprovider/providers/azure/azure_controllerCommon.go index 881a7dbb2c4..ea32f3f477f 100644 --- a/pkg/cloudprovider/providers/azure/azure_controllerCommon.go +++ b/pkg/cloudprovider/providers/azure/azure_controllerCommon.go @@ -53,7 +53,6 @@ var defaultBackOff = kwait.Backoff{ } type controllerCommon struct { - tenantID string subscriptionID string location string storageEndpointSuffix string @@ -71,12 +70,11 @@ type controllerCommon struct { // AttachDisk attaches a vhd to vm // the vhd must exist, can be identified by diskName, diskURI, and lun. func (c *controllerCommon) AttachDisk(isManagedDisk bool, diskName, diskURI string, nodeName types.NodeName, lun int32, cachingMode compute.CachingTypes) error { - vm, exists, err := c.cloud.getVirtualMachine(nodeName) + vm, err := c.cloud.getVirtualMachine(nodeName) if err != nil { return err - } else if !exists { - return cloudprovider.InstanceNotFound } + disks := *vm.StorageProfile.DataDisks if isManagedDisk { disks = append(disks, @@ -134,6 +132,8 @@ func (c *controllerCommon) AttachDisk(isManagedDisk bool, diskName, diskURI stri } } else { glog.V(4).Infof("azureDisk - azure attach succeeded") + // Invalidate the cache right after updating + vmCache.Delete(vmName) } return err } @@ -141,8 +141,8 @@ func (c *controllerCommon) AttachDisk(isManagedDisk bool, diskName, diskURI stri // DetachDiskByName detaches a vhd from host // the vhd can be identified by diskName or diskURI func (c *controllerCommon) DetachDiskByName(diskName, diskURI string, nodeName types.NodeName) error { - vm, exists, err := c.cloud.getVirtualMachine(nodeName) - if err != nil || !exists { + vm, err := c.cloud.getVirtualMachine(nodeName) + if err != nil { // if host doesn't exist, no need to detach glog.Warningf("azureDisk - cannot find node %s, skip detaching disk %s", nodeName, diskName) return nil @@ -192,17 +192,17 @@ func (c *controllerCommon) DetachDiskByName(diskName, diskURI string, nodeName t glog.Errorf("azureDisk - azure disk detach failed, err: %v", err) } else { glog.V(4).Infof("azureDisk - azure disk detach succeeded") + // Invalidate the cache right after updating + vmCache.Delete(vmName) } return err } // GetDiskLun finds the lun on the host that the vhd is attached to, given a vhd's diskName and diskURI func (c *controllerCommon) GetDiskLun(diskName, diskURI string, nodeName types.NodeName) (int32, error) { - vm, exists, err := c.cloud.getVirtualMachine(nodeName) + vm, err := c.cloud.getVirtualMachine(nodeName) if err != nil { return -1, err - } else if !exists { - return -1, cloudprovider.InstanceNotFound } disks := *vm.StorageProfile.DataDisks for _, disk := range disks { @@ -220,11 +220,9 @@ func (c *controllerCommon) GetDiskLun(diskName, diskURI string, nodeName types.N // GetNextDiskLun searches all vhd attachment on the host and find unused lun // return -1 if all luns are used func (c *controllerCommon) GetNextDiskLun(nodeName types.NodeName) (int32, error) { - vm, exists, err := c.cloud.getVirtualMachine(nodeName) + vm, err := c.cloud.getVirtualMachine(nodeName) if err != nil { return -1, err - } else if !exists { - return -1, cloudprovider.InstanceNotFound } used := make([]bool, maxLUN) disks := *vm.StorageProfile.DataDisks @@ -247,8 +245,8 @@ func (c *controllerCommon) DisksAreAttached(diskNames []string, nodeName types.N for _, diskName := range diskNames { attached[diskName] = false } - vm, exists, err := c.cloud.getVirtualMachine(nodeName) - if !exists { + vm, err := c.cloud.getVirtualMachine(nodeName) + if err == cloudprovider.InstanceNotFound { // if host doesn't exist, no need to detach glog.Warningf("azureDisk - Cannot find node %q, DisksAreAttached will assume disks %v are not attached to it.", nodeName, diskNames) diff --git a/pkg/cloudprovider/providers/azure/azure_fakes.go b/pkg/cloudprovider/providers/azure/azure_fakes.go new file mode 100644 index 00000000000..dd66d509f17 --- /dev/null +++ b/pkg/cloudprovider/providers/azure/azure_fakes.go @@ -0,0 +1,1104 @@ +/* +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 azure + +import ( + "fmt" + "math/rand" + "net/http" + "strings" + "sync" + "time" + + "github.com/Azure/azure-sdk-for-go/arm/compute" + "github.com/Azure/azure-sdk-for-go/arm/disk" + "github.com/Azure/azure-sdk-for-go/arm/network" + "github.com/Azure/azure-sdk-for-go/arm/storage" + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/to" +) + +type fakeAzureLBClient struct { + mutex *sync.Mutex + FakeStore map[string]map[string]network.LoadBalancer +} + +func newFakeAzureLBClient() fakeAzureLBClient { + fLBC := fakeAzureLBClient{} + fLBC.FakeStore = make(map[string]map[string]network.LoadBalancer) + fLBC.mutex = &sync.Mutex{} + return fLBC +} + +func (fLBC fakeAzureLBClient) CreateOrUpdate(resourceGroupName string, loadBalancerName string, parameters network.LoadBalancer, cancel <-chan struct{}) (<-chan network.LoadBalancer, <-chan error) { + fLBC.mutex.Lock() + defer fLBC.mutex.Unlock() + resultChan := make(chan network.LoadBalancer, 1) + errChan := make(chan error, 1) + var result network.LoadBalancer + var err error + defer func() { + resultChan <- result + errChan <- err + close(resultChan) + close(errChan) + }() + if _, ok := fLBC.FakeStore[resourceGroupName]; !ok { + fLBC.FakeStore[resourceGroupName] = make(map[string]network.LoadBalancer) + } + + // For dynamic ip allocation, just fill in the PrivateIPAddress + if parameters.FrontendIPConfigurations != nil { + for idx, config := range *parameters.FrontendIPConfigurations { + if config.PrivateIPAllocationMethod == network.Dynamic { + // Here we randomly assign an ip as private ip + // It dosen't smart enough to know whether it is in the subnet's range + (*parameters.FrontendIPConfigurations)[idx].PrivateIPAddress = getRandomIPPtr() + } + } + } + fLBC.FakeStore[resourceGroupName][loadBalancerName] = parameters + result = fLBC.FakeStore[resourceGroupName][loadBalancerName] + result.Response.Response = &http.Response{ + StatusCode: http.StatusOK, + } + err = nil + return resultChan, errChan +} + +func (fLBC fakeAzureLBClient) Delete(resourceGroupName string, loadBalancerName string, cancel <-chan struct{}) (<-chan autorest.Response, <-chan error) { + fLBC.mutex.Lock() + defer fLBC.mutex.Unlock() + respChan := make(chan autorest.Response, 1) + errChan := make(chan error, 1) + var resp autorest.Response + var err error + defer func() { + respChan <- resp + errChan <- err + close(respChan) + close(errChan) + }() + if rgLBs, ok := fLBC.FakeStore[resourceGroupName]; ok { + if _, ok := rgLBs[loadBalancerName]; ok { + delete(rgLBs, loadBalancerName) + resp.Response = &http.Response{ + StatusCode: http.StatusAccepted, + } + err = nil + return respChan, errChan + } + } + resp.Response = &http.Response{ + StatusCode: http.StatusNotFound, + } + err = autorest.DetailedError{ + StatusCode: http.StatusNotFound, + Message: "Not such LB", + } + return respChan, errChan +} + +func (fLBC fakeAzureLBClient) Get(resourceGroupName string, loadBalancerName string, expand string) (result network.LoadBalancer, err error) { + fLBC.mutex.Lock() + defer fLBC.mutex.Unlock() + if _, ok := fLBC.FakeStore[resourceGroupName]; ok { + if entity, ok := fLBC.FakeStore[resourceGroupName][loadBalancerName]; ok { + return entity, nil + } + } + return result, autorest.DetailedError{ + StatusCode: http.StatusNotFound, + Message: "Not such LB", + } +} + +func (fLBC fakeAzureLBClient) List(resourceGroupName string) (result network.LoadBalancerListResult, err error) { + fLBC.mutex.Lock() + defer fLBC.mutex.Unlock() + var value []network.LoadBalancer + if _, ok := fLBC.FakeStore[resourceGroupName]; ok { + for _, v := range fLBC.FakeStore[resourceGroupName] { + value = append(value, v) + } + } + result.Response.Response = &http.Response{ + StatusCode: http.StatusOK, + } + result.NextLink = nil + result.Value = &value + return result, nil +} + +func (fLBC fakeAzureLBClient) ListNextResults(lastResult network.LoadBalancerListResult) (result network.LoadBalancerListResult, err error) { + fLBC.mutex.Lock() + defer fLBC.mutex.Unlock() + result.Response.Response = &http.Response{ + StatusCode: http.StatusOK, + } + result.NextLink = nil + result.Value = nil + return result, nil +} + +type fakeAzurePIPClient struct { + mutex *sync.Mutex + FakeStore map[string]map[string]network.PublicIPAddress + SubscriptionID string +} + +const publicIPAddressIDTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/publicIPAddresses/%s" + +// returns the full identifier of a publicIPAddress. +func getpublicIPAddressID(subscriptionID string, resourceGroupName, pipName string) string { + return fmt.Sprintf( + publicIPAddressIDTemplate, + subscriptionID, + resourceGroupName, + pipName) +} + +func newFakeAzurePIPClient(subscriptionID string) fakeAzurePIPClient { + fAPC := fakeAzurePIPClient{} + fAPC.FakeStore = make(map[string]map[string]network.PublicIPAddress) + fAPC.SubscriptionID = subscriptionID + fAPC.mutex = &sync.Mutex{} + return fAPC +} + +func (fAPC fakeAzurePIPClient) CreateOrUpdate(resourceGroupName string, publicIPAddressName string, parameters network.PublicIPAddress, cancel <-chan struct{}) (<-chan network.PublicIPAddress, <-chan error) { + fAPC.mutex.Lock() + defer fAPC.mutex.Unlock() + resultChan := make(chan network.PublicIPAddress, 1) + errChan := make(chan error, 1) + var result network.PublicIPAddress + var err error + defer func() { + resultChan <- result + errChan <- err + close(resultChan) + close(errChan) + }() + if _, ok := fAPC.FakeStore[resourceGroupName]; !ok { + fAPC.FakeStore[resourceGroupName] = make(map[string]network.PublicIPAddress) + } + + // assign id + pipID := getpublicIPAddressID(fAPC.SubscriptionID, resourceGroupName, publicIPAddressName) + parameters.ID = &pipID + + // only create in the case user has not provided + if parameters.PublicIPAddressPropertiesFormat != nil && + parameters.PublicIPAddressPropertiesFormat.PublicIPAllocationMethod == network.Static { + // assign ip + parameters.IPAddress = getRandomIPPtr() + } + + fAPC.FakeStore[resourceGroupName][publicIPAddressName] = parameters + result = fAPC.FakeStore[resourceGroupName][publicIPAddressName] + result.Response.Response = &http.Response{ + StatusCode: http.StatusOK, + } + err = nil + return resultChan, errChan +} + +func (fAPC fakeAzurePIPClient) Delete(resourceGroupName string, publicIPAddressName string, cancel <-chan struct{}) (<-chan autorest.Response, <-chan error) { + fAPC.mutex.Lock() + defer fAPC.mutex.Unlock() + respChan := make(chan autorest.Response, 1) + errChan := make(chan error, 1) + var resp autorest.Response + var err error + defer func() { + respChan <- resp + errChan <- err + close(respChan) + close(errChan) + }() + if rgPIPs, ok := fAPC.FakeStore[resourceGroupName]; ok { + if _, ok := rgPIPs[publicIPAddressName]; ok { + delete(rgPIPs, publicIPAddressName) + resp.Response = &http.Response{ + StatusCode: http.StatusAccepted, + } + err = nil + return respChan, errChan + } + } + resp.Response = &http.Response{ + StatusCode: http.StatusNotFound, + } + err = autorest.DetailedError{ + StatusCode: http.StatusNotFound, + Message: "Not such PIP", + } + return respChan, errChan +} + +func (fAPC fakeAzurePIPClient) Get(resourceGroupName string, publicIPAddressName string, expand string) (result network.PublicIPAddress, err error) { + fAPC.mutex.Lock() + defer fAPC.mutex.Unlock() + if _, ok := fAPC.FakeStore[resourceGroupName]; ok { + if entity, ok := fAPC.FakeStore[resourceGroupName][publicIPAddressName]; ok { + return entity, nil + } + } + return result, autorest.DetailedError{ + StatusCode: http.StatusNotFound, + Message: "Not such PIP", + } +} + +func (fAPC fakeAzurePIPClient) ListNextResults(lastResults network.PublicIPAddressListResult) (result network.PublicIPAddressListResult, err error) { + fAPC.mutex.Lock() + defer fAPC.mutex.Unlock() + return network.PublicIPAddressListResult{}, nil +} + +func (fAPC fakeAzurePIPClient) List(resourceGroupName string) (result network.PublicIPAddressListResult, err error) { + fAPC.mutex.Lock() + defer fAPC.mutex.Unlock() + var value []network.PublicIPAddress + if _, ok := fAPC.FakeStore[resourceGroupName]; ok { + for _, v := range fAPC.FakeStore[resourceGroupName] { + value = append(value, v) + } + } + result.Response.Response = &http.Response{ + StatusCode: http.StatusOK, + } + result.NextLink = nil + result.Value = &value + return result, nil +} + +type fakeAzureInterfacesClient struct { + mutex *sync.Mutex + FakeStore map[string]map[string]network.Interface +} + +func newFakeAzureInterfacesClient() fakeAzureInterfacesClient { + fIC := fakeAzureInterfacesClient{} + fIC.FakeStore = make(map[string]map[string]network.Interface) + fIC.mutex = &sync.Mutex{} + + return fIC +} + +func (fIC fakeAzureInterfacesClient) CreateOrUpdate(resourceGroupName string, networkInterfaceName string, parameters network.Interface, cancel <-chan struct{}) (<-chan network.Interface, <-chan error) { + fIC.mutex.Lock() + defer fIC.mutex.Unlock() + resultChan := make(chan network.Interface, 1) + errChan := make(chan error, 1) + var result network.Interface + var err error + defer func() { + resultChan <- result + errChan <- err + close(resultChan) + close(errChan) + }() + if _, ok := fIC.FakeStore[resourceGroupName]; !ok { + fIC.FakeStore[resourceGroupName] = make(map[string]network.Interface) + } + fIC.FakeStore[resourceGroupName][networkInterfaceName] = parameters + result = fIC.FakeStore[resourceGroupName][networkInterfaceName] + result.Response.Response = &http.Response{ + StatusCode: http.StatusOK, + } + err = nil + + return resultChan, errChan +} + +func (fIC fakeAzureInterfacesClient) Get(resourceGroupName string, networkInterfaceName string, expand string) (result network.Interface, err error) { + fIC.mutex.Lock() + defer fIC.mutex.Unlock() + if _, ok := fIC.FakeStore[resourceGroupName]; ok { + if entity, ok := fIC.FakeStore[resourceGroupName][networkInterfaceName]; ok { + return entity, nil + } + } + return result, autorest.DetailedError{ + StatusCode: http.StatusNotFound, + Message: "Not such Interface", + } +} + +func (fIC fakeAzureInterfacesClient) GetVirtualMachineScaleSetNetworkInterface(resourceGroupName string, virtualMachineScaleSetName string, virtualmachineIndex string, networkInterfaceName string, expand string) (result network.Interface, err error) { + return result, nil +} + +type fakeAzureVirtualMachinesClient struct { + mutex *sync.Mutex + FakeStore map[string]map[string]compute.VirtualMachine +} + +func newFakeAzureVirtualMachinesClient() fakeAzureVirtualMachinesClient { + fVMC := fakeAzureVirtualMachinesClient{} + fVMC.FakeStore = make(map[string]map[string]compute.VirtualMachine) + fVMC.mutex = &sync.Mutex{} + return fVMC +} + +func (fVMC fakeAzureVirtualMachinesClient) CreateOrUpdate(resourceGroupName string, VMName string, parameters compute.VirtualMachine, cancel <-chan struct{}) (<-chan compute.VirtualMachine, <-chan error) { + fVMC.mutex.Lock() + defer fVMC.mutex.Unlock() + resultChan := make(chan compute.VirtualMachine, 1) + errChan := make(chan error, 1) + var result compute.VirtualMachine + var err error + defer func() { + resultChan <- result + errChan <- err + close(resultChan) + close(errChan) + }() + if _, ok := fVMC.FakeStore[resourceGroupName]; !ok { + fVMC.FakeStore[resourceGroupName] = make(map[string]compute.VirtualMachine) + } + fVMC.FakeStore[resourceGroupName][VMName] = parameters + result = fVMC.FakeStore[resourceGroupName][VMName] + result.Response.Response = &http.Response{ + StatusCode: http.StatusOK, + } + err = nil + return resultChan, errChan +} + +func (fVMC fakeAzureVirtualMachinesClient) Get(resourceGroupName string, VMName string, expand compute.InstanceViewTypes) (result compute.VirtualMachine, err error) { + fVMC.mutex.Lock() + defer fVMC.mutex.Unlock() + if _, ok := fVMC.FakeStore[resourceGroupName]; ok { + if entity, ok := fVMC.FakeStore[resourceGroupName][VMName]; ok { + return entity, nil + } + } + return result, autorest.DetailedError{ + StatusCode: http.StatusNotFound, + Message: "Not such VM", + } +} + +func (fVMC fakeAzureVirtualMachinesClient) List(resourceGroupName string) (result compute.VirtualMachineListResult, err error) { + fVMC.mutex.Lock() + defer fVMC.mutex.Unlock() + var value []compute.VirtualMachine + if _, ok := fVMC.FakeStore[resourceGroupName]; ok { + for _, v := range fVMC.FakeStore[resourceGroupName] { + value = append(value, v) + } + } + result.Response.Response = &http.Response{ + StatusCode: http.StatusOK, + } + result.NextLink = nil + result.Value = &value + return result, nil +} +func (fVMC fakeAzureVirtualMachinesClient) ListNextResults(lastResults compute.VirtualMachineListResult) (result compute.VirtualMachineListResult, err error) { + fVMC.mutex.Lock() + defer fVMC.mutex.Unlock() + return compute.VirtualMachineListResult{}, nil +} + +type fakeAzureSubnetsClient struct { + mutex *sync.Mutex + FakeStore map[string]map[string]network.Subnet +} + +func newFakeAzureSubnetsClient() fakeAzureSubnetsClient { + fASC := fakeAzureSubnetsClient{} + fASC.FakeStore = make(map[string]map[string]network.Subnet) + fASC.mutex = &sync.Mutex{} + return fASC +} + +func (fASC fakeAzureSubnetsClient) CreateOrUpdate(resourceGroupName string, virtualNetworkName string, subnetName string, subnetParameters network.Subnet, cancel <-chan struct{}) (<-chan network.Subnet, <-chan error) { + fASC.mutex.Lock() + defer fASC.mutex.Unlock() + resultChan := make(chan network.Subnet, 1) + errChan := make(chan error, 1) + var result network.Subnet + var err error + defer func() { + resultChan <- result + errChan <- err + close(resultChan) + close(errChan) + }() + rgVnet := strings.Join([]string{resourceGroupName, virtualNetworkName}, "AND") + if _, ok := fASC.FakeStore[rgVnet]; !ok { + fASC.FakeStore[rgVnet] = make(map[string]network.Subnet) + } + fASC.FakeStore[rgVnet][subnetName] = subnetParameters + result = fASC.FakeStore[rgVnet][subnetName] + result.Response.Response = &http.Response{ + StatusCode: http.StatusOK, + } + err = nil + return resultChan, errChan +} + +func (fASC fakeAzureSubnetsClient) Delete(resourceGroupName string, virtualNetworkName string, subnetName string, cancel <-chan struct{}) (<-chan autorest.Response, <-chan error) { + fASC.mutex.Lock() + defer fASC.mutex.Unlock() + respChan := make(chan autorest.Response, 1) + errChan := make(chan error, 1) + var resp autorest.Response + var err error + defer func() { + respChan <- resp + errChan <- err + close(respChan) + close(errChan) + }() + + rgVnet := strings.Join([]string{resourceGroupName, virtualNetworkName}, "AND") + if rgSubnets, ok := fASC.FakeStore[rgVnet]; ok { + if _, ok := rgSubnets[subnetName]; ok { + delete(rgSubnets, subnetName) + resp.Response = &http.Response{ + StatusCode: http.StatusAccepted, + } + err = nil + return respChan, errChan + } + } + resp.Response = &http.Response{ + StatusCode: http.StatusNotFound, + } + err = autorest.DetailedError{ + StatusCode: http.StatusNotFound, + Message: "Not such Subnet", + } + return respChan, errChan +} +func (fASC fakeAzureSubnetsClient) Get(resourceGroupName string, virtualNetworkName string, subnetName string, expand string) (result network.Subnet, err error) { + fASC.mutex.Lock() + defer fASC.mutex.Unlock() + rgVnet := strings.Join([]string{resourceGroupName, virtualNetworkName}, "AND") + if _, ok := fASC.FakeStore[rgVnet]; ok { + if entity, ok := fASC.FakeStore[rgVnet][subnetName]; ok { + return entity, nil + } + } + return result, autorest.DetailedError{ + StatusCode: http.StatusNotFound, + Message: "Not such Subnet", + } +} +func (fASC fakeAzureSubnetsClient) List(resourceGroupName string, virtualNetworkName string) (result network.SubnetListResult, err error) { + fASC.mutex.Lock() + defer fASC.mutex.Unlock() + rgVnet := strings.Join([]string{resourceGroupName, virtualNetworkName}, "AND") + var value []network.Subnet + if _, ok := fASC.FakeStore[rgVnet]; ok { + for _, v := range fASC.FakeStore[rgVnet] { + value = append(value, v) + } + } + result.Response.Response = &http.Response{ + StatusCode: http.StatusOK, + } + result.NextLink = nil + result.Value = &value + return result, nil +} + +type fakeAzureNSGClient struct { + mutex *sync.Mutex + FakeStore map[string]map[string]network.SecurityGroup +} + +func newFakeAzureNSGClient() fakeAzureNSGClient { + fNSG := fakeAzureNSGClient{} + fNSG.FakeStore = make(map[string]map[string]network.SecurityGroup) + fNSG.mutex = &sync.Mutex{} + return fNSG +} + +func (fNSG fakeAzureNSGClient) CreateOrUpdate(resourceGroupName string, networkSecurityGroupName string, parameters network.SecurityGroup, cancel <-chan struct{}) (<-chan network.SecurityGroup, <-chan error) { + fNSG.mutex.Lock() + defer fNSG.mutex.Unlock() + resultChan := make(chan network.SecurityGroup, 1) + errChan := make(chan error, 1) + var result network.SecurityGroup + var err error + defer func() { + resultChan <- result + errChan <- err + close(resultChan) + close(errChan) + }() + if _, ok := fNSG.FakeStore[resourceGroupName]; !ok { + fNSG.FakeStore[resourceGroupName] = make(map[string]network.SecurityGroup) + } + fNSG.FakeStore[resourceGroupName][networkSecurityGroupName] = parameters + result = fNSG.FakeStore[resourceGroupName][networkSecurityGroupName] + result.Response.Response = &http.Response{ + StatusCode: http.StatusOK, + } + err = nil + return resultChan, errChan +} + +func (fNSG fakeAzureNSGClient) Delete(resourceGroupName string, networkSecurityGroupName string, cancel <-chan struct{}) (<-chan autorest.Response, <-chan error) { + fNSG.mutex.Lock() + defer fNSG.mutex.Unlock() + respChan := make(chan autorest.Response, 1) + errChan := make(chan error, 1) + var resp autorest.Response + var err error + defer func() { + respChan <- resp + errChan <- err + close(respChan) + close(errChan) + }() + if rgSGs, ok := fNSG.FakeStore[resourceGroupName]; ok { + if _, ok := rgSGs[networkSecurityGroupName]; ok { + delete(rgSGs, networkSecurityGroupName) + resp.Response = &http.Response{ + StatusCode: http.StatusAccepted, + } + err = nil + return respChan, errChan + } + } + resp.Response = &http.Response{ + StatusCode: http.StatusNotFound, + } + err = autorest.DetailedError{ + StatusCode: http.StatusNotFound, + Message: "Not such NSG", + } + return respChan, errChan +} + +func (fNSG fakeAzureNSGClient) Get(resourceGroupName string, networkSecurityGroupName string, expand string) (result network.SecurityGroup, err error) { + fNSG.mutex.Lock() + defer fNSG.mutex.Unlock() + if _, ok := fNSG.FakeStore[resourceGroupName]; ok { + if entity, ok := fNSG.FakeStore[resourceGroupName][networkSecurityGroupName]; ok { + return entity, nil + } + } + return result, autorest.DetailedError{ + StatusCode: http.StatusNotFound, + Message: "Not such NSG", + } +} + +func (fNSG fakeAzureNSGClient) List(resourceGroupName string) (result network.SecurityGroupListResult, err error) { + fNSG.mutex.Lock() + defer fNSG.mutex.Unlock() + var value []network.SecurityGroup + if _, ok := fNSG.FakeStore[resourceGroupName]; ok { + for _, v := range fNSG.FakeStore[resourceGroupName] { + value = append(value, v) + } + } + result.Response.Response = &http.Response{ + StatusCode: http.StatusOK, + } + result.NextLink = nil + result.Value = &value + return result, nil +} + +func getRandomIPPtr() *string { + rand.Seed(time.Now().UnixNano()) + return to.StringPtr(fmt.Sprintf("%d.%d.%d.%d", rand.Intn(256), rand.Intn(256), rand.Intn(256), rand.Intn(256))) +} + +type fakeVirtualMachineScaleSetVMsClient struct { + mutex *sync.Mutex + FakeStore map[string]map[string]compute.VirtualMachineScaleSetVM +} + +func newFakeVirtualMachineScaleSetVMsClient() fakeVirtualMachineScaleSetVMsClient { + fVMC := fakeVirtualMachineScaleSetVMsClient{} + fVMC.FakeStore = make(map[string]map[string]compute.VirtualMachineScaleSetVM) + fVMC.mutex = &sync.Mutex{} + + return fVMC +} + +func (fVMC fakeVirtualMachineScaleSetVMsClient) List(resourceGroupName string, virtualMachineScaleSetName string, filter string, selectParameter string, expand string) (result compute.VirtualMachineScaleSetVMListResult, err error) { + fVMC.mutex.Lock() + defer fVMC.mutex.Unlock() + + value := []compute.VirtualMachineScaleSetVM{} + if _, ok := fVMC.FakeStore[resourceGroupName]; ok { + for _, v := range fVMC.FakeStore[resourceGroupName] { + value = append(value, v) + } + } + + result.Response.Response = &http.Response{ + StatusCode: http.StatusOK, + } + result.NextLink = nil + result.Value = &value + return result, nil +} + +func (fVMC fakeVirtualMachineScaleSetVMsClient) ListNextResults(lastResults compute.VirtualMachineScaleSetVMListResult) (result compute.VirtualMachineScaleSetVMListResult, err error) { + return result, nil +} + +func (fVMC fakeVirtualMachineScaleSetVMsClient) Get(resourceGroupName string, VMScaleSetName string, instanceID string) (result compute.VirtualMachineScaleSetVM, err error) { + fVMC.mutex.Lock() + defer fVMC.mutex.Unlock() + + vmKey := fmt.Sprintf("%s-%s", VMScaleSetName, instanceID) + if scaleSetMap, ok := fVMC.FakeStore[resourceGroupName]; ok { + if entity, ok := scaleSetMap[vmKey]; ok { + return entity, nil + } + } + + return result, autorest.DetailedError{ + StatusCode: http.StatusNotFound, + Message: "No such VirtualMachineScaleSetVM", + } +} + +func (fVMC fakeVirtualMachineScaleSetVMsClient) GetInstanceView(resourceGroupName string, VMScaleSetName string, instanceID string) (result compute.VirtualMachineScaleSetVMInstanceView, err error) { + _, err = fVMC.Get(resourceGroupName, VMScaleSetName, instanceID) + if err != nil { + return result, err + } + + return result, nil +} + +type fakeVirtualMachineScaleSetsClient struct { + mutex *sync.Mutex + FakeStore map[string]map[string]compute.VirtualMachineScaleSet +} + +func newFakeVirtualMachineScaleSetsClient() fakeVirtualMachineScaleSetsClient { + fVMSSC := fakeVirtualMachineScaleSetsClient{} + fVMSSC.FakeStore = make(map[string]map[string]compute.VirtualMachineScaleSet) + fVMSSC.mutex = &sync.Mutex{} + + return fVMSSC +} + +func (fVMSSC fakeVirtualMachineScaleSetsClient) CreateOrUpdate(resourceGroupName string, VMScaleSetName string, parameters compute.VirtualMachineScaleSet, cancel <-chan struct{}) (<-chan compute.VirtualMachineScaleSet, <-chan error) { + fVMSSC.mutex.Lock() + defer fVMSSC.mutex.Unlock() + + resultChan := make(chan compute.VirtualMachineScaleSet, 1) + errChan := make(chan error, 1) + var result compute.VirtualMachineScaleSet + var err error + defer func() { + resultChan <- result + errChan <- err + close(resultChan) + close(errChan) + }() + + if _, ok := fVMSSC.FakeStore[resourceGroupName]; !ok { + fVMSSC.FakeStore[resourceGroupName] = make(map[string]compute.VirtualMachineScaleSet) + } + fVMSSC.FakeStore[resourceGroupName][VMScaleSetName] = parameters + result = fVMSSC.FakeStore[resourceGroupName][VMScaleSetName] + result.Response.Response = &http.Response{ + StatusCode: http.StatusOK, + } + err = nil + return resultChan, errChan +} + +func (fVMSSC fakeVirtualMachineScaleSetsClient) Get(resourceGroupName string, VMScaleSetName string) (result compute.VirtualMachineScaleSet, err error) { + fVMSSC.mutex.Lock() + defer fVMSSC.mutex.Unlock() + + if scaleSetMap, ok := fVMSSC.FakeStore[resourceGroupName]; ok { + if entity, ok := scaleSetMap[VMScaleSetName]; ok { + return entity, nil + } + } + + return result, autorest.DetailedError{ + StatusCode: http.StatusNotFound, + Message: "No such ScaleSet", + } +} + +func (fVMSSC fakeVirtualMachineScaleSetsClient) List(resourceGroupName string) (result compute.VirtualMachineScaleSetListResult, err error) { + fVMSSC.mutex.Lock() + defer fVMSSC.mutex.Unlock() + + value := []compute.VirtualMachineScaleSet{} + if _, ok := fVMSSC.FakeStore[resourceGroupName]; ok { + for _, v := range fVMSSC.FakeStore[resourceGroupName] { + value = append(value, v) + } + } + + result.Response.Response = &http.Response{ + StatusCode: http.StatusOK, + } + result.NextLink = nil + result.Value = &value + return result, nil +} + +func (fVMSSC fakeVirtualMachineScaleSetsClient) ListNextResults(lastResults compute.VirtualMachineScaleSetListResult) (result compute.VirtualMachineScaleSetListResult, err error) { + return result, nil +} + +func (fVMSSC fakeVirtualMachineScaleSetsClient) UpdateInstances(resourceGroupName string, VMScaleSetName string, VMInstanceIDs compute.VirtualMachineScaleSetVMInstanceRequiredIDs, cancel <-chan struct{}) (<-chan compute.OperationStatusResponse, <-chan error) { + resultChan := make(chan compute.OperationStatusResponse, 1) + errChan := make(chan error, 1) + var result compute.OperationStatusResponse + var err error + defer func() { + resultChan <- result + errChan <- err + close(resultChan) + close(errChan) + }() + + result.Response.Response = &http.Response{ + StatusCode: http.StatusOK, + } + err = nil + return resultChan, errChan +} + +type fakeRoutesClient struct { + mutex *sync.Mutex + FakeStore map[string]map[string]network.Route +} + +func newFakeRoutesClient() fakeRoutesClient { + fRC := fakeRoutesClient{} + fRC.FakeStore = make(map[string]map[string]network.Route) + fRC.mutex = &sync.Mutex{} + return fRC +} + +func (fRC fakeRoutesClient) CreateOrUpdate(resourceGroupName string, routeTableName string, routeName string, routeParameters network.Route, cancel <-chan struct{}) (<-chan network.Route, <-chan error) { + fRC.mutex.Lock() + defer fRC.mutex.Unlock() + + resultChan := make(chan network.Route, 1) + errChan := make(chan error, 1) + var result network.Route + var err error + defer func() { + resultChan <- result + errChan <- err + close(resultChan) + close(errChan) + }() + + if _, ok := fRC.FakeStore[routeTableName]; !ok { + fRC.FakeStore[routeTableName] = make(map[string]network.Route) + } + fRC.FakeStore[routeTableName][routeName] = routeParameters + result = fRC.FakeStore[routeTableName][routeName] + result.Response.Response = &http.Response{ + StatusCode: http.StatusOK, + } + err = nil + return resultChan, errChan +} + +func (fRC fakeRoutesClient) Delete(resourceGroupName string, routeTableName string, routeName string, cancel <-chan struct{}) (<-chan autorest.Response, <-chan error) { + fRC.mutex.Lock() + defer fRC.mutex.Unlock() + + respChan := make(chan autorest.Response, 1) + errChan := make(chan error, 1) + var resp autorest.Response + var err error + defer func() { + respChan <- resp + errChan <- err + close(respChan) + close(errChan) + }() + if routes, ok := fRC.FakeStore[routeTableName]; ok { + if _, ok := routes[routeName]; ok { + delete(routes, routeName) + resp.Response = &http.Response{ + StatusCode: http.StatusAccepted, + } + + err = nil + return respChan, errChan + } + } + resp.Response = &http.Response{ + StatusCode: http.StatusNotFound, + } + err = autorest.DetailedError{ + StatusCode: http.StatusNotFound, + Message: "Not such Route", + } + return respChan, errChan +} + +type fakeRouteTablesClient struct { + mutex *sync.Mutex + FakeStore map[string]map[string]network.RouteTable +} + +func newFakeRouteTablesClient() fakeRouteTablesClient { + fRTC := fakeRouteTablesClient{} + fRTC.FakeStore = make(map[string]map[string]network.RouteTable) + fRTC.mutex = &sync.Mutex{} + return fRTC +} + +func (fRTC fakeRouteTablesClient) CreateOrUpdate(resourceGroupName string, routeTableName string, parameters network.RouteTable, cancel <-chan struct{}) (<-chan network.RouteTable, <-chan error) { + fRTC.mutex.Lock() + defer fRTC.mutex.Unlock() + + resultChan := make(chan network.RouteTable, 1) + errChan := make(chan error, 1) + var result network.RouteTable + var err error + defer func() { + resultChan <- result + errChan <- err + close(resultChan) + close(errChan) + }() + + if _, ok := fRTC.FakeStore[resourceGroupName]; !ok { + fRTC.FakeStore[resourceGroupName] = make(map[string]network.RouteTable) + } + fRTC.FakeStore[resourceGroupName][routeTableName] = parameters + result = fRTC.FakeStore[resourceGroupName][routeTableName] + result.Response.Response = &http.Response{ + StatusCode: http.StatusOK, + } + err = nil + return resultChan, errChan +} + +func (fRTC fakeRouteTablesClient) Get(resourceGroupName string, routeTableName string, expand string) (result network.RouteTable, err error) { + fRTC.mutex.Lock() + defer fRTC.mutex.Unlock() + if _, ok := fRTC.FakeStore[resourceGroupName]; ok { + if entity, ok := fRTC.FakeStore[resourceGroupName][routeTableName]; ok { + return entity, nil + } + } + return result, autorest.DetailedError{ + StatusCode: http.StatusNotFound, + Message: "Not such RouteTable", + } +} + +type fakeStorageAccountClient struct { + mutex *sync.Mutex + FakeStore map[string]map[string]storage.Account +} + +func newFakeStorageAccountClient() fakeStorageAccountClient { + fSAC := fakeStorageAccountClient{} + fSAC.FakeStore = make(map[string]map[string]storage.Account) + fSAC.mutex = &sync.Mutex{} + return fSAC +} + +func (fSAC fakeStorageAccountClient) Create(resourceGroupName string, accountName string, parameters storage.AccountCreateParameters, cancel <-chan struct{}) (<-chan storage.Account, <-chan error) { + fSAC.mutex.Lock() + defer fSAC.mutex.Unlock() + + resultChan := make(chan storage.Account, 1) + errChan := make(chan error, 1) + var result storage.Account + var err error + defer func() { + resultChan <- result + errChan <- err + close(resultChan) + close(errChan) + }() + + if _, ok := fSAC.FakeStore[resourceGroupName]; !ok { + fSAC.FakeStore[resourceGroupName] = make(map[string]storage.Account) + } + fSAC.FakeStore[resourceGroupName][accountName] = storage.Account{ + Name: &accountName, + Sku: parameters.Sku, + Kind: parameters.Kind, + Location: parameters.Location, + Identity: parameters.Identity, + Tags: parameters.Tags, + AccountProperties: &storage.AccountProperties{}, + } + result = fSAC.FakeStore[resourceGroupName][accountName] + result.Response.Response = &http.Response{ + StatusCode: http.StatusOK, + } + err = nil + return resultChan, errChan +} + +func (fSAC fakeStorageAccountClient) Delete(resourceGroupName string, accountName string) (result autorest.Response, err error) { + fSAC.mutex.Lock() + defer fSAC.mutex.Unlock() + + if rgAccounts, ok := fSAC.FakeStore[resourceGroupName]; ok { + if _, ok := rgAccounts[accountName]; ok { + delete(rgAccounts, accountName) + result.Response = &http.Response{ + StatusCode: http.StatusAccepted, + } + return result, nil + } + } + + result.Response = &http.Response{ + StatusCode: http.StatusNotFound, + } + err = autorest.DetailedError{ + StatusCode: http.StatusNotFound, + Message: "Not such StorageAccount", + } + return result, err +} + +func (fSAC fakeStorageAccountClient) ListKeys(resourceGroupName string, accountName string) (result storage.AccountListKeysResult, err error) { + return storage.AccountListKeysResult{}, nil +} + +func (fSAC fakeStorageAccountClient) ListByResourceGroup(resourceGroupName string) (result storage.AccountListResult, err error) { + return storage.AccountListResult{}, nil +} + +func (fSAC fakeStorageAccountClient) GetProperties(resourceGroupName string, accountName string) (result storage.Account, err error) { + fSAC.mutex.Lock() + defer fSAC.mutex.Unlock() + + if _, ok := fSAC.FakeStore[resourceGroupName]; ok { + if entity, ok := fSAC.FakeStore[resourceGroupName][accountName]; ok { + return entity, nil + } + } + + return result, autorest.DetailedError{ + StatusCode: http.StatusNotFound, + Message: "Not such StorageAccount", + } +} + +type fakeDisksClient struct { + mutex *sync.Mutex + FakeStore map[string]map[string]disk.Model +} + +func newFakeDisksClient() fakeDisksClient { + fDC := fakeDisksClient{} + fDC.FakeStore = make(map[string]map[string]disk.Model) + fDC.mutex = &sync.Mutex{} + return fDC +} + +func (fDC fakeDisksClient) CreateOrUpdate(resourceGroupName string, diskName string, diskParameter disk.Model, cancel <-chan struct{}) (<-chan disk.Model, <-chan error) { + fDC.mutex.Lock() + defer fDC.mutex.Unlock() + + resultChan := make(chan disk.Model, 1) + errChan := make(chan error, 1) + var result disk.Model + var err error + defer func() { + resultChan <- result + errChan <- err + close(resultChan) + close(errChan) + }() + + if _, ok := fDC.FakeStore[resourceGroupName]; !ok { + fDC.FakeStore[resourceGroupName] = make(map[string]disk.Model) + } + fDC.FakeStore[resourceGroupName][diskName] = diskParameter + result = fDC.FakeStore[resourceGroupName][diskName] + result.Response.Response = &http.Response{ + StatusCode: http.StatusOK, + } + err = nil + return resultChan, errChan +} + +func (fDC fakeDisksClient) Delete(resourceGroupName string, diskName string, cancel <-chan struct{}) (<-chan disk.OperationStatusResponse, <-chan error) { + fDC.mutex.Lock() + defer fDC.mutex.Unlock() + + respChan := make(chan disk.OperationStatusResponse, 1) + errChan := make(chan error, 1) + var resp disk.OperationStatusResponse + var err error + defer func() { + respChan <- resp + errChan <- err + close(respChan) + close(errChan) + }() + if rgDisks, ok := fDC.FakeStore[resourceGroupName]; ok { + if _, ok := rgDisks[diskName]; ok { + delete(rgDisks, diskName) + resp.Response = autorest.Response{ + Response: &http.Response{ + StatusCode: http.StatusAccepted, + }, + } + + err = nil + return respChan, errChan + } + } + resp.Response = autorest.Response{ + Response: &http.Response{ + StatusCode: http.StatusNotFound, + }, + } + err = autorest.DetailedError{ + StatusCode: http.StatusNotFound, + Message: "Not such Disk", + } + return respChan, errChan +} + +func (fDC fakeDisksClient) Get(resourceGroupName string, diskName string) (result disk.Model, err error) { + fDC.mutex.Lock() + defer fDC.mutex.Unlock() + + if _, ok := fDC.FakeStore[resourceGroupName]; ok { + if entity, ok := fDC.FakeStore[resourceGroupName][diskName]; ok { + return entity, nil + } + } + + return result, autorest.DetailedError{ + StatusCode: http.StatusNotFound, + Message: "Not such Disk", + } +} diff --git a/pkg/cloudprovider/providers/azure/azure_instance_metadata.go b/pkg/cloudprovider/providers/azure/azure_instance_metadata.go deleted file mode 100644 index 6df99083272..00000000000 --- a/pkg/cloudprovider/providers/azure/azure_instance_metadata.go +++ /dev/null @@ -1,113 +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 azure - -import ( - "encoding/json" - "io/ioutil" - "net/http" -) - -const metadataURL = "http://169.254.169.254/metadata/" - -// NetworkMetadata contains metadata about an instance's network -type NetworkMetadata struct { - Interface []NetworkInterface `json:"interface"` -} - -// NetworkInterface represents an instances network interface. -type NetworkInterface struct { - IPV4 NetworkData `json:"ipv4"` - IPV6 NetworkData `json:"ipv6"` - MAC string `json:"macAddress"` -} - -// NetworkData contains IP information for a network. -type NetworkData struct { - IPAddress []IPAddress `json:"ipAddress"` - Subnet []Subnet `json:"subnet"` -} - -// IPAddress represents IP address information. -type IPAddress struct { - PrivateIP string `json:"privateIPAddress"` - PublicIP string `json:"publicIPAddress"` -} - -// Subnet represents subnet information. -type Subnet struct { - Address string `json:"address"` - Prefix string `json:"prefix"` -} - -// InstanceMetadata knows how to query the Azure instance metadata server. -type InstanceMetadata struct { - baseURL string -} - -// NewInstanceMetadata creates an instance of the InstanceMetadata accessor object. -func NewInstanceMetadata() *InstanceMetadata { - return &InstanceMetadata{ - baseURL: metadataURL, - } -} - -// makeMetadataURL makes a complete metadata URL from the given path. -func (i *InstanceMetadata) makeMetadataURL(path string) string { - return i.baseURL + path -} - -// Object queries the metadata server and populates the passed in object -func (i *InstanceMetadata) Object(path string, obj interface{}) error { - data, err := i.queryMetadataBytes(path, "json") - if err != nil { - return err - } - return json.Unmarshal(data, obj) -} - -// Text queries the metadata server and returns the corresponding text -func (i *InstanceMetadata) Text(path string) (string, error) { - data, err := i.queryMetadataBytes(path, "text") - if err != nil { - return "", err - } - return string(data), err -} - -func (i *InstanceMetadata) queryMetadataBytes(path, format string) ([]byte, error) { - client := &http.Client{} - - req, err := http.NewRequest("GET", i.makeMetadataURL(path), nil) - if err != nil { - return nil, err - } - req.Header.Add("Metadata", "True") - - q := req.URL.Query() - q.Add("format", format) - q.Add("api-version", "2017-04-02") - req.URL.RawQuery = q.Encode() - - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - return ioutil.ReadAll(resp.Body) -} diff --git a/pkg/cloudprovider/providers/azure/azure_instances.go b/pkg/cloudprovider/providers/azure/azure_instances.go index ea85388ca4a..9f93c80750c 100644 --- a/pkg/cloudprovider/providers/azure/azure_instances.go +++ b/pkg/cloudprovider/providers/azure/azure_instances.go @@ -22,45 +22,16 @@ import ( "k8s.io/api/core/v1" "k8s.io/kubernetes/pkg/cloudprovider" - "github.com/Azure/azure-sdk-for-go/arm/compute" "github.com/golang/glog" "k8s.io/apimachinery/pkg/types" ) // NodeAddresses returns the addresses of the specified instance. func (az *Cloud) NodeAddresses(name types.NodeName) ([]v1.NodeAddress, error) { - if az.UseInstanceMetadata { - ipAddress := IPAddress{} - err := az.metadata.Object("instance/network/interface/0/ipv4/ipAddress/0", &ipAddress) - if err != nil { - return nil, err - } - addresses := []v1.NodeAddress{ - {Type: v1.NodeInternalIP, Address: ipAddress.PrivateIP}, - {Type: v1.NodeHostName, Address: string(name)}, - } - if len(ipAddress.PublicIP) > 0 { - addr := v1.NodeAddress{ - Type: v1.NodeExternalIP, - Address: ipAddress.PublicIP, - } - addresses = append(addresses, addr) - } - return addresses, nil - } - ip, err := az.getIPForMachine(name) + ip, err := az.GetIPForMachineWithRetry(name) if err != nil { - if az.CloudProviderBackoff { - glog.V(2).Infof("NodeAddresses(%s) backing off", name) - ip, err = az.GetIPForMachineWithRetry(name) - if err != nil { - glog.V(2).Infof("NodeAddresses(%s) abort backoff", name) - return nil, err - } - } else { - glog.Errorf("error: az.NodeAddresses, az.getIPForMachine(%s), err=%v", name, err) - return nil, err - } + glog.V(2).Infof("NodeAddresses(%s) abort backoff", name) + return nil, err } return []v1.NodeAddress{ @@ -73,7 +44,7 @@ func (az *Cloud) NodeAddresses(name types.NodeName) ([]v1.NodeAddress, error) { // This method will not be called from the node that is requesting this ID. i.e. metadata service // and other local methods cannot be used here func (az *Cloud) NodeAddressesByProviderID(providerID string) ([]v1.NodeAddress, error) { - name, err := splitProviderID(providerID) + name, err := az.vmSet.GetNodeNameByProviderID(providerID) if err != nil { return nil, err } @@ -89,57 +60,33 @@ func (az *Cloud) ExternalID(name types.NodeName) (string, error) { // InstanceExistsByProviderID returns true if the instance with the given provider id still exists and is running. // If false is returned with no error, the instance will be immediately deleted by the cloud controller manager. func (az *Cloud) InstanceExistsByProviderID(providerID string) (bool, error) { - return false, cloudprovider.NotImplemented -} + name, err := az.vmSet.GetNodeNameByProviderID(providerID) + if err != nil { + return false, err + } -func (az *Cloud) isCurrentInstance(name types.NodeName) (bool, error) { - nodeName := mapNodeNameToVMName(name) - metadataName, err := az.metadata.Text("instance/compute/name") - return (metadataName == nodeName), err + _, err = az.InstanceID(name) + if err != nil { + if err == cloudprovider.InstanceNotFound { + return false, nil + } + return false, err + } + + return true, nil } // InstanceID returns the cloud provider ID of the specified instance. // Note that if the instance does not exist or is no longer running, we must return ("", cloudprovider.InstanceNotFound) func (az *Cloud) InstanceID(name types.NodeName) (string, error) { - if az.UseInstanceMetadata { - isLocalInstance, err := az.isCurrentInstance(name) - if err != nil { - return "", err - } - if isLocalInstance { - externalInstanceID, err := az.metadata.Text("instance/compute/vmId") - if err == nil { - return externalInstanceID, nil - } - } - } - var machine compute.VirtualMachine - var exists bool - var err error - az.operationPollRateLimiter.Accept() - machine, exists, err = az.getVirtualMachine(name) - if err != nil { - if az.CloudProviderBackoff { - glog.V(2).Infof("InstanceID(%s) backing off", name) - machine, exists, err = az.GetVirtualMachineWithRetry(name) - if err != nil { - glog.V(2).Infof("InstanceID(%s) abort backoff", name) - return "", err - } - } else { - return "", err - } - } else if !exists { - return "", cloudprovider.InstanceNotFound - } - return *machine.ID, nil + return az.vmSet.GetInstanceIDByNodeName(string(name)) } // InstanceTypeByProviderID returns the cloudprovider instance type of the node with the specified unique providerID // This method will not be called from the node that is requesting this ID. i.e. metadata service // and other local methods cannot be used here func (az *Cloud) InstanceTypeByProviderID(providerID string) (string, error) { - name, err := splitProviderID(providerID) + name, err := az.vmSet.GetNodeNameByProviderID(providerID) if err != nil { return "", err } @@ -152,26 +99,7 @@ func (az *Cloud) InstanceTypeByProviderID(providerID string) (string, error) { // (Implementer Note): This is used by kubelet. Kubelet will label the node. Real log from kubelet: // Adding node label from cloud provider: beta.kubernetes.io/instance-type=[value] func (az *Cloud) InstanceType(name types.NodeName) (string, error) { - if az.UseInstanceMetadata { - isLocalInstance, err := az.isCurrentInstance(name) - if err != nil { - return "", err - } - if isLocalInstance { - machineType, err := az.metadata.Text("instance/compute/vmSize") - if err == nil { - return machineType, nil - } - } - } - machine, exists, err := az.getVirtualMachine(name) - if err != nil { - glog.Errorf("error: az.InstanceType(%s), az.getVirtualMachine(%s) err=%v", name, name, err) - return "", err - } else if !exists { - return "", cloudprovider.InstanceNotFound - } - return string(machine.HardwareProfile.VMSize), nil + return az.vmSet.GetInstanceTypeByNodeName(string(name)) } // AddSSHKeyToAllInstances adds an SSH public key as a legal identity for all instances @@ -180,45 +108,12 @@ func (az *Cloud) AddSSHKeyToAllInstances(user string, keyData []byte) error { return fmt.Errorf("not supported") } -// CurrentNodeName returns the name of the node we are currently running on -// On most clouds (e.g. GCE) this is the hostname, so we provide the hostname +// CurrentNodeName returns the name of the node we are currently running on. +// On Azure this is the hostname, so we just return the hostname. func (az *Cloud) CurrentNodeName(hostname string) (types.NodeName, error) { return types.NodeName(hostname), nil } -func (az *Cloud) listAllNodesInResourceGroup() ([]compute.VirtualMachine, error) { - allNodes := []compute.VirtualMachine{} - - az.operationPollRateLimiter.Accept() - glog.V(10).Infof("VirtualMachinesClient.List(%s): start", az.ResourceGroup) - result, err := az.VirtualMachinesClient.List(az.ResourceGroup) - glog.V(10).Infof("VirtualMachinesClient.List(%s): end", az.ResourceGroup) - if err != nil { - glog.Errorf("error: az.listAllNodesInResourceGroup(), az.VirtualMachinesClient.List(%s), err=%v", az.ResourceGroup, err) - return nil, err - } - - morePages := (result.Value != nil && len(*result.Value) > 1) - - for morePages { - allNodes = append(allNodes, *result.Value...) - - az.operationPollRateLimiter.Accept() - glog.V(10).Infof("VirtualMachinesClient.ListAllNextResults(%v): start", az.ResourceGroup) - result, err = az.VirtualMachinesClient.ListAllNextResults(result) - glog.V(10).Infof("VirtualMachinesClient.ListAllNextResults(%v): end", az.ResourceGroup) - if err != nil { - glog.Errorf("error: az.listAllNodesInResourceGroup(), az.VirtualMachinesClient.ListAllNextResults(%v), err=%v", result, err) - return nil, err - } - - morePages = (result.Value != nil && len(*result.Value) > 1) - } - - return allNodes, nil - -} - // mapNodeNameToVMName maps a k8s NodeName to an Azure VM Name // This is a simple string cast. func mapNodeNameToVMName(nodeName types.NodeName) string { diff --git a/pkg/cloudprovider/providers/azure/azure_loadbalancer.go b/pkg/cloudprovider/providers/azure/azure_loadbalancer.go index 5ad04a8f85a..d6f4bdfac38 100644 --- a/pkg/cloudprovider/providers/azure/azure_loadbalancer.go +++ b/pkg/cloudprovider/providers/azure/azure_loadbalancer.go @@ -18,282 +18,117 @@ package azure import ( "fmt" + "math" "strconv" "strings" "k8s.io/api/core/v1" - utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/sets" serviceapi "k8s.io/kubernetes/pkg/api/v1/service" - "github.com/Azure/azure-sdk-for-go/arm/compute" "github.com/Azure/azure-sdk-for-go/arm/network" "github.com/Azure/go-autorest/autorest/to" "github.com/golang/glog" - "k8s.io/apimachinery/pkg/types" ) -// ServiceAnnotationLoadBalancerInternal is the annotation used on the service -const ServiceAnnotationLoadBalancerInternal = "service.beta.kubernetes.io/azure-load-balancer-internal" +const ( + // ServiceAnnotationLoadBalancerInternal is the annotation used on the service + ServiceAnnotationLoadBalancerInternal = "service.beta.kubernetes.io/azure-load-balancer-internal" -// ServiceAnnotationLoadBalancerInternalSubnet is the annotation used on the service -// to specify what subnet it is exposed on -const ServiceAnnotationLoadBalancerInternalSubnet = "service.beta.kubernetes.io/azure-load-balancer-internal-subnet" + // ServiceAnnotationLoadBalancerInternalSubnet is the annotation used on the service + // to specify what subnet it is exposed on + ServiceAnnotationLoadBalancerInternalSubnet = "service.beta.kubernetes.io/azure-load-balancer-internal-subnet" + + // ServiceAnnotationLoadBalancerMode is the annotation used on the service to specify the + // Azure load balancer selection based on availability sets + // There are currently three possible load balancer selection modes : + // 1. Default mode - service has no annotation ("service.beta.kubernetes.io/azure-load-balancer-mode") + // In this case the Loadbalancer of the primary Availability set is selected + // 2. "__auto__" mode - service is annotated with __auto__ value, this when loadbalancer from any availability set + // is selected which has the minimum rules associated with it. + // 3. "as1,as2" mode - this is when the load balancer from the specified availability sets is selected that has the + // minimum rules associated with it. + ServiceAnnotationLoadBalancerMode = "service.beta.kubernetes.io/azure-load-balancer-mode" + + // ServiceAnnotationLoadBalancerAutoModeValue is the annotation used on the service to specify the + // Azure load balancer auto selection from the availability sets + ServiceAnnotationLoadBalancerAutoModeValue = "__auto__" + + // ServiceAnnotationDNSLabelName is the annotation used on the service + // to specify the DNS label name for the service. + ServiceAnnotationDNSLabelName = "service.beta.kubernetes.io/azure-dns-label-name" + + // ServiceAnnotationSharedSecurityRule is the annotation used on the service + // to specify that the service should be exposed using an Azure security rule + // that may be shared with other service, trading specificity of rules for an + // increase in the number of services that can be exposed. This relies on the + // Azure "augmented security rules" feature which at the time of writing is in + // preview and available only in certain regions. + ServiceAnnotationSharedSecurityRule = "service.beta.kubernetes.io/azure-shared-securityrule" +) + +// ServiceAnnotationLoadBalancerResourceGroup is the annotation used on the service +// to specify the resource group of load balancer objects that are not in the same resource group as the cluster. +const ServiceAnnotationLoadBalancerResourceGroup = "service.beta.kubernetes.io/azure-load-balancer-resource-group" // GetLoadBalancer returns whether the specified load balancer exists, and // if so, what its status is. func (az *Cloud) GetLoadBalancer(clusterName string, service *v1.Service) (status *v1.LoadBalancerStatus, exists bool, err error) { - isInternal := requiresInternalLoadBalancer(service) - lbName := getLoadBalancerName(clusterName, isInternal) - serviceName := getServiceName(service) - - lb, existsLb, err := az.getAzureLoadBalancer(lbName) + _, status, exists, err = az.getServiceLoadBalancer(service, clusterName, nil, false) if err != nil { return nil, false, err } - if !existsLb { - glog.V(5).Infof("get(%s): lb(%s) - doesn't exist", serviceName, lbName) - return nil, false, nil + if exists == false { + serviceName := getServiceName(service) + glog.V(5).Infof("getloadbalancer (cluster:%s) (service:%s)- IP doesn't exist in any of the lbs", clusterName, serviceName) + return nil, false, fmt.Errorf("Service(%s) - Loadbalancer not found", serviceName) } - - var lbIP *string - - if isInternal { - lbFrontendIPConfigName := getFrontendIPConfigName(service, subnet(service)) - for _, ipConfiguration := range *lb.FrontendIPConfigurations { - if lbFrontendIPConfigName == *ipConfiguration.Name { - lbIP = ipConfiguration.PrivateIPAddress - break - } - } - } else { - // TODO: Consider also read address from lb's FrontendIPConfigurations - pipName, err := az.determinePublicIPName(clusterName, service) - if err != nil { - return nil, false, err - } - pip, existsPip, err := az.getPublicIPAddress(pipName) - if err != nil { - return nil, false, err - } - if existsPip { - lbIP = pip.IPAddress - } - } - - if lbIP == nil { - glog.V(5).Infof("get(%s): lb(%s) - IP doesn't exist", serviceName, lbName) - return nil, false, nil - } - - return &v1.LoadBalancerStatus{ - Ingress: []v1.LoadBalancerIngress{{IP: *lbIP}}, - }, true, nil + return status, true, nil } -func (az *Cloud) determinePublicIPName(clusterName string, service *v1.Service) (string, error) { - loadBalancerIP := service.Spec.LoadBalancerIP - if len(loadBalancerIP) == 0 { - return getPublicIPName(clusterName, service), nil +func getPublicIPDomainNameLabel(service *v1.Service) string { + if labelName, found := service.Annotations[ServiceAnnotationDNSLabelName]; found { + return labelName } - - az.operationPollRateLimiter.Accept() - glog.V(10).Infof("PublicIPAddressesClient.List(%v): start", az.ResourceGroup) - list, err := az.PublicIPAddressesClient.List(az.ResourceGroup) - glog.V(10).Infof("PublicIPAddressesClient.List(%v): end", az.ResourceGroup) - if err != nil { - return "", err - } - - if list.Value != nil { - for ix := range *list.Value { - ip := &(*list.Value)[ix] - if ip.PublicIPAddressPropertiesFormat.IPAddress != nil && - *ip.PublicIPAddressPropertiesFormat.IPAddress == loadBalancerIP { - return *ip.Name, nil - } - } - } - // TODO: follow next link here? Will there really ever be that many public IPs? - - return "", fmt.Errorf("user supplied IP Address %s was not found", loadBalancerIP) + return "" } // EnsureLoadBalancer creates a new load balancer 'name', or updates the existing one. Returns the status of the balancer func (az *Cloud) EnsureLoadBalancer(clusterName string, service *v1.Service, nodes []*v1.Node) (*v1.LoadBalancerStatus, error) { - isInternal := requiresInternalLoadBalancer(service) - lbName := getLoadBalancerName(clusterName, isInternal) - // When a client updates the internal load balancer annotation, // the service may be switched from an internal LB to a public one, or vise versa. // Here we'll firstly ensure service do not lie in the opposite LB. - err := az.cleanupLoadBalancer(clusterName, service, !isInternal) - if err != nil { - return nil, err - } - serviceName := getServiceName(service) - glog.V(5).Infof("ensure(%s): START clusterName=%q lbName=%q", serviceName, clusterName, lbName) + glog.V(5).Infof("ensureloadbalancer(%s): START clusterName=%q", serviceName, clusterName) + flippedService := flipServiceInternalAnnotation(service) + if _, err := az.reconcileLoadBalancer(clusterName, flippedService, nil, false /* wantLb */); err != nil { + return nil, err + } - az.operationPollRateLimiter.Accept() - glog.V(10).Infof("SecurityGroupsClient.Get(%q): start", az.SecurityGroupName) - sg, err := az.SecurityGroupsClient.Get(az.ResourceGroup, az.SecurityGroupName, "") - glog.V(10).Infof("SecurityGroupsClient.Get(%q): end", az.SecurityGroupName) + if _, err := az.reconcilePublicIP(clusterName, service, true /* wantLb */); err != nil { + return nil, err + } + + lb, err := az.reconcileLoadBalancer(clusterName, service, nodes, true /* wantLb */) if err != nil { return nil, err } - sg, sgNeedsUpdate, err := az.reconcileSecurityGroup(sg, clusterName, service, true /* wantLb */) + + lbStatus, err := az.getServiceLoadBalancerStatus(service, lb) if err != nil { return nil, err } - if sgNeedsUpdate { - glog.V(3).Infof("ensure(%s): sg(%s) - updating", serviceName, *sg.Name) - // azure-sdk-for-go introduced contraint validation which breaks the updating here if we don't set these - // to nil. This is a workaround until https://github.com/Azure/go-autorest/issues/112 is fixed - sg.SecurityGroupPropertiesFormat.NetworkInterfaces = nil - sg.SecurityGroupPropertiesFormat.Subnets = nil - az.operationPollRateLimiter.Accept() - glog.V(10).Infof("SecurityGroupsClient.CreateOrUpdate(%q): start", *sg.Name) - respChan, errChan := az.SecurityGroupsClient.CreateOrUpdate(az.ResourceGroup, *sg.Name, sg, nil) - resp := <-respChan - err := <-errChan - glog.V(10).Infof("SecurityGroupsClient.CreateOrUpdate(%q): end", *sg.Name) - if az.CloudProviderBackoff && shouldRetryAPIRequest(resp.Response, err) { - glog.V(2).Infof("ensure(%s) backing off: sg(%s) - updating", serviceName, *sg.Name) - retryErr := az.CreateOrUpdateSGWithRetry(sg) - if retryErr != nil { - glog.V(2).Infof("ensure(%s) abort backoff: sg(%s) - updating", serviceName, *sg.Name) - return nil, retryErr - } - } - if err != nil { - return nil, err - } - } - lb, existsLb, err := az.getAzureLoadBalancer(lbName) - if err != nil { + var serviceIP *string + if lbStatus != nil && len(lbStatus.Ingress) > 0 { + serviceIP = &lbStatus.Ingress[0].IP + } + glog.V(10).Infof("Calling reconcileSecurityGroup from EnsureLoadBalancer for %s with IP %s, wantLb = true", service.Name, logSafe(serviceIP)) + if _, err := az.reconcileSecurityGroup(clusterName, service, serviceIP, true /* wantLb */); err != nil { return nil, err } - if !existsLb { - lb = network.LoadBalancer{ - Name: &lbName, - Location: &az.Location, - LoadBalancerPropertiesFormat: &network.LoadBalancerPropertiesFormat{}, - } - } - var lbIP *string - var fipConfigurationProperties *network.FrontendIPConfigurationPropertiesFormat - - if isInternal { - subnetName := subnet(service) - if subnetName == nil { - subnetName = &az.SubnetName - } - subnet, existsSubnet, err := az.getSubnet(az.VnetName, *subnetName) - if err != nil { - return nil, err - } - - if !existsSubnet { - return nil, fmt.Errorf("ensure(%s): lb(%s) - failed to get subnet: %s/%s", serviceName, lbName, az.VnetName, az.SubnetName) - } - - configProperties := network.FrontendIPConfigurationPropertiesFormat{ - Subnet: &network.Subnet{ - ID: subnet.ID, - }, - } - - loadBalancerIP := service.Spec.LoadBalancerIP - if loadBalancerIP != "" { - configProperties.PrivateIPAllocationMethod = network.Static - configProperties.PrivateIPAddress = &loadBalancerIP - lbIP = &loadBalancerIP - } else { - // We'll need to call GetLoadBalancer later to retrieve allocated IP. - configProperties.PrivateIPAllocationMethod = network.Dynamic - } - - fipConfigurationProperties = &configProperties - } else { - pipName, err := az.determinePublicIPName(clusterName, service) - if err != nil { - return nil, err - } - pip, err := az.ensurePublicIPExists(serviceName, pipName) - if err != nil { - return nil, err - } - - lbIP = pip.IPAddress - fipConfigurationProperties = &network.FrontendIPConfigurationPropertiesFormat{ - PublicIPAddress: &network.PublicIPAddress{ID: pip.ID}, - } - } - - lb, lbNeedsUpdate, err := az.reconcileLoadBalancer(lb, fipConfigurationProperties, clusterName, service, nodes) - if err != nil { - return nil, err - } - if !existsLb || lbNeedsUpdate { - glog.V(3).Infof("ensure(%s): lb(%s) - updating", serviceName, lbName) - az.operationPollRateLimiter.Accept() - glog.V(10).Infof("LoadBalancerClient.CreateOrUpdate(%q): start", *lb.Name) - respChan, errChan := az.LoadBalancerClient.CreateOrUpdate(az.ResourceGroup, *lb.Name, lb, nil) - resp := <-respChan - err := <-errChan - glog.V(10).Infof("LoadBalancerClient.CreateOrUpdate(%q): end", *lb.Name) - if az.CloudProviderBackoff && shouldRetryAPIRequest(resp.Response, err) { - glog.V(2).Infof("ensure(%s) backing off: lb(%s) - updating", serviceName, lbName) - retryErr := az.CreateOrUpdateLBWithRetry(lb) - if retryErr != nil { - glog.V(2).Infof("ensure(%s) abort backoff: lb(%s) - updating", serviceName, lbName) - return nil, retryErr - } - } - if err != nil { - return nil, err - } - } - - // Add the machines to the backend pool if they're not already - lbBackendName := getBackendPoolName(clusterName) - lbBackendPoolID := az.getBackendPoolID(lbName, lbBackendName) - hostUpdates := make([]func() error, len(nodes)) - for i, node := range nodes { - localNodeName := node.Name - f := func() error { - err := az.ensureHostInPool(serviceName, types.NodeName(localNodeName), lbBackendPoolID) - if err != nil { - return fmt.Errorf("ensure(%s): lb(%s) - failed to ensure host in pool: %q", serviceName, lbName, err) - } - return nil - } - hostUpdates[i] = f - } - - errs := utilerrors.AggregateGoroutines(hostUpdates...) - if errs != nil { - return nil, utilerrors.Flatten(errs) - } - - glog.V(2).Infof("ensure(%s): lb(%s) finished", serviceName, lbName) - - if lbIP == nil { - lbStatus, exists, err := az.GetLoadBalancer(clusterName, service) - if err != nil { - return nil, err - } - if !exists { - return nil, fmt.Errorf("ensure(%s): lb(%s) - failed to get back load balancer", serviceName, lbName) - } - return lbStatus, nil - } - - return &v1.LoadBalancerStatus{ - Ingress: []v1.LoadBalancerIngress{{IP: *lbIP}}, - }, nil + return lbStatus, nil } // UpdateLoadBalancer updates hosts under the specified load balancer. @@ -310,153 +145,256 @@ func (az *Cloud) UpdateLoadBalancer(clusterName string, service *v1.Service, nod // doesn't exist even if some part of it is still laying around. func (az *Cloud) EnsureLoadBalancerDeleted(clusterName string, service *v1.Service) error { isInternal := requiresInternalLoadBalancer(service) - lbName := getLoadBalancerName(clusterName, isInternal) serviceName := getServiceName(service) + glog.V(5).Infof("delete(%s): START clusterName=%q", serviceName, clusterName) - glog.V(5).Infof("delete(%s): START clusterName=%q lbName=%q", serviceName, clusterName, lbName) - - err := az.cleanupLoadBalancer(clusterName, service, isInternal) + serviceIPToCleanup, err := az.findServiceIPAddress(clusterName, service, isInternal) if err != nil { return err } - sg, existsSg, err := az.getSecurityGroup() - if err != nil { + glog.V(10).Infof("Calling reconcileSecurityGroup from EnsureLoadBalancerDeleted for %s with IP %s, wantLb = false", service.Name, serviceIPToCleanup) + if _, err := az.reconcileSecurityGroup(clusterName, service, &serviceIPToCleanup, false /* wantLb */); err != nil { return err } - if existsSg { - reconciledSg, sgNeedsUpdate, reconcileErr := az.reconcileSecurityGroup(sg, clusterName, service, false /* wantLb */) - if reconcileErr != nil { - return reconcileErr - } - if sgNeedsUpdate { - glog.V(3).Infof("delete(%s): sg(%s) - updating", serviceName, az.SecurityGroupName) - // azure-sdk-for-go introduced contraint validation which breaks the updating here if we don't set these - // to nil. This is a workaround until https://github.com/Azure/go-autorest/issues/112 is fixed - sg.SecurityGroupPropertiesFormat.NetworkInterfaces = nil - sg.SecurityGroupPropertiesFormat.Subnets = nil - az.operationPollRateLimiter.Accept() - glog.V(10).Infof("SecurityGroupsClient.CreateOrUpdate(%q): start", *reconciledSg.Name) - respChan, errChan := az.SecurityGroupsClient.CreateOrUpdate(az.ResourceGroup, *reconciledSg.Name, reconciledSg, nil) - resp := <-respChan - err := <-errChan - glog.V(10).Infof("SecurityGroupsClient.CreateOrUpdate(%q): end", *reconciledSg.Name) - if az.CloudProviderBackoff && shouldRetryAPIRequest(resp.Response, err) { - glog.V(2).Infof("delete(%s) backing off: sg(%s) - updating", serviceName, az.SecurityGroupName) - retryErr := az.CreateOrUpdateSGWithRetry(reconciledSg) - if retryErr != nil { - err = retryErr - glog.V(2).Infof("delete(%s) abort backoff: sg(%s) - updating", serviceName, az.SecurityGroupName) - } - } - if err != nil { - return err - } - } + + if _, err := az.reconcileLoadBalancer(clusterName, service, nil, false /* wantLb */); err != nil { + return err + } + + if _, err := az.reconcilePublicIP(clusterName, service, false /* wantLb */); err != nil { + return err } glog.V(2).Infof("delete(%s): FINISH", serviceName) return nil } -func (az *Cloud) cleanupLoadBalancer(clusterName string, service *v1.Service, isInternalLb bool) error { - lbName := getLoadBalancerName(clusterName, isInternalLb) - serviceName := getServiceName(service) +// getServiceLoadBalancer gets the loadbalancer for the service if it already exists. +// If wantLb is TRUE then -it selects a new load balancer. +// In case the selected load balancer does not exists it returns network.LoadBalancer struct +// with added metadata (such as name, location) and existsLB set to FALSE. +// By default - cluster default LB is returned. +func (az *Cloud) getServiceLoadBalancer(service *v1.Service, clusterName string, nodes []*v1.Node, wantLb bool) (lb *network.LoadBalancer, status *v1.LoadBalancerStatus, exists bool, err error) { + isInternal := requiresInternalLoadBalancer(service) + var defaultLB *network.LoadBalancer + primaryVMSetName := az.vmSet.GetPrimaryVMSetName() + defaultLBName := az.getLoadBalancerName(clusterName, primaryVMSetName, isInternal) - glog.V(10).Infof("ensure lb deleted: clusterName=%q, serviceName=%s, lbName=%q", clusterName, serviceName, lbName) - - lb, existsLb, err := az.getAzureLoadBalancer(lbName) + existingLBs, err := az.ListLBWithRetry() if err != nil { - return err + return nil, nil, false, err } - if existsLb { - var publicIPToCleanup *string - if !isInternalLb { - // Find public ip resource to clean up from IP configuration - lbFrontendIPConfigName := getFrontendIPConfigName(service, nil) - for _, config := range *lb.FrontendIPConfigurations { - if strings.EqualFold(*config.Name, lbFrontendIPConfigName) { - if config.PublicIPAddress != nil { - // Only ID property is available - publicIPToCleanup = config.PublicIPAddress.ID - } - break - } + // check if the service already has a load balancer + if existingLBs != nil { + for _, existingLB := range existingLBs { + if strings.EqualFold(*existingLB.Name, defaultLBName) { + defaultLB = &existingLB } - } - - lb, lbNeedsUpdate, reconcileErr := az.reconcileLoadBalancer(lb, nil, clusterName, service, []*v1.Node{}) - if reconcileErr != nil { - return reconcileErr - } - if lbNeedsUpdate { - if len(*lb.FrontendIPConfigurations) > 0 { - glog.V(3).Infof("delete(%s): lb(%s) - updating", serviceName, lbName) - az.operationPollRateLimiter.Accept() - glog.V(10).Infof("LoadBalancerClient.CreateOrUpdate(%q): start", *lb.Name) - respChan, errChan := az.LoadBalancerClient.CreateOrUpdate(az.ResourceGroup, *lb.Name, lb, nil) - resp := <-respChan - err := <-errChan - glog.V(10).Infof("LoadBalancerClient.CreateOrUpdate(%q): end", *lb.Name) - if az.CloudProviderBackoff && shouldRetryAPIRequest(resp.Response, err) { - glog.V(2).Infof("delete(%s) backing off: sg(%s) - updating", serviceName, az.SecurityGroupName) - retryErr := az.CreateOrUpdateLBWithRetry(lb) - if retryErr != nil { - err = retryErr - glog.V(2).Infof("delete(%s) abort backoff: sg(%s) - updating", serviceName, az.SecurityGroupName) - } - } - if err != nil { - return err - } - } else { - glog.V(3).Infof("delete(%s): lb(%s) - deleting; no remaining frontendipconfigs", serviceName, lbName) - - az.operationPollRateLimiter.Accept() - glog.V(10).Infof("LoadBalancerClient.Delete(%q): start", lbName) - respChan, errChan := az.LoadBalancerClient.Delete(az.ResourceGroup, lbName, nil) - resp := <-respChan - err := <-errChan - glog.V(10).Infof("LoadBalancerClient.Delete(%q): end", lbName) - if az.CloudProviderBackoff && shouldRetryAPIRequest(resp, err) { - glog.V(2).Infof("delete(%s) backing off: lb(%s) - deleting; no remaining frontendipconfigs", serviceName, lbName) - retryErr := az.DeleteLBWithRetry(lbName) - if retryErr != nil { - err = retryErr - glog.V(2).Infof("delete(%s) abort backoff: lb(%s) - deleting; no remaining frontendipconfigs", serviceName, lbName) - } - } - if err != nil { - return err - } + if isInternalLoadBalancer(&existingLB) != isInternal { + continue } - } - - // Public IP can be deleted after frontend ip configuration rule deleted. - if publicIPToCleanup != nil { - // Only delete an IP address if we created it, deducing by name. - if index := strings.LastIndex(*publicIPToCleanup, "/"); index != -1 { - managedPipName := getPublicIPName(clusterName, service) - pipName := (*publicIPToCleanup)[index+1:] - if strings.EqualFold(managedPipName, pipName) { - glog.V(5).Infof("Deleting public IP resource %q.", pipName) - err = az.ensurePublicIPDeleted(serviceName, pipName) - if err != nil { - return err - } - } else { - glog.V(5).Infof("Public IP resource %q found, but it does not match managed name %q, skip deleting.", pipName, managedPipName) - } + status, err = az.getServiceLoadBalancerStatus(service, &existingLB) + if err != nil { + return nil, nil, false, err } + if status == nil { + // service is not om this load balancer + continue + } + + return &existingLB, status, true, nil } } - return nil + // service does not have a load balancer, select one + if wantLb { + // select new load balancer for service + selectedLB, exists, err := az.selectLoadBalancer(clusterName, service, &existingLBs, nodes) + if err != nil { + return nil, nil, false, err + } + + return selectedLB, nil, exists, err + } + + // create a default LB with meta data if not present + if defaultLB == nil { + defaultLB = &network.LoadBalancer{ + Name: &defaultLBName, + Location: &az.Location, + LoadBalancerPropertiesFormat: &network.LoadBalancerPropertiesFormat{}, + } + } + + return defaultLB, nil, false, nil } -func (az *Cloud) ensurePublicIPExists(serviceName, pipName string) (*network.PublicIPAddress, error) { - pip, existsPip, err := az.getPublicIPAddress(pipName) +// select load balancer for the service in the cluster +// the selection algorithm selectes the the load balancer with currently has +// the minimum lb rules, there there are multiple LB's with same number of rules +// it selects the first one (sorted based on name) +func (az *Cloud) selectLoadBalancer(clusterName string, service *v1.Service, existingLBs *[]network.LoadBalancer, nodes []*v1.Node) (selectedLB *network.LoadBalancer, existsLb bool, err error) { + isInternal := requiresInternalLoadBalancer(service) + serviceName := getServiceName(service) + glog.V(3).Infof("selectLoadBalancer(%s): isInternal(%s) - start", serviceName, isInternal) + vmSetNames, err := az.vmSet.GetVMSetNames(service, nodes) + if err != nil { + glog.Errorf("az.selectLoadBalancer: cluster(%s) service(%s) isInternal(%t) - az.GetVMSetNames failed, err=(%v)", clusterName, serviceName, isInternal, err) + return nil, false, err + } + glog.Infof("selectLoadBalancer: cluster(%s) service(%s) isInternal(%t) - vmSetNames %v", clusterName, serviceName, isInternal, *vmSetNames) + + mapExistingLBs := map[string]network.LoadBalancer{} + for _, lb := range *existingLBs { + mapExistingLBs[*lb.Name] = lb + } + selectedLBRuleCount := math.MaxInt32 + for _, currASName := range *vmSetNames { + currLBName := az.getLoadBalancerName(clusterName, currASName, isInternal) + lb, exists := mapExistingLBs[currLBName] + if !exists { + // select this LB as this is a new LB and will have minimum rules + // create tmp lb struct to hold metadata for the new load-balancer + selectedLB = &network.LoadBalancer{ + Name: &currLBName, + Location: &az.Location, + LoadBalancerPropertiesFormat: &network.LoadBalancerPropertiesFormat{}, + } + + return selectedLB, false, nil + } + + lbRules := *lb.LoadBalancingRules + currLBRuleCount := 0 + if lbRules != nil { + currLBRuleCount = len(lbRules) + } + if currLBRuleCount < selectedLBRuleCount { + selectedLBRuleCount = currLBRuleCount + selectedLB = &lb + } + } + + if selectedLB == nil { + err = fmt.Errorf("selectLoadBalancer: cluster(%s) service(%s) isInternal(%t) - unable to find load balancer for selected VM sets %v", clusterName, serviceName, isInternal, *vmSetNames) + glog.Error(err) + return nil, false, err + } + // validate if the selected LB has not exceeded the MaximumLoadBalancerRuleCount + if az.Config.MaximumLoadBalancerRuleCount != 0 && selectedLBRuleCount >= az.Config.MaximumLoadBalancerRuleCount { + err = fmt.Errorf("selectLoadBalancer: cluster(%s) service(%s) isInternal(%t) - all available load balancers have exceeded maximum rule limit %d, vmSetNames (%v)", clusterName, serviceName, isInternal, selectedLBRuleCount, *vmSetNames) + glog.Error(err) + return selectedLB, existsLb, err + } + + return selectedLB, existsLb, nil +} + +func (az *Cloud) getServiceLoadBalancerStatus(service *v1.Service, lb *network.LoadBalancer) (status *v1.LoadBalancerStatus, err error) { + if lb == nil { + glog.V(10).Infof("getServiceLoadBalancerStatus lb is nil") + return nil, nil + } + if lb.FrontendIPConfigurations == nil || *lb.FrontendIPConfigurations == nil { + return nil, nil + } + isInternal := requiresInternalLoadBalancer(service) + lbFrontendIPConfigName := getFrontendIPConfigName(service, subnet(service)) + serviceName := getServiceName(service) + for _, ipConfiguration := range *lb.FrontendIPConfigurations { + if lbFrontendIPConfigName == *ipConfiguration.Name { + var lbIP *string + if isInternal { + lbIP = ipConfiguration.PrivateIPAddress + } else { + if ipConfiguration.PublicIPAddress == nil { + return nil, fmt.Errorf("get(%s): lb(%s) - failed to get LB PublicIPAddress is Nil", serviceName, *lb.Name) + } + pipID := ipConfiguration.PublicIPAddress.ID + if pipID == nil { + return nil, fmt.Errorf("get(%s): lb(%s) - failed to get LB PublicIPAddress ID is Nil", serviceName, *lb.Name) + } + pipName, err := getLastSegment(*pipID) + if err != nil { + return nil, fmt.Errorf("get(%s): lb(%s) - failed to get LB PublicIPAddress Name from ID(%s)", serviceName, *lb.Name, *pipID) + } + pip, existsPip, err := az.getPublicIPAddress(az.getPublicIPAddressResourceGroup(service), pipName) + if err != nil { + return nil, err + } + if existsPip { + lbIP = pip.IPAddress + } + } + + return &v1.LoadBalancerStatus{Ingress: []v1.LoadBalancerIngress{{IP: *lbIP}}}, nil + } + } + + return nil, nil +} + +func (az *Cloud) determinePublicIPName(clusterName string, service *v1.Service) (string, error) { + loadBalancerIP := service.Spec.LoadBalancerIP + if len(loadBalancerIP) == 0 { + return getPublicIPName(clusterName, service), nil + } + + pipResourceGroup := az.getPublicIPAddressResourceGroup(service) + + pips, err := az.ListPIPWithRetry(pipResourceGroup) + if err != nil { + return "", err + } + + for _, pip := range pips { + if pip.PublicIPAddressPropertiesFormat.IPAddress != nil && + *pip.PublicIPAddressPropertiesFormat.IPAddress == loadBalancerIP { + return *pip.Name, nil + } + } + return "", fmt.Errorf("user supplied IP Address %s was not found in resource group %s", loadBalancerIP, pipResourceGroup) +} + +func flipServiceInternalAnnotation(service *v1.Service) *v1.Service { + copyService := service.DeepCopy() + if copyService.Annotations == nil { + copyService.Annotations = map[string]string{} + } + if v, ok := copyService.Annotations[ServiceAnnotationLoadBalancerInternal]; ok && v == "true" { + // If it is internal now, we make it external by remove the annotation + delete(copyService.Annotations, ServiceAnnotationLoadBalancerInternal) + } else { + // If it is external now, we make it internal + copyService.Annotations[ServiceAnnotationLoadBalancerInternal] = "true" + } + return copyService +} + +func (az *Cloud) findServiceIPAddress(clusterName string, service *v1.Service, isInternalLb bool) (string, error) { + if len(service.Spec.LoadBalancerIP) > 0 { + return service.Spec.LoadBalancerIP, nil + } + + lbStatus, existsLb, err := az.GetLoadBalancer(clusterName, service) + if err != nil { + return "", err + } + if !existsLb { + return "", fmt.Errorf("Expected to find an IP address for service %s but did not", service.Name) + } + if len(lbStatus.Ingress) < 1 { + return "", fmt.Errorf("Expected to find an IP address for service %s but it had no ingresses", service.Name) + } + + return lbStatus.Ingress[0].IP, nil +} + +func (az *Cloud) ensurePublicIPExists(service *v1.Service, pipName string, domainNameLabel string) (*network.PublicIPAddress, error) { + pipResourceGroup := az.getPublicIPAddressResourceGroup(service) + pip, existsPip, err := az.getPublicIPAddress(pipResourceGroup, pipName) if err != nil { return nil, err } @@ -464,79 +402,58 @@ func (az *Cloud) ensurePublicIPExists(serviceName, pipName string) (*network.Pub return &pip, nil } + serviceName := getServiceName(service) pip.Name = to.StringPtr(pipName) pip.Location = to.StringPtr(az.Location) pip.PublicIPAddressPropertiesFormat = &network.PublicIPAddressPropertiesFormat{ PublicIPAllocationMethod: network.Static, } - pip.Tags = &map[string]*string{"service": &serviceName} - - glog.V(3).Infof("ensure(%s): pip(%s) - creating", serviceName, *pip.Name) - az.operationPollRateLimiter.Accept() - glog.V(10).Infof("PublicIPAddressesClient.CreateOrUpdate(%q): start", *pip.Name) - respChan, errChan := az.PublicIPAddressesClient.CreateOrUpdate(az.ResourceGroup, *pip.Name, pip, nil) - resp := <-respChan - err = <-errChan - glog.V(10).Infof("PublicIPAddressesClient.CreateOrUpdate(%q): end", *pip.Name) - if az.CloudProviderBackoff && shouldRetryAPIRequest(resp.Response, err) { - glog.V(2).Infof("ensure(%s) backing off: pip(%s) - creating", serviceName, *pip.Name) - retryErr := az.CreateOrUpdatePIPWithRetry(pip) - if retryErr != nil { - glog.V(2).Infof("ensure(%s) abort backoff: pip(%s) - creating", serviceName, *pip.Name) - err = retryErr + if len(domainNameLabel) > 0 { + pip.PublicIPAddressPropertiesFormat.DNSSettings = &network.PublicIPAddressDNSSettings{ + DomainNameLabel: &domainNameLabel, } } + pip.Tags = &map[string]*string{"service": &serviceName} + glog.V(3).Infof("ensure(%s): pip(%s) - creating", serviceName, *pip.Name) + az.operationPollRateLimiter.Accept() + glog.V(10).Infof("CreateOrUpdatePIPWithRetry(%s, %q): start", pipResourceGroup, *pip.Name) + err = az.CreateOrUpdatePIPWithRetry(pipResourceGroup, pip) if err != nil { + glog.V(2).Infof("ensure(%s) abort backoff: pip(%s) - creating", serviceName, *pip.Name) return nil, err } + glog.V(10).Infof("CreateOrUpdatePIPWithRetry(%s, %q): end", pipResourceGroup, *pip.Name) az.operationPollRateLimiter.Accept() - glog.V(10).Infof("PublicIPAddressesClient.Get(%q): start", *pip.Name) - pip, err = az.PublicIPAddressesClient.Get(az.ResourceGroup, *pip.Name, "") - glog.V(10).Infof("PublicIPAddressesClient.Get(%q): end", *pip.Name) + glog.V(10).Infof("PublicIPAddressesClient.Get(%s, %q): start", pipResourceGroup, *pip.Name) + pip, err = az.PublicIPAddressesClient.Get(pipResourceGroup, *pip.Name, "") + glog.V(10).Infof("PublicIPAddressesClient.Get(%s, %q): end", pipResourceGroup, *pip.Name) if err != nil { return nil, err } return &pip, nil - -} - -func (az *Cloud) ensurePublicIPDeleted(serviceName, pipName string) error { - glog.V(2).Infof("ensure(%s): pip(%s) - deleting", serviceName, pipName) - az.operationPollRateLimiter.Accept() - glog.V(10).Infof("PublicIPAddressesClient.Delete(%q): start", pipName) - resp, deleteErrChan := az.PublicIPAddressesClient.Delete(az.ResourceGroup, pipName, nil) - deleteErr := <-deleteErrChan - glog.V(10).Infof("PublicIPAddressesClient.Delete(%q): end", pipName) // response not read yet... - if az.CloudProviderBackoff && shouldRetryAPIRequest(<-resp, deleteErr) { - glog.V(2).Infof("ensure(%s) backing off: pip(%s) - deleting", serviceName, pipName) - retryErr := az.DeletePublicIPWithRetry(pipName) - if retryErr != nil { - glog.V(2).Infof("ensure(%s) abort backoff: pip(%s) - deleting", serviceName, pipName) - return retryErr - } - } - _, realErr := checkResourceExistsFromError(deleteErr) - if realErr != nil { - return nil - } - return nil } // This ensures load balancer exists and the frontend ip config is setup. // This also reconciles the Service's Ports with the LoadBalancer config. // This entails adding rules/probes for expected Ports and removing stale rules/ports. -func (az *Cloud) reconcileLoadBalancer(lb network.LoadBalancer, fipConfigurationProperties *network.FrontendIPConfigurationPropertiesFormat, clusterName string, service *v1.Service, nodes []*v1.Node) (network.LoadBalancer, bool, error) { +// nodes only used if wantLb is true +func (az *Cloud) reconcileLoadBalancer(clusterName string, service *v1.Service, nodes []*v1.Node, wantLb bool) (*network.LoadBalancer, error) { isInternal := requiresInternalLoadBalancer(service) - lbName := getLoadBalancerName(clusterName, isInternal) serviceName := getServiceName(service) + glog.V(2).Infof("reconcileLoadBalancer(%s) - wantLb(%t): started", serviceName, wantLb) + lb, _, _, err := az.getServiceLoadBalancer(service, clusterName, nodes, wantLb) + if err != nil { + return nil, err + } + lbName := *lb.Name + glog.V(2).Infof("reconcileLoadBalancer(%s): lb(%s) wantLb(%t) resolved load balancer name", serviceName, lbName, wantLb) lbFrontendIPConfigName := getFrontendIPConfigName(service, subnet(service)) lbFrontendIPConfigID := az.getFrontendIPConfigID(lbName, lbFrontendIPConfigName) lbBackendPoolName := getBackendPoolName(clusterName) lbBackendPoolID := az.getBackendPoolID(lbName, lbBackendPoolName) - wantLb := fipConfigurationProperties != nil dirtyLb := false // Ensure LoadBalancer's Backend Pool Configuration @@ -573,6 +490,7 @@ func (az *Cloud) reconcileLoadBalancer(lb network.LoadBalancer, fipConfiguration if lb.FrontendIPConfigurations != nil { newConfigs = *lb.FrontendIPConfigurations } + if !wantLb { for i := len(newConfigs) - 1; i >= 0; i-- { config := newConfigs[i] @@ -601,6 +519,51 @@ func (az *Cloud) reconcileLoadBalancer(lb network.LoadBalancer, fipConfiguration } } if !foundConfig { + // construct FrontendIPConfigurationPropertiesFormat + var fipConfigurationProperties *network.FrontendIPConfigurationPropertiesFormat + if isInternal { + subnetName := subnet(service) + if subnetName == nil { + subnetName = &az.SubnetName + } + subnet, existsSubnet, err := az.getSubnet(az.VnetName, *subnetName) + if err != nil { + return nil, err + } + + if !existsSubnet { + return nil, fmt.Errorf("ensure(%s): lb(%s) - failed to get subnet: %s/%s", serviceName, lbName, az.VnetName, az.SubnetName) + } + + configProperties := network.FrontendIPConfigurationPropertiesFormat{ + Subnet: &subnet, + } + + loadBalancerIP := service.Spec.LoadBalancerIP + if loadBalancerIP != "" { + configProperties.PrivateIPAllocationMethod = network.Static + configProperties.PrivateIPAddress = &loadBalancerIP + } else { + // We'll need to call GetLoadBalancer later to retrieve allocated IP. + configProperties.PrivateIPAllocationMethod = network.Dynamic + } + + fipConfigurationProperties = &configProperties + } else { + pipName, err := az.determinePublicIPName(clusterName, service) + if err != nil { + return nil, err + } + domainNameLabel := getPublicIPDomainNameLabel(service) + pip, err := az.ensurePublicIPExists(service, pipName, domainNameLabel) + if err != nil { + return nil, err + } + fipConfigurationProperties = &network.FrontendIPConfigurationPropertiesFormat{ + PublicIPAddress: &network.PublicIPAddress{ID: pip.ID}, + } + } + newConfigs = append(newConfigs, network.FrontendIPConfiguration{ Name: to.StringPtr(lbFrontendIPConfigName), @@ -630,7 +593,7 @@ func (az *Cloud) reconcileLoadBalancer(lb network.LoadBalancer, fipConfiguration transportProto, _, probeProto, err := getProtocolsFromKubernetesProtocol(port.Protocol) if err != nil { - return lb, false, err + return nil, err } if serviceapi.NeedsHealthCheck(service) { @@ -638,7 +601,7 @@ func (az *Cloud) reconcileLoadBalancer(lb network.LoadBalancer, fipConfiguration // ERROR: this isn't supported // health check (aka source ip preservation) is not // compatible with UDP (it uses an HTTP check) - return lb, false, fmt.Errorf("services requiring health checks are incompatible with UDP ports") + return nil, fmt.Errorf("services requiring health checks are incompatible with UDP ports") } podPresencePath, podPresencePort := serviceapi.GetServiceHealthCheckPathPort(service) @@ -779,23 +742,94 @@ func (az *Cloud) reconcileLoadBalancer(lb network.LoadBalancer, fipConfiguration lb.LoadBalancingRules = &updatedRules } - return lb, dirtyLb, nil + // We don't care if the LB exists or not + // We only care about if there is any change in the LB, which means dirtyLB + // If it is not exist, and no change to that, we don't CreateOrUpdate LB + if dirtyLb { + if lb.FrontendIPConfigurations == nil || len(*lb.FrontendIPConfigurations) == 0 { + // When FrontendIPConfigurations is empty, we need to delete the Azure load balancer resource itself, + // because an Azure load balancer cannot have an empty FrontendIPConfigurations collection + glog.V(3).Infof("delete(%s): lb(%s) - deleting; no remaining frontendipconfigs", serviceName, lbName) + + // Remove backend pools from vmSets. This is required for virtual machine scale sets before removing the LB. + vmSetName := az.mapLoadBalancerNameToVMSet(lbName, clusterName) + glog.V(10).Infof("EnsureBackendPoolDeleted(%s, %s): start", lbBackendPoolID, vmSetName) + err := az.vmSet.EnsureBackendPoolDeleted(lbBackendPoolID, vmSetName) + if err != nil { + glog.Errorf("EnsureBackendPoolDeleted(%s, %s) failed: %v", lbBackendPoolID, vmSetName, err) + return nil, err + } + glog.V(10).Infof("EnsureBackendPoolDeleted(%s, %s): end", lbBackendPoolID, vmSetName) + + // Remove the LB. + az.operationPollRateLimiter.Accept() + glog.V(10).Infof("LoadBalancerClient.Delete(%q): start", lbName) + err = az.DeleteLBWithRetry(lbName) + if err != nil { + glog.V(2).Infof("delete(%s) abort backoff: lb(%s) - deleting; no remaining frontendipconfigs", serviceName, lbName) + return nil, err + } + glog.V(10).Infof("LoadBalancerClient.Delete(%q): end", lbName) + } else { + glog.V(3).Infof("ensure(%s): lb(%s) - updating", serviceName, lbName) + err := az.CreateOrUpdateLBWithRetry(*lb) + if err != nil { + glog.V(2).Infof("ensure(%s) abort backoff: lb(%s) - updating", serviceName, lbName) + return nil, err + } + } + } + + if wantLb && nodes != nil { + // Add the machines to the backend pool if they're not already + vmSetName := az.mapLoadBalancerNameToVMSet(lbName, clusterName) + err := az.vmSet.EnsureHostsInPool(serviceName, nodes, lbBackendPoolID, vmSetName) + if err != nil { + return nil, err + } + } + + glog.V(2).Infof("ensure(%s): lb(%s) finished", serviceName, lbName) + return lb, nil } // This reconciles the Network Security Group similar to how the LB is reconciled. // This entails adding required, missing SecurityRules and removing stale rules. -func (az *Cloud) reconcileSecurityGroup(sg network.SecurityGroup, clusterName string, service *v1.Service, wantLb bool) (network.SecurityGroup, bool, error) { +func (az *Cloud) reconcileSecurityGroup(clusterName string, service *v1.Service, lbIP *string, wantLb bool) (*network.SecurityGroup, error) { serviceName := getServiceName(service) - var ports []v1.ServicePort - if wantLb { - ports = service.Spec.Ports - } else { + glog.V(5).Infof("reconcileSecurityGroup(%s): START clusterName=%q lbName=%q", serviceName, clusterName) + + ports := service.Spec.Ports + if ports == nil { + if useSharedSecurityRule(service) { + glog.V(2).Infof("Attempting to reconcile security group for service %s, but service uses shared rule and we don't know which port it's for", service.Name) + return nil, fmt.Errorf("No port info for reconciling shared rule for service %s", service.Name) + } ports = []v1.ServicePort{} } + az.operationPollRateLimiter.Accept() + glog.V(10).Infof("SecurityGroupsClient.Get(%q): start", az.SecurityGroupName) + sg, err := az.SecurityGroupsClient.Get(az.ResourceGroup, az.SecurityGroupName, "") + glog.V(10).Infof("SecurityGroupsClient.Get(%q): end", az.SecurityGroupName) + if err != nil { + return nil, err + } + + destinationIPAddress := "" + if wantLb && lbIP == nil { + return nil, fmt.Errorf("No load balancer IP for setting up security rules for service %s", service.Name) + } + if lbIP != nil { + destinationIPAddress = *lbIP + } + if destinationIPAddress == "" { + destinationIPAddress = "*" + } + sourceRanges, err := serviceapi.GetLoadBalancerSourceRanges(service) if err != nil { - return sg, false, err + return nil, err } var sourceAddressPrefixes []string if sourceRanges == nil || serviceapi.IsAllowAll(sourceRanges) { @@ -807,38 +841,52 @@ func (az *Cloud) reconcileSecurityGroup(sg network.SecurityGroup, clusterName st sourceAddressPrefixes = append(sourceAddressPrefixes, ip.String()) } } - expectedSecurityRules := make([]network.SecurityRule, len(ports)*len(sourceAddressPrefixes)) + expectedSecurityRules := []network.SecurityRule{} - for i, port := range ports { - _, securityProto, _, err := getProtocolsFromKubernetesProtocol(port.Protocol) - if err != nil { - return sg, false, err - } - for j := range sourceAddressPrefixes { - ix := i*len(sourceAddressPrefixes) + j - securityRuleName := getSecurityRuleName(service, port, sourceAddressPrefixes[j]) - expectedSecurityRules[ix] = network.SecurityRule{ - Name: to.StringPtr(securityRuleName), - SecurityRulePropertiesFormat: &network.SecurityRulePropertiesFormat{ - Protocol: *securityProto, - SourcePortRange: to.StringPtr("*"), - DestinationPortRange: to.StringPtr(strconv.Itoa(int(port.Port))), - SourceAddressPrefix: to.StringPtr(sourceAddressPrefixes[j]), - DestinationAddressPrefix: to.StringPtr("*"), - Access: network.SecurityRuleAccessAllow, - Direction: network.SecurityRuleDirectionInbound, - }, + if wantLb { + expectedSecurityRules = make([]network.SecurityRule, len(ports)*len(sourceAddressPrefixes)) + + for i, port := range ports { + _, securityProto, _, err := getProtocolsFromKubernetesProtocol(port.Protocol) + if err != nil { + return nil, err + } + for j := range sourceAddressPrefixes { + ix := i*len(sourceAddressPrefixes) + j + securityRuleName := getSecurityRuleName(service, port, sourceAddressPrefixes[j]) + expectedSecurityRules[ix] = network.SecurityRule{ + Name: to.StringPtr(securityRuleName), + SecurityRulePropertiesFormat: &network.SecurityRulePropertiesFormat{ + Protocol: *securityProto, + SourcePortRange: to.StringPtr("*"), + DestinationPortRange: to.StringPtr(strconv.Itoa(int(port.Port))), + SourceAddressPrefix: to.StringPtr(sourceAddressPrefixes[j]), + DestinationAddressPrefix: to.StringPtr(destinationIPAddress), + Access: network.SecurityRuleAccessAllow, + Direction: network.SecurityRuleDirectionInbound, + }, + } } } } + for _, r := range expectedSecurityRules { + glog.V(10).Infof("Expecting security rule for %s: %s:%s -> %s:%s", service.Name, *r.SourceAddressPrefix, *r.SourcePortRange, *r.DestinationAddressPrefix, *r.DestinationPortRange) + } + // update security rules dirtySg := false var updatedRules []network.SecurityRule if sg.SecurityRules != nil { updatedRules = *sg.SecurityRules } - // update security rules: remove unwanted + + for _, r := range updatedRules { + glog.V(10).Infof("Existing security rule while processing %s: %s:%s -> %s:%s", service.Name, logSafe(r.SourceAddressPrefix), logSafe(r.SourcePortRange), logSafeCollection(r.DestinationAddressPrefix, r.DestinationAddressPrefixes), logSafe(r.DestinationPortRange)) + } + + // update security rules: remove unwanted rules that belong privately + // to this service for i := len(updatedRules) - 1; i >= 0; i-- { existingRule := updatedRules[i] if serviceOwnsRule(service, *existingRule.Name) { @@ -855,6 +903,50 @@ func (az *Cloud) reconcileSecurityGroup(sg network.SecurityGroup, clusterName st } } } + // update security rules: if the service uses a shared rule and is being deleted, + // then remove it from the shared rule + if useSharedSecurityRule(service) && !wantLb { + for _, port := range ports { + for _, sourceAddressPrefix := range sourceAddressPrefixes { + sharedRuleName := getSecurityRuleName(service, port, sourceAddressPrefix) + sharedIndex, sharedRule, sharedRuleFound := findSecurityRuleByName(updatedRules, sharedRuleName) + if !sharedRuleFound { + glog.V(4).Infof("Expected to find shared rule %s for service %s being deleted, but did not", sharedRuleName, service.Name) + return nil, fmt.Errorf("Expected to find shared rule %s for service %s being deleted, but did not", sharedRuleName, service.Name) + } + if sharedRule.DestinationAddressPrefixes == nil { + glog.V(4).Infof("Expected to have array of destinations in shared rule for service %s being deleted, but did not", service.Name) + return nil, fmt.Errorf("Expected to have array of destinations in shared rule for service %s being deleted, but did not", service.Name) + } + existingPrefixes := *sharedRule.DestinationAddressPrefixes + addressIndex, found := findIndex(existingPrefixes, destinationIPAddress) + if !found { + glog.V(4).Infof("Expected to find destination address %s in shared rule %s for service %s being deleted, but did not", destinationIPAddress, sharedRuleName, service.Name) + return nil, fmt.Errorf("Expected to find destination address %s in shared rule %s for service %s being deleted, but did not", destinationIPAddress, sharedRuleName, service.Name) + } + if len(existingPrefixes) == 1 { + updatedRules = append(updatedRules[:sharedIndex], updatedRules[sharedIndex+1:]...) + } else { + newDestinations := append(existingPrefixes[:addressIndex], existingPrefixes[addressIndex+1:]...) + sharedRule.DestinationAddressPrefixes = &newDestinations + updatedRules[sharedIndex] = sharedRule + } + dirtySg = true + } + } + } + + // update security rules: prepare rules for consolidation + for index, rule := range updatedRules { + if allowsConsolidation(rule) { + updatedRules[index] = makeConsolidatable(rule) + } + } + for index, rule := range expectedSecurityRules { + if allowsConsolidation(rule) { + expectedSecurityRules[index] = makeConsolidatable(rule) + } + } // update security rules: add needed for _, expectedRule := range expectedSecurityRules { foundRule := false @@ -862,12 +954,17 @@ func (az *Cloud) reconcileSecurityGroup(sg network.SecurityGroup, clusterName st glog.V(10).Infof("reconcile(%s)(%t): sg rule(%s) - already exists", serviceName, wantLb, *expectedRule.Name) foundRule = true } + if foundRule && allowsConsolidation(expectedRule) { + index, _ := findConsolidationCandidate(updatedRules, expectedRule) + updatedRules[index] = consolidate(updatedRules[index], expectedRule) + dirtySg = true + } if !foundRule { glog.V(10).Infof("reconcile(%s)(%t): sg rule(%s) - adding", serviceName, wantLb, *expectedRule.Name) nextAvailablePriority, err := getNextAvailablePriority(updatedRules) if err != nil { - return sg, false, err + return nil, err } expectedRule.Priority = to.Int32Ptr(nextAvailablePriority) @@ -875,15 +972,238 @@ func (az *Cloud) reconcileSecurityGroup(sg network.SecurityGroup, clusterName st dirtySg = true } } + + for _, r := range updatedRules { + glog.V(10).Infof("Updated security rule while processing %s: %s:%s -> %s:%s", service.Name, logSafe(r.SourceAddressPrefix), logSafe(r.SourcePortRange), logSafeCollection(r.DestinationAddressPrefix, r.DestinationAddressPrefixes), logSafe(r.DestinationPortRange)) + } + if dirtySg { sg.SecurityRules = &updatedRules + glog.V(3).Infof("ensure(%s): sg(%s) - updating", serviceName, *sg.Name) + az.operationPollRateLimiter.Accept() + glog.V(10).Infof("CreateOrUpdateSGWithRetry(%q): start", *sg.Name) + err := az.CreateOrUpdateSGWithRetry(sg) + if err != nil { + glog.V(2).Infof("ensure(%s) abort backoff: sg(%s) - updating", serviceName, *sg.Name) + // TODO (Nov 2017): remove when augmented security rules are out of preview + // we could try to parse the response but it's not worth it for bridging a preview + errorDescription := err.Error() + if strings.Contains(errorDescription, "SubscriptionNotRegisteredForFeature") && strings.Contains(errorDescription, "Microsoft.Network/AllowAccessRuleExtendedProperties") { + sharedRuleError := fmt.Errorf("Shared security rules are not available in this Azure region. Details: %v", errorDescription) + return nil, sharedRuleError + } + // END TODO + return nil, err + } + glog.V(10).Infof("CreateOrUpdateSGWithRetry(%q): end", *sg.Name) } - return sg, dirtySg, nil + return &sg, nil +} + +func logSafe(s *string) string { + if s == nil { + return "(nil)" + } + return *s +} + +func logSafeCollection(s *string, strs *[]string) string { + if s == nil { + if strs == nil { + return "(nil)" + } + return "[" + strings.Join(*strs, ",") + "]" + } + return *s +} + +func findSecurityRuleByName(rules []network.SecurityRule, ruleName string) (int, network.SecurityRule, bool) { + for index, rule := range rules { + if rule.Name != nil && strings.EqualFold(*rule.Name, ruleName) { + return index, rule, true + } + } + return 0, network.SecurityRule{}, false +} + +func findIndex(strs []string, s string) (int, bool) { + for index, str := range strs { + if strings.EqualFold(str, s) { + return index, true + } + } + return 0, false +} + +func allowsConsolidation(rule network.SecurityRule) bool { + return strings.HasPrefix(*rule.Name, "shared") +} + +func findConsolidationCandidate(rules []network.SecurityRule, rule network.SecurityRule) (int, bool) { + for index, r := range rules { + if allowsConsolidation(r) { + if strings.EqualFold(*r.Name, *rule.Name) { + return index, true + } + } + } + + return 0, false +} + +func makeConsolidatable(rule network.SecurityRule) network.SecurityRule { + return network.SecurityRule{ + Name: rule.Name, + SecurityRulePropertiesFormat: &network.SecurityRulePropertiesFormat{ + Priority: rule.Priority, + Protocol: rule.Protocol, + SourcePortRange: rule.SourcePortRange, + SourcePortRanges: rule.SourcePortRanges, + DestinationPortRange: rule.DestinationPortRange, + DestinationPortRanges: rule.DestinationPortRanges, + SourceAddressPrefix: rule.SourceAddressPrefix, + SourceAddressPrefixes: rule.SourceAddressPrefixes, + DestinationAddressPrefixes: collectionOrSingle(rule.DestinationAddressPrefixes, rule.DestinationAddressPrefix), + Access: rule.Access, + Direction: rule.Direction, + }, + } +} + +func consolidate(existingRule network.SecurityRule, newRule network.SecurityRule) network.SecurityRule { + destinations := appendElements(existingRule.SecurityRulePropertiesFormat.DestinationAddressPrefixes, newRule.DestinationAddressPrefix, newRule.DestinationAddressPrefixes) + destinations = deduplicate(destinations) // there are transient conditions during controller startup where it tries to add a service that is already added + + return network.SecurityRule{ + Name: existingRule.Name, + SecurityRulePropertiesFormat: &network.SecurityRulePropertiesFormat{ + Priority: existingRule.Priority, + Protocol: existingRule.Protocol, + SourcePortRange: existingRule.SourcePortRange, + SourcePortRanges: existingRule.SourcePortRanges, + DestinationPortRange: existingRule.DestinationPortRange, + DestinationPortRanges: existingRule.DestinationPortRanges, + SourceAddressPrefix: existingRule.SourceAddressPrefix, + SourceAddressPrefixes: existingRule.SourceAddressPrefixes, + DestinationAddressPrefixes: destinations, + Access: existingRule.Access, + Direction: existingRule.Direction, + }, + } +} + +func collectionOrSingle(collection *[]string, s *string) *[]string { + if collection != nil && len(*collection) > 0 { + return collection + } + if s == nil { + return &[]string{} + } + return &[]string{*s} +} + +func appendElements(collection *[]string, appendString *string, appendStrings *[]string) *[]string { + newCollection := []string{} + + if collection != nil { + newCollection = append(newCollection, *collection...) + } + if appendString != nil { + newCollection = append(newCollection, *appendString) + } + if appendStrings != nil { + newCollection = append(newCollection, *appendStrings...) + } + + return &newCollection +} + +func deduplicate(collection *[]string) *[]string { + if collection == nil { + return nil + } + + seen := map[string]bool{} + result := make([]string, 0, len(*collection)) + + for _, v := range *collection { + if seen[v] == true { + // skip this element + } else { + seen[v] = true + result = append(result, v) + } + } + + return &result +} + +// This reconciles the PublicIP resources similar to how the LB is reconciled. +func (az *Cloud) reconcilePublicIP(clusterName string, service *v1.Service, wantLb bool) (*network.PublicIPAddress, error) { + isInternal := requiresInternalLoadBalancer(service) + serviceName := getServiceName(service) + var desiredPipName string + var err error + if !isInternal && wantLb { + desiredPipName, err = az.determinePublicIPName(clusterName, service) + if err != nil { + return nil, err + } + } + + pipResourceGroup := az.getPublicIPAddressResourceGroup(service) + + pips, err := az.ListPIPWithRetry(pipResourceGroup) + if err != nil { + return nil, err + } + + for _, pip := range pips { + if pip.Tags != nil && + (*pip.Tags)["service"] != nil && + *(*pip.Tags)["service"] == serviceName { + // We need to process for pips belong to this service + pipName := *pip.Name + if wantLb && !isInternal && pipName == desiredPipName { + // This is the only case we should preserve the + // Public ip resource with match service tag + } else { + glog.V(2).Infof("ensure(%s): pip(%s) - deleting", serviceName, pipName) + az.operationPollRateLimiter.Accept() + glog.V(10).Infof("DeletePublicIPWithRetry(%s, %q): start", pipResourceGroup, pipName) + err = az.DeletePublicIPWithRetry(pipResourceGroup, pipName) + if err != nil { + glog.V(2).Infof("ensure(%s) abort backoff: pip(%s) - deleting", serviceName, pipName) + // We let err to pass through + // It may be ignorable + } + glog.V(10).Infof("DeletePublicIPWithRetry(%s, %q): end", pipResourceGroup, pipName) // response not read yet... + + err = ignoreStatusNotFoundFromError(err) + if err != nil { + return nil, err + } + glog.V(2).Infof("ensure(%s): pip(%s) - finished", serviceName, pipName) + } + } + + } + + if !isInternal && wantLb { + // Confirm desired public ip resource exists + var pip *network.PublicIPAddress + domainNameLabel := getPublicIPDomainNameLabel(service) + if pip, err = az.ensurePublicIPExists(service, desiredPipName, domainNameLabel); err != nil { + return nil, err + } + return pip, nil + } + return nil, nil } func findProbe(probes []network.Probe, probe network.Probe) bool { for _, existingProbe := range probes { - if strings.EqualFold(*existingProbe.Name, *probe.Name) { + if strings.EqualFold(*existingProbe.Name, *probe.Name) && *existingProbe.Port == *probe.Port { return true } } @@ -899,115 +1219,54 @@ func findRule(rules []network.LoadBalancingRule, rule network.LoadBalancingRule) return false } +// This compares rule's Name, Protocol, SourcePortRange, DestinationPortRange, SourceAddressPrefix, Access, and Direction. +// Note that it compares rule's DestinationAddressPrefix only when it's not consolidated rule as such rule does not have DestinationAddressPrefix defined. +// We intentionally do not compare DestinationAddressPrefixes in consolidated case because reconcileSecurityRule has to consider the two rules equal, +// despite different DestinationAddressPrefixes, in order to give it a chance to consolidate the two rules. func findSecurityRule(rules []network.SecurityRule, rule network.SecurityRule) bool { for _, existingRule := range rules { - if strings.EqualFold(*existingRule.Name, *rule.Name) { - return true + if !strings.EqualFold(*existingRule.Name, *rule.Name) { + continue } + if existingRule.Protocol != rule.Protocol { + continue + } + if !strings.EqualFold(*existingRule.SourcePortRange, *rule.SourcePortRange) { + continue + } + if !strings.EqualFold(*existingRule.DestinationPortRange, *rule.DestinationPortRange) { + continue + } + if !strings.EqualFold(*existingRule.SourceAddressPrefix, *rule.SourceAddressPrefix) { + continue + } + if !allowsConsolidation(existingRule) && !allowsConsolidation(rule) { + if !strings.EqualFold(*existingRule.DestinationAddressPrefix, *rule.DestinationAddressPrefix) { + continue + } + } + if existingRule.Access != rule.Access { + continue + } + if existingRule.Direction != rule.Direction { + continue + } + return true } return false } -// This ensures the given VM's Primary NIC's Primary IP Configuration is -// participating in the specified LoadBalancer Backend Pool. -func (az *Cloud) ensureHostInPool(serviceName string, nodeName types.NodeName, backendPoolID string) error { - var machine compute.VirtualMachine - vmName := mapNodeNameToVMName(nodeName) - az.operationPollRateLimiter.Accept() - glog.V(10).Infof("VirtualMachinesClient.Get(%q): start", vmName) - machine, err := az.VirtualMachinesClient.Get(az.ResourceGroup, vmName, "") - glog.V(10).Infof("VirtualMachinesClient.Get(%q): end", vmName) - if err != nil { - if az.CloudProviderBackoff { - glog.V(2).Infof("ensureHostInPool(%s, %s, %s) backing off", serviceName, nodeName, backendPoolID) - machine, err = az.VirtualMachineClientGetWithRetry(az.ResourceGroup, vmName, "") - if err != nil { - glog.V(2).Infof("ensureHostInPool(%s, %s, %s) abort backoff", serviceName, nodeName, backendPoolID) - return err - } - } else { - return err - } +func (az *Cloud) getPublicIPAddressResourceGroup(service *v1.Service) string { + if resourceGroup, found := service.Annotations[ServiceAnnotationLoadBalancerResourceGroup]; found { + return resourceGroup } - primaryNicID, err := getPrimaryInterfaceID(machine) - if err != nil { - return err - } - nicName, err := getLastSegment(primaryNicID) - if err != nil { - return err - } - - // Check availability set - if az.PrimaryAvailabilitySetName != "" { - expectedAvailabilitySetName := az.getAvailabilitySetID(az.PrimaryAvailabilitySetName) - if machine.AvailabilitySet == nil || !strings.EqualFold(*machine.AvailabilitySet.ID, expectedAvailabilitySetName) { - glog.V(3).Infof( - "nicupdate(%s): skipping nic (%s) since it is not in the primaryAvailabilitSet(%s)", - serviceName, nicName, az.PrimaryAvailabilitySetName) - return nil - } - } - - az.operationPollRateLimiter.Accept() - glog.V(10).Infof("InterfacesClient.Get(%q): start", nicName) - nic, err := az.InterfacesClient.Get(az.ResourceGroup, nicName, "") - glog.V(10).Infof("InterfacesClient.Get(%q): end", nicName) - if err != nil { - return err - } - - var primaryIPConfig *network.InterfaceIPConfiguration - primaryIPConfig, err = getPrimaryIPConfig(nic) - if err != nil { - return err - } - - foundPool := false - newBackendPools := []network.BackendAddressPool{} - if primaryIPConfig.LoadBalancerBackendAddressPools != nil { - newBackendPools = *primaryIPConfig.LoadBalancerBackendAddressPools - } - for _, existingPool := range newBackendPools { - if strings.EqualFold(backendPoolID, *existingPool.ID) { - foundPool = true - break - } - } - if !foundPool { - newBackendPools = append(newBackendPools, - network.BackendAddressPool{ - ID: to.StringPtr(backendPoolID), - }) - - primaryIPConfig.LoadBalancerBackendAddressPools = &newBackendPools - - glog.V(3).Infof("nicupdate(%s): nic(%s) - updating", serviceName, nicName) - az.operationPollRateLimiter.Accept() - glog.V(10).Infof("InterfacesClient.CreateOrUpdate(%q): start", *nic.Name) - respChan, errChan := az.InterfacesClient.CreateOrUpdate(az.ResourceGroup, *nic.Name, nic, nil) - resp := <-respChan - err := <-errChan - glog.V(10).Infof("InterfacesClient.CreateOrUpdate(%q): end", *nic.Name) - if az.CloudProviderBackoff && shouldRetryAPIRequest(resp.Response, err) { - glog.V(2).Infof("nicupdate(%s) backing off: nic(%s) - updating, err=%v", serviceName, nicName, err) - retryErr := az.CreateOrUpdateInterfaceWithRetry(nic) - if retryErr != nil { - err = retryErr - glog.V(2).Infof("nicupdate(%s) abort backoff: nic(%s) - updating", serviceName, nicName) - } - } - if err != nil { - return err - } - } - return nil + return az.ResourceGroup } // Check if service requires an internal load balancer. func requiresInternalLoadBalancer(service *v1.Service) bool { - if l, ok := service.Annotations[ServiceAnnotationLoadBalancerInternal]; ok { + if l, found := service.Annotations[ServiceAnnotationLoadBalancerInternal]; found { return l == "true" } @@ -1016,10 +1275,42 @@ func requiresInternalLoadBalancer(service *v1.Service) bool { func subnet(service *v1.Service) *string { if requiresInternalLoadBalancer(service) { - if l, ok := service.Annotations[ServiceAnnotationLoadBalancerInternalSubnet]; ok { + if l, found := service.Annotations[ServiceAnnotationLoadBalancerInternalSubnet]; found { return &l } } return nil } + +// getServiceLoadBalancerMode parses the mode value. +// if the value is __auto__ it returns isAuto = TRUE. +// if anything else it returns the unique VM set names after triming spaces. +func getServiceLoadBalancerMode(service *v1.Service) (hasMode bool, isAuto bool, vmSetNames []string) { + mode, hasMode := service.Annotations[ServiceAnnotationLoadBalancerMode] + mode = strings.TrimSpace(mode) + isAuto = strings.EqualFold(mode, ServiceAnnotationLoadBalancerAutoModeValue) + if !isAuto { + // Break up list of "AS1,AS2" + vmSetParsedList := strings.Split(mode, ",") + + // Trim the VM set names and remove duplicates + // e.g. {"AS1"," AS2", "AS3", "AS3"} => {"AS1", "AS2", "AS3"} + vmSetNameSet := sets.NewString() + for _, v := range vmSetParsedList { + vmSetNameSet.Insert(strings.TrimSpace(v)) + } + + vmSetNames = vmSetNameSet.List() + } + + return hasMode, isAuto, vmSetNames +} + +func useSharedSecurityRule(service *v1.Service) bool { + if l, ok := service.Annotations[ServiceAnnotationSharedSecurityRule]; ok { + return l == "true" + } + + return false +} diff --git a/pkg/cloudprovider/providers/azure/azure_loadbalancer.md b/pkg/cloudprovider/providers/azure/azure_loadbalancer.md new file mode 100644 index 00000000000..141d066cf15 --- /dev/null +++ b/pkg/cloudprovider/providers/azure/azure_loadbalancer.md @@ -0,0 +1,77 @@ +# Azure LoadBalancer + +The way azure define LoadBalancer is different with GCE or AWS. Azure's LB can have multiple frontend IP refs. The GCE and AWS can only allow one, if you want more, you better to have another LB. Because of the fact, Public IP is not part of the LB in Azure. NSG is not part of LB in Azure either. However, you cannot delete them in parallel, Public IP can only be delete after LB's frontend IP ref is removed. + +For different Azure Resources, such as LB, Public IP, NSG. They are the same tier azure resources. We need to make sure there is no connection in their own ensure loops. In another words, They would be eventually reconciled regardless of other resources' state. They should only depends on service state. + +Despite the ideal philosophy above, we have to face the reality. NSG depends on LB's frontend ip to adjust NSG rules. So when we want to reconcile NSG, the LB should contain the corresponding frontend ip config. + +And also, For Azure, we cannot afford to have more than 1 worker of service_controller. Because, different services could operate on the same LB, concurrent execution could result in conflict or unexpected result. For AWS and GCE, they apparently doesn't have the problem, they use one LB per service, no such conflict. + +There are two load balancers per availability set internal and external. There is a limit on number of services that can be associated with a single load balancer. +By default primary load balancer is selected. Services can be annotated to allow auto selection of available load balancers. Service annotations can also be used to provide specific availability sets that host the load balancers. Note that in case of auto selection or specific availability set selection, when the availability set is lost incase of downtime or cluster scale down the services are currently not auto assigned to an available load balancer. +Service Annotation for Auto and specific load balancer mode + +- service.beta.kubernetes.io/azure-load-balancer-mode" (__auto__|as1,as2...) + +## Introduce Functions + +- reconcileLoadBalancer(clusterName string, service *v1.Service, nodes []*v1.Node, wantLb bool) (*network.LoadBalancer, error) + - Go through lb's properties, update based on wantLb + - If any change on the lb, no matter if the lb exists or not + - Call az cloud to CreateOrUpdate on this lb, or Delete if nothing left + - return lb, err + +- reconcileSecurityGroup(clusterName string, service *v1.Service, lbIP *string, wantLb bool) (*network.SecurityGroup, error) + - Go though NSG' properties, update based on wantLb + - Use destinationIPAddress as target address if possible + - Consolidate NSG rules if possible + - If any change on the NSG, (the NSG should always exists) + - Call az cloud to CreateOrUpdate on this NSG + - return sg, err + +- reconcilePublicIP(clusterName string, service *v1.Service, wantLb bool) (*network.PublicIPAddress, error) + - List all the public ip in the resource group + - Make sure we only touch Public IP resources has tags[service] = "namespace/serviceName" + - skip for wantLb && !isInternal && pipName == desiredPipName + - delete other public ip resources if any + - if !isInternal && wantLb + - ensure Public IP with desiredPipName exists + +- getServiceLoadBalancer(service *v1.Service, clusterName string, nodes []*v1.Node, wantLb bool) (lb, status, exists, error) + - gets the loadbalancer for the service if it already exists + - If wantLb is TRUE then -it selects a new load balancer, the selction helps distribute the services across load balancers + - In case the selected load balancer does not exists it returns network.LoadBalancer struct with added metadata (such as name, location) and existsLB set to FALSE + - By default - cluster default LB is returned + +## Define interface behaviors + +### GetLoadBalancer + +- Get LoadBalancer status, return status, error + - return the load balancer status for this service + - it will not create or update or delete any resource + +### EnsureLoadBalancer + +- Reconcile LB for the flipped service + - Call reconcileLoadBalancer(clusterName, flipedService, nil, false/* wantLb */) +- Reconcile Public IP + - Call reconcilePublicIP(cluster, service, true) +- Reconcile LB's related and owned resources, such as FrontEndIPConfig, Rules, Probe. + - Call reconcileLoadBalancer(clusterName, service, nodes, true /* wantLb */) +- Reconcile NSG rules, it need to be called after reconcileLB + - Call reconcileSecurityGroup(clusterName, service, lbStatus, true /* wantLb */) + +### UpdateLoadBalancer + +- Has no difference with EnsureLoadBalancer + +### EnsureLoadBalancerDeleted + +- Reconcile NSG first, before reconcile LB, because SG need LB to be there + - Call reconcileSecurityGroup(clusterName, service, nil, false /* wantLb */) +- Reconcile LB's related and owned resources, such as FrontEndIPConfig, Rules, Probe. + - Call reconcileLoadBalancer(clusterName, service, nodes, false) +- Reconcile Public IP, public IP needs related LB reconciled first + - Call reconcilePublicIP(cluster, service, false) \ No newline at end of file diff --git a/pkg/cloudprovider/providers/azure/azure_loadbalancer_test.go b/pkg/cloudprovider/providers/azure/azure_loadbalancer_test.go new file mode 100644 index 00000000000..e09ec585fad --- /dev/null +++ b/pkg/cloudprovider/providers/azure/azure_loadbalancer_test.go @@ -0,0 +1,99 @@ +/* +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 azure + +import ( + "fmt" + "testing" + + "github.com/Azure/azure-sdk-for-go/arm/network" + "github.com/Azure/go-autorest/autorest/to" + "github.com/stretchr/testify/assert" +) + +func TestFindProbe(t *testing.T) { + tests := []struct { + msg string + existingProbe []network.Probe + curProbe network.Probe + expected bool + }{ + { + msg: "empty existing probes should return false", + expected: false, + }, + { + msg: "probe names match while ports unmatch should return false", + existingProbe: []network.Probe{ + { + Name: to.StringPtr("httpProbe"), + ProbePropertiesFormat: &network.ProbePropertiesFormat{ + Port: to.Int32Ptr(1), + }, + }, + }, + curProbe: network.Probe{ + Name: to.StringPtr("httpProbe"), + ProbePropertiesFormat: &network.ProbePropertiesFormat{ + Port: to.Int32Ptr(2), + }, + }, + expected: false, + }, + { + msg: "probe ports match while names unmatch should return false", + existingProbe: []network.Probe{ + { + Name: to.StringPtr("probe1"), + ProbePropertiesFormat: &network.ProbePropertiesFormat{ + Port: to.Int32Ptr(1), + }, + }, + }, + curProbe: network.Probe{ + Name: to.StringPtr("probe2"), + ProbePropertiesFormat: &network.ProbePropertiesFormat{ + Port: to.Int32Ptr(1), + }, + }, + expected: false, + }, + { + msg: "both probe ports and names match should return true", + existingProbe: []network.Probe{ + { + Name: to.StringPtr("matchName"), + ProbePropertiesFormat: &network.ProbePropertiesFormat{ + Port: to.Int32Ptr(1), + }, + }, + }, + curProbe: network.Probe{ + Name: to.StringPtr("matchName"), + ProbePropertiesFormat: &network.ProbePropertiesFormat{ + Port: to.Int32Ptr(1), + }, + }, + expected: true, + }, + } + + for i, test := range tests { + findResult := findProbe(test.existingProbe, test.curProbe) + assert.Equal(t, test.expected, findResult, fmt.Sprintf("TestCase[%d]: %s", i, test.msg)) + } +} diff --git a/pkg/cloudprovider/providers/azure/azure_managedDiskController.go b/pkg/cloudprovider/providers/azure/azure_managedDiskController.go index 5acdf583583..71b341d9d3f 100644 --- a/pkg/cloudprovider/providers/azure/azure_managedDiskController.go +++ b/pkg/cloudprovider/providers/azure/azure_managedDiskController.go @@ -106,8 +106,8 @@ func (c *ManagedDiskController) DeleteManagedDisk(diskURI string) error { if err != nil { return err } - // We don't need poll here, k8s will immediatly stop referencing the disk - // the disk will be evantually deleted - cleanly - by ARM + // We don't need poll here, k8s will immediately stop referencing the disk + // the disk will be eventually deleted - cleanly - by ARM glog.V(2).Infof("azureDisk - deleted a managed disk: %s", diskURI) diff --git a/pkg/cloudprovider/providers/azure/azure_storage.go b/pkg/cloudprovider/providers/azure/azure_storage.go index 81388bc428a..641fb8fd6ad 100644 --- a/pkg/cloudprovider/providers/azure/azure_storage.go +++ b/pkg/cloudprovider/providers/azure/azure_storage.go @@ -42,20 +42,24 @@ func (az *Cloud) CreateFileShare(name, storageAccount, storageType, location str // find the access key with this account key, err := az.getStorageAccesskey(account.Name) if err != nil { - glog.V(2).Infof("no key found for storage account %s", account.Name) + err = fmt.Errorf("could not get storage key for storage account %s: %v", account.Name, err) continue } err = az.createFileShare(account.Name, key, name, requestGB) if err != nil { - glog.V(2).Infof("failed to create share %s in account %s: %v", name, account.Name, err) + err = fmt.Errorf("failed to create share %s in account %s: %v", name, account.Name, err) continue } glog.V(4).Infof("created share %s in account %s", name, account.Name) return account.Name, key, err } } - return "", "", fmt.Errorf("failed to find a matching storage account") + + if err == nil { + err = fmt.Errorf("failed to find a matching storage account") + } + return "", "", err } // DeleteFileShare deletes a file share using storage account name and key diff --git a/pkg/cloudprovider/providers/azure/azure_test.go b/pkg/cloudprovider/providers/azure/azure_test.go index e579a159fb6..47181bb0d3f 100644 --- a/pkg/cloudprovider/providers/azure/azure_test.go +++ b/pkg/cloudprovider/providers/azure/azure_test.go @@ -17,31 +17,55 @@ limitations under the License. package azure import ( - "encoding/json" "fmt" - "net/http" - "net/http/httptest" - "reflect" + "math" "strings" "testing" "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/flowcontrol" serviceapi "k8s.io/kubernetes/pkg/api/v1/service" + "k8s.io/kubernetes/pkg/cloudprovider/providers/azure/auth" + kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis" + "github.com/Azure/azure-sdk-for-go/arm/compute" "github.com/Azure/azure-sdk-for-go/arm/network" "github.com/Azure/go-autorest/autorest/to" ) var testClusterName = "testCluster" +// Test flipServiceInternalAnnotation +func TestFlipServiceInternalAnnotation(t *testing.T) { + svc := getTestService("servicea", v1.ProtocolTCP, 80) + svcUpdated := flipServiceInternalAnnotation(&svc) + if !requiresInternalLoadBalancer(svcUpdated) { + t.Errorf("Expected svc to be an internal service") + } + svcUpdated = flipServiceInternalAnnotation(svcUpdated) + if requiresInternalLoadBalancer(svcUpdated) { + t.Errorf("Expected svc to be an external service") + } + + svc2 := getInternalTestService("serviceb", 8081) + svc2Updated := flipServiceInternalAnnotation(&svc2) + if requiresInternalLoadBalancer(svc2Updated) { + t.Errorf("Expected svc to be an external service") + } + + svc2Updated = flipServiceInternalAnnotation(svc2Updated) + if !requiresInternalLoadBalancer(svc2Updated) { + t.Errorf("Expected svc to be an internal service") + } +} + // Test additional of a new service/port. -func TestReconcileLoadBalancerAddPort(t *testing.T) { +func TestAddPort(t *testing.T) { az := getTestCloud() svc := getTestService("servicea", v1.ProtocolTCP, 80) - configProperties := getTestPublicFipConfigurationProperties() - lb := getTestLoadBalancer() - nodes := []*v1.Node{} + clusterResources := getClusterResources(az, 1, 1) svc.Spec.Ports = append(svc.Spec.Ports, v1.ServicePort{ Name: fmt.Sprintf("port-udp-%d", 1234), @@ -50,15 +74,11 @@ func TestReconcileLoadBalancerAddPort(t *testing.T) { NodePort: getBackendPort(1234), }) - lb, updated, err := az.reconcileLoadBalancer(lb, &configProperties, testClusterName, &svc, nodes) + lb, err := az.reconcileLoadBalancer(testClusterName, &svc, clusterResources.nodes, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } - if !updated { - t.Error("Expected the loadbalancer to need an update") - } - // ensure we got a frontend ip configuration if len(*lb.FrontendIPConfigurations) != 1 { t.Error("Expected the loadbalancer to have a frontend ip configuration") @@ -67,24 +87,300 @@ func TestReconcileLoadBalancerAddPort(t *testing.T) { validateLoadBalancer(t, lb, svc) } +func TestLoadBalancerInternalServiceModeSelection(t *testing.T) { + testLoadBalancerServiceDefaultModeSelection(t, true) + testLoadBalancerServiceAutoModeSelection(t, true) + testLoadBalancerServicesSpecifiedSelection(t, true) + testLoadBalancerMaxRulesServices(t, true) + testLoadBalancerServiceAutoModeDeleteSelection(t, true) +} + +func TestLoadBalancerExternalServiceModeSelection(t *testing.T) { + testLoadBalancerServiceDefaultModeSelection(t, false) + testLoadBalancerServiceAutoModeSelection(t, false) + testLoadBalancerServicesSpecifiedSelection(t, false) + testLoadBalancerMaxRulesServices(t, false) + testLoadBalancerServiceAutoModeDeleteSelection(t, false) +} + +func testLoadBalancerServiceDefaultModeSelection(t *testing.T, isInternal bool) { + az := getTestCloud() + const vmCount = 8 + const availabilitySetCount = 4 + const serviceCount = 9 + + clusterResources := getClusterResources(az, vmCount, availabilitySetCount) + getTestSecurityGroup(az) + + for index := 1; index <= serviceCount; index++ { + svcName := fmt.Sprintf("service-%d", index) + var svc v1.Service + if isInternal { + svc = getInternalTestService(svcName, 8081) + addTestSubnet(t, az, &svc) + } else { + svc = getTestService(svcName, v1.ProtocolTCP, 8081) + } + + lbStatus, err := az.EnsureLoadBalancer(testClusterName, &svc, clusterResources.nodes) + if err != nil { + t.Errorf("Unexpected error: %q", err) + } + if lbStatus == nil { + t.Errorf("Unexpected error: %s", svcName) + } + + expectedLBName := testClusterName + if isInternal { + expectedLBName = testClusterName + "-internal" + } + + result, _ := az.LoadBalancerClient.List(az.Config.ResourceGroup) + lb := (*result.Value)[0] + lbCount := len(*result.Value) + expectedNumOfLB := 1 + if lbCount != expectedNumOfLB { + t.Errorf("Unexpected number of LB's: Expected (%d) Found (%d)", expectedNumOfLB, lbCount) + } + + if !strings.EqualFold(*lb.Name, expectedLBName) { + t.Errorf("lb name should be the default LB name Extected (%s) Fouund (%s)", expectedLBName, *lb.Name) + } + + ruleCount := len(*lb.LoadBalancingRules) + if ruleCount != index { + t.Errorf("lb rule count should be equal to nuber of services deployed, expected (%d) Found (%d)", index, ruleCount) + } + } +} + +// Validate even distribution of external services across load balancers +// based on number of availability sets +func testLoadBalancerServiceAutoModeSelection(t *testing.T, isInternal bool) { + az := getTestCloud() + const vmCount = 8 + const availabilitySetCount = 4 + const serviceCount = 9 + + clusterResources := getClusterResources(az, vmCount, availabilitySetCount) + getTestSecurityGroup(az) + + for index := 1; index <= serviceCount; index++ { + svcName := fmt.Sprintf("service-%d", index) + var svc v1.Service + if isInternal { + svc = getInternalTestService(svcName, 8081) + addTestSubnet(t, az, &svc) + } else { + svc = getTestService(svcName, v1.ProtocolTCP, 8081) + } + setLoadBalancerAutoModeAnnotation(&svc) + lbStatus, err := az.EnsureLoadBalancer(testClusterName, &svc, clusterResources.nodes) + if err != nil { + t.Errorf("Unexpected error: %q", err) + } + if lbStatus == nil { + t.Errorf("Unexpected error: %s", svcName) + } + + // expected is MIN(index, availabilitySetCount) + expectedNumOfLB := int(math.Min(float64(index), float64(availabilitySetCount))) + result, _ := az.LoadBalancerClient.List(az.Config.ResourceGroup) + lbCount := len(*result.Value) + if lbCount != expectedNumOfLB { + t.Errorf("Unexpected number of LB's: Expected (%d) Found (%d)", expectedNumOfLB, lbCount) + } + + maxRules := 0 + minRules := serviceCount + for _, lb := range *result.Value { + ruleCount := len(*lb.LoadBalancingRules) + if ruleCount < minRules { + minRules = ruleCount + } + if ruleCount > maxRules { + maxRules = ruleCount + } + } + + delta := maxRules - minRules + if delta > 1 { + t.Errorf("Unexpected min or max rule in LB's in resource group: Service Index (%d) Min (%d) Max(%d)", index, minRules, maxRules) + } + } +} + +// Validate availability set selection of services across load balancers +// based on provided availability sets through service annotation +// The scenario is that there are 4 availability sets in the agent pool but the +// services will be assigned load balancers that are part of the provided availability sets +// specified in service annotation +func testLoadBalancerServicesSpecifiedSelection(t *testing.T, isInternal bool) { + az := getTestCloud() + const vmCount = 8 + const availabilitySetCount = 4 + const serviceCount = 9 + + clusterResources := getClusterResources(az, vmCount, availabilitySetCount) + getTestSecurityGroup(az) + + selectedAvailabilitySetName1 := getAvailabilitySetName(az, 1, availabilitySetCount) + selectedAvailabilitySetName2 := getAvailabilitySetName(az, 2, availabilitySetCount) + for index := 1; index <= serviceCount; index++ { + svcName := fmt.Sprintf("service-%d", index) + var svc v1.Service + if isInternal { + svc = getInternalTestService(svcName, 8081) + addTestSubnet(t, az, &svc) + } else { + svc = getTestService(svcName, v1.ProtocolTCP, 8081) + } + lbMode := fmt.Sprintf("%s,%s", selectedAvailabilitySetName1, selectedAvailabilitySetName2) + setLoadBalancerModeAnnotation(&svc, lbMode) + + lbStatus, err := az.EnsureLoadBalancer(testClusterName, &svc, clusterResources.nodes) + if err != nil { + t.Errorf("Unexpected error: %q", err) + } + if lbStatus == nil { + t.Errorf("Unexpected error: %s", svcName) + } + + // expected is MIN(index, 2) + expectedNumOfLB := int(math.Min(float64(index), float64(2))) + result, _ := az.LoadBalancerClient.List(az.Config.ResourceGroup) + lbCount := len(*result.Value) + if lbCount != expectedNumOfLB { + t.Errorf("Unexpected number of LB's: Expected (%d) Found (%d)", expectedNumOfLB, lbCount) + } + } +} + +func testLoadBalancerMaxRulesServices(t *testing.T, isInternal bool) { + az := getTestCloud() + const vmCount = 1 + const availabilitySetCount = 1 + + clusterResources := getClusterResources(az, vmCount, availabilitySetCount) + getTestSecurityGroup(az) + + az.Config.MaximumLoadBalancerRuleCount = 1 + + for index := 1; index <= az.Config.MaximumLoadBalancerRuleCount; index++ { + svcName := fmt.Sprintf("service-%d", index) + var svc v1.Service + if isInternal { + svc = getInternalTestService(svcName, 8081) + addTestSubnet(t, az, &svc) + } else { + svc = getTestService(svcName, v1.ProtocolTCP, 8081) + } + + lbStatus, err := az.EnsureLoadBalancer(testClusterName, &svc, clusterResources.nodes) + if err != nil { + t.Errorf("Unexpected error: %q", err) + } + if lbStatus == nil { + t.Errorf("Unexpected error: %s", svcName) + } + + // expected is MIN(index, az.Config.MaximumLoadBalancerRuleCount) + expectedNumOfLBRules := int(math.Min(float64(index), float64(az.Config.MaximumLoadBalancerRuleCount))) + result, _ := az.LoadBalancerClient.List(az.Config.ResourceGroup) + lbCount := len(*result.Value) + if lbCount != expectedNumOfLBRules { + t.Errorf("Unexpected number of LB's: Expected (%d) Found (%d)", expectedNumOfLBRules, lbCount) + } + } + + // validate adding a new service fails since it will exceed the max limit on LB + svcName := fmt.Sprintf("service-%d", az.Config.MaximumLoadBalancerRuleCount+1) + var svc v1.Service + if isInternal { + svc = getInternalTestService(svcName, 8081) + addTestSubnet(t, az, &svc) + } else { + svc = getTestService(svcName, v1.ProtocolTCP, 8081) + } + _, err := az.EnsureLoadBalancer(testClusterName, &svc, clusterResources.nodes) + if err == nil { + t.Errorf("Expect any new service to fail as max limit in lb has reached") + } else { + expectedErrMessageSubString := "all available load balancers have exceeded maximum rule limit" + if !strings.Contains(err.Error(), expectedErrMessageSubString) { + t.Errorf("Error message returned is not expected, expected sub string=%s, actual error message=%v", expectedErrMessageSubString, err) + } + } +} + +// Validate service deletion in lb auto selection mode +func testLoadBalancerServiceAutoModeDeleteSelection(t *testing.T, isInternal bool) { + az := getTestCloud() + const vmCount = 8 + const availabilitySetCount = 4 + const serviceCount = 9 + + clusterResources := getClusterResources(az, vmCount, availabilitySetCount) + getTestSecurityGroup(az) + + for index := 1; index <= serviceCount; index++ { + svcName := fmt.Sprintf("service-%d", index) + var svc v1.Service + if isInternal { + svc = getInternalTestService(svcName, 8081) + addTestSubnet(t, az, &svc) + } else { + svc = getTestService(svcName, v1.ProtocolTCP, 8081) + } + setLoadBalancerAutoModeAnnotation(&svc) + lbStatus, err := az.EnsureLoadBalancer(testClusterName, &svc, clusterResources.nodes) + if err != nil { + t.Errorf("Unexpected error: %q", err) + } + if lbStatus == nil { + t.Errorf("Unexpected error: %s", svcName) + } + } + + for index := serviceCount; index >= 1; index-- { + svcName := fmt.Sprintf("service-%d", index) + var svc v1.Service + if isInternal { + svc = getInternalTestService(svcName, 8081) + addTestSubnet(t, az, &svc) + } else { + svc = getTestService(svcName, v1.ProtocolTCP, 8081) + } + + setLoadBalancerAutoModeAnnotation(&svc) + + // expected is MIN(index, availabilitySetCount) + expectedNumOfLB := int(math.Min(float64(index), float64(availabilitySetCount))) + result, _ := az.LoadBalancerClient.List(az.Config.ResourceGroup) + lbCount := len(*result.Value) + if lbCount != expectedNumOfLB { + t.Errorf("Unexpected number of LB's: Expected (%d) Found (%d)", expectedNumOfLB, lbCount) + } + + err := az.EnsureLoadBalancerDeleted(testClusterName, &svc) + if err != nil { + t.Errorf("Unexpected error: %q", err) + } + } +} + // Test addition of a new service on an internal LB with a subnet. func TestReconcileLoadBalancerAddServiceOnInternalSubnet(t *testing.T) { az := getTestCloud() + clusterResources := getClusterResources(az, 1, 1) svc := getInternalTestService("servicea", 80) - addTestSubnet(t, &svc) - configProperties := getTestInternalFipConfigurationProperties(to.StringPtr("TestSubnet")) - lb := getTestLoadBalancer() - nodes := []*v1.Node{} + addTestSubnet(t, az, &svc) - lb, updated, err := az.reconcileLoadBalancer(lb, &configProperties, testClusterName, &svc, nodes) + lb, err := az.reconcileLoadBalancer(testClusterName, &svc, clusterResources.nodes, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } - if !updated { - t.Error("Expected the loadbalancer to need an update") - } - // ensure we got a frontend ip configuration if len(*lb.FrontendIPConfigurations) != 1 { t.Error("Expected the loadbalancer to have a frontend ip configuration") @@ -93,49 +389,81 @@ func TestReconcileLoadBalancerAddServiceOnInternalSubnet(t *testing.T) { validateLoadBalancer(t, lb, svc) } +func TestReconcileSecurityGroupFromAnyDestinationAddressPrefixToLoadBalancerIP(t *testing.T) { + az := getTestCloud() + svc1 := getTestService("serviceea", v1.ProtocolTCP, 80) + svc1.Spec.LoadBalancerIP = "192.168.0.0" + sg := getTestSecurityGroup(az) + // Simulate a pre-Kubernetes 1.8 NSG, where we do not specify the destination address prefix + sg, err := az.reconcileSecurityGroup(testClusterName, &svc1, to.StringPtr(""), true) + if err != nil { + t.Errorf("Unexpected error: %q", err) + } + sg, err = az.reconcileSecurityGroup(testClusterName, &svc1, to.StringPtr(svc1.Spec.LoadBalancerIP), true) + if err != nil { + t.Errorf("Unexpected error: %q", err) + } + validateSecurityGroup(t, sg, svc1) +} + +func TestReconcileSecurityGroupDynamicLoadBalancerIP(t *testing.T) { + az := getTestCloud() + svc1 := getTestService("servicea", v1.ProtocolTCP, 80) + svc1.Spec.LoadBalancerIP = "" + sg := getTestSecurityGroup(az) + dynamicallyAssignedIP := "192.168.0.0" + sg, err := az.reconcileSecurityGroup(testClusterName, &svc1, to.StringPtr(dynamicallyAssignedIP), true) + if err != nil { + t.Errorf("unexpected error: %q", err) + } + validateSecurityGroup(t, sg, svc1) +} + // Test addition of services on an internal LB using both default and explicit subnets. func TestReconcileLoadBalancerAddServicesOnMultipleSubnets(t *testing.T) { az := getTestCloud() + clusterResources := getClusterResources(az, 1, 1) svc1 := getTestService("service1", v1.ProtocolTCP, 8081) svc2 := getInternalTestService("service2", 8081) - addTestSubnet(t, &svc2) - configProperties1 := getTestPublicFipConfigurationProperties() - configProperties2 := getTestInternalFipConfigurationProperties(to.StringPtr("TestSubnet")) - lb := getTestLoadBalancer() - nodes := []*v1.Node{} - lb, updated, err := az.reconcileLoadBalancer(lb, &configProperties1, testClusterName, &svc1, nodes) + // Internal and External service cannot reside on the same LB resource + addTestSubnet(t, az, &svc2) + + // svc1 is using LB without "-internal" suffix + lb, err := az.reconcileLoadBalancer(testClusterName, &svc1, clusterResources.nodes, true /* wantLb */) if err != nil { t.Errorf("Unexpected error reconciling svc1: %q", err) } - lb, updated, err = az.reconcileLoadBalancer(lb, &configProperties2, testClusterName, &svc2, nodes) + // ensure we got a frontend ip configuration for each service + if len(*lb.FrontendIPConfigurations) != 1 { + t.Error("Expected the loadbalancer to have 1 frontend ip configurations") + } + + validateLoadBalancer(t, lb, svc1) + + // svc2 is using LB with "-internal" suffix + lb, err = az.reconcileLoadBalancer(testClusterName, &svc2, nil, true /* wantLb */) if err != nil { t.Errorf("Unexpected error reconciling svc2: %q", err) } - if !updated { - t.Error("Expected the loadbalancer to need an update") - } - // ensure we got a frontend ip configuration for each service - if len(*lb.FrontendIPConfigurations) != 2 { - t.Error("Expected the loadbalancer to have 2 frontend ip configurations") + if len(*lb.FrontendIPConfigurations) != 1 { + t.Error("Expected the loadbalancer to have 1 frontend ip configurations") } - validateLoadBalancer(t, lb, svc1, svc2) + validateLoadBalancer(t, lb, svc2) } // Test moving a service exposure from one subnet to another. func TestReconcileLoadBalancerEditServiceSubnet(t *testing.T) { az := getTestCloud() + clusterResources := getClusterResources(az, 1, 1) svc := getInternalTestService("service1", 8081) - addTestSubnet(t, &svc) - configProperties := getTestInternalFipConfigurationProperties(to.StringPtr("TestSubnet")) - lb := getTestLoadBalancer() - nodes := []*v1.Node{} + addTestSubnet(t, az, &svc) - lb, updated, err := az.reconcileLoadBalancer(lb, &configProperties, testClusterName, &svc, nodes) + lb, err := az.reconcileLoadBalancer(testClusterName, &svc, clusterResources.nodes, true /* wantLb */) if err != nil { t.Errorf("Unexpected error reconciling initial svc: %q", err) } @@ -143,17 +471,13 @@ func TestReconcileLoadBalancerEditServiceSubnet(t *testing.T) { validateLoadBalancer(t, lb, svc) svc.Annotations[ServiceAnnotationLoadBalancerInternalSubnet] = "NewSubnet" - configProperties = getTestInternalFipConfigurationProperties(to.StringPtr("NewSubnet")) + addTestSubnet(t, az, &svc) - lb, updated, err = az.reconcileLoadBalancer(lb, &configProperties, testClusterName, &svc, nodes) + lb, err = az.reconcileLoadBalancer(testClusterName, &svc, clusterResources.nodes, true /* wantLb */) if err != nil { t.Errorf("Unexpected error reconciling edits to svc: %q", err) } - if !updated { - t.Error("Expected the loadbalancer to need an update") - } - // ensure we got a frontend ip configuration for the service if len(*lb.FrontendIPConfigurations) != 1 { t.Error("Expected the loadbalancer to have 1 frontend ip configuration") @@ -164,23 +488,16 @@ func TestReconcileLoadBalancerEditServiceSubnet(t *testing.T) { func TestReconcileLoadBalancerNodeHealth(t *testing.T) { az := getTestCloud() + clusterResources := getClusterResources(az, 1, 1) svc := getTestService("servicea", v1.ProtocolTCP, 80) svc.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyTypeLocal svc.Spec.HealthCheckNodePort = int32(32456) - configProperties := getTestPublicFipConfigurationProperties() - lb := getTestLoadBalancer() - nodes := []*v1.Node{} - - lb, updated, err := az.reconcileLoadBalancer(lb, &configProperties, testClusterName, &svc, nodes) + lb, err := az.reconcileLoadBalancer(testClusterName, &svc, clusterResources.nodes, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } - if !updated { - t.Error("Expected the loadbalancer to need an update") - } - // ensure we got a frontend ip configuration if len(*lb.FrontendIPConfigurations) != 1 { t.Error("Expected the loadbalancer to have a frontend ip configuration") @@ -192,24 +509,17 @@ func TestReconcileLoadBalancerNodeHealth(t *testing.T) { // Test removing all services results in removing the frontend ip configuration func TestReconcileLoadBalancerRemoveService(t *testing.T) { az := getTestCloud() + clusterResources := getClusterResources(az, 1, 1) svc := getTestService("servicea", v1.ProtocolTCP, 80, 443) - lb := getTestLoadBalancer() - configProperties := getTestPublicFipConfigurationProperties() - nodes := []*v1.Node{} - lb, updated, err := az.reconcileLoadBalancer(lb, &configProperties, testClusterName, &svc, nodes) - if err != nil { - t.Errorf("Unexpected error: %q", err) - } - validateLoadBalancer(t, lb, svc) - - lb, updated, err = az.reconcileLoadBalancer(lb, nil, testClusterName, &svc, nodes) + lb, err := az.reconcileLoadBalancer(testClusterName, &svc, clusterResources.nodes, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } - if !updated { - t.Error("Expected the loadbalancer to need an update") + lb, err = az.reconcileLoadBalancer(testClusterName, &svc, clusterResources.nodes, false /* wantLb */) + if err != nil { + t.Errorf("Unexpected error: %q", err) } // ensure we abandoned the frontend ip configuration @@ -223,27 +533,21 @@ func TestReconcileLoadBalancerRemoveService(t *testing.T) { // Test removing all service ports results in removing the frontend ip configuration func TestReconcileLoadBalancerRemoveAllPortsRemovesFrontendConfig(t *testing.T) { az := getTestCloud() + clusterResources := getClusterResources(az, 1, 1) svc := getTestService("servicea", v1.ProtocolTCP, 80) - lb := getTestLoadBalancer() - configProperties := getTestPublicFipConfigurationProperties() - nodes := []*v1.Node{} - lb, updated, err := az.reconcileLoadBalancer(lb, &configProperties, testClusterName, &svc, nodes) + lb, err := az.reconcileLoadBalancer(testClusterName, &svc, clusterResources.nodes, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } validateLoadBalancer(t, lb, svc) svcUpdated := getTestService("servicea", v1.ProtocolTCP) - lb, updated, err = az.reconcileLoadBalancer(lb, nil, testClusterName, &svcUpdated, nodes) + lb, err = az.reconcileLoadBalancer(testClusterName, &svcUpdated, clusterResources.nodes, false /* wantLb*/) if err != nil { t.Errorf("Unexpected error: %q", err) } - if !updated { - t.Error("Expected the loadbalancer to need an update") - } - // ensure we abandoned the frontend ip configuration if len(*lb.FrontendIPConfigurations) != 0 { t.Error("Expected the loadbalancer to have no frontend ip configuration") @@ -255,37 +559,36 @@ func TestReconcileLoadBalancerRemoveAllPortsRemovesFrontendConfig(t *testing.T) // Test removal of a port from an existing service. func TestReconcileLoadBalancerRemovesPort(t *testing.T) { az := getTestCloud() + clusterResources := getClusterResources(az, 1, 1) + svc := getTestService("servicea", v1.ProtocolTCP, 80, 443) - configProperties := getTestPublicFipConfigurationProperties() - nodes := []*v1.Node{} - - existingLoadBalancer := getTestLoadBalancer(svc) - - svcUpdated := getTestService("servicea", v1.ProtocolTCP, 80) - updatedLoadBalancer, _, err := az.reconcileLoadBalancer(existingLoadBalancer, &configProperties, testClusterName, &svcUpdated, nodes) + lb, err := az.reconcileLoadBalancer(testClusterName, &svc, clusterResources.nodes, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } - validateLoadBalancer(t, updatedLoadBalancer, svcUpdated) + svcUpdated := getTestService("servicea", v1.ProtocolTCP, 80) + lb, err = az.reconcileLoadBalancer(testClusterName, &svcUpdated, clusterResources.nodes, true /* wantLb */) + if err != nil { + t.Errorf("Unexpected error: %q", err) + } + + validateLoadBalancer(t, lb, svcUpdated) } // Test reconciliation of multiple services on same port func TestReconcileLoadBalancerMultipleServices(t *testing.T) { az := getTestCloud() + clusterResources := getClusterResources(az, 1, 1) svc1 := getTestService("servicea", v1.ProtocolTCP, 80, 443) svc2 := getTestService("serviceb", v1.ProtocolTCP, 80) - configProperties := getTestPublicFipConfigurationProperties() - nodes := []*v1.Node{} - existingLoadBalancer := getTestLoadBalancer() - - updatedLoadBalancer, _, err := az.reconcileLoadBalancer(existingLoadBalancer, &configProperties, testClusterName, &svc1, nodes) + updatedLoadBalancer, err := az.reconcileLoadBalancer(testClusterName, &svc1, clusterResources.nodes, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } - updatedLoadBalancer, _, err = az.reconcileLoadBalancer(updatedLoadBalancer, &configProperties, testClusterName, &svc2, nodes) + updatedLoadBalancer, err = az.reconcileLoadBalancer(testClusterName, &svc2, clusterResources.nodes, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } @@ -293,13 +596,90 @@ func TestReconcileLoadBalancerMultipleServices(t *testing.T) { validateLoadBalancer(t, updatedLoadBalancer, svc1, svc2) } +func findLBRuleForPort(lbRules []network.LoadBalancingRule, port int32) (network.LoadBalancingRule, error) { + for _, lbRule := range lbRules { + if *lbRule.FrontendPort == port { + return lbRule, nil + } + } + return network.LoadBalancingRule{}, fmt.Errorf("Expected LB rule with port %d but none found", port) +} + +func TestServiceDefaultsToNoSessionPersistence(t *testing.T) { + az := getTestCloud() + svc := getTestService("service-sa-omitted", v1.ProtocolTCP, 7170) + clusterResources := getClusterResources(az, 1, 1) + + lb, err := az.reconcileLoadBalancer(testClusterName, &svc, clusterResources.nodes, true /* wantLb */) + if err != nil { + t.Errorf("Unexpected error reconciling svc1: %q", err) + } + validateLoadBalancer(t, lb, svc) + lbRule, err := findLBRuleForPort(*lb.LoadBalancingRules, 7170) + if err != nil { + t.Error(err) + } + + if lbRule.LoadDistribution != network.Default { + t.Errorf("Expected LB rule to have default load distribution but was %s", lbRule.LoadDistribution) + } +} + +func TestServiceRespectsNoSessionAffinity(t *testing.T) { + az := getTestCloud() + svc := getTestService("service-sa-none", v1.ProtocolTCP, 7170) + svc.Spec.SessionAffinity = v1.ServiceAffinityNone + clusterResources := getClusterResources(az, 1, 1) + + lb, err := az.reconcileLoadBalancer(testClusterName, &svc, clusterResources.nodes, true /* wantLb */) + if err != nil { + t.Errorf("Unexpected error reconciling svc1: %q", err) + } + + validateLoadBalancer(t, lb, svc) + + lbRule, err := findLBRuleForPort(*lb.LoadBalancingRules, 7170) + if err != nil { + t.Error(err) + } + + if lbRule.LoadDistribution != network.Default { + t.Errorf("Expected LB rule to have default load distribution but was %s", lbRule.LoadDistribution) + } +} + +func TestServiceRespectsClientIPSessionAffinity(t *testing.T) { + az := getTestCloud() + svc := getTestService("service-sa-clientip", v1.ProtocolTCP, 7170) + svc.Spec.SessionAffinity = v1.ServiceAffinityClientIP + clusterResources := getClusterResources(az, 1, 1) + + lb, err := az.reconcileLoadBalancer(testClusterName, &svc, clusterResources.nodes, true /* wantLb */) + if err != nil { + t.Errorf("Unexpected error reconciling svc1: %q", err) + } + + validateLoadBalancer(t, lb, svc) + + lbRule, err := findLBRuleForPort(*lb.LoadBalancingRules, 7170) + if err != nil { + t.Error(err) + } + + if lbRule.LoadDistribution != network.SourceIP { + t.Errorf("Expected LB rule to have SourceIP load distribution but was %s", lbRule.LoadDistribution) + } +} + func TestReconcileSecurityGroupNewServiceAddsPort(t *testing.T) { az := getTestCloud() - svc1 := getTestService("serviceea", v1.ProtocolTCP, 80) + getTestSecurityGroup(az) + svc1 := getTestService("servicea", v1.ProtocolTCP, 80) + clusterResources := getClusterResources(az, 1, 1) + lb, _ := az.reconcileLoadBalancer(testClusterName, &svc1, clusterResources.nodes, true) + lbStatus, _ := az.getServiceLoadBalancerStatus(&svc1, lb) - sg := getTestSecurityGroup() - - sg, _, err := az.reconcileSecurityGroup(sg, testClusterName, &svc1, true) + sg, err := az.reconcileSecurityGroup(testClusterName, &svc1, &lbStatus.Ingress[0].IP, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } @@ -309,11 +689,14 @@ func TestReconcileSecurityGroupNewServiceAddsPort(t *testing.T) { func TestReconcileSecurityGroupNewInternalServiceAddsPort(t *testing.T) { az := getTestCloud() + getTestSecurityGroup(az) svc1 := getInternalTestService("serviceea", 80) + addTestSubnet(t, az, &svc1) + clusterResources := getClusterResources(az, 1, 1) - sg := getTestSecurityGroup() - - sg, _, err := az.reconcileSecurityGroup(sg, testClusterName, &svc1, true) + lb, _ := az.reconcileLoadBalancer(testClusterName, &svc1, clusterResources.nodes, true) + lbStatus, _ := az.getServiceLoadBalancerStatus(&svc1, lb) + sg, err := az.reconcileSecurityGroup(testClusterName, &svc1, &lbStatus.Ingress[0].IP, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } @@ -322,14 +705,20 @@ func TestReconcileSecurityGroupNewInternalServiceAddsPort(t *testing.T) { } func TestReconcileSecurityGroupRemoveService(t *testing.T) { + az := getTestCloud() service1 := getTestService("servicea", v1.ProtocolTCP, 81) service2 := getTestService("serviceb", v1.ProtocolTCP, 82) + clusterResources := getClusterResources(az, 1, 1) - sg := getTestSecurityGroup(service1, service2) + lb, _ := az.reconcileLoadBalancer(testClusterName, &service1, clusterResources.nodes, true) + az.reconcileLoadBalancer(testClusterName, &service2, clusterResources.nodes, true) + lbStatus, _ := az.getServiceLoadBalancerStatus(&service1, lb) + + sg := getTestSecurityGroup(az, service1, service2) validateSecurityGroup(t, sg, service1, service2) - az := getTestCloud() - sg, _, err := az.reconcileSecurityGroup(sg, testClusterName, &service1, false) + + sg, err := az.reconcileSecurityGroup(testClusterName, &service1, &lbStatus.Ingress[0].IP, false /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } @@ -340,11 +729,14 @@ func TestReconcileSecurityGroupRemoveService(t *testing.T) { func TestReconcileSecurityGroupRemoveServiceRemovesPort(t *testing.T) { az := getTestCloud() svc := getTestService("servicea", v1.ProtocolTCP, 80, 443) + clusterResources := getClusterResources(az, 1, 1) - sg := getTestSecurityGroup(svc) - + sg := getTestSecurityGroup(az, svc) svcUpdated := getTestService("servicea", v1.ProtocolTCP, 80) - sg, _, err := az.reconcileSecurityGroup(sg, testClusterName, &svcUpdated, true) + lb, _ := az.reconcileLoadBalancer(testClusterName, &svc, clusterResources.nodes, true) + lbStatus, _ := az.getServiceLoadBalancerStatus(&svc, lb) + + sg, err := az.reconcileSecurityGroup(testClusterName, &svcUpdated, &lbStatus.Ingress[0].IP, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } @@ -359,9 +751,13 @@ func TestReconcileSecurityWithSourceRanges(t *testing.T) { "192.168.0.0/24", "10.0.0.0/32", } + clusterResources := getClusterResources(az, 1, 1) - sg := getTestSecurityGroup(svc) - sg, _, err := az.reconcileSecurityGroup(sg, testClusterName, &svc, true) + sg := getTestSecurityGroup(az, svc) + lb, _ := az.reconcileLoadBalancer(testClusterName, &svc, clusterResources.nodes, true) + lbStatus, _ := az.getServiceLoadBalancerStatus(&svc, lb) + + sg, err := az.reconcileSecurityGroup(testClusterName, &svc, &lbStatus.Ingress[0].IP, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } @@ -369,19 +765,237 @@ func TestReconcileSecurityWithSourceRanges(t *testing.T) { validateSecurityGroup(t, sg, svc) } -func getTestCloud() *Cloud { - return &Cloud{ +func TestReconcilePublicIPWithNewService(t *testing.T) { + az := getTestCloud() + svc := getTestService("servicea", v1.ProtocolTCP, 80, 443) + + pip, err := az.reconcilePublicIP(testClusterName, &svc, true /* wantLb*/) + if err != nil { + t.Errorf("Unexpected error: %q", err) + } + validatePublicIP(t, pip, &svc, true) + + pip2, err := az.reconcilePublicIP(testClusterName, &svc, true /* wantLb */) + if err != nil { + t.Errorf("Unexpected error: %q", err) + } + validatePublicIP(t, pip2, &svc, true) + if pip.Name != pip2.Name || + pip.PublicIPAddressPropertiesFormat.IPAddress != pip2.PublicIPAddressPropertiesFormat.IPAddress { + t.Errorf("We should get the exact same public ip resource after a second reconcile") + } +} + +func TestReconcilePublicIPRemoveService(t *testing.T) { + az := getTestCloud() + svc := getTestService("servicea", v1.ProtocolTCP, 80, 443) + + pip, err := az.reconcilePublicIP(testClusterName, &svc, true /* wantLb*/) + if err != nil { + t.Errorf("Unexpected error: %q", err) + } + + validatePublicIP(t, pip, &svc, true) + + // Remove the service + pip, err = az.reconcilePublicIP(testClusterName, &svc, false /* wantLb */) + if err != nil { + t.Errorf("Unexpected error: %q", err) + } + validatePublicIP(t, pip, &svc, false) + +} + +func TestReconcilePublicIPWithInternalService(t *testing.T) { + az := getTestCloud() + svc := getInternalTestService("servicea", 80, 443) + + pip, err := az.reconcilePublicIP(testClusterName, &svc, true /* wantLb*/) + if err != nil { + t.Errorf("Unexpected error: %q", err) + } + + validatePublicIP(t, pip, &svc, true) +} + +func TestReconcilePublicIPWithExternalAndInternalSwitch(t *testing.T) { + az := getTestCloud() + svc := getInternalTestService("servicea", 80, 443) + + pip, err := az.reconcilePublicIP(testClusterName, &svc, true /* wantLb*/) + if err != nil { + t.Errorf("Unexpected error: %q", err) + } + validatePublicIP(t, pip, &svc, true) + + // Update to external service + svcUpdated := getTestService("servicea", v1.ProtocolTCP, 80) + pip, err = az.reconcilePublicIP(testClusterName, &svcUpdated, true /* wantLb*/) + if err != nil { + t.Errorf("Unexpected error: %q", err) + } + validatePublicIP(t, pip, &svcUpdated, true) + + // Update to internal service again + pip, err = az.reconcilePublicIP(testClusterName, &svc, true /* wantLb*/) + if err != nil { + t.Errorf("Unexpected error: %q", err) + } + validatePublicIP(t, pip, &svc, true) +} + +func getTestCloud() (az *Cloud) { + az = &Cloud{ Config: Config{ - TenantID: "tenant", - SubscriptionID: "subscription", - ResourceGroup: "rg", - Location: "westus", - VnetName: "vnet", - SubnetName: "subnet", - SecurityGroupName: "nsg", - RouteTableName: "rt", + AzureAuthConfig: auth.AzureAuthConfig{ + TenantID: "tenant", + SubscriptionID: "subscription", + }, + ResourceGroup: "rg", + VnetResourceGroup: "rg", + Location: "westus", + VnetName: "vnet", + SubnetName: "subnet", + SecurityGroupName: "nsg", + RouteTableName: "rt", + PrimaryAvailabilitySetName: "as", + MaximumLoadBalancerRuleCount: 250, }, } + az.operationPollRateLimiter = flowcontrol.NewTokenBucketRateLimiter(100, 100) + az.LoadBalancerClient = newFakeAzureLBClient() + az.PublicIPAddressesClient = newFakeAzurePIPClient(az.Config.SubscriptionID) + az.SubnetsClient = newFakeAzureSubnetsClient() + az.SecurityGroupsClient = newFakeAzureNSGClient() + az.VirtualMachinesClient = newFakeAzureVirtualMachinesClient() + az.InterfacesClient = newFakeAzureInterfacesClient() + az.VirtualMachineScaleSetsClient = newFakeVirtualMachineScaleSetsClient() + az.VirtualMachineScaleSetVMsClient = newFakeVirtualMachineScaleSetVMsClient() + az.vmSet = newAvailabilitySet(az) + + return az +} + +const networkInterfacesIDTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/networkInterfaces/%s" +const primaryIPConfigIDTemplate = "%s/ipConfigurations/ipconfig" + +// returns the full identifier of Network Interface. +func getNetworkInterfaceID(subscriptionID string, resourceGroupName, nicName string) string { + return fmt.Sprintf( + networkInterfacesIDTemplate, + subscriptionID, + resourceGroupName, + nicName) +} + +// returns the full identifier of a private ipconfig of the nic +func getPrimaryIPConfigID(nicID string) string { + return fmt.Sprintf( + primaryIPConfigIDTemplate, + nicID) +} + +const TestResourceNameFormat = "%s-%d" +const TestVMResourceBaseName = "vm" +const TestASResourceBaseName = "as" + +func getTestResourceName(resourceBaseName string, index int) string { + return fmt.Sprintf(TestResourceNameFormat, resourceBaseName, index) +} + +func getVMName(vmIndex int) string { + return getTestResourceName(TestVMResourceBaseName, vmIndex) +} + +func getAvailabilitySetName(az *Cloud, vmIndex int, numAS int) string { + asIndex := vmIndex % numAS + if asIndex == 0 { + return az.Config.PrimaryAvailabilitySetName + } + + return getTestResourceName(TestASResourceBaseName, asIndex) +} + +// test supporting on 1 nic per vm +// we really dont care about the name of the nic +// just using the vm name for testing purposes +func getNICName(vmIndex int) string { + return getVMName(vmIndex) +} + +type ClusterResources struct { + nodes []*v1.Node + availabilitySetNames []string +} + +func getClusterResources(az *Cloud, vmCount int, availabilitySetCount int) (clusterResources *ClusterResources) { + if vmCount < availabilitySetCount { + return nil + } + clusterResources = &ClusterResources{} + clusterResources.nodes = []*v1.Node{} + clusterResources.availabilitySetNames = []string{} + for vmIndex := 0; vmIndex < vmCount; vmIndex++ { + vmName := getVMName(vmIndex) + asName := getAvailabilitySetName(az, vmIndex, availabilitySetCount) + clusterResources.availabilitySetNames = append(clusterResources.availabilitySetNames, asName) + + nicName := getNICName(vmIndex) + nicID := getNetworkInterfaceID(az.Config.SubscriptionID, az.Config.ResourceGroup, nicName) + primaryIPConfigID := getPrimaryIPConfigID(nicID) + isPrimary := true + newNIC := network.Interface{ + ID: &nicID, + Name: &nicName, + InterfacePropertiesFormat: &network.InterfacePropertiesFormat{ + IPConfigurations: &[]network.InterfaceIPConfiguration{ + { + ID: &primaryIPConfigID, + InterfaceIPConfigurationPropertiesFormat: &network.InterfaceIPConfigurationPropertiesFormat{ + PrivateIPAddress: &nicName, + Primary: &isPrimary, + }, + }, + }, + }, + } + az.InterfacesClient.CreateOrUpdate(az.Config.ResourceGroup, nicName, newNIC, nil) + + // create vm + asID := az.getAvailabilitySetID(asName) + newVM := compute.VirtualMachine{ + Name: &vmName, + Location: &az.Config.Location, + VirtualMachineProperties: &compute.VirtualMachineProperties{ + AvailabilitySet: &compute.SubResource{ + ID: &asID, + }, + NetworkProfile: &compute.NetworkProfile{ + NetworkInterfaces: &[]compute.NetworkInterfaceReference{ + { + ID: &nicID, + }, + }, + }, + }, + } + + _, errChan := az.VirtualMachinesClient.CreateOrUpdate(az.Config.ResourceGroup, vmName, newVM, nil) + if err := <-errChan; err != nil { + } + // add to kubernetes + newNode := &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: vmName, + Labels: map[string]string{ + kubeletapis.LabelHostname: vmName, + }, + }, + } + clusterResources.nodes = append(clusterResources.nodes, newNode) + } + + return clusterResources } func getBackendPort(port int32) int32 { @@ -433,10 +1047,17 @@ func getTestService(identifier string, proto v1.Protocol, requestedPorts ...int3 func getInternalTestService(identifier string, requestedPorts ...int32) v1.Service { svc := getTestService(identifier, v1.ProtocolTCP, requestedPorts...) svc.Annotations[ServiceAnnotationLoadBalancerInternal] = "true" - return svc } +func setLoadBalancerModeAnnotation(service *v1.Service, lbMode string) { + service.Annotations[ServiceAnnotationLoadBalancerMode] = lbMode +} + +func setLoadBalancerAutoModeAnnotation(service *v1.Service) { + setLoadBalancerModeAnnotation(service, ServiceAnnotationLoadBalancerAutoModeValue) +} + func getTestLoadBalancer(services ...v1.Service) network.LoadBalancer { rules := []network.LoadBalancingRule{} probes := []network.Probe{} @@ -480,7 +1101,7 @@ func getServiceSourceRanges(service *v1.Service) []string { return service.Spec.LoadBalancerSourceRanges } -func getTestSecurityGroup(services ...v1.Service) network.SecurityGroup { +func getTestSecurityGroup(az *Cloud, services ...v1.Service) *network.SecurityGroup { rules := []network.SecurityRule{} for _, service := range services { @@ -500,15 +1121,22 @@ func getTestSecurityGroup(services ...v1.Service) network.SecurityGroup { } sg := network.SecurityGroup{ + Name: &az.SecurityGroupName, SecurityGroupPropertiesFormat: &network.SecurityGroupPropertiesFormat{ SecurityRules: &rules, }, } - return sg + az.SecurityGroupsClient.CreateOrUpdate( + az.ResourceGroup, + az.SecurityGroupName, + sg, + nil) + + return &sg } -func validateLoadBalancer(t *testing.T, loadBalancer network.LoadBalancer, services ...v1.Service) { +func validateLoadBalancer(t *testing.T, loadBalancer *network.LoadBalancer, services ...v1.Service) { expectedRuleCount := 0 expectedFrontendIPCount := 0 expectedProbeCount := 0 @@ -635,19 +1263,100 @@ func describeFIPs(frontendIPs []network.FrontendIPConfiguration) string { return description } -func validateSecurityGroup(t *testing.T, securityGroup network.SecurityGroup, services ...v1.Service) { - expectedRuleCount := 0 +func validatePublicIP(t *testing.T, publicIP *network.PublicIPAddress, service *v1.Service, wantLb bool) { + isInternal := requiresInternalLoadBalancer(service) + if isInternal || !wantLb { + if publicIP != nil { + t.Errorf("Expected publicIP resource to be nil, when it is an internal service or doesn't want LB") + } + return + } + + // For external service + if publicIP == nil { + t.Errorf("Expected publicIP resource exists, when it is not an internal service") + } + + if publicIP.Tags == nil || (*publicIP.Tags)["service"] == nil { + t.Errorf("Expected publicIP resource has tags[service]") + } + + serviceName := getServiceName(service) + if serviceName != *(*publicIP.Tags)["service"] { + t.Errorf("Expected publicIP resource has matching tags[service]") + } + // We cannot use service.Spec.LoadBalancerIP to compare with + // Public IP's IPAddress + // Becuase service properties are updated outside of cloudprovider code +} + +func contains(ruleValues []string, targetValue string) bool { + for _, ruleValue := range ruleValues { + if strings.EqualFold(ruleValue, targetValue) { + return true + } + } + return false +} + +func securityRuleMatches(serviceSourceRange string, servicePort v1.ServicePort, serviceIP string, securityRule network.SecurityRule) error { + ruleSource := securityRule.SourceAddressPrefixes + if ruleSource == nil || len(*ruleSource) == 0 { + if securityRule.SourceAddressPrefix == nil { + ruleSource = &[]string{} + } else { + ruleSource = &[]string{*securityRule.SourceAddressPrefix} + } + } + + rulePorts := securityRule.DestinationPortRanges + if rulePorts == nil || len(*rulePorts) == 0 { + if securityRule.DestinationPortRange == nil { + rulePorts = &[]string{} + } else { + rulePorts = &[]string{*securityRule.DestinationPortRange} + } + } + + ruleDestination := securityRule.DestinationAddressPrefixes + if ruleDestination == nil || len(*ruleDestination) == 0 { + if securityRule.DestinationAddressPrefix == nil { + ruleDestination = &[]string{} + } else { + ruleDestination = &[]string{*securityRule.DestinationAddressPrefix} + } + } + + if !contains(*ruleSource, serviceSourceRange) { + return fmt.Errorf("Rule does not contain source %s", serviceSourceRange) + } + + if !contains(*rulePorts, fmt.Sprintf("%d", servicePort.Port)) { + return fmt.Errorf("Rule does not contain port %d", servicePort.Port) + } + + if serviceIP != "" && !contains(*ruleDestination, serviceIP) { + return fmt.Errorf("Rule does not contain destination %s", serviceIP) + } + + return nil +} + +func validateSecurityGroup(t *testing.T, securityGroup *network.SecurityGroup, services ...v1.Service) { + seenRules := make(map[string]string) for _, svc := range services { for _, wantedRule := range svc.Spec.Ports { sources := getServiceSourceRanges(&svc) for _, source := range sources { wantedRuleName := getSecurityRuleName(&svc, wantedRule, source) - expectedRuleCount++ + seenRules[wantedRuleName] = wantedRuleName foundRule := false for _, actualRule := range *securityGroup.SecurityRules { - if strings.EqualFold(*actualRule.Name, wantedRuleName) && - *actualRule.SourceAddressPrefix == source && - *actualRule.DestinationPortRange == fmt.Sprintf("%d", wantedRule.Port) { + if strings.EqualFold(*actualRule.Name, wantedRuleName) { + err := securityRuleMatches(source, wantedRule, svc.Spec.LoadBalancerIP, actualRule) + if err != nil { + t.Errorf("Found matching security rule %q but properties were incorrect: %v", wantedRuleName, err) + } foundRule = true break } @@ -660,6 +1369,7 @@ func validateSecurityGroup(t *testing.T, securityGroup network.SecurityGroup, se } lenRules := len(*securityGroup.SecurityRules) + expectedRuleCount := len(seenRules) if lenRules != expectedRuleCount { t.Errorf("Expected the loadbalancer to have %d rules. Found %d.\n", expectedRuleCount, lenRules) } @@ -756,10 +1466,6 @@ func TestNewCloudFromJSON(t *testing.T) { "routeTableName": "--route-table-name--", "primaryAvailabilitySetName": "--primary-availability-set-name--", "cloudProviderBackoff": true, - "cloudProviderBackoffRetries": 6, - "cloudProviderBackoffExponent": 1.5, - "cloudProviderBackoffDuration": 5, - "cloudProviderBackoffJitter": 1.0, "cloudProviderRatelimit": true, "cloudProviderRateLimitQPS": 0.5, "cloudProviderRateLimitBucket": 5 @@ -927,7 +1633,8 @@ func TestDecodeInstanceInfo(t *testing.T) { } } -func TestSplitProviderID(t *testing.T) { +func TestGetNodeNameByProviderID(t *testing.T) { + az := getTestCloud() providers := []struct { providerID string name types.NodeName @@ -962,7 +1669,7 @@ func TestSplitProviderID(t *testing.T) { } for _, test := range providers { - name, err := splitProviderID(test.providerID) + name, err := az.vmSet.GetNodeNameByProviderID(test.providerID) if (err != nil) != test.fail { t.Errorf("Expected to failt=%t, with pattern %v", test.fail, test) } @@ -978,76 +1685,857 @@ func TestSplitProviderID(t *testing.T) { } } -func TestMetadataURLGeneration(t *testing.T) { - metadata := NewInstanceMetadata() - fullPath := metadata.makeMetadataURL("some/path") - if fullPath != "http://169.254.169.254/metadata/some/path" { - t.Errorf("Expected http://169.254.169.254/metadata/some/path saw %s", fullPath) - } -} - -func TestMetadataParsing(t *testing.T) { - data := ` -{ - "interface": [ - { - "ipv4": { - "ipAddress": [ - { - "privateIpAddress": "10.0.1.4", - "publicIpAddress": "X.X.X.X" - } - ], - "subnet": [ - { - "address": "10.0.1.0", - "prefix": "24" - } - ] - }, - "ipv6": { - "ipAddress": [ - - ] - }, - "macAddress": "002248020E1E" - } - ] -} -` - - network := NetworkMetadata{} - if err := json.Unmarshal([]byte(data), &network); err != nil { - t.Errorf("Unexpected error: %v", err) - } - - ip := network.Interface[0].IPV4.IPAddress[0].PrivateIP - if ip != "10.0.1.4" { - t.Errorf("Unexpected value: %s, expected 10.0.1.4", ip) - } - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, data) - })) - defer server.Close() - - metadata := &InstanceMetadata{ - baseURL: server.URL, - } - - networkJSON := NetworkMetadata{} - if err := metadata.Object("/some/path", &networkJSON); err != nil { - t.Errorf("Unexpected error: %v", err) - } - - if !reflect.DeepEqual(network, networkJSON) { - t.Errorf("Unexpected inequality:\n%#v\nvs\n%#v", network, networkJSON) - } -} - -func addTestSubnet(t *testing.T, svc *v1.Service) { +func addTestSubnet(t *testing.T, az *Cloud, svc *v1.Service) { if svc.Annotations[ServiceAnnotationLoadBalancerInternal] != "true" { t.Error("Subnet added to non-internal service") } - svc.Annotations[ServiceAnnotationLoadBalancerInternalSubnet] = "TestSubnet" + subName := svc.Annotations[ServiceAnnotationLoadBalancerInternalSubnet] + if subName == "" { + subName = az.SubnetName + } + + subnetID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/%s/subnets/%s", + az.SubscriptionID, + az.VnetResourceGroup, + az.VnetName, + subName) + + _, errChan := az.SubnetsClient.CreateOrUpdate(az.VnetResourceGroup, az.VnetName, subName, + network.Subnet{ + ID: &subnetID, + Name: &subName, + }, nil) + + if err := <-errChan; err != nil { + t.Errorf("Subnet cannot be created or update, %v", err) + } + svc.Annotations[ServiceAnnotationLoadBalancerInternalSubnet] = subName } + +func TestIfServiceSpecifiesSharedRuleAndRuleDoesNotExistItIsCreated(t *testing.T) { + az := getTestCloud() + svc := getTestService("servicesr", v1.ProtocolTCP, 80) + svc.Spec.LoadBalancerIP = "192.168.77.88" + svc.Annotations[ServiceAnnotationSharedSecurityRule] = "true" + + sg := getTestSecurityGroup(az) + + sg, err := az.reconcileSecurityGroup(testClusterName, &svc, to.StringPtr(svc.Spec.LoadBalancerIP), true) + if err != nil { + t.Errorf("Unexpected error: %q", err) + } + + validateSecurityGroup(t, sg, svc) + + expectedRuleName := "shared-TCP-80-Internet" + _, securityRule, ruleFound := findSecurityRuleByName(*sg.SecurityRules, expectedRuleName) + if !ruleFound { + t.Fatalf("Expected security rule %q but it was not present", expectedRuleName) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 80}, "192.168.77.88", securityRule) + if err != nil { + t.Errorf("Shared rule was not updated with new service IP: %v", err) + } + + if securityRule.Priority == nil { + t.Errorf("Shared rule %s had no priority", expectedRuleName) + } + + if securityRule.Access != network.SecurityRuleAccessAllow { + t.Errorf("Shared rule %s did not have Allow access", expectedRuleName) + } + + if securityRule.Direction != network.SecurityRuleDirectionInbound { + t.Errorf("Shared rule %s did not have Inbound direction", expectedRuleName) + } +} + +func TestIfServiceSpecifiesSharedRuleAndRuleExistsThenTheServicesPortAndAddressAreAdded(t *testing.T) { + az := getTestCloud() + svc := getTestService("servicesr", v1.ProtocolTCP, 80) + svc.Spec.LoadBalancerIP = "192.168.77.88" + svc.Annotations[ServiceAnnotationSharedSecurityRule] = "true" + + expectedRuleName := "shared-TCP-80-Internet" + + sg := getTestSecurityGroup(az) + sg.SecurityRules = &[]network.SecurityRule{ + { + Name: &expectedRuleName, + SecurityRulePropertiesFormat: &network.SecurityRulePropertiesFormat{ + Protocol: network.SecurityRuleProtocolTCP, + SourcePortRange: to.StringPtr("*"), + SourceAddressPrefix: to.StringPtr("Internet"), + DestinationPortRange: to.StringPtr("80"), + DestinationAddressPrefix: to.StringPtr("192.168.33.44"), + Access: network.SecurityRuleAccessAllow, + Direction: network.SecurityRuleDirectionInbound, + }, + }, + } + + sg, err := az.reconcileSecurityGroup(testClusterName, &svc, to.StringPtr(svc.Spec.LoadBalancerIP), true) + if err != nil { + t.Errorf("Unexpected error: %q", err) + } + + validateSecurityGroup(t, sg, svc) + + _, securityRule, ruleFound := findSecurityRuleByName(*sg.SecurityRules, expectedRuleName) + if !ruleFound { + t.Fatalf("Expected security rule %q but it was not present", expectedRuleName) + } + + expectedDestinationIPCount := 2 + if len(*securityRule.DestinationAddressPrefixes) != expectedDestinationIPCount { + t.Errorf("Shared rule should have had %d destination IP addresses but had %d", expectedDestinationIPCount, len(*securityRule.DestinationAddressPrefixes)) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 80}, "192.168.33.44", securityRule) + if err != nil { + t.Errorf("Shared rule no longer matched other service IP: %v", err) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 80}, "192.168.77.88", securityRule) + if err != nil { + t.Errorf("Shared rule was not updated with new service IP: %v", err) + } +} + +func TestIfServicesSpecifySharedRuleButDifferentPortsThenSeparateRulesAreCreated(t *testing.T) { + az := getTestCloud() + + svc1 := getTestService("servicesr1", v1.ProtocolTCP, 4444) + svc1.Spec.LoadBalancerIP = "192.168.77.88" + svc1.Annotations[ServiceAnnotationSharedSecurityRule] = "true" + + svc2 := getTestService("servicesr2", v1.ProtocolTCP, 8888) + svc2.Spec.LoadBalancerIP = "192.168.33.44" + svc2.Annotations[ServiceAnnotationSharedSecurityRule] = "true" + + expectedRuleName1 := "shared-TCP-4444-Internet" + expectedRuleName2 := "shared-TCP-8888-Internet" + + sg := getTestSecurityGroup(az) + + sg, err := az.reconcileSecurityGroup(testClusterName, &svc1, to.StringPtr(svc1.Spec.LoadBalancerIP), true) + if err != nil { + t.Errorf("Unexpected error adding svc1: %q", err) + } + + sg, err = az.reconcileSecurityGroup(testClusterName, &svc2, to.StringPtr(svc2.Spec.LoadBalancerIP), true) + if err != nil { + t.Errorf("Unexpected error adding svc2: %q", err) + } + + validateSecurityGroup(t, sg, svc1, svc2) + + _, securityRule1, rule1Found := findSecurityRuleByName(*sg.SecurityRules, expectedRuleName1) + if !rule1Found { + t.Fatalf("Expected security rule %q but it was not present", expectedRuleName1) + } + + _, securityRule2, rule2Found := findSecurityRuleByName(*sg.SecurityRules, expectedRuleName2) + if !rule2Found { + t.Fatalf("Expected security rule %q but it was not present", expectedRuleName2) + } + + expectedDestinationIPCount1 := 1 + if len(*securityRule1.DestinationAddressPrefixes) != expectedDestinationIPCount1 { + t.Errorf("Shared rule %s should have had %d destination IP addresses but had %d", expectedRuleName1, expectedDestinationIPCount1, len(*securityRule1.DestinationAddressPrefixes)) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 4444}, "192.168.77.88", securityRule1) + if err != nil { + t.Errorf("Shared rule %s did not match service IP: %v", expectedRuleName1, err) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 8888}, "192.168.33.44", securityRule1) + if err == nil { + t.Errorf("Shared rule %s matched wrong service's port and IP", expectedRuleName1) + } + + expectedDestinationIPCount2 := 1 + if len(*securityRule2.DestinationAddressPrefixes) != expectedDestinationIPCount2 { + t.Errorf("Shared rule %s should have had %d destination IP addresses but had %d", expectedRuleName2, expectedDestinationIPCount2, len(*securityRule2.DestinationAddressPrefixes)) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 8888}, "192.168.33.44", securityRule2) + if err != nil { + t.Errorf("Shared rule %s did not match service IP: %v", expectedRuleName2, err) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 4444}, "192.168.77.88", securityRule2) + if err == nil { + t.Errorf("Shared rule %s matched wrong service's port and IP", expectedRuleName2) + } +} + +func TestIfServicesSpecifySharedRuleButDifferentProtocolsThenSeparateRulesAreCreated(t *testing.T) { + az := getTestCloud() + + svc1 := getTestService("servicesr1", v1.ProtocolTCP, 4444) + svc1.Spec.LoadBalancerIP = "192.168.77.88" + svc1.Annotations[ServiceAnnotationSharedSecurityRule] = "true" + + svc2 := getTestService("servicesr2", v1.ProtocolUDP, 4444) + svc2.Spec.LoadBalancerIP = "192.168.77.88" + svc2.Annotations[ServiceAnnotationSharedSecurityRule] = "true" + + expectedRuleName1 := "shared-TCP-4444-Internet" + expectedRuleName2 := "shared-UDP-4444-Internet" + + sg := getTestSecurityGroup(az) + + sg, err := az.reconcileSecurityGroup(testClusterName, &svc1, to.StringPtr(svc1.Spec.LoadBalancerIP), true) + if err != nil { + t.Errorf("Unexpected error adding svc1: %q", err) + } + + sg, err = az.reconcileSecurityGroup(testClusterName, &svc2, to.StringPtr(svc2.Spec.LoadBalancerIP), true) + if err != nil { + t.Errorf("Unexpected error adding svc2: %q", err) + } + + validateSecurityGroup(t, sg, svc1, svc2) + + _, securityRule1, rule1Found := findSecurityRuleByName(*sg.SecurityRules, expectedRuleName1) + if !rule1Found { + t.Fatalf("Expected security rule %q but it was not present", expectedRuleName1) + } + + _, securityRule2, rule2Found := findSecurityRuleByName(*sg.SecurityRules, expectedRuleName2) + if !rule2Found { + t.Fatalf("Expected security rule %q but it was not present", expectedRuleName2) + } + + expectedDestinationIPCount1 := 1 + if len(*securityRule1.DestinationAddressPrefixes) != expectedDestinationIPCount1 { + t.Errorf("Shared rule %s should have had %d destination IP addresses but had %d", expectedRuleName1, expectedDestinationIPCount1, len(*securityRule1.DestinationAddressPrefixes)) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 4444}, "192.168.77.88", securityRule1) + if err != nil { + t.Errorf("Shared rule %s did not match service IP: %v", expectedRuleName1, err) + } + + if securityRule1.Protocol != network.SecurityRuleProtocolTCP { + t.Errorf("Shared rule %s should have been %s but was %s", expectedRuleName1, network.SecurityRuleProtocolTCP, securityRule1.Protocol) + } + + expectedDestinationIPCount2 := 1 + if len(*securityRule2.DestinationAddressPrefixes) != expectedDestinationIPCount2 { + t.Errorf("Shared rule %s should have had %d destination IP addresses but had %d", expectedRuleName2, expectedDestinationIPCount2, len(*securityRule2.DestinationAddressPrefixes)) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 4444}, "192.168.77.88", securityRule2) + if err != nil { + t.Errorf("Shared rule %s did not match service IP: %v", expectedRuleName2, err) + } + + if securityRule2.Protocol != network.SecurityRuleProtocolUDP { + t.Errorf("Shared rule %s should have been %s but was %s", expectedRuleName2, network.SecurityRuleProtocolUDP, securityRule2.Protocol) + } +} + +func TestIfServicesSpecifySharedRuleButDifferentSourceAddressesThenSeparateRulesAreCreated(t *testing.T) { + az := getTestCloud() + + svc1 := getTestService("servicesr1", v1.ProtocolTCP, 80) + svc1.Spec.LoadBalancerIP = "192.168.77.88" + svc1.Spec.LoadBalancerSourceRanges = []string{"192.168.12.0/24"} + svc1.Annotations[ServiceAnnotationSharedSecurityRule] = "true" + + svc2 := getTestService("servicesr2", v1.ProtocolTCP, 80) + svc2.Spec.LoadBalancerIP = "192.168.33.44" + svc2.Spec.LoadBalancerSourceRanges = []string{"192.168.34.0/24"} + svc2.Annotations[ServiceAnnotationSharedSecurityRule] = "true" + + expectedRuleName1 := "shared-TCP-80-192.168.12.0_24" + expectedRuleName2 := "shared-TCP-80-192.168.34.0_24" + + sg := getTestSecurityGroup(az) + + sg, err := az.reconcileSecurityGroup(testClusterName, &svc1, to.StringPtr(svc1.Spec.LoadBalancerIP), true) + if err != nil { + t.Errorf("Unexpected error adding svc1: %q", err) + } + + sg, err = az.reconcileSecurityGroup(testClusterName, &svc2, to.StringPtr(svc2.Spec.LoadBalancerIP), true) + if err != nil { + t.Errorf("Unexpected error adding svc2: %q", err) + } + + validateSecurityGroup(t, sg, svc1, svc2) + + _, securityRule1, rule1Found := findSecurityRuleByName(*sg.SecurityRules, expectedRuleName1) + if !rule1Found { + t.Fatalf("Expected security rule %q but it was not present", expectedRuleName1) + } + + _, securityRule2, rule2Found := findSecurityRuleByName(*sg.SecurityRules, expectedRuleName2) + if !rule2Found { + t.Fatalf("Expected security rule %q but it was not present", expectedRuleName2) + } + + expectedDestinationIPCount1 := 1 + if len(*securityRule1.DestinationAddressPrefixes) != expectedDestinationIPCount1 { + t.Errorf("Shared rule %s should have had %d destination IP addresses but had %d", expectedRuleName1, expectedDestinationIPCount1, len(*securityRule1.DestinationAddressPrefixes)) + } + + err = securityRuleMatches(svc1.Spec.LoadBalancerSourceRanges[0], v1.ServicePort{Port: 80}, "192.168.77.88", securityRule1) + if err != nil { + t.Errorf("Shared rule %s did not match service IP: %v", expectedRuleName1, err) + } + + err = securityRuleMatches(svc2.Spec.LoadBalancerSourceRanges[0], v1.ServicePort{Port: 80}, "192.168.33.44", securityRule1) + if err == nil { + t.Errorf("Shared rule %s matched wrong service's port and IP", expectedRuleName1) + } + + expectedDestinationIPCount2 := 1 + if len(*securityRule2.DestinationAddressPrefixes) != expectedDestinationIPCount2 { + t.Errorf("Shared rule %s should have had %d destination IP addresses but had %d", expectedRuleName2, expectedDestinationIPCount2, len(*securityRule2.DestinationAddressPrefixes)) + } + + err = securityRuleMatches(svc2.Spec.LoadBalancerSourceRanges[0], v1.ServicePort{Port: 80}, "192.168.33.44", securityRule2) + if err != nil { + t.Errorf("Shared rule %s did not match service IP: %v", expectedRuleName2, err) + } + + err = securityRuleMatches(svc1.Spec.LoadBalancerSourceRanges[0], v1.ServicePort{Port: 80}, "192.168.77.88", securityRule2) + if err == nil { + t.Errorf("Shared rule %s matched wrong service's port and IP", expectedRuleName2) + } +} + +func TestIfServicesSpecifySharedRuleButSomeAreOnDifferentPortsThenRulesAreSeparatedOrConsoliatedByPort(t *testing.T) { + az := getTestCloud() + + svc1 := getTestService("servicesr1", v1.ProtocolTCP, 4444) + svc1.Spec.LoadBalancerIP = "192.168.77.88" + svc1.Annotations[ServiceAnnotationSharedSecurityRule] = "true" + + svc2 := getTestService("servicesr2", v1.ProtocolTCP, 8888) + svc2.Spec.LoadBalancerIP = "192.168.33.44" + svc2.Annotations[ServiceAnnotationSharedSecurityRule] = "true" + + svc3 := getTestService("servicesr3", v1.ProtocolTCP, 4444) + svc3.Spec.LoadBalancerIP = "192.168.99.11" + svc3.Annotations[ServiceAnnotationSharedSecurityRule] = "true" + + expectedRuleName13 := "shared-TCP-4444-Internet" + expectedRuleName2 := "shared-TCP-8888-Internet" + + sg := getTestSecurityGroup(az) + + sg, err := az.reconcileSecurityGroup(testClusterName, &svc1, to.StringPtr(svc1.Spec.LoadBalancerIP), true) + if err != nil { + t.Errorf("Unexpected error adding svc1: %q", err) + } + + sg, err = az.reconcileSecurityGroup(testClusterName, &svc2, to.StringPtr(svc2.Spec.LoadBalancerIP), true) + if err != nil { + t.Errorf("Unexpected error adding svc2: %q", err) + } + + sg, err = az.reconcileSecurityGroup(testClusterName, &svc3, to.StringPtr(svc3.Spec.LoadBalancerIP), true) + if err != nil { + t.Errorf("Unexpected error adding svc3: %q", err) + } + + validateSecurityGroup(t, sg, svc1, svc2, svc3) + + _, securityRule13, rule13Found := findSecurityRuleByName(*sg.SecurityRules, expectedRuleName13) + if !rule13Found { + t.Fatalf("Expected security rule %q but it was not present", expectedRuleName13) + } + + _, securityRule2, rule2Found := findSecurityRuleByName(*sg.SecurityRules, expectedRuleName2) + if !rule2Found { + t.Fatalf("Expected security rule %q but it was not present", expectedRuleName2) + } + + expectedDestinationIPCount13 := 2 + if len(*securityRule13.DestinationAddressPrefixes) != expectedDestinationIPCount13 { + t.Errorf("Shared rule %s should have had %d destination IP addresses but had %d", expectedRuleName13, expectedDestinationIPCount13, len(*securityRule13.DestinationAddressPrefixes)) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 4444}, "192.168.77.88", securityRule13) + if err != nil { + t.Errorf("Shared rule %s did not match service IP: %v", expectedRuleName13, err) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 4444}, "192.168.99.11", securityRule13) + if err != nil { + t.Errorf("Shared rule %s did not match service IP: %v", expectedRuleName13, err) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 8888}, "192.168.33.44", securityRule13) + if err == nil { + t.Errorf("Shared rule %s matched wrong service's port and IP", expectedRuleName13) + } + + if securityRule13.Priority == nil { + t.Errorf("Shared rule %s had no priority", expectedRuleName13) + } + + if securityRule13.Access != network.SecurityRuleAccessAllow { + t.Errorf("Shared rule %s did not have Allow access", expectedRuleName13) + } + + if securityRule13.Direction != network.SecurityRuleDirectionInbound { + t.Errorf("Shared rule %s did not have Inbound direction", expectedRuleName13) + } + + expectedDestinationIPCount2 := 1 + if len(*securityRule2.DestinationAddressPrefixes) != expectedDestinationIPCount2 { + t.Errorf("Shared rule %s should have had %d destination IP addresses but had %d", expectedRuleName2, expectedDestinationIPCount2, len(*securityRule2.DestinationAddressPrefixes)) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 8888}, "192.168.33.44", securityRule2) + if err != nil { + t.Errorf("Shared rule %s did not match service IP: %v", expectedRuleName2, err) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 4444}, "192.168.77.88", securityRule2) + if err == nil { + t.Errorf("Shared rule %s matched wrong service's port and IP", expectedRuleName2) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 4444}, "192.168.99.11", securityRule2) + if err == nil { + t.Errorf("Shared rule %s matched wrong service's port and IP", expectedRuleName2) + } +} + +func TestIfServiceSpecifiesSharedRuleAndServiceIsDeletedThenTheServicesPortAndAddressAreRemoved(t *testing.T) { + az := getTestCloud() + + svc1 := getTestService("servicesr1", v1.ProtocolTCP, 80) + svc1.Spec.LoadBalancerIP = "192.168.77.88" + svc1.Annotations[ServiceAnnotationSharedSecurityRule] = "true" + + svc2 := getTestService("servicesr2", v1.ProtocolTCP, 80) + svc2.Spec.LoadBalancerIP = "192.168.33.44" + svc2.Annotations[ServiceAnnotationSharedSecurityRule] = "true" + + expectedRuleName := "shared-TCP-80-Internet" + + sg := getTestSecurityGroup(az) + + sg, err := az.reconcileSecurityGroup(testClusterName, &svc1, to.StringPtr(svc1.Spec.LoadBalancerIP), true) + if err != nil { + t.Errorf("Unexpected error adding svc1: %q", err) + } + + sg, err = az.reconcileSecurityGroup(testClusterName, &svc2, to.StringPtr(svc2.Spec.LoadBalancerIP), true) + if err != nil { + t.Errorf("Unexpected error adding svc2: %q", err) + } + + validateSecurityGroup(t, sg, svc1, svc2) + + sg, err = az.reconcileSecurityGroup(testClusterName, &svc1, to.StringPtr(svc1.Spec.LoadBalancerIP), false) + if err != nil { + t.Errorf("Unexpected error removing svc1: %q", err) + } + + validateSecurityGroup(t, sg, svc2) + + _, securityRule, ruleFound := findSecurityRuleByName(*sg.SecurityRules, expectedRuleName) + if !ruleFound { + t.Fatalf("Expected security rule %q but it was not present", expectedRuleName) + } + + expectedDestinationIPCount := 1 + if len(*securityRule.DestinationAddressPrefixes) != expectedDestinationIPCount { + t.Errorf("Shared rule should have had %d destination IP addresses but had %d", expectedDestinationIPCount, len(*securityRule.DestinationAddressPrefixes)) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 80}, "192.168.33.44", securityRule) + if err != nil { + t.Errorf("Shared rule no longer matched other service IP: %v", err) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 80}, "192.168.77.88", securityRule) + if err == nil { + t.Error("Shared rule was not updated to remove deleted service IP") + } +} + +func TestIfSomeServicesShareARuleAndOneIsDeletedItIsRemovedFromTheRightRule(t *testing.T) { + az := getTestCloud() + + svc1 := getTestService("servicesr1", v1.ProtocolTCP, 4444) + svc1.Spec.LoadBalancerIP = "192.168.77.88" + svc1.Annotations[ServiceAnnotationSharedSecurityRule] = "true" + + svc2 := getTestService("servicesr2", v1.ProtocolTCP, 8888) + svc2.Spec.LoadBalancerIP = "192.168.33.44" + svc2.Annotations[ServiceAnnotationSharedSecurityRule] = "true" + + svc3 := getTestService("servicesr3", v1.ProtocolTCP, 4444) + svc3.Spec.LoadBalancerIP = "192.168.99.11" + svc3.Annotations[ServiceAnnotationSharedSecurityRule] = "true" + + expectedRuleName13 := "shared-TCP-4444-Internet" + expectedRuleName2 := "shared-TCP-8888-Internet" + + sg := getTestSecurityGroup(az) + + sg, err := az.reconcileSecurityGroup(testClusterName, &svc1, to.StringPtr(svc1.Spec.LoadBalancerIP), true) + if err != nil { + t.Errorf("Unexpected error adding svc1: %q", err) + } + + sg, err = az.reconcileSecurityGroup(testClusterName, &svc2, to.StringPtr(svc2.Spec.LoadBalancerIP), true) + if err != nil { + t.Errorf("Unexpected error adding svc2: %q", err) + } + + sg, err = az.reconcileSecurityGroup(testClusterName, &svc3, to.StringPtr(svc3.Spec.LoadBalancerIP), true) + if err != nil { + t.Errorf("Unexpected error adding svc3: %q", err) + } + + validateSecurityGroup(t, sg, svc1, svc2, svc3) + + sg, err = az.reconcileSecurityGroup(testClusterName, &svc1, to.StringPtr(svc1.Spec.LoadBalancerIP), false) + if err != nil { + t.Errorf("Unexpected error removing svc1: %q", err) + } + + validateSecurityGroup(t, sg, svc2, svc3) + + _, securityRule13, rule13Found := findSecurityRuleByName(*sg.SecurityRules, expectedRuleName13) + if !rule13Found { + t.Fatalf("Expected security rule %q but it was not present", expectedRuleName13) + } + + _, securityRule2, rule2Found := findSecurityRuleByName(*sg.SecurityRules, expectedRuleName2) + if !rule2Found { + t.Fatalf("Expected security rule %q but it was not present", expectedRuleName2) + } + + expectedDestinationIPCount13 := 1 + if len(*securityRule13.DestinationAddressPrefixes) != expectedDestinationIPCount13 { + t.Errorf("Shared rule %s should have had %d destination IP addresses but had %d", expectedRuleName13, expectedDestinationIPCount13, len(*securityRule13.DestinationAddressPrefixes)) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 4444}, "192.168.77.88", securityRule13) + if err == nil { + t.Errorf("Shared rule %s should have had svc1 removed but did not", expectedRuleName13) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 4444}, "192.168.99.11", securityRule13) + if err != nil { + t.Errorf("Shared rule %s did not match service IP: %v", expectedRuleName13, err) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 8888}, "192.168.33.44", securityRule13) + if err == nil { + t.Errorf("Shared rule %s matched wrong service's port and IP", expectedRuleName13) + } + + if securityRule13.Priority == nil { + t.Errorf("Shared rule %s had no priority", expectedRuleName13) + } + + if securityRule13.Access != network.SecurityRuleAccessAllow { + t.Errorf("Shared rule %s did not have Allow access", expectedRuleName13) + } + + if securityRule13.Direction != network.SecurityRuleDirectionInbound { + t.Errorf("Shared rule %s did not have Inbound direction", expectedRuleName13) + } + + expectedDestinationIPCount2 := 1 + if len(*securityRule2.DestinationAddressPrefixes) != expectedDestinationIPCount2 { + t.Errorf("Shared rule %s should have had %d destination IP addresses but had %d", expectedRuleName2, expectedDestinationIPCount2, len(*securityRule2.DestinationAddressPrefixes)) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 8888}, "192.168.33.44", securityRule2) + if err != nil { + t.Errorf("Shared rule %s did not match service IP: %v", expectedRuleName2, err) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 4444}, "192.168.77.88", securityRule2) + if err == nil { + t.Errorf("Shared rule %s matched wrong service's port and IP", expectedRuleName2) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 4444}, "192.168.99.11", securityRule2) + if err == nil { + t.Errorf("Shared rule %s matched wrong service's port and IP", expectedRuleName2) + } +} + +func TestIfServiceSpecifiesSharedRuleAndLastServiceIsDeletedThenRuleIsDeleted(t *testing.T) { + az := getTestCloud() + + svc1 := getTestService("servicesr1", v1.ProtocolTCP, 4444) + svc1.Spec.LoadBalancerIP = "192.168.77.88" + svc1.Annotations[ServiceAnnotationSharedSecurityRule] = "true" + + svc2 := getTestService("servicesr2", v1.ProtocolTCP, 8888) + svc2.Spec.LoadBalancerIP = "192.168.33.44" + svc2.Annotations[ServiceAnnotationSharedSecurityRule] = "true" + + svc3 := getTestService("servicesr3", v1.ProtocolTCP, 4444) + svc3.Spec.LoadBalancerIP = "192.168.99.11" + svc3.Annotations[ServiceAnnotationSharedSecurityRule] = "true" + + expectedRuleName13 := "shared-TCP-4444-Internet" + expectedRuleName2 := "shared-TCP-8888-Internet" + + sg := getTestSecurityGroup(az) + + sg, err := az.reconcileSecurityGroup(testClusterName, &svc1, to.StringPtr(svc1.Spec.LoadBalancerIP), true) + if err != nil { + t.Errorf("Unexpected error adding svc1: %q", err) + } + + sg, err = az.reconcileSecurityGroup(testClusterName, &svc2, to.StringPtr(svc2.Spec.LoadBalancerIP), true) + if err != nil { + t.Errorf("Unexpected error adding svc2: %q", err) + } + + sg, err = az.reconcileSecurityGroup(testClusterName, &svc3, to.StringPtr(svc3.Spec.LoadBalancerIP), true) + if err != nil { + t.Errorf("Unexpected error adding svc3: %q", err) + } + + validateSecurityGroup(t, sg, svc1, svc2, svc3) + + sg, err = az.reconcileSecurityGroup(testClusterName, &svc1, to.StringPtr(svc1.Spec.LoadBalancerIP), false) + if err != nil { + t.Errorf("Unexpected error removing svc1: %q", err) + } + + sg, err = az.reconcileSecurityGroup(testClusterName, &svc3, to.StringPtr(svc3.Spec.LoadBalancerIP), false) + if err != nil { + t.Errorf("Unexpected error removing svc3: %q", err) + } + + validateSecurityGroup(t, sg, svc2) + + _, _, rule13Found := findSecurityRuleByName(*sg.SecurityRules, expectedRuleName13) + if rule13Found { + t.Fatalf("Expected security rule %q to have been deleted but it was still present", expectedRuleName13) + } + + _, securityRule2, rule2Found := findSecurityRuleByName(*sg.SecurityRules, expectedRuleName2) + if !rule2Found { + t.Fatalf("Expected security rule %q but it was not present", expectedRuleName2) + } + + expectedDestinationIPCount2 := 1 + if len(*securityRule2.DestinationAddressPrefixes) != expectedDestinationIPCount2 { + t.Errorf("Shared rule %s should have had %d destination IP addresses but had %d", expectedRuleName2, expectedDestinationIPCount2, len(*securityRule2.DestinationAddressPrefixes)) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 8888}, "192.168.33.44", securityRule2) + if err != nil { + t.Errorf("Shared rule %s did not match service IP: %v", expectedRuleName2, err) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 4444}, "192.168.77.88", securityRule2) + if err == nil { + t.Errorf("Shared rule %s matched wrong service's port and IP", expectedRuleName2) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 4444}, "192.168.99.11", securityRule2) + if err == nil { + t.Errorf("Shared rule %s matched wrong service's port and IP", expectedRuleName2) + } +} + +func TestCanCombineSharedAndPrivateRulesInSameGroup(t *testing.T) { + az := getTestCloud() + + svc1 := getTestService("servicesr1", v1.ProtocolTCP, 4444) + svc1.Spec.LoadBalancerIP = "192.168.77.88" + svc1.Annotations[ServiceAnnotationSharedSecurityRule] = "true" + + svc2 := getTestService("servicesr2", v1.ProtocolTCP, 8888) + svc2.Spec.LoadBalancerIP = "192.168.33.44" + svc2.Annotations[ServiceAnnotationSharedSecurityRule] = "true" + + svc3 := getTestService("servicesr3", v1.ProtocolTCP, 4444) + svc3.Spec.LoadBalancerIP = "192.168.99.11" + svc3.Annotations[ServiceAnnotationSharedSecurityRule] = "true" + + svc4 := getTestService("servicesr4", v1.ProtocolTCP, 4444) + svc4.Spec.LoadBalancerIP = "192.168.22.33" + svc4.Annotations[ServiceAnnotationSharedSecurityRule] = "false" + + svc5 := getTestService("servicesr5", v1.ProtocolTCP, 8888) + svc5.Spec.LoadBalancerIP = "192.168.22.33" + svc5.Annotations[ServiceAnnotationSharedSecurityRule] = "false" + + expectedRuleName13 := "shared-TCP-4444-Internet" + expectedRuleName2 := "shared-TCP-8888-Internet" + expectedRuleName4 := getSecurityRuleName(&svc4, v1.ServicePort{Port: 4444, Protocol: v1.ProtocolTCP}, "Internet") + expectedRuleName5 := getSecurityRuleName(&svc5, v1.ServicePort{Port: 8888, Protocol: v1.ProtocolTCP}, "Internet") + + sg := getTestSecurityGroup(az) + + sg, err := az.reconcileSecurityGroup(testClusterName, &svc1, to.StringPtr(svc1.Spec.LoadBalancerIP), true) + if err != nil { + t.Errorf("Unexpected error adding svc1: %q", err) + } + + sg, err = az.reconcileSecurityGroup(testClusterName, &svc2, to.StringPtr(svc2.Spec.LoadBalancerIP), true) + if err != nil { + t.Errorf("Unexpected error adding svc2: %q", err) + } + + sg, err = az.reconcileSecurityGroup(testClusterName, &svc3, to.StringPtr(svc3.Spec.LoadBalancerIP), true) + if err != nil { + t.Errorf("Unexpected error adding svc3: %q", err) + } + + sg, err = az.reconcileSecurityGroup(testClusterName, &svc4, to.StringPtr(svc4.Spec.LoadBalancerIP), true) + if err != nil { + t.Errorf("Unexpected error adding svc4: %q", err) + } + + sg, err = az.reconcileSecurityGroup(testClusterName, &svc5, to.StringPtr(svc5.Spec.LoadBalancerIP), true) + if err != nil { + t.Errorf("Unexpected error adding svc4: %q", err) + } + + validateSecurityGroup(t, sg, svc1, svc2, svc3, svc4, svc5) + + expectedRuleCount := 4 + if len(*sg.SecurityRules) != expectedRuleCount { + t.Errorf("Expected security group to have %d rules but it had %d", expectedRuleCount, len(*sg.SecurityRules)) + } + + _, securityRule13, rule13Found := findSecurityRuleByName(*sg.SecurityRules, expectedRuleName13) + if !rule13Found { + t.Fatalf("Expected security rule %q but it was not present", expectedRuleName13) + } + + _, securityRule2, rule2Found := findSecurityRuleByName(*sg.SecurityRules, expectedRuleName2) + if !rule2Found { + t.Fatalf("Expected security rule %q but it was not present", expectedRuleName2) + } + + _, securityRule4, rule4Found := findSecurityRuleByName(*sg.SecurityRules, expectedRuleName4) + if !rule4Found { + t.Fatalf("Expected security rule %q but it was not present", expectedRuleName4) + } + + _, securityRule5, rule5Found := findSecurityRuleByName(*sg.SecurityRules, expectedRuleName5) + if !rule5Found { + t.Fatalf("Expected security rule %q but it was not present", expectedRuleName5) + } + + expectedDestinationIPCount13 := 2 + if len(*securityRule13.DestinationAddressPrefixes) != expectedDestinationIPCount13 { + t.Errorf("Shared rule %s should have had %d destination IP addresses but had %d", expectedRuleName13, expectedDestinationIPCount13, len(*securityRule13.DestinationAddressPrefixes)) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 4444}, "192.168.77.88", securityRule13) + if err != nil { + t.Errorf("Shared rule %s did not match service IP: %v", expectedRuleName13, err) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 4444}, "192.168.99.11", securityRule13) + if err != nil { + t.Errorf("Shared rule %s did not match service IP: %v", expectedRuleName13, err) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 4444}, "192.168.22.33", securityRule13) + if err == nil { + t.Errorf("Shared rule %s matched wrong (unshared) service's port and IP", expectedRuleName13) + } + + expectedDestinationIPCount2 := 1 + if len(*securityRule2.DestinationAddressPrefixes) != expectedDestinationIPCount2 { + t.Errorf("Shared rule %s should have had %d destination IP addresses but had %d", expectedRuleName2, expectedDestinationIPCount2, len(*securityRule2.DestinationAddressPrefixes)) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 8888}, "192.168.33.44", securityRule2) + if err != nil { + t.Errorf("Shared rule %s did not match service IP: %v", expectedRuleName2, err) + } + + err = securityRuleMatches("Internet", v1.ServicePort{Port: 8888}, "192.168.22.33", securityRule2) + if err == nil { + t.Errorf("Shared rule %s matched wrong (unshared) service's port and IP", expectedRuleName2) + } + + if securityRule4.DestinationAddressPrefixes != nil { + t.Errorf("Expected unshared rule %s to use single destination IP address but used collection", expectedRuleName4) + } + + if securityRule4.DestinationAddressPrefix == nil { + t.Errorf("Expected unshared rule %s to have a destination IP address", expectedRuleName4) + } else { + if !strings.EqualFold(*securityRule4.DestinationAddressPrefix, svc4.Spec.LoadBalancerIP) { + t.Errorf("Expected unshared rule %s to have a destination %s but had %s", expectedRuleName4, svc4.Spec.LoadBalancerIP, *securityRule4.DestinationAddressPrefix) + } + } + + if securityRule5.DestinationAddressPrefixes != nil { + t.Errorf("Expected unshared rule %s to use single destination IP address but used collection", expectedRuleName5) + } + + if securityRule5.DestinationAddressPrefix == nil { + t.Errorf("Expected unshared rule %s to have a destination IP address", expectedRuleName5) + } else { + if !strings.EqualFold(*securityRule5.DestinationAddressPrefix, svc5.Spec.LoadBalancerIP) { + t.Errorf("Expected unshared rule %s to have a destination %s but had %s", expectedRuleName5, svc5.Spec.LoadBalancerIP, *securityRule5.DestinationAddressPrefix) + } + } + + sg, err = az.reconcileSecurityGroup(testClusterName, &svc1, to.StringPtr(svc1.Spec.LoadBalancerIP), false) + if err != nil { + t.Errorf("Unexpected error removing svc1: %q", err) + } + + sg, err = az.reconcileSecurityGroup(testClusterName, &svc5, to.StringPtr(svc5.Spec.LoadBalancerIP), false) + if err != nil { + t.Errorf("Unexpected error removing svc5: %q", err) + } + + _, securityRule13, rule13Found = findSecurityRuleByName(*sg.SecurityRules, expectedRuleName13) + if !rule13Found { + t.Fatalf("Expected security rule %q but it was not present", expectedRuleName13) + } + + _, securityRule2, rule2Found = findSecurityRuleByName(*sg.SecurityRules, expectedRuleName2) + if !rule2Found { + t.Fatalf("Expected security rule %q but it was not present", expectedRuleName2) + } + + _, securityRule4, rule4Found = findSecurityRuleByName(*sg.SecurityRules, expectedRuleName4) + if !rule4Found { + t.Fatalf("Expected security rule %q but it was not present", expectedRuleName4) + } + + _, _, rule5Found = findSecurityRuleByName(*sg.SecurityRules, expectedRuleName5) + if rule5Found { + t.Fatalf("Expected security rule %q to have been removed but it was not present", expectedRuleName5) + } + + expectedDestinationIPCount13 = 1 + if len(*securityRule13.DestinationAddressPrefixes) != expectedDestinationIPCount13 { + t.Errorf("Shared rule %s should have had %d destination IP addresses but had %d", expectedRuleName13, expectedDestinationIPCount13, len(*securityRule13.DestinationAddressPrefixes)) + } +} + +// TODO: sanity check if the same IP address incorrectly gets put in twice? +// (shouldn't happen but...) + +// func TestIfServiceIsEditedFromOwnRuleToSharedRuleThenOwnRuleIsDeletedAndSharedRuleIsCreated(t *testing.T) { +// t.Error() +// } + +// func TestIfServiceIsEditedFromSharedRuleToOwnRuleThenItIsRemovedFromSharedRuleAndOwnRuleIsCreated(t *testing.T) { +// t.Error() +// } diff --git a/pkg/cloudprovider/providers/azure/azure_util.go b/pkg/cloudprovider/providers/azure/azure_util.go index bfd3e08bce9..c7fcef10ebd 100644 --- a/pkg/cloudprovider/providers/azure/azure_util.go +++ b/pkg/cloudprovider/providers/azure/azure_util.go @@ -21,6 +21,7 @@ import ( "fmt" "hash/crc32" "regexp" + "sort" "strconv" "strings" @@ -29,8 +30,11 @@ import ( "github.com/Azure/azure-sdk-for-go/arm/compute" "github.com/Azure/azure-sdk-for-go/arm/network" + "github.com/Azure/go-autorest/autorest/to" "github.com/golang/glog" "k8s.io/apimachinery/pkg/types" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/sets" ) const ( @@ -44,8 +48,15 @@ const ( loadBalancerRuleIDTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/loadBalancers/%s/loadBalancingRules/%s" loadBalancerProbeIDTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/loadBalancers/%s/probes/%s" securityRuleIDTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/networkSecurityGroups/%s/securityRules/%s" + + // InternalLoadBalancerNameSuffix is load balancer posfix + InternalLoadBalancerNameSuffix = "-internal" + + // nodeLabelRole specifies the role of a node + nodeLabelRole = "kubernetes.io/role" ) +var errNotInVMSet = errors.New("vm is not in the vmset") var providerIDRE = regexp.MustCompile(`^` + CloudProviderName + `://(?:.*)/Microsoft.Compute/virtualMachines/(.+)$`) // returns the full identifier of a machine @@ -116,6 +127,50 @@ func (az *Cloud) getSecurityRuleID(securityRuleName string) string { securityRuleName) } +// returns the full identifier of a publicIPAddress. +func (az *Cloud) getpublicIPAddressID(pipName string) string { + return fmt.Sprintf( + publicIPAddressIDTemplate, + az.SubscriptionID, + az.ResourceGroup, + pipName) +} + +func (az *Cloud) mapLoadBalancerNameToVMSet(lbName string, clusterName string) (vmSetName string) { + vmSetName = strings.TrimSuffix(lbName, InternalLoadBalancerNameSuffix) + if strings.EqualFold(clusterName, lbName) { + vmSetName = az.vmSet.GetPrimaryVMSetName() + } + + return vmSetName +} + +// For a load balancer, all frontend ip should reference either a subnet or publicIpAddress. +// Thus Azure do not allow mixed type (public and internal) load balancer. +// So we'd have a separate name for internal load balancer. +// This would be the name for Azure LoadBalancer resource. +func (az *Cloud) getLoadBalancerName(clusterName string, vmSetName string, isInternal bool) string { + lbNamePrefix := vmSetName + if strings.EqualFold(vmSetName, az.vmSet.GetPrimaryVMSetName()) { + lbNamePrefix = clusterName + } + if isInternal { + return fmt.Sprintf("%s%s", lbNamePrefix, InternalLoadBalancerNameSuffix) + } + return lbNamePrefix +} + +// isMasterNode returns returns true is the node has a master role label. +// The master role is determined by looking for: +// * a kubernetes.io/role="master" label +func isMasterNode(node *v1.Node) bool { + if val, ok := node.Labels[nodeLabelRole]; ok && val == "master" { + return true + } + + return false +} + // returns the deepest child's identifier from a full identifier string. func getLastSegment(ID string) (string, error) { parts := strings.Split(ID, "/") @@ -179,16 +234,8 @@ func getPrimaryIPConfig(nic network.Interface) (*network.InterfaceIPConfiguratio return nil, fmt.Errorf("failed to determine the determine primary ipconfig. nicname=%q", *nic.Name) } -// For a load balancer, all frontend ip should reference either a subnet or publicIpAddress. -// Thus Azure do not allow mixed type (public and internal) load balancer. -// So we'd have a separate name for internal load balancer. -// This would be the name for Azure LoadBalancer resource. -func getLoadBalancerName(clusterName string, isInternal bool) string { - if isInternal { - return fmt.Sprintf("%s-internal", clusterName) - } - - return clusterName +func isInternalLoadBalancer(lb *network.LoadBalancer) bool { + return strings.HasSuffix(*lb.Name, InternalLoadBalancerNameSuffix) } func getBackendPoolName(clusterName string) string { @@ -203,6 +250,10 @@ func getLoadBalancerRuleName(service *v1.Service, port v1.ServicePort, subnetNam } func getSecurityRuleName(service *v1.Service, port v1.ServicePort, sourceAddrPrefix string) string { + if useSharedSecurityRule(service) { + safePrefix := strings.Replace(sourceAddrPrefix, "/", "_", -1) + return fmt.Sprintf("shared-%s-%d-%s", port.Protocol, port.Port, safePrefix) + } safePrefix := strings.Replace(sourceAddrPrefix, "/", "_", -1) return fmt.Sprintf("%s-%s-%d-%s", getRulePrefix(service), port.Protocol, port.Port, safePrefix) } @@ -261,54 +312,7 @@ outer: } func (az *Cloud) getIPForMachine(nodeName types.NodeName) (string, error) { - az.operationPollRateLimiter.Accept() - machine, exists, err := az.getVirtualMachine(nodeName) - if !exists { - return "", cloudprovider.InstanceNotFound - } - if err != nil { - glog.Errorf("error: az.getIPForMachine(%s), az.getVirtualMachine(%s), err=%v", nodeName, nodeName, err) - return "", err - } - - nicID, err := getPrimaryInterfaceID(machine) - if err != nil { - glog.Errorf("error: az.getIPForMachine(%s), getPrimaryInterfaceID(%v), err=%v", nodeName, machine, err) - return "", err - } - - nicName, err := getLastSegment(nicID) - if err != nil { - glog.Errorf("error: az.getIPForMachine(%s), getLastSegment(%s), err=%v", nodeName, nicID, err) - return "", err - } - - az.operationPollRateLimiter.Accept() - glog.V(10).Infof("InterfacesClient.Get(%q): start", nicName) - nic, err := az.InterfacesClient.Get(az.ResourceGroup, nicName, "") - glog.V(10).Infof("InterfacesClient.Get(%q): end", nicName) - if err != nil { - glog.Errorf("error: az.getIPForMachine(%s), az.InterfacesClient.Get(%s, %s, %s), err=%v", nodeName, az.ResourceGroup, nicName, "", err) - return "", err - } - - ipConfig, err := getPrimaryIPConfig(nic) - if err != nil { - glog.Errorf("error: az.getIPForMachine(%s), getPrimaryIPConfig(%v), err=%v", nodeName, nic, err) - return "", err - } - - targetIP := *ipConfig.PrivateIPAddress - return targetIP, nil -} - -// splitProviderID converts a providerID to a NodeName. -func splitProviderID(providerID string) (types.NodeName, error) { - matches := providerIDRE.FindStringSubmatch(providerID) - if len(matches) != 2 { - return "", errors.New("error splitting providerID") - } - return types.NodeName(matches[1]), nil + return az.vmSet.GetIPByNodeName(string(nodeName), "") } var polyTable = crc32.MakeTable(crc32.Koopman) @@ -365,3 +369,326 @@ func ExtractDiskData(diskData interface{}) (provisioningState string, diskState } return provisioningState, diskState, nil } + +// availabilitySet implements VMSet interface for Azure availability sets. +type availabilitySet struct { + *Cloud +} + +// newStandardSet creates a new availabilitySet. +func newAvailabilitySet(az *Cloud) VMSet { + return &availabilitySet{ + Cloud: az, + } +} + +// GetInstanceIDByNodeName gets the cloud provider ID by node name. +// It must return ("", cloudprovider.InstanceNotFound) if the instance does +// not exist or is no longer running. +func (as *availabilitySet) GetInstanceIDByNodeName(name string) (string, error) { + var machine compute.VirtualMachine + var err error + + as.operationPollRateLimiter.Accept() + machine, err = as.getVirtualMachine(types.NodeName(name)) + if err != nil { + if as.CloudProviderBackoff { + glog.V(2).Infof("InstanceID(%s) backing off", name) + machine, err = as.GetVirtualMachineWithRetry(types.NodeName(name)) + if err != nil { + glog.V(2).Infof("InstanceID(%s) abort backoff", name) + return "", err + } + } else { + return "", err + } + } + return *machine.ID, nil +} + +// GetNodeNameByProviderID gets the node name by provider ID. +func (as *availabilitySet) GetNodeNameByProviderID(providerID string) (types.NodeName, error) { + // NodeName is part of providerID for standard instances. + matches := providerIDRE.FindStringSubmatch(providerID) + if len(matches) != 2 { + return "", errors.New("error splitting providerID") + } + + return types.NodeName(matches[1]), nil +} + +// GetInstanceTypeByNodeName gets the instance type by node name. +func (as *availabilitySet) GetInstanceTypeByNodeName(name string) (string, error) { + machine, err := as.getVirtualMachine(types.NodeName(name)) + if err != nil { + glog.Errorf("error: as.GetInstanceTypeByNodeName(%s), as.getVirtualMachine(%s) err=%v", name, name, err) + return "", err + } + + return string(machine.HardwareProfile.VMSize), nil +} + +// GetZoneByNodeName gets zone from instance view. +func (as *availabilitySet) GetZoneByNodeName(name string) (cloudprovider.Zone, error) { + vm, err := as.getVirtualMachine(types.NodeName(name)) + if err != nil { + return cloudprovider.Zone{}, err + } + + failureDomain := strconv.Itoa(int(*vm.VirtualMachineProperties.InstanceView.PlatformFaultDomain)) + zone := cloudprovider.Zone{ + FailureDomain: failureDomain, + Region: *(vm.Location), + } + return zone, nil +} + +// GetPrimaryVMSetName returns the VM set name depending on the configured vmType. +// It returns config.PrimaryScaleSetName for vmss and config.PrimaryAvailabilitySetName for standard vmType. +func (as *availabilitySet) GetPrimaryVMSetName() string { + return as.Config.PrimaryAvailabilitySetName +} + +// GetIPByNodeName gets machine IP by node name. +func (as *availabilitySet) GetIPByNodeName(name, vmSetName string) (string, error) { + nic, err := as.GetPrimaryInterface(name, vmSetName) + if err != nil { + return "", err + } + + ipConfig, err := getPrimaryIPConfig(nic) + if err != nil { + glog.Errorf("error: as.GetIPByNodeName(%s), getPrimaryIPConfig(%v), err=%v", name, nic, err) + return "", err + } + + targetIP := *ipConfig.PrivateIPAddress + return targetIP, nil +} + +// getAgentPoolAvailabiliySets lists the virtual machines for for the resource group and then builds +// a list of availability sets that match the nodes available to k8s. +func (as *availabilitySet) getAgentPoolAvailabiliySets(nodes []*v1.Node) (agentPoolAvailabilitySets *[]string, err error) { + vms, err := as.VirtualMachineClientListWithRetry() + if err != nil { + glog.Errorf("as.getNodeAvailabilitySet - VirtualMachineClientListWithRetry failed, err=%v", err) + return nil, err + } + vmNameToAvailabilitySetID := make(map[string]string, len(vms)) + for vmx := range vms { + vm := vms[vmx] + if vm.AvailabilitySet != nil { + vmNameToAvailabilitySetID[*vm.Name] = *vm.AvailabilitySet.ID + } + } + availabilitySetIDs := sets.NewString() + agentPoolAvailabilitySets = &[]string{} + for nx := range nodes { + nodeName := (*nodes[nx]).Name + if isMasterNode(nodes[nx]) { + continue + } + asID, ok := vmNameToAvailabilitySetID[nodeName] + if !ok { + glog.Errorf("as.getNodeAvailabilitySet - Node(%s) has no availability sets", nodeName) + return nil, fmt.Errorf("Node (%s) - has no availability sets", nodeName) + } + if availabilitySetIDs.Has(asID) { + // already added in the list + continue + } + asName, err := getLastSegment(asID) + if err != nil { + glog.Errorf("as.getNodeAvailabilitySet - Node (%s)- getLastSegment(%s), err=%v", nodeName, asID, err) + return nil, err + } + // AvailabilitySet ID is currently upper cased in a indeterministic way + // We want to keep it lower case, before the ID get fixed + asName = strings.ToLower(asName) + + *agentPoolAvailabilitySets = append(*agentPoolAvailabilitySets, asName) + } + + return agentPoolAvailabilitySets, nil +} + +// GetVMSetNames selects all possible availability sets or scale sets +// (depending vmType configured) for service load balancer, if the service has +// no loadbalancer mode annotaion returns the primary VMSet. If service annotation +// for loadbalancer exists then return the eligible VMSet. +func (as *availabilitySet) GetVMSetNames(service *v1.Service, nodes []*v1.Node) (availabilitySetNames *[]string, err error) { + hasMode, isAuto, serviceAvailabilitySetNames := getServiceLoadBalancerMode(service) + if !hasMode { + // no mode specified in service annotation default to PrimaryAvailabilitySetName + availabilitySetNames = &[]string{as.Config.PrimaryAvailabilitySetName} + return availabilitySetNames, nil + } + availabilitySetNames, err = as.getAgentPoolAvailabiliySets(nodes) + if err != nil { + glog.Errorf("as.GetVMSetNames - getAgentPoolAvailabiliySets failed err=(%v)", err) + return nil, err + } + if len(*availabilitySetNames) == 0 { + glog.Errorf("as.GetVMSetNames - No availability sets found for nodes in the cluster, node count(%d)", len(nodes)) + return nil, fmt.Errorf("No availability sets found for nodes, node count(%d)", len(nodes)) + } + // sort the list to have deterministic selection + sort.Strings(*availabilitySetNames) + if !isAuto { + if serviceAvailabilitySetNames == nil || len(serviceAvailabilitySetNames) == 0 { + return nil, fmt.Errorf("service annotation for LoadBalancerMode is empty, it should have __auto__ or availability sets value") + } + // validate availability set exists + var found bool + for sasx := range serviceAvailabilitySetNames { + for asx := range *availabilitySetNames { + if strings.EqualFold((*availabilitySetNames)[asx], serviceAvailabilitySetNames[sasx]) { + found = true + serviceAvailabilitySetNames[sasx] = (*availabilitySetNames)[asx] + break + } + } + if !found { + glog.Errorf("as.GetVMSetNames - Availability set (%s) in service annotation not found", serviceAvailabilitySetNames[sasx]) + return nil, fmt.Errorf("availability set (%s) - not found", serviceAvailabilitySetNames[sasx]) + } + } + availabilitySetNames = &serviceAvailabilitySetNames + } + + return availabilitySetNames, nil +} + +// GetPrimaryInterface gets machine primary network interface by node name and vmSet. +func (as *availabilitySet) GetPrimaryInterface(nodeName, vmSetName string) (network.Interface, error) { + var machine compute.VirtualMachine + + as.operationPollRateLimiter.Accept() + machine, err := as.GetVirtualMachineWithRetry(types.NodeName(nodeName)) + if err != nil { + glog.V(2).Infof("GetPrimaryInterface(%s, %s) abort backoff", nodeName, vmSetName) + return network.Interface{}, err + } + + primaryNicID, err := getPrimaryInterfaceID(machine) + if err != nil { + return network.Interface{}, err + } + nicName, err := getLastSegment(primaryNicID) + if err != nil { + return network.Interface{}, err + } + + // Check availability set + if vmSetName != "" { + expectedAvailabilitySetName := as.getAvailabilitySetID(vmSetName) + if machine.AvailabilitySet == nil || !strings.EqualFold(*machine.AvailabilitySet.ID, expectedAvailabilitySetName) { + glog.V(3).Infof( + "GetPrimaryInterface: nic (%s) is not in the availabilitySet(%s)", nicName, vmSetName) + return network.Interface{}, errNotInVMSet + } + } + + as.operationPollRateLimiter.Accept() + glog.V(10).Infof("InterfacesClient.Get(%q): start", nicName) + nic, err := as.InterfacesClient.Get(as.ResourceGroup, nicName, "") + glog.V(10).Infof("InterfacesClient.Get(%q): end", nicName) + if err != nil { + return network.Interface{}, err + } + + return nic, nil +} + +// ensureHostInPool ensures the given VM's Primary NIC's Primary IP Configuration is +// participating in the specified LoadBalancer Backend Pool. +func (as *availabilitySet) ensureHostInPool(serviceName string, nodeName types.NodeName, backendPoolID string, vmSetName string) error { + vmName := mapNodeNameToVMName(nodeName) + nic, err := as.GetPrimaryInterface(vmName, vmSetName) + if err != nil { + if err == errNotInVMSet { + glog.V(3).Infof("ensureHostInPool skips node %s because it is not in the vmSet %s", nodeName, vmSetName) + return nil + } + + glog.Errorf("error: az.ensureHostInPool(%s), az.vmSet.GetPrimaryInterface.Get(%s, %s), err=%v", nodeName, vmName, vmSetName, err) + return err + } + + var primaryIPConfig *network.InterfaceIPConfiguration + primaryIPConfig, err = getPrimaryIPConfig(nic) + if err != nil { + return err + } + + foundPool := false + newBackendPools := []network.BackendAddressPool{} + if primaryIPConfig.LoadBalancerBackendAddressPools != nil { + newBackendPools = *primaryIPConfig.LoadBalancerBackendAddressPools + } + for _, existingPool := range newBackendPools { + if strings.EqualFold(backendPoolID, *existingPool.ID) { + foundPool = true + break + } + } + if !foundPool { + newBackendPools = append(newBackendPools, + network.BackendAddressPool{ + ID: to.StringPtr(backendPoolID), + }) + + primaryIPConfig.LoadBalancerBackendAddressPools = &newBackendPools + + nicName := *nic.Name + glog.V(3).Infof("nicupdate(%s): nic(%s) - updating", serviceName, nicName) + as.operationPollRateLimiter.Accept() + glog.V(10).Infof("InterfacesClient.CreateOrUpdate(%q): start", *nic.Name) + respChan, errChan := as.InterfacesClient.CreateOrUpdate(as.ResourceGroup, *nic.Name, nic, nil) + resp := <-respChan + err := <-errChan + glog.V(10).Infof("InterfacesClient.CreateOrUpdate(%q): end", *nic.Name) + if as.CloudProviderBackoff && shouldRetryAPIRequest(resp.Response, err) { + glog.V(2).Infof("nicupdate(%s) backing off: nic(%s) - updating, err=%v", serviceName, nicName, err) + retryErr := as.CreateOrUpdateInterfaceWithRetry(nic) + if retryErr != nil { + err = retryErr + glog.V(2).Infof("nicupdate(%s) abort backoff: nic(%s) - updating", serviceName, nicName) + } + } + if err != nil { + return err + } + } + return nil +} + +// EnsureHostsInPool ensures the given Node's primary IP configurations are +// participating in the specified LoadBalancer Backend Pool. +func (as *availabilitySet) EnsureHostsInPool(serviceName string, nodes []*v1.Node, backendPoolID string, vmSetName string) error { + hostUpdates := make([]func() error, len(nodes)) + for i, node := range nodes { + localNodeName := node.Name + f := func() error { + err := as.ensureHostInPool(serviceName, types.NodeName(localNodeName), backendPoolID, vmSetName) + if err != nil { + return fmt.Errorf("ensure(%s): backendPoolID(%s) - failed to ensure host in pool: %q", serviceName, backendPoolID, err) + } + return nil + } + hostUpdates[i] = f + } + + errs := utilerrors.AggregateGoroutines(hostUpdates...) + if errs != nil { + return utilerrors.Flatten(errs) + } + + return nil +} + +// EnsureBackendPoolDeleted ensures the loadBalancer backendAddressPools deleted from the specified vmSet. +func (as *availabilitySet) EnsureBackendPoolDeleted(poolID, vmSetName string) error { + // Do nothing for availability set. + return nil +} diff --git a/pkg/cloudprovider/providers/azure/azure_util_cache.go b/pkg/cloudprovider/providers/azure/azure_util_cache.go new file mode 100644 index 00000000000..8e416601ad8 --- /dev/null +++ b/pkg/cloudprovider/providers/azure/azure_util_cache.go @@ -0,0 +1,81 @@ +/* +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 azure + +import ( + "sync" + "time" + + "k8s.io/client-go/tools/cache" +) + +type timedcacheEntry struct { + key string + data interface{} +} + +type timedcache struct { + store cache.Store + lock sync.Mutex +} + +// ttl time.Duration +func newTimedcache(ttl time.Duration) timedcache { + return timedcache{ + store: cache.NewTTLStore(cacheKeyFunc, ttl), + } +} + +func cacheKeyFunc(obj interface{}) (string, error) { + return obj.(*timedcacheEntry).key, nil +} + +func (t *timedcache) GetOrCreate(key string, createFunc func() interface{}) (interface{}, error) { + entry, exists, err := t.store.GetByKey(key) + if err != nil { + return nil, err + } + if exists { + return (entry.(*timedcacheEntry)).data, nil + } + + t.lock.Lock() + defer t.lock.Unlock() + entry, exists, err = t.store.GetByKey(key) + if err != nil { + return nil, err + } + if exists { + return (entry.(*timedcacheEntry)).data, nil + } + + if createFunc == nil { + return nil, nil + } + created := createFunc() + t.store.Add(&timedcacheEntry{ + key: key, + data: created, + }) + return created, nil +} + +func (t *timedcache) Delete(key string) { + _ = t.store.Delete(&timedcacheEntry{ + key: key, + }) +} diff --git a/pkg/cloudprovider/providers/azure/azure_util_cache_test.go b/pkg/cloudprovider/providers/azure/azure_util_cache_test.go new file mode 100644 index 00000000000..0ac26d2e98a --- /dev/null +++ b/pkg/cloudprovider/providers/azure/azure_util_cache_test.go @@ -0,0 +1,96 @@ +/* +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 azure + +import ( + "sync/atomic" + "testing" + "time" +) + +func TestCacheReturnsSameObject(t *testing.T) { + type cacheTestingStruct struct{} + c := newTimedcache(1 * time.Minute) + o1 := cacheTestingStruct{} + get1, _ := c.GetOrCreate("b1", func() interface{} { + return o1 + }) + o2 := cacheTestingStruct{} + get2, _ := c.GetOrCreate("b1", func() interface{} { + return o2 + }) + if get1 != get2 { + t.Error("Get not equal") + } +} + +func TestCacheCallsCreateFuncOnce(t *testing.T) { + var callsCount uint32 + f1 := func() interface{} { + atomic.AddUint32(&callsCount, 1) + return 1 + } + c := newTimedcache(500 * time.Millisecond) + for index := 0; index < 20; index++ { + _, _ = c.GetOrCreate("b1", f1) + } + + if callsCount != 1 { + t.Error("Count not match") + } + time.Sleep(500 * time.Millisecond) + c.GetOrCreate("b1", f1) + if callsCount != 2 { + t.Error("Count not match") + } +} + +func TestCacheExpires(t *testing.T) { + f1 := func() interface{} { + return 1 + } + c := newTimedcache(500 * time.Millisecond) + get1, _ := c.GetOrCreate("b1", f1) + if get1 != 1 { + t.Error("Value not equal") + } + time.Sleep(500 * time.Millisecond) + get1, _ = c.GetOrCreate("b1", nil) + if get1 != nil { + t.Error("value not expired") + } +} + +func TestCacheDelete(t *testing.T) { + f1 := func() interface{} { + return 1 + } + c := newTimedcache(500 * time.Millisecond) + get1, _ := c.GetOrCreate("b1", f1) + if get1 != 1 { + t.Error("Value not equal") + } + get1, _ = c.GetOrCreate("b1", nil) + if get1 != 1 { + t.Error("Value not equal") + } + c.Delete("b1") + get1, _ = c.GetOrCreate("b1", nil) + if get1 != nil { + t.Error("value not deleted") + } +} diff --git a/pkg/cloudprovider/providers/azure/azure_util_test.go b/pkg/cloudprovider/providers/azure/azure_util_test.go new file mode 100644 index 00000000000..cac803c2eb0 --- /dev/null +++ b/pkg/cloudprovider/providers/azure/azure_util_test.go @@ -0,0 +1,53 @@ +/* +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 azure + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetScaleSetVMInstanceID(t *testing.T) { + tests := []struct { + msg string + machineName string + expectError bool + expectedInstanceID string + }{{ + msg: "invalid vmss instance name", + machineName: "vmvm", + expectError: true, + }, + { + msg: "valid vmss instance name", + machineName: "vm00000Z", + expectError: false, + expectedInstanceID: "35", + }, + } + + for i, test := range tests { + instanceID, err := getScaleSetVMInstanceID(test.machineName) + if test.expectError { + assert.Error(t, err, fmt.Sprintf("TestCase[%d]: %s", i, test.msg)) + } else { + assert.Equal(t, test.expectedInstanceID, instanceID, fmt.Sprintf("TestCase[%d]: %s", i, test.msg)) + } + } +} diff --git a/pkg/cloudprovider/providers/azure/azure_util_vmss.go b/pkg/cloudprovider/providers/azure/azure_util_vmss.go new file mode 100644 index 00000000000..c9b827c308e --- /dev/null +++ b/pkg/cloudprovider/providers/azure/azure_util_vmss.go @@ -0,0 +1,962 @@ +/* +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 azure + +import ( + "errors" + "fmt" + "regexp" + "sort" + "strconv" + "strings" + "sync" + "time" + + "github.com/Azure/azure-sdk-for-go/arm/compute" + "github.com/Azure/azure-sdk-for-go/arm/network" + "github.com/Azure/go-autorest/autorest/to" + "github.com/golang/glog" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/kubernetes/pkg/cloudprovider" +) + +var ( + // ErrorNotVmssInstance indicates an instance is not belongint to any vmss. + ErrorNotVmssInstance = errors.New("not a vmss instance") + + scaleSetNameRE = regexp.MustCompile(`.*/subscriptions/(?:.*)/Microsoft.Compute/virtualMachineScaleSets/(.+)/virtualMachines(?:.*)`) +) + +// scaleSetVMInfo includes basic information of a virtual machine. +type scaleSetVMInfo struct { + // The ID of the machine. + ID string + // Instance ID of the machine (only for scale sets vm). + InstanceID string + // Node name of the machine. + NodeName string + // Set name of the machine. + ScaleSetName string + // The type of the machine. + Type string + // The region of the machine. + Region string + // Primary interface ID of the machine. + PrimaryInterfaceID string + // Fault domain of the machine. + FaultDomain string +} + +// scaleSet implements VMSet interface for Azure scale set. +type scaleSet struct { + *Cloud + + // availabilitySet is also required for scaleSet because some instances + // (e.g. master nodes) may not belong to any scale sets. + availabilitySet VMSet + + cacheMutex sync.Mutex + // A local cache of scale sets. The key is scale set name and the value is a + // list of virtual machines belonging to the scale set. + cache map[string][]scaleSetVMInfo + availabilitySetNodesCache sets.String +} + +// newScaleSet creates a new scaleSet. +func newScaleSet(az *Cloud) VMSet { + ss := &scaleSet{ + Cloud: az, + availabilitySet: newAvailabilitySet(az), + availabilitySetNodesCache: sets.NewString(), + cache: make(map[string][]scaleSetVMInfo), + } + + go wait.Until(func() { + ss.cacheMutex.Lock() + defer ss.cacheMutex.Unlock() + + if err := ss.updateCache(); err != nil { + glog.Errorf("updateCache failed: %v", err) + } + }, 5*time.Minute, wait.NeverStop) + + return ss +} + +// updateCache updates scale sets cache. It should be called within a lock. +func (ss *scaleSet) updateCache() error { + scaleSetNames, err := ss.listScaleSetsWithRetry() + if err != nil { + return err + } + + localCache := make(map[string][]scaleSetVMInfo) + for _, scaleSetName := range scaleSetNames { + if _, ok := localCache[scaleSetName]; !ok { + localCache[scaleSetName] = make([]scaleSetVMInfo, 0) + } + vms, err := ss.listScaleSetVMsWithRetry(scaleSetName) + if err != nil { + return err + } + + for _, vm := range vms { + nodeName := "" + if vm.OsProfile != nil && vm.OsProfile.ComputerName != nil { + nodeName = *vm.OsProfile.ComputerName + } + + vmSize := "" + if vm.Sku != nil && vm.Sku.Name != nil { + vmSize = *vm.Sku.Name + } + + primaryInterfaceID, err := ss.getPrimaryInterfaceID(vm) + if err != nil { + glog.Errorf("getPrimaryInterfaceID for %s failed: %v", nodeName, err) + return err + } + + faultDomain := "" + if vm.InstanceView != nil && vm.InstanceView.PlatformFaultDomain != nil { + faultDomain = strconv.Itoa(int(*vm.InstanceView.PlatformFaultDomain)) + } + + localCache[scaleSetName] = append(localCache[scaleSetName], scaleSetVMInfo{ + ID: *vm.ID, + Type: vmSize, + NodeName: nodeName, + FaultDomain: faultDomain, + ScaleSetName: scaleSetName, + Region: *vm.Location, + InstanceID: *vm.InstanceID, + PrimaryInterfaceID: primaryInterfaceID, + }) + } + } + + // Only update cache after all steps are success. + ss.cache = localCache + + return nil +} + +// getCachedVirtualMachine gets virtualMachine by nodeName from cache. +// It returns cloudprovider.InstanceNotFound if node does not belong to any scale sets. +func (ss *scaleSet) getCachedVirtualMachine(nodeName string) (scaleSetVMInfo, error) { + ss.cacheMutex.Lock() + defer ss.cacheMutex.Unlock() + + getVMFromCache := func(nodeName string) (scaleSetVMInfo, bool) { + glog.V(8).Infof("Getting scaleSetVMInfo for %q from cache %v", nodeName, ss.cache) + for scaleSetName := range ss.cache { + for _, vm := range ss.cache[scaleSetName] { + if vm.NodeName == nodeName { + return vm, true + } + } + } + + return scaleSetVMInfo{}, false + } + + vm, found := getVMFromCache(nodeName) + if found { + return vm, nil + } + + // Known node not managed by scale sets. + if ss.availabilitySetNodesCache.Has(nodeName) { + glog.V(10).Infof("Found node %q in availabilitySetNodesCache", nodeName) + return scaleSetVMInfo{}, cloudprovider.InstanceNotFound + } + + // Update cache and try again. + if err := ss.updateCache(); err != nil { + glog.Errorf("updateCache failed with error: %v", err) + return scaleSetVMInfo{}, err + } + vm, found = getVMFromCache(nodeName) + if found { + return vm, nil + } + + // Node still not found, assuming it is not managed by scale sets. + glog.V(8).Infof("Node %q doesn't belong to any scale sets, adding it to availabilitySetNodesCache", nodeName) + ss.availabilitySetNodesCache.Insert(nodeName) + return scaleSetVMInfo{}, cloudprovider.InstanceNotFound +} + +// getCachedVirtualMachineByInstanceID gets scaleSetVMInfo from cache. +// The node must belong to one of scale sets. +func (ss *scaleSet) getCachedVirtualMachineByInstanceID(scaleSetName, instanceID string) (scaleSetVMInfo, error) { + ss.cacheMutex.Lock() + defer ss.cacheMutex.Unlock() + + getVMByID := func(scaleSetName, instanceID string) (scaleSetVMInfo, bool) { + glog.V(8).Infof("Getting scaleSetVMInfo with scaleSetName: %q and instanceID %q from cache %v", scaleSetName, instanceID, ss.cache) + vms, ok := ss.cache[scaleSetName] + if !ok { + glog.V(4).Infof("scale set (%s) not found", scaleSetName) + return scaleSetVMInfo{}, false + } + + for _, vm := range vms { + if vm.InstanceID == instanceID { + glog.V(4).Infof("getCachedVirtualMachineByInstanceID gets vm (%s) by instanceID (%s) within scale set (%s)", vm.NodeName, instanceID, scaleSetName) + return vm, true + } + } + + glog.V(4).Infof("instanceID (%s) not found in scale set (%s)", instanceID, scaleSetName) + return scaleSetVMInfo{}, false + } + + vm, found := getVMByID(scaleSetName, instanceID) + if found { + return vm, nil + } + + // Update cache and try again. + if err := ss.updateCache(); err != nil { + glog.Errorf("updateCache failed with error: %v", err) + return scaleSetVMInfo{}, err + } + vm, found = getVMByID(scaleSetName, instanceID) + if found { + return vm, nil + } + + return scaleSetVMInfo{}, cloudprovider.InstanceNotFound +} + +// GetInstanceIDByNodeName gets the cloud provider ID by node name. +// It must return ("", cloudprovider.InstanceNotFound) if the instance does +// not exist or is no longer running. +func (ss *scaleSet) GetInstanceIDByNodeName(name string) (string, error) { + vm, err := ss.getCachedVirtualMachine(name) + if err != nil { + if err == cloudprovider.InstanceNotFound { + glog.V(4).Infof("GetInstanceIDByNodeName: node %q is not found in scale sets, assuming it is managed by availability set", name) + + // Retry with standard type because master nodes may not belong to any vmss. + // TODO: find a better way to identify the type of VM. + return ss.availabilitySet.GetInstanceIDByNodeName(name) + } + + return "", err + } + + return vm.ID, nil +} + +// GetNodeNameByProviderID gets the node name by provider ID. +func (ss *scaleSet) GetNodeNameByProviderID(providerID string) (types.NodeName, error) { + // NodeName is not part of providerID for vmss instances. + scaleSetName, err := extractScaleSetNameByVMID(providerID) + if err != nil { + glog.V(4).Infof("Can not extract scale set name from providerID (%s), assuming it is mananaged by availability set: %v", providerID, err) + return ss.availabilitySet.GetNodeNameByProviderID(providerID) + } + + instanceID, err := getLastSegment(providerID) + if err != nil { + glog.V(4).Infof("Can not extract instanceID from providerID (%s), assuming it is mananaged by availability set: %v", providerID, err) + return ss.availabilitySet.GetNodeNameByProviderID(providerID) + } + + vm, err := ss.getCachedVirtualMachineByInstanceID(scaleSetName, instanceID) + if err != nil { + return "", err + } + + return types.NodeName(vm.NodeName), nil +} + +// GetInstanceTypeByNodeName gets the instance type by node name. +func (ss *scaleSet) GetInstanceTypeByNodeName(name string) (string, error) { + vm, err := ss.getCachedVirtualMachine(name) + if err != nil { + if err == cloudprovider.InstanceNotFound { + glog.V(4).Infof("GetInstanceTypeByNodeName: node %q is not found in scale sets, assuming it is managed by availability set", name) + + // Retry with standard type because master nodes may not belong to any vmss. + // TODO: find a better way to identify the type of VM. + return ss.availabilitySet.GetInstanceTypeByNodeName(name) + } + + return "", err + } + + return vm.Type, nil +} + +// GetZoneByNodeName gets cloudprovider.Zone by node name. +func (ss *scaleSet) GetZoneByNodeName(name string) (cloudprovider.Zone, error) { + vm, err := ss.getCachedVirtualMachine(name) + if err != nil { + if err == cloudprovider.InstanceNotFound { + glog.V(4).Infof("GetZoneByNodeName: node %q is not found in scale sets, assuming it is managed by availability set", name) + // Retry with standard type because master nodes may not belong to any vmss. + // TODO: find a better way to identify the type of VM. + return ss.availabilitySet.GetZoneByNodeName(name) + } + return cloudprovider.Zone{}, err + } + + return cloudprovider.Zone{ + FailureDomain: vm.FaultDomain, + Region: vm.Region, + }, nil +} + +// GetPrimaryVMSetName returns the VM set name depending on the configured vmType. +// It returns config.PrimaryScaleSetName for vmss and config.PrimaryAvailabilitySetName for standard vmType. +func (ss *scaleSet) GetPrimaryVMSetName() string { + return ss.Config.PrimaryScaleSetName +} + +// GetIPByNodeName gets machine IP by node name. +func (ss *scaleSet) GetIPByNodeName(nodeName, vmSetName string) (string, error) { + nic, err := ss.GetPrimaryInterface(nodeName, vmSetName) + if err != nil { + glog.Errorf("error: ss.GetIPByNodeName(%s), GetPrimaryInterface(%q, %q), err=%v", nodeName, nodeName, vmSetName, err) + return "", err + } + + ipConfig, err := getPrimaryIPConfig(nic) + if err != nil { + glog.Errorf("error: ss.GetIPByNodeName(%s), getPrimaryIPConfig(%v), err=%v", nodeName, nic, err) + return "", err + } + + targetIP := *ipConfig.PrivateIPAddress + return targetIP, nil +} + +// This returns the full identifier of the primary NIC for the given VM. +func (ss *scaleSet) getPrimaryInterfaceID(machine compute.VirtualMachineScaleSetVM) (string, error) { + if len(*machine.NetworkProfile.NetworkInterfaces) == 1 { + return *(*machine.NetworkProfile.NetworkInterfaces)[0].ID, nil + } + + for _, ref := range *machine.NetworkProfile.NetworkInterfaces { + if *ref.Primary { + return *ref.ID, nil + } + } + + return "", fmt.Errorf("failed to find a primary nic for the vm. vmname=%q", *machine.Name) +} + +// machineName is composed of computerNamePrefix and 36-based instanceID. +// And instanceID part if in fixed length of 6 characters. +// Refer https://msftstack.wordpress.com/2017/05/10/figuring-out-azure-vm-scale-set-machine-names/. +func getScaleSetVMInstanceID(machineName string) (string, error) { + nameLength := len(machineName) + if nameLength < 6 { + return "", ErrorNotVmssInstance + } + + instanceID, err := strconv.ParseUint(machineName[nameLength-6:], 36, 64) + if err != nil { + return "", ErrorNotVmssInstance + } + + return fmt.Sprintf("%d", instanceID), nil +} + +// extractScaleSetNameByVMID extracts the scaleset name by scaleSetVirtualMachine's ID. +func extractScaleSetNameByVMID(vmID string) (string, error) { + matches := scaleSetNameRE.FindStringSubmatch(vmID) + if len(matches) != 2 { + return "", ErrorNotVmssInstance + } + + return matches[1], nil +} + +// listScaleSetsWithRetry lists scale sets with exponential backoff retry. +func (ss *scaleSet) listScaleSetsWithRetry() ([]string, error) { + var err error + var result compute.VirtualMachineScaleSetListResult + allScaleSets := make([]string, 0) + + backoffError := wait.ExponentialBackoff(ss.requestBackoff(), func() (bool, error) { + ss.operationPollRateLimiter.Accept() + glog.V(10).Infof("VirtualMachineScaleSetsClient.List start for %v", ss.ResourceGroup) + result, err = ss.VirtualMachineScaleSetsClient.List(ss.ResourceGroup) + glog.V(10).Infof("VirtualMachineScaleSetsClient.List end for %v", ss.ResourceGroup) + if err != nil { + glog.Errorf("VirtualMachineScaleSetsClient.List for %v failed: %v", ss.ResourceGroup, err) + return false, err + } + + return true, nil + }) + if backoffError != nil { + return nil, backoffError + } + + appendResults := (result.Value != nil && len(*result.Value) > 0) + for appendResults { + for _, scaleSet := range *result.Value { + allScaleSets = append(allScaleSets, *scaleSet.Name) + } + appendResults = false + + if result.NextLink != nil { + backoffError := wait.ExponentialBackoff(ss.requestBackoff(), func() (bool, error) { + ss.operationPollRateLimiter.Accept() + glog.V(10).Infof("VirtualMachineScaleSetsClient.ListNextResults start for %v", ss.ResourceGroup) + result, err = ss.VirtualMachineScaleSetsClient.ListNextResults(result) + glog.V(10).Infof("VirtualMachineScaleSetsClient.ListNextResults end for %v", ss.ResourceGroup) + if err != nil { + glog.Errorf("VirtualMachineScaleSetsClient.ListNextResults for %v failed: %v", ss.ResourceGroup, err) + return false, err + } + + return true, nil + }) + if backoffError != nil { + return nil, backoffError + } + + appendResults = (result.Value != nil && len(*result.Value) > 0) + } + + } + + return allScaleSets, nil +} + +// listScaleSetVMsWithRetry lists VMs belonging to the specified scale set with exponential backoff retry. +func (ss *scaleSet) listScaleSetVMsWithRetry(scaleSetName string) ([]compute.VirtualMachineScaleSetVM, error) { + var err error + var result compute.VirtualMachineScaleSetVMListResult + allVMs := make([]compute.VirtualMachineScaleSetVM, 0) + + backoffError := wait.ExponentialBackoff(ss.requestBackoff(), func() (bool, error) { + ss.operationPollRateLimiter.Accept() + glog.V(10).Infof("VirtualMachineScaleSetVMsClient.List start for %v", scaleSetName) + result, err = ss.VirtualMachineScaleSetVMsClient.List(ss.ResourceGroup, scaleSetName, "", "", string(compute.InstanceView)) + glog.V(10).Infof("VirtualMachineScaleSetVMsClient.List end for %v", scaleSetName) + if err != nil { + glog.Errorf("VirtualMachineScaleSetVMsClient.List for %v failed: %v", scaleSetName, err) + return false, err + } + + return true, nil + }) + if backoffError != nil { + return nil, backoffError + } + + appendResults := (result.Value != nil && len(*result.Value) > 0) + for appendResults { + allVMs = append(allVMs, *result.Value...) + appendResults = false + + if result.NextLink != nil { + backoffError := wait.ExponentialBackoff(ss.requestBackoff(), func() (bool, error) { + ss.operationPollRateLimiter.Accept() + glog.V(10).Infof("VirtualMachineScaleSetVMsClient.ListNextResults start for %v", scaleSetName) + result, err = ss.VirtualMachineScaleSetVMsClient.ListNextResults(result) + glog.V(10).Infof("VirtualMachineScaleSetVMsClient.ListNextResults end for %v", ss.ResourceGroup) + if err != nil { + glog.Errorf("VirtualMachineScaleSetVMsClient.ListNextResults for %v failed: %v", scaleSetName, err) + return false, err + } + + return true, nil + }) + if backoffError != nil { + return nil, backoffError + } + + appendResults = (result.Value != nil && len(*result.Value) > 0) + } + + } + + return allVMs, nil +} + +// getAgentPoolScaleSets lists the virtual machines for for the resource group and then builds +// a list of scale sets that match the nodes available to k8s. +func (ss *scaleSet) getAgentPoolScaleSets(nodes []*v1.Node) (*[]string, error) { + ss.cacheMutex.Lock() + defer ss.cacheMutex.Unlock() + + // Always update cache to get latest lists of scale sets and virtual machines. + if err := ss.updateCache(); err != nil { + return nil, err + } + + vmNameToScaleSetName := make(map[string]string) + for scaleSetName := range ss.cache { + vms := ss.cache[scaleSetName] + for idx := range vms { + vm := vms[idx] + if vm.NodeName != "" { + vmNameToScaleSetName[vm.NodeName] = scaleSetName + } + } + } + + agentPoolScaleSets := &[]string{} + availableScaleSetNames := sets.NewString() + for nx := range nodes { + if isMasterNode(nodes[nx]) { + continue + } + + nodeName := nodes[nx].Name + ssName, ok := vmNameToScaleSetName[nodeName] + if !ok { + // TODO: support master nodes not managed by VMSS. + glog.Errorf("Node %q is not belonging to any known scale sets", nodeName) + return nil, fmt.Errorf("node %q is not belonging to any known scale sets", nodeName) + } + + if availableScaleSetNames.Has(ssName) { + continue + } + + *agentPoolScaleSets = append(*agentPoolScaleSets, ssName) + } + + return agentPoolScaleSets, nil +} + +// GetVMSetNames selects all possible availability sets or scale sets +// (depending vmType configured) for service load balancer. If the service has +// no loadbalancer mode annotaion returns the primary VMSet. If service annotation +// for loadbalancer exists then return the eligible VMSet. +func (ss *scaleSet) GetVMSetNames(service *v1.Service, nodes []*v1.Node) (vmSetNames *[]string, err error) { + hasMode, isAuto, serviceVMSetNames := getServiceLoadBalancerMode(service) + if !hasMode { + // no mode specified in service annotation default to PrimaryScaleSetName. + scaleSetNames := &[]string{ss.Config.PrimaryScaleSetName} + return scaleSetNames, nil + } + + scaleSetNames, err := ss.getAgentPoolScaleSets(nodes) + if err != nil { + glog.Errorf("ss.GetVMSetNames - getAgentPoolScaleSets failed err=(%v)", err) + return nil, err + } + if len(*scaleSetNames) == 0 { + glog.Errorf("ss.GetVMSetNames - No scale sets found for nodes in the cluster, node count(%d)", len(nodes)) + return nil, fmt.Errorf("No scale sets found for nodes, node count(%d)", len(nodes)) + } + + // sort the list to have deterministic selection + sort.Strings(*scaleSetNames) + + if !isAuto { + if serviceVMSetNames == nil || len(serviceVMSetNames) == 0 { + return nil, fmt.Errorf("service annotation for LoadBalancerMode is empty, it should have __auto__ or availability sets value") + } + // validate scale set exists + var found bool + for sasx := range serviceVMSetNames { + for asx := range *scaleSetNames { + if strings.EqualFold((*scaleSetNames)[asx], serviceVMSetNames[sasx]) { + found = true + serviceVMSetNames[sasx] = (*scaleSetNames)[asx] + break + } + } + if !found { + glog.Errorf("ss.GetVMSetNames - scale set (%s) in service annotation not found", serviceVMSetNames[sasx]) + return nil, fmt.Errorf("scale set (%s) - not found", serviceVMSetNames[sasx]) + } + } + vmSetNames = &serviceVMSetNames + } + + return vmSetNames, nil +} + +// GetPrimaryInterface gets machine primary network interface by node name and vmSet. +func (ss *scaleSet) GetPrimaryInterface(nodeName, vmSetName string) (network.Interface, error) { + vm, err := ss.getCachedVirtualMachine(nodeName) + if err != nil { + if err == cloudprovider.InstanceNotFound { + // Retry with standard type because master nodes may not belong to any vmss. + // TODO: find a better way to identify the type of VM. + return ss.availabilitySet.GetPrimaryInterface(nodeName, "") + } + + glog.Errorf("error: ss.GetPrimaryInterface(%s), ss.getCachedVirtualMachine(%s), err=%v", nodeName, nodeName, err) + return network.Interface{}, err + } + + // Check scale set name. + if vmSetName != "" && !strings.EqualFold(vm.ScaleSetName, vmSetName) { + return network.Interface{}, errNotInVMSet + } + + nicName, err := getLastSegment(vm.PrimaryInterfaceID) + if err != nil { + glog.Errorf("error: ss.GetPrimaryInterface(%s), getLastSegment(%s), err=%v", nodeName, vm.PrimaryInterfaceID, err) + return network.Interface{}, err + } + + ss.operationPollRateLimiter.Accept() + glog.V(10).Infof("InterfacesClient.Get(%q): start", nicName) + nic, err := ss.InterfacesClient.GetVirtualMachineScaleSetNetworkInterface(ss.ResourceGroup, vm.ScaleSetName, vm.InstanceID, nicName, "") + glog.V(10).Infof("InterfacesClient.Get(%q): end", nicName) + if err != nil { + glog.Errorf("error: ss.GetPrimaryInterface(%s), ss.GetVirtualMachineScaleSetNetworkInterface.Get(%s, %s, %s), err=%v", nodeName, ss.ResourceGroup, vm.ScaleSetName, nicName, err) + return network.Interface{}, err + } + + // Fix interface's location, which is required when updating the interface. + // TODO: is this a bug of azure SDK? + if nic.Location == nil || *nic.Location == "" { + nic.Location = &vm.Region + } + + return nic, nil +} + +// getScaleSet gets a scale set by name. +func (ss *scaleSet) getScaleSet(name string) (compute.VirtualMachineScaleSet, bool, error) { + ss.operationPollRateLimiter.Accept() + glog.V(10).Infof("VirtualMachineScaleSetsClient.Get(%s): start", name) + result, err := ss.VirtualMachineScaleSetsClient.Get(ss.ResourceGroup, name) + glog.V(10).Infof("VirtualMachineScaleSetsClient.Get(%s): end", name) + + exists, realErr := checkResourceExistsFromError(err) + if realErr != nil { + return result, false, realErr + } + + if !exists { + return result, false, nil + } + + return result, exists, err +} + +// getScaleSetWithRetry gets scale set with exponential backoff retry +func (ss *scaleSet) getScaleSetWithRetry(name string) (compute.VirtualMachineScaleSet, bool, error) { + var result compute.VirtualMachineScaleSet + var exists bool + + err := wait.ExponentialBackoff(ss.requestBackoff(), func() (bool, error) { + var retryErr error + result, exists, retryErr = ss.getScaleSet(name) + if retryErr != nil { + glog.Errorf("backoff: failure, will retry,err=%v", retryErr) + return false, nil + } + glog.V(2).Infof("backoff: success") + return true, nil + }) + + return result, exists, err +} + +// getPrimaryNetworkConfiguration gets primary network interface configuration for scale sets. +func (ss *scaleSet) getPrimaryNetworkConfiguration(networkConfigurationList *[]compute.VirtualMachineScaleSetNetworkConfiguration, scaleSetName string) (*compute.VirtualMachineScaleSetNetworkConfiguration, error) { + networkConfigurations := *networkConfigurationList + if len(networkConfigurations) == 1 { + return &networkConfigurations[0], nil + } + + for idx := range networkConfigurations { + networkConfig := &networkConfigurations[idx] + if networkConfig.Primary != nil && *networkConfig.Primary == true { + return networkConfig, nil + } + } + + return nil, fmt.Errorf("failed to find a primary network configuration for the scale set %q", scaleSetName) +} + +func (ss *scaleSet) getPrimaryIPConfigForScaleSet(config *compute.VirtualMachineScaleSetNetworkConfiguration, scaleSetName string) (*compute.VirtualMachineScaleSetIPConfiguration, error) { + ipConfigurations := *config.IPConfigurations + if len(ipConfigurations) == 1 { + return &ipConfigurations[0], nil + } + + for idx := range ipConfigurations { + ipConfig := &ipConfigurations[idx] + if ipConfig.Primary != nil && *ipConfig.Primary == true { + return ipConfig, nil + } + } + + return nil, fmt.Errorf("failed to find a primary IP configuration for the scale set %q", scaleSetName) +} + +// createOrUpdateVMSSWithRetry invokes ss.VirtualMachineScaleSetsClient.CreateOrUpdate with exponential backoff retry. +func (ss *scaleSet) createOrUpdateVMSSWithRetry(virtualMachineScaleSet compute.VirtualMachineScaleSet) error { + return wait.ExponentialBackoff(ss.requestBackoff(), func() (bool, error) { + ss.operationPollRateLimiter.Accept() + glog.V(10).Infof("VirtualMachineScaleSetsClient.CreateOrUpdate(%s): start", *virtualMachineScaleSet.Name) + respChan, errChan := ss.VirtualMachineScaleSetsClient.CreateOrUpdate(ss.ResourceGroup, *virtualMachineScaleSet.Name, virtualMachineScaleSet, nil) + resp := <-respChan + err := <-errChan + glog.V(10).Infof("VirtualMachineScaleSetsClient.CreateOrUpdate(%s): end", *virtualMachineScaleSet.Name) + return processRetryResponse(resp.Response, err) + }) +} + +// updateVMSSInstancesWithRetry invokes ss.VirtualMachineScaleSetsClient.UpdateInstances with exponential backoff retry. +func (ss *scaleSet) updateVMSSInstancesWithRetry(scaleSetName string, vmInstanceIDs compute.VirtualMachineScaleSetVMInstanceRequiredIDs) error { + return wait.ExponentialBackoff(ss.requestBackoff(), func() (bool, error) { + ss.operationPollRateLimiter.Accept() + glog.V(10).Infof("VirtualMachineScaleSetsClient.UpdateInstances(%s): start", scaleSetName) + respChan, errChan := ss.VirtualMachineScaleSetsClient.UpdateInstances(ss.ResourceGroup, scaleSetName, vmInstanceIDs, nil) + resp := <-respChan + err := <-errChan + glog.V(10).Infof("VirtualMachineScaleSetsClient.UpdateInstances(%s): end", scaleSetName) + return processRetryResponse(resp.Response, err) + }) +} + +// EnsureHostsInPool ensures the given Node's primary IP configurations are +// participating in the specified LoadBalancer Backend Pool. +func (ss *scaleSet) EnsureHostsInPool(serviceName string, nodes []*v1.Node, backendPoolID string, vmSetName string) error { + virtualMachineScaleSet, exists, err := ss.getScaleSetWithRetry(vmSetName) + if err != nil { + glog.Errorf("ss.getScaleSetWithRetry(%s) for service %q failed: %v", vmSetName, serviceName, err) + return err + } + if !exists { + errorMessage := fmt.Errorf("Scale set %q not found", vmSetName) + glog.Errorf("%v", errorMessage) + return errorMessage + } + + // Find primary network interface configuration. + networkConfigureList := virtualMachineScaleSet.VirtualMachineProfile.NetworkProfile.NetworkInterfaceConfigurations + primaryNetworkConfiguration, err := ss.getPrimaryNetworkConfiguration(networkConfigureList, vmSetName) + if err != nil { + return err + } + + // Find primary IP configuration. + primaryIPConfiguration, err := ss.getPrimaryIPConfigForScaleSet(primaryNetworkConfiguration, vmSetName) + if err != nil { + return err + } + + // Update primary IP configuration's LoadBalancerBackendAddressPools. + foundPool := false + newBackendPools := []compute.SubResource{} + if primaryIPConfiguration.LoadBalancerBackendAddressPools != nil { + newBackendPools = *primaryIPConfiguration.LoadBalancerBackendAddressPools + } + for _, existingPool := range newBackendPools { + if strings.EqualFold(backendPoolID, *existingPool.ID) { + foundPool = true + break + } + } + if !foundPool { + newBackendPools = append(newBackendPools, + compute.SubResource{ + ID: to.StringPtr(backendPoolID), + }) + primaryIPConfiguration.LoadBalancerBackendAddressPools = &newBackendPools + + glog.V(3).Infof("VirtualMachineScaleSetsClient.CreateOrUpdate for service (%s): scale set (%s) - updating", serviceName, vmSetName) + ss.operationPollRateLimiter.Accept() + glog.V(10).Infof("VirtualMachineScaleSetsClient.CreateOrUpdate(%q): start", vmSetName) + respChan, errChan := ss.VirtualMachineScaleSetsClient.CreateOrUpdate(ss.ResourceGroup, vmSetName, virtualMachineScaleSet, nil) + resp := <-respChan + err := <-errChan + glog.V(10).Infof("VirtualMachineScaleSetsClient.CreateOrUpdate(%q): end", vmSetName) + if ss.CloudProviderBackoff && shouldRetryAPIRequest(resp.Response, err) { + glog.V(2).Infof("VirtualMachineScaleSetsClient.CreateOrUpdate for service (%): scale set (%s) - updating, err=%v", serviceName, vmSetName, err) + retryErr := ss.createOrUpdateVMSSWithRetry(virtualMachineScaleSet) + if retryErr != nil { + err = retryErr + glog.V(2).Infof("VirtualMachineScaleSetsClient.CreateOrUpdate for service (%) abort backoff: scale set (%s) - updating", serviceName, vmSetName) + } + } + if err != nil { + return err + } + } + + // Construct instanceIDs from nodes. + instanceIDs := []string{} + for _, curNode := range nodes { + curScaleSetName, err := extractScaleSetNameByVMID(curNode.Spec.ExternalID) + if err != nil { + glog.V(2).Infof("Node %q is not belonging to any scale sets, omitting it", curNode.Name) + continue + } + if curScaleSetName != vmSetName { + glog.V(2).Infof("Node %q is not belonging to scale set %q, omitting it", curNode.Name, vmSetName) + continue + } + + instanceID, err := getLastSegment(curNode.Spec.ExternalID) + if err != nil { + glog.Errorf("Failed to get last segment from %q: %v", curNode.Spec.ExternalID, err) + return err + } + + instanceIDs = append(instanceIDs, instanceID) + } + + // Update instances to latest VMSS model. + vmInstanceIDs := compute.VirtualMachineScaleSetVMInstanceRequiredIDs{ + InstanceIds: &instanceIDs, + } + ss.operationPollRateLimiter.Accept() + glog.V(10).Infof("VirtualMachineScaleSetsClient.UpdateInstances(%q): start", vmSetName) + respChan, errChan := ss.VirtualMachineScaleSetsClient.UpdateInstances(ss.ResourceGroup, vmSetName, vmInstanceIDs, nil) + resp := <-respChan + err = <-errChan + glog.V(10).Infof("VirtualMachineScaleSetsClient.UpdateInstances(%q): end", vmSetName) + if ss.CloudProviderBackoff && shouldRetryAPIRequest(resp.Response, err) { + glog.V(2).Infof("VirtualMachineScaleSetsClient.UpdateInstances for service (%): scale set (%s) - updating, err=%v", serviceName, vmSetName, err) + retryErr := ss.updateVMSSInstancesWithRetry(vmSetName, vmInstanceIDs) + if retryErr != nil { + err = retryErr + glog.V(2).Infof("VirtualMachineScaleSetsClient.UpdateInstances for service (%) abort backoff: scale set (%s) - updating", serviceName, vmSetName) + } + } + if err != nil { + return err + } + + return nil +} + +// EnsureBackendPoolDeleted ensures the loadBalancer backendAddressPools deleted from the specified vmSet. +func (ss *scaleSet) EnsureBackendPoolDeleted(poolID, vmSetName string) error { + virtualMachineScaleSet, exists, err := ss.getScaleSetWithRetry(vmSetName) + if err != nil { + glog.Errorf("ss.EnsureBackendPoolDeleted(%s, %s) getScaleSetWithRetry(%s) failed: %v", poolID, vmSetName, vmSetName, err) + return err + } + if !exists { + glog.V(2).Infof("ss.EnsureBackendPoolDeleted(%s, %s), scale set %s has already been non-exist", poolID, vmSetName, vmSetName) + return nil + } + + // Find primary network interface configuration. + networkConfigureList := virtualMachineScaleSet.VirtualMachineProfile.NetworkProfile.NetworkInterfaceConfigurations + primaryNetworkConfiguration, err := ss.getPrimaryNetworkConfiguration(networkConfigureList, vmSetName) + if err != nil { + return err + } + + // Find primary IP configuration. + primaryIPConfiguration, err := ss.getPrimaryIPConfigForScaleSet(primaryNetworkConfiguration, vmSetName) + if err != nil { + return err + } + + // Construct new loadBalancerBackendAddressPools and remove backendAddressPools from primary IP configuration. + if primaryIPConfiguration.LoadBalancerBackendAddressPools == nil || len(*primaryIPConfiguration.LoadBalancerBackendAddressPools) == 0 { + return nil + } + existingBackendPools := *primaryIPConfiguration.LoadBalancerBackendAddressPools + newBackendPools := []compute.SubResource{} + foundPool := false + for i := len(existingBackendPools) - 1; i >= 0; i-- { + curPool := existingBackendPools[i] + if strings.EqualFold(poolID, *curPool.ID) { + glog.V(10).Infof("EnsureBackendPoolDeleted gets unwanted backend pool %q for scale set %q", poolID, vmSetName) + foundPool = true + newBackendPools = append(existingBackendPools[:i], existingBackendPools[i+1:]...) + } + } + if !foundPool { + // Pool not found, assume it has been already removed. + return nil + } + + // Update scale set with backoff. + primaryIPConfiguration.LoadBalancerBackendAddressPools = &newBackendPools + glog.V(3).Infof("VirtualMachineScaleSetsClient.CreateOrUpdate: scale set (%s) - updating", vmSetName) + ss.operationPollRateLimiter.Accept() + glog.V(10).Infof("VirtualMachineScaleSetsClient.CreateOrUpdate(%q): start", vmSetName) + respChan, errChan := ss.VirtualMachineScaleSetsClient.CreateOrUpdate(ss.ResourceGroup, vmSetName, virtualMachineScaleSet, nil) + resp := <-respChan + err = <-errChan + glog.V(10).Infof("VirtualMachineScaleSetsClient.CreateOrUpdate(%q): end", vmSetName) + if ss.CloudProviderBackoff && shouldRetryAPIRequest(resp.Response, err) { + glog.V(2).Infof("VirtualMachineScaleSetsClient.CreateOrUpdate: scale set (%s) - updating, err=%v", vmSetName, err) + retryErr := ss.createOrUpdateVMSSWithRetry(virtualMachineScaleSet) + if retryErr != nil { + err = retryErr + glog.V(2).Infof("VirtualMachineScaleSetsClient.CreateOrUpdate abort backoff: scale set (%s) - updating", vmSetName) + } + } + if err != nil { + return err + } + + // Update instances to latest VMSS model. + instanceIDs := []string{"*"} + vmInstanceIDs := compute.VirtualMachineScaleSetVMInstanceRequiredIDs{ + InstanceIds: &instanceIDs, + } + ss.operationPollRateLimiter.Accept() + glog.V(10).Infof("VirtualMachineScaleSetsClient.UpdateInstances(%q): start", vmSetName) + updateRespChan, errChan := ss.VirtualMachineScaleSetsClient.UpdateInstances(ss.ResourceGroup, vmSetName, vmInstanceIDs, nil) + updateResp := <-updateRespChan + err = <-errChan + glog.V(10).Infof("VirtualMachineScaleSetsClient.UpdateInstances(%q): end", vmSetName) + if ss.CloudProviderBackoff && shouldRetryAPIRequest(updateResp.Response, err) { + glog.V(2).Infof("VirtualMachineScaleSetsClient.UpdateInstances scale set (%s) - updating, err=%v", vmSetName, err) + retryErr := ss.updateVMSSInstancesWithRetry(vmSetName, vmInstanceIDs) + if retryErr != nil { + err = retryErr + glog.V(2).Infof("VirtualMachineScaleSetsClient.UpdateInstances abort backoff: scale set (%s) - updating", vmSetName) + } + } + if err != nil { + return err + } + + // Update virtualMachineScaleSet again. This is a workaround for removing VMSS reference from LB. + // TODO: remove this workaround when figuring out the root cause. + if len(newBackendPools) == 0 { + glog.V(3).Infof("VirtualMachineScaleSetsClient.CreateOrUpdate: scale set (%s) - updating second time", vmSetName) + ss.operationPollRateLimiter.Accept() + glog.V(10).Infof("VirtualMachineScaleSetsClient.CreateOrUpdate(%q): start", vmSetName) + respChan, errChan = ss.VirtualMachineScaleSetsClient.CreateOrUpdate(ss.ResourceGroup, vmSetName, virtualMachineScaleSet, nil) + resp = <-respChan + err = <-errChan + glog.V(10).Infof("VirtualMachineScaleSetsClient.CreateOrUpdate(%q): end", vmSetName) + if ss.CloudProviderBackoff && shouldRetryAPIRequest(resp.Response, err) { + glog.V(2).Infof("VirtualMachineScaleSetsClient.CreateOrUpdate: scale set (%s) - updating, err=%v", vmSetName, err) + retryErr := ss.createOrUpdateVMSSWithRetry(virtualMachineScaleSet) + if retryErr != nil { + glog.V(2).Infof("VirtualMachineScaleSetsClient.CreateOrUpdate abort backoff: scale set (%s) - updating", vmSetName) + } + } + } + + return nil +} diff --git a/pkg/cloudprovider/providers/azure/azure_vmsets.go b/pkg/cloudprovider/providers/azure/azure_vmsets.go new file mode 100644 index 00000000000..dd5cc308dcd --- /dev/null +++ b/pkg/cloudprovider/providers/azure/azure_vmsets.go @@ -0,0 +1,59 @@ +/* +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 azure + +import ( + "github.com/Azure/azure-sdk-for-go/arm/network" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/kubernetes/pkg/cloudprovider" +) + +// VMSet defines functions all vmsets (including scale set and availabitlity +// set) should be implemented. +type VMSet interface { + // GetInstanceIDByNodeName gets the cloud provider ID by node name. + // It must return ("", cloudprovider.InstanceNotFound) if the instance does + // not exist or is no longer running. + GetInstanceIDByNodeName(name string) (string, error) + // GetInstanceTypeByNodeName gets the instance type by node name. + GetInstanceTypeByNodeName(name string) (string, error) + // GetIPByNodeName gets machine IP by node name. + GetIPByNodeName(name, vmSetName string) (string, error) + // GetPrimaryInterface gets machine primary network interface by node name and vmSet. + GetPrimaryInterface(nodeName, vmSetName string) (network.Interface, error) + // GetNodeNameByProviderID gets the node name by provider ID. + GetNodeNameByProviderID(providerID string) (types.NodeName, error) + + // GetZoneByNodeName gets cloudprovider.Zone by node name. + GetZoneByNodeName(name string) (cloudprovider.Zone, error) + + // GetPrimaryVMSetName returns the VM set name depending on the configured vmType. + // It returns config.PrimaryScaleSetName for vmss and config.PrimaryAvailabilitySetName for standard vmType. + GetPrimaryVMSetName() string + // GetVMSetNames selects all possible availability sets or scale sets + // (depending vmType configured) for service load balancer, if the service has + // no loadbalancer mode annotaion returns the primary VMSet. If service annotation + // for loadbalancer exists then return the eligible VMSet. + GetVMSetNames(service *v1.Service, nodes []*v1.Node) (availabilitySetNames *[]string, err error) + // EnsureHostsInPool ensures the given Node's primary IP configurations are + // participating in the specified LoadBalancer Backend Pool. + EnsureHostsInPool(serviceName string, nodes []*v1.Node, backendPoolID string, vmSetName string) error + // EnsureBackendPoolDeleted ensures the loadBalancer backendAddressPools deleted from the specified vmSet. + EnsureBackendPoolDeleted(poolID, vmSetName string) error +} diff --git a/pkg/cloudprovider/providers/azure/azure_wrap.go b/pkg/cloudprovider/providers/azure/azure_wrap.go index e9c06dc6fc2..85b67c456b0 100644 --- a/pkg/cloudprovider/providers/azure/azure_wrap.go +++ b/pkg/cloudprovider/providers/azure/azure_wrap.go @@ -18,12 +18,16 @@ package azure import ( "net/http" + "sync" + "time" "github.com/Azure/azure-sdk-for-go/arm/compute" "github.com/Azure/azure-sdk-for-go/arm/network" "github.com/Azure/go-autorest/autorest" "github.com/golang/glog" + "k8s.io/apimachinery/pkg/types" + "k8s.io/kubernetes/pkg/cloudprovider" ) // checkExistsFromError inspects an error and returns a true if err is nil, @@ -34,31 +38,85 @@ func checkResourceExistsFromError(err error) (bool, error) { return true, nil } v, ok := err.(autorest.DetailedError) - if ok && v.StatusCode == http.StatusNotFound { + if !ok { + return false, err + } + if v.StatusCode == http.StatusNotFound { return false, nil } return false, v } -func (az *Cloud) getVirtualMachine(nodeName types.NodeName) (vm compute.VirtualMachine, exists bool, err error) { - var realErr error +// If it is StatusNotFound return nil, +// Otherwise, return what it is +func ignoreStatusNotFoundFromError(err error) error { + if err == nil { + return nil + } + v, ok := err.(autorest.DetailedError) + if ok && v.StatusCode == http.StatusNotFound { + return nil + } + return err +} +// cache used by getVirtualMachine +// 15s for expiration duration +var vmCache = newTimedcache(15 * time.Second) + +type vmRequest struct { + lock *sync.Mutex + vm *compute.VirtualMachine +} + +/// getVirtualMachine calls 'VirtualMachinesClient.Get' with a timed cache +/// The service side has throttling control that delays responses if there're multiple requests onto certain vm +/// resource request in short period. +func (az *Cloud) getVirtualMachine(nodeName types.NodeName) (vm compute.VirtualMachine, err error) { vmName := string(nodeName) - az.operationPollRateLimiter.Accept() - glog.V(10).Infof("VirtualMachinesClient.Get(%s): start", vmName) - vm, err = az.VirtualMachinesClient.Get(az.ResourceGroup, vmName, "") - glog.V(10).Infof("VirtualMachinesClient.Get(%s): end", vmName) - exists, realErr = checkResourceExistsFromError(err) - if realErr != nil { - return vm, false, realErr + cachedRequest, err := vmCache.GetOrCreate(vmName, func() interface{} { + return &vmRequest{ + lock: &sync.Mutex{}, + vm: nil, + } + }) + if err != nil { + return compute.VirtualMachine{}, err + } + request := cachedRequest.(*vmRequest) + + if request.vm == nil { + request.lock.Lock() + defer request.lock.Unlock() + if request.vm == nil { + // Currently InstanceView request are used by azure_zones, while the calls come after non-InstanceView + // request. If we first send an InstanceView request and then a non InstanceView request, the second + // request will still hit throttling. This is what happens now for cloud controller manager: In this + // case we do get instance view every time to fulfill the azure_zones requirement without hitting + // throttling. + // Consider adding separate parameter for controlling 'InstanceView' once node update issue #56276 is fixed + az.operationPollRateLimiter.Accept() + glog.V(10).Infof("VirtualMachinesClient.Get(%s): start", vmName) + vm, err = az.VirtualMachinesClient.Get(az.ResourceGroup, vmName, compute.InstanceView) + glog.V(10).Infof("VirtualMachinesClient.Get(%s): end", vmName) + + exists, realErr := checkResourceExistsFromError(err) + if realErr != nil { + return vm, realErr + } + + if !exists { + return vm, cloudprovider.InstanceNotFound + } + + request.vm = &vm + } + return vm, nil } - if !exists { - return vm, false, nil - } - - return vm, exists, err + glog.V(6).Infof("getVirtualMachine hits cache for(%s)", vmName) + return *request.vm, nil } func (az *Cloud) getRouteTable() (routeTable network.RouteTable, exists bool, err error) { @@ -103,7 +161,6 @@ func (az *Cloud) getSecurityGroup() (sg network.SecurityGroup, exists bool, err func (az *Cloud) getAzureLoadBalancer(name string) (lb network.LoadBalancer, exists bool, err error) { var realErr error - az.operationPollRateLimiter.Accept() glog.V(10).Infof("LoadBalancerClient.Get(%s): start", name) lb, err = az.LoadBalancerClient.Get(az.ResourceGroup, name, "") @@ -121,13 +178,36 @@ func (az *Cloud) getAzureLoadBalancer(name string) (lb network.LoadBalancer, exi return lb, exists, err } -func (az *Cloud) getPublicIPAddress(name string) (pip network.PublicIPAddress, exists bool, err error) { +func (az *Cloud) listLoadBalancers() (lbListResult network.LoadBalancerListResult, exists bool, err error) { var realErr error az.operationPollRateLimiter.Accept() - glog.V(10).Infof("PublicIPAddressesClient.Get(%s): start", name) - pip, err = az.PublicIPAddressesClient.Get(az.ResourceGroup, name, "") - glog.V(10).Infof("PublicIPAddressesClient.Get(%s): end", name) + glog.V(10).Infof("LoadBalancerClient.List(%s): start", az.ResourceGroup) + lbListResult, err = az.LoadBalancerClient.List(az.ResourceGroup) + glog.V(10).Infof("LoadBalancerClient.List(%s): end", az.ResourceGroup) + exists, realErr = checkResourceExistsFromError(err) + if realErr != nil { + return lbListResult, false, realErr + } + + if !exists { + return lbListResult, false, nil + } + + return lbListResult, exists, err +} + +func (az *Cloud) getPublicIPAddress(pipResourceGroup string, pipName string) (pip network.PublicIPAddress, exists bool, err error) { + resourceGroup := az.ResourceGroup + if pipResourceGroup != "" { + resourceGroup = pipResourceGroup + } + + var realErr error + az.operationPollRateLimiter.Accept() + glog.V(10).Infof("PublicIPAddressesClient.Get(%s, %s): start", resourceGroup, pipName) + pip, err = az.PublicIPAddressesClient.Get(resourceGroup, pipName, "") + glog.V(10).Infof("PublicIPAddressesClient.Get(%s, %s): end", resourceGroup, pipName) exists, realErr = checkResourceExistsFromError(err) if realErr != nil { diff --git a/pkg/cloudprovider/providers/azure/azure_wrap_test.go b/pkg/cloudprovider/providers/azure/azure_wrap_test.go new file mode 100644 index 00000000000..380194ba9c3 --- /dev/null +++ b/pkg/cloudprovider/providers/azure/azure_wrap_test.go @@ -0,0 +1,53 @@ +/* +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 azure + +import ( + "fmt" + "net/http" + "reflect" + "testing" + + "github.com/Azure/go-autorest/autorest" +) + +func TestExtractNotFound(t *testing.T) { + notFound := autorest.DetailedError{StatusCode: http.StatusNotFound} + otherHTTP := autorest.DetailedError{StatusCode: http.StatusForbidden} + otherErr := fmt.Errorf("other error") + + tests := []struct { + err error + expectedErr error + exists bool + }{ + {nil, nil, true}, + {otherErr, otherErr, false}, + {notFound, nil, false}, + {otherHTTP, otherHTTP, false}, + } + + for _, test := range tests { + exists, err := checkResourceExistsFromError(test.err) + if test.exists != exists { + t.Errorf("expected: %v, saw: %v", test.exists, exists) + } + if !reflect.DeepEqual(test.expectedErr, err) { + t.Errorf("expected err: %v, saw: %v", test.expectedErr, err) + } + } +} diff --git a/pkg/cloudprovider/providers/azure/azure_zones.go b/pkg/cloudprovider/providers/azure/azure_zones.go index 2f9d58decf6..75d0c412515 100644 --- a/pkg/cloudprovider/providers/azure/azure_zones.go +++ b/pkg/cloudprovider/providers/azure/azure_zones.go @@ -21,13 +21,10 @@ import ( "io" "io/ioutil" "net/http" - "strconv" "sync" "k8s.io/apimachinery/pkg/types" "k8s.io/kubernetes/pkg/cloudprovider" - - "github.com/Azure/azure-sdk-for-go/arm/compute" ) const instanceInfoURL = "http://169.254.169.254/metadata/v1/InstanceInfo" @@ -44,6 +41,7 @@ type instanceInfo struct { // GetZone returns the Zone containing the current failure zone and locality region that the program is running in func (az *Cloud) GetZone() (cloudprovider.Zone, error) { faultMutex.Lock() + defer faultMutex.Unlock() if faultDomain == nil { var err error faultDomain, err = fetchFaultDomain() @@ -55,7 +53,6 @@ func (az *Cloud) GetZone() (cloudprovider.Zone, error) { FailureDomain: *faultDomain, Region: az.Location, } - faultMutex.Unlock() return zone, nil } @@ -63,10 +60,11 @@ func (az *Cloud) GetZone() (cloudprovider.Zone, error) { // This is particularly useful in external cloud providers where the kubelet // does not initialize node data. func (az *Cloud) GetZoneByProviderID(providerID string) (cloudprovider.Zone, error) { - nodeName, err := splitProviderID(providerID) + nodeName, err := az.vmSet.GetNodeNameByProviderID(providerID) if err != nil { return cloudprovider.Zone{}, err } + return az.GetZoneByNodeName(nodeName) } @@ -74,20 +72,7 @@ func (az *Cloud) GetZoneByProviderID(providerID string) (cloudprovider.Zone, err // This is particularly useful in external cloud providers where the kubelet // does not initialize node data. func (az *Cloud) GetZoneByNodeName(nodeName types.NodeName) (cloudprovider.Zone, error) { - - vm, err := az.VirtualMachinesClient.Get(az.ResourceGroup, string(nodeName), compute.InstanceView) - - if err != nil { - return cloudprovider.Zone{}, err - } - - failureDomain := strconv.Itoa(int(*vm.VirtualMachineProperties.InstanceView.PlatformFaultDomain)) - - zone := cloudprovider.Zone{ - FailureDomain: failureDomain, - Region: *(vm.Location), - } - return zone, nil + return az.vmSet.GetZoneByNodeName(string(nodeName)) } func fetchFaultDomain() (*string, error) { diff --git a/pkg/cloudprovider/providers/cloudstack/BUILD b/pkg/cloudprovider/providers/cloudstack/BUILD index 9798a63fb13..0ad40d29ea0 100644 --- a/pkg/cloudprovider/providers/cloudstack/BUILD +++ b/pkg/cloudprovider/providers/cloudstack/BUILD @@ -13,11 +13,40 @@ go_library( "cloudstack_instances.go", "cloudstack_loadbalancer.go", "metadata.go", - "metadata_other.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "metadata_other.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "metadata_other.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "metadata_other.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "metadata_other.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ "metadata_linux.go", ], + "@io_bazel_rules_go//go/platform:nacl": [ + "metadata_other.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "metadata_other.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "metadata_other.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "metadata_other.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "metadata_other.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "metadata_other.go", + ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/cloudstack", @@ -25,21 +54,55 @@ go_library( "//pkg/cloudprovider:go_default_library", "//pkg/controller:go_default_library", "//vendor/github.com/d2g/dhcp4:go_default_library", - "//vendor/github.com/d2g/dhcp4client:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/kardianos/osext:go_default_library", "//vendor/github.com/xanzy/go-cloudstack/cloudstack:go_default_library", "//vendor/gopkg.in/gcfg.v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - ], + ] + select({ + "@io_bazel_rules_go//go/platform:android": [ + "//vendor/github.com/d2g/dhcp4client:go_default_library", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "//vendor/github.com/d2g/dhcp4client:go_default_library", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "//vendor/github.com/d2g/dhcp4client:go_default_library", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "//vendor/github.com/d2g/dhcp4client:go_default_library", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "//vendor/github.com/d2g/dhcp4client:go_default_library", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "//vendor/github.com/d2g/dhcp4client:go_default_library", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "//vendor/github.com/d2g/dhcp4client:go_default_library", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "//vendor/github.com/d2g/dhcp4client:go_default_library", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "//vendor/github.com/d2g/dhcp4client:go_default_library", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "//vendor/github.com/d2g/dhcp4client:go_default_library", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "//vendor/github.com/d2g/dhcp4client:go_default_library", + ], + "//conditions:default": [], + }), ) go_test( name = "go_default_test", srcs = ["cloudstack_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/cloudstack", - library = ":go_default_library", deps = [ "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/cloudprovider/providers/cloudstack/cloudstack.go b/pkg/cloudprovider/providers/cloudstack/cloudstack.go index e4979a75c42..89d2c1f01f2 100644 --- a/pkg/cloudprovider/providers/cloudstack/cloudstack.go +++ b/pkg/cloudprovider/providers/cloudstack/cloudstack.go @@ -181,11 +181,6 @@ func (cs *CSCloud) ProviderName() string { return ProviderName } -// ScrubDNS filters DNS settings for pods. -func (cs *CSCloud) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) { - return nameservers, searches -} - // HasClusterID returns true if the cluster has a clusterID func (cs *CSCloud) HasClusterID() bool { return true diff --git a/pkg/cloudprovider/providers/cloudstack/metadata.go b/pkg/cloudprovider/providers/cloudstack/metadata.go index 0ec991d1980..1d76b609a32 100644 --- a/pkg/cloudprovider/providers/cloudstack/metadata.go +++ b/pkg/cloudprovider/providers/cloudstack/metadata.go @@ -91,7 +91,7 @@ func (m *metadata) InstanceID(name types.NodeName) (string, error) { // InstanceType returns the type of the specified instance. func (m *metadata) InstanceType(name types.NodeName) (string, error) { instanceType, err := m.get(metadataTypeInstanceType) - if err == nil { + if err != nil { return "", fmt.Errorf("could not get instance type: %v", err) } diff --git a/pkg/cloudprovider/providers/fake/fake.go b/pkg/cloudprovider/providers/fake/fake.go index 5efc09a11bf..d040c51df65 100644 --- a/pkg/cloudprovider/providers/fake/fake.go +++ b/pkg/cloudprovider/providers/fake/fake.go @@ -110,11 +110,6 @@ func (f *FakeCloud) ProviderName() string { return f.Provider } -// ScrubDNS filters DNS settings for pods. -func (f *FakeCloud) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) { - return nameservers, searches -} - // HasClusterID returns true if the cluster has a clusterID func (f *FakeCloud) HasClusterID() bool { return true diff --git a/pkg/cloudprovider/providers/gce/BUILD b/pkg/cloudprovider/providers/gce/BUILD index d25739c9484..18205b9fcad 100644 --- a/pkg/cloudprovider/providers/gce/BUILD +++ b/pkg/cloudprovider/providers/gce/BUILD @@ -40,7 +40,6 @@ go_library( "gce_urlmap.go", "gce_util.go", "gce_zones.go", - "kms.go", "metrics.go", "token_source.go", ], @@ -53,6 +52,7 @@ go_library( "//pkg/master/ports:go_default_library", "//pkg/util/net/sets:go_default_library", "//pkg/util/version:go_default_library", + "//pkg/version:go_default_library", "//pkg/volume:go_default_library", "//pkg/volume/util:go_default_library", "//vendor/cloud.google.com/go/compute/metadata:go_default_library", @@ -60,7 +60,6 @@ go_library( "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", "//vendor/golang.org/x/oauth2:go_default_library", "//vendor/golang.org/x/oauth2/google:go_default_library", - "//vendor/google.golang.org/api/cloudkms/v1:go_default_library", "//vendor/google.golang.org/api/compute/v0.alpha:go_default_library", "//vendor/google.golang.org/api/compute/v0.beta:go_default_library", "//vendor/google.golang.org/api/compute/v1:go_default_library", @@ -68,6 +67,7 @@ go_library( "//vendor/google.golang.org/api/googleapi:go_default_library", "//vendor/gopkg.in/gcfg.v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", @@ -76,8 +76,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/apiserver/pkg/server/options/encryptionconfig:go_default_library", - "//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope:go_default_library", + "//vendor/k8s.io/client-go/informers:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library", "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", @@ -96,10 +95,11 @@ go_test( "gce_healthchecks_test.go", "gce_loadbalancer_external_test.go", "gce_test.go", + "gce_util_test.go", "metrics_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/gce", - library = ":go_default_library", deps = [ "//pkg/cloudprovider:go_default_library", "//pkg/kubelet/apis:go_default_library", diff --git a/pkg/cloudprovider/providers/gce/gce.go b/pkg/cloudprovider/providers/gce/gce.go index 1c77a4f79a4..b515c8ff67a 100644 --- a/pkg/cloudprovider/providers/gce/gce.go +++ b/pkg/cloudprovider/providers/gce/gce.go @@ -17,10 +17,11 @@ limitations under the License. package gce import ( + "context" "fmt" "io" "net/http" - "regexp" + "runtime" "strconv" "strings" "sync" @@ -33,20 +34,21 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/apiserver/pkg/server/options/encryptionconfig" - "k8s.io/apiserver/pkg/storage/value/encrypt/envelope" + "k8s.io/client-go/informers" clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" v1core "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/flowcontrol" "k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/controller" + kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis" + "k8s.io/kubernetes/pkg/version" "github.com/golang/glog" "golang.org/x/oauth2" "golang.org/x/oauth2/google" - cloudkms "google.golang.org/api/cloudkms/v1" computealpha "google.golang.org/api/compute/v0.alpha" computebeta "google.golang.org/api/compute/v0.beta" compute "google.golang.org/api/compute/v1" @@ -100,19 +102,21 @@ type GCECloud struct { // for the cloudprovider to start watching the configmap. ClusterID ClusterID - service *compute.Service - serviceBeta *computebeta.Service - serviceAlpha *computealpha.Service - containerService *container.Service - cloudkmsService *cloudkms.Service - client clientset.Interface - clientBuilder controller.ControllerClientBuilder - eventBroadcaster record.EventBroadcaster - eventRecorder record.EventRecorder - projectID string - region string - localZone string // The zone in which we are running - managedZones []string // List of zones we are spanning (for multi-AZ clusters, primarily when running on master) + service *compute.Service + serviceBeta *computebeta.Service + serviceAlpha *computealpha.Service + containerService *container.Service + client clientset.Interface + clientBuilder controller.ControllerClientBuilder + eventBroadcaster record.EventBroadcaster + eventRecorder record.EventRecorder + projectID string + region string + localZone string // The zone in which we are running + // managedZones will be set to the 1 zone if running a single zone cluster + // it will be set to ALL zones in region for any multi-zone cluster + // Use GetAllCurrentZones to get only zones that contain nodes + managedZones []string networkURL string isLegacyNetwork bool subnetworkURL string @@ -127,6 +131,12 @@ type GCECloud struct { useMetadataServer bool operationPollRateLimiter flowcontrol.RateLimiter manager diskServiceManager + // Lock for access to nodeZones + nodeZonesLock sync.Mutex + // nodeZones is a mapping from Zone to a sets.String of Node's names in the Zone + // it is updated by the nodeInformer + nodeZones map[string]sets.String + nodeInformerSynced cache.InformerSynced // sharedResourceLock is used to serialize GCE operations that may mutate shared state to // prevent inconsistencies. For example, load balancers manipulation methods will take the // lock to prevent shared resources from being prematurely deleted while the operation is @@ -139,6 +149,7 @@ type GCECloud struct { AlphaFeatureGate *AlphaFeatureGate } +// TODO: replace gcfg with json type ConfigGlobal struct { TokenURL string `gcfg:"token-url"` TokenBody string `gcfg:"token-body"` @@ -163,7 +174,7 @@ type ConfigGlobal struct { // located in (i.e. where the controller will be running). If this is // blank, then the local zone will be discovered via the metadata server. LocalZone string `gcfg:"local-zone"` - // Possible values: List of api names separated by comma. Default to none. + // Default to none. // For example: MyFeatureFlag AlphaFeatures []string `gcfg:"alpha-features"` } @@ -193,10 +204,6 @@ type CloudConfig struct { AlphaFeatureGate *AlphaFeatureGate } -// kmsPluginRegisterOnce prevents the cloudprovider from registering its KMS plugin -// more than once in the KMS plugin registry. -var kmsPluginRegisterOnce sync.Once - func init() { cloudprovider.RegisterCloudProvider( ProviderName, @@ -205,14 +212,17 @@ func init() { }) } -// Raw access to the underlying GCE service, probably should only be used for e2e tests -func (g *GCECloud) GetComputeService() *compute.Service { - return g.service +// Services is the set of all versions of the compute service. +type Services struct { + // GA, Alpha, Beta versions of the compute API. + GA *compute.Service + Alpha *computealpha.Service + Beta *computebeta.Service } -// Raw access to the cloudkmsService of GCE cloud. Required for encryption of etcd using Google KMS. -func (g *GCECloud) GetKMSService() *cloudkms.Service { - return g.cloudkmsService +// ComputeServices returns access to the internal compute services. +func (g *GCECloud) ComputeServices() *Services { + return &Services{g.service, g.serviceAlpha, g.serviceBeta} } // newGCECloud creates a new instance of GCECloud. @@ -251,6 +261,8 @@ func generateCloudConfig(configFile *ConfigFile) (cloudConfig *CloudConfig, err cloudConfig.TokenSource = google.ComputeTokenSource("") cloudConfig.UseMetadataServer = true + featureMap := make(map[string]bool) + cloudConfig.AlphaFeatureGate = &AlphaFeatureGate{featureMap} if configFile != nil { if configFile.Global.ApiEndpoint != "" { cloudConfig.ApiEndpoint = configFile.Global.ApiEndpoint @@ -274,6 +286,13 @@ func generateCloudConfig(configFile *ConfigFile) (cloudConfig *CloudConfig, err glog.Errorf("Encountered error for creating alpha feature gate: %v", err) } cloudConfig.AlphaFeatureGate = alphaFeatureGate + } else { + // initialize AlphaFeatureGate when no AlphaFeatures are configured. + alphaFeatureGate, err := NewAlphaFeatureGate([]string{}) + if err != nil { + glog.Errorf("Encountered error for initializing alpha feature gate: %v", err) + } + cloudConfig.AlphaFeatureGate = alphaFeatureGate } // retrieve projectID and zone @@ -344,6 +363,13 @@ func generateCloudConfig(configFile *ConfigFile) (cloudConfig *CloudConfig, err // If no tokenSource is specified, uses oauth2.DefaultTokenSource. // If managedZones is nil / empty all zones in the region will be managed. func CreateGCECloud(config *CloudConfig) (*GCECloud, error) { + // Remove any pre-release version and build metadata from the semver, leaving only the MAJOR.MINOR.PATCH portion. + // See http://semver.org/. + version := strings.TrimLeft(strings.Split(strings.Split(version.Get().GitVersion, "-")[0], "+")[0], "v") + + // Create a user-agent header append string to supply to the Google API clients, to identify Kubernetes as the origin of the GCP API calls. + userAgent := fmt.Sprintf("Kubernetes/%s (%s %s)", version, runtime.GOOS, runtime.GOARCH) + // Use ProjectID for NetworkProjectID, if it wasn't explicitly set. if config.NetworkProjectID == "" { config.NetworkProjectID = config.ProjectID @@ -357,6 +383,7 @@ func CreateGCECloud(config *CloudConfig) (*GCECloud, error) { if err != nil { return nil, err } + service.UserAgent = userAgent client, err = newOauthClient(config.TokenSource) if err != nil { @@ -366,6 +393,7 @@ func CreateGCECloud(config *CloudConfig) (*GCECloud, error) { if err != nil { return nil, err } + serviceBeta.UserAgent = userAgent client, err = newOauthClient(config.TokenSource) if err != nil { @@ -375,6 +403,7 @@ func CreateGCECloud(config *CloudConfig) (*GCECloud, error) { if err != nil { return nil, err } + serviceAlpha.UserAgent = userAgent // Expect override api endpoint to always be v1 api and follows the same pattern as prod. // Generate alpha and beta api endpoints based on override v1 api endpoint. @@ -390,11 +419,7 @@ func CreateGCECloud(config *CloudConfig) (*GCECloud, error) { if err != nil { return nil, err } - - cloudkmsService, err := cloudkms.New(client) - if err != nil { - return nil, err - } + containerService.UserAgent = userAgent // ProjectID and.NetworkProjectID may be project number or name. projID, netProjID := tryConvertToProjectNames(config.ProjectID, config.NetworkProjectID, service) @@ -419,24 +444,26 @@ func CreateGCECloud(config *CloudConfig) (*GCECloud, error) { } else if config.SubnetworkName != "" { subnetURL = gceSubnetworkURL(config.ApiEndpoint, netProjID, config.Region, config.SubnetworkName) } else { - // Attempt to determine the subnetwork in case it's an automatic network. - // Legacy networks will not have a subnetwork, so subnetworkURL should remain empty. + // Determine the type of network and attempt to discover the correct subnet for AUTO mode. + // Gracefully fail because kubelet calls CreateGCECloud without any config, and minions + // lack the proper credentials for API calls. if networkName := lastComponent(networkURL); networkName != "" { - if n, err := getNetwork(service, netProjID, networkName); err != nil { - // Gracefully fail because kubelet calls CreateGCECloud without any config, and API calls will fail coming from minions. - glog.Warningf("Could not retrieve network %q in attempt to determine if legacy network or see list of subnets, err %v", networkURL, err) + var n *compute.Network + if n, err = getNetwork(service, netProjID, networkName); err != nil { + glog.Warningf("Could not retrieve network %q; err: %v", networkName, err) } else { - // Legacy networks have a non-empty IPv4Range - if len(n.IPv4Range) > 0 { - glog.Infof("Determined network %q is type legacy", networkURL) + switch typeOfNetwork(n) { + case netTypeLegacy: + glog.Infof("Network %q is type legacy - no subnetwork", networkName) isLegacyNetwork = true - } else { - // Try to find the subnet in the list of subnets - subnetURL = findSubnetForRegion(n.Subnetworks, config.Region) - if len(subnetURL) > 0 { - glog.Infof("Using subnet %q within network %q & region %q because none was specified.", subnetURL, n.Name, config.Region) + case netTypeCustom: + glog.Warningf("Network %q is type custom - cannot auto select a subnetwork", networkName) + case netTypeAuto: + subnetURL, err = determineSubnetURL(service, netProjID, networkName, config.Region) + if err != nil { + glog.Warningf("Could not determine subnetwork for network %q and region %v; err: %v", networkName, config.Region, err) } else { - glog.Warningf("Could not find any subnet in region %q within list %v.", config.Region, n.Subnetworks) + glog.Infof("Auto selecting subnetwork %q", subnetURL) } } } @@ -449,7 +476,7 @@ func CreateGCECloud(config *CloudConfig) (*GCECloud, error) { return nil, err } } - if len(config.ManagedZones) != 1 { + if len(config.ManagedZones) > 1 { glog.Infof("managing multiple zones: %v", config.ManagedZones) } @@ -460,7 +487,6 @@ func CreateGCECloud(config *CloudConfig) (*GCECloud, error) { serviceAlpha: serviceAlpha, serviceBeta: serviceBeta, containerService: containerService, - cloudkmsService: cloudkmsService, projectID: projID, networkProjectID: netProjID, onXPN: onXPN, @@ -476,21 +502,38 @@ func CreateGCECloud(config *CloudConfig) (*GCECloud, error) { useMetadataServer: config.UseMetadataServer, operationPollRateLimiter: operationPollRateLimiter, AlphaFeatureGate: config.AlphaFeatureGate, + nodeZones: map[string]sets.String{}, } gce.manager = &gceServiceManager{gce} - // Registering the KMS plugin only the first time. - kmsPluginRegisterOnce.Do(func() { - // Register the Google Cloud KMS based service in the KMS plugin registry. - encryptionconfig.KMSPluginRegistry.Register(KMSServiceName, func(config io.Reader) (envelope.Service, error) { - return gce.getGCPCloudKMSService(config) - }) - }) - return gce, nil } +// determineSubnetURL queries for all subnetworks in a region for a given network and returns +// the URL of the subnetwork which exists in the auto-subnet range. +func determineSubnetURL(service *compute.Service, networkProjectID, networkName, region string) (string, error) { + subnets, err := listSubnetworksOfNetwork(service, networkProjectID, networkName, region) + if err != nil { + return "", err + } + + autoSubnets, err := subnetsInCIDR(subnets, autoSubnetIPRange) + if err != nil { + return "", err + } + + if len(autoSubnets) == 0 { + return "", fmt.Errorf("no subnet exists in auto CIDR") + } + + if len(autoSubnets) > 1 { + return "", fmt.Errorf("multiple subnetworks in the same region exist in auto CIDR") + } + + return autoSubnets[0].SelfLink, nil +} + func tryConvertToProjectNames(configProject, configNetworkProject string, service *compute.Service) (projID, netProjID string) { projID = configProject if isProjectNumber(projID) { @@ -526,7 +569,7 @@ func (gce *GCECloud) Initialize(clientBuilder controller.ControllerClientBuilder if gce.OnXPN() { gce.eventBroadcaster = record.NewBroadcaster() - gce.eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(gce.client.Core().RESTClient()).Events("")}) + gce.eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(gce.client.CoreV1().RESTClient()).Events("")}) gce.eventRecorder = gce.eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "gce-cloudprovider"}) } @@ -596,18 +639,66 @@ func (gce *GCECloud) IsLegacyNetwork() bool { return gce.isLegacyNetwork } -// Known-useless DNS search path. -var uselessDNSSearchRE = regexp.MustCompile(`^[0-9]+.google.internal.$`) +func (gce *GCECloud) SetInformers(informerFactory informers.SharedInformerFactory) { + glog.Infof("Setting up informers for GCECloud") + nodeInformer := informerFactory.Core().V1().Nodes().Informer() + nodeInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + node := obj.(*v1.Node) + gce.updateNodeZones(nil, node) + }, + UpdateFunc: func(prev, obj interface{}) { + prevNode := prev.(*v1.Node) + newNode := obj.(*v1.Node) + if newNode.Labels[kubeletapis.LabelZoneFailureDomain] == + prevNode.Labels[kubeletapis.LabelZoneFailureDomain] { + return + } + gce.updateNodeZones(prevNode, newNode) + }, + DeleteFunc: func(obj interface{}) { + node, isNode := obj.(*v1.Node) + // We can get DeletedFinalStateUnknown instead of *v1.Node here + // and we need to handle that correctly. + if !isNode { + deletedState, ok := obj.(cache.DeletedFinalStateUnknown) + if !ok { + glog.Errorf("Received unexpected object: %v", obj) + return + } + node, ok = deletedState.Obj.(*v1.Node) + if !ok { + glog.Errorf("DeletedFinalStateUnknown contained non-Node object: %v", deletedState.Obj) + return + } + } + gce.updateNodeZones(node, nil) + }, + }) + gce.nodeInformerSynced = nodeInformer.HasSynced +} -// ScrubDNS filters DNS settings for pods. -func (gce *GCECloud) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) { - // GCE has too many search paths by default. Filter the ones we know are useless. - for _, s := range searches { - if !uselessDNSSearchRE.MatchString(s) { - srchOut = append(srchOut, s) +func (gce *GCECloud) updateNodeZones(prevNode, newNode *v1.Node) { + gce.nodeZonesLock.Lock() + defer gce.nodeZonesLock.Unlock() + if prevNode != nil { + prevZone, ok := prevNode.ObjectMeta.Labels[kubeletapis.LabelZoneFailureDomain] + if ok { + gce.nodeZones[prevZone].Delete(prevNode.ObjectMeta.Name) + if gce.nodeZones[prevZone].Len() == 0 { + gce.nodeZones[prevZone] = nil + } + } + } + if newNode != nil { + newZone, ok := newNode.ObjectMeta.Labels[kubeletapis.LabelZoneFailureDomain] + if ok { + if gce.nodeZones[newZone] == nil { + gce.nodeZones[newZone] = sets.NewString() + } + gce.nodeZones[newZone].Insert(newNode.ObjectMeta.Name) } } - return nameservers, srchOut } // HasClusterID returns true if the cluster has a clusterID @@ -639,20 +730,6 @@ func gceSubnetworkURL(apiEndpoint, project, region, subnetwork string) string { return apiEndpoint + strings.Join([]string{"projects", project, "regions", region, "subnetworks", subnetwork}, "/") } -// getProjectIDInURL parses full resource URLS and shorter URLS -// https://www.googleapis.com/compute/v1/projects/myproject/global/networks/mycustom -// projects/myproject/global/networks/mycustom -// All return "myproject" -func getProjectIDInURL(urlStr string) (string, error) { - fields := strings.Split(urlStr, "/") - for i, v := range fields { - if v == "projects" && i < len(fields)-1 { - return fields[i+1], nil - } - } - return "", fmt.Errorf("could not find project field in url: %v", urlStr) -} - // getRegionInURL parses full resource URLS and shorter URLS // https://www.googleapis.com/compute/v1/projects/myproject/regions/us-central1/subnetworks/a // projects/myproject/regions/us-central1/subnetworks/a @@ -684,6 +761,16 @@ func getNetwork(svc *compute.Service, networkProjectID, networkID string) (*comp return svc.Networks.Get(networkProjectID, networkID).Do() } +// listSubnetworksOfNetwork returns a list of subnetworks for a particular region of a network. +func listSubnetworksOfNetwork(svc *compute.Service, networkProjectID, networkID, region string) ([]*compute.Subnetwork, error) { + var subnets []*compute.Subnetwork + err := svc.Subnetworks.List(networkProjectID, region).Filter(fmt.Sprintf("network eq .*/%v$", networkID)).Pages(context.Background(), func(res *compute.SubnetworkList) error { + subnets = append(subnets, res.Items...) + return nil + }) + return subnets, err +} + // getProjectID returns the project's string ID given a project number or string func getProjectID(svc *compute.Service, projectNumberOrID string) (string, error) { proj, err := svc.Projects.Get(projectNumberOrID).Do() diff --git a/pkg/cloudprovider/providers/gce/gce_address_manager.go b/pkg/cloudprovider/providers/gce/gce_address_manager.go index 004148f249b..ad7c38b89d2 100644 --- a/pkg/cloudprovider/providers/gce/gce_address_manager.go +++ b/pkg/cloudprovider/providers/gce/gce_address_manager.go @@ -20,7 +20,7 @@ import ( "fmt" "net/http" - computebeta "google.golang.org/api/compute/v0.beta" + compute "google.golang.org/api/compute/v1" "github.com/golang/glog" ) @@ -63,7 +63,7 @@ func (am *addressManager) HoldAddress() (string, error) { // calls since it indicates whether a Delete is necessary before Reserve. glog.V(4).Infof("%v: attempting hold of IP %q Type %q", am.logPrefix, am.targetIP, am.addressType) // Get the address in case it was orphaned earlier - addr, err := am.svc.GetBetaRegionAddress(am.name, am.region) + addr, err := am.svc.GetRegionAddress(am.name, am.region) if err != nil && !isNotFound(err) { return "", err } @@ -118,7 +118,7 @@ func (am *addressManager) ReleaseAddress() error { func (am *addressManager) ensureAddressReservation() (string, error) { // Try reserving the IP with controller-owned address name // If am.targetIP is an empty string, a new IP will be created. - newAddr := &computebeta.Address{ + newAddr := &compute.Address{ Name: am.name, Description: fmt.Sprintf(`{"kubernetes.io/service-name":"%s"}`, am.serviceName), Address: am.targetIP, @@ -126,7 +126,7 @@ func (am *addressManager) ensureAddressReservation() (string, error) { Subnetwork: am.subnetURL, } - reserveErr := am.svc.ReserveBetaRegionAddress(newAddr, am.region) + reserveErr := am.svc.ReserveRegionAddress(newAddr, am.region) if reserveErr == nil { if newAddr.Address != "" { glog.V(4).Infof("%v: successfully reserved IP %q with name %q", am.logPrefix, newAddr.Address, newAddr.Name) @@ -155,7 +155,7 @@ func (am *addressManager) ensureAddressReservation() (string, error) { // Reserving the address failed due to a conflict or bad request. The address manager just checked that no address // exists with the name, so it may belong to the user. - addr, err := am.svc.GetBetaRegionAddressByIP(am.region, am.targetIP) + addr, err := am.svc.GetRegionAddressByIP(am.region, am.targetIP) if err != nil { return "", fmt.Errorf("failed to get address by IP %q after reservation attempt, err: %q, reservation err: %q", am.targetIP, err, reserveErr) } @@ -178,7 +178,7 @@ func (am *addressManager) ensureAddressReservation() (string, error) { return addr.Address, nil } -func (am *addressManager) validateAddress(addr *computebeta.Address) error { +func (am *addressManager) validateAddress(addr *compute.Address) error { if am.targetIP != "" && am.targetIP != addr.Address { return fmt.Errorf("address %q does not have the expected IP %q, actual: %q", addr.Name, am.targetIP, addr.Address) } @@ -189,7 +189,7 @@ func (am *addressManager) validateAddress(addr *computebeta.Address) error { return nil } -func (am *addressManager) isManagedAddress(addr *computebeta.Address) bool { +func (am *addressManager) isManagedAddress(addr *compute.Address) bool { return addr.Name == am.name } diff --git a/pkg/cloudprovider/providers/gce/gce_address_manager_test.go b/pkg/cloudprovider/providers/gce/gce_address_manager_test.go index 6a83171074d..1eee47cae93 100644 --- a/pkg/cloudprovider/providers/gce/gce_address_manager_test.go +++ b/pkg/cloudprovider/providers/gce/gce_address_manager_test.go @@ -21,7 +21,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - computebeta "google.golang.org/api/compute/v0.beta" + compute "google.golang.org/api/compute/v1" ) const testSvcName = "my-service" @@ -55,8 +55,8 @@ func TestAddressManagerOrphaned(t *testing.T) { svc := NewFakeCloudAddressService() targetIP := "1.1.1.1" - addr := &computebeta.Address{Name: testLBName, Address: targetIP, AddressType: string(schemeInternal)} - err := svc.ReserveBetaRegionAddress(addr, testRegion) + addr := &compute.Address{Name: testLBName, Address: targetIP, AddressType: string(schemeInternal)} + err := svc.ReserveRegionAddress(addr, testRegion) require.NoError(t, err) mgr := newAddressManager(svc, testSvcName, testRegion, testSubnet, testLBName, targetIP, schemeInternal) @@ -71,8 +71,8 @@ func TestAddressManagerOutdatedOrphan(t *testing.T) { previousAddress := "1.1.0.0" targetIP := "1.1.1.1" - addr := &computebeta.Address{Name: testLBName, Address: previousAddress, AddressType: string(schemeExternal)} - err := svc.ReserveBetaRegionAddress(addr, testRegion) + addr := &compute.Address{Name: testLBName, Address: previousAddress, AddressType: string(schemeExternal)} + err := svc.ReserveRegionAddress(addr, testRegion) require.NoError(t, err) mgr := newAddressManager(svc, testSvcName, testRegion, testSubnet, testLBName, targetIP, schemeInternal) @@ -86,8 +86,8 @@ func TestAddressManagerExternallyOwned(t *testing.T) { svc := NewFakeCloudAddressService() targetIP := "1.1.1.1" - addr := &computebeta.Address{Name: "my-important-address", Address: targetIP, AddressType: string(schemeInternal)} - err := svc.ReserveBetaRegionAddress(addr, testRegion) + addr := &compute.Address{Name: "my-important-address", Address: targetIP, AddressType: string(schemeInternal)} + err := svc.ReserveRegionAddress(addr, testRegion) require.NoError(t, err) mgr := newAddressManager(svc, testSvcName, testRegion, testSubnet, testLBName, targetIP, schemeInternal) @@ -107,8 +107,8 @@ func TestAddressManagerBadExternallyOwned(t *testing.T) { svc := NewFakeCloudAddressService() targetIP := "1.1.1.1" - addr := &computebeta.Address{Name: "my-important-address", Address: targetIP, AddressType: string(schemeExternal)} - err := svc.ReserveBetaRegionAddress(addr, testRegion) + addr := &compute.Address{Name: "my-important-address", Address: targetIP, AddressType: string(schemeExternal)} + err := svc.ReserveRegionAddress(addr, testRegion) require.NoError(t, err) mgr := newAddressManager(svc, testSvcName, testRegion, testSubnet, testLBName, targetIP, schemeInternal) @@ -121,7 +121,7 @@ func testHoldAddress(t *testing.T, mgr *addressManager, svc CloudAddressService, require.NoError(t, err) assert.NotEmpty(t, ipToUse) - addr, err := svc.GetBetaRegionAddress(name, region) + addr, err := svc.GetRegionAddress(name, region) require.NoError(t, err) if targetIP != "" { assert.EqualValues(t, targetIP, addr.Address) @@ -132,6 +132,6 @@ func testHoldAddress(t *testing.T, mgr *addressManager, svc CloudAddressService, func testReleaseAddress(t *testing.T, mgr *addressManager, svc CloudAddressService, name, region string) { err := mgr.ReleaseAddress() require.NoError(t, err) - _, err = svc.GetBetaRegionAddress(name, region) + _, err = svc.GetRegionAddress(name, region) assert.True(t, isNotFound(err)) } diff --git a/pkg/cloudprovider/providers/gce/gce_clusterid.go b/pkg/cloudprovider/providers/gce/gce_clusterid.go index 6f1b667c8ef..46b4ff4f6a0 100644 --- a/pkg/cloudprovider/providers/gce/gce_clusterid.go +++ b/pkg/cloudprovider/providers/gce/gce_clusterid.go @@ -101,7 +101,7 @@ func (gce *GCECloud) watchClusterID() { }, } - listerWatcher := cache.NewListWatchFromClient(gce.ClusterID.client.Core().RESTClient(), "configmaps", UIDNamespace, fields.Everything()) + listerWatcher := cache.NewListWatchFromClient(gce.ClusterID.client.CoreV1().RESTClient(), "configmaps", UIDNamespace, fields.Everything()) var controller cache.Controller gce.ClusterID.store, controller = cache.NewInformer(newSingleObjectListerWatcher(listerWatcher, UIDConfigMapName), &v1.ConfigMap{}, updateFuncFrequency, mapEventHandler) @@ -189,7 +189,7 @@ func (ci *ClusterID) getOrInitialize() error { UIDProvider: newId, } - if _, err := ci.client.Core().ConfigMaps(UIDNamespace).Create(cfg); err != nil { + if _, err := ci.client.CoreV1().ConfigMaps(UIDNamespace).Create(cfg); err != nil { glog.Errorf("GCE cloud provider failed to create %v config map to store cluster id: %v", ci.cfgMapKey, err) return err } diff --git a/pkg/cloudprovider/providers/gce/gce_disks.go b/pkg/cloudprovider/providers/gce/gce_disks.go index 48dfd18badd..7f9ad894d68 100644 --- a/pkg/cloudprovider/providers/gce/gce_disks.go +++ b/pkg/cloudprovider/providers/gce/gce_disks.go @@ -24,6 +24,7 @@ import ( "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/kubernetes/pkg/cloudprovider" @@ -90,6 +91,9 @@ type diskServiceManager interface { instanceName string, devicePath string) (gceObject, error) + ResizeDiskOnCloudProvider(disk *GCEDisk, sizeGb int64, zone string) (gceObject, error) + RegionalResizeDiskOnCloudProvider(disk *GCEDisk, sizeGb int64) (gceObject, error) + // Gets the persistent disk from GCE with the given diskName. GetDiskFromCloudProvider(zone string, diskName string) (*GCEDisk, error) @@ -169,7 +173,7 @@ func (manager *gceServiceManager) CreateRegionalDiskOnCloudProvider( manager.gce.projectID, manager.gce.region, diskToCreateAlpha).Do() } - return nil, fmt.Errorf("The regional PD feature is only available via the GCE Alpha API. Enable \"GCEDiskAlphaAPI\" in the list of \"alpha-features\" in \"gce.conf\" to use the feature.") + return nil, fmt.Errorf("The regional PD feature is only available via the GCE Alpha API. Enable \"DiskAlphaAPI\" in the list of \"alpha-features\" in \"gce.conf\" to use the feature.") } func (manager *gceServiceManager) AttachDiskOnCloudProvider( @@ -264,6 +268,7 @@ func (manager *gceServiceManager) GetDiskFromCloudProvider( Name: diskAlpha.Name, Kind: diskAlpha.Kind, Type: diskAlpha.Type, + SizeGb: diskAlpha.SizeGb, }, nil } @@ -289,6 +294,7 @@ func (manager *gceServiceManager) GetDiskFromCloudProvider( Name: diskStable.Name, Kind: diskStable.Kind, Type: diskStable.Type, + SizeGb: diskStable.SizeGb, }, nil } @@ -313,10 +319,11 @@ func (manager *gceServiceManager) GetRegionalDiskFromCloudProvider( Name: diskAlpha.Name, Kind: diskAlpha.Kind, Type: diskAlpha.Type, + SizeGb: diskAlpha.SizeGb, }, nil } - return nil, fmt.Errorf("The regional PD feature is only available via the GCE Alpha API. Enable \"GCEDiskAlphaAPI\" in the list of \"alpha-features\" in \"gce.conf\" to use the feature.") + return nil, fmt.Errorf("The regional PD feature is only available via the GCE Alpha API. Enable \"DiskAlphaAPI\" in the list of \"alpha-features\" in \"gce.conf\" to use the feature.") } func (manager *gceServiceManager) DeleteDiskOnCloudProvider( @@ -339,7 +346,7 @@ func (manager *gceServiceManager) DeleteRegionalDiskOnCloudProvider( manager.gce.projectID, manager.gce.region, diskName).Do() } - return nil, fmt.Errorf("DeleteRegionalDiskOnCloudProvider is a regional PD feature and is only available via the GCE Alpha API. Enable \"GCEDiskAlphaAPI\" in the list of \"alpha-features\" in \"gce.conf\" to use the feature.") + return nil, fmt.Errorf("DeleteRegionalDiskOnCloudProvider is a regional PD feature and is only available via the GCE Alpha API. Enable \"DiskAlphaAPI\" in the list of \"alpha-features\" in \"gce.conf\" to use the feature.") } func (manager *gceServiceManager) WaitForZoneOp( @@ -469,6 +476,30 @@ func (manager *gceServiceManager) getRegionFromZone(zoneInfo zoneType) (string, return region, nil } +func (manager *gceServiceManager) ResizeDiskOnCloudProvider(disk *GCEDisk, sizeGb int64, zone string) (gceObject, error) { + if manager.gce.AlphaFeatureGate.Enabled(AlphaFeatureGCEDisk) { + resizeServiceRequest := &computealpha.DisksResizeRequest{ + SizeGb: sizeGb, + } + return manager.gce.serviceAlpha.Disks.Resize(manager.gce.projectID, zone, disk.Name, resizeServiceRequest).Do() + + } + resizeServiceRequest := &compute.DisksResizeRequest{ + SizeGb: sizeGb, + } + return manager.gce.service.Disks.Resize(manager.gce.projectID, zone, disk.Name, resizeServiceRequest).Do() +} + +func (manager *gceServiceManager) RegionalResizeDiskOnCloudProvider(disk *GCEDisk, sizeGb int64) (gceObject, error) { + if manager.gce.AlphaFeatureGate.Enabled(AlphaFeatureGCEDisk) { + resizeServiceRequest := &computealpha.RegionDisksResizeRequest{ + SizeGb: sizeGb, + } + return manager.gce.serviceAlpha.RegionDisks.Resize(manager.gce.projectID, disk.Region, disk.Name, resizeServiceRequest).Do() + } + return nil, fmt.Errorf("RegionalResizeDiskOnCloudProvider is a regional PD feature and is only available via the GCE Alpha API. Enable \"DiskAlphaAPI\" in the list of \"alpha-features\" in \"gce.conf\" to use the feature.") +} + // Disks is interface for manipulation with GCE PDs. type Disks interface { // AttachDisk attaches given disk to the node with the specified NodeName. @@ -498,6 +529,9 @@ type Disks interface { // DeleteDisk deletes PD. DeleteDisk(diskToDelete string) error + // ResizeDisk resizes PD and returns new disk size + ResizeDisk(diskToResize string, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) + // GetAutoLabelsForPD returns labels to apply to PersistentVolume // representing this PD, namely failure domain and zone. // zone can be provided to specify the zone for the PD, @@ -517,6 +551,7 @@ type GCEDisk struct { Name string Kind string Type string + SizeGb int64 } type zoneType interface { @@ -690,11 +725,14 @@ func (gce *GCECloud) DisksAreAttached(diskNames []string, nodeName types.NodeNam // JSON in Description field. func (gce *GCECloud) CreateDisk( name string, diskType string, zone string, sizeGb int64, tags map[string]string) error { - - // Do not allow creation of PDs in zones that are not managed. Such PDs - // then cannot be deleted by DeleteDisk. - if isManaged := gce.verifyZoneIsManaged(zone); !isManaged { - return fmt.Errorf("kubernetes does not manage zone %q", zone) + // Do not allow creation of PDs in zones that are do not have nodes. Such PDs + // are not currently usable. + curZones, err := gce.GetAllCurrentZones() + if err != nil { + return err + } + if !curZones.Has(zone) { + return fmt.Errorf("kubernetes does not have a node in zone %q", zone) } tagsStr, err := gce.encodeDiskTags(tags) @@ -733,17 +771,16 @@ func (gce *GCECloud) CreateDisk( func (gce *GCECloud) CreateRegionalDisk( name string, diskType string, replicaZones sets.String, sizeGb int64, tags map[string]string) error { - // Do not allow creation of PDs in zones that are not managed. Such PDs - // then cannot be deleted by DeleteDisk. - unmanagedZones := []string{} - for _, zone := range replicaZones.UnsortedList() { - if isManaged := gce.verifyZoneIsManaged(zone); !isManaged { - unmanagedZones = append(unmanagedZones, zone) - } + // Do not allow creation of PDs in zones that are do not have nodes. Such PDs + // are not currently usable. This functionality should be reverted to checking + // against managed zones if we want users to be able to create RegionalDisks + // in zones where there are no nodes + curZones, err := gce.GetAllCurrentZones() + if err != nil { + return err } - - if len(unmanagedZones) > 0 { - return fmt.Errorf("kubernetes does not manage specified zones: %q. Managed Zones: %q", unmanagedZones, gce.managedZones) + if !curZones.IsSuperset(replicaZones) { + return fmt.Errorf("kubernetes does not have nodes in specified zones: %q. Zones that contain nodes: %q", replicaZones.Difference(curZones), curZones) } tagsStr, err := gce.encodeDiskTags(tags) @@ -776,16 +813,6 @@ func (gce *GCECloud) CreateRegionalDisk( return err } -func (gce *GCECloud) verifyZoneIsManaged(zone string) bool { - for _, managedZone := range gce.managedZones { - if zone == managedZone { - return true - } - } - - return false -} - func getDiskType(diskType string) (string, error) { switch diskType { case DiskTypeSSD, DiskTypeStandard: @@ -809,6 +836,57 @@ func (gce *GCECloud) DeleteDisk(diskToDelete string) error { return err } +// ResizeDisk expands given disk and returns new disk size +func (gce *GCECloud) ResizeDisk(diskToResize string, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) { + disk, err := gce.GetDiskByNameUnknownZone(diskToResize) + if err != nil { + return oldSize, err + } + + requestBytes := newSize.Value() + // GCE resizes in chunks of GBs (not GiB) + requestGB := volume.RoundUpSize(requestBytes, 1000*1000*1000) + newSizeQuant := resource.MustParse(fmt.Sprintf("%dG", requestGB)) + + // If disk is already of size equal or greater than requested size, we simply return + if disk.SizeGb >= requestGB { + return newSizeQuant, nil + } + + var mc *metricContext + + switch zoneInfo := disk.ZoneInfo.(type) { + case singleZone: + mc = newDiskMetricContextZonal("resize", disk.Region, zoneInfo.zone) + resizeOp, err := gce.manager.ResizeDiskOnCloudProvider(disk, requestGB, zoneInfo.zone) + + if err != nil { + return oldSize, mc.Observe(err) + } + waitErr := gce.manager.WaitForZoneOp(resizeOp, zoneInfo.zone, mc) + if waitErr != nil { + return oldSize, waitErr + } + return newSizeQuant, nil + case multiZone: + mc = newDiskMetricContextRegional("resize", disk.Region) + resizeOp, err := gce.manager.RegionalResizeDiskOnCloudProvider(disk, requestGB) + + if err != nil { + return oldSize, mc.Observe(err) + } + waitErr := gce.manager.WaitForRegionalOp(resizeOp, mc) + if waitErr != nil { + return oldSize, waitErr + } + return newSizeQuant, nil + case nil: + return oldSize, fmt.Errorf("PD has nil ZoneInfo: %v", disk) + default: + return oldSize, fmt.Errorf("disk.ZoneInfo has unexpected type %T", zoneInfo) + } +} + // Builds the labels that should be automatically added to a PersistentVolume backed by a GCE PD // Specifically, this builds FailureDomain (zone) and Region labels. // The PersistentVolumeLabel admission controller calls this and adds the labels when a PV is created. diff --git a/pkg/cloudprovider/providers/gce/gce_disks_test.go b/pkg/cloudprovider/providers/gce/gce_disks_test.go index 66a13e2ee82..34418d737a0 100644 --- a/pkg/cloudprovider/providers/gce/gce_disks_test.go +++ b/pkg/cloudprovider/providers/gce/gce_disks_test.go @@ -37,16 +37,19 @@ func TestCreateDisk_Basic(t *testing.T) { /* Arrange */ gceProjectId := "test-project" gceRegion := "fake-region" + zonesWithNodes := []string{"zone1"} fakeManager := newFakeManager(gceProjectId, gceRegion) alphaFeatureGate, featureGateErr := NewAlphaFeatureGate([]string{}) if featureGateErr != nil { t.Error(featureGateErr) } gce := GCECloud{ - manager: fakeManager, - managedZones: []string{"zone1"}, - projectID: gceProjectId, - AlphaFeatureGate: alphaFeatureGate, + manager: fakeManager, + managedZones: []string{"zone1"}, + projectID: gceProjectId, + AlphaFeatureGate: alphaFeatureGate, + nodeZones: createNodeZones(zonesWithNodes), + nodeInformerSynced: func() bool { return true }, } diskName := "disk" @@ -95,16 +98,20 @@ func TestCreateRegionalDisk_Basic(t *testing.T) { /* Arrange */ gceProjectId := "test-project" gceRegion := "fake-region" + zonesWithNodes := []string{"zone1", "zone3", "zone2"} fakeManager := newFakeManager(gceProjectId, gceRegion) alphaFeatureGate, featureGateErr := NewAlphaFeatureGate([]string{AlphaFeatureGCEDisk}) + if featureGateErr != nil { t.Error(featureGateErr) } gce := GCECloud{ - manager: fakeManager, - managedZones: []string{"zone1", "zone3", "zone2"}, - projectID: gceProjectId, - AlphaFeatureGate: alphaFeatureGate, + manager: fakeManager, + managedZones: zonesWithNodes, + projectID: gceProjectId, + AlphaFeatureGate: alphaFeatureGate, + nodeZones: createNodeZones(zonesWithNodes), + nodeInformerSynced: func() bool { return true }, } diskName := "disk" @@ -153,15 +160,18 @@ func TestCreateDisk_DiskAlreadyExists(t *testing.T) { /* Arrange */ gceProjectId := "test-project" gceRegion := "fake-region" + zonesWithNodes := []string{"zone1"} fakeManager := newFakeManager(gceProjectId, gceRegion) alphaFeatureGate, featureGateErr := NewAlphaFeatureGate([]string{}) if featureGateErr != nil { t.Error(featureGateErr) } gce := GCECloud{ - manager: fakeManager, - managedZones: []string{"zone1"}, - AlphaFeatureGate: alphaFeatureGate, + manager: fakeManager, + managedZones: zonesWithNodes, + AlphaFeatureGate: alphaFeatureGate, + nodeZones: createNodeZones(zonesWithNodes), + nodeInformerSynced: func() bool { return true }, } // Inject disk AlreadyExists error. @@ -184,8 +194,13 @@ func TestCreateDisk_WrongZone(t *testing.T) { /* Arrange */ gceProjectId := "test-project" gceRegion := "fake-region" + zonesWithNodes := []string{"zone1"} fakeManager := newFakeManager(gceProjectId, gceRegion) - gce := GCECloud{manager: fakeManager, managedZones: []string{"zone1"}} + gce := GCECloud{ + manager: fakeManager, + managedZones: zonesWithNodes, + nodeZones: createNodeZones(zonesWithNodes), + nodeInformerSynced: func() bool { return true }} diskName := "disk" diskType := DiskTypeSSD @@ -204,8 +219,13 @@ func TestCreateDisk_NoManagedZone(t *testing.T) { /* Arrange */ gceProjectId := "test-project" gceRegion := "fake-region" + zonesWithNodes := []string{} fakeManager := newFakeManager(gceProjectId, gceRegion) - gce := GCECloud{manager: fakeManager, managedZones: []string{}} + gce := GCECloud{ + manager: fakeManager, + managedZones: zonesWithNodes, + nodeZones: createNodeZones(zonesWithNodes), + nodeInformerSynced: func() bool { return true }} diskName := "disk" diskType := DiskTypeSSD @@ -224,8 +244,12 @@ func TestCreateDisk_BadDiskType(t *testing.T) { /* Arrange */ gceProjectId := "test-project" gceRegion := "fake-region" + zonesWithNodes := []string{"zone1"} fakeManager := newFakeManager(gceProjectId, gceRegion) - gce := GCECloud{manager: fakeManager, managedZones: []string{"zone1"}} + gce := GCECloud{manager: fakeManager, + managedZones: zonesWithNodes, + nodeZones: createNodeZones(zonesWithNodes), + nodeInformerSynced: func() bool { return true }} diskName := "disk" diskType := "arbitrary-disk" @@ -245,15 +269,18 @@ func TestCreateDisk_MultiZone(t *testing.T) { /* Arrange */ gceProjectId := "test-project" gceRegion := "fake-region" + zonesWithNodes := []string{"zone1", "zone2", "zone3"} fakeManager := newFakeManager(gceProjectId, gceRegion) alphaFeatureGate, featureGateErr := NewAlphaFeatureGate([]string{}) if featureGateErr != nil { t.Error(featureGateErr) } gce := GCECloud{ - manager: fakeManager, - managedZones: []string{"zone1", "zone2", "zone3"}, - AlphaFeatureGate: alphaFeatureGate, + manager: fakeManager, + managedZones: zonesWithNodes, + AlphaFeatureGate: alphaFeatureGate, + nodeZones: createNodeZones(zonesWithNodes), + nodeInformerSynced: func() bool { return true }, } diskName := "disk" @@ -274,15 +301,18 @@ func TestDeleteDisk_Basic(t *testing.T) { /* Arrange */ gceProjectId := "test-project" gceRegion := "fake-region" + zonesWithNodes := []string{"zone1"} fakeManager := newFakeManager(gceProjectId, gceRegion) alphaFeatureGate, featureGateErr := NewAlphaFeatureGate([]string{}) if featureGateErr != nil { t.Error(featureGateErr) } gce := GCECloud{ - manager: fakeManager, - managedZones: []string{"zone1"}, - AlphaFeatureGate: alphaFeatureGate, + manager: fakeManager, + managedZones: zonesWithNodes, + AlphaFeatureGate: alphaFeatureGate, + nodeZones: createNodeZones(zonesWithNodes), + nodeInformerSynced: func() bool { return true }, } diskName := "disk" diskType := DiskTypeSSD @@ -311,15 +341,18 @@ func TestDeleteDisk_NotFound(t *testing.T) { /* Arrange */ gceProjectId := "test-project" gceRegion := "fake-region" + zonesWithNodes := []string{"zone1"} fakeManager := newFakeManager(gceProjectId, gceRegion) alphaFeatureGate, featureGateErr := NewAlphaFeatureGate([]string{}) if featureGateErr != nil { t.Error(featureGateErr) } gce := GCECloud{ - manager: fakeManager, - managedZones: []string{"zone1"}, - AlphaFeatureGate: alphaFeatureGate, + manager: fakeManager, + managedZones: zonesWithNodes, + AlphaFeatureGate: alphaFeatureGate, + nodeZones: createNodeZones(zonesWithNodes), + nodeInformerSynced: func() bool { return true }, } diskName := "disk" @@ -336,15 +369,18 @@ func TestDeleteDisk_ResourceBeingUsed(t *testing.T) { /* Arrange */ gceProjectId := "test-project" gceRegion := "fake-region" + zonesWithNodes := []string{"zone1"} fakeManager := newFakeManager(gceProjectId, gceRegion) alphaFeatureGate, featureGateErr := NewAlphaFeatureGate([]string{}) if featureGateErr != nil { t.Error(featureGateErr) } gce := GCECloud{ - manager: fakeManager, - managedZones: []string{"zone1"}, - AlphaFeatureGate: alphaFeatureGate, + manager: fakeManager, + managedZones: zonesWithNodes, + AlphaFeatureGate: alphaFeatureGate, + nodeZones: createNodeZones(zonesWithNodes), + nodeInformerSynced: func() bool { return true }, } diskName := "disk" diskType := DiskTypeSSD @@ -367,15 +403,18 @@ func TestDeleteDisk_SameDiskMultiZone(t *testing.T) { /* Assert */ gceProjectId := "test-project" gceRegion := "fake-region" + zonesWithNodes := []string{"zone1", "zone2", "zone3"} fakeManager := newFakeManager(gceProjectId, gceRegion) alphaFeatureGate, featureGateErr := NewAlphaFeatureGate([]string{}) if featureGateErr != nil { t.Error(featureGateErr) } gce := GCECloud{ - manager: fakeManager, - managedZones: []string{"zone1", "zone2", "zone3"}, - AlphaFeatureGate: alphaFeatureGate, + manager: fakeManager, + managedZones: zonesWithNodes, + AlphaFeatureGate: alphaFeatureGate, + nodeZones: createNodeZones(zonesWithNodes), + nodeInformerSynced: func() bool { return true }, } diskName := "disk" diskType := DiskTypeSSD @@ -401,15 +440,18 @@ func TestDeleteDisk_DiffDiskMultiZone(t *testing.T) { /* Arrange */ gceProjectId := "test-project" gceRegion := "fake-region" + zonesWithNodes := []string{"zone1"} fakeManager := newFakeManager(gceProjectId, gceRegion) alphaFeatureGate, featureGateErr := NewAlphaFeatureGate([]string{}) if featureGateErr != nil { t.Error(featureGateErr) } gce := GCECloud{ - manager: fakeManager, - managedZones: []string{"zone1"}, - AlphaFeatureGate: alphaFeatureGate, + manager: fakeManager, + managedZones: zonesWithNodes, + AlphaFeatureGate: alphaFeatureGate, + nodeZones: createNodeZones(zonesWithNodes), + nodeInformerSynced: func() bool { return true }, } diskName := "disk" diskType := DiskTypeSSD @@ -435,19 +477,22 @@ func TestGetAutoLabelsForPD_Basic(t *testing.T) { /* Arrange */ gceProjectId := "test-project" gceRegion := "us-central1" + zone := "us-central1-c" + zonesWithNodes := []string{zone} fakeManager := newFakeManager(gceProjectId, gceRegion) diskName := "disk" diskType := DiskTypeSSD - zone := "us-central1-c" const sizeGb int64 = 128 alphaFeatureGate, featureGateErr := NewAlphaFeatureGate([]string{}) if featureGateErr != nil { t.Error(featureGateErr) } gce := GCECloud{ - manager: fakeManager, - managedZones: []string{zone}, - AlphaFeatureGate: alphaFeatureGate, + manager: fakeManager, + managedZones: zonesWithNodes, + AlphaFeatureGate: alphaFeatureGate, + nodeZones: createNodeZones(zonesWithNodes), + nodeInformerSynced: func() bool { return true }, } gce.CreateDisk(diskName, diskType, zone, sizeGb, nil) @@ -472,19 +517,22 @@ func TestGetAutoLabelsForPD_NoZone(t *testing.T) { /* Arrange */ gceProjectId := "test-project" gceRegion := "europe-west1" + zone := "europe-west1-d" + zonesWithNodes := []string{zone} fakeManager := newFakeManager(gceProjectId, gceRegion) diskName := "disk" diskType := DiskTypeStandard - zone := "europe-west1-d" const sizeGb int64 = 128 alphaFeatureGate, featureGateErr := NewAlphaFeatureGate([]string{}) if featureGateErr != nil { t.Error(featureGateErr) } gce := GCECloud{ - manager: fakeManager, - managedZones: []string{zone}, - AlphaFeatureGate: alphaFeatureGate, + manager: fakeManager, + managedZones: zonesWithNodes, + AlphaFeatureGate: alphaFeatureGate, + nodeZones: createNodeZones(zonesWithNodes), + nodeInformerSynced: func() bool { return true }, } gce.CreateDisk(diskName, diskType, zone, sizeGb, nil) @@ -508,10 +556,14 @@ func TestGetAutoLabelsForPD_DiskNotFound(t *testing.T) { /* Arrange */ gceProjectId := "test-project" gceRegion := "fake-region" + zone := "asia-northeast1-a" + zonesWithNodes := []string{zone} fakeManager := newFakeManager(gceProjectId, gceRegion) diskName := "disk" - zone := "asia-northeast1-a" - gce := GCECloud{manager: fakeManager, managedZones: []string{zone}} + gce := GCECloud{manager: fakeManager, + managedZones: zonesWithNodes, + nodeZones: createNodeZones(zonesWithNodes), + nodeInformerSynced: func() bool { return true }} /* Act */ _, err := gce.GetAutoLabelsForPD(diskName, zone) @@ -526,6 +578,7 @@ func TestGetAutoLabelsForPD_DiskNotFoundAndNoZone(t *testing.T) { /* Arrange */ gceProjectId := "test-project" gceRegion := "fake-region" + zonesWithNodes := []string{} fakeManager := newFakeManager(gceProjectId, gceRegion) diskName := "disk" alphaFeatureGate, featureGateErr := NewAlphaFeatureGate([]string{}) @@ -533,9 +586,11 @@ func TestGetAutoLabelsForPD_DiskNotFoundAndNoZone(t *testing.T) { t.Error(featureGateErr) } gce := GCECloud{ - manager: fakeManager, - managedZones: []string{}, - AlphaFeatureGate: alphaFeatureGate, + manager: fakeManager, + managedZones: zonesWithNodes, + AlphaFeatureGate: alphaFeatureGate, + nodeZones: createNodeZones(zonesWithNodes), + nodeInformerSynced: func() bool { return true }, } /* Act */ @@ -551,6 +606,7 @@ func TestGetAutoLabelsForPD_DupDisk(t *testing.T) { /* Arrange */ gceProjectId := "test-project" gceRegion := "us-west1" + zonesWithNodes := []string{"us-west1-b", "asia-southeast1-a"} fakeManager := newFakeManager(gceProjectId, gceRegion) diskName := "disk" diskType := DiskTypeStandard @@ -562,9 +618,11 @@ func TestGetAutoLabelsForPD_DupDisk(t *testing.T) { t.Error(featureGateErr) } gce := GCECloud{ - manager: fakeManager, - managedZones: []string{"us-west1-b", "asia-southeast1-a"}, - AlphaFeatureGate: alphaFeatureGate, + manager: fakeManager, + managedZones: zonesWithNodes, + AlphaFeatureGate: alphaFeatureGate, + nodeZones: createNodeZones(zonesWithNodes), + nodeInformerSynced: func() bool { return true }, } for _, zone := range gce.managedZones { gce.CreateDisk(diskName, diskType, zone, sizeGb, nil) @@ -590,6 +648,7 @@ func TestGetAutoLabelsForPD_DupDiskNoZone(t *testing.T) { /* Arrange */ gceProjectId := "test-project" gceRegion := "fake-region" + zonesWithNodes := []string{"us-west1-b", "asia-southeast1-a"} fakeManager := newFakeManager(gceProjectId, gceRegion) diskName := "disk" diskType := DiskTypeStandard @@ -600,9 +659,11 @@ func TestGetAutoLabelsForPD_DupDiskNoZone(t *testing.T) { t.Error(featureGateErr) } gce := GCECloud{ - manager: fakeManager, - managedZones: []string{"us-west1-b", "asia-southeast1-a"}, - AlphaFeatureGate: alphaFeatureGate, + manager: fakeManager, + managedZones: zonesWithNodes, + AlphaFeatureGate: alphaFeatureGate, + nodeZones: createNodeZones(zonesWithNodes), + nodeInformerSynced: func() bool { return true }, } for _, zone := range gce.managedZones { gce.CreateDisk(diskName, diskType, zone, sizeGb, nil) @@ -836,6 +897,19 @@ func (manager *FakeServiceManager) GetRegionalDiskFromCloudProvider( }, nil } +func (manager *FakeServiceManager) ResizeDiskOnCloudProvider( + disk *GCEDisk, + size int64, + zone string) (gceObject, error) { + panic("Not implmented") +} + +func (manager *FakeServiceManager) RegionalResizeDiskOnCloudProvider( + disk *GCEDisk, + size int64) (gceObject, error) { + panic("Not implemented") +} + /** * Disk info is removed from the FakeServiceManager. */ @@ -925,3 +999,11 @@ func (manager *FakeServiceManager) WaitForRegionalOp( } return manager.waitForOpError } + +func createNodeZones(zones []string) map[string]sets.String { + nodeZones := map[string]sets.String{} + for _, zone := range zones { + nodeZones[zone] = sets.NewString("dummynode") + } + return nodeZones +} diff --git a/pkg/cloudprovider/providers/gce/gce_instances.go b/pkg/cloudprovider/providers/gce/gce_instances.go index f55b06d6c40..2a17d442999 100644 --- a/pkg/cloudprovider/providers/gce/gce_instances.go +++ b/pkg/cloudprovider/providers/gce/gce_instances.go @@ -116,19 +116,35 @@ func (gce *GCECloud) NodeAddressesByProviderID(providerID string) ([]v1.NodeAddr return nodeAddresses, nil } +// instanceByProviderID returns the cloudprovider instance of the node +// with the specified unique providerID +func (gce *GCECloud) instanceByProviderID(providerID string) (*gceInstance, error) { + project, zone, name, err := splitProviderID(providerID) + if err != nil { + return nil, err + } + + instance, err := gce.getInstanceFromProjectInZoneByName(project, zone, name) + if err != nil { + if isHTTPErrorCode(err, http.StatusNotFound) { + return nil, cloudprovider.InstanceNotFound + } + return nil, err + } + + return instance, nil +} + // InstanceTypeByProviderID returns the cloudprovider instance type of the node // with the specified unique providerID This method will not be called from the // node that is requesting this ID. i.e. metadata service and other local // methods cannot be used here func (gce *GCECloud) InstanceTypeByProviderID(providerID string) (string, error) { - project, zone, name, err := splitProviderID(providerID) - if err != nil { - return "", err - } - instance, err := gce.getInstanceFromProjectInZoneByName(project, zone, name) + instance, err := gce.instanceByProviderID(providerID) if err != nil { return "", err } + return instance.Type, nil } @@ -156,7 +172,15 @@ func (gce *GCECloud) ExternalID(nodeName types.NodeName) (string, error) { // InstanceExistsByProviderID returns true if the instance with the given provider id still exists and is running. // If false is returned with no error, the instance will be immediately deleted by the cloud controller manager. func (gce *GCECloud) InstanceExistsByProviderID(providerID string) (bool, error) { - return false, cloudprovider.NotImplemented + _, err := gce.instanceByProviderID(providerID) + if err != nil { + if err == cloudprovider.InstanceNotFound { + return false, nil + } + return false, err + } + + return true, nil } // InstanceID returns the cloud provider ID of the node with the specified NodeName. @@ -249,35 +273,39 @@ func (gce *GCECloud) AddSSHKeyToAllInstances(user string, keyData []byte) error }) } -// GetAllZones returns all the zones in which nodes are running -func (gce *GCECloud) GetAllZones() (sets.String, error) { - // Fast-path for non-multizone - if len(gce.managedZones) == 1 { - return sets.NewString(gce.managedZones...), nil +// GetAllCurrentZones returns all the zones in which k8s nodes are currently running +func (gce *GCECloud) GetAllCurrentZones() (sets.String, error) { + if gce.nodeInformerSynced == nil { + glog.Warningf("GCECloud object does not have informers set, should only happen in E2E binary.") + return gce.GetAllZonesFromCloudProvider() } + gce.nodeZonesLock.Lock() + defer gce.nodeZonesLock.Unlock() + if !gce.nodeInformerSynced() { + return nil, fmt.Errorf("Node informer is not synced when trying to GetAllCurrentZones") + } + zones := sets.NewString() + for zone, nodes := range gce.nodeZones { + if len(nodes) > 0 { + zones.Insert(zone) + } + } + return zones, nil +} - // TODO: Caching, but this is currently only called when we are creating a volume, - // which is a relatively infrequent operation, and this is only # zones API calls +// GetAllZonesFromCloudProvider returns all the zones in which nodes are running +// Only use this in E2E tests to get zones, on real clusters this will +// get all zones with compute instances in them even if not k8s instances!!! +// ex. I have k8s nodes in us-central1-c and us-central1-b. I also have +// a non-k8s compute in us-central1-a. This func will return a,b, and c. +func (gce *GCECloud) GetAllZonesFromCloudProvider() (sets.String, error) { zones := sets.NewString() - // TODO: Parallelize, although O(zones) so not too bad (N <= 3 typically) for _, zone := range gce.managedZones { mc := newInstancesMetricContext("list", zone) // We only retrieve one page in each zone - we only care about existence listCall := gce.service.Instances.List(gce.projectID, zone) - // No filter: We assume that a zone is either used or unused - // We could only consider running nodes (like we do in List above), - // but probably if instances are starting we still want to consider them. - // I think we should wait until we have a reason to make the - // call one way or the other; we generally can't guarantee correct - // volume spreading if the set of zones is changing - // (and volume spreading is currently only a heuristic). - // Long term we want to replace GetAllZones (which primarily supports volume - // spreading) with a scheduler policy that is able to see the global state of - // volumes and the health of zones. - - // Just a minimal set of fields - we only care about existence listCall = listCall.Fields("items(name)") res, err := listCall.Do() if err != nil { @@ -293,6 +321,16 @@ func (gce *GCECloud) GetAllZones() (sets.String, error) { return zones, nil } +// InsertInstance creates a new instance on GCP +func (gce *GCECloud) InsertInstance(project string, zone string, rb *compute.Instance) error { + mc := newInstancesMetricContext("create", zone) + op, err := gce.service.Instances.Insert(project, zone, rb).Do() + if err != nil { + return mc.Observe(err) + } + return gce.waitForZoneOp(op, zone, mc) +} + // ListInstanceNames returns a string of instance names seperated by spaces. func (gce *GCECloud) ListInstanceNames(project, zone string) (string, error) { res, err := gce.service.Instances.List(project, zone).Fields("items(name)").Do() @@ -371,7 +409,7 @@ func (gce *GCECloud) AddAliasToInstance(nodeName types.NodeName, alias *net.IPNe mc := newInstancesMetricContext("addalias", v1instance.Zone) op, err := gce.serviceAlpha.Instances.UpdateNetworkInterface( - gce.projectID, instance.Zone, instance.Name, iface.Name, iface).Do() + gce.projectID, lastComponent(instance.Zone), instance.Name, iface.Name, iface).Do() if err != nil { return mc.Observe(err) } @@ -464,6 +502,7 @@ func (gce *GCECloud) getInstanceByName(name string) (*gceInstance, error) { if isHTTPErrorCode(err, http.StatusNotFound) { continue } + glog.Errorf("getInstanceByName: failed to get instance %s in zone %s; err: %v", name, zone, err) return nil, err } return instance, nil @@ -478,7 +517,6 @@ func (gce *GCECloud) getInstanceFromProjectInZoneByName(project, zone, name stri res, err := gce.service.Instances.Get(project, zone, name).Do() mc.Observe(err) if err != nil { - glog.Errorf("getInstanceFromProjectInZoneByName: failed to get instance %s; err: %v", name, err) return nil, err } diff --git a/pkg/cloudprovider/providers/gce/gce_loadbalancer.go b/pkg/cloudprovider/providers/gce/gce_loadbalancer.go index 6b8995b2f50..3d8f68e6f84 100644 --- a/pkg/cloudprovider/providers/gce/gce_loadbalancer.go +++ b/pkg/cloudprovider/providers/gce/gce_loadbalancer.go @@ -20,6 +20,7 @@ import ( "flag" "fmt" "net" + "sort" "strings" "github.com/golang/glog" @@ -62,7 +63,9 @@ func init() { // String is the method to format the flag's value, part of the flag.Value interface. func (c *cidrs) String() string { - return strings.Join(c.ipn.StringSlice(), ",") + s := c.ipn.StringSlice() + sort.Strings(s) + return strings.Join(s, ",") } // Set supports a value of CSV or the flag repeated multiple times diff --git a/pkg/cloudprovider/providers/gce/gce_loadbalancer_external.go b/pkg/cloudprovider/providers/gce/gce_loadbalancer_external.go index a7f7b55b1e5..aabef25feb7 100644 --- a/pkg/cloudprovider/providers/gce/gce_loadbalancer_external.go +++ b/pkg/cloudprovider/providers/gce/gce_loadbalancer_external.go @@ -66,18 +66,17 @@ func (gce *GCECloud) ensureExternalLoadBalancer(clusterName, clusterID string, a affinityType := apiService.Spec.SessionAffinity serviceName := types.NamespacedName{Namespace: apiService.Namespace, Name: apiService.Name} - glog.V(2).Infof("EnsureLoadBalancer(%v, %v, %v, %v, %v, %v, %v)", - loadBalancerName, gce.region, requestedIP, portStr, hostNames, serviceName, apiService.Annotations) - lbRefStr := fmt.Sprintf("%v(%v)", loadBalancerName, serviceName) + glog.V(2).Infof("ensureExternalLoadBalancer(%s, %v, %v, %v, %v, %v)", lbRefStr, gce.region, requestedIP, portStr, hostNames, apiService.Annotations) + // Check the current and the desired network tiers. If they do not match, // tear down the existing resources with the wrong tier. netTier, err := gce.getServiceNetworkTier(apiService) if err != nil { - glog.Errorf("EnsureLoadBalancer(%s): failed to get the desired network tier: %v", lbRefStr, err) + glog.Errorf("ensureExternalLoadBalancer(%s): Failed to get the desired network tier: %v.", lbRefStr, err) return nil, err } - glog.V(4).Infof("EnsureLoadBalancer(%s): desired network tier %q ", lbRefStr, netTier) + glog.V(4).Infof("ensureExternalLoadBalancer(%s): Desired network tier %q.", lbRefStr, netTier) if gce.AlphaFeatureGate.Enabled(AlphaFeatureNetworkTiers) { gce.deleteWrongNetworkTieredResources(loadBalancerName, lbRefStr, netTier) } @@ -88,8 +87,7 @@ func (gce *GCECloud) ensureExternalLoadBalancer(clusterName, clusterID string, a return nil, err } if !fwdRuleExists { - glog.V(2).Infof("Forwarding rule %v for Service %v/%v doesn't exist", - loadBalancerName, apiService.Namespace, apiService.Name) + glog.V(2).Infof("ensureExternalLoadBalancer(%s): Forwarding rule %v doesn't exist.", lbRefStr, loadBalancerName) } // Make sure we know which IP address will be used and have properly reserved @@ -124,14 +122,14 @@ func (gce *GCECloud) ensureExternalLoadBalancer(clusterName, clusterID string, a } if isSafeToReleaseIP { if err := gce.DeleteRegionAddress(loadBalancerName, gce.region); err != nil && !isNotFound(err) { - glog.Errorf("Failed to release static IP %s for load balancer (%v(%v), %v): %v", ipAddressToUse, loadBalancerName, serviceName, gce.region, err) + glog.Errorf("ensureExternalLoadBalancer(%s): Failed to release static IP %s in region %v: %v.", lbRefStr, ipAddressToUse, gce.region, err) } else if isNotFound(err) { - glog.V(2).Infof("EnsureLoadBalancer(%v(%v)): address %s is not reserved.", loadBalancerName, serviceName, ipAddressToUse) + glog.V(2).Infof("ensureExternalLoadBalancer(%s): IP address %s is not reserved.", lbRefStr, ipAddressToUse) } else { - glog.V(2).Infof("EnsureLoadBalancer(%v(%v)): released static IP %s", loadBalancerName, serviceName, ipAddressToUse) + glog.Infof("ensureExternalLoadBalancer(%s): Released static IP %s.", lbRefStr, ipAddressToUse) } } else { - glog.Warningf("orphaning static IP %s during update of load balancer (%v(%v), %v): %v", ipAddressToUse, loadBalancerName, serviceName, gce.region, err) + glog.Warningf("ensureExternalLoadBalancer(%s): Orphaning static IP %s in region %v: %v.", lbRefStr, ipAddressToUse, gce.region, err) } }() @@ -150,9 +148,9 @@ func (gce *GCECloud) ensureExternalLoadBalancer(clusterName, clusterID string, a // emphemeral IP used by the fwd rule, or create a new static IP. ipAddr, existed, err := ensureStaticIP(gce, loadBalancerName, serviceName.String(), gce.region, fwdRuleIP, netTier) if err != nil { - return nil, fmt.Errorf("failed to ensure a static IP for the LB: %v", err) + return nil, fmt.Errorf("failed to ensure a static IP for load balancer (%s): %v", lbRefStr, err) } - glog.V(4).Infof("EnsureLoadBalancer(%s): ensured IP address %s (tier: %s)", lbRefStr, ipAddr, netTier) + glog.Infof("ensureExternalLoadBalancer(%s): Ensured IP address %s (tier: %s).", lbRefStr, ipAddr, netTier) // If the IP was not owned by the user, but it already existed, it // could indicate that the previous update cycle failed. We can use // this IP and try to run through the process again, but we should @@ -180,17 +178,17 @@ func (gce *GCECloud) ensureExternalLoadBalancer(clusterName, clusterID string, a // Unlike forwarding rules and target pools, firewalls can be updated // without needing to be deleted and recreated. if firewallExists { - glog.Infof("EnsureLoadBalancer(%v(%v)): updating firewall", loadBalancerName, serviceName) + glog.Infof("ensureExternalLoadBalancer(%s): Updating firewall.", lbRefStr) if err := gce.updateFirewall(apiService, MakeFirewallName(loadBalancerName), gce.region, desc, sourceRanges, ports, hosts); err != nil { return nil, err } - glog.Infof("EnsureLoadBalancer(%v(%v)): updated firewall", loadBalancerName, serviceName) + glog.Infof("ensureExternalLoadBalancer(%s): Updated firewall.", lbRefStr) } else { - glog.Infof("EnsureLoadBalancer(%v(%v)): creating firewall", loadBalancerName, serviceName) + glog.Infof("ensureExternalLoadBalancer(%s): Creating firewall.", lbRefStr) if err := gce.createFirewall(apiService, MakeFirewallName(loadBalancerName), gce.region, desc, sourceRanges, ports, hosts); err != nil { return nil, err } - glog.Infof("EnsureLoadBalancer(%v(%v)): created firewall", loadBalancerName, serviceName) + glog.Infof("ensureExternalLoadBalancer(%s): Created firewall.", lbRefStr) } } @@ -199,7 +197,7 @@ func (gce *GCECloud) ensureExternalLoadBalancer(clusterName, clusterID string, a return nil, err } if !tpExists { - glog.Infof("Target pool %v for Service %v/%v doesn't exist", loadBalancerName, apiService.Namespace, apiService.Name) + glog.Infof("ensureExternalLoadBalancer(%s): Target pool for service doesn't exist.", lbRefStr) } // Check which health check needs to create and which health check needs to delete. @@ -207,15 +205,15 @@ func (gce *GCECloud) ensureExternalLoadBalancer(clusterName, clusterID string, a var hcToCreate, hcToDelete *compute.HttpHealthCheck hcLocalTrafficExisting, err := gce.GetHttpHealthCheck(loadBalancerName) if err != nil && !isHTTPErrorCode(err, http.StatusNotFound) { - return nil, fmt.Errorf("error checking HTTP health check %s: %v", loadBalancerName, err) + return nil, fmt.Errorf("error checking HTTP health check for load balancer (%s): %v", lbRefStr, err) } if path, healthCheckNodePort := apiservice.GetServiceHealthCheckPathPort(apiService); path != "" { - glog.V(4).Infof("service %v (%v) needs local traffic health checks on: %d%s)", apiService.Name, loadBalancerName, healthCheckNodePort, path) + glog.V(4).Infof("ensureExternalLoadBalancer(%s): Service needs local traffic health checks on: %d%s.", lbRefStr, healthCheckNodePort, path) if hcLocalTrafficExisting == nil { // This logic exists to detect a transition for non-OnlyLocal to OnlyLocal service // turn on the tpNeedsUpdate flag to delete/recreate fwdrule/tpool updating the // target pool to use local traffic health check. - glog.V(2).Infof("Updating from nodes health checks to local traffic health checks for service %v LB %v", apiService.Name, loadBalancerName) + glog.V(2).Infof("ensureExternalLoadBalancer(%s): Updating from nodes health checks to local traffic health checks.", lbRefStr) if supportsNodesHealthCheck { hcToDelete = makeHttpHealthCheck(MakeNodesHealthCheckName(clusterID), GetNodesHealthCheckPath(), GetNodesHealthCheckPort()) } @@ -223,12 +221,12 @@ func (gce *GCECloud) ensureExternalLoadBalancer(clusterName, clusterID string, a } hcToCreate = makeHttpHealthCheck(loadBalancerName, path, healthCheckNodePort) } else { - glog.V(4).Infof("Service %v needs nodes health checks.", apiService.Name) + glog.V(4).Infof("ensureExternalLoadBalancer(%s): Service needs nodes health checks.", lbRefStr) if hcLocalTrafficExisting != nil { // This logic exists to detect a transition from OnlyLocal to non-OnlyLocal service // and turn on the tpNeedsUpdate flag to delete/recreate fwdrule/tpool updating the // target pool to use nodes health check. - glog.V(2).Infof("Updating from local traffic health checks to nodes health checks for service %v LB %v", apiService.Name, loadBalancerName) + glog.V(2).Infof("ensureExternalLoadBalancer(%s): Updating from local traffic health checks to nodes health checks.", lbRefStr) hcToDelete = hcLocalTrafficExisting tpNeedsUpdate = true } @@ -249,9 +247,9 @@ func (gce *GCECloud) ensureExternalLoadBalancer(clusterName, clusterID string, a // IP. That way we can come back to it later. isSafeToReleaseIP = false if err := gce.DeleteRegionForwardingRule(loadBalancerName, gce.region); err != nil && !isNotFound(err) { - return nil, fmt.Errorf("failed to delete existing forwarding rule %s for load balancer update: %v", loadBalancerName, err) + return nil, fmt.Errorf("failed to delete existing forwarding rule for load balancer (%s) update: %v", lbRefStr, err) } - glog.Infof("EnsureLoadBalancer(%v(%v)): deleted forwarding rule", loadBalancerName, serviceName) + glog.Infof("ensureExternalLoadBalancer(%s): Deleted forwarding rule.", lbRefStr) } if tpExists && tpNeedsUpdate { // Pass healthchecks to DeleteExternalTargetPoolAndChecks to cleanup health checks after cleaning up the target pool itself. @@ -260,9 +258,9 @@ func (gce *GCECloud) ensureExternalLoadBalancer(clusterName, clusterID string, a hcNames = append(hcNames, hcToDelete.Name) } if err := gce.DeleteExternalTargetPoolAndChecks(apiService, loadBalancerName, gce.region, clusterID, hcNames...); err != nil { - return nil, fmt.Errorf("failed to delete existing target pool %s for load balancer update: %v", loadBalancerName, err) + return nil, fmt.Errorf("failed to delete existing target pool for load balancer (%s) update: %v", lbRefStr, err) } - glog.Infof("EnsureLoadBalancer(%v(%v)): deleted target pool", loadBalancerName, serviceName) + glog.Infof("ensureExternalLoadBalancer(%s): Deleted target pool.", lbRefStr) } // Once we've deleted the resources (if necessary), build them back up (or for @@ -274,37 +272,37 @@ func (gce *GCECloud) ensureExternalLoadBalancer(clusterName, clusterID string, a } // Pass healthchecks to createTargetPool which needs them as health check links in the target pool if err := gce.createTargetPool(apiService, loadBalancerName, serviceName.String(), ipAddressToUse, gce.region, clusterID, createInstances, affinityType, hcToCreate); err != nil { - return nil, fmt.Errorf("failed to create target pool %s: %v", loadBalancerName, err) + return nil, fmt.Errorf("failed to create target pool for load balancer (%s): %v", lbRefStr, err) } if hcToCreate != nil { - glog.Infof("EnsureLoadBalancer(%v(%v)): created health checks %v for target pool", loadBalancerName, serviceName, hcToCreate.Name) + glog.Infof("ensureExternalLoadBalancer(%s): Created health checks %v.", lbRefStr, hcToCreate.Name) } if len(hosts) <= maxTargetPoolCreateInstances { - glog.Infof("EnsureLoadBalancer(%v(%v)): created target pool", loadBalancerName, serviceName) + glog.Infof("ensureExternalLoadBalancer(%s): Created target pool.", lbRefStr) } else { - glog.Infof("EnsureLoadBalancer(%v(%v)): created initial target pool (now updating with %d hosts)", loadBalancerName, serviceName, len(hosts)-maxTargetPoolCreateInstances) + glog.Infof("ensureExternalLoadBalancer(%s): Created initial target pool (now updating with %d hosts).", lbRefStr, len(hosts)-maxTargetPoolCreateInstances) created := sets.NewString() for _, host := range createInstances { created.Insert(host.makeComparableHostPath()) } if err := gce.updateTargetPool(loadBalancerName, created, hosts); err != nil { - return nil, fmt.Errorf("failed to update target pool %s: %v", loadBalancerName, err) + return nil, fmt.Errorf("failed to update target pool for load balancer (%s): %v", lbRefStr, err) } - glog.Infof("EnsureLoadBalancer(%v(%v)): updated target pool (with %d hosts)", loadBalancerName, serviceName, len(hosts)-maxTargetPoolCreateInstances) + glog.Infof("ensureExternalLoadBalancer(%s): Updated target pool (with %d hosts).", lbRefStr, len(hosts)-maxTargetPoolCreateInstances) } } if tpNeedsUpdate || fwdRuleNeedsUpdate { - glog.Infof("EnsureLoadBalancer(%v(%v)): creating forwarding rule, IP %s (tier: %s)", loadBalancerName, serviceName, ipAddressToUse, netTier) + glog.Infof("ensureExternalLoadBalancer(%s): Creating forwarding rule, IP %s (tier: %s).", lbRefStr, ipAddressToUse, netTier) if err := createForwardingRule(gce, loadBalancerName, serviceName.String(), gce.region, ipAddressToUse, gce.targetPoolURL(loadBalancerName), ports, netTier); err != nil { - return nil, fmt.Errorf("failed to create forwarding rule %s: %v", loadBalancerName, err) + return nil, fmt.Errorf("failed to create forwarding rule for load balancer (%s): %v", lbRefStr, err) } // End critical section. It is safe to release the static IP (which // just demotes it to ephemeral) now that it is attached. In the case // of a user-requested IP, the "is user-owned" flag will be set, // preventing it from actually being released. isSafeToReleaseIP = true - glog.Infof("EnsureLoadBalancer(%v(%v)): created forwarding rule, IP %s", loadBalancerName, serviceName, ipAddressToUse) + glog.Infof("ensureExternalLoadBalancer(%s): Created forwarding rule, IP %s.", lbRefStr, ipAddressToUse) } status := &v1.LoadBalancerStatus{} @@ -336,15 +334,20 @@ func (gce *GCECloud) updateExternalLoadBalancer(clusterName string, service *v1. // ensureExternalLoadBalancerDeleted is the external implementation of LoadBalancer.EnsureLoadBalancerDeleted func (gce *GCECloud) ensureExternalLoadBalancerDeleted(clusterName, clusterID string, service *v1.Service) error { loadBalancerName := cloudprovider.GetLoadBalancerName(service) + serviceName := types.NamespacedName{Namespace: service.Namespace, Name: service.Name} + lbRefStr := fmt.Sprintf("%v(%v)", loadBalancerName, serviceName) var hcNames []string if path, _ := apiservice.GetServiceHealthCheckPathPort(service); path != "" { hcToDelete, err := gce.GetHttpHealthCheck(loadBalancerName) if err != nil && !isHTTPErrorCode(err, http.StatusNotFound) { - glog.Infof("Failed to retrieve health check %v:%v", loadBalancerName, err) + glog.Infof("ensureExternalLoadBalancerDeleted(%s): Failed to retrieve health check:%v.", lbRefStr, err) return err } - hcNames = append(hcNames, hcToDelete.Name) + // If we got 'StatusNotFound' LB was already deleted and it's safe to ignore. + if err == nil { + hcNames = append(hcNames, hcToDelete.Name) + } } else { // EnsureLoadBalancerDeleted() could be triggered by changing service from // LoadBalancer type to others. In this case we have no idea whether it was @@ -356,10 +359,11 @@ func (gce *GCECloud) ensureExternalLoadBalancerDeleted(clusterName, clusterID st errs := utilerrors.AggregateGoroutines( func() error { + glog.Infof("ensureExternalLoadBalancerDeleted(%s): Deleting firewall rule.", lbRefStr) fwName := MakeFirewallName(loadBalancerName) err := ignoreNotFound(gce.DeleteFirewall(fwName)) if isForbidden(err) && gce.OnXPN() { - glog.V(4).Infof("ensureExternalLoadBalancerDeleted(%v): do not have permission to delete firewall rule (on XPN). Raising event.", loadBalancerName) + glog.V(4).Infof("ensureExternalLoadBalancerDeleted(%s): Do not have permission to delete firewall rule %v (on XPN). Raising event.", lbRefStr, fwName) gce.raiseFirewallChangeNeededEvent(service, FirewallToGCloudDeleteCmd(fwName, gce.NetworkProjectID())) return nil } @@ -368,13 +372,18 @@ func (gce *GCECloud) ensureExternalLoadBalancerDeleted(clusterName, clusterID st // Even though we don't hold on to static IPs for load balancers, it's // possible that EnsureLoadBalancer left one around in a failed // creation/update attempt, so make sure we clean it up here just in case. - func() error { return ignoreNotFound(gce.DeleteRegionAddress(loadBalancerName, gce.region)) }, func() error { + glog.Infof("ensureExternalLoadBalancerDeleted(%s): Deleting IP address.", lbRefStr) + return ignoreNotFound(gce.DeleteRegionAddress(loadBalancerName, gce.region)) + }, + func() error { + glog.Infof("ensureExternalLoadBalancerDeleted(%s): Deleting forwarding rule.", lbRefStr) // The forwarding rule must be deleted before either the target pool can, // unfortunately, so we have to do these two serially. if err := ignoreNotFound(gce.DeleteRegionForwardingRule(loadBalancerName, gce.region)); err != nil { return err } + glog.Infof("ensureExternalLoadBalancerDeleted(%s): Deleting target pool.", lbRefStr) if err := gce.DeleteExternalTargetPoolAndChecks(service, loadBalancerName, gce.region, clusterID, hcNames...); err != nil { return err } @@ -388,10 +397,13 @@ func (gce *GCECloud) ensureExternalLoadBalancerDeleted(clusterName, clusterID st } func (gce *GCECloud) DeleteExternalTargetPoolAndChecks(service *v1.Service, name, region, clusterID string, hcNames ...string) error { + serviceName := types.NamespacedName{Namespace: service.Namespace, Name: service.Name} + lbRefStr := fmt.Sprintf("%v(%v)", name, serviceName) + if err := gce.DeleteTargetPool(name, region); err != nil && isHTTPErrorCode(err, http.StatusNotFound) { - glog.Infof("Target pool %s already deleted. Continuing to delete other resources.", name) + glog.Infof("DeleteExternalTargetPoolAndChecks(%v): Target pool already deleted. Continuing to delete other resources.", lbRefStr) } else if err != nil { - glog.Warningf("Failed to delete target pool %s, got error %s.", name, err.Error()) + glog.Warningf("DeleteExternalTargetPoolAndChecks(%v): Failed to delete target pool, got error %s.", lbRefStr, err.Error()) return err } @@ -406,14 +418,14 @@ func (gce *GCECloud) DeleteExternalTargetPoolAndChecks(service *v1.Service, name gce.sharedResourceLock.Lock() defer gce.sharedResourceLock.Unlock() } - glog.Infof("Deleting health check %v", hcName) + glog.Infof("DeleteExternalTargetPoolAndChecks(%v): Deleting health check %v.", lbRefStr, hcName) if err := gce.DeleteHttpHealthCheck(hcName); err != nil { // Delete nodes health checks will fail if any other target pool is using it. if isInUsedByError(err) { - glog.V(4).Infof("Health check %v is in used: %v.", hcName, err) + glog.V(4).Infof("DeleteExternalTargetPoolAndChecks(%v): Health check %v is in used: %v.", lbRefStr, hcName, err) return nil } else if !isHTTPErrorCode(err, http.StatusNotFound) { - glog.Warningf("Failed to delete health check %v: %v", hcName, err) + glog.Warningf("DeleteExternalTargetPoolAndChecks(%v): Failed to delete health check %v: %v.", lbRefStr, hcName, err) return err } // StatusNotFound could happen when: @@ -423,15 +435,15 @@ func (gce *GCECloud) DeleteExternalTargetPoolAndChecks(service *v1.Service, name // - This is a retry and in previous round we failed to delete the healthcheck firewall // after deleted the healthcheck. // We continue to delete the healthcheck firewall to prevent leaking. - glog.V(4).Infof("Health check %v is already deleted.", hcName) + glog.V(4).Infof("DeleteExternalTargetPoolAndChecks(%v): Health check %v is already deleted.", lbRefStr, hcName) } // If health check is deleted without error, it means no load-balancer is using it. // So we should delete the health check firewall as well. fwName := MakeHealthCheckFirewallName(clusterID, hcName, isNodesHealthCheck) - glog.Infof("Deleting firewall %v.", fwName) + glog.Infof("DeleteExternalTargetPoolAndChecks(%v): Deleting health check firewall %v.", lbRefStr, fwName) if err := ignoreNotFound(gce.DeleteFirewall(fwName)); err != nil { if isForbidden(err) && gce.OnXPN() { - glog.V(4).Infof("DeleteExternalTargetPoolAndChecks(%v): do not have permission to delete firewall rule (on XPN). Raising event.", hcName) + glog.V(4).Infof("DeleteExternalTargetPoolAndChecks(%v): Do not have permission to delete firewall rule %v (on XPN). Raising event.", lbRefStr, fwName) gce.raiseFirewallChangeNeededEvent(service, FirewallToGCloudDeleteCmd(fwName, gce.NetworkProjectID())) return nil } diff --git a/pkg/cloudprovider/providers/gce/gce_loadbalancer_internal.go b/pkg/cloudprovider/providers/gce/gce_loadbalancer_internal.go index ff14cbacdbe..d4acf9fa031 100644 --- a/pkg/cloudprovider/providers/gce/gce_loadbalancer_internal.go +++ b/pkg/cloudprovider/providers/gce/gce_loadbalancer_internal.go @@ -525,11 +525,6 @@ func (gce *GCECloud) ensureInternalBackendService(name, description string, affi glog.V(2).Infof("ensureInternalBackendService: created backend service %v successfully", name) return nil } - // Check existing backend service - existingIGLinks := sets.NewString() - for _, be := range bs.Backends { - existingIGLinks.Insert(be.Group) - } if backendSvcEqual(expectedBS, bs) { return nil diff --git a/pkg/cloudprovider/providers/gce/gce_test.go b/pkg/cloudprovider/providers/gce/gce_test.go index e3020acb7db..d4201b54bd4 100644 --- a/pkg/cloudprovider/providers/gce/gce_test.go +++ b/pkg/cloudprovider/providers/gce/gce_test.go @@ -175,42 +175,6 @@ func TestComparingHostURLs(t *testing.T) { } } -func TestScrubDNS(t *testing.T) { - tcs := []struct { - nameserversIn []string - searchesIn []string - nameserversOut []string - searchesOut []string - }{ - { - nameserversIn: []string{"1.2.3.4", "5.6.7.8"}, - nameserversOut: []string{"1.2.3.4", "5.6.7.8"}, - }, - { - searchesIn: []string{"c.prj.internal.", "12345678910.google.internal.", "google.internal."}, - searchesOut: []string{"c.prj.internal.", "google.internal."}, - }, - { - searchesIn: []string{"c.prj.internal.", "12345678910.google.internal.", "zone.c.prj.internal.", "google.internal."}, - searchesOut: []string{"c.prj.internal.", "zone.c.prj.internal.", "google.internal."}, - }, - { - searchesIn: []string{"c.prj.internal.", "12345678910.google.internal.", "zone.c.prj.internal.", "google.internal.", "unexpected"}, - searchesOut: []string{"c.prj.internal.", "zone.c.prj.internal.", "google.internal.", "unexpected"}, - }, - } - gce := &GCECloud{} - for i := range tcs { - n, s := gce.ScrubDNS(tcs[i].nameserversIn, tcs[i].searchesIn) - if !reflect.DeepEqual(n, tcs[i].nameserversOut) { - t.Errorf("Expected %v, got %v", tcs[i].nameserversOut, n) - } - if !reflect.DeepEqual(s, tcs[i].searchesOut) { - t.Errorf("Expected %v, got %v", tcs[i].searchesOut, s) - } - } -} - func TestSplitProviderID(t *testing.T) { providers := []struct { providerID string diff --git a/pkg/cloudprovider/providers/gce/gce_util.go b/pkg/cloudprovider/providers/gce/gce_util.go index 484a67e0b37..fb70d1dd053 100644 --- a/pkg/cloudprovider/providers/gce/gce_util.go +++ b/pkg/cloudprovider/providers/gce/gce_util.go @@ -19,6 +19,7 @@ package gce import ( "errors" "fmt" + "net" "net/http" "regexp" "strings" @@ -40,6 +41,13 @@ type gceInstance struct { Type string } +var ( + autoSubnetIPRange = &net.IPNet{ + IP: net.ParseIP("10.128.0.0"), + Mask: net.CIDRMask(9, 32), + } +) + var providerIdRE = regexp.MustCompile(`^` + ProviderName + `://([^/]+)/([^/]+)/([^/]+)$`) func getProjectAndZone() (string, string, error) { @@ -211,3 +219,58 @@ func handleAlphaNetworkTierGetError(err error) (string, error) { // Can't get the network tier, just return an error. return "", err } + +// containsCIDR returns true if outer contains inner. +func containsCIDR(outer, inner *net.IPNet) bool { + return outer.Contains(firstIPInRange(inner)) && outer.Contains(lastIPInRange(inner)) +} + +// firstIPInRange returns the first IP in a given IP range. +func firstIPInRange(ipNet *net.IPNet) net.IP { + return ipNet.IP.Mask(ipNet.Mask) +} + +// lastIPInRange returns the last IP in a given IP range. +func lastIPInRange(cidr *net.IPNet) net.IP { + ip := append([]byte{}, cidr.IP...) + for i, b := range cidr.Mask { + ip[i] |= ^b + } + return ip +} + +// subnetsInCIDR takes a list of subnets for a single region and +// returns subnets which exists in the specified CIDR range. +func subnetsInCIDR(subnets []*compute.Subnetwork, cidr *net.IPNet) ([]*compute.Subnetwork, error) { + var res []*compute.Subnetwork + for _, subnet := range subnets { + _, subnetRange, err := net.ParseCIDR(subnet.IpCidrRange) + if err != nil { + return nil, fmt.Errorf("unable to parse CIDR %q for subnet %q: %v", subnet.IpCidrRange, subnet.Name, err) + } + if containsCIDR(cidr, subnetRange) { + res = append(res, subnet) + } + } + return res, nil +} + +type netType string + +const ( + netTypeLegacy netType = "LEGACY" + netTypeAuto netType = "AUTO" + netTypeCustom netType = "CUSTOM" +) + +func typeOfNetwork(network *compute.Network) netType { + if network.IPv4Range != "" { + return netTypeLegacy + } + + if network.AutoCreateSubnetworks { + return netTypeAuto + } + + return netTypeCustom +} diff --git a/pkg/cloudprovider/providers/gce/gce_util_test.go b/pkg/cloudprovider/providers/gce/gce_util_test.go new file mode 100644 index 00000000000..f0bd4379b00 --- /dev/null +++ b/pkg/cloudprovider/providers/gce/gce_util_test.go @@ -0,0 +1,90 @@ +/* +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 gce + +import ( + "net" + "reflect" + "testing" + + compute "google.golang.org/api/compute/v1" +) + +func TestLastIPInRange(t *testing.T) { + for _, tc := range []struct { + cidr string + want string + }{ + {"10.1.2.3/32", "10.1.2.3"}, + {"10.1.2.0/31", "10.1.2.1"}, + {"10.1.0.0/30", "10.1.0.3"}, + {"10.0.0.0/29", "10.0.0.7"}, + {"::0/128", "::"}, + {"::0/127", "::1"}, + {"::0/126", "::3"}, + {"::0/120", "::ff"}, + } { + _, c, err := net.ParseCIDR(tc.cidr) + if err != nil { + t.Errorf("net.ParseCIDR(%v) = _, %v, %v; want nil", tc.cidr, c, err) + continue + } + + if lastIP := lastIPInRange(c); lastIP.String() != tc.want { + t.Errorf("LastIPInRange(%v) = %v; want %v", tc.cidr, lastIP, tc.want) + } + } +} + +func TestSubnetsInCIDR(t *testing.T) { + subnets := []*compute.Subnetwork{ + { + Name: "A", + IpCidrRange: "10.0.0.0/20", + }, + { + Name: "B", + IpCidrRange: "10.0.16.0/20", + }, + { + Name: "C", + IpCidrRange: "10.132.0.0/20", + }, + { + Name: "D", + IpCidrRange: "10.0.32.0/20", + }, + { + Name: "E", + IpCidrRange: "10.134.0.0/20", + }, + } + expectedNames := []string{"C", "E"} + + gotSubs, err := subnetsInCIDR(subnets, autoSubnetIPRange) + if err != nil { + t.Errorf("autoSubnetInList() = _, %v", err) + } + + var gotNames []string + for _, v := range gotSubs { + gotNames = append(gotNames, v.Name) + } + if !reflect.DeepEqual(gotNames, expectedNames) { + t.Errorf("autoSubnetInList() = %v, expected: %v", gotNames, expectedNames) + } +} diff --git a/pkg/cloudprovider/providers/gce/kms.go b/pkg/cloudprovider/providers/gce/kms.go deleted file mode 100644 index fbe62f523b5..00000000000 --- a/pkg/cloudprovider/providers/gce/kms.go +++ /dev/null @@ -1,167 +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 gce - -import ( - "encoding/base64" - "fmt" - "io" - - "github.com/golang/glog" - cloudkms "google.golang.org/api/cloudkms/v1" - "google.golang.org/api/googleapi" - gcfg "gopkg.in/gcfg.v1" - "k8s.io/apiserver/pkg/storage/value/encrypt/envelope" -) - -const ( - // KMSServiceName is the name of the cloudkms provider registered by this cloud. - KMSServiceName = "gcp-cloudkms" - - defaultGKMSKeyRing = "google-container-engine" - defaultGKMSKeyRingLocation = "global" -) - -// gkmsConfig contains the GCE specific KMS configuration for setting up a KMS connection. -type gkmsConfig struct { - Global struct { - // location is the KMS location of the KeyRing to be used for encryption. - // It can be found by checking the available KeyRings in the IAM UI. - // This is not the same as the GCP location of the project. - // +optional - Location string `gcfg:"kms-location"` - // keyRing is the keyRing of the hosted key to be used. The default value is "google-kubernetes". - // +optional - KeyRing string `gcfg:"kms-keyring"` - // cryptoKey is the name of the key to be used for encryption of Data-Encryption-Keys. - CryptoKey string `gcfg:"kms-cryptokey"` - } -} - -// readGCPCloudKMSConfig parses and returns the configuration parameters for Google Cloud KMS. -func readGCPCloudKMSConfig(reader io.Reader) (*gkmsConfig, error) { - cfg := &gkmsConfig{} - if err := gcfg.FatalOnly(gcfg.ReadInto(cfg, reader)); err != nil { - glog.Errorf("Couldn't read Google Cloud KMS config: %v", err) - return nil, err - } - return cfg, nil -} - -// gkmsService provides Encrypt and Decrypt methods which allow cryptographic operations -// using Google Cloud KMS service. -type gkmsService struct { - parentName string - cloudkmsService *cloudkms.Service -} - -// getGCPCloudKMSService provides a Google Cloud KMS based implementation of envelope.Service. -func (gce *GCECloud) getGCPCloudKMSService(config io.Reader) (envelope.Service, error) { - kmsConfig, err := readGCPCloudKMSConfig(config) - if err != nil { - return nil, err - } - - // Hosting on GCE/GKE with Google KMS encryption provider - cloudkmsService := gce.GetKMSService() - - // Set defaults for location and keyRing. - location := kmsConfig.Global.Location - if len(location) == 0 { - location = defaultGKMSKeyRingLocation - } - keyRing := kmsConfig.Global.KeyRing - if len(keyRing) == 0 { - keyRing = defaultGKMSKeyRing - } - - cryptoKey := kmsConfig.Global.CryptoKey - if len(cryptoKey) == 0 { - return nil, fmt.Errorf("missing cryptoKey for cloudprovided KMS: " + KMSServiceName) - } - - parentName := fmt.Sprintf("projects/%s/locations/%s", gce.projectID, location) - - // Create the keyRing if it does not exist yet - _, err = cloudkmsService.Projects.Locations.KeyRings.Create(parentName, - &cloudkms.KeyRing{}).KeyRingId(keyRing).Do() - if err != nil && unrecoverableCreationError(err) { - return nil, err - } - parentName = parentName + "/keyRings/" + keyRing - - // Create the cryptoKey if it does not exist yet - _, err = cloudkmsService.Projects.Locations.KeyRings.CryptoKeys.Create(parentName, - &cloudkms.CryptoKey{ - Purpose: "ENCRYPT_DECRYPT", - }).CryptoKeyId(cryptoKey).Do() - if err != nil && unrecoverableCreationError(err) { - return nil, err - } - parentName = parentName + "/cryptoKeys/" + cryptoKey - - service := &gkmsService{ - parentName: parentName, - cloudkmsService: cloudkmsService, - } - - // Sanity check before startup. For non-GCP clusters, the user's account may not have permissions to create - // the key. We need to verify the existence of the key before apiserver startup. - _, err = service.Encrypt([]byte("test")) - if err != nil { - return nil, fmt.Errorf("failed to encrypt data using Google cloudkms, using key %s. Ensure that the keyRing and cryptoKey exist. Got error: %v", parentName, err) - } - - return service, nil -} - -// Decrypt decrypts a base64 representation of encrypted bytes. -func (t *gkmsService) Decrypt(data string) ([]byte, error) { - resp, err := t.cloudkmsService.Projects.Locations.KeyRings.CryptoKeys. - Decrypt(t.parentName, &cloudkms.DecryptRequest{ - Ciphertext: data, - }).Do() - if err != nil { - return nil, err - } - return base64.StdEncoding.DecodeString(resp.Plaintext) -} - -// Encrypt encrypts bytes, and returns base64 representation of the ciphertext. -func (t *gkmsService) Encrypt(data []byte) (string, error) { - resp, err := t.cloudkmsService.Projects.Locations.KeyRings.CryptoKeys. - Encrypt(t.parentName, &cloudkms.EncryptRequest{ - Plaintext: base64.StdEncoding.EncodeToString(data), - }).Do() - if err != nil { - return "", err - } - return resp.Ciphertext, nil -} - -// unrecoverableCreationError decides if Kubernetes should ignore the encountered Google KMS -// error. Only to be used for errors seen while creating a KeyRing or CryptoKey. -func unrecoverableCreationError(err error) bool { - apiError, isAPIError := err.(*googleapi.Error) - // 409 means the object exists. - // 403 means we do not have permission to create the object, the user must do it. - // Else, it is an unrecoverable error. - if !isAPIError || (apiError.Code != 409 && apiError.Code != 403) { - return true - } - return false -} diff --git a/pkg/cloudprovider/providers/openstack/BUILD b/pkg/cloudprovider/providers/openstack/BUILD index b634bde37b8..42a185e2f4c 100644 --- a/pkg/cloudprovider/providers/openstack/BUILD +++ b/pkg/cloudprovider/providers/openstack/BUILD @@ -20,8 +20,8 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/openstack", deps = [ - "//pkg/api/v1/helper:go_default_library", "//pkg/api/v1/service:go_default_library", + "//pkg/apis/core/v1/helper:go_default_library", "//pkg/cloudprovider:go_default_library", "//pkg/controller:go_default_library", "//pkg/util/mount:go_default_library", @@ -29,32 +29,33 @@ go_library( "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/gophercloud/gophercloud:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers:go_default_library", - "//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members:go_default_library", - "//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors:go_default_library", - "//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools:go_default_library", - "//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports:go_default_library", "//vendor/github.com/gophercloud/gophercloud/pagination:go_default_library", "//vendor/github.com/mitchellh/mapstructure:go_default_library", "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", "//vendor/gopkg.in/gcfg.v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", @@ -71,13 +72,15 @@ go_test( "openstack_routes_test.go", "openstack_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/openstack", - library = ":go_default_library", deps = [ "//pkg/cloudprovider:go_default_library", "//vendor/github.com/gophercloud/gophercloud:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/rand:go_default_library", diff --git a/pkg/cloudprovider/providers/openstack/metadata.go b/pkg/cloudprovider/providers/openstack/metadata.go index 18fb4a5b0fb..d3cf22d9238 100644 --- a/pkg/cloudprovider/providers/openstack/metadata.go +++ b/pkg/cloudprovider/providers/openstack/metadata.go @@ -33,11 +33,12 @@ import ( ) const ( - // metadataUrl is URL to OpenStack metadata server. It's hardcoded IPv4 - // link-local address as documented in "OpenStack Cloud Administrator Guide", - // chapter Compute - Networking with nova-network. + // metadataUrlTemplate allows building an OpenStack Metadata service URL. + // It's a hardcoded IPv4 link-local address as documented in "OpenStack Cloud + // Administrator Guide", chapter Compute - Networking with nova-network. // https://docs.openstack.org/admin-guide/compute-networking-nova.html#metadata-service - metadataUrl = "http://169.254.169.254/openstack/2012-08-10/meta_data.json" + defaultMetadataVersion = "2012-08-10" + metadataUrlTemplate = "http://169.254.169.254/openstack/%s/meta_data.json" // metadataID is used as an identifier on the metadata search order configuration. metadataID = "metadataService" @@ -45,21 +46,32 @@ const ( // Config drive is defined as an iso9660 or vfat (deprecated) drive // with the "config-2" label. // http://docs.openstack.org/user-guide/cli-config-drive.html - configDriveLabel = "config-2" - configDrivePath = "openstack/2012-08-10/meta_data.json" + configDriveLabel = "config-2" + configDrivePathTemplate = "openstack/%s/meta_data.json" // configDriveID is used as an identifier on the metadata search order configuration. configDriveID = "configDrive" ) -var ErrBadMetadata = errors.New("Invalid OpenStack metadata, got empty uuid") +var ErrBadMetadata = errors.New("invalid OpenStack metadata, got empty uuid") + +// There are multiple device types. To keep it simple, we're using a single structure +// for all device metadata types. +type DeviceMetadata struct { + Type string `json:"type"` + Bus string `json:"bus,omitempty"` + Serial string `json:"serial,omitempty"` + Address string `json:"address,omitempty"` + // .. and other fields. +} // Assumes the "2012-08-10" meta_data.json format. // See http://docs.openstack.org/user-guide/cli_config_drive.html type Metadata struct { - Uuid string `json:"uuid"` - Name string `json:"name"` - AvailabilityZone string `json:"availability_zone"` + Uuid string `json:"uuid"` + Name string `json:"name"` + AvailabilityZone string `json:"availability_zone"` + Devices []DeviceMetadata `json:"devices,omitempty"` // .. and other fields we don't care about. Expand as necessary. } @@ -79,7 +91,15 @@ func parseMetadata(r io.Reader) (*Metadata, error) { return &metadata, nil } -func getMetadataFromConfigDrive() (*Metadata, error) { +func getMetadataUrl(metadataVersion string) string { + return fmt.Sprintf(metadataUrlTemplate, metadataVersion) +} + +func getConfigDrivePath(metadataVersion string) string { + return fmt.Sprintf(configDrivePathTemplate, metadataVersion) +} + +func getMetadataFromConfigDrive(metadataVersion string) (*Metadata, error) { // Try to read instance UUID from config drive. dev := "/dev/disk/by-label/" + configDriveLabel if _, err := os.Stat(dev); os.IsNotExist(err) { @@ -89,8 +109,7 @@ func getMetadataFromConfigDrive() (*Metadata, error) { "-o", "device", ).CombinedOutput() if err != nil { - glog.V(2).Infof("Unable to run blkid: %v", err) - return nil, err + return nil, fmt.Errorf("unable to run blkid: %v", err) } dev = strings.TrimSpace(string(out)) } @@ -109,37 +128,35 @@ func getMetadataFromConfigDrive() (*Metadata, error) { err = mounter.Mount(dev, mntdir, "vfat", []string{"ro"}) } if err != nil { - glog.Errorf("Error mounting configdrive %s: %v", dev, err) - return nil, err + return nil, fmt.Errorf("error mounting configdrive %s: %v", dev, err) } defer mounter.Unmount(mntdir) glog.V(4).Infof("Configdrive mounted on %s", mntdir) + configDrivePath := getConfigDrivePath(metadataVersion) f, err := os.Open( filepath.Join(mntdir, configDrivePath)) if err != nil { - glog.Errorf("Error reading %s on config drive: %v", configDrivePath, err) - return nil, err + return nil, fmt.Errorf("error reading %s on config drive: %v", configDrivePath, err) } defer f.Close() return parseMetadata(f) } -func getMetadataFromMetadataService() (*Metadata, error) { - // Try to get JSON from metdata server. +func getMetadataFromMetadataService(metadataVersion string) (*Metadata, error) { + // Try to get JSON from metadata server. + metadataUrl := getMetadataUrl(metadataVersion) glog.V(4).Infof("Attempting to fetch metadata from %s", metadataUrl) resp, err := http.Get(metadataUrl) if err != nil { - glog.V(3).Infof("Cannot read %s: %v", metadataUrl, err) - return nil, err + return nil, fmt.Errorf("error fetching %s: %v", metadataUrl, err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - err = fmt.Errorf("Unexpected status code when reading metadata from %s: %s", metadataUrl, resp.Status) - glog.V(3).Infof("%v", err) + err = fmt.Errorf("unexpected status code when reading metadata from %s: %s", metadataUrl, resp.Status) return nil, err } @@ -159,9 +176,9 @@ func getMetadata(order string) (*Metadata, error) { id = strings.TrimSpace(id) switch id { case configDriveID: - md, err = getMetadataFromConfigDrive() + md, err = getMetadataFromConfigDrive(defaultMetadataVersion) case metadataID: - md, err = getMetadataFromMetadataService() + md, err = getMetadataFromMetadataService(defaultMetadataVersion) default: err = fmt.Errorf("%s is not a valid metadata search order option. Supported options are %s and %s", id, configDriveID, metadataID) } diff --git a/pkg/cloudprovider/providers/openstack/metadata_test.go b/pkg/cloudprovider/providers/openstack/metadata_test.go index feeb04b0f96..d224a598bbb 100644 --- a/pkg/cloudprovider/providers/openstack/metadata_test.go +++ b/pkg/cloudprovider/providers/openstack/metadata_test.go @@ -64,7 +64,16 @@ func TestParseMetadata(t *testing.T) { "public_keys": { "mykey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDBqUfVvCSez0/Wfpd8dLLgZXV9GtXQ7hnMN+Z0OWQUyebVEHey1CXuin0uY1cAJMhUq8j98SiW+cU0sU4J3x5l2+xi1bodDm1BtFWVeLIOQINpfV1n8fKjHB+ynPpe1F6tMDvrFGUlJs44t30BrujMXBe8Rq44cCk6wqyjATA3rQ== Generated by Nova\n" }, - "uuid": "83679162-1378-4288-a2d4-70e13ec132aa" + "uuid": "83679162-1378-4288-a2d4-70e13ec132aa", + "devices": [ + { + "bus": "scsi", + "serial": "6df1888b-f373-41cf-b960-3786e60a28ef", + "tags": ["fake_tag"], + "type": "disk", + "address": "0:0:0:0" + } + ] } `) md, err := parseMetadata(data) @@ -83,4 +92,20 @@ func TestParseMetadata(t *testing.T) { if md.AvailabilityZone != "nova" { t.Errorf("incorrect az: %s", md.AvailabilityZone) } + + if len(md.Devices) != 1 { + t.Errorf("expecting to find 1 device, found %d", len(md.Devices)) + } + + if md.Devices[0].Bus != "scsi" { + t.Errorf("incorrect disk bus: %s", md.Devices[0].Bus) + } + + if md.Devices[0].Address != "0:0:0:0" { + t.Errorf("incorrect disk address: %s", md.Devices[0].Address) + } + + if md.Devices[0].Type != "disk" { + t.Errorf("incorrect device type: %s", md.Devices[0].Type) + } } diff --git a/pkg/cloudprovider/providers/openstack/openstack.go b/pkg/cloudprovider/providers/openstack/openstack.go index e8dbd36336d..00e15d228b2 100644 --- a/pkg/cloudprovider/providers/openstack/openstack.go +++ b/pkg/cloudprovider/providers/openstack/openstack.go @@ -42,7 +42,7 @@ import ( "k8s.io/apimachinery/pkg/types" netutil "k8s.io/apimachinery/pkg/util/net" certutil "k8s.io/client-go/util/cert" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" "k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/controller" ) @@ -53,9 +53,9 @@ const ( defaultTimeOut = 60 * time.Second ) -var ErrNotFound = errors.New("Failed to find object") -var ErrMultipleResults = errors.New("Multiple results where only one expected") -var ErrNoAddressFound = errors.New("No address found for host") +var ErrNotFound = errors.New("failed to find object") +var ErrMultipleResults = errors.New("multiple results where only one expected") +var ErrNoAddressFound = errors.New("no address found for host") // encoding.TextUnmarshaler interface for time.Duration type MyDuration struct { @@ -74,14 +74,17 @@ func (d *MyDuration) UnmarshalText(text []byte) error { type LoadBalancer struct { network *gophercloud.ServiceClient compute *gophercloud.ServiceClient + lb *gophercloud.ServiceClient opts LoadBalancerOpts } type LoadBalancerOpts struct { - LBVersion string `gcfg:"lb-version"` // overrides autodetection. v1 or v2 + LBVersion string `gcfg:"lb-version"` // overrides autodetection. Only support v2. + UseOctavia bool `gcfg:"use-octavia"` // uses Octavia V2 service catalog endpoint SubnetId string `gcfg:"subnet-id"` // overrides autodetection. FloatingNetworkId string `gcfg:"floating-network-id"` // If specified, will create floating ip for loadbalancer, or do not create floating ip. LBMethod string `gcfg:"lb-method"` // default to ROUND_ROBIN. + LBProvider string `gcfg:"lb-provider"` CreateMonitor bool `gcfg:"create-monitor"` MonitorDelay MyDuration `gcfg:"monitor-delay"` MonitorTimeout MyDuration `gcfg:"monitor-timeout"` @@ -179,8 +182,7 @@ func (cfg Config) toAuth3Options() tokens3.AuthOptions { func readConfig(config io.Reader) (Config, error) { if config == nil { - err := fmt.Errorf("no OpenStack cloud provider config file given") - return Config{}, err + return Config{}, fmt.Errorf("no OpenStack cloud provider config file given") } var cfg Config @@ -484,11 +486,6 @@ func (os *OpenStack) ProviderName() string { return ProviderName } -// ScrubDNS filters DNS settings for pods. -func (os *OpenStack) ScrubDNS(nameServers, searches []string) ([]string, []string) { - return nameServers, searches -} - // HasClusterID returns true if the cluster has a clusterID func (os *OpenStack) HasClusterID() bool { return true @@ -507,39 +504,22 @@ func (os *OpenStack) LoadBalancer() (cloudprovider.LoadBalancer, bool) { return nil, false } - lbVersion := os.lbOpts.LBVersion - if lbVersion == "" { - // No version specified, try newest supported by server - netExts, err := networkExtensions(network) - if err != nil { - glog.Warningf("Failed to list neutron extensions: %v", err) - return nil, false - } + lb, err := os.NewLoadBalancerV2() + if err != nil { + return nil, false + } - if netExts["lbaasv2"] { - lbVersion = "v2" - } else if netExts["lbaas"] { - lbVersion = "v1" - } else { - glog.Warningf("Failed to find neutron LBaaS extension (v1 or v2)") - return nil, false - } - glog.V(3).Infof("Using LBaaS extension %v", lbVersion) + // LBaaS v1 is deprecated in the OpenStack Liberty release. + // Currently kubernetes OpenStack cloud provider just support LBaaS v2. + lbVersion := os.lbOpts.LBVersion + if lbVersion != "" && lbVersion != "v2" { + glog.Warningf("Config error: currently only support LBaaS v2, unrecognised lb-version \"%v\"", lbVersion) + return nil, false } glog.V(1).Info("Claiming to support LoadBalancer") - if lbVersion == "v2" { - return &LbaasV2{LoadBalancer{network, compute, os.lbOpts}}, true - } else if lbVersion == "v1" { - // Since LBaaS v1 is deprecated in the OpenStack Liberty release, so deprecate LBaaSV1 at V1.8, then remove LBaaSV1 after V1.9. - // Reference OpenStack doc: https://docs.openstack.org/mitaka/networking-guide/config-lbaas.html - glog.Warningf("The LBaaS v1 of OpenStack cloud provider has been deprecated, Please use LBaaS v2") - return &LbaasV1{LoadBalancer{network, compute, os.lbOpts}}, true - } else { - glog.Warningf("Config error: unrecognised lb-version \"%v\"", lbVersion) - return nil, false - } + return &LbaasV2{LoadBalancer{network, compute, lb, os.lbOpts}}, true } func isNotFound(err error) bool { @@ -549,7 +529,6 @@ func isNotFound(err error) bool { func (os *OpenStack) Zones() (cloudprovider.Zones, bool) { glog.V(1).Info("Claiming to support Zones") - return os, true } @@ -563,8 +542,7 @@ func (os *OpenStack) GetZone() (cloudprovider.Zone, error) { FailureDomain: md.AvailabilityZone, Region: os.region, } - glog.V(1).Infof("Current zone is %v", zone) - + glog.V(4).Infof("Current zone is %v", zone) return zone, nil } @@ -592,7 +570,6 @@ func (os *OpenStack) GetZoneByProviderID(providerID string) (cloudprovider.Zone, Region: os.region, } glog.V(4).Infof("The instance %s in zone %v", srv.Name, zone) - return zone, nil } @@ -618,7 +595,6 @@ func (os *OpenStack) GetZoneByNodeName(nodeName types.NodeName) (cloudprovider.Z Region: os.region, } glog.V(4).Infof("The instance %s in zone %v", srv.Name, zone) - return zone, nil } @@ -653,7 +629,6 @@ func (os *OpenStack) Routes() (cloudprovider.Routes, bool) { } glog.V(1).Info("Claiming to support Routes") - return r, true } @@ -680,42 +655,50 @@ func (os *OpenStack) volumeService(forceVersion string) (volumeService, error) { } glog.V(3).Infof("Using Blockstorage API V2") return &VolumesV2{sClient, os.bsOpts}, nil - case "auto": - // Currently kubernetes just support Cinder v1 and Cinder v2. - // Choose Cinder v2 firstly, if kubernetes can't initialize cinder v2 client, try to initialize cinder v1 client. - // Return appropriate message when kubernetes can't initialize them. - // TODO(FengyunPan): revisit 'auto' after supporting Cinder v3. - sClient, err := os.NewBlockStorageV2() + case "v3": + sClient, err := os.NewBlockStorageV3() if err != nil { - sClient, err = os.NewBlockStorageV1() - if err != nil { - // Nothing suitable found, failed autodetection, just exit with appropriate message - err_txt := "BlockStorage API version autodetection failed. " + - "Please set it explicitly in cloud.conf in section [BlockStorage] with key `bs-version`" - return nil, errors.New(err_txt) - } else { - glog.V(3).Infof("Using Blockstorage API V1") - return &VolumesV1{sClient, os.bsOpts}, nil - } - } else { + return nil, err + } + glog.V(3).Infof("Using Blockstorage API V3") + return &VolumesV3{sClient, os.bsOpts}, nil + case "auto": + // Currently kubernetes support Cinder v1 / Cinder v2 / Cinder v3. + // Choose Cinder v3 firstly, if kubernetes can't initialize cinder v3 client, try to initialize cinder v2 client. + // If kubernetes can't initialize cinder v2 client, try to initialize cinder v1 client. + // Return appropriate message when kubernetes can't initialize them. + if sClient, err := os.NewBlockStorageV3(); err == nil { + glog.V(3).Infof("Using Blockstorage API V3") + return &VolumesV3{sClient, os.bsOpts}, nil + } + + if sClient, err := os.NewBlockStorageV2(); err == nil { glog.V(3).Infof("Using Blockstorage API V2") return &VolumesV2{sClient, os.bsOpts}, nil } + + if sClient, err := os.NewBlockStorageV1(); err == nil { + glog.V(3).Infof("Using Blockstorage API V1") + return &VolumesV1{sClient, os.bsOpts}, nil + } + + err_txt := "BlockStorage API version autodetection failed. " + + "Please set it explicitly in cloud.conf in section [BlockStorage] with key `bs-version`" + return nil, errors.New(err_txt) default: err_txt := fmt.Sprintf("Config error: unrecognised bs-version \"%v\"", os.bsOpts.BSVersion) - glog.Warningf(err_txt) return nil, errors.New(err_txt) } } func checkMetadataSearchOrder(order string) error { if order == "" { - return errors.New("Invalid value in section [Metadata] with key `search-order`. Value cannot be empty") + return errors.New("invalid value in section [Metadata] with key `search-order`. Value cannot be empty") } elements := strings.Split(order, ",") if len(elements) > 2 { - return errors.New("Invalid value in section [Metadata] with key `search-order`. Value cannot contain more than 2 elements") + return errors.New("invalid value in section [Metadata] with key `search-order`. Value cannot contain more than 2 elements") } for _, id := range elements { @@ -724,9 +707,8 @@ func checkMetadataSearchOrder(order string) error { case configDriveID: case metadataID: default: - errTxt := "Invalid element '%s' found in section [Metadata] with key `search-order`." + - "Supported elements include '%s' and '%s'" - return fmt.Errorf(errTxt, id, configDriveID, metadataID) + return fmt.Errorf("invalid element %q found in section [Metadata] with key `search-order`."+ + "Supported elements include %q and %q", id, configDriveID, metadataID) } } diff --git a/pkg/cloudprovider/providers/openstack/openstack_client.go b/pkg/cloudprovider/providers/openstack/openstack_client.go index 916c4a09c56..04851070e4d 100644 --- a/pkg/cloudprovider/providers/openstack/openstack_client.go +++ b/pkg/cloudprovider/providers/openstack/openstack_client.go @@ -17,10 +17,10 @@ limitations under the License. package openstack import ( + "fmt" + "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" - - "github.com/golang/glog" ) func (os *OpenStack) NewNetworkV2() (*gophercloud.ServiceClient, error) { @@ -28,8 +28,7 @@ func (os *OpenStack) NewNetworkV2() (*gophercloud.ServiceClient, error) { Region: os.region, }) if err != nil { - glog.Warningf("Failed to find network v2 endpoint for region %s: %v", os.region, err) - return nil, err + return nil, fmt.Errorf("failed to find network v2 endpoint for region %s: %v", os.region, err) } return network, nil } @@ -39,8 +38,7 @@ func (os *OpenStack) NewComputeV2() (*gophercloud.ServiceClient, error) { Region: os.region, }) if err != nil { - glog.Warningf("Failed to find compute v2 endpoint for region %s: %v", os.region, err) - return nil, err + return nil, fmt.Errorf("failed to find compute v2 endpoint for region %s: %v", os.region, err) } return compute, nil } @@ -50,8 +48,7 @@ func (os *OpenStack) NewBlockStorageV1() (*gophercloud.ServiceClient, error) { Region: os.region, }) if err != nil { - glog.Errorf("Unable to initialize cinder v1 client for region %s: %v", os.region, err) - return nil, err + return nil, fmt.Errorf("unable to initialize cinder v1 client for region %s: %v", os.region, err) } return storage, nil } @@ -61,8 +58,35 @@ func (os *OpenStack) NewBlockStorageV2() (*gophercloud.ServiceClient, error) { Region: os.region, }) if err != nil { - glog.Errorf("Unable to initialize cinder v2 client for region %s: %v", os.region, err) - return nil, err + return nil, fmt.Errorf("unable to initialize cinder v2 client for region %s: %v", os.region, err) } return storage, nil } + +func (os *OpenStack) NewBlockStorageV3() (*gophercloud.ServiceClient, error) { + storage, err := openstack.NewBlockStorageV3(os.provider, gophercloud.EndpointOpts{ + Region: os.region, + }) + if err != nil { + return nil, fmt.Errorf("unable to initialize cinder v3 client for region %s: %v", os.region, err) + } + return storage, nil +} + +func (os *OpenStack) NewLoadBalancerV2() (*gophercloud.ServiceClient, error) { + var lb *gophercloud.ServiceClient + var err error + if os.lbOpts.UseOctavia { + lb, err = openstack.NewLoadBalancerV2(os.provider, gophercloud.EndpointOpts{ + Region: os.region, + }) + } else { + lb, err = openstack.NewNetworkV2(os.provider, gophercloud.EndpointOpts{ + Region: os.region, + }) + } + if err != nil { + return nil, fmt.Errorf("failed to find load-balancer v2 endpoint for region %s: %v", os.region, err) + } + return lb, nil +} diff --git a/pkg/cloudprovider/providers/openstack/openstack_instances.go b/pkg/cloudprovider/providers/openstack/openstack_instances.go index 68876de57fd..3cf1733b322 100644 --- a/pkg/cloudprovider/providers/openstack/openstack_instances.go +++ b/pkg/cloudprovider/providers/openstack/openstack_instances.go @@ -116,7 +116,25 @@ func (i *Instances) ExternalID(name types.NodeName) (string, error) { // InstanceExistsByProviderID returns true if the instance with the given provider id still exists and is running. // If false is returned with no error, the instance will be immediately deleted by the cloud controller manager. func (i *Instances) InstanceExistsByProviderID(providerID string) (bool, error) { - return false, cloudprovider.NotImplemented + instanceID, err := instanceIDFromProviderID(providerID) + if err != nil { + return false, err + } + + server, err := servers.Get(i.compute, instanceID).Extract() + if err != nil { + if isNotFound(err) { + return false, nil + } + return false, err + } + + if server.Status != "ACTIVE" { + glog.Warningf("the instance %s is not active", instanceID) + return false, nil + } + + return true, nil } // InstanceID returns the kubelet's cloud provider ID. diff --git a/pkg/cloudprovider/providers/openstack/openstack_loadbalancer.go b/pkg/cloudprovider/providers/openstack/openstack_loadbalancer.go index c55f0e22a48..2089c14f4ce 100644 --- a/pkg/cloudprovider/providers/openstack/openstack_loadbalancer.go +++ b/pkg/cloudprovider/providers/openstack/openstack_loadbalancer.go @@ -25,17 +25,15 @@ import ( "github.com/golang/glog" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" - "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members" - "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors" - "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools" - "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers" v2monitors "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors" v2pools "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules" + "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" neutronports "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" "github.com/gophercloud/gophercloud/pagination" @@ -54,7 +52,7 @@ const ( // going into ACTIVE loadbalancer provisioning status. Starting with 1 // seconds, multiplying by 1.2 with each step and taking 19 steps at maximum // it will time out after 128s, which roughly corresponds to 120s - loadbalancerActiveInitDealy = 1 * time.Second + loadbalancerActiveInitDelay = 1 * time.Second loadbalancerActiveFactor = 1.2 loadbalancerActiveSteps = 19 @@ -62,7 +60,7 @@ const ( // waiting for delete operation to complete. Starting with 1 // seconds, multiplying by 1.2 with each step and taking 13 steps at maximum // it will time out after 32s, which roughly corresponds to 30s - loadbalancerDeleteInitDealy = 1 * time.Second + loadbalancerDeleteInitDelay = 1 * time.Second loadbalancerDeleteFactor = 1.2 loadbalancerDeleteSteps = 13 @@ -77,13 +75,6 @@ const ( ServiceAnnotationLoadBalancerInternal = "service.beta.kubernetes.io/openstack-internal-load-balancer" ) -// Deprecated; Since LBaaS v1 is deprecated in the OpenStack Liberty release, Kubernetes deprecated it at V1.8. -// TODO(FengyunPan): remove LBaaS v1 after kubernetes V1.9. -// LoadBalancer implementation for LBaaS v1 -type LbaasV1 struct { - LoadBalancer -} - // LoadBalancer implementation for LBaaS v2 type LbaasV2 struct { LoadBalancer @@ -144,76 +135,6 @@ func getFloatingIPByPortID(client *gophercloud.ServiceClient, portID string) (*f return &floatingIPList[0], nil } -func getPoolByName(client *gophercloud.ServiceClient, name string) (*pools.Pool, error) { - opts := pools.ListOpts{ - Name: name, - } - pager := pools.List(client, opts) - - poolList := make([]pools.Pool, 0, 1) - - err := pager.EachPage(func(page pagination.Page) (bool, error) { - p, err := pools.ExtractPools(page) - if err != nil { - return false, err - } - poolList = append(poolList, p...) - if len(poolList) > 1 { - return false, ErrMultipleResults - } - return true, nil - }) - if err != nil { - if isNotFound(err) { - return nil, ErrNotFound - } - return nil, err - } - - if len(poolList) == 0 { - return nil, ErrNotFound - } else if len(poolList) > 1 { - return nil, ErrMultipleResults - } - - return &poolList[0], nil -} - -func getVipByName(client *gophercloud.ServiceClient, name string) (*vips.VirtualIP, error) { - opts := vips.ListOpts{ - Name: name, - } - pager := vips.List(client, opts) - - vipList := make([]vips.VirtualIP, 0, 1) - - err := pager.EachPage(func(page pagination.Page) (bool, error) { - v, err := vips.ExtractVIPs(page) - if err != nil { - return false, err - } - vipList = append(vipList, v...) - if len(vipList) > 1 { - return false, ErrMultipleResults - } - return true, nil - }) - if err != nil { - if isNotFound(err) { - return nil, ErrNotFound - } - return nil, err - } - - if len(vipList) == 0 { - return nil, ErrNotFound - } else if len(vipList) > 1 { - return nil, ErrMultipleResults - } - - return &vipList[0], nil -} - func getLoadbalancerByName(client *gophercloud.ServiceClient, name string) (*loadbalancers.LoadBalancer, error) { opts := loadbalancers.ListOpts{ Name: name, @@ -373,8 +294,14 @@ func popMember(members []v2pools.Member, addr string, port int) []v2pools.Member return members } -func getSecurityGroupName(clusterName string, service *v1.Service) string { - return fmt.Sprintf("lb-sg-%s-%s-%s", clusterName, service.Namespace, service.Name) +func getSecurityGroupName(service *v1.Service) string { + securityGroupName := fmt.Sprintf("lb-sg-%s-%s-%s", service.UID, service.Namespace, service.Name) + //OpenStack requires that the name of a security group is shorter than 255 bytes. + if len(securityGroupName) > 255 { + securityGroupName = securityGroupName[:255] + } + + return securityGroupName } func getSecurityGroupRules(client *gophercloud.ServiceClient, opts rules.ListOpts) ([]rules.SecGroupRule, error) { @@ -401,7 +328,7 @@ func getSecurityGroupRules(client *gophercloud.ServiceClient, opts rules.ListOpt func waitLoadbalancerActiveProvisioningStatus(client *gophercloud.ServiceClient, loadbalancerID string) (string, error) { backoff := wait.Backoff{ - Duration: loadbalancerActiveInitDealy, + Duration: loadbalancerActiveInitDelay, Factor: loadbalancerActiveFactor, Steps: loadbalancerActiveSteps, } @@ -416,7 +343,7 @@ func waitLoadbalancerActiveProvisioningStatus(client *gophercloud.ServiceClient, if loadbalancer.ProvisioningStatus == activeStatus { return true, nil } else if loadbalancer.ProvisioningStatus == errorStatus { - return true, fmt.Errorf("Loadbalancer has gone into ERROR state") + return true, fmt.Errorf("loadbalancer has gone into ERROR state") } else { return false, nil } @@ -424,14 +351,14 @@ func waitLoadbalancerActiveProvisioningStatus(client *gophercloud.ServiceClient, }) if err == wait.ErrWaitTimeout { - err = fmt.Errorf("Loadbalancer failed to go into ACTIVE provisioning status within alloted time") + err = fmt.Errorf("loadbalancer failed to go into ACTIVE provisioning status within alloted time") } return provisioningStatus, err } func waitLoadbalancerDeleted(client *gophercloud.ServiceClient, loadbalancerID string) error { backoff := wait.Backoff{ - Duration: loadbalancerDeleteInitDealy, + Duration: loadbalancerDeleteInitDelay, Factor: loadbalancerDeleteFactor, Steps: loadbalancerDeleteSteps, } @@ -449,7 +376,7 @@ func waitLoadbalancerDeleted(client *gophercloud.ServiceClient, loadbalancerID s }) if err == wait.ErrWaitTimeout { - err = fmt.Errorf("Loadbalancer failed to delete within the alloted time") + err = fmt.Errorf("loadbalancer failed to delete within the alloted time") } return err @@ -515,6 +442,7 @@ func (lbaas *LbaasV2) createLoadBalancer(service *v1.Service, name string, inter Name: name, Description: fmt.Sprintf("Kubernetes external service %s", name), VipSubnetID: lbaas.opts.SubnetId, + Provider: lbaas.opts.LBProvider, } loadBalancerIP := service.Spec.LoadBalancerIP @@ -522,16 +450,16 @@ func (lbaas *LbaasV2) createLoadBalancer(service *v1.Service, name string, inter createOpts.VipAddress = loadBalancerIP } - loadbalancer, err := loadbalancers.Create(lbaas.network, createOpts).Extract() + loadbalancer, err := loadbalancers.Create(lbaas.lb, createOpts).Extract() if err != nil { - return nil, fmt.Errorf("Error creating loadbalancer %v: %v", createOpts, err) + return nil, fmt.Errorf("error creating loadbalancer %v: %v", createOpts, err) } return loadbalancer, nil } func (lbaas *LbaasV2) GetLoadBalancer(clusterName string, service *v1.Service) (*v1.LoadBalancerStatus, bool, error) { loadBalancerName := cloudprovider.GetLoadBalancerName(service) - loadbalancer, err := getLoadbalancerByName(lbaas.network, loadBalancerName) + loadbalancer, err := getLoadbalancerByName(lbaas.lb, loadBalancerName) if err == ErrNotFound { return nil, false, nil } @@ -545,7 +473,7 @@ func (lbaas *LbaasV2) GetLoadBalancer(clusterName string, service *v1.Service) ( if portID != "" { floatIP, err := getFloatingIPByPortID(lbaas.network, portID) if err != nil { - return nil, false, fmt.Errorf("Error getting floating ip for port %s: %v", portID, err) + return nil, false, fmt.Errorf("error getting floating ip for port %s: %v", portID, err) } status.Ingress = []v1.LoadBalancerIngress{{IP: floatIP.FloatingIP}} } else { @@ -640,6 +568,52 @@ func getNodeSecurityGroupIDForLB(compute *gophercloud.ServiceClient, nodes []*v1 return nodeSecurityGroupIDs.List(), nil } +// getFloatingNetworkIdForLB returns a floating-network-id for cluster. +func getFloatingNetworkIdForLB(client *gophercloud.ServiceClient) (string, error) { + var floatingNetworkIds []string + + type NetworkWithExternalExt struct { + networks.Network + external.NetworkExternalExt + } + + err := networks.List(client, networks.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + var externalNetwork []NetworkWithExternalExt + err := networks.ExtractNetworksInto(page, &externalNetwork) + if err != nil { + return false, err + } + + for _, externalNet := range externalNetwork { + if externalNet.External { + floatingNetworkIds = append(floatingNetworkIds, externalNet.ID) + } + } + + if len(floatingNetworkIds) > 1 { + return false, ErrMultipleResults + } + return true, nil + }) + if err != nil { + if isNotFound(err) { + return "", ErrNotFound + } + + if err == ErrMultipleResults { + glog.V(4).Infof("find multiple external networks, pick the first one when there are no explicit configuration.") + return floatingNetworkIds[0], nil + } + return "", err + } + + if len(floatingNetworkIds) == 0 { + return "", ErrNotFound + } + + return floatingNetworkIds[0], nil +} + // TODO: This code currently ignores 'region' and always creates a // loadbalancer in only the current OpenStack region. We should take // a list of regions (from config) and query/create loadbalancers in @@ -649,7 +623,7 @@ func (lbaas *LbaasV2) EnsureLoadBalancer(clusterName string, apiService *v1.Serv glog.V(4).Infof("EnsureLoadBalancer(%v, %v, %v, %v, %v, %v, %v)", clusterName, apiService.Namespace, apiService.Name, apiService.Spec.LoadBalancerIP, apiService.Spec.Ports, nodes, apiService.Annotations) if len(nodes) == 0 { - return nil, fmt.Errorf("There are no available nodes for LoadBalancer service %s/%s", apiService.Namespace, apiService.Name) + return nil, fmt.Errorf("there are no available nodes for LoadBalancer service %s/%s", apiService.Namespace, apiService.Name) } if len(lbaas.opts.SubnetId) == 0 { @@ -658,7 +632,7 @@ func (lbaas *LbaasV2) EnsureLoadBalancer(clusterName string, apiService *v1.Serv subnetID, err := getSubnetIDForLB(lbaas.compute, *nodes[0]) if err != nil { glog.Warningf("Failed to find subnet-id for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) - return nil, fmt.Errorf("No subnet-id for service %s/%s : subnet-id not set in cloud provider config, "+ + return nil, fmt.Errorf("no subnet-id for service %s/%s : subnet-id not set in cloud provider config, "+ "and failed to find subnet-id from OpenStack: %v", apiService.Namespace, apiService.Name, err) } lbaas.opts.SubnetId = subnetID @@ -670,7 +644,13 @@ func (lbaas *LbaasV2) EnsureLoadBalancer(clusterName string, apiService *v1.Serv } floatingPool := getStringFromServiceAnnotation(apiService, ServiceAnnotationLoadBalancerFloatingNetworkId, lbaas.opts.FloatingNetworkId) - glog.V(4).Infof("EnsureLoadBalancer using floatingPool: %v", floatingPool) + if len(floatingPool) == 0 { + var err error + floatingPool, err = getFloatingNetworkIdForLB(lbaas.network) + if err != nil { + glog.Warningf("Failed to find floating-network-id for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) + } + } var internalAnnotation bool internal := getStringFromServiceAnnotation(apiService, ServiceAnnotationLoadBalancerInternal, "false") @@ -680,13 +660,13 @@ func (lbaas *LbaasV2) EnsureLoadBalancer(clusterName string, apiService *v1.Serv internalAnnotation = true case "false": if len(floatingPool) != 0 { - glog.V(4).Infof("Ensure an external loadbalancer service.") + glog.V(4).Infof("Ensure an external loadbalancer service, using floatingPool: %v", floatingPool) internalAnnotation = false } else { - return nil, fmt.Errorf("floating-network-id or loadbalancer.openstack.org/floating-network-id should be specified when ensuring an external loadbalancer service.") + return nil, fmt.Errorf("floating-network-id or loadbalancer.openstack.org/floating-network-id should be specified when ensuring an external loadbalancer service") } default: - return nil, fmt.Errorf("unknow service.beta.kubernetes.io/openstack-internal-load-balancer annotation: %v, specify \"true\" or \"false\".", + return nil, fmt.Errorf("unknown service.beta.kubernetes.io/openstack-internal-load-balancer annotation: %v, specify \"true\" or \"false\" ", internal) } @@ -694,17 +674,17 @@ func (lbaas *LbaasV2) EnsureLoadBalancer(clusterName string, apiService *v1.Serv // TODO: Convert all error messages to use an event recorder for _, port := range ports { if port.Protocol != v1.ProtocolTCP { - return nil, fmt.Errorf("Only TCP LoadBalancer is supported for openstack load balancers") + return nil, fmt.Errorf("only TCP LoadBalancer is supported for openstack load balancers") } } sourceRanges, err := service.GetLoadBalancerSourceRanges(apiService) if err != nil { - return nil, fmt.Errorf("Failed to get source ranges for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) + return nil, fmt.Errorf("failed to get source ranges for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) } if !service.IsAllowAll(sourceRanges) && !lbaas.opts.ManageSecurityGroups { - return nil, fmt.Errorf("Source range restrictions are not supported for openstack load balancers without managing security groups") + return nil, fmt.Errorf("source range restrictions are not supported for openstack load balancers without managing security groups") } affinity := apiService.Spec.SessionAffinity @@ -719,37 +699,40 @@ func (lbaas *LbaasV2) EnsureLoadBalancer(clusterName string, apiService *v1.Serv } name := cloudprovider.GetLoadBalancerName(apiService) - loadbalancer, err := getLoadbalancerByName(lbaas.network, name) + loadbalancer, err := getLoadbalancerByName(lbaas.lb, name) if err != nil { if err != ErrNotFound { - return nil, fmt.Errorf("Error getting loadbalancer %s: %v", name, err) + return nil, fmt.Errorf("error getting loadbalancer %s: %v", name, err) } glog.V(2).Infof("Creating loadbalancer %s", name) loadbalancer, err = lbaas.createLoadBalancer(apiService, name, internalAnnotation) if err != nil { // Unknown error, retry later - return nil, fmt.Errorf("Error creating loadbalancer %s: %v", name, err) + return nil, fmt.Errorf("error creating loadbalancer %s: %v", name, err) } } else { glog.V(2).Infof("LoadBalancer %s already exists", name) } - waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) + } lbmethod := v2pools.LBMethod(lbaas.opts.LBMethod) if lbmethod == "" { lbmethod = v2pools.LBMethodRoundRobin } - oldListeners, err := getListenersByLoadBalancerID(lbaas.network, loadbalancer.ID) + oldListeners, err := getListenersByLoadBalancerID(lbaas.lb, loadbalancer.ID) if err != nil { - return nil, fmt.Errorf("Error getting LB %s listeners: %v", name, err) + return nil, fmt.Errorf("error getting LB %s listeners: %v", name, err) } for portIndex, port := range ports { listener := getListenerForPort(oldListeners, port) if listener == nil { glog.V(4).Infof("Creating listener for port %d", int(port.Port)) - listener, err = listeners.Create(lbaas.network, listeners.CreateOpts{ + listener, err = listeners.Create(lbaas.lb, listeners.CreateOpts{ Name: fmt.Sprintf("listener_%s_%d", name, portIndex), Protocol: listeners.Protocol(port.Protocol), ProtocolPort: int(port.Port), @@ -757,9 +740,12 @@ func (lbaas *LbaasV2) EnsureLoadBalancer(clusterName string, apiService *v1.Serv }).Extract() if err != nil { // Unknown error, retry later - return nil, fmt.Errorf("Error creating LB listener: %v", err) + return nil, fmt.Errorf("error creating LB listener: %v", err) + } + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) } - waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) } glog.V(4).Infof("Listener for %s port %d: %s", string(port.Protocol), int(port.Port), listener.ID) @@ -767,14 +753,14 @@ func (lbaas *LbaasV2) EnsureLoadBalancer(clusterName string, apiService *v1.Serv // After all ports have been processed, remaining listeners are removed as obsolete. // Pop valid listeners. oldListeners = popListener(oldListeners, listener.ID) - pool, err := getPoolByListenerID(lbaas.network, loadbalancer.ID, listener.ID) + pool, err := getPoolByListenerID(lbaas.lb, loadbalancer.ID, listener.ID) if err != nil && err != ErrNotFound { // Unknown error, retry later - return nil, fmt.Errorf("Error getting pool for listener %s: %v", listener.ID, err) + return nil, fmt.Errorf("error getting pool for listener %s: %v", listener.ID, err) } if pool == nil { glog.V(4).Infof("Creating pool for listener %s", listener.ID) - pool, err = v2pools.Create(lbaas.network, v2pools.CreateOpts{ + pool, err = v2pools.Create(lbaas.lb, v2pools.CreateOpts{ Name: fmt.Sprintf("pool_%s_%d", name, portIndex), Protocol: v2pools.Protocol(port.Protocol), LBMethod: lbmethod, @@ -783,15 +769,19 @@ func (lbaas *LbaasV2) EnsureLoadBalancer(clusterName string, apiService *v1.Serv }).Extract() if err != nil { // Unknown error, retry later - return nil, fmt.Errorf("Error creating pool for listener %s: %v", listener.ID, err) + return nil, fmt.Errorf("error creating pool for listener %s: %v", listener.ID, err) } - waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) + } + } glog.V(4).Infof("Pool for listener %s: %s", listener.ID, pool.ID) - members, err := getMembersByPoolID(lbaas.network, pool.ID) + members, err := getMembersByPoolID(lbaas.lb, pool.ID) if err != nil && !isNotFound(err) { - return nil, fmt.Errorf("Error getting pool members %s: %v", pool.ID, err) + return nil, fmt.Errorf("error getting pool members %s: %v", pool.ID, err) } for _, node := range nodes { addr, err := nodeAddressForLB(node) @@ -801,22 +791,25 @@ func (lbaas *LbaasV2) EnsureLoadBalancer(clusterName string, apiService *v1.Serv glog.Warningf("Failed to create LB pool member for node %s: %v", node.Name, err) continue } else { - return nil, fmt.Errorf("Error getting address for node %s: %v", node.Name, err) + return nil, fmt.Errorf("error getting address for node %s: %v", node.Name, err) } } if !memberExists(members, addr, int(port.NodePort)) { glog.V(4).Infof("Creating member for pool %s", pool.ID) - _, err := v2pools.CreateMember(lbaas.network, pool.ID, v2pools.CreateMemberOpts{ + _, err := v2pools.CreateMember(lbaas.lb, pool.ID, v2pools.CreateMemberOpts{ ProtocolPort: int(port.NodePort), Address: addr, SubnetID: lbaas.opts.SubnetId, }).Extract() if err != nil { - return nil, fmt.Errorf("Error creating LB pool member for node: %s, %v", node.Name, err) + return nil, fmt.Errorf("error creating LB pool member for node: %s, %v", node.Name, err) } - waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) + } } else { // After all members have been processed, remaining members are deleted as obsolete. members = popMember(members, addr, int(port.NodePort)) @@ -828,17 +821,20 @@ func (lbaas *LbaasV2) EnsureLoadBalancer(clusterName string, apiService *v1.Serv // Delete obsolete members for this pool for _, member := range members { glog.V(4).Infof("Deleting obsolete member %s for pool %s address %s", member.ID, pool.ID, member.Address) - err := v2pools.DeleteMember(lbaas.network, pool.ID, member.ID).ExtractErr() + err := v2pools.DeleteMember(lbaas.lb, pool.ID, member.ID).ExtractErr() if err != nil && !isNotFound(err) { - return nil, fmt.Errorf("Error deleting obsolete member %s for pool %s address %s: %v", member.ID, pool.ID, member.Address, err) + return nil, fmt.Errorf("error deleting obsolete member %s for pool %s address %s: %v", member.ID, pool.ID, member.Address, err) + } + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) } - waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) } monitorID := pool.MonitorID if monitorID == "" && lbaas.opts.CreateMonitor { glog.V(4).Infof("Creating monitor for pool %s", pool.ID) - monitor, err := v2monitors.Create(lbaas.network, v2monitors.CreateOpts{ + monitor, err := v2monitors.Create(lbaas.lb, v2monitors.CreateOpts{ PoolID: pool.ID, Type: string(port.Protocol), Delay: int(lbaas.opts.MonitorDelay.Duration.Seconds()), @@ -846,9 +842,12 @@ func (lbaas *LbaasV2) EnsureLoadBalancer(clusterName string, apiService *v1.Serv MaxRetries: int(lbaas.opts.MonitorMaxRetries), }).Extract() if err != nil { - return nil, fmt.Errorf("Error creating LB pool healthmonitor: %v", err) + return nil, fmt.Errorf("error creating LB pool healthmonitor: %v", err) + } + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) } - waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) monitorID = monitor.ID } else if lbaas.opts.CreateMonitor == false { glog.V(4).Infof("Do not create monitor for pool %s when create-monitor is false", pool.ID) @@ -863,57 +862,69 @@ func (lbaas *LbaasV2) EnsureLoadBalancer(clusterName string, apiService *v1.Serv for _, listener := range oldListeners { glog.V(4).Infof("Deleting obsolete listener %s:", listener.ID) // get pool for listener - pool, err := getPoolByListenerID(lbaas.network, loadbalancer.ID, listener.ID) + pool, err := getPoolByListenerID(lbaas.lb, loadbalancer.ID, listener.ID) if err != nil && err != ErrNotFound { - return nil, fmt.Errorf("Error getting pool for obsolete listener %s: %v", listener.ID, err) + return nil, fmt.Errorf("error getting pool for obsolete listener %s: %v", listener.ID, err) } if pool != nil { // get and delete monitor monitorID := pool.MonitorID if monitorID != "" { glog.V(4).Infof("Deleting obsolete monitor %s for pool %s", monitorID, pool.ID) - err = v2monitors.Delete(lbaas.network, monitorID).ExtractErr() + err = v2monitors.Delete(lbaas.lb, monitorID).ExtractErr() if err != nil && !isNotFound(err) { - return nil, fmt.Errorf("Error deleting obsolete monitor %s for pool %s: %v", monitorID, pool.ID, err) + return nil, fmt.Errorf("error deleting obsolete monitor %s for pool %s: %v", monitorID, pool.ID, err) + } + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) } - waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) } // get and delete pool members - members, err := getMembersByPoolID(lbaas.network, pool.ID) + members, err := getMembersByPoolID(lbaas.lb, pool.ID) if err != nil && !isNotFound(err) { - return nil, fmt.Errorf("Error getting members for pool %s: %v", pool.ID, err) + return nil, fmt.Errorf("error getting members for pool %s: %v", pool.ID, err) } if members != nil { for _, member := range members { glog.V(4).Infof("Deleting obsolete member %s for pool %s address %s", member.ID, pool.ID, member.Address) - err := v2pools.DeleteMember(lbaas.network, pool.ID, member.ID).ExtractErr() + err := v2pools.DeleteMember(lbaas.lb, pool.ID, member.ID).ExtractErr() if err != nil && !isNotFound(err) { - return nil, fmt.Errorf("Error deleting obsolete member %s for pool %s address %s: %v", member.ID, pool.ID, member.Address, err) + return nil, fmt.Errorf("error deleting obsolete member %s for pool %s address %s: %v", member.ID, pool.ID, member.Address, err) + } + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) } - waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) } } glog.V(4).Infof("Deleting obsolete pool %s for listener %s", pool.ID, listener.ID) // delete pool - err = v2pools.Delete(lbaas.network, pool.ID).ExtractErr() + err = v2pools.Delete(lbaas.lb, pool.ID).ExtractErr() if err != nil && !isNotFound(err) { - return nil, fmt.Errorf("Error deleting obsolete pool %s for listener %s: %v", pool.ID, listener.ID, err) + return nil, fmt.Errorf("error deleting obsolete pool %s for listener %s: %v", pool.ID, listener.ID, err) + } + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) } - waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) } // delete listener - err = listeners.Delete(lbaas.network, listener.ID).ExtractErr() + err = listeners.Delete(lbaas.lb, listener.ID).ExtractErr() if err != nil && !isNotFound(err) { - return nil, fmt.Errorf("Error deleteting obsolete listener: %v", err) + return nil, fmt.Errorf("error deleteting obsolete listener: %v", err) + } + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return nil, fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) } - waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) glog.V(2).Infof("Deleted obsolete listener: %s", listener.ID) } portID := loadbalancer.VipPortID - floatIP, err := getFloatingIPByPortID(lbaas.network, portID) + floatIP, err := getFloatingIPByPortID(lbaas.lb, portID) if err != nil && err != ErrNotFound { - return nil, fmt.Errorf("Error getting floating ip for port %s: %v", portID, err) + return nil, fmt.Errorf("error getting floating ip for port %s: %v", portID, err) } if floatIP == nil && floatingPool != "" && !internalAnnotation { glog.V(4).Infof("Creating floating ip for loadbalancer %s port %s", loadbalancer.ID, portID) @@ -929,7 +940,7 @@ func (lbaas *LbaasV2) EnsureLoadBalancer(clusterName string, apiService *v1.Serv floatIP, err = floatingips.Create(lbaas.network, floatIPOpts).Extract() if err != nil { - return nil, fmt.Errorf("Error creating LB floatingip %+v: %v", floatIPOpts, err) + return nil, fmt.Errorf("error creating LB floatingip %+v: %v", floatIPOpts, err) } } @@ -948,6 +959,14 @@ func (lbaas *LbaasV2) EnsureLoadBalancer(clusterName string, apiService *v1.Serv _ = lbaas.EnsureLoadBalancerDeleted(clusterName, apiService) return status, err } + + // delete the old Security Group for the service + // Related to #53764 + // TODO(FengyunPan): Remove it at V1.10 + err = lbaas.EnsureOldSecurityGroupDeleted(clusterName, apiService) + if err != nil { + return status, fmt.Errorf("Failed to delete the Security Group for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) + } } return status, nil @@ -961,7 +980,7 @@ func (lbaas *LbaasV2) ensureSecurityGroup(clusterName string, apiService *v1.Ser if len(lbaas.opts.NodeSecurityGroupIDs) == 0 { lbaas.opts.NodeSecurityGroupIDs, err = getNodeSecurityGroupIDForLB(lbaas.compute, nodes) if err != nil { - return fmt.Errorf("Failed to find node-security-group for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) + return fmt.Errorf("failed to find node-security-group for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) } } glog.V(4).Infof("find node-security-group %v for loadbalancer service %s/%s", lbaas.opts.NodeSecurityGroupIDs, apiService.Namespace, apiService.Name) @@ -975,11 +994,11 @@ func (lbaas *LbaasV2) ensureSecurityGroup(clusterName string, apiService *v1.Ser // get service source ranges sourceRanges, err := service.GetLoadBalancerSourceRanges(apiService) if err != nil { - return fmt.Errorf("Failed to get source ranges for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) + return fmt.Errorf("failed to get source ranges for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) } // ensure security group for LB - lbSecGroupName := getSecurityGroupName(clusterName, apiService) + lbSecGroupName := getSecurityGroupName(apiService) lbSecGroupID, err := groups.IDFromName(lbaas.network, lbSecGroupName) if err != nil { // check whether security group does not exist @@ -988,19 +1007,19 @@ func (lbaas *LbaasV2) ensureSecurityGroup(clusterName string, apiService *v1.Ser // create it later lbSecGroupID = "" } else { - return fmt.Errorf("Error occurred finding security group: %s: %v", lbSecGroupName, err) + return fmt.Errorf("error occurred finding security group: %s: %v", lbSecGroupName, err) } } if len(lbSecGroupID) == 0 { // create security group lbSecGroupCreateOpts := groups.CreateOpts{ - Name: getSecurityGroupName(clusterName, apiService), - Description: fmt.Sprintf("Securty Group for loadbalancer service %s/%s", apiService.Namespace, apiService.Name), + Name: getSecurityGroupName(apiService), + Description: fmt.Sprintf("Security Group for %s/%s Service LoadBalancer in cluster %s", apiService.Namespace, apiService.Name, clusterName), } lbSecGroup, err := groups.Create(lbaas.network, lbSecGroupCreateOpts).Extract() if err != nil { - return fmt.Errorf("Failed to create Security Group for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) + return fmt.Errorf("failed to create Security Group for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) } lbSecGroupID = lbSecGroup.ID @@ -1011,7 +1030,7 @@ func (lbaas *LbaasV2) ensureSecurityGroup(clusterName string, apiService *v1.Ser network, _, err := net.ParseCIDR(sourceRange) if err != nil { - return fmt.Errorf("Error parsing source range %s as a CIDR: %v", sourceRange, err) + return fmt.Errorf("error parsing source range %s as a CIDR: %v", sourceRange, err) } if network.To4() == nil { @@ -1031,7 +1050,7 @@ func (lbaas *LbaasV2) ensureSecurityGroup(clusterName string, apiService *v1.Ser _, err = rules.Create(lbaas.network, lbSecGroupRuleCreateOpts).Extract() if err != nil { - return fmt.Errorf("Error occured creating rule for SecGroup %s: %v", lbSecGroup.ID, err) + return fmt.Errorf("error occured creating rule for SecGroup %s: %v", lbSecGroup.ID, err) } } } @@ -1049,7 +1068,7 @@ func (lbaas *LbaasV2) ensureSecurityGroup(clusterName string, apiService *v1.Ser _, err = rules.Create(lbaas.network, lbSecGroupRuleCreateOpts).Extract() if err != nil { - return fmt.Errorf("Error occured creating rule for SecGroup %s: %v", lbSecGroup.ID, err) + return fmt.Errorf("error occured creating rule for SecGroup %s: %v", lbSecGroup.ID, err) } lbSecGroupRuleCreateOpts = rules.CreateOpts{ @@ -1064,7 +1083,7 @@ func (lbaas *LbaasV2) ensureSecurityGroup(clusterName string, apiService *v1.Ser _, err = rules.Create(lbaas.network, lbSecGroupRuleCreateOpts).Extract() if err != nil { - return fmt.Errorf("Error occured creating rule for SecGroup %s: %v", lbSecGroup.ID, err) + return fmt.Errorf("error occured creating rule for SecGroup %s: %v", lbSecGroup.ID, err) } // get security groups of port @@ -1119,7 +1138,7 @@ func (lbaas *LbaasV2) ensureSecurityGroup(clusterName string, apiService *v1.Ser // Add the rules in the Node Security Group err = createNodeSecurityGroup(lbaas.network, nodeSecurityGroupID, int(port.NodePort), port.Protocol, lbSecGroupID) if err != nil { - return fmt.Errorf("Error occured creating security group for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) + return fmt.Errorf("error occured creating security group for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) } } } @@ -1137,7 +1156,7 @@ func (lbaas *LbaasV2) UpdateLoadBalancer(clusterName string, service *v1.Service subnetID, err := getSubnetIDForLB(lbaas.compute, *nodes[0]) if err != nil { glog.Warningf("Failed to find subnet-id for loadbalancer service %s/%s: %v", service.Namespace, service.Name, err) - return fmt.Errorf("No subnet-id for service %s/%s : subnet-id not set in cloud provider config, "+ + return fmt.Errorf("no subnet-id for service %s/%s : subnet-id not set in cloud provider config, "+ "and failed to find subnet-id from OpenStack: %v", service.Namespace, service.Name, err) } lbaas.opts.SubnetId = subnetID @@ -1148,12 +1167,12 @@ func (lbaas *LbaasV2) UpdateLoadBalancer(clusterName string, service *v1.Service return fmt.Errorf("no ports provided to openstack load balancer") } - loadbalancer, err := getLoadbalancerByName(lbaas.network, loadBalancerName) + loadbalancer, err := getLoadbalancerByName(lbaas.lb, loadBalancerName) if err != nil { return err } if loadbalancer == nil { - return fmt.Errorf("Loadbalancer %s does not exist", loadBalancerName) + return fmt.Errorf("loadbalancer %s does not exist", loadBalancerName) } // Get all listeners for this loadbalancer, by "port key". @@ -1163,9 +1182,9 @@ func (lbaas *LbaasV2) UpdateLoadBalancer(clusterName string, service *v1.Service } var listenerIDs []string lbListeners := make(map[portKey]listeners.Listener) - allListeners, err := getListenersByLoadBalancerID(lbaas.network, loadbalancer.ID) + allListeners, err := getListenersByLoadBalancerID(lbaas.lb, loadbalancer.ID) if err != nil { - return fmt.Errorf("Error getting listeners for LB %s: %v", loadBalancerName, err) + return fmt.Errorf("error getting listeners for LB %s: %v", loadBalancerName, err) } for _, l := range allListeners { key := portKey{Protocol: listeners.Protocol(l.Protocol), Port: l.ProtocolPort} @@ -1176,9 +1195,9 @@ func (lbaas *LbaasV2) UpdateLoadBalancer(clusterName string, service *v1.Service // Get all pools for this loadbalancer, by listener ID. lbPools := make(map[string]v2pools.Pool) for _, listenerID := range listenerIDs { - pool, err := getPoolByListenerID(lbaas.network, loadbalancer.ID, listenerID) + pool, err := getPoolByListenerID(lbaas.lb, loadbalancer.ID, listenerID) if err != nil { - return fmt.Errorf("Error getting pool for listener %s: %v", listenerID, err) + return fmt.Errorf("error getting pool for listener %s: %v", listenerID, err) } lbPools[listenerID] = *pool } @@ -1201,19 +1220,19 @@ func (lbaas *LbaasV2) UpdateLoadBalancer(clusterName string, service *v1.Service Port: int(port.Port), }] if !ok { - return fmt.Errorf("Loadbalancer %s does not contain required listener for port %d and protocol %s", loadBalancerName, port.Port, port.Protocol) + return fmt.Errorf("loadbalancer %s does not contain required listener for port %d and protocol %s", loadBalancerName, port.Port, port.Protocol) } // Get pool associated with this listener pool, ok := lbPools[listener.ID] if !ok { - return fmt.Errorf("Loadbalancer %s does not contain required pool for listener %s", loadBalancerName, listener.ID) + return fmt.Errorf("loadbalancer %s does not contain required pool for listener %s", loadBalancerName, listener.ID) } // Find existing pool members (by address) for this port - getMembers, err := getMembersByPoolID(lbaas.network, pool.ID) + getMembers, err := getMembersByPoolID(lbaas.lb, pool.ID) if err != nil { - return fmt.Errorf("Error getting pool members %s: %v", pool.ID, err) + return fmt.Errorf("error getting pool members %s: %v", pool.ID, err) } members := make(map[string]v2pools.Member) for _, member := range getMembers { @@ -1226,7 +1245,7 @@ func (lbaas *LbaasV2) UpdateLoadBalancer(clusterName string, service *v1.Service // Already exists, do not create member continue } - _, err := v2pools.CreateMember(lbaas.network, pool.ID, v2pools.CreateMemberOpts{ + _, err := v2pools.CreateMember(lbaas.lb, pool.ID, v2pools.CreateMemberOpts{ Address: addr, ProtocolPort: int(port.NodePort), SubnetID: lbaas.opts.SubnetId, @@ -1234,7 +1253,10 @@ func (lbaas *LbaasV2) UpdateLoadBalancer(clusterName string, service *v1.Service if err != nil { return err } - waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) + } } // Remove any old members for this port @@ -1243,18 +1265,21 @@ func (lbaas *LbaasV2) UpdateLoadBalancer(clusterName string, service *v1.Service // Still present, do not delete member continue } - err = v2pools.DeleteMember(lbaas.network, pool.ID, member.ID).ExtractErr() + err = v2pools.DeleteMember(lbaas.lb, pool.ID, member.ID).ExtractErr() if err != nil && !isNotFound(err) { return err } - waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) + } } } if lbaas.opts.ManageSecurityGroups { err := lbaas.updateSecurityGroup(clusterName, service, nodes, loadbalancer) if err != nil { - return fmt.Errorf("Failed to update Securty Group for loadbalancer service %s/%s: %v", service.Namespace, service.Name, err) + return fmt.Errorf("failed to update Security Group for loadbalancer service %s/%s: %v", service.Namespace, service.Name, err) } } @@ -1268,7 +1293,7 @@ func (lbaas *LbaasV2) updateSecurityGroup(clusterName string, apiService *v1.Ser var err error lbaas.opts.NodeSecurityGroupIDs, err = getNodeSecurityGroupIDForLB(lbaas.compute, nodes) if err != nil { - return fmt.Errorf("Failed to find node-security-group for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) + return fmt.Errorf("failed to find node-security-group for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) } glog.V(4).Infof("find node-security-group %v for loadbalancer service %s/%s", lbaas.opts.NodeSecurityGroupIDs, apiService.Namespace, apiService.Name) @@ -1277,10 +1302,10 @@ func (lbaas *LbaasV2) updateSecurityGroup(clusterName string, apiService *v1.Ser removals := original.Difference(current) // Generate Name - lbSecGroupName := getSecurityGroupName(clusterName, apiService) + lbSecGroupName := getSecurityGroupName(apiService) lbSecGroupID, err := groups.IDFromName(lbaas.network, lbSecGroupName) if err != nil { - return fmt.Errorf("Error occurred finding security group: %s: %v", lbSecGroupName, err) + return fmt.Errorf("error occurred finding security group: %s: %v", lbSecGroupName, err) } ports := apiService.Spec.Ports @@ -1301,14 +1326,13 @@ func (lbaas *LbaasV2) updateSecurityGroup(clusterName string, apiService *v1.Ser } secGroupRules, err := getSecurityGroupRules(lbaas.network, opts) if err != nil && !isNotFound(err) { - msg := fmt.Sprintf("Error finding rules for remote group id %s in security group id %s: %v", lbSecGroupID, removal, err) - return fmt.Errorf(msg) + return fmt.Errorf("error finding rules for remote group id %s in security group id %s: %v", lbSecGroupID, removal, err) } for _, rule := range secGroupRules { res := rules.Delete(lbaas.network, rule.ID) if res.Err != nil && !isNotFound(res.Err) { - return fmt.Errorf("Error occurred deleting security group rule: %s: %v", rule.ID, res.Err) + return fmt.Errorf("error occurred deleting security group rule: %s: %v", rule.ID, res.Err) } } } @@ -1324,8 +1348,7 @@ func (lbaas *LbaasV2) updateSecurityGroup(clusterName string, apiService *v1.Ser } secGroupRules, err := getSecurityGroupRules(lbaas.network, opts) if err != nil && !isNotFound(err) { - msg := fmt.Sprintf("Error finding rules for remote group id %s in security group id %s: %v", lbSecGroupID, nodeSecurityGroupID, err) - return fmt.Errorf(msg) + return fmt.Errorf("error finding rules for remote group id %s in security group id %s: %v", lbSecGroupID, nodeSecurityGroupID, err) } if len(secGroupRules) != 0 { // Do not add rule when find rules for remote group in the Node Security Group @@ -1335,7 +1358,7 @@ func (lbaas *LbaasV2) updateSecurityGroup(clusterName string, apiService *v1.Ser // Add the rules in the Node Security Group err = createNodeSecurityGroup(lbaas.network, nodeSecurityGroupID, int(port.NodePort), port.Protocol, lbSecGroupID) if err != nil { - return fmt.Errorf("Error occured creating security group for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) + return fmt.Errorf("error occured creating security group for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) } } } @@ -1347,7 +1370,7 @@ func (lbaas *LbaasV2) EnsureLoadBalancerDeleted(clusterName string, service *v1. loadBalancerName := cloudprovider.GetLoadBalancerName(service) glog.V(4).Infof("EnsureLoadBalancerDeleted(%v, %v)", clusterName, loadBalancerName) - loadbalancer, err := getLoadbalancerByName(lbaas.network, loadBalancerName) + loadbalancer, err := getLoadbalancerByName(lbaas.lb, loadBalancerName) if err != nil && err != ErrNotFound { return err } @@ -1355,7 +1378,7 @@ func (lbaas *LbaasV2) EnsureLoadBalancerDeleted(clusterName string, service *v1. return nil } - if loadbalancer != nil && loadbalancer.VipPortID != "" { + if loadbalancer.VipPortID != "" { portID := loadbalancer.VipPortID floatingIP, err := getFloatingIPByPortID(lbaas.network, portID) if err != nil && err != ErrNotFound { @@ -1370,18 +1393,18 @@ func (lbaas *LbaasV2) EnsureLoadBalancerDeleted(clusterName string, service *v1. } // get all listeners associated with this loadbalancer - listenerList, err := getListenersByLoadBalancerID(lbaas.network, loadbalancer.ID) + listenerList, err := getListenersByLoadBalancerID(lbaas.lb, loadbalancer.ID) if err != nil { - return fmt.Errorf("Error getting LB %s listeners: %v", loadbalancer.ID, err) + return fmt.Errorf("error getting LB %s listeners: %v", loadbalancer.ID, err) } // get all pools (and health monitors) associated with this loadbalancer var poolIDs []string var monitorIDs []string for _, listener := range listenerList { - pool, err := getPoolByListenerID(lbaas.network, loadbalancer.ID, listener.ID) + pool, err := getPoolByListenerID(lbaas.lb, loadbalancer.ID, listener.ID) if err != nil && err != ErrNotFound { - return fmt.Errorf("Error getting pool for listener %s: %v", listener.ID, err) + return fmt.Errorf("error getting pool for listener %s: %v", listener.ID, err) } if pool != nil { poolIDs = append(poolIDs, pool.ID) @@ -1395,9 +1418,9 @@ func (lbaas *LbaasV2) EnsureLoadBalancerDeleted(clusterName string, service *v1. // get all members associated with each poolIDs var memberIDs []string for _, pool := range poolIDs { - membersList, err := getMembersByPoolID(lbaas.network, pool) + membersList, err := getMembersByPoolID(lbaas.lb, pool) if err != nil && !isNotFound(err) { - return fmt.Errorf("Error getting pool members %s: %v", pool, err) + return fmt.Errorf("error getting pool members %s: %v", pool, err) } for _, member := range membersList { memberIDs = append(memberIDs, member.ID) @@ -1406,94 +1429,127 @@ func (lbaas *LbaasV2) EnsureLoadBalancerDeleted(clusterName string, service *v1. // delete all monitors for _, monitorID := range monitorIDs { - err := v2monitors.Delete(lbaas.network, monitorID).ExtractErr() + err := v2monitors.Delete(lbaas.lb, monitorID).ExtractErr() if err != nil && !isNotFound(err) { return err } - waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) + } } // delete all members and pools for _, poolID := range poolIDs { // delete all members for this pool for _, memberID := range memberIDs { - err := v2pools.DeleteMember(lbaas.network, poolID, memberID).ExtractErr() + err := v2pools.DeleteMember(lbaas.lb, poolID, memberID).ExtractErr() if err != nil && !isNotFound(err) { return err } - waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) + } } // delete pool - err := v2pools.Delete(lbaas.network, poolID).ExtractErr() + err := v2pools.Delete(lbaas.lb, poolID).ExtractErr() if err != nil && !isNotFound(err) { return err } - waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) + } } // delete all listeners for _, listener := range listenerList { - err := listeners.Delete(lbaas.network, listener.ID).ExtractErr() + err := listeners.Delete(lbaas.lb, listener.ID).ExtractErr() if err != nil && !isNotFound(err) { return err } - waitLoadbalancerActiveProvisioningStatus(lbaas.network, loadbalancer.ID) + provisioningStatus, err := waitLoadbalancerActiveProvisioningStatus(lbaas.lb, loadbalancer.ID) + if err != nil { + return fmt.Errorf("failed to loadbalance ACTIVE provisioning status %v: %v", provisioningStatus, err) + } } // delete loadbalancer - err = loadbalancers.Delete(lbaas.network, loadbalancer.ID).ExtractErr() + err = loadbalancers.Delete(lbaas.lb, loadbalancer.ID).ExtractErr() if err != nil && !isNotFound(err) { return err } - waitLoadbalancerDeleted(lbaas.network, loadbalancer.ID) + err = waitLoadbalancerDeleted(lbaas.lb, loadbalancer.ID) + if err != nil { + return fmt.Errorf("failed to delete loadbalancer: %v", err) + } // Delete the Security Group if lbaas.opts.ManageSecurityGroups { - // Generate Name - lbSecGroupName := getSecurityGroupName(clusterName, service) - lbSecGroupID, err := groups.IDFromName(lbaas.network, lbSecGroupName) + err := lbaas.EnsureSecurityGroupDeleted(clusterName, service) if err != nil { - // check whether security group does not exist - _, ok := err.(*gophercloud.ErrResourceNotFound) - if ok { - // It is OK when the security group has been deleted by others. - return nil - } else { - return fmt.Errorf("Error occurred finding security group: %s: %v", lbSecGroupName, err) - } + return fmt.Errorf("Failed to delete Security Group for loadbalancer service %s/%s: %v", service.Namespace, service.Name, err) } - lbSecGroup := groups.Delete(lbaas.network, lbSecGroupID) - if lbSecGroup.Err != nil && !isNotFound(lbSecGroup.Err) { - return lbSecGroup.Err + // delete the old Security Group for the service + // Related to #53764 + // TODO(FengyunPan): Remove it at V1.10 + err = lbaas.EnsureOldSecurityGroupDeleted(clusterName, service) + if err != nil { + return fmt.Errorf("Failed to delete the Security Group for loadbalancer service %s/%s: %v", service.Namespace, service.Name, err) } + } - if len(lbaas.opts.NodeSecurityGroupIDs) == 0 { - // Just happen when nodes have not Security Group, or should not happen - // UpdateLoadBalancer and EnsureLoadBalancer can set lbaas.opts.NodeSecurityGroupIDs when it is empty - // And service controller call UpdateLoadBalancer to set lbaas.opts.NodeSecurityGroupIDs when controller manager service is restarted. - glog.Warningf("Can not find node-security-group from all the nodes of this cluser when delete loadbalancer service %s/%s", - service.Namespace, service.Name) + return nil +} + +// EnsureSecurityGroupDeleted deleting security group for specific loadbalancer service. +func (lbaas *LbaasV2) EnsureSecurityGroupDeleted(clusterName string, service *v1.Service) error { + // Generate Name + lbSecGroupName := getSecurityGroupName(service) + lbSecGroupID, err := groups.IDFromName(lbaas.network, lbSecGroupName) + if err != nil { + // check whether security group does not exist + _, ok := err.(*gophercloud.ErrResourceNotFound) + if ok { + // It is OK when the security group has been deleted by others. + return nil } else { - // Delete the rules in the Node Security Group - for _, nodeSecurityGroupID := range lbaas.opts.NodeSecurityGroupIDs { - opts := rules.ListOpts{ - SecGroupID: nodeSecurityGroupID, - RemoteGroupID: lbSecGroupID, - } - secGroupRules, err := getSecurityGroupRules(lbaas.network, opts) + return fmt.Errorf("Error occurred finding security group: %s: %v", lbSecGroupName, err) + } + } - if err != nil && !isNotFound(err) { - msg := fmt.Sprintf("Error finding rules for remote group id %s in security group id %s: %v", lbSecGroupID, nodeSecurityGroupID, err) - return fmt.Errorf(msg) - } + lbSecGroup := groups.Delete(lbaas.network, lbSecGroupID) + if lbSecGroup.Err != nil && !isNotFound(lbSecGroup.Err) { + return lbSecGroup.Err + } - for _, rule := range secGroupRules { - res := rules.Delete(lbaas.network, rule.ID) - if res.Err != nil && !isNotFound(res.Err) { - return fmt.Errorf("Error occurred deleting security group rule: %s: %v", rule.ID, res.Err) - } + if len(lbaas.opts.NodeSecurityGroupIDs) == 0 { + // Just happen when nodes have not Security Group, or should not happen + // UpdateLoadBalancer and EnsureLoadBalancer can set lbaas.opts.NodeSecurityGroupIDs when it is empty + // And service controller call UpdateLoadBalancer to set lbaas.opts.NodeSecurityGroupIDs when controller manager service is restarted. + glog.Warningf("Can not find node-security-group from all the nodes of this cluster when delete loadbalancer service %s/%s", + service.Namespace, service.Name) + } else { + // Delete the rules in the Node Security Group + for _, nodeSecurityGroupID := range lbaas.opts.NodeSecurityGroupIDs { + opts := rules.ListOpts{ + SecGroupID: nodeSecurityGroupID, + RemoteGroupID: lbSecGroupID, + } + secGroupRules, err := getSecurityGroupRules(lbaas.network, opts) + + if err != nil && !isNotFound(err) { + msg := fmt.Sprintf("Error finding rules for remote group id %s in security group id %s: %v", lbSecGroupID, nodeSecurityGroupID, err) + return fmt.Errorf(msg) + } + + for _, rule := range secGroupRules { + res := rules.Delete(lbaas.network, rule.ID) + if res.Err != nil && !isNotFound(res.Err) { + return fmt.Errorf("Error occurred deleting security group rule: %s: %v", rule.ID, res.Err) } } } @@ -1502,353 +1558,64 @@ func (lbaas *LbaasV2) EnsureLoadBalancerDeleted(clusterName string, service *v1. return nil } -func (lb *LbaasV1) GetLoadBalancer(clusterName string, service *v1.Service) (*v1.LoadBalancerStatus, bool, error) { - loadBalancerName := cloudprovider.GetLoadBalancerName(service) - vip, err := getVipByName(lb.network, loadBalancerName) - if err == ErrNotFound { - return nil, false, nil - } - if vip == nil { - return nil, false, err - } - - status := &v1.LoadBalancerStatus{} - - if vip.PortID != "" { - floatingIP, err := getFloatingIPByPortID(lb.network, vip.PortID) - if err != nil { - return nil, false, fmt.Errorf("Error getting floating ip for port %s: %v", vip.PortID, err) - } - status.Ingress = []v1.LoadBalancerIngress{{IP: floatingIP.FloatingIP}} - } else { - status.Ingress = []v1.LoadBalancerIngress{{IP: vip.Address}} - } - - return status, true, err +// getOldSecurityGroupName is used to get the old security group name +// Related to #53764 +// TODO(FengyunPan): Remove it at V1.10 +func getOldSecurityGroupName(clusterName string, service *v1.Service) string { + return fmt.Sprintf("lb-sg-%s-%v", clusterName, service.Name) } -// TODO: This code currently ignores 'region' and always creates a -// loadbalancer in only the current OpenStack region. We should take -// a list of regions (from config) and query/create loadbalancers in -// each region. - -func (lb *LbaasV1) EnsureLoadBalancer(clusterName string, apiService *v1.Service, nodes []*v1.Node) (*v1.LoadBalancerStatus, error) { - glog.V(4).Infof("EnsureLoadBalancer(%v, %v, %v, %v, %v, %v, %v)", clusterName, apiService.Namespace, apiService.Name, apiService.Spec.LoadBalancerIP, apiService.Spec.Ports, nodes, apiService.Annotations) - - if len(nodes) == 0 { - return nil, fmt.Errorf("There are no available nodes for LoadBalancer service %s/%s", apiService.Namespace, apiService.Name) - } - - if len(lb.opts.SubnetId) == 0 { - // Get SubnetId automatically. - // The LB needs to be configured with instance addresses on the same subnet, so get SubnetId by one node. - subnetID, err := getSubnetIDForLB(lb.compute, *nodes[0]) - if err != nil { - glog.Warningf("Failed to find subnet-id for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err) - return nil, fmt.Errorf("No subnet-id for service %s/%s : subnet-id not set in cloud provider config, "+ - "and failed to find subnet-id from OpenStack: %v", apiService.Namespace, apiService.Name, err) - } - lb.opts.SubnetId = subnetID - } - - floatingPool := getStringFromServiceAnnotation(apiService, ServiceAnnotationLoadBalancerFloatingNetworkId, lb.opts.FloatingNetworkId) - glog.V(4).Infof("EnsureLoadBalancer using floatingPool: %v", floatingPool) - - var internalAnnotation bool - internal := getStringFromServiceAnnotation(apiService, ServiceAnnotationLoadBalancerInternal, "false") - switch internal { - case "true": - glog.V(4).Infof("Ensure an internal loadbalancer service.") - internalAnnotation = true - case "false": - if len(floatingPool) != 0 { - glog.V(4).Infof("Ensure an external loadbalancer service.") - internalAnnotation = false +// EnsureOldSecurityGroupDeleted deleting old security group for specific loadbalancer service. +// Related to #53764 +// TODO(FengyunPan): Remove it at V1.10 +func (lbaas *LbaasV2) EnsureOldSecurityGroupDeleted(clusterName string, service *v1.Service) error { + glog.V(4).Infof("EnsureOldSecurityGroupDeleted(%v, %v)", clusterName, service) + // Generate Name + lbSecGroupName := getOldSecurityGroupName(clusterName, service) + lbSecGroupID, err := groups.IDFromName(lbaas.network, lbSecGroupName) + if err != nil { + // check whether security group does not exist + _, ok := err.(*gophercloud.ErrResourceNotFound) + if ok { + // It is OK when the security group has been deleted by others. + return nil } else { - return nil, fmt.Errorf("floating-network-id or loadbalancer.openstack.org/floating-network-id should be specified when ensuring an external loadbalancer service.") - } - default: - return nil, fmt.Errorf("unknow service.beta.kubernetes.io/openstack-internal-load-balancer annotation: %v, specify \"true\" or \"false\".", - internal) - } - - ports := apiService.Spec.Ports - if len(ports) > 1 { - return nil, fmt.Errorf("multiple ports are not supported in openstack v1 load balancers") - } else if len(ports) == 0 { - return nil, fmt.Errorf("no ports provided to openstack load balancer") - } - - // The service controller verified all the protocols match on the ports, just check and use the first one - // TODO: Convert all error messages to use an event recorder - if ports[0].Protocol != v1.ProtocolTCP { - return nil, fmt.Errorf("Only TCP LoadBalancer is supported for openstack load balancers") - } - - affinity := apiService.Spec.SessionAffinity - var persistence *vips.SessionPersistence - switch affinity { - case v1.ServiceAffinityNone: - persistence = nil - case v1.ServiceAffinityClientIP: - persistence = &vips.SessionPersistence{Type: "SOURCE_IP"} - default: - return nil, fmt.Errorf("unsupported load balancer affinity: %v", affinity) - } - - sourceRanges, err := service.GetLoadBalancerSourceRanges(apiService) - if err != nil { - return nil, err - } - - if !service.IsAllowAll(sourceRanges) { - return nil, fmt.Errorf("Source range restrictions are not supported for openstack load balancers") - } - - glog.V(2).Infof("Checking if openstack load balancer already exists: %s", cloudprovider.GetLoadBalancerName(apiService)) - _, exists, err := lb.GetLoadBalancer(clusterName, apiService) - if err != nil { - return nil, fmt.Errorf("error checking if openstack load balancer already exists: %v", err) - } - - // TODO: Implement a more efficient update strategy for common changes than delete & create - // In particular, if we implement hosts update, we can get rid of UpdateHosts - if exists { - err := lb.EnsureLoadBalancerDeleted(clusterName, apiService) - if err != nil { - return nil, fmt.Errorf("error deleting existing openstack load balancer: %v", err) + return fmt.Errorf("Error occurred finding security group: %s: %v", lbSecGroupName, err) } } - lbmethod := pools.LBMethod(lb.opts.LBMethod) - if lbmethod == "" { - lbmethod = pools.LBMethodRoundRobin - } - name := cloudprovider.GetLoadBalancerName(apiService) - pool, err := pools.Create(lb.network, pools.CreateOpts{ - Name: name, - Protocol: pools.ProtocolTCP, - SubnetID: lb.opts.SubnetId, - LBMethod: lbmethod, - }).Extract() - if err != nil { - return nil, fmt.Errorf("Error creating pool for openstack load balancer %s: %v", name, err) + lbSecGroup := groups.Delete(lbaas.network, lbSecGroupID) + if lbSecGroup.Err != nil && !isNotFound(lbSecGroup.Err) { + return lbSecGroup.Err } - for _, node := range nodes { - addr, err := nodeAddressForLB(node) - if err != nil { - return nil, err - } - - _, err = members.Create(lb.network, members.CreateOpts{ - PoolID: pool.ID, - ProtocolPort: int(ports[0].NodePort), //Note: only handles single port - Address: addr, - }).Extract() - if err != nil { - return nil, fmt.Errorf("Error creating member for the pool(%s) of openstack load balancer %s: %v", - pool.ID, name, err) - } - } - - var mon *monitors.Monitor - if lb.opts.CreateMonitor { - mon, err = monitors.Create(lb.network, monitors.CreateOpts{ - Type: monitors.TypeTCP, - Delay: int(lb.opts.MonitorDelay.Duration.Seconds()), - Timeout: int(lb.opts.MonitorTimeout.Duration.Seconds()), - MaxRetries: int(lb.opts.MonitorMaxRetries), - }).Extract() - if err != nil { - return nil, fmt.Errorf("Error creating monitor for openstack load balancer %s: %v", name, err) - } - - _, err = pools.AssociateMonitor(lb.network, pool.ID, mon.ID).Extract() - if err != nil { - return nil, fmt.Errorf("Error associating monitor(%s) with pool(%s) for"+ - "openstack load balancer %s: %v", mon.ID, pool.ID, name, err) - } - } - - createOpts := vips.CreateOpts{ - Name: name, - Description: fmt.Sprintf("Kubernetes external service %s", name), - Protocol: "TCP", - ProtocolPort: int(ports[0].Port), //TODO: need to handle multi-port - PoolID: pool.ID, - SubnetID: lb.opts.SubnetId, - Persistence: persistence, - } - - loadBalancerIP := apiService.Spec.LoadBalancerIP - if loadBalancerIP != "" && internalAnnotation { - createOpts.Address = loadBalancerIP - } - - vip, err := vips.Create(lb.network, createOpts).Extract() - if err != nil { - return nil, fmt.Errorf("Error creating vip for openstack load balancer %s: %v", name, err) - } - - status := &v1.LoadBalancerStatus{} - if floatingPool != "" && !internalAnnotation { - floatIPOpts := floatingips.CreateOpts{ - FloatingNetworkID: floatingPool, - PortID: vip.PortID, - } - - loadBalancerIP := apiService.Spec.LoadBalancerIP - if loadBalancerIP != "" { - floatIPOpts.FloatingIP = loadBalancerIP - } - - floatIP, err := floatingips.Create(lb.network, floatIPOpts).Extract() - if err != nil { - return nil, fmt.Errorf("Error creating floatingip for openstack load balancer %s: %v", name, err) - } - - status.Ingress = []v1.LoadBalancerIngress{{IP: floatIP.FloatingIP}} + if len(lbaas.opts.NodeSecurityGroupIDs) == 0 { + // Just happen when nodes have not Security Group, or should not happen + // UpdateLoadBalancer and EnsureLoadBalancer can set lbaas.opts.NodeSecurityGroupIDs when it is empty + // And service controller call UpdateLoadBalancer to set lbaas.opts.NodeSecurityGroupIDs when controller manager service is restarted. + glog.Warningf("Can not find node-security-group from all the nodes of this cluster when delete loadbalancer service %s/%s", + service.Namespace, service.Name) } else { - status.Ingress = []v1.LoadBalancerIngress{{IP: vip.Address}} - } + // Delete the rules in the Node Security Group + for _, nodeSecurityGroupID := range lbaas.opts.NodeSecurityGroupIDs { + opts := rules.ListOpts{ + SecGroupID: nodeSecurityGroupID, + RemoteGroupID: lbSecGroupID, + } + secGroupRules, err := getSecurityGroupRules(lbaas.network, opts) - return status, nil + if err != nil && !isNotFound(err) { + msg := fmt.Sprintf("Error finding rules for remote group id %s in security group id %s: %v", lbSecGroupID, nodeSecurityGroupID, err) + return fmt.Errorf(msg) + } -} - -func (lb *LbaasV1) UpdateLoadBalancer(clusterName string, service *v1.Service, nodes []*v1.Node) error { - loadBalancerName := cloudprovider.GetLoadBalancerName(service) - glog.V(4).Infof("UpdateLoadBalancer(%v, %v, %v)", clusterName, loadBalancerName, nodes) - - vip, err := getVipByName(lb.network, loadBalancerName) - if err != nil { - return err - } - - // Set of member (addresses) that _should_ exist - addrs := map[string]bool{} - for _, node := range nodes { - addr, err := nodeAddressForLB(node) - if err != nil { - return err - } - - addrs[addr] = true - } - - // Iterate over members that _do_ exist - pager := members.List(lb.network, members.ListOpts{PoolID: vip.PoolID}) - err = pager.EachPage(func(page pagination.Page) (bool, error) { - memList, err := members.ExtractMembers(page) - if err != nil { - return false, err - } - - for _, member := range memList { - if _, found := addrs[member.Address]; found { - // Member already exists - delete(addrs, member.Address) - } else { - // Member needs to be deleted - err = members.Delete(lb.network, member.ID).ExtractErr() - if err != nil { - return false, err + for _, rule := range secGroupRules { + res := rules.Delete(lbaas.network, rule.ID) + if res.Err != nil && !isNotFound(res.Err) { + return fmt.Errorf("Error occurred deleting security group rule: %s: %v", rule.ID, res.Err) } } } - - return true, nil - }) - if err != nil { - return err - } - - // Anything left in addrs is a new member that needs to be added - for addr := range addrs { - _, err := members.Create(lb.network, members.CreateOpts{ - PoolID: vip.PoolID, - Address: addr, - ProtocolPort: vip.ProtocolPort, - }).Extract() - if err != nil { - return err - } - } - - return nil -} - -func (lb *LbaasV1) EnsureLoadBalancerDeleted(clusterName string, service *v1.Service) error { - loadBalancerName := cloudprovider.GetLoadBalancerName(service) - glog.V(4).Infof("EnsureLoadBalancerDeleted(%v, %v)", clusterName, loadBalancerName) - - vip, err := getVipByName(lb.network, loadBalancerName) - if err != nil && err != ErrNotFound { - return err - } - - if vip != nil && vip.PortID != "" { - floatingIP, err := getFloatingIPByPortID(lb.network, vip.PortID) - if err != nil && !isNotFound(err) { - return err - } - if floatingIP != nil { - err = floatingips.Delete(lb.network, floatingIP.ID).ExtractErr() - if err != nil && !isNotFound(err) { - return err - } - } - } - - // We have to delete the VIP before the pool can be deleted, - // so no point continuing if this fails. - if vip != nil { - err := vips.Delete(lb.network, vip.ID).ExtractErr() - if err != nil && !isNotFound(err) { - return err - } - } - - var pool *pools.Pool - if vip != nil { - pool, err = pools.Get(lb.network, vip.PoolID).Extract() - if err != nil && !isNotFound(err) { - return err - } - } else { - // The VIP is gone, but it is conceivable that a Pool - // still exists that we failed to delete on some - // previous occasion. Make a best effort attempt to - // cleanup any pools with the same name as the VIP. - pool, err = getPoolByName(lb.network, service.Name) - if err != nil && err != ErrNotFound { - return err - } - } - - if pool != nil { - for _, monId := range pool.MonitorIDs { - _, err = pools.DisassociateMonitor(lb.network, pool.ID, monId).Extract() - if err != nil { - return err - } - - err = monitors.Delete(lb.network, monId).ExtractErr() - if err != nil && !isNotFound(err) { - return err - } - } - for _, memberId := range pool.MemberIDs { - err = members.Delete(lb.network, memberId).ExtractErr() - if err != nil && !isNotFound(err) { - return err - } - } - err = pools.Delete(lb.network, pool.ID).ExtractErr() - if err != nil && !isNotFound(err) { - return err - } } return nil diff --git a/pkg/cloudprovider/providers/openstack/openstack_routes_test.go b/pkg/cloudprovider/providers/openstack/openstack_routes_test.go index 2cfdc7d8f79..7b44c7945a9 100644 --- a/pkg/cloudprovider/providers/openstack/openstack_routes_test.go +++ b/pkg/cloudprovider/providers/openstack/openstack_routes_test.go @@ -20,6 +20,8 @@ import ( "net" "testing" + "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers" "k8s.io/apimachinery/pkg/types" "k8s.io/kubernetes/pkg/cloudprovider" ) @@ -37,14 +39,18 @@ func TestRoutes(t *testing.T) { t.Fatalf("Failed to construct/authenticate OpenStack: %s", err) } + // Pick the first router and server to try a test with + os.routeOpts.RouterId = getRouters(os)[0].ID + servername := getServers(os)[0].Name + r, ok := os.Routes() if !ok { - t.Fatalf("Routes() returned false - perhaps your stack doens't support Neutron?") + t.Skip("Routes() returned false - perhaps your stack does not support Neutron extraroute extension?") } newroute := cloudprovider.Route{ DestinationCIDR: "10.164.2.0/24", - TargetNode: types.NodeName("testinstance"), + TargetNode: types.NodeName(servername), } err = r.CreateRoute(clusterName, "myhint", &newroute) if err != nil { @@ -69,3 +75,39 @@ func TestRoutes(t *testing.T) { t.Fatalf("DeleteRoute error: %v", err) } } + +func getServers(os *OpenStack) []servers.Server { + c, err := os.NewComputeV2() + allPages, err := servers.List(c, servers.ListOpts{}).AllPages() + if err != nil { + panic(err) + } + allServers, err := servers.ExtractServers(allPages) + if err != nil { + panic(err) + } + if len(allServers) == 0 { + panic("No servers to test with") + } + return allServers +} + +func getRouters(os *OpenStack) []routers.Router { + listOpts := routers.ListOpts{} + n, err := os.NewNetworkV2() + if err != nil { + panic(err) + } + allPages, err := routers.List(n, listOpts).AllPages() + if err != nil { + panic(err) + } + allRouters, err := routers.ExtractRouters(allPages) + if err != nil { + panic(err) + } + if len(allRouters) == 0 { + panic("No routers to test with") + } + return allRouters +} diff --git a/pkg/cloudprovider/providers/openstack/openstack_test.go b/pkg/cloudprovider/providers/openstack/openstack_test.go index 2c5f0886da5..168f6d301b7 100644 --- a/pkg/cloudprovider/providers/openstack/openstack_test.go +++ b/pkg/cloudprovider/providers/openstack/openstack_test.go @@ -20,6 +20,7 @@ import ( "fmt" "os" "reflect" + "regexp" "sort" "strings" "testing" @@ -29,6 +30,7 @@ import ( "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/rand" "k8s.io/apimachinery/pkg/util/wait" @@ -44,14 +46,14 @@ const ( // waiting for specified volume status. Starting with 1 // seconds, multiplying by 1.2 with each step and taking 13 steps at maximum // it will time out after 32s, which roughly corresponds to 30s - volumeStatusInitDealy = 1 * time.Second + volumeStatusInitDelay = 1 * time.Second volumeStatusFactor = 1.2 volumeStatusSteps = 13 ) func WaitForVolumeStatus(t *testing.T, os *OpenStack, volumeName string, status string) { backoff := wait.Backoff{ - Duration: volumeStatusInitDealy, + Duration: volumeStatusInitDelay, Factor: volumeStatusFactor, Steps: volumeStatusSteps, } @@ -169,6 +171,7 @@ func TestCheckOpenStackOpts(t *testing.T) { SubnetId: "6261548e-ffde-4bc7-bd22-59c83578c5ef", FloatingNetworkId: "38b8b5f9-64dc-4424-bf86-679595714786", LBMethod: "ROUND_ROBIN", + LBProvider: "haproxy", CreateMonitor: true, MonitorDelay: delay, MonitorTimeout: timeout, @@ -227,7 +230,7 @@ func TestCheckOpenStackOpts(t *testing.T) { SearchOrder: "", }, }, - expectedError: fmt.Errorf("Invalid value in section [Metadata] with key `search-order`. Value cannot be empty"), + expectedError: fmt.Errorf("invalid value in section [Metadata] with key `search-order`. Value cannot be empty"), }, { name: "test5", @@ -237,7 +240,7 @@ func TestCheckOpenStackOpts(t *testing.T) { SearchOrder: "value1,value2,value3", }, }, - expectedError: fmt.Errorf("Invalid value in section [Metadata] with key `search-order`. Value cannot contain more than 2 elements"), + expectedError: fmt.Errorf("invalid value in section [Metadata] with key `search-order`. Value cannot contain more than 2 elements"), }, { name: "test6", @@ -247,8 +250,8 @@ func TestCheckOpenStackOpts(t *testing.T) { SearchOrder: "value1", }, }, - expectedError: fmt.Errorf("Invalid element '%s' found in section [Metadata] with key `search-order`."+ - "Supported elements include '%s' and '%s'", "value1", configDriveID, metadataID), + expectedError: fmt.Errorf("invalid element %q found in section [Metadata] with key `search-order`."+ + "Supported elements include %q and %q", "value1", configDriveID, metadataID), }, } @@ -419,6 +422,7 @@ func configFromEnv() (cfg Config, ok bool) { cfg.Global.DomainId != "" || cfg.Global.DomainName != "")) cfg.Metadata.SearchOrder = fmt.Sprintf("%s,%s", configDriveID, metadataID) + cfg.BlockStorage.BSVersion = "auto" return } @@ -441,7 +445,7 @@ func TestLoadBalancer(t *testing.T) { t.Skipf("No config found in environment") } - versions := []string{"v1", "v2", ""} + versions := []string{"v2", ""} for _, v := range versions { t.Logf("Trying LBVersion = '%s'\n", v) @@ -497,6 +501,8 @@ func TestZones(t *testing.T) { } } +var diskPathRegexp = regexp.MustCompile("/dev/disk/(?:by-id|by-path)/") + func TestVolumes(t *testing.T) { cfg, ok := configFromEnv() if !ok { @@ -521,28 +527,40 @@ func TestVolumes(t *testing.T) { id, err := os.InstanceID() if err != nil { - t.Fatalf("Cannot find instance id: %v", err) + t.Logf("Cannot find instance id: %v - perhaps you are running this test outside a VM launched by OpenStack", err) + } else { + diskId, err := os.AttachDisk(id, vol) + if err != nil { + t.Fatalf("Cannot AttachDisk Cinder volume %s: %v", vol, err) + } + t.Logf("Volume (%s) attached, disk ID: %s\n", vol, diskId) + + WaitForVolumeStatus(t, os, vol, volumeInUseStatus) + + devicePath := os.GetDevicePath(diskId) + if diskPathRegexp.FindString(devicePath) == "" { + t.Fatalf("GetDevicePath returned and unexpected path for Cinder volume %s, returned %s", vol, devicePath) + } + t.Logf("Volume (%s) found at path: %s\n", vol, devicePath) + + err = os.DetachDisk(id, vol) + if err != nil { + t.Fatalf("Cannot DetachDisk Cinder volume %s: %v", vol, err) + } + t.Logf("Volume (%s) detached\n", vol) + + WaitForVolumeStatus(t, os, vol, volumeAvailableStatus) } - diskId, err := os.AttachDisk(id, vol) + expectedVolSize := resource.MustParse("2Gi") + newVolSize, err := os.ExpandVolume(vol, resource.MustParse("1Gi"), expectedVolSize) if err != nil { - t.Fatalf("Cannot AttachDisk Cinder volume %s: %v", vol, err) + t.Fatalf("Cannot expand a Cinder volume: %v", err) } - t.Logf("Volume (%s) attached, disk ID: %s\n", vol, diskId) - - WaitForVolumeStatus(t, os, vol, volumeInUseStatus) - - devicePath := os.GetDevicePath(diskId) - if !strings.HasPrefix(devicePath, "/dev/disk/by-id/") { - t.Fatalf("GetDevicePath returned and unexpected path for Cinder volume %s, returned %s", vol, devicePath) + if newVolSize != expectedVolSize { + t.Logf("Expected: %v but got: %v ", expectedVolSize, newVolSize) } - t.Logf("Volume (%s) found at path: %s\n", vol, devicePath) - - err = os.DetachDisk(id, vol) - if err != nil { - t.Fatalf("Cannot DetachDisk Cinder volume %s: %v", vol, err) - } - t.Logf("Volume (%s) detached\n", vol) + t.Logf("Volume expanded to (%v) \n", newVolSize) WaitForVolumeStatus(t, os, vol, volumeAvailableStatus) diff --git a/pkg/cloudprovider/providers/openstack/openstack_volumes.go b/pkg/cloudprovider/providers/openstack/openstack_volumes.go index f670bce6d06..eab5b7c9b5d 100644 --- a/pkg/cloudprovider/providers/openstack/openstack_volumes.go +++ b/pkg/cloudprovider/providers/openstack/openstack_volumes.go @@ -17,18 +17,21 @@ limitations under the License. package openstack import ( - "errors" "fmt" "io/ioutil" "path" + "path/filepath" "strings" "time" + "k8s.io/apimachinery/pkg/api/resource" k8s_volume "k8s.io/kubernetes/pkg/volume" "github.com/gophercloud/gophercloud" + volumeexpand "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions" volumes_v1 "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes" volumes_v2 "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes" + volumes_v3 "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach" "github.com/prometheus/client_golang/prometheus" @@ -39,6 +42,7 @@ type volumeService interface { createVolume(opts VolumeCreateOpts) (string, string, error) getVolume(volumeID string) (Volume, error) deleteVolume(volumeName string) error + expandVolume(volumeID string, newSize int) error } // Volumes implementation for v1 @@ -53,6 +57,12 @@ type VolumesV2 struct { opts BlockStorageOpts } +// Volumes implementation for v3 +type VolumesV3 struct { + blockstorage *gophercloud.ServiceClient + opts BlockStorageOpts +} + type Volume struct { // ID of the instance, to which this volume is attached. "" if not attached AttachedServerId string @@ -64,6 +74,8 @@ type Volume struct { Name string // Current status of the volume. Status string + // Volume size in GB + Size int } type VolumeCreateOpts struct { @@ -79,6 +91,11 @@ const ( VolumeInUseStatus = "in-use" VolumeDeletedStatus = "deleted" VolumeErrorStatus = "error" + + // On some environments, we need to query the metadata service in order + // to locate disks. We'll use the Newton version, which includes device + // metadata. + NewtonMetadataVersion = "2016-06-30" ) func (volumes *VolumesV1) createVolume(opts VolumeCreateOpts) (string, string, error) { @@ -121,20 +138,40 @@ func (volumes *VolumesV2) createVolume(opts VolumeCreateOpts) (string, string, e return vol.ID, vol.AvailabilityZone, nil } +func (volumes *VolumesV3) createVolume(opts VolumeCreateOpts) (string, string, error) { + startTime := time.Now() + + create_opts := volumes_v3.CreateOpts{ + Name: opts.Name, + Size: opts.Size, + VolumeType: opts.VolumeType, + AvailabilityZone: opts.Availability, + Metadata: opts.Metadata, + } + + vol, err := volumes_v3.Create(volumes.blockstorage, create_opts).Extract() + timeTaken := time.Since(startTime).Seconds() + recordOpenstackOperationMetric("create_v3_volume", timeTaken, err) + if err != nil { + return "", "", err + } + return vol.ID, vol.AvailabilityZone, nil +} + func (volumes *VolumesV1) getVolume(volumeID string) (Volume, error) { startTime := time.Now() volumeV1, err := volumes_v1.Get(volumes.blockstorage, volumeID).Extract() timeTaken := time.Since(startTime).Seconds() recordOpenstackOperationMetric("get_v1_volume", timeTaken, err) if err != nil { - glog.Errorf("Error occurred getting volume by ID: %s", volumeID) - return Volume{}, err + return Volume{}, fmt.Errorf("error occurred getting volume by ID: %s, err: %v", volumeID, err) } volume := Volume{ ID: volumeV1.ID, Name: volumeV1.Name, Status: volumeV1.Status, + Size: volumeV1.Size, } if len(volumeV1.Attachments) > 0 && volumeV1.Attachments[0]["server_id"] != nil { @@ -151,14 +188,14 @@ func (volumes *VolumesV2) getVolume(volumeID string) (Volume, error) { timeTaken := time.Since(startTime).Seconds() recordOpenstackOperationMetric("get_v2_volume", timeTaken, err) if err != nil { - glog.Errorf("Error occurred getting volume by ID: %s", volumeID) - return Volume{}, err + return Volume{}, fmt.Errorf("error occurred getting volume by ID: %s, err: %v", volumeID, err) } volume := Volume{ ID: volumeV2.ID, Name: volumeV2.Name, Status: volumeV2.Status, + Size: volumeV2.Size, } if len(volumeV2.Attachments) > 0 { @@ -169,15 +206,34 @@ func (volumes *VolumesV2) getVolume(volumeID string) (Volume, error) { return volume, nil } +func (volumes *VolumesV3) getVolume(volumeID string) (Volume, error) { + startTime := time.Now() + volumeV3, err := volumes_v3.Get(volumes.blockstorage, volumeID).Extract() + timeTaken := time.Since(startTime).Seconds() + recordOpenstackOperationMetric("get_v3_volume", timeTaken, err) + if err != nil { + return Volume{}, fmt.Errorf("error occurred getting volume by ID: %s, err: %v", volumeID, err) + } + + volume := Volume{ + ID: volumeV3.ID, + Name: volumeV3.Name, + Status: volumeV3.Status, + } + + if len(volumeV3.Attachments) > 0 { + volume.AttachedServerId = volumeV3.Attachments[0].ServerID + volume.AttachedDevice = volumeV3.Attachments[0].Device + } + + return volume, nil +} + func (volumes *VolumesV1) deleteVolume(volumeID string) error { startTime := time.Now() err := volumes_v1.Delete(volumes.blockstorage, volumeID).ExtractErr() timeTaken := time.Since(startTime).Seconds() recordOpenstackOperationMetric("delete_v1_volume", timeTaken, err) - if err != nil { - glog.Errorf("Cannot delete volume %s: %v", volumeID, err) - } - return err } @@ -186,10 +242,47 @@ func (volumes *VolumesV2) deleteVolume(volumeID string) error { err := volumes_v2.Delete(volumes.blockstorage, volumeID).ExtractErr() timeTaken := time.Since(startTime).Seconds() recordOpenstackOperationMetric("delete_v2_volume", timeTaken, err) - if err != nil { - glog.Errorf("Cannot delete volume %s: %v", volumeID, err) - } + return err +} +func (volumes *VolumesV3) deleteVolume(volumeID string) error { + startTime := time.Now() + err := volumes_v3.Delete(volumes.blockstorage, volumeID).ExtractErr() + timeTaken := time.Since(startTime).Seconds() + recordOpenstackOperationMetric("delete_v3_volume", timeTaken, err) + return err +} + +func (volumes *VolumesV1) expandVolume(volumeID string, newSize int) error { + startTime := time.Now() + create_opts := volumeexpand.ExtendSizeOpts{ + NewSize: newSize, + } + err := volumeexpand.ExtendSize(volumes.blockstorage, volumeID, create_opts).ExtractErr() + timeTaken := time.Since(startTime).Seconds() + recordOpenstackOperationMetric("expand_volume", timeTaken, err) + return err +} + +func (volumes *VolumesV2) expandVolume(volumeID string, newSize int) error { + startTime := time.Now() + create_opts := volumeexpand.ExtendSizeOpts{ + NewSize: newSize, + } + err := volumeexpand.ExtendSize(volumes.blockstorage, volumeID, create_opts).ExtractErr() + timeTaken := time.Since(startTime).Seconds() + recordOpenstackOperationMetric("expand_volume", timeTaken, err) + return err +} + +func (volumes *VolumesV3) expandVolume(volumeID string, newSize int) error { + startTime := time.Now() + create_opts := volumeexpand.ExtendSizeOpts{ + NewSize: newSize, + } + err := volumeexpand.ExtendSize(volumes.blockstorage, volumeID, create_opts).ExtractErr() + timeTaken := time.Since(startTime).Seconds() + recordOpenstackOperationMetric("expand_volume", timeTaken, err) return err } @@ -200,7 +293,6 @@ func (os *OpenStack) OperationPending(diskName string) (bool, string, error) { } volumeStatus := volume.Status if volumeStatus == VolumeErrorStatus { - glog.Errorf("status of volume %s is %s", diskName, volumeStatus) return false, volumeStatus, nil } if volumeStatus == VolumeAvailableStatus || volumeStatus == VolumeInUseStatus || volumeStatus == VolumeDeletedStatus { @@ -226,9 +318,7 @@ func (os *OpenStack) AttachDisk(instanceID, volumeID string) (string, error) { glog.V(4).Infof("Disk %s is already attached to instance %s", volumeID, instanceID) return volume.ID, nil } - errmsg := fmt.Sprintf("Disk %s is attached to a different instance (%s)", volumeID, volume.AttachedServerId) - glog.V(2).Infof(errmsg) - return "", errors.New(errmsg) + return "", fmt.Errorf("disk %s is attached to a different instance (%s)", volumeID, volume.AttachedServerId) } startTime := time.Now() @@ -239,8 +329,7 @@ func (os *OpenStack) AttachDisk(instanceID, volumeID string) (string, error) { timeTaken := time.Since(startTime).Seconds() recordOpenstackOperationMetric("attach_disk", timeTaken, err) if err != nil { - glog.Errorf("Failed to attach %s volume to %s compute: %v", volumeID, instanceID, err) - return "", err + return "", fmt.Errorf("failed to attach %s volume to %s compute: %v", volumeID, instanceID, err) } glog.V(2).Infof("Successfully attached %s volume to %s compute", volumeID, instanceID) return volume.ID, nil @@ -259,18 +348,14 @@ func (os *OpenStack) DetachDisk(instanceID, volumeID string) error { } if volume.Status != VolumeInUseStatus { - errmsg := fmt.Sprintf("can not detach volume %s, its status is %s.", volume.Name, volume.Status) - glog.Errorf(errmsg) - return errors.New(errmsg) + return fmt.Errorf("can not detach volume %s, its status is %s", volume.Name, volume.Status) } cClient, err := os.NewComputeV2() if err != nil { return err } if volume.AttachedServerId != instanceID { - errMsg := fmt.Sprintf("Disk: %s has no attachments or is not attached to compute: %s", volume.Name, instanceID) - glog.Errorf(errMsg) - return errors.New(errMsg) + return fmt.Errorf("disk: %s has no attachments or is not attached to compute: %s", volume.Name, instanceID) } else { startTime := time.Now() // This is a blocking call and effects kubelet's performance directly. @@ -279,8 +364,7 @@ func (os *OpenStack) DetachDisk(instanceID, volumeID string) error { timeTaken := time.Since(startTime).Seconds() recordOpenstackOperationMetric("detach_disk", timeTaken, err) if err != nil { - glog.Errorf("Failed to delete volume %s from compute %s attached %v", volume.ID, instanceID, err) - return err + return fmt.Errorf("failed to delete volume %s from compute %s attached %v", volume.ID, instanceID, err) } glog.V(2).Infof("Successfully detached volume: %s from compute: %s", volume.ID, instanceID) } @@ -288,12 +372,44 @@ func (os *OpenStack) DetachDisk(instanceID, volumeID string) error { return nil } +// ExpandVolume expands the size of specific cinder volume (in GiB) +func (os *OpenStack) ExpandVolume(volumeID string, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) { + volume, err := os.getVolume(volumeID) + if err != nil { + return oldSize, err + } + if volume.Status != VolumeAvailableStatus { + // cinder volume can not be expanded if its status is not available + return oldSize, fmt.Errorf("volume status is not available") + } + + volSizeBytes := newSize.Value() + // Cinder works with gigabytes, convert to GiB with rounding up + volSizeGB := int(k8s_volume.RoundUpSize(volSizeBytes, 1024*1024*1024)) + newSizeQuant := resource.MustParse(fmt.Sprintf("%dGi", volSizeGB)) + + // if volume size equals to or greater than the newSize, return nil + if volume.Size >= volSizeGB { + return newSizeQuant, nil + } + + volumes, err := os.volumeService("") + if err != nil { + return oldSize, err + } + + err = volumes.expandVolume(volumeID, volSizeGB) + if err != nil { + return oldSize, err + } + return newSizeQuant, nil +} + // getVolume retrieves Volume by its ID. func (os *OpenStack) getVolume(volumeID string) (Volume, error) { volumes, err := os.volumeService("") - if err != nil || volumes == nil { - glog.Errorf("Unable to initialize cinder client for region: %s", os.region) - return Volume{}, err + if err != nil { + return Volume{}, fmt.Errorf("unable to initialize cinder client for region: %s, err: %v", os.region, err) } return volumes.getVolume(volumeID) } @@ -301,9 +417,8 @@ func (os *OpenStack) getVolume(volumeID string) (Volume, error) { // CreateVolume creates a volume of given size (in GiB) func (os *OpenStack) CreateVolume(name string, size int, vtype, availability string, tags *map[string]string) (string, string, bool, error) { volumes, err := os.volumeService("") - if err != nil || volumes == nil { - glog.Errorf("Unable to initialize cinder client for region: %s", os.region) - return "", "", os.bsOpts.IgnoreVolumeAZ, err + if err != nil { + return "", "", os.bsOpts.IgnoreVolumeAZ, fmt.Errorf("unable to initialize cinder client for region: %s, err: %v", os.region, err) } opts := VolumeCreateOpts{ @@ -319,8 +434,7 @@ func (os *OpenStack) CreateVolume(name string, size int, vtype, availability str volumeID, volumeAZ, err := volumes.createVolume(opts) if err != nil { - glog.Errorf("Failed to create a %d GB volume: %v", size, err) - return "", "", os.bsOpts.IgnoreVolumeAZ, err + return "", "", os.bsOpts.IgnoreVolumeAZ, fmt.Errorf("failed to create a %d GB volume: %v", size, err) } glog.Infof("Created volume %v in Availability Zone: %v Ignore volume AZ: %v", volumeID, volumeAZ, os.bsOpts.IgnoreVolumeAZ) @@ -328,8 +442,9 @@ func (os *OpenStack) CreateVolume(name string, size int, vtype, availability str } // GetDevicePath returns the path of an attached block storage volume, specified by its id. -func (os *OpenStack) GetDevicePath(volumeID string) string { - // Build a list of candidate device paths +func (os *OpenStack) GetDevicePathBySerialId(volumeID string) string { + // Build a list of candidate device paths. + // Certain Nova drivers will set the disk serial ID, including the Cinder volume id. candidateDeviceNodes := []string{ // KVM fmt.Sprintf("virtio-%s", volumeID[:20]), @@ -350,10 +465,74 @@ func (os *OpenStack) GetDevicePath(volumeID string) string { } } - glog.Warningf("Failed to find device for the volumeID: %q\n", volumeID) + glog.V(4).Infof("Failed to find device for the volumeID: %q by serial ID", volumeID) return "" } +func (os *OpenStack) GetDevicePathFromInstanceMetadata(volumeID string) string { + // Nova Hyper-V hosts cannot override disk SCSI IDs. In order to locate + // volumes, we're querying the metadata service. Note that the Hyper-V + // driver will include device metadata for untagged volumes as well. + // + // We're avoiding using cached metadata (or the configdrive), + // relying on the metadata service. + instanceMetadata, err := getMetadataFromMetadataService( + NewtonMetadataVersion) + + if err != nil { + glog.V(4).Infof( + "Could not retrieve instance metadata. Error: %v", err) + return "" + } + + for _, device := range instanceMetadata.Devices { + if device.Type == "disk" && device.Serial == volumeID { + glog.V(4).Infof( + "Found disk metadata for volumeID %q. Bus: %q, Address: %q", + volumeID, device.Bus, device.Address) + + diskPattern := fmt.Sprintf( + "/dev/disk/by-path/*-%s-%s", + device.Bus, device.Address) + diskPaths, err := filepath.Glob(diskPattern) + if err != nil { + glog.Errorf( + "could not retrieve disk path for volumeID: %q. Error filepath.Glob(%q): %v", + volumeID, diskPattern, err) + return "" + } + + if len(diskPaths) == 1 { + return diskPaths[0] + } + + glog.Errorf( + "expecting to find one disk path for volumeID %q, found %d: %v", + volumeID, len(diskPaths), diskPaths) + return "" + } + } + + glog.V(4).Infof( + "Could not retrieve device metadata for volumeID: %q", volumeID) + return "" +} + +// GetDevicePath returns the path of an attached block storage volume, specified by its id. +func (os *OpenStack) GetDevicePath(volumeID string) string { + devicePath := os.GetDevicePathBySerialId(volumeID) + + if devicePath == "" { + devicePath = os.GetDevicePathFromInstanceMetadata(volumeID) + } + + if devicePath == "" { + glog.Warningf("Failed to find device for the volumeID: %q", volumeID) + } + + return devicePath +} + func (os *OpenStack) DeleteVolume(volumeID string) error { used, err := os.diskIsUsed(volumeID) if err != nil { @@ -365,15 +544,11 @@ func (os *OpenStack) DeleteVolume(volumeID string) error { } volumes, err := os.volumeService("") - if err != nil || volumes == nil { - glog.Errorf("Unable to initialize cinder client for region: %s", os.region) - return err + if err != nil { + return fmt.Errorf("unable to initialize cinder client for region: %s, err: %v", os.region, err) } err = volumes.deleteVolume(volumeID) - if err != nil { - glog.Errorf("Cannot delete volume %s: %v", volumeID, err) - } return err } @@ -387,9 +562,7 @@ func (os *OpenStack) GetAttachmentDiskPath(instanceID, volumeID string) (string, return "", err } if volume.Status != VolumeInUseStatus { - errmsg := fmt.Sprintf("can not get device path of volume %s, its status is %s.", volume.Name, volume.Status) - glog.Errorf(errmsg) - return "", errors.New(errmsg) + return "", fmt.Errorf("can not get device path of volume %s, its status is %s ", volume.Name, volume.Status) } if volume.AttachedServerId != "" { if instanceID == volume.AttachedServerId { @@ -397,12 +570,10 @@ func (os *OpenStack) GetAttachmentDiskPath(instanceID, volumeID string) (string, // see http://developer.openstack.org/api-ref-blockstorage-v1.html return volume.AttachedDevice, nil } else { - errMsg := fmt.Sprintf("Disk %q is attached to a different compute: %q, should be detached before proceeding", volumeID, volume.AttachedServerId) - glog.Errorf(errMsg) - return "", errors.New(errMsg) + return "", fmt.Errorf("disk %q is attached to a different compute: %q, should be detached before proceeding", volumeID, volume.AttachedServerId) } } - return "", fmt.Errorf("volume %s has no ServerId.", volumeID) + return "", fmt.Errorf("volume %s has no ServerId", volumeID) } // DiskIsAttached queries if a volume is attached to a compute instance @@ -419,7 +590,11 @@ func (os *OpenStack) DiskIsAttached(instanceID, volumeID string) (bool, error) { func (os *OpenStack) DisksAreAttached(instanceID string, volumeIDs []string) (map[string]bool, error) { attached := make(map[string]bool) for _, volumeID := range volumeIDs { - isAttached, _ := os.DiskIsAttached(instanceID, volumeID) + isAttached, err := os.DiskIsAttached(instanceID, volumeID) + if err != nil && err != ErrNotFound { + attached[volumeID] = true + continue + } attached[volumeID] = isAttached } return attached, nil diff --git a/pkg/cloudprovider/providers/ovirt/BUILD b/pkg/cloudprovider/providers/ovirt/BUILD index a3ec5a0034a..35390b0e711 100644 --- a/pkg/cloudprovider/providers/ovirt/BUILD +++ b/pkg/cloudprovider/providers/ovirt/BUILD @@ -22,8 +22,8 @@ go_library( go_test( name = "go_default_test", srcs = ["ovirt_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/ovirt", - library = ":go_default_library", deps = ["//pkg/cloudprovider:go_default_library"], ) diff --git a/pkg/cloudprovider/providers/ovirt/ovirt.go b/pkg/cloudprovider/providers/ovirt/ovirt.go index e688257ad87..eaade007074 100644 --- a/pkg/cloudprovider/providers/ovirt/ovirt.go +++ b/pkg/cloudprovider/providers/ovirt/ovirt.go @@ -128,11 +128,6 @@ func (v *OVirtCloud) ProviderName() string { return ProviderName } -// ScrubDNS filters DNS settings for pods. -func (v *OVirtCloud) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) { - return nameservers, searches -} - // HasClusterID returns true if the cluster has a clusterID func (v *OVirtCloud) HasClusterID() bool { return true diff --git a/pkg/cloudprovider/providers/photon/BUILD b/pkg/cloudprovider/providers/photon/BUILD index 875fec35449..3dc4b33d4b3 100644 --- a/pkg/cloudprovider/providers/photon/BUILD +++ b/pkg/cloudprovider/providers/photon/BUILD @@ -11,7 +11,7 @@ go_library( srcs = ["photon.go"], importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/photon", deps = [ - "//pkg/api/v1/helper:go_default_library", + "//pkg/apis/core/v1/helper:go_default_library", "//pkg/cloudprovider:go_default_library", "//pkg/controller:go_default_library", "//vendor/github.com/golang/glog:go_default_library", @@ -25,8 +25,8 @@ go_library( go_test( name = "go_default_test", srcs = ["photon_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/photon", - library = ":go_default_library", deps = [ "//pkg/cloudprovider:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", diff --git a/pkg/cloudprovider/providers/photon/photon.go b/pkg/cloudprovider/providers/photon/photon.go index 0abb8149fa0..d2e8c2d780a 100644 --- a/pkg/cloudprovider/providers/photon/photon.go +++ b/pkg/cloudprovider/providers/photon/photon.go @@ -38,7 +38,7 @@ import ( "gopkg.in/gcfg.v1" "k8s.io/api/core/v1" k8stypes "k8s.io/apimachinery/pkg/types" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" "k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/controller" ) @@ -546,11 +546,6 @@ func (pc *PCCloud) Routes() (cloudprovider.Routes, bool) { return nil, false } -// ScrubDNS filters DNS settings for pods. -func (pc *PCCloud) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) { - return nameservers, searches -} - // HasClusterID returns true if the cluster has a clusterID func (pc *PCCloud) HasClusterID() bool { return true diff --git a/pkg/cloudprovider/providers/vsphere/BUILD b/pkg/cloudprovider/providers/vsphere/BUILD index 598a2700a4f..2ccae8c2f2d 100644 --- a/pkg/cloudprovider/providers/vsphere/BUILD +++ b/pkg/cloudprovider/providers/vsphere/BUILD @@ -9,33 +9,36 @@ load( go_library( name = "go_default_library", srcs = [ + "nodemanager.go", "vsphere.go", "vsphere_util.go", ], importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/vsphere", deps = [ - "//pkg/api/v1/helper:go_default_library", + "//pkg/apis/core/v1/helper:go_default_library", "//pkg/cloudprovider:go_default_library", "//pkg/cloudprovider/providers/vsphere/vclib:go_default_library", "//pkg/cloudprovider/providers/vsphere/vclib/diskmanagers:go_default_library", "//pkg/controller:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/vmware/govmomi:go_default_library", - "//vendor/github.com/vmware/govmomi/object:go_default_library", "//vendor/github.com/vmware/govmomi/vim25:go_default_library", "//vendor/github.com/vmware/govmomi/vim25/mo:go_default_library", "//vendor/golang.org/x/net/context:go_default_library", "//vendor/gopkg.in/gcfg.v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/client-go/informers:go_default_library", + "//vendor/k8s.io/client-go/tools/cache:go_default_library", ], ) go_test( name = "go_default_test", srcs = ["vsphere_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/vsphere", - library = ":go_default_library", deps = [ "//pkg/cloudprovider:go_default_library", "//pkg/cloudprovider/providers/vsphere/vclib:go_default_library", diff --git a/pkg/cloudprovider/providers/vsphere/nodemanager.go b/pkg/cloudprovider/providers/vsphere/nodemanager.go new file mode 100644 index 00000000000..580dbae413d --- /dev/null +++ b/pkg/cloudprovider/providers/vsphere/nodemanager.go @@ -0,0 +1,319 @@ +/* +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 vsphere + +import ( + "fmt" + "strings" + "sync" + + "github.com/golang/glog" + "golang.org/x/net/context" + "k8s.io/api/core/v1" + k8stypes "k8s.io/apimachinery/pkg/types" + "k8s.io/kubernetes/pkg/cloudprovider/providers/vsphere/vclib" +) + +// Stores info about the kubernetes node +type NodeInfo struct { + dataCenter *vclib.Datacenter + vm *vclib.VirtualMachine + vcServer string +} + +type NodeManager struct { + // TODO: replace map with concurrent map when k8s supports go v1.9 + + // Maps the VC server to VSphereInstance + vsphereInstanceMap map[string]*VSphereInstance + // Maps node name to node info. + nodeInfoMap map[string]*NodeInfo + // Maps node name to node structure + registeredNodes map[string]*v1.Node + + // Mutexes + registeredNodesLock sync.RWMutex + nodeInfoLock sync.RWMutex +} + +type NodeDetails struct { + NodeName string + vm *vclib.VirtualMachine +} + +// TODO: Make it configurable in vsphere.conf +const ( + POOL_SIZE = 8 + QUEUE_SIZE = POOL_SIZE * 10 +) + +func (nm *NodeManager) DiscoverNode(node *v1.Node) error { + type VmSearch struct { + vc string + datacenter *vclib.Datacenter + } + + var mutex = &sync.Mutex{} + var globalErrMutex = &sync.Mutex{} + var queueChannel chan *VmSearch + var wg sync.WaitGroup + var globalErr *error + + queueChannel = make(chan *VmSearch, QUEUE_SIZE) + nodeUUID := node.Status.NodeInfo.SystemUUID + vmFound := false + globalErr = nil + + setGlobalErr := func(err error) { + globalErrMutex.Lock() + globalErr = &err + globalErrMutex.Unlock() + } + + setVMFound := func(found bool) { + mutex.Lock() + vmFound = found + mutex.Unlock() + } + + getVMFound := func() bool { + mutex.Lock() + found := vmFound + mutex.Unlock() + return found + } + + go func() { + var datacenterObjs []*vclib.Datacenter + for vc, vsi := range nm.vsphereInstanceMap { + + found := getVMFound() + if found == true { + break + } + + // Create context + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + err := vsi.conn.Connect(ctx) + if err != nil { + glog.V(4).Info("Discovering node error vc:", err) + setGlobalErr(err) + continue + } + + if vsi.cfg.Datacenters == "" { + datacenterObjs, err = vclib.GetAllDatacenter(ctx, vsi.conn) + if err != nil { + glog.V(4).Info("Discovering node error dc:", err) + setGlobalErr(err) + continue + } + } else { + datacenters := strings.Split(vsi.cfg.Datacenters, ",") + for _, dc := range datacenters { + dc = strings.TrimSpace(dc) + if dc == "" { + continue + } + datacenterObj, err := vclib.GetDatacenter(ctx, vsi.conn, dc) + if err != nil { + glog.V(4).Info("Discovering node error dc:", err) + setGlobalErr(err) + continue + } + datacenterObjs = append(datacenterObjs, datacenterObj) + } + } + + for _, datacenterObj := range datacenterObjs { + found := getVMFound() + if found == true { + break + } + + glog.V(4).Infof("Finding node %s in vc=%s and datacenter=%s", node.Name, vc, datacenterObj.Name()) + queueChannel <- &VmSearch{ + vc: vc, + datacenter: datacenterObj, + } + } + } + close(queueChannel) + }() + + for i := 0; i < POOL_SIZE; i++ { + go func() { + for res := range queueChannel { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + vm, err := res.datacenter.GetVMByUUID(ctx, nodeUUID) + if err != nil { + glog.V(4).Infof("Error %q while looking for vm=%+v in vc=%s and datacenter=%s", + err, node.Name, vm, res.vc, res.datacenter.Name()) + if err != vclib.ErrNoVMFound { + setGlobalErr(err) + } else { + glog.V(4).Infof("Did not find node %s in vc=%s and datacenter=%s", + node.Name, res.vc, res.datacenter.Name(), err) + } + continue + } + if vm != nil { + glog.V(4).Infof("Found node %s as vm=%+v in vc=%s and datacenter=%s", + node.Name, vm, res.vc, res.datacenter.Name()) + + nodeInfo := &NodeInfo{dataCenter: res.datacenter, vm: vm, vcServer: res.vc} + nm.addNodeInfo(node.ObjectMeta.Name, nodeInfo) + for range queueChannel { + } + setVMFound(true) + break + } + } + wg.Done() + }() + wg.Add(1) + } + wg.Wait() + if vmFound { + return nil + } + if globalErr != nil { + return *globalErr + } + + glog.V(4).Infof("Discovery Node: %q vm not found", node.Name) + return vclib.ErrNoVMFound +} + +func (nm *NodeManager) RegisterNode(node *v1.Node) error { + nm.addNode(node) + nm.DiscoverNode(node) + return nil +} + +func (nm *NodeManager) UnRegisterNode(node *v1.Node) error { + nm.removeNode(node) + return nil +} + +func (nm *NodeManager) RediscoverNode(nodeName k8stypes.NodeName) error { + node, err := nm.GetNode(nodeName) + + if err != nil { + return err + } + return nm.DiscoverNode(&node) +} + +func (nm *NodeManager) GetNode(nodeName k8stypes.NodeName) (v1.Node, error) { + nm.registeredNodesLock.RLock() + node := nm.registeredNodes[convertToString(nodeName)] + nm.registeredNodesLock.RUnlock() + if node == nil { + return v1.Node{}, vclib.ErrNoVMFound + } + return *node, nil +} + +func (nm *NodeManager) addNode(node *v1.Node) { + nm.registeredNodesLock.Lock() + nm.registeredNodes[node.ObjectMeta.Name] = node + nm.registeredNodesLock.Unlock() +} + +func (nm *NodeManager) removeNode(node *v1.Node) { + nm.registeredNodesLock.Lock() + delete(nm.registeredNodes, node.ObjectMeta.Name) + nm.registeredNodesLock.Unlock() + + nm.nodeInfoLock.Lock() + delete(nm.nodeInfoMap, node.ObjectMeta.Name) + nm.nodeInfoLock.Unlock() +} + +// GetNodeInfo returns a NodeInfo which datacenter, vm and vc server ip address. +// This method returns an error if it is unable find node VCs and DCs listed in vSphere.conf +// NodeInfo returned may not be updated to reflect current VM location. +func (nm *NodeManager) GetNodeInfo(nodeName k8stypes.NodeName) (NodeInfo, error) { + getNodeInfo := func(nodeName k8stypes.NodeName) *NodeInfo { + nm.nodeInfoLock.RLock() + nodeInfo := nm.nodeInfoMap[convertToString(nodeName)] + nm.nodeInfoLock.RUnlock() + return nodeInfo + } + nodeInfo := getNodeInfo(nodeName) + if nodeInfo == nil { + err := nm.RediscoverNode(nodeName) + if err != nil { + glog.V(4).Infof("error %q node info for node %q not found", err, convertToString(nodeName)) + return NodeInfo{}, err + } + nodeInfo = getNodeInfo(nodeName) + } + return *nodeInfo, nil +} + +func (nm *NodeManager) GetNodeDetails() ([]NodeDetails, error) { + nm.nodeInfoLock.RLock() + defer nm.nodeInfoLock.RUnlock() + var nodeDetails []NodeDetails + vsphereSessionRefreshMap := make(map[string]bool) + + // Create context + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + for nodeName, nodeInfo := range nm.nodeInfoMap { + nodeDetails = append(nodeDetails, NodeDetails{nodeName, nodeInfo.vm}) + if vsphereSessionRefreshMap[nodeInfo.vcServer] { + continue + } + vsphereInstance := nm.vsphereInstanceMap[nodeInfo.vcServer] + if vsphereInstance == nil { + err := fmt.Errorf("vSphereInstance for vc server %q not found while looking for vm %q", nodeInfo.vcServer, nodeInfo.vm) + return nil, err + } + err := vsphereInstance.conn.Connect(ctx) + if err != nil { + return nil, err + } + vsphereSessionRefreshMap[nodeInfo.vcServer] = true + } + return nodeDetails, nil +} + +func (nm *NodeManager) addNodeInfo(nodeName string, nodeInfo *NodeInfo) { + nm.nodeInfoLock.Lock() + nm.nodeInfoMap[nodeName] = nodeInfo + nm.nodeInfoLock.Unlock() +} + +func (nm *NodeManager) GetVSphereInstance(nodeName k8stypes.NodeName) (VSphereInstance, error) { + nodeInfo, err := nm.GetNodeInfo(nodeName) + if err != nil { + glog.V(4).Infof("node info for node %q not found", convertToString(nodeName)) + return VSphereInstance{}, err + } + vsphereInstance := nm.vsphereInstanceMap[nodeInfo.vcServer] + if vsphereInstance == nil { + return VSphereInstance{}, fmt.Errorf("vSphereInstance for vc server %q not found while looking for node %q", nodeInfo.vcServer, convertToString(nodeName)) + } + return *vsphereInstance, nil +} diff --git a/pkg/cloudprovider/providers/vsphere/vclib/custom_errors.go b/pkg/cloudprovider/providers/vsphere/vclib/custom_errors.go index 391f328f426..6709c4cf21a 100644 --- a/pkg/cloudprovider/providers/vsphere/vclib/custom_errors.go +++ b/pkg/cloudprovider/providers/vsphere/vclib/custom_errors.go @@ -25,6 +25,7 @@ const ( NoDevicesFoundErrMsg = "No devices found" DiskNotFoundErrMsg = "No vSphere disk ID found" InvalidVolumeOptionsErrMsg = "VolumeOptions verification failed" + NoVMFoundErrMsg = "No VM found" ) // Error constants @@ -34,4 +35,5 @@ var ( ErrNoDevicesFound = errors.New(NoDevicesFoundErrMsg) ErrNoDiskIDFound = errors.New(DiskNotFoundErrMsg) ErrInvalidVolumeOptions = errors.New(InvalidVolumeOptionsErrMsg) + ErrNoVMFound = errors.New(NoVMFoundErrMsg) ) diff --git a/pkg/cloudprovider/providers/vsphere/vclib/datacenter.go b/pkg/cloudprovider/providers/vsphere/vclib/datacenter.go index ebb54b94312..8b0a10e9a92 100644 --- a/pkg/cloudprovider/providers/vsphere/vclib/datacenter.go +++ b/pkg/cloudprovider/providers/vsphere/vclib/datacenter.go @@ -49,6 +49,22 @@ func GetDatacenter(ctx context.Context, connection *VSphereConnection, datacente return &dc, nil } +// GetAllDatacenter returns all the DataCenter Objects +func GetAllDatacenter(ctx context.Context, connection *VSphereConnection) ([]*Datacenter, error) { + var dc []*Datacenter + finder := find.NewFinder(connection.GoVmomiClient.Client, true) + datacenters, err := finder.DatacenterList(ctx, "*") + if err != nil { + glog.Errorf("Failed to find the datacenter. err: %+v", err) + return nil, err + } + for _, datacenter := range datacenters { + dc = append(dc, &(Datacenter{datacenter})) + } + + return dc, nil +} + // GetVMByUUID gets the VM object from the given vmUUID func (dc *Datacenter) GetVMByUUID(ctx context.Context, vmUUID string) (*VirtualMachine, error) { s := object.NewSearchIndex(dc.Client()) @@ -60,7 +76,7 @@ func (dc *Datacenter) GetVMByUUID(ctx context.Context, vmUUID string) (*VirtualM } if svm == nil { glog.Errorf("Unable to find VM by UUID. VM UUID: %s", vmUUID) - return nil, fmt.Errorf("Failed to find VM by UUID: %s", vmUUID) + return nil, ErrNoVMFound } virtualMachine := VirtualMachine{object.NewVirtualMachine(dc.Client(), svm.Reference()), dc} return &virtualMachine, nil @@ -79,6 +95,41 @@ func (dc *Datacenter) GetVMByPath(ctx context.Context, vmPath string) (*VirtualM return &virtualMachine, nil } +// GetAllDatastores gets the datastore URL to DatastoreInfo map for all the datastores in +// the datacenter. +func (dc *Datacenter) GetAllDatastores(ctx context.Context) (map[string]*DatastoreInfo, error) { + finder := getFinder(dc) + datastores, err := finder.DatastoreList(ctx, "*") + if err != nil { + glog.Errorf("Failed to get all the datastores. err: %+v", err) + return nil, err + } + var dsList []types.ManagedObjectReference + for _, ds := range datastores { + dsList = append(dsList, ds.Reference()) + } + + var dsMoList []mo.Datastore + pc := property.DefaultCollector(dc.Client()) + properties := []string{DatastoreInfoProperty} + err = pc.Retrieve(ctx, dsList, properties, &dsMoList) + if err != nil { + glog.Errorf("Failed to get Datastore managed objects from datastore objects."+ + " dsObjList: %+v, properties: %+v, err: %v", dsList, properties, err) + return nil, err + } + + dsURLInfoMap := make(map[string]*DatastoreInfo) + for _, dsMo := range dsMoList { + dsURLInfoMap[dsMo.Info.GetDatastoreInfo().Url] = &DatastoreInfo{ + &Datastore{object.NewDatastore(dc.Client(), dsMo.Reference()), + dc}, + dsMo.Info.GetDatastoreInfo()} + } + glog.V(9).Infof("dsURLInfoMap : %+v", dsURLInfoMap) + return dsURLInfoMap, nil +} + // GetDatastoreByPath gets the Datastore object from the given vmDiskPath func (dc *Datacenter) GetDatastoreByPath(ctx context.Context, vmDiskPath string) (*Datastore, error) { datastorePathObj := new(object.DatastorePath) @@ -87,14 +138,8 @@ func (dc *Datacenter) GetDatastoreByPath(ctx context.Context, vmDiskPath string) glog.Errorf("Failed to parse vmDiskPath: %s", vmDiskPath) return nil, errors.New("Failed to parse vmDiskPath") } - finder := getFinder(dc) - ds, err := finder.Datastore(ctx, datastorePathObj.Datastore) - if err != nil { - glog.Errorf("Failed while searching for datastore: %s. err: %+v", datastorePathObj.Datastore, err) - return nil, err - } - datastore := Datastore{ds, dc} - return &datastore, nil + + return dc.GetDatastoreByName(ctx, datastorePathObj.Datastore) } // GetDatastoreByName gets the Datastore object for the given datastore name @@ -109,6 +154,23 @@ func (dc *Datacenter) GetDatastoreByName(ctx context.Context, name string) (*Dat return &datastore, nil } +// GetResourcePool gets the resource pool for the given path +func (dc *Datacenter) GetResourcePool(ctx context.Context, computePath string) (*object.ResourcePool, error) { + finder := getFinder(dc) + var computeResource *object.ComputeResource + var err error + if computePath == "" { + computeResource, err = finder.DefaultComputeResource(ctx) + } else { + computeResource, err = finder.ComputeResource(ctx, computePath) + } + if err != nil { + glog.Errorf("Failed to get the ResourcePool for computePath '%s'. err: %+v", computePath, err) + return nil, err + } + return computeResource.ResourcePool(ctx) +} + // GetFolderByPath gets the Folder Object from the given folder path // folderPath should be the full path to folder func (dc *Datacenter) GetFolderByPath(ctx context.Context, folderPath string) (*Folder, error) { diff --git a/pkg/cloudprovider/providers/vsphere/vclib/datastore.go b/pkg/cloudprovider/providers/vsphere/vclib/datastore.go index 1901af18909..8fba424bbd9 100644 --- a/pkg/cloudprovider/providers/vsphere/vclib/datastore.go +++ b/pkg/cloudprovider/providers/vsphere/vclib/datastore.go @@ -17,6 +17,7 @@ limitations under the License. package vclib import ( + "fmt" "github.com/golang/glog" "github.com/vmware/govmomi/object" "github.com/vmware/govmomi/property" @@ -32,6 +33,16 @@ type Datastore struct { Datacenter *Datacenter } +// DatastoreInfo is a structure to store the Datastore and it's Info. +type DatastoreInfo struct { + *Datastore + Info *types.DatastoreInfo +} + +func (di DatastoreInfo) String() string { + return fmt.Sprintf("Datastore: %+v, datastore URL: %s", di.Datastore, di.Info.Url) +} + // CreateDirectory creates the directory at location specified by directoryPath. // If the intermediate level folders do not exist, and the parameter createParents is true, all the non-existent folders are created. // directoryPath must be in the format "[vsanDatastore] kubevols" diff --git a/pkg/cloudprovider/providers/vsphere/vclib/diskmanagers/vdm.go b/pkg/cloudprovider/providers/vsphere/vclib/diskmanagers/vdm.go index 8d860b9d548..3e6d9b2ecd9 100644 --- a/pkg/cloudprovider/providers/vsphere/vclib/diskmanagers/vdm.go +++ b/pkg/cloudprovider/providers/vsphere/vclib/diskmanagers/vdm.go @@ -70,13 +70,13 @@ func (diskManager virtualDiskManager) Create(ctx context.Context, datastore *vcl } // Delete implements Disk's Delete interface -func (diskManager virtualDiskManager) Delete(ctx context.Context, datastore *vclib.Datastore) error { +func (diskManager virtualDiskManager) Delete(ctx context.Context, datacenter *vclib.Datacenter) error { // Create a virtual disk manager - virtualDiskManager := object.NewVirtualDiskManager(datastore.Client()) - diskPath := vclib.RemoveClusterFromVDiskPath(diskManager.diskPath) + virtualDiskManager := object.NewVirtualDiskManager(datacenter.Client()) + diskPath := vclib.RemoveStorageClusterORFolderNameFromVDiskPath(diskManager.diskPath) requestTime := time.Now() // Delete virtual disk - task, err := virtualDiskManager.DeleteVirtualDisk(ctx, diskPath, datastore.Datacenter.Datacenter) + task, err := virtualDiskManager.DeleteVirtualDisk(ctx, diskPath, datacenter.Datacenter) if err != nil { glog.Errorf("Failed to delete virtual disk. err: %v", err) vclib.RecordvSphereMetric(vclib.APIDeleteVolume, requestTime, err) diff --git a/pkg/cloudprovider/providers/vsphere/vclib/diskmanagers/virtualdisk.go b/pkg/cloudprovider/providers/vsphere/vclib/diskmanagers/virtualdisk.go index fbe14b5fbbd..533f49ece30 100644 --- a/pkg/cloudprovider/providers/vsphere/vclib/diskmanagers/virtualdisk.go +++ b/pkg/cloudprovider/providers/vsphere/vclib/diskmanagers/virtualdisk.go @@ -40,7 +40,7 @@ const ( // VirtualDiskProvider defines interfaces for creating disk type VirtualDiskProvider interface { Create(ctx context.Context, datastore *vclib.Datastore) (string, error) - Delete(ctx context.Context, datastore *vclib.Datastore) error + Delete(ctx context.Context, datacenter *vclib.Datacenter) error } // getDiskManager returns vmDiskManager or vdmDiskManager based on given volumeoptions @@ -75,6 +75,6 @@ func (virtualDisk *VirtualDisk) Create(ctx context.Context, datastore *vclib.Dat } // Delete gets appropriate disk manager and calls respective delete method -func (virtualDisk *VirtualDisk) Delete(ctx context.Context, datastore *vclib.Datastore) error { - return getDiskManager(virtualDisk, VirtualDiskDeleteOperation).Delete(ctx, datastore) +func (virtualDisk *VirtualDisk) Delete(ctx context.Context, datacenter *vclib.Datacenter) error { + return getDiskManager(virtualDisk, VirtualDiskDeleteOperation).Delete(ctx, datacenter) } diff --git a/pkg/cloudprovider/providers/vsphere/vclib/diskmanagers/vmdm.go b/pkg/cloudprovider/providers/vsphere/vclib/diskmanagers/vmdm.go index 62c7018c5cf..6942dffb7f8 100644 --- a/pkg/cloudprovider/providers/vsphere/vclib/diskmanagers/vmdm.go +++ b/pkg/cloudprovider/providers/vsphere/vclib/diskmanagers/vmdm.go @@ -157,7 +157,7 @@ func (vmdisk vmDiskManager) Create(ctx context.Context, datastore *vclib.Datasto return vmdisk.diskPath, nil } -func (vmdisk vmDiskManager) Delete(ctx context.Context, datastore *vclib.Datastore) error { +func (vmdisk vmDiskManager) Delete(ctx context.Context, datacenter *vclib.Datacenter) error { return fmt.Errorf("vmDiskManager.Delete is not supported") } diff --git a/pkg/cloudprovider/providers/vsphere/vclib/pbm.go b/pkg/cloudprovider/providers/vsphere/vclib/pbm.go index df749fb8966..5ec83c62b36 100644 --- a/pkg/cloudprovider/providers/vsphere/vclib/pbm.go +++ b/pkg/cloudprovider/providers/vsphere/vclib/pbm.go @@ -85,7 +85,7 @@ func (pbmClient *PbmClient) IsDatastoreCompatible(ctx context.Context, storagePo // GetCompatibleDatastores filters and returns compatible list of datastores for given storage policy id // For Non Compatible Datastores, fault message with the Datastore Name is also returned -func (pbmClient *PbmClient) GetCompatibleDatastores(ctx context.Context, storagePolicyID string, datastores []*Datastore) ([]*Datastore, string, error) { +func (pbmClient *PbmClient) GetCompatibleDatastores(ctx context.Context, dc *Datacenter, storagePolicyID string, datastores []*DatastoreInfo) ([]*DatastoreInfo, string, error) { var ( dsMorNameMap = getDsMorNameMap(ctx, datastores) localizedMessagesForNotCompatibleDatastores = "" @@ -96,7 +96,7 @@ func (pbmClient *PbmClient) GetCompatibleDatastores(ctx context.Context, storage return nil, "", err } compatibleHubs := compatibilityResult.CompatibleDatastores() - var compatibleDatastoreList []*Datastore + var compatibleDatastoreList []*DatastoreInfo for _, hub := range compatibleHubs { compatibleDatastoreList = append(compatibleDatastoreList, getDatastoreFromPlacementHub(datastores, hub)) } @@ -121,7 +121,7 @@ func (pbmClient *PbmClient) GetCompatibleDatastores(ctx context.Context, storage } // GetPlacementCompatibilityResult gets placement compatibility result based on storage policy requirements. -func (pbmClient *PbmClient) GetPlacementCompatibilityResult(ctx context.Context, storagePolicyID string, datastore []*Datastore) (pbm.PlacementCompatibilityResult, error) { +func (pbmClient *PbmClient) GetPlacementCompatibilityResult(ctx context.Context, storagePolicyID string, datastore []*DatastoreInfo) (pbm.PlacementCompatibilityResult, error) { var hubs []pbmtypes.PbmPlacementHub for _, ds := range datastore { hubs = append(hubs, pbmtypes.PbmPlacementHub{ @@ -145,7 +145,7 @@ func (pbmClient *PbmClient) GetPlacementCompatibilityResult(ctx context.Context, } // getDataStoreForPlacementHub returns matching datastore associated with given pbmPlacementHub -func getDatastoreFromPlacementHub(datastore []*Datastore, pbmPlacementHub pbmtypes.PbmPlacementHub) *Datastore { +func getDatastoreFromPlacementHub(datastore []*DatastoreInfo, pbmPlacementHub pbmtypes.PbmPlacementHub) *DatastoreInfo { for _, ds := range datastore { if ds.Reference().Type == pbmPlacementHub.HubType && ds.Reference().Value == pbmPlacementHub.HubId { return ds @@ -155,7 +155,7 @@ func getDatastoreFromPlacementHub(datastore []*Datastore, pbmPlacementHub pbmtyp } // getDsMorNameMap returns map of ds Mor and Datastore Object Name -func getDsMorNameMap(ctx context.Context, datastores []*Datastore) map[string]string { +func getDsMorNameMap(ctx context.Context, datastores []*DatastoreInfo) map[string]string { dsMorNameMap := make(map[string]string) for _, ds := range datastores { dsObjectName, err := ds.ObjectName(ctx) diff --git a/pkg/cloudprovider/providers/vsphere/vclib/utils.go b/pkg/cloudprovider/providers/vsphere/vclib/utils.go index 791d05d33da..d449e5fe905 100644 --- a/pkg/cloudprovider/providers/vsphere/vclib/utils.go +++ b/pkg/cloudprovider/providers/vsphere/vclib/utils.go @@ -25,6 +25,8 @@ import ( "github.com/golang/glog" "github.com/vmware/govmomi/find" "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/soap" "github.com/vmware/govmomi/vim25/types" ) @@ -121,10 +123,10 @@ func getSCSIControllers(vmDevices object.VirtualDeviceList) []*types.VirtualCont return scsiControllers } -// RemoveClusterFromVDiskPath removes the cluster or folder path from the vDiskPath +// RemoveStorageClusterORFolderNameFromVDiskPath removes the cluster or folder path from the vDiskPath // for vDiskPath [DatastoreCluster/sharedVmfs-0] kubevols/e2e-vmdk-1234.vmdk, return value is [sharedVmfs-0] kubevols/e2e-vmdk-1234.vmdk // for vDiskPath [sharedVmfs-0] kubevols/e2e-vmdk-1234.vmdk, return value remains same [sharedVmfs-0] kubevols/e2e-vmdk-1234.vmdk -func RemoveClusterFromVDiskPath(vDiskPath string) string { +func RemoveStorageClusterORFolderNameFromVDiskPath(vDiskPath string) string { datastore := regexp.MustCompile("\\[(.*?)\\]").FindStringSubmatch(vDiskPath)[1] if filepath.Base(datastore) != datastore { vDiskPath = strings.Replace(vDiskPath, datastore, filepath.Base(datastore), 1) @@ -144,18 +146,6 @@ func GetPathFromVMDiskPath(vmDiskPath string) string { return datastorePathObj.Path } -// GetDatastoreFromVMDiskPath retrieves the path from VM Disk Path. -// Example: For vmDiskPath - [vsanDatastore] kubevols/volume.vmdk, the path is vsanDatastore -func GetDatastoreFromVMDiskPath(vmDiskPath string) string { - datastorePathObj := new(object.DatastorePath) - isSuccess := datastorePathObj.FromString(vmDiskPath) - if !isSuccess { - glog.Errorf("Failed to parse vmDiskPath: %s", vmDiskPath) - return "" - } - return datastorePathObj.Datastore -} - //GetDatastorePathObjFromVMDiskPath gets the datastorePathObj from VM disk path. func GetDatastorePathObjFromVMDiskPath(vmDiskPath string) (*object.DatastorePath, error) { datastorePathObj := new(object.DatastorePath) @@ -172,3 +162,40 @@ func IsValidUUID(uuid string) bool { r := regexp.MustCompile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$") return r.MatchString(uuid) } + +// IsManagedObjectNotFoundError returns true if error is of type ManagedObjectNotFound +func IsManagedObjectNotFoundError(err error) bool { + isManagedObjectNotFoundError := false + if soap.IsSoapFault(err) { + _, isManagedObjectNotFoundError = soap.ToSoapFault(err).VimFault().(types.ManagedObjectNotFound) + } + return isManagedObjectNotFoundError +} + +// VerifyVolumePathsForVM verifies if the volume paths (volPaths) are attached to VM. +func VerifyVolumePathsForVM(vmMo mo.VirtualMachine, volPaths []string, nodeName string, nodeVolumeMap map[string]map[string]bool) { + // Verify if the volume paths are present on the VM backing virtual disk devices + vmDevices := object.VirtualDeviceList(vmMo.Config.Hardware.Device) + VerifyVolumePathsForVMDevices(vmDevices, volPaths, nodeName, nodeVolumeMap) + +} + +// VerifyVolumePathsForVMDevices verifies if the volume paths (volPaths) are attached to VM. +func VerifyVolumePathsForVMDevices(vmDevices object.VirtualDeviceList, volPaths []string, nodeName string, nodeVolumeMap map[string]map[string]bool) { + volPathsMap := make(map[string]bool) + for _, volPath := range volPaths { + volPathsMap[volPath] = true + } + // Verify if the volume paths are present on the VM backing virtual disk devices + for _, device := range vmDevices { + if vmDevices.TypeName(device) == "VirtualDisk" { + virtualDevice := device.GetVirtualDevice() + if backing, ok := virtualDevice.Backing.(*types.VirtualDiskFlatVer2BackingInfo); ok { + if volPathsMap[backing.FileName] { + setNodeVolumeMap(nodeVolumeMap, backing.FileName, nodeName, true) + } + } + } + } + +} diff --git a/pkg/cloudprovider/providers/vsphere/vclib/virtualmachine.go b/pkg/cloudprovider/providers/vsphere/vclib/virtualmachine.go index 2796f6b6877..db45b8e1935 100644 --- a/pkg/cloudprovider/providers/vsphere/vclib/virtualmachine.go +++ b/pkg/cloudprovider/providers/vsphere/vclib/virtualmachine.go @@ -19,10 +19,12 @@ package vclib import ( "context" "fmt" + "strings" "time" "github.com/golang/glog" "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/property" "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/types" ) @@ -63,7 +65,7 @@ func (vm *VirtualMachine) AttachDisk(ctx context.Context, vmDiskPath string, vol return "", fmt.Errorf("Not a valid SCSI Controller Type. Valid options are %q", SCSIControllerTypeValidOptions()) } vmDiskPathCopy := vmDiskPath - vmDiskPath = RemoveClusterFromVDiskPath(vmDiskPath) + vmDiskPath = RemoveStorageClusterORFolderNameFromVDiskPath(vmDiskPath) attached, err := vm.IsDiskAttached(ctx, vmDiskPath) if err != nil { glog.Errorf("Error occurred while checking if disk is attached on VM: %q. vmDiskPath: %q, err: %+v", vm.InventoryPath, vmDiskPath, err) @@ -75,6 +77,20 @@ func (vm *VirtualMachine) AttachDisk(ctx context.Context, vmDiskPath string, vol return diskUUID, nil } + if volumeOptions.StoragePolicyName != "" { + pbmClient, err := NewPbmClient(ctx, vm.Client()) + if err != nil { + glog.Errorf("Error occurred while creating new pbmClient. err: %+v", err) + return "", err + } + + volumeOptions.StoragePolicyID, err = pbmClient.ProfileIDByName(ctx, volumeOptions.StoragePolicyName) + if err != nil { + glog.Errorf("Failed to get Profile ID by name: %s. err: %+v", volumeOptions.StoragePolicyName, err) + return "", err + } + } + dsObj, err := vm.Datacenter.GetDatastoreByPath(ctx, vmDiskPathCopy) if err != nil { glog.Errorf("Failed to get datastore from vmDiskPath: %q. err: %+v", vmDiskPath, err) @@ -139,7 +155,7 @@ func (vm *VirtualMachine) AttachDisk(ctx context.Context, vmDiskPath string, vol // DetachDisk detaches the disk specified by vmDiskPath func (vm *VirtualMachine) DetachDisk(ctx context.Context, vmDiskPath string) error { - vmDiskPath = RemoveClusterFromVDiskPath(vmDiskPath) + vmDiskPath = RemoveStorageClusterORFolderNameFromVDiskPath(vmDiskPath) device, err := vm.getVirtualDeviceByPath(ctx, vmDiskPath) if err != nil { glog.Errorf("Disk ID not found for VM: %q with diskPath: %q", vm.InventoryPath, vmDiskPath) @@ -186,7 +202,7 @@ func (vm *VirtualMachine) IsActive(ctx context.Context) (bool, error) { } // GetAllAccessibleDatastores gets the list of accessible Datastores for the given Virtual Machine -func (vm *VirtualMachine) GetAllAccessibleDatastores(ctx context.Context) ([]*Datastore, error) { +func (vm *VirtualMachine) GetAllAccessibleDatastores(ctx context.Context) ([]*DatastoreInfo, error) { host, err := vm.HostSystem(ctx) if err != nil { glog.Errorf("Failed to get host system for VM: %q. err: %+v", vm.InventoryPath, err) @@ -199,9 +215,28 @@ func (vm *VirtualMachine) GetAllAccessibleDatastores(ctx context.Context) ([]*Da glog.Errorf("Failed to retrieve datastores for host: %+v. err: %+v", host, err) return nil, err } - var dsObjList []*Datastore + var dsRefList []types.ManagedObjectReference for _, dsRef := range hostSystemMo.Datastore { - dsObjList = append(dsObjList, &Datastore{object.NewDatastore(vm.Client(), dsRef), vm.Datacenter}) + dsRefList = append(dsRefList, dsRef) + } + + var dsMoList []mo.Datastore + pc := property.DefaultCollector(vm.Client()) + properties := []string{DatastoreInfoProperty} + err = pc.Retrieve(ctx, dsRefList, properties, &dsMoList) + if err != nil { + glog.Errorf("Failed to get Datastore managed objects from datastore objects."+ + " dsObjList: %+v, properties: %+v, err: %v", dsRefList, properties, err) + return nil, err + } + glog.V(9).Infof("Result dsMoList: %+v", dsMoList) + var dsObjList []*DatastoreInfo + for _, dsMo := range dsMoList { + dsObjList = append(dsObjList, + &DatastoreInfo{ + &Datastore{object.NewDatastore(vm.Client(), dsMo.Reference()), + vm.Datacenter}, + dsMo.Info.GetDatastoreInfo()}) } return dsObjList, nil } @@ -328,12 +363,14 @@ func (vm *VirtualMachine) getVirtualDeviceByPath(ctx context.Context, diskPath s glog.Errorf("Failed to get the devices for VM: %q. err: %+v", vm.InventoryPath, err) return nil, err } + // filter vm devices to retrieve device for the given vmdk file identified by disk path for _, device := range vmDevices { if vmDevices.TypeName(device) == "VirtualDisk" { virtualDevice := device.GetVirtualDevice() if backing, ok := virtualDevice.Backing.(*types.VirtualDiskFlatVer2BackingInfo); ok { - if backing.FileName == diskPath { + if matchVirtualDiskAndVolPath(backing.FileName, diskPath) { + glog.V(LogLevel).Infof("Found VirtualDisk backing with filename %q for diskPath %q", backing.FileName, diskPath) return device, nil } } @@ -342,6 +379,13 @@ func (vm *VirtualMachine) getVirtualDeviceByPath(ctx context.Context, diskPath s return nil, nil } +func matchVirtualDiskAndVolPath(diskPath, volPath string) bool { + fileExt := ".vmdk" + diskPath = strings.TrimSuffix(diskPath, fileExt) + volPath = strings.TrimSuffix(volPath, fileExt) + return diskPath == volPath +} + // deleteController removes latest added SCSI controller from VM. func (vm *VirtualMachine) deleteController(ctx context.Context, controllerDevice types.BaseVirtualDevice, vmDevices object.VirtualDeviceList) error { controllerDeviceList := vmDevices.SelectByType(controllerDevice) diff --git a/pkg/cloudprovider/providers/vsphere/vsphere.go b/pkg/cloudprovider/providers/vsphere/vsphere.go index b8968357791..5707dd3d06c 100644 --- a/pkg/cloudprovider/providers/vsphere/vsphere.go +++ b/pkg/cloudprovider/providers/vsphere/vsphere.go @@ -21,6 +21,7 @@ import ( "fmt" "io" "net" + "os" "path" "path/filepath" "runtime" @@ -34,7 +35,10 @@ import ( "golang.org/x/net/context" "k8s.io/api/core/v1" k8stypes "k8s.io/apimachinery/pkg/types" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/informers" + "k8s.io/client-go/tools/cache" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" "k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/cloudprovider/providers/vsphere/vclib" "k8s.io/kubernetes/pkg/cloudprovider/providers/vsphere/vclib/diskmanagers" @@ -47,7 +51,6 @@ const ( VolDir = "kubevols" RoundTripperDefaultCount = 3 DummyVMPrefixName = "vsphere-k8s" - VSANDatastoreType = "vsan" MacOuiVC = "00:50:56" MacOuiEsx = "00:0c:29" CleanUpDummyVMRoutineInterval = 5 @@ -58,25 +61,49 @@ const ( var cleanUpRoutineInitialized = false var datastoreFolderIDMap = make(map[string]map[string]string) -var clientLock sync.Mutex var cleanUpRoutineInitLock sync.Mutex var cleanUpDummyVMLock sync.RWMutex // VSphere is an implementation of cloud provider Interface for VSphere. type VSphere struct { - conn *vclib.VSphereConnection - cfg *VSphereConfig - // InstanceID of the server where this VSphere object is instantiated. - localInstanceID string + cfg *VSphereConfig + hostName string + // Maps the VSphere IP address to VSphereInstance + vsphereInstanceMap map[string]*VSphereInstance + // Responsible for managing discovery of k8s node, their location etc. + nodeManager *NodeManager } -// VSphereConfig information that is used by vSphere Cloud Provider to connect to VC +// Represents a vSphere instance where one or more kubernetes nodes are running. +type VSphereInstance struct { + conn *vclib.VSphereConnection + cfg *VirtualCenterConfig +} + +// Structure that represents Virtual Center configuration +type VirtualCenterConfig struct { + // vCenter username. + User string `gcfg:"user"` + // vCenter password in clear text. + Password string `gcfg:"password"` + // vCenter port. + VCenterPort string `gcfg:"port"` + // Datacenter in which VMs are located. + Datacenters string `gcfg:"datacenters"` + // Soap round tripper count (retries = RoundTripper - 1) + RoundTripperCount uint `gcfg:"soap-roundtrip-count"` +} + +// Structure that represents the content of vsphere.conf file. +// Users specify the configuration of one or more Virtual Centers in vsphere.conf where +// the Kubernetes master and worker nodes are running. type VSphereConfig struct { Global struct { // vCenter username. User string `gcfg:"user"` // vCenter password in clear text. Password string `gcfg:"password"` + // Deprecated. Use VirtualCenter to specify multiple vCenter Servers. // vCenter IP. VCenterIP string `gcfg:"server"` // vCenter port. @@ -84,23 +111,32 @@ type VSphereConfig struct { // True if vCenter uses self-signed cert. InsecureFlag bool `gcfg:"insecure-flag"` // Datacenter in which VMs are located. + // Deprecated. Use "datacenters" instead. Datacenter string `gcfg:"datacenter"` + // Datacenter in which VMs are located. + Datacenters string `gcfg:"datacenters"` // Datastore in which vmdks are stored. - Datastore string `gcfg:"datastore"` - // WorkingDir is path where VMs can be found. + // Deprecated. See Workspace.DefaultDatastore + DefaultDatastore string `gcfg:"datastore"` + // WorkingDir is path where VMs can be found. Also used to create dummy VMs. + // Deprecated. WorkingDir string `gcfg:"working-dir"` // Soap round tripper count (retries = RoundTripper - 1) RoundTripperCount uint `gcfg:"soap-roundtrip-count"` + // Deprecated as the virtual machines will be automatically discovered. // VMUUID is the VM Instance UUID of virtual machine which can be retrieved from instanceUuid // property in VmConfigInfo, or also set as vc.uuid in VMX file. // If not set, will be fetched from the machine via sysfs (requires root) VMUUID string `gcfg:"vm-uuid"` + // Deprecated as virtual machine will be automatically discovered. // VMName is the VM name of virtual machine // Combining the WorkingDir and VMName can form a unique InstanceID. // When vm-name is set, no username/password is required on worker nodes. VMName string `gcfg:"vm-name"` } + VirtualCenter map[string]*VirtualCenterConfig + Network struct { // PublicNetwork is name of the network the VMs are joined to. PublicNetwork string `gcfg:"public-network"` @@ -110,12 +146,21 @@ type VSphereConfig struct { // SCSIControllerType defines SCSI controller to be used. SCSIControllerType string `dcfg:"scsicontrollertype"` } + + // Endpoint used to create volumes + Workspace struct { + VCenterIP string `gcfg:"server"` + Datacenter string `gcfg:"datacenter"` + Folder string `gcfg:"folder"` + DefaultDatastore string `gcfg:"default-datastore"` + ResourcePoolPath string `gcfg:"resourcepool-path"` + } } type Volumes interface { // AttachDisk attaches given disk to given node. Current node // is used when nodeName is empty string. - AttachDisk(vmDiskPath string, storagePolicyID string, nodeName k8stypes.NodeName) (diskUUID string, err error) + AttachDisk(vmDiskPath string, storagePolicyName string, nodeName k8stypes.NodeName) (diskUUID string, err error) // DetachDisk detaches given disk to given node. Current node // is used when nodeName is empty string. @@ -152,19 +197,169 @@ func readConfig(config io.Reader) (VSphereConfig, error) { func init() { vclib.RegisterMetrics() cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) { + // If vSphere.conf file is not present then it is worker node. + if config == nil { + return newWorkerNode() + } cfg, err := readConfig(config) if err != nil { return nil, err } - return newVSphere(cfg) + return newControllerNode(cfg) }) } // Initialize passes a Kubernetes clientBuilder interface to the cloud provider -func (vs *VSphere) Initialize(clientBuilder controller.ControllerClientBuilder) {} +func (vs *VSphere) Initialize(clientBuilder controller.ControllerClientBuilder) { + if vs.cfg == nil { + return + } -func newVSphere(cfg VSphereConfig) (*VSphere, error) { + // Only on controller node it is required to register listeners. + // Register callbacks for node updates + client := clientBuilder.ClientOrDie("vsphere-cloud-provider") + factory := informers.NewSharedInformerFactory(client, 5*time.Minute) + nodeInformer := factory.Core().V1().Nodes() + nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: vs.NodeAdded, + DeleteFunc: vs.NodeDeleted, + }) + go nodeInformer.Informer().Run(wait.NeverStop) + glog.V(4).Infof("vSphere cloud provider initialized") +} + +// Creates new worker node interface and returns +func newWorkerNode() (*VSphere, error) { var err error + vs := VSphere{} + vs.hostName, err = os.Hostname() + if err != nil { + glog.Errorf("Failed to get hostname. err: %+v", err) + return nil, err + } + + return &vs, nil +} + +func populateVsphereInstanceMap(cfg *VSphereConfig) (map[string]*VSphereInstance, error) { + vsphereInstanceMap := make(map[string]*VSphereInstance) + + // Check if the vsphere.conf is in old format. In this + // format the cfg.VirtualCenter will be nil or empty. + if cfg.VirtualCenter == nil || len(cfg.VirtualCenter) == 0 { + glog.V(4).Infof("Config is not per virtual center and is in old format.") + if cfg.Global.User == "" { + glog.Error("Global.User is empty!") + return nil, errors.New("Global.User is empty!") + } + if cfg.Global.Password == "" { + glog.Error("Global.Password is empty!") + return nil, errors.New("Global.Password is empty!") + } + if cfg.Global.WorkingDir == "" { + glog.Error("Global.WorkingDir is empty!") + return nil, errors.New("Global.WorkingDir is empty!") + } + if cfg.Global.VCenterIP == "" { + glog.Error("Global.VCenterIP is empty!") + return nil, errors.New("Global.VCenterIP is empty!") + } + if cfg.Global.Datacenter == "" { + glog.Error("Global.Datacenter is empty!") + return nil, errors.New("Global.Datacenter is empty!") + } + cfg.Workspace.VCenterIP = cfg.Global.VCenterIP + cfg.Workspace.Datacenter = cfg.Global.Datacenter + cfg.Workspace.Folder = cfg.Global.WorkingDir + cfg.Workspace.DefaultDatastore = cfg.Global.DefaultDatastore + + vcConfig := VirtualCenterConfig{ + User: cfg.Global.User, + Password: cfg.Global.Password, + VCenterPort: cfg.Global.VCenterPort, + Datacenters: cfg.Global.Datacenter, + RoundTripperCount: cfg.Global.RoundTripperCount, + } + + vSphereConn := vclib.VSphereConnection{ + Username: vcConfig.User, + Password: vcConfig.Password, + Hostname: cfg.Global.VCenterIP, + Insecure: cfg.Global.InsecureFlag, + RoundTripperCount: vcConfig.RoundTripperCount, + Port: vcConfig.VCenterPort, + } + vsphereIns := VSphereInstance{ + conn: &vSphereConn, + cfg: &vcConfig, + } + vsphereInstanceMap[cfg.Global.VCenterIP] = &vsphereIns + } else { + if cfg.Workspace.VCenterIP == "" || cfg.Workspace.Folder == "" || cfg.Workspace.Datacenter == "" { + msg := fmt.Sprintf("All fields in workspace are mandatory."+ + " vsphere.conf does not have the workspace specified correctly. cfg.Workspace: %+v", cfg.Workspace) + glog.Error(msg) + return nil, errors.New(msg) + } + for vcServer, vcConfig := range cfg.VirtualCenter { + glog.V(4).Infof("Initializing vc server %s", vcServer) + if vcServer == "" { + glog.Error("vsphere.conf does not have the VirtualCenter IP address specified") + return nil, errors.New("vsphere.conf does not have the VirtualCenter IP address specified") + } + if vcConfig.User == "" { + vcConfig.User = cfg.Global.User + } + if vcConfig.Password == "" { + vcConfig.Password = cfg.Global.Password + } + if vcConfig.User == "" { + msg := fmt.Sprintf("vcConfig.User is empty for vc %s!", vcServer) + glog.Error(msg) + return nil, errors.New(msg) + } + if vcConfig.Password == "" { + msg := fmt.Sprintf("vcConfig.Password is empty for vc %s!", vcServer) + glog.Error(msg) + return nil, errors.New(msg) + } + if vcConfig.VCenterPort == "" { + vcConfig.VCenterPort = cfg.Global.VCenterPort + } + if vcConfig.Datacenters == "" { + if cfg.Global.Datacenters != "" { + vcConfig.Datacenters = cfg.Global.Datacenters + } else { + // cfg.Global.Datacenter is deprecated, so giving it the last preference. + vcConfig.Datacenters = cfg.Global.Datacenter + } + } + if vcConfig.RoundTripperCount == 0 { + vcConfig.RoundTripperCount = cfg.Global.RoundTripperCount + } + + vSphereConn := vclib.VSphereConnection{ + Username: vcConfig.User, + Password: vcConfig.Password, + Hostname: vcServer, + Insecure: cfg.Global.InsecureFlag, + RoundTripperCount: vcConfig.RoundTripperCount, + Port: vcConfig.VCenterPort, + } + vsphereIns := VSphereInstance{ + conn: &vSphereConn, + cfg: vcConfig, + } + vsphereInstanceMap[vcServer] = &vsphereIns + } + } + return vsphereInstanceMap, nil +} + +// Creates new Contreoller node interface and returns +func newControllerNode(cfg VSphereConfig) (*VSphere, error) { + var err error + if cfg.Disk.SCSIControllerType == "" { cfg.Disk.SCSIControllerType = vclib.PVSCSIControllerType } else if !vclib.CheckControllerSupported(cfg.Disk.SCSIControllerType) { @@ -188,56 +383,37 @@ func newVSphere(cfg VSphereConfig) (*VSphere, error) { return nil, err } } - vSphereConn := vclib.VSphereConnection{ - Username: cfg.Global.User, - Password: cfg.Global.Password, - Hostname: cfg.Global.VCenterIP, - Insecure: cfg.Global.InsecureFlag, - RoundTripperCount: cfg.Global.RoundTripperCount, - Port: cfg.Global.VCenterPort, + vsphereInstanceMap, err := populateVsphereInstanceMap(&cfg) + if err != nil { + return nil, err } - var instanceID string - if cfg.Global.VMName == "" { - // if VMName is not set in the cloud config file, each nodes (including worker nodes) need credentials to obtain VMName from vCenter - glog.V(4).Infof("Cannot find VMName from cloud config file, start obtaining it from vCenter") - // Create context - ctx, cancel := context.WithCancel(context.TODO()) - defer cancel() - err = vSphereConn.Connect(ctx) - if err != nil { - glog.Errorf("Failed to connect to vSphere") - return nil, err - } - dc, err := vclib.GetDatacenter(ctx, &vSphereConn, cfg.Global.Datacenter) - if err != nil { - return nil, err - } - vm, err := dc.GetVMByUUID(ctx, cfg.Global.VMUUID) - if err != nil { - return nil, err - } - vmName, err := vm.ObjectName(ctx) - if err != nil { - return nil, err - } - instanceID = vmName - } else { - instanceID = cfg.Global.VMName - } vs := VSphere{ - conn: &vSphereConn, - cfg: &cfg, - localInstanceID: instanceID, + vsphereInstanceMap: vsphereInstanceMap, + nodeManager: &NodeManager{ + vsphereInstanceMap: vsphereInstanceMap, + nodeInfoMap: make(map[string]*NodeInfo), + registeredNodes: make(map[string]*v1.Node), + }, + cfg: &cfg, + } + + vs.hostName, err = os.Hostname() + if err != nil { + glog.Errorf("Failed to get hostname. err: %+v", err) + return nil, err } runtime.SetFinalizer(&vs, logout) return &vs, nil } func logout(vs *VSphere) { - if vs.conn.GoVmomiClient != nil { - vs.conn.GoVmomiClient.Logout(context.TODO()) + for _, vsphereIns := range vs.vsphereInstanceMap { + if vsphereIns.conn.GoVmomiClient != nil { + vsphereIns.conn.GoVmomiClient.Logout(context.TODO()) + } } + } // Instances returns an implementation of Instances for vSphere. @@ -284,43 +460,74 @@ func getLocalIP() ([]v1.NodeAddress, error) { return addrs, nil } +func (vs *VSphere) getVSphereInstance(nodeName k8stypes.NodeName) (*VSphereInstance, error) { + vsphereIns, err := vs.nodeManager.GetVSphereInstance(nodeName) + if err != nil { + glog.Errorf("Cannot find node %q in cache. Node not found!!!", nodeName) + return nil, err + } + return &vsphereIns, nil +} + +func (vs *VSphere) getVSphereInstanceForServer(vcServer string, ctx context.Context) (*VSphereInstance, error) { + vsphereIns, ok := vs.vsphereInstanceMap[vcServer] + if !ok { + glog.Errorf("cannot find vcServer %q in cache. VC not found!!!", vcServer) + return nil, errors.New(fmt.Sprintf("Cannot find node %q in vsphere configuration map", vcServer)) + } + // Ensure client is logged in and session is valid + err := vsphereIns.conn.Connect(ctx) + if err != nil { + glog.Errorf("failed connecting to vcServer %q with error %+v", vcServer, err) + return nil, err + } + + return vsphereIns, nil +} + // Get the VM Managed Object instance by from the node -func (vs *VSphere) getVMByName(ctx context.Context, nodeName k8stypes.NodeName) (*vclib.VirtualMachine, error) { - dc, err := vclib.GetDatacenter(ctx, vs.conn, vs.cfg.Global.Datacenter) +func (vs *VSphere) getVMFromNodeName(ctx context.Context, nodeName k8stypes.NodeName) (*vclib.VirtualMachine, error) { + nodeInfo, err := vs.nodeManager.GetNodeInfo(nodeName) if err != nil { return nil, err } - vmPath := vs.cfg.Global.WorkingDir + "/" + nodeNameToVMName(nodeName) - vm, err := dc.GetVMByPath(ctx, vmPath) - if err != nil { - return nil, err - } - return vm, nil + return nodeInfo.vm, nil } // NodeAddresses is an implementation of Instances.NodeAddresses. func (vs *VSphere) NodeAddresses(nodeName k8stypes.NodeName) ([]v1.NodeAddress, error) { // Get local IP addresses if node is local node - if vs.localInstanceID == nodeNameToVMName(nodeName) { + if vs.hostName == convertToString(nodeName) { return getLocalIP() } + + if vs.cfg == nil { + return nil, cloudprovider.InstanceNotFound + } + + // Below logic can be executed only on master as VC details are present. addrs := []v1.NodeAddress{} // Create context ctx, cancel := context.WithCancel(context.Background()) defer cancel() - // Ensure client is logged in and session is valid - err := vs.conn.Connect(ctx) + vsi, err := vs.getVSphereInstance(nodeName) if err != nil { return nil, err } - vm, err := vs.getVMByName(ctx, nodeName) + // Ensure client is logged in and session is valid + err = vsi.conn.Connect(ctx) if err != nil { - glog.Errorf("Failed to get VM object for node: %q. err: +%v", nodeNameToVMName(nodeName), err) + return nil, err + } + + vm, err := vs.getVMFromNodeName(ctx, nodeName) + if err != nil { + glog.Errorf("Failed to get VM object for node: %q. err: +%v", convertToString(nodeName), err) return nil, err } vmMoList, err := vm.Datacenter.GetVMMoList(ctx, []*vclib.VirtualMachine{vm}, []string{"guest.net"}) if err != nil { - glog.Errorf("Failed to get VM Managed object with property guest.net for node: %q. err: +%v", nodeNameToVMName(nodeName), err) + glog.Errorf("Failed to get VM Managed object with property guest.net for node: %q. err: +%v", convertToString(nodeName), err) return nil, err } // retrieve VM's ip(s) @@ -348,8 +555,7 @@ func (vs *VSphere) NodeAddresses(nodeName k8stypes.NodeName) ([]v1.NodeAddress, // This method will not be called from the node that is requesting this ID. i.e. metadata service // and other local methods cannot be used here func (vs *VSphere) NodeAddressesByProviderID(providerID string) ([]v1.NodeAddress, error) { - vmName := path.Base(providerID) - return vs.NodeAddresses(vmNameToNodeName(vmName)) + return vs.NodeAddresses(convertToK8sType(providerID)) } // AddSSHKeyToAllInstances add SSH key to all instances @@ -359,16 +565,14 @@ func (vs *VSphere) AddSSHKeyToAllInstances(user string, keyData []byte) error { // CurrentNodeName gives the current node name func (vs *VSphere) CurrentNodeName(hostname string) (k8stypes.NodeName, error) { - return vmNameToNodeName(vs.localInstanceID), nil + return convertToK8sType(vs.hostName), nil } -// nodeNameToVMName maps a NodeName to the vmware infrastructure name -func nodeNameToVMName(nodeName k8stypes.NodeName) string { +func convertToString(nodeName k8stypes.NodeName) string { return string(nodeName) } -// nodeNameToVMName maps a vmware infrastructure name to a NodeName -func vmNameToNodeName(vmName string) k8stypes.NodeName { +func convertToK8sType(vmName string) k8stypes.NodeName { return k8stypes.NodeName(vmName) } @@ -380,40 +584,74 @@ func (vs *VSphere) ExternalID(nodeName k8stypes.NodeName) (string, error) { // InstanceExistsByProviderID returns true if the instance with the given provider id still exists and is running. // If false is returned with no error, the instance will be immediately deleted by the cloud controller manager. func (vs *VSphere) InstanceExistsByProviderID(providerID string) (bool, error) { - return false, cloudprovider.NotImplemented + _, err := vs.InstanceID(convertToK8sType(providerID)) + if err == nil { + return true, nil + } + + return false, err } // InstanceID returns the cloud provider ID of the node with the specified Name. func (vs *VSphere) InstanceID(nodeName k8stypes.NodeName) (string, error) { - if vs.localInstanceID == nodeNameToVMName(nodeName) { - return vs.cfg.Global.WorkingDir + "/" + vs.localInstanceID, nil - } - // Create context - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - // Ensure client is logged in and session is valid - err := vs.conn.Connect(ctx) - if err != nil { - return "", err - } - vm, err := vs.getVMByName(ctx, nodeName) - if err != nil { - if vclib.IsNotFound(err) { - return "", cloudprovider.InstanceNotFound + + instanceIDInternal := func() (string, error) { + if vs.hostName == convertToString(nodeName) { + return vs.hostName, nil } - glog.Errorf("Failed to get VM object for node: %q. err: +%v", nodeNameToVMName(nodeName), err) - return "", err + + // Below logic can be performed only on master node where VC details are preset. + if vs.cfg == nil { + return "", fmt.Errorf("The current node can't detremine InstanceID for %q", convertToString(nodeName)) + } + + // Create context + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + vsi, err := vs.getVSphereInstance(nodeName) + if err != nil { + return "", err + } + // Ensure client is logged in and session is valid + err = vsi.conn.Connect(ctx) + if err != nil { + return "", err + } + vm, err := vs.getVMFromNodeName(ctx, nodeName) + if err != nil { + if err == vclib.ErrNoVMFound { + return "", cloudprovider.InstanceNotFound + } + glog.Errorf("Failed to get VM object for node: %q. err: +%v", convertToString(nodeName), err) + return "", err + } + isActive, err := vm.IsActive(ctx) + if err != nil { + glog.Errorf("Failed to check whether node %q is active. err: %+v.", convertToString(nodeName), err) + return "", err + } + if isActive { + return convertToString(nodeName), nil + } + glog.Warningf("The VM: %s is not in %s state", convertToString(nodeName), vclib.ActivePowerState) + return "", cloudprovider.InstanceNotFound } - isActive, err := vm.IsActive(ctx) + + instanceID, err := instanceIDInternal() if err != nil { - glog.Errorf("Failed to check whether node %q is active. err: %+v.", nodeNameToVMName(nodeName), err) - return "", err + var isManagedObjectNotFoundError bool + isManagedObjectNotFoundError, err = vs.retry(nodeName, err) + if isManagedObjectNotFoundError { + if err == nil { + glog.V(4).Infof("InstanceID: Found node %q", convertToString(nodeName)) + instanceID, err = instanceIDInternal() + } else if err == vclib.ErrNoVMFound { + return "", cloudprovider.InstanceNotFound + } + } } - if isActive { - return "/" + vm.InventoryPath, nil - } - glog.Warningf("The VM: %s is not in %s state", nodeNameToVMName(nodeName), vclib.ActivePowerState) - return "", cloudprovider.InstanceNotFound + + return instanceID, err } // InstanceTypeByProviderID returns the cloudprovider instance type of the node with the specified unique providerID @@ -452,78 +690,116 @@ func (vs *VSphere) Routes() (cloudprovider.Routes, bool) { return nil, false } -// ScrubDNS filters DNS settings for pods. -func (vs *VSphere) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) { - return nameservers, searches -} - // AttachDisk attaches given virtual disk volume to the compute running kubelet. -func (vs *VSphere) AttachDisk(vmDiskPath string, storagePolicyID string, nodeName k8stypes.NodeName) (diskUUID string, err error) { - attachDiskInternal := func(vmDiskPath string, storagePolicyID string, nodeName k8stypes.NodeName) (diskUUID string, err error) { +func (vs *VSphere) AttachDisk(vmDiskPath string, storagePolicyName string, nodeName k8stypes.NodeName) (diskUUID string, err error) { + attachDiskInternal := func(vmDiskPath string, storagePolicyName string, nodeName k8stypes.NodeName) (diskUUID string, err error) { if nodeName == "" { - nodeName = vmNameToNodeName(vs.localInstanceID) + nodeName = convertToK8sType(vs.hostName) } // Create context ctx, cancel := context.WithCancel(context.Background()) defer cancel() + vsi, err := vs.getVSphereInstance(nodeName) + if err != nil { + return "", err + } // Ensure client is logged in and session is valid - err = vs.conn.Connect(ctx) + err = vsi.conn.Connect(ctx) if err != nil { return "", err } - vm, err := vs.getVMByName(ctx, nodeName) + + vm, err := vs.getVMFromNodeName(ctx, nodeName) if err != nil { - glog.Errorf("Failed to get VM object for node: %q. err: +%v", nodeNameToVMName(nodeName), err) + glog.Errorf("Failed to get VM object for node: %q. err: +%v", convertToString(nodeName), err) return "", err } - diskUUID, err = vm.AttachDisk(ctx, vmDiskPath, &vclib.VolumeOptions{SCSIControllerType: vclib.PVSCSIControllerType, StoragePolicyID: storagePolicyID}) + + diskUUID, err = vm.AttachDisk(ctx, vmDiskPath, &vclib.VolumeOptions{SCSIControllerType: vclib.PVSCSIControllerType, StoragePolicyName: storagePolicyName}) if err != nil { - glog.Errorf("Failed to attach disk: %s for node: %s. err: +%v", vmDiskPath, nodeNameToVMName(nodeName), err) + glog.Errorf("Failed to attach disk: %s for node: %s. err: +%v", vmDiskPath, convertToString(nodeName), err) return "", err } return diskUUID, nil } requestTime := time.Now() - diskUUID, err = attachDiskInternal(vmDiskPath, storagePolicyID, nodeName) + diskUUID, err = attachDiskInternal(vmDiskPath, storagePolicyName, nodeName) + if err != nil { + var isManagedObjectNotFoundError bool + isManagedObjectNotFoundError, err = vs.retry(nodeName, err) + if isManagedObjectNotFoundError { + if err == nil { + glog.V(4).Infof("AttachDisk: Found node %q", convertToString(nodeName)) + diskUUID, err = attachDiskInternal(vmDiskPath, storagePolicyName, nodeName) + glog.V(4).Infof("AttachDisk: Retry: diskUUID %s, err +%v", convertToString(nodeName), diskUUID, err) + } + } + } + glog.V(4).Infof("AttachDisk executed for node %s and volume %s with diskUUID %s. Err: %s", convertToString(nodeName), vmDiskPath, diskUUID, err) vclib.RecordvSphereMetric(vclib.OperationAttachVolume, requestTime, err) return diskUUID, err } +func (vs *VSphere) retry(nodeName k8stypes.NodeName, err error) (bool, error) { + isManagedObjectNotFoundError := false + if err != nil { + if vclib.IsManagedObjectNotFoundError(err) { + isManagedObjectNotFoundError = true + glog.V(4).Infof("error %q ManagedObjectNotFound for node %q", err, convertToString(nodeName)) + err = vs.nodeManager.RediscoverNode(nodeName) + } + } + return isManagedObjectNotFoundError, err +} + // DetachDisk detaches given virtual disk volume from the compute running kubelet. func (vs *VSphere) DetachDisk(volPath string, nodeName k8stypes.NodeName) error { detachDiskInternal := func(volPath string, nodeName k8stypes.NodeName) error { if nodeName == "" { - nodeName = vmNameToNodeName(vs.localInstanceID) + nodeName = convertToK8sType(vs.hostName) } // Create context ctx, cancel := context.WithCancel(context.Background()) defer cancel() - // Ensure client is logged in and session is valid - err := vs.conn.Connect(ctx) + vsi, err := vs.getVSphereInstance(nodeName) if err != nil { return err } - vm, err := vs.getVMByName(ctx, nodeName) + // Ensure client is logged in and session is valid + err = vsi.conn.Connect(ctx) + if err != nil { + return err + } + vm, err := vs.getVMFromNodeName(ctx, nodeName) if err != nil { // If node doesn't exist, disk is already detached from node. - if vclib.IsNotFound(err) { - glog.Infof("Node %q does not exist, disk %s is already detached from node.", nodeNameToVMName(nodeName), volPath) + if err == vclib.ErrNoVMFound { + glog.Infof("Node %q does not exist, disk %s is already detached from node.", convertToString(nodeName), volPath) return nil } - glog.Errorf("Failed to get VM object for node: %q. err: +%v", nodeNameToVMName(nodeName), err) + glog.Errorf("Failed to get VM object for node: %q. err: +%v", convertToString(nodeName), err) return err } err = vm.DetachDisk(ctx, volPath) if err != nil { - glog.Errorf("Failed to detach disk: %s for node: %s. err: +%v", volPath, nodeNameToVMName(nodeName), err) + glog.Errorf("Failed to detach disk: %s for node: %s. err: +%v", volPath, convertToString(nodeName), err) return err } return nil } requestTime := time.Now() err := detachDiskInternal(volPath, nodeName) - vclib.RecordvSphereMetric(vclib.OperationDetachVolume, requestTime, nil) + if err != nil { + var isManagedObjectNotFoundError bool + isManagedObjectNotFoundError, err = vs.retry(nodeName, err) + if isManagedObjectNotFoundError { + if err == nil { + err = detachDiskInternal(volPath, nodeName) + } + } + } + vclib.RecordvSphereMetric(vclib.OperationDetachVolume, requestTime, err) return err } @@ -532,22 +808,26 @@ func (vs *VSphere) DiskIsAttached(volPath string, nodeName k8stypes.NodeName) (b diskIsAttachedInternal := func(volPath string, nodeName k8stypes.NodeName) (bool, error) { var vSphereInstance string if nodeName == "" { - vSphereInstance = vs.localInstanceID - nodeName = vmNameToNodeName(vSphereInstance) + vSphereInstance = vs.hostName + nodeName = convertToK8sType(vSphereInstance) } else { - vSphereInstance = nodeNameToVMName(nodeName) + vSphereInstance = convertToString(nodeName) } // Create context ctx, cancel := context.WithCancel(context.Background()) defer cancel() - // Ensure client is logged in and session is valid - err := vs.conn.Connect(ctx) + vsi, err := vs.getVSphereInstance(nodeName) if err != nil { return false, err } - vm, err := vs.getVMByName(ctx, nodeName) + // Ensure client is logged in and session is valid + err = vsi.conn.Connect(ctx) if err != nil { - if vclib.IsNotFound(err) { + return false, err + } + vm, err := vs.getVMFromNodeName(ctx, nodeName) + if err != nil { + if err == vclib.ErrNoVMFound { glog.Warningf("Node %q does not exist, vsphere CP will assume disk %v is not attached to it.", nodeName, volPath) // make the disk as detached and return false without error. return false, nil @@ -556,66 +836,157 @@ func (vs *VSphere) DiskIsAttached(volPath string, nodeName k8stypes.NodeName) (b return false, err } + volPath = vclib.RemoveStorageClusterORFolderNameFromVDiskPath(volPath) attached, err := vm.IsDiskAttached(ctx, volPath) if err != nil { glog.Errorf("DiskIsAttached failed to determine whether disk %q is still attached on node %q", volPath, vSphereInstance) } + glog.V(4).Infof("DiskIsAttached result: %q and error: %q, for volume: %q", attached, err, volPath) return attached, err } requestTime := time.Now() isAttached, err := diskIsAttachedInternal(volPath, nodeName) + if err != nil { + var isManagedObjectNotFoundError bool + isManagedObjectNotFoundError, err = vs.retry(nodeName, err) + if isManagedObjectNotFoundError { + if err == vclib.ErrNoVMFound { + isAttached, err = false, nil + } else if err == nil { + isAttached, err = diskIsAttachedInternal(volPath, nodeName) + } + } + } vclib.RecordvSphereMetric(vclib.OperationDiskIsAttached, requestTime, err) return isAttached, err } // DisksAreAttached returns if disks are attached to the VM using controllers supported by the plugin. +// 1. Converts volPaths into canonical form so that it can be compared with the VM device path. +// 2. Segregates nodes by vCenter and Datacenter they are present in. This reduces calls to VC. +// 3. Creates go routines per VC-DC to find whether disks are attached to the nodes. +// 4. If the some of the VMs are not found or migrated then they are added to a list. +// 5. After successful execution of goroutines, +// 5a. If there are any VMs which needs to be retried, they are rediscovered and the whole operation is initiated again for only rediscovered VMs. +// 5b. If VMs are removed from vSphere inventory they are ignored. func (vs *VSphere) DisksAreAttached(nodeVolumes map[k8stypes.NodeName][]string) (map[k8stypes.NodeName]map[string]bool, error) { disksAreAttachedInternal := func(nodeVolumes map[k8stypes.NodeName][]string) (map[k8stypes.NodeName]map[string]bool, error) { - attached := make(map[k8stypes.NodeName]map[string]bool) - if len(nodeVolumes) == 0 { - return attached, nil + + // disksAreAttach checks whether disks are attached to the nodes. + // Returns nodes that need to be retried if retry is true + // Segregates nodes per VC and DC + // Creates go routines per VC-DC to find whether disks are attached to the nodes. + disksAreAttach := func(ctx context.Context, nodeVolumes map[k8stypes.NodeName][]string, attached map[string]map[string]bool, retry bool) ([]k8stypes.NodeName, error) { + + var wg sync.WaitGroup + var localAttachedMaps []map[string]map[string]bool + var nodesToRetry []k8stypes.NodeName + var globalErr error + globalErr = nil + globalErrMutex := &sync.Mutex{} + nodesToRetryMutex := &sync.Mutex{} + + // Segregate nodes according to VC-DC + dcNodes := make(map[string][]k8stypes.NodeName) + for nodeName := range nodeVolumes { + nodeInfo, err := vs.nodeManager.GetNodeInfo(nodeName) + if err != nil { + glog.Errorf("Failed to get node info: %+v. err: %+v", nodeInfo.vm, err) + return nodesToRetry, err + } + VC_DC := nodeInfo.vcServer + nodeInfo.dataCenter.String() + dcNodes[VC_DC] = append(dcNodes[VC_DC], nodeName) + } + + for _, nodes := range dcNodes { + localAttachedMap := make(map[string]map[string]bool) + localAttachedMaps = append(localAttachedMaps, localAttachedMap) + // Start go routines per VC-DC to check disks are attached + go func() { + nodesToRetryLocal, err := vs.checkDiskAttached(ctx, nodes, nodeVolumes, localAttachedMap, retry) + if err != nil { + if !vclib.IsManagedObjectNotFoundError(err) { + globalErrMutex.Lock() + globalErr = err + globalErrMutex.Unlock() + glog.Errorf("Failed to check disk attached for nodes: %+v. err: %+v", nodes, err) + } + } + nodesToRetryMutex.Lock() + nodesToRetry = append(nodesToRetry, nodesToRetryLocal...) + nodesToRetryMutex.Unlock() + wg.Done() + }() + wg.Add(1) + } + wg.Wait() + if globalErr != nil { + return nodesToRetry, globalErr + } + for _, localAttachedMap := range localAttachedMaps { + for key, value := range localAttachedMap { + attached[key] = value + } + } + + return nodesToRetry, nil } + + glog.V(4).Info("Starting DisksAreAttached API for vSphere with nodeVolumes: %+v", nodeVolumes) // Create context ctx, cancel := context.WithCancel(context.Background()) defer cancel() - // Ensure client is logged in and session is valid - err := vs.conn.Connect(ctx) + + disksAttached := make(map[k8stypes.NodeName]map[string]bool) + if len(nodeVolumes) == 0 { + return disksAttached, nil + } + + // Convert VolPaths into canonical form so that it can be compared with the VM device path. + vmVolumes, err := vs.convertVolPathsToDevicePaths(ctx, nodeVolumes) if err != nil { + glog.Errorf("Failed to convert volPaths to devicePaths: %+v. err: %+v", nodeVolumes, err) return nil, err } - dc, err := vclib.GetDatacenter(ctx, vs.conn, vs.cfg.Global.Datacenter) + attached := make(map[string]map[string]bool) + nodesToRetry, err := disksAreAttach(ctx, vmVolumes, attached, false) if err != nil { return nil, err } - vmVolumes := make(map[string][]string) - for nodeName, volPaths := range nodeVolumes { - for i, volPath := range volPaths { - // Get the canonical volume path for volPath. - canonicalVolumePath, err := getcanonicalVolumePath(ctx, dc, volPath) + if len(nodesToRetry) != 0 { + // Rediscover nodes which are need to be retried + remainingNodesVolumes := make(map[k8stypes.NodeName][]string) + for _, nodeName := range nodesToRetry { + err = vs.nodeManager.RediscoverNode(nodeName) if err != nil { - glog.Errorf("Failed to get canonical vsphere volume path for volume: %s. err: %+v", volPath, err) + if err == vclib.ErrNoVMFound { + glog.V(4).Infof("node %s not found. err: %+v", nodeName, err) + continue + } + glog.Errorf("Failed to rediscover node %s. err: %+v", nodeName, err) return nil, err } - // Check if the volume path contains .vmdk extension. If not, add the extension and update the nodeVolumes Map - if len(canonicalVolumePath) > 0 && filepath.Ext(canonicalVolumePath) != ".vmdk" { - canonicalVolumePath += ".vmdk" - } - volPaths[i] = canonicalVolumePath + remainingNodesVolumes[nodeName] = nodeVolumes[nodeName] + } + + // If some remaining nodes are still registered + if len(remainingNodesVolumes) != 0 { + nodesToRetry, err = disksAreAttach(ctx, remainingNodesVolumes, attached, true) + if err != nil || len(nodesToRetry) != 0 { + glog.Errorf("Failed to retry disksAreAttach for nodes %+v. err: %+v", remainingNodesVolumes, err) + return nil, err + } + } + + for nodeName, volPaths := range attached { + disksAttached[convertToK8sType(nodeName)] = volPaths } - vmVolumes[nodeNameToVMName(nodeName)] = volPaths } - // Check if the disks are attached to their respective nodes - disksAttachedList, err := dc.CheckDisksAttached(ctx, vmVolumes) - if err != nil { - return nil, err - } - for vmName, volPaths := range disksAttachedList { - attached[vmNameToNodeName(vmName)] = volPaths - } - return attached, nil + glog.V(4).Infof("DisksAreAttach successfully executed. result: %+v", attached) + return disksAttached, nil } requestTime := time.Now() attached, err := disksAreAttachedInternal(nodeVolumes) @@ -631,9 +1002,9 @@ func (vs *VSphere) CreateVolume(volumeOptions *vclib.VolumeOptions) (canonicalVo glog.V(1).Infof("Starting to create a vSphere volume with volumeOptions: %+v", volumeOptions) createVolumeInternal := func(volumeOptions *vclib.VolumeOptions) (canonicalVolumePath string, err error) { var datastore string - // Default datastore is the datastore in the vSphere config file that is used to initialize vSphere cloud provider. + // If datastore not specified, then use default datastore if volumeOptions.Datastore == "" { - datastore = vs.cfg.Global.Datastore + datastore = vs.cfg.Workspace.DefaultDatastore } else { datastore = volumeOptions.Datastore } @@ -641,12 +1012,11 @@ func (vs *VSphere) CreateVolume(volumeOptions *vclib.VolumeOptions) (canonicalVo // Create context ctx, cancel := context.WithCancel(context.Background()) defer cancel() - // Ensure client is logged in and session is valid - err = vs.conn.Connect(ctx) + vsi, err := vs.getVSphereInstanceForServer(vs.cfg.Workspace.VCenterIP, ctx) if err != nil { return "", err } - dc, err := vclib.GetDatacenter(ctx, vs.conn, vs.cfg.Global.Datacenter) + dc, err := vclib.GetDatacenter(ctx, vsi.conn, vs.cfg.Workspace.Datacenter) if err != nil { return "", err } @@ -664,18 +1034,37 @@ func (vs *VSphere) CreateVolume(volumeOptions *vclib.VolumeOptions) (canonicalVo cleanUpRoutineInitialized = true } cleanUpRoutineInitLock.Unlock() - vmOptions, err = vs.setVMOptions(ctx, dc) + vmOptions, err = vs.setVMOptions(ctx, dc, vs.cfg.Workspace.ResourcePoolPath) if err != nil { glog.Errorf("Failed to set VM options requires to create a vsphere volume. err: %+v", err) return "", err } } if volumeOptions.StoragePolicyName != "" && volumeOptions.Datastore == "" { - datastore, err = getPbmCompatibleDatastore(ctx, dc.Client(), volumeOptions.StoragePolicyName, vmOptions.VMFolder) + datastore, err = getPbmCompatibleDatastore(ctx, dc, volumeOptions.StoragePolicyName, vs.nodeManager) if err != nil { glog.Errorf("Failed to get pbm compatible datastore with storagePolicy: %s. err: %+v", volumeOptions.StoragePolicyName, err) return "", err } + } else { + // Since no storage policy is specified but datastore is specified, check + // if the given datastore is a shared datastore across all node VMs. + sharedDsList, err := getSharedDatastoresInK8SCluster(ctx, dc, vs.nodeManager) + if err != nil { + glog.Errorf("Failed to get shared datastore: %+v", err) + return "", err + } + found := false + for _, sharedDs := range sharedDsList { + if datastore == sharedDs.Info.Name { + found = true + break + } + } + if !found { + msg := fmt.Sprintf("The specified datastore %s is not a shared datastore across node VMs", datastore) + return "", errors.New(msg) + } } ds, err := dc.GetDatastoreByName(ctx, datastore) if err != nil { @@ -714,7 +1103,7 @@ func (vs *VSphere) CreateVolume(volumeOptions *vclib.VolumeOptions) (canonicalVo requestTime := time.Now() canonicalVolumePath, err = createVolumeInternal(volumeOptions) vclib.RecordCreateVolumeMetric(volumeOptions, requestTime, err) - glog.V(1).Infof("The canonical volume path for the newly created vSphere volume is %q", canonicalVolumePath) + glog.V(4).Infof("The canonical volume path for the newly created vSphere volume is %q", canonicalVolumePath) return canonicalVolumePath, err } @@ -725,16 +1114,11 @@ func (vs *VSphere) DeleteVolume(vmDiskPath string) error { // Create context ctx, cancel := context.WithCancel(context.Background()) defer cancel() - // Ensure client is logged in and session is valid - err := vs.conn.Connect(ctx) + vsi, err := vs.getVSphereInstanceForServer(vs.cfg.Workspace.VCenterIP, ctx) if err != nil { return err } - dc, err := vclib.GetDatacenter(ctx, vs.conn, vs.cfg.Global.Datacenter) - if err != nil { - return err - } - ds, err := dc.GetDatastoreByName(ctx, vs.cfg.Global.Datastore) + dc, err := vclib.GetDatacenter(ctx, vsi.conn, vs.cfg.Workspace.Datacenter) if err != nil { return err } @@ -743,7 +1127,7 @@ func (vs *VSphere) DeleteVolume(vmDiskPath string) error { VolumeOptions: &vclib.VolumeOptions{}, VMOptions: &vclib.VMOptions{}, } - err = disk.Delete(ctx, ds) + err = disk.Delete(ctx, dc) if err != nil { glog.Errorf("Failed to delete vsphere volume with vmDiskPath: %s. err: %+v", vmDiskPath, err) } @@ -759,3 +1143,27 @@ func (vs *VSphere) DeleteVolume(vmDiskPath string) error { func (vs *VSphere) HasClusterID() bool { return true } + +// Notification handler when node is added into k8s cluster. +func (vs *VSphere) NodeAdded(obj interface{}) { + node, ok := obj.(*v1.Node) + if node == nil || !ok { + glog.Warningf("NodeAdded: unrecognized object %+v", obj) + return + } + + glog.V(4).Infof("Node added: %+v", node) + vs.nodeManager.RegisterNode(node) +} + +// Notification handler when node is removed from k8s cluster. +func (vs *VSphere) NodeDeleted(obj interface{}) { + node, ok := obj.(*v1.Node) + if node == nil || !ok { + glog.Warningf("NodeDeleted: unrecognized object %+v", obj) + return + } + + glog.V(4).Infof("Node deleted: %+v", node) + vs.nodeManager.UnRegisterNode(node) +} diff --git a/pkg/cloudprovider/providers/vsphere/vsphere_test.go b/pkg/cloudprovider/providers/vsphere/vsphere_test.go index b8b54e99aef..b49224124e9 100644 --- a/pkg/cloudprovider/providers/vsphere/vsphere_test.go +++ b/pkg/cloudprovider/providers/vsphere/vsphere_test.go @@ -39,7 +39,7 @@ func configFromEnv() (cfg VSphereConfig, ok bool) { cfg.Global.Password = os.Getenv("VSPHERE_PASSWORD") cfg.Global.Datacenter = os.Getenv("VSPHERE_DATACENTER") cfg.Network.PublicNetwork = os.Getenv("VSPHERE_PUBLIC_NETWORK") - cfg.Global.Datastore = os.Getenv("VSPHERE_DATASTORE") + cfg.Global.DefaultDatastore = os.Getenv("VSPHERE_DATASTORE") cfg.Disk.SCSIControllerType = os.Getenv("VSPHERE_SCSICONTROLLER_TYPE") cfg.Global.WorkingDir = os.Getenv("VSPHERE_WORKING_DIR") cfg.Global.VMName = os.Getenv("VSPHERE_VM_NAME") @@ -103,7 +103,7 @@ func TestNewVSphere(t *testing.T) { t.Skipf("No config found in environment") } - _, err := newVSphere(cfg) + _, err := newControllerNode(cfg) if err != nil { t.Fatalf("Failed to construct/authenticate vSphere: %s", err) } @@ -116,7 +116,7 @@ func TestVSphereLogin(t *testing.T) { } // Create vSphere configuration object - vs, err := newVSphere(cfg) + vs, err := newControllerNode(cfg) if err != nil { t.Fatalf("Failed to construct/authenticate vSphere: %s", err) } @@ -126,11 +126,16 @@ func TestVSphereLogin(t *testing.T) { defer cancel() // Create vSphere client - err = vs.conn.Connect(ctx) + var vcInstance *VSphereInstance + if vcInstance, ok = vs.vsphereInstanceMap[cfg.Global.VCenterIP]; !ok { + t.Fatalf("Couldn't get vSphere instance: %s", cfg.Global.VCenterIP) + } + + err = vcInstance.conn.Connect(ctx) if err != nil { t.Errorf("Failed to connect to vSphere: %s", err) } - defer vs.conn.GoVmomiClient.Logout(ctx) + defer vcInstance.conn.GoVmomiClient.Logout(ctx) } func TestZones(t *testing.T) { @@ -154,7 +159,7 @@ func TestInstances(t *testing.T) { t.Skipf("No config found in environment") } - vs, err := newVSphere(cfg) + vs, err := newControllerNode(cfg) if err != nil { t.Fatalf("Failed to construct/authenticate vSphere: %s", err) } @@ -213,7 +218,7 @@ func TestVolumes(t *testing.T) { t.Skipf("No config found in environment") } - vs, err := newVSphere(cfg) + vs, err := newControllerNode(cfg) if err != nil { t.Fatalf("Failed to construct/authenticate vSphere: %s", err) } diff --git a/pkg/cloudprovider/providers/vsphere/vsphere_util.go b/pkg/cloudprovider/providers/vsphere/vsphere_util.go index 3fbe2b621f1..0f4edb155ae 100644 --- a/pkg/cloudprovider/providers/vsphere/vsphere_util.go +++ b/pkg/cloudprovider/providers/vsphere/vsphere_util.go @@ -28,12 +28,15 @@ import ( "github.com/golang/glog" "github.com/vmware/govmomi" - "github.com/vmware/govmomi/object" "github.com/vmware/govmomi/vim25" - "github.com/vmware/govmomi/vim25/mo" "fmt" + "path/filepath" + + "github.com/vmware/govmomi/vim25/mo" + "k8s.io/api/core/v1" + k8stypes "k8s.io/apimachinery/pkg/types" "k8s.io/kubernetes/pkg/cloudprovider/providers/vsphere/vclib" "k8s.io/kubernetes/pkg/cloudprovider/providers/vsphere/vclib/diskmanagers" ) @@ -55,10 +58,28 @@ func GetVSphere() (*VSphere, error) { return nil, err } vSphereConn.GoVmomiClient = client + vsphereIns := &VSphereInstance{ + conn: vSphereConn, + cfg: &VirtualCenterConfig{ + User: cfg.Global.User, + Password: cfg.Global.Password, + VCenterPort: cfg.Global.VCenterPort, + Datacenters: cfg.Global.Datacenters, + RoundTripperCount: cfg.Global.RoundTripperCount, + }, + } + vsphereInsMap := make(map[string]*VSphereInstance) + vsphereInsMap[cfg.Global.VCenterIP] = vsphereIns + // TODO: Initialize nodeManager and set it in VSphere. vs := &VSphere{ - conn: vSphereConn, - cfg: cfg, - localInstanceID: "", + vsphereInstanceMap: vsphereInsMap, + hostName: "", + cfg: cfg, + nodeManager: &NodeManager{ + vsphereInstanceMap: vsphereInsMap, + nodeInfoMap: make(map[string]*NodeInfo), + registeredNodes: make(map[string]*v1.Node), + }, } runtime.SetFinalizer(vs, logout) return vs, nil @@ -70,14 +91,18 @@ func getVSphereConfig() *VSphereConfig { cfg.Global.VCenterPort = os.Getenv("VSPHERE_VCENTER_PORT") cfg.Global.User = os.Getenv("VSPHERE_USER") cfg.Global.Password = os.Getenv("VSPHERE_PASSWORD") - cfg.Global.Datacenter = os.Getenv("VSPHERE_DATACENTER") - cfg.Global.Datastore = os.Getenv("VSPHERE_DATASTORE") + cfg.Global.Datacenters = os.Getenv("VSPHERE_DATACENTER") + cfg.Global.DefaultDatastore = os.Getenv("VSPHERE_DATASTORE") cfg.Global.WorkingDir = os.Getenv("VSPHERE_WORKING_DIR") cfg.Global.VMName = os.Getenv("VSPHERE_VM_NAME") cfg.Global.InsecureFlag = false if strings.ToLower(os.Getenv("VSPHERE_INSECURE")) == "true" { cfg.Global.InsecureFlag = true } + cfg.Workspace.VCenterIP = cfg.Global.VCenterIP + cfg.Workspace.Datacenter = cfg.Global.Datacenters + cfg.Workspace.DefaultDatastore = cfg.Global.DefaultDatastore + cfg.Workspace.Folder = cfg.Global.WorkingDir return &cfg } @@ -127,49 +152,93 @@ func getvmUUID() (string, error) { return uuid, nil } -// Get all datastores accessible for the virtual machine object. -func getSharedDatastoresInK8SCluster(ctx context.Context, folder *vclib.Folder) ([]*vclib.Datastore, error) { - vmList, err := folder.GetVirtualMachines(ctx) +// Returns the accessible datastores for the given node VM. +func getAccessibleDatastores(ctx context.Context, nodeVmDetail *NodeDetails, nodeManager *NodeManager) ([]*vclib.DatastoreInfo, error) { + accessibleDatastores, err := nodeVmDetail.vm.GetAllAccessibleDatastores(ctx) if err != nil { - glog.Errorf("Failed to get virtual machines in the kubernetes cluster: %s, err: %+v", folder.InventoryPath, err) - return nil, err - } - if vmList == nil || len(vmList) == 0 { - glog.Errorf("No virtual machines found in the kubernetes cluster: %s", folder.InventoryPath) - return nil, fmt.Errorf("No virtual machines found in the kubernetes cluster: %s", folder.InventoryPath) - } - index := 0 - var sharedDatastores []*vclib.Datastore - for _, vm := range vmList { - vmName, err := vm.ObjectName(ctx) - if err != nil { - return nil, err - } - if !strings.HasPrefix(vmName, DummyVMPrefixName) { - accessibleDatastores, err := vm.GetAllAccessibleDatastores(ctx) - if err != nil { + // Check if the node VM is not found which indicates that the node info in the node manager is stale. + // If so, rediscover the node and retry. + if vclib.IsManagedObjectNotFoundError(err) { + glog.V(4).Infof("error %q ManagedObjectNotFound for node %q. Rediscovering...", err, nodeVmDetail.NodeName) + err = nodeManager.RediscoverNode(convertToK8sType(nodeVmDetail.NodeName)) + if err == nil { + glog.V(4).Infof("Discovered node %s successfully", nodeVmDetail.NodeName) + nodeInfo, err := nodeManager.GetNodeInfo(convertToK8sType(nodeVmDetail.NodeName)) + if err != nil { + glog.V(4).Infof("error %q getting node info for node %+v", err, nodeVmDetail) + return nil, err + } + + accessibleDatastores, err = nodeInfo.vm.GetAllAccessibleDatastores(ctx) + if err != nil { + glog.V(4).Infof("error %q getting accessible datastores for node %+v", err, nodeVmDetail) + return nil, err + } + } else { + glog.V(4).Infof("error %q rediscovering node %+v", err, nodeVmDetail) return nil, err } - if index == 0 { - sharedDatastores = accessibleDatastores - } else { - sharedDatastores = intersect(sharedDatastores, accessibleDatastores) - if len(sharedDatastores) == 0 { - return nil, fmt.Errorf("No shared datastores found in the Kubernetes cluster: %s", folder.InventoryPath) - } - } - index++ + } else { + glog.V(4).Infof("error %q getting accessible datastores for node %+v", err, nodeVmDetail) + return nil, err } } + return accessibleDatastores, nil +} + +// Get all datastores accessible for the virtual machine object. +func getSharedDatastoresInK8SCluster(ctx context.Context, dc *vclib.Datacenter, nodeManager *NodeManager) ([]*vclib.DatastoreInfo, error) { + nodeVmDetails, err := nodeManager.GetNodeDetails() + if err != nil { + glog.Errorf("Error while obtaining Kubernetes node nodeVmDetail details. error : %+v", err) + return nil, err + } + + if len(nodeVmDetails) == 0 { + msg := fmt.Sprintf("Kubernetes node nodeVmDetail details is empty. nodeVmDetails : %+v", nodeVmDetails) + glog.Error(msg) + return nil, fmt.Errorf(msg) + } + var sharedDatastores []*vclib.DatastoreInfo + for _, nodeVmDetail := range nodeVmDetails { + glog.V(9).Infof("Getting accessible datastores for node %s", nodeVmDetail.NodeName) + accessibleDatastores, err := getAccessibleDatastores(ctx, &nodeVmDetail, nodeManager) + if err != nil { + if err == vclib.ErrNoVMFound { + glog.V(9).Infof("Got NoVMFound error for node %s", nodeVmDetail.NodeName) + continue + } + return nil, err + } + + if len(sharedDatastores) == 0 { + sharedDatastores = accessibleDatastores + } else { + sharedDatastores = intersect(sharedDatastores, accessibleDatastores) + if len(sharedDatastores) == 0 { + return nil, fmt.Errorf("No shared datastores found in the Kubernetes cluster for nodeVmDetails: %+v", nodeVmDetails) + } + } + } + glog.V(9).Infof("sharedDatastores : %+v", sharedDatastores) + sharedDatastores, err = getDatastoresForEndpointVC(ctx, dc, sharedDatastores) + if err != nil { + glog.Errorf("Failed to get shared datastores from endpoint VC. err: %+v", err) + return nil, err + } + glog.V(9).Infof("sharedDatastores at endpoint VC: %+v", sharedDatastores) return sharedDatastores, nil } -func intersect(list1 []*vclib.Datastore, list2 []*vclib.Datastore) []*vclib.Datastore { - var sharedDs []*vclib.Datastore +func intersect(list1 []*vclib.DatastoreInfo, list2 []*vclib.DatastoreInfo) []*vclib.DatastoreInfo { + glog.V(9).Infof("list1: %+v", list1) + glog.V(9).Infof("list2: %+v", list2) + var sharedDs []*vclib.DatastoreInfo for _, val1 := range list1 { // Check if val1 is found in list2 for _, val2 := range list2 { - if val1.Reference().Value == val2.Reference().Value { + // Intersection is performed based on the datastoreUrl as this uniquely identifies the datastore. + if val1.Info.Url == val2.Info.Url { sharedDs = append(sharedDs, val1) break } @@ -178,46 +247,42 @@ func intersect(list1 []*vclib.Datastore, list2 []*vclib.Datastore) []*vclib.Data return sharedDs } -// Get the datastores accessible for the virtual machine object. -func getAllAccessibleDatastores(ctx context.Context, client *vim25.Client, vmMo mo.VirtualMachine) ([]string, error) { - host := vmMo.Summary.Runtime.Host - if host == nil { - return nil, errors.New("VM doesn't have a HostSystem") - } - var hostSystemMo mo.HostSystem - s := object.NewSearchIndex(client) - err := s.Properties(ctx, host.Reference(), []string{DatastoreProperty}, &hostSystemMo) - if err != nil { - return nil, err - } - var dsRefValues []string - for _, dsRef := range hostSystemMo.Datastore { - dsRefValues = append(dsRefValues, dsRef.Value) - } - return dsRefValues, nil -} - // getMostFreeDatastore gets the best fit compatible datastore by free space. -func getMostFreeDatastoreName(ctx context.Context, client *vim25.Client, dsObjList []*vclib.Datastore) (string, error) { - dsMoList, err := dsObjList[0].Datacenter.GetDatastoreMoList(ctx, dsObjList, []string{DatastoreInfoProperty}) - if err != nil { - return "", err - } +func getMostFreeDatastoreName(ctx context.Context, client *vim25.Client, dsInfoList []*vclib.DatastoreInfo) (string, error) { var curMax int64 curMax = -1 var index int - for i, dsMo := range dsMoList { - dsFreeSpace := dsMo.Info.GetDatastoreInfo().FreeSpace + for i, dsInfo := range dsInfoList { + dsFreeSpace := dsInfo.Info.GetDatastoreInfo().FreeSpace if dsFreeSpace > curMax { curMax = dsFreeSpace index = i } } - return dsMoList[index].Info.GetDatastoreInfo().Name, nil + return dsInfoList[index].Info.GetDatastoreInfo().Name, nil } -func getPbmCompatibleDatastore(ctx context.Context, client *vim25.Client, storagePolicyName string, folder *vclib.Folder) (string, error) { - pbmClient, err := vclib.NewPbmClient(ctx, client) +// Returns the datastores in the given datacenter by performing lookup based on datastore URL. +func getDatastoresForEndpointVC(ctx context.Context, dc *vclib.Datacenter, sharedDsInfos []*vclib.DatastoreInfo) ([]*vclib.DatastoreInfo, error) { + var datastores []*vclib.DatastoreInfo + allDsInfoMap, err := dc.GetAllDatastores(ctx) + if err != nil { + return nil, err + } + for _, sharedDsInfo := range sharedDsInfos { + dsInfo, ok := allDsInfoMap[sharedDsInfo.Info.Url] + if ok { + datastores = append(datastores, dsInfo) + } else { + glog.V(4).Infof("Warning: Shared datastore with URL %s does not exist in endpoint VC", sharedDsInfo.Info.Url) + } + } + glog.V(9).Infof("Datastore from endpoint VC: %+v", datastores) + return datastores, nil +} + +func getPbmCompatibleDatastore(ctx context.Context, dc *vclib.Datacenter, storagePolicyName string, nodeManager *NodeManager) (string, error) { + pbmClient, err := vclib.NewPbmClient(ctx, dc.Client()) if err != nil { return "", err } @@ -226,35 +291,40 @@ func getPbmCompatibleDatastore(ctx context.Context, client *vim25.Client, storag glog.Errorf("Failed to get Profile ID by name: %s. err: %+v", storagePolicyName, err) return "", err } - sharedDsList, err := getSharedDatastoresInK8SCluster(ctx, folder) + sharedDs, err := getSharedDatastoresInK8SCluster(ctx, dc, nodeManager) if err != nil { - glog.Errorf("Failed to get shared datastores from kubernetes cluster: %s. err: %+v", folder.InventoryPath, err) + glog.Errorf("Failed to get shared datastores. err: %+v", err) return "", err } - compatibleDatastores, _, err := pbmClient.GetCompatibleDatastores(ctx, storagePolicyID, sharedDsList) + if len(sharedDs) == 0 { + msg := "No shared datastores found in the endpoint virtual center" + glog.Errorf(msg) + return "", errors.New(msg) + } + compatibleDatastores, _, err := pbmClient.GetCompatibleDatastores(ctx, dc, storagePolicyID, sharedDs) if err != nil { - glog.Errorf("Failed to get compatible datastores from datastores : %+v with storagePolicy: %s. err: %+v", sharedDsList, storagePolicyID, err) + glog.Errorf("Failed to get compatible datastores from datastores : %+v with storagePolicy: %s. err: %+v", + sharedDs, storagePolicyID, err) return "", err } - datastore, err := getMostFreeDatastoreName(ctx, client, compatibleDatastores) + glog.V(9).Infof("compatibleDatastores : %+v", compatibleDatastores) + datastore, err := getMostFreeDatastoreName(ctx, dc.Client(), compatibleDatastores) if err != nil { glog.Errorf("Failed to get most free datastore from compatible datastores: %+v. err: %+v", compatibleDatastores, err) return "", err } + glog.V(4).Infof("Most free datastore : %+s", datastore) return datastore, err } -func (vs *VSphere) setVMOptions(ctx context.Context, dc *vclib.Datacenter) (*vclib.VMOptions, error) { +func (vs *VSphere) setVMOptions(ctx context.Context, dc *vclib.Datacenter, resourcePoolPath string) (*vclib.VMOptions, error) { var vmOptions vclib.VMOptions - vm, err := dc.GetVMByPath(ctx, vs.cfg.Global.WorkingDir+"/"+vs.localInstanceID) + resourcePool, err := dc.GetResourcePool(ctx, resourcePoolPath) if err != nil { return nil, err } - resourcePool, err := vm.GetResourcePool(ctx) - if err != nil { - return nil, err - } - folder, err := dc.GetFolderByPath(ctx, vs.cfg.Global.WorkingDir) + glog.V(9).Infof("Resource pool path %s, resourcePool %+v", resourcePoolPath, resourcePool) + folder, err := dc.GetFolderByPath(ctx, vs.cfg.Workspace.Folder) if err != nil { return nil, err } @@ -270,28 +340,27 @@ func (vs *VSphere) cleanUpDummyVMs(dummyVMPrefix string) { defer cancel() for { time.Sleep(CleanUpDummyVMRoutineInterval * time.Minute) - // Ensure client is logged in and session is valid - err := vs.conn.Connect(ctx) + vsi, err := vs.getVSphereInstanceForServer(vs.cfg.Workspace.VCenterIP, ctx) if err != nil { - glog.V(4).Infof("Failed to connect to VC with err: %+v. Retrying again...", err) + glog.V(4).Infof("Failed to get VSphere instance with err: %+v. Retrying again...", err) continue } - dc, err := vclib.GetDatacenter(ctx, vs.conn, vs.cfg.Global.Datacenter) + dc, err := vclib.GetDatacenter(ctx, vsi.conn, vs.cfg.Workspace.Datacenter) if err != nil { - glog.V(4).Infof("Failed to get the datacenter: %s from VC. err: %+v", vs.cfg.Global.Datacenter, err) + glog.V(4).Infof("Failed to get the datacenter: %s from VC. err: %+v", vs.cfg.Workspace.Datacenter, err) continue } // Get the folder reference for global working directory where the dummy VM needs to be created. - vmFolder, err := dc.GetFolderByPath(ctx, vs.cfg.Global.WorkingDir) + vmFolder, err := dc.GetFolderByPath(ctx, vs.cfg.Workspace.Folder) if err != nil { - glog.V(4).Infof("Unable to get the kubernetes folder: %q reference. err: %+v", vs.cfg.Global.WorkingDir, err) + glog.V(4).Infof("Unable to get the kubernetes folder: %q reference. err: %+v", vs.cfg.Workspace.Folder, err) continue } // A write lock is acquired to make sure the cleanUp routine doesn't delete any VM's created by ongoing PVC requests. defer cleanUpDummyVMLock.Lock() err = diskmanagers.CleanUpDummyVMs(ctx, vmFolder, dc) if err != nil { - glog.V(4).Infof("Unable to clean up dummy VM's in the kubernetes cluster: %q. err: %+v", vs.cfg.Global.WorkingDir, err) + glog.V(4).Infof("Unable to clean up dummy VM's in the kubernetes cluster: %q. err: %+v", vs.cfg.Workspace.Folder, err) } } } @@ -353,3 +422,118 @@ func setdatastoreFolderIDMap( } folderNameIDMap[folderName] = folderID } + +func convertVolPathToDevicePath(ctx context.Context, dc *vclib.Datacenter, volPath string) (string, error) { + volPath = vclib.RemoveStorageClusterORFolderNameFromVDiskPath(volPath) + // Get the canonical volume path for volPath. + canonicalVolumePath, err := getcanonicalVolumePath(ctx, dc, volPath) + if err != nil { + glog.Errorf("Failed to get canonical vsphere volume path for volume: %s. err: %+v", volPath, err) + return "", err + } + // Check if the volume path contains .vmdk extension. If not, add the extension and update the nodeVolumes Map + if len(canonicalVolumePath) > 0 && filepath.Ext(canonicalVolumePath) != ".vmdk" { + canonicalVolumePath += ".vmdk" + } + return canonicalVolumePath, nil +} + +// convertVolPathsToDevicePaths removes cluster or folder path from volPaths and convert to canonicalPath +func (vs *VSphere) convertVolPathsToDevicePaths(ctx context.Context, nodeVolumes map[k8stypes.NodeName][]string) (map[k8stypes.NodeName][]string, error) { + vmVolumes := make(map[k8stypes.NodeName][]string) + for nodeName, volPaths := range nodeVolumes { + nodeInfo, err := vs.nodeManager.GetNodeInfo(nodeName) + if err != nil { + return nil, err + } + + _, err = vs.getVSphereInstanceForServer(nodeInfo.vcServer, ctx) + if err != nil { + return nil, err + } + + for i, volPath := range volPaths { + deviceVolPath, err := convertVolPathToDevicePath(ctx, nodeInfo.dataCenter, volPath) + if err != nil { + glog.Errorf("Failed to convert vsphere volume path %s to device path for volume %s. err: %+v", volPath, deviceVolPath, err) + return nil, err + } + volPaths[i] = deviceVolPath + } + vmVolumes[nodeName] = volPaths + } + return vmVolumes, nil +} + +// checkDiskAttached verifies volumes are attached to the VMs which are in same vCenter and Datacenter +// Returns nodes if exist any for which VM is not found in that vCenter and Datacenter +func (vs *VSphere) checkDiskAttached(ctx context.Context, nodes []k8stypes.NodeName, nodeVolumes map[k8stypes.NodeName][]string, attached map[string]map[string]bool, retry bool) ([]k8stypes.NodeName, error) { + var nodesToRetry []k8stypes.NodeName + var vmList []*vclib.VirtualMachine + var nodeInfo NodeInfo + var err error + + for _, nodeName := range nodes { + nodeInfo, err = vs.nodeManager.GetNodeInfo(nodeName) + if err != nil { + return nodesToRetry, err + } + vmList = append(vmList, nodeInfo.vm) + } + + // Making sure session is valid + _, err = vs.getVSphereInstanceForServer(nodeInfo.vcServer, ctx) + if err != nil { + return nodesToRetry, err + } + + // If any of the nodes are not present property collector query will fail for entire operation + vmMoList, err := nodeInfo.dataCenter.GetVMMoList(ctx, vmList, []string{"config.hardware.device", "name", "config.uuid"}) + if err != nil { + if vclib.IsManagedObjectNotFoundError(err) && !retry { + glog.V(4).Infof("checkDiskAttached: ManagedObjectNotFound for property collector query for nodes: %+v vms: %+v", nodes, vmList) + // Property Collector Query failed + // VerifyVolumePaths per VM + for _, nodeName := range nodes { + nodeInfo, err := vs.nodeManager.GetNodeInfo(nodeName) + if err != nil { + return nodesToRetry, err + } + devices, err := nodeInfo.vm.VirtualMachine.Device(ctx) + if err != nil { + if vclib.IsManagedObjectNotFoundError(err) { + glog.V(4).Infof("checkDiskAttached: ManagedObjectNotFound for Kubernetes node: %s with vSphere Virtual Machine reference: %v", nodeName, nodeInfo.vm) + nodesToRetry = append(nodesToRetry, nodeName) + continue + } + return nodesToRetry, err + } + glog.V(4).Infof("Verifying Volume Paths by devices for node %s and VM %s", nodeName, nodeInfo.vm) + vclib.VerifyVolumePathsForVMDevices(devices, nodeVolumes[nodeName], convertToString(nodeName), attached) + } + } + return nodesToRetry, err + } + + vmMoMap := make(map[string]mo.VirtualMachine) + for _, vmMo := range vmMoList { + if vmMo.Config == nil { + glog.Errorf("Config is not available for VM: %q", vmMo.Name) + continue + } + glog.V(9).Infof("vmMoMap vmname: %q vmuuid: %s", vmMo.Name, strings.ToLower(vmMo.Config.Uuid)) + vmMoMap[strings.ToLower(vmMo.Config.Uuid)] = vmMo + } + + glog.V(9).Infof("vmMoMap: +%v", vmMoMap) + + for _, nodeName := range nodes { + node, err := vs.nodeManager.GetNode(nodeName) + if err != nil { + return nodesToRetry, err + } + glog.V(9).Infof("Verifying volume for nodeName: %q with nodeuuid: %s", nodeName, node.Status.NodeInfo.SystemUUID, vmMoMap) + vclib.VerifyVolumePathsForVM(vmMoMap[strings.ToLower(node.Status.NodeInfo.SystemUUID)], nodeVolumes[nodeName], convertToString(nodeName), attached) + } + return nodesToRetry, nil +} diff --git a/pkg/controller/BUILD b/pkg/controller/BUILD index 7e31009c93a..7c2db32b5d4 100644 --- a/pkg/controller/BUILD +++ b/pkg/controller/BUILD @@ -12,12 +12,12 @@ go_test( "controller_ref_manager_test.go", "controller_utils_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/testapi:go_default_library", + "//pkg/apis/core/install:go_default_library", "//pkg/controller/testutil:go_default_library", "//pkg/securitycontext:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", @@ -52,10 +52,11 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/controller", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/v1/pod:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/install:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//pkg/serviceaccount:go_default_library", "//pkg/util/hash:go_default_library", "//pkg/util/taints:go_default_library", @@ -82,13 +83,11 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", "//vendor/k8s.io/apiserver/pkg/authentication/serviceaccount:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library", "//vendor/k8s.io/client-go/kubernetes/typed/authentication/v1:go_default_library", "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", "//vendor/k8s.io/client-go/tools/record:go_default_library", - "//vendor/k8s.io/client-go/tools/reference:go_default_library", "//vendor/k8s.io/client-go/util/integer:go_default_library", "//vendor/k8s.io/client-go/util/retry:go_default_library", ], @@ -108,6 +107,7 @@ filegroup( "//pkg/controller/bootstrap:all-srcs", "//pkg/controller/certificates:all-srcs", "//pkg/controller/cloud:all-srcs", + "//pkg/controller/clusterroleaggregation:all-srcs", "//pkg/controller/cronjob:all-srcs", "//pkg/controller/daemon:all-srcs", "//pkg/controller/deployment:all-srcs", @@ -117,7 +117,8 @@ filegroup( "//pkg/controller/history:all-srcs", "//pkg/controller/job:all-srcs", "//pkg/controller/namespace:all-srcs", - "//pkg/controller/node:all-srcs", + "//pkg/controller/nodeipam:all-srcs", + "//pkg/controller/nodelifecycle:all-srcs", "//pkg/controller/podautoscaler:all-srcs", "//pkg/controller/podgc:all-srcs", "//pkg/controller/replicaset:all-srcs", @@ -129,10 +130,12 @@ filegroup( "//pkg/controller/statefulset:all-srcs", "//pkg/controller/testutil:all-srcs", "//pkg/controller/ttl:all-srcs", + "//pkg/controller/util/node:all-srcs", "//pkg/controller/volume/attachdetach:all-srcs", "//pkg/controller/volume/events:all-srcs", "//pkg/controller/volume/expand:all-srcs", "//pkg/controller/volume/persistentvolume:all-srcs", + "//pkg/controller/volume/pvcprotection:all-srcs", ], tags = ["automanaged"], ) diff --git a/pkg/controller/bootstrap/BUILD b/pkg/controller/bootstrap/BUILD index fe86ddc5557..6776adbe55c 100644 --- a/pkg/controller/bootstrap/BUILD +++ b/pkg/controller/bootstrap/BUILD @@ -15,11 +15,11 @@ go_test( "tokencleaner_test.go", "util_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/bootstrap", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", "//pkg/bootstrap/api:go_default_library", "//vendor/github.com/davecgh/go-spew/spew:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", @@ -42,11 +42,11 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/controller/bootstrap", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/bootstrap/api:go_default_library", "//pkg/util/metrics:go_default_library", "//vendor/github.com/golang/glog:go_default_library", - "//vendor/github.com/square/go-jose:go_default_library", + "//vendor/gopkg.in/square/go-jose.v2:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/controller/bootstrap/bootstrapsigner.go b/pkg/controller/bootstrap/bootstrapsigner.go index 07a987658be..5bb53a44837 100644 --- a/pkg/controller/bootstrap/bootstrapsigner.go +++ b/pkg/controller/bootstrap/bootstrapsigner.go @@ -33,7 +33,7 @@ import ( clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api" "k8s.io/kubernetes/pkg/util/metrics" ) @@ -92,26 +92,28 @@ type BootstrapSigner struct { // NewBootstrapSigner returns a new *BootstrapSigner. // // TODO: Switch to shared informers -func NewBootstrapSigner(cl clientset.Interface, options BootstrapSignerOptions) *BootstrapSigner { +func NewBootstrapSigner(cl clientset.Interface, options BootstrapSignerOptions) (*BootstrapSigner, error) { e := &BootstrapSigner{ client: cl, configMapKey: options.ConfigMapNamespace + "/" + options.ConfigMapName, secretNamespace: options.TokenSecretNamespace, syncQueue: workqueue.NewNamed("bootstrap_signer_queue"), } - if cl.Core().RESTClient().GetRateLimiter() != nil { - metrics.RegisterMetricAndTrackRateLimiterUsage("bootstrap_signer", cl.Core().RESTClient().GetRateLimiter()) + if cl.CoreV1().RESTClient().GetRateLimiter() != nil { + if err := metrics.RegisterMetricAndTrackRateLimiterUsage("bootstrap_signer", cl.CoreV1().RESTClient().GetRateLimiter()); err != nil { + return nil, err + } } configMapSelector := fields.SelectorFromSet(map[string]string{api.ObjectNameField: options.ConfigMapName}) e.configMaps, e.configMapsController = cache.NewInformer( &cache.ListWatch{ ListFunc: func(lo metav1.ListOptions) (runtime.Object, error) { lo.FieldSelector = configMapSelector.String() - return e.client.Core().ConfigMaps(options.ConfigMapNamespace).List(lo) + return e.client.CoreV1().ConfigMaps(options.ConfigMapNamespace).List(lo) }, WatchFunc: func(lo metav1.ListOptions) (watch.Interface, error) { lo.FieldSelector = configMapSelector.String() - return e.client.Core().ConfigMaps(options.ConfigMapNamespace).Watch(lo) + return e.client.CoreV1().ConfigMaps(options.ConfigMapNamespace).Watch(lo) }, }, &v1.ConfigMap{}, @@ -127,11 +129,11 @@ func NewBootstrapSigner(cl clientset.Interface, options BootstrapSignerOptions) &cache.ListWatch{ ListFunc: func(lo metav1.ListOptions) (runtime.Object, error) { lo.FieldSelector = secretSelector.String() - return e.client.Core().Secrets(e.secretNamespace).List(lo) + return e.client.CoreV1().Secrets(e.secretNamespace).List(lo) }, WatchFunc: func(lo metav1.ListOptions) (watch.Interface, error) { lo.FieldSelector = secretSelector.String() - return e.client.Core().Secrets(e.secretNamespace).Watch(lo) + return e.client.CoreV1().Secrets(e.secretNamespace).Watch(lo) }, }, &v1.Secret{}, @@ -142,7 +144,7 @@ func NewBootstrapSigner(cl clientset.Interface, options BootstrapSignerOptions) DeleteFunc: func(_ interface{}) { e.pokeConfigMapSync() }, }, ) - return e + return e, nil } // Run runs controller loops and returns when they are done @@ -227,7 +229,7 @@ func (e *BootstrapSigner) signConfigMap() { } func (e *BootstrapSigner) updateConfigMap(cm *v1.ConfigMap) { - _, err := e.client.Core().ConfigMaps(cm.Namespace).Update(cm) + _, err := e.client.CoreV1().ConfigMaps(cm.Namespace).Update(cm) if err != nil && !apierrors.IsConflict(err) && !apierrors.IsNotFound(err) { glog.V(3).Infof("Error updating ConfigMap: %v", err) } diff --git a/pkg/controller/bootstrap/bootstrapsigner_test.go b/pkg/controller/bootstrap/bootstrapsigner_test.go index 79e707748a6..ba92382300d 100644 --- a/pkg/controller/bootstrap/bootstrapsigner_test.go +++ b/pkg/controller/bootstrap/bootstrapsigner_test.go @@ -26,7 +26,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/kubernetes/fake" core "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api" ) @@ -36,10 +36,14 @@ func init() { const testTokenID = "abc123" -func newBootstrapSigner() (*BootstrapSigner, *fake.Clientset) { +func newBootstrapSigner() (*BootstrapSigner, *fake.Clientset, error) { options := DefaultBootstrapSignerOptions() cl := fake.NewSimpleClientset() - return NewBootstrapSigner(cl, options), cl + bsc, err := NewBootstrapSigner(cl, options) + if err != nil { + return nil, nil, err + } + return bsc, cl, nil } func newConfigMap(tokenID, signature string) *v1.ConfigMap { @@ -60,13 +64,19 @@ func newConfigMap(tokenID, signature string) *v1.ConfigMap { } func TestNoConfigMap(t *testing.T) { - signer, cl := newBootstrapSigner() + signer, cl, err := newBootstrapSigner() + if err != nil { + t.Fatalf("error creating BootstrapSigner: %v", err) + } signer.signConfigMap() verifyActions(t, []core.Action{}, cl.Actions()) } func TestSimpleSign(t *testing.T) { - signer, cl := newBootstrapSigner() + signer, cl, err := newBootstrapSigner() + if err != nil { + t.Fatalf("error creating BootstrapSigner: %v", err) + } cm := newConfigMap("", "") signer.configMaps.Add(cm) @@ -87,7 +97,10 @@ func TestSimpleSign(t *testing.T) { } func TestNoSignNeeded(t *testing.T) { - signer, cl := newBootstrapSigner() + signer, cl, err := newBootstrapSigner() + if err != nil { + t.Fatalf("error creating BootstrapSigner: %v", err) + } cm := newConfigMap(testTokenID, "eyJhbGciOiJIUzI1NiIsImtpZCI6ImFiYzEyMyJ9..QSxpUG7Q542CirTI2ECPSZjvBOJURUW5a7XqFpNI958") signer.configMaps.Add(cm) @@ -102,7 +115,10 @@ func TestNoSignNeeded(t *testing.T) { } func TestUpdateSignature(t *testing.T) { - signer, cl := newBootstrapSigner() + signer, cl, err := newBootstrapSigner() + if err != nil { + t.Fatalf("error creating BootstrapSigner: %v", err) + } cm := newConfigMap(testTokenID, "old signature") signer.configMaps.Add(cm) @@ -123,7 +139,10 @@ func TestUpdateSignature(t *testing.T) { } func TestRemoveSignature(t *testing.T) { - signer, cl := newBootstrapSigner() + signer, cl, err := newBootstrapSigner() + if err != nil { + t.Fatalf("error creating BootstrapSigner: %v", err) + } cm := newConfigMap(testTokenID, "old signature") signer.configMaps.Add(cm) diff --git a/pkg/controller/bootstrap/common_test.go b/pkg/controller/bootstrap/common_test.go index c4937deff1f..a61ebc21882 100644 --- a/pkg/controller/bootstrap/common_test.go +++ b/pkg/controller/bootstrap/common_test.go @@ -24,7 +24,7 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" core "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api/helper" + "k8s.io/kubernetes/pkg/apis/core/helper" bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api" ) diff --git a/pkg/controller/bootstrap/jws.go b/pkg/controller/bootstrap/jws.go index ec73ceb4887..273a002c202 100644 --- a/pkg/controller/bootstrap/jws.go +++ b/pkg/controller/bootstrap/jws.go @@ -20,19 +20,28 @@ import ( "fmt" "strings" - jose "github.com/square/go-jose" + jose "gopkg.in/square/go-jose.v2" ) // computeDetachedSig takes content and token details and computes a detached // JWS signature. This is described in Appendix F of RFC 7515. Basically, this // is a regular JWS with the content part of the signature elided. func computeDetachedSig(content, tokenID, tokenSecret string) (string, error) { - jwk := &jose.JsonWebKey{ + jwk := &jose.JSONWebKey{ Key: []byte(tokenSecret), KeyID: tokenID, } - signer, err := jose.NewSigner(jose.HS256, jwk) + opts := &jose.SignerOptions{ + // Since this is a symetric key, go-jose doesn't automatically include + // the KeyID as part of the protected header. We have to pass it here + // explicitly. + ExtraHeaders: map[jose.HeaderKey]interface{}{ + "kid": tokenID, + }, + } + + signer, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.HS256, Key: jwk}, opts) if err != nil { return "", fmt.Errorf("can't make a HS256 signer from the given token: %v", err) } diff --git a/pkg/controller/bootstrap/tokencleaner.go b/pkg/controller/bootstrap/tokencleaner.go index 38328edda20..6c099a4c733 100644 --- a/pkg/controller/bootstrap/tokencleaner.go +++ b/pkg/controller/bootstrap/tokencleaner.go @@ -30,7 +30,7 @@ import ( "k8s.io/apimachinery/pkg/watch" clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api" "k8s.io/kubernetes/pkg/util/metrics" ) @@ -66,13 +66,15 @@ type TokenCleaner struct { // NewTokenCleaner returns a new *NewTokenCleaner. // // TODO: Switch to shared informers -func NewTokenCleaner(cl clientset.Interface, options TokenCleanerOptions) *TokenCleaner { +func NewTokenCleaner(cl clientset.Interface, options TokenCleanerOptions) (*TokenCleaner, error) { e := &TokenCleaner{ client: cl, tokenSecretNamespace: options.TokenSecretNamespace, } - if cl.Core().RESTClient().GetRateLimiter() != nil { - metrics.RegisterMetricAndTrackRateLimiterUsage("token_cleaner", cl.Core().RESTClient().GetRateLimiter()) + if cl.CoreV1().RESTClient().GetRateLimiter() != nil { + if err := metrics.RegisterMetricAndTrackRateLimiterUsage("token_cleaner", cl.CoreV1().RESTClient().GetRateLimiter()); err != nil { + return nil, err + } } secretSelector := fields.SelectorFromSet(map[string]string{api.SecretTypeField: string(bootstrapapi.SecretTypeBootstrapToken)}) @@ -80,11 +82,11 @@ func NewTokenCleaner(cl clientset.Interface, options TokenCleanerOptions) *Token &cache.ListWatch{ ListFunc: func(lo metav1.ListOptions) (runtime.Object, error) { lo.FieldSelector = secretSelector.String() - return e.client.Core().Secrets(e.tokenSecretNamespace).List(lo) + return e.client.CoreV1().Secrets(e.tokenSecretNamespace).List(lo) }, WatchFunc: func(lo metav1.ListOptions) (watch.Interface, error) { lo.FieldSelector = secretSelector.String() - return e.client.Core().Secrets(e.tokenSecretNamespace).Watch(lo) + return e.client.CoreV1().Secrets(e.tokenSecretNamespace).Watch(lo) }, }, &v1.Secret{}, @@ -94,7 +96,7 @@ func NewTokenCleaner(cl clientset.Interface, options TokenCleanerOptions) *Token UpdateFunc: func(oldSecret, newSecret interface{}) { e.evalSecret(newSecret) }, }, ) - return e + return e, nil } // Run runs controller loops and returns when they are done @@ -118,7 +120,7 @@ func (tc *TokenCleaner) evalSecret(o interface{}) { if len(secret.UID) > 0 { options = &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &secret.UID}} } - err := tc.client.Core().Secrets(secret.Namespace).Delete(secret.Name, options) + err := tc.client.CoreV1().Secrets(secret.Namespace).Delete(secret.Name, options) // NotFound isn't a real error (it's already been deleted) // Conflict isn't a real error (the UID precondition failed) if err != nil && !apierrors.IsConflict(err) && !apierrors.IsNotFound(err) { diff --git a/pkg/controller/bootstrap/tokencleaner_test.go b/pkg/controller/bootstrap/tokencleaner_test.go index e2bc1ff2b4f..47059dd4d19 100644 --- a/pkg/controller/bootstrap/tokencleaner_test.go +++ b/pkg/controller/bootstrap/tokencleaner_test.go @@ -25,21 +25,28 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/kubernetes/fake" core "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) func init() { spew.Config.DisableMethods = true } -func newTokenCleaner() (*TokenCleaner, *fake.Clientset) { +func newTokenCleaner() (*TokenCleaner, *fake.Clientset, error) { options := DefaultTokenCleanerOptions() cl := fake.NewSimpleClientset() - return NewTokenCleaner(cl, options), cl + tcc, err := NewTokenCleaner(cl, options) + if err != nil { + return nil, nil, err + } + return tcc, cl, nil } func TestCleanerNoExpiration(t *testing.T) { - cleaner, cl := newTokenCleaner() + cleaner, cl, err := newTokenCleaner() + if err != nil { + t.Fatalf("error creating TokenCleaner: %v", err) + } secret := newTokenSecret("tokenID", "tokenSecret") cleaner.secrets.Add(secret) @@ -52,7 +59,10 @@ func TestCleanerNoExpiration(t *testing.T) { } func TestCleanerExpired(t *testing.T) { - cleaner, cl := newTokenCleaner() + cleaner, cl, err := newTokenCleaner() + if err != nil { + t.Fatalf("error creating TokenCleaner: %v", err) + } secret := newTokenSecret("tokenID", "tokenSecret") addSecretExpiration(secret, timeString(-time.Hour)) @@ -71,7 +81,10 @@ func TestCleanerExpired(t *testing.T) { } func TestCleanerNotExpired(t *testing.T) { - cleaner, cl := newTokenCleaner() + cleaner, cl, err := newTokenCleaner() + if err != nil { + t.Fatalf("error creating TokenCleaner: %v", err) + } secret := newTokenSecret("tokenID", "tokenSecret") addSecretExpiration(secret, timeString(time.Hour)) diff --git a/pkg/controller/certificates/BUILD b/pkg/controller/certificates/BUILD index f99fbb06901..a6940009acc 100644 --- a/pkg/controller/certificates/BUILD +++ b/pkg/controller/certificates/BUILD @@ -15,6 +15,7 @@ go_library( deps = [ "//pkg/controller:go_default_library", "//vendor/github.com/golang/glog:go_default_library", + "//vendor/github.com/juju/ratelimit:go_default_library", "//vendor/k8s.io/api/certificates/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", @@ -53,8 +54,8 @@ filegroup( go_test( name = "go_default_test", srcs = ["certificate_controller_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/certificates", - library = ":go_default_library", deps = [ "//pkg/controller:go_default_library", "//vendor/k8s.io/api/certificates/v1beta1:go_default_library", diff --git a/pkg/controller/certificates/approver/BUILD b/pkg/controller/certificates/approver/BUILD index 1e05a1408ac..81294579020 100644 --- a/pkg/controller/certificates/approver/BUILD +++ b/pkg/controller/certificates/approver/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["sarapprove_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/certificates/approver", - library = ":go_default_library", deps = [ "//pkg/apis/certificates/v1beta1:go_default_library", "//vendor/k8s.io/api/authorization/v1beta1:go_default_library", diff --git a/pkg/controller/certificates/approver/sarapprove.go b/pkg/controller/certificates/approver/sarapprove.go index d2eb5ac5efc..3aa6be1ce08 100644 --- a/pkg/controller/certificates/approver/sarapprove.go +++ b/pkg/controller/certificates/approver/sarapprove.go @@ -44,7 +44,7 @@ type sarApprover struct { recognizers []csrRecognizer } -func NewCSRApprovingController(client clientset.Interface, csrInformer certificatesinformers.CertificateSigningRequestInformer) (*certificates.CertificateController, error) { +func NewCSRApprovingController(client clientset.Interface, csrInformer certificatesinformers.CertificateSigningRequestInformer) *certificates.CertificateController { approver := &sarApprover{ client: client, recognizers: recognizers(), @@ -106,7 +106,7 @@ func (a *sarApprover) handle(csr *capi.CertificateSigningRequest) error { } if approved { appendApprovalCondition(csr, r.successMessage) - _, err = a.client.Certificates().CertificateSigningRequests().UpdateApproval(csr) + _, err = a.client.CertificatesV1beta1().CertificateSigningRequests().UpdateApproval(csr) if err != nil { return fmt.Errorf("error updating approval for csr: %v", err) } @@ -115,7 +115,7 @@ func (a *sarApprover) handle(csr *capi.CertificateSigningRequest) error { } if len(tried) != 0 { - return fmt.Errorf("recognized csr %q as %v but subject access review was not approved", csr.Name, tried) + return certificates.IgnorableError("recognized csr %q as %v but subject access review was not approved", csr.Name, tried) } return nil diff --git a/pkg/controller/certificates/certificate_controller.go b/pkg/controller/certificates/certificate_controller.go index 2caa30de327..e86810a4be1 100644 --- a/pkg/controller/certificates/certificate_controller.go +++ b/pkg/controller/certificates/certificate_controller.go @@ -36,6 +36,7 @@ import ( "k8s.io/kubernetes/pkg/controller" "github.com/golang/glog" + "github.com/juju/ratelimit" ) type CertificateController struct { @@ -53,16 +54,20 @@ func NewCertificateController( kubeClient clientset.Interface, csrInformer certificatesinformers.CertificateSigningRequestInformer, handler func(*certificates.CertificateSigningRequest) error, -) (*CertificateController, error) { +) *CertificateController { // Send events to the apiserver eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(glog.Infof) - eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.Core().RESTClient()).Events("")}) + eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.CoreV1().RESTClient()).Events("")}) cc := &CertificateController{ kubeClient: kubeClient, - queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "certificate"), - handler: handler, + queue: workqueue.NewNamedRateLimitingQueue(workqueue.NewMaxOfRateLimiter( + workqueue.NewItemExponentialFailureRateLimiter(200*time.Millisecond, 1000*time.Second), + // 10 qps, 100 bucket size. This is only for retry speed and its only the overall factor (not per item) + &workqueue.BucketRateLimiter{Bucket: ratelimit.NewBucketWithRate(float64(10), int64(100))}, + ), "certificate"), + handler: handler, } // Manage the addition/update of certificate requests @@ -97,8 +102,7 @@ func NewCertificateController( }) cc.csrLister = csrInformer.Lister() cc.csrsSynced = csrInformer.Informer().HasSynced - cc.handler = handler - return cc, nil + return cc } // Run the main goroutine responsible for watching and syncing jobs. @@ -136,7 +140,11 @@ func (cc *CertificateController) processNextWorkItem() bool { if err := cc.syncFunc(cKey.(string)); err != nil { cc.queue.AddRateLimited(cKey) - utilruntime.HandleError(fmt.Errorf("Sync %v failed with : %v", cKey, err)) + if _, ignorable := err.(ignorableError); !ignorable { + utilruntime.HandleError(fmt.Errorf("Sync %v failed with : %v", cKey, err)) + } else { + glog.V(4).Infof("Sync %v failed with : %v", cKey, err) + } return true } @@ -182,3 +190,17 @@ func (cc *CertificateController) syncFunc(key string) error { return cc.handler(csr) } + +// IgnorableError returns an error that we shouldn't handle (i.e. log) because +// it's spammy and usually user error. Instead we will log these errors at a +// higher log level. We still need to throw these errors to signal that the +// sync should be retried. +func IgnorableError(s string, args ...interface{}) ignorableError { + return ignorableError(fmt.Sprintf(s, args...)) +} + +type ignorableError string + +func (e ignorableError) Error() string { + return string(e) +} diff --git a/pkg/controller/certificates/certificate_controller_test.go b/pkg/controller/certificates/certificate_controller_test.go index 20a858b4bbf..8805fbfcef5 100644 --- a/pkg/controller/certificates/certificate_controller_test.go +++ b/pkg/controller/certificates/certificate_controller_test.go @@ -54,14 +54,11 @@ func TestCertificateController(t *testing.T) { return nil } - controller, err := NewCertificateController( + controller := NewCertificateController( client, informerFactory.Certificates().V1beta1().CertificateSigningRequests(), handler, ) - if err != nil { - t.Fatalf("error creating controller: %v", err) - } controller.csrsSynced = func() bool { return true } stopCh := make(chan struct{}) diff --git a/pkg/controller/certificates/cleaner/BUILD b/pkg/controller/certificates/cleaner/BUILD index 14ca29e60ee..ada38c62c26 100644 --- a/pkg/controller/certificates/cleaner/BUILD +++ b/pkg/controller/certificates/cleaner/BUILD @@ -35,8 +35,8 @@ filegroup( go_test( name = "go_default_test", srcs = ["cleaner_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/certificates/cleaner", - library = ":go_default_library", deps = [ "//vendor/k8s.io/api/certificates/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/controller/certificates/signer/BUILD b/pkg/controller/certificates/signer/BUILD index d7da4d31e07..d4119b034aa 100644 --- a/pkg/controller/certificates/signer/BUILD +++ b/pkg/controller/certificates/signer/BUILD @@ -14,8 +14,8 @@ go_test( "testdata/ca.key", "testdata/kubelet.csr", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/certificates/signer", - library = ":go_default_library", deps = [ "//vendor/k8s.io/api/certificates/v1beta1:go_default_library", "//vendor/k8s.io/client-go/util/cert:go_default_library", diff --git a/pkg/controller/certificates/signer/cfssl_signer.go b/pkg/controller/certificates/signer/cfssl_signer.go index 1a5f3ff7e7b..dd57d68a0dc 100644 --- a/pkg/controller/certificates/signer/cfssl_signer.go +++ b/pkg/controller/certificates/signer/cfssl_signer.go @@ -50,7 +50,7 @@ func NewCSRSigningController( client, csrInformer, signer.handle, - ) + ), nil } type cfsslSigner struct { @@ -103,7 +103,7 @@ func (s *cfsslSigner) handle(csr *capi.CertificateSigningRequest) error { if err != nil { return fmt.Errorf("error auto signing csr: %v", err) } - _, err = s.client.Certificates().CertificateSigningRequests().UpdateStatus(csr) + _, err = s.client.CertificatesV1beta1().CertificateSigningRequests().UpdateStatus(csr) if err != nil { return fmt.Errorf("error updating signature for csr: %v", err) } diff --git a/pkg/controller/client_builder.go b/pkg/controller/client_builder.go index df873fb3e50..041717623d7 100644 --- a/pkg/controller/client_builder.go +++ b/pkg/controller/client_builder.go @@ -28,13 +28,13 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount" - clientgoclientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes" v1authentication "k8s.io/client-go/kubernetes/typed/authentication/v1" v1core "k8s.io/client-go/kubernetes/typed/core/v1" restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/cache" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/serviceaccount" "github.com/golang/glog" @@ -46,8 +46,8 @@ type ControllerClientBuilder interface { ConfigOrDie(name string) *restclient.Config Client(name string) (clientset.Interface, error) ClientOrDie(name string) clientset.Interface - ClientGoClient(name string) (clientgoclientset.Interface, error) - ClientGoClientOrDie(name string) clientgoclientset.Interface + ClientGoClient(name string) (clientset.Interface, error) + ClientGoClientOrDie(name string) clientset.Interface } // SimpleControllerClientBuilder returns a fixed client with different user agents @@ -85,15 +85,15 @@ func (b SimpleControllerClientBuilder) ClientOrDie(name string) clientset.Interf return client } -func (b SimpleControllerClientBuilder) ClientGoClient(name string) (clientgoclientset.Interface, error) { +func (b SimpleControllerClientBuilder) ClientGoClient(name string) (clientset.Interface, error) { clientConfig, err := b.Config(name) if err != nil { return nil, err } - return clientgoclientset.NewForConfig(clientConfig) + return clientset.NewForConfig(clientConfig) } -func (b SimpleControllerClientBuilder) ClientGoClientOrDie(name string) clientgoclientset.Interface { +func (b SimpleControllerClientBuilder) ClientGoClientOrDie(name string) clientset.Interface { client, err := b.ClientGoClient(name) if err != nil { glog.Fatal(err) @@ -237,7 +237,7 @@ func (b SAControllerClientBuilder) getAuthenticatedConfig(sa *v1.ServiceAccount, // If we couldn't run the token review, the API might be disabled or we might not have permission. // Try to make a request to /apis with the token. If we get a 401 we should consider the token invalid. clientConfigCopy := *clientConfig - clientConfigCopy.NegotiatedSerializer = api.Codecs + clientConfigCopy.NegotiatedSerializer = legacyscheme.Codecs client, err := restclient.UnversionedRESTClientFor(&clientConfigCopy) if err != nil { return nil, false, err @@ -275,15 +275,15 @@ func (b SAControllerClientBuilder) ClientOrDie(name string) clientset.Interface return client } -func (b SAControllerClientBuilder) ClientGoClient(name string) (clientgoclientset.Interface, error) { +func (b SAControllerClientBuilder) ClientGoClient(name string) (clientset.Interface, error) { clientConfig, err := b.Config(name) if err != nil { return nil, err } - return clientgoclientset.NewForConfig(clientConfig) + return clientset.NewForConfig(clientConfig) } -func (b SAControllerClientBuilder) ClientGoClientOrDie(name string) clientgoclientset.Interface { +func (b SAControllerClientBuilder) ClientGoClientOrDie(name string) clientset.Interface { client, err := b.ClientGoClient(name) if err != nil { glog.Fatal(err) diff --git a/pkg/controller/cloud/BUILD b/pkg/controller/cloud/BUILD index 2202e0823ce..dfd2739720b 100644 --- a/pkg/controller/cloud/BUILD +++ b/pkg/controller/cloud/BUILD @@ -18,8 +18,8 @@ go_library( "//pkg/cloudprovider:go_default_library", "//pkg/controller:go_default_library", "//pkg/kubelet/apis:go_default_library", + "//pkg/scheduler/algorithm:go_default_library", "//pkg/util/node:go_default_library", - "//plugin/pkg/scheduler/algorithm:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", @@ -48,15 +48,15 @@ go_test( "node_controller_test.go", "pvlcontroller_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/cloud", - library = ":go_default_library", deps = [ "//pkg/cloudprovider:go_default_library", "//pkg/cloudprovider/providers/fake:go_default_library", "//pkg/controller:go_default_library", "//pkg/controller/testutil:go_default_library", "//pkg/kubelet/apis:go_default_library", - "//plugin/pkg/scheduler/algorithm:go_default_library", + "//pkg/scheduler/algorithm:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/controller/cloud/node_controller.go b/pkg/controller/cloud/node_controller.go index 7f884779947..0f548e7d954 100644 --- a/pkg/controller/cloud/node_controller.go +++ b/pkg/controller/cloud/node_controller.go @@ -37,8 +37,8 @@ import ( nodeutilv1 "k8s.io/kubernetes/pkg/api/v1/node" "k8s.io/kubernetes/pkg/cloudprovider" kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis" + "k8s.io/kubernetes/pkg/scheduler/algorithm" nodeutil "k8s.io/kubernetes/pkg/util/node" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" ) var UpdateNodeSpecBackoff = wait.Backoff{ @@ -147,6 +147,15 @@ func (cnc *CloudNodeController) updateNodeAddress(node *v1.Node, instances cloud glog.V(5).Infof("This node %s is still tainted. Will not process.", node.Name) return } + // Node that isn't present according to the cloud provider shouldn't have its address updated + exists, err := ensureNodeExistsByProviderIDOrExternalID(instances, node) + if err != nil { + // Continue to update node address when not sure the node is not exists + glog.Errorf("%v", err) + } else if !exists { + glog.V(4).Infof("The node %s is no longer present according to the cloud provider, do not process.", node.Name) + return + } nodeAddresses, err := getNodeAddressesByProviderIDOrName(instances, node) if err != nil { @@ -183,7 +192,7 @@ func (cnc *CloudNodeController) updateNodeAddress(node *v1.Node, instances cloud if !nodeAddressesChangeDetected(node.Status.Addresses, newNode.Status.Addresses) { return } - _, err = nodeutil.PatchNodeStatus(cnc.kubeClient.CoreV1(), types.NodeName(node.Name), node, newNode) + _, _, err = nodeutil.PatchNodeStatus(cnc.kubeClient.CoreV1(), types.NodeName(node.Name), node, newNode) if err != nil { glog.Errorf("Error patching node with cloud ip addresses = [%v]", err) } diff --git a/pkg/controller/cloud/node_controller_test.go b/pkg/controller/cloud/node_controller_test.go index 27d2b95e106..a8faf8352f4 100644 --- a/pkg/controller/cloud/node_controller_test.go +++ b/pkg/controller/cloud/node_controller_test.go @@ -35,7 +35,7 @@ import ( "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller/testutil" kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" + "k8s.io/kubernetes/pkg/scheduler/algorithm" "github.com/golang/glog" "github.com/stretchr/testify/assert" @@ -458,7 +458,7 @@ func TestGCECondition(t *testing.T) { } } - assert.True(t, conditionAdded, "Network Route Condition for GCE not added by external cloud intializer") + assert.True(t, conditionAdded, "Network Route Condition for GCE not added by external cloud initializer") } // This test checks that a node with the external cloud provider taint is cloudprovider initialized and @@ -888,6 +888,75 @@ func TestNodeAddressesChangeDetected(t *testing.T) { "Node address changes are not detected correctly") } +// This test checks that a node with the external cloud provider taint is cloudprovider initialized and +// and node addresses will not be updated when node isn't present according to the cloudprovider +func TestNodeAddressesNotUpdate(t *testing.T) { + fnh := &testutil.FakeNodeHandler{ + Existing: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{}, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + Spec: v1.NodeSpec{ + Taints: []v1.Taint{ + { + Key: "ImproveCoverageTaint", + Value: "true", + Effect: v1.TaintEffectNoSchedule, + }, + }, + }, + }, + }, + } + + factory := informers.NewSharedInformerFactory(fnh, controller.NoResyncPeriodFunc()) + + fakeCloud := &fakecloud.FakeCloud{ + InstanceTypes: map[types.NodeName]string{}, + Addresses: []v1.NodeAddress{ + { + Type: v1.NodeHostName, + Address: "node0.cloud.internal", + }, + { + Type: v1.NodeInternalIP, + Address: "10.0.0.1", + }, + { + Type: v1.NodeExternalIP, + Address: "132.143.154.163", + }, + }, + ExistsByProviderID: false, + Err: nil, + } + + cloudNodeController := &CloudNodeController{ + kubeClient: fnh, + nodeInformer: factory.Core().V1().Nodes(), + cloud: fakeCloud, + } + + cloudNodeController.updateNodeAddress(fnh.Existing[0], fakeCloud) + + if len(fnh.UpdatedNodes) != 0 { + t.Errorf("Node was not correctly updated, the updated len(nodes) got: %v, wanted=0", len(fnh.UpdatedNodes)) + } +} + // This test checks that a node is set with the correct providerID func TestNodeProviderID(t *testing.T) { fnh := &testutil.FakeNodeHandler{ diff --git a/pkg/controller/cloud/pvlcontroller.go b/pkg/controller/cloud/pvlcontroller.go index 1a75a9c2867..a567643bfdd 100644 --- a/pkg/controller/cloud/pvlcontroller.go +++ b/pkg/controller/cloud/pvlcontroller.go @@ -182,7 +182,7 @@ func (pvlc *PersistentVolumeLabelController) addLabels(key string) error { func (pvlc *PersistentVolumeLabelController) addLabelsToVolume(vol *v1.PersistentVolume) error { var volumeLabels map[string]string - // Only add labels if in the list of initializers + // Only add labels if the next pending initializer. if needsInitialization(vol.Initializers, initializerName) { if labeler, ok := (pvlc.cloud).(cloudprovider.PVLabeler); ok { labels, err := labeler.GetLabelsForVolume(vol) @@ -235,7 +235,7 @@ func (pvlc *PersistentVolumeLabelController) updateVolume(vol *v1.PersistentVolu return err } - _, err = pvlc.kubeClient.Core().PersistentVolumes().Patch(string(volName), types.StrategicMergePatchType, patchBytes) + _, err = pvlc.kubeClient.CoreV1().PersistentVolumes().Patch(string(volName), types.StrategicMergePatchType, patchBytes) if err != nil { return fmt.Errorf("failed to update PersistentVolume %s: %v", volName, err) } @@ -265,16 +265,17 @@ func removeInitializer(initializers *metav1.Initializers, name string) *metav1.I return &metav1.Initializers{Pending: updated} } +// needsInitialization checks whether or not the PVL is the next pending initializer. func needsInitialization(initializers *metav1.Initializers, name string) bool { - hasInitializer := false - - if initializers != nil { - for _, pending := range initializers.Pending { - if pending.Name == name { - hasInitializer = true - break - } - } + if initializers == nil { + return false } - return hasInitializer + + if len(initializers.Pending) == 0 { + return false + } + + // There is at least one initializer still pending so check to + // see if the PVL is the next in line. + return initializers.Pending[0].Name == name } diff --git a/pkg/controller/cloud/pvlcontroller_test.go b/pkg/controller/cloud/pvlcontroller_test.go index 48b079122c5..2ab2f11394d 100644 --- a/pkg/controller/cloud/pvlcontroller_test.go +++ b/pkg/controller/cloud/pvlcontroller_test.go @@ -146,11 +146,16 @@ func TestAddLabelsToVolume(t *testing.T) { initializers: &metav1.Initializers{Pending: []metav1.Initializer{{Name: initializerName}}}, shouldLabel: true, }, - "PV with other initializers": { + "PV with other initializers only": { vol: pv, initializers: &metav1.Initializers{Pending: []metav1.Initializer{{Name: "OtherInit"}}}, shouldLabel: false, }, + "PV with other initializers first": { + vol: pv, + initializers: &metav1.Initializers{Pending: []metav1.Initializer{{Name: "OtherInit"}, {Name: initializerName}}}, + shouldLabel: false, + }, } for d, tc := range testCases { diff --git a/pkg/controller/clusterroleaggregation/BUILD b/pkg/controller/clusterroleaggregation/BUILD new file mode 100644 index 00000000000..965c04691a6 --- /dev/null +++ b/pkg/controller/clusterroleaggregation/BUILD @@ -0,0 +1,57 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["clusterroleaggregation_controller.go"], + importpath = "k8s.io/kubernetes/pkg/controller/clusterroleaggregation", + visibility = ["//visibility:public"], + deps = [ + "//pkg/controller:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/api/rbac/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/client-go/informers/rbac/v1:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/typed/rbac/v1:go_default_library", + "//vendor/k8s.io/client-go/listers/rbac/v1:go_default_library", + "//vendor/k8s.io/client-go/tools/cache:go_default_library", + "//vendor/k8s.io/client-go/util/workqueue:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) + +go_test( + name = "go_default_test", + srcs = ["clusterroleaggregation_controller_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/controller/clusterroleaggregation", + deps = [ + "//pkg/controller:go_default_library", + "//vendor/k8s.io/api/rbac/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", + "//vendor/k8s.io/client-go/listers/rbac/v1:go_default_library", + "//vendor/k8s.io/client-go/testing:go_default_library", + "//vendor/k8s.io/client-go/tools/cache:go_default_library", + ], +) diff --git a/pkg/controller/clusterroleaggregation/clusterroleaggregation_controller.go b/pkg/controller/clusterroleaggregation/clusterroleaggregation_controller.go new file mode 100644 index 00000000000..05879e0e681 --- /dev/null +++ b/pkg/controller/clusterroleaggregation/clusterroleaggregation_controller.go @@ -0,0 +1,213 @@ +/* +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 clusterroleaggregation + +import ( + "fmt" + "sort" + "time" + + "github.com/golang/glog" + + 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/labels" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + rbacinformers "k8s.io/client-go/informers/rbac/v1" + rbacclient "k8s.io/client-go/kubernetes/typed/rbac/v1" + rbaclisters "k8s.io/client-go/listers/rbac/v1" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" + "k8s.io/kubernetes/pkg/controller" +) + +// ClusterRoleAggregationController is a controller to combine cluster roles +type ClusterRoleAggregationController struct { + clusterRoleClient rbacclient.ClusterRolesGetter + clusterRoleLister rbaclisters.ClusterRoleLister + clusterRolesSynced cache.InformerSynced + + syncHandler func(key string) error + queue workqueue.RateLimitingInterface +} + +// NewClusterRoleAggregation creates a new controller +func NewClusterRoleAggregation(clusterRoleInformer rbacinformers.ClusterRoleInformer, clusterRoleClient rbacclient.ClusterRolesGetter) *ClusterRoleAggregationController { + c := &ClusterRoleAggregationController{ + clusterRoleClient: clusterRoleClient, + clusterRoleLister: clusterRoleInformer.Lister(), + clusterRolesSynced: clusterRoleInformer.Informer().HasSynced, + + queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "ClusterRoleAggregator"), + } + c.syncHandler = c.syncClusterRole + + clusterRoleInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + c.enqueue() + }, + UpdateFunc: func(old, cur interface{}) { + c.enqueue() + }, + DeleteFunc: func(uncast interface{}) { + c.enqueue() + }, + }) + return c +} + +func (c *ClusterRoleAggregationController) syncClusterRole(key string) error { + _, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + return err + } + sharedClusterRole, err := c.clusterRoleLister.Get(name) + if errors.IsNotFound(err) { + return nil + } + if err != nil { + return err + } + if sharedClusterRole.AggregationRule == nil { + return nil + } + + newPolicyRules := []rbacv1.PolicyRule{} + for i := range sharedClusterRole.AggregationRule.ClusterRoleSelectors { + selector := sharedClusterRole.AggregationRule.ClusterRoleSelectors[i] + runtimeLabelSelector, err := metav1.LabelSelectorAsSelector(&selector) + if err != nil { + return err + } + clusterRoles, err := c.clusterRoleLister.List(runtimeLabelSelector) + if err != nil { + return err + } + sort.Sort(byName(clusterRoles)) + + for i := range clusterRoles { + if clusterRoles[i].Name == sharedClusterRole.Name { + continue + } + + for j := range clusterRoles[i].Rules { + currRule := clusterRoles[i].Rules[j] + if !ruleExists(newPolicyRules, currRule) { + newPolicyRules = append(newPolicyRules, currRule) + } + } + } + } + + if equality.Semantic.DeepEqual(newPolicyRules, sharedClusterRole.Rules) { + return nil + } + + // we need to update + clusterRole := sharedClusterRole.DeepCopy() + clusterRole.Rules = nil + for _, rule := range newPolicyRules { + clusterRole.Rules = append(clusterRole.Rules, *rule.DeepCopy()) + } + _, err = c.clusterRoleClient.ClusterRoles().Update(clusterRole) + + return err +} + +func ruleExists(haystack []rbacv1.PolicyRule, needle rbacv1.PolicyRule) bool { + for _, curr := range haystack { + if equality.Semantic.DeepEqual(curr, needle) { + return true + } + } + return false +} + +// Run starts the controller and blocks until stopCh is closed. +func (c *ClusterRoleAggregationController) Run(workers int, stopCh <-chan struct{}) { + defer utilruntime.HandleCrash() + defer c.queue.ShutDown() + + glog.Infof("Starting ClusterRoleAggregator") + defer glog.Infof("Shutting down ClusterRoleAggregator") + + if !controller.WaitForCacheSync("ClusterRoleAggregator", stopCh, c.clusterRolesSynced) { + return + } + + for i := 0; i < workers; i++ { + go wait.Until(c.runWorker, time.Second, stopCh) + } + + <-stopCh +} + +func (c *ClusterRoleAggregationController) runWorker() { + for c.processNextWorkItem() { + } +} + +func (c *ClusterRoleAggregationController) processNextWorkItem() bool { + dsKey, quit := c.queue.Get() + if quit { + return false + } + defer c.queue.Done(dsKey) + + err := c.syncHandler(dsKey.(string)) + if err == nil { + c.queue.Forget(dsKey) + return true + } + + utilruntime.HandleError(fmt.Errorf("%v failed with : %v", dsKey, err)) + c.queue.AddRateLimited(dsKey) + + return true +} + +func (c *ClusterRoleAggregationController) enqueue() { + // this is unusual, but since the set of all clusterroles is small and we don't know the dependency + // graph, just queue up every thing each time. This allows errors to be selectively retried if there + // is a problem updating a single role + allClusterRoles, err := c.clusterRoleLister.List(labels.Everything()) + if err != nil { + utilruntime.HandleError(fmt.Errorf("Couldn't list all objects %v", err)) + return + } + for _, clusterRole := range allClusterRoles { + // only queue ones that we may need to aggregate + if clusterRole.AggregationRule == nil { + continue + } + key, err := controller.KeyFunc(clusterRole) + if err != nil { + utilruntime.HandleError(fmt.Errorf("Couldn't get key for object %#v: %v", clusterRole, err)) + return + } + c.queue.Add(key) + } +} + +type byName []*rbacv1.ClusterRole + +func (a byName) Len() int { return len(a) } +func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byName) Less(i, j int) bool { return a[i].Name < a[j].Name } diff --git a/pkg/controller/clusterroleaggregation/clusterroleaggregation_controller_test.go b/pkg/controller/clusterroleaggregation/clusterroleaggregation_controller_test.go new file mode 100644 index 00000000000..de007cdd3f3 --- /dev/null +++ b/pkg/controller/clusterroleaggregation/clusterroleaggregation_controller_test.go @@ -0,0 +1,182 @@ +/* +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 clusterroleaggregation + +import ( + "testing" + + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/equality" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/diff" + fakeclient "k8s.io/client-go/kubernetes/fake" + rbaclisters "k8s.io/client-go/listers/rbac/v1" + clienttesting "k8s.io/client-go/testing" + "k8s.io/client-go/tools/cache" + "k8s.io/kubernetes/pkg/controller" +) + +func TestSyncClusterRole(t *testing.T) { + hammerRules := func() []rbacv1.PolicyRule { + return []rbacv1.PolicyRule{ + {Verbs: []string{"hammer"}, Resources: []string{"nails"}}, + {Verbs: []string{"hammer"}, Resources: []string{"wedges"}}, + } + } + chiselRules := func() []rbacv1.PolicyRule { + return []rbacv1.PolicyRule{ + {Verbs: []string{"chisel"}, Resources: []string{"mortises"}}, + } + } + sawRules := func() []rbacv1.PolicyRule { + return []rbacv1.PolicyRule{ + {Verbs: []string{"saw"}, Resources: []string{"boards"}}, + } + } + role := func(name string, labels map[string]string, rules []rbacv1.PolicyRule) *rbacv1.ClusterRole { + return &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{Name: name, Labels: labels}, + Rules: rules, + } + } + combinedRole := func(selectors []map[string]string, rules ...[]rbacv1.PolicyRule) *rbacv1.ClusterRole { + ret := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{Name: "combined"}, + AggregationRule: &rbacv1.AggregationRule{}, + } + for _, selector := range selectors { + ret.AggregationRule.ClusterRoleSelectors = append(ret.AggregationRule.ClusterRoleSelectors, + metav1.LabelSelector{MatchLabels: selector}) + } + for _, currRules := range rules { + ret.Rules = append(ret.Rules, currRules...) + } + return ret + } + + tests := []struct { + name string + startingClusterRoles []*rbacv1.ClusterRole + clusterRoleToSync string + expectedClusterRole *rbacv1.ClusterRole + }{ + { + name: "remove dead rules", + startingClusterRoles: []*rbacv1.ClusterRole{ + role("hammer", map[string]string{"foo": "bar"}, hammerRules()), + combinedRole([]map[string]string{{"foo": "bar"}}, sawRules()), + }, + clusterRoleToSync: "combined", + expectedClusterRole: combinedRole([]map[string]string{{"foo": "bar"}}, hammerRules()), + }, + { + name: "strip rules", + startingClusterRoles: []*rbacv1.ClusterRole{ + role("hammer", map[string]string{"foo": "not-bar"}, hammerRules()), + combinedRole([]map[string]string{{"foo": "bar"}}, hammerRules()), + }, + clusterRoleToSync: "combined", + expectedClusterRole: combinedRole([]map[string]string{{"foo": "bar"}}), + }, + { + name: "select properly and put in order", + startingClusterRoles: []*rbacv1.ClusterRole{ + role("hammer", map[string]string{"foo": "bar"}, hammerRules()), + role("chisel", map[string]string{"foo": "bar"}, chiselRules()), + role("saw", map[string]string{"foo": "not-bar"}, sawRules()), + combinedRole([]map[string]string{{"foo": "bar"}}), + }, + clusterRoleToSync: "combined", + expectedClusterRole: combinedRole([]map[string]string{{"foo": "bar"}}, chiselRules(), hammerRules()), + }, + { + name: "select properly with multiple selectors", + startingClusterRoles: []*rbacv1.ClusterRole{ + role("hammer", map[string]string{"foo": "bar"}, hammerRules()), + role("chisel", map[string]string{"foo": "bar"}, chiselRules()), + role("saw", map[string]string{"foo": "not-bar"}, sawRules()), + combinedRole([]map[string]string{{"foo": "bar"}, {"foo": "not-bar"}}), + }, + clusterRoleToSync: "combined", + expectedClusterRole: combinedRole([]map[string]string{{"foo": "bar"}, {"foo": "not-bar"}}, chiselRules(), hammerRules(), sawRules()), + }, + { + name: "select properly remove duplicates", + startingClusterRoles: []*rbacv1.ClusterRole{ + role("hammer", map[string]string{"foo": "bar"}, hammerRules()), + role("chisel", map[string]string{"foo": "bar"}, chiselRules()), + role("saw", map[string]string{"foo": "bar"}, sawRules()), + role("other-saw", map[string]string{"foo": "not-bar"}, sawRules()), + combinedRole([]map[string]string{{"foo": "bar"}, {"foo": "not-bar"}}), + }, + clusterRoleToSync: "combined", + expectedClusterRole: combinedRole([]map[string]string{{"foo": "bar"}, {"foo": "not-bar"}}, chiselRules(), hammerRules(), sawRules()), + }, + { + name: "no diff skip", + startingClusterRoles: []*rbacv1.ClusterRole{ + role("hammer", map[string]string{"foo": "bar"}, hammerRules()), + combinedRole([]map[string]string{{"foo": "bar"}}, hammerRules()), + }, + clusterRoleToSync: "combined", + expectedClusterRole: nil, + }} + + 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...) + c := ClusterRoleAggregationController{ + clusterRoleClient: fakeClient.RbacV1(), + clusterRoleLister: rbaclisters.NewClusterRoleLister(indexer), + } + err := c.syncClusterRole(test.clusterRoleToSync) + if err != nil { + t.Fatal(err) + } + + if test.expectedClusterRole == nil { + if len(fakeClient.Actions()) != 0 { + t.Fatalf("unexpected actions %#v", fakeClient.Actions()) + } + return + } + if len(fakeClient.Actions()) != 1 { + t.Fatalf("unexpected actions %#v", fakeClient.Actions()) + } + + action := fakeClient.Actions()[0] + 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", diff.ObjectDiff(test.expectedClusterRole, updateAction.GetObject().(*rbacv1.ClusterRole))) + + } + }) + } +} diff --git a/pkg/controller/controller_utils.go b/pkg/controller/controller_utils.go index 360f2091b2d..b662fa6fb57 100644 --- a/pkg/controller/controller_utils.go +++ b/pkg/controller/controller_utils.go @@ -38,15 +38,13 @@ import ( "k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/util/wait" clientset "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" - ref "k8s.io/client-go/tools/reference" "k8s.io/client-go/util/integer" clientretry "k8s.io/client-go/util/retry" - _ "k8s.io/kubernetes/pkg/api/install" podutil "k8s.io/kubernetes/pkg/api/v1/pod" - "k8s.io/kubernetes/pkg/api/validation" + _ "k8s.io/kubernetes/pkg/apis/core/install" + "k8s.io/kubernetes/pkg/apis/core/validation" hashutil "k8s.io/kubernetes/pkg/util/hash" taintutils "k8s.io/kubernetes/pkg/util/taints" @@ -412,7 +410,7 @@ type RealRSControl struct { var _ RSControlInterface = &RealRSControl{} func (r RealRSControl) PatchReplicaSet(namespace, name string, data []byte) error { - _, err := r.KubeClient.Extensions().ReplicaSets(namespace).Patch(name, types.StrategicMergePatchType, data) + _, err := r.KubeClient.ExtensionsV1beta1().ReplicaSets(namespace).Patch(name, types.StrategicMergePatchType, data) return err } @@ -474,29 +472,12 @@ func getPodsFinalizers(template *v1.PodTemplateSpec) []string { return desiredFinalizers } -func getPodsAnnotationSet(template *v1.PodTemplateSpec, object runtime.Object) (labels.Set, error) { +func getPodsAnnotationSet(template *v1.PodTemplateSpec) labels.Set { desiredAnnotations := make(labels.Set) for k, v := range template.Annotations { desiredAnnotations[k] = v } - createdByRef, err := ref.GetReference(scheme.Scheme, object) - if err != nil { - return desiredAnnotations, fmt.Errorf("unable to get controller reference: %v", err) - } - - // TODO: this code was not safe previously - as soon as new code came along that switched to v2, old clients - // would be broken upon reading it. This is explicitly hardcoded to v1 to guarantee predictable deployment. - // We need to consistently handle this case of annotation versioning. - codec := scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion) - - createdByRefJson, err := runtime.Encode(codec, &v1.SerializedReference{ - Reference: *createdByRef, - }) - if err != nil { - return desiredAnnotations, fmt.Errorf("unable to serialize controller reference: %v", err) - } - desiredAnnotations[v1.CreatedByAnnotation] = string(createdByRefJson) - return desiredAnnotations, nil + return desiredAnnotations } func getPodsPrefix(controllerName string) string { @@ -546,17 +527,14 @@ func (r RealPodControl) CreatePodsOnNode(nodeName, namespace string, template *v } func (r RealPodControl) PatchPod(namespace, name string, data []byte) error { - _, err := r.KubeClient.Core().Pods(namespace).Patch(name, types.StrategicMergePatchType, data) + _, err := r.KubeClient.CoreV1().Pods(namespace).Patch(name, types.StrategicMergePatchType, data) return err } func GetPodFromTemplate(template *v1.PodTemplateSpec, parentObject runtime.Object, controllerRef *metav1.OwnerReference) (*v1.Pod, error) { desiredLabels := getPodsLabelSet(template) desiredFinalizers := getPodsFinalizers(template) - desiredAnnotations, err := getPodsAnnotationSet(template, parentObject) - if err != nil { - return nil, err - } + desiredAnnotations := getPodsAnnotationSet(template) accessor, err := meta.Accessor(parentObject) if err != nil { return nil, fmt.Errorf("parentObject does not have ObjectMeta, %v", err) @@ -589,7 +567,7 @@ func (r RealPodControl) createPods(nodeName, namespace string, template *v1.PodT if labels.Set(pod.Labels).AsSelectorPreValidated().Empty() { return fmt.Errorf("unable to create pods, no labels") } - if newPod, err := r.KubeClient.Core().Pods(namespace).Create(pod); err != nil { + if newPod, err := r.KubeClient.CoreV1().Pods(namespace).Create(pod); err != nil { r.Recorder.Eventf(object, v1.EventTypeWarning, FailedCreatePodReason, "Error creating: %v", err) return err } else { @@ -610,7 +588,7 @@ func (r RealPodControl) DeletePod(namespace string, podID string, object runtime return fmt.Errorf("object does not have ObjectMeta, %v", err) } glog.V(2).Infof("Controller %v deleting pod %v/%v", accessor.GetName(), namespace, podID) - if err := r.KubeClient.Core().Pods(namespace).Delete(podID, nil); err != nil { + if err := r.KubeClient.CoreV1().Pods(namespace).Delete(podID, nil); err != nil { r.Recorder.Eventf(object, v1.EventTypeWarning, FailedDeletePodReason, "Error deleting: %v", err) return fmt.Errorf("unable to delete pods: %v", err) } else { @@ -925,10 +903,10 @@ func AddOrUpdateTaintOnNode(c clientset.Interface, nodeName string, taints ...*v // First we try getting node from the API server cache, as it's cheaper. If it fails // we get it from etcd to be sure to have fresh data. if firstTry { - oldNode, err = c.Core().Nodes().Get(nodeName, metav1.GetOptions{ResourceVersion: "0"}) + oldNode, err = c.CoreV1().Nodes().Get(nodeName, metav1.GetOptions{ResourceVersion: "0"}) firstTry = false } else { - oldNode, err = c.Core().Nodes().Get(nodeName, metav1.GetOptions{}) + oldNode, err = c.CoreV1().Nodes().Get(nodeName, metav1.GetOptions{}) } if err != nil { return err @@ -982,10 +960,10 @@ func RemoveTaintOffNode(c clientset.Interface, nodeName string, node *v1.Node, t // First we try getting node from the API server cache, as it's cheaper. If it fails // we get it from etcd to be sure to have fresh data. if firstTry { - oldNode, err = c.Core().Nodes().Get(nodeName, metav1.GetOptions{ResourceVersion: "0"}) + oldNode, err = c.CoreV1().Nodes().Get(nodeName, metav1.GetOptions{ResourceVersion: "0"}) firstTry = false } else { - oldNode, err = c.Core().Nodes().Get(nodeName, metav1.GetOptions{}) + oldNode, err = c.CoreV1().Nodes().Get(nodeName, metav1.GetOptions{}) } if err != nil { return err @@ -1030,7 +1008,7 @@ func PatchNodeTaints(c clientset.Interface, nodeName string, oldNode *v1.Node, n return fmt.Errorf("failed to create patch for node %q: %v", nodeName, err) } - _, err = c.Core().Nodes().Patch(string(nodeName), types.StrategicMergePatchType, patchBytes) + _, err = c.CoreV1().Nodes().Patch(nodeName, types.StrategicMergePatchType, patchBytes) return err } diff --git a/pkg/controller/controller_utils_test.go b/pkg/controller/controller_utils_test.go index 0a7797dad41..f3599ba6d7f 100644 --- a/pkg/controller/controller_utils_test.go +++ b/pkg/controller/controller_utils_test.go @@ -41,9 +41,9 @@ import ( "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" utiltesting "k8s.io/client-go/util/testing" - "k8s.io/kubernetes/pkg/api" - _ "k8s.io/kubernetes/pkg/api/install" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/testapi" + _ "k8s.io/kubernetes/pkg/apis/core/install" "k8s.io/kubernetes/pkg/controller/testutil" "k8s.io/kubernetes/pkg/securitycontext" @@ -62,7 +62,7 @@ func NewFakeControllerExpectationsLookup(ttl time.Duration) (*ControllerExpectat func newReplicationController(replicas int) *v1.ReplicationController { rc := &v1.ReplicationController{ - TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, + TypeMeta: metav1.TypeMeta{APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ UID: uuid.NewUUID(), Name: "foobar", @@ -124,7 +124,7 @@ func newPodList(store cache.Store, count int, status v1.PodPhase, rc *v1.Replica func newReplicaSet(name string, replicas int) *extensions.ReplicaSet { return &extensions.ReplicaSet{ - TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, + TypeMeta: metav1.TypeMeta{APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ UID: uuid.NewUUID(), Name: name, @@ -286,7 +286,7 @@ func TestCreatePods(t *testing.T) { } testServer := httptest.NewServer(&fakeHandler) defer testServer.Close() - clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) podControl := RealPodControl{ KubeClient: clientset, @@ -439,10 +439,6 @@ func TestActiveReplicaSetsFiltering(t *testing.T) { "expected %v, got %v", expectedNames.List(), gotNames.List()) } -func int64P(num int64) *int64 { - return &num -} - func TestComputeHash(t *testing.T) { collisionCount := int32(1) otherCollisionCount := int32(2) diff --git a/pkg/controller/cronjob/BUILD b/pkg/controller/cronjob/BUILD index 17d82782d61..d1fee235557 100644 --- a/pkg/controller/cronjob/BUILD +++ b/pkg/controller/cronjob/BUILD @@ -16,7 +16,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/controller/cronjob", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/util/metrics:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/robfig/cron:go_default_library", @@ -46,11 +46,11 @@ go_test( "cronjob_controller_test.go", "utils_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/cronjob", - library = ":go_default_library", deps = [ - "//pkg/api/install:go_default_library", "//pkg/apis/batch/install:go_default_library", + "//pkg/apis/core/install:go_default_library", "//vendor/k8s.io/api/batch/v1:go_default_library", "//vendor/k8s.io/api/batch/v1beta1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/controller/cronjob/cronjob_controller.go b/pkg/controller/cronjob/cronjob_controller.go index 682ae721a4a..679895e85df 100644 --- a/pkg/controller/cronjob/cronjob_controller.go +++ b/pkg/controller/cronjob/cronjob_controller.go @@ -66,14 +66,16 @@ type CronJobController struct { recorder record.EventRecorder } -func NewCronJobController(kubeClient clientset.Interface) *CronJobController { +func NewCronJobController(kubeClient clientset.Interface) (*CronJobController, error) { eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(glog.Infof) // TODO: remove the wrapper when every clients have moved to use the clientset. - eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.Core().RESTClient()).Events("")}) + eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.CoreV1().RESTClient()).Events("")}) if kubeClient != nil && kubeClient.CoreV1().RESTClient().GetRateLimiter() != nil { - metrics.RegisterMetricAndTrackRateLimiterUsage("cronjob_controller", kubeClient.Core().RESTClient().GetRateLimiter()) + if err := metrics.RegisterMetricAndTrackRateLimiterUsage("cronjob_controller", kubeClient.CoreV1().RESTClient().GetRateLimiter()); err != nil { + return nil, err + } } jm := &CronJobController{ @@ -84,12 +86,15 @@ func NewCronJobController(kubeClient clientset.Interface) *CronJobController { recorder: eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "cronjob-controller"}), } - return jm + return jm, nil } -func NewCronJobControllerFromClient(kubeClient clientset.Interface) *CronJobController { - jm := NewCronJobController(kubeClient) - return jm +func NewCronJobControllerFromClient(kubeClient clientset.Interface) (*CronJobController, error) { + jm, err := NewCronJobController(kubeClient) + if err != nil { + return nil, err + } + return jm, nil } // Run the main goroutine responsible for watching and syncing jobs. diff --git a/pkg/controller/cronjob/cronjob_controller_test.go b/pkg/controller/cronjob/cronjob_controller_test.go index bc08af54e74..63cff8ef049 100644 --- a/pkg/controller/cronjob/cronjob_controller_test.go +++ b/pkg/controller/cronjob/cronjob_controller_test.go @@ -31,8 +31,8 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/tools/record" // For the cronjob controller to do conversions. - _ "k8s.io/kubernetes/pkg/api/install" _ "k8s.io/kubernetes/pkg/apis/batch/install" + _ "k8s.io/kubernetes/pkg/apis/core/install" ) var ( diff --git a/pkg/controller/cronjob/utils.go b/pkg/controller/cronjob/utils.go index 5909659def1..d3286d99ced 100644 --- a/pkg/controller/cronjob/utils.go +++ b/pkg/controller/cronjob/utils.go @@ -31,7 +31,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" ref "k8s.io/client-go/tools/reference" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" ) // Utilities for dealing with Jobs and CronJobs and time. @@ -176,11 +176,6 @@ func getJobFromTemplate(sj *batchv1beta1.CronJob, scheduledTime time.Time) (*bat // scheduled-job-name=$SJ_NAME -- for user convenience labels := copyLabels(&sj.Spec.JobTemplate) annotations := copyAnnotations(&sj.Spec.JobTemplate) - createdByRefJson, err := makeCreatedByRefJson(sj) - if err != nil { - return nil, err - } - annotations[v1.CreatedByAnnotation] = string(createdByRefJson) // We want job names for a given nominal start time to have a deterministic name to avoid the same job being created twice name := fmt.Sprintf("%s-%d", sj.Name, getTimeHash(scheduledTime)) @@ -192,7 +187,7 @@ func getJobFromTemplate(sj *batchv1beta1.CronJob, scheduledTime time.Time) (*bat OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(sj, controllerKind)}, }, } - if err := api.Scheme.Convert(&sj.Spec.JobTemplate.Spec, &job.Spec, nil); err != nil { + if err := legacyscheme.Scheme.Convert(&sj.Spec.JobTemplate.Spec, &job.Spec, nil); err != nil { return nil, fmt.Errorf("unable to convert job template: %v", err) } return job, nil @@ -205,7 +200,7 @@ func getTimeHash(scheduledTime time.Time) int64 { // makeCreatedByRefJson makes a json string with an object reference for use in "created-by" annotation value func makeCreatedByRefJson(object runtime.Object) (string, error) { - createdByRef, err := ref.GetReference(api.Scheme, object) + createdByRef, err := ref.GetReference(legacyscheme.Scheme, object) if err != nil { return "", fmt.Errorf("unable to get controller reference: %v", err) } @@ -213,7 +208,7 @@ func makeCreatedByRefJson(object runtime.Object) (string, error) { // TODO: this code was not safe previously - as soon as new code came along that switched to v2, old clients // would be broken upon reading it. This is explicitly hardcoded to v1 to guarantee predictable deployment. // We need to consistently handle this case of annotation versioning. - codec := api.Codecs.LegacyCodec(schema.GroupVersion{Group: v1.GroupName, Version: "v1"}) + codec := legacyscheme.Codecs.LegacyCodec(schema.GroupVersion{Group: v1.GroupName, Version: "v1"}) createdByRefJson, err := runtime.Encode(codec, &v1.SerializedReference{ Reference: *createdByRef, diff --git a/pkg/controller/cronjob/utils_test.go b/pkg/controller/cronjob/utils_test.go index dbb1512121e..fb6b569f2e6 100644 --- a/pkg/controller/cronjob/utils_test.go +++ b/pkg/controller/cronjob/utils_test.go @@ -83,21 +83,9 @@ func TestGetJobFromTemplate(t *testing.T) { if len(job.ObjectMeta.Labels) != 1 { t.Errorf("Wrong number of labels") } - if len(job.ObjectMeta.Annotations) != 2 { + if len(job.ObjectMeta.Annotations) != 1 { t.Errorf("Wrong number of annotations") } - v, ok := job.ObjectMeta.Annotations[v1.CreatedByAnnotation] - if !ok { - t.Errorf("Missing created-by annotation") - } - expectedCreatedBy := `{"kind":"SerializedReference","apiVersion":"v1","reference":{"kind":"CronJob","namespace":"snazzycats","name":"mycronjob","uid":"1a2b3c","apiVersion":"batch"}} -` - if len(v) != len(expectedCreatedBy) { - t.Errorf("Wrong length for created-by annotation, expected %v got %v", len(expectedCreatedBy), len(v)) - } - if v != expectedCreatedBy { - t.Errorf("Wrong value for created-by annotation, expected %v got %v", expectedCreatedBy, v) - } } func TestGetParentUIDFromJob(t *testing.T) { diff --git a/pkg/controller/daemon/BUILD b/pkg/controller/daemon/BUILD index 9a76e7658b8..761262f4108 100644 --- a/pkg/controller/daemon/BUILD +++ b/pkg/controller/daemon/BUILD @@ -15,17 +15,17 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/controller/daemon", deps = [ - "//pkg/api/v1/helper:go_default_library", "//pkg/api/v1/pod:go_default_library", + "//pkg/apis/core/v1/helper:go_default_library", "//pkg/controller:go_default_library", "//pkg/controller/daemon/util:go_default_library", "//pkg/features:go_default_library", "//pkg/kubelet/types:go_default_library", + "//pkg/scheduler/algorithm:go_default_library", + "//pkg/scheduler/algorithm/predicates:go_default_library", + "//pkg/scheduler/schedulercache:go_default_library", "//pkg/util/labels:go_default_library", "//pkg/util/metrics:go_default_library", - "//plugin/pkg/scheduler/algorithm:go_default_library", - "//plugin/pkg/scheduler/algorithm/predicates:go_default_library", - "//plugin/pkg/scheduler/schedulercache:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/apps/v1beta1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", @@ -65,17 +65,18 @@ go_test( "daemon_controller_test.go", "update_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/daemon", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/testapi:go_default_library", "//pkg/api/v1/pod:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/controller:go_default_library", "//pkg/kubelet/types:go_default_library", + "//pkg/scheduler/algorithm:go_default_library", "//pkg/securitycontext:go_default_library", "//pkg/util/labels:go_default_library", - "//plugin/pkg/scheduler/algorithm:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", diff --git a/pkg/controller/daemon/OWNERS b/pkg/controller/daemon/OWNERS index bb110c20dd1..d83d4dd2878 100755 --- a/pkg/controller/daemon/OWNERS +++ b/pkg/controller/daemon/OWNERS @@ -5,3 +5,4 @@ reviewers: - lukaszo - mikedanese - tnozicka +- k82cn diff --git a/pkg/controller/daemon/daemon_controller.go b/pkg/controller/daemon/daemon_controller.go index 0b0328eee82..c316c52a933 100644 --- a/pkg/controller/daemon/daemon_controller.go +++ b/pkg/controller/daemon/daemon_controller.go @@ -48,16 +48,16 @@ import ( "k8s.io/client-go/tools/record" "k8s.io/client-go/util/integer" "k8s.io/client-go/util/workqueue" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" podutil "k8s.io/kubernetes/pkg/api/v1/pod" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller/daemon/util" "k8s.io/kubernetes/pkg/features" kubelettypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + "k8s.io/kubernetes/pkg/scheduler/algorithm/predicates" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" "k8s.io/kubernetes/pkg/util/metrics" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" "github.com/golang/glog" ) @@ -130,14 +130,16 @@ type DaemonSetsController struct { suspendedDaemonPods map[string]sets.String } -func NewDaemonSetsController(daemonSetInformer extensionsinformers.DaemonSetInformer, historyInformer appsinformers.ControllerRevisionInformer, podInformer coreinformers.PodInformer, nodeInformer coreinformers.NodeInformer, kubeClient clientset.Interface) *DaemonSetsController { +func NewDaemonSetsController(daemonSetInformer extensionsinformers.DaemonSetInformer, historyInformer appsinformers.ControllerRevisionInformer, podInformer coreinformers.PodInformer, nodeInformer coreinformers.NodeInformer, kubeClient clientset.Interface) (*DaemonSetsController, error) { eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(glog.Infof) // TODO: remove the wrapper when every clients have moved to use the clientset. - eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.Core().RESTClient()).Events("")}) + eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.CoreV1().RESTClient()).Events("")}) - if kubeClient != nil && kubeClient.Core().RESTClient().GetRateLimiter() != nil { - metrics.RegisterMetricAndTrackRateLimiterUsage("daemon_controller", kubeClient.Core().RESTClient().GetRateLimiter()) + if kubeClient != nil && kubeClient.CoreV1().RESTClient().GetRateLimiter() != nil { + if err := metrics.RegisterMetricAndTrackRateLimiterUsage("daemon_controller", kubeClient.CoreV1().RESTClient().GetRateLimiter()); err != nil { + return nil, err + } } dsc := &DaemonSetsController{ kubeClient: kubeClient, @@ -201,7 +203,7 @@ func NewDaemonSetsController(daemonSetInformer extensionsinformers.DaemonSetInfo dsc.syncHandler = dsc.syncDaemonSet dsc.enqueueDaemonSet = dsc.enqueue dsc.enqueueDaemonSetRateLimited = dsc.enqueueRateLimited - return dsc + return dsc, nil } func (dsc *DaemonSetsController) deleteDaemonset(obj interface{}) { @@ -298,8 +300,8 @@ func (dsc *DaemonSetsController) enqueueDaemonSetAfter(obj interface{}, after ti dsc.queue.AddAfter(key, after) } -// getPodDaemonSets returns a list of DaemonSets that potentially match the pod. -func (dsc *DaemonSetsController) getPodDaemonSets(pod *v1.Pod) []*extensions.DaemonSet { +// getDaemonSetsForPod returns a list of DaemonSets that potentially match the pod. +func (dsc *DaemonSetsController) getDaemonSetsForPod(pod *v1.Pod) []*extensions.DaemonSet { sets, err := dsc.dsLister.GetPodDaemonSets(pod) if err != nil { return nil @@ -362,8 +364,8 @@ func (dsc *DaemonSetsController) addHistory(obj interface{}) { } // updateHistory figures out what DaemonSet(s) manage a ControllerRevision when the ControllerRevision -// is updated and wake them up. If the anything of the ControllerRevision have changed, we need to -// awaken both the old and new DaemonSets. +// is updated and wake them up. If anything of the ControllerRevision has changed, we need to awaken +// both the old and new DaemonSets. func (dsc *DaemonSetsController) updateHistory(old, cur interface{}) { curHistory := cur.(*apps.ControllerRevision) oldHistory := old.(*apps.ControllerRevision) @@ -474,7 +476,7 @@ func (dsc *DaemonSetsController) addPod(obj interface{}) { // them to see if anyone wants to adopt it. // DO NOT observe creation because no controller should be waiting for an // orphan. - dss := dsc.getPodDaemonSets(pod) + dss := dsc.getDaemonSetsForPod(pod) if len(dss) == 0 { return } @@ -495,8 +497,6 @@ func (dsc *DaemonSetsController) updatePod(old, cur interface{}) { // Two different versions of the same pod will always have different RVs. return } - changedToReady := !podutil.IsPodReady(oldPod) && podutil.IsPodReady(curPod) - labelChanged := !reflect.DeepEqual(curPod.Labels, oldPod.Labels) curControllerRef := metav1.GetControllerOf(curPod) oldControllerRef := metav1.GetControllerOf(oldPod) @@ -516,6 +516,7 @@ func (dsc *DaemonSetsController) updatePod(old, cur interface{}) { } glog.V(4).Infof("Pod %s updated.", curPod.Name) dsc.enqueueDaemonSet(ds) + changedToReady := !podutil.IsPodReady(oldPod) && podutil.IsPodReady(curPod) // See https://github.com/kubernetes/kubernetes/pull/38076 for more details if changedToReady && ds.Spec.MinReadySeconds > 0 { // Add a second to avoid milliseconds skew in AddAfter. @@ -527,11 +528,12 @@ func (dsc *DaemonSetsController) updatePod(old, cur interface{}) { // Otherwise, it's an orphan. If anything changed, sync matching controllers // to see if anyone wants to adopt it now. - dss := dsc.getPodDaemonSets(curPod) + dss := dsc.getDaemonSetsForPod(curPod) if len(dss) == 0 { return } glog.V(4).Infof("Orphan Pod %s updated.", curPod.Name) + labelChanged := !reflect.DeepEqual(curPod.Labels, oldPod.Labels) if labelChanged || controllerRefChanged { for _, ds := range dss { dsc.enqueueDaemonSet(ds) @@ -707,7 +709,7 @@ func (dsc *DaemonSetsController) updateNode(old, cur interface{}) { dsList, err := dsc.dsLister.List(labels.Everything()) if err != nil { - glog.V(4).Infof("Error enqueueing daemon sets: %v", err) + glog.V(4).Infof("Error listing daemon sets: %v", err) return } // TODO: it'd be nice to pass a hint with these enqueues, so that each ds would only examine the added node (unless it has other work to do, too). @@ -799,6 +801,10 @@ func (dsc *DaemonSetsController) resolveControllerRef(namespace string, controll return ds } +// manage manages the scheduling and running of Pods of ds on nodes. +// After figuring out which nodes should run a Pod of ds but not yet running one and +// which nodes should not run a Pod of ds but currently running one, it calls function +// syncNodes with a list of pods to remove and a list of nodes to run a Pod of ds. func (dsc *DaemonSetsController) manage(ds *extensions.DaemonSet, hash string) error { // Find out which nodes are running the daemon pods controlled by ds. nodeToDaemonPods, err := dsc.getNodesToDaemonPods(ds) @@ -1065,7 +1071,7 @@ func (dsc *DaemonSetsController) updateDaemonSetStatus(ds *extensions.DaemonSet, } numberUnavailable := desiredNumberScheduled - numberAvailable - err = storeDaemonSetStatus(dsc.kubeClient.Extensions().DaemonSets(ds.Namespace), ds, desiredNumberScheduled, currentNumberScheduled, numberMisscheduled, numberReady, updatedNumberScheduled, numberAvailable, numberUnavailable) + err = storeDaemonSetStatus(dsc.kubeClient.ExtensionsV1beta1().DaemonSets(ds.Namespace), ds, desiredNumberScheduled, currentNumberScheduled, numberMisscheduled, numberReady, updatedNumberScheduled, numberAvailable, numberUnavailable) if err != nil { return fmt.Errorf("error storing status for daemon set %#v: %v", ds, err) } diff --git a/pkg/controller/daemon/daemon_controller_test.go b/pkg/controller/daemon/daemon_controller_test.go index 5a64914cf7c..cd5c4f07376 100644 --- a/pkg/controller/daemon/daemon_controller_test.go +++ b/pkg/controller/daemon/daemon_controller_test.go @@ -39,14 +39,15 @@ import ( "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/testapi" podutil "k8s.io/kubernetes/pkg/api/v1/pod" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/controller" kubelettypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/pkg/scheduler/algorithm" "k8s.io/kubernetes/pkg/securitycontext" labelsutil "k8s.io/kubernetes/pkg/util/labels" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" ) var ( @@ -146,7 +147,7 @@ func updateStrategies() []*extensions.DaemonSetUpdateStrategy { func newNode(name string, label map[string]string) *v1.Node { return &v1.Node{ - TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, + TypeMeta: metav1.TypeMeta{APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ Name: name, Labels: label, @@ -196,7 +197,7 @@ func newPod(podName string, nodeName string, label map[string]string, ds *extens } pod := &v1.Pod{ - TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, + TypeMeta: metav1.TypeMeta{APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ GenerateName: podName, Labels: newLabels, @@ -255,7 +256,7 @@ func (f *fakePodControl) CreatePodsOnNode(nodeName, namespace string, template * }, } - if err := api.Scheme.Convert(&template.Spec, &pod.Spec, nil); err != nil { + if err := legacyscheme.Scheme.Convert(&template.Spec, &pod.Spec, nil); err != nil { return fmt.Errorf("unable to convert pod template: %v", err) } if len(nodeName) != 0 { @@ -293,17 +294,20 @@ type daemonSetsController struct { fakeRecorder *record.FakeRecorder } -func newTestController(initialObjects ...runtime.Object) (*daemonSetsController, *fakePodControl, *fake.Clientset) { +func newTestController(initialObjects ...runtime.Object) (*daemonSetsController, *fakePodControl, *fake.Clientset, error) { clientset := fake.NewSimpleClientset(initialObjects...) informerFactory := informers.NewSharedInformerFactory(clientset, controller.NoResyncPeriodFunc()) - dsc := NewDaemonSetsController( + dsc, err := NewDaemonSetsController( informerFactory.Extensions().V1beta1().DaemonSets(), informerFactory.Apps().V1beta1().ControllerRevisions(), informerFactory.Core().V1().Pods(), informerFactory.Core().V1().Nodes(), clientset, ) + if err != nil { + return nil, nil, nil, err + } fakeRecorder := record.NewFakeRecorder(100) dsc.eventRecorder = fakeRecorder @@ -323,7 +327,7 @@ func newTestController(initialObjects ...runtime.Object) (*daemonSetsController, informerFactory.Core().V1().Pods().Informer().GetStore(), informerFactory.Core().V1().Nodes().Informer().GetStore(), fakeRecorder, - }, podControl, clientset + }, podControl, clientset, nil } func validateSyncDaemonSets(t *testing.T, manager *daemonSetsController, fakePodControl *fakePodControl, expectedCreates, expectedDeletes int, expectedEvents int) { @@ -377,7 +381,10 @@ func clearExpectations(t *testing.T, manager *daemonSetsController, ds *extensio func TestDeleteFinalStateUnknown(t *testing.T) { for _, strategy := range updateStrategies() { - manager, _, _ := newTestController() + manager, _, _, err := newTestController() + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } addNodes(manager.nodeStore, 0, 1, nil) ds := newDaemonSet("foo") ds.Spec.UpdateStrategy = *strategy @@ -408,7 +415,10 @@ func TestSimpleDaemonSetLaunchesPods(t *testing.T) { for _, strategy := range updateStrategies() { ds := newDaemonSet("foo") ds.Spec.UpdateStrategy = *strategy - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } addNodes(manager.nodeStore, 0, 5, nil) manager.dsStore.Add(ds) syncAndValidateDaemonSets(t, manager, ds, podControl, 5, 0, 0) @@ -421,7 +431,10 @@ func TestSimpleDaemonSetPodCreateErrors(t *testing.T) { for _, strategy := range updateStrategies() { ds := newDaemonSet("foo") ds.Spec.UpdateStrategy = *strategy - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } podControl.FakePodControl.CreateLimit = 10 addNodes(manager.nodeStore, 0, podControl.FakePodControl.CreateLimit*10, nil) manager.dsStore.Add(ds) @@ -441,7 +454,10 @@ func TestSimpleDaemonSetUpdatesStatusAfterLaunchingPods(t *testing.T) { for _, strategy := range updateStrategies() { ds := newDaemonSet("foo") ds.Spec.UpdateStrategy = *strategy - manager, podControl, clientset := newTestController(ds) + manager, podControl, clientset, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } var updated *extensions.DaemonSet clientset.PrependReactor("update", "daemonsets", func(action core.Action) (handled bool, ret runtime.Object, err error) { @@ -469,7 +485,10 @@ func TestSimpleDaemonSetUpdatesStatusAfterLaunchingPods(t *testing.T) { // DaemonSets should do nothing if there aren't any nodes func TestNoNodesDoesNothing(t *testing.T) { for _, strategy := range updateStrategies() { - manager, podControl, _ := newTestController() + manager, podControl, _, err := newTestController() + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } ds := newDaemonSet("foo") ds.Spec.UpdateStrategy = *strategy manager.dsStore.Add(ds) @@ -483,7 +502,10 @@ func TestOneNodeDaemonLaunchesPod(t *testing.T) { for _, strategy := range updateStrategies() { ds := newDaemonSet("foo") ds.Spec.UpdateStrategy = *strategy - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } manager.nodeStore.Add(newNode("only-node", nil)) manager.dsStore.Add(ds) syncAndValidateDaemonSets(t, manager, ds, podControl, 1, 0, 0) @@ -491,11 +513,14 @@ func TestOneNodeDaemonLaunchesPod(t *testing.T) { } // DaemonSets should place onto NotReady nodes -func TestNotReadNodeDaemonDoesNotLaunchPod(t *testing.T) { +func TestNotReadyNodeDaemonDoesLaunchPod(t *testing.T) { for _, strategy := range updateStrategies() { ds := newDaemonSet("foo") ds.Spec.UpdateStrategy = *strategy - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } node := newNode("not-ready", nil) node.Status.Conditions = []v1.NodeCondition{ {Type: v1.NodeReady, Status: v1.ConditionFalse}, @@ -517,6 +542,12 @@ func resourcePodSpec(nodeName, memory, cpu string) v1.PodSpec { } } +func resourceContainerSpec(memory, cpu string) v1.ResourceRequirements { + return v1.ResourceRequirements{ + Requests: allocatableResources(memory, cpu), + } +} + func resourcePodSpecWithoutNodeName(memory, cpu string) v1.PodSpec { return v1.PodSpec{ Containers: []v1.Container{{ @@ -542,7 +573,10 @@ func TestInsufficientCapacityNodeDaemonDoesNotLaunchPod(t *testing.T) { ds := newDaemonSet("foo") ds.Spec.UpdateStrategy = *strategy ds.Spec.Template.Spec = podSpec - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } node := newNode("too-much-mem", nil) node.Status.Allocatable = allocatableResources("100M", "200m") manager.nodeStore.Add(node) @@ -569,7 +603,10 @@ func TestInsufficientCapacityNodeDaemonDoesNotUnscheduleRunningPod(t *testing.T) ds := newDaemonSet("foo") ds.Spec.UpdateStrategy = *strategy ds.Spec.Template.Spec = podSpec - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } node := newNode("too-much-mem", nil) node.Status.Allocatable = allocatableResources("100M", "200m") manager.nodeStore.Add(node) @@ -594,7 +631,10 @@ func TestInsufficientCapacityNodeSufficientCapacityWithNodeLabelDaemonLaunchPod( ds := newDaemonSet("foo") ds.Spec.Template.Spec = podSpec ds.Spec.Template.Spec.NodeSelector = simpleNodeLabel - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } node1 := newNode("not-enough-resource", nil) node1.Status.Allocatable = allocatableResources("10M", "20m") node2 := newNode("enough-resource", simpleNodeLabel) @@ -615,7 +655,10 @@ func TestSufficientCapacityWithTerminatedPodsDaemonLaunchesPod(t *testing.T) { ds := newDaemonSet("foo") ds.Spec.UpdateStrategy = *strategy ds.Spec.Template.Spec = podSpec - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } node := newNode("too-much-mem", nil) node.Status.Allocatable = allocatableResources("100M", "200m") manager.nodeStore.Add(node) @@ -635,7 +678,10 @@ func TestSufficientCapacityNodeDaemonLaunchesPod(t *testing.T) { ds := newDaemonSet("foo") ds.Spec.UpdateStrategy = *strategy ds.Spec.Template.Spec = podSpec - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } node := newNode("not-too-much-mem", nil) node.Status.Allocatable = allocatableResources("200M", "200m") manager.nodeStore.Add(node) @@ -652,7 +698,10 @@ func TestNetworkUnavailableNodeDaemonLaunchesPod(t *testing.T) { for _, strategy := range updateStrategies() { ds := newDaemonSet("simple") ds.Spec.UpdateStrategy = *strategy - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } node := newNode("network-unavailable", nil) node.Status.Conditions = []v1.NodeCondition{ @@ -674,7 +723,10 @@ func TestDontDoAnythingIfBeingDeleted(t *testing.T) { ds.Spec.Template.Spec = podSpec now := metav1.Now() ds.DeletionTimestamp = &now - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } node := newNode("not-too-much-mem", nil) node.Status.Allocatable = allocatableResources("200M", "200m") manager.nodeStore.Add(node) @@ -693,7 +745,10 @@ func TestDontDoAnythingIfBeingDeletedRace(t *testing.T) { ds.Spec.UpdateStrategy = *strategy now := metav1.Now() ds.DeletionTimestamp = &now - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } addNodes(manager.nodeStore, 0, 5, nil) // Lister (cache) says it's NOT deleted. @@ -720,7 +775,10 @@ func TestPortConflictNodeDaemonDoesNotLaunchPod(t *testing.T) { }}, }}, } - manager, podControl, _ := newTestController() + manager, podControl, _, err := newTestController() + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } node := newNode("port-conflict", nil) manager.nodeStore.Add(node) manager.podStore.Add(&v1.Pod{ @@ -749,7 +807,10 @@ func TestPortConflictWithSameDaemonPodDoesNotDeletePod(t *testing.T) { }}, }}, } - manager, podControl, _ := newTestController() + manager, podControl, _, err := newTestController() + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } node := newNode("port-conflict", nil) manager.nodeStore.Add(node) ds := newDaemonSet("foo") @@ -784,7 +845,10 @@ func TestNoPortConflictNodeDaemonLaunchesPod(t *testing.T) { ds := newDaemonSet("foo") ds.Spec.UpdateStrategy = *strategy ds.Spec.Template.Spec = podSpec2 - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } node := newNode("no-port-conflict", nil) manager.nodeStore.Add(node) manager.podStore.Add(&v1.Pod{ @@ -815,7 +879,10 @@ func TestPodIsNotDeletedByDaemonsetWithEmptyLabelSelector(t *testing.T) { ds.Spec.Selector = &ls ds.Spec.Template.Spec.NodeSelector = map[string]string{"foo": "bar"} - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } manager.nodeStore.Add(newNode("node1", nil)) // Create pod not controlled by a daemonset. manager.podStore.Add(&v1.Pod{ @@ -838,7 +905,10 @@ func TestDealsWithExistingPods(t *testing.T) { for _, strategy := range updateStrategies() { ds := newDaemonSet("foo") ds.Spec.UpdateStrategy = *strategy - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } manager.dsStore.Add(ds) addNodes(manager.nodeStore, 0, 5, nil) addPods(manager.podStore, "node-1", simpleDaemonSetLabel, ds, 1) @@ -855,7 +925,10 @@ func TestSelectorDaemonLaunchesPods(t *testing.T) { daemon := newDaemonSet("foo") daemon.Spec.UpdateStrategy = *strategy daemon.Spec.Template.Spec.NodeSelector = simpleNodeLabel - manager, podControl, _ := newTestController(daemon) + manager, podControl, _, err := newTestController(daemon) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } addNodes(manager.nodeStore, 0, 4, nil) addNodes(manager.nodeStore, 4, 3, simpleNodeLabel) manager.dsStore.Add(daemon) @@ -869,7 +942,10 @@ func TestSelectorDaemonDeletesUnselectedPods(t *testing.T) { ds := newDaemonSet("foo") ds.Spec.UpdateStrategy = *strategy ds.Spec.Template.Spec.NodeSelector = simpleNodeLabel - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } manager.dsStore.Add(ds) addNodes(manager.nodeStore, 0, 5, nil) addNodes(manager.nodeStore, 5, 5, simpleNodeLabel) @@ -887,7 +963,10 @@ func TestSelectorDaemonDealsWithExistingPods(t *testing.T) { ds := newDaemonSet("foo") ds.Spec.UpdateStrategy = *strategy ds.Spec.Template.Spec.NodeSelector = simpleNodeLabel - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } manager.dsStore.Add(ds) addNodes(manager.nodeStore, 0, 5, nil) addNodes(manager.nodeStore, 5, 5, simpleNodeLabel) @@ -906,7 +985,10 @@ func TestSelectorDaemonDealsWithExistingPods(t *testing.T) { // DaemonSet with node selector which does not match any node labels should not launch pods. func TestBadSelectorDaemonDoesNothing(t *testing.T) { for _, strategy := range updateStrategies() { - manager, podControl, _ := newTestController() + manager, podControl, _, err := newTestController() + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } addNodes(manager.nodeStore, 0, 4, nil) addNodes(manager.nodeStore, 4, 3, simpleNodeLabel) ds := newDaemonSet("foo") @@ -923,7 +1005,10 @@ func TestNameDaemonSetLaunchesPods(t *testing.T) { ds := newDaemonSet("foo") ds.Spec.UpdateStrategy = *strategy ds.Spec.Template.Spec.NodeName = "node-0" - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } addNodes(manager.nodeStore, 0, 5, nil) manager.dsStore.Add(ds) syncAndValidateDaemonSets(t, manager, ds, podControl, 1, 0, 0) @@ -936,7 +1021,10 @@ func TestBadNameDaemonSetDoesNothing(t *testing.T) { ds := newDaemonSet("foo") ds.Spec.UpdateStrategy = *strategy ds.Spec.Template.Spec.NodeName = "node-10" - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } addNodes(manager.nodeStore, 0, 5, nil) manager.dsStore.Add(ds) syncAndValidateDaemonSets(t, manager, ds, podControl, 0, 0, 0) @@ -950,7 +1038,10 @@ func TestNameAndSelectorDaemonSetLaunchesPods(t *testing.T) { ds.Spec.UpdateStrategy = *strategy ds.Spec.Template.Spec.NodeSelector = simpleNodeLabel ds.Spec.Template.Spec.NodeName = "node-6" - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } addNodes(manager.nodeStore, 0, 4, nil) addNodes(manager.nodeStore, 4, 3, simpleNodeLabel) manager.dsStore.Add(ds) @@ -965,7 +1056,10 @@ func TestInconsistentNameSelectorDaemonSetDoesNothing(t *testing.T) { ds.Spec.UpdateStrategy = *strategy ds.Spec.Template.Spec.NodeSelector = simpleNodeLabel ds.Spec.Template.Spec.NodeName = "node-0" - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } addNodes(manager.nodeStore, 0, 4, nil) addNodes(manager.nodeStore, 4, 3, simpleNodeLabel) manager.dsStore.Add(ds) @@ -977,7 +1071,10 @@ func TestInconsistentNameSelectorDaemonSetDoesNothing(t *testing.T) { func TestSelectorDaemonSetLaunchesPods(t *testing.T) { ds := newDaemonSet("foo") ds.Spec.Template.Spec.NodeSelector = simpleNodeLabel - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } addNodes(manager.nodeStore, 0, 4, nil) addNodes(manager.nodeStore, 4, 3, simpleNodeLabel) manager.dsStore.Add(ds) @@ -1007,7 +1104,10 @@ func TestNodeAffinityDaemonLaunchesPods(t *testing.T) { }, } - manager, podControl, _ := newTestController(daemon) + manager, podControl, _, err := newTestController(daemon) + if err != nil { + t.Fatalf("rrror creating DaemonSetsController: %v", err) + } addNodes(manager.nodeStore, 0, 4, nil) addNodes(manager.nodeStore, 4, 3, simpleNodeLabel) manager.dsStore.Add(daemon) @@ -1019,7 +1119,10 @@ func TestNumberReadyStatus(t *testing.T) { for _, strategy := range updateStrategies() { ds := newDaemonSet("foo") ds.Spec.UpdateStrategy = *strategy - manager, podControl, clientset := newTestController(ds) + manager, podControl, clientset, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } var updated *extensions.DaemonSet clientset.PrependReactor("update", "daemonsets", func(action core.Action) (handled bool, ret runtime.Object, err error) { if action.GetSubresource() != "status" { @@ -1059,7 +1162,10 @@ func TestObservedGeneration(t *testing.T) { ds := newDaemonSet("foo") ds.Spec.UpdateStrategy = *strategy ds.Generation = 1 - manager, podControl, clientset := newTestController(ds) + manager, podControl, clientset, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } var updated *extensions.DaemonSet clientset.PrependReactor("update", "daemonsets", func(action core.Action) (handled bool, ret runtime.Object, err error) { if action.GetSubresource() != "status" { @@ -1100,7 +1206,10 @@ func TestDaemonKillFailedPods(t *testing.T) { for _, strategy := range updateStrategies() { ds := newDaemonSet("foo") ds.Spec.UpdateStrategy = *strategy - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } manager.dsStore.Add(ds) addNodes(manager.nodeStore, 0, 1, nil) addFailedPods(manager.podStore, "node-0", simpleDaemonSetLabel, ds, test.numFailedPods) @@ -1116,7 +1225,10 @@ func TestNoScheduleTaintedDoesntEvicitRunningIntolerantPod(t *testing.T) { for _, strategy := range updateStrategies() { ds := newDaemonSet("intolerant") ds.Spec.UpdateStrategy = *strategy - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } node := newNode("tainted", nil) manager.nodeStore.Add(node) @@ -1134,7 +1246,10 @@ func TestNoExecuteTaintedDoesEvicitRunningIntolerantPod(t *testing.T) { for _, strategy := range updateStrategies() { ds := newDaemonSet("intolerant") ds.Spec.UpdateStrategy = *strategy - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } node := newNode("tainted", nil) manager.nodeStore.Add(node) @@ -1151,7 +1266,10 @@ func TestTaintedNodeDaemonDoesNotLaunchIntolerantPod(t *testing.T) { for _, strategy := range updateStrategies() { ds := newDaemonSet("intolerant") ds.Spec.UpdateStrategy = *strategy - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } node := newNode("tainted", nil) setNodeTaint(node, noScheduleTaints) @@ -1168,7 +1286,10 @@ func TestTaintedNodeDaemonLaunchesToleratePod(t *testing.T) { ds := newDaemonSet("tolerate") ds.Spec.UpdateStrategy = *strategy setDaemonSetToleration(ds, noScheduleTolerations) - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } node := newNode("tainted", nil) setNodeTaint(node, noScheduleTaints) @@ -1184,7 +1305,10 @@ func TestNotReadyNodeDaemonLaunchesPod(t *testing.T) { for _, strategy := range updateStrategies() { ds := newDaemonSet("simple") ds.Spec.UpdateStrategy = *strategy - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } node := newNode("tainted", nil) setNodeTaint(node, nodeNotReady) @@ -1203,7 +1327,10 @@ func TestUnreachableNodeDaemonLaunchesPod(t *testing.T) { for _, strategy := range updateStrategies() { ds := newDaemonSet("simple") ds.Spec.UpdateStrategy = *strategy - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } node := newNode("tainted", nil) setNodeTaint(node, nodeUnreachable) @@ -1223,7 +1350,10 @@ func TestNodeDaemonLaunchesToleratePod(t *testing.T) { ds := newDaemonSet("tolerate") ds.Spec.UpdateStrategy = *strategy setDaemonSetToleration(ds, noScheduleTolerations) - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } addNodes(manager.nodeStore, 0, 1, nil) manager.dsStore.Add(ds) @@ -1236,7 +1366,10 @@ func TestDaemonSetRespectsTermination(t *testing.T) { for _, strategy := range updateStrategies() { ds := newDaemonSet("foo") ds.Spec.UpdateStrategy = *strategy - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } addNodes(manager.nodeStore, 0, 1, simpleNodeLabel) pod := newPod(fmt.Sprintf("%s-", "node-0"), "node-0", simpleDaemonSetLabel, ds) @@ -1263,7 +1396,10 @@ func TestTaintOutOfDiskNodeDaemonLaunchesCriticalPod(t *testing.T) { ds := newDaemonSet("critical") ds.Spec.UpdateStrategy = *strategy setDaemonSetCritical(ds) - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } node := newNode("not-enough-disk", nil) node.Status.Conditions = []v1.NodeCondition{{Type: v1.NodeOutOfDisk, Status: v1.ConditionTrue}} @@ -1296,7 +1432,10 @@ func TestTaintPressureNodeDaemonLaunchesPod(t *testing.T) { ds := newDaemonSet("critical") ds.Spec.UpdateStrategy = *strategy setDaemonSetCritical(ds) - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } node := newNode("resources-pressure", nil) node.Status.Conditions = []v1.NodeCondition{ @@ -1328,7 +1467,10 @@ func TestInsufficientCapacityNodeDaemonLaunchesCriticalPod(t *testing.T) { ds.Spec.Template.Spec = podSpec setDaemonSetCritical(ds) - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } node := newNode("too-much-mem", nil) node.Status.Allocatable = allocatableResources("100M", "200m") manager.nodeStore.Add(node) @@ -1372,7 +1514,10 @@ func TestPortConflictNodeDaemonDoesNotLaunchCriticalPod(t *testing.T) { }}, }}, } - manager, podControl, _ := newTestController() + manager, podControl, _, err := newTestController() + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } node := newNode("port-conflict", nil) manager.nodeStore.Add(node) manager.podStore.Add(&v1.Pod{ @@ -1399,6 +1544,7 @@ func setDaemonSetCritical(ds *extensions.DaemonSet) { func TestNodeShouldRunDaemonPod(t *testing.T) { cases := []struct { + predicateName string podsOnNode []*v1.Pod nodeCondition []v1.NodeCondition ds *extensions.DaemonSet @@ -1406,6 +1552,7 @@ func TestNodeShouldRunDaemonPod(t *testing.T) { err error }{ { + predicateName: "ShouldRunDaemonPod", ds: &extensions.DaemonSet{ Spec: extensions.DaemonSetSpec{ Selector: &metav1.LabelSelector{MatchLabels: simpleDaemonSetLabel}, @@ -1422,6 +1569,7 @@ func TestNodeShouldRunDaemonPod(t *testing.T) { shouldContinueRunning: true, }, { + predicateName: "InsufficientResourceError", ds: &extensions.DaemonSet{ Spec: extensions.DaemonSetSpec{ Selector: &metav1.LabelSelector{MatchLabels: simpleDaemonSetLabel}, @@ -1438,6 +1586,7 @@ func TestNodeShouldRunDaemonPod(t *testing.T) { shouldContinueRunning: true, }, { + predicateName: "ErrPodNotMatchHostName", ds: &extensions.DaemonSet{ Spec: extensions.DaemonSetSpec{ Selector: &metav1.LabelSelector{MatchLabels: simpleDaemonSetLabel}, @@ -1454,6 +1603,7 @@ func TestNodeShouldRunDaemonPod(t *testing.T) { shouldContinueRunning: false, }, { + predicateName: "ErrPodNotFitsHostPorts", podsOnNode: []*v1.Pod{ { Spec: v1.PodSpec{ @@ -1486,14 +1636,183 @@ func TestNodeShouldRunDaemonPod(t *testing.T) { shouldSchedule: false, shouldContinueRunning: false, }, + { + predicateName: "InsufficientResourceError", + podsOnNode: []*v1.Pod{ + { + Spec: v1.PodSpec{ + Containers: []v1.Container{{ + Ports: []v1.ContainerPort{{ + HostPort: 666, + }}, + Resources: resourceContainerSpec("50M", "0.5"), + }}, + }, + }, + }, + ds: &extensions.DaemonSet{ + Spec: extensions.DaemonSetSpec{ + Selector: &metav1.LabelSelector{MatchLabels: simpleDaemonSetLabel}, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: simpleDaemonSetLabel, + }, + Spec: resourcePodSpec("", "100M", "0.5"), + }, + }, + }, + wantToRun: true, + shouldSchedule: false, + shouldContinueRunning: true, + }, + { + predicateName: "ShouldRunDaemonPod", + podsOnNode: []*v1.Pod{ + { + Spec: v1.PodSpec{ + Containers: []v1.Container{{ + Ports: []v1.ContainerPort{{ + HostPort: 666, + }}, + Resources: resourceContainerSpec("50M", "0.5"), + }}, + }, + }, + }, + ds: &extensions.DaemonSet{ + Spec: extensions.DaemonSetSpec{ + Selector: &metav1.LabelSelector{MatchLabels: simpleDaemonSetLabel}, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: simpleDaemonSetLabel, + }, + Spec: resourcePodSpec("", "50M", "0.5"), + }, + }, + }, + wantToRun: true, + shouldSchedule: true, + shouldContinueRunning: true, + }, + { + predicateName: "ErrNodeSelectorNotMatch", + ds: &extensions.DaemonSet{ + Spec: extensions.DaemonSetSpec{ + Selector: &metav1.LabelSelector{MatchLabels: simpleDaemonSetLabel}, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: simpleDaemonSetLabel, + }, + Spec: v1.PodSpec{ + NodeSelector: simpleDaemonSetLabel2, + }, + }, + }, + }, + wantToRun: false, + shouldSchedule: false, + shouldContinueRunning: false, + }, + { + predicateName: "ShouldRunDaemonPod", + ds: &extensions.DaemonSet{ + Spec: extensions.DaemonSetSpec{ + Selector: &metav1.LabelSelector{MatchLabels: simpleDaemonSetLabel}, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: simpleDaemonSetLabel, + }, + Spec: v1.PodSpec{ + NodeSelector: simpleDaemonSetLabel, + }, + }, + }, + }, + wantToRun: true, + shouldSchedule: true, + shouldContinueRunning: true, + }, + { + predicateName: "ErrPodAffinityNotMatch", + ds: &extensions.DaemonSet{ + Spec: extensions.DaemonSetSpec{ + Selector: &metav1.LabelSelector{MatchLabels: simpleDaemonSetLabel}, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: simpleDaemonSetLabel, + }, + Spec: v1.PodSpec{ + Affinity: &v1.Affinity{ + NodeAffinity: &v1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "type", + Operator: v1.NodeSelectorOpIn, + Values: []string{"test"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + wantToRun: false, + shouldSchedule: false, + shouldContinueRunning: false, + }, + { + predicateName: "ShouldRunDaemonPod", + ds: &extensions.DaemonSet{ + Spec: extensions.DaemonSetSpec{ + Selector: &metav1.LabelSelector{MatchLabels: simpleDaemonSetLabel}, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: simpleDaemonSetLabel, + }, + Spec: v1.PodSpec{ + Affinity: &v1.Affinity{ + NodeAffinity: &v1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "type", + Operator: v1.NodeSelectorOpIn, + Values: []string{"production"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + wantToRun: true, + shouldSchedule: true, + shouldContinueRunning: true, + }, } for i, c := range cases { for _, strategy := range updateStrategies() { - node := newNode("test-node", nil) + node := newNode("test-node", simpleDaemonSetLabel) node.Status.Conditions = append(node.Status.Conditions, c.nodeCondition...) node.Status.Allocatable = allocatableResources("100M", "1") - manager, _, _ := newTestController() + manager, _, _, err := newTestController() + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } manager.nodeStore.Add(node) for _, p := range c.podsOnNode { manager.podStore.Add(p) @@ -1503,16 +1822,16 @@ func TestNodeShouldRunDaemonPod(t *testing.T) { wantToRun, shouldSchedule, shouldContinueRunning, err := manager.nodeShouldRunDaemonPod(node, c.ds) if wantToRun != c.wantToRun { - t.Errorf("[%v] expected wantToRun: %v, got: %v", i, c.wantToRun, wantToRun) + t.Errorf("[%v] strategy: %v, predicateName: %v expected wantToRun: %v, got: %v", i, c.ds.Spec.UpdateStrategy.Type, c.predicateName, c.wantToRun, wantToRun) } if shouldSchedule != c.shouldSchedule { - t.Errorf("[%v] expected shouldSchedule: %v, got: %v", i, c.shouldSchedule, shouldSchedule) + t.Errorf("[%v] strategy: %v, predicateName: %v expected shouldSchedule: %v, got: %v", i, c.ds.Spec.UpdateStrategy.Type, c.predicateName, c.shouldSchedule, shouldSchedule) } if shouldContinueRunning != c.shouldContinueRunning { - t.Errorf("[%v] expected shouldContinueRunning: %v, got: %v", i, c.shouldContinueRunning, shouldContinueRunning) + t.Errorf("[%v] strategy: %v, predicateName: %v expected shouldContinueRunning: %v, got: %v", i, c.ds.Spec.UpdateStrategy.Type, c.predicateName, c.shouldContinueRunning, shouldContinueRunning) } if err != c.err { - t.Errorf("[%v] expected err: %v, got: %v", i, c.err, err) + t.Errorf("[%v] strategy: %v, predicateName: %v expected err: %v, got: %v", i, c.predicateName, c.ds.Spec.UpdateStrategy.Type, c.err, err) } } } @@ -1565,7 +1884,10 @@ func TestUpdateNode(t *testing.T) { } for _, c := range cases { for _, strategy := range updateStrategies() { - manager, podControl, _ := newTestController() + manager, podControl, _, err := newTestController() + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } manager.nodeStore.Add(c.oldNode) c.ds.Spec.UpdateStrategy = *strategy manager.dsStore.Add(c.ds) @@ -1728,7 +2050,10 @@ func TestDeleteNoDaemonPod(t *testing.T) { for _, c := range cases { for _, strategy := range updateStrategies() { - manager, podControl, _ := newTestController() + manager, podControl, _, err := newTestController() + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } manager.nodeStore.Add(c.node) c.ds.Spec.UpdateStrategy = *strategy manager.dsStore.Add(c.ds) @@ -1765,7 +2090,10 @@ func TestGetNodesToDaemonPods(t *testing.T) { ds.Spec.UpdateStrategy = *strategy ds2 := newDaemonSet("foo2") ds2.Spec.UpdateStrategy = *strategy - manager, _, _ := newTestController(ds, ds2) + manager, _, _, err := newTestController(ds, ds2) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } manager.dsStore.Add(ds) manager.dsStore.Add(ds2) addNodes(manager.nodeStore, 0, 2, nil) @@ -1820,7 +2148,10 @@ func TestGetNodesToDaemonPods(t *testing.T) { } func TestAddNode(t *testing.T) { - manager, _, _ := newTestController() + manager, _, _, err := newTestController() + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } node1 := newNode("node1", nil) ds := newDaemonSet("ds") ds.Spec.Template.Spec.NodeSelector = simpleNodeLabel @@ -1844,7 +2175,10 @@ func TestAddNode(t *testing.T) { func TestAddPod(t *testing.T) { for _, strategy := range updateStrategies() { - manager, _, _ := newTestController() + manager, _, _, err := newTestController() + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } ds1 := newDaemonSet("foo1") ds1.Spec.UpdateStrategy = *strategy ds2 := newDaemonSet("foo2") @@ -1884,7 +2218,10 @@ func TestAddPod(t *testing.T) { func TestAddPodOrphan(t *testing.T) { for _, strategy := range updateStrategies() { - manager, _, _ := newTestController() + manager, _, _, err := newTestController() + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } ds1 := newDaemonSet("foo1") ds1.Spec.UpdateStrategy = *strategy ds2 := newDaemonSet("foo2") @@ -1910,7 +2247,10 @@ func TestAddPodOrphan(t *testing.T) { func TestUpdatePod(t *testing.T) { for _, strategy := range updateStrategies() { - manager, _, _ := newTestController() + manager, _, _, err := newTestController() + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } ds1 := newDaemonSet("foo1") ds1.Spec.UpdateStrategy = *strategy ds2 := newDaemonSet("foo2") @@ -1954,7 +2294,10 @@ func TestUpdatePod(t *testing.T) { func TestUpdatePodOrphanSameLabels(t *testing.T) { for _, strategy := range updateStrategies() { - manager, _, _ := newTestController() + manager, _, _, err := newTestController() + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } ds1 := newDaemonSet("foo1") ds1.Spec.UpdateStrategy = *strategy ds2 := newDaemonSet("foo2") @@ -1974,7 +2317,10 @@ func TestUpdatePodOrphanSameLabels(t *testing.T) { func TestUpdatePodOrphanWithNewLabels(t *testing.T) { for _, strategy := range updateStrategies() { - manager, _, _ := newTestController() + manager, _, _, err := newTestController() + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } ds1 := newDaemonSet("foo1") ds1.Spec.UpdateStrategy = *strategy ds2 := newDaemonSet("foo2") @@ -2000,7 +2346,10 @@ func TestUpdatePodChangeControllerRef(t *testing.T) { for _, strategy := range updateStrategies() { ds := newDaemonSet("foo") ds.Spec.UpdateStrategy = *strategy - manager, _, _ := newTestController() + manager, _, _, err := newTestController() + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } ds1 := newDaemonSet("foo1") ds2 := newDaemonSet("foo2") manager.dsStore.Add(ds1) @@ -2019,7 +2368,10 @@ func TestUpdatePodChangeControllerRef(t *testing.T) { func TestUpdatePodControllerRefRemoved(t *testing.T) { for _, strategy := range updateStrategies() { - manager, _, _ := newTestController() + manager, _, _, err := newTestController() + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } ds1 := newDaemonSet("foo1") ds1.Spec.UpdateStrategy = *strategy ds2 := newDaemonSet("foo2") @@ -2040,7 +2392,10 @@ func TestUpdatePodControllerRefRemoved(t *testing.T) { func TestDeletePod(t *testing.T) { for _, strategy := range updateStrategies() { - manager, _, _ := newTestController() + manager, _, _, err := newTestController() + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } ds1 := newDaemonSet("foo1") ds1.Spec.UpdateStrategy = *strategy ds2 := newDaemonSet("foo2") @@ -2080,7 +2435,10 @@ func TestDeletePod(t *testing.T) { func TestDeletePodOrphan(t *testing.T) { for _, strategy := range updateStrategies() { - manager, _, _ := newTestController() + manager, _, _, err := newTestController() + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } ds1 := newDaemonSet("foo1") ds1.Spec.UpdateStrategy = *strategy ds2 := newDaemonSet("foo2") diff --git a/pkg/controller/daemon/update.go b/pkg/controller/daemon/update.go index 1ed7f3f0f5e..bbc9015a28a 100644 --- a/pkg/controller/daemon/update.go +++ b/pkg/controller/daemon/update.go @@ -228,7 +228,7 @@ func (dsc *DaemonSetsController) dedupCurHistories(ds *extensions.DaemonSet, cur toUpdate.Labels = make(map[string]string) } toUpdate.Labels[extensions.DefaultDaemonSetUniqueLabelKey] = keepCur.Labels[extensions.DefaultDaemonSetUniqueLabelKey] - _, err = dsc.kubeClient.Core().Pods(ds.Namespace).Update(toUpdate) + _, err = dsc.kubeClient.CoreV1().Pods(ds.Namespace).Update(toUpdate) if err != nil { return nil, err } diff --git a/pkg/controller/daemon/update_test.go b/pkg/controller/daemon/update_test.go index 1585f8e4e9c..0521bb576bf 100644 --- a/pkg/controller/daemon/update_test.go +++ b/pkg/controller/daemon/update_test.go @@ -27,7 +27,10 @@ import ( func TestDaemonSetUpdatesPods(t *testing.T) { ds := newDaemonSet("foo") - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } maxUnavailable := 2 addNodes(manager.nodeStore, 0, 5, nil) manager.dsStore.Add(ds) @@ -66,7 +69,10 @@ func TestDaemonSetUpdatesPods(t *testing.T) { func TestDaemonSetUpdatesWhenNewPosIsNotReady(t *testing.T) { ds := newDaemonSet("foo") - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } maxUnavailable := 3 addNodes(manager.nodeStore, 0, 5, nil) manager.dsStore.Add(ds) @@ -93,7 +99,10 @@ func TestDaemonSetUpdatesWhenNewPosIsNotReady(t *testing.T) { func TestDaemonSetUpdatesAllOldPodsNotReady(t *testing.T) { ds := newDaemonSet("foo") - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } maxUnavailable := 3 addNodes(manager.nodeStore, 0, 5, nil) manager.dsStore.Add(ds) @@ -119,7 +128,10 @@ func TestDaemonSetUpdatesAllOldPodsNotReady(t *testing.T) { func TestDaemonSetUpdatesNoTemplateChanged(t *testing.T) { ds := newDaemonSet("foo") - manager, podControl, _ := newTestController(ds) + manager, podControl, _, err := newTestController(ds) + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } maxUnavailable := 3 addNodes(manager.nodeStore, 0, 5, nil) manager.dsStore.Add(ds) @@ -149,7 +161,10 @@ func TestGetUnavailableNumbers(t *testing.T) { { name: "No nodes", Manager: func() *daemonSetsController { - manager, _, _ := newTestController() + manager, _, _, err := newTestController() + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } return manager }(), ds: func() *extensions.DaemonSet { @@ -165,7 +180,10 @@ func TestGetUnavailableNumbers(t *testing.T) { { name: "Two nodes with ready pods", Manager: func() *daemonSetsController { - manager, _, _ := newTestController() + manager, _, _, err := newTestController() + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } addNodes(manager.nodeStore, 0, 2, nil) return manager }(), @@ -191,7 +209,10 @@ func TestGetUnavailableNumbers(t *testing.T) { { name: "Two nodes, one node without pods", Manager: func() *daemonSetsController { - manager, _, _ := newTestController() + manager, _, _, err := newTestController() + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } addNodes(manager.nodeStore, 0, 2, nil) return manager }(), @@ -214,7 +235,10 @@ func TestGetUnavailableNumbers(t *testing.T) { { name: "Two nodes with pods, MaxUnavailable in percents", Manager: func() *daemonSetsController { - manager, _, _ := newTestController() + manager, _, _, err := newTestController() + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } addNodes(manager.nodeStore, 0, 2, nil) return manager }(), @@ -240,7 +264,10 @@ func TestGetUnavailableNumbers(t *testing.T) { { name: "Two nodes with pods, MaxUnavailable in percents, pod terminating", Manager: func() *daemonSetsController { - manager, _, _ := newTestController() + manager, _, _, err := newTestController() + if err != nil { + t.Fatalf("error creating DaemonSets controller: %v", err) + } addNodes(manager.nodeStore, 0, 2, nil) return manager }(), diff --git a/pkg/controller/daemon/util/BUILD b/pkg/controller/daemon/util/BUILD index 72743c30219..bb9f1f11cf4 100644 --- a/pkg/controller/daemon/util/BUILD +++ b/pkg/controller/daemon/util/BUILD @@ -11,12 +11,12 @@ go_library( srcs = ["daemonset_util.go"], importpath = "k8s.io/kubernetes/pkg/controller/daemon/util", deps = [ - "//pkg/api/v1/helper:go_default_library", "//pkg/api/v1/pod:go_default_library", + "//pkg/apis/core/v1/helper:go_default_library", "//pkg/features:go_default_library", "//pkg/kubelet/types:go_default_library", + "//pkg/scheduler/algorithm:go_default_library", "//pkg/util/labels:go_default_library", - "//plugin/pkg/scheduler/algorithm:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -40,8 +40,8 @@ filegroup( go_test( name = "go_default_test", srcs = ["daemonset_util_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/daemon/util", - library = ":go_default_library", deps = [ "//pkg/api/testapi:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/controller/daemon/util/daemonset_util.go b/pkg/controller/daemon/util/daemonset_util.go index 6ef70a3fac5..3c69e8a3a0b 100644 --- a/pkg/controller/daemon/util/daemonset_util.go +++ b/pkg/controller/daemon/util/daemonset_util.go @@ -23,12 +23,12 @@ import ( extensions "k8s.io/api/extensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilfeature "k8s.io/apiserver/pkg/util/feature" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" podutil "k8s.io/kubernetes/pkg/api/v1/pod" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" "k8s.io/kubernetes/pkg/features" kubelettypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/pkg/scheduler/algorithm" labelsutil "k8s.io/kubernetes/pkg/util/labels" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" ) // CreatePodTemplate returns copy of provided template with additional diff --git a/pkg/controller/deployment/BUILD b/pkg/controller/deployment/BUILD index ca75a1f1171..41b1c163df3 100644 --- a/pkg/controller/deployment/BUILD +++ b/pkg/controller/deployment/BUILD @@ -56,17 +56,17 @@ go_test( "rolling_test.go", "sync_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/deployment", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/apps/install:go_default_library", "//pkg/apis/authentication/install:go_default_library", "//pkg/apis/authorization/install:go_default_library", "//pkg/apis/autoscaling/install:go_default_library", "//pkg/apis/batch/install:go_default_library", "//pkg/apis/certificates/install:go_default_library", + "//pkg/apis/core/install:go_default_library", "//pkg/apis/extensions/install:go_default_library", "//pkg/apis/policy/install:go_default_library", "//pkg/apis/rbac/install:go_default_library", diff --git a/pkg/controller/deployment/deployment_controller.go b/pkg/controller/deployment/deployment_controller.go index d3d5c7d4258..0b3461f30d9 100644 --- a/pkg/controller/deployment/deployment_controller.go +++ b/pkg/controller/deployment/deployment_controller.go @@ -97,14 +97,16 @@ type DeploymentController struct { } // NewDeploymentController creates a new DeploymentController. -func NewDeploymentController(dInformer extensionsinformers.DeploymentInformer, rsInformer extensionsinformers.ReplicaSetInformer, podInformer coreinformers.PodInformer, client clientset.Interface) *DeploymentController { +func NewDeploymentController(dInformer extensionsinformers.DeploymentInformer, rsInformer extensionsinformers.ReplicaSetInformer, podInformer coreinformers.PodInformer, client clientset.Interface) (*DeploymentController, error) { eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(glog.Infof) // TODO: remove the wrapper when every clients have moved to use the clientset. - eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(client.Core().RESTClient()).Events("")}) + eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(client.CoreV1().RESTClient()).Events("")}) - if client != nil && client.Core().RESTClient().GetRateLimiter() != nil { - metrics.RegisterMetricAndTrackRateLimiterUsage("deployment_controller", client.Core().RESTClient().GetRateLimiter()) + if client != nil && client.CoreV1().RESTClient().GetRateLimiter() != nil { + if err := metrics.RegisterMetricAndTrackRateLimiterUsage("deployment_controller", client.CoreV1().RESTClient().GetRateLimiter()); err != nil { + return nil, err + } } dc := &DeploymentController{ client: client, @@ -140,7 +142,7 @@ func NewDeploymentController(dInformer extensionsinformers.DeploymentInformer, r dc.dListerSynced = dInformer.Informer().HasSynced dc.rsListerSynced = rsInformer.Informer().HasSynced dc.podListerSynced = podInformer.Informer().HasSynced - return dc + return dc, nil } // Run begins watching and syncing. @@ -585,7 +587,7 @@ func (dc *DeploymentController) syncDeployment(key string) error { dc.eventRecorder.Eventf(d, v1.EventTypeWarning, "SelectingAll", "This deployment is selecting all pods. A non-empty selector is required.") if d.Status.ObservedGeneration < d.Generation { d.Status.ObservedGeneration = d.Generation - dc.client.Extensions().Deployments(d.Namespace).UpdateStatus(d) + dc.client.ExtensionsV1beta1().Deployments(d.Namespace).UpdateStatus(d) } return nil } diff --git a/pkg/controller/deployment/deployment_controller_test.go b/pkg/controller/deployment/deployment_controller_test.go index 6fbc0de699f..7b4ed1432b2 100644 --- a/pkg/controller/deployment/deployment_controller_test.go +++ b/pkg/controller/deployment/deployment_controller_test.go @@ -31,14 +31,14 @@ import ( "k8s.io/client-go/kubernetes/fake" core "k8s.io/client-go/testing" "k8s.io/client-go/tools/record" - "k8s.io/kubernetes/pkg/api" - _ "k8s.io/kubernetes/pkg/api/install" + "k8s.io/kubernetes/pkg/api/legacyscheme" _ "k8s.io/kubernetes/pkg/apis/apps/install" _ "k8s.io/kubernetes/pkg/apis/authentication/install" _ "k8s.io/kubernetes/pkg/apis/authorization/install" _ "k8s.io/kubernetes/pkg/apis/autoscaling/install" _ "k8s.io/kubernetes/pkg/apis/batch/install" _ "k8s.io/kubernetes/pkg/apis/certificates/install" + _ "k8s.io/kubernetes/pkg/apis/core/install" _ "k8s.io/kubernetes/pkg/apis/extensions/install" _ "k8s.io/kubernetes/pkg/apis/policy/install" _ "k8s.io/kubernetes/pkg/apis/rbac/install" @@ -78,7 +78,7 @@ func newRSWithStatus(name string, specReplicas, statusReplicas int, selector map func newDeployment(name string, replicas int, revisionHistoryLimit *int32, maxSurge, maxUnavailable *intstr.IntOrString, selector map[string]string) *extensions.Deployment { d := extensions.Deployment{ - TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(extensions.GroupName).GroupVersion.String()}, + TypeMeta: metav1.TypeMeta{APIVersion: legacyscheme.Registry.GroupOrDie(extensions.GroupName).GroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ UID: uuid.NewUUID(), Name: name, @@ -187,10 +187,13 @@ func newFixture(t *testing.T) *fixture { return f } -func (f *fixture) newController() (*DeploymentController, informers.SharedInformerFactory) { +func (f *fixture) newController() (*DeploymentController, informers.SharedInformerFactory, error) { f.client = fake.NewSimpleClientset(f.objects...) informers := informers.NewSharedInformerFactory(f.client, controller.NoResyncPeriodFunc()) - c := NewDeploymentController(informers.Extensions().V1beta1().Deployments(), informers.Extensions().V1beta1().ReplicaSets(), informers.Core().V1().Pods(), f.client) + c, err := NewDeploymentController(informers.Extensions().V1beta1().Deployments(), informers.Extensions().V1beta1().ReplicaSets(), informers.Core().V1().Pods(), f.client) + if err != nil { + return nil, nil, err + } c.eventRecorder = &record.FakeRecorder{} c.dListerSynced = alwaysReady c.rsListerSynced = alwaysReady @@ -204,7 +207,7 @@ func (f *fixture) newController() (*DeploymentController, informers.SharedInform for _, pod := range f.podLister { informers.Core().V1().Pods().Informer().GetIndexer().Add(pod) } - return c, informers + return c, informers, nil } func (f *fixture) runExpectError(deploymentName string, startInformers bool) { @@ -216,14 +219,17 @@ func (f *fixture) run(deploymentName string) { } func (f *fixture) run_(deploymentName string, startInformers bool, expectError bool) { - c, informers := f.newController() + c, informers, err := f.newController() + if err != nil { + f.t.Fatalf("error creating Deployment controller: %v", err) + } if startInformers { stopCh := make(chan struct{}) defer close(stopCh) informers.Start(stopCh) } - err := c.syncDeployment(deploymentName) + err = c.syncDeployment(deploymentName) if !expectError && err != nil { f.t.Errorf("error syncing deployment: %v", err) } else if expectError && err == nil { @@ -378,7 +384,10 @@ func TestPodDeletionEnqueuesRecreateDeployment(t *testing.T) { f.rsLister = append(f.rsLister, rs) f.objects = append(f.objects, foo, rs) - c, _ := f.newController() + c, _, err := f.newController() + if err != nil { + t.Fatalf("error creating Deployment controller: %v", err) + } enqueued := false c.enqueueDeployment = func(d *extensions.Deployment) { if d.Name == "foo" { @@ -411,7 +420,10 @@ func TestPodDeletionDoesntEnqueueRecreateDeployment(t *testing.T) { // return a non-empty list. f.podLister = append(f.podLister, pod1, pod2) - c, _ := f.newController() + c, _, err := f.newController() + if err != nil { + t.Fatalf("error creating Deployment controller: %v", err) + } enqueued := false c.enqueueDeployment = func(d *extensions.Deployment) { if d.Name == "foo" { @@ -444,7 +456,10 @@ func TestPodDeletionPartialReplicaSetOwnershipEnqueueRecreateDeployment(t *testi f.rsLister = append(f.rsLister, rs1, rs2) f.objects = append(f.objects, foo, rs1, rs2) - c, _ := f.newController() + c, _, err := f.newController() + if err != nil { + t.Fatalf("error creating Deployment controller: %v", err) + } enqueued := false c.enqueueDeployment = func(d *extensions.Deployment) { if d.Name == "foo" { @@ -480,7 +495,10 @@ func TestPodDeletionPartialReplicaSetOwnershipDoesntEnqueueRecreateDeployment(t // return a non-empty list. f.podLister = append(f.podLister, pod) - c, _ := f.newController() + c, _, err := f.newController() + if err != nil { + t.Fatalf("error creating Deployment controller: %v", err) + } enqueued := false c.enqueueDeployment = func(d *extensions.Deployment) { if d.Name == "foo" { @@ -512,7 +530,10 @@ func TestGetReplicaSetsForDeployment(t *testing.T) { f.objects = append(f.objects, d1, d2, rs1, rs2) // Start the fixture. - c, informers := f.newController() + c, informers, err := f.newController() + if err != nil { + t.Fatalf("error creating Deployment controller: %v", err) + } stopCh := make(chan struct{}) defer close(stopCh) informers.Start(stopCh) @@ -559,7 +580,10 @@ func TestGetReplicaSetsForDeploymentAdoptRelease(t *testing.T) { f.objects = append(f.objects, d, rsAdopt, rsRelease) // Start the fixture. - c, informers := f.newController() + c, informers, err := f.newController() + if err != nil { + t.Fatalf("error creating Deployment controller: %v", err) + } stopCh := make(chan struct{}) defer close(stopCh) informers.Start(stopCh) @@ -603,7 +627,10 @@ func TestGetPodMapForReplicaSets(t *testing.T) { f.objects = append(f.objects, d, rs1, rs2, pod1, pod2, pod3, pod4) // Start the fixture. - c, informers := f.newController() + c, informers, err := f.newController() + if err != nil { + t.Fatalf("error creating Deployment controller: %v", err) + } stopCh := make(chan struct{}) defer close(stopCh) informers.Start(stopCh) @@ -656,7 +683,10 @@ func TestAddReplicaSet(t *testing.T) { // Create the fixture but don't start it, // so nothing happens in the background. - dc, _ := f.newController() + dc, _, err := f.newController() + if err != nil { + t.Fatalf("error creating Deployment controller: %v", err) + } dc.addReplicaSet(rs1) if got, want := dc.queue.Len(), 1; got != want { @@ -703,7 +733,10 @@ func TestAddReplicaSetOrphan(t *testing.T) { // Create the fixture but don't start it, // so nothing happens in the background. - dc, _ := f.newController() + dc, _, err := f.newController() + if err != nil { + t.Fatalf("error creating Deployment controller: %v", err) + } dc.addReplicaSet(rs) if got, want := dc.queue.Len(), 2; got != want { @@ -728,7 +761,10 @@ func TestUpdateReplicaSet(t *testing.T) { // Create the fixture but don't start it, // so nothing happens in the background. - dc, _ := f.newController() + dc, _, err := f.newController() + if err != nil { + t.Fatalf("error creating Deployment controller: %v", err) + } prev := *rs1 next := *rs1 @@ -779,7 +815,10 @@ func TestUpdateReplicaSetOrphanWithNewLabels(t *testing.T) { // Create the fixture but don't start it, // so nothing happens in the background. - dc, _ := f.newController() + dc, _, err := f.newController() + if err != nil { + t.Fatalf("error creating Deployment controller: %v", err) + } // Change labels and expect all matching controllers to queue. prev := *rs @@ -806,7 +845,10 @@ func TestUpdateReplicaSetChangeControllerRef(t *testing.T) { // Create the fixture but don't start it, // so nothing happens in the background. - dc, _ := f.newController() + dc, _, err := f.newController() + if err != nil { + t.Fatalf("error creating Deployment controller: %v", err) + } // Change ControllerRef and expect both old and new to queue. prev := *rs @@ -833,7 +875,10 @@ func TestUpdateReplicaSetRelease(t *testing.T) { // Create the fixture but don't start it, // so nothing happens in the background. - dc, _ := f.newController() + dc, _, err := f.newController() + if err != nil { + t.Fatalf("error creating Deployment controller: %v", err) + } // Remove ControllerRef and expect all matching controller to sync orphan. prev := *rs @@ -863,7 +908,10 @@ func TestDeleteReplicaSet(t *testing.T) { // Create the fixture but don't start it, // so nothing happens in the background. - dc, _ := f.newController() + dc, _, err := f.newController() + if err != nil { + t.Fatalf("error creating Deployment controller: %v", err) + } dc.deleteReplicaSet(rs1) if got, want := dc.queue.Len(), 1; got != want { @@ -908,7 +956,10 @@ func TestDeleteReplicaSetOrphan(t *testing.T) { // Create the fixture but don't start it, // so nothing happens in the background. - dc, _ := f.newController() + dc, _, err := f.newController() + if err != nil { + t.Fatalf("error creating Deployment controller: %v", err) + } dc.deleteReplicaSet(rs) if got, want := dc.queue.Len(), 0; got != want { diff --git a/pkg/controller/deployment/progress.go b/pkg/controller/deployment/progress.go index 259920cb0a1..1cd25ed1fff 100644 --- a/pkg/controller/deployment/progress.go +++ b/pkg/controller/deployment/progress.go @@ -112,7 +112,7 @@ func (dc *DeploymentController) syncRolloutStatus(allRSs []*extensions.ReplicaSe newDeployment := d newDeployment.Status = newStatus - _, err := dc.client.Extensions().Deployments(newDeployment.Namespace).UpdateStatus(newDeployment) + _, err := dc.client.ExtensionsV1beta1().Deployments(newDeployment.Namespace).UpdateStatus(newDeployment) return err } diff --git a/pkg/controller/deployment/progress_test.go b/pkg/controller/deployment/progress_test.go index 41125e7bc23..9677728e7d0 100644 --- a/pkg/controller/deployment/progress_test.go +++ b/pkg/controller/deployment/progress_test.go @@ -23,6 +23,7 @@ import ( "k8s.io/api/core/v1" extensions "k8s.io/api/extensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/util/workqueue" "k8s.io/kubernetes/pkg/controller/deployment/util" ) @@ -54,6 +55,16 @@ func currentDeployment(pds *int32, replicas, statusReplicas, updatedReplicas, av return d } +// helper to create RS with given availableReplicas +func newRSWithAvailable(name string, specReplicas, statusReplicas, availableReplicas int) *extensions.ReplicaSet { + rs := rs(name, specReplicas, nil, metav1.Time{}) + rs.Status = extensions.ReplicaSetStatus{ + Replicas: int32(statusReplicas), + AvailableReplicas: int32(availableReplicas), + } + return rs +} + func TestRequeueStuckDeployment(t *testing.T) { pds := int32(60) failed := []extensions.DeploymentCondition{ @@ -161,3 +172,170 @@ func TestRequeueStuckDeployment(t *testing.T) { } } } + +func TestSyncRolloutStatus(t *testing.T) { + pds := int32(60) + testTime := metav1.Date(2017, 2, 15, 18, 49, 00, 00, time.UTC) + failedTimedOut := extensions.DeploymentCondition{ + Type: extensions.DeploymentProgressing, + Status: v1.ConditionFalse, + Reason: util.TimedOutReason, + } + newRSAvailable := extensions.DeploymentCondition{ + Type: extensions.DeploymentProgressing, + Status: v1.ConditionTrue, + Reason: util.NewRSAvailableReason, + LastUpdateTime: testTime, + LastTransitionTime: testTime, + } + replicaSetUpdated := extensions.DeploymentCondition{ + Type: extensions.DeploymentProgressing, + Status: v1.ConditionTrue, + Reason: util.ReplicaSetUpdatedReason, + LastUpdateTime: testTime, + LastTransitionTime: testTime, + } + + tests := []struct { + name string + d *extensions.Deployment + allRSs []*extensions.ReplicaSet + newRS *extensions.ReplicaSet + conditionType extensions.DeploymentConditionType + conditionStatus v1.ConditionStatus + conditionReason string + lastUpdate metav1.Time + lastTransition metav1.Time + }{ + { + name: "General: remove Progressing condition and do not estimate progress if deployment has no Progress Deadline", + d: currentDeployment(nil, 3, 2, 2, 2, []extensions.DeploymentCondition{replicaSetUpdated}), + allRSs: []*extensions.ReplicaSet{newRSWithAvailable("bar", 0, 1, 1)}, + newRS: newRSWithAvailable("foo", 3, 2, 2), + }, + { + name: "General: do not estimate progress of deployment with only one active ReplicaSet", + d: currentDeployment(&pds, 3, 3, 3, 3, []extensions.DeploymentCondition{newRSAvailable}), + allRSs: []*extensions.ReplicaSet{newRSWithAvailable("bar", 3, 3, 3)}, + conditionType: extensions.DeploymentProgressing, + conditionStatus: v1.ConditionTrue, + conditionReason: util.NewRSAvailableReason, + lastUpdate: testTime, + lastTransition: testTime, + }, + { + name: "DeploymentProgressing: dont update lastTransitionTime if deployment already has Progressing=True", + d: currentDeployment(&pds, 3, 2, 2, 2, []extensions.DeploymentCondition{replicaSetUpdated}), + allRSs: []*extensions.ReplicaSet{newRSWithAvailable("bar", 0, 1, 1)}, + newRS: newRSWithAvailable("foo", 3, 2, 2), + conditionType: extensions.DeploymentProgressing, + conditionStatus: v1.ConditionTrue, + conditionReason: util.ReplicaSetUpdatedReason, + lastTransition: testTime, + }, + { + name: "DeploymentProgressing: update everything if deployment has Progressing=False", + d: currentDeployment(&pds, 3, 2, 2, 2, []extensions.DeploymentCondition{failedTimedOut}), + allRSs: []*extensions.ReplicaSet{newRSWithAvailable("bar", 0, 1, 1)}, + newRS: newRSWithAvailable("foo", 3, 2, 2), + conditionType: extensions.DeploymentProgressing, + conditionStatus: v1.ConditionTrue, + conditionReason: util.ReplicaSetUpdatedReason, + }, + { + name: "DeploymentProgressing: create Progressing condition if it does not exist", + d: currentDeployment(&pds, 3, 2, 2, 2, []extensions.DeploymentCondition{}), + allRSs: []*extensions.ReplicaSet{newRSWithAvailable("bar", 0, 1, 1)}, + newRS: newRSWithAvailable("foo", 3, 2, 2), + conditionType: extensions.DeploymentProgressing, + conditionStatus: v1.ConditionTrue, + conditionReason: util.ReplicaSetUpdatedReason, + }, + { + name: "DeploymentComplete: dont update lastTransitionTime if deployment already has Progressing=True", + d: currentDeployment(&pds, 3, 3, 3, 3, []extensions.DeploymentCondition{replicaSetUpdated}), + allRSs: []*extensions.ReplicaSet{}, + newRS: newRSWithAvailable("foo", 3, 3, 3), + conditionType: extensions.DeploymentProgressing, + conditionStatus: v1.ConditionTrue, + conditionReason: util.NewRSAvailableReason, + lastTransition: testTime, + }, + { + name: "DeploymentComplete: update everything if deployment has Progressing=False", + d: currentDeployment(&pds, 3, 3, 3, 3, []extensions.DeploymentCondition{failedTimedOut}), + allRSs: []*extensions.ReplicaSet{}, + newRS: newRSWithAvailable("foo", 3, 3, 3), + conditionType: extensions.DeploymentProgressing, + conditionStatus: v1.ConditionTrue, + conditionReason: util.NewRSAvailableReason, + }, + { + name: "DeploymentComplete: create Progressing condition if it does not exist", + d: currentDeployment(&pds, 3, 3, 3, 3, []extensions.DeploymentCondition{}), + allRSs: []*extensions.ReplicaSet{}, + newRS: newRSWithAvailable("foo", 3, 3, 3), + conditionType: extensions.DeploymentProgressing, + conditionStatus: v1.ConditionTrue, + conditionReason: util.NewRSAvailableReason, + }, + { + name: "DeploymentComplete: defend against NPE when newRS=nil", + d: currentDeployment(&pds, 0, 3, 3, 3, []extensions.DeploymentCondition{replicaSetUpdated}), + allRSs: []*extensions.ReplicaSet{newRSWithAvailable("foo", 0, 0, 0)}, + conditionType: extensions.DeploymentProgressing, + conditionStatus: v1.ConditionTrue, + conditionReason: util.NewRSAvailableReason, + }, + { + name: "DeploymentTimedOut: update status if rollout exceeds Progress Deadline", + d: currentDeployment(&pds, 3, 2, 2, 2, []extensions.DeploymentCondition{replicaSetUpdated}), + allRSs: []*extensions.ReplicaSet{}, + newRS: newRSWithAvailable("foo", 3, 2, 2), + conditionType: extensions.DeploymentProgressing, + conditionStatus: v1.ConditionFalse, + conditionReason: util.TimedOutReason, + }, + { + name: "DeploymentTimedOut: do not update status if deployment has existing timedOut condition", + d: currentDeployment(&pds, 3, 2, 2, 2, []extensions.DeploymentCondition{failedTimedOut}), + allRSs: []*extensions.ReplicaSet{}, + newRS: newRSWithAvailable("foo", 3, 2, 2), + conditionType: extensions.DeploymentProgressing, + conditionStatus: v1.ConditionFalse, + conditionReason: util.TimedOutReason, + lastUpdate: testTime, + lastTransition: testTime, + }, + } + + for _, test := range tests { + fake := fake.Clientset{} + dc := &DeploymentController{ + client: &fake, + } + + if test.newRS != nil { + test.allRSs = append(test.allRSs, test.newRS) + } + + err := dc.syncRolloutStatus(test.allRSs, test.newRS, test.d) + if err != nil { + t.Error(err) + } + + newCond := util.GetDeploymentCondition(test.d.Status, test.conditionType) + switch { + case newCond == nil: + if test.d.Spec.ProgressDeadlineSeconds != nil { + t.Errorf("%s: expected deployment condition: %s", test.name, test.conditionType) + } + case newCond.Status != test.conditionStatus || newCond.Reason != test.conditionReason: + t.Errorf("%s: DeploymentProgressing has status %s with reason %s. Expected %s with %s.", test.name, newCond.Status, newCond.Reason, test.conditionStatus, test.conditionReason) + case !test.lastUpdate.IsZero() && test.lastUpdate != testTime: + t.Errorf("%s: LastUpdateTime was changed to %s but expected %s;", test.name, test.lastUpdate, testTime) + case !test.lastTransition.IsZero() && test.lastTransition != testTime: + t.Errorf("%s: LastTransitionTime was changed to %s but expected %s;", test.name, test.lastTransition, testTime) + } + } +} diff --git a/pkg/controller/deployment/recreate_test.go b/pkg/controller/deployment/recreate_test.go index 4dc7d334557..2cf8661780a 100644 --- a/pkg/controller/deployment/recreate_test.go +++ b/pkg/controller/deployment/recreate_test.go @@ -65,7 +65,10 @@ func TestScaleDownOldReplicaSets(t *testing.T) { kc := fake.NewSimpleClientset(expected...) informers := informers.NewSharedInformerFactory(kc, controller.NoResyncPeriodFunc()) - c := NewDeploymentController(informers.Extensions().V1beta1().Deployments(), informers.Extensions().V1beta1().ReplicaSets(), informers.Core().V1().Pods(), kc) + c, err := NewDeploymentController(informers.Extensions().V1beta1().Deployments(), informers.Extensions().V1beta1().ReplicaSets(), informers.Core().V1().Pods(), kc) + if err != nil { + t.Fatalf("error creating Deployment controller: %v", err) + } c.eventRecorder = &record.FakeRecorder{} c.scaleDownOldReplicaSetsForRecreate(oldRSs, test.d) diff --git a/pkg/controller/deployment/rollback.go b/pkg/controller/deployment/rollback.go index 3c1af0bdb94..826185afc33 100644 --- a/pkg/controller/deployment/rollback.go +++ b/pkg/controller/deployment/rollback.go @@ -112,6 +112,6 @@ func (dc *DeploymentController) emitRollbackNormalEvent(d *extensions.Deployment func (dc *DeploymentController) updateDeploymentAndClearRollbackTo(d *extensions.Deployment) error { glog.V(4).Infof("Cleans up rollbackTo of deployment %q", d.Name) d.Spec.RollbackTo = nil - _, err := dc.client.Extensions().Deployments(d.Namespace).Update(d) + _, err := dc.client.ExtensionsV1beta1().Deployments(d.Namespace).Update(d) return err } diff --git a/pkg/controller/deployment/sync.go b/pkg/controller/deployment/sync.go index c208ef18854..497e82cb1d9 100644 --- a/pkg/controller/deployment/sync.go +++ b/pkg/controller/deployment/sync.go @@ -100,7 +100,7 @@ func (dc *DeploymentController) checkPausedConditions(d *extensions.Deployment) } var err error - d, err = dc.client.Extensions().Deployments(d.Namespace).UpdateStatus(d) + d, err = dc.client.ExtensionsV1beta1().Deployments(d.Namespace).UpdateStatus(d) return err } @@ -167,7 +167,7 @@ func (dc *DeploymentController) addHashKeyToRSAndPods(rs *extensions.ReplicaSet, return nil, err } // 1. Add hash template label to the rs. This ensures that any newly created pods will have the new label. - updatedRS, err := deploymentutil.UpdateRSWithRetries(dc.client.Extensions().ReplicaSets(rs.Namespace), dc.rsLister, rs.Namespace, rs.Name, + updatedRS, err := deploymentutil.UpdateRSWithRetries(dc.client.ExtensionsV1beta1().ReplicaSets(rs.Namespace), dc.rsLister, rs.Namespace, rs.Name, func(updated *extensions.ReplicaSet) error { // Precondition: the RS doesn't contain the new hash in its pod template label. if updated.Spec.Template.Labels[extensions.DefaultDeploymentUniqueLabelKey] == hash { @@ -207,7 +207,7 @@ func (dc *DeploymentController) addHashKeyToRSAndPods(rs *extensions.ReplicaSet, // 3. Update rs label and selector to include the new hash label // Copy the old selector, so that we can scrub out any orphaned pods - updatedRS, err = deploymentutil.UpdateRSWithRetries(dc.client.Extensions().ReplicaSets(rs.Namespace), dc.rsLister, rs.Namespace, rs.Name, func(updated *extensions.ReplicaSet) error { + updatedRS, err = deploymentutil.UpdateRSWithRetries(dc.client.ExtensionsV1beta1().ReplicaSets(rs.Namespace), dc.rsLister, rs.Namespace, rs.Name, func(updated *extensions.ReplicaSet) error { // Precondition: the RS doesn't contain the new hash in its label and selector. if updated.Labels[extensions.DefaultDeploymentUniqueLabelKey] == hash && updated.Spec.Selector.MatchLabels[extensions.DefaultDeploymentUniqueLabelKey] == hash { return utilerrors.ErrPreconditionViolated @@ -251,7 +251,7 @@ func (dc *DeploymentController) getNewReplicaSet(d *extensions.Deployment, rsLis minReadySecondsNeedsUpdate := rsCopy.Spec.MinReadySeconds != d.Spec.MinReadySeconds if annotationsUpdated || minReadySecondsNeedsUpdate { rsCopy.Spec.MinReadySeconds = d.Spec.MinReadySeconds - return dc.client.Extensions().ReplicaSets(rsCopy.ObjectMeta.Namespace).Update(rsCopy) + return dc.client.ExtensionsV1beta1().ReplicaSets(rsCopy.ObjectMeta.Namespace).Update(rsCopy) } // Should use the revision in existingNewRS's annotation, since it set by before @@ -269,7 +269,7 @@ func (dc *DeploymentController) getNewReplicaSet(d *extensions.Deployment, rsLis if needsUpdate { var err error - if d, err = dc.client.Extensions().Deployments(d.Namespace).UpdateStatus(d); err != nil { + if d, err = dc.client.ExtensionsV1beta1().Deployments(d.Namespace).UpdateStatus(d); err != nil { return nil, err } } @@ -315,7 +315,7 @@ func (dc *DeploymentController) getNewReplicaSet(d *extensions.Deployment, rsLis // hash collisions. If there is any other error, we need to report it in the status of // the Deployment. alreadyExists := false - createdRS, err := dc.client.Extensions().ReplicaSets(d.Namespace).Create(&newRS) + createdRS, err := dc.client.ExtensionsV1beta1().ReplicaSets(d.Namespace).Create(&newRS) switch { // We may end up hitting this due to a slow cache or a fast resync of the Deployment. // Fetch a copy of the ReplicaSet. If its PodTemplateSpec is semantically deep equal @@ -338,7 +338,7 @@ func (dc *DeploymentController) getNewReplicaSet(d *extensions.Deployment, rsLis *d.Status.CollisionCount++ // Update the collisionCount for the Deployment and let it requeue by returning the original // error. - _, dErr := dc.client.Extensions().Deployments(d.Namespace).UpdateStatus(d) + _, dErr := dc.client.ExtensionsV1beta1().Deployments(d.Namespace).UpdateStatus(d) if dErr == nil { glog.V(2).Infof("Found a hash collision for deployment %q - bumping collisionCount (%d->%d) to resolve it", d.Name, preCollisionCount, *d.Status.CollisionCount) } @@ -355,7 +355,7 @@ func (dc *DeploymentController) getNewReplicaSet(d *extensions.Deployment, rsLis // We don't really care about this error at this point, since we have a bigger issue to report. // TODO: Identify which errors are permanent and switch DeploymentIsFailed to take into account // these reasons as well. Related issue: https://github.com/kubernetes/kubernetes/issues/18568 - _, _ = dc.client.Extensions().Deployments(d.Namespace).UpdateStatus(d) + _, _ = dc.client.ExtensionsV1beta1().Deployments(d.Namespace).UpdateStatus(d) } dc.eventRecorder.Eventf(d, v1.EventTypeWarning, deploymentutil.FailedRSCreateReason, msg) return nil, err @@ -372,7 +372,7 @@ func (dc *DeploymentController) getNewReplicaSet(d *extensions.Deployment, rsLis needsUpdate = true } if needsUpdate { - _, err = dc.client.Extensions().Deployments(d.Namespace).UpdateStatus(d) + _, err = dc.client.ExtensionsV1beta1().Deployments(d.Namespace).UpdateStatus(d) } return createdRS, err } @@ -508,7 +508,7 @@ func (dc *DeploymentController) scaleReplicaSet(rs *extensions.ReplicaSet, newSc var err error if sizeNeedsUpdate || annotationsNeedUpdate { *(rsCopy.Spec.Replicas) = newScale - rs, err = dc.client.Extensions().ReplicaSets(rsCopy.Namespace).Update(rsCopy) + rs, err = dc.client.ExtensionsV1beta1().ReplicaSets(rsCopy.Namespace).Update(rsCopy) if err == nil && sizeNeedsUpdate { scaled = true dc.eventRecorder.Eventf(deployment, v1.EventTypeNormal, "ScalingReplicaSet", "Scaled %s replica set %s to %d", scalingOperation, rs.Name, newScale) @@ -546,7 +546,7 @@ func (dc *DeploymentController) cleanupDeployment(oldRSs []*extensions.ReplicaSe continue } glog.V(4).Infof("Trying to cleanup replica set %q for deployment %q", rs.Name, deployment.Name) - if err := dc.client.Extensions().ReplicaSets(rs.Namespace).Delete(rs.Name, nil); err != nil && !errors.IsNotFound(err) { + if err := dc.client.ExtensionsV1beta1().ReplicaSets(rs.Namespace).Delete(rs.Name, nil); err != nil && !errors.IsNotFound(err) { // Return error instead of aggregating and continuing DELETEs on the theory // that we may be overloading the api server. return err @@ -566,7 +566,7 @@ func (dc *DeploymentController) syncDeploymentStatus(allRSs []*extensions.Replic newDeployment := d newDeployment.Status = newStatus - _, err := dc.client.Extensions().Deployments(newDeployment.Namespace).UpdateStatus(newDeployment) + _, err := dc.client.ExtensionsV1beta1().Deployments(newDeployment.Namespace).UpdateStatus(newDeployment) return err } diff --git a/pkg/controller/deployment/sync_test.go b/pkg/controller/deployment/sync_test.go index 416f2826d74..ce74a3eead6 100644 --- a/pkg/controller/deployment/sync_test.go +++ b/pkg/controller/deployment/sync_test.go @@ -399,7 +399,10 @@ func TestDeploymentController_cleanupDeployment(t *testing.T) { fake := &fake.Clientset{} informers := informers.NewSharedInformerFactory(fake, controller.NoResyncPeriodFunc()) - controller := NewDeploymentController(informers.Extensions().V1beta1().Deployments(), informers.Extensions().V1beta1().ReplicaSets(), informers.Core().V1().Pods(), fake) + controller, err := NewDeploymentController(informers.Extensions().V1beta1().Deployments(), informers.Extensions().V1beta1().ReplicaSets(), informers.Core().V1().Pods(), fake) + if err != nil { + t.Fatalf("error creating Deployment controller: %v", err) + } controller.eventRecorder = &record.FakeRecorder{} controller.dListerSynced = alwaysReady diff --git a/pkg/controller/deployment/util/BUILD b/pkg/controller/deployment/util/BUILD index fd967e837f8..693faa90c1a 100644 --- a/pkg/controller/deployment/util/BUILD +++ b/pkg/controller/deployment/util/BUILD @@ -45,8 +45,8 @@ go_test( "deployment_util_test.go", "hash_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/deployment/util", - library = ":go_default_library", deps = [ "//pkg/controller:go_default_library", "//pkg/util/hash:go_default_library", diff --git a/pkg/controller/deployment/util/deployment_util.go b/pkg/controller/deployment/util/deployment_util.go index e9bd1d6317b..d017e33709e 100644 --- a/pkg/controller/deployment/util/deployment_util.go +++ b/pkg/controller/deployment/util/deployment_util.go @@ -721,7 +721,7 @@ func LabelPodsWithHash(podList *v1.PodList, c clientset.Interface, podLister cor } // Only label the pod that doesn't already have the new hash if pod.Labels[extensions.DefaultDeploymentUniqueLabelKey] != hash { - _, err := UpdatePodWithRetries(c.Core().Pods(namespace), podLister, pod.Namespace, pod.Name, + _, err := UpdatePodWithRetries(c.CoreV1().Pods(namespace), podLister, pod.Namespace, pod.Name, func(podToUpdate *v1.Pod) error { // Precondition: the pod doesn't contain the new hash in its label. if podToUpdate.Labels[extensions.DefaultDeploymentUniqueLabelKey] == hash { diff --git a/pkg/controller/disruption/BUILD b/pkg/controller/disruption/BUILD index aca10cc140e..a8e49825899 100644 --- a/pkg/controller/disruption/BUILD +++ b/pkg/controller/disruption/BUILD @@ -45,11 +45,11 @@ go_library( go_test( name = "go_default_test", srcs = ["disruption_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/disruption", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core/install:go_default_library", "//pkg/controller:go_default_library", "//vendor/github.com/Azure/go-autorest/autorest/to:go_default_library", "//vendor/k8s.io/api/apps/v1beta1:go_default_library", diff --git a/pkg/controller/disruption/disruption.go b/pkg/controller/disruption/disruption.go index ab474ed2228..b234f5ca0b6 100644 --- a/pkg/controller/disruption/disruption.go +++ b/pkg/controller/disruption/disruption.go @@ -294,7 +294,7 @@ func (dc *DisruptionController) Run(stopCh <-chan struct{}) { if dc.kubeClient != nil { glog.Infof("Sending events to api server.") - dc.broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(dc.kubeClient.Core().RESTClient()).Events("")}) + dc.broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(dc.kubeClient.CoreV1().RESTClient()).Events("")}) } else { glog.Infof("No api server defined - no events will be sent to API server.") } @@ -728,7 +728,7 @@ func refresh(pdbClient policyclientset.PodDisruptionBudgetInterface, pdb *policy } func (dc *DisruptionController) writePdbStatus(pdb *policy.PodDisruptionBudget) error { - pdbClient := dc.kubeClient.Policy().PodDisruptionBudgets(pdb.Namespace) + pdbClient := dc.kubeClient.PolicyV1beta1().PodDisruptionBudgets(pdb.Namespace) st := pdb.Status var err error diff --git a/pkg/controller/disruption/disruption_test.go b/pkg/controller/disruption/disruption_test.go index 7d792f6e340..b64a7a10c7d 100644 --- a/pkg/controller/disruption/disruption_test.go +++ b/pkg/controller/disruption/disruption_test.go @@ -33,8 +33,8 @@ import ( "k8s.io/client-go/informers" "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" - "k8s.io/kubernetes/pkg/api" - _ "k8s.io/kubernetes/pkg/api/install" + "k8s.io/kubernetes/pkg/api/legacyscheme" + _ "k8s.io/kubernetes/pkg/apis/core/install" "k8s.io/kubernetes/pkg/controller" "github.com/Azure/go-autorest/autorest/to" @@ -142,7 +142,7 @@ func newSelFooBar() *metav1.LabelSelector { func newMinAvailablePodDisruptionBudget(t *testing.T, minAvailable intstr.IntOrString) (*policy.PodDisruptionBudget, string) { pdb := &policy.PodDisruptionBudget{ - TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, + TypeMeta: metav1.TypeMeta{APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ UID: uuid.NewUUID(), Name: "foobar", @@ -165,7 +165,7 @@ func newMinAvailablePodDisruptionBudget(t *testing.T, minAvailable intstr.IntOrS func newMaxUnavailablePodDisruptionBudget(t *testing.T, maxUnavailable intstr.IntOrString) (*policy.PodDisruptionBudget, string) { pdb := &policy.PodDisruptionBudget{ - TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, + TypeMeta: metav1.TypeMeta{APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ UID: uuid.NewUUID(), Name: "foobar", @@ -210,7 +210,7 @@ func updatePodOwnerToSs(t *testing.T, pod *v1.Pod, ss *apps.StatefulSet) { func newPod(t *testing.T, name string) (*v1.Pod, string) { pod := &v1.Pod{ - TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, + TypeMeta: metav1.TypeMeta{APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ UID: uuid.NewUUID(), Annotations: make(map[string]string), @@ -237,7 +237,7 @@ func newPod(t *testing.T, name string) (*v1.Pod, string) { func newReplicationController(t *testing.T, size int32) (*v1.ReplicationController, string) { rc := &v1.ReplicationController{ - TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, + TypeMeta: metav1.TypeMeta{APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ UID: uuid.NewUUID(), Name: "foobar", @@ -261,7 +261,7 @@ func newReplicationController(t *testing.T, size int32) (*v1.ReplicationControll func newDeployment(t *testing.T, size int32) (*extensions.Deployment, string) { d := &extensions.Deployment{ - TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, + TypeMeta: metav1.TypeMeta{APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ UID: uuid.NewUUID(), Name: "foobar", @@ -285,7 +285,7 @@ func newDeployment(t *testing.T, size int32) (*extensions.Deployment, string) { func newReplicaSet(t *testing.T, size int32) (*extensions.ReplicaSet, string) { rs := &extensions.ReplicaSet{ - TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, + TypeMeta: metav1.TypeMeta{APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ UID: uuid.NewUUID(), Name: "foobar", @@ -309,7 +309,7 @@ func newReplicaSet(t *testing.T, size int32) (*extensions.ReplicaSet, string) { func newStatefulSet(t *testing.T, size int32) (*apps.StatefulSet, string) { ss := &apps.StatefulSet{ - TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, + TypeMeta: metav1.TypeMeta{APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ UID: uuid.NewUUID(), Name: "foobar", diff --git a/pkg/controller/endpoint/BUILD b/pkg/controller/endpoint/BUILD index ba98c203812..0cfedaba881 100644 --- a/pkg/controller/endpoint/BUILD +++ b/pkg/controller/endpoint/BUILD @@ -14,9 +14,9 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/controller/endpoint", deps = [ - "//pkg/api:go_default_library", "//pkg/api/v1/endpoints:go_default_library", "//pkg/api/v1/pod:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/controller:go_default_library", "//pkg/util/metrics:go_default_library", "//vendor/github.com/golang/glog:go_default_library", @@ -40,12 +40,13 @@ go_library( go_test( name = "go_default_test", srcs = ["endpoints_controller_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/endpoint", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/testapi:go_default_library", "//pkg/api/v1/endpoints:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/controller:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/controller/endpoint/endpoints_controller.go b/pkg/controller/endpoint/endpoints_controller.go index 13ea96fd81a..b7d46c3e07e 100644 --- a/pkg/controller/endpoint/endpoints_controller.go +++ b/pkg/controller/endpoint/endpoints_controller.go @@ -36,9 +36,9 @@ import ( "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/leaderelection/resourcelock" "k8s.io/client-go/util/workqueue" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/v1/endpoints" podutil "k8s.io/kubernetes/pkg/api/v1/pod" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/util/metrics" @@ -75,8 +75,8 @@ var ( // NewEndpointController returns a new *EndpointController. func NewEndpointController(podInformer coreinformers.PodInformer, serviceInformer coreinformers.ServiceInformer, endpointsInformer coreinformers.EndpointsInformer, client clientset.Interface) *EndpointController { - if client != nil && client.Core().RESTClient().GetRateLimiter() != nil { - metrics.RegisterMetricAndTrackRateLimiterUsage("endpoint_controller", client.Core().RESTClient().GetRateLimiter()) + if client != nil && client.CoreV1().RESTClient().GetRateLimiter() != nil { + metrics.RegisterMetricAndTrackRateLimiterUsage("endpoint_controller", client.CoreV1().RESTClient().GetRateLimiter()) } e := &EndpointController{ client: client, @@ -144,7 +144,7 @@ type EndpointController struct { workerLoopPeriod time.Duration } -// Runs e; will not return until stopCh is closed. workers determines how many +// Run will not return until stopCh is closed. workers determines how many // endpoints will be handled in parallel. func (e *EndpointController) Run(workers int, stopCh <-chan struct{}) { defer utilruntime.HandleCrash() @@ -215,6 +215,10 @@ func podToEndpointAddress(pod *v1.Pod) *v1.EndpointAddress { } func podChanged(oldPod, newPod *v1.Pod) bool { + // If the pod's deletion timestamp is set, remove endpoint from ready address. + if newPod.DeletionTimestamp != oldPod.DeletionTimestamp { + return true + } // If the pod's readiness has changed, the associated endpoint address // will move from the unready endpoints set to the ready endpoints. // So for the purposes of an endpoint, a readiness change on a pod @@ -390,15 +394,7 @@ func (e *EndpointController) syncService(key string) error { } service, err := e.serviceLister.Services(namespace).Get(name) if err != nil { - // Delete the corresponding endpoint, as the service has been deleted. - // TODO: Please note that this will delete an endpoint when a - // service is deleted. However, if we're down at the time when - // the service is deleted, we will miss that deletion, so this - // doesn't completely solve the problem. See #6877. - err = e.client.Core().Endpoints(namespace).Delete(name, nil) - if err != nil && !errors.IsNotFound(err) { - return err - } + // Service has been deleted. So no need to do any more operations. return nil } @@ -508,10 +504,10 @@ func (e *EndpointController) syncService(key string) error { glog.V(4).Infof("Update endpoints for %v/%v, ready: %d not ready: %d", service.Namespace, service.Name, totalReadyEps, totalNotReadyEps) if createEndpoints { // No previous endpoints, create them - _, err = e.client.Core().Endpoints(service.Namespace).Create(newEndpoints) + _, err = e.client.CoreV1().Endpoints(service.Namespace).Create(newEndpoints) } else { // Pre-existing - _, err = e.client.Core().Endpoints(service.Namespace).Update(newEndpoints) + _, err = e.client.CoreV1().Endpoints(service.Namespace).Update(newEndpoints) } if err != nil { if createEndpoints && errors.IsForbidden(err) { diff --git a/pkg/controller/endpoint/endpoints_controller_test.go b/pkg/controller/endpoint/endpoints_controller_test.go index 496aec14be0..49e786d9c74 100644 --- a/pkg/controller/endpoint/endpoints_controller_test.go +++ b/pkg/controller/endpoint/endpoints_controller_test.go @@ -34,9 +34,10 @@ import ( restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/cache" utiltesting "k8s.io/client-go/util/testing" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/testapi" endptspkg "k8s.io/kubernetes/pkg/api/v1/endpoints" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/controller" ) @@ -47,7 +48,7 @@ var emptyNodeName string func addPods(store cache.Store, namespace string, nPods int, nPorts int, nNotReady int) { for i := 0; i < nPods+nNotReady; i++ { p := &v1.Pod{ - TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, + TypeMeta: metav1.TypeMeta{APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, Name: fmt.Sprintf("pod%d", i), @@ -80,7 +81,7 @@ func addPods(store cache.Store, namespace string, nPods int, nPorts int, nNotRea func addNotReadyPodsWithSpecifiedRestartPolicyAndPhase(store cache.Store, namespace string, nPods int, nPorts int, restartPolicy v1.RestartPolicy, podPhase v1.PodPhase) { for i := 0; i < nPods; i++ { p := &v1.Pod{ - TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, + TypeMeta: metav1.TypeMeta{APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, Name: fmt.Sprintf("pod%d", i), @@ -137,7 +138,7 @@ type endpointController struct { } func newController(url string) *endpointController { - client := clientset.NewForConfigOrDie(&restclient.Config{Host: url, ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + client := clientset.NewForConfigOrDie(&restclient.Config{Host: url, ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) endpoints := NewEndpointController(informerFactory.Core().V1().Pods(), informerFactory.Core().V1().Services(), informerFactory.Core().V1().Endpoints(), client) @@ -1038,6 +1039,13 @@ func TestPodChanged(t *testing.T) { t.Errorf("Expected pod to be changed with pod readiness change") } oldPod.Status.Conditions = saveConditions + + now := metav1.NewTime(time.Now().UTC()) + newPod.ObjectMeta.DeletionTimestamp = &now + if !podChanged(oldPod, newPod) { + t.Errorf("Expected pod to be changed with DeletionTimestamp change") + } + newPod.ObjectMeta.DeletionTimestamp = oldPod.ObjectMeta.DeletionTimestamp.DeepCopy() } func TestDetermineNeededServiceUpdates(t *testing.T) { diff --git a/pkg/controller/garbagecollector/BUILD b/pkg/controller/garbagecollector/BUILD index da7243d0834..4541ca39b0a 100644 --- a/pkg/controller/garbagecollector/BUILD +++ b/pkg/controller/garbagecollector/BUILD @@ -50,11 +50,11 @@ go_library( go_test( name = "go_default_test", srcs = ["garbagecollector_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/garbagecollector", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core/install:go_default_library", "//pkg/controller/garbagecollector/metaonly:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", @@ -66,6 +66,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library", + "//vendor/k8s.io/client-go/discovery:go_default_library", "//vendor/k8s.io/client-go/dynamic:go_default_library", "//vendor/k8s.io/client-go/informers:go_default_library", "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", diff --git a/pkg/controller/garbagecollector/garbagecollector.go b/pkg/controller/garbagecollector/garbagecollector.go index 88536408713..957775177ae 100644 --- a/pkg/controller/garbagecollector/garbagecollector.go +++ b/pkg/controller/garbagecollector/garbagecollector.go @@ -38,7 +38,6 @@ import ( "k8s.io/client-go/informers" "k8s.io/client-go/util/workqueue" "k8s.io/kubernetes/pkg/controller" - "k8s.io/kubernetes/pkg/controller/garbagecollector/metaonly" _ "k8s.io/kubernetes/pkg/util/reflector/prometheus" // for reflector metric registration // install the prometheus plugin _ "k8s.io/kubernetes/pkg/util/workqueue/prometheus" @@ -171,17 +170,7 @@ func (gc *GarbageCollector) Sync(discoveryClient discovery.DiscoveryInterface, p oldResources := make(map[schema.GroupVersionResource]struct{}) wait.Until(func() { // Get the current resource list from discovery. - newResources, err := GetDeletableResources(discoveryClient) - if err != nil { - utilruntime.HandleError(err) - return - } - - // Detect first or abnormal sync and try again later. - if oldResources == nil || len(oldResources) == 0 { - oldResources = newResources - return - } + newResources := GetDeletableResources(discoveryClient) // Decide whether discovery has reported a change. if reflect.DeepEqual(oldResources, newResources) { @@ -189,9 +178,8 @@ func (gc *GarbageCollector) Sync(discoveryClient discovery.DiscoveryInterface, p return } - // Something has changed, so track the new state and perform a sync. + // Something has changed, time to sync. glog.V(2).Infof("syncing garbage collector with updated resources from discovery: %v", newResources) - oldResources = newResources // Ensure workers are paused to avoid processing events before informers // have resynced. @@ -216,9 +204,19 @@ func (gc *GarbageCollector) Sync(discoveryClient discovery.DiscoveryInterface, p utilruntime.HandleError(fmt.Errorf("failed to sync resource monitors: %v", err)) return } + // TODO: WaitForCacheSync can block forever during normal operation. Could + // pass a timeout channel, but we have to consider the implications of + // un-pausing the GC with a partially synced graph builder. if !controller.WaitForCacheSync("garbage collector", stopCh, gc.dependencyGraphBuilder.IsSynced) { utilruntime.HandleError(fmt.Errorf("timed out waiting for dependency graph builder sync during GC sync")) + return } + + // Finally, keep track of our new state. Do this after all preceding steps + // have succeeded to ensure we'll retry on subsequent syncs if an error + // occured. + oldResources = newResources + glog.V(2).Infof("synced garbage collector") }, period, stopCh) } @@ -260,24 +258,16 @@ func (gc *GarbageCollector) attemptToDeleteWorker() bool { } // retry if garbage collection of an object failed. gc.attemptToDelete.AddRateLimited(item) + } else if !n.isObserved() { + // requeue if item hasn't been observed via an informer event yet. + // otherwise a virtual node for an item added AND removed during watch reestablishment can get stuck in the graph and never removed. + // see https://issue.k8s.io/56121 + glog.V(5).Infof("item %s hasn't been observed via informer yet", n.identity) + gc.attemptToDelete.AddRateLimited(item) } return true } -func objectReferenceToMetadataOnlyObject(ref objectReference) *metaonly.MetadataOnlyObject { - return &metaonly.MetadataOnlyObject{ - TypeMeta: metav1.TypeMeta{ - APIVersion: ref.APIVersion, - Kind: ref.Kind, - }, - ObjectMeta: metav1.ObjectMeta{ - Namespace: ref.Namespace, - UID: ref.UID, - Name: ref.Name, - }, - } -} - // isDangling check if a reference is pointing to an object that doesn't exist. // If isDangling looks up the referenced object at the API server, it also // returns its latest state. @@ -298,7 +288,7 @@ func (gc *GarbageCollector) isDangling(reference metav1.OwnerReference, item *no if err != nil { return false, nil, err } - resource, err := gc.apiResource(reference.APIVersion, reference.Kind, len(item.identity.Namespace) != 0) + resource, err := gc.apiResource(reference.APIVersion, reference.Kind) if err != nil { return false, nil, err } @@ -354,15 +344,6 @@ func (gc *GarbageCollector) classifyReferences(item *node, latestReferences []me return solid, dangling, waitingForDependentsDeletion, nil } -func (gc *GarbageCollector) generateVirtualDeleteEvent(identity objectReference) { - event := &event{ - eventType: deleteEvent, - obj: objectReferenceToMetadataOnlyObject(identity), - } - glog.V(5).Infof("generating virtual delete event for %s\n\n", event.obj) - gc.dependencyGraphBuilder.enqueueChanges(event) -} - func ownerRefsToUIDs(refs []metav1.OwnerReference) []types.UID { var ret []types.UID for _, ref := range refs { @@ -388,7 +369,10 @@ func (gc *GarbageCollector) attemptToDeleteItem(item *node) error { // exist yet, so we need to enqueue a virtual Delete event to remove // the virtual node from GraphBuilder.uidToNode. glog.V(5).Infof("item %v not found, generating a virtual delete event", item.identity) - gc.generateVirtualDeleteEvent(item.identity) + gc.dependencyGraphBuilder.enqueueVirtualDeleteEvent(item.identity) + // since we're manually inserting a delete event to remove this node, + // we don't need to keep tracking it as a virtual node and requeueing in attemptToDelete + item.markObserved() return nil case err != nil: return err @@ -396,7 +380,10 @@ func (gc *GarbageCollector) attemptToDeleteItem(item *node) error { if latest.GetUID() != item.identity.UID { glog.V(5).Infof("UID doesn't match, item %v not found, generating a virtual delete event", item.identity) - gc.generateVirtualDeleteEvent(item.identity) + gc.dependencyGraphBuilder.enqueueVirtualDeleteEvent(item.identity) + // since we're manually inserting a delete event to remove this node, + // we don't need to keep tracking it as a virtual node and requeueing in attemptToDelete + item.markObserved() return nil } @@ -563,14 +550,14 @@ func (gc *GarbageCollector) attemptToOrphanWorker() bool { err := gc.orphanDependents(owner.identity, dependents) if err != nil { - glog.V(5).Infof("orphanDependents for %s failed with %v", owner.identity, err) + utilruntime.HandleError(fmt.Errorf("orphanDependents for %s failed with %v", owner.identity, err)) gc.attemptToOrphan.AddRateLimited(item) return true } // update the owner, remove "orphaningFinalizer" from its finalizers list err = gc.removeFinalizer(owner, metav1.FinalizerOrphanDependents) if err != nil { - glog.V(5).Infof("removeOrphanFinalizer for %s failed with %v", owner.identity, err) + utilruntime.HandleError(fmt.Errorf("removeOrphanFinalizer for %s failed with %v", owner.identity, err)) gc.attemptToOrphan.AddRateLimited(item) } return true @@ -591,16 +578,38 @@ func (gc *GarbageCollector) GraphHasUID(UIDs []types.UID) bool { // GetDeletableResources returns all resources from discoveryClient that the // garbage collector should recognize and work with. More specifically, all -// preferred resources which support the 'delete' verb. -func GetDeletableResources(discoveryClient discovery.DiscoveryInterface) (map[schema.GroupVersionResource]struct{}, error) { +// preferred resources which support the 'delete', 'list', and 'watch' verbs. +// +// All discovery errors are considered temporary. Upon encountering any error, +// GetDeletableResources will log and return any discovered resources it was +// able to process (which may be none). +func GetDeletableResources(discoveryClient discovery.ServerResourcesInterface) map[schema.GroupVersionResource]struct{} { preferredResources, err := discoveryClient.ServerPreferredResources() if err != nil { - return nil, fmt.Errorf("failed to get supported resources from server: %v", err) + if discovery.IsGroupDiscoveryFailedError(err) { + glog.Warningf("failed to discover some groups: %v", err.(*discovery.ErrGroupDiscoveryFailed).Groups) + } else { + glog.Warningf("failed to discover preferred resources: %v", err) + } } - deletableResources := discovery.FilteredBy(discovery.SupportsAllVerbs{Verbs: []string{"delete"}}, preferredResources) - deletableGroupVersionResources, err := discovery.GroupVersionResources(deletableResources) - if err != nil { - return nil, fmt.Errorf("Failed to parse resources from server: %v", err) + if preferredResources == nil { + return map[schema.GroupVersionResource]struct{}{} } - return deletableGroupVersionResources, nil + + // This is extracted from discovery.GroupVersionResources to allow tolerating + // failures on a per-resource basis. + deletableResources := discovery.FilteredBy(discovery.SupportsAllVerbs{Verbs: []string{"delete", "list", "watch"}}, preferredResources) + deletableGroupVersionResources := map[schema.GroupVersionResource]struct{}{} + for _, rl := range deletableResources { + gv, err := schema.ParseGroupVersion(rl.GroupVersion) + if err != nil { + glog.Warningf("ignoring invalid discovered resource %q: %v", rl.GroupVersion, err) + continue + } + for i := range rl.APIResources { + deletableGroupVersionResources[schema.GroupVersionResource{Group: gv.Group, Version: gv.Version, Resource: rl.APIResources[i].Name}] = struct{}{} + } + } + + return deletableGroupVersionResources } diff --git a/pkg/controller/garbagecollector/garbagecollector_test.go b/pkg/controller/garbagecollector/garbagecollector_test.go index b6b2dc4788f..469d7550782 100644 --- a/pkg/controller/garbagecollector/garbagecollector_test.go +++ b/pkg/controller/garbagecollector/garbagecollector_test.go @@ -17,6 +17,7 @@ limitations under the License. package garbagecollector import ( + "fmt" "net/http" "net/http/httptest" "reflect" @@ -26,7 +27,7 @@ import ( "github.com/stretchr/testify/assert" - _ "k8s.io/kubernetes/pkg/api/install" + _ "k8s.io/kubernetes/pkg/apis/core/install" "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" @@ -37,12 +38,13 @@ import ( "k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/strategicpatch" + "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" restclient "k8s.io/client-go/rest" "k8s.io/client-go/util/workqueue" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/controller/garbagecollector/metaonly" ) @@ -56,7 +58,7 @@ func TestGarbageCollectorConstruction(t *testing.T) { config := &restclient.Config{} config.ContentConfig.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: metaonly.NewMetadataCodecFactory()} tweakableRM := meta.NewDefaultRESTMapper(nil, nil) - rm := &testRESTMapper{meta.MultiRESTMapper{tweakableRM, api.Registry.RESTMapper()}} + rm := &testRESTMapper{meta.MultiRESTMapper{tweakableRM, legacyscheme.Registry.RESTMapper()}} metaOnlyClientPool := dynamic.NewClientPool(config, rm, dynamic.LegacyAPIPathResolverFunc) config.ContentConfig.NegotiatedSerializer = nil clientPool := dynamic.NewClientPool(config, rm, dynamic.LegacyAPIPathResolverFunc) @@ -170,15 +172,15 @@ type garbageCollector struct { func setupGC(t *testing.T, config *restclient.Config) garbageCollector { config.ContentConfig.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: metaonly.NewMetadataCodecFactory()} - metaOnlyClientPool := dynamic.NewClientPool(config, api.Registry.RESTMapper(), dynamic.LegacyAPIPathResolverFunc) + metaOnlyClientPool := dynamic.NewClientPool(config, legacyscheme.Registry.RESTMapper(), dynamic.LegacyAPIPathResolverFunc) config.ContentConfig.NegotiatedSerializer = nil - clientPool := dynamic.NewClientPool(config, api.Registry.RESTMapper(), dynamic.LegacyAPIPathResolverFunc) + clientPool := dynamic.NewClientPool(config, legacyscheme.Registry.RESTMapper(), dynamic.LegacyAPIPathResolverFunc) podResource := map[schema.GroupVersionResource]struct{}{{Version: "v1", Resource: "pods"}: {}} client := fake.NewSimpleClientset() sharedInformers := informers.NewSharedInformerFactory(client, 0) alwaysStarted := make(chan struct{}) close(alwaysStarted) - gc, err := NewGarbageCollector(metaOnlyClientPool, clientPool, &testRESTMapper{api.Registry.RESTMapper()}, podResource, ignoredResources, sharedInformers, alwaysStarted) + gc, err := NewGarbageCollector(metaOnlyClientPool, clientPool, &testRESTMapper{legacyscheme.Registry.RESTMapper()}, podResource, ignoredResources, sharedInformers, alwaysStarted) if err != nil { t.Fatal(err) } @@ -413,7 +415,7 @@ func TestGCListWatcher(t *testing.T) { testHandler := &fakeActionHandler{} srv, clientConfig := testServerAndClientConfig(testHandler.ServeHTTP) defer srv.Close() - clientPool := dynamic.NewClientPool(clientConfig, api.Registry.RESTMapper(), dynamic.LegacyAPIPathResolverFunc) + clientPool := dynamic.NewClientPool(clientConfig, legacyscheme.Registry.RESTMapper(), dynamic.LegacyAPIPathResolverFunc) podResource := schema.GroupVersionResource{Version: "v1", Resource: "pods"} client, err := clientPool.ClientForGroupVersionResource(podResource) if err != nil { @@ -671,3 +673,116 @@ func TestOrphanDependentsFailure(t *testing.T) { t.Errorf("expected error contains text %s, got %v", expected, err) } } + +// TestGetDeletableResources ensures GetDeletableResources always returns +// something usable regardless of discovery output. +func TestGetDeletableResources(t *testing.T) { + tests := map[string]struct { + serverResources []*metav1.APIResourceList + err error + deletableResources map[schema.GroupVersionResource]struct{} + }{ + "no error": { + serverResources: []*metav1.APIResourceList{ + { + // Valid GroupVersion + GroupVersion: "apps/v1", + APIResources: []metav1.APIResource{ + {Name: "pods", Namespaced: true, Kind: "Pod", Verbs: metav1.Verbs{"delete", "list", "watch"}}, + {Name: "services", Namespaced: true, Kind: "Service"}, + }, + }, + { + // Invalid GroupVersion, should be ignored + GroupVersion: "foo//whatever", + APIResources: []metav1.APIResource{ + {Name: "bars", Namespaced: true, Kind: "Bar", Verbs: metav1.Verbs{"delete", "list", "watch"}}, + }, + }, + { + // Valid GroupVersion, missing required verbs, should be ignored + GroupVersion: "acme/v1", + APIResources: []metav1.APIResource{ + {Name: "widgets", Namespaced: true, Kind: "Widget", Verbs: metav1.Verbs{"delete"}}, + }, + }, + }, + err: nil, + deletableResources: map[schema.GroupVersionResource]struct{}{ + {Group: "apps", Version: "v1", Resource: "pods"}: {}, + }, + }, + "nonspecific failure, includes usable results": { + serverResources: []*metav1.APIResourceList{ + { + GroupVersion: "apps/v1", + APIResources: []metav1.APIResource{ + {Name: "pods", Namespaced: true, Kind: "Pod", Verbs: metav1.Verbs{"delete", "list", "watch"}}, + {Name: "services", Namespaced: true, Kind: "Service"}, + }, + }, + }, + err: fmt.Errorf("internal error"), + deletableResources: map[schema.GroupVersionResource]struct{}{ + {Group: "apps", Version: "v1", Resource: "pods"}: {}, + }, + }, + "partial discovery failure, includes usable results": { + serverResources: []*metav1.APIResourceList{ + { + GroupVersion: "apps/v1", + APIResources: []metav1.APIResource{ + {Name: "pods", Namespaced: true, Kind: "Pod", Verbs: metav1.Verbs{"delete", "list", "watch"}}, + {Name: "services", Namespaced: true, Kind: "Service"}, + }, + }, + }, + err: &discovery.ErrGroupDiscoveryFailed{ + Groups: map[schema.GroupVersion]error{ + {Group: "foo", Version: "v1"}: fmt.Errorf("discovery failure"), + }, + }, + deletableResources: map[schema.GroupVersionResource]struct{}{ + {Group: "apps", Version: "v1", Resource: "pods"}: {}, + }, + }, + "discovery failure, no results": { + serverResources: nil, + err: fmt.Errorf("internal error"), + deletableResources: map[schema.GroupVersionResource]struct{}{}, + }, + } + + for name, test := range tests { + t.Logf("testing %q", name) + client := &fakeServerResources{ + PreferredResources: test.serverResources, + Error: test.err, + } + actual := GetDeletableResources(client) + if !reflect.DeepEqual(test.deletableResources, actual) { + t.Errorf("expected resources:\n%v\ngot:\n%v", test.deletableResources, actual) + } + } +} + +type fakeServerResources struct { + PreferredResources []*metav1.APIResourceList + Error error +} + +func (_ *fakeServerResources) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) { + return nil, nil +} + +func (_ *fakeServerResources) ServerResources() ([]*metav1.APIResourceList, error) { + return nil, nil +} + +func (f *fakeServerResources) ServerPreferredResources() ([]*metav1.APIResourceList, error) { + return f.PreferredResources, f.Error +} + +func (_ *fakeServerResources) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) { + return nil, nil +} diff --git a/pkg/controller/garbagecollector/graph.go b/pkg/controller/garbagecollector/graph.go index 282256cbdba..7282d51b6f6 100644 --- a/pkg/controller/garbagecollector/graph.go +++ b/pkg/controller/garbagecollector/graph.go @@ -53,6 +53,9 @@ type node struct { // this records if the object's deletionTimestamp is non-nil. beingDeleted bool beingDeletedLock sync.RWMutex + // this records if the object was constructed virtually and never observed via informer event + virtual bool + 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 @@ -72,6 +75,17 @@ func (n *node) isBeingDeleted() bool { return n.beingDeleted } +func (n *node) markObserved() { + n.virtualLock.Lock() + defer n.virtualLock.Unlock() + n.virtual = false +} +func (n *node) isObserved() bool { + n.virtualLock.RLock() + defer n.virtualLock.RUnlock() + return n.virtual == false +} + func (n *node) markDeletingDependents() { n.deletingDependentsLock.Lock() defer n.deletingDependentsLock.Unlock() diff --git a/pkg/controller/garbagecollector/graph_builder.go b/pkg/controller/garbagecollector/graph_builder.go index c19305312e9..56afc5f5ce9 100644 --- a/pkg/controller/garbagecollector/graph_builder.go +++ b/pkg/controller/garbagecollector/graph_builder.go @@ -37,6 +37,7 @@ import ( "k8s.io/client-go/informers" "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" + "k8s.io/kubernetes/pkg/controller/garbagecollector/metaonly" ) type eventType int @@ -230,7 +231,7 @@ func (gb *GraphBuilder) syncMonitors(resources map[schema.GroupVersionResource]s kept := 0 added := 0 for resource := range resources { - if _, ok := ignoredResources[resource.GroupResource()]; ok { + if _, ok := gb.ignoredResources[resource.GroupResource()]; ok { continue } if m, ok := toRemove[resource]; ok { @@ -342,6 +343,9 @@ func (gb *GraphBuilder) Run(stopCh <-chan struct{}) { close(monitor.stopCh) } } + + // reset monitors so that the graph builder can be safely re-run/synced. + gb.monitors = nil glog.Infof("stopped %d of %d monitors", stopped, len(monitors)) } @@ -366,8 +370,16 @@ func DefaultIgnoredResources() map[schema.GroupResource]struct{} { return ignoredResources } -func (gb *GraphBuilder) enqueueChanges(e *event) { - gb.graphChanges.Add(e) +// enqueueVirtualDeleteEvent is used to add a virtual delete event to be processed for virtual nodes +// once it is determined they do not have backing objects in storage +func (gb *GraphBuilder) enqueueVirtualDeleteEvent(ref objectReference) { + gb.graphChanges.Add(&event{ + eventType: deleteEvent, + obj: &metaonly.MetadataOnlyObject{ + TypeMeta: metav1.TypeMeta{APIVersion: ref.APIVersion, Kind: ref.Kind}, + ObjectMeta: metav1.ObjectMeta{Namespace: ref.Namespace, UID: ref.UID, Name: ref.Name}, + }, + }) } // addDependentToOwners adds n to owners' dependents list. If the owner does not @@ -379,22 +391,26 @@ func (gb *GraphBuilder) addDependentToOwners(n *node, owners []metav1.OwnerRefer ownerNode, ok := gb.uidToNode.Read(owner.UID) if !ok { // Create a "virtual" node in the graph for the owner if it doesn't - // exist in the graph yet. Then enqueue the virtual node into the - // attemptToDelete. The garbage processor will enqueue a virtual delete - // event to delete it from the graph if API server confirms this - // owner doesn't exist. + // exist in the graph yet. ownerNode = &node{ identity: objectReference{ OwnerReference: owner, Namespace: n.identity.Namespace, }, dependents: make(map[*node]struct{}), + virtual: true, } glog.V(5).Infof("add virtual node.identity: %s\n\n", ownerNode.identity) gb.uidToNode.Write(ownerNode) - gb.attemptToDelete.Add(ownerNode) } ownerNode.addDependent(n) + if !ok { + // Enqueue the virtual node into attemptToDelete. + // The garbage processor will enqueue a virtual delete + // event to delete it from the graph if API server confirms this + // owner doesn't exist. + gb.attemptToDelete.Add(ownerNode) + } } } @@ -585,6 +601,12 @@ func (gb *GraphBuilder) processGraphChanges() bool { glog.V(5).Infof("GraphBuilder process object: %s/%s, namespace %s, name %s, uid %s, event type %v", event.gvk.GroupVersion().String(), event.gvk.Kind, accessor.GetNamespace(), accessor.GetName(), string(accessor.GetUID()), event.eventType) // Check if the node already exsits existingNode, found := gb.uidToNode.Read(accessor.GetUID()) + if found { + // this marks the node as having been observed via an informer event + // 1. this depends on graphChanges only containing add/update events from the actual informer + // 2. this allows things tracking virtual nodes' existence to stop polling and rely on informer events + existingNode.markObserved() + } switch { case (event.eventType == addEvent || event.eventType == updateEvent) && !found: newNode := &node{ diff --git a/pkg/controller/garbagecollector/metaonly/BUILD b/pkg/controller/garbagecollector/metaonly/BUILD index 995d3364228..097f327d34d 100644 --- a/pkg/controller/garbagecollector/metaonly/BUILD +++ b/pkg/controller/garbagecollector/metaonly/BUILD @@ -15,9 +15,8 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/controller/garbagecollector/metaonly", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", @@ -27,10 +26,10 @@ go_library( go_test( name = "go_default_test", srcs = ["metaonly_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/garbagecollector/metaonly", - library = ":go_default_library", deps = [ - "//pkg/api/install:go_default_library", + "//pkg/apis/core/install:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/controller/garbagecollector/metaonly/metaonly.go b/pkg/controller/garbagecollector/metaonly/metaonly.go index 5f1db87d397..aec98ba3356 100644 --- a/pkg/controller/garbagecollector/metaonly/metaonly.go +++ b/pkg/controller/garbagecollector/metaonly/metaonly.go @@ -24,7 +24,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" ) type metaOnlyJSONScheme struct{} @@ -41,10 +41,10 @@ func gvkToMetadataOnlyObject(gvk schema.GroupVersionKind) runtime.Object { } func NewMetadataCodecFactory() serializer.CodecFactory { - // populating another scheme from api.Scheme, registering every kind with + // populating another scheme from legacyscheme.Scheme, registering every kind with // MetadataOnlyObject (or MetadataOnlyObjectList). scheme := runtime.NewScheme() - allTypes := api.Scheme.AllKnownTypes() + allTypes := legacyscheme.Scheme.AllKnownTypes() for kind := range allTypes { if kind.Version == runtime.APIVersionInternal { continue diff --git a/pkg/controller/garbagecollector/metaonly/metaonly_test.go b/pkg/controller/garbagecollector/metaonly/metaonly_test.go index 6c0952c339e..2bdf9c879d5 100644 --- a/pkg/controller/garbagecollector/metaonly/metaonly_test.go +++ b/pkg/controller/garbagecollector/metaonly/metaonly_test.go @@ -27,7 +27,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" - _ "k8s.io/kubernetes/pkg/api/install" + _ "k8s.io/kubernetes/pkg/apis/core/install" ) func getPod() *v1.Pod { diff --git a/pkg/controller/garbagecollector/metaonly/zz_generated.deepcopy.go b/pkg/controller/garbagecollector/metaonly/zz_generated.deepcopy.go index 70e2946939a..edc16e4f13d 100644 --- a/pkg/controller/garbagecollector/metaonly/zz_generated.deepcopy.go +++ b/pkg/controller/garbagecollector/metaonly/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,27 +21,9 @@ limitations under the License. package metaonly import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -// GetGeneratedDeepCopyFuncs returns the generated funcs, since we aren't registering them. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func GetGeneratedDeepCopyFuncs() []conversion.GeneratedDeepCopyFunc { - return []conversion.GeneratedDeepCopyFunc{ - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*MetadataOnlyObject).DeepCopyInto(out.(*MetadataOnlyObject)) - return nil - }, InType: reflect.TypeOf(&MetadataOnlyObject{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*MetadataOnlyObjectList).DeepCopyInto(out.(*MetadataOnlyObjectList)) - return nil - }, InType: reflect.TypeOf(&MetadataOnlyObjectList{})}, - } -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MetadataOnlyObject) DeepCopyInto(out *MetadataOnlyObject) { *out = *in diff --git a/pkg/controller/garbagecollector/operations.go b/pkg/controller/garbagecollector/operations.go index 36897547b97..1c898431dc0 100644 --- a/pkg/controller/garbagecollector/operations.go +++ b/pkg/controller/garbagecollector/operations.go @@ -32,16 +32,16 @@ import ( // apiResource consults the REST mapper to translate an tuple to a unversioned.APIResource struct. -func (gc *GarbageCollector) apiResource(apiVersion, kind string, namespaced bool) (*metav1.APIResource, error) { +func (gc *GarbageCollector) apiResource(apiVersion, kind string) (*metav1.APIResource, error) { fqKind := schema.FromAPIVersionAndKind(apiVersion, kind) - mapping, err := gc.restMapper.RESTMapping(fqKind.GroupKind(), apiVersion) + mapping, err := gc.restMapper.RESTMapping(fqKind.GroupKind(), fqKind.Version) if err != nil { return nil, newRESTMappingError(kind, apiVersion) } glog.V(5).Infof("map kind %s, version %s to resource %s", kind, apiVersion, mapping.Resource) resource := metav1.APIResource{ Name: mapping.Resource, - Namespaced: namespaced, + Namespaced: mapping.Scope == meta.RESTScopeNamespace, Kind: kind, } return &resource, nil @@ -53,7 +53,7 @@ func (gc *GarbageCollector) deleteObject(item objectReference, policy *metav1.De if err != nil { return err } - resource, err := gc.apiResource(item.APIVersion, item.Kind, len(item.Namespace) != 0) + resource, err := gc.apiResource(item.APIVersion, item.Kind) if err != nil { return err } @@ -69,7 +69,7 @@ func (gc *GarbageCollector) getObject(item objectReference) (*unstructured.Unstr if err != nil { return nil, err } - resource, err := gc.apiResource(item.APIVersion, item.Kind, len(item.Namespace) != 0) + resource, err := gc.apiResource(item.APIVersion, item.Kind) if err != nil { return nil, err } @@ -82,7 +82,7 @@ func (gc *GarbageCollector) updateObject(item objectReference, obj *unstructured if err != nil { return nil, err } - resource, err := gc.apiResource(item.APIVersion, item.Kind, len(item.Namespace) != 0) + resource, err := gc.apiResource(item.APIVersion, item.Kind) if err != nil { return nil, err } @@ -95,7 +95,7 @@ func (gc *GarbageCollector) patchObject(item objectReference, patch []byte) (*un if err != nil { return nil, err } - resource, err := gc.apiResource(item.APIVersion, item.Kind, len(item.Namespace) != 0) + resource, err := gc.apiResource(item.APIVersion, item.Kind) if err != nil { return nil, err } diff --git a/pkg/controller/history/BUILD b/pkg/controller/history/BUILD index 5a5f4ebae9c..fa3fe327bcb 100644 --- a/pkg/controller/history/BUILD +++ b/pkg/controller/history/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["controller_history_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/history", - library = ":go_default_library", deps = [ "//pkg/api/testapi:go_default_library", "//pkg/controller:go_default_library", diff --git a/pkg/controller/job/BUILD b/pkg/controller/job/BUILD index 3c8891a10bd..49b22b6707c 100644 --- a/pkg/controller/job/BUILD +++ b/pkg/controller/job/BUILD @@ -45,11 +45,11 @@ go_test( "job_controller_test.go", "utils_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/job", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core/install:go_default_library", "//pkg/controller:go_default_library", "//vendor/k8s.io/api/batch/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/controller/job/job_controller.go b/pkg/controller/job/job_controller.go index e31f8394366..a12cbed1c46 100644 --- a/pkg/controller/job/job_controller.go +++ b/pkg/controller/job/job_controller.go @@ -770,7 +770,7 @@ func (jm *JobController) manageJob(activePods []*v1.Pod, succeeded int32, job *b } func (jm *JobController) updateJobStatus(job *batch.Job) error { - _, err := jm.kubeClient.Batch().Jobs(job.Namespace).UpdateStatus(job) + _, err := jm.kubeClient.BatchV1().Jobs(job.Namespace).UpdateStatus(job) return err } diff --git a/pkg/controller/job/job_controller_test.go b/pkg/controller/job/job_controller_test.go index eb5dffec391..21a2b8f8d25 100644 --- a/pkg/controller/job/job_controller_test.go +++ b/pkg/controller/job/job_controller_test.go @@ -36,8 +36,8 @@ import ( restclient "k8s.io/client-go/rest" core "k8s.io/client-go/testing" "k8s.io/client-go/tools/cache" - "k8s.io/kubernetes/pkg/api" - _ "k8s.io/kubernetes/pkg/api/install" + "k8s.io/kubernetes/pkg/api/legacyscheme" + _ "k8s.io/kubernetes/pkg/apis/core/install" "k8s.io/kubernetes/pkg/controller" ) @@ -102,24 +102,43 @@ func newJobControllerFromClient(kubeClient clientset.Interface, resyncPeriod con return jm, sharedInformers } +func newPod(name string, job *batch.Job) *v1.Pod { + return &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Labels: job.Spec.Selector.MatchLabels, + Namespace: job.Namespace, + OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(job, controllerKind)}, + }, + } +} + // create count pods with the given phase for the given job func newPodList(count int32, status v1.PodPhase, job *batch.Job) []v1.Pod { pods := []v1.Pod{} for i := int32(0); i < count; i++ { - newPod := v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("pod-%v", rand.String(10)), - Labels: job.Spec.Selector.MatchLabels, - Namespace: job.Namespace, - OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(job, controllerKind)}, - }, - Status: v1.PodStatus{Phase: status}, - } - pods = append(pods, newPod) + newPod := newPod(fmt.Sprintf("pod-%v", rand.String(10)), job) + newPod.Status = v1.PodStatus{Phase: status} + pods = append(pods, *newPod) } return pods } +func setPodsStatuses(podIndexer cache.Indexer, job *batch.Job, pendingPods, activePods, succeededPods, failedPods int32) { + for _, pod := range newPodList(pendingPods, v1.PodPending, job) { + podIndexer.Add(&pod) + } + for _, pod := range newPodList(activePods, v1.PodRunning, job) { + podIndexer.Add(&pod) + } + for _, pod := range newPodList(succeededPods, v1.PodSucceeded, job) { + podIndexer.Add(&pod) + } + for _, pod := range newPodList(failedPods, v1.PodFailed, job) { + podIndexer.Add(&pod) + } +} + func TestControllerSyncJob(t *testing.T) { jobConditionComplete := batch.JobComplete jobConditionFailed := batch.JobFailed @@ -253,7 +272,7 @@ func TestControllerSyncJob(t *testing.T) { for name, tc := range testCases { // job manager setup - clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) manager, sharedInformerFactory := newJobControllerFromClient(clientset, controller.NoResyncPeriodFunc) fakePodControl := controller.FakePodControl{Err: tc.podControllerError, CreateLimit: tc.podLimit} manager.podControl = &fakePodControl @@ -273,18 +292,7 @@ func TestControllerSyncJob(t *testing.T) { } sharedInformerFactory.Batch().V1().Jobs().Informer().GetIndexer().Add(job) podIndexer := sharedInformerFactory.Core().V1().Pods().Informer().GetIndexer() - for _, pod := range newPodList(tc.pendingPods, v1.PodPending, job) { - podIndexer.Add(&pod) - } - for _, pod := range newPodList(tc.activePods, v1.PodRunning, job) { - podIndexer.Add(&pod) - } - for _, pod := range newPodList(tc.succeededPods, v1.PodSucceeded, job) { - podIndexer.Add(&pod) - } - for _, pod := range newPodList(tc.failedPods, v1.PodFailed, job) { - podIndexer.Add(&pod) - } + setPodsStatuses(podIndexer, job, tc.pendingPods, tc.activePods, tc.succeededPods, tc.failedPods) // run forget, err := manager.syncJob(getKey(job, t)) @@ -405,7 +413,7 @@ func TestSyncJobPastDeadline(t *testing.T) { for name, tc := range testCases { // job manager setup - clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) manager, sharedInformerFactory := newJobControllerFromClient(clientset, controller.NoResyncPeriodFunc) fakePodControl := controller.FakePodControl{} manager.podControl = &fakePodControl @@ -424,15 +432,7 @@ func TestSyncJobPastDeadline(t *testing.T) { job.Status.StartTime = &start sharedInformerFactory.Batch().V1().Jobs().Informer().GetIndexer().Add(job) podIndexer := sharedInformerFactory.Core().V1().Pods().Informer().GetIndexer() - for _, pod := range newPodList(tc.activePods, v1.PodRunning, job) { - podIndexer.Add(&pod) - } - for _, pod := range newPodList(tc.succeededPods, v1.PodSucceeded, job) { - podIndexer.Add(&pod) - } - for _, pod := range newPodList(tc.failedPods, v1.PodFailed, job) { - podIndexer.Add(&pod) - } + setPodsStatuses(podIndexer, job, 0, tc.activePods, tc.succeededPods, tc.failedPods) // run forget, err := manager.syncJob(getKey(job, t)) @@ -479,7 +479,7 @@ func getCondition(job *batch.Job, condition batch.JobConditionType, reason strin } func TestSyncPastDeadlineJobFinished(t *testing.T) { - clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) manager, sharedInformerFactory := newJobControllerFromClient(clientset, controller.NoResyncPeriodFunc) fakePodControl := controller.FakePodControl{} manager.podControl = &fakePodControl @@ -517,7 +517,7 @@ func TestSyncPastDeadlineJobFinished(t *testing.T) { } func TestSyncJobComplete(t *testing.T) { - clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) manager, sharedInformerFactory := newJobControllerFromClient(clientset, controller.NoResyncPeriodFunc) fakePodControl := controller.FakePodControl{} manager.podControl = &fakePodControl @@ -545,7 +545,7 @@ func TestSyncJobComplete(t *testing.T) { } func TestSyncJobDeleted(t *testing.T) { - clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) manager, _ := newJobControllerFromClient(clientset, controller.NoResyncPeriodFunc) fakePodControl := controller.FakePodControl{} manager.podControl = &fakePodControl @@ -569,7 +569,7 @@ func TestSyncJobDeleted(t *testing.T) { } func TestSyncJobUpdateRequeue(t *testing.T) { - clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) DefaultJobBackOff = time.Duration(0) // overwrite the default value for testing manager, sharedInformerFactory := newJobControllerFromClient(clientset, controller.NoResyncPeriodFunc) fakePodControl := controller.FakePodControl{} @@ -599,7 +599,7 @@ func TestSyncJobUpdateRequeue(t *testing.T) { } func TestJobPodLookup(t *testing.T) { - clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) manager, sharedInformerFactory := newJobControllerFromClient(clientset, controller.NoResyncPeriodFunc) manager.podStoreSynced = alwaysReady manager.jobStoreSynced = alwaysReady @@ -680,19 +680,8 @@ func TestJobPodLookup(t *testing.T) { } } -func newPod(name string, job *batch.Job) *v1.Pod { - return &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Labels: job.Spec.Selector.MatchLabels, - Namespace: job.Namespace, - OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(job, controllerKind)}, - }, - } -} - func TestGetPodsForJob(t *testing.T) { - clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) jm, informer := newJobControllerFromClient(clientset, controller.NoResyncPeriodFunc) jm.podStoreSynced = alwaysReady jm.jobStoreSynced = alwaysReady @@ -828,7 +817,7 @@ func TestGetPodsForJobNoAdoptIfBeingDeletedRace(t *testing.T) { } func TestGetPodsForJobRelease(t *testing.T) { - clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) jm, informer := newJobControllerFromClient(clientset, controller.NoResyncPeriodFunc) jm.podStoreSynced = alwaysReady jm.jobStoreSynced = alwaysReady @@ -857,7 +846,7 @@ func TestGetPodsForJobRelease(t *testing.T) { } func TestAddPod(t *testing.T) { - clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) jm, informer := newJobControllerFromClient(clientset, controller.NoResyncPeriodFunc) jm.podStoreSynced = alwaysReady jm.jobStoreSynced = alwaysReady @@ -902,7 +891,7 @@ func TestAddPod(t *testing.T) { } func TestAddPodOrphan(t *testing.T) { - clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) jm, informer := newJobControllerFromClient(clientset, controller.NoResyncPeriodFunc) jm.podStoreSynced = alwaysReady jm.jobStoreSynced = alwaysReady @@ -930,7 +919,7 @@ func TestAddPodOrphan(t *testing.T) { } func TestUpdatePod(t *testing.T) { - clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) jm, informer := newJobControllerFromClient(clientset, controller.NoResyncPeriodFunc) jm.podStoreSynced = alwaysReady jm.jobStoreSynced = alwaysReady @@ -979,7 +968,7 @@ func TestUpdatePod(t *testing.T) { } func TestUpdatePodOrphanWithNewLabels(t *testing.T) { - clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) jm, informer := newJobControllerFromClient(clientset, controller.NoResyncPeriodFunc) jm.podStoreSynced = alwaysReady jm.jobStoreSynced = alwaysReady @@ -1006,7 +995,7 @@ func TestUpdatePodOrphanWithNewLabels(t *testing.T) { } func TestUpdatePodChangeControllerRef(t *testing.T) { - clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) jm, informer := newJobControllerFromClient(clientset, controller.NoResyncPeriodFunc) jm.podStoreSynced = alwaysReady jm.jobStoreSynced = alwaysReady @@ -1032,7 +1021,7 @@ func TestUpdatePodChangeControllerRef(t *testing.T) { } func TestUpdatePodRelease(t *testing.T) { - clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) jm, informer := newJobControllerFromClient(clientset, controller.NoResyncPeriodFunc) jm.podStoreSynced = alwaysReady jm.jobStoreSynced = alwaysReady @@ -1058,7 +1047,7 @@ func TestUpdatePodRelease(t *testing.T) { } func TestDeletePod(t *testing.T) { - clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) jm, informer := newJobControllerFromClient(clientset, controller.NoResyncPeriodFunc) jm.podStoreSynced = alwaysReady jm.jobStoreSynced = alwaysReady @@ -1103,7 +1092,7 @@ func TestDeletePod(t *testing.T) { } func TestDeletePodOrphan(t *testing.T) { - clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) jm, informer := newJobControllerFromClient(clientset, controller.NoResyncPeriodFunc) jm.podStoreSynced = alwaysReady jm.jobStoreSynced = alwaysReady @@ -1143,7 +1132,7 @@ func (fe FakeJobExpectations) SatisfiedExpectations(controllerKey string) bool { // TestSyncJobExpectations tests that a pod cannot sneak in between counting active pods // and checking expectations. func TestSyncJobExpectations(t *testing.T) { - clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) manager, sharedInformerFactory := newJobControllerFromClient(clientset, controller.NoResyncPeriodFunc) fakePodControl := controller.FakePodControl{} manager.podControl = &fakePodControl @@ -1269,3 +1258,78 @@ func bumpResourceVersion(obj metav1.Object) { ver, _ := strconv.ParseInt(obj.GetResourceVersion(), 10, 32) obj.SetResourceVersion(strconv.FormatInt(ver+1, 10)) } + +type pods struct { + pending int32 + active int32 + succeed int32 + failed int32 +} + +func TestJobBackoffReset(t *testing.T) { + testCases := map[string]struct { + // job setup + parallelism int32 + completions int32 + backoffLimit int32 + + // pod setup - each row is additive! + pods []pods + }{ + "parallelism=1": { + 1, 2, 1, + []pods{ + {0, 1, 0, 1}, + {0, 0, 1, 0}, + }, + }, + "parallelism=2 (just failure)": { + 2, 2, 1, + []pods{ + {0, 2, 0, 1}, + {0, 0, 1, 0}, + }, + }, + } + + for name, tc := range testCases { + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + DefaultJobBackOff = time.Duration(0) // overwrite the default value for testing + manager, sharedInformerFactory := newJobControllerFromClient(clientset, controller.NoResyncPeriodFunc) + fakePodControl := controller.FakePodControl{} + manager.podControl = &fakePodControl + manager.podStoreSynced = alwaysReady + manager.jobStoreSynced = alwaysReady + var actual *batch.Job + manager.updateHandler = func(job *batch.Job) error { + actual = job + return nil + } + + // job & pods setup + job := newJob(tc.parallelism, tc.completions, tc.backoffLimit) + key := getKey(job, t) + sharedInformerFactory.Batch().V1().Jobs().Informer().GetIndexer().Add(job) + podIndexer := sharedInformerFactory.Core().V1().Pods().Informer().GetIndexer() + + setPodsStatuses(podIndexer, job, tc.pods[0].pending, tc.pods[0].active, tc.pods[0].succeed, tc.pods[0].failed) + manager.queue.Add(key) + manager.processNextWorkItem() + retries := manager.queue.NumRequeues(key) + if retries != 1 { + t.Errorf("%s: expected exactly 1 retry, got %d", name, retries) + } + + job = actual + sharedInformerFactory.Batch().V1().Jobs().Informer().GetIndexer().Replace([]interface{}{actual}, actual.ResourceVersion) + setPodsStatuses(podIndexer, job, tc.pods[1].pending, tc.pods[1].active, tc.pods[1].succeed, tc.pods[1].failed) + manager.processNextWorkItem() + retries = manager.queue.NumRequeues(key) + if retries != 0 { + t.Errorf("%s: expected exactly 0 retries, got %d", name, retries) + } + if getCondition(actual, batch.JobFailed, "BackoffLimitExceeded") { + t.Errorf("%s: unexpected job failure", name) + } + } +} diff --git a/pkg/controller/namespace/deletion/BUILD b/pkg/controller/namespace/deletion/BUILD index ecc3fdb0d4d..40a0517cd79 100644 --- a/pkg/controller/namespace/deletion/BUILD +++ b/pkg/controller/namespace/deletion/BUILD @@ -29,10 +29,11 @@ go_library( go_test( name = "go_default_test", srcs = ["namespaced_resources_deleter_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/namespace/deletion", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/controller/namespace/deletion/namespaced_resources_deleter_test.go b/pkg/controller/namespace/deletion/namespaced_resources_deleter_test.go index b9ffc80a200..d20cb4b1032 100644 --- a/pkg/controller/namespace/deletion/namespaced_resources_deleter_test.go +++ b/pkg/controller/namespace/deletion/namespaced_resources_deleter_test.go @@ -36,7 +36,8 @@ import ( "k8s.io/client-go/kubernetes/fake" restclient "k8s.io/client-go/rest" core "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestFinalized(t *testing.T) { @@ -172,7 +173,7 @@ func testSyncNamespaceThatIsTerminating(t *testing.T, versions *metav1.APIVersio defer srv.Close() mockClient := fake.NewSimpleClientset(testInput.testNamespace) - clientPool := dynamic.NewClientPool(clientConfig, api.Registry.RESTMapper(), dynamic.LegacyAPIPathResolverFunc) + clientPool := dynamic.NewClientPool(clientConfig, legacyscheme.Registry.RESTMapper(), dynamic.LegacyAPIPathResolverFunc) fn := func() ([]*metav1.APIResourceList, error) { return resources, nil diff --git a/pkg/controller/namespace/namespace_controller.go b/pkg/controller/namespace/namespace_controller.go index c3bdd28be7a..32a0bb33fd9 100644 --- a/pkg/controller/namespace/namespace_controller.go +++ b/pkg/controller/namespace/namespace_controller.go @@ -72,11 +72,11 @@ func NewNamespaceController( // create the controller so we can inject the enqueue function namespaceController := &NamespaceController{ queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "namespace"), - namespacedResourcesDeleter: deletion.NewNamespacedResourcesDeleter(kubeClient.Core().Namespaces(), clientPool, kubeClient.Core(), discoverResourcesFn, finalizerToken, true), + namespacedResourcesDeleter: deletion.NewNamespacedResourcesDeleter(kubeClient.CoreV1().Namespaces(), clientPool, kubeClient.CoreV1(), discoverResourcesFn, finalizerToken, true), } - if kubeClient != nil && kubeClient.Core().RESTClient().GetRateLimiter() != nil { - metrics.RegisterMetricAndTrackRateLimiterUsage("namespace_controller", kubeClient.Core().RESTClient().GetRateLimiter()) + if kubeClient != nil && kubeClient.CoreV1().RESTClient().GetRateLimiter() != nil { + metrics.RegisterMetricAndTrackRateLimiterUsage("namespace_controller", kubeClient.CoreV1().RESTClient().GetRateLimiter()) } // configure the namespace informer event handlers diff --git a/pkg/controller/node/BUILD b/pkg/controller/node/BUILD deleted file mode 100644 index 78e93347834..00000000000 --- a/pkg/controller/node/BUILD +++ /dev/null @@ -1,104 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = ["nodecontroller_test.go"], - importpath = "k8s.io/kubernetes/pkg/controller/node", - library = ":go_default_library", - deps = [ - "//pkg/cloudprovider:go_default_library", - "//pkg/cloudprovider/providers/fake:go_default_library", - "//pkg/controller:go_default_library", - "//pkg/controller/node/ipam:go_default_library", - "//pkg/controller/node/scheduler:go_default_library", - "//pkg/controller/node/util:go_default_library", - "//pkg/controller/testutil:go_default_library", - "//pkg/kubelet/apis:go_default_library", - "//pkg/util/node:go_default_library", - "//pkg/util/taints:go_default_library", - "//plugin/pkg/scheduler/algorithm:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/client-go/informers:go_default_library", - "//vendor/k8s.io/client-go/informers/core/v1:go_default_library", - "//vendor/k8s.io/client-go/informers/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", - "//vendor/k8s.io/client-go/testing:go_default_library", - ], -) - -go_library( - name = "go_default_library", - srcs = [ - "doc.go", - "metrics.go", - "node_controller.go", - ], - importpath = "k8s.io/kubernetes/pkg/controller/node", - deps = [ - "//pkg/api/v1/node:go_default_library", - "//pkg/cloudprovider:go_default_library", - "//pkg/controller:go_default_library", - "//pkg/controller/node/ipam:go_default_library", - "//pkg/controller/node/ipam/sync:go_default_library", - "//pkg/controller/node/scheduler:go_default_library", - "//pkg/controller/node/util:go_default_library", - "//pkg/util/metrics:go_default_library", - "//pkg/util/node:go_default_library", - "//pkg/util/system:go_default_library", - "//pkg/util/taints:go_default_library", - "//pkg/util/version:go_default_library", - "//plugin/pkg/scheduler/algorithm:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/client-go/informers/core/v1:go_default_library", - "//vendor/k8s.io/client-go/informers/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library", - "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", - "//vendor/k8s.io/client-go/listers/core/v1:go_default_library", - "//vendor/k8s.io/client-go/listers/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/client-go/tools/cache:go_default_library", - "//vendor/k8s.io/client-go/tools/record:go_default_library", - "//vendor/k8s.io/client-go/util/flowcontrol:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//pkg/controller/node/ipam:all-srcs", - "//pkg/controller/node/scheduler:all-srcs", - "//pkg/controller/node/util:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/pkg/controller/node/OWNERS b/pkg/controller/node/OWNERS deleted file mode 100755 index b94f08c6b3e..00000000000 --- a/pkg/controller/node/OWNERS +++ /dev/null @@ -1,8 +0,0 @@ -approvers: -- gmarek -- bowei -reviewers: -- gmarek -- smarterclayton -- ingvagabund -- aveshagarwal diff --git a/pkg/controller/node/doc.go b/pkg/controller/node/doc.go deleted file mode 100644 index b649f1dda49..00000000000 --- a/pkg/controller/node/doc.go +++ /dev/null @@ -1,19 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package node contains code for syncing cloud instances with -// node registry -package node // import "k8s.io/kubernetes/pkg/controller/node" diff --git a/pkg/controller/node/ipam/BUILD b/pkg/controller/node/ipam/BUILD deleted file mode 100644 index 31c1c34838c..00000000000 --- a/pkg/controller/node/ipam/BUILD +++ /dev/null @@ -1,83 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = [ - "controller_test.go", - "range_allocator_test.go", - "timeout_test.go", - ], - importpath = "k8s.io/kubernetes/pkg/controller/node/ipam", - library = ":go_default_library", - deps = [ - "//pkg/controller/node/ipam/cidrset:go_default_library", - "//pkg/controller/node/ipam/test:go_default_library", - "//pkg/controller/testutil:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", - ], -) - -go_library( - name = "go_default_library", - srcs = [ - "adapter.go", - "cidr_allocator.go", - "cloud_cidr_allocator.go", - "controller.go", - "doc.go", - "range_allocator.go", - "timeout.go", - ], - importpath = "k8s.io/kubernetes/pkg/controller/node/ipam", - deps = [ - "//pkg/cloudprovider:go_default_library", - "//pkg/cloudprovider/providers/gce:go_default_library", - "//pkg/controller/node/ipam/cidrset:go_default_library", - "//pkg/controller/node/ipam/sync:go_default_library", - "//pkg/controller/node/util:go_default_library", - "//pkg/util/node:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/client-go/informers/core/v1:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library", - "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", - "//vendor/k8s.io/client-go/tools/cache:go_default_library", - "//vendor/k8s.io/client-go/tools/record:go_default_library", - "//vendor/k8s.io/metrics/pkg/client/clientset_generated/clientset/scheme:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//pkg/controller/node/ipam/cidrset:all-srcs", - "//pkg/controller/node/ipam/sync:all-srcs", - "//pkg/controller/node/ipam/test:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/pkg/controller/node/ipam/adapter.go b/pkg/controller/node/ipam/adapter.go deleted file mode 100644 index 00a91535c8e..00000000000 --- a/pkg/controller/node/ipam/adapter.go +++ /dev/null @@ -1,125 +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 ipam - -import ( - "context" - "encoding/json" - "net" - - "github.com/golang/glog" - - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - clientset "k8s.io/client-go/kubernetes" - v1core "k8s.io/client-go/kubernetes/typed/core/v1" - "k8s.io/client-go/tools/record" - "k8s.io/kubernetes/pkg/cloudprovider/providers/gce" - nodeutil "k8s.io/kubernetes/pkg/util/node" - "k8s.io/metrics/pkg/client/clientset_generated/clientset/scheme" -) - -type adapter struct { - k8s clientset.Interface - cloud *gce.GCECloud - - recorder record.EventRecorder -} - -func newAdapter(k8s clientset.Interface, cloud *gce.GCECloud) *adapter { - ret := &adapter{ - k8s: k8s, - cloud: cloud, - } - - broadcaster := record.NewBroadcaster() - broadcaster.StartLogging(glog.Infof) - ret.recorder = broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "cloudCIDRAllocator"}) - glog.V(0).Infof("Sending events to api server.") - broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{ - Interface: v1core.New(k8s.Core().RESTClient()).Events(""), - }) - - return ret -} - -func (a *adapter) Alias(ctx context.Context, nodeName string) (*net.IPNet, error) { - cidrs, err := a.cloud.AliasRanges(types.NodeName(nodeName)) - if err != nil { - return nil, err - } - - switch len(cidrs) { - case 0: - return nil, nil - case 1: - break - default: - glog.Warningf("Node %q has more than one alias assigned (%v), defaulting to the first", nodeName, cidrs) - } - - _, cidrRange, err := net.ParseCIDR(cidrs[0]) - if err != nil { - return nil, err - } - - return cidrRange, nil -} - -func (a *adapter) AddAlias(ctx context.Context, nodeName string, cidrRange *net.IPNet) error { - return a.cloud.AddAliasToInstance(types.NodeName(nodeName), cidrRange) -} - -func (a *adapter) Node(ctx context.Context, name string) (*v1.Node, error) { - return a.k8s.Core().Nodes().Get(name, metav1.GetOptions{}) -} - -func (a *adapter) UpdateNodePodCIDR(ctx context.Context, node *v1.Node, cidrRange *net.IPNet) error { - patch := map[string]interface{}{ - "apiVersion": node.APIVersion, - "kind": node.Kind, - "metadata": map[string]interface{}{"name": node.Name}, - "spec": map[string]interface{}{"podCIDR": cidrRange.String()}, - } - bytes, err := json.Marshal(patch) - if err != nil { - return err - } - - _, err = a.k8s.Core().Nodes().Patch(node.Name, types.StrategicMergePatchType, bytes) - return err -} - -func (a *adapter) UpdateNodeNetworkUnavailable(nodeName string, unavailable bool) error { - condition := v1.ConditionFalse - if unavailable { - condition = v1.ConditionTrue - } - return nodeutil.SetNodeCondition(a.k8s, types.NodeName(nodeName), v1.NodeCondition{ - Type: v1.NodeNetworkUnavailable, - Status: condition, - Reason: "RouteCreated", - Message: "NodeController created an implicit route", - LastTransitionTime: metav1.Now(), - }) -} - -func (a *adapter) EmitNodeWarningEvent(nodeName, reason, fmt string, args ...interface{}) { - ref := &v1.ObjectReference{Kind: "Node", Name: nodeName} - a.recorder.Eventf(ref, v1.EventTypeNormal, reason, fmt, args...) -} diff --git a/pkg/controller/node/ipam/cidrset/BUILD b/pkg/controller/node/ipam/cidrset/BUILD deleted file mode 100644 index 5ea716c59ff..00000000000 --- a/pkg/controller/node/ipam/cidrset/BUILD +++ /dev/null @@ -1,34 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = ["cidr_set_test.go"], - importpath = "k8s.io/kubernetes/pkg/controller/node/ipam/cidrset", - library = ":go_default_library", - deps = ["//vendor/github.com/golang/glog:go_default_library"], -) - -go_library( - name = "go_default_library", - srcs = ["cidr_set.go"], - importpath = "k8s.io/kubernetes/pkg/controller/node/ipam/cidrset", -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/pkg/controller/node/ipam/cidrset/cidr_set.go b/pkg/controller/node/ipam/cidrset/cidr_set.go deleted file mode 100644 index c977fd08b89..00000000000 --- a/pkg/controller/node/ipam/cidrset/cidr_set.go +++ /dev/null @@ -1,303 +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 cidrset - -import ( - "encoding/binary" - "errors" - "fmt" - "math/big" - "net" - "sync" -) - -// CidrSet manages a set of CIDR ranges from which blocks of IPs can -// be allocated from. -type CidrSet struct { - sync.Mutex - clusterCIDR *net.IPNet - clusterIP net.IP - clusterMaskSize int - maxCIDRs int - nextCandidate int - used big.Int - subNetMaskSize int -} - -const ( - // The subnet mask size cannot be greater than 16 more than the cluster mask size - // TODO: https://github.com/kubernetes/kubernetes/issues/44918 - // clusterSubnetMaxDiff limited to 16 due to the uncompressed bitmap - clusterSubnetMaxDiff = 16 - // halfIPv6Len is the half of the IPv6 length - halfIPv6Len = net.IPv6len / 2 -) - -var ( - // ErrCIDRRangeNoCIDRsRemaining occurs when there are no more space - // to allocate CIDR ranges. - ErrCIDRRangeNoCIDRsRemaining = errors.New( - "CIDR allocation failed; there are no remaining CIDRs left to allocate in the accepted range") -) - -// NewCIDRSet creates a new CidrSet. -func NewCIDRSet(clusterCIDR *net.IPNet, subNetMaskSize int) *CidrSet { - clusterMask := clusterCIDR.Mask - clusterMaskSize, _ := clusterMask.Size() - - var maxCIDRs int - if (clusterCIDR.IP.To4() == nil) && (subNetMaskSize-clusterMaskSize > clusterSubnetMaxDiff) { - maxCIDRs = 0 - } else { - maxCIDRs = 1 << uint32(subNetMaskSize-clusterMaskSize) - } - return &CidrSet{ - clusterCIDR: clusterCIDR, - clusterIP: clusterCIDR.IP, - clusterMaskSize: clusterMaskSize, - maxCIDRs: maxCIDRs, - subNetMaskSize: subNetMaskSize, - } -} - -// TODO: Remove this function when upgrading to go 1.9 -var len8tab = [256]uint8{ - 0x00, 0x01, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, - 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, - 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, - 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, - 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, - 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, - 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, - 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, -} - -// TODO: Remove this function when upgrading to go 1.9 -// len64 returns the minimum number of bits required to represent x; the result is 0 for x == 0. -func len64(x uint64) (n int) { - if x >= 1<<32 { - x >>= 32 - n = 32 - } - if x >= 1<<16 { - x >>= 16 - n += 16 - } - if x >= 1<<8 { - x >>= 8 - n += 8 - } - return n + int(len8tab[x]) -} - -// TODO: Remove this function when upgrading to go 1.9 -// leadingZeros64 returns the number of leading zero bits in x; the result is 64 for x == 0. -func leadingZeros64(x uint64) int { return 64 - len64(x) } - -func (s *CidrSet) indexToCIDRBlock(index int) *net.IPNet { - var ip []byte - var mask int - switch /*v4 or v6*/ { - case s.clusterIP.To4() != nil: - { - j := uint32(index) << uint32(32-s.subNetMaskSize) - ipInt := (binary.BigEndian.Uint32(s.clusterIP)) | j - ip = make([]byte, 4) - binary.BigEndian.PutUint32(ip, ipInt) - mask = 32 - - } - case s.clusterIP.To16() != nil: - { - // leftClusterIP | rightClusterIP - // 2001:0DB8:1234:0000:0000:0000:0000:0000 - const v6NBits = 128 - const halfV6NBits = v6NBits / 2 - leftClusterIP := binary.BigEndian.Uint64(s.clusterIP[:halfIPv6Len]) - rightClusterIP := binary.BigEndian.Uint64(s.clusterIP[halfIPv6Len:]) - - leftIP, rightIP := make([]byte, halfIPv6Len), make([]byte, halfIPv6Len) - - if s.subNetMaskSize <= halfV6NBits { - // We only care about left side IP - leftClusterIP |= uint64(index) << uint(halfV6NBits-s.subNetMaskSize) - } else { - if s.clusterMaskSize < halfV6NBits { - // see how many bits are needed to reach the left side - btl := uint(s.subNetMaskSize - halfV6NBits) - // TODO: Replace this with math/bits.LeadingZeros64 when upgrading to go 1.9 - indexMaxBit := uint(64 - leadingZeros64(uint64(index))) - if indexMaxBit > btl { - leftClusterIP |= uint64(index) >> btl - } - } - // the right side will be calculated the same way either the - // subNetMaskSize affects both left and right sides - rightClusterIP |= uint64(index) << uint(v6NBits-s.subNetMaskSize) - } - binary.BigEndian.PutUint64(leftIP, leftClusterIP) - binary.BigEndian.PutUint64(rightIP, rightClusterIP) - - ip = append(leftIP, rightIP...) - mask = 128 - } - } - return &net.IPNet{ - IP: ip, - Mask: net.CIDRMask(s.subNetMaskSize, mask), - } -} - -// AllocateNext allocates the next free CIDR range. This will set the range -// as occupied and return the allocated range. -func (s *CidrSet) AllocateNext() (*net.IPNet, error) { - s.Lock() - defer s.Unlock() - - nextUnused := -1 - for i := 0; i < s.maxCIDRs; i++ { - candidate := (i + s.nextCandidate) % s.maxCIDRs - if s.used.Bit(candidate) == 0 { - nextUnused = candidate - break - } - } - if nextUnused == -1 { - return nil, ErrCIDRRangeNoCIDRsRemaining - } - s.nextCandidate = (nextUnused + 1) % s.maxCIDRs - - s.used.SetBit(&s.used, nextUnused, 1) - - return s.indexToCIDRBlock(nextUnused), nil -} - -func (s *CidrSet) getBeginingAndEndIndices(cidr *net.IPNet) (begin, end int, err error) { - begin, end = 0, s.maxCIDRs-1 - cidrMask := cidr.Mask - maskSize, _ := cidrMask.Size() - var ipSize int - - if cidr == nil { - return -1, -1, fmt.Errorf("Error getting indices for cluster cidr %v, cidr is nil", s.clusterCIDR) - } - - if !s.clusterCIDR.Contains(cidr.IP.Mask(s.clusterCIDR.Mask)) && !cidr.Contains(s.clusterCIDR.IP.Mask(cidr.Mask)) { - return -1, -1, fmt.Errorf("cidr %v is out the range of cluster cidr %v", cidr, s.clusterCIDR) - } - - if s.clusterMaskSize < maskSize { - - ipSize = net.IPv4len - if cidr.IP.To4() == nil { - ipSize = net.IPv6len - } - subNetMask := net.CIDRMask(s.subNetMaskSize, ipSize*8) - begin, err = s.getIndexForCIDR(&net.IPNet{ - IP: cidr.IP.Mask(subNetMask), - Mask: subNetMask, - }) - if err != nil { - return -1, -1, err - } - ip := make([]byte, ipSize) - if cidr.IP.To4() != nil { - ipInt := binary.BigEndian.Uint32(cidr.IP) | (^binary.BigEndian.Uint32(cidr.Mask)) - binary.BigEndian.PutUint32(ip, ipInt) - } else { - // ipIntLeft | ipIntRight - // 2001:0DB8:1234:0000:0000:0000:0000:0000 - ipIntLeft := binary.BigEndian.Uint64(cidr.IP[:net.IPv6len/2]) | (^binary.BigEndian.Uint64(cidr.Mask[:net.IPv6len/2])) - ipIntRight := binary.BigEndian.Uint64(cidr.IP[net.IPv6len/2:]) | (^binary.BigEndian.Uint64(cidr.Mask[net.IPv6len/2:])) - binary.BigEndian.PutUint64(ip[:net.IPv6len/2], ipIntLeft) - binary.BigEndian.PutUint64(ip[net.IPv6len/2:], ipIntRight) - } - end, err = s.getIndexForCIDR(&net.IPNet{ - IP: net.IP(ip).Mask(subNetMask), - Mask: subNetMask, - }) - if err != nil { - return -1, -1, err - } - } - return begin, end, nil -} - -// Release releases the given CIDR range. -func (s *CidrSet) Release(cidr *net.IPNet) error { - begin, end, err := s.getBeginingAndEndIndices(cidr) - if err != nil { - return err - } - s.Lock() - defer s.Unlock() - for i := begin; i <= end; i++ { - s.used.SetBit(&s.used, i, 0) - } - return nil -} - -// Occupy marks the given CIDR range as used. Occupy does not check if the CIDR -// range was previously used. -func (s *CidrSet) Occupy(cidr *net.IPNet) (err error) { - begin, end, err := s.getBeginingAndEndIndices(cidr) - if err != nil { - return err - } - - s.Lock() - defer s.Unlock() - for i := begin; i <= end; i++ { - s.used.SetBit(&s.used, i, 1) - } - - return nil -} - -func (s *CidrSet) getIndexForCIDR(cidr *net.IPNet) (int, error) { - return s.getIndexForIP(cidr.IP) -} - -func (s *CidrSet) getIndexForIP(ip net.IP) (int, error) { - if ip.To4() != nil { - cidrIndex := (binary.BigEndian.Uint32(s.clusterIP) ^ binary.BigEndian.Uint32(ip.To4())) >> uint32(32-s.subNetMaskSize) - if cidrIndex >= uint32(s.maxCIDRs) { - return 0, fmt.Errorf("CIDR: %v/%v is out of the range of CIDR allocator", ip, s.subNetMaskSize) - } - return int(cidrIndex), nil - } - if ip.To16() != nil { - bigIP := big.NewInt(0).SetBytes(s.clusterIP) - bigIP = bigIP.Xor(bigIP, big.NewInt(0).SetBytes(ip)) - cidrIndexBig := bigIP.Rsh(bigIP, uint(net.IPv6len*8-s.subNetMaskSize)) - cidrIndex := cidrIndexBig.Uint64() - if cidrIndex >= uint64(s.maxCIDRs) { - return 0, fmt.Errorf("CIDR: %v/%v is out of the range of CIDR allocator", ip, s.subNetMaskSize) - } - return int(cidrIndex), nil - } - - return 0, fmt.Errorf("invalid IP: %v", ip) -} diff --git a/pkg/controller/node/ipam/cloud_cidr_allocator.go b/pkg/controller/node/ipam/cloud_cidr_allocator.go deleted file mode 100644 index 5403ebb91a1..00000000000 --- a/pkg/controller/node/ipam/cloud_cidr_allocator.go +++ /dev/null @@ -1,234 +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 ipam - -import ( - "fmt" - "net" - "sync" - - "github.com/golang/glog" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/wait" - informers "k8s.io/client-go/informers/core/v1" - "k8s.io/client-go/tools/cache" - "k8s.io/client-go/tools/record" - - "k8s.io/api/core/v1" - clientset "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/scheme" - v1core "k8s.io/client-go/kubernetes/typed/core/v1" - "k8s.io/kubernetes/pkg/cloudprovider" - "k8s.io/kubernetes/pkg/cloudprovider/providers/gce" - "k8s.io/kubernetes/pkg/controller/node/util" - nodeutil "k8s.io/kubernetes/pkg/util/node" -) - -// cloudCIDRAllocator allocates node CIDRs according to IP address aliases -// assigned by the cloud provider. In this case, the allocation and -// deallocation is delegated to the external provider, and the controller -// merely takes the assignment and updates the node spec. -type cloudCIDRAllocator struct { - client clientset.Interface - cloud *gce.GCECloud - - // Channel that is used to pass updating Nodes with assigned CIDRs to the background - // This increases a throughput of CIDR assignment by not blocking on long operations. - nodeCIDRUpdateChannel chan nodeAndCIDR - recorder record.EventRecorder - - // Keep a set of nodes that are currectly being processed to avoid races in CIDR allocation - lock sync.Mutex - nodesInProcessing sets.String -} - -var _ CIDRAllocator = (*cloudCIDRAllocator)(nil) - -// NewCloudCIDRAllocator creates a new cloud CIDR allocator. -func NewCloudCIDRAllocator(client clientset.Interface, cloud cloudprovider.Interface) (CIDRAllocator, error) { - if client == nil { - glog.Fatalf("kubeClient is nil when starting NodeController") - } - - eventBroadcaster := record.NewBroadcaster() - recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "cidrAllocator"}) - eventBroadcaster.StartLogging(glog.Infof) - glog.V(0).Infof("Sending events to api server.") - eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(client.Core().RESTClient()).Events("")}) - - gceCloud, ok := cloud.(*gce.GCECloud) - if !ok { - err := fmt.Errorf("cloudCIDRAllocator does not support %v provider", cloud.ProviderName()) - return nil, err - } - - ca := &cloudCIDRAllocator{ - client: client, - cloud: gceCloud, - nodeCIDRUpdateChannel: make(chan nodeAndCIDR, cidrUpdateQueueSize), - recorder: recorder, - nodesInProcessing: sets.NewString(), - } - - for i := 0; i < cidrUpdateWorkers; i++ { - // TODO: Take stopChan as an argument to NewCloudCIDRAllocator and pass it to the worker. - go ca.worker(wait.NeverStop) - } - - glog.V(0).Infof("Using cloud CIDR allocator (provider: %v)", cloud.ProviderName()) - return ca, nil -} - -func (ca *cloudCIDRAllocator) worker(stopChan <-chan struct{}) { - for { - select { - case workItem, ok := <-ca.nodeCIDRUpdateChannel: - if !ok { - glog.Warning("Channel nodeCIDRUpdateChannel was unexpectedly closed") - return - } - ca.updateCIDRAllocation(workItem) - case <-stopChan: - return - } - } -} - -func (ca *cloudCIDRAllocator) insertNodeToProcessing(nodeName string) bool { - ca.lock.Lock() - defer ca.lock.Unlock() - if ca.nodesInProcessing.Has(nodeName) { - return false - } - ca.nodesInProcessing.Insert(nodeName) - return true -} - -func (ca *cloudCIDRAllocator) removeNodeFromProcessing(nodeName string) { - ca.lock.Lock() - defer ca.lock.Unlock() - ca.nodesInProcessing.Delete(nodeName) -} - -// WARNING: If you're adding any return calls or defer any more work from this -// function you have to make sure to update nodesInProcessing properly with the -// disposition of the node when the work is done. -func (ca *cloudCIDRAllocator) AllocateOrOccupyCIDR(node *v1.Node) error { - if node == nil { - return nil - } - if !ca.insertNodeToProcessing(node.Name) { - glog.V(2).Infof("Node %v is already in a process of CIDR assignment.", node.Name) - return nil - } - cidrs, err := ca.cloud.AliasRanges(types.NodeName(node.Name)) - if err != nil { - ca.removeNodeFromProcessing(node.Name) - util.RecordNodeStatusChange(ca.recorder, node, "CIDRNotAvailable") - return fmt.Errorf("failed to allocate cidr: %v", err) - } - if len(cidrs) == 0 { - ca.removeNodeFromProcessing(node.Name) - util.RecordNodeStatusChange(ca.recorder, node, "CIDRNotAvailable") - return fmt.Errorf("failed to allocate cidr: Node %v has no CIDRs", node.Name) - } - _, cidr, err := net.ParseCIDR(cidrs[0]) - if err != nil { - return fmt.Errorf("failed to parse string '%s' as a CIDR: %v", cidrs[0], err) - } - - glog.V(4).Infof("Putting node %s with CIDR %s into the work queue", node.Name, cidrs[0]) - ca.nodeCIDRUpdateChannel <- nodeAndCIDR{ - nodeName: node.Name, - cidr: cidr, - } - return nil -} - -// updateCIDRAllocation assigns CIDR to Node and sends an update to the API server. -func (ca *cloudCIDRAllocator) updateCIDRAllocation(data nodeAndCIDR) error { - var err error - var node *v1.Node - defer ca.removeNodeFromProcessing(data.nodeName) - podCIDR := data.cidr.String() - for rep := 0; rep < cidrUpdateRetries; rep++ { - // TODO: change it to using PATCH instead of full Node updates. - node, err = ca.client.Core().Nodes().Get(data.nodeName, metav1.GetOptions{}) - if err != nil { - glog.Errorf("Failed while getting node %v to retry updating Node.Spec.PodCIDR: %v", data.nodeName, err) - continue - } - if node.Spec.PodCIDR != "" { - if node.Spec.PodCIDR == podCIDR { - glog.V(4).Infof("Node %v already has allocated CIDR %v. It matches the proposed one.", node.Name, podCIDR) - return nil - } - glog.Errorf("PodCIDR being reassigned! Node %v spec has %v, but cloud provider has assigned %v", - node.Name, node.Spec.PodCIDR, podCIDR) - // We fall through and set the CIDR despite this error. This - // implements the same logic as implemented in the - // rangeAllocator. - // - // See https://github.com/kubernetes/kubernetes/pull/42147#discussion_r103357248 - } - node.Spec.PodCIDR = podCIDR - if _, err = ca.client.Core().Nodes().Update(node); err == nil { - glog.Infof("Set node %v PodCIDR to %v", node.Name, podCIDR) - break - } - glog.Errorf("Failed to update node %v PodCIDR to %v (%d retries left): %v", node.Name, podCIDR, cidrUpdateRetries-rep-1, err) - } - if err != nil { - util.RecordNodeStatusChange(ca.recorder, node, "CIDRAssignmentFailed") - glog.Errorf("CIDR assignment for node %v failed: %v.", data.nodeName, err) - return err - } - - err = nodeutil.SetNodeCondition(ca.client, types.NodeName(node.Name), v1.NodeCondition{ - Type: v1.NodeNetworkUnavailable, - Status: v1.ConditionFalse, - Reason: "RouteCreated", - Message: "NodeController create implicit route", - LastTransitionTime: metav1.Now(), - }) - if err != nil { - glog.Errorf("Error setting route status for node %v: %v", node.Name, err) - } - return err -} - -func (ca *cloudCIDRAllocator) ReleaseCIDR(node *v1.Node) error { - glog.V(2).Infof("Node %v PodCIDR (%v) will be released by external cloud provider (not managed by controller)", - node.Name, node.Spec.PodCIDR) - return nil -} - -func (ca *cloudCIDRAllocator) Register(nodeInformer informers.NodeInformer) { - nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: util.CreateAddNodeHandler(ca.AllocateOrOccupyCIDR), - UpdateFunc: util.CreateUpdateNodeHandler(func(_, newNode *v1.Node) error { - if newNode.Spec.PodCIDR == "" { - return ca.AllocateOrOccupyCIDR(newNode) - } - return nil - }), - DeleteFunc: util.CreateDeleteNodeHandler(ca.ReleaseCIDR), - }) -} diff --git a/pkg/controller/node/ipam/controller.go b/pkg/controller/node/ipam/controller.go deleted file mode 100644 index f8a2375678f..00000000000 --- a/pkg/controller/node/ipam/controller.go +++ /dev/null @@ -1,210 +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 ipam - -import ( - "fmt" - "net" - "sync" - "time" - - "github.com/golang/glog" - - "k8s.io/api/core/v1" - informers "k8s.io/client-go/informers/core/v1" - clientset "k8s.io/client-go/kubernetes" - "k8s.io/client-go/tools/cache" - "k8s.io/kubernetes/pkg/cloudprovider" - "k8s.io/kubernetes/pkg/cloudprovider/providers/gce" - "k8s.io/kubernetes/pkg/controller/node/ipam/cidrset" - nodesync "k8s.io/kubernetes/pkg/controller/node/ipam/sync" - "k8s.io/kubernetes/pkg/controller/node/util" -) - -// Config for the IPAM controller. -type Config struct { - // Resync is the default timeout duration when there are no errors. - Resync time.Duration - // MaxBackoff is the maximum timeout when in a error backoff state. - MaxBackoff time.Duration - // InitialRetry is the initial retry interval when an error is reported. - InitialRetry time.Duration - // Mode to use to synchronize. - Mode nodesync.NodeSyncMode -} - -// Controller is the controller for synchronizing cluster and cloud node -// pod CIDR range assignments. -type Controller struct { - config *Config - adapter *adapter - - lock sync.Mutex - syncers map[string]*nodesync.NodeSync - - set *cidrset.CidrSet -} - -// NewController returns a new instance of the IPAM controller. -func NewController( - config *Config, - kubeClient clientset.Interface, - cloud cloudprovider.Interface, - clusterCIDR, serviceCIDR *net.IPNet, - nodeCIDRMaskSize int) (*Controller, error) { - - if !nodesync.IsValidMode(config.Mode) { - return nil, fmt.Errorf("invalid IPAM controller mode %q", config.Mode) - } - - gceCloud, ok := cloud.(*gce.GCECloud) - if !ok { - return nil, fmt.Errorf("cloud IPAM controller does not support %q provider", cloud.ProviderName()) - } - - c := &Controller{ - config: config, - adapter: newAdapter(kubeClient, gceCloud), - syncers: make(map[string]*nodesync.NodeSync), - set: cidrset.NewCIDRSet(clusterCIDR, nodeCIDRMaskSize), - } - - if err := occupyServiceCIDR(c.set, clusterCIDR, serviceCIDR); err != nil { - return nil, err - } - - return c, nil -} - -// Start initializes the Controller with the existing list of nodes and -// registers the informers for node chnages. This will start synchronization -// of the node and cloud CIDR range allocations. -func (c *Controller) Start(nodeInformer informers.NodeInformer) error { - glog.V(0).Infof("Starting IPAM controller (config=%+v)", c.config) - - nodes, err := listNodes(c.adapter.k8s) - if err != nil { - return err - } - for _, node := range nodes.Items { - if node.Spec.PodCIDR != "" { - _, cidrRange, err := net.ParseCIDR(node.Spec.PodCIDR) - if err == nil { - c.set.Occupy(cidrRange) - glog.V(3).Infof("Occupying CIDR for node %q (%v)", node.Name, node.Spec.PodCIDR) - } else { - glog.Errorf("Node %q has an invalid CIDR (%q): %v", node.Name, node.Spec.PodCIDR, err) - } - } - - func() { - c.lock.Lock() - defer c.lock.Unlock() - - // XXX/bowei -- stagger the start of each sync cycle. - syncer := c.newSyncer(node.Name) - c.syncers[node.Name] = syncer - go syncer.Loop(nil) - }() - } - - nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: util.CreateAddNodeHandler(c.onAdd), - UpdateFunc: util.CreateUpdateNodeHandler(c.onUpdate), - DeleteFunc: util.CreateDeleteNodeHandler(c.onDelete), - }) - - return nil -} - -// occupyServiceCIDR removes the service CIDR range from the cluster CIDR if it -// intersects. -func occupyServiceCIDR(set *cidrset.CidrSet, clusterCIDR, serviceCIDR *net.IPNet) error { - if clusterCIDR.Contains(serviceCIDR.IP) || serviceCIDR.Contains(clusterCIDR.IP) { - if err := set.Occupy(serviceCIDR); err != nil { - return err - } - } - return nil -} - -type nodeState struct { - t Timeout -} - -func (ns *nodeState) ReportResult(err error) { - ns.t.Update(err == nil) -} - -func (ns *nodeState) ResyncTimeout() time.Duration { - return ns.t.Next() -} - -func (c *Controller) newSyncer(name string) *nodesync.NodeSync { - ns := &nodeState{ - Timeout{ - Resync: c.config.Resync, - MaxBackoff: c.config.MaxBackoff, - InitialRetry: c.config.InitialRetry, - }, - } - return nodesync.New(ns, c.adapter, c.adapter, c.config.Mode, name, c.set) -} - -func (c *Controller) onAdd(node *v1.Node) error { - c.lock.Lock() - defer c.lock.Unlock() - - if syncer, ok := c.syncers[node.Name]; !ok { - syncer = c.newSyncer(node.Name) - c.syncers[node.Name] = syncer - go syncer.Loop(nil) - } else { - glog.Warningf("Add for node %q that already exists", node.Name) - syncer.Update(node) - } - - return nil -} - -func (c *Controller) onUpdate(_, node *v1.Node) error { - c.lock.Lock() - defer c.lock.Unlock() - - if sync, ok := c.syncers[node.Name]; ok { - sync.Update(node) - } else { - glog.Errorf("Received update for non-existant node %q", node.Name) - return fmt.Errorf("unknown node %q", node.Name) - } - - return nil -} - -func (c *Controller) onDelete(node *v1.Node) error { - c.lock.Lock() - defer c.lock.Unlock() - - if syncer, ok := c.syncers[node.Name]; ok { - syncer.Delete(node) - delete(c.syncers, node.Name) - } else { - glog.Warning("Node %q was already deleted", node.Name) - } - - return nil -} diff --git a/pkg/controller/node/ipam/controller_test.go b/pkg/controller/node/ipam/controller_test.go deleted file mode 100644 index 22baf2081b0..00000000000 --- a/pkg/controller/node/ipam/controller_test.go +++ /dev/null @@ -1,64 +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 ipam - -import ( - "net" - "testing" - - "k8s.io/kubernetes/pkg/controller/node/ipam/cidrset" - "k8s.io/kubernetes/pkg/controller/node/ipam/test" -) - -func TestOccupyServiceCIDR(t *testing.T) { - const clusterCIDR = "10.1.0.0/16" - -TestCase: - for _, tc := range []struct { - serviceCIDR string - }{ - {"10.0.255.0/24"}, - {"10.1.0.0/24"}, - {"10.1.255.0/24"}, - {"10.2.0.0/24"}, - } { - serviceCIDR := test.MustParseCIDR(tc.serviceCIDR) - set := cidrset.NewCIDRSet(test.MustParseCIDR(clusterCIDR), 24) - if err := occupyServiceCIDR(set, test.MustParseCIDR(clusterCIDR), serviceCIDR); err != nil { - t.Errorf("test case %+v: occupyServiceCIDR() = %v, want nil", tc, err) - } - // Allocate until full. - var cidrs []*net.IPNet - for { - cidr, err := set.AllocateNext() - if err != nil { - if err == cidrset.ErrCIDRRangeNoCIDRsRemaining { - break - } - t.Errorf("set.AllocateNext() = %v, want %v", err, cidrset.ErrCIDRRangeNoCIDRsRemaining) - continue TestCase - } - cidrs = append(cidrs, cidr) - } - // No allocated CIDR range should intersect with serviceCIDR. - for _, c := range cidrs { - if c.Contains(serviceCIDR.IP) || serviceCIDR.Contains(c.IP) { - t.Errorf("test case %+v: allocated CIDR %v from service range", tc, c) - } - } - } -} diff --git a/pkg/controller/node/ipam/sync/BUILD b/pkg/controller/node/ipam/sync/BUILD deleted file mode 100644 index bc34f435e84..00000000000 --- a/pkg/controller/node/ipam/sync/BUILD +++ /dev/null @@ -1,41 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = ["sync.go"], - importpath = "k8s.io/kubernetes/pkg/controller/node/ipam/sync", - visibility = ["//visibility:public"], - deps = [ - "//pkg/controller/node/ipam/cidrset:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["sync_test.go"], - importpath = "k8s.io/kubernetes/pkg/controller/node/ipam/sync", - library = ":go_default_library", - deps = [ - "//pkg/controller/node/ipam/cidrset:go_default_library", - "//pkg/controller/node/ipam/test:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], - visibility = ["//visibility:public"], -) diff --git a/pkg/controller/node/ipam/test/BUILD b/pkg/controller/node/ipam/test/BUILD deleted file mode 100644 index 38155ed0970..00000000000 --- a/pkg/controller/node/ipam/test/BUILD +++ /dev/null @@ -1,22 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "go_default_library", - srcs = ["utils.go"], - importpath = "k8s.io/kubernetes/pkg/controller/node/ipam/test", - visibility = ["//visibility:public"], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], - visibility = ["//visibility:public"], -) diff --git a/pkg/controller/node/metrics.go b/pkg/controller/node/metrics.go deleted file mode 100644 index 31bba5b2332..00000000000 --- a/pkg/controller/node/metrics.go +++ /dev/null @@ -1,78 +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 node - -import ( - "sync" - - "github.com/prometheus/client_golang/prometheus" -) - -const ( - nodeControllerSubsystem = "node_collector" - zoneHealthStatisticKey = "zone_health" - zoneSizeKey = "zone_size" - zoneNoUnhealthyNodesKey = "unhealthy_nodes_in_zone" - evictionsNumberKey = "evictions_number" -) - -var ( - zoneHealth = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Subsystem: nodeControllerSubsystem, - Name: zoneHealthStatisticKey, - Help: "Gauge measuring percentage of healthy nodes per zone.", - }, - []string{"zone"}, - ) - zoneSize = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Subsystem: nodeControllerSubsystem, - Name: zoneSizeKey, - Help: "Gauge measuring number of registered Nodes per zones.", - }, - []string{"zone"}, - ) - unhealthyNodes = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Subsystem: nodeControllerSubsystem, - Name: zoneNoUnhealthyNodesKey, - Help: "Gauge measuring number of not Ready Nodes per zones.", - }, - []string{"zone"}, - ) - evictionsNumber = prometheus.NewCounterVec( - prometheus.CounterOpts{ - Subsystem: nodeControllerSubsystem, - Name: evictionsNumberKey, - Help: "Number of Node evictions that happened since current instance of NodeController started.", - }, - []string{"zone"}, - ) -) - -var registerMetrics sync.Once - -// Register the metrics that are to be monitored. -func Register() { - registerMetrics.Do(func() { - prometheus.MustRegister(zoneHealth) - prometheus.MustRegister(zoneSize) - prometheus.MustRegister(unhealthyNodes) - prometheus.MustRegister(evictionsNumber) - }) -} diff --git a/pkg/controller/node/node_controller.go b/pkg/controller/node/node_controller.go deleted file mode 100644 index a33e0b8b75a..00000000000 --- a/pkg/controller/node/node_controller.go +++ /dev/null @@ -1,1196 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package node - -import ( - "fmt" - "net" - "sync" - "time" - - "github.com/golang/glog" - - 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/labels" - "k8s.io/apimachinery/pkg/types" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/apimachinery/pkg/util/wait" - - "k8s.io/client-go/kubernetes/scheme" - v1core "k8s.io/client-go/kubernetes/typed/core/v1" - "k8s.io/client-go/tools/cache" - "k8s.io/client-go/tools/record" - "k8s.io/client-go/util/flowcontrol" - - "k8s.io/api/core/v1" - coreinformers "k8s.io/client-go/informers/core/v1" - extensionsinformers "k8s.io/client-go/informers/extensions/v1beta1" - clientset "k8s.io/client-go/kubernetes" - corelisters "k8s.io/client-go/listers/core/v1" - extensionslisters "k8s.io/client-go/listers/extensions/v1beta1" - v1node "k8s.io/kubernetes/pkg/api/v1/node" - "k8s.io/kubernetes/pkg/cloudprovider" - "k8s.io/kubernetes/pkg/controller" - "k8s.io/kubernetes/pkg/controller/node/ipam" - nodesync "k8s.io/kubernetes/pkg/controller/node/ipam/sync" - "k8s.io/kubernetes/pkg/controller/node/scheduler" - "k8s.io/kubernetes/pkg/controller/node/util" - "k8s.io/kubernetes/pkg/util/metrics" - utilnode "k8s.io/kubernetes/pkg/util/node" - "k8s.io/kubernetes/pkg/util/system" - taintutils "k8s.io/kubernetes/pkg/util/taints" - utilversion "k8s.io/kubernetes/pkg/util/version" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" -) - -func init() { - // Register prometheus metrics - Register() -} - -var ( - gracefulDeletionVersion = utilversion.MustParseSemantic("v1.1.0") - // UnreachableTaintTemplate is the taint for when a node becomes unreachable. - UnreachableTaintTemplate = &v1.Taint{ - Key: algorithm.TaintNodeUnreachable, - Effect: v1.TaintEffectNoExecute, - } - // NotReadyTaintTemplate is the taint for when a node is not ready for - // executing pods - NotReadyTaintTemplate = &v1.Taint{ - Key: algorithm.TaintNodeNotReady, - Effect: v1.TaintEffectNoExecute, - } - - nodeConditionToTaintKeyMap = map[v1.NodeConditionType]string{ - v1.NodeMemoryPressure: algorithm.TaintNodeMemoryPressure, - v1.NodeOutOfDisk: algorithm.TaintNodeOutOfDisk, - v1.NodeDiskPressure: algorithm.TaintNodeDiskPressure, - v1.NodeNetworkUnavailable: algorithm.TaintNodeNetworkUnavailable, - } - - taintKeyToNodeConditionMap = map[string]v1.NodeConditionType{ - algorithm.TaintNodeNetworkUnavailable: v1.NodeNetworkUnavailable, - algorithm.TaintNodeMemoryPressure: v1.NodeMemoryPressure, - algorithm.TaintNodeOutOfDisk: v1.NodeOutOfDisk, - algorithm.TaintNodeDiskPressure: v1.NodeDiskPressure, - } -) - -const ( - // The amount of time the nodecontroller polls on the list nodes endpoint. - apiserverStartupGracePeriod = 10 * time.Minute - // The amount of time the nodecontroller should sleep between retrying NodeStatus updates - retrySleepTime = 20 * time.Millisecond - - // ipamResyncInterval is the amount of time between when the cloud and node - // CIDR range assignments are synchronized. - ipamResyncInterval = 30 * time.Second - // ipamMaxBackoff is the maximum backoff for retrying synchronization of a - // given in the error state. - ipamMaxBackoff = 10 * time.Second - // ipamInitialRetry is the initial retry interval for retrying synchronization of a - // given in the error state. - ipamInitialBackoff = 250 * time.Millisecond -) - -// ZoneState is the state of a given zone. -type ZoneState string - -const ( - stateInitial = ZoneState("Initial") - stateNormal = ZoneState("Normal") - stateFullDisruption = ZoneState("FullDisruption") - statePartialDisruption = ZoneState("PartialDisruption") -) - -type nodeStatusData struct { - probeTimestamp metav1.Time - readyTransitionTimestamp metav1.Time - status v1.NodeStatus -} - -// Controller is the controller that manages node related cluster state. -type Controller struct { - allocateNodeCIDRs bool - allocatorType ipam.CIDRAllocatorType - - cloud cloudprovider.Interface - clusterCIDR *net.IPNet - serviceCIDR *net.IPNet - knownNodeSet map[string]*v1.Node - kubeClient clientset.Interface - // Method for easy mocking in unittest. - lookupIP func(host string) ([]net.IP, error) - // Value used if sync_nodes_status=False. Controller will not proactively - // sync node status in this case, but will monitor node status updated from kubelet. If - // it doesn't receive update for this amount of time, it will start posting "NodeReady== - // ConditionUnknown". The amount of time before which Controller start evicting pods - // is controlled via flag 'pod-eviction-timeout'. - // Note: be cautious when changing the constant, it must work with nodeStatusUpdateFrequency - // in kubelet. There are several constraints: - // 1. nodeMonitorGracePeriod must be N times more than nodeStatusUpdateFrequency, where - // N means number of retries allowed for kubelet to post node status. It is pointless - // to make nodeMonitorGracePeriod be less than nodeStatusUpdateFrequency, since there - // will only be fresh values from Kubelet at an interval of nodeStatusUpdateFrequency. - // The constant must be less than podEvictionTimeout. - // 2. nodeMonitorGracePeriod can't be too large for user experience - larger value takes - // longer for user to see up-to-date node status. - nodeMonitorGracePeriod time.Duration - // Value controlling Controller monitoring period, i.e. how often does Controller - // check node status posted from kubelet. This value should be lower than nodeMonitorGracePeriod. - // TODO: Change node status monitor to watch based. - nodeMonitorPeriod time.Duration - // Value used if sync_nodes_status=False, only for node startup. When node - // is just created, e.g. cluster bootstrap or node creation, we give a longer grace period. - nodeStartupGracePeriod time.Duration - // per Node map storing last observed Status together with a local time when it was observed. - nodeStatusMap map[string]nodeStatusData - // This timestamp is to be used instead of LastProbeTime stored in Condition. We do this - // to aviod the problem with time skew across the cluster. - now func() metav1.Time - // Lock to access evictor workers - evictorLock sync.Mutex - // workers that evicts pods from unresponsive nodes. - zonePodEvictor map[string]*scheduler.RateLimitedTimedQueue - // workers that are responsible for tainting nodes. - zoneNoExecuteTainer map[string]*scheduler.RateLimitedTimedQueue - podEvictionTimeout time.Duration - // The maximum duration before a pod evicted from a node can be forcefully terminated. - maximumGracePeriod time.Duration - recorder record.EventRecorder - - nodeLister corelisters.NodeLister - nodeInformerSynced cache.InformerSynced - - daemonSetStore extensionslisters.DaemonSetLister - daemonSetInformerSynced cache.InformerSynced - - podInformerSynced cache.InformerSynced - cidrAllocator ipam.CIDRAllocator - taintManager *scheduler.NoExecuteTaintManager - - forcefullyDeletePod func(*v1.Pod) error - nodeExistsInCloudProvider func(types.NodeName) (bool, error) - computeZoneStateFunc func(nodeConditions []*v1.NodeCondition) (int, ZoneState) - enterPartialDisruptionFunc func(nodeNum int) float32 - enterFullDisruptionFunc func(nodeNum int) float32 - - zoneStates map[string]ZoneState - evictionLimiterQPS float32 - secondaryEvictionLimiterQPS float32 - largeClusterThreshold int32 - unhealthyZoneThreshold float32 - - // if set to true Controller will start TaintManager that will evict Pods from - // tainted nodes, if they're not tolerated. - runTaintManager bool - - // if set to true Controller will taint Nodes with 'TaintNodeNotReady' and 'TaintNodeUnreachable' - // taints instead of evicting Pods itself. - useTaintBasedEvictions bool - - // if set to true, NodeController will taint Nodes based on its condition for 'NetworkUnavailable', - // 'MemoryPressure', 'OutOfDisk' and 'DiskPressure'. - taintNodeByCondition bool -} - -// NewNodeController returns a new node controller to sync instances from cloudprovider. -// This method returns an error if it is unable to initialize the CIDR bitmap with -// podCIDRs it has already allocated to nodes. Since we don't allow podCIDR changes -// currently, this should be handled as a fatal error. -func NewNodeController( - podInformer coreinformers.PodInformer, - nodeInformer coreinformers.NodeInformer, - daemonSetInformer extensionsinformers.DaemonSetInformer, - cloud cloudprovider.Interface, - kubeClient clientset.Interface, - podEvictionTimeout time.Duration, - evictionLimiterQPS float32, - secondaryEvictionLimiterQPS float32, - largeClusterThreshold int32, - unhealthyZoneThreshold float32, - nodeMonitorGracePeriod time.Duration, - nodeStartupGracePeriod time.Duration, - nodeMonitorPeriod time.Duration, - clusterCIDR *net.IPNet, - serviceCIDR *net.IPNet, - nodeCIDRMaskSize int, - allocateNodeCIDRs bool, - allocatorType ipam.CIDRAllocatorType, - runTaintManager bool, - useTaintBasedEvictions bool, - taintNodeByCondition bool) (*Controller, error) { - - if kubeClient == nil { - glog.Fatalf("kubeClient is nil when starting Controller") - } - - eventBroadcaster := record.NewBroadcaster() - recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "node-controller"}) - eventBroadcaster.StartLogging(glog.Infof) - - glog.V(0).Infof("Sending events to api server.") - eventBroadcaster.StartRecordingToSink( - &v1core.EventSinkImpl{ - Interface: v1core.New(kubeClient.Core().RESTClient()).Events(""), - }) - - if kubeClient != nil && kubeClient.Core().RESTClient().GetRateLimiter() != nil { - metrics.RegisterMetricAndTrackRateLimiterUsage("node_controller", kubeClient.Core().RESTClient().GetRateLimiter()) - } - - if allocateNodeCIDRs { - if clusterCIDR == nil { - glog.Fatal("Controller: Must specify clusterCIDR if allocateNodeCIDRs == true.") - } - mask := clusterCIDR.Mask - if maskSize, _ := mask.Size(); maskSize > nodeCIDRMaskSize { - glog.Fatal("Controller: Invalid clusterCIDR, mask size of clusterCIDR must be less than nodeCIDRMaskSize.") - } - } - - nc := &Controller{ - cloud: cloud, - knownNodeSet: make(map[string]*v1.Node), - kubeClient: kubeClient, - recorder: recorder, - podEvictionTimeout: podEvictionTimeout, - maximumGracePeriod: 5 * time.Minute, - zonePodEvictor: make(map[string]*scheduler.RateLimitedTimedQueue), - zoneNoExecuteTainer: make(map[string]*scheduler.RateLimitedTimedQueue), - nodeStatusMap: make(map[string]nodeStatusData), - nodeMonitorGracePeriod: nodeMonitorGracePeriod, - nodeMonitorPeriod: nodeMonitorPeriod, - nodeStartupGracePeriod: nodeStartupGracePeriod, - lookupIP: net.LookupIP, - now: metav1.Now, - clusterCIDR: clusterCIDR, - serviceCIDR: serviceCIDR, - allocateNodeCIDRs: allocateNodeCIDRs, - allocatorType: allocatorType, - forcefullyDeletePod: func(p *v1.Pod) error { - return util.ForcefullyDeletePod(kubeClient, p) - }, - nodeExistsInCloudProvider: func(nodeName types.NodeName) (bool, error) { - return util.NodeExistsInCloudProvider(cloud, nodeName) - }, - evictionLimiterQPS: evictionLimiterQPS, - secondaryEvictionLimiterQPS: secondaryEvictionLimiterQPS, - largeClusterThreshold: largeClusterThreshold, - unhealthyZoneThreshold: unhealthyZoneThreshold, - zoneStates: make(map[string]ZoneState), - runTaintManager: runTaintManager, - useTaintBasedEvictions: useTaintBasedEvictions && runTaintManager, - taintNodeByCondition: taintNodeByCondition, - } - if useTaintBasedEvictions { - glog.Infof("Controller is using taint based evictions.") - } - nc.enterPartialDisruptionFunc = nc.ReducedQPSFunc - nc.enterFullDisruptionFunc = nc.HealthyQPSFunc - nc.computeZoneStateFunc = nc.ComputeZoneState - - podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - pod := obj.(*v1.Pod) - if nc.taintManager != nil { - nc.taintManager.PodUpdated(nil, pod) - } - }, - UpdateFunc: func(prev, obj interface{}) { - prevPod := prev.(*v1.Pod) - newPod := obj.(*v1.Pod) - if nc.taintManager != nil { - nc.taintManager.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 { - glog.Errorf("Received unexpected object: %v", obj) - return - } - pod, ok = deletedState.Obj.(*v1.Pod) - if !ok { - glog.Errorf("DeletedFinalStateUnknown contained non-Pod object: %v", deletedState.Obj) - return - } - } - if nc.taintManager != nil { - nc.taintManager.PodUpdated(pod, nil) - } - }, - }) - nc.podInformerSynced = podInformer.Informer().HasSynced - - if nc.allocateNodeCIDRs { - if nc.allocatorType == ipam.IPAMFromClusterAllocatorType || nc.allocatorType == ipam.IPAMFromCloudAllocatorType { - cfg := &ipam.Config{ - Resync: ipamResyncInterval, - MaxBackoff: ipamMaxBackoff, - InitialRetry: ipamInitialBackoff, - } - switch nc.allocatorType { - case ipam.IPAMFromClusterAllocatorType: - cfg.Mode = nodesync.SyncFromCluster - case ipam.IPAMFromCloudAllocatorType: - cfg.Mode = nodesync.SyncFromCloud - } - ipamc, err := ipam.NewController(cfg, kubeClient, cloud, clusterCIDR, serviceCIDR, nodeCIDRMaskSize) - if err != nil { - glog.Fatalf("Error creating ipam controller: %v", err) - } - if err := ipamc.Start(nodeInformer); err != nil { - glog.Fatalf("Error trying to Init(): %v", err) - } - } else { - var err error - nc.cidrAllocator, err = ipam.New( - kubeClient, cloud, nc.allocatorType, nc.clusterCIDR, nc.serviceCIDR, nodeCIDRMaskSize) - if err != nil { - return nil, err - } - nc.cidrAllocator.Register(nodeInformer) - } - } - - if nc.runTaintManager { - nc.taintManager = scheduler.NewNoExecuteTaintManager(kubeClient) - nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: util.CreateAddNodeHandler(func(node *v1.Node) error { - nc.taintManager.NodeUpdated(nil, node) - return nil - }), - UpdateFunc: util.CreateUpdateNodeHandler(func(oldNode, newNode *v1.Node) error { - nc.taintManager.NodeUpdated(oldNode, newNode) - return nil - }), - DeleteFunc: util.CreateDeleteNodeHandler(func(node *v1.Node) error { - nc.taintManager.NodeUpdated(node, nil) - return nil - }), - }) - } - - if nc.taintNodeByCondition { - glog.Infof("Controller will taint node by condition.") - nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: util.CreateAddNodeHandler(func(node *v1.Node) error { - return nc.doNoScheduleTaintingPass(node) - }), - UpdateFunc: util.CreateUpdateNodeHandler(func(_, newNode *v1.Node) error { - return nc.doNoScheduleTaintingPass(newNode) - }), - }) - } - - // NOTE(resouer): nodeInformer to substitute deprecated taint key (notReady -> not-ready). - // Remove this logic when we don't need this backwards compatibility - nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: util.CreateAddNodeHandler(func(node *v1.Node) error { - return nc.doFixDeprecatedTaintKeyPass(node) - }), - UpdateFunc: util.CreateUpdateNodeHandler(func(_, newNode *v1.Node) error { - return nc.doFixDeprecatedTaintKeyPass(newNode) - }), - }) - - nc.nodeLister = nodeInformer.Lister() - nc.nodeInformerSynced = nodeInformer.Informer().HasSynced - - nc.daemonSetStore = daemonSetInformer.Lister() - nc.daemonSetInformerSynced = daemonSetInformer.Informer().HasSynced - - return nc, nil -} - -func (nc *Controller) doEvictionPass() { - nc.evictorLock.Lock() - defer nc.evictorLock.Unlock() - for k := range nc.zonePodEvictor { - // Function should return 'false' and a time after which it should be retried, or 'true' if it shouldn't (it succeeded). - nc.zonePodEvictor[k].Try(func(value scheduler.TimedValue) (bool, time.Duration) { - node, err := nc.nodeLister.Get(value.Value) - if apierrors.IsNotFound(err) { - glog.Warningf("Node %v no longer present in nodeLister!", value.Value) - } else if err != nil { - glog.Warningf("Failed to get Node %v from the nodeLister: %v", value.Value, err) - } else { - zone := utilnode.GetZoneKey(node) - evictionsNumber.WithLabelValues(zone).Inc() - } - nodeUID, _ := value.UID.(string) - remaining, err := util.DeletePods(nc.kubeClient, nc.recorder, value.Value, nodeUID, nc.daemonSetStore) - if err != nil { - utilruntime.HandleError(fmt.Errorf("unable to evict node %q: %v", value.Value, err)) - return false, 0 - } - if remaining { - glog.Infof("Pods awaiting deletion due to Controller eviction") - } - return true, 0 - }) - } -} - -// doFixDeprecatedTaintKeyPass checks and replaces deprecated taint key with proper key name if needed. -func (nc *Controller) doFixDeprecatedTaintKeyPass(node *v1.Node) error { - taintsToAdd := []*v1.Taint{} - taintsToDel := []*v1.Taint{} - - for _, taint := range node.Spec.Taints { - if taint.Key == algorithm.DeprecatedTaintNodeNotReady { - // delete old taint - tDel := taint - taintsToDel = append(taintsToDel, &tDel) - - // add right taint - tAdd := taint - tAdd.Key = algorithm.TaintNodeNotReady - taintsToAdd = append(taintsToAdd, &tAdd) - - glog.Warningf("Detected deprecated taint key: %v on node: %v, will substitute it with %v", - algorithm.DeprecatedTaintNodeNotReady, node.GetName(), algorithm.TaintNodeNotReady) - - break - } - } - - if len(taintsToAdd) == 0 && len(taintsToDel) == 0 { - return nil - } - if !util.SwapNodeControllerTaint(nc.kubeClient, taintsToAdd, taintsToDel, node) { - return fmt.Errorf("failed to swap taints of node %+v", node) - } - return nil -} - -func (nc *Controller) doNoScheduleTaintingPass(node *v1.Node) error { - // Map node's condition to Taints. - taints := []v1.Taint{} - for _, condition := range node.Status.Conditions { - if _, found := nodeConditionToTaintKeyMap[condition.Type]; found { - if condition.Status == v1.ConditionTrue { - taints = append(taints, v1.Taint{ - Key: nodeConditionToTaintKeyMap[condition.Type], - Effect: v1.TaintEffectNoSchedule, - }) - } - } - } - nodeTaints := taintutils.TaintSetFilter(node.Spec.Taints, func(t *v1.Taint) bool { - _, found := taintKeyToNodeConditionMap[t.Key] - return found - }) - taintsToAdd, taintsToDel := taintutils.TaintSetDiff(taints, nodeTaints) - // If nothing to add not delete, return true directly. - if len(taintsToAdd) == 0 && len(taintsToDel) == 0 { - return nil - } - if !util.SwapNodeControllerTaint(nc.kubeClient, taintsToAdd, taintsToDel, node) { - return fmt.Errorf("failed to swap taints of node %+v", node) - } - return nil -} - -func (nc *Controller) doNoExecuteTaintingPass() { - nc.evictorLock.Lock() - defer nc.evictorLock.Unlock() - for k := range nc.zoneNoExecuteTainer { - // Function should return 'false' and a time after which it should be retried, or 'true' if it shouldn't (it succeeded). - nc.zoneNoExecuteTainer[k].Try(func(value scheduler.TimedValue) (bool, time.Duration) { - node, err := nc.nodeLister.Get(value.Value) - if apierrors.IsNotFound(err) { - glog.Warningf("Node %v no longer present in nodeLister!", value.Value) - return true, 0 - } else if err != nil { - glog.Warningf("Failed to get Node %v from the nodeLister: %v", value.Value, err) - // retry in 50 millisecond - return false, 50 * time.Millisecond - } else { - zone := utilnode.GetZoneKey(node) - evictionsNumber.WithLabelValues(zone).Inc() - } - _, condition := v1node.GetNodeCondition(&node.Status, v1.NodeReady) - // Because we want to mimic NodeStatus.Condition["Ready"] we make "unreachable" and "not ready" taints mutually exclusive. - taintToAdd := v1.Taint{} - oppositeTaint := v1.Taint{} - if condition.Status == v1.ConditionFalse { - taintToAdd = *NotReadyTaintTemplate - oppositeTaint = *UnreachableTaintTemplate - } else if condition.Status == v1.ConditionUnknown { - taintToAdd = *UnreachableTaintTemplate - oppositeTaint = *NotReadyTaintTemplate - } else { - // It seems that the Node is ready again, so there's no need to taint it. - glog.V(4).Infof("Node %v was in a taint queue, but it's ready now. Ignoring taint request.", value.Value) - return true, 0 - } - - return util.SwapNodeControllerTaint(nc.kubeClient, []*v1.Taint{&taintToAdd}, []*v1.Taint{&oppositeTaint}, node), 0 - }) - } -} - -// Run starts an asynchronous loop that monitors the status of cluster nodes. -func (nc *Controller) Run(stopCh <-chan struct{}) { - defer utilruntime.HandleCrash() - - glog.Infof("Starting node controller") - defer glog.Infof("Shutting down node controller") - - if !controller.WaitForCacheSync("node", stopCh, nc.nodeInformerSynced, nc.podInformerSynced, nc.daemonSetInformerSynced) { - return - } - - // Incorporate the results of node status pushed from kubelet to master. - go wait.Until(func() { - if err := nc.monitorNodeStatus(); err != nil { - glog.Errorf("Error monitoring node status: %v", err) - } - }, nc.nodeMonitorPeriod, wait.NeverStop) - - if nc.runTaintManager { - go nc.taintManager.Run(wait.NeverStop) - } - - if nc.useTaintBasedEvictions { - // Handling taint based evictions. Because we don't want a dedicated logic in TaintManager for NC-originated - // taints and we normally don't rate limit evictions caused by taints, we need to rate limit adding taints. - go wait.Until(nc.doNoExecuteTaintingPass, scheduler.NodeEvictionPeriod, wait.NeverStop) - } else { - // Managing eviction of nodes: - // When we delete pods off a node, if the node was not empty at the time we then - // queue an eviction watcher. If we hit an error, retry deletion. - go wait.Until(nc.doEvictionPass, scheduler.NodeEvictionPeriod, wait.NeverStop) - } - - <-stopCh -} - -// addPodEvictorForNewZone checks if new zone appeared, and if so add new evictor. -func (nc *Controller) addPodEvictorForNewZone(node *v1.Node) { - zone := utilnode.GetZoneKey(node) - if _, found := nc.zoneStates[zone]; !found { - nc.zoneStates[zone] = stateInitial - if !nc.useTaintBasedEvictions { - nc.zonePodEvictor[zone] = - scheduler.NewRateLimitedTimedQueue( - flowcontrol.NewTokenBucketRateLimiter(nc.evictionLimiterQPS, scheduler.EvictionRateLimiterBurst)) - } else { - nc.zoneNoExecuteTainer[zone] = - scheduler.NewRateLimitedTimedQueue( - flowcontrol.NewTokenBucketRateLimiter(nc.evictionLimiterQPS, scheduler.EvictionRateLimiterBurst)) - } - // Init the metric for the new zone. - glog.Infof("Initializing eviction metric for zone: %v", zone) - evictionsNumber.WithLabelValues(zone).Add(0) - } -} - -// monitorNodeStatus verifies node status are constantly updated by kubelet, and if not, -// post "NodeReady==ConditionUnknown". It also evicts all pods if node is not ready or -// not reachable for a long period of time. -func (nc *Controller) monitorNodeStatus() error { - // We are listing nodes from local cache as we can tolerate some small delays - // comparing to state from etcd and there is eventual consistency anyway. - nodes, err := nc.nodeLister.List(labels.Everything()) - if err != nil { - return err - } - added, deleted, newZoneRepresentatives := nc.classifyNodes(nodes) - - for i := range newZoneRepresentatives { - nc.addPodEvictorForNewZone(newZoneRepresentatives[i]) - } - - for i := range added { - glog.V(1).Infof("Controller observed a new Node: %#v", added[i].Name) - util.RecordNodeEvent(nc.recorder, added[i].Name, string(added[i].UID), v1.EventTypeNormal, "RegisteredNode", fmt.Sprintf("Registered Node %v in Controller", added[i].Name)) - nc.knownNodeSet[added[i].Name] = added[i] - nc.addPodEvictorForNewZone(added[i]) - if nc.useTaintBasedEvictions { - nc.markNodeAsReachable(added[i]) - } else { - nc.cancelPodEviction(added[i]) - } - } - - for i := range deleted { - glog.V(1).Infof("Controller observed a Node deletion: %v", deleted[i].Name) - util.RecordNodeEvent(nc.recorder, deleted[i].Name, string(deleted[i].UID), v1.EventTypeNormal, "RemovingNode", fmt.Sprintf("Removing Node %v from Controller", deleted[i].Name)) - delete(nc.knownNodeSet, deleted[i].Name) - } - - zoneToNodeConditions := map[string][]*v1.NodeCondition{} - for i := range nodes { - var gracePeriod time.Duration - var observedReadyCondition v1.NodeCondition - var currentReadyCondition *v1.NodeCondition - node := nodes[i].DeepCopy() - if err := wait.PollImmediate(retrySleepTime, retrySleepTime*scheduler.NodeStatusUpdateRetry, func() (bool, error) { - gracePeriod, observedReadyCondition, currentReadyCondition, err = nc.tryUpdateNodeStatus(node) - if err == nil { - return true, nil - } - name := node.Name - node, err = nc.kubeClient.Core().Nodes().Get(name, metav1.GetOptions{}) - if err != nil { - glog.Errorf("Failed while getting a Node to retry updating NodeStatus. Probably Node %s was deleted.", name) - return false, err - } - return false, nil - }); err != nil { - glog.Errorf("Update status of Node '%v' from Controller error: %v. "+ - "Skipping - no pods will be evicted.", node.Name, err) - continue - } - - // We do not treat a master node as a part of the cluster for network disruption checking. - if !system.IsMasterNode(node.Name) { - zoneToNodeConditions[utilnode.GetZoneKey(node)] = append(zoneToNodeConditions[utilnode.GetZoneKey(node)], currentReadyCondition) - } - - decisionTimestamp := nc.now() - if currentReadyCondition != nil { - // Check eviction timeout against decisionTimestamp - if observedReadyCondition.Status == v1.ConditionFalse { - if nc.useTaintBasedEvictions { - // We want to update the taint straight away if Node is already tainted with the UnreachableTaint - if taintutils.TaintExists(node.Spec.Taints, UnreachableTaintTemplate) { - taintToAdd := *NotReadyTaintTemplate - if !util.SwapNodeControllerTaint(nc.kubeClient, []*v1.Taint{&taintToAdd}, []*v1.Taint{UnreachableTaintTemplate}, node) { - glog.Errorf("Failed to instantly swap UnreachableTaint to NotReadyTaint. Will try again in the next cycle.") - } - } else if nc.markNodeForTainting(node) { - glog.V(2).Infof("Node %v is NotReady as of %v. Adding it to the Taint queue.", - node.Name, - decisionTimestamp, - ) - } - } else { - if decisionTimestamp.After(nc.nodeStatusMap[node.Name].readyTransitionTimestamp.Add(nc.podEvictionTimeout)) { - if nc.evictPods(node) { - glog.V(2).Infof("Node is NotReady. Adding Pods on Node %s to eviction queue: %v is later than %v + %v", - node.Name, - decisionTimestamp, - nc.nodeStatusMap[node.Name].readyTransitionTimestamp, - nc.podEvictionTimeout, - ) - } - } - } - } - if observedReadyCondition.Status == v1.ConditionUnknown { - if nc.useTaintBasedEvictions { - // We want to update the taint straight away if Node is already tainted with the UnreachableTaint - if taintutils.TaintExists(node.Spec.Taints, NotReadyTaintTemplate) { - taintToAdd := *UnreachableTaintTemplate - if !util.SwapNodeControllerTaint(nc.kubeClient, []*v1.Taint{&taintToAdd}, []*v1.Taint{NotReadyTaintTemplate}, node) { - glog.Errorf("Failed to instantly swap UnreachableTaint to NotReadyTaint. Will try again in the next cycle.") - } - } else if nc.markNodeForTainting(node) { - glog.V(2).Infof("Node %v is unresponsive as of %v. Adding it to the Taint queue.", - node.Name, - decisionTimestamp, - ) - } - } else { - if decisionTimestamp.After(nc.nodeStatusMap[node.Name].probeTimestamp.Add(nc.podEvictionTimeout)) { - if nc.evictPods(node) { - glog.V(2).Infof("Node is unresponsive. Adding Pods on Node %s to eviction queues: %v is later than %v + %v", - node.Name, - decisionTimestamp, - nc.nodeStatusMap[node.Name].readyTransitionTimestamp, - nc.podEvictionTimeout-gracePeriod, - ) - } - } - } - } - if observedReadyCondition.Status == v1.ConditionTrue { - if nc.useTaintBasedEvictions { - removed, err := nc.markNodeAsReachable(node) - if err != nil { - glog.Errorf("Failed to remove taints from node %v. Will retry in next iteration.", node.Name) - } - if removed { - glog.V(2).Infof("Node %s is healthy again, removing all taints", node.Name) - } - } else { - if nc.cancelPodEviction(node) { - glog.V(2).Infof("Node %s is ready again, cancelled pod eviction", node.Name) - } - } - } - - // Report node event. - if currentReadyCondition.Status != v1.ConditionTrue && observedReadyCondition.Status == v1.ConditionTrue { - util.RecordNodeStatusChange(nc.recorder, node, "NodeNotReady") - if err = util.MarkAllPodsNotReady(nc.kubeClient, node); err != nil { - utilruntime.HandleError(fmt.Errorf("Unable to mark all pods NotReady on node %v: %v", node.Name, err)) - } - } - - // Check with the cloud provider to see if the node still exists. If it - // doesn't, delete the node immediately. - if currentReadyCondition.Status != v1.ConditionTrue && nc.cloud != nil { - exists, err := nc.nodeExistsInCloudProvider(types.NodeName(node.Name)) - if err != nil { - glog.Errorf("Error determining if node %v exists in cloud: %v", node.Name, err) - continue - } - if !exists { - glog.V(2).Infof("Deleting node (no longer present in cloud provider): %s", node.Name) - util.RecordNodeEvent(nc.recorder, node.Name, string(node.UID), v1.EventTypeNormal, "DeletingNode", fmt.Sprintf("Deleting Node %v because it's not present according to cloud provider", node.Name)) - go func(nodeName string) { - defer utilruntime.HandleCrash() - // Kubelet is not reporting and Cloud Provider says node - // is gone. Delete it without worrying about grace - // periods. - if err := util.ForcefullyDeleteNode(nc.kubeClient, nodeName); err != nil { - glog.Errorf("Unable to forcefully delete node %q: %v", nodeName, err) - } - }(node.Name) - } - } - } - } - nc.handleDisruption(zoneToNodeConditions, nodes) - - return nil -} - -func (nc *Controller) handleDisruption(zoneToNodeConditions map[string][]*v1.NodeCondition, nodes []*v1.Node) { - newZoneStates := map[string]ZoneState{} - allAreFullyDisrupted := true - for k, v := range zoneToNodeConditions { - zoneSize.WithLabelValues(k).Set(float64(len(v))) - unhealthy, newState := nc.computeZoneStateFunc(v) - zoneHealth.WithLabelValues(k).Set(float64(100*(len(v)-unhealthy)) / float64(len(v))) - unhealthyNodes.WithLabelValues(k).Set(float64(unhealthy)) - if newState != stateFullDisruption { - allAreFullyDisrupted = false - } - newZoneStates[k] = newState - if _, had := nc.zoneStates[k]; !had { - glog.Errorf("Setting initial state for unseen zone: %v", k) - nc.zoneStates[k] = stateInitial - } - } - - allWasFullyDisrupted := true - for k, v := range nc.zoneStates { - if _, have := zoneToNodeConditions[k]; !have { - zoneSize.WithLabelValues(k).Set(0) - zoneHealth.WithLabelValues(k).Set(100) - unhealthyNodes.WithLabelValues(k).Set(0) - delete(nc.zoneStates, k) - continue - } - if v != stateFullDisruption { - allWasFullyDisrupted = false - break - } - } - - // At least one node was responding in previous pass or in the current pass. Semantics is as follows: - // - if the new state is "partialDisruption" we call a user defined function that returns a new limiter to use, - // - if the new state is "normal" we resume normal operation (go back to default limiter settings), - // - if new state is "fullDisruption" we restore normal eviction rate, - // - unless all zones in the cluster are in "fullDisruption" - in that case we stop all evictions. - if !allAreFullyDisrupted || !allWasFullyDisrupted { - // We're switching to full disruption mode - if allAreFullyDisrupted { - glog.V(0).Info("Controller detected that all Nodes are not-Ready. Entering master disruption mode.") - for i := range nodes { - if nc.useTaintBasedEvictions { - _, err := nc.markNodeAsReachable(nodes[i]) - if err != nil { - glog.Errorf("Failed to remove taints from Node %v", nodes[i].Name) - } - } else { - nc.cancelPodEviction(nodes[i]) - } - } - // We stop all evictions. - for k := range nc.zoneStates { - if nc.useTaintBasedEvictions { - nc.zoneNoExecuteTainer[k].SwapLimiter(0) - } else { - nc.zonePodEvictor[k].SwapLimiter(0) - } - } - for k := range nc.zoneStates { - nc.zoneStates[k] = stateFullDisruption - } - // All rate limiters are updated, so we can return early here. - return - } - // We're exiting full disruption mode - if allWasFullyDisrupted { - glog.V(0).Info("Controller detected that some Nodes are Ready. Exiting master disruption mode.") - // When exiting disruption mode update probe timestamps on all Nodes. - now := nc.now() - for i := range nodes { - v := nc.nodeStatusMap[nodes[i].Name] - v.probeTimestamp = now - v.readyTransitionTimestamp = now - nc.nodeStatusMap[nodes[i].Name] = v - } - // We reset all rate limiters to settings appropriate for the given state. - for k := range nc.zoneStates { - nc.setLimiterInZone(k, len(zoneToNodeConditions[k]), newZoneStates[k]) - nc.zoneStates[k] = newZoneStates[k] - } - return - } - // We know that there's at least one not-fully disrupted so, - // we can use default behavior for rate limiters - for k, v := range nc.zoneStates { - newState := newZoneStates[k] - if v == newState { - continue - } - glog.V(0).Infof("Controller detected that zone %v is now in state %v.", k, newState) - nc.setLimiterInZone(k, len(zoneToNodeConditions[k]), newState) - nc.zoneStates[k] = newState - } - } -} - -func (nc *Controller) setLimiterInZone(zone string, zoneSize int, state ZoneState) { - switch state { - case stateNormal: - if nc.useTaintBasedEvictions { - nc.zoneNoExecuteTainer[zone].SwapLimiter(nc.evictionLimiterQPS) - } else { - nc.zonePodEvictor[zone].SwapLimiter(nc.evictionLimiterQPS) - } - case statePartialDisruption: - if nc.useTaintBasedEvictions { - nc.zoneNoExecuteTainer[zone].SwapLimiter( - nc.enterPartialDisruptionFunc(zoneSize)) - } else { - nc.zonePodEvictor[zone].SwapLimiter( - nc.enterPartialDisruptionFunc(zoneSize)) - } - case stateFullDisruption: - if nc.useTaintBasedEvictions { - nc.zoneNoExecuteTainer[zone].SwapLimiter( - nc.enterFullDisruptionFunc(zoneSize)) - } else { - nc.zonePodEvictor[zone].SwapLimiter( - nc.enterFullDisruptionFunc(zoneSize)) - } - } -} - -// tryUpdateNodeStatus checks a given node's conditions and tries to update it. Returns grace period to -// which given node is entitled, state of current and last observed Ready Condition, and an error if it occurred. -func (nc *Controller) tryUpdateNodeStatus(node *v1.Node) (time.Duration, v1.NodeCondition, *v1.NodeCondition, error) { - var err error - var gracePeriod time.Duration - var observedReadyCondition v1.NodeCondition - _, currentReadyCondition := v1node.GetNodeCondition(&node.Status, v1.NodeReady) - if currentReadyCondition == nil { - // If ready condition is nil, then kubelet (or nodecontroller) never posted node status. - // A fake ready condition is created, where LastProbeTime and LastTransitionTime is set - // to node.CreationTimestamp to avoid handle the corner case. - observedReadyCondition = v1.NodeCondition{ - Type: v1.NodeReady, - Status: v1.ConditionUnknown, - LastHeartbeatTime: node.CreationTimestamp, - LastTransitionTime: node.CreationTimestamp, - } - gracePeriod = nc.nodeStartupGracePeriod - nc.nodeStatusMap[node.Name] = nodeStatusData{ - status: node.Status, - probeTimestamp: node.CreationTimestamp, - readyTransitionTimestamp: node.CreationTimestamp, - } - } else { - // If ready condition is not nil, make a copy of it, since we may modify it in place later. - observedReadyCondition = *currentReadyCondition - gracePeriod = nc.nodeMonitorGracePeriod - } - - savedNodeStatus, found := nc.nodeStatusMap[node.Name] - // There are following cases to check: - // - both saved and new status have no Ready Condition set - we leave everything as it is, - // - saved status have no Ready Condition, but current one does - Controller was restarted with Node data already present in etcd, - // - saved status have some Ready Condition, but current one does not - it's an error, but we fill it up because that's probably a good thing to do, - // - both saved and current statuses have Ready Conditions and they have the same LastProbeTime - nothing happened on that Node, it may be - // unresponsive, so we leave it as it is, - // - both saved and current statuses have Ready Conditions, they have different LastProbeTimes, but the same Ready Condition State - - // everything's in order, no transition occurred, we update only probeTimestamp, - // - both saved and current statuses have Ready Conditions, different LastProbeTimes and different Ready Condition State - - // Ready Condition changed it state since we last seen it, so we update both probeTimestamp and readyTransitionTimestamp. - // TODO: things to consider: - // - if 'LastProbeTime' have gone back in time its probably an error, currently we ignore it, - // - currently only correct Ready State transition outside of Node Controller is marking it ready by Kubelet, we don't check - // if that's the case, but it does not seem necessary. - var savedCondition *v1.NodeCondition - if found { - _, savedCondition = v1node.GetNodeCondition(&savedNodeStatus.status, v1.NodeReady) - } - _, observedCondition := v1node.GetNodeCondition(&node.Status, v1.NodeReady) - if !found { - glog.Warningf("Missing timestamp for Node %s. Assuming now as a timestamp.", node.Name) - savedNodeStatus = nodeStatusData{ - status: node.Status, - probeTimestamp: nc.now(), - readyTransitionTimestamp: nc.now(), - } - } else if savedCondition == nil && observedCondition != nil { - glog.V(1).Infof("Creating timestamp entry for newly observed Node %s", node.Name) - savedNodeStatus = nodeStatusData{ - status: node.Status, - probeTimestamp: nc.now(), - readyTransitionTimestamp: nc.now(), - } - } else if savedCondition != nil && observedCondition == nil { - glog.Errorf("ReadyCondition was removed from Status of Node %s", node.Name) - // TODO: figure out what to do in this case. For now we do the same thing as above. - savedNodeStatus = nodeStatusData{ - status: node.Status, - probeTimestamp: nc.now(), - readyTransitionTimestamp: nc.now(), - } - } else if savedCondition != nil && observedCondition != nil && savedCondition.LastHeartbeatTime != observedCondition.LastHeartbeatTime { - var transitionTime metav1.Time - // If ReadyCondition changed since the last time we checked, we update the transition timestamp to "now", - // otherwise we leave it as it is. - if savedCondition.LastTransitionTime != observedCondition.LastTransitionTime { - glog.V(3).Infof("ReadyCondition for Node %s transitioned from %v to %v", node.Name, savedCondition.Status, observedCondition) - transitionTime = nc.now() - } else { - transitionTime = savedNodeStatus.readyTransitionTimestamp - } - if glog.V(5) { - glog.V(5).Infof("Node %s ReadyCondition updated. Updating timestamp: %+v vs %+v.", node.Name, savedNodeStatus.status, node.Status) - } else { - glog.V(3).Infof("Node %s ReadyCondition updated. Updating timestamp.", node.Name) - } - savedNodeStatus = nodeStatusData{ - status: node.Status, - probeTimestamp: nc.now(), - readyTransitionTimestamp: transitionTime, - } - } - nc.nodeStatusMap[node.Name] = savedNodeStatus - - if nc.now().After(savedNodeStatus.probeTimestamp.Add(gracePeriod)) { - // NodeReady condition was last set longer ago than gracePeriod, so update it to Unknown - // (regardless of its current value) in the master. - if currentReadyCondition == nil { - glog.V(2).Infof("node %v is never updated by kubelet", node.Name) - node.Status.Conditions = append(node.Status.Conditions, v1.NodeCondition{ - Type: v1.NodeReady, - Status: v1.ConditionUnknown, - Reason: "NodeStatusNeverUpdated", - Message: fmt.Sprintf("Kubelet never posted node status."), - LastHeartbeatTime: node.CreationTimestamp, - LastTransitionTime: nc.now(), - }) - } else { - glog.V(4).Infof("node %v hasn't been updated for %+v. Last ready condition is: %+v", - node.Name, nc.now().Time.Sub(savedNodeStatus.probeTimestamp.Time), observedReadyCondition) - if observedReadyCondition.Status != v1.ConditionUnknown { - currentReadyCondition.Status = v1.ConditionUnknown - currentReadyCondition.Reason = "NodeStatusUnknown" - currentReadyCondition.Message = "Kubelet stopped posting node status." - // LastProbeTime is the last time we heard from kubelet. - currentReadyCondition.LastHeartbeatTime = observedReadyCondition.LastHeartbeatTime - currentReadyCondition.LastTransitionTime = nc.now() - } - } - - // remaining node conditions should also be set to Unknown - remainingNodeConditionTypes := []v1.NodeConditionType{ - v1.NodeMemoryPressure, - v1.NodeDiskPressure, - // We don't change 'NodeNetworkUnavailable' condition, as it's managed on a control plane level. - // v1.NodeNetworkUnavailable, - } - - nowTimestamp := nc.now() - for _, nodeConditionType := range remainingNodeConditionTypes { - _, currentCondition := v1node.GetNodeCondition(&node.Status, nodeConditionType) - if currentCondition == nil { - glog.V(2).Infof("Condition %v of node %v was never updated by kubelet", nodeConditionType, node.Name) - node.Status.Conditions = append(node.Status.Conditions, v1.NodeCondition{ - Type: nodeConditionType, - Status: v1.ConditionUnknown, - Reason: "NodeStatusNeverUpdated", - Message: "Kubelet never posted node status.", - LastHeartbeatTime: node.CreationTimestamp, - LastTransitionTime: nowTimestamp, - }) - } else { - glog.V(4).Infof("node %v hasn't been updated for %+v. Last %v is: %+v", - node.Name, nc.now().Time.Sub(savedNodeStatus.probeTimestamp.Time), nodeConditionType, currentCondition) - if currentCondition.Status != v1.ConditionUnknown { - currentCondition.Status = v1.ConditionUnknown - currentCondition.Reason = "NodeStatusUnknown" - currentCondition.Message = "Kubelet stopped posting node status." - currentCondition.LastTransitionTime = nowTimestamp - } - } - } - - _, currentCondition := v1node.GetNodeCondition(&node.Status, v1.NodeReady) - if !apiequality.Semantic.DeepEqual(currentCondition, &observedReadyCondition) { - if _, err = nc.kubeClient.Core().Nodes().UpdateStatus(node); err != nil { - glog.Errorf("Error updating node %s: %v", node.Name, err) - return gracePeriod, observedReadyCondition, currentReadyCondition, err - } - nc.nodeStatusMap[node.Name] = nodeStatusData{ - status: node.Status, - probeTimestamp: nc.nodeStatusMap[node.Name].probeTimestamp, - readyTransitionTimestamp: nc.now(), - } - return gracePeriod, observedReadyCondition, currentReadyCondition, nil - } - } - - return gracePeriod, observedReadyCondition, currentReadyCondition, err -} - -// classifyNodes classifies the allNodes to three categories: -// 1. added: the nodes that in 'allNodes', but not in 'knownNodeSet' -// 2. deleted: the nodes that in 'knownNodeSet', but not in 'allNodes' -// 3. newZoneRepresentatives: the nodes that in both 'knownNodeSet' and 'allNodes', but no zone states -func (nc *Controller) classifyNodes(allNodes []*v1.Node) (added, deleted, newZoneRepresentatives []*v1.Node) { - for i := range allNodes { - if _, has := nc.knownNodeSet[allNodes[i].Name]; !has { - added = append(added, allNodes[i]) - } else { - // Currently, we only consider new zone as updated. - zone := utilnode.GetZoneKey(allNodes[i]) - if _, found := nc.zoneStates[zone]; !found { - newZoneRepresentatives = append(newZoneRepresentatives, allNodes[i]) - } - } - } - - // If there's a difference between lengths of known Nodes and observed nodes - // we must have removed some Node. - if len(nc.knownNodeSet)+len(added) != len(allNodes) { - knowSetCopy := map[string]*v1.Node{} - for k, v := range nc.knownNodeSet { - knowSetCopy[k] = v - } - for i := range allNodes { - delete(knowSetCopy, allNodes[i].Name) - } - for i := range knowSetCopy { - deleted = append(deleted, knowSetCopy[i]) - } - } - return -} - -// cancelPodEviction removes any queued evictions, typically because the node is available again. It -// returns true if an eviction was queued. -func (nc *Controller) cancelPodEviction(node *v1.Node) bool { - zone := utilnode.GetZoneKey(node) - nc.evictorLock.Lock() - defer nc.evictorLock.Unlock() - wasDeleting := nc.zonePodEvictor[zone].Remove(node.Name) - if wasDeleting { - glog.V(2).Infof("Cancelling pod Eviction on Node: %v", node.Name) - return true - } - return false -} - -// evictPods queues an eviction for the provided node name, and returns false if the node is already -// queued for eviction. -func (nc *Controller) evictPods(node *v1.Node) bool { - nc.evictorLock.Lock() - defer nc.evictorLock.Unlock() - return nc.zonePodEvictor[utilnode.GetZoneKey(node)].Add(node.Name, string(node.UID)) -} - -func (nc *Controller) markNodeForTainting(node *v1.Node) bool { - nc.evictorLock.Lock() - defer nc.evictorLock.Unlock() - return nc.zoneNoExecuteTainer[utilnode.GetZoneKey(node)].Add(node.Name, string(node.UID)) -} - -func (nc *Controller) markNodeAsReachable(node *v1.Node) (bool, error) { - nc.evictorLock.Lock() - defer nc.evictorLock.Unlock() - err := controller.RemoveTaintOffNode(nc.kubeClient, node.Name, node, UnreachableTaintTemplate) - if err != nil { - glog.Errorf("Failed to remove taint from node %v: %v", node.Name, err) - return false, err - } - err = controller.RemoveTaintOffNode(nc.kubeClient, node.Name, node, NotReadyTaintTemplate) - if err != nil { - glog.Errorf("Failed to remove taint from node %v: %v", node.Name, err) - return false, err - } - return nc.zoneNoExecuteTainer[utilnode.GetZoneKey(node)].Remove(node.Name), nil -} - -// HealthyQPSFunc returns the default value for cluster eviction rate - we take -// nodeNum for consistency with ReducedQPSFunc. -func (nc *Controller) HealthyQPSFunc(nodeNum int) float32 { - return nc.evictionLimiterQPS -} - -// ReducedQPSFunc returns the QPS for when a the cluster is large make -// evictions slower, if they're small stop evictions altogether. -func (nc *Controller) ReducedQPSFunc(nodeNum int) float32 { - if int32(nodeNum) > nc.largeClusterThreshold { - return nc.secondaryEvictionLimiterQPS - } - return 0 -} - -// ComputeZoneState returns a slice of NodeReadyConditions for all Nodes in a given zone. -// The zone is considered: -// - fullyDisrupted if there're no Ready Nodes, -// - partiallyDisrupted if at least than nc.unhealthyZoneThreshold percent of Nodes are not Ready, -// - normal otherwise -func (nc *Controller) ComputeZoneState(nodeReadyConditions []*v1.NodeCondition) (int, ZoneState) { - readyNodes := 0 - notReadyNodes := 0 - for i := range nodeReadyConditions { - if nodeReadyConditions[i] != nil && nodeReadyConditions[i].Status == v1.ConditionTrue { - readyNodes++ - } else { - notReadyNodes++ - } - } - switch { - case readyNodes == 0 && notReadyNodes > 0: - return notReadyNodes, stateFullDisruption - case notReadyNodes > 2 && float32(notReadyNodes)/float32(notReadyNodes+readyNodes) >= nc.unhealthyZoneThreshold: - return notReadyNodes, statePartialDisruption - default: - return notReadyNodes, stateNormal - } -} diff --git a/pkg/controller/node/nodecontroller_test.go b/pkg/controller/node/nodecontroller_test.go deleted file mode 100644 index f242a2516b4..00000000000 --- a/pkg/controller/node/nodecontroller_test.go +++ /dev/null @@ -1,2187 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package node - -import ( - "net" - "strings" - "testing" - "time" - - "k8s.io/api/core/v1" - extensions "k8s.io/api/extensions/v1beta1" - 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/types" - "k8s.io/apimachinery/pkg/util/diff" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/client-go/informers" - coreinformers "k8s.io/client-go/informers/core/v1" - extensionsinformers "k8s.io/client-go/informers/extensions/v1beta1" - clientset "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/fake" - testcore "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/cloudprovider" - fakecloud "k8s.io/kubernetes/pkg/cloudprovider/providers/fake" - "k8s.io/kubernetes/pkg/controller" - "k8s.io/kubernetes/pkg/controller/node/ipam" - "k8s.io/kubernetes/pkg/controller/node/scheduler" - "k8s.io/kubernetes/pkg/controller/node/util" - "k8s.io/kubernetes/pkg/controller/testutil" - kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis" - "k8s.io/kubernetes/pkg/util/node" - taintutils "k8s.io/kubernetes/pkg/util/taints" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" -) - -const ( - testNodeMonitorGracePeriod = 40 * time.Second - testNodeStartupGracePeriod = 60 * time.Second - testNodeMonitorPeriod = 5 * time.Second - testRateLimiterQPS = float32(10000) - testLargeClusterThreshold = 20 - testUnhealthyThreshold = float32(0.55) -) - -func alwaysReady() bool { return true } - -type nodeController struct { - *Controller - nodeInformer coreinformers.NodeInformer - daemonSetInformer extensionsinformers.DaemonSetInformer -} - -func newNodeControllerFromClient( - cloud cloudprovider.Interface, - kubeClient clientset.Interface, - podEvictionTimeout time.Duration, - evictionLimiterQPS float32, - secondaryEvictionLimiterQPS float32, - largeClusterThreshold int32, - unhealthyZoneThreshold float32, - nodeMonitorGracePeriod time.Duration, - nodeStartupGracePeriod time.Duration, - nodeMonitorPeriod time.Duration, - clusterCIDR *net.IPNet, - serviceCIDR *net.IPNet, - nodeCIDRMaskSize int, - allocateNodeCIDRs bool, - useTaints bool, -) (*nodeController, error) { - - factory := informers.NewSharedInformerFactory(kubeClient, controller.NoResyncPeriodFunc()) - - nodeInformer := factory.Core().V1().Nodes() - daemonSetInformer := factory.Extensions().V1beta1().DaemonSets() - - nc, err := NewNodeController( - factory.Core().V1().Pods(), - nodeInformer, - daemonSetInformer, - cloud, - kubeClient, - podEvictionTimeout, - evictionLimiterQPS, - secondaryEvictionLimiterQPS, - largeClusterThreshold, - unhealthyZoneThreshold, - nodeMonitorGracePeriod, - nodeStartupGracePeriod, - nodeMonitorPeriod, - clusterCIDR, - serviceCIDR, - nodeCIDRMaskSize, - allocateNodeCIDRs, - ipam.RangeAllocatorType, - useTaints, - useTaints, - useTaints, - ) - if err != nil { - return nil, err - } - - nc.podInformerSynced = alwaysReady - nc.nodeInformerSynced = alwaysReady - nc.daemonSetInformerSynced = alwaysReady - - return &nodeController{nc, nodeInformer, daemonSetInformer}, nil -} - -func syncNodeStore(nc *nodeController, fakeNodeHandler *testutil.FakeNodeHandler) error { - nodes, err := fakeNodeHandler.List(metav1.ListOptions{}) - if err != nil { - return err - } - newElems := make([]interface{}, 0, len(nodes.Items)) - for i := range nodes.Items { - newElems = append(newElems, &nodes.Items[i]) - } - return nc.nodeInformer.Informer().GetStore().Replace(newElems, "newRV") -} - -func TestMonitorNodeStatusEvictPods(t *testing.T) { - fakeNow := metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC) - evictionTimeout := 10 * time.Minute - labels := map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - } - - // Because of the logic that prevents NC from evicting anything when all Nodes are NotReady - // we need second healthy node in tests. Because of how the tests are written we need to update - // the status of this Node. - healthyNodeNewStatus := v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - // Node status has just been updated, and is NotReady for 10min. - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 9, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - } - - table := []struct { - fakeNodeHandler *testutil.FakeNodeHandler - daemonSets []extensions.DaemonSet - timeToPass time.Duration - newNodeStatus v1.NodeStatus - secondNodeNewStatus v1.NodeStatus - expectedEvictPods bool - description string - }{ - // Node created recently, with no status (happens only at cluster startup). - { - fakeNodeHandler: &testutil.FakeNodeHandler{ - Existing: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: fakeNow, - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node1", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - }, - Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), - }, - daemonSets: nil, - timeToPass: 0, - newNodeStatus: v1.NodeStatus{}, - secondNodeNewStatus: healthyNodeNewStatus, - expectedEvictPods: false, - description: "Node created recently, with no status.", - }, - // Node created recently without FailureDomain labels which is added back later, with no status (happens only at cluster startup). - { - fakeNodeHandler: &testutil.FakeNodeHandler{ - Existing: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: fakeNow, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node1", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - }, - Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), - }, - daemonSets: nil, - timeToPass: 0, - newNodeStatus: v1.NodeStatus{}, - secondNodeNewStatus: healthyNodeNewStatus, - expectedEvictPods: false, - description: "Node created recently without FailureDomain labels which is added back later, with no status (happens only at cluster startup).", - }, - // Node created long time ago, and kubelet posted NotReady for a short period of time. - { - fakeNodeHandler: &testutil.FakeNodeHandler{ - Existing: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionFalse, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node1", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - }, - Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), - }, - daemonSets: nil, - timeToPass: evictionTimeout, - newNodeStatus: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionFalse, - // Node status has just been updated, and is NotReady for 10min. - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 9, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - secondNodeNewStatus: healthyNodeNewStatus, - expectedEvictPods: false, - description: "Node created long time ago, and kubelet posted NotReady for a short period of time.", - }, - // Pod is ds-managed, and kubelet posted NotReady for a long period of time. - { - fakeNodeHandler: &testutil.FakeNodeHandler{ - Existing: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionFalse, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node1", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - }, - Clientset: fake.NewSimpleClientset( - &v1.PodList{ - Items: []v1.Pod{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pod0", - Namespace: "default", - Labels: map[string]string{"daemon": "yes"}, - }, - Spec: v1.PodSpec{ - NodeName: "node0", - }, - }, - }, - }, - ), - }, - daemonSets: []extensions.DaemonSet{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "ds0", - Namespace: "default", - }, - Spec: extensions.DaemonSetSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"daemon": "yes"}, - }, - }, - }, - }, - timeToPass: time.Hour, - newNodeStatus: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionFalse, - // Node status has just been updated, and is NotReady for 1hr. - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 59, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - secondNodeNewStatus: healthyNodeNewStatus, - expectedEvictPods: false, - description: "Pod is ds-managed, and kubelet posted NotReady for a long period of time.", - }, - // Node created long time ago, and kubelet posted NotReady for a long period of time. - { - fakeNodeHandler: &testutil.FakeNodeHandler{ - Existing: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionFalse, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node1", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - }, - Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), - }, - daemonSets: nil, - timeToPass: time.Hour, - newNodeStatus: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionFalse, - // Node status has just been updated, and is NotReady for 1hr. - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 59, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - secondNodeNewStatus: healthyNodeNewStatus, - expectedEvictPods: true, - description: "Node created long time ago, and kubelet posted NotReady for a long period of time.", - }, - // Node created long time ago, node controller posted Unknown for a short period of time. - { - fakeNodeHandler: &testutil.FakeNodeHandler{ - Existing: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionUnknown, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node1", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - }, - Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), - }, - daemonSets: nil, - timeToPass: evictionTimeout - testNodeMonitorGracePeriod, - newNodeStatus: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionUnknown, - // Node status was updated by nodecontroller 10min ago - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - secondNodeNewStatus: healthyNodeNewStatus, - expectedEvictPods: false, - description: "Node created long time ago, node controller posted Unknown for a short period of time.", - }, - // Node created long time ago, node controller posted Unknown for a long period of time. - { - fakeNodeHandler: &testutil.FakeNodeHandler{ - Existing: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionUnknown, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node1", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - }, - Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), - }, - daemonSets: nil, - timeToPass: 60 * time.Minute, - newNodeStatus: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionUnknown, - // Node status was updated by nodecontroller 1hr ago - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - secondNodeNewStatus: healthyNodeNewStatus, - expectedEvictPods: true, - description: "Node created long time ago, node controller posted Unknown for a long period of time.", - }, - } - - for _, item := range table { - nodeController, _ := newNodeControllerFromClient( - nil, - item.fakeNodeHandler, - evictionTimeout, - testRateLimiterQPS, - testRateLimiterQPS, - testLargeClusterThreshold, - testUnhealthyThreshold, - testNodeMonitorGracePeriod, - testNodeStartupGracePeriod, - testNodeMonitorPeriod, - nil, - nil, - 0, - false, - false) - nodeController.now = func() metav1.Time { return fakeNow } - nodeController.recorder = testutil.NewFakeRecorder() - for _, ds := range item.daemonSets { - nodeController.daemonSetInformer.Informer().GetStore().Add(&ds) - } - if err := syncNodeStore(nodeController, item.fakeNodeHandler); err != nil { - t.Errorf("unexpected error: %v", err) - } - if err := nodeController.monitorNodeStatus(); err != nil { - t.Errorf("unexpected error: %v", err) - } - if item.timeToPass > 0 { - nodeController.now = func() metav1.Time { return metav1.Time{Time: fakeNow.Add(item.timeToPass)} } - item.fakeNodeHandler.Existing[0].Status = item.newNodeStatus - item.fakeNodeHandler.Existing[1].Status = item.secondNodeNewStatus - } - if len(item.fakeNodeHandler.Existing[0].Labels) == 0 && len(item.fakeNodeHandler.Existing[1].Labels) == 0 { - item.fakeNodeHandler.Existing[0].Labels = labels - item.fakeNodeHandler.Existing[1].Labels = labels - } - if err := syncNodeStore(nodeController, item.fakeNodeHandler); err != nil { - t.Errorf("unexpected error: %v", err) - } - if err := nodeController.monitorNodeStatus(); err != nil { - t.Errorf("unexpected error: %v", err) - } - zones := testutil.GetZones(item.fakeNodeHandler) - for _, zone := range zones { - if _, ok := nodeController.zonePodEvictor[zone]; ok { - nodeController.zonePodEvictor[zone].Try(func(value scheduler.TimedValue) (bool, time.Duration) { - nodeUID, _ := value.UID.(string) - util.DeletePods(item.fakeNodeHandler, nodeController.recorder, value.Value, nodeUID, nodeController.daemonSetInformer.Lister()) - return true, 0 - }) - } else { - t.Fatalf("Zone %v was unitialized!", zone) - } - } - - podEvicted := false - for _, action := range item.fakeNodeHandler.Actions() { - if action.GetVerb() == "delete" && action.GetResource().Resource == "pods" { - podEvicted = true - } - } - - if item.expectedEvictPods != podEvicted { - t.Errorf("expected pod eviction: %+v, got %+v for %+v", item.expectedEvictPods, - podEvicted, item.description) - } - } -} - -func TestPodStatusChange(t *testing.T) { - fakeNow := metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC) - evictionTimeout := 10 * time.Minute - - // Because of the logic that prevents NC from evicting anything when all Nodes are NotReady - // we need second healthy node in tests. Because of how the tests are written we need to update - // the status of this Node. - healthyNodeNewStatus := v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - // Node status has just been updated, and is NotReady for 10min. - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 9, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - } - - // Node created long time ago, node controller posted Unknown for a long period of time. - table := []struct { - fakeNodeHandler *testutil.FakeNodeHandler - daemonSets []extensions.DaemonSet - timeToPass time.Duration - newNodeStatus v1.NodeStatus - secondNodeNewStatus v1.NodeStatus - expectedPodUpdate bool - expectedReason string - description string - }{ - { - fakeNodeHandler: &testutil.FakeNodeHandler{ - Existing: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionUnknown, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node1", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - }, - Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), - }, - timeToPass: 60 * time.Minute, - newNodeStatus: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionUnknown, - // Node status was updated by nodecontroller 1hr ago - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - secondNodeNewStatus: healthyNodeNewStatus, - expectedPodUpdate: true, - expectedReason: node.NodeUnreachablePodReason, - description: "Node created long time ago, node controller posted Unknown for a " + - "long period of time, the pod status must include reason for termination.", - }, - } - - for _, item := range table { - nodeController, _ := newNodeControllerFromClient(nil, item.fakeNodeHandler, - evictionTimeout, testRateLimiterQPS, testRateLimiterQPS, testLargeClusterThreshold, testUnhealthyThreshold, testNodeMonitorGracePeriod, - testNodeStartupGracePeriod, testNodeMonitorPeriod, nil, nil, 0, false, false) - nodeController.now = func() metav1.Time { return fakeNow } - nodeController.recorder = testutil.NewFakeRecorder() - if err := syncNodeStore(nodeController, item.fakeNodeHandler); err != nil { - t.Errorf("unexpected error: %v", err) - } - if err := nodeController.monitorNodeStatus(); err != nil { - t.Errorf("unexpected error: %v", err) - } - if item.timeToPass > 0 { - nodeController.now = func() metav1.Time { return metav1.Time{Time: fakeNow.Add(item.timeToPass)} } - item.fakeNodeHandler.Existing[0].Status = item.newNodeStatus - item.fakeNodeHandler.Existing[1].Status = item.secondNodeNewStatus - } - if err := syncNodeStore(nodeController, item.fakeNodeHandler); err != nil { - t.Errorf("unexpected error: %v", err) - } - if err := nodeController.monitorNodeStatus(); err != nil { - t.Errorf("unexpected error: %v", err) - } - zones := testutil.GetZones(item.fakeNodeHandler) - for _, zone := range zones { - nodeController.zonePodEvictor[zone].Try(func(value scheduler.TimedValue) (bool, time.Duration) { - nodeUID, _ := value.UID.(string) - util.DeletePods(item.fakeNodeHandler, nodeController.recorder, value.Value, nodeUID, nodeController.daemonSetStore) - return true, 0 - }) - } - - podReasonUpdate := false - for _, action := range item.fakeNodeHandler.Actions() { - if action.GetVerb() == "update" && action.GetResource().Resource == "pods" { - updateReason := action.(testcore.UpdateActionImpl).GetObject().(*v1.Pod).Status.Reason - podReasonUpdate = true - if updateReason != item.expectedReason { - t.Errorf("expected pod status reason: %+v, got %+v for %+v", item.expectedReason, updateReason, item.description) - } - } - } - - if podReasonUpdate != item.expectedPodUpdate { - t.Errorf("expected pod update: %+v, got %+v for %+v", podReasonUpdate, item.expectedPodUpdate, item.description) - } - } - -} - -func TestMonitorNodeStatusEvictPodsWithDisruption(t *testing.T) { - fakeNow := metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC) - evictionTimeout := 10 * time.Minute - timeToPass := 60 * time.Minute - - // Because of the logic that prevents NC from evicting anything when all Nodes are NotReady - // we need second healthy node in tests. Because of how the tests are written we need to update - // the status of this Node. - healthyNodeNewStatus := v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 13, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - } - unhealthyNodeNewStatus := v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionUnknown, - // Node status was updated by nodecontroller 1hr ago - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - } - - table := []struct { - nodeList []*v1.Node - podList []v1.Pod - updatedNodeStatuses []v1.NodeStatus - expectedInitialStates map[string]ZoneState - expectedFollowingStates map[string]ZoneState - expectedEvictPods bool - description string - }{ - // NetworkDisruption: Node created long time ago, node controller posted Unknown for a long period of time on both Nodes. - // Only zone is down - eviction shouldn't take place - { - nodeList: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionUnknown, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node1", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionUnknown, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - }, - podList: []v1.Pod{*testutil.NewPod("pod0", "node0")}, - updatedNodeStatuses: []v1.NodeStatus{ - unhealthyNodeNewStatus, - unhealthyNodeNewStatus, - }, - expectedInitialStates: map[string]ZoneState{testutil.CreateZoneID("region1", "zone1"): stateFullDisruption}, - expectedFollowingStates: map[string]ZoneState{testutil.CreateZoneID("region1", "zone1"): stateFullDisruption}, - expectedEvictPods: false, - description: "Network Disruption: Only zone is down - eviction shouldn't take place.", - }, - // NetworkDisruption: Node created long time ago, node controller posted Unknown for a long period of time on both Nodes. - // Both zones down - eviction shouldn't take place - { - nodeList: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionUnknown, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node1", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region2", - kubeletapis.LabelZoneFailureDomain: "zone2", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionUnknown, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - }, - - podList: []v1.Pod{*testutil.NewPod("pod0", "node0")}, - updatedNodeStatuses: []v1.NodeStatus{ - unhealthyNodeNewStatus, - unhealthyNodeNewStatus, - }, - expectedInitialStates: map[string]ZoneState{ - testutil.CreateZoneID("region1", "zone1"): stateFullDisruption, - testutil.CreateZoneID("region2", "zone2"): stateFullDisruption, - }, - expectedFollowingStates: map[string]ZoneState{ - testutil.CreateZoneID("region1", "zone1"): stateFullDisruption, - testutil.CreateZoneID("region2", "zone2"): stateFullDisruption, - }, - expectedEvictPods: false, - description: "Network Disruption: Both zones down - eviction shouldn't take place.", - }, - // NetworkDisruption: Node created long time ago, node controller posted Unknown for a long period of time on both Nodes. - // One zone is down - eviction should take place - { - nodeList: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionUnknown, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node1", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone2", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - }, - podList: []v1.Pod{*testutil.NewPod("pod0", "node0")}, - updatedNodeStatuses: []v1.NodeStatus{ - unhealthyNodeNewStatus, - healthyNodeNewStatus, - }, - expectedInitialStates: map[string]ZoneState{ - testutil.CreateZoneID("region1", "zone1"): stateFullDisruption, - testutil.CreateZoneID("region1", "zone2"): stateNormal, - }, - expectedFollowingStates: map[string]ZoneState{ - testutil.CreateZoneID("region1", "zone1"): stateFullDisruption, - testutil.CreateZoneID("region1", "zone2"): stateNormal, - }, - expectedEvictPods: true, - description: "Network Disruption: One zone is down - eviction should take place.", - }, - // NetworkDisruption: Node created long time ago, node controller posted Unknown for a long period - // of on first Node, eviction should stop even though -master Node is healthy. - { - nodeList: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionUnknown, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node-master", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - }, - podList: []v1.Pod{*testutil.NewPod("pod0", "node0")}, - updatedNodeStatuses: []v1.NodeStatus{ - unhealthyNodeNewStatus, - healthyNodeNewStatus, - }, - expectedInitialStates: map[string]ZoneState{ - testutil.CreateZoneID("region1", "zone1"): stateFullDisruption, - }, - expectedFollowingStates: map[string]ZoneState{ - testutil.CreateZoneID("region1", "zone1"): stateFullDisruption, - }, - expectedEvictPods: false, - description: "NetworkDisruption: eviction should stop, only -master Node is healthy", - }, - // NetworkDisruption: Node created long time ago, node controller posted Unknown for a long period of time on both Nodes. - // Initially both zones down, one comes back - eviction should take place - { - nodeList: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionUnknown, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node1", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone2", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionUnknown, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - }, - - podList: []v1.Pod{*testutil.NewPod("pod0", "node0")}, - updatedNodeStatuses: []v1.NodeStatus{ - unhealthyNodeNewStatus, - healthyNodeNewStatus, - }, - expectedInitialStates: map[string]ZoneState{ - testutil.CreateZoneID("region1", "zone1"): stateFullDisruption, - testutil.CreateZoneID("region1", "zone2"): stateFullDisruption, - }, - expectedFollowingStates: map[string]ZoneState{ - testutil.CreateZoneID("region1", "zone1"): stateFullDisruption, - testutil.CreateZoneID("region1", "zone2"): stateNormal, - }, - expectedEvictPods: true, - description: "Initially both zones down, one comes back - eviction should take place", - }, - // NetworkDisruption: Node created long time ago, node controller posted Unknown for a long period of time on both Nodes. - // Zone is partially disrupted - eviction should take place - { - nodeList: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionUnknown, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node1", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionUnknown, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node2", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionUnknown, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node3", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node4", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - }, - - podList: []v1.Pod{*testutil.NewPod("pod0", "node0")}, - updatedNodeStatuses: []v1.NodeStatus{ - unhealthyNodeNewStatus, - unhealthyNodeNewStatus, - unhealthyNodeNewStatus, - healthyNodeNewStatus, - healthyNodeNewStatus, - }, - expectedInitialStates: map[string]ZoneState{ - testutil.CreateZoneID("region1", "zone1"): statePartialDisruption, - }, - expectedFollowingStates: map[string]ZoneState{ - testutil.CreateZoneID("region1", "zone1"): statePartialDisruption, - }, - expectedEvictPods: true, - description: "Zone is partially disrupted - eviction should take place.", - }, - } - - for _, item := range table { - fakeNodeHandler := &testutil.FakeNodeHandler{ - Existing: item.nodeList, - Clientset: fake.NewSimpleClientset(&v1.PodList{Items: item.podList}), - } - nodeController, _ := newNodeControllerFromClient(nil, fakeNodeHandler, - evictionTimeout, testRateLimiterQPS, testRateLimiterQPS, testLargeClusterThreshold, testUnhealthyThreshold, testNodeMonitorGracePeriod, - testNodeStartupGracePeriod, testNodeMonitorPeriod, nil, nil, 0, false, false) - nodeController.now = func() metav1.Time { return fakeNow } - nodeController.enterPartialDisruptionFunc = func(nodeNum int) float32 { - return testRateLimiterQPS - } - nodeController.recorder = testutil.NewFakeRecorder() - nodeController.enterFullDisruptionFunc = func(nodeNum int) float32 { - return testRateLimiterQPS - } - if err := syncNodeStore(nodeController, fakeNodeHandler); err != nil { - t.Errorf("unexpected error: %v", err) - } - if err := nodeController.monitorNodeStatus(); err != nil { - t.Errorf("%v: unexpected error: %v", item.description, err) - } - - for zone, state := range item.expectedInitialStates { - if state != nodeController.zoneStates[zone] { - t.Errorf("%v: Unexpected zone state: %v: %v instead %v", item.description, zone, nodeController.zoneStates[zone], state) - } - } - - nodeController.now = func() metav1.Time { return metav1.Time{Time: fakeNow.Add(timeToPass)} } - for i := range item.updatedNodeStatuses { - fakeNodeHandler.Existing[i].Status = item.updatedNodeStatuses[i] - } - - if err := syncNodeStore(nodeController, fakeNodeHandler); err != nil { - t.Errorf("unexpected error: %v", err) - } - if err := nodeController.monitorNodeStatus(); err != nil { - t.Errorf("%v: unexpected error: %v", item.description, err) - } - for zone, state := range item.expectedFollowingStates { - if state != nodeController.zoneStates[zone] { - t.Errorf("%v: Unexpected zone state: %v: %v instead %v", item.description, zone, nodeController.zoneStates[zone], state) - } - } - var podEvicted bool - start := time.Now() - // Infinite loop, used for retrying in case ratelimiter fails to reload for Try function. - // this breaks when we have the status that we need for test case or when we don't see the - // intended result after 1 minute. - for { - podEvicted = nodeController.doEviction(fakeNodeHandler) - if podEvicted == item.expectedEvictPods || time.Since(start) > 1*time.Minute { - break - } - } - if item.expectedEvictPods != podEvicted { - t.Errorf("%v: expected pod eviction: %+v, got %+v", item.description, item.expectedEvictPods, podEvicted) - } - } -} - -// doEviction does the fake eviction and returns the status of eviction operation. -func (nc *nodeController) doEviction(fakeNodeHandler *testutil.FakeNodeHandler) bool { - var podEvicted bool - zones := testutil.GetZones(fakeNodeHandler) - for _, zone := range zones { - nc.zonePodEvictor[zone].Try(func(value scheduler.TimedValue) (bool, time.Duration) { - uid, _ := value.UID.(string) - util.DeletePods(fakeNodeHandler, nc.recorder, value.Value, uid, nc.daemonSetStore) - return true, 0 - }) - } - - for _, action := range fakeNodeHandler.Actions() { - if action.GetVerb() == "delete" && action.GetResource().Resource == "pods" { - podEvicted = true - return podEvicted - } - } - return podEvicted -} - -// TestCloudProviderNoRateLimit tests that monitorNodes() immediately deletes -// pods and the node when kubelet has not reported, and the cloudprovider says -// the node is gone. -func TestCloudProviderNoRateLimit(t *testing.T) { - fnh := &testutil.FakeNodeHandler{ - Existing: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionUnknown, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - }, - Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0"), *testutil.NewPod("pod1", "node0")}}), - DeleteWaitChan: make(chan struct{}), - } - nodeController, _ := newNodeControllerFromClient(nil, fnh, 10*time.Minute, - testRateLimiterQPS, testRateLimiterQPS, testLargeClusterThreshold, testUnhealthyThreshold, - testNodeMonitorGracePeriod, testNodeStartupGracePeriod, - testNodeMonitorPeriod, nil, nil, 0, false, false) - nodeController.cloud = &fakecloud.FakeCloud{} - nodeController.now = func() metav1.Time { return metav1.Date(2016, 1, 1, 12, 0, 0, 0, time.UTC) } - nodeController.recorder = testutil.NewFakeRecorder() - nodeController.nodeExistsInCloudProvider = func(nodeName types.NodeName) (bool, error) { - return false, nil - } - // monitorNodeStatus should allow this node to be immediately deleted - if err := syncNodeStore(nodeController, fnh); err != nil { - t.Errorf("unexpected error: %v", err) - } - if err := nodeController.monitorNodeStatus(); err != nil { - t.Errorf("unexpected error: %v", err) - } - select { - case <-fnh.DeleteWaitChan: - case <-time.After(wait.ForeverTestTimeout): - t.Errorf("Timed out waiting %v for node to be deleted", wait.ForeverTestTimeout) - } - if len(fnh.DeletedNodes) != 1 || fnh.DeletedNodes[0].Name != "node0" { - t.Errorf("Node was not deleted") - } - if nodeOnQueue := nodeController.zonePodEvictor[""].Remove("node0"); nodeOnQueue { - t.Errorf("Node was queued for eviction. Should have been immediately deleted.") - } -} - -func TestMonitorNodeStatusUpdateStatus(t *testing.T) { - fakeNow := metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC) - table := []struct { - fakeNodeHandler *testutil.FakeNodeHandler - timeToPass time.Duration - newNodeStatus v1.NodeStatus - expectedEvictPods bool - expectedRequestCount int - expectedNodes []*v1.Node - }{ - // Node created long time ago, without status: - // Expect Unknown status posted from node controller. - { - fakeNodeHandler: &testutil.FakeNodeHandler{ - Existing: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - }, - }, - }, - Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), - }, - expectedRequestCount: 2, // List+Update - expectedNodes: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionUnknown, - Reason: "NodeStatusNeverUpdated", - Message: "Kubelet never posted node status.", - LastHeartbeatTime: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - LastTransitionTime: fakeNow, - }, - { - Type: v1.NodeMemoryPressure, - Status: v1.ConditionUnknown, - Reason: "NodeStatusNeverUpdated", - Message: "Kubelet never posted node status.", - LastHeartbeatTime: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - LastTransitionTime: fakeNow, - }, - { - Type: v1.NodeDiskPressure, - Status: v1.ConditionUnknown, - Reason: "NodeStatusNeverUpdated", - Message: "Kubelet never posted node status.", - LastHeartbeatTime: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - LastTransitionTime: fakeNow, - }, - }, - }, - }, - }, - }, - // Node created recently, without status. - // Expect no action from node controller (within startup grace period). - { - fakeNodeHandler: &testutil.FakeNodeHandler{ - Existing: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: fakeNow, - }, - }, - }, - Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), - }, - expectedRequestCount: 1, // List - expectedNodes: nil, - }, - // Node created long time ago, with status updated by kubelet exceeds grace period. - // Expect Unknown status posted from node controller. - { - fakeNodeHandler: &testutil.FakeNodeHandler{ - Existing: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - // Node status hasn't been updated for 1hr. - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - Capacity: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), - }, - }, - Spec: v1.NodeSpec{ - ExternalID: "node0", - }, - }, - }, - Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), - }, - expectedRequestCount: 3, // (List+)List+Update - timeToPass: time.Hour, - newNodeStatus: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - // Node status hasn't been updated for 1hr. - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - Capacity: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), - }, - }, - expectedNodes: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionUnknown, - Reason: "NodeStatusUnknown", - Message: "Kubelet stopped posting node status.", - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Time{Time: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC).Add(time.Hour)}, - }, - { - Type: v1.NodeMemoryPressure, - Status: v1.ConditionUnknown, - Reason: "NodeStatusNeverUpdated", - Message: "Kubelet never posted node status.", - LastHeartbeatTime: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), // should default to node creation time if condition was never updated - LastTransitionTime: metav1.Time{Time: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC).Add(time.Hour)}, - }, - { - Type: v1.NodeDiskPressure, - Status: v1.ConditionUnknown, - Reason: "NodeStatusNeverUpdated", - Message: "Kubelet never posted node status.", - LastHeartbeatTime: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), // should default to node creation time if condition was never updated - LastTransitionTime: metav1.Time{Time: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC).Add(time.Hour)}, - }, - }, - Capacity: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), - }, - }, - Spec: v1.NodeSpec{ - ExternalID: "node0", - }, - }, - }, - }, - // Node created long time ago, with status updated recently. - // Expect no action from node controller (within monitor grace period). - { - fakeNodeHandler: &testutil.FakeNodeHandler{ - Existing: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - // Node status has just been updated. - LastHeartbeatTime: fakeNow, - LastTransitionTime: fakeNow, - }, - }, - Capacity: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), - }, - }, - Spec: v1.NodeSpec{ - ExternalID: "node0", - }, - }, - }, - Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), - }, - expectedRequestCount: 1, // List - expectedNodes: nil, - }, - } - - for i, item := range table { - nodeController, _ := newNodeControllerFromClient(nil, item.fakeNodeHandler, 5*time.Minute, - testRateLimiterQPS, testRateLimiterQPS, testLargeClusterThreshold, testUnhealthyThreshold, - testNodeMonitorGracePeriod, testNodeStartupGracePeriod, testNodeMonitorPeriod, nil, nil, 0, false, false) - nodeController.now = func() metav1.Time { return fakeNow } - nodeController.recorder = testutil.NewFakeRecorder() - if err := syncNodeStore(nodeController, item.fakeNodeHandler); err != nil { - t.Errorf("unexpected error: %v", err) - } - if err := nodeController.monitorNodeStatus(); err != nil { - t.Errorf("unexpected error: %v", err) - } - if item.timeToPass > 0 { - nodeController.now = func() metav1.Time { return metav1.Time{Time: fakeNow.Add(item.timeToPass)} } - item.fakeNodeHandler.Existing[0].Status = item.newNodeStatus - if err := syncNodeStore(nodeController, item.fakeNodeHandler); err != nil { - t.Errorf("unexpected error: %v", err) - } - if err := nodeController.monitorNodeStatus(); err != nil { - t.Errorf("unexpected error: %v", err) - } - } - if item.expectedRequestCount != item.fakeNodeHandler.RequestCount { - t.Errorf("expected %v call, but got %v.", item.expectedRequestCount, item.fakeNodeHandler.RequestCount) - } - if len(item.fakeNodeHandler.UpdatedNodes) > 0 && !apiequality.Semantic.DeepEqual(item.expectedNodes, item.fakeNodeHandler.UpdatedNodes) { - t.Errorf("Case[%d] unexpected nodes: %s", i, diff.ObjectDiff(item.expectedNodes[0], item.fakeNodeHandler.UpdatedNodes[0])) - } - if len(item.fakeNodeHandler.UpdatedNodeStatuses) > 0 && !apiequality.Semantic.DeepEqual(item.expectedNodes, item.fakeNodeHandler.UpdatedNodeStatuses) { - t.Errorf("Case[%d] unexpected nodes: %s", i, diff.ObjectDiff(item.expectedNodes[0], item.fakeNodeHandler.UpdatedNodeStatuses[0])) - } - } -} - -func TestMonitorNodeStatusMarkPodsNotReady(t *testing.T) { - fakeNow := metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC) - table := []struct { - fakeNodeHandler *testutil.FakeNodeHandler - timeToPass time.Duration - newNodeStatus v1.NodeStatus - expectedPodStatusUpdate bool - }{ - // Node created recently, without status. - // Expect no action from node controller (within startup grace period). - { - fakeNodeHandler: &testutil.FakeNodeHandler{ - Existing: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: fakeNow, - }, - }, - }, - Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), - }, - expectedPodStatusUpdate: false, - }, - // Node created long time ago, with status updated recently. - // Expect no action from node controller (within monitor grace period). - { - fakeNodeHandler: &testutil.FakeNodeHandler{ - Existing: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - // Node status has just been updated. - LastHeartbeatTime: fakeNow, - LastTransitionTime: fakeNow, - }, - }, - Capacity: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), - }, - }, - Spec: v1.NodeSpec{ - ExternalID: "node0", - }, - }, - }, - Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), - }, - expectedPodStatusUpdate: false, - }, - // Node created long time ago, with status updated by kubelet exceeds grace period. - // Expect pods status updated and Unknown node status posted from node controller - { - fakeNodeHandler: &testutil.FakeNodeHandler{ - Existing: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - // Node status hasn't been updated for 1hr. - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - Capacity: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), - }, - }, - Spec: v1.NodeSpec{ - ExternalID: "node0", - }, - }, - }, - Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), - }, - timeToPass: 1 * time.Minute, - newNodeStatus: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - // Node status hasn't been updated for 1hr. - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - Capacity: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), - }, - }, - expectedPodStatusUpdate: true, - }, - } - - for i, item := range table { - nodeController, _ := newNodeControllerFromClient(nil, item.fakeNodeHandler, 5*time.Minute, - testRateLimiterQPS, testRateLimiterQPS, testLargeClusterThreshold, testUnhealthyThreshold, - testNodeMonitorGracePeriod, testNodeStartupGracePeriod, testNodeMonitorPeriod, nil, nil, 0, false, false) - nodeController.now = func() metav1.Time { return fakeNow } - nodeController.recorder = testutil.NewFakeRecorder() - if err := syncNodeStore(nodeController, item.fakeNodeHandler); err != nil { - t.Errorf("unexpected error: %v", err) - } - if err := nodeController.monitorNodeStatus(); err != nil { - t.Errorf("Case[%d] unexpected error: %v", i, err) - } - if item.timeToPass > 0 { - nodeController.now = func() metav1.Time { return metav1.Time{Time: fakeNow.Add(item.timeToPass)} } - item.fakeNodeHandler.Existing[0].Status = item.newNodeStatus - if err := syncNodeStore(nodeController, item.fakeNodeHandler); err != nil { - t.Errorf("unexpected error: %v", err) - } - if err := nodeController.monitorNodeStatus(); err != nil { - t.Errorf("Case[%d] unexpected error: %v", i, err) - } - } - - podStatusUpdated := false - for _, action := range item.fakeNodeHandler.Actions() { - if action.GetVerb() == "update" && action.GetResource().Resource == "pods" && action.GetSubresource() == "status" { - podStatusUpdated = true - } - } - if podStatusUpdated != item.expectedPodStatusUpdate { - t.Errorf("Case[%d] expect pod status updated to be %v, but got %v", i, item.expectedPodStatusUpdate, podStatusUpdated) - } - } -} - -func TestSwapUnreachableNotReadyTaints(t *testing.T) { - fakeNow := metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC) - evictionTimeout := 10 * time.Minute - - fakeNodeHandler := &testutil.FakeNodeHandler{ - Existing: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionUnknown, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - // Because of the logic that prevents NC from evicting anything when all Nodes are NotReady - // we need second healthy node in tests. Because of how the tests are written we need to update - // the status of this Node. - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node1", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - LastHeartbeatTime: metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - }, - Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), - } - timeToPass := evictionTimeout - newNodeStatus := v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionFalse, - // Node status has just been updated, and is NotReady for 10min. - LastHeartbeatTime: metav1.Date(2017, 1, 1, 12, 9, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - } - healthyNodeNewStatus := v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - LastHeartbeatTime: metav1.Date(2017, 1, 1, 12, 10, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - } - originalTaint := UnreachableTaintTemplate - updatedTaint := NotReadyTaintTemplate - - nodeController, _ := newNodeControllerFromClient(nil, fakeNodeHandler, - evictionTimeout, testRateLimiterQPS, testRateLimiterQPS, testLargeClusterThreshold, testUnhealthyThreshold, testNodeMonitorGracePeriod, - testNodeStartupGracePeriod, testNodeMonitorPeriod, nil, nil, 0, false, true) - nodeController.now = func() metav1.Time { return fakeNow } - nodeController.recorder = testutil.NewFakeRecorder() - if err := syncNodeStore(nodeController, fakeNodeHandler); err != nil { - t.Errorf("unexpected error: %v", err) - } - if err := nodeController.monitorNodeStatus(); err != nil { - t.Errorf("unexpected error: %v", err) - } - nodeController.doNoExecuteTaintingPass() - - node0, err := fakeNodeHandler.Get("node0", metav1.GetOptions{}) - if err != nil { - t.Errorf("Can't get current node0...") - return - } - node1, err := fakeNodeHandler.Get("node1", metav1.GetOptions{}) - if err != nil { - t.Errorf("Can't get current node1...") - return - } - - if originalTaint != nil && !taintutils.TaintExists(node0.Spec.Taints, originalTaint) { - t.Errorf("Can't find taint %v in %v", originalTaint, node0.Spec.Taints) - } - - nodeController.now = func() metav1.Time { return metav1.Time{Time: fakeNow.Add(timeToPass)} } - - node0.Status = newNodeStatus - node1.Status = healthyNodeNewStatus - _, err = fakeNodeHandler.UpdateStatus(node0) - if err != nil { - t.Errorf(err.Error()) - return - } - _, err = fakeNodeHandler.UpdateStatus(node1) - if err != nil { - t.Errorf(err.Error()) - return - } - - if err := syncNodeStore(nodeController, fakeNodeHandler); err != nil { - t.Errorf("unexpected error: %v", err) - } - if err := nodeController.monitorNodeStatus(); err != nil { - t.Errorf("unexpected error: %v", err) - } - nodeController.doNoExecuteTaintingPass() - - node0, err = fakeNodeHandler.Get("node0", metav1.GetOptions{}) - if err != nil { - t.Errorf("Can't get current node0...") - return - } - if updatedTaint != nil { - if !taintutils.TaintExists(node0.Spec.Taints, updatedTaint) { - t.Errorf("Can't find taint %v in %v", updatedTaint, node0.Spec.Taints) - } - } -} - -func TestTaintsNodeByCondition(t *testing.T) { - fakeNow := metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC) - evictionTimeout := 10 * time.Minute - - fakeNodeHandler := &testutil.FakeNodeHandler{ - Existing: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - }, - Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), - } - - nodeController, _ := newNodeControllerFromClient(nil, fakeNodeHandler, evictionTimeout, - testRateLimiterQPS, testRateLimiterQPS, testLargeClusterThreshold, testUnhealthyThreshold, testNodeMonitorGracePeriod, - testNodeStartupGracePeriod, testNodeMonitorPeriod, nil, nil, 0, false, true) - nodeController.now = func() metav1.Time { return fakeNow } - nodeController.recorder = testutil.NewFakeRecorder() - - outOfDiskTaint := &v1.Taint{ - Key: algorithm.TaintNodeOutOfDisk, - Effect: v1.TaintEffectNoSchedule, - } - networkUnavailableTaint := &v1.Taint{ - Key: algorithm.TaintNodeNetworkUnavailable, - Effect: v1.TaintEffectNoSchedule, - } - - tests := []struct { - Name string - Node *v1.Node - ExpectedTaints []*v1.Taint - }{ - { - Name: "NetworkUnavailable is true", - Node: &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - { - Type: v1.NodeNetworkUnavailable, - Status: v1.ConditionTrue, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - ExpectedTaints: []*v1.Taint{networkUnavailableTaint}, - }, - { - Name: "NetworkUnavailable and OutOfDisk are true", - Node: &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - { - Type: v1.NodeNetworkUnavailable, - Status: v1.ConditionTrue, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - { - Type: v1.NodeOutOfDisk, - Status: v1.ConditionTrue, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - ExpectedTaints: []*v1.Taint{networkUnavailableTaint, outOfDiskTaint}, - }, - { - Name: "NetworkUnavailable is true, OutOfDisk is unknown", - Node: &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), - Labels: map[string]string{ - kubeletapis.LabelZoneRegion: "region1", - kubeletapis.LabelZoneFailureDomain: "zone1", - }, - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - { - Type: v1.NodeNetworkUnavailable, - Status: v1.ConditionTrue, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - { - Type: v1.NodeOutOfDisk, - Status: v1.ConditionUnknown, - LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), - }, - }, - }, - }, - ExpectedTaints: []*v1.Taint{networkUnavailableTaint}, - }, - } - - for _, test := range tests { - fakeNodeHandler.Update(test.Node) - if err := syncNodeStore(nodeController, fakeNodeHandler); err != nil { - t.Errorf("unexpected error: %v", err) - } - nodeController.doNoScheduleTaintingPass(test.Node) - if err := syncNodeStore(nodeController, fakeNodeHandler); err != nil { - t.Errorf("unexpected error: %v", err) - } - node0, err := nodeController.nodeLister.Get("node0") - if err != nil { - t.Errorf("Can't get current node0...") - return - } - if len(node0.Spec.Taints) != len(test.ExpectedTaints) { - t.Errorf("%s: Unexpected number of taints: expected %d, got %d", - test.Name, len(test.ExpectedTaints), len(node0.Spec.Taints)) - } - for _, taint := range test.ExpectedTaints { - if !taintutils.TaintExists(node0.Spec.Taints, taint) { - t.Errorf("%s: Can't find taint %v in %v", test.Name, taint, node0.Spec.Taints) - } - } - } -} - -func TestNodeEventGeneration(t *testing.T) { - fakeNow := metav1.Date(2016, 9, 10, 12, 0, 0, 0, time.UTC) - fakeNodeHandler := &testutil.FakeNodeHandler{ - Existing: []*v1.Node{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "node0", - UID: "1234567890", - CreationTimestamp: metav1.Date(2015, 8, 10, 0, 0, 0, 0, time.UTC), - }, - Spec: v1.NodeSpec{ - ExternalID: "node0", - }, - Status: v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionUnknown, - LastHeartbeatTime: metav1.Date(2015, 8, 10, 0, 0, 0, 0, time.UTC), - LastTransitionTime: metav1.Date(2015, 8, 10, 0, 0, 0, 0, time.UTC), - }, - }, - }, - }, - }, - Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), - } - - nodeController, _ := newNodeControllerFromClient(nil, fakeNodeHandler, 5*time.Minute, - testRateLimiterQPS, testRateLimiterQPS, testLargeClusterThreshold, testUnhealthyThreshold, - testNodeMonitorGracePeriod, testNodeStartupGracePeriod, - testNodeMonitorPeriod, nil, nil, 0, false, false) - nodeController.cloud = &fakecloud.FakeCloud{} - nodeController.nodeExistsInCloudProvider = func(nodeName types.NodeName) (bool, error) { - return false, nil - } - nodeController.now = func() metav1.Time { return fakeNow } - fakeRecorder := testutil.NewFakeRecorder() - nodeController.recorder = fakeRecorder - if err := syncNodeStore(nodeController, fakeNodeHandler); err != nil { - t.Errorf("unexpected error: %v", err) - } - if err := nodeController.monitorNodeStatus(); err != nil { - t.Errorf("unexpected error: %v", err) - } - if len(fakeRecorder.Events) != 2 { - t.Fatalf("unexpected events, got %v, expected %v: %+v", len(fakeRecorder.Events), 2, fakeRecorder.Events) - } - if fakeRecorder.Events[0].Reason != "RegisteredNode" || fakeRecorder.Events[1].Reason != "DeletingNode" { - var reasons []string - for _, event := range fakeRecorder.Events { - reasons = append(reasons, event.Reason) - } - t.Fatalf("unexpected events generation: %v", strings.Join(reasons, ",")) - } - for _, event := range fakeRecorder.Events { - involvedObject := event.InvolvedObject - actualUID := string(involvedObject.UID) - if actualUID != "1234567890" { - t.Fatalf("unexpected event uid: %v", actualUID) - } - } -} diff --git a/pkg/controller/node/scheduler/BUILD b/pkg/controller/node/scheduler/BUILD deleted file mode 100644 index 4c57a382861..00000000000 --- a/pkg/controller/node/scheduler/BUILD +++ /dev/null @@ -1,67 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = [ - "rate_limited_queue_test.go", - "taint_controller_test.go", - "timed_workers_test.go", - ], - importpath = "k8s.io/kubernetes/pkg/controller/node/scheduler", - library = ":go_default_library", - deps = [ - "//pkg/controller/testutil:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", - "//vendor/k8s.io/client-go/testing:go_default_library", - "//vendor/k8s.io/client-go/util/flowcontrol:go_default_library", - ], -) - -go_library( - name = "go_default_library", - srcs = [ - "rate_limited_queue.go", - "taint_controller.go", - "timed_workers.go", - ], - importpath = "k8s.io/kubernetes/pkg/controller/node/scheduler", - deps = [ - "//pkg/api/helper:go_default_library", - "//pkg/api/v1/helper:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library", - "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", - "//vendor/k8s.io/client-go/tools/record:go_default_library", - "//vendor/k8s.io/client-go/util/flowcontrol:go_default_library", - "//vendor/k8s.io/client-go/util/workqueue:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/pkg/controller/node/scheduler/taint_controller.go b/pkg/controller/node/scheduler/taint_controller.go deleted file mode 100644 index bab01348968..00000000000 --- a/pkg/controller/node/scheduler/taint_controller.go +++ /dev/null @@ -1,436 +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 scheduler - -import ( - "fmt" - "sync" - "time" - - "k8s.io/api/core/v1" - clientset "k8s.io/client-go/kubernetes" - "k8s.io/kubernetes/pkg/api/helper" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/types" - - "k8s.io/client-go/kubernetes/scheme" - v1core "k8s.io/client-go/kubernetes/typed/core/v1" - "k8s.io/client-go/tools/record" - "k8s.io/client-go/util/workqueue" - - "github.com/golang/glog" -) - -const ( - nodeUpdateChannelSize = 10 - podUpdateChannelSize = 1 - retries = 5 -) - -// Needed to make workqueue work -type updateItemInterface interface{} - -type nodeUpdateItem struct { - oldNode *v1.Node - newNode *v1.Node - newTaints []v1.Taint -} - -type podUpdateItem struct { - oldPod *v1.Pod - newPod *v1.Pod - newTolerations []v1.Toleration -} - -// NoExecuteTaintManager listens to Taint/Toleration changes and is responsible for removing Pods -// from Nodes tainted with NoExecute Taints. -type NoExecuteTaintManager struct { - client clientset.Interface - recorder record.EventRecorder - - taintEvictionQueue *TimedWorkerQueue - // keeps a map from nodeName to all noExecute taints on that Node - taintedNodesLock sync.Mutex - taintedNodes map[string][]v1.Taint - - nodeUpdateChannel chan *nodeUpdateItem - podUpdateChannel chan *podUpdateItem - - nodeUpdateQueue workqueue.Interface - podUpdateQueue workqueue.Interface -} - -func deletePodHandler(c clientset.Interface, emitEventFunc func(types.NamespacedName)) func(args *WorkArgs) error { - return func(args *WorkArgs) error { - ns := args.NamespacedName.Namespace - name := args.NamespacedName.Name - glog.V(0).Infof("NoExecuteTaintManager is deleting Pod: %v", args.NamespacedName.String()) - if emitEventFunc != nil { - emitEventFunc(args.NamespacedName) - } - var err error - for i := 0; i < retries; i++ { - err = c.Core().Pods(ns).Delete(name, &metav1.DeleteOptions{}) - if err == nil { - break - } - time.Sleep(10 * time.Millisecond) - } - return err - } -} - -func getNoExecuteTaints(taints []v1.Taint) []v1.Taint { - result := []v1.Taint{} - for i := range taints { - if taints[i].Effect == v1.TaintEffectNoExecute { - result = append(result, taints[i]) - } - } - return result -} - -func getPodsAssignedToNode(c clientset.Interface, nodeName string) ([]v1.Pod, error) { - selector := fields.SelectorFromSet(fields.Set{"spec.nodeName": nodeName}) - pods, err := c.Core().Pods(v1.NamespaceAll).List(metav1.ListOptions{ - FieldSelector: selector.String(), - LabelSelector: labels.Everything().String(), - }) - for i := 0; i < retries && err != nil; i++ { - pods, err = c.Core().Pods(v1.NamespaceAll).List(metav1.ListOptions{ - FieldSelector: selector.String(), - LabelSelector: labels.Everything().String(), - }) - time.Sleep(100 * time.Millisecond) - } - if err != nil { - return []v1.Pod{}, fmt.Errorf("failed to get Pods assigned to node %v", nodeName) - } - return pods.Items, nil -} - -// getMinTolerationTime returns minimal toleration time from the given slice, or -1 if it's infinite. -func getMinTolerationTime(tolerations []v1.Toleration) time.Duration { - minTolerationTime := int64(-1) - if len(tolerations) == 0 { - return 0 - } - - for i := range tolerations { - if tolerations[i].TolerationSeconds != nil { - tolerationSeconds := *(tolerations[i].TolerationSeconds) - if tolerationSeconds <= 0 { - return 0 - } else if tolerationSeconds < minTolerationTime || minTolerationTime == -1 { - minTolerationTime = tolerationSeconds - } - } - } - - return time.Duration(minTolerationTime) * time.Second -} - -// NewNoExecuteTaintManager creates a new NoExecuteTaintManager that will use passed clientset to -// communicate with the API server. -func NewNoExecuteTaintManager(c clientset.Interface) *NoExecuteTaintManager { - eventBroadcaster := record.NewBroadcaster() - recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "taint-controller"}) - eventBroadcaster.StartLogging(glog.Infof) - if c != nil { - glog.V(0).Infof("Sending events to api server.") - eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(c.Core().RESTClient()).Events("")}) - } else { - glog.Fatalf("kubeClient is nil when starting NodeController") - } - - tm := &NoExecuteTaintManager{ - client: c, - recorder: recorder, - taintedNodes: make(map[string][]v1.Taint), - nodeUpdateChannel: make(chan *nodeUpdateItem, nodeUpdateChannelSize), - podUpdateChannel: make(chan *podUpdateItem, podUpdateChannelSize), - - nodeUpdateQueue: workqueue.New(), - podUpdateQueue: workqueue.New(), - } - tm.taintEvictionQueue = CreateWorkerQueue(deletePodHandler(c, tm.emitPodDeletionEvent)) - - return tm -} - -// Run starts NoExecuteTaintManager which will run in loop until `stopCh` is closed. -func (tc *NoExecuteTaintManager) Run(stopCh <-chan struct{}) { - glog.V(0).Infof("Starting NoExecuteTaintManager") - // Functions that are responsible for taking work items out of the workqueues and putting them - // into channels. - go func(stopCh <-chan struct{}) { - for { - item, shutdown := tc.nodeUpdateQueue.Get() - if shutdown { - break - } - nodeUpdate := item.(*nodeUpdateItem) - select { - case <-stopCh: - break - case tc.nodeUpdateChannel <- nodeUpdate: - } - } - }(stopCh) - - go func(stopCh <-chan struct{}) { - for { - item, shutdown := tc.podUpdateQueue.Get() - if shutdown { - break - } - podUpdate := item.(*podUpdateItem) - select { - case <-stopCh: - break - case tc.podUpdateChannel <- podUpdate: - } - } - }(stopCh) - - // When processing events we want to prioritize Node updates over Pod updates, - // as NodeUpdates that interest NoExecuteTaintManager should be handled as soon as possible - - // we don't want user (or system) to wait until PodUpdate queue is drained before it can - // start evicting Pods from tainted Nodes. - for { - select { - case <-stopCh: - break - case nodeUpdate := <-tc.nodeUpdateChannel: - tc.handleNodeUpdate(nodeUpdate) - case podUpdate := <-tc.podUpdateChannel: - // If we found a Pod update we need to empty Node queue first. - priority: - for { - select { - case nodeUpdate := <-tc.nodeUpdateChannel: - tc.handleNodeUpdate(nodeUpdate) - default: - break priority - } - } - // After Node queue is emptied we process podUpdate. - tc.handlePodUpdate(podUpdate) - } - } -} - -// PodUpdated is used to notify NoExecuteTaintManager about Pod changes. -func (tc *NoExecuteTaintManager) PodUpdated(oldPod *v1.Pod, newPod *v1.Pod) { - oldTolerations := []v1.Toleration{} - if oldPod != nil { - oldTolerations = oldPod.Spec.Tolerations - } - newTolerations := []v1.Toleration{} - if newPod != nil { - newTolerations = newPod.Spec.Tolerations - } - - if oldPod != nil && newPod != nil && helper.Semantic.DeepEqual(oldTolerations, newTolerations) && oldPod.Spec.NodeName == newPod.Spec.NodeName { - return - } - updateItem := &podUpdateItem{ - oldPod: oldPod, - newPod: newPod, - newTolerations: newTolerations, - } - - tc.podUpdateQueue.Add(updateItemInterface(updateItem)) -} - -// NodeUpdated is used to notify NoExecuteTaintManager about Node changes. -func (tc *NoExecuteTaintManager) NodeUpdated(oldNode *v1.Node, newNode *v1.Node) { - oldTaints := []v1.Taint{} - if oldNode != nil { - oldTaints = oldNode.Spec.Taints - } - oldTaints = getNoExecuteTaints(oldTaints) - - newTaints := []v1.Taint{} - if newNode != nil { - newTaints = newNode.Spec.Taints - } - newTaints = getNoExecuteTaints(newTaints) - - if oldNode != nil && newNode != nil && helper.Semantic.DeepEqual(oldTaints, newTaints) { - return - } - updateItem := &nodeUpdateItem{ - oldNode: oldNode, - newNode: newNode, - newTaints: newTaints, - } - - tc.nodeUpdateQueue.Add(updateItemInterface(updateItem)) -} - -func (tc *NoExecuteTaintManager) cancelWorkWithEvent(nsName types.NamespacedName) { - if tc.taintEvictionQueue.CancelWork(nsName.String()) { - tc.emitCancelPodDeletionEvent(nsName) - } -} - -func (tc *NoExecuteTaintManager) processPodOnNode( - podNamespacedName types.NamespacedName, - nodeName string, - tolerations []v1.Toleration, - taints []v1.Taint, - now time.Time, -) { - if len(taints) == 0 { - tc.cancelWorkWithEvent(podNamespacedName) - } - allTolerated, usedTolerations := v1helper.GetMatchingTolerations(taints, tolerations) - if !allTolerated { - glog.V(2).Infof("Not all taints are tolerated after update for Pod %v on %v", podNamespacedName.String(), nodeName) - // We're canceling scheduled work (if any), as we're going to delete the Pod right away. - tc.cancelWorkWithEvent(podNamespacedName) - tc.taintEvictionQueue.AddWork(NewWorkArgs(podNamespacedName.Name, podNamespacedName.Namespace), time.Now(), time.Now()) - return - } - minTolerationTime := getMinTolerationTime(usedTolerations) - // getMinTolerationTime returns negative value to denote infinite toleration. - if minTolerationTime < 0 { - glog.V(4).Infof("New tolerations for %v tolerate forever. Scheduled deletion won't be cancelled if already scheduled.", podNamespacedName.String()) - return - } - - startTime := now - triggerTime := startTime.Add(minTolerationTime) - scheduledEviction := tc.taintEvictionQueue.GetWorkerUnsafe(podNamespacedName.String()) - if scheduledEviction != nil { - startTime = scheduledEviction.CreatedAt - if startTime.Add(minTolerationTime).Before(triggerTime) { - return - } - tc.cancelWorkWithEvent(podNamespacedName) - } - tc.taintEvictionQueue.AddWork(NewWorkArgs(podNamespacedName.Name, podNamespacedName.Namespace), startTime, triggerTime) -} - -func (tc *NoExecuteTaintManager) handlePodUpdate(podUpdate *podUpdateItem) { - // Delete - if podUpdate.newPod == nil { - pod := podUpdate.oldPod - podNamespacedName := types.NamespacedName{Namespace: pod.Namespace, Name: pod.Name} - glog.V(4).Infof("Noticed pod deletion: %#v", podNamespacedName) - tc.cancelWorkWithEvent(podNamespacedName) - return - } - // Create or Update - pod := podUpdate.newPod - podNamespacedName := types.NamespacedName{Namespace: pod.Namespace, Name: pod.Name} - glog.V(4).Infof("Noticed pod update: %#v", podNamespacedName) - nodeName := pod.Spec.NodeName - if nodeName == "" { - return - } - taints, ok := func() ([]v1.Taint, bool) { - tc.taintedNodesLock.Lock() - defer tc.taintedNodesLock.Unlock() - taints, ok := tc.taintedNodes[nodeName] - return taints, ok - }() - // It's possible that Node was deleted, or Taints were removed before, which triggered - // eviction cancelling if it was needed. - if !ok { - return - } - tc.processPodOnNode(podNamespacedName, nodeName, podUpdate.newTolerations, taints, time.Now()) -} - -func (tc *NoExecuteTaintManager) handleNodeUpdate(nodeUpdate *nodeUpdateItem) { - // Delete - if nodeUpdate.newNode == nil { - node := nodeUpdate.oldNode - glog.V(4).Infof("Noticed node deletion: %#v", node.Name) - tc.taintedNodesLock.Lock() - defer tc.taintedNodesLock.Unlock() - delete(tc.taintedNodes, node.Name) - return - } - // Create or Update - glog.V(4).Infof("Noticed node update: %#v", nodeUpdate) - node := nodeUpdate.newNode - taints := nodeUpdate.newTaints - func() { - tc.taintedNodesLock.Lock() - defer tc.taintedNodesLock.Unlock() - glog.V(4).Infof("Updating known taints on node %v: %v", node.Name, taints) - if len(taints) == 0 { - delete(tc.taintedNodes, node.Name) - } else { - tc.taintedNodes[node.Name] = taints - } - }() - pods, err := getPodsAssignedToNode(tc.client, node.Name) - if err != nil { - glog.Errorf(err.Error()) - return - } - if len(pods) == 0 { - return - } - // Short circuit, to make this controller a bit faster. - if len(taints) == 0 { - glog.V(4).Infof("All taints were removed from the Node %v. Cancelling all evictions...", node.Name) - for i := range pods { - tc.cancelWorkWithEvent(types.NamespacedName{Namespace: pods[i].Namespace, Name: pods[i].Name}) - } - return - } - - now := time.Now() - for i := range pods { - pod := &pods[i] - podNamespacedName := types.NamespacedName{Namespace: pod.Namespace, Name: pod.Name} - tc.processPodOnNode(podNamespacedName, node.Name, pod.Spec.Tolerations, taints, now) - } -} - -func (tc *NoExecuteTaintManager) emitPodDeletionEvent(nsName types.NamespacedName) { - if tc.recorder == nil { - return - } - ref := &v1.ObjectReference{ - Kind: "Pod", - Name: nsName.Name, - Namespace: nsName.Namespace, - } - tc.recorder.Eventf(ref, v1.EventTypeNormal, "TaintManagerEviction", "Marking for deletion Pod %s", nsName.String()) -} - -func (tc *NoExecuteTaintManager) emitCancelPodDeletionEvent(nsName types.NamespacedName) { - if tc.recorder == nil { - return - } - ref := &v1.ObjectReference{ - Kind: "Pod", - Name: nsName.Name, - Namespace: nsName.Namespace, - } - tc.recorder.Eventf(ref, v1.EventTypeNormal, "TaintManagerEviction", "Cancelling deletion of Pod %s", nsName.String()) -} diff --git a/pkg/controller/node/util/BUILD b/pkg/controller/node/util/BUILD deleted file mode 100644 index d49ec9e920d..00000000000 --- a/pkg/controller/node/util/BUILD +++ /dev/null @@ -1,45 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["controller_utils.go"], - importpath = "k8s.io/kubernetes/pkg/controller/node/util", - deps = [ - "//pkg/api:go_default_library", - "//pkg/cloudprovider:go_default_library", - "//pkg/controller:go_default_library", - "//pkg/kubelet/util/format:go_default_library", - "//pkg/util/node:go_default_library", - "//pkg/util/version:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/listers/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/client-go/tools/cache:go_default_library", - "//vendor/k8s.io/client-go/tools/record:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/pkg/controller/nodeipam/BUILD b/pkg/controller/nodeipam/BUILD new file mode 100644 index 00000000000..46a62bb93b6 --- /dev/null +++ b/pkg/controller/nodeipam/BUILD @@ -0,0 +1,48 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "metrics.go", + "node_ipam_controller.go", + ], + importpath = "k8s.io/kubernetes/pkg/controller/nodeipam", + deps = [ + "//pkg/cloudprovider:go_default_library", + "//pkg/controller:go_default_library", + "//pkg/controller/nodeipam/ipam:go_default_library", + "//pkg/controller/nodeipam/ipam/sync:go_default_library", + "//pkg/util/metrics:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", + "//vendor/k8s.io/client-go/informers/core/v1:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", + "//vendor/k8s.io/client-go/listers/core/v1:go_default_library", + "//vendor/k8s.io/client-go/tools/cache:go_default_library", + "//vendor/k8s.io/client-go/tools/record:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//pkg/controller/nodeipam/ipam:all-srcs", + ], + tags = ["automanaged"], +) diff --git a/pkg/controller/nodeipam/OWNERS b/pkg/controller/nodeipam/OWNERS new file mode 100755 index 00000000000..99dd2eda0e1 --- /dev/null +++ b/pkg/controller/nodeipam/OWNERS @@ -0,0 +1,9 @@ +approvers: +- gmarek +- bowei +reviewers: +- gmarek +- smarterclayton +- ingvagabund +- aveshagarwal +- k82cn diff --git a/pkg/controller/nodeipam/doc.go b/pkg/controller/nodeipam/doc.go new file mode 100644 index 00000000000..a7b2d12db8e --- /dev/null +++ b/pkg/controller/nodeipam/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package nodeipam contains code for syncing cloud instances with +// node registry +package nodeipam // import "k8s.io/kubernetes/pkg/controller/nodeipam" diff --git a/pkg/controller/nodeipam/ipam/BUILD b/pkg/controller/nodeipam/ipam/BUILD new file mode 100644 index 00000000000..5a1e1018a20 --- /dev/null +++ b/pkg/controller/nodeipam/ipam/BUILD @@ -0,0 +1,90 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_test( + name = "go_default_test", + srcs = [ + "controller_test.go", + "range_allocator_test.go", + "timeout_test.go", + ], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/controller/nodeipam/ipam", + deps = [ + "//pkg/controller:go_default_library", + "//pkg/controller/nodeipam/ipam/cidrset:go_default_library", + "//pkg/controller/nodeipam/ipam/test:go_default_library", + "//pkg/controller/testutil:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/client-go/informers:go_default_library", + "//vendor/k8s.io/client-go/informers/core/v1:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", + ], +) + +go_library( + name = "go_default_library", + srcs = [ + "adapter.go", + "cidr_allocator.go", + "cloud_cidr_allocator.go", + "controller.go", + "doc.go", + "range_allocator.go", + "timeout.go", + ], + importpath = "k8s.io/kubernetes/pkg/controller/nodeipam/ipam", + deps = [ + "//pkg/api/v1/node:go_default_library", + "//pkg/cloudprovider:go_default_library", + "//pkg/cloudprovider/providers/gce:go_default_library", + "//pkg/controller:go_default_library", + "//pkg/controller/nodeipam/ipam/cidrset:go_default_library", + "//pkg/controller/nodeipam/ipam/sync:go_default_library", + "//pkg/controller/util/node:go_default_library", + "//pkg/util/node:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/client-go/informers/core/v1:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", + "//vendor/k8s.io/client-go/listers/core/v1:go_default_library", + "//vendor/k8s.io/client-go/tools/cache:go_default_library", + "//vendor/k8s.io/client-go/tools/record:go_default_library", + "//vendor/k8s.io/metrics/pkg/client/clientset_generated/clientset/scheme:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//pkg/controller/nodeipam/ipam/cidrset:all-srcs", + "//pkg/controller/nodeipam/ipam/sync:all-srcs", + "//pkg/controller/nodeipam/ipam/test:all-srcs", + ], + tags = ["automanaged"], +) diff --git a/pkg/controller/node/ipam/OWNERS b/pkg/controller/nodeipam/ipam/OWNERS similarity index 100% rename from pkg/controller/node/ipam/OWNERS rename to pkg/controller/nodeipam/ipam/OWNERS diff --git a/pkg/controller/nodeipam/ipam/adapter.go b/pkg/controller/nodeipam/ipam/adapter.go new file mode 100644 index 00000000000..6a5d9e480b0 --- /dev/null +++ b/pkg/controller/nodeipam/ipam/adapter.go @@ -0,0 +1,125 @@ +/* +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 ipam + +import ( + "context" + "encoding/json" + "net" + + "github.com/golang/glog" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + clientset "k8s.io/client-go/kubernetes" + v1core "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/tools/record" + "k8s.io/kubernetes/pkg/cloudprovider/providers/gce" + nodeutil "k8s.io/kubernetes/pkg/util/node" + "k8s.io/metrics/pkg/client/clientset_generated/clientset/scheme" +) + +type adapter struct { + k8s clientset.Interface + cloud *gce.GCECloud + + recorder record.EventRecorder +} + +func newAdapter(k8s clientset.Interface, cloud *gce.GCECloud) *adapter { + ret := &adapter{ + k8s: k8s, + cloud: cloud, + } + + broadcaster := record.NewBroadcaster() + broadcaster.StartLogging(glog.Infof) + ret.recorder = broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "cloudCIDRAllocator"}) + glog.V(0).Infof("Sending events to api server.") + broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{ + Interface: v1core.New(k8s.CoreV1().RESTClient()).Events(""), + }) + + return ret +} + +func (a *adapter) Alias(ctx context.Context, nodeName string) (*net.IPNet, error) { + cidrs, err := a.cloud.AliasRanges(types.NodeName(nodeName)) + if err != nil { + return nil, err + } + + switch len(cidrs) { + case 0: + return nil, nil + case 1: + break + default: + glog.Warningf("Node %q has more than one alias assigned (%v), defaulting to the first", nodeName, cidrs) + } + + _, cidrRange, err := net.ParseCIDR(cidrs[0]) + if err != nil { + return nil, err + } + + return cidrRange, nil +} + +func (a *adapter) AddAlias(ctx context.Context, nodeName string, cidrRange *net.IPNet) error { + return a.cloud.AddAliasToInstance(types.NodeName(nodeName), cidrRange) +} + +func (a *adapter) Node(ctx context.Context, name string) (*v1.Node, error) { + return a.k8s.CoreV1().Nodes().Get(name, metav1.GetOptions{}) +} + +func (a *adapter) UpdateNodePodCIDR(ctx context.Context, node *v1.Node, cidrRange *net.IPNet) error { + patch := map[string]interface{}{ + "apiVersion": node.APIVersion, + "kind": node.Kind, + "metadata": map[string]interface{}{"name": node.Name}, + "spec": map[string]interface{}{"podCIDR": cidrRange.String()}, + } + bytes, err := json.Marshal(patch) + if err != nil { + return err + } + + _, err = a.k8s.CoreV1().Nodes().Patch(node.Name, types.StrategicMergePatchType, bytes) + return err +} + +func (a *adapter) UpdateNodeNetworkUnavailable(nodeName string, unavailable bool) error { + condition := v1.ConditionFalse + if unavailable { + condition = v1.ConditionTrue + } + return nodeutil.SetNodeCondition(a.k8s, types.NodeName(nodeName), v1.NodeCondition{ + Type: v1.NodeNetworkUnavailable, + Status: condition, + Reason: "RouteCreated", + Message: "NodeController created an implicit route", + LastTransitionTime: metav1.Now(), + }) +} + +func (a *adapter) EmitNodeWarningEvent(nodeName, reason, fmt string, args ...interface{}) { + ref := &v1.ObjectReference{Kind: "Node", Name: nodeName} + a.recorder.Eventf(ref, v1.EventTypeNormal, reason, fmt, args...) +} diff --git a/pkg/controller/node/ipam/cidr_allocator.go b/pkg/controller/nodeipam/ipam/cidr_allocator.go similarity index 88% rename from pkg/controller/node/ipam/cidr_allocator.go rename to pkg/controller/nodeipam/ipam/cidr_allocator.go index 6fa8a684e06..b9a97938ad3 100644 --- a/pkg/controller/node/ipam/cidr_allocator.go +++ b/pkg/controller/nodeipam/ipam/cidr_allocator.go @@ -80,12 +80,12 @@ type CIDRAllocator interface { AllocateOrOccupyCIDR(node *v1.Node) error // ReleaseCIDR releases the CIDR of the removed node ReleaseCIDR(node *v1.Node) error - // Register allocator with the nodeInformer for updates. - Register(nodeInformer informers.NodeInformer) + // Run starts all the working logic of the allocator. + Run(stopCh <-chan struct{}) } // New creates a new CIDR range allocator. -func New(kubeClient clientset.Interface, cloud cloudprovider.Interface, allocatorType CIDRAllocatorType, clusterCIDR, serviceCIDR *net.IPNet, nodeCIDRMaskSize int) (CIDRAllocator, error) { +func New(kubeClient clientset.Interface, cloud cloudprovider.Interface, nodeInformer informers.NodeInformer, allocatorType CIDRAllocatorType, clusterCIDR, serviceCIDR *net.IPNet, nodeCIDRMaskSize int) (CIDRAllocator, error) { nodeList, err := listNodes(kubeClient) if err != nil { return nil, err @@ -93,9 +93,9 @@ func New(kubeClient clientset.Interface, cloud cloudprovider.Interface, allocato switch allocatorType { case RangeAllocatorType: - return NewCIDRRangeAllocator(kubeClient, clusterCIDR, serviceCIDR, nodeCIDRMaskSize, nodeList) + return NewCIDRRangeAllocator(kubeClient, nodeInformer, clusterCIDR, serviceCIDR, nodeCIDRMaskSize, nodeList) case CloudAllocatorType: - return NewCloudCIDRAllocator(kubeClient, cloud) + return NewCloudCIDRAllocator(kubeClient, cloud, nodeInformer) default: return nil, fmt.Errorf("Invalid CIDR allocator type: %v", allocatorType) } @@ -107,7 +107,7 @@ func listNodes(kubeClient clientset.Interface) (*v1.NodeList, error) { // controller manager to restart. if pollErr := wait.Poll(10*time.Second, apiserverStartupGracePeriod, func() (bool, error) { var err error - nodeList, err = kubeClient.Core().Nodes().List(metav1.ListOptions{ + nodeList, err = kubeClient.CoreV1().Nodes().List(metav1.ListOptions{ FieldSelector: fields.Everything().String(), LabelSelector: labels.Everything().String(), }) diff --git a/pkg/controller/nodeipam/ipam/cidrset/BUILD b/pkg/controller/nodeipam/ipam/cidrset/BUILD new file mode 100644 index 00000000000..c1bbda1c697 --- /dev/null +++ b/pkg/controller/nodeipam/ipam/cidrset/BUILD @@ -0,0 +1,34 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_test( + name = "go_default_test", + srcs = ["cidr_set_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/controller/nodeipam/ipam/cidrset", + deps = ["//vendor/github.com/golang/glog:go_default_library"], +) + +go_library( + name = "go_default_library", + srcs = ["cidr_set.go"], + importpath = "k8s.io/kubernetes/pkg/controller/nodeipam/ipam/cidrset", +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/pkg/controller/nodeipam/ipam/cidrset/cidr_set.go b/pkg/controller/nodeipam/ipam/cidrset/cidr_set.go new file mode 100644 index 00000000000..38a0521d2dd --- /dev/null +++ b/pkg/controller/nodeipam/ipam/cidrset/cidr_set.go @@ -0,0 +1,264 @@ +/* +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 cidrset + +import ( + "encoding/binary" + "errors" + "fmt" + "math/big" + "math/bits" + "net" + "sync" +) + +// CidrSet manages a set of CIDR ranges from which blocks of IPs can +// be allocated from. +type CidrSet struct { + sync.Mutex + clusterCIDR *net.IPNet + clusterIP net.IP + clusterMaskSize int + maxCIDRs int + nextCandidate int + used big.Int + subNetMaskSize int +} + +const ( + // The subnet mask size cannot be greater than 16 more than the cluster mask size + // TODO: https://github.com/kubernetes/kubernetes/issues/44918 + // clusterSubnetMaxDiff limited to 16 due to the uncompressed bitmap + clusterSubnetMaxDiff = 16 + // halfIPv6Len is the half of the IPv6 length + halfIPv6Len = net.IPv6len / 2 +) + +var ( + // ErrCIDRRangeNoCIDRsRemaining occurs when there is no more space + // to allocate CIDR ranges. + ErrCIDRRangeNoCIDRsRemaining = errors.New( + "CIDR allocation failed; there are no remaining CIDRs left to allocate in the accepted range") + // ErrCIDRSetSubNetTooBig occurs when the subnet mask size is too + // big compared to the CIDR mask size. + ErrCIDRSetSubNetTooBig = errors.New( + "New CIDR set failed; the node CIDR size is too big") +) + +// NewCIDRSet creates a new CidrSet. +func NewCIDRSet(clusterCIDR *net.IPNet, subNetMaskSize int) (*CidrSet, error) { + clusterMask := clusterCIDR.Mask + clusterMaskSize, _ := clusterMask.Size() + + var maxCIDRs int + if (clusterCIDR.IP.To4() == nil) && (subNetMaskSize-clusterMaskSize > clusterSubnetMaxDiff) { + return nil, ErrCIDRSetSubNetTooBig + } + maxCIDRs = 1 << uint32(subNetMaskSize-clusterMaskSize) + return &CidrSet{ + clusterCIDR: clusterCIDR, + clusterIP: clusterCIDR.IP, + clusterMaskSize: clusterMaskSize, + maxCIDRs: maxCIDRs, + subNetMaskSize: subNetMaskSize, + }, nil +} + +func (s *CidrSet) indexToCIDRBlock(index int) *net.IPNet { + var ip []byte + var mask int + switch /*v4 or v6*/ { + case s.clusterIP.To4() != nil: + { + j := uint32(index) << uint32(32-s.subNetMaskSize) + ipInt := (binary.BigEndian.Uint32(s.clusterIP)) | j + ip = make([]byte, 4) + binary.BigEndian.PutUint32(ip, ipInt) + mask = 32 + + } + case s.clusterIP.To16() != nil: + { + // leftClusterIP | rightClusterIP + // 2001:0DB8:1234:0000:0000:0000:0000:0000 + const v6NBits = 128 + const halfV6NBits = v6NBits / 2 + leftClusterIP := binary.BigEndian.Uint64(s.clusterIP[:halfIPv6Len]) + rightClusterIP := binary.BigEndian.Uint64(s.clusterIP[halfIPv6Len:]) + + leftIP, rightIP := make([]byte, halfIPv6Len), make([]byte, halfIPv6Len) + + if s.subNetMaskSize <= halfV6NBits { + // We only care about left side IP + leftClusterIP |= uint64(index) << uint(halfV6NBits-s.subNetMaskSize) + } else { + if s.clusterMaskSize < halfV6NBits { + // see how many bits are needed to reach the left side + btl := uint(s.subNetMaskSize - halfV6NBits) + indexMaxBit := uint(64 - bits.LeadingZeros64(uint64(index))) + if indexMaxBit > btl { + leftClusterIP |= uint64(index) >> btl + } + } + // the right side will be calculated the same way either the + // subNetMaskSize affects both left and right sides + rightClusterIP |= uint64(index) << uint(v6NBits-s.subNetMaskSize) + } + binary.BigEndian.PutUint64(leftIP, leftClusterIP) + binary.BigEndian.PutUint64(rightIP, rightClusterIP) + + ip = append(leftIP, rightIP...) + mask = 128 + } + } + return &net.IPNet{ + IP: ip, + Mask: net.CIDRMask(s.subNetMaskSize, mask), + } +} + +// AllocateNext allocates the next free CIDR range. This will set the range +// as occupied and return the allocated range. +func (s *CidrSet) AllocateNext() (*net.IPNet, error) { + s.Lock() + defer s.Unlock() + + nextUnused := -1 + for i := 0; i < s.maxCIDRs; i++ { + candidate := (i + s.nextCandidate) % s.maxCIDRs + if s.used.Bit(candidate) == 0 { + nextUnused = candidate + break + } + } + if nextUnused == -1 { + return nil, ErrCIDRRangeNoCIDRsRemaining + } + s.nextCandidate = (nextUnused + 1) % s.maxCIDRs + + s.used.SetBit(&s.used, nextUnused, 1) + + return s.indexToCIDRBlock(nextUnused), nil +} + +func (s *CidrSet) getBeginingAndEndIndices(cidr *net.IPNet) (begin, end int, err error) { + begin, end = 0, s.maxCIDRs-1 + cidrMask := cidr.Mask + maskSize, _ := cidrMask.Size() + var ipSize int + + if cidr == nil { + return -1, -1, fmt.Errorf("Error getting indices for cluster cidr %v, cidr is nil", s.clusterCIDR) + } + + if !s.clusterCIDR.Contains(cidr.IP.Mask(s.clusterCIDR.Mask)) && !cidr.Contains(s.clusterCIDR.IP.Mask(cidr.Mask)) { + return -1, -1, fmt.Errorf("cidr %v is out the range of cluster cidr %v", cidr, s.clusterCIDR) + } + + if s.clusterMaskSize < maskSize { + + ipSize = net.IPv4len + if cidr.IP.To4() == nil { + ipSize = net.IPv6len + } + subNetMask := net.CIDRMask(s.subNetMaskSize, ipSize*8) + begin, err = s.getIndexForCIDR(&net.IPNet{ + IP: cidr.IP.Mask(subNetMask), + Mask: subNetMask, + }) + if err != nil { + return -1, -1, err + } + ip := make([]byte, ipSize) + if cidr.IP.To4() != nil { + ipInt := binary.BigEndian.Uint32(cidr.IP) | (^binary.BigEndian.Uint32(cidr.Mask)) + binary.BigEndian.PutUint32(ip, ipInt) + } else { + // ipIntLeft | ipIntRight + // 2001:0DB8:1234:0000:0000:0000:0000:0000 + ipIntLeft := binary.BigEndian.Uint64(cidr.IP[:net.IPv6len/2]) | (^binary.BigEndian.Uint64(cidr.Mask[:net.IPv6len/2])) + ipIntRight := binary.BigEndian.Uint64(cidr.IP[net.IPv6len/2:]) | (^binary.BigEndian.Uint64(cidr.Mask[net.IPv6len/2:])) + binary.BigEndian.PutUint64(ip[:net.IPv6len/2], ipIntLeft) + binary.BigEndian.PutUint64(ip[net.IPv6len/2:], ipIntRight) + } + end, err = s.getIndexForCIDR(&net.IPNet{ + IP: net.IP(ip).Mask(subNetMask), + Mask: subNetMask, + }) + if err != nil { + return -1, -1, err + } + } + return begin, end, nil +} + +// Release releases the given CIDR range. +func (s *CidrSet) Release(cidr *net.IPNet) error { + begin, end, err := s.getBeginingAndEndIndices(cidr) + if err != nil { + return err + } + s.Lock() + defer s.Unlock() + for i := begin; i <= end; i++ { + s.used.SetBit(&s.used, i, 0) + } + return nil +} + +// Occupy marks the given CIDR range as used. Occupy does not check if the CIDR +// range was previously used. +func (s *CidrSet) Occupy(cidr *net.IPNet) (err error) { + begin, end, err := s.getBeginingAndEndIndices(cidr) + if err != nil { + return err + } + + s.Lock() + defer s.Unlock() + for i := begin; i <= end; i++ { + s.used.SetBit(&s.used, i, 1) + } + + return nil +} + +func (s *CidrSet) getIndexForCIDR(cidr *net.IPNet) (int, error) { + return s.getIndexForIP(cidr.IP) +} + +func (s *CidrSet) getIndexForIP(ip net.IP) (int, error) { + if ip.To4() != nil { + cidrIndex := (binary.BigEndian.Uint32(s.clusterIP) ^ binary.BigEndian.Uint32(ip.To4())) >> uint32(32-s.subNetMaskSize) + if cidrIndex >= uint32(s.maxCIDRs) { + return 0, fmt.Errorf("CIDR: %v/%v is out of the range of CIDR allocator", ip, s.subNetMaskSize) + } + return int(cidrIndex), nil + } + if ip.To16() != nil { + bigIP := big.NewInt(0).SetBytes(s.clusterIP) + bigIP = bigIP.Xor(bigIP, big.NewInt(0).SetBytes(ip)) + cidrIndexBig := bigIP.Rsh(bigIP, uint(net.IPv6len*8-s.subNetMaskSize)) + cidrIndex := cidrIndexBig.Uint64() + if cidrIndex >= uint64(s.maxCIDRs) { + return 0, fmt.Errorf("CIDR: %v/%v is out of the range of CIDR allocator", ip, s.subNetMaskSize) + } + return int(cidrIndex), nil + } + + return 0, fmt.Errorf("invalid IP: %v", ip) +} diff --git a/pkg/controller/node/ipam/cidrset/cidr_set_test.go b/pkg/controller/nodeipam/ipam/cidrset/cidr_set_test.go similarity index 94% rename from pkg/controller/node/ipam/cidrset/cidr_set_test.go rename to pkg/controller/nodeipam/ipam/cidrset/cidr_set_test.go index a79cc100a14..826eeba9bd8 100644 --- a/pkg/controller/node/ipam/cidrset/cidr_set_test.go +++ b/pkg/controller/nodeipam/ipam/cidrset/cidr_set_test.go @@ -47,8 +47,10 @@ func TestCIDRSetFullyAllocated(t *testing.T) { } for _, tc := range cases { _, clusterCIDR, _ := net.ParseCIDR(tc.clusterCIDRStr) - a := NewCIDRSet(clusterCIDR, tc.subNetMaskSize) - + a, err := NewCIDRSet(clusterCIDR, tc.subNetMaskSize) + if err != nil { + t.Fatalf("unexpected error: %v for %v", err, tc.description) + } p, err := a.AllocateNext() if err != nil { t.Fatalf("unexpected error: %v for %v", err, tc.description) @@ -196,7 +198,10 @@ func TestIndexToCIDRBlock(t *testing.T) { } for _, tc := range cases { _, clusterCIDR, _ := net.ParseCIDR(tc.clusterCIDRStr) - a := NewCIDRSet(clusterCIDR, tc.subnetMaskSize) + a, err := NewCIDRSet(clusterCIDR, tc.subnetMaskSize) + if err != nil { + t.Fatalf("error for %v ", tc.description) + } cidr := a.indexToCIDRBlock(tc.index) if cidr.String() != tc.CIDRBlock { t.Fatalf("error for %v index %d %s", tc.description, tc.index, cidr.String()) @@ -220,7 +225,10 @@ func TestCIDRSet_RandomishAllocation(t *testing.T) { } for _, tc := range cases { _, clusterCIDR, _ := net.ParseCIDR(tc.clusterCIDRStr) - a := NewCIDRSet(clusterCIDR, 24) + a, err := NewCIDRSet(clusterCIDR, 24) + if err != nil { + t.Fatalf("Error allocating CIDRSet for %v", tc.description) + } // allocate all the CIDRs var cidrs []*net.IPNet @@ -232,7 +240,7 @@ func TestCIDRSet_RandomishAllocation(t *testing.T) { } } - var err error + //var err error _, err = a.AllocateNext() if err == nil { t.Fatalf("expected error because of fully-allocated range for %v", tc.description) @@ -278,8 +286,10 @@ func TestCIDRSet_AllocationOccupied(t *testing.T) { } for _, tc := range cases { _, clusterCIDR, _ := net.ParseCIDR(tc.clusterCIDRStr) - a := NewCIDRSet(clusterCIDR, 24) - + a, err := NewCIDRSet(clusterCIDR, 24) + if err != nil { + t.Fatalf("Error allocating CIDRSet for %v", tc.description) + } // allocate all the CIDRs var cidrs []*net.IPNet var numCIDRs = 256 @@ -292,7 +302,7 @@ func TestCIDRSet_AllocationOccupied(t *testing.T) { } } - var err error + //var err error _, err = a.AllocateNext() if err == nil { t.Fatalf("expected error because of fully-allocated range for %v", tc.description) @@ -457,8 +467,10 @@ func TestGetBitforCIDR(t *testing.T) { t.Fatalf("unexpected error: %v for %v", err, tc.description) } - cs := NewCIDRSet(clusterCIDR, tc.subNetMaskSize) - + cs, err := NewCIDRSet(clusterCIDR, tc.subNetMaskSize) + if err != nil { + t.Fatalf("Error allocating CIDRSet for %v", tc.description) + } _, subnetCIDR, err := net.ParseCIDR(tc.subNetCIDRStr) if err != nil { t.Fatalf("unexpected error: %v for %v", err, tc.description) @@ -625,7 +637,10 @@ func TestOccupy(t *testing.T) { t.Fatalf("unexpected error: %v for %v", err, tc.description) } - cs := NewCIDRSet(clusterCIDR, tc.subNetMaskSize) + cs, err := NewCIDRSet(clusterCIDR, tc.subNetMaskSize) + if err != nil { + t.Fatalf("Error allocating CIDRSet for %v", tc.description) + } _, subnetCIDR, err := net.ParseCIDR(tc.subNetCIDRStr) if err != nil { @@ -686,7 +701,13 @@ func TestCIDRSetv6(t *testing.T) { } for _, tc := range cases { _, clusterCIDR, _ := net.ParseCIDR(tc.clusterCIDRStr) - a := NewCIDRSet(clusterCIDR, tc.subNetMaskSize) + a, err := NewCIDRSet(clusterCIDR, tc.subNetMaskSize) + if err != nil { + if tc.expectErr { + continue + } + t.Fatalf("Error allocating CIDRSet for %v", tc.description) + } p, err := a.AllocateNext() if err == nil && tc.expectErr { diff --git a/pkg/controller/nodeipam/ipam/cloud_cidr_allocator.go b/pkg/controller/nodeipam/ipam/cloud_cidr_allocator.go new file mode 100644 index 00000000000..7a07409c7cd --- /dev/null +++ b/pkg/controller/nodeipam/ipam/cloud_cidr_allocator.go @@ -0,0 +1,262 @@ +/* +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 ipam + +import ( + "fmt" + "net" + "sync" + + "github.com/golang/glog" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/sets" + informers "k8s.io/client-go/informers/core/v1" + corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/record" + + "k8s.io/api/core/v1" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + v1core "k8s.io/client-go/kubernetes/typed/core/v1" + v1node "k8s.io/kubernetes/pkg/api/v1/node" + "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/cloudprovider/providers/gce" + "k8s.io/kubernetes/pkg/controller" + nodeutil "k8s.io/kubernetes/pkg/controller/util/node" + utilnode "k8s.io/kubernetes/pkg/util/node" +) + +// cloudCIDRAllocator allocates node CIDRs according to IP address aliases +// assigned by the cloud provider. In this case, the allocation and +// deallocation is delegated to the external provider, and the controller +// merely takes the assignment and updates the node spec. +type cloudCIDRAllocator struct { + client clientset.Interface + cloud *gce.GCECloud + + // nodeLister is able to list/get nodes and is populated by the shared informer passed to + // NewCloudCIDRAllocator. + nodeLister corelisters.NodeLister + // nodesSynced returns true if the node shared informer has been synced at least once. + nodesSynced cache.InformerSynced + + // Channel that is used to pass updating Nodes to the background. + // This increases the throughput of CIDR assignment by parallelization + // and not blocking on long operations (which shouldn't be done from + // event handlers anyway). + nodeUpdateChannel chan string + recorder record.EventRecorder + + // Keep a set of nodes that are currectly being processed to avoid races in CIDR allocation + lock sync.Mutex + nodesInProcessing sets.String +} + +var _ CIDRAllocator = (*cloudCIDRAllocator)(nil) + +// NewCloudCIDRAllocator creates a new cloud CIDR allocator. +func NewCloudCIDRAllocator(client clientset.Interface, cloud cloudprovider.Interface, nodeInformer informers.NodeInformer) (CIDRAllocator, error) { + if client == nil { + glog.Fatalf("kubeClient is nil when starting NodeController") + } + + eventBroadcaster := record.NewBroadcaster() + recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "cidrAllocator"}) + eventBroadcaster.StartLogging(glog.Infof) + glog.V(0).Infof("Sending events to api server.") + eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(client.CoreV1().RESTClient()).Events("")}) + + gceCloud, ok := cloud.(*gce.GCECloud) + if !ok { + err := fmt.Errorf("cloudCIDRAllocator does not support %v provider", cloud.ProviderName()) + return nil, err + } + + ca := &cloudCIDRAllocator{ + client: client, + cloud: gceCloud, + nodeLister: nodeInformer.Lister(), + nodesSynced: nodeInformer.Informer().HasSynced, + nodeUpdateChannel: make(chan string, cidrUpdateQueueSize), + recorder: recorder, + nodesInProcessing: sets.NewString(), + } + + nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: nodeutil.CreateAddNodeHandler(ca.AllocateOrOccupyCIDR), + UpdateFunc: nodeutil.CreateUpdateNodeHandler(func(_, newNode *v1.Node) error { + if newNode.Spec.PodCIDR == "" { + return ca.AllocateOrOccupyCIDR(newNode) + } + // Even if PodCIDR is assigned, but NetworkUnavailable condition is + // set to true, we need to process the node to set the condition. + _, cond := v1node.GetNodeCondition(&newNode.Status, v1.NodeNetworkUnavailable) + if cond == nil || cond.Status != v1.ConditionFalse { + return ca.AllocateOrOccupyCIDR(newNode) + } + return nil + }), + DeleteFunc: nodeutil.CreateDeleteNodeHandler(ca.ReleaseCIDR), + }) + + glog.V(0).Infof("Using cloud CIDR allocator (provider: %v)", cloud.ProviderName()) + return ca, nil +} + +func (ca *cloudCIDRAllocator) Run(stopCh <-chan struct{}) { + defer utilruntime.HandleCrash() + + glog.Infof("Starting cloud CIDR allocator") + defer glog.Infof("Shutting down cloud CIDR allocator") + + if !controller.WaitForCacheSync("cidrallocator", stopCh, ca.nodesSynced) { + return + } + + for i := 0; i < cidrUpdateWorkers; i++ { + go ca.worker(stopCh) + } + + <-stopCh +} + +func (ca *cloudCIDRAllocator) worker(stopChan <-chan struct{}) { + for { + select { + case workItem, ok := <-ca.nodeUpdateChannel: + if !ok { + glog.Warning("Channel nodeCIDRUpdateChannel was unexpectedly closed") + return + } + if err := ca.updateCIDRAllocation(workItem); err != nil { + // Requeue the failed node for update again. + ca.nodeUpdateChannel <- workItem + } + case <-stopChan: + return + } + } +} + +func (ca *cloudCIDRAllocator) insertNodeToProcessing(nodeName string) bool { + ca.lock.Lock() + defer ca.lock.Unlock() + if ca.nodesInProcessing.Has(nodeName) { + return false + } + ca.nodesInProcessing.Insert(nodeName) + return true +} + +func (ca *cloudCIDRAllocator) removeNodeFromProcessing(nodeName string) { + ca.lock.Lock() + defer ca.lock.Unlock() + ca.nodesInProcessing.Delete(nodeName) +} + +// WARNING: If you're adding any return calls or defer any more work from this +// function you have to make sure to update nodesInProcessing properly with the +// disposition of the node when the work is done. +func (ca *cloudCIDRAllocator) AllocateOrOccupyCIDR(node *v1.Node) error { + if node == nil { + return nil + } + if !ca.insertNodeToProcessing(node.Name) { + glog.V(2).Infof("Node %v is already in a process of CIDR assignment.", node.Name) + return nil + } + + glog.V(4).Infof("Putting node %s into the work queue", node.Name) + ca.nodeUpdateChannel <- node.Name + return nil +} + +// updateCIDRAllocation assigns CIDR to Node and sends an update to the API server. +func (ca *cloudCIDRAllocator) updateCIDRAllocation(nodeName string) error { + var err error + var node *v1.Node + defer ca.removeNodeFromProcessing(nodeName) + + cidrs, err := ca.cloud.AliasRanges(types.NodeName(nodeName)) + if err != nil { + nodeutil.RecordNodeStatusChange(ca.recorder, node, "CIDRNotAvailable") + return fmt.Errorf("failed to allocate cidr: %v", err) + } + if len(cidrs) == 0 { + nodeutil.RecordNodeStatusChange(ca.recorder, node, "CIDRNotAvailable") + return fmt.Errorf("failed to allocate cidr: Node %v has no CIDRs", node.Name) + } + _, cidr, err := net.ParseCIDR(cidrs[0]) + if err != nil { + return fmt.Errorf("failed to parse string '%s' as a CIDR: %v", cidrs[0], err) + } + podCIDR := cidr.String() + + for rep := 0; rep < cidrUpdateRetries; rep++ { + node, err = ca.nodeLister.Get(nodeName) + if err != nil { + glog.Errorf("Failed while getting node %v to retry updating Node.Spec.PodCIDR: %v", nodeName, err) + continue + } + if node.Spec.PodCIDR != "" { + if node.Spec.PodCIDR == podCIDR { + glog.V(4).Infof("Node %v already has allocated CIDR %v. It matches the proposed one.", node.Name, podCIDR) + // We don't return to set the NetworkUnavailable condition if needed. + break + } + glog.Errorf("PodCIDR being reassigned! Node %v spec has %v, but cloud provider has assigned %v", + node.Name, node.Spec.PodCIDR, podCIDR) + // We fall through and set the CIDR despite this error. This + // implements the same logic as implemented in the + // rangeAllocator. + // + // See https://github.com/kubernetes/kubernetes/pull/42147#discussion_r103357248 + } + if err = utilnode.PatchNodeCIDR(ca.client, types.NodeName(node.Name), podCIDR); err == nil { + glog.Infof("Set node %v PodCIDR to %v", node.Name, podCIDR) + break + } + glog.Errorf("Failed to update node %v PodCIDR to %v (%d retries left): %v", node.Name, podCIDR, cidrUpdateRetries-rep-1, err) + } + if err != nil { + nodeutil.RecordNodeStatusChange(ca.recorder, node, "CIDRAssignmentFailed") + glog.Errorf("CIDR assignment for node %v failed: %v.", nodeName, err) + return err + } + + err = utilnode.SetNodeCondition(ca.client, types.NodeName(node.Name), v1.NodeCondition{ + Type: v1.NodeNetworkUnavailable, + Status: v1.ConditionFalse, + Reason: "RouteCreated", + Message: "NodeController create implicit route", + LastTransitionTime: metav1.Now(), + }) + if err != nil { + glog.Errorf("Error setting route status for node %v: %v", node.Name, err) + } + return err +} + +func (ca *cloudCIDRAllocator) ReleaseCIDR(node *v1.Node) error { + glog.V(2).Infof("Node %v PodCIDR (%v) will be released by external cloud provider (not managed by controller)", + node.Name, node.Spec.PodCIDR) + return nil +} diff --git a/pkg/controller/nodeipam/ipam/controller.go b/pkg/controller/nodeipam/ipam/controller.go new file mode 100644 index 00000000000..6ab18d69f65 --- /dev/null +++ b/pkg/controller/nodeipam/ipam/controller.go @@ -0,0 +1,215 @@ +/* +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 ipam + +import ( + "fmt" + "net" + "sync" + "time" + + "github.com/golang/glog" + + "k8s.io/api/core/v1" + informers "k8s.io/client-go/informers/core/v1" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" + "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/cloudprovider/providers/gce" + "k8s.io/kubernetes/pkg/controller/nodeipam/ipam/cidrset" + nodesync "k8s.io/kubernetes/pkg/controller/nodeipam/ipam/sync" + nodeutil "k8s.io/kubernetes/pkg/controller/util/node" +) + +// Config for the IPAM controller. +type Config struct { + // Resync is the default timeout duration when there are no errors. + Resync time.Duration + // MaxBackoff is the maximum timeout when in a error backoff state. + MaxBackoff time.Duration + // InitialRetry is the initial retry interval when an error is reported. + InitialRetry time.Duration + // Mode to use to synchronize. + Mode nodesync.NodeSyncMode +} + +// Controller is the controller for synchronizing cluster and cloud node +// pod CIDR range assignments. +type Controller struct { + config *Config + adapter *adapter + + lock sync.Mutex + syncers map[string]*nodesync.NodeSync + + set *cidrset.CidrSet +} + +// NewController returns a new instance of the IPAM controller. +func NewController( + config *Config, + kubeClient clientset.Interface, + cloud cloudprovider.Interface, + clusterCIDR, serviceCIDR *net.IPNet, + nodeCIDRMaskSize int) (*Controller, error) { + + if !nodesync.IsValidMode(config.Mode) { + return nil, fmt.Errorf("invalid IPAM controller mode %q", config.Mode) + } + + gceCloud, ok := cloud.(*gce.GCECloud) + if !ok { + return nil, fmt.Errorf("cloud IPAM controller does not support %q provider", cloud.ProviderName()) + } + + set, err := cidrset.NewCIDRSet(clusterCIDR, nodeCIDRMaskSize) + if err != nil { + return nil, err + } + + c := &Controller{ + config: config, + adapter: newAdapter(kubeClient, gceCloud), + syncers: make(map[string]*nodesync.NodeSync), + set: set, + } + + if err := occupyServiceCIDR(c.set, clusterCIDR, serviceCIDR); err != nil { + return nil, err + } + + return c, nil +} + +// Start initializes the Controller with the existing list of nodes and +// registers the informers for node chnages. This will start synchronization +// of the node and cloud CIDR range allocations. +func (c *Controller) Start(nodeInformer informers.NodeInformer) error { + glog.V(0).Infof("Starting IPAM controller (config=%+v)", c.config) + + nodes, err := listNodes(c.adapter.k8s) + if err != nil { + return err + } + for _, node := range nodes.Items { + if node.Spec.PodCIDR != "" { + _, cidrRange, err := net.ParseCIDR(node.Spec.PodCIDR) + if err == nil { + c.set.Occupy(cidrRange) + glog.V(3).Infof("Occupying CIDR for node %q (%v)", node.Name, node.Spec.PodCIDR) + } else { + glog.Errorf("Node %q has an invalid CIDR (%q): %v", node.Name, node.Spec.PodCIDR, err) + } + } + + func() { + c.lock.Lock() + defer c.lock.Unlock() + + // XXX/bowei -- stagger the start of each sync cycle. + syncer := c.newSyncer(node.Name) + c.syncers[node.Name] = syncer + go syncer.Loop(nil) + }() + } + + nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: nodeutil.CreateAddNodeHandler(c.onAdd), + UpdateFunc: nodeutil.CreateUpdateNodeHandler(c.onUpdate), + DeleteFunc: nodeutil.CreateDeleteNodeHandler(c.onDelete), + }) + + return nil +} + +// occupyServiceCIDR removes the service CIDR range from the cluster CIDR if it +// intersects. +func occupyServiceCIDR(set *cidrset.CidrSet, clusterCIDR, serviceCIDR *net.IPNet) error { + if clusterCIDR.Contains(serviceCIDR.IP) || serviceCIDR.Contains(clusterCIDR.IP) { + if err := set.Occupy(serviceCIDR); err != nil { + return err + } + } + return nil +} + +type nodeState struct { + t Timeout +} + +func (ns *nodeState) ReportResult(err error) { + ns.t.Update(err == nil) +} + +func (ns *nodeState) ResyncTimeout() time.Duration { + return ns.t.Next() +} + +func (c *Controller) newSyncer(name string) *nodesync.NodeSync { + ns := &nodeState{ + Timeout{ + Resync: c.config.Resync, + MaxBackoff: c.config.MaxBackoff, + InitialRetry: c.config.InitialRetry, + }, + } + return nodesync.New(ns, c.adapter, c.adapter, c.config.Mode, name, c.set) +} + +func (c *Controller) onAdd(node *v1.Node) error { + c.lock.Lock() + defer c.lock.Unlock() + + if syncer, ok := c.syncers[node.Name]; !ok { + syncer = c.newSyncer(node.Name) + c.syncers[node.Name] = syncer + go syncer.Loop(nil) + } else { + glog.Warningf("Add for node %q that already exists", node.Name) + syncer.Update(node) + } + + return nil +} + +func (c *Controller) onUpdate(_, node *v1.Node) error { + c.lock.Lock() + defer c.lock.Unlock() + + if sync, ok := c.syncers[node.Name]; ok { + sync.Update(node) + } else { + glog.Errorf("Received update for non-existant node %q", node.Name) + return fmt.Errorf("unknown node %q", node.Name) + } + + return nil +} + +func (c *Controller) onDelete(node *v1.Node) error { + c.lock.Lock() + defer c.lock.Unlock() + + if syncer, ok := c.syncers[node.Name]; ok { + syncer.Delete(node) + delete(c.syncers, node.Name) + } else { + glog.Warning("Node %q was already deleted", node.Name) + } + + return nil +} diff --git a/pkg/controller/nodeipam/ipam/controller_test.go b/pkg/controller/nodeipam/ipam/controller_test.go new file mode 100644 index 00000000000..6e5a6f99571 --- /dev/null +++ b/pkg/controller/nodeipam/ipam/controller_test.go @@ -0,0 +1,67 @@ +/* +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 ipam + +import ( + "net" + "testing" + + "k8s.io/kubernetes/pkg/controller/nodeipam/ipam/cidrset" + "k8s.io/kubernetes/pkg/controller/nodeipam/ipam/test" +) + +func TestOccupyServiceCIDR(t *testing.T) { + const clusterCIDR = "10.1.0.0/16" + +TestCase: + for _, tc := range []struct { + serviceCIDR string + }{ + {"10.0.255.0/24"}, + {"10.1.0.0/24"}, + {"10.1.255.0/24"}, + {"10.2.0.0/24"}, + } { + serviceCIDR := test.MustParseCIDR(tc.serviceCIDR) + set, err := cidrset.NewCIDRSet(test.MustParseCIDR(clusterCIDR), 24) + if err != nil { + t.Errorf("test case %+v: NewCIDRSet() = %v, want nil", tc, err) + } + if err := occupyServiceCIDR(set, test.MustParseCIDR(clusterCIDR), serviceCIDR); err != nil { + t.Errorf("test case %+v: occupyServiceCIDR() = %v, want nil", tc, err) + } + // Allocate until full. + var cidrs []*net.IPNet + for { + cidr, err := set.AllocateNext() + if err != nil { + if err == cidrset.ErrCIDRRangeNoCIDRsRemaining { + break + } + t.Errorf("set.AllocateNext() = %v, want %v", err, cidrset.ErrCIDRRangeNoCIDRsRemaining) + continue TestCase + } + cidrs = append(cidrs, cidr) + } + // No allocated CIDR range should intersect with serviceCIDR. + for _, c := range cidrs { + if c.Contains(serviceCIDR.IP) || serviceCIDR.Contains(c.IP) { + t.Errorf("test case %+v: allocated CIDR %v from service range", tc, c) + } + } + } +} diff --git a/pkg/controller/node/ipam/doc.go b/pkg/controller/nodeipam/ipam/doc.go similarity index 100% rename from pkg/controller/node/ipam/doc.go rename to pkg/controller/nodeipam/ipam/doc.go diff --git a/pkg/controller/node/ipam/range_allocator.go b/pkg/controller/nodeipam/ipam/range_allocator.go similarity index 81% rename from pkg/controller/node/ipam/range_allocator.go rename to pkg/controller/nodeipam/ipam/range_allocator.go index cacc47a8617..5de2195854b 100644 --- a/pkg/controller/node/ipam/range_allocator.go +++ b/pkg/controller/nodeipam/ipam/range_allocator.go @@ -25,18 +25,20 @@ import ( "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" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/wait" informers "k8s.io/client-go/informers/core/v1" 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" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" - - "k8s.io/kubernetes/pkg/controller/node/ipam/cidrset" - "k8s.io/kubernetes/pkg/controller/node/util" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/controller/nodeipam/ipam/cidrset" + nodeutil "k8s.io/kubernetes/pkg/controller/util/node" + utilnode "k8s.io/kubernetes/pkg/util/node" ) type rangeAllocator struct { @@ -45,6 +47,12 @@ type rangeAllocator struct { clusterCIDR *net.IPNet maxCIDRs int + // nodeLister is able to list/get nodes and is populated by the shared informer passed to + // NewCloudCIDRAllocator. + nodeLister corelisters.NodeLister + // nodesSynced returns true if the node shared informer has been synced at least once. + nodesSynced cache.InformerSynced + // Channel that is used to pass updating Nodes with assigned CIDRs to the background // This increases a throughput of CIDR assignment by not blocking on long operations. nodeCIDRUpdateChannel chan nodeAndCIDR @@ -59,7 +67,7 @@ type rangeAllocator struct { // Caller must ensure subNetMaskSize is not less than cluster CIDR mask size. // Caller must always pass in a list of existing nodes so the new allocator // can initialize its CIDR map. NodeList is only nil in testing. -func NewCIDRRangeAllocator(client clientset.Interface, clusterCIDR *net.IPNet, serviceCIDR *net.IPNet, subNetMaskSize int, nodeList *v1.NodeList) (CIDRAllocator, error) { +func NewCIDRRangeAllocator(client clientset.Interface, nodeInformer informers.NodeInformer, clusterCIDR *net.IPNet, serviceCIDR *net.IPNet, subNetMaskSize int, nodeList *v1.NodeList) (CIDRAllocator, error) { if client == nil { glog.Fatalf("kubeClient is nil when starting NodeController") } @@ -68,12 +76,18 @@ func NewCIDRRangeAllocator(client clientset.Interface, clusterCIDR *net.IPNet, s recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "cidrAllocator"}) eventBroadcaster.StartLogging(glog.Infof) glog.V(0).Infof("Sending events to api server.") - eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(client.Core().RESTClient()).Events("")}) + eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(client.CoreV1().RESTClient()).Events("")}) + set, err := cidrset.NewCIDRSet(clusterCIDR, subNetMaskSize) + if err != nil { + return nil, err + } ra := &rangeAllocator{ client: client, - cidrs: cidrset.NewCIDRSet(clusterCIDR, subNetMaskSize), + cidrs: set, clusterCIDR: clusterCIDR, + nodeLister: nodeInformer.Lister(), + nodesSynced: nodeInformer.Informer().HasSynced, nodeCIDRUpdateChannel: make(chan nodeAndCIDR, cidrUpdateQueueSize), recorder: recorder, nodesInProcessing: sets.NewString(), @@ -103,14 +117,57 @@ func NewCIDRRangeAllocator(client clientset.Interface, clusterCIDR *net.IPNet, s } } } - for i := 0; i < cidrUpdateWorkers; i++ { - // TODO: Take stopChan as an argument to NewCIDRRangeAllocator and pass it to the worker. - go ra.worker(wait.NeverStop) - } + + nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: nodeutil.CreateAddNodeHandler(ra.AllocateOrOccupyCIDR), + UpdateFunc: nodeutil.CreateUpdateNodeHandler(func(_, newNode *v1.Node) error { + // If the PodCIDR is not empty we either: + // - already processed a Node that already had a CIDR after NC restarted + // (cidr is marked as used), + // - already processed a Node successfully and allocated a CIDR for it + // (cidr is marked as used), + // - already processed a Node but we did saw a "timeout" response and + // request eventually got through in this case we haven't released + // the allocated CIDR (cidr is still marked as used). + // There's a possible error here: + // - NC sees a new Node and assigns a CIDR X to it, + // - Update Node call fails with a timeout, + // - Node is updated by some other component, NC sees an update and + // assigns CIDR Y to the Node, + // - Both CIDR X and CIDR Y are marked as used in the local cache, + // even though Node sees only CIDR Y + // The problem here is that in in-memory cache we see CIDR X as marked, + // which prevents it from being assigned to any new node. The cluster + // state is correct. + // Restart of NC fixes the issue. + if newNode.Spec.PodCIDR == "" { + return ra.AllocateOrOccupyCIDR(newNode) + } + return nil + }), + DeleteFunc: nodeutil.CreateDeleteNodeHandler(ra.ReleaseCIDR), + }) return ra, nil } +func (r *rangeAllocator) Run(stopCh <-chan struct{}) { + defer utilruntime.HandleCrash() + + glog.Infof("Starting range CIDR allocator") + defer glog.Infof("Shutting down range CIDR allocator") + + if !controller.WaitForCacheSync("cidrallocator", stopCh, r.nodesSynced) { + return + } + + for i := 0; i < cidrUpdateWorkers; i++ { + go r.worker(stopCh) + } + + <-stopCh +} + func (r *rangeAllocator) worker(stopChan <-chan struct{}) { for { select { @@ -119,7 +176,10 @@ func (r *rangeAllocator) worker(stopChan <-chan struct{}) { glog.Warning("Channel nodeCIDRUpdateChannel was unexpectedly closed") return } - r.updateCIDRAllocation(workItem) + if err := r.updateCIDRAllocation(workItem); err != nil { + // Requeue the failed node for update again. + r.nodeCIDRUpdateChannel <- workItem + } case <-stopChan: return } @@ -174,7 +234,7 @@ func (r *rangeAllocator) AllocateOrOccupyCIDR(node *v1.Node) error { podCIDR, err := r.cidrs.AllocateNext() if err != nil { r.removeNodeFromProcessing(node.Name) - util.RecordNodeStatusChange(r.recorder, node, "CIDRNotAvailable") + nodeutil.RecordNodeStatusChange(r.recorder, node, "CIDRNotAvailable") return fmt.Errorf("failed to allocate cidr: %v", err) } @@ -227,8 +287,7 @@ func (r *rangeAllocator) updateCIDRAllocation(data nodeAndCIDR) error { podCIDR := data.cidr.String() for rep := 0; rep < cidrUpdateRetries; rep++ { - // TODO: change it to using PATCH instead of full Node updates. - node, err = r.client.Core().Nodes().Get(data.nodeName, metav1.GetOptions{}) + node, err = r.nodeLister.Get(data.nodeName) if err != nil { glog.Errorf("Failed while getting node %v to retry updating Node.Spec.PodCIDR: %v", data.nodeName, err) continue @@ -244,15 +303,14 @@ func (r *rangeAllocator) updateCIDRAllocation(data nodeAndCIDR) error { } return nil } - node.Spec.PodCIDR = podCIDR - if _, err = r.client.Core().Nodes().Update(node); err == nil { + if err = utilnode.PatchNodeCIDR(r.client, types.NodeName(node.Name), podCIDR); err == nil { glog.Infof("Set node %v PodCIDR to %v", node.Name, podCIDR) break } glog.Errorf("Failed to update node %v PodCIDR to %v (%d retries left): %v", node.Name, podCIDR, cidrUpdateRetries-rep-1, err) } if err != nil { - util.RecordNodeStatusChange(r.recorder, node, "CIDRAssignmentFailed") + nodeutil.RecordNodeStatusChange(r.recorder, node, "CIDRAssignmentFailed") // We accept the fact that we may leek CIDRs here. This is safer than releasing // them in case when we don't know if request went through. // NodeController restart will return all falsely allocated CIDRs to the pool. @@ -265,35 +323,3 @@ func (r *rangeAllocator) updateCIDRAllocation(data nodeAndCIDR) error { } return err } - -func (r *rangeAllocator) Register(nodeInformer informers.NodeInformer) { - nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: util.CreateAddNodeHandler(r.AllocateOrOccupyCIDR), - UpdateFunc: util.CreateUpdateNodeHandler(func(_, newNode *v1.Node) error { - // If the PodCIDR is not empty we either: - // - already processed a Node that already had a CIDR after NC restarted - // (cidr is marked as used), - // - already processed a Node successfully and allocated a CIDR for it - // (cidr is marked as used), - // - already processed a Node but we did saw a "timeout" response and - // request eventually got through in this case we haven't released - // the allocated CIDR (cidr is still marked as used). - // There's a possible error here: - // - NC sees a new Node and assigns a CIDR X to it, - // - Update Node call fails with a timeout, - // - Node is updated by some other component, NC sees an update and - // assigns CIDR Y to the Node, - // - Both CIDR X and CIDR Y are marked as used in the local cache, - // even though Node sees only CIDR Y - // The problem here is that in in-memory cache we see CIDR X as marked, - // which prevents it from being assigned to any new node. The cluster - // state is correct. - // Restart of NC fixes the issue. - if newNode.Spec.PodCIDR == "" { - return r.AllocateOrOccupyCIDR(newNode) - } - return nil - }), - DeleteFunc: util.CreateDeleteNodeHandler(r.ReleaseCIDR), - }) -} diff --git a/pkg/controller/node/ipam/range_allocator_test.go b/pkg/controller/nodeipam/ipam/range_allocator_test.go similarity index 83% rename from pkg/controller/node/ipam/range_allocator_test.go rename to pkg/controller/nodeipam/ipam/range_allocator_test.go index 3f911a899b3..eadb608831f 100644 --- a/pkg/controller/node/ipam/range_allocator_test.go +++ b/pkg/controller/nodeipam/ipam/range_allocator_test.go @@ -24,7 +24,10 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/informers" + coreinformers "k8s.io/client-go/informers/core/v1" "k8s.io/client-go/kubernetes/fake" + "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller/testutil" ) @@ -32,6 +35,8 @@ const ( nodePollInterval = 100 * time.Millisecond ) +var alwaysReady = func() bool { return true } + func waitForUpdatedNodeWithTimeout(nodeHandler *testutil.FakeNodeHandler, number int, timeout time.Duration) error { return wait.Poll(nodePollInterval, timeout, func() (bool, error) { if len(nodeHandler.GetUpdatedNodesCopy()) >= number { @@ -41,6 +46,19 @@ func waitForUpdatedNodeWithTimeout(nodeHandler *testutil.FakeNodeHandler, number }) } +// Creates a fakeNodeInformer using the provided fakeNodeHandler. +func getFakeNodeInformer(fakeNodeHandler *testutil.FakeNodeHandler) coreinformers.NodeInformer { + fakeClient := &fake.Clientset{} + fakeInformerFactory := informers.NewSharedInformerFactory(fakeClient, controller.NoResyncPeriodFunc()) + fakeNodeInformer := fakeInformerFactory.Core().V1().Nodes() + + for _, node := range fakeNodeHandler.Existing { + fakeNodeInformer.Informer().GetStore().Add(node) + } + + return fakeNodeInformer +} + func TestAllocateOrOccupyCIDRSuccess(t *testing.T) { testCases := []struct { description string @@ -130,19 +148,23 @@ func TestAllocateOrOccupyCIDRSuccess(t *testing.T) { expectedAllocatedCIDR string allocatedCIDRs []string }) { - allocator, _ := NewCIDRRangeAllocator(tc.fakeNodeHandler, tc.clusterCIDR, tc.serviceCIDR, tc.subNetMaskSize, nil) + // Initialize the range allocator. + allocator, _ := NewCIDRRangeAllocator(tc.fakeNodeHandler, getFakeNodeInformer(tc.fakeNodeHandler), tc.clusterCIDR, tc.serviceCIDR, tc.subNetMaskSize, nil) + rangeAllocator, ok := allocator.(*rangeAllocator) + if !ok { + t.Logf("%v: found non-default implementation of CIDRAllocator, skipping white-box test...", tc.description) + return + } + rangeAllocator.nodesSynced = alwaysReady + rangeAllocator.recorder = testutil.NewFakeRecorder() + go allocator.Run(wait.NeverStop) + // this is a bit of white box testing for _, allocated := range tc.allocatedCIDRs { _, cidr, err := net.ParseCIDR(allocated) if err != nil { t.Fatalf("%v: unexpected error when parsing CIDR %v: %v", tc.description, allocated, err) } - rangeAllocator, ok := allocator.(*rangeAllocator) - if !ok { - t.Logf("%v: found non-default implementation of CIDRAllocator, skipping white-box test...", tc.description) - return - } - rangeAllocator.recorder = testutil.NewFakeRecorder() if err = rangeAllocator.cidrs.Occupy(cidr); err != nil { t.Fatalf("%v: unexpected error when occupying CIDR %v: %v", tc.description, allocated, err) } @@ -212,19 +234,23 @@ func TestAllocateOrOccupyCIDRFailure(t *testing.T) { subNetMaskSize int allocatedCIDRs []string }) { - allocator, _ := NewCIDRRangeAllocator(tc.fakeNodeHandler, tc.clusterCIDR, tc.serviceCIDR, tc.subNetMaskSize, nil) + // Initialize the range allocator. + allocator, _ := NewCIDRRangeAllocator(tc.fakeNodeHandler, getFakeNodeInformer(tc.fakeNodeHandler), tc.clusterCIDR, tc.serviceCIDR, tc.subNetMaskSize, nil) + rangeAllocator, ok := allocator.(*rangeAllocator) + if !ok { + t.Logf("%v: found non-default implementation of CIDRAllocator, skipping white-box test...", tc.description) + return + } + rangeAllocator.nodesSynced = alwaysReady + rangeAllocator.recorder = testutil.NewFakeRecorder() + go allocator.Run(wait.NeverStop) + // this is a bit of white box testing for _, allocated := range tc.allocatedCIDRs { _, cidr, err := net.ParseCIDR(allocated) if err != nil { t.Fatalf("%v: unexpected error when parsing CIDR %v: %v", tc.description, allocated, err) } - rangeAllocator, ok := allocator.(*rangeAllocator) - if !ok { - t.Logf("%v: found non-default implementation of CIDRAllocator, skipping white-box test...", tc.description) - return - } - rangeAllocator.recorder = testutil.NewFakeRecorder() err = rangeAllocator.cidrs.Occupy(cidr) if err != nil { t.Fatalf("%v: unexpected error when occupying CIDR %v: %v", tc.description, allocated, err) @@ -307,6 +333,7 @@ func TestReleaseCIDRSuccess(t *testing.T) { }(), serviceCIDR: nil, subNetMaskSize: 30, + allocatedCIDRs: []string{"127.123.234.4/30", "127.123.234.8/30", "127.123.234.12/30"}, expectedAllocatedCIDRFirstRound: "127.123.234.0/30", cidrsToRelease: []string{"127.123.234.0/30"}, expectedAllocatedCIDRSecondRound: "127.123.234.0/30", @@ -324,19 +351,23 @@ func TestReleaseCIDRSuccess(t *testing.T) { allocatedCIDRs []string cidrsToRelease []string }) { - allocator, _ := NewCIDRRangeAllocator(tc.fakeNodeHandler, tc.clusterCIDR, tc.serviceCIDR, tc.subNetMaskSize, nil) + // Initialize the range allocator. + allocator, _ := NewCIDRRangeAllocator(tc.fakeNodeHandler, getFakeNodeInformer(tc.fakeNodeHandler), tc.clusterCIDR, tc.serviceCIDR, tc.subNetMaskSize, nil) + rangeAllocator, ok := allocator.(*rangeAllocator) + if !ok { + t.Logf("%v: found non-default implementation of CIDRAllocator, skipping white-box test...", tc.description) + return + } + rangeAllocator.nodesSynced = alwaysReady + rangeAllocator.recorder = testutil.NewFakeRecorder() + go allocator.Run(wait.NeverStop) + // this is a bit of white box testing for _, allocated := range tc.allocatedCIDRs { _, cidr, err := net.ParseCIDR(allocated) if err != nil { t.Fatalf("%v: unexpected error when parsing CIDR %v: %v", tc.description, allocated, err) } - rangeAllocator, ok := allocator.(*rangeAllocator) - if !ok { - t.Logf("%v: found non-default implementation of CIDRAllocator, skipping white-box test...", tc.description) - return - } - rangeAllocator.recorder = testutil.NewFakeRecorder() err = rangeAllocator.cidrs.Occupy(cidr) if err != nil { t.Fatalf("%v: unexpected error when occupying CIDR %v: %v", tc.description, allocated, err) diff --git a/pkg/controller/nodeipam/ipam/sync/BUILD b/pkg/controller/nodeipam/ipam/sync/BUILD new file mode 100644 index 00000000000..2ecba089f4e --- /dev/null +++ b/pkg/controller/nodeipam/ipam/sync/BUILD @@ -0,0 +1,41 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["sync.go"], + importpath = "k8s.io/kubernetes/pkg/controller/nodeipam/ipam/sync", + visibility = ["//visibility:public"], + deps = [ + "//pkg/controller/nodeipam/ipam/cidrset:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["sync_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/controller/nodeipam/ipam/sync", + deps = [ + "//pkg/controller/nodeipam/ipam/cidrset:go_default_library", + "//pkg/controller/nodeipam/ipam/test:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/controller/node/ipam/sync/sync.go b/pkg/controller/nodeipam/ipam/sync/sync.go similarity index 99% rename from pkg/controller/node/ipam/sync/sync.go rename to pkg/controller/nodeipam/ipam/sync/sync.go index 4995f425543..fabc3a11260 100644 --- a/pkg/controller/node/ipam/sync/sync.go +++ b/pkg/controller/nodeipam/ipam/sync/sync.go @@ -25,7 +25,7 @@ import ( "github.com/golang/glog" "k8s.io/api/core/v1" - "k8s.io/kubernetes/pkg/controller/node/ipam/cidrset" + "k8s.io/kubernetes/pkg/controller/nodeipam/ipam/cidrset" ) const ( diff --git a/pkg/controller/node/ipam/sync/sync_test.go b/pkg/controller/nodeipam/ipam/sync/sync_test.go similarity index 95% rename from pkg/controller/node/ipam/sync/sync_test.go rename to pkg/controller/nodeipam/ipam/sync/sync_test.go index 15d3e1b5b35..4a47280d94b 100644 --- a/pkg/controller/node/ipam/sync/sync_test.go +++ b/pkg/controller/nodeipam/ipam/sync/sync_test.go @@ -26,8 +26,8 @@ import ( "github.com/golang/glog" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/controller/node/ipam/cidrset" - "k8s.io/kubernetes/pkg/controller/node/ipam/test" + "k8s.io/kubernetes/pkg/controller/nodeipam/ipam/cidrset" + "k8s.io/kubernetes/pkg/controller/nodeipam/ipam/test" "k8s.io/api/core/v1" ) @@ -194,7 +194,8 @@ func TestNodeSyncUpdate(t *testing.T) { wantError: false, }, } { - sync := New(&tc.fake, &tc.fake, &tc.fake, tc.mode, "node1", cidrset.NewCIDRSet(clusterCIDRRange, 24)) + cidr, _ := cidrset.NewCIDRSet(clusterCIDRRange, 24) + sync := New(&tc.fake, &tc.fake, &tc.fake, tc.mode, "node1", cidr) doneChan := make(chan struct{}) // Do a single step of the loop. @@ -225,7 +226,8 @@ func TestNodeSyncResync(t *testing.T) { resyncTimeout: time.Millisecond, reportChan: make(chan struct{}), } - sync := New(fake, fake, fake, SyncFromCluster, "node1", cidrset.NewCIDRSet(clusterCIDRRange, 24)) + cidr, _ := cidrset.NewCIDRSet(clusterCIDRRange, 24) + sync := New(fake, fake, fake, SyncFromCluster, "node1", cidr) doneChan := make(chan struct{}) go sync.Loop(doneChan) @@ -267,7 +269,8 @@ func TestNodeSyncDelete(t *testing.T) { }, }, } { - sync := New(&tc.fake, &tc.fake, &tc.fake, tc.mode, "node1", cidrset.NewCIDRSet(clusterCIDRRange, 24)) + cidr, _ := cidrset.NewCIDRSet(clusterCIDRRange, 24) + sync := New(&tc.fake, &tc.fake, &tc.fake, tc.mode, "node1", cidr) doneChan := make(chan struct{}) // Do a single step of the loop. diff --git a/pkg/controller/nodeipam/ipam/test/BUILD b/pkg/controller/nodeipam/ipam/test/BUILD new file mode 100644 index 00000000000..0c6fd3a2816 --- /dev/null +++ b/pkg/controller/nodeipam/ipam/test/BUILD @@ -0,0 +1,22 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["utils.go"], + importpath = "k8s.io/kubernetes/pkg/controller/nodeipam/ipam/test", + visibility = ["//visibility:public"], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/controller/node/ipam/test/utils.go b/pkg/controller/nodeipam/ipam/test/utils.go similarity index 100% rename from pkg/controller/node/ipam/test/utils.go rename to pkg/controller/nodeipam/ipam/test/utils.go diff --git a/pkg/controller/node/ipam/timeout.go b/pkg/controller/nodeipam/ipam/timeout.go similarity index 100% rename from pkg/controller/node/ipam/timeout.go rename to pkg/controller/nodeipam/ipam/timeout.go diff --git a/pkg/controller/node/ipam/timeout_test.go b/pkg/controller/nodeipam/ipam/timeout_test.go similarity index 100% rename from pkg/controller/node/ipam/timeout_test.go rename to pkg/controller/nodeipam/ipam/timeout_test.go diff --git a/pkg/controller/nodeipam/metrics.go b/pkg/controller/nodeipam/metrics.go new file mode 100644 index 00000000000..9211ce3f382 --- /dev/null +++ b/pkg/controller/nodeipam/metrics.go @@ -0,0 +1,21 @@ +/* +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 nodeipam + +// Register the metrics that are to be monitored. +func Register() { +} diff --git a/pkg/controller/nodeipam/node_ipam_controller.go b/pkg/controller/nodeipam/node_ipam_controller.go new file mode 100644 index 00000000000..e2dad9e4f58 --- /dev/null +++ b/pkg/controller/nodeipam/node_ipam_controller.go @@ -0,0 +1,187 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package nodeipam + +import ( + "net" + "time" + + "github.com/golang/glog" + + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + + v1core "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/record" + + "k8s.io/api/core/v1" + coreinformers "k8s.io/client-go/informers/core/v1" + clientset "k8s.io/client-go/kubernetes" + corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/controller/nodeipam/ipam" + nodesync "k8s.io/kubernetes/pkg/controller/nodeipam/ipam/sync" + "k8s.io/kubernetes/pkg/util/metrics" +) + +func init() { + // Register prometheus metrics + Register() +} + +const ( + // ipamResyncInterval is the amount of time between when the cloud and node + // CIDR range assignments are synchronized. + ipamResyncInterval = 30 * time.Second + // ipamMaxBackoff is the maximum backoff for retrying synchronization of a + // given in the error state. + ipamMaxBackoff = 10 * time.Second + // ipamInitialRetry is the initial retry interval for retrying synchronization of a + // given in the error state. + ipamInitialBackoff = 250 * time.Millisecond +) + +// Controller is the controller that manages node ipam state. +type Controller struct { + allocateNodeCIDRs bool + allocatorType ipam.CIDRAllocatorType + + cloud cloudprovider.Interface + clusterCIDR *net.IPNet + serviceCIDR *net.IPNet + kubeClient clientset.Interface + // Method for easy mocking in unittest. + lookupIP func(host string) ([]net.IP, error) + + nodeLister corelisters.NodeLister + nodeInformerSynced cache.InformerSynced + + cidrAllocator ipam.CIDRAllocator + + forcefullyDeletePod func(*v1.Pod) error +} + +// NewNodeIpamController returns a new node IP Address Management controller to +// sync instances from cloudprovider. +// This method returns an error if it is unable to initialize the CIDR bitmap with +// podCIDRs it has already allocated to nodes. Since we don't allow podCIDR changes +// currently, this should be handled as a fatal error. +func NewNodeIpamController( + nodeInformer coreinformers.NodeInformer, + cloud cloudprovider.Interface, + kubeClient clientset.Interface, + clusterCIDR *net.IPNet, + serviceCIDR *net.IPNet, + nodeCIDRMaskSize int, + allocateNodeCIDRs bool, + allocatorType ipam.CIDRAllocatorType) (*Controller, error) { + + if kubeClient == nil { + glog.Fatalf("kubeClient is nil when starting Controller") + } + + eventBroadcaster := record.NewBroadcaster() + eventBroadcaster.StartLogging(glog.Infof) + + glog.V(0).Infof("Sending events to api server.") + eventBroadcaster.StartRecordingToSink( + &v1core.EventSinkImpl{ + Interface: v1core.New(kubeClient.CoreV1().RESTClient()).Events(""), + }) + + if kubeClient != nil && kubeClient.CoreV1().RESTClient().GetRateLimiter() != nil { + metrics.RegisterMetricAndTrackRateLimiterUsage("node_ipam_controller", kubeClient.CoreV1().RESTClient().GetRateLimiter()) + } + + if allocateNodeCIDRs { + if clusterCIDR == nil { + glog.Fatal("Controller: Must specify clusterCIDR if allocateNodeCIDRs == true.") + } + mask := clusterCIDR.Mask + if maskSize, _ := mask.Size(); maskSize > nodeCIDRMaskSize { + glog.Fatal("Controller: Invalid clusterCIDR, mask size of clusterCIDR must be less than nodeCIDRMaskSize.") + } + } + + ic := &Controller{ + cloud: cloud, + kubeClient: kubeClient, + lookupIP: net.LookupIP, + clusterCIDR: clusterCIDR, + serviceCIDR: serviceCIDR, + allocateNodeCIDRs: allocateNodeCIDRs, + allocatorType: allocatorType, + } + + // TODO: Abstract this check into a generic controller manager should run method. + if ic.allocateNodeCIDRs { + if ic.allocatorType == ipam.IPAMFromClusterAllocatorType || ic.allocatorType == ipam.IPAMFromCloudAllocatorType { + cfg := &ipam.Config{ + Resync: ipamResyncInterval, + MaxBackoff: ipamMaxBackoff, + InitialRetry: ipamInitialBackoff, + } + switch ic.allocatorType { + case ipam.IPAMFromClusterAllocatorType: + cfg.Mode = nodesync.SyncFromCluster + case ipam.IPAMFromCloudAllocatorType: + cfg.Mode = nodesync.SyncFromCloud + } + ipamc, err := ipam.NewController(cfg, kubeClient, cloud, clusterCIDR, serviceCIDR, nodeCIDRMaskSize) + if err != nil { + glog.Fatalf("Error creating ipam controller: %v", err) + } + if err := ipamc.Start(nodeInformer); err != nil { + glog.Fatalf("Error trying to Init(): %v", err) + } + } else { + var err error + ic.cidrAllocator, err = ipam.New( + kubeClient, cloud, nodeInformer, ic.allocatorType, ic.clusterCIDR, ic.serviceCIDR, nodeCIDRMaskSize) + if err != nil { + return nil, err + } + } + } + + ic.nodeLister = nodeInformer.Lister() + ic.nodeInformerSynced = nodeInformer.Informer().HasSynced + + return ic, nil +} + +// Run starts an asynchronous loop that monitors the status of cluster nodes. +func (nc *Controller) Run(stopCh <-chan struct{}) { + defer utilruntime.HandleCrash() + + glog.Infof("Starting ipam controller") + defer glog.Infof("Shutting down ipam controller") + + if !controller.WaitForCacheSync("node", stopCh, nc.nodeInformerSynced) { + return + } + + // TODO: Abstract this check into a generic controller manager should run method. + if nc.allocateNodeCIDRs { + if nc.allocatorType != ipam.IPAMFromClusterAllocatorType && nc.allocatorType != ipam.IPAMFromCloudAllocatorType { + go nc.cidrAllocator.Run(stopCh) + } + } + + <-stopCh +} diff --git a/pkg/controller/nodelifecycle/BUILD b/pkg/controller/nodelifecycle/BUILD new file mode 100644 index 00000000000..55f577fe54f --- /dev/null +++ b/pkg/controller/nodelifecycle/BUILD @@ -0,0 +1,93 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "metrics.go", + "node_lifecycle_controller.go", + ], + importpath = "k8s.io/kubernetes/pkg/controller/nodelifecycle", + visibility = ["//visibility:public"], + deps = [ + "//pkg/api/v1/node:go_default_library", + "//pkg/cloudprovider:go_default_library", + "//pkg/controller:go_default_library", + "//pkg/controller/nodelifecycle/scheduler:go_default_library", + "//pkg/controller/util/node:go_default_library", + "//pkg/scheduler/algorithm:go_default_library", + "//pkg/util/metrics:go_default_library", + "//pkg/util/node:go_default_library", + "//pkg/util/system:go_default_library", + "//pkg/util/taints:go_default_library", + "//pkg/util/version:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/client-go/informers/core/v1:go_default_library", + "//vendor/k8s.io/client-go/informers/extensions/v1beta1:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library", + "//vendor/k8s.io/client-go/listers/core/v1:go_default_library", + "//vendor/k8s.io/client-go/listers/extensions/v1beta1:go_default_library", + "//vendor/k8s.io/client-go/tools/cache:go_default_library", + "//vendor/k8s.io/client-go/tools/record:go_default_library", + "//vendor/k8s.io/client-go/util/flowcontrol:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//pkg/controller/nodelifecycle/scheduler:all-srcs", + ], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) + +go_test( + name = "go_default_test", + srcs = ["node_lifecycle_controller_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/controller/nodelifecycle", + deps = [ + "//pkg/cloudprovider:go_default_library", + "//pkg/cloudprovider/providers/fake:go_default_library", + "//pkg/controller:go_default_library", + "//pkg/controller/nodelifecycle/scheduler:go_default_library", + "//pkg/controller/testutil:go_default_library", + "//pkg/controller/util/node:go_default_library", + "//pkg/kubelet/apis:go_default_library", + "//pkg/scheduler/algorithm:go_default_library", + "//pkg/util/node:go_default_library", + "//pkg/util/taints:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/client-go/informers:go_default_library", + "//vendor/k8s.io/client-go/informers/core/v1:go_default_library", + "//vendor/k8s.io/client-go/informers/extensions/v1beta1:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", + "//vendor/k8s.io/client-go/testing:go_default_library", + ], +) diff --git a/pkg/controller/nodelifecycle/OWNERS b/pkg/controller/nodelifecycle/OWNERS new file mode 100755 index 00000000000..99dd2eda0e1 --- /dev/null +++ b/pkg/controller/nodelifecycle/OWNERS @@ -0,0 +1,9 @@ +approvers: +- gmarek +- bowei +reviewers: +- gmarek +- smarterclayton +- ingvagabund +- aveshagarwal +- k82cn diff --git a/pkg/controller/nodelifecycle/metrics.go b/pkg/controller/nodelifecycle/metrics.go new file mode 100644 index 00000000000..ae61266c8ad --- /dev/null +++ b/pkg/controller/nodelifecycle/metrics.go @@ -0,0 +1,78 @@ +/* +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 nodelifecycle + +import ( + "sync" + + "github.com/prometheus/client_golang/prometheus" +) + +const ( + nodeControllerSubsystem = "node_collector" + zoneHealthStatisticKey = "zone_health" + zoneSizeKey = "zone_size" + zoneNoUnhealthyNodesKey = "unhealthy_nodes_in_zone" + evictionsNumberKey = "evictions_number" +) + +var ( + zoneHealth = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Subsystem: nodeControllerSubsystem, + Name: zoneHealthStatisticKey, + Help: "Gauge measuring percentage of healthy nodes per zone.", + }, + []string{"zone"}, + ) + zoneSize = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Subsystem: nodeControllerSubsystem, + Name: zoneSizeKey, + Help: "Gauge measuring number of registered Nodes per zones.", + }, + []string{"zone"}, + ) + unhealthyNodes = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Subsystem: nodeControllerSubsystem, + Name: zoneNoUnhealthyNodesKey, + Help: "Gauge measuring number of not Ready Nodes per zones.", + }, + []string{"zone"}, + ) + evictionsNumber = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Subsystem: nodeControllerSubsystem, + Name: evictionsNumberKey, + Help: "Number of Node evictions that happened since current instance of NodeController started.", + }, + []string{"zone"}, + ) +) + +var registerMetrics sync.Once + +// Register the metrics that are to be monitored. +func Register() { + registerMetrics.Do(func() { + prometheus.MustRegister(zoneHealth) + prometheus.MustRegister(zoneSize) + prometheus.MustRegister(unhealthyNodes) + prometheus.MustRegister(evictionsNumber) + }) +} diff --git a/pkg/controller/nodelifecycle/node_lifecycle_controller.go b/pkg/controller/nodelifecycle/node_lifecycle_controller.go new file mode 100644 index 00000000000..17f87bad292 --- /dev/null +++ b/pkg/controller/nodelifecycle/node_lifecycle_controller.go @@ -0,0 +1,1126 @@ +/* +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. +*/ + +// The Controller sets tainted annotations on nodes. +// Tainted nodes should not be used for new work loads and +// some effort should be given to getting existing work +// loads off of tainted nodes. + +package nodelifecycle + +import ( + "k8s.io/api/core/v1" + 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/labels" + "k8s.io/apimachinery/pkg/types" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + coreinformers "k8s.io/client-go/informers/core/v1" + extensionsinformers "k8s.io/client-go/informers/extensions/v1beta1" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + corelisters "k8s.io/client-go/listers/core/v1" + extensionslisters "k8s.io/client-go/listers/extensions/v1beta1" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/record" + "k8s.io/client-go/util/flowcontrol" + v1node "k8s.io/kubernetes/pkg/api/v1/node" + "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/controller/nodelifecycle/scheduler" + nodeutil "k8s.io/kubernetes/pkg/controller/util/node" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + "k8s.io/kubernetes/pkg/util/metrics" + utilnode "k8s.io/kubernetes/pkg/util/node" + "k8s.io/kubernetes/pkg/util/system" + taintutils "k8s.io/kubernetes/pkg/util/taints" + utilversion "k8s.io/kubernetes/pkg/util/version" + + "fmt" + "github.com/golang/glog" + "sync" + "time" +) + +func init() { + // Register prometheus metrics + Register() +} + +var ( + gracefulDeletionVersion = utilversion.MustParseSemantic("v1.1.0") + + // UnreachableTaintTemplate is the taint for when a node becomes unreachable. + UnreachableTaintTemplate = &v1.Taint{ + Key: algorithm.TaintNodeUnreachable, + Effect: v1.TaintEffectNoExecute, + } + + // NotReadyTaintTemplate is the taint for when a node is not ready for + // executing pods + NotReadyTaintTemplate = &v1.Taint{ + Key: algorithm.TaintNodeNotReady, + Effect: v1.TaintEffectNoExecute, + } + + nodeConditionToTaintKeyMap = map[v1.NodeConditionType]string{ + v1.NodeMemoryPressure: algorithm.TaintNodeMemoryPressure, + v1.NodeOutOfDisk: algorithm.TaintNodeOutOfDisk, + v1.NodeDiskPressure: algorithm.TaintNodeDiskPressure, + v1.NodeNetworkUnavailable: algorithm.TaintNodeNetworkUnavailable, + } + + taintKeyToNodeConditionMap = map[string]v1.NodeConditionType{ + algorithm.TaintNodeNetworkUnavailable: v1.NodeNetworkUnavailable, + algorithm.TaintNodeMemoryPressure: v1.NodeMemoryPressure, + algorithm.TaintNodeOutOfDisk: v1.NodeOutOfDisk, + algorithm.TaintNodeDiskPressure: v1.NodeDiskPressure, + } +) + +// ZoneState is the state of a given zone. +type ZoneState string + +const ( + stateInitial = ZoneState("Initial") + stateNormal = ZoneState("Normal") + stateFullDisruption = ZoneState("FullDisruption") + statePartialDisruption = ZoneState("PartialDisruption") +) + +const ( + // The amount of time the nodecontroller polls on the list nodes endpoint. + apiserverStartupGracePeriod = 10 * time.Minute + // The amount of time the nodecontroller should sleep between retrying NodeStatus updates + retrySleepTime = 20 * time.Millisecond +) + +type nodeStatusData struct { + probeTimestamp metav1.Time + readyTransitionTimestamp metav1.Time + status v1.NodeStatus +} + +// Controller is the controller that manages node's life cycle. +type Controller struct { + taintManager *scheduler.NoExecuteTaintManager + + podInformerSynced cache.InformerSynced + cloud cloudprovider.Interface + kubeClient clientset.Interface + + // This timestamp is to be used instead of LastProbeTime stored in Condition. We do this + // to aviod the problem with time skew across the cluster. + now func() metav1.Time + + enterPartialDisruptionFunc func(nodeNum int) float32 + enterFullDisruptionFunc func(nodeNum int) float32 + computeZoneStateFunc func(nodeConditions []*v1.NodeCondition) (int, ZoneState) + + knownNodeSet map[string]*v1.Node + // per Node map storing last observed Status together with a local time when it was observed. + nodeStatusMap map[string]nodeStatusData + + // Lock to access evictor workers + evictorLock sync.Mutex + + // workers that evicts pods from unresponsive nodes. + zonePodEvictor map[string]*scheduler.RateLimitedTimedQueue + + // workers that are responsible for tainting nodes. + zoneNoExecuteTainter map[string]*scheduler.RateLimitedTimedQueue + + zoneStates map[string]ZoneState + + daemonSetStore extensionslisters.DaemonSetLister + daemonSetInformerSynced cache.InformerSynced + + nodeLister corelisters.NodeLister + nodeInformerSynced cache.InformerSynced + nodeExistsInCloudProvider func(types.NodeName) (bool, error) + + recorder record.EventRecorder + + // Value controlling Controller monitoring period, i.e. how often does Controller + // check node status posted from kubelet. This value should be lower than nodeMonitorGracePeriod. + // TODO: Change node status monitor to watch based. + nodeMonitorPeriod time.Duration + + // Value used if sync_nodes_status=False, only for node startup. When node + // is just created, e.g. cluster bootstrap or node creation, we give a longer grace period. + nodeStartupGracePeriod time.Duration + + // Value used if sync_nodes_status=False. Controller will not proactively + // sync node status in this case, but will monitor node status updated from kubelet. If + // it doesn't receive update for this amount of time, it will start posting "NodeReady== + // ConditionUnknown". The amount of time before which Controller start evicting pods + // is controlled via flag 'pod-eviction-timeout'. + // Note: be cautious when changing the constant, it must work with nodeStatusUpdateFrequency + // in kubelet. There are several constraints: + // 1. nodeMonitorGracePeriod must be N times more than nodeStatusUpdateFrequency, where + // N means number of retries allowed for kubelet to post node status. It is pointless + // to make nodeMonitorGracePeriod be less than nodeStatusUpdateFrequency, since there + // will only be fresh values from Kubelet at an interval of nodeStatusUpdateFrequency. + // The constant must be less than podEvictionTimeout. + // 2. nodeMonitorGracePeriod can't be too large for user experience - larger value takes + // longer for user to see up-to-date node status. + nodeMonitorGracePeriod time.Duration + + podEvictionTimeout time.Duration + evictionLimiterQPS float32 + secondaryEvictionLimiterQPS float32 + largeClusterThreshold int32 + unhealthyZoneThreshold float32 + + // if set to true Controller will start TaintManager that will evict Pods from + // tainted nodes, if they're not tolerated. + runTaintManager bool + + // if set to true Controller will taint Nodes with 'TaintNodeNotReady' and 'TaintNodeUnreachable' + // taints instead of evicting Pods itself. + useTaintBasedEvictions bool + + // if set to true, NodeController will taint Nodes based on its condition for 'NetworkUnavailable', + // 'MemoryPressure', 'OutOfDisk' and 'DiskPressure'. + taintNodeByCondition bool +} + +// NewNodeLifecycleController returns a new taint controller. +func NewNodeLifecycleController(podInformer coreinformers.PodInformer, + nodeInformer coreinformers.NodeInformer, + daemonSetInformer extensionsinformers.DaemonSetInformer, + cloud cloudprovider.Interface, + kubeClient clientset.Interface, + nodeMonitorPeriod time.Duration, + nodeStartupGracePeriod time.Duration, + nodeMonitorGracePeriod time.Duration, + podEvictionTimeout time.Duration, + evictionLimiterQPS float32, + secondaryEvictionLimiterQPS float32, + largeClusterThreshold int32, + unhealthyZoneThreshold float32, + runTaintManager bool, + useTaintBasedEvictions bool, + taintNodeByCondition bool) (*Controller, error) { + + if kubeClient == nil { + glog.Fatalf("kubeClient is nil when starting Controller") + } + + eventBroadcaster := record.NewBroadcaster() + recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "node-controller"}) + + if kubeClient != nil && kubeClient.CoreV1().RESTClient().GetRateLimiter() != nil { + metrics.RegisterMetricAndTrackRateLimiterUsage("node_lifecycle_controller", kubeClient.CoreV1().RESTClient().GetRateLimiter()) + } + + nc := &Controller{ + cloud: cloud, + kubeClient: kubeClient, + now: metav1.Now, + knownNodeSet: make(map[string]*v1.Node), + nodeStatusMap: make(map[string]nodeStatusData), + nodeExistsInCloudProvider: func(nodeName types.NodeName) (bool, error) { + return nodeutil.ExistsInCloudProvider(cloud, nodeName) + }, + recorder: recorder, + nodeMonitorPeriod: nodeMonitorPeriod, + nodeStartupGracePeriod: nodeStartupGracePeriod, + nodeMonitorGracePeriod: nodeMonitorGracePeriod, + zonePodEvictor: make(map[string]*scheduler.RateLimitedTimedQueue), + zoneNoExecuteTainter: make(map[string]*scheduler.RateLimitedTimedQueue), + zoneStates: make(map[string]ZoneState), + podEvictionTimeout: podEvictionTimeout, + evictionLimiterQPS: evictionLimiterQPS, + secondaryEvictionLimiterQPS: secondaryEvictionLimiterQPS, + largeClusterThreshold: largeClusterThreshold, + unhealthyZoneThreshold: unhealthyZoneThreshold, + runTaintManager: runTaintManager, + useTaintBasedEvictions: useTaintBasedEvictions && runTaintManager, + taintNodeByCondition: taintNodeByCondition, + } + if useTaintBasedEvictions { + glog.Infof("Controller is using taint based evictions.") + } + + nc.enterPartialDisruptionFunc = nc.ReducedQPSFunc + nc.enterFullDisruptionFunc = nc.HealthyQPSFunc + nc.computeZoneStateFunc = nc.ComputeZoneState + + podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + pod := obj.(*v1.Pod) + if nc.taintManager != nil { + nc.taintManager.PodUpdated(nil, pod) + } + }, + UpdateFunc: func(prev, obj interface{}) { + prevPod := prev.(*v1.Pod) + newPod := obj.(*v1.Pod) + if nc.taintManager != nil { + nc.taintManager.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 { + glog.Errorf("Received unexpected object: %v", obj) + return + } + pod, ok = deletedState.Obj.(*v1.Pod) + if !ok { + glog.Errorf("DeletedFinalStateUnknown contained non-Pod object: %v", deletedState.Obj) + return + } + } + if nc.taintManager != nil { + nc.taintManager.PodUpdated(pod, nil) + } + }, + }) + nc.podInformerSynced = podInformer.Informer().HasSynced + + if nc.runTaintManager { + nc.taintManager = scheduler.NewNoExecuteTaintManager(kubeClient) + nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: nodeutil.CreateAddNodeHandler(func(node *v1.Node) error { + nc.taintManager.NodeUpdated(nil, node) + return nil + }), + UpdateFunc: nodeutil.CreateUpdateNodeHandler(func(oldNode, newNode *v1.Node) error { + nc.taintManager.NodeUpdated(oldNode, newNode) + return nil + }), + DeleteFunc: nodeutil.CreateDeleteNodeHandler(func(node *v1.Node) error { + nc.taintManager.NodeUpdated(node, nil) + return nil + }), + }) + } + + if nc.taintNodeByCondition { + glog.Infof("Controller will taint node by condition.") + nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: nodeutil.CreateAddNodeHandler(func(node *v1.Node) error { + return nc.doNoScheduleTaintingPass(node) + }), + UpdateFunc: nodeutil.CreateUpdateNodeHandler(func(_, newNode *v1.Node) error { + return nc.doNoScheduleTaintingPass(newNode) + }), + }) + } + + // NOTE(resouer): nodeInformer to substitute deprecated taint key (notReady -> not-ready). + // Remove this logic when we don't need this backwards compatibility + nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: nodeutil.CreateAddNodeHandler(func(node *v1.Node) error { + return nc.doFixDeprecatedTaintKeyPass(node) + }), + UpdateFunc: nodeutil.CreateUpdateNodeHandler(func(_, newNode *v1.Node) error { + return nc.doFixDeprecatedTaintKeyPass(newNode) + }), + }) + + nc.nodeLister = nodeInformer.Lister() + nc.nodeInformerSynced = nodeInformer.Informer().HasSynced + + nc.daemonSetStore = daemonSetInformer.Lister() + nc.daemonSetInformerSynced = daemonSetInformer.Informer().HasSynced + + return nc, nil +} + +// Run starts an asynchronous loop that monitors the status of cluster nodes. +func (nc *Controller) Run(stopCh <-chan struct{}) { + defer utilruntime.HandleCrash() + + glog.Infof("Starting node controller") + defer glog.Infof("Shutting down node controller") + + if !controller.WaitForCacheSync("taint", stopCh, nc.nodeInformerSynced, nc.podInformerSynced, nc.daemonSetInformerSynced) { + return + } + + if nc.runTaintManager { + go nc.taintManager.Run(wait.NeverStop) + } + + if nc.useTaintBasedEvictions { + // Handling taint based evictions. Because we don't want a dedicated logic in TaintManager for NC-originated + // taints and we normally don't rate limit evictions caused by taints, we need to rate limit adding taints. + go wait.Until(nc.doNoExecuteTaintingPass, scheduler.NodeEvictionPeriod, wait.NeverStop) + } else { + // Managing eviction of nodes: + // When we delete pods off a node, if the node was not empty at the time we then + // queue an eviction watcher. If we hit an error, retry deletion. + go wait.Until(nc.doEvictionPass, scheduler.NodeEvictionPeriod, wait.NeverStop) + } + + // Incorporate the results of node status pushed from kubelet to master. + go wait.Until(func() { + if err := nc.monitorNodeStatus(); err != nil { + glog.Errorf("Error monitoring node status: %v", err) + } + }, nc.nodeMonitorPeriod, wait.NeverStop) + + <-stopCh +} + +// doFixDeprecatedTaintKeyPass checks and replaces deprecated taint key with proper key name if needed. +func (nc *Controller) doFixDeprecatedTaintKeyPass(node *v1.Node) error { + taintsToAdd := []*v1.Taint{} + taintsToDel := []*v1.Taint{} + + for _, taint := range node.Spec.Taints { + if taint.Key == algorithm.DeprecatedTaintNodeNotReady { + tDel := taint + taintsToDel = append(taintsToDel, &tDel) + + tAdd := taint + tAdd.Key = algorithm.TaintNodeNotReady + taintsToAdd = append(taintsToAdd, &tAdd) + } + + if taint.Key == algorithm.DeprecatedTaintNodeUnreachable { + tDel := taint + taintsToDel = append(taintsToDel, &tDel) + + tAdd := taint + tAdd.Key = algorithm.TaintNodeUnreachable + taintsToAdd = append(taintsToAdd, &tAdd) + } + } + + if len(taintsToAdd) == 0 && len(taintsToDel) == 0 { + return nil + } + + glog.Warningf("Detected deprecated taint keys: %v on node: %v, will substitute them with %v", + taintsToDel, node.GetName(), taintsToAdd) + + if !nodeutil.SwapNodeControllerTaint(nc.kubeClient, taintsToAdd, taintsToDel, node) { + return fmt.Errorf("failed to swap taints of node %+v", node) + } + return nil +} + +func (nc *Controller) doNoScheduleTaintingPass(node *v1.Node) error { + // Map node's condition to Taints. + taints := []v1.Taint{} + for _, condition := range node.Status.Conditions { + if _, found := nodeConditionToTaintKeyMap[condition.Type]; found { + if condition.Status == v1.ConditionTrue { + taints = append(taints, v1.Taint{ + Key: nodeConditionToTaintKeyMap[condition.Type], + Effect: v1.TaintEffectNoSchedule, + }) + } + } + } + nodeTaints := taintutils.TaintSetFilter(node.Spec.Taints, func(t *v1.Taint) bool { + _, found := taintKeyToNodeConditionMap[t.Key] + return found + }) + taintsToAdd, taintsToDel := taintutils.TaintSetDiff(taints, nodeTaints) + // If nothing to add not delete, return true directly. + if len(taintsToAdd) == 0 && len(taintsToDel) == 0 { + return nil + } + if !nodeutil.SwapNodeControllerTaint(nc.kubeClient, taintsToAdd, taintsToDel, node) { + return fmt.Errorf("failed to swap taints of node %+v", node) + } + return nil +} + +func (nc *Controller) doNoExecuteTaintingPass() { + nc.evictorLock.Lock() + defer nc.evictorLock.Unlock() + for k := range nc.zoneNoExecuteTainter { + // Function should return 'false' and a time after which it should be retried, or 'true' if it shouldn't (it succeeded). + nc.zoneNoExecuteTainter[k].Try(func(value scheduler.TimedValue) (bool, time.Duration) { + node, err := nc.nodeLister.Get(value.Value) + if apierrors.IsNotFound(err) { + glog.Warningf("Node %v no longer present in nodeLister!", value.Value) + return true, 0 + } else if err != nil { + glog.Warningf("Failed to get Node %v from the nodeLister: %v", value.Value, err) + // retry in 50 millisecond + return false, 50 * time.Millisecond + } else { + zone := utilnode.GetZoneKey(node) + evictionsNumber.WithLabelValues(zone).Inc() + } + _, condition := v1node.GetNodeCondition(&node.Status, v1.NodeReady) + // Because we want to mimic NodeStatus.Condition["Ready"] we make "unreachable" and "not ready" taints mutually exclusive. + taintToAdd := v1.Taint{} + oppositeTaint := v1.Taint{} + if condition.Status == v1.ConditionFalse { + taintToAdd = *NotReadyTaintTemplate + oppositeTaint = *UnreachableTaintTemplate + } else if condition.Status == v1.ConditionUnknown { + taintToAdd = *UnreachableTaintTemplate + oppositeTaint = *NotReadyTaintTemplate + } else { + // It seems that the Node is ready again, so there's no need to taint it. + glog.V(4).Infof("Node %v was in a taint queue, but it's ready now. Ignoring taint request.", value.Value) + return true, 0 + } + + return nodeutil.SwapNodeControllerTaint(nc.kubeClient, []*v1.Taint{&taintToAdd}, []*v1.Taint{&oppositeTaint}, node), 0 + }) + } +} + +func (nc *Controller) doEvictionPass() { + nc.evictorLock.Lock() + defer nc.evictorLock.Unlock() + for k := range nc.zonePodEvictor { + // Function should return 'false' and a time after which it should be retried, or 'true' if it shouldn't (it succeeded). + nc.zonePodEvictor[k].Try(func(value scheduler.TimedValue) (bool, time.Duration) { + node, err := nc.nodeLister.Get(value.Value) + if apierrors.IsNotFound(err) { + glog.Warningf("Node %v no longer present in nodeLister!", value.Value) + } else if err != nil { + glog.Warningf("Failed to get Node %v from the nodeLister: %v", value.Value, err) + } else { + zone := utilnode.GetZoneKey(node) + evictionsNumber.WithLabelValues(zone).Inc() + } + nodeUID, _ := value.UID.(string) + remaining, err := nodeutil.DeletePods(nc.kubeClient, nc.recorder, value.Value, nodeUID, nc.daemonSetStore) + if err != nil { + utilruntime.HandleError(fmt.Errorf("unable to evict node %q: %v", value.Value, err)) + return false, 0 + } + if remaining { + glog.Infof("Pods awaiting deletion due to Controller eviction") + } + return true, 0 + }) + } +} + +// monitorNodeStatus verifies node status are constantly updated by kubelet, and if not, +// post "NodeReady==ConditionUnknown". It also evicts all pods if node is not ready or +// not reachable for a long period of time. +func (nc *Controller) monitorNodeStatus() error { + // We are listing nodes from local cache as we can tolerate some small delays + // comparing to state from etcd and there is eventual consistency anyway. + nodes, err := nc.nodeLister.List(labels.Everything()) + if err != nil { + return err + } + added, deleted, newZoneRepresentatives := nc.classifyNodes(nodes) + + for i := range newZoneRepresentatives { + nc.addPodEvictorForNewZone(newZoneRepresentatives[i]) + } + + for i := range added { + glog.V(1).Infof("Controller observed a new Node: %#v", added[i].Name) + nodeutil.RecordNodeEvent(nc.recorder, added[i].Name, string(added[i].UID), v1.EventTypeNormal, "RegisteredNode", fmt.Sprintf("Registered Node %v in Controller", added[i].Name)) + nc.knownNodeSet[added[i].Name] = added[i] + nc.addPodEvictorForNewZone(added[i]) + if nc.useTaintBasedEvictions { + nc.markNodeAsReachable(added[i]) + } else { + nc.cancelPodEviction(added[i]) + } + } + + for i := range deleted { + glog.V(1).Infof("Controller observed a Node deletion: %v", deleted[i].Name) + nodeutil.RecordNodeEvent(nc.recorder, deleted[i].Name, string(deleted[i].UID), v1.EventTypeNormal, "RemovingNode", fmt.Sprintf("Removing Node %v from Controller", deleted[i].Name)) + delete(nc.knownNodeSet, deleted[i].Name) + } + + zoneToNodeConditions := map[string][]*v1.NodeCondition{} + for i := range nodes { + var gracePeriod time.Duration + var observedReadyCondition v1.NodeCondition + var currentReadyCondition *v1.NodeCondition + node := nodes[i].DeepCopy() + if err := wait.PollImmediate(retrySleepTime, retrySleepTime*scheduler.NodeStatusUpdateRetry, func() (bool, error) { + gracePeriod, observedReadyCondition, currentReadyCondition, err = nc.tryUpdateNodeStatus(node) + if err == nil { + return true, nil + } + name := node.Name + node, err = nc.kubeClient.CoreV1().Nodes().Get(name, metav1.GetOptions{}) + if err != nil { + glog.Errorf("Failed while getting a Node to retry updating NodeStatus. Probably Node %s was deleted.", name) + return false, err + } + return false, nil + }); err != nil { + glog.Errorf("Update status of Node '%v' from Controller error: %v. "+ + "Skipping - no pods will be evicted.", node.Name, err) + continue + } + + // We do not treat a master node as a part of the cluster for network disruption checking. + if !system.IsMasterNode(node.Name) { + zoneToNodeConditions[utilnode.GetZoneKey(node)] = append(zoneToNodeConditions[utilnode.GetZoneKey(node)], currentReadyCondition) + } + + decisionTimestamp := nc.now() + if currentReadyCondition != nil { + // Check eviction timeout against decisionTimestamp + if observedReadyCondition.Status == v1.ConditionFalse { + if nc.useTaintBasedEvictions { + // We want to update the taint straight away if Node is already tainted with the UnreachableTaint + if taintutils.TaintExists(node.Spec.Taints, UnreachableTaintTemplate) { + taintToAdd := *NotReadyTaintTemplate + if !nodeutil.SwapNodeControllerTaint(nc.kubeClient, []*v1.Taint{&taintToAdd}, []*v1.Taint{UnreachableTaintTemplate}, node) { + glog.Errorf("Failed to instantly swap UnreachableTaint to NotReadyTaint. Will try again in the next cycle.") + } + } else if nc.markNodeForTainting(node) { + glog.V(2).Infof("Node %v is NotReady as of %v. Adding it to the Taint queue.", + node.Name, + decisionTimestamp, + ) + } + } else { + if decisionTimestamp.After(nc.nodeStatusMap[node.Name].readyTransitionTimestamp.Add(nc.podEvictionTimeout)) { + if nc.evictPods(node) { + glog.V(2).Infof("Node is NotReady. Adding Pods on Node %s to eviction queue: %v is later than %v + %v", + node.Name, + decisionTimestamp, + nc.nodeStatusMap[node.Name].readyTransitionTimestamp, + nc.podEvictionTimeout, + ) + } + } + } + } + if observedReadyCondition.Status == v1.ConditionUnknown { + if nc.useTaintBasedEvictions { + // We want to update the taint straight away if Node is already tainted with the UnreachableTaint + if taintutils.TaintExists(node.Spec.Taints, NotReadyTaintTemplate) { + taintToAdd := *UnreachableTaintTemplate + if !nodeutil.SwapNodeControllerTaint(nc.kubeClient, []*v1.Taint{&taintToAdd}, []*v1.Taint{NotReadyTaintTemplate}, node) { + glog.Errorf("Failed to instantly swap UnreachableTaint to NotReadyTaint. Will try again in the next cycle.") + } + } else if nc.markNodeForTainting(node) { + glog.V(2).Infof("Node %v is unresponsive as of %v. Adding it to the Taint queue.", + node.Name, + decisionTimestamp, + ) + } + } else { + if decisionTimestamp.After(nc.nodeStatusMap[node.Name].probeTimestamp.Add(nc.podEvictionTimeout)) { + if nc.evictPods(node) { + glog.V(2).Infof("Node is unresponsive. Adding Pods on Node %s to eviction queues: %v is later than %v + %v", + node.Name, + decisionTimestamp, + nc.nodeStatusMap[node.Name].readyTransitionTimestamp, + nc.podEvictionTimeout-gracePeriod, + ) + } + } + } + } + if observedReadyCondition.Status == v1.ConditionTrue { + if nc.useTaintBasedEvictions { + removed, err := nc.markNodeAsReachable(node) + if err != nil { + glog.Errorf("Failed to remove taints from node %v. Will retry in next iteration.", node.Name) + } + if removed { + glog.V(2).Infof("Node %s is healthy again, removing all taints", node.Name) + } + } else { + if nc.cancelPodEviction(node) { + glog.V(2).Infof("Node %s is ready again, cancelled pod eviction", node.Name) + } + } + } + + // Report node event. + if currentReadyCondition.Status != v1.ConditionTrue && observedReadyCondition.Status == v1.ConditionTrue { + nodeutil.RecordNodeStatusChange(nc.recorder, node, "NodeNotReady") + if err = nodeutil.MarkAllPodsNotReady(nc.kubeClient, node); err != nil { + utilruntime.HandleError(fmt.Errorf("Unable to mark all pods NotReady on node %v: %v", node.Name, err)) + } + } + + // Check with the cloud provider to see if the node still exists. If it + // doesn't, delete the node immediately. + if currentReadyCondition.Status != v1.ConditionTrue && nc.cloud != nil { + exists, err := nc.nodeExistsInCloudProvider(types.NodeName(node.Name)) + if err != nil { + glog.Errorf("Error determining if node %v exists in cloud: %v", node.Name, err) + continue + } + if !exists { + glog.V(2).Infof("Deleting node (no longer present in cloud provider): %s", node.Name) + nodeutil.RecordNodeEvent(nc.recorder, node.Name, string(node.UID), v1.EventTypeNormal, "DeletingNode", fmt.Sprintf("Deleting Node %v because it's not present according to cloud provider", node.Name)) + go func(nodeName string) { + defer utilruntime.HandleCrash() + // Kubelet is not reporting and Cloud Provider says node + // is gone. Delete it without worrying about grace + // periods. + if err := nodeutil.ForcefullyDeleteNode(nc.kubeClient, nodeName); err != nil { + glog.Errorf("Unable to forcefully delete node %q: %v", nodeName, err) + } + }(node.Name) + } + } + } + } + nc.handleDisruption(zoneToNodeConditions, nodes) + + return nil +} + +// tryUpdateNodeStatus checks a given node's conditions and tries to update it. Returns grace period to +// which given node is entitled, state of current and last observed Ready Condition, and an error if it occurred. +func (nc *Controller) tryUpdateNodeStatus(node *v1.Node) (time.Duration, v1.NodeCondition, *v1.NodeCondition, error) { + var err error + var gracePeriod time.Duration + var observedReadyCondition v1.NodeCondition + _, currentReadyCondition := v1node.GetNodeCondition(&node.Status, v1.NodeReady) + if currentReadyCondition == nil { + // If ready condition is nil, then kubelet (or nodecontroller) never posted node status. + // A fake ready condition is created, where LastProbeTime and LastTransitionTime is set + // to node.CreationTimestamp to avoid handle the corner case. + observedReadyCondition = v1.NodeCondition{ + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + LastHeartbeatTime: node.CreationTimestamp, + LastTransitionTime: node.CreationTimestamp, + } + gracePeriod = nc.nodeStartupGracePeriod + nc.nodeStatusMap[node.Name] = nodeStatusData{ + status: node.Status, + probeTimestamp: node.CreationTimestamp, + readyTransitionTimestamp: node.CreationTimestamp, + } + } else { + // If ready condition is not nil, make a copy of it, since we may modify it in place later. + observedReadyCondition = *currentReadyCondition + gracePeriod = nc.nodeMonitorGracePeriod + } + + savedNodeStatus, found := nc.nodeStatusMap[node.Name] + // There are following cases to check: + // - both saved and new status have no Ready Condition set - we leave everything as it is, + // - saved status have no Ready Condition, but current one does - Controller was restarted with Node data already present in etcd, + // - saved status have some Ready Condition, but current one does not - it's an error, but we fill it up because that's probably a good thing to do, + // - both saved and current statuses have Ready Conditions and they have the same LastProbeTime - nothing happened on that Node, it may be + // unresponsive, so we leave it as it is, + // - both saved and current statuses have Ready Conditions, they have different LastProbeTimes, but the same Ready Condition State - + // everything's in order, no transition occurred, we update only probeTimestamp, + // - both saved and current statuses have Ready Conditions, different LastProbeTimes and different Ready Condition State - + // Ready Condition changed it state since we last seen it, so we update both probeTimestamp and readyTransitionTimestamp. + // TODO: things to consider: + // - if 'LastProbeTime' have gone back in time its probably an error, currently we ignore it, + // - currently only correct Ready State transition outside of Node Controller is marking it ready by Kubelet, we don't check + // if that's the case, but it does not seem necessary. + var savedCondition *v1.NodeCondition + if found { + _, savedCondition = v1node.GetNodeCondition(&savedNodeStatus.status, v1.NodeReady) + } + _, observedCondition := v1node.GetNodeCondition(&node.Status, v1.NodeReady) + if !found { + glog.Warningf("Missing timestamp for Node %s. Assuming now as a timestamp.", node.Name) + savedNodeStatus = nodeStatusData{ + status: node.Status, + probeTimestamp: nc.now(), + readyTransitionTimestamp: nc.now(), + } + } else if savedCondition == nil && observedCondition != nil { + glog.V(1).Infof("Creating timestamp entry for newly observed Node %s", node.Name) + savedNodeStatus = nodeStatusData{ + status: node.Status, + probeTimestamp: nc.now(), + readyTransitionTimestamp: nc.now(), + } + } else if savedCondition != nil && observedCondition == nil { + glog.Errorf("ReadyCondition was removed from Status of Node %s", node.Name) + // TODO: figure out what to do in this case. For now we do the same thing as above. + savedNodeStatus = nodeStatusData{ + status: node.Status, + probeTimestamp: nc.now(), + readyTransitionTimestamp: nc.now(), + } + } else if savedCondition != nil && observedCondition != nil && savedCondition.LastHeartbeatTime != observedCondition.LastHeartbeatTime { + var transitionTime metav1.Time + // If ReadyCondition changed since the last time we checked, we update the transition timestamp to "now", + // otherwise we leave it as it is. + if savedCondition.LastTransitionTime != observedCondition.LastTransitionTime { + glog.V(3).Infof("ReadyCondition for Node %s transitioned from %v to %v", node.Name, savedCondition, observedCondition) + transitionTime = nc.now() + } else { + transitionTime = savedNodeStatus.readyTransitionTimestamp + } + if glog.V(5) { + glog.V(5).Infof("Node %s ReadyCondition updated. Updating timestamp: %+v vs %+v.", node.Name, savedNodeStatus.status, node.Status) + } else { + glog.V(3).Infof("Node %s ReadyCondition updated. Updating timestamp.", node.Name) + } + savedNodeStatus = nodeStatusData{ + status: node.Status, + probeTimestamp: nc.now(), + readyTransitionTimestamp: transitionTime, + } + } + nc.nodeStatusMap[node.Name] = savedNodeStatus + + if nc.now().After(savedNodeStatus.probeTimestamp.Add(gracePeriod)) { + // NodeReady condition was last set longer ago than gracePeriod, so update it to Unknown + // (regardless of its current value) in the master. + if currentReadyCondition == nil { + glog.V(2).Infof("node %v is never updated by kubelet", node.Name) + node.Status.Conditions = append(node.Status.Conditions, v1.NodeCondition{ + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + Reason: "NodeStatusNeverUpdated", + Message: fmt.Sprintf("Kubelet never posted node status."), + LastHeartbeatTime: node.CreationTimestamp, + LastTransitionTime: nc.now(), + }) + } else { + glog.V(4).Infof("node %v hasn't been updated for %+v. Last ready condition is: %+v", + node.Name, nc.now().Time.Sub(savedNodeStatus.probeTimestamp.Time), observedReadyCondition) + if observedReadyCondition.Status != v1.ConditionUnknown { + currentReadyCondition.Status = v1.ConditionUnknown + currentReadyCondition.Reason = "NodeStatusUnknown" + currentReadyCondition.Message = "Kubelet stopped posting node status." + // LastProbeTime is the last time we heard from kubelet. + currentReadyCondition.LastHeartbeatTime = observedReadyCondition.LastHeartbeatTime + currentReadyCondition.LastTransitionTime = nc.now() + } + } + + // remaining node conditions should also be set to Unknown + remainingNodeConditionTypes := []v1.NodeConditionType{ + v1.NodeMemoryPressure, + v1.NodeDiskPressure, + // We don't change 'NodeNetworkUnavailable' condition, as it's managed on a control plane level. + // v1.NodeNetworkUnavailable, + } + + nowTimestamp := nc.now() + for _, nodeConditionType := range remainingNodeConditionTypes { + _, currentCondition := v1node.GetNodeCondition(&node.Status, nodeConditionType) + if currentCondition == nil { + glog.V(2).Infof("Condition %v of node %v was never updated by kubelet", nodeConditionType, node.Name) + node.Status.Conditions = append(node.Status.Conditions, v1.NodeCondition{ + Type: nodeConditionType, + Status: v1.ConditionUnknown, + Reason: "NodeStatusNeverUpdated", + Message: "Kubelet never posted node status.", + LastHeartbeatTime: node.CreationTimestamp, + LastTransitionTime: nowTimestamp, + }) + } else { + glog.V(4).Infof("node %v hasn't been updated for %+v. Last %v is: %+v", + node.Name, nc.now().Time.Sub(savedNodeStatus.probeTimestamp.Time), nodeConditionType, currentCondition) + if currentCondition.Status != v1.ConditionUnknown { + currentCondition.Status = v1.ConditionUnknown + currentCondition.Reason = "NodeStatusUnknown" + currentCondition.Message = "Kubelet stopped posting node status." + currentCondition.LastTransitionTime = nowTimestamp + } + } + } + + _, currentCondition := v1node.GetNodeCondition(&node.Status, v1.NodeReady) + if !apiequality.Semantic.DeepEqual(currentCondition, &observedReadyCondition) { + if _, err = nc.kubeClient.CoreV1().Nodes().UpdateStatus(node); err != nil { + glog.Errorf("Error updating node %s: %v", node.Name, err) + return gracePeriod, observedReadyCondition, currentReadyCondition, err + } + nc.nodeStatusMap[node.Name] = nodeStatusData{ + status: node.Status, + probeTimestamp: nc.nodeStatusMap[node.Name].probeTimestamp, + readyTransitionTimestamp: nc.now(), + } + return gracePeriod, observedReadyCondition, currentReadyCondition, nil + } + } + + return gracePeriod, observedReadyCondition, currentReadyCondition, err +} + +func (nc *Controller) handleDisruption(zoneToNodeConditions map[string][]*v1.NodeCondition, nodes []*v1.Node) { + newZoneStates := map[string]ZoneState{} + allAreFullyDisrupted := true + for k, v := range zoneToNodeConditions { + zoneSize.WithLabelValues(k).Set(float64(len(v))) + unhealthy, newState := nc.computeZoneStateFunc(v) + zoneHealth.WithLabelValues(k).Set(float64(100*(len(v)-unhealthy)) / float64(len(v))) + unhealthyNodes.WithLabelValues(k).Set(float64(unhealthy)) + if newState != stateFullDisruption { + allAreFullyDisrupted = false + } + newZoneStates[k] = newState + if _, had := nc.zoneStates[k]; !had { + glog.Errorf("Setting initial state for unseen zone: %v", k) + nc.zoneStates[k] = stateInitial + } + } + + allWasFullyDisrupted := true + for k, v := range nc.zoneStates { + if _, have := zoneToNodeConditions[k]; !have { + zoneSize.WithLabelValues(k).Set(0) + zoneHealth.WithLabelValues(k).Set(100) + unhealthyNodes.WithLabelValues(k).Set(0) + delete(nc.zoneStates, k) + continue + } + if v != stateFullDisruption { + allWasFullyDisrupted = false + break + } + } + + // At least one node was responding in previous pass or in the current pass. Semantics is as follows: + // - if the new state is "partialDisruption" we call a user defined function that returns a new limiter to use, + // - if the new state is "normal" we resume normal operation (go back to default limiter settings), + // - if new state is "fullDisruption" we restore normal eviction rate, + // - unless all zones in the cluster are in "fullDisruption" - in that case we stop all evictions. + if !allAreFullyDisrupted || !allWasFullyDisrupted { + // We're switching to full disruption mode + if allAreFullyDisrupted { + glog.V(0).Info("Controller detected that all Nodes are not-Ready. Entering master disruption mode.") + for i := range nodes { + if nc.useTaintBasedEvictions { + _, err := nc.markNodeAsReachable(nodes[i]) + if err != nil { + glog.Errorf("Failed to remove taints from Node %v", nodes[i].Name) + } + } else { + nc.cancelPodEviction(nodes[i]) + } + } + // We stop all evictions. + for k := range nc.zoneStates { + if nc.useTaintBasedEvictions { + nc.zoneNoExecuteTainter[k].SwapLimiter(0) + } else { + nc.zonePodEvictor[k].SwapLimiter(0) + } + } + for k := range nc.zoneStates { + nc.zoneStates[k] = stateFullDisruption + } + // All rate limiters are updated, so we can return early here. + return + } + // We're exiting full disruption mode + if allWasFullyDisrupted { + glog.V(0).Info("Controller detected that some Nodes are Ready. Exiting master disruption mode.") + // When exiting disruption mode update probe timestamps on all Nodes. + now := nc.now() + for i := range nodes { + v := nc.nodeStatusMap[nodes[i].Name] + v.probeTimestamp = now + v.readyTransitionTimestamp = now + nc.nodeStatusMap[nodes[i].Name] = v + } + // We reset all rate limiters to settings appropriate for the given state. + for k := range nc.zoneStates { + nc.setLimiterInZone(k, len(zoneToNodeConditions[k]), newZoneStates[k]) + nc.zoneStates[k] = newZoneStates[k] + } + return + } + // We know that there's at least one not-fully disrupted so, + // we can use default behavior for rate limiters + for k, v := range nc.zoneStates { + newState := newZoneStates[k] + if v == newState { + continue + } + glog.V(0).Infof("Controller detected that zone %v is now in state %v.", k, newState) + nc.setLimiterInZone(k, len(zoneToNodeConditions[k]), newState) + nc.zoneStates[k] = newState + } + } +} + +func (nc *Controller) setLimiterInZone(zone string, zoneSize int, state ZoneState) { + switch state { + case stateNormal: + if nc.useTaintBasedEvictions { + nc.zoneNoExecuteTainter[zone].SwapLimiter(nc.evictionLimiterQPS) + } else { + nc.zonePodEvictor[zone].SwapLimiter(nc.evictionLimiterQPS) + } + case statePartialDisruption: + if nc.useTaintBasedEvictions { + nc.zoneNoExecuteTainter[zone].SwapLimiter( + nc.enterPartialDisruptionFunc(zoneSize)) + } else { + nc.zonePodEvictor[zone].SwapLimiter( + nc.enterPartialDisruptionFunc(zoneSize)) + } + case stateFullDisruption: + if nc.useTaintBasedEvictions { + nc.zoneNoExecuteTainter[zone].SwapLimiter( + nc.enterFullDisruptionFunc(zoneSize)) + } else { + nc.zonePodEvictor[zone].SwapLimiter( + nc.enterFullDisruptionFunc(zoneSize)) + } + } +} + +// classifyNodes classifies the allNodes to three categories: +// 1. added: the nodes that in 'allNodes', but not in 'knownNodeSet' +// 2. deleted: the nodes that in 'knownNodeSet', but not in 'allNodes' +// 3. newZoneRepresentatives: the nodes that in both 'knownNodeSet' and 'allNodes', but no zone states +func (nc *Controller) classifyNodes(allNodes []*v1.Node) (added, deleted, newZoneRepresentatives []*v1.Node) { + for i := range allNodes { + if _, has := nc.knownNodeSet[allNodes[i].Name]; !has { + added = append(added, allNodes[i]) + } else { + // Currently, we only consider new zone as updated. + zone := utilnode.GetZoneKey(allNodes[i]) + if _, found := nc.zoneStates[zone]; !found { + newZoneRepresentatives = append(newZoneRepresentatives, allNodes[i]) + } + } + } + + // If there's a difference between lengths of known Nodes and observed nodes + // we must have removed some Node. + if len(nc.knownNodeSet)+len(added) != len(allNodes) { + knowSetCopy := map[string]*v1.Node{} + for k, v := range nc.knownNodeSet { + knowSetCopy[k] = v + } + for i := range allNodes { + delete(knowSetCopy, allNodes[i].Name) + } + for i := range knowSetCopy { + deleted = append(deleted, knowSetCopy[i]) + } + } + return +} + +// HealthyQPSFunc returns the default value for cluster eviction rate - we take +// nodeNum for consistency with ReducedQPSFunc. +func (nc *Controller) HealthyQPSFunc(nodeNum int) float32 { + return nc.evictionLimiterQPS +} + +// ReducedQPSFunc returns the QPS for when a the cluster is large make +// evictions slower, if they're small stop evictions altogether. +func (nc *Controller) ReducedQPSFunc(nodeNum int) float32 { + if int32(nodeNum) > nc.largeClusterThreshold { + return nc.secondaryEvictionLimiterQPS + } + return 0 +} + +// addPodEvictorForNewZone checks if new zone appeared, and if so add new evictor. +func (nc *Controller) addPodEvictorForNewZone(node *v1.Node) { + zone := utilnode.GetZoneKey(node) + if _, found := nc.zoneStates[zone]; !found { + nc.zoneStates[zone] = stateInitial + if !nc.useTaintBasedEvictions { + nc.zonePodEvictor[zone] = + scheduler.NewRateLimitedTimedQueue( + flowcontrol.NewTokenBucketRateLimiter(nc.evictionLimiterQPS, scheduler.EvictionRateLimiterBurst)) + } else { + nc.zoneNoExecuteTainter[zone] = + scheduler.NewRateLimitedTimedQueue( + flowcontrol.NewTokenBucketRateLimiter(nc.evictionLimiterQPS, scheduler.EvictionRateLimiterBurst)) + } + // Init the metric for the new zone. + glog.Infof("Initializing eviction metric for zone: %v", zone) + evictionsNumber.WithLabelValues(zone).Add(0) + } +} + +// cancelPodEviction removes any queued evictions, typically because the node is available again. It +// returns true if an eviction was queued. +func (nc *Controller) cancelPodEviction(node *v1.Node) bool { + zone := utilnode.GetZoneKey(node) + nc.evictorLock.Lock() + defer nc.evictorLock.Unlock() + wasDeleting := nc.zonePodEvictor[zone].Remove(node.Name) + if wasDeleting { + glog.V(2).Infof("Cancelling pod Eviction on Node: %v", node.Name) + return true + } + return false +} + +// evictPods queues an eviction for the provided node name, and returns false if the node is already +// queued for eviction. +func (nc *Controller) evictPods(node *v1.Node) bool { + nc.evictorLock.Lock() + defer nc.evictorLock.Unlock() + return nc.zonePodEvictor[utilnode.GetZoneKey(node)].Add(node.Name, string(node.UID)) +} + +func (nc *Controller) markNodeForTainting(node *v1.Node) bool { + nc.evictorLock.Lock() + defer nc.evictorLock.Unlock() + return nc.zoneNoExecuteTainter[utilnode.GetZoneKey(node)].Add(node.Name, string(node.UID)) +} + +func (nc *Controller) markNodeAsReachable(node *v1.Node) (bool, error) { + nc.evictorLock.Lock() + defer nc.evictorLock.Unlock() + err := controller.RemoveTaintOffNode(nc.kubeClient, node.Name, node, UnreachableTaintTemplate) + if err != nil { + glog.Errorf("Failed to remove taint from node %v: %v", node.Name, err) + return false, err + } + err = controller.RemoveTaintOffNode(nc.kubeClient, node.Name, node, NotReadyTaintTemplate) + if err != nil { + glog.Errorf("Failed to remove taint from node %v: %v", node.Name, err) + return false, err + } + return nc.zoneNoExecuteTainter[utilnode.GetZoneKey(node)].Remove(node.Name), nil +} + +// ComputeZoneState returns a slice of NodeReadyConditions for all Nodes in a given zone. +// The zone is considered: +// - fullyDisrupted if there're no Ready Nodes, +// - partiallyDisrupted if at least than nc.unhealthyZoneThreshold percent of Nodes are not Ready, +// - normal otherwise +func (nc *Controller) ComputeZoneState(nodeReadyConditions []*v1.NodeCondition) (int, ZoneState) { + readyNodes := 0 + notReadyNodes := 0 + for i := range nodeReadyConditions { + if nodeReadyConditions[i] != nil && nodeReadyConditions[i].Status == v1.ConditionTrue { + readyNodes++ + } else { + notReadyNodes++ + } + } + switch { + case readyNodes == 0 && notReadyNodes > 0: + return notReadyNodes, stateFullDisruption + case notReadyNodes > 2 && float32(notReadyNodes)/float32(notReadyNodes+readyNodes) >= nc.unhealthyZoneThreshold: + return notReadyNodes, statePartialDisruption + default: + return notReadyNodes, stateNormal + } +} diff --git a/pkg/controller/nodelifecycle/node_lifecycle_controller_test.go b/pkg/controller/nodelifecycle/node_lifecycle_controller_test.go new file mode 100644 index 00000000000..6813a0909d3 --- /dev/null +++ b/pkg/controller/nodelifecycle/node_lifecycle_controller_test.go @@ -0,0 +1,2407 @@ +/* +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 nodelifecycle + +import ( + "strings" + "testing" + "time" + + "k8s.io/api/core/v1" + extensions "k8s.io/api/extensions/v1beta1" + 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/types" + "k8s.io/apimachinery/pkg/util/diff" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/informers" + coreinformers "k8s.io/client-go/informers/core/v1" + extensionsinformers "k8s.io/client-go/informers/extensions/v1beta1" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/fake" + testcore "k8s.io/client-go/testing" + "k8s.io/kubernetes/pkg/cloudprovider" + fakecloud "k8s.io/kubernetes/pkg/cloudprovider/providers/fake" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/controller/nodelifecycle/scheduler" + "k8s.io/kubernetes/pkg/controller/testutil" + nodeutil "k8s.io/kubernetes/pkg/controller/util/node" + kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + "k8s.io/kubernetes/pkg/util/node" + taintutils "k8s.io/kubernetes/pkg/util/taints" +) + +const ( + testNodeMonitorGracePeriod = 40 * time.Second + testNodeStartupGracePeriod = 60 * time.Second + testNodeMonitorPeriod = 5 * time.Second + testRateLimiterQPS = float32(10000) + testLargeClusterThreshold = 20 + testUnhealthyThreshold = float32(0.55) +) + +func alwaysReady() bool { return true } + +type nodeLifecycleController struct { + *Controller + nodeInformer coreinformers.NodeInformer + daemonSetInformer extensionsinformers.DaemonSetInformer +} + +// doEviction does the fake eviction and returns the status of eviction operation. +func (nc *nodeLifecycleController) doEviction(fakeNodeHandler *testutil.FakeNodeHandler) bool { + var podEvicted bool + zones := testutil.GetZones(fakeNodeHandler) + for _, zone := range zones { + nc.zonePodEvictor[zone].Try(func(value scheduler.TimedValue) (bool, time.Duration) { + uid, _ := value.UID.(string) + nodeutil.DeletePods(fakeNodeHandler, nc.recorder, value.Value, uid, nc.daemonSetStore) + return true, 0 + }) + } + + for _, action := range fakeNodeHandler.Actions() { + if action.GetVerb() == "delete" && action.GetResource().Resource == "pods" { + podEvicted = true + return podEvicted + } + } + return podEvicted +} + +func (nc *nodeLifecycleController) syncNodeStore(fakeNodeHandler *testutil.FakeNodeHandler) error { + nodes, err := fakeNodeHandler.List(metav1.ListOptions{}) + if err != nil { + return err + } + newElems := make([]interface{}, 0, len(nodes.Items)) + for i := range nodes.Items { + newElems = append(newElems, &nodes.Items[i]) + } + return nc.nodeInformer.Informer().GetStore().Replace(newElems, "newRV") +} + +func newNodeLifecycleControllerFromClient( + cloud cloudprovider.Interface, + kubeClient clientset.Interface, + podEvictionTimeout time.Duration, + evictionLimiterQPS float32, + secondaryEvictionLimiterQPS float32, + largeClusterThreshold int32, + unhealthyZoneThreshold float32, + nodeMonitorGracePeriod time.Duration, + nodeStartupGracePeriod time.Duration, + nodeMonitorPeriod time.Duration, + useTaints bool, +) (*nodeLifecycleController, error) { + + factory := informers.NewSharedInformerFactory(kubeClient, controller.NoResyncPeriodFunc()) + + nodeInformer := factory.Core().V1().Nodes() + daemonSetInformer := factory.Extensions().V1beta1().DaemonSets() + + nc, err := NewNodeLifecycleController( + factory.Core().V1().Pods(), + nodeInformer, + daemonSetInformer, + cloud, + kubeClient, + nodeMonitorPeriod, + nodeStartupGracePeriod, + nodeMonitorGracePeriod, + podEvictionTimeout, + evictionLimiterQPS, + secondaryEvictionLimiterQPS, + largeClusterThreshold, + unhealthyZoneThreshold, + useTaints, + useTaints, + useTaints, + ) + if err != nil { + return nil, err + } + + nc.podInformerSynced = alwaysReady + nc.nodeInformerSynced = alwaysReady + nc.daemonSetInformerSynced = alwaysReady + + return &nodeLifecycleController{nc, nodeInformer, daemonSetInformer}, nil +} + +func TestMonitorNodeStatusEvictPods(t *testing.T) { + fakeNow := metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC) + evictionTimeout := 10 * time.Minute + labels := map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + } + + // Because of the logic that prevents NC from evicting anything when all Nodes are NotReady + // we need second healthy node in tests. Because of how the tests are written we need to update + // the status of this Node. + healthyNodeNewStatus := v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + // Node status has just been updated, and is NotReady for 10min. + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 9, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + } + + table := []struct { + fakeNodeHandler *testutil.FakeNodeHandler + daemonSets []extensions.DaemonSet + timeToPass time.Duration + newNodeStatus v1.NodeStatus + secondNodeNewStatus v1.NodeStatus + expectedEvictPods bool + description string + }{ + // Node created recently, with no status (happens only at cluster startup). + { + fakeNodeHandler: &testutil.FakeNodeHandler{ + Existing: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: fakeNow, + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + }, + Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), + }, + daemonSets: nil, + timeToPass: 0, + newNodeStatus: v1.NodeStatus{}, + secondNodeNewStatus: healthyNodeNewStatus, + expectedEvictPods: false, + description: "Node created recently, with no status.", + }, + // Node created recently without FailureDomain labels which is added back later, with no status (happens only at cluster startup). + { + fakeNodeHandler: &testutil.FakeNodeHandler{ + Existing: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: fakeNow, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + }, + Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), + }, + daemonSets: nil, + timeToPass: 0, + newNodeStatus: v1.NodeStatus{}, + secondNodeNewStatus: healthyNodeNewStatus, + expectedEvictPods: false, + description: "Node created recently without FailureDomain labels which is added back later, with no status (happens only at cluster startup).", + }, + // Node created long time ago, and kubelet posted NotReady for a short period of time. + { + fakeNodeHandler: &testutil.FakeNodeHandler{ + Existing: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionFalse, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + }, + Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), + }, + daemonSets: nil, + timeToPass: evictionTimeout, + newNodeStatus: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionFalse, + // Node status has just been updated, and is NotReady for 10min. + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 9, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + secondNodeNewStatus: healthyNodeNewStatus, + expectedEvictPods: false, + description: "Node created long time ago, and kubelet posted NotReady for a short period of time.", + }, + // Pod is ds-managed, and kubelet posted NotReady for a long period of time. + { + fakeNodeHandler: &testutil.FakeNodeHandler{ + Existing: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionFalse, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + }, + Clientset: fake.NewSimpleClientset( + &v1.PodList{ + Items: []v1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "pod0", + Namespace: "default", + Labels: map[string]string{"daemon": "yes"}, + }, + Spec: v1.PodSpec{ + NodeName: "node0", + }, + }, + }, + }, + ), + }, + daemonSets: []extensions.DaemonSet{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "ds0", + Namespace: "default", + }, + Spec: extensions.DaemonSetSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"daemon": "yes"}, + }, + }, + }, + }, + timeToPass: time.Hour, + newNodeStatus: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionFalse, + // Node status has just been updated, and is NotReady for 1hr. + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 59, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + secondNodeNewStatus: healthyNodeNewStatus, + expectedEvictPods: false, + description: "Pod is ds-managed, and kubelet posted NotReady for a long period of time.", + }, + // Node created long time ago, and kubelet posted NotReady for a long period of time. + { + fakeNodeHandler: &testutil.FakeNodeHandler{ + Existing: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionFalse, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + }, + Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), + }, + daemonSets: nil, + timeToPass: time.Hour, + newNodeStatus: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionFalse, + // Node status has just been updated, and is NotReady for 1hr. + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 59, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + secondNodeNewStatus: healthyNodeNewStatus, + expectedEvictPods: true, + description: "Node created long time ago, and kubelet posted NotReady for a long period of time.", + }, + // Node created long time ago, node controller posted Unknown for a short period of time. + { + fakeNodeHandler: &testutil.FakeNodeHandler{ + Existing: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + }, + Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), + }, + daemonSets: nil, + timeToPass: evictionTimeout - testNodeMonitorGracePeriod, + newNodeStatus: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + // Node status was updated by nodecontroller 10min ago + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + secondNodeNewStatus: healthyNodeNewStatus, + expectedEvictPods: false, + description: "Node created long time ago, node controller posted Unknown for a short period of time.", + }, + // Node created long time ago, node controller posted Unknown for a long period of time. + { + fakeNodeHandler: &testutil.FakeNodeHandler{ + Existing: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + }, + Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), + }, + daemonSets: nil, + timeToPass: 60 * time.Minute, + newNodeStatus: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + // Node status was updated by nodecontroller 1hr ago + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + secondNodeNewStatus: healthyNodeNewStatus, + expectedEvictPods: true, + description: "Node created long time ago, node controller posted Unknown for a long period of time.", + }, + } + + for _, item := range table { + nodeController, _ := newNodeLifecycleControllerFromClient( + nil, + item.fakeNodeHandler, + evictionTimeout, + testRateLimiterQPS, + testRateLimiterQPS, + testLargeClusterThreshold, + testUnhealthyThreshold, + testNodeMonitorGracePeriod, + testNodeStartupGracePeriod, + testNodeMonitorPeriod, + false) + nodeController.now = func() metav1.Time { return fakeNow } + nodeController.recorder = testutil.NewFakeRecorder() + for _, ds := range item.daemonSets { + nodeController.daemonSetInformer.Informer().GetStore().Add(&ds) + } + if err := nodeController.syncNodeStore(item.fakeNodeHandler); err != nil { + t.Errorf("unexpected error: %v", err) + } + if err := nodeController.monitorNodeStatus(); err != nil { + t.Errorf("unexpected error: %v", err) + } + if item.timeToPass > 0 { + nodeController.now = func() metav1.Time { return metav1.Time{Time: fakeNow.Add(item.timeToPass)} } + item.fakeNodeHandler.Existing[0].Status = item.newNodeStatus + item.fakeNodeHandler.Existing[1].Status = item.secondNodeNewStatus + } + if len(item.fakeNodeHandler.Existing[0].Labels) == 0 && len(item.fakeNodeHandler.Existing[1].Labels) == 0 { + item.fakeNodeHandler.Existing[0].Labels = labels + item.fakeNodeHandler.Existing[1].Labels = labels + } + if err := nodeController.syncNodeStore(item.fakeNodeHandler); err != nil { + t.Errorf("unexpected error: %v", err) + } + if err := nodeController.monitorNodeStatus(); err != nil { + t.Errorf("unexpected error: %v", err) + } + zones := testutil.GetZones(item.fakeNodeHandler) + for _, zone := range zones { + if _, ok := nodeController.zonePodEvictor[zone]; ok { + nodeController.zonePodEvictor[zone].Try(func(value scheduler.TimedValue) (bool, time.Duration) { + nodeUID, _ := value.UID.(string) + nodeutil.DeletePods(item.fakeNodeHandler, nodeController.recorder, value.Value, nodeUID, nodeController.daemonSetInformer.Lister()) + return true, 0 + }) + } else { + t.Fatalf("Zone %v was unitialized!", zone) + } + } + + podEvicted := false + for _, action := range item.fakeNodeHandler.Actions() { + if action.GetVerb() == "delete" && action.GetResource().Resource == "pods" { + podEvicted = true + } + } + + if item.expectedEvictPods != podEvicted { + t.Errorf("expected pod eviction: %+v, got %+v for %+v", item.expectedEvictPods, + podEvicted, item.description) + } + } +} + +func TestPodStatusChange(t *testing.T) { + fakeNow := metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC) + evictionTimeout := 10 * time.Minute + + // Because of the logic that prevents NC from evicting anything when all Nodes are NotReady + // we need second healthy node in tests. Because of how the tests are written we need to update + // the status of this Node. + healthyNodeNewStatus := v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + // Node status has just been updated, and is NotReady for 10min. + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 9, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + } + + // Node created long time ago, node controller posted Unknown for a long period of time. + table := []struct { + fakeNodeHandler *testutil.FakeNodeHandler + daemonSets []extensions.DaemonSet + timeToPass time.Duration + newNodeStatus v1.NodeStatus + secondNodeNewStatus v1.NodeStatus + expectedPodUpdate bool + expectedReason string + description string + }{ + { + fakeNodeHandler: &testutil.FakeNodeHandler{ + Existing: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + }, + Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), + }, + timeToPass: 60 * time.Minute, + newNodeStatus: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + // Node status was updated by nodecontroller 1hr ago + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + secondNodeNewStatus: healthyNodeNewStatus, + expectedPodUpdate: true, + expectedReason: node.NodeUnreachablePodReason, + description: "Node created long time ago, node controller posted Unknown for a " + + "long period of time, the pod status must include reason for termination.", + }, + } + + for _, item := range table { + nodeController, _ := newNodeLifecycleControllerFromClient( + nil, + item.fakeNodeHandler, + evictionTimeout, + testRateLimiterQPS, + testRateLimiterQPS, + testLargeClusterThreshold, + testUnhealthyThreshold, + testNodeMonitorGracePeriod, + testNodeStartupGracePeriod, + testNodeMonitorPeriod, + false) + nodeController.now = func() metav1.Time { return fakeNow } + nodeController.recorder = testutil.NewFakeRecorder() + if err := nodeController.syncNodeStore(item.fakeNodeHandler); err != nil { + t.Errorf("unexpected error: %v", err) + } + if err := nodeController.monitorNodeStatus(); err != nil { + t.Errorf("unexpected error: %v", err) + } + if item.timeToPass > 0 { + nodeController.now = func() metav1.Time { return metav1.Time{Time: fakeNow.Add(item.timeToPass)} } + item.fakeNodeHandler.Existing[0].Status = item.newNodeStatus + item.fakeNodeHandler.Existing[1].Status = item.secondNodeNewStatus + } + if err := nodeController.syncNodeStore(item.fakeNodeHandler); err != nil { + t.Errorf("unexpected error: %v", err) + } + if err := nodeController.monitorNodeStatus(); err != nil { + t.Errorf("unexpected error: %v", err) + } + zones := testutil.GetZones(item.fakeNodeHandler) + for _, zone := range zones { + nodeController.zonePodEvictor[zone].Try(func(value scheduler.TimedValue) (bool, time.Duration) { + nodeUID, _ := value.UID.(string) + nodeutil.DeletePods(item.fakeNodeHandler, nodeController.recorder, value.Value, nodeUID, nodeController.daemonSetStore) + return true, 0 + }) + } + + podReasonUpdate := false + for _, action := range item.fakeNodeHandler.Actions() { + if action.GetVerb() == "update" && action.GetResource().Resource == "pods" { + updateReason := action.(testcore.UpdateActionImpl).GetObject().(*v1.Pod).Status.Reason + podReasonUpdate = true + if updateReason != item.expectedReason { + t.Errorf("expected pod status reason: %+v, got %+v for %+v", item.expectedReason, updateReason, item.description) + } + } + } + + if podReasonUpdate != item.expectedPodUpdate { + t.Errorf("expected pod update: %+v, got %+v for %+v", podReasonUpdate, item.expectedPodUpdate, item.description) + } + } +} + +func TestMonitorNodeStatusEvictPodsWithDisruption(t *testing.T) { + fakeNow := metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC) + evictionTimeout := 10 * time.Minute + timeToPass := 60 * time.Minute + + // Because of the logic that prevents NC from evicting anything when all Nodes are NotReady + // we need second healthy node in tests. Because of how the tests are written we need to update + // the status of this Node. + healthyNodeNewStatus := v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 13, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + } + unhealthyNodeNewStatus := v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + // Node status was updated by nodecontroller 1hr ago + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + } + + table := []struct { + nodeList []*v1.Node + podList []v1.Pod + updatedNodeStatuses []v1.NodeStatus + expectedInitialStates map[string]ZoneState + expectedFollowingStates map[string]ZoneState + expectedEvictPods bool + description string + }{ + // NetworkDisruption: Node created long time ago, node controller posted Unknown for a long period of time on both Nodes. + // Only zone is down - eviction shouldn't take place + { + nodeList: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + }, + podList: []v1.Pod{*testutil.NewPod("pod0", "node0")}, + updatedNodeStatuses: []v1.NodeStatus{ + unhealthyNodeNewStatus, + unhealthyNodeNewStatus, + }, + expectedInitialStates: map[string]ZoneState{testutil.CreateZoneID("region1", "zone1"): stateFullDisruption}, + expectedFollowingStates: map[string]ZoneState{testutil.CreateZoneID("region1", "zone1"): stateFullDisruption}, + expectedEvictPods: false, + description: "Network Disruption: Only zone is down - eviction shouldn't take place.", + }, + // NetworkDisruption: Node created long time ago, node controller posted Unknown for a long period of time on both Nodes. + // Both zones down - eviction shouldn't take place + { + nodeList: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region2", + kubeletapis.LabelZoneFailureDomain: "zone2", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + }, + + podList: []v1.Pod{*testutil.NewPod("pod0", "node0")}, + updatedNodeStatuses: []v1.NodeStatus{ + unhealthyNodeNewStatus, + unhealthyNodeNewStatus, + }, + expectedInitialStates: map[string]ZoneState{ + testutil.CreateZoneID("region1", "zone1"): stateFullDisruption, + testutil.CreateZoneID("region2", "zone2"): stateFullDisruption, + }, + expectedFollowingStates: map[string]ZoneState{ + testutil.CreateZoneID("region1", "zone1"): stateFullDisruption, + testutil.CreateZoneID("region2", "zone2"): stateFullDisruption, + }, + expectedEvictPods: false, + description: "Network Disruption: Both zones down - eviction shouldn't take place.", + }, + // NetworkDisruption: Node created long time ago, node controller posted Unknown for a long period of time on both Nodes. + // One zone is down - eviction should take place + { + nodeList: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone2", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + }, + podList: []v1.Pod{*testutil.NewPod("pod0", "node0")}, + updatedNodeStatuses: []v1.NodeStatus{ + unhealthyNodeNewStatus, + healthyNodeNewStatus, + }, + expectedInitialStates: map[string]ZoneState{ + testutil.CreateZoneID("region1", "zone1"): stateFullDisruption, + testutil.CreateZoneID("region1", "zone2"): stateNormal, + }, + expectedFollowingStates: map[string]ZoneState{ + testutil.CreateZoneID("region1", "zone1"): stateFullDisruption, + testutil.CreateZoneID("region1", "zone2"): stateNormal, + }, + expectedEvictPods: true, + description: "Network Disruption: One zone is down - eviction should take place.", + }, + // NetworkDisruption: Node created long time ago, node controller posted Unknown for a long period + // of on first Node, eviction should stop even though -master Node is healthy. + { + nodeList: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node-master", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + }, + podList: []v1.Pod{*testutil.NewPod("pod0", "node0")}, + updatedNodeStatuses: []v1.NodeStatus{ + unhealthyNodeNewStatus, + healthyNodeNewStatus, + }, + expectedInitialStates: map[string]ZoneState{ + testutil.CreateZoneID("region1", "zone1"): stateFullDisruption, + }, + expectedFollowingStates: map[string]ZoneState{ + testutil.CreateZoneID("region1", "zone1"): stateFullDisruption, + }, + expectedEvictPods: false, + description: "NetworkDisruption: eviction should stop, only -master Node is healthy", + }, + // NetworkDisruption: Node created long time ago, node controller posted Unknown for a long period of time on both Nodes. + // Initially both zones down, one comes back - eviction should take place + { + nodeList: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone2", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + }, + + podList: []v1.Pod{*testutil.NewPod("pod0", "node0")}, + updatedNodeStatuses: []v1.NodeStatus{ + unhealthyNodeNewStatus, + healthyNodeNewStatus, + }, + expectedInitialStates: map[string]ZoneState{ + testutil.CreateZoneID("region1", "zone1"): stateFullDisruption, + testutil.CreateZoneID("region1", "zone2"): stateFullDisruption, + }, + expectedFollowingStates: map[string]ZoneState{ + testutil.CreateZoneID("region1", "zone1"): stateFullDisruption, + testutil.CreateZoneID("region1", "zone2"): stateNormal, + }, + expectedEvictPods: true, + description: "Initially both zones down, one comes back - eviction should take place", + }, + // NetworkDisruption: Node created long time ago, node controller posted Unknown for a long period of time on both Nodes. + // Zone is partially disrupted - eviction should take place + { + nodeList: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node2", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node3", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node4", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + }, + + podList: []v1.Pod{*testutil.NewPod("pod0", "node0")}, + updatedNodeStatuses: []v1.NodeStatus{ + unhealthyNodeNewStatus, + unhealthyNodeNewStatus, + unhealthyNodeNewStatus, + healthyNodeNewStatus, + healthyNodeNewStatus, + }, + expectedInitialStates: map[string]ZoneState{ + testutil.CreateZoneID("region1", "zone1"): statePartialDisruption, + }, + expectedFollowingStates: map[string]ZoneState{ + testutil.CreateZoneID("region1", "zone1"): statePartialDisruption, + }, + expectedEvictPods: true, + description: "Zone is partially disrupted - eviction should take place.", + }, + } + + for _, item := range table { + fakeNodeHandler := &testutil.FakeNodeHandler{ + Existing: item.nodeList, + Clientset: fake.NewSimpleClientset(&v1.PodList{Items: item.podList}), + } + nodeController, _ := newNodeLifecycleControllerFromClient( + nil, + fakeNodeHandler, + evictionTimeout, + testRateLimiterQPS, + testRateLimiterQPS, + testLargeClusterThreshold, + testUnhealthyThreshold, + testNodeMonitorGracePeriod, + testNodeStartupGracePeriod, + testNodeMonitorPeriod, + false) + nodeController.now = func() metav1.Time { return fakeNow } + nodeController.enterPartialDisruptionFunc = func(nodeNum int) float32 { + return testRateLimiterQPS + } + nodeController.recorder = testutil.NewFakeRecorder() + nodeController.enterFullDisruptionFunc = func(nodeNum int) float32 { + return testRateLimiterQPS + } + if err := nodeController.syncNodeStore(fakeNodeHandler); err != nil { + t.Errorf("unexpected error: %v", err) + } + if err := nodeController.monitorNodeStatus(); err != nil { + t.Errorf("%v: unexpected error: %v", item.description, err) + } + + for zone, state := range item.expectedInitialStates { + if state != nodeController.zoneStates[zone] { + t.Errorf("%v: Unexpected zone state: %v: %v instead %v", item.description, zone, nodeController.zoneStates[zone], state) + } + } + + nodeController.now = func() metav1.Time { return metav1.Time{Time: fakeNow.Add(timeToPass)} } + for i := range item.updatedNodeStatuses { + fakeNodeHandler.Existing[i].Status = item.updatedNodeStatuses[i] + } + + if err := nodeController.syncNodeStore(fakeNodeHandler); err != nil { + t.Errorf("unexpected error: %v", err) + } + if err := nodeController.monitorNodeStatus(); err != nil { + t.Errorf("%v: unexpected error: %v", item.description, err) + } + for zone, state := range item.expectedFollowingStates { + if state != nodeController.zoneStates[zone] { + t.Errorf("%v: Unexpected zone state: %v: %v instead %v", item.description, zone, nodeController.zoneStates[zone], state) + } + } + var podEvicted bool + start := time.Now() + // Infinite loop, used for retrying in case ratelimiter fails to reload for Try function. + // this breaks when we have the status that we need for test case or when we don't see the + // intended result after 1 minute. + for { + podEvicted = nodeController.doEviction(fakeNodeHandler) + if podEvicted == item.expectedEvictPods || time.Since(start) > 1*time.Minute { + break + } + } + if item.expectedEvictPods != podEvicted { + t.Errorf("%v: expected pod eviction: %+v, got %+v", item.description, item.expectedEvictPods, podEvicted) + } + } +} + +// TestCloudProviderNoRateLimit tests that monitorNodes() immediately deletes +// pods and the node when kubelet has not reported, and the cloudprovider says +// the node is gone. +func TestCloudProviderNoRateLimit(t *testing.T) { + fnh := &testutil.FakeNodeHandler{ + Existing: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + }, + Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0"), *testutil.NewPod("pod1", "node0")}}), + DeleteWaitChan: make(chan struct{}), + } + nodeController, _ := newNodeLifecycleControllerFromClient( + nil, + fnh, + 10*time.Minute, + testRateLimiterQPS, + testRateLimiterQPS, + testLargeClusterThreshold, + testUnhealthyThreshold, + testNodeMonitorGracePeriod, + testNodeStartupGracePeriod, + testNodeMonitorPeriod, + false) + nodeController.cloud = &fakecloud.FakeCloud{} + nodeController.now = func() metav1.Time { return metav1.Date(2016, 1, 1, 12, 0, 0, 0, time.UTC) } + nodeController.recorder = testutil.NewFakeRecorder() + nodeController.nodeExistsInCloudProvider = func(nodeName types.NodeName) (bool, error) { + return false, nil + } + // monitorNodeStatus should allow this node to be immediately deleted + if err := nodeController.syncNodeStore(fnh); err != nil { + t.Errorf("unexpected error: %v", err) + } + if err := nodeController.monitorNodeStatus(); err != nil { + t.Errorf("unexpected error: %v", err) + } + select { + case <-fnh.DeleteWaitChan: + case <-time.After(wait.ForeverTestTimeout): + t.Errorf("Timed out waiting %v for node to be deleted", wait.ForeverTestTimeout) + } + if len(fnh.DeletedNodes) != 1 || fnh.DeletedNodes[0].Name != "node0" { + t.Errorf("Node was not deleted") + } + if nodeOnQueue := nodeController.zonePodEvictor[""].Remove("node0"); nodeOnQueue { + t.Errorf("Node was queued for eviction. Should have been immediately deleted.") + } +} + +func TestMonitorNodeStatusUpdateStatus(t *testing.T) { + fakeNow := metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC) + table := []struct { + fakeNodeHandler *testutil.FakeNodeHandler + timeToPass time.Duration + newNodeStatus v1.NodeStatus + expectedEvictPods bool + expectedRequestCount int + expectedNodes []*v1.Node + }{ + // Node created long time ago, without status: + // Expect Unknown status posted from node controller. + { + fakeNodeHandler: &testutil.FakeNodeHandler{ + Existing: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + }, + }, + Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), + }, + expectedRequestCount: 2, // List+Update + expectedNodes: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + Reason: "NodeStatusNeverUpdated", + Message: "Kubelet never posted node status.", + LastHeartbeatTime: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + LastTransitionTime: fakeNow, + }, + { + Type: v1.NodeMemoryPressure, + Status: v1.ConditionUnknown, + Reason: "NodeStatusNeverUpdated", + Message: "Kubelet never posted node status.", + LastHeartbeatTime: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + LastTransitionTime: fakeNow, + }, + { + Type: v1.NodeDiskPressure, + Status: v1.ConditionUnknown, + Reason: "NodeStatusNeverUpdated", + Message: "Kubelet never posted node status.", + LastHeartbeatTime: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + LastTransitionTime: fakeNow, + }, + }, + }, + }, + }, + }, + // Node created recently, without status. + // Expect no action from node controller (within startup grace period). + { + fakeNodeHandler: &testutil.FakeNodeHandler{ + Existing: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: fakeNow, + }, + }, + }, + Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), + }, + expectedRequestCount: 1, // List + expectedNodes: nil, + }, + // Node created long time ago, with status updated by kubelet exceeds grace period. + // Expect Unknown status posted from node controller. + { + fakeNodeHandler: &testutil.FakeNodeHandler{ + Existing: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + // Node status hasn't been updated for 1hr. + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + Capacity: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), + }, + }, + Spec: v1.NodeSpec{ + ExternalID: "node0", + }, + }, + }, + Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), + }, + expectedRequestCount: 3, // (List+)List+Update + timeToPass: time.Hour, + newNodeStatus: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + // Node status hasn't been updated for 1hr. + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + Capacity: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), + }, + }, + expectedNodes: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + Reason: "NodeStatusUnknown", + Message: "Kubelet stopped posting node status.", + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Time{Time: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC).Add(time.Hour)}, + }, + { + Type: v1.NodeMemoryPressure, + Status: v1.ConditionUnknown, + Reason: "NodeStatusNeverUpdated", + Message: "Kubelet never posted node status.", + LastHeartbeatTime: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), // should default to node creation time if condition was never updated + LastTransitionTime: metav1.Time{Time: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC).Add(time.Hour)}, + }, + { + Type: v1.NodeDiskPressure, + Status: v1.ConditionUnknown, + Reason: "NodeStatusNeverUpdated", + Message: "Kubelet never posted node status.", + LastHeartbeatTime: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), // should default to node creation time if condition was never updated + LastTransitionTime: metav1.Time{Time: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC).Add(time.Hour)}, + }, + }, + Capacity: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), + }, + }, + Spec: v1.NodeSpec{ + ExternalID: "node0", + }, + }, + }, + }, + // Node created long time ago, with status updated recently. + // Expect no action from node controller (within monitor grace period). + { + fakeNodeHandler: &testutil.FakeNodeHandler{ + Existing: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + // Node status has just been updated. + LastHeartbeatTime: fakeNow, + LastTransitionTime: fakeNow, + }, + }, + Capacity: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), + }, + }, + Spec: v1.NodeSpec{ + ExternalID: "node0", + }, + }, + }, + Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), + }, + expectedRequestCount: 1, // List + expectedNodes: nil, + }, + } + + for i, item := range table { + nodeController, _ := newNodeLifecycleControllerFromClient( + nil, + item.fakeNodeHandler, + 5*time.Minute, + testRateLimiterQPS, + testRateLimiterQPS, + testLargeClusterThreshold, + testUnhealthyThreshold, + testNodeMonitorGracePeriod, + testNodeStartupGracePeriod, + testNodeMonitorPeriod, + false) + nodeController.now = func() metav1.Time { return fakeNow } + nodeController.recorder = testutil.NewFakeRecorder() + if err := nodeController.syncNodeStore(item.fakeNodeHandler); err != nil { + t.Errorf("unexpected error: %v", err) + } + if err := nodeController.monitorNodeStatus(); err != nil { + t.Errorf("unexpected error: %v", err) + } + if item.timeToPass > 0 { + nodeController.now = func() metav1.Time { return metav1.Time{Time: fakeNow.Add(item.timeToPass)} } + item.fakeNodeHandler.Existing[0].Status = item.newNodeStatus + if err := nodeController.syncNodeStore(item.fakeNodeHandler); err != nil { + t.Errorf("unexpected error: %v", err) + } + if err := nodeController.monitorNodeStatus(); err != nil { + t.Errorf("unexpected error: %v", err) + } + } + if item.expectedRequestCount != item.fakeNodeHandler.RequestCount { + t.Errorf("expected %v call, but got %v.", item.expectedRequestCount, item.fakeNodeHandler.RequestCount) + } + if len(item.fakeNodeHandler.UpdatedNodes) > 0 && !apiequality.Semantic.DeepEqual(item.expectedNodes, item.fakeNodeHandler.UpdatedNodes) { + t.Errorf("Case[%d] unexpected nodes: %s", i, diff.ObjectDiff(item.expectedNodes[0], item.fakeNodeHandler.UpdatedNodes[0])) + } + if len(item.fakeNodeHandler.UpdatedNodeStatuses) > 0 && !apiequality.Semantic.DeepEqual(item.expectedNodes, item.fakeNodeHandler.UpdatedNodeStatuses) { + t.Errorf("Case[%d] unexpected nodes: %s", i, diff.ObjectDiff(item.expectedNodes[0], item.fakeNodeHandler.UpdatedNodeStatuses[0])) + } + } +} + +func TestMonitorNodeStatusMarkPodsNotReady(t *testing.T) { + fakeNow := metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC) + table := []struct { + fakeNodeHandler *testutil.FakeNodeHandler + timeToPass time.Duration + newNodeStatus v1.NodeStatus + expectedPodStatusUpdate bool + }{ + // Node created recently, without status. + // Expect no action from node controller (within startup grace period). + { + fakeNodeHandler: &testutil.FakeNodeHandler{ + Existing: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: fakeNow, + }, + }, + }, + Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), + }, + expectedPodStatusUpdate: false, + }, + // Node created long time ago, with status updated recently. + // Expect no action from node controller (within monitor grace period). + { + fakeNodeHandler: &testutil.FakeNodeHandler{ + Existing: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + // Node status has just been updated. + LastHeartbeatTime: fakeNow, + LastTransitionTime: fakeNow, + }, + }, + Capacity: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), + }, + }, + Spec: v1.NodeSpec{ + ExternalID: "node0", + }, + }, + }, + Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), + }, + expectedPodStatusUpdate: false, + }, + // Node created long time ago, with status updated by kubelet exceeds grace period. + // Expect pods status updated and Unknown node status posted from node controller + { + fakeNodeHandler: &testutil.FakeNodeHandler{ + Existing: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + // Node status hasn't been updated for 1hr. + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + Capacity: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), + }, + }, + Spec: v1.NodeSpec{ + ExternalID: "node0", + }, + }, + }, + Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), + }, + timeToPass: 1 * time.Minute, + newNodeStatus: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + // Node status hasn't been updated for 1hr. + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + Capacity: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"), + }, + }, + expectedPodStatusUpdate: true, + }, + } + + for i, item := range table { + nodeController, _ := newNodeLifecycleControllerFromClient( + nil, + item.fakeNodeHandler, + 5*time.Minute, + testRateLimiterQPS, + testRateLimiterQPS, + testLargeClusterThreshold, + testUnhealthyThreshold, + testNodeMonitorGracePeriod, + testNodeStartupGracePeriod, + testNodeMonitorPeriod, + false) + nodeController.now = func() metav1.Time { return fakeNow } + nodeController.recorder = testutil.NewFakeRecorder() + if err := nodeController.syncNodeStore(item.fakeNodeHandler); err != nil { + t.Errorf("unexpected error: %v", err) + } + if err := nodeController.monitorNodeStatus(); err != nil { + t.Errorf("Case[%d] unexpected error: %v", i, err) + } + if item.timeToPass > 0 { + nodeController.now = func() metav1.Time { return metav1.Time{Time: fakeNow.Add(item.timeToPass)} } + item.fakeNodeHandler.Existing[0].Status = item.newNodeStatus + if err := nodeController.syncNodeStore(item.fakeNodeHandler); err != nil { + t.Errorf("unexpected error: %v", err) + } + if err := nodeController.monitorNodeStatus(); err != nil { + t.Errorf("Case[%d] unexpected error: %v", i, err) + } + } + + podStatusUpdated := false + for _, action := range item.fakeNodeHandler.Actions() { + if action.GetVerb() == "update" && action.GetResource().Resource == "pods" && action.GetSubresource() == "status" { + podStatusUpdated = true + } + } + if podStatusUpdated != item.expectedPodStatusUpdate { + t.Errorf("Case[%d] expect pod status updated to be %v, but got %v", i, item.expectedPodStatusUpdate, podStatusUpdated) + } + } +} + +func TestSwapUnreachableNotReadyTaints(t *testing.T) { + fakeNow := metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC) + evictionTimeout := 10 * time.Minute + + fakeNodeHandler := &testutil.FakeNodeHandler{ + Existing: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + // Because of the logic that prevents NC from evicting anything when all Nodes are NotReady + // we need second healthy node in tests. Because of how the tests are written we need to update + // the status of this Node. + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + LastHeartbeatTime: metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + }, + Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), + } + timeToPass := evictionTimeout + newNodeStatus := v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionFalse, + // Node status has just been updated, and is NotReady for 10min. + LastHeartbeatTime: metav1.Date(2017, 1, 1, 12, 9, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + } + healthyNodeNewStatus := v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + LastHeartbeatTime: metav1.Date(2017, 1, 1, 12, 10, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + } + originalTaint := UnreachableTaintTemplate + updatedTaint := NotReadyTaintTemplate + + nodeController, _ := newNodeLifecycleControllerFromClient( + nil, + fakeNodeHandler, + evictionTimeout, + testRateLimiterQPS, + testRateLimiterQPS, + testLargeClusterThreshold, + testUnhealthyThreshold, + testNodeMonitorGracePeriod, + testNodeStartupGracePeriod, + testNodeMonitorPeriod, + true) + nodeController.now = func() metav1.Time { return fakeNow } + nodeController.recorder = testutil.NewFakeRecorder() + if err := nodeController.syncNodeStore(fakeNodeHandler); err != nil { + t.Errorf("unexpected error: %v", err) + } + if err := nodeController.monitorNodeStatus(); err != nil { + t.Errorf("unexpected error: %v", err) + } + nodeController.doNoExecuteTaintingPass() + + node0, err := fakeNodeHandler.Get("node0", metav1.GetOptions{}) + if err != nil { + t.Errorf("Can't get current node0...") + return + } + node1, err := fakeNodeHandler.Get("node1", metav1.GetOptions{}) + if err != nil { + t.Errorf("Can't get current node1...") + return + } + + if originalTaint != nil && !taintutils.TaintExists(node0.Spec.Taints, originalTaint) { + t.Errorf("Can't find taint %v in %v", originalTaint, node0.Spec.Taints) + } + + nodeController.now = func() metav1.Time { return metav1.Time{Time: fakeNow.Add(timeToPass)} } + + node0.Status = newNodeStatus + node1.Status = healthyNodeNewStatus + _, err = fakeNodeHandler.UpdateStatus(node0) + if err != nil { + t.Errorf(err.Error()) + return + } + _, err = fakeNodeHandler.UpdateStatus(node1) + if err != nil { + t.Errorf(err.Error()) + return + } + + if err := nodeController.syncNodeStore(fakeNodeHandler); err != nil { + t.Errorf("unexpected error: %v", err) + } + if err := nodeController.monitorNodeStatus(); err != nil { + t.Errorf("unexpected error: %v", err) + } + nodeController.doNoExecuteTaintingPass() + + node0, err = fakeNodeHandler.Get("node0", metav1.GetOptions{}) + if err != nil { + t.Errorf("Can't get current node0...") + return + } + if updatedTaint != nil { + if !taintutils.TaintExists(node0.Spec.Taints, updatedTaint) { + t.Errorf("Can't find taint %v in %v", updatedTaint, node0.Spec.Taints) + } + } +} + +func TestTaintsNodeByCondition(t *testing.T) { + fakeNow := metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC) + evictionTimeout := 10 * time.Minute + + fakeNodeHandler := &testutil.FakeNodeHandler{ + Existing: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + }, + Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), + } + + nodeController, _ := newNodeLifecycleControllerFromClient( + nil, + fakeNodeHandler, + evictionTimeout, + testRateLimiterQPS, + testRateLimiterQPS, + testLargeClusterThreshold, + testUnhealthyThreshold, + testNodeMonitorGracePeriod, + testNodeStartupGracePeriod, + testNodeMonitorPeriod, + true) + nodeController.now = func() metav1.Time { return fakeNow } + nodeController.recorder = testutil.NewFakeRecorder() + + outOfDiskTaint := &v1.Taint{ + Key: algorithm.TaintNodeOutOfDisk, + Effect: v1.TaintEffectNoSchedule, + } + networkUnavailableTaint := &v1.Taint{ + Key: algorithm.TaintNodeNetworkUnavailable, + Effect: v1.TaintEffectNoSchedule, + } + + tests := []struct { + Name string + Node *v1.Node + ExpectedTaints []*v1.Taint + }{ + { + Name: "NetworkUnavailable is true", + Node: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + { + Type: v1.NodeNetworkUnavailable, + Status: v1.ConditionTrue, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + ExpectedTaints: []*v1.Taint{networkUnavailableTaint}, + }, + { + Name: "NetworkUnavailable and OutOfDisk are true", + Node: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + { + Type: v1.NodeNetworkUnavailable, + Status: v1.ConditionTrue, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + { + Type: v1.NodeOutOfDisk, + Status: v1.ConditionTrue, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + ExpectedTaints: []*v1.Taint{networkUnavailableTaint, outOfDiskTaint}, + }, + { + Name: "NetworkUnavailable is true, OutOfDisk is unknown", + Node: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionTrue, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + { + Type: v1.NodeNetworkUnavailable, + Status: v1.ConditionTrue, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + { + Type: v1.NodeOutOfDisk, + Status: v1.ConditionUnknown, + LastHeartbeatTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + ExpectedTaints: []*v1.Taint{networkUnavailableTaint}, + }, + } + + for _, test := range tests { + fakeNodeHandler.Update(test.Node) + if err := nodeController.syncNodeStore(fakeNodeHandler); err != nil { + t.Errorf("unexpected error: %v", err) + } + nodeController.doNoScheduleTaintingPass(test.Node) + if err := nodeController.syncNodeStore(fakeNodeHandler); err != nil { + t.Errorf("unexpected error: %v", err) + } + node0, err := nodeController.nodeLister.Get("node0") + if err != nil { + t.Errorf("Can't get current node0...") + return + } + if len(node0.Spec.Taints) != len(test.ExpectedTaints) { + t.Errorf("%s: Unexpected number of taints: expected %d, got %d", + test.Name, len(test.ExpectedTaints), len(node0.Spec.Taints)) + } + for _, taint := range test.ExpectedTaints { + if !taintutils.TaintExists(node0.Spec.Taints, taint) { + t.Errorf("%s: Can't find taint %v in %v", test.Name, taint, node0.Spec.Taints) + } + } + } +} + +func TestNodeEventGeneration(t *testing.T) { + fakeNow := metav1.Date(2016, 9, 10, 12, 0, 0, 0, time.UTC) + fakeNodeHandler := &testutil.FakeNodeHandler{ + Existing: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + UID: "1234567890", + CreationTimestamp: metav1.Date(2015, 8, 10, 0, 0, 0, 0, time.UTC), + }, + Spec: v1.NodeSpec{ + ExternalID: "node0", + }, + Status: v1.NodeStatus{ + Conditions: []v1.NodeCondition{ + { + Type: v1.NodeReady, + Status: v1.ConditionUnknown, + LastHeartbeatTime: metav1.Date(2015, 8, 10, 0, 0, 0, 0, time.UTC), + LastTransitionTime: metav1.Date(2015, 8, 10, 0, 0, 0, 0, time.UTC), + }, + }, + }, + }, + }, + Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), + } + + nodeController, _ := newNodeLifecycleControllerFromClient( + nil, + fakeNodeHandler, + 5*time.Minute, + testRateLimiterQPS, + testRateLimiterQPS, + testLargeClusterThreshold, + testUnhealthyThreshold, + testNodeMonitorGracePeriod, + testNodeStartupGracePeriod, + testNodeMonitorPeriod, + false) + nodeController.cloud = &fakecloud.FakeCloud{} + nodeController.nodeExistsInCloudProvider = func(nodeName types.NodeName) (bool, error) { + return false, nil + } + nodeController.now = func() metav1.Time { return fakeNow } + fakeRecorder := testutil.NewFakeRecorder() + nodeController.recorder = fakeRecorder + if err := nodeController.syncNodeStore(fakeNodeHandler); err != nil { + t.Errorf("unexpected error: %v", err) + } + if err := nodeController.monitorNodeStatus(); err != nil { + t.Errorf("unexpected error: %v", err) + } + if len(fakeRecorder.Events) != 2 { + t.Fatalf("unexpected events, got %v, expected %v: %+v", len(fakeRecorder.Events), 2, fakeRecorder.Events) + } + if fakeRecorder.Events[0].Reason != "RegisteredNode" || fakeRecorder.Events[1].Reason != "DeletingNode" { + var reasons []string + for _, event := range fakeRecorder.Events { + reasons = append(reasons, event.Reason) + } + t.Fatalf("unexpected events generation: %v", strings.Join(reasons, ",")) + } + for _, event := range fakeRecorder.Events { + involvedObject := event.InvolvedObject + actualUID := string(involvedObject.UID) + if actualUID != "1234567890" { + t.Fatalf("unexpected event uid: %v", actualUID) + } + } +} + +// TestFixDeprecatedTaintKey verifies we have backwards compatibility after upgraded alpha taint key to GA taint key. +// TODO(resouer): this is introduced in 1.9 and should be removed in the future. +func TestFixDeprecatedTaintKey(t *testing.T) { + fakeNow := metav1.Date(2017, 1, 1, 12, 0, 0, 0, time.UTC) + evictionTimeout := 10 * time.Minute + + fakeNodeHandler := &testutil.FakeNodeHandler{ + Existing: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + }, + }, + Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), + } + + nodeController, _ := newNodeLifecycleControllerFromClient( + nil, + fakeNodeHandler, + evictionTimeout, + testRateLimiterQPS, + testRateLimiterQPS, + testLargeClusterThreshold, + testUnhealthyThreshold, + testNodeMonitorGracePeriod, + testNodeStartupGracePeriod, + testNodeMonitorPeriod, + true) + nodeController.now = func() metav1.Time { return fakeNow } + nodeController.recorder = testutil.NewFakeRecorder() + + deprecatedNotReadyTaint := &v1.Taint{ + Key: algorithm.DeprecatedTaintNodeNotReady, + Effect: v1.TaintEffectNoExecute, + } + + nodeNotReadyTaint := &v1.Taint{ + Key: algorithm.TaintNodeNotReady, + Effect: v1.TaintEffectNoExecute, + } + + deprecatedUnreachableTaint := &v1.Taint{ + Key: algorithm.DeprecatedTaintNodeUnreachable, + Effect: v1.TaintEffectNoExecute, + } + + nodeUnreachableTaint := &v1.Taint{ + Key: algorithm.TaintNodeUnreachable, + Effect: v1.TaintEffectNoExecute, + } + + tests := []struct { + Name string + Node *v1.Node + ExpectedTaints []*v1.Taint + }{ + { + Name: "Node with deprecated not-ready taint key", + Node: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Spec: v1.NodeSpec{ + Taints: []v1.Taint{ + *deprecatedNotReadyTaint, + }, + }, + }, + ExpectedTaints: []*v1.Taint{nodeNotReadyTaint}, + }, + { + Name: "Node with deprecated unreachable taint key", + Node: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Spec: v1.NodeSpec{ + Taints: []v1.Taint{ + *deprecatedUnreachableTaint, + }, + }, + }, + ExpectedTaints: []*v1.Taint{nodeUnreachableTaint}, + }, + { + Name: "Node with not-ready taint key", + Node: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Spec: v1.NodeSpec{ + Taints: []v1.Taint{ + *nodeNotReadyTaint, + }, + }, + }, + ExpectedTaints: []*v1.Taint{nodeNotReadyTaint}, + }, + { + Name: "Node with unreachable taint key", + Node: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node0", + CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + Labels: map[string]string{ + kubeletapis.LabelZoneRegion: "region1", + kubeletapis.LabelZoneFailureDomain: "zone1", + }, + }, + Spec: v1.NodeSpec{ + Taints: []v1.Taint{ + *nodeUnreachableTaint, + }, + }, + }, + ExpectedTaints: []*v1.Taint{nodeUnreachableTaint}, + }, + } + + for _, test := range tests { + fakeNodeHandler.Update(test.Node) + if err := nodeController.syncNodeStore(fakeNodeHandler); err != nil { + t.Errorf("unexpected error: %v", err) + } + nodeController.doFixDeprecatedTaintKeyPass(test.Node) + if err := nodeController.syncNodeStore(fakeNodeHandler); err != nil { + t.Errorf("unexpected error: %v", err) + } + node, err := nodeController.nodeLister.Get(test.Node.GetName()) + if err != nil { + t.Errorf("Can't get current node...") + return + } + if len(node.Spec.Taints) != len(test.ExpectedTaints) { + t.Errorf("%s: Unexpected number of taints: expected %d, got %d", + test.Name, len(test.ExpectedTaints), len(node.Spec.Taints)) + } + for _, taint := range test.ExpectedTaints { + if !taintutils.TaintExists(node.Spec.Taints, taint) { + t.Errorf("%s: Can't find taint %v in %v", test.Name, taint, node.Spec.Taints) + } + } + } +} diff --git a/pkg/controller/nodelifecycle/scheduler/BUILD b/pkg/controller/nodelifecycle/scheduler/BUILD new file mode 100644 index 00000000000..c9d54bd628b --- /dev/null +++ b/pkg/controller/nodelifecycle/scheduler/BUILD @@ -0,0 +1,63 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "rate_limited_queue.go", + "taint_manager.go", + "timed_workers.go", + ], + importpath = "k8s.io/kubernetes/pkg/controller/nodelifecycle/scheduler", + visibility = ["//visibility:public"], + deps = [ + "//pkg/apis/core/helper:go_default_library", + "//pkg/apis/core/v1/helper:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", + "//vendor/k8s.io/client-go/tools/record:go_default_library", + "//vendor/k8s.io/client-go/util/flowcontrol:go_default_library", + "//vendor/k8s.io/client-go/util/workqueue:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "rate_limited_queue_test.go", + "taint_manager_test.go", + "timed_workers_test.go", + ], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/controller/nodelifecycle/scheduler", + deps = [ + "//pkg/controller/testutil:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", + "//vendor/k8s.io/client-go/testing:go_default_library", + "//vendor/k8s.io/client-go/util/flowcontrol:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/controller/node/scheduler/rate_limited_queue.go b/pkg/controller/nodelifecycle/scheduler/rate_limited_queue.go similarity index 100% rename from pkg/controller/node/scheduler/rate_limited_queue.go rename to pkg/controller/nodelifecycle/scheduler/rate_limited_queue.go diff --git a/pkg/controller/node/scheduler/rate_limited_queue_test.go b/pkg/controller/nodelifecycle/scheduler/rate_limited_queue_test.go similarity index 100% rename from pkg/controller/node/scheduler/rate_limited_queue_test.go rename to pkg/controller/nodelifecycle/scheduler/rate_limited_queue_test.go diff --git a/pkg/controller/nodelifecycle/scheduler/taint_manager.go b/pkg/controller/nodelifecycle/scheduler/taint_manager.go new file mode 100644 index 00000000000..a71fa8fc788 --- /dev/null +++ b/pkg/controller/nodelifecycle/scheduler/taint_manager.go @@ -0,0 +1,435 @@ +/* +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 scheduler + +import ( + "fmt" + "k8s.io/api/core/v1" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/kubernetes/pkg/apis/core/helper" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" + "sync" + "time" + + "k8s.io/client-go/kubernetes/scheme" + v1core "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/tools/record" + "k8s.io/client-go/util/workqueue" + + "github.com/golang/glog" +) + +const ( + nodeUpdateChannelSize = 10 + podUpdateChannelSize = 1 + retries = 5 +) + +// Needed to make workqueue work +type updateItemInterface interface{} + +type nodeUpdateItem struct { + oldNode *v1.Node + newNode *v1.Node + newTaints []v1.Taint +} + +type podUpdateItem struct { + oldPod *v1.Pod + newPod *v1.Pod + newTolerations []v1.Toleration +} + +// NoExecuteTaintManager listens to Taint/Toleration changes and is responsible for removing Pods +// from Nodes tainted with NoExecute Taints. +type NoExecuteTaintManager struct { + client clientset.Interface + recorder record.EventRecorder + + taintEvictionQueue *TimedWorkerQueue + // keeps a map from nodeName to all noExecute taints on that Node + taintedNodesLock sync.Mutex + taintedNodes map[string][]v1.Taint + + nodeUpdateChannel chan *nodeUpdateItem + podUpdateChannel chan *podUpdateItem + + nodeUpdateQueue workqueue.Interface + podUpdateQueue workqueue.Interface +} + +func deletePodHandler(c clientset.Interface, emitEventFunc func(types.NamespacedName)) func(args *WorkArgs) error { + return func(args *WorkArgs) error { + ns := args.NamespacedName.Namespace + name := args.NamespacedName.Name + glog.V(0).Infof("NoExecuteTaintManager is deleting Pod: %v", args.NamespacedName.String()) + if emitEventFunc != nil { + emitEventFunc(args.NamespacedName) + } + var err error + for i := 0; i < retries; i++ { + err = c.CoreV1().Pods(ns).Delete(name, &metav1.DeleteOptions{}) + if err == nil { + break + } + time.Sleep(10 * time.Millisecond) + } + return err + } +} + +func getNoExecuteTaints(taints []v1.Taint) []v1.Taint { + result := []v1.Taint{} + for i := range taints { + if taints[i].Effect == v1.TaintEffectNoExecute { + result = append(result, taints[i]) + } + } + return result +} + +func getPodsAssignedToNode(c clientset.Interface, nodeName string) ([]v1.Pod, error) { + selector := fields.SelectorFromSet(fields.Set{"spec.nodeName": nodeName}) + pods, err := c.CoreV1().Pods(v1.NamespaceAll).List(metav1.ListOptions{ + FieldSelector: selector.String(), + LabelSelector: labels.Everything().String(), + }) + for i := 0; i < retries && err != nil; i++ { + pods, err = c.CoreV1().Pods(v1.NamespaceAll).List(metav1.ListOptions{ + FieldSelector: selector.String(), + LabelSelector: labels.Everything().String(), + }) + time.Sleep(100 * time.Millisecond) + } + if err != nil { + return []v1.Pod{}, fmt.Errorf("failed to get Pods assigned to node %v", nodeName) + } + return pods.Items, nil +} + +// getMinTolerationTime returns minimal toleration time from the given slice, or -1 if it's infinite. +func getMinTolerationTime(tolerations []v1.Toleration) time.Duration { + minTolerationTime := int64(-1) + if len(tolerations) == 0 { + return 0 + } + + for i := range tolerations { + if tolerations[i].TolerationSeconds != nil { + tolerationSeconds := *(tolerations[i].TolerationSeconds) + if tolerationSeconds <= 0 { + return 0 + } else if tolerationSeconds < minTolerationTime || minTolerationTime == -1 { + minTolerationTime = tolerationSeconds + } + } + } + + return time.Duration(minTolerationTime) * time.Second +} + +// NewNoExecuteTaintManager creates a new NoExecuteTaintManager that will use passed clientset to +// communicate with the API server. +func NewNoExecuteTaintManager(c clientset.Interface) *NoExecuteTaintManager { + eventBroadcaster := record.NewBroadcaster() + recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "taint-controller"}) + eventBroadcaster.StartLogging(glog.Infof) + if c != nil { + glog.V(0).Infof("Sending events to api server.") + eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(c.CoreV1().RESTClient()).Events("")}) + } else { + glog.Fatalf("kubeClient is nil when starting NodeController") + } + + tm := &NoExecuteTaintManager{ + client: c, + recorder: recorder, + taintedNodes: make(map[string][]v1.Taint), + nodeUpdateChannel: make(chan *nodeUpdateItem, nodeUpdateChannelSize), + podUpdateChannel: make(chan *podUpdateItem, podUpdateChannelSize), + + nodeUpdateQueue: workqueue.New(), + podUpdateQueue: workqueue.New(), + } + tm.taintEvictionQueue = CreateWorkerQueue(deletePodHandler(c, tm.emitPodDeletionEvent)) + + return tm +} + +// Run starts NoExecuteTaintManager which will run in loop until `stopCh` is closed. +func (tc *NoExecuteTaintManager) Run(stopCh <-chan struct{}) { + glog.V(0).Infof("Starting NoExecuteTaintManager") + // Functions that are responsible for taking work items out of the workqueues and putting them + // into channels. + go func(stopCh <-chan struct{}) { + for { + item, shutdown := tc.nodeUpdateQueue.Get() + if shutdown { + break + } + nodeUpdate := item.(*nodeUpdateItem) + select { + case <-stopCh: + break + case tc.nodeUpdateChannel <- nodeUpdate: + } + } + }(stopCh) + + go func(stopCh <-chan struct{}) { + for { + item, shutdown := tc.podUpdateQueue.Get() + if shutdown { + break + } + podUpdate := item.(*podUpdateItem) + select { + case <-stopCh: + break + case tc.podUpdateChannel <- podUpdate: + } + } + }(stopCh) + + // When processing events we want to prioritize Node updates over Pod updates, + // as NodeUpdates that interest NoExecuteTaintManager should be handled as soon as possible - + // we don't want user (or system) to wait until PodUpdate queue is drained before it can + // start evicting Pods from tainted Nodes. + for { + select { + case <-stopCh: + break + case nodeUpdate := <-tc.nodeUpdateChannel: + tc.handleNodeUpdate(nodeUpdate) + case podUpdate := <-tc.podUpdateChannel: + // If we found a Pod update we need to empty Node queue first. + priority: + for { + select { + case nodeUpdate := <-tc.nodeUpdateChannel: + tc.handleNodeUpdate(nodeUpdate) + default: + break priority + } + } + // After Node queue is emptied we process podUpdate. + tc.handlePodUpdate(podUpdate) + } + } +} + +// PodUpdated is used to notify NoExecuteTaintManager about Pod changes. +func (tc *NoExecuteTaintManager) PodUpdated(oldPod *v1.Pod, newPod *v1.Pod) { + oldTolerations := []v1.Toleration{} + if oldPod != nil { + oldTolerations = oldPod.Spec.Tolerations + } + newTolerations := []v1.Toleration{} + if newPod != nil { + newTolerations = newPod.Spec.Tolerations + } + + if oldPod != nil && newPod != nil && helper.Semantic.DeepEqual(oldTolerations, newTolerations) && oldPod.Spec.NodeName == newPod.Spec.NodeName { + return + } + updateItem := &podUpdateItem{ + oldPod: oldPod, + newPod: newPod, + newTolerations: newTolerations, + } + + tc.podUpdateQueue.Add(updateItemInterface(updateItem)) +} + +// NodeUpdated is used to notify NoExecuteTaintManager about Node changes. +func (tc *NoExecuteTaintManager) NodeUpdated(oldNode *v1.Node, newNode *v1.Node) { + oldTaints := []v1.Taint{} + if oldNode != nil { + oldTaints = oldNode.Spec.Taints + } + oldTaints = getNoExecuteTaints(oldTaints) + + newTaints := []v1.Taint{} + if newNode != nil { + newTaints = newNode.Spec.Taints + } + newTaints = getNoExecuteTaints(newTaints) + + if oldNode != nil && newNode != nil && helper.Semantic.DeepEqual(oldTaints, newTaints) { + return + } + updateItem := &nodeUpdateItem{ + oldNode: oldNode, + newNode: newNode, + newTaints: newTaints, + } + + tc.nodeUpdateQueue.Add(updateItemInterface(updateItem)) +} + +func (tc *NoExecuteTaintManager) cancelWorkWithEvent(nsName types.NamespacedName) { + if tc.taintEvictionQueue.CancelWork(nsName.String()) { + tc.emitCancelPodDeletionEvent(nsName) + } +} + +func (tc *NoExecuteTaintManager) processPodOnNode( + podNamespacedName types.NamespacedName, + nodeName string, + tolerations []v1.Toleration, + taints []v1.Taint, + now time.Time, +) { + if len(taints) == 0 { + tc.cancelWorkWithEvent(podNamespacedName) + } + allTolerated, usedTolerations := v1helper.GetMatchingTolerations(taints, tolerations) + if !allTolerated { + glog.V(2).Infof("Not all taints are tolerated after update for Pod %v on %v", podNamespacedName.String(), nodeName) + // We're canceling scheduled work (if any), as we're going to delete the Pod right away. + tc.cancelWorkWithEvent(podNamespacedName) + tc.taintEvictionQueue.AddWork(NewWorkArgs(podNamespacedName.Name, podNamespacedName.Namespace), time.Now(), time.Now()) + return + } + minTolerationTime := getMinTolerationTime(usedTolerations) + // getMinTolerationTime returns negative value to denote infinite toleration. + if minTolerationTime < 0 { + glog.V(4).Infof("New tolerations for %v tolerate forever. Scheduled deletion won't be cancelled if already scheduled.", podNamespacedName.String()) + return + } + + startTime := now + triggerTime := startTime.Add(minTolerationTime) + scheduledEviction := tc.taintEvictionQueue.GetWorkerUnsafe(podNamespacedName.String()) + if scheduledEviction != nil { + startTime = scheduledEviction.CreatedAt + if startTime.Add(minTolerationTime).Before(triggerTime) { + return + } + tc.cancelWorkWithEvent(podNamespacedName) + } + tc.taintEvictionQueue.AddWork(NewWorkArgs(podNamespacedName.Name, podNamespacedName.Namespace), startTime, triggerTime) +} + +func (tc *NoExecuteTaintManager) handlePodUpdate(podUpdate *podUpdateItem) { + // Delete + if podUpdate.newPod == nil { + pod := podUpdate.oldPod + podNamespacedName := types.NamespacedName{Namespace: pod.Namespace, Name: pod.Name} + glog.V(4).Infof("Noticed pod deletion: %#v", podNamespacedName) + tc.cancelWorkWithEvent(podNamespacedName) + return + } + // Create or Update + pod := podUpdate.newPod + podNamespacedName := types.NamespacedName{Namespace: pod.Namespace, Name: pod.Name} + glog.V(4).Infof("Noticed pod update: %#v", podNamespacedName) + nodeName := pod.Spec.NodeName + if nodeName == "" { + return + } + taints, ok := func() ([]v1.Taint, bool) { + tc.taintedNodesLock.Lock() + defer tc.taintedNodesLock.Unlock() + taints, ok := tc.taintedNodes[nodeName] + return taints, ok + }() + // It's possible that Node was deleted, or Taints were removed before, which triggered + // eviction cancelling if it was needed. + if !ok { + return + } + tc.processPodOnNode(podNamespacedName, nodeName, podUpdate.newTolerations, taints, time.Now()) +} + +func (tc *NoExecuteTaintManager) handleNodeUpdate(nodeUpdate *nodeUpdateItem) { + // Delete + if nodeUpdate.newNode == nil { + node := nodeUpdate.oldNode + glog.V(4).Infof("Noticed node deletion: %#v", node.Name) + tc.taintedNodesLock.Lock() + defer tc.taintedNodesLock.Unlock() + delete(tc.taintedNodes, node.Name) + return + } + // Create or Update + glog.V(4).Infof("Noticed node update: %#v", nodeUpdate) + node := nodeUpdate.newNode + taints := nodeUpdate.newTaints + func() { + tc.taintedNodesLock.Lock() + defer tc.taintedNodesLock.Unlock() + glog.V(4).Infof("Updating known taints on node %v: %v", node.Name, taints) + if len(taints) == 0 { + delete(tc.taintedNodes, node.Name) + } else { + tc.taintedNodes[node.Name] = taints + } + }() + pods, err := getPodsAssignedToNode(tc.client, node.Name) + if err != nil { + glog.Errorf(err.Error()) + return + } + if len(pods) == 0 { + return + } + // Short circuit, to make this controller a bit faster. + if len(taints) == 0 { + glog.V(4).Infof("All taints were removed from the Node %v. Cancelling all evictions...", node.Name) + for i := range pods { + tc.cancelWorkWithEvent(types.NamespacedName{Namespace: pods[i].Namespace, Name: pods[i].Name}) + } + return + } + + now := time.Now() + for i := range pods { + pod := &pods[i] + podNamespacedName := types.NamespacedName{Namespace: pod.Namespace, Name: pod.Name} + tc.processPodOnNode(podNamespacedName, node.Name, pod.Spec.Tolerations, taints, now) + } +} + +func (tc *NoExecuteTaintManager) emitPodDeletionEvent(nsName types.NamespacedName) { + if tc.recorder == nil { + return + } + ref := &v1.ObjectReference{ + Kind: "Pod", + Name: nsName.Name, + Namespace: nsName.Namespace, + } + tc.recorder.Eventf(ref, v1.EventTypeNormal, "TaintManagerEviction", "Marking for deletion Pod %s", nsName.String()) +} + +func (tc *NoExecuteTaintManager) emitCancelPodDeletionEvent(nsName types.NamespacedName) { + if tc.recorder == nil { + return + } + ref := &v1.ObjectReference{ + Kind: "Pod", + Name: nsName.Name, + Namespace: nsName.Namespace, + } + tc.recorder.Eventf(ref, v1.EventTypeNormal, "TaintManagerEviction", "Cancelling deletion of Pod %s", nsName.String()) +} diff --git a/pkg/controller/node/scheduler/taint_controller_test.go b/pkg/controller/nodelifecycle/scheduler/taint_manager_test.go similarity index 100% rename from pkg/controller/node/scheduler/taint_controller_test.go rename to pkg/controller/nodelifecycle/scheduler/taint_manager_test.go diff --git a/pkg/controller/node/scheduler/timed_workers.go b/pkg/controller/nodelifecycle/scheduler/timed_workers.go similarity index 100% rename from pkg/controller/node/scheduler/timed_workers.go rename to pkg/controller/nodelifecycle/scheduler/timed_workers.go diff --git a/pkg/controller/node/scheduler/timed_workers_test.go b/pkg/controller/nodelifecycle/scheduler/timed_workers_test.go similarity index 100% rename from pkg/controller/node/scheduler/timed_workers_test.go rename to pkg/controller/nodelifecycle/scheduler/timed_workers_test.go diff --git a/pkg/controller/podautoscaler/BUILD b/pkg/controller/podautoscaler/BUILD index 549c2d66e7d..6f923b2c845 100644 --- a/pkg/controller/podautoscaler/BUILD +++ b/pkg/controller/podautoscaler/BUILD @@ -16,7 +16,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/controller/podautoscaler", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/v1/pod:go_default_library", "//pkg/controller:go_default_library", "//pkg/controller/podautoscaler/metrics:go_default_library", @@ -24,9 +24,9 @@ go_library( "//vendor/k8s.io/api/autoscaling/v1:go_default_library", "//vendor/k8s.io/api/autoscaling/v2beta1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", @@ -39,8 +39,8 @@ go_library( "//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library", "//vendor/k8s.io/client-go/kubernetes/typed/autoscaling/v1:go_default_library", "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", - "//vendor/k8s.io/client-go/kubernetes/typed/extensions/v1beta1:go_default_library", "//vendor/k8s.io/client-go/listers/autoscaling/v1:go_default_library", + "//vendor/k8s.io/client-go/scale:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", "//vendor/k8s.io/client-go/tools/record:go_default_library", "//vendor/k8s.io/client-go/util/workqueue:go_default_library", @@ -55,12 +55,13 @@ go_test( "legacy_replica_calculator_test.go", "replica_calculator_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/podautoscaler", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/autoscaling:go_default_library", "//pkg/apis/autoscaling/install:go_default_library", + "//pkg/apis/core/install:go_default_library", "//pkg/apis/extensions/install:go_default_library", "//pkg/controller:go_default_library", "//pkg/controller/podautoscaler/metrics:go_default_library", @@ -69,15 +70,16 @@ go_test( "//vendor/k8s.io/api/autoscaling/v1:go_default_library", "//vendor/k8s.io/api/autoscaling/v2beta1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", "//vendor/k8s.io/client-go/informers:go_default_library", "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", + "//vendor/k8s.io/client-go/scale/fake:go_default_library", "//vendor/k8s.io/client-go/testing:go_default_library", "//vendor/k8s.io/heapster/metrics/api/v1/types:go_default_library", "//vendor/k8s.io/metrics/pkg/apis/custom_metrics/v1beta1:go_default_library", diff --git a/pkg/controller/podautoscaler/horizontal.go b/pkg/controller/podautoscaler/horizontal.go index 52541929fca..65a7a6f1702 100644 --- a/pkg/controller/podautoscaler/horizontal.go +++ b/pkg/controller/podautoscaler/horizontal.go @@ -25,9 +25,9 @@ import ( autoscalingv1 "k8s.io/api/autoscaling/v1" autoscalingv2 "k8s.io/api/autoscaling/v2beta1" "k8s.io/api/core/v1" - extensions "k8s.io/api/extensions/v1beta1" apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/errors" + apimeta "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -39,12 +39,12 @@ import ( "k8s.io/client-go/kubernetes/scheme" autoscalingclient "k8s.io/client-go/kubernetes/typed/autoscaling/v1" v1core "k8s.io/client-go/kubernetes/typed/core/v1" - extensionsclient "k8s.io/client-go/kubernetes/typed/extensions/v1beta1" autoscalinglisters "k8s.io/client-go/listers/autoscaling/v1" + scaleclient "k8s.io/client-go/scale" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/controller" ) @@ -57,8 +57,9 @@ var ( // in the system with the actual deployments/replication controllers they // control. type HorizontalController struct { - scaleNamespacer extensionsclient.ScalesGetter + scaleNamespacer scaleclient.ScalesGetter hpaNamespacer autoscalingclient.HorizontalPodAutoscalersGetter + mapper apimeta.RESTMapper replicaCalc *ReplicaCalculator eventRecorder record.EventRecorder @@ -78,8 +79,9 @@ type HorizontalController struct { // NewHorizontalController creates a new HorizontalController. func NewHorizontalController( evtNamespacer v1core.EventsGetter, - scaleNamespacer extensionsclient.ScalesGetter, + scaleNamespacer scaleclient.ScalesGetter, hpaNamespacer autoscalingclient.HorizontalPodAutoscalersGetter, + mapper apimeta.RESTMapper, replicaCalc *ReplicaCalculator, hpaInformer autoscalinginformers.HorizontalPodAutoscalerInformer, resyncPeriod time.Duration, @@ -88,6 +90,7 @@ func NewHorizontalController( ) *HorizontalController { broadcaster := record.NewBroadcaster() + broadcaster.StartLogging(glog.Infof) // TODO: remove the wrapper when every clients have moved to use the clientset. broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: evtNamespacer.Events("")}) recorder := broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "horizontal-pod-autoscaler"}) @@ -99,7 +102,8 @@ func NewHorizontalController( hpaNamespacer: hpaNamespacer, upscaleForbiddenWindow: upscaleForbiddenWindow, downscaleForbiddenWindow: downscaleForbiddenWindow, - queue: workqueue.NewNamedRateLimitingQueue(NewDefaultHPARateLimiter(resyncPeriod), "horizontalpodautoscaler"), + queue: workqueue.NewNamedRateLimitingQueue(NewDefaultHPARateLimiter(resyncPeriod), "horizontalpodautoscaler"), + mapper: mapper, } hpaInformer.Informer().AddEventHandlerWithResyncPeriod( @@ -189,7 +193,7 @@ func (a *HorizontalController) processNextWorkItem() bool { // Computes the desired number of replicas for the metric specifications listed in the HPA, returning the maximum // of the computed replica counts, a description of the associated metric, and the statuses of all metrics // computed. -func (a *HorizontalController) computeReplicasForMetrics(hpa *autoscalingv2.HorizontalPodAutoscaler, scale *extensions.Scale, +func (a *HorizontalController) computeReplicasForMetrics(hpa *autoscalingv2.HorizontalPodAutoscaler, scale *autoscalingv1.Scale, metricSpecs []autoscalingv2.MetricSpec) (replicas int32, metric string, statuses []autoscalingv2.MetricStatus, timestamp time.Time, err error) { currentReplicas := scale.Status.Replicas @@ -197,21 +201,14 @@ func (a *HorizontalController) computeReplicasForMetrics(hpa *autoscalingv2.Hori statuses = make([]autoscalingv2.MetricStatus, len(metricSpecs)) for i, metricSpec := range metricSpecs { - if len(scale.Status.Selector) == 0 && len(scale.Status.TargetSelector) == 0 { + if scale.Status.Selector == "" { errMsg := "selector is required" a.eventRecorder.Event(hpa, v1.EventTypeWarning, "SelectorRequired", errMsg) setCondition(hpa, autoscalingv2.ScalingActive, v1.ConditionFalse, "InvalidSelector", "the HPA target's scale is missing a selector") return 0, "", nil, time.Time{}, fmt.Errorf(errMsg) } - var selector labels.Selector - var err error - if len(scale.Status.Selector) > 0 { - selector = labels.SelectorFromSet(labels.Set(scale.Status.Selector)) - err = nil - } else { - selector, err = labels.Parse(scale.Status.TargetSelector) - } + selector, err := labels.Parse(scale.Status.Selector) if err != nil { errMsg := fmt.Sprintf("couldn't convert selector into a corresponding internal selector object: %v", err) a.eventRecorder.Event(hpa, v1.EventTypeWarning, "InvalidSelector", errMsg) @@ -349,7 +346,28 @@ func (a *HorizontalController) reconcileAutoscaler(hpav1Shared *autoscalingv1.Ho reference := fmt.Sprintf("%s/%s/%s", hpa.Spec.ScaleTargetRef.Kind, hpa.Namespace, hpa.Spec.ScaleTargetRef.Name) - scale, err := a.scaleNamespacer.Scales(hpa.Namespace).Get(hpa.Spec.ScaleTargetRef.Kind, hpa.Spec.ScaleTargetRef.Name) + targetGV, err := schema.ParseGroupVersion(hpa.Spec.ScaleTargetRef.APIVersion) + if err != nil { + a.eventRecorder.Event(hpa, v1.EventTypeWarning, "FailedGetScale", err.Error()) + setCondition(hpa, autoscalingv2.AbleToScale, v1.ConditionFalse, "FailedGetScale", "the HPA controller was unable to get the target's current scale: %v", err) + a.updateStatusIfNeeded(hpaStatusOriginal, hpa) + return fmt.Errorf("invalid API version in scale target reference: %v", err) + } + + targetGK := schema.GroupKind{ + Group: targetGV.Group, + Kind: hpa.Spec.ScaleTargetRef.Kind, + } + + mappings, err := a.mapper.RESTMappings(targetGK) + if err != nil { + a.eventRecorder.Event(hpa, v1.EventTypeWarning, "FailedGetScale", err.Error()) + setCondition(hpa, autoscalingv2.AbleToScale, v1.ConditionFalse, "FailedGetScale", "the HPA controller was unable to get the target's current scale: %v", err) + a.updateStatusIfNeeded(hpaStatusOriginal, hpa) + return fmt.Errorf("unable to determine resource for scale target reference: %v", err) + } + + scale, targetGR, err := a.scaleForResourceMappings(hpa.Namespace, hpa.Spec.ScaleTargetRef.Name, mappings) if err != nil { a.eventRecorder.Event(hpa, v1.EventTypeWarning, "FailedGetScale", err.Error()) setCondition(hpa, autoscalingv2.AbleToScale, v1.ConditionFalse, "FailedGetScale", "the HPA controller was unable to get the target's current scale: %v", err) @@ -410,46 +428,7 @@ func (a *HorizontalController) reconcileAutoscaler(hpav1Shared *autoscalingv1.Ho rescaleReason = "All metrics below target" } - // Do not upscale too much to prevent incorrect rapid increase of the number of master replicas caused by - // bogus CPU usage report from heapster/kubelet (like in issue #32304). - scaleUpLimit := calculateScaleUpLimit(currentReplicas) - - switch { - case desiredReplicas > scaleUpLimit: - setCondition(hpa, autoscalingv2.ScalingLimited, v1.ConditionTrue, "ScaleUpLimit", "the desired replica count is increasing faster than the maximum scale rate") - desiredReplicas = scaleUpLimit - - // Ensure that even if the scaleUpLimit is greater - // than the maximum number of replicas, we only - // set the max number of replicas as desired. - if scaleUpLimit > hpa.Spec.MaxReplicas { - setCondition(hpa, autoscalingv2.ScalingLimited, v1.ConditionTrue, "TooManyReplicas", "the desired replica count was more than the maximum replica count") - desiredReplicas = hpa.Spec.MaxReplicas - } - - case hpa.Spec.MinReplicas != nil && desiredReplicas < *hpa.Spec.MinReplicas: - // make sure we aren't below our minimum - var statusMsg string - if desiredReplicas == 0 { - statusMsg = "the desired replica count was zero" - } else { - statusMsg = "the desired replica count was less than the minimum replica count" - } - - setCondition(hpa, autoscalingv2.ScalingLimited, v1.ConditionTrue, "TooFewReplicas", statusMsg) - desiredReplicas = *hpa.Spec.MinReplicas - case desiredReplicas == 0: - // never scale down to 0, reserved for disabling autoscaling - setCondition(hpa, autoscalingv2.ScalingLimited, v1.ConditionTrue, "TooFewReplicas", "the desired replica count was zero") - desiredReplicas = 1 - case desiredReplicas > hpa.Spec.MaxReplicas: - // make sure we aren't above our maximum - setCondition(hpa, autoscalingv2.ScalingLimited, v1.ConditionTrue, "TooManyReplicas", "the desired replica count was more than the maximum replica count") - desiredReplicas = hpa.Spec.MaxReplicas - default: - // mark that we're within acceptible limits - setCondition(hpa, autoscalingv2.ScalingLimited, v1.ConditionFalse, "DesiredWithinRange", "the desired replica count is within the acceptible range") - } + desiredReplicas = a.normalizeDesiredReplicas(hpa, currentReplicas, desiredReplicas) rescale = a.shouldScale(hpa, currentReplicas, desiredReplicas, timestamp) backoffDown := false @@ -479,7 +458,7 @@ func (a *HorizontalController) reconcileAutoscaler(hpav1Shared *autoscalingv1.Ho if rescale { scale.Spec.Replicas = desiredReplicas - _, err = a.scaleNamespacer.Scales(hpa.Namespace).Update(hpa.Spec.ScaleTargetRef.Kind, scale) + _, err = a.scaleNamespacer.Scales(hpa.Namespace).Update(targetGR, scale) if err != nil { a.eventRecorder.Eventf(hpa, v1.EventTypeWarning, "FailedRescale", "New size: %d; reason: %s; error: %v", desiredReplicas, rescaleReason, err.Error()) setCondition(hpa, autoscalingv2.AbleToScale, v1.ConditionFalse, "FailedUpdateScale", "the HPA controller was unable to update the target scale: %v", err) @@ -502,6 +481,75 @@ func (a *HorizontalController) reconcileAutoscaler(hpav1Shared *autoscalingv1.Ho return a.updateStatusIfNeeded(hpaStatusOriginal, hpa) } +// normalizeDesiredReplicas takes the metrics desired replicas value and normalizes it based on the appropriate conditions (i.e. < maxReplicas, > +// minReplicas, etc...) +func (a *HorizontalController) normalizeDesiredReplicas(hpa *autoscalingv2.HorizontalPodAutoscaler, currentReplicas int32, prenormalizedDesiredReplicas int32) int32 { + var minReplicas int32 + if hpa.Spec.MinReplicas != nil { + minReplicas = *hpa.Spec.MinReplicas + } else { + minReplicas = 0 + } + + desiredReplicas, condition, reason := convertDesiredReplicasWithRules(currentReplicas, prenormalizedDesiredReplicas, minReplicas, hpa.Spec.MaxReplicas) + + if desiredReplicas == prenormalizedDesiredReplicas { + setCondition(hpa, autoscalingv2.ScalingLimited, v1.ConditionFalse, condition, reason) + } else { + setCondition(hpa, autoscalingv2.ScalingLimited, v1.ConditionTrue, condition, reason) + } + + return desiredReplicas +} + +// convertDesiredReplicas performs the actual normalization, without depending on `HorizontalController` or `HorizontalPodAutoscaler` +func convertDesiredReplicasWithRules(currentReplicas, desiredReplicas, hpaMinReplicas, hpaMaxReplicas int32) (int32, string, string) { + + var minimumAllowedReplicas int32 + var maximumAllowedReplicas int32 + + var possibleLimitingCondition string + var possibleLimitingReason string + + if hpaMinReplicas == 0 { + minimumAllowedReplicas = 1 + possibleLimitingReason = "the desired replica count is zero" + } else { + minimumAllowedReplicas = hpaMinReplicas + possibleLimitingReason = "the desired replica count is less than the minimum replica count" + } + + // Do not upscale too much to prevent incorrect rapid increase of the number of master replicas caused by + // bogus CPU usage report from heapster/kubelet (like in issue #32304). + scaleUpLimit := calculateScaleUpLimit(currentReplicas) + + if hpaMaxReplicas > scaleUpLimit { + maximumAllowedReplicas = scaleUpLimit + + possibleLimitingCondition = "ScaleUpLimit" + possibleLimitingReason = "the desired replica count is increasing faster than the maximum scale rate" + } else { + maximumAllowedReplicas = hpaMaxReplicas + + possibleLimitingCondition = "TooManyReplicas" + possibleLimitingReason = "the desired replica count is more than the maximum replica count" + } + + if desiredReplicas < minimumAllowedReplicas { + possibleLimitingCondition = "TooFewReplicas" + + return minimumAllowedReplicas, possibleLimitingCondition, possibleLimitingReason + } else if desiredReplicas > maximumAllowedReplicas { + return maximumAllowedReplicas, possibleLimitingCondition, possibleLimitingReason + } + + return desiredReplicas, "DesiredWithinRange", "the desired count is within the acceptable range" +} + +func calculateScaleUpLimit(currentReplicas int32) int32 { + return int32(math.Max(scaleUpLimitFactor*float64(currentReplicas), scaleUpLimitMinimum)) +} + func (a *HorizontalController) shouldScale(hpa *autoscalingv2.HorizontalPodAutoscaler, currentReplicas, desiredReplicas int32, timestamp time.Time) bool { if desiredReplicas == currentReplicas { return false @@ -526,6 +574,35 @@ func (a *HorizontalController) shouldScale(hpa *autoscalingv2.HorizontalPodAutos return false } +// scaleForResourceMappings attempts to fetch the scale for the +// resource with the given name and namespace, trying each RESTMapping +// in turn until a working one is found. If none work, the first error +// is returned. It returns both the scale, as well as the group-resource from +// the working mapping. +func (a *HorizontalController) scaleForResourceMappings(namespace, name string, mappings []*apimeta.RESTMapping) (*autoscalingv1.Scale, schema.GroupResource, error) { + var firstErr error + for i, mapping := range mappings { + targetGR := mapping.GroupVersionKind.GroupVersion().WithResource(mapping.Resource).GroupResource() + scale, err := a.scaleNamespacer.Scales(namespace).Get(targetGR, name) + if err == nil { + return scale, targetGR, nil + } + + // if this is the first error, remember it, + // then go on and try other mappings until we find a good one + if i == 0 { + firstErr = err + } + } + + // make sure we handle an empty set of mappings + if firstErr == nil { + firstErr = fmt.Errorf("unrecognized resource") + } + + return nil, schema.GroupResource{}, firstErr +} + // setCurrentReplicasInStatus sets the current replica count in the status of the HPA. func (a *HorizontalController) setCurrentReplicasInStatus(hpa *autoscalingv2.HorizontalPodAutoscaler, currentReplicas int32) { a.setStatus(hpa, currentReplicas, hpa.Status.DesiredReplicas, hpa.Status.CurrentMetrics, false) @@ -576,17 +653,17 @@ func (a *HorizontalController) updateStatus(hpa *autoscalingv2.HorizontalPodAuto return nil } -// unsafeConvertToVersionVia is like api.Scheme.UnsafeConvertToVersion, but it does so via an internal version first. +// unsafeConvertToVersionVia is like Scheme.UnsafeConvertToVersion, but it does so via an internal version first. // We use it since working with v2alpha1 is convenient here, but we want to use the v1 client (and // can't just use the internal version). Note that conversion mutates the object, so you need to deepcopy // *before* you call this if the input object came out of a shared cache. func unsafeConvertToVersionVia(obj runtime.Object, externalVersion schema.GroupVersion) (runtime.Object, error) { - objInt, err := api.Scheme.UnsafeConvertToVersion(obj, schema.GroupVersion{Group: externalVersion.Group, Version: runtime.APIVersionInternal}) + objInt, err := legacyscheme.Scheme.UnsafeConvertToVersion(obj, schema.GroupVersion{Group: externalVersion.Group, Version: runtime.APIVersionInternal}) if err != nil { return nil, fmt.Errorf("failed to convert the given object to the internal version: %v", err) } - objExt, err := api.Scheme.UnsafeConvertToVersion(objInt, externalVersion) + objExt, err := legacyscheme.Scheme.UnsafeConvertToVersion(objInt, externalVersion) if err != nil { return nil, fmt.Errorf("failed to convert the given object back to the external version: %v", err) } @@ -632,7 +709,3 @@ func setConditionInList(inputList []autoscalingv2.HorizontalPodAutoscalerConditi return resList } - -func calculateScaleUpLimit(currentReplicas int32) int32 { - return int32(math.Max(scaleUpLimitFactor*float64(currentReplicas), scaleUpLimitMinimum)) -} diff --git a/pkg/controller/podautoscaler/horizontal_test.go b/pkg/controller/podautoscaler/horizontal_test.go index 544930fe940..3018f8e1313 100644 --- a/pkg/controller/podautoscaler/horizontal_test.go +++ b/pkg/controller/podautoscaler/horizontal_test.go @@ -27,25 +27,25 @@ import ( autoscalingv1 "k8s.io/api/autoscaling/v1" autoscalingv2 "k8s.io/api/autoscaling/v2beta1" "k8s.io/api/core/v1" - extensions "k8s.io/api/extensions/v1beta1" "k8s.io/apimachinery/pkg/api/resource" 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/watch" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" clientfake "k8s.io/client-go/kubernetes/fake" + scalefake "k8s.io/client-go/scale/fake" core "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/autoscaling" "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller/podautoscaler/metrics" - metricsfake "k8s.io/metrics/pkg/client/clientset_generated/clientset/fake" - cmfake "k8s.io/metrics/pkg/client/custom_metrics/fake" - cmapi "k8s.io/metrics/pkg/apis/custom_metrics/v1beta1" metricsapi "k8s.io/metrics/pkg/apis/metrics/v1beta1" + metricsfake "k8s.io/metrics/pkg/client/clientset_generated/clientset/fake" + cmfake "k8s.io/metrics/pkg/client/custom_metrics/fake" "github.com/stretchr/testify/assert" @@ -122,6 +122,7 @@ type testCase struct { testClient *fake.Clientset testMetricsClient *metricsfake.Clientset testCMClient *cmfake.FakeCustomMetricsClient + testScaleClient *scalefake.FakeScaleClient } // Needs to be called under a lock. @@ -145,12 +146,12 @@ func init() { scaleUpLimitFactor = 8 } -func (tc *testCase) prepareTestClient(t *testing.T) (*fake.Clientset, *metricsfake.Clientset, *cmfake.FakeCustomMetricsClient) { +func (tc *testCase) prepareTestClient(t *testing.T) (*fake.Clientset, *metricsfake.Clientset, *cmfake.FakeCustomMetricsClient, *scalefake.FakeScaleClient) { namespace := "test-namespace" hpaName := "test-hpa" podNamePrefix := "test-pod" - // TODO: also test with TargetSelector - selector := map[string]string{"name": podNamePrefix} + labelSet := map[string]string{"name": podNamePrefix} + selector := labels.SelectorFromSet(labelSet).String() tc.Lock() @@ -162,13 +163,11 @@ func (tc *testCase) prepareTestClient(t *testing.T) (*fake.Clientset, *metricsfa tc.computeCPUCurrent() } - // TODO(madhusudancs): HPA only supports resources in extensions/v1beta1 right now. Add - // tests for "v1" replicationcontrollers when HPA adds support for cross-group scale. if tc.resource == nil { tc.resource = &fakeResource{ name: "test-rc", - apiVersion: "extensions/v1beta1", - kind: "replicationcontrollers", + apiVersion: "v1", + kind: "ReplicationController", } } tc.Unlock() @@ -240,66 +239,6 @@ func (tc *testCase) prepareTestClient(t *testing.T) (*fake.Clientset, *metricsfa return true, objv1, nil }) - fakeClient.AddReactor("get", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) { - tc.Lock() - defer tc.Unlock() - - obj := &extensions.Scale{ - ObjectMeta: metav1.ObjectMeta{ - Name: tc.resource.name, - Namespace: namespace, - }, - Spec: extensions.ScaleSpec{ - Replicas: tc.initialReplicas, - }, - Status: extensions.ScaleStatus{ - Replicas: tc.initialReplicas, - Selector: selector, - }, - } - return true, obj, nil - }) - - fakeClient.AddReactor("get", "deployments", func(action core.Action) (handled bool, ret runtime.Object, err error) { - tc.Lock() - defer tc.Unlock() - - obj := &extensions.Scale{ - ObjectMeta: metav1.ObjectMeta{ - Name: tc.resource.name, - Namespace: namespace, - }, - Spec: extensions.ScaleSpec{ - Replicas: tc.initialReplicas, - }, - Status: extensions.ScaleStatus{ - Replicas: tc.initialReplicas, - Selector: selector, - }, - } - return true, obj, nil - }) - - fakeClient.AddReactor("get", "replicasets", func(action core.Action) (handled bool, ret runtime.Object, err error) { - tc.Lock() - defer tc.Unlock() - - obj := &extensions.Scale{ - ObjectMeta: metav1.ObjectMeta{ - Name: tc.resource.name, - Namespace: namespace, - }, - Spec: extensions.ScaleSpec{ - Replicas: tc.initialReplicas, - }, - Status: extensions.ScaleStatus{ - Replicas: tc.initialReplicas, - Selector: selector, - }, - } - return true, obj, nil - }) - fakeClient.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { tc.Lock() defer tc.Unlock() @@ -345,39 +284,6 @@ func (tc *testCase) prepareTestClient(t *testing.T) (*fake.Clientset, *metricsfa return true, obj, nil }) - fakeClient.AddReactor("update", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) { - tc.Lock() - defer tc.Unlock() - - obj := action.(core.UpdateAction).GetObject().(*extensions.Scale) - replicas := action.(core.UpdateAction).GetObject().(*extensions.Scale).Spec.Replicas - assert.Equal(t, tc.desiredReplicas, replicas, "the replica count of the RC should be as expected") - tc.scaleUpdated = true - return true, obj, nil - }) - - fakeClient.AddReactor("update", "deployments", func(action core.Action) (handled bool, ret runtime.Object, err error) { - tc.Lock() - defer tc.Unlock() - - obj := action.(core.UpdateAction).GetObject().(*extensions.Scale) - replicas := action.(core.UpdateAction).GetObject().(*extensions.Scale).Spec.Replicas - assert.Equal(t, tc.desiredReplicas, replicas, "the replica count of the deployment should be as expected") - tc.scaleUpdated = true - return true, obj, nil - }) - - fakeClient.AddReactor("update", "replicasets", func(action core.Action) (handled bool, ret runtime.Object, err error) { - tc.Lock() - defer tc.Unlock() - - obj := action.(core.UpdateAction).GetObject().(*extensions.Scale) - replicas := action.(core.UpdateAction).GetObject().(*extensions.Scale).Spec.Replicas - assert.Equal(t, tc.desiredReplicas, replicas, "the replica count of the replicaset should be as expected") - tc.scaleUpdated = true - return true, obj, nil - }) - fakeClient.AddReactor("update", "horizontalpodautoscalers", func(action core.Action) (handled bool, ret runtime.Object, err error) { tc.Lock() defer tc.Unlock() @@ -387,8 +293,9 @@ func (tc *testCase) prepareTestClient(t *testing.T) (*fake.Clientset, *metricsfa assert.Equal(t, hpaName, obj.Name, "the HPA name should be as expected") assert.Equal(t, tc.desiredReplicas, obj.Status.DesiredReplicas, "the desired replica count reported in the object status should be as expected") if tc.verifyCPUCurrent { - assert.NotNil(t, obj.Status.CurrentCPUUtilizationPercentage, "the reported CPU utilization percentage should be non-nil") - assert.Equal(t, tc.CPUCurrent, *obj.Status.CurrentCPUUtilizationPercentage, "the report CPU utilization percentage should be as expected") + if assert.NotNil(t, obj.Status.CurrentCPUUtilizationPercentage, "the reported CPU utilization percentage should be non-nil") { + assert.Equal(t, tc.CPUCurrent, *obj.Status.CurrentCPUUtilizationPercentage, "the report CPU utilization percentage should be as expected") + } } var actualConditions []autoscalingv1.HorizontalPodAutoscalerCondition if err := json.Unmarshal([]byte(obj.ObjectMeta.Annotations[autoscaling.HorizontalPodAutoscalerConditionsAnnotation]), &actualConditions); err != nil { @@ -412,6 +319,100 @@ func (tc *testCase) prepareTestClient(t *testing.T) (*fake.Clientset, *metricsfa return true, obj, nil }) + fakeScaleClient := &scalefake.FakeScaleClient{} + fakeScaleClient.AddReactor("get", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) { + tc.Lock() + defer tc.Unlock() + + obj := &autoscalingv1.Scale{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.resource.name, + Namespace: namespace, + }, + Spec: autoscalingv1.ScaleSpec{ + Replicas: tc.initialReplicas, + }, + Status: autoscalingv1.ScaleStatus{ + Replicas: tc.initialReplicas, + Selector: selector, + }, + } + return true, obj, nil + }) + + fakeScaleClient.AddReactor("get", "deployments", func(action core.Action) (handled bool, ret runtime.Object, err error) { + tc.Lock() + defer tc.Unlock() + + obj := &autoscalingv1.Scale{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.resource.name, + Namespace: namespace, + }, + Spec: autoscalingv1.ScaleSpec{ + Replicas: tc.initialReplicas, + }, + Status: autoscalingv1.ScaleStatus{ + Replicas: tc.initialReplicas, + Selector: selector, + }, + } + return true, obj, nil + }) + + fakeScaleClient.AddReactor("get", "replicasets", func(action core.Action) (handled bool, ret runtime.Object, err error) { + tc.Lock() + defer tc.Unlock() + + obj := &autoscalingv1.Scale{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.resource.name, + Namespace: namespace, + }, + Spec: autoscalingv1.ScaleSpec{ + Replicas: tc.initialReplicas, + }, + Status: autoscalingv1.ScaleStatus{ + Replicas: tc.initialReplicas, + Selector: selector, + }, + } + return true, obj, nil + }) + + fakeScaleClient.AddReactor("update", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) { + tc.Lock() + defer tc.Unlock() + + obj := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale) + replicas := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale).Spec.Replicas + assert.Equal(t, tc.desiredReplicas, replicas, "the replica count of the RC should be as expected") + tc.scaleUpdated = true + return true, obj, nil + }) + + fakeScaleClient.AddReactor("update", "deployments", func(action core.Action) (handled bool, ret runtime.Object, err error) { + tc.Lock() + defer tc.Unlock() + + obj := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale) + replicas := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale).Spec.Replicas + assert.Equal(t, tc.desiredReplicas, replicas, "the replica count of the deployment should be as expected") + tc.scaleUpdated = true + return true, obj, nil + }) + + fakeScaleClient.AddReactor("update", "replicasets", func(action core.Action) (handled bool, ret runtime.Object, err error) { + tc.Lock() + defer tc.Unlock() + + obj := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale) + replicas := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale).Spec.Replicas + assert.Equal(t, tc.desiredReplicas, replicas, "the replica count of the replicaset should be as expected") + tc.scaleUpdated = true + return true, obj, nil + }) + fakeWatch := watch.NewFake() fakeClient.AddWatchReactor("*", core.DefaultWatchReactor(fakeWatch, nil)) @@ -428,7 +429,7 @@ func (tc *testCase) prepareTestClient(t *testing.T) (*fake.Clientset, *metricsfa ObjectMeta: metav1.ObjectMeta{ Name: fmt.Sprintf("%s-%d", podNamePrefix, i), Namespace: namespace, - Labels: selector, + Labels: labelSet, }, Timestamp: metav1.Time{Time: time.Now()}, Containers: []metricsapi.ContainerMetrics{ @@ -486,7 +487,7 @@ func (tc *testCase) prepareTestClient(t *testing.T) (*fake.Clientset, *metricsfa } name := getForAction.GetName() - mapper := api.Registry.RESTMapper() + mapper := legacyscheme.Registry.RESTMapper() metrics := &cmapi.MetricValueList{} var matchedTarget *autoscalingv2.MetricSpec for i, target := range tc.metricsTarget { @@ -523,7 +524,7 @@ func (tc *testCase) prepareTestClient(t *testing.T) (*fake.Clientset, *metricsfa return true, metrics, nil }) - return fakeClient, fakeMetricsClient, fakeCMClient + return fakeClient, fakeMetricsClient, fakeCMClient, fakeScaleClient } func (tc *testCase) verifyResults(t *testing.T) { @@ -538,7 +539,7 @@ func (tc *testCase) verifyResults(t *testing.T) { } func (tc *testCase) setupController(t *testing.T) (*HorizontalController, informers.SharedInformerFactory) { - testClient, testMetricsClient, testCMClient := tc.prepareTestClient(t) + testClient, testMetricsClient, testCMClient, testScaleClient := tc.prepareTestClient(t) if tc.testClient != nil { testClient = tc.testClient } @@ -548,6 +549,9 @@ func (tc *testCase) setupController(t *testing.T) (*HorizontalController, inform if tc.testCMClient != nil { testCMClient = tc.testCMClient } + if tc.testScaleClient != nil { + testScaleClient = tc.testScaleClient + } metricsClient := metrics.NewRESTMetricsClient( testMetricsClient.MetricsV1beta1(), testCMClient, @@ -588,8 +592,9 @@ func (tc *testCase) setupController(t *testing.T) (*HorizontalController, inform hpaController := NewHorizontalController( eventClient.Core(), - testClient.Extensions(), + testScaleClient, testClient.Autoscaling(), + legacyscheme.Registry.RESTMapper(), replicaCalc, informerFactory.Autoscaling().V1().HorizontalPodAutoscalers(), controller.NoResyncPeriodFunc(), @@ -693,7 +698,7 @@ func TestScaleUpDeployment(t *testing.T) { resource: &fakeResource{ name: "test-dep", apiVersion: "extensions/v1beta1", - kind: "deployments", + kind: "Deployment", }, } tc.runTest(t) @@ -713,7 +718,7 @@ func TestScaleUpReplicaSet(t *testing.T) { resource: &fakeResource{ name: "test-replicaset", apiVersion: "extensions/v1beta1", - kind: "replicasets", + kind: "ReplicaSet", }, } tc.runTest(t) @@ -1232,10 +1237,6 @@ func TestUpscaleCapGreaterThanMaxReplicas(t *testing.T) { reportedCPURequests: []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")}, useMetricsAPI: true, expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{ - Type: autoscalingv2.ScalingLimited, - Status: v1.ConditionTrue, - Reason: "ScaleUpLimit", - }, autoscalingv2.HorizontalPodAutoscalerCondition{ Type: autoscalingv2.ScalingLimited, Status: v1.ConditionTrue, Reason: "TooManyReplicas", @@ -1268,18 +1269,18 @@ func TestConditionInvalidSelectorMissing(t *testing.T) { }, } - testClient, _, _ := tc.prepareTestClient(t) - tc.testClient = testClient + _, _, _, testScaleClient := tc.prepareTestClient(t) + tc.testScaleClient = testScaleClient - testClient.PrependReactor("get", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) { - obj := &extensions.Scale{ + testScaleClient.PrependReactor("get", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) { + obj := &autoscalingv1.Scale{ ObjectMeta: metav1.ObjectMeta{ Name: tc.resource.name, }, - Spec: extensions.ScaleSpec{ + Spec: autoscalingv1.ScaleSpec{ Replicas: tc.initialReplicas, }, - Status: extensions.ScaleStatus{ + Status: autoscalingv1.ScaleStatus{ Replicas: tc.initialReplicas, }, } @@ -1313,20 +1314,20 @@ func TestConditionInvalidSelectorUnparsable(t *testing.T) { }, } - testClient, _, _ := tc.prepareTestClient(t) - tc.testClient = testClient + _, _, _, testScaleClient := tc.prepareTestClient(t) + tc.testScaleClient = testScaleClient - testClient.PrependReactor("get", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) { - obj := &extensions.Scale{ + testScaleClient.PrependReactor("get", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) { + obj := &autoscalingv1.Scale{ ObjectMeta: metav1.ObjectMeta{ Name: tc.resource.name, }, - Spec: extensions.ScaleSpec{ + Spec: autoscalingv1.ScaleSpec{ Replicas: tc.initialReplicas, }, - Status: extensions.ScaleStatus{ - Replicas: tc.initialReplicas, - TargetSelector: "cheddar cheese", + Status: autoscalingv1.ScaleStatus{ + Replicas: tc.initialReplicas, + Selector: "cheddar cheese", }, } return true, obj, nil @@ -1374,7 +1375,7 @@ func TestConditionFailedGetMetrics(t *testing.T) { reportedCPURequests: []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")}, useMetricsAPI: true, } - _, testMetricsClient, testCMClient := tc.prepareTestClient(t) + _, testMetricsClient, testCMClient, _ := tc.prepareTestClient(t) tc.testMetricsClient = testMetricsClient tc.testCMClient = testCMClient @@ -1447,11 +1448,11 @@ func TestConditionFailedGetScale(t *testing.T) { }, } - testClient, _, _ := tc.prepareTestClient(t) - tc.testClient = testClient + _, _, _, testScaleClient := tc.prepareTestClient(t) + tc.testScaleClient = testScaleClient - testClient.PrependReactor("get", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) { - return true, &extensions.Scale{}, fmt.Errorf("something went wrong") + testScaleClient.PrependReactor("get", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) { + return true, &autoscalingv1.Scale{}, fmt.Errorf("something went wrong") }) tc.runTest(t) @@ -1474,11 +1475,11 @@ func TestConditionFailedUpdateScale(t *testing.T) { }), } - testClient, _, _ := tc.prepareTestClient(t) - tc.testClient = testClient + _, _, _, testScaleClient := tc.prepareTestClient(t) + tc.testScaleClient = testScaleClient - testClient.PrependReactor("update", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) { - return true, &extensions.Scale{}, fmt.Errorf("something went wrong") + testScaleClient.PrependReactor("update", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) { + return true, &autoscalingv1.Scale{}, fmt.Errorf("something went wrong") }) tc.runTest(t) @@ -1660,7 +1661,7 @@ func TestAvoidUncessaryUpdates(t *testing.T) { reportedPodReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse}, useMetricsAPI: true, } - testClient, _, _ := tc.prepareTestClient(t) + testClient, _, _, _ := tc.prepareTestClient(t) tc.testClient = testClient var savedHPA *autoscalingv1.HorizontalPodAutoscaler testClient.PrependReactor("list", "horizontalpodautoscalers", func(action core.Action) (handled bool, ret runtime.Object, err error) { @@ -1715,4 +1716,71 @@ func TestAvoidUncessaryUpdates(t *testing.T) { tc.runTestWithController(t, controller, informerFactory) } +func TestConvertDesiredReplicasWithRules(t *testing.T) { + conversionTestCases := []struct { + currentReplicas int32 + desiredReplicas int32 + hpaMinReplicas int32 + hpaMaxReplicas int32 + expectedConvertedDesiredReplicas int32 + expectedCondition string + annotation string + }{ + { + currentReplicas: 5, + desiredReplicas: 7, + hpaMinReplicas: 3, + hpaMaxReplicas: 8, + expectedConvertedDesiredReplicas: 7, + expectedCondition: "DesiredWithinRange", + annotation: "prenormalized desired replicas within range", + }, + { + currentReplicas: 3, + desiredReplicas: 1, + hpaMinReplicas: 2, + hpaMaxReplicas: 8, + expectedConvertedDesiredReplicas: 2, + expectedCondition: "TooFewReplicas", + annotation: "prenormalized desired replicas < minReplicas", + }, + { + currentReplicas: 1, + desiredReplicas: 0, + hpaMinReplicas: 0, + hpaMaxReplicas: 10, + expectedConvertedDesiredReplicas: 1, + expectedCondition: "TooFewReplicas", + annotation: "1 is minLimit because hpaMinReplicas < 1", + }, + { + currentReplicas: 20, + desiredReplicas: 1000, + hpaMinReplicas: 1, + hpaMaxReplicas: 10, + expectedConvertedDesiredReplicas: 10, + expectedCondition: "TooManyReplicas", + annotation: "maxReplicas is the limit because maxReplicas < scaleUpLimit", + }, + { + currentReplicas: 3, + desiredReplicas: 1000, + hpaMinReplicas: 1, + hpaMaxReplicas: 2000, + expectedConvertedDesiredReplicas: calculateScaleUpLimit(3), + expectedCondition: "ScaleUpLimit", + annotation: "scaleUpLimit is the limit because scaleUpLimit < maxReplicas", + }, + } + + for _, ctc := range conversionTestCases { + actualConvertedDesiredReplicas, actualCondition, _ := convertDesiredReplicasWithRules( + ctc.currentReplicas, ctc.desiredReplicas, ctc.hpaMinReplicas, ctc.hpaMaxReplicas, + ) + + assert.Equal(t, ctc.expectedConvertedDesiredReplicas, actualConvertedDesiredReplicas, ctc.annotation) + assert.Equal(t, ctc.expectedCondition, actualCondition, ctc.annotation) + } +} + // TODO: add more tests diff --git a/pkg/controller/podautoscaler/legacy_horizontal_test.go b/pkg/controller/podautoscaler/legacy_horizontal_test.go index b94ce82c6c2..ba12990b464 100644 --- a/pkg/controller/podautoscaler/legacy_horizontal_test.go +++ b/pkg/controller/podautoscaler/legacy_horizontal_test.go @@ -30,16 +30,18 @@ import ( autoscalingv1 "k8s.io/api/autoscaling/v1" autoscalingv2 "k8s.io/api/autoscaling/v2beta1" "k8s.io/api/core/v1" - extensions "k8s.io/api/extensions/v1beta1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" clientfake "k8s.io/client-go/kubernetes/fake" restclient "k8s.io/client-go/rest" + scalefake "k8s.io/client-go/scale/fake" core "k8s.io/client-go/testing" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller/podautoscaler/metrics" @@ -49,6 +51,7 @@ import ( "github.com/stretchr/testify/assert" _ "k8s.io/kubernetes/pkg/apis/autoscaling/install" + _ "k8s.io/kubernetes/pkg/apis/core/install" _ "k8s.io/kubernetes/pkg/apis/extensions/install" ) @@ -114,12 +117,12 @@ func (tc *legacyTestCase) computeCPUCurrent() { tc.CPUCurrent = int32(100 * reported / requested) } -func (tc *legacyTestCase) prepareTestClient(t *testing.T) *fake.Clientset { +func (tc *legacyTestCase) prepareTestClient(t *testing.T) (*fake.Clientset, *scalefake.FakeScaleClient) { namespace := "test-namespace" hpaName := "test-hpa" podNamePrefix := "test-pod" - // TODO: also test with TargetSelector - selector := map[string]string{"name": podNamePrefix} + labelSet := map[string]string{"name": podNamePrefix} + selector := labels.SelectorFromSet(labelSet).String() tc.Lock() @@ -131,13 +134,11 @@ func (tc *legacyTestCase) prepareTestClient(t *testing.T) *fake.Clientset { tc.computeCPUCurrent() } - // TODO(madhusudancs): HPA only supports resources in extensions/v1beta1 right now. Add - // tests for "v1" replicationcontrollers when HPA adds support for cross-group scale. if tc.resource == nil { tc.resource = &fakeResource{ name: "test-rc", - apiVersion: "extensions/v1beta1", - kind: "replicationcontrollers", + apiVersion: "v1", + kind: "ReplicationController", } } tc.Unlock() @@ -208,66 +209,6 @@ func (tc *legacyTestCase) prepareTestClient(t *testing.T) *fake.Clientset { return true, objv1, nil }) - fakeClient.AddReactor("get", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) { - tc.Lock() - defer tc.Unlock() - - obj := &extensions.Scale{ - ObjectMeta: metav1.ObjectMeta{ - Name: tc.resource.name, - Namespace: namespace, - }, - Spec: extensions.ScaleSpec{ - Replicas: tc.initialReplicas, - }, - Status: extensions.ScaleStatus{ - Replicas: tc.initialReplicas, - Selector: selector, - }, - } - return true, obj, nil - }) - - fakeClient.AddReactor("get", "deployments", func(action core.Action) (handled bool, ret runtime.Object, err error) { - tc.Lock() - defer tc.Unlock() - - obj := &extensions.Scale{ - ObjectMeta: metav1.ObjectMeta{ - Name: tc.resource.name, - Namespace: namespace, - }, - Spec: extensions.ScaleSpec{ - Replicas: tc.initialReplicas, - }, - Status: extensions.ScaleStatus{ - Replicas: tc.initialReplicas, - Selector: selector, - }, - } - return true, obj, nil - }) - - fakeClient.AddReactor("get", "replicasets", func(action core.Action) (handled bool, ret runtime.Object, err error) { - tc.Lock() - defer tc.Unlock() - - obj := &extensions.Scale{ - ObjectMeta: metav1.ObjectMeta{ - Name: tc.resource.name, - Namespace: namespace, - }, - Spec: extensions.ScaleSpec{ - Replicas: tc.initialReplicas, - }, - Status: extensions.ScaleStatus{ - Replicas: tc.initialReplicas, - Selector: selector, - }, - } - return true, obj, nil - }) - fakeClient.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { tc.Lock() defer tc.Unlock() @@ -386,39 +327,6 @@ func (tc *legacyTestCase) prepareTestClient(t *testing.T) *fake.Clientset { return true, newFakeResponseWrapper(heapsterRawMemResponse), nil }) - fakeClient.AddReactor("update", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) { - tc.Lock() - defer tc.Unlock() - - obj := action.(core.UpdateAction).GetObject().(*extensions.Scale) - replicas := action.(core.UpdateAction).GetObject().(*extensions.Scale).Spec.Replicas - assert.Equal(t, tc.desiredReplicas, replicas, "the replica count of the RC should be as expected") - tc.scaleUpdated = true - return true, obj, nil - }) - - fakeClient.AddReactor("update", "deployments", func(action core.Action) (handled bool, ret runtime.Object, err error) { - tc.Lock() - defer tc.Unlock() - - obj := action.(core.UpdateAction).GetObject().(*extensions.Scale) - replicas := action.(core.UpdateAction).GetObject().(*extensions.Scale).Spec.Replicas - assert.Equal(t, tc.desiredReplicas, replicas, "the replica count of the deployment should be as expected") - tc.scaleUpdated = true - return true, obj, nil - }) - - fakeClient.AddReactor("update", "replicasets", func(action core.Action) (handled bool, ret runtime.Object, err error) { - tc.Lock() - defer tc.Unlock() - - obj := action.(core.UpdateAction).GetObject().(*extensions.Scale) - replicas := action.(core.UpdateAction).GetObject().(*extensions.Scale).Spec.Replicas - assert.Equal(t, tc.desiredReplicas, replicas, "the replica count of the replicaset should be as expected") - tc.scaleUpdated = true - return true, obj, nil - }) - fakeClient.AddReactor("update", "horizontalpodautoscalers", func(action core.Action) (handled bool, ret runtime.Object, err error) { tc.Lock() defer tc.Unlock() @@ -428,8 +336,9 @@ func (tc *legacyTestCase) prepareTestClient(t *testing.T) *fake.Clientset { assert.Equal(t, hpaName, obj.Name, "the HPA name should be as expected") assert.Equal(t, tc.desiredReplicas, obj.Status.DesiredReplicas, "the desired replica count reported in the object status should be as expected") if tc.verifyCPUCurrent { - assert.NotNil(t, obj.Status.CurrentCPUUtilizationPercentage, "the reported CPU utilization percentage should be non-nil") - assert.Equal(t, tc.CPUCurrent, *obj.Status.CurrentCPUUtilizationPercentage, "the report CPU utilization percentage should be as expected") + if assert.NotNil(t, obj.Status.CurrentCPUUtilizationPercentage, "the reported CPU utilization percentage should be non-nil") { + assert.Equal(t, tc.CPUCurrent, *obj.Status.CurrentCPUUtilizationPercentage, "the report CPU utilization percentage should be as expected") + } } tc.statusUpdated = true // Every time we reconcile HPA object we are updating status. @@ -437,10 +346,104 @@ func (tc *legacyTestCase) prepareTestClient(t *testing.T) *fake.Clientset { return true, obj, nil }) + fakeScaleClient := &scalefake.FakeScaleClient{} + fakeScaleClient.AddReactor("get", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) { + tc.Lock() + defer tc.Unlock() + + obj := &autoscalingv1.Scale{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.resource.name, + Namespace: namespace, + }, + Spec: autoscalingv1.ScaleSpec{ + Replicas: tc.initialReplicas, + }, + Status: autoscalingv1.ScaleStatus{ + Replicas: tc.initialReplicas, + Selector: selector, + }, + } + return true, obj, nil + }) + + fakeScaleClient.AddReactor("get", "deployments", func(action core.Action) (handled bool, ret runtime.Object, err error) { + tc.Lock() + defer tc.Unlock() + + obj := &autoscalingv1.Scale{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.resource.name, + Namespace: namespace, + }, + Spec: autoscalingv1.ScaleSpec{ + Replicas: tc.initialReplicas, + }, + Status: autoscalingv1.ScaleStatus{ + Replicas: tc.initialReplicas, + Selector: selector, + }, + } + return true, obj, nil + }) + + fakeScaleClient.AddReactor("get", "replicasets", func(action core.Action) (handled bool, ret runtime.Object, err error) { + tc.Lock() + defer tc.Unlock() + + obj := &autoscalingv1.Scale{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.resource.name, + Namespace: namespace, + }, + Spec: autoscalingv1.ScaleSpec{ + Replicas: tc.initialReplicas, + }, + Status: autoscalingv1.ScaleStatus{ + Replicas: tc.initialReplicas, + Selector: selector, + }, + } + return true, obj, nil + }) + + fakeScaleClient.AddReactor("update", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) { + tc.Lock() + defer tc.Unlock() + + obj := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale) + replicas := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale).Spec.Replicas + assert.Equal(t, tc.desiredReplicas, replicas, "the replica count of the RC should be as expected") + tc.scaleUpdated = true + return true, obj, nil + }) + + fakeScaleClient.AddReactor("update", "deployments", func(action core.Action) (handled bool, ret runtime.Object, err error) { + tc.Lock() + defer tc.Unlock() + + obj := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale) + replicas := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale).Spec.Replicas + assert.Equal(t, tc.desiredReplicas, replicas, "the replica count of the deployment should be as expected") + tc.scaleUpdated = true + return true, obj, nil + }) + + fakeScaleClient.AddReactor("update", "replicasets", func(action core.Action) (handled bool, ret runtime.Object, err error) { + tc.Lock() + defer tc.Unlock() + + obj := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale) + replicas := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale).Spec.Replicas + assert.Equal(t, tc.desiredReplicas, replicas, "the replica count of the replicaset should be as expected") + tc.scaleUpdated = true + return true, obj, nil + }) + fakeWatch := watch.NewFake() fakeClient.AddWatchReactor("*", core.DefaultWatchReactor(fakeWatch, nil)) - return fakeClient + return fakeClient, fakeScaleClient } func (tc *legacyTestCase) verifyResults(t *testing.T) { @@ -455,7 +458,7 @@ func (tc *legacyTestCase) verifyResults(t *testing.T) { } func (tc *legacyTestCase) runTest(t *testing.T) { - testClient := tc.prepareTestClient(t) + testClient, testScaleClient := tc.prepareTestClient(t) metricsClient := metrics.NewHeapsterMetricsClient(testClient, metrics.DefaultHeapsterNamespace, metrics.DefaultHeapsterScheme, metrics.DefaultHeapsterService, metrics.DefaultHeapsterPort) eventClient := &clientfake.Clientset{} @@ -493,8 +496,9 @@ func (tc *legacyTestCase) runTest(t *testing.T) { hpaController := NewHorizontalController( eventClient.Core(), - testClient.Extensions(), + testScaleClient, testClient.Autoscaling(), + legacyscheme.Registry.RESTMapper(), replicaCalc, informerFactory.Autoscaling().V1().HorizontalPodAutoscalers(), controller.NoResyncPeriodFunc(), @@ -584,7 +588,7 @@ func LegacyTestScaleUpDeployment(t *testing.T) { resource: &fakeResource{ name: "test-dep", apiVersion: "extensions/v1beta1", - kind: "deployments", + kind: "Deployment", }, } tc.runTest(t) @@ -604,7 +608,7 @@ func LegacyTestScaleUpReplicaSet(t *testing.T) { resource: &fakeResource{ name: "test-replicaset", apiVersion: "extensions/v1beta1", - kind: "replicasets", + kind: "ReplicaSet", }, } tc.runTest(t) diff --git a/pkg/controller/podautoscaler/metrics/BUILD b/pkg/controller/podautoscaler/metrics/BUILD index 52f234be2c3..8592a565082 100644 --- a/pkg/controller/podautoscaler/metrics/BUILD +++ b/pkg/controller/podautoscaler/metrics/BUILD @@ -38,10 +38,10 @@ go_test( "legacy_metrics_client_test.go", "rest_metrics_client_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/podautoscaler/metrics", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/extensions/install:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/k8s.io/api/autoscaling/v2beta1:go_default_library", diff --git a/pkg/controller/podautoscaler/metrics/legacy_metrics_client.go b/pkg/controller/podautoscaler/metrics/legacy_metrics_client.go index 36c24da2f63..5d13acbd65b 100644 --- a/pkg/controller/podautoscaler/metrics/legacy_metrics_client.go +++ b/pkg/controller/podautoscaler/metrics/legacy_metrics_client.go @@ -54,8 +54,8 @@ type HeapsterMetricsClient struct { func NewHeapsterMetricsClient(client clientset.Interface, namespace, scheme, service, port string) MetricsClient { return &HeapsterMetricsClient{ - services: client.Core().Services(namespace), - podsGetter: client.Core(), + services: client.CoreV1().Services(namespace), + podsGetter: client.CoreV1(), heapsterScheme: scheme, heapsterService: service, heapsterPort: port, diff --git a/pkg/controller/podautoscaler/metrics/rest_metrics_client.go b/pkg/controller/podautoscaler/metrics/rest_metrics_client.go index 07dd290a563..e8894bfcc95 100644 --- a/pkg/controller/podautoscaler/metrics/rest_metrics_client.go +++ b/pkg/controller/podautoscaler/metrics/rest_metrics_client.go @@ -58,11 +58,11 @@ type resourceMetricsClient struct { func (c *resourceMetricsClient) GetResourceMetric(resource v1.ResourceName, namespace string, selector labels.Selector) (PodMetricsInfo, time.Time, error) { metrics, err := c.client.PodMetricses(namespace).List(metav1.ListOptions{LabelSelector: selector.String()}) if err != nil { - return nil, time.Time{}, fmt.Errorf("unable to fetch metrics from API: %v", err) + return nil, time.Time{}, fmt.Errorf("unable to fetch metrics from resource metrics API: %v", err) } if len(metrics.Items) == 0 { - return nil, time.Time{}, fmt.Errorf("no metrics returned from heapster") + return nil, time.Time{}, fmt.Errorf("no metrics returned from resource metrics API") } res := make(PodMetricsInfo, len(metrics.Items)) @@ -101,7 +101,7 @@ type customMetricsClient struct { func (c *customMetricsClient) GetRawMetric(metricName string, namespace string, selector labels.Selector) (PodMetricsInfo, time.Time, error) { metrics, err := c.client.NamespacedMetrics(namespace).GetForObjects(schema.GroupKind{Kind: "Pod"}, selector, metricName) if err != nil { - return nil, time.Time{}, fmt.Errorf("unable to fetch metrics from API: %v", err) + return nil, time.Time{}, fmt.Errorf("unable to fetch metrics from custom metrics API: %v", err) } if len(metrics.Items) == 0 { @@ -134,7 +134,7 @@ func (c *customMetricsClient) GetObjectMetric(metricName string, namespace strin } if err != nil { - return 0, time.Time{}, fmt.Errorf("unable to fetch metrics from API: %v", err) + return 0, time.Time{}, fmt.Errorf("unable to fetch metrics from custom metrics API: %v", err) } return metricValue.Value.MilliValue(), metricValue.Timestamp.Time, nil diff --git a/pkg/controller/podautoscaler/metrics/rest_metrics_client_test.go b/pkg/controller/podautoscaler/metrics/rest_metrics_client_test.go index 19ecfd0a7fc..289b93f04d7 100644 --- a/pkg/controller/podautoscaler/metrics/rest_metrics_client_test.go +++ b/pkg/controller/podautoscaler/metrics/rest_metrics_client_test.go @@ -30,14 +30,12 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" core "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" - // For api.Registry.RESTMapper() + "k8s.io/kubernetes/pkg/api/legacyscheme" _ "k8s.io/kubernetes/pkg/apis/extensions/install" - metricsfake "k8s.io/metrics/pkg/client/clientset_generated/clientset/fake" - cmfake "k8s.io/metrics/pkg/client/custom_metrics/fake" - cmapi "k8s.io/metrics/pkg/apis/custom_metrics/v1beta1" metricsapi "k8s.io/metrics/pkg/apis/metrics/v1beta1" + metricsfake "k8s.io/metrics/pkg/client/clientset_generated/clientset/fake" + cmfake "k8s.io/metrics/pkg/client/custom_metrics/fake" "github.com/stretchr/testify/assert" ) @@ -131,7 +129,7 @@ func (tc *restClientTestCase) prepareTestClient(t *testing.T) (*metricsfake.Clie return true, &metrics, nil } else { name := getForAction.GetName() - mapper := api.Registry.RESTMapper() + mapper := legacyscheme.Registry.RESTMapper() assert.NotNil(t, tc.singleObject, "should have only requested a single-object metric when we asked for metrics for a single object") gk := schema.FromAPIVersionAndKind(tc.singleObject.APIVersion, tc.singleObject.Kind).GroupKind() mapping, err := mapper.RESTMapping(gk) @@ -254,7 +252,7 @@ func TestRESTClientQpsSumEqualZero(t *testing.T) { func TestRESTClientCPUEmptyMetrics(t *testing.T) { tc := restClientTestCase{ resourceName: v1.ResourceCPU, - desiredError: fmt.Errorf("no metrics returned from heapster"), + desiredError: fmt.Errorf("no metrics returned from resource metrics API"), reportedMetricPoints: []metricPoint{}, reportedPodMetrics: [][]int64{}, } diff --git a/pkg/controller/podautoscaler/replica_calculator_test.go b/pkg/controller/podautoscaler/replica_calculator_test.go index 3353adbe572..a5c3bf03872 100644 --- a/pkg/controller/podautoscaler/replica_calculator_test.go +++ b/pkg/controller/podautoscaler/replica_calculator_test.go @@ -30,13 +30,12 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/kubernetes/fake" core "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/controller/podautoscaler/metrics" - metricsfake "k8s.io/metrics/pkg/client/clientset_generated/clientset/fake" - cmfake "k8s.io/metrics/pkg/client/custom_metrics/fake" - cmapi "k8s.io/metrics/pkg/apis/custom_metrics/v1beta1" metricsapi "k8s.io/metrics/pkg/apis/metrics/v1beta1" + metricsfake "k8s.io/metrics/pkg/client/clientset_generated/clientset/fake" + cmfake "k8s.io/metrics/pkg/client/custom_metrics/fake" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -208,7 +207,7 @@ func (tc *replicaCalcTestCase) prepareTestClient(t *testing.T) (*fake.Clientset, return true, &metrics, nil } name := getForAction.GetName() - mapper := api.Registry.RESTMapper() + mapper := legacyscheme.Registry.RESTMapper() metrics := &cmapi.MetricValueList{} assert.NotNil(t, tc.metric.singleObject, "should have only requested a single-object metric when calling GetObjectMetricReplicas") gk := schema.FromAPIVersionAndKind(tc.metric.singleObject.APIVersion, tc.metric.singleObject.Kind).GroupKind() @@ -580,7 +579,7 @@ func TestReplicaCalcMissingMetrics(t *testing.T) { func TestReplicaCalcEmptyMetrics(t *testing.T) { tc := replicaCalcTestCase{ currentReplicas: 4, - expectedError: fmt.Errorf("unable to get metrics for resource cpu: no metrics returned from heapster"), + expectedError: fmt.Errorf("unable to get metrics for resource cpu: no metrics returned from resource metrics API"), resource: &resourceInfo{ name: v1.ResourceCPU, requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, diff --git a/pkg/controller/podgc/BUILD b/pkg/controller/podgc/BUILD index 01209d81fc7..da2b2317aee 100644 --- a/pkg/controller/podgc/BUILD +++ b/pkg/controller/podgc/BUILD @@ -33,8 +33,8 @@ go_library( go_test( name = "go_default_test", srcs = ["gc_controller_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/podgc", - library = ":go_default_library", deps = [ "//pkg/controller:go_default_library", "//pkg/controller/testutil:go_default_library", diff --git a/pkg/controller/podgc/gc_controller.go b/pkg/controller/podgc/gc_controller.go index 76179b736b2..8badcc4ea37 100644 --- a/pkg/controller/podgc/gc_controller.go +++ b/pkg/controller/podgc/gc_controller.go @@ -52,15 +52,15 @@ type PodGCController struct { } func NewPodGC(kubeClient clientset.Interface, podInformer coreinformers.PodInformer, terminatedPodThreshold int) *PodGCController { - if kubeClient != nil && kubeClient.Core().RESTClient().GetRateLimiter() != nil { - metrics.RegisterMetricAndTrackRateLimiterUsage("gc_controller", kubeClient.Core().RESTClient().GetRateLimiter()) + if kubeClient != nil && kubeClient.CoreV1().RESTClient().GetRateLimiter() != nil { + metrics.RegisterMetricAndTrackRateLimiterUsage("gc_controller", kubeClient.CoreV1().RESTClient().GetRateLimiter()) } gcc := &PodGCController{ kubeClient: kubeClient, terminatedPodThreshold: terminatedPodThreshold, deletePod: func(namespace, name string) error { glog.Infof("PodGC is force deleting Pod: %v:%v", namespace, name) - return kubeClient.Core().Pods(namespace).Delete(name, metav1.NewDeleteOptions(0)) + return kubeClient.CoreV1().Pods(namespace).Delete(name, metav1.NewDeleteOptions(0)) }, } @@ -143,7 +143,7 @@ func (gcc *PodGCController) gcTerminated(pods []*v1.Pod) { func (gcc *PodGCController) gcOrphaned(pods []*v1.Pod) { glog.V(4).Infof("GC'ing orphaned") // We want to get list of Nodes from the etcd, to make sure that it's as fresh as possible. - nodes, err := gcc.kubeClient.Core().Nodes().List(metav1.ListOptions{}) + nodes, err := gcc.kubeClient.CoreV1().Nodes().List(metav1.ListOptions{}) if err != nil { return } diff --git a/pkg/controller/replicaset/BUILD b/pkg/controller/replicaset/BUILD index 9ddc5b0f0a9..7ad0a4ca3df 100644 --- a/pkg/controller/replicaset/BUILD +++ b/pkg/controller/replicaset/BUILD @@ -24,6 +24,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/client-go/informers/core/v1:go_default_library", @@ -43,11 +44,14 @@ go_library( go_test( name = "go_default_test", - srcs = ["replica_set_test.go"], + srcs = [ + "replica_set_test.go", + "replica_set_utils_test.go", + ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/replicaset", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/controller:go_default_library", "//pkg/securitycontext:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/controller/replicaset/OWNERS b/pkg/controller/replicaset/OWNERS index 3fc7da4b658..1cf42254041 100755 --- a/pkg/controller/replicaset/OWNERS +++ b/pkg/controller/replicaset/OWNERS @@ -1,7 +1,9 @@ approvers: - caesarxuchao - lavalamp +- enisoc reviewers: - caesarxuchao - lavalamp - tnozicka +- enisoc diff --git a/pkg/controller/replicaset/replica_set.go b/pkg/controller/replicaset/replica_set.go index 3a24370b078..a111e5b6241 100644 --- a/pkg/controller/replicaset/replica_set.go +++ b/pkg/controller/replicaset/replica_set.go @@ -14,7 +14,16 @@ See the License for the specific language governing permissions and limitations under the License. */ -// If you make changes to this file, you should also make the corresponding change in ReplicationController. +// ### ATTENTION ### +// +// This code implements both ReplicaSet and ReplicationController. +// +// For RC, the objects are converted on the way in and out (see ../replication/), +// as if ReplicationController were just an older API version of ReplicaSet. +// However, RC and RS still have separate storage and separate instantiations +// of the ReplicaSetController object. +// +// Use rsc.Kind in log messages rather than hard-coding "ReplicaSet". package replicaset @@ -22,6 +31,7 @@ import ( "fmt" "reflect" "sort" + "strings" "sync" "time" @@ -32,6 +42,7 @@ import ( "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" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" coreinformers "k8s.io/client-go/informers/core/v1" @@ -59,12 +70,14 @@ const ( statusUpdateRetries = 1 ) -// controllerKind contains the schema.GroupVersionKind for this controller type. -var controllerKind = v1beta1.SchemeGroupVersion.WithKind("ReplicaSet") - // ReplicaSetController is responsible for synchronizing ReplicaSet objects stored // in the system with actual running pods. type ReplicaSetController struct { + // GroupVersionKind indicates the controller type. + // Different instances of this struct may handle different GVKs. + // For example, this struct can be used (with adapters) to handle ReplicationController. + schema.GroupVersionKind + kubeClient clientset.Interface podControl controller.PodControlInterface @@ -95,22 +108,35 @@ type ReplicaSetController struct { // NewReplicaSetController configures a replica set controller with the specified event recorder func NewReplicaSetController(rsInformer extensionsinformers.ReplicaSetInformer, podInformer coreinformers.PodInformer, kubeClient clientset.Interface, burstReplicas int) *ReplicaSetController { - if kubeClient != nil && kubeClient.Core().RESTClient().GetRateLimiter() != nil { - metrics.RegisterMetricAndTrackRateLimiterUsage("replicaset_controller", kubeClient.Core().RESTClient().GetRateLimiter()) - } eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(glog.Infof) - eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.Core().RESTClient()).Events("")}) - - rsc := &ReplicaSetController{ - kubeClient: kubeClient, - podControl: controller.RealPodControl{ + eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.CoreV1().RESTClient()).Events("")}) + return NewBaseController(rsInformer, podInformer, kubeClient, burstReplicas, + v1beta1.SchemeGroupVersion.WithKind("ReplicaSet"), + "replicaset_controller", + "replicaset", + controller.RealPodControl{ KubeClient: kubeClient, Recorder: eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "replicaset-controller"}), }, - burstReplicas: burstReplicas, - expectations: controller.NewUIDTrackingControllerExpectations(controller.NewControllerExpectations()), - queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "replicaset"), + ) +} + +// NewBaseController is the implementation of NewReplicaSetController with additional injected +// parameters so that it can also serve as the implementation of NewReplicationController. +func NewBaseController(rsInformer extensionsinformers.ReplicaSetInformer, podInformer coreinformers.PodInformer, kubeClient clientset.Interface, burstReplicas int, + gvk schema.GroupVersionKind, metricOwnerName, queueName string, podControl controller.PodControlInterface) *ReplicaSetController { + if kubeClient != nil && kubeClient.CoreV1().RESTClient().GetRateLimiter() != nil { + metrics.RegisterMetricAndTrackRateLimiterUsage(metricOwnerName, kubeClient.CoreV1().RESTClient().GetRateLimiter()) + } + + rsc := &ReplicaSetController{ + GroupVersionKind: gvk, + kubeClient: kubeClient, + podControl: podControl, + burstReplicas: burstReplicas, + expectations: controller.NewUIDTrackingControllerExpectations(controller.NewControllerExpectations()), + queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), queueName), } rsInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ @@ -153,10 +179,11 @@ func (rsc *ReplicaSetController) Run(workers int, stopCh <-chan struct{}) { defer utilruntime.HandleCrash() defer rsc.queue.ShutDown() - glog.Infof("Starting replica set controller") - defer glog.Infof("Shutting down replica set Controller") + controllerName := strings.ToLower(rsc.Kind) + glog.Infof("Starting %v controller", controllerName) + defer glog.Infof("Shutting down %v controller", controllerName) - if !controller.WaitForCacheSync("replica set", stopCh, rsc.podListerSynced, rsc.rsListerSynced) { + if !controller.WaitForCacheSync(rsc.Kind, stopCh, rsc.podListerSynced, rsc.rsListerSynced) { return } @@ -176,7 +203,7 @@ func (rsc *ReplicaSetController) getPodReplicaSets(pod *v1.Pod) []*extensions.Re if len(rss) > 1 { // ControllerRef will ensure we don't do anything crazy, but more than one // item in this list nevertheless constitutes user error. - utilruntime.HandleError(fmt.Errorf("user error! more than one ReplicaSet is selecting pods with labels: %+v", pod.Labels)) + utilruntime.HandleError(fmt.Errorf("user error! more than one %v is selecting pods with labels: %+v", rsc.Kind, pod.Labels)) } return rss } @@ -187,7 +214,7 @@ func (rsc *ReplicaSetController) getPodReplicaSets(pod *v1.Pod) []*extensions.Re func (rsc *ReplicaSetController) resolveControllerRef(namespace string, controllerRef *metav1.OwnerReference) *extensions.ReplicaSet { // We can't look up by UID, so look up by Name and then verify UID. // Don't even try to look up by Name if it's the wrong Kind. - if controllerRef.Kind != controllerKind.Kind { + if controllerRef.Kind != rsc.Kind { return nil } rs, err := rsc.rsLister.ReplicaSets(namespace).Get(controllerRef.Name) @@ -220,7 +247,7 @@ func (rsc *ReplicaSetController) updateRS(old, cur interface{}) { // that bad as ReplicaSets that haven't met expectations yet won't // sync, and all the listing is done using local stores. if *(oldRS.Spec.Replicas) != *(curRS.Spec.Replicas) { - glog.V(4).Infof("Replica set %v updated. Desired pod count change: %d->%d", curRS.Name, *(oldRS.Spec.Replicas), *(curRS.Spec.Replicas)) + glog.V(4).Infof("%v %v updated. Desired pod count change: %d->%d", rsc.Kind, curRS.Name, *(oldRS.Spec.Replicas), *(curRS.Spec.Replicas)) } rsc.enqueueReplicaSet(cur) } @@ -319,7 +346,7 @@ func (rsc *ReplicaSetController) updatePod(old, cur interface{}) { // Note that this still suffers from #29229, we are just moving the problem one level // "closer" to kubelet (from the deployment to the replica set controller). if !podutil.IsPodReady(oldPod) && podutil.IsPodReady(curPod) && rs.Spec.MinReadySeconds > 0 { - glog.V(2).Infof("ReplicaSet %q will be enqueued after %ds for availability check", rs.Name, rs.Spec.MinReadySeconds) + glog.V(2).Infof("%v %q will be enqueued after %ds for availability check", rsc.Kind, rs.Name, rs.Spec.MinReadySeconds) // Add a second to avoid milliseconds skew in AddAfter. // See https://github.com/kubernetes/kubernetes/issues/39785#issuecomment-279959133 for more info. rsc.enqueueReplicaSetAfter(rs, (time.Duration(rs.Spec.MinReadySeconds)*time.Second)+time.Second) @@ -434,13 +461,11 @@ func (rsc *ReplicaSetController) manageReplicas(filteredPods []*v1.Pod, rs *exte diff := len(filteredPods) - int(*(rs.Spec.Replicas)) rsKey, err := controller.KeyFunc(rs) if err != nil { - utilruntime.HandleError(fmt.Errorf("Couldn't get key for ReplicaSet %#v: %v", rs, err)) + utilruntime.HandleError(fmt.Errorf("Couldn't get key for %v %#v: %v", rsc.Kind, rs, err)) return nil } - var errCh chan error if diff < 0 { diff *= -1 - errCh = make(chan error, diff) if diff > rsc.burstReplicas { diff = rsc.burstReplicas } @@ -450,8 +475,7 @@ func (rsc *ReplicaSetController) manageReplicas(filteredPods []*v1.Pod, rs *exte // into a performance bottleneck. We should generate a UID for the pod // beforehand and store it via ExpectCreations. rsc.expectations.ExpectCreations(rsKey, diff) - var wg sync.WaitGroup - glog.V(2).Infof("Too few %q/%q replicas, need %d, creating %d", rs.Namespace, rs.Name, *(rs.Spec.Replicas), diff) + glog.V(2).Infof("Too few replicas for %v %s/%s, need %d, creating %d", rsc.Kind, rs.Namespace, rs.Name, *(rs.Spec.Replicas), diff) // Batch the pod creates. Batch sizes start at SlowStartInitialBatchSize // and double with each successful iteration in a kind of "slow start". // This handles attempts to start large numbers of pods that would @@ -460,105 +484,85 @@ func (rsc *ReplicaSetController) manageReplicas(filteredPods []*v1.Pod, rs *exte // prevented from spamming the API service with the pod create requests // after one of its pods fails. Conveniently, this also prevents the // event spam that those failures would generate. - for batchSize := integer.IntMin(diff, controller.SlowStartInitialBatchSize); diff > 0; batchSize = integer.IntMin(2*batchSize, diff) { - errorCount := len(errCh) - wg.Add(batchSize) - for i := 0; i < batchSize; i++ { - go func() { - defer wg.Done() - var err error - boolPtr := func(b bool) *bool { return &b } - controllerRef := &metav1.OwnerReference{ - APIVersion: controllerKind.GroupVersion().String(), - Kind: controllerKind.Kind, - Name: rs.Name, - UID: rs.UID, - BlockOwnerDeletion: boolPtr(true), - Controller: boolPtr(true), - } - err = rsc.podControl.CreatePodsWithControllerRef(rs.Namespace, &rs.Spec.Template, rs, controllerRef) - if err != nil && errors.IsTimeout(err) { - // Pod is created but its initialization has timed out. - // If the initialization is successful eventually, the - // controller will observe the creation via the informer. - // If the initialization fails, or if the pod keeps - // uninitialized for a long time, the informer will not - // receive any update, and the controller will create a new - // pod when the expectation expires. - return - } - if err != nil { - // Decrement the expected number of creates because the informer won't observe this pod - glog.V(2).Infof("Failed creation, decrementing expectations for replica set %q/%q", rs.Namespace, rs.Name) - rsc.expectations.CreationObserved(rsKey) - errCh <- err - } - }() + successfulCreations, err := slowStartBatch(diff, controller.SlowStartInitialBatchSize, func() error { + boolPtr := func(b bool) *bool { return &b } + controllerRef := &metav1.OwnerReference{ + APIVersion: rsc.GroupVersion().String(), + Kind: rsc.Kind, + Name: rs.Name, + UID: rs.UID, + BlockOwnerDeletion: boolPtr(true), + Controller: boolPtr(true), } - wg.Wait() - // any skipped pods that we never attempted to start shouldn't be expected. - skippedPods := diff - batchSize - if errorCount < len(errCh) && skippedPods > 0 { - glog.V(2).Infof("Slow-start failure. Skipping creation of %d pods, decrementing expectations for replica set %q/%q", skippedPods, rs.Namespace, rs.Name) - for i := 0; i < skippedPods; i++ { - // Decrement the expected number of creates because the informer won't observe this pod - rsc.expectations.CreationObserved(rsKey) - } - // The skipped pods will be retried later. The next controller resync will - // retry the slow start process. - break + err := rsc.podControl.CreatePodsWithControllerRef(rs.Namespace, &rs.Spec.Template, rs, controllerRef) + if err != nil && errors.IsTimeout(err) { + // Pod is created but its initialization has timed out. + // If the initialization is successful eventually, the + // controller will observe the creation via the informer. + // If the initialization fails, or if the pod keeps + // uninitialized for a long time, the informer will not + // receive any update, and the controller will create a new + // pod when the expectation expires. + return nil + } + return err + }) + + // Any skipped pods that we never attempted to start shouldn't be expected. + // The skipped pods will be retried later. The next controller resync will + // retry the slow start process. + if skippedPods := diff - successfulCreations; skippedPods > 0 { + glog.V(2).Infof("Slow-start failure. Skipping creation of %d pods, decrementing expectations for %v %v/%v", skippedPods, rsc.Kind, rs.Namespace, rs.Name) + for i := 0; i < skippedPods; i++ { + // Decrement the expected number of creates because the informer won't observe this pod + rsc.expectations.CreationObserved(rsKey) } - diff -= batchSize } + return err } else if diff > 0 { if diff > rsc.burstReplicas { diff = rsc.burstReplicas } - errCh = make(chan error, diff) - glog.V(2).Infof("Too many %q/%q replicas, need %d, deleting %d", rs.Namespace, rs.Name, *(rs.Spec.Replicas), diff) - // No need to sort pods if we are about to delete all of them - if *(rs.Spec.Replicas) != 0 { - // Sort the pods in the order such that not-ready < ready, unscheduled - // < scheduled, and pending < running. This ensures that we delete pods - // in the earlier stages whenever possible. - sort.Sort(controller.ActivePods(filteredPods)) - } + glog.V(2).Infof("Too many replicas for %v %s/%s, need %d, deleting %d", rsc.Kind, rs.Namespace, rs.Name, *(rs.Spec.Replicas), diff) + + // Choose which Pods to delete, preferring those in earlier phases of startup. + podsToDelete := getPodsToDelete(filteredPods, diff) + // Snapshot the UIDs (ns/name) of the pods we're expecting to see // deleted, so we know to record their expectations exactly once either // when we see it as an update of the deletion timestamp, or as a delete. // Note that if the labels on a pod/rs change in a way that the pod gets // orphaned, the rs will only wake up after the expectations have // expired even if other pods are deleted. - deletedPodKeys := []string{} - for i := 0; i < diff; i++ { - deletedPodKeys = append(deletedPodKeys, controller.PodKey(filteredPods[i])) - } - rsc.expectations.ExpectDeletions(rsKey, deletedPodKeys) + rsc.expectations.ExpectDeletions(rsKey, getPodKeys(podsToDelete)) + + errCh := make(chan error, diff) var wg sync.WaitGroup wg.Add(diff) - for i := 0; i < diff; i++ { - go func(ix int) { + for _, pod := range podsToDelete { + go func(targetPod *v1.Pod) { defer wg.Done() - if err := rsc.podControl.DeletePod(rs.Namespace, filteredPods[ix].Name, rs); err != nil { + if err := rsc.podControl.DeletePod(rs.Namespace, targetPod.Name, rs); err != nil { // Decrement the expected number of deletes because the informer won't observe this deletion - podKey := controller.PodKey(filteredPods[ix]) - glog.V(2).Infof("Failed to delete %v, decrementing expectations for controller %q/%q", podKey, rs.Namespace, rs.Name) + podKey := controller.PodKey(targetPod) + glog.V(2).Infof("Failed to delete %v, decrementing expectations for %v %s/%s", podKey, rsc.Kind, rs.Namespace, rs.Name) rsc.expectations.DeletionObserved(rsKey, podKey) errCh <- err } - }(i) + }(pod) } wg.Wait() + + select { + case err := <-errCh: + // all errors have been reported before and they're likely to be the same, so we'll only return the first one we hit. + if err != nil { + return err + } + default: + } } - select { - case err := <-errCh: - // all errors have been reported before and they're likely to be the same, so we'll only return the first one we hit. - if err != nil { - return err - } - default: - } return nil } @@ -566,9 +570,10 @@ func (rsc *ReplicaSetController) manageReplicas(filteredPods []*v1.Pod, rs *exte // meaning it did not expect to see any more of its pods created or deleted. This function is not meant to be // invoked concurrently with the same key. func (rsc *ReplicaSetController) syncReplicaSet(key string) error { + startTime := time.Now() defer func() { - glog.V(4).Infof("Finished syncing replica set %q (%v)", key, time.Now().Sub(startTime)) + glog.V(4).Infof("Finished syncing %v %q (%v)", rsc.Kind, key, time.Since(startTime)) }() namespace, name, err := cache.SplitMetaNamespaceKey(key) @@ -577,7 +582,7 @@ func (rsc *ReplicaSetController) syncReplicaSet(key string) error { } rs, err := rsc.rsLister.ReplicaSets(namespace).Get(name) if errors.IsNotFound(err) { - glog.V(4).Infof("ReplicaSet has been deleted %v", key) + glog.V(4).Infof("%v %v has been deleted", rsc.Kind, key) rsc.expectations.DeleteExpectations(key) return nil } @@ -606,22 +611,10 @@ func (rsc *ReplicaSetController) syncReplicaSet(key string) error { filteredPods = append(filteredPods, pod) } } - // If any adoptions are attempted, we should first recheck for deletion with - // an uncached quorum read sometime after listing Pods (see #42639). - canAdoptFunc := controller.RecheckDeletionTimestamp(func() (metav1.Object, error) { - fresh, err := rsc.kubeClient.ExtensionsV1beta1().ReplicaSets(rs.Namespace).Get(rs.Name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - if fresh.UID != rs.UID { - return nil, fmt.Errorf("original ReplicaSet %v/%v is gone: got uid %v, wanted %v", rs.Namespace, rs.Name, fresh.UID, rs.UID) - } - return fresh, nil - }) - cm := controller.NewPodControllerRefManager(rsc.podControl, rs, selector, controllerKind, canAdoptFunc) + // NOTE: filteredPods are pointing to objects from cache - if you need to // modify them, you need to copy it first. - filteredPods, err = cm.ClaimPods(filteredPods) + filteredPods, err = rsc.claimPods(rs, selector, filteredPods) if err != nil { return err } @@ -630,13 +623,11 @@ func (rsc *ReplicaSetController) syncReplicaSet(key string) error { if rsNeedsSync && rs.DeletionTimestamp == nil { manageReplicasErr = rsc.manageReplicas(filteredPods, rs) } - rs = rs.DeepCopy() - newStatus := calculateStatus(rs, filteredPods, manageReplicasErr) // Always updates status as pods come up or die. - updatedRS, err := updateReplicaSetStatus(rsc.kubeClient.Extensions().ReplicaSets(rs.Namespace), rs, newStatus) + updatedRS, err := updateReplicaSetStatus(rsc.kubeClient.ExtensionsV1beta1().ReplicaSets(rs.Namespace), rs, newStatus) if err != nil { // Multiple things could lead to this update failing. Requeuing the replica set ensures // Returning an error causes a requeue without forcing a hotloop @@ -650,3 +641,77 @@ func (rsc *ReplicaSetController) syncReplicaSet(key string) error { } return manageReplicasErr } + +func (rsc *ReplicaSetController) claimPods(rs *extensions.ReplicaSet, selector labels.Selector, filteredPods []*v1.Pod) ([]*v1.Pod, error) { + // If any adoptions are attempted, we should first recheck for deletion with + // an uncached quorum read sometime after listing Pods (see #42639). + canAdoptFunc := controller.RecheckDeletionTimestamp(func() (metav1.Object, error) { + fresh, err := rsc.kubeClient.ExtensionsV1beta1().ReplicaSets(rs.Namespace).Get(rs.Name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + if fresh.UID != rs.UID { + return nil, fmt.Errorf("original %v %v/%v is gone: got uid %v, wanted %v", rsc.Kind, rs.Namespace, rs.Name, fresh.UID, rs.UID) + } + return fresh, nil + }) + cm := controller.NewPodControllerRefManager(rsc.podControl, rs, selector, rsc.GroupVersionKind, canAdoptFunc) + return cm.ClaimPods(filteredPods) +} + +// slowStartBatch tries to call the provided function a total of 'count' times, +// starting slow to check for errors, then speeding up if calls succeed. +// +// It groups the calls into batches, starting with a group of initialBatchSize. +// Within each batch, it may call the function multiple times concurrently. +// +// If a whole batch succeeds, the next batch may get exponentially larger. +// If there are any failures in a batch, all remaining batches are skipped +// after waiting for the current batch to complete. +// +// It returns the number of successful calls to the function. +func slowStartBatch(count int, initialBatchSize int, fn func() error) (int, error) { + remaining := count + successes := 0 + for batchSize := integer.IntMin(remaining, initialBatchSize); batchSize > 0; batchSize = integer.IntMin(2*batchSize, remaining) { + errCh := make(chan error, batchSize) + var wg sync.WaitGroup + wg.Add(batchSize) + for i := 0; i < batchSize; i++ { + go func() { + defer wg.Done() + if err := fn(); err != nil { + errCh <- err + } + }() + } + wg.Wait() + curSuccesses := batchSize - len(errCh) + successes += curSuccesses + if len(errCh) > 0 { + return successes, <-errCh + } + remaining -= batchSize + } + return successes, nil +} + +func getPodsToDelete(filteredPods []*v1.Pod, diff int) []*v1.Pod { + // No need to sort pods if we are about to delete all of them. + // diff will always be <= len(filteredPods), so not need to handle > case. + if diff < len(filteredPods) { + // Sort the pods in the order such that not-ready < ready, unscheduled + // < scheduled, and pending < running. This ensures that we delete pods + // in the earlier stages whenever possible. + sort.Sort(controller.ActivePods(filteredPods)) + } + return filteredPods[:diff] +} + +func getPodKeys(pods []*v1.Pod) []string { + podKeys := make([]string, 0, len(pods)) + for _, pod := range pods { + podKeys = append(podKeys, controller.PodKey(pod)) + } + return podKeys +} diff --git a/pkg/controller/replicaset/replica_set_test.go b/pkg/controller/replicaset/replica_set_test.go index 88ca66d2f66..da71ea727b6 100644 --- a/pkg/controller/replicaset/replica_set_test.go +++ b/pkg/controller/replicaset/replica_set_test.go @@ -14,8 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// If you make changes to this file, you should also make the corresponding change in ReplicationController. - package replicaset import ( @@ -26,6 +24,7 @@ import ( "net/url" "reflect" "strings" + "sync" "testing" "time" @@ -47,7 +46,7 @@ import ( "k8s.io/client-go/tools/cache" utiltesting "k8s.io/client-go/util/testing" "k8s.io/client-go/util/workqueue" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/securitycontext" ) @@ -91,7 +90,7 @@ func getKey(rs *extensions.ReplicaSet, t *testing.T) string { func newReplicaSet(replicas int, selectorMap map[string]string) *extensions.ReplicaSet { rs := &extensions.ReplicaSet{ - TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, + TypeMeta: metav1.TypeMeta{APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ UID: uuid.NewUUID(), Name: "foobar", @@ -209,7 +208,7 @@ func replicaSetResourceName() string { } func TestSyncReplicaSetDoesNothing(t *testing.T) { - client := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + client := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) fakePodControl := controller.FakePodControl{} stopCh := make(chan struct{}) defer close(stopCh) @@ -227,7 +226,7 @@ func TestSyncReplicaSetDoesNothing(t *testing.T) { } func TestDeleteFinalStateUnknown(t *testing.T) { - client := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + client := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) fakePodControl := controller.FakePodControl{} stopCh := make(chan struct{}) defer close(stopCh) @@ -298,7 +297,7 @@ func TestSyncReplicaSetDormancy(t *testing.T) { } testServer := httptest.NewServer(&fakeHandler) defer testServer.Close() - client := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + client := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) fakePodControl := controller.FakePodControl{} stopCh := make(chan struct{}) @@ -359,7 +358,7 @@ func TestSyncReplicaSetDormancy(t *testing.T) { func TestPodControllerLookup(t *testing.T) { stopCh := make(chan struct{}) defer close(stopCh) - manager, informers := testNewReplicaSetControllerFromClient(clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}), stopCh, BurstReplicas) + manager, informers := testNewReplicaSetControllerFromClient(clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}), stopCh, BurstReplicas) testCases := []struct { inRSs []*extensions.ReplicaSet pod *v1.Pod @@ -887,7 +886,7 @@ func (fe FakeRSExpectations) SatisfiedExpectations(controllerKey string) bool { // TestRSSyncExpectations tests that a pod cannot sneak in between counting active pods // and checking expectations. func TestRSSyncExpectations(t *testing.T) { - client := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + client := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) fakePodControl := controller.FakePodControl{} stopCh := make(chan struct{}) defer close(stopCh) @@ -968,7 +967,7 @@ func shuffle(controllers []*extensions.ReplicaSet) []*extensions.ReplicaSet { } func TestOverlappingRSs(t *testing.T) { - client := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + client := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) labelMap := map[string]string{"foo": "bar"} stopCh := make(chan struct{}) @@ -1011,7 +1010,7 @@ func TestOverlappingRSs(t *testing.T) { } func TestDeletionTimestamp(t *testing.T) { - c := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + c := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) labelMap := map[string]string{"foo": "bar"} stopCh := make(chan struct{}) defer close(stopCh) @@ -1373,3 +1372,254 @@ func TestRemoveCondition(t *testing.T) { } } } + +func TestSlowStartBatch(t *testing.T) { + fakeErr := fmt.Errorf("fake error") + callCnt := 0 + callLimit := 0 + var lock sync.Mutex + fn := func() error { + lock.Lock() + defer lock.Unlock() + callCnt++ + if callCnt > callLimit { + return fakeErr + } + return nil + } + + tests := []struct { + name string + count int + callLimit int + fn func() error + expectedSuccesses int + expectedErr error + expectedCallCnt int + }{ + { + name: "callLimit = 0 (all fail)", + count: 10, + callLimit: 0, + fn: fn, + expectedSuccesses: 0, + expectedErr: fakeErr, + expectedCallCnt: 1, // 1(first batch): function will be called at least once + }, + { + name: "callLimit = count (all succeed)", + count: 10, + callLimit: 10, + fn: fn, + expectedSuccesses: 10, + expectedErr: nil, + expectedCallCnt: 10, // 1(first batch) + 2(2nd batch) + 4(3rd batch) + 3(4th batch) = 10 + }, + { + name: "callLimit < count (some succeed)", + count: 10, + callLimit: 5, + fn: fn, + expectedSuccesses: 5, + expectedErr: fakeErr, + expectedCallCnt: 7, // 1(first batch) + 2(2nd batch) + 4(3rd batch) = 7 + }, + } + + for _, test := range tests { + callCnt = 0 + callLimit = test.callLimit + successes, err := slowStartBatch(test.count, 1, test.fn) + if successes != test.expectedSuccesses { + t.Errorf("%s: unexpected processed batch size, expected %d, got %d", test.name, test.expectedSuccesses, successes) + } + if err != test.expectedErr { + t.Errorf("%s: unexpected processed batch size, expected %v, got %v", test.name, test.expectedErr, err) + } + // verify that slowStartBatch stops trying more calls after a batch fails + if callCnt != test.expectedCallCnt { + t.Errorf("%s: slowStartBatch() still tries calls after a batch fails, expected %d calls, got %d", test.name, test.expectedCallCnt, callCnt) + } + } +} + +func TestGetPodsToDelete(t *testing.T) { + labelMap := map[string]string{"name": "foo"} + rs := newReplicaSet(1, labelMap) + // an unscheduled, pending pod + unscheduledPendingPod := newPod("unscheduled-pending-pod", rs, v1.PodPending, nil, true) + // a scheduled, pending pod + scheduledPendingPod := newPod("scheduled-pending-pod", rs, v1.PodPending, nil, true) + scheduledPendingPod.Spec.NodeName = "fake-node" + // a scheduled, running, not-ready pod + scheduledRunningNotReadyPod := newPod("scheduled-running-not-ready-pod", rs, v1.PodRunning, nil, true) + scheduledRunningNotReadyPod.Spec.NodeName = "fake-node" + scheduledRunningNotReadyPod.Status.Conditions = []v1.PodCondition{ + { + Type: v1.PodReady, + Status: v1.ConditionFalse, + }, + } + // a scheduled, running, ready pod + scheduledRunningReadyPod := newPod("scheduled-running-ready-pod", rs, v1.PodRunning, nil, true) + scheduledRunningReadyPod.Spec.NodeName = "fake-node" + scheduledRunningReadyPod.Status.Conditions = []v1.PodCondition{ + { + Type: v1.PodReady, + Status: v1.ConditionTrue, + }, + } + + tests := []struct { + name string + pods []*v1.Pod + diff int + expectedPodsToDelete []*v1.Pod + }{ + // Order used when selecting pods for deletion: + // an unscheduled, pending pod + // a scheduled, pending pod + // a scheduled, running, not-ready pod + // a scheduled, running, ready pod + // Note that a pending pod cannot be ready + { + "len(pods) = 0 (i.e., diff = 0 too)", + []*v1.Pod{}, + 0, + []*v1.Pod{}, + }, + { + "diff = len(pods)", + []*v1.Pod{ + scheduledRunningNotReadyPod, + scheduledRunningReadyPod, + }, + 2, + []*v1.Pod{scheduledRunningNotReadyPod, scheduledRunningReadyPod}, + }, + { + "diff < len(pods)", + []*v1.Pod{ + scheduledRunningReadyPod, + scheduledRunningNotReadyPod, + }, + 1, + []*v1.Pod{scheduledRunningNotReadyPod}, + }, + { + "various pod phases and conditions, diff = len(pods)", + []*v1.Pod{ + scheduledRunningReadyPod, + scheduledRunningNotReadyPod, + scheduledPendingPod, + unscheduledPendingPod, + }, + 4, + []*v1.Pod{ + scheduledRunningReadyPod, + scheduledRunningNotReadyPod, + scheduledPendingPod, + unscheduledPendingPod, + }, + }, + { + "scheduled vs unscheduled, diff < len(pods)", + []*v1.Pod{ + scheduledPendingPod, + unscheduledPendingPod, + }, + 1, + []*v1.Pod{ + unscheduledPendingPod, + }, + }, + { + "ready vs not-ready, diff < len(pods)", + []*v1.Pod{ + scheduledRunningReadyPod, + scheduledRunningNotReadyPod, + scheduledRunningNotReadyPod, + }, + 2, + []*v1.Pod{ + scheduledRunningNotReadyPod, + scheduledRunningNotReadyPod, + }, + }, + { + "pending vs running, diff < len(pods)", + []*v1.Pod{ + scheduledPendingPod, + scheduledRunningNotReadyPod, + }, + 1, + []*v1.Pod{ + scheduledPendingPod, + }, + }, + { + "various pod phases and conditions, diff < len(pods)", + []*v1.Pod{ + scheduledRunningReadyPod, + scheduledRunningNotReadyPod, + scheduledPendingPod, + unscheduledPendingPod, + }, + 3, + []*v1.Pod{ + unscheduledPendingPod, + scheduledPendingPod, + scheduledRunningNotReadyPod, + }, + }, + } + + for _, test := range tests { + podsToDelete := getPodsToDelete(test.pods, test.diff) + if len(podsToDelete) != len(test.expectedPodsToDelete) { + t.Errorf("%s: unexpected pods to delete, expected %v, got %v", test.name, test.expectedPodsToDelete, podsToDelete) + } + if !reflect.DeepEqual(podsToDelete, test.expectedPodsToDelete) { + t.Errorf("%s: unexpected pods to delete, expected %v, got %v", test.name, test.expectedPodsToDelete, podsToDelete) + } + } +} + +func TestGetPodKeys(t *testing.T) { + labelMap := map[string]string{"name": "foo"} + rs := newReplicaSet(1, labelMap) + pod1 := newPod("pod1", rs, v1.PodRunning, nil, true) + pod2 := newPod("pod2", rs, v1.PodRunning, nil, true) + + tests := []struct { + name string + pods []*v1.Pod + expectedPodKeys []string + }{ + { + "len(pods) = 0 (i.e., pods = nil)", + []*v1.Pod{}, + []string{}, + }, + { + "len(pods) > 0", + []*v1.Pod{ + pod1, + pod2, + }, + []string{"default/pod1", "default/pod2"}, + }, + } + + for _, test := range tests { + podKeys := getPodKeys(test.pods) + if len(podKeys) != len(test.expectedPodKeys) { + t.Errorf("%s: unexpected keys for pods to delete, expected %v, got %v", test.name, test.expectedPodKeys, podKeys) + } + for i := 0; i < len(podKeys); i++ { + if podKeys[i] != test.expectedPodKeys[i] { + t.Errorf("%s: unexpected keys for pods to delete, expected %v, got %v", test.name, test.expectedPodKeys, podKeys) + } + } + } +} diff --git a/pkg/controller/replicaset/replica_set_utils.go b/pkg/controller/replicaset/replica_set_utils.go index ec11fd106fa..ad628da9551 100644 --- a/pkg/controller/replicaset/replica_set_utils.go +++ b/pkg/controller/replicaset/replica_set_utils.go @@ -55,7 +55,7 @@ func updateReplicaSetStatus(c unversionedextensions.ReplicaSetInterface, rs *ext var getErr, updateErr error var updatedRS *extensions.ReplicaSet for i, rs := 0, rs; ; i++ { - glog.V(4).Infof(fmt.Sprintf("Updating status for ReplicaSet: %s/%s, ", rs.Namespace, rs.Name) + + glog.V(4).Infof(fmt.Sprintf("Updating status for %v: %s/%s, ", rs.Kind, rs.Namespace, rs.Name) + fmt.Sprintf("replicas %d->%d (need %d), ", rs.Status.Replicas, newStatus.Replicas, *(rs.Spec.Replicas)) + fmt.Sprintf("fullyLabeledReplicas %d->%d, ", rs.Status.FullyLabeledReplicas, newStatus.FullyLabeledReplicas) + fmt.Sprintf("readyReplicas %d->%d, ", rs.Status.ReadyReplicas, newStatus.ReadyReplicas) + diff --git a/pkg/controller/replicaset/replica_set_utils_test.go b/pkg/controller/replicaset/replica_set_utils_test.go new file mode 100644 index 00000000000..fbde4d0b40a --- /dev/null +++ b/pkg/controller/replicaset/replica_set_utils_test.go @@ -0,0 +1,246 @@ +/* +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. +*/ + +// If you make changes to this file, you should also make the corresponding change in ReplicationController. + +package replicaset + +import ( + "fmt" + "reflect" + "testing" + + "k8s.io/api/core/v1" + extensions "k8s.io/api/extensions/v1beta1" +) + +func TestCalculateStatus(t *testing.T) { + labelMap := map[string]string{"name": "foo"} + fullLabelMap := map[string]string{"name": "foo", "type": "production"} + notFullyLabelledRS := newReplicaSet(1, labelMap) + // Set replica num to 2 for status condition testing (diff < 0, diff > 0) + fullyLabelledRS := newReplicaSet(2, fullLabelMap) + longMinReadySecondsRS := newReplicaSet(1, fullLabelMap) + longMinReadySecondsRS.Spec.MinReadySeconds = 3600 + + rsStatusTests := []struct { + name string + replicaset *extensions.ReplicaSet + filteredPods []*v1.Pod + expectedReplicaSetStatus extensions.ReplicaSetStatus + }{ + { + "1 fully labelled pod", + fullyLabelledRS, + []*v1.Pod{ + newPod("pod1", fullyLabelledRS, v1.PodRunning, nil, true), + }, + extensions.ReplicaSetStatus{ + Replicas: 1, + FullyLabeledReplicas: 1, + ReadyReplicas: 1, + AvailableReplicas: 1, + }, + }, + { + "1 not fully labelled pod", + notFullyLabelledRS, + []*v1.Pod{ + newPod("pod1", notFullyLabelledRS, v1.PodRunning, nil, true), + }, + extensions.ReplicaSetStatus{ + Replicas: 1, + FullyLabeledReplicas: 0, + ReadyReplicas: 1, + AvailableReplicas: 1, + }, + }, + { + "2 fully labelled pods", + fullyLabelledRS, + []*v1.Pod{ + newPod("pod1", fullyLabelledRS, v1.PodRunning, nil, true), + newPod("pod2", fullyLabelledRS, v1.PodRunning, nil, true), + }, + extensions.ReplicaSetStatus{ + Replicas: 2, + FullyLabeledReplicas: 2, + ReadyReplicas: 2, + AvailableReplicas: 2, + }, + }, + { + "2 not fully labelled pods", + notFullyLabelledRS, + []*v1.Pod{ + newPod("pod1", notFullyLabelledRS, v1.PodRunning, nil, true), + newPod("pod2", notFullyLabelledRS, v1.PodRunning, nil, true), + }, + extensions.ReplicaSetStatus{ + Replicas: 2, + FullyLabeledReplicas: 0, + ReadyReplicas: 2, + AvailableReplicas: 2, + }, + }, + { + "1 fully labelled pod, 1 not fully labelled pod", + notFullyLabelledRS, + []*v1.Pod{ + newPod("pod1", notFullyLabelledRS, v1.PodRunning, nil, true), + newPod("pod2", fullyLabelledRS, v1.PodRunning, nil, true), + }, + extensions.ReplicaSetStatus{ + Replicas: 2, + FullyLabeledReplicas: 1, + ReadyReplicas: 2, + AvailableReplicas: 2, + }, + }, + { + "1 non-ready pod", + fullyLabelledRS, + []*v1.Pod{ + newPod("pod1", fullyLabelledRS, v1.PodPending, nil, true), + }, + extensions.ReplicaSetStatus{ + Replicas: 1, + FullyLabeledReplicas: 1, + ReadyReplicas: 0, + AvailableReplicas: 0, + }, + }, + { + "1 ready but non-available pod", + longMinReadySecondsRS, + []*v1.Pod{ + newPod("pod1", longMinReadySecondsRS, v1.PodRunning, nil, true), + }, + extensions.ReplicaSetStatus{ + Replicas: 1, + FullyLabeledReplicas: 1, + ReadyReplicas: 1, + AvailableReplicas: 0, + }, + }, + } + + for _, test := range rsStatusTests { + replicaSetStatus := calculateStatus(test.replicaset, test.filteredPods, nil) + if !reflect.DeepEqual(replicaSetStatus, test.expectedReplicaSetStatus) { + t.Errorf("%s: unexpected replicaset status: expected %v, got %v", test.name, test.expectedReplicaSetStatus, replicaSetStatus) + } + } +} + +func TestCalculateStatusConditions(t *testing.T) { + labelMap := map[string]string{"name": "foo"} + rs := newReplicaSet(2, labelMap) + replicaFailureRS := newReplicaSet(10, labelMap) + replicaFailureRS.Status.Conditions = []extensions.ReplicaSetCondition{ + { + Type: extensions.ReplicaSetReplicaFailure, + Status: v1.ConditionTrue, + }, + } + + rsStatusConditionTests := []struct { + name string + replicaset *extensions.ReplicaSet + filteredPods []*v1.Pod + manageReplicasErr error + expectedReplicaSetConditions []extensions.ReplicaSetCondition + }{ + + { + "manageReplicasErr != nil && failureCond == nil, diff < 0", + rs, + []*v1.Pod{ + newPod("pod1", rs, v1.PodRunning, nil, true), + }, + fmt.Errorf("fake manageReplicasErr"), + []extensions.ReplicaSetCondition{ + { + Type: extensions.ReplicaSetReplicaFailure, + Status: v1.ConditionTrue, + Reason: "FailedCreate", + Message: "fake manageReplicasErr", + }, + }, + }, + { + "manageReplicasErr != nil && failureCond == nil, diff > 0", + rs, + []*v1.Pod{ + newPod("pod1", rs, v1.PodRunning, nil, true), + newPod("pod2", rs, v1.PodRunning, nil, true), + newPod("pod3", rs, v1.PodRunning, nil, true), + }, + fmt.Errorf("fake manageReplicasErr"), + []extensions.ReplicaSetCondition{ + { + Type: extensions.ReplicaSetReplicaFailure, + Status: v1.ConditionTrue, + Reason: "FailedDelete", + Message: "fake manageReplicasErr", + }, + }, + }, + { + "manageReplicasErr == nil && failureCond != nil", + replicaFailureRS, + []*v1.Pod{ + newPod("pod1", replicaFailureRS, v1.PodRunning, nil, true), + }, + nil, + nil, + }, + { + "manageReplicasErr != nil && failureCond != nil", + replicaFailureRS, + []*v1.Pod{ + newPod("pod1", replicaFailureRS, v1.PodRunning, nil, true), + }, + fmt.Errorf("fake manageReplicasErr"), + []extensions.ReplicaSetCondition{ + { + Type: extensions.ReplicaSetReplicaFailure, + Status: v1.ConditionTrue, + }, + }, + }, + { + "manageReplicasErr == nil && failureCond == nil", + rs, + []*v1.Pod{ + newPod("pod1", rs, v1.PodRunning, nil, true), + }, + nil, + nil, + }, + } + + for _, test := range rsStatusConditionTests { + replicaSetStatus := calculateStatus(test.replicaset, test.filteredPods, test.manageReplicasErr) + // all test cases have at most 1 status condition + if len(replicaSetStatus.Conditions) > 0 { + test.expectedReplicaSetConditions[0].LastTransitionTime = replicaSetStatus.Conditions[0].LastTransitionTime + } + if !reflect.DeepEqual(replicaSetStatus.Conditions, test.expectedReplicaSetConditions) { + t.Errorf("%s: unexpected replicaset status: expected %v, got %v", test.name, test.expectedReplicaSetConditions, replicaSetStatus.Conditions) + } + } +} diff --git a/pkg/controller/replication/BUILD b/pkg/controller/replication/BUILD index 9c0c18ec80a..6e9e1710be5 100644 --- a/pkg/controller/replication/BUILD +++ b/pkg/controller/replication/BUILD @@ -9,63 +9,47 @@ load( go_library( name = "go_default_library", srcs = [ + "conversion.go", "doc.go", "replication_controller.go", "replication_controller_utils.go", ], importpath = "k8s.io/kubernetes/pkg/controller/replication", deps = [ - "//pkg/api/v1/pod:go_default_library", + "//pkg/apis/core/v1:go_default_library", + "//pkg/apis/extensions:go_default_library", + "//pkg/apis/extensions/v1beta1:go_default_library", "//pkg/controller:go_default_library", - "//pkg/util/metrics:go_default_library", + "//pkg/controller/replicaset:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/apiserver/pkg/util/trace:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", "//vendor/k8s.io/client-go/informers/core/v1:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/typed/apps/v1:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/typed/apps/v1beta2:go_default_library", "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/typed/extensions/v1beta1:go_default_library", "//vendor/k8s.io/client-go/listers/core/v1:go_default_library", + "//vendor/k8s.io/client-go/listers/extensions/v1beta1:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", "//vendor/k8s.io/client-go/tools/record:go_default_library", - "//vendor/k8s.io/client-go/util/integer:go_default_library", - "//vendor/k8s.io/client-go/util/workqueue:go_default_library", ], ) go_test( name = "go_default_test", - srcs = ["replication_controller_test.go"], + srcs = ["replication_controller_utils_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/replication", - library = ":go_default_library", - deps = [ - "//pkg/api:go_default_library", - "//pkg/api/testapi:go_default_library", - "//pkg/controller:go_default_library", - "//pkg/securitycontext:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/informers:go_default_library", - "//vendor/k8s.io/client-go/informers/core/v1:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - "//vendor/k8s.io/client-go/testing:go_default_library", - "//vendor/k8s.io/client-go/tools/cache:go_default_library", - "//vendor/k8s.io/client-go/util/testing:go_default_library", - "//vendor/k8s.io/client-go/util/workqueue:go_default_library", - ], + deps = ["//vendor/k8s.io/api/core/v1:go_default_library"], ) filegroup( diff --git a/pkg/controller/replication/OWNERS b/pkg/controller/replication/OWNERS index 3fc7da4b658..1cf42254041 100755 --- a/pkg/controller/replication/OWNERS +++ b/pkg/controller/replication/OWNERS @@ -1,7 +1,9 @@ approvers: - caesarxuchao - lavalamp +- enisoc reviewers: - caesarxuchao - lavalamp - tnozicka +- enisoc diff --git a/pkg/controller/replication/conversion.go b/pkg/controller/replication/conversion.go new file mode 100644 index 00000000000..044c1b24081 --- /dev/null +++ b/pkg/controller/replication/conversion.go @@ -0,0 +1,372 @@ +/* +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. +*/ + +// This file contains adapters that convert between RC and RS, +// as if ReplicationController were an older API version of ReplicaSet. +// It allows ReplicaSetController to directly replace the old ReplicationManager, +// which was previously a manually-maintained copy-paste of RSC. + +package replication + +import ( + "errors" + "fmt" + "time" + + "k8s.io/api/core/v1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/watch" + coreinformers "k8s.io/client-go/informers/core/v1" + clientset "k8s.io/client-go/kubernetes" + appsv1 "k8s.io/client-go/kubernetes/typed/apps/v1" + appsv1beta2 "k8s.io/client-go/kubernetes/typed/apps/v1beta2" + v1client "k8s.io/client-go/kubernetes/typed/core/v1" + extensionsv1beta1client "k8s.io/client-go/kubernetes/typed/extensions/v1beta1" + v1listers "k8s.io/client-go/listers/core/v1" + extensionslisters "k8s.io/client-go/listers/extensions/v1beta1" + "k8s.io/client-go/tools/cache" + apiv1 "k8s.io/kubernetes/pkg/apis/core/v1" + "k8s.io/kubernetes/pkg/apis/extensions" + extensionsinternalv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + "k8s.io/kubernetes/pkg/controller" +) + +// informerAdapter implements ReplicaSetInformer by wrapping ReplicationControllerInformer +// and converting objects. +type informerAdapter struct { + rcInformer coreinformers.ReplicationControllerInformer +} + +func (i informerAdapter) Informer() cache.SharedIndexInformer { + return conversionInformer{i.rcInformer.Informer()} +} + +func (i informerAdapter) Lister() extensionslisters.ReplicaSetLister { + return conversionLister{i.rcInformer.Lister()} +} + +type conversionInformer struct { + cache.SharedIndexInformer +} + +func (i conversionInformer) AddEventHandler(handler cache.ResourceEventHandler) { + i.SharedIndexInformer.AddEventHandler(conversionEventHandler{handler}) +} + +func (i conversionInformer) AddEventHandlerWithResyncPeriod(handler cache.ResourceEventHandler, resyncPeriod time.Duration) { + i.SharedIndexInformer.AddEventHandlerWithResyncPeriod(conversionEventHandler{handler}, resyncPeriod) +} + +type conversionLister struct { + rcLister v1listers.ReplicationControllerLister +} + +func (l conversionLister) List(selector labels.Selector) ([]*extensionsv1beta1.ReplicaSet, error) { + rcList, err := l.rcLister.List(selector) + if err != nil { + return nil, err + } + return convertSlice(rcList) +} + +func (l conversionLister) ReplicaSets(namespace string) extensionslisters.ReplicaSetNamespaceLister { + return conversionNamespaceLister{l.rcLister.ReplicationControllers(namespace)} +} + +func (l conversionLister) GetPodReplicaSets(pod *v1.Pod) ([]*extensionsv1beta1.ReplicaSet, error) { + rcList, err := l.rcLister.GetPodControllers(pod) + if err != nil { + return nil, err + } + return convertSlice(rcList) +} + +type conversionNamespaceLister struct { + rcLister v1listers.ReplicationControllerNamespaceLister +} + +func (l conversionNamespaceLister) List(selector labels.Selector) ([]*extensionsv1beta1.ReplicaSet, error) { + rcList, err := l.rcLister.List(selector) + if err != nil { + return nil, err + } + return convertSlice(rcList) +} + +func (l conversionNamespaceLister) Get(name string) (*extensionsv1beta1.ReplicaSet, error) { + rc, err := l.rcLister.Get(name) + if err != nil { + return nil, err + } + return convertRCtoRS(rc, nil) +} + +type conversionEventHandler struct { + handler cache.ResourceEventHandler +} + +func (h conversionEventHandler) OnAdd(obj interface{}) { + rs, err := convertRCtoRS(obj.(*v1.ReplicationController), nil) + if err != nil { + utilruntime.HandleError(fmt.Errorf("dropping RC OnAdd event: can't convert object %#v to RS: %v", obj, err)) + return + } + h.handler.OnAdd(rs) +} + +func (h conversionEventHandler) OnUpdate(oldObj, newObj interface{}) { + oldRS, err := convertRCtoRS(oldObj.(*v1.ReplicationController), nil) + if err != nil { + utilruntime.HandleError(fmt.Errorf("dropping RC OnUpdate event: can't convert old object %#v to RS: %v", oldObj, err)) + return + } + newRS, err := convertRCtoRS(newObj.(*v1.ReplicationController), nil) + if err != nil { + utilruntime.HandleError(fmt.Errorf("dropping RC OnUpdate event: can't convert new object %#v to RS: %v", newObj, err)) + return + } + h.handler.OnUpdate(oldRS, newRS) +} + +func (h conversionEventHandler) OnDelete(obj interface{}) { + rc, ok := obj.(*v1.ReplicationController) + if !ok { + // Convert the Obj inside DeletedFinalStateUnknown. + tombstone, ok := obj.(cache.DeletedFinalStateUnknown) + if !ok { + utilruntime.HandleError(fmt.Errorf("dropping RC OnDelete event: couldn't get object from tombstone %+v", obj)) + return + } + rc, ok = tombstone.Obj.(*v1.ReplicationController) + if !ok { + utilruntime.HandleError(fmt.Errorf("dropping RC OnDelete event: tombstone contained object that is not a RC %#v", obj)) + return + } + rs, err := convertRCtoRS(rc, nil) + if err != nil { + utilruntime.HandleError(fmt.Errorf("dropping RC OnDelete event: can't convert object %#v to RS: %v", obj, err)) + return + } + h.handler.OnDelete(cache.DeletedFinalStateUnknown{Key: tombstone.Key, Obj: rs}) + return + } + + // It's a regular RC object. + rs, err := convertRCtoRS(rc, nil) + if err != nil { + utilruntime.HandleError(fmt.Errorf("dropping RC OnDelete event: can't convert object %#v to RS: %v", obj, err)) + return + } + h.handler.OnDelete(rs) +} + +type clientsetAdapter struct { + clientset.Interface +} + +func (c clientsetAdapter) ExtensionsV1beta1() extensionsv1beta1client.ExtensionsV1beta1Interface { + return conversionExtensionsClient{c.Interface, c.Interface.ExtensionsV1beta1()} +} + +func (c clientsetAdapter) Extensions() extensionsv1beta1client.ExtensionsV1beta1Interface { + return conversionExtensionsClient{c.Interface, c.Interface.ExtensionsV1beta1()} +} + +func (c clientsetAdapter) AppsV1beta2() appsv1beta2.AppsV1beta2Interface { + return conversionAppsV1beta2Client{c.Interface, c.Interface.AppsV1beta2()} +} + +func (c clientsetAdapter) AppsV1() appsv1.AppsV1Interface { + return conversionAppsV1Client{c.Interface, c.Interface.AppsV1()} +} + +func (c clientsetAdapter) Apps() appsv1.AppsV1Interface { + return conversionAppsV1Client{c.Interface, c.Interface.AppsV1()} +} + +type conversionAppsV1beta2Client struct { + clientset clientset.Interface + appsv1beta2.AppsV1beta2Interface +} + +func (c conversionAppsV1beta2Client) ReplicaSets(namespace string) appsv1beta2.ReplicaSetInterface { + // TODO(enisoc): This will force RC integration tests to fail if anyone tries to update + // ReplicaSetController to use apps/v1beta2 without updating this conversion adapter. + // Please change conversionClient to use the new RS version instead of extensions/v1beta1, + // and then return a conversionClient here. + panic("need to update RC/RS conversionClient for apps/v1beta2") +} + +type conversionAppsV1Client struct { + clientset clientset.Interface + appsv1.AppsV1Interface +} + +func (c conversionAppsV1Client) ReplicaSets(namespace string) appsv1.ReplicaSetInterface { + // TODO(enisoc): This will force RC integration tests to fail if anyone tries to update + // ReplicaSetController to use apps/v1 without updating this conversion adapter. + // Please change conversionClient to use the new RS version instead of extensions/v1beta1, + // and then return a conversionClient here. + panic("need to update RC/RS conversionClient for apps/v1") +} + +type conversionExtensionsClient struct { + clientset clientset.Interface + extensionsv1beta1client.ExtensionsV1beta1Interface +} + +func (c conversionExtensionsClient) ReplicaSets(namespace string) extensionsv1beta1client.ReplicaSetInterface { + return conversionClient{c.clientset.CoreV1().ReplicationControllers(namespace)} +} + +type conversionClient struct { + v1client.ReplicationControllerInterface +} + +func (c conversionClient) Create(rs *extensionsv1beta1.ReplicaSet) (*extensionsv1beta1.ReplicaSet, error) { + return convertCall(c.ReplicationControllerInterface.Create, rs) +} + +func (c conversionClient) Update(rs *extensionsv1beta1.ReplicaSet) (*extensionsv1beta1.ReplicaSet, error) { + return convertCall(c.ReplicationControllerInterface.Update, rs) +} + +func (c conversionClient) UpdateStatus(rs *extensionsv1beta1.ReplicaSet) (*extensionsv1beta1.ReplicaSet, error) { + return convertCall(c.ReplicationControllerInterface.UpdateStatus, rs) +} + +func (c conversionClient) Get(name string, options metav1.GetOptions) (*extensionsv1beta1.ReplicaSet, error) { + rc, err := c.ReplicationControllerInterface.Get(name, options) + if err != nil { + return nil, err + } + return convertRCtoRS(rc, nil) +} + +func (c conversionClient) List(opts metav1.ListOptions) (*extensionsv1beta1.ReplicaSetList, error) { + rcList, err := c.ReplicationControllerInterface.List(opts) + if err != nil { + return nil, err + } + return convertList(rcList) +} + +func (c conversionClient) Watch(opts metav1.ListOptions) (watch.Interface, error) { + // This is not used by RSC because we wrap the shared informer instead. + return nil, errors.New("Watch() is not implemented for conversionClient") +} + +func (c conversionClient) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *extensionsv1beta1.ReplicaSet, err error) { + // This is not used by RSC. + return nil, errors.New("Patch() is not implemented for conversionClient") +} + +func convertSlice(rcList []*v1.ReplicationController) ([]*extensionsv1beta1.ReplicaSet, error) { + rsList := make([]*extensionsv1beta1.ReplicaSet, 0, len(rcList)) + for _, rc := range rcList { + rs, err := convertRCtoRS(rc, nil) + if err != nil { + return nil, err + } + rsList = append(rsList, rs) + } + return rsList, nil +} + +func convertList(rcList *v1.ReplicationControllerList) (*extensionsv1beta1.ReplicaSetList, error) { + rsList := &extensionsv1beta1.ReplicaSetList{Items: make([]extensionsv1beta1.ReplicaSet, len(rcList.Items))} + for i := range rcList.Items { + rc := &rcList.Items[i] + _, err := convertRCtoRS(rc, &rsList.Items[i]) + if err != nil { + return nil, err + } + } + return rsList, nil +} + +func convertCall(fn func(*v1.ReplicationController) (*v1.ReplicationController, error), rs *extensionsv1beta1.ReplicaSet) (*extensionsv1beta1.ReplicaSet, error) { + rc, err := convertRStoRC(rs) + if err != nil { + return nil, err + } + result, err := fn(rc) + if err != nil { + return nil, err + } + return convertRCtoRS(result, nil) +} + +func convertRCtoRS(rc *v1.ReplicationController, out *extensionsv1beta1.ReplicaSet) (*extensionsv1beta1.ReplicaSet, error) { + var rsInternal extensions.ReplicaSet + if err := apiv1.Convert_v1_ReplicationController_to_extensions_ReplicaSet(rc, &rsInternal, nil); err != nil { + return nil, fmt.Errorf("can't convert ReplicationController %v/%v to ReplicaSet: %v", rc.Namespace, rc.Name, err) + } + if out == nil { + out = new(extensionsv1beta1.ReplicaSet) + } + if err := extensionsinternalv1beta1.Convert_extensions_ReplicaSet_To_v1beta1_ReplicaSet(&rsInternal, out, nil); err != nil { + return nil, fmt.Errorf("can't convert ReplicaSet (converted from ReplicationController %v/%v) from internal to extensions/v1beta1: %v", rc.Namespace, rc.Name, err) + } + return out, nil +} + +func convertRStoRC(rs *extensionsv1beta1.ReplicaSet) (*v1.ReplicationController, error) { + var rsInternal extensions.ReplicaSet + if err := extensionsinternalv1beta1.Convert_v1beta1_ReplicaSet_To_extensions_ReplicaSet(rs, &rsInternal, nil); err != nil { + return nil, fmt.Errorf("can't convert ReplicaSet (converting to ReplicationController %v/%v) from extensions/v1beta1 to internal: %v", rs.Namespace, rs.Name, err) + } + var rc v1.ReplicationController + if err := apiv1.Convert_extensions_ReplicaSet_to_v1_ReplicationController(&rsInternal, &rc, nil); err != nil { + return nil, fmt.Errorf("can't convert ReplicaSet to ReplicationController %v/%v: %v", rs.Namespace, rs.Name, err) + } + return &rc, nil +} + +type podControlAdapter struct { + controller.PodControlInterface +} + +func (pc podControlAdapter) CreatePods(namespace string, template *v1.PodTemplateSpec, object runtime.Object) error { + // This is not used by RSC. + return errors.New("CreatePods() is not implemented for podControlAdapter") +} + +func (pc podControlAdapter) CreatePodsOnNode(nodeName, namespace string, template *v1.PodTemplateSpec, object runtime.Object, controllerRef *metav1.OwnerReference) error { + // This is not used by RSC. + return errors.New("CreatePodsOnNode() is not implemented for podControlAdapter") +} + +func (pc podControlAdapter) CreatePodsWithControllerRef(namespace string, template *v1.PodTemplateSpec, object runtime.Object, controllerRef *metav1.OwnerReference) error { + rc, err := convertRStoRC(object.(*extensionsv1beta1.ReplicaSet)) + if err != nil { + return err + } + return pc.PodControlInterface.CreatePodsWithControllerRef(namespace, template, rc, controllerRef) +} + +func (pc podControlAdapter) DeletePod(namespace string, podID string, object runtime.Object) error { + rc, err := convertRStoRC(object.(*extensionsv1beta1.ReplicaSet)) + if err != nil { + return err + } + return pc.PodControlInterface.DeletePod(namespace, podID, rc) +} diff --git a/pkg/controller/replication/replication_controller.go b/pkg/controller/replication/replication_controller.go index ff032f84035..b181db0f335 100644 --- a/pkg/controller/replication/replication_controller.go +++ b/pkg/controller/replication/replication_controller.go @@ -14,653 +14,54 @@ See the License for the specific language governing permissions and limitations under the License. */ -// If you make changes to this file, you should also make the corresponding change in ReplicaSet. +// ### ATTENTION ### +// +// ReplicationManager is now just a wrapper around ReplicaSetController, +// with a conversion layer that effectively treats ReplicationController +// as if it were an older API version of ReplicaSet. +// +// However, RC and RS still have separate storage and separate instantiations +// of the ReplicaSetController object. package replication import ( - "fmt" - "reflect" - "sort" - "sync" - "time" - "github.com/golang/glog" "k8s.io/api/core/v1" - "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" - utiltrace "k8s.io/apiserver/pkg/util/trace" coreinformers "k8s.io/client-go/informers/core/v1" 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" - "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" - "k8s.io/client-go/util/integer" - "k8s.io/client-go/util/workqueue" - podutil "k8s.io/kubernetes/pkg/api/v1/pod" "k8s.io/kubernetes/pkg/controller" - "k8s.io/kubernetes/pkg/util/metrics" + "k8s.io/kubernetes/pkg/controller/replicaset" ) const ( - // Realistic value of the burstReplica field for the replication manager based off - // performance requirements for kubernetes 1.0. - BurstReplicas = 500 - - // The number of times we retry updating a replication controller's status. - statusUpdateRetries = 1 + BurstReplicas = replicaset.BurstReplicas ) -// controllerKind contains the schema.GroupVersionKind for this controller type. -var controllerKind = v1.SchemeGroupVersion.WithKind("ReplicationController") - // ReplicationManager is responsible for synchronizing ReplicationController objects stored // in the system with actual running pods. -// NOTE: using this name to distinguish this type from API object "ReplicationController"; will -// not fix it right now. Refer to #41459 for more detail. +// It is actually just a wrapper around ReplicaSetController. type ReplicationManager struct { - kubeClient clientset.Interface - podControl controller.PodControlInterface - - // An rc is temporarily suspended after creating/deleting these many replicas. - // It resumes normal action after observing the watch events for them. - burstReplicas int - // To allow injection of syncReplicationController for testing. - syncHandler func(rcKey string) error - - // A TTLCache of pod creates/deletes each rc expects to see. - expectations *controller.UIDTrackingControllerExpectations - - rcLister corelisters.ReplicationControllerLister - rcListerSynced cache.InformerSynced - - podLister corelisters.PodLister - // podListerSynced returns true if the pod store has been synced at least once. - // Added as a member to the struct to allow injection for testing. - podListerSynced cache.InformerSynced - - // Controllers that need to be synced - queue workqueue.RateLimitingInterface + replicaset.ReplicaSetController } // NewReplicationManager configures a replication manager with the specified event recorder func NewReplicationManager(podInformer coreinformers.PodInformer, rcInformer coreinformers.ReplicationControllerInformer, kubeClient clientset.Interface, burstReplicas int) *ReplicationManager { - if kubeClient != nil && kubeClient.Core().RESTClient().GetRateLimiter() != nil { - metrics.RegisterMetricAndTrackRateLimiterUsage("replication_controller", kubeClient.Core().RESTClient().GetRateLimiter()) - } - eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(glog.Infof) - eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.Core().RESTClient()).Events("")}) - - rm := &ReplicationManager{ - kubeClient: kubeClient, - podControl: controller.RealPodControl{ - KubeClient: kubeClient, - Recorder: eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "replication-controller"}), - }, - burstReplicas: burstReplicas, - expectations: controller.NewUIDTrackingControllerExpectations(controller.NewControllerExpectations()), - queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "replicationmanager"), - } - - rcInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: rm.enqueueController, - UpdateFunc: rm.updateRC, - // This will enter the sync loop and no-op, because the controller has been deleted from the store. - // Note that deleting a controller immediately after scaling it to 0 will not work. The recommended - // way of achieving this is by performing a `stop` operation on the controller. - DeleteFunc: rm.enqueueController, - }) - rm.rcLister = rcInformer.Lister() - rm.rcListerSynced = rcInformer.Informer().HasSynced - - podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: rm.addPod, - // This invokes the rc for every pod change, eg: host assignment. Though this might seem like overkill - // the most frequent pod update is status, and the associated rc will only list from local storage, so - // it should be ok. - UpdateFunc: rm.updatePod, - DeleteFunc: rm.deletePod, - }) - rm.podLister = podInformer.Lister() - rm.podListerSynced = podInformer.Informer().HasSynced - - rm.syncHandler = rm.syncReplicationController - return rm -} - -// SetEventRecorder replaces the event recorder used by the replication manager -// with the given recorder. Only used for testing. -func (rm *ReplicationManager) SetEventRecorder(recorder record.EventRecorder) { - // TODO: Hack. We can't cleanly shutdown the event recorder, so benchmarks - // need to pass in a fake. - rm.podControl = controller.RealPodControl{KubeClient: rm.kubeClient, Recorder: recorder} -} - -// Run begins watching and syncing. -func (rm *ReplicationManager) Run(workers int, stopCh <-chan struct{}) { - defer utilruntime.HandleCrash() - defer rm.queue.ShutDown() - - glog.Infof("Starting RC controller") - defer glog.Infof("Shutting down RC controller") - - if !controller.WaitForCacheSync("RC", stopCh, rm.podListerSynced, rm.rcListerSynced) { - return - } - - for i := 0; i < workers; i++ { - go wait.Until(rm.worker, time.Second, stopCh) - } - - <-stopCh -} - -// getPodControllers returns a list of ReplicationControllers matching the given pod. -func (rm *ReplicationManager) getPodControllers(pod *v1.Pod) []*v1.ReplicationController { - rcs, err := rm.rcLister.GetPodControllers(pod) - if err != nil { - return nil - } - if len(rcs) > 1 { - // ControllerRef will ensure we don't do anything crazy, but more than one - // item in this list nevertheless constitutes user error. - utilruntime.HandleError(fmt.Errorf("user error! more than one ReplicationController is selecting pods with labels: %+v", pod.Labels)) - } - return rcs -} - -// resolveControllerRef returns the controller referenced by a ControllerRef, -// or nil if the ControllerRef could not be resolved to a matching controller -// of the correct Kind. -func (rm *ReplicationManager) resolveControllerRef(namespace string, controllerRef *metav1.OwnerReference) *v1.ReplicationController { - // We can't look up by UID, so look up by Name and then verify UID. - // Don't even try to look up by Name if it's the wrong Kind. - if controllerRef.Kind != controllerKind.Kind { - return nil - } - rc, err := rm.rcLister.ReplicationControllers(namespace).Get(controllerRef.Name) - if err != nil { - return nil - } - if rc.UID != controllerRef.UID { - // The controller we found with this Name is not the same one that the - // ControllerRef points to. - return nil - } - return rc -} - -// callback when RC is updated -func (rm *ReplicationManager) updateRC(old, cur interface{}) { - oldRC := old.(*v1.ReplicationController) - curRC := cur.(*v1.ReplicationController) - - // You might imagine that we only really need to enqueue the - // controller when Spec changes, but it is safer to sync any - // time this function is triggered. That way a full informer - // resync can requeue any controllers that don't yet have pods - // but whose last attempts at creating a pod have failed (since - // we don't block on creation of pods) instead of those - // controllers stalling indefinitely. Enqueueing every time - // does result in some spurious syncs (like when Status.Replica - // is updated and the watch notification from it retriggers - // this function), but in general extra resyncs shouldn't be - // that bad as rcs that haven't met expectations yet won't - // sync, and all the listing is done using local stores. - if *(oldRC.Spec.Replicas) != *(curRC.Spec.Replicas) { - glog.V(4).Infof("Replication controller %v updated. Desired pod count change: %d->%d", curRC.Name, *(oldRC.Spec.Replicas), *(curRC.Spec.Replicas)) - } - rm.enqueueController(cur) -} - -// When a pod is created, enqueue the ReplicationController that manages it and update its expectations. -func (rm *ReplicationManager) addPod(obj interface{}) { - pod := obj.(*v1.Pod) - - if pod.DeletionTimestamp != nil { - // on a restart of the controller manager, it's possible a new pod shows up in a state that - // is already pending deletion. Prevent the pod from being a creation observation. - rm.deletePod(pod) - return - } - - // If it has a ControllerRef, that's all that matters. - if controllerRef := metav1.GetControllerOf(pod); controllerRef != nil { - rc := rm.resolveControllerRef(pod.Namespace, controllerRef) - if rc == nil { - return - } - rsKey, err := controller.KeyFunc(rc) - if err != nil { - return - } - glog.V(4).Infof("Pod %s created: %#v.", pod.Name, pod) - rm.expectations.CreationObserved(rsKey) - rm.enqueueController(rc) - return - } - - // Otherwise, it's an orphan. Get a list of all matching ReplicationControllers and sync - // them to see if anyone wants to adopt it. - // DO NOT observe creation because no controller should be waiting for an - // orphan. - rcs := rm.getPodControllers(pod) - if len(rcs) == 0 { - return - } - glog.V(4).Infof("Orphan Pod %s created: %#v.", pod.Name, pod) - for _, rc := range rcs { - rm.enqueueController(rc) + eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.CoreV1().RESTClient()).Events("")}) + return &ReplicationManager{ + *replicaset.NewBaseController(informerAdapter{rcInformer}, podInformer, clientsetAdapter{kubeClient}, burstReplicas, + v1.SchemeGroupVersion.WithKind("ReplicationController"), + "replication_controller", + "replicationmanager", + podControlAdapter{controller.RealPodControl{ + KubeClient: kubeClient, + Recorder: eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "replication-controller"}), + }}, + ), } } - -// When a pod is updated, figure out what ReplicationController/s manage it and wake them -// up. If the labels of the pod have changed we need to awaken both the old -// and new ReplicationController. old and cur must be *v1.Pod types. -func (rm *ReplicationManager) updatePod(old, cur interface{}) { - curPod := cur.(*v1.Pod) - oldPod := old.(*v1.Pod) - if curPod.ResourceVersion == oldPod.ResourceVersion { - // Periodic resync will send update events for all known pods. - // Two different versions of the same pod will always have different RVs. - return - } - - labelChanged := !reflect.DeepEqual(curPod.Labels, oldPod.Labels) - if curPod.DeletionTimestamp != nil { - // when a pod is deleted gracefully it's deletion timestamp is first modified to reflect a grace period, - // and after such time has passed, the kubelet actually deletes it from the store. We receive an update - // for modification of the deletion timestamp and expect an rc to create more replicas asap, not wait - // until the kubelet actually deletes the pod. This is different from the Phase of a pod changing, because - // an rc never initiates a phase change, and so is never asleep waiting for the same. - rm.deletePod(curPod) - if labelChanged { - // we don't need to check the oldPod.DeletionTimestamp because DeletionTimestamp cannot be unset. - rm.deletePod(oldPod) - } - return - } - - curControllerRef := metav1.GetControllerOf(curPod) - oldControllerRef := metav1.GetControllerOf(oldPod) - controllerRefChanged := !reflect.DeepEqual(curControllerRef, oldControllerRef) - if controllerRefChanged && oldControllerRef != nil { - // The ControllerRef was changed. Sync the old controller, if any. - if rc := rm.resolveControllerRef(oldPod.Namespace, oldControllerRef); rc != nil { - rm.enqueueController(rc) - } - } - - // If it has a ControllerRef, that's all that matters. - if curControllerRef != nil { - rc := rm.resolveControllerRef(curPod.Namespace, curControllerRef) - if rc == nil { - return - } - glog.V(4).Infof("Pod %s updated, objectMeta %+v -> %+v.", curPod.Name, oldPod.ObjectMeta, curPod.ObjectMeta) - rm.enqueueController(rc) - // TODO: MinReadySeconds in the Pod will generate an Available condition to be added in - // the Pod status which in turn will trigger a requeue of the owning ReplicationController thus - // having its status updated with the newly available replica. For now, we can fake the - // update by resyncing the controller MinReadySeconds after the it is requeued because - // a Pod transitioned to Ready. - // Note that this still suffers from #29229, we are just moving the problem one level - // "closer" to kubelet (from the deployment to the ReplicationController controller). - if !podutil.IsPodReady(oldPod) && podutil.IsPodReady(curPod) && rc.Spec.MinReadySeconds > 0 { - glog.V(2).Infof("ReplicationController %q will be enqueued after %ds for availability check", rc.Name, rc.Spec.MinReadySeconds) - // Add a second to avoid milliseconds skew in AddAfter. - // See https://github.com/kubernetes/kubernetes/issues/39785#issuecomment-279959133 for more info. - rm.enqueueControllerAfter(rc, (time.Duration(rc.Spec.MinReadySeconds)*time.Second)+time.Second) - } - return - } - - // Otherwise, it's an orphan. If anything changed, sync matching controllers - // to see if anyone wants to adopt it now. - if labelChanged || controllerRefChanged { - rcs := rm.getPodControllers(curPod) - if len(rcs) == 0 { - return - } - glog.V(4).Infof("Orphan Pod %s updated, objectMeta %+v -> %+v.", curPod.Name, oldPod.ObjectMeta, curPod.ObjectMeta) - for _, rc := range rcs { - rm.enqueueController(rc) - } - } -} - -// When a pod is deleted, enqueue the ReplicationController that manages the pod and update its expectations. -// obj could be an *v1.Pod, or a DeletionFinalStateUnknown marker item. -func (rm *ReplicationManager) deletePod(obj interface{}) { - pod, ok := obj.(*v1.Pod) - - // When a delete is dropped, the relist will notice a pod in the store not - // in the list, leading to the insertion of a tombstone object which contains - // the deleted key/value. Note that this value might be stale. If the pod - // changed labels the new ReplicationController will not be woken up till the periodic resync. - if !ok { - tombstone, ok := obj.(cache.DeletedFinalStateUnknown) - if !ok { - utilruntime.HandleError(fmt.Errorf("couldn't get object from tombstone %+v", obj)) - return - } - pod, ok = tombstone.Obj.(*v1.Pod) - if !ok { - utilruntime.HandleError(fmt.Errorf("tombstone contained object that is not a pod %#v", obj)) - return - } - } - - controllerRef := metav1.GetControllerOf(pod) - if controllerRef == nil { - // No controller should care about orphans being deleted. - return - } - rc := rm.resolveControllerRef(pod.Namespace, controllerRef) - if rc == nil { - return - } - rsKey, err := controller.KeyFunc(rc) - if err != nil { - return - } - glog.V(4).Infof("Pod %s/%s deleted through %v, timestamp %+v: %#v.", pod.Namespace, pod.Name, utilruntime.GetCaller(), pod.DeletionTimestamp, pod) - rm.expectations.DeletionObserved(rsKey, controller.PodKey(pod)) - rm.enqueueController(rc) -} - -// obj could be an *v1.ReplicationController, or a DeletionFinalStateUnknown marker item. -func (rm *ReplicationManager) enqueueController(obj interface{}) { - key, err := controller.KeyFunc(obj) - if err != nil { - utilruntime.HandleError(fmt.Errorf("couldn't get key for object %+v: %v", obj, err)) - return - } - rm.queue.Add(key) -} - -// obj could be an *v1.ReplicationController, or a DeletionFinalStateUnknown marker item. -func (rm *ReplicationManager) enqueueControllerAfter(obj interface{}, after time.Duration) { - key, err := controller.KeyFunc(obj) - if err != nil { - utilruntime.HandleError(fmt.Errorf("couldn't get key for object %+v: %v", obj, err)) - return - } - rm.queue.AddAfter(key, after) -} - -// worker 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 (rm *ReplicationManager) worker() { - for rm.processNextWorkItem() { - } - glog.Infof("replication controller worker shutting down") -} - -func (rm *ReplicationManager) processNextWorkItem() bool { - key, quit := rm.queue.Get() - if quit { - return false - } - defer rm.queue.Done(key) - - err := rm.syncHandler(key.(string)) - if err == nil { - rm.queue.Forget(key) - return true - } - - rm.queue.AddRateLimited(key) - utilruntime.HandleError(err) - return true -} - -// manageReplicas checks and updates replicas for the given replication controller. -// Does NOT modify . -func (rm *ReplicationManager) manageReplicas(filteredPods []*v1.Pod, rc *v1.ReplicationController) error { - diff := len(filteredPods) - int(*(rc.Spec.Replicas)) - rcKey, err := controller.KeyFunc(rc) - if err != nil { - return err - } - if diff == 0 { - return nil - } - - if diff < 0 { - diff *= -1 - if diff > rm.burstReplicas { - diff = rm.burstReplicas - } - // TODO: Track UIDs of creates just like deletes. The problem currently - // is we'd need to wait on the result of a create to record the pod's - // UID, which would require locking *across* the create, which will turn - // into a performance bottleneck. We should generate a UID for the pod - // beforehand and store it via ExpectCreations. - errCh := make(chan error, diff) - rm.expectations.ExpectCreations(rcKey, diff) - var wg sync.WaitGroup - glog.V(2).Infof("Too few %q/%q replicas, need %d, creating %d", rc.Namespace, rc.Name, *(rc.Spec.Replicas), diff) - // Batch the pod creates. Batch sizes start at SlowStartInitialBatchSize - // and double with each successful iteration in a kind of "slow start". - // This handles attempts to start large numbers of pods that would - // likely all fail with the same error. For example a project with a - // low quota that attempts to create a large number of pods will be - // prevented from spamming the API service with the pod create requests - // after one of its pods fails. Conveniently, this also prevents the - // event spam that those failures would generate. - for batchSize := integer.IntMin(diff, controller.SlowStartInitialBatchSize); diff > 0; batchSize = integer.IntMin(2*batchSize, diff) { - errorCount := len(errCh) - wg.Add(batchSize) - for i := 0; i < batchSize; i++ { - go func() { - defer wg.Done() - var err error - boolPtr := func(b bool) *bool { return &b } - controllerRef := &metav1.OwnerReference{ - APIVersion: controllerKind.GroupVersion().String(), - Kind: controllerKind.Kind, - Name: rc.Name, - UID: rc.UID, - BlockOwnerDeletion: boolPtr(true), - Controller: boolPtr(true), - } - err = rm.podControl.CreatePodsWithControllerRef(rc.Namespace, rc.Spec.Template, rc, controllerRef) - if err != nil && errors.IsTimeout(err) { - // Pod is created but its initialization has timed out. - // If the initialization is successful eventually, the - // controller will observe the creation via the informer. - // If the initialization fails, or if the pod keeps - // uninitialized for a long time, the informer will not - // receive any update, and the controller will create a new - // pod when the expectation expires. - return - } - if err != nil { - // Decrement the expected number of creates because the informer won't observe this pod - glog.V(2).Infof("Failed creation, decrementing expectations for controller %q/%q", rc.Namespace, rc.Name) - rm.expectations.CreationObserved(rcKey) - errCh <- err - utilruntime.HandleError(err) - } - }() - } - wg.Wait() - // any skipped pods that we never attempted to start shouldn't be expected. - skippedPods := diff - batchSize - if errorCount < len(errCh) && skippedPods > 0 { - glog.V(2).Infof("Slow-start failure. Skipping creation of %d pods, decrementing expectations for controller %q/%q", skippedPods, rc.Namespace, rc.Name) - for i := 0; i < skippedPods; i++ { - // Decrement the expected number of creates because the informer won't observe this pod - rm.expectations.CreationObserved(rcKey) - } - // The skipped pods will be retried later. The next controller resync will - // retry the slow start process. - break - } - diff -= batchSize - } - - select { - case err := <-errCh: - // all errors have been reported before and they're likely to be the same, so we'll only return the first one we hit. - if err != nil { - return err - } - default: - } - - return nil - } - - if diff > rm.burstReplicas { - diff = rm.burstReplicas - } - glog.V(2).Infof("Too many %q/%q replicas, need %d, deleting %d", rc.Namespace, rc.Name, *(rc.Spec.Replicas), diff) - // No need to sort pods if we are about to delete all of them - if *(rc.Spec.Replicas) != 0 { - // Sort the pods in the order such that not-ready < ready, unscheduled - // < scheduled, and pending < running. This ensures that we delete pods - // in the earlier stages whenever possible. - sort.Sort(controller.ActivePods(filteredPods)) - } - // Snapshot the UIDs (ns/name) of the pods we're expecting to see - // deleted, so we know to record their expectations exactly once either - // when we see it as an update of the deletion timestamp, or as a delete. - // Note that if the labels on a pod/rc change in a way that the pod gets - // orphaned, the rs will only wake up after the expectations have - // expired even if other pods are deleted. - deletedPodKeys := []string{} - for i := 0; i < diff; i++ { - deletedPodKeys = append(deletedPodKeys, controller.PodKey(filteredPods[i])) - } - // We use pod namespace/name as a UID to wait for deletions, so if the - // labels on a pod/rc change in a way that the pod gets orphaned, the - // rc will only wake up after the expectation has expired. - errCh := make(chan error, diff) - rm.expectations.ExpectDeletions(rcKey, deletedPodKeys) - var wg sync.WaitGroup - wg.Add(diff) - for i := 0; i < diff; i++ { - go func(ix int) { - defer wg.Done() - if err := rm.podControl.DeletePod(rc.Namespace, filteredPods[ix].Name, rc); err != nil { - // Decrement the expected number of deletes because the informer won't observe this deletion - podKey := controller.PodKey(filteredPods[ix]) - glog.V(2).Infof("Failed to delete %v due to %v, decrementing expectations for controller %q/%q", podKey, err, rc.Namespace, rc.Name) - rm.expectations.DeletionObserved(rcKey, podKey) - errCh <- err - utilruntime.HandleError(err) - } - }(i) - } - wg.Wait() - - select { - case err := <-errCh: - // all errors have been reported before and they're likely to be the same, so we'll only return the first one we hit. - if err != nil { - return err - } - default: - } - - return nil - -} - -// syncReplicationController will sync the rc with the given key if it has had its expectations fulfilled, meaning -// it did not expect to see any more of its pods created or deleted. This function is not meant to be invoked -// concurrently with the same key. -func (rm *ReplicationManager) syncReplicationController(key string) error { - trace := utiltrace.New("syncReplicationController: " + key) - defer trace.LogIfLong(250 * time.Millisecond) - - startTime := time.Now() - defer func() { - glog.V(4).Infof("Finished syncing controller %q (%v)", key, time.Now().Sub(startTime)) - }() - - namespace, name, err := cache.SplitMetaNamespaceKey(key) - if err != nil { - return err - } - rc, err := rm.rcLister.ReplicationControllers(namespace).Get(name) - if errors.IsNotFound(err) { - glog.Infof("Replication Controller has been deleted %v", key) - rm.expectations.DeleteExpectations(key) - return nil - } - if err != nil { - return err - } - - trace.Step("ReplicationController restored") - rcNeedsSync := rm.expectations.SatisfiedExpectations(key) - trace.Step("Expectations restored") - - // list all pods to include the pods that don't match the rc's selector - // anymore but has the stale controller ref. - // TODO: Do the List and Filter in a single pass, or use an index. - allPods, err := rm.podLister.Pods(rc.Namespace).List(labels.Everything()) - if err != nil { - return err - } - // Ignore inactive pods. - var filteredPods []*v1.Pod - for _, pod := range allPods { - if controller.IsPodActive(pod) { - filteredPods = append(filteredPods, pod) - } - } - // If any adoptions are attempted, we should first recheck for deletion with - // an uncached quorum read sometime after listing Pods (see #42639). - canAdoptFunc := controller.RecheckDeletionTimestamp(func() (metav1.Object, error) { - fresh, err := rm.kubeClient.CoreV1().ReplicationControllers(rc.Namespace).Get(rc.Name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - if fresh.UID != rc.UID { - return nil, fmt.Errorf("original ReplicationController %v/%v is gone: got uid %v, wanted %v", rc.Namespace, rc.Name, fresh.UID, rc.UID) - } - return fresh, nil - }) - cm := controller.NewPodControllerRefManager(rm.podControl, rc, labels.Set(rc.Spec.Selector).AsSelectorPreValidated(), controllerKind, canAdoptFunc) - // NOTE: filteredPods are pointing to objects from cache - if you need to - // modify them, you need to copy it first. - filteredPods, err = cm.ClaimPods(filteredPods) - if err != nil { - return err - } - - var manageReplicasErr error - if rcNeedsSync && rc.DeletionTimestamp == nil { - manageReplicasErr = rm.manageReplicas(filteredPods, rc) - } - trace.Step("manageReplicas done") - - rc = rc.DeepCopy() - - newStatus := calculateStatus(rc, filteredPods, manageReplicasErr) - - // Always updates status as pods come up or die. - updatedRC, err := updateReplicationControllerStatus(rm.kubeClient.Core().ReplicationControllers(rc.Namespace), *rc, newStatus) - if err != nil { - // Multiple things could lead to this update failing. Returning an error causes a requeue without forcing a hotloop - return err - } - // Resync the ReplicationController after MinReadySeconds as a last line of defense to guard against clock-skew. - if manageReplicasErr == nil && updatedRC.Spec.MinReadySeconds > 0 && - updatedRC.Status.ReadyReplicas == *(updatedRC.Spec.Replicas) && - updatedRC.Status.AvailableReplicas != *(updatedRC.Spec.Replicas) { - rm.enqueueControllerAfter(updatedRC, time.Duration(updatedRC.Spec.MinReadySeconds)*time.Second) - } - return manageReplicasErr -} diff --git a/pkg/controller/replication/replication_controller_test.go b/pkg/controller/replication/replication_controller_test.go deleted file mode 100644 index 45ca053ebde..00000000000 --- a/pkg/controller/replication/replication_controller_test.go +++ /dev/null @@ -1,1719 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// If you make changes to this file, you should also make the corresponding change in ReplicaSet. - -package replication - -import ( - "errors" - "fmt" - "math/rand" - "net/http/httptest" - "net/url" - "reflect" - "strings" - "testing" - "time" - - "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/util/sets" - "k8s.io/apimachinery/pkg/util/uuid" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/client-go/informers" - coreinformers "k8s.io/client-go/informers/core/v1" - clientset "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/fake" - fakeclientset "k8s.io/client-go/kubernetes/fake" - restclient "k8s.io/client-go/rest" - core "k8s.io/client-go/testing" - "k8s.io/client-go/tools/cache" - utiltesting "k8s.io/client-go/util/testing" - "k8s.io/client-go/util/workqueue" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/testapi" - "k8s.io/kubernetes/pkg/controller" - "k8s.io/kubernetes/pkg/securitycontext" -) - -var alwaysReady = func() bool { return true } - -func getKey(rc *v1.ReplicationController, t *testing.T) string { - if key, err := controller.KeyFunc(rc); err != nil { - t.Errorf("Unexpected error getting key for rc %v: %v", rc.Name, err) - return "" - } else { - return key - } -} - -func newReplicationController(replicas int) *v1.ReplicationController { - rc := &v1.ReplicationController{ - TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, - ObjectMeta: metav1.ObjectMeta{ - UID: uuid.NewUUID(), - Name: "foobar", - Namespace: metav1.NamespaceDefault, - ResourceVersion: "18", - }, - Spec: v1.ReplicationControllerSpec{ - Replicas: func() *int32 { i := int32(replicas); return &i }(), - Selector: map[string]string{"foo": "bar"}, - Template: &v1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "name": "foo", - "type": "production", - }, - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Image: "foo/bar", - TerminationMessagePath: v1.TerminationMessagePathDefault, - ImagePullPolicy: v1.PullIfNotPresent, - SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(), - }, - }, - RestartPolicy: v1.RestartPolicyAlways, - DNSPolicy: v1.DNSDefault, - NodeSelector: map[string]string{ - "baz": "blah", - }, - }, - }, - }, - } - return rc -} - -// create a pod with the given phase for the given rc (same selectors and namespace). -func newPod(name string, rc *v1.ReplicationController, status v1.PodPhase, lastTransitionTime *metav1.Time, properlyOwned bool) *v1.Pod { - var conditions []v1.PodCondition - if status == v1.PodRunning { - condition := v1.PodCondition{Type: v1.PodReady, Status: v1.ConditionTrue} - if lastTransitionTime != nil { - condition.LastTransitionTime = *lastTransitionTime - } - conditions = append(conditions, condition) - } - var controllerReference metav1.OwnerReference - if properlyOwned { - var trueVar = true - controllerReference = metav1.OwnerReference{UID: rc.UID, APIVersion: "v1beta1", Kind: "ReplicaSet", Name: rc.Name, Controller: &trueVar} - } - - return &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Labels: rc.Spec.Selector, - Namespace: rc.Namespace, - OwnerReferences: []metav1.OwnerReference{controllerReference}, - }, - Status: v1.PodStatus{Phase: status, Conditions: conditions}, - } -} - -// create count pods with the given phase for the given rc (same selectors and namespace), and add them to the store. -func newPodList(store cache.Store, count int, status v1.PodPhase, rc *v1.ReplicationController, name string) *v1.PodList { - pods := []v1.Pod{} - var trueVar = true - controllerReference := metav1.OwnerReference{UID: rc.UID, APIVersion: "v1", Kind: "ReplicationController", Name: rc.Name, Controller: &trueVar} - for i := 0; i < count; i++ { - pod := newPod(fmt.Sprintf("%s%d", name, i), rc, status, nil, false) - pod.OwnerReferences = []metav1.OwnerReference{controllerReference} - if store != nil { - store.Add(pod) - } - pods = append(pods, *pod) - } - return &v1.PodList{ - Items: pods, - } -} - -// processSync initiates a sync via processNextWorkItem() to test behavior that -// depends on both functions (such as re-queueing on sync error). -func processSync(rm *ReplicationManager, key string) error { - // Save old syncHandler and replace with one that captures the error. - oldSyncHandler := rm.syncHandler - defer func() { - rm.syncHandler = oldSyncHandler - }() - var syncErr error - rm.syncHandler = func(key string) error { - syncErr = oldSyncHandler(key) - return syncErr - } - rm.queue.Add(key) - rm.processNextWorkItem() - return syncErr -} - -func validateSyncReplication(t *testing.T, fakePodControl *controller.FakePodControl, expectedCreates, expectedDeletes, expectedPatches int) { - if e, a := expectedCreates, len(fakePodControl.Templates); e != a { - t.Errorf("Unexpected number of creates. Expected %d, saw %d\n", e, a) - } - if e, a := expectedDeletes, len(fakePodControl.DeletePodName); e != a { - t.Errorf("Unexpected number of deletes. Expected %d, saw %d\n", e, a) - } - if e, a := expectedPatches, len(fakePodControl.Patches); e != a { - t.Errorf("Unexpected number of patches. Expected %d, saw %d\n", e, a) - } -} - -func replicationControllerResourceName() string { - return "replicationcontrollers" -} - -type serverResponse struct { - statusCode int - obj interface{} -} - -func newReplicationManagerFromClient(kubeClient clientset.Interface, burstReplicas int) (*ReplicationManager, coreinformers.PodInformer, coreinformers.ReplicationControllerInformer) { - informerFactory := informers.NewSharedInformerFactory(kubeClient, controller.NoResyncPeriodFunc()) - podInformer := informerFactory.Core().V1().Pods() - rcInformer := informerFactory.Core().V1().ReplicationControllers() - rm := NewReplicationManager(podInformer, rcInformer, kubeClient, burstReplicas) - rm.podListerSynced = alwaysReady - rm.rcListerSynced = alwaysReady - return rm, podInformer, rcInformer -} - -func TestSyncReplicationControllerDoesNothing(t *testing.T) { - c := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) - fakePodControl := controller.FakePodControl{} - manager, podInformer, rcInformer := newReplicationManagerFromClient(c, BurstReplicas) - - // 2 running pods, a controller with 2 replicas, sync is a no-op - controllerSpec := newReplicationController(2) - rcInformer.Informer().GetIndexer().Add(controllerSpec) - newPodList(podInformer.Informer().GetIndexer(), 2, v1.PodRunning, controllerSpec, "pod") - - manager.podControl = &fakePodControl - manager.syncReplicationController(getKey(controllerSpec, t)) - validateSyncReplication(t, &fakePodControl, 0, 0, 0) -} - -func TestSyncReplicationControllerDeletes(t *testing.T) { - controllerSpec := newReplicationController(1) - - c := fake.NewSimpleClientset(controllerSpec) - fakePodControl := controller.FakePodControl{} - manager, podInformer, rcInformer := newReplicationManagerFromClient(c, BurstReplicas) - manager.podControl = &fakePodControl - - // 2 running pods and a controller with 1 replica, one pod delete expected - rcInformer.Informer().GetIndexer().Add(controllerSpec) - newPodList(podInformer.Informer().GetIndexer(), 2, v1.PodRunning, controllerSpec, "pod") - - err := manager.syncReplicationController(getKey(controllerSpec, t)) - if err != nil { - t.Fatalf("syncReplicationController() error: %v", err) - } - validateSyncReplication(t, &fakePodControl, 0, 1, 0) -} - -func TestDeleteFinalStateUnknown(t *testing.T) { - c := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) - fakePodControl := controller.FakePodControl{} - manager, _, rcInformer := newReplicationManagerFromClient(c, BurstReplicas) - manager.podControl = &fakePodControl - - received := make(chan string) - manager.syncHandler = func(key string) error { - received <- key - return nil - } - - // The DeletedFinalStateUnknown object should cause the rc manager to insert - // the controller matching the selectors of the deleted pod into the work queue. - controllerSpec := newReplicationController(1) - rcInformer.Informer().GetIndexer().Add(controllerSpec) - pods := newPodList(nil, 1, v1.PodRunning, controllerSpec, "pod") - manager.deletePod(cache.DeletedFinalStateUnknown{Key: "foo", Obj: &pods.Items[0]}) - - go manager.worker() - - expected := getKey(controllerSpec, t) - select { - case key := <-received: - if key != expected { - t.Errorf("Unexpected sync all for rc %v, expected %v", key, expected) - } - case <-time.After(wait.ForeverTestTimeout): - t.Errorf("Processing DeleteFinalStateUnknown took longer than expected") - } -} - -func TestSyncReplicationControllerCreates(t *testing.T) { - rc := newReplicationController(2) - c := fake.NewSimpleClientset(rc) - manager, podInformer, rcInformer := newReplicationManagerFromClient(c, BurstReplicas) - - // A controller with 2 replicas and no active pods in the store. - // Inactive pods should be ignored. 2 creates expected. - rcInformer.Informer().GetIndexer().Add(rc) - failedPod := newPod("failed-pod", rc, v1.PodFailed, nil, true) - deletedPod := newPod("deleted-pod", rc, v1.PodRunning, nil, true) - deletedPod.DeletionTimestamp = &metav1.Time{Time: time.Now()} - podInformer.Informer().GetIndexer().Add(failedPod) - podInformer.Informer().GetIndexer().Add(deletedPod) - - fakePodControl := controller.FakePodControl{} - manager.podControl = &fakePodControl - manager.syncReplicationController(getKey(rc, t)) - validateSyncReplication(t, &fakePodControl, 2, 0, 0) -} - -// Tell the controller to create 100 replicas, but simulate a limit (like a quota limit) -// of 10, and verify that the controller doesn't make 100 create calls per sync pass -func TestSyncReplicationControllerCreateFailures(t *testing.T) { - fakePodControl := controller.FakePodControl{} - fakePodControl.CreateLimit = 10 - - rc := newReplicationController(fakePodControl.CreateLimit * 10) - c := fake.NewSimpleClientset(rc) - manager, _ /*podInformer*/, rcInformer := newReplicationManagerFromClient(c, BurstReplicas) - - rcInformer.Informer().GetIndexer().Add(rc) - - manager.podControl = &fakePodControl - manager.syncReplicationController(getKey(rc, t)) - validateSyncReplication(t, &fakePodControl, fakePodControl.CreateLimit, 0, 0) - expectedLimit := 0 - for pass := uint8(0); expectedLimit <= fakePodControl.CreateLimit; pass++ { - expectedLimit += controller.SlowStartInitialBatchSize << pass - } - if fakePodControl.CreateCallCount > expectedLimit { - t.Errorf("Unexpected number of create calls. Expected <= %d, saw %d\n", fakePodControl.CreateLimit*2, fakePodControl.CreateCallCount) - } -} - -func TestStatusUpdatesWithoutReplicasChange(t *testing.T) { - // Setup a fake server to listen for requests, and run the rc manager in steady state - fakeHandler := utiltesting.FakeHandler{ - StatusCode: 200, - ResponseBody: "", - SkipRequestFn: func(verb string, url url.URL) bool { - if verb == "GET" { - // Ignore refetch to check DeletionTimestamp. - return true - } - return false - }, - } - testServer := httptest.NewServer(&fakeHandler) - defer testServer.Close() - c := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) - manager, podInformer, rcInformer := newReplicationManagerFromClient(c, BurstReplicas) - - // Steady state for the replication controller, no Status.Replicas updates expected - activePods := 5 - rc := newReplicationController(activePods) - rcInformer.Informer().GetIndexer().Add(rc) - rc.Status = v1.ReplicationControllerStatus{Replicas: int32(activePods), ReadyReplicas: int32(activePods), AvailableReplicas: int32(activePods)} - newPodList(podInformer.Informer().GetIndexer(), activePods, v1.PodRunning, rc, "pod") - - fakePodControl := controller.FakePodControl{} - manager.podControl = &fakePodControl - manager.syncReplicationController(getKey(rc, t)) - - validateSyncReplication(t, &fakePodControl, 0, 0, 0) - if fakeHandler.RequestReceived != nil { - t.Errorf("Unexpected update when pods and rcs are in a steady state") - } - - // This response body is just so we don't err out decoding the http response, all - // we care about is the request body sent below. - response := runtime.EncodeOrDie(testapi.Default.Codec(), &v1.ReplicationController{}) - fakeHandler.ResponseBody = response - - rc.Generation = rc.Generation + 1 - manager.syncReplicationController(getKey(rc, t)) - - rc.Status.ObservedGeneration = rc.Generation - updatedRc := runtime.EncodeOrDie(testapi.Default.Codec(), rc) - fakeHandler.ValidateRequest(t, testapi.Default.ResourcePath(replicationControllerResourceName(), rc.Namespace, rc.Name)+"/status", "PUT", &updatedRc) -} - -func TestControllerUpdateReplicas(t *testing.T) { - // This is a happy server just to record the PUT request we expect for status.Replicas - fakeHandler := utiltesting.FakeHandler{ - StatusCode: 200, - ResponseBody: "", - } - testServer := httptest.NewServer(&fakeHandler) - defer testServer.Close() - c := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) - manager, podInformer, rcInformer := newReplicationManagerFromClient(c, BurstReplicas) - - // Insufficient number of pods in the system, and Status.Replicas is wrong; - // Status.Replica should update to match number of pods in system, 1 new pod should be created. - rc := newReplicationController(5) - rcInformer.Informer().GetIndexer().Add(rc) - rc.Status = v1.ReplicationControllerStatus{Replicas: 2, FullyLabeledReplicas: 6, ReadyReplicas: 2, AvailableReplicas: 2, ObservedGeneration: 0} - rc.Generation = 1 - newPodList(podInformer.Informer().GetIndexer(), 2, v1.PodRunning, rc, "pod") - rcCopy := *rc - extraLabelMap := map[string]string{"foo": "bar", "extraKey": "extraValue"} - rcCopy.Spec.Selector = extraLabelMap - newPodList(podInformer.Informer().GetIndexer(), 2, v1.PodRunning, &rcCopy, "podWithExtraLabel") - - // This response body is just so we don't err out decoding the http response - response := runtime.EncodeOrDie(testapi.Default.Codec(), &v1.ReplicationController{}) - fakeHandler.ResponseBody = response - - fakePodControl := controller.FakePodControl{} - manager.podControl = &fakePodControl - - manager.syncReplicationController(getKey(rc, t)) - - // 1. Status.Replicas should go up from 2->4 even though we created 5-4=1 pod. - // 2. Status.FullyLabeledReplicas should equal to the number of pods that - // has the extra labels, i.e., 2. - // 3. Every update to the status should include the Generation of the spec. - rc.Status = v1.ReplicationControllerStatus{Replicas: 4, ReadyReplicas: 4, AvailableReplicas: 4, ObservedGeneration: 1} - - decRc := runtime.EncodeOrDie(testapi.Default.Codec(), rc) - fakeHandler.ValidateRequest(t, testapi.Default.ResourcePath(replicationControllerResourceName(), rc.Namespace, rc.Name)+"/status", "PUT", &decRc) - validateSyncReplication(t, &fakePodControl, 1, 0, 0) -} - -func TestSyncReplicationControllerDormancy(t *testing.T) { - controllerSpec := newReplicationController(2) - c := fake.NewSimpleClientset(controllerSpec) - fakePodControl := controller.FakePodControl{} - manager, podInformer, rcInformer := newReplicationManagerFromClient(c, BurstReplicas) - manager.podControl = &fakePodControl - - rcInformer.Informer().GetIndexer().Add(controllerSpec) - newPodList(podInformer.Informer().GetIndexer(), 1, v1.PodRunning, controllerSpec, "pod") - - // Creates a replica and sets expectations - controllerSpec.Status.Replicas = 1 - controllerSpec.Status.ReadyReplicas = 1 - controllerSpec.Status.AvailableReplicas = 1 - manager.syncReplicationController(getKey(controllerSpec, t)) - validateSyncReplication(t, &fakePodControl, 1, 0, 0) - - // Expectations prevents replicas but not an update on status - controllerSpec.Status.Replicas = 0 - controllerSpec.Status.ReadyReplicas = 0 - controllerSpec.Status.AvailableReplicas = 0 - fakePodControl.Clear() - manager.syncReplicationController(getKey(controllerSpec, t)) - validateSyncReplication(t, &fakePodControl, 0, 0, 0) - - // Get the key for the controller - rcKey, err := controller.KeyFunc(controllerSpec) - if err != nil { - t.Errorf("Couldn't get key for object %#v: %v", controllerSpec, err) - } - - // Lowering expectations should lead to a sync that creates a replica, however the - // fakePodControl error will prevent this, leaving expectations at 0, 0. - manager.expectations.CreationObserved(rcKey) - controllerSpec.Status.Replicas = 1 - controllerSpec.Status.ReadyReplicas = 1 - controllerSpec.Status.AvailableReplicas = 1 - fakePodControl.Clear() - fakePodControl.Err = fmt.Errorf("Fake Error") - - manager.syncReplicationController(getKey(controllerSpec, t)) - validateSyncReplication(t, &fakePodControl, 1, 0, 0) - - // This replica should not need a Lowering of expectations, since the previous create failed - fakePodControl.Clear() - fakePodControl.Err = nil - manager.syncReplicationController(getKey(controllerSpec, t)) - validateSyncReplication(t, &fakePodControl, 1, 0, 0) -} - -func TestPodControllerLookup(t *testing.T) { - manager, _, rcInformer := newReplicationManagerFromClient(clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}), BurstReplicas) - testCases := []struct { - inRCs []*v1.ReplicationController - pod *v1.Pod - outRCName string - }{ - // pods without labels don't match any rcs - { - inRCs: []*v1.ReplicationController{ - {ObjectMeta: metav1.ObjectMeta{Name: "basic"}}}, - pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: metav1.NamespaceAll}}, - outRCName: "", - }, - // Matching labels, not namespace - { - inRCs: []*v1.ReplicationController{ - { - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: v1.ReplicationControllerSpec{ - Selector: map[string]string{"foo": "bar"}, - }, - }, - }, - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo2", Namespace: "ns", Labels: map[string]string{"foo": "bar"}}}, - outRCName: "", - }, - // Matching ns and labels returns the key to the rc, not the rc name - { - inRCs: []*v1.ReplicationController{ - { - ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "ns"}, - Spec: v1.ReplicationControllerSpec{ - Selector: map[string]string{"foo": "bar"}, - }, - }, - }, - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo3", Namespace: "ns", Labels: map[string]string{"foo": "bar"}}}, - outRCName: "bar", - }, - } - for _, c := range testCases { - for _, r := range c.inRCs { - rcInformer.Informer().GetIndexer().Add(r) - } - if rcs := manager.getPodControllers(c.pod); rcs != nil { - if len(rcs) != 1 { - t.Errorf("len(rcs) = %v, want %v", len(rcs), 1) - continue - } - rc := rcs[0] - if c.outRCName != rc.Name { - t.Errorf("Got controller %+v expected %+v", rc.Name, c.outRCName) - } - } else if c.outRCName != "" { - t.Errorf("Expected a controller %v pod %v, found none", c.outRCName, c.pod.Name) - } - } -} - -func TestWatchControllers(t *testing.T) { - fakeWatch := watch.NewFake() - c := &fake.Clientset{} - c.AddWatchReactor("replicationcontrollers", core.DefaultWatchReactor(fakeWatch, nil)) - stopCh := make(chan struct{}) - defer close(stopCh) - informers := informers.NewSharedInformerFactory(c, controller.NoResyncPeriodFunc()) - podInformer := informers.Core().V1().Pods() - rcInformer := informers.Core().V1().ReplicationControllers() - manager := NewReplicationManager(podInformer, rcInformer, c, BurstReplicas) - informers.Start(stopCh) - - var testControllerSpec v1.ReplicationController - received := make(chan string) - - // The update sent through the fakeWatcher should make its way into the workqueue, - // and eventually into the syncHandler. The handler validates the received controller - // and closes the received channel to indicate that the test can finish. - manager.syncHandler = func(key string) error { - obj, exists, err := rcInformer.Informer().GetIndexer().GetByKey(key) - if !exists || err != nil { - t.Errorf("Expected to find controller under key %v", key) - } - controllerSpec := *obj.(*v1.ReplicationController) - if !apiequality.Semantic.DeepDerivative(controllerSpec, testControllerSpec) { - t.Errorf("Expected %#v, but got %#v", testControllerSpec, controllerSpec) - } - close(received) - return nil - } - - // Start only the rc watcher and the workqueue, send a watch event, - // and make sure it hits the sync method. - go wait.Until(manager.worker, 10*time.Millisecond, stopCh) - - testControllerSpec.Name = "foo" - fakeWatch.Add(&testControllerSpec) - - select { - case <-received: - case <-time.After(wait.ForeverTestTimeout): - t.Errorf("unexpected timeout from result channel") - } -} - -func TestWatchPods(t *testing.T) { - fakeWatch := watch.NewFake() - c := &fake.Clientset{} - c.AddWatchReactor("*", core.DefaultWatchReactor(fakeWatch, nil)) - manager, podInformer, rcInformer := newReplicationManagerFromClient(c, BurstReplicas) - - // Put one rc and one pod into the controller's stores - testControllerSpec := newReplicationController(1) - rcInformer.Informer().GetIndexer().Add(testControllerSpec) - received := make(chan string) - // The pod update sent through the fakeWatcher should figure out the managing rc and - // send it into the syncHandler. - manager.syncHandler = func(key string) error { - - obj, exists, err := rcInformer.Informer().GetIndexer().GetByKey(key) - if !exists || err != nil { - t.Errorf("Expected to find controller under key %v", key) - } - controllerSpec := obj.(*v1.ReplicationController) - if !apiequality.Semantic.DeepDerivative(controllerSpec, testControllerSpec) { - t.Errorf("\nExpected %#v,\nbut got %#v", testControllerSpec, controllerSpec) - } - close(received) - return nil - } - // Start only the pod watcher and the workqueue, send a watch event, - // and make sure it hits the sync method for the right rc. - stopCh := make(chan struct{}) - defer close(stopCh) - go podInformer.Informer().Run(stopCh) - go wait.Until(manager.worker, 10*time.Millisecond, stopCh) - - pods := newPodList(nil, 1, v1.PodRunning, testControllerSpec, "pod") - testPod := pods.Items[0] - testPod.Status.Phase = v1.PodFailed - fakeWatch.Add(&testPod) - - select { - case <-received: - case <-time.After(wait.ForeverTestTimeout): - t.Errorf("unexpected timeout from result channel") - } -} - -func TestUpdatePods(t *testing.T) { - manager, podInformer, rcInformer := newReplicationManagerFromClient(fake.NewSimpleClientset(), BurstReplicas) - - received := make(chan string) - - manager.syncHandler = func(key string) error { - obj, exists, err := rcInformer.Informer().GetIndexer().GetByKey(key) - if !exists || err != nil { - t.Errorf("Expected to find controller under key %v", key) - } - received <- obj.(*v1.ReplicationController).Name - return nil - } - - stopCh := make(chan struct{}) - defer close(stopCh) - go wait.Until(manager.worker, 10*time.Millisecond, stopCh) - - // Put 2 rcs and one pod into the controller's stores - labelMap1 := map[string]string{"foo": "bar"} - testControllerSpec1 := newReplicationController(1) - testControllerSpec1.Spec.Selector = labelMap1 - rcInformer.Informer().GetIndexer().Add(testControllerSpec1) - labelMap2 := map[string]string{"bar": "foo"} - testControllerSpec2 := *testControllerSpec1 - testControllerSpec2.Spec.Selector = labelMap2 - testControllerSpec2.Name = "barfoo" - rcInformer.Informer().GetIndexer().Add(&testControllerSpec2) - - isController := true - controllerRef1 := metav1.OwnerReference{UID: testControllerSpec1.UID, APIVersion: "v1", Kind: "ReplicationController", Name: testControllerSpec1.Name, Controller: &isController} - controllerRef2 := metav1.OwnerReference{UID: testControllerSpec2.UID, APIVersion: "v1", Kind: "ReplicationController", Name: testControllerSpec2.Name, Controller: &isController} - - // case 1: Pod with a ControllerRef - pod1 := newPodList(podInformer.Informer().GetIndexer(), 1, v1.PodRunning, testControllerSpec1, "pod").Items[0] - pod1.OwnerReferences = []metav1.OwnerReference{controllerRef1} - pod1.ResourceVersion = "1" - pod2 := pod1 - pod2.Labels = labelMap2 - pod2.ResourceVersion = "2" - manager.updatePod(&pod1, &pod2) - expected := sets.NewString(testControllerSpec1.Name) - for _, name := range expected.List() { - t.Logf("Expecting update for %+v", name) - select { - case got := <-received: - if !expected.Has(got) { - t.Errorf("Expected keys %#v got %v", expected, got) - } - case <-time.After(wait.ForeverTestTimeout): - t.Errorf("Expected update notifications for ReplicationControllers") - } - } - - // case 2: Remove ControllerRef (orphan). Expect to sync label-matching RC. - pod1 = newPodList(podInformer.Informer().GetIndexer(), 1, v1.PodRunning, testControllerSpec1, "pod").Items[0] - pod1.ResourceVersion = "1" - pod1.Labels = labelMap2 - pod1.OwnerReferences = []metav1.OwnerReference{controllerRef2} - pod2 = pod1 - pod2.OwnerReferences = nil - pod2.ResourceVersion = "2" - manager.updatePod(&pod1, &pod2) - expected = sets.NewString(testControllerSpec2.Name) - for _, name := range expected.List() { - t.Logf("Expecting update for %+v", name) - select { - case got := <-received: - if !expected.Has(got) { - t.Errorf("Expected keys %#v got %v", expected, got) - } - case <-time.After(wait.ForeverTestTimeout): - t.Errorf("Expected update notifications for ReplicationControllers") - } - } - - // case 2: Remove ControllerRef (orphan). Expect to sync both former owner and - // any label-matching RC. - pod1 = newPodList(podInformer.Informer().GetIndexer(), 1, v1.PodRunning, testControllerSpec1, "pod").Items[0] - pod1.ResourceVersion = "1" - pod1.Labels = labelMap2 - pod1.OwnerReferences = []metav1.OwnerReference{controllerRef1} - pod2 = pod1 - pod2.OwnerReferences = nil - pod2.ResourceVersion = "2" - manager.updatePod(&pod1, &pod2) - expected = sets.NewString(testControllerSpec1.Name, testControllerSpec2.Name) - for _, name := range expected.List() { - t.Logf("Expecting update for %+v", name) - select { - case got := <-received: - if !expected.Has(got) { - t.Errorf("Expected keys %#v got %v", expected, got) - } - case <-time.After(wait.ForeverTestTimeout): - t.Errorf("Expected update notifications for ReplicationControllers") - } - } - - // case 4: Keep ControllerRef, change labels. Expect to sync owning RC. - pod1 = newPodList(podInformer.Informer().GetIndexer(), 1, v1.PodRunning, testControllerSpec1, "pod").Items[0] - pod1.ResourceVersion = "1" - pod1.Labels = labelMap1 - pod1.OwnerReferences = []metav1.OwnerReference{controllerRef2} - pod2 = pod1 - pod2.Labels = labelMap2 - pod2.ResourceVersion = "2" - manager.updatePod(&pod1, &pod2) - expected = sets.NewString(testControllerSpec2.Name) - for _, name := range expected.List() { - t.Logf("Expecting update for %+v", name) - select { - case got := <-received: - if !expected.Has(got) { - t.Errorf("Expected keys %#v got %v", expected, got) - } - case <-time.After(wait.ForeverTestTimeout): - t.Errorf("Expected update notifications for ReplicationControllers") - } - } -} - -func TestControllerUpdateRequeue(t *testing.T) { - // This server should force a requeue of the controller because it fails to update status.Replicas. - rc := newReplicationController(1) - c := fake.NewSimpleClientset(rc) - c.PrependReactor("update", "replicationcontrollers", - func(action core.Action) (bool, runtime.Object, error) { - if action.GetSubresource() != "status" { - return false, nil, nil - } - return true, nil, errors.New("failed to update status") - }) - manager, podInformer, rcInformer := newReplicationManagerFromClient(c, BurstReplicas) - - rcInformer.Informer().GetIndexer().Add(rc) - rc.Status = v1.ReplicationControllerStatus{Replicas: 2} - newPodList(podInformer.Informer().GetIndexer(), 1, v1.PodRunning, rc, "pod") - - fakePodControl := controller.FakePodControl{} - manager.podControl = &fakePodControl - - // Enqueue once. Then process it. Disable rate-limiting for this. - manager.queue = workqueue.NewRateLimitingQueue(workqueue.NewMaxOfRateLimiter()) - manager.enqueueController(rc) - manager.processNextWorkItem() - // It should have been requeued. - if got, want := manager.queue.Len(), 1; got != want { - t.Errorf("queue.Len() = %v, want %v", got, want) - } -} - -func TestControllerUpdateStatusWithFailure(t *testing.T) { - rc := newReplicationController(1) - c := &fake.Clientset{} - c.AddReactor("get", "replicationcontrollers", func(action core.Action) (bool, runtime.Object, error) { - return true, rc, nil - }) - c.AddReactor("*", "*", func(action core.Action) (bool, runtime.Object, error) { - return true, &v1.ReplicationController{}, fmt.Errorf("Fake error") - }) - fakeRCClient := c.Core().ReplicationControllers("default") - numReplicas := int32(10) - status := v1.ReplicationControllerStatus{Replicas: numReplicas} - updateReplicationControllerStatus(fakeRCClient, *rc, status) - updates, gets := 0, 0 - for _, a := range c.Actions() { - if a.GetResource().Resource != "replicationcontrollers" { - t.Errorf("Unexpected action %+v", a) - continue - } - - switch action := a.(type) { - case core.GetAction: - gets++ - // Make sure the get is for the right rc even though the update failed. - if action.GetName() != rc.Name { - t.Errorf("Expected get for rc %v, got %+v instead", rc.Name, action.GetName()) - } - case core.UpdateAction: - updates++ - // Confirm that the update has the right status.Replicas even though the Get - // returned an rc with replicas=1. - if c, ok := action.GetObject().(*v1.ReplicationController); !ok { - t.Errorf("Expected an rc as the argument to update, got %T", c) - } else if c.Status.Replicas != numReplicas { - t.Errorf("Expected update for rc to contain replicas %v, got %v instead", - numReplicas, c.Status.Replicas) - } - default: - t.Errorf("Unexpected action %+v", a) - break - } - } - if gets != 1 || updates != 2 { - t.Errorf("Expected 1 get and 2 updates, got %d gets %d updates", gets, updates) - } -} - -// TODO: This test is too hairy for a unittest. It should be moved to an E2E suite. -func doTestControllerBurstReplicas(t *testing.T, burstReplicas, numReplicas int) { - controllerSpec := newReplicationController(numReplicas) - c := fake.NewSimpleClientset(controllerSpec) - fakePodControl := controller.FakePodControl{} - manager, podInformer, rcInformer := newReplicationManagerFromClient(c, burstReplicas) - manager.podControl = &fakePodControl - - rcInformer.Informer().GetIndexer().Add(controllerSpec) - - expectedPods := 0 - pods := newPodList(nil, numReplicas, v1.PodPending, controllerSpec, "pod") - - rcKey, err := controller.KeyFunc(controllerSpec) - if err != nil { - t.Errorf("Couldn't get key for object %#v: %v", controllerSpec, err) - } - - // Size up the controller, then size it down, and confirm the expected create/delete pattern - for _, replicas := range []int{numReplicas, 0} { - - *(controllerSpec.Spec.Replicas) = int32(replicas) - rcInformer.Informer().GetIndexer().Add(controllerSpec) - - for i := 0; i < numReplicas; i += burstReplicas { - manager.syncReplicationController(getKey(controllerSpec, t)) - - // The store accrues active pods. It's also used by the rc to determine how many - // replicas to create. - activePods := len(podInformer.Informer().GetIndexer().List()) - if replicas != 0 { - // This is the number of pods currently "in flight". They were created by the rc manager above, - // which then puts the rc to sleep till all of them have been observed. - expectedPods = replicas - activePods - if expectedPods > burstReplicas { - expectedPods = burstReplicas - } - // This validates the rc manager sync actually created pods - validateSyncReplication(t, &fakePodControl, expectedPods, 0, 0) - - // This simulates the watch events for all but 1 of the expected pods. - // None of these should wake the controller because it has expectations==BurstReplicas. - for i := 0; i < expectedPods-1; i++ { - podInformer.Informer().GetIndexer().Add(&pods.Items[i]) - manager.addPod(&pods.Items[i]) - } - - podExp, exists, err := manager.expectations.GetExpectations(rcKey) - if !exists || err != nil { - t.Fatalf("Did not find expectations for rc.") - } - if add, _ := podExp.GetExpectations(); add != 1 { - t.Fatalf("Expectations are wrong %v", podExp) - } - } else { - expectedPods = (replicas - activePods) * -1 - if expectedPods > burstReplicas { - expectedPods = burstReplicas - } - validateSyncReplication(t, &fakePodControl, 0, expectedPods, 0) - - // To accurately simulate a watch we must delete the exact pods - // the rc is waiting for. - expectedDels := manager.expectations.GetUIDs(getKey(controllerSpec, t)) - podsToDelete := []*v1.Pod{} - isController := true - for _, key := range expectedDels.List() { - nsName := strings.Split(key, "/") - podsToDelete = append(podsToDelete, &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: nsName[1], - Namespace: nsName[0], - Labels: controllerSpec.Spec.Selector, - OwnerReferences: []metav1.OwnerReference{ - {UID: controllerSpec.UID, APIVersion: "v1", Kind: "ReplicationController", Name: controllerSpec.Name, Controller: &isController}, - }, - }, - }) - } - // Don't delete all pods because we confirm that the last pod - // has exactly one expectation at the end, to verify that we - // don't double delete. - for i := range podsToDelete[1:] { - podInformer.Informer().GetIndexer().Delete(podsToDelete[i]) - manager.deletePod(podsToDelete[i]) - } - podExp, exists, err := manager.expectations.GetExpectations(rcKey) - if !exists || err != nil { - t.Fatalf("Did not find expectations for rc.") - } - if _, del := podExp.GetExpectations(); del != 1 { - t.Fatalf("Expectations are wrong %v", podExp) - } - } - - // Check that the rc didn't take any action for all the above pods - fakePodControl.Clear() - manager.syncReplicationController(getKey(controllerSpec, t)) - validateSyncReplication(t, &fakePodControl, 0, 0, 0) - - // Create/Delete the last pod - // The last add pod will decrease the expectation of the rc to 0, - // which will cause it to create/delete the remaining replicas up to burstReplicas. - if replicas != 0 { - podInformer.Informer().GetIndexer().Add(&pods.Items[expectedPods-1]) - manager.addPod(&pods.Items[expectedPods-1]) - } else { - expectedDel := manager.expectations.GetUIDs(getKey(controllerSpec, t)) - if expectedDel.Len() != 1 { - t.Fatalf("Waiting on unexpected number of deletes.") - } - nsName := strings.Split(expectedDel.List()[0], "/") - isController := true - lastPod := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: nsName[1], - Namespace: nsName[0], - Labels: controllerSpec.Spec.Selector, - OwnerReferences: []metav1.OwnerReference{ - {UID: controllerSpec.UID, APIVersion: "v1", Kind: "ReplicationController", Name: controllerSpec.Name, Controller: &isController}, - }, - }, - } - podInformer.Informer().GetIndexer().Delete(lastPod) - manager.deletePod(lastPod) - } - pods.Items = pods.Items[expectedPods:] - } - - // Confirm that we've created the right number of replicas - activePods := int32(len(podInformer.Informer().GetIndexer().List())) - if activePods != *(controllerSpec.Spec.Replicas) { - t.Fatalf("Unexpected number of active pods, expected %d, got %d", *(controllerSpec.Spec.Replicas), activePods) - } - // Replenish the pod list, since we cut it down sizing up - pods = newPodList(nil, replicas, v1.PodRunning, controllerSpec, "pod") - } -} - -func TestControllerBurstReplicas(t *testing.T) { - doTestControllerBurstReplicas(t, 5, 30) - doTestControllerBurstReplicas(t, 5, 12) - doTestControllerBurstReplicas(t, 3, 2) -} - -type FakeRCExpectations struct { - *controller.ControllerExpectations - satisfied bool - expSatisfied func() -} - -func (fe FakeRCExpectations) SatisfiedExpectations(controllerKey string) bool { - fe.expSatisfied() - return fe.satisfied -} - -// TestRCSyncExpectations tests that a pod cannot sneak in between counting active pods -// and checking expectations. -func TestRCSyncExpectations(t *testing.T) { - c := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) - fakePodControl := controller.FakePodControl{} - manager, podInformer, rcInformer := newReplicationManagerFromClient(c, 2) - manager.podControl = &fakePodControl - - controllerSpec := newReplicationController(2) - rcInformer.Informer().GetIndexer().Add(controllerSpec) - pods := newPodList(nil, 2, v1.PodPending, controllerSpec, "pod") - podInformer.Informer().GetIndexer().Add(&pods.Items[0]) - postExpectationsPod := pods.Items[1] - - manager.expectations = controller.NewUIDTrackingControllerExpectations(FakeRCExpectations{ - controller.NewControllerExpectations(), true, func() { - // If we check active pods before checking expectataions, the rc - // will create a new replica because it doesn't see this pod, but - // has fulfilled its expectations. - podInformer.Informer().GetIndexer().Add(&postExpectationsPod) - }, - }) - manager.syncReplicationController(getKey(controllerSpec, t)) - validateSyncReplication(t, &fakePodControl, 0, 0, 0) -} - -func TestDeleteControllerAndExpectations(t *testing.T) { - rc := newReplicationController(1) - c := fake.NewSimpleClientset(rc) - manager, podInformer, rcInformer := newReplicationManagerFromClient(c, 10) - - rcInformer.Informer().GetIndexer().Add(rc) - - fakePodControl := controller.FakePodControl{} - manager.podControl = &fakePodControl - - // This should set expectations for the rc - manager.syncReplicationController(getKey(rc, t)) - validateSyncReplication(t, &fakePodControl, 1, 0, 0) - fakePodControl.Clear() - - // Get the RC key - rcKey, err := controller.KeyFunc(rc) - if err != nil { - t.Errorf("Couldn't get key for object %#v: %v", rc, err) - } - - // This is to simulate a concurrent addPod, that has a handle on the expectations - // as the controller deletes it. - podExp, exists, err := manager.expectations.GetExpectations(rcKey) - if !exists || err != nil { - t.Errorf("No expectations found for rc") - } - rcInformer.Informer().GetIndexer().Delete(rc) - manager.syncReplicationController(getKey(rc, t)) - - if _, exists, err = manager.expectations.GetExpectations(rcKey); exists { - t.Errorf("Found expectaions, expected none since the rc has been deleted.") - } - - // This should have no effect, since we've deleted the rc. - podExp.Add(-1, 0) - podInformer.Informer().GetIndexer().Replace(make([]interface{}, 0), "0") - manager.syncReplicationController(getKey(rc, t)) - validateSyncReplication(t, &fakePodControl, 0, 0, 0) -} - -// shuffle returns a new shuffled list of container controllers. -func shuffle(controllers []*v1.ReplicationController) []*v1.ReplicationController { - numControllers := len(controllers) - randIndexes := rand.Perm(numControllers) - shuffled := make([]*v1.ReplicationController, numControllers) - for i := 0; i < numControllers; i++ { - shuffled[i] = controllers[randIndexes[i]] - } - return shuffled -} - -func TestOverlappingRCs(t *testing.T) { - c := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) - - manager, _, rcInformer := newReplicationManagerFromClient(c, 10) - - // Create 10 rcs, shuffled them randomly and insert them into the - // rc manager's store. - // All use the same CreationTimestamp since ControllerRef should be able - // to handle that. - var controllers []*v1.ReplicationController - timestamp := metav1.Date(2014, time.December, 0, 0, 0, 0, 0, time.Local) - for j := 1; j < 10; j++ { - controllerSpec := newReplicationController(1) - controllerSpec.CreationTimestamp = timestamp - controllerSpec.Name = fmt.Sprintf("rc%d", j) - controllers = append(controllers, controllerSpec) - } - shuffledControllers := shuffle(controllers) - for j := range shuffledControllers { - rcInformer.Informer().GetIndexer().Add(shuffledControllers[j]) - } - // Add a pod with a ControllerRef and make sure only the corresponding - // ReplicationController is synced. Pick a RC in the middle since the old code - // used to sort by name if all timestamps were equal. - rc := controllers[3] - pods := newPodList(nil, 1, v1.PodPending, rc, "pod") - pod := &pods.Items[0] - isController := true - pod.OwnerReferences = []metav1.OwnerReference{ - {UID: rc.UID, APIVersion: "v1", Kind: "ReplicationController", Name: rc.Name, Controller: &isController}, - } - rcKey := getKey(rc, t) - - manager.addPod(pod) - queueRC, _ := manager.queue.Get() - if queueRC != rcKey { - t.Fatalf("Expected to find key %v in queue, found %v", rcKey, queueRC) - } -} - -func TestDeletionTimestamp(t *testing.T) { - c := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) - manager, _, rcInformer := newReplicationManagerFromClient(c, 10) - - controllerSpec := newReplicationController(1) - rcInformer.Informer().GetIndexer().Add(controllerSpec) - rcKey, err := controller.KeyFunc(controllerSpec) - if err != nil { - t.Errorf("Couldn't get key for object %#v: %v", controllerSpec, err) - } - pod := newPodList(nil, 1, v1.PodPending, controllerSpec, "pod").Items[0] - pod.DeletionTimestamp = &metav1.Time{Time: time.Now()} - pod.ResourceVersion = "1" - manager.expectations.ExpectDeletions(rcKey, []string{controller.PodKey(&pod)}) - - // A pod added with a deletion timestamp should decrement deletions, not creations. - manager.addPod(&pod) - - queueRC, _ := manager.queue.Get() - if queueRC != rcKey { - t.Fatalf("Expected to find key %v in queue, found %v", rcKey, queueRC) - } - manager.queue.Done(rcKey) - - podExp, exists, err := manager.expectations.GetExpectations(rcKey) - if !exists || err != nil || !podExp.Fulfilled() { - t.Fatalf("Wrong expectations %#v", podExp) - } - - // An update from no deletion timestamp to having one should be treated - // as a deletion. - oldPod := newPodList(nil, 1, v1.PodPending, controllerSpec, "pod").Items[0] - oldPod.ResourceVersion = "2" - manager.expectations.ExpectDeletions(rcKey, []string{controller.PodKey(&pod)}) - manager.updatePod(&oldPod, &pod) - - queueRC, _ = manager.queue.Get() - if queueRC != rcKey { - t.Fatalf("Expected to find key %v in queue, found %v", rcKey, queueRC) - } - manager.queue.Done(rcKey) - - podExp, exists, err = manager.expectations.GetExpectations(rcKey) - if !exists || err != nil || !podExp.Fulfilled() { - t.Fatalf("Wrong expectations %#v", podExp) - } - - // An update to the pod (including an update to the deletion timestamp) - // should not be counted as a second delete. - isController := true - secondPod := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: pod.Namespace, - Name: "secondPod", - Labels: pod.Labels, - OwnerReferences: []metav1.OwnerReference{ - {UID: controllerSpec.UID, APIVersion: "v1", Kind: "ReplicationController", Name: controllerSpec.Name, Controller: &isController}, - }, - }, - } - manager.expectations.ExpectDeletions(rcKey, []string{controller.PodKey(secondPod)}) - oldPod.DeletionTimestamp = &metav1.Time{Time: time.Now()} - oldPod.ResourceVersion = "2" - manager.updatePod(&oldPod, &pod) - - podExp, exists, err = manager.expectations.GetExpectations(rcKey) - if !exists || err != nil || podExp.Fulfilled() { - t.Fatalf("Wrong expectations %#v", podExp) - } - - // A pod with a non-nil deletion timestamp should also be ignored by the - // delete handler, because it's already been counted in the update. - manager.deletePod(&pod) - podExp, exists, err = manager.expectations.GetExpectations(rcKey) - if !exists || err != nil || podExp.Fulfilled() { - t.Fatalf("Wrong expectations %#v", podExp) - } - - // Deleting the second pod should clear expectations. - manager.deletePod(secondPod) - - queueRC, _ = manager.queue.Get() - if queueRC != rcKey { - t.Fatalf("Expected to find key %v in queue, found %v", rcKey, queueRC) - } - manager.queue.Done(rcKey) - - podExp, exists, err = manager.expectations.GetExpectations(rcKey) - if !exists || err != nil || !podExp.Fulfilled() { - t.Fatalf("Wrong expectations %#v", podExp) - } -} - -func BenchmarkGetPodControllerMultiNS(b *testing.B) { - client := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) - manager, _, rcInformer := newReplicationManagerFromClient(client, BurstReplicas) - - const nsNum = 1000 - - pods := []v1.Pod{} - for i := 0; i < nsNum; i++ { - ns := fmt.Sprintf("ns-%d", i) - for j := 0; j < 10; j++ { - rcName := fmt.Sprintf("rc-%d", j) - for k := 0; k < 10; k++ { - podName := fmt.Sprintf("pod-%d-%d", j, k) - pods = append(pods, v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: podName, - Namespace: ns, - Labels: map[string]string{"rcName": rcName}, - }, - }) - } - } - } - - for i := 0; i < nsNum; i++ { - ns := fmt.Sprintf("ns-%d", i) - for j := 0; j < 10; j++ { - rcName := fmt.Sprintf("rc-%d", j) - rcInformer.Informer().GetIndexer().Add(&v1.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{Name: rcName, Namespace: ns}, - Spec: v1.ReplicationControllerSpec{ - Selector: map[string]string{"rcName": rcName}, - }, - }) - } - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - for _, pod := range pods { - manager.getPodControllers(&pod) - } - } -} - -func BenchmarkGetPodControllerSingleNS(b *testing.B) { - client := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) - manager, _, rcInformer := newReplicationManagerFromClient(client, BurstReplicas) - - const rcNum = 1000 - const replicaNum = 3 - - pods := []v1.Pod{} - for i := 0; i < rcNum; i++ { - rcName := fmt.Sprintf("rc-%d", i) - for j := 0; j < replicaNum; j++ { - podName := fmt.Sprintf("pod-%d-%d", i, j) - pods = append(pods, v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: podName, - Namespace: "foo", - Labels: map[string]string{"rcName": rcName}, - }, - }) - } - } - - for i := 0; i < rcNum; i++ { - rcName := fmt.Sprintf("rc-%d", i) - rcInformer.Informer().GetIndexer().Add(&v1.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{Name: rcName, Namespace: "foo"}, - Spec: v1.ReplicationControllerSpec{ - Selector: map[string]string{"rcName": rcName}, - }, - }) - } - b.ResetTimer() - - for i := 0; i < b.N; i++ { - for _, pod := range pods { - manager.getPodControllers(&pod) - } - } -} - -// setupManagerWithGCEnabled creates a RC manager with a fakePodControl -func setupManagerWithGCEnabled(objs ...runtime.Object) (manager *ReplicationManager, fakePodControl *controller.FakePodControl, podInformer coreinformers.PodInformer, rcInformer coreinformers.ReplicationControllerInformer) { - c := fakeclientset.NewSimpleClientset(objs...) - fakePodControl = &controller.FakePodControl{} - manager, podInformer, rcInformer = newReplicationManagerFromClient(c, BurstReplicas) - manager.podControl = fakePodControl - return manager, fakePodControl, podInformer, rcInformer -} - -func TestDoNotPatchPodWithOtherControlRef(t *testing.T) { - rc := newReplicationController(2) - manager, fakePodControl, podInformer, rcInformer := setupManagerWithGCEnabled(rc) - rcInformer.Informer().GetIndexer().Add(rc) - var trueVar = true - otherControllerReference := metav1.OwnerReference{UID: uuid.NewUUID(), APIVersion: "v1", Kind: "ReplicationController", Name: "AnotherRC", Controller: &trueVar} - // add to podLister a matching Pod controlled by another controller. Expect no patch. - pod := newPod("pod", rc, v1.PodRunning, nil, false) - pod.OwnerReferences = []metav1.OwnerReference{otherControllerReference} - podInformer.Informer().GetIndexer().Add(pod) - err := manager.syncReplicationController(getKey(rc, t)) - if err != nil { - t.Fatal(err) - } - // because the matching pod already has a controller, so 2 pods should be created. - validateSyncReplication(t, fakePodControl, 2, 0, 0) -} - -func TestPatchPodWithOtherOwnerRef(t *testing.T) { - rc := newReplicationController(2) - manager, fakePodControl, podInformer, rcInformer := setupManagerWithGCEnabled(rc) - rcInformer.Informer().GetIndexer().Add(rc) - // add to podLister one more matching pod that doesn't have a controller - // ref, but has an owner ref pointing to other object. Expect a patch to - // take control of it. - unrelatedOwnerReference := metav1.OwnerReference{UID: uuid.NewUUID(), APIVersion: "batch/v1", Kind: "Job", Name: "Job"} - pod := newPod("pod", rc, v1.PodRunning, nil, false) - pod.OwnerReferences = []metav1.OwnerReference{unrelatedOwnerReference} - podInformer.Informer().GetIndexer().Add(pod) - - err := manager.syncReplicationController(getKey(rc, t)) - if err != nil { - t.Fatal(err) - } - // 1 patch to take control of pod, and 1 create of new pod. - validateSyncReplication(t, fakePodControl, 1, 0, 1) -} - -func TestPatchPodWithCorrectOwnerRef(t *testing.T) { - rc := newReplicationController(2) - manager, fakePodControl, podInformer, rcInformer := setupManagerWithGCEnabled(rc) - rcInformer.Informer().GetIndexer().Add(rc) - // add to podLister a matching pod that has an ownerRef pointing to the rc, - // but ownerRef.Controller is false. Expect a patch to take control it. - rcOwnerReference := metav1.OwnerReference{UID: rc.UID, APIVersion: "v1", Kind: "ReplicationController", Name: rc.Name} - pod := newPod("pod", rc, v1.PodRunning, nil, false) - pod.OwnerReferences = []metav1.OwnerReference{rcOwnerReference} - podInformer.Informer().GetIndexer().Add(pod) - - err := manager.syncReplicationController(getKey(rc, t)) - if err != nil { - t.Fatal(err) - } - // 1 patch to take control of pod, and 1 create of new pod. - validateSyncReplication(t, fakePodControl, 1, 0, 1) -} - -func TestPatchPodFails(t *testing.T) { - rc := newReplicationController(2) - manager, fakePodControl, podInformer, rcInformer := setupManagerWithGCEnabled(rc) - rcInformer.Informer().GetIndexer().Add(rc) - // add to podLister two matching pods. Expect two patches to take control - // them. - podInformer.Informer().GetIndexer().Add(newPod("pod1", rc, v1.PodRunning, nil, false)) - podInformer.Informer().GetIndexer().Add(newPod("pod2", rc, v1.PodRunning, nil, false)) - // let both patches fail. The rc manager will assume it fails to take - // control of the pods and requeue to try again. - fakePodControl.Err = fmt.Errorf("Fake Error") - rcKey := getKey(rc, t) - err := processSync(manager, rcKey) - if err == nil || !strings.Contains(err.Error(), "Fake Error") { - t.Fatalf("expected Fake Error, got %v", err) - } - // 2 patches to take control of pod1 and pod2 (both fail). - validateSyncReplication(t, fakePodControl, 0, 0, 2) - // RC should requeue itself. - queueRC, _ := manager.queue.Get() - if queueRC != rcKey { - t.Fatalf("Expected to find key %v in queue, found %v", rcKey, queueRC) - } -} - -func TestPatchExtraPodsThenDelete(t *testing.T) { - rc := newReplicationController(2) - manager, fakePodControl, podInformer, rcInformer := setupManagerWithGCEnabled(rc) - rcInformer.Informer().GetIndexer().Add(rc) - // add to podLister three matching pods. Expect three patches to take control - // them, and later delete one of them. - podInformer.Informer().GetIndexer().Add(newPod("pod1", rc, v1.PodRunning, nil, false)) - podInformer.Informer().GetIndexer().Add(newPod("pod2", rc, v1.PodRunning, nil, false)) - podInformer.Informer().GetIndexer().Add(newPod("pod3", rc, v1.PodRunning, nil, false)) - err := manager.syncReplicationController(getKey(rc, t)) - if err != nil { - t.Fatal(err) - } - // 3 patches to take control of the pods, and 1 deletion because there is an extra pod. - validateSyncReplication(t, fakePodControl, 0, 1, 3) -} - -func TestUpdateLabelsRemoveControllerRef(t *testing.T) { - rc := newReplicationController(2) - manager, fakePodControl, podInformer, rcInformer := setupManagerWithGCEnabled(rc) - rcInformer.Informer().GetIndexer().Add(rc) - // put one pod in the podLister - pod := newPod("pod", rc, v1.PodRunning, nil, false) - pod.ResourceVersion = "1" - var trueVar = true - rcOwnerReference := metav1.OwnerReference{UID: rc.UID, APIVersion: "v1", Kind: "ReplicationController", Name: rc.Name, Controller: &trueVar} - pod.OwnerReferences = []metav1.OwnerReference{rcOwnerReference} - updatedPod := *pod - // reset the labels - updatedPod.Labels = make(map[string]string) - updatedPod.ResourceVersion = "2" - // add the updatedPod to the store. This is consistent with the behavior of - // the Informer: Informer updates the store before call the handler - // (updatePod() in this case). - podInformer.Informer().GetIndexer().Add(&updatedPod) - // send a update of the same pod with modified labels - manager.updatePod(pod, &updatedPod) - // verifies that rc is added to the queue - rcKey := getKey(rc, t) - queueRC, _ := manager.queue.Get() - if queueRC != rcKey { - t.Fatalf("Expected to find key %v in queue, found %v", rcKey, queueRC) - } - manager.queue.Done(queueRC) - err := manager.syncReplicationController(rcKey) - if err != nil { - t.Fatal(err) - } - // expect 1 patch to be sent to remove the controllerRef for the pod. - // expect 2 creates because the *(rc.Spec.Replicas)=2 and there exists no - // matching pod. - validateSyncReplication(t, fakePodControl, 2, 0, 1) - fakePodControl.Clear() -} - -func TestUpdateSelectorControllerRef(t *testing.T) { - rc := newReplicationController(2) - manager, fakePodControl, podInformer, rcInformer := setupManagerWithGCEnabled(rc) - // put 2 pods in the podLister - newPodList(podInformer.Informer().GetIndexer(), 2, v1.PodRunning, rc, "pod") - // update the RC so that its selector no longer matches the pods - updatedRC := *rc - updatedRC.Spec.Selector = map[string]string{"foo": "baz"} - // put the updatedRC into the store. This is consistent with the behavior of - // the Informer: Informer updates the store before call the handler - // (updateRC() in this case). - rcInformer.Informer().GetIndexer().Add(&updatedRC) - manager.updateRC(rc, &updatedRC) - // verifies that the rc is added to the queue - rcKey := getKey(rc, t) - queueRC, _ := manager.queue.Get() - if queueRC != rcKey { - t.Fatalf("Expected to find key %v in queue, found %v", rcKey, queueRC) - } - manager.queue.Done(queueRC) - err := manager.syncReplicationController(rcKey) - if err != nil { - t.Fatal(err) - } - // expect 2 patches to be sent to remove the controllerRef for the pods. - // expect 2 creates because the *(rc.Spec.Replicas)=2 and there exists no - // matching pod. - validateSyncReplication(t, fakePodControl, 2, 0, 2) - fakePodControl.Clear() -} - -// RC manager shouldn't adopt or create more pods if the rc is about to be -// deleted. -func TestDoNotAdoptOrCreateIfBeingDeleted(t *testing.T) { - rc := newReplicationController(2) - now := metav1.Now() - rc.DeletionTimestamp = &now - manager, fakePodControl, podInformer, rcInformer := setupManagerWithGCEnabled(rc) - rcInformer.Informer().GetIndexer().Add(rc) - pod1 := newPod("pod1", rc, v1.PodRunning, nil, false) - podInformer.Informer().GetIndexer().Add(pod1) - - // no patch, no create - err := manager.syncReplicationController(getKey(rc, t)) - if err != nil { - t.Fatal(err) - } - validateSyncReplication(t, fakePodControl, 0, 0, 0) -} - -func TestDoNotAdoptOrCreateIfBeingDeletedRace(t *testing.T) { - // Bare client says it IS deleted. - rc := newReplicationController(2) - now := metav1.Now() - rc.DeletionTimestamp = &now - manager, fakePodControl, podInformer, rcInformer := setupManagerWithGCEnabled(rc) - // Lister (cache) says it's NOT deleted. - rc2 := *rc - rc2.DeletionTimestamp = nil - rcInformer.Informer().GetIndexer().Add(&rc2) - - // Recheck occurs if a matching orphan is present. - pod1 := newPod("pod1", rc, v1.PodRunning, nil, false) - podInformer.Informer().GetIndexer().Add(pod1) - - // sync should abort. - err := manager.syncReplicationController(getKey(rc, t)) - if err == nil { - t.Error("syncReplicationController() err = nil, expected non-nil") - } - // no patch, no create. - validateSyncReplication(t, fakePodControl, 0, 0, 0) -} - -func TestReadyReplicas(t *testing.T) { - // This is a happy server just to record the PUT request we expect for status.Replicas - fakeHandler := utiltesting.FakeHandler{ - StatusCode: 200, - ResponseBody: "{}", - } - testServer := httptest.NewServer(&fakeHandler) - defer testServer.Close() - - c := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) - manager, podInformer, rcInformer := newReplicationManagerFromClient(c, BurstReplicas) - - // Status.Replica should update to match number of pods in system, 1 new pod should be created. - rc := newReplicationController(2) - rc.Status = v1.ReplicationControllerStatus{Replicas: 2, ReadyReplicas: 0, AvailableReplicas: 0, ObservedGeneration: 1} - rc.Generation = 1 - rcInformer.Informer().GetIndexer().Add(rc) - - newPodList(podInformer.Informer().GetIndexer(), 2, v1.PodPending, rc, "pod") - newPodList(podInformer.Informer().GetIndexer(), 2, v1.PodRunning, rc, "pod") - - // This response body is just so we don't err out decoding the http response - response := runtime.EncodeOrDie(testapi.Default.Codec(), &v1.ReplicationController{}) - fakeHandler.ResponseBody = response - - fakePodControl := controller.FakePodControl{} - manager.podControl = &fakePodControl - - manager.syncReplicationController(getKey(rc, t)) - - // ReadyReplicas should go from 0 to 2. - rc.Status = v1.ReplicationControllerStatus{Replicas: 2, ReadyReplicas: 2, AvailableReplicas: 2, ObservedGeneration: 1} - - decRc := runtime.EncodeOrDie(testapi.Default.Codec(), rc) - fakeHandler.ValidateRequest(t, testapi.Default.ResourcePath(replicationControllerResourceName(), rc.Namespace, rc.Name)+"/status", "PUT", &decRc) - validateSyncReplication(t, &fakePodControl, 0, 0, 0) -} - -func TestAvailableReplicas(t *testing.T) { - // This is a happy server just to record the PUT request we expect for status.Replicas - fakeHandler := utiltesting.FakeHandler{ - StatusCode: 200, - ResponseBody: "{}", - } - testServer := httptest.NewServer(&fakeHandler) - defer testServer.Close() - - c := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) - manager, podInformer, rcInformer := newReplicationManagerFromClient(c, BurstReplicas) - - // Status.Replica should update to match number of pods in system, 1 new pod should be created. - rc := newReplicationController(2) - rc.Status = v1.ReplicationControllerStatus{Replicas: 2, ReadyReplicas: 0, ObservedGeneration: 1} - rc.Generation = 1 - // minReadySeconds set to 15s - rc.Spec.MinReadySeconds = 15 - rcInformer.Informer().GetIndexer().Add(rc) - - // First pod becomes ready 20s ago - moment := metav1.Time{Time: time.Now().Add(-2e10)} - pod := newPod("pod", rc, v1.PodRunning, &moment, true) - podInformer.Informer().GetIndexer().Add(pod) - - // Second pod becomes ready now - otherMoment := metav1.Now() - otherPod := newPod("otherPod", rc, v1.PodRunning, &otherMoment, true) - podInformer.Informer().GetIndexer().Add(otherPod) - - // This response body is just so we don't err out decoding the http response - response := runtime.EncodeOrDie(testapi.Default.Codec(), &v1.ReplicationController{}) - fakeHandler.ResponseBody = response - - fakePodControl := controller.FakePodControl{} - manager.podControl = &fakePodControl - - // The controller should see only one available pod. - manager.syncReplicationController(getKey(rc, t)) - - rc.Status = v1.ReplicationControllerStatus{Replicas: 2, ReadyReplicas: 2, AvailableReplicas: 1, ObservedGeneration: 1} - - decRc := runtime.EncodeOrDie(testapi.Default.Codec(), rc) - fakeHandler.ValidateRequest(t, testapi.Default.ResourcePath(replicationControllerResourceName(), rc.Namespace, rc.Name)+"/status", "PUT", &decRc) - validateSyncReplication(t, &fakePodControl, 0, 0, 0) -} - -var ( - imagePullBackOff v1.ReplicationControllerConditionType = "ImagePullBackOff" - - condImagePullBackOff = func() v1.ReplicationControllerCondition { - return v1.ReplicationControllerCondition{ - Type: imagePullBackOff, - Status: v1.ConditionTrue, - Reason: "NonExistentImage", - } - } - - condReplicaFailure = func() v1.ReplicationControllerCondition { - return v1.ReplicationControllerCondition{ - Type: v1.ReplicationControllerReplicaFailure, - Status: v1.ConditionTrue, - Reason: "OtherFailure", - } - } - - condReplicaFailure2 = func() v1.ReplicationControllerCondition { - return v1.ReplicationControllerCondition{ - Type: v1.ReplicationControllerReplicaFailure, - Status: v1.ConditionTrue, - Reason: "AnotherFailure", - } - } - - status = func() *v1.ReplicationControllerStatus { - return &v1.ReplicationControllerStatus{ - Conditions: []v1.ReplicationControllerCondition{condReplicaFailure()}, - } - } -) - -func TestGetCondition(t *testing.T) { - exampleStatus := status() - - tests := []struct { - name string - - status v1.ReplicationControllerStatus - condType v1.ReplicationControllerConditionType - condStatus v1.ConditionStatus - condReason string - - expected bool - }{ - { - name: "condition exists", - - status: *exampleStatus, - condType: v1.ReplicationControllerReplicaFailure, - - expected: true, - }, - { - name: "condition does not exist", - - status: *exampleStatus, - condType: imagePullBackOff, - - expected: false, - }, - } - - for _, test := range tests { - cond := GetCondition(test.status, test.condType) - exists := cond != nil - if exists != test.expected { - t.Errorf("%s: expected condition to exist: %t, got: %t", test.name, test.expected, exists) - } - } -} - -func TestSetCondition(t *testing.T) { - tests := []struct { - name string - - status *v1.ReplicationControllerStatus - cond v1.ReplicationControllerCondition - - expectedStatus *v1.ReplicationControllerStatus - }{ - { - name: "set for the first time", - - status: &v1.ReplicationControllerStatus{}, - cond: condReplicaFailure(), - - expectedStatus: &v1.ReplicationControllerStatus{Conditions: []v1.ReplicationControllerCondition{condReplicaFailure()}}, - }, - { - name: "simple set", - - status: &v1.ReplicationControllerStatus{Conditions: []v1.ReplicationControllerCondition{condImagePullBackOff()}}, - cond: condReplicaFailure(), - - expectedStatus: &v1.ReplicationControllerStatus{Conditions: []v1.ReplicationControllerCondition{condImagePullBackOff(), condReplicaFailure()}}, - }, - { - name: "overwrite", - - status: &v1.ReplicationControllerStatus{Conditions: []v1.ReplicationControllerCondition{condReplicaFailure()}}, - cond: condReplicaFailure2(), - - expectedStatus: &v1.ReplicationControllerStatus{Conditions: []v1.ReplicationControllerCondition{condReplicaFailure2()}}, - }, - } - - for _, test := range tests { - SetCondition(test.status, test.cond) - if !reflect.DeepEqual(test.status, test.expectedStatus) { - t.Errorf("%s: expected status: %v, got: %v", test.name, test.expectedStatus, test.status) - } - } -} - -func TestRemoveCondition(t *testing.T) { - tests := []struct { - name string - - status *v1.ReplicationControllerStatus - condType v1.ReplicationControllerConditionType - - expectedStatus *v1.ReplicationControllerStatus - }{ - { - name: "remove from empty status", - - status: &v1.ReplicationControllerStatus{}, - condType: v1.ReplicationControllerReplicaFailure, - - expectedStatus: &v1.ReplicationControllerStatus{}, - }, - { - name: "simple remove", - - status: &v1.ReplicationControllerStatus{Conditions: []v1.ReplicationControllerCondition{condReplicaFailure()}}, - condType: v1.ReplicationControllerReplicaFailure, - - expectedStatus: &v1.ReplicationControllerStatus{}, - }, - { - name: "doesn't remove anything", - - status: status(), - condType: imagePullBackOff, - - expectedStatus: status(), - }, - } - - for _, test := range tests { - RemoveCondition(test.status, test.condType) - if !reflect.DeepEqual(test.status, test.expectedStatus) { - t.Errorf("%s: expected status: %v, got: %v", test.name, test.expectedStatus, test.status) - } - } -} diff --git a/pkg/controller/replication/replication_controller_utils.go b/pkg/controller/replication/replication_controller_utils.go index 625317becb3..506074b83e4 100644 --- a/pkg/controller/replication/replication_controller_utils.go +++ b/pkg/controller/replication/replication_controller_utils.go @@ -19,123 +19,10 @@ limitations under the License. package replication import ( - "fmt" - "reflect" - - "github.com/golang/glog" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - v1core "k8s.io/client-go/kubernetes/typed/core/v1" - podutil "k8s.io/kubernetes/pkg/api/v1/pod" ) -// updateReplicationControllerStatus attempts to update the Status.Replicas of the given controller, with a single GET/PUT retry. -func updateReplicationControllerStatus(c v1core.ReplicationControllerInterface, rc v1.ReplicationController, newStatus v1.ReplicationControllerStatus) (*v1.ReplicationController, error) { - // This is the steady state. It happens when the rc doesn't have any expectations, since - // we do a periodic relist every 30s. If the generations differ but the replicas are - // the same, a caller might've resized to the same replica count. - if rc.Status.Replicas == newStatus.Replicas && - rc.Status.FullyLabeledReplicas == newStatus.FullyLabeledReplicas && - rc.Status.ReadyReplicas == newStatus.ReadyReplicas && - rc.Status.AvailableReplicas == newStatus.AvailableReplicas && - rc.Generation == rc.Status.ObservedGeneration && - reflect.DeepEqual(rc.Status.Conditions, newStatus.Conditions) { - return &rc, nil - } - // Save the generation number we acted on, otherwise we might wrongfully indicate - // that we've seen a spec update when we retry. - // TODO: This can clobber an update if we allow multiple agents to write to the - // same status. - newStatus.ObservedGeneration = rc.Generation - - var getErr, updateErr error - var updatedRC *v1.ReplicationController - for i, rc := 0, &rc; ; i++ { - glog.V(4).Infof(fmt.Sprintf("Updating status for rc: %s/%s, ", rc.Namespace, rc.Name) + - fmt.Sprintf("replicas %d->%d (need %d), ", rc.Status.Replicas, newStatus.Replicas, *(rc.Spec.Replicas)) + - fmt.Sprintf("fullyLabeledReplicas %d->%d, ", rc.Status.FullyLabeledReplicas, newStatus.FullyLabeledReplicas) + - fmt.Sprintf("readyReplicas %d->%d, ", rc.Status.ReadyReplicas, newStatus.ReadyReplicas) + - fmt.Sprintf("availableReplicas %d->%d, ", rc.Status.AvailableReplicas, newStatus.AvailableReplicas) + - fmt.Sprintf("sequence No: %v->%v", rc.Status.ObservedGeneration, newStatus.ObservedGeneration)) - - rc.Status = newStatus - updatedRC, updateErr = c.UpdateStatus(rc) - if updateErr == nil { - return updatedRC, nil - } - // Stop retrying if we exceed statusUpdateRetries - the replicationController will be requeued with a rate limit. - if i >= statusUpdateRetries { - break - } - // Update the controller with the latest resource version for the next poll - if rc, getErr = c.Get(rc.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 nil, getErr - } - } - - return nil, updateErr -} - -// OverlappingControllers sorts a list of controllers by creation timestamp, using their names as a tie breaker. -type OverlappingControllers []*v1.ReplicationController - -func (o OverlappingControllers) Len() int { return len(o) } -func (o OverlappingControllers) Swap(i, j int) { o[i], o[j] = o[j], o[i] } - -func (o OverlappingControllers) Less(i, j int) bool { - if o[i].CreationTimestamp.Equal(&o[j].CreationTimestamp) { - return o[i].Name < o[j].Name - } - return o[i].CreationTimestamp.Before(&o[j].CreationTimestamp) -} - -func calculateStatus(rc *v1.ReplicationController, filteredPods []*v1.Pod, manageReplicasErr error) v1.ReplicationControllerStatus { - newStatus := rc.Status - // Count the number of pods that have labels matching the labels of the pod - // template of the replication controller, the matching pods may have more - // labels than are in the template. Because the label of podTemplateSpec is - // a superset of the selector of the replication controller, so the possible - // matching pods must be part of the filteredPods. - fullyLabeledReplicasCount := 0 - readyReplicasCount := 0 - availableReplicasCount := 0 - templateLabel := labels.Set(rc.Spec.Template.Labels).AsSelectorPreValidated() - for _, pod := range filteredPods { - if templateLabel.Matches(labels.Set(pod.Labels)) { - fullyLabeledReplicasCount++ - } - if podutil.IsPodReady(pod) { - readyReplicasCount++ - if podutil.IsPodAvailable(pod, rc.Spec.MinReadySeconds, metav1.Now()) { - availableReplicasCount++ - } - } - } - - failureCond := GetCondition(rc.Status, v1.ReplicationControllerReplicaFailure) - if manageReplicasErr != nil && failureCond == nil { - var reason string - if diff := len(filteredPods) - int(*(rc.Spec.Replicas)); diff < 0 { - reason = "FailedCreate" - } else if diff > 0 { - reason = "FailedDelete" - } - cond := NewReplicationControllerCondition(v1.ReplicationControllerReplicaFailure, v1.ConditionTrue, reason, manageReplicasErr.Error()) - SetCondition(&newStatus, cond) - } else if manageReplicasErr == nil && failureCond != nil { - RemoveCondition(&newStatus, v1.ReplicationControllerReplicaFailure) - } - - newStatus.Replicas = int32(len(filteredPods)) - newStatus.FullyLabeledReplicas = int32(fullyLabeledReplicasCount) - newStatus.ReadyReplicas = int32(readyReplicasCount) - newStatus.AvailableReplicas = int32(availableReplicasCount) - return newStatus -} - // NewReplicationControllerCondition creates a new replication controller condition. func NewReplicationControllerCondition(condType v1.ReplicationControllerConditionType, status v1.ConditionStatus, reason, msg string) v1.ReplicationControllerCondition { return v1.ReplicationControllerCondition{ diff --git a/pkg/controller/replication/replication_controller_utils_test.go b/pkg/controller/replication/replication_controller_utils_test.go new file mode 100644 index 00000000000..0acaf0b0b20 --- /dev/null +++ b/pkg/controller/replication/replication_controller_utils_test.go @@ -0,0 +1,184 @@ +/* +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 replication + +import ( + "reflect" + "testing" + + "k8s.io/api/core/v1" +) + +var ( + imagePullBackOff v1.ReplicationControllerConditionType = "ImagePullBackOff" + + condImagePullBackOff = func() v1.ReplicationControllerCondition { + return v1.ReplicationControllerCondition{ + Type: imagePullBackOff, + Status: v1.ConditionTrue, + Reason: "NonExistentImage", + } + } + + condReplicaFailure = func() v1.ReplicationControllerCondition { + return v1.ReplicationControllerCondition{ + Type: v1.ReplicationControllerReplicaFailure, + Status: v1.ConditionTrue, + Reason: "OtherFailure", + } + } + + condReplicaFailure2 = func() v1.ReplicationControllerCondition { + return v1.ReplicationControllerCondition{ + Type: v1.ReplicationControllerReplicaFailure, + Status: v1.ConditionTrue, + Reason: "AnotherFailure", + } + } + + status = func() *v1.ReplicationControllerStatus { + return &v1.ReplicationControllerStatus{ + Conditions: []v1.ReplicationControllerCondition{condReplicaFailure()}, + } + } +) + +func TestGetCondition(t *testing.T) { + exampleStatus := status() + + tests := []struct { + name string + + status v1.ReplicationControllerStatus + condType v1.ReplicationControllerConditionType + condStatus v1.ConditionStatus + condReason string + + expected bool + }{ + { + name: "condition exists", + + status: *exampleStatus, + condType: v1.ReplicationControllerReplicaFailure, + + expected: true, + }, + { + name: "condition does not exist", + + status: *exampleStatus, + condType: imagePullBackOff, + + expected: false, + }, + } + + for _, test := range tests { + cond := GetCondition(test.status, test.condType) + exists := cond != nil + if exists != test.expected { + t.Errorf("%s: expected condition to exist: %t, got: %t", test.name, test.expected, exists) + } + } +} + +func TestSetCondition(t *testing.T) { + tests := []struct { + name string + + status *v1.ReplicationControllerStatus + cond v1.ReplicationControllerCondition + + expectedStatus *v1.ReplicationControllerStatus + }{ + { + name: "set for the first time", + + status: &v1.ReplicationControllerStatus{}, + cond: condReplicaFailure(), + + expectedStatus: &v1.ReplicationControllerStatus{Conditions: []v1.ReplicationControllerCondition{condReplicaFailure()}}, + }, + { + name: "simple set", + + status: &v1.ReplicationControllerStatus{Conditions: []v1.ReplicationControllerCondition{condImagePullBackOff()}}, + cond: condReplicaFailure(), + + expectedStatus: &v1.ReplicationControllerStatus{Conditions: []v1.ReplicationControllerCondition{condImagePullBackOff(), condReplicaFailure()}}, + }, + { + name: "overwrite", + + status: &v1.ReplicationControllerStatus{Conditions: []v1.ReplicationControllerCondition{condReplicaFailure()}}, + cond: condReplicaFailure2(), + + expectedStatus: &v1.ReplicationControllerStatus{Conditions: []v1.ReplicationControllerCondition{condReplicaFailure2()}}, + }, + } + + for _, test := range tests { + SetCondition(test.status, test.cond) + if !reflect.DeepEqual(test.status, test.expectedStatus) { + t.Errorf("%s: expected status: %v, got: %v", test.name, test.expectedStatus, test.status) + } + } +} + +func TestRemoveCondition(t *testing.T) { + tests := []struct { + name string + + status *v1.ReplicationControllerStatus + condType v1.ReplicationControllerConditionType + + expectedStatus *v1.ReplicationControllerStatus + }{ + { + name: "remove from empty status", + + status: &v1.ReplicationControllerStatus{}, + condType: v1.ReplicationControllerReplicaFailure, + + expectedStatus: &v1.ReplicationControllerStatus{}, + }, + { + name: "simple remove", + + status: &v1.ReplicationControllerStatus{Conditions: []v1.ReplicationControllerCondition{condReplicaFailure()}}, + condType: v1.ReplicationControllerReplicaFailure, + + expectedStatus: &v1.ReplicationControllerStatus{}, + }, + { + name: "doesn't remove anything", + + status: status(), + condType: imagePullBackOff, + + expectedStatus: status(), + }, + } + + for _, test := range tests { + RemoveCondition(test.status, test.condType) + if !reflect.DeepEqual(test.status, test.expectedStatus) { + t.Errorf("%s: expected status: %v, got: %v", test.name, test.expectedStatus, test.status) + } + } +} diff --git a/pkg/controller/resourcequota/BUILD b/pkg/controller/resourcequota/BUILD index a50bca09b6d..001f1dbfa79 100644 --- a/pkg/controller/resourcequota/BUILD +++ b/pkg/controller/resourcequota/BUILD @@ -10,16 +10,17 @@ go_library( name = "go_default_library", srcs = [ "doc.go", - "replenishment_controller.go", "resource_quota_controller.go", + "resource_quota_monitor.go", ], importpath = "k8s.io/kubernetes/pkg/controller/resourcequota", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/v1:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/v1:go_default_library", "//pkg/controller:go_default_library", "//pkg/quota:go_default_library", "//pkg/quota/evaluator/core:go_default_library", + "//pkg/quota/generic:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", @@ -27,11 +28,12 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/client-go/discovery:go_default_library", "//vendor/k8s.io/client-go/informers:go_default_library", "//vendor/k8s.io/client-go/informers/core/v1:go_default_library", "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", @@ -43,15 +45,12 @@ go_library( go_test( name = "go_default_test", - srcs = [ - "replenishment_controller_test.go", - "resource_quota_controller_test.go", - ], + srcs = ["resource_quota_controller_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/resourcequota", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", "//pkg/controller:go_default_library", + "//pkg/quota:go_default_library", "//pkg/quota/generic:go_default_library", "//pkg/quota/install:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", @@ -59,12 +58,12 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/client-go/informers:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", "//vendor/k8s.io/client-go/testing:go_default_library", + "//vendor/k8s.io/client-go/tools/cache:go_default_library", ], ) diff --git a/pkg/controller/resourcequota/replenishment_controller.go b/pkg/controller/resourcequota/replenishment_controller.go deleted file mode 100644 index 5f063e65683..00000000000 --- a/pkg/controller/resourcequota/replenishment_controller.go +++ /dev/null @@ -1,233 +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 resourcequota - -import ( - "fmt" - - "github.com/golang/glog" - - "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/clock" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/client-go/informers" - "k8s.io/client-go/tools/cache" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/controller" - "k8s.io/kubernetes/pkg/quota/evaluator/core" -) - -// ReplenishmentFunc is a function that is invoked when controller sees a change -// that may require a quota to be replenished (i.e. object deletion, or object moved to terminal state) -type ReplenishmentFunc func(groupKind schema.GroupKind, namespace string, object runtime.Object) - -// ReplenishmentControllerOptions is an options struct that tells a factory -// how to configure a controller that can inform the quota system it should -// replenish quota -type ReplenishmentControllerOptions struct { - // The kind monitored for replenishment - GroupKind schema.GroupKind - // The period that should be used to re-sync the monitored resource - ResyncPeriod controller.ResyncPeriodFunc - // The function to invoke when a change is observed that should trigger - // replenishment - ReplenishmentFunc ReplenishmentFunc -} - -// PodReplenishmentUpdateFunc will replenish if the old pod was quota tracked but the new is not -func PodReplenishmentUpdateFunc(options *ReplenishmentControllerOptions, clock clock.Clock) func(oldObj, newObj interface{}) { - return func(oldObj, newObj interface{}) { - oldPod := oldObj.(*v1.Pod) - newPod := newObj.(*v1.Pod) - if core.QuotaV1Pod(oldPod, clock) && !core.QuotaV1Pod(newPod, clock) { - options.ReplenishmentFunc(options.GroupKind, newPod.Namespace, oldPod) - } - } -} - -// ObjectReplenishmentDeleteFunc will replenish on every delete -func ObjectReplenishmentDeleteFunc(options *ReplenishmentControllerOptions) func(obj interface{}) { - return func(obj interface{}) { - metaObject, err := meta.Accessor(obj) - if err != nil { - tombstone, ok := obj.(cache.DeletedFinalStateUnknown) - if !ok { - glog.Errorf("replenishment controller could not get object from tombstone %+v, could take up to %v before quota is replenished", obj, options.ResyncPeriod()) - utilruntime.HandleError(err) - return - } - metaObject, err = meta.Accessor(tombstone.Obj) - if err != nil { - glog.Errorf("replenishment controller tombstone contained object that is not a meta %+v, could take up to %v before quota is replenished", tombstone.Obj, options.ResyncPeriod()) - utilruntime.HandleError(err) - return - } - } - options.ReplenishmentFunc(options.GroupKind, metaObject.GetNamespace(), nil) - } -} - -// ReplenishmentControllerFactory knows how to build replenishment controllers -type ReplenishmentControllerFactory interface { - // NewController returns a controller configured with the specified options. - // This method is NOT thread-safe. - NewController(options *ReplenishmentControllerOptions) (cache.Controller, error) -} - -// replenishmentControllerFactory implements ReplenishmentControllerFactory -type replenishmentControllerFactory struct { - sharedInformerFactory informers.SharedInformerFactory -} - -// NewReplenishmentControllerFactory returns a factory that knows how to build controllers -// to replenish resources when updated or deleted -func NewReplenishmentControllerFactory(f informers.SharedInformerFactory) ReplenishmentControllerFactory { - return &replenishmentControllerFactory{ - sharedInformerFactory: f, - } -} - -func (r *replenishmentControllerFactory) NewController(options *ReplenishmentControllerOptions) (cache.Controller, error) { - var ( - informer informers.GenericInformer - err error - ) - - switch options.GroupKind { - case api.Kind("Pod"): - informer, err = r.sharedInformerFactory.ForResource(v1.SchemeGroupVersion.WithResource("pods")) - if err != nil { - return nil, err - } - clock := clock.RealClock{} - informer.Informer().AddEventHandlerWithResyncPeriod( - cache.ResourceEventHandlerFuncs{ - UpdateFunc: PodReplenishmentUpdateFunc(options, clock), - DeleteFunc: ObjectReplenishmentDeleteFunc(options), - }, - options.ResyncPeriod(), - ) - case api.Kind("Service"): - informer, err = r.sharedInformerFactory.ForResource(v1.SchemeGroupVersion.WithResource("services")) - if err != nil { - return nil, err - } - informer.Informer().AddEventHandlerWithResyncPeriod( - cache.ResourceEventHandlerFuncs{ - UpdateFunc: ServiceReplenishmentUpdateFunc(options), - DeleteFunc: ObjectReplenishmentDeleteFunc(options), - }, - options.ResyncPeriod(), - ) - case api.Kind("ReplicationController"): - informer, err = r.sharedInformerFactory.ForResource(v1.SchemeGroupVersion.WithResource("replicationcontrollers")) - if err != nil { - return nil, err - } - informer.Informer().AddEventHandlerWithResyncPeriod( - cache.ResourceEventHandlerFuncs{ - DeleteFunc: ObjectReplenishmentDeleteFunc(options), - }, - options.ResyncPeriod(), - ) - case api.Kind("PersistentVolumeClaim"): - informer, err = r.sharedInformerFactory.ForResource(v1.SchemeGroupVersion.WithResource("persistentvolumeclaims")) - if err != nil { - return nil, err - } - informer.Informer().AddEventHandlerWithResyncPeriod( - cache.ResourceEventHandlerFuncs{ - DeleteFunc: ObjectReplenishmentDeleteFunc(options), - }, - options.ResyncPeriod(), - ) - case api.Kind("Secret"): - informer, err = r.sharedInformerFactory.ForResource(v1.SchemeGroupVersion.WithResource("secrets")) - if err != nil { - return nil, err - } - informer.Informer().AddEventHandlerWithResyncPeriod( - cache.ResourceEventHandlerFuncs{ - DeleteFunc: ObjectReplenishmentDeleteFunc(options), - }, - options.ResyncPeriod(), - ) - case api.Kind("ConfigMap"): - informer, err = r.sharedInformerFactory.ForResource(v1.SchemeGroupVersion.WithResource("configmaps")) - if err != nil { - return nil, err - } - informer.Informer().AddEventHandlerWithResyncPeriod( - cache.ResourceEventHandlerFuncs{ - DeleteFunc: ObjectReplenishmentDeleteFunc(options), - }, - options.ResyncPeriod(), - ) - default: - return nil, NewUnhandledGroupKindError(options.GroupKind) - } - return informer.Informer().GetController(), nil -} - -// ServiceReplenishmentUpdateFunc will replenish if the service was quota tracked has changed service type -func ServiceReplenishmentUpdateFunc(options *ReplenishmentControllerOptions) func(oldObj, newObj interface{}) { - return func(oldObj, newObj interface{}) { - oldService := oldObj.(*v1.Service) - newService := newObj.(*v1.Service) - if core.GetQuotaServiceType(oldService) != core.GetQuotaServiceType(newService) { - options.ReplenishmentFunc(options.GroupKind, newService.Namespace, nil) - } - } -} - -type unhandledKindErr struct { - kind schema.GroupKind -} - -func (e unhandledKindErr) Error() string { - return fmt.Sprintf("no replenishment controller available for %s", e.kind) -} - -func NewUnhandledGroupKindError(kind schema.GroupKind) error { - return unhandledKindErr{kind: kind} -} - -func IsUnhandledGroupKindError(err error) bool { - if err == nil { - return false - } - _, ok := err.(unhandledKindErr) - return ok -} - -// UnionReplenishmentControllerFactory iterates through its constituent factories ignoring, UnhandledGroupKindErrors -// returning the first success or failure it hits. If there are no hits either way, it return an UnhandledGroupKind error -type UnionReplenishmentControllerFactory []ReplenishmentControllerFactory - -func (f UnionReplenishmentControllerFactory) NewController(options *ReplenishmentControllerOptions) (cache.Controller, error) { - for _, factory := range f { - controller, err := factory.NewController(options) - if !IsUnhandledGroupKindError(err) { - return controller, err - } - } - - return nil, NewUnhandledGroupKindError(options.GroupKind) -} diff --git a/pkg/controller/resourcequota/replenishment_controller_test.go b/pkg/controller/resourcequota/replenishment_controller_test.go deleted file mode 100644 index 45df8164ec1..00000000000 --- a/pkg/controller/resourcequota/replenishment_controller_test.go +++ /dev/null @@ -1,160 +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 resourcequota - -import ( - "testing" - "time" - - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/clock" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/controller" -) - -// testReplenishment lets us test replenishment functions are invoked -type testReplenishment struct { - groupKind schema.GroupKind - namespace string -} - -// mock function that holds onto the last kind that was replenished -func (t *testReplenishment) Replenish(groupKind schema.GroupKind, namespace string, object runtime.Object) { - t.groupKind = groupKind - t.namespace = namespace -} - -func TestPodReplenishmentUpdateFunc(t *testing.T) { - mockReplenish := &testReplenishment{} - options := ReplenishmentControllerOptions{ - GroupKind: api.Kind("Pod"), - ReplenishmentFunc: mockReplenish.Replenish, - ResyncPeriod: controller.NoResyncPeriodFunc, - } - oldPod := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "pod"}, - Status: v1.PodStatus{Phase: v1.PodRunning}, - } - newPod := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "pod"}, - Status: v1.PodStatus{Phase: v1.PodFailed}, - } - fakeClock := clock.NewFakeClock(time.Now()) - updateFunc := PodReplenishmentUpdateFunc(&options, fakeClock) - updateFunc(oldPod, newPod) - if mockReplenish.groupKind != api.Kind("Pod") { - t.Errorf("Unexpected group kind %v", mockReplenish.groupKind) - } - if mockReplenish.namespace != oldPod.Namespace { - t.Errorf("Unexpected namespace %v", mockReplenish.namespace) - } -} - -func TestObjectReplenishmentDeleteFunc(t *testing.T) { - mockReplenish := &testReplenishment{} - options := ReplenishmentControllerOptions{ - GroupKind: api.Kind("Pod"), - ReplenishmentFunc: mockReplenish.Replenish, - ResyncPeriod: controller.NoResyncPeriodFunc, - } - oldPod := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "pod"}, - Status: v1.PodStatus{Phase: v1.PodRunning}, - } - deleteFunc := ObjectReplenishmentDeleteFunc(&options) - deleteFunc(oldPod) - if mockReplenish.groupKind != api.Kind("Pod") { - t.Errorf("Unexpected group kind %v", mockReplenish.groupKind) - } - if mockReplenish.namespace != oldPod.Namespace { - t.Errorf("Unexpected namespace %v", mockReplenish.namespace) - } -} - -func TestServiceReplenishmentUpdateFunc(t *testing.T) { - mockReplenish := &testReplenishment{} - options := ReplenishmentControllerOptions{ - GroupKind: api.Kind("Service"), - ReplenishmentFunc: mockReplenish.Replenish, - ResyncPeriod: controller.NoResyncPeriodFunc, - } - oldService := &v1.Service{ - ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "mysvc"}, - Spec: v1.ServiceSpec{ - Type: v1.ServiceTypeNodePort, - Ports: []v1.ServicePort{{ - Port: 80, - TargetPort: intstr.FromInt(80), - }}, - }, - } - newService := &v1.Service{ - ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "mysvc"}, - Spec: v1.ServiceSpec{ - Type: v1.ServiceTypeClusterIP, - Ports: []v1.ServicePort{{ - Port: 80, - TargetPort: intstr.FromInt(80), - }}}, - } - updateFunc := ServiceReplenishmentUpdateFunc(&options) - updateFunc(oldService, newService) - if mockReplenish.groupKind != api.Kind("Service") { - t.Errorf("Unexpected group kind %v", mockReplenish.groupKind) - } - if mockReplenish.namespace != oldService.Namespace { - t.Errorf("Unexpected namespace %v", mockReplenish.namespace) - } - - mockReplenish = &testReplenishment{} - options = ReplenishmentControllerOptions{ - GroupKind: api.Kind("Service"), - ReplenishmentFunc: mockReplenish.Replenish, - ResyncPeriod: controller.NoResyncPeriodFunc, - } - oldService = &v1.Service{ - ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "mysvc"}, - Spec: v1.ServiceSpec{ - Type: v1.ServiceTypeNodePort, - Ports: []v1.ServicePort{{ - Port: 80, - TargetPort: intstr.FromInt(80), - }}, - }, - } - newService = &v1.Service{ - ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "mysvc"}, - Spec: v1.ServiceSpec{ - Type: v1.ServiceTypeNodePort, - Ports: []v1.ServicePort{{ - Port: 81, - TargetPort: intstr.FromInt(81), - }}}, - } - updateFunc = ServiceReplenishmentUpdateFunc(&options) - updateFunc(oldService, newService) - if mockReplenish.groupKind == api.Kind("Service") { - t.Errorf("Unexpected group kind %v", mockReplenish.groupKind) - } - if mockReplenish.namespace == oldService.Namespace { - t.Errorf("Unexpected namespace %v", mockReplenish.namespace) - } -} diff --git a/pkg/controller/resourcequota/resource_quota_controller.go b/pkg/controller/resourcequota/resource_quota_controller.go index 43acf9878cf..b2ae6d1f6e2 100644 --- a/pkg/controller/resourcequota/resource_quota_controller.go +++ b/pkg/controller/resourcequota/resource_quota_controller.go @@ -18,6 +18,8 @@ package resourcequota import ( "fmt" + "reflect" + "sync" "time" "github.com/golang/glog" @@ -27,21 +29,35 @@ import ( "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" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/discovery" + "k8s.io/client-go/informers" coreinformers "k8s.io/client-go/informers/core/v1" corev1client "k8s.io/client-go/kubernetes/typed/core/v1" corelisters "k8s.io/client-go/listers/core/v1" "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" - "k8s.io/kubernetes/pkg/api" - k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" + api "k8s.io/kubernetes/pkg/apis/core" + k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/quota" ) +// NamespacedResourcesFunc knows how to discover namespaced resources. +type NamespacedResourcesFunc func() ([]*metav1.APIResourceList, error) + +// ReplenishmentFunc is a signal that a resource changed in specified namespace +// that may require quota to be recalculated. +type ReplenishmentFunc func(groupResource schema.GroupResource, namespace string) + +// InformerFactory is all the quota system needs to interface with informers. +type InformerFactory interface { + ForResource(resource schema.GroupVersionResource) (informers.GenericInformer, error) + Start(stopCh <-chan struct{}) +} + // ResourceQuotaControllerOptions holds options for creating a quota controller type ResourceQuotaControllerOptions struct { // Must have authority to list all quotas, and update quota status @@ -50,15 +66,18 @@ type ResourceQuotaControllerOptions struct { ResourceQuotaInformer coreinformers.ResourceQuotaInformer // Controls full recalculation of quota usage ResyncPeriod controller.ResyncPeriodFunc - // Knows how to calculate usage + // Maintains evaluators that know how to calculate usage for group resource Registry quota.Registry - // Knows how to build controllers that notify replenishment events - ControllerFactory ReplenishmentControllerFactory + // Discover list of supported resources on the server. + DiscoveryFunc NamespacedResourcesFunc + // A function that returns the list of resources to ignore + IgnoredResourcesFunc func() map[schema.GroupResource]struct{} + // InformersStarted knows if informers were started. + InformersStarted <-chan struct{} + // InformerFactory interfaces with informers. + InformerFactory InformerFactory // Controls full resync of objects monitored for replenishment. ReplenishmentResyncPeriod controller.ResyncPeriodFunc - // List of GroupKind objects that should be monitored for replenishment at - // a faster frequency than the quota controller recalculation interval - GroupKindsToReplenish []schema.GroupKind } // ResourceQuotaController is responsible for tracking quota usage status in the system @@ -79,21 +98,25 @@ type ResourceQuotaController struct { resyncPeriod controller.ResyncPeriodFunc // knows how to calculate usage registry quota.Registry - // controllers monitoring to notify for replenishment - replenishmentControllers []cache.Controller + // knows how to monitor all the resources tracked by quota and trigger replenishment + quotaMonitor *QuotaMonitor + // controls the workers that process quotas + // this lock is acquired to control write access to the monitors and ensures that all + // monitors are synced before the controller can process quotas. + workerLock sync.RWMutex } -func NewResourceQuotaController(options *ResourceQuotaControllerOptions) *ResourceQuotaController { +// NewResourceQuotaController creates a quota controller with specified options +func NewResourceQuotaController(options *ResourceQuotaControllerOptions) (*ResourceQuotaController, error) { // build the resource quota controller rq := &ResourceQuotaController{ - rqClient: options.QuotaClient, - rqLister: options.ResourceQuotaInformer.Lister(), - informerSyncedFuncs: []cache.InformerSynced{options.ResourceQuotaInformer.Informer().HasSynced}, - queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "resourcequota_primary"), - missingUsageQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "resourcequota_priority"), - resyncPeriod: options.ResyncPeriod, - registry: options.Registry, - replenishmentControllers: []cache.Controller{}, + rqClient: options.QuotaClient, + rqLister: options.ResourceQuotaInformer.Lister(), + informerSyncedFuncs: []cache.InformerSynced{options.ResourceQuotaInformer.Informer().HasSynced}, + queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "resourcequota_primary"), + missingUsageQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "resourcequota_priority"), + resyncPeriod: options.ResyncPeriod, + registry: options.Registry, } // set the synchronization handler rq.syncHandler = rq.syncResourceQuotaFromKey @@ -125,21 +148,30 @@ func NewResourceQuotaController(options *ResourceQuotaControllerOptions) *Resour rq.resyncPeriod(), ) - for _, groupKindToReplenish := range options.GroupKindsToReplenish { - controllerOptions := &ReplenishmentControllerOptions{ - GroupKind: groupKindToReplenish, - ResyncPeriod: options.ReplenishmentResyncPeriod, - ReplenishmentFunc: rq.replenishQuota, - } - replenishmentController, err := options.ControllerFactory.NewController(controllerOptions) - if err != nil { - glog.Warningf("quota controller unable to replenish %s due to %v, changes only accounted during full resync", groupKindToReplenish, err) - } else { - // make sure we wait for each shared informer's cache to sync - rq.informerSyncedFuncs = append(rq.informerSyncedFuncs, replenishmentController.HasSynced) - } + qm := &QuotaMonitor{ + informersStarted: options.InformersStarted, + informerFactory: options.InformerFactory, + ignoredResources: options.IgnoredResourcesFunc(), + resourceChanges: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "resource_quota_controller_resource_changes"), + resyncPeriod: options.ReplenishmentResyncPeriod, + replenishmentFunc: rq.replenishQuota, + registry: rq.registry, } - return rq + rq.quotaMonitor = qm + + // do initial quota monitor setup + resources, err := GetQuotableResources(options.DiscoveryFunc) + if err != nil { + return nil, err + } + if err = qm.syncMonitors(resources); err != nil { + utilruntime.HandleError(fmt.Errorf("initial monitor sync has error: %v", err)) + } + + // only start quota once all informers synced + rq.informerSyncedFuncs = append(rq.informerSyncedFuncs, qm.IsSynced) + + return rq, nil } // enqueueAll is called at the fullResyncPeriod interval to force a full recalculation of quota usage statistics @@ -189,7 +221,7 @@ func (rq *ResourceQuotaController) addQuota(obj interface{}) { for constraint := range resourceQuota.Status.Hard { if _, usageFound := resourceQuota.Status.Used[constraint]; !usageFound { matchedResources := []api.ResourceName{api.ResourceName(constraint)} - for _, evaluator := range rq.registry.Evaluators() { + for _, evaluator := range rq.registry.List() { if intersection := evaluator.MatchingResources(matchedResources); len(intersection) > 0 { rq.missingUsageQueue.Add(key) return @@ -205,6 +237,10 @@ func (rq *ResourceQuotaController) addQuota(obj interface{}) { // worker runs a worker thread that just dequeues items, processes them, and marks them done. func (rq *ResourceQuotaController) worker(queue workqueue.RateLimitingInterface) func() { workFunc := func() bool { + + rq.workerLock.RLock() + defer rq.workerLock.RUnlock() + key, quit := queue.Get() if quit { return true @@ -238,10 +274,7 @@ func (rq *ResourceQuotaController) Run(workers int, stopCh <-chan struct{}) { glog.Infof("Starting resource quota controller") defer glog.Infof("Shutting down resource quota controller") - // the controllers that replenish other resources to respond rapidly to state changes - for _, replenishmentController := range rq.replenishmentControllers { - go replenishmentController.Run(stopCh) - } + go rq.quotaMonitor.Run(stopCh) if !controller.WaitForCacheSync("resource quota", stopCh, rq.informerSyncedFuncs...) { return @@ -287,7 +320,7 @@ func (rq *ResourceQuotaController) syncResourceQuota(v1ResourceQuota *v1.Resourc dirty := !apiequality.Semantic.DeepEqual(v1ResourceQuota.Spec.Hard, v1ResourceQuota.Status.Hard) resourceQuota := api.ResourceQuota{} - if err := k8s_api_v1.Convert_v1_ResourceQuota_To_api_ResourceQuota(v1ResourceQuota, &resourceQuota, nil); err != nil { + if err := k8s_api_v1.Convert_v1_ResourceQuota_To_core_ResourceQuota(v1ResourceQuota, &resourceQuota, nil); err != nil { return err } @@ -334,7 +367,7 @@ func (rq *ResourceQuotaController) syncResourceQuota(v1ResourceQuota *v1.Resourc // there was a change observed by this controller that requires we update quota if dirty { v1Usage := &v1.ResourceQuota{} - if err := k8s_api_v1.Convert_api_ResourceQuota_To_v1_ResourceQuota(&usage, v1Usage, nil); err != nil { + if err := k8s_api_v1.Convert_core_ResourceQuota_To_v1_ResourceQuota(&usage, v1Usage, nil); err != nil { return err } _, err = rq.rqClient.ResourceQuotas(usage.Namespace).UpdateStatus(v1Usage) @@ -344,11 +377,10 @@ func (rq *ResourceQuotaController) syncResourceQuota(v1ResourceQuota *v1.Resourc } // replenishQuota is a replenishment function invoked by a controller to notify that a quota should be recalculated -func (rq *ResourceQuotaController) replenishQuota(groupKind schema.GroupKind, namespace string, object runtime.Object) { - // check if the quota controller can evaluate this kind, if not, ignore it altogether... - evaluators := rq.registry.Evaluators() - evaluator, found := evaluators[groupKind] - if !found { +func (rq *ResourceQuotaController) replenishQuota(groupResource schema.GroupResource, namespace string) { + // check if the quota controller can evaluate this groupResource, if not, ignore it altogether... + evaluator := rq.registry.Get(groupResource) + if evaluator == nil { return } @@ -370,7 +402,7 @@ func (rq *ResourceQuotaController) replenishQuota(groupKind schema.GroupKind, na for i := range resourceQuotas { resourceQuota := resourceQuotas[i] internalResourceQuota := &api.ResourceQuota{} - if err := k8s_api_v1.Convert_v1_ResourceQuota_To_api_ResourceQuota(resourceQuota, internalResourceQuota, nil); err != nil { + if err := k8s_api_v1.Convert_v1_ResourceQuota_To_core_ResourceQuota(resourceQuota, internalResourceQuota, nil); err != nil { glog.Error(err) continue } @@ -381,3 +413,66 @@ func (rq *ResourceQuotaController) replenishQuota(groupKind schema.GroupKind, na } } } + +// Sync periodically resyncs the controller when new resources are observed from discovery. +func (rq *ResourceQuotaController) Sync(discoveryFunc NamespacedResourcesFunc, period time.Duration, stopCh <-chan struct{}) { + // Something has changed, so track the new state and perform a sync. + oldResources := make(map[schema.GroupVersionResource]struct{}) + wait.Until(func() { + // Get the current resource list from discovery. + newResources, err := GetQuotableResources(discoveryFunc) + if err != nil { + utilruntime.HandleError(err) + return + } + + // Decide whether discovery has reported a change. + if reflect.DeepEqual(oldResources, newResources) { + glog.V(4).Infof("no resource updates from discovery, skipping resource quota sync") + return + } + + // Something has changed, so track the new state and perform a sync. + glog.V(2).Infof("syncing resource quota controller with updated resources from discovery: %v", newResources) + oldResources = newResources + + // Ensure workers are paused to avoid processing events before informers + // have resynced. + rq.workerLock.Lock() + defer rq.workerLock.Unlock() + + // Perform the monitor resync and wait for controllers to report cache sync. + if err := rq.resyncMonitors(newResources); err != nil { + utilruntime.HandleError(fmt.Errorf("failed to sync resource monitors: %v", err)) + return + } + if !controller.WaitForCacheSync("resource quota", stopCh, rq.quotaMonitor.IsSynced) { + utilruntime.HandleError(fmt.Errorf("timed out waiting for quota monitor sync")) + } + }, period, stopCh) +} + +// resyncMonitors starts or stops quota monitors as needed to ensure that all +// (and only) those resources present in the map are monitored. +func (rq *ResourceQuotaController) resyncMonitors(resources map[schema.GroupVersionResource]struct{}) error { + if err := rq.quotaMonitor.syncMonitors(resources); err != nil { + return err + } + rq.quotaMonitor.startMonitors() + return nil +} + +// GetQuotableResources returns all resources that the quota system should recognize. +// It requires a resource supports the following verbs: 'create','list','delete' +func GetQuotableResources(discoveryFunc NamespacedResourcesFunc) (map[schema.GroupVersionResource]struct{}, error) { + possibleResources, err := discoveryFunc() + if err != nil { + return nil, fmt.Errorf("failed to discover resources: %v", err) + } + quotableResources := discovery.FilteredBy(discovery.SupportsAllVerbs{Verbs: []string{"create", "list", "watch", "delete"}}, possibleResources) + quotableGroupVersionResources, err := discovery.GroupVersionResources(quotableResources) + if err != nil { + return nil, fmt.Errorf("Failed to parse resources: %v", err) + } + return quotableGroupVersionResources, nil +} diff --git a/pkg/controller/resourcequota/resource_quota_controller_test.go b/pkg/controller/resourcequota/resource_quota_controller_test.go index 3ab76385998..6ed7ba9c968 100644 --- a/pkg/controller/resourcequota/resource_quota_controller_test.go +++ b/pkg/controller/resourcequota/resource_quota_controller_test.go @@ -17,19 +17,23 @@ limitations under the License. package resourcequota import ( + "fmt" "strings" "testing" "k8s.io/api/core/v1" "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" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" core "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" + "k8s.io/client-go/tools/cache" "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/quota" "k8s.io/kubernetes/pkg/quota/generic" "k8s.io/kubernetes/pkg/quota/install" ) @@ -52,383 +56,249 @@ func getResourceRequirements(requests, limits v1.ResourceList) v1.ResourceRequir return res } +func mockDiscoveryFunc() ([]*metav1.APIResourceList, error) { + return []*metav1.APIResourceList{}, nil +} + +func mockListerForResourceFunc(listersForResource map[schema.GroupVersionResource]cache.GenericLister) quota.ListerForResourceFunc { + return func(gvr schema.GroupVersionResource) (cache.GenericLister, error) { + lister, found := listersForResource[gvr] + if !found { + return nil, fmt.Errorf("no lister found for resource") + } + return lister, nil + } +} + +func newGenericLister(groupResource schema.GroupResource, items []runtime.Object) cache.GenericLister { + store := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc}) + for _, item := range items { + store.Add(item) + } + return cache.NewGenericLister(store, groupResource) +} + +type quotaController struct { + *ResourceQuotaController + stop chan struct{} +} + +func setupQuotaController(t *testing.T, kubeClient kubernetes.Interface, lister quota.ListerForResourceFunc) quotaController { + informerFactory := informers.NewSharedInformerFactory(kubeClient, controller.NoResyncPeriodFunc()) + quotaConfiguration := install.NewQuotaConfigurationForControllers(lister) + alwaysStarted := make(chan struct{}) + close(alwaysStarted) + resourceQuotaControllerOptions := &ResourceQuotaControllerOptions{ + QuotaClient: kubeClient.CoreV1(), + ResourceQuotaInformer: informerFactory.Core().V1().ResourceQuotas(), + ResyncPeriod: controller.NoResyncPeriodFunc, + ReplenishmentResyncPeriod: controller.NoResyncPeriodFunc, + IgnoredResourcesFunc: quotaConfiguration.IgnoredResources, + DiscoveryFunc: mockDiscoveryFunc, + Registry: generic.NewRegistry(quotaConfiguration.Evaluators()), + InformersStarted: alwaysStarted, + } + qc, err := NewResourceQuotaController(resourceQuotaControllerOptions) + if err != nil { + t.Fatal(err) + } + stop := make(chan struct{}) + go informerFactory.Start(stop) + return quotaController{qc, stop} +} + +func newTestPods() []runtime.Object { + return []runtime.Object{ + &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod-running", Namespace: "testing"}, + Status: v1.PodStatus{Phase: v1.PodRunning}, + Spec: v1.PodSpec{ + Volumes: []v1.Volume{{Name: "vol"}}, + Containers: []v1.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements(getResourceList("100m", "1Gi"), getResourceList("", ""))}}, + }, + }, + &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod-running-2", Namespace: "testing"}, + Status: v1.PodStatus{Phase: v1.PodRunning}, + Spec: v1.PodSpec{ + Volumes: []v1.Volume{{Name: "vol"}}, + Containers: []v1.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements(getResourceList("100m", "1Gi"), getResourceList("", ""))}}, + }, + }, + &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod-failed", Namespace: "testing"}, + Status: v1.PodStatus{Phase: v1.PodFailed}, + Spec: v1.PodSpec{ + Volumes: []v1.Volume{{Name: "vol"}}, + Containers: []v1.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements(getResourceList("100m", "1Gi"), getResourceList("", ""))}}, + }, + }, + } +} + func TestSyncResourceQuota(t *testing.T) { - podList := v1.PodList{ - Items: []v1.Pod{ - { - ObjectMeta: metav1.ObjectMeta{Name: "pod-running", Namespace: "testing"}, - Status: v1.PodStatus{Phase: v1.PodRunning}, - Spec: v1.PodSpec{ - Volumes: []v1.Volume{{Name: "vol"}}, - Containers: []v1.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements(getResourceList("100m", "1Gi"), getResourceList("", ""))}}, + testCases := map[string]struct { + gvr schema.GroupVersionResource + items []runtime.Object + quota v1.ResourceQuota + status v1.ResourceQuotaStatus + expectedActionSet sets.String + }{ + "pods": { + gvr: v1.SchemeGroupVersion.WithResource("pods"), + quota: v1.ResourceQuota{ + ObjectMeta: metav1.ObjectMeta{Name: "quota", Namespace: "testing"}, + Spec: v1.ResourceQuotaSpec{ + Hard: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("3"), + v1.ResourceMemory: resource.MustParse("100Gi"), + v1.ResourcePods: resource.MustParse("5"), + }, }, }, - { - ObjectMeta: metav1.ObjectMeta{Name: "pod-running-2", Namespace: "testing"}, - Status: v1.PodStatus{Phase: v1.PodRunning}, - Spec: v1.PodSpec{ - Volumes: []v1.Volume{{Name: "vol"}}, - Containers: []v1.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements(getResourceList("100m", "1Gi"), getResourceList("", ""))}}, + status: v1.ResourceQuotaStatus{ + Hard: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("3"), + v1.ResourceMemory: resource.MustParse("100Gi"), + v1.ResourcePods: resource.MustParse("5"), + }, + Used: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), + v1.ResourceMemory: resource.MustParse("2Gi"), + v1.ResourcePods: resource.MustParse("2"), }, }, - { - ObjectMeta: metav1.ObjectMeta{Name: "pod-failed", Namespace: "testing"}, - Status: v1.PodStatus{Phase: v1.PodFailed}, - Spec: v1.PodSpec{ - Volumes: []v1.Volume{{Name: "vol"}}, - Containers: []v1.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements(getResourceList("100m", "1Gi"), getResourceList("", ""))}}, + expectedActionSet: sets.NewString( + strings.Join([]string{"update", "resourcequotas", "status"}, "-"), + ), + items: newTestPods(), + }, + "quota-spec-hard-updated": { + gvr: v1.SchemeGroupVersion.WithResource("pods"), + quota: v1.ResourceQuota{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "rq", + }, + Spec: v1.ResourceQuotaSpec{ + Hard: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("4"), + }, + }, + Status: v1.ResourceQuotaStatus{ + Hard: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("3"), + }, + Used: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("0"), + }, }, }, + status: v1.ResourceQuotaStatus{ + Hard: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("4"), + }, + Used: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("0"), + }, + }, + expectedActionSet: sets.NewString( + strings.Join([]string{"update", "resourcequotas", "status"}, "-"), + ), + items: []runtime.Object{}, }, - } - resourceQuota := v1.ResourceQuota{ - ObjectMeta: metav1.ObjectMeta{Name: "quota", Namespace: "testing"}, - Spec: v1.ResourceQuotaSpec{ - Hard: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("3"), - v1.ResourceMemory: resource.MustParse("100Gi"), - v1.ResourcePods: resource.MustParse("5"), + "quota-unchanged": { + gvr: v1.SchemeGroupVersion.WithResource("pods"), + quota: v1.ResourceQuota{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "rq", + }, + Spec: v1.ResourceQuotaSpec{ + Hard: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("4"), + }, + }, + Status: v1.ResourceQuotaStatus{ + Hard: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("0"), + }, + }, }, - }, - } - expectedUsage := v1.ResourceQuota{ - Status: v1.ResourceQuotaStatus{ - Hard: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("3"), - v1.ResourceMemory: resource.MustParse("100Gi"), - v1.ResourcePods: resource.MustParse("5"), - }, - Used: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("200m"), - v1.ResourceMemory: resource.MustParse("2Gi"), - v1.ResourcePods: resource.MustParse("2"), + status: v1.ResourceQuotaStatus{ + Hard: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("4"), + }, + Used: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("0"), + }, }, + expectedActionSet: sets.NewString(), + items: []runtime.Object{}, }, } - kubeClient := fake.NewSimpleClientset(&podList, &resourceQuota) - informerFactory := informers.NewSharedInformerFactory(kubeClient, controller.NoResyncPeriodFunc()) - resourceQuotaControllerOptions := &ResourceQuotaControllerOptions{ - QuotaClient: kubeClient.Core(), - ResourceQuotaInformer: informerFactory.Core().V1().ResourceQuotas(), - ResyncPeriod: controller.NoResyncPeriodFunc, - Registry: install.NewRegistry(kubeClient, nil), - GroupKindsToReplenish: []schema.GroupKind{ - api.Kind("Pod"), - api.Kind("Service"), - api.Kind("ReplicationController"), - api.Kind("PersistentVolumeClaim"), - }, - ControllerFactory: NewReplenishmentControllerFactory(informerFactory), - ReplenishmentResyncPeriod: controller.NoResyncPeriodFunc, - } - quotaController := NewResourceQuotaController(resourceQuotaControllerOptions) - err := quotaController.syncResourceQuota(&resourceQuota) - if err != nil { - t.Fatalf("Unexpected error %v", err) - } - expectedActionSet := sets.NewString( - strings.Join([]string{"list", "pods", ""}, "-"), - strings.Join([]string{"update", "resourcequotas", "status"}, "-"), - ) - actionSet := sets.NewString() - for _, action := range kubeClient.Actions() { - actionSet.Insert(strings.Join([]string{action.GetVerb(), action.GetResource().Resource, action.GetSubresource()}, "-")) - } - if !actionSet.HasAll(expectedActionSet.List()...) { - t.Errorf("Expected actions:\n%v\n but got:\n%v\nDifference:\n%v", expectedActionSet, actionSet, expectedActionSet.Difference(actionSet)) - } - - lastActionIndex := len(kubeClient.Actions()) - 1 - usage := kubeClient.Actions()[lastActionIndex].(core.UpdateAction).GetObject().(*v1.ResourceQuota) - - // ensure hard and used limits are what we expected - for k, v := range expectedUsage.Status.Hard { - actual := usage.Status.Hard[k] - actualValue := actual.String() - expectedValue := v.String() - if expectedValue != actualValue { - t.Errorf("Usage Hard: Key: %v, Expected: %v, Actual: %v", k, expectedValue, actualValue) + for testName, testCase := range testCases { + kubeClient := fake.NewSimpleClientset(&testCase.quota) + listersForResourceConfig := map[schema.GroupVersionResource]cache.GenericLister{ + testCase.gvr: newGenericLister(testCase.gvr.GroupResource(), testCase.items), } - } - for k, v := range expectedUsage.Status.Used { - actual := usage.Status.Used[k] - actualValue := actual.String() - expectedValue := v.String() - if expectedValue != actualValue { - t.Errorf("Usage Used: Key: %v, Expected: %v, Actual: %v", k, expectedValue, actualValue) + qc := setupQuotaController(t, kubeClient, mockListerForResourceFunc(listersForResourceConfig)) + defer close(qc.stop) + + if err := qc.syncResourceQuota(&testCase.quota); err != nil { + t.Fatalf("test: %s, unexpected error: %v", testName, err) } - } -} -func TestSyncResourceQuotaSpecChange(t *testing.T) { - resourceQuota := v1.ResourceQuota{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "rq", - }, - Spec: v1.ResourceQuotaSpec{ - Hard: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("4"), - }, - }, - Status: v1.ResourceQuotaStatus{ - Hard: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("3"), - }, - Used: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("0"), - }, - }, - } - - expectedUsage := v1.ResourceQuota{ - Status: v1.ResourceQuotaStatus{ - Hard: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("4"), - }, - Used: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("0"), - }, - }, - } - - kubeClient := fake.NewSimpleClientset(&resourceQuota) - informerFactory := informers.NewSharedInformerFactory(kubeClient, controller.NoResyncPeriodFunc()) - resourceQuotaControllerOptions := &ResourceQuotaControllerOptions{ - QuotaClient: kubeClient.Core(), - ResourceQuotaInformer: informerFactory.Core().V1().ResourceQuotas(), - ResyncPeriod: controller.NoResyncPeriodFunc, - Registry: install.NewRegistry(kubeClient, nil), - GroupKindsToReplenish: []schema.GroupKind{ - api.Kind("Pod"), - api.Kind("Service"), - api.Kind("ReplicationController"), - api.Kind("PersistentVolumeClaim"), - }, - ControllerFactory: NewReplenishmentControllerFactory(informerFactory), - ReplenishmentResyncPeriod: controller.NoResyncPeriodFunc, - } - quotaController := NewResourceQuotaController(resourceQuotaControllerOptions) - err := quotaController.syncResourceQuota(&resourceQuota) - if err != nil { - t.Fatalf("Unexpected error %v", err) - } - - expectedActionSet := sets.NewString( - strings.Join([]string{"list", "pods", ""}, "-"), - strings.Join([]string{"update", "resourcequotas", "status"}, "-"), - ) - actionSet := sets.NewString() - for _, action := range kubeClient.Actions() { - actionSet.Insert(strings.Join([]string{action.GetVerb(), action.GetResource().Resource, action.GetSubresource()}, "-")) - } - if !actionSet.HasAll(expectedActionSet.List()...) { - t.Errorf("Expected actions:\n%v\n but got:\n%v\nDifference:\n%v", expectedActionSet, actionSet, expectedActionSet.Difference(actionSet)) - } - - lastActionIndex := len(kubeClient.Actions()) - 1 - usage := kubeClient.Actions()[lastActionIndex].(core.UpdateAction).GetObject().(*v1.ResourceQuota) - - // ensure hard and used limits are what we expected - for k, v := range expectedUsage.Status.Hard { - actual := usage.Status.Hard[k] - actualValue := actual.String() - expectedValue := v.String() - if expectedValue != actualValue { - t.Errorf("Usage Hard: Key: %v, Expected: %v, Actual: %v", k, expectedValue, actualValue) + actionSet := sets.NewString() + for _, action := range kubeClient.Actions() { + actionSet.Insert(strings.Join([]string{action.GetVerb(), action.GetResource().Resource, action.GetSubresource()}, "-")) } - } - for k, v := range expectedUsage.Status.Used { - actual := usage.Status.Used[k] - actualValue := actual.String() - expectedValue := v.String() - if expectedValue != actualValue { - t.Errorf("Usage Used: Key: %v, Expected: %v, Actual: %v", k, expectedValue, actualValue) + if !actionSet.HasAll(testCase.expectedActionSet.List()...) { + t.Errorf("test: %s,\nExpected actions:\n%v\n but got:\n%v\nDifference:\n%v", testName, testCase.expectedActionSet, actionSet, testCase.expectedActionSet.Difference(actionSet)) } - } -} -func TestSyncResourceQuotaSpecHardChange(t *testing.T) { - resourceQuota := v1.ResourceQuota{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "rq", - }, - Spec: v1.ResourceQuotaSpec{ - Hard: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("4"), - }, - }, - Status: v1.ResourceQuotaStatus{ - Hard: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("3"), - v1.ResourceMemory: resource.MustParse("1Gi"), - }, - Used: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("0"), - v1.ResourceMemory: resource.MustParse("0"), - }, - }, - } + lastActionIndex := len(kubeClient.Actions()) - 1 + usage := kubeClient.Actions()[lastActionIndex].(core.UpdateAction).GetObject().(*v1.ResourceQuota) - expectedUsage := v1.ResourceQuota{ - Status: v1.ResourceQuotaStatus{ - Hard: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("4"), - }, - Used: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("0"), - }, - }, - } - - kubeClient := fake.NewSimpleClientset(&resourceQuota) - informerFactory := informers.NewSharedInformerFactory(kubeClient, controller.NoResyncPeriodFunc()) - resourceQuotaControllerOptions := &ResourceQuotaControllerOptions{ - QuotaClient: kubeClient.Core(), - ResourceQuotaInformer: informerFactory.Core().V1().ResourceQuotas(), - ResyncPeriod: controller.NoResyncPeriodFunc, - Registry: install.NewRegistry(kubeClient, nil), - GroupKindsToReplenish: []schema.GroupKind{ - api.Kind("Pod"), - api.Kind("Service"), - api.Kind("ReplicationController"), - api.Kind("PersistentVolumeClaim"), - }, - ControllerFactory: NewReplenishmentControllerFactory(informerFactory), - ReplenishmentResyncPeriod: controller.NoResyncPeriodFunc, - } - quotaController := NewResourceQuotaController(resourceQuotaControllerOptions) - err := quotaController.syncResourceQuota(&resourceQuota) - if err != nil { - t.Fatalf("Unexpected error %v", err) - } - - expectedActionSet := sets.NewString( - strings.Join([]string{"list", "pods", ""}, "-"), - strings.Join([]string{"update", "resourcequotas", "status"}, "-"), - ) - actionSet := sets.NewString() - for _, action := range kubeClient.Actions() { - actionSet.Insert(strings.Join([]string{action.GetVerb(), action.GetResource().Resource, action.GetSubresource()}, "-")) - } - if !actionSet.HasAll(expectedActionSet.List()...) { - t.Errorf("Expected actions:\n%v\n but got:\n%v\nDifference:\n%v", expectedActionSet, actionSet, expectedActionSet.Difference(actionSet)) - } - - lastActionIndex := len(kubeClient.Actions()) - 1 - usage := kubeClient.Actions()[lastActionIndex].(core.UpdateAction).GetObject().(*v1.ResourceQuota) - - // ensure hard and used limits are what we expected - for k, v := range expectedUsage.Status.Hard { - actual := usage.Status.Hard[k] - actualValue := actual.String() - expectedValue := v.String() - if expectedValue != actualValue { - t.Errorf("Usage Hard: Key: %v, Expected: %v, Actual: %v", k, expectedValue, actualValue) + // ensure usage is as expected + if len(usage.Status.Hard) != len(testCase.status.Hard) { + t.Errorf("test: %s, status hard lengths do not match", testName) } - } - for k, v := range expectedUsage.Status.Used { - actual := usage.Status.Used[k] - actualValue := actual.String() - expectedValue := v.String() - if expectedValue != actualValue { - t.Errorf("Usage Used: Key: %v, Expected: %v, Actual: %v", k, expectedValue, actualValue) + if len(usage.Status.Used) != len(testCase.status.Used) { + t.Errorf("test: %s, status used lengths do not match", testName) } - } - - // ensure usage hard and used are are synced with spec hard, not have dirty resource - for k, v := range usage.Status.Hard { - if k == v1.ResourceMemory { - t.Errorf("Unexpected Usage Hard: Key: %v, Value: %v", k, v.String()) + for k, v := range testCase.status.Hard { + actual := usage.Status.Hard[k] + actualValue := actual.String() + expectedValue := v.String() + if expectedValue != actualValue { + t.Errorf("test: %s, Usage Hard: Key: %v, Expected: %v, Actual: %v", testName, k, expectedValue, actualValue) + } } - } - - for k, v := range usage.Status.Used { - if k == v1.ResourceMemory { - t.Errorf("Unexpected Usage Used: Key: %v, Value: %v", k, v.String()) + for k, v := range testCase.status.Used { + actual := usage.Status.Used[k] + actualValue := actual.String() + expectedValue := v.String() + if expectedValue != actualValue { + t.Errorf("test: %s, Usage Used: Key: %v, Expected: %v, Actual: %v", testName, k, expectedValue, actualValue) + } } } } -func TestSyncResourceQuotaNoChange(t *testing.T) { - resourceQuota := v1.ResourceQuota{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "rq", - }, - Spec: v1.ResourceQuotaSpec{ - Hard: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("4"), - }, - }, - Status: v1.ResourceQuotaStatus{ - Hard: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("4"), - }, - Used: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("0"), - }, - }, - } - - kubeClient := fake.NewSimpleClientset(&v1.PodList{}, &resourceQuota) - informerFactory := informers.NewSharedInformerFactory(kubeClient, controller.NoResyncPeriodFunc()) - resourceQuotaControllerOptions := &ResourceQuotaControllerOptions{ - QuotaClient: kubeClient.Core(), - ResourceQuotaInformer: informerFactory.Core().V1().ResourceQuotas(), - ResyncPeriod: controller.NoResyncPeriodFunc, - Registry: install.NewRegistry(kubeClient, nil), - GroupKindsToReplenish: []schema.GroupKind{ - api.Kind("Pod"), - api.Kind("Service"), - api.Kind("ReplicationController"), - api.Kind("PersistentVolumeClaim"), - }, - ControllerFactory: NewReplenishmentControllerFactory(informerFactory), - ReplenishmentResyncPeriod: controller.NoResyncPeriodFunc, - } - quotaController := NewResourceQuotaController(resourceQuotaControllerOptions) - err := quotaController.syncResourceQuota(&resourceQuota) - if err != nil { - t.Fatalf("Unexpected error %v", err) - } - expectedActionSet := sets.NewString( - strings.Join([]string{"list", "pods", ""}, "-"), - ) - actionSet := sets.NewString() - for _, action := range kubeClient.Actions() { - actionSet.Insert(strings.Join([]string{action.GetVerb(), action.GetResource().Resource, action.GetSubresource()}, "-")) - } - if !actionSet.HasAll(expectedActionSet.List()...) { - t.Errorf("Expected actions:\n%v\n but got:\n%v\nDifference:\n%v", expectedActionSet, actionSet, expectedActionSet.Difference(actionSet)) - } -} - func TestAddQuota(t *testing.T) { kubeClient := fake.NewSimpleClientset() - informerFactory := informers.NewSharedInformerFactory(kubeClient, controller.NoResyncPeriodFunc()) - resourceQuotaControllerOptions := &ResourceQuotaControllerOptions{ - QuotaClient: kubeClient.Core(), - ResourceQuotaInformer: informerFactory.Core().V1().ResourceQuotas(), - ResyncPeriod: controller.NoResyncPeriodFunc, - Registry: install.NewRegistry(kubeClient, nil), - GroupKindsToReplenish: []schema.GroupKind{ - api.Kind("Pod"), - api.Kind("ReplicationController"), - api.Kind("PersistentVolumeClaim"), - }, - ControllerFactory: NewReplenishmentControllerFactory(informerFactory), - ReplenishmentResyncPeriod: controller.NoResyncPeriodFunc, + gvr := v1.SchemeGroupVersion.WithResource("pods") + listersForResourceConfig := map[schema.GroupVersionResource]cache.GenericLister{ + gvr: newGenericLister(gvr.GroupResource(), newTestPods()), } - quotaController := NewResourceQuotaController(resourceQuotaControllerOptions) - delete(quotaController.registry.(*generic.GenericRegistry).InternalEvaluators, api.Kind("Service")) + qc := setupQuotaController(t, kubeClient, mockListerForResourceFunc(listersForResourceConfig)) + defer close(qc.stop) testCases := []struct { - name string - + name string quota *v1.ResourceQuota expectedPriority bool }{ @@ -491,7 +361,7 @@ func TestAddQuota(t *testing.T) { }, }, { - name: "status, missing usage, but don't care", + name: "status, missing usage, but don't care (no informer)", expectedPriority: false, quota: &v1.ResourceQuota{ ObjectMeta: metav1.ObjectMeta{ @@ -500,12 +370,12 @@ func TestAddQuota(t *testing.T) { }, Spec: v1.ResourceQuotaSpec{ Hard: v1.ResourceList{ - v1.ResourceServices: resource.MustParse("4"), + "count/foobars.example.com": resource.MustParse("4"), }, }, Status: v1.ResourceQuotaStatus{ Hard: v1.ResourceList{ - v1.ResourceServices: resource.MustParse("4"), + "count/foobars.example.com": resource.MustParse("4"), }, }, }, @@ -536,30 +406,29 @@ func TestAddQuota(t *testing.T) { } for _, tc := range testCases { - quotaController.addQuota(tc.quota) + qc.addQuota(tc.quota) if tc.expectedPriority { - if e, a := 1, quotaController.missingUsageQueue.Len(); e != a { + if e, a := 1, qc.missingUsageQueue.Len(); e != a { t.Errorf("%s: expected %v, got %v", tc.name, e, a) } - if e, a := 0, quotaController.queue.Len(); e != a { + if e, a := 0, qc.queue.Len(); e != a { t.Errorf("%s: expected %v, got %v", tc.name, e, a) } } else { - if e, a := 0, quotaController.missingUsageQueue.Len(); e != a { + if e, a := 0, qc.missingUsageQueue.Len(); e != a { t.Errorf("%s: expected %v, got %v", tc.name, e, a) } - if e, a := 1, quotaController.queue.Len(); e != a { + if e, a := 1, qc.queue.Len(); e != a { t.Errorf("%s: expected %v, got %v", tc.name, e, a) } } - - for quotaController.missingUsageQueue.Len() > 0 { - key, _ := quotaController.missingUsageQueue.Get() - quotaController.missingUsageQueue.Done(key) + for qc.missingUsageQueue.Len() > 0 { + key, _ := qc.missingUsageQueue.Get() + qc.missingUsageQueue.Done(key) } - for quotaController.queue.Len() > 0 { - key, _ := quotaController.queue.Get() - quotaController.queue.Done(key) + for qc.queue.Len() > 0 { + key, _ := qc.queue.Get() + qc.queue.Done(key) } } } diff --git a/pkg/controller/resourcequota/resource_quota_monitor.go b/pkg/controller/resourcequota/resource_quota_monitor.go new file mode 100644 index 00000000000..e5d6dc593f9 --- /dev/null +++ b/pkg/controller/resourcequota/resource_quota_monitor.go @@ -0,0 +1,341 @@ +/* +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 resourcequota + +import ( + "fmt" + "sync" + "time" + + "github.com/golang/glog" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/clock" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/quota" + "k8s.io/kubernetes/pkg/quota/evaluator/core" + "k8s.io/kubernetes/pkg/quota/generic" +) + +type eventType int + +func (e eventType) String() string { + switch e { + case addEvent: + return "add" + case updateEvent: + return "update" + case deleteEvent: + return "delete" + default: + return fmt.Sprintf("unknown(%d)", int(e)) + } +} + +const ( + addEvent eventType = iota + updateEvent + deleteEvent +) + +type event struct { + eventType eventType + obj interface{} + oldObj interface{} + gvr schema.GroupVersionResource +} + +type QuotaMonitor struct { + // each monitor list/watches a resource and determines if we should replenish quota + monitors monitors + monitorLock sync.Mutex + // informersStarted is closed after after all of the controllers have been initialized and are running. + // After that it is safe to start them here, before that it is not. + informersStarted <-chan struct{} + + // stopCh drives shutdown. If it is nil, it indicates that Run() has not been + // called yet. If it is non-nil, then when closed it indicates everything + // should shut down. + // + // This channel is also protected by monitorLock. + stopCh <-chan struct{} + + // monitors are the producer of the resourceChanges queue + resourceChanges workqueue.RateLimitingInterface + + // interfaces with informers + informerFactory InformerFactory + + // list of resources to ignore + ignoredResources map[schema.GroupResource]struct{} + + // The period that should be used to re-sync the monitored resource + resyncPeriod controller.ResyncPeriodFunc + + // callback to alert that a change may require quota recalculation + replenishmentFunc ReplenishmentFunc + + // maintains list of evaluators + registry quota.Registry +} + +// monitor runs a Controller with a local stop channel. +type monitor struct { + controller cache.Controller + + // stopCh stops Controller. If stopCh is nil, the monitor is considered to be + // not yet started. + stopCh chan struct{} +} + +// Run is intended to be called in a goroutine. Multiple calls of this is an +// error. +func (m *monitor) Run() { + m.controller.Run(m.stopCh) +} + +type monitors map[schema.GroupVersionResource]*monitor + +func (qm *QuotaMonitor) controllerFor(resource schema.GroupVersionResource) (cache.Controller, error) { + // TODO: pass this down + clock := clock.RealClock{} + handlers := cache.ResourceEventHandlerFuncs{ + UpdateFunc: func(oldObj, newObj interface{}) { + // TODO: leaky abstraction! live w/ it for now, but should pass down an update filter func. + // we only want to queue the updates we care about though as too much noise will overwhelm queue. + notifyUpdate := false + switch resource.GroupResource() { + case schema.GroupResource{Resource: "pods"}: + oldPod := oldObj.(*v1.Pod) + newPod := newObj.(*v1.Pod) + notifyUpdate = core.QuotaV1Pod(oldPod, clock) && !core.QuotaV1Pod(newPod, clock) + case schema.GroupResource{Resource: "services"}: + oldService := oldObj.(*v1.Service) + newService := newObj.(*v1.Service) + notifyUpdate = core.GetQuotaServiceType(oldService) != core.GetQuotaServiceType(newService) + } + if notifyUpdate { + event := &event{ + eventType: updateEvent, + obj: newObj, + oldObj: oldObj, + gvr: resource, + } + qm.resourceChanges.Add(event) + } + }, + DeleteFunc: func(obj interface{}) { + // delta fifo may wrap the object in a cache.DeletedFinalStateUnknown, unwrap it + if deletedFinalStateUnknown, ok := obj.(cache.DeletedFinalStateUnknown); ok { + obj = deletedFinalStateUnknown.Obj + } + event := &event{ + eventType: deleteEvent, + obj: obj, + gvr: resource, + } + qm.resourceChanges.Add(event) + }, + } + shared, err := qm.informerFactory.ForResource(resource) + if err == nil { + glog.V(4).Infof("QuotaMonitor using a shared informer for resource %q", resource.String()) + shared.Informer().AddEventHandlerWithResyncPeriod(handlers, qm.resyncPeriod()) + return shared.Informer().GetController(), nil + } + glog.V(4).Infof("QuotaMonitor unable to use a shared informer for resource %q: %v", resource.String(), 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. + return nil, fmt.Errorf("unable to monitor quota for resource %q", resource.String()) +} + +// syncMonitors rebuilds the monitor set according to the supplied resources, +// creating or deleting monitors as necessary. It will return any error +// encountered, but will make an attempt to create a monitor for each resource +// instead of immediately exiting on an error. It may be called before or after +// Run. Monitors are NOT started as part of the sync. To ensure all existing +// monitors are started, call startMonitors. +func (qm *QuotaMonitor) syncMonitors(resources map[schema.GroupVersionResource]struct{}) error { + qm.monitorLock.Lock() + defer qm.monitorLock.Unlock() + + toRemove := qm.monitors + if toRemove == nil { + toRemove = monitors{} + } + current := monitors{} + errs := []error{} + kept := 0 + added := 0 + for resource := range resources { + if _, ok := qm.ignoredResources[resource.GroupResource()]; ok { + continue + } + if m, ok := toRemove[resource]; ok { + current[resource] = m + delete(toRemove, resource) + kept++ + continue + } + c, err := qm.controllerFor(resource) + if err != nil { + errs = append(errs, fmt.Errorf("couldn't start monitor for resource %q: %v", resource, err)) + continue + } + + // check if we need to create an evaluator for this resource (if none previously registered) + evaluator := qm.registry.Get(resource.GroupResource()) + if evaluator == nil { + listerFunc := generic.ListerFuncForResourceFunc(qm.informerFactory.ForResource) + listResourceFunc := generic.ListResourceUsingListerFunc(listerFunc, resource) + evaluator = generic.NewObjectCountEvaluator(false, resource.GroupResource(), listResourceFunc, "") + qm.registry.Add(evaluator) + glog.Infof("QuotaMonitor created object count evaluator for %s", resource.GroupResource()) + } + + // track the monitor + current[resource] = &monitor{controller: c} + added++ + } + qm.monitors = current + + for _, monitor := range toRemove { + if monitor.stopCh != nil { + close(monitor.stopCh) + } + } + + glog.V(4).Infof("quota synced monitors; added %d, kept %d, removed %d", added, kept, len(toRemove)) + // NewAggregate returns nil if errs is 0-length + return utilerrors.NewAggregate(errs) +} + +// startMonitors ensures the current set of monitors are running. Any newly +// started monitors will also cause shared informers to be started. +// +// If called before Run, startMonitors does nothing (as there is no stop channel +// to support monitor/informer execution). +func (qm *QuotaMonitor) startMonitors() { + qm.monitorLock.Lock() + defer qm.monitorLock.Unlock() + + if qm.stopCh == nil { + return + } + + // we're waiting until after the informer start that happens once all the controllers are initialized. This ensures + // that they don't get unexpected events on their work queues. + <-qm.informersStarted + + monitors := qm.monitors + started := 0 + for _, monitor := range monitors { + if monitor.stopCh == nil { + monitor.stopCh = make(chan struct{}) + qm.informerFactory.Start(qm.stopCh) + go monitor.Run() + started++ + } + } + glog.V(4).Infof("QuotaMonitor started %d new monitors, %d currently running", started, len(monitors)) +} + +// IsSynced returns true if any monitors exist AND all those monitors' +// controllers HasSynced functions return true. This means IsSynced could return +// true at one time, and then later return false if all monitors were +// reconstructed. +func (qm *QuotaMonitor) IsSynced() bool { + qm.monitorLock.Lock() + defer qm.monitorLock.Unlock() + + if len(qm.monitors) == 0 { + return false + } + + for _, monitor := range qm.monitors { + if !monitor.controller.HasSynced() { + return false + } + } + return true +} + +// Run sets the stop channel and starts monitor execution until stopCh is +// closed. Any running monitors will be stopped before Run returns. +func (qm *QuotaMonitor) Run(stopCh <-chan struct{}) { + glog.Infof("QuotaMonitor running") + defer glog.Infof("QuotaMonitor stopping") + + // Set up the stop channel. + qm.monitorLock.Lock() + qm.stopCh = stopCh + qm.monitorLock.Unlock() + + // Start monitors and begin change processing until the stop channel is + // closed. + qm.startMonitors() + wait.Until(qm.runProcessResourceChanges, 1*time.Second, stopCh) + + // Stop any running monitors. + qm.monitorLock.Lock() + defer qm.monitorLock.Unlock() + monitors := qm.monitors + stopped := 0 + for _, monitor := range monitors { + if monitor.stopCh != nil { + stopped++ + close(monitor.stopCh) + } + } + glog.Infof("QuotaMonitor stopped %d of %d monitors", stopped, len(monitors)) +} + +func (qm *QuotaMonitor) runProcessResourceChanges() { + for qm.processResourceChanges() { + } +} + +// Dequeueing an event from resourceChanges to process +func (qm *QuotaMonitor) processResourceChanges() bool { + item, quit := qm.resourceChanges.Get() + if quit { + return false + } + defer qm.resourceChanges.Done(item) + event, ok := item.(*event) + if !ok { + utilruntime.HandleError(fmt.Errorf("expect a *event, got %v", item)) + return true + } + obj := event.obj + accessor, err := meta.Accessor(obj) + if err != nil { + utilruntime.HandleError(fmt.Errorf("cannot access obj: %v", err)) + return true + } + glog.V(4).Infof("QuotaMonitor process object: %s, namespace %s, name %s, uid %s, event type %v", event.gvr.String(), accessor.GetNamespace(), accessor.GetName(), string(accessor.GetUID()), event.eventType) + qm.replenishmentFunc(event.gvr.GroupResource(), accessor.GetNamespace()) + return true +} diff --git a/pkg/controller/route/BUILD b/pkg/controller/route/BUILD index 02efb7343d9..9f9fea10732 100644 --- a/pkg/controller/route/BUILD +++ b/pkg/controller/route/BUILD @@ -40,8 +40,8 @@ go_library( go_test( name = "go_default_test", srcs = ["route_controller_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/route", - library = ":go_default_library", deps = [ "//pkg/api/v1/node:go_default_library", "//pkg/cloudprovider:go_default_library", diff --git a/pkg/controller/route/route_controller.go b/pkg/controller/route/route_controller.go index bb6995d775e..9a399a02709 100644 --- a/pkg/controller/route/route_controller.go +++ b/pkg/controller/route/route_controller.go @@ -66,8 +66,8 @@ type RouteController struct { } func New(routes cloudprovider.Routes, kubeClient clientset.Interface, nodeInformer coreinformers.NodeInformer, clusterName string, clusterCIDR *net.IPNet) *RouteController { - if kubeClient != nil && kubeClient.Core().RESTClient().GetRateLimiter() != nil { - metrics.RegisterMetricAndTrackRateLimiterUsage("route_controller", kubeClient.Core().RESTClient().GetRateLimiter()) + if kubeClient != nil && kubeClient.CoreV1().RESTClient().GetRateLimiter() != nil { + metrics.RegisterMetricAndTrackRateLimiterUsage("route_controller", kubeClient.CoreV1().RESTClient().GetRateLimiter()) } if clusterCIDR == nil { @@ -75,7 +75,8 @@ func New(routes cloudprovider.Routes, kubeClient clientset.Interface, nodeInform } eventBroadcaster := record.NewBroadcaster() - recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "route-controller"}) + eventBroadcaster.StartLogging(glog.Infof) + recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "route_controller"}) rc := &RouteController{ routes: routes, @@ -102,7 +103,7 @@ func (rc *RouteController) Run(stopCh <-chan struct{}, syncPeriod time.Duration) } if rc.broadcaster != nil { - rc.broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(rc.kubeClient.Core().RESTClient()).Events("")}) + rc.broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(rc.kubeClient.CoreV1().RESTClient()).Events("")}) } // TODO: If we do just the full Resync every 5 minutes (default value) @@ -114,7 +115,7 @@ func (rc *RouteController) Run(stopCh <-chan struct{}, syncPeriod time.Duration) if err := rc.reconcileNodeRoutes(); err != nil { glog.Errorf("Couldn't reconcile node routes: %v", err) } - }, syncPeriod, wait.NeverStop) + }, syncPeriod, stopCh) <-stopCh } @@ -254,7 +255,7 @@ func (rc *RouteController) updateNetworkingCondition(nodeName types.NodeName, ro glog.Errorf("Error updating node %s: %v", nodeName, err) return err } - glog.Errorf("Error updating node %s, retrying: %v", nodeName, err) + glog.V(4).Infof("Error updating node %s, retrying: %v", nodeName, err) } glog.Errorf("Error updating node %s: %v", nodeName, err) return err diff --git a/pkg/controller/service/BUILD b/pkg/controller/service/BUILD index 8e37fd9612c..459108f05ac 100644 --- a/pkg/controller/service/BUILD +++ b/pkg/controller/service/BUILD @@ -14,15 +14,17 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/controller/service", deps = [ - "//pkg/api/v1/helper:go_default_library", + "//pkg/apis/core/v1/helper:go_default_library", "//pkg/cloudprovider:go_default_library", "//pkg/controller:go_default_library", + "//pkg/features:go_default_library", "//pkg/util/metrics:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", "//vendor/k8s.io/client-go/informers/core/v1:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library", @@ -37,8 +39,8 @@ go_library( go_test( name = "go_default_test", srcs = ["service_controller_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/service", - library = ":go_default_library", deps = [ "//pkg/api/testapi:go_default_library", "//pkg/cloudprovider/providers/fake:go_default_library", diff --git a/pkg/controller/service/OWNERS b/pkg/controller/service/OWNERS index fcaf2646616..d1170c74d0f 100644 --- a/pkg/controller/service/OWNERS +++ b/pkg/controller/service/OWNERS @@ -3,3 +3,8 @@ reviewers: - MrHohn - thockin - matchstick +approvers: +- bowei +- MrHohn +- thockin +- matchstick diff --git a/pkg/controller/service/service_controller.go b/pkg/controller/service/service_controller.go index 0f7c1f686b8..3496f0681ca 100644 --- a/pkg/controller/service/service_controller.go +++ b/pkg/controller/service/service_controller.go @@ -29,6 +29,7 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "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" "k8s.io/client-go/kubernetes/scheme" @@ -37,9 +38,10 @@ import ( "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" "k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/controller" + kubefeatures "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/util/metrics" ) @@ -69,7 +71,7 @@ const ( // LabelNodeRoleExcludeBalancer specifies that the node should be // exclude from load balancers created by a cloud provider. - LabelNodeRoleExcludeBalancer = "alpha.node.role.kubernetes.io/exclude-balancer" + LabelNodeRoleExcludeBalancer = "alpha.service-controller.kubernetes.io/exclude-balancer" ) type cachedService struct { @@ -112,12 +114,14 @@ func New( clusterName string, ) (*ServiceController, error) { broadcaster := record.NewBroadcaster() - broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.Core().RESTClient()).Events("")}) - recorder := broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "service-controller"}) broadcaster.StartLogging(glog.Infof) + broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.CoreV1().RESTClient()).Events("")}) + recorder := broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "service-controller"}) - if kubeClient != nil && kubeClient.Core().RESTClient().GetRateLimiter() != nil { - metrics.RegisterMetricAndTrackRateLimiterUsage("service_controller", kubeClient.Core().RESTClient().GetRateLimiter()) + if kubeClient != nil && kubeClient.CoreV1().RESTClient().GetRateLimiter() != nil { + if err := metrics.RegisterMetricAndTrackRateLimiterUsage("service_controller", kubeClient.CoreV1().RESTClient().GetRateLimiter()); err != nil { + return nil, err + } } s := &ServiceController{ @@ -184,6 +188,7 @@ func (s *ServiceController) Run(stopCh <-chan struct{}, workers int) { defer glog.Info("Shutting down service controller") if !controller.WaitForCacheSync("service", stopCh, s.serviceListerSynced, s.nodeListerSynced) { + return } for i := 0; i < workers; i++ { @@ -215,12 +220,12 @@ func (s *ServiceController) worker() { func (s *ServiceController) init() error { if s.cloud == nil { - return fmt.Errorf("WARNING: no cloud provider provided, services of type LoadBalancer will fail.") + return fmt.Errorf("WARNING: no cloud provider provided, services of type LoadBalancer will fail") } balancer, ok := s.cloud.LoadBalancer() if !ok { - return fmt.Errorf("the cloud provider does not support external load balancers.") + return fmt.Errorf("the cloud provider does not support external load balancers") } s.balancer = balancer @@ -244,15 +249,17 @@ func (s *ServiceController) processServiceUpdate(cachedService *cachedService, s err, retry := s.createLoadBalancerIfNeeded(key, service) if err != nil { message := "Error creating load balancer" + var retryToReturn time.Duration if retry { message += " (will retry): " + retryToReturn = cachedService.nextRetryDelay() } else { message += " (will not retry): " + retryToReturn = doNotRetry } message += err.Error() s.eventRecorder.Event(service, v1.EventTypeWarning, "CreatingLoadBalancerFailed", message) - - return err, cachedService.nextRetryDelay() + return err, retryToReturn } // Always update the cache upon success. // NOTE: Since we update the cached service if and only if we successfully @@ -278,7 +285,7 @@ func (s *ServiceController) createLoadBalancerIfNeeded(key string, service *v1.S if !wantsLoadBalancer(service) { _, exists, err := s.balancer.GetLoadBalancer(s.clusterName, service) if err != nil { - return fmt.Errorf("Error getting LB for service %s: %v", key, err), retryable + return fmt.Errorf("error getting LB for service %s: %v", key, err), retryable } if exists { glog.Infof("Deleting existing load balancer for service %s that no longer needs a load balancer.", key) @@ -298,7 +305,7 @@ func (s *ServiceController) createLoadBalancerIfNeeded(key string, service *v1.S s.eventRecorder.Event(service, v1.EventTypeNormal, "EnsuringLoadBalancer", "Ensuring load balancer") newState, err = s.ensureLoadBalancer(service) if err != nil { - return fmt.Errorf("Failed to ensure load balancer for service %s: %v", key, err), retryable + return fmt.Errorf("failed to ensure load balancer for service %s: %v", key, err), retryable } s.eventRecorder.Event(service, v1.EventTypeNormal, "EnsuredLoadBalancer", "Ensured load balancer") } @@ -313,7 +320,7 @@ func (s *ServiceController) createLoadBalancerIfNeeded(key string, service *v1.S service.Status.LoadBalancer = *newState if err := s.persistUpdate(service); err != nil { - return fmt.Errorf("Failed to persist updated status to apiserver, even after retries. Giving up: %v", err), notRetryable + return fmt.Errorf("failed to persist updated status to apiserver, even after retries. Giving up: %v", err), notRetryable } } else { glog.V(2).Infof("Not persisting unchanged LoadBalancerStatus for service %s to registry.", key) @@ -325,7 +332,7 @@ func (s *ServiceController) createLoadBalancerIfNeeded(key string, service *v1.S func (s *ServiceController) persistUpdate(service *v1.Service) error { var err error for i := 0; i < clientRetryCount; i++ { - _, err = s.kubeClient.Core().Services(service.Namespace).UpdateStatus(service) + _, err = s.kubeClient.CoreV1().Services(service.Namespace).UpdateStatus(service) if err == nil { return nil } @@ -340,7 +347,7 @@ func (s *ServiceController) persistUpdate(service *v1.Service) error { // TODO: Try to resolve the conflict if the change was unrelated to load // balancer status. For now, just pass it up the stack. if errors.IsConflict(err) { - return fmt.Errorf("Not persisting update to service '%s/%s' that has been changed since we received it: %v", + return fmt.Errorf("not persisting update to service '%s/%s' that has been changed since we received it: %v", service.Namespace, service.Name, err) } glog.Warningf("Failed to persist updated LoadBalancerStatus to service '%s/%s' after creating its load balancer: %v", @@ -356,6 +363,11 @@ func (s *ServiceController) ensureLoadBalancer(service *v1.Service) (*v1.LoadBal return nil, err } + // If there are no available nodes for LoadBalancer service, make a EventTypeWarning event for it. + if len(nodes) == 0 { + s.eventRecorder.Eventf(service, v1.EventTypeWarning, "UnAvailableLoadBalancer", "There are no available nodes for LoadBalancer service %s/%s", service.Namespace, service.Name) + } + // - Only one protocol supported per service // - Not all cloud providers support all protocols and the next step is expected to return // an error for unsupported protocols @@ -500,7 +512,7 @@ func getPortsForLB(service *v1.Service) ([]*v1.ServicePort, error) { protocol = sp.Protocol } else if protocol != sp.Protocol && wantsLoadBalancer(service) { // TODO: Convert error messages to use event recorder - return nil, fmt.Errorf("mixed protocol external load balancers are not supported.") + return nil, fmt.Errorf("mixed protocol external load balancers are not supported") } } return ports, nil @@ -588,10 +600,6 @@ func stringSlicesEqual(x, y []string) bool { return true } -func includeNodeFromNodeList(node *v1.Node) bool { - return !node.Spec.Unschedulable -} - func getNodeConditionPredicate() corelisters.NodeConditionPredicate { return func(node *v1.Node) bool { // We add the master to the node list, but its unschedulable. So we use this to filter @@ -606,8 +614,10 @@ func getNodeConditionPredicate() corelisters.NodeConditionPredicate { return false } - if _, hasExcludeBalancerLabel := node.Labels[LabelNodeRoleExcludeBalancer]; hasExcludeBalancerLabel { - return false + if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.ServiceNodeExclusion) { + if _, hasExcludeBalancerLabel := node.Labels[LabelNodeRoleExcludeBalancer]; hasExcludeBalancerLabel { + return false + } } // If we have no info, don't accept @@ -683,7 +693,12 @@ func (s *ServiceController) lockedUpdateLoadBalancerHosts(service *v1.Service, h // This operation doesn't normally take very long (and happens pretty often), so we only record the final event err := s.balancer.UpdateLoadBalancer(s.clusterName, service, hosts) if err == nil { - s.eventRecorder.Event(service, v1.EventTypeNormal, "UpdatedLoadBalancer", "Updated load balancer with new hosts") + // If there are no available nodes for LoadBalancer service, make a EventTypeWarning event for it. + if len(hosts) == 0 { + s.eventRecorder.Eventf(service, v1.EventTypeWarning, "UnAvailableLoadBalancer", "There are no available nodes for LoadBalancer service %s/%s", service.Namespace, service.Name) + } else { + s.eventRecorder.Event(service, v1.EventTypeNormal, "UpdatedLoadBalancer", "Updated load balancer with new hosts") + } return nil } @@ -732,7 +747,7 @@ func (s *ServiceController) syncService(key string) error { var cachedService *cachedService var retryDelay time.Duration defer func() { - glog.V(4).Infof("Finished syncing service %q (%v)", key, time.Now().Sub(startTime)) + glog.V(4).Infof("Finished syncing service %q (%v)", key, time.Since(startTime)) }() namespace, name, err := cache.SplitMetaNamespaceKey(key) @@ -758,7 +773,7 @@ func (s *ServiceController) syncService(key string) error { if retryDelay != 0 { // Add the failed service back to the queue so we'll retry it. - glog.Errorf("Failed to process service. Retrying in %s: %v", retryDelay, err) + glog.Errorf("Failed to process service %v. Retrying in %s: %v", key, retryDelay, err) go func(obj interface{}, delay time.Duration) { // put back the service key to working queue, it is possible that more entries of the service // were added into the queue during the delay, but it does not mess as when handling the retry, @@ -766,7 +781,7 @@ func (s *ServiceController) syncService(key string) error { s.workingQueue.AddAfter(obj, delay) }(key, retryDelay) } else if err != nil { - runtime.HandleError(fmt.Errorf("Failed to process service. Not retrying: %v", err)) + runtime.HandleError(fmt.Errorf("failed to process service %v. Not retrying: %v", key, err)) } return nil } @@ -777,7 +792,7 @@ func (s *ServiceController) syncService(key string) error { func (s *ServiceController) processServiceDeletion(key string) (error, time.Duration) { cachedService, ok := s.cache.get(key) if !ok { - return fmt.Errorf("Service %s not in cache even though the watcher thought it was. Ignoring the deletion.", key), doNotRetry + return fmt.Errorf("service %s not in cache even though the watcher thought it was. Ignoring the deletion", key), doNotRetry } return s.processLoadBalancerDelete(cachedService, key) } diff --git a/pkg/controller/service/service_controller_test.go b/pkg/controller/service/service_controller_test.go index be5e8ddde2d..0c4990adb1a 100644 --- a/pkg/controller/service/service_controller_test.go +++ b/pkg/controller/service/service_controller_test.go @@ -506,7 +506,7 @@ func TestProcessServiceDeletion(t *testing.T) { }, expectedFn: func(svcErr error, retryDuration time.Duration) error { - expectedError := "Service external-balancer not in cache even though the watcher thought it was. Ignoring the deletion." + expectedError := "service external-balancer not in cache even though the watcher thought it was. Ignoring the deletion" if svcErr == nil || svcErr.Error() != expectedError { //cannot be nil or Wrong error message return fmt.Errorf("Expected=%v Obtained=%v", expectedError, svcErr) diff --git a/pkg/controller/serviceaccount/BUILD b/pkg/controller/serviceaccount/BUILD index 85d5012bbde..dc5bc8fd5fe 100644 --- a/pkg/controller/serviceaccount/BUILD +++ b/pkg/controller/serviceaccount/BUILD @@ -16,7 +16,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/controller/serviceaccount", deps = [ - "//pkg/api/v1:go_default_library", + "//pkg/apis/core/v1:go_default_library", "//pkg/controller:go_default_library", "//pkg/registry/core/secret:go_default_library", "//pkg/registry/core/secret/storage:go_default_library", @@ -51,10 +51,10 @@ go_test( "serviceaccounts_controller_test.go", "tokens_controller_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/serviceaccount", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/controller:go_default_library", "//vendor/github.com/davecgh/go-spew/spew:go_default_library", "//vendor/github.com/golang/glog:go_default_library", diff --git a/pkg/controller/serviceaccount/serviceaccounts_controller.go b/pkg/controller/serviceaccount/serviceaccounts_controller.go index a84ce61382d..e0c41b82f8c 100644 --- a/pkg/controller/serviceaccount/serviceaccounts_controller.go +++ b/pkg/controller/serviceaccount/serviceaccounts_controller.go @@ -61,14 +61,16 @@ func DefaultServiceAccountsControllerOptions() ServiceAccountsControllerOptions } // NewServiceAccountsController returns a new *ServiceAccountsController. -func NewServiceAccountsController(saInformer coreinformers.ServiceAccountInformer, nsInformer coreinformers.NamespaceInformer, cl clientset.Interface, options ServiceAccountsControllerOptions) *ServiceAccountsController { +func NewServiceAccountsController(saInformer coreinformers.ServiceAccountInformer, nsInformer coreinformers.NamespaceInformer, cl clientset.Interface, options ServiceAccountsControllerOptions) (*ServiceAccountsController, error) { e := &ServiceAccountsController{ client: cl, serviceAccountsToEnsure: options.ServiceAccounts, queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "serviceaccount"), } - if cl != nil && cl.Core().RESTClient().GetRateLimiter() != nil { - metrics.RegisterMetricAndTrackRateLimiterUsage("serviceaccount_controller", cl.Core().RESTClient().GetRateLimiter()) + if cl != nil && cl.CoreV1().RESTClient().GetRateLimiter() != nil { + if err := metrics.RegisterMetricAndTrackRateLimiterUsage("serviceaccount_controller", cl.CoreV1().RESTClient().GetRateLimiter()); err != nil { + return nil, err + } } saInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ @@ -86,7 +88,7 @@ func NewServiceAccountsController(saInformer coreinformers.ServiceAccountInforme e.syncHandler = e.syncNamespace - return e + return e, nil } // ServiceAccountsController manages ServiceAccount objects inside Namespaces @@ -210,7 +212,7 @@ func (c *ServiceAccountsController) syncNamespace(key string) error { // TODO eliminate this once the fake client can handle creation without NS sa.Namespace = ns.Name - if _, err := c.client.Core().ServiceAccounts(ns.Name).Create(&sa); err != nil && !apierrs.IsAlreadyExists(err) { + if _, err := c.client.CoreV1().ServiceAccounts(ns.Name).Create(&sa); err != nil && !apierrs.IsAlreadyExists(err) { createFailures = append(createFailures, err) } } diff --git a/pkg/controller/serviceaccount/serviceaccounts_controller_test.go b/pkg/controller/serviceaccount/serviceaccounts_controller_test.go index 83589b084ef..969f8a17c05 100644 --- a/pkg/controller/serviceaccount/serviceaccounts_controller_test.go +++ b/pkg/controller/serviceaccount/serviceaccounts_controller_test.go @@ -165,12 +165,15 @@ func TestServiceAccountCreation(t *testing.T) { } saInformer := informers.Core().V1().ServiceAccounts() nsInformer := informers.Core().V1().Namespaces() - controller := NewServiceAccountsController( + controller, err := NewServiceAccountsController( saInformer, nsInformer, client, options, ) + if err != nil { + t.Fatalf("error creating ServiceAccounts controller: %v", err) + } controller.saListerSynced = alwaysReady controller.nsListerSynced = alwaysReady diff --git a/pkg/controller/serviceaccount/tokengetter.go b/pkg/controller/serviceaccount/tokengetter.go index e81f1381573..d21f578b29c 100644 --- a/pkg/controller/serviceaccount/tokengetter.go +++ b/pkg/controller/serviceaccount/tokengetter.go @@ -23,7 +23,7 @@ import ( "k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/storage/storagebackend" clientset "k8s.io/client-go/kubernetes" - apiv1 "k8s.io/kubernetes/pkg/api/v1" + apiv1 "k8s.io/kubernetes/pkg/apis/core/v1" "k8s.io/kubernetes/pkg/registry/core/secret" secretstore "k8s.io/kubernetes/pkg/registry/core/secret/storage" serviceaccountregistry "k8s.io/kubernetes/pkg/registry/core/serviceaccount" @@ -44,10 +44,10 @@ func NewGetterFromClient(c clientset.Interface) serviceaccount.ServiceAccountTok return clientGetter{c} } func (c clientGetter) GetServiceAccount(namespace, name string) (*v1.ServiceAccount, error) { - return c.client.Core().ServiceAccounts(namespace).Get(name, metav1.GetOptions{}) + return c.client.CoreV1().ServiceAccounts(namespace).Get(name, metav1.GetOptions{}) } func (c clientGetter) GetSecret(namespace, name string) (*v1.Secret, error) { - return c.client.Core().Secrets(namespace).Get(name, metav1.GetOptions{}) + return c.client.CoreV1().Secrets(namespace).Get(name, metav1.GetOptions{}) } // registryGetter implements ServiceAccountTokenGetter using a service account and secret registry @@ -68,7 +68,7 @@ func (r *registryGetter) GetServiceAccount(namespace, name string) (*v1.ServiceA return nil, err } v1ServiceAccount := v1.ServiceAccount{} - err = apiv1.Convert_api_ServiceAccount_To_v1_ServiceAccount(internalServiceAccount, &v1ServiceAccount, nil) + err = apiv1.Convert_core_ServiceAccount_To_v1_ServiceAccount(internalServiceAccount, &v1ServiceAccount, nil) return &v1ServiceAccount, err } @@ -79,7 +79,7 @@ func (r *registryGetter) GetSecret(namespace, name string) (*v1.Secret, error) { return nil, err } v1Secret := v1.Secret{} - err = apiv1.Convert_api_Secret_To_v1_Secret(internalSecret, &v1Secret, nil) + err = apiv1.Convert_core_Secret_To_v1_Secret(internalSecret, &v1Secret, nil) return &v1Secret, err } diff --git a/pkg/controller/serviceaccount/tokens_controller.go b/pkg/controller/serviceaccount/tokens_controller.go index f8d5851101c..c34e600ef11 100644 --- a/pkg/controller/serviceaccount/tokens_controller.go +++ b/pkg/controller/serviceaccount/tokens_controller.go @@ -70,7 +70,7 @@ type TokensControllerOptions struct { } // NewTokensController returns a new *TokensController. -func NewTokensController(serviceAccounts informers.ServiceAccountInformer, secrets informers.SecretInformer, cl clientset.Interface, options TokensControllerOptions) *TokensController { +func NewTokensController(serviceAccounts informers.ServiceAccountInformer, secrets informers.SecretInformer, cl clientset.Interface, options TokensControllerOptions) (*TokensController, error) { maxRetries := options.MaxRetries if maxRetries == 0 { maxRetries = 10 @@ -86,8 +86,10 @@ func NewTokensController(serviceAccounts informers.ServiceAccountInformer, secre maxRetries: maxRetries, } - if cl != nil && cl.Core().RESTClient().GetRateLimiter() != nil { - metrics.RegisterMetricAndTrackRateLimiterUsage("serviceaccount_tokens_controller", cl.Core().RESTClient().GetRateLimiter()) + if cl != nil && cl.CoreV1().RESTClient().GetRateLimiter() != nil { + if err := metrics.RegisterMetricAndTrackRateLimiterUsage("serviceaccount_tokens_controller", cl.CoreV1().RESTClient().GetRateLimiter()); err != nil { + return nil, err + } } e.serviceAccounts = serviceAccounts.Lister() @@ -124,7 +126,7 @@ func NewTokensController(serviceAccounts informers.ServiceAccountInformer, secre options.SecretResync, ) - return e + return e, nil } // TokensController manages ServiceAccountToken secrets for ServiceAccount objects @@ -344,7 +346,7 @@ func (e *TokensController) deleteToken(ns, name string, uid types.UID) ( /*retry if len(uid) > 0 { opts = &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &uid}} } - err := e.client.Core().Secrets(ns).Delete(name, opts) + err := e.client.CoreV1().Secrets(ns).Delete(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) { @@ -366,7 +368,7 @@ func (e *TokensController) ensureReferencedToken(serviceAccount *v1.ServiceAccou // We don't want to update the cache's copy of the service account // so add the secret to a freshly retrieved copy of the service account - serviceAccounts := e.client.Core().ServiceAccounts(serviceAccount.Namespace) + serviceAccounts := e.client.CoreV1().ServiceAccounts(serviceAccount.Namespace) liveServiceAccount, err := serviceAccounts.Get(serviceAccount.Name, metav1.GetOptions{}) if err != nil { // Retry if we cannot fetch the live service account (for a NotFound error, either the live lookup or our cache are stale) @@ -405,7 +407,7 @@ func (e *TokensController) ensureReferencedToken(serviceAccount *v1.ServiceAccou } // Save the secret - createdToken, err := e.client.Core().Secrets(serviceAccount.Namespace).Create(secret) + createdToken, err := e.client.CoreV1().Secrets(serviceAccount.Namespace).Create(secret) if err != nil { // retriable error return true, err @@ -455,7 +457,7 @@ func (e *TokensController) ensureReferencedToken(serviceAccount *v1.ServiceAccou // we weren't able to use the token, try to clean it up. glog.V(2).Infof("deleting secret %s/%s because reference couldn't be added (%v)", secret.Namespace, secret.Name, err) deleteOpts := &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &createdToken.UID}} - if deleteErr := e.client.Core().Secrets(createdToken.Namespace).Delete(createdToken.Name, deleteOpts); deleteErr != nil { + if deleteErr := e.client.CoreV1().Secrets(createdToken.Namespace).Delete(createdToken.Name, deleteOpts); deleteErr != nil { glog.Error(deleteErr) // if we fail, just log it } } @@ -513,7 +515,7 @@ func (e *TokensController) generateTokenIfNeeded(serviceAccount *v1.ServiceAccou // 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.Core().Secrets(cachedSecret.Namespace) + secrets := e.client.CoreV1().Secrets(cachedSecret.Namespace) liveSecret, err := secrets.Get(cachedSecret.Name, metav1.GetOptions{}) if err != nil { // Retry for any error other than a NotFound @@ -577,7 +579,7 @@ func (e *TokensController) generateTokenIfNeeded(serviceAccount *v1.ServiceAccou func (e *TokensController) removeSecretReference(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.Core().ServiceAccounts(saNamespace) + serviceAccounts := e.client.CoreV1().ServiceAccounts(saNamespace) serviceAccount, err := serviceAccounts.Get(saName, metav1.GetOptions{}) // Ignore NotFound errors when attempting to remove a reference if apierrors.IsNotFound(err) { @@ -631,7 +633,7 @@ func (e *TokensController) getServiceAccount(ns string, name string, uid types.U } // Live lookup - sa, err = e.client.Core().ServiceAccounts(ns).Get(name, metav1.GetOptions{}) + sa, err = e.client.CoreV1().ServiceAccounts(ns).Get(name, metav1.GetOptions{}) if apierrors.IsNotFound(err) { return nil, nil } @@ -667,7 +669,7 @@ func (e *TokensController) getSecret(ns string, name string, uid types.UID, fetc } // Live lookup - secret, err := e.client.Core().Secrets(ns).Get(name, metav1.GetOptions{}) + secret, err := e.client.CoreV1().Secrets(ns).Get(name, metav1.GetOptions{}) if apierrors.IsNotFound(err) { return nil, nil } diff --git a/pkg/controller/serviceaccount/tokens_controller_test.go b/pkg/controller/serviceaccount/tokens_controller_test.go index f9ffe53b297..bacf60500cd 100644 --- a/pkg/controller/serviceaccount/tokens_controller_test.go +++ b/pkg/controller/serviceaccount/tokens_controller_test.go @@ -34,7 +34,7 @@ import ( "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" core "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/controller" ) @@ -586,7 +586,10 @@ func TestTokenCreation(t *testing.T) { secretInformer := informers.Core().V1().Secrets().Informer() secrets := secretInformer.GetStore() serviceAccounts := informers.Core().V1().ServiceAccounts().Informer().GetStore() - controller := NewTokensController(informers.Core().V1().ServiceAccounts(), informers.Core().V1().Secrets(), client, TokensControllerOptions{TokenGenerator: generator, RootCA: []byte("CA Data"), MaxRetries: tc.MaxRetries}) + controller, err := NewTokensController(informers.Core().V1().ServiceAccounts(), informers.Core().V1().Secrets(), client, TokensControllerOptions{TokenGenerator: generator, RootCA: []byte("CA Data"), MaxRetries: tc.MaxRetries}) + if err != nil { + t.Fatalf("error creating Tokens controller: %v", err) + } if tc.ExistingServiceAccount != nil { serviceAccounts.Add(tc.ExistingServiceAccount) diff --git a/pkg/controller/statefulset/BUILD b/pkg/controller/statefulset/BUILD index d2203271faf..b38f1119d94 100644 --- a/pkg/controller/statefulset/BUILD +++ b/pkg/controller/statefulset/BUILD @@ -54,12 +54,12 @@ go_test( "stateful_set_test.go", "stateful_set_utils_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/statefulset", - library = ":go_default_library", deps = [ - "//pkg/api/install:go_default_library", "//pkg/api/v1/pod:go_default_library", "//pkg/apis/apps/install:go_default_library", + "//pkg/apis/core/install:go_default_library", "//pkg/controller:go_default_library", "//pkg/controller/history:go_default_library", "//vendor/k8s.io/api/apps/v1beta1:go_default_library", diff --git a/pkg/controller/statefulset/stateful_pod_control.go b/pkg/controller/statefulset/stateful_pod_control.go index ef33a78b16b..fff08046297 100644 --- a/pkg/controller/statefulset/stateful_pod_control.go +++ b/pkg/controller/statefulset/stateful_pod_control.go @@ -77,7 +77,7 @@ func (spc *realStatefulPodControl) CreateStatefulPod(set *apps.StatefulSet, pod return err } // If we created the PVCs attempt to create the Pod - _, err := spc.client.Core().Pods(set.Namespace).Create(pod) + _, err := spc.client.CoreV1().Pods(set.Namespace).Create(pod) // sink already exists errors if apierrors.IsAlreadyExists(err) { return err @@ -113,7 +113,7 @@ func (spc *realStatefulPodControl) UpdateStatefulPod(set *apps.StatefulSet, pod attemptedUpdate = true // commit the update, retrying on conflicts - _, updateErr := spc.client.Core().Pods(set.Namespace).Update(pod) + _, updateErr := spc.client.CoreV1().Pods(set.Namespace).Update(pod) if updateErr == nil { return nil } @@ -134,7 +134,7 @@ func (spc *realStatefulPodControl) UpdateStatefulPod(set *apps.StatefulSet, pod } func (spc *realStatefulPodControl) DeleteStatefulPod(set *apps.StatefulSet, pod *v1.Pod) error { - err := spc.client.Core().Pods(set.Namespace).Delete(pod.Name, nil) + err := spc.client.CoreV1().Pods(set.Namespace).Delete(pod.Name, nil) spc.recordPodEvent("delete", set, pod, err) return err } @@ -182,7 +182,7 @@ func (spc *realStatefulPodControl) createPersistentVolumeClaims(set *apps.Statef _, err := spc.pvcLister.PersistentVolumeClaims(claim.Namespace).Get(claim.Name) switch { case apierrors.IsNotFound(err): - _, err := spc.client.Core().PersistentVolumeClaims(claim.Namespace).Create(&claim) + _, err := spc.client.CoreV1().PersistentVolumeClaims(claim.Namespace).Create(&claim) if err != nil { errs = append(errs, fmt.Errorf("Failed to create PVC %s: %s", claim.Name, err)) } diff --git a/pkg/controller/statefulset/stateful_pod_control_test.go b/pkg/controller/statefulset/stateful_pod_control_test.go index 9d397930a7d..31ea8ac4674 100644 --- a/pkg/controller/statefulset/stateful_pod_control_test.go +++ b/pkg/controller/statefulset/stateful_pod_control_test.go @@ -31,8 +31,8 @@ import ( "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes/fake" corelisters "k8s.io/client-go/listers/core/v1" - _ "k8s.io/kubernetes/pkg/api/install" _ "k8s.io/kubernetes/pkg/apis/apps/install" + _ "k8s.io/kubernetes/pkg/apis/core/install" ) func TestStatefulPodControlCreatesPods(t *testing.T) { diff --git a/pkg/controller/statefulset/stateful_set.go b/pkg/controller/statefulset/stateful_set.go index 03c9c28dac2..a49ba7deb3c 100644 --- a/pkg/controller/statefulset/stateful_set.go +++ b/pkg/controller/statefulset/stateful_set.go @@ -71,6 +71,8 @@ type StatefulSetController struct { setListerSynced cache.InformerSynced // pvcListerSynced returns true if the pvc shared informer has synced at least once pvcListerSynced cache.InformerSynced + // revListerSynced returns true if the rev shared informer has synced at least once + revListerSynced cache.InformerSynced // StatefulSets that need to be synced. queue workqueue.RateLimitingInterface } @@ -85,7 +87,7 @@ func NewStatefulSetController( ) *StatefulSetController { eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(glog.Infof) - eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.Core().RESTClient()).Events("")}) + eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.CoreV1().RESTClient()).Events("")}) recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "statefulset-controller"}) ssc := &StatefulSetController{ @@ -103,6 +105,8 @@ func NewStatefulSetController( pvcListerSynced: pvcInformer.Informer().HasSynced, queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "statefulset"), podControl: controller.RealPodControl{KubeClient: kubeClient, Recorder: recorder}, + + revListerSynced: revInformer.Informer().HasSynced, } podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ @@ -146,7 +150,7 @@ func (ssc *StatefulSetController) Run(workers int, stopCh <-chan struct{}) { glog.Infof("Starting stateful set controller") defer glog.Infof("Shutting down statefulset controller") - if !controller.WaitForCacheSync("stateful set", stopCh, ssc.podListerSynced, ssc.setListerSynced, ssc.pvcListerSynced) { + if !controller.WaitForCacheSync("stateful set", stopCh, ssc.podListerSynced, ssc.setListerSynced, ssc.pvcListerSynced, ssc.revListerSynced) { return } diff --git a/pkg/controller/statefulset/stateful_set_utils.go b/pkg/controller/statefulset/stateful_set_utils.go index fc8ba3e5448..4e23784ac3d 100644 --- a/pkg/controller/statefulset/stateful_set_utils.go +++ b/pkg/controller/statefulset/stateful_set_utils.go @@ -112,7 +112,8 @@ func identityMatches(set *apps.StatefulSet, pod *v1.Pod) bool { return ordinal >= 0 && set.Name == parent && pod.Name == getPodName(set, ordinal) && - pod.Namespace == set.Namespace + pod.Namespace == set.Namespace && + pod.Labels[apps.StatefulSetPodNameLabel] == pod.Name } // storageMatches returns true if pod's Volumes cover the set of PersistentVolumeClaims @@ -187,11 +188,15 @@ func initIdentity(set *apps.StatefulSet, pod *v1.Pod) { pod.Spec.Subdomain = set.Spec.ServiceName } -// updateIdentity updates pod's name, hostname, and subdomain to conform to set's name and headless service. +// updateIdentity updates pod's name, hostname, and subdomain, and StatefulSetPodNameLabel to conform to set's name +// and headless service. func updateIdentity(set *apps.StatefulSet, pod *v1.Pod) { pod.Name = getPodName(set, getOrdinal(pod)) pod.Namespace = set.Namespace - + if pod.Labels == nil { + pod.Labels = make(map[string]string) + } + pod.Labels[apps.StatefulSetPodNameLabel] = pod.Name } // isRunningAndReady returns true if pod is in the PodRunning Phase, if it has a condition of PodReady. diff --git a/pkg/controller/statefulset/stateful_set_utils_test.go b/pkg/controller/statefulset/stateful_set_utils_test.go index 3b5c10c99fc..af5b71c02e9 100644 --- a/pkg/controller/statefulset/stateful_set_utils_test.go +++ b/pkg/controller/statefulset/stateful_set_utils_test.go @@ -78,6 +78,11 @@ func TestIdentityMatches(t *testing.T) { if identityMatches(set, pod) { t.Error("identity matches for a Pod with the wrong namespace") } + pod = newStatefulSetPod(set, 1) + delete(pod.Labels, apps.StatefulSetPodNameLabel) + if identityMatches(set, pod) { + t.Error("identity matches for a Pod with the wrong statefulSetPodNameLabel") + } } func TestStorageMatches(t *testing.T) { @@ -127,6 +132,11 @@ func TestUpdateIdentity(t *testing.T) { if !identityMatches(set, pod) { t.Error("updateIdentity failed to update the Pods namespace") } + delete(pod.Labels, apps.StatefulSetPodNameLabel) + updateIdentity(set, pod) + if !identityMatches(set, pod) { + t.Error("updateIdentity failed to restore the statefulSetPodName label") + } } func TestUpdateStorage(t *testing.T) { diff --git a/pkg/controller/testutil/BUILD b/pkg/controller/testutil/BUILD index 488102b9595..0bfd0d077ce 100644 --- a/pkg/controller/testutil/BUILD +++ b/pkg/controller/testutil/BUILD @@ -10,7 +10,8 @@ go_library( srcs = ["test_utils.go"], importpath = "k8s.io/kubernetes/pkg/controller/testutil", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/util/node:go_default_library", "//vendor/github.com/evanphx/json-patch:go_default_library", "//vendor/github.com/golang/glog:go_default_library", diff --git a/pkg/controller/testutil/test_utils.go b/pkg/controller/testutil/test_utils.go index 3801c4a93d6..1ecada1f6f2 100644 --- a/pkg/controller/testutil/test_utils.go +++ b/pkg/controller/testutil/test_utils.go @@ -38,7 +38,8 @@ import ( "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes/fake" v1core "k8s.io/client-go/kubernetes/typed/core/v1" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" utilnode "k8s.io/kubernetes/pkg/util/node" jsonpatch "github.com/evanphx/json-patch" @@ -363,7 +364,7 @@ func (f *FakeRecorder) PastEventf(obj runtime.Object, timestamp metav1.Time, eve func (f *FakeRecorder) generateEvent(obj runtime.Object, timestamp metav1.Time, eventtype, reason, message string) { f.Lock() defer f.Unlock() - ref, err := ref.GetReference(api.Scheme, obj) + ref, err := ref.GetReference(legacyscheme.Scheme, obj) if err != nil { glog.Errorf("Encoutered error while getting reference: %v", err) return diff --git a/pkg/controller/ttl/BUILD b/pkg/controller/ttl/BUILD index 7c96860f757..7ba7c587065 100644 --- a/pkg/controller/ttl/BUILD +++ b/pkg/controller/ttl/BUILD @@ -44,8 +44,8 @@ filegroup( go_test( name = "go_default_test", srcs = ["ttl_controller_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/ttl", - library = ":go_default_library", deps = [ "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/controller/ttl/ttl_controller.go b/pkg/controller/ttl/ttl_controller.go index 3581e0519f0..2938ca8270b 100644 --- a/pkg/controller/ttl/ttl_controller.go +++ b/pkg/controller/ttl/ttl_controller.go @@ -263,7 +263,7 @@ func (ttlc *TTLController) patchNodeWithAnnotation(node *v1.Node, annotationKey if err != nil { return err } - _, err = ttlc.kubeClient.Core().Nodes().Patch(node.Name, types.StrategicMergePatchType, patchBytes) + _, err = ttlc.kubeClient.CoreV1().Nodes().Patch(node.Name, types.StrategicMergePatchType, patchBytes) if err != nil { glog.V(2).Infof("Failed to change ttl annotation for node %s: %v", node.Name, err) return err diff --git a/pkg/controller/util/node/BUILD b/pkg/controller/util/node/BUILD new file mode 100644 index 00000000000..8a462510406 --- /dev/null +++ b/pkg/controller/util/node/BUILD @@ -0,0 +1,41 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["controller_utils.go"], + importpath = "k8s.io/kubernetes/pkg/controller/util/node", + visibility = ["//visibility:public"], + deps = [ + "//pkg/apis/core:go_default_library", + "//pkg/cloudprovider:go_default_library", + "//pkg/controller:go_default_library", + "//pkg/kubelet/util/format:go_default_library", + "//pkg/util/node:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/client-go/listers/extensions/v1beta1:go_default_library", + "//vendor/k8s.io/client-go/tools/cache:go_default_library", + "//vendor/k8s.io/client-go/tools/record:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/controller/node/util/controller_utils.go b/pkg/controller/util/node/controller_utils.go similarity index 85% rename from pkg/controller/node/util/controller_utils.go rename to pkg/controller/util/node/controller_utils.go index 17258e774f4..4c9a9279acc 100644 --- a/pkg/controller/node/util/controller_utils.go +++ b/pkg/controller/util/node/controller_utils.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util +package node import ( "errors" @@ -33,12 +33,11 @@ import ( "k8s.io/api/core/v1" clientset "k8s.io/client-go/kubernetes" extensionslisters "k8s.io/client-go/listers/extensions/v1beta1" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/kubelet/util/format" nodepkg "k8s.io/kubernetes/pkg/util/node" - utilversion "k8s.io/kubernetes/pkg/util/version" "github.com/golang/glog" ) @@ -47,10 +46,6 @@ var ( // ErrCloudInstance occurs when the cloud provider does not support // the Instances API. ErrCloudInstance = errors.New("cloud provider doesn't support instances") - // podStatusReconciliationVersion is the the minimum kubelet version - // for which the nodecontroller can safely flip pod.Status to - // NotReady. - podStatusReconciliationVersion = utilversion.MustParseSemantic("v1.2.0") ) // DeletePods will delete all pods from master running on given node, @@ -60,7 +55,7 @@ func DeletePods(kubeClient clientset.Interface, recorder record.EventRecorder, n remaining := false selector := fields.OneTermEqualSelector(api.PodHostField, nodeName).String() options := metav1.ListOptions{FieldSelector: selector} - pods, err := kubeClient.Core().Pods(metav1.NamespaceAll).List(options) + pods, err := kubeClient.CoreV1().Pods(metav1.NamespaceAll).List(options) var updateErrList []error if err != nil { @@ -98,7 +93,7 @@ func DeletePods(kubeClient clientset.Interface, recorder record.EventRecorder, n glog.V(2).Infof("Starting deletion of pod %v/%v", pod.Namespace, pod.Name) recorder.Eventf(&pod, v1.EventTypeNormal, "NodeControllerEviction", "Marking for deletion Pod %s from Node %s", pod.Name, nodeName) - if err := kubeClient.Core().Pods(pod.Namespace).Delete(pod.Name, nil); err != nil { + if err := kubeClient.CoreV1().Pods(pod.Namespace).Delete(pod.Name, nil); err != nil { return false, err } remaining = true @@ -123,27 +118,16 @@ func SetPodTerminationReason(kubeClient clientset.Interface, pod *v1.Pod, nodeNa var updatedPod *v1.Pod var err error - if updatedPod, err = kubeClient.Core().Pods(pod.Namespace).UpdateStatus(pod); err != nil { + if updatedPod, err = kubeClient.CoreV1().Pods(pod.Namespace).UpdateStatus(pod); err != nil { return nil, err } return updatedPod, nil } -// ForcefullyDeletePod deletes the pod immediately. -func ForcefullyDeletePod(c clientset.Interface, pod *v1.Pod) error { - var zero int64 - glog.Infof("NodeController is force deleting Pod: %v:%v", pod.Namespace, pod.Name) - err := c.Core().Pods(pod.Namespace).Delete(pod.Name, &metav1.DeleteOptions{GracePeriodSeconds: &zero}) - if err == nil { - glog.V(4).Infof("forceful deletion of %s succeeded", pod.Name) - } - return err -} - // ForcefullyDeleteNode deletes the node immediately. The pods on the // node are cleaned up by the podGC. func ForcefullyDeleteNode(kubeClient clientset.Interface, nodeName string) error { - if err := kubeClient.Core().Nodes().Delete(nodeName, nil); err != nil { + if err := kubeClient.CoreV1().Nodes().Delete(nodeName, nil); err != nil { return fmt.Errorf("unable to delete node %q: %v", nodeName, err) } return nil @@ -155,7 +139,7 @@ func MarkAllPodsNotReady(kubeClient clientset.Interface, node *v1.Node) error { nodeName := node.Name glog.V(2).Infof("Update ready status of pods on node [%v]", nodeName) opts := metav1.ListOptions{FieldSelector: fields.OneTermEqualSelector(api.PodHostField, nodeName).String()} - pods, err := kubeClient.Core().Pods(metav1.NamespaceAll).List(opts) + pods, err := kubeClient.CoreV1().Pods(metav1.NamespaceAll).List(opts) if err != nil { return err } @@ -171,7 +155,7 @@ func MarkAllPodsNotReady(kubeClient clientset.Interface, node *v1.Node) error { if cond.Type == v1.PodReady { pod.Status.Conditions[i].Status = v1.ConditionFalse glog.V(2).Infof("Updating ready status of pod %v to false", pod.Name) - _, err := kubeClient.Core().Pods(pod.Namespace).UpdateStatus(&pod) + _, err := kubeClient.CoreV1().Pods(pod.Namespace).UpdateStatus(&pod) if err != nil { glog.Warningf("Failed to update status for pod %q: %v", format.Pod(&pod), err) errMsg = append(errMsg, fmt.Sprintf("%v", err)) @@ -186,9 +170,9 @@ func MarkAllPodsNotReady(kubeClient clientset.Interface, node *v1.Node) error { return fmt.Errorf("%v", strings.Join(errMsg, "; ")) } -// NodeExistsInCloudProvider returns true if the node exists in the +// ExistsInCloudProvider returns true if the node exists in the // cloud provider. -func NodeExistsInCloudProvider(cloud cloudprovider.Interface, nodeName types.NodeName) (bool, error) { +func ExistsInCloudProvider(cloud cloudprovider.Interface, nodeName types.NodeName) (bool, error) { instances, ok := cloud.Instances() if !ok { return false, fmt.Errorf("%v", ErrCloudInstance) @@ -214,7 +198,7 @@ func RecordNodeEvent(recorder record.EventRecorder, nodeName, nodeUID, eventtype recorder.Eventf(ref, eventtype, reason, "Node %s event: %s", nodeName, event) } -// RecordNodeStatusChange records a event related to a node status change. +// RecordNodeStatusChange records a event related to a node status change. (Common to lifecycle and ipam) func RecordNodeStatusChange(recorder record.EventRecorder, node *v1.Node, newStatus string) { ref := &v1.ObjectReference{ Kind: "Node", @@ -268,12 +252,12 @@ func CreateAddNodeHandler(f func(node *v1.Node) error) func(obj interface{}) { return func(originalObj interface{}) { node := originalObj.(*v1.Node).DeepCopy() if err := f(node); err != nil { - utilruntime.HandleError(fmt.Errorf("Error while processing Node Delete: %v", err)) + utilruntime.HandleError(fmt.Errorf("Error while processing Node Add: %v", err)) } } } -// CreateUpdateNodeHandler creates a node update handler. +// CreateUpdateNodeHandler creates a node update handler. (Common to lifecycle and ipam) func CreateUpdateNodeHandler(f func(oldNode, newNode *v1.Node) error) func(oldObj, newObj interface{}) { return func(origOldObj, origNewObj interface{}) { node := origNewObj.(*v1.Node).DeepCopy() @@ -285,7 +269,7 @@ func CreateUpdateNodeHandler(f func(oldNode, newNode *v1.Node) error) func(oldOb } } -// CreateDeleteNodeHandler creates a delete node handler. +// CreateDeleteNodeHandler creates a delete node handler. (Common to lifecycle and ipam) func CreateDeleteNodeHandler(f func(node *v1.Node) error) func(obj interface{}) { return func(originalObj interface{}) { originalNode, isNode := originalObj.(*v1.Node) diff --git a/pkg/controller/volume/OWNERS b/pkg/controller/volume/OWNERS index debfb819850..f77dc41828f 100755 --- a/pkg/controller/volume/OWNERS +++ b/pkg/controller/volume/OWNERS @@ -7,8 +7,8 @@ reviewers: - saad-ali - jsafrane - jingxu97 -- pmorie -- justinsb - rootfs - gnufied - msau42 +- verult +- davidz627 diff --git a/pkg/controller/volume/attachdetach/BUILD b/pkg/controller/volume/attachdetach/BUILD index 7dae9e1ff60..77a6feacd8f 100644 --- a/pkg/controller/volume/attachdetach/BUILD +++ b/pkg/controller/volume/attachdetach/BUILD @@ -21,6 +21,7 @@ go_library( "//pkg/util/io:go_default_library", "//pkg/util/mount:go_default_library", "//pkg/volume:go_default_library", + "//pkg/volume/util:go_default_library", "//pkg/volume/util/operationexecutor:go_default_library", "//pkg/volume/util/volumehelper:go_default_library", "//vendor/github.com/golang/glog:go_default_library", @@ -41,8 +42,8 @@ go_library( go_test( name = "go_default_test", srcs = ["attach_detach_controller_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/volume/attachdetach", - library = ":go_default_library", deps = [ "//pkg/controller:go_default_library", "//pkg/controller/volume/attachdetach/cache:go_default_library", diff --git a/pkg/controller/volume/attachdetach/attach_detach_controller.go b/pkg/controller/volume/attachdetach/attach_detach_controller.go index 847ab12d769..3c01e6b05a4 100644 --- a/pkg/controller/volume/attachdetach/attach_detach_controller.go +++ b/pkg/controller/volume/attachdetach/attach_detach_controller.go @@ -45,6 +45,7 @@ import ( "k8s.io/kubernetes/pkg/util/io" "k8s.io/kubernetes/pkg/util/mount" "k8s.io/kubernetes/pkg/volume" + volumeutil "k8s.io/kubernetes/pkg/volume/util" "k8s.io/kubernetes/pkg/volume/util/operationexecutor" "k8s.io/kubernetes/pkg/volume/util/volumehelper" ) @@ -134,8 +135,9 @@ func NewAttachDetachController( eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(glog.Infof) - eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.Core().RESTClient()).Events("")}) + eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.CoreV1().RESTClient()).Events("")}) recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "attachdetach-controller"}) + blkutil := volumeutil.NewBlockVolumePathHandler() adc.desiredStateOfWorld = cache.NewDesiredStateOfWorld(&adc.volumePluginMgr) adc.actualStateOfWorld = cache.NewActualStateOfWorld(&adc.volumePluginMgr) @@ -144,7 +146,8 @@ func NewAttachDetachController( kubeClient, &adc.volumePluginMgr, recorder, - false)) // flag for experimental binary check for volume mount + false, // flag for experimental binary check for volume mount + blkutil)) adc.nodeStatusUpdater = statusupdater.NewNodeStatusUpdater( kubeClient, nodeInformer.Lister(), adc.actualStateOfWorld) @@ -515,6 +518,10 @@ func (adc *attachDetachController) GetPluginDir(podUID string) string { return "" } +func (adc *attachDetachController) GetVolumeDevicePluginDir(podUID string) string { + return "" +} + func (adc *attachDetachController) GetPodVolumeDir(podUID types.UID, pluginName, volumeName string) string { return "" } @@ -523,6 +530,10 @@ func (adc *attachDetachController) GetPodPluginDir(podUID types.UID, pluginName return "" } +func (adc *attachDetachController) GetPodVolumeDeviceDir(podUID types.UID, pluginName string) string { + return "" +} + func (adc *attachDetachController) GetKubeClient() clientset.Interface { return adc.kubeClient } @@ -592,3 +603,7 @@ func (adc *attachDetachController) addNodeToDswp(node *v1.Node, nodeName types.N func (adc *attachDetachController) GetNodeLabels() (map[string]string, error) { return nil, fmt.Errorf("GetNodeLabels() unsupported in Attach/Detach controller") } + +func (adc *attachDetachController) GetNodeName() types.NodeName { + return "" +} diff --git a/pkg/controller/volume/attachdetach/attach_detach_controller_test.go b/pkg/controller/volume/attachdetach/attach_detach_controller_test.go index 9f8b5865801..283a910f24c 100644 --- a/pkg/controller/volume/attachdetach/attach_detach_controller_test.go +++ b/pkg/controller/volume/attachdetach/attach_detach_controller_test.go @@ -239,7 +239,7 @@ func attachDetachRecoveryTestCase(t *testing.T, extraPods1 []*v1.Pod, extraPods2 for _, newPod := range extraPods1 { // Add a new pod between ASW and DSW ppoulators - _, err = adc.kubeClient.Core().Pods(newPod.ObjectMeta.Namespace).Create(newPod) + _, err = adc.kubeClient.CoreV1().Pods(newPod.ObjectMeta.Namespace).Create(newPod) if err != nil { t.Fatalf("Run failed with error. Failed to create a new pod: <%v>", err) } @@ -256,7 +256,7 @@ func attachDetachRecoveryTestCase(t *testing.T, extraPods1 []*v1.Pod, extraPods2 for _, newPod := range extraPods2 { // Add a new pod between DSW ppoulator and reconciler run - _, err = adc.kubeClient.Core().Pods(newPod.ObjectMeta.Namespace).Create(newPod) + _, err = adc.kubeClient.CoreV1().Pods(newPod.ObjectMeta.Namespace).Create(newPod) if err != nil { t.Fatalf("Run failed with error. Failed to create a new pod: <%v>", err) } diff --git a/pkg/controller/volume/attachdetach/cache/BUILD b/pkg/controller/volume/attachdetach/cache/BUILD index 6bc3d938c54..91ab48ee3f0 100644 --- a/pkg/controller/volume/attachdetach/cache/BUILD +++ b/pkg/controller/volume/attachdetach/cache/BUILD @@ -30,8 +30,8 @@ go_test( "actual_state_of_world_test.go", "desired_state_of_world_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/volume/attachdetach/cache", - library = ":go_default_library", deps = [ "//pkg/controller/volume/attachdetach/testing:go_default_library", "//pkg/volume/testing:go_default_library", diff --git a/pkg/controller/volume/attachdetach/cache/actual_state_of_world.go b/pkg/controller/volume/attachdetach/cache/actual_state_of_world.go index fbb529154be..4d4a7523d8d 100644 --- a/pkg/controller/volume/attachdetach/cache/actual_state_of_world.go +++ b/pkg/controller/volume/attachdetach/cache/actual_state_of_world.go @@ -475,7 +475,6 @@ func (asw *actualStateOfWorld) updateNodeStatusUpdateNeeded(nodeName types.NodeN // should not happen errMsg := fmt.Sprintf("Failed to set statusUpdateNeeded to needed %t because nodeName=%q does not exist", needed, nodeName) - glog.Errorf(errMsg) return fmt.Errorf(errMsg) } @@ -489,7 +488,7 @@ func (asw *actualStateOfWorld) SetNodeStatusUpdateNeeded(nodeName types.NodeName asw.Lock() defer asw.Unlock() if err := asw.updateNodeStatusUpdateNeeded(nodeName, true); err != nil { - glog.Errorf("Failed to update statusUpdateNeeded field in actual state of world: %v", err) + glog.Warningf("Failed to update statusUpdateNeeded field in actual state of world: %v", err) } } diff --git a/pkg/controller/volume/attachdetach/populator/BUILD b/pkg/controller/volume/attachdetach/populator/BUILD index 78f4ac36473..e479149bf12 100644 --- a/pkg/controller/volume/attachdetach/populator/BUILD +++ b/pkg/controller/volume/attachdetach/populator/BUILD @@ -41,8 +41,8 @@ filegroup( go_test( name = "go_default_test", srcs = ["desired_state_of_world_populator_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/volume/attachdetach/populator", - library = ":go_default_library", deps = [ "//pkg/controller:go_default_library", "//pkg/controller/volume/attachdetach/cache:go_default_library", diff --git a/pkg/controller/volume/attachdetach/reconciler/BUILD b/pkg/controller/volume/attachdetach/reconciler/BUILD index 16379f5c0b9..f36d643138a 100644 --- a/pkg/controller/volume/attachdetach/reconciler/BUILD +++ b/pkg/controller/volume/attachdetach/reconciler/BUILD @@ -27,8 +27,8 @@ go_library( go_test( name = "go_default_test", srcs = ["reconciler_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/volume/attachdetach/reconciler", - library = ":go_default_library", deps = [ "//pkg/controller:go_default_library", "//pkg/controller/volume/attachdetach/cache:go_default_library", diff --git a/pkg/controller/volume/attachdetach/reconciler/reconciler_test.go b/pkg/controller/volume/attachdetach/reconciler/reconciler_test.go index 79bbc520fe1..9d7f2aefbdf 100644 --- a/pkg/controller/volume/attachdetach/reconciler/reconciler_test.go +++ b/pkg/controller/volume/attachdetach/reconciler/reconciler_test.go @@ -50,7 +50,13 @@ func Test_Run_Positive_DoNothing(t *testing.T) { asw := cache.NewActualStateOfWorld(volumePluginMgr) fakeKubeClient := controllervolumetesting.CreateTestClient() fakeRecorder := &record.FakeRecorder{} - ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(fakeKubeClient, volumePluginMgr, fakeRecorder, false /* checkNodeCapabilitiesBeforeMount */)) + fakeHandler := volumetesting.NewBlockVolumePathHandler() + ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( + fakeKubeClient, + volumePluginMgr, + fakeRecorder, + false, /* checkNodeCapabilitiesBeforeMount */ + fakeHandler)) informerFactory := informers.NewSharedInformerFactory(fakeKubeClient, controller.NoResyncPeriodFunc()) nsu := statusupdater.NewNodeStatusUpdater( fakeKubeClient, informerFactory.Core().V1().Nodes().Lister(), asw) @@ -80,7 +86,13 @@ func Test_Run_Positive_OneDesiredVolumeAttach(t *testing.T) { asw := cache.NewActualStateOfWorld(volumePluginMgr) fakeKubeClient := controllervolumetesting.CreateTestClient() fakeRecorder := &record.FakeRecorder{} - ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(fakeKubeClient, volumePluginMgr, fakeRecorder, false /* checkNodeCapabilitiesBeforeMount */)) + fakeHandler := volumetesting.NewBlockVolumePathHandler() + ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( + fakeKubeClient, + volumePluginMgr, + fakeRecorder, + false, /* checkNodeCapabilitiesBeforeMount */ + fakeHandler)) nsu := statusupdater.NewFakeNodeStatusUpdater(false /* returnError */) reconciler := NewReconciler( reconcilerLoopPeriod, maxWaitForUnmountDuration, syncLoopPeriod, false, dsw, asw, ad, nsu, fakeRecorder) @@ -126,7 +138,13 @@ func Test_Run_Positive_OneDesiredVolumeAttachThenDetachWithUnmountedVolume(t *te asw := cache.NewActualStateOfWorld(volumePluginMgr) fakeKubeClient := controllervolumetesting.CreateTestClient() fakeRecorder := &record.FakeRecorder{} - ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(fakeKubeClient, volumePluginMgr, fakeRecorder, false /* checkNodeCapabilitiesBeforeMount */)) + fakeHandler := volumetesting.NewBlockVolumePathHandler() + ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( + fakeKubeClient, + volumePluginMgr, + fakeRecorder, + false, /* checkNodeCapabilitiesBeforeMount */ + fakeHandler)) nsu := statusupdater.NewFakeNodeStatusUpdater(false /* returnError */) reconciler := NewReconciler( reconcilerLoopPeriod, maxWaitForUnmountDuration, syncLoopPeriod, false, dsw, asw, ad, nsu, fakeRecorder) @@ -193,7 +211,13 @@ func Test_Run_Positive_OneDesiredVolumeAttachThenDetachWithMountedVolume(t *test asw := cache.NewActualStateOfWorld(volumePluginMgr) fakeKubeClient := controllervolumetesting.CreateTestClient() fakeRecorder := &record.FakeRecorder{} - ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(fakeKubeClient, volumePluginMgr, fakeRecorder, false /* checkNodeCapabilitiesBeforeMount */)) + fakeHandler := volumetesting.NewBlockVolumePathHandler() + ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( + fakeKubeClient, + volumePluginMgr, + fakeRecorder, + false, /* checkNodeCapabilitiesBeforeMount */ + fakeHandler)) nsu := statusupdater.NewFakeNodeStatusUpdater(false /* returnError */) reconciler := NewReconciler( reconcilerLoopPeriod, maxWaitForUnmountDuration, syncLoopPeriod, false, dsw, asw, ad, nsu, fakeRecorder) @@ -260,7 +284,13 @@ func Test_Run_Negative_OneDesiredVolumeAttachThenDetachWithUnmountedVolumeUpdate asw := cache.NewActualStateOfWorld(volumePluginMgr) fakeKubeClient := controllervolumetesting.CreateTestClient() fakeRecorder := &record.FakeRecorder{} - ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(fakeKubeClient, volumePluginMgr, fakeRecorder, false /* checkNodeCapabilitiesBeforeMount */)) + fakeHandler := volumetesting.NewBlockVolumePathHandler() + ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( + fakeKubeClient, + volumePluginMgr, + fakeRecorder, + false, /* checkNodeCapabilitiesBeforeMount */ + fakeHandler)) nsu := statusupdater.NewFakeNodeStatusUpdater(true /* returnError */) reconciler := NewReconciler( reconcilerLoopPeriod, maxWaitForUnmountDuration, syncLoopPeriod, false, dsw, asw, ad, nsu, fakeRecorder) @@ -330,7 +360,13 @@ func Test_Run_OneVolumeAttachAndDetachMultipleNodesWithReadWriteMany(t *testing. asw := cache.NewActualStateOfWorld(volumePluginMgr) fakeKubeClient := controllervolumetesting.CreateTestClient() fakeRecorder := &record.FakeRecorder{} - ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(fakeKubeClient, volumePluginMgr, fakeRecorder, false /* checkNodeCapabilitiesBeforeMount */)) + fakeHandler := volumetesting.NewBlockVolumePathHandler() + ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( + fakeKubeClient, + volumePluginMgr, + fakeRecorder, + false, /* checkNodeCapabilitiesBeforeMount */ + fakeHandler)) nsu := statusupdater.NewFakeNodeStatusUpdater(false /* returnError */) reconciler := NewReconciler( reconcilerLoopPeriod, maxWaitForUnmountDuration, syncLoopPeriod, false, dsw, asw, ad, nsu, fakeRecorder) @@ -416,7 +452,13 @@ func Test_Run_OneVolumeAttachAndDetachMultipleNodesWithReadWriteOnce(t *testing. asw := cache.NewActualStateOfWorld(volumePluginMgr) fakeKubeClient := controllervolumetesting.CreateTestClient() fakeRecorder := &record.FakeRecorder{} - ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(fakeKubeClient, volumePluginMgr, fakeRecorder, false /* checkNodeCapabilitiesBeforeMount */)) + fakeHandler := volumetesting.NewBlockVolumePathHandler() + ad := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( + fakeKubeClient, + volumePluginMgr, + fakeRecorder, + false, /* checkNodeCapabilitiesBeforeMount */ + fakeHandler)) nsu := statusupdater.NewFakeNodeStatusUpdater(false /* returnError */) reconciler := NewReconciler( reconcilerLoopPeriod, maxWaitForUnmountDuration, syncLoopPeriod, false, dsw, asw, ad, nsu, fakeRecorder) diff --git a/pkg/controller/volume/attachdetach/statusupdater/BUILD b/pkg/controller/volume/attachdetach/statusupdater/BUILD index e60d31be92e..2ef64b0f6e9 100644 --- a/pkg/controller/volume/attachdetach/statusupdater/BUILD +++ b/pkg/controller/volume/attachdetach/statusupdater/BUILD @@ -14,11 +14,11 @@ go_library( importpath = "k8s.io/kubernetes/pkg/controller/volume/attachdetach/statusupdater", deps = [ "//pkg/controller/volume/attachdetach/cache:go_default_library", + "//pkg/util/node:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/listers/core/v1:go_default_library", ], diff --git a/pkg/controller/volume/attachdetach/statusupdater/node_status_updater.go b/pkg/controller/volume/attachdetach/statusupdater/node_status_updater.go index e1b31fbccc8..b5cbb224464 100644 --- a/pkg/controller/volume/attachdetach/statusupdater/node_status_updater.go +++ b/pkg/controller/volume/attachdetach/statusupdater/node_status_updater.go @@ -19,18 +19,15 @@ limitations under the License. package statusupdater import ( - "encoding/json" - "fmt" - "github.com/golang/glog" "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/strategicpatch" clientset "k8s.io/client-go/kubernetes" corelisters "k8s.io/client-go/listers/core/v1" "k8s.io/kubernetes/pkg/controller/volume/attachdetach/cache" + nodeutil "k8s.io/kubernetes/pkg/util/node" ) // NodeStatusUpdater defines a set of operations for updating the @@ -100,47 +97,12 @@ func (nsu *nodeStatusUpdater) UpdateNodeStatuses() error { func (nsu *nodeStatusUpdater) updateNodeStatus(nodeName types.NodeName, nodeObj *v1.Node, attachedVolumes []v1.AttachedVolume) error { node := nodeObj.DeepCopy() - - // TODO: Change to pkg/util/node.UpdateNodeStatus. - oldData, err := json.Marshal(node) - if err != nil { - return fmt.Errorf( - "failed to Marshal oldData for node %q. %v", - nodeName, - err) - } - node.Status.VolumesAttached = attachedVolumes - - newData, err := json.Marshal(node) + _, patchBytes, err := nodeutil.PatchNodeStatus(nsu.kubeClient.CoreV1(), nodeName, nodeObj, node) if err != nil { - return fmt.Errorf( - "failed to Marshal newData for node %q. %v", - nodeName, - err) + return err } - patchBytes, err := - strategicpatch.CreateTwoWayMergePatch(oldData, newData, node) - if err != nil { - return fmt.Errorf( - "failed to CreateTwoWayMergePatch for node %q. %v", - nodeName, - err) - } - - _, err = nsu.kubeClient.Core().Nodes().PatchStatus(string(nodeName), patchBytes) - if err != nil { - return fmt.Errorf( - "failed to kubeClient.Core().Nodes().Patch for node %q. %v", - nodeName, - err) - } - glog.V(4).Infof( - "Updating status for node %q succeeded. patchBytes: %q VolumesAttached: %v", - nodeName, - string(patchBytes), - node.Status.VolumesAttached) - + glog.V(4).Infof("Updating status %q for node %q succeeded. VolumesAttached: %v", patchBytes, nodeName, attachedVolumes) return nil } diff --git a/pkg/controller/volume/events/event.go b/pkg/controller/volume/events/event.go index b5ac3fb7be8..c99c30c99a7 100644 --- a/pkg/controller/volume/events/event.go +++ b/pkg/controller/volume/events/event.go @@ -29,4 +29,5 @@ const ( ProvisioningFailed = "ProvisioningFailed" ProvisioningCleanupFailed = "ProvisioningCleanupFailed" ProvisioningSucceeded = "ProvisioningSucceeded" + WaitForFirstConsumer = "WaitForFirstConsumer" ) diff --git a/pkg/controller/volume/expand/BUILD b/pkg/controller/volume/expand/BUILD index 5784984899b..74ae103b74c 100644 --- a/pkg/controller/volume/expand/BUILD +++ b/pkg/controller/volume/expand/BUILD @@ -22,6 +22,7 @@ go_library( "//pkg/util/io:go_default_library", "//pkg/util/mount:go_default_library", "//pkg/volume:go_default_library", + "//pkg/volume/util:go_default_library", "//pkg/volume/util/operationexecutor:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/controller/volume/expand/cache/BUILD b/pkg/controller/volume/expand/cache/BUILD index c9f64da1a97..2085dcda258 100644 --- a/pkg/controller/volume/expand/cache/BUILD +++ b/pkg/controller/volume/expand/cache/BUILD @@ -39,8 +39,8 @@ filegroup( go_test( name = "go_default_test", srcs = ["volume_resize_map_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/volume/expand/cache", - library = ":go_default_library", deps = [ "//pkg/volume/util/types:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", diff --git a/pkg/controller/volume/expand/cache/volume_resize_map.go b/pkg/controller/volume/expand/cache/volume_resize_map.go index 2645a294c23..c35af900f02 100644 --- a/pkg/controller/volume/expand/cache/volume_resize_map.go +++ b/pkg/controller/volume/expand/cache/volume_resize_map.go @@ -52,7 +52,7 @@ type volumeResizeMap struct { // kube client for making API calls kubeClient clientset.Interface // for guarding access to pvcrs map - sync.RWMutex + sync.Mutex } // PVCWithResizeRequest struct defines data structure that stores state needed for @@ -103,9 +103,6 @@ func (resizeMap *volumeResizeMap) AddPVCUpdate(pvc *v1.PersistentVolumeClaim, pv return } - resizeMap.Lock() - defer resizeMap.Unlock() - pvcSize := pvc.Spec.Resources.Requests[v1.ResourceStorage] pvcStatusSize := pvc.Status.Capacity[v1.ResourceStorage] @@ -121,6 +118,9 @@ func (resizeMap *volumeResizeMap) AddPVCUpdate(pvc *v1.PersistentVolumeClaim, pv ExpectedSize: pvcSize, PersistentVolume: pv, } + + resizeMap.Lock() + defer resizeMap.Unlock() resizeMap.pvcrs[types.UniquePVCName(pvc.UID)] = pvcRequest } @@ -141,18 +141,15 @@ func (resizeMap *volumeResizeMap) GetPVCsWithResizeRequest() []*PVCWithResizeReq // DeletePVC removes given pvc object from list of pvcs that needs resizing. // deleting a pvc in this map doesn't affect operations that are already inflight. func (resizeMap *volumeResizeMap) DeletePVC(pvc *v1.PersistentVolumeClaim) { - resizeMap.Lock() - defer resizeMap.Unlock() pvcUniqueName := types.UniquePVCName(pvc.UID) glog.V(5).Infof("Removing PVC %v from resize map", pvcUniqueName) + resizeMap.Lock() + defer resizeMap.Unlock() delete(resizeMap.pvcrs, pvcUniqueName) } // MarkAsResized marks a pvc as fully resized func (resizeMap *volumeResizeMap) MarkAsResized(pvcr *PVCWithResizeRequest, newSize resource.Quantity) error { - resizeMap.Lock() - defer resizeMap.Unlock() - emptyCondition := []v1.PersistentVolumeClaimCondition{} err := resizeMap.updatePVCCapacityAndConditions(pvcr, newSize, emptyCondition) @@ -165,9 +162,6 @@ func (resizeMap *volumeResizeMap) MarkAsResized(pvcr *PVCWithResizeRequest, newS // UpdatePVSize updates just pv size after cloudprovider resizing is successful func (resizeMap *volumeResizeMap) UpdatePVSize(pvcr *PVCWithResizeRequest, newSize resource.Quantity) error { - resizeMap.Lock() - defer resizeMap.Unlock() - oldPv := pvcr.PersistentVolume pvClone := oldPv.DeepCopy() @@ -201,7 +195,6 @@ func (resizeMap *volumeResizeMap) UpdatePVSize(pvcr *PVCWithResizeRequest, newSi } func (resizeMap *volumeResizeMap) updatePVCCapacityAndConditions(pvcr *PVCWithResizeRequest, newSize resource.Quantity, pvcConditions []v1.PersistentVolumeClaimCondition) error { - claimClone := pvcr.PVC.DeepCopy() claimClone.Status.Capacity[v1.ResourceStorage] = newSize diff --git a/pkg/controller/volume/expand/expand_controller.go b/pkg/controller/volume/expand/expand_controller.go index 1c71528ef91..6d6ac0edf9b 100644 --- a/pkg/controller/volume/expand/expand_controller.go +++ b/pkg/controller/volume/expand/expand_controller.go @@ -42,6 +42,7 @@ import ( "k8s.io/kubernetes/pkg/util/io" "k8s.io/kubernetes/pkg/util/mount" "k8s.io/kubernetes/pkg/volume" + "k8s.io/kubernetes/pkg/volume/util" "k8s.io/kubernetes/pkg/volume/util/operationexecutor" ) @@ -117,12 +118,14 @@ func NewExpandController( eventBroadcaster.StartLogging(glog.Infof) eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.CoreV1().RESTClient()).Events("")}) recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "volume_expand"}) + blkutil := util.NewBlockVolumePathHandler() expc.opExecutor = operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( kubeClient, &expc.volumePluginMgr, recorder, - false)) + false, + blkutil)) expc.resizeMap = cache.NewVolumeResizeMap(expc.kubeClient) @@ -203,10 +206,18 @@ func (expc *expandController) GetPluginDir(pluginName string) string { return "" } +func (expc *expandController) GetVolumeDevicePluginDir(pluginName string) string { + return "" +} + func (expc *expandController) GetPodVolumeDir(podUID types.UID, pluginName string, volumeName string) string { return "" } +func (expc *expandController) GetPodVolumeDeviceDir(podUID types.UID, pluginName string) string { + return "" +} + func (expc *expandController) GetPodPluginDir(podUID types.UID, pluginName string) string { return "" } @@ -266,3 +277,7 @@ func (expc *expandController) GetConfigMapFunc() func(namespace, name string) (* func (expc *expandController) GetNodeLabels() (map[string]string, error) { return nil, fmt.Errorf("GetNodeLabels unsupported in expandController") } + +func (expc *expandController) GetNodeName() types.NodeName { + return "" +} diff --git a/pkg/controller/volume/persistentvolume/BUILD b/pkg/controller/volume/persistentvolume/BUILD index e2871ab095e..eaef2490e52 100644 --- a/pkg/controller/volume/persistentvolume/BUILD +++ b/pkg/controller/volume/persistentvolume/BUILD @@ -12,14 +12,19 @@ go_library( "index.go", "pv_controller.go", "pv_controller_base.go", + "scheduler_assume_cache.go", + "scheduler_binder.go", + "scheduler_binder_cache.go", + "scheduler_binder_fake.go", "volume_host.go", ], importpath = "k8s.io/kubernetes/pkg/controller/volume/persistentvolume", deps = [ - "//pkg/api/v1/helper:go_default_library", + "//pkg/apis/core/v1/helper:go_default_library", "//pkg/cloudprovider:go_default_library", "//pkg/controller:go_default_library", "//pkg/controller/volume/events:go_default_library", + "//pkg/features:go_default_library", "//pkg/util/goroutinemap:go_default_library", "//pkg/util/goroutinemap/exponentialbackoff:go_default_library", "//pkg/util/io:go_default_library", @@ -37,6 +42,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", "//vendor/k8s.io/client-go/informers/core/v1:go_default_library", "//vendor/k8s.io/client-go/informers/storage/v1:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", @@ -61,12 +67,16 @@ go_test( "provision_test.go", "pv_controller_test.go", "recycle_test.go", + "scheduler_assume_cache_test.go", + "scheduler_binder_cache_test.go", + "scheduler_binder_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/controller/volume/persistentvolume", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", "//pkg/api/testapi:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/v1/helper:go_default_library", "//pkg/controller:go_default_library", "//pkg/volume:go_default_library", "//vendor/github.com/golang/glog:go_default_library", @@ -80,6 +90,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", "//vendor/k8s.io/client-go/informers:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", diff --git a/pkg/controller/volume/persistentvolume/binder_test.go b/pkg/controller/volume/persistentvolume/binder_test.go index 6f000e9edde..a34b595747f 100644 --- a/pkg/controller/volume/persistentvolume/binder_test.go +++ b/pkg/controller/volume/persistentvolume/binder_test.go @@ -21,6 +21,8 @@ import ( "k8s.io/api/core/v1" storage "k8s.io/api/storage/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utilfeature "k8s.io/apiserver/pkg/util/feature" ) // Test single call to syncClaim and syncVolume methods. @@ -33,6 +35,8 @@ func TestSync(t *testing.T) { "foo": "true", "bar": "false", } + modeBlock := v1.PersistentVolumeBlock + modeFile := v1.PersistentVolumeFilesystem tests := []controllerTest{ // [Unit test set 1] User did not care which PV they get. @@ -175,6 +179,25 @@ func TestSync(t *testing.T) { []string{"Normal FailedBinding"}, noerrors, testSyncClaim, }, + { + // syncClaim does not do anything when binding is delayed + "1-13 - delayed binding", + newVolumeArray("volume1-1", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classWait), + newVolumeArray("volume1-1", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classWait), + newClaimArray("claim1-1", "uid1-1", "1Gi", "", v1.ClaimPending, &classWait), + newClaimArray("claim1-1", "uid1-1", "1Gi", "", v1.ClaimPending, &classWait), + []string{"Normal WaitForFirstConsumer"}, + noerrors, testSyncClaim, + }, + { + // syncClaim binds when binding is delayed but PV is prebound to PVC + "1-14 - successful prebound PV", + newVolumeArray("volume1-1", "1Gi", "", "claim1-1", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classWait), + newVolumeArray("volume1-1", "1Gi", "uid1-1", "claim1-1", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, classWait), + newClaimArray("claim1-1", "uid1-1", "1Gi", "", v1.ClaimPending, &classWait), + newClaimArray("claim1-1", "uid1-1", "1Gi", "volume1-1", v1.ClaimBound, &classWait, annBoundByController, annBindCompleted), + noevents, noerrors, testSyncClaim, + }, // [Unit test set 2] User asked for a specific PV. // Test the binding when pv.ClaimRef is already set by controller or @@ -517,7 +540,216 @@ func TestSync(t *testing.T) { newClaimArray("claim13-5", "uid13-5", "1Gi", "volume13-5", v1.ClaimBound, nil, annBoundByController, annBindCompleted), noevents, noerrors, testSyncClaim, }, + + // All of these should bind as feature set is not enabled for BlockVolume + // meaning volumeMode will be ignored and dropped + { + // syncVolume binds a requested block claim to a block volume + "14-1 - binding to volumeMode block", + withVolumeVolumeMode(&modeBlock, newVolumeArray("volume14-1", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classEmpty)), + withVolumeVolumeMode(&modeBlock, newVolumeArray("volume14-1", "10Gi", "uid14-1", "claim14-1", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, classEmpty, annBoundByController)), + withClaimVolumeMode(&modeBlock, newClaimArray("claim14-1", "uid14-1", "10Gi", "", v1.ClaimPending, nil)), + withClaimVolumeMode(&modeBlock, newClaimArray("claim14-1", "uid14-1", "10Gi", "volume14-1", v1.ClaimBound, nil, annBoundByController, annBindCompleted)), + noevents, noerrors, testSyncClaim, + }, + { + // syncVolume binds a requested filesystem claim to a filesystem volume + "14-2 - binding to volumeMode filesystem", + withVolumeVolumeMode(&modeFile, newVolumeArray("volume14-2", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classEmpty)), + withVolumeVolumeMode(&modeFile, newVolumeArray("volume14-2", "10Gi", "uid14-2", "claim14-2", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, classEmpty, annBoundByController)), + withClaimVolumeMode(&modeFile, newClaimArray("claim14-2", "uid14-2", "10Gi", "", v1.ClaimPending, nil)), + withClaimVolumeMode(&modeFile, newClaimArray("claim14-2", "uid14-2", "10Gi", "volume14-2", v1.ClaimBound, nil, annBoundByController, annBindCompleted)), + noevents, noerrors, testSyncClaim, + }, + { + // syncVolume binds an unspecified volumemode for claim to a specified filesystem volume + "14-3 - binding to volumeMode filesystem using default for claim", + withVolumeVolumeMode(&modeFile, newVolumeArray("volume14-3", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classEmpty)), + withVolumeVolumeMode(&modeFile, newVolumeArray("volume14-3", "10Gi", "uid14-3", "claim14-3", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, classEmpty, annBoundByController)), + withClaimVolumeMode(nil, newClaimArray("claim14-3", "uid14-3", "10Gi", "", v1.ClaimPending, nil)), + withClaimVolumeMode(nil, newClaimArray("claim14-3", "uid14-3", "10Gi", "volume14-3", v1.ClaimBound, nil, annBoundByController, annBindCompleted)), + noevents, noerrors, testSyncClaim, + }, + { + // syncVolume binds a requested filesystem claim to an unspecified volumeMode for volume + "14-4 - binding to unspecified volumeMode using requested filesystem for claim", + withVolumeVolumeMode(nil, newVolumeArray("volume14-4", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classEmpty)), + withVolumeVolumeMode(nil, newVolumeArray("volume14-4", "10Gi", "uid14-4", "claim14-4", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, classEmpty, annBoundByController)), + withClaimVolumeMode(&modeFile, newClaimArray("claim14-4", "uid14-4", "10Gi", "", v1.ClaimPending, nil)), + withClaimVolumeMode(&modeFile, newClaimArray("claim14-4", "uid14-4", "10Gi", "volume14-4", v1.ClaimBound, nil, annBoundByController, annBindCompleted)), + noevents, noerrors, testSyncClaim, + }, + { + // syncVolume binds a requested filesystem claim to an unspecified volumeMode for volume + "14-5 - binding different volumeModes should be ignored", + withVolumeVolumeMode(&modeBlock, newVolumeArray("volume14-5", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classEmpty)), + withVolumeVolumeMode(&modeBlock, newVolumeArray("volume14-5", "10Gi", "uid14-5", "claim14-5", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, classEmpty, annBoundByController)), + withClaimVolumeMode(&modeFile, newClaimArray("claim14-5", "uid14-5", "10Gi", "", v1.ClaimPending, nil)), + withClaimVolumeMode(&modeFile, newClaimArray("claim14-5", "uid14-5", "10Gi", "volume14-5", v1.ClaimBound, nil, annBoundByController, annBindCompleted)), + noevents, noerrors, testSyncClaim, + }, } + + utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true") + defer utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false") + + runSyncTests(t, tests, []*storage.StorageClass{ + { + ObjectMeta: metav1.ObjectMeta{Name: classWait}, + VolumeBindingMode: &modeWait, + }, + }) +} + +func TestSyncAlphaBlockVolume(t *testing.T) { + modeBlock := v1.PersistentVolumeBlock + modeFile := v1.PersistentVolumeFilesystem + + // Tests assume defaulting, so feature enabled will never have nil volumeMode + tests := []controllerTest{ + // PVC with VolumeMode + { + // syncVolume binds a requested block claim to a block volume + "14-1 - binding to volumeMode block", + withVolumeVolumeMode(&modeBlock, newVolumeArray("volume14-1", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classEmpty)), + withVolumeVolumeMode(&modeBlock, newVolumeArray("volume14-1", "10Gi", "uid14-1", "claim14-1", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, classEmpty, annBoundByController)), + withClaimVolumeMode(&modeBlock, newClaimArray("claim14-1", "uid14-1", "10Gi", "", v1.ClaimPending, nil)), + withClaimVolumeMode(&modeBlock, newClaimArray("claim14-1", "uid14-1", "10Gi", "volume14-1", v1.ClaimBound, nil, annBoundByController, annBindCompleted)), + noevents, noerrors, testSyncClaim, + }, + { + // syncVolume binds a requested filesystem claim to a filesystem volume + "14-2 - binding to volumeMode filesystem", + withVolumeVolumeMode(&modeFile, newVolumeArray("volume14-2", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classEmpty)), + withVolumeVolumeMode(&modeFile, newVolumeArray("volume14-2", "10Gi", "uid14-2", "claim14-2", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, classEmpty, annBoundByController)), + withClaimVolumeMode(&modeFile, newClaimArray("claim14-2", "uid14-2", "10Gi", "", v1.ClaimPending, nil)), + withClaimVolumeMode(&modeFile, newClaimArray("claim14-2", "uid14-2", "10Gi", "volume14-2", v1.ClaimBound, nil, annBoundByController, annBindCompleted)), + noevents, noerrors, testSyncClaim, + }, + { + // failed syncVolume do not bind to an unspecified volumemode for claim to a specified filesystem volume + "14-3 - do not bind pv volumeMode filesystem and pvc volumeMode block", + withVolumeVolumeMode(&modeFile, newVolumeArray("volume14-3", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classEmpty)), + withVolumeVolumeMode(&modeFile, newVolumeArray("volume14-3", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classEmpty)), + withClaimVolumeMode(&modeBlock, newClaimArray("claim14-3", "uid14-3", "10Gi", "", v1.ClaimPending, nil)), + withClaimVolumeMode(&modeBlock, newClaimArray("claim14-3", "uid14-3", "10Gi", "", v1.ClaimPending, nil)), + []string{"Normal FailedBinding"}, + noerrors, testSyncClaim, + }, + { + // failed syncVolume do not bind a requested filesystem claim to an unspecified volumeMode for volume + "14-4 - do not bind pv volumeMode block and pvc volumeMode filesystem", + withVolumeVolumeMode(&modeBlock, newVolumeArray("volume14-4", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classEmpty)), + withVolumeVolumeMode(&modeBlock, newVolumeArray("volume14-4", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classEmpty)), + withClaimVolumeMode(&modeFile, newClaimArray("claim14-4", "uid14-4", "10Gi", "", v1.ClaimPending, nil)), + withClaimVolumeMode(&modeFile, newClaimArray("claim14-4", "uid14-4", "10Gi", "", v1.ClaimPending, nil)), + []string{"Normal FailedBinding"}, + noerrors, testSyncClaim, + }, + { + // failed syncVolume do not bind when matching class but not matching volumeModes + "14-5 - do not bind when matching class but not volumeMode", + withVolumeVolumeMode(&modeFile, newVolumeArray("volume14-5", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classGold)), + withVolumeVolumeMode(&modeFile, newVolumeArray("volume14-5", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classGold)), + withClaimVolumeMode(&modeBlock, newClaimArray("claim14-5", "uid14-5", "10Gi", "", v1.ClaimPending, &classGold)), + withClaimVolumeMode(&modeBlock, newClaimArray("claim14-5", "uid14-5", "10Gi", "", v1.ClaimPending, &classGold)), + []string{"Warning ProvisioningFailed"}, + noerrors, testSyncClaim, + }, + { + // failed syncVolume do not bind when matching volumeModes but class does not match + "14-5-1 - do not bind when matching volumeModes but class does not match", + withVolumeVolumeMode(&modeFile, newVolumeArray("volume14-5-1", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classGold)), + withVolumeVolumeMode(&modeFile, newVolumeArray("volume14-5-1", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classGold)), + withClaimVolumeMode(&modeFile, newClaimArray("claim14-5-1", "uid14-5-1", "10Gi", "", v1.ClaimPending, &classSilver)), + withClaimVolumeMode(&modeFile, newClaimArray("claim14-5-1", "uid14-5-1", "10Gi", "", v1.ClaimPending, &classSilver)), + []string{"Warning ProvisioningFailed"}, + noerrors, testSyncClaim, + }, + { + // failed syncVolume do not bind when pvc is prebound to pv with matching volumeModes but class does not match + "14-5-2 - do not bind when pvc is prebound to pv with matching volumeModes but class does not match", + withVolumeVolumeMode(&modeFile, newVolumeArray("volume14-5-2", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classGold)), + withVolumeVolumeMode(&modeFile, newVolumeArray("volume14-5-2", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classGold)), + withClaimVolumeMode(&modeFile, newClaimArray("claim14-5-2", "uid14-5-2", "10Gi", "volume14-5-2", v1.ClaimPending, &classSilver)), + withClaimVolumeMode(&modeFile, newClaimArray("claim14-5-2", "uid14-5-2", "10Gi", "volume14-5-2", v1.ClaimPending, &classSilver)), + []string{"Warning VolumeMismatch"}, + noerrors, testSyncClaim, + }, + { + // syncVolume bind when pv is prebound and volumeModes match + "14-7 - bind when pv volume is prebound and volumeModes match", + withVolumeVolumeMode(&modeBlock, newVolumeArray("volume14-7", "10Gi", "", "claim14-7", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classEmpty)), + withVolumeVolumeMode(&modeBlock, newVolumeArray("volume14-7", "10Gi", "uid14-7", "claim14-7", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, classEmpty)), + withClaimVolumeMode(&modeBlock, newClaimArray("claim14-7", "uid14-7", "10Gi", "", v1.ClaimPending, nil)), + withClaimVolumeMode(&modeBlock, newClaimArray("claim14-7", "uid14-7", "10Gi", "volume14-7", v1.ClaimBound, nil, annBoundByController, annBindCompleted)), + noevents, noerrors, testSyncClaim, + }, + { + // failed syncVolume do not bind when pvc is prebound to pv with mismatching volumeModes + "14-8 - do not bind when pvc is prebound to pv with mismatching volumeModes", + withVolumeVolumeMode(&modeBlock, newVolumeArray("volume14-8", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classEmpty)), + withVolumeVolumeMode(&modeBlock, newVolumeArray("volume14-8", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classEmpty)), + withClaimVolumeMode(&modeFile, newClaimArray("claim14-8", "uid14-8", "10Gi", "volume14-8", v1.ClaimPending, nil)), + withClaimVolumeMode(&modeFile, newClaimArray("claim14-8", "uid14-8", "10Gi", "volume14-8", v1.ClaimPending, nil)), + []string{"Warning VolumeMismatch"}, + noerrors, testSyncClaim, + }, + { + // failed syncVolume do not bind when pvc is prebound to pv with mismatching volumeModes + "14-8-1 - do not bind when pv is prebound to pvc with mismatching volumeModes", + withVolumeVolumeMode(&modeBlock, newVolumeArray("volume14-8-1", "10Gi", "", "claim14-8-1", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classEmpty)), + withVolumeVolumeMode(&modeBlock, newVolumeArray("volume14-8-1", "10Gi", "", "claim14-8-1", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classEmpty)), + withClaimVolumeMode(&modeFile, newClaimArray("claim14-8-1", "uid14-8-1", "10Gi", "", v1.ClaimPending, nil)), + withClaimVolumeMode(&modeFile, newClaimArray("claim14-8-1", "uid14-8-1", "10Gi", "", v1.ClaimPending, nil)), + []string{"Normal FailedBinding"}, + noerrors, testSyncClaim, + }, + { + // syncVolume binds when pvc is prebound to pv with matching volumeModes block + "14-9 - bind when pvc is prebound to pv with matching volumeModes block", + withVolumeVolumeMode(&modeBlock, newVolumeArray("volume14-9", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classEmpty)), + withVolumeVolumeMode(&modeBlock, newVolumeArray("volume14-9", "10Gi", "uid14-9", "claim14-9", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, classEmpty, annBoundByController)), + withClaimVolumeMode(&modeBlock, newClaimArray("claim14-9", "uid14-9", "10Gi", "volume14-9", v1.ClaimPending, nil)), + withClaimVolumeMode(&modeBlock, newClaimArray("claim14-9", "uid14-9", "10Gi", "volume14-9", v1.ClaimBound, nil, annBindCompleted)), + noevents, noerrors, testSyncClaim, + }, + { + // syncVolume binds when pv is prebound to pvc with matching volumeModes block + "14-10 - bind when pv is prebound to pvc with matching volumeModes block", + withVolumeVolumeMode(&modeBlock, newVolumeArray("volume14-10", "10Gi", "", "claim14-10", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classEmpty)), + withVolumeVolumeMode(&modeBlock, newVolumeArray("volume14-10", "10Gi", "uid14-10", "claim14-10", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, classEmpty)), + withClaimVolumeMode(&modeBlock, newClaimArray("claim14-10", "uid14-10", "10Gi", "", v1.ClaimPending, nil)), + withClaimVolumeMode(&modeBlock, newClaimArray("claim14-10", "uid14-10", "10Gi", "volume14-10", v1.ClaimBound, nil, annBoundByController, annBindCompleted)), + noevents, noerrors, testSyncClaim, + }, + { + // syncVolume binds when pvc is prebound to pv with matching volumeModes filesystem + "14-11 - bind when pvc is prebound to pv with matching volumeModes filesystem", + withVolumeVolumeMode(&modeFile, newVolumeArray("volume14-11", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classEmpty)), + withVolumeVolumeMode(&modeFile, newVolumeArray("volume14-11", "10Gi", "uid14-11", "claim14-11", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, classEmpty, annBoundByController)), + withClaimVolumeMode(&modeFile, newClaimArray("claim14-11", "uid14-11", "10Gi", "volume14-11", v1.ClaimPending, nil)), + withClaimVolumeMode(&modeFile, newClaimArray("claim14-11", "uid14-11", "10Gi", "volume14-11", v1.ClaimBound, nil, annBindCompleted)), + noevents, noerrors, testSyncClaim, + }, + { + // syncVolume binds when pv is prebound to pvc with matching volumeModes filesystem + "14-12 - bind when pv is prebound to pvc with matching volumeModes filesystem", + withVolumeVolumeMode(&modeFile, newVolumeArray("volume14-12", "10Gi", "", "claim14-12", v1.VolumePending, v1.PersistentVolumeReclaimRetain, classEmpty)), + withVolumeVolumeMode(&modeFile, newVolumeArray("volume14-12", "10Gi", "uid14-12", "claim14-12", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, classEmpty)), + withClaimVolumeMode(&modeFile, newClaimArray("claim14-12", "uid14-12", "10Gi", "", v1.ClaimPending, nil)), + withClaimVolumeMode(&modeFile, newClaimArray("claim14-12", "uid14-12", "10Gi", "volume14-12", v1.ClaimBound, nil, annBoundByController, annBindCompleted)), + noevents, noerrors, testSyncClaim, + }, + } + + err := utilfeature.DefaultFeatureGate.Set("BlockVolume=true") + if err != nil { + t.Errorf("Failed to enable feature gate for BlockVolume: %v", err) + return + } + defer utilfeature.DefaultFeatureGate.Set("BlockVolume=false") + runSyncTests(t, tests, []*storage.StorageClass{}) } diff --git a/pkg/controller/volume/persistentvolume/framework_test.go b/pkg/controller/volume/persistentvolume/framework_test.go index e81a60ccb36..5c165abd946 100644 --- a/pkg/controller/volume/persistentvolume/framework_test.go +++ b/pkg/controller/volume/persistentvolume/framework_test.go @@ -196,6 +196,8 @@ func (r *volumeReactor) React(action core.Action) (handled bool, ret runtime.Obj if storedVer != requestedVer { return true, obj, versionConflictError } + // Don't modify the existing object + volume = volume.DeepCopy() volume.ResourceVersion = strconv.Itoa(storedVer + 1) } else { return true, nil, fmt.Errorf("Cannot update volume %s: volume not found", volume.Name) @@ -220,6 +222,8 @@ func (r *volumeReactor) React(action core.Action) (handled bool, ret runtime.Obj if storedVer != requestedVer { return true, obj, versionConflictError } + // Don't modify the existing object + claim = claim.DeepCopy() claim.ResourceVersion = strconv.Itoa(storedVer + 1) } else { return true, nil, fmt.Errorf("Cannot update claim %s: claim not found", claim.Name) @@ -301,7 +305,12 @@ func (r *volumeReactor) checkVolumes(expectedVolumes []*v1.PersistentVolume) err gotMap := make(map[string]*v1.PersistentVolume) // Clear any ResourceVersion from both sets for _, v := range expectedVolumes { + // Don't modify the existing object + v := v.DeepCopy() v.ResourceVersion = "" + if v.Spec.ClaimRef != nil { + v.Spec.ClaimRef.ResourceVersion = "" + } expectedMap[v.Name] = v } for _, v := range r.volumes { @@ -331,6 +340,8 @@ func (r *volumeReactor) checkClaims(expectedClaims []*v1.PersistentVolumeClaim) expectedMap := make(map[string]*v1.PersistentVolumeClaim) gotMap := make(map[string]*v1.PersistentVolumeClaim) for _, c := range expectedClaims { + // Don't modify the existing object + c = c.DeepCopy() c.ResourceVersion = "" expectedMap[c.Name] = c } @@ -680,6 +691,22 @@ func withLabelSelector(labels map[string]string, claims []*v1.PersistentVolumeCl return claims } +// withVolumeVolumeMode applies the given VolumeMode to the first volume in the array and +// returns the array. Meant to be used to compose volumes specified inline in +// a test. +func withVolumeVolumeMode(mode *v1.PersistentVolumeMode, volumes []*v1.PersistentVolume) []*v1.PersistentVolume { + volumes[0].Spec.VolumeMode = mode + return volumes +} + +// withClaimVolumeMode applies the given VolumeMode to the first claim in the array and +// returns the array. Meant to be used to compose volumes specified inline in +// a test. +func withClaimVolumeMode(mode *v1.PersistentVolumeMode, claims []*v1.PersistentVolumeClaim) []*v1.PersistentVolumeClaim { + claims[0].Spec.VolumeMode = mode + return claims +} + // withExpectedCapacity sets the claim.Spec.Capacity of the first claim in the // array to given value and returns the array. Meant to be used to compose // claims specified inline in a test. @@ -806,6 +833,9 @@ var ( classUnknownInternal string = "unknown-internal" classUnsupportedMountOptions string = "unsupported-mountoptions" classLarge string = "large" + classWait string = "wait" + + modeWait = storage.VolumeBindingWaitForFirstConsumer ) // wrapTestWithPluginCalls returns a testCall that: diff --git a/pkg/controller/volume/persistentvolume/index.go b/pkg/controller/volume/persistentvolume/index.go index e9a40fe1c85..5c345744564 100644 --- a/pkg/controller/volume/persistentvolume/index.go +++ b/pkg/controller/volume/persistentvolume/index.go @@ -24,9 +24,12 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/tools/cache" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/volume" + volumeutil "k8s.io/kubernetes/pkg/volume/util" ) // persistentVolumeOrderedIndex is a cache.Store that keeps persistent volumes @@ -72,7 +75,7 @@ func (pvIndex *persistentVolumeOrderedIndex) listByAccessModes(modes []v1.Persis } // find returns the nearest PV from the ordered list or nil if a match is not found -func (pvIndex *persistentVolumeOrderedIndex) findByClaim(claim *v1.PersistentVolumeClaim) (*v1.PersistentVolume, error) { +func (pvIndex *persistentVolumeOrderedIndex) findByClaim(claim *v1.PersistentVolumeClaim, delayBinding bool) (*v1.PersistentVolume, error) { // PVs are indexed by their access modes to allow easier searching. Each // index is the string representation of a set of access modes. There is a // finite number of possible sets and PVs will only be indexed in one of @@ -88,6 +91,45 @@ func (pvIndex *persistentVolumeOrderedIndex) findByClaim(claim *v1.PersistentVol // example above). allPossibleModes := pvIndex.allPossibleMatchingAccessModes(claim.Spec.AccessModes) + for _, modes := range allPossibleModes { + volumes, err := pvIndex.listByAccessModes(modes) + if err != nil { + return nil, err + } + + bestVol, err := findMatchingVolume(claim, volumes, nil /* node for topology binding*/, nil /* exclusion map */, delayBinding) + if err != nil { + return nil, err + } + + if bestVol != nil { + return bestVol, nil + } + } + return nil, nil +} + +// findMatchingVolume goes through the list of volumes to find the best matching volume +// for the claim. +// +// This function is used by both the PV controller and scheduler. +// +// delayBinding is true only in the PV controller path. When set, prebound PVs are still returned +// as a match for the claim, but unbound PVs are skipped. +// +// node is set only in the scheduler path. When set, the PV node affinity is checked against +// the node's labels. +// +// excludedVolumes is only used in the scheduler path, and is needed for evaluating multiple +// unbound PVCs for a single Pod at one time. As each PVC finds a matching PV, the chosen +// PV needs to be excluded from future matching. +func findMatchingVolume( + claim *v1.PersistentVolumeClaim, + volumes []*v1.PersistentVolume, + node *v1.Node, + excludedVolumes map[string]*v1.PersistentVolume, + delayBinding bool) (*v1.PersistentVolume, error) { + var smallestVolume *v1.PersistentVolume var smallestVolumeQty resource.Quantity requestedQty := claim.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)] @@ -103,63 +145,130 @@ func (pvIndex *persistentVolumeOrderedIndex) findByClaim(claim *v1.PersistentVol selector = internalSelector } - for _, modes := range allPossibleModes { - volumes, err := pvIndex.listByAccessModes(modes) + // Go through all available volumes with two goals: + // - find a volume that is either pre-bound by user or dynamically + // provisioned for this claim. Because of this we need to loop through + // all volumes. + // - find the smallest matching one if there is no volume pre-bound to + // the claim. + for _, volume := range volumes { + if _, ok := excludedVolumes[volume.Name]; ok { + // Skip volumes in the excluded list + continue + } + + volumeQty := volume.Spec.Capacity[v1.ResourceStorage] + + // check if volumeModes do not match (Alpha and feature gate protected) + isMisMatch, err := checkVolumeModeMisMatches(&claim.Spec, &volume.Spec) if err != nil { - return nil, err + return nil, fmt.Errorf("error checking if volumeMode was a mismatch: %v", err) + } + // filter out mismatching volumeModes + if isMisMatch { + continue } - // Go through all available volumes with two goals: - // - find a volume that is either pre-bound by user or dynamically - // provisioned for this claim. Because of this we need to loop through - // all volumes. - // - find the smallest matching one if there is no volume pre-bound to - // the claim. - for _, volume := range volumes { - if isVolumeBoundToClaim(volume, claim) { - // this claim and volume are pre-bound; return - // the volume if the size request is satisfied, - // otherwise continue searching for a match - volumeQty := volume.Spec.Capacity[v1.ResourceStorage] - if volumeQty.Cmp(requestedQty) < 0 { - continue - } - return volume, nil - } - - // filter out: - // - volumes bound to another claim - // - volumes whose labels don't match the claim's selector, if specified - // - volumes in Class that is not requested - if volume.Spec.ClaimRef != nil { - continue - } else if selector != nil && !selector.Matches(labels.Set(volume.Labels)) { - continue - } - if v1helper.GetPersistentVolumeClass(volume) != requestedClass { - continue - } - - volumeQty := volume.Spec.Capacity[v1.ResourceStorage] - if volumeQty.Cmp(requestedQty) >= 0 { - if smallestVolume == nil || smallestVolumeQty.Cmp(volumeQty) > 0 { - smallestVolume = volume - smallestVolumeQty = volumeQty - } + nodeAffinityValid := true + if node != nil { + // Scheduler path, check that the PV NodeAffinity + // is satisfied by the node + err := volumeutil.CheckNodeAffinity(volume, node.Labels) + if err != nil { + nodeAffinityValid = false } } - if smallestVolume != nil { - // Found a matching volume - return smallestVolume, nil + if isVolumeBoundToClaim(volume, claim) { + // this claim and volume are pre-bound; return + // the volume if the size request is satisfied, + // otherwise continue searching for a match + if volumeQty.Cmp(requestedQty) < 0 { + continue + } + + // If PV node affinity is invalid, return no match. + // This means the prebound PV (and therefore PVC) + // is not suitable for this node. + if !nodeAffinityValid { + return nil, nil + } + + return volume, nil + } + + if node == nil && delayBinding { + // PV controller does not bind this claim. + // Scheduler will handle binding unbound volumes + // Scheduler path will have node != nil + continue + } + + // filter out: + // - volumes bound to another claim + // - volumes whose labels don't match the claim's selector, if specified + // - volumes in Class that is not requested + // - volumes whose NodeAffinity does not match the node + if volume.Spec.ClaimRef != nil { + continue + } else if selector != nil && !selector.Matches(labels.Set(volume.Labels)) { + continue + } + if v1helper.GetPersistentVolumeClass(volume) != requestedClass { + continue + } + if !nodeAffinityValid { + continue + } + + if node != nil { + // Scheduler path + // Check that the access modes match + if !checkAccessModes(claim, volume) { + continue + } + } + + if volumeQty.Cmp(requestedQty) >= 0 { + if smallestVolume == nil || smallestVolumeQty.Cmp(volumeQty) > 0 { + smallestVolume = volume + smallestVolumeQty = volumeQty + } } } + + if smallestVolume != nil { + // Found a matching volume + return smallestVolume, nil + } + return nil, nil } +// checkVolumeModeMatches is a convenience method that checks volumeMode for PersistentVolume +// and PersistentVolumeClaims along with making sure that the Alpha feature gate BlockVolume is +// enabled. +// This is Alpha and could change in the future. +func checkVolumeModeMisMatches(pvcSpec *v1.PersistentVolumeClaimSpec, pvSpec *v1.PersistentVolumeSpec) (bool, error) { + if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + if pvSpec.VolumeMode != nil && pvcSpec.VolumeMode != nil { + requestedVolumeMode := *pvcSpec.VolumeMode + pvVolumeMode := *pvSpec.VolumeMode + return requestedVolumeMode != pvVolumeMode, nil + } else { + // This also should retrun an error, this means that + // the defaulting has failed. + return true, fmt.Errorf("api defaulting for volumeMode failed") + } + } else { + // feature gate is disabled + return false, nil + } +} + // findBestMatchForClaim is a convenience method that finds a volume by the claim's AccessModes and requests for Storage -func (pvIndex *persistentVolumeOrderedIndex) findBestMatchForClaim(claim *v1.PersistentVolumeClaim) (*v1.PersistentVolume, error) { - return pvIndex.findByClaim(claim) +func (pvIndex *persistentVolumeOrderedIndex) findBestMatchForClaim(claim *v1.PersistentVolumeClaim, delayBinding bool) (*v1.PersistentVolume, error) { + return pvIndex.findByClaim(claim, delayBinding) } // allPossibleMatchingAccessModes returns an array of AccessMode arrays that @@ -241,3 +350,19 @@ func claimToClaimKey(claim *v1.PersistentVolumeClaim) string { func claimrefToClaimKey(claimref *v1.ObjectReference) string { return fmt.Sprintf("%s/%s", claimref.Namespace, claimref.Name) } + +// Returns true if PV satisfies all the PVC's requested AccessModes +func checkAccessModes(claim *v1.PersistentVolumeClaim, volume *v1.PersistentVolume) bool { + pvModesMap := map[v1.PersistentVolumeAccessMode]bool{} + for _, mode := range volume.Spec.AccessModes { + pvModesMap[mode] = true + } + + for _, mode := range claim.Spec.AccessModes { + _, ok := pvModesMap[mode] + if !ok { + return false + } + } + return true +} diff --git a/pkg/controller/volume/persistentvolume/index_test.go b/pkg/controller/volume/persistentvolume/index_test.go index 7bab162d937..5f5b50af50f 100644 --- a/pkg/controller/volume/persistentvolume/index_test.go +++ b/pkg/controller/volume/persistentvolume/index_test.go @@ -20,12 +20,16 @@ import ( "sort" "testing" + "github.com/golang/glog" + "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" "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/apis/core/v1/helper" "k8s.io/kubernetes/pkg/volume" ) @@ -50,6 +54,28 @@ func makePVC(size string, modfn func(*v1.PersistentVolumeClaim)) *v1.PersistentV return &pvc } +func makeVolumeModePVC(size string, mode *v1.PersistentVolumeMode, modfn func(*v1.PersistentVolumeClaim)) *v1.PersistentVolumeClaim { + pvc := v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "claim01", + Namespace: "myns", + }, + Spec: v1.PersistentVolumeClaimSpec{ + VolumeMode: mode, + AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceName(v1.ResourceStorage): resource.MustParse(size), + }, + }, + }, + } + if modfn != nil { + modfn(&pvc) + } + return &pvc +} + func TestMatchVolume(t *testing.T) { volList := newPersistentVolumeOrderedIndex() for _, pv := range createTestVolumes() { @@ -155,7 +181,7 @@ func TestMatchVolume(t *testing.T) { } for name, scenario := range scenarios { - volume, err := volList.findBestMatchForClaim(scenario.claim) + volume, err := volList.findBestMatchForClaim(scenario.claim, false) if err != nil { t.Errorf("Unexpected error matching volume by claim: %v", err) } @@ -226,7 +252,7 @@ func TestMatchingWithBoundVolumes(t *testing.T) { }, } - volume, err := volumeIndex.findBestMatchForClaim(claim) + volume, err := volumeIndex.findBestMatchForClaim(claim, false) if err != nil { t.Fatalf("Unexpected error matching volume by claim: %v", err) } @@ -349,27 +375,27 @@ func TestFindingVolumeWithDifferentAccessModes(t *testing.T) { index.store.Add(ebs) index.store.Add(nfs) - volume, _ := index.findBestMatchForClaim(claim) + volume, _ := index.findBestMatchForClaim(claim, false) if volume.Name != ebs.Name { t.Errorf("Expected %s but got volume %s instead", ebs.Name, volume.Name) } claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadOnlyMany} - volume, _ = index.findBestMatchForClaim(claim) + volume, _ = index.findBestMatchForClaim(claim, false) if volume.Name != gce.Name { t.Errorf("Expected %s but got volume %s instead", gce.Name, volume.Name) } // order of the requested modes should not matter claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteMany, v1.ReadWriteOnce, v1.ReadOnlyMany} - volume, _ = index.findBestMatchForClaim(claim) + volume, _ = index.findBestMatchForClaim(claim, false) if volume.Name != nfs.Name { t.Errorf("Expected %s but got volume %s instead", nfs.Name, volume.Name) } // fewer modes requested should still match claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteMany} - volume, _ = index.findBestMatchForClaim(claim) + volume, _ = index.findBestMatchForClaim(claim, false) if volume.Name != nfs.Name { t.Errorf("Expected %s but got volume %s instead", nfs.Name, volume.Name) } @@ -377,7 +403,7 @@ func TestFindingVolumeWithDifferentAccessModes(t *testing.T) { // pretend the exact match is bound. should get the next level up of modes. ebs.Spec.ClaimRef = &v1.ObjectReference{} claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce} - volume, _ = index.findBestMatchForClaim(claim) + volume, _ = index.findBestMatchForClaim(claim, false) if volume.Name != gce.Name { t.Errorf("Expected %s but got volume %s instead", gce.Name, volume.Name) } @@ -385,7 +411,7 @@ func TestFindingVolumeWithDifferentAccessModes(t *testing.T) { // continue up the levels of modes. gce.Spec.ClaimRef = &v1.ObjectReference{} claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce} - volume, _ = index.findBestMatchForClaim(claim) + volume, _ = index.findBestMatchForClaim(claim, false) if volume.Name != nfs.Name { t.Errorf("Expected %s but got volume %s instead", nfs.Name, volume.Name) } @@ -393,7 +419,7 @@ func TestFindingVolumeWithDifferentAccessModes(t *testing.T) { // partial mode request gce.Spec.ClaimRef = nil claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany} - volume, _ = index.findBestMatchForClaim(claim) + volume, _ = index.findBestMatchForClaim(claim, false) if volume.Name != gce.Name { t.Errorf("Expected %s but got volume %s instead", gce.Name, volume.Name) } @@ -652,6 +678,87 @@ func createTestVolumes() []*v1.PersistentVolume { StorageClassName: classLarge, }, }, + { + ObjectMeta: metav1.ObjectMeta{ + UID: "affinity-pv", + Name: "affinity001", + Annotations: getAnnotationWithNodeAffinity("key1", "value1"), + }, + Spec: v1.PersistentVolumeSpec{ + Capacity: v1.ResourceList{ + v1.ResourceName(v1.ResourceStorage): resource.MustParse("100G"), + }, + PersistentVolumeSource: v1.PersistentVolumeSource{ + Local: &v1.LocalVolumeSource{}, + }, + AccessModes: []v1.PersistentVolumeAccessMode{ + v1.ReadWriteOnce, + v1.ReadOnlyMany, + }, + StorageClassName: classWait, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + UID: "affinity-pv2", + Name: "affinity002", + Annotations: getAnnotationWithNodeAffinity("key1", "value1"), + }, + Spec: v1.PersistentVolumeSpec{ + Capacity: v1.ResourceList{ + v1.ResourceName(v1.ResourceStorage): resource.MustParse("150G"), + }, + PersistentVolumeSource: v1.PersistentVolumeSource{ + Local: &v1.LocalVolumeSource{}, + }, + AccessModes: []v1.PersistentVolumeAccessMode{ + v1.ReadWriteOnce, + v1.ReadOnlyMany, + }, + StorageClassName: classWait, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + UID: "affinity-prebound", + Name: "affinity003", + Annotations: getAnnotationWithNodeAffinity("key1", "value1"), + }, + Spec: v1.PersistentVolumeSpec{ + Capacity: v1.ResourceList{ + v1.ResourceName(v1.ResourceStorage): resource.MustParse("100G"), + }, + PersistentVolumeSource: v1.PersistentVolumeSource{ + Local: &v1.LocalVolumeSource{}, + }, + AccessModes: []v1.PersistentVolumeAccessMode{ + v1.ReadWriteOnce, + v1.ReadOnlyMany, + }, + StorageClassName: classWait, + ClaimRef: &v1.ObjectReference{Name: "claim02", Namespace: "myns"}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + UID: "affinity-pv3", + Name: "affinity003", + Annotations: getAnnotationWithNodeAffinity("key1", "value3"), + }, + Spec: v1.PersistentVolumeSpec{ + Capacity: v1.ResourceList{ + v1.ResourceName(v1.ResourceStorage): resource.MustParse("200G"), + }, + PersistentVolumeSource: v1.PersistentVolumeSource{ + Local: &v1.LocalVolumeSource{}, + }, + AccessModes: []v1.PersistentVolumeAccessMode{ + v1.ReadWriteOnce, + v1.ReadOnlyMany, + }, + StorageClassName: classWait, + }, + }, } } @@ -669,6 +776,280 @@ func testVolume(name, size string) *v1.PersistentVolume { } } +func getAnnotationWithNodeAffinity(key string, value string) map[string]string { + affinity := &v1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: key, + Operator: v1.NodeSelectorOpIn, + Values: []string{value}, + }, + }, + }, + }, + }, + } + + annotations := map[string]string{} + err := helper.StorageNodeAffinityToAlphaAnnotation(annotations, affinity) + if err != nil { + glog.Fatalf("Failed to get node affinity annotation: %v", err) + } + + return annotations +} + +func createVolumeModeBlockTestVolume() *v1.PersistentVolume { + blockMode := v1.PersistentVolumeBlock + + return &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + UID: "local-1", + Name: "block", + }, + Spec: v1.PersistentVolumeSpec{ + Capacity: v1.ResourceList{ + v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G"), + }, + PersistentVolumeSource: v1.PersistentVolumeSource{ + Local: &v1.LocalVolumeSource{}, + }, + AccessModes: []v1.PersistentVolumeAccessMode{ + v1.ReadWriteOnce, + }, + VolumeMode: &blockMode, + }, + } +} + +func createVolumeModeFilesystemTestVolume() *v1.PersistentVolume { + filesystemMode := v1.PersistentVolumeFilesystem + + return &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + UID: "local-1", + Name: "block", + }, + Spec: v1.PersistentVolumeSpec{ + Capacity: v1.ResourceList{ + v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G"), + }, + PersistentVolumeSource: v1.PersistentVolumeSource{ + Local: &v1.LocalVolumeSource{}, + }, + AccessModes: []v1.PersistentVolumeAccessMode{ + v1.ReadWriteOnce, + }, + VolumeMode: &filesystemMode, + }, + } +} + +func createTestVolOrderedIndex(pv *v1.PersistentVolume) persistentVolumeOrderedIndex { + volFile := newPersistentVolumeOrderedIndex() + volFile.store.Add(pv) + return volFile +} + +func toggleBlockVolumeFeature(toggleFlag bool, t *testing.T) { + if toggleFlag { + // Enable alpha feature BlockVolume + err := utilfeature.DefaultFeatureGate.Set("BlockVolume=true") + if err != nil { + t.Errorf("Failed to enable feature gate for BlockVolume: %v", err) + return + } + } else { + err := utilfeature.DefaultFeatureGate.Set("BlockVolume=false") + if err != nil { + t.Errorf("Failed to disable feature gate for BlockVolume: %v", err) + return + } + } +} + +func TestAlphaVolumeModeCheck(t *testing.T) { + + blockMode := v1.PersistentVolumeBlock + filesystemMode := v1.PersistentVolumeFilesystem + + // If feature gate is enabled, VolumeMode will always be defaulted + // If feature gate is disabled, VolumeMode is dropped by API and ignored + scenarios := map[string]struct { + isExpectedMisMatch bool + vol *v1.PersistentVolume + pvc *v1.PersistentVolumeClaim + enableBlock bool + }{ + "feature enabled - pvc block and pv filesystem": { + isExpectedMisMatch: true, + vol: createVolumeModeFilesystemTestVolume(), + pvc: makeVolumeModePVC("8G", &blockMode, nil), + enableBlock: true, + }, + "feature enabled - pvc filesystem and pv block": { + isExpectedMisMatch: true, + vol: createVolumeModeBlockTestVolume(), + pvc: makeVolumeModePVC("8G", &filesystemMode, nil), + enableBlock: true, + }, + "feature enabled - pvc block and pv block": { + isExpectedMisMatch: false, + vol: createVolumeModeBlockTestVolume(), + pvc: makeVolumeModePVC("8G", &blockMode, nil), + enableBlock: true, + }, + "feature enabled - pvc filesystem and pv filesystem": { + isExpectedMisMatch: false, + vol: createVolumeModeFilesystemTestVolume(), + pvc: makeVolumeModePVC("8G", &filesystemMode, nil), + enableBlock: true, + }, + "feature disabled - pvc block and pv filesystem": { + isExpectedMisMatch: false, + vol: createVolumeModeFilesystemTestVolume(), + pvc: makeVolumeModePVC("8G", &blockMode, nil), + enableBlock: false, + }, + "feature disabled - pvc filesystem and pv block": { + isExpectedMisMatch: false, + vol: createVolumeModeBlockTestVolume(), + pvc: makeVolumeModePVC("8G", &filesystemMode, nil), + enableBlock: false, + }, + "feature disabled - pvc block and pv block": { + isExpectedMisMatch: false, + vol: createVolumeModeBlockTestVolume(), + pvc: makeVolumeModePVC("8G", &blockMode, nil), + enableBlock: false, + }, + "feature disabled - pvc filesystem and pv filesystem": { + isExpectedMisMatch: false, + vol: createVolumeModeFilesystemTestVolume(), + pvc: makeVolumeModePVC("8G", &filesystemMode, nil), + enableBlock: false, + }, + } + + for name, scenario := range scenarios { + toggleBlockVolumeFeature(scenario.enableBlock, t) + expectedMisMatch, err := checkVolumeModeMisMatches(&scenario.pvc.Spec, &scenario.vol.Spec) + if err != nil { + t.Errorf("Unexpected failure for checkVolumeModeMisMatches: %v", err) + } + // expected to match but either got an error or no returned pvmatch + if expectedMisMatch && !scenario.isExpectedMisMatch { + t.Errorf("Unexpected failure for scenario, expected not to mismatch on modes but did: %s", name) + } + if !expectedMisMatch && scenario.isExpectedMisMatch { + t.Errorf("Unexpected failure for scenario, did not mismatch on mode when expected to mismatch: %s", name) + } + } + + // make sure feature gate is turned off + toggleBlockVolumeFeature(false, t) +} + +func TestAlphaFilteringVolumeModes(t *testing.T) { + blockMode := v1.PersistentVolumeBlock + filesystemMode := v1.PersistentVolumeFilesystem + + // If feature gate is enabled, VolumeMode will always be defaulted + // If feature gate is disabled, VolumeMode is dropped by API and ignored + scenarios := map[string]struct { + isExpectedMatch bool + vol persistentVolumeOrderedIndex + pvc *v1.PersistentVolumeClaim + enableBlock bool + }{ + "1-1 feature enabled - pvc block and pv filesystem": { + isExpectedMatch: false, + vol: createTestVolOrderedIndex(createVolumeModeFilesystemTestVolume()), + pvc: makeVolumeModePVC("8G", &blockMode, nil), + enableBlock: true, + }, + "1-2 feature enabled - pvc filesystem and pv block": { + isExpectedMatch: false, + vol: createTestVolOrderedIndex(createVolumeModeBlockTestVolume()), + pvc: makeVolumeModePVC("8G", &filesystemMode, nil), + enableBlock: true, + }, + "1-3 feature enabled - pvc block and pv no mode with default filesystem": { + isExpectedMatch: false, + vol: createTestVolOrderedIndex(createVolumeModeFilesystemTestVolume()), + pvc: makeVolumeModePVC("8G", &blockMode, nil), + enableBlock: true, + }, + "1-4 feature enabled - pvc no mode defaulted to filesystem and pv block": { + isExpectedMatch: false, + vol: createTestVolOrderedIndex(createVolumeModeBlockTestVolume()), + pvc: makeVolumeModePVC("8G", &filesystemMode, nil), + enableBlock: true, + }, + "1-5 feature enabled - pvc block and pv block": { + isExpectedMatch: true, + vol: createTestVolOrderedIndex(createVolumeModeBlockTestVolume()), + pvc: makeVolumeModePVC("8G", &blockMode, nil), + enableBlock: true, + }, + "1-6 feature enabled - pvc filesystem and pv filesystem": { + isExpectedMatch: true, + vol: createTestVolOrderedIndex(createVolumeModeFilesystemTestVolume()), + pvc: makeVolumeModePVC("8G", &filesystemMode, nil), + enableBlock: true, + }, + "1-7 feature enabled - pvc mode is nil and defaulted and pv mode is nil and defaulted": { + isExpectedMatch: true, + vol: createTestVolOrderedIndex(createVolumeModeFilesystemTestVolume()), + pvc: makeVolumeModePVC("8G", &filesystemMode, nil), + enableBlock: true, + }, + "2-1 feature disabled - pvc mode is nil and pv mode is nil": { + isExpectedMatch: true, + vol: createTestVolOrderedIndex(testVolume("nomode-1", "8G")), + pvc: makeVolumeModePVC("8G", nil, nil), + enableBlock: false, + }, + "2-2 feature disabled - pvc mode is block and pv mode is block - fields should be dropped by api and not analyzed with gate disabled": { + isExpectedMatch: true, + vol: createTestVolOrderedIndex(createVolumeModeBlockTestVolume()), + pvc: makeVolumeModePVC("8G", &blockMode, nil), + enableBlock: false, + }, + "2-3 feature disabled - pvc mode is filesystem and pv mode is filesystem - fields should be dropped by api and not analyzed with gate disabled": { + isExpectedMatch: true, + vol: createTestVolOrderedIndex(createVolumeModeFilesystemTestVolume()), + pvc: makeVolumeModePVC("8G", &filesystemMode, nil), + enableBlock: false, + }, + } + + for name, scenario := range scenarios { + toggleBlockVolumeFeature(scenario.enableBlock, t) + pvmatch, err := scenario.vol.findBestMatchForClaim(scenario.pvc, false) + // expected to match but either got an error or no returned pvmatch + if pvmatch == nil && scenario.isExpectedMatch { + t.Errorf("Unexpected failure for scenario, no matching volume: %s", name) + } + if err != nil && scenario.isExpectedMatch { + t.Errorf("Unexpected failure for scenario: %s - %+v", name, err) + } + // expected to not match but either got an error or a returned pvmatch + if pvmatch != nil && !scenario.isExpectedMatch { + t.Errorf("Unexpected failure for scenario, expected no matching volume: %s", name) + } + if err != nil && !scenario.isExpectedMatch { + t.Errorf("Unexpected failure for scenario: %s - %+v", name, err) + } + } + + // make sure feature gate is turned off + toggleBlockVolumeFeature(false, t) +} + func TestFindingPreboundVolumes(t *testing.T) { claim := &v1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ @@ -701,14 +1082,14 @@ func TestFindingPreboundVolumes(t *testing.T) { index.store.Add(pvBadMode) // expected exact match on size - volume, _ := index.findBestMatchForClaim(claim) + volume, _ := index.findBestMatchForClaim(claim, false) if volume.Name != pv1.Name { t.Errorf("Expected %s but got volume %s instead", pv1.Name, volume.Name) } // pretend the exact match is pre-bound. should get the next size up. pv1.Spec.ClaimRef = &v1.ObjectReference{Name: "foo", Namespace: "bar"} - volume, _ = index.findBestMatchForClaim(claim) + volume, _ = index.findBestMatchForClaim(claim, false) if volume.Name != pv5.Name { t.Errorf("Expected %s but got volume %s instead", pv5.Name, volume.Name) } @@ -716,7 +1097,7 @@ func TestFindingPreboundVolumes(t *testing.T) { // pretend the exact match is available but the largest volume is pre-bound to the claim. pv1.Spec.ClaimRef = nil pv8.Spec.ClaimRef = claimRef - volume, _ = index.findBestMatchForClaim(claim) + volume, _ = index.findBestMatchForClaim(claim, false) if volume.Name != pv8.Name { t.Errorf("Expected %s but got volume %s instead", pv8.Name, volume.Name) } @@ -724,7 +1105,7 @@ func TestFindingPreboundVolumes(t *testing.T) { // pretend the volume with too small a size is pre-bound to the claim. should get the exact match. pv8.Spec.ClaimRef = nil pvBadSize.Spec.ClaimRef = claimRef - volume, _ = index.findBestMatchForClaim(claim) + volume, _ = index.findBestMatchForClaim(claim, false) if volume.Name != pv1.Name { t.Errorf("Expected %s but got volume %s instead", pv1.Name, volume.Name) } @@ -732,12 +1113,186 @@ func TestFindingPreboundVolumes(t *testing.T) { // pretend the volume without the right access mode is pre-bound to the claim. should get the exact match. pvBadSize.Spec.ClaimRef = nil pvBadMode.Spec.ClaimRef = claimRef - volume, _ = index.findBestMatchForClaim(claim) + volume, _ = index.findBestMatchForClaim(claim, false) if volume.Name != pv1.Name { t.Errorf("Expected %s but got volume %s instead", pv1.Name, volume.Name) } } +func TestBestMatchDelayed(t *testing.T) { + volList := newPersistentVolumeOrderedIndex() + for _, pv := range createTestVolumes() { + volList.store.Add(pv) + } + + // binding through PV controller should be delayed + claim := makePVC("8G", nil) + volume, err := volList.findBestMatchForClaim(claim, true) + if err != nil { + t.Errorf("Unexpected error matching volume by claim: %v", err) + } + if volume != nil { + t.Errorf("Unexpected match with %q", volume.UID) + } +} + +func TestFindMatchVolumeWithNode(t *testing.T) { + volumes := createTestVolumes() + node1 := &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"key1": "value1"}, + }, + } + node2 := &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"key1": "value2"}, + }, + } + node3 := &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"key1": "value3"}, + }, + } + + scenarios := map[string]struct { + expectedMatch string + claim *v1.PersistentVolumeClaim + node *v1.Node + excludedVolumes map[string]*v1.PersistentVolume + }{ + "success-match": { + expectedMatch: "affinity-pv", + claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) { + pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce} + pvc.Spec.StorageClassName = &classWait + }), + node: node1, + }, + "success-prebound": { + expectedMatch: "affinity-prebound", + claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) { + pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce} + pvc.Spec.StorageClassName = &classWait + pvc.Name = "claim02" + }), + node: node1, + }, + "success-exclusion": { + expectedMatch: "affinity-pv2", + claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) { + pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce} + pvc.Spec.StorageClassName = &classWait + }), + node: node1, + excludedVolumes: map[string]*v1.PersistentVolume{"affinity001": nil}, + }, + "fail-exclusion": { + expectedMatch: "", + claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) { + pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce} + pvc.Spec.StorageClassName = &classWait + }), + node: node1, + excludedVolumes: map[string]*v1.PersistentVolume{"affinity001": nil, "affinity002": nil}, + }, + "fail-accessmode": { + expectedMatch: "", + claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) { + pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteMany} + pvc.Spec.StorageClassName = &classWait + }), + node: node1, + }, + "fail-nodeaffinity": { + expectedMatch: "", + claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) { + pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce} + pvc.Spec.StorageClassName = &classWait + }), + node: node2, + }, + "fail-prebound-node-affinity": { + expectedMatch: "", + claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) { + pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce} + pvc.Spec.StorageClassName = &classWait + pvc.Name = "claim02" + }), + node: node3, + }, + "success-bad-and-good-node-affinity": { + expectedMatch: "affinity-pv3", + claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) { + pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce} + pvc.Spec.StorageClassName = &classWait + pvc.Name = "claim03" + }), + node: node3, + }, + } + + for name, scenario := range scenarios { + volume, err := findMatchingVolume(scenario.claim, volumes, scenario.node, scenario.excludedVolumes, true) + if err != nil { + t.Errorf("Unexpected error matching volume by claim: %v", err) + } + if len(scenario.expectedMatch) != 0 && volume == nil { + t.Errorf("Expected match but received nil volume for scenario: %s", name) + } + if len(scenario.expectedMatch) != 0 && volume != nil && string(volume.UID) != scenario.expectedMatch { + t.Errorf("Expected %s but got volume %s in scenario %s", scenario.expectedMatch, volume.UID, name) + } + if len(scenario.expectedMatch) == 0 && volume != nil { + t.Errorf("Unexpected match for scenario: %s, matched with %s instead", name, volume.UID) + } + } +} + +func TestCheckAccessModes(t *testing.T) { + volume := &v1.PersistentVolume{ + Spec: v1.PersistentVolumeSpec{ + AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadWriteMany}, + }, + } + + scenarios := map[string]struct { + shouldSucceed bool + claim *v1.PersistentVolumeClaim + }{ + "success-single-mode": { + shouldSucceed: true, + claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) { + pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteMany} + }), + }, + "success-many-modes": { + shouldSucceed: true, + claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) { + pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteMany, v1.ReadWriteOnce} + }), + }, + "fail-single-mode": { + shouldSucceed: false, + claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) { + pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany} + }), + }, + "fail-many-modes": { + shouldSucceed: false, + claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) { + pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteMany, v1.ReadOnlyMany} + }), + }, + } + + for name, scenario := range scenarios { + result := checkAccessModes(scenario.claim, volume) + if result != scenario.shouldSucceed { + t.Errorf("Test %q failed: Expected %v, got %v", name, scenario.shouldSucceed, result) + } + } +} + // byCapacity is used to order volumes by ascending storage size type byCapacity struct { volumes []*v1.PersistentVolume diff --git a/pkg/controller/volume/persistentvolume/provision_test.go b/pkg/controller/volume/persistentvolume/provision_test.go index db247b080ce..15b8987090d 100644 --- a/pkg/controller/volume/persistentvolume/provision_test.go +++ b/pkg/controller/volume/persistentvolume/provision_test.go @@ -24,7 +24,7 @@ import ( storage "k8s.io/api/storage/v1" apierrs "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) var class1Parameters = map[string]string{ diff --git a/pkg/controller/volume/persistentvolume/pv_controller.go b/pkg/controller/volume/persistentvolume/pv_controller.go index 3c6b60fb650..1883defac93 100644 --- a/pkg/controller/volume/persistentvolume/pv_controller.go +++ b/pkg/controller/volume/persistentvolume/pv_controller.go @@ -26,6 +26,7 @@ import ( storage "k8s.io/api/storage/v1" apierrs "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utilfeature "k8s.io/apiserver/pkg/util/feature" clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" corelisters "k8s.io/client-go/listers/core/v1" @@ -34,9 +35,10 @@ import ( "k8s.io/client-go/tools/record" ref "k8s.io/client-go/tools/reference" "k8s.io/client-go/util/workqueue" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" "k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/controller/volume/events" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/util/goroutinemap" "k8s.io/kubernetes/pkg/util/goroutinemap/exponentialbackoff" vol "k8s.io/kubernetes/pkg/volume" @@ -231,6 +233,10 @@ func (ctrl *PersistentVolumeController) syncClaim(claim *v1.PersistentVolumeClai func checkVolumeSatisfyClaim(volume *v1.PersistentVolume, claim *v1.PersistentVolumeClaim) error { requestedQty := claim.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)] requestedSize := requestedQty.Value() + isMisMatch, err := checkVolumeModeMisMatches(&claim.Spec, &volume.Spec) + if err != nil { + return fmt.Errorf("error checking if volumeMode was a mismatch: %v", err) + } volumeQty := volume.Spec.Capacity[v1.ResourceStorage] volumeSize := volumeQty.Value() @@ -243,9 +249,37 @@ func checkVolumeSatisfyClaim(volume *v1.PersistentVolume, claim *v1.PersistentVo return fmt.Errorf("Class of volume[%s] is not the same as claim[%v]", volume.Name, claimToClaimKey(claim)) } + if isMisMatch { + return fmt.Errorf("VolumeMode[%v] of volume[%s] is incompatible with VolumeMode[%v] of claim[%v]", volume.Spec.VolumeMode, volume.Name, claim.Spec.VolumeMode, claim.Name) + } + return nil } +func (ctrl *PersistentVolumeController) shouldDelayBinding(claim *v1.PersistentVolumeClaim) (bool, error) { + if !utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) { + return false, nil + } + + className := v1helper.GetPersistentVolumeClaimClass(claim) + if className == "" { + return false, nil + } + + class, err := ctrl.classLister.Get(className) + if err != nil { + return false, nil + } + + if class.VolumeBindingMode == nil { + return false, fmt.Errorf("VolumeBindingMode not set for StorageClass %q", className) + } + + // TODO: add check to handle dynamic provisioning later + + return *class.VolumeBindingMode == storage.VolumeBindingWaitForFirstConsumer, nil +} + // syncUnboundClaim is the main controller method to decide what to do with an // unbound claim. func (ctrl *PersistentVolumeController) syncUnboundClaim(claim *v1.PersistentVolumeClaim) error { @@ -253,9 +287,13 @@ func (ctrl *PersistentVolumeController) syncUnboundClaim(claim *v1.PersistentVol // OBSERVATION: pvc is "Pending" if claim.Spec.VolumeName == "" { // User did not care which PV they get. + delayBinding, err := ctrl.shouldDelayBinding(claim) + if err != nil { + return err + } // [Unit test set 1] - volume, err := ctrl.volumes.findBestMatchForClaim(claim) + volume, err := ctrl.volumes.findBestMatchForClaim(claim, delayBinding) if err != nil { glog.V(2).Infof("synchronizing unbound PersistentVolumeClaim[%s]: Error finding PV for claim: %v", claimToClaimKey(claim), err) return fmt.Errorf("Error finding PV for claim %q: %v", claimToClaimKey(claim), err) @@ -264,15 +302,21 @@ func (ctrl *PersistentVolumeController) syncUnboundClaim(claim *v1.PersistentVol glog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: no volume found", claimToClaimKey(claim)) // No PV could be found // OBSERVATION: pvc is "Pending", will retry - if v1helper.GetPersistentVolumeClaimClass(claim) != "" { + switch { + case delayBinding: + // TODO: Skip dynamic provisioning for now + ctrl.eventRecorder.Event(claim, v1.EventTypeNormal, events.WaitForFirstConsumer, "waiting for first consumer to be created before binding") + case v1helper.GetPersistentVolumeClaimClass(claim) != "": if err = ctrl.provisionClaim(claim); err != nil { return err } return nil + default: + ctrl.eventRecorder.Event(claim, v1.EventTypeNormal, events.FailedBinding, "no persistent volumes available for this claim and no storage class is set") } + // Mark the claim as Pending and try to find a match in the next // periodic syncClaim - ctrl.eventRecorder.Event(claim, v1.EventTypeNormal, events.FailedBinding, "no persistent volumes available for this claim and no storage class is set") if _, err = ctrl.updateClaimStatus(claim, v1.ClaimPending, nil); err != nil { return err } @@ -641,7 +685,7 @@ func (ctrl *PersistentVolumeController) updateClaimStatus(claim *v1.PersistentVo return claim, nil } - newClaim, err := ctrl.kubeClient.Core().PersistentVolumeClaims(claimClone.Namespace).UpdateStatus(claimClone) + newClaim, err := ctrl.kubeClient.CoreV1().PersistentVolumeClaims(claimClone.Namespace).UpdateStatus(claimClone) if err != nil { glog.V(4).Infof("updating PersistentVolumeClaim[%s] status: set phase %s failed: %v", claimToClaimKey(claim), phase, err) return newClaim, err @@ -697,7 +741,7 @@ func (ctrl *PersistentVolumeController) updateVolumePhase(volume *v1.PersistentV volumeClone.Status.Phase = phase volumeClone.Status.Message = message - newVol, err := ctrl.kubeClient.Core().PersistentVolumes().UpdateStatus(volumeClone) + newVol, err := ctrl.kubeClient.CoreV1().PersistentVolumes().UpdateStatus(volumeClone) if err != nil { glog.V(4).Infof("updating PersistentVolume[%s]: set phase %s failed: %v", volume.Name, phase, err) return newVol, err @@ -740,6 +784,42 @@ func (ctrl *PersistentVolumeController) updateVolumePhaseWithEvent(volume *v1.Pe func (ctrl *PersistentVolumeController) bindVolumeToClaim(volume *v1.PersistentVolume, claim *v1.PersistentVolumeClaim) (*v1.PersistentVolume, error) { glog.V(4).Infof("updating PersistentVolume[%s]: binding to %q", volume.Name, claimToClaimKey(claim)) + volumeClone, dirty, err := ctrl.getBindVolumeToClaim(volume, claim) + if err != nil { + return nil, err + } + + // Save the volume only if something was changed + if dirty { + return ctrl.updateBindVolumeToClaim(volumeClone, claim, true) + } + + glog.V(4).Infof("updating PersistentVolume[%s]: already bound to %q", volume.Name, claimToClaimKey(claim)) + return volume, nil +} + +// bindVolumeToClaim modifies given volume to be bound to a claim and saves it to +// API server. The claim is not modified in this method! +func (ctrl *PersistentVolumeController) updateBindVolumeToClaim(volumeClone *v1.PersistentVolume, claim *v1.PersistentVolumeClaim, updateCache bool) (*v1.PersistentVolume, error) { + glog.V(2).Infof("claim %q bound to volume %q", claimToClaimKey(claim), volumeClone.Name) + newVol, err := ctrl.kubeClient.CoreV1().PersistentVolumes().Update(volumeClone) + if err != nil { + glog.V(4).Infof("updating PersistentVolume[%s]: binding to %q failed: %v", volumeClone.Name, claimToClaimKey(claim), err) + return newVol, err + } + if updateCache { + _, err = ctrl.storeVolumeUpdate(newVol) + if err != nil { + glog.V(4).Infof("updating PersistentVolume[%s]: cannot update internal cache: %v", volumeClone.Name, err) + return newVol, err + } + } + glog.V(4).Infof("updating PersistentVolume[%s]: bound to %q", newVol.Name, claimToClaimKey(claim)) + return newVol, nil +} + +// Get new PV object only, no API or cache update +func (ctrl *PersistentVolumeController) getBindVolumeToClaim(volume *v1.PersistentVolume, claim *v1.PersistentVolumeClaim) (*v1.PersistentVolume, bool, error) { dirty := false // Check if the volume was already bound (either by user or by controller) @@ -760,7 +840,7 @@ func (ctrl *PersistentVolumeController) bindVolumeToClaim(volume *v1.PersistentV claimRef, err := ref.GetReference(scheme.Scheme, claim) if err != nil { - return nil, fmt.Errorf("Unexpected error getting claim reference: %v", err) + return nil, false, fmt.Errorf("Unexpected error getting claim reference: %v", err) } volumeClone.Spec.ClaimRef = claimRef dirty = true @@ -772,25 +852,7 @@ func (ctrl *PersistentVolumeController) bindVolumeToClaim(volume *v1.PersistentV dirty = true } - // Save the volume only if something was changed - if dirty { - glog.V(2).Infof("claim %q bound to volume %q", claimToClaimKey(claim), volume.Name) - newVol, err := ctrl.kubeClient.Core().PersistentVolumes().Update(volumeClone) - if err != nil { - glog.V(4).Infof("updating PersistentVolume[%s]: binding to %q failed: %v", volume.Name, claimToClaimKey(claim), err) - return newVol, err - } - _, err = ctrl.storeVolumeUpdate(newVol) - if err != nil { - glog.V(4).Infof("updating PersistentVolume[%s]: cannot update internal cache: %v", volume.Name, err) - return newVol, err - } - glog.V(4).Infof("updating PersistentVolume[%s]: bound to %q", newVol.Name, claimToClaimKey(claim)) - return newVol, nil - } - - glog.V(4).Infof("updating PersistentVolume[%s]: already bound to %q", volume.Name, claimToClaimKey(claim)) - return volume, nil + return volumeClone, dirty, nil } // bindClaimToVolume modifies the given claim to be bound to a volume and @@ -829,7 +891,7 @@ func (ctrl *PersistentVolumeController) bindClaimToVolume(claim *v1.PersistentVo if dirty { glog.V(2).Infof("volume %q bound to claim %q", volume.Name, claimToClaimKey(claim)) - newClaim, err := ctrl.kubeClient.Core().PersistentVolumeClaims(claim.Namespace).Update(claimClone) + newClaim, err := ctrl.kubeClient.CoreV1().PersistentVolumeClaims(claim.Namespace).Update(claimClone) if err != nil { glog.V(4).Infof("updating PersistentVolumeClaim[%s]: binding to %q failed: %v", claimToClaimKey(claim), volume.Name, err) return newClaim, err @@ -916,7 +978,7 @@ func (ctrl *PersistentVolumeController) unbindVolume(volume *v1.PersistentVolume volumeClone.Spec.ClaimRef.UID = "" } - newVol, err := ctrl.kubeClient.Core().PersistentVolumes().Update(volumeClone) + newVol, err := ctrl.kubeClient.CoreV1().PersistentVolumes().Update(volumeClone) if err != nil { glog.V(4).Infof("updating PersistentVolume[%s]: rollback failed: %v", volume.Name, err) return err @@ -977,7 +1039,7 @@ func (ctrl *PersistentVolumeController) recycleVolumeOperation(arg interface{}) // This method may have been waiting for a volume lock for some time. // Previous recycleVolumeOperation might just have saved an updated version, // so read current volume state now. - newVolume, err := ctrl.kubeClient.Core().PersistentVolumes().Get(volume.Name, metav1.GetOptions{}) + newVolume, err := ctrl.kubeClient.CoreV1().PersistentVolumes().Get(volume.Name, metav1.GetOptions{}) if err != nil { glog.V(3).Infof("error reading peristent volume %q: %v", volume.Name, err) return @@ -1056,7 +1118,7 @@ func (ctrl *PersistentVolumeController) deleteVolumeOperation(arg interface{}) e // 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. - newVolume, err := ctrl.kubeClient.Core().PersistentVolumes().Get(volume.Name, metav1.GetOptions{}) + newVolume, err := ctrl.kubeClient.CoreV1().PersistentVolumes().Get(volume.Name, metav1.GetOptions{}) if err != nil { glog.V(3).Infof("error reading peristent volume %q: %v", volume.Name, err) return nil @@ -1100,7 +1162,7 @@ func (ctrl *PersistentVolumeController) deleteVolumeOperation(arg interface{}) e glog.V(4).Infof("deleteVolumeOperation [%s]: success", volume.Name) // Delete the volume - if err = ctrl.kubeClient.Core().PersistentVolumes().Delete(volume.Name, nil); err != nil { + if err = ctrl.kubeClient.CoreV1().PersistentVolumes().Delete(volume.Name, nil); err != nil { // Oops, could not delete the volume and therefore the controller will // try to delete the volume again on next update. We _could_ maintain a // cache of "recently deleted volumes" and avoid unnecessary deletion, @@ -1188,7 +1250,7 @@ func (ctrl *PersistentVolumeController) doDeleteVolume(volume *v1.PersistentVolu opComplete := util.OperationCompleteHook(plugin.GetPluginName(), "volume_delete") err = deleter.Delete() - opComplete(err) + opComplete(&err) if err != nil { // Deleter failed return false, err @@ -1260,7 +1322,7 @@ func (ctrl *PersistentVolumeController) provisionClaimOperation(claimObj interfa // yet. pvName := ctrl.getProvisionedVolumeNameForClaim(claim) - volume, err := ctrl.kubeClient.Core().PersistentVolumes().Get(pvName, metav1.GetOptions{}) + volume, err := ctrl.kubeClient.CoreV1().PersistentVolumes().Get(pvName, metav1.GetOptions{}) if err == nil && volume != nil { // Volume has been already provisioned, nothing to do. glog.V(4).Infof("provisionClaimOperation [%s]: volume already exists, skipping", claimToClaimKey(claim)) @@ -1311,7 +1373,7 @@ func (ctrl *PersistentVolumeController) provisionClaimOperation(claimObj interfa opComplete := util.OperationCompleteHook(plugin.GetPluginName(), "volume_provision") volume, err = provisioner.Provision() - opComplete(err) + opComplete(&err) if err != nil { strerr := fmt.Sprintf("Failed to provision volume with StorageClass %q: %v", storageClass.Name, err) glog.V(2).Infof("failed to provision volume for claim %q with StorageClass %q: %v", claimToClaimKey(claim), storageClass.Name, err) @@ -1338,7 +1400,7 @@ func (ctrl *PersistentVolumeController) provisionClaimOperation(claimObj interfa for i := 0; i < ctrl.createProvisionedPVRetryCount; i++ { glog.V(4).Infof("provisionClaimOperation [%s]: trying to save volume %s", claimToClaimKey(claim), volume.Name) var newVol *v1.PersistentVolume - if newVol, err = ctrl.kubeClient.Core().PersistentVolumes().Create(volume); err == nil || apierrs.IsAlreadyExists(err) { + if newVol, err = ctrl.kubeClient.CoreV1().PersistentVolumes().Create(volume); err == nil || apierrs.IsAlreadyExists(err) { // Save succeeded. if err != nil { glog.V(3).Infof("volume %q for claim %q already exists, reusing", volume.Name, claimToClaimKey(claim)) diff --git a/pkg/controller/volume/persistentvolume/pv_controller_base.go b/pkg/controller/volume/persistentvolume/pv_controller_base.go index 2fb5852f7f4..2ba5d831cbd 100644 --- a/pkg/controller/volume/persistentvolume/pv_controller_base.go +++ b/pkg/controller/volume/persistentvolume/pv_controller_base.go @@ -70,7 +70,8 @@ func NewController(p ControllerParameters) (*PersistentVolumeController, error) eventRecorder := p.EventRecorder if eventRecorder == nil { broadcaster := record.NewBroadcaster() - broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(p.KubeClient.Core().RESTClient()).Events("")}) + broadcaster.StartLogging(glog.Infof) + broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(p.KubeClient.CoreV1().RESTClient()).Events("")}) eventRecorder = broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "persistentvolume-controller"}) } @@ -242,11 +243,15 @@ func (ctrl *PersistentVolumeController) deleteClaim(claim *v1.PersistentVolumeCl _ = ctrl.claims.Delete(claim) glog.V(4).Infof("claim %q deleted", claimToClaimKey(claim)) + volumeName := claim.Spec.VolumeName + if volumeName == "" { + glog.V(5).Infof("deleteClaim[%q]: volume not bound", claimToClaimKey(claim)) + return + } // sync the volume when its claim is deleted. Explicitly sync'ing the // volume here in response to claim deletion prevents the volume from // waiting until the next sync period for its Release. - volumeName := claim.Spec.VolumeName - glog.V(5).Infof("deleteClaim[%s]: scheduling sync of volume %q", claimToClaimKey(claim), volumeName) + glog.V(5).Infof("deleteClaim[%q]: scheduling sync of volume %s", claimToClaimKey(claim), volumeName) ctrl.volumeQueue.Add(volumeName) } @@ -424,7 +429,7 @@ func (ctrl *PersistentVolumeController) setClaimProvisioner(claim *v1.Persistent // modify these, therefore create a copy. claimClone := claim.DeepCopy() metav1.SetMetaDataAnnotation(&claimClone.ObjectMeta, annStorageProvisioner, class.Provisioner) - newClaim, err := ctrl.kubeClient.Core().PersistentVolumeClaims(claim.Namespace).Update(claimClone) + newClaim, err := ctrl.kubeClient.CoreV1().PersistentVolumeClaims(claim.Namespace).Update(claimClone) if err != nil { return newClaim, err } diff --git a/pkg/controller/volume/persistentvolume/pv_controller_test.go b/pkg/controller/volume/persistentvolume/pv_controller_test.go index 41d5ce4b831..5454fe26ece 100644 --- a/pkg/controller/volume/persistentvolume/pv_controller_test.go +++ b/pkg/controller/volume/persistentvolume/pv_controller_test.go @@ -21,8 +21,12 @@ import ( "time" "github.com/golang/glog" + "k8s.io/api/core/v1" + storagev1 "k8s.io/api/storage/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/watch" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" core "k8s.io/client-go/testing" @@ -232,3 +236,106 @@ func addVolumeAnnotation(volume *v1.PersistentVolume, annName, annValue string) volume.Annotations[annName] = annValue return volume } + +func makePVCClass(scName *string) *v1.PersistentVolumeClaim { + return &v1.PersistentVolumeClaim{ + Spec: v1.PersistentVolumeClaimSpec{ + StorageClassName: scName, + }, + } +} + +func makeStorageClass(scName string, mode *storagev1.VolumeBindingMode) *storagev1.StorageClass { + return &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: scName, + }, + VolumeBindingMode: mode, + } +} + +func TestDelayBinding(t *testing.T) { + var ( + classNotHere = "not-here" + classNoMode = "no-mode" + classImmediateMode = "immediate-mode" + classWaitMode = "wait-mode" + + modeImmediate = storagev1.VolumeBindingImmediate + modeWait = storagev1.VolumeBindingWaitForFirstConsumer + ) + + tests := map[string]struct { + pvc *v1.PersistentVolumeClaim + shouldDelay bool + shouldFail bool + }{ + "nil-class": { + pvc: makePVCClass(nil), + shouldDelay: false, + }, + "class-not-found": { + pvc: makePVCClass(&classNotHere), + shouldDelay: false, + }, + "no-mode-class": { + pvc: makePVCClass(&classNoMode), + shouldDelay: false, + shouldFail: true, + }, + "immediate-mode-class": { + pvc: makePVCClass(&classImmediateMode), + shouldDelay: false, + }, + "wait-mode-class": { + pvc: makePVCClass(&classWaitMode), + shouldDelay: true, + }, + } + + classes := []*storagev1.StorageClass{ + makeStorageClass(classNoMode, nil), + makeStorageClass(classImmediateMode, &modeImmediate), + makeStorageClass(classWaitMode, &modeWait), + } + + client := &fake.Clientset{} + informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) + classInformer := informerFactory.Storage().V1().StorageClasses() + ctrl := &PersistentVolumeController{ + classLister: classInformer.Lister(), + } + + for _, class := range classes { + if err := classInformer.Informer().GetIndexer().Add(class); err != nil { + t.Fatalf("Failed to add storage class %q: %v", class.Name, err) + } + } + + // When feature gate is disabled, should always be delayed + name := "feature-disabled" + shouldDelay, err := ctrl.shouldDelayBinding(makePVCClass(&classWaitMode)) + if err != nil { + t.Errorf("Test %q returned error: %v", name, err) + } + if shouldDelay { + t.Errorf("Test %q returned true, expected false", name) + } + + // Enable feature gate + utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true") + defer utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false") + + for name, test := range tests { + shouldDelay, err = ctrl.shouldDelayBinding(test.pvc) + if err != nil && !test.shouldFail { + t.Errorf("Test %q returned error: %v", name, err) + } + if err == nil && test.shouldFail { + t.Errorf("Test %q returned success, expected error", name) + } + if shouldDelay != test.shouldDelay { + t.Errorf("Test %q returned unexpected %v", name, test.shouldDelay) + } + } +} diff --git a/pkg/controller/volume/persistentvolume/scheduler_assume_cache.go b/pkg/controller/volume/persistentvolume/scheduler_assume_cache.go new file mode 100644 index 00000000000..28884004d7c --- /dev/null +++ b/pkg/controller/volume/persistentvolume/scheduler_assume_cache.go @@ -0,0 +1,318 @@ +/* +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 persistentvolume + +import ( + "fmt" + "strconv" + "sync" + + "github.com/golang/glog" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/client-go/tools/cache" +) + +// AssumeCache is a cache on top of the informer that allows for updating +// objects outside of informer events and also restoring the informer +// cache's version of the object. Objects are assumed to be +// Kubernetes API objects that implement meta.Interface +type AssumeCache interface { + // Assume updates the object in-memory only + Assume(obj interface{}) error + + // Restore the informer cache's version of the object + Restore(objName string) + + // Get the object by name + Get(objName string) (interface{}, error) + + // List all the objects in the cache + List() []interface{} +} + +type errWrongType struct { + typeName string + object interface{} +} + +func (e *errWrongType) Error() string { + return fmt.Sprintf("could not convert object to type %v: %+v", e.typeName, e.object) +} + +type errNotFound struct { + typeName string + objectName string +} + +func (e *errNotFound) Error() string { + return fmt.Sprintf("could not find %v %q", e.typeName, e.objectName) +} + +type errObjectName struct { + detailedErr error +} + +func (e *errObjectName) Error() string { + return fmt.Sprintf("failed to get object name: %v", e.detailedErr) +} + +// assumeCache stores two pointers to represent a single object: +// * The pointer to the informer object. +// * The pointer to the latest object, which could be the same as +// the informer object, or an in-memory object. +// +// An informer update always overrides the latest object pointer. +// +// Assume() only updates the latest object pointer. +// Restore() sets the latest object pointer back to the informer object. +// Get/List() always returns the latest object pointer. +type assumeCache struct { + mutex sync.Mutex + + // describes the object stored + description string + + // Stores objInfo pointers + store cache.Store +} + +type objInfo struct { + // name of the object + name string + + // Latest version of object could be cached-only or from informer + latestObj interface{} + + // Latest object from informer + apiObj interface{} +} + +func objInfoKeyFunc(obj interface{}) (string, error) { + objInfo, ok := obj.(*objInfo) + if !ok { + return "", &errWrongType{"objInfo", obj} + } + return objInfo.name, nil +} + +func NewAssumeCache(informer cache.SharedIndexInformer, description string) *assumeCache { + // TODO: index by storageclass + c := &assumeCache{store: cache.NewStore(objInfoKeyFunc), description: description} + + // Unit tests don't use informers + if informer != nil { + informer.AddEventHandler( + cache.ResourceEventHandlerFuncs{ + AddFunc: c.add, + UpdateFunc: c.update, + DeleteFunc: c.delete, + }, + ) + } + return c +} + +func (c *assumeCache) add(obj interface{}) { + if obj == nil { + return + } + + name, err := cache.MetaNamespaceKeyFunc(obj) + if err != nil { + glog.Errorf("add failed: %v", &errObjectName{err}) + return + } + + c.mutex.Lock() + defer c.mutex.Unlock() + + objInfo := &objInfo{name: name, latestObj: obj, apiObj: obj} + c.store.Update(objInfo) +} + +func (c *assumeCache) update(oldObj interface{}, newObj interface{}) { + c.add(newObj) +} + +func (c *assumeCache) delete(obj interface{}) { + if obj == nil { + return + } + + name, err := cache.MetaNamespaceKeyFunc(obj) + if err != nil { + glog.Errorf("delete failed: %v", &errObjectName{err}) + return + } + + c.mutex.Lock() + defer c.mutex.Unlock() + + objInfo := &objInfo{name: name} + err = c.store.Delete(objInfo) + if err != nil { + glog.Errorf("delete: failed to delete %v %v: %v", c.description, name, err) + } +} + +func (c *assumeCache) getObjVersion(name string, obj interface{}) (int64, error) { + objAccessor, err := meta.Accessor(obj) + if err != nil { + return -1, err + } + + objResourceVersion, err := strconv.ParseInt(objAccessor.GetResourceVersion(), 10, 64) + if err != nil { + return -1, fmt.Errorf("error parsing ResourceVersion %q for %v %q: %s", objAccessor.GetResourceVersion(), c.description, name, err) + } + return objResourceVersion, nil +} + +func (c *assumeCache) getObjInfo(name string) (*objInfo, error) { + obj, ok, err := c.store.GetByKey(name) + if err != nil { + return nil, err + } + if !ok { + return nil, &errNotFound{c.description, name} + } + + objInfo, ok := obj.(*objInfo) + if !ok { + return nil, &errWrongType{"objInfo", obj} + } + return objInfo, nil +} + +func (c *assumeCache) Get(objName string) (interface{}, error) { + c.mutex.Lock() + defer c.mutex.Unlock() + + objInfo, err := c.getObjInfo(objName) + if err != nil { + return nil, err + } + return objInfo.latestObj, nil +} + +func (c *assumeCache) List() []interface{} { + c.mutex.Lock() + defer c.mutex.Unlock() + + allObjs := []interface{}{} + for _, obj := range c.store.List() { + objInfo, ok := obj.(*objInfo) + if !ok { + glog.Errorf("list error: %v", &errWrongType{"objInfo", obj}) + continue + } + allObjs = append(allObjs, objInfo.latestObj) + } + return allObjs +} + +func (c *assumeCache) Assume(obj interface{}) error { + name, err := cache.MetaNamespaceKeyFunc(obj) + if err != nil { + return &errObjectName{err} + } + + c.mutex.Lock() + defer c.mutex.Unlock() + + objInfo, err := c.getObjInfo(name) + if err != nil { + return err + } + + newVersion, err := c.getObjVersion(name, obj) + if err != nil { + return err + } + + storedVersion, err := c.getObjVersion(name, objInfo.latestObj) + if err != nil { + return err + } + + if newVersion < storedVersion { + return fmt.Errorf("%v %q is out of sync", c.description, name) + } + + // Only update the cached object + objInfo.latestObj = obj + glog.V(4).Infof("Assumed %v %q, version %v", c.description, name, newVersion) + return nil +} + +func (c *assumeCache) Restore(objName string) { + c.mutex.Lock() + defer c.mutex.Unlock() + + objInfo, err := c.getObjInfo(objName) + if err != nil { + // This could be expected if object got deleted + glog.V(5).Infof("Restore %v %q warning: %v", c.description, objName, err) + } else { + objInfo.latestObj = objInfo.apiObj + glog.V(4).Infof("Restored %v %q", c.description, objName) + } +} + +// PVAssumeCache is a AssumeCache for PersistentVolume objects +type PVAssumeCache interface { + AssumeCache + + GetPV(pvName string) (*v1.PersistentVolume, error) + ListPVs() []*v1.PersistentVolume +} + +type pvAssumeCache struct { + *assumeCache +} + +func NewPVAssumeCache(informer cache.SharedIndexInformer) PVAssumeCache { + return &pvAssumeCache{assumeCache: NewAssumeCache(informer, "v1.PersistentVolume")} +} + +func (c *pvAssumeCache) GetPV(pvName string) (*v1.PersistentVolume, error) { + obj, err := c.Get(pvName) + if err != nil { + return nil, err + } + + pv, ok := obj.(*v1.PersistentVolume) + if !ok { + return nil, &errWrongType{"v1.PersistentVolume", obj} + } + return pv, nil +} + +func (c *pvAssumeCache) ListPVs() []*v1.PersistentVolume { + objs := c.List() + pvs := []*v1.PersistentVolume{} + for _, obj := range objs { + pv, ok := obj.(*v1.PersistentVolume) + if !ok { + glog.Errorf("ListPVs: %v", &errWrongType{"v1.PersistentVolume", obj}) + } + pvs = append(pvs, pv) + } + return pvs +} diff --git a/pkg/controller/volume/persistentvolume/scheduler_assume_cache_test.go b/pkg/controller/volume/persistentvolume/scheduler_assume_cache_test.go new file mode 100644 index 00000000000..7332c4d474a --- /dev/null +++ b/pkg/controller/volume/persistentvolume/scheduler_assume_cache_test.go @@ -0,0 +1,212 @@ +/* +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 persistentvolume + +import ( + "fmt" + "testing" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func makePV(name, version string) *v1.PersistentVolume { + return &v1.PersistentVolume{ObjectMeta: metav1.ObjectMeta{Name: name, ResourceVersion: version}} +} + +func TestAssumePV(t *testing.T) { + scenarios := map[string]struct { + oldPV *v1.PersistentVolume + newPV *v1.PersistentVolume + shouldSucceed bool + }{ + "success-same-version": { + oldPV: makePV("pv1", "5"), + newPV: makePV("pv1", "5"), + shouldSucceed: true, + }, + "success-new-higher-version": { + oldPV: makePV("pv1", "5"), + newPV: makePV("pv1", "6"), + shouldSucceed: true, + }, + "fail-old-not-found": { + oldPV: makePV("pv2", "5"), + newPV: makePV("pv1", "5"), + shouldSucceed: false, + }, + "fail-new-lower-version": { + oldPV: makePV("pv1", "5"), + newPV: makePV("pv1", "4"), + shouldSucceed: false, + }, + "fail-new-bad-version": { + oldPV: makePV("pv1", "5"), + newPV: makePV("pv1", "a"), + shouldSucceed: false, + }, + "fail-old-bad-version": { + oldPV: makePV("pv1", "a"), + newPV: makePV("pv1", "5"), + shouldSucceed: false, + }, + } + + for name, scenario := range scenarios { + cache := NewPVAssumeCache(nil) + internal_cache, ok := cache.(*pvAssumeCache) + if !ok { + t.Fatalf("Failed to get internal cache") + } + + // Add oldPV to cache + internal_cache.add(scenario.oldPV) + if err := getPV(cache, scenario.oldPV.Name, scenario.oldPV); err != nil { + t.Errorf("Failed to GetPV() after initial update: %v", err) + continue + } + + // Assume newPV + err := cache.Assume(scenario.newPV) + if scenario.shouldSucceed && err != nil { + t.Errorf("Test %q failed: Assume() returned error %v", name, err) + } + if !scenario.shouldSucceed && err == nil { + t.Errorf("Test %q failed: Assume() returned success but expected error", name) + } + + // Check that GetPV returns correct PV + expectedPV := scenario.newPV + if !scenario.shouldSucceed { + expectedPV = scenario.oldPV + } + if err := getPV(cache, scenario.oldPV.Name, expectedPV); err != nil { + t.Errorf("Failed to GetPV() after initial update: %v", err) + } + } +} + +func TestRestorePV(t *testing.T) { + cache := NewPVAssumeCache(nil) + internal_cache, ok := cache.(*pvAssumeCache) + if !ok { + t.Fatalf("Failed to get internal cache") + } + + oldPV := makePV("pv1", "5") + newPV := makePV("pv1", "5") + + // Restore PV that doesn't exist + cache.Restore("nothing") + + // Add oldPV to cache + internal_cache.add(oldPV) + if err := getPV(cache, oldPV.Name, oldPV); err != nil { + t.Fatalf("Failed to GetPV() after initial update: %v", err) + } + + // Restore PV + cache.Restore(oldPV.Name) + if err := getPV(cache, oldPV.Name, oldPV); err != nil { + t.Fatalf("Failed to GetPV() after iniital restore: %v", err) + } + + // Assume newPV + if err := cache.Assume(newPV); err != nil { + t.Fatalf("Assume() returned error %v", err) + } + if err := getPV(cache, oldPV.Name, newPV); err != nil { + t.Fatalf("Failed to GetPV() after Assume: %v", err) + } + + // Restore PV + cache.Restore(oldPV.Name) + if err := getPV(cache, oldPV.Name, oldPV); err != nil { + t.Fatalf("Failed to GetPV() after restore: %v", err) + } +} + +func TestBasicPVCache(t *testing.T) { + cache := NewPVAssumeCache(nil) + internal_cache, ok := cache.(*pvAssumeCache) + if !ok { + t.Fatalf("Failed to get internal cache") + } + + // Get object that doesn't exist + pv, err := cache.GetPV("nothere") + if err == nil { + t.Errorf("GetPV() returned unexpected success") + } + if pv != nil { + t.Errorf("GetPV() returned unexpected PV %q", pv.Name) + } + + // Add a bunch of PVs + pvs := map[string]*v1.PersistentVolume{} + for i := 0; i < 10; i++ { + pv := makePV(fmt.Sprintf("test-pv%v", i), "1") + pvs[pv.Name] = pv + internal_cache.add(pv) + } + + // List them + verifyListPVs(t, cache, pvs) + + // Update a PV + updatedPV := makePV("test-pv3", "2") + pvs[updatedPV.Name] = updatedPV + internal_cache.update(nil, updatedPV) + + // List them + verifyListPVs(t, cache, pvs) + + // Delete a PV + deletedPV := pvs["test-pv7"] + delete(pvs, deletedPV.Name) + internal_cache.delete(deletedPV) + + // List them + verifyListPVs(t, cache, pvs) +} + +func verifyListPVs(t *testing.T, cache PVAssumeCache, expectedPVs map[string]*v1.PersistentVolume) { + pvList := cache.ListPVs() + if len(pvList) != len(expectedPVs) { + t.Errorf("ListPVs() returned %v PVs, expected %v", len(pvList), len(expectedPVs)) + } + for _, pv := range pvList { + expectedPV, ok := expectedPVs[pv.Name] + if !ok { + t.Errorf("ListPVs() returned unexpected PV %q", pv.Name) + } + if expectedPV != pv { + t.Errorf("ListPVs() returned PV %p, expected %p", pv, expectedPV) + } + } +} + +func getPV(cache PVAssumeCache, name string, expectedPV *v1.PersistentVolume) error { + pv, err := cache.GetPV(name) + if err != nil { + return err + } + if pv != expectedPV { + return fmt.Errorf("GetPV() returned %p, expected %p", pv, expectedPV) + } + return nil +} diff --git a/pkg/controller/volume/persistentvolume/scheduler_binder.go b/pkg/controller/volume/persistentvolume/scheduler_binder.go new file mode 100644 index 00000000000..7edd1ef459d --- /dev/null +++ b/pkg/controller/volume/persistentvolume/scheduler_binder.go @@ -0,0 +1,420 @@ +/* +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 persistentvolume + +import ( + "fmt" + "sort" + + "github.com/golang/glog" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + coreinformers "k8s.io/client-go/informers/core/v1" + storageinformers "k8s.io/client-go/informers/storage/v1" + clientset "k8s.io/client-go/kubernetes" + corelisters "k8s.io/client-go/listers/core/v1" + volumeutil "k8s.io/kubernetes/pkg/volume/util" +) + +// SchedulerVolumeBinder is used by the scheduler to handle PVC/PV binding +// and dynamic provisioning. The binding decisions are integrated into the pod scheduling +// workflow so that the PV NodeAffinity is also considered along with the pod's other +// scheduling requirements. +// +// This integrates into the existing default scheduler workflow as follows: +// 1. The scheduler takes a Pod off the scheduler queue and processes it serially: +// a. Invokes all predicate functions, parallelized across nodes. FindPodVolumes() is invoked here. +// b. Invokes all priority functions. Future/TBD +// c. Selects the best node for the Pod. +// d. Cache the node selection for the Pod. (Assume phase) +// i. If PVC binding is required, cache in-memory only: +// * Updated PV objects for prebinding to the corresponding PVCs. +// * For the pod, which PVs need API updates. +// AssumePodVolumes() is invoked here. Then BindPodVolumes() is called asynchronously by the +// scheduler. After BindPodVolumes() is complete, the Pod is added back to the scheduler queue +// to be processed again until all PVCs are bound. +// ii. If PVC binding is not required, cache the Pod->Node binding in the scheduler's pod cache, +// and asynchronously bind the Pod to the Node. This is handled in the scheduler and not here. +// 2. Once the assume operation is done, the scheduler processes the next Pod in the scheduler queue +// while the actual binding operation occurs in the background. +type SchedulerVolumeBinder interface { + // FindPodVolumes checks if all of a Pod's PVCs can be satisfied by the node. + // + // If a PVC is bound, it checks if the PV's NodeAffinity matches the Node. + // Otherwise, it tries to find an available PV to bind to the PVC. + // + // It returns true if there are matching PVs that can satisfy all of the Pod's PVCs, and returns true + // if bound volumes satisfy the PV NodeAffinity. + // + // This function is called by the volume binding scheduler predicate and can be called in parallel + FindPodVolumes(pod *v1.Pod, nodeName string) (unboundVolumesSatisified, boundVolumesSatisfied bool, err error) + + // AssumePodVolumes will take the PV matches for unbound PVCs and update the PV cache assuming + // that the PV is prebound to the PVC. + // + // It returns true if all volumes are fully bound, and returns true if any volume binding API operation needs + // to be done afterwards. + // + // This function will modify assumedPod with the node name. + // This function is called serially. + AssumePodVolumes(assumedPod *v1.Pod, nodeName string) (allFullyBound bool, bindingRequired bool, err error) + + // BindPodVolumes will initiate the volume binding by making the API call to prebind the PV + // to its matching PVC. + // + // This function can be called in parallel. + BindPodVolumes(assumedPod *v1.Pod) error + + // GetBindingsCache returns the cache used (if any) to store volume binding decisions. + GetBindingsCache() PodBindingCache +} + +type volumeBinder struct { + ctrl *PersistentVolumeController + + // TODO: Need AssumeCache for PVC for dynamic provisioning + pvcCache corelisters.PersistentVolumeClaimLister + nodeCache corelisters.NodeLister + pvCache PVAssumeCache + + // Stores binding decisions that were made in FindPodVolumes for use in AssumePodVolumes. + // AssumePodVolumes modifies the bindings again for use in BindPodVolumes. + podBindingCache PodBindingCache +} + +// NewVolumeBinder sets up all the caches needed for the scheduler to make volume binding decisions. +func NewVolumeBinder( + kubeClient clientset.Interface, + pvcInformer coreinformers.PersistentVolumeClaimInformer, + pvInformer coreinformers.PersistentVolumeInformer, + nodeInformer coreinformers.NodeInformer, + storageClassInformer storageinformers.StorageClassInformer) SchedulerVolumeBinder { + + // TODO: find better way... + ctrl := &PersistentVolumeController{ + kubeClient: kubeClient, + classLister: storageClassInformer.Lister(), + } + + b := &volumeBinder{ + ctrl: ctrl, + pvcCache: pvcInformer.Lister(), + nodeCache: nodeInformer.Lister(), + pvCache: NewPVAssumeCache(pvInformer.Informer()), + podBindingCache: NewPodBindingCache(), + } + + return b +} + +func (b *volumeBinder) GetBindingsCache() PodBindingCache { + return b.podBindingCache +} + +// FindPodVolumes caches the matching PVs per node in podBindingCache +func (b *volumeBinder) FindPodVolumes(pod *v1.Pod, nodeName string) (unboundVolumesSatisfied, boundVolumesSatisfied bool, err error) { + podName := getPodName(pod) + + glog.V(4).Infof("FindPodVolumes for pod %q, node %q", podName, nodeName) + + // Initialize to true for pods that don't have volumes + unboundVolumesSatisfied = true + boundVolumesSatisfied = true + + node, err := b.nodeCache.Get(nodeName) + if node == nil || err != nil { + return false, false, fmt.Errorf("error getting node %q: %v", nodeName, err) + } + + // The pod's volumes need to be processed in one call to avoid the race condition where + // volumes can get bound in between calls. + boundClaims, unboundClaims, unboundClaimsImmediate, err := b.getPodVolumes(pod) + if err != nil { + return false, false, err + } + + // Immediate claims should be bound + if len(unboundClaimsImmediate) > 0 { + return false, false, fmt.Errorf("pod has unbound PersistentVolumeClaims") + } + + // Check PV node affinity on bound volumes + if len(boundClaims) > 0 { + boundVolumesSatisfied, err = b.checkBoundClaims(boundClaims, node, podName) + if err != nil { + return false, false, err + } + } + + // Find PVs for unbound volumes + if len(unboundClaims) > 0 { + unboundVolumesSatisfied, err = b.findMatchingVolumes(pod, unboundClaims, node) + if err != nil { + return false, false, err + } + } + + return unboundVolumesSatisfied, boundVolumesSatisfied, nil +} + +// AssumePodVolumes will take the cached matching PVs in podBindingCache for the chosen node +// and update the pvCache with the new prebound PV. It will update podBindingCache again +// with the PVs that need an API update. +func (b *volumeBinder) AssumePodVolumes(assumedPod *v1.Pod, nodeName string) (allFullyBound, bindingRequired bool, err error) { + podName := getPodName(assumedPod) + + glog.V(4).Infof("AssumePodVolumes for pod %q, node %q", podName, nodeName) + + if allBound := b.arePodVolumesBound(assumedPod); allBound { + glog.V(4).Infof("AssumePodVolumes: all PVCs bound and nothing to do") + return true, false, nil + } + + assumedPod.Spec.NodeName = nodeName + claimsToBind := b.podBindingCache.GetBindings(assumedPod, nodeName) + newBindings := []*bindingInfo{} + + for _, binding := range claimsToBind { + newPV, dirty, err := b.ctrl.getBindVolumeToClaim(binding.pv, binding.pvc) + glog.V(5).Infof("AssumePodVolumes: getBindVolumeToClaim for PV %q, PVC %q. newPV %p, dirty %v, err: %v", + binding.pv.Name, + binding.pvc.Name, + newPV, + dirty, + err) + if err != nil { + b.revertAssumedPVs(newBindings) + return false, true, err + } + if dirty { + err = b.pvCache.Assume(newPV) + if err != nil { + b.revertAssumedPVs(newBindings) + return false, true, err + } + + newBindings = append(newBindings, &bindingInfo{pv: newPV, pvc: binding.pvc}) + } + } + + if len(newBindings) == 0 { + // Don't update cached bindings if no API updates are needed. This can happen if we + // previously updated the PV object and are waiting for the PV controller to finish binding. + glog.V(4).Infof("AssumePodVolumes: PVs already assumed") + return false, false, nil + } + b.podBindingCache.UpdateBindings(assumedPod, nodeName, newBindings) + + return false, true, nil +} + +// BindPodVolumes gets the cached bindings in podBindingCache and makes the API update for those PVs. +func (b *volumeBinder) BindPodVolumes(assumedPod *v1.Pod) error { + glog.V(4).Infof("BindPodVolumes for pod %q", getPodName(assumedPod)) + + bindings := b.podBindingCache.GetBindings(assumedPod, assumedPod.Spec.NodeName) + + // Do the actual prebinding. Let the PV controller take care of the rest + // There is no API rollback if the actual binding fails + for i, bindingInfo := range bindings { + _, err := b.ctrl.updateBindVolumeToClaim(bindingInfo.pv, bindingInfo.pvc, false) + if err != nil { + // only revert assumed cached updates for volumes we haven't successfully bound + b.revertAssumedPVs(bindings[i:]) + return err + } + } + + return nil +} + +func getPodName(pod *v1.Pod) string { + return pod.Namespace + "/" + pod.Name +} + +func getPVCName(pvc *v1.PersistentVolumeClaim) string { + return pvc.Namespace + "/" + pvc.Name +} + +func (b *volumeBinder) isVolumeBound(namespace string, vol *v1.Volume, checkFullyBound bool) (bool, *v1.PersistentVolumeClaim, error) { + if vol.PersistentVolumeClaim == nil { + return true, nil, nil + } + + pvcName := vol.PersistentVolumeClaim.ClaimName + pvc, err := b.pvcCache.PersistentVolumeClaims(namespace).Get(pvcName) + if err != nil || pvc == nil { + return false, nil, fmt.Errorf("error getting PVC %q: %v", pvcName, err) + } + + pvName := pvc.Spec.VolumeName + if pvName != "" { + if checkFullyBound { + if metav1.HasAnnotation(pvc.ObjectMeta, annBindCompleted) { + glog.V(5).Infof("PVC %q is fully bound to PV %q", getPVCName(pvc), pvName) + return true, pvc, nil + } else { + glog.V(5).Infof("PVC %q is not fully bound to PV %q", getPVCName(pvc), pvName) + return false, pvc, nil + } + } + glog.V(5).Infof("PVC %q is bound or prebound to PV %q", getPVCName(pvc), pvName) + return true, pvc, nil + } + + glog.V(5).Infof("PVC %q is not bound", getPVCName(pvc)) + return false, pvc, nil +} + +// arePodVolumesBound returns true if all volumes are fully bound +func (b *volumeBinder) arePodVolumesBound(pod *v1.Pod) bool { + for _, vol := range pod.Spec.Volumes { + if isBound, _, _ := b.isVolumeBound(pod.Namespace, &vol, true); !isBound { + // Pod has at least one PVC that needs binding + return false + } + } + return true +} + +// getPodVolumes returns a pod's PVCs separated into bound (including prebound), unbound with delayed binding, +// and unbound with immediate binding +func (b *volumeBinder) getPodVolumes(pod *v1.Pod) (boundClaims []*v1.PersistentVolumeClaim, unboundClaims []*bindingInfo, unboundClaimsImmediate []*v1.PersistentVolumeClaim, err error) { + boundClaims = []*v1.PersistentVolumeClaim{} + unboundClaimsImmediate = []*v1.PersistentVolumeClaim{} + unboundClaims = []*bindingInfo{} + + for _, vol := range pod.Spec.Volumes { + volumeBound, pvc, err := b.isVolumeBound(pod.Namespace, &vol, false) + if err != nil { + return nil, nil, nil, err + } + if pvc == nil { + continue + } + if volumeBound { + boundClaims = append(boundClaims, pvc) + } else { + delayBinding, err := b.ctrl.shouldDelayBinding(pvc) + if err != nil { + return nil, nil, nil, err + } + if delayBinding { + // Scheduler path + unboundClaims = append(unboundClaims, &bindingInfo{pvc: pvc}) + } else { + // Immediate binding should have already been bound + unboundClaimsImmediate = append(unboundClaimsImmediate, pvc) + } + } + } + return boundClaims, unboundClaims, unboundClaimsImmediate, nil +} + +func (b *volumeBinder) checkBoundClaims(claims []*v1.PersistentVolumeClaim, node *v1.Node, podName string) (bool, error) { + for _, pvc := range claims { + pvName := pvc.Spec.VolumeName + pv, err := b.pvCache.GetPV(pvName) + if err != nil { + return false, err + } + + err = volumeutil.CheckNodeAffinity(pv, node.Labels) + if err != nil { + glog.V(4).Infof("PersistentVolume %q, Node %q mismatch for Pod %q: %v", pvName, node.Name, err.Error(), podName) + return false, nil + } + glog.V(5).Infof("PersistentVolume %q, Node %q matches for Pod %q", pvName, node.Name, podName) + } + + glog.V(4).Infof("All volumes for Pod %q match with Node %q", podName, node.Name) + return true, nil +} + +func (b *volumeBinder) findMatchingVolumes(pod *v1.Pod, claimsToBind []*bindingInfo, node *v1.Node) (foundMatches bool, err error) { + // Sort all the claims by increasing size request to get the smallest fits + sort.Sort(byPVCSize(claimsToBind)) + + allPVs := b.pvCache.ListPVs() + chosenPVs := map[string]*v1.PersistentVolume{} + + for _, bindingInfo := range claimsToBind { + // Find a matching PV + bindingInfo.pv, err = findMatchingVolume(bindingInfo.pvc, allPVs, node, chosenPVs, true) + if err != nil { + return false, err + } + if bindingInfo.pv == nil { + glog.V(4).Infof("No matching volumes for PVC %q on node %q", getPVCName(bindingInfo.pvc), node.Name) + return false, nil + } + + // matching PV needs to be excluded so we don't select it again + chosenPVs[bindingInfo.pv.Name] = bindingInfo.pv + } + + // Mark cache with all the matches for each PVC for this node + b.podBindingCache.UpdateBindings(pod, node.Name, claimsToBind) + glog.V(4).Infof("Found matching volumes on node %q", node.Name) + + return true, nil +} + +func (b *volumeBinder) revertAssumedPVs(bindings []*bindingInfo) { + for _, bindingInfo := range bindings { + b.pvCache.Restore(bindingInfo.pv.Name) + } +} + +type bindingInfo struct { + // Claim that needs to be bound + pvc *v1.PersistentVolumeClaim + + // Proposed PV to bind to this claim + pv *v1.PersistentVolume +} + +// Used in unit test errors +func (b bindingInfo) String() string { + pvcName := "" + pvName := "" + if b.pvc != nil { + pvcName = getPVCName(b.pvc) + } + if b.pv != nil { + pvName = b.pv.Name + } + return fmt.Sprintf("[PVC %q, PV %q]", pvcName, pvName) +} + +type byPVCSize []*bindingInfo + +func (a byPVCSize) Len() int { + return len(a) +} + +func (a byPVCSize) Swap(i, j int) { + a[i], a[j] = a[j], a[i] +} + +func (a byPVCSize) Less(i, j int) bool { + iSize := a[i].pvc.Spec.Resources.Requests[v1.ResourceStorage] + jSize := a[j].pvc.Spec.Resources.Requests[v1.ResourceStorage] + // return true if iSize is less than jSize + return iSize.Cmp(jSize) == -1 +} diff --git a/pkg/controller/volume/persistentvolume/scheduler_binder_cache.go b/pkg/controller/volume/persistentvolume/scheduler_binder_cache.go new file mode 100644 index 00000000000..8a0a7796085 --- /dev/null +++ b/pkg/controller/volume/persistentvolume/scheduler_binder_cache.go @@ -0,0 +1,87 @@ +/* +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 persistentvolume + +import ( + "sync" + + "k8s.io/api/core/v1" +) + +// podBindingCache stores PV binding decisions per pod per node. +// Pod entries are removed when the Pod is deleted or updated to +// no longer be schedulable. +type PodBindingCache interface { + // UpdateBindings will update the cache with the given bindings for the + // pod and node. + UpdateBindings(pod *v1.Pod, node string, bindings []*bindingInfo) + + // DeleteBindings will remove all cached bindings for the given pod. + DeleteBindings(pod *v1.Pod) + + // GetBindings will return the cached bindings for the given pod and node. + GetBindings(pod *v1.Pod, node string) []*bindingInfo +} + +type podBindingCache struct { + mutex sync.Mutex + + // Key = pod name + // Value = nodeBindings + bindings map[string]nodeBindings +} + +// Key = nodeName +// Value = array of bindingInfo +type nodeBindings map[string][]*bindingInfo + +func NewPodBindingCache() PodBindingCache { + return &podBindingCache{bindings: map[string]nodeBindings{}} +} + +func (c *podBindingCache) DeleteBindings(pod *v1.Pod) { + c.mutex.Lock() + defer c.mutex.Unlock() + + podName := getPodName(pod) + delete(c.bindings, podName) +} + +func (c *podBindingCache) UpdateBindings(pod *v1.Pod, node string, bindings []*bindingInfo) { + c.mutex.Lock() + defer c.mutex.Unlock() + + podName := getPodName(pod) + nodeBinding, ok := c.bindings[podName] + if !ok { + nodeBinding = nodeBindings{} + c.bindings[podName] = nodeBinding + } + nodeBinding[node] = bindings +} + +func (c *podBindingCache) GetBindings(pod *v1.Pod, node string) []*bindingInfo { + c.mutex.Lock() + defer c.mutex.Unlock() + + podName := getPodName(pod) + nodeBindings, ok := c.bindings[podName] + if !ok { + return nil + } + return nodeBindings[node] +} diff --git a/pkg/controller/volume/persistentvolume/scheduler_binder_cache_test.go b/pkg/controller/volume/persistentvolume/scheduler_binder_cache_test.go new file mode 100644 index 00000000000..c73cea970d0 --- /dev/null +++ b/pkg/controller/volume/persistentvolume/scheduler_binder_cache_test.go @@ -0,0 +1,112 @@ +/* +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 persistentvolume + +import ( + "reflect" + "testing" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestUpdateGetBindings(t *testing.T) { + scenarios := map[string]struct { + updateBindings []*bindingInfo + updatePod string + updateNode string + + getBindings []*bindingInfo + getPod string + getNode string + }{ + "no-pod": { + getPod: "pod1", + getNode: "node1", + }, + "no-node": { + updatePod: "pod1", + updateNode: "node1", + updateBindings: []*bindingInfo{}, + getPod: "pod1", + getNode: "node2", + }, + "binding-exists": { + updatePod: "pod1", + updateNode: "node1", + updateBindings: []*bindingInfo{{pvc: &v1.PersistentVolumeClaim{ObjectMeta: metav1.ObjectMeta{Name: "pvc1"}}}}, + getPod: "pod1", + getNode: "node1", + getBindings: []*bindingInfo{{pvc: &v1.PersistentVolumeClaim{ObjectMeta: metav1.ObjectMeta{Name: "pvc1"}}}}, + }, + } + + for name, scenario := range scenarios { + cache := NewPodBindingCache() + + // Perform updates + updatePod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: scenario.updatePod, Namespace: "ns"}} + cache.UpdateBindings(updatePod, scenario.updateNode, scenario.updateBindings) + + // Verify updated bindings + bindings := cache.GetBindings(updatePod, scenario.updateNode) + if !reflect.DeepEqual(bindings, scenario.updateBindings) { + t.Errorf("Test %v failed: returned bindings after update different. Got %+v, expected %+v", name, bindings, scenario.updateBindings) + } + + // Get bindings + getPod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: scenario.getPod, Namespace: "ns"}} + bindings = cache.GetBindings(getPod, scenario.getNode) + if !reflect.DeepEqual(bindings, scenario.getBindings) { + t.Errorf("Test %v failed: unexpected bindings returned. Got %+v, expected %+v", name, bindings, scenario.updateBindings) + } + } +} + +func TestDeleteBindings(t *testing.T) { + initialBindings := []*bindingInfo{{pvc: &v1.PersistentVolumeClaim{ObjectMeta: metav1.ObjectMeta{Name: "pvc1"}}}} + cache := NewPodBindingCache() + + pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod1", Namespace: "ns"}} + + // Get nil bindings + bindings := cache.GetBindings(pod, "node1") + if bindings != nil { + t.Errorf("Test failed: expected inital nil bindings, got %+v", bindings) + } + + // Delete nothing + cache.DeleteBindings(pod) + + // Perform updates + cache.UpdateBindings(pod, "node1", initialBindings) + + // Get bindings + bindings = cache.GetBindings(pod, "node1") + if !reflect.DeepEqual(bindings, initialBindings) { + t.Errorf("Test failed: expected bindings %+v, got %+v", initialBindings, bindings) + } + + // Delete + cache.DeleteBindings(pod) + + // Get bindings + bindings = cache.GetBindings(pod, "node1") + if bindings != nil { + t.Errorf("Test failed: expected nil bindings, got %+v", bindings) + } +} diff --git a/pkg/controller/volume/persistentvolume/scheduler_binder_fake.go b/pkg/controller/volume/persistentvolume/scheduler_binder_fake.go new file mode 100644 index 00000000000..2810276b161 --- /dev/null +++ b/pkg/controller/volume/persistentvolume/scheduler_binder_fake.go @@ -0,0 +1,63 @@ +/* +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 persistentvolume + +import ( + "k8s.io/api/core/v1" +) + +type FakeVolumeBinderConfig struct { + AllBound bool + FindUnboundSatsified bool + FindBoundSatsified bool + FindErr error + AssumeBindingRequired bool + AssumeErr error + BindErr error +} + +// NewVolumeBinder sets up all the caches needed for the scheduler to make +// topology-aware volume binding decisions. +func NewFakeVolumeBinder(config *FakeVolumeBinderConfig) *FakeVolumeBinder { + return &FakeVolumeBinder{ + config: config, + } +} + +type FakeVolumeBinder struct { + config *FakeVolumeBinderConfig + AssumeCalled bool + BindCalled bool +} + +func (b *FakeVolumeBinder) FindPodVolumes(pod *v1.Pod, nodeName string) (unboundVolumesSatisfied, boundVolumesSatsified bool, err error) { + return b.config.FindUnboundSatsified, b.config.FindBoundSatsified, b.config.FindErr +} + +func (b *FakeVolumeBinder) AssumePodVolumes(assumedPod *v1.Pod, nodeName string) (bool, bool, error) { + b.AssumeCalled = true + return b.config.AllBound, b.config.AssumeBindingRequired, b.config.AssumeErr +} + +func (b *FakeVolumeBinder) BindPodVolumes(assumedPod *v1.Pod) error { + b.BindCalled = true + return b.config.BindErr +} + +func (b *FakeVolumeBinder) GetBindingsCache() PodBindingCache { + return nil +} diff --git a/pkg/controller/volume/persistentvolume/scheduler_binder_test.go b/pkg/controller/volume/persistentvolume/scheduler_binder_test.go new file mode 100644 index 00000000000..c5f33f0409d --- /dev/null +++ b/pkg/controller/volume/persistentvolume/scheduler_binder_test.go @@ -0,0 +1,755 @@ +/* +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 persistentvolume + +import ( + "fmt" + "reflect" + "testing" + + "github.com/golang/glog" + + "k8s.io/api/core/v1" + storagev1 "k8s.io/api/storage/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/diff" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/client-go/informers" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/tools/cache" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/controller" +) + +var ( + unboundPVC = makeTestPVC("unbound-pvc", "1G", pvcUnbound, "", &waitClass) + unboundPVC2 = makeTestPVC("unbound-pvc2", "5G", pvcUnbound, "", &waitClass) + preboundPVC = makeTestPVC("prebound-pvc", "1G", pvcPrebound, "pv-node1a", &waitClass) + boundPVC = makeTestPVC("bound-pvc", "1G", pvcBound, "pv-bound", &waitClass) + boundPVC2 = makeTestPVC("bound-pvc2", "1G", pvcBound, "pv-bound2", &waitClass) + badPVC = makeBadPVC() + immediateUnboundPVC = makeTestPVC("immediate-unbound-pvc", "1G", pvcUnbound, "", &immediateClass) + immediateBoundPVC = makeTestPVC("immediate-bound-pvc", "1G", pvcBound, "pv-bound-immediate", &immediateClass) + + pvNoNode = makeTestPV("pv-no-node", "", "1G", "1", nil, waitClass) + pvNode1a = makeTestPV("pv-node1a", "node1", "5G", "1", nil, waitClass) + pvNode1b = makeTestPV("pv-node1b", "node1", "10G", "1", nil, waitClass) + pvNode2 = makeTestPV("pv-node2", "node2", "1G", "1", nil, waitClass) + pvPrebound = makeTestPV("pv-prebound", "node1", "1G", "1", unboundPVC, waitClass) + pvBound = makeTestPV("pv-bound", "node1", "1G", "1", boundPVC, waitClass) + pvNode1aBound = makeTestPV("pv-node1a", "node1", "1G", "1", unboundPVC, waitClass) + pvNode1bBound = makeTestPV("pv-node1b", "node1", "5G", "1", unboundPVC2, waitClass) + pvNode1bBoundHigherVersion = makeTestPV("pv-node1b", "node1", "5G", "2", unboundPVC2, waitClass) + pvBoundImmediate = makeTestPV("pv-bound-immediate", "node1", "1G", "1", immediateBoundPVC, immediateClass) + pvBoundImmediateNode2 = makeTestPV("pv-bound-immediate", "node2", "1G", "1", immediateBoundPVC, immediateClass) + + binding1a = makeBinding(unboundPVC, pvNode1a) + binding1b = makeBinding(unboundPVC2, pvNode1b) + bindingNoNode = makeBinding(unboundPVC, pvNoNode) + bindingBad = makeBinding(badPVC, pvNode1b) + binding1aBound = makeBinding(unboundPVC, pvNode1aBound) + binding1bBound = makeBinding(unboundPVC2, pvNode1bBound) + + waitClass = "waitClass" + immediateClass = "immediateClass" +) + +type testEnv struct { + client clientset.Interface + reactor *volumeReactor + binder SchedulerVolumeBinder + internalBinder *volumeBinder + internalPVCache *pvAssumeCache + internalPVCCache cache.Indexer +} + +func newTestBinder(t *testing.T) *testEnv { + client := &fake.Clientset{} + reactor := newVolumeReactor(client, nil, nil, nil, nil) + informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) + + pvcInformer := informerFactory.Core().V1().PersistentVolumeClaims() + nodeInformer := informerFactory.Core().V1().Nodes() + classInformer := informerFactory.Storage().V1().StorageClasses() + + binder := NewVolumeBinder( + client, + pvcInformer, + informerFactory.Core().V1().PersistentVolumes(), + nodeInformer, + classInformer) + + // Add a node + err := nodeInformer.Informer().GetIndexer().Add(&v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + Labels: map[string]string{"key1": "node1"}, + }, + }) + if err != nil { + t.Fatalf("Failed to add node to internal cache: %v", err) + } + + // Add storageclasses + waitMode := storagev1.VolumeBindingWaitForFirstConsumer + immediateMode := storagev1.VolumeBindingImmediate + classes := []*storagev1.StorageClass{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: waitClass, + }, + VolumeBindingMode: &waitMode, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: immediateClass, + }, + VolumeBindingMode: &immediateMode, + }, + } + for _, class := range classes { + if err = classInformer.Informer().GetIndexer().Add(class); err != nil { + t.Fatalf("Failed to add storage class to internal cache: %v", err) + } + } + + // Get internal types + internalBinder, ok := binder.(*volumeBinder) + if !ok { + t.Fatalf("Failed to convert to internal binder") + } + + pvCache := internalBinder.pvCache + internalPVCache, ok := pvCache.(*pvAssumeCache) + if !ok { + t.Fatalf("Failed to convert to internal PV cache") + } + + return &testEnv{ + client: client, + reactor: reactor, + binder: binder, + internalBinder: internalBinder, + internalPVCache: internalPVCache, + internalPVCCache: pvcInformer.Informer().GetIndexer(), + } +} + +func (env *testEnv) initClaims(t *testing.T, pvcs []*v1.PersistentVolumeClaim) { + for _, pvc := range pvcs { + err := env.internalPVCCache.Add(pvc) + if err != nil { + t.Fatalf("Failed to add PVC %q to internal cache: %v", pvc.Name, err) + } + env.reactor.claims[pvc.Name] = pvc + } +} + +func (env *testEnv) initVolumes(cachedPVs []*v1.PersistentVolume, apiPVs []*v1.PersistentVolume) { + internalPVCache := env.internalPVCache + for _, pv := range cachedPVs { + internalPVCache.add(pv) + if apiPVs == nil { + env.reactor.volumes[pv.Name] = pv + } + } + for _, pv := range apiPVs { + env.reactor.volumes[pv.Name] = pv + } + +} + +func (env *testEnv) assumeVolumes(t *testing.T, name, node string, pod *v1.Pod, bindings []*bindingInfo) { + pvCache := env.internalBinder.pvCache + for _, binding := range bindings { + if err := pvCache.Assume(binding.pv); err != nil { + t.Fatalf("Failed to setup test %q: error: %v", name, err) + } + } + + env.internalBinder.podBindingCache.UpdateBindings(pod, node, bindings) +} + +func (env *testEnv) initPodCache(pod *v1.Pod, node string, bindings []*bindingInfo) { + cache := env.internalBinder.podBindingCache + cache.UpdateBindings(pod, node, bindings) +} + +func (env *testEnv) validatePodCache(t *testing.T, name, node string, pod *v1.Pod, expectedBindings []*bindingInfo) { + cache := env.internalBinder.podBindingCache + bindings := cache.GetBindings(pod, node) + + if !reflect.DeepEqual(expectedBindings, bindings) { + t.Errorf("Test %q failed: Expected bindings %+v, got %+v", name, expectedBindings, bindings) + } +} + +func (env *testEnv) validateAssume(t *testing.T, name string, pod *v1.Pod, bindings []*bindingInfo) { + // TODO: Check binding cache + + // Check pv cache + pvCache := env.internalBinder.pvCache + for _, b := range bindings { + pv, err := pvCache.GetPV(b.pv.Name) + if err != nil { + t.Errorf("Test %q failed: GetPV %q returned error: %v", name, b.pv.Name, err) + continue + } + if pv.Spec.ClaimRef == nil { + t.Errorf("Test %q failed: PV %q ClaimRef is nil", name, b.pv.Name) + continue + } + if pv.Spec.ClaimRef.Name != b.pvc.Name { + t.Errorf("Test %q failed: expected PV.ClaimRef.Name %q, got %q", name, b.pvc.Name, pv.Spec.ClaimRef.Name) + } + if pv.Spec.ClaimRef.Namespace != b.pvc.Namespace { + t.Errorf("Test %q failed: expected PV.ClaimRef.Namespace %q, got %q", name, b.pvc.Namespace, pv.Spec.ClaimRef.Namespace) + } + } +} + +func (env *testEnv) validateFailedAssume(t *testing.T, name string, pod *v1.Pod, bindings []*bindingInfo) { + // All PVs have been unmodified in cache + pvCache := env.internalBinder.pvCache + for _, b := range bindings { + pv, _ := pvCache.GetPV(b.pv.Name) + // PV could be nil if it's missing from cache + if pv != nil && pv != b.pv { + t.Errorf("Test %q failed: PV %q was modified in cache", name, b.pv.Name) + } + } +} + +func (env *testEnv) validateBind( + t *testing.T, + name string, + pod *v1.Pod, + expectedPVs []*v1.PersistentVolume, + expectedAPIPVs []*v1.PersistentVolume) { + + // Check pv cache + pvCache := env.internalBinder.pvCache + for _, pv := range expectedPVs { + cachedPV, err := pvCache.GetPV(pv.Name) + if err != nil { + t.Errorf("Test %q failed: GetPV %q returned error: %v", name, pv.Name, err) + } + if !reflect.DeepEqual(cachedPV, pv) { + t.Errorf("Test %q failed: cached PV check failed [A-expected, B-got]:\n%s", name, diff.ObjectDiff(pv, cachedPV)) + } + } + + // Check reactor for API updates + if err := env.reactor.checkVolumes(expectedAPIPVs); err != nil { + t.Errorf("Test %q failed: API reactor validation failed: %v", name, err) + } +} + +const ( + pvcUnbound = iota + pvcPrebound + pvcBound +) + +func makeTestPVC(name, size string, pvcBoundState int, pvName string, className *string) *v1.PersistentVolumeClaim { + pvc := &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "testns", + UID: types.UID("pvc-uid"), + ResourceVersion: "1", + SelfLink: testapi.Default.SelfLink("pvc", name), + }, + Spec: v1.PersistentVolumeClaimSpec{ + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceName(v1.ResourceStorage): resource.MustParse(size), + }, + }, + StorageClassName: className, + }, + } + + switch pvcBoundState { + case pvcBound: + metav1.SetMetaDataAnnotation(&pvc.ObjectMeta, annBindCompleted, "yes") + fallthrough + case pvcPrebound: + pvc.Spec.VolumeName = pvName + } + return pvc +} + +func makeBadPVC() *v1.PersistentVolumeClaim { + return &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bad-pvc", + Namespace: "testns", + UID: types.UID("pvc-uid"), + ResourceVersion: "1", + // Don't include SefLink, so that GetReference will fail + }, + Spec: v1.PersistentVolumeClaimSpec{ + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"), + }, + }, + StorageClassName: &waitClass, + }, + } +} + +func makeTestPV(name, node, capacity, version string, boundToPVC *v1.PersistentVolumeClaim, className string) *v1.PersistentVolume { + pv := &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + ResourceVersion: version, + }, + Spec: v1.PersistentVolumeSpec{ + Capacity: v1.ResourceList{ + v1.ResourceName(v1.ResourceStorage): resource.MustParse(capacity), + }, + StorageClassName: className, + }, + } + if node != "" { + pv.Annotations = getAnnotationWithNodeAffinity("key1", node) + } + + if boundToPVC != nil { + pv.Spec.ClaimRef = &v1.ObjectReference{ + Name: boundToPVC.Name, + Namespace: boundToPVC.Namespace, + UID: boundToPVC.UID, + } + metav1.SetMetaDataAnnotation(&pv.ObjectMeta, annBoundByController, "yes") + } + + return pv +} + +func makePod(pvcs []*v1.PersistentVolumeClaim) *v1.Pod { + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pod", + Namespace: "testns", + }, + } + + volumes := []v1.Volume{} + for i, pvc := range pvcs { + pvcVol := v1.Volume{ + Name: fmt.Sprintf("vol%v", i), + VolumeSource: v1.VolumeSource{ + PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ + ClaimName: pvc.Name, + }, + }, + } + volumes = append(volumes, pvcVol) + } + pod.Spec.Volumes = volumes + pod.Spec.NodeName = "node1" + return pod +} + +func makePodWithoutPVC() *v1.Pod { + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pod", + Namespace: "testns", + }, + Spec: v1.PodSpec{ + Volumes: []v1.Volume{ + { + VolumeSource: v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }, + }, + }, + }, + } + return pod +} + +func makeBinding(pvc *v1.PersistentVolumeClaim, pv *v1.PersistentVolume) *bindingInfo { + return &bindingInfo{pvc: pvc, pv: pv} +} + +func makeStringPtr(str string) *string { + s := fmt.Sprintf("%v", str) + return &s +} + +func TestFindPodVolumes(t *testing.T) { + scenarios := map[string]struct { + // Inputs + pvs []*v1.PersistentVolume + podPVCs []*v1.PersistentVolumeClaim + // Defaults to node1 + node string + // If nil, use pod PVCs + cachePVCs []*v1.PersistentVolumeClaim + // If nil, makePod with podPVCs + pod *v1.Pod + + // Expected podBindingCache fields + expectedBindings []*bindingInfo + + // Expected return values + expectedUnbound bool + expectedBound bool + shouldFail bool + }{ + "no-volumes": { + pod: makePod(nil), + expectedUnbound: true, + expectedBound: true, + }, + "no-pvcs": { + pod: makePodWithoutPVC(), + expectedUnbound: true, + expectedBound: true, + }, + "pvc-not-found": { + cachePVCs: []*v1.PersistentVolumeClaim{}, + podPVCs: []*v1.PersistentVolumeClaim{boundPVC}, + expectedUnbound: false, + expectedBound: false, + shouldFail: true, + }, + "bound-pvc": { + podPVCs: []*v1.PersistentVolumeClaim{boundPVC}, + pvs: []*v1.PersistentVolume{pvBound}, + expectedUnbound: true, + expectedBound: true, + }, + "bound-pvc,pv-not-exists": { + podPVCs: []*v1.PersistentVolumeClaim{boundPVC}, + expectedUnbound: false, + expectedBound: false, + shouldFail: true, + }, + "prebound-pvc": { + podPVCs: []*v1.PersistentVolumeClaim{preboundPVC}, + pvs: []*v1.PersistentVolume{pvNode1aBound}, + expectedUnbound: true, + expectedBound: true, + }, + "unbound-pvc,node-not-exists": { + podPVCs: []*v1.PersistentVolumeClaim{unboundPVC}, + node: "node12", + expectedUnbound: false, + expectedBound: false, + shouldFail: true, + }, + "unbound-pvc,pv-same-node": { + podPVCs: []*v1.PersistentVolumeClaim{unboundPVC}, + pvs: []*v1.PersistentVolume{pvNode2, pvNode1a, pvNode1b}, + expectedBindings: []*bindingInfo{binding1a}, + expectedUnbound: true, + expectedBound: true, + }, + "unbound-pvc,pv-different-node": { + podPVCs: []*v1.PersistentVolumeClaim{unboundPVC}, + pvs: []*v1.PersistentVolume{pvNode2}, + expectedUnbound: false, + expectedBound: true, + }, + "two-unbound-pvcs": { + podPVCs: []*v1.PersistentVolumeClaim{unboundPVC, unboundPVC2}, + pvs: []*v1.PersistentVolume{pvNode1a, pvNode1b}, + expectedBindings: []*bindingInfo{binding1a, binding1b}, + expectedUnbound: true, + expectedBound: true, + }, + "two-unbound-pvcs,order-by-size": { + podPVCs: []*v1.PersistentVolumeClaim{unboundPVC2, unboundPVC}, + pvs: []*v1.PersistentVolume{pvNode1a, pvNode1b}, + expectedBindings: []*bindingInfo{binding1a, binding1b}, + expectedUnbound: true, + expectedBound: true, + }, + "two-unbound-pvcs,partial-match": { + podPVCs: []*v1.PersistentVolumeClaim{unboundPVC, unboundPVC2}, + pvs: []*v1.PersistentVolume{pvNode1a}, + expectedUnbound: false, + expectedBound: true, + }, + "one-bound,one-unbound": { + podPVCs: []*v1.PersistentVolumeClaim{unboundPVC, boundPVC}, + pvs: []*v1.PersistentVolume{pvBound, pvNode1a}, + expectedBindings: []*bindingInfo{binding1a}, + expectedUnbound: true, + expectedBound: true, + }, + "one-bound,one-unbound,no-match": { + podPVCs: []*v1.PersistentVolumeClaim{unboundPVC, boundPVC}, + pvs: []*v1.PersistentVolume{pvBound, pvNode2}, + expectedUnbound: false, + expectedBound: true, + }, + "one-prebound,one-unbound": { + podPVCs: []*v1.PersistentVolumeClaim{unboundPVC, preboundPVC}, + pvs: []*v1.PersistentVolume{pvNode1a, pvNode1b}, + expectedBindings: []*bindingInfo{binding1a}, + expectedUnbound: true, + expectedBound: true, + }, + "immediate-bound-pvc": { + podPVCs: []*v1.PersistentVolumeClaim{immediateBoundPVC}, + pvs: []*v1.PersistentVolume{pvBoundImmediate}, + expectedUnbound: true, + expectedBound: true, + }, + "immediate-bound-pvc-wrong-node": { + podPVCs: []*v1.PersistentVolumeClaim{immediateBoundPVC}, + pvs: []*v1.PersistentVolume{pvBoundImmediateNode2}, + expectedUnbound: true, + expectedBound: false, + }, + "immediate-unbound-pvc": { + podPVCs: []*v1.PersistentVolumeClaim{immediateUnboundPVC}, + expectedUnbound: false, + expectedBound: false, + shouldFail: true, + }, + "immediate-unbound-pvc,delayed-mode-bound": { + podPVCs: []*v1.PersistentVolumeClaim{immediateUnboundPVC, boundPVC}, + pvs: []*v1.PersistentVolume{pvBound}, + expectedUnbound: false, + expectedBound: false, + shouldFail: true, + }, + "immediate-unbound-pvc,delayed-mode-unbound": { + podPVCs: []*v1.PersistentVolumeClaim{immediateUnboundPVC, unboundPVC}, + expectedUnbound: false, + expectedBound: false, + shouldFail: true, + }, + } + + // Set feature gate + utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true") + defer utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false") + + for name, scenario := range scenarios { + glog.V(5).Infof("Running test case %q", name) + + // Setup + testEnv := newTestBinder(t) + testEnv.initVolumes(scenario.pvs, scenario.pvs) + if scenario.node == "" { + scenario.node = "node1" + } + + // a. Init pvc cache + if scenario.cachePVCs == nil { + scenario.cachePVCs = scenario.podPVCs + } + testEnv.initClaims(t, scenario.cachePVCs) + + // b. Generate pod with given claims + if scenario.pod == nil { + scenario.pod = makePod(scenario.podPVCs) + } + + // Execute + unboundSatisfied, boundSatisfied, err := testEnv.binder.FindPodVolumes(scenario.pod, scenario.node) + + // Validate + if !scenario.shouldFail && err != nil { + t.Errorf("Test %q failed: returned error: %v", name, err) + } + if scenario.shouldFail && err == nil { + t.Errorf("Test %q failed: returned success but expected error", name) + } + if boundSatisfied != scenario.expectedBound { + t.Errorf("Test %q failed: expected boundSatsified %v, got %v", name, scenario.expectedBound, boundSatisfied) + } + if unboundSatisfied != scenario.expectedUnbound { + t.Errorf("Test %q failed: expected unboundSatsified %v, got %v", name, scenario.expectedUnbound, unboundSatisfied) + } + testEnv.validatePodCache(t, name, scenario.node, scenario.pod, scenario.expectedBindings) + } +} + +func TestAssumePodVolumes(t *testing.T) { + scenarios := map[string]struct { + // Inputs + podPVCs []*v1.PersistentVolumeClaim + pvs []*v1.PersistentVolume + bindings []*bindingInfo + + // Expected return values + shouldFail bool + expectedBindingRequired bool + expectedAllBound bool + + // if nil, use bindings + expectedBindings []*bindingInfo + }{ + "all-bound": { + podPVCs: []*v1.PersistentVolumeClaim{boundPVC}, + pvs: []*v1.PersistentVolume{pvBound}, + expectedAllBound: true, + }, + "prebound-pvc": { + podPVCs: []*v1.PersistentVolumeClaim{preboundPVC}, + pvs: []*v1.PersistentVolume{pvNode1a}, + }, + "one-binding": { + podPVCs: []*v1.PersistentVolumeClaim{unboundPVC}, + bindings: []*bindingInfo{binding1a}, + pvs: []*v1.PersistentVolume{pvNode1a}, + expectedBindingRequired: true, + }, + "two-bindings": { + podPVCs: []*v1.PersistentVolumeClaim{unboundPVC, unboundPVC2}, + bindings: []*bindingInfo{binding1a, binding1b}, + pvs: []*v1.PersistentVolume{pvNode1a, pvNode1b}, + expectedBindingRequired: true, + }, + "pv-already-bound": { + podPVCs: []*v1.PersistentVolumeClaim{unboundPVC}, + bindings: []*bindingInfo{binding1aBound}, + pvs: []*v1.PersistentVolume{pvNode1aBound}, + expectedBindingRequired: false, + expectedBindings: []*bindingInfo{}, + }, + "claimref-failed": { + podPVCs: []*v1.PersistentVolumeClaim{unboundPVC}, + bindings: []*bindingInfo{binding1a, bindingBad}, + pvs: []*v1.PersistentVolume{pvNode1a, pvNode1b}, + shouldFail: true, + expectedBindingRequired: true, + }, + "tmpupdate-failed": { + podPVCs: []*v1.PersistentVolumeClaim{unboundPVC}, + bindings: []*bindingInfo{binding1a, binding1b}, + pvs: []*v1.PersistentVolume{pvNode1a}, + shouldFail: true, + expectedBindingRequired: true, + }, + } + + for name, scenario := range scenarios { + glog.V(5).Infof("Running test case %q", name) + + // Setup + testEnv := newTestBinder(t) + testEnv.initClaims(t, scenario.podPVCs) + pod := makePod(scenario.podPVCs) + testEnv.initPodCache(pod, "node1", scenario.bindings) + testEnv.initVolumes(scenario.pvs, scenario.pvs) + + // Execute + allBound, bindingRequired, err := testEnv.binder.AssumePodVolumes(pod, "node1") + + // Validate + if !scenario.shouldFail && err != nil { + t.Errorf("Test %q failed: returned error: %v", name, err) + } + if scenario.shouldFail && err == nil { + t.Errorf("Test %q failed: returned success but expected error", name) + } + if scenario.expectedBindingRequired != bindingRequired { + t.Errorf("Test %q failed: returned unexpected bindingRequired: %v", name, bindingRequired) + } + if scenario.expectedAllBound != allBound { + t.Errorf("Test %q failed: returned unexpected allBound: %v", name, allBound) + } + if scenario.expectedBindings == nil { + scenario.expectedBindings = scenario.bindings + } + if scenario.shouldFail { + testEnv.validateFailedAssume(t, name, pod, scenario.expectedBindings) + } else { + testEnv.validateAssume(t, name, pod, scenario.expectedBindings) + } + } +} + +func TestBindPodVolumes(t *testing.T) { + scenarios := map[string]struct { + // Inputs + bindings []*bindingInfo + cachedPVs []*v1.PersistentVolume + // if nil, use cachedPVs + apiPVs []*v1.PersistentVolume + + // Expected return values + shouldFail bool + expectedPVs []*v1.PersistentVolume + // if nil, use expectedPVs + expectedAPIPVs []*v1.PersistentVolume + }{ + "all-bound": {}, + "not-fully-bound": { + bindings: []*bindingInfo{}, + }, + "one-binding": { + bindings: []*bindingInfo{binding1aBound}, + cachedPVs: []*v1.PersistentVolume{pvNode1a}, + expectedPVs: []*v1.PersistentVolume{pvNode1aBound}, + }, + "two-bindings": { + bindings: []*bindingInfo{binding1aBound, binding1bBound}, + cachedPVs: []*v1.PersistentVolume{pvNode1a, pvNode1b}, + expectedPVs: []*v1.PersistentVolume{pvNode1aBound, pvNode1bBound}, + }, + "api-update-failed": { + bindings: []*bindingInfo{binding1aBound, binding1bBound}, + cachedPVs: []*v1.PersistentVolume{pvNode1a, pvNode1b}, + apiPVs: []*v1.PersistentVolume{pvNode1a, pvNode1bBoundHigherVersion}, + expectedPVs: []*v1.PersistentVolume{pvNode1aBound, pvNode1b}, + expectedAPIPVs: []*v1.PersistentVolume{pvNode1aBound, pvNode1bBoundHigherVersion}, + shouldFail: true, + }, + } + for name, scenario := range scenarios { + glog.V(5).Infof("Running test case %q", name) + + // Setup + testEnv := newTestBinder(t) + pod := makePod(nil) + if scenario.apiPVs == nil { + scenario.apiPVs = scenario.cachedPVs + } + testEnv.initVolumes(scenario.cachedPVs, scenario.apiPVs) + testEnv.assumeVolumes(t, name, "node1", pod, scenario.bindings) + + // Execute + err := testEnv.binder.BindPodVolumes(pod) + + // Validate + if !scenario.shouldFail && err != nil { + t.Errorf("Test %q failed: returned error: %v", name, err) + } + if scenario.shouldFail && err == nil { + t.Errorf("Test %q failed: returned success but expected error", name) + } + if scenario.expectedAPIPVs == nil { + scenario.expectedAPIPVs = scenario.expectedPVs + } + testEnv.validateBind(t, name, pod, scenario.expectedPVs, scenario.expectedAPIPVs) + } +} diff --git a/pkg/controller/volume/persistentvolume/volume_host.go b/pkg/controller/volume/persistentvolume/volume_host.go index 8f182edc6b9..27d45629e8a 100644 --- a/pkg/controller/volume/persistentvolume/volume_host.go +++ b/pkg/controller/volume/persistentvolume/volume_host.go @@ -37,6 +37,10 @@ func (ctrl *PersistentVolumeController) GetPluginDir(pluginName string) string { return "" } +func (ctrl *PersistentVolumeController) GetVolumeDevicePluginDir(pluginName string) string { + return "" +} + func (ctrl *PersistentVolumeController) GetPodVolumeDir(podUID types.UID, pluginName string, volumeName string) string { return "" } @@ -45,6 +49,10 @@ func (ctrl *PersistentVolumeController) GetPodPluginDir(podUID types.UID, plugin return "" } +func (ctrl *PersistentVolumeController) GetPodVolumeDeviceDir(ppodUID types.UID, pluginName string) string { + return "" +} + func (ctrl *PersistentVolumeController) GetKubeClient() clientset.Interface { return ctrl.kubeClient } @@ -100,3 +108,7 @@ func (adc *PersistentVolumeController) GetExec(pluginName string) mount.Exec { func (ctrl *PersistentVolumeController) GetNodeLabels() (map[string]string, error) { return nil, fmt.Errorf("GetNodeLabels() unsupported in PersistentVolumeController") } + +func (ctrl *PersistentVolumeController) GetNodeName() types.NodeName { + return "" +} diff --git a/pkg/controller/volume/pvcprotection/BUILD b/pkg/controller/volume/pvcprotection/BUILD new file mode 100644 index 00000000000..ebc1bb28def --- /dev/null +++ b/pkg/controller/volume/pvcprotection/BUILD @@ -0,0 +1,62 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["pvc_protection_controller.go"], + importpath = "k8s.io/kubernetes/pkg/controller/volume/pvcprotection", + visibility = ["//visibility:public"], + deps = [ + "//pkg/controller:go_default_library", + "//pkg/util/metrics:go_default_library", + "//pkg/util/slice:go_default_library", + "//pkg/volume/util:go_default_library", + "//pkg/volume/util/volumehelper:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/client-go/informers/core/v1:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/client-go/listers/core/v1:go_default_library", + "//vendor/k8s.io/client-go/tools/cache:go_default_library", + "//vendor/k8s.io/client-go/util/workqueue:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["pvc_protection_controller_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/controller/volume/pvcprotection", + deps = [ + "//pkg/controller:go_default_library", + "//pkg/volume/util:go_default_library", + "//vendor/github.com/davecgh/go-spew/spew:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/client-go/informers:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", + "//vendor/k8s.io/client-go/testing:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/controller/volume/pvcprotection/pvc_protection_controller.go b/pkg/controller/volume/pvcprotection/pvc_protection_controller.go new file mode 100644 index 00000000000..8ce491ffcd3 --- /dev/null +++ b/pkg/controller/volume/pvcprotection/pvc_protection_controller.go @@ -0,0 +1,293 @@ +/* +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 pvcprotection + +import ( + "fmt" + "time" + + "github.com/golang/glog" + "k8s.io/api/core/v1" + apierrs "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + coreinformers "k8s.io/client-go/informers/core/v1" + clientset "k8s.io/client-go/kubernetes" + corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/util/metrics" + "k8s.io/kubernetes/pkg/util/slice" + volumeutil "k8s.io/kubernetes/pkg/volume/util" + "k8s.io/kubernetes/pkg/volume/util/volumehelper" +) + +// Controller is controller that removes PVCProtectionFinalizer +// from PVCs that are used by no pods. +type Controller struct { + client clientset.Interface + + pvcLister corelisters.PersistentVolumeClaimLister + pvcListerSynced cache.InformerSynced + + podLister corelisters.PodLister + podListerSynced cache.InformerSynced + + queue workqueue.RateLimitingInterface +} + +// NewPVCProtectionController returns a new *{VCProtectionController. +func NewPVCProtectionController(pvcInformer coreinformers.PersistentVolumeClaimInformer, podInformer coreinformers.PodInformer, cl clientset.Interface) *Controller { + e := &Controller{ + client: cl, + queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "pvcprotection"), + } + if cl != nil && cl.CoreV1().RESTClient().GetRateLimiter() != nil { + metrics.RegisterMetricAndTrackRateLimiterUsage("persistentvolumeclaim_protection_controller", cl.CoreV1().RESTClient().GetRateLimiter()) + } + + e.pvcLister = pvcInformer.Lister() + e.pvcListerSynced = pvcInformer.Informer().HasSynced + pvcInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: e.pvcAddedUpdated, + UpdateFunc: func(old, new interface{}) { + e.pvcAddedUpdated(new) + }, + }) + + e.podLister = podInformer.Lister() + e.podListerSynced = podInformer.Informer().HasSynced + podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + e.podAddedDeletedUpdated(obj, false) + }, + DeleteFunc: func(obj interface{}) { + e.podAddedDeletedUpdated(obj, true) + }, + UpdateFunc: func(old, new interface{}) { + e.podAddedDeletedUpdated(new, false) + }, + }) + + return e +} + +// Run runs the controller goroutines. +func (c *Controller) Run(workers int, stopCh <-chan struct{}) { + defer utilruntime.HandleCrash() + defer c.queue.ShutDown() + + glog.Infof("Starting PVC protection controller") + defer glog.Infof("Shutting down PVC protection controller") + + if !controller.WaitForCacheSync("PVC protection", stopCh, c.pvcListerSynced, c.podListerSynced) { + return + } + + for i := 0; i < workers; i++ { + go wait.Until(c.runWorker, time.Second, stopCh) + } + + <-stopCh +} + +func (c *Controller) runWorker() { + for c.processNextWorkItem() { + } +} + +// processNextWorkItem deals with one pvcKey off the queue. It returns false when it's time to quit. +func (c *Controller) processNextWorkItem() bool { + pvcKey, quit := c.queue.Get() + if quit { + return false + } + defer c.queue.Done(pvcKey) + + pvcNamespace, pvcName, err := cache.SplitMetaNamespaceKey(pvcKey.(string)) + if err != nil { + utilruntime.HandleError(fmt.Errorf("Error parsing PVC key %q: %v", pvcKey, err)) + return true + } + + err = c.processPVC(pvcNamespace, pvcName) + if err == nil { + c.queue.Forget(pvcKey) + return true + } + + utilruntime.HandleError(fmt.Errorf("PVC %v failed with : %v", pvcKey, err)) + c.queue.AddRateLimited(pvcKey) + + return true +} + +func (c *Controller) processPVC(pvcNamespace, pvcName string) error { + glog.V(4).Infof("Processing PVC %s/%s", pvcNamespace, pvcName) + startTime := time.Now() + defer func() { + glog.V(4).Infof("Finished processing PVC %s/%s (%v)", pvcNamespace, pvcName, time.Now().Sub(startTime)) + }() + + pvc, err := c.pvcLister.PersistentVolumeClaims(pvcNamespace).Get(pvcName) + if apierrs.IsNotFound(err) { + glog.V(4).Infof("PVC %s/%s not found, ignoring", pvcNamespace, pvcName) + return nil + } + if err != nil { + return err + } + + if isDeletionCandidate(pvc) { + // PVC should be deleted. Check if it's used and remove finalizer if + // it's not. + isUsed, err := c.isBeingUsed(pvc) + if err != nil { + return err + } + if !isUsed { + return c.removeFinalizer(pvc) + } + } + + if needToAddFinalizer(pvc) { + // PVC is not being deleted -> it should have the finalizer. The + // finalizer should be added by admission plugin, this is just to add + // the finalizer to old PVCs that were created before the admission + // plugin was enabled. + return c.addFinalizer(pvc) + } + return nil +} + +func (c *Controller) addFinalizer(pvc *v1.PersistentVolumeClaim) error { + claimClone := pvc.DeepCopy() + claimClone.ObjectMeta.Finalizers = append(claimClone.ObjectMeta.Finalizers, volumeutil.PVCProtectionFinalizer) + _, err := c.client.CoreV1().PersistentVolumeClaims(claimClone.Namespace).Update(claimClone) + if err != nil { + glog.V(3).Infof("Error adding protection finalizer to PVC %s/%s: %v", pvc.Namespace, pvc.Name) + return err + } + glog.V(3).Infof("Added protection finalizer to PVC %s/%s", pvc.Namespace, pvc.Name) + return nil +} + +func (c *Controller) removeFinalizer(pvc *v1.PersistentVolumeClaim) error { + claimClone := pvc.DeepCopy() + claimClone.ObjectMeta.Finalizers = slice.RemoveString(claimClone.ObjectMeta.Finalizers, volumeutil.PVCProtectionFinalizer, nil) + _, err := c.client.CoreV1().PersistentVolumeClaims(claimClone.Namespace).Update(claimClone) + if err != nil { + glog.V(3).Infof("Error removing protection finalizer from PVC %s/%s: %v", pvc.Namespace, pvc.Name, err) + return err + } + glog.V(3).Infof("Removed protection finalizer from PVC %s/%s", pvc.Namespace, pvc.Name) + return nil +} + +func (c *Controller) isBeingUsed(pvc *v1.PersistentVolumeClaim) (bool, error) { + pods, err := c.podLister.Pods(pvc.Namespace).List(labels.Everything()) + if err != nil { + return false, err + } + for _, pod := range pods { + if pod.Spec.NodeName == "" { + // This pod is not scheduled. We have a predicated in scheduler that + // prevents scheduling pods with deletion timestamp, so we can be + // pretty sure it won't be scheduled in parallel to this check. + // Therefore this pod does not block the PVC from deletion. + glog.V(4).Infof("Skipping unscheduled pod %s when checking PVC %s/%s", pod.Name, pvc.Namespace, pvc.Name) + continue + } + if volumehelper.IsPodTerminated(pod, pod.Status) { + // This pod is being unmounted/detached or is already + // unmounted/detached. It does not block the PVC from deletion. + continue + } + for _, volume := range pod.Spec.Volumes { + if volume.PersistentVolumeClaim == nil { + continue + } + if volume.PersistentVolumeClaim.ClaimName == pvc.Name { + glog.V(2).Infof("Keeping PVC %s/%s, it is used by pod %s/%s", pvc.Namespace, pvc.Name, pod.Namespace, pod.Name) + return true, nil + } + } + } + + glog.V(3).Infof("PVC %s/%s is unused", pvc.Namespace, pvc.Name) + return false, nil +} + +// pvcAddedUpdated reacts to pvc added/updated/deleted events +func (c *Controller) pvcAddedUpdated(obj interface{}) { + pvc, ok := obj.(*v1.PersistentVolumeClaim) + if !ok { + utilruntime.HandleError(fmt.Errorf("PVC informer returned non-PVC object: %#v", obj)) + return + } + key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(pvc) + if err != nil { + utilruntime.HandleError(fmt.Errorf("Couldn't get key for Persistent Volume Claim %#v: %v", pvc, err)) + return + } + glog.V(4).Infof("Got event on PVC %s", key) + + if needToAddFinalizer(pvc) || isDeletionCandidate(pvc) { + c.queue.Add(key) + } +} + +// podAddedDeletedUpdated reacts to Pod events +func (c *Controller) podAddedDeletedUpdated(obj interface{}, deleted bool) { + 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 + } + pod, ok = tombstone.Obj.(*v1.Pod) + if !ok { + utilruntime.HandleError(fmt.Errorf("Tombstone contained object that is not a Pod %#v", obj)) + return + } + } + + // Filter out pods that can't help us to remove a finalizer on PVC + if !deleted && !volumehelper.IsPodTerminated(pod, pod.Status) && pod.Spec.NodeName != "" { + return + } + + glog.V(4).Infof("Got event on pod %s/%s", pod.Namespace, pod.Name) + + // Enqueue all PVCs that the pod uses + for _, volume := range pod.Spec.Volumes { + if volume.PersistentVolumeClaim != nil { + c.queue.Add(pod.Namespace + "/" + volume.PersistentVolumeClaim.ClaimName) + } + } +} + +func isDeletionCandidate(pvc *v1.PersistentVolumeClaim) bool { + return pvc.ObjectMeta.DeletionTimestamp != nil && slice.ContainsString(pvc.ObjectMeta.Finalizers, volumeutil.PVCProtectionFinalizer, nil) +} + +func needToAddFinalizer(pvc *v1.PersistentVolumeClaim) bool { + return pvc.ObjectMeta.DeletionTimestamp == nil && !slice.ContainsString(pvc.ObjectMeta.Finalizers, volumeutil.PVCProtectionFinalizer, nil) +} diff --git a/pkg/controller/volume/pvcprotection/pvc_protection_controller_test.go b/pkg/controller/volume/pvcprotection/pvc_protection_controller_test.go new file mode 100644 index 00000000000..0d7a2f9302c --- /dev/null +++ b/pkg/controller/volume/pvcprotection/pvc_protection_controller_test.go @@ -0,0 +1,397 @@ +/* +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 pvcprotection + +import ( + "errors" + "reflect" + "testing" + "time" + + "github.com/davecgh/go-spew/spew" + "github.com/golang/glog" + + "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes/fake" + clienttesting "k8s.io/client-go/testing" + "k8s.io/kubernetes/pkg/controller" + volumeutil "k8s.io/kubernetes/pkg/volume/util" +) + +type reaction struct { + verb string + resource string + reactorfn clienttesting.ReactionFunc +} + +const ( + defaultNS = "default" + defaultPVCName = "pvc1" + defaultPodName = "pod1" + defaultNodeName = "node1" +) + +func pod() *v1.Pod { + return &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: defaultPodName, + Namespace: defaultNS, + }, + Spec: v1.PodSpec{ + NodeName: defaultNodeName, + }, + Status: v1.PodStatus{ + Phase: v1.PodPending, + }, + } +} + +func unscheduled(pod *v1.Pod) *v1.Pod { + pod.Spec.NodeName = "" + return pod +} + +func withPVC(pvcName string, pod *v1.Pod) *v1.Pod { + volume := v1.Volume{ + Name: pvcName, + VolumeSource: v1.VolumeSource{ + PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ + ClaimName: pvcName, + }, + }, + } + pod.Spec.Volumes = append(pod.Spec.Volumes, volume) + return pod +} + +func withEmptyDir(pod *v1.Pod) *v1.Pod { + volume := v1.Volume{ + Name: "emptyDir", + VolumeSource: v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }, + } + pod.Spec.Volumes = append(pod.Spec.Volumes, volume) + return pod +} + +func withStatus(phase v1.PodPhase, pod *v1.Pod) *v1.Pod { + pod.Status.Phase = phase + return pod +} + +func pvc() *v1.PersistentVolumeClaim { + return &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: defaultPVCName, + Namespace: defaultNS, + }, + } +} + +func withProtectionFinalizer(pvc *v1.PersistentVolumeClaim) *v1.PersistentVolumeClaim { + pvc.Finalizers = append(pvc.Finalizers, volumeutil.PVCProtectionFinalizer) + return pvc +} + +func deleted(pvc *v1.PersistentVolumeClaim) *v1.PersistentVolumeClaim { + pvc.DeletionTimestamp = &metav1.Time{} + return pvc +} + +func generateUpdateErrorFunc(t *testing.T, failures int) clienttesting.ReactionFunc { + i := 0 + return func(action clienttesting.Action) (bool, runtime.Object, error) { + i++ + if i <= failures { + // Update fails + update, ok := action.(clienttesting.UpdateAction) + + if !ok { + t.Fatalf("Reactor got non-update action: %+v", action) + } + acc, _ := meta.Accessor(update.GetObject()) + return true, nil, apierrors.NewForbidden(update.GetResource().GroupResource(), acc.GetName(), errors.New("Mock error")) + } + // Update succeeds + return false, nil, nil + } +} + +func TestPVCProtectionController(t *testing.T) { + pvcVer := schema.GroupVersionResource{ + Group: v1.GroupName, + Version: "v1", + Resource: "persistentvolumeclaims", + } + + tests := []struct { + name string + // Object to insert into fake kubeclient before the test starts. + initialObjects []runtime.Object + // Optional client reactors. + reactors []reaction + // PVC event to simulate. This PVC will be automatically added to + // initalObjects. + updatedPVC *v1.PersistentVolumeClaim + // Pod event to simulate. This Pod will be automatically added to + // initalObjects. + updatedPod *v1.Pod + // Pod event to similate. This Pod is *not* added to + // initalObjects. + deletedPod *v1.Pod + // List of expected kubeclient actions that should happen during the + // test. + expectedActions []clienttesting.Action + }{ + // + // PVC events + // + { + name: "PVC without finalizer -> finalizer is added", + updatedPVC: pvc(), + expectedActions: []clienttesting.Action{ + clienttesting.NewUpdateAction(pvcVer, defaultNS, withProtectionFinalizer(pvc())), + }, + }, + { + name: "PVC with finalizer -> no action", + updatedPVC: withProtectionFinalizer(pvc()), + expectedActions: []clienttesting.Action{}, + }, + { + name: "saving PVC finalizer fails -> controller retries", + updatedPVC: pvc(), + reactors: []reaction{ + { + verb: "update", + resource: "persistentvolumeclaims", + reactorfn: generateUpdateErrorFunc(t, 2 /* update fails twice*/), + }, + }, + expectedActions: []clienttesting.Action{ + // This fails + clienttesting.NewUpdateAction(pvcVer, defaultNS, withProtectionFinalizer(pvc())), + // This fails too + clienttesting.NewUpdateAction(pvcVer, defaultNS, withProtectionFinalizer(pvc())), + // This succeeds + clienttesting.NewUpdateAction(pvcVer, defaultNS, withProtectionFinalizer(pvc())), + }, + }, + { + name: "deleted PVC with finalizer -> finalizer is removed", + updatedPVC: deleted(withProtectionFinalizer(pvc())), + expectedActions: []clienttesting.Action{ + clienttesting.NewUpdateAction(pvcVer, defaultNS, deleted(pvc())), + }, + }, + { + name: "finalizer removal fails -> controller retries", + updatedPVC: deleted(withProtectionFinalizer(pvc())), + reactors: []reaction{ + { + verb: "update", + resource: "persistentvolumeclaims", + reactorfn: generateUpdateErrorFunc(t, 2 /* update fails twice*/), + }, + }, + expectedActions: []clienttesting.Action{ + // Fails + clienttesting.NewUpdateAction(pvcVer, defaultNS, deleted(pvc())), + // Fails too + clienttesting.NewUpdateAction(pvcVer, defaultNS, deleted(pvc())), + // Succeeds + clienttesting.NewUpdateAction(pvcVer, defaultNS, deleted(pvc())), + }, + }, + { + name: "deleted PVC with finalizer + pods with the PVC exists -> finalizer is not removed", + initialObjects: []runtime.Object{ + withPVC(defaultPVCName, pod()), + }, + updatedPVC: deleted(withProtectionFinalizer(pvc())), + expectedActions: []clienttesting.Action{}, + }, + { + name: "deleted PVC with finalizer + pods with unrelated PVC and EmptyDir exists -> finalizer is removed", + initialObjects: []runtime.Object{ + withEmptyDir(withPVC("unrelatedPVC", pod())), + }, + updatedPVC: deleted(withProtectionFinalizer(pvc())), + expectedActions: []clienttesting.Action{ + clienttesting.NewUpdateAction(pvcVer, defaultNS, deleted(pvc())), + }, + }, + { + name: "deleted PVC with finalizer + pods with the PVC andis finished -> finalizer is removed", + initialObjects: []runtime.Object{ + withStatus(v1.PodFailed, withPVC(defaultPVCName, pod())), + }, + updatedPVC: deleted(withProtectionFinalizer(pvc())), + expectedActions: []clienttesting.Action{ + clienttesting.NewUpdateAction(pvcVer, defaultNS, deleted(pvc())), + }, + }, + // + // Pod events + // + { + name: "updated running Pod -> no action", + initialObjects: []runtime.Object{ + deleted(withProtectionFinalizer(pvc())), + }, + updatedPod: withStatus(v1.PodRunning, withPVC(defaultPVCName, pod())), + expectedActions: []clienttesting.Action{}, + }, + { + name: "updated finished Pod -> finalizer is removed", + initialObjects: []runtime.Object{ + deleted(withProtectionFinalizer(pvc())), + }, + updatedPod: withStatus(v1.PodSucceeded, withPVC(defaultPVCName, pod())), + expectedActions: []clienttesting.Action{ + clienttesting.NewUpdateAction(pvcVer, defaultNS, deleted(pvc())), + }, + }, + { + name: "updated unscheduled Pod -> finalizer is removed", + initialObjects: []runtime.Object{ + deleted(withProtectionFinalizer(pvc())), + }, + updatedPod: unscheduled(withPVC(defaultPVCName, pod())), + expectedActions: []clienttesting.Action{ + clienttesting.NewUpdateAction(pvcVer, defaultNS, deleted(pvc())), + }, + }, + { + name: "deleted running Pod -> finalizer is removed", + initialObjects: []runtime.Object{ + deleted(withProtectionFinalizer(pvc())), + }, + deletedPod: withStatus(v1.PodRunning, withPVC(defaultPVCName, pod())), + expectedActions: []clienttesting.Action{ + clienttesting.NewUpdateAction(pvcVer, defaultNS, deleted(pvc())), + }, + }, + } + + for _, test := range tests { + // Create client with initial data + objs := test.initialObjects + if test.updatedPVC != nil { + objs = append(objs, test.updatedPVC) + } + if test.updatedPod != nil { + objs = append(objs, test.updatedPod) + } + client := fake.NewSimpleClientset(objs...) + + // Create informers + informers := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) + pvcInformer := informers.Core().V1().PersistentVolumeClaims() + podInformer := informers.Core().V1().Pods() + + // Populate the informers with initial objects so the controller can + // Get() and List() it. + for _, obj := range objs { + switch obj.(type) { + case *v1.PersistentVolumeClaim: + pvcInformer.Informer().GetStore().Add(obj) + case *v1.Pod: + podInformer.Informer().GetStore().Add(obj) + default: + t.Fatalf("Unknown initalObject type: %+v", obj) + } + } + + // Add reactor to inject test errors. + for _, reactor := range test.reactors { + client.Fake.PrependReactor(reactor.verb, reactor.resource, reactor.reactorfn) + } + + // Create the controller + ctrl := NewPVCProtectionController(pvcInformer, podInformer, client) + + // Start the test by simulating an event + if test.updatedPVC != nil { + ctrl.pvcAddedUpdated(test.updatedPVC) + } + if test.updatedPod != nil { + ctrl.podAddedDeletedUpdated(test.updatedPod, false) + } + if test.deletedPod != nil { + ctrl.podAddedDeletedUpdated(test.deletedPod, true) + } + + // Process the controller queue until we get expected results + timeout := time.Now().Add(10 * time.Second) + lastReportedActionCount := 0 + for { + if time.Now().After(timeout) { + t.Errorf("Test %q: timed out", test.name) + break + } + if ctrl.queue.Len() > 0 { + glog.V(5).Infof("Test %q: %d events queue, processing one", test.name, ctrl.queue.Len()) + ctrl.processNextWorkItem() + } + if ctrl.queue.Len() > 0 { + // There is still some work in the queue, process it now + continue + } + currentActionCount := len(client.Actions()) + if currentActionCount < len(test.expectedActions) { + // Do not log evey wait, only when the action count changes. + if lastReportedActionCount < currentActionCount { + glog.V(5).Infof("Test %q: got %d actions out of %d, waiting for the rest", test.name, currentActionCount, len(test.expectedActions)) + lastReportedActionCount = currentActionCount + } + // The test expected more to happen, wait for the actions. + // Most probably it's exponential backoff + time.Sleep(10 * time.Millisecond) + continue + } + break + } + actions := client.Actions() + for i, action := range actions { + if len(test.expectedActions) < i+1 { + t.Errorf("Test %q: %d unexpected actions: %+v", test.name, len(actions)-len(test.expectedActions), spew.Sdump(actions[i:])) + break + } + + expectedAction := test.expectedActions[i] + if !reflect.DeepEqual(expectedAction, action) { + t.Errorf("Test %q: action %d\nExpected:\n%s\ngot:\n%s", test.name, i, spew.Sdump(expectedAction), spew.Sdump(action)) + } + } + + if len(test.expectedActions) > len(actions) { + t.Errorf("Test %q: %d additional expected actions", test.name, len(test.expectedActions)-len(actions)) + for _, a := range test.expectedActions[len(actions):] { + t.Logf(" %+v", a) + } + } + + } +} diff --git a/pkg/credentialprovider/BUILD b/pkg/credentialprovider/BUILD index 89f836f4905..4cec1e472ce 100644 --- a/pkg/credentialprovider/BUILD +++ b/pkg/credentialprovider/BUILD @@ -19,7 +19,6 @@ go_library( deps = [ "//vendor/github.com/docker/docker/api/types:go_default_library", "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", ], ) @@ -31,8 +30,8 @@ go_test( "keyring_test.go", "provider_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/credentialprovider", - library = ":go_default_library", deps = ["//vendor/github.com/docker/docker/api/types:go_default_library"], ) @@ -51,6 +50,7 @@ filegroup( "//pkg/credentialprovider/azure:all-srcs", "//pkg/credentialprovider/gcp:all-srcs", "//pkg/credentialprovider/rancher:all-srcs", + "//pkg/credentialprovider/secrets:all-srcs", ], tags = ["automanaged"], ) diff --git a/pkg/credentialprovider/aws/BUILD b/pkg/credentialprovider/aws/BUILD index 5cb5b7647f7..5c66c309dbb 100644 --- a/pkg/credentialprovider/aws/BUILD +++ b/pkg/credentialprovider/aws/BUILD @@ -23,8 +23,8 @@ go_library( go_test( name = "go_default_test", srcs = ["aws_credentials_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/credentialprovider/aws", - library = ":go_default_library", deps = [ "//pkg/credentialprovider:go_default_library", "//vendor/github.com/aws/aws-sdk-go/aws:go_default_library", diff --git a/pkg/credentialprovider/aws/aws_credentials.go b/pkg/credentialprovider/aws/aws_credentials.go index 7e8955e9c0c..d889cbf1fa8 100644 --- a/pkg/credentialprovider/aws/aws_credentials.go +++ b/pkg/credentialprovider/aws/aws_credentials.go @@ -30,7 +30,10 @@ import ( "k8s.io/kubernetes/pkg/credentialprovider" ) -const registryURLTemplate = "*.dkr.ecr.%s.amazonaws.com" +const awsChinaRegionPrefix = "cn-" +const awsStandardDNSSuffix = "amazonaws.com" +const awsChinaDNSSuffix = "amazonaws.com.cn" +const registryURLTemplate = "*.dkr.ecr.%s.%s" // awsHandlerLogger is a handler that logs all AWS SDK requests // Copied from pkg/cloudprovider/providers/aws/log_handler.go @@ -80,6 +83,16 @@ type ecrProvider struct { var _ credentialprovider.DockerConfigProvider = &ecrProvider{} +// registryURL has different suffix in AWS China region +func registryURL(region string) string { + dnsSuffix := awsStandardDNSSuffix + // deal with aws none standard regions + if strings.HasPrefix(region, awsChinaRegionPrefix) { + dnsSuffix = awsChinaDNSSuffix + } + return fmt.Sprintf(registryURLTemplate, region, dnsSuffix) +} + // RegisterCredentialsProvider registers a credential provider for the specified region. // It creates a lazy provider for each AWS region, in order to support // cross-region ECR access. They have to be lazy because it's unlikely, but not @@ -92,7 +105,7 @@ func RegisterCredentialsProvider(region string) { credentialprovider.RegisterCredentialProvider("aws-ecr-"+region, &lazyEcrProvider{ region: region, - regionURL: fmt.Sprintf(registryURLTemplate, region), + regionURL: registryURL(region), }) } @@ -136,7 +149,7 @@ func (p *lazyEcrProvider) Provide() credentialprovider.DockerConfig { func newEcrProvider(region string, getter tokenGetter) *ecrProvider { return &ecrProvider{ region: region, - regionURL: fmt.Sprintf(registryURLTemplate, region), + regionURL: registryURL(region), getter: getter, } } diff --git a/pkg/credentialprovider/aws/aws_credentials_test.go b/pkg/credentialprovider/aws/aws_credentials_test.go index 7299c85ce57..499af17b78b 100644 --- a/pkg/credentialprovider/aws/aws_credentials_test.go +++ b/pkg/credentialprovider/aws/aws_credentials_test.go @@ -59,6 +59,7 @@ func (p *testTokenGetter) GetAuthorizationToken(input *ecr.GetAuthorizationToken func TestEcrProvide(t *testing.T) { registry := "123456789012.dkr.ecr.lala-land-1.amazonaws.com" otherRegistries := []string{ + "123456789012.dkr.ecr.cn-foo-1.amazonaws.com.cn", "private.registry.com", "gcr.io", } @@ -107,3 +108,56 @@ func TestEcrProvide(t *testing.T) { } } } + +func TestChinaEcrProvide(t *testing.T) { + registry := "123456789012.dkr.ecr.cn-foo-1.amazonaws.com.cn" + otherRegistries := []string{ + "123456789012.dkr.ecr.lala-land-1.amazonaws.com", + "private.registry.com", + "gcr.io", + } + image := "foo/bar" + + provider := newEcrProvider("cn-foo-1", + &testTokenGetter{ + user: user, + password: password, + endpoint: registry, + }) + + keyring := &credentialprovider.BasicDockerKeyring{} + keyring.Add(provider.Provide()) + + // Verify that we get the expected username/password combo for + // an ECR image name. + fullImage := path.Join(registry, image) + creds, ok := keyring.Lookup(fullImage) + if !ok { + t.Errorf("Didn't find expected URL: %s", fullImage) + return + } + if len(creds) > 1 { + t.Errorf("Got more hits than expected: %s", creds) + } + val := creds[0] + + if user != val.Username { + t.Errorf("Unexpected username value, want: _token, got: %s", val.Username) + } + if password != val.Password { + t.Errorf("Unexpected password value, want: %s, got: %s", password, val.Password) + } + if email != val.Email { + t.Errorf("Unexpected email value, want: %s, got: %s", email, val.Email) + } + + // Verify that we get an error for other images. + for _, otherRegistry := range otherRegistries { + fullImage = path.Join(otherRegistry, image) + creds, ok = keyring.Lookup(fullImage) + if ok { + t.Errorf("Unexpectedly found image: %s", fullImage) + return + } + } +} diff --git a/pkg/credentialprovider/azure/BUILD b/pkg/credentialprovider/azure/BUILD index ee3b570baf0..afb23f31b7d 100644 --- a/pkg/credentialprovider/azure/BUILD +++ b/pkg/credentialprovider/azure/BUILD @@ -14,13 +14,14 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/credentialprovider/azure", deps = [ - "//pkg/cloudprovider/providers/azure:go_default_library", + "//pkg/cloudprovider/providers/azure/auth:go_default_library", "//pkg/credentialprovider:go_default_library", "//vendor/github.com/Azure/azure-sdk-for-go/arm/containerregistry:go_default_library", "//vendor/github.com/Azure/go-autorest/autorest:go_default_library", "//vendor/github.com/Azure/go-autorest/autorest/adal:go_default_library", "//vendor/github.com/Azure/go-autorest/autorest/azure:go_default_library", "//vendor/github.com/dgrijalva/jwt-go:go_default_library", + "//vendor/github.com/ghodss/yaml:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", ], @@ -29,8 +30,8 @@ go_library( go_test( name = "go_default_test", srcs = ["azure_credentials_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/credentialprovider/azure", - library = ":go_default_library", deps = [ "//vendor/github.com/Azure/azure-sdk-for-go/arm/containerregistry:go_default_library", "//vendor/github.com/Azure/go-autorest/autorest/to:go_default_library", diff --git a/pkg/credentialprovider/azure/OWNERS b/pkg/credentialprovider/azure/OWNERS new file mode 100644 index 00000000000..6b1eb11dd34 --- /dev/null +++ b/pkg/credentialprovider/azure/OWNERS @@ -0,0 +1,12 @@ +approvers: +- andyzhangx +- brendandburns +- feiskyer +- karataliu +- khenidak +reviewers: +- andyzhangx +- brendandburns +- feiskyer +- karataliu +- khenidak diff --git a/pkg/credentialprovider/azure/azure_credentials.go b/pkg/credentialprovider/azure/azure_credentials.go index ff251497ce3..e48d8133f55 100644 --- a/pkg/credentialprovider/azure/azure_credentials.go +++ b/pkg/credentialprovider/azure/azure_credentials.go @@ -18,16 +18,18 @@ package azure import ( "io" + "io/ioutil" "os" "time" "github.com/Azure/azure-sdk-for-go/arm/containerregistry" "github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/autorest/adal" - azureapi "github.com/Azure/go-autorest/autorest/azure" + "github.com/Azure/go-autorest/autorest/azure" + "github.com/ghodss/yaml" "github.com/golang/glog" "github.com/spf13/pflag" - "k8s.io/kubernetes/pkg/cloudprovider/providers/azure" + "k8s.io/kubernetes/pkg/cloudprovider/providers/azure/auth" "k8s.io/kubernetes/pkg/credentialprovider" ) @@ -60,18 +62,44 @@ func NewACRProvider(configFile *string) credentialprovider.DockerConfigProvider type acrProvider struct { file *string - config *azure.Config - environment *azureapi.Environment + config *auth.AzureAuthConfig + environment *azure.Environment registryClient RegistriesClient servicePrincipalToken *adal.ServicePrincipalToken } +// ParseConfig returns a parsed configuration for an Azure cloudprovider config file +func parseConfig(configReader io.Reader) (*auth.AzureAuthConfig, error) { + var config auth.AzureAuthConfig + + if configReader == nil { + return &config, nil + } + + configContents, err := ioutil.ReadAll(configReader) + if err != nil { + return nil, err + } + err = yaml.Unmarshal(configContents, &config) + if err != nil { + return nil, err + } + + return &config, nil +} + func (a *acrProvider) loadConfig(rdr io.Reader) error { var err error - a.config, a.environment, err = azure.ParseConfig(rdr) + a.config, err = parseConfig(rdr) if err != nil { glog.Errorf("Failed to load azure credential file: %v", err) } + + a.environment, err = auth.ParseAzureEnvironment(a.config.Cloud) + if err != nil { + return err + } + return nil } @@ -94,7 +122,7 @@ func (a *acrProvider) Enabled() bool { return false } - a.servicePrincipalToken, err = azure.GetServicePrincipalToken(a.config, a.environment) + a.servicePrincipalToken, err = auth.GetServicePrincipalToken(a.config, a.environment) if err != nil { glog.Errorf("Failed to create service principal token: %v", err) return false diff --git a/pkg/credentialprovider/gcp/BUILD b/pkg/credentialprovider/gcp/BUILD index 4d903e0dcf7..0a3b647a963 100644 --- a/pkg/credentialprovider/gcp/BUILD +++ b/pkg/credentialprovider/gcp/BUILD @@ -31,8 +31,8 @@ go_test( "jwt_test.go", "metadata_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/credentialprovider/gcp", - library = ":go_default_library", deps = [ "//pkg/credentialprovider:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", diff --git a/pkg/credentialprovider/keyring.go b/pkg/credentialprovider/keyring.go index 9ec96312577..b269f474600 100644 --- a/pkg/credentialprovider/keyring.go +++ b/pkg/credentialprovider/keyring.go @@ -17,7 +17,6 @@ limitations under the License. package credentialprovider import ( - "encoding/json" "net" "net/url" "path/filepath" @@ -27,7 +26,6 @@ import ( "github.com/golang/glog" dockertypes "github.com/docker/docker/api/types" - "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/sets" ) @@ -284,14 +282,12 @@ func (f *FakeKeyring) Lookup(image string) ([]LazyAuthConfiguration, bool) { return f.auth, f.ok } -// unionDockerKeyring delegates to a set of keyrings. -type unionDockerKeyring struct { - keyrings []DockerKeyring -} +// UnionDockerKeyring delegates to a set of keyrings. +type UnionDockerKeyring []DockerKeyring -func (k *unionDockerKeyring) Lookup(image string) ([]LazyAuthConfiguration, bool) { +func (k UnionDockerKeyring) Lookup(image string) ([]LazyAuthConfiguration, bool) { authConfigs := []LazyAuthConfiguration{} - for _, subKeyring := range k.keyrings { + for _, subKeyring := range k { if subKeyring == nil { continue } @@ -302,37 +298,3 @@ func (k *unionDockerKeyring) Lookup(image string) ([]LazyAuthConfiguration, bool return authConfigs, (len(authConfigs) > 0) } - -// MakeDockerKeyring inspects the passedSecrets to see if they contain any DockerConfig secrets. If they do, -// then a DockerKeyring is built based on every hit and unioned with the defaultKeyring. -// If they do not, then the default keyring is returned -func MakeDockerKeyring(passedSecrets []v1.Secret, defaultKeyring DockerKeyring) (DockerKeyring, error) { - passedCredentials := []DockerConfig{} - for _, passedSecret := range passedSecrets { - if dockerConfigJsonBytes, dockerConfigJsonExists := passedSecret.Data[v1.DockerConfigJsonKey]; (passedSecret.Type == v1.SecretTypeDockerConfigJson) && dockerConfigJsonExists && (len(dockerConfigJsonBytes) > 0) { - dockerConfigJson := DockerConfigJson{} - if err := json.Unmarshal(dockerConfigJsonBytes, &dockerConfigJson); err != nil { - return nil, err - } - - passedCredentials = append(passedCredentials, dockerConfigJson.Auths) - } else if dockercfgBytes, dockercfgExists := passedSecret.Data[v1.DockerConfigKey]; (passedSecret.Type == v1.SecretTypeDockercfg) && dockercfgExists && (len(dockercfgBytes) > 0) { - dockercfg := DockerConfig{} - if err := json.Unmarshal(dockercfgBytes, &dockercfg); err != nil { - return nil, err - } - - passedCredentials = append(passedCredentials, dockercfg) - } - } - - if len(passedCredentials) > 0 { - basicKeyring := &BasicDockerKeyring{} - for _, currCredentials := range passedCredentials { - basicKeyring.Add(currCredentials) - } - return &unionDockerKeyring{[]DockerKeyring{basicKeyring, defaultKeyring}}, nil - } - - return defaultKeyring, nil -} diff --git a/pkg/credentialprovider/rancher/BUILD b/pkg/credentialprovider/rancher/BUILD index afc26b6ddef..8d5150e6aaa 100644 --- a/pkg/credentialprovider/rancher/BUILD +++ b/pkg/credentialprovider/rancher/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["rancher_registry_credentials_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/credentialprovider/rancher", - library = ":go_default_library", deps = [ "//pkg/credentialprovider:go_default_library", "//vendor/github.com/rancher/go-rancher/client:go_default_library", diff --git a/pkg/credentialprovider/secrets/BUILD b/pkg/credentialprovider/secrets/BUILD new file mode 100644 index 00000000000..15d41a63711 --- /dev/null +++ b/pkg/credentialprovider/secrets/BUILD @@ -0,0 +1,26 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["secrets.go"], + importpath = "k8s.io/kubernetes/pkg/credentialprovider/secrets", + visibility = ["//visibility:public"], + deps = [ + "//pkg/credentialprovider:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/credentialprovider/secrets/secrets.go b/pkg/credentialprovider/secrets/secrets.go new file mode 100644 index 00000000000..d5397d931c2 --- /dev/null +++ b/pkg/credentialprovider/secrets/secrets.go @@ -0,0 +1,58 @@ +/* +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 secrets + +import ( + "encoding/json" + + "k8s.io/api/core/v1" + "k8s.io/kubernetes/pkg/credentialprovider" +) + +// MakeDockerKeyring inspects the passedSecrets to see if they contain any DockerConfig secrets. If they do, +// then a DockerKeyring is built based on every hit and unioned with the defaultKeyring. +// If they do not, then the default keyring is returned +func MakeDockerKeyring(passedSecrets []v1.Secret, defaultKeyring credentialprovider.DockerKeyring) (credentialprovider.DockerKeyring, error) { + passedCredentials := []credentialprovider.DockerConfig{} + for _, passedSecret := range passedSecrets { + if dockerConfigJSONBytes, dockerConfigJSONExists := passedSecret.Data[v1.DockerConfigJsonKey]; (passedSecret.Type == v1.SecretTypeDockerConfigJson) && dockerConfigJSONExists && (len(dockerConfigJSONBytes) > 0) { + dockerConfigJSON := credentialprovider.DockerConfigJson{} + if err := json.Unmarshal(dockerConfigJSONBytes, &dockerConfigJSON); err != nil { + return nil, err + } + + passedCredentials = append(passedCredentials, dockerConfigJSON.Auths) + } else if dockercfgBytes, dockercfgExists := passedSecret.Data[v1.DockerConfigKey]; (passedSecret.Type == v1.SecretTypeDockercfg) && dockercfgExists && (len(dockercfgBytes) > 0) { + dockercfg := credentialprovider.DockerConfig{} + if err := json.Unmarshal(dockercfgBytes, &dockercfg); err != nil { + return nil, err + } + + passedCredentials = append(passedCredentials, dockercfg) + } + } + + if len(passedCredentials) > 0 { + basicKeyring := &credentialprovider.BasicDockerKeyring{} + for _, currCredentials := range passedCredentials { + basicKeyring.Add(currCredentials) + } + return credentialprovider.UnionDockerKeyring{basicKeyring, defaultKeyring}, nil + } + + return defaultKeyring, nil +} diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index 7033678cc69..a80f86a756d 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -64,6 +64,8 @@ const ( // owner: @vishh // alpha: v1.6 // + // This is deprecated and will be removed in v1.11. Use DevicePlugins instead. + // // Enables support for GPUs as a schedulable resource. // Only Nvidia GPUs are supported as of v1.6. // Works only with Docker Container Runtime. @@ -98,7 +100,7 @@ const ( // the API server as the certificate approaches expiration. RotateKubeletClientCertificate utilfeature.Feature = "RotateKubeletClientCertificate" - // owner: @msau + // owner: @msau42 // alpha: v1.7 // // A new volume type that supports local disks on a node. @@ -140,12 +142,6 @@ const ( // 'MemoryPressure', 'OutOfDisk' and 'DiskPressure'. TaintNodesByCondition utilfeature.Feature = "TaintNodesByCondition" - // owner: @haibinxie - // alpha: v1.8 - // - // Implement IPVS-based in-cluster service load balancing - SupportIPVSProxyMode utilfeature.Feature = "SupportIPVSProxyMode" - // owner: @jsafrane // alpha: v1.8 // @@ -159,10 +155,65 @@ const ( CPUManager utilfeature.Feature = "CPUManager" // owner: @derekwaynecarr - // alpha: v1.8 + // beta: v1.10 // // Enable pods to consume pre-allocated huge pages of varying page sizes HugePages utilfeature.Feature = "HugePages" + + // owner @brendandburns + // alpha: v1.9 + // + // Enable nodes to exclude themselves from service load balancers + ServiceNodeExclusion utilfeature.Feature = "ServiceNodeExclusion" + + // owner: @jsafrane + // alpha: v1.9 + // + // Enable running mount utilities in containers. + MountContainers utilfeature.Feature = "MountContainers" + + // owner: @msau42 + // alpha: v1.9 + // + // Extend the default scheduler to be aware of PV topology and handle PV binding + // Before moving to beta, resolve Kubernetes issue #56180 + VolumeScheduling utilfeature.Feature = "VolumeScheduling" + + // owner: @vladimirvivien + // alpha: v1.9 + // + // Enable mount/attachment of Container Storage Interface (CSI) backed PVs + CSIPersistentVolume utilfeature.Feature = "CSIPersistentVolume" + + // owner @MrHohn + // alpha: v1.9 + // + // Support configurable pod DNS parameters. + CustomPodDNS utilfeature.Feature = "CustomPodDNS" + + // owner: @screeley44 + // alpha: v1.9 + // + // Enable Block volume support in containers. + BlockVolume utilfeature.Feature = "BlockVolume" + + // owner: @pospispa + // + // alpha: v1.9 + // Postpone deletion of a persistent volume claim in case it is used by a pod + PVCProtection utilfeature.Feature = "PVCProtection" + + // owner: @aveshagarwal + // alpha: v1.9 + // + // Enable resource limits priority function + ResourceLimitsPriorityFunction utilfeature.Feature = "ResourceLimitsPriorityFunction" + + // owner: @m1093782566 + // beta: v1.9 + // + // Implement IPVS-based in-cluster service load balancing + SupportIPVSProxyMode utilfeature.Feature = "SupportIPVSProxyMode" ) func init() { @@ -186,14 +237,23 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS RotateKubeletClientCertificate: {Default: true, PreRelease: utilfeature.Beta}, PersistentLocalVolumes: {Default: false, PreRelease: utilfeature.Alpha}, LocalStorageCapacityIsolation: {Default: false, PreRelease: utilfeature.Alpha}, - HugePages: {Default: false, PreRelease: utilfeature.Alpha}, + HugePages: {Default: true, PreRelease: utilfeature.Beta}, DebugContainers: {Default: false, PreRelease: utilfeature.Alpha}, PodPriority: {Default: false, PreRelease: utilfeature.Alpha}, EnableEquivalenceClassCache: {Default: false, PreRelease: utilfeature.Alpha}, TaintNodesByCondition: {Default: false, PreRelease: utilfeature.Alpha}, MountPropagation: {Default: false, PreRelease: utilfeature.Alpha}, ExpandPersistentVolumes: {Default: false, PreRelease: utilfeature.Alpha}, - CPUManager: {Default: false, PreRelease: utilfeature.Alpha}, + CPUManager: {Default: true, PreRelease: utilfeature.Beta}, + ServiceNodeExclusion: {Default: false, PreRelease: utilfeature.Alpha}, + MountContainers: {Default: false, PreRelease: utilfeature.Alpha}, + VolumeScheduling: {Default: false, PreRelease: utilfeature.Alpha}, + CSIPersistentVolume: {Default: false, PreRelease: utilfeature.Alpha}, + CustomPodDNS: {Default: false, PreRelease: utilfeature.Alpha}, + BlockVolume: {Default: false, PreRelease: utilfeature.Alpha}, + PVCProtection: {Default: false, PreRelease: utilfeature.Alpha}, + ResourceLimitsPriorityFunction: {Default: false, PreRelease: utilfeature.Alpha}, + SupportIPVSProxyMode: {Default: false, PreRelease: utilfeature.Beta}, // inherited features from generic apiserver, relisted here to get a conflict if it is changed // unintentionally on either side: @@ -205,6 +265,5 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS // inherited features from apiextensions-apiserver, relisted here to get a conflict if it is changed // unintentionally on either side: - apiextensionsfeatures.CustomResourceValidation: {Default: false, PreRelease: utilfeature.Alpha}, - SupportIPVSProxyMode: {Default: false, PreRelease: utilfeature.Alpha}, + apiextensionsfeatures.CustomResourceValidation: {Default: true, PreRelease: utilfeature.Beta}, } diff --git a/pkg/fieldpath/BUILD b/pkg/fieldpath/BUILD index 054686cbd5c..00cda4dc143 100644 --- a/pkg/fieldpath/BUILD +++ b/pkg/fieldpath/BUILD @@ -13,14 +13,17 @@ go_library( "fieldpath.go", ], importpath = "k8s.io/kubernetes/pkg/fieldpath", - deps = ["//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", + ], ) go_test( name = "go_default_test", srcs = ["fieldpath_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/fieldpath", - library = ":go_default_library", deps = [ "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/fieldpath/fieldpath.go b/pkg/fieldpath/fieldpath.go index caf0096ca0e..43754145892 100644 --- a/pkg/fieldpath/fieldpath.go +++ b/pkg/fieldpath/fieldpath.go @@ -21,6 +21,7 @@ import ( "strings" "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/util/validation" ) // FormatMap formats map[string]string to a string. @@ -42,6 +43,23 @@ func ExtractFieldPathAsString(obj interface{}, fieldPath string) (string, error) return "", nil } + if path, subscript, ok := SplitMaybeSubscriptedPath(fieldPath); ok { + switch path { + case "metadata.annotations": + if errs := validation.IsQualifiedName(strings.ToLower(subscript)); len(errs) != 0 { + return "", fmt.Errorf("invalid key subscript in %s: %s", fieldPath, strings.Join(errs, ";")) + } + return accessor.GetAnnotations()[subscript], nil + case "metadata.labels": + if errs := validation.IsQualifiedName(subscript); len(errs) != 0 { + return "", fmt.Errorf("invalid key subscript in %s: %s", fieldPath, strings.Join(errs, ";")) + } + return accessor.GetLabels()[subscript], nil + default: + return "", fmt.Errorf("fieldPath %q does not support subscript", fieldPath) + } + } + switch fieldPath { case "metadata.annotations": return FormatMap(accessor.GetAnnotations()), nil @@ -57,3 +75,29 @@ func ExtractFieldPathAsString(obj interface{}, fieldPath string) (string, error) return "", fmt.Errorf("unsupported fieldPath: %v", fieldPath) } + +// SplitMaybeSubscriptedPath checks whether the specified fieldPath is +// subscripted, and +// - if yes, this function splits the fieldPath into path and subscript, and +// returns (path, subscript, true). +// - if no, this function returns (fieldPath, "", false). +// +// Example inputs and outputs: +// - "metadata.annotations['myKey']" --> ("metadata.annotations", "myKey", true) +// - "metadata.annotations['a[b]c']" --> ("metadata.annotations", "a[b]c", true) +// - "metadata.labels['']" --> ("metadata.labels", "", true) +// - "metadata.labels" --> ("metadata.labels", "", false) +func SplitMaybeSubscriptedPath(fieldPath string) (string, string, bool) { + if !strings.HasSuffix(fieldPath, "']") { + return fieldPath, "", false + } + s := strings.TrimSuffix(fieldPath, "']") + parts := strings.SplitN(s, "['", 2) + if len(parts) < 2 { + return fieldPath, "", false + } + if len(parts[0]) == 0 { + return fieldPath, "", false + } + return parts[0], parts[1], true +} diff --git a/pkg/fieldpath/fieldpath_test.go b/pkg/fieldpath/fieldpath_test.go index 7e543952195..aa87f02a8e1 100644 --- a/pkg/fieldpath/fieldpath_test.go +++ b/pkg/fieldpath/fieldpath_test.go @@ -36,7 +36,6 @@ func TestExtractFieldPathAsString(t *testing.T) { name: "not an API object", fieldPath: "metadata.name", obj: "", - expectedMessageFragment: "expected struct", }, { name: "ok - namespace", @@ -88,7 +87,26 @@ func TestExtractFieldPathAsString(t *testing.T) { }, expectedValue: "builder=\"john-doe\"", }, - + { + name: "ok - annotation", + fieldPath: "metadata.annotations['spec.pod.beta.kubernetes.io/statefulset-index']", + obj: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"spec.pod.beta.kubernetes.io/statefulset-index": "1"}, + }, + }, + expectedValue: "1", + }, + { + name: "ok - annotation", + fieldPath: "metadata.annotations['Www.k8s.io/test']", + obj: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"Www.k8s.io/test": "1"}, + }, + }, + expectedValue: "1", + }, { name: "invalid expression", fieldPath: "metadata.whoops", @@ -99,6 +117,26 @@ func TestExtractFieldPathAsString(t *testing.T) { }, expectedMessageFragment: "unsupported fieldPath", }, + { + name: "invalid annotation key", + fieldPath: "metadata.annotations['invalid~key']", + obj: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"foo": "bar"}, + }, + }, + expectedMessageFragment: "invalid key subscript in metadata.annotations", + }, + { + name: "invalid label key", + fieldPath: "metadata.labels['Www.k8s.io/test']", + obj: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"foo": "bar"}, + }, + }, + expectedMessageFragment: "invalid key subscript in metadata.labels", + }, } for _, tc := range cases { @@ -111,8 +149,93 @@ func TestExtractFieldPathAsString(t *testing.T) { } else { t.Errorf("%v: unexpected error: %v", tc.name, err) } + } else if tc.expectedMessageFragment != "" { + t.Errorf("%v: expected error: %v", tc.name, tc.expectedMessageFragment) } else if e := tc.expectedValue; e != "" && e != actual { t.Errorf("%v: unexpected result; got %q, expected %q", tc.name, actual, e) } } } + +func TestSplitMaybeSubscriptedPath(t *testing.T) { + cases := []struct { + fieldPath string + expectedPath string + expectedSubscript string + expectedOK bool + }{ + { + fieldPath: "metadata.annotations['key']", + expectedPath: "metadata.annotations", + expectedSubscript: "key", + expectedOK: true, + }, + { + fieldPath: "metadata.annotations['a[b']c']", + expectedPath: "metadata.annotations", + expectedSubscript: "a[b']c", + expectedOK: true, + }, + { + fieldPath: "metadata.labels['['key']", + expectedPath: "metadata.labels", + expectedSubscript: "['key", + expectedOK: true, + }, + { + fieldPath: "metadata.labels['key']']", + expectedPath: "metadata.labels", + expectedSubscript: "key']", + expectedOK: true, + }, + { + fieldPath: "metadata.labels['']", + expectedPath: "metadata.labels", + expectedSubscript: "", + expectedOK: true, + }, + { + fieldPath: "metadata.labels[' ']", + expectedPath: "metadata.labels", + expectedSubscript: " ", + expectedOK: true, + }, + { + fieldPath: "metadata.labels[ 'key' ]", + expectedOK: false, + }, + { + fieldPath: "metadata.labels[]", + expectedOK: false, + }, + { + fieldPath: "metadata.labels[']", + expectedOK: false, + }, + { + fieldPath: "metadata.labels['key']foo", + expectedOK: false, + }, + { + fieldPath: "['key']", + expectedOK: false, + }, + { + fieldPath: "metadata.labels", + expectedOK: false, + }, + } + for _, tc := range cases { + path, subscript, ok := SplitMaybeSubscriptedPath(tc.fieldPath) + if !ok { + if tc.expectedOK { + t.Errorf("SplitMaybeSubscriptedPath(%q) expected to return (_, _, true)", tc.fieldPath) + } + continue + } + if path != tc.expectedPath || subscript != tc.expectedSubscript { + t.Errorf("SplitMaybeSubscriptedPath(%q) = (%q, %q, true), expect (%q, %q, true)", + tc.fieldPath, path, subscript, tc.expectedPath, tc.expectedSubscript) + } + } +} diff --git a/pkg/generated/bindata.go b/pkg/generated/bindata.go index b7e8c56798b..3a7f54b2aff 100644 --- a/pkg/generated/bindata.go +++ b/pkg/generated/bindata.go @@ -383,8 +383,8 @@ msgstr "" msgid "" "\n" "\t\t# Auto scale a deployment \"foo\", with the number of pods between 2 and " -"10, target CPU utilization specified so a default autoscaling policy will be " -"used:\n" +"10, no target CPU utilization specified so a default autoscaling policy will " +"be used:\n" "\t\tkubectl autoscale deployment foo --min=2 --max=10\n" "\n" "\t\t# Auto scale a replication controller \"foo\", with the number of pods " @@ -3606,8 +3606,8 @@ msgstr "" msgid "" "\n" "\t\t# Auto scale a deployment \"foo\", with the number of pods between 2 and " -"10, target CPU utilization specified so a default autoscaling policy will be " -"used:\n" +"10, no target CPU utilization specified so a default autoscaling policy will " +"be used:\n" "\t\tkubectl autoscale deployment foo --min=2 --max=10\n" "\n" "\t\t# Auto scale a replication controller \"foo\", with the number of pods " @@ -3616,8 +3616,8 @@ msgid "" msgstr "" "\n" "\t\t# Auto scale a deployment \"foo\", with the number of pods between 2 and " -"10, target CPU utilization specified so a default autoscaling policy will be " -"used:\n" +"10, no target CPU utilization specified so a default autoscaling policy will " +"be used:\n" "\t\tkubectl autoscale deployment foo --min=2 --max=10\n" "\n" "\t\t# Auto scale a replication controller \"foo\", with the number of pods " @@ -7083,8 +7083,8 @@ msgstr "" msgid "" "\n" "\t\t# Auto scale a deployment \"foo\", with the number of pods between 2 and " -"10, target CPU utilization specified so a default autoscaling policy will be " -"used:\n" +"10, no target CPU utilization specified so a default autoscaling policy will " +"be used:\n" "\t\tkubectl autoscale deployment foo --min=2 --max=10\n" "\n" "\t\t# Auto scale a replication controller \"foo\", with the number of pods " @@ -7093,8 +7093,8 @@ msgid "" msgstr "" "\n" "\t\t# Auto scale a deployment \"foo\", with the number of pods between 2 and " -"10, target CPU utilization specified so a default autoscaling policy will be " -"used:\n" +"10, no target CPU utilization specified so a default autoscaling policy will " +"be used:\n" "\t\tkubectl autoscale deployment foo --min=2 --max=10\n" "\n" "\t\t# Auto scale a replication controller \"foo\", with the number of pods " @@ -10690,8 +10690,8 @@ msgstr "" msgid "" "\n" "\t\t# Auto scale a deployment \"foo\", with the number of pods between 2 and " -"10, target CPU utilization specified so a default autoscaling policy will be " -"used:\n" +"10, no target CPU utilization specified so a default autoscaling policy will " +"be used:\n" "\t\tkubectl autoscale deployment foo --min=2 --max=10\n" "\n" "\t\t# Auto scale a replication controller \"foo\", with the number of pods " @@ -14096,8 +14096,8 @@ msgstr "" msgid "" "\n" "\t\t# Auto scale a deployment \"foo\", with the number of pods between 2 and " -"10, target CPU utilization specified so a default autoscaling policy will be " -"used:\n" +"10, no target CPU utilization specified so a default autoscaling policy will " +"be used:\n" "\t\tkubectl autoscale deployment foo --min=2 --max=10\n" "\n" "\t\t# Auto scale a replication controller \"foo\", with the number of pods " @@ -16131,7 +16131,7 @@ func translationsKubectlTemplatePot() (*asset, error) { return a, nil } -var _translationsKubectlZh_cnLc_messagesK8sMo = []byte("\xde\x12\x04\x95\x00\x00\x00\x00\x11\x00\x00\x00\x1c\x00\x00\x00\xa4\x00\x00\x00\x17\x00\x00\x00,\x01\x00\x00\x00\x00\x00\x00\x88\x01\x00\x008\x00\x00\x00\x89\x01\x00\x000\x00\x00\x00\xc2\x01\x00\x000\x00\x00\x00\xf3\x01\x00\x00\x1d\x00\x00\x00$\x02\x00\x00*\x00\x00\x00B\x02\x00\x00A\x00\x00\x00m\x02\x00\x00\x1c\x00\x00\x00\xaf\x02\x00\x00\x17\x00\x00\x00\xcc\x02\x00\x00\"\x00\x00\x00\xe4\x02\x00\x00\"\x00\x00\x00\a\x03\x00\x00\x1f\x00\x00\x00*\x03\x00\x00-\x00\x00\x00J\x03\x00\x00-\x00\x00\x00x\x03\x00\x00/\x00\x00\x00\xa6\x03\x00\x00$\x00\x00\x00\xd6\x03\x00\x00\xc5\x00\x00\x00\xfb\x03\x00\x00\xa3\x01\x00\x00\xc1\x04\x00\x00@\x00\x00\x00e\x06\x00\x00*\x00\x00\x00\xa6\x06\x00\x00-\x00\x00\x00\xd1\x06\x00\x00\x1e\x00\x00\x00\xff\x06\x00\x00*\x00\x00\x00\x1e\a\x00\x00E\x00\x00\x00I\a\x00\x00\x18\x00\x00\x00\x8f\a\x00\x00\x18\x00\x00\x00\xa8\a\x00\x000\x00\x00\x00\xc1\a\x00\x003\x00\x00\x00\xf2\a\x00\x000\x00\x00\x00&\b\x00\x00-\x00\x00\x00W\b\x00\x00-\x00\x00\x00\x85\b\x00\x003\x00\x00\x00\xb3\b\x00\x00\x1b\x00\x00\x00\xe7\b\x00\x00\x93\x00\x00\x00\x03\t\x00\x00\x01\x00\x00\x00\n\x00\x00\x00\v\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\t\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\a\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x00\f\x00\x00\x00\x05\x00\x00\x00\r\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00Apply a configuration to a resource by filename or stdin\x00Delete the specified cluster from the kubeconfig\x00Delete the specified context from the kubeconfig\x00Describe one or many contexts\x00Display clusters defined in the kubeconfig\x00Display merged kubeconfig settings or a specified kubeconfig file\x00Displays the current-context\x00Modify kubeconfig files\x00Sets a cluster entry in kubeconfig\x00Sets a context entry in kubeconfig\x00Sets a user entry in kubeconfig\x00Sets an individual value in a kubeconfig file\x00Sets the current-context in a kubeconfig file\x00Unsets an individual value in a kubeconfig file\x00Update the annotations on a resource\x00watch is only supported on individual resources and resource collections - %d resources were found\x00watch is only supported on individual resources and resource collections - %d resources were found\x00Project-Id-Version: gettext-go-examples-hello\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2013-12-12 20:03+0000\nPO-Revision-Date: 2017-05-19 16:16+0800\nLast-Translator: Shiyang Wang \nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nX-Generator: Poedit 1.8.8\nX-Poedit-SourceCharset: UTF-8\nLanguage-Team: \nPlural-Forms: nplurals=2; plural=(n > 1);\nLanguage: zh\n\x00\u901a\u8fc7\u6587\u4ef6\u540d\u6216\u6807\u51c6\u8f93\u5165\u6d41(stdin)\u5bf9\u8d44\u6e90\u8fdb\u884c\u914d\u7f6e\u3002\x00\u5220\u9664 kubeconfig \u6587\u4ef6\u4e2d\u6307\u5b9a\u7684\u96c6\u7fa4\x00\u5220\u9664 kubeconfig \u6587\u4ef6\u4e2d\u6307\u5b9a\u7684\u4e0a\u4e0b\u6587\x00\u63cf\u8ff0\u4e00\u4e2a\u6216\u591a\u4e2a\u4e0a\u4e0b\u6587\x00\u663e\u793a kubeconfig \u6587\u4ef6\u4e2d\u5b9a\u4e49\u7684\u96c6\u7fa4\x00\u663e\u793a\u5408\u5e76\u7684 kubeconfig \u914d\u7f6e\u6216\u4e00\u4e2a\u6307\u5b9a\u7684 kubeconfig \u6587\u4ef6\x00\u663e\u793a\u5f53\u524d\u7684\u4e0a\u4e0b\u6587\x00\u4fee\u6539 kubeconfig \u6587\u4ef6\x00\u8bbe\u7f6e kubeconfig \u6587\u4ef6\u4e2d\u7684\u4e00\u4e2a\u96c6\u7fa4\u6761\u76ee\x00\u8bbe\u7f6e kubeconfig \u6587\u4ef6\u4e2d\u7684\u4e00\u4e2a\u4e0a\u4e0b\u6587\u6761\u76ee\x00\u8bbe\u7f6e kubeconfig \u6587\u4ef6\u4e2d\u7684\u4e00\u4e2a\u7528\u6237\u6761\u76ee\x00\u8bbe\u7f6e kubeconfig \u6587\u4ef6\u4e2d\u7684\u4e00\u4e2a\u5355\u4e2a\u503c\x00\u8bbe\u7f6e kubeconfig \u6587\u4ef6\u4e2d\u7684\u5f53\u524d\u4e0a\u4e0b\u6587\x00\u53d6\u6d88\u8bbe\u7f6e kubeconfig \u6587\u4ef6\u4e2d\u7684\u4e00\u4e2a\u5355\u4e2a\u503c\x00\u66f4\u65b0\u4e00\u4e2a\u8d44\u6e90\u7684\u6ce8\u89e3\x00watch \u4ec5\u652f\u6301\u5355\u72ec\u7684\u8d44\u6e90\u6216\u8005\u8d44\u6e90\u96c6\u5408 - \u627e\u5230\u4e86 %d \u4e2a\u8d44\u6e90\x00watch \u4ec5\u652f\u6301\u5355\u72ec\u7684\u8d44\u6e90\u6216\u8005\u8d44\u6e90\u96c6\u5408 - \u627e\u5230\u4e86 %d \u4e2a\u8d44\u6e90\x00") +var _translationsKubectlZh_cnLc_messagesK8sMo = []byte("\xde\x12\x04\x95\x00\x00\x00\x00\xea\x00\x00\x00\x1c\x00\x00\x00l\a\x00\x009\x01\x00\x00\xbc\x0e\x00\x00\x00\x00\x00\x00\xa0\x13\x00\x00\xdc\x00\x00\x00\xa1\x13\x00\x00\xb6\x00\x00\x00~\x14\x00\x00\v\x02\x00\x005\x15\x00\x00\x1f\x01\x00\x00A\x17\x00\x00z\x00\x00\x00a\x18\x00\x00_\x02\x00\x00\xdc\x18\x00\x00|\x01\x00\x00<\x1b\x00\x00\x8f\x01\x00\x00\xb9\x1c\x00\x00k\x01\x00\x00I\x1e\x00\x00>\x01\x00\x00\xb5\x1f\x00\x00\x03\x02\x00\x00\xf4 \x00\x00o\x01\x00\x00\xf8\"\x00\x00H\x05\x00\x00h$\x00\x00g\x02\x00\x00\xb1)\x00\x00\x1b\x02\x00\x00\x19,\x00\x00u\x01\x00\x005.\x00\x00\xa8\x01\x00\x00\xab/\x00\x00\xd4\x01\x00\x00T1\x00\x00\x02\x02\x00\x00)3\x00\x00\xb4\x00\x00\x00,5\x00\x00\xb7\x02\x00\x00\xe15\x00\x00\x92\x03\x00\x00\x998\x00\x00\xbf\x01\x00\x00,<\x00\x00=\x00\x00\x00\xec=\x00\x00;\x00\x00\x00*>\x00\x00\xcd\x02\x00\x00f>\x00\x00<\x00\x00\x004A\x00\x00P\x00\x00\x00qA\x00\x00S\x00\x00\x00\xc2A\x00\x00<\x00\x00\x00\x16B\x00\x00\xac\x01\x00\x00SB\x00\x00\x13\x03\x00\x00\x00D\x00\x00\xea\x01\x00\x00\x14G\x00\x00\xfa\x01\x00\x00\xffH\x00\x00\xda\x01\x00\x00\xfaJ\x00\x00c\x01\x00\x00\xd5L\x00\x00T\x01\x00\x009N\x00\x00\xba\x06\x00\x00\x8eO\x00\x00\xf9\x01\x00\x00IV\x00\x00\xe0\x02\x00\x00CX\x00\x00\x02\x03\x00\x00$[\x00\x00\xfb\x00\x00\x00'^\x00\x00\xa5\x01\x00\x00#_\x00\x00\xb4\x01\x00\x00\xc9`\x00\x00\x18\x00\x00\x00~b\x00\x00<\x00\x00\x00\x97b\x00\x00=\x00\x00\x00\xd4b\x00\x00\xc6\x00\x00\x00\x12c\x00\x00g\x02\x00\x00\xd9c\x00\x00.\x00\x00\x00Af\x00\x001\x03\x00\x00pf\x00\x00g\x00\x00\x00\xa2i\x00\x00Q\x00\x00\x00\nj\x00\x00R\x00\x00\x00\\j\x00\x00\"\x00\x00\x00\xafj\x00\x00X\x02\x00\x00\xd2j\x00\x004\x00\x00\x00+m\x00\x00}\x00\x00\x00`m\x00\x00k\x01\x00\x00\xdem\x00\x00\x81\a\x00\x00Jo\x00\x00f\x01\x00\x00\xccv\x00\x00\x85\x00\x00\x003x\x00\x00\xea\x00\x00\x00\xb9x\x00\x00\xd9\x00\x00\x00\xa4y\x00\x00\n\x05\x00\x00~z\x00\x00\x10\x05\x00\x00\x89\u007f\x00\x00\x1c\x00\x00\x00\x9a\x84\x00\x00\x1e\x00\x00\x00\xb7\x84\x00\x00\x99\x02\x00\x00\u0584\x00\x00\xbc\x01\x00\x00p\x87\x00\x00\x9c\x01\x00\x00-\x89\x00\x00q\x01\x00\x00\u028a\x00\x00\x05\x01\x00\x00<\x8c\x00\x00\xdf\x01\x00\x00B\x8d\x00\x00\x1c\x01\x00\x00\"\x8f\x00\x00\xc1\x01\x00\x00?\x90\x00\x00\x1b\x02\x00\x00\x01\x92\x00\x00\xc0\x00\x00\x00\x1d\x94\x00\x00\xd5\x02\x00\x00\u0794\x00\x00\x9d\x00\x00\x00\xb4\x97\x00\x00X\x00\x00\x00R\x98\x00\x00%\x02\x00\x00\xab\x98\x00\x00o\x00\x00\x00\u045a\x00\x00u\x00\x00\x00A\x9b\x00\x00\x01\x01\x00\x00\xb7\x9b\x00\x00v\x00\x00\x00\xb9\x9c\x00\x00t\x00\x00\x000\x9d\x00\x00\xef\x00\x00\x00\xa5\x9d\x00\x00}\x00\x00\x00\x95\x9e\x00\x00j\x00\x00\x00\x13\x9f\x00\x00\xc4\x01\x00\x00~\x9f\x00\x00\xf7\x03\x00\x00C\xa1\x00\x00;\x00\x00\x00;\xa5\x00\x008\x00\x00\x00w\xa5\x00\x001\x00\x00\x00\xb0\xa5\x00\x007\x00\x00\x00\xe2\xa5\x00\x00u\x02\x00\x00\x1a\xa6\x00\x00\xb0\x00\x00\x00\x90\xa8\x00\x00[\x00\x00\x00A\xa9\x00\x00J\x00\x00\x00\x9d\xa9\x00\x00a\x00\x00\x00\xe8\xa9\x00\x00\xbd\x00\x00\x00J\xaa\x00\x009\x00\x00\x00\b\xab\x00\x00\xc5\x00\x00\x00B\xab\x00\x00\xae\x00\x00\x00\b\xac\x00\x00\xd6\x00\x00\x00\xb7\xac\x00\x008\x00\x00\x00\x8e\xad\x00\x00%\x00\x00\x00\u01ed\x00\x00W\x00\x00\x00\xed\xad\x00\x00\x1d\x00\x00\x00E\xae\x00\x00=\x00\x00\x00c\xae\x00\x00u\x00\x00\x00\xa1\xae\x00\x004\x00\x00\x00\x17\xaf\x00\x00-\x00\x00\x00L\xaf\x00\x00\xa3\x00\x00\x00z\xaf\x00\x003\x00\x00\x00\x1e\xb0\x00\x002\x00\x00\x00R\xb0\x00\x008\x00\x00\x00\x85\xb0\x00\x00\x1e\x00\x00\x00\xbe\xb0\x00\x00\x1a\x00\x00\x00\u0770\x00\x009\x00\x00\x00\xf8\xb0\x00\x00\x13\x00\x00\x002\xb1\x00\x00\x1b\x00\x00\x00F\xb1\x00\x00@\x00\x00\x00b\xb1\x00\x00,\x00\x00\x00\xa3\xb1\x00\x00*\x00\x00\x00\u0431\x00\x007\x00\x00\x00\xfb\xb1\x00\x00'\x00\x00\x003\xb2\x00\x00&\x00\x00\x00[\xb2\x00\x00.\x00\x00\x00\x82\xb2\x00\x00=\x00\x00\x00\xb1\xb2\x00\x00*\x00\x00\x00\xef\xb2\x00\x000\x00\x00\x00\x1a\xb3\x00\x00,\x00\x00\x00K\xb3\x00\x00\x1f\x00\x00\x00x\xb3\x00\x00]\x00\x00\x00\x98\xb3\x00\x000\x00\x00\x00\xf6\xb3\x00\x000\x00\x00\x00'\xb4\x00\x00\"\x00\x00\x00X\xb4\x00\x00?\x00\x00\x00{\xb4\x00\x00\x1d\x00\x00\x00\xbb\xb4\x00\x00,\x00\x00\x00\u0674\x00\x00+\x00\x00\x00\x06\xb5\x00\x00$\x00\x00\x002\xb5\x00\x00\x14\x00\x00\x00W\xb5\x00\x00*\x00\x00\x00l\xb5\x00\x00A\x00\x00\x00\x97\xb5\x00\x00\x1d\x00\x00\x00\u0675\x00\x00\x1c\x00\x00\x00\xf7\xb5\x00\x00\x1a\x00\x00\x00\x14\xb6\x00\x00)\x00\x00\x00/\xb6\x00\x006\x00\x00\x00Y\xb6\x00\x00\x1d\x00\x00\x00\x90\xb6\x00\x00\x19\x00\x00\x00\xae\xb6\x00\x00 \x00\x00\x00\u0236\x00\x00v\x00\x00\x00\xe9\xb6\x00\x00(\x00\x00\x00`\xb7\x00\x00\x16\x00\x00\x00\x89\xb7\x00\x00p\x00\x00\x00\xa0\xb7\x00\x00`\x00\x00\x00\x11\xb8\x00\x00\x9b\x00\x00\x00r\xb8\x00\x00\x97\x00\x00\x00\x0e\xb9\x00\x00\xa8\x00\x00\x00\xa6\xb9\x00\x00\x1b\x00\x00\x00O\xba\x00\x00\x18\x00\x00\x00k\xba\x00\x00\x1a\x00\x00\x00\x84\xba\x00\x00$\x00\x00\x00\x9f\xba\x00\x00\x1d\x00\x00\x00\u013a\x00\x00\x17\x00\x00\x00\xe2\xba\x00\x00a\x00\x00\x00\xfa\xba\x00\x00s\x00\x00\x00\\\xbb\x00\x00B\x00\x00\x00\u043b\x00\x00Y\x00\x00\x00\x13\xbc\x00\x00+\x00\x00\x00m\xbc\x00\x00+\x00\x00\x00\x99\xbc\x00\x006\x00\x00\x00\u017c\x00\x00;\x00\x00\x00\xfc\xbc\x00\x00q\x00\x00\x008\xbd\x00\x00/\x00\x00\x00\xaa\xbd\x00\x001\x00\x00\x00\u06bd\x00\x00'\x00\x00\x00\f\xbe\x00\x00'\x00\x00\x004\xbe\x00\x00\x18\x00\x00\x00\\\xbe\x00\x00&\x00\x00\x00u\xbe\x00\x00%\x00\x00\x00\x9c\xbe\x00\x00(\x00\x00\x00\u00be\x00\x00#\x00\x00\x00\xeb\xbe\x00\x00K\x00\x00\x00\x0f\xbf\x00\x00 \x00\x00\x00[\xbf\x00\x00_\x00\x00\x00|\xbf\x00\x00\x1e\x00\x00\x00\u073f\x00\x00\"\x00\x00\x00\xfb\xbf\x00\x00\"\x00\x00\x00\x1e\xc0\x00\x00\x1f\x00\x00\x00A\xc0\x00\x00-\x00\x00\x00a\xc0\x00\x00-\x00\x00\x00\x8f\xc0\x00\x009\x00\x00\x00\xbd\xc0\x00\x00\x1e\x00\x00\x00\xf7\xc0\x00\x00\x19\x00\x00\x00\x16\xc1\x00\x00c\x00\x00\x000\xc1\x00\x00#\x00\x00\x00\x94\xc1\x00\x00\x82\x00\x00\x00\xb8\xc1\x00\x00\x94\x00\x00\x00;\xc2\x00\x00H\x00\x00\x00\xd0\xc2\x00\x00&\x00\x00\x00\x19\xc3\x00\x00e\x00\x00\x00@\xc3\x00\x00z\x00\x00\x00\xa6\xc3\x00\x00J\x00\x00\x00!\xc4\x00\x00\xe5\x00\x00\x00l\xc4\x00\x00W\x00\x00\x00R\xc5\x00\x00E\x00\x00\x00\xaa\xc5\x00\x00a\x00\x00\x00\xf0\xc5\x00\x00v\x00\x00\x00R\xc6\x00\x00\xcb\x00\x00\x00\xc9\xc6\x00\x00\xcf\x00\x00\x00\x95\xc7\x00\x00\x1e\x01\x00\x00e\xc8\x00\x00\x1c\x00\x00\x00\x84\xc9\x00\x00T\x00\x00\x00\xa1\xc9\x00\x00\x17\x00\x00\x00\xf6\xc9\x00\x00/\x00\x00\x00\x0e\xca\x00\x009\x00\x00\x00>\xca\x00\x00\x1e\x00\x00\x00x\xca\x00\x00=\x00\x00\x00\x97\xca\x00\x00$\x00\x00\x00\xd5\xca\x00\x00\x1f\x00\x00\x00\xfa\xca\x00\x00&\x00\x00\x00\x1a\xcb\x00\x00+\x00\x00\x00A\xcb\x00\x00G\x00\x00\x00m\xcb\x00\x00\x14\x00\x00\x00\xb5\xcb\x00\x00r\x00\x00\x00\xca\xcb\x00\x00\x13\x00\x00\x00=\xcc\x00\x00\x18\x00\x00\x00Q\xcc\x00\x00/\x00\x00\x00j\xcc\x00\x00\xa6\x01\x00\x00\x9a\xcc\x00\x00\xdd\x00\x00\x00A\xce\x00\x00\xb7\x00\x00\x00\x1f\xcf\x00\x00#\x02\x00\x00\xd7\xcf\x00\x00\"\x01\x00\x00\xfb\xd1\x00\x00\x80\x00\x00\x00\x1e\xd3\x00\x000\x02\x00\x00\x9f\xd3\x00\x00Z\x01\x00\x00\xd0\xd5\x00\x00u\x01\x00\x00+\xd7\x00\x00w\x01\x00\x00\xa1\xd8\x00\x00F\x01\x00\x00\x19\xda\x00\x00\xe6\x01\x00\x00`\xdb\x00\x00u\x01\x00\x00G\xdd\x00\x009\x05\x00\x00\xbd\xde\x00\x00W\x02\x00\x00\xf7\xe3\x00\x00#\x02\x00\x00O\xe6\x00\x00r\x01\x00\x00s\xe8\x00\x00\xc2\x01\x00\x00\xe6\xe9\x00\x00\xdf\x01\x00\x00\xa9\xeb\x00\x00 \x02\x00\x00\x89\xed\x00\x00\x8b\x00\x00\x00\xaa\xef\x00\x00\x95\x02\x00\x006\xf0\x00\x00{\x03\x00\x00\xcc\xf2\x00\x00\xd0\x01\x00\x00H\xf6\x00\x00@\x00\x00\x00\x19\xf8\x00\x00>\x00\x00\x00Z\xf8\x00\x00\xd4\x02\x00\x00\x99\xf8\x00\x008\x00\x00\x00n\xfb\x00\x00H\x00\x00\x00\xa7\xfb\x00\x00<\x00\x00\x00\xf0\xfb\x00\x006\x00\x00\x00-\xfc\x00\x00\xbe\x01\x00\x00d\xfc\x00\x00\x13\x03\x00\x00#\xfe\x00\x00\x02\x02\x00\x007\x01\x01\x00?\x02\x00\x00:\x03\x01\x00\xcd\x01\x00\x00z\x05\x01\x00e\x01\x00\x00H\a\x01\x00T\x01\x00\x00\xae\b\x01\x00\xba\x06\x00\x00\x03\n\x01\x00\xf9\x01\x00\x00\xbe\x10\x01\x00\xe0\x02\x00\x00\xb8\x12\x01\x00\x02\x03\x00\x00\x99\x15\x01\x00\xfb\x00\x00\x00\x9c\x18\x01\x00\xaa\x01\x00\x00\x98\x19\x01\x00\x83\x01\x00\x00C\x1b\x01\x00\x1c\x00\x00\x00\xc7\x1c\x01\x00=\x00\x00\x00\xe4\x1c\x01\x00A\x00\x00\x00\"\x1d\x01\x00\xc4\x00\x00\x00d\x1d\x01\x00g\x02\x00\x00)\x1e\x01\x00*\x00\x00\x00\x91 \x01\x001\x03\x00\x00\xbc \x01\x00g\x00\x00\x00\xee#\x01\x00h\x00\x00\x00V$\x01\x00R\x00\x00\x00\xbf$\x01\x00\x1e\x00\x00\x00\x12%\x01\x00X\x02\x00\x001%\x01\x00/\x00\x00\x00\x8a'\x01\x00}\x00\x00\x00\xba'\x01\x00k\x01\x00\x008(\x01\x00\x81\a\x00\x00\xa4)\x01\x00f\x01\x00\x00&1\x01\x00\x80\x00\x00\x00\x8d2\x01\x00\xe3\x00\x00\x00\x0e3\x01\x00\xd4\x00\x00\x00\xf23\x01\x00\x05\x05\x00\x00\xc74\x01\x00\x86\x04\x00\x00\xcd9\x01\x00\x1f\x00\x00\x00T>\x01\x00!\x00\x00\x00t>\x01\x00\x99\x02\x00\x00\x96>\x01\x00\xb6\x01\x00\x000A\x01\x00\x9c\x01\x00\x00\xe7B\x01\x00q\x01\x00\x00\x84D\x01\x00\x05\x01\x00\x00\xf6E\x01\x00\xdf\x01\x00\x00\xfcF\x01\x00\x1c\x01\x00\x00\xdcH\x01\x00\xc1\x01\x00\x00\xf9I\x01\x00 \x02\x00\x00\xbbK\x01\x00\xc0\x00\x00\x00\xdcM\x01\x00\xe8\x02\x00\x00\x9dN\x01\x00\x94\x00\x00\x00\x86Q\x01\x00_\x00\x00\x00\x1bR\x01\x00%\x02\x00\x00{R\x01\x00o\x00\x00\x00\xa1T\x01\x00u\x00\x00\x00\x11U\x01\x00\x01\x01\x00\x00\x87U\x01\x00v\x00\x00\x00\x89V\x01\x00{\x00\x00\x00\x00W\x01\x00\x00\x01\x00\x00|W\x01\x00\x80\x00\x00\x00}X\x01\x00q\x00\x00\x00\xfeX\x01\x00\xc2\x01\x00\x00pY\x01\x00\t\x04\x00\x003[\x01\x00B\x00\x00\x00=_\x01\x00?\x00\x00\x00\x80_\x01\x008\x00\x00\x00\xc0_\x01\x00>\x00\x00\x00\xf9_\x01\x00u\x02\x00\x008`\x01\x00\xb0\x00\x00\x00\xaeb\x01\x00[\x00\x00\x00_c\x01\x00J\x00\x00\x00\xbbc\x01\x00a\x00\x00\x00\x06d\x01\x00\xbd\x00\x00\x00hd\x01\x009\x00\x00\x00&e\x01\x00\xc5\x00\x00\x00`e\x01\x00\xae\x00\x00\x00&f\x01\x00\xd6\x00\x00\x00\xd5f\x01\x00=\x00\x00\x00\xacg\x01\x00\x1e\x00\x00\x00\xeag\x01\x00W\x00\x00\x00\th\x01\x00&\x00\x00\x00ah\x01\x00W\x00\x00\x00\x88h\x01\x00u\x00\x00\x00\xe0h\x01\x00+\x00\x00\x00Vi\x01\x00$\x00\x00\x00\x82i\x01\x00\xa3\x00\x00\x00\xa7i\x01\x00,\x00\x00\x00Kj\x01\x00X\x00\x00\x00xj\x01\x00>\x00\x00\x00\xd1j\x01\x00\"\x00\x00\x00\x10k\x01\x00\x1e\x00\x00\x003k\x01\x00B\x00\x00\x00Rk\x01\x00\x17\x00\x00\x00\x95k\x01\x00\x1f\x00\x00\x00\xadk\x01\x00E\x00\x00\x00\xcdk\x01\x00'\x00\x00\x00\x13l\x01\x00%\x00\x00\x00;l\x01\x002\x00\x00\x00al\x01\x00\"\x00\x00\x00\x94l\x01\x00=\x00\x00\x00\xb7l\x01\x000\x00\x00\x00\xf5l\x01\x00B\x00\x00\x00&m\x01\x00.\x00\x00\x00im\x01\x00+\x00\x00\x00\x98m\x01\x000\x00\x00\x00\xc4m\x01\x00\x1f\x00\x00\x00\xf5m\x01\x00]\x00\x00\x00\x15n\x01\x00*\x00\x00\x00sn\x01\x00,\x00\x00\x00\x9en\x01\x00\x1e\x00\x00\x00\xcbn\x01\x00?\x00\x00\x00\xean\x01\x00\x1e\x00\x00\x00*o\x01\x00-\x00\x00\x00Io\x01\x00,\x00\x00\x00wo\x01\x00$\x00\x00\x00\xa4o\x01\x00\x12\x00\x00\x00\xc9o\x01\x00*\x00\x00\x00\xdco\x01\x00E\x00\x00\x00\ap\x01\x00\x1f\x00\x00\x00Mp\x01\x00\x16\x00\x00\x00mp\x01\x00\x15\x00\x00\x00\x84p\x01\x00)\x00\x00\x00\x9ap\x01\x006\x00\x00\x00\xc4p\x01\x00!\x00\x00\x00\xfbp\x01\x00\x19\x00\x00\x00\x1dq\x01\x00)\x00\x00\x007q\x01\x00v\x00\x00\x00aq\x01\x00(\x00\x00\x00\xd8q\x01\x00\x16\x00\x00\x00\x01r\x01\x00p\x00\x00\x00\x18r\x01\x00`\x00\x00\x00\x89r\x01\x00\x9b\x00\x00\x00\xear\x01\x00\x97\x00\x00\x00\x86s\x01\x00\xa8\x00\x00\x00\x1et\x01\x00#\x00\x00\x00\xc7t\x01\x00\x1b\x00\x00\x00\xebt\x01\x00\x1d\x00\x00\x00\au\x01\x00(\x00\x00\x00%u\x01\x00\x1a\x00\x00\x00Nu\x01\x00\x18\x00\x00\x00iu\x01\x00a\x00\x00\x00\x82u\x01\x00s\x00\x00\x00\xe4u\x01\x00B\x00\x00\x00Xv\x01\x00Y\x00\x00\x00\x9bv\x01\x00+\x00\x00\x00\xf5v\x01\x00+\x00\x00\x00!w\x01\x006\x00\x00\x00Mw\x01\x005\x00\x00\x00\x84w\x01\x00q\x00\x00\x00\xbaw\x01\x00(\x00\x00\x00,x\x01\x00!\x00\x00\x00Ux\x01\x00 \x00\x00\x00wx\x01\x00.\x00\x00\x00\x98x\x01\x00\x1e\x00\x00\x00\xc7x\x01\x00$\x00\x00\x00\xe6x\x01\x00'\x00\x00\x00\vy\x01\x00,\x00\x00\x003y\x01\x00#\x00\x00\x00`y\x01\x00\\\x00\x00\x00\x84y\x01\x00'\x00\x00\x00\xe1y\x01\x00_\x00\x00\x00\tz\x01\x00\x1c\x00\x00\x00iz\x01\x000\x00\x00\x00\x86z\x01\x003\x00\x00\x00\xb7z\x01\x000\x00\x00\x00\xebz\x01\x00-\x00\x00\x00\x1c{\x01\x00-\x00\x00\x00J{\x01\x00=\x00\x00\x00x{\x01\x00\x18\x00\x00\x00\xb6{\x01\x00\x19\x00\x00\x00\xcf{\x01\x00p\x00\x00\x00\xe9{\x01\x00\x1f\x00\x00\x00Z|\x01\x00o\x00\x00\x00z|\x01\x00x\x00\x00\x00\xea|\x01\x009\x00\x00\x00c}\x01\x00\x1f\x00\x00\x00\x9d}\x01\x00Z\x00\x00\x00\xbd}\x01\x00v\x00\x00\x00\x18~\x01\x00=\x00\x00\x00\x8f~\x01\x00\xe1\x00\x00\x00\xcd~\x01\x00[\x00\x00\x00\xaf\u007f\x01\x00N\x00\x00\x00\v\x80\x01\x00R\x00\x00\x00Z\x80\x01\x00v\x00\x00\x00\xad\x80\x01\x00\xcb\x00\x00\x00$\x81\x01\x00\xaa\x00\x00\x00\xf0\x81\x01\x00G\x01\x00\x00\x9b\x82\x01\x00\x1a\x00\x00\x00\xe3\x83\x01\x00Y\x00\x00\x00\xfe\x83\x01\x00\x1a\x00\x00\x00X\x84\x01\x003\x00\x00\x00s\x84\x01\x00;\x00\x00\x00\xa7\x84\x01\x00#\x00\x00\x00\xe3\x84\x01\x00=\x00\x00\x00\a\x85\x01\x00\x1b\x00\x00\x00E\x85\x01\x00\"\x00\x00\x00a\x85\x01\x00+\x00\x00\x00\x84\x85\x01\x00+\x00\x00\x00\xb0\x85\x01\x00J\x00\x00\x00\u0705\x01\x00\x15\x00\x00\x00'\x86\x01\x00f\x00\x00\x00=\x86\x01\x00\x13\x00\x00\x00\xa4\x86\x01\x00\x15\x00\x00\x00\xb8\x86\x01\x00(\x00\x00\x00\u0386\x01\x00\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\x00E\x00\x00\x00\xc3\x00\x00\x00\x0e\x00\x00\x00\xc2\x00\x00\x00\x00\x00\x00\x00,\x00\x00\x00\x00\x00\x00\x00\x85\x00\x00\x00\xea\x00\x00\x00b\x00\x00\x00\x00\x00\x00\x000\x00\x00\x00n\x00\x00\x00|\x00\x00\x00\x00\x00\x00\x00I\x00\x00\x00\x00\x00\x00\x00\xd7\x00\x00\x00\x97\x00\x00\x00T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1b\x00\x00\x00\x16\x00\x00\x00t\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc8\x00\x00\x00\xb6\x00\x00\x00\xd6\x00\x00\x00)\x00\x00\x00\x98\x00\x00\x00\x00\x00\x00\x00\x1e\x00\x00\x00\x83\x00\x00\x00\x9b\x00\x00\x00\xe5\x00\x00\x00\x9c\x00\x00\x00\xc4\x00\x00\x00\xd8\x00\x00\x00\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\xcc\x00\x00\x00\xca\x00\x00\x00x\x00\x00\x00\x96\x00\x00\x00\xb9\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17\x00\x00\x00\x00\x00\x00\x00B\x00\x00\x00\x92\x00\x00\x00\xac\x00\x00\x00\xe0\x00\x00\x00\xa5\x00\x00\x00\xcf\x00\x00\x00q\x00\x00\x00*\x00\x00\x005\x00\x00\x00\x00\x00\x00\x00\xa4\x00\x00\x00\u007f\x00\x00\x00\x00\x00\x00\x00g\x00\x00\x00\x9f\x00\x00\x00\x00\x00\x00\x00\xd0\x00\x00\x00\xdd\x00\x00\x00:\x00\x00\x00\x00\x00\x00\x00\xe8\x00\x00\x00\xe6\x00\x00\x00F\x00\x00\x00\x00\x00\x00\x00y\x00\x00\x00.\x00\x00\x00U\x00\x00\x00_\x00\x00\x00\xe2\x00\x00\x00 \x00\x00\x00}\x00\x00\x00\x00\x00\x00\x00\xe1\x00\x00\x00\xd2\x00\x00\x00\x87\x00\x00\x00k\x00\x00\x00r\x00\x00\x00f\x00\x00\x00\x05\x00\x00\x00\xc5\x00\x00\x00\"\x00\x00\x00\x9e\x00\x00\x00\x00\x00\x00\x00\xb0\x00\x00\x00\x00\x00\x00\x00\xc1\x00\x00\x00\x12\x00\x00\x00R\x00\x00\x00\x00\x00\x00\x00#\x00\x00\x00\xc0\x00\x00\x00\xb4\x00\x00\x00W\x00\x00\x00l\x00\x00\x00\t\x00\x00\x00w\x00\x00\x00\xb7\x00\x00\x00\xbc\x00\x00\x00j\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00;\x00\x00\x00D\x00\x00\x00\xbe\x00\x00\x00\xbb\x00\x00\x00\x00\x00\x00\x009\x00\x00\x00\x81\x00\x00\x00\x80\x00\x00\x00%\x00\x00\x00\xdf\x00\x00\x00\x00\x00\x00\x00Z\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x04\x00\x00\x00=\x00\x00\x00H\x00\x00\x00\x93\x00\x00\x00\x8e\x00\x00\x00\xcd\x00\x00\x00>\x00\x00\x00X\x00\x00\x00\xd9\x00\x00\x00\x14\x00\x00\x00\x00\x00\x00\x00&\x00\x00\x003\x00\x00\x00\xcb\x00\x00\x00\v\x00\x00\x004\x00\x00\x00'\x00\x00\x00\x00\x00\x00\x00\xba\x00\x00\x00\x00\x00\x00\x00\xa8\x00\x00\x00\x9d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00N\x00\x00\x00\x1f\x00\x00\x00(\x00\x00\x00\xce\x00\x00\x00\x00\x00\x00\x00\x1a\x00\x00\x00Y\x00\x00\x00!\x00\x00\x00\x00\x00\x00\x00u\x00\x00\x00\\\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x00\x00\x00i\x00\x00\x007\x00\x00\x00\xa2\x00\x00\x00p\x00\x00\x00s\x00\x00\x00^\x00\x00\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x89\x00\x00\x00?\x00\x00\x00\xd1\x00\x00\x00+\x00\x00\x00\x00\x00\x00\x00\x84\x00\x00\x00\x00\x00\x00\x00\xe4\x00\x00\x00\x00\x00\x00\x00\xc7\x00\x00\x00\x94\x00\x00\x00\x06\x00\x00\x00\xa7\x00\x00\x00\xad\x00\x00\x00\xa0\x00\x00\x00\x00\x00\x00\x00\x90\x00\x00\x00\r\x00\x00\x00z\x00\x00\x00\xa6\x00\x00\x00\x00\x00\x00\x00\xb5\x00\x00\x00h\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd4\x00\x00\x00K\x00\x00\x00\x00\x00\x00\x00\xe9\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00v\x00\x00\x00\x91\x00\x00\x00<\x00\x00\x00\xae\x00\x00\x00\a\x00\x00\x00\xde\x00\x00\x00\xbf\x00\x00\x00M\x00\x00\x00$\x00\x00\x008\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xda\x00\x00\x00-\x00\x00\x00\x00\x00\x00\x00~\x00\x00\x00\xbd\x00\x00\x00\x8c\x00\x00\x00\x00\x00\x00\x00O\x00\x00\x00\xb2\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Q\x00\x00\x00C\x00\x00\x00A\x00\x00\x00m\x00\x00\x00\x00\x00\x00\x00\xd5\x00\x00\x00\x82\x00\x00\x00\n\x00\x00\x00V\x00\x00\x00\x13\x00\x00\x00P\x00\x00\x00\xd3\x00\x00\x00c\x00\x00\x00\xab\x00\x00\x00\x15\x00\x00\x00\x95\x00\x00\x00J\x00\x00\x001\x00\x00\x00\x19\x00\x00\x00\xb3\x00\x00\x00e\x00\x00\x00\xa1\x00\x00\x00\xe7\x00\x00\x00\x02\x00\x00\x00@\x00\x00\x00\xe3\x00\x00\x00\x8b\x00\x00\x00\x99\x00\x00\x00\b\x00\x00\x00\xaa\x00\x00\x00L\x00\x00\x006\x00\x00\x00/\x00\x00\x00\x00\x00\x00\x00\x1c\x00\x00\x00\x00\x00\x00\x00\xdb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9a\x00\x00\x00\x88\x00\x00\x00\x00\x00\x00\x00\xdc\x00\x00\x00\x8d\x00\x00\x00\xc9\x00\x00\x00G\x00\x00\x00\x00\x00\x00\x00\xb1\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\xaf\x00\x00\x00\x00\x00\x00\x00{\x00\x00\x002\x00\x00\x00S\x00\x00\x00\x86\x00\x00\x00a\x00\x00\x00\x00\x00\x00\x00\x1d\x00\x00\x00\xa9\x00\x00\x00\xa3\x00\x00\x00\x00\x00\x00\x00o\x00\x00\x00\xc6\x00\x00\x00\x8a\x00\x00\x00\x00\n\t\t # Create a ClusterRoleBinding for user1, user2, and group1 using the cluster-admin ClusterRole\n\t\t kubectl create clusterrolebinding cluster-admin --clusterrole=cluster-admin --user=user1 --user=user2 --group=group1\x00\n\t\t # Create a RoleBinding for user1, user2, and group1 using the admin ClusterRole\n\t\t kubectl create rolebinding admin --clusterrole=admin --user=user1 --user=user2 --group=group1\x00\n\t\t # Create a new configmap named my-config based on folder bar\n\t\t kubectl create configmap my-config --from-file=path/to/bar\n\n\t\t # Create a new configmap named my-config with specified keys instead of file basenames on disk\n\t\t kubectl create configmap my-config --from-file=key1=/path/to/bar/file1.txt --from-file=key2=/path/to/bar/file2.txt\n\n\t\t # Create a new configmap named my-config with key1=config1 and key2=config2\n\t\t kubectl create configmap my-config --from-literal=key1=config1 --from-literal=key2=config2\x00\n\t\t # If you don't already have a .dockercfg file, you can create a dockercfg secret directly by using:\n\t\t kubectl create secret docker-registry my-secret --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL\x00\n\t\t # Show metrics for all nodes\n\t\t kubectl top node\n\n\t\t # Show metrics for a given node\n\t\t kubectl top node NODE_NAME\x00\n\t\t# Apply the configuration in pod.json to a pod.\n\t\tkubectl apply -f ./pod.json\n\n\t\t# Apply the JSON passed into stdin to a pod.\n\t\tcat pod.json | kubectl apply -f -\n\n\t\t# Note: --prune is still in Alpha\n\t\t# Apply the configuration in manifest.yaml that matches label app=nginx and delete all the other resources that are not in the file and match label app=nginx.\n\t\tkubectl apply --prune -f manifest.yaml -l app=nginx\n\n\t\t# Apply the configuration in manifest.yaml and delete all the other configmaps that are not in the file.\n\t\tkubectl apply --prune -f manifest.yaml --all --prune-whitelist=core/v1/ConfigMap\x00\n\t\t# Auto scale a deployment \"foo\", with the number of pods between 2 and 10, target CPU utilization specified so a default autoscaling policy will be used:\n\t\tkubectl autoscale deployment foo --min=2 --max=10\n\n\t\t# Auto scale a replication controller \"foo\", with the number of pods between 1 and 5, target CPU utilization at 80%:\n\t\tkubectl autoscale rc foo --max=5 --cpu-percent=80\x00\n\t\t# Convert 'pod.yaml' to latest version and print to stdout.\n\t\tkubectl convert -f pod.yaml\n\n\t\t# Convert the live state of the resource specified by 'pod.yaml' to the latest version\n\t\t# and print to stdout in json format.\n\t\tkubectl convert -f pod.yaml --local -o json\n\n\t\t# Convert all files under current directory to latest version and create them all.\n\t\tkubectl convert -f . | kubectl create -f -\x00\n\t\t# Create a ClusterRole named \"pod-reader\" that allows user to perform \"get\", \"watch\" and \"list\" on pods\n\t\tkubectl create clusterrole pod-reader --verb=get,list,watch --resource=pods\n\n\t\t# Create a ClusterRole named \"pod-reader\" with ResourceName specified\n\t\tkubectl create clusterrole pod-reader --verb=get,list,watch --resource=pods --resource-name=readablepod\x00\n\t\t# Create a new resourcequota named my-quota\n\t\tkubectl create quota my-quota --hard=cpu=1,memory=1G,pods=2,services=3,replicationcontrollers=2,resourcequotas=1,secrets=5,persistentvolumeclaims=10\n\n\t\t# Create a new resourcequota named best-effort\n\t\tkubectl create quota best-effort --hard=pods=100 --scopes=BestEffort\x00\n\t\t# Create a pod disruption budget named my-pdb that will select all pods with the app=rails label\n\t\t# and require at least one of them being available at any point in time.\n\t\tkubectl create poddisruptionbudget my-pdb --selector=app=rails --min-available=1\n\n\t\t# Create a pod disruption budget named my-pdb that will select all pods with the app=nginx label\n\t\t# and require at least half of the pods selected to be available at any point in time.\n\t\tkubectl create pdb my-pdb --selector=app=nginx --min-available=50%\x00\n\t\t# Create a pod using the data in pod.json.\n\t\tkubectl create -f ./pod.json\n\n\t\t# Create a pod based on the JSON passed into stdin.\n\t\tcat pod.json | kubectl create -f -\n\n\t\t# Edit the data in docker-registry.yaml in JSON using the v1 API format then create the resource using the edited data.\n\t\tkubectl create -f docker-registry.yaml --edit --output-version=v1 -o json\x00\n\t\t# Create a service for a replicated nginx, which serves on port 80 and connects to the containers on port 8000.\n\t\tkubectl expose rc nginx --port=80 --target-port=8000\n\n\t\t# Create a service for a replication controller identified by type and name specified in \"nginx-controller.yaml\", which serves on port 80 and connects to the containers on port 8000.\n\t\tkubectl expose -f nginx-controller.yaml --port=80 --target-port=8000\n\n\t\t# Create a service for a pod valid-pod, which serves on port 444 with the name \"frontend\"\n\t\tkubectl expose pod valid-pod --port=444 --name=frontend\n\n\t\t# Create a second service based on the above service, exposing the container port 8443 as port 443 with the name \"nginx-https\"\n\t\tkubectl expose service nginx --port=443 --target-port=8443 --name=nginx-https\n\n\t\t# Create a service for a replicated streaming application on port 4100 balancing UDP traffic and named 'video-stream'.\n\t\tkubectl expose rc streamer --port=4100 --protocol=udp --name=video-stream\n\n\t\t# Create a service for a replicated nginx using replica set, which serves on port 80 and connects to the containers on port 8000.\n\t\tkubectl expose rs nginx --port=80 --target-port=8000\n\n\t\t# Create a service for an nginx deployment, which serves on port 80 and connects to the containers on port 8000.\n\t\tkubectl expose deployment nginx --port=80 --target-port=8000\x00\n\t\t# Delete a pod using the type and name specified in pod.json.\n\t\tkubectl delete -f ./pod.json\n\n\t\t# Delete a pod based on the type and name in the JSON passed into stdin.\n\t\tcat pod.json | kubectl delete -f -\n\n\t\t# Delete pods and services with same names \"baz\" and \"foo\"\n\t\tkubectl delete pod,service baz foo\n\n\t\t# Delete pods and services with label name=myLabel.\n\t\tkubectl delete pods,services -l name=myLabel\n\n\t\t# Delete a pod with minimal delay\n\t\tkubectl delete pod foo --now\n\n\t\t# Force delete a pod on a dead node\n\t\tkubectl delete pod foo --grace-period=0 --force\n\n\t\t# Delete all pods\n\t\tkubectl delete pods --all\x00\n\t\t# Describe a node\n\t\tkubectl describe nodes kubernetes-node-emt8.c.myproject.internal\n\n\t\t# Describe a pod\n\t\tkubectl describe pods/nginx\n\n\t\t# Describe a pod identified by type and name in \"pod.json\"\n\t\tkubectl describe -f pod.json\n\n\t\t# Describe all pods\n\t\tkubectl describe pods\n\n\t\t# Describe pods by label name=myLabel\n\t\tkubectl describe po -l name=myLabel\n\n\t\t# Describe all pods managed by the 'frontend' replication controller (rc-created pods\n\t\t# get the name of the rc as a prefix in the pod the name).\n\t\tkubectl describe pods frontend\x00\n\t\t# Drain node \"foo\", even if there are pods not managed by a ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet on it.\n\t\t$ kubectl drain foo --force\n\n\t\t# As above, but abort if there are pods not managed by a ReplicationController, ReplicaSet, Job, DaemonSet \u6216\u8005 StatefulSet, and use a grace period of 15 minutes.\n\t\t$ kubectl drain foo --grace-period=900\x00\n\t\t# Edit the service named 'docker-registry':\n\t\tkubectl edit svc/docker-registry\n\n\t\t# Use an alternative editor\n\t\tKUBE_EDITOR=\"nano\" kubectl edit svc/docker-registry\n\n\t\t# Edit the job 'myjob' in JSON using the v1 API format:\n\t\tkubectl edit job.v1.batch/myjob -o json\n\n\t\t# Edit the deployment 'mydeployment' in YAML and save the modified config in its annotation:\n\t\tkubectl edit deployment/mydeployment -o yaml --save-config\x00\n\t\t# Get output from running 'date' from pod 123456-7890, using the first container by default\n\t\tkubectl exec 123456-7890 date\n\n\t\t# Get output from running 'date' in ruby-container from pod 123456-7890\n\t\tkubectl exec 123456-7890 -c ruby-container date\n\n\t\t# Switch to raw terminal mode, sends stdin to 'bash' in ruby-container from pod 123456-7890\n\t\t# and sends stdout/stderr from 'bash' back to the client\n\t\tkubectl exec 123456-7890 -c ruby-container -i -t -- bash -il\x00\n\t\t# Get output from running pod 123456-7890, using the first container by default\n\t\tkubectl attach 123456-7890\n\n\t\t# Get output from ruby-container from pod 123456-7890\n\t\tkubectl attach 123456-7890 -c ruby-container\n\n\t\t# Switch to raw terminal mode, sends stdin to 'bash' in ruby-container from pod 123456-7890\n\t\t# and sends stdout/stderr from 'bash' back to the client\n\t\tkubectl attach 123456-7890 -c ruby-container -i -t\n\n\t\t# Get output from the first pod of a ReplicaSet named nginx\n\t\tkubectl attach rs/nginx\n\t\t\x00\n\t\t# Get the documentation of the resource and its fields\n\t\tkubectl explain pods\n\n\t\t# Get the documentation of a specific field of a resource\n\t\tkubectl explain pods.spec.containers\x00\n\t\t# Install bash completion on a Mac using homebrew\n\t\tbrew install bash-completion\n\t\tprintf \"\n# Bash completion support\nsource $(brew --prefix)/etc/bash_completion\n\" >> $HOME/.bash_profile\n\t\tsource $HOME/.bash_profile\n\n\t\t# Load the kubectl completion code for bash into the current shell\n\t\tsource <(kubectl completion bash)\n\n\t\t# Write bash completion code to a file and source if from .bash_profile\n\t\tkubectl completion bash > ~/.kube/completion.bash.inc\n\t\tprintf \"\n# Kubectl shell completion\nsource '$HOME/.kube/completion.bash.inc'\n\" >> $HOME/.bash_profile\n\t\tsource $HOME/.bash_profile\n\n\t\t# Load the kubectl completion code for zsh[1] into the current shell\n\t\tsource <(kubectl completion zsh)\x00\n\t\t# List all pods in ps output format.\n\t\tkubectl get pods\n\n\t\t# List all pods in ps output format with more information (such as node name).\n\t\tkubectl get pods -o wide\n\n\t\t# List a single replication controller with specified NAME in ps output format.\n\t\tkubectl get replicationcontroller web\n\n\t\t# List a single pod in JSON output format.\n\t\tkubectl get -o json pod web-pod-13je7\n\n\t\t# List a pod identified by type and name specified in \"pod.yaml\" in JSON output format.\n\t\tkubectl get -f pod.yaml -o json\n\n\t\t# Return only the phase value of the specified pod.\n\t\tkubectl get -o template pod/web-pod-13je7 --template={{.status.phase}}\n\n\t\t# List all replication controllers and services together in ps output format.\n\t\tkubectl get rc,services\n\n\t\t# List one or more resources by their type and names.\n\t\tkubectl get rc/web service/frontend pods/web-pod-13je7\n\n\t\t# List all resources with different types.\n\t\tkubectl get all\x00\n\t\t# Listen on ports 5000 and 6000 locally, forwarding data to/from ports 5000 and 6000 in the pod\n\t\tkubectl port-forward mypod 5000 6000\n\n\t\t# Listen on port 8888 locally, forwarding to 5000 in the pod\n\t\tkubectl port-forward mypod 8888:5000\n\n\t\t# Listen on a random port locally, forwarding to 5000 in the pod\n\t\tkubectl port-forward mypod :5000\n\n\t\t# Listen on a random port locally, forwarding to 5000 in the pod\n\t\tkubectl port-forward mypod 0:5000\x00\n\t\t# Mark node \"foo\" as schedulable.\n\t\t$ kubectl uncordon foo\x00\n\t\t# Mark node \"foo\" as unschedulable.\n\t\tkubectl cordon foo\x00\n\t\t# Partially update a node using strategic merge patch\n\t\tkubectl patch node k8s-node-1 -p '{\"spec\":{\"unschedulable\":true}}'\n\n\t\t# Partially update a node identified by the type and name specified in \"node.json\" using strategic merge patch\n\t\tkubectl patch -f node.json -p '{\"spec\":{\"unschedulable\":true}}'\n\n\t\t# Update a container's image; spec.containers[*].name is required because it's a merge key\n\t\tkubectl patch pod valid-pod -p '{\"spec\":{\"containers\":[{\"name\":\"kubernetes-serve-hostname\",\"image\":\"new image\"}]}}'\n\n\t\t# Update a container's image using a json patch with positional arrays\n\t\tkubectl patch pod valid-pod --type='json' -p='[{\"op\": \"replace\", \"path\": \"/spec/containers/0/image\", \"value\":\"new image\"}]'\x00\n\t\t# Print flags inherited by all commands\n\t\tkubectl options\x00\n\t\t# Print the address of the master and cluster services\n\t\tkubectl cluster-info\x00\n\t\t# Print the client and server versions for the current context\n\t\tkubectl version\x00\n\t\t# Print the supported API versions\n\t\tkubectl api-versions\x00\n\t\t# Replace a pod using the data in pod.json.\n\t\tkubectl replace -f ./pod.json\n\n\t\t# Replace a pod based on the JSON passed into stdin.\n\t\tcat pod.json | kubectl replace -f -\n\n\t\t# Update a single-container pod's image version (tag) to v4\n\t\tkubectl get pod mypod -o yaml | sed 's/\\(image: myimage\\):.*$/\x01:v4/' | kubectl replace -f -\n\n\t\t# Force replace, delete and then re-create the resource\n\t\tkubectl replace --force -f ./pod.json\x00\n\t\t# Return snapshot logs from pod nginx with only one container\n\t\tkubectl logs nginx\n\n\t\t# Return snapshot logs for the pods defined by label app=nginx\n\t\tkubectl logs -lapp=nginx\n\n\t\t# Return snapshot of previous terminated ruby container logs from pod web-1\n\t\tkubectl logs -p -c ruby web-1\n\n\t\t# Begin streaming the logs of the ruby container in pod web-1\n\t\tkubectl logs -f -c ruby web-1\n\n\t\t# Display only the most recent 20 lines of output in pod nginx\n\t\tkubectl logs --tail=20 nginx\n\n\t\t# Show all logs from pod nginx written in the last hour\n\t\tkubectl logs --since=1h nginx\n\n\t\t# Return snapshot logs from first container of a job named hello\n\t\tkubectl logs job/hello\n\n\t\t# Return snapshot logs from container nginx-1 of a deployment named nginx\n\t\tkubectl logs deployment/nginx -c nginx-1\x00\n\t\t# Run a proxy to kubernetes apiserver on port 8011, serving static content from ./local/www/\n\t\tkubectl proxy --port=8011 --www=./local/www/\n\n\t\t# Run a proxy to kubernetes apiserver on an arbitrary local port.\n\t\t# The chosen port for the server will be output to stdout.\n\t\tkubectl proxy --port=0\n\n\t\t# Run a proxy to kubernetes apiserver, changing the api prefix to k8s-api\n\t\t# This makes e.g. the pods api available at localhost:8001/k8s-api/v1/pods/\n\t\tkubectl proxy --api-prefix=/k8s-api\x00\n\t\t# Scale a replicaset named 'foo' to 3.\n\t\tkubectl scale --replicas=3 rs/foo\n\n\t\t# Scale a resource identified by type and name specified in \"foo.yaml\" to 3.\n\t\tkubectl scale --replicas=3 -f foo.yaml\n\n\t\t# If the deployment named mysql's current size is 2, scale mysql to 3.\n\t\tkubectl scale --current-replicas=2 --replicas=3 deployment/mysql\n\n\t\t# Scale multiple replication controllers.\n\t\tkubectl scale --replicas=5 rc/foo rc/bar rc/baz\n\n\t\t# Scale job named 'cron' to 3.\n\t\tkubectl scale --replicas=3 job/cron\x00\n\t\t# Set the last-applied-configuration of a resource to match the contents of a file.\n\t\tkubectl apply set-last-applied -f deploy.yaml\n\n\t\t# Execute set-last-applied against each configuration file in a directory.\n\t\tkubectl apply set-last-applied -f path/\n\n\t\t# Set the last-applied-configuration of a resource to match the contents of a file, will create the annotation if it does not already exist.\n\t\tkubectl apply set-last-applied -f deploy.yaml --create-annotation=true\n\t\t\x00\n\t\t# Show metrics for all pods in the default namespace\n\t\tkubectl top pod\n\n\t\t# Show metrics for all pods in the given namespace\n\t\tkubectl top pod --namespace=NAMESPACE\n\n\t\t# Show metrics for a given pod and its containers\n\t\tkubectl top pod POD_NAME --containers\n\n\t\t# Show metrics for the pods defined by label name=myLabel\n\t\tkubectl top pod -l name=myLabel\x00\n\t\t# Shut down foo.\n\t\tkubectl stop replicationcontroller foo\n\n\t\t# Stop pods and services with label name=myLabel.\n\t\tkubectl stop pods,services -l name=myLabel\n\n\t\t# Shut down the service defined in service.json\n\t\tkubectl stop -f service.json\n\n\t\t# Shut down all resources in the path/to/resources directory\n\t\tkubectl stop -f path/to/resources\x00\n\t\t# Start a single instance of nginx.\n\t\tkubectl run nginx --image=nginx\n\n\t\t# Start a single instance of hazelcast and let the container expose port 5701 .\n\t\tkubectl run hazelcast --image=hazelcast --port=5701\n\n\t\t# Start a single instance of hazelcast and set environment variables \"DNS_DOMAIN=cluster\" and \"POD_NAMESPACE=default\" in the container.\n\t\tkubectl run hazelcast --image=hazelcast --env=\"DNS_DOMAIN=cluster\" --env=\"POD_NAMESPACE=default\"\n\n\t\t# Start a replicated instance of nginx.\n\t\tkubectl run nginx --image=nginx --replicas=5\n\n\t\t# Dry run. Print the corresponding API objects without creating them.\n\t\tkubectl run nginx --image=nginx --dry-run\n\n\t\t# Start a single instance of nginx, but overload the spec of the deployment with a partial set of values parsed from JSON.\n\t\tkubectl run nginx --image=nginx --overrides='{ \"apiVersion\": \"v1\", \"spec\": { ... } }'\n\n\t\t# Start a pod of busybox and keep it in the foreground, don't restart it if it exits.\n\t\tkubectl run -i -t busybox --image=busybox --restart=Never\n\n\t\t# Start the nginx container using the default command, but use custom arguments (arg1 .. argN) for that command.\n\t\tkubectl run nginx --image=nginx -- ... \n\n\t\t# Start the nginx container using a different command and custom arguments.\n\t\tkubectl run nginx --image=nginx --command -- ... \n\n\t\t# Start the perl container to compute \u03c0 to 2000 places and print it out.\n\t\tkubectl run pi --image=perl --restart=OnFailure -- perl -Mbignum=bpi -wle 'print bpi(2000)'\n\n\t\t# Start the cron job to compute \u03c0 to 2000 places and print it out every 5 minutes.\n\t\tkubectl run pi --schedule=\"0/5 * * * ?\" --image=perl --restart=OnFailure -- perl -Mbignum=bpi -wle 'print bpi(2000)'\x00\n\t\t# Update node 'foo' with a taint with key 'dedicated' and value 'special-user' and effect 'NoSchedule'.\n\t\t# If a taint with that key and effect already exists, its value is replaced as specified.\n\t\tkubectl taint nodes foo dedicated=special-user:NoSchedule\n\n\t\t# Remove from node 'foo' the taint with key 'dedicated' and effect 'NoSchedule' if one exists.\n\t\tkubectl taint nodes foo dedicated:NoSchedule-\n\n\t\t# Remove from node 'foo' all the taints with key 'dedicated'\n\t\tkubectl taint nodes foo dedicated-\x00\n\t\t# Update pod 'foo' with the label 'unhealthy' and the value 'true'.\n\t\tkubectl label pods foo unhealthy=true\n\n\t\t# Update pod 'foo' with the label 'status' and the value 'unhealthy', overwriting any existing value.\n\t\tkubectl label --overwrite pods foo status=unhealthy\n\n\t\t# Update all pods in the namespace\n\t\tkubectl label pods --all status=unhealthy\n\n\t\t# Update a pod identified by the type and name in \"pod.json\"\n\t\tkubectl label -f pod.json status=unhealthy\n\n\t\t# Update pod 'foo' only if the resource is unchanged from version 1.\n\t\tkubectl label pods foo status=unhealthy --resource-version=1\n\n\t\t# Update pod 'foo' by removing a label named 'bar' if it exists.\n\t\t# Does not require the --overwrite flag.\n\t\tkubectl label pods foo bar-\x00\n\t\t# Update pods of frontend-v1 using new replication controller data in frontend-v2.json.\n\t\tkubectl rolling-update frontend-v1 -f frontend-v2.json\n\n\t\t# Update pods of frontend-v1 using JSON data passed into stdin.\n\t\tcat frontend-v2.json | kubectl rolling-update frontend-v1 -f -\n\n\t\t# Update the pods of frontend-v1 to frontend-v2 by just changing the image, and switching the\n\t\t# name of the replication controller.\n\t\tkubectl rolling-update frontend-v1 frontend-v2 --image=image:v2\n\n\t\t# Update the pods of frontend by just changing the image, and keeping the old name.\n\t\tkubectl rolling-update frontend --image=image:v2\n\n\t\t# Abort and reverse an existing rollout in progress (from frontend-v1 to frontend-v2).\n\t\tkubectl rolling-update frontend-v1 frontend-v2 --rollback\x00\n\t\t# View the last-applied-configuration annotations by type/name in YAML.\n\t\tkubectl apply view-last-applied deployment/nginx\n\n\t\t# View the last-applied-configuration annotations by file in JSON\n\t\tkubectl apply view-last-applied -f deploy.yaml -o json\x00\n\t\tApply a configuration to a resource by filename or stdin.\n\t\tThis resource will be created if it doesn't exist yet.\n\t\tTo use 'apply', always create the resource initially with either 'apply' or 'create --save-config'.\n\n\t\tJSON and YAML formats are accepted.\n\n\t\tAlpha Disclaimer: the --prune functionality is not yet complete. Do not use unless you are aware of what the current state is. See https://issues.k8s.io/34274.\x00\n\t\tConvert config files between different API versions. Both YAML\n\t\tand JSON formats are accepted.\n\n\t\tThe command takes filename, directory, or URL as input, and convert it into format\n\t\tof version specified by --output-version flag. If target version is not specified or\n\t\tnot supported, convert to latest version.\n\n\t\tThe default output will be printed to stdout in YAML format. One can use -o option\n\t\tto change to output destination.\x00\n\t\tCreate a ClusterRole.\x00\n\t\tCreate a ClusterRoleBinding for a particular ClusterRole.\x00\n\t\tCreate a RoleBinding for a particular Role or ClusterRole.\x00\n\t\tCreate a TLS secret from the given public/private key pair.\n\n\t\tThe public/private key pair must exist before hand. The public key certificate must be .PEM encoded and match the given private key.\x00\n\t\tCreate a configmap based on a file, directory, or specified literal value.\n\n\t\tA single configmap may package one or more key/value pairs.\n\n\t\tWhen creating a configmap based on a file, the key will default to the basename of the file, and the value will\n\t\tdefault to the file content. If the basename is an invalid key, you may specify an alternate key.\n\n\t\tWhen creating a configmap based on a directory, each file whose basename is a valid key in the directory will be\n\t\tpackaged into the configmap. Any directory entries except regular files are ignored (e.g. subdirectories,\n\t\tsymlinks, devices, pipes, etc).\x00\n\t\tCreate a namespace with the specified name.\x00\n\t\tCreate a new secret for use with Docker registries.\n\n\t\tDockercfg secrets are used to authenticate against Docker registries.\n\n\t\tWhen using the Docker command line to push images, you can authenticate to a given registry by running\n\n\t\t $ docker login DOCKER_REGISTRY_SERVER --username=DOCKER_USER --password=DOCKER_PASSWORD --email=DOCKER_EMAIL'.\n\n That produces a ~/.dockercfg file that is used by subsequent 'docker push' and 'docker pull' commands to\n\t\tauthenticate to the registry. The email address is optional.\n\n\t\tWhen creating applications, you may have a Docker registry that requires authentication. In order for the\n\t\tnodes to pull images on your behalf, they have to have the credentials. You can provide this information\n\t\tby creating a dockercfg secret and attaching it to your service account.\x00\n\t\tCreate a pod disruption budget with the specified name, selector, and desired minimum available pods\x00\n\t\tCreate a resource by filename or stdin.\n\n\t\tJSON and YAML formats are accepted.\x00\n\t\tCreate a resourcequota with the specified name, hard limits and optional scopes\x00\n\t\tCreate a role with single rule.\x00\n\t\tCreate a secret based on a file, directory, or specified literal value.\n\n\t\tA single secret may package one or more key/value pairs.\n\n\t\tWhen creating a secret based on a file, the key will default to the basename of the file, and the value will\n\t\tdefault to the file content. If the basename is an invalid key, you may specify an alternate key.\n\n\t\tWhen creating a secret based on a directory, each file whose basename is a valid key in the directory will be\n\t\tpackaged into the secret. Any directory entries except regular files are ignored (e.g. subdirectories,\n\t\tsymlinks, devices, pipes, etc).\x00\n\t\tCreate a service account with the specified name.\x00\n\t\tCreate and run a particular image, possibly replicated.\n\n\t\tCreates a deployment or job to manage the created container(s).\x00\n\t\tCreates an autoscaler that automatically chooses and sets the number of pods that run in a kubernetes cluster.\n\n\t\tLooks up a Deployment, ReplicaSet, or ReplicationController by name and creates an autoscaler that uses the given resource as a reference.\n\t\tAn autoscaler can automatically increase or decrease number of pods deployed within the system as needed.\x00\n\t\tDelete resources by filenames, stdin, resources and names, or by resources and label selector.\n\n\t\tJSON and YAML formats are accepted. Only one type of the arguments may be specified: filenames,\n\t\tresources and names, or resources and label selector.\n\n\t\tSome resources, such as pods, support graceful deletion. These resources define a default period\n\t\tbefore they are forcibly terminated (the grace period) but you may override that value with\n\t\tthe --grace-period flag, or pass --now to set a grace-period of 1. Because these resources often\n\t\trepresent entities in the cluster, deletion may not be acknowledged immediately. If the node\n\t\thosting a pod is down or cannot reach the API server, termination may take significantly longer\n\t\tthan the grace period. To force delete a resource,\tyou must pass a grace\tperiod of 0 and specify\n\t\tthe --force flag.\n\n\t\tIMPORTANT: Force deleting pods does not wait for confirmation that the pod's processes have been\n\t\tterminated, which can leave those processes running until the node detects the deletion and\n\t\tcompletes graceful deletion. If your processes use shared storage or talk to a remote API and\n\t\tdepend on the name of the pod to identify themselves, force deleting those pods may result in\n\t\tmultiple processes running on different machines using the same identification which may lead\n\t\tto data corruption or inconsistency. Only force delete pods when you are sure the pod is\n\t\tterminated, or if your application can tolerate multiple copies of the same pod running at once.\n\t\tAlso, if you force delete pods the scheduler may place new pods on those nodes before the node\n\t\thas released those resources and causing those pods to be evicted immediately.\n\n\t\tNote that the delete command does NOT do resource version checks, so if someone\n\t\tsubmits an update to a resource right when you submit a delete, their update\n\t\twill be lost along with the rest of the resource.\x00\n\t\tDeprecated: Gracefully shut down a resource by name or filename.\n\n\t\tThe stop command is deprecated, all its functionalities are covered by delete command.\n\t\tSee 'kubectl delete --help' for more details.\n\n\t\tAttempts to shut down and delete a resource that supports graceful termination.\n\t\tIf the resource is scalable it will be scaled to 0 before deletion.\x00\n\t\tDisplay Resource (CPU/Memory/Storage) usage of nodes.\n\n\t\tThe top-node command allows you to see the resource consumption of nodes.\x00\n\t\tDisplay Resource (CPU/Memory/Storage) usage of pods.\n\n\t\tThe 'top pod' command allows you to see the resource consumption of pods.\n\n\t\tDue to the metrics pipeline delay, they may be unavailable for a few minutes\n\t\tsince pod creation.\x00\n\t\tDisplay Resource (CPU/Memory/Storage) usage.\n\n\t\tThe top command allows you to see the resource consumption for nodes or pods.\n\n\t\tThis command requires Heapster to be correctly configured and working on the server. \x00\n\t\tDrain node in preparation for maintenance.\n\n\t\tThe given node will be marked unschedulable to prevent new pods from arriving.\n\t\t'drain' evicts the pods if the APIServer supports eviction\n\t\t(http://kubernetes.io/docs/admin/disruptions/). Otherwise, it will use normal DELETE\n\t\tto delete the pods.\n\t\tThe 'drain' evicts or deletes all pods except mirror pods (which cannot be deleted through\n\t\tthe API server). If there are DaemonSet-managed pods, drain will not proceed\n\t\twithout --ignore-daemonsets, and regardless it will not delete any\n\t\tDaemonSet-managed pods, because those pods would be immediately replaced by the\n\t\tDaemonSet controller, which ignores unschedulable markings. If there are any\n\t\tpods that are neither mirror pods nor managed by ReplicationController,\n\t\tReplicaSet, DaemonSet, StatefulSet or Job, then drain will not delete any pods unless you\n\t\tuse --force. --force will also allow deletion to proceed if the managing resource of one\n\t\tor more pods is missing.\n\n\t\t'drain' waits for graceful termination. You should not operate on the machine until\n\t\tthe command completes.\n\n\t\tWhen you are ready to put the node back into service, use kubectl uncordon, which\n\t\twill make the node schedulable again.\n\n\t\t![Workflow](http://kubernetes.io/images/docs/kubectl_drain.svg)\x00\n\t\tEdit a resource from the default editor.\n\n\t\tThe edit command allows you to directly edit any API resource you can retrieve via the\n\t\tcommand line tools. It will open the editor defined by your KUBE_EDITOR, or EDITOR\n\t\tenvironment variables, or fall back to 'vi' for Linux or 'notepad' for Windows.\n\t\tYou can edit multiple objects, although changes are applied one at a time. The command\n\t\taccepts filenames as well as command line arguments, although the files you point to must\n\t\tbe previously saved versions of resources.\n\n\t\tEditing is done with the API version used to fetch the resource.\n\t\tTo edit using a specific API version, fully-qualify the resource, version, and group.\n\n\t\tThe default format is YAML. To edit in JSON, specify \"-o json\".\n\n\t\tThe flag --windows-line-endings can be used to force Windows line endings,\n\t\totherwise the default for your operating system will be used.\n\n\t\tIn the event an error occurs while updating, a temporary file will be created on disk\n\t\tthat contains your unapplied changes. The most common error when updating a resource\n\t\tis another editor changing the resource on the server. When this occurs, you will have\n\t\tto apply your changes to the newer version of the resource, or update your temporary\n\t\tsaved copy to include the latest resource version.\x00\n\t\tMark node as schedulable.\x00\n\t\tMark node as unschedulable.\x00\n\t\tOutput shell completion code for the specified shell (bash or zsh).\n\t\tThe shell code must be evalutated to provide interactive\n\t\tcompletion of kubectl commands. This can be done by sourcing it from\n\t\tthe .bash_profile.\n\n\t\tNote: this requires the bash-completion framework, which is not installed\n\t\tby default on Mac. This can be installed by using homebrew:\n\n\t\t $ brew install bash-completion\n\n\t\tOnce installed, bash_completion must be evaluated. This can be done by adding the\n\t\tfollowing line to the .bash_profile\n\n\t\t $ source $(brew --prefix)/etc/bash_completion\n\n\t\tNote for zsh users: [1] zsh completions are only supported in versions of zsh >= 5.2\x00\n\t\tPerform a rolling update of the given ReplicationController.\n\n\t\tReplaces the specified replication controller with a new replication controller by updating one pod at a time to use the\n\t\tnew PodTemplate. The new-controller.json must specify the same namespace as the\n\t\texisting replication controller and overwrite at least one (common) label in its replicaSelector.\n\n\t\t![Workflow](http://kubernetes.io/images/docs/kubectl_rollingupdate.svg)\x00\n\t\tReplace a resource by filename or stdin.\n\n\t\tJSON and YAML formats are accepted. If replacing an existing resource, the\n\t\tcomplete resource spec must be provided. This can be obtained by\n\n\t\t $ kubectl get TYPE NAME -o yaml\n\n\t\tPlease refer to the models in https://htmlpreview.github.io/?https://github.com/kubernetes/kubernetes/blob/HEAD/docs/api-reference/v1/definitions.html to find if a field is mutable.\x00\n\t\tSet a new size for a Deployment, ReplicaSet, Replication Controller, or Job.\n\n\t\tScale also allows users to specify one or more preconditions for the scale action.\n\n\t\tIf --current-replicas or --resource-version is specified, it is validated before the\n\t\tscale is attempted, and it is guaranteed that the precondition holds true when the\n\t\tscale is sent to the server.\x00\n\t\tSet the latest last-applied-configuration annotations by setting it to match the contents of a file.\n\t\tThis results in the last-applied-configuration being updated as though 'kubectl apply -f ' was run,\n\t\twithout updating any other parts of the object.\x00\n\t\tTo proxy all of the kubernetes api and nothing else, use:\n\n\t\t $ kubectl proxy --api-prefix=/\n\n\t\tTo proxy only part of the kubernetes api and also some static files:\n\n\t\t $ kubectl proxy --www=/my/files --www-prefix=/static/ --api-prefix=/api/\n\n\t\tThe above lets you 'curl localhost:8001/api/v1/pods'.\n\n\t\tTo proxy the entire kubernetes api at a different root, use:\n\n\t\t $ kubectl proxy --api-prefix=/custom/\n\n\t\tThe above lets you 'curl localhost:8001/custom/api/v1/pods'\x00\n\t\tUpdate field(s) of a resource using strategic merge patch\n\n\t\tJSON and YAML formats are accepted.\n\n\t\tPlease refer to the models in https://htmlpreview.github.io/?https://github.com/kubernetes/kubernetes/blob/HEAD/docs/api-reference/v1/definitions.html to find if a field is mutable.\x00\n\t\tUpdate the labels on a resource.\n\n\t\t* A label must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[1]d characters.\n\t\t* If --overwrite is true, then existing labels can be overwritten, otherwise attempting to overwrite a label will result in an error.\n\t\t* If --resource-version is specified, then updates will use this resource version, otherwise the existing resource-version will be used.\x00\n\t\tUpdate the taints on one or more nodes.\n\n\t\t* A taint consists of a key, value, and effect. As an argument here, it is expressed as key=value:effect.\n\t\t* The key must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[1]d characters.\n\t\t* The value must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[2]d characters.\n\t\t* The effect must be NoSchedule, PreferNoSchedule or NoExecute.\n\t\t* Currently taint can only apply to node.\x00\n\t\tView the latest last-applied-configuration annotations by type/name or file.\n\n\t\tThe default output will be printed to stdout in YAML format. One can use -o option\n\t\tto change output format.\x00\n\t # !!!Important Note!!!\n\t # Requires that the 'tar' binary is present in your container\n\t # image. If 'tar' is not present, 'kubectl cp' will fail.\n\n\t # Copy /tmp/foo_dir local directory to /tmp/bar_dir in a remote pod in the default namespace\n\t\tkubectl cp /tmp/foo_dir :/tmp/bar_dir\n\n # Copy /tmp/foo local file to /tmp/bar in a remote pod in a specific container\n\t\tkubectl cp /tmp/foo :/tmp/bar -c \n\n\t\t# Copy /tmp/foo local file to /tmp/bar in a remote pod in namespace \n\t\tkubectl cp /tmp/foo /:/tmp/bar\n\n\t\t# Copy /tmp/foo from a remote pod to /tmp/bar locally\n\t\tkubectl cp /:/tmp/foo /tmp/bar\x00\n\t # Create a new TLS secret named tls-secret with the given key pair:\n\t kubectl create secret tls tls-secret --cert=path/to/tls.cert --key=path/to/tls.key\x00\n\t # Create a new namespace named my-namespace\n\t kubectl create namespace my-namespace\x00\n\t # Create a new secret named my-secret with keys for each file in folder bar\n\t kubectl create secret generic my-secret --from-file=path/to/bar\n\n\t # Create a new secret named my-secret with specified keys instead of names on disk\n\t kubectl create secret generic my-secret --from-file=ssh-privatekey=~/.ssh/id_rsa --from-file=ssh-publickey=~/.ssh/id_rsa.pub\n\n\t # Create a new secret named my-secret with key1=supersecret and key2=topsecret\n\t kubectl create secret generic my-secret --from-literal=key1=supersecret --from-literal=key2=topsecret\x00\n\t # Create a new service account named my-service-account\n\t kubectl create serviceaccount my-service-account\x00\n\t# Create a new ExternalName service named my-ns \n\tkubectl create service externalname my-ns --external-name bar.com\x00\n\tCreate an ExternalName service with the specified name.\n\n\tExternalName service references to an external DNS address instead of\n\tonly pods, which will allow application authors to reference services\n\tthat exist off platform, on other clusters, or locally.\x00\n\tHelp provides help for any command in the application.\n\tSimply type kubectl help [path to command] for full details.\x00\n # Create a new LoadBalancer service named my-lbs\n kubectl create service loadbalancer my-lbs --tcp=5678:8080\x00\n # Create a new clusterIP service named my-cs\n kubectl create service clusterip my-cs --tcp=5678:8080\n\n # Create a new clusterIP service named my-cs (in headless mode)\n kubectl create service clusterip my-cs --clusterip=\"None\"\x00\n # Create a new deployment named my-dep that runs the busybox image.\n kubectl create deployment my-dep --image=busybox\x00\n # Create a new nodeport service named my-ns\n kubectl create service nodeport my-ns --tcp=5678:8080\x00\n # Dump current cluster state to stdout\n kubectl cluster-info dump\n\n # Dump current cluster state to /path/to/cluster-state\n kubectl cluster-info dump --output-directory=/path/to/cluster-state\n\n # Dump all namespaces to stdout\n kubectl cluster-info dump --all-namespaces\n\n # Dump a set of namespaces to /path/to/cluster-state\n kubectl cluster-info dump --namespaces default,kube-system --output-directory=/path/to/cluster-state\x00\n # Update pod 'foo' with the annotation 'description' and the value 'my frontend'.\n # If the same annotation is set multiple times, only the last value will be applied\n kubectl annotate pods foo description='my frontend'\n\n # Update a pod identified by type and name in \"pod.json\"\n kubectl annotate -f pod.json description='my frontend'\n\n # Update pod 'foo' with the annotation 'description' and the value 'my frontend running nginx', overwriting any existing value.\n kubectl annotate --overwrite pods foo description='my frontend running nginx'\n\n # Update all pods in the namespace\n kubectl annotate pods --all description='my frontend running nginx'\n\n # Update pod 'foo' only if the resource is unchanged from version 1.\n kubectl annotate pods foo description='my frontend running nginx' --resource-version=1\n\n # Update pod 'foo' by removing an annotation named 'description' if it exists.\n # Does not require the --overwrite flag.\n kubectl annotate pods foo description-\x00\n Create a LoadBalancer service with the specified name.\x00\n Create a clusterIP service with the specified name.\x00\n Create a deployment with the specified name.\x00\n Create a nodeport service with the specified name.\x00\n Dumps cluster info out suitable for debugging and diagnosing cluster problems. By default, dumps everything to\n stdout. You can optionally specify a directory with --output-directory. If you specify a directory, kubernetes will\n build a set of files in that directory. By default only dumps things in the 'kube-system' namespace, but you can\n switch to a different namespace with the --namespaces flag, or specify --all-namespaces to dump all namespaces.\n\n The command also dumps the logs of all of the pods in the cluster, these logs are dumped into different directories\n based on namespace and pod name.\x00\n Display addresses of the master and services with label kubernetes.io/cluster-service=true\n To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.\x00A comma-delimited set of quota scopes that must all match each object tracked by the quota.\x00A comma-delimited set of resource=quantity pairs that define a hard limit.\x00A label selector to use for this budget. Only equality-based selector requirements are supported.\x00A label selector to use for this service. Only equality-based selector requirements are supported. If empty (the default) infer the selector from the replication controller or replica set.)\x00A schedule in the Cron format the job should be run with.\x00Additional external IP address (not managed by Kubernetes) to accept for the service. If this IP is routed to a node, the service can be accessed by this IP in addition to its generated service IP.\x00An inline JSON override for the generated object. If this is non-empty, it is used to override the generated object. Requires that the object supply a valid apiVersion field.\x00An inline JSON override for the generated service object. If this is non-empty, it is used to override the generated object. Requires that the object supply a valid apiVersion field. Only used if --expose is true.\x00Apply a configuration to a resource by filename or stdin\x00Approve a certificate signing request\x00Assign your own ClusterIP or set to 'None' for a 'headless' service (no loadbalancing).\x00Attach to a running container\x00Auto-scale a Deployment, ReplicaSet, or ReplicationController\x00ClusterIP to be assigned to the service. Leave empty to auto-allocate, or set to 'None' to create a headless service.\x00ClusterRole this ClusterRoleBinding should reference\x00ClusterRole this RoleBinding should reference\x00Container name which will have its image upgraded. Only relevant when --image is specified, ignored otherwise. Required when using --image on a multi-container pod\x00Convert config files between different API versions\x00Copy files and directories to and from containers.\x00Create a ClusterRoleBinding for a particular ClusterRole\x00Create a LoadBalancer service.\x00Create a NodePort service.\x00Create a RoleBinding for a particular Role or ClusterRole\x00Create a TLS secret\x00Create a clusterIP service.\x00Create a configmap from a local file, directory or literal value\x00Create a deployment with the specified name.\x00Create a namespace with the specified name\x00Create a pod disruption budget with the specified name.\x00Create a quota with the specified name.\x00Create a resource by filename or stdin\x00Create a secret for use with a Docker registry\x00Create a secret from a local file, directory or literal value\x00Create a secret using specified subcommand\x00Create a service account with the specified name\x00Create a service using specified subcommand.\x00Create an ExternalName service.\x00Delete resources by filenames, stdin, resources and names, or by resources and label selector\x00Delete the specified cluster from the kubeconfig\x00Delete the specified context from the kubeconfig\x00Deny a certificate signing request\x00Deprecated: Gracefully shut down a resource by name or filename\x00Describe one or many contexts\x00Display Resource (CPU/Memory) usage of nodes\x00Display Resource (CPU/Memory) usage of pods\x00Display Resource (CPU/Memory) usage.\x00Display cluster info\x00Display clusters defined in the kubeconfig\x00Display merged kubeconfig settings or a specified kubeconfig file\x00Display one or many resources\x00Displays the current-context\x00Documentation of resources\x00Drain node in preparation for maintenance\x00Dump lots of relevant info for debugging and diagnosis\x00Edit a resource on the server\x00Email for Docker registry\x00Execute a command in a container\x00Explicit policy for when to pull container images. Required when --image is same as existing image, ignored otherwise.\x00Forward one or more local ports to a pod\x00Help about any command\x00IP to assign to the Load Balancer. If empty, an ephemeral IP will be created and used (cloud-provider specific).\x00If non-empty, set the session affinity for the service to this; legal values: 'None', 'ClientIP'\x00If non-empty, the annotation update will only succeed if this is the current resource-version for the object. Only valid when specifying a single resource.\x00If non-empty, the labels update will only succeed if this is the current resource-version for the object. Only valid when specifying a single resource.\x00Image to use for upgrading the replication controller. Must be distinct from the existing image (either new image or new image tag). Can not be used with --filename/-f\x00Manage a deployment rollout\x00Mark node as schedulable\x00Mark node as unschedulable\x00Mark the provided resource as paused\x00Modify certificate resources.\x00Modify kubeconfig files\x00Name or number for the port on the container that the service should direct traffic to. Optional.\x00Only return logs after a specific date (RFC3339). Defaults to all logs. Only one of since-time / since may be used.\x00Output shell completion code for the specified shell (bash or zsh)\x00Output the formatted object with the given group version (for ex: 'extensions/v1beta1').)\x00Password for Docker registry authentication\x00Path to PEM encoded public key certificate.\x00Path to private key associated with given certificate.\x00Perform a rolling update of the given ReplicationController\x00Precondition for resource version. Requires that the current resource version match this value in order to scale.\x00Print the client and server version information\x00Print the list of flags inherited by all commands\x00Print the logs for a container in a pod\x00Replace a resource by filename or stdin\x00Resume a paused resource\x00Role this RoleBinding should reference\x00Run a particular image on the cluster\x00Run a proxy to the Kubernetes API server\x00Server location for Docker registry\x00Set a new size for a Deployment, ReplicaSet, Replication Controller, or Job\x00Set specific features on objects\x00Set the last-applied-configuration annotation on a live object to match the contents of a file.\x00Set the selector on a resource\x00Sets a cluster entry in kubeconfig\x00Sets a context entry in kubeconfig\x00Sets a user entry in kubeconfig\x00Sets an individual value in a kubeconfig file\x00Sets the current-context in a kubeconfig file\x00Show details of a specific resource or group of resources\x00Show the status of the rollout\x00Synonym for --target-port\x00Take a replication controller, service, deployment or pod and expose it as a new Kubernetes Service\x00The image for the container to run.\x00The image pull policy for the container. If left empty, this value will not be specified by the client and defaulted by the server\x00The key to use to differentiate between two different controllers, default 'deployment'. Only relevant when --image is specified, ignored otherwise\x00The minimum number or percentage of available pods this budget requires.\x00The name for the newly created object.\x00The name for the newly created object. If not specified, the name of the input resource will be used.\x00The name of the API generator to use, see http://kubernetes.io/docs/user-guide/kubectl-conventions/#generators for a list.\x00The name of the API generator to use. Currently there is only 1 generator.\x00The name of the API generator to use. There are 2 generators: 'service/v1' and 'service/v2'. The only difference between them is that service port in v1 is named 'default', while it is left unnamed in v2. Default is 'service/v2'.\x00The name of the generator to use for creating a service. Only used if --expose is true\x00The network protocol for the service to be created. Default is 'TCP'.\x00The port that the service should serve on. Copied from the resource being exposed, if unspecified\x00The port that this container exposes. If --expose is true, this is also the port used by the service that is created.\x00The resource requirement limits for this container. For example, 'cpu=200m,memory=512Mi'. Note that server side components may assign limits depending on the server configuration, such as limit ranges.\x00The resource requirement requests for this container. For example, 'cpu=100m,memory=256Mi'. Note that server side components may assign requests depending on the server configuration, such as limit ranges.\x00The restart policy for this Pod. Legal values [Always, OnFailure, Never]. If set to 'Always' a deployment is created, if set to 'OnFailure' a job is created, if set to 'Never', a regular pod is created. For the latter two --replicas must be 1. Default 'Always', for CronJobs `Never`.\x00The type of secret to create\x00Type for this service: ClusterIP, NodePort, or LoadBalancer. Default is 'ClusterIP'.\x00Undo a previous rollout\x00Unsets an individual value in a kubeconfig file\x00Update field(s) of a resource using strategic merge patch\x00Update image of a pod template\x00Update resource requests/limits on objects with pod templates\x00Update the annotations on a resource\x00Update the labels on a resource\x00Update the taints on one or more nodes\x00Username for Docker registry authentication\x00View latest last-applied-configuration annotations of a resource/object\x00View rollout history\x00Where to output the files. If empty or '-' uses stdout, otherwise creates a directory hierarchy in that directory\x00dummy restart flag)\x00external name of service\x00kubectl controls the Kubernetes cluster manager\x00Project-Id-Version: gettext-go-examples-hello\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2013-12-12 20:03+0000\nPO-Revision-Date: 2017-11-11 19:01+0800\nLast-Translator: zhengjiajin \nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nX-Generator: Poedit 2.0.4\nX-Poedit-SourceCharset: UTF-8\nLanguage-Team: \nPlural-Forms: nplurals=2; plural=(n > 1);\nLanguage: zh\n\x00\n\t\t # \u4f7f\u7528 cluster-admin ClusterRole \u4e3a user1, user2, and group1 \u521b\u5efa\u4e00\u4e2a ClusterRoleBinding\n\t\t kubectl create clusterrolebinding cluster-admin --clusterrole=cluster-admin --user=user1 --user=user2 --group=group1\x00\n\t\t # \u4f7f\u7528 admin ClusterRole \u4e3a user1, user2, and group1 \u521b\u5efa\u4e00\u4e2a RoleBinding\n\t\t kubectl create rolebinding admin --clusterrole=admin --user=user1 --user=user2 --group=group1\x00\n\t\t # \u901a\u8fc7\u6587\u4ef6\u5939 bar \u521b\u5efa\u4e00\u4e2a\u540d\u79f0\u4e3a my-config \u7684 configmap\n\t\t kubectl create configmap my-config --from-file=path/to/bar\n\n\t\t # \u521b\u5efa\u4e00\u4e2a\u540d\u79f0\u4e3a my-config \u7684 configmap \u5e76\u6307\u5b9a keys \u800c\u4e0d\u662f\u4f7f\u7528\u78c1\u76d8\u4e0a\u6240\u5728\u7684\u6587\u4ef6\u540d\n\t\t kubectl create configmap my-config --from-file=key1=/path/to/bar/file1.txt --from-file=key2=/path/to/bar/file2.txt\n\n\t\t # \u521b\u5efa\u4e00\u4e2a\u540d\u79f0\u4e3a my-config \u7684 configmap \u4e14 key1=config1 \u548c key2=config2\n\t\t kubectl create configmap my-config --from-literal=key1=config1 --from-literal=key2=config2\x00\n\t\t # \u5982\u679c\u4f60\u8fd8\u6ca1\u6709\u4e00\u4e2a .dockercfg \u6587\u4ef6, \u4f60\u53ef\u4ee5\u521b\u5efa\u4e00\u4e2a dockercfg \u7684 secret \u76f4\u63a5\u4f7f\u7528:\n\t\t kubectl create secret docker-registry my-secret --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL\x00\n\t\t # \u663e\u793a\u6240\u6709 nodes \u4e0a\u7684\u6307\u6807\n\t\t kubectl top node\n\n\t\t # \u663e\u793a\u6307\u5b9a node \u4e0a\u7684\u6307\u6807\n\t\t kubectl top node NODE_NAME\x00\n\t\t# \u5c06 pod.json \u4e0a\u7684\u914d\u7f6e\u5e94\u7528\u4e8e pod.\n\t\tkubectl apply -f ./pod.json\n\n\t\t# \u5c06\u4f20\u5165 stdin \u7684 JSON \u5e94\u7528\u5230\u4e00\u4e2a pod.\n\t\tcat pod.json | kubectl apply -f -\n\n\t\t# Note: --prune \u4ecd\u7136\u5728 Alpha\n\t\t# \u5e94\u7528\u5728 manifest.yaml \u4e2d\u5339\u914d\u6807\u7b7e app=nginx \u7684\u8d44\u6e90\u914d\u7f6e\u5e76\u5220\u9664\u6240\u6709\u4e0d\u5728\u8fd9\u4e2a\u6587\u4ef6\u4e2d\u5e76\u5339\u914d\u6807\u7b7eapp=nginx \u7684\u8d44\u6e90\n\t\tkubectl apply --prune -f manifest.yaml -l app=nginx\n\n\t\t# \u5e94\u7528 manifest.yaml \u7684\u914d\u7f6e\u5e76\u5220\u9664\u6240\u6709\u4e0d\u5728\u8fd9\u4e2a\u6587\u4ef6\u4e2d\u7684 configmaps.\n\t\tkubectl apply --prune -f manifest.yaml --all --prune-whitelist=core/v1/ConfigMap\x00\n\t\t# \u81ea\u52a8\u5f39\u6027\u4f38\u7f29 deployment \"foo\", pods \u7684\u6570\u91cf\u5728 2 \u548c 10 \u4e4b\u95f4, \u76ee\u6807 CPU \u6307\u5b9a\u4e3a\u9ed8\u8ba4\u7684\u5f39\u6027\u4f38\u7f29\u7b56\u7565:\n\t\tkubectl autoscale deployment foo --min=2 --max=10\n\n\t\t# \u81ea\u52a8\u5f39\u6027\u4f38\u7f29 replication controller \"foo\", pods \u7684\u6570\u91cf\u5728 1 \u548c 5 \u4e4b\u95f4, \u76ee\u6807 CPU \u5229\u7528\u7387\u4e3a 80%:\n\t\tkubectl autoscale rc foo --max=5 --cpu-percent=80\x00\n\t\t# \u5c06\u2019pod.yaml' \u8f6c\u6362\u4e3a\u6700\u65b0\u7248\u672c\u5e76\u6253\u5370\u5230 stdout.\n\t\tkubectl convert -f pod.yaml\n\n\t\t# \u5c06 \u2018pod.yaml' \u6307\u5b9a\u7684\u8d44\u6e90\u7684\u5b9e\u65f6\u72b6\u6001\u8f6c\u6362\u4e3a\u6700\u65b0\u7248\u672c\n\t\t# \u5e76\u4ee5 json \u683c\u5f0f\u6253\u5370\u5230 stdout.\n\t\tkubectl convert -f pod.yaml --local -o json\n\n\t\t# \u5c06\u5f53\u524d\u76ee\u5f55\u4e0b\u7684\u6240\u4ee5\u6587\u4ef6\u8f6c\u6362\u4e3a\u6700\u65b0\u7248\u672c\u5e76\u521b\u5efa\u5b83\u4eec.\n\t\tkubectl convert -f . | kubectl create -f -\x00\n\t\t# \u521b\u5efa\u4e00\u4e2a\u540d\u4e3a \"pod-reader\" \u7684 ClusterRole, \u5141\u8bb8\u7528\u6237\u5728 pods \u4e0a\u6267\u884c \u201cget\", \"watch\" \u548c \"list\"\n\t\tkubectl create clusterrole pod-reader --verb=get,list,watch --resource=pods\n\n\t\t# \u521b\u5efa\u4e00\u4e2a\u540d\u4e3a \"pod-reader\" ClusterRole, \u5176\u4e2d\u6307\u5b9a\u4e86 ResourceName\n\t\tkubectl create clusterrole pod-reader --verb=get,list,watch --resource=pods --resource-name=readablepod\x00\n\t\t# \u521b\u5efa\u4e00\u4e2a\u540d\u4e3a my-quota \u7684 resourcequota\n\t\tkubectl create quota my-quota --hard=cpu=1,memory=1G,pods=2,services=3,replicationcontrollers=2,resourcequotas=1,secrets=5,persistentvolumeclaims=10\n\n\t\t# \u521b\u5efa\u4e00\u4e2a\u540d\u4e3a best-effort \u7684 resourcequota\n\t\tkubectl create quota best-effort --hard=pods=100 --scopes=BestEffort\x00\n\t\t# \u521b\u5efa\u4e00\u4e2a\u540d\u79f0\u4e3a my-pdb \u7684 pod disruption budget \u5e76\u5c06\u4f1a\u9009\u62e9\u6240\u6709 app=rails \u6807\u7b7e\u7684 pods\n\t\t# \u5e76\u8981\u6c42\u4ed6\u4eec\u5728\u540c\u4e00\u65f6\u95f4\u4e2d\u6700\u5c11\u6709\u4e00\u4e2a\u53ef\u7528. \n\t\tkubectl create poddisruptionbudget my-pdb --selector=app=rails --min-available=1\n\n\t\t# \u521b\u5efa\u4e00\u4e2a\u540d\u79f0\u4e3a my-pdb \u7684 pod disruption budget \u5e76\u5c06\u4f1a\u9009\u62e9\u6240\u6709 app=rails \u6807\u7b7e\u7684 pods\n\t\t# \u5e76\u8981\u6c42\u4ed6\u4eec\u5728\u540c\u4e00\u65f6\u95f4\u4e2d\u6700\u5c11\u6709\u4e00\u534a\u53ef\u7528.\n\t\tkubectl create pdb my-pdb --selector=app=nginx --min-available=50%\x00\n\t\t# \u4f7f\u7528\u5728 pod.json \u7684 \u6570\u636e\u521b\u5efa\u4e00\u4e2a pod.\n\t\tkubectl create -f ./pod.json\n\n\t\t# \u6839\u636e\u4f20\u5165 stdin \u7684 JSON \u521b\u5efa\u4e00\u4e2a pod.\n\t\tcat pod.json | kubectl create -f -\n\n\t\t# \u4f7f\u7528 v1 API \u683c\u5f0f\u5728 JSON \u4e2d\u7f16\u8f91\u5728 docker-registry.yaml \u4e2d\u7684\u6570\u636e\u7136\u540e\u4f7f\u7528\u88ab\u7f16\u8f91\u540e\u7684\u6570\u636e\u521b\u5efa\u8d44\u6e90.\n\t\tkubectl create -f docker-registry.yaml --edit --output-version=v1 -o json\x00\n\t\t# \u4e3a\u4e00\u4e2a replicated nginx \u521b\u5efa\u4e00\u4e2a service, \u670d\u52a1\u5728\u7aef\u53e3 80 \u5e76\u8fde\u63a5\u5230 containers \u76848000\u7aef\u53e3.\n\t\tkubectl expose rc nginx --port=80 --target-port=8000\n\n\t\t# \u4f7f\u7528\u5728 \"nginx-controller.yaml\\ \u4e2d\u6307\u5b9a\u7684 type \u548c name \u4e3a\u4e00\u4e2areplication controller \u521b\u5efa\u4e00\u4e2a service, \u670d\u52a1\u5728\u7aef\u53e3 80 \u5e76\u8fde\u63a5\u5230 containers \u76848000\u7aef\u53e3.\n\t\tkubectl expose -f nginx-controller.yaml --port=80 --target-port=8000\n\n\t\t# \u4e3a\u540d\u4e3a valid-pod \u7684 pod \u521b\u5efa\u4e00\u4e2a service, \u670d\u52a1\u5728\u7aef\u53e3 444 \u5e76\u547d\u540d\u4e3a \"frontend\" \n\t\tkubectl expose pod valid-pod --port=444 --name=frontend\n\n\t\t# \u57fa\u4e8e\u4e0a\u9762\u7684 service \u521b\u5efa\u7b2c\u4e8c\u4e2a service, \u66b4\u9732\u5bb9\u5668\u7aef\u53e3 8443 \u5e76\u547d\u540d\u4e3a \"nginx-https\" \u7aef\u53e3\u4e3a 443 \n\t\tkubectl expose service nginx --port=443 --target-port=8443 --name=nginx-https\n\n\t\t# \u4e3a\u4e00\u4e2a\u540d\u79f0\u4e3a streaming \u7684\u5e94\u7528\u521b\u5efa\u4e00\u4e2a service \u66b4\u9732\u7aef\u53e3 4100, \u534f\u8bae\u4e3a UDP \u540d\u79f0\u4e3a 'video-stream'.\n\t\tkubectl expose rc streamer --port=4100 --protocol=udp --name=video-stream\n\n\t\t# \u4e3a\u4e00\u4e2a\u540d\u79f0\u4e3a nginx \u7684 replica set \u521b\u5efa\u4e00\u4e2a service, \u670d\u52a1\u5728 \u7aef\u53e3 80 \u4e14\u8fde\u63a5\u5230\u5bb9\u5668\u7aef\u53e3 8000.\n\t\tkubectl expose rs nginx --port=80 --target-port=8000\n\n\t\t# \u4e3a\u4e00\u4e2a\u540d\u79f0\u4e3a nginx \u7684 deployment \u521b\u5efa\u4e00\u4e2a service, \u670d\u52a1\u5728\u7aef\u53e3 80 \u4e14 \u8fde\u63a5\u5230 containers \u7684 8000 \u7aef\u53e3.\n\t\tkubectl expose deployment nginx --port=80 --target-port=8000\x00\n\t\t# \u4f7f\u7528 pod.json \u4e2d\u7684\u7c7b\u578b\u548c\u540d\u79f0\u5220\u9664\u4e00\u4e2a pod.\n\t\tkubectl delete -f ./pod.json\n\n\t\t# \u57fa\u4e8e\u91cd\u5b9a\u5411\u5230 stdin \u4e2d\u7684 JSON \u7684\u7c7b\u578b\u548c\u540d\u79f0\u5220\u9664\u4e00\u4e2a pod.\n\t\tcat pod.json | kubectl delete -f -\n\n\t\t# \u5220\u9664\u540d\u4e3a \"baz\" \u548c \"foo\" \u7684 pod \u548c service\n\t\tkubectl delete pod,service baz foo\n\n\t\t# \u5220\u9664\u6807\u7b7e\u4e3a name=myLabel \u7684 pods \u548c services.\n\t\tkubectl delete pods,services -l name=myLabel\n\n\t\t# \u5220\u9664\u6700\u5c0f\u5ef6\u8fdf\u7684 pod\n\t\tkubectl delete pod foo --now\n\n\t\t# \u5f3a\u5236\u5220\u9664\u540d\u4e3a foo \u7684 pod\n\t\tkubectl delete pod foo --grace-period=0 --force\n\n\t\t# \u5220\u9664\u6240\u6709 pods\n\t\tkubectl delete pods --all\x00\n\t\t# \u63cf\u8ff0\u4e00\u4e2a node\n\t\tkubectl describe nodes kubernetes-node-emt8.c.myproject.internal\n\n\t\t# \u63cf\u8ff0\u4e00\u4e2a pod\n\t\tkubectl describe pods/nginx\n\n\t\t# \u63cf\u8ff0\u4e00\u4e2a\u88ab \"pod.json\" \u4e2d\u7684\u7c7b\u578b\u548c\u540d\u79f0\u6807\u8bc6\u7684 pod\n\t\tkubectl describe -f pod.json\n\n\t\t# \u63cf\u8ff0\u6240\u6709 pods\n\t\tkubectl describe pods\n\n\t\t# \u63cf\u8ff0\u6807\u7b7e\u4e3a name=myLabel \u7684 pods\n\t\tkubectl describe po -l name=myLabel\n\n\t\t# \u63cf\u8ff0\u6240\u6709\u88ab\u540d\u79f0\u4e3a 'frontend' \u7684 replication controller \u7ba1\u7406\u7684 pods(rc-\u521b\u5efa pods\n\t\t# \u5e76\u4f7f\u7528 rc \u7684\u540d\u79f0\u4f5c\u4e3a pod \u7684\u524d\u7f00).\n\t\tkubectl describe pods frontend\x00\n\t\t# \u9a71\u9010\u8282\u70b9 \"foo\", \u5373\u4f7f\u5f88\u591a pods \u6ca1\u6709\u88ab\u4e00\u4e2a\u5728 node \u4e0a\u7684 ReplicationController, ReplicaSet, Job, DaemonSet \u6216\u8005 StatefulSet \u7ba1\u7406.\n\t\t$ kubectl drain foo --force\n\n\t\t# \u540c\u4e0a, \u5982\u679c\u5b58\u5728 pods \u6ca1\u6709\u88ab\u4e00\u4e2a ReplicationController, ReplicaSet, Job, DaemonSet \u6216\u8005 StatefulSet \u7ba1\u7406\u8d85\u8fc7 15 \u5206\u949f\u5219\u9000\u51fa.\n\t\t$ kubectl drain foo --grace-period=900\x00\n\t\t# \u7f16\u8f91\u540d\u4e3a 'docker-registry' \u7684 service:\n\t\tkubectl edit svc/docker-registry\n\n\t\t# \u4f7f\u7528\u4e00\u4e2a\u53ef\u9009\u62e9\u7684\u7f16\u8f91\u5668\n\t\tKUBE_EDITOR=\"nano\" kubectl edit svc/docker-registry\n\n\t\t# \u4f7f\u7528 v1 API \u683c\u5f0f\u7684 JSON \u7f16\u8f91\u540d\u4e3a 'myjob' \u7684 job:\n\t\tkubectl edit job.v1.batch/myjob -o json\n\n\t\t# \u5728 YAML \u4e2d\u7f16\u8f91\u540d\u4e3a 'mydeployment' \u7684 deployment \u5e76\u5728\u5b83\u7684\u6ce8\u89e3\u4e2d\u4fdd\u5b58\u4fee\u6539\u540e\u7684\u914d\u7f6e:\n\t\tkubectl edit deployment/mydeployment -o yaml --save-config\x00\n\t\t# \u4ece\u8fd0\u884c\u4e2dpod 123456-7890 \u83b7\u53d6\u6267\u884c 'date' \u7684\u8f93\u51fa, \u9ed8\u8ba4\u4f7f\u7528\u7b2c\u4e00\u4e2a\u5bb9\u5668\n\t\tkubectl exec 123456-7890 date\n\n\t\t# \u4ece pod 123456-7890 \u7684\u5bb9\u5668 ruby-container \u83b7\u53d6\u6267\u884c 'date' \u7684\u8f93\u51fa\n\t\tkubectl exec 123456-7890 -c ruby-container date\n\n\t\t# \u5207\u6362\u5230 terminal \u6a21\u5f0f, \u53d1\u9001 stdin \u5230\u8fd0\u884c\u5728 pod 123456-7890 \u7684\u5bb9\u5668 ruby-container 'bash' \n\t\t# \u5e76\u4ece 'bash' \u53d1\u9001 stdout/stderr \u8fd4\u56de\u5230 client\n\t\tkubectl exec 123456-7890 -c ruby-container -i -t -- bash -il\x00\n\t\t# \u4ece\u8fd0\u884c\u4e2dpod 123456-7890 \u83b7\u53d6\u6267\u884c 'date' \u7684\u8f93\u51fa, \u9ed8\u8ba4\u4f7f\u7528\u7b2c\u4e00\u4e2a\u5bb9\u5668\n\t\tkubectl attach 123456-7890\n\n\t\t# \u4ece pod 123456-7890 \u7684\u5bb9\u5668 ruby-container \u83b7\u53d6\u8f93\u51fa\n\t\tkubectl attach 123456-7890 -c ruby-container\n\n\t\t# \u5207\u6362\u5230 terminal \u6a21\u5f0f, \u53d1\u9001 stdin \u5230\u8fd0\u884c\u5728 pod 123456-7890 \u7684\u5bb9\u5668 ruby-container 'bash' \n\t\t# \u5e76\u4ece 'bash' \u53d1\u9001 stdout/stderr \u8fd4\u56de\u5230 client\n\t\tkubectl attach 123456-7890 -c ruby-container -i -t\n\n\t\t# \u4ece\u540d\u79f0\u4e3a nginx \u7684 ReplicaSet \u83b7\u53d6\u7b2c\u4e00\u4e2a pod \u7684\u8f93\u51fa\n\t\tkubectl attach rs/nginx\n\t\t\x00\n\t\t# \u83b7\u53d6\u8d44\u6e90\u53ca\u5176\u5b57\u6bb5\u7684\u6587\u6863\n\t\tkubectl explain pods\n\n\t\t# \u83b7\u53d6\u8d44\u6e90\u6307\u5b9a\u5b57\u6bb5\u7684\u6587\u6863\n\t\tkubectl explain pods.spec.containers\x00\n\t\t# \u5728\u4e00\u4e2a Mac \u4e2d\u4f7f\u7528 homebrew \u5b89\u88c5 bash \u8865\u5168\n\t\tbrew install bash-completion\n\t\tprintf \"\n# Bash \u8865\u5168\u652f\u6301\nsource $(brew --prefix)/etc/bash_completion\n\" >> $HOME/.bash_profile\n\t\tsource $HOME/.bash_profile\n\n\t\t# \u5bfc\u5165 kubectl \u8865\u5168\u4ee3\u7801\u5230\u5f53\u524d shell\n\t\tsource <(kubectl completion bash)\n\n\t\t# \u5199\u5165 bash \u8865\u5168\u4ee3\u7801\u5230\u4e00\u4e2a\u6587\u4ef6\u5e76 source \u5982\u679c\u5b83\u662f .bash_profile\n\t\tkubectl completion bash > ~/.kube/completion.bash.inc\n\t\tprintf \"\n# Kubectl shell \u8865\u5168\nsource '$HOME/.kube/completion.bash.inc'\n\" >> $HOME/.bash_profile\n\t\tsource $HOME/.bash_profile\n\n\t\t# \u4e3a zsh[1] \u5bfc\u5165 kubectl \u8865\u5168\u4ee3\u7801\u5230\u5f53\u524d shell\n\t\tsource <(kubectl completion zsh)\x00\n\t\t# \u4ee5 ps \u8f93\u51fa\u683c\u5f0f\u5217\u51fa\u6240\u6709 pod.\n\t\tkubectl get pods\n\n\t\t# \u4ee5 ps \u8f93\u51fa\u683c\u5f0f\u5217\u51fa\u6240\u6709 pod(\u5982\u8282\u70b9\u540d\u79f0).\n\t\tkubectl get pods -o wide\n\n\t\t# \u83b7\u53d6\u540d\u79f0\u4e3a web \u7684 replicationcontroller.\n\t\tkubectl get replicationcontroller web\n\n\t\t# \u4f7f\u7528 JSON \u683c\u5f0f\u5316\u8f93\u51fa\u663e\u793a\u4e00\u4e2a\u5355\u72ec\u7684 pod.\n\t\tkubectl get -o json pod web-pod-13je7\n\n\t\t# \u663e\u793a\u4e00\u4e2a\u88ab \"pod.yaml\" \u4e2d\u7684 type \u548c name \u6807\u8bc6\u7684 pod \u5e76\u4f7f\u7528 JSON \u683c\u5f0f\u5316\u8f93\u51fa.\n\t\tkubectl get -f pod.yaml -o json\n\n\t\t# \u53ea\u8fd4\u56de\u88ab\u6307\u5b9a pod \u4e2d phase \u7684\u503c.\n\t\tkubectl get -o template pod/web-pod-13je7 --template={{.status.phase}}\n\n\t\t# \u663e\u793a\u6240\u6709\u7684 replication controllers \u548c services \u5e76\u683c\u5f0f\u5316\u8f93\u51fa.\n\t\tkubectl get rc,services\n\n\t\t# \u663e\u793a\u4e00\u4e2a\u6216\u8005\u66f4\u591a resources \u901a\u8fc7\u5b83\u4eec\u7684 type \u548c names.\n\t\tkubectl get rc/web service/frontend pods/web-pod-13je7\n\n\t\t# \u4f7f\u7528\u4e0d\u540c\u7684 types \u663e\u793a\u6240\u6709 resources.\n\t\tkubectl get all\x00\n\t\t# \u5728\u672c\u5730\u76d1\u542c\u7aef\u53e3 5000 \u548c 6000 , forwarding \u6570\u636e to/from \u5728 pod 5000 \u548c 6000 \u7aef\u53e3\n\t\tkubectl port-forward mypod 5000 6000\n\n\t\t# \u5728\u672c\u5730\u76d1\u542c\u7aef\u53e3 8888 , forwarding \u5230 pod \u7684 5000\u7aef\u53e3\n\t\tkubectl port-forward mypod 8888:5000\n\n\t\t# \u5728\u672c\u5730\u968f\u673a\u76d1\u542c\u4e00\u4e2a\u7aef\u53e3 , forwarding \u5230 pod \u7684 5000\u7aef\u53e3\n\t\tkubectl port-forward mypod :5000\n\n\t\t# \u5728\u672c\u5730\u968f\u673a\u76d1\u542c\u4e00\u4e2a\u7aef\u53e3 , forwarding \u5230 pod \u7684 5000\u7aef\u53e3\n\t\tkubectl port-forward mypod 0:5000\x00\n\t\t# \u6807\u8bb0 node \"foo\" \u4e3a schedulable.\n\t\t$ kubectl uncordon foo\x00\n\t\t# \u6807\u8bb0 node \"foo\" \u4e3a unschedulable.\n\t\tkubectl cordon foo\x00\n\t\t# \u4f7f\u7528 strategic merge patch \u90e8\u5206\u66f4\u65b0\u4e00\u4e2a node\n\t\tkubectl patch node k8s-node-1 -p '{\"spec\":{\"unschedulable\":true}}'\n\n\t\t# \u4f7f\u7528 strategic merge patch \u90e8\u5206\u66f4\u65b0\u4e00\u4e2a\u88ab \"node.json\" \u7684 type \u548c name \u6807\u793a \u7684 node.\n\t\tkubectl patch -f node.json -p '{\"spec\":{\"unschedulable\":true}}'\n\n\t\t# \u66f4\u65b0\u4e00\u4e2a container \u7684 image; spec.containers[*].name \u662f\u5fc5\u987b\u7684 \u56e0\u4e3a\u5b83\u662f\u4e00\u4e2a merge key\n\t\tkubectl patch pod valid-pod -p '{\"spec\":{\"containers\":[{\"name\":\"kubernetes-serve-hostname\",\"image\":\"new image\"}]}}'\n\n\t\t# \u4f7f\u7528\u4e00\u4e2a json patch \u66f4\u65b0\u4e00\u4e2a\u6307\u5b9a\u5750\u6807\u7684 container \u7684 image \n\t\tkubectl patch pod valid-pod --type='json' -p='[{\"op\": \"replace\", \"path\": \"/spec/containers/0/image\", \"value\":\"new image\"}]'\x00\n\t\t# \u8f93\u51fa\u6240\u6709\u547d\u4ee4\u7ee7\u627f\u7684 flags\n\t\tkubectl options\x00\n\t\t# \u8f93\u51fa master \u548c cluster services \u7684\u5730\u5740\n\t\tkubectl cluster-info\x00\n\t\t# \u8f93\u51fa\u5f53\u524d client \u548c server \u7248\u672c\n\t\tkubectl version\x00\n\t\t# \u8f93\u51fa\u652f\u6301\u7684 API \u7248\u672c\n\t\tkubectl api-versions\x00\n\t\t# \u4f7f\u7528\u5728 pod.json \u4e2d\u7684\u6570\u636e\u66ff\u6362\u4e00\u4e2a pod.\n\t\tkubectl replace -f ./pod.json\n\n\t\t# \u57fa\u4e8e\u88ab\u91cd\u5b9a\u5411\u5230 stdin \u4e2d\u7684 JSON \u66ff\u6362\u4e00\u4e2a pod.\n\t\tcat pod.json | kubectl replace -f -\n\n\t\t# \u66f4\u65b0\u4e00\u4e2a\u5355\u72ec\u5bb9\u5668\u7684 pod \u7684 image \u7248\u672c (tag) \u5230 v4\n\t\tkubectl get pod mypod -o yaml | sed 's/\\(image: myimage\\):.*$/\x01:v4/' | kubectl replace -f -\n\n\t\t# \u5f3a\u5236\u66ff\u6362, \u5220\u9664\u7136\u540e\u91cd\u65b0\u521b\u5efa\u8fd9\u4e2a resource\n\t\tkubectl replace --force -f ./pod.json\x00\n\t\t# \u8fd4\u56de\u4ec5\u6709\u4e00\u4e2a\u5bb9\u5668 pod \u540d\u79f0\u4e3a nginx \u7684 snapshot \u65e5\u5fd7\n\t\tkubectl logs nginx\n\n\t\t# \u8fd4\u56de label \u4e3a app=nginx \u7684 pods \u7684 snapshot \u65e5\u5fd7\n\t\tkubectl logs -lapp=nginx\n\n\t\t# Return snapshot of previous terminated ruby container logs from pod web-1\n\t\tkubectl logs -p -c ruby web-1\n\n\t\t# Begin streaming the logs of the ruby container in pod web-1\n\t\tkubectl logs -f -c ruby web-1\n\n\t\t# Display only the most recent 20 lines of output in pod nginx\n\t\tkubectl logs --tail=20 nginx\n\n\t\t# Show all logs from pod nginx written in the last hour\n\t\tkubectl logs --since=1h nginx\n\n\t\t# Return snapshot logs from first container of a job named hello\n\t\tkubectl logs job/hello\n\n\t\t# Return snapshot logs from container nginx-1 of a deployment named nginx\n\t\tkubectl logs deployment/nginx -c nginx-1\x00\n\t\t# \u8fd0\u884c proxy \u5230 kubernetes apiserver \u7684 8011 \u7aef\u53e3\u4e0a, \u670d\u52a1\u9759\u6001\u5185\u5bb9\u8def\u5f84\u4e3a ./local/www/\n\t\tkubectl proxy --port=8011 --www=./local/www/\n\n\t\t# \u5728\u4efb\u610f\u7684\u672c\u5730\u7aef\u53e3\u4e0a\u8fd0\u884c\u4e00\u4e2a proxy \u5230 kubernetes apiserver.\n\t\t# \u4e3a\u8fd9\u4e2a server \u6311\u9009\u7684\u7aef\u53e3\u5c06\u4f1a\u88ab\u8f93\u51fa\u5230 stdout.\n\t\tkubectl proxy --port=0\n\n\t\t# \u8fd0\u884c\u4e00\u4e2a proxy \u5230 kubernetes apiserver, \u4fee\u6539 api prefix \u4e3a k8s-api\n\t\t# \u8fd9\u4f1a\u4f7f e.g. \u8fd9\u4e2a pods \u7684\u6709\u6548 api \u4e3a localhost:8001/k8s-api/v1/pods/\n\t\tkubectl proxy --api-prefix=/k8s-api\x00\n\t\t# Scale \u4e00\u4e2a\u540d\u79f0\u4e3a \u2018foo\u2019 \u7684 replicaset \u670d\u672c\u6570\u4e3a 3.\n\t\tkubectl scale --replicas=3 rs/foo\n\n\t\t# Scale \u6307\u5b9a\u7684 \"foo.yaml\" \u7684 type \u548c name \u6807\u8bc6\u7684 resource \u526f\u672c\u6570\u91cf\u4e3a 3.\n\t\tkubectl scale --replicas=3 -f foo.yaml\n\n\t\t# \u5982\u679c\u540d\u79f0\u4e3a mysql \u7684 deployment \u5f53\u524d\u526f\u672c\u6570\u91cf\u4e3a 2, scale mysql \u5230 3.\n\t\tkubectl scale --current-replicas=2 --replicas=3 deployment/mysql\n\n\t\t# Scale \u591a\u4e2a replication controllers.\n\t\tkubectl scale --replicas=5 rc/foo rc/bar rc/baz\n\n\t\t# Scale \u540d\u79f0\u4e3a \u2019cron\u2019 \u7684 job \u526f\u672c\u6570\u91cf\u4e3a 3.\n\t\tkubectl scale --replicas=3 job/cron\x00\n\t\t# \u8bbe\u7f6e\u4e00\u4e2a\u8d44\u6e90\u7684 last-applied-configuration \u53bb\u5339\u914d\u4e00\u4e2a\u6587\u4ef6\u7684\u5185\u5bb9.\n\t\tkubectl apply set-last-applied -f deploy.yaml\n\n\t\t# Execute set-last-applied against each configuration file in a directory.\n\t\tkubectl apply set-last-applied -f path/\n\n\t\t# \u8bbe\u7f6e\u4e00\u4e2a\u8d44\u6e90\u7684 last-applied-configuration \u53bb\u5339\u914d\u4e00\u4e2a\u6587\u4ef6\u7684\u5185\u5bb9, \u5982\u679c\u4e0d\u5b58\u5728\u5c06\u4f1a\u521b\u5efa\u4e00\u4e2a annotation.\n\t\tkubectl apply set-last-applied -f deploy.yaml --create-annotation=true\n\t\t\x00\n\t\t# \u663e\u793a default namespace \u4e0b\u6240\u6709 pods \u4e0b\u7684 metrics\n\t\tkubectl top pod\n\n\t\t# \u663e\u793a\u6307\u5b9a namespace \u4e0b\u6240\u6709 pods \u7684 metrics\n\t\tkubectl top pod --namespace=NAMESPACE\n\n\t\t# \u663e\u793a\u6307\u5b9a pod \u548c\u5b83\u7684\u5bb9\u5668\u7684 metrics\n\t\tkubectl top pod POD_NAME --containers\n\n\t\t# \u663e\u793a\u6307\u5b9a label \u4e3a name=myLabel \u7684 pods \u7684 metrics\n\t\tkubectl top pod -l name=myLabel\x00\n\t\t# Shut down foo.\n\t\tkubectl stop replicationcontroller foo\n\n\t\t# Stop pods and services with label name=myLabel.\n\t\tkubectl stop pods,services -l name=myLabel\n\n\t\t# Shut down the service defined in service.json\n\t\tkubectl stop -f service.json\n\n\t\t# Shut down all resources in the path/to/resources directory\n\t\tkubectl stop -f path/to/resources\x00\n\t\t# Start a single instance of nginx.\n\t\tkubectl run nginx --image=nginx\n\n\t\t# Start a single instance of hazelcast and let the container expose port 5701 .\n\t\tkubectl run hazelcast --image=hazelcast --port=5701\n\n\t\t# Start a single instance of hazelcast and set environment variables \"DNS_DOMAIN=cluster\" and \"POD_NAMESPACE=default\" in the container.\n\t\tkubectl run hazelcast --image=hazelcast --env=\"DNS_DOMAIN=cluster\" --env=\"POD_NAMESPACE=default\"\n\n\t\t# Start a replicated instance of nginx.\n\t\tkubectl run nginx --image=nginx --replicas=5\n\n\t\t# Dry run. Print the corresponding API objects without creating them.\n\t\tkubectl run nginx --image=nginx --dry-run\n\n\t\t# Start a single instance of nginx, but overload the spec of the deployment with a partial set of values parsed from JSON.\n\t\tkubectl run nginx --image=nginx --overrides='{ \"apiVersion\": \"v1\", \"spec\": { ... } }'\n\n\t\t# Start a pod of busybox and keep it in the foreground, don't restart it if it exits.\n\t\tkubectl run -i -t busybox --image=busybox --restart=Never\n\n\t\t# Start the nginx container using the default command, but use custom arguments (arg1 .. argN) for that command.\n\t\tkubectl run nginx --image=nginx -- ... \n\n\t\t# Start the nginx container using a different command and custom arguments.\n\t\tkubectl run nginx --image=nginx --command -- ... \n\n\t\t# Start the perl container to compute \u03c0 to 2000 places and print it out.\n\t\tkubectl run pi --image=perl --restart=OnFailure -- perl -Mbignum=bpi -wle 'print bpi(2000)'\n\n\t\t# Start the cron job to compute \u03c0 to 2000 places and print it out every 5 minutes.\n\t\tkubectl run pi --schedule=\"0/5 * * * ?\" --image=perl --restart=OnFailure -- perl -Mbignum=bpi -wle 'print bpi(2000)'\x00\n\t\t# Update node 'foo' with a taint with key 'dedicated' and value 'special-user' and effect 'NoSchedule'.\n\t\t# If a taint with that key and effect already exists, its value is replaced as specified.\n\t\tkubectl taint nodes foo dedicated=special-user:NoSchedule\n\n\t\t# Remove from node 'foo' the taint with key 'dedicated' and effect 'NoSchedule' if one exists.\n\t\tkubectl taint nodes foo dedicated:NoSchedule-\n\n\t\t# Remove from node 'foo' all the taints with key 'dedicated'\n\t\tkubectl taint nodes foo dedicated-\x00\n\t\t# Update pod 'foo' with the label 'unhealthy' and the value 'true'.\n\t\tkubectl label pods foo unhealthy=true\n\n\t\t# Update pod 'foo' with the label 'status' and the value 'unhealthy', overwriting any existing value.\n\t\tkubectl label --overwrite pods foo status=unhealthy\n\n\t\t# Update all pods in the namespace\n\t\tkubectl label pods --all status=unhealthy\n\n\t\t# Update a pod identified by the type and name in \"pod.json\"\n\t\tkubectl label -f pod.json status=unhealthy\n\n\t\t# Update pod 'foo' only if the resource is unchanged from version 1.\n\t\tkubectl label pods foo status=unhealthy --resource-version=1\n\n\t\t# Update pod 'foo' by removing a label named 'bar' if it exists.\n\t\t# Does not require the --overwrite flag.\n\t\tkubectl label pods foo bar-\x00\n\t\t# Update pods of frontend-v1 using new replication controller data in frontend-v2.json.\n\t\tkubectl rolling-update frontend-v1 -f frontend-v2.json\n\n\t\t# Update pods of frontend-v1 using JSON data passed into stdin.\n\t\tcat frontend-v2.json | kubectl rolling-update frontend-v1 -f -\n\n\t\t# Update the pods of frontend-v1 to frontend-v2 by just changing the image, and switching the\n\t\t# name of the replication controller.\n\t\tkubectl rolling-update frontend-v1 frontend-v2 --image=image:v2\n\n\t\t# Update the pods of frontend by just changing the image, and keeping the old name.\n\t\tkubectl rolling-update frontend --image=image:v2\n\n\t\t# Abort and reverse an existing rollout in progress (from frontend-v1 to frontend-v2).\n\t\tkubectl rolling-update frontend-v1 frontend-v2 --rollback\x00\n\t\t# View the last-applied-configuration annotations by type/name in YAML.\n\t\tkubectl apply view-last-applied deployment/nginx\n\n\t\t# View the last-applied-configuration annotations by file in JSON\n\t\tkubectl apply view-last-applied -f deploy.yaml -o json\x00\n\t\t\u901a\u8fc7\u6587\u4ef6\u540d\u6216\u6807\u51c6\u8f93\u5165\u6d41(stdin)\u5bf9\u8d44\u6e90\u8fdb\u884c\u914d\u7f6e.\n\t\tThis resource will be created if it doesn't exist yet.\n\t\tTo use 'apply', always create the resource initially with either 'apply' or 'create --save-config'.\n\n\t\tJSON and YAML formats are accepted.\n\n\t\tAlpha Disclaimer: the --prune functionality is not yet complete. Do not use unless you are aware of what the current state is. See https://issues.k8s.io/34274.\x00\n\t\t\u5728\u4e0d\u540c\u7684 API versions \u8f6c\u6362\u914d\u7f6e\u6587\u4ef6. \u63a5\u53d7 YAML\n\t\t\u548c JSON \u683c\u5f0f.\n\n\t\t\u8fd9\u4e2a\u547d\u4ee4\u4ee5 filename, directory, \u6216\u8005 URL \u4f5c\u4e3a\u8f93\u5165, \u5e76\u901a\u8fc7 \u2014output-version flag\n\t\t \u8f6c\u6362\u5230\u6307\u5b9a\u7248\u672c\u7684\u683c\u5f0f. \u5982\u679c\u76ee\u6807\u7248\u672c\u6ca1\u6709\u88ab\u6307\u5b9a\u6216\u8005\n\t\t\u4e0d\u652f\u6301, \u8f6c\u6362\u5230\u6700\u540e\u7684\u7248\u672c.\n\n\t\t\u9ed8\u8ba4\u4ee5 YAML \u683c\u5f0f\u8f93\u51fa\u5230 stdout. \u53ef\u4ee5\u4f7f\u7528 -o option\n\t\t\u4fee\u6539\u76ee\u6807\u8f93\u51fa\u7684\u683c\u5f0f.\x00\n\t\t\u521b\u5efa\u4e00\u4e2a ClusterRole.\x00\n\t\t \u4e3a\u6307\u5b9a\u7684 ClusterRole \u521b\u5efa\u4e00\u4e2a ClusterRoleBinding.\x00\n\t\t\u4e3a\u6307\u5b9a\u7684 Role \u6216\u8005 ClusterRole \u521b\u5efa\u4e00\u4e2a RoleBinding.\x00\n\t\t\u4e3a\u6307\u5b9a\u7684 public/private key pair \u521b\u5efa\u4e00\u4e2a TLS secret.\n\n\t\tpublic/private key pair \u5fc5\u987b\u5728\u4f20\u9012\u524d\u5b58\u5728. public key certificate \u5fc5\u987b\u4ee5 .PEM \u88ab\u7f16\u7801\u4e14\u5339\u914d\u6307\u5b9a\u7684 private key.\x00\n\t\tCreate a configmap based on a file, directory, or specified literal value.\n\n\t\tA single configmap may package one or more key/value pairs.\n\n\t\tWhen creating a configmap based on a file, the key will default to the basename of the file, and the value will\n\t\tdefault to the file content. If the basename is an invalid key, you may specify an alternate key.\n\n\t\tWhen creating a configmap based on a directory, each file whose basename is a valid key in the directory will be\n\t\tpackaged into the configmap. Any directory entries except regular files are ignored (e.g. subdirectories,\n\t\tsymlinks, devices, pipes, etc).\x00\n\t\t\u521b\u5efa\u4e00\u4e2a namespace \u5e76\u6307\u5b9a\u540d\u79f0.\x00\n\t\tCreate a new secret for use with Docker registries.\n\n\t\tDockercfg secrets are used to authenticate against Docker registries.\n\n\t\tWhen using the Docker command line to push images, you can authenticate to a given registry by running\n\n\t\t $ docker login DOCKER_REGISTRY_SERVER --username=DOCKER_USER --password=DOCKER_PASSWORD --email=DOCKER_EMAIL'.\n\n That produces a ~/.dockercfg file that is used by subsequent 'docker push' and 'docker pull' commands to\n\t\tauthenticate to the registry. The email address is optional.\n\n\t\tWhen creating applications, you may have a Docker registry that requires authentication. In order for the\n\t\tnodes to pull images on your behalf, they have to have the credentials. You can provide this information\n\t\tby creating a dockercfg secret and attaching it to your service account.\x00\n\t\tCreate a pod disruption budget with the specified name, selector, and desired minimum available pods\x00\n\t\t\u901a\u8fc7\u6587\u4ef6\u540d\u6216\u8005\u6807\u51c6\u8f93\u5165\u6d41(stdin)\u521b\u5efa\u4e00\u4e2a\u8d44\u6e90.\n\n\t\tJSON and YAML formats are accepted.\x00\n\t\tCreate a resourcequota with the specified name, hard limits and optional scopes\x00\n\t\t\u521b\u5efa\u5355\u4e00 rule \u7684 role.\x00\n\t\tCreate a secret based on a file, directory, or specified literal value.\n\n\t\tA single secret may package one or more key/value pairs.\n\n\t\tWhen creating a secret based on a file, the key will default to the basename of the file, and the value will\n\t\tdefault to the file content. If the basename is an invalid key, you may specify an alternate key.\n\n\t\tWhen creating a secret based on a directory, each file whose basename is a valid key in the directory will be\n\t\tpackaged into the secret. Any directory entries except regular files are ignored (e.g. subdirectories,\n\t\tsymlinks, devices, pipes, etc).\x00\n\t\t\u521b\u5efa\u4e00\u4e2a\u6307\u5b9a\u540d\u79f0\u7684 service account.\x00\n\t\tCreate and run a particular image, possibly replicated.\n\n\t\tCreates a deployment or job to manage the created container(s).\x00\n\t\tCreates an autoscaler that automatically chooses and sets the number of pods that run in a kubernetes cluster.\n\n\t\tLooks up a Deployment, ReplicaSet, or ReplicationController by name and creates an autoscaler that uses the given resource as a reference.\n\t\tAn autoscaler can automatically increase or decrease number of pods deployed within the system as needed.\x00\n\t\tDelete resources by filenames, stdin, resources and names, or by resources and label selector.\n\n\t\tJSON and YAML formats are accepted. Only one type of the arguments may be specified: filenames,\n\t\tresources and names, or resources and label selector.\n\n\t\tSome resources, such as pods, support graceful deletion. These resources define a default period\n\t\tbefore they are forcibly terminated (the grace period) but you may override that value with\n\t\tthe --grace-period flag, or pass --now to set a grace-period of 1. Because these resources often\n\t\trepresent entities in the cluster, deletion may not be acknowledged immediately. If the node\n\t\thosting a pod is down or cannot reach the API server, termination may take significantly longer\n\t\tthan the grace period. To force delete a resource,\tyou must pass a grace\tperiod of 0 and specify\n\t\tthe --force flag.\n\n\t\tIMPORTANT: Force deleting pods does not wait for confirmation that the pod's processes have been\n\t\tterminated, which can leave those processes running until the node detects the deletion and\n\t\tcompletes graceful deletion. If your processes use shared storage or talk to a remote API and\n\t\tdepend on the name of the pod to identify themselves, force deleting those pods may result in\n\t\tmultiple processes running on different machines using the same identification which may lead\n\t\tto data corruption or inconsistency. Only force delete pods when you are sure the pod is\n\t\tterminated, or if your application can tolerate multiple copies of the same pod running at once.\n\t\tAlso, if you force delete pods the scheduler may place new pods on those nodes before the node\n\t\thas released those resources and causing those pods to be evicted immediately.\n\n\t\tNote that the delete command does NOT do resource version checks, so if someone\n\t\tsubmits an update to a resource right when you submit a delete, their update\n\t\twill be lost along with the rest of the resource.\x00\n\t\tDeprecated: Gracefully shut down a resource by name or filename.\n\n\t\tThe stop command is deprecated, all its functionalities are covered by delete command.\n\t\tSee 'kubectl delete --help' for more details.\n\n\t\tAttempts to shut down and delete a resource that supports graceful termination.\n\t\tIf the resource is scalable it will be scaled to 0 before deletion.\x00\n\t\t\u663e\u793a node \u7684\u8d44\u6e90(CPU/Memory/Storage)\u4f7f\u7528.\n\n\t\tThe top-node command allows you to see the resource consumption of nodes.\x00\n\t\t\u663e\u793a pods \u8d44\u6e90(CPU/Memory/Storage)\u4f7f\u7528.\n\n\t\tThe 'top pod' command allows you to see the resource consumption of pods.\n\n\t\tDue to the metrics pipeline delay, they may be unavailable for a few minutes\n\t\tsince pod creation.\x00\n\t\t\u663e\u793a\u8d44\u6e90(CPU/Memory/Storage)\u4f7f\u7528.\n\n\t\tThe top command allows you to see the resource consumption for nodes or pods.\n\n\t\tThis command requires Heapster to be correctly configured and working on the server. \x00\n\t\t\u6e05\u7406\u8282\u70b9\u4e3a\u8282\u70b9\u7ef4\u62a4\u505a\u51c6\u5907.\n\n\t\tThe given node will be marked unschedulable to prevent new pods from arriving.\n\t\t'drain' evicts the pods if the APIServer supports eviction\n\t\t(http://kubernetes.io/docs/admin/disruptions/). Otherwise, it will use normal DELETE\n\t\tto delete the pods.\n\t\tThe 'drain' evicts or deletes all pods except mirror pods (which cannot be deleted through\n\t\tthe API server). If there are DaemonSet-managed pods, drain will not proceed\n\t\twithout --ignore-daemonsets, and regardless it will not delete any\n\t\tDaemonSet-managed pods, because those pods would be immediately replaced by the\n\t\tDaemonSet controller, which ignores unschedulable markings. If there are any\n\t\tpods that are neither mirror pods nor managed by ReplicationController,\n\t\tReplicaSet, DaemonSet, StatefulSet or Job, then drain will not delete any pods unless you\n\t\tuse --force. --force will also allow deletion to proceed if the managing resource of one\n\t\tor more pods is missing.\n\n\t\t'drain' waits for graceful termination. You should not operate on the machine until\n\t\tthe command completes.\n\n\t\tWhen you are ready to put the node back into service, use kubectl uncordon, which\n\t\twill make the node schedulable again.\n\n\t\t![Workflow](http://kubernetes.io/images/docs/kubectl_drain.svg)\x00\n\t\t\u4f7f\u7528\u9ed8\u8ba4\u7684\u7f16\u8f91\u5668\u4fee\u6539\u8d44\u6e90.\n\n\t\tedit \u547d\u4ee4\u5141\u8bb8\u4f60\u901a\u8fc7\u547d\u4ee4\u884c\u76f4\u63a5\u4fee\u6539 API \u8d44\u6e90.\n\t\t\u5b83\u4f1a\u6253\u5f00\u4f60\u5728 KUBE_EDITOR \u6216\u8005EDITOR \u73af\u5883\u53d8\u91cf\u4e2d\u5b9a\u4e49\u7684\u7f16\u8f91\u5668\n\t\t\u6216\u8005\u56de\u6eda\u5230 Linux vi \u7f16\u8f91\u5668\u6216\u8005 Windows notepad.\n\t\t\u4f60\u53ef\u4ee5\u4fee\u6539\u591a\u4e2a\u5bf9\u8c61, \u867d\u7136\u6bcf\u6b21\u53ea\u80fd\u4fee\u6539\u4e00\u6b21. \u8fd9\u4e2a\u547d\u4ee4\n\t\t\u540c\u65f6\u4e5f\u63a5\u53d7\u6587\u4ef6\u540d\u4f5c\u4e3a\u547d\u4ee4\u884c\u53c2\u6570, \u5c3d\u7ba1\u8fd9\u4e9b\u6587\u4ef6\u4f60\u6307\u51fa\u5fc5\u987b\u662f\n\t\t\u4f60\u4e4b\u524d\u4fdd\u5b58\u7684\u8d44\u6e90\u7248\u672c.\n\n\t\tEditing \u662f\u901a\u8fc7\u7528\u4e8e\u83b7\u53d6\u8d44\u6e90\u7684API\u7248\u672c\u5b8c\u6210\u7684.\n\t\t\u4e3a\u4e86\u80fd\u901a\u8fc7\u6307\u5b9a\u7684 API \u7248\u672c\u4fee\u6539, \u8bf7\u5b8c\u5168\u9650\u5b9a resource, version \u548c group.\n\n\t\t\u9ed8\u8ba4\u662f YAML \u683c\u5f0f. \u60f3\u5728 JSON \u4e2d\u4fee\u6539, \u6307\u5b9a \"-o json\".\n\n\t\t--windows-line-endings \u547d\u4ee4\u884c\u53c2\u6570\u53ef\u4ee5\u7528\u6765\u5f3a\u5236\u4f7f\u7528 Windows line endings,\n\t\t\u5426\u5219\u4f1a\u4f7f\u7528\u4f60\u64cd\u4f5c\u7cfb\u7edf\u7684\u9ed8\u8ba4\u503c.\n\n\t\t\u5982\u679c\u66f4\u65b0\u65f6\u53d1\u751f\u9519\u8bef\uff0c\u5c06\u5728\u78c1\u76d8\u4e0a\u521b\u5efa\u4e00\u4e2a\u4e34\u65f6\u6587\u4ef6\n\t\t\u91cc\u9762\u5305\u542b\u60a8\u672a\u5e94\u7528\u7684\u66f4\u6539. \u66f4\u65b0\u8d44\u6e90\u65f6\u6700\u5e38\u89c1\u7684\u9519\u8bef\n\t\t\u662f\u53e6\u4e00\u4e2a\u7f16\u8f91\u5668\u4e5f\u5728\u670d\u52a1\u5668\u4e2d\u4fee\u6539\u8fd9\u4e2a\u8d44\u6e90. \u5f53\u53d1\u751f\u8fd9\u79cd\u60c5\u51b5\u65f6, \u4f60\u5c06\n\t\t\u9700\u8981\u5e94\u7528\u4f60\u7684\u4fee\u6539\u5230\u8d44\u6e90\u7684\u6700\u65b0\u7248\u672c, \u6216\u8005\u66f4\u65b0\u4f60\u88ab\u4fdd\u5b58\u7684\u4e34\u65f6\u6587\u4ef6\n\t\t\u590d\u5236\u5b83\u5e76\u4f7f\u7528\u6700\u65b0\u7684\u7248\u672c.\x00\n\t\t\u6807\u8bb0 node \u4e3a schedulable.\x00\n\t\t\u6807\u8bb0 node \u4e3a unschedulable.\x00\n\t\tOutput shell completion code for the specified shell (bash or zsh).\n\t\tThe shell code must be evalutated to provide interactive\n\t\tcompletion of kubectl commands. This can be done by sourcing it from\n\t\tthe .bash_profile.\n\n\t\tNote: this requires the bash-completion framework, which is not installed\n\t\tby default on Mac. This can be installed by using homebrew:\n\n\t\t $ brew install bash-completion\n\n\t\tOnce installed, bash_completion must be evaluated. This can be done by adding the\n\t\tfollowing line to the .bash_profile\n\n\t\t $ source $(brew --prefix)/etc/bash_completion\n\n\t\tNote for zsh users: [1] zsh completions are only supported in versions of zsh >= 5.2\x00\n\t\t\u5b8c\u6210\u6307\u5b9a\u7684 ReplicationController \u7684\u6eda\u52a8\u5347\u7ea7.\n\n\t\tReplaces the specified replication controller with a new replication controller by updating one pod at a time to use the\n\t\tnew PodTemplate. The new-controller.json must specify the same namespace as the\n\t\texisting replication controller and overwrite at least one (common) label in its replicaSelector.\n\n\t\t![Workflow](http://kubernetes.io/images/docs/kubectl_rollingupdate.svg)\x00\n\t\tReplace a resource by filename or stdin.\n\n\t\tJSON and YAML formats are accepted. If replacing an existing resource, the\n\t\tcomplete resource spec must be provided. This can be obtained by\n\n\t\t $ kubectl get TYPE NAME -o yaml\n\n\t\tPlease refer to the models in https://htmlpreview.github.io/?https://github.com/kubernetes/kubernetes/blob/HEAD/docs/api-reference/v1/definitions.html to find if a field is mutable.\x00\n\t\tSet a new size for a Deployment, ReplicaSet, Replication Controller, or Job.\n\n\t\tScale also allows users to specify one or more preconditions for the scale action.\n\n\t\tIf --current-replicas or --resource-version is specified, it is validated before the\n\t\tscale is attempted, and it is guaranteed that the precondition holds true when the\n\t\tscale is sent to the server.\x00\n\t\tSet the latest last-applied-configuration annotations by setting it to match the contents of a file.\n\t\tThis results in the last-applied-configuration being updated as though 'kubectl apply -f ' was run,\n\t\twithout updating any other parts of the object.\x00\n\t\tTo proxy all of the kubernetes api and nothing else, use:\n\n\t\t $ kubectl proxy --api-prefix=/\n\n\t\tTo proxy only part of the kubernetes api and also some static files:\n\n\t\t $ kubectl proxy --www=/my/files --www-prefix=/static/ --api-prefix=/api/\n\n\t\tThe above lets you 'curl localhost:8001/api/v1/pods'.\n\n\t\tTo proxy the entire kubernetes api at a different root, use:\n\n\t\t $ kubectl proxy --api-prefix=/custom/\n\n\t\tThe above lets you 'curl localhost:8001/custom/api/v1/pods'\x00\n\t\tUpdate field(s) of a resource using strategic merge patch\n\n\t\tJSON and YAML formats are accepted.\n\n\t\tPlease refer to the models in https://htmlpreview.github.io/?https://github.com/kubernetes/kubernetes/blob/HEAD/docs/api-reference/v1/definitions.html to find if a field is mutable.\x00\n\t\tUpdate the labels on a resource.\n\n\t\t* A label must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[1]d characters.\n\t\t* If --overwrite is true, then existing labels can be overwritten, otherwise attempting to overwrite a label will result in an error.\n\t\t* If --resource-version is specified, then updates will use this resource version, otherwise the existing resource-version will be used.\x00\n\t\t\u66f4\u65b0\u4e00\u4e2a\u6216\u8005\u591a\u4e2a node \u4e0a\u7684 taints.\n\n\t\t* A taint consists of a key, value, and effect. As an argument here, it is expressed as key=value:effect.\n\t\t* The key must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[1]d characters.\n\t\t* The value must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[2]d characters.\n\t\t* The effect must be NoSchedule, PreferNoSchedule or NoExecute.\n\t\t* Currently taint can only apply to node.\x00\n\t\tView the latest last-applied-configuration annotations by type/name or file.\n\n\t\tThe default output will be printed to stdout in YAML format. One can use -o option\n\t\tto change output format.\x00\n\t # !!!\u6ce8\u610f!!!\n\t # \u8981\u6c42\u5bb9\u5668\u4e2d\u6709 'tar' \u547d\u4ee4\n\t # image. If 'tar' is not present, 'kubectl cp' will fail.\n\n\t # \u590d\u5236\u672c\u5730\u76ee\u5f55 /tmp/foo_dir \u5230 default namespace \u4e0b\u7684\u8fdc\u7a0b pod \u7684 /tmp/bar_dir \u8def\u5f84 \n\t\tkubectl cp /tmp/foo_dir :/tmp/bar_dir\n\n # \u590d\u5236 /tmp/foo local \u672c\u5730\u6587\u4ef6\u5230\u6307\u5b9a\u8fdc\u7a0b pod \u7684\u6307\u5b9a\u5bb9\u5668\u7684 /tmp/bar \u8def\u5f84\n\t\tkubectl cp /tmp/foo :/tmp/bar -c \n\n\t\t# \u590d\u5236 /tmp/foo \u672c\u5730\u6587\u4ef6\u5230\u5728 namespace \u4e0b\u7684\u67d0\u4e2a pod \u7684 /tmp/bar \u8def\u5f84\n\t\tkubectl cp /tmp/foo /:/tmp/bar\n\n\t\t# \u4ece\u4e00\u4e2a\u8fdc\u7a0b\u7684 pod \u7684 /tmp/foo \u8def\u5f84\u590d\u5236\u5230\u672c\u5730 /tmp/bar \u8def\u5f84\n\t\tkubectl cp /:/tmp/foo /tmp/bar\x00\n\t # \u4f7f\u7528\u63d0\u4f9b\u7684 key pair \u540d\u79f0\u4e3atls-secret \u7684 secret:\n\t kubectl create secret tls tls-secret --cert=path/to/tls.cert --key=path/to/tls.key\x00\n\t # \u521b\u5efa\u4e00\u4e2a\u540d\u79f0\u4e3a my-namespace \u7684 namespace\n\t kubectl create namespace my-namespace\x00\n\t # Create a new secret named my-secret with keys for each file in folder bar\n\t kubectl create secret generic my-secret --from-file=path/to/bar\n\n\t # Create a new secret named my-secret with specified keys instead of names on disk\n\t kubectl create secret generic my-secret --from-file=ssh-privatekey=~/.ssh/id_rsa --from-file=ssh-publickey=~/.ssh/id_rsa.pub\n\n\t # Create a new secret named my-secret with key1=supersecret and key2=topsecret\n\t kubectl create secret generic my-secret --from-literal=key1=supersecret --from-literal=key2=topsecret\x00\n\t # Create a new service account named my-service-account\n\t kubectl create serviceaccount my-service-account\x00\n\t# Create a new ExternalName service named my-ns \n\tkubectl create service externalname my-ns --external-name bar.com\x00\n\tCreate an ExternalName service with the specified name.\n\n\tExternalName service references to an external DNS address instead of\n\tonly pods, which will allow application authors to reference services\n\tthat exist off platform, on other clusters, or locally.\x00\n\tHelp provides help for any command in the application.\n\tSimply type kubectl help [path to command] for full details.\x00\n # \u521b\u5efa\u4e00\u4e2a\u540d\u79f0\u4e3a my-lbs \u7684 LoadBalancer service\n kubectl create service loadbalancer my-lbs --tcp=5678:8080\x00\n # \u521b\u5efa\u4e00\u4e2a\u540d\u79f0\u4e3a my-cs \u7684 clusterIP service\n kubectl create service clusterip my-cs --tcp=5678:8080\n\n # \u521b\u5efa\u4e00\u4e2a\u540d\u79f0\u4e3a my-cs \u7684 clusterIP service (\u5728 headless \u6a21\u5f0f)\n kubectl create service clusterip my-cs --clusterip=\"None\"\x00\n # \u521b\u5efa\u4e00\u4e2a\u540d\u79f0\u4e3a my-dep \u7684 deployment \u5e76\u8fd0\u884c busybox image.\n kubectl create deployment my-dep --image=busybox\x00\n # \u521b\u5efa\u4e00\u4e2a\u540d\u79f0\u4e3a my-ns \u7684 nodeport service\n kubectl create service nodeport my-ns --tcp=5678:8080\x00\n # \u5bfc\u51fa\u5f53\u524d\u7684\u96c6\u7fa4\u72b6\u6001\u4fe1\u606f\u5230 stdout\n kubectl cluster-info dump\n\n # \u5bfc\u51fa\u5f53\u524d\u7684\u96c6\u7fa4\u72b6\u6001 /path/to/cluster-state\n kubectl cluster-info dump --output-directory=/path/to/cluster-state\n\n # \u5bfc\u51fa\u6240\u6709\u5206\u533a\u5230 stdout\n kubectl cluster-info dump --all-namespaces\n\n # \u5bfc\u51fa\u4e00\u7ec4\u5206\u533a\u5230 /path/to/cluster-state\n kubectl cluster-info dump --namespaces default,kube-system --output-directory=/path/to/cluster-state\x00\n # Update pod 'foo' with the annotation 'description' and the value 'my frontend'.\n # If the same annotation is set multiple times, only the last value will be applied\n kubectl annotate pods foo description='my frontend'\n\n # Update a pod identified by type and name in \"pod.json\"\n kubectl annotate -f pod.json description='my frontend'\n\n # Update pod 'foo' with the annotation 'description' and the value 'my frontend running nginx', overwriting any existing value.\n kubectl annotate --overwrite pods foo description='my frontend running nginx'\n\n # Update all pods in the namespace\n kubectl annotate pods --all description='my frontend running nginx'\n\n # Update pod 'foo' only if the resource is unchanged from version 1.\n kubectl annotate pods foo description='my frontend running nginx' --resource-version=1\n\n # \u66f4\u65b0\u540d\u79f0\u4e3a 'foo' \u7684 pod, \u5220\u9664\u4e00\u4e2a\u540d\u79f0\u4e3a 'description' \u7684 annotation \u5982\u679c\u5b83\u5b58\u5728. \n # \u4e0d\u8981\u6c42\u4f7f\u7528 --overwrite flag.\n kubectl annotate pods foo description-\x00\n \u4f7f\u7528\u4e00\u4e2a\u6307\u5b9a\u7684\u540d\u79f0\u521b\u5efa\u4e00\u4e2a LoadBalancer service.\x00\n \u4f7f\u7528\u4e00\u4e2a\u6307\u5b9a\u7684\u540d\u79f0\u521b\u5efa\u4e00\u4e2a clusterIP service.\x00\n \u4f7f\u7528\u4e00\u4e2a\u6307\u5b9a\u7684\u540d\u79f0\u521b\u5efa\u4e00\u4e2a deployment.\x00\n \u4f7f\u7528\u4e00\u4e2a\u6307\u5b9a\u7684\u540d\u79f0\u521b\u5efa\u4e00\u4e2a nodeport service.\x00\n Dumps cluster info out suitable for debugging and diagnosing cluster problems. By default, dumps everything to\n stdout. You can optionally specify a directory with --output-directory. If you specify a directory, kubernetes will\n build a set of files in that directory. By default only dumps things in the 'kube-system' namespace, but you can\n switch to a different namespace with the --namespaces flag, or specify --all-namespaces to dump all namespaces.\n\n The command also dumps the logs of all of the pods in the cluster, these logs are dumped into different directories\n based on namespace and pod name.\x00\n Display addresses of the master and services with label kubernetes.io/cluster-service=true\n To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.\x00A comma-delimited set of quota scopes that must all match each object tracked by the quota.\x00A comma-delimited set of resource=quantity pairs that define a hard limit.\x00A label selector to use for this budget. Only equality-based selector requirements are supported.\x00A label selector to use for this service. Only equality-based selector requirements are supported. If empty (the default) infer the selector from the replication controller or replica set.)\x00A schedule in the Cron format the job should be run with.\x00Additional external IP address (not managed by Kubernetes) to accept for the service. If this IP is routed to a node, the service can be accessed by this IP in addition to its generated service IP.\x00An inline JSON override for the generated object. If this is non-empty, it is used to override the generated object. Requires that the object supply a valid apiVersion field.\x00An inline JSON override for the generated service object. If this is non-empty, it is used to override the generated object. Requires that the object supply a valid apiVersion field. Only used if --expose is true.\x00\u901a\u8fc7\u6587\u4ef6\u540d\u6216\u6807\u51c6\u8f93\u5165\u6d41(stdin)\u5bf9\u8d44\u6e90\u8fdb\u884c\u914d\u7f6e\x00\u540c\u610f\u4e00\u4e2a\u81ea\u7b7e\u8bc1\u4e66\u8bf7\u6c42\x00Assign your own ClusterIP or set to 'None' for a 'headless' service (no loadbalancing).\x00Attach \u5230\u4e00\u4e2a\u8fd0\u884c\u4e2d\u7684 container\x00\u81ea\u52a8\u8c03\u6574\u4e00\u4e2a Deployment, ReplicaSet, \u6216\u8005 ReplicationController \u7684\u526f\u672c\u6570\u91cf\x00ClusterIP to be assigned to the service. Leave empty to auto-allocate, or set to 'None' to create a headless service.\x00ClusterRoleBinding \u5e94\u8be5\u6307\u5b9a ClusterRole\x00RoleBinding \u5e94\u8be5\u6307\u5b9a ClusterRole\x00Container name which will have its image upgraded. Only relevant when --image is specified, ignored otherwise. Required when using --image on a multi-container pod\x00\u5728\u4e0d\u540c\u7684 API versions \u8f6c\u6362\u914d\u7f6e\u6587\u4ef6\x00\u590d\u5236 files \u548c directories \u5230 containers \u548c\u4ece\u5bb9\u5668\u4e2d\u590d\u5236 files \u548c directories.\x00\u4e3a\u4e00\u4e2a\u6307\u5b9a\u7684 ClusterRole \u521b\u5efa\u4e00\u4e2a ClusterRoleBinding\x00\u521b\u5efa\u4e00\u4e2a LoadBalancer service.\x00\u521b\u5efa\u4e00\u4e2a NodePort service.\x00\u4e3a\u4e00\u4e2a\u6307\u5b9a\u7684 Role \u6216\u8005 ClusterRole\u521b\u5efa\u4e00\u4e2a RoleBinding\x00\u521b\u5efa\u4e00\u4e2a TLS secret\x00\u521b\u5efa\u4e00\u4e2a clusterIP service.\x00\u4ece\u672c\u5730 file, directory \u6216\u8005 literal value \u521b\u5efa\u4e00\u4e2a configmap\x00\u521b\u5efa\u4e00\u4e2a\u6307\u5b9a\u540d\u79f0\u7684 deployment.\x00\u521b\u5efa\u4e00\u4e2a\u6307\u5b9a\u540d\u79f0\u7684 namespace\x00\u521b\u5efa\u4e00\u4e2a\u6307\u5b9a\u540d\u79f0\u7684 pod disruption budget.\x00\u521b\u5efa\u4e00\u4e2a\u6307\u5b9a\u540d\u79f0\u7684 quota.\x00\u901a\u8fc7\u6587\u4ef6\u540d\u6216\u8005\u6807\u51c6\u8f93\u5165\u6d41(stdin)\u521b\u5efa\u4e00\u4e2a\u8d44\u6e90\x00\u521b\u5efa\u4e00\u4e2a\u7ed9 Docker registry \u4f7f\u7528\u7684 secret\x00\u4ece\u672c\u5730 file, directory \u6216\u8005 literal value \u521b\u5efa\u4e00\u4e2a secret\x00\u4f7f\u7528\u6307\u5b9a\u7684 subcommand \u521b\u5efa\u4e00\u4e2a secret\x00\u521b\u5efa\u4e00\u4e2a\u6307\u5b9a\u540d\u79f0\u7684 service account\x00\u4f7f\u7528\u6307\u5b9a\u7684 subcommand \u521b\u5efa\u4e00\u4e2a service.\x00Create an ExternalName service.\x00Delete resources by filenames, stdin, resources and names, or by resources and label selector\x00\u5220\u9664 kubeconfig \u6587\u4ef6\u4e2d\u6307\u5b9a\u7684\u96c6\u7fa4\x00\u5220\u9664 kubeconfig \u6587\u4ef6\u4e2d\u6307\u5b9a\u7684 context\x00\u62d2\u7edd\u4e00\u4e2a\u81ea\u7b7e\u8bc1\u4e66\u8bf7\u6c42\x00Deprecated: Gracefully shut down a resource by name or filename\x00\u63cf\u8ff0\u4e00\u4e2a\u6216\u591a\u4e2a contexts\x00\u663e\u793a nodes \u7684 Resource (CPU/Memory) \u4f7f\u7528\x00\u663e\u793a pods \u7684 Resource (CPU/Memory) \u4f7f\u7528\x00\u663e\u793a Resource (CPU/Memory) \u4f7f\u7528.\x00\u663e\u793a\u96c6\u7fa4\u4fe1\u606f\x00\u663e\u793a kubeconfig \u6587\u4ef6\u4e2d\u5b9a\u4e49\u7684\u96c6\u7fa4\x00\u663e\u793a\u5408\u5e76\u7684 kubeconfig \u914d\u7f6e\u6216\u4e00\u4e2a\u6307\u5b9a\u7684 kubeconfig \u6587\u4ef6\x00\u663e\u793a\u4e00\u4e2a\u6216\u66f4\u591a resources\x00\u663e\u793a current_context\x00\u67e5\u770b\u8d44\u6e90\u7684\u6587\u6863\x00Drain node in preparation for maintenance\x00Dump lots of relevant info for debugging and diagnosis\x00\u5728\u670d\u52a1\u5668\u4e0a\u7f16\u8f91\u4e00\u4e2a\u8d44\u6e90\x00Email for Docker registry\x00\u5728\u4e00\u4e2a container \u4e2d\u6267\u884c\u4e00\u4e2a\u547d\u4ee4\x00Explicit policy for when to pull container images. Required when --image is same as existing image, ignored otherwise.\x00Forward one or more local ports to a pod\x00Help about any command\x00IP to assign to the Load Balancer. If empty, an ephemeral IP will be created and used (cloud-provider specific).\x00If non-empty, set the session affinity for the service to this; legal values: 'None', 'ClientIP'\x00If non-empty, the annotation update will only succeed if this is the current resource-version for the object. Only valid when specifying a single resource.\x00If non-empty, the labels update will only succeed if this is the current resource-version for the object. Only valid when specifying a single resource.\x00Image to use for upgrading the replication controller. Must be distinct from the existing image (either new image or new image tag). Can not be used with --filename/-f\x00\u7ba1\u7406\u4e00\u4e2a deployment \u7684 rollout\x00\u6807\u8bb0 node \u4e3a schedulable\x00\u6807\u8bb0 node \u4e3a unschedulable\x00\u6807\u8bb0\u63d0\u4f9b\u7684 resource \u4e3a\u4e2d\u6b62\u72b6\u6001\x00\u4fee\u6539 certificate \u8d44\u6e90.\x00\u4fee\u6539 kubeconfig \u6587\u4ef6\x00Name or number for the port on the container that the service should direct traffic to. Optional.\x00Only return logs after a specific date (RFC3339). Defaults to all logs. Only one of since-time / since may be used.\x00Output shell completion code for the specified shell (bash or zsh)\x00Output the formatted object with the given group version (for ex: 'extensions/v1beta1').)\x00Password for Docker registry authentication\x00Path to PEM encoded public key certificate.\x00Path to private key associated with given certificate.\x00\u5b8c\u6210\u6307\u5b9a\u7684 ReplicationController \u7684\u6eda\u52a8\u5347\u7ea7\x00Precondition for resource version. Requires that the current resource version match this value in order to scale.\x00\u8f93\u51fa client \u548c server \u7684\u7248\u672c\u4fe1\u606f\x00\u8f93\u51fa\u6240\u6709\u547d\u4ee4\u7684\u5c42\u7ea7\u5173\u7cfb\x00\u8f93\u51fa\u5bb9\u5668\u5728 pod \u4e2d\u7684\u65e5\u5fd7\x00\u901a\u8fc7 filename \u6216\u8005 stdin\u66ff\u6362\u4e00\u4e2a\u8d44\u6e90\x00\u7ee7\u7eed\u4e00\u4e2a\u505c\u6b62\u7684 resource\x00RoleBinding \u7684 Role \u5e94\u8be5\u88ab\u5f15\u7528\x00\u5728\u96c6\u7fa4\u4e2d\u8fd0\u884c\u4e00\u4e2a\u6307\u5b9a\u7684\u955c\u50cf\x00\u8fd0\u884c\u4e00\u4e2a proxy \u5230 Kubernetes API server\x00Server location for Docker registry\x00\u4e3a Deployment, ReplicaSet, Replication Controller \u6216\u8005 Job \u8bbe\u7f6e\u4e00\u4e2a\u65b0\u7684\u526f\u672c\u6570\u91cf\x00\u4e3a objects \u8bbe\u7f6e\u4e00\u4e2a\u6307\u5b9a\u7684\u7279\u5f81\x00Set the last-applied-configuration annotation on a live object to match the contents of a file.\x00\u8bbe\u7f6e resource \u7684 selector\x00\u8bbe\u7f6e kubeconfig \u6587\u4ef6\u4e2d\u7684\u4e00\u4e2a\u96c6\u7fa4\u6761\u76ee\x00\u8bbe\u7f6e kubeconfig \u6587\u4ef6\u4e2d\u7684\u4e00\u4e2a context \u6761\u76ee\x00\u8bbe\u7f6e kubeconfig \u6587\u4ef6\u4e2d\u7684\u4e00\u4e2a\u7528\u6237\u6761\u76ee\x00\u8bbe\u7f6e kubeconfig \u6587\u4ef6\u4e2d\u7684\u4e00\u4e2a\u5355\u4e2a\u503c\x00\u8bbe\u7f6e kubeconfig \u6587\u4ef6\u4e2d\u7684\u5f53\u524d\u4e0a\u4e0b\u6587\x00\u663e\u793a\u4e00\u4e2a\u6307\u5b9a resource \u6216\u8005 group \u7684 resources \u8be6\u60c5\x00\u663e\u793a rollout \u7684\u72b6\u6001\x00Synonym for --target-port\x00\u4f7f\u7528 replication controller, service, deployment \u6216\u8005 pod \u5e76\u66b4\u9732\u5b83\u4f5c\u4e3a\u4e00\u4e2a \u65b0\u7684 Kubernetes Service\x00\u6307\u5b9a\u5bb9\u5668\u8981\u8fd0\u884c\u7684\u955c\u50cf.\x00\u5bb9\u5668\u7684\u955c\u50cf\u62c9\u53d6\u7b56\u7565. \u5982\u679c\u4e3a\u7a7a, \u8fd9\u4e2a\u503c\u5c06\u4e0d\u4f1a \u88ab client \u6307\u5b9a\u4e14\u4f7f\u7528 server \u7aef\u7684\u9ed8\u8ba4\u503c\x00\u8fd9\u4e2a key \u4f7f\u7528\u6709\u533a\u522b\u5728\u4e24\u4e2a\u4e0d\u540c\u7684 controllers, \u9ed8\u8ba4 'deployment'. \u53ea\u6709\u5f53 --image \u6307\u5b9a\u503c, \u5426\u5219\u5ffd\u7565\x00\u6700\u5c0f\u6570\u91cf\u767e\u5206\u6bd4\u53ef\u7528\u7684 pods \u4f5c\u4e3a budget \u8981\u6c42.\x00\u540d\u79f0\u4e3a\u6700\u65b0\u521b\u5efa\u7684\u5bf9\u8c61.\x00\u540d\u79f0\u4e3a\u6700\u65b0\u521b\u5efa\u7684\u5bf9\u8c61. \u5982\u679c\u6ca1\u6709\u6307\u5b9a, \u8f93\u5165\u8d44\u6e90\u7684 \u540d\u79f0\u5373\u5c06\u88ab\u4f7f\u7528.\x00\u4f7f\u7528 API generator \u7684\u540d\u5b57, \u5728 http://kubernetes.io/docs/user-guide/kubectl-conventions/#generators \u67e5\u770b\u5217\u8868.\x00\u4f7f\u7528 API generator \u7684\u540d\u5b57. \u76ee\u524d\u53ea\u6709 1 \u4e2a generator.\x00\u4f7f\u7528 generator \u7684\u540d\u79f0. \u8fd9\u91cc\u6709 2 \u4e2a generators: 'service/v1' \u548c 'service/v2'. \u4e3a\u4e00\u4e2a\u4e0d\u540c\u5730\u65b9\u662f\u670d\u52a1\u7aef\u53e3\u5728 v1 \u7684\u60c5\u51b5\u4e0b\u53eb 'default', \u5982\u679c\u5728 v2 \u4e2d\u6ca1\u6709\u6307\u5b9a\u540d\u79f0. \u9ed8\u8ba4\u7684\u540d\u79f0\u662f 'service/v2'.\x00\u4f7f\u7528 gnerator \u7684\u540d\u79f0\u521b\u5efa\u4e00\u4e2a service. \u53ea\u6709\u5728 --expose \u4e3a true \u7684\u65f6\u5019\u4f7f\u7528\x00\u521b\u5efa service \u7684\u65f6\u5019\u4f34\u968f\u7740\u4e00\u4e2a\u7f51\u7edc\u534f\u8bae\u88ab\u521b\u5efa. \u9ed8\u8ba4\u662f 'TCP'.\x00\u670d\u52a1\u7684\u7aef\u53e3\u5e94\u8be5\u88ab\u6307\u5b9a. \u5982\u679c\u6ca1\u6709\u6307\u5b9a, \u4ece\u88ab\u521b\u5efa\u7684\u8d44\u6e90\u4e2d\u590d\u5236\x00The port that this container exposes. If --expose is true, this is also the port used by the service that is created.\x00The resource requirement limits for this container. For example, 'cpu=200m,memory=512Mi'. Note that server side components may assign limits depending on the server configuration, such as limit ranges.\x00\u8d44\u6e90\u4e3a container \u8bf7\u6c42 requests . \u4f8b\u5982, 'cpu=100m,memory=256Mi'. \u6ce8\u610f\u670d\u52a1\u7aef\u7ec4\u4ef6\u4e5f\u8bb8\u4f1a\u8d4b\u4e88 requests, \u8fd9\u51b3\u5b9a\u4e8e\u670d\u52a1\u5668\u7aef\u914d\u7f6e, \u6bd4\u5982 limit ranges.\x00\u8fd9\u4e2a Pod \u7684 restart policy. Legal values [Always, OnFailure, Never]. \u5982\u679c\u8bbe\u7f6e\u4e3a 'Always' \u4e00\u4e2a deployment \u88ab\u521b\u5efa, \u5982\u679c\u8bbe\u7f6e\u4e3a \u2019OnFailure' \u4e00\u4e2a job \u88ab\u521b\u5efa, \u5982\u679c\u8bbe\u7f6e\u4e3a 'Never', \u4e00\u4e2a\u666e\u901a\u7684 pod \u88ab\u521b\u5efa. \u5bf9\u4e8e\u540e\u9762\u4e24\u4e2a --replicas \u5fc5\u987b\u4e3a 1. \u9ed8\u8ba4 'Always', \u4e3a CronJobs \u8bbe\u7f6e\u4e3a `Never`.\x00\u521b\u5efa secret \u7c7b\u578b\u8d44\u6e90\x00\u5bf9\u4e8e\u670d\u52a1\u7684\u7c7b\u578b: ClusterIP, NodePort, \u6216\u8005 LoadBalancer. \u9ed8\u8ba4\u662f 'ClusterIP\u2019.\x00\u64a4\u9500\u4e0a\u4e00\u6b21\u7684 rollout\x00\u53d6\u6d88\u8bbe\u7f6e kubeconfig \u6587\u4ef6\u4e2d\u7684\u4e00\u4e2a\u5355\u4e2a\u503c\x00\u4f7f\u7528 strategic merge patch \u66f4\u65b0\u4e00\u4e2a\u8d44\u6e90\u7684 field(s)\x00\u66f4\u65b0\u4e00\u4e2a pod template \u7684\u955c\u50cf\x00\u5728\u5bf9\u8c61\u7684 pod templates \u4e0a\u66f4\u65b0\u8d44\u6e90\u7684 requests/limits\x00\u66f4\u65b0\u4e00\u4e2a\u8d44\u6e90\u7684\u6ce8\u89e3\x00\u66f4\u65b0\u5728\u8fd9\u4e2a\u8d44\u6e90\u4e0a\u7684 labels\x00\u66f4\u65b0\u4e00\u4e2a\u6216\u8005\u591a\u4e2a node \u4e0a\u7684 taints\x00Username \u4e3a Docker registry authentication\x00\u663e\u793a\u6700\u540e\u7684 resource/object \u7684 last-applied-configuration annotations\x00\u663e\u793a rollout \u5386\u53f2\x00\u8f93\u51fa\u5230 files. \u5982\u679c\u662f empty or '-' \u4f7f\u7528 stdout, \u5426\u5219\u521b\u5efa\u4e00\u4e2a \u76ee\u5f55\u5c42\u7ea7\u5728\u90a3\u4e2a\u76ee\u5f55\x00dummy restart flag)\x00\u670d\u52a1\u7684\u5916\u90e8\u540d\u79f0\x00kubectl \u63a7\u5236 Kubernetes cluster \u7ba1\u7406\x00") func translationsKubectlZh_cnLc_messagesK8sMoBytes() ([]byte, error) { return _translationsKubectlZh_cnLc_messagesK8sMo, nil @@ -16152,91 +16152,2816 @@ var _translationsKubectlZh_cnLc_messagesK8sPo = []byte(`# Test translations for # Copyright (C) 2017 # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR shiywang@redhat.com, 2017. +# FIRST AUTHOR zhengjiajin@caicloud.io, 2017. # msgid "" msgstr "" "Project-Id-Version: gettext-go-examples-hello\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-12-12 20:03+0000\n" -"PO-Revision-Date: 2017-05-19 16:16+0800\n" -"Last-Translator: Shiyang Wang \n" +"PO-Revision-Date: 2017-11-11 19:01+0800\n" +"Last-Translator: zhengjiajin \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 1.8.8\n" +"X-Generator: Poedit 2.0.4\n" "X-Poedit-SourceCharset: UTF-8\n" "Language-Team: \n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "Language: zh\n" +#: pkg/kubectl/cmd/create_clusterrolebinding.go:35 +msgid "" +"\n" +"\t\t # Create a ClusterRoleBinding for user1, user2, and group1 using the cluster-admin ClusterRole\n" +"\t\t kubectl create clusterrolebinding cluster-admin --clusterrole=cluster-admin --user=user1 --user=user2 --group=group1" +msgstr "" +"\n" +"\t\t # 使用 cluster-admin ClusterRole 为 user1, user2, and group1 创建一个 ClusterRoleBinding\n" +"\t\t kubectl create clusterrolebinding cluster-admin --clusterrole=cluster-admin --user=user1 --user=user2 --group=group1" + +#: pkg/kubectl/cmd/create_rolebinding.go:35 +msgid "" +"\n" +"\t\t # Create a RoleBinding for user1, user2, and group1 using the admin ClusterRole\n" +"\t\t kubectl create rolebinding admin --clusterrole=admin --user=user1 --user=user2 --group=group1" +msgstr "" +"\n" +"\t\t # 使用 admin ClusterRole 为 user1, user2, and group1 创建一个 RoleBinding\n" +"\t\t kubectl create rolebinding admin --clusterrole=admin --user=user1 --user=user2 --group=group1" + +#: pkg/kubectl/cmd/create_configmap.go:44 +msgid "" +"\n" +"\t\t # Create a new configmap named my-config based on folder bar\n" +"\t\t kubectl create configmap my-config --from-file=path/to/bar\n" +"\n" +"\t\t # Create a new configmap named my-config with specified keys instead of file basenames on disk\n" +"\t\t kubectl create configmap my-config --from-file=key1=/path/to/bar/file1.txt --from-file=key2=/path/to/bar/file2.txt\n" +"\n" +"\t\t # Create a new configmap named my-config with key1=config1 and key2=config2\n" +"\t\t kubectl create configmap my-config --from-literal=key1=config1 --from-literal=key2=config2" +msgstr "" +"\n" +"\t\t # 通过文件夹 bar 创建一个名称为 my-config 的 configmap\n" +"\t\t kubectl create configmap my-config --from-file=path/to/bar\n" +"\n" +"\t\t # 创建一个名称为 my-config 的 configmap 并指定 keys 而不是使用磁盘上所在的文件名\n" +"\t\t kubectl create configmap my-config --from-file=key1=/path/to/bar/file1.txt --from-file=key2=/path/to/bar/file2.txt\n" +"\n" +"\t\t # 创建一个名称为 my-config 的 configmap 且 key1=config1 和 key2=config2\n" +"\t\t kubectl create configmap my-config --from-literal=key1=config1 --from-literal=key2=config2" + +#: pkg/kubectl/cmd/create_secret.go:135 +msgid "" +"\n" +"\t\t # If you don't already have a .dockercfg file, you can create a dockercfg secret directly by using:\n" +"\t\t kubectl create secret docker-registry my-secret --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL" +msgstr "" +"\n" +"\t\t # 如果你还没有一个 .dockercfg 文件, 你可以创建一个 dockercfg 的 secret 直接使用:\n" +"\t\t kubectl create secret docker-registry my-secret --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL" + +#: pkg/kubectl/cmd/top_node.go:65 +msgid "" +"\n" +"\t\t # Show metrics for all nodes\n" +"\t\t kubectl top node\n" +"\n" +"\t\t # Show metrics for a given node\n" +"\t\t kubectl top node NODE_NAME" +msgstr "" +"\n" +"\t\t # 显示所有 nodes 上的指标\n" +"\t\t kubectl top node\n" +"\n" +"\t\t # 显示指定 node 上的指标\n" +"\t\t kubectl top node NODE_NAME" + +#: pkg/kubectl/cmd/apply.go:84 +msgid "" +"\n" +"\t\t# Apply the configuration in pod.json to a pod.\n" +"\t\tkubectl apply -f ./pod.json\n" +"\n" +"\t\t# Apply the JSON passed into stdin to a pod.\n" +"\t\tcat pod.json | kubectl apply -f -\n" +"\n" +"\t\t# Note: --prune is still in Alpha\n" +"\t\t# Apply the configuration in manifest.yaml that matches label app=nginx and delete all the other resources that are not in the file and match label app=nginx.\n" +"\t\tkubectl apply --prune -f manifest.yaml -l app=nginx\n" +"\n" +"\t\t# Apply the configuration in manifest.yaml and delete all the other configmaps that are not in the file.\n" +"\t\tkubectl apply --prune -f manifest.yaml --all --prune-whitelist=core/v1/ConfigMap" +msgstr "" +"\n" +"\t\t# 将 pod.json 上的配置应用于 pod.\n" +"\t\tkubectl apply -f ./pod.json\n" +"\n" +"\t\t# 将传入 stdin 的 JSON 应用到一个 pod.\n" +"\t\tcat pod.json | kubectl apply -f -\n" +"\n" +"\t\t# Note: --prune 仍然在 Alpha\n" +"\t\t# 应用在 manifest.yaml 中匹配标签 app=nginx 的资源配置并删除所有不在这个文件中并匹配标签app=nginx 的资源\n" +"\t\tkubectl apply --prune -f manifest.yaml -l app=nginx\n" +"\n" +"\t\t# 应用 manifest.yaml 的配置并删除所有不在这个文件中的 configmaps.\n" +"\t\tkubectl apply --prune -f manifest.yaml --all --prune-whitelist=core/v1/ConfigMap" + +#: pkg/kubectl/cmd/autoscale.go:40 +#, c-format +msgid "" +"\n" +"\t\t# Auto scale a deployment \"foo\", with the number of pods between 2 and 10, target CPU utilization specified so a default autoscaling policy will be used:\n" +"\t\tkubectl autoscale deployment foo --min=2 --max=10\n" +"\n" +"\t\t# Auto scale a replication controller \"foo\", with the number of pods between 1 and 5, target CPU utilization at 80%:\n" +"\t\tkubectl autoscale rc foo --max=5 --cpu-percent=80" +msgstr "" +"\n" +"\t\t# 自动弹性伸缩 deployment \"foo\", pods 的数量在 2 和 10 之间, 目标 CPU 指定为默认的弹性伸缩策略:\n" +"\t\tkubectl autoscale deployment foo --min=2 --max=10\n" +"\n" +"\t\t# 自动弹性伸缩 replication controller \"foo\", pods 的数量在 1 和 5 之间, 目标 CPU 利用率为 80%:\n" +"\t\tkubectl autoscale rc foo --max=5 --cpu-percent=80" + +#: pkg/kubectl/cmd/convert.go:49 +msgid "" +"\n" +"\t\t# Convert 'pod.yaml' to latest version and print to stdout.\n" +"\t\tkubectl convert -f pod.yaml\n" +"\n" +"\t\t# Convert the live state of the resource specified by 'pod.yaml' to the latest version\n" +"\t\t# and print to stdout in json format.\n" +"\t\tkubectl convert -f pod.yaml --local -o json\n" +"\n" +"\t\t# Convert all files under current directory to latest version and create them all.\n" +"\t\tkubectl convert -f . | kubectl create -f -" +msgstr "" +"\n" +"\t\t# 将’pod.yaml' 转换为最新版本并打印到 stdout.\n" +"\t\tkubectl convert -f pod.yaml\n" +"\n" +"\t\t# 将 ‘pod.yaml' 指定的资源的实时状态转换为最新版本\n" +"\t\t# 并以 json 格式打印到 stdout.\n" +"\t\tkubectl convert -f pod.yaml --local -o json\n" +"\n" +"\t\t# 将当前目录下的所以文件转换为最新版本并创建它们.\n" +"\t\tkubectl convert -f . | kubectl create -f -" + +#: pkg/kubectl/cmd/create_role.go:41 +msgid "" +"\n" +"\t\t# Create a ClusterRole named \"pod-reader\" that allows user to perform \"get\", \"watch\" and \"list\" on pods\n" +"\t\tkubectl create clusterrole pod-reader --verb=get,list,watch --resource=pods\n" +"\n" +"\t\t# Create a ClusterRole named \"pod-reader\" with ResourceName specified\n" +"\t\tkubectl create clusterrole pod-reader --verb=get,list,watch --resource=pods --resource-name=readablepod" +msgstr "" +"\n" +"\t\t# 创建一个名为 \"pod-reader\" 的 ClusterRole, 允许用户在 pods 上执行 “get\", \"watch\" 和 \"list\"\n" +"\t\tkubectl create clusterrole pod-reader --verb=get,list,watch --resource=pods\n" +"\n" +"\t\t# 创建一个名为 \"pod-reader\" ClusterRole, 其中指定了 ResourceName\n" +"\t\tkubectl create clusterrole pod-reader --verb=get,list,watch --resource=pods --resource-name=readablepod" + +#: pkg/kubectl/cmd/create_quota.go:35 +msgid "" +"\n" +"\t\t# Create a new resourcequota named my-quota\n" +"\t\tkubectl create quota my-quota --hard=cpu=1,memory=1G,pods=2,services=3,replicationcontrollers=2,resourcequotas=1,secrets=5,persistentvolumeclaims=10\n" +"\n" +"\t\t# Create a new resourcequota named best-effort\n" +"\t\tkubectl create quota best-effort --hard=pods=100 --scopes=BestEffort" +msgstr "" +"\n" +"\t\t# 创建一个名为 my-quota 的 resourcequota\n" +"\t\tkubectl create quota my-quota --hard=cpu=1,memory=1G,pods=2,services=3,replicationcontrollers=2,resourcequotas=1,secrets=5,persistentvolumeclaims=10\n" +"\n" +"\t\t# 创建一个名为 best-effort 的 resourcequota\n" +"\t\tkubectl create quota best-effort --hard=pods=100 --scopes=BestEffort" + +#: pkg/kubectl/cmd/create_pdb.go:35 +#, c-format +msgid "" +"\n" +"\t\t# Create a pod disruption budget named my-pdb that will select all pods with the app=rails label\n" +"\t\t# and require at least one of them being available at any point in time.\n" +"\t\tkubectl create poddisruptionbudget my-pdb --selector=app=rails --min-available=1\n" +"\n" +"\t\t# Create a pod disruption budget named my-pdb that will select all pods with the app=nginx label\n" +"\t\t# and require at least half of the pods selected to be available at any point in time.\n" +"\t\tkubectl create pdb my-pdb --selector=app=nginx --min-available=50%" +msgstr "" +"\n" +"\t\t# 创建一个名称为 my-pdb 的 pod disruption budget 并将会选择所有 app=rails 标签的 pods\n" +"\t\t# 并要求他们在同一时间中最少有一个可用. \n" +"\t\tkubectl create poddisruptionbudget my-pdb --selector=app=rails --min-available=1\n" +"\n" +"\t\t# 创建一个名称为 my-pdb 的 pod disruption budget 并将会选择所有 app=rails 标签的 pods\n" +"\t\t# 并要求他们在同一时间中最少有一半可用.\n" +"\t\tkubectl create pdb my-pdb --selector=app=nginx --min-available=50%" + +#: pkg/kubectl/cmd/create.go:47 +msgid "" +"\n" +"\t\t# Create a pod using the data in pod.json.\n" +"\t\tkubectl create -f ./pod.json\n" +"\n" +"\t\t# Create a pod based on the JSON passed into stdin.\n" +"\t\tcat pod.json | kubectl create -f -\n" +"\n" +"\t\t# Edit the data in docker-registry.yaml in JSON using the v1 API format then create the resource using the edited data.\n" +"\t\tkubectl create -f docker-registry.yaml --edit --output-version=v1 -o json" +msgstr "" +"\n" +"\t\t# 使用在 pod.json 的 数据创建一个 pod.\n" +"\t\tkubectl create -f ./pod.json\n" +"\n" +"\t\t# 根据传入 stdin 的 JSON 创建一个 pod.\n" +"\t\tcat pod.json | kubectl create -f -\n" +"\n" +"\t\t# 使用 v1 API 格式在 JSON 中编辑在 docker-registry.yaml 中的数据然后使用被编辑后的数据创建资源.\n" +"\t\tkubectl create -f docker-registry.yaml --edit --output-version=v1 -o json" + +#: pkg/kubectl/cmd/expose.go:53 +msgid "" +"\n" +"\t\t# Create a service for a replicated nginx, which serves on port 80 and connects to the containers on port 8000.\n" +"\t\tkubectl expose rc nginx --port=80 --target-port=8000\n" +"\n" +"\t\t# Create a service for a replication controller identified by type and name specified in \"nginx-controller.yaml\", which serves on port 80 and connects to the containers on port 8000.\n" +"\t\tkubectl expose -f nginx-controller.yaml --port=80 --target-port=8000\n" +"\n" +"\t\t# Create a service for a pod valid-pod, which serves on port 444 with the name \"frontend\"\n" +"\t\tkubectl expose pod valid-pod --port=444 --name=frontend\n" +"\n" +"\t\t# Create a second service based on the above service, exposing the container port 8443 as port 443 with the name \"nginx-https\"\n" +"\t\tkubectl expose service nginx --port=443 --target-port=8443 --name=nginx-https\n" +"\n" +"\t\t# Create a service for a replicated streaming application on port 4100 balancing UDP traffic and named 'video-stream'.\n" +"\t\tkubectl expose rc streamer --port=4100 --protocol=udp --name=video-stream\n" +"\n" +"\t\t# Create a service for a replicated nginx using replica set, which serves on port 80 and connects to the containers on port 8000.\n" +"\t\tkubectl expose rs nginx --port=80 --target-port=8000\n" +"\n" +"\t\t# Create a service for an nginx deployment, which serves on port 80 and connects to the containers on port 8000.\n" +"\t\tkubectl expose deployment nginx --port=80 --target-port=8000" +msgstr "" +"\n" +"\t\t# 为一个 replicated nginx 创建一个 service, 服务在端口 80 并连接到 containers 的8000端口.\n" +"\t\tkubectl expose rc nginx --port=80 --target-port=8000\n" +"\n" +"\t\t# 使用在 \"nginx-controller.yaml\\ 中指定的 type 和 name 为一个replication controller 创建一个 service, 服务在端口 80 并连接到 containers 的8000端口.\n" +"\t\tkubectl expose -f nginx-controller.yaml --port=80 --target-port=8000\n" +"\n" +"\t\t# 为名为 valid-pod 的 pod 创建一个 service, 服务在端口 444 并命名为 \"frontend\" \n" +"\t\tkubectl expose pod valid-pod --port=444 --name=frontend\n" +"\n" +"\t\t# 基于上面的 service 创建第二个 service, 暴露容器端口 8443 并命名为 \"nginx-https\" 端口为 443 \n" +"\t\tkubectl expose service nginx --port=443 --target-port=8443 --name=nginx-https\n" +"\n" +"\t\t# 为一个名称为 streaming 的应用创建一个 service 暴露端口 4100, 协议为 UDP 名称为 'video-stream'.\n" +"\t\tkubectl expose rc streamer --port=4100 --protocol=udp --name=video-stream\n" +"\n" +"\t\t# 为一个名称为 nginx 的 replica set 创建一个 service, 服务在 端口 80 且连接到容器端口 8000.\n" +"\t\tkubectl expose rs nginx --port=80 --target-port=8000\n" +"\n" +"\t\t# 为一个名称为 nginx 的 deployment 创建一个 service, 服务在端口 80 且 连接到 containers 的 8000 端口.\n" +"\t\tkubectl expose deployment nginx --port=80 --target-port=8000" + +#: pkg/kubectl/cmd/delete.go:68 +msgid "" +"\n" +"\t\t# Delete a pod using the type and name specified in pod.json.\n" +"\t\tkubectl delete -f ./pod.json\n" +"\n" +"\t\t# Delete a pod based on the type and name in the JSON passed into stdin.\n" +"\t\tcat pod.json | kubectl delete -f -\n" +"\n" +"\t\t# Delete pods and services with same names \"baz\" and \"foo\"\n" +"\t\tkubectl delete pod,service baz foo\n" +"\n" +"\t\t# Delete pods and services with label name=myLabel.\n" +"\t\tkubectl delete pods,services -l name=myLabel\n" +"\n" +"\t\t# Delete a pod with minimal delay\n" +"\t\tkubectl delete pod foo --now\n" +"\n" +"\t\t# Force delete a pod on a dead node\n" +"\t\tkubectl delete pod foo --grace-period=0 --force\n" +"\n" +"\t\t# Delete all pods\n" +"\t\tkubectl delete pods --all" +msgstr "" +"\n" +"\t\t# 使用 pod.json 中的类型和名称删除一个 pod.\n" +"\t\tkubectl delete -f ./pod.json\n" +"\n" +"\t\t# 基于重定向到 stdin 中的 JSON 的类型和名称删除一个 pod.\n" +"\t\tcat pod.json | kubectl delete -f -\n" +"\n" +"\t\t# 删除名为 \"baz\" 和 \"foo\" 的 pod 和 service\n" +"\t\tkubectl delete pod,service baz foo\n" +"\n" +"\t\t# 删除标签为 name=myLabel 的 pods 和 services.\n" +"\t\tkubectl delete pods,services -l name=myLabel\n" +"\n" +"\t\t# 删除最小延迟的 pod\n" +"\t\tkubectl delete pod foo --now\n" +"\n" +"\t\t# 强制删除名为 foo 的 pod\n" +"\t\tkubectl delete pod foo --grace-period=0 --force\n" +"\n" +"\t\t# 删除所有 pods\n" +"\t\tkubectl delete pods --all" + +#: pkg/kubectl/cmd/describe.go:54 +msgid "" +"\n" +"\t\t# Describe a node\n" +"\t\tkubectl describe nodes kubernetes-node-emt8.c.myproject.internal\n" +"\n" +"\t\t# Describe a pod\n" +"\t\tkubectl describe pods/nginx\n" +"\n" +"\t\t# Describe a pod identified by type and name in \"pod.json\"\n" +"\t\tkubectl describe -f pod.json\n" +"\n" +"\t\t# Describe all pods\n" +"\t\tkubectl describe pods\n" +"\n" +"\t\t# Describe pods by label name=myLabel\n" +"\t\tkubectl describe po -l name=myLabel\n" +"\n" +"\t\t# Describe all pods managed by the 'frontend' replication controller (rc-created pods\n" +"\t\t# get the name of the rc as a prefix in the pod the name).\n" +"\t\tkubectl describe pods frontend" +msgstr "" +"\n" +"\t\t# 描述一个 node\n" +"\t\tkubectl describe nodes kubernetes-node-emt8.c.myproject.internal\n" +"\n" +"\t\t# 描述一个 pod\n" +"\t\tkubectl describe pods/nginx\n" +"\n" +"\t\t# 描述一个被 \"pod.json\" 中的类型和名称标识的 pod\n" +"\t\tkubectl describe -f pod.json\n" +"\n" +"\t\t# 描述所有 pods\n" +"\t\tkubectl describe pods\n" +"\n" +"\t\t# 描述标签为 name=myLabel 的 pods\n" +"\t\tkubectl describe po -l name=myLabel\n" +"\n" +"\t\t# 描述所有被名称为 'frontend' 的 replication controller 管理的 pods(rc-创建 pods\n" +"\t\t# 并使用 rc 的名称作为 pod 的前缀).\n" +"\t\tkubectl describe pods frontend" + +#: pkg/kubectl/cmd/drain.go:165 +msgid "" +"\n" +"\t\t# Drain node \"foo\", even if there are pods not managed by a ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet on it.\n" +"\t\t$ kubectl drain foo --force\n" +"\n" +"\t\t# As above, but abort if there are pods not managed by a ReplicationController, ReplicaSet, Job, DaemonSet 或者 StatefulSet, and use a grace period of 15 minutes.\n" +"\t\t$ kubectl drain foo --grace-period=900" +msgstr "" +"\n" +"\t\t# 驱逐节点 \"foo\", 即使很多 pods 没有被一个在 node 上的 ReplicationController, ReplicaSet, Job, DaemonSet 或者 StatefulSet 管理.\n" +"\t\t$ kubectl drain foo --force\n" +"\n" +"\t\t# 同上, 如果存在 pods 没有被一个 ReplicationController, ReplicaSet, Job, DaemonSet 或者 StatefulSet 管理超过 15 分钟则退出.\n" +"\t\t$ kubectl drain foo --grace-period=900" + +#: pkg/kubectl/cmd/edit.go:80 +msgid "" +"\n" +"\t\t# Edit the service named 'docker-registry':\n" +"\t\tkubectl edit svc/docker-registry\n" +"\n" +"\t\t# Use an alternative editor\n" +"\t\tKUBE_EDITOR=\"nano\" kubectl edit svc/docker-registry\n" +"\n" +"\t\t# Edit the job 'myjob' in JSON using the v1 API format:\n" +"\t\tkubectl edit job.v1.batch/myjob -o json\n" +"\n" +"\t\t# Edit the deployment 'mydeployment' in YAML and save the modified config in its annotation:\n" +"\t\tkubectl edit deployment/mydeployment -o yaml --save-config" +msgstr "" +"\n" +"\t\t# 编辑名为 'docker-registry' 的 service:\n" +"\t\tkubectl edit svc/docker-registry\n" +"\n" +"\t\t# 使用一个可选择的编辑器\n" +"\t\tKUBE_EDITOR=\"nano\" kubectl edit svc/docker-registry\n" +"\n" +"\t\t# 使用 v1 API 格式的 JSON 编辑名为 'myjob' 的 job:\n" +"\t\tkubectl edit job.v1.batch/myjob -o json\n" +"\n" +"\t\t# 在 YAML 中编辑名为 'mydeployment' 的 deployment 并在它的注解中保存修改后的配置:\n" +"\t\tkubectl edit deployment/mydeployment -o yaml --save-config" + +#: pkg/kubectl/cmd/exec.go:41 +msgid "" +"\n" +"\t\t# Get output from running 'date' from pod 123456-7890, using the first container by default\n" +"\t\tkubectl exec 123456-7890 date\n" +"\n" +"\t\t# Get output from running 'date' in ruby-container from pod 123456-7890\n" +"\t\tkubectl exec 123456-7890 -c ruby-container date\n" +"\n" +"\t\t# Switch to raw terminal mode, sends stdin to 'bash' in ruby-container from pod 123456-7890\n" +"\t\t# and sends stdout/stderr from 'bash' back to the client\n" +"\t\tkubectl exec 123456-7890 -c ruby-container -i -t -- bash -il" +msgstr "" +"\n" +"\t\t# 从运行中pod 123456-7890 获取执行 'date' 的输出, 默认使用第一个容器\n" +"\t\tkubectl exec 123456-7890 date\n" +"\n" +"\t\t# 从 pod 123456-7890 的容器 ruby-container 获取执行 'date' 的输出\n" +"\t\tkubectl exec 123456-7890 -c ruby-container date\n" +"\n" +"\t\t# 切换到 terminal 模式, 发送 stdin 到运行在 pod 123456-7890 的容器 ruby-container 'bash' \n" +"\t\t# 并从 'bash' 发送 stdout/stderr 返回到 client\n" +"\t\tkubectl exec 123456-7890 -c ruby-container -i -t -- bash -il" + +#: pkg/kubectl/cmd/attach.go:42 +msgid "" +"\n" +"\t\t# Get output from running pod 123456-7890, using the first container by default\n" +"\t\tkubectl attach 123456-7890\n" +"\n" +"\t\t# Get output from ruby-container from pod 123456-7890\n" +"\t\tkubectl attach 123456-7890 -c ruby-container\n" +"\n" +"\t\t# Switch to raw terminal mode, sends stdin to 'bash' in ruby-container from pod 123456-7890\n" +"\t\t# and sends stdout/stderr from 'bash' back to the client\n" +"\t\tkubectl attach 123456-7890 -c ruby-container -i -t\n" +"\n" +"\t\t# Get output from the first pod of a ReplicaSet named nginx\n" +"\t\tkubectl attach rs/nginx\n" +"\t\t" +msgstr "" +"\n" +"\t\t# 从运行中pod 123456-7890 获取执行 'date' 的输出, 默认使用第一个容器\n" +"\t\tkubectl attach 123456-7890\n" +"\n" +"\t\t# 从 pod 123456-7890 的容器 ruby-container 获取输出\n" +"\t\tkubectl attach 123456-7890 -c ruby-container\n" +"\n" +"\t\t# 切换到 terminal 模式, 发送 stdin 到运行在 pod 123456-7890 的容器 ruby-container 'bash' \n" +"\t\t# 并从 'bash' 发送 stdout/stderr 返回到 client\n" +"\t\tkubectl attach 123456-7890 -c ruby-container -i -t\n" +"\n" +"\t\t# 从名称为 nginx 的 ReplicaSet 获取第一个 pod 的输出\n" +"\t\tkubectl attach rs/nginx\n" +"\t\t" + +#: pkg/kubectl/cmd/explain.go:39 +msgid "" +"\n" +"\t\t# Get the documentation of the resource and its fields\n" +"\t\tkubectl explain pods\n" +"\n" +"\t\t# Get the documentation of a specific field of a resource\n" +"\t\tkubectl explain pods.spec.containers" +msgstr "" +"\n" +"\t\t# 获取资源及其字段的文档\n" +"\t\tkubectl explain pods\n" +"\n" +"\t\t# 获取资源指定字段的文档\n" +"\t\tkubectl explain pods.spec.containers" + +#: pkg/kubectl/cmd/completion.go:65 +msgid "" +"\n" +"\t\t# Install bash completion on a Mac using homebrew\n" +"\t\tbrew install bash-completion\n" +"\t\tprintf \"\n" +"# Bash completion support\n" +"source $(brew --prefix)/etc/bash_completion\n" +"\" >> $HOME/.bash_profile\n" +"\t\tsource $HOME/.bash_profile\n" +"\n" +"\t\t# Load the kubectl completion code for bash into the current shell\n" +"\t\tsource <(kubectl completion bash)\n" +"\n" +"\t\t# Write bash completion code to a file and source if from .bash_profile\n" +"\t\tkubectl completion bash > ~/.kube/completion.bash.inc\n" +"\t\tprintf \"\n" +"# Kubectl shell completion\n" +"source '$HOME/.kube/completion.bash.inc'\n" +"\" >> $HOME/.bash_profile\n" +"\t\tsource $HOME/.bash_profile\n" +"\n" +"\t\t# Load the kubectl completion code for zsh[1] into the current shell\n" +"\t\tsource <(kubectl completion zsh)" +msgstr "" +"\n" +"\t\t# 在一个 Mac 中使用 homebrew 安装 bash 补全\n" +"\t\tbrew install bash-completion\n" +"\t\tprintf \"\n" +"# Bash 补全支持\n" +"source $(brew --prefix)/etc/bash_completion\n" +"\" >> $HOME/.bash_profile\n" +"\t\tsource $HOME/.bash_profile\n" +"\n" +"\t\t# 导入 kubectl 补全代码到当前 shell\n" +"\t\tsource <(kubectl completion bash)\n" +"\n" +"\t\t# 写入 bash 补全代码到一个文件并 source 如果它是 .bash_profile\n" +"\t\tkubectl completion bash > ~/.kube/completion.bash.inc\n" +"\t\tprintf \"\n" +"# Kubectl shell 补全\n" +"source '$HOME/.kube/completion.bash.inc'\n" +"\" >> $HOME/.bash_profile\n" +"\t\tsource $HOME/.bash_profile\n" +"\n" +"\t\t# 为 zsh[1] 导入 kubectl 补全代码到当前 shell\n" +"\t\tsource <(kubectl completion zsh)" + +#: pkg/kubectl/cmd/get.go:64 +msgid "" +"\n" +"\t\t# List all pods in ps output format.\n" +"\t\tkubectl get pods\n" +"\n" +"\t\t# List all pods in ps output format with more information (such as node name).\n" +"\t\tkubectl get pods -o wide\n" +"\n" +"\t\t# List a single replication controller with specified NAME in ps output format.\n" +"\t\tkubectl get replicationcontroller web\n" +"\n" +"\t\t# List a single pod in JSON output format.\n" +"\t\tkubectl get -o json pod web-pod-13je7\n" +"\n" +"\t\t# List a pod identified by type and name specified in \"pod.yaml\" in JSON output format.\n" +"\t\tkubectl get -f pod.yaml -o json\n" +"\n" +"\t\t# Return only the phase value of the specified pod.\n" +"\t\tkubectl get -o template pod/web-pod-13je7 --template={{.status.phase}}\n" +"\n" +"\t\t# List all replication controllers and services together in ps output format.\n" +"\t\tkubectl get rc,services\n" +"\n" +"\t\t# List one or more resources by their type and names.\n" +"\t\tkubectl get rc/web service/frontend pods/web-pod-13je7\n" +"\n" +"\t\t# List all resources with different types.\n" +"\t\tkubectl get all" +msgstr "" +"\n" +"\t\t# 以 ps 输出格式列出所有 pod.\n" +"\t\tkubectl get pods\n" +"\n" +"\t\t# 以 ps 输出格式列出所有 pod(如节点名称).\n" +"\t\tkubectl get pods -o wide\n" +"\n" +"\t\t# 获取名称为 web 的 replicationcontroller.\n" +"\t\tkubectl get replicationcontroller web\n" +"\n" +"\t\t# 使用 JSON 格式化输出显示一个单独的 pod.\n" +"\t\tkubectl get -o json pod web-pod-13je7\n" +"\n" +"\t\t# 显示一个被 \"pod.yaml\" 中的 type 和 name 标识的 pod 并使用 JSON 格式化输出.\n" +"\t\tkubectl get -f pod.yaml -o json\n" +"\n" +"\t\t# 只返回被指定 pod 中 phase 的值.\n" +"\t\tkubectl get -o template pod/web-pod-13je7 --template={{.status.phase}}\n" +"\n" +"\t\t# 显示所有的 replication controllers 和 services 并格式化输出.\n" +"\t\tkubectl get rc,services\n" +"\n" +"\t\t# 显示一个或者更多 resources 通过它们的 type 和 names.\n" +"\t\tkubectl get rc/web service/frontend pods/web-pod-13je7\n" +"\n" +"\t\t# 使用不同的 types 显示所有 resources.\n" +"\t\tkubectl get all" + +#: pkg/kubectl/cmd/portforward.go:53 +msgid "" +"\n" +"\t\t# Listen on ports 5000 and 6000 locally, forwarding data to/from ports 5000 and 6000 in the pod\n" +"\t\tkubectl port-forward mypod 5000 6000\n" +"\n" +"\t\t# Listen on port 8888 locally, forwarding to 5000 in the pod\n" +"\t\tkubectl port-forward mypod 8888:5000\n" +"\n" +"\t\t# Listen on a random port locally, forwarding to 5000 in the pod\n" +"\t\tkubectl port-forward mypod :5000\n" +"\n" +"\t\t# Listen on a random port locally, forwarding to 5000 in the pod\n" +"\t\tkubectl port-forward mypod 0:5000" +msgstr "" +"\n" +"\t\t# 在本地监听端口 5000 和 6000 , forwarding 数据 to/from 在 pod 5000 和 6000 端口\n" +"\t\tkubectl port-forward mypod 5000 6000\n" +"\n" +"\t\t# 在本地监听端口 8888 , forwarding 到 pod 的 5000端口\n" +"\t\tkubectl port-forward mypod 8888:5000\n" +"\n" +"\t\t# 在本地随机监听一个端口 , forwarding 到 pod 的 5000端口\n" +"\t\tkubectl port-forward mypod :5000\n" +"\n" +"\t\t# 在本地随机监听一个端口 , forwarding 到 pod 的 5000端口\n" +"\t\tkubectl port-forward mypod 0:5000" + +#: pkg/kubectl/cmd/drain.go:118 +msgid "" +"\n" +"\t\t# Mark node \"foo\" as schedulable.\n" +"\t\t$ kubectl uncordon foo" +msgstr "" +"\n" +"\t\t# 标记 node \"foo\" 为 schedulable.\n" +"\t\t$ kubectl uncordon foo" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/drain.go#L102 +#: pkg/kubectl/cmd/drain.go:93 +msgid "" +"\n" +"\t\t# Mark node \"foo\" as unschedulable.\n" +"\t\tkubectl cordon foo" +msgstr "" +"\n" +"\t\t# 标记 node \"foo\" 为 unschedulable.\n" +"\t\tkubectl cordon foo" + +#: pkg/kubectl/cmd/patch.go:66 +msgid "" +"\n" +"\t\t# Partially update a node using strategic merge patch\n" +"\t\tkubectl patch node k8s-node-1 -p '{\"spec\":{\"unschedulable\":true}}'\n" +"\n" +"\t\t# Partially update a node identified by the type and name specified in \"node.json\" using strategic merge patch\n" +"\t\tkubectl patch -f node.json -p '{\"spec\":{\"unschedulable\":true}}'\n" +"\n" +"\t\t# Update a container's image; spec.containers[*].name is required because it's a merge key\n" +"\t\tkubectl patch pod valid-pod -p '{\"spec\":{\"containers\":[{\"name\":\"kubernetes-serve-hostname\",\"image\":\"new image\"}]}}'\n" +"\n" +"\t\t# Update a container's image using a json patch with positional arrays\n" +"\t\tkubectl patch pod valid-pod --type='json' -p='[{\"op\": \"replace\", \"path\": \"/spec/containers/0/image\", \"value\":\"new image\"}]'" +msgstr "" +"\n" +"\t\t# 使用 strategic merge patch 部分更新一个 node\n" +"\t\tkubectl patch node k8s-node-1 -p '{\"spec\":{\"unschedulable\":true}}'\n" +"\n" +"\t\t# 使用 strategic merge patch 部分更新一个被 \"node.json\" 的 type 和 name 标示 的 node.\n" +"\t\tkubectl patch -f node.json -p '{\"spec\":{\"unschedulable\":true}}'\n" +"\n" +"\t\t# 更新一个 container 的 image; spec.containers[*].name 是必须的 因为它是一个 merge key\n" +"\t\tkubectl patch pod valid-pod -p '{\"spec\":{\"containers\":[{\"name\":\"kubernetes-serve-hostname\",\"image\":\"new image\"}]}}'\n" +"\n" +"\t\t# 使用一个 json patch 更新一个指定坐标的 container 的 image \n" +"\t\tkubectl patch pod valid-pod --type='json' -p='[{\"op\": \"replace\", \"path\": \"/spec/containers/0/image\", \"value\":\"new image\"}]'" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/options.go#L37 +#: pkg/kubectl/cmd/options.go:29 +msgid "" +"\n" +"\t\t# Print flags inherited by all commands\n" +"\t\tkubectl options" +msgstr "" +"\n" +"\t\t# 输出所有命令继承的 flags\n" +"\t\tkubectl options" + +#: pkg/kubectl/cmd/clusterinfo.go:41 +msgid "" +"\n" +"\t\t# Print the address of the master and cluster services\n" +"\t\tkubectl cluster-info" +msgstr "" +"\n" +"\t\t# 输出 master 和 cluster services 的地址\n" +"\t\tkubectl cluster-info" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/version.go#L39 +#: pkg/kubectl/cmd/version.go:32 +msgid "" +"\n" +"\t\t# Print the client and server versions for the current context\n" +"\t\tkubectl version" +msgstr "" +"\n" +"\t\t# 输出当前 client 和 server 版本\n" +"\t\tkubectl version" + +#: pkg/kubectl/cmd/apiversions.go:34 +msgid "" +"\n" +"\t\t# Print the supported API versions\n" +"\t\tkubectl api-versions" +msgstr "" +"\n" +"\t\t# 输出支持的 API 版本\n" +"\t\tkubectl api-versions" + +#: pkg/kubectl/cmd/replace.go:50 +msgid "" +"\n" +"\t\t# Replace a pod using the data in pod.json.\n" +"\t\tkubectl replace -f ./pod.json\n" +"\n" +"\t\t# Replace a pod based on the JSON passed into stdin.\n" +"\t\tcat pod.json | kubectl replace -f -\n" +"\n" +"\t\t# Update a single-container pod's image version (tag) to v4\n" +"\t\tkubectl get pod mypod -o yaml | sed 's/\\(image: myimage\\):.*$/:v4/' | kubectl replace -f -\n" +"\n" +"\t\t# Force replace, delete and then re-create the resource\n" +"\t\tkubectl replace --force -f ./pod.json" +msgstr "" +"\n" +"\t\t# 使用在 pod.json 中的数据替换一个 pod.\n" +"\t\tkubectl replace -f ./pod.json\n" +"\n" +"\t\t# 基于被重定向到 stdin 中的 JSON 替换一个 pod.\n" +"\t\tcat pod.json | kubectl replace -f -\n" +"\n" +"\t\t# 更新一个单独容器的 pod 的 image 版本 (tag) 到 v4\n" +"\t\tkubectl get pod mypod -o yaml | sed 's/\\(image: myimage\\):.*$/:v4/' | kubectl replace -f -\n" +"\n" +"\t\t# 强制替换, 删除然后重新创建这个 resource\n" +"\t\tkubectl replace --force -f ./pod.json" + +#: pkg/kubectl/cmd/logs.go:40 +msgid "" +"\n" +"\t\t# Return snapshot logs from pod nginx with only one container\n" +"\t\tkubectl logs nginx\n" +"\n" +"\t\t# Return snapshot logs for the pods defined by label app=nginx\n" +"\t\tkubectl logs -lapp=nginx\n" +"\n" +"\t\t# Return snapshot of previous terminated ruby container logs from pod web-1\n" +"\t\tkubectl logs -p -c ruby web-1\n" +"\n" +"\t\t# Begin streaming the logs of the ruby container in pod web-1\n" +"\t\tkubectl logs -f -c ruby web-1\n" +"\n" +"\t\t# Display only the most recent 20 lines of output in pod nginx\n" +"\t\tkubectl logs --tail=20 nginx\n" +"\n" +"\t\t# Show all logs from pod nginx written in the last hour\n" +"\t\tkubectl logs --since=1h nginx\n" +"\n" +"\t\t# Return snapshot logs from first container of a job named hello\n" +"\t\tkubectl logs job/hello\n" +"\n" +"\t\t# Return snapshot logs from container nginx-1 of a deployment named nginx\n" +"\t\tkubectl logs deployment/nginx -c nginx-1" +msgstr "" +"\n" +"\t\t# 返回仅有一个容器 pod 名称为 nginx 的 snapshot 日志\n" +"\t\tkubectl logs nginx\n" +"\n" +"\t\t# 返回 label 为 app=nginx 的 pods 的 snapshot 日志\n" +"\t\tkubectl logs -lapp=nginx\n" +"\n" +"\t\t# Return snapshot of previous terminated ruby container logs from pod web-1\n" +"\t\tkubectl logs -p -c ruby web-1\n" +"\n" +"\t\t# Begin streaming the logs of the ruby container in pod web-1\n" +"\t\tkubectl logs -f -c ruby web-1\n" +"\n" +"\t\t# Display only the most recent 20 lines of output in pod nginx\n" +"\t\tkubectl logs --tail=20 nginx\n" +"\n" +"\t\t# Show all logs from pod nginx written in the last hour\n" +"\t\tkubectl logs --since=1h nginx\n" +"\n" +"\t\t# Return snapshot logs from first container of a job named hello\n" +"\t\tkubectl logs job/hello\n" +"\n" +"\t\t# Return snapshot logs from container nginx-1 of a deployment named nginx\n" +"\t\tkubectl logs deployment/nginx -c nginx-1" + +#: pkg/kubectl/cmd/proxy.go:53 +msgid "" +"\n" +"\t\t# Run a proxy to kubernetes apiserver on port 8011, serving static content from ./local/www/\n" +"\t\tkubectl proxy --port=8011 --www=./local/www/\n" +"\n" +"\t\t# Run a proxy to kubernetes apiserver on an arbitrary local port.\n" +"\t\t# The chosen port for the server will be output to stdout.\n" +"\t\tkubectl proxy --port=0\n" +"\n" +"\t\t# Run a proxy to kubernetes apiserver, changing the api prefix to k8s-api\n" +"\t\t# This makes e.g. the pods api available at localhost:8001/k8s-api/v1/pods/\n" +"\t\tkubectl proxy --api-prefix=/k8s-api" +msgstr "" +"\n" +"\t\t# 运行 proxy 到 kubernetes apiserver 的 8011 端口上, 服务静态内容路径为 ./local/www/\n" +"\t\tkubectl proxy --port=8011 --www=./local/www/\n" +"\n" +"\t\t# 在任意的本地端口上运行一个 proxy 到 kubernetes apiserver.\n" +"\t\t# 为这个 server 挑选的端口将会被输出到 stdout.\n" +"\t\tkubectl proxy --port=0\n" +"\n" +"\t\t# 运行一个 proxy 到 kubernetes apiserver, 修改 api prefix 为 k8s-api\n" +"\t\t# 这会使 e.g. 这个 pods 的有效 api 为 localhost:8001/k8s-api/v1/pods/\n" +"\t\tkubectl proxy --api-prefix=/k8s-api" + +#: pkg/kubectl/cmd/scale.go:43 +msgid "" +"\n" +"\t\t# Scale a replicaset named 'foo' to 3.\n" +"\t\tkubectl scale --replicas=3 rs/foo\n" +"\n" +"\t\t# Scale a resource identified by type and name specified in \"foo.yaml\" to 3.\n" +"\t\tkubectl scale --replicas=3 -f foo.yaml\n" +"\n" +"\t\t# If the deployment named mysql's current size is 2, scale mysql to 3.\n" +"\t\tkubectl scale --current-replicas=2 --replicas=3 deployment/mysql\n" +"\n" +"\t\t# Scale multiple replication controllers.\n" +"\t\tkubectl scale --replicas=5 rc/foo rc/bar rc/baz\n" +"\n" +"\t\t# Scale job named 'cron' to 3.\n" +"\t\tkubectl scale --replicas=3 job/cron" +msgstr "" +"\n" +"\t\t# Scale 一个名称为 ‘foo’ 的 replicaset 服本数为 3.\n" +"\t\tkubectl scale --replicas=3 rs/foo\n" +"\n" +"\t\t# Scale 指定的 \"foo.yaml\" 的 type 和 name 标识的 resource 副本数量为 3.\n" +"\t\tkubectl scale --replicas=3 -f foo.yaml\n" +"\n" +"\t\t# 如果名称为 mysql 的 deployment 当前副本数量为 2, scale mysql 到 3.\n" +"\t\tkubectl scale --current-replicas=2 --replicas=3 deployment/mysql\n" +"\n" +"\t\t# Scale 多个 replication controllers.\n" +"\t\tkubectl scale --replicas=5 rc/foo rc/bar rc/baz\n" +"\n" +"\t\t# Scale 名称为 ’cron’ 的 job 副本数量为 3.\n" +"\t\tkubectl scale --replicas=3 job/cron" + +#: pkg/kubectl/cmd/apply_set_last_applied.go:67 +msgid "" +"\n" +"\t\t# Set the last-applied-configuration of a resource to match the contents of a file.\n" +"\t\tkubectl apply set-last-applied -f deploy.yaml\n" +"\n" +"\t\t# Execute set-last-applied against each configuration file in a directory.\n" +"\t\tkubectl apply set-last-applied -f path/\n" +"\n" +"\t\t# Set the last-applied-configuration of a resource to match the contents of a file, will create the annotation if it does not already exist.\n" +"\t\tkubectl apply set-last-applied -f deploy.yaml --create-annotation=true\n" +"\t\t" +msgstr "" +"\n" +"\t\t# 设置一个资源的 last-applied-configuration 去匹配一个文件的内容.\n" +"\t\tkubectl apply set-last-applied -f deploy.yaml\n" +"\n" +"\t\t# Execute set-last-applied against each configuration file in a directory.\n" +"\t\tkubectl apply set-last-applied -f path/\n" +"\n" +"\t\t# 设置一个资源的 last-applied-configuration 去匹配一个文件的内容, 如果不存在将会创建一个 annotation.\n" +"\t\tkubectl apply set-last-applied -f deploy.yaml --create-annotation=true\n" +"\t\t" + +#: pkg/kubectl/cmd/top_pod.go:61 +msgid "" +"\n" +"\t\t# Show metrics for all pods in the default namespace\n" +"\t\tkubectl top pod\n" +"\n" +"\t\t# Show metrics for all pods in the given namespace\n" +"\t\tkubectl top pod --namespace=NAMESPACE\n" +"\n" +"\t\t# Show metrics for a given pod and its containers\n" +"\t\tkubectl top pod POD_NAME --containers\n" +"\n" +"\t\t# Show metrics for the pods defined by label name=myLabel\n" +"\t\tkubectl top pod -l name=myLabel" +msgstr "" +"\n" +"\t\t# 显示 default namespace 下所有 pods 下的 metrics\n" +"\t\tkubectl top pod\n" +"\n" +"\t\t# 显示指定 namespace 下所有 pods 的 metrics\n" +"\t\tkubectl top pod --namespace=NAMESPACE\n" +"\n" +"\t\t# 显示指定 pod 和它的容器的 metrics\n" +"\t\tkubectl top pod POD_NAME --containers\n" +"\n" +"\t\t# 显示指定 label 为 name=myLabel 的 pods 的 metrics\n" +"\t\tkubectl top pod -l name=myLabel" + +#: pkg/kubectl/cmd/stop.go:40 +msgid "" +"\n" +"\t\t# Shut down foo.\n" +"\t\tkubectl stop replicationcontroller foo\n" +"\n" +"\t\t# Stop pods and services with label name=myLabel.\n" +"\t\tkubectl stop pods,services -l name=myLabel\n" +"\n" +"\t\t# Shut down the service defined in service.json\n" +"\t\tkubectl stop -f service.json\n" +"\n" +"\t\t# Shut down all resources in the path/to/resources directory\n" +"\t\tkubectl stop -f path/to/resources" +msgstr "" +"\n" +"\t\t# Shut down foo.\n" +"\t\tkubectl stop replicationcontroller foo\n" +"\n" +"\t\t# Stop pods and services with label name=myLabel.\n" +"\t\tkubectl stop pods,services -l name=myLabel\n" +"\n" +"\t\t# Shut down the service defined in service.json\n" +"\t\tkubectl stop -f service.json\n" +"\n" +"\t\t# Shut down all resources in the path/to/resources directory\n" +"\t\tkubectl stop -f path/to/resources" + +#: pkg/kubectl/cmd/run.go:57 +msgid "" +"\n" +"\t\t# Start a single instance of nginx.\n" +"\t\tkubectl run nginx --image=nginx\n" +"\n" +"\t\t# Start a single instance of hazelcast and let the container expose port 5701 .\n" +"\t\tkubectl run hazelcast --image=hazelcast --port=5701\n" +"\n" +"\t\t# Start a single instance of hazelcast and set environment variables \"DNS_DOMAIN=cluster\" and \"POD_NAMESPACE=default\" in the container.\n" +"\t\tkubectl run hazelcast --image=hazelcast --env=\"DNS_DOMAIN=cluster\" --env=\"POD_NAMESPACE=default\"\n" +"\n" +"\t\t# Start a replicated instance of nginx.\n" +"\t\tkubectl run nginx --image=nginx --replicas=5\n" +"\n" +"\t\t# Dry run. Print the corresponding API objects without creating them.\n" +"\t\tkubectl run nginx --image=nginx --dry-run\n" +"\n" +"\t\t# Start a single instance of nginx, but overload the spec of the deployment with a partial set of values parsed from JSON.\n" +"\t\tkubectl run nginx --image=nginx --overrides='{ \"apiVersion\": \"v1\", \"spec\": { ... } }'\n" +"\n" +"\t\t# Start a pod of busybox and keep it in the foreground, don't restart it if it exits.\n" +"\t\tkubectl run -i -t busybox --image=busybox --restart=Never\n" +"\n" +"\t\t# Start the nginx container using the default command, but use custom arguments (arg1 .. argN) for that command.\n" +"\t\tkubectl run nginx --image=nginx -- ... \n" +"\n" +"\t\t# Start the nginx container using a different command and custom arguments.\n" +"\t\tkubectl run nginx --image=nginx --command -- ... \n" +"\n" +"\t\t# Start the perl container to compute π to 2000 places and print it out.\n" +"\t\tkubectl run pi --image=perl --restart=OnFailure -- perl -Mbignum=bpi -wle 'print bpi(2000)'\n" +"\n" +"\t\t# Start the cron job to compute π to 2000 places and print it out every 5 minutes.\n" +"\t\tkubectl run pi --schedule=\"0/5 * * * ?\" --image=perl --restart=OnFailure -- perl -Mbignum=bpi -wle 'print bpi(2000)'" +msgstr "" +"\n" +"\t\t# Start a single instance of nginx.\n" +"\t\tkubectl run nginx --image=nginx\n" +"\n" +"\t\t# Start a single instance of hazelcast and let the container expose port 5701 .\n" +"\t\tkubectl run hazelcast --image=hazelcast --port=5701\n" +"\n" +"\t\t# Start a single instance of hazelcast and set environment variables \"DNS_DOMAIN=cluster\" and \"POD_NAMESPACE=default\" in the container.\n" +"\t\tkubectl run hazelcast --image=hazelcast --env=\"DNS_DOMAIN=cluster\" --env=\"POD_NAMESPACE=default\"\n" +"\n" +"\t\t# Start a replicated instance of nginx.\n" +"\t\tkubectl run nginx --image=nginx --replicas=5\n" +"\n" +"\t\t# Dry run. Print the corresponding API objects without creating them.\n" +"\t\tkubectl run nginx --image=nginx --dry-run\n" +"\n" +"\t\t# Start a single instance of nginx, but overload the spec of the deployment with a partial set of values parsed from JSON.\n" +"\t\tkubectl run nginx --image=nginx --overrides='{ \"apiVersion\": \"v1\", \"spec\": { ... } }'\n" +"\n" +"\t\t# Start a pod of busybox and keep it in the foreground, don't restart it if it exits.\n" +"\t\tkubectl run -i -t busybox --image=busybox --restart=Never\n" +"\n" +"\t\t# Start the nginx container using the default command, but use custom arguments (arg1 .. argN) for that command.\n" +"\t\tkubectl run nginx --image=nginx -- ... \n" +"\n" +"\t\t# Start the nginx container using a different command and custom arguments.\n" +"\t\tkubectl run nginx --image=nginx --command -- ... \n" +"\n" +"\t\t# Start the perl container to compute π to 2000 places and print it out.\n" +"\t\tkubectl run pi --image=perl --restart=OnFailure -- perl -Mbignum=bpi -wle 'print bpi(2000)'\n" +"\n" +"\t\t# Start the cron job to compute π to 2000 places and print it out every 5 minutes.\n" +"\t\tkubectl run pi --schedule=\"0/5 * * * ?\" --image=perl --restart=OnFailure -- perl -Mbignum=bpi -wle 'print bpi(2000)'" + +#: pkg/kubectl/cmd/taint.go:67 +msgid "" +"\n" +"\t\t# Update node 'foo' with a taint with key 'dedicated' and value 'special-user' and effect 'NoSchedule'.\n" +"\t\t# If a taint with that key and effect already exists, its value is replaced as specified.\n" +"\t\tkubectl taint nodes foo dedicated=special-user:NoSchedule\n" +"\n" +"\t\t# Remove from node 'foo' the taint with key 'dedicated' and effect 'NoSchedule' if one exists.\n" +"\t\tkubectl taint nodes foo dedicated:NoSchedule-\n" +"\n" +"\t\t# Remove from node 'foo' all the taints with key 'dedicated'\n" +"\t\tkubectl taint nodes foo dedicated-" +msgstr "" +"\n" +"\t\t# Update node 'foo' with a taint with key 'dedicated' and value 'special-user' and effect 'NoSchedule'.\n" +"\t\t# If a taint with that key and effect already exists, its value is replaced as specified.\n" +"\t\tkubectl taint nodes foo dedicated=special-user:NoSchedule\n" +"\n" +"\t\t# Remove from node 'foo' the taint with key 'dedicated' and effect 'NoSchedule' if one exists.\n" +"\t\tkubectl taint nodes foo dedicated:NoSchedule-\n" +"\n" +"\t\t# Remove from node 'foo' all the taints with key 'dedicated'\n" +"\t\tkubectl taint nodes foo dedicated-" + +#: pkg/kubectl/cmd/label.go:77 +msgid "" +"\n" +"\t\t# Update pod 'foo' with the label 'unhealthy' and the value 'true'.\n" +"\t\tkubectl label pods foo unhealthy=true\n" +"\n" +"\t\t# Update pod 'foo' with the label 'status' and the value 'unhealthy', overwriting any existing value.\n" +"\t\tkubectl label --overwrite pods foo status=unhealthy\n" +"\n" +"\t\t# Update all pods in the namespace\n" +"\t\tkubectl label pods --all status=unhealthy\n" +"\n" +"\t\t# Update a pod identified by the type and name in \"pod.json\"\n" +"\t\tkubectl label -f pod.json status=unhealthy\n" +"\n" +"\t\t# Update pod 'foo' only if the resource is unchanged from version 1.\n" +"\t\tkubectl label pods foo status=unhealthy --resource-version=1\n" +"\n" +"\t\t# Update pod 'foo' by removing a label named 'bar' if it exists.\n" +"\t\t# Does not require the --overwrite flag.\n" +"\t\tkubectl label pods foo bar-" +msgstr "" +"\n" +"\t\t# Update pod 'foo' with the label 'unhealthy' and the value 'true'.\n" +"\t\tkubectl label pods foo unhealthy=true\n" +"\n" +"\t\t# Update pod 'foo' with the label 'status' and the value 'unhealthy', overwriting any existing value.\n" +"\t\tkubectl label --overwrite pods foo status=unhealthy\n" +"\n" +"\t\t# Update all pods in the namespace\n" +"\t\tkubectl label pods --all status=unhealthy\n" +"\n" +"\t\t# Update a pod identified by the type and name in \"pod.json\"\n" +"\t\tkubectl label -f pod.json status=unhealthy\n" +"\n" +"\t\t# Update pod 'foo' only if the resource is unchanged from version 1.\n" +"\t\tkubectl label pods foo status=unhealthy --resource-version=1\n" +"\n" +"\t\t# Update pod 'foo' by removing a label named 'bar' if it exists.\n" +"\t\t# Does not require the --overwrite flag.\n" +"\t\tkubectl label pods foo bar-" + +#: pkg/kubectl/cmd/rollingupdate.go:54 +msgid "" +"\n" +"\t\t# Update pods of frontend-v1 using new replication controller data in frontend-v2.json.\n" +"\t\tkubectl rolling-update frontend-v1 -f frontend-v2.json\n" +"\n" +"\t\t# Update pods of frontend-v1 using JSON data passed into stdin.\n" +"\t\tcat frontend-v2.json | kubectl rolling-update frontend-v1 -f -\n" +"\n" +"\t\t# Update the pods of frontend-v1 to frontend-v2 by just changing the image, and switching the\n" +"\t\t# name of the replication controller.\n" +"\t\tkubectl rolling-update frontend-v1 frontend-v2 --image=image:v2\n" +"\n" +"\t\t# Update the pods of frontend by just changing the image, and keeping the old name.\n" +"\t\tkubectl rolling-update frontend --image=image:v2\n" +"\n" +"\t\t# Abort and reverse an existing rollout in progress (from frontend-v1 to frontend-v2).\n" +"\t\tkubectl rolling-update frontend-v1 frontend-v2 --rollback" +msgstr "" +"\n" +"\t\t# Update pods of frontend-v1 using new replication controller data in frontend-v2.json.\n" +"\t\tkubectl rolling-update frontend-v1 -f frontend-v2.json\n" +"\n" +"\t\t# Update pods of frontend-v1 using JSON data passed into stdin.\n" +"\t\tcat frontend-v2.json | kubectl rolling-update frontend-v1 -f -\n" +"\n" +"\t\t# Update the pods of frontend-v1 to frontend-v2 by just changing the image, and switching the\n" +"\t\t# name of the replication controller.\n" +"\t\tkubectl rolling-update frontend-v1 frontend-v2 --image=image:v2\n" +"\n" +"\t\t# Update the pods of frontend by just changing the image, and keeping the old name.\n" +"\t\tkubectl rolling-update frontend --image=image:v2\n" +"\n" +"\t\t# Abort and reverse an existing rollout in progress (from frontend-v1 to frontend-v2).\n" +"\t\tkubectl rolling-update frontend-v1 frontend-v2 --rollback" + +#: pkg/kubectl/cmd/apply_view_last_applied.go:52 +msgid "" +"\n" +"\t\t# View the last-applied-configuration annotations by type/name in YAML.\n" +"\t\tkubectl apply view-last-applied deployment/nginx\n" +"\n" +"\t\t# View the last-applied-configuration annotations by file in JSON\n" +"\t\tkubectl apply view-last-applied -f deploy.yaml -o json" +msgstr "" +"\n" +"\t\t# View the last-applied-configuration annotations by type/name in YAML.\n" +"\t\tkubectl apply view-last-applied deployment/nginx\n" +"\n" +"\t\t# View the last-applied-configuration annotations by file in JSON\n" +"\t\tkubectl apply view-last-applied -f deploy.yaml -o json" + +#: pkg/kubectl/cmd/apply.go:75 +msgid "" +"\n" +"\t\tApply a configuration to a resource by filename or stdin.\n" +"\t\tThis resource will be created if it doesn't exist yet.\n" +"\t\tTo use 'apply', always create the resource initially with either 'apply' or 'create --save-config'.\n" +"\n" +"\t\tJSON and YAML formats are accepted.\n" +"\n" +"\t\tAlpha Disclaimer: the --prune functionality is not yet complete. Do not use unless you are aware of what the current state is. See https://issues.k8s.io/34274." +msgstr "" +"\n" +"\t\t通过文件名或标准输入流(stdin)对资源进行配置.\n" +"\t\tThis resource will be created if it doesn't exist yet.\n" +"\t\tTo use 'apply', always create the resource initially with either 'apply' or 'create --save-config'.\n" +"\n" +"\t\tJSON and YAML formats are accepted.\n" +"\n" +"\t\tAlpha Disclaimer: the --prune functionality is not yet complete. Do not use unless you are aware of what the current state is. See https://issues.k8s.io/34274." + +#: pkg/kubectl/cmd/convert.go:38 +msgid "" +"\n" +"\t\tConvert config files between different API versions. Both YAML\n" +"\t\tand JSON formats are accepted.\n" +"\n" +"\t\tThe command takes filename, directory, or URL as input, and convert it into format\n" +"\t\tof version specified by --output-version flag. If target version is not specified or\n" +"\t\tnot supported, convert to latest version.\n" +"\n" +"\t\tThe default output will be printed to stdout in YAML format. One can use -o option\n" +"\t\tto change to output destination." +msgstr "" +"\n" +"\t\t在不同的 API versions 转换配置文件. 接受 YAML\n" +"\t\t和 JSON 格式.\n" +"\n" +"\t\t这个命令以 filename, directory, 或者 URL 作为输入, 并通过 —output-version flag\n" +"\t\t 转换到指定版本的格式. 如果目标版本没有被指定或者\n" +"\t\t不支持, 转换到最后的版本.\n" +"\n" +"\t\t默认以 YAML 格式输出到 stdout. 可以使用 -o option\n" +"\t\t修改目标输出的格式." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_service.go#L68 +#: pkg/kubectl/cmd/create_clusterrole.go:31 +msgid "" +"\n" +"\t\tCreate a ClusterRole." +msgstr "" +"\n" +"\t\t创建一个 ClusterRole." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_clusterrolebinding.go#L43 +#: pkg/kubectl/cmd/create_clusterrolebinding.go:32 +msgid "" +"\n" +"\t\tCreate a ClusterRoleBinding for a particular ClusterRole." +msgstr "" +"\n" +"\t\t 为指定的 ClusterRole 创建一个 ClusterRoleBinding." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_rolebinding.go#L43 +#: pkg/kubectl/cmd/create_rolebinding.go:32 +msgid "" +"\n" +"\t\tCreate a RoleBinding for a particular Role or ClusterRole." +msgstr "" +"\n" +"\t\t为指定的 Role 或者 ClusterRole 创建一个 RoleBinding." + +#: pkg/kubectl/cmd/create_secret.go:200 +msgid "" +"\n" +"\t\tCreate a TLS secret from the given public/private key pair.\n" +"\n" +"\t\tThe public/private key pair must exist before hand. The public key certificate must be .PEM encoded and match the given private key." +msgstr "" +"\n" +"\t\t为指定的 public/private key pair 创建一个 TLS secret.\n" +"\n" +"\t\tpublic/private key pair 必须在传递前存在. public key certificate 必须以 .PEM 被编码且匹配指定的 private key." + +#: pkg/kubectl/cmd/create_configmap.go:32 +msgid "" +"\n" +"\t\tCreate a configmap based on a file, directory, or specified literal value.\n" +"\n" +"\t\tA single configmap may package one or more key/value pairs.\n" +"\n" +"\t\tWhen creating a configmap based on a file, the key will default to the basename of the file, and the value will\n" +"\t\tdefault to the file content. If the basename is an invalid key, you may specify an alternate key.\n" +"\n" +"\t\tWhen creating a configmap based on a directory, each file whose basename is a valid key in the directory will be\n" +"\t\tpackaged into the configmap. Any directory entries except regular files are ignored (e.g. subdirectories,\n" +"\t\tsymlinks, devices, pipes, etc)." +msgstr "" +"\n" +"\t\tCreate a configmap based on a file, directory, or specified literal value.\n" +"\n" +"\t\tA single configmap may package one or more key/value pairs.\n" +"\n" +"\t\tWhen creating a configmap based on a file, the key will default to the basename of the file, and the value will\n" +"\t\tdefault to the file content. If the basename is an invalid key, you may specify an alternate key.\n" +"\n" +"\t\tWhen creating a configmap based on a directory, each file whose basename is a valid key in the directory will be\n" +"\t\tpackaged into the configmap. Any directory entries except regular files are ignored (e.g. subdirectories,\n" +"\t\tsymlinks, devices, pipes, etc)." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_namespace.go#L44 +#: pkg/kubectl/cmd/create_namespace.go:32 +msgid "" +"\n" +"\t\tCreate a namespace with the specified name." +msgstr "" +"\n" +"\t\t创建一个 namespace 并指定名称." + +#: pkg/kubectl/cmd/create_secret.go:119 +msgid "" +"\n" +"\t\tCreate a new secret for use with Docker registries.\n" +"\n" +"\t\tDockercfg secrets are used to authenticate against Docker registries.\n" +"\n" +"\t\tWhen using the Docker command line to push images, you can authenticate to a given registry by running\n" +"\n" +"\t\t $ docker login DOCKER_REGISTRY_SERVER --username=DOCKER_USER --password=DOCKER_PASSWORD --email=DOCKER_EMAIL'.\n" +"\n" +" That produces a ~/.dockercfg file that is used by subsequent 'docker push' and 'docker pull' commands to\n" +"\t\tauthenticate to the registry. The email address is optional.\n" +"\n" +"\t\tWhen creating applications, you may have a Docker registry that requires authentication. In order for the\n" +"\t\tnodes to pull images on your behalf, they have to have the credentials. You can provide this information\n" +"\t\tby creating a dockercfg secret and attaching it to your service account." +msgstr "" +"\n" +"\t\tCreate a new secret for use with Docker registries.\n" +"\n" +"\t\tDockercfg secrets are used to authenticate against Docker registries.\n" +"\n" +"\t\tWhen using the Docker command line to push images, you can authenticate to a given registry by running\n" +"\n" +"\t\t $ docker login DOCKER_REGISTRY_SERVER --username=DOCKER_USER --password=DOCKER_PASSWORD --email=DOCKER_EMAIL'.\n" +"\n" +" That produces a ~/.dockercfg file that is used by subsequent 'docker push' and 'docker pull' commands to\n" +"\t\tauthenticate to the registry. The email address is optional.\n" +"\n" +"\t\tWhen creating applications, you may have a Docker registry that requires authentication. In order for the\n" +"\t\tnodes to pull images on your behalf, they have to have the credentials. You can provide this information\n" +"\t\tby creating a dockercfg secret and attaching it to your service account." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_pdb.go#L49 +#: pkg/kubectl/cmd/create_pdb.go:32 +msgid "" +"\n" +"\t\tCreate a pod disruption budget with the specified name, selector, and desired minimum available pods" +msgstr "" +"\n" +"\t\tCreate a pod disruption budget with the specified name, selector, and desired minimum available pods" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create.go#L56 +#: pkg/kubectl/cmd/create.go:42 +msgid "" +"\n" +"\t\tCreate a resource by filename or stdin.\n" +"\n" +"\t\tJSON and YAML formats are accepted." +msgstr "" +"\n" +"\t\t通过文件名或者标准输入流(stdin)创建一个资源.\n" +"\n" +"\t\tJSON and YAML formats are accepted." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_quota.go#L47 +#: pkg/kubectl/cmd/create_quota.go:32 +msgid "" +"\n" +"\t\tCreate a resourcequota with the specified name, hard limits and optional scopes" +msgstr "" +"\n" +"\t\tCreate a resourcequota with the specified name, hard limits and optional scopes" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_quota.go#L47 +#: pkg/kubectl/cmd/create_role.go:38 +msgid "" +"\n" +"\t\tCreate a role with single rule." +msgstr "" +"\n" +"\t\t创建单一 rule 的 role." + +#: pkg/kubectl/cmd/create_secret.go:47 +msgid "" +"\n" +"\t\tCreate a secret based on a file, directory, or specified literal value.\n" +"\n" +"\t\tA single secret may package one or more key/value pairs.\n" +"\n" +"\t\tWhen creating a secret based on a file, the key will default to the basename of the file, and the value will\n" +"\t\tdefault to the file content. If the basename is an invalid key, you may specify an alternate key.\n" +"\n" +"\t\tWhen creating a secret based on a directory, each file whose basename is a valid key in the directory will be\n" +"\t\tpackaged into the secret. Any directory entries except regular files are ignored (e.g. subdirectories,\n" +"\t\tsymlinks, devices, pipes, etc)." +msgstr "" +"\n" +"\t\tCreate a secret based on a file, directory, or specified literal value.\n" +"\n" +"\t\tA single secret may package one or more key/value pairs.\n" +"\n" +"\t\tWhen creating a secret based on a file, the key will default to the basename of the file, and the value will\n" +"\t\tdefault to the file content. If the basename is an invalid key, you may specify an alternate key.\n" +"\n" +"\t\tWhen creating a secret based on a directory, each file whose basename is a valid key in the directory will be\n" +"\t\tpackaged into the secret. Any directory entries except regular files are ignored (e.g. subdirectories,\n" +"\t\tsymlinks, devices, pipes, etc)." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_serviceaccount.go#L44 +#: pkg/kubectl/cmd/create_serviceaccount.go:32 +msgid "" +"\n" +"\t\tCreate a service account with the specified name." +msgstr "" +"\n" +"\t\t创建一个指定名称的 service account." + +#: pkg/kubectl/cmd/run.go:52 +msgid "" +"\n" +"\t\tCreate and run a particular image, possibly replicated.\n" +"\n" +"\t\tCreates a deployment or job to manage the created container(s)." +msgstr "" +"\n" +"\t\tCreate and run a particular image, possibly replicated.\n" +"\n" +"\t\tCreates a deployment or job to manage the created container(s)." + +#: pkg/kubectl/cmd/autoscale.go:34 +msgid "" +"\n" +"\t\tCreates an autoscaler that automatically chooses and sets the number of pods that run in a kubernetes cluster.\n" +"\n" +"\t\tLooks up a Deployment, ReplicaSet, or ReplicationController by name and creates an autoscaler that uses the given resource as a reference.\n" +"\t\tAn autoscaler can automatically increase or decrease number of pods deployed within the system as needed." +msgstr "" +"\n" +"\t\tCreates an autoscaler that automatically chooses and sets the number of pods that run in a kubernetes cluster.\n" +"\n" +"\t\tLooks up a Deployment, ReplicaSet, or ReplicationController by name and creates an autoscaler that uses the given resource as a reference.\n" +"\t\tAn autoscaler can automatically increase or decrease number of pods deployed within the system as needed." + +#: pkg/kubectl/cmd/delete.go:40 +msgid "" +"\n" +"\t\tDelete resources by filenames, stdin, resources and names, or by resources and label selector.\n" +"\n" +"\t\tJSON and YAML formats are accepted. Only one type of the arguments may be specified: filenames,\n" +"\t\tresources and names, or resources and label selector.\n" +"\n" +"\t\tSome resources, such as pods, support graceful deletion. These resources define a default period\n" +"\t\tbefore they are forcibly terminated (the grace period) but you may override that value with\n" +"\t\tthe --grace-period flag, or pass --now to set a grace-period of 1. Because these resources often\n" +"\t\trepresent entities in the cluster, deletion may not be acknowledged immediately. If the node\n" +"\t\thosting a pod is down or cannot reach the API server, termination may take significantly longer\n" +"\t\tthan the grace period. To force delete a resource,\tyou must pass a grace\tperiod of 0 and specify\n" +"\t\tthe --force flag.\n" +"\n" +"\t\tIMPORTANT: Force deleting pods does not wait for confirmation that the pod's processes have been\n" +"\t\tterminated, which can leave those processes running until the node detects the deletion and\n" +"\t\tcompletes graceful deletion. If your processes use shared storage or talk to a remote API and\n" +"\t\tdepend on the name of the pod to identify themselves, force deleting those pods may result in\n" +"\t\tmultiple processes running on different machines using the same identification which may lead\n" +"\t\tto data corruption or inconsistency. Only force delete pods when you are sure the pod is\n" +"\t\tterminated, or if your application can tolerate multiple copies of the same pod running at once.\n" +"\t\tAlso, if you force delete pods the scheduler may place new pods on those nodes before the node\n" +"\t\thas released those resources and causing those pods to be evicted immediately.\n" +"\n" +"\t\tNote that the delete command does NOT do resource version checks, so if someone\n" +"\t\tsubmits an update to a resource right when you submit a delete, their update\n" +"\t\twill be lost along with the rest of the resource." +msgstr "" +"\n" +"\t\tDelete resources by filenames, stdin, resources and names, or by resources and label selector.\n" +"\n" +"\t\tJSON and YAML formats are accepted. Only one type of the arguments may be specified: filenames,\n" +"\t\tresources and names, or resources and label selector.\n" +"\n" +"\t\tSome resources, such as pods, support graceful deletion. These resources define a default period\n" +"\t\tbefore they are forcibly terminated (the grace period) but you may override that value with\n" +"\t\tthe --grace-period flag, or pass --now to set a grace-period of 1. Because these resources often\n" +"\t\trepresent entities in the cluster, deletion may not be acknowledged immediately. If the node\n" +"\t\thosting a pod is down or cannot reach the API server, termination may take significantly longer\n" +"\t\tthan the grace period. To force delete a resource,\tyou must pass a grace\tperiod of 0 and specify\n" +"\t\tthe --force flag.\n" +"\n" +"\t\tIMPORTANT: Force deleting pods does not wait for confirmation that the pod's processes have been\n" +"\t\tterminated, which can leave those processes running until the node detects the deletion and\n" +"\t\tcompletes graceful deletion. If your processes use shared storage or talk to a remote API and\n" +"\t\tdepend on the name of the pod to identify themselves, force deleting those pods may result in\n" +"\t\tmultiple processes running on different machines using the same identification which may lead\n" +"\t\tto data corruption or inconsistency. Only force delete pods when you are sure the pod is\n" +"\t\tterminated, or if your application can tolerate multiple copies of the same pod running at once.\n" +"\t\tAlso, if you force delete pods the scheduler may place new pods on those nodes before the node\n" +"\t\thas released those resources and causing those pods to be evicted immediately.\n" +"\n" +"\t\tNote that the delete command does NOT do resource version checks, so if someone\n" +"\t\tsubmits an update to a resource right when you submit a delete, their update\n" +"\t\twill be lost along with the rest of the resource." + +#: pkg/kubectl/cmd/stop.go:31 +msgid "" +"\n" +"\t\tDeprecated: Gracefully shut down a resource by name or filename.\n" +"\n" +"\t\tThe stop command is deprecated, all its functionalities are covered by delete command.\n" +"\t\tSee 'kubectl delete --help' for more details.\n" +"\n" +"\t\tAttempts to shut down and delete a resource that supports graceful termination.\n" +"\t\tIf the resource is scalable it will be scaled to 0 before deletion." +msgstr "" +"\n" +"\t\tDeprecated: Gracefully shut down a resource by name or filename.\n" +"\n" +"\t\tThe stop command is deprecated, all its functionalities are covered by delete command.\n" +"\t\tSee 'kubectl delete --help' for more details.\n" +"\n" +"\t\tAttempts to shut down and delete a resource that supports graceful termination.\n" +"\t\tIf the resource is scalable it will be scaled to 0 before deletion." + +#: pkg/kubectl/cmd/top_node.go:60 +msgid "" +"\n" +"\t\tDisplay Resource (CPU/Memory/Storage) usage of nodes.\n" +"\n" +"\t\tThe top-node command allows you to see the resource consumption of nodes." +msgstr "" +"\n" +"\t\t显示 node 的资源(CPU/Memory/Storage)使用.\n" +"\n" +"\t\tThe top-node command allows you to see the resource consumption of nodes." + +#: pkg/kubectl/cmd/top_pod.go:53 +msgid "" +"\n" +"\t\tDisplay Resource (CPU/Memory/Storage) usage of pods.\n" +"\n" +"\t\tThe 'top pod' command allows you to see the resource consumption of pods.\n" +"\n" +"\t\tDue to the metrics pipeline delay, they may be unavailable for a few minutes\n" +"\t\tsince pod creation." +msgstr "" +"\n" +"\t\t显示 pods 资源(CPU/Memory/Storage)使用.\n" +"\n" +"\t\tThe 'top pod' command allows you to see the resource consumption of pods.\n" +"\n" +"\t\tDue to the metrics pipeline delay, they may be unavailable for a few minutes\n" +"\t\tsince pod creation." + +#: pkg/kubectl/cmd/top.go:33 +msgid "" +"\n" +"\t\tDisplay Resource (CPU/Memory/Storage) usage.\n" +"\n" +"\t\tThe top command allows you to see the resource consumption for nodes or pods.\n" +"\n" +"\t\tThis command requires Heapster to be correctly configured and working on the server. " +msgstr "" +"\n" +"\t\t显示资源(CPU/Memory/Storage)使用.\n" +"\n" +"\t\tThe top command allows you to see the resource consumption for nodes or pods.\n" +"\n" +"\t\tThis command requires Heapster to be correctly configured and working on the server. " + +#: pkg/kubectl/cmd/drain.go:140 +msgid "" +"\n" +"\t\tDrain node in preparation for maintenance.\n" +"\n" +"\t\tThe given node will be marked unschedulable to prevent new pods from arriving.\n" +"\t\t'drain' evicts the pods if the APIServer supports eviction\n" +"\t\t(http://kubernetes.io/docs/admin/disruptions/). Otherwise, it will use normal DELETE\n" +"\t\tto delete the pods.\n" +"\t\tThe 'drain' evicts or deletes all pods except mirror pods (which cannot be deleted through\n" +"\t\tthe API server). If there are DaemonSet-managed pods, drain will not proceed\n" +"\t\twithout --ignore-daemonsets, and regardless it will not delete any\n" +"\t\tDaemonSet-managed pods, because those pods would be immediately replaced by the\n" +"\t\tDaemonSet controller, which ignores unschedulable markings. If there are any\n" +"\t\tpods that are neither mirror pods nor managed by ReplicationController,\n" +"\t\tReplicaSet, DaemonSet, StatefulSet or Job, then drain will not delete any pods unless you\n" +"\t\tuse --force. --force will also allow deletion to proceed if the managing resource of one\n" +"\t\tor more pods is missing.\n" +"\n" +"\t\t'drain' waits for graceful termination. You should not operate on the machine until\n" +"\t\tthe command completes.\n" +"\n" +"\t\tWhen you are ready to put the node back into service, use kubectl uncordon, which\n" +"\t\twill make the node schedulable again.\n" +"\n" +"\t\t![Workflow](http://kubernetes.io/images/docs/kubectl_drain.svg)" +msgstr "" +"\n" +"\t\t清理节点为节点维护做准备.\n" +"\n" +"\t\tThe given node will be marked unschedulable to prevent new pods from arriving.\n" +"\t\t'drain' evicts the pods if the APIServer supports eviction\n" +"\t\t(http://kubernetes.io/docs/admin/disruptions/). Otherwise, it will use normal DELETE\n" +"\t\tto delete the pods.\n" +"\t\tThe 'drain' evicts or deletes all pods except mirror pods (which cannot be deleted through\n" +"\t\tthe API server). If there are DaemonSet-managed pods, drain will not proceed\n" +"\t\twithout --ignore-daemonsets, and regardless it will not delete any\n" +"\t\tDaemonSet-managed pods, because those pods would be immediately replaced by the\n" +"\t\tDaemonSet controller, which ignores unschedulable markings. If there are any\n" +"\t\tpods that are neither mirror pods nor managed by ReplicationController,\n" +"\t\tReplicaSet, DaemonSet, StatefulSet or Job, then drain will not delete any pods unless you\n" +"\t\tuse --force. --force will also allow deletion to proceed if the managing resource of one\n" +"\t\tor more pods is missing.\n" +"\n" +"\t\t'drain' waits for graceful termination. You should not operate on the machine until\n" +"\t\tthe command completes.\n" +"\n" +"\t\tWhen you are ready to put the node back into service, use kubectl uncordon, which\n" +"\t\twill make the node schedulable again.\n" +"\n" +"\t\t![Workflow](http://kubernetes.io/images/docs/kubectl_drain.svg)" + +#: pkg/kubectl/cmd/edit.go:56 +msgid "" +"\n" +"\t\tEdit a resource from the default editor.\n" +"\n" +"\t\tThe edit command allows you to directly edit any API resource you can retrieve via the\n" +"\t\tcommand line tools. It will open the editor defined by your KUBE_EDITOR, or EDITOR\n" +"\t\tenvironment variables, or fall back to 'vi' for Linux or 'notepad' for Windows.\n" +"\t\tYou can edit multiple objects, although changes are applied one at a time. The command\n" +"\t\taccepts filenames as well as command line arguments, although the files you point to must\n" +"\t\tbe previously saved versions of resources.\n" +"\n" +"\t\tEditing is done with the API version used to fetch the resource.\n" +"\t\tTo edit using a specific API version, fully-qualify the resource, version, and group.\n" +"\n" +"\t\tThe default format is YAML. To edit in JSON, specify \"-o json\".\n" +"\n" +"\t\tThe flag --windows-line-endings can be used to force Windows line endings,\n" +"\t\totherwise the default for your operating system will be used.\n" +"\n" +"\t\tIn the event an error occurs while updating, a temporary file will be created on disk\n" +"\t\tthat contains your unapplied changes. The most common error when updating a resource\n" +"\t\tis another editor changing the resource on the server. When this occurs, you will have\n" +"\t\tto apply your changes to the newer version of the resource, or update your temporary\n" +"\t\tsaved copy to include the latest resource version." +msgstr "" +"\n" +"\t\t使用默认的编辑器修改资源.\n" +"\n" +"\t\tedit 命令允许你通过命令行直接修改 API 资源.\n" +"\t\t它会打开你在 KUBE_EDITOR 或者EDITOR 环境变量中定义的编辑器\n" +"\t\t或者回滚到 Linux vi 编辑器或者 Windows notepad.\n" +"\t\t你可以修改多个对象, 虽然每次只能修改一次. 这个命令\n" +"\t\t同时也接受文件名作为命令行参数, 尽管这些文件你指出必须是\n" +"\t\t你之前保存的资源版本.\n" +"\n" +"\t\tEditing 是通过用于获取资源的API版本完成的.\n" +"\t\t为了能通过指定的 API 版本修改, 请完全限定 resource, version 和 group.\n" +"\n" +"\t\t默认是 YAML 格式. 想在 JSON 中修改, 指定 \"-o json\".\n" +"\n" +"\t\t--windows-line-endings 命令行参数可以用来强制使用 Windows line endings,\n" +"\t\t否则会使用你操作系统的默认值.\n" +"\n" +"\t\t如果更新时发生错误,将在磁盘上创建一个临时文件\n" +"\t\t里面包含您未应用的更改. 更新资源时最常见的错误\n" +"\t\t是另一个编辑器也在服务器中修改这个资源. 当发生这种情况时, 你将\n" +"\t\t需要应用你的修改到资源的最新版本, 或者更新你被保存的临时文件\n" +"\t\t复制它并使用最新的版本." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/drain.go#L127 +#: pkg/kubectl/cmd/drain.go:115 +msgid "" +"\n" +"\t\tMark node as schedulable." +msgstr "" +"\n" +"\t\t标记 node 为 schedulable." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/drain.go#L102 +#: pkg/kubectl/cmd/drain.go:90 +msgid "" +"\n" +"\t\tMark node as unschedulable." +msgstr "" +"\n" +"\t\t标记 node 为 unschedulable." + +#: pkg/kubectl/cmd/completion.go:47 +msgid "" +"\n" +"\t\tOutput shell completion code for the specified shell (bash or zsh).\n" +"\t\tThe shell code must be evalutated to provide interactive\n" +"\t\tcompletion of kubectl commands. This can be done by sourcing it from\n" +"\t\tthe .bash_profile.\n" +"\n" +"\t\tNote: this requires the bash-completion framework, which is not installed\n" +"\t\tby default on Mac. This can be installed by using homebrew:\n" +"\n" +"\t\t $ brew install bash-completion\n" +"\n" +"\t\tOnce installed, bash_completion must be evaluated. This can be done by adding the\n" +"\t\tfollowing line to the .bash_profile\n" +"\n" +"\t\t $ source $(brew --prefix)/etc/bash_completion\n" +"\n" +"\t\tNote for zsh users: [1] zsh completions are only supported in versions of zsh >= 5.2" +msgstr "" +"\n" +"\t\tOutput shell completion code for the specified shell (bash or zsh).\n" +"\t\tThe shell code must be evalutated to provide interactive\n" +"\t\tcompletion of kubectl commands. This can be done by sourcing it from\n" +"\t\tthe .bash_profile.\n" +"\n" +"\t\tNote: this requires the bash-completion framework, which is not installed\n" +"\t\tby default on Mac. This can be installed by using homebrew:\n" +"\n" +"\t\t $ brew install bash-completion\n" +"\n" +"\t\tOnce installed, bash_completion must be evaluated. This can be done by adding the\n" +"\t\tfollowing line to the .bash_profile\n" +"\n" +"\t\t $ source $(brew --prefix)/etc/bash_completion\n" +"\n" +"\t\tNote for zsh users: [1] zsh completions are only supported in versions of zsh >= 5.2" + +#: pkg/kubectl/cmd/rollingupdate.go:45 +msgid "" +"\n" +"\t\tPerform a rolling update of the given ReplicationController.\n" +"\n" +"\t\tReplaces the specified replication controller with a new replication controller by updating one pod at a time to use the\n" +"\t\tnew PodTemplate. The new-controller.json must specify the same namespace as the\n" +"\t\texisting replication controller and overwrite at least one (common) label in its replicaSelector.\n" +"\n" +"\t\t![Workflow](http://kubernetes.io/images/docs/kubectl_rollingupdate.svg)" +msgstr "" +"\n" +"\t\t完成指定的 ReplicationController 的滚动升级.\n" +"\n" +"\t\tReplaces the specified replication controller with a new replication controller by updating one pod at a time to use the\n" +"\t\tnew PodTemplate. The new-controller.json must specify the same namespace as the\n" +"\t\texisting replication controller and overwrite at least one (common) label in its replicaSelector.\n" +"\n" +"\t\t![Workflow](http://kubernetes.io/images/docs/kubectl_rollingupdate.svg)" + +#: pkg/kubectl/cmd/replace.go:40 +msgid "" +"\n" +"\t\tReplace a resource by filename or stdin.\n" +"\n" +"\t\tJSON and YAML formats are accepted. If replacing an existing resource, the\n" +"\t\tcomplete resource spec must be provided. This can be obtained by\n" +"\n" +"\t\t $ kubectl get TYPE NAME -o yaml\n" +"\n" +"\t\tPlease refer to the models in https://htmlpreview.github.io/?https://github.com/kubernetes/kubernetes/blob/HEAD/docs/api-reference/v1/definitions.html to find if a field is mutable." +msgstr "" +"\n" +"\t\tReplace a resource by filename or stdin.\n" +"\n" +"\t\tJSON and YAML formats are accepted. If replacing an existing resource, the\n" +"\t\tcomplete resource spec must be provided. This can be obtained by\n" +"\n" +"\t\t $ kubectl get TYPE NAME -o yaml\n" +"\n" +"\t\tPlease refer to the models in https://htmlpreview.github.io/?https://github.com/kubernetes/kubernetes/blob/HEAD/docs/api-reference/v1/definitions.html to find if a field is mutable." + +#: pkg/kubectl/cmd/scale.go:34 +msgid "" +"\n" +"\t\tSet a new size for a Deployment, ReplicaSet, Replication Controller, or Job.\n" +"\n" +"\t\tScale also allows users to specify one or more preconditions for the scale action.\n" +"\n" +"\t\tIf --current-replicas or --resource-version is specified, it is validated before the\n" +"\t\tscale is attempted, and it is guaranteed that the precondition holds true when the\n" +"\t\tscale is sent to the server." +msgstr "" +"\n" +"\t\tSet a new size for a Deployment, ReplicaSet, Replication Controller, or Job.\n" +"\n" +"\t\tScale also allows users to specify one or more preconditions for the scale action.\n" +"\n" +"\t\tIf --current-replicas or --resource-version is specified, it is validated before the\n" +"\t\tscale is attempted, and it is guaranteed that the precondition holds true when the\n" +"\t\tscale is sent to the server." + +#: pkg/kubectl/cmd/apply_set_last_applied.go:62 +msgid "" +"\n" +"\t\tSet the latest last-applied-configuration annotations by setting it to match the contents of a file.\n" +"\t\tThis results in the last-applied-configuration being updated as though 'kubectl apply -f ' was run,\n" +"\t\twithout updating any other parts of the object." +msgstr "" +"\n" +"\t\tSet the latest last-applied-configuration annotations by setting it to match the contents of a file.\n" +"\t\tThis results in the last-applied-configuration being updated as though 'kubectl apply -f ' was run,\n" +"\t\twithout updating any other parts of the object." + +#: pkg/kubectl/cmd/proxy.go:36 +msgid "" +"\n" +"\t\tTo proxy all of the kubernetes api and nothing else, use:\n" +"\n" +"\t\t $ kubectl proxy --api-prefix=/\n" +"\n" +"\t\tTo proxy only part of the kubernetes api and also some static files:\n" +"\n" +"\t\t $ kubectl proxy --www=/my/files --www-prefix=/static/ --api-prefix=/api/\n" +"\n" +"\t\tThe above lets you 'curl localhost:8001/api/v1/pods'.\n" +"\n" +"\t\tTo proxy the entire kubernetes api at a different root, use:\n" +"\n" +"\t\t $ kubectl proxy --api-prefix=/custom/\n" +"\n" +"\t\tThe above lets you 'curl localhost:8001/custom/api/v1/pods'" +msgstr "" +"\n" +"\t\tTo proxy all of the kubernetes api and nothing else, use:\n" +"\n" +"\t\t $ kubectl proxy --api-prefix=/\n" +"\n" +"\t\tTo proxy only part of the kubernetes api and also some static files:\n" +"\n" +"\t\t $ kubectl proxy --www=/my/files --www-prefix=/static/ --api-prefix=/api/\n" +"\n" +"\t\tThe above lets you 'curl localhost:8001/api/v1/pods'.\n" +"\n" +"\t\tTo proxy the entire kubernetes api at a different root, use:\n" +"\n" +"\t\t $ kubectl proxy --api-prefix=/custom/\n" +"\n" +"\t\tThe above lets you 'curl localhost:8001/custom/api/v1/pods'" + +#: pkg/kubectl/cmd/patch.go:59 +msgid "" +"\n" +"\t\tUpdate field(s) of a resource using strategic merge patch\n" +"\n" +"\t\tJSON and YAML formats are accepted.\n" +"\n" +"\t\tPlease refer to the models in https://htmlpreview.github.io/?https://github.com/kubernetes/kubernetes/blob/HEAD/docs/api-reference/v1/definitions.html to find if a field is mutable." +msgstr "" +"\n" +"\t\tUpdate field(s) of a resource using strategic merge patch\n" +"\n" +"\t\tJSON and YAML formats are accepted.\n" +"\n" +"\t\tPlease refer to the models in https://htmlpreview.github.io/?https://github.com/kubernetes/kubernetes/blob/HEAD/docs/api-reference/v1/definitions.html to find if a field is mutable." + +#: pkg/kubectl/cmd/label.go:70 +#, c-format +msgid "" +"\n" +"\t\tUpdate the labels on a resource.\n" +"\n" +"\t\t* A label must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[1]d characters.\n" +"\t\t* If --overwrite is true, then existing labels can be overwritten, otherwise attempting to overwrite a label will result in an error.\n" +"\t\t* If --resource-version is specified, then updates will use this resource version, otherwise the existing resource-version will be used." +msgstr "" +"\n" +"\t\tUpdate the labels on a resource.\n" +"\n" +"\t\t* A label must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[1]d characters.\n" +"\t\t* If --overwrite is true, then existing labels can be overwritten, otherwise attempting to overwrite a label will result in an error.\n" +"\t\t* If --resource-version is specified, then updates will use this resource version, otherwise the existing resource-version will be used." + +#: pkg/kubectl/cmd/taint.go:58 +#, c-format +msgid "" +"\n" +"\t\tUpdate the taints on one or more nodes.\n" +"\n" +"\t\t* A taint consists of a key, value, and effect. As an argument here, it is expressed as key=value:effect.\n" +"\t\t* The key must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[1]d characters.\n" +"\t\t* The value must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[2]d characters.\n" +"\t\t* The effect must be NoSchedule, PreferNoSchedule or NoExecute.\n" +"\t\t* Currently taint can only apply to node." +msgstr "" +"\n" +"\t\t更新一个或者多个 node 上的 taints.\n" +"\n" +"\t\t* A taint consists of a key, value, and effect. As an argument here, it is expressed as key=value:effect.\n" +"\t\t* The key must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[1]d characters.\n" +"\t\t* The value must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[2]d characters.\n" +"\t\t* The effect must be NoSchedule, PreferNoSchedule or NoExecute.\n" +"\t\t* Currently taint can only apply to node." + +#: pkg/kubectl/cmd/apply_view_last_applied.go:46 +msgid "" +"\n" +"\t\tView the latest last-applied-configuration annotations by type/name or file.\n" +"\n" +"\t\tThe default output will be printed to stdout in YAML format. One can use -o option\n" +"\t\tto change output format." +msgstr "" +"\n" +"\t\tView the latest last-applied-configuration annotations by type/name or file.\n" +"\n" +"\t\tThe default output will be printed to stdout in YAML format. One can use -o option\n" +"\t\tto change output format." + +#: pkg/kubectl/cmd/cp.go:37 +msgid "" +"\n" +"\t # !!!Important Note!!!\n" +"\t # Requires that the 'tar' binary is present in your container\n" +"\t # image. If 'tar' is not present, 'kubectl cp' will fail.\n" +"\n" +"\t # Copy /tmp/foo_dir local directory to /tmp/bar_dir in a remote pod in the default namespace\n" +"\t\tkubectl cp /tmp/foo_dir :/tmp/bar_dir\n" +"\n" +" # Copy /tmp/foo local file to /tmp/bar in a remote pod in a specific container\n" +"\t\tkubectl cp /tmp/foo :/tmp/bar -c \n" +"\n" +"\t\t# Copy /tmp/foo local file to /tmp/bar in a remote pod in namespace \n" +"\t\tkubectl cp /tmp/foo /:/tmp/bar\n" +"\n" +"\t\t# Copy /tmp/foo from a remote pod to /tmp/bar locally\n" +"\t\tkubectl cp /:/tmp/foo /tmp/bar" +msgstr "" +"\n" +"\t # !!!注意!!!\n" +"\t # 要求容器中有 'tar' 命令\n" +"\t # image. If 'tar' is not present, 'kubectl cp' will fail.\n" +"\n" +"\t # 复制本地目录 /tmp/foo_dir 到 default namespace 下的远程 pod 的 /tmp/bar_dir 路径 \n" +"\t\tkubectl cp /tmp/foo_dir :/tmp/bar_dir\n" +"\n" +" # 复制 /tmp/foo local 本地文件到指定远程 pod 的指定容器的 /tmp/bar 路径\n" +"\t\tkubectl cp /tmp/foo :/tmp/bar -c \n" +"\n" +"\t\t# 复制 /tmp/foo 本地文件到在 namespace 下的某个 pod 的 /tmp/bar 路径\n" +"\t\tkubectl cp /tmp/foo /:/tmp/bar\n" +"\n" +"\t\t# 从一个远程的 pod 的 /tmp/foo 路径复制到本地 /tmp/bar 路径\n" +"\t\tkubectl cp /:/tmp/foo /tmp/bar" + +#: pkg/kubectl/cmd/create_secret.go:205 +msgid "" +"\n" +"\t # Create a new TLS secret named tls-secret with the given key pair:\n" +"\t kubectl create secret tls tls-secret --cert=path/to/tls.cert --key=path/to/tls.key" +msgstr "" +"\n" +"\t # 使用提供的 key pair 名称为tls-secret 的 secret:\n" +"\t kubectl create secret tls tls-secret --cert=path/to/tls.cert --key=path/to/tls.key" + +#: pkg/kubectl/cmd/create_namespace.go:35 +msgid "" +"\n" +"\t # Create a new namespace named my-namespace\n" +"\t kubectl create namespace my-namespace" +msgstr "" +"\n" +"\t # 创建一个名称为 my-namespace 的 namespace\n" +"\t kubectl create namespace my-namespace" + +#: pkg/kubectl/cmd/create_secret.go:59 +msgid "" +"\n" +"\t # Create a new secret named my-secret with keys for each file in folder bar\n" +"\t kubectl create secret generic my-secret --from-file=path/to/bar\n" +"\n" +"\t # Create a new secret named my-secret with specified keys instead of names on disk\n" +"\t kubectl create secret generic my-secret --from-file=ssh-privatekey=~/.ssh/id_rsa --from-file=ssh-publickey=~/.ssh/id_rsa.pub\n" +"\n" +"\t # Create a new secret named my-secret with key1=supersecret and key2=topsecret\n" +"\t kubectl create secret generic my-secret --from-literal=key1=supersecret --from-literal=key2=topsecret" +msgstr "" +"\n" +"\t # Create a new secret named my-secret with keys for each file in folder bar\n" +"\t kubectl create secret generic my-secret --from-file=path/to/bar\n" +"\n" +"\t # Create a new secret named my-secret with specified keys instead of names on disk\n" +"\t kubectl create secret generic my-secret --from-file=ssh-privatekey=~/.ssh/id_rsa --from-file=ssh-publickey=~/.ssh/id_rsa.pub\n" +"\n" +"\t # Create a new secret named my-secret with key1=supersecret and key2=topsecret\n" +"\t kubectl create secret generic my-secret --from-literal=key1=supersecret --from-literal=key2=topsecret" + +#: pkg/kubectl/cmd/create_serviceaccount.go:35 +msgid "" +"\n" +"\t # Create a new service account named my-service-account\n" +"\t kubectl create serviceaccount my-service-account" +msgstr "" +"\n" +"\t # Create a new service account named my-service-account\n" +"\t kubectl create serviceaccount my-service-account" + +#: pkg/kubectl/cmd/create_service.go:232 +msgid "" +"\n" +"\t# Create a new ExternalName service named my-ns \n" +"\tkubectl create service externalname my-ns --external-name bar.com" +msgstr "" +"\n" +"\t# Create a new ExternalName service named my-ns \n" +"\tkubectl create service externalname my-ns --external-name bar.com" + +#: pkg/kubectl/cmd/create_service.go:225 +msgid "" +"\n" +"\tCreate an ExternalName service with the specified name.\n" +"\n" +"\tExternalName service references to an external DNS address instead of\n" +"\tonly pods, which will allow application authors to reference services\n" +"\tthat exist off platform, on other clusters, or locally." +msgstr "" +"\n" +"\tCreate an ExternalName service with the specified name.\n" +"\n" +"\tExternalName service references to an external DNS address instead of\n" +"\tonly pods, which will allow application authors to reference services\n" +"\tthat exist off platform, on other clusters, or locally." + +#: pkg/kubectl/cmd/help.go:30 +msgid "" +"\n" +"\tHelp provides help for any command in the application.\n" +"\tSimply type kubectl help [path to command] for full details." +msgstr "" +"\n" +"\tHelp provides help for any command in the application.\n" +"\tSimply type kubectl help [path to command] for full details." + +#: pkg/kubectl/cmd/create_service.go:173 +msgid "" +"\n" +" # Create a new LoadBalancer service named my-lbs\n" +" kubectl create service loadbalancer my-lbs --tcp=5678:8080" +msgstr "" +"\n" +" # 创建一个名称为 my-lbs 的 LoadBalancer service\n" +" kubectl create service loadbalancer my-lbs --tcp=5678:8080" + +#: pkg/kubectl/cmd/create_service.go:53 +msgid "" +"\n" +" # Create a new clusterIP service named my-cs\n" +" kubectl create service clusterip my-cs --tcp=5678:8080\n" +"\n" +" # Create a new clusterIP service named my-cs (in headless mode)\n" +" kubectl create service clusterip my-cs --clusterip=\"None\"" +msgstr "" +"\n" +" # 创建一个名称为 my-cs 的 clusterIP service\n" +" kubectl create service clusterip my-cs --tcp=5678:8080\n" +"\n" +" # 创建一个名称为 my-cs 的 clusterIP service (在 headless 模式)\n" +" kubectl create service clusterip my-cs --clusterip=\"None\"" + +#: pkg/kubectl/cmd/create_deployment.go:36 +msgid "" +"\n" +" # Create a new deployment named my-dep that runs the busybox image.\n" +" kubectl create deployment my-dep --image=busybox" +msgstr "" +"\n" +" # 创建一个名称为 my-dep 的 deployment 并运行 busybox image.\n" +" kubectl create deployment my-dep --image=busybox" + +#: pkg/kubectl/cmd/create_service.go:116 +msgid "" +"\n" +" # Create a new nodeport service named my-ns\n" +" kubectl create service nodeport my-ns --tcp=5678:8080" +msgstr "" +"\n" +" # 创建一个名称为 my-ns 的 nodeport service\n" +" kubectl create service nodeport my-ns --tcp=5678:8080" + +#: pkg/kubectl/cmd/clusterinfo_dump.go:62 +msgid "" +"\n" +" # Dump current cluster state to stdout\n" +" kubectl cluster-info dump\n" +"\n" +" # Dump current cluster state to /path/to/cluster-state\n" +" kubectl cluster-info dump --output-directory=/path/to/cluster-state\n" +"\n" +" # Dump all namespaces to stdout\n" +" kubectl cluster-info dump --all-namespaces\n" +"\n" +" # Dump a set of namespaces to /path/to/cluster-state\n" +" kubectl cluster-info dump --namespaces default,kube-system --output-directory=/path/to/cluster-state" +msgstr "" +"\n" +" # 导出当前的集群状态信息到 stdout\n" +" kubectl cluster-info dump\n" +"\n" +" # 导出当前的集群状态 /path/to/cluster-state\n" +" kubectl cluster-info dump --output-directory=/path/to/cluster-state\n" +"\n" +" # 导出所有分区到 stdout\n" +" kubectl cluster-info dump --all-namespaces\n" +"\n" +" # 导出一组分区到 /path/to/cluster-state\n" +" kubectl cluster-info dump --namespaces default,kube-system --output-directory=/path/to/cluster-state" + +#: pkg/kubectl/cmd/annotate.go:78 +msgid "" +"\n" +" # Update pod 'foo' with the annotation 'description' and the value 'my frontend'.\n" +" # If the same annotation is set multiple times, only the last value will be applied\n" +" kubectl annotate pods foo description='my frontend'\n" +"\n" +" # Update a pod identified by type and name in \"pod.json\"\n" +" kubectl annotate -f pod.json description='my frontend'\n" +"\n" +" # Update pod 'foo' with the annotation 'description' and the value 'my frontend running nginx', overwriting any existing value.\n" +" kubectl annotate --overwrite pods foo description='my frontend running nginx'\n" +"\n" +" # Update all pods in the namespace\n" +" kubectl annotate pods --all description='my frontend running nginx'\n" +"\n" +" # Update pod 'foo' only if the resource is unchanged from version 1.\n" +" kubectl annotate pods foo description='my frontend running nginx' --resource-version=1\n" +"\n" +" # Update pod 'foo' by removing an annotation named 'description' if it exists.\n" +" # Does not require the --overwrite flag.\n" +" kubectl annotate pods foo description-" +msgstr "" +"\n" +" # Update pod 'foo' with the annotation 'description' and the value 'my frontend'.\n" +" # If the same annotation is set multiple times, only the last value will be applied\n" +" kubectl annotate pods foo description='my frontend'\n" +"\n" +" # Update a pod identified by type and name in \"pod.json\"\n" +" kubectl annotate -f pod.json description='my frontend'\n" +"\n" +" # Update pod 'foo' with the annotation 'description' and the value 'my frontend running nginx', overwriting any existing value.\n" +" kubectl annotate --overwrite pods foo description='my frontend running nginx'\n" +"\n" +" # Update all pods in the namespace\n" +" kubectl annotate pods --all description='my frontend running nginx'\n" +"\n" +" # Update pod 'foo' only if the resource is unchanged from version 1.\n" +" kubectl annotate pods foo description='my frontend running nginx' --resource-version=1\n" +"\n" +" # 更新名称为 'foo' 的 pod, 删除一个名称为 'description' 的 annotation 如果它存在. \n" +" # 不要求使用 --overwrite flag.\n" +" kubectl annotate pods foo description-" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_namespace.go#L44 +#: pkg/kubectl/cmd/create_service.go:170 +msgid "" +"\n" +" Create a LoadBalancer service with the specified name." +msgstr "" +"\n" +" 使用一个指定的名称创建一个 LoadBalancer service." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_serviceaccount.go#L44 +#: pkg/kubectl/cmd/create_service.go:50 +msgid "" +"\n" +" Create a clusterIP service with the specified name." +msgstr "" +"\n" +" 使用一个指定的名称创建一个 clusterIP service." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_deployment.go#L44 +#: pkg/kubectl/cmd/create_deployment.go:33 +msgid "" +"\n" +" Create a deployment with the specified name." +msgstr "" +"\n" +" 使用一个指定的名称创建一个 deployment." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_deployment.go#L44 +#: pkg/kubectl/cmd/create_service.go:113 +msgid "" +"\n" +" Create a nodeport service with the specified name." +msgstr "" +"\n" +" 使用一个指定的名称创建一个 nodeport service." + +#: pkg/kubectl/cmd/clusterinfo_dump.go:53 +msgid "" +"\n" +" Dumps cluster info out suitable for debugging and diagnosing cluster problems. By default, dumps everything to\n" +" stdout. You can optionally specify a directory with --output-directory. If you specify a directory, kubernetes will\n" +" build a set of files in that directory. By default only dumps things in the 'kube-system' namespace, but you can\n" +" switch to a different namespace with the --namespaces flag, or specify --all-namespaces to dump all namespaces.\n" +"\n" +" The command also dumps the logs of all of the pods in the cluster, these logs are dumped into different directories\n" +" based on namespace and pod name." +msgstr "" +"\n" +" Dumps cluster info out suitable for debugging and diagnosing cluster problems. By default, dumps everything to\n" +" stdout. You can optionally specify a directory with --output-directory. If you specify a directory, kubernetes will\n" +" build a set of files in that directory. By default only dumps things in the 'kube-system' namespace, but you can\n" +" switch to a different namespace with the --namespaces flag, or specify --all-namespaces to dump all namespaces.\n" +"\n" +" The command also dumps the logs of all of the pods in the cluster, these logs are dumped into different directories\n" +" based on namespace and pod name." + +#: pkg/kubectl/cmd/clusterinfo.go:37 +msgid "" +"\n" +" Display addresses of the master and services with label kubernetes.io/cluster-service=true\n" +" To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'." +msgstr "" +"\n" +" Display addresses of the master and services with label kubernetes.io/cluster-service=true\n" +" To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_quota.go#L61 +#: pkg/kubectl/cmd/create_quota.go:62 +msgid "A comma-delimited set of quota scopes that must all match each object tracked by the quota." +msgstr "A comma-delimited set of quota scopes that must all match each object tracked by the quota." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_quota.go#L60 +#: pkg/kubectl/cmd/create_quota.go:61 +msgid "A comma-delimited set of resource=quantity pairs that define a hard limit." +msgstr "A comma-delimited set of resource=quantity pairs that define a hard limit." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_pdb.go#L63 +#: pkg/kubectl/cmd/create_pdb.go:64 +msgid "A label selector to use for this budget. Only equality-based selector requirements are supported." +msgstr "A label selector to use for this budget. Only equality-based selector requirements are supported." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/expose.go#L106 +#: pkg/kubectl/cmd/expose.go:104 +msgid "A label selector to use for this service. Only equality-based selector requirements are supported. If empty (the default) infer the selector from the replication controller or replica set.)" +msgstr "A label selector to use for this service. Only equality-based selector requirements are supported. If empty (the default) infer the selector from the replication controller or replica set.)" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/run.go#L136 +#: pkg/kubectl/cmd/run.go:139 +msgid "A schedule in the Cron format the job should be run with." +msgstr "A schedule in the Cron format the job should be run with." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/expose.go#L111 +#: pkg/kubectl/cmd/expose.go:109 +msgid "Additional external IP address (not managed by Kubernetes) to accept for the service. If this IP is routed to a node, the service can be accessed by this IP in addition to its generated service IP." +msgstr "Additional external IP address (not managed by Kubernetes) to accept for the service. If this IP is routed to a node, the service can be accessed by this IP in addition to its generated service IP." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/run.go#L119 +#: pkg/kubectl/cmd/expose.go:110 pkg/kubectl/cmd/run.go:122 +msgid "An inline JSON override for the generated object. If this is non-empty, it is used to override the generated object. Requires that the object supply a valid apiVersion field." +msgstr "An inline JSON override for the generated object. If this is non-empty, it is used to override the generated object. Requires that the object supply a valid apiVersion field." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/run.go#L134 +#: pkg/kubectl/cmd/run.go:137 +msgid "An inline JSON override for the generated service object. If this is non-empty, it is used to override the generated object. Requires that the object supply a valid apiVersion field. Only used if --expose is true." +msgstr "An inline JSON override for the generated service object. If this is non-empty, it is used to override the generated object. Requires that the object supply a valid apiVersion field. Only used if --expose is true." + #: pkg/kubectl/cmd/apply.go:104 msgid "Apply a configuration to a resource by filename or stdin" -msgstr "通过文件名或标准输入流(stdin)对资源进行配置。" +msgstr "通过文件名或标准输入流(stdin)对资源进行配置" +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/certificates.go#L71 +#: pkg/kubectl/cmd/certificates.go:72 +msgid "Approve a certificate signing request" +msgstr "同意一个自签证书请求" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_service.go#L81 +#: pkg/kubectl/cmd/create_service.go:82 +msgid "Assign your own ClusterIP or set to 'None' for a 'headless' service (no loadbalancing)." +msgstr "Assign your own ClusterIP or set to 'None' for a 'headless' service (no loadbalancing)." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/attach.go#L64 +#: pkg/kubectl/cmd/attach.go:70 +msgid "Attach to a running container" +msgstr "Attach 到一个运行中的 container" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/autoscale.go#L55 +#: pkg/kubectl/cmd/autoscale.go:56 +msgid "Auto-scale a Deployment, ReplicaSet, or ReplicationController" +msgstr "自动调整一个 Deployment, ReplicaSet, 或者 ReplicationController 的副本数量" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/expose.go#L115 +#: pkg/kubectl/cmd/expose.go:113 +msgid "ClusterIP to be assigned to the service. Leave empty to auto-allocate, or set to 'None' to create a headless service." +msgstr "ClusterIP to be assigned to the service. Leave empty to auto-allocate, or set to 'None' to create a headless service." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_clusterrolebinding.go#L55 +#: pkg/kubectl/cmd/create_clusterrolebinding.go:56 +msgid "ClusterRole this ClusterRoleBinding should reference" +msgstr "ClusterRoleBinding 应该指定 ClusterRole" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_rolebinding.go#L55 +#: pkg/kubectl/cmd/create_rolebinding.go:56 +msgid "ClusterRole this RoleBinding should reference" +msgstr "RoleBinding 应该指定 ClusterRole" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/rollingupdate.go#L101 +#: pkg/kubectl/cmd/rollingupdate.go:102 +msgid "Container name which will have its image upgraded. Only relevant when --image is specified, ignored otherwise. Required when using --image on a multi-container pod" +msgstr "Container name which will have its image upgraded. Only relevant when --image is specified, ignored otherwise. Required when using --image on a multi-container pod" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/convert.go#L67 +#: pkg/kubectl/cmd/convert.go:68 +msgid "Convert config files between different API versions" +msgstr "在不同的 API versions 转换配置文件" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/cp.go#L64 +#: pkg/kubectl/cmd/cp.go:65 +msgid "Copy files and directories to and from containers." +msgstr "复制 files 和 directories 到 containers 和从容器中复制 files 和 directories." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_clusterrolebinding.go#L43 +#: pkg/kubectl/cmd/create_clusterrolebinding.go:44 +msgid "Create a ClusterRoleBinding for a particular ClusterRole" +msgstr "为一个指定的 ClusterRole 创建一个 ClusterRoleBinding" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_service.go#L181 +#: pkg/kubectl/cmd/create_service.go:182 +msgid "Create a LoadBalancer service." +msgstr "创建一个 LoadBalancer service." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_service.go#L124 +#: pkg/kubectl/cmd/create_service.go:125 +msgid "Create a NodePort service." +msgstr "创建一个 NodePort service." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_rolebinding.go#L43 +#: pkg/kubectl/cmd/create_rolebinding.go:44 +msgid "Create a RoleBinding for a particular Role or ClusterRole" +msgstr "为一个指定的 Role 或者 ClusterRole创建一个 RoleBinding" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_secret.go#L214 +#: pkg/kubectl/cmd/create_secret.go:214 +msgid "Create a TLS secret" +msgstr "创建一个 TLS secret" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_service.go#L68 +#: pkg/kubectl/cmd/create_service.go:69 +msgid "Create a clusterIP service." +msgstr "创建一个 clusterIP service." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_configmap.go#L59 +#: pkg/kubectl/cmd/create_configmap.go:60 +msgid "Create a configmap from a local file, directory or literal value" +msgstr "从本地 file, directory 或者 literal value 创建一个 configmap" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_deployment.go#L44 +#: pkg/kubectl/cmd/create_deployment.go:46 +msgid "Create a deployment with the specified name." +msgstr "创建一个指定名称的 deployment." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_namespace.go#L44 +#: pkg/kubectl/cmd/create_namespace.go:45 +msgid "Create a namespace with the specified name" +msgstr "创建一个指定名称的 namespace" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_pdb.go#L49 +#: pkg/kubectl/cmd/create_pdb.go:50 +msgid "Create a pod disruption budget with the specified name." +msgstr "创建一个指定名称的 pod disruption budget." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_quota.go#L47 +#: pkg/kubectl/cmd/create_quota.go:48 +msgid "Create a quota with the specified name." +msgstr "创建一个指定名称的 quota." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create.go#L56 +#: pkg/kubectl/cmd/create.go:63 +msgid "Create a resource by filename or stdin" +msgstr "通过文件名或者标准输入流(stdin)创建一个资源" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_secret.go#L143 +#: pkg/kubectl/cmd/create_secret.go:144 +msgid "Create a secret for use with a Docker registry" +msgstr "创建一个给 Docker registry 使用的 secret" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_secret.go#L73 +#: pkg/kubectl/cmd/create_secret.go:74 +msgid "Create a secret from a local file, directory or literal value" +msgstr "从本地 file, directory 或者 literal value 创建一个 secret" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_secret.go#L34 +#: pkg/kubectl/cmd/create_secret.go:35 +msgid "Create a secret using specified subcommand" +msgstr "使用指定的 subcommand 创建一个 secret" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_serviceaccount.go#L44 +#: pkg/kubectl/cmd/create_serviceaccount.go:45 +msgid "Create a service account with the specified name" +msgstr "创建一个指定名称的 service account" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_service.go#L36 +#: pkg/kubectl/cmd/create_service.go:37 +msgid "Create a service using specified subcommand." +msgstr "使用指定的 subcommand 创建一个 service." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_service.go#L240 +#: pkg/kubectl/cmd/create_service.go:241 +msgid "Create an ExternalName service." +msgstr "Create an ExternalName service." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/delete.go#L130 +#: pkg/kubectl/cmd/delete.go:132 +msgid "Delete resources by filenames, stdin, resources and names, or by resources and label selector" +msgstr "Delete resources by filenames, stdin, resources and names, or by resources and label selector" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/config/delete_cluster.go#L38 #: pkg/kubectl/cmd/config/delete_cluster.go:39 msgid "Delete the specified cluster from the kubeconfig" msgstr "删除 kubeconfig 文件中指定的集群" +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/config/delete_context.go#L38 #: pkg/kubectl/cmd/config/delete_context.go:39 msgid "Delete the specified context from the kubeconfig" -msgstr "删除 kubeconfig 文件中指定的上下文" +msgstr "删除 kubeconfig 文件中指定的 context" +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/certificates.go#L121 +#: pkg/kubectl/cmd/certificates.go:122 +msgid "Deny a certificate signing request" +msgstr "拒绝一个自签证书请求" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/stop.go#L58 +#: pkg/kubectl/cmd/stop.go:59 +msgid "Deprecated: Gracefully shut down a resource by name or filename" +msgstr "Deprecated: Gracefully shut down a resource by name or filename" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/config/get_contexts.go#L62 #: pkg/kubectl/cmd/config/get_contexts.go:64 msgid "Describe one or many contexts" -msgstr "描述一个或多个上下文" +msgstr "描述一个或多个 contexts" +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/top_node.go#L77 +#: pkg/kubectl/cmd/top_node.go:78 +msgid "Display Resource (CPU/Memory) usage of nodes" +msgstr "显示 nodes 的 Resource (CPU/Memory) 使用" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/top_pod.go#L79 +#: pkg/kubectl/cmd/top_pod.go:80 +msgid "Display Resource (CPU/Memory) usage of pods" +msgstr "显示 pods 的 Resource (CPU/Memory) 使用" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/top.go#L43 +#: pkg/kubectl/cmd/top.go:44 +msgid "Display Resource (CPU/Memory) usage." +msgstr "显示 Resource (CPU/Memory) 使用." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/clusterinfo.go#L49 +#: pkg/kubectl/cmd/clusterinfo.go:51 +msgid "Display cluster info" +msgstr "显示集群信息" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/config/get_clusters.go#L40 #: pkg/kubectl/cmd/config/get_clusters.go:41 msgid "Display clusters defined in the kubeconfig" msgstr "显示 kubeconfig 文件中定义的集群" +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/config/view.go#L64 #: pkg/kubectl/cmd/config/view.go:67 msgid "Display merged kubeconfig settings or a specified kubeconfig file" msgstr "显示合并的 kubeconfig 配置或一个指定的 kubeconfig 文件" +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/get.go#L107 +#: pkg/kubectl/cmd/get.go:111 +msgid "Display one or many resources" +msgstr "显示一个或更多 resources" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/config/current_context.go#L48 #: pkg/kubectl/cmd/config/current_context.go:49 msgid "Displays the current-context" -msgstr "显示当前的上下文" +msgstr "显示 current_context" +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/explain.go#L50 +#: pkg/kubectl/cmd/explain.go:51 +msgid "Documentation of resources" +msgstr "查看资源的文档" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/drain.go#L176 +#: pkg/kubectl/cmd/drain.go:178 +msgid "Drain node in preparation for maintenance" +msgstr "Drain node in preparation for maintenance" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/clusterinfo_dump.go#L37 +#: pkg/kubectl/cmd/clusterinfo_dump.go:39 +msgid "Dump lots of relevant info for debugging and diagnosis" +msgstr "Dump lots of relevant info for debugging and diagnosis" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/edit.go#L100 +#: pkg/kubectl/cmd/edit.go:110 +msgid "Edit a resource on the server" +msgstr "在服务器上编辑一个资源" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_secret.go#L159 +#: pkg/kubectl/cmd/create_secret.go:160 +msgid "Email for Docker registry" +msgstr "Email for Docker registry" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/exec.go#L68 +#: pkg/kubectl/cmd/exec.go:69 +msgid "Execute a command in a container" +msgstr "在一个 container 中执行一个命令" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/rollingupdate.go#L102 +#: pkg/kubectl/cmd/rollingupdate.go:103 +msgid "Explicit policy for when to pull container images. Required when --image is same as existing image, ignored otherwise." +msgstr "Explicit policy for when to pull container images. Required when --image is same as existing image, ignored otherwise." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/portforward.go#L75 +#: pkg/kubectl/cmd/portforward.go:76 +msgid "Forward one or more local ports to a pod" +msgstr "Forward one or more local ports to a pod" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/help.go#L36 +#: pkg/kubectl/cmd/help.go:37 +msgid "Help about any command" +msgstr "Help about any command" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/expose.go#L105 +#: pkg/kubectl/cmd/expose.go:103 +msgid "IP to assign to the Load Balancer. If empty, an ephemeral IP will be created and used (cloud-provider specific)." +msgstr "IP to assign to the Load Balancer. If empty, an ephemeral IP will be created and used (cloud-provider specific)." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/expose.go#L114 +#: pkg/kubectl/cmd/expose.go:112 +msgid "If non-empty, set the session affinity for the service to this; legal values: 'None', 'ClientIP'" +msgstr "If non-empty, set the session affinity for the service to this; legal values: 'None', 'ClientIP'" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/annotate.go#L135 +#: pkg/kubectl/cmd/annotate.go:136 +msgid "If non-empty, the annotation update will only succeed if this is the current resource-version for the object. Only valid when specifying a single resource." +msgstr "If non-empty, the annotation update will only succeed if this is the current resource-version for the object. Only valid when specifying a single resource." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/label.go#L132 +#: pkg/kubectl/cmd/label.go:134 +msgid "If non-empty, the labels update will only succeed if this is the current resource-version for the object. Only valid when specifying a single resource." +msgstr "If non-empty, the labels update will only succeed if this is the current resource-version for the object. Only valid when specifying a single resource." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/rollingupdate.go#L98 +#: pkg/kubectl/cmd/rollingupdate.go:99 +msgid "Image to use for upgrading the replication controller. Must be distinct from the existing image (either new image or new image tag). Can not be used with --filename/-f" +msgstr "Image to use for upgrading the replication controller. Must be distinct from the existing image (either new image or new image tag). Can not be used with --filename/-f" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/rollout/rollout.go#L46 +#: pkg/kubectl/cmd/rollout/rollout.go:47 +msgid "Manage a deployment rollout" +msgstr "管理一个 deployment 的 rollout" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/drain.go#L127 +#: pkg/kubectl/cmd/drain.go:128 +msgid "Mark node as schedulable" +msgstr "标记 node 为 schedulable" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/drain.go#L102 +#: pkg/kubectl/cmd/drain.go:103 +msgid "Mark node as unschedulable" +msgstr "标记 node 为 unschedulable" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/rollout/rollout_pause.go#L73 +#: pkg/kubectl/cmd/rollout/rollout_pause.go:74 +msgid "Mark the provided resource as paused" +msgstr "标记提供的 resource 为中止状态" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/certificates.go#L35 +#: pkg/kubectl/cmd/certificates.go:36 +msgid "Modify certificate resources." +msgstr "修改 certificate 资源." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/config/config.go#L39 #: pkg/kubectl/cmd/config/config.go:40 msgid "Modify kubeconfig files" msgstr "修改 kubeconfig 文件" +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/expose.go#L110 +#: pkg/kubectl/cmd/expose.go:108 +msgid "Name or number for the port on the container that the service should direct traffic to. Optional." +msgstr "Name or number for the port on the container that the service should direct traffic to. Optional." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/logs.go#L108 +#: pkg/kubectl/cmd/logs.go:113 +msgid "Only return logs after a specific date (RFC3339). Defaults to all logs. Only one of since-time / since may be used." +msgstr "Only return logs after a specific date (RFC3339). Defaults to all logs. Only one of since-time / since may be used." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/completion.go#L97 +#: pkg/kubectl/cmd/completion.go:104 +msgid "Output shell completion code for the specified shell (bash or zsh)" +msgstr "Output shell completion code for the specified shell (bash or zsh)" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/edit.go#L115 +#: pkg/kubectl/cmd/convert.go:85 +msgid "Output the formatted object with the given group version (for ex: 'extensions/v1beta1').)" +msgstr "Output the formatted object with the given group version (for ex: 'extensions/v1beta1').)" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_secret.go#L157 +#: pkg/kubectl/cmd/create_secret.go:158 +msgid "Password for Docker registry authentication" +msgstr "Password for Docker registry authentication" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_secret.go#L226 +#: pkg/kubectl/cmd/create_secret.go:226 +msgid "Path to PEM encoded public key certificate." +msgstr "Path to PEM encoded public key certificate." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_secret.go#L227 +#: pkg/kubectl/cmd/create_secret.go:227 +msgid "Path to private key associated with given certificate." +msgstr "Path to private key associated with given certificate." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/rollingupdate.go#L84 +#: pkg/kubectl/cmd/rollingupdate.go:85 +msgid "Perform a rolling update of the given ReplicationController" +msgstr "完成指定的 ReplicationController 的滚动升级" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/scale.go#L82 +#: pkg/kubectl/cmd/scale.go:83 +msgid "Precondition for resource version. Requires that the current resource version match this value in order to scale." +msgstr "Precondition for resource version. Requires that the current resource version match this value in order to scale." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/version.go#L39 +#: pkg/kubectl/cmd/version.go:40 +msgid "Print the client and server version information" +msgstr "输出 client 和 server 的版本信息" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/options.go#L37 +#: pkg/kubectl/cmd/options.go:38 +msgid "Print the list of flags inherited by all commands" +msgstr "输出所有命令的层级关系" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/logs.go#L86 +#: pkg/kubectl/cmd/logs.go:93 +msgid "Print the logs for a container in a pod" +msgstr "输出容器在 pod 中的日志" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/replace.go#L70 +#: pkg/kubectl/cmd/replace.go:71 +msgid "Replace a resource by filename or stdin" +msgstr "通过 filename 或者 stdin替换一个资源" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/rollout/rollout_resume.go#L71 +#: pkg/kubectl/cmd/rollout/rollout_resume.go:72 +msgid "Resume a paused resource" +msgstr "继续一个停止的 resource" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_rolebinding.go#L56 +#: pkg/kubectl/cmd/create_rolebinding.go:57 +msgid "Role this RoleBinding should reference" +msgstr "RoleBinding 的 Role 应该被引用" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/run.go#L94 +#: pkg/kubectl/cmd/run.go:97 +msgid "Run a particular image on the cluster" +msgstr "在集群中运行一个指定的镜像" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/proxy.go#L68 +#: pkg/kubectl/cmd/proxy.go:69 +msgid "Run a proxy to the Kubernetes API server" +msgstr "运行一个 proxy 到 Kubernetes API server" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_secret.go#L161 +#: pkg/kubectl/cmd/create_secret.go:161 +msgid "Server location for Docker registry" +msgstr "Server location for Docker registry" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/scale.go#L71 +#: pkg/kubectl/cmd/scale.go:71 +msgid "Set a new size for a Deployment, ReplicaSet, Replication Controller, or Job" +msgstr "为 Deployment, ReplicaSet, Replication Controller 或者 Job 设置一个新的副本数量" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/set/set.go#L37 +#: pkg/kubectl/cmd/set/set.go:38 +msgid "Set specific features on objects" +msgstr "为 objects 设置一个指定的特征" + +#: pkg/kubectl/cmd/apply_set_last_applied.go:83 +msgid "Set the last-applied-configuration annotation on a live object to match the contents of a file." +msgstr "Set the last-applied-configuration annotation on a live object to match the contents of a file." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/set/set_selector.go#L81 +#: pkg/kubectl/cmd/set/set_selector.go:82 +msgid "Set the selector on a resource" +msgstr "设置 resource 的 selector" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/config/create_cluster.go#L67 #: pkg/kubectl/cmd/config/create_cluster.go:68 msgid "Sets a cluster entry in kubeconfig" msgstr "设置 kubeconfig 文件中的一个集群条目" +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/config/create_context.go#L57 #: pkg/kubectl/cmd/config/create_context.go:58 msgid "Sets a context entry in kubeconfig" -msgstr "设置 kubeconfig 文件中的一个上下文条目" +msgstr "设置 kubeconfig 文件中的一个 context 条目" +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/config/create_authinfo.go#L103 #: pkg/kubectl/cmd/config/create_authinfo.go:104 msgid "Sets a user entry in kubeconfig" msgstr "设置 kubeconfig 文件中的一个用户条目" +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/config/set.go#L59 #: pkg/kubectl/cmd/config/set.go:60 msgid "Sets an individual value in a kubeconfig file" msgstr "设置 kubeconfig 文件中的一个单个值" +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/config/use_context.go#L48 #: pkg/kubectl/cmd/config/use_context.go:49 msgid "Sets the current-context in a kubeconfig file" msgstr "设置 kubeconfig 文件中的当前上下文" +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/describe.go#L80 +#: pkg/kubectl/cmd/describe.go:86 +msgid "Show details of a specific resource or group of resources" +msgstr "显示一个指定 resource 或者 group 的 resources 详情" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/rollout/rollout_status.go#L57 +#: pkg/kubectl/cmd/rollout/rollout_status.go:58 +msgid "Show the status of the rollout" +msgstr "显示 rollout 的状态" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/expose.go#L108 +#: pkg/kubectl/cmd/expose.go:106 +msgid "Synonym for --target-port" +msgstr "Synonym for --target-port" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/expose.go#L87 +#: pkg/kubectl/cmd/expose.go:88 +msgid "Take a replication controller, service, deployment or pod and expose it as a new Kubernetes Service" +msgstr "使用 replication controller, service, deployment 或者 pod 并暴露它作为一个 新的 Kubernetes Service" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/run.go#L114 +#: pkg/kubectl/cmd/run.go:117 +msgid "The image for the container to run." +msgstr "指定容器要运行的镜像." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/run.go#L116 +#: pkg/kubectl/cmd/run.go:119 +msgid "The image pull policy for the container. If left empty, this value will not be specified by the client and defaulted by the server" +msgstr "容器的镜像拉取策略. 如果为空, 这个值将不会 被 client 指定且使用 server 端的默认值" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/rollingupdate.go#L100 +#: pkg/kubectl/cmd/rollingupdate.go:101 +msgid "The key to use to differentiate between two different controllers, default 'deployment'. Only relevant when --image is specified, ignored otherwise" +msgstr "这个 key 使用有区别在两个不同的 controllers, 默认 'deployment'. 只有当 --image 指定值, 否则忽略" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_pdb.go#L62 +#: pkg/kubectl/cmd/create_pdb.go:63 +msgid "The minimum number or percentage of available pods this budget requires." +msgstr "最小数量百分比可用的 pods 作为 budget 要求." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/expose.go#L113 +#: pkg/kubectl/cmd/expose.go:111 +msgid "The name for the newly created object." +msgstr "名称为最新创建的对象." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/autoscale.go#L71 +#: pkg/kubectl/cmd/autoscale.go:72 +msgid "The name for the newly created object. If not specified, the name of the input resource will be used." +msgstr "名称为最新创建的对象. 如果没有指定, 输入资源的 名称即将被使用." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/run.go#L113 +#: pkg/kubectl/cmd/run.go:116 +msgid "The name of the API generator to use, see http://kubernetes.io/docs/user-guide/kubectl-conventions/#generators for a list." +msgstr "使用 API generator 的名字, 在 http://kubernetes.io/docs/user-guide/kubectl-conventions/#generators 查看列表." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/autoscale.go#L66 +#: pkg/kubectl/cmd/autoscale.go:67 +msgid "The name of the API generator to use. Currently there is only 1 generator." +msgstr "使用 API generator 的名字. 目前只有 1 个 generator." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/expose.go#L98 +#: pkg/kubectl/cmd/expose.go:99 +msgid "The name of the API generator to use. There are 2 generators: 'service/v1' and 'service/v2'. The only difference between them is that service port in v1 is named 'default', while it is left unnamed in v2. Default is 'service/v2'." +msgstr "使用 generator 的名称. 这里有 2 个 generators: 'service/v1' 和 'service/v2'. 为一个不同地方是服务端口在 v1 的情况下叫 'default', 如果在 v2 中没有指定名称. 默认的名称是 'service/v2'." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/run.go#L133 +#: pkg/kubectl/cmd/run.go:136 +msgid "The name of the generator to use for creating a service. Only used if --expose is true" +msgstr "使用 gnerator 的名称创建一个 service. 只有在 --expose 为 true 的时候使用" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/expose.go#L99 +#: pkg/kubectl/cmd/expose.go:100 +msgid "The network protocol for the service to be created. Default is 'TCP'." +msgstr "创建 service 的时候伴随着一个网络协议被创建. 默认是 'TCP'." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/expose.go#L100 +#: pkg/kubectl/cmd/expose.go:101 +msgid "The port that the service should serve on. Copied from the resource being exposed, if unspecified" +msgstr "服务的端口应该被指定. 如果没有指定, 从被创建的资源中复制" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/run.go#L121 +#: pkg/kubectl/cmd/run.go:124 +msgid "The port that this container exposes. If --expose is true, this is also the port used by the service that is created." +msgstr "The port that this container exposes. If --expose is true, this is also the port used by the service that is created." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/run.go#L131 +#: pkg/kubectl/cmd/run.go:134 +msgid "The resource requirement limits for this container. For example, 'cpu=200m,memory=512Mi'. Note that server side components may assign limits depending on the server configuration, such as limit ranges." +msgstr "The resource requirement limits for this container. For example, 'cpu=200m,memory=512Mi'. Note that server side components may assign limits depending on the server configuration, such as limit ranges." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/run.go#L130 +#: pkg/kubectl/cmd/run.go:133 +msgid "The resource requirement requests for this container. For example, 'cpu=100m,memory=256Mi'. Note that server side components may assign requests depending on the server configuration, such as limit ranges." +msgstr "资源为 container 请求 requests . 例如, 'cpu=100m,memory=256Mi'. 注意服务端组件也许会赋予 requests, 这决定于服务器端配置, 比如 limit ranges." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/run.go#L128 +#: pkg/kubectl/cmd/run.go:131 +msgid "The restart policy for this Pod. Legal values [Always, OnFailure, Never]. If set to 'Always' a deployment is created, if set to 'OnFailure' a job is created, if set to 'Never', a regular pod is created. For the latter two --replicas must be 1. Default 'Always', for CronJobs ` + "`" + `Never` + "`" + `." +msgstr "这个 Pod 的 restart policy. Legal values [Always, OnFailure, Never]. 如果设置为 'Always' 一个 deployment 被创建, 如果设置为 ’OnFailure' 一个 job 被创建, 如果设置为 'Never', 一个普通的 pod 被创建. 对于后面两个 --replicas 必须为 1. 默认 'Always', 为 CronJobs 设置为 ` + "`" + `Never` + "`" + `." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_secret.go#L87 +#: pkg/kubectl/cmd/create_secret.go:88 +msgid "The type of secret to create" +msgstr "创建 secret 类型资源" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/expose.go#L101 +#: pkg/kubectl/cmd/expose.go:102 +msgid "Type for this service: ClusterIP, NodePort, or LoadBalancer. Default is 'ClusterIP'." +msgstr "对于服务的类型: ClusterIP, NodePort, 或者 LoadBalancer. 默认是 'ClusterIP’." + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/rollout/rollout_undo.go#L71 +#: pkg/kubectl/cmd/rollout/rollout_undo.go:72 +msgid "Undo a previous rollout" +msgstr "撤销上一次的 rollout" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/config/unset.go#L47 #: pkg/kubectl/cmd/config/unset.go:48 msgid "Unsets an individual value in a kubeconfig file" msgstr "取消设置 kubeconfig 文件中的一个单个值" +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/patch.go#L91 +#: pkg/kubectl/cmd/patch.go:96 +msgid "Update field(s) of a resource using strategic merge patch" +msgstr "使用 strategic merge patch 更新一个资源的 field(s)" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/set/set_image.go#L94 +#: pkg/kubectl/cmd/set/set_image.go:95 +msgid "Update image of a pod template" +msgstr "更新一个 pod template 的镜像" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/set/set_resources.go#L101 +#: pkg/kubectl/cmd/set/set_resources.go:102 +msgid "Update resource requests/limits on objects with pod templates" +msgstr "在对象的 pod templates 上更新资源的 requests/limits" + #: pkg/kubectl/cmd/annotate.go:116 msgid "Update the annotations on a resource" msgstr "更新一个资源的注解" -msgid "" -"watch is only supported on individual resources and resource collections - " -"%d resources were found" -msgid_plural "" -"watch is only supported on individual resources and resource collections - " -"%d resources were found" -msgstr[0] "watch 仅支持单独的资源或者资源集合 - 找到了 %d 个资源" -msgstr[1] "watch 仅支持单独的资源或者资源集合 - 找到了 %d 个资源" +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/label.go#L109 +#: pkg/kubectl/cmd/label.go:114 +msgid "Update the labels on a resource" +msgstr "更新在这个资源上的 labels" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/taint.go#L88 +#: pkg/kubectl/cmd/taint.go:87 +msgid "Update the taints on one or more nodes" +msgstr "更新一个或者多个 node 上的 taints" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_secret.go#L155 +#: pkg/kubectl/cmd/create_secret.go:156 +msgid "Username for Docker registry authentication" +msgstr "Username 为 Docker registry authentication" + +#: pkg/kubectl/cmd/apply_view_last_applied.go:64 +msgid "View latest last-applied-configuration annotations of a resource/object" +msgstr "显示最后的 resource/object 的 last-applied-configuration annotations" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/rollout/rollout_history.go#L51 +#: pkg/kubectl/cmd/rollout/rollout_history.go:52 +msgid "View rollout history" +msgstr "显示 rollout 历史" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/clusterinfo_dump.go#L45 +#: pkg/kubectl/cmd/clusterinfo_dump.go:46 +msgid "Where to output the files. If empty or '-' uses stdout, otherwise creates a directory hierarchy in that directory" +msgstr "输出到 files. 如果是 empty or '-' 使用 stdout, 否则创建一个 目录层级在那个目录" + +#: pkg/kubectl/cmd/run_test.go:85 +msgid "dummy restart flag)" +msgstr "dummy restart flag)" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create_service.go#L253 +#: pkg/kubectl/cmd/create_service.go:254 +msgid "external name of service" +msgstr "服务的外部名称" + +# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/cmd.go#L217 +#: pkg/kubectl/cmd/cmd.go:227 +msgid "kubectl controls the Kubernetes cluster manager" +msgstr "kubectl 控制 Kubernetes cluster 管理" + +#~ msgid "watch is only supported on individual resources and resource collections - %d resources were found" +#~ msgid_plural "watch is only supported on individual resources and resource collections - %d resources were found" +#~ msgstr[0] "watch 仅支持单独的资源或者资源集合 - 找到了 %d 个资源watch is only supported on individual resources and resource collections - %d resource was found" +#~ msgstr[1] "watch 仅支持单独的资源或者资源集合 - 找到了 %d 个资源watch is only supported on individual resources and resource collections - %d resources were found" `) func translationsKubectlZh_cnLc_messagesK8sPoBytes() ([]byte, error) { diff --git a/pkg/generated/openapi/BUILD b/pkg/generated/openapi/BUILD index b421da0e599..5c224582214 100644 --- a/pkg/generated/openapi/BUILD +++ b/pkg/generated/openapi/BUILD @@ -4,22 +4,26 @@ package(default_visibility = ["//visibility:public"]) load("//pkg/generated/openapi:def.bzl", "openapi_library") +load("//build:openapi.bzl", "openapi_go_prefix", "openapi_vendor_prefix") openapi_library( name = "go_default_library", srcs = ["doc.go"], + go_prefix = openapi_go_prefix, openapi_targets = [ - "federation/apis/federation/v1beta1", "pkg/apis/abac/v0", "pkg/apis/abac/v1beta1", "pkg/apis/componentconfig/v1alpha1", "pkg/kubelet/apis/kubeletconfig/v1alpha1", + "pkg/proxy/apis/kubeproxyconfig/v1alpha1", "pkg/version", ], tags = ["automanaged"], + vendor_prefix = openapi_vendor_prefix, vendor_targets = [ - "k8s.io/api/admission/v1alpha1", + "k8s.io/api/admission/v1beta1", "k8s.io/api/admissionregistration/v1alpha1", + "k8s.io/api/admissionregistration/v1beta1", "k8s.io/api/apps/v1", "k8s.io/api/apps/v1beta1", "k8s.io/api/apps/v1beta2", @@ -34,6 +38,7 @@ openapi_library( "k8s.io/api/batch/v2alpha1", "k8s.io/api/certificates/v1beta1", "k8s.io/api/core/v1", + "k8s.io/api/events/v1beta1", "k8s.io/api/extensions/v1beta1", "k8s.io/api/imagepolicy/v1alpha1", "k8s.io/api/networking/v1", @@ -44,6 +49,7 @@ openapi_library( "k8s.io/api/scheduling/v1alpha1", "k8s.io/api/settings/v1alpha1", "k8s.io/api/storage/v1", + "k8s.io/api/storage/v1alpha1", "k8s.io/api/storage/v1beta1", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1", "k8s.io/apimachinery/pkg/api/resource", @@ -56,6 +62,7 @@ openapi_library( "k8s.io/apiserver/pkg/apis/audit/v1alpha1", "k8s.io/apiserver/pkg/apis/audit/v1beta1", "k8s.io/apiserver/pkg/apis/example/v1", + "k8s.io/apiserver/pkg/apis/example2/v1", "k8s.io/client-go/pkg/version", "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1", "k8s.io/metrics/pkg/apis/custom_metrics/v1beta1", diff --git a/pkg/generated/openapi/def.bzl b/pkg/generated/openapi/def.bzl index fa530f2b745..87fa23acd1c 100644 --- a/pkg/generated/openapi/def.bzl +++ b/pkg/generated/openapi/def.bzl @@ -1,7 +1,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") load("@io_kubernetes_build//defs:go.bzl", "go_genrule") -def openapi_library(name, tags, srcs, openapi_targets=[], vendor_targets=[]): +def openapi_library(name, tags, srcs, go_prefix, vendor_prefix="", openapi_targets=[], vendor_targets=[]): deps = [ "//vendor/github.com/go-openapi/spec:go_default_library", "//vendor/k8s.io/kube-openapi/pkg/common:go_default_library", @@ -14,17 +14,17 @@ def openapi_library(name, tags, srcs, openapi_targets=[], vendor_targets=[]): ) go_genrule( name = "zz_generated.openapi", - srcs = srcs + ["//hack/boilerplate:boilerplate.go.txt"], + srcs = srcs + ["//" + vendor_prefix + "hack/boilerplate:boilerplate.go.txt"], outs = ["zz_generated.openapi.go"], cmd = " ".join([ "$(location //vendor/k8s.io/code-generator/cmd/openapi-gen)", "--v 1", "--logtostderr", - "--go-header-file $(location //hack/boilerplate:boilerplate.go.txt)", + "--go-header-file $(location //" + vendor_prefix + "hack/boilerplate:boilerplate.go.txt)", "--output-file-base zz_generated.openapi", - "--output-package k8s.io/kubernetes/pkg/generated/openapi", - "--input-dirs " + ",".join(["k8s.io/kubernetes/" + target for target in openapi_targets] + ["k8s.io/kubernetes/vendor/" + target for target in vendor_targets]), - "&& cp pkg/generated/openapi/zz_generated.openapi.go $(location :zz_generated.openapi.go)", + "--output-package " + go_prefix + vendor_prefix + "pkg/generated/openapi", + "--input-dirs " + ",".join([go_prefix + target for target in openapi_targets] + [go_prefix + "vendor/" + target for target in vendor_targets]), + "&& cp " + vendor_prefix + "pkg/generated/openapi/zz_generated.openapi.go $(location :zz_generated.openapi.go)", ]), go_deps = deps, tools = ["//vendor/k8s.io/code-generator/cmd/openapi-gen"], diff --git a/pkg/hyperkube/BUILD b/pkg/hyperkube/BUILD deleted file mode 100644 index 3a623579705..00000000000 --- a/pkg/hyperkube/BUILD +++ /dev/null @@ -1,22 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) - -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "go_default_library", - srcs = ["doc.go"], - importpath = "k8s.io/kubernetes/pkg/hyperkube", -) diff --git a/pkg/hyperkube/doc.go b/pkg/hyperkube/doc.go deleted file mode 100644 index d66d3ac3c26..00000000000 --- a/pkg/hyperkube/doc.go +++ /dev/null @@ -1,30 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package hyperkube is a framework for kubernetes server components. It -// allows us to combine all of the kubernetes server components into a single -// binary where the user selects which components to run in any individual -// process. -// -// Currently, only one server component can be run at once. As such there is -// no need to harmonize flags or identify logs across the various servers. In -// the future we will support launching and running many servers -- either by -// managing processes or running in-proc. -// -// This package is inspired by https://github.com/spf13/cobra. However, as -// the eventual goal is to run *multiple* servers from one call, a new package -// was needed. -package hyperkube // import "k8s.io/kubernetes/pkg/hyperkube" diff --git a/pkg/kubeapiserver/BUILD b/pkg/kubeapiserver/BUILD index 81416341a8d..63cc305a016 100644 --- a/pkg/kubeapiserver/BUILD +++ b/pkg/kubeapiserver/BUILD @@ -14,7 +14,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/kubeapiserver", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apiserver/pkg/server/storage:go_default_library", @@ -46,11 +46,11 @@ filegroup( go_test( name = "go_default_test", srcs = ["default_storage_factory_builder_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubeapiserver", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core/install:go_default_library", "//pkg/apis/extensions/install:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", diff --git a/pkg/kubeapiserver/admission/BUILD b/pkg/kubeapiserver/admission/BUILD index 9e2b4c1be06..eb3da8d7b3c 100644 --- a/pkg/kubeapiserver/admission/BUILD +++ b/pkg/kubeapiserver/admission/BUILD @@ -8,9 +8,9 @@ load( go_test( name = "go_default_test", - srcs = ["init_test.go"], + srcs = ["initializer_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubeapiserver/admission", - library = ":go_default_library", deps = ["//vendor/k8s.io/apiserver/pkg/admission:go_default_library"], ) @@ -24,6 +24,7 @@ go_library( "//pkg/quota:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library", "//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", ], diff --git a/pkg/kubeapiserver/admission/init_test.go b/pkg/kubeapiserver/admission/init_test.go deleted file mode 100644 index 80152a291bc..00000000000 --- a/pkg/kubeapiserver/admission/init_test.go +++ /dev/null @@ -1,73 +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 admission - -import ( - "net/url" - "testing" - - "k8s.io/apiserver/pkg/admission" -) - -type doNothingAdmission struct{} - -func (doNothingAdmission) Admit(a admission.Attributes) error { return nil } -func (doNothingAdmission) Handles(o admission.Operation) bool { return false } -func (doNothingAdmission) Validate() error { return nil } - -type WantsCloudConfigAdmissionPlugin struct { - doNothingAdmission - cloudConfig []byte -} - -func (self *WantsCloudConfigAdmissionPlugin) SetCloudConfig(cloudConfig []byte) { - self.cloudConfig = cloudConfig -} - -func TestCloudConfigAdmissionPlugin(t *testing.T) { - cloudConfig := []byte("cloud-configuration") - initializer := NewPluginInitializer(nil, nil, cloudConfig, nil, nil) - wantsCloudConfigAdmission := &WantsCloudConfigAdmissionPlugin{} - initializer.Initialize(wantsCloudConfigAdmission) - - if wantsCloudConfigAdmission.cloudConfig == nil { - t.Errorf("Expected cloud config to be initialized but found nil") - } -} - -type fakeServiceResolver struct{} - -func (f *fakeServiceResolver) ResolveEndpoint(namespace, name string) (*url.URL, error) { - return nil, nil -} - -type serviceWanter struct { - doNothingAdmission - got ServiceResolver -} - -func (s *serviceWanter) SetServiceResolver(sr ServiceResolver) { s.got = sr } - -func TestWantsServiceResolver(t *testing.T) { - sw := &serviceWanter{} - fsr := &fakeServiceResolver{} - i := &PluginInitializer{} - i.SetServiceResolver(fsr).Initialize(sw) - if got, ok := sw.got.(*fakeServiceResolver); !ok || got != fsr { - t.Errorf("plumbing fail - %v %v#", ok, got) - } -} diff --git a/pkg/kubeapiserver/admission/initializer.go b/pkg/kubeapiserver/admission/initializer.go index d90e9bc63b3..0a7903227c7 100644 --- a/pkg/kubeapiserver/admission/initializer.go +++ b/pkg/kubeapiserver/admission/initializer.go @@ -17,11 +17,9 @@ limitations under the License. package admission import ( - "net/http" - "net/url" - "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apiserver/pkg/admission" + webhookconfig "k8s.io/apiserver/pkg/admission/plugin/webhook/config" "k8s.io/apiserver/pkg/authorization/authorizer" clientset "k8s.io/client-go/kubernetes" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" @@ -34,13 +32,13 @@ import ( // WantsInternalKubeClientSet defines a function which sets ClientSet for admission plugins that need it type WantsInternalKubeClientSet interface { SetInternalKubeClientSet(internalclientset.Interface) - admission.Validator + admission.InitializationValidator } // WantsInternalKubeInformerFactory defines a function which sets InformerFactory for admission plugins that need it type WantsInternalKubeInformerFactory interface { SetInternalKubeInformerFactory(informers.SharedInformerFactory) - admission.Validator + admission.InitializationValidator } // WantsCloudConfig defines a function which sets CloudConfig for admission plugins that need it. @@ -53,42 +51,23 @@ type WantsRESTMapper interface { SetRESTMapper(meta.RESTMapper) } -// WantsQuotaRegistry defines a function which sets quota registry for admission plugins that need it. -type WantsQuotaRegistry interface { - SetQuotaRegistry(quota.Registry) - admission.Validator -} - -// WantsServiceResolver defines a fuction that accepts a ServiceResolver for -// admission plugins that need to make calls to services. -type WantsServiceResolver interface { - SetServiceResolver(ServiceResolver) -} - -// ServiceResolver knows how to convert a service reference into an actual -// location. -type ServiceResolver interface { - ResolveEndpoint(namespace, name string) (*url.URL, error) -} - -// WantsProxyTransport defines a fuction that accepts a proxy transport for admission -// plugins that need to make calls to pods. -type WantsProxyTransport interface { - SetProxyTransport(proxyTransport *http.Transport) +// WantsQuotaConfiguration defines a function which sets quota configuration for admission plugins that need it. +type WantsQuotaConfiguration interface { + SetQuotaConfiguration(quota.Configuration) + admission.InitializationValidator } +// PluginInitializer is used for initialization of the Kubernetes specific admission plugins. type PluginInitializer struct { - internalClient internalclientset.Interface - externalClient clientset.Interface - informers informers.SharedInformerFactory - authorizer authorizer.Authorizer - cloudConfig []byte - restMapper meta.RESTMapper - quotaRegistry quota.Registry - serviceResolver ServiceResolver - - // for proving we are apiserver in call-outs - proxyTransport *http.Transport + internalClient internalclientset.Interface + externalClient clientset.Interface + informers informers.SharedInformerFactory + authorizer authorizer.Authorizer + cloudConfig []byte + restMapper meta.RESTMapper + quotaConfiguration quota.Configuration + serviceResolver webhookconfig.ServiceResolver + authenticationInfoResolverWrapper webhookconfig.AuthenticationInfoResolverWrapper } var _ admission.PluginInitializer = &PluginInitializer{} @@ -101,29 +80,17 @@ func NewPluginInitializer( sharedInformers informers.SharedInformerFactory, cloudConfig []byte, restMapper meta.RESTMapper, - quotaRegistry quota.Registry, + quotaConfiguration quota.Configuration, ) *PluginInitializer { return &PluginInitializer{ - internalClient: internalClient, - informers: sharedInformers, - cloudConfig: cloudConfig, - restMapper: restMapper, - quotaRegistry: quotaRegistry, + internalClient: internalClient, + informers: sharedInformers, + cloudConfig: cloudConfig, + restMapper: restMapper, + quotaConfiguration: quotaConfiguration, } } -// SetServiceResolver sets the service resolver which is needed by some plugins. -func (i *PluginInitializer) SetServiceResolver(s ServiceResolver) *PluginInitializer { - i.serviceResolver = s - return i -} - -// SetProxyTransport sets the proxyTransport which is needed by some plugins. -func (i *PluginInitializer) SetProxyTransport(proxyTransport *http.Transport) *PluginInitializer { - i.proxyTransport = proxyTransport - return i -} - // Initialize checks the initialization interfaces implemented by each plugin // and provide the appropriate initialization data func (i *PluginInitializer) Initialize(plugin admission.Interface) { @@ -143,15 +110,7 @@ func (i *PluginInitializer) Initialize(plugin admission.Interface) { wants.SetRESTMapper(i.restMapper) } - if wants, ok := plugin.(WantsQuotaRegistry); ok { - wants.SetQuotaRegistry(i.quotaRegistry) - } - - if wants, ok := plugin.(WantsServiceResolver); ok { - wants.SetServiceResolver(i.serviceResolver) - } - - if wants, ok := plugin.(WantsProxyTransport); ok { - wants.SetProxyTransport(i.proxyTransport) + if wants, ok := plugin.(WantsQuotaConfiguration); ok { + wants.SetQuotaConfiguration(i.quotaConfiguration) } } diff --git a/pkg/kubeapiserver/admission/initializer_test.go b/pkg/kubeapiserver/admission/initializer_test.go new file mode 100644 index 00000000000..b88021d8a69 --- /dev/null +++ b/pkg/kubeapiserver/admission/initializer_test.go @@ -0,0 +1,49 @@ +/* +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 admission + +import ( + "testing" + + "k8s.io/apiserver/pkg/admission" +) + +type doNothingAdmission struct{} + +func (doNothingAdmission) Admit(a admission.Attributes) error { return nil } +func (doNothingAdmission) Handles(o admission.Operation) bool { return false } +func (doNothingAdmission) Validate() error { return nil } + +type WantsCloudConfigAdmissionPlugin struct { + doNothingAdmission + cloudConfig []byte +} + +func (self *WantsCloudConfigAdmissionPlugin) SetCloudConfig(cloudConfig []byte) { + self.cloudConfig = cloudConfig +} + +func TestCloudConfigAdmissionPlugin(t *testing.T) { + cloudConfig := []byte("cloud-configuration") + initializer := NewPluginInitializer(nil, nil, cloudConfig, nil, nil) + wantsCloudConfigAdmission := &WantsCloudConfigAdmissionPlugin{} + initializer.Initialize(wantsCloudConfigAdmission) + + if wantsCloudConfigAdmission.cloudConfig == nil { + t.Errorf("Expected cloud config to be initialized but found nil") + } +} diff --git a/pkg/kubeapiserver/authorizer/BUILD b/pkg/kubeapiserver/authorizer/BUILD index 5d5acebfb76..d15558e2079 100644 --- a/pkg/kubeapiserver/authorizer/BUILD +++ b/pkg/kubeapiserver/authorizer/BUILD @@ -12,8 +12,8 @@ go_test( data = [ "//pkg/auth/authorizer/abac:example_policy", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubeapiserver/authorizer", - library = ":go_default_library", deps = ["//pkg/kubeapiserver/authorizer/modes:go_default_library"], ) diff --git a/pkg/kubeapiserver/authorizer/modes/BUILD b/pkg/kubeapiserver/authorizer/modes/BUILD index 9be3d02c9a8..038d64aa1f2 100644 --- a/pkg/kubeapiserver/authorizer/modes/BUILD +++ b/pkg/kubeapiserver/authorizer/modes/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["modes_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes", - library = ":go_default_library", ) go_library( diff --git a/pkg/kubeapiserver/default_storage_factory_builder.go b/pkg/kubeapiserver/default_storage_factory_builder.go index 4f266e528a3..4803cce2eb8 100644 --- a/pkg/kubeapiserver/default_storage_factory_builder.go +++ b/pkg/kubeapiserver/default_storage_factory_builder.go @@ -26,11 +26,11 @@ import ( serverstorage "k8s.io/apiserver/pkg/server/storage" "k8s.io/apiserver/pkg/storage/storagebackend" utilflag "k8s.io/apiserver/pkg/util/flag" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" ) -// specialDefaultResourcePrefixes are prefixes compiled into Kubernetes. -var specialDefaultResourcePrefixes = map[schema.GroupResource]string{ +// SpecialDefaultResourcePrefixes are prefixes compiled into Kubernetes. +var SpecialDefaultResourcePrefixes = map[schema.GroupResource]string{ {Group: "", Resource: "replicationControllers"}: "controllers", {Group: "", Resource: "replicationcontrollers"}: "controllers", {Group: "", Resource: "endpoints"}: "services/endpoints", @@ -53,10 +53,10 @@ func NewStorageFactory(storageConfig storagebackend.Config, defaultMediaType str if err != nil { return nil, err } - return serverstorage.NewDefaultStorageFactory(storageConfig, defaultMediaType, serializer, resourceEncodingConfig, apiResourceConfig, specialDefaultResourcePrefixes), nil + return serverstorage.NewDefaultStorageFactory(storageConfig, defaultMediaType, serializer, resourceEncodingConfig, apiResourceConfig, SpecialDefaultResourcePrefixes), nil } -// Merges the given defaultResourceConfig with specifc GroupvVersionResource overrides. +// Merges the given defaultResourceConfig with specific GroupVersionResource overrides. func mergeResourceEncodingConfigs(defaultResourceEncoding *serverstorage.DefaultResourceEncodingConfig, resourceEncodingOverrides []schema.GroupVersionResource) *serverstorage.DefaultResourceEncodingConfig { resourceEncodingConfig := defaultResourceEncoding for _, gvr := range resourceEncodingOverrides { @@ -66,7 +66,7 @@ func mergeResourceEncodingConfigs(defaultResourceEncoding *serverstorage.Default return resourceEncodingConfig } -// Merges the given defaultResourceConfig with specifc GroupVersion overrides. +// Merges the given defaultResourceConfig with specific GroupVersion overrides. func mergeGroupEncodingConfigs(defaultResourceEncoding *serverstorage.DefaultResourceEncodingConfig, storageEncodingOverrides map[string]schema.GroupVersion) *serverstorage.DefaultResourceEncodingConfig { resourceEncodingConfig := defaultResourceEncoding for group, storageEncodingVersion := range storageEncodingOverrides { @@ -85,9 +85,9 @@ func mergeAPIResourceConfigs(defaultAPIResourceConfig *serverstorage.ResourceCon if ok { if allAPIFlagValue == "false" { // Disable all group versions. - resourceConfig.DisableVersions(api.Registry.RegisteredGroupVersions()...) + resourceConfig.DisableVersions(legacyscheme.Registry.RegisteredGroupVersions()...) } else if allAPIFlagValue == "true" { - resourceConfig.EnableVersions(api.Registry.RegisteredGroupVersions()...) + resourceConfig.EnableVersions(legacyscheme.Registry.RegisteredGroupVersions()...) } } @@ -121,8 +121,8 @@ func mergeAPIResourceConfigs(defaultAPIResourceConfig *serverstorage.ResourceCon if err != nil { return nil, fmt.Errorf("invalid key %s", key) } - // Verify that the groupVersion is api.Registry. - if !api.Registry.IsRegisteredVersion(groupVersion) { + // Verify that the groupVersion is legacyscheme.Registry. + if !legacyscheme.Registry.IsRegisteredVersion(groupVersion) { return nil, fmt.Errorf("group version %s that has not been registered", groupVersion.String()) } enabled, err := getRuntimeConfigValue(overrides, key, false) @@ -153,8 +153,8 @@ func mergeAPIResourceConfigs(defaultAPIResourceConfig *serverstorage.ResourceCon return nil, fmt.Errorf("invalid key %s", key) } resource := tokens[2] - // Verify that the groupVersion is api.Registry. - if !api.Registry.IsRegisteredVersion(groupVersion) { + // Verify that the groupVersion is legacyscheme.Registry. + if !legacyscheme.Registry.IsRegisteredVersion(groupVersion) { return nil, fmt.Errorf("group version %s that has not been registered", groupVersion.String()) } diff --git a/pkg/kubeapiserver/default_storage_factory_builder_test.go b/pkg/kubeapiserver/default_storage_factory_builder_test.go index 869fffba1a2..b5c9b4d8867 100644 --- a/pkg/kubeapiserver/default_storage_factory_builder_test.go +++ b/pkg/kubeapiserver/default_storage_factory_builder_test.go @@ -24,8 +24,8 @@ import ( extensionsapiv1beta1 "k8s.io/api/extensions/v1beta1" "k8s.io/apimachinery/pkg/runtime/schema" serverstorage "k8s.io/apiserver/pkg/server/storage" - "k8s.io/kubernetes/pkg/api" - _ "k8s.io/kubernetes/pkg/api/install" + "k8s.io/kubernetes/pkg/api/legacyscheme" + _ "k8s.io/kubernetes/pkg/apis/core/install" _ "k8s.io/kubernetes/pkg/apis/extensions/install" ) @@ -173,7 +173,7 @@ func TestParseRuntimeConfig(t *testing.T) { }, expectedAPIConfig: func() *serverstorage.ResourceConfig { config := serverstorage.NewResourceConfig() - config.EnableVersions(api.Registry.RegisteredGroupVersions()...) + config.EnableVersions(legacyscheme.Registry.RegisteredGroupVersions()...) return config }, err: false, @@ -188,7 +188,7 @@ func TestParseRuntimeConfig(t *testing.T) { }, expectedAPIConfig: func() *serverstorage.ResourceConfig { config := serverstorage.NewResourceConfig() - config.DisableVersions(api.Registry.RegisteredGroupVersions()...) + config.DisableVersions(legacyscheme.Registry.RegisteredGroupVersions()...) return config }, err: false, diff --git a/pkg/kubeapiserver/options/BUILD b/pkg/kubeapiserver/options/BUILD index a6c122961a0..8236765c55e 100644 --- a/pkg/kubeapiserver/options/BUILD +++ b/pkg/kubeapiserver/options/BUILD @@ -19,9 +19,8 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/kubeapiserver/options", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", - "//pkg/cloudprovider:go_default_library", "//pkg/kubeapiserver/authenticator:go_default_library", "//pkg/kubeapiserver/authorizer:go_default_library", "//pkg/kubeapiserver/authorizer/modes:go_default_library", @@ -29,7 +28,6 @@ go_library( "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/pborman/uuid:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", "//vendor/k8s.io/apiserver/pkg/server:go_default_library", @@ -54,7 +52,7 @@ filegroup( go_test( name = "go_default_test", srcs = ["storage_versions_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubeapiserver/options", - library = ":go_default_library", deps = ["//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library"], ) diff --git a/pkg/kubeapiserver/options/api_enablement.go b/pkg/kubeapiserver/options/api_enablement.go index fe5accd4f1a..a5d7f625de0 100644 --- a/pkg/kubeapiserver/options/api_enablement.go +++ b/pkg/kubeapiserver/options/api_enablement.go @@ -38,7 +38,8 @@ func NewAPIEnablementOptions() *APIEnablementOptions { func (s *APIEnablementOptions) AddFlags(fs *pflag.FlagSet) { fs.Var(&s.RuntimeConfig, "runtime-config", ""+ "A set of key=value pairs that describe runtime configuration that may be passed "+ - "to apiserver. apis/ key can be used to turn on/off specific api versions. "+ - "apis// can be used to turn on/off specific resources. api/all and "+ + "to apiserver. / (or for the core group) key can be used to "+ + "turn on/off specific api versions. // (or / "+ + "for the core group) can be used to turn on/off specific resources. api/all and "+ "api/legacy are special keys to control all and legacy api versions respectively.") } diff --git a/pkg/kubeapiserver/options/authentication.go b/pkg/kubeapiserver/options/authentication.go index 9c89512755a..6a6b579f215 100644 --- a/pkg/kubeapiserver/options/authentication.go +++ b/pkg/kubeapiserver/options/authentication.go @@ -294,8 +294,10 @@ func (s *BuiltInAuthenticationOptions) ToAuthenticationConfig() authenticator.Au ret.OIDCCAFile = s.OIDC.CAFile ret.OIDCClientID = s.OIDC.ClientID ret.OIDCGroupsClaim = s.OIDC.GroupsClaim + ret.OIDCGroupsPrefix = s.OIDC.GroupsPrefix ret.OIDCIssuerURL = s.OIDC.IssuerURL ret.OIDCUsernameClaim = s.OIDC.UsernameClaim + ret.OIDCUsernamePrefix = s.OIDC.UsernamePrefix } if s.PasswordFile != nil { diff --git a/pkg/kubeapiserver/options/cloudprovider.go b/pkg/kubeapiserver/options/cloudprovider.go index f1e59976242..9b8119fb072 100644 --- a/pkg/kubeapiserver/options/cloudprovider.go +++ b/pkg/kubeapiserver/options/cloudprovider.go @@ -17,15 +17,7 @@ limitations under the License. package options import ( - "fmt" - "os" - - "github.com/golang/glog" "github.com/spf13/pflag" - - "k8s.io/api/core/v1" - genericoptions "k8s.io/apiserver/pkg/server/options" - "k8s.io/kubernetes/pkg/cloudprovider" ) type CloudProviderOptions struct { @@ -49,42 +41,3 @@ func (s *CloudProviderOptions) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&s.CloudConfigFile, "cloud-config", s.CloudConfigFile, "The path to the cloud provider configuration file. Empty string for no configuration file.") } - -func (s *CloudProviderOptions) DefaultExternalHost(genericoptions *genericoptions.ServerRunOptions) error { - if len(genericoptions.ExternalHost) != 0 { - return nil - } - - if cloudprovider.IsCloudProvider(s.CloudProvider) { - glog.Info("--external-hostname was not specified. Trying to get it from the cloud provider.") - - cloud, err := cloudprovider.InitCloudProvider(s.CloudProvider, s.CloudConfigFile) - if err != nil { - return fmt.Errorf("%q cloud provider could not be initialized: %v", s.CloudProvider, err) - } - instances, supported := cloud.Instances() - if !supported { - return fmt.Errorf("%q cloud provider has no instances", s.CloudProvider) - } - hostname, err := os.Hostname() - if err != nil { - return fmt.Errorf("failed to get hostname: %v", err) - } - nodeName, err := instances.CurrentNodeName(hostname) - if err != nil { - return fmt.Errorf("failed to get NodeName from %q cloud provider: %v", s.CloudProvider, err) - } - addrs, err := instances.NodeAddresses(nodeName) - if err != nil { - return fmt.Errorf("failed to get external host address from %q cloud provider: %v", s.CloudProvider, err) - } else { - for _, addr := range addrs { - if addr.Type == v1.NodeExternalIP { - genericoptions.ExternalHost = addr.Address - } - } - } - } - - return nil -} diff --git a/pkg/kubeapiserver/options/serving.go b/pkg/kubeapiserver/options/serving.go index d3d2614921a..79e3f67500b 100644 --- a/pkg/kubeapiserver/options/serving.go +++ b/pkg/kubeapiserver/options/serving.go @@ -85,7 +85,7 @@ func (s InsecureServingOptions) Validate(portArg string) []error { errors := []error{} if s.BindPort < 0 || s.BindPort > 65535 { - errors = append(errors, fmt.Errorf("--insecure-port %v must be between 0 and 65535, inclusive. 0 for turning off secure port.", s.BindPort)) + errors = append(errors, fmt.Errorf("--insecure-port %v must be between 0 and 65535, inclusive. 0 for turning off insecure (HTTP) port.", s.BindPort)) } return errors @@ -103,7 +103,7 @@ func (s *InsecureServingOptions) AddFlags(fs *pflag.FlagSet) { "The port on which to serve unsecured, unauthenticated access. It is assumed "+ "that firewall rules are set up such that this port is not reachable from outside of "+ "the cluster and that port 443 on the cluster's public address is proxied to this "+ - "port. This is performed by nginx in the default setup.") + "port. This is performed by nginx in the default setup. Set to zero to disable") } func (s *InsecureServingOptions) AddDeprecatedFlags(fs *pflag.FlagSet) { diff --git a/pkg/kubeapiserver/options/storage_versions.go b/pkg/kubeapiserver/options/storage_versions.go index d466900431a..bac8725605e 100644 --- a/pkg/kubeapiserver/options/storage_versions.go +++ b/pkg/kubeapiserver/options/storage_versions.go @@ -20,7 +20,7 @@ import ( "strings" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "github.com/spf13/pflag" ) @@ -40,8 +40,8 @@ type StorageSerializationOptions struct { func NewStorageSerializationOptions() *StorageSerializationOptions { return &StorageSerializationOptions{ - DefaultStorageVersions: api.Registry.AllPreferredGroupVersions(), - StorageVersions: api.Registry.AllPreferredGroupVersions(), + DefaultStorageVersions: legacyscheme.Registry.AllPreferredGroupVersions(), + StorageVersions: legacyscheme.Registry.AllPreferredGroupVersions(), } } diff --git a/pkg/kubeapiserver/server/BUILD b/pkg/kubeapiserver/server/BUILD index 241deddee97..2c7cf9a245e 100644 --- a/pkg/kubeapiserver/server/BUILD +++ b/pkg/kubeapiserver/server/BUILD @@ -17,6 +17,7 @@ go_library( "//vendor/k8s.io/apiserver/pkg/features:go_default_library", "//vendor/k8s.io/apiserver/pkg/server:go_default_library", "//vendor/k8s.io/apiserver/pkg/server/filters:go_default_library", + "//vendor/k8s.io/apiserver/pkg/server/options:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", ], diff --git a/pkg/kubeapiserver/server/insecure_handler.go b/pkg/kubeapiserver/server/insecure_handler.go index 8186a0ac7ab..c3a3921f50a 100644 --- a/pkg/kubeapiserver/server/insecure_handler.go +++ b/pkg/kubeapiserver/server/insecure_handler.go @@ -19,6 +19,7 @@ package server import ( "net" "net/http" + "time" "github.com/golang/glog" @@ -28,6 +29,7 @@ import ( "k8s.io/apiserver/pkg/features" "k8s.io/apiserver/pkg/server" genericfilters "k8s.io/apiserver/pkg/server/filters" + "k8s.io/apiserver/pkg/server/options" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/rest" ) @@ -45,11 +47,12 @@ func BuildInsecureHandlerChain(apiHandler http.Handler, c *server.Config) http.H } handler = genericapifilters.WithAuthentication(handler, c.RequestContextMapper, insecureSuperuser{}, nil) handler = genericfilters.WithCORS(handler, c.CorsAllowedOriginList, nil, nil, nil, "true") - handler = genericfilters.WithPanicRecovery(handler) handler = genericfilters.WithTimeoutForNonLongRunningRequests(handler, c.RequestContextMapper, c.LongRunningFunc, c.RequestTimeout) handler = genericfilters.WithMaxInFlightLimit(handler, c.MaxRequestsInFlight, c.MaxMutatingRequestsInFlight, c.RequestContextMapper, c.LongRunningFunc) + handler = genericfilters.WithWaitGroup(handler, c.RequestContextMapper, c.LongRunningFunc, c.HandlerChainWaitGroup) handler = genericapifilters.WithRequestInfo(handler, server.NewRequestInfoResolver(c), c.RequestContextMapper) handler = apirequest.WithRequestContext(handler, c.RequestContextMapper) + handler = genericfilters.WithPanicRecovery(handler) return handler } @@ -84,12 +87,12 @@ func (s *InsecureServingInfo) NewLoopbackClientConfig(token string) (*rest.Confi // NonBlockingRun spawns the insecure http server. An error is // returned if the ports cannot be listened on. -func NonBlockingRun(insecureServingInfo *InsecureServingInfo, insecureHandler http.Handler, stopCh <-chan struct{}) error { +func NonBlockingRun(insecureServingInfo *InsecureServingInfo, insecureHandler http.Handler, shutDownTimeout time.Duration, stopCh <-chan struct{}) error { // Use an internal stop channel to allow cleanup of the listeners on error. internalStopCh := make(chan struct{}) if insecureServingInfo != nil && insecureHandler != nil { - if err := serveInsecurely(insecureServingInfo, insecureHandler, internalStopCh); err != nil { + if err := serveInsecurely(insecureServingInfo, insecureHandler, shutDownTimeout, internalStopCh); err != nil { close(internalStopCh) return err } @@ -109,15 +112,18 @@ func NonBlockingRun(insecureServingInfo *InsecureServingInfo, insecureHandler ht // serveInsecurely run the insecure http server. It fails only if the initial listen // call fails. The actual server loop (stoppable by closing stopCh) runs in a go // routine, i.e. serveInsecurely does not block. -func serveInsecurely(insecureServingInfo *InsecureServingInfo, insecureHandler http.Handler, stopCh <-chan struct{}) error { +func serveInsecurely(insecureServingInfo *InsecureServingInfo, insecureHandler http.Handler, shutDownTimeout time.Duration, stopCh <-chan struct{}) error { insecureServer := &http.Server{ Addr: insecureServingInfo.BindAddress, Handler: insecureHandler, MaxHeaderBytes: 1 << 20, } glog.Infof("Serving insecurely on %s", insecureServingInfo.BindAddress) - var err error - _, err = server.RunServer(insecureServer, insecureServingInfo.BindNetwork, stopCh) + ln, _, err := options.CreateListener(insecureServingInfo.BindNetwork, insecureServingInfo.BindAddress) + if err != nil { + return err + } + err = server.RunServer(insecureServer, ln, shutDownTimeout, stopCh) return err } diff --git a/pkg/kubectl/.import-restrictions b/pkg/kubectl/.import-restrictions new file mode 100644 index 00000000000..6d8fdf7cb8c --- /dev/null +++ b/pkg/kubectl/.import-restrictions @@ -0,0 +1,151 @@ +{ + "Rules": [{ + "SelectorRegexp": "k8s[.]io/kubernetes/pkg", + "AllowedPrefixes": [ + "k8s.io/kubernetes/pkg/api", + "k8s.io/kubernetes/pkg/api/events", + "k8s.io/kubernetes/pkg/api/legacyscheme", + "k8s.io/kubernetes/pkg/api/pod", + "k8s.io/kubernetes/pkg/api/ref", + "k8s.io/kubernetes/pkg/api/resource", + "k8s.io/kubernetes/pkg/api/service", + "k8s.io/kubernetes/pkg/api/v1/pod", + "k8s.io/kubernetes/pkg/api/v1/service", + "k8s.io/kubernetes/pkg/apis/admissionregistration", + "k8s.io/kubernetes/pkg/apis/admissionregistration/install", + "k8s.io/kubernetes/pkg/apis/admissionregistration/v1alpha1", + "k8s.io/kubernetes/pkg/apis/apps", + "k8s.io/kubernetes/pkg/apis/apps/install", + "k8s.io/kubernetes/pkg/apis/apps/v1", + "k8s.io/kubernetes/pkg/apis/apps/v1beta1", + "k8s.io/kubernetes/pkg/apis/apps/v1beta2", + "k8s.io/kubernetes/pkg/apis/authentication", + "k8s.io/kubernetes/pkg/apis/authentication/install", + "k8s.io/kubernetes/pkg/apis/authentication/v1", + "k8s.io/kubernetes/pkg/apis/authentication/v1beta1", + "k8s.io/kubernetes/pkg/apis/authorization", + "k8s.io/kubernetes/pkg/apis/authorization/install", + "k8s.io/kubernetes/pkg/apis/authorization/v1", + "k8s.io/kubernetes/pkg/apis/authorization/v1beta1", + "k8s.io/kubernetes/pkg/apis/autoscaling", + "k8s.io/kubernetes/pkg/apis/autoscaling/install", + "k8s.io/kubernetes/pkg/apis/autoscaling/v1", + "k8s.io/kubernetes/pkg/apis/autoscaling/v2beta1", + "k8s.io/kubernetes/pkg/apis/batch", + "k8s.io/kubernetes/pkg/apis/batch/install", + "k8s.io/kubernetes/pkg/apis/batch/v1", + "k8s.io/kubernetes/pkg/apis/batch/v1beta1", + "k8s.io/kubernetes/pkg/apis/batch/v2alpha1", + "k8s.io/kubernetes/pkg/apis/certificates", + "k8s.io/kubernetes/pkg/apis/certificates/install", + "k8s.io/kubernetes/pkg/apis/certificates/v1beta1", + "k8s.io/kubernetes/pkg/apis/componentconfig", + "k8s.io/kubernetes/pkg/apis/componentconfig/install", + "k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1", + "k8s.io/kubernetes/pkg/apis/core", + "k8s.io/kubernetes/pkg/apis/core/helper", + "k8s.io/kubernetes/pkg/apis/core/helper/qos", + "k8s.io/kubernetes/pkg/apis/core/install", + "k8s.io/kubernetes/pkg/apis/core/v1", + "k8s.io/kubernetes/pkg/apis/core/v1/helper", + "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos", + "k8s.io/kubernetes/pkg/apis/core/validation", + "k8s.io/kubernetes/pkg/apis/extensions", + "k8s.io/kubernetes/pkg/apis/extensions/install", + "k8s.io/kubernetes/pkg/apis/extensions/v1beta1", + "k8s.io/kubernetes/pkg/apis/networking", + "k8s.io/kubernetes/pkg/apis/networking/install", + "k8s.io/kubernetes/pkg/apis/networking/v1", + "k8s.io/kubernetes/pkg/apis/policy", + "k8s.io/kubernetes/pkg/apis/policy/install", + "k8s.io/kubernetes/pkg/apis/policy/v1beta1", + "k8s.io/kubernetes/pkg/apis/rbac", + "k8s.io/kubernetes/pkg/apis/rbac/install", + "k8s.io/kubernetes/pkg/apis/rbac/v1", + "k8s.io/kubernetes/pkg/apis/rbac/v1alpha1", + "k8s.io/kubernetes/pkg/apis/rbac/v1beta1", + "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/settings", + "k8s.io/kubernetes/pkg/apis/settings/install", + "k8s.io/kubernetes/pkg/apis/settings/v1alpha1", + "k8s.io/kubernetes/pkg/apis/storage", + "k8s.io/kubernetes/pkg/apis/storage/install", + "k8s.io/kubernetes/pkg/apis/storage/util", + "k8s.io/kubernetes/pkg/apis/storage/v1", + "k8s.io/kubernetes/pkg/apis/storage/v1beta1", + "k8s.io/kubernetes/pkg/capabilities", + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset", + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme", + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion", + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion", + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion", + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion", + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion", + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion", + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion", + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion", + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion", + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion", + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion", + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion", + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion", + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion", + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion", + "k8s.io/kubernetes/pkg/client/metrics/prometheus", + "k8s.io/kubernetes/pkg/client/unversioned", + "k8s.io/kubernetes/pkg/cloudprovider", + "k8s.io/kubernetes/pkg/cloudprovider/providers/aws", + "k8s.io/kubernetes/pkg/controller", + "k8s.io/kubernetes/pkg/controller/daemon", + "k8s.io/kubernetes/pkg/controller/daemon/util", + "k8s.io/kubernetes/pkg/controller/deployment/util", + "k8s.io/kubernetes/pkg/controller/history", + "k8s.io/kubernetes/pkg/controller/statefulset", + "k8s.io/kubernetes/pkg/credentialprovider", + "k8s.io/kubernetes/pkg/credentialprovider/aws", + "k8s.io/kubernetes/pkg/features", + "k8s.io/kubernetes/pkg/fieldpath", + "k8s.io/kubernetes/pkg/generated", + "k8s.io/kubernetes/pkg/kubectl", + "k8s.io/kubernetes/pkg/kubelet/apis", + "k8s.io/kubernetes/pkg/kubelet/qos", + "k8s.io/kubernetes/pkg/kubelet/types", + "k8s.io/kubernetes/pkg/master/ports", + "k8s.io/kubernetes/pkg/printers", + "k8s.io/kubernetes/pkg/printers/internalversion", + "k8s.io/kubernetes/pkg/registry/rbac/reconciliation", + "k8s.io/kubernetes/pkg/registry/rbac/validation", + "k8s.io/kubernetes/pkg/scheduler/algorithm", + "k8s.io/kubernetes/pkg/scheduler/algorithm/predicates", + "k8s.io/kubernetes/pkg/scheduler/algorithm/priorities/util", + "k8s.io/kubernetes/pkg/scheduler/api", + "k8s.io/kubernetes/pkg/scheduler/schedulercache", + "k8s.io/kubernetes/pkg/scheduler/util", + "k8s.io/kubernetes/pkg/scheduler/volumebinder", + "k8s.io/kubernetes/pkg/security/apparmor", + "k8s.io/kubernetes/pkg/serviceaccount", + "k8s.io/kubernetes/pkg/util/file", + "k8s.io/kubernetes/pkg/util/goroutinemap", + "k8s.io/kubernetes/pkg/util/hash", + "k8s.io/kubernetes/pkg/util/interrupt", + "k8s.io/kubernetes/pkg/util/io", + "k8s.io/kubernetes/pkg/util/labels", + "k8s.io/kubernetes/pkg/util/metrics", + "k8s.io/kubernetes/pkg/util/mount", + "k8s.io/kubernetes/pkg/util/net/sets", + "k8s.io/kubernetes/pkg/util/node", + "k8s.io/kubernetes/pkg/util/nsenter", + "k8s.io/kubernetes/pkg/util/parsers", + "k8s.io/kubernetes/pkg/util/pointer", + "k8s.io/kubernetes/pkg/util/slice", + "k8s.io/kubernetes/pkg/util/taints", + "k8s.io/kubernetes/pkg/version", + "k8s.io/kubernetes/pkg/version/prometheus", + "k8s.io/kubernetes/pkg/volume", + "k8s.io/kubernetes/pkg/volume/util" + ], + "ForbiddenPrefixes": [] + }] +} diff --git a/pkg/kubectl/BUILD b/pkg/kubectl/BUILD index 775701b946a..a67266440bc 100644 --- a/pkg/kubectl/BUILD +++ b/pkg/kubectl/BUILD @@ -10,17 +10,20 @@ go_test( name = "go_default_test", srcs = [ "autoscale_test.go", - "cluster_test.go", "clusterrolebinding_test.go", "configmap_test.go", "delete_test.go", "deployment_test.go", "env_file_test.go", "generate_test.go", + "history_test.go", "namespace_test.go", + "pdb_test.go", + "priorityclass_test.go", "quota_test.go", "resource_filter_test.go", "rolebinding_test.go", + "rollback_test.go", "rolling_updater_test.go", "rollout_status_test.go", "run_test.go", @@ -33,19 +36,19 @@ go_test( "serviceaccount_test.go", "sorting_printer_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubectl", - library = ":go_default_library", deps = [ - "//federation/apis/federation/v1beta1:go_default_library", - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/testapi:go_default_library", "//pkg/api/testing:go_default_library", "//pkg/apis/apps:go_default_library", "//pkg/apis/batch:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", - "//pkg/apis/rbac:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", + "//pkg/client/clientset_generated/internalclientset/typed/apps/internalversion:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/batch/internalversion:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion:go_default_library", @@ -53,26 +56,37 @@ go_test( "//pkg/printers:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", "//vendor/k8s.io/api/apps/v1beta1:go_default_library", + "//vendor/k8s.io/api/apps/v1beta2:go_default_library", "//vendor/k8s.io/api/autoscaling/v1:go_default_library", "//vendor/k8s.io/api/batch/v1:go_default_library", "//vendor/k8s.io/api/batch/v1beta1:go_default_library", "//vendor/k8s.io/api/batch/v2alpha1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", + "//vendor/k8s.io/api/policy/v1beta1:go_default_library", + "//vendor/k8s.io/api/rbac/v1:go_default_library", "//vendor/k8s.io/api/rbac/v1beta1:go_default_library", + "//vendor/k8s.io/api/scheduling/v1alpha1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", + "//vendor/k8s.io/client-go/discovery:go_default_library", + "//vendor/k8s.io/client-go/discovery/fake:go_default_library", + "//vendor/k8s.io/client-go/dynamic:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/rest/fake:go_default_library", + "//vendor/k8s.io/client-go/scale:go_default_library", "//vendor/k8s.io/client-go/testing:go_default_library", "//vendor/k8s.io/client-go/util/testing:go_default_library", ], @@ -84,7 +98,6 @@ go_library( "apply.go", "autoscale.go", "bash_comp_utils.go", - "cluster.go", "clusterrolebinding.go", "configmap.go", "delete.go", @@ -97,6 +110,7 @@ go_library( "kubectl.go", "namespace.go", "pdb.go", + "priorityclass.go", "quota.go", "resource_filter.go", "rolebinding.go", @@ -112,19 +126,16 @@ go_library( "service_basic.go", "serviceaccount.go", "sorting_printer.go", - "versioned_client.go", ], importpath = "k8s.io/kubernetes/pkg/kubectl", deps = [ - "//federation/apis/federation/v1beta1:go_default_library", - "//pkg/api:go_default_library", - "//pkg/api/v1:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/v1/pod:go_default_library", "//pkg/apis/apps:go_default_library", "//pkg/apis/batch:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/v1:go_default_library", "//pkg/apis/extensions:go_default_library", - "//pkg/apis/policy:go_default_library", - "//pkg/apis/rbac:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/apps/internalversion:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/batch/internalversion:go_default_library", @@ -135,6 +146,7 @@ go_library( "//pkg/controller/deployment/util:go_default_library", "//pkg/controller/statefulset:go_default_library", "//pkg/credentialprovider:go_default_library", + "//pkg/kubectl/apps:go_default_library", "//pkg/kubectl/resource:go_default_library", "//pkg/kubectl/util:go_default_library", "//pkg/kubectl/util/hash:go_default_library", @@ -151,7 +163,10 @@ go_library( "//vendor/k8s.io/api/batch/v2alpha1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", + "//vendor/k8s.io/api/policy/v1beta1:go_default_library", + "//vendor/k8s.io/api/rbac/v1:go_default_library", "//vendor/k8s.io/api/rbac/v1beta1:go_default_library", + "//vendor/k8s.io/api/scheduling/v1alpha1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", @@ -171,9 +186,11 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/kubernetes/typed/apps/v1beta1:go_default_library", "//vendor/k8s.io/client-go/kubernetes/typed/extensions/v1beta1:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", + "//vendor/k8s.io/client-go/scale:go_default_library", "//vendor/k8s.io/client-go/util/integer:go_default_library", "//vendor/k8s.io/client-go/util/jsonpath:go_default_library", "//vendor/k8s.io/client-go/util/retry:go_default_library", @@ -194,13 +211,14 @@ filegroup( ":package-srcs", "//pkg/kubectl/apply:all-srcs", "//pkg/kubectl/apps:all-srcs", + "//pkg/kubectl/categories:all-srcs", "//pkg/kubectl/cmd:all-srcs", "//pkg/kubectl/explain:all-srcs", "//pkg/kubectl/metricsutil:all-srcs", "//pkg/kubectl/plugins:all-srcs", "//pkg/kubectl/proxy:all-srcs", "//pkg/kubectl/resource:all-srcs", - "//pkg/kubectl/testing:all-srcs", + "//pkg/kubectl/scheme:all-srcs", "//pkg/kubectl/util:all-srcs", "//pkg/kubectl/validation:all-srcs", ], diff --git a/pkg/kubectl/apply.go b/pkg/kubectl/apply.go index ba748889529..1e8f79caecd 100644 --- a/pkg/kubectl/apply.go +++ b/pkg/kubectl/apply.go @@ -17,9 +17,9 @@ limitations under the License. package kubectl import ( + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/kubectl/resource" ) @@ -35,7 +35,7 @@ func GetOriginalConfiguration(mapping *meta.RESTMapping, obj runtime.Object) ([] return nil, nil } - original, ok := annots[api.LastAppliedConfigAnnotation] + original, ok := annots[v1.LastAppliedConfigAnnotation] if !ok { return nil, nil } @@ -60,7 +60,7 @@ func SetOriginalConfiguration(info *resource.Info, original []byte) error { annots = map[string]string{} } - annots[api.LastAppliedConfigAnnotation] = string(original) + annots[v1.LastAppliedConfigAnnotation] = string(original) return info.Mapping.MetadataAccessor.SetAnnotations(info.Object, annots) } @@ -72,59 +72,33 @@ func GetModifiedConfiguration(info *resource.Info, annotate bool, codec runtime. // First serialize the object without the annotation to prevent recursion, // then add that serialization to it as the annotation and serialize it again. var modified []byte - if info.VersionedObject != nil { - // If an object was read from input, use that version. - accessor, err := meta.Accessor(info.VersionedObject) - if err != nil { - return nil, err - } - // Get the current annotations from the object. - annots := accessor.GetAnnotations() - if annots == nil { - annots = map[string]string{} - } + // Otherwise, use the server side version of the object. + accessor := info.Mapping.MetadataAccessor + // Get the current annotations from the object. + annots, err := accessor.Annotations(info.Object) + if err != nil { + return nil, err + } - original := annots[api.LastAppliedConfigAnnotation] - delete(annots, api.LastAppliedConfigAnnotation) - accessor.SetAnnotations(annots) - // TODO: this needs to be abstracted - there should be no assumption that versioned object - // can be marshalled to JSON. - modified, err = runtime.Encode(codec, info.VersionedObject) - if err != nil { - return nil, err - } + if annots == nil { + annots = map[string]string{} + } - if annotate { - annots[api.LastAppliedConfigAnnotation] = string(modified) - accessor.SetAnnotations(annots) - // TODO: this needs to be abstracted - there should be no assumption that versioned object - // can be marshalled to JSON. - modified, err = runtime.Encode(codec, info.VersionedObject) - if err != nil { - return nil, err - } - } + original := annots[v1.LastAppliedConfigAnnotation] + delete(annots, v1.LastAppliedConfigAnnotation) + if err := accessor.SetAnnotations(info.Object, annots); err != nil { + return nil, err + } - // Restore the object to its original condition. - annots[api.LastAppliedConfigAnnotation] = original - accessor.SetAnnotations(annots) - } else { - // Otherwise, use the server side version of the object. - accessor := info.Mapping.MetadataAccessor - // Get the current annotations from the object. - annots, err := accessor.Annotations(info.Object) - if err != nil { - return nil, err - } + modified, err = runtime.Encode(codec, info.Object) + if err != nil { + return nil, err + } - if annots == nil { - annots = map[string]string{} - } - - original := annots[api.LastAppliedConfigAnnotation] - delete(annots, api.LastAppliedConfigAnnotation) - if err := accessor.SetAnnotations(info.Object, annots); err != nil { + if annotate { + annots[v1.LastAppliedConfigAnnotation] = string(modified) + if err := info.Mapping.MetadataAccessor.SetAnnotations(info.Object, annots); err != nil { return nil, err } @@ -132,24 +106,12 @@ func GetModifiedConfiguration(info *resource.Info, annotate bool, codec runtime. if err != nil { return nil, err } + } - if annotate { - annots[api.LastAppliedConfigAnnotation] = string(modified) - if err := info.Mapping.MetadataAccessor.SetAnnotations(info.Object, annots); err != nil { - return nil, err - } - - modified, err = runtime.Encode(codec, info.Object) - if err != nil { - return nil, err - } - } - - // Restore the object to its original condition. - annots[api.LastAppliedConfigAnnotation] = original - if err := info.Mapping.MetadataAccessor.SetAnnotations(info.Object, annots); err != nil { - return nil, err - } + // Restore the object to its original condition. + annots[v1.LastAppliedConfigAnnotation] = original + if err := info.Mapping.MetadataAccessor.SetAnnotations(info.Object, annots); err != nil { + return nil, err } return modified, nil diff --git a/pkg/kubectl/apply/element.go b/pkg/kubectl/apply/element.go index 44b90d85aad..0b55b109b5c 100644 --- a/pkg/kubectl/apply/element.go +++ b/pkg/kubectl/apply/element.go @@ -78,16 +78,14 @@ type FieldMeta interface { // FieldMetaImpl implements FieldMeta type FieldMetaImpl struct { - // The type of merge strategy to use for this field + // MergeType is the type of merge strategy to use for this field // maybe "merge", "replace" or "retainkeys" - // TODO: There maybe multiple strategies, so this may need to be a slice, map, or struct - // Address this in a follow up in the PR to introduce retainkeys strategy MergeType string - // The merge key to use when the MergeType is "merge" and underlying type is a list + // MergeKeys are the merge keys to use when the MergeType is "merge" and underlying type is a list MergeKeys MergeKeys - // The openapi type of the field - "list", "primitive", "map" + // Type is the openapi type of the field - "list", "primitive", "map" Type string // Name contains of the field diff --git a/pkg/kubectl/apply/parse/BUILD b/pkg/kubectl/apply/parse/BUILD index 89842456a8b..aedfc3f8a7f 100644 --- a/pkg/kubectl/apply/parse/BUILD +++ b/pkg/kubectl/apply/parse/BUILD @@ -19,6 +19,7 @@ go_library( "//pkg/kubectl/apply:go_default_library", "//pkg/kubectl/cmd/util/openapi:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", ], ) diff --git a/pkg/kubectl/apply/parse/factory.go b/pkg/kubectl/apply/parse/factory.go index 7bd20c43530..c9cc8fb2e2c 100644 --- a/pkg/kubectl/apply/parse/factory.go +++ b/pkg/kubectl/apply/parse/factory.go @@ -20,6 +20,7 @@ import ( "fmt" "reflect" + "k8s.io/kube-openapi/pkg/util/proto" "k8s.io/kubernetes/pkg/kubectl/apply" "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" ) @@ -60,7 +61,7 @@ func (b *Factory) CreateElement(recorded, local, remote map[string]interface{}) } // getItem returns the appropriate Item based on the underlying type of the arguments -func (v *ElementBuildingVisitor) getItem(s openapi.Schema, name string, data apply.RawElementData) (Item, error) { +func (v *ElementBuildingVisitor) getItem(s proto.Schema, name string, data apply.RawElementData) (Item, error) { kind, err := getType(data.GetRecorded(), data.GetLocal(), data.GetRemote()) if err != nil { return nil, err @@ -77,25 +78,43 @@ func (v *ElementBuildingVisitor) getItem(s openapi.Schema, name string, data app reflect.String: p, err := getPrimitive(s) if err != nil { - return nil, fmt.Errorf("expected openapi Primitive, was %T for %v", s, kind) + return nil, fmt.Errorf("expected openapi Primitive, was %T for %v (%v)", s, kind, err) } return &primitiveItem{name, p, data}, nil case reflect.Array, reflect.Slice: a, err := getArray(s) if err != nil { - return nil, fmt.Errorf("expected openapi Array, was %T for %v", s, kind) + return nil, fmt.Errorf("expected openapi Array, was %T for %v (%v)", s, kind, err) } - return &listItem{name, a, apply.ListElementData{data}}, nil + return &listItem{ + Name: name, + Array: a, + ListElementData: apply.ListElementData{ + RawElementData: data, + }, + }, nil case reflect.Map: if k, err := getKind(s); err == nil { - return &typeItem{name, k, apply.MapElementData{data}}, nil + return &typeItem{ + Name: name, + Type: k, + MapElementData: apply.MapElementData{ + RawElementData: data, + }, + }, nil } // If it looks like a map, and no openapi type is found, default to mapItem m, err := getMap(s) if err != nil { - return nil, fmt.Errorf("expected openapi Kind or Map, was %T for %v", s, kind) + return nil, fmt.Errorf("expected openapi Kind or Map, was %T for %v (%v)", s, kind, err) } - return &mapItem{name, m, apply.MapElementData{data}}, nil + return &mapItem{ + Name: name, + Map: m, + MapElementData: apply.MapElementData{ + RawElementData: data, + }, + }, nil } return nil, fmt.Errorf("unsupported type type %v", kind) } diff --git a/pkg/kubectl/apply/parse/item.go b/pkg/kubectl/apply/parse/item.go index e2642a760f4..15eae075f7d 100644 --- a/pkg/kubectl/apply/parse/item.go +++ b/pkg/kubectl/apply/parse/item.go @@ -17,8 +17,8 @@ limitations under the License. package parse import ( + "k8s.io/kube-openapi/pkg/util/proto" "k8s.io/kubernetes/pkg/kubectl/apply" - "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" ) // Item wraps values from 3 sources (recorded, local, remote). @@ -31,7 +31,7 @@ type Item interface { // primitiveItem contains a recorded, local, and remote value type primitiveItem struct { Name string - Primitive *openapi.Primitive + Primitive *proto.Primitive apply.RawElementData } @@ -40,7 +40,7 @@ func (i *primitiveItem) CreateElement(v ItemVisitor) (apply.Element, error) { return v.CreatePrimitiveElement(i) } -func (i *primitiveItem) GetMeta() openapi.Schema { +func (i *primitiveItem) GetMeta() proto.Schema { // https://golang.org/doc/faq#nil_error if i.Primitive != nil { return i.Primitive @@ -51,7 +51,7 @@ func (i *primitiveItem) GetMeta() openapi.Schema { // listItem contains a recorded, local, and remote list type listItem struct { Name string - Array *openapi.Array + Array *proto.Array apply.ListElementData } @@ -60,7 +60,7 @@ func (i *listItem) CreateElement(v ItemVisitor) (apply.Element, error) { return v.CreateListElement(i) } -func (i *listItem) GetMeta() openapi.Schema { +func (i *listItem) GetMeta() proto.Schema { // https://golang.org/doc/faq#nil_error if i.Array != nil { return i.Array @@ -71,7 +71,7 @@ func (i *listItem) GetMeta() openapi.Schema { // mapItem contains a recorded, local, and remote map type mapItem struct { Name string - Map *openapi.Map + Map *proto.Map apply.MapElementData } @@ -80,7 +80,7 @@ func (i *mapItem) CreateElement(v ItemVisitor) (apply.Element, error) { return v.CreateMapElement(i) } -func (i *mapItem) GetMeta() openapi.Schema { +func (i *mapItem) GetMeta() proto.Schema { // https://golang.org/doc/faq#nil_error if i.Map != nil { return i.Map @@ -91,12 +91,12 @@ func (i *mapItem) GetMeta() openapi.Schema { // mapItem contains a recorded, local, and remote map type typeItem struct { Name string - Type *openapi.Kind + Type *proto.Kind apply.MapElementData } -func (i *typeItem) GetMeta() openapi.Schema { +func (i *typeItem) GetMeta() proto.Schema { // https://golang.org/doc/faq#nil_error if i.Type != nil { return i.Type diff --git a/pkg/kubectl/apply/parse/list_element.go b/pkg/kubectl/apply/parse/list_element.go index ed9a59dd2fa..487a5f19ff5 100644 --- a/pkg/kubectl/apply/parse/list_element.go +++ b/pkg/kubectl/apply/parse/list_element.go @@ -18,8 +18,9 @@ package parse import ( "fmt" + + "k8s.io/kube-openapi/pkg/util/proto" "k8s.io/kubernetes/pkg/kubectl/apply" - "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" ) // Contains the heavy lifting for finding tuples of matching elements in lists based on the merge key @@ -44,7 +45,7 @@ func (v ElementBuildingVisitor) mergeListElement(meta apply.FieldMetaImpl, item func (v ElementBuildingVisitor) doPrimitiveList(meta apply.FieldMetaImpl, item *listItem) (*apply.ListElement, error) { result := &apply.ListElement{ FieldMetaImpl: apply.FieldMetaImpl{ - MergeType: "merge", + MergeType: apply.MergeStrategy, Name: item.Name, }, ListElementData: item.ListElementData, @@ -70,7 +71,7 @@ func (v ElementBuildingVisitor) doPrimitiveList(meta apply.FieldMetaImpl, item * } for i, l := range orderedKeys.Items { - var s openapi.Schema + var s proto.Schema if item.Array != nil && item.Array.SubType != nil { s = item.Array.SubType } @@ -100,7 +101,7 @@ func (v ElementBuildingVisitor) doMapList(meta apply.FieldMetaImpl, item *listIt key := meta.GetFieldMergeKeys() result := &apply.ListElement{ FieldMetaImpl: apply.FieldMetaImpl{ - MergeType: "merge", + MergeType: apply.MergeStrategy, MergeKeys: key, Name: item.Name, }, @@ -127,7 +128,7 @@ func (v ElementBuildingVisitor) doMapList(meta apply.FieldMetaImpl, item *listIt } for i, l := range orderedKeys.Items { - var s openapi.Schema + var s proto.Schema if item.Array != nil && item.Array.SubType != nil { s = item.Array.SubType } @@ -153,7 +154,11 @@ func (v ElementBuildingVisitor) doMapList(meta apply.FieldMetaImpl, item *listIt // Uses the "replace" strategy and identify "same" elements across lists by their index func (v ElementBuildingVisitor) replaceListElement(meta apply.FieldMetaImpl, item *listItem) (*apply.ListElement, error) { meta.Name = item.Name - result := &apply.ListElement{meta, item.ListElementData, []apply.Element{}} + result := &apply.ListElement{ + FieldMetaImpl: meta, + ListElementData: item.ListElementData, + Values: []apply.Element{}, + } // Use the max length to iterate over the slices for i := 0; i < max(len(item.GetRecordedList()), len(item.GetLocalList()), len(item.GetRemoteList())); i++ { @@ -171,7 +176,7 @@ func (v ElementBuildingVisitor) replaceListElement(meta apply.FieldMetaImpl, ite } // Create the Item - var s openapi.Schema + var s proto.Schema if item.Array != nil && item.Array.SubType != nil { s = item.Array.SubType } diff --git a/pkg/kubectl/apply/parse/map_element.go b/pkg/kubectl/apply/parse/map_element.go index 6f21c566fb3..0580ab1ba11 100644 --- a/pkg/kubectl/apply/parse/map_element.go +++ b/pkg/kubectl/apply/parse/map_element.go @@ -17,14 +17,14 @@ limitations under the License. package parse import ( + "k8s.io/kube-openapi/pkg/util/proto" "k8s.io/kubernetes/pkg/kubectl/apply" - "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" ) // mapElement builds a new mapElement from a mapItem func (v ElementBuildingVisitor) mapElement(meta apply.FieldMetaImpl, item *mapItem) (*apply.MapElement, error) { // Function to return schema type of the map values - var fn schemaFn = func(string) openapi.Schema { + var fn schemaFn = func(string) proto.Schema { // All map values share the same schema if item.Map != nil && item.Map.SubType != nil { return item.Map.SubType @@ -39,11 +39,15 @@ func (v ElementBuildingVisitor) mapElement(meta apply.FieldMetaImpl, item *mapIt } // Return the result - return &apply.MapElement{meta, item.MapElementData, values}, nil + return &apply.MapElement{ + FieldMetaImpl: meta, + MapElementData: item.MapElementData, + Values: values, + }, nil } // schemaFn returns the schema for a field or map value based on its name or key -type schemaFn func(key string) openapi.Schema +type schemaFn func(key string) proto.Schema // createMapValues combines the recorded, local and remote values from // data into a map of elements. diff --git a/pkg/kubectl/apply/parse/openapi.go b/pkg/kubectl/apply/parse/openapi.go index 68d81567bea..a55594de4b1 100644 --- a/pkg/kubectl/apply/parse/openapi.go +++ b/pkg/kubectl/apply/parse/openapi.go @@ -18,13 +18,15 @@ package parse import ( "fmt" - "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" + "strings" + + "k8s.io/kube-openapi/pkg/util/proto" ) // Contains functions for casting openapi interfaces to their underlying types // getSchemaType returns the string type of the schema - e.g. array, primitive, map, kind, reference -func getSchemaType(schema openapi.Schema) string { +func getSchemaType(schema proto.Schema) string { if schema == nil { return "" } @@ -33,8 +35,8 @@ func getSchemaType(schema openapi.Schema) string { return visitor.Kind } -// getKind converts schema to an *openapi.Kind object -func getKind(schema openapi.Schema) (*openapi.Kind, error) { +// getKind converts schema to an *proto.Kind object +func getKind(schema proto.Schema) (*proto.Kind, error) { if schema == nil { return nil, nil } @@ -43,8 +45,8 @@ func getKind(schema openapi.Schema) (*openapi.Kind, error) { return visitor.Result, visitor.Err } -// getArray converts schema to an *openapi.Array object -func getArray(schema openapi.Schema) (*openapi.Array, error) { +// getArray converts schema to an *proto.Array object +func getArray(schema proto.Schema) (*proto.Array, error) { if schema == nil { return nil, nil } @@ -53,8 +55,8 @@ func getArray(schema openapi.Schema) (*openapi.Array, error) { return visitor.Result, visitor.Err } -// getMap converts schema to an *openapi.Map object -func getMap(schema openapi.Schema) (*openapi.Map, error) { +// getMap converts schema to an *proto.Map object +func getMap(schema proto.Schema) (*proto.Map, error) { if schema == nil { return nil, nil } @@ -63,8 +65,8 @@ func getMap(schema openapi.Schema) (*openapi.Map, error) { return visitor.Result, visitor.Err } -// getPrimitive converts schema to an *openapi.Primitive object -func getPrimitive(schema openapi.Schema) (*openapi.Primitive, error) { +// getPrimitive converts schema to an *proto.Primitive object +func getPrimitive(schema proto.Schema) (*proto.Primitive, error) { if schema == nil { return nil, nil } @@ -79,95 +81,147 @@ type baseSchemaVisitor struct { } // VisitArray implements openapi -func (v *baseSchemaVisitor) VisitArray(array *openapi.Array) { +func (v *baseSchemaVisitor) VisitArray(array *proto.Array) { v.Kind = "array" v.Err = fmt.Errorf("Array type not expected") } // MergeMap implements openapi -func (v *baseSchemaVisitor) VisitMap(*openapi.Map) { +func (v *baseSchemaVisitor) VisitMap(*proto.Map) { v.Kind = "map" v.Err = fmt.Errorf("Map type not expected") } // MergePrimitive implements openapi -func (v *baseSchemaVisitor) VisitPrimitive(*openapi.Primitive) { +func (v *baseSchemaVisitor) VisitPrimitive(*proto.Primitive) { v.Kind = "primitive" v.Err = fmt.Errorf("Primitive type not expected") } // VisitKind implements openapi -func (v *baseSchemaVisitor) VisitKind(*openapi.Kind) { +func (v *baseSchemaVisitor) VisitKind(*proto.Kind) { v.Kind = "kind" v.Err = fmt.Errorf("Kind type not expected") } // VisitReference implements openapi -func (v *baseSchemaVisitor) VisitReference(reference openapi.Reference) { +func (v *baseSchemaVisitor) VisitReference(reference proto.Reference) { v.Kind = "reference" v.Err = fmt.Errorf("Reference type not expected") } type kindSchemaVisitor struct { baseSchemaVisitor - Result *openapi.Kind + Result *proto.Kind } // VisitKind implements openapi -func (v *kindSchemaVisitor) VisitKind(result *openapi.Kind) { +func (v *kindSchemaVisitor) VisitKind(result *proto.Kind) { v.Result = result v.Kind = "kind" } // VisitReference implements openapi -func (v *kindSchemaVisitor) VisitReference(reference openapi.Reference) { +func (v *kindSchemaVisitor) VisitReference(reference proto.Reference) { reference.SubSchema().Accept(v) + if v.Err == nil { + v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions) + } +} + +func copyExtensions(field string, from, to map[string]interface{}) error { + // Copy extensions from field to type for references + for key, val := range from { + if curr, found := to[key]; found { + // Don't allow the same extension to be defined both on the field and on the type + return fmt.Errorf("Cannot override value for extension %s on field %s from %v to %v", + key, field, curr, val) + } + to[key] = val + } + return nil } type mapSchemaVisitor struct { baseSchemaVisitor - Result *openapi.Map + Result *proto.Map } // MergeMap implements openapi -func (v *mapSchemaVisitor) VisitMap(result *openapi.Map) { +func (v *mapSchemaVisitor) VisitMap(result *proto.Map) { v.Result = result v.Kind = "map" } // VisitReference implements openapi -func (v *mapSchemaVisitor) VisitReference(reference openapi.Reference) { +func (v *mapSchemaVisitor) VisitReference(reference proto.Reference) { reference.SubSchema().Accept(v) + if v.Err == nil { + v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions) + } } type arraySchemaVisitor struct { baseSchemaVisitor - Result *openapi.Array + Result *proto.Array } // VisitArray implements openapi -func (v *arraySchemaVisitor) VisitArray(result *openapi.Array) { +func (v *arraySchemaVisitor) VisitArray(result *proto.Array) { v.Result = result v.Kind = "array" + v.Err = copySubElementPatchStrategy(result.Path.String(), result.GetExtensions(), result.SubType.GetExtensions()) +} + +// copyPatchStrategy copies the strategies to subelements to the subtype +// e.g. PodTemplate.Volumes is a []Volume with "x-kubernetes-patch-strategy": "merge,retainKeys" +// the "retainKeys" strategy applies to merging Volumes, and must be copied to the sub element +func copySubElementPatchStrategy(field string, from, to map[string]interface{}) error { + // Check if the parent has a patch strategy extension + if ext, found := from["x-kubernetes-patch-strategy"]; found { + strategy, ok := ext.(string) + if !ok { + return fmt.Errorf("Expected string value for x-kubernetes-patch-strategy on %s, was %T", + field, ext) + } + // Check of the parent patch strategy has a sub patch strategy, and if so copy to the sub type + if strings.Contains(strategy, ",") { + strategies := strings.Split(strategy, ",") + if len(strategies) != 2 { + // Only 1 sub strategy is supported + return fmt.Errorf( + "Expected between 0 and 2 elements for x-kubernetes-patch-merge-strategy by got %v", + strategies) + } + to["x-kubernetes-patch-strategy"] = strategies[1] + } + } + return nil } // MergePrimitive implements openapi -func (v *arraySchemaVisitor) VisitReference(reference openapi.Reference) { +func (v *arraySchemaVisitor) VisitReference(reference proto.Reference) { reference.SubSchema().Accept(v) + if v.Err == nil { + v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions) + } } type primitiveSchemaVisitor struct { baseSchemaVisitor - Result *openapi.Primitive + Result *proto.Primitive } // MergePrimitive implements openapi -func (v *primitiveSchemaVisitor) VisitPrimitive(result *openapi.Primitive) { +func (v *primitiveSchemaVisitor) VisitPrimitive(result *proto.Primitive) { v.Result = result v.Kind = "primitive" } // VisitReference implements openapi -func (v *primitiveSchemaVisitor) VisitReference(reference openapi.Reference) { +func (v *primitiveSchemaVisitor) VisitReference(reference proto.Reference) { reference.SubSchema().Accept(v) + if v.Err == nil { + v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions) + } } diff --git a/pkg/kubectl/apply/parse/primitive_element.go b/pkg/kubectl/apply/parse/primitive_element.go index 5fac5ce04ee..de393e86a50 100644 --- a/pkg/kubectl/apply/parse/primitive_element.go +++ b/pkg/kubectl/apply/parse/primitive_element.go @@ -21,5 +21,8 @@ import "k8s.io/kubernetes/pkg/kubectl/apply" // primitiveElement builds a new primitiveElement from a PrimitiveItem func (v ElementBuildingVisitor) primitiveElement(item *primitiveItem) (*apply.PrimitiveElement, error) { meta := apply.FieldMetaImpl{Name: item.Name} - return &apply.PrimitiveElement{meta, item.RawElementData}, nil + return &apply.PrimitiveElement{ + FieldMetaImpl: meta, + RawElementData: item.RawElementData, + }, nil } diff --git a/pkg/kubectl/apply/parse/type_element.go b/pkg/kubectl/apply/parse/type_element.go index 0eabd9df020..e2fa1d8b682 100644 --- a/pkg/kubectl/apply/parse/type_element.go +++ b/pkg/kubectl/apply/parse/type_element.go @@ -17,14 +17,14 @@ limitations under the License. package parse import ( + "k8s.io/kube-openapi/pkg/util/proto" "k8s.io/kubernetes/pkg/kubectl/apply" - "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" ) // typeElement builds a new mapElement from a typeItem func (v ElementBuildingVisitor) typeElement(meta apply.FieldMetaImpl, item *typeItem) (*apply.TypeElement, error) { // Function to get the schema of a field from its key - var fn schemaFn = func(key string) openapi.Schema { + var fn schemaFn = func(key string) proto.Schema { if item.Type != nil && item.Type.Fields != nil { return item.Type.Fields[key] } @@ -38,5 +38,9 @@ func (v ElementBuildingVisitor) typeElement(meta apply.FieldMetaImpl, item *type } // Return the result - return &apply.TypeElement{meta, item.MapElementData, values}, nil + return &apply.TypeElement{ + FieldMetaImpl: meta, + MapElementData: item.MapElementData, + Values: values, + }, nil } diff --git a/pkg/kubectl/apply/parse/util.go b/pkg/kubectl/apply/parse/util.go index 9d170abf777..ce7adddab84 100644 --- a/pkg/kubectl/apply/parse/util.go +++ b/pkg/kubectl/apply/parse/util.go @@ -22,8 +22,8 @@ import ( "strings" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kube-openapi/pkg/util/proto" "k8s.io/kubernetes/pkg/kubectl/apply" - "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" ) // nilSafeLookup returns the value from the map if the map is non-nil @@ -97,17 +97,24 @@ func getType(args ...interface{}) (reflect.Type, error) { } // getFieldMeta parses the metadata about the field from the openapi spec -func getFieldMeta(s openapi.Schema, name string) (apply.FieldMetaImpl, error) { +func getFieldMeta(s proto.Schema, name string) (apply.FieldMetaImpl, error) { m := apply.FieldMetaImpl{} if s != nil { ext := s.GetExtensions() - if s, found := ext["x-kubernetes-patch-strategy"]; found { - strategy, ok := s.(string) + if e, found := ext["x-kubernetes-patch-strategy"]; found { + strategy, ok := e.(string) if !ok { return apply.FieldMetaImpl{}, fmt.Errorf("Expected string for x-kubernetes-patch-strategy by got %T", s) } - // TODO: Support multi-strategy - m.MergeType = strategy + + // Take the first strategy if there are substrategies. + // Sub strategies are copied to sub types in openapi.go + strategies := strings.Split(strategy, ",") + if len(strategies) > 2 { + return apply.FieldMetaImpl{}, fmt.Errorf("Expected between 0 and 2 elements for x-kubernetes-patch-merge-strategy by got %v", strategies) + } + // For lists, choose the strategy for this type, not the subtype + m.MergeType = strategies[0] } if k, found := ext["x-kubernetes-patch-merge-key"]; found { key, ok := k.(string) diff --git a/pkg/kubectl/apply/parse/visitor.go b/pkg/kubectl/apply/parse/visitor.go index 5c01d26c16e..d67ba6a79ba 100644 --- a/pkg/kubectl/apply/parse/visitor.go +++ b/pkg/kubectl/apply/parse/visitor.go @@ -54,7 +54,7 @@ func (v ElementBuildingVisitor) CreateListElement(item *listItem) (apply.Element if err != nil { return nil, err } - if meta.GetFieldMergeType() == "merge" { + if meta.GetFieldMergeType() == apply.MergeStrategy { return v.mergeListElement(meta, item) } return v.replaceListElement(meta, item) diff --git a/pkg/kubectl/apply/strategy/BUILD b/pkg/kubectl/apply/strategy/BUILD index b489a954517..e1810a7862f 100644 --- a/pkg/kubectl/apply/strategy/BUILD +++ b/pkg/kubectl/apply/strategy/BUILD @@ -25,6 +25,7 @@ go_test( "replace_map_list_test.go", "replace_map_test.go", "replace_primitive_list_test.go", + "retain_keys_test.go", "suite_test.go", "utils_test.go", ], diff --git a/pkg/kubectl/apply/strategy/merge_map_list_test.go b/pkg/kubectl/apply/strategy/merge_map_list_test.go index df0dfffe4c8..f18eb875fc9 100644 --- a/pkg/kubectl/apply/strategy/merge_map_list_test.go +++ b/pkg/kubectl/apply/strategy/merge_map_list_test.go @@ -20,6 +20,8 @@ import ( . "github.com/onsi/ginkgo" "k8s.io/kubernetes/pkg/kubectl/apply/strategy" + "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" + tst "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing" ) var _ = Describe("Merging fields of type list-of-map with openapi", func() { @@ -431,6 +433,11 @@ spec: }) var _ = Describe("Merging fields of type list-of-map with openapi containing a multi-field mergekey", func() { + var resources openapi.Resources + BeforeEach(func() { + resources = tst.NewFakeResources("test_swagger.json") + }) + Context("where one of the items has been deleted", func() { It("should delete the item", func() { recorded := create(` @@ -492,7 +499,7 @@ spec: protocol: TCP hostPort: 2020 `) - runWith(strategy.Create(strategy.Options{}), recorded, local, remote, expected, "test_swagger.json") + runWith(strategy.Create(strategy.Options{}), recorded, local, remote, expected, resources) }) }) @@ -564,7 +571,7 @@ spec: hostPort: 2022 hostIP: "127.0.0.1" `) - runWith(strategy.Create(strategy.Options{}), recorded, local, remote, expected, "test_swagger.json") + runWith(strategy.Create(strategy.Options{}), recorded, local, remote, expected, resources) }) }) @@ -630,7 +637,7 @@ spec: protocol: UDP hostPort: 2022 `) - runWith(strategy.Create(strategy.Options{}), recorded, local, remote, expected, "test_swagger.json") + runWith(strategy.Create(strategy.Options{}), recorded, local, remote, expected, resources) }) }) }) diff --git a/pkg/kubectl/apply/strategy/merge_visitor.go b/pkg/kubectl/apply/strategy/merge_visitor.go index 3a3c414fcb4..94a9d701f18 100644 --- a/pkg/kubectl/apply/strategy/merge_visitor.go +++ b/pkg/kubectl/apply/strategy/merge_visitor.go @@ -29,13 +29,13 @@ func createMergeStrategy(options Options, strategic *delegatingStrategy) mergeSt } } -// mergeStrategy creates a patch to merge a local file value into a remote field value +// mergeStrategy merges the values in an Element into a single Result type mergeStrategy struct { strategic *delegatingStrategy options Options } -// MergeList creates a patch to merge a local list field value into a remote list field value +// MergeList merges the lists in a ListElement into a single Result func (v mergeStrategy) MergeList(e apply.ListElement) (apply.Result, error) { // No merge logic if adding or deleting a field if result, done := v.doAddOrDelete(e); done { @@ -51,7 +51,6 @@ func (v mergeStrategy) MergeList(e apply.ListElement) (apply.Result, error) { return apply.Result{}, err } - fmt.Printf("\nResult %+v\n%+v\n", m.MergedResult, value) switch m.Operation { case apply.SET: // Keep the list item value @@ -71,26 +70,32 @@ func (v mergeStrategy) MergeList(e apply.ListElement) (apply.Result, error) { return apply.Result{Operation: apply.SET, MergedResult: merged}, nil } -// MergeMap creates a patch to merge a local map field into a remote map field +// MergeMap merges the maps in a MapElement into a single Result func (v mergeStrategy) MergeMap(e apply.MapElement) (apply.Result, error) { - return v.doMergeMap(e) -} - -// MergeType creates a patch to merge a local map field into a remote map field -func (v mergeStrategy) MergeType(e apply.TypeElement) (apply.Result, error) { - return v.doMergeMap(e) -} - -// do merges a recorded, local and remote map into a new object -func (v mergeStrategy) doMergeMap(e apply.MapValuesElement) (apply.Result, error) { // No merge logic if adding or deleting a field if result, done := v.doAddOrDelete(e); done { return result, nil } + return v.doMergeMap(e.GetValues()) +} + +// MergeMap merges the type instances in a TypeElement into a single Result +func (v mergeStrategy) MergeType(e apply.TypeElement) (apply.Result, error) { + // No merge logic if adding or deleting a field + if result, done := v.doAddOrDelete(e); done { + return result, nil + } + + return v.doMergeMap(e.GetValues()) +} + +// do merges a recorded, local and remote map into a new object +func (v mergeStrategy) doMergeMap(e map[string]apply.Element) (apply.Result, error) { + // Merge each item in the list merged := map[string]interface{}{} - for key, value := range e.GetValues() { + for key, value := range e { // Recursively merge the map element before adding the value to the map result, err := value.Merge(v.strategic) if err != nil { @@ -135,7 +140,7 @@ func (v mergeStrategy) MergePrimitive(diff apply.PrimitiveElement) (apply.Result return apply.Result{}, fmt.Errorf("Cannot merge primitive element %v", diff.Name) } -// MergeEmpty +// MergeEmpty returns an empty result func (v mergeStrategy) MergeEmpty(diff apply.EmptyElement) (apply.Result, error) { return apply.Result{Operation: apply.SET}, nil } diff --git a/pkg/kubectl/apply/strategy/replace_map_test.go b/pkg/kubectl/apply/strategy/replace_map_test.go index ff6141ea4fb..e6873c17afb 100644 --- a/pkg/kubectl/apply/strategy/replace_map_test.go +++ b/pkg/kubectl/apply/strategy/replace_map_test.go @@ -20,9 +20,16 @@ import ( . "github.com/onsi/ginkgo" "k8s.io/kubernetes/pkg/kubectl/apply/strategy" + "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" + tst "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing" ) var _ = Describe("Replacing fields of type map with openapi for some fields", func() { + var resources openapi.Resources + BeforeEach(func() { + resources = tst.NewFakeResources("test_swagger.json") + }) + Context("where a field is has been updated", func() { It("should update the field", func() { recorded := create(` @@ -67,7 +74,7 @@ spec: `) // Use modified swagger for ReplicaSet spec - runWith(strategy.Create(strategy.Options{}), recorded, local, remote, expected, "test_swagger.json") + runWith(strategy.Create(strategy.Options{}), recorded, local, remote, expected, resources) }) }) }) diff --git a/pkg/kubectl/apply/strategy/retain_keys_test.go b/pkg/kubectl/apply/strategy/retain_keys_test.go new file mode 100644 index 00000000000..481da91a098 --- /dev/null +++ b/pkg/kubectl/apply/strategy/retain_keys_test.go @@ -0,0 +1,195 @@ +/* +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 strategy_test + +import ( + . "github.com/onsi/ginkgo" + + "k8s.io/kubernetes/pkg/kubectl/apply/strategy" +) + +var _ = Describe("Merging fields with the retainkeys strategy", func() { + Context("where some fields are only defined remotely", func() { + It("should drop those fields ", func() { + recorded := create(` +apiVersion: extensions/v1beta1 +kind: Deployment +spec: + strategy: +`) + local := create(` +apiVersion: extensions/v1beta1 +kind: Deployment +spec: + strategy: + type: Recreate +`) + remote := create(` +apiVersion: extensions/v1beta1 +kind: Deployment +spec: + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 1 + maxSurge: 1 +`) + expected := create(` +apiVersion: extensions/v1beta1 +kind: Deployment +spec: + strategy: + type: Recreate +`) + run(strategy.Create(strategy.Options{}), recorded, local, remote, expected) + }) + }) + + Context("where some fields are defined both locally and remotely", func() { + It("should merge those fields", func() { + recorded := create(` +apiVersion: extensions/v1beta1 +kind: Deployment +spec: + strategy: +`) + local := create(` +apiVersion: extensions/v1beta1 +kind: Deployment +spec: + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 2 +`) + remote := create(` +apiVersion: extensions/v1beta1 +kind: Deployment +spec: + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 +`) + expected := create(` +apiVersion: extensions/v1beta1 +kind: Deployment +spec: + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 2 + maxSurge: 1 +`) + run(strategy.Create(strategy.Options{}), recorded, local, remote, expected) + }) + }) + + Context("where the elements are in a list and some fields are only defined remotely", func() { + It("should drop those fields ", func() { + recorded := create(` +apiVersion: apps/v1beta1 +kind: Deployment +spec: + template: + spec: +`) + local := create(` +apiVersion: apps/v1beta1 +kind: Deployment +spec: + template: + spec: + volumes: + - name: cache-volume + emptyDir: +`) + remote := create(` +apiVersion: apps/v1beta1 +kind: Deployment +spec: + template: + spec: + volumes: + - name: cache-volume + hostPath: + path: /tmp/cache-volume +`) + expected := create(` +apiVersion: apps/v1beta1 +kind: Deployment +spec: + template: + spec: + volumes: + - name: cache-volume + emptyDir: +`) + run(strategy.Create(strategy.Options{}), recorded, local, remote, expected) + }) + }) + + Context("where the elements are in a list", func() { + It("the fields defined both locally and remotely should be merged", func() { + recorded := create(` +apiVersion: apps/v1beta1 +kind: Deployment +spec: + template: + spec: +`) + local := create(` +apiVersion: apps/v1beta1 +kind: Deployment +spec: + template: + spec: + volumes: + - name: cache-volume + hostPath: + path: /tmp/cache-volume + emptyDir: +`) + remote := create(` +apiVersion: apps/v1beta1 +kind: Deployment +spec: + template: + spec: + volumes: + - name: cache-volume + hostPath: + path: /tmp/cache-volume + type: Directory +`) + expected := create(` +apiVersion: apps/v1beta1 +kind: Deployment +spec: + template: + spec: + volumes: + - name: cache-volume + hostPath: + path: /tmp/cache-volume + type: Directory + emptyDir: +`) + run(strategy.Create(strategy.Options{}), recorded, local, remote, expected) + }) + }) +}) diff --git a/pkg/kubectl/apply/strategy/retain_keys_visitor.go b/pkg/kubectl/apply/strategy/retain_keys_visitor.go index 2483af0fab3..b901285be1e 100644 --- a/pkg/kubectl/apply/strategy/retain_keys_visitor.go +++ b/pkg/kubectl/apply/strategy/retain_keys_visitor.go @@ -16,4 +16,62 @@ limitations under the License. package strategy -// TODO: Write this +import ( + "fmt" + "k8s.io/kubernetes/pkg/kubectl/apply" +) + +func createRetainKeysStrategy(options Options, strategic *delegatingStrategy) retainKeysStrategy { + return retainKeysStrategy{ + &mergeStrategy{strategic, options}, + strategic, + options, + } +} + +// retainKeysStrategy merges the values in an Element into a single Result, +// dropping any fields omitted from the local copy. (but merging values when +// defined locally and remotely) +type retainKeysStrategy struct { + merge *mergeStrategy + strategic *delegatingStrategy + options Options +} + +// MergeMap merges the type instances in a TypeElement into a single Result +// keeping only the fields defined locally, but merging their values with +// the remote values. +func (v retainKeysStrategy) MergeType(e apply.TypeElement) (apply.Result, error) { + // No merge logic if adding or deleting a field + if result, done := v.merge.doAddOrDelete(&e); done { + return result, nil + } + + elem := map[string]apply.Element{} + for key := range e.GetLocalMap() { + elem[key] = e.GetValues()[key] + } + return v.merge.doMergeMap(elem) +} + +// MergeMap returns an error. Only TypeElements can have retainKeys. +func (v retainKeysStrategy) MergeMap(e apply.MapElement) (apply.Result, error) { + return apply.Result{}, fmt.Errorf("Cannot use retainkeys with map element %v", e.Name) +} + +// MergeList returns an error. Only TypeElements can have retainKeys. +func (v retainKeysStrategy) MergeList(e apply.ListElement) (apply.Result, error) { + return apply.Result{}, fmt.Errorf("Cannot use retainkeys with list element %v", e.Name) +} + +// MergePrimitive returns an error. Only TypeElements can have retainKeys. +func (v retainKeysStrategy) MergePrimitive(diff apply.PrimitiveElement) (apply.Result, error) { + return apply.Result{}, fmt.Errorf("Cannot use retainkeys with primitive element %v", diff.Name) +} + +// MergeEmpty returns an empty result +func (v retainKeysStrategy) MergeEmpty(diff apply.EmptyElement) (apply.Result, error) { + return v.merge.MergeEmpty(diff) +} + +var _ apply.Strategy = &retainKeysStrategy{} diff --git a/pkg/kubectl/apply/strategy/strategic_visitor.go b/pkg/kubectl/apply/strategy/strategic_visitor.go index 3992271729c..eae1f9230ad 100644 --- a/pkg/kubectl/apply/strategy/strategic_visitor.go +++ b/pkg/kubectl/apply/strategy/strategic_visitor.go @@ -16,14 +16,17 @@ limitations under the License. package strategy -import "k8s.io/kubernetes/pkg/kubectl/apply" +import ( + "k8s.io/kubernetes/pkg/kubectl/apply" +) // delegatingStrategy delegates merging fields to other visitor implementations // based on the merge strategy preferred by the field. type delegatingStrategy struct { - options Options - merge mergeStrategy - replace replaceStrategy + options Options + merge mergeStrategy + replace replaceStrategy + retainKeys retainKeysStrategy } // createDelegatingStrategy returns a new delegatingStrategy @@ -33,18 +36,20 @@ func createDelegatingStrategy(options Options) *delegatingStrategy { } v.replace = createReplaceStrategy(options, v) v.merge = createMergeStrategy(options, v) + v.retainKeys = createRetainKeysStrategy(options, v) return v } // MergeList delegates visiting a list based on the field patch strategy. // Defaults to "replace" func (v delegatingStrategy) MergeList(diff apply.ListElement) (apply.Result, error) { - // TODO: Support retainkeys switch diff.GetFieldMergeType() { - case "merge": + case apply.MergeStrategy: return v.merge.MergeList(diff) - case "replace": + case apply.ReplaceStrategy: return v.replace.MergeList(diff) + case apply.RetainKeysStrategy: + return v.retainKeys.MergeList(diff) default: return v.replace.MergeList(diff) } @@ -53,12 +58,13 @@ func (v delegatingStrategy) MergeList(diff apply.ListElement) (apply.Result, err // MergeMap delegates visiting a map based on the field patch strategy. // Defaults to "merge" func (v delegatingStrategy) MergeMap(diff apply.MapElement) (apply.Result, error) { - // TODO: Support retainkeys switch diff.GetFieldMergeType() { - case "merge": + case apply.MergeStrategy: return v.merge.MergeMap(diff) - case "replace": + case apply.ReplaceStrategy: return v.replace.MergeMap(diff) + case apply.RetainKeysStrategy: + return v.retainKeys.MergeMap(diff) default: return v.merge.MergeMap(diff) } @@ -67,12 +73,13 @@ func (v delegatingStrategy) MergeMap(diff apply.MapElement) (apply.Result, error // MergeType delegates visiting a map based on the field patch strategy. // Defaults to "merge" func (v delegatingStrategy) MergeType(diff apply.TypeElement) (apply.Result, error) { - // TODO: Support retainkeys switch diff.GetFieldMergeType() { - case "merge": + case apply.MergeStrategy: return v.merge.MergeType(diff) - case "replace": + case apply.ReplaceStrategy: return v.replace.MergeType(diff) + case apply.RetainKeysStrategy: + return v.retainKeys.MergeType(diff) default: return v.merge.MergeType(diff) } diff --git a/pkg/kubectl/apply/strategy/utils_test.go b/pkg/kubectl/apply/strategy/utils_test.go index a5c226e5399..1ca687bac2b 100644 --- a/pkg/kubectl/apply/strategy/utils_test.go +++ b/pkg/kubectl/apply/strategy/utils_test.go @@ -32,19 +32,15 @@ import ( tst "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing" ) +var fakeResources = tst.NewFakeResources(filepath.Join("..", "..", "..", "..", "api", "openapi-spec", "swagger.json")) + // run parses the openapi and runs the tests func run(instance apply.Strategy, recorded, local, remote, expected map[string]interface{}) { - runWith(instance, recorded, local, remote, expected, - filepath.Join("..", "..", "..", "..", "api", "openapi-spec", "swagger.json")) + runWith(instance, recorded, local, remote, expected, fakeResources) } -func runWith(instance apply.Strategy, recorded, local, remote, expected map[string]interface{}, swaggerPath string) { - fakeSchema := tst.Fake{Path: swaggerPath} - s, err := fakeSchema.OpenAPISchema() - Expect(err).To(BeNil()) - resources, err := openapi.NewOpenAPIData(s) - Expect(err).To(BeNil()) - parseFactory := parse.Factory{resources} +func runWith(instance apply.Strategy, recorded, local, remote, expected map[string]interface{}, resources openapi.Resources) { + parseFactory := parse.Factory{Resources: resources} parsed, err := parseFactory.CreateElement(recorded, local, remote) Expect(err).Should(Not(HaveOccurred())) diff --git a/pkg/kubectl/apply/visitor.go b/pkg/kubectl/apply/visitor.go index 0cf895718b4..d63d973cd77 100644 --- a/pkg/kubectl/apply/visitor.go +++ b/pkg/kubectl/apply/visitor.go @@ -56,8 +56,13 @@ type Result struct { MergedResult interface{} } -// MapValuesElement exposes how to get the field / key - value pairs out of a Map or Type Element -type MapValuesElement interface { - Element - GetValues() map[string]Element -} +const ( + // MergeStrategy is the strategy to merge the local and remote values + MergeStrategy = "merge" + + // RetainKeysStrategy is the strategy to merge the local and remote values, but drop any fields not defined locally + RetainKeysStrategy = "retainKeys" + + // ReplaceStrategy is the strategy to replace the remote value with the local value + ReplaceStrategy = "replace" +) diff --git a/pkg/kubectl/apps/kind_visitor.go b/pkg/kubectl/apps/kind_visitor.go index 69d1d76609d..9c3ad07fe7e 100644 --- a/pkg/kubectl/apps/kind_visitor.go +++ b/pkg/kubectl/apps/kind_visitor.go @@ -32,6 +32,7 @@ type KindVisitor interface { VisitReplicaSet(kind GroupKindElement) VisitReplicationController(kind GroupKindElement) VisitStatefulSet(kind GroupKindElement) + VisitCronJob(kind GroupKindElement) } // GroupKindElement defines a Kubernetes API group elem @@ -48,12 +49,14 @@ func (elem GroupKindElement) Accept(visitor KindVisitor) error { visitor.VisitJob(elem) case elem.GroupMatch("", "core") && elem.Kind == "Pod": visitor.VisitPod(elem) - case elem.GroupMatch("extensions") && elem.Kind == "ReplicaSet": + case elem.GroupMatch("apps", "extensions") && elem.Kind == "ReplicaSet": visitor.VisitReplicaSet(elem) case elem.GroupMatch("", "core") && elem.Kind == "ReplicationController": visitor.VisitReplicationController(elem) case elem.GroupMatch("apps") && elem.Kind == "StatefulSet": visitor.VisitStatefulSet(elem) + case elem.GroupMatch("batch") && elem.Kind == "CronJob": + visitor.VisitCronJob(elem) default: return fmt.Errorf("no visitor method exists for %v", elem) } @@ -83,3 +86,4 @@ func (*NoOpKindVisitor) VisitPod(kind GroupKindElement) {} func (*NoOpKindVisitor) VisitReplicaSet(kind GroupKindElement) {} func (*NoOpKindVisitor) VisitReplicationController(kind GroupKindElement) {} func (*NoOpKindVisitor) VisitStatefulSet(kind GroupKindElement) {} +func (*NoOpKindVisitor) VisitCronJob(kind GroupKindElement) {} diff --git a/pkg/kubectl/apps/kind_visitor_test.go b/pkg/kubectl/apps/kind_visitor_test.go index c5c2d1a631c..10704b65e9e 100644 --- a/pkg/kubectl/apps/kind_visitor_test.go +++ b/pkg/kubectl/apps/kind_visitor_test.go @@ -145,6 +145,17 @@ var _ = Describe("When KindVisitor accepts a GroupKind", func() { })) }) + It("should Visit CronJob iff the Kind is a CronJob", func() { + kind := apps.GroupKindElement{ + Kind: "CronJob", + Group: "batch", + } + Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred()) + Expect(visitor.visits).To(Equal(map[string]int{ + "CronJob": 1, + })) + }) + It("should give an error if the Kind is unknown", func() { kind := apps.GroupKindElement{ Kind: "Unknown", @@ -171,3 +182,4 @@ func (t *TestKindVisitor) VisitPod(kind apps.GroupKindElement) func (t *TestKindVisitor) VisitReplicaSet(kind apps.GroupKindElement) { t.Visit(kind) } func (t *TestKindVisitor) VisitReplicationController(kind apps.GroupKindElement) { t.Visit(kind) } func (t *TestKindVisitor) VisitStatefulSet(kind apps.GroupKindElement) { t.Visit(kind) } +func (t *TestKindVisitor) VisitCronJob(kind apps.GroupKindElement) { t.Visit(kind) } diff --git a/pkg/kubectl/autoscale.go b/pkg/kubectl/autoscale.go index dcbfadde84a..39c78ca31df 100644 --- a/pkg/kubectl/autoscale.go +++ b/pkg/kubectl/autoscale.go @@ -18,98 +18,68 @@ package kubectl import ( "fmt" - "strconv" autoscalingv1 "k8s.io/api/autoscaling/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) -type HorizontalPodAutoscalerV1 struct{} - -func (HorizontalPodAutoscalerV1) ParamNames() []GeneratorParam { - return []GeneratorParam{ - {"default-name", true}, - {"name", false}, - {"scaleRef-kind", false}, - {"scaleRef-name", false}, - {"scaleRef-apiVersion", false}, - {"min", false}, - {"max", true}, - {"cpu-percent", false}, - } +// HorizontalPodAutoscalerV1Generator supports stable generation of a horizontal pod autoscaler. +type HorizontalPodAutoscalerGeneratorV1 struct { + Name string + ScaleRefKind string + ScaleRefName string + ScaleRefApiVersion string + MinReplicas int32 + MaxReplicas int32 + CPUPercent int32 } -func (HorizontalPodAutoscalerV1) Generate(genericParams map[string]interface{}) (runtime.Object, error) { - return generateHPA(genericParams) -} +// Ensure it supports the generator pattern that uses parameters specified during construction. +var _ StructuredGenerator = &HorizontalPodAutoscalerGeneratorV1{} -func generateHPA(genericParams map[string]interface{}) (runtime.Object, error) { - params := map[string]string{} - for key, value := range genericParams { - strVal, isString := value.(string) - if !isString { - return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key) - } - params[key] = strVal - } - - name, found := params["name"] - if !found || len(name) == 0 { - name, found = params["default-name"] - if !found || len(name) == 0 { - return nil, fmt.Errorf("'name' is a required parameter.") - } - } - minString, found := params["min"] - min := -1 - var err error - if found { - if min, err = strconv.Atoi(minString); err != nil { - return nil, err - } - } - maxString, found := params["max"] - if !found { - return nil, fmt.Errorf("'max' is a required parameter.") - } - max, err := strconv.Atoi(maxString) - if err != nil { +// StructuredGenerate outputs a horizontal pod autoscaler object using the configured fields. +func (s *HorizontalPodAutoscalerGeneratorV1) StructuredGenerate() (runtime.Object, error) { + if err := s.validate(); err != nil { return nil, err } - if min > max { - return nil, fmt.Errorf("'max' must be greater than or equal to 'min'.") - } - - cpuString, found := params["cpu-percent"] - cpu := -1 - if found { - if cpu, err = strconv.Atoi(cpuString); err != nil { - return nil, err - } - } - scaler := autoscalingv1.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{ - Name: name, + Name: s.Name, }, Spec: autoscalingv1.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{ - Kind: params["scaleRef-kind"], - Name: params["scaleRef-name"], - APIVersion: params["scaleRef-apiVersion"], + Kind: s.ScaleRefKind, + Name: s.ScaleRefName, + APIVersion: s.ScaleRefApiVersion, }, - MaxReplicas: int32(max), + MaxReplicas: s.MaxReplicas, }, } - if min > 0 { - v := int32(min) + + if s.MinReplicas > 0 { + v := int32(s.MinReplicas) scaler.Spec.MinReplicas = &v } - if cpu >= 0 { - c := int32(cpu) + if s.CPUPercent >= 0 { + c := int32(s.CPUPercent) scaler.Spec.TargetCPUUtilizationPercentage = &c } + return &scaler, nil } + +// validate check if the caller has set the right fields. +func (s HorizontalPodAutoscalerGeneratorV1) validate() error { + if len(s.Name) == 0 { + return fmt.Errorf("name must be specified") + } + if s.MaxReplicas < 1 { + return fmt.Errorf("'max' is a required parameter and must be at least 1") + } + if s.MinReplicas > s.MaxReplicas { + return fmt.Errorf("'max' must be greater than or equal to 'min'") + } + return nil +} diff --git a/pkg/kubectl/autoscale_test.go b/pkg/kubectl/autoscale_test.go index e87a7075c13..86b6c1eab55 100644 --- a/pkg/kubectl/autoscale_test.go +++ b/pkg/kubectl/autoscale_test.go @@ -26,22 +26,26 @@ import ( func TestHPAGenerate(t *testing.T) { tests := []struct { - name string - params map[string]interface{} - expected *autoscalingv1.HorizontalPodAutoscaler - expectErr bool + name string + HPAName string + scaleRefKind string + scaleRefName string + scaleRefApiVersion string + minReplicas int32 + maxReplicas int32 + CPUPercent int32 + expected *autoscalingv1.HorizontalPodAutoscaler + expectErr bool }{ { - name: "valid case", - params: map[string]interface{}{ - "name": "foo", - "min": "1", - "max": "10", - "cpu-percent": "80", - "scaleRef-kind": "kind", - "scaleRef-name": "name", - "scaleRef-apiVersion": "apiVersion", - }, + name: "valid case", + HPAName: "foo", + minReplicas: 1, + maxReplicas: 10, + CPUPercent: 80, + scaleRefKind: "kind", + scaleRefName: "name", + scaleRefApiVersion: "apiVersion", expected: &autoscalingv1.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -60,79 +64,53 @@ func TestHPAGenerate(t *testing.T) { expectErr: false, }, { - name: "'name' is a required parameter", - params: map[string]interface{}{ - "scaleRef-kind": "kind", - "scaleRef-name": "name", - "scaleRef-apiVersion": "apiVersion", - }, - expectErr: true, + name: "'name' is a required parameter", + scaleRefKind: "kind", + scaleRefName: "name", + scaleRefApiVersion: "apiVersion", + expectErr: true, }, { - name: "'max' is a required parameter", - params: map[string]interface{}{ - "default-name": "foo", - "scaleRef-kind": "kind", - "scaleRef-name": "name", - "scaleRef-apiVersion": "apiVersion", - }, - expectErr: true, + name: "'max' is a required parameter", + HPAName: "foo", + scaleRefKind: "kind", + scaleRefName: "name", + scaleRefApiVersion: "apiVersion", + expectErr: true, }, { - name: "'max' must be greater than or equal to 'min'", - params: map[string]interface{}{ - "name": "foo", - "min": "10", - "max": "1", - "scaleRef-kind": "kind", - "scaleRef-name": "name", - "scaleRef-apiVersion": "apiVersion", - }, - expectErr: true, + name: "'max' must be greater than or equal to 'min'", + HPAName: "foo", + minReplicas: 10, + maxReplicas: 1, + scaleRefKind: "kind", + scaleRefName: "name", + scaleRefApiVersion: "apiVersion", + expectErr: true, }, { - name: "cpu-percent must be an integer if specified", - params: map[string]interface{}{ - "name": "foo", - "min": "1", - "max": "10", - "cpu-percent": "", - "scaleRef-kind": "kind", - "scaleRef-name": "name", - "scaleRef-apiVersion": "apiVersion", - }, - expectErr: true, - }, - { - name: "'min' must be an integer if specified", - params: map[string]interface{}{ - "name": "foo", - "min": "foo", - "max": "10", - "cpu-percent": "60", - "scaleRef-kind": "kind", - "scaleRef-name": "name", - "scaleRef-apiVersion": "apiVersion", - }, - expectErr: true, - }, - { - name: "'max' must be an integer if specified", - params: map[string]interface{}{ - "name": "foo", - "min": "1", - "max": "bar", - "cpu-percent": "90", - "scaleRef-kind": "kind", - "scaleRef-name": "name", - "scaleRef-apiVersion": "apiVersion", - }, - expectErr: true, + name: "'max' must be at least 1", + HPAName: "foo", + minReplicas: 1, + maxReplicas: -10, + scaleRefKind: "kind", + scaleRefName: "name", + scaleRefApiVersion: "apiVersion", + expectErr: true, }, } - generator := HorizontalPodAutoscalerV1{} + for _, test := range tests { - obj, err := generator.Generate(test.params) + generator := HorizontalPodAutoscalerGeneratorV1{ + Name: test.HPAName, + ScaleRefKind: test.scaleRefKind, + ScaleRefName: test.scaleRefName, + ScaleRefApiVersion: test.scaleRefApiVersion, + MinReplicas: test.minReplicas, + MaxReplicas: test.maxReplicas, + CPUPercent: test.CPUPercent, + } + obj, err := generator.StructuredGenerate() if test.expectErr && err != nil { continue } diff --git a/pkg/kubectl/categories/BUILD b/pkg/kubectl/categories/BUILD new file mode 100644 index 00000000000..433cad9130d --- /dev/null +++ b/pkg/kubectl/categories/BUILD @@ -0,0 +1,42 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["categories.go"], + importpath = "k8s.io/kubernetes/pkg/kubectl/categories", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/client-go/discovery:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["categories_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/kubectl/categories", + deps = [ + "//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/version:go_default_library", + "//vendor/k8s.io/client-go/discovery:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + "//vendor/k8s.io/client-go/rest/fake:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/kubectl/resource/categories.go b/pkg/kubectl/categories/categories.go similarity index 95% rename from pkg/kubectl/resource/categories.go rename to pkg/kubectl/categories/categories.go index bee08a05c53..4a669599af7 100644 --- a/pkg/kubectl/resource/categories.go +++ b/pkg/kubectl/categories/categories.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package resource +package categories import ( "k8s.io/apimachinery/pkg/runtime/schema" @@ -179,10 +179,3 @@ var LegacyCategoryExpander CategoryExpander = SimpleCategoryExpander{ "all": legacyUserResources, }, } - -// LegacyFederationCategoryExpander is the old hardcoded expansion for federation -var LegacyFederationCategoryExpander CategoryExpander = SimpleCategoryExpander{ - Expansions: map[string][]schema.GroupResource{ - "all": {{Group: "", Resource: "services"}}, - }, -} diff --git a/pkg/kubectl/resource/categories_test.go b/pkg/kubectl/categories/categories_test.go similarity index 99% rename from pkg/kubectl/resource/categories_test.go rename to pkg/kubectl/categories/categories_test.go index 7e5a6c7aed6..5344bad2242 100644 --- a/pkg/kubectl/resource/categories_test.go +++ b/pkg/kubectl/categories/categories_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package resource +package categories import ( "reflect" diff --git a/pkg/kubectl/cluster.go b/pkg/kubectl/cluster.go deleted file mode 100644 index ffac838b4bb..00000000000 --- a/pkg/kubectl/cluster.go +++ /dev/null @@ -1,159 +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 kubectl - -import ( - "fmt" - - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" -) - -const ( - ServiceAccountNameAnnotation = "federation.kubernetes.io/servive-account-name" - ClusterRoleNameAnnotation = "federation.kubernetes.io/cluster-role-name" -) - -// ClusterGeneratorV1Beta1 supports stable generation of a -// federation/cluster resource. -type ClusterGeneratorV1Beta1 struct { - // Name of the cluster context (required) - Name string - // ClientCIDR is the CIDR range in which the Kubernetes APIServer - // is available for the client (optional) - ClientCIDR string - // ServerAddress is the APIServer address of the Kubernetes cluster - // that is being registered (required) - ServerAddress string - // SecretName is the name of the secret that stores the credentials - // for the Kubernetes cluster that is being registered (optional) - SecretName string - // ServiceAccountName is the name of the service account that is - // created in the cluster being registered. If this is provided, - // then ClusterRoleName must also be provided (optional) - ServiceAccountName string - // ClusterRoleName is the name of the cluster role and cluster role - // binding that are created in the cluster being registered. If this - // is provided, then ServiceAccountName must also be provided - // (optional) - ClusterRoleName string -} - -// Ensure it supports the generator pattern that uses parameter -// injection. -var _ Generator = &ClusterGeneratorV1Beta1{} - -// Ensure it supports the generator pattern that uses parameters -// specified during construction. -var _ StructuredGenerator = &ClusterGeneratorV1Beta1{} - -// Generate returns a cluster resource using the specified parameters. -func (s ClusterGeneratorV1Beta1) Generate(genericParams map[string]interface{}) (runtime.Object, error) { - err := ValidateParams(s.ParamNames(), genericParams) - if err != nil { - return nil, err - } - clustergen := &ClusterGeneratorV1Beta1{} - params := map[string]string{} - for key, value := range genericParams { - strVal, isString := value.(string) - if !isString { - return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key) - } - params[key] = strVal - } - clustergen.Name = params["name"] - clustergen.ClientCIDR = params["client-cidr"] - clustergen.ServerAddress = params["server-address"] - clustergen.SecretName = params["secret"] - clustergen.ServiceAccountName = params["service-account-name"] - clustergen.ClusterRoleName = params["cluster-role-name"] - return clustergen.StructuredGenerate() -} - -// ParamNames returns the set of supported input parameters when using -// the parameter injection generator pattern. -func (s ClusterGeneratorV1Beta1) ParamNames() []GeneratorParam { - return []GeneratorParam{ - {"name", true}, - {"client-cidr", false}, - {"server-address", true}, - {"secret", false}, - {"service-account-name", false}, - {"cluster-role-name", false}, - } -} - -// StructuredGenerate outputs a federation cluster resource object -// using the configured fields. -func (s ClusterGeneratorV1Beta1) StructuredGenerate() (runtime.Object, error) { - if err := s.validate(); err != nil { - return nil, err - } - if s.ClientCIDR == "" { - s.ClientCIDR = "0.0.0.0/0" - } - if s.SecretName == "" { - s.SecretName = s.Name - } - cluster := &federationapi.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: s.Name, - }, - Spec: federationapi.ClusterSpec{ - ServerAddressByClientCIDRs: []federationapi.ServerAddressByClientCIDR{ - { - ClientCIDR: s.ClientCIDR, - ServerAddress: s.ServerAddress, - }, - }, - SecretRef: &v1.LocalObjectReference{ - Name: s.SecretName, - }, - }, - } - - annotations := make(map[string]string) - if s.ServiceAccountName != "" { - annotations[ServiceAccountNameAnnotation] = s.ServiceAccountName - } - if s.ClusterRoleName != "" { - annotations[ClusterRoleNameAnnotation] = s.ClusterRoleName - } - if len(annotations) == 1 { - return nil, fmt.Errorf("Either both or neither of ServiceAccountName and ClusterRoleName must be provided.") - } - if len(annotations) > 0 { - cluster.SetAnnotations(annotations) - } - - return cluster, nil -} - -// validate validates required fields are set to support structured -// generation. -func (s ClusterGeneratorV1Beta1) validate() error { - if len(s.Name) == 0 { - return fmt.Errorf("name must be specified") - } - if len(s.ServerAddress) == 0 { - return fmt.Errorf("server address must be specified") - } - return nil -} diff --git a/pkg/kubectl/cluster_test.go b/pkg/kubectl/cluster_test.go deleted file mode 100644 index ef21accd1ec..00000000000 --- a/pkg/kubectl/cluster_test.go +++ /dev/null @@ -1,239 +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 kubectl - -import ( - "reflect" - "testing" - - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" -) - -func TestClusterGenerate(t *testing.T) { - tests := []struct { - params map[string]interface{} - expected *federationapi.Cluster - expectErr bool - }{ - { - params: map[string]interface{}{ - "name": "foo", - "client-cidr": "0.0.0.0/0", - "server-address": "10.20.30.40", - "secret": "foo-credentials", - }, - expected: &federationapi.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: federationapi.ClusterSpec{ - ServerAddressByClientCIDRs: []federationapi.ServerAddressByClientCIDR{ - { - ClientCIDR: "0.0.0.0/0", - ServerAddress: "10.20.30.40", - }, - }, - SecretRef: &v1.LocalObjectReference{ - Name: "foo-credentials", - }, - }, - }, - expectErr: false, - }, - { - params: map[string]interface{}{ - "name": "foo", - "client-cidr": "10.20.30.40/16", - "server-address": "https://foo.example.com", - "secret": "foo-credentials", - }, - expected: &federationapi.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: federationapi.ClusterSpec{ - ServerAddressByClientCIDRs: []federationapi.ServerAddressByClientCIDR{ - { - ClientCIDR: "10.20.30.40/16", - ServerAddress: "https://foo.example.com", - }, - }, - SecretRef: &v1.LocalObjectReference{ - Name: "foo-credentials", - }, - }, - }, - expectErr: false, - }, - { - params: map[string]interface{}{ - "name": "bar-cluster", - "client-cidr": "10.20.30.40/16", - "server-address": "http://10.20.30.40", - "secret": "credentials", - }, - expected: &federationapi.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bar-cluster", - }, - Spec: federationapi.ClusterSpec{ - ServerAddressByClientCIDRs: []federationapi.ServerAddressByClientCIDR{ - { - ClientCIDR: "10.20.30.40/16", - ServerAddress: "http://10.20.30.40", - }, - }, - SecretRef: &v1.LocalObjectReference{ - Name: "credentials", - }, - }, - }, - expectErr: false, - }, - { - params: map[string]interface{}{ - "name": "bar-cluster", - "client-cidr": "10.20.30.40/16", - "server-address": "http://10.20.30.40", - "secret": "credentials", - }, - expected: &federationapi.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bar-cluster", - }, - Spec: federationapi.ClusterSpec{ - ServerAddressByClientCIDRs: []federationapi.ServerAddressByClientCIDR{ - { - ClientCIDR: "10.20.30.40/16", - ServerAddress: "http://10.20.30.40", - }, - }, - SecretRef: &v1.LocalObjectReference{ - Name: "credentials", - }, - }, - }, - expectErr: false, - }, - { - params: map[string]interface{}{ - "name": "bar-cluster", - "client-cidr": "10.20.30.40/16", - "server-address": "http://10.20.30.40", - "secret": "credentials", - "service-account-name": "service-account", - "cluster-role-name": "cluster-role", - }, - expected: &federationapi.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bar-cluster", - Annotations: map[string]string{ - ServiceAccountNameAnnotation: "service-account", - ClusterRoleNameAnnotation: "cluster-role", - }, - }, - Spec: federationapi.ClusterSpec{ - ServerAddressByClientCIDRs: []federationapi.ServerAddressByClientCIDR{ - { - ClientCIDR: "10.20.30.40/16", - ServerAddress: "http://10.20.30.40", - }, - }, - SecretRef: &v1.LocalObjectReference{ - Name: "credentials", - }, - }, - }, - expectErr: false, - }, - { - params: map[string]interface{}{ - "server-address": "https://10.20.30.40", - }, - expected: nil, - expectErr: true, - }, - { - params: map[string]interface{}{ - "secret": "baz-credentials", - }, - expected: nil, - expectErr: true, - }, - { - params: map[string]interface{}{ - "server-address": "http://foo.example.com", - "secret": "foo-credentials", - }, - expected: nil, - expectErr: true, - }, - { - params: map[string]interface{}{ - "name": "foo", - "secret": "foo-credentials", - }, - expected: nil, - expectErr: true, - }, - { - params: map[string]interface{}{ - "name": "foo", - "client-cidr": "10.20.30.40/16", - }, - expected: nil, - expectErr: true, - }, - { - params: map[string]interface{}{ - "name": "bar-cluster", - "client-cidr": "10.20.30.40/16", - "server-address": "http://10.20.30.40", - "secret": "credentials", - "cluster-role-name": "cluster-role", - }, - expected: nil, - expectErr: true, - }, - { - params: map[string]interface{}{ - "name": "bar-cluster", - "client-cidr": "10.20.30.40/16", - "server-address": "http://10.20.30.40", - "secret": "credentials", - "service-account-name": "service-account", - }, - expected: nil, - expectErr: true, - }, - } - generator := ClusterGeneratorV1Beta1{} - for i, test := range tests { - obj, err := generator.Generate(test.params) - if !test.expectErr && err != nil { - t.Errorf("[%d] unexpected error: %v", i, err) - } - if test.expectErr && err != nil { - continue - } - if !reflect.DeepEqual(obj.(*federationapi.Cluster), test.expected) { - t.Errorf("\n[%d] want:\n%#v\n[%d] got:\n%#v", i, test.expected, i, obj.(*federationapi.Cluster)) - } - } -} diff --git a/pkg/kubectl/cmd/BUILD b/pkg/kubectl/cmd/BUILD index 07324d2934d..0bf32689830 100644 --- a/pkg/kubectl/cmd/BUILD +++ b/pkg/kubectl/cmd/BUILD @@ -30,6 +30,7 @@ go_library( "create_deployment.go", "create_namespace.go", "create_pdb.go", + "create_priorityclass.go", "create_quota.go", "create_role.go", "create_rolebinding.go", @@ -38,12 +39,12 @@ go_library( "create_serviceaccount.go", "delete.go", "describe.go", + "diff.go", "drain.go", "edit.go", "exec.go", "explain.go", "expose.go", - "get.go", "help.go", "label.go", "logs.go", @@ -67,18 +68,19 @@ go_library( "//build/visible_to:pkg_kubectl_cmd_CONSUMERS", ], deps = [ - "//pkg/api:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/certificates:go_default_library", - "//pkg/apis/policy:go_default_library", - "//pkg/apis/rbac:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library", - "//pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion:go_default_library", "//pkg/client/unversioned:go_default_library", "//pkg/kubectl:go_default_library", + "//pkg/kubectl/apply/parse:go_default_library", + "//pkg/kubectl/apply/strategy:go_default_library", "//pkg/kubectl/cmd/auth:go_default_library", "//pkg/kubectl/cmd/config:go_default_library", + "//pkg/kubectl/cmd/resource:go_default_library", "//pkg/kubectl/cmd/rollout:go_default_library", "//pkg/kubectl/cmd/set:go_default_library", "//pkg/kubectl/cmd/templates:go_default_library", @@ -90,6 +92,7 @@ go_library( "//pkg/kubectl/plugins:go_default_library", "//pkg/kubectl/proxy:go_default_library", "//pkg/kubectl/resource:go_default_library", + "//pkg/kubectl/scheme:go_default_library", "//pkg/kubectl/util:go_default_library", "//pkg/kubectl/util/i18n:go_default_library", "//pkg/kubectl/util/term:go_default_library", @@ -112,6 +115,8 @@ go_library( "//vendor/k8s.io/api/batch/v1beta1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", + "//vendor/k8s.io/api/policy/v1beta1:go_default_library", + "//vendor/k8s.io/api/rbac/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -135,11 +140,15 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/version:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/typed/rbac/v1:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", "//vendor/k8s.io/client-go/tools/portforward:go_default_library", "//vendor/k8s.io/client-go/tools/remotecommand:go_default_library", "//vendor/k8s.io/client-go/transport/spdy:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library", ], ) @@ -152,6 +161,7 @@ go_test( "attach_test.go", "clusterinfo_dump_test.go", "cmd_test.go", + "convert_test.go", "cp_test.go", "create_clusterrole_test.go", "create_clusterrolebinding_test.go", @@ -159,6 +169,7 @@ go_test( "create_deployment_test.go", "create_namespace_test.go", "create_pdb_test.go", + "create_priorityclass_test.go", "create_quota_test.go", "create_role_test.go", "create_rolebinding_test.go", @@ -168,11 +179,11 @@ go_test( "create_test.go", "delete_test.go", "describe_test.go", + "diff_test.go", "drain_test.go", "edit_test.go", "exec_test.go", "expose_test.go", - "get_test.go", "label_test.go", "logs_test.go", "patch_test.go", @@ -188,26 +199,27 @@ go_test( ], data = [ "testdata", + "//api/openapi-spec:swagger-spec", "//examples:config", "//test/fixtures", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubectl/cmd", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/ref:go_default_library", "//pkg/api/testapi:go_default_library", "//pkg/api/testing:go_default_library", "//pkg/apis/batch:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", - "//pkg/apis/policy:go_default_library", - "//pkg/apis/rbac:go_default_library", "//pkg/kubectl:go_default_library", "//pkg/kubectl/cmd/testing:go_default_library", "//pkg/kubectl/cmd/util:go_default_library", "//pkg/kubectl/cmd/util/openapi:go_default_library", "//pkg/kubectl/plugins:go_default_library", "//pkg/kubectl/resource:go_default_library", + "//pkg/kubectl/scheme:go_default_library", "//pkg/kubectl/util/i18n:go_default_library", "//pkg/kubectl/util/term:go_default_library", "//pkg/printers:go_default_library", @@ -217,8 +229,9 @@ go_test( "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/gopkg.in/yaml.v2:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/policy/v1beta1:go_default_library", + "//vendor/k8s.io/api/rbac/v1:go_default_library", "//vendor/k8s.io/api/rbac/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", @@ -226,21 +239,19 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/serializer/json:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/serializer/streaming:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/strategicpatch/testing:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", "//vendor/k8s.io/client-go/dynamic:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/rest/fake:go_default_library", - "//vendor/k8s.io/client-go/rest/watch:go_default_library", "//vendor/k8s.io/client-go/tools/remotecommand:go_default_library", "//vendor/k8s.io/metrics/pkg/apis/metrics/v1alpha1:go_default_library", + "//vendor/k8s.io/utils/exec:go_default_library", ], ) @@ -256,6 +267,7 @@ filegroup( ":package-srcs", "//pkg/kubectl/cmd/auth:all-srcs", "//pkg/kubectl/cmd/config:all-srcs", + "//pkg/kubectl/cmd/resource:all-srcs", "//pkg/kubectl/cmd/rollout:all-srcs", "//pkg/kubectl/cmd/set:all-srcs", "//pkg/kubectl/cmd/templates:all-srcs", diff --git a/pkg/kubectl/cmd/alpha.go b/pkg/kubectl/cmd/alpha.go index efec2b9b6d7..afbe7347dc0 100644 --- a/pkg/kubectl/cmd/alpha.go +++ b/pkg/kubectl/cmd/alpha.go @@ -37,6 +37,7 @@ func NewCmdAlpha(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cobra.Com // Alpha commands should be added here. As features graduate from alpha they should move // from here to the CommandGroups defined by NewKubeletCommand() in cmd.go. //cmd.AddCommand(NewCmdDebug(f, in, out, err)) + cmd.AddCommand(NewCmdDiff(f, out, err)) // NewKubeletCommand() will hide the alpha command if it has no subcommands. Overriding // the help function ensures a reasonable message if someone types the hidden command anyway. diff --git a/pkg/kubectl/cmd/annotate.go b/pkg/kubectl/cmd/annotate.go index 3d53db96977..09541ea0e24 100644 --- a/pkg/kubectl/cmd/annotate.go +++ b/pkg/kubectl/cmd/annotate.go @@ -65,14 +65,16 @@ type AnnotateOptions struct { var ( annotateLong = templates.LongDesc(` - Update the annotations on one or more resources. + Update the annotations on one or more resources - * An annotation is a key/value pair that can hold larger (compared to a label), and possibly not human-readable, data. - * It is intended to store non-identifying auxiliary data, especially data manipulated by tools and system extensions. - * If --overwrite is true, then existing annotations can be overwritten, otherwise attempting to overwrite an annotation will result in an error. - * If --resource-version is specified, then updates will use this resource version, otherwise the existing resource-version will be used. + All Kubernetes objects support the ability to store additional data with the object as + annotations. Annotations are key/value pairs that can be larger than labels and include + arbitrary string values such as structured JSON. Tools and system extensions may use + annotations to store their own data. - ` + validResources) + Attempting to set an annotation that already exists will fail unless --overwrite is set. + If --resource-version is specified and does not match the current resource version on + the server the command will fail.`) annotateExample = templates.Examples(i18n.T(` # Update pod 'foo' with the annotation 'description' and the value 'my frontend'. @@ -113,7 +115,7 @@ func NewCmdAnnotate(f cmdutil.Factory, out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "annotate [--overwrite] (-f FILENAME | TYPE NAME) KEY_1=VAL_1 ... KEY_N=VAL_N [--resource-version=version]", Short: i18n.T("Update the annotations on a resource"), - Long: annotateLong, + Long: annotateLong + "\n\n" + cmdutil.ValidResourceTypeList(f), Example: annotateExample, Run: func(cmd *cobra.Command, args []string) { if err := options.Complete(out, cmd, args); err != nil { @@ -188,6 +190,8 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false) b := f.NewBuilder(). + Unstructured(). + LocalParam(o.local). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). FilenameParam(enforceNamespace, &o.FilenameOptions). @@ -195,20 +199,9 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro Flatten() if !o.local { - // call this method here, as it requires an api call - // and will cause the command to fail when there is - // no connection to a server - mapper, typer, err := f.UnstructuredObject() - if err != nil { - return err - } - - b = b.SelectorParam(o.selector). - Unstructured(f.UnstructuredClientForMapping, mapper, typer). + b = b.LabelSelectorParam(o.selector). ResourceTypeOrNameArgs(o.all, o.resources...). Latest() - } else { - b = b.Local(f.ClientForMapping) } r := b.Do() @@ -285,19 +278,11 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro } } - var mapper meta.RESTMapper - if o.local { - mapper, _ = f.Object() - } else { - mapper, _, err = f.UnstructuredObject() - if err != nil { - return err - } - } + mapper := r.Mapper().RESTMapper if len(o.outputFormat) > 0 { return f.PrintObject(cmd, o.local, mapper, outputObj, o.out) } - cmdutil.PrintSuccess(mapper, false, o.out, info.Mapping.Resource, info.Name, o.dryrun, "annotated") + f.PrintSuccess(mapper, false, o.out, info.Mapping.Resource, info.Name, o.dryrun, "annotated") return nil }) } diff --git a/pkg/kubectl/cmd/annotate_test.go b/pkg/kubectl/cmd/annotate_test.go index e1b6ca7d6dd..fdd968df260 100644 --- a/pkg/kubectl/cmd/annotate_test.go +++ b/pkg/kubectl/cmd/annotate_test.go @@ -23,11 +23,11 @@ import ( "strings" "testing" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - restclient "k8s.io/client-go/rest" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" ) @@ -117,8 +117,8 @@ func TestParseAnnotations(t *testing.T) { expectErr: false, }, { - annotations: []string{"url=" + testURL, api.CreatedByAnnotation + "=" + testJSON}, - expected: map[string]string{"url": testURL, api.CreatedByAnnotation: testJSON}, + annotations: []string{"url=" + testURL, "fake.kubernetes.io/annotation=" + testJSON}, + expected: map[string]string{"url": testURL, "fake.kubernetes.io/annotation": testJSON}, expectedRemove: []string{}, scenario: "add annotations with special characters", expectErr: false, @@ -226,7 +226,7 @@ func TestUpdateAnnotations(t *testing.T) { expectErr bool }{ { - obj: &api.Pod{ + obj: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{"a": "b"}, }, @@ -235,41 +235,41 @@ func TestUpdateAnnotations(t *testing.T) { expectErr: true, }, { - obj: &api.Pod{ + obj: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{"a": "b"}, }, }, annotations: map[string]string{"a": "c"}, overwrite: true, - expected: &api.Pod{ + expected: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{"a": "c"}, }, }, }, { - obj: &api.Pod{ + obj: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{"a": "b"}, }, }, annotations: map[string]string{"c": "d"}, - expected: &api.Pod{ + expected: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{"a": "b", "c": "d"}, }, }, }, { - obj: &api.Pod{ + obj: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{"a": "b"}, }, }, annotations: map[string]string{"c": "d"}, version: "2", - expected: &api.Pod{ + expected: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{"a": "b", "c": "d"}, ResourceVersion: "2", @@ -277,28 +277,28 @@ func TestUpdateAnnotations(t *testing.T) { }, }, { - obj: &api.Pod{ + obj: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{"a": "b"}, }, }, annotations: map[string]string{}, remove: []string{"a"}, - expected: &api.Pod{ + expected: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{}, }, }, }, { - obj: &api.Pod{ + obj: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{"a": "b", "c": "d"}, }, }, annotations: map[string]string{"e": "f"}, remove: []string{"a"}, - expected: &api.Pod{ + expected: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ "c": "d", @@ -308,14 +308,14 @@ func TestUpdateAnnotations(t *testing.T) { }, }, { - obj: &api.Pod{ + obj: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{"a": "b", "c": "d"}, }, }, annotations: map[string]string{"e": "f"}, remove: []string{"g"}, - expected: &api.Pod{ + expected: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ "a": "b", @@ -326,13 +326,13 @@ func TestUpdateAnnotations(t *testing.T) { }, }, { - obj: &api.Pod{ + obj: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{"a": "b", "c": "d"}, }, }, remove: []string{"e"}, - expected: &api.Pod{ + expected: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ "a": "b", @@ -342,11 +342,11 @@ func TestUpdateAnnotations(t *testing.T) { }, }, { - obj: &api.Pod{ + obj: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{}, }, annotations: map[string]string{"a": "b"}, - expected: &api.Pod{ + expected: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{"a": "b"}, }, @@ -418,7 +418,7 @@ func TestAnnotateErrors(t *testing.T) { f, tf, _, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}} + tf.ClientConfig = defaultClientConfig() buf := bytes.NewBuffer([]byte{}) cmd := NewCmdAnnotate(f, buf) @@ -451,7 +451,7 @@ func TestAnnotateObject(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Group: "testgroup", Version: "v1"}, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.Method { @@ -478,7 +478,7 @@ func TestAnnotateObject(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}} + tf.ClientConfig = defaultClientConfig() buf := bytes.NewBuffer([]byte{}) cmd := NewCmdAnnotate(f, buf) @@ -502,7 +502,7 @@ func TestAnnotateObjectFromFile(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Group: "testgroup", Version: "v1"}, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.Method { @@ -529,7 +529,7 @@ func TestAnnotateObjectFromFile(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}} + tf.ClientConfig = defaultClientConfig() buf := bytes.NewBuffer([]byte{}) cmd := NewCmdAnnotate(f, buf) @@ -551,7 +551,7 @@ func TestAnnotateObjectFromFile(t *testing.T) { func TestAnnotateLocal(t *testing.T) { f, tf, _, _ := cmdtesting.NewAPIFactory() tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Group: "testgroup", Version: "v1"}, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) @@ -559,7 +559,7 @@ func TestAnnotateLocal(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}} + tf.ClientConfig = defaultClientConfig() buf := bytes.NewBuffer([]byte{}) cmd := NewCmdAnnotate(f, buf) @@ -584,7 +584,7 @@ func TestAnnotateMultipleObjects(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Group: "testgroup", Version: "v1"}, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.Method { @@ -613,7 +613,7 @@ func TestAnnotateMultipleObjects(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}} + tf.ClientConfig = defaultClientConfig() buf := bytes.NewBuffer([]byte{}) cmd := NewCmdAnnotate(f, buf) diff --git a/pkg/kubectl/cmd/apply.go b/pkg/kubectl/cmd/apply.go index adda03661cd..e132743231c 100644 --- a/pkg/kubectl/cmd/apply.go +++ b/pkg/kubectl/cmd/apply.go @@ -37,12 +37,15 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/kubernetes/pkg/api" + oapi "k8s.io/kube-openapi/pkg/util/proto" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/kubectl/util/i18n" ) @@ -126,6 +129,7 @@ func NewCmdApply(baseName string, f cmdutil.Factory, out, errOut io.Writer) *cob cmd.Flags().StringVarP(&options.Selector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") cmd.Flags().Bool("all", false, "Select all resources in the namespace of the specified resource types.") cmd.Flags().StringArray("prune-whitelist", []string{}, "Overwrite the default whitelist with for --prune") + cmd.Flags().Bool("openapi-patch", true, "If true, use openapi to calculate diff when the openapi presents and the resource can be found in the openapi spec. Otherwise, fall back to use baked-in types.") cmdutil.AddDryRunFlag(cmd) cmdutil.AddPrinterFlags(cmd) cmdutil.AddRecordFlag(cmd) @@ -192,55 +196,56 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti return err } + var openapiSchema openapi.Resources + if cmdutil.GetFlagBool(cmd, "openapi-patch") { + openapiSchema, err = f.OpenAPISchema() + if err != nil { + openapiSchema = nil + } + } + cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err } - mapper, typer, err := f.UnstructuredObject() - if err != nil { - return err - } - - if options.Prune { - options.PruneResources, err = parsePruneResources(mapper, cmdutil.GetFlagStringArray(cmd, "prune-whitelist")) - if err != nil { - return err - } - } - // include the uninitialized objects by default if --prune is true // unless explicitly set --include-uninitialized=false includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, options.Prune) r := f.NewBuilder(). - Unstructured(f.UnstructuredClientForMapping, mapper, typer). + Unstructured(). Schema(schema). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &options.FilenameOptions). - SelectorParam(options.Selector). + LabelSelectorParam(options.Selector). IncludeUninitialized(includeUninitialized). Flatten(). Do() - err = r.Err() - if err != nil { + if err := r.Err(); err != nil { return err } + if options.Prune { + options.PruneResources, err = parsePruneResources(r.Mapper().RESTMapper, cmdutil.GetFlagStringArray(cmd, "prune-whitelist")) + if err != nil { + return err + } + } + dryRun := cmdutil.GetFlagBool(cmd, "dry-run") output := cmdutil.GetFlagString(cmd, "output") shortOutput := output == "name" encoder := f.JSONEncoder() decoder := f.Decoder(false) + mapper := r.Mapper().RESTMapper visitedUids := sets.NewString() visitedNamespaces := sets.NewString() count := 0 err = r.Visit(func(info *resource.Info, err error) error { - // In this method, info.Object contains the object retrieved from the server - // and info.VersionedObject contains the object decoded from the input source. if err != nil { return err } @@ -251,10 +256,7 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti // Add change-cause annotation to resource info if it should be recorded if cmdutil.ShouldRecord(cmd, info) { - recordInObj := info.VersionedObject - if info.VersionedObject == nil { - recordInObj = info.Object - } + recordInObj := info.Object if err := cmdutil.RecordChangeCause(recordInObj, f.Command(cmd, false)); err != nil { glog.V(4).Infof("error recording current command: %v", err) } @@ -292,9 +294,9 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti count++ if len(output) > 0 && !shortOutput { - return cmdutil.PrintResourceInfoForCommand(cmd, info, f, out) + return f.PrintResourceInfoForCommand(cmd, info, out) } - cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, dryRun, "created") + f.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, dryRun, "created") return nil } @@ -321,9 +323,10 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti cascade: options.Cascade, timeout: options.Timeout, gracePeriod: options.GracePeriod, + openapiSchema: openapiSchema, } - patchBytes, patchedObject, err := patcher.patch(info.Object, modified, info.Source, info.Namespace, info.Name) + patchBytes, patchedObject, err := patcher.patch(info.Object, modified, info.Source, info.Namespace, info.Name, errOut) if err != nil { return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patchBytes, info), info.Source, err) } @@ -335,12 +338,18 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti } else { visitedUids.Insert(string(uid)) } + + if string(patchBytes) == "{}" { + count++ + f.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, false, "unchanged") + return nil + } } count++ if len(output) > 0 && !shortOutput { - return cmdutil.PrintResourceInfoForCommand(cmd, info, f, out) + return f.PrintResourceInfoForCommand(cmd, info, out) } - cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, dryRun, "configured") + f.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, dryRun, "configured") return nil }) @@ -360,8 +369,8 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti clientFunc: f.UnstructuredClientForMapping, clientsetFunc: f.ClientSet, - selector: options.Selector, - visitedUids: visitedUids, + labelSelector: options.Selector, + visitedUids: visitedUids, cascade: options.Cascade, dryRun: dryRun, @@ -377,13 +386,13 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti for n := range visitedNamespaces { for _, m := range namespacedRESTMappings { - if err := p.prune(n, m, shortOutput, includeUninitialized); err != nil { + if err := p.prune(f, n, m, shortOutput, includeUninitialized); err != nil { return fmt.Errorf("error pruning namespaced object %v: %v", m.GroupVersionKind, err) } } } for _, m := range nonNamespacedRESTMappings { - if err := p.prune(metav1.NamespaceNone, m, shortOutput, includeUninitialized); err != nil { + if err := p.prune(f, metav1.NamespaceNone, m, shortOutput, includeUninitialized); err != nil { return fmt.Errorf("error pruning nonNamespaced object %v: %v", m.GroupVersionKind, err) } } @@ -446,8 +455,9 @@ type pruner struct { clientFunc resource.ClientMapperFunc clientsetFunc func() (internalclientset.Interface, error) - visitedUids sets.String - selector string + visitedUids sets.String + labelSelector string + fieldSelector string cascade bool dryRun bool @@ -456,13 +466,22 @@ type pruner struct { out io.Writer } -func (p *pruner) prune(namespace string, mapping *meta.RESTMapping, shortOutput, includeUninitialized bool) error { +func (p *pruner) prune(f cmdutil.Factory, namespace string, mapping *meta.RESTMapping, shortOutput, includeUninitialized bool) error { c, err := p.clientFunc(mapping) if err != nil { return err } - objList, err := resource.NewHelper(c, mapping).List(namespace, mapping.GroupVersionKind.Version, p.selector, false, includeUninitialized) + objList, err := resource.NewHelper(c, mapping).List( + namespace, + mapping.GroupVersionKind.Version, + false, + &metav1.ListOptions{ + LabelSelector: p.labelSelector, + FieldSelector: p.fieldSelector, + IncludeUninitialized: includeUninitialized, + }, + ) if err != nil { return err } @@ -497,7 +516,7 @@ func (p *pruner) prune(namespace string, mapping *meta.RESTMapping, shortOutput, return err } } - cmdutil.PrintSuccess(p.mapper, shortOutput, p.out, mapping.Resource, name, p.dryRun, "pruned") + f.PrintSuccess(p.mapper, shortOutput, p.out, mapping.Resource, name, p.dryRun, "pruned") } return nil } @@ -563,9 +582,11 @@ type patcher struct { cascade bool timeout time.Duration gracePeriod int + + openapiSchema openapi.Resources } -func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, namespace, name string) ([]byte, runtime.Object, error) { +func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, namespace, name string, errOut io.Writer) ([]byte, runtime.Object, error) { // Serialize the current configuration of the object from the server. current, err := runtime.Encode(p.encoder, obj) if err != nil { @@ -578,12 +599,15 @@ func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, names return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf("retrieving original configuration from:\n%v\nfor:", obj), source, err) } - // Create the versioned struct from the type defined in the restmapping - // (which is the API version we'll be submitting the patch to) - versionedObject, err := api.Scheme.New(p.mapping.GroupVersionKind) var patchType types.PatchType var patch []byte + var lookupPatchMeta strategicpatch.LookupPatchMeta + var schema oapi.Schema createPatchErrFormat := "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor:" + + // Create the versioned struct from the type defined in the restmapping + // (which is the API version we'll be submitting the patch to) + versionedObject, err := scheme.Scheme.New(p.mapping.GroupVersionKind) switch { case runtime.IsNotRegisteredError(err): // fall back to generic JSON merge patch @@ -602,19 +626,44 @@ func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, names case err == nil: // Compute a three way strategic merge patch to send to server. patchType = types.StrategicMergePatchType - patch, err = strategicpatch.CreateThreeWayMergePatch(original, modified, current, versionedObject, p.overwrite) - if err != nil { - return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf(createPatchErrFormat, original, modified, current), source, err) + + // Try to use openapi first if the openapi spec is available and can successfully calculate the patch. + // Otherwise, fall back to baked-in types. + if p.openapiSchema != nil { + if schema = p.openapiSchema.LookupResource(p.mapping.GroupVersionKind); schema != nil { + lookupPatchMeta = strategicpatch.PatchMetaFromOpenAPI{Schema: schema} + if openapiPatch, err := strategicpatch.CreateThreeWayMergePatch(original, modified, current, lookupPatchMeta, p.overwrite); err != nil { + fmt.Fprintf(errOut, "warning: error calculating patch from openapi spec: %v\n", err) + } else { + patchType = types.StrategicMergePatchType + patch = openapiPatch + } + } } + + if patch == nil { + lookupPatchMeta, err = strategicpatch.NewPatchMetaFromStruct(versionedObject) + if err != nil { + return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf(createPatchErrFormat, original, modified, current), source, err) + } + patch, err = strategicpatch.CreateThreeWayMergePatch(original, modified, current, lookupPatchMeta, p.overwrite) + if err != nil { + return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf(createPatchErrFormat, original, modified, current), source, err) + } + } + } + + if string(patch) == "{}" { + return patch, obj, nil } patchedObj, err := p.helper.Patch(namespace, name, patchType, patch) return patch, patchedObj, err } -func (p *patcher) patch(current runtime.Object, modified []byte, source, namespace, name string) ([]byte, runtime.Object, error) { +func (p *patcher) patch(current runtime.Object, modified []byte, source, namespace, name string, errOut io.Writer) ([]byte, runtime.Object, error) { var getErr error - patchBytes, patchObject, err := p.patchSimple(current, modified, source, namespace, name) + patchBytes, patchObject, err := p.patchSimple(current, modified, source, namespace, name, errOut) for i := 1; i <= maxPatchRetry && errors.IsConflict(err); i++ { if i > triesBeforeBackOff { p.backOff.Sleep(backOffPeriod) @@ -623,7 +672,7 @@ func (p *patcher) patch(current runtime.Object, modified []byte, source, namespa if getErr != nil { return nil, nil, getErr } - patchBytes, patchObject, err = p.patchSimple(current, modified, source, namespace, name) + patchBytes, patchObject, err = p.patchSimple(current, modified, source, namespace, name, errOut) } if err != nil && p.force { patchBytes, patchObject, err = p.deleteAndCreate(modified, namespace, name) diff --git a/pkg/kubectl/cmd/apply_set_last_applied.go b/pkg/kubectl/cmd/apply_set_last_applied.go index f1f06648ae2..640009b2788 100644 --- a/pkg/kubectl/cmd/apply_set_last_applied.go +++ b/pkg/kubectl/cmd/apply_set_last_applied.go @@ -30,7 +30,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" apijson "k8s.io/apimachinery/pkg/util/json" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -112,7 +112,7 @@ func (o *SetLastAppliedOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) o.Codec = f.JSONEncoder() var err error - o.Mapper, o.Typer, err = f.UnstructuredObject() + o.Mapper, o.Typer = f.Object() if err != nil { return err } @@ -122,29 +122,18 @@ func (o *SetLastAppliedOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) } func (o *SetLastAppliedOptions) Validate(f cmdutil.Factory, cmd *cobra.Command) error { - mapper, typer, err := f.UnstructuredObject() - if err != nil { - return err - } - r := f.NewBuilder(). - Unstructured(f.UnstructuredClientForMapping, mapper, typer). + Unstructured(). NamespaceParam(o.Namespace).DefaultNamespace(). FilenameParam(o.EnforceNamespace, &o.FilenameOptions). - Latest(). Flatten(). Do() - err = r.Err() - if err != nil { - return err - } - err = r.Visit(func(info *resource.Info, err error) error { + err := r.Visit(func(info *resource.Info, err error) error { if err != nil { return err } - - patchBuf, diffBuf, patchType, err := editor.GetApplyPatch(info.VersionedObject, o.Codec) + patchBuf, diffBuf, patchType, err := editor.GetApplyPatch(info.Object, o.Codec) if err != nil { return err } @@ -157,16 +146,16 @@ func (o *SetLastAppliedOptions) Validate(f cmdutil.Factory, cmd *cobra.Command) return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%v\nfrom server for:", info), info.Source, err) } } - oringalBuf, err := kubectl.GetOriginalConfiguration(info.Mapping, info.Object) + originalBuf, err := kubectl.GetOriginalConfiguration(info.Mapping, info.Object) if err != nil { return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%v\nfrom server for:", info), info.Source, err) } - if oringalBuf == nil && !o.CreateAnnotation { + if originalBuf == nil && !o.CreateAnnotation { return cmdutil.UsageErrorf(cmd, "no last-applied-configuration annotation found on resource: %s, to create the annotation, run the command with --create-annotation", info.Name) } //only add to PatchBufferList when changed - if !bytes.Equal(cmdutil.StripComments(oringalBuf), cmdutil.StripComments(diffBuf)) { + if !bytes.Equal(cmdutil.StripComments(originalBuf), cmdutil.StripComments(diffBuf)) { p := PatchBuffer{Patch: patchBuf, PatchType: patchType} o.PatchBufferList = append(o.PatchBufferList, p) o.InfoList = append(o.InfoList, info) @@ -196,16 +185,16 @@ func (o *SetLastAppliedOptions) RunSetLastApplied(f cmdutil.Factory, cmd *cobra. if len(o.Output) > 0 && !o.ShortOutput { info.Refresh(patchedObj, false) - return cmdutil.PrintResourceInfoForCommand(cmd, info, f, o.Out) + return f.PrintResourceInfoForCommand(cmd, info, o.Out) } - cmdutil.PrintSuccess(o.Mapper, o.ShortOutput, o.Out, info.Mapping.Resource, info.Name, o.DryRun, "configured") + f.PrintSuccess(o.Mapper, o.ShortOutput, o.Out, info.Mapping.Resource, info.Name, o.DryRun, "configured") } else { err := o.formatPrinter(o.Output, patch.Patch, o.Out) if err != nil { return err } - cmdutil.PrintSuccess(o.Mapper, o.ShortOutput, o.Out, info.Mapping.Resource, info.Name, o.DryRun, "configured") + f.PrintSuccess(o.Mapper, o.ShortOutput, o.Out, info.Mapping.Resource, info.Name, o.DryRun, "configured") } } return nil @@ -234,7 +223,7 @@ func (o *SetLastAppliedOptions) getPatch(info *resource.Info) ([]byte, []byte, e objMap := map[string]map[string]map[string]string{} metadataMap := map[string]map[string]string{} annotationsMap := map[string]string{} - localFile, err := runtime.Encode(o.Codec, info.VersionedObject) + localFile, err := runtime.Encode(o.Codec, info.Object) if err != nil { return nil, localFile, err } diff --git a/pkg/kubectl/cmd/apply_test.go b/pkg/kubectl/cmd/apply_test.go index 6f184ac864a..7e291a406ff 100644 --- a/pkg/kubectl/cmd/apply_test.go +++ b/pkg/kubectl/cmd/apply_test.go @@ -19,10 +19,12 @@ package cmd import ( "bytes" "encoding/json" + "errors" "fmt" "io/ioutil" "net/http" "os" + "path/filepath" "strings" "testing" @@ -33,16 +35,33 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + sptest "k8s.io/apimachinery/pkg/util/strategicpatch/testing" restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" "k8s.io/kubernetes/pkg/printers" ) +var ( + fakeSchema = sptest.Fake{Path: filepath.Join("..", "..", "..", "api", "openapi-spec", "swagger.json")} + testingOpenAPISchemaFns = []func() (openapi.Resources, error){nil, AlwaysErrorOpenAPISchemaFn, openAPISchemaFn} + AlwaysErrorOpenAPISchemaFn = func() (openapi.Resources, error) { + return nil, errors.New("cannot get openapi spec") + } + openAPISchemaFn = func() (openapi.Resources, error) { + s, err := fakeSchema.OpenAPISchema() + if err != nil { + return nil, err + } + return openapi.NewOpenAPIData(s) + } +) + func TestApplyExtraArgsFail(t *testing.T) { buf := bytes.NewBuffer([]byte{}) errBuf := bytes.NewBuffer([]byte{}) @@ -62,15 +81,17 @@ func validateApplyArgs(cmd *cobra.Command, args []string) error { } const ( - filenameRC = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc.yaml" - filenameRCNoAnnotation = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc-no-annotation.yaml" - filenameRCLASTAPPLIED = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc-lastapplied.yaml" - filenameSVC = "../../../test/fixtures/pkg/kubectl/cmd/apply/service.yaml" - filenameRCSVC = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc-service.yaml" - filenameNoExistRC = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc-noexist.yaml" - filenameRCPatchTest = "../../../test/fixtures/pkg/kubectl/cmd/apply/patch.json" - dirName = "../../../test/fixtures/pkg/kubectl/cmd/apply/testdir" - filenameRCJSON = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc.json" + filenameRC = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc.yaml" + filenameRCArgs = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc-args.yaml" + filenameRCLastAppliedArgs = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc-lastapplied-args.yaml" + filenameRCNoAnnotation = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc-no-annotation.yaml" + filenameRCLASTAPPLIED = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc-lastapplied.yaml" + filenameSVC = "../../../test/fixtures/pkg/kubectl/cmd/apply/service.yaml" + filenameRCSVC = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc-service.yaml" + filenameNoExistRC = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc-noexist.yaml" + filenameRCPatchTest = "../../../test/fixtures/pkg/kubectl/cmd/apply/patch.json" + dirName = "../../../test/fixtures/pkg/kubectl/cmd/apply/testdir" + filenameRCJSON = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc.json" filenameWidgetClientside = "../../../test/fixtures/pkg/kubectl/cmd/apply/widget-clientside.yaml" filenameWidgetServerside = "../../../test/fixtures/pkg/kubectl/cmd/apply/widget-serverside.yaml" @@ -228,6 +249,7 @@ func walkMapPath(t *testing.T, start map[string]interface{}, path []string) map[ func TestRunApplyViewLastApplied(t *testing.T) { _, rcBytesWithConfig := readReplicationController(t, filenameRCLASTAPPLIED) + _, rcBytesWithArgs := readReplicationController(t, filenameRCLastAppliedArgs) nameRC, rcBytes := readReplicationController(t, filenameRC) pathRC := "/namespaces/test/replicationcontrollers/" + nameRC @@ -246,6 +268,16 @@ func TestRunApplyViewLastApplied(t *testing.T) { args: []string{}, respBytes: rcBytesWithConfig, }, + { + name: "test with file include `%s` in arguments", + filePath: filenameRCArgs, + outputFormat: "", + expectedErr: "", + expectedOut: "args: -random_flag=%s@domain.com\n", + selector: "", + args: []string{}, + respBytes: rcBytesWithArgs, + }, { name: "view with file json format", filePath: filenameRC, @@ -301,7 +333,7 @@ func TestRunApplyViewLastApplied(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Version: "v1"}, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -357,7 +389,6 @@ func TestApplyObjectWithoutAnnotation(t *testing.T) { f, tf, _, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -399,39 +430,44 @@ func TestApplyObject(t *testing.T) { nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC) pathRC := "/namespaces/test/replicationcontrollers/" + nameRC - f, tf, _, _ := cmdtesting.NewAPIFactory() - tf.Printer = &testPrinter{} - tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, - NegotiatedSerializer: unstructuredSerializer, - Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { - switch p, m := req.URL.Path, req.Method; { - case p == pathRC && m == "GET": - bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil - case p == pathRC && m == "PATCH": - validatePatchApplication(t, req) - bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil - default: - t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) - return nil, nil - } - }), - } - tf.Namespace = "test" - buf := bytes.NewBuffer([]byte{}) - errBuf := bytes.NewBuffer([]byte{}) + for _, fn := range testingOpenAPISchemaFns { + f, tf, _, _ := cmdtesting.NewAPIFactory() + tf.Printer = &testPrinter{} + tf.UnstructuredClient = &fake.RESTClient{ + NegotiatedSerializer: unstructuredSerializer, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + switch p, m := req.URL.Path, req.Method; { + case p == pathRC && m == "GET": + bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil + case p == pathRC && m == "PATCH": + validatePatchApplication(t, req) + bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil + } + }), + } + tf.OpenAPISchemaFunc = fn + tf.Namespace = "test" + buf := bytes.NewBuffer([]byte{}) + errBuf := bytes.NewBuffer([]byte{}) - cmd := NewCmdApply("kubectl", f, buf, errBuf) - cmd.Flags().Set("filename", filenameRC) - cmd.Flags().Set("output", "name") - cmd.Run(cmd, []string{}) + cmd := NewCmdApply("kubectl", f, buf, errBuf) + cmd.Flags().Set("filename", filenameRC) + cmd.Flags().Set("output", "name") + cmd.Run(cmd, []string{}) - // uses the name from the file, not the response - expectRC := "replicationcontroller/" + nameRC + "\n" - if buf.String() != expectRC { - t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expectRC) + // uses the name from the file, not the response + expectRC := "replicationcontroller/" + nameRC + "\n" + if buf.String() != expectRC { + t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expectRC) + } + if errBuf.String() != "" { + t.Fatalf("unexpected error output: %s", errBuf.String()) + } } } @@ -456,40 +492,45 @@ func TestApplyObjectOutput(t *testing.T) { t.Fatal(err) } - f, tf, _, _ := cmdtesting.NewAPIFactory() - tf.Printer = &printers.YAMLPrinter{} - tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, - NegotiatedSerializer: unstructuredSerializer, - Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { - switch p, m := req.URL.Path, req.Method; { - case p == pathRC && m == "GET": - bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil - case p == pathRC && m == "PATCH": - validatePatchApplication(t, req) - bodyRC := ioutil.NopCloser(bytes.NewReader(postPatchData)) - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil - default: - t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) - return nil, nil - } - }), - } - tf.Namespace = "test" - buf := bytes.NewBuffer([]byte{}) - errBuf := bytes.NewBuffer([]byte{}) + for _, fn := range testingOpenAPISchemaFns { + f, tf, _, _ := cmdtesting.NewAPIFactory() + tf.Printer = &printers.YAMLPrinter{} + tf.UnstructuredClient = &fake.RESTClient{ + NegotiatedSerializer: unstructuredSerializer, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + switch p, m := req.URL.Path, req.Method; { + case p == pathRC && m == "GET": + bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil + case p == pathRC && m == "PATCH": + validatePatchApplication(t, req) + bodyRC := ioutil.NopCloser(bytes.NewReader(postPatchData)) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil + } + }), + } + tf.OpenAPISchemaFunc = fn + tf.Namespace = "test" + buf := bytes.NewBuffer([]byte{}) + errBuf := bytes.NewBuffer([]byte{}) - cmd := NewCmdApply("kubectl", f, buf, errBuf) - cmd.Flags().Set("filename", filenameRC) - cmd.Flags().Set("output", "yaml") - cmd.Run(cmd, []string{}) + cmd := NewCmdApply("kubectl", f, buf, errBuf) + cmd.Flags().Set("filename", filenameRC) + cmd.Flags().Set("output", "yaml") + cmd.Run(cmd, []string{}) - if !strings.Contains(buf.String(), "name: test-rc") { - t.Fatalf("unexpected output: %s\nexpected to contain: %s", buf.String(), "name: test-rc") - } - if !strings.Contains(buf.String(), "post-patch: value") { - t.Fatalf("unexpected output: %s\nexpected to contain: %s", buf.String(), "post-patch: value") + if !strings.Contains(buf.String(), "name: test-rc") { + t.Fatalf("unexpected output: %s\nexpected to contain: %s", buf.String(), "name: test-rc") + } + if !strings.Contains(buf.String(), "post-patch: value") { + t.Fatalf("unexpected output: %s\nexpected to contain: %s", buf.String(), "post-patch: value") + } + if errBuf.String() != "" { + t.Fatalf("unexpected error output: %s", errBuf.String()) + } } } @@ -498,55 +539,60 @@ func TestApplyRetry(t *testing.T) { nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC) pathRC := "/namespaces/test/replicationcontrollers/" + nameRC - firstPatch := true - retry := false - getCount := 0 - f, tf, _, _ := cmdtesting.NewAPIFactory() - tf.Printer = &testPrinter{} - tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, - NegotiatedSerializer: unstructuredSerializer, - Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { - switch p, m := req.URL.Path, req.Method; { - case p == pathRC && m == "GET": - getCount++ - bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil - case p == pathRC && m == "PATCH": - if firstPatch { - firstPatch = false - statusErr := kubeerr.NewConflict(schema.GroupResource{Group: "", Resource: "rc"}, "test-rc", fmt.Errorf("the object has been modified. Please apply at first.")) - bodyBytes, _ := json.Marshal(statusErr) - bodyErr := ioutil.NopCloser(bytes.NewReader(bodyBytes)) - return &http.Response{StatusCode: http.StatusConflict, Header: defaultHeader(), Body: bodyErr}, nil + for _, fn := range testingOpenAPISchemaFns { + firstPatch := true + retry := false + getCount := 0 + f, tf, _, _ := cmdtesting.NewAPIFactory() + tf.Printer = &testPrinter{} + tf.UnstructuredClient = &fake.RESTClient{ + NegotiatedSerializer: unstructuredSerializer, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + switch p, m := req.URL.Path, req.Method; { + case p == pathRC && m == "GET": + getCount++ + bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil + case p == pathRC && m == "PATCH": + if firstPatch { + firstPatch = false + statusErr := kubeerr.NewConflict(schema.GroupResource{Group: "", Resource: "rc"}, "test-rc", fmt.Errorf("the object has been modified. Please apply at first.")) + bodyBytes, _ := json.Marshal(statusErr) + bodyErr := ioutil.NopCloser(bytes.NewReader(bodyBytes)) + return &http.Response{StatusCode: http.StatusConflict, Header: defaultHeader(), Body: bodyErr}, nil + } + retry = true + validatePatchApplication(t, req) + bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil } - retry = true - validatePatchApplication(t, req) - bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil - default: - t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) - return nil, nil - } - }), - } - tf.Namespace = "test" - buf := bytes.NewBuffer([]byte{}) - errBuf := bytes.NewBuffer([]byte{}) + }), + } + tf.OpenAPISchemaFunc = fn + tf.Namespace = "test" + buf := bytes.NewBuffer([]byte{}) + errBuf := bytes.NewBuffer([]byte{}) - cmd := NewCmdApply("kubectl", f, buf, errBuf) - cmd.Flags().Set("filename", filenameRC) - cmd.Flags().Set("output", "name") - cmd.Run(cmd, []string{}) + cmd := NewCmdApply("kubectl", f, buf, errBuf) + cmd.Flags().Set("filename", filenameRC) + cmd.Flags().Set("output", "name") + cmd.Run(cmd, []string{}) - if !retry || getCount != 2 { - t.Fatalf("apply didn't retry when get conflict error") - } + if !retry || getCount != 2 { + t.Fatalf("apply didn't retry when get conflict error") + } - // uses the name from the file, not the response - expectRC := "replicationcontroller/" + nameRC + "\n" - if buf.String() != expectRC { - t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expectRC) + // uses the name from the file, not the response + expectRC := "replicationcontroller/" + nameRC + "\n" + if buf.String() != expectRC { + t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expectRC) + } + if errBuf.String() != "" { + t.Fatalf("unexpected error output: %s", errBuf.String()) + } } } @@ -558,7 +604,6 @@ func TestApplyNonExistObject(t *testing.T) { f, tf, _, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -591,6 +636,75 @@ func TestApplyNonExistObject(t *testing.T) { } } +func TestApplyEmptyPatch(t *testing.T) { + initTestErrorHandler(t) + nameRC, _ := readAndAnnotateReplicationController(t, filenameRC) + pathRC := "/namespaces/test/replicationcontrollers" + pathNameRC := pathRC + "/" + nameRC + + verifyPost := false + + var body []byte + + f, tf, _, _ := cmdtesting.NewAPIFactory() + tf.Printer = &testPrinter{} + tf.UnstructuredClient = &fake.RESTClient{ + GroupVersion: schema.GroupVersion{Version: "v1"}, + NegotiatedSerializer: unstructuredSerializer, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + switch p, m := req.URL.Path, req.Method; { + case p == "/api/v1/namespaces/test" && m == "GET": + return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: ioutil.NopCloser(bytes.NewReader(nil))}, nil + case p == pathNameRC && m == "GET": + if body == nil { + return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: ioutil.NopCloser(bytes.NewReader(nil))}, nil + } + bodyRC := ioutil.NopCloser(bytes.NewReader(body)) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil + case p == pathRC && m == "POST": + body, _ = ioutil.ReadAll(req.Body) + verifyPost = true + bodyRC := ioutil.NopCloser(bytes.NewReader(body)) + return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: bodyRC}, nil + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil + } + }), + } + tf.Namespace = "test" + + // 1. apply non exist object + buf := bytes.NewBuffer([]byte{}) + errBuf := bytes.NewBuffer([]byte{}) + + cmd := NewCmdApply("kubectl", f, buf, errBuf) + cmd.Flags().Set("filename", filenameRC) + cmd.Flags().Set("output", "name") + cmd.Run(cmd, []string{}) + + expectRC := "replicationcontroller/" + nameRC + "\n" + if buf.String() != expectRC { + t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expectRC) + } + if !verifyPost { + t.Fatal("No server-side post call detected") + } + + // 2. test apply already exist object, will not send empty patch request + buf = bytes.NewBuffer([]byte{}) + errBuf = bytes.NewBuffer([]byte{}) + + cmd = NewCmdApply("kubectl", f, buf, errBuf) + cmd.Flags().Set("filename", filenameRC) + cmd.Flags().Set("output", "name") + cmd.Run(cmd, []string{}) + + if buf.String() != expectRC { + t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expectRC) + } +} + func TestApplyMultipleObjectsAsList(t *testing.T) { testApplyMultipleObjects(t, true) } @@ -606,56 +720,61 @@ func testApplyMultipleObjects(t *testing.T, asList bool) { nameSVC, currentSVC := readAndAnnotateService(t, filenameSVC) pathSVC := "/namespaces/test/services/" + nameSVC - f, tf, _, _ := cmdtesting.NewAPIFactory() - tf.Printer = &testPrinter{} - tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, - NegotiatedSerializer: unstructuredSerializer, - Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { - switch p, m := req.URL.Path, req.Method; { - case p == pathRC && m == "GET": - bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil - case p == pathRC && m == "PATCH": - validatePatchApplication(t, req) - bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil - case p == pathSVC && m == "GET": - bodySVC := ioutil.NopCloser(bytes.NewReader(currentSVC)) - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodySVC}, nil - case p == pathSVC && m == "PATCH": - validatePatchApplication(t, req) - bodySVC := ioutil.NopCloser(bytes.NewReader(currentSVC)) - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodySVC}, nil - default: - t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) - return nil, nil - } - }), - } - tf.Namespace = "test" - buf := bytes.NewBuffer([]byte{}) - errBuf := bytes.NewBuffer([]byte{}) + for _, fn := range testingOpenAPISchemaFns { + f, tf, _, _ := cmdtesting.NewAPIFactory() + tf.Printer = &testPrinter{} + tf.UnstructuredClient = &fake.RESTClient{ + NegotiatedSerializer: unstructuredSerializer, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + switch p, m := req.URL.Path, req.Method; { + case p == pathRC && m == "GET": + bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil + case p == pathRC && m == "PATCH": + validatePatchApplication(t, req) + bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil + case p == pathSVC && m == "GET": + bodySVC := ioutil.NopCloser(bytes.NewReader(currentSVC)) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodySVC}, nil + case p == pathSVC && m == "PATCH": + validatePatchApplication(t, req) + bodySVC := ioutil.NopCloser(bytes.NewReader(currentSVC)) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodySVC}, nil + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil + } + }), + } + tf.OpenAPISchemaFunc = fn + tf.Namespace = "test" + buf := bytes.NewBuffer([]byte{}) + errBuf := bytes.NewBuffer([]byte{}) - cmd := NewCmdApply("kubectl", f, buf, errBuf) - if asList { - cmd.Flags().Set("filename", filenameRCSVC) - } else { - cmd.Flags().Set("filename", filenameRC) - cmd.Flags().Set("filename", filenameSVC) - } - cmd.Flags().Set("output", "name") + cmd := NewCmdApply("kubectl", f, buf, errBuf) + if asList { + cmd.Flags().Set("filename", filenameRCSVC) + } else { + cmd.Flags().Set("filename", filenameRC) + cmd.Flags().Set("filename", filenameSVC) + } + cmd.Flags().Set("output", "name") - cmd.Run(cmd, []string{}) + cmd.Run(cmd, []string{}) - // Names should come from the REST response, NOT the files - expectRC := "replicationcontroller/" + nameRC + "\n" - expectSVC := "service/" + nameSVC + "\n" - // Test both possible orders since output is non-deterministic. - expectOne := expectRC + expectSVC - expectTwo := expectSVC + expectRC - if buf.String() != expectOne && buf.String() != expectTwo { - t.Fatalf("unexpected output: %s\nexpected: %s OR %s", buf.String(), expectOne, expectTwo) + // Names should come from the REST response, NOT the files + expectRC := "replicationcontroller/" + nameRC + "\n" + expectSVC := "service/" + nameSVC + "\n" + // Test both possible orders since output is non-deterministic. + expectOne := expectRC + expectSVC + expectTwo := expectSVC + expectRC + if buf.String() != expectOne && buf.String() != expectTwo { + t.Fatalf("unexpected output: %s\nexpected: %s OR %s", buf.String(), expectOne, expectTwo) + } + if errBuf.String() != "" { + t.Fatalf("unexpected error output: %s", errBuf.String()) + } } } @@ -685,63 +804,67 @@ func TestApplyNULLPreservation(t *testing.T) { verifiedPatch := false deploymentBytes := readDeploymentFromFile(t, filenameDeployObjServerside) - f, tf, _, _ := cmdtesting.NewTestFactory() - tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, - NegotiatedSerializer: unstructuredSerializer, - Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { - switch p, m := req.URL.Path, req.Method; { - case p == deploymentPath && m == "GET": - body := ioutil.NopCloser(bytes.NewReader(deploymentBytes)) - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil - case p == deploymentPath && m == "PATCH": - patch, err := ioutil.ReadAll(req.Body) - if err != nil { - t.Fatal(err) + for _, fn := range testingOpenAPISchemaFns { + f, tf, _, _ := cmdtesting.NewAPIFactory() + tf.UnstructuredClient = &fake.RESTClient{ + NegotiatedSerializer: unstructuredSerializer, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + switch p, m := req.URL.Path, req.Method; { + case p == deploymentPath && m == "GET": + body := ioutil.NopCloser(bytes.NewReader(deploymentBytes)) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil + case p == deploymentPath && m == "PATCH": + patch, err := ioutil.ReadAll(req.Body) + if err != nil { + t.Fatal(err) + } + + patchMap := map[string]interface{}{} + if err := json.Unmarshal(patch, &patchMap); err != nil { + t.Fatal(err) + } + annotationMap := walkMapPath(t, patchMap, []string{"metadata", "annotations"}) + if _, ok := annotationMap[api.LastAppliedConfigAnnotation]; !ok { + t.Fatalf("patch does not contain annotation:\n%s\n", patch) + } + strategy := walkMapPath(t, patchMap, []string{"spec", "strategy"}) + if value, ok := strategy["rollingUpdate"]; !ok || value != nil { + t.Fatalf("patch did not retain null value in key: rollingUpdate:\n%s\n", patch) + } + verifiedPatch = true + + // The real API server would had returned the patched object but Kubectl + // is ignoring the actual return object. + // TODO: Make this match actual server behavior by returning the patched object. + body := ioutil.NopCloser(bytes.NewReader(deploymentBytes)) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil } + }), + } + tf.OpenAPISchemaFunc = fn + tf.Namespace = "test" + buf := bytes.NewBuffer([]byte{}) + errBuf := bytes.NewBuffer([]byte{}) - patchMap := map[string]interface{}{} - if err := json.Unmarshal(patch, &patchMap); err != nil { - t.Fatal(err) - } - annotationMap := walkMapPath(t, patchMap, []string{"metadata", "annotations"}) - if _, ok := annotationMap[api.LastAppliedConfigAnnotation]; !ok { - t.Fatalf("patch does not contain annotation:\n%s\n", patch) - } - strategy := walkMapPath(t, patchMap, []string{"spec", "strategy"}) - if value, ok := strategy["rollingUpdate"]; !ok || value != nil { - t.Fatalf("patch did not retain null value in key: rollingUpdate:\n%s\n", patch) - } - verifiedPatch = true + cmd := NewCmdApply("kubectl", f, buf, errBuf) + cmd.Flags().Set("filename", filenameDeployObjClientside) + cmd.Flags().Set("output", "name") - // The real API server would had returned the patched object but Kubectl - // is ignoring the actual return object. - // TODO: Make this match actual server behavior by returning the patched object. - body := ioutil.NopCloser(bytes.NewReader(deploymentBytes)) - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil - default: - t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) - return nil, nil - } - }), - } - tf.Namespace = "test" - tf.ClientConfig = defaultClientConfig() - buf := bytes.NewBuffer([]byte{}) - errBuf := bytes.NewBuffer([]byte{}) + cmd.Run(cmd, []string{}) - cmd := NewCmdApply("kubectl", f, buf, errBuf) - cmd.Flags().Set("filename", filenameDeployObjClientside) - cmd.Flags().Set("output", "name") - - cmd.Run(cmd, []string{}) - - expected := "deployment/" + deploymentName + "\n" - if buf.String() != expected { - t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expected) - } - if !verifiedPatch { - t.Fatal("No server-side patch call detected") + expected := "deployment/" + deploymentName + "\n" + if buf.String() != expected { + t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expected) + } + if errBuf.String() != "" { + t.Fatalf("unexpected error output: %s", errBuf.String()) + } + if !verifiedPatch { + t.Fatal("No server-side patch call detected") + } } } @@ -753,54 +876,58 @@ func TestUnstructuredApply(t *testing.T) { verifiedPatch := false - f, tf, _, _ := cmdtesting.NewAPIFactory() - tf.Printer = &testPrinter{} - tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, - NegotiatedSerializer: unstructuredSerializer, - Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { - switch p, m := req.URL.Path, req.Method; { - case p == path && m == "GET": - body := ioutil.NopCloser(bytes.NewReader(curr)) - return &http.Response{ - StatusCode: 200, - Header: defaultHeader(), - Body: body}, nil - case p == path && m == "PATCH": - contentType := req.Header.Get("Content-Type") - if contentType != "application/merge-patch+json" { - t.Fatalf("Unexpected Content-Type: %s", contentType) + for _, fn := range testingOpenAPISchemaFns { + f, tf, _, _ := cmdtesting.NewAPIFactory() + tf.Printer = &testPrinter{} + tf.UnstructuredClient = &fake.RESTClient{ + NegotiatedSerializer: unstructuredSerializer, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + switch p, m := req.URL.Path, req.Method; { + case p == path && m == "GET": + body := ioutil.NopCloser(bytes.NewReader(curr)) + return &http.Response{ + StatusCode: 200, + Header: defaultHeader(), + Body: body}, nil + case p == path && m == "PATCH": + contentType := req.Header.Get("Content-Type") + if contentType != "application/merge-patch+json" { + t.Fatalf("Unexpected Content-Type: %s", contentType) + } + validatePatchApplication(t, req) + verifiedPatch = true + + body := ioutil.NopCloser(bytes.NewReader(curr)) + return &http.Response{ + StatusCode: 200, + Header: defaultHeader(), + Body: body}, nil + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil } - validatePatchApplication(t, req) - verifiedPatch = true + }), + } + tf.OpenAPISchemaFunc = fn + tf.Namespace = "test" + buf := bytes.NewBuffer([]byte{}) + errBuf := bytes.NewBuffer([]byte{}) - body := ioutil.NopCloser(bytes.NewReader(curr)) - return &http.Response{ - StatusCode: 200, - Header: defaultHeader(), - Body: body}, nil - default: - t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) - return nil, nil - } - }), - } + cmd := NewCmdApply("kubectl", f, buf, errBuf) + cmd.Flags().Set("filename", filenameWidgetClientside) + cmd.Flags().Set("output", "name") + cmd.Run(cmd, []string{}) - tf.Namespace = "test" - buf := bytes.NewBuffer([]byte{}) - errBuf := bytes.NewBuffer([]byte{}) - - cmd := NewCmdApply("kubectl", f, buf, errBuf) - cmd.Flags().Set("filename", filenameWidgetClientside) - cmd.Flags().Set("output", "name") - cmd.Run(cmd, []string{}) - - expected := "widget/" + name + "\n" - if buf.String() != expected { - t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expected) - } - if !verifiedPatch { - t.Fatal("No server-side patch call detected") + expected := "widget/" + name + "\n" + if buf.String() != expected { + t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expected) + } + if errBuf.String() != "" { + t.Fatalf("unexpected error output: %s", errBuf.String()) + } + if !verifiedPatch { + t.Fatal("No server-side patch call detected") + } } } @@ -817,77 +944,81 @@ func TestUnstructuredIdempotentApply(t *testing.T) { verifiedPatch := false - f, tf, _, _ := cmdtesting.NewAPIFactory() - tf.Printer = &testPrinter{} - tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, - NegotiatedSerializer: unstructuredSerializer, - Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { - switch p, m := req.URL.Path, req.Method; { - case p == path && m == "GET": - body := ioutil.NopCloser(bytes.NewReader(serversideData)) - return &http.Response{ - StatusCode: 200, - Header: defaultHeader(), - Body: body}, nil - case p == path && m == "PATCH": - // In idempotent updates, kubectl sends a logically empty - // request body with the PATCH request. - // Should look like this: - // Request Body: {"metadata":{"annotations":{}}} + for _, fn := range testingOpenAPISchemaFns { + f, tf, _, _ := cmdtesting.NewAPIFactory() + tf.Printer = &testPrinter{} + tf.UnstructuredClient = &fake.RESTClient{ + NegotiatedSerializer: unstructuredSerializer, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + switch p, m := req.URL.Path, req.Method; { + case p == path && m == "GET": + body := ioutil.NopCloser(bytes.NewReader(serversideData)) + return &http.Response{ + StatusCode: 200, + Header: defaultHeader(), + Body: body}, nil + case p == path && m == "PATCH": + // In idempotent updates, kubectl sends a logically empty + // request body with the PATCH request. + // Should look like this: + // Request Body: {"metadata":{"annotations":{}}} - patch, err := ioutil.ReadAll(req.Body) - if err != nil { - t.Fatal(err) + patch, err := ioutil.ReadAll(req.Body) + if err != nil { + t.Fatal(err) + } + + contentType := req.Header.Get("Content-Type") + if contentType != "application/merge-patch+json" { + t.Fatalf("Unexpected Content-Type: %s", contentType) + } + + patchMap := map[string]interface{}{} + if err := json.Unmarshal(patch, &patchMap); err != nil { + t.Fatal(err) + } + if len(patchMap) != 1 { + t.Fatalf("Unexpected Patch. Has more than 1 entry. path: %s", patch) + } + + annotationsMap := walkMapPath(t, patchMap, []string{"metadata", "annotations"}) + if len(annotationsMap) != 0 { + t.Fatalf("Unexpected Patch. Found unexpected annotation: %s", patch) + } + + verifiedPatch = true + + body := ioutil.NopCloser(bytes.NewReader(serversideData)) + return &http.Response{ + StatusCode: 200, + Header: defaultHeader(), + Body: body}, nil + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil } + }), + } + tf.OpenAPISchemaFunc = fn + tf.Namespace = "test" + buf := bytes.NewBuffer([]byte{}) + errBuf := bytes.NewBuffer([]byte{}) - contentType := req.Header.Get("Content-Type") - if contentType != "application/merge-patch+json" { - t.Fatalf("Unexpected Content-Type: %s", contentType) - } + cmd := NewCmdApply("kubectl", f, buf, errBuf) + cmd.Flags().Set("filename", filenameWidgetClientside) + cmd.Flags().Set("output", "name") + cmd.Run(cmd, []string{}) - patchMap := map[string]interface{}{} - if err := json.Unmarshal(patch, &patchMap); err != nil { - t.Fatal(err) - } - if len(patchMap) != 1 { - t.Fatalf("Unexpected Patch. Has more than 1 entry. path: %s", patch) - } - - annotationsMap := walkMapPath(t, patchMap, []string{"metadata", "annotations"}) - if len(annotationsMap) != 0 { - t.Fatalf("Unexpected Patch. Found unexpected annotation: %s", patch) - } - - verifiedPatch = true - - body := ioutil.NopCloser(bytes.NewReader(serversideData)) - return &http.Response{ - StatusCode: 200, - Header: defaultHeader(), - Body: body}, nil - default: - t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) - return nil, nil - } - }), - } - - tf.Namespace = "test" - buf := bytes.NewBuffer([]byte{}) - errBuf := bytes.NewBuffer([]byte{}) - - cmd := NewCmdApply("kubectl", f, buf, errBuf) - cmd.Flags().Set("filename", filenameWidgetClientside) - cmd.Flags().Set("output", "name") - cmd.Run(cmd, []string{}) - - expected := "widget/widget\n" - if buf.String() != expected { - t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expected) - } - if !verifiedPatch { - t.Fatal("No server-side patch call detected") + expected := "widget/widget\n" + if buf.String() != expected { + t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expected) + } + if errBuf.String() != "" { + t.Fatalf("unexpected error output: %s", errBuf.String()) + } + if !verifiedPatch { + t.Fatal("No server-side patch call detected") + } } } @@ -942,51 +1073,53 @@ func TestRunApplySetLastApplied(t *testing.T) { }, } for _, test := range tests { - f, tf, codec, _ := cmdtesting.NewAPIFactory() - tf.Printer = &testPrinter{} - tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, - NegotiatedSerializer: unstructuredSerializer, - Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { - switch p, m := req.URL.Path, req.Method; { - case p == pathRC && m == "GET": - bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil - case p == noAnnotationPath && m == "GET": - bodyRC := ioutil.NopCloser(bytes.NewReader(noAnnotationRC)) - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil - case p == noExistPath && m == "GET": - return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: objBody(codec, &api.Pod{})}, nil - case p == pathRC && m == "PATCH": - checkPatchString(t, req) - bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil - case p == "/api/v1/namespaces/test" && m == "GET": - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &api.Namespace{})}, nil - default: - t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) - return nil, nil - } - }), - } - tf.Namespace = "test" - tf.ClientConfig = defaultClientConfig() - buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) + t.Run(test.name, func(t *testing.T) { + f, tf, codec, _ := cmdtesting.NewAPIFactory() + tf.Printer = &testPrinter{} + tf.UnstructuredClient = &fake.RESTClient{ + GroupVersion: schema.GroupVersion{Version: "v1"}, + NegotiatedSerializer: unstructuredSerializer, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + switch p, m := req.URL.Path, req.Method; { + case p == pathRC && m == "GET": + bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil + case p == noAnnotationPath && m == "GET": + bodyRC := ioutil.NopCloser(bytes.NewReader(noAnnotationRC)) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil + case p == noExistPath && m == "GET": + return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: objBody(codec, &api.Pod{})}, nil + case p == pathRC && m == "PATCH": + checkPatchString(t, req) + bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil + case p == "/api/v1/namespaces/test" && m == "GET": + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &api.Namespace{})}, nil + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil + } + }), + } + tf.Namespace = "test" + tf.ClientConfig = defaultClientConfig() + buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) - cmdutil.BehaviorOnFatal(func(str string, code int) { - if str != test.expectedErr { - t.Errorf("%s: unexpected error: %s\nexpected: %s", test.name, str, test.expectedErr) + cmdutil.BehaviorOnFatal(func(str string, code int) { + if str != test.expectedErr { + t.Errorf("%s: unexpected error: %s\nexpected: %s", test.name, str, test.expectedErr) + } + }) + + cmd := NewCmdApplySetLastApplied(f, buf, errBuf) + cmd.Flags().Set("filename", test.filePath) + cmd.Flags().Set("output", test.output) + cmd.Run(cmd, []string{}) + + if buf.String() != test.expectedOut { + t.Fatalf("%s: unexpected output: %s\nexpected: %s", test.name, buf.String(), test.expectedOut) } }) - - cmd := NewCmdApplySetLastApplied(f, buf, errBuf) - cmd.Flags().Set("filename", test.filePath) - cmd.Flags().Set("output", test.output) - cmd.Run(cmd, []string{}) - - if buf.String() != test.expectedOut { - t.Fatalf("%s: unexpected output: %s\nexpected: %s", test.name, buf.String(), test.expectedOut) - } } cmdutil.BehaviorOnFatal(func(str string, code int) {}) } @@ -1019,8 +1152,6 @@ func TestForceApply(t *testing.T) { nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC) pathRC := "/namespaces/test/replicationcontrollers/" + nameRC pathRCList := "/namespaces/test/replicationcontrollers" - deleted := false - counts := map[string]int{} expected := map[string]int{ "getOk": 9, "getNotFound": 1, @@ -1031,85 +1162,92 @@ func TestForceApply(t *testing.T) { "post": 1, } - f, tf, _, _ := cmdtesting.NewAPIFactory() - tf.Printer = &testPrinter{} - tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, - NegotiatedSerializer: unstructuredSerializer, - Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { - switch p, m := req.URL.Path, req.Method; { - case strings.HasSuffix(p, pathRC) && m == "GET": - if deleted { - counts["getNotFound"]++ - return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte{}))}, nil + for _, fn := range testingOpenAPISchemaFns { + deleted := false + counts := map[string]int{} + f, tf, _, _ := cmdtesting.NewAPIFactory() + tf.Printer = &testPrinter{} + tf.UnstructuredClient = &fake.RESTClient{ + NegotiatedSerializer: unstructuredSerializer, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + switch p, m := req.URL.Path, req.Method; { + case strings.HasSuffix(p, pathRC) && m == "GET": + if deleted { + counts["getNotFound"]++ + return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte{}))}, nil + } + counts["getOk"]++ + bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil + case strings.HasSuffix(p, pathRCList) && m == "GET": + counts["getList"]++ + rcObj := readUnstructuredFromFile(t, filenameRC) + list := &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ReplicationControllerList", + }, + Items: []unstructured.Unstructured{*rcObj}, + } + listBytes, err := runtime.Encode(testapi.Default.Codec(), list) + if err != nil { + t.Fatal(err) + } + bodyRCList := ioutil.NopCloser(bytes.NewReader(listBytes)) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRCList}, nil + case strings.HasSuffix(p, pathRC) && m == "PATCH": + counts["patch"]++ + if counts["patch"] <= 6 { + statusErr := kubeerr.NewConflict(schema.GroupResource{Group: "", Resource: "rc"}, "test-rc", fmt.Errorf("the object has been modified. Please apply at first.")) + bodyBytes, _ := json.Marshal(statusErr) + bodyErr := ioutil.NopCloser(bytes.NewReader(bodyBytes)) + return &http.Response{StatusCode: http.StatusConflict, Header: defaultHeader(), Body: bodyErr}, nil + } + t.Fatalf("unexpected request: %#v after %v tries\n%#v", req.URL, counts["patch"], req) + return nil, nil + case strings.HasSuffix(p, pathRC) && m == "DELETE": + counts["delete"]++ + deleted = true + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte{}))}, nil + case strings.HasSuffix(p, pathRC) && m == "PUT": + counts["put"]++ + bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil + case strings.HasSuffix(p, pathRCList) && m == "POST": + counts["post"]++ + deleted = false + bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil } - counts["getOk"]++ - bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil - case strings.HasSuffix(p, pathRCList) && m == "GET": - counts["getList"]++ - rcObj := readUnstructuredFromFile(t, filenameRC) - list := &unstructured.UnstructuredList{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "ReplicationControllerList", - }, - Items: []unstructured.Unstructured{*rcObj}, - } - listBytes, err := runtime.Encode(testapi.Default.Codec(), list) - if err != nil { - t.Fatal(err) - } - bodyRCList := ioutil.NopCloser(bytes.NewReader(listBytes)) - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRCList}, nil - case strings.HasSuffix(p, pathRC) && m == "PATCH": - counts["patch"]++ - if counts["patch"] <= 6 { - statusErr := kubeerr.NewConflict(schema.GroupResource{Group: "", Resource: "rc"}, "test-rc", fmt.Errorf("the object has been modified. Please apply at first.")) - bodyBytes, _ := json.Marshal(statusErr) - bodyErr := ioutil.NopCloser(bytes.NewReader(bodyBytes)) - return &http.Response{StatusCode: http.StatusConflict, Header: defaultHeader(), Body: bodyErr}, nil - } - t.Fatalf("unexpected request: %#v after %v tries\n%#v", req.URL, counts["patch"], req) - return nil, nil - case strings.HasSuffix(p, pathRC) && m == "DELETE": - counts["delete"]++ - deleted = true - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte{}))}, nil - case strings.HasSuffix(p, pathRC) && m == "PUT": - counts["put"]++ - bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil - case strings.HasSuffix(p, pathRCList) && m == "POST": - counts["post"]++ - deleted = false - bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil - default: - t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) - return nil, nil + }), + } + tf.OpenAPISchemaFunc = fn + tf.Client = tf.UnstructuredClient + tf.ClientConfig = &restclient.Config{} + tf.Namespace = "test" + buf := bytes.NewBuffer([]byte{}) + errBuf := bytes.NewBuffer([]byte{}) + + cmd := NewCmdApply("kubectl", f, buf, errBuf) + cmd.Flags().Set("filename", filenameRC) + cmd.Flags().Set("output", "name") + cmd.Flags().Set("force", "true") + cmd.Run(cmd, []string{}) + + for method, exp := range expected { + if exp != counts[method] { + t.Errorf("Unexpected amount of %q API calls, wanted %v got %v", method, exp, counts[method]) } - }), - } - tf.Client = tf.UnstructuredClient - tf.ClientConfig = &restclient.Config{} - tf.Namespace = "test" - buf := bytes.NewBuffer([]byte{}) - errBuf := bytes.NewBuffer([]byte{}) + } - cmd := NewCmdApply("kubectl", f, buf, errBuf) - cmd.Flags().Set("filename", filenameRC) - cmd.Flags().Set("output", "name") - cmd.Flags().Set("force", "true") - cmd.Run(cmd, []string{}) - - for method, exp := range expected { - if exp != counts[method] { - t.Errorf("Unexpected amount of %q API calls, wanted %v got %v", method, exp, counts[method]) + if expected := "replicationcontroller/" + nameRC + "\n"; buf.String() != expected { + t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expected) + } + if errBuf.String() != "" { + t.Fatalf("unexpected error output: %s", errBuf.String()) } } - - if expected := "replicationcontroller/" + nameRC + "\n"; buf.String() != expected { - t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expected) - } } diff --git a/pkg/kubectl/cmd/apply_view_last_applied.go b/pkg/kubectl/cmd/apply_view_last_applied.go index 4c7cec8214d..7282ae6265f 100644 --- a/pkg/kubectl/cmd/apply_view_last_applied.go +++ b/pkg/kubectl/cmd/apply_view_last_applied.go @@ -87,18 +87,13 @@ func (o *ViewLastAppliedOptions) Complete(f cmdutil.Factory, args []string) erro return err } - mapper, typer, err := f.UnstructuredObject() - if err != nil { - return err - } - r := f.NewBuilder(). - Unstructured(f.UnstructuredClientForMapping, mapper, typer). + Unstructured(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &o.FilenameOptions). ResourceTypeOrNameArgs(enforceNamespace, args...). SelectAllParam(o.All). - SelectorParam(o.Selector). + LabelSelectorParam(o.Selector). Latest(). Flatten(). Do() diff --git a/pkg/kubectl/cmd/attach.go b/pkg/kubectl/cmd/attach.go index 00861784910..7bc49ca06da 100644 --- a/pkg/kubectl/cmd/attach.go +++ b/pkg/kubectl/cmd/attach.go @@ -30,7 +30,8 @@ import ( utilerrors "k8s.io/apimachinery/pkg/util/errors" restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/remotecommand" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -143,6 +144,7 @@ func (p *AttachOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn [ } builder := f.NewBuilder(). + Internal(). NamespaceParam(namespace).DefaultNamespace() switch len(argsIn) { @@ -257,11 +259,6 @@ func (p *AttachOptions) Run() error { } fn := func() error { - - if !p.Quiet && stderr != nil { - fmt.Fprintln(stderr, "If you don't see a command prompt, try pressing enter.") - } - restClient, err := restclient.RESTClientFor(p.Config) if err != nil { return err @@ -278,11 +275,14 @@ func (p *AttachOptions) Run() error { Stdout: p.Out != nil, Stderr: p.Err != nil, TTY: t.Raw, - }, api.ParameterCodec) + }, legacyscheme.ParameterCodec) return p.Attach.Attach("POST", req.URL(), p.Config, p.In, p.Out, p.Err, t.Raw, sizeQueue) } + if !p.Quiet && stderr != nil { + fmt.Fprintln(stderr, "If you don't see a command prompt, try pressing enter.") + } if err := t.Safe(fn); err != nil { return err } diff --git a/pkg/kubectl/cmd/attach_test.go b/pkg/kubectl/cmd/attach_test.go index bbe04e26fd6..42801a9b257 100644 --- a/pkg/kubectl/cmd/attach_test.go +++ b/pkg/kubectl/cmd/attach_test.go @@ -34,7 +34,8 @@ import ( restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" "k8s.io/client-go/tools/remotecommand" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) @@ -139,7 +140,7 @@ func TestPodAndContainerAttach(t *testing.T) { for _, test := range tests { f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { if test.obj != nil { @@ -175,7 +176,7 @@ func TestPodAndContainerAttach(t *testing.T) { } func TestAttach(t *testing.T) { - version := api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version + version := legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.Version tests := []struct { name, version, podPath, fetchPodPath, attachPath, container string pod *api.Pod @@ -216,7 +217,7 @@ func TestAttach(t *testing.T) { for _, test := range tests { f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -234,7 +235,7 @@ func TestAttach(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: api.Codecs, GroupVersion: &schema.GroupVersion{Version: test.version}}} + tf.ClientConfig = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs, GroupVersion: &schema.GroupVersion{Version: test.version}}} bufOut := bytes.NewBuffer([]byte{}) bufErr := bytes.NewBuffer([]byte{}) bufIn := bytes.NewBuffer([]byte{}) @@ -283,7 +284,7 @@ func TestAttach(t *testing.T) { } func TestAttachWarnings(t *testing.T) { - version := api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version + version := legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.Version tests := []struct { name, container, version, podPath, fetchPodPath, expectedErr, expectedOut string pod *api.Pod @@ -303,7 +304,7 @@ func TestAttachWarnings(t *testing.T) { for _, test := range tests { f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -320,7 +321,7 @@ func TestAttachWarnings(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: api.Codecs, GroupVersion: &schema.GroupVersion{Version: test.version}}} + tf.ClientConfig = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs, GroupVersion: &schema.GroupVersion{Version: test.version}}} bufOut := bytes.NewBuffer([]byte{}) bufErr := bytes.NewBuffer([]byte{}) bufIn := bytes.NewBuffer([]byte{}) diff --git a/pkg/kubectl/cmd/auth/BUILD b/pkg/kubectl/cmd/auth/BUILD index dddcdf6754f..3393ed5aab6 100644 --- a/pkg/kubectl/cmd/auth/BUILD +++ b/pkg/kubectl/cmd/auth/BUILD @@ -50,11 +50,11 @@ filegroup( go_test( name = "go_default_test", srcs = ["cani_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/auth", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", "//pkg/kubectl/cmd/testing:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/rest/fake:go_default_library", ], diff --git a/pkg/kubectl/cmd/auth/cani_test.go b/pkg/kubectl/cmd/auth/cani_test.go index a2210ec87cd..0b4407f6d86 100644 --- a/pkg/kubectl/cmd/auth/cani_test.go +++ b/pkg/kubectl/cmd/auth/cani_test.go @@ -24,9 +24,9 @@ import ( "strings" "testing" + "k8s.io/apimachinery/pkg/runtime/schema" restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" ) @@ -121,7 +121,7 @@ func TestRunAccessCheck(t *testing.T) { f, tf, _, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Group: "", Version: "v1"}, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { expectPath := "/apis/authorization.k8s.io/v1/selfsubjectaccessreviews" @@ -152,7 +152,7 @@ func TestRunAccessCheck(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}} if err := test.o.Complete(f, test.args); err != nil { t.Errorf("%s: %v", test.name, err) diff --git a/pkg/kubectl/cmd/auth/reconcile.go b/pkg/kubectl/cmd/auth/reconcile.go index 948d8f3fac5..0c65c954c38 100644 --- a/pkg/kubectl/cmd/auth/reconcile.go +++ b/pkg/kubectl/cmd/auth/reconcile.go @@ -35,9 +35,9 @@ import ( // ReconcileOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of // referencing the cmd.Flags() type ReconcileOptions struct { - ResourceBuilder *resource.Builder + Visitor resource.Visitor RBACClient internalrbacclient.RbacInterface - CoreClient internalcoreclient.CoreInterface + NamespaceClient internalcoreclient.NamespaceInterface Print func(*resource.Info) error @@ -92,18 +92,26 @@ func (o *ReconcileOptions) Complete(cmd *cobra.Command, f cmdutil.Factory, args if err != nil { return err } - o.ResourceBuilder = f.NewBuilder(). + + r := f.NewBuilder(). + Internal(). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). FilenameParam(enforceNamespace, options). - Flatten() + Flatten(). + Do() + + if err := r.Err(); err != nil { + return err + } + o.Visitor = r client, err := f.ClientSet() if err != nil { return err } o.RBACClient = client.Rbac() - o.CoreClient = client.Core() + o.NamespaceClient = client.Core().Namespaces() mapper, _ := f.Object() dryRun := false @@ -111,9 +119,9 @@ func (o *ReconcileOptions) Complete(cmd *cobra.Command, f cmdutil.Factory, args shortOutput := output == "name" o.Print = func(info *resource.Info) error { if len(output) > 0 && !shortOutput { - return cmdutil.PrintResourceInfoForCommand(cmd, info, f, o.Out) + return f.PrintResourceInfoForCommand(cmd, info, o.Out) } - cmdutil.PrintSuccess(mapper, shortOutput, o.Out, info.Mapping.Resource, info.Name, dryRun, "reconciled") + f.PrintSuccess(mapper, shortOutput, o.Out, info.Mapping.Resource, info.Name, dryRun, "reconciled") return nil } @@ -121,17 +129,29 @@ func (o *ReconcileOptions) Complete(cmd *cobra.Command, f cmdutil.Factory, args } func (o *ReconcileOptions) Validate() error { + if o.Visitor == nil { + return errors.New("ReconcileOptions.Visitor must be set") + } + if o.RBACClient == nil { + return errors.New("ReconcileOptions.RBACClient must be set") + } + if o.NamespaceClient == nil { + return errors.New("ReconcileOptions.NamespaceClient must be set") + } + if o.Print == nil { + return errors.New("ReconcileOptions.Print must be set") + } + if o.Out == nil { + return errors.New("ReconcileOptions.Out must be set") + } + if o.Err == nil { + return errors.New("ReconcileOptions.Err must be set") + } return nil } func (o *ReconcileOptions) RunReconcile() error { - r := o.ResourceBuilder.Do() - err := r.Err() - if err != nil { - return err - } - - err = r.Visit(func(info *resource.Info, err error) error { + return o.Visitor.Visit(func(info *resource.Info, err error) error { if err != nil { return err } @@ -147,7 +167,7 @@ func (o *ReconcileOptions) RunReconcile() error { RemoveExtraPermissions: false, Role: reconciliation.RoleRuleOwner{Role: t}, Client: reconciliation.RoleModifier{ - NamespaceClient: o.CoreClient.Namespaces(), + NamespaceClient: o.NamespaceClient, Client: o.RBACClient, }, } @@ -181,7 +201,7 @@ func (o *ReconcileOptions) RunReconcile() error { RoleBinding: reconciliation.RoleBindingAdapter{RoleBinding: t}, Client: reconciliation.RoleBindingClientAdapter{ Client: o.RBACClient, - NamespaceClient: o.CoreClient.Namespaces(), + NamespaceClient: o.NamespaceClient, }, } result, err := reconcileOptions.Run() @@ -214,6 +234,4 @@ func (o *ReconcileOptions) RunReconcile() error { return nil }) - - return err } diff --git a/pkg/kubectl/cmd/autoscale.go b/pkg/kubectl/cmd/autoscale.go index 27f320d5d1f..30406934fce 100644 --- a/pkg/kubectl/cmd/autoscale.go +++ b/pkg/kubectl/cmd/autoscale.go @@ -38,7 +38,7 @@ var ( An autoscaler can automatically increase or decrease number of pods deployed within the system as needed.`)) autoscaleExample = templates.Examples(i18n.T(` - # Auto scale a deployment "foo", with the number of pods between 2 and 10, target CPU utilization specified so a default autoscaling policy will be used: + # Auto scale a deployment "foo", with the number of pods between 2 and 10, no target CPU utilization specified so a default autoscaling policy will be used: kubectl autoscale deployment foo --min=2 --max=10 # Auto scale a replication controller "foo", with the number of pods between 1 and 5, target CPU utilization at 80%: @@ -64,11 +64,11 @@ func NewCmdAutoscale(f cmdutil.Factory, out io.Writer) *cobra.Command { ArgAliases: argAliases, } cmdutil.AddPrinterFlags(cmd) - cmd.Flags().String("generator", "horizontalpodautoscaler/v1", i18n.T("The name of the API generator to use. Currently there is only 1 generator.")) - cmd.Flags().Int("min", -1, "The lower limit for the number of pods that can be set by the autoscaler. If it's not specified or negative, the server will apply a default value.") - cmd.Flags().Int("max", -1, "The upper limit for the number of pods that can be set by the autoscaler. Required.") + cmd.Flags().String("generator", cmdutil.HorizontalPodAutoscalerV1GeneratorName, i18n.T("The name of the API generator to use. Currently there is only 1 generator.")) + cmd.Flags().Int32("min", -1, "The lower limit for the number of pods that can be set by the autoscaler. If it's not specified or negative, the server will apply a default value.") + cmd.Flags().Int32("max", -1, "The upper limit for the number of pods that can be set by the autoscaler. Required.") cmd.MarkFlagRequired("max") - cmd.Flags().Int("cpu-percent", -1, fmt.Sprintf("The target average CPU utilization (represented as a percent of requested CPU) over all the pods. If it's not specified or negative, a default autoscaling policy will be used.")) + cmd.Flags().Int32("cpu-percent", -1, fmt.Sprintf("The target average CPU utilization (represented as a percent of requested CPU) over all the pods. If it's not specified or negative, a default autoscaling policy will be used.")) cmd.Flags().String("name", "", i18n.T("The name for the newly created object. If not specified, the name of the input resource will be used.")) cmdutil.AddDryRunFlag(cmd) usage := "identifying the resource to autoscale." @@ -91,6 +91,7 @@ func RunAutoscale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s } r := f.NewBuilder(). + Internal(). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). FilenameParam(enforceNamespace, options). @@ -102,15 +103,6 @@ func RunAutoscale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s return err } - // Get the generator, setup and validate all required parameters - generatorName := cmdutil.GetFlagString(cmd, "generator") - generators := f.Generators("autoscale") - generator, found := generators[generatorName] - if !found { - return cmdutil.UsageErrorf(cmd, "generator %q not found.", generatorName) - } - names := generator.ParamNames() - count := 0 err = r.Visit(func(info *resource.Info, err error) error { if err != nil { @@ -122,24 +114,25 @@ func RunAutoscale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s return err } - name := info.Name - params := kubectl.MakeParams(cmd, names) - params["default-name"] = name - - params["scaleRef-kind"] = mapping.GroupVersionKind.Kind - params["scaleRef-name"] = name - params["scaleRef-apiVersion"] = mapping.GroupVersionKind.GroupVersion().String() - - if err = kubectl.ValidateParams(names, params); err != nil { - return err - } - // Check for invalid flags used against the present generator. - if err := kubectl.EnsureFlagsValid(cmd, generators, generatorName); err != nil { - return err + // get the generator + var generator kubectl.StructuredGenerator + switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName { + case cmdutil.HorizontalPodAutoscalerV1GeneratorName: + generator = &kubectl.HorizontalPodAutoscalerGeneratorV1{ + Name: info.Name, + MinReplicas: cmdutil.GetFlagInt32(cmd, "min"), + MaxReplicas: cmdutil.GetFlagInt32(cmd, "max"), + CPUPercent: cmdutil.GetFlagInt32(cmd, "cpu-percent"), + ScaleRefName: info.Name, + ScaleRefKind: mapping.GroupVersionKind.Kind, + ScaleRefApiVersion: mapping.GroupVersionKind.GroupVersion().String(), + } + default: + return errUnsupportedGenerator(cmd, generatorName) } // Generate new object - object, err := generator.Generate(params) + object, err := generator.StructuredGenerate() if err != nil { return err } @@ -179,7 +172,7 @@ func RunAutoscale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s return f.PrintObject(cmd, false, mapper, object, out) } - cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, cmdutil.GetDryRunFlag(cmd), "autoscaled") + f.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, cmdutil.GetDryRunFlag(cmd), "autoscaled") return nil }) if err != nil { @@ -193,7 +186,7 @@ func RunAutoscale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s func validateFlags(cmd *cobra.Command) error { errs := []error{} - max, min := cmdutil.GetFlagInt(cmd, "max"), cmdutil.GetFlagInt(cmd, "min") + max, min := cmdutil.GetFlagInt32(cmd, "max"), cmdutil.GetFlagInt32(cmd, "min") if max < 1 { errs = append(errs, fmt.Errorf("--max=MAXPODS is required and must be at least 1, max: %d", max)) } diff --git a/pkg/kubectl/cmd/certificates.go b/pkg/kubectl/cmd/certificates.go index 43388b4b57e..bc2974322c2 100644 --- a/pkg/kubectl/cmd/certificates.go +++ b/pkg/kubectl/cmd/certificates.go @@ -168,6 +168,7 @@ func (options *CertificateOptions) modifyCertificateCondition(f cmdutil.Factory, return err } r := f.NewBuilder(). + Internal(). ContinueOnError(). FilenameParam(false, &options.FilenameOptions). ResourceNames("certificatesigningrequest", options.csrNames...). @@ -188,7 +189,7 @@ func (options *CertificateOptions) modifyCertificateCondition(f cmdutil.Factory, return err } found++ - cmdutil.PrintSuccess(mapper, options.outputStyle == "name", out, info.Mapping.Resource, info.Name, false, verb) + f.PrintSuccess(mapper, options.outputStyle == "name", out, info.Mapping.Resource, info.Name, false, verb) return nil }) if found == 0 { diff --git a/pkg/kubectl/cmd/clusterinfo.go b/pkg/kubectl/cmd/clusterinfo.go index 83b90c04eee..4924691c68a 100644 --- a/pkg/kubectl/cmd/clusterinfo.go +++ b/pkg/kubectl/cmd/clusterinfo.go @@ -23,7 +23,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilnet "k8s.io/apimachinery/pkg/util/net" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" @@ -73,11 +73,12 @@ func RunClusterInfo(f cmdutil.Factory, out io.Writer, cmd *cobra.Command) error // TODO use generalized labels once they are implemented (#341) b := f.NewBuilder(). + Internal(). NamespaceParam(cmdNamespace).DefaultNamespace(). - SelectorParam("kubernetes.io/cluster-service=true"). + LabelSelectorParam("kubernetes.io/cluster-service=true"). ResourceTypeOrNameArgs(false, []string{"services"}...). Latest() - b.Do().Visit(func(r *resource.Info, err error) error { + err = b.Do().Visit(func(r *resource.Info, err error) error { if err != nil { return err } @@ -125,7 +126,7 @@ func RunClusterInfo(f cmdutil.Factory, out io.Writer, cmd *cobra.Command) error return nil }) out.Write([]byte("\nTo further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.\n")) - return nil + return err // TODO consider printing more information about cluster } diff --git a/pkg/kubectl/cmd/clusterinfo_dump.go b/pkg/kubectl/cmd/clusterinfo_dump.go index b718bf6e7ee..511bba5d936 100644 --- a/pkg/kubectl/cmd/clusterinfo_dump.go +++ b/pkg/kubectl/cmd/clusterinfo_dump.go @@ -25,7 +25,7 @@ import ( "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/util/i18n" diff --git a/pkg/kubectl/cmd/cmd.go b/pkg/kubectl/cmd/cmd.go index 8287b198068..04429a76b4e 100644 --- a/pkg/kubectl/cmd/cmd.go +++ b/pkg/kubectl/cmd/cmd.go @@ -24,6 +24,7 @@ import ( "k8s.io/client-go/tools/clientcmd" "k8s.io/kubernetes/pkg/kubectl/cmd/auth" cmdconfig "k8s.io/kubernetes/pkg/kubectl/cmd/config" + "k8s.io/kubernetes/pkg/kubectl/cmd/resource" "k8s.io/kubernetes/pkg/kubectl/cmd/rollout" "k8s.io/kubernetes/pkg/kubectl/cmd/set" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" @@ -36,22 +37,22 @@ import ( const ( bashCompletionFunc = `# call kubectl get $1, -__kubectl_override_flag_list=(kubeconfig cluster user context namespace server) +__kubectl_override_flag_list=(--kubeconfig --cluster --user --context --namespace --server -n -s) __kubectl_override_flags() { - local ${__kubectl_override_flag_list[*]} two_word_of of + local ${__kubectl_override_flag_list[*]##*-} two_word_of of var for w in "${words[@]}"; do if [ -n "${two_word_of}" ]; then - eval "${two_word_of}=\"--${two_word_of}=\${w}\"" + eval "${two_word_of##*-}=\"${two_word_of}=\${w}\"" two_word_of= continue fi for of in "${__kubectl_override_flag_list[@]}"; do case "${w}" in - --${of}=*) - eval "${of}=\"${w}\"" + ${of}=*) + eval "${of##*-}=\"${w}\"" ;; - --${of}) + ${of}) two_word_of="${of}" ;; esac @@ -60,9 +61,9 @@ __kubectl_override_flags() namespace="--all-namespaces" fi done - for of in "${__kubectl_override_flag_list[@]}"; do - if eval "test -n \"\$${of}\""; then - eval "echo \${${of}}" + for var in "${__kubectl_override_flag_list[@]##*-}"; do + if eval "test -n \"\$${var}\""; then + eval "echo \${${var}}" fi done } @@ -187,7 +188,7 @@ __custom_func() { __kubectl_get_resource_node return ;; - kubectl_config_use-context) + kubectl_config_use-context | kubectl_config_rename-context) __kubectl_config_get_contexts return ;; @@ -199,52 +200,6 @@ __custom_func() { ;; esac } -` - - // If you add a resource to this list, please also take a look at pkg/kubectl/kubectl.go - // and add a short forms entry in expandResourceShortcut() when appropriate. - // TODO: This should be populated using the discovery information from apiserver. - validResources = `Valid resource types include: - - * all - * certificatesigningrequests (aka 'csr') - * clusterrolebindings - * clusterroles - * clusters (valid only for federation apiservers) - * componentstatuses (aka 'cs') - * configmaps (aka 'cm') - * controllerrevisions - * cronjobs - * customresourcedefinition (aka 'crd') - * daemonsets (aka 'ds') - * deployments (aka 'deploy') - * endpoints (aka 'ep') - * events (aka 'ev') - * horizontalpodautoscalers (aka 'hpa') - * ingresses (aka 'ing') - * jobs - * limitranges (aka 'limits') - * namespaces (aka 'ns') - * networkpolicies (aka 'netpol') - * nodes (aka 'no') - * persistentvolumeclaims (aka 'pvc') - * persistentvolumes (aka 'pv') - * poddisruptionbudgets (aka 'pdb') - * podpreset - * pods (aka 'po') - * podsecuritypolicies (aka 'psp') - * podtemplates - * replicasets (aka 'rs') - * replicationcontrollers (aka 'rc') - * resourcequotas (aka 'quota') - * rolebindings - * roles - * secrets - * serviceaccounts (aka 'sa') - * services (aka 'svc') - * statefulsets (aka 'sts') - * storageclasses (aka 'sc') - ` ) @@ -266,7 +221,8 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob Long: templates.LongDesc(` kubectl controls the Kubernetes cluster manager. - Find more information at https://github.com/kubernetes/kubernetes.`), + Find more information at: + https://kubernetes.io/docs/reference/kubectl/overview/`), Run: runHelp, BashCompletionFunction: bashCompletionFunc, } @@ -298,7 +254,7 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob { Message: "Basic Commands (Intermediate):", Commands: []*cobra.Command{ - NewCmdGet(f, out, err), + resource.NewCmdGet(f, out, err), NewCmdExplain(f, out, err), NewCmdEdit(f, out, err), NewCmdDelete(f, out, err), @@ -381,7 +337,7 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob } cmds.AddCommand(alpha) - cmds.AddCommand(cmdconfig.NewCmdConfig(clientcmd.NewDefaultPathOptions(), out, err)) + cmds.AddCommand(cmdconfig.NewCmdConfig(f, clientcmd.NewDefaultPathOptions(), out, err)) cmds.AddCommand(NewCmdPlugin(f, in, out, err)) cmds.AddCommand(NewCmdVersion(f, out)) cmds.AddCommand(NewCmdApiVersions(f, out)) @@ -412,13 +368,3 @@ func deprecatedAlias(deprecatedVersion string, cmd *cobra.Command) *cobra.Comman cmd.Hidden = true return cmd } - -// deprecated is similar to deprecatedAlias, but it is used for deprecations -// that are not simple aliases; this command is actually a different -// (deprecated) codepath. -func deprecated(baseName, to string, parent, cmd *cobra.Command) string { - cmd.Long = fmt.Sprintf("Deprecated: all functionality can be found in \"%s %s\"", baseName, to) - cmd.Short = fmt.Sprintf("Deprecated: use %s", to) - parent.AddCommand(cmd) - return cmd.Name() -} diff --git a/pkg/kubectl/cmd/cmd_test.go b/pkg/kubectl/cmd/cmd_test.go index 41639c76060..dcfe0d490df 100644 --- a/pkg/kubectl/cmd/cmd_test.go +++ b/pkg/kubectl/cmd/cmd_test.go @@ -36,15 +36,22 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" + apitesting "k8s.io/kubernetes/pkg/api/testing" + api "k8s.io/kubernetes/pkg/apis/core" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/printers" printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" "k8s.io/kubernetes/pkg/util/strings" ) +// This init should be removed after switching this command and its tests to user external types. +func init() { + api.AddToScheme(scheme.Scheme) +} + func initTestErrorHandler(t *testing.T) { cmdutil.BehaviorOnFatal(func(str string, code int) { t.Errorf("Error running command (exit code %d): %s", code, str) @@ -61,22 +68,57 @@ func defaultClientConfig() *restclient.Config { return &restclient.Config{ APIPath: "/api", ContentConfig: restclient.ContentConfig{ - NegotiatedSerializer: api.Codecs, + NegotiatedSerializer: scheme.Codecs, ContentType: runtime.ContentTypeJSON, - GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion, + GroupVersion: &schema.GroupVersion{Version: "v1"}, }, } } -func defaultClientConfigForVersion(version *schema.GroupVersion) *restclient.Config { - return &restclient.Config{ - APIPath: "/api", - ContentConfig: restclient.ContentConfig{ - NegotiatedSerializer: api.Codecs, - ContentType: runtime.ContentTypeJSON, - GroupVersion: version, +func testData() (*api.PodList, *api.ServiceList, *api.ReplicationControllerList) { + pods := &api.PodList{ + ListMeta: metav1.ListMeta{ + ResourceVersion: "15", + }, + Items: []api.Pod{ + { + ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"}, + Spec: apitesting.DeepEqualSafePodSpec(), + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "test", ResourceVersion: "11"}, + Spec: apitesting.DeepEqualSafePodSpec(), + }, }, } + svc := &api.ServiceList{ + ListMeta: metav1.ListMeta{ + ResourceVersion: "16", + }, + Items: []api.Service{ + { + ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"}, + Spec: api.ServiceSpec{ + SessionAffinity: "None", + Type: api.ServiceTypeClusterIP, + }, + }, + }, + } + rc := &api.ReplicationControllerList{ + ListMeta: metav1.ListMeta{ + ResourceVersion: "17", + }, + Items: []api.ReplicationController{ + { + ObjectMeta: metav1.ObjectMeta{Name: "rc1", Namespace: "test", ResourceVersion: "18"}, + Spec: api.ReplicationControllerSpec{ + Replicas: 1, + }, + }, + }, + } + return pods, svc, rc } type testPrinter struct { @@ -162,7 +204,6 @@ func Example_printReplicationControllerWithNamespace() { printersinternal.AddHandlers(p) tf.Printer = p tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: ns, Client: nil, } @@ -215,7 +256,6 @@ func Example_printMultiContainersReplicationControllerWithWide() { printersinternal.AddHandlers(p) tf.Printer = p tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: ns, Client: nil, } @@ -269,7 +309,6 @@ func Example_printReplicationController() { printersinternal.AddHandlers(p) tf.Printer = p tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: ns, Client: nil, } @@ -324,7 +363,6 @@ func Example_printPodWithWideFormat() { printersinternal.AddHandlers(p) tf.Printer = p tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: ns, Client: nil, } @@ -367,7 +405,6 @@ func Example_printPodWithShowLabels() { printersinternal.AddHandlers(p) tf.Printer = p tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: ns, Client: nil, } @@ -504,7 +541,6 @@ func Example_printPodHideTerminated() { printersinternal.AddHandlers(p) tf.Printer = p tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: ns, Client: nil, } @@ -512,7 +548,7 @@ func Example_printPodHideTerminated() { podList := newAllPhasePodList() // filter pods filterFuncs := f.DefaultResourceFilterFunc() - filterOpts := f.DefaultResourceFilterOptions(cmd, false) + filterOpts := cmdutil.ExtractCmdPrintOptions(cmd, false) _, filteredPodList, errs := cmdutil.FilterResourceList(podList, filterFuncs, filterOpts) if errs != nil { fmt.Printf("Unexpected filter error: %v\n", errs) @@ -540,7 +576,6 @@ func Example_printPodShowAll() { printersinternal.AddHandlers(p) tf.Printer = p tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: ns, Client: nil, } @@ -569,7 +604,6 @@ func Example_printServiceWithNamespacesAndLabels() { printersinternal.AddHandlers(p) tf.Printer = p tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: ns, Client: nil, } diff --git a/pkg/kubectl/cmd/config/BUILD b/pkg/kubectl/cmd/config/BUILD index 3a1fd047769..5bdadef9ce7 100644 --- a/pkg/kubectl/cmd/config/BUILD +++ b/pkg/kubectl/cmd/config/BUILD @@ -33,8 +33,6 @@ go_library( "//pkg/kubectl/util/i18n:go_default_library", "//pkg/printers:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library", @@ -63,8 +61,8 @@ go_test( "use_context_test.go", "view_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/config", - library = ":go_default_library", deps = [ "//pkg/kubectl/cmd/util:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", diff --git a/pkg/kubectl/cmd/config/config.go b/pkg/kubectl/cmd/config/config.go index cffd0aee3d7..b63d1cf15f3 100644 --- a/pkg/kubectl/cmd/config/config.go +++ b/pkg/kubectl/cmd/config/config.go @@ -31,7 +31,7 @@ import ( ) // NewCmdConfig creates a command object for the "config" action, and adds all child commands to it. -func NewCmdConfig(pathOptions *clientcmd.PathOptions, out, errOut io.Writer) *cobra.Command { +func NewCmdConfig(f cmdutil.Factory, pathOptions *clientcmd.PathOptions, out, errOut io.Writer) *cobra.Command { if len(pathOptions.ExplicitFileFlag) == 0 { pathOptions.ExplicitFileFlag = clientcmd.RecommendedConfigPathFlag } @@ -53,7 +53,7 @@ func NewCmdConfig(pathOptions *clientcmd.PathOptions, out, errOut io.Writer) *co // file paths are common to all sub commands cmd.PersistentFlags().StringVar(&pathOptions.LoadingRules.ExplicitPath, pathOptions.ExplicitFileFlag, pathOptions.LoadingRules.ExplicitPath, "use a particular kubeconfig file") - cmd.AddCommand(NewCmdConfigView(out, errOut, pathOptions)) + cmd.AddCommand(NewCmdConfigView(f, out, errOut, pathOptions)) cmd.AddCommand(NewCmdConfigSetCluster(out, pathOptions)) cmd.AddCommand(NewCmdConfigSetAuthInfo(out, pathOptions)) cmd.AddCommand(NewCmdConfigSetContext(out, pathOptions)) diff --git a/pkg/kubectl/cmd/config/config_test.go b/pkg/kubectl/cmd/config/config_test.go index e331aaed086..2fc8000278d 100644 --- a/pkg/kubectl/cmd/config/config_test.go +++ b/pkg/kubectl/cmd/config/config_test.go @@ -865,7 +865,7 @@ func testConfigCommand(args []string, startingConfig clientcmdapi.Config, t *tes buf := bytes.NewBuffer([]byte{}) - cmd := NewCmdConfig(clientcmd.NewDefaultPathOptions(), buf, buf) + cmd := NewCmdConfig(cmdutil.NewFactory(nil), clientcmd.NewDefaultPathOptions(), buf, buf) cmd.SetArgs(argsToUse) cmd.Execute() diff --git a/pkg/kubectl/cmd/config/get_contexts.go b/pkg/kubectl/cmd/config/get_contexts.go index 9ba113b1554..b018a52dca5 100644 --- a/pkg/kubectl/cmd/config/get_contexts.go +++ b/pkg/kubectl/cmd/config/get_contexts.go @@ -19,6 +19,7 @@ package config import ( "fmt" "io" + "sort" "strings" "text/tabwriter" @@ -138,6 +139,7 @@ func (o GetContextsOptions) RunGetContexts() error { } } + sort.Strings(toPrint) for _, name := range toPrint { err = printContext(name, config.Contexts[name], out, o.nameOnly, config.CurrentContext == name) if err != nil { diff --git a/pkg/kubectl/cmd/config/get_contexts_test.go b/pkg/kubectl/cmd/config/get_contexts_test.go index 209d26c05c9..d21c4470a43 100644 --- a/pkg/kubectl/cmd/config/get_contexts_test.go +++ b/pkg/kubectl/cmd/config/get_contexts_test.go @@ -66,6 +66,27 @@ func TestGetContextsAllNoHeader(t *testing.T) { test.run(t) } +func TestGetContextsAllSorted(t *testing.T) { + tconf := clientcmdapi.Config{ + CurrentContext: "shaker-context", + Contexts: map[string]*clientcmdapi.Context{ + "shaker-context": {AuthInfo: "blue-user", Cluster: "big-cluster", Namespace: "saw-ns"}, + "abc": {AuthInfo: "blue-user", Cluster: "abc-cluster", Namespace: "kube-system"}, + "xyz": {AuthInfo: "blue-user", Cluster: "xyz-cluster", Namespace: "default"}}} + test := getContextsTest{ + startingConfig: tconf, + names: []string{}, + noHeader: false, + nameOnly: false, + expectedOut: `CURRENT NAME CLUSTER AUTHINFO NAMESPACE + abc abc-cluster blue-user kube-system +* shaker-context big-cluster blue-user saw-ns + xyz xyz-cluster blue-user default +`, + } + test.run(t) +} + func TestGetContextsAllName(t *testing.T) { tconf := clientcmdapi.Config{ Contexts: map[string]*clientcmdapi.Context{ diff --git a/pkg/kubectl/cmd/config/rename_context.go b/pkg/kubectl/cmd/config/rename_context.go index d1a3d6f8eac..3216a7c83ea 100644 --- a/pkg/kubectl/cmd/config/rename_context.go +++ b/pkg/kubectl/cmd/config/rename_context.go @@ -43,7 +43,7 @@ const ( var ( renameContextLong = templates.LongDesc(` - Renames a context from the kubeconfig file . + Renames a context from the kubeconfig file. CONTEXT_NAME is the context name that you wish change. @@ -70,7 +70,7 @@ func NewCmdConfigRenameContext(out io.Writer, configAccess clientcmd.ConfigAcces cmdutil.CheckErr(err) } if err := options.Validate(); err != nil { - cmdutil.UsageErrorf(cmd, err.Error()) + cmdutil.CheckErr(cmdutil.UsageErrorf(cmd, err.Error())) } if err := options.RunRenameContext(out); err != nil { cmdutil.CheckErr(err) @@ -130,6 +130,6 @@ func (o RenameContextOptions) RunRenameContext(out io.Writer) error { return err } - fmt.Fprintf(out, "Context %q was renamed to %q.\n", o.contextName, o.newName) + fmt.Fprintf(out, "Context %q renamed to %q.\n", o.contextName, o.newName) return nil } diff --git a/pkg/kubectl/cmd/config/rename_context_test.go b/pkg/kubectl/cmd/config/rename_context_test.go index 4a074a49e81..cfe371497f1 100644 --- a/pkg/kubectl/cmd/config/rename_context_test.go +++ b/pkg/kubectl/cmd/config/rename_context_test.go @@ -62,7 +62,7 @@ func TestRenameContext(t *testing.T) { initialConfig: initialConfig, expectedConfig: expectedConfig, args: []string{currentContext, newContext}, - expectedOut: fmt.Sprintf("Context %q was renamed to %q.\n", currentContext, newContext), + expectedOut: fmt.Sprintf("Context %q renamed to %q.\n", currentContext, newContext), expectedErr: "", } test.run(t) diff --git a/pkg/kubectl/cmd/config/view.go b/pkg/kubectl/cmd/config/view.go index c12418e9c13..d6b9de100dc 100644 --- a/pkg/kubectl/cmd/config/view.go +++ b/pkg/kubectl/cmd/config/view.go @@ -23,8 +23,6 @@ import ( "github.com/spf13/cobra" - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/util/flag" "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" @@ -57,7 +55,7 @@ var ( kubectl config view -o jsonpath='{.users[?(@.name == "e2e")].user.password}'`) ) -func NewCmdConfigView(out, errOut io.Writer, ConfigAccess clientcmd.ConfigAccess) *cobra.Command { +func NewCmdConfigView(f cmdutil.Factory, out, errOut io.Writer, ConfigAccess clientcmd.ConfigAccess) *cobra.Command { options := &ViewOptions{ConfigAccess: ConfigAccess} // Default to yaml defaultOutputFormat := "yaml" @@ -81,7 +79,8 @@ func NewCmdConfigView(out, errOut io.Writer, ConfigAccess clientcmd.ConfigAccess cmd.Flags().Set("output", defaultOutputFormat) } - printer, err := cmdutil.PrinterForCommand(cmd, nil, meta.NewDefaultRESTMapper(nil, nil), latest.Scheme, nil, []runtime.Decoder{latest.Codec}, printers.PrintOptions{}) + printOpts := cmdutil.ExtractCmdPrintOptions(cmd, false) + printer, err := f.PrinterForOptions(printOpts) cmdutil.CheckErr(err) printer = printers.NewVersionedPrinter(printer, latest.Scheme, latest.ExternalVersion) @@ -93,8 +92,8 @@ func NewCmdConfigView(out, errOut io.Writer, ConfigAccess clientcmd.ConfigAccess cmd.Flags().Set("output", defaultOutputFormat) options.Merge.Default(true) - f := cmd.Flags().VarPF(&options.Merge, "merge", "", "Merge the full hierarchy of kubeconfig files") - f.NoOptDefVal = "true" + mergeFlag := cmd.Flags().VarPF(&options.Merge, "merge", "", "Merge the full hierarchy of kubeconfig files") + mergeFlag.NoOptDefVal = "true" cmd.Flags().BoolVar(&options.RawByteData, "raw", false, "Display raw byte data") cmd.Flags().BoolVar(&options.Flatten, "flatten", false, "Flatten the resulting kubeconfig file into self-contained output (useful for creating portable kubeconfig files)") cmd.Flags().BoolVar(&options.Minify, "minify", false, "Remove all information not used by current-context from the output") diff --git a/pkg/kubectl/cmd/config/view_test.go b/pkg/kubectl/cmd/config/view_test.go index 177210d024b..873e5f0797d 100644 --- a/pkg/kubectl/cmd/config/view_test.go +++ b/pkg/kubectl/cmd/config/view_test.go @@ -24,6 +24,7 @@ import ( "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) type viewClusterTest struct { @@ -142,7 +143,7 @@ func (test viewClusterTest) run(t *testing.T) { pathOptions.EnvVar = "" buf := bytes.NewBuffer([]byte{}) errBuf := bytes.NewBuffer([]byte{}) - cmd := NewCmdConfigView(buf, errBuf, pathOptions) + cmd := NewCmdConfigView(cmdutil.NewFactory(nil), buf, errBuf, pathOptions) cmd.Flags().Parse(test.flags) if err := cmd.Execute(); err != nil { t.Fatalf("unexpected error executing command: %v,kubectl config view flags: %v", err, test.flags) diff --git a/pkg/kubectl/cmd/convert.go b/pkg/kubectl/cmd/convert.go index 3f369d0cc38..285b726011d 100644 --- a/pkg/kubectl/cmd/convert.go +++ b/pkg/kubectl/cmd/convert.go @@ -23,7 +23,8 @@ import ( "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/kubernetes/pkg/api" + scheme "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" @@ -51,7 +52,7 @@ var ( kubectl convert -f pod.yaml # Convert the live state of the resource specified by 'pod.yaml' to the latest version - # and print to stdout in json format. + # and print to stdout in JSON format. kubectl convert -f pod.yaml --local -o json # Convert all files under current directory to latest version and create them all. @@ -98,19 +99,15 @@ type ConvertOptions struct { out io.Writer printer printers.ResourcePrinter - outputVersion schema.GroupVersion + specifiedOutputVersion schema.GroupVersion } // outputVersion returns the preferred output version for generic content (JSON, YAML, or templates) // defaultVersion is never mutated. Nil simply allows clean passing in common usage from client.Config -func outputVersion(cmd *cobra.Command, defaultVersion *schema.GroupVersion) (schema.GroupVersion, error) { +func outputVersion(cmd *cobra.Command) (schema.GroupVersion, error) { outputVersionString := cmdutil.GetFlagString(cmd, "output-version") if len(outputVersionString) == 0 { - if defaultVersion == nil { - return schema.GroupVersion{}, nil - } - - return *defaultVersion, nil + return schema.GroupVersion{}, nil } return schema.ParseGroupVersion(outputVersionString) @@ -118,31 +115,28 @@ func outputVersion(cmd *cobra.Command, defaultVersion *schema.GroupVersion) (sch // Complete collects information required to run Convert command from command line. func (o *ConvertOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.Command) (err error) { - o.outputVersion, err = outputVersion(cmd, &api.Registry.EnabledVersionsForGroup(api.GroupName)[0]) + o.specifiedOutputVersion, err = outputVersion(cmd) if err != nil { return err } - if !api.Registry.IsEnabledVersion(o.outputVersion) { - cmdutil.UsageErrorf(cmd, "'%s' is not a registered version.", o.outputVersion) - } // build the builder - o.builder = f.NewBuilder() + o.builder = f.NewBuilder(). + Internal(). + LocalParam(o.local) if !o.local { schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate")) if err != nil { return err } - o.builder = o.builder.Schema(schema) - } else { - o.builder = o.builder.Local(f.ClientForMapping) + o.builder.Schema(schema) } cmdNamespace, _, err := f.DefaultNamespace() if err != nil { return err } - o.builder = o.builder.NamespaceParam(cmdNamespace). + o.builder.NamespaceParam(cmdNamespace). ContinueOnError(). FilenameParam(false, &o.FilenameOptions). Flatten() @@ -161,7 +155,7 @@ func (o *ConvertOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.C cmd.Flags().Set("output", outputFormat) } o.encoder = f.JSONEncoder() - o.printer, err = f.PrinterForCommand(cmd, o.local, nil, printers.PrintOptions{}) + o.printer, err = f.PrinterForOptions(cmdutil.ExtractCmdPrintOptions(cmd, false)) return err } @@ -183,7 +177,7 @@ func (o *ConvertOptions) RunConvert() error { return fmt.Errorf("no objects passed to convert") } - objects, err := asVersionedObject(infos, !singleItemImplied, o.outputVersion, o.encoder) + objects, err := asVersionedObject(infos, !singleItemImplied, o.specifiedOutputVersion, o.encoder) if err != nil { return err } @@ -193,7 +187,7 @@ func (o *ConvertOptions) RunConvert() error { if err != nil { return err } - filteredObj, err := objectListToVersionedObject(items, o.outputVersion) + filteredObj, err := objectListToVersionedObject(items, o.specifiedOutputVersion) if err != nil { return err } @@ -203,23 +197,28 @@ func (o *ConvertOptions) RunConvert() error { return o.printer.PrintObj(objects, o.out) } -// ObjectListToVersionedObject receives a list of api objects and a group version +// objectListToVersionedObject receives a list of api objects and a group version // and squashes the list's items into a single versioned runtime.Object. -func objectListToVersionedObject(objects []runtime.Object, version schema.GroupVersion) (runtime.Object, error) { +func objectListToVersionedObject(objects []runtime.Object, specifiedOutputVersion schema.GroupVersion) (runtime.Object, error) { objectList := &api.List{Items: objects} - converted, err := tryConvert(api.Scheme, objectList, version, api.Registry.GroupOrDie(api.GroupName).GroupVersion) + targetVersions := []schema.GroupVersion{} + if !specifiedOutputVersion.Empty() { + targetVersions = append(targetVersions, specifiedOutputVersion) + } + targetVersions = append(targetVersions, scheme.Registry.GroupOrDie(api.GroupName).GroupVersion) + converted, err := tryConvert(scheme.Scheme, objectList, targetVersions...) if err != nil { return nil, err } return converted, nil } -// AsVersionedObject converts a list of infos into a single object - either a List containing +// asVersionedObject converts a list of infos into a single object - either a List containing // the objects as children, or if only a single Object is present, as that object. The provided // version will be preferred as the conversion target, but the Object's mapping version will be // used if that version is not present. -func asVersionedObject(infos []*resource.Info, forceList bool, version schema.GroupVersion, encoder runtime.Encoder) (runtime.Object, error) { - objects, err := asVersionedObjects(infos, version, encoder) +func asVersionedObject(infos []*resource.Info, forceList bool, specifiedOutputVersion schema.GroupVersion, encoder runtime.Encoder) (runtime.Object, error) { + objects, err := asVersionedObjects(infos, specifiedOutputVersion, encoder) if err != nil { return nil, err } @@ -229,7 +228,13 @@ func asVersionedObject(infos []*resource.Info, forceList bool, version schema.Gr object = objects[0] } else { object = &api.List{Items: objects} - converted, err := tryConvert(api.Scheme, object, version, api.Registry.GroupOrDie(api.GroupName).GroupVersion) + targetVersions := []schema.GroupVersion{} + if !specifiedOutputVersion.Empty() { + targetVersions = append(targetVersions, specifiedOutputVersion) + } + targetVersions = append(targetVersions, scheme.Registry.GroupOrDie(api.GroupName).GroupVersion) + + converted, err := tryConvert(scheme.Scheme, object, targetVersions...) if err != nil { return nil, err } @@ -237,7 +242,7 @@ func asVersionedObject(infos []*resource.Info, forceList bool, version schema.Gr } actualVersion := object.GetObjectKind().GroupVersionKind() - if actualVersion.Version != version.Version { + if actualVersion.Version != specifiedOutputVersion.Version { defaultVersionInfo := "" if len(actualVersion.Version) > 0 { defaultVersionInfo = fmt.Sprintf("Defaulting to %q", actualVersion.Version) @@ -247,20 +252,21 @@ func asVersionedObject(infos []*resource.Info, forceList bool, version schema.Gr return object, nil } -// AsVersionedObjects converts a list of infos into versioned objects. The provided +// asVersionedObjects converts a list of infos into versioned objects. The provided // version will be preferred as the conversion target, but the Object's mapping version will be // used if that version is not present. -func asVersionedObjects(infos []*resource.Info, version schema.GroupVersion, encoder runtime.Encoder) ([]runtime.Object, error) { +func asVersionedObjects(infos []*resource.Info, specifiedOutputVersion schema.GroupVersion, encoder runtime.Encoder) ([]runtime.Object, error) { objects := []runtime.Object{} for _, info := range infos { if info.Object == nil { continue } + targetVersions := []schema.GroupVersion{} // objects that are not part of api.Scheme must be converted to JSON // TODO: convert to map[string]interface{}, attach to runtime.Unknown? - if !version.Empty() { - if _, _, err := api.Scheme.ObjectKinds(info.Object); runtime.IsNotRegisteredError(err) { + if !specifiedOutputVersion.Empty() { + if _, _, err := scheme.Scheme.ObjectKinds(info.Object); runtime.IsNotRegisteredError(err) { // TODO: ideally this would encode to version, but we don't expose multiple codecs here. data, err := runtime.Encode(encoder, info.Object) if err != nil { @@ -270,9 +276,19 @@ func asVersionedObjects(infos []*resource.Info, version schema.GroupVersion, enc objects = append(objects, &runtime.Unknown{Raw: data}) continue } + targetVersions = append(targetVersions, specifiedOutputVersion) + } else { + gvks, _, err := scheme.Scheme.ObjectKinds(info.Object) + if err == nil { + for _, gvk := range gvks { + for _, version := range scheme.Registry.EnabledVersionsForGroup(gvk.Group) { + targetVersions = append(targetVersions, version) + } + } + } } - converted, err := tryConvert(info.Mapping.ObjectConvertor, info.Object, version, info.Mapping.GroupVersionKind.GroupVersion()) + converted, err := tryConvert(info.Mapping.ObjectConvertor, info.Object, targetVersions...) if err != nil { return nil, err } diff --git a/pkg/kubectl/cmd/convert_test.go b/pkg/kubectl/cmd/convert_test.go new file mode 100644 index 00000000000..39e368749a7 --- /dev/null +++ b/pkg/kubectl/cmd/convert_test.go @@ -0,0 +1,128 @@ +/* +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 cmd + +import ( + "bytes" + "fmt" + "net/http" + "testing" + + "k8s.io/client-go/rest/fake" + cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" + "k8s.io/kubernetes/pkg/printers" +) + +type testcase struct { + name string + file string + outputVersion string + fields []checkField +} + +type checkField struct { + template string + expected string +} + +func TestConvertObject(t *testing.T) { + testcases := []testcase{ + { + name: "apps deployment to extensions deployment", + file: "../../../test/fixtures/pkg/kubectl/cmd/convert/appsdeployment.yaml", + outputVersion: "extensions/v1beta1", + fields: []checkField{ + { + template: "{{.apiVersion}}", + expected: "extensions/v1beta1", + }, + }, + }, + { + name: "extensions deployment to apps deployment", + file: "../../../test/fixtures/pkg/kubectl/cmd/convert/extensionsdeployment.yaml", + outputVersion: "apps/v1beta2", + fields: []checkField{ + { + template: "{{.apiVersion}}", + expected: "apps/v1beta2", + }, + }, + }, + { + name: "v1 HPA to v2beta1 HPA", + file: "../../../test/fixtures/pkg/kubectl/cmd/convert/v1HPA.yaml", + outputVersion: "autoscaling/v2beta1", + fields: []checkField{ + { + template: "{{.apiVersion}}", + expected: "autoscaling/v2beta1", + }, + { + template: "{{(index .spec.metrics 0).resource.name}}", + expected: "cpu", + }, + { + template: "{{(index .spec.metrics 0).resource.targetAverageUtilization}}", + expected: "50", + }, + }, + }, + { + name: "v2beta1 HPA to v1 HPA", + file: "../../../test/fixtures/pkg/kubectl/cmd/convert/v2beta1HPA.yaml", + outputVersion: "autoscaling/v1", + fields: []checkField{ + { + template: "{{.apiVersion}}", + expected: "autoscaling/v1", + }, + { + template: "{{.spec.targetCPUUtilizationPercentage}}", + expected: "50", + }, + }, + }, + } + + for _, tc := range testcases { + for _, field := range tc.fields { + t.Run(fmt.Sprintf("%s %s", tc.name, field), func(t *testing.T) { + f, tf, _, _ := cmdtesting.NewAPIFactory() + tf.UnstructuredClient = &fake.RESTClient{ + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil + }), + } + tf.Namespace = "test" + + buf := bytes.NewBuffer([]byte{}) + cmd := NewCmdConvert(f, buf) + cmd.Flags().Set("filename", tc.file) + cmd.Flags().Set("output-version", tc.outputVersion) + cmd.Flags().Set("local", "true") + cmd.Flags().Set("output", "go-template") + tf.Printer, _ = printers.NewTemplatePrinter([]byte(field.template)) + cmd.Run(cmd, []string{}) + if buf.String() != field.expected { + t.Errorf("unexpected output when converting %s to %q, expected: %q, but got %q", tc.file, tc.outputVersion, field.expected, buf.String()) + } + }) + } + } +} diff --git a/pkg/kubectl/cmd/cp.go b/pkg/kubectl/cmd/cp.go index 8b3a89096b6..a5d67de3f96 100644 --- a/pkg/kubectl/cmd/cp.go +++ b/pkg/kubectl/cmd/cp.go @@ -301,10 +301,12 @@ func recursiveTar(srcBase, srcFile, destBase, destFile string, tw *tar.Writer) e if err != nil { return err } - defer f.Close() - _, err = io.Copy(tw, f) - return err + + if _, err := io.Copy(tw, f); err != nil { + return err + } + return f.Close() } return nil } @@ -330,7 +332,6 @@ func untarAll(reader io.Reader, destFile, prefix string) error { return err } if header.FileInfo().IsDir() { - if err := os.MkdirAll(outFileName, 0755); err != nil { return err } @@ -359,7 +360,12 @@ func untarAll(reader io.Reader, destFile, prefix string) error { return err } defer outFile.Close() - io.Copy(outFile, tarReader) + if _, err := io.Copy(outFile, tarReader); err != nil { + return err + } + if err := outFile.Close(); err != nil { + return err + } } } diff --git a/pkg/kubectl/cmd/cp_test.go b/pkg/kubectl/cmd/cp_test.go index b410bc694ba..8236fef7a37 100644 --- a/pkg/kubectl/cmd/cp_test.go +++ b/pkg/kubectl/cmd/cp_test.go @@ -109,8 +109,8 @@ func TestGetPrefix(t *testing.T) { } func TestTarUntar(t *testing.T) { - dir, err := ioutil.TempDir(os.TempDir(), "input") - dir2, err2 := ioutil.TempDir(os.TempDir(), "output") + dir, err := ioutil.TempDir("", "input") + dir2, err2 := ioutil.TempDir("", "output") if err != nil || err2 != nil { t.Errorf("unexpected error: %v | %v", err, err2) t.FailNow() @@ -160,74 +160,74 @@ func TestTarUntar(t *testing.T) { for _, file := range files { filepath := path.Join(dir, file.name) if err := os.MkdirAll(path.Dir(filepath), 0755); err != nil { - t.Errorf("unexpected error: %v", err) - t.FailNow() + t.Fatalf("unexpected error: %v", err) } if file.fileType == RegularFile { f, err := os.Create(filepath) if err != nil { - t.Errorf("unexpected error: %v", err) - t.FailNow() + t.Fatalf("unexpected error: %v", err) } defer f.Close() if _, err := io.Copy(f, bytes.NewBuffer([]byte(file.data))); err != nil { - t.Errorf("unexpected error: %v", err) - t.FailNow() + t.Fatalf("unexpected error: %v", err) + } + if err := f.Close(); err != nil { + t.Fatal(err) } } else if file.fileType == SymLink { err := os.Symlink(file.data, filepath) if err != nil { - t.Errorf("unexpected error: %v", err) - t.FailNow() + t.Fatalf("unexpected error: %v", err) } } else { - t.Errorf("unexpected file type: %v", file) - t.FailNow() + t.Fatalf("unexpected file type: %v", file) } } writer := &bytes.Buffer{} if err := makeTar(dir, dir, writer); err != nil { - t.Errorf("unexpected error: %v", err) + t.Fatalf("unexpected error: %v", err) } reader := bytes.NewBuffer(writer.Bytes()) if err := untarAll(reader, dir2, ""); err != nil { - t.Errorf("unexpected error: %v", err) - t.FailNow() + t.Fatalf("unexpected error: %v", err) } for _, file := range files { - absPath := dir2 + strings.TrimPrefix(dir, os.TempDir()) - filepath := path.Join(absPath, file.name) + absPath := filepath.Join(dir2, strings.TrimPrefix(dir, os.TempDir())) + filePath := filepath.Join(absPath, file.name) if file.fileType == RegularFile { - f, err := os.Open(filepath) + f, err := os.Open(filePath) if err != nil { - t.Errorf("unexpected error: %v", err) + t.Fatalf("unexpected error: %v", err) } defer f.Close() buff := &bytes.Buffer{} - io.Copy(buff, f) - + if _, err := io.Copy(buff, f); err != nil { + t.Fatal(err) + } + if err := f.Close(); err != nil { + t.Fatal(err) + } if file.data != string(buff.Bytes()) { - t.Errorf("expected: %s, saw: %s", file.data, string(buff.Bytes())) + t.Fatalf("expected: %s, saw: %s", file.data, string(buff.Bytes())) } } else if file.fileType == SymLink { - dest, err := os.Readlink(filepath) + dest, err := os.Readlink(filePath) if err != nil { - t.Errorf("unexpected error: %v", err) + t.Fatalf("unexpected error: %v", err) } if file.data != dest { - t.Errorf("expected: %s, saw: %s", file.data, dest) + t.Fatalf("expected: %s, saw: %s", file.data, dest) } } else { - t.Errorf("unexpected file type: %v", file) - t.FailNow() + t.Fatalf("unexpected file type: %v", file) } } } diff --git a/pkg/kubectl/cmd/create.go b/pkg/kubectl/cmd/create.go index 5ed32cb813f..fd6d7298667 100644 --- a/pkg/kubectl/cmd/create.go +++ b/pkg/kubectl/cmd/create.go @@ -19,10 +19,15 @@ package cmd import ( "fmt" "io" + "os" "runtime" + "strings" "github.com/spf13/cobra" + "net/url" + + "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" @@ -36,6 +41,7 @@ type CreateOptions struct { FilenameOptions resource.FilenameOptions Selector string EditBeforeCreate bool + Raw string } var ( @@ -69,7 +75,7 @@ func NewCmdCreate(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { defaultRunFunc(cmd, args) return } - cmdutil.CheckErr(ValidateArgs(cmd, args)) + cmdutil.CheckErr(options.ValidateArgs(cmd, args)) cmdutil.CheckErr(RunCreate(f, cmd, out, errOut, &options)) }, } @@ -87,6 +93,7 @@ func NewCmdCreate(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { cmdutil.AddDryRunFlag(cmd) cmdutil.AddInclude3rdPartyFlags(cmd) cmd.Flags().StringVarP(&options.Selector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") + cmd.Flags().StringVar(&options.Raw, "raw", options.Raw, "Raw URI to POST to the server. Uses the transport specified by the kubeconfig file.") // create subcommands cmd.AddCommand(NewCmdCreateNamespace(f, out)) @@ -101,17 +108,69 @@ func NewCmdCreate(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { cmd.AddCommand(NewCmdCreateRole(f, out)) cmd.AddCommand(NewCmdCreateRoleBinding(f, out)) cmd.AddCommand(NewCmdCreatePodDisruptionBudget(f, out)) + cmd.AddCommand(NewCmdCreatePriorityClass(f, out)) return cmd } -func ValidateArgs(cmd *cobra.Command, args []string) error { +func (o *CreateOptions) ValidateArgs(cmd *cobra.Command, args []string) error { if len(args) != 0 { return cmdutil.UsageErrorf(cmd, "Unexpected args: %v", args) } + if len(o.Raw) > 0 { + if o.EditBeforeCreate { + return cmdutil.UsageErrorf(cmd, "--raw and --edit are mutually exclusive") + } + if len(o.FilenameOptions.Filenames) != 1 { + return cmdutil.UsageErrorf(cmd, "--raw can only use a single local file or stdin") + } + if strings.Index(o.FilenameOptions.Filenames[0], "http://") == 0 || strings.Index(o.FilenameOptions.Filenames[0], "https://") == 0 { + return cmdutil.UsageErrorf(cmd, "--raw cannot read from a url") + } + if o.FilenameOptions.Recursive { + return cmdutil.UsageErrorf(cmd, "--raw and --recursive are mutually exclusive") + } + if len(o.Selector) > 0 { + return cmdutil.UsageErrorf(cmd, "--raw and --selector (-l) are mutually exclusive") + } + if len(cmdutil.GetFlagString(cmd, "output")) > 0 { + return cmdutil.UsageErrorf(cmd, "--raw and --output are mutually exclusive") + } + if _, err := url.ParseRequestURI(o.Raw); err != nil { + return cmdutil.UsageErrorf(cmd, "--raw must be a valid URL path: %v", err) + } + } + return nil } func RunCreate(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, options *CreateOptions) error { + // raw only makes sense for a single file resource multiple objects aren't likely to do what you want. + // the validator enforces this, so + if len(options.Raw) > 0 { + restClient, err := f.RESTClient() + if err != nil { + return err + } + + var data io.ReadCloser + if options.FilenameOptions.Filenames[0] == "-" { + data = os.Stdin + } else { + data, err = os.Open(options.FilenameOptions.Filenames[0]) + if err != nil { + return err + } + } + // TODO post content with stream. Right now it ignores body content + bytes, err := restClient.Post().RequestURI(options.Raw).Body(data).DoRaw() + if err != nil { + return err + } + + fmt.Fprintf(out, "%v", string(bytes)) + return nil + } + if options.EditBeforeCreate { return RunEditOnCreate(f, out, errOut, cmd, &options.FilenameOptions) } @@ -125,18 +184,13 @@ func RunCreate(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opt return err } - mapper, typer, err := f.UnstructuredObject() - if err != nil { - return err - } - r := f.NewBuilder(). - Unstructured(f.UnstructuredClientForMapping, mapper, typer). + Unstructured(). Schema(schema). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &options.FilenameOptions). - SelectorParam(options.Selector). + LabelSelectorParam(options.Selector). Flatten(). Do() err = r.Err() @@ -146,6 +200,7 @@ func RunCreate(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opt dryRun := cmdutil.GetFlagBool(cmd, "dry-run") output := cmdutil.GetFlagString(cmd, "output") + mapper := r.Mapper().RESTMapper count := 0 err = r.Visit(func(info *resource.Info, err error) error { @@ -172,13 +227,13 @@ func RunCreate(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opt shortOutput := output == "name" if len(output) > 0 && !shortOutput { - return cmdutil.PrintResourceInfoForCommand(cmd, info, f, out) + return f.PrintResourceInfoForCommand(cmd, info, out) } if !shortOutput { f.PrintObjectSpecificMessage(info.Object, out) } - cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, dryRun, "created") + f.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, dryRun, "created") return nil }) if err != nil { @@ -223,8 +278,8 @@ func createAndRefresh(info *resource.Info) error { // NameFromCommandArgs is a utility function for commands that assume the first argument is a resource name func NameFromCommandArgs(cmd *cobra.Command, args []string) (string, error) { - if len(args) == 0 { - return "", cmdutil.UsageErrorf(cmd, "NAME is required") + if len(args) != 1 { + return "", cmdutil.UsageErrorf(cmd, "exactly one NAME is required, got %d", len(args)) } return args[0], nil } @@ -242,7 +297,7 @@ type CreateSubcommandOptions struct { // RunCreateSubcommand executes a create subcommand using the specified options func RunCreateSubcommand(f cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *CreateSubcommandOptions) error { - namespace, _, err := f.DefaultNamespace() + namespace, nsOverriden, err := f.DefaultNamespace() if err != nil { return err } @@ -283,10 +338,14 @@ func RunCreateSubcommand(f cmdutil.Factory, cmd *cobra.Command, out io.Writer, o if err != nil { return err } + } else { + if meta, err := meta.Accessor(obj); err == nil && nsOverriden { + meta.SetNamespace(namespace) + } } if useShortOutput := options.OutputFormat == "name"; useShortOutput || len(options.OutputFormat) == 0 { - cmdutil.PrintSuccess(mapper, useShortOutput, out, mapping.Resource, info.Name, options.DryRun, "created") + f.PrintSuccess(mapper, useShortOutput, out, mapping.Resource, info.Name, options.DryRun, "created") return nil } diff --git a/pkg/kubectl/cmd/create_clusterrole.go b/pkg/kubectl/cmd/create_clusterrole.go index fd77ef0bc4d..5ed01ce3c33 100644 --- a/pkg/kubectl/cmd/create_clusterrole.go +++ b/pkg/kubectl/cmd/create_clusterrole.go @@ -23,7 +23,7 @@ import ( "github.com/spf13/cobra" - "k8s.io/kubernetes/pkg/apis/rbac" + rbacv1 "k8s.io/api/rbac/v1" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/util/i18n" @@ -155,7 +155,7 @@ func (c *CreateClusterRoleOptions) Validate() error { } func (c *CreateClusterRoleOptions) RunCreateRole() error { - clusterRole := &rbac.ClusterRole{} + clusterRole := &rbacv1.ClusterRole{} clusterRole.Name = c.Name rules, err := generateResourcePolicyRules(c.Mapper, c.Verbs, c.Resources, c.ResourceNames, c.NonResourceURLs) if err != nil { @@ -172,7 +172,7 @@ func (c *CreateClusterRoleOptions) RunCreateRole() error { } if useShortOutput := c.OutputFormat == "name"; useShortOutput || len(c.OutputFormat) == 0 { - cmdutil.PrintSuccess(c.Mapper, useShortOutput, c.Out, "clusterroles", c.Name, c.DryRun, "created") + c.PrintSuccess(c.Mapper, useShortOutput, c.Out, "clusterroles", c.Name, c.DryRun, "created") return nil } diff --git a/pkg/kubectl/cmd/create_clusterrole_test.go b/pkg/kubectl/cmd/create_clusterrole_test.go index e930c6eacf0..6a82531bc63 100644 --- a/pkg/kubectl/cmd/create_clusterrole_test.go +++ b/pkg/kubectl/cmd/create_clusterrole_test.go @@ -22,10 +22,10 @@ import ( "reflect" "testing" + rbac "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/apis/rbac" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" ) @@ -444,13 +444,15 @@ func TestClusterRoleValidate(t *testing.T) { } for name, test := range tests { - test.clusterRoleOptions.Mapper, _ = f.Object() - err := test.clusterRoleOptions.Validate() - if test.expectErr && err == nil { - t.Errorf("%s: expect error happens, but validate passes.", name) - } - if !test.expectErr && err != nil { - t.Errorf("%s: unexpected error: %v", name, err) - } + t.Run(name, func(t *testing.T) { + test.clusterRoleOptions.Mapper, _ = f.Object() + err := test.clusterRoleOptions.Validate() + if test.expectErr && err == nil { + t.Errorf("%s: expect error happens, but validate passes.", name) + } + if !test.expectErr && err != nil { + t.Errorf("%s: unexpected error: %v", name, err) + } + }) } } diff --git a/pkg/kubectl/cmd/create_clusterrolebinding_test.go b/pkg/kubectl/cmd/create_clusterrolebinding_test.go index f21aceba8ed..37aefc81e3b 100644 --- a/pkg/kubectl/cmd/create_clusterrolebinding_test.go +++ b/pkg/kubectl/cmd/create_clusterrolebinding_test.go @@ -30,7 +30,6 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" ) @@ -77,7 +76,6 @@ func TestCreateClusterRoleBinding(t *testing.T) { tf.Printer = &testPrinter{} tf.Client = &ClusterRoleBindingRESTClient{ RESTClient: &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -130,7 +128,6 @@ type ClusterRoleBindingRESTClient struct { func (c *ClusterRoleBindingRESTClient) Post() *restclient.Request { config := restclient.ContentConfig{ ContentType: runtime.ContentTypeJSON, - GroupVersion: &schema.GroupVersion{Group: "rbac.authorization.k8s.io", Version: "v1beta1"}, NegotiatedSerializer: c.NegotiatedSerializer, } diff --git a/pkg/kubectl/cmd/create_configmap_test.go b/pkg/kubectl/cmd/create_configmap_test.go index b86548c40e9..2ac2a939a81 100644 --- a/pkg/kubectl/cmd/create_configmap_test.go +++ b/pkg/kubectl/cmd/create_configmap_test.go @@ -21,18 +21,19 @@ import ( "net/http" "testing" + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" ) func TestCreateConfigMap(t *testing.T) { - configMap := &api.ConfigMap{} + configMap := &v1.ConfigMap{} configMap.Name = "my-configmap" f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Group: "", Version: "v1"}, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { diff --git a/pkg/kubectl/cmd/create_deployment_test.go b/pkg/kubectl/cmd/create_deployment_test.go index 2afc37c3e53..a266fa01fe6 100644 --- a/pkg/kubectl/cmd/create_deployment_test.go +++ b/pkg/kubectl/cmd/create_deployment_test.go @@ -26,7 +26,6 @@ import ( restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/kubectl" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -76,7 +75,6 @@ func TestCreateDeployment(t *testing.T) { depName := "jonny-dep" f, tf, _, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { return &http.Response{ @@ -105,7 +103,6 @@ func TestCreateDeploymentNoImage(t *testing.T) { depName := "jonny-dep" f, tf, _, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { return &http.Response{ diff --git a/pkg/kubectl/cmd/create_namespace_test.go b/pkg/kubectl/cmd/create_namespace_test.go index 548b90c8a8e..3b172b67eb0 100644 --- a/pkg/kubectl/cmd/create_namespace_test.go +++ b/pkg/kubectl/cmd/create_namespace_test.go @@ -21,18 +21,19 @@ import ( "net/http" "testing" + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" ) func TestCreateNamespace(t *testing.T) { - namespaceObject := &api.Namespace{} + namespaceObject := &v1.Namespace{} namespaceObject.Name = "my-namespace" f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Version: "v1"}, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { diff --git a/pkg/kubectl/cmd/create_pdb_test.go b/pkg/kubectl/cmd/create_pdb_test.go index aabf0590f98..e99d5b57c6b 100644 --- a/pkg/kubectl/cmd/create_pdb_test.go +++ b/pkg/kubectl/cmd/create_pdb_test.go @@ -22,9 +22,9 @@ import ( "net/http" "testing" + "k8s.io/apimachinery/pkg/runtime/schema" restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" ) @@ -32,7 +32,7 @@ func TestCreatePdb(t *testing.T) { pdbName := "my-pdb" f, tf, _, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Group: "policy", Version: "v1beta1"}, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { return &http.Response{ diff --git a/pkg/kubectl/cmd/create_priorityclass.go b/pkg/kubectl/cmd/create_priorityclass.go new file mode 100644 index 00000000000..7f3561ae614 --- /dev/null +++ b/pkg/kubectl/cmd/create_priorityclass.go @@ -0,0 +1,90 @@ +/* +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 cmd + +import ( + "io" + + "github.com/spf13/cobra" + + "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/util/i18n" +) + +var ( + pcLong = templates.LongDesc(i18n.T(` + Create a priorityclass with the specified name, value, globalDefault and description`)) + + pcExample = templates.Examples(i18n.T(` + # Create a priorityclass named high-priority + kubectl create priorityclass default-priority --value=1000 --description="high priority" + + # Create a priorityclass named default-priority that considered as the global default priority + kubectl create priorityclass default-priority --value=1000 --global-default=true --description="default priority"`)) +) + +// NewCmdCreatePriorityClass is a macro command to create a new priorityClass. +func NewCmdCreatePriorityClass(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "priorityclass NAME --value=VALUE --global-default=BOOL [--dry-run]", + Aliases: []string{"pc"}, + Short: i18n.T("Create a priorityclass with the specified name."), + Long: pcLong, + Example: pcExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(CreatePriorityClass(f, cmdOut, cmd, args)) + }, + } + + cmdutil.AddApplyAnnotationFlags(cmd) + cmdutil.AddValidateFlags(cmd) + cmdutil.AddPrinterFlags(cmd) + cmdutil.AddGeneratorFlags(cmd, cmdutil.PriorityClassV1Alpha1GeneratorName) + + cmd.Flags().Int32("value", 0, i18n.T("the value of this priority class.")) + cmd.Flags().Bool("global-default", false, i18n.T("global-default specifies whether this PriorityClass should be considered as the default priority.")) + cmd.Flags().String("description", "", i18n.T("description is an arbitrary string that usually provides guidelines on when this priority class should be used.")) + return cmd +} + +// CreatePriorityClass implements the behavior to run the create priorityClass command. +func CreatePriorityClass(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error { + name, err := NameFromCommandArgs(cmd, args) + if err != nil { + return err + } + var generator kubectl.StructuredGenerator + switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName { + case cmdutil.PriorityClassV1Alpha1GeneratorName: + generator = &kubectl.PriorityClassV1Generator{ + Name: name, + Value: cmdutil.GetFlagInt32(cmd, "value"), + GlobalDefault: cmdutil.GetFlagBool(cmd, "global-default"), + Description: cmdutil.GetFlagString(cmd, "description"), + } + default: + return errUnsupportedGenerator(cmd, generatorName) + } + return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{ + Name: name, + StructuredGenerator: generator, + DryRun: cmdutil.GetFlagBool(cmd, "dry-run"), + OutputFormat: cmdutil.GetFlagString(cmd, "output"), + }) +} diff --git a/pkg/kubectl/cmd/create_priorityclass_test.go b/pkg/kubectl/cmd/create_priorityclass_test.go new file mode 100644 index 00000000000..2ef8b143a9d --- /dev/null +++ b/pkg/kubectl/cmd/create_priorityclass_test.go @@ -0,0 +1,59 @@ +/* +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 cmd + +import ( + "bytes" + "io/ioutil" + "net/http" + "testing" + + "k8s.io/apimachinery/pkg/runtime/schema" + restclient "k8s.io/client-go/rest" + "k8s.io/client-go/rest/fake" + cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" +) + +func TestCreatePriorityClass(t *testing.T) { + pcName := "my-pc" + f, tf, _, ns := cmdtesting.NewAPIFactory() + tf.Client = &fake.RESTClient{ + GroupVersion: schema.GroupVersion{Group: "scheduling.k8s.io", Version: "v1alpha1"}, + NegotiatedSerializer: ns, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(&bytes.Buffer{}), + }, nil + }), + } + tf.ClientConfig = &restclient.Config{} + tf.Printer = &testPrinter{} + buf := bytes.NewBuffer([]byte{}) + + cmd := NewCmdCreatePriorityClass(f, buf) + cmd.Flags().Set("value", "1000") + cmd.Flags().Set("global-default", "true") + cmd.Flags().Set("description", "my priority") + cmd.Flags().Set("dry-run", "true") + cmd.Flags().Set("output", "name") + CreatePriorityClass(f, buf, cmd, []string{pcName}) + expectedOutput := "priorityclass/" + pcName + "\n" + if buf.String() != expectedOutput { + t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String()) + } +} diff --git a/pkg/kubectl/cmd/create_quota_test.go b/pkg/kubectl/cmd/create_quota_test.go index 659530cb7f7..abf0e33a82e 100644 --- a/pkg/kubectl/cmd/create_quota_test.go +++ b/pkg/kubectl/cmd/create_quota_test.go @@ -21,18 +21,19 @@ import ( "net/http" "testing" + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" ) func TestCreateQuota(t *testing.T) { - resourceQuotaObject := &api.ResourceQuota{} + resourceQuotaObject := &v1.ResourceQuota{} resourceQuotaObject.Name = "my-quota" f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Version: "v1"}, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { diff --git a/pkg/kubectl/cmd/create_role.go b/pkg/kubectl/cmd/create_role.go index 9ea8cc33aa4..0a881e779c7 100644 --- a/pkg/kubectl/cmd/create_role.go +++ b/pkg/kubectl/cmd/create_role.go @@ -23,12 +23,12 @@ import ( "github.com/spf13/cobra" + rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/apis/rbac" - internalversionrbac "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion" + clientgorbacv1 "k8s.io/client-go/kubernetes/typed/rbac/v1" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/util/i18n" @@ -108,10 +108,11 @@ type CreateRoleOptions struct { DryRun bool OutputFormat string Namespace string - Client internalversionrbac.RbacInterface + Client clientgorbacv1.RbacV1Interface Mapper meta.RESTMapper Out io.Writer PrintObject func(obj runtime.Object) error + PrintSuccess func(mapper meta.RESTMapper, shortOutput bool, out io.Writer, resource, name string, dryRun bool, operation string) } // Role is a command to ease creating Roles. @@ -161,6 +162,7 @@ func (c *CreateRoleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args } } c.Verbs = verbs + c.PrintSuccess = f.PrintSuccess // Support resource.group pattern. If no API Group specified, use "" as core API Group. // e.g. --resource=pods,deployments.extensions @@ -206,11 +208,11 @@ func (c *CreateRoleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args return f.PrintObject(cmd, false, c.Mapper, obj, c.Out) } - clientSet, err := f.ClientSet() + clientset, err := f.KubernetesClientSet() if err != nil { return err } - c.Client = clientSet.Rbac() + c.Client = clientset.RbacV1() return nil } @@ -275,7 +277,7 @@ func (c *CreateRoleOptions) validateResource() error { } func (c *CreateRoleOptions) RunCreateRole() error { - role := &rbac.Role{} + role := &rbacv1.Role{} role.Name = c.Name rules, err := generateResourcePolicyRules(c.Mapper, c.Verbs, c.Resources, c.ResourceNames, []string{}) if err != nil { @@ -292,7 +294,7 @@ func (c *CreateRoleOptions) RunCreateRole() error { } if useShortOutput := c.OutputFormat == "name"; useShortOutput || len(c.OutputFormat) == 0 { - cmdutil.PrintSuccess(c.Mapper, useShortOutput, c.Out, "roles", c.Name, c.DryRun, "created") + c.PrintSuccess(c.Mapper, useShortOutput, c.Out, "roles", c.Name, c.DryRun, "created") return nil } @@ -308,7 +310,7 @@ func arrayContains(s []string, e string) bool { return false } -func generateResourcePolicyRules(mapper meta.RESTMapper, verbs []string, resources []ResourceOptions, resourceNames []string, nonResourceURLs []string) ([]rbac.PolicyRule, error) { +func generateResourcePolicyRules(mapper meta.RESTMapper, verbs []string, resources []ResourceOptions, resourceNames []string, nonResourceURLs []string) ([]rbacv1.PolicyRule, error) { // groupResourceMapping is a apigroup-resource map. The key of this map is api group, while the value // is a string array of resources under this api group. // E.g. groupResourceMapping = {"extensions": ["replicasets", "deployments"], "batch":["jobs"]} @@ -334,9 +336,9 @@ func generateResourcePolicyRules(mapper meta.RESTMapper, verbs []string, resourc } // Create separate rule for each of the api group. - rules := []rbac.PolicyRule{} + rules := []rbacv1.PolicyRule{} for _, g := range sets.StringKeySet(groupResourceMapping).List() { - rule := rbac.PolicyRule{} + rule := rbacv1.PolicyRule{} rule.Verbs = verbs rule.Resources = groupResourceMapping[g] rule.APIGroups = []string{g} @@ -345,7 +347,7 @@ func generateResourcePolicyRules(mapper meta.RESTMapper, verbs []string, resourc } if len(nonResourceURLs) > 0 { - rule := rbac.PolicyRule{} + rule := rbacv1.PolicyRule{} rule.Verbs = verbs rule.NonResourceURLs = nonResourceURLs rules = append(rules, rule) diff --git a/pkg/kubectl/cmd/create_role_test.go b/pkg/kubectl/cmd/create_role_test.go index 1dcd14705ba..2d6fadf077a 100644 --- a/pkg/kubectl/cmd/create_role_test.go +++ b/pkg/kubectl/cmd/create_role_test.go @@ -22,10 +22,11 @@ import ( "reflect" "testing" + rbac "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/diff" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/apis/rbac" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" ) @@ -143,19 +144,21 @@ func TestCreateRole(t *testing.T) { } for name, test := range tests { - buf := bytes.NewBuffer([]byte{}) - cmd := NewCmdCreateRole(f, buf) - cmd.Flags().Set("dry-run", "true") - cmd.Flags().Set("output", "object") - cmd.Flags().Set("verb", test.verbs) - cmd.Flags().Set("resource", test.resources) - if test.resourceNames != "" { - cmd.Flags().Set("resource-name", test.resourceNames) - } - cmd.Run(cmd, []string{roleName}) - if !reflect.DeepEqual(test.expectedRole, printer.CachedRole) { - t.Errorf("%s:\nexpected:\n%#v\nsaw:\n%#v", name, test.expectedRole, printer.CachedRole) - } + t.Run(name, func(t *testing.T) { + buf := bytes.NewBuffer([]byte{}) + cmd := NewCmdCreateRole(f, buf) + cmd.Flags().Set("dry-run", "true") + cmd.Flags().Set("output", "object") + cmd.Flags().Set("verb", test.verbs) + cmd.Flags().Set("resource", test.resources) + if test.resourceNames != "" { + cmd.Flags().Set("resource-name", test.resourceNames) + } + cmd.Run(cmd, []string{roleName}) + if !reflect.DeepEqual(test.expectedRole, printer.CachedRole) { + t.Errorf("%s", diff.ObjectReflectDiff(test.expectedRole, printer.CachedRole)) + } + }) } } diff --git a/pkg/kubectl/cmd/create_rolebinding_test.go b/pkg/kubectl/cmd/create_rolebinding_test.go index 910738c60bd..154ae26a1b3 100644 --- a/pkg/kubectl/cmd/create_rolebinding_test.go +++ b/pkg/kubectl/cmd/create_rolebinding_test.go @@ -24,20 +24,23 @@ import ( "reflect" "testing" + rbac "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/rbac" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" ) -var groupVersion = schema.GroupVersion{Group: "rbac.authorization.k8s.io", Version: "v1alpha1"} +var groupVersion = schema.GroupVersion{Group: "rbac.authorization.k8s.io", Version: "v1"} func TestCreateRoleBinding(t *testing.T) { expectBinding := &rbac.RoleBinding{ + TypeMeta: v1.TypeMeta{ + APIVersion: "rbac.authorization.k8s.io/v1", + Kind: "RoleBinding", + }, ObjectMeta: v1.ObjectMeta{ Name: "fake-binding", }, @@ -75,7 +78,6 @@ func TestCreateRoleBinding(t *testing.T) { tf.Printer = &testPrinter{} tf.Client = &RoleBindingRESTClient{ RESTClient: &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { diff --git a/pkg/kubectl/cmd/create_secret.go b/pkg/kubectl/cmd/create_secret.go index 85909ceb05b..f49048bf9a3 100644 --- a/pkg/kubectl/cmd/create_secret.go +++ b/pkg/kubectl/cmd/create_secret.go @@ -169,6 +169,7 @@ func NewCmdCreateSecretDockerRegistry(f cmdutil.Factory, cmdOut io.Writer) *cobr cmd.Flags().String("docker-email", "", i18n.T("Email for Docker registry")) cmd.Flags().String("docker-server", "https://index.docker.io/v1/", i18n.T("Server location for Docker registry")) cmd.Flags().Bool("append-hash", false, "Append a hash of the secret to its name.") + cmdutil.AddInclude3rdPartyFlags(cmd) return cmd } diff --git a/pkg/kubectl/cmd/create_secret_test.go b/pkg/kubectl/cmd/create_secret_test.go index 9f9128db27f..0db9873d546 100644 --- a/pkg/kubectl/cmd/create_secret_test.go +++ b/pkg/kubectl/cmd/create_secret_test.go @@ -21,13 +21,14 @@ import ( "net/http" "testing" + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" ) func TestCreateSecretGeneric(t *testing.T) { - secretObject := &api.Secret{ + secretObject := &v1.Secret{ Data: map[string][]byte{ "password": []byte("includes,comma"), "username": []byte("test_user"), @@ -37,7 +38,7 @@ func TestCreateSecretGeneric(t *testing.T) { f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Version: "v1"}, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -63,12 +64,12 @@ func TestCreateSecretGeneric(t *testing.T) { } func TestCreateSecretDockerRegistry(t *testing.T) { - secretObject := &api.Secret{} + secretObject := &v1.Secret{} secretObject.Name = "my-secret" f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Version: "v1"}, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { diff --git a/pkg/kubectl/cmd/create_service.go b/pkg/kubectl/cmd/create_service.go index 54587422933..52496a03b5e 100644 --- a/pkg/kubectl/cmd/create_service.go +++ b/pkg/kubectl/cmd/create_service.go @@ -21,7 +21,7 @@ import ( "github.com/spf13/cobra" - "k8s.io/kubernetes/pkg/api" + "k8s.io/api/core/v1" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -47,13 +47,13 @@ func NewCmdCreateService(f cmdutil.Factory, cmdOut, errOut io.Writer) *cobra.Com var ( serviceClusterIPLong = templates.LongDesc(i18n.T(` - Create a clusterIP service with the specified name.`)) + Create a ClusterIP service with the specified name.`)) serviceClusterIPExample = templates.Examples(i18n.T(` - # Create a new clusterIP service named my-cs + # Create a new ClusterIP service named my-cs kubectl create service clusterip my-cs --tcp=5678:8080 - # Create a new clusterIP service named my-cs (in headless mode) + # Create a new ClusterIP service named my-cs (in headless mode) kubectl create service clusterip my-cs --clusterip="None"`)) ) @@ -61,11 +61,11 @@ func addPortFlags(cmd *cobra.Command) { cmd.Flags().StringSlice("tcp", []string{}, "Port pairs can be specified as ':'.") } -// NewCmdCreateServiceClusterIP is a command to create a clusterIP service +// NewCmdCreateServiceClusterIP is a command to create a ClusterIP service func NewCmdCreateServiceClusterIP(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "clusterip NAME [--tcp=:] [--dry-run]", - Short: i18n.T("Create a clusterIP service."), + Short: i18n.T("Create a ClusterIP service."), Long: serviceClusterIPLong, Example: serviceClusterIPExample, Run: func(cmd *cobra.Command, args []string) { @@ -86,7 +86,7 @@ func errUnsupportedGenerator(cmd *cobra.Command, generatorName string) error { return cmdutil.UsageErrorf(cmd, "Generator %s not supported. ", generatorName) } -// CreateServiceClusterIP implements the behavior to run the create service clusterIP command +// CreateServiceClusterIP is the implementation of the create service clusterip command func CreateServiceClusterIP(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error { name, err := NameFromCommandArgs(cmd, args) if err != nil { @@ -98,7 +98,7 @@ func CreateServiceClusterIP(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Comm generator = &kubectl.ServiceCommonGeneratorV1{ Name: name, TCP: cmdutil.GetFlagStringSlice(cmd, "tcp"), - Type: api.ServiceTypeClusterIP, + Type: v1.ServiceTypeClusterIP, ClusterIP: cmdutil.GetFlagString(cmd, "clusterip"), } default: @@ -114,10 +114,10 @@ func CreateServiceClusterIP(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Comm var ( serviceNodePortLong = templates.LongDesc(i18n.T(` - Create a nodeport service with the specified name.`)) + Create a NodePort service with the specified name.`)) serviceNodePortExample = templates.Examples(i18n.T(` - # Create a new nodeport service named my-ns + # Create a new NodePort service named my-ns kubectl create service nodeport my-ns --tcp=5678:8080`)) ) @@ -154,7 +154,7 @@ func CreateServiceNodePort(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Comma generator = &kubectl.ServiceCommonGeneratorV1{ Name: name, TCP: cmdutil.GetFlagStringSlice(cmd, "tcp"), - Type: api.ServiceTypeNodePort, + Type: v1.ServiceTypeNodePort, ClusterIP: "", NodePort: cmdutil.GetFlagInt(cmd, "node-port"), } @@ -198,7 +198,7 @@ func NewCmdCreateServiceLoadBalancer(f cmdutil.Factory, cmdOut io.Writer) *cobra return cmd } -// CreateServiceLoadBalancer is the implementation of the service loadbalancer command +// CreateServiceLoadBalancer is the implementation of the create service loadbalancer command func CreateServiceLoadBalancer(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error { name, err := NameFromCommandArgs(cmd, args) if err != nil { @@ -210,7 +210,7 @@ func CreateServiceLoadBalancer(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.C generator = &kubectl.ServiceCommonGeneratorV1{ Name: name, TCP: cmdutil.GetFlagStringSlice(cmd, "tcp"), - Type: api.ServiceTypeLoadBalancer, + Type: v1.ServiceTypeLoadBalancer, ClusterIP: "", } default: @@ -237,7 +237,7 @@ var ( kubectl create service externalname my-ns --external-name bar.com`)) ) -// NewCmdCreateServiceExternalName is a macro command for creating a ExternalName service +// NewCmdCreateServiceExternalName is a macro command for creating an ExternalName service func NewCmdCreateServiceExternalName(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "externalname NAME --external-name external.name [--dry-run]", @@ -259,7 +259,7 @@ func NewCmdCreateServiceExternalName(f cmdutil.Factory, cmdOut io.Writer) *cobra return cmd } -// CreateExternalNameService is the implementation of the service externalname command +// CreateExternalNameService is the implementation of the create service externalname command func CreateExternalNameService(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error { name, err := NameFromCommandArgs(cmd, args) if err != nil { @@ -270,7 +270,7 @@ func CreateExternalNameService(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.C case cmdutil.ServiceExternalNameGeneratorV1Name: generator = &kubectl.ServiceCommonGeneratorV1{ Name: name, - Type: api.ServiceTypeExternalName, + Type: v1.ServiceTypeExternalName, ExternalName: cmdutil.GetFlagString(cmd, "external-name"), ClusterIP: "", } diff --git a/pkg/kubectl/cmd/create_service_test.go b/pkg/kubectl/cmd/create_service_test.go index 75fa4ae1340..9e7e1bb5139 100644 --- a/pkg/kubectl/cmd/create_service_test.go +++ b/pkg/kubectl/cmd/create_service_test.go @@ -21,18 +21,19 @@ import ( "net/http" "testing" + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" ) func TestCreateService(t *testing.T) { - service := &api.Service{} + service := &v1.Service{} service.Name = "my-service" f, tf, codec, negSer := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Version: "v1"}, NegotiatedSerializer: negSer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -57,12 +58,12 @@ func TestCreateService(t *testing.T) { } func TestCreateServiceNodePort(t *testing.T) { - service := &api.Service{} + service := &v1.Service{} service.Name = "my-node-port-service" f, tf, codec, negSer := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Version: "v1"}, NegotiatedSerializer: negSer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -87,12 +88,12 @@ func TestCreateServiceNodePort(t *testing.T) { } func TestCreateServiceExternalName(t *testing.T) { - service := &api.Service{} + service := &v1.Service{} service.Name = "my-external-name-service" f, tf, codec, negSer := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Version: "v1"}, NegotiatedSerializer: negSer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { diff --git a/pkg/kubectl/cmd/create_serviceaccount_test.go b/pkg/kubectl/cmd/create_serviceaccount_test.go index 63e71dc0016..c510cb52e93 100644 --- a/pkg/kubectl/cmd/create_serviceaccount_test.go +++ b/pkg/kubectl/cmd/create_serviceaccount_test.go @@ -21,18 +21,19 @@ import ( "net/http" "testing" + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" ) func TestCreateServiceAccount(t *testing.T) { - serviceAccountObject := &api.ServiceAccount{} + serviceAccountObject := &v1.ServiceAccount{} serviceAccountObject.Name = "my-service-account" f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Version: "v1"}, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { diff --git a/pkg/kubectl/cmd/create_test.go b/pkg/kubectl/cmd/create_test.go index b68670afcc6..a554a17b901 100644 --- a/pkg/kubectl/cmd/create_test.go +++ b/pkg/kubectl/cmd/create_test.go @@ -21,8 +21,8 @@ import ( "net/http" "testing" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" ) @@ -33,7 +33,8 @@ func TestExtraArgsFail(t *testing.T) { f, _, _, _ := cmdtesting.NewAPIFactory() c := NewCmdCreate(f, buf, errBuf) - if ValidateArgs(c, []string{"rc"}) == nil { + options := CreateOptions{} + if options.ValidateArgs(c, []string{"rc"}) == nil { t.Errorf("unexpected non-error") } } @@ -46,7 +47,7 @@ func TestCreateObject(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Version: "v1"}, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -80,7 +81,7 @@ func TestCreateMultipleObject(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Version: "v1"}, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -118,7 +119,7 @@ func TestCreateDirectory(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Version: "v1"}, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { diff --git a/pkg/kubectl/cmd/delete.go b/pkg/kubectl/cmd/delete.go index 50f32f4ca0e..80a9bd49920 100644 --- a/pkg/kubectl/cmd/delete.go +++ b/pkg/kubectl/cmd/delete.go @@ -169,20 +169,13 @@ func (o *DeleteOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args return err } - // Set up client based support. - mapper, typer, err := f.UnstructuredObject() - if err != nil { - return err - } - - o.Mapper = mapper includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false) r := f.NewBuilder(). - Unstructured(f.UnstructuredClientForMapping, mapper, typer). + Unstructured(). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &o.FilenameOptions). - SelectorParam(o.Selector). + LabelSelectorParam(o.Selector). IncludeUninitialized(includeUninitialized). SelectAllParam(o.DeleteAll). ResourceTypeOrNameArgs(false, args...).RequireObject(false). @@ -193,6 +186,7 @@ func (o *DeleteOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args return err } o.Result = r + o.Mapper = r.Mapper().RESTMapper o.f = f // Set up writer @@ -240,7 +234,7 @@ func (o *DeleteOptions) RunDelete() error { if o.Cascade { return ReapResult(o.Result, o.f, o.Out, true, o.IgnoreNotFound, o.Timeout, o.GracePeriod, o.WaitForDeletion, shortOutput, o.Mapper, false) } - return DeleteResult(o.Result, o.Out, o.IgnoreNotFound, o.GracePeriod, shortOutput, o.Mapper) + return DeleteResult(o.Result, o.f, o.Out, o.IgnoreNotFound, o.GracePeriod, shortOutput, o.Mapper) } func ReapResult(r *resource.Result, f cmdutil.Factory, out io.Writer, isDefaultDelete, ignoreNotFound bool, timeout time.Duration, gracePeriod int, waitForDeletion, shortOutput bool, mapper meta.RESTMapper, quiet bool) error { @@ -258,7 +252,7 @@ func ReapResult(r *resource.Result, f cmdutil.Factory, out io.Writer, isDefaultD // If there is no reaper for this resources and the user didn't explicitly ask for stop. if kubectl.IsNoSuchReaperError(err) && isDefaultDelete { // No client side reaper found. Let the server do cascading deletion. - return cascadingDeleteResource(info, out, shortOutput, mapper) + return cascadingDeleteResource(info, f, out, shortOutput, mapper) } return cmdutil.AddSourceToErr("reaping", info.Source, err) } @@ -275,7 +269,7 @@ func ReapResult(r *resource.Result, f cmdutil.Factory, out io.Writer, isDefaultD } } if !quiet { - cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, false, "deleted") + f.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, false, "deleted") } return nil }) @@ -288,7 +282,7 @@ func ReapResult(r *resource.Result, f cmdutil.Factory, out io.Writer, isDefaultD return nil } -func DeleteResult(r *resource.Result, out io.Writer, ignoreNotFound bool, gracePeriod int, shortOutput bool, mapper meta.RESTMapper) error { +func DeleteResult(r *resource.Result, f cmdutil.Factory, out io.Writer, ignoreNotFound bool, gracePeriod int, shortOutput bool, mapper meta.RESTMapper) error { found := 0 if ignoreNotFound { r = r.IgnoreErrors(errors.IsNotFound) @@ -306,7 +300,7 @@ func DeleteResult(r *resource.Result, out io.Writer, ignoreNotFound bool, graceP options = metav1.NewDeleteOptions(int64(gracePeriod)) } options.OrphanDependents = &orphan - return deleteResource(info, out, shortOutput, mapper, options) + return deleteResource(info, f, out, shortOutput, mapper, options) }) if err != nil { return err @@ -317,17 +311,17 @@ func DeleteResult(r *resource.Result, out io.Writer, ignoreNotFound bool, graceP return nil } -func cascadingDeleteResource(info *resource.Info, out io.Writer, shortOutput bool, mapper meta.RESTMapper) error { +func cascadingDeleteResource(info *resource.Info, f cmdutil.Factory, out io.Writer, shortOutput bool, mapper meta.RESTMapper) error { falseVar := false deleteOptions := &metav1.DeleteOptions{OrphanDependents: &falseVar} - return deleteResource(info, out, shortOutput, mapper, deleteOptions) + return deleteResource(info, f, out, shortOutput, mapper, deleteOptions) } -func deleteResource(info *resource.Info, out io.Writer, shortOutput bool, mapper meta.RESTMapper, deleteOptions *metav1.DeleteOptions) error { +func deleteResource(info *resource.Info, f cmdutil.Factory, out io.Writer, shortOutput bool, mapper meta.RESTMapper, deleteOptions *metav1.DeleteOptions) error { if err := resource.NewHelper(info.Client, info.Mapping).DeleteWithOptions(info.Namespace, info.Name, deleteOptions); err != nil { return cmdutil.AddSourceToErr("deleting", info.Source, err) } - cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, false, "deleted") + f.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, false, "deleted") return nil } diff --git a/pkg/kubectl/cmd/delete_test.go b/pkg/kubectl/cmd/delete_test.go index 5cd244d391e..5b78ba09761 100644 --- a/pkg/kubectl/cmd/delete_test.go +++ b/pkg/kubectl/cmd/delete_test.go @@ -32,9 +32,8 @@ import ( "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/dynamic" - restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/kubectl" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -57,7 +56,6 @@ func TestDeleteObjectByTuple(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -122,7 +120,6 @@ func TestOrphanDependentsInDeleteObject(t *testing.T) { tf.Printer = &testPrinter{} var expectedOrphanDependents *bool tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m, b := req.URL.Path, req.Method, req.Body; { @@ -171,7 +168,6 @@ func TestDeleteNamedObject(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -222,7 +218,6 @@ func TestDeleteObject(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -281,7 +276,6 @@ func TestDeleteObjectGraceZero(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { t.Logf("got request %s %s", req.Method, req.URL.Path) @@ -331,7 +325,6 @@ func TestDeleteObjectNotFound(t *testing.T) { f, tf, _, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -369,7 +362,6 @@ func TestDeleteObjectIgnoreNotFound(t *testing.T) { f, tf, _, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -407,7 +399,6 @@ func TestDeleteAllNotFound(t *testing.T) { tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -457,7 +448,6 @@ func TestDeleteAllIgnoreNotFound(t *testing.T) { tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -494,7 +484,6 @@ func TestDeleteMultipleObject(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -530,7 +519,6 @@ func TestDeleteMultipleObjectContinueOnMissing(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -575,7 +563,6 @@ func TestDeleteMultipleResourcesWithTheSameName(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -614,7 +601,6 @@ func TestDeleteDirectory(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -647,17 +633,16 @@ func TestDeleteMultipleSelector(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { case p == "/namespaces/test/pods" && m == "GET": - if req.URL.Query().Get(metav1.LabelSelectorQueryParam(api.Registry.GroupOrDie(api.GroupName).GroupVersion.String())) != "a=b" { + if req.URL.Query().Get(metav1.LabelSelectorQueryParam("v1")) != "a=b" { t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) } return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, nil case p == "/namespaces/test/services" && m == "GET": - if req.URL.Query().Get(metav1.LabelSelectorQueryParam(api.Registry.GroupOrDie(api.GroupName).GroupVersion.String())) != "a=b" { + if req.URL.Query().Get(metav1.LabelSelectorQueryParam("v1")) != "a=b" { t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) } return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, svc)}, nil @@ -713,7 +698,7 @@ func TestResourceErrors(t *testing.T) { f, tf, _, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}} + tf.ClientConfig = defaultClientConfig() buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) diff --git a/pkg/kubectl/cmd/describe.go b/pkg/kubectl/cmd/describe.go index 579d521de94..f77304ec599 100644 --- a/pkg/kubectl/cmd/describe.go +++ b/pkg/kubectl/cmd/describe.go @@ -24,7 +24,6 @@ import ( "github.com/spf13/cobra" apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/kubernetes/pkg/kubectl" @@ -37,20 +36,19 @@ import ( ) var ( - describe_long = templates.LongDesc(` - Show details of a specific resource or group of resources. - It includes the uninitialized objects, unless --include-uninitialized=false is explicitly set. - This command joins many API calls together to form a detailed description of a - given resource or group of resources. + describeLong = templates.LongDesc(` + Show details of a specific resource or group of resources + + Print a detailed description of the selected resources, including related resources such + as events or controllers. You may select a single object by name, all objects of that + type, provide a name prefix, or label selector. For example: $ kubectl describe TYPE NAME_PREFIX will first check for an exact match on TYPE and NAME_PREFIX. If no such resource - exists, it will output details for every resource that has a name prefixed with NAME_PREFIX. + exists, it will output details for every resource that has a name prefixed with NAME_PREFIX.`) - ` + validResources) - - describe_example = templates.Examples(i18n.T(` + describeExample = templates.Examples(i18n.T(` # Describe a node kubectl describe nodes kubernetes-node-emt8.c.myproject.internal @@ -83,8 +81,8 @@ func NewCmdDescribe(f cmdutil.Factory, out, cmdErr io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "describe (-f FILENAME | TYPE [NAME_PREFIX | -l label] | TYPE/NAME)", Short: i18n.T("Show details of a specific resource or group of resources"), - Long: describe_long, - Example: describe_example, + Long: describeLong + "\n\n" + cmdutil.ValidResourceTypeList(f), + Example: describeExample, Run: func(cmd *cobra.Command, args []string) { err := RunDescribe(f, out, cmdErr, cmd, args, options, describerSettings) cmdutil.CheckErr(err) @@ -113,24 +111,19 @@ func RunDescribe(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, a enforceNamespace = false } if len(args) == 0 && cmdutil.IsFilenameSliceEmpty(options.Filenames) { - fmt.Fprint(cmdErr, "You must specify the type of resource to describe. ", validResources) + fmt.Fprint(cmdErr, "You must specify the type of resource to describe. ", cmdutil.ValidResourceTypeList(f)) return cmdutil.UsageErrorf(cmd, "Required resource not specified.") } - mapper, typer, err := f.UnstructuredObject() - if err != nil { - return err - } - // include the uninitialized objects by default // unless user explicitly set --include-uninitialized=false includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, true) r := f.NewBuilder(). - Unstructured(f.UnstructuredClientForMapping, mapper, typer). + Unstructured(). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces). FilenameParam(enforceNamespace, options). - SelectorParam(selector). + LabelSelectorParam(selector). IncludeUninitialized(includeUninitialized). ResourceTypeOrNameArgs(true, args...). Flatten(). @@ -183,11 +176,8 @@ func RunDescribe(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, a } func DescribeMatchingResources(f cmdutil.Factory, namespace, rsrc, prefix string, describerSettings *printers.DescriberSettings, out io.Writer, originalError error) error { - mapper, typer, err := f.UnstructuredObject() - if err != nil { - return err - } - r := resource.NewBuilder(mapper, f.CategoryExpander(), typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme). + r := f.NewBuilder(). + Unstructured(). NamespaceParam(namespace).DefaultNamespace(). ResourceTypeOrNameArgs(true, rsrc). SingleResourceType(). diff --git a/pkg/kubectl/cmd/describe_test.go b/pkg/kubectl/cmd/describe_test.go index 1064d981f32..2c3ec49c5c8 100644 --- a/pkg/kubectl/cmd/describe_test.go +++ b/pkg/kubectl/cmd/describe_test.go @@ -23,7 +23,6 @@ import ( "testing" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" ) @@ -33,7 +32,6 @@ func TestDescribeUnknownSchemaObject(t *testing.T) { f, tf, codec, _ := cmdtesting.NewTestFactory() tf.Describer = d tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, cmdtesting.NewInternalType("", "", "foo"))}, } @@ -58,7 +56,6 @@ func TestDescribeUnknownNamespacedSchemaObject(t *testing.T) { f, tf, codec, _ := cmdtesting.NewTestFactory() tf.Describer = d tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, cmdtesting.NewInternalNamespacedType("", "", "foo", "non-default"))}, } @@ -83,7 +80,6 @@ func TestDescribeObject(t *testing.T) { d := &testDescriber{Output: "test output"} tf.Describer = d tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -117,7 +113,6 @@ func TestDescribeListObjects(t *testing.T) { d := &testDescriber{Output: "test output"} tf.Describer = d tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, } @@ -138,7 +133,6 @@ func TestDescribeObjectShowEvents(t *testing.T) { d := &testDescriber{Output: "test output"} tf.Describer = d tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, } @@ -160,7 +154,6 @@ func TestDescribeObjectSkipEvents(t *testing.T) { d := &testDescriber{Output: "test output"} tf.Describer = d tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, } diff --git a/pkg/kubectl/cmd/diff.go b/pkg/kubectl/cmd/diff.go new file mode 100644 index 00000000000..e536069952d --- /dev/null +++ b/pkg/kubectl/cmd/diff.go @@ -0,0 +1,453 @@ +/* +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 cmd + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + + "github.com/ghodss/yaml" + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/kubectl/apply/parse" + "k8s.io/kubernetes/pkg/kubectl/apply/strategy" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/kubectl/util/i18n" + "k8s.io/utils/exec" +) + +var ( + diffLong = templates.LongDesc(i18n.T(` + Diff configurations specified by filename or stdin between their local, + last-applied, live and/or "merged" versions. + + LOCAL and LIVE versions are diffed by default. Other available keywords + are MERGED and LAST. + + Output is always YAML. + + KUBERNETES_EXTERNAL_DIFF environment variable can be used to select your own + diff command. By default, the "diff" command available in your path will be + run with "-u" (unicode) and "-N" (treat new files as empty) options.`)) + diffExample = templates.Examples(i18n.T(` + # Diff resources included in pod.json. By default, it will diff LOCAL and LIVE versions + kubectl alpha diff -f pod.json + + # When one version is specified, diff that version against LIVE + cat service.yaml | kubectl alpha diff -f - MERGED + + # Or specify both versions + kubectl alpha diff -f pod.json -f service.yaml LAST LOCAL`)) +) + +type DiffOptions struct { + FilenameOptions resource.FilenameOptions +} + +func isValidArgument(arg string) error { + switch arg { + case "LOCAL", "LIVE", "LAST", "MERGED": + return nil + default: + return fmt.Errorf(`Invalid parameter %q, must be either "LOCAL", "LIVE", "LAST" or "MERGED"`, arg) + } + +} + +func parseDiffArguments(args []string) (string, string, error) { + if len(args) > 2 { + return "", "", fmt.Errorf("Invalid number of arguments: expected at most 2.") + } + // Default values + from := "LOCAL" + to := "LIVE" + if len(args) > 0 { + from = args[0] + } + if len(args) > 1 { + to = args[1] + } + + if err := isValidArgument(to); err != nil { + return "", "", err + } + if err := isValidArgument(from); err != nil { + return "", "", err + } + + return from, to, nil +} + +func NewCmdDiff(f cmdutil.Factory, stdout, stderr io.Writer) *cobra.Command { + var options DiffOptions + diff := DiffProgram{ + Exec: exec.New(), + Stdout: stdout, + Stderr: stderr, + } + cmd := &cobra.Command{ + Use: "diff -f FILENAME", + Short: i18n.T("Diff different versions of configurations"), + Long: diffLong, + Example: diffExample, + Run: func(cmd *cobra.Command, args []string) { + from, to, err := parseDiffArguments(args) + cmdutil.CheckErr(err) + cmdutil.CheckErr(RunDiff(f, &diff, &options, from, to)) + }, + } + + usage := "contains the configuration to diff" + cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage) + cmd.MarkFlagRequired("filename") + + return cmd +} + +// DiffProgram finds and run the diff program. The value of +// KUBERNETES_EXTERNAL_DIFF environment variable will be used a diff +// program. By default, `diff(1)` will be used. +type DiffProgram struct { + Exec exec.Interface + Stdout io.Writer + Stderr io.Writer +} + +func (d *DiffProgram) getCommand(args ...string) exec.Cmd { + diff := "" + if envDiff := os.Getenv("KUBERNETES_EXTERNAL_DIFF"); envDiff != "" { + diff = envDiff + } else { + diff = "diff" + args = append([]string{"-u", "-N"}, args...) + } + + cmd := d.Exec.Command(diff, args...) + cmd.SetStdout(d.Stdout) + cmd.SetStderr(d.Stderr) + + return cmd +} + +// Run runs the detected diff program. `from` and `to` are the directory to diff. +func (d *DiffProgram) Run(from, to string) error { + d.getCommand(from, to).Run() // Ignore diff return code + return nil +} + +// Printer is used to print an object. +type Printer struct{} + +// Print the object inside the writer w. +func (p *Printer) Print(obj map[string]interface{}, w io.Writer) error { + if obj == nil { + return nil + } + data, err := yaml.Marshal(obj) + if err != nil { + return err + } + _, err = w.Write(data) + return err + +} + +// DiffVersion gets the proper version of objects, and aggregate them into a directory. +type DiffVersion struct { + Dir *Directory + Name string +} + +// NewDiffVersion creates a new DiffVersion with the named version. +func NewDiffVersion(name string) (*DiffVersion, error) { + dir, err := CreateDirectory(name) + if err != nil { + return nil, err + } + return &DiffVersion{ + Dir: dir, + Name: name, + }, nil +} + +func (v *DiffVersion) getObject(obj Object) (map[string]interface{}, error) { + switch v.Name { + case "LIVE": + return obj.Live() + case "MERGED": + return obj.Merged() + case "LOCAL": + return obj.Local() + case "LAST": + return obj.Last() + } + return nil, fmt.Errorf("Unknown version: %v", v.Name) +} + +// Print prints the object using the printer into a new file in the directory. +func (v *DiffVersion) Print(obj Object, printer Printer) error { + vobj, err := v.getObject(obj) + if err != nil { + return err + } + f, err := v.Dir.NewFile(obj.Name()) + if err != nil { + return err + } + defer f.Close() + return printer.Print(vobj, f) +} + +// Directory creates a new temp directory, and allows to easily create new files. +type Directory struct { + Name string +} + +// CreateDirectory does create the actual disk directory, and return a +// new representation of it. +func CreateDirectory(prefix string) (*Directory, error) { + name, err := ioutil.TempDir("", prefix+"-") + if err != nil { + return nil, err + } + + return &Directory{ + Name: name, + }, nil +} + +// NewFile creates a new file in the directory. +func (d *Directory) NewFile(name string) (*os.File, error) { + return os.OpenFile(filepath.Join(d.Name, name), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0700) +} + +// Delete removes the directory recursively. +func (d *Directory) Delete() error { + return os.RemoveAll(d.Name) +} + +// Object is an interface that let's you retrieve multiple version of +// it. +type Object interface { + Local() (map[string]interface{}, error) + Live() (map[string]interface{}, error) + Last() (map[string]interface{}, error) + Merged() (map[string]interface{}, error) + + Name() string +} + +// InfoObject is an implementation of the Object interface. It gets all +// the information from the Info object. +type InfoObject struct { + Info *resource.Info + Encoder runtime.Encoder + Parser *parse.Factory +} + +var _ Object = &InfoObject{} + +func (obj InfoObject) toMap(data []byte) (map[string]interface{}, error) { + m := map[string]interface{}{} + if len(data) == 0 { + return m, nil + } + err := json.Unmarshal(data, &m) + return m, err +} + +func (obj InfoObject) Local() (map[string]interface{}, error) { + data, err := runtime.Encode(obj.Encoder, obj.Info.Object) + if err != nil { + return nil, err + } + return obj.toMap(data) +} + +func (obj InfoObject) Live() (map[string]interface{}, error) { + if obj.Info.Object == nil { + return nil, nil // Object doesn't exist on cluster. + } + data, err := runtime.Encode(obj.Encoder, obj.Info.Object) + if err != nil { + return nil, err + } + return obj.toMap(data) +} + +func (obj InfoObject) Merged() (map[string]interface{}, error) { + local, err := obj.Local() + if err != nil { + return nil, err + } + + live, err := obj.Live() + if err != nil { + return nil, err + } + + last, err := obj.Last() + if err != nil { + return nil, err + } + + if live == nil || last == nil { + return local, nil // We probably don't have a live verison, merged is local. + } + + elmt, err := obj.Parser.CreateElement(last, local, live) + if err != nil { + return nil, err + } + result, err := elmt.Merge(strategy.Create(strategy.Options{})) + return result.MergedResult.(map[string]interface{}), err +} + +func (obj InfoObject) Last() (map[string]interface{}, error) { + if obj.Info.Object == nil { + return nil, nil // No object is live, return empty + } + accessor, err := meta.Accessor(obj.Info.Object) + if err != nil { + return nil, err + } + annots := accessor.GetAnnotations() + if annots == nil { + return nil, nil // Not an error, just empty. + } + + return obj.toMap([]byte(annots[api.LastAppliedConfigAnnotation])) +} + +func (obj InfoObject) Name() string { + return obj.Info.Name +} + +// Differ creates two DiffVersion and diffs them. +type Differ struct { + From *DiffVersion + To *DiffVersion +} + +func NewDiffer(from, to string) (*Differ, error) { + differ := Differ{} + var err error + differ.From, err = NewDiffVersion(from) + if err != nil { + return nil, err + } + differ.To, err = NewDiffVersion(to) + if err != nil { + differ.From.Dir.Delete() + return nil, err + } + + return &differ, nil +} + +// Diff diffs to versions of a specific object, and print both versions to directories. +func (d *Differ) Diff(obj Object, printer Printer) error { + if err := d.From.Print(obj, printer); err != nil { + return err + } + if err := d.To.Print(obj, printer); err != nil { + return err + } + return nil +} + +// Run runs the diff program against both directories. +func (d *Differ) Run(diff *DiffProgram) error { + return diff.Run(d.From.Dir.Name, d.To.Dir.Name) +} + +// TearDown removes both temporary directories recursively. +func (d *Differ) TearDown() { + d.From.Dir.Delete() // Ignore error + d.To.Dir.Delete() // Ignore error +} + +// RunDiff uses the factory to parse file arguments, find the version to +// diff, and find each Info object for each files, and runs against the +// differ. +func RunDiff(f cmdutil.Factory, diff *DiffProgram, options *DiffOptions, from, to string) error { + openapi, err := f.OpenAPISchema() + if err != nil { + return err + } + parser := &parse.Factory{Resources: openapi} + + differ, err := NewDiffer(from, to) + if err != nil { + return err + } + defer differ.TearDown() + + printer := Printer{} + + cmdNamespace, enforceNamespace, err := f.DefaultNamespace() + if err != nil { + return err + } + + r := f.NewBuilder(). + Unstructured(). + NamespaceParam(cmdNamespace).DefaultNamespace(). + FilenameParam(enforceNamespace, &options.FilenameOptions). + Flatten(). + Do() + if err := r.Err(); err != nil { + return err + } + + err = r.Visit(func(info *resource.Info, err error) error { + if err != nil { + return err + } + + if err := info.Get(); err != nil { + if !errors.IsNotFound(err) { + return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%v\nfrom server for:", info), info.Source, err) + } + info.Object = nil + } + + obj := InfoObject{ + Info: info, + Parser: parser, + Encoder: f.JSONEncoder(), + } + + return differ.Diff(obj, printer) + }) + if err != nil { + return err + } + + differ.Run(diff) + + return nil +} diff --git a/pkg/kubectl/cmd/diff_test.go b/pkg/kubectl/cmd/diff_test.go new file mode 100644 index 00000000000..4400023ff3e --- /dev/null +++ b/pkg/kubectl/cmd/diff_test.go @@ -0,0 +1,285 @@ +/* +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 cmd + +import ( + "bytes" + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" + "testing" + + "k8s.io/utils/exec" +) + +type FakeObject struct { + name string + local map[string]interface{} + merged map[string]interface{} + live map[string]interface{} + last map[string]interface{} +} + +var _ Object = &FakeObject{} + +func (f *FakeObject) Name() string { + return f.name +} + +func (f *FakeObject) Local() (map[string]interface{}, error) { + return f.local, nil +} + +func (f *FakeObject) Merged() (map[string]interface{}, error) { + return f.merged, nil +} + +func (f *FakeObject) Live() (map[string]interface{}, error) { + return f.live, nil +} + +func (f *FakeObject) Last() (map[string]interface{}, error) { + return f.last, nil +} + +func TestArguments(t *testing.T) { + tests := []struct { + // Input + args []string + + // Outputs + from string + to string + err string + }{ + // Defaults + { + args: []string{}, + from: "LOCAL", + to: "LIVE", + err: "", + }, + // One valid argument + { + args: []string{"MERGED"}, + from: "MERGED", + to: "LIVE", + err: "", + }, + // One invalid argument + { + args: []string{"WRONG"}, + from: "", + to: "", + err: `Invalid parameter "WRONG", must be either "LOCAL", "LIVE", "LAST" or "MERGED"`, + }, + // Two valid arguments + { + args: []string{"MERGED", "LAST"}, + from: "MERGED", + to: "LAST", + err: "", + }, + // Two same arguments is fine + { + args: []string{"MERGED", "MERGED"}, + from: "MERGED", + to: "MERGED", + err: "", + }, + // Second argument is invalid + { + args: []string{"MERGED", "WRONG"}, + from: "", + to: "", + err: `Invalid parameter "WRONG", must be either "LOCAL", "LIVE", "LAST" or "MERGED"`, + }, + // Three arguments + { + args: []string{"MERGED", "LIVE", "LAST"}, + from: "", + to: "", + err: `Invalid number of arguments: expected at most 2.`, + }, + } + + for _, test := range tests { + from, to, e := parseDiffArguments(test.args) + err := "" + if e != nil { + err = e.Error() + } + if from != test.from || to != test.to || err != test.err { + t.Errorf("parseDiffArguments(%v) = (%v, %v, %v), expected (%v, %v, %v)", + test.args, + from, to, err, + test.from, test.to, test.err, + ) + } + } +} + +func TestDiffProgram(t *testing.T) { + os.Setenv("KUBERNETES_EXTERNAL_DIFF", "echo") + stdout := bytes.Buffer{} + diff := DiffProgram{ + Stdout: &stdout, + Stderr: &bytes.Buffer{}, + Exec: exec.New(), + } + err := diff.Run("one", "two") + if err != nil { + t.Fatal(err) + } + if output := stdout.String(); output != "one two\n" { + t.Fatalf(`stdout = %q, expected "one two\n"`, output) + } +} + +func TestPrinter(t *testing.T) { + printer := Printer{} + + obj := map[string]interface{}{ + "string": "string", + "list": []int{1, 2, 3}, + "int": 12, + } + buf := bytes.Buffer{} + printer.Print(obj, &buf) + want := `int: 12 +list: +- 1 +- 2 +- 3 +string: string +` + if buf.String() != want { + t.Errorf("Print() = %q, want %q", buf.String(), want) + } +} + +func TestDiffVersion(t *testing.T) { + diff, err := NewDiffVersion("LOCAL") + if err != nil { + t.Fatal(err) + } + defer diff.Dir.Delete() + + obj := FakeObject{ + name: "bla", + local: map[string]interface{}{"local": true}, + last: map[string]interface{}{"last": true}, + live: map[string]interface{}{"live": true}, + merged: map[string]interface{}{"merged": true}, + } + err = diff.Print(&obj, Printer{}) + if err != nil { + t.Fatal(err) + } + fcontent, err := ioutil.ReadFile(path.Join(diff.Dir.Name, obj.Name())) + if err != nil { + t.Fatal(err) + } + econtent := "local: true\n" + if string(fcontent) != econtent { + t.Fatalf("File has %q, expected %q", string(fcontent), econtent) + } +} + +func TestDirectory(t *testing.T) { + dir, err := CreateDirectory("prefix") + defer dir.Delete() + if err != nil { + t.Fatal(err) + } + _, err = os.Stat(dir.Name) + if err != nil { + t.Fatal(err) + } + if !strings.HasPrefix(filepath.Base(dir.Name), "prefix") { + t.Fatalf(`Directory doesn't start with "prefix": %q`, dir.Name) + } + entries, err := ioutil.ReadDir(dir.Name) + if err != nil { + t.Fatal(err) + } + if len(entries) != 0 { + t.Fatalf("Directory should be empty, has %d elements", len(entries)) + } + _, err = dir.NewFile("ONE") + if err != nil { + t.Fatal(err) + } + _, err = dir.NewFile("TWO") + if err != nil { + t.Fatal(err) + } + entries, err = ioutil.ReadDir(dir.Name) + if err != nil { + t.Fatal(err) + } + if len(entries) != 2 { + t.Fatalf("ReadDir should have two elements, has %d elements", len(entries)) + } + err = dir.Delete() + if err != nil { + t.Fatal(err) + } + _, err = os.Stat(dir.Name) + if err == nil { + t.Fatal("Directory should be gone, still present.") + } +} + +func TestDiffer(t *testing.T) { + diff, err := NewDiffer("LOCAL", "LIVE") + if err != nil { + t.Fatal(err) + } + defer diff.TearDown() + + obj := FakeObject{ + name: "bla", + local: map[string]interface{}{"local": true}, + last: map[string]interface{}{"last": true}, + live: map[string]interface{}{"live": true}, + merged: map[string]interface{}{"merged": true}, + } + err = diff.Diff(&obj, Printer{}) + if err != nil { + t.Fatal(err) + } + fcontent, err := ioutil.ReadFile(path.Join(diff.From.Dir.Name, obj.Name())) + if err != nil { + t.Fatal(err) + } + econtent := "local: true\n" + if string(fcontent) != econtent { + t.Fatalf("File has %q, expected %q", string(fcontent), econtent) + } + + fcontent, err = ioutil.ReadFile(path.Join(diff.To.Dir.Name, obj.Name())) + if err != nil { + t.Fatal(err) + } + econtent = "live: true\n" + if string(fcontent) != econtent { + t.Fatalf("File has %q, expected %q", string(fcontent), econtent) + } +} diff --git a/pkg/kubectl/cmd/drain.go b/pkg/kubectl/cmd/drain.go index 7d0a9b4ba08..ba5ae1f1a72 100644 --- a/pkg/kubectl/cmd/drain.go +++ b/pkg/kubectl/cmd/drain.go @@ -28,21 +28,21 @@ import ( "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" + policyv1beta1 "k8s.io/api/policy/v1beta1" apierrors "k8s.io/apimachinery/pkg/api/errors" "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/types" "k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/policy" - "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -51,7 +51,7 @@ import ( ) type DrainOptions struct { - client internalclientset.Interface + client kubernetes.Interface restClient *restclient.RESTClient Factory cmdutil.Factory Force bool @@ -62,6 +62,7 @@ type DrainOptions struct { backOff clockwork.Clock DeleteLocalData bool Selector string + PodSelector string mapper meta.RESTMapper nodeInfos []*resource.Info Out io.Writer @@ -71,7 +72,7 @@ type DrainOptions struct { // Takes a pod and returns a bool indicating whether or not to operate on the // pod, an optional warning message, and an optional fatal error. -type podFilter func(api.Pod) (include bool, w *warning, f *fatal) +type podFilter func(corev1.Pod) (include bool, w *warning, f *fatal) type warning struct { string } @@ -198,6 +199,8 @@ func NewCmdDrain(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { cmd.Flags().IntVar(&options.GracePeriodSeconds, "grace-period", -1, "Period of time in seconds given to each pod to terminate gracefully. If negative, the default value specified in the pod will be used.") cmd.Flags().DurationVar(&options.Timeout, "timeout", 0, "The length of time to wait before giving up, zero means infinite") cmd.Flags().StringVarP(&options.Selector, "selector", "l", options.Selector, "Selector (label query) to filter on") + cmd.Flags().StringVarP(&options.PodSelector, "pod-selector", "", options.PodSelector, "Label selector to filter pods on the node") + cmdutil.AddDryRunFlag(cmd) return cmd } @@ -220,10 +223,16 @@ func (o *DrainOptions) SetupDrain(cmd *cobra.Command, args []string) error { o.DryRun = cmdutil.GetFlagBool(cmd, "dry-run") - if o.client, err = o.Factory.ClientSet(); err != nil { + if o.client, err = o.Factory.KubernetesClientSet(); err != nil { return err } + if len(o.PodSelector) > 0 { + if _, err := labels.Parse(o.PodSelector); err != nil { + return errors.New("--pod-selector= must be a valid label selector") + } + } + o.restClient, err = o.Factory.RESTClient() if err != nil { return err @@ -237,20 +246,19 @@ func (o *DrainOptions) SetupDrain(cmd *cobra.Command, args []string) error { return err } - nameArgs := []string{"nodes"} - if len(args) > 0 { - nameArgs = append(nameArgs, args[0]) - if strings.Contains(args[0], "/") { - nameArgs = []string{args[0]} - } + builder := o.Factory.NewBuilder(). + Internal(). + NamespaceParam(cmdNamespace).DefaultNamespace(). + ResourceNames("nodes", args...). + SingleResourceType(). + Flatten() + + if len(o.Selector) > 0 { + builder = builder.LabelSelectorParam(o.Selector). + ResourceTypes("nodes") } - r := o.Factory.NewBuilder(). - NamespaceParam(cmdNamespace).DefaultNamespace(). - SelectorParam(o.Selector). - ResourceTypeOrNameArgs(true, nameArgs...). - Flatten(). - Do() + r := builder.Do() if err = r.Err(); err != nil { return err @@ -260,6 +268,10 @@ func (o *DrainOptions) SetupDrain(cmd *cobra.Command, args []string) error { if err != nil { return err } + if info.Mapping.Resource != "nodes" { + return fmt.Errorf("error: expected resource of type node, got %q", info.Mapping.Resource) + } + o.nodeInfos = append(o.nodeInfos, info) return nil }) @@ -281,7 +293,7 @@ func (o *DrainOptions) RunDrain() error { } if err == nil || o.DryRun { drainedNodes.Insert(info.Name) - cmdutil.PrintSuccess(o.mapper, false, o.Out, "node", info.Name, o.DryRun, "drained") + o.Factory.PrintSuccess(o.mapper, false, o.Out, "node", info.Name, o.DryRun, "drained") } else { fmt.Fprintf(o.ErrOut, "error: unable to drain node %q, aborting command...\n\n", info.Name) remainingNodes := []string{} @@ -326,64 +338,28 @@ func (o *DrainOptions) deleteOrEvictPodsSimple(nodeInfo *resource.Info) error { return err } -func (o *DrainOptions) getController(namespace string, controllerRef *metav1.OwnerReference) (interface{}, error) { - switch controllerRef.Kind { - case "ReplicationController": - return o.client.Core().ReplicationControllers(namespace).Get(controllerRef.Name, metav1.GetOptions{}) - case "DaemonSet": - return o.client.Extensions().DaemonSets(namespace).Get(controllerRef.Name, metav1.GetOptions{}) - case "Job": - return o.client.Batch().Jobs(namespace).Get(controllerRef.Name, metav1.GetOptions{}) - case "ReplicaSet": - return o.client.Extensions().ReplicaSets(namespace).Get(controllerRef.Name, metav1.GetOptions{}) - case "StatefulSet": - return o.client.Apps().StatefulSets(namespace).Get(controllerRef.Name, metav1.GetOptions{}) - } - return nil, fmt.Errorf("Unknown controller kind %q", controllerRef.Kind) +func (o *DrainOptions) getPodController(pod corev1.Pod) *metav1.OwnerReference { + return metav1.GetControllerOf(&pod) } -func (o *DrainOptions) getPodController(pod api.Pod) (*metav1.OwnerReference, error) { - controllerRef := metav1.GetControllerOf(&pod) - if controllerRef == nil { - return nil, nil - } - - // We assume the only reason for an error is because the controller is - // gone/missing, not for any other cause. - // TODO(mml): something more sophisticated than this - // TODO(juntee): determine if it's safe to remove getController(), - // so that drain can work for controller types that we don't know about - _, err := o.getController(pod.Namespace, controllerRef) - if err != nil { - return nil, err - } - return controllerRef, nil -} - -func (o *DrainOptions) unreplicatedFilter(pod api.Pod) (bool, *warning, *fatal) { +func (o *DrainOptions) unreplicatedFilter(pod corev1.Pod) (bool, *warning, *fatal) { // any finished pod can be removed - if pod.Status.Phase == api.PodSucceeded || pod.Status.Phase == api.PodFailed { + if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed { return true, nil, nil } - controllerRef, err := o.getPodController(pod) - if err != nil { - // if we're forcing, remove orphaned pods with a warning - if apierrors.IsNotFound(err) && o.Force { - return true, &warning{err.Error()}, nil - } - return false, nil, &fatal{err.Error()} - } + controllerRef := o.getPodController(pod) if controllerRef != nil { return true, nil, nil } - if !o.Force { - return false, nil, &fatal{kUnmanagedFatal} + if o.Force { + return true, &warning{kUnmanagedWarning}, nil } - return true, &warning{kUnmanagedWarning}, nil + + return false, nil, &fatal{kUnmanagedFatal} } -func (o *DrainOptions) daemonsetFilter(pod api.Pod) (bool, *warning, *fatal) { +func (o *DrainOptions) daemonsetFilter(pod corev1.Pod) (bool, *warning, *fatal) { // Note that we return false in cases where the pod is DaemonSet managed, // regardless of flags. We never delete them, the only question is whether // their presence constitutes an error. @@ -391,34 +367,34 @@ func (o *DrainOptions) daemonsetFilter(pod api.Pod) (bool, *warning, *fatal) { // The exception is for pods that are orphaned (the referencing // management resource - including DaemonSet - is not found). // Such pods will be deleted if --force is used. - controllerRef, err := o.getPodController(pod) - if err != nil { - // if we're forcing, remove orphaned pods with a warning + controllerRef := o.getPodController(pod) + if controllerRef == nil || controllerRef.Kind != "DaemonSet" { + return true, nil, nil + } + + if _, err := o.client.ExtensionsV1beta1().DaemonSets(pod.Namespace).Get(controllerRef.Name, metav1.GetOptions{}); err != nil { + // remove orphaned pods with a warning if --force is used if apierrors.IsNotFound(err) && o.Force { return true, &warning{err.Error()}, nil } return false, nil, &fatal{err.Error()} } - if controllerRef == nil || controllerRef.Kind != "DaemonSet" { - return true, nil, nil - } - if _, err := o.client.Extensions().DaemonSets(pod.Namespace).Get(controllerRef.Name, metav1.GetOptions{}); err != nil { - return false, nil, &fatal{err.Error()} - } + if !o.IgnoreDaemonsets { return false, nil, &fatal{kDaemonsetFatal} } + return false, &warning{kDaemonsetWarning}, nil } -func mirrorPodFilter(pod api.Pod) (bool, *warning, *fatal) { +func mirrorPodFilter(pod corev1.Pod) (bool, *warning, *fatal) { if _, found := pod.ObjectMeta.Annotations[corev1.MirrorPodAnnotationKey]; found { return false, nil, nil } return true, nil, nil } -func hasLocalStorage(pod api.Pod) bool { +func hasLocalStorage(pod corev1.Pod) bool { for _, volume := range pod.Spec.Volumes { if volume.EmptyDir != nil { return true @@ -428,7 +404,7 @@ func hasLocalStorage(pod api.Pod) bool { return false } -func (o *DrainOptions) localStorageFilter(pod api.Pod) (bool, *warning, *fatal) { +func (o *DrainOptions) localStorageFilter(pod corev1.Pod) (bool, *warning, *fatal) { if !hasLocalStorage(pod) { return true, nil, nil } @@ -452,8 +428,14 @@ func (ps podStatuses) Message() string { // getPodsForDeletion receives resource info for a node, and returns all the pods from the given node that we // are planning on deleting. If there are any pods preventing us from deleting, we return that list in an error. -func (o *DrainOptions) getPodsForDeletion(nodeInfo *resource.Info) (pods []api.Pod, err error) { - podList, err := o.client.Core().Pods(metav1.NamespaceAll).List(metav1.ListOptions{ +func (o *DrainOptions) getPodsForDeletion(nodeInfo *resource.Info) (pods []corev1.Pod, err error) { + labelSelector, err := labels.Parse(o.PodSelector) + if err != nil { + return pods, err + } + + podList, err := o.client.CoreV1().Pods(metav1.NamespaceAll).List(metav1.ListOptions{ + LabelSelector: labelSelector.String(), FieldSelector: fields.SelectorFromSet(fields.Set{"spec.nodeName": nodeInfo.Name}).String()}) if err != nil { return pods, err @@ -481,7 +463,7 @@ func (o *DrainOptions) getPodsForDeletion(nodeInfo *resource.Info) (pods []api.P } if len(fs) > 0 { - return []api.Pod{}, errors.New(fs.Message()) + return []corev1.Pod{}, errors.New(fs.Message()) } if len(ws) > 0 { fmt.Fprintf(o.ErrOut, "WARNING: %s\n", ws.Message()) @@ -489,22 +471,22 @@ func (o *DrainOptions) getPodsForDeletion(nodeInfo *resource.Info) (pods []api.P return pods, nil } -func (o *DrainOptions) deletePod(pod api.Pod) error { +func (o *DrainOptions) deletePod(pod corev1.Pod) error { deleteOptions := &metav1.DeleteOptions{} if o.GracePeriodSeconds >= 0 { gracePeriodSeconds := int64(o.GracePeriodSeconds) deleteOptions.GracePeriodSeconds = &gracePeriodSeconds } - return o.client.Core().Pods(pod.Namespace).Delete(pod.Name, deleteOptions) + return o.client.CoreV1().Pods(pod.Namespace).Delete(pod.Name, deleteOptions) } -func (o *DrainOptions) evictPod(pod api.Pod, policyGroupVersion string) error { +func (o *DrainOptions) evictPod(pod corev1.Pod, policyGroupVersion string) error { deleteOptions := &metav1.DeleteOptions{} if o.GracePeriodSeconds >= 0 { gracePeriodSeconds := int64(o.GracePeriodSeconds) deleteOptions.GracePeriodSeconds = &gracePeriodSeconds } - eviction := &policy.Eviction{ + eviction := &policyv1beta1.Eviction{ TypeMeta: metav1.TypeMeta{ APIVersion: policyGroupVersion, Kind: EvictionKind, @@ -516,11 +498,11 @@ func (o *DrainOptions) evictPod(pod api.Pod, policyGroupVersion string) error { DeleteOptions: deleteOptions, } // Remember to change change the URL manipulation func when Evction's version change - return o.client.Policy().Evictions(eviction.Namespace).Evict(eviction) + return o.client.PolicyV1beta1().Evictions(eviction.Namespace).Evict(eviction) } // deleteOrEvictPods deletes or evicts the pods on the api server -func (o *DrainOptions) deleteOrEvictPods(pods []api.Pod) error { +func (o *DrainOptions) deleteOrEvictPods(pods []corev1.Pod) error { if len(pods) == 0 { return nil } @@ -530,8 +512,8 @@ func (o *DrainOptions) deleteOrEvictPods(pods []api.Pod) error { return err } - getPodFn := func(namespace, name string) (*api.Pod, error) { - return o.client.Core().Pods(namespace).Get(name, metav1.GetOptions{}) + getPodFn := func(namespace, name string) (*corev1.Pod, error) { + return o.client.CoreV1().Pods(namespace).Get(name, metav1.GetOptions{}) } if len(policyGroupVersion) > 0 { @@ -541,12 +523,12 @@ func (o *DrainOptions) deleteOrEvictPods(pods []api.Pod) error { } } -func (o *DrainOptions) evictPods(pods []api.Pod, policyGroupVersion string, getPodFn func(namespace, name string) (*api.Pod, error)) error { +func (o *DrainOptions) evictPods(pods []corev1.Pod, policyGroupVersion string, getPodFn func(namespace, name string) (*corev1.Pod, error)) error { doneCh := make(chan bool, len(pods)) errCh := make(chan error, 1) for _, pod := range pods { - go func(pod api.Pod, doneCh chan bool, errCh chan error) { + go func(pod corev1.Pod, doneCh chan bool, errCh chan error) { var err error for { err = o.evictPod(pod, policyGroupVersion) @@ -562,7 +544,7 @@ func (o *DrainOptions) evictPods(pods []api.Pod, policyGroupVersion string, getP return } } - podArray := []api.Pod{pod} + podArray := []corev1.Pod{pod} _, err = o.waitForDelete(podArray, kubectl.Interval, time.Duration(math.MaxInt64), true, getPodFn) if err == nil { doneCh <- true @@ -595,7 +577,7 @@ func (o *DrainOptions) evictPods(pods []api.Pod, policyGroupVersion string, getP } } -func (o *DrainOptions) deletePods(pods []api.Pod, getPodFn func(namespace, name string) (*api.Pod, error)) error { +func (o *DrainOptions) deletePods(pods []corev1.Pod, getPodFn func(namespace, name string) (*corev1.Pod, error)) error { // 0 timeout means infinite, we use MaxInt64 to represent it. var globalTimeout time.Duration if o.Timeout == 0 { @@ -613,7 +595,7 @@ func (o *DrainOptions) deletePods(pods []api.Pod, getPodFn func(namespace, name return err } -func (o *DrainOptions) waitForDelete(pods []api.Pod, interval, timeout time.Duration, usingEviction bool, getPodFn func(string, string) (*api.Pod, error)) ([]api.Pod, error) { +func (o *DrainOptions) waitForDelete(pods []corev1.Pod, interval, timeout time.Duration, usingEviction bool, getPodFn func(string, string) (*corev1.Pod, error)) ([]corev1.Pod, error) { var verbStr string if usingEviction { verbStr = "evicted" @@ -621,11 +603,11 @@ func (o *DrainOptions) waitForDelete(pods []api.Pod, interval, timeout time.Dura verbStr = "deleted" } err := wait.PollImmediate(interval, timeout, func() (bool, error) { - pendingPods := []api.Pod{} + pendingPods := []corev1.Pod{} for i, pod := range pods { p, err := getPodFn(pod.Namespace, pod.Name) if apierrors.IsNotFound(err) || (p != nil && p.ObjectMeta.UID != pod.ObjectMeta.UID) { - cmdutil.PrintSuccess(o.mapper, false, o.Out, "pod", pod.Name, false, verbStr) + o.Factory.PrintSuccess(o.mapper, false, o.Out, "pod", pod.Name, false, verbStr) continue } else if err != nil { return false, err @@ -644,7 +626,7 @@ func (o *DrainOptions) waitForDelete(pods []api.Pod, interval, timeout time.Dura // SupportEviction uses Discovery API to find out if the server support eviction subresource // If support, it will return its groupVersion; Otherwise, it will return "" -func SupportEviction(clientset internalclientset.Interface) (string, error) { +func SupportEviction(clientset kubernetes.Interface) (string, error) { discoveryClient := clientset.Discovery() groupList, err := discoveryClient.ServerGroups() if err != nil { @@ -706,7 +688,7 @@ func (o *DrainOptions) RunCordonOrUncordon(desired bool) error { } unsched := node.Spec.Unschedulable if unsched == desired { - cmdutil.PrintSuccess(o.mapper, false, o.Out, nodeInfo.Mapping.Resource, nodeInfo.Name, o.DryRun, already(desired)) + o.Factory.PrintSuccess(o.mapper, false, o.Out, nodeInfo.Mapping.Resource, nodeInfo.Name, o.DryRun, already(desired)) } else { if !o.DryRun { helper := resource.NewHelper(o.restClient, nodeInfo.Mapping) @@ -727,10 +709,10 @@ func (o *DrainOptions) RunCordonOrUncordon(desired bool) error { continue } } - cmdutil.PrintSuccess(o.mapper, false, o.Out, nodeInfo.Mapping.Resource, nodeInfo.Name, o.DryRun, changed(desired)) + o.Factory.PrintSuccess(o.mapper, false, o.Out, nodeInfo.Mapping.Resource, nodeInfo.Name, o.DryRun, changed(desired)) } } else { - cmdutil.PrintSuccess(o.mapper, false, o.Out, nodeInfo.Mapping.Resource, nodeInfo.Name, o.DryRun, "skipped") + o.Factory.PrintSuccess(o.mapper, false, o.Out, nodeInfo.Mapping.Resource, nodeInfo.Name, o.DryRun, "skipped") } } diff --git a/pkg/kubectl/cmd/drain_test.go b/pkg/kubectl/cmd/drain_test.go index 9cd2e330b28..5200e95cd89 100644 --- a/pkg/kubectl/cmd/drain_test.go +++ b/pkg/kubectl/cmd/drain_test.go @@ -33,7 +33,8 @@ import ( "github.com/spf13/cobra" - "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" + policyv1beta1 "k8s.io/api/policy/v1beta1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -42,12 +43,12 @@ import ( "k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/ref" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/apis/batch" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" - "k8s.io/kubernetes/pkg/apis/policy" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) @@ -57,22 +58,22 @@ const ( DeleteMethod = "Delete" ) -var node *v1.Node -var cordoned_node *v1.Node +var node *corev1.Node +var cordoned_node *corev1.Node func boolptr(b bool) *bool { return &b } func TestMain(m *testing.M) { // Create a node. - node = &v1.Node{ + node = &corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: "node", CreationTimestamp: metav1.Time{Time: time.Now()}, }, - Spec: v1.NodeSpec{ + Spec: corev1.NodeSpec{ ExternalID: "node", }, - Status: v1.NodeStatus{}, + Status: corev1.NodeStatus{}, } // A copy of the same node, but cordoned. @@ -84,8 +85,8 @@ func TestMain(m *testing.M) { func TestCordon(t *testing.T) { tests := []struct { description string - node *v1.Node - expected *v1.Node + node *corev1.Node + expected *corev1.Node cmd func(cmdutil.Factory, io.Writer) *cobra.Command arg string expectFatal bool @@ -150,10 +151,10 @@ func TestCordon(t *testing.T) { for _, test := range tests { f, tf, codec, ns := cmdtesting.NewAPIFactory() - new_node := &v1.Node{} + new_node := &corev1.Node{} updated := false tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { m := &MyReq{req} @@ -172,7 +173,7 @@ func TestCordon(t *testing.T) { if err != nil { t.Fatalf("%s: unexpected error: %v", test.description, err) } - appliedPatch, err := strategicpatch.StrategicMergePatch(oldJSON, data, &v1.Node{}) + appliedPatch, err := strategicpatch.StrategicMergePatch(oldJSON, data, &corev1.Node{}) if err != nil { t.Fatalf("%s: unexpected error: %v", test.description, err) } @@ -246,17 +247,13 @@ func TestDrain(t *testing.T) { }, } - rc_anno := make(map[string]string) - rc_anno[api.CreatedByAnnotation] = refJson(t, &rc) - - rc_pod := api.Pod{ + rc_pod := corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "default", CreationTimestamp: metav1.Time{Time: time.Now()}, Labels: labels, SelfLink: testapi.Default.SelfLink("pods", "bar"), - Annotations: rc_anno, OwnerReferences: []metav1.OwnerReference{ { APIVersion: "v1", @@ -268,7 +265,7 @@ func TestDrain(t *testing.T) { }, }, }, - Spec: api.PodSpec{ + Spec: corev1.PodSpec{ NodeName: "node", }, } @@ -285,17 +282,13 @@ func TestDrain(t *testing.T) { }, } - ds_anno := make(map[string]string) - ds_anno[api.CreatedByAnnotation] = refJson(t, &ds) - - ds_pod := api.Pod{ + ds_pod := corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "default", CreationTimestamp: metav1.Time{Time: time.Now()}, Labels: labels, SelfLink: testapi.Default.SelfLink("pods", "bar"), - Annotations: ds_anno, OwnerReferences: []metav1.OwnerReference{ { APIVersion: "extensions/v1beta1", @@ -306,45 +299,20 @@ func TestDrain(t *testing.T) { }, }, }, - Spec: api.PodSpec{ + Spec: corev1.PodSpec{ NodeName: "node", }, } - missing_ds := extensions.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "missing-ds", - Namespace: "default", - CreationTimestamp: metav1.Time{Time: time.Now()}, - SelfLink: testapi.Default.SelfLink("daemonsets", "missing-ds"), - }, - Spec: extensions.DaemonSetSpec{ - Selector: &metav1.LabelSelector{MatchLabels: labels}, - }, - } - - missing_ds_anno := make(map[string]string) - missing_ds_anno[api.CreatedByAnnotation] = refJson(t, &missing_ds) - - orphaned_ds_pod := api.Pod{ + orphaned_ds_pod := corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "default", CreationTimestamp: metav1.Time{Time: time.Now()}, Labels: labels, SelfLink: testapi.Default.SelfLink("pods", "bar"), - Annotations: missing_ds_anno, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "extensions/v1beta1", - Kind: "DaemonSet", - Name: "missing-ds", - BlockOwnerDeletion: boolptr(true), - Controller: boolptr(true), - }, - }, }, - Spec: api.PodSpec{ + Spec: corev1.PodSpec{ NodeName: "node", }, } @@ -361,14 +329,13 @@ func TestDrain(t *testing.T) { }, } - job_pod := api.Pod{ + job_pod := corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "default", CreationTimestamp: metav1.Time{Time: time.Now()}, Labels: labels, SelfLink: testapi.Default.SelfLink("pods", "bar"), - Annotations: map[string]string{api.CreatedByAnnotation: refJson(t, &job)}, OwnerReferences: []metav1.OwnerReference{ { APIVersion: "v1", @@ -394,17 +361,13 @@ func TestDrain(t *testing.T) { }, } - rs_anno := make(map[string]string) - rs_anno[api.CreatedByAnnotation] = refJson(t, &rs) - - rs_pod := api.Pod{ + rs_pod := corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "default", CreationTimestamp: metav1.Time{Time: time.Now()}, Labels: labels, SelfLink: testapi.Default.SelfLink("pods", "bar"), - Annotations: rs_anno, OwnerReferences: []metav1.OwnerReference{ { APIVersion: "v1", @@ -415,36 +378,36 @@ func TestDrain(t *testing.T) { }, }, }, - Spec: api.PodSpec{ + Spec: corev1.PodSpec{ NodeName: "node", }, } - naked_pod := api.Pod{ + naked_pod := corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "default", CreationTimestamp: metav1.Time{Time: time.Now()}, Labels: labels, }, - Spec: api.PodSpec{ + Spec: corev1.PodSpec{ NodeName: "node", }, } - emptydir_pod := api.Pod{ + emptydir_pod := corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "default", CreationTimestamp: metav1.Time{Time: time.Now()}, Labels: labels, }, - Spec: api.PodSpec{ + Spec: corev1.PodSpec{ NodeName: "node", - Volumes: []api.Volume{ + Volumes: []corev1.Volume{ { Name: "scratch", - VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{Medium: ""}}, + VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{Medium: ""}}, }, }, }, @@ -452,9 +415,9 @@ func TestDrain(t *testing.T) { tests := []struct { description string - node *v1.Node - expected *v1.Node - pods []api.Pod + node *corev1.Node + expected *corev1.Node + pods []corev1.Pod rcs []api.ReplicationController replicaSets []extensions.ReplicaSet args []string @@ -465,7 +428,7 @@ func TestDrain(t *testing.T) { description: "RC-managed pod", node: node, expected: cordoned_node, - pods: []api.Pod{rc_pod}, + pods: []corev1.Pod{rc_pod}, rcs: []api.ReplicationController{rc}, args: []string{"node"}, expectFatal: false, @@ -475,7 +438,7 @@ func TestDrain(t *testing.T) { description: "DS-managed pod", node: node, expected: cordoned_node, - pods: []api.Pod{ds_pod}, + pods: []corev1.Pod{ds_pod}, rcs: []api.ReplicationController{rc}, args: []string{"node"}, expectFatal: true, @@ -485,7 +448,7 @@ func TestDrain(t *testing.T) { description: "orphaned DS-managed pod", node: node, expected: cordoned_node, - pods: []api.Pod{orphaned_ds_pod}, + pods: []corev1.Pod{orphaned_ds_pod}, rcs: []api.ReplicationController{}, args: []string{"node"}, expectFatal: true, @@ -495,7 +458,7 @@ func TestDrain(t *testing.T) { description: "orphaned DS-managed pod with --force", node: node, expected: cordoned_node, - pods: []api.Pod{orphaned_ds_pod}, + pods: []corev1.Pod{orphaned_ds_pod}, rcs: []api.ReplicationController{}, args: []string{"node", "--force"}, expectFatal: false, @@ -505,7 +468,7 @@ func TestDrain(t *testing.T) { description: "DS-managed pod with --ignore-daemonsets", node: node, expected: cordoned_node, - pods: []api.Pod{ds_pod}, + pods: []corev1.Pod{ds_pod}, rcs: []api.ReplicationController{rc}, args: []string{"node", "--ignore-daemonsets"}, expectFatal: false, @@ -515,7 +478,7 @@ func TestDrain(t *testing.T) { description: "Job-managed pod", node: node, expected: cordoned_node, - pods: []api.Pod{job_pod}, + pods: []corev1.Pod{job_pod}, rcs: []api.ReplicationController{rc}, args: []string{"node"}, expectFatal: false, @@ -525,7 +488,7 @@ func TestDrain(t *testing.T) { description: "RS-managed pod", node: node, expected: cordoned_node, - pods: []api.Pod{rs_pod}, + pods: []corev1.Pod{rs_pod}, replicaSets: []extensions.ReplicaSet{rs}, args: []string{"node"}, expectFatal: false, @@ -535,7 +498,7 @@ func TestDrain(t *testing.T) { description: "naked pod", node: node, expected: cordoned_node, - pods: []api.Pod{naked_pod}, + pods: []corev1.Pod{naked_pod}, rcs: []api.ReplicationController{}, args: []string{"node"}, expectFatal: true, @@ -545,7 +508,7 @@ func TestDrain(t *testing.T) { description: "naked pod with --force", node: node, expected: cordoned_node, - pods: []api.Pod{naked_pod}, + pods: []corev1.Pod{naked_pod}, rcs: []api.ReplicationController{}, args: []string{"node", "--force"}, expectFatal: false, @@ -555,7 +518,7 @@ func TestDrain(t *testing.T) { description: "pod with EmptyDir", node: node, expected: cordoned_node, - pods: []api.Pod{emptydir_pod}, + pods: []corev1.Pod{emptydir_pod}, args: []string{"node", "--force"}, expectFatal: true, expectDelete: false, @@ -564,7 +527,7 @@ func TestDrain(t *testing.T) { description: "pod with EmptyDir and --delete-local-data", node: node, expected: cordoned_node, - pods: []api.Pod{emptydir_pod}, + pods: []corev1.Pod{emptydir_pod}, args: []string{"node", "--force", "--delete-local-data=true"}, expectFatal: false, expectDelete: true, @@ -573,7 +536,7 @@ func TestDrain(t *testing.T) { description: "empty node", node: node, expected: cordoned_node, - pods: []api.Pod{}, + pods: []corev1.Pod{}, rcs: []api.ReplicationController{rc}, args: []string{"node"}, expectFatal: false, @@ -591,12 +554,12 @@ func TestDrain(t *testing.T) { currMethod = DeleteMethod } for _, test := range tests { - new_node := &v1.Node{} + new_node := &corev1.Node{} deleted := false evicted := false f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { m := &MyReq{req} @@ -644,7 +607,7 @@ func TestDrain(t *testing.T) { case m.isFor("GET", "/namespaces/default/replicasets/rs"): return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(testapi.Extensions.Codec(), &test.replicaSets[0])}, nil case m.isFor("GET", "/namespaces/default/pods/bar"): - return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: objBody(codec, &api.Pod{})}, nil + return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: objBody(codec, &corev1.Pod{})}, nil case m.isFor("GET", "/pods"): values, err := url.ParseQuery(req.URL.RawQuery) if err != nil { @@ -655,7 +618,7 @@ func TestDrain(t *testing.T) { if !reflect.DeepEqual(get_params, values) { t.Fatalf("%s: expected:\n%v\nsaw:\n%v\n", test.description, get_params, values) } - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &api.PodList{Items: test.pods})}, nil + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &corev1.PodList{Items: test.pods})}, nil case m.isFor("GET", "/replicationcontrollers"): return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &api.ReplicationControllerList{Items: test.rcs})}, nil case m.isFor("PATCH", "/nodes/node"): @@ -668,7 +631,7 @@ func TestDrain(t *testing.T) { if err != nil { t.Fatalf("%s: unexpected error: %v", test.description, err) } - appliedPatch, err := strategicpatch.StrategicMergePatch(oldJSON, data, &v1.Node{}) + appliedPatch, err := strategicpatch.StrategicMergePatch(oldJSON, data, &corev1.Node{}) if err != nil { t.Fatalf("%s: unexpected error: %v", test.description, err) } @@ -684,7 +647,7 @@ func TestDrain(t *testing.T) { return &http.Response{StatusCode: 204, Header: defaultHeader(), Body: objBody(codec, &test.pods[0])}, nil case m.isFor("POST", "/namespaces/default/pods/bar/eviction"): evicted = true - return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: policyObjBody(&policy.Eviction{})}, nil + return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: policyObjBody(&policyv1beta1.Eviction{})}, nil default: t.Fatalf("%s: unexpected request: %v %#v\n%#v", test.description, req.Method, req.URL, req) return nil, nil @@ -743,7 +706,7 @@ func TestDeletePods(t *testing.T) { expectPendingPods bool expectError bool expectedError *error - getPodFn func(namespace, name string) (*api.Pod, error) + getPodFn func(namespace, name string) (*corev1.Pod, error) }{ { description: "Wait for deleting to complete", @@ -752,7 +715,7 @@ func TestDeletePods(t *testing.T) { expectPendingPods: false, expectError: false, expectedError: nil, - getPodFn: func(namespace, name string) (*api.Pod, error) { + getPodFn: func(namespace, name string) (*corev1.Pod, error) { oldPodMap, _ := createPods(false) newPodMap, _ := createPods(true) if oldPod, found := oldPodMap[name]; found { @@ -777,7 +740,7 @@ func TestDeletePods(t *testing.T) { expectPendingPods: true, expectError: true, expectedError: &wait.ErrWaitTimeout, - getPodFn: func(namespace, name string) (*api.Pod, error) { + getPodFn: func(namespace, name string) (*corev1.Pod, error) { oldPodMap, _ := createPods(false) if oldPod, found := oldPodMap[name]; found { return &oldPod, nil @@ -792,7 +755,7 @@ func TestDeletePods(t *testing.T) { expectPendingPods: true, expectError: true, expectedError: nil, - getPodFn: func(namespace, name string) (*api.Pod, error) { + getPodFn: func(namespace, name string) (*corev1.Pod, error) { return nil, errors.New("This is a random error for testing") }, }, @@ -800,7 +763,7 @@ func TestDeletePods(t *testing.T) { for _, test := range tests { f, _, _, _ := cmdtesting.NewAPIFactory() - o := DrainOptions{} + o := DrainOptions{Factory: f} o.mapper, _ = f.Object() o.Out = os.Stdout _, pods := createPods(false) @@ -827,9 +790,9 @@ func TestDeletePods(t *testing.T) { } } -func createPods(ifCreateNewPods bool) (map[string]api.Pod, []api.Pod) { - podMap := make(map[string]api.Pod) - podSlice := []api.Pod{} +func createPods(ifCreateNewPods bool) (map[string]corev1.Pod, []corev1.Pod) { + podMap := make(map[string]corev1.Pod) + podSlice := []corev1.Pod{} for i := 0; i < 8; i++ { var uid types.UID if ifCreateNewPods { @@ -837,7 +800,7 @@ func createPods(ifCreateNewPods bool) (map[string]api.Pod, []api.Pod) { } else { uid = types.UID(strconv.Itoa(i) + strconv.Itoa(i)) } - pod := api.Pod{ + pod := corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "pod" + strconv.Itoa(i), Namespace: "default", @@ -865,7 +828,7 @@ func (m *MyReq) isFor(method string, path string) bool { } func refJson(t *testing.T, o runtime.Object) string { - ref, err := ref.GetReference(api.Scheme, o) + ref, err := ref.GetReference(legacyscheme.Scheme, o) if err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/pkg/kubectl/cmd/edit_test.go b/pkg/kubectl/cmd/edit_test.go index ef3be5e40d1..eef95fa5a26 100644 --- a/pkg/kubectl/cmd/edit_test.go +++ b/pkg/kubectl/cmd/edit_test.go @@ -36,7 +36,6 @@ import ( "k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" @@ -194,95 +193,94 @@ func TestEdit(t *testing.T) { } for _, testcaseName := range testcases.List() { - t.Logf("Running testcase: %s", testcaseName) - i = 0 - name = testcaseName - testcase = EditTestCase{} - testcaseDir := filepath.Join("testdata", "edit", "testcase-"+name) - testcaseData, err := ioutil.ReadFile(filepath.Join(testcaseDir, "test.yaml")) - if err != nil { - t.Fatalf("%s: %v", name, err) - } - if err := yaml.Unmarshal(testcaseData, &testcase); err != nil { - t.Fatalf("%s: %v", name, err) - } - - f, tf, _, _ := cmdtesting.NewAPIFactory() - tf.Printer = &testPrinter{} - tf.UnstructuredClientForMappingFunc = func(mapping *meta.RESTMapping) (resource.RESTClient, error) { - versionedAPIPath := "" - if mapping.GroupVersionKind.Group == "" { - versionedAPIPath = "/api/" + mapping.GroupVersionKind.Version - } else { - versionedAPIPath = "/apis/" + mapping.GroupVersionKind.Group + "/" + mapping.GroupVersionKind.Version + t.Run(testcaseName, func(t *testing.T) { + i = 0 + name = testcaseName + testcase = EditTestCase{} + testcaseDir := filepath.Join("testdata", "edit", "testcase-"+name) + testcaseData, err := ioutil.ReadFile(filepath.Join(testcaseDir, "test.yaml")) + if err != nil { + t.Fatalf("%s: %v", name, err) + } + if err := yaml.Unmarshal(testcaseData, &testcase); err != nil { + t.Fatalf("%s: %v", name, err) } - return &fake.RESTClient{ - APIRegistry: api.Registry, - VersionedAPIPath: versionedAPIPath, - NegotiatedSerializer: unstructuredSerializer, - Client: fake.CreateHTTPClient(reqResp), - }, nil - } - if len(testcase.Namespace) > 0 { - tf.Namespace = testcase.Namespace - } - tf.ClientConfig = defaultClientConfig() - tf.Command = "edit test cmd invocation" - buf := bytes.NewBuffer([]byte{}) - errBuf := bytes.NewBuffer([]byte{}) + f, tf, _, _ := cmdtesting.NewAPIFactory() + tf.Printer = &testPrinter{} + tf.UnstructuredClientForMappingFunc = func(mapping *meta.RESTMapping) (resource.RESTClient, error) { + versionedAPIPath := "" + if mapping.GroupVersionKind.Group == "" { + versionedAPIPath = "/api/" + mapping.GroupVersionKind.Version + } else { + versionedAPIPath = "/apis/" + mapping.GroupVersionKind.Group + "/" + mapping.GroupVersionKind.Version + } + return &fake.RESTClient{ + VersionedAPIPath: versionedAPIPath, + NegotiatedSerializer: unstructuredSerializer, + Client: fake.CreateHTTPClient(reqResp), + }, nil + } - var cmd *cobra.Command - switch testcase.Mode { - case "edit": - cmd = NewCmdEdit(f, buf, errBuf) - case "create": - cmd = NewCmdCreate(f, buf, errBuf) - cmd.Flags().Set("edit", "true") - case "edit-last-applied": - cmd = NewCmdApplyEditLastApplied(f, buf, errBuf) - default: - t.Errorf("%s: unexpected mode %s", name, testcase.Mode) - continue - } - if len(testcase.Filename) > 0 { - cmd.Flags().Set("filename", filepath.Join(testcaseDir, testcase.Filename)) - } - if len(testcase.Output) > 0 { - cmd.Flags().Set("output", testcase.Output) - } - if len(testcase.OutputPatch) > 0 { - cmd.Flags().Set("output-patch", testcase.OutputPatch) - } - if len(testcase.SaveConfig) > 0 { - cmd.Flags().Set("save-config", testcase.SaveConfig) - } + if len(testcase.Namespace) > 0 { + tf.Namespace = testcase.Namespace + } + tf.ClientConfig = defaultClientConfig() + tf.Command = "edit test cmd invocation" + buf := bytes.NewBuffer([]byte{}) + errBuf := bytes.NewBuffer([]byte{}) - cmdutil.BehaviorOnFatal(func(str string, code int) { - errBuf.WriteString(str) - if testcase.ExpectedExitCode != code { - t.Errorf("%s: expected exit code %d, got %d: %s", name, testcase.ExpectedExitCode, code, str) + var cmd *cobra.Command + switch testcase.Mode { + case "edit": + cmd = NewCmdEdit(f, buf, errBuf) + case "create": + cmd = NewCmdCreate(f, buf, errBuf) + cmd.Flags().Set("edit", "true") + case "edit-last-applied": + cmd = NewCmdApplyEditLastApplied(f, buf, errBuf) + default: + t.Fatalf("%s: unexpected mode %s", name, testcase.Mode) + } + if len(testcase.Filename) > 0 { + cmd.Flags().Set("filename", filepath.Join(testcaseDir, testcase.Filename)) + } + if len(testcase.Output) > 0 { + cmd.Flags().Set("output", testcase.Output) + } + if len(testcase.OutputPatch) > 0 { + cmd.Flags().Set("output-patch", testcase.OutputPatch) + } + if len(testcase.SaveConfig) > 0 { + cmd.Flags().Set("save-config", testcase.SaveConfig) + } + + cmdutil.BehaviorOnFatal(func(str string, code int) { + errBuf.WriteString(str) + if testcase.ExpectedExitCode != code { + t.Errorf("%s: expected exit code %d, got %d: %s", name, testcase.ExpectedExitCode, code, str) + } + }) + + cmd.Run(cmd, testcase.Args) + + stdout := buf.String() + stderr := errBuf.String() + + for _, s := range testcase.ExpectedStdout { + if !strings.Contains(stdout, s) { + t.Errorf("%s: expected to see '%s' in stdout\n\nstdout:\n%s\n\nstderr:\n%s", name, s, stdout, stderr) + } + } + for _, s := range testcase.ExpectedStderr { + if !strings.Contains(stderr, s) { + t.Errorf("%s: expected to see '%s' in stderr\n\nstdout:\n%s\n\nstderr:\n%s", name, s, stdout, stderr) + } + } + if i < len(testcase.Steps) { + t.Errorf("%s: saw %d steps, testcase included %d additional steps that were not exercised", name, i, len(testcase.Steps)-i) } }) - - cmd.Run(cmd, testcase.Args) - - stdout := buf.String() - stderr := errBuf.String() - - for _, s := range testcase.ExpectedStdout { - if !strings.Contains(stdout, s) { - t.Errorf("%s: expected to see '%s' in stdout\n\nstdout:\n%s\n\nstderr:\n%s", name, s, stdout, stderr) - } - } - for _, s := range testcase.ExpectedStderr { - if !strings.Contains(stderr, s) { - t.Errorf("%s: expected to see '%s' in stderr\n\nstdout:\n%s\n\nstderr:\n%s", name, s, stdout, stderr) - } - } - if i < len(testcase.Steps) { - t.Errorf("%s: saw %d steps, testcase included %d additional steps that were not exercised", name, i, len(testcase.Steps)-i) - } } } diff --git a/pkg/kubectl/cmd/exec.go b/pkg/kubectl/cmd/exec.go index 10f65b0bb73..cca3d61a928 100644 --- a/pkg/kubectl/cmd/exec.go +++ b/pkg/kubectl/cmd/exec.go @@ -27,7 +27,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/remotecommand" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -166,20 +167,20 @@ func (p *ExecOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn []s } } - cmdParent := cmd.Parent() - if cmdParent != nil { - p.FullCmdName = cmdParent.CommandPath() - } - if len(p.FullCmdName) > 0 && cmdutil.IsSiblingCommandExists(cmd, "describe") { - p.SuggestedCmdUsage = fmt.Sprintf("Use '%s describe pod/%s' to see all of the containers in this pod.", p.FullCmdName, p.PodName) - } - namespace, _, err := f.DefaultNamespace() if err != nil { return err } p.Namespace = namespace + cmdParent := cmd.Parent() + if cmdParent != nil { + p.FullCmdName = cmdParent.CommandPath() + } + if len(p.FullCmdName) > 0 && cmdutil.IsSiblingCommandExists(cmd, "describe") { + p.SuggestedCmdUsage = fmt.Sprintf("Use '%s describe pod/%s -n %s' to see all of the containers in this pod.", p.FullCmdName, p.PodName, p.Namespace) + } + config, err := f.ClientConfig() if err != nil { return err @@ -320,7 +321,7 @@ func (p *ExecOptions) Run() error { Stdout: p.Out != nil, Stderr: p.Err != nil, TTY: t.Raw, - }, api.ParameterCodec) + }, legacyscheme.ParameterCodec) return p.Executor.Execute("POST", req.URL(), p.Config, p.In, p.Out, p.Err, t.Raw, sizeQueue) } diff --git a/pkg/kubectl/cmd/exec_test.go b/pkg/kubectl/cmd/exec_test.go index 6c57bbd80c0..afa52ca634b 100644 --- a/pkg/kubectl/cmd/exec_test.go +++ b/pkg/kubectl/cmd/exec_test.go @@ -33,7 +33,7 @@ import ( restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" "k8s.io/client-go/tools/remotecommand" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" "k8s.io/kubernetes/pkg/kubectl/util/term" ) @@ -130,7 +130,6 @@ func TestPodAndContainer(t *testing.T) { for _, test := range tests { f, tf, _, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { return nil, nil }), } @@ -162,7 +161,7 @@ func TestPodAndContainer(t *testing.T) { } func TestExec(t *testing.T) { - version := api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version + version := "v1" tests := []struct { name, podPath, execPath, container string pod *api.Pod @@ -185,7 +184,6 @@ func TestExec(t *testing.T) { for _, test := range tests { f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { diff --git a/pkg/kubectl/cmd/explain.go b/pkg/kubectl/cmd/explain.go index 55d095690b5..d1ecf3d65d4 100644 --- a/pkg/kubectl/cmd/explain.go +++ b/pkg/kubectl/cmd/explain.go @@ -23,18 +23,24 @@ import ( "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/explain" + "k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/kubectl/util/i18n" ) var ( explainLong = templates.LongDesc(` - Documentation of resources. - - ` + validResources) + List the fields for supported resources + + This command describes the fields associated with each supported API resource. + Fields are identified via a simple JSONPath identifier: + + .[.] + + Add the --recursive flag to display all of the fields at once without descriptions. + Information about each field is retrieved from the server in OpenAPI format.`) explainExamples = templates.Examples(i18n.T(` # Get the documentation of the resource and its fields @@ -49,7 +55,7 @@ func NewCmdExplain(f cmdutil.Factory, out, cmdErr io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "explain RESOURCE", Short: i18n.T("Documentation of resources"), - Long: explainLong, + Long: explainLong + "\n\n" + cmdutil.ValidResourceTypeList(f), Example: explainExamples, Run: func(cmd *cobra.Command, args []string) { err := RunExplain(f, out, cmdErr, cmd, args) @@ -65,7 +71,7 @@ func NewCmdExplain(f cmdutil.Factory, out, cmdErr io.Writer) *cobra.Command { // RunExplain executes the appropriate steps to print a model's documentation func RunExplain(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, args []string) error { if len(args) == 0 { - fmt.Fprintf(cmdErr, "You must specify the type of resource to explain. %s\n", validResources) + fmt.Fprintf(cmdErr, "You must specify the type of resource to explain. %s\n", cmdutil.ValidResourceTypeList(f)) return cmdutil.UsageErrorf(cmd, "Required resource not specified.") } if len(args) > 1 { @@ -99,7 +105,7 @@ func RunExplain(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, ar } if len(apiVersionString) == 0 { - groupMeta, err := api.Registry.Group(gvk.Group) + groupMeta, err := scheme.Registry.Group(gvk.Group) if err != nil { return err } @@ -123,5 +129,5 @@ func RunExplain(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, ar return fmt.Errorf("Couldn't find resource for %q", gvk) } - return explain.PrintModelDescription(fieldsPath, out, schema, recursive) + return explain.PrintModelDescription(fieldsPath, out, schema, gvk, recursive) } diff --git a/pkg/kubectl/cmd/expose.go b/pkg/kubectl/cmd/expose.go index 6ae43873763..ed84c1d76b7 100644 --- a/pkg/kubectl/cmd/expose.go +++ b/pkg/kubectl/cmd/expose.go @@ -98,7 +98,7 @@ func NewCmdExposeService(f cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().String("generator", "service/v2", i18n.T("The name of the API generator to use. There are 2 generators: 'service/v1' and 'service/v2'. The only difference between them is that service port in v1 is named 'default', while it is left unnamed in v2. Default is 'service/v2'.")) cmd.Flags().String("protocol", "", i18n.T("The network protocol for the service to be created. Default is 'TCP'.")) cmd.Flags().String("port", "", i18n.T("The port that the service should serve on. Copied from the resource being exposed, if unspecified")) - cmd.Flags().String("type", "", i18n.T("Type for this service: ClusterIP, NodePort, or LoadBalancer. Default is 'ClusterIP'.")) + cmd.Flags().String("type", "", i18n.T("Type for this service: ClusterIP, NodePort, LoadBalancer, or ExternalName. Default is 'ClusterIP'.")) cmd.Flags().String("load-balancer-ip", "", i18n.T("IP to assign to the Load Balancer. If empty, an ephemeral IP will be created and used (cloud-provider specific).")) cmd.Flags().String("selector", "", i18n.T("A label selector to use for this service. Only equality-based selector requirements are supported. If empty (the default) infer the selector from the replication controller or replica set.)")) cmd.Flags().StringP("labels", "l", "", "Labels to apply to the service created by this call.") @@ -127,6 +127,7 @@ func RunExpose(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri mapper, typer := f.Object() r := f.NewBuilder(). + Internal(). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). FilenameParam(enforceNamespace, options). @@ -256,7 +257,7 @@ func RunExpose(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri if len(cmdutil.GetFlagString(cmd, "output")) > 0 { return f.PrintObject(cmd, false, mapper, object, out) } - cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, true, "exposed") + f.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, true, "exposed") return nil } if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info, f.JSONEncoder()); err != nil { @@ -273,7 +274,7 @@ func RunExpose(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri return f.PrintObject(cmd, false, mapper, object, out) } - cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, false, "exposed") + f.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, false, "exposed") return nil }) if err != nil { diff --git a/pkg/kubectl/cmd/expose_test.go b/pkg/kubectl/cmd/expose_test.go index fdf67367fe1..edcc75ee868 100644 --- a/pkg/kubectl/cmd/expose_test.go +++ b/pkg/kubectl/cmd/expose_test.go @@ -25,13 +25,20 @@ 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/intstr" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" + "k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/printers" ) +// This init should be removed after switching this command and its tests to user external types. +func init() { + api.AddToScheme(scheme.Scheme) +} + func TestRunExposeService(t *testing.T) { tests := []struct { name string @@ -462,7 +469,7 @@ func TestRunExposeService(t *testing.T) { f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Printer = &printers.JSONPrinter{} tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Version: "v1"}, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { diff --git a/pkg/kubectl/cmd/get.go b/pkg/kubectl/cmd/get.go deleted file mode 100644 index 79f03b6a231..00000000000 --- a/pkg/kubectl/cmd/get.go +++ /dev/null @@ -1,638 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cmd - -import ( - "encoding/json" - "fmt" - "io" - "strings" - - "github.com/golang/glog" - "github.com/spf13/cobra" - - kapierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - utilerrors "k8s.io/apimachinery/pkg/util/errors" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/kubectl" - "k8s.io/kubernetes/pkg/kubectl/cmd/templates" - cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" - "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" - "k8s.io/kubernetes/pkg/kubectl/resource" - "k8s.io/kubernetes/pkg/kubectl/util/i18n" - "k8s.io/kubernetes/pkg/printers" - "k8s.io/kubernetes/pkg/util/interrupt" -) - -// GetOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of -// referencing the cmd.Flags() -type GetOptions struct { - resource.FilenameOptions - - IgnoreNotFound bool - Raw string -} - -var ( - getLong = templates.LongDesc(` - Display one or many resources. - - ` + validResources + ` - - This command will hide resources that have completed, such as pods that are - in the Succeeded or Failed phases. You can see the full results for any - resource by providing the '--show-all' flag, but this flag does not include - the uninitialized objects by default, unless '--include-uninitialized' is explicitly set. - - By specifying the output as 'template' and providing a Go template as the value - of the --template flag, you can filter the attributes of the fetched resources.`) - - getExample = templates.Examples(i18n.T(` - # List all pods in ps output format. - kubectl get pods - - # List all pods in ps output format with more information (such as node name). - kubectl get pods -o wide - - # List a single replication controller with specified NAME in ps output format. - kubectl get replicationcontroller web - - # List a single pod in JSON output format. - kubectl get -o json pod web-pod-13je7 - - # List a pod identified by type and name specified in "pod.yaml" in JSON output format. - kubectl get -f pod.yaml -o json - - # Return only the phase value of the specified pod. - kubectl get -o template pod/web-pod-13je7 --template={{.status.phase}} - - # List all replication controllers and services together in ps output format. - kubectl get rc,services - - # List one or more resources by their type and names. - kubectl get rc/web service/frontend pods/web-pod-13je7 - - # List all resources with different types. - kubectl get all`)) -) - -const ( - useOpenAPIPrintColumnFlagLabel = "experimental-use-openapi-print-columns" -) - -// NewCmdGet creates a command object for the generic "get" action, which -// retrieves one or more resources from a server. -func NewCmdGet(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Command { - options := &GetOptions{} - - // retrieve a list of handled resources from printer as valid args - validArgs, argAliases := []string{}, []string{} - p, err := f.Printer(nil, printers.PrintOptions{ - ColumnLabels: []string{}, - }) - cmdutil.CheckErr(err) - if p != nil { - validArgs = p.HandledResources() - argAliases = kubectl.ResourceAliases(validArgs) - } - - cmd := &cobra.Command{ - Use: "get [(-o|--output=)json|yaml|wide|custom-columns=...|custom-columns-file=...|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=...] (TYPE [NAME | -l label] | TYPE/NAME ...) [flags]", - Short: i18n.T("Display one or many resources"), - Long: getLong, - Example: getExample, - Run: func(cmd *cobra.Command, args []string) { - err := RunGet(f, out, errOut, cmd, args, options) - cmdutil.CheckErr(err) - }, - SuggestFor: []string{"list", "ps"}, - ValidArgs: validArgs, - ArgAliases: argAliases, - } - cmdutil.AddPrinterFlags(cmd) - cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") - cmdutil.AddIncludeUninitializedFlag(cmd) - cmd.Flags().BoolP("watch", "w", false, "After listing/getting the requested object, watch for changes. Uninitialized objects are excluded if no object name is provided.") - cmd.Flags().Bool("watch-only", false, "Watch for changes to the requested object(s), without listing/getting first.") - cmd.Flags().Bool("show-kind", false, "If present, list the resource type for the requested object(s).") - cmd.Flags().Bool("all-namespaces", false, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.") - cmd.Flags().BoolVar(&options.IgnoreNotFound, "ignore-not-found", false, "Treat \"resource not found\" as a successful retrieval.") - cmd.Flags().StringSliceP("label-columns", "L", []string{}, "Accepts a comma separated list of labels that are going to be presented as columns. Names are case-sensitive. You can also use multiple flag options like -L label1 -L label2...") - cmd.Flags().Bool("export", false, "If true, use 'export' for the resources. Exported resources are stripped of cluster-specific information.") - addOpenAPIPrintColumnFlags(cmd) - usage := "identifying the resource to get from a server." - cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage) - cmdutil.AddInclude3rdPartyFlags(cmd) - cmd.Flags().StringVar(&options.Raw, "raw", options.Raw, "Raw URI to request from the server. Uses the transport specified by the kubeconfig file.") - return cmd -} - -// RunGet implements the generic Get command -// TODO: convert all direct flag accessors to a struct and pass that instead of cmd -func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args []string, options *GetOptions) error { - if len(options.Raw) > 0 { - restClient, err := f.RESTClient() - if err != nil { - return err - } - - stream, err := restClient.Get().RequestURI(options.Raw).Stream() - if err != nil { - return err - } - defer stream.Close() - - _, err = io.Copy(out, stream) - if err != nil && err != io.EOF { - return err - } - return nil - } - - mapper, typer, err := f.UnstructuredObject() - if err != nil { - return err - } - - selector := cmdutil.GetFlagString(cmd, "selector") - allNamespaces := cmdutil.GetFlagBool(cmd, "all-namespaces") - showKind := cmdutil.GetFlagBool(cmd, "show-kind") - builder := f.NewBuilder().Unstructured(f.UnstructuredClientForMapping, mapper, typer) - - cmdNamespace, enforceNamespace, err := f.DefaultNamespace() - if err != nil { - return err - } - - if allNamespaces { - enforceNamespace = false - } - - if len(args) == 0 && cmdutil.IsFilenameSliceEmpty(options.Filenames) { - fmt.Fprint(errOut, "You must specify the type of resource to get. ", validResources) - - fullCmdName := cmd.Parent().CommandPath() - usageString := "Required resource not specified." - if len(fullCmdName) > 0 && cmdutil.IsSiblingCommandExists(cmd, "explain") { - usageString = fmt.Sprintf("%s\nUse \"%s explain \" for a detailed description of that resource (e.g. %[2]s explain pods).", usageString, fullCmdName) - } - - return cmdutil.UsageErrorf(cmd, usageString) - } - - export := cmdutil.GetFlagBool(cmd, "export") - - filterFuncs := f.DefaultResourceFilterFunc() - filterOpts := f.DefaultResourceFilterOptions(cmd, allNamespaces) - - // handle watch separately since we cannot watch multiple resource types - isWatch, isWatchOnly := cmdutil.GetFlagBool(cmd, "watch"), cmdutil.GetFlagBool(cmd, "watch-only") - - var includeUninitialized bool - if isWatch && len(args) == 2 { - // include the uninitialized one for watching on a single object - // unless explicitly set --include-uninitialized=false - includeUninitialized = true - } - includeUninitialized = cmdutil.ShouldIncludeUninitialized(cmd, includeUninitialized) - - if isWatch || isWatchOnly { - r := f.NewBuilder(). - Unstructured(f.UnstructuredClientForMapping, mapper, typer). - NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces). - FilenameParam(enforceNamespace, &options.FilenameOptions). - SelectorParam(selector). - ExportParam(export). - IncludeUninitialized(includeUninitialized). - ResourceTypeOrNameArgs(true, args...). - SingleResourceType(). - Latest(). - Do() - err = r.Err() - if err != nil { - return err - } - infos, err := r.Infos() - if err != nil { - return err - } - if len(infos) != 1 { - return i18n.Errorf("watch is only supported on individual resources and resource collections - %d resources were found", len(infos)) - } - if r.TargetsSingleItems() { - filterFuncs = nil - } - info := infos[0] - mapping := info.ResourceMapping() - printer, err := f.PrinterForMapping(cmd, false, nil, mapping, allNamespaces) - if err != nil { - return err - } - obj, err := r.Object() - if err != nil { - return err - } - - // watching from resourceVersion 0, starts the watch at ~now and - // will return an initial watch event. Starting form ~now, rather - // the rv of the object will insure that we start the watch from - // inside the watch window, which the rv of the object might not be. - rv := "0" - isList := meta.IsListType(obj) - if isList { - // the resourceVersion of list objects is ~now but won't return - // an initial watch event - rv, err = mapping.MetadataAccessor.ResourceVersion(obj) - if err != nil { - return err - } - } - - // print the current object - if !isWatchOnly { - var objsToPrint []runtime.Object - writer := printers.GetNewTabWriter(out) - - if isList { - objsToPrint, _ = meta.ExtractList(obj) - } else { - objsToPrint = append(objsToPrint, obj) - } - for _, objToPrint := range objsToPrint { - if isFiltered, err := filterFuncs.Filter(objToPrint, filterOpts); !isFiltered { - if err != nil { - glog.V(2).Infof("Unable to filter resource: %v", err) - } else if err := printer.PrintObj(objToPrint, writer); err != nil { - return fmt.Errorf("unable to output the provided object: %v", err) - } - } - } - writer.Flush() - } - - // print watched changes - w, err := r.Watch(rv) - if err != nil { - return err - } - - first := true - intr := interrupt.New(nil, w.Stop) - intr.Run(func() error { - _, err := watch.Until(0, w, func(e watch.Event) (bool, error) { - if !isList && first { - // drop the initial watch event in the single resource case - first = false - return false, nil - } - - if isFiltered, err := filterFuncs.Filter(e.Object, filterOpts); !isFiltered { - if err != nil { - glog.V(2).Infof("Unable to filter resource: %v", err) - } else if err := printer.PrintObj(e.Object, out); err != nil { - return false, err - } - } - return false, nil - }) - return err - }) - return nil - } - - r := builder. - NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces). - FilenameParam(enforceNamespace, &options.FilenameOptions). - SelectorParam(selector). - ExportParam(export). - IncludeUninitialized(includeUninitialized). - ResourceTypeOrNameArgs(true, args...). - ContinueOnError(). - Latest(). - Flatten(). - Do() - err = r.Err() - if err != nil { - return err - } - - printer, err := f.PrinterForCommand(cmd, false, nil, printers.PrintOptions{}) - if err != nil { - return err - } - if r.TargetsSingleItems() { - filterFuncs = nil - } - if options.IgnoreNotFound { - r.IgnoreErrors(kapierrors.IsNotFound) - } - - if printer.IsGeneric() { - // we flattened the data from the builder, so we have individual items, but now we'd like to either: - // 1. if there is more than one item, combine them all into a single list - // 2. if there is a single item and that item is a list, leave it as its specific list - // 3. if there is a single item and it is not a a list, leave it as a single item - var errs []error - singleItemImplied := false - infos, err := r.IntoSingleItemImplied(&singleItemImplied).Infos() - if err != nil { - if singleItemImplied { - return err - } - errs = append(errs, err) - } - - if len(infos) == 0 && options.IgnoreNotFound { - return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs))) - } - - var obj runtime.Object - if !singleItemImplied || len(infos) > 1 { - // we have more than one item, so coerce all items into a list - // we have more than one item, so coerce all items into a list. - // we don't want an *unstructured.Unstructured list yet, as we - // may be dealing with non-unstructured objects. Compose all items - // into an api.List, and then decode using an unstructured scheme. - list := api.List{ - TypeMeta: metav1.TypeMeta{ - Kind: "List", - APIVersion: "v1", - }, - ListMeta: metav1.ListMeta{}, - } - for _, info := range infos { - list.Items = append(list.Items, info.Object) - } - - listData, err := json.Marshal(list) - if err != nil { - return err - } - - converted, err := runtime.Decode(unstructured.UnstructuredJSONScheme, listData) - if err != nil { - return err - } - - obj = converted - } else { - obj = infos[0].Object - } - - isList := meta.IsListType(obj) - if isList { - _, items, err := cmdutil.FilterResourceList(obj, filterFuncs, filterOpts) - if err != nil { - return err - } - - // take the filtered items and create a new list for display - list := &unstructured.UnstructuredList{ - Object: map[string]interface{}{ - "kind": "List", - "apiVersion": "v1", - "metadata": map[string]interface{}{}, - }, - } - if listMeta, err := meta.ListAccessor(obj); err == nil { - list.Object["metadata"] = map[string]interface{}{ - "selfLink": listMeta.GetSelfLink(), - "resourceVersion": listMeta.GetResourceVersion(), - } - } - - for _, item := range items { - list.Items = append(list.Items, *item.(*unstructured.Unstructured)) - } - if err := printer.PrintObj(list, out); err != nil { - errs = append(errs, err) - } - return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs))) - } - - if isFiltered, err := filterFuncs.Filter(obj, filterOpts); !isFiltered { - if err != nil { - glog.V(2).Infof("Unable to filter resource: %v", err) - } else if err := printer.PrintObj(obj, out); err != nil { - errs = append(errs, err) - } - } - - return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs))) - } - - allErrs := []error{} - errs := sets.NewString() - infos, err := r.Infos() - if err != nil { - allErrs = append(allErrs, err) - } - - objs := make([]runtime.Object, len(infos)) - for ix := range infos { - objs[ix] = infos[ix].Object - } - - sorting, err := cmd.Flags().GetString("sort-by") - if err != nil { - return err - } - var sorter *kubectl.RuntimeSort - if len(sorting) > 0 && len(objs) > 1 { - // TODO: questionable - if sorter, err = kubectl.SortObjects(f.Decoder(true), objs, sorting); err != nil { - return err - } - } - - // use the default printer for each object - printer = nil - var lastMapping *meta.RESTMapping - w := printers.GetNewTabWriter(out) - - useOpenAPIPrintColumns := cmdutil.GetFlagBool(cmd, useOpenAPIPrintColumnFlagLabel) - - if resource.MultipleTypesRequested(args) || cmdutil.MustPrintWithKinds(objs, infos, sorter) { - showKind = true - } - - filteredResourceCount := 0 - for ix := range objs { - var mapping *meta.RESTMapping - var original runtime.Object - - if sorter != nil { - mapping = infos[sorter.OriginalPosition(ix)].Mapping - original = infos[sorter.OriginalPosition(ix)].Object - } else { - mapping = infos[ix].Mapping - original = infos[ix].Object - } - if shouldGetNewPrinterForMapping(printer, lastMapping, mapping) { - if printer != nil { - w.Flush() - } - - var outputOpts *printers.OutputOptions - // if cmd does not specify output format and useOpenAPIPrintColumnFlagLabel flag is true, - // then get the default output options for this mapping from OpenAPI schema. - if !cmdSpecifiesOutputFmt(cmd) && useOpenAPIPrintColumns { - outputOpts, _ = outputOptsForMappingFromOpenAPI(f, mapping) - } - - printer, err = f.PrinterForMapping(cmd, false, outputOpts, mapping, allNamespaces) - if err != nil { - if !errs.Has(err.Error()) { - errs.Insert(err.Error()) - allErrs = append(allErrs, err) - } - continue - } - - // add linebreak between resource groups (if there is more than one) - // skip linebreak above first resource group - noHeaders := cmdutil.GetFlagBool(cmd, "no-headers") - if lastMapping != nil && !noHeaders { - fmt.Fprintf(errOut, "%s\n", "") - } - - lastMapping = mapping - } - - // try to convert before apply filter func - decodedObj, _ := kubectl.DecodeUnknownObject(original) - - // filter objects if filter has been defined for current object - if isFiltered, err := filterFuncs.Filter(decodedObj, filterOpts); isFiltered { - if err == nil { - filteredResourceCount++ - continue - } - if !errs.Has(err.Error()) { - errs.Insert(err.Error()) - allErrs = append(allErrs, err) - } - } - - if resourcePrinter, found := printer.(*printers.HumanReadablePrinter); found { - resourceName := resourcePrinter.GetResourceKind() - if mapping != nil { - if resourceName == "" { - resourceName = mapping.Resource - } - if alias, ok := kubectl.ResourceShortFormFor(mapping.Resource); ok { - resourceName = alias - } else if resourceName == "" { - resourceName = "none" - } - } else { - resourceName = "none" - } - - if showKind { - resourcePrinter.EnsurePrintWithKind(resourceName) - } - - if err := printer.PrintObj(decodedObj, w); err != nil { - if !errs.Has(err.Error()) { - errs.Insert(err.Error()) - allErrs = append(allErrs, err) - } - } - continue - } - objToPrint := decodedObj - if printer.IsGeneric() { - // use raw object as recieved from the builder when using generic - // printer instead of decodedObj - objToPrint = original - } - if err := printer.PrintObj(objToPrint, w); err != nil { - if !errs.Has(err.Error()) { - errs.Insert(err.Error()) - allErrs = append(allErrs, err) - } - continue - } - } - w.Flush() - cmdutil.PrintFilterCount(errOut, len(objs), filteredResourceCount, len(allErrs), filterOpts, options.IgnoreNotFound) - return utilerrors.NewAggregate(allErrs) -} - -func addOpenAPIPrintColumnFlags(cmd *cobra.Command) { - cmd.Flags().Bool(useOpenAPIPrintColumnFlagLabel, false, "If true, use x-kubernetes-print-column metadata (if present) from openapi schema for displaying a resource.") - // marking it deprecated so that it is hidden from usage/help text. - cmd.Flags().MarkDeprecated(useOpenAPIPrintColumnFlagLabel, "It's an experimental feature.") -} - -func shouldGetNewPrinterForMapping(printer printers.ResourcePrinter, lastMapping, mapping *meta.RESTMapping) bool { - return printer == nil || lastMapping == nil || mapping == nil || mapping.Resource != lastMapping.Resource -} - -func cmdSpecifiesOutputFmt(cmd *cobra.Command) bool { - return cmdutil.GetFlagString(cmd, "output") != "" -} - -// outputOptsForMappingFromOpenAPI looks for the output format metatadata in the -// openapi schema and returns the output options for the mapping if found. -func outputOptsForMappingFromOpenAPI(f cmdutil.Factory, mapping *meta.RESTMapping) (*printers.OutputOptions, bool) { - - // user has not specified any output format, check if OpenAPI has - // default specification to print this resource type - api, err := f.OpenAPISchema() - if err != nil { - // Error getting schema - return nil, false - } - // Found openapi metadata for this resource - schema := api.LookupResource(mapping.GroupVersionKind) - if schema == nil { - // Schema not found, return empty columns - return nil, false - } - - columns, found := openapi.GetPrintColumns(schema.GetExtensions()) - if !found { - // Extension not found, return empty columns - return nil, false - } - - return outputOptsFromStr(columns) -} - -// outputOptsFromStr parses the print-column metadata and generates printer.OutputOptions object. -func outputOptsFromStr(columnStr string) (*printers.OutputOptions, bool) { - if columnStr == "" { - return nil, false - } - parts := strings.SplitN(columnStr, "=", 2) - if len(parts) < 2 { - return nil, false - } - return &printers.OutputOptions{ - FmtType: parts[0], - FmtArg: parts[1], - AllowMissingKeys: true, - }, true -} diff --git a/pkg/kubectl/cmd/label.go b/pkg/kubectl/cmd/label.go index 01166db620d..786fbe6afcb 100644 --- a/pkg/kubectl/cmd/label.go +++ b/pkg/kubectl/cmd/label.go @@ -70,7 +70,8 @@ var ( labelLong = templates.LongDesc(i18n.T(` Update the labels on a resource. - * A label must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[1]d characters. + * A label key and value must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[1]d characters each. + * Optionally, the key can begin with a DNS subdomain prefix and a single '/', like example.com/my-app * If --overwrite is true, then existing labels can be overwritten, otherwise attempting to overwrite a label will result in an error. * If --resource-version is specified, then updates will use this resource version, otherwise the existing resource-version will be used.`)) @@ -191,6 +192,8 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error { includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false) b := f.NewBuilder(). + Unstructured(). + LocalParam(o.local). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &o.FilenameOptions). @@ -198,20 +201,9 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error { Flatten() if !o.local { - // call this method here, as it requires an api call - // and will cause the command to fail when there is - // no connection to a server - mapper, typer, err := f.UnstructuredObject() - if err != nil { - return err - } - - b = b.SelectorParam(o.selector). - Unstructured(f.UnstructuredClientForMapping, mapper, typer). + b = b.LabelSelectorParam(o.selector). ResourceTypeOrNameArgs(o.all, o.resources...). Latest() - } else { - b = b.Local(f.ClientForMapping) } one := false @@ -308,19 +300,10 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error { return nil } - var mapper meta.RESTMapper - if o.local { - mapper, _ = f.Object() - } else { - mapper, _, err = f.UnstructuredObject() - if err != nil { - return err - } - } if o.outputFormat != "" { - return f.PrintObject(cmd, o.local, mapper, outputObj, o.out) + return f.PrintObject(cmd, o.local, r.Mapper().RESTMapper, outputObj, o.out) } - cmdutil.PrintSuccess(mapper, false, o.out, info.Mapping.Resource, info.Name, o.dryrun, dataChangeMsg) + f.PrintSuccess(r.Mapper().RESTMapper, false, o.out, info.Mapping.Resource, info.Name, o.dryrun, dataChangeMsg) return nil }) } diff --git a/pkg/kubectl/cmd/label_test.go b/pkg/kubectl/cmd/label_test.go index 5140ca2bcf2..0aaf460e93d 100644 --- a/pkg/kubectl/cmd/label_test.go +++ b/pkg/kubectl/cmd/label_test.go @@ -23,11 +23,10 @@ import ( "strings" "testing" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" "k8s.io/kubernetes/pkg/kubectl/resource" ) @@ -165,7 +164,7 @@ func TestLabelFunc(t *testing.T) { expectErr bool }{ { - obj: &api.Pod{ + obj: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"a": "b"}, }, @@ -174,41 +173,41 @@ func TestLabelFunc(t *testing.T) { expectErr: true, }, { - obj: &api.Pod{ + obj: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"a": "b"}, }, }, labels: map[string]string{"a": "c"}, overwrite: true, - expected: &api.Pod{ + expected: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"a": "c"}, }, }, }, { - obj: &api.Pod{ + obj: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"a": "b"}, }, }, labels: map[string]string{"c": "d"}, - expected: &api.Pod{ + expected: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"a": "b", "c": "d"}, }, }, }, { - obj: &api.Pod{ + obj: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"a": "b"}, }, }, labels: map[string]string{"c": "d"}, version: "2", - expected: &api.Pod{ + expected: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"a": "b", "c": "d"}, ResourceVersion: "2", @@ -216,28 +215,28 @@ func TestLabelFunc(t *testing.T) { }, }, { - obj: &api.Pod{ + obj: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"a": "b"}, }, }, labels: map[string]string{}, remove: []string{"a"}, - expected: &api.Pod{ + expected: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{}, }, }, }, { - obj: &api.Pod{ + obj: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"a": "b", "c": "d"}, }, }, labels: map[string]string{"e": "f"}, remove: []string{"a"}, - expected: &api.Pod{ + expected: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{ "c": "d", @@ -247,11 +246,11 @@ func TestLabelFunc(t *testing.T) { }, }, { - obj: &api.Pod{ + obj: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{}, }, labels: map[string]string{"a": "b"}, - expected: &api.Pod{ + expected: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"a": "b"}, }, @@ -323,7 +322,7 @@ func TestLabelErrors(t *testing.T) { f, tf, _, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}} + tf.ClientConfig = defaultClientConfig() buf := bytes.NewBuffer([]byte{}) cmd := NewCmdLabel(f, buf) @@ -357,7 +356,6 @@ func TestLabelForResourceFromFile(t *testing.T) { pods, _, _ := testData() f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.Method { @@ -384,7 +382,7 @@ func TestLabelForResourceFromFile(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}} + tf.ClientConfig = defaultClientConfig() buf := bytes.NewBuffer([]byte{}) cmd := NewCmdLabel(f, buf) @@ -408,7 +406,6 @@ func TestLabelForResourceFromFile(t *testing.T) { func TestLabelLocal(t *testing.T) { f, tf, _, _ := cmdtesting.NewAPIFactory() tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) @@ -416,7 +413,7 @@ func TestLabelLocal(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}} + tf.ClientConfig = defaultClientConfig() buf := bytes.NewBuffer([]byte{}) cmd := NewCmdLabel(f, buf) @@ -442,7 +439,6 @@ func TestLabelMultipleObjects(t *testing.T) { pods, _, _ := testData() f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.Method { @@ -471,7 +467,7 @@ func TestLabelMultipleObjects(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}} + tf.ClientConfig = defaultClientConfig() buf := bytes.NewBuffer([]byte{}) cmd := NewCmdLabel(f, buf) diff --git a/pkg/kubectl/cmd/logs.go b/pkg/kubectl/cmd/logs.go index 8392c95babc..cccceb154bc 100644 --- a/pkg/kubectl/cmd/logs.go +++ b/pkg/kubectl/cmd/logs.go @@ -28,8 +28,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" restclient "k8s.io/client-go/rest" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/validation" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/validation" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/util" @@ -193,13 +193,14 @@ func (o *LogsOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.Comm if o.Object == nil { builder := f.NewBuilder(). + Internal(). NamespaceParam(o.Namespace).DefaultNamespace(). SingleResourceType() if o.ResourceArg != "" { builder.ResourceNames("pods", o.ResourceArg) } if selector != "" { - builder.ResourceTypes("pods").SelectorParam(selector) + builder.ResourceTypes("pods").LabelSelectorParam(selector) } infos, err := builder.Do().Infos() if err != nil { diff --git a/pkg/kubectl/cmd/logs_test.go b/pkg/kubectl/cmd/logs_test.go index 246edc9dda2..be3dc7cab11 100644 --- a/pkg/kubectl/cmd/logs_test.go +++ b/pkg/kubectl/cmd/logs_test.go @@ -27,10 +27,8 @@ import ( "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" ) @@ -51,7 +49,6 @@ func TestLog(t *testing.T) { logContent := "test log content" f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -69,7 +66,7 @@ func TestLog(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: api.Codecs, GroupVersion: &schema.GroupVersion{Version: test.version}}} + tf.ClientConfig = defaultClientConfig() buf := bytes.NewBuffer([]byte{}) cmd := NewCmdLogs(f, buf) @@ -110,6 +107,11 @@ func TestValidateLogFlags(t *testing.T) { flags: map[string]string{"since": "1h", "since-time": "2006-01-02T15:04:05Z"}, expected: "at most one of `sinceTime` or `sinceSeconds` may be specified", }, + { + name: "negative since-time", + flags: map[string]string{"since": "-1s"}, + expected: "must be greater than 0", + }, { name: "negative limit-bytes", flags: map[string]string{"limit-bytes": "-100"}, @@ -141,3 +143,59 @@ func TestValidateLogFlags(t *testing.T) { } } } + +func TestLogComplete(t *testing.T) { + f, _, _, _ := cmdtesting.NewAPIFactory() + + tests := []struct { + name string + args []string + flags map[string]string + expected string + }{ + { + name: "No args case", + flags: map[string]string{"selector": ""}, + expected: "'logs (POD | TYPE/NAME) [CONTAINER_NAME]'.\nPOD or TYPE/NAME is a required argument for the logs command", + }, + { + name: "One args case", + args: []string{"foo"}, + flags: map[string]string{"selector": "foo"}, + expected: "only a selector (-l) or a POD name is allowed", + }, + { + name: "Two args case", + args: []string{"foo", "foo1"}, + flags: map[string]string{"container": "foo1"}, + expected: "only one of -c or an inline [CONTAINER] arg is allowed", + }, + { + name: "More than two args case", + args: []string{"foo", "foo1", "foo2"}, + flags: map[string]string{"tail": "1"}, + expected: "'logs (POD | TYPE/NAME) [CONTAINER_NAME]'.\nPOD or TYPE/NAME is a required argument for the logs command", + }, + { + name: "follow and selecter conflict", + flags: map[string]string{"selector": "foo", "follow": "true"}, + expected: "only one of follow (-f) or selector (-l) is allowed", + }, + } + for _, test := range tests { + cmd := NewCmdLogs(f, bytes.NewBuffer([]byte{})) + var err error + out := "" + for flag, value := range test.flags { + cmd.Flags().Set(flag, value) + } + // checkErr breaks tests in case of errors, plus we just + // need to check errors returned by the command validation + o := &LogsOptions{} + err = o.Complete(f, os.Stdout, cmd, test.args) + out = err.Error() + if !strings.Contains(out, test.expected) { + t.Errorf("%s: expected to find:\n\t%s\nfound:\n\t%s\n", test.name, test.expected, out) + } + } +} diff --git a/pkg/kubectl/cmd/patch.go b/pkg/kubectl/cmd/patch.go index be49545d561..325c2584722 100644 --- a/pkg/kubectl/cmd/patch.go +++ b/pkg/kubectl/cmd/patch.go @@ -34,11 +34,11 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/util/yaml" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/printers" ) @@ -153,13 +153,8 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin return fmt.Errorf("unable to parse %q: %v", patch, err) } - mapper, typer, err := f.UnstructuredObject() - if err != nil { - return err - } - r := f.NewBuilder(). - Unstructured(f.UnstructuredClientForMapping, mapper, typer). + Unstructured(). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &options.FilenameOptions). @@ -196,7 +191,6 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin // Copy the resource info and update with the result of applying the user's patch infoCopy := *info infoCopy.Object = patchedObj - infoCopy.VersionedObject = patchedObj if patch, patchType, err := cmdutil.ChangeResourcePatch(&infoCopy, f.Command(cmd, true)); err == nil { if recordedObj, err := helper.Patch(info.Namespace, info.Name, patchType, patch); err != nil { glog.V(4).Infof("error recording reason: %v", err) @@ -226,13 +220,9 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin } if len(options.OutputFormat) > 0 && options.OutputFormat != "name" { - return cmdutil.PrintResourceInfoForCommand(cmd, info, f, out) + return f.PrintResourceInfoForCommand(cmd, info, out) } - mapper, _, err := f.UnstructuredObject() - if err != nil { - return err - } - cmdutil.PrintSuccess(mapper, options.OutputFormat == "name", out, info.Mapping.Resource, info.Name, false, dataChangedMsg) + f.PrintSuccess(r.Mapper().RESTMapper, options.OutputFormat == "name", out, info.Mapping.Resource, info.Name, false, dataChangedMsg) // if object was not successfully patched, exit with error code 1 if !didPatch { @@ -244,12 +234,12 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin count++ - originalObjJS, err := runtime.Encode(unstructured.UnstructuredJSONScheme, info.VersionedObject) + originalObjJS, err := runtime.Encode(unstructured.UnstructuredJSONScheme, info.Object) if err != nil { return err } - originalPatchedObjJS, err := getPatchedJSON(patchType, originalObjJS, patchBytes, mapping.GroupVersionKind, api.Scheme) + originalPatchedObjJS, err := getPatchedJSON(patchType, originalObjJS, patchBytes, mapping.GroupVersionKind, scheme.Scheme) if err != nil { return err } @@ -266,7 +256,7 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin if err := info.Refresh(targetObj, true); err != nil { return err } - return cmdutil.PrintResourceInfoForCommand(cmd, info, f, out) + return f.PrintResourceInfoForCommand(cmd, info, out) }) if err != nil { return err diff --git a/pkg/kubectl/cmd/patch_test.go b/pkg/kubectl/cmd/patch_test.go index c72d308eec3..f9692a9770c 100644 --- a/pkg/kubectl/cmd/patch_test.go +++ b/pkg/kubectl/cmd/patch_test.go @@ -23,7 +23,6 @@ import ( "testing" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" "k8s.io/kubernetes/pkg/printers" ) @@ -33,7 +32,6 @@ func TestPatchObject(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -72,7 +70,6 @@ func TestPatchObjectFromFile(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -107,7 +104,6 @@ func TestPatchNoop(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -153,7 +149,6 @@ func TestPatchObjectFromFileOutput(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &printers.YAMLPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { diff --git a/pkg/kubectl/cmd/plugin.go b/pkg/kubectl/cmd/plugin.go index e883d1a65df..b69e7a89c68 100644 --- a/pkg/kubectl/cmd/plugin.go +++ b/pkg/kubectl/cmd/plugin.go @@ -19,6 +19,9 @@ package cmd import ( "fmt" "io" + "os" + "os/exec" + "syscall" "github.com/golang/glog" "github.com/spf13/cobra" @@ -109,6 +112,15 @@ func NewCmdForPlugin(f cmdutil.Factory, plugin *plugins.Plugin, runner plugins.P } if err := runner.Run(plugin, runningContext); err != nil { + if exiterr, ok := err.(*exec.ExitError); ok { + // check for (and exit with) the correct exit code + // from a failed plugin command execution + if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { + fmt.Fprintf(errout, "error: %v\n", err) + os.Exit(status.ExitStatus()) + } + } + cmdutil.CheckErr(err) } }, diff --git a/pkg/kubectl/cmd/portforward.go b/pkg/kubectl/cmd/portforward.go index b9ef5073662..cedb93bb232 100644 --- a/pkg/kubectl/cmd/portforward.go +++ b/pkg/kubectl/cmd/portforward.go @@ -30,7 +30,7 @@ import ( restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/portforward" "k8s.io/client-go/transport/spdy" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" diff --git a/pkg/kubectl/cmd/portforward_test.go b/pkg/kubectl/cmd/portforward_test.go index e896e4ace0e..dcb6f1da82d 100644 --- a/pkg/kubectl/cmd/portforward_test.go +++ b/pkg/kubectl/cmd/portforward_test.go @@ -25,8 +25,9 @@ import ( "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" ) @@ -43,7 +44,7 @@ func (f *fakePortForwarder) ForwardPorts(method string, url *url.URL, opts PortF } func testPortForward(t *testing.T, flags map[string]string, args []string) { - version := api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version + version := "v1" tests := []struct { name string @@ -69,7 +70,7 @@ func testPortForward(t *testing.T, flags map[string]string, args []string) { var err error f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Group: ""}, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { diff --git a/pkg/kubectl/cmd/replace.go b/pkg/kubectl/cmd/replace.go index d6f5e26357c..16b80ae3247 100644 --- a/pkg/kubectl/cmd/replace.go +++ b/pkg/kubectl/cmd/replace.go @@ -27,7 +27,6 @@ import ( "github.com/golang/glog" "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" @@ -120,24 +119,20 @@ func RunReplace(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str return fmt.Errorf("--timeout must have --force specified") } - mapper, typer, err := f.UnstructuredObject() - if err != nil { - return err - } - r := f.NewBuilder(). - Unstructured(f.UnstructuredClientForMapping, mapper, typer). + Unstructured(). Schema(schema). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, options). Flatten(). Do() - err = r.Err() - if err != nil { + if err := r.Err(); err != nil { return err } + mapper := r.Mapper().RESTMapper + return r.Visit(func(info *resource.Info, err error) error { if err != nil { return err @@ -161,7 +156,7 @@ func RunReplace(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str info.Refresh(obj, true) f.PrintObjectSpecificMessage(obj, out) - cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, false, "replaced") + f.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, false, "replaced") return nil }) } @@ -193,21 +188,20 @@ func forceReplace(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s } } - mapper, typer, err := f.UnstructuredObject() - if err != nil { - return err - } - r := resource.NewBuilder(mapper, f.CategoryExpander(), typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme). + r := f.NewBuilder(). + Unstructured(). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, options). ResourceTypeOrNameArgs(false, args...).RequireObject(false). Flatten(). Do() - err = r.Err() - if err != nil { + if err := r.Err(); err != nil { return err } + + mapper := r.Mapper().RESTMapper + //Replace will create a resource if it doesn't exist already, so ignore not found error ignoreNotFound := true timeout := cmdutil.GetFlagDuration(cmd, "timeout") @@ -224,7 +218,7 @@ func forceReplace(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s glog.Warningf("\"cascade\" is set, kubectl will delete and re-create all resources managed by this resource (e.g. Pods created by a ReplicationController). Consider using \"kubectl rolling-update\" if you want to update a ReplicationController together with its Pods.") err = ReapResult(r, f, out, cmdutil.GetFlagBool(cmd, "cascade"), ignoreNotFound, timeout, gracePeriod, waitForDeletion, shortOutput, mapper, false) } else { - err = DeleteResult(r, out, ignoreNotFound, gracePeriod, shortOutput, mapper) + err = DeleteResult(r, f, out, ignoreNotFound, gracePeriod, shortOutput, mapper) } if err != nil { return err @@ -233,7 +227,7 @@ func forceReplace(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s if timeout == 0 { timeout = kubectl.Timeout } - r.Visit(func(info *resource.Info, err error) error { + err = r.Visit(func(info *resource.Info, err error) error { if err != nil { return err } @@ -245,9 +239,12 @@ func forceReplace(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s return true, nil }) }) + if err != nil { + return err + } r = f.NewBuilder(). - Unstructured(f.UnstructuredClientForMapping, mapper, typer). + Unstructured(). Schema(schema). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). @@ -283,7 +280,7 @@ func forceReplace(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s count++ info.Refresh(obj, true) f.PrintObjectSpecificMessage(obj, out) - cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, false, "replaced") + f.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, false, "replaced") return nil }) if err != nil { diff --git a/pkg/kubectl/cmd/replace_test.go b/pkg/kubectl/cmd/replace_test.go index cb54fddf81c..2e5e175acca 100644 --- a/pkg/kubectl/cmd/replace_test.go +++ b/pkg/kubectl/cmd/replace_test.go @@ -23,7 +23,7 @@ import ( "testing" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" ) @@ -34,7 +34,6 @@ func TestReplaceObject(t *testing.T) { tf.Printer = &testPrinter{} deleted := false tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -91,7 +90,6 @@ func TestReplaceMultipleObject(t *testing.T) { redisMasterDeleted := false frontendDeleted := false tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -160,7 +158,6 @@ func TestReplaceDirectory(t *testing.T) { tf.Printer = &testPrinter{} created := map[string]bool{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -216,7 +213,6 @@ func TestForceReplaceObjectNotFound(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { diff --git a/pkg/kubectl/cmd/resource/BUILD b/pkg/kubectl/cmd/resource/BUILD new file mode 100644 index 00000000000..cea1a70a4d8 --- /dev/null +++ b/pkg/kubectl/cmd/resource/BUILD @@ -0,0 +1,81 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["get.go"], + importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/resource", + visibility = ["//visibility:public"], + deps = [ + "//pkg/apis/core:go_default_library", + "//pkg/kubectl:go_default_library", + "//pkg/kubectl/cmd/templates:go_default_library", + "//pkg/kubectl/cmd/util:go_default_library", + "//pkg/kubectl/cmd/util/openapi:go_default_library", + "//pkg/kubectl/resource:go_default_library", + "//pkg/kubectl/util/i18n:go_default_library", + "//pkg/printers:go_default_library", + "//pkg/util/interrupt:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/github.com/spf13/cobra:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["get_test.go"], + data = [ + "//api/openapi-spec:swagger-spec", + "//examples:config", + "//test/fixtures", + ], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/resource", + deps = [ + "//pkg/api/testapi:go_default_library", + "//pkg/api/testing:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/v1:go_default_library", + "//pkg/kubectl/cmd/testing:go_default_library", + "//pkg/kubectl/cmd/util:go_default_library", + "//pkg/kubectl/cmd/util/openapi:go_default_library", + "//pkg/kubectl/cmd/util/openapi/testing:go_default_library", + "//pkg/kubectl/scheme:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer/json:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer/streaming:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", + "//vendor/k8s.io/client-go/dynamic:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + "//vendor/k8s.io/client-go/rest/fake:go_default_library", + "//vendor/k8s.io/client-go/rest/watch:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/kubectl/cmd/resource/get.go b/pkg/kubectl/cmd/resource/get.go new file mode 100644 index 00000000000..4013cb42874 --- /dev/null +++ b/pkg/kubectl/cmd/resource/get.go @@ -0,0 +1,707 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES 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 ( + "encoding/json" + "fmt" + "io" + "strings" + + "github.com/golang/glog" + "github.com/spf13/cobra" + + kapierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/watch" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" + "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/kubectl/util/i18n" + "k8s.io/kubernetes/pkg/printers" + "k8s.io/kubernetes/pkg/util/interrupt" +) + +// GetOptions contains the input to the get command. +type GetOptions struct { + Out, ErrOut io.Writer + + resource.FilenameOptions + + Raw string + Watch bool + WatchOnly bool + ChunkSize int64 + + LabelSelector string + FieldSelector string + AllNamespaces bool + Namespace string + ExplicitNamespace bool + + IgnoreNotFound bool + ShowKind bool + LabelColumns []string + Export bool +} + +var ( + getLong = templates.LongDesc(` + Display one or many resources + + Prints a table of the most important information about the specified resources. + You can filter the list using a label selector and the --selector flag. If the + desired resource type is namespaced you will only see results in your current + namespace unless you pass --all-namespaces. + + This command will hide resources that have completed, such as pods that are + in the Succeeded or Failed phases. You can see the full results for any + resource by providing the --show-all flag. Uninitialized objects are not + shown unless --include-uninitialized is passed. + + By specifying the output as 'template' and providing a Go template as the value + of the --template flag, you can filter the attributes of the fetched resources.`) + + getExample = templates.Examples(i18n.T(` + # List all pods in ps output format. + kubectl get pods + + # List all pods in ps output format with more information (such as node name). + kubectl get pods -o wide + + # List a single replication controller with specified NAME in ps output format. + kubectl get replicationcontroller web + + # List a single pod in JSON output format. + kubectl get -o json pod web-pod-13je7 + + # List a pod identified by type and name specified in "pod.yaml" in JSON output format. + kubectl get -f pod.yaml -o json + + # Return only the phase value of the specified pod. + kubectl get -o template pod/web-pod-13je7 --template={{.status.phase}} + + # List all replication controllers and services together in ps output format. + kubectl get rc,services + + # List one or more resources by their type and names. + kubectl get rc/web service/frontend pods/web-pod-13je7 + + # List all resources with different types. + kubectl get all`)) +) + +const ( + useOpenAPIPrintColumnFlagLabel = "use-openapi-print-columns" +) + +// NewCmdGet creates a command object for the generic "get" action, which +// retrieves one or more resources from a server. +func NewCmdGet(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Command { + options := &GetOptions{ + Out: out, + ErrOut: errOut, + } + + // TODO: this needs to be abstracted behind the factory like ValidResourceTypeList + // and use discovery + // retrieve a list of handled resources from printer as valid args + validArgs, argAliases := []string{}, []string{} + p, err := f.Printer(nil, printers.PrintOptions{ + ColumnLabels: []string{}, + }) + cmdutil.CheckErr(err) + if p != nil { + validArgs = p.HandledResources() + argAliases = kubectl.ResourceAliases(validArgs) + } + + cmd := &cobra.Command{ + Use: "get [(-o|--output=)json|yaml|wide|custom-columns=...|custom-columns-file=...|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=...] (TYPE [NAME | -l label] | TYPE/NAME ...) [flags]", + Short: i18n.T("Display one or many resources"), + Long: getLong + "\n\n" + cmdutil.ValidResourceTypeList(f), + Example: getExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(options.Complete(f, cmd, args)) + cmdutil.CheckErr(options.Validate(cmd)) + cmdutil.CheckErr(options.Run(f, cmd, args)) + }, + SuggestFor: []string{"list", "ps"}, + ValidArgs: validArgs, + ArgAliases: argAliases, + } + + cmd.Flags().StringVar(&options.Raw, "raw", options.Raw, "Raw URI to request from the server. Uses the transport specified by the kubeconfig file.") + cmd.Flags().BoolVarP(&options.Watch, "watch", "w", options.Watch, "After listing/getting the requested object, watch for changes. Uninitialized objects are excluded if no object name is provided.") + cmd.Flags().BoolVar(&options.WatchOnly, "watch-only", options.WatchOnly, "Watch for changes to the requested object(s), without listing/getting first.") + cmd.Flags().Int64Var(&options.ChunkSize, "chunk-size", 500, "Return large lists in chunks rather than all at once. Pass 0 to disable. This flag is beta and may change in the future.") + cmd.Flags().BoolVar(&options.IgnoreNotFound, "ignore-not-found", options.IgnoreNotFound, "If the requested object does not exist the command will return exit code 0.") + cmd.Flags().StringVarP(&options.LabelSelector, "selector", "l", options.LabelSelector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") + cmd.Flags().StringVar(&options.FieldSelector, "field-selector", options.FieldSelector, "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type.") + cmd.Flags().BoolVar(&options.AllNamespaces, "all-namespaces", options.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.") + cmdutil.AddIncludeUninitializedFlag(cmd) + cmdutil.AddPrinterFlags(cmd) + addOpenAPIPrintColumnFlags(cmd) + cmd.Flags().BoolVar(&options.ShowKind, "show-kind", options.ShowKind, "If present, list the resource type for the requested object(s).") + cmd.Flags().StringSliceVarP(&options.LabelColumns, "label-columns", "L", options.LabelColumns, "Accepts a comma separated list of labels that are going to be presented as columns. Names are case-sensitive. You can also use multiple flag options like -L label1 -L label2...") + cmd.Flags().BoolVar(&options.Export, "export", options.Export, "If true, use 'export' for the resources. Exported resources are stripped of cluster-specific information.") + cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, "identifying the resource to get from a server.") + cmdutil.AddInclude3rdPartyFlags(cmd) + return cmd +} + +// Complete takes the command arguments and factory and infers any remaining options. +func (options *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + if len(options.Raw) > 0 { + if len(args) > 0 { + return fmt.Errorf("arguments may not be passed when --raw is specified") + } + return nil + } + + var err error + options.Namespace, options.ExplicitNamespace, err = f.DefaultNamespace() + if err != nil { + return err + } + if options.AllNamespaces { + options.ExplicitNamespace = false + } + + switch { + case options.Watch || options.WatchOnly: + + default: + if len(args) == 0 && cmdutil.IsFilenameSliceEmpty(options.Filenames) { + fmt.Fprint(options.ErrOut, "You must specify the type of resource to get. ", cmdutil.ValidResourceTypeList(f)) + fullCmdName := cmd.Parent().CommandPath() + usageString := "Required resource not specified." + if len(fullCmdName) > 0 && cmdutil.IsSiblingCommandExists(cmd, "explain") { + usageString = fmt.Sprintf("%s\nUse \"%s explain \" for a detailed description of that resource (e.g. %[2]s explain pods).", usageString, fullCmdName) + } + + return cmdutil.UsageErrorf(cmd, usageString) + } + } + return nil +} + +// Validate checks the set of flags provided by the user. +func (options *GetOptions) Validate(cmd *cobra.Command) error { + if len(options.Raw) > 0 && (options.Watch || options.WatchOnly || len(options.LabelSelector) > 0 || options.Export) { + return fmt.Errorf("--raw may not be specified with other flags that filter the server request or alter the output") + } + if cmdutil.GetFlagBool(cmd, "show-labels") { + outputOption := cmd.Flags().Lookup("output").Value.String() + if outputOption != "" && outputOption != "wide" { + return fmt.Errorf("--show-labels option cannot be used with %s printer", outputOption) + } + } + return nil +} + +// Run performs the get operation. +// TODO: remove the need to pass these arguments, like other commands. +func (options *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + if len(options.Raw) > 0 { + return options.raw(f) + } + if options.Watch || options.WatchOnly { + return options.watch(f, cmd, args) + } + + r := f.NewBuilder(). + Unstructured(). + NamespaceParam(options.Namespace).DefaultNamespace().AllNamespaces(options.AllNamespaces). + FilenameParam(options.ExplicitNamespace, &options.FilenameOptions). + LabelSelectorParam(options.LabelSelector). + FieldSelectorParam(options.FieldSelector). + ExportParam(options.Export). + RequestChunksOf(options.ChunkSize). + IncludeUninitialized(cmdutil.ShouldIncludeUninitialized(cmd, false)). // TODO: this needs to be better factored + ResourceTypeOrNameArgs(true, args...). + ContinueOnError(). + Latest(). + Flatten(). + Do() + + if options.IgnoreNotFound { + r.IgnoreErrors(kapierrors.IsNotFound) + } + if err := r.Err(); err != nil { + return err + } + + printOpts := cmdutil.ExtractCmdPrintOptions(cmd, options.AllNamespaces) + printer, err := f.PrinterForOptions(printOpts) + if err != nil { + return err + } + + filterOpts := cmdutil.ExtractCmdPrintOptions(cmd, options.AllNamespaces) + filterFuncs := f.DefaultResourceFilterFunc() + if r.TargetsSingleItems() { + filterFuncs = nil + } + + if printer.IsGeneric() { + return options.printGeneric(printer, r, filterFuncs, filterOpts) + } + + allErrs := []error{} + errs := sets.NewString() + infos, err := r.Infos() + if err != nil { + allErrs = append(allErrs, err) + } + + objs := make([]runtime.Object, len(infos)) + for ix := range infos { + objs[ix] = infos[ix].Object + } + + sorting, err := cmd.Flags().GetString("sort-by") + if err != nil { + return err + } + var sorter *kubectl.RuntimeSort + if len(sorting) > 0 && len(objs) > 1 { + // TODO: questionable + if sorter, err = kubectl.SortObjects(f.Decoder(true), objs, sorting); err != nil { + return err + } + } + + // use the default printer for each object + printer = nil + var lastMapping *meta.RESTMapping + w := printers.GetNewTabWriter(options.Out) + + useOpenAPIPrintColumns := cmdutil.GetFlagBool(cmd, useOpenAPIPrintColumnFlagLabel) + + showKind := options.ShowKind + // TODO: abstract more cleanly + if resource.MultipleTypesRequested(args) || cmdutil.MustPrintWithKinds(objs, infos, sorter) { + showKind = true + } + + filteredResourceCount := 0 + noHeaders := cmdutil.GetFlagBool(cmd, "no-headers") + for ix := range objs { + var mapping *meta.RESTMapping + var original runtime.Object + var info *resource.Info + if sorter != nil { + info = infos[sorter.OriginalPosition(ix)] + mapping = info.Mapping + original = info.Object + } else { + info = infos[ix] + mapping = info.Mapping + original = info.Object + } + if shouldGetNewPrinterForMapping(printer, lastMapping, mapping) { + if printer != nil { + w.Flush() + } + + printWithNamespace := options.AllNamespaces + if mapping != nil && mapping.Scope.Name() == meta.RESTScopeNameRoot { + printWithNamespace = false + } + + printOpts := cmdutil.ExtractCmdPrintOptions(cmd, printWithNamespace) + // if cmd does not specify output format and useOpenAPIPrintColumnFlagLabel flag is true, + // then get the default output options for this mapping from OpenAPI schema. + if !cmdSpecifiesOutputFmt(cmd) && useOpenAPIPrintColumns { + updatePrintOptionsForOpenAPI(f, mapping, printOpts) + } + + printer, err = f.PrinterForMapping(printOpts, mapping) + if err != nil { + if !errs.Has(err.Error()) { + errs.Insert(err.Error()) + allErrs = append(allErrs, err) + } + continue + } + + // TODO: this doesn't belong here + // add linebreak between resource groups (if there is more than one) + // skip linebreak above first resource group + if lastMapping != nil && !noHeaders { + fmt.Fprintf(options.ErrOut, "%s\n", "") + } + + lastMapping = mapping + } + + typedObj := info.AsInternal() + + // filter objects if filter has been defined for current object + if isFiltered, err := filterFuncs.Filter(typedObj, filterOpts); isFiltered { + if err == nil { + filteredResourceCount++ + continue + } + if !errs.Has(err.Error()) { + errs.Insert(err.Error()) + allErrs = append(allErrs, err) + } + } + + if resourcePrinter, found := printer.(*printers.HumanReadablePrinter); found { + resourceName := resourcePrinter.GetResourceKind() + if mapping != nil { + if resourceName == "" { + resourceName = mapping.Resource + } + if alias, ok := kubectl.ResourceShortFormFor(mapping.Resource); ok { + resourceName = alias + } else if resourceName == "" { + resourceName = "none" + } + } else { + resourceName = "none" + } + + if showKind { + resourcePrinter.EnsurePrintWithKind(resourceName) + } + + if err := printer.PrintObj(typedObj, w); err != nil { + if !errs.Has(err.Error()) { + errs.Insert(err.Error()) + allErrs = append(allErrs, err) + } + } + continue + } + objToPrint := typedObj + if printer.IsGeneric() { + // use raw object as recieved from the builder when using generic + // printer instead of decodedObj + objToPrint = original + } + if err := printer.PrintObj(objToPrint, w); err != nil { + if !errs.Has(err.Error()) { + errs.Insert(err.Error()) + allErrs = append(allErrs, err) + } + continue + } + } + w.Flush() + cmdutil.PrintFilterCount(options.ErrOut, len(objs), filteredResourceCount, len(allErrs), filterOpts, options.IgnoreNotFound) + return utilerrors.NewAggregate(allErrs) +} + +// raw makes a simple HTTP request to the provided path on the server using the default +// credentials. +func (options *GetOptions) raw(f cmdutil.Factory) error { + restClient, err := f.RESTClient() + if err != nil { + return err + } + + stream, err := restClient.Get().RequestURI(options.Raw).Stream() + if err != nil { + return err + } + defer stream.Close() + + _, err = io.Copy(options.Out, stream) + if err != nil && err != io.EOF { + return err + } + return nil +} + +// watch starts a client-side watch of one or more resources. +// TODO: remove the need for arguments here. +func (options *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + // TODO: this could be better factored + // include uninitialized objects when watching on a single object + // unless explicitly set --include-uninitialized=false + includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, len(args) == 2) + + r := f.NewBuilder(). + Unstructured(). + NamespaceParam(options.Namespace).DefaultNamespace().AllNamespaces(options.AllNamespaces). + FilenameParam(options.ExplicitNamespace, &options.FilenameOptions). + LabelSelectorParam(options.LabelSelector). + FieldSelectorParam(options.FieldSelector). + ExportParam(options.Export). + RequestChunksOf(options.ChunkSize). + IncludeUninitialized(includeUninitialized). + ResourceTypeOrNameArgs(true, args...). + SingleResourceType(). + Latest(). + Do() + if err := r.Err(); err != nil { + return err + } + infos, err := r.Infos() + if err != nil { + return err + } + if len(infos) != 1 { + return i18n.Errorf("watch is only supported on individual resources and resource collections - %d resources were found", len(infos)) + } + + filterOpts := cmdutil.ExtractCmdPrintOptions(cmd, options.AllNamespaces) + filterFuncs := f.DefaultResourceFilterFunc() + if r.TargetsSingleItems() { + filterFuncs = nil + } + + info := infos[0] + mapping := info.ResourceMapping() + printOpts := cmdutil.ExtractCmdPrintOptions(cmd, options.AllNamespaces) + printer, err := f.PrinterForMapping(printOpts, mapping) + if err != nil { + return err + } + obj, err := r.Object() + if err != nil { + return err + } + + // watching from resourceVersion 0, starts the watch at ~now and + // will return an initial watch event. Starting form ~now, rather + // the rv of the object will insure that we start the watch from + // inside the watch window, which the rv of the object might not be. + rv := "0" + isList := meta.IsListType(obj) + if isList { + // the resourceVersion of list objects is ~now but won't return + // an initial watch event + rv, err = mapping.MetadataAccessor.ResourceVersion(obj) + if err != nil { + return err + } + } + + // print the current object + if !options.WatchOnly { + var objsToPrint []runtime.Object + writer := printers.GetNewTabWriter(options.Out) + + if isList { + objsToPrint, _ = meta.ExtractList(obj) + } else { + objsToPrint = append(objsToPrint, obj) + } + for _, objToPrint := range objsToPrint { + if isFiltered, err := filterFuncs.Filter(objToPrint, filterOpts); !isFiltered { + if err != nil { + glog.V(2).Infof("Unable to filter resource: %v", err) + } else if err := printer.PrintObj(objToPrint, writer); err != nil { + return fmt.Errorf("unable to output the provided object: %v", err) + } + } + } + writer.Flush() + } + + // print watched changes + w, err := r.Watch(rv) + if err != nil { + return err + } + + first := true + intr := interrupt.New(nil, w.Stop) + intr.Run(func() error { + _, err := watch.Until(0, w, func(e watch.Event) (bool, error) { + if !isList && first { + // drop the initial watch event in the single resource case + first = false + return false, nil + } + + if isFiltered, err := filterFuncs.Filter(e.Object, filterOpts); !isFiltered { + if err != nil { + glog.V(2).Infof("Unable to filter resource: %v", err) + } else if err := printer.PrintObj(e.Object, options.Out); err != nil { + return false, err + } + } + return false, nil + }) + return err + }) + return nil +} + +func (options *GetOptions) printGeneric(printer printers.ResourcePrinter, r *resource.Result, filterFuncs kubectl.Filters, filterOpts *printers.PrintOptions) error { + // we flattened the data from the builder, so we have individual items, but now we'd like to either: + // 1. if there is more than one item, combine them all into a single list + // 2. if there is a single item and that item is a list, leave it as its specific list + // 3. if there is a single item and it is not a list, leave it as a single item + var errs []error + singleItemImplied := false + infos, err := r.IntoSingleItemImplied(&singleItemImplied).Infos() + if err != nil { + if singleItemImplied { + return err + } + errs = append(errs, err) + } + + if len(infos) == 0 && options.IgnoreNotFound { + return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs))) + } + + var obj runtime.Object + if !singleItemImplied || len(infos) > 1 { + // we have more than one item, so coerce all items into a list + // we have more than one item, so coerce all items into a list. + // we don't want an *unstructured.Unstructured list yet, as we + // may be dealing with non-unstructured objects. Compose all items + // into an api.List, and then decode using an unstructured scheme. + list := api.List{ + TypeMeta: metav1.TypeMeta{ + Kind: "List", + APIVersion: "v1", + }, + ListMeta: metav1.ListMeta{}, + } + for _, info := range infos { + list.Items = append(list.Items, info.Object) + } + + listData, err := json.Marshal(list) + if err != nil { + return err + } + + converted, err := runtime.Decode(unstructured.UnstructuredJSONScheme, listData) + if err != nil { + return err + } + + obj = converted + } else { + obj = infos[0].Object + } + + isList := meta.IsListType(obj) + if isList { + _, items, err := cmdutil.FilterResourceList(obj, filterFuncs, filterOpts) + if err != nil { + return err + } + + // take the filtered items and create a new list for display + list := &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "kind": "List", + "apiVersion": "v1", + "metadata": map[string]interface{}{}, + }, + } + if listMeta, err := meta.ListAccessor(obj); err == nil { + list.Object["metadata"] = map[string]interface{}{ + "selfLink": listMeta.GetSelfLink(), + "resourceVersion": listMeta.GetResourceVersion(), + } + } + + for _, item := range items { + list.Items = append(list.Items, *item.(*unstructured.Unstructured)) + } + if err := printer.PrintObj(list, options.Out); err != nil { + errs = append(errs, err) + } + return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs))) + } + + if isFiltered, err := filterFuncs.Filter(obj, filterOpts); !isFiltered { + if err != nil { + glog.V(2).Infof("Unable to filter resource: %v", err) + } else if err := printer.PrintObj(obj, options.Out); err != nil { + errs = append(errs, err) + } + } + + return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs))) +} + +func addOpenAPIPrintColumnFlags(cmd *cobra.Command) { + cmd.Flags().Bool(useOpenAPIPrintColumnFlagLabel, true, "If true, use x-kubernetes-print-column metadata (if present) from the OpenAPI schema for displaying a resource.") +} + +func shouldGetNewPrinterForMapping(printer printers.ResourcePrinter, lastMapping, mapping *meta.RESTMapping) bool { + return printer == nil || lastMapping == nil || mapping == nil || mapping.Resource != lastMapping.Resource +} + +func cmdSpecifiesOutputFmt(cmd *cobra.Command) bool { + return cmdutil.GetFlagString(cmd, "output") != "" +} + +// outputOptsForMappingFromOpenAPI looks for the output format metatadata in the +// openapi schema and modifies the passed print options for the mapping if found. +func updatePrintOptionsForOpenAPI(f cmdutil.Factory, mapping *meta.RESTMapping, printOpts *printers.PrintOptions) bool { + + // user has not specified any output format, check if OpenAPI has + // default specification to print this resource type + api, err := f.OpenAPISchema() + if err != nil { + // Error getting schema + return false + } + // Found openapi metadata for this resource + schema := api.LookupResource(mapping.GroupVersionKind) + if schema == nil { + // Schema not found, return empty columns + return false + } + + columns, found := openapi.GetPrintColumns(schema.GetExtensions()) + if !found { + // Extension not found, return empty columns + return false + } + + return outputOptsFromStr(columns, printOpts) +} + +// outputOptsFromStr parses the print-column metadata and generates printer.OutputOptions object. +func outputOptsFromStr(columnStr string, printOpts *printers.PrintOptions) bool { + if columnStr == "" { + return false + } + parts := strings.SplitN(columnStr, "=", 2) + if len(parts) < 2 { + return false + } + + printOpts.OutputFormatType = parts[0] + printOpts.OutputFormatArgument = parts[1] + printOpts.AllowMissingKeys = true + + return true +} diff --git a/pkg/kubectl/cmd/get_test.go b/pkg/kubectl/cmd/resource/get_test.go similarity index 78% rename from pkg/kubectl/cmd/get_test.go rename to pkg/kubectl/cmd/resource/get_test.go index f62369bd37c..9d30530aa96 100644 --- a/pkg/kubectl/cmd/get_test.go +++ b/pkg/kubectl/cmd/resource/get_test.go @@ -14,14 +14,16 @@ See the License for the specific language governing permissions and limitations under the License. */ -package cmd +package resource import ( "bytes" encjson "encoding/json" + "fmt" "io" "io/ioutil" "net/http" + "path/filepath" "reflect" "strings" "testing" @@ -34,17 +36,103 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer/json" "k8s.io/apimachinery/pkg/runtime/serializer/streaming" + "k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/dynamic" restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" restclientwatch "k8s.io/client-go/rest/watch" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kube-openapi/pkg/util/proto" "k8s.io/kubernetes/pkg/api/testapi" apitesting "k8s.io/kubernetes/pkg/api/testing" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/v1" + cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" + openapitesting "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing" + "k8s.io/kubernetes/pkg/kubectl/scheme" ) +var openapiSchemaPath = filepath.Join("..", "..", "..", "..", "api", "openapi-spec", "swagger.json") + +// This init should be removed after switching this command and its tests to user external types. +func init() { + api.AddToScheme(scheme.Scheme) + scheme.Scheme.AddConversionFuncs(v1.Convert_core_PodSpec_To_v1_PodSpec) + scheme.Scheme.AddConversionFuncs(v1.Convert_v1_PodSecurityContext_To_core_PodSecurityContext) +} + +var unstructuredSerializer = dynamic.ContentConfig().NegotiatedSerializer + +func defaultHeader() http.Header { + header := http.Header{} + header.Set("Content-Type", runtime.ContentTypeJSON) + return header +} + +func defaultClientConfig() *restclient.Config { + return &restclient.Config{ + APIPath: "/api", + ContentConfig: restclient.ContentConfig{ + NegotiatedSerializer: scheme.Codecs, + ContentType: runtime.ContentTypeJSON, + GroupVersion: &scheme.Registry.GroupOrDie(api.GroupName).GroupVersion, + }, + } +} + +func defaultClientConfigForVersion(version *schema.GroupVersion) *restclient.Config { + return &restclient.Config{ + APIPath: "/api", + ContentConfig: restclient.ContentConfig{ + NegotiatedSerializer: scheme.Codecs, + ContentType: runtime.ContentTypeJSON, + GroupVersion: version, + }, + } +} + +type testPrinter struct { + Objects []runtime.Object + Err error + GenericPrinter bool +} + +func (t *testPrinter) PrintObj(obj runtime.Object, out io.Writer) error { + t.Objects = append(t.Objects, obj) + fmt.Fprintf(out, "%#v", obj) + return t.Err +} + +// TODO: implement HandledResources() +func (t *testPrinter) HandledResources() []string { + return []string{} +} + +func (t *testPrinter) AfterPrint(output io.Writer, res string) error { + return nil +} + +func (t *testPrinter) IsGeneric() bool { + return t.GenericPrinter +} + +func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser { + return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj)))) +} + +func stringBody(body string) io.ReadCloser { + return ioutil.NopCloser(bytes.NewReader([]byte(body))) +} + +func initTestErrorHandler(t *testing.T) { + cmdutil.BehaviorOnFatal(func(str string, code int) { + t.Errorf("Error running command (exit code %d): %s", code, str) + }) +} + func testData() (*api.PodList, *api.ServiceList, *api.ReplicationControllerList) { pods := &api.PodList{ ListMeta: metav1.ListMeta{ @@ -121,15 +209,46 @@ func testComponentStatusData() *api.ComponentStatusList { // Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get. func TestGetUnknownSchemaObject(t *testing.T) { f, tf, _, _ := cmdtesting.NewAPIFactory() + tf.WithCustomScheme() _, _, codec, _ := cmdtesting.NewTestFactory() tf.Printer = &testPrinter{} + tf.OpenAPISchemaFunc = openapitesting.CreateOpenAPISchemaFunc(openapiSchemaPath) + + obj := &cmdtesting.ExternalType{ + Kind: "Type", + APIVersion: "apitest/unlikelyversion", + Name: "foo", + } + tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, - Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, cmdtesting.NewInternalType("", "", "foo"))}, + Resp: &http.Response{ + StatusCode: 200, Header: defaultHeader(), + Body: objBody(codec, obj), + }, } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}} + tf.ClientConfig = defaultClientConfig() + + mapper, _ := f.Object() + m, err := mapper.RESTMapping(schema.GroupKind{Group: "apitest", Kind: "Type"}) + if err != nil { + t.Fatal(err) + } + convertedObj, err := m.ConvertToVersion(&unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "Type", + "apiVersion": "apitest/unlikelyversion", + "name": "foo", + }, + }, schema.GroupVersion{Group: "apitest", Version: "unlikelyversion"}) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(convertedObj, obj) { + t.Fatalf("unexpected conversion of unstructured object to structured: %s", diff.ObjectReflectDiff(convertedObj, obj)) + } + buf := bytes.NewBuffer([]byte{}) errBuf := bytes.NewBuffer([]byte{}) @@ -142,6 +261,7 @@ func TestGetUnknownSchemaObject(t *testing.T) { if len(actual) != len(expected) { t.Fatalf("expected: %#v, but actual: %#v", expected, actual) } + t.Logf("actual: %#v", actual[0]) for i, obj := range actual { expectedJSON := runtime.EncodeOrDie(codec, expected[i]) expectedMap := map[string]interface{}{} @@ -149,7 +269,7 @@ func TestGetUnknownSchemaObject(t *testing.T) { t.Fatal(err) } - actualJSON := runtime.EncodeOrDie(api.Codecs.LegacyCodec(), obj) + actualJSON := runtime.EncodeOrDie(codec, obj) actualMap := map[string]interface{}{} if err := encjson.Unmarshal([]byte(actualJSON), &actualMap); err != nil { t.Fatal(err) @@ -165,16 +285,15 @@ func TestGetUnknownSchemaObject(t *testing.T) { func TestGetSchemaObject(t *testing.T) { f, tf, _, _ := cmdtesting.NewAPIFactory() tf.Mapper = testapi.Default.RESTMapper() - tf.Typer = api.Scheme + tf.Typer = scheme.Scheme codec := testapi.Default.Codec() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &api.ReplicationController{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})}, } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: "v1"}}} + tf.ClientConfig = defaultClientConfig() buf := bytes.NewBuffer([]byte{}) errBuf := bytes.NewBuffer([]byte{}) @@ -195,7 +314,6 @@ func TestGetObjectsWithOpenAPIOutputFormatPresent(t *testing.T) { // for Pod type. tf.OpenAPISchemaFunc = testOpenAPISchemaData tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, } @@ -217,10 +335,10 @@ func TestGetObjectsWithOpenAPIOutputFormatPresent(t *testing.T) { } type FakeResources struct { - resources map[schema.GroupVersionKind]openapi.Schema + resources map[schema.GroupVersionKind]proto.Schema } -func (f FakeResources) LookupResource(s schema.GroupVersionKind) openapi.Schema { +func (f FakeResources) LookupResource(s schema.GroupVersionKind) proto.Schema { return f.resources[s] } @@ -228,12 +346,12 @@ var _ openapi.Resources = &FakeResources{} func testOpenAPISchemaData() (openapi.Resources, error) { return &FakeResources{ - resources: map[schema.GroupVersionKind]openapi.Schema{ + resources: map[schema.GroupVersionKind]proto.Schema{ { Version: "v1", Kind: "Pod", - }: &openapi.Primitive{ - BaseSchema: openapi.BaseSchema{ + }: &proto.Primitive{ + BaseSchema: proto.BaseSchema{ Extensions: map[string]interface{}{ "x-kubernetes-print-columns": "custom-columns=NAME:.metadata.name,RSRC:.metadata.resourceVersion", }, @@ -249,7 +367,6 @@ func TestGetObjects(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, } @@ -289,7 +406,7 @@ func TestGetObjectsFiltered(t *testing.T) { {args: []string{"pods"}, flags: map[string]string{"show-all": "true"}, resp: pods, expect: []runtime.Object{first, second}}, {args: []string{"pods/foo"}, resp: first, expect: []runtime.Object{first}, genericPrinter: true}, {args: []string{"pods"}, flags: map[string]string{"output": "yaml"}, resp: pods, expect: []runtime.Object{second}}, - {args: []string{}, flags: map[string]string{"filename": "../../../examples/storage/cassandra/cassandra-controller.yaml"}, resp: pods, expect: []runtime.Object{first, second}}, + {args: []string{}, flags: map[string]string{"filename": "../../../../examples/storage/cassandra/cassandra-controller.yaml"}, resp: pods, expect: []runtime.Object{first, second}}, {args: []string{"pods"}, resp: pods, expect: []runtime.Object{second}}, {args: []string{"pods"}, flags: map[string]string{"show-all": "true", "output": "yaml"}, resp: pods, expect: []runtime.Object{first, second}}, @@ -297,30 +414,32 @@ func TestGetObjectsFiltered(t *testing.T) { } for i, test := range testCases { - t.Logf("%d", i) - f, tf, codec, _ := cmdtesting.NewAPIFactory() - tf.Printer = &testPrinter{GenericPrinter: test.genericPrinter} - tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, - NegotiatedSerializer: unstructuredSerializer, - Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, test.resp)}, - } - tf.Namespace = "test" - buf := bytes.NewBuffer([]byte{}) - errBuf := bytes.NewBuffer([]byte{}) + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + f, tf, codec, _ := cmdtesting.NewAPIFactory() + tf.WithLegacyScheme() + tf.Printer = &testPrinter{GenericPrinter: test.genericPrinter} + tf.UnstructuredClient = &fake.RESTClient{ + GroupVersion: schema.GroupVersion{Version: "v1"}, + NegotiatedSerializer: unstructuredSerializer, + Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, test.resp)}, + } + tf.Namespace = "test" + buf := bytes.NewBuffer([]byte{}) + errBuf := bytes.NewBuffer([]byte{}) - cmd := NewCmdGet(f, buf, errBuf) - cmd.SetOutput(buf) - for k, v := range test.flags { - cmd.Flags().Lookup(k).Value.Set(v) - } - cmd.Run(cmd, test.args) + cmd := NewCmdGet(f, buf, errBuf) + cmd.SetOutput(buf) + for k, v := range test.flags { + cmd.Flags().Lookup(k).Value.Set(v) + } + cmd.Run(cmd, test.args) - verifyObjects(t, test.expect, tf.Printer.(*testPrinter).Objects) + verifyObjects(t, test.expect, tf.Printer.(*testPrinter).Objects) - if len(buf.String()) == 0 { - t.Errorf("%d: unexpected empty output", i) - } + if len(buf.String()) == 0 { + t.Errorf("%d: unexpected empty output", i) + } + }) } } @@ -342,7 +461,6 @@ func TestGetObjectIgnoreNotFound(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{GenericPrinter: true} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -395,7 +513,6 @@ func TestGetSortedObjects(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, } @@ -432,8 +549,8 @@ func verifyObjects(t *testing.T, expected, actual []runtime.Object) { switch obj.(type) { case runtime.Unstructured, *runtime.Unknown: actualObj, err = runtime.Decode( - api.Codecs.UniversalDecoder(), - []byte(runtime.EncodeOrDie(api.Codecs.LegacyCodec(), obj))) + scheme.Codecs.UniversalDecoder(), + []byte(runtime.EncodeOrDie(scheme.Codecs.LegacyCodec(), obj))) default: actualObj = obj err = nil @@ -454,7 +571,6 @@ func TestGetObjectsIdentifiedByFile(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{GenericPrinter: true} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, } @@ -464,7 +580,7 @@ func TestGetObjectsIdentifiedByFile(t *testing.T) { cmd := NewCmdGet(f, buf, errBuf) cmd.SetOutput(buf) - cmd.Flags().Set("filename", "../../../examples/storage/cassandra/cassandra-controller.yaml") + cmd.Flags().Set("filename", "../../../../examples/storage/cassandra/cassandra-controller.yaml") cmd.Run(cmd, []string{}) expected := []runtime.Object{&pods.Items[0]} @@ -481,7 +597,6 @@ func TestGetListObjects(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, } @@ -522,7 +637,6 @@ func TestGetAllListObjects(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, } @@ -552,7 +666,6 @@ func TestGetListComponentStatus(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, statuses)}, } @@ -594,7 +707,6 @@ func TestGetMixedGenericObjects(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{GenericPrinter: true} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.URL.Path { @@ -607,7 +719,7 @@ func TestGetMixedGenericObjects(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}} + tf.ClientConfig = defaultClientConfig() buf := bytes.NewBuffer([]byte{}) errBuf := bytes.NewBuffer([]byte{}) @@ -622,7 +734,7 @@ func TestGetMixedGenericObjects(t *testing.T) { actual := tf.Printer.(*testPrinter).Objects fn := func(obj runtime.Object) unstructured.Unstructured { - data, err := runtime.Encode(api.Codecs.LegacyCodec(schema.GroupVersion{Version: "v1"}), obj) + data, err := runtime.Encode(scheme.Codecs.LegacyCodec(schema.GroupVersion{Version: "v1"}), obj) if err != nil { panic(err) } @@ -658,7 +770,6 @@ func TestGetMultipleTypeObjects(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.URL.Path { @@ -697,7 +808,6 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{GenericPrinter: true} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.URL.Path { @@ -712,7 +822,7 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}} + tf.ClientConfig = defaultClientConfig() buf := bytes.NewBuffer([]byte{}) errBuf := bytes.NewBuffer([]byte{}) @@ -724,7 +834,7 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) { actual := tf.Printer.(*testPrinter).Objects fn := func(obj runtime.Object) unstructured.Unstructured { - data, err := runtime.Encode(api.Codecs.LegacyCodec(schema.GroupVersion{Version: "v1"}), obj) + data, err := runtime.Encode(scheme.Codecs.LegacyCodec(schema.GroupVersion{Version: "v1"}), obj) if err != nil { t.Fatal(err) } @@ -756,16 +866,15 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) { } } -func TestGetMultipleTypeObjectsWithSelector(t *testing.T) { +func TestGetMultipleTypeObjectsWithLabelSelector(t *testing.T) { pods, svc, _ := testData() f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { - if req.URL.Query().Get(metav1.LabelSelectorQueryParam(api.Registry.GroupOrDie(api.GroupName).GroupVersion.String())) != "a=b" { + if req.URL.Query().Get(metav1.LabelSelectorQueryParam("v1")) != "a=b" { t.Fatalf("request url: %#v,and request: %#v", req.URL, req) } switch req.URL.Path { @@ -800,6 +909,49 @@ func TestGetMultipleTypeObjectsWithSelector(t *testing.T) { } } +func TestGetMultipleTypeObjectsWithFieldSelector(t *testing.T) { + pods, svc, _ := testData() + + f, tf, codec, _ := cmdtesting.NewAPIFactory() + tf.Printer = &testPrinter{} + tf.UnstructuredClient = &fake.RESTClient{ + NegotiatedSerializer: unstructuredSerializer, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + if req.URL.Query().Get(metav1.FieldSelectorQueryParam("v1")) != "a=b" { + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + } + switch req.URL.Path { + case "/namespaces/test/pods": + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, nil + case "/namespaces/test/services": + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, svc)}, nil + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil + } + }), + } + tf.Namespace = "test" + buf := bytes.NewBuffer([]byte{}) + errBuf := bytes.NewBuffer([]byte{}) + + cmd := NewCmdGet(f, buf, errBuf) + cmd.SetOutput(buf) + + cmd.Flags().Set("field-selector", "a=b") + cmd.Run(cmd, []string{"pods,services"}) + + expected, err := extractResourceList([]runtime.Object{pods, svc}) + if err != nil { + t.Fatal(err) + } + verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects) + + if len(buf.String()) == 0 { + t.Errorf("unexpected empty output") + } +} + func TestGetMultipleTypeObjectsWithDirectReference(t *testing.T) { _, svc, _ := testData() node := &api.Node{ @@ -814,7 +966,6 @@ func TestGetMultipleTypeObjectsWithDirectReference(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.URL.Path { @@ -851,7 +1002,6 @@ func TestGetByFormatForcesFlag(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{GenericPrinter: true} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, } @@ -940,7 +1090,7 @@ func watchTestData() ([]api.Pod, []watch.Event) { return pods, events } -func TestWatchSelector(t *testing.T) { +func TestWatchLabelSelector(t *testing.T) { pods, events := watchTestData() f, tf, codec, _ := cmdtesting.NewAPIFactory() @@ -952,19 +1102,17 @@ func TestWatchSelector(t *testing.T) { }, } tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { - if req.URL.Query().Get(metav1.LabelSelectorQueryParam(api.Registry.GroupOrDie(api.GroupName).GroupVersion.String())) != "a=b" { + if req.URL.Query().Get(metav1.LabelSelectorQueryParam("v1")) != "a=b" { t.Fatalf("request url: %#v,and request: %#v", req.URL, req) } switch req.URL.Path { case "/namespaces/test/pods": if req.URL.Query().Get("watch") == "true" { return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: watchBody(codec, events[2:])}, nil - } else { - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, podList)}, nil } + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, podList)}, nil default: t.Fatalf("request url: %#v,and request: %#v", req.URL, req) return nil, nil @@ -990,13 +1138,60 @@ func TestWatchSelector(t *testing.T) { } } +func TestWatchFieldSelector(t *testing.T) { + pods, events := watchTestData() + + f, tf, codec, _ := cmdtesting.NewAPIFactory() + tf.Printer = &testPrinter{} + podList := &api.PodList{ + Items: pods, + ListMeta: metav1.ListMeta{ + ResourceVersion: "10", + }, + } + tf.UnstructuredClient = &fake.RESTClient{ + NegotiatedSerializer: unstructuredSerializer, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + if req.URL.Query().Get(metav1.FieldSelectorQueryParam("v1")) != "a=b" { + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + } + switch req.URL.Path { + case "/namespaces/test/pods": + if req.URL.Query().Get("watch") == "true" { + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: watchBody(codec, events[2:])}, nil + } + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, podList)}, nil + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil + } + }), + } + tf.Namespace = "test" + buf := bytes.NewBuffer([]byte{}) + errBuf := bytes.NewBuffer([]byte{}) + + cmd := NewCmdGet(f, buf, errBuf) + cmd.SetOutput(buf) + + cmd.Flags().Set("watch", "true") + cmd.Flags().Set("field-selector", "a=b") + cmd.Run(cmd, []string{"pods"}) + + expected := []runtime.Object{&pods[0], &pods[1], events[2].Object, events[3].Object} + verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects) + + if len(buf.String()) == 0 { + t.Errorf("unexpected empty output") + } +} + func TestWatchResource(t *testing.T) { pods, events := watchTestData() f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.URL.Path { @@ -1038,7 +1233,6 @@ func TestWatchResourceIdentifiedByFile(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.URL.Path { @@ -1064,7 +1258,7 @@ func TestWatchResourceIdentifiedByFile(t *testing.T) { cmd.SetOutput(buf) cmd.Flags().Set("watch", "true") - cmd.Flags().Set("filename", "../../../examples/storage/cassandra/cassandra-controller.yaml") + cmd.Flags().Set("filename", "../../../../examples/storage/cassandra/cassandra-controller.yaml") cmd.Run(cmd, []string{}) expected := []runtime.Object{&pods[1], events[2].Object, events[3].Object} @@ -1081,7 +1275,6 @@ func TestWatchOnlyResource(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.URL.Path { @@ -1129,16 +1322,14 @@ func TestWatchOnlyList(t *testing.T) { }, } tf.UnstructuredClient = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.URL.Path { case "/namespaces/test/pods": if req.URL.Query().Get("watch") == "true" { return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: watchBody(codec, events[2:])}, nil - } else { - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, podList)}, nil } + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, podList)}, nil default: t.Fatalf("request url: %#v,and request: %#v", req.URL, req) return nil, nil diff --git a/pkg/kubectl/cmd/rollingupdate.go b/pkg/kubectl/cmd/rollingupdate.go index 3f6ac6d046b..117cacbfb10 100644 --- a/pkg/kubectl/cmd/rollingupdate.go +++ b/pkg/kubectl/cmd/rollingupdate.go @@ -31,7 +31,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -191,7 +192,7 @@ func RunRollingUpdate(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args var keepOldName bool var replicasDefaulted bool - mapper, typer := f.Object() + mapper, _ := f.Object() if len(filename) != 0 { schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate")) @@ -200,46 +201,40 @@ func RunRollingUpdate(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args } request := f.NewBuilder(). + Unstructured(). Schema(schema). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &resource.FilenameOptions{Recursive: false, Filenames: []string{filename}}). + Flatten(). Do() - obj, err := request.Object() + infos, err := request.Infos() if err != nil { return err } - var ok bool - // Handle filename input from stdin. The resource builder always returns an api.List - // when creating resource(s) from a stream. - if list, ok := obj.(*api.List); ok { - if len(list.Items) > 1 { - return cmdutil.UsageErrorf(cmd, "%s specifies multiple items", filename) - } - if len(list.Items) == 0 { - return cmdutil.UsageErrorf(cmd, "please make sure %s exists and is not empty", filename) - } - obj = list.Items[0] + // Handle filename input from stdin. + if len(infos) > 1 { + return cmdutil.UsageErrorf(cmd, "%s specifies multiple items", filename) } - newRc, ok = obj.(*api.ReplicationController) - if !ok { - if gvks, _, err := typer.ObjectKinds(obj); err == nil { - return cmdutil.UsageErrorf(cmd, "%s contains a %v not a ReplicationController", filename, gvks[0]) - } - glog.V(4).Infof("Object %#v is not a ReplicationController", obj) - return cmdutil.UsageErrorf(cmd, "%s does not specify a valid ReplicationController", filename) + if len(infos) == 0 { + return cmdutil.UsageErrorf(cmd, "please make sure %s exists and is not empty", filename) } - infos, err := request.Infos() - if err != nil || len(infos) != 1 { - glog.V(2).Infof("was not able to recover adequate information to discover if .spec.replicas was defaulted") - } else { - replicasDefaulted = isReplicasDefaulted(infos[0]) + + switch t := infos[0].AsVersioned().(type) { + case *v1.ReplicationController: + replicasDefaulted = t.Spec.Replicas == nil + newRc, _ = infos[0].AsInternal().(*api.ReplicationController) + } + if newRc == nil { + glog.V(4).Infof("Object %T is not a ReplicationController", infos[0].Object) + return cmdutil.UsageErrorf(cmd, "%s contains a %v not a ReplicationController", filename, infos[0].Object.GetObjectKind().GroupVersionKind()) } } + // If the --image option is specified, we need to create a new rc with at least one different selector // than the old rc. This selector is the hash of the rc, with a suffix to provide uniqueness for // same-image updates. if len(image) != 0 { - codec := api.Codecs.LegacyCodec(v1.SchemeGroupVersion) + codec := legacyscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion) keepOldName = len(args) == 1 newName := findNewName(args, oldRc) if newRc, err = kubectl.LoadExistingNextReplicationController(coreClient, cmdNamespace, newName); err != nil { @@ -378,7 +373,7 @@ func RunRollingUpdate(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args if outputFormat != "" { return f.PrintObject(cmd, false, mapper, newRc, out) } - cmdutil.PrintSuccess(mapper, false, out, "replicationcontrollers", oldName, dryrun, message) + f.PrintSuccess(mapper, false, out, "replicationcontrollers", oldName, dryrun, message) return nil } @@ -392,15 +387,3 @@ func findNewName(args []string, oldRc *api.ReplicationController) string { } return "" } - -func isReplicasDefaulted(info *resource.Info) bool { - if info == nil || info.VersionedObject == nil { - // was unable to recover versioned info - return false - } - switch t := info.VersionedObject.(type) { - case *v1.ReplicationController: - return t.Spec.Replicas == nil - } - return false -} diff --git a/pkg/kubectl/cmd/rollout/rollout_history.go b/pkg/kubectl/cmd/rollout/rollout_history.go index a4fe03fba77..91b5711f1d2 100644 --- a/pkg/kubectl/cmd/rollout/rollout_history.go +++ b/pkg/kubectl/cmd/rollout/rollout_history.go @@ -80,6 +80,7 @@ func RunHistory(f cmdutil.Factory, cmd *cobra.Command, out io.Writer, args []str } r := f.NewBuilder(). + Internal(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, options). ResourceTypeOrNameArgs(true, args...). diff --git a/pkg/kubectl/cmd/rollout/rollout_pause.go b/pkg/kubectl/cmd/rollout/rollout_pause.go index c4472b7a68c..5a9f3bd4b7f 100644 --- a/pkg/kubectl/cmd/rollout/rollout_pause.go +++ b/pkg/kubectl/cmd/rollout/rollout_pause.go @@ -45,7 +45,8 @@ type PauseConfig struct { Encoder runtime.Encoder Infos []*resource.Info - Out io.Writer + PrintSuccess func(mapper meta.RESTMapper, shortOutput bool, out io.Writer, resource, name string, dryRun bool, operation string) + Out io.Writer } var ( @@ -100,6 +101,7 @@ func (o *PauseConfig) CompletePause(f cmdutil.Factory, cmd *cobra.Command, out i return cmdutil.UsageErrorf(cmd, "%s", cmd.Use) } + o.PrintSuccess = f.PrintSuccess o.Mapper, o.Typer = f.Object() o.Encoder = f.JSONEncoder() @@ -112,6 +114,7 @@ func (o *PauseConfig) CompletePause(f cmdutil.Factory, cmd *cobra.Command, out i } r := f.NewBuilder(). + Internal(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &o.FilenameOptions). ResourceTypeOrNameArgs(true, args...). @@ -141,7 +144,7 @@ func (o PauseConfig) RunPause() error { } if string(patch.Patch) == "{}" || len(patch.Patch) == 0 { - cmdutil.PrintSuccess(o.Mapper, false, o.Out, info.Mapping.Resource, info.Name, false, "already paused") + o.PrintSuccess(o.Mapper, false, o.Out, info.Mapping.Resource, info.Name, false, "already paused") continue } @@ -152,7 +155,7 @@ func (o PauseConfig) RunPause() error { } info.Refresh(obj, true) - cmdutil.PrintSuccess(o.Mapper, false, o.Out, info.Mapping.Resource, info.Name, false, "paused") + o.PrintSuccess(o.Mapper, false, o.Out, info.Mapping.Resource, info.Name, false, "paused") } return utilerrors.NewAggregate(allErrs) diff --git a/pkg/kubectl/cmd/rollout/rollout_resume.go b/pkg/kubectl/cmd/rollout/rollout_resume.go index 72ea49e965c..427fac9acfa 100644 --- a/pkg/kubectl/cmd/rollout/rollout_resume.go +++ b/pkg/kubectl/cmd/rollout/rollout_resume.go @@ -45,7 +45,8 @@ type ResumeConfig struct { Encoder runtime.Encoder Infos []*resource.Info - Out io.Writer + PrintSuccess func(mapper meta.RESTMapper, shortOutput bool, out io.Writer, resource, name string, dryRun bool, operation string) + Out io.Writer } var ( @@ -98,6 +99,7 @@ func (o *ResumeConfig) CompleteResume(f cmdutil.Factory, cmd *cobra.Command, out return cmdutil.UsageErrorf(cmd, "%s", cmd.Use) } + o.PrintSuccess = f.PrintSuccess o.Mapper, o.Typer = f.Object() o.Encoder = f.JSONEncoder() @@ -110,6 +112,7 @@ func (o *ResumeConfig) CompleteResume(f cmdutil.Factory, cmd *cobra.Command, out } r := f.NewBuilder(). + Internal(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &o.FilenameOptions). ResourceTypeOrNameArgs(true, args...). @@ -146,7 +149,7 @@ func (o ResumeConfig) RunResume() error { } if string(patch.Patch) == "{}" || len(patch.Patch) == 0 { - cmdutil.PrintSuccess(o.Mapper, false, o.Out, info.Mapping.Resource, info.Name, false, "already resumed") + o.PrintSuccess(o.Mapper, false, o.Out, info.Mapping.Resource, info.Name, false, "already resumed") continue } @@ -157,7 +160,7 @@ func (o ResumeConfig) RunResume() error { } info.Refresh(obj, true) - cmdutil.PrintSuccess(o.Mapper, false, o.Out, info.Mapping.Resource, info.Name, false, "resumed") + o.PrintSuccess(o.Mapper, false, o.Out, info.Mapping.Resource, info.Name, false, "resumed") } return utilerrors.NewAggregate(allErrs) diff --git a/pkg/kubectl/cmd/rollout/rollout_status.go b/pkg/kubectl/cmd/rollout/rollout_status.go index d817a81a6d1..6655c7c92f5 100644 --- a/pkg/kubectl/cmd/rollout/rollout_status.go +++ b/pkg/kubectl/cmd/rollout/rollout_status.go @@ -83,6 +83,7 @@ func RunStatus(f cmdutil.Factory, cmd *cobra.Command, out io.Writer, args []stri } r := f.NewBuilder(). + Internal(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, options). ResourceTypeOrNameArgs(true, args...). diff --git a/pkg/kubectl/cmd/rollout/rollout_undo.go b/pkg/kubectl/cmd/rollout/rollout_undo.go index 7d1f0cba578..fb5c7dc09cc 100644 --- a/pkg/kubectl/cmd/rollout/rollout_undo.go +++ b/pkg/kubectl/cmd/rollout/rollout_undo.go @@ -43,7 +43,8 @@ type UndoOptions struct { ToRevision int64 DryRun bool - Out io.Writer + PrintSuccess func(mapper meta.RESTMapper, shortOutput bool, out io.Writer, resource, name string, dryRun bool, operation string) + Out io.Writer } var ( @@ -100,6 +101,7 @@ func (o *UndoOptions) CompleteUndo(f cmdutil.Factory, cmd *cobra.Command, out io return cmdutil.UsageErrorf(cmd, "Required resource not specified.") } + o.PrintSuccess = f.PrintSuccess o.ToRevision = cmdutil.GetFlagInt64(cmd, "to-revision") o.Mapper, o.Typer = f.Object() o.Out = out @@ -111,6 +113,7 @@ func (o *UndoOptions) CompleteUndo(f cmdutil.Factory, cmd *cobra.Command, out io } r := f.NewBuilder(). + Internal(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &o.FilenameOptions). ResourceTypeOrNameArgs(true, args...). @@ -146,7 +149,7 @@ func (o *UndoOptions) RunUndo() error { allErrs = append(allErrs, cmdutil.AddSourceToErr("undoing", info.Source, err)) continue } - cmdutil.PrintSuccess(o.Mapper, false, o.Out, info.Mapping.Resource, info.Name, false, result) + o.PrintSuccess(o.Mapper, false, o.Out, info.Mapping.Resource, info.Name, false, result) } return utilerrors.NewAggregate(allErrs) } diff --git a/pkg/kubectl/cmd/run.go b/pkg/kubectl/cmd/run.go index 5f0989317e8..087e8d40b85 100644 --- a/pkg/kubectl/cmd/run.go +++ b/pkg/kubectl/cmd/run.go @@ -31,7 +31,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" conditions "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/kubectl" @@ -354,6 +354,7 @@ func RunRun(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *c return err } r := f.NewBuilder(). + Internal(). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). ResourceNames(obj.Mapping.Resource, name). @@ -404,7 +405,7 @@ func RunRun(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *c if outputFormat != "" || cmdutil.GetDryRunFlag(cmd) { return f.PrintObject(cmd, false, runObject.Mapper, runObject.Object, cmdOut) } - cmdutil.PrintSuccess(runObject.Mapper, false, cmdOut, runObject.Mapping.Resource, args[0], cmdutil.GetDryRunFlag(cmd), "created") + f.PrintSuccess(runObject.Mapper, false, cmdOut, runObject.Mapping.Resource, args[0], cmdutil.GetDryRunFlag(cmd), "created") return nil } @@ -559,7 +560,7 @@ func generateService(f cmdutil.Factory, cmd *cobra.Command, args []string, servi } return runObject, nil } - cmdutil.PrintSuccess(runObject.Mapper, false, out, runObject.Mapping.Resource, args[0], cmdutil.GetDryRunFlag(cmd), "created") + f.PrintSuccess(runObject.Mapper, false, out, runObject.Mapping.Resource, args[0], cmdutil.GetDryRunFlag(cmd), "created") return runObject, nil } diff --git a/pkg/kubectl/cmd/run_test.go b/pkg/kubectl/cmd/run_test.go index 0cd8cf89330..1e8790ac546 100644 --- a/pkg/kubectl/cmd/run_test.go +++ b/pkg/kubectl/cmd/run_test.go @@ -35,12 +35,18 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/kubectl/util/i18n" ) +// This init should be removed after switching this command and its tests to user external types. +func init() { + api.AddToScheme(scheme.Scheme) +} + func TestGetRestartPolicy(t *testing.T) { tests := []struct { input string @@ -163,7 +169,7 @@ func TestRunArgsFollowDashRules(t *testing.T) { for _, test := range tests { f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Version: "v1"}, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { if req.URL.Path == "/namespaces/test/replicationcontrollers" { @@ -284,10 +290,10 @@ func TestGenerateService(t *testing.T) { for _, test := range tests { sawPOST := false f, tf, codec, ns := cmdtesting.NewAPIFactory() - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}} + tf.ClientConfig = defaultClientConfig() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Version: "v1"}, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -428,12 +434,11 @@ func TestRunValidations(t *testing.T) { f, tf, codec, ns := cmdtesting.NewTestFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: ns, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, cmdtesting.NewInternalType("", "", ""))}, } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: "v1"}}} + tf.ClientConfig = defaultClientConfig() inBuf := bytes.NewReader([]byte{}) outBuf := bytes.NewBuffer([]byte{}) errBuf := bytes.NewBuffer([]byte{}) diff --git a/pkg/kubectl/cmd/scale.go b/pkg/kubectl/cmd/scale.go index de3fef08ffd..be722abc962 100644 --- a/pkg/kubectl/cmd/scale.go +++ b/pkg/kubectl/cmd/scale.go @@ -105,12 +105,13 @@ func RunScale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin mapper, _ := f.Object() r := f.NewBuilder(). + Internal(). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, options). ResourceTypeOrNameArgs(all, args...). Flatten(). - SelectorParam(selector). + LabelSelectorParam(selector). Do() err = r.Err() if resource.IsUsageError(err) { @@ -179,7 +180,7 @@ func RunScale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin } } counter++ - cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, false, "scaled") + f.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, false, "scaled") return nil }) if err != nil { diff --git a/pkg/kubectl/cmd/set/BUILD b/pkg/kubectl/cmd/set/BUILD index 45e13426011..64f1e6dc7d8 100644 --- a/pkg/kubectl/cmd/set/BUILD +++ b/pkg/kubectl/cmd/set/BUILD @@ -19,7 +19,6 @@ go_library( importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/set", visibility = ["//build/visible_to:pkg_kubectl_cmd_set_CONSUMERS"], deps = [ - "//pkg/api:go_default_library", "//pkg/apis/rbac:go_default_library", "//pkg/kubectl:go_default_library", "//pkg/kubectl/cmd/templates:go_default_library", @@ -28,6 +27,7 @@ go_library( "//pkg/kubectl/resource:go_default_library", "//pkg/kubectl/util/i18n:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -37,6 +37,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", ], ) @@ -55,23 +56,28 @@ go_test( "//examples:config", "//test/fixtures", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/set", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", "//pkg/api/testapi:go_default_library", - "//pkg/apis/apps:go_default_library", - "//pkg/apis/batch:go_default_library", - "//pkg/apis/extensions:go_default_library", "//pkg/apis/rbac:go_default_library", + "//pkg/kubectl/categories:go_default_library", "//pkg/kubectl/cmd/testing:go_default_library", "//pkg/kubectl/cmd/util:go_default_library", "//pkg/kubectl/resource:go_default_library", + "//pkg/kubectl/scheme:go_default_library", "//pkg/printers:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", + "//vendor/k8s.io/api/apps/v1:go_default_library", + "//vendor/k8s.io/api/apps/v1beta1:go_default_library", + "//vendor/k8s.io/api/apps/v1beta2:go_default_library", + "//vendor/k8s.io/api/batch/v1:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/rest/fake:go_default_library", ], diff --git a/pkg/kubectl/cmd/set/helper.go b/pkg/kubectl/cmd/set/helper.go index eabbb974b17..5d6bed200d1 100644 --- a/pkg/kubectl/cmd/set/helper.go +++ b/pkg/kubectl/cmd/set/helper.go @@ -21,19 +21,19 @@ import ( "io" "strings" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/strategicpatch" - "k8s.io/kubernetes/pkg/api" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" ) // selectContainers allows one or more containers to be matched against a string or wildcard -func selectContainers(containers []api.Container, spec string) ([]*api.Container, []*api.Container) { - out := []*api.Container{} - skipped := []*api.Container{} +func selectContainers(containers []v1.Container, spec string) ([]*v1.Container, []*v1.Container) { + out := []*v1.Container{} + skipped := []*v1.Container{} for i, c := range containers { if selectString(c.Name, spec) { out = append(out, &containers[i]) @@ -126,10 +126,7 @@ type patchFn func(*resource.Info) ([]byte, error) // the changes in the object. Encoder must be able to encode the info into the appropriate destination type. // This function returns whether the mutation function made any change in the original object. func CalculatePatch(patch *Patch, encoder runtime.Encoder, mutateFn patchFn) bool { - versionedEncoder := api.Codecs.EncoderForVersion(encoder, patch.Info.Mapping.GroupVersionKind.GroupVersion()) - - patch.Before, patch.Err = runtime.Encode(versionedEncoder, patch.Info.Object) - + patch.Before, patch.Err = runtime.Encode(encoder, patch.Info.Object) patch.After, patch.Err = mutateFn(patch.Info) if patch.Err != nil { return true @@ -138,14 +135,7 @@ func CalculatePatch(patch *Patch, encoder runtime.Encoder, mutateFn patchFn) boo return false } - // TODO: should be via New - versioned, err := patch.Info.Mapping.ConvertToVersion(patch.Info.Object, patch.Info.Mapping.GroupVersionKind.GroupVersion()) - if err != nil { - patch.Err = err - return true - } - - patch.Patch, patch.Err = strategicpatch.CreateTwoWayMergePatch(patch.Before, patch.After, versioned) + patch.Patch, patch.Err = strategicpatch.CreateTwoWayMergePatch(patch.Before, patch.After, patch.Info.Object) return true } @@ -162,17 +152,17 @@ func CalculatePatches(infos []*resource.Info, encoder runtime.Encoder, mutateFn return patches } -func findEnv(env []api.EnvVar, name string) (api.EnvVar, bool) { +func findEnv(env []v1.EnvVar, name string) (v1.EnvVar, bool) { for _, e := range env { if e.Name == name { return e, true } } - return api.EnvVar{}, false + return v1.EnvVar{}, false } -func updateEnv(existing []api.EnvVar, env []api.EnvVar, remove []string) []api.EnvVar { - out := []api.EnvVar{} +func updateEnv(existing []v1.EnvVar, env []v1.EnvVar, remove []string) []v1.EnvVar { + out := []v1.EnvVar{} covered := sets.NewString(remove...) for _, e := range existing { if covered.Has(e.Name) { diff --git a/pkg/kubectl/cmd/set/set_env.go b/pkg/kubectl/cmd/set/set_env.go index c4d2a309b11..36c3b810050 100644 --- a/pkg/kubectl/cmd/set/set_env.go +++ b/pkg/kubectl/cmd/set/set_env.go @@ -25,10 +25,11 @@ import ( "strings" "github.com/spf13/cobra" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" - "k8s.io/kubernetes/pkg/api" + "k8s.io/client-go/kubernetes" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" envutil "k8s.io/kubernetes/pkg/kubectl/cmd/util/env" @@ -38,7 +39,8 @@ import ( ) var ( - envResources = ` + validEnvNameRegexp = regexp.MustCompile("[^a-zA-Z0-9_]") + envResources = ` pod (po), replicationcontroller (rc), deployment (deploy), daemonset (ds), job, replicaset (rs)` envLong = templates.LongDesc(` @@ -73,10 +75,10 @@ var ( kubectl set env rc --all ENV=prod # Import environment from a secret - kubectl set env --from=secret/mysecret dc/myapp + kubectl set env --from=secret/mysecret deployment/myapp # Import environment from a config map with a prefix - kubectl set env --from=configmap/myconfigmap --prefix=MYSQL_ dc/myapp + kubectl set env --from=configmap/myconfigmap --prefix=MYSQL_ deployment/myapp # Remove the environment variable ENV from container 'c1' in all deployment configs kubectl set env deployments --all --containers="c1" ENV- @@ -86,7 +88,7 @@ var ( kubectl set env -f deploy.json ENV- # Set some of the local shell environment into a deployment config on the server - env | grep RAILS_ | kubectl set env -e - dc/registry`) + env | grep RAILS_ | kubectl set env -e - deployment/registry`) ) type EnvOptions struct { @@ -115,14 +117,13 @@ type EnvOptions struct { Prefix string Mapper meta.RESTMapper - Typer runtime.ObjectTyper Builder *resource.Builder Infos []*resource.Info Encoder runtime.Encoder Cmd *cobra.Command - UpdatePodSpecForObject func(obj runtime.Object, fn func(*api.PodSpec) error) (bool, error) + UpdatePodSpecForObject func(obj runtime.Object, fn func(*v1.PodSpec) error) (bool, error) PrintObject func(cmd *cobra.Command, isLocal bool, mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error } @@ -152,7 +153,7 @@ func NewCmdEnv(f cmdutil.Factory, in io.Reader, out, errout io.Writer) *cobra.Co cmd.Flags().BoolVar(&options.List, "list", options.List, "If true, display the environment and any changes in the standard format. this flag will removed when we have kubectl view env.") cmd.Flags().BoolVar(&options.Resolve, "resolve", options.Resolve, "If true, show secret or configmap references when listing variables") cmd.Flags().StringVarP(&options.Selector, "selector", "l", options.Selector, "Selector (label query) to filter on") - cmd.Flags().BoolVar(&options.Local, "local", false, "If true, set image will NOT contact api-server but run locally.") + cmd.Flags().BoolVar(&options.Local, "local", false, "If true, set env will NOT contact api-server but run locally.") cmd.Flags().BoolVar(&options.All, "all", options.All, "If true, select all resources in the namespace of the specified resource types") cmd.Flags().BoolVar(&options.Overwrite, "overwrite", true, "If true, allow environment to be overwritten, otherwise reject updates that overwrite existing environment.") @@ -162,7 +163,7 @@ func NewCmdEnv(f cmdutil.Factory, in io.Reader, out, errout io.Writer) *cobra.Co return cmd } -func validateNoOverwrites(existing []api.EnvVar, env []api.EnvVar) error { +func validateNoOverwrites(existing []v1.EnvVar, env []v1.EnvVar) error { for _, e := range env { if current, exists := findEnv(existing, e.Name); exists && current.Value != e.Value { return fmt.Errorf("'%s' already has a value (%s), and --overwrite is false", current.Name, current.Value) @@ -172,7 +173,6 @@ func validateNoOverwrites(existing []api.EnvVar, env []api.EnvVar) error { } func keyToEnvName(key string) string { - validEnvNameRegexp := regexp.MustCompile("[^a-zA-Z0-9_]") return strings.ToUpper(validEnvNameRegexp.ReplaceAllString(key, "_")) } @@ -185,7 +185,7 @@ func (o *EnvOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri return cmdutil.UsageErrorf(cmd, "one or more resources must be specified as or /") } - o.Mapper, o.Typer = f.Object() + o.Mapper, _ = f.Object() o.UpdatePodSpecForObject = f.UpdatePodSpecForObject o.Encoder = f.JSONEncoder() o.ContainerSelector = cmdutil.GetFlagString(cmd, "containers") @@ -215,9 +215,13 @@ func (o *EnvOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri // RunEnv contains all the necessary functionality for the OpenShift cli env command func (o *EnvOptions) RunEnv(f cmdutil.Factory) error { - kubeClient, err := f.ClientSet() - if err != nil { - return err + var kubeClient *kubernetes.Clientset + if o.List { + client, err := f.KubernetesClientSet() + if err != nil { + return err + } + kubeClient = client } cmdNamespace, enforceNamespace, err := f.DefaultNamespace() @@ -232,6 +236,8 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error { if len(o.From) != 0 { b := f.NewBuilder(). + Internal(). + LocalParam(o.Local). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &o.FilenameOptions). @@ -239,11 +245,9 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error { if !o.Local { b = b. - SelectorParam(o.Selector). + LabelSelectorParam(o.Selector). ResourceTypeOrNameArgs(o.All, o.From). Latest() - } else { - b = b.Local(f.ClientForMapping) } infos, err := b.Do().Infos() @@ -252,14 +256,18 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error { } for _, info := range infos { - switch from := info.Object.(type) { - case *api.Secret: + versionedObject, err := info.Mapping.ConvertToVersion(info.Object, info.Mapping.GroupVersionKind.GroupVersion()) + if err != nil { + return err + } + switch from := versionedObject.(type) { + case *v1.Secret: for key := range from.Data { - envVar := api.EnvVar{ + envVar := v1.EnvVar{ Name: keyToEnvName(key), - ValueFrom: &api.EnvVarSource{ - SecretKeyRef: &api.SecretKeySelector{ - LocalObjectReference: api.LocalObjectReference{ + ValueFrom: &v1.EnvVarSource{ + SecretKeyRef: &v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ Name: from.Name, }, Key: key, @@ -268,13 +276,13 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error { } env = append(env, envVar) } - case *api.ConfigMap: + case *v1.ConfigMap: for key := range from.Data { - envVar := api.EnvVar{ + envVar := v1.EnvVar{ Name: keyToEnvName(key), - ValueFrom: &api.EnvVarSource{ - ConfigMapKeyRef: &api.ConfigMapKeySelector{ - LocalObjectReference: api.LocalObjectReference{ + ValueFrom: &v1.EnvVarSource{ + ConfigMapKeyRef: &v1.ConfigMapKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ Name: from.Name, }, Key: key, @@ -296,18 +304,17 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error { } b := f.NewBuilder(). + Internal(). + LocalParam(o.Local). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &o.FilenameOptions). Flatten() if !o.Local { - b = b. - SelectorParam(o.Selector). + b.LabelSelectorParam(o.Selector). ResourceTypeOrNameArgs(o.All, o.Resources...). Latest() - } else { - b = b.Local(f.ClientForMapping) } o.Infos, err = b.Do().Infos() @@ -315,7 +322,8 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error { return err } patches := CalculatePatches(o.Infos, o.Encoder, func(info *resource.Info) ([]byte, error) { - _, err := f.UpdatePodSpecForObject(info.Object, func(spec *api.PodSpec) error { + info.Object = info.AsVersioned() + _, err := o.UpdatePodSpecForObject(info.Object, func(spec *v1.PodSpec) error { resolutionErrorsEncountered := false containers, _ := selectContainers(spec.Containers, o.ContainerSelector) if len(containers) == 0 { @@ -381,9 +389,7 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error { }) if err == nil { - // TODO: switch UpdatePodSpecForObject to work on v1.PodSpec, use info.VersionedObject, and avoid conversion completely - versionedEncoder := api.Codecs.EncoderForVersion(o.Encoder, info.Mapping.GroupVersionKind.GroupVersion()) - return runtime.Encode(versionedEncoder, info.Object) + return runtime.Encode(o.Encoder, info.Object) } return nil, err }) @@ -407,7 +413,7 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error { } if o.PrintObject != nil && (o.Local || o.DryRun) { - if err := o.PrintObject(o.Cmd, o.Local, o.Mapper, info.Object, o.Out); err != nil { + if err := o.PrintObject(o.Cmd, o.Local, o.Mapper, patch.Info.AsVersioned(), o.Out); err != nil { return err } continue @@ -427,10 +433,13 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error { } if len(o.Output) > 0 { - return o.PrintObject(o.Cmd, o.Local, o.Mapper, obj, o.Out) + if err := o.PrintObject(o.Cmd, o.Local, o.Mapper, info.AsVersioned(), o.Out); err != nil { + return err + } + continue } - cmdutil.PrintSuccess(o.Mapper, o.ShortOutput, o.Out, info.Mapping.Resource, info.Name, false, "env updated") + f.PrintSuccess(o.Mapper, o.ShortOutput, o.Out, info.Mapping.Resource, info.Name, false, "env updated") } return utilerrors.NewAggregate(allErrs) } diff --git a/pkg/kubectl/cmd/set/set_env_test.go b/pkg/kubectl/cmd/set/set_env_test.go index 129a5c98c6e..14594d039e0 100644 --- a/pkg/kubectl/cmd/set/set_env_test.go +++ b/pkg/kubectl/cmd/set/set_env_test.go @@ -18,24 +18,38 @@ package set import ( "bytes" + "fmt" + "io/ioutil" "net/http" + "os" + "path" "strings" "testing" + "github.com/stretchr/testify/assert" + appsv1 "k8s.io/api/apps/v1" + appsv1beta1 "k8s.io/api/apps/v1beta1" + appsv1beta2 "k8s.io/api/apps/v1beta2" + batchv1 "k8s.io/api/batch/v1" + "k8s.io/api/core/v1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/kubectl/categories" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/printers" - "os" ) func TestSetEnvLocal(t *testing.T) { f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Version: ""}, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) @@ -43,7 +57,7 @@ func TestSetEnvLocal(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}} buf := bytes.NewBuffer([]byte{}) cmd := NewCmdEnv(f, os.Stdin, buf, buf) @@ -68,3 +82,398 @@ func TestSetEnvLocal(t *testing.T) { t.Errorf("did not set env: %s", buf.String()) } } + +func TestSetMultiResourcesEnvLocal(t *testing.T) { + f, tf, codec, ns := cmdtesting.NewAPIFactory() + tf.Client = &fake.RESTClient{ + GroupVersion: schema.GroupVersion{Version: ""}, + NegotiatedSerializer: ns, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) + return nil, nil + }), + } + tf.Namespace = "test" + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}} + + buf := bytes.NewBuffer([]byte{}) + cmd := NewCmdEnv(f, os.Stdin, buf, buf) + cmd.SetOutput(buf) + cmd.Flags().Set("output", "name") + cmd.Flags().Set("local", "true") + mapper, typer := f.Object() + tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{codec}, Typer: typer, Mapper: mapper} + + opts := EnvOptions{FilenameOptions: resource.FilenameOptions{ + Filenames: []string{"../../../../test/fixtures/pkg/kubectl/cmd/set/multi-resource-yaml.yaml"}}, + Out: buf, + Local: true} + err := opts.Complete(f, cmd, []string{"env=prod"}) + if err == nil { + err = opts.RunEnv(f) + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + expectedOut := "replicationcontrollers/first-rc\nreplicationcontrollers/second-rc\n" + if buf.String() != expectedOut { + t.Errorf("expected out:\n%s\nbut got:\n%s", expectedOut, buf.String()) + } +} + +func TestSetEnvRemote(t *testing.T) { + inputs := []struct { + object runtime.Object + apiPrefix, apiGroup, apiVersion string + testAPIGroup string + args []string + }{ + { + object: &extensionsv1beta1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: extensionsv1beta1.ReplicaSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "extensions", apiVersion: "v1beta1", + args: []string{"replicaset", "nginx", "env=prod"}, + }, + { + object: &appsv1beta2.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.ReplicaSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"replicaset", "nginx", "env=prod"}, + }, + { + object: &appsv1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.ReplicaSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"replicaset", "nginx", "env=prod"}, + }, + { + object: &extensionsv1beta1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: extensionsv1beta1.DaemonSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "extensions", apiVersion: "v1beta1", + args: []string{"daemonset", "nginx", "env=prod"}, + }, + { + object: &appsv1beta2.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.DaemonSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"daemonset", "nginx", "env=prod"}, + }, + { + object: &appsv1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.DaemonSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"daemonset", "nginx", "env=prod"}, + }, + { + object: &extensionsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: extensionsv1beta1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "extensions", apiVersion: "v1beta1", + args: []string{"deployment", "nginx", "env=prod"}, + }, + { + object: &appsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta1", + args: []string{"deployment", "nginx", "env=prod"}, + }, + { + object: &appsv1beta2.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"deployment", "nginx", "env=prod"}, + }, + { + object: &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"deployment", "nginx", "env=prod"}, + }, + { + object: &appsv1beta1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta1.StatefulSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "apps", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta1", + args: []string{"statefulset", "nginx", "env=prod"}, + }, + { + object: &appsv1beta2.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.StatefulSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "apps", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"statefulset", "nginx", "env=prod"}, + }, + { + object: &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.StatefulSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "apps", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"statefulset", "nginx", "env=prod"}, + }, + { + object: &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: batchv1.JobSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "batch", + apiPrefix: "/apis", apiGroup: "batch", apiVersion: "v1", + args: []string{"job", "nginx", "env=prod"}, + }, + { + object: &v1.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: v1.ReplicationControllerSpec{ + Template: &v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "", + apiPrefix: "/api", apiGroup: "", apiVersion: "v1", + args: []string{"replicationcontroller", "nginx", "env=prod"}, + }, + } + for _, input := range inputs { + groupVersion := schema.GroupVersion{Group: input.apiGroup, Version: input.apiVersion} + testapi.Default = testapi.Groups[input.testAPIGroup] + f, tf, _, ns := cmdtesting.NewAPIFactory() + codec := scheme.Codecs.CodecForVersions(scheme.Codecs.LegacyCodec(groupVersion), scheme.Codecs.UniversalDecoder(groupVersion), groupVersion, groupVersion) + tf.Printer = printers.NewVersionedPrinter(&printers.YAMLPrinter{}, testapi.Default.Converter(), *testapi.Default.GroupVersion()) + tf.Namespace = "test" + tf.CategoryExpander = categories.LegacyCategoryExpander + tf.Client = &fake.RESTClient{ + GroupVersion: groupVersion, + NegotiatedSerializer: ns, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + resourcePath := testapi.Default.ResourcePath(input.args[0]+"s", tf.Namespace, input.args[1]) + switch p, m := req.URL.Path, req.Method; { + case p == resourcePath && m == http.MethodGet: + return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, input.object)}, nil + case p == resourcePath && m == http.MethodPatch: + stream, err := req.GetBody() + if err != nil { + return nil, err + } + bytes, err := ioutil.ReadAll(stream) + if err != nil { + return nil, err + } + assert.Contains(t, string(bytes), `"value":`+`"`+"prod"+`"`, fmt.Sprintf("env not updated for %#v", input.object)) + return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, input.object)}, nil + default: + t.Errorf("%s: unexpected request: %s %#v\n%#v", "image", req.Method, req.URL, req) + return nil, fmt.Errorf("unexpected request") + } + }), + VersionedAPIPath: path.Join(input.apiPrefix, testapi.Default.GroupVersion().String()), + } + out := new(bytes.Buffer) + cmd := NewCmdEnv(f, out, out, out) + cmd.SetOutput(out) + cmd.Flags().Set("output", "yaml") + opts := EnvOptions{ + Out: out, + Local: false} + err := opts.Complete(f, cmd, input.args) + assert.NoError(t, err) + err = opts.RunEnv(f) + assert.NoError(t, err) + } +} diff --git a/pkg/kubectl/cmd/set/set_image.go b/pkg/kubectl/cmd/set/set_image.go index d366d74049c..09bff8302ee 100644 --- a/pkg/kubectl/cmd/set/set_image.go +++ b/pkg/kubectl/cmd/set/set_image.go @@ -21,11 +21,11 @@ import ( "io" "github.com/spf13/cobra" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" utilerrors "k8s.io/apimachinery/pkg/util/errors" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" @@ -38,9 +38,9 @@ type ImageOptions struct { resource.FilenameOptions Mapper meta.RESTMapper - Typer runtime.ObjectTyper Infos []*resource.Info Encoder runtime.Encoder + Decoder runtime.Decoder Selector string Out io.Writer Err io.Writer @@ -54,8 +54,9 @@ type ImageOptions struct { Cmd *cobra.Command ResolveImage func(in string) (string, error) + PrintSuccess func(mapper meta.RESTMapper, shortOutput bool, out io.Writer, resource, name string, dryRun bool, operation string) PrintObject func(cmd *cobra.Command, isLocal bool, mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error - UpdatePodSpecForObject func(obj runtime.Object, fn func(*api.PodSpec) error) (bool, error) + UpdatePodSpecForObject func(obj runtime.Object, fn func(*v1.PodSpec) error) (bool, error) Resources []string ContainerImages map[string]string } @@ -115,9 +116,11 @@ func NewCmdImage(f cmdutil.Factory, out, err io.Writer) *cobra.Command { } func (o *ImageOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { - o.Mapper, o.Typer = f.Object() + o.Mapper, _ = f.Object() + o.PrintSuccess = f.PrintSuccess o.UpdatePodSpecForObject = f.UpdatePodSpecForObject o.Encoder = f.JSONEncoder() + o.Decoder = f.Decoder(true) o.ShortOutput = cmdutil.GetFlagString(cmd, "output") == "name" o.Record = cmdutil.GetRecordFlag(cmd) o.ChangeCause = f.Command(cmd, false) @@ -140,6 +143,8 @@ func (o *ImageOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false) builder := f.NewBuilder(). + Internal(). + LocalParam(o.Local). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &o.FilenameOptions). @@ -147,8 +152,7 @@ func (o *ImageOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st Flatten() if !o.Local { - builder = builder. - SelectorParam(o.Selector). + builder.LabelSelectorParam(o.Selector). ResourceTypeOrNameArgs(o.All, o.Resources...). Latest() } else { @@ -158,8 +162,6 @@ func (o *ImageOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st if len(o.Resources) > 0 { return resource.LocalResourceError } - - builder = builder.Local(f.ClientForMapping) } o.Infos, err = builder.Do().Infos() @@ -188,7 +190,8 @@ func (o *ImageOptions) Run() error { patches := CalculatePatches(o.Infos, o.Encoder, func(info *resource.Info) ([]byte, error) { transformed := false - _, err := o.UpdatePodSpecForObject(info.Object, func(spec *api.PodSpec) error { + info.Object = info.AsVersioned() + _, err := o.UpdatePodSpecForObject(info.Object, func(spec *v1.PodSpec) error { for name, image := range o.ContainerImages { var ( containerFound bool @@ -225,9 +228,7 @@ func (o *ImageOptions) Run() error { return nil }) if transformed && err == nil { - // TODO: switch UpdatePodSpecForObject to work on v1.PodSpec, use info.VersionedObject, and avoid conversion completely - versionedEncoder := api.Codecs.EncoderForVersion(o.Encoder, info.Mapping.GroupVersionKind.GroupVersion()) - return runtime.Encode(versionedEncoder, info.Object) + return runtime.Encode(o.Encoder, info.Object) } return nil, err }) @@ -245,7 +246,7 @@ func (o *ImageOptions) Run() error { } if o.PrintObject != nil && (o.Local || o.DryRun) { - if err := o.PrintObject(o.Cmd, o.Local, o.Mapper, info.Object, o.Out); err != nil { + if err := o.PrintObject(o.Cmd, o.Local, o.Mapper, patch.Info.AsVersioned(), o.Out); err != nil { return err } continue @@ -271,12 +272,12 @@ func (o *ImageOptions) Run() error { info.Refresh(obj, true) if len(o.Output) > 0 { - if err := o.PrintObject(o.Cmd, o.Local, o.Mapper, obj, o.Out); err != nil { + if err := o.PrintObject(o.Cmd, o.Local, o.Mapper, info.AsVersioned(), o.Out); err != nil { return err } continue } - cmdutil.PrintSuccess(o.Mapper, o.ShortOutput, o.Out, info.Mapping.Resource, info.Name, o.DryRun, "image updated") + o.PrintSuccess(o.Mapper, o.ShortOutput, o.Out, info.Mapping.Resource, info.Name, o.DryRun, "image updated") } return utilerrors.NewAggregate(allErrs) } diff --git a/pkg/kubectl/cmd/set/set_image_test.go b/pkg/kubectl/cmd/set/set_image_test.go index 0faf88eb634..ca27bbcdd9c 100644 --- a/pkg/kubectl/cmd/set/set_image_test.go +++ b/pkg/kubectl/cmd/set/set_image_test.go @@ -18,23 +18,37 @@ package set import ( "bytes" + "fmt" + "io/ioutil" "net/http" + "path" "strings" "testing" + "github.com/stretchr/testify/assert" + appsv1 "k8s.io/api/apps/v1" + appsv1beta1 "k8s.io/api/apps/v1beta1" + appsv1beta2 "k8s.io/api/apps/v1beta2" + batchv1 "k8s.io/api/batch/v1" + "k8s.io/api/core/v1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/kubectl/categories" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/printers" ) func TestImageLocal(t *testing.T) { f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Version: ""}, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) @@ -42,7 +56,7 @@ func TestImageLocal(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}} buf := bytes.NewBuffer([]byte{}) cmd := NewCmdImage(f, buf, buf) @@ -133,3 +147,400 @@ func TestSetImageValidation(t *testing.T) { } } } + +func TestSetMultiResourcesImageLocal(t *testing.T) { + f, tf, codec, ns := cmdtesting.NewAPIFactory() + tf.Client = &fake.RESTClient{ + GroupVersion: schema.GroupVersion{Version: ""}, + NegotiatedSerializer: ns, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) + return nil, nil + }), + } + tf.Namespace = "test" + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}} + + buf := bytes.NewBuffer([]byte{}) + cmd := NewCmdImage(f, buf, buf) + cmd.SetOutput(buf) + cmd.Flags().Set("output", "name") + cmd.Flags().Set("local", "true") + mapper, typer := f.Object() + tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{codec}, Typer: typer, Mapper: mapper} + + opts := ImageOptions{FilenameOptions: resource.FilenameOptions{ + Filenames: []string{"../../../../test/fixtures/pkg/kubectl/cmd/set/multi-resource-yaml.yaml"}}, + Out: buf, + Local: true} + err := opts.Complete(f, cmd, []string{"*=thingy"}) + if err == nil { + err = opts.Validate() + } + if err == nil { + err = opts.Run() + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + expectedOut := "replicationcontrollers/first-rc\nreplicationcontrollers/second-rc\n" + if buf.String() != expectedOut { + t.Errorf("expected out:\n%s\nbut got:\n%s", expectedOut, buf.String()) + } +} + +func TestSetImageRemote(t *testing.T) { + inputs := []struct { + object runtime.Object + apiPrefix, apiGroup, apiVersion string + testAPIGroup string + args []string + }{ + { + object: &extensionsv1beta1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: extensionsv1beta1.ReplicaSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "extensions", apiVersion: "v1beta1", + args: []string{"replicaset", "nginx", "*=thingy"}, + }, + { + object: &appsv1beta2.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.ReplicaSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"replicaset", "nginx", "*=thingy"}, + }, + { + object: &appsv1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.ReplicaSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"replicaset", "nginx", "*=thingy"}, + }, + { + object: &extensionsv1beta1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: extensionsv1beta1.DaemonSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "extensions", apiVersion: "v1beta1", + args: []string{"daemonset", "nginx", "*=thingy"}, + }, + { + object: &appsv1beta2.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.DaemonSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"daemonset", "nginx", "*=thingy"}, + }, + { + object: &appsv1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.DaemonSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"daemonset", "nginx", "*=thingy"}, + }, + { + object: &extensionsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: extensionsv1beta1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "extensions", apiVersion: "v1beta1", + args: []string{"deployment", "nginx", "*=thingy"}, + }, + { + object: &appsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta1", + args: []string{"deployment", "nginx", "*=thingy"}, + }, + { + object: &appsv1beta2.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"deployment", "nginx", "*=thingy"}, + }, + { + object: &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"deployment", "nginx", "*=thingy"}, + }, + { + object: &appsv1beta1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta1.StatefulSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "apps", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta1", + args: []string{"statefulset", "nginx", "*=thingy"}, + }, + { + object: &appsv1beta2.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.StatefulSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "apps", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"statefulset", "nginx", "*=thingy"}, + }, + { + object: &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.StatefulSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "apps", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"statefulset", "nginx", "*=thingy"}, + }, + { + object: &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: batchv1.JobSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "batch", + apiPrefix: "/apis", apiGroup: "batch", apiVersion: "v1", + args: []string{"job", "nginx", "*=thingy"}, + }, + { + object: &v1.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: v1.ReplicationControllerSpec{ + Template: &v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "", + apiPrefix: "/api", apiGroup: "", apiVersion: "v1", + args: []string{"replicationcontroller", "nginx", "*=thingy"}, + }, + } + for _, input := range inputs { + groupVersion := schema.GroupVersion{Group: input.apiGroup, Version: input.apiVersion} + testapi.Default = testapi.Groups[input.testAPIGroup] + f, tf, _, ns := cmdtesting.NewAPIFactory() + codec := scheme.Codecs.CodecForVersions(scheme.Codecs.LegacyCodec(groupVersion), scheme.Codecs.UniversalDecoder(groupVersion), groupVersion, groupVersion) + tf.Printer = printers.NewVersionedPrinter(&printers.YAMLPrinter{}, testapi.Default.Converter(), *testapi.Default.GroupVersion()) + tf.Namespace = "test" + tf.CategoryExpander = categories.LegacyCategoryExpander + tf.Client = &fake.RESTClient{ + GroupVersion: groupVersion, + NegotiatedSerializer: ns, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + resourcePath := testapi.Default.ResourcePath(input.args[0]+"s", tf.Namespace, input.args[1]) + switch p, m := req.URL.Path, req.Method; { + case p == resourcePath && m == http.MethodGet: + return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, input.object)}, nil + case p == resourcePath && m == http.MethodPatch: + stream, err := req.GetBody() + if err != nil { + return nil, err + } + bytes, err := ioutil.ReadAll(stream) + if err != nil { + return nil, err + } + assert.Contains(t, string(bytes), `"image":`+`"`+"thingy"+`"`, fmt.Sprintf("image not updated for %#v", input.object)) + return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, input.object)}, nil + default: + t.Errorf("%s: unexpected request: %s %#v\n%#v", "image", req.Method, req.URL, req) + return nil, fmt.Errorf("unexpected request") + } + }), + VersionedAPIPath: path.Join(input.apiPrefix, testapi.Default.GroupVersion().String()), + } + out := new(bytes.Buffer) + cmd := NewCmdImage(f, out, out) + cmd.SetOutput(out) + cmd.Flags().Set("output", "yaml") + opts := ImageOptions{ + Out: out, + Local: false} + err := opts.Complete(f, cmd, input.args) + assert.NoError(t, err) + err = opts.Run() + assert.NoError(t, err) + } +} diff --git a/pkg/kubectl/cmd/set/set_resources.go b/pkg/kubectl/cmd/set/set_resources.go index 7ee0f99ec61..5284e52ac58 100644 --- a/pkg/kubectl/cmd/set/set_resources.go +++ b/pkg/kubectl/cmd/set/set_resources.go @@ -22,8 +22,8 @@ import ( "strings" "github.com/spf13/cobra" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/kubernetes/pkg/api" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -63,7 +63,6 @@ type ResourcesOptions struct { resource.FilenameOptions Mapper meta.RESTMapper - Typer runtime.ObjectTyper Infos []*resource.Info Encoder runtime.Encoder Out io.Writer @@ -79,10 +78,11 @@ type ResourcesOptions struct { Limits string Requests string - ResourceRequirements api.ResourceRequirements + ResourceRequirements v1.ResourceRequirements + PrintSuccess func(mapper meta.RESTMapper, shortOutput bool, out io.Writer, resource, name string, dryRun bool, operation string) PrintObject func(cmd *cobra.Command, isLocal bool, mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error - UpdatePodSpecForObject func(obj runtime.Object, fn func(*api.PodSpec) error) (bool, error) + UpdatePodSpecForObject func(obj runtime.Object, fn func(*v1.PodSpec) error) (bool, error) Resources []string } @@ -127,7 +127,8 @@ func NewCmdResources(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra. } func (o *ResourcesOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { - o.Mapper, o.Typer = f.Object() + o.Mapper, _ = f.Object() + o.PrintSuccess = f.PrintSuccess o.UpdatePodSpecForObject = f.UpdatePodSpecForObject o.Encoder = f.JSONEncoder() o.Output = cmdutil.GetFlagString(cmd, "output") @@ -144,6 +145,8 @@ func (o *ResourcesOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false) builder := f.NewBuilder(). + Internal(). + LocalParam(o.Local). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &o.FilenameOptions). @@ -151,19 +154,18 @@ func (o *ResourcesOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args Flatten() if !o.Local { - builder = builder. - SelectorParam(o.Selector). + builder.LabelSelectorParam(o.Selector). ResourceTypeOrNameArgs(o.All, args...). Latest() } else { // if a --local flag was provided, and a resource was specified in the form // /, fail immediately as --local cannot query the api server // for the specified resource. + // TODO: this should be in the builder - if someone specifies tuples, fail when + // local is true if len(args) > 0 { return resource.LocalResourceError } - - builder = builder.Local(f.ClientForMapping) } o.Infos, err = builder.Do().Infos() @@ -179,7 +181,7 @@ func (o *ResourcesOptions) Validate() error { return fmt.Errorf("you must specify an update to requests or limits (in the form of --requests/--limits)") } - o.ResourceRequirements, err = kubectl.HandleResourceRequirements(map[string]string{"limits": o.Limits, "requests": o.Requests}) + o.ResourceRequirements, err = kubectl.HandleResourceRequirementsV1(map[string]string{"limits": o.Limits, "requests": o.Requests}) if err != nil { return err } @@ -191,19 +193,20 @@ func (o *ResourcesOptions) Run() error { allErrs := []error{} patches := CalculatePatches(o.Infos, o.Encoder, func(info *resource.Info) ([]byte, error) { transformed := false - _, err := o.UpdatePodSpecForObject(info.Object, func(spec *api.PodSpec) error { + info.Object = info.AsVersioned() + _, err := o.UpdatePodSpecForObject(info.Object, func(spec *v1.PodSpec) error { containers, _ := selectContainers(spec.Containers, o.ContainerSelector) if len(containers) != 0 { for i := range containers { if len(o.Limits) != 0 && len(containers[i].Resources.Limits) == 0 { - containers[i].Resources.Limits = make(api.ResourceList) + containers[i].Resources.Limits = make(v1.ResourceList) } for key, value := range o.ResourceRequirements.Limits { containers[i].Resources.Limits[key] = value } if len(o.Requests) != 0 && len(containers[i].Resources.Requests) == 0 { - containers[i].Resources.Requests = make(api.ResourceList) + containers[i].Resources.Requests = make(v1.ResourceList) } for key, value := range o.ResourceRequirements.Requests { containers[i].Resources.Requests[key] = value @@ -216,9 +219,7 @@ func (o *ResourcesOptions) Run() error { return nil }) if transformed && err == nil { - // TODO: switch UpdatePodSpecForObject to work on v1.PodSpec, use info.VersionedObject, and avoid conversion completely - versionedEncoder := api.Codecs.EncoderForVersion(o.Encoder, info.Mapping.GroupVersionKind.GroupVersion()) - return runtime.Encode(versionedEncoder, info.Object) + return runtime.Encode(o.Encoder, info.Object) } return nil, err }) @@ -237,7 +238,10 @@ func (o *ResourcesOptions) Run() error { } if o.Local || cmdutil.GetDryRunFlag(o.Cmd) { - return o.PrintObject(o.Cmd, o.Local, o.Mapper, info.Object, o.Out) + if err := o.PrintObject(o.Cmd, o.Local, o.Mapper, patch.Info.AsVersioned(), o.Out); err != nil { + return err + } + continue } obj, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch) @@ -259,9 +263,12 @@ func (o *ResourcesOptions) Run() error { shortOutput := o.Output == "name" if len(o.Output) > 0 && !shortOutput { - return o.PrintObject(o.Cmd, o.Local, o.Mapper, info.Object, o.Out) + if err := o.PrintObject(o.Cmd, o.Local, o.Mapper, info.AsVersioned(), o.Out); err != nil { + return err + } + continue } - cmdutil.PrintSuccess(o.Mapper, shortOutput, o.Out, info.Mapping.Resource, info.Name, false, "resource requirements updated") + o.PrintSuccess(o.Mapper, shortOutput, o.Out, info.Mapping.Resource, info.Name, false, "resource requirements updated") } return utilerrors.NewAggregate(allErrs) } diff --git a/pkg/kubectl/cmd/set/set_resources_test.go b/pkg/kubectl/cmd/set/set_resources_test.go index 16bd5300bef..306a43379c6 100644 --- a/pkg/kubectl/cmd/set/set_resources_test.go +++ b/pkg/kubectl/cmd/set/set_resources_test.go @@ -18,23 +18,37 @@ package set import ( "bytes" + "fmt" + "io/ioutil" "net/http" + "path" "strings" "testing" + "github.com/stretchr/testify/assert" + appsv1 "k8s.io/api/apps/v1" + appsv1beta1 "k8s.io/api/apps/v1beta1" + appsv1beta2 "k8s.io/api/apps/v1beta2" + batchv1 "k8s.io/api/batch/v1" + "k8s.io/api/core/v1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/kubectl/categories" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/printers" ) func TestResourcesLocal(t *testing.T) { f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Version: ""}, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) @@ -42,7 +56,7 @@ func TestResourcesLocal(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}} buf := bytes.NewBuffer([]byte{}) cmd := NewCmdResources(f, buf, buf) @@ -74,3 +88,413 @@ func TestResourcesLocal(t *testing.T) { t.Errorf("did not set resources: %s", buf.String()) } } + +func TestSetMultiResourcesLimitsLocal(t *testing.T) { + f, tf, codec, ns := cmdtesting.NewAPIFactory() + tf.Client = &fake.RESTClient{ + GroupVersion: schema.GroupVersion{Version: ""}, + NegotiatedSerializer: ns, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) + return nil, nil + }), + } + tf.Namespace = "test" + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}} + + buf := bytes.NewBuffer([]byte{}) + cmd := NewCmdResources(f, buf, buf) + cmd.SetOutput(buf) + cmd.Flags().Set("output", "name") + cmd.Flags().Set("local", "true") + mapper, typer := f.Object() + tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{codec}, Typer: typer, Mapper: mapper} + + opts := ResourcesOptions{FilenameOptions: resource.FilenameOptions{ + Filenames: []string{"../../../../test/fixtures/pkg/kubectl/cmd/set/multi-resource-yaml.yaml"}}, + Out: buf, + Local: true, + Limits: "cpu=200m,memory=512Mi", + Requests: "cpu=200m,memory=512Mi", + ContainerSelector: "*"} + + err := opts.Complete(f, cmd, []string{}) + if err == nil { + err = opts.Validate() + } + if err == nil { + err = opts.Run() + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + expectedOut := "replicationcontrollers/first-rc\nreplicationcontrollers/second-rc\n" + if buf.String() != expectedOut { + t.Errorf("expected out:\n%s\nbut got:\n%s", expectedOut, buf.String()) + } +} + +func TestSetResourcesRemote(t *testing.T) { + inputs := []struct { + object runtime.Object + apiPrefix, apiGroup, apiVersion string + testAPIGroup string + args []string + }{ + { + object: &extensionsv1beta1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: extensionsv1beta1.ReplicaSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "extensions", apiVersion: "v1beta1", + args: []string{"replicaset", "nginx"}, + }, + { + object: &appsv1beta2.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.ReplicaSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"replicaset", "nginx"}, + }, + { + object: &appsv1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.ReplicaSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"replicaset", "nginx"}, + }, + { + object: &extensionsv1beta1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: extensionsv1beta1.DaemonSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "extensions", apiVersion: "v1beta1", + args: []string{"daemonset", "nginx"}, + }, + { + object: &appsv1beta2.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.DaemonSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"daemonset", "nginx"}, + }, + { + object: &appsv1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.DaemonSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"daemonset", "nginx"}, + }, + { + object: &extensionsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: extensionsv1beta1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "extensions", apiVersion: "v1beta1", + args: []string{"deployment", "nginx"}, + }, + { + object: &appsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta1", + args: []string{"deployment", "nginx"}, + }, + { + object: &appsv1beta2.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"deployment", "nginx"}, + }, + { + object: &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"deployment", "nginx"}, + }, + { + object: &appsv1beta1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta1.StatefulSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "apps", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta1", + args: []string{"statefulset", "nginx"}, + }, + { + object: &appsv1beta2.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.StatefulSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "apps", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"statefulset", "nginx"}, + }, + { + object: &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.StatefulSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "apps", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"statefulset", "nginx"}, + }, + { + object: &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: batchv1.JobSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "batch", + apiPrefix: "/apis", apiGroup: "batch", apiVersion: "v1", + args: []string{"job", "nginx"}, + }, + { + object: &v1.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: v1.ReplicationControllerSpec{ + Template: &v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "", + apiPrefix: "/api", apiGroup: "", apiVersion: "v1", + args: []string{"replicationcontroller", "nginx"}, + }, + } + for i, input := range inputs { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + groupVersion := schema.GroupVersion{Group: input.apiGroup, Version: input.apiVersion} + testapi.Default = testapi.Groups[input.testAPIGroup] + f, tf, _, ns := cmdtesting.NewAPIFactory() + codec := scheme.Codecs.CodecForVersions(scheme.Codecs.LegacyCodec(groupVersion), scheme.Codecs.UniversalDecoder(groupVersion), groupVersion, groupVersion) + mapper, typer := f.Object() + tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{testapi.Default.Codec()}, Typer: typer, Mapper: mapper} + tf.Namespace = "test" + tf.CategoryExpander = categories.LegacyCategoryExpander + tf.Client = &fake.RESTClient{ + GroupVersion: groupVersion, + NegotiatedSerializer: ns, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + resourcePath := testapi.Default.ResourcePath(input.args[0]+"s", tf.Namespace, input.args[1]) + switch p, m := req.URL.Path, req.Method; { + case p == resourcePath && m == http.MethodGet: + return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, input.object)}, nil + case p == resourcePath && m == http.MethodPatch: + stream, err := req.GetBody() + if err != nil { + return nil, err + } + bytes, err := ioutil.ReadAll(stream) + if err != nil { + return nil, err + } + assert.Contains(t, string(bytes), "200m", fmt.Sprintf("resources not updated for %#v", input.object)) + return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, input.object)}, nil + default: + t.Errorf("%s: unexpected request: %s %#v\n%#v", "resources", req.Method, req.URL, req) + return nil, fmt.Errorf("unexpected request") + } + }), + VersionedAPIPath: path.Join(input.apiPrefix, testapi.Default.GroupVersion().String()), + } + buf := new(bytes.Buffer) + cmd := NewCmdResources(f, buf, buf) + cmd.SetOutput(buf) + cmd.Flags().Set("output", "yaml") + opts := ResourcesOptions{ + Out: buf, + Local: true, + Limits: "cpu=200m,memory=512Mi", + ContainerSelector: "*"} + err := opts.Complete(f, cmd, input.args) + if err == nil { + err = opts.Validate() + } + if err == nil { + err = opts.Run() + } + assert.NoError(t, err) + }) + } +} diff --git a/pkg/kubectl/cmd/set/set_selector.go b/pkg/kubectl/cmd/set/set_selector.go index 343c8051803..3579f63cf4b 100644 --- a/pkg/kubectl/cmd/set/set_selector.go +++ b/pkg/kubectl/cmd/set/set_selector.go @@ -21,13 +21,12 @@ import ( "io" "github.com/spf13/cobra" - + "k8s.io/api/core/v1" "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/validation" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" @@ -50,6 +49,7 @@ type SelectorOptions struct { selector *metav1.LabelSelector out io.Writer + PrintSuccess func(mapper meta.RESTMapper, shortOutput bool, out io.Writer, resource, name string, dryRun bool, operation string) PrintObject func(obj runtime.Object) error ClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error) @@ -115,10 +115,13 @@ func (o *SelectorOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [ return err } + o.PrintSuccess = f.PrintSuccess + o.changeCause = f.Command(cmd, false) mapper, _ := f.Object() o.mapper = mapper o.encoder = f.JSONEncoder() + o.resources, o.selector, err = getResourcesAndSelector(args) if err != nil { return err @@ -126,6 +129,8 @@ func (o *SelectorOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [ includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false) o.builder = f.NewBuilder(). + Internal(). + LocalParam(o.local). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &o.fileOptions). @@ -133,7 +138,7 @@ func (o *SelectorOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [ Flatten() if !o.local { - o.builder = o.builder. + o.builder. ResourceTypeOrNameArgs(o.all, o.resources...). Latest() } else { @@ -143,8 +148,6 @@ func (o *SelectorOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [ if len(o.resources) > 0 { return resource.LocalResourceError } - - o.builder = o.builder.Local(f.ClientForMapping) } o.PrintObject = func(obj runtime.Object) error { @@ -178,6 +181,8 @@ func (o *SelectorOptions) RunSelector() error { return r.Visit(func(info *resource.Info, err error) error { patch := &Patch{Info: info} CalculatePatch(patch, o.encoder, func(info *resource.Info) ([]byte, error) { + versioned := info.AsVersioned() + patch.Info.Object = versioned selectErr := updateSelectorForObject(info.Object, *o.selector) if selectErr == nil { @@ -190,8 +195,7 @@ func (o *SelectorOptions) RunSelector() error { return patch.Err } if o.local || o.dryrun { - o.PrintObject(info.Object) - return nil + return o.PrintObject(info.Object) } patched, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch) @@ -211,9 +215,9 @@ func (o *SelectorOptions) RunSelector() error { shortOutput := o.output == "name" if len(o.output) > 0 && !shortOutput { - return o.PrintObject(info.Object) + return o.PrintObject(patched) } - cmdutil.PrintSuccess(o.mapper, shortOutput, o.out, info.Mapping.Resource, info.Name, o.dryrun, "selector updated") + o.PrintSuccess(o.mapper, shortOutput, o.out, info.Mapping.Resource, info.Name, o.dryrun, "selector updated") return nil }) } @@ -231,7 +235,7 @@ func updateSelectorForObject(obj runtime.Object, selector metav1.LabelSelector) } var err error switch t := obj.(type) { - case *api.Service: + case *v1.Service: t.Spec.Selector, err = copyOldSelector() default: err = fmt.Errorf("setting a selector is only supported for Services") diff --git a/pkg/kubectl/cmd/set/set_selector_test.go b/pkg/kubectl/cmd/set/set_selector_test.go index 425f29c3819..901b9564991 100644 --- a/pkg/kubectl/cmd/set/set_selector_test.go +++ b/pkg/kubectl/cmd/set/set_selector_test.go @@ -24,13 +24,14 @@ import ( "testing" "github.com/stretchr/testify/assert" + batchv1 "k8s.io/api/batch/v1" + "k8s.io/api/core/v1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/batch" - "k8s.io/kubernetes/pkg/apis/extensions" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" "k8s.io/kubernetes/pkg/printers" ) @@ -45,14 +46,14 @@ func TestUpdateSelectorForObjectTypes(t *testing.T) { }, }} - rc := api.ReplicationController{} - ser := api.Service{} - dep := extensions.Deployment{Spec: extensions.DeploymentSpec{Selector: &before}} - ds := extensions.DaemonSet{Spec: extensions.DaemonSetSpec{Selector: &before}} - rs := extensions.ReplicaSet{Spec: extensions.ReplicaSetSpec{Selector: &before}} - job := batch.Job{Spec: batch.JobSpec{Selector: &before}} - pvc := api.PersistentVolumeClaim{Spec: api.PersistentVolumeClaimSpec{Selector: &before}} - sa := api.ServiceAccount{} + rc := v1.ReplicationController{} + ser := v1.Service{} + dep := extensionsv1beta1.Deployment{Spec: extensionsv1beta1.DeploymentSpec{Selector: &before}} + ds := extensionsv1beta1.DaemonSet{Spec: extensionsv1beta1.DaemonSetSpec{Selector: &before}} + rs := extensionsv1beta1.ReplicaSet{Spec: extensionsv1beta1.ReplicaSetSpec{Selector: &before}} + job := batchv1.Job{Spec: batchv1.JobSpec{Selector: &before}} + pvc := v1.PersistentVolumeClaim{Spec: v1.PersistentVolumeClaimSpec{Selector: &before}} + sa := v1.ServiceAccount{} type args struct { obj runtime.Object selector metav1.LabelSelector @@ -127,7 +128,7 @@ func TestUpdateSelectorForObjectTypes(t *testing.T) { } func TestUpdateNewSelectorValuesForObject(t *testing.T) { - ser := api.Service{} + ser := v1.Service{} type args struct { obj runtime.Object selector metav1.LabelSelector @@ -169,7 +170,7 @@ func TestUpdateNewSelectorValuesForObject(t *testing.T) { } func TestUpdateOldSelectorValuesForObject(t *testing.T) { - ser := api.Service{Spec: api.ServiceSpec{Selector: map[string]string{"fee": "true"}}} + ser := v1.Service{Spec: v1.ServiceSpec{Selector: map[string]string{"fee": "true"}}} type args struct { obj runtime.Object selector metav1.LabelSelector @@ -317,7 +318,7 @@ func TestGetResourcesAndSelector(t *testing.T) { func TestSelectorTest(t *testing.T) { f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Version: ""}, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) @@ -325,7 +326,7 @@ func TestSelectorTest(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}} buf := bytes.NewBuffer([]byte{}) cmd := NewCmdSelector(f, buf) diff --git a/pkg/kubectl/cmd/set/set_serviceaccount.go b/pkg/kubectl/cmd/set/set_serviceaccount.go index f767bce078e..65ac0d166c3 100644 --- a/pkg/kubectl/cmd/set/set_serviceaccount.go +++ b/pkg/kubectl/cmd/set/set_serviceaccount.go @@ -23,11 +23,11 @@ import ( "github.com/spf13/cobra" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" utilerrors "k8s.io/apimachinery/pkg/util/errors" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" @@ -61,14 +61,16 @@ type serviceAccountConfig struct { out io.Writer err io.Writer dryRun bool + cmd *cobra.Command shortOutput bool all bool record bool output string changeCause string local bool - saPrint func(obj runtime.Object) error - updatePodSpecForObject func(runtime.Object, func(*api.PodSpec) error) (bool, error) + PrintObject func(cmd *cobra.Command, isLocal bool, mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error + updatePodSpecForObject func(runtime.Object, func(*v1.PodSpec) error) (bool, error) + printSuccess func(mapper meta.RESTMapper, shortOutput bool, out io.Writer, resource, name string, dryRun bool, operation string) infos []*resource.Info serviceAccountName string } @@ -96,7 +98,7 @@ func NewCmdServiceAccount(f cmdutil.Factory, out, err io.Writer) *cobra.Command usage := "identifying the resource to get from a server." cmdutil.AddFilenameOptionFlags(cmd, &saConfig.fileNameOptions, usage) cmd.Flags().BoolVar(&saConfig.all, "all", false, "Select all resources, including uninitialized ones, in the namespace of the specified resource types") - cmd.Flags().BoolVar(&saConfig.local, "local", false, "If true, set image will NOT contact api-server but run locally.") + cmd.Flags().BoolVar(&saConfig.local, "local", false, "If true, set serviceaccount will NOT contact api-server but run locally.") cmdutil.AddRecordFlag(cmd) cmdutil.AddDryRunFlag(cmd) cmdutil.AddIncludeUninitializedFlag(cmd) @@ -113,9 +115,10 @@ func (saConfig *serviceAccountConfig) Complete(f cmdutil.Factory, cmd *cobra.Com saConfig.dryRun = cmdutil.GetDryRunFlag(cmd) saConfig.output = cmdutil.GetFlagString(cmd, "output") saConfig.updatePodSpecForObject = f.UpdatePodSpecForObject - saConfig.saPrint = func(obj runtime.Object) error { - return f.PrintObject(cmd, saConfig.local, saConfig.mapper, obj, saConfig.out) - } + saConfig.PrintObject = f.PrintObject + saConfig.cmd = cmd + + saConfig.printSuccess = f.PrintSuccess cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err @@ -126,7 +129,10 @@ func (saConfig *serviceAccountConfig) Complete(f cmdutil.Factory, cmd *cobra.Com saConfig.serviceAccountName = args[len(args)-1] resources := args[:len(args)-1] includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false) - builder := f.NewBuilder().ContinueOnError(). + builder := f.NewBuilder(). + Internal(). + LocalParam(saConfig.local). + ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &saConfig.fileNameOptions). IncludeUninitialized(includeUninitialized). @@ -134,8 +140,6 @@ func (saConfig *serviceAccountConfig) Complete(f cmdutil.Factory, cmd *cobra.Com if !saConfig.local { builder.ResourceTypeOrNameArgs(saConfig.all, resources...). Latest() - } else { - builder = builder.Local(f.ClientForMapping) } saConfig.infos, err = builder.Do().Infos() if err != nil { @@ -148,7 +152,8 @@ func (saConfig *serviceAccountConfig) Complete(f cmdutil.Factory, cmd *cobra.Com func (saConfig *serviceAccountConfig) Run() error { patchErrs := []error{} patchFn := func(info *resource.Info) ([]byte, error) { - saConfig.updatePodSpecForObject(info.Object, func(podSpec *api.PodSpec) error { + info.Object = info.AsVersioned() + saConfig.updatePodSpecForObject(info.Object, func(podSpec *v1.PodSpec) error { podSpec.ServiceAccountName = saConfig.serviceAccountName return nil }) @@ -162,7 +167,9 @@ func (saConfig *serviceAccountConfig) Run() error { continue } if saConfig.local || saConfig.dryRun { - saConfig.saPrint(patch.Info.Object) + if err := saConfig.PrintObject(saConfig.cmd, saConfig.local, saConfig.mapper, patch.Info.AsVersioned(), saConfig.out); err != nil { + return err + } continue } patched, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch) @@ -179,9 +186,12 @@ func (saConfig *serviceAccountConfig) Run() error { } } if len(saConfig.output) > 0 { - saConfig.saPrint(patched) + if err := saConfig.PrintObject(saConfig.cmd, saConfig.local, saConfig.mapper, info.AsVersioned(), saConfig.out); err != nil { + return err + } + continue } - cmdutil.PrintSuccess(saConfig.mapper, saConfig.shortOutput, saConfig.out, info.Mapping.Resource, info.Name, saConfig.dryRun, "serviceaccount updated") + saConfig.printSuccess(saConfig.mapper, saConfig.shortOutput, saConfig.out, info.Mapping.Resource, info.Name, saConfig.dryRun, "serviceaccount updated") } return utilerrors.NewAggregate(patchErrs) } diff --git a/pkg/kubectl/cmd/set/set_serviceaccount_test.go b/pkg/kubectl/cmd/set/set_serviceaccount_test.go index 16cfbdc5dfe..713ba1b5b3e 100644 --- a/pkg/kubectl/cmd/set/set_serviceaccount_test.go +++ b/pkg/kubectl/cmd/set/set_serviceaccount_test.go @@ -26,17 +26,22 @@ import ( "testing" "github.com/stretchr/testify/assert" - + appsv1 "k8s.io/api/apps/v1" + appsv1beta1 "k8s.io/api/apps/v1beta1" + appsv1beta2 "k8s.io/api/apps/v1beta2" + batchv1 "k8s.io/api/batch/v1" + "k8s.io/api/core/v1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" - "k8s.io/kubernetes/pkg/apis/apps" - "k8s.io/kubernetes/pkg/apis/batch" - "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/kubectl/categories" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/printers" ) @@ -49,113 +54,275 @@ Example resource specifications include: ' ' ''` -func TestServiceAccountLocal(t *testing.T) { +func TestSetServiceAccountLocal(t *testing.T) { inputs := []struct { yaml string apiGroup string }{ - {yaml: "../../../../test/fixtures/doc-yaml/user-guide/replication.yaml", apiGroup: api.GroupName}, - {yaml: "../../../../test/fixtures/doc-yaml/admin/daemon.yaml", apiGroup: extensions.GroupName}, - {yaml: "../../../../test/fixtures/doc-yaml/user-guide/replicaset/redis-slave.yaml", apiGroup: extensions.GroupName}, - {yaml: "../../../../test/fixtures/doc-yaml/user-guide/job.yaml", apiGroup: batch.GroupName}, - {yaml: "../../../../test/fixtures/doc-yaml/user-guide/deployment.yaml", apiGroup: extensions.GroupName}, - {yaml: "../../../../examples/storage/minio/minio-distributed-statefulset.yaml", apiGroup: apps.GroupName}, + {yaml: "../../../../test/fixtures/doc-yaml/user-guide/replication.yaml", apiGroup: ""}, + {yaml: "../../../../test/fixtures/doc-yaml/admin/daemon.yaml", apiGroup: "extensions"}, + {yaml: "../../../../test/fixtures/doc-yaml/user-guide/replicaset/redis-slave.yaml", apiGroup: "extensions"}, + {yaml: "../../../../test/fixtures/doc-yaml/user-guide/job.yaml", apiGroup: "batch"}, + {yaml: "../../../../test/fixtures/doc-yaml/user-guide/deployment.yaml", apiGroup: "extensions"}, } - f, tf, _, _ := cmdtesting.NewAPIFactory() + for i, input := range inputs { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + f, tf, _, _ := cmdtesting.NewAPIFactory() + tf.Client = &fake.RESTClient{ + GroupVersion: schema.GroupVersion{Version: "v1"}, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) + return nil, nil + }), + } + tf.Namespace = "test" + out := new(bytes.Buffer) + cmd := NewCmdServiceAccount(f, out, out) + cmd.SetOutput(out) + cmd.Flags().Set("output", "yaml") + cmd.Flags().Set("local", "true") + testapi.Default = testapi.Groups[input.apiGroup] + tf.Printer = printers.NewVersionedPrinter(&printers.YAMLPrinter{}, testapi.Default.Converter(), *testapi.Default.GroupVersion()) + saConfig := serviceAccountConfig{fileNameOptions: resource.FilenameOptions{ + Filenames: []string{input.yaml}}, + out: out, + local: true} + err := saConfig.Complete(f, cmd, []string{serviceAccount}) + assert.NoError(t, err) + err = saConfig.Run() + assert.NoError(t, err) + assert.Contains(t, out.String(), "serviceAccountName: "+serviceAccount, fmt.Sprintf("serviceaccount not updated for %s", input.yaml)) + }) + } +} + +func TestSetServiceAccountMultiLocal(t *testing.T) { + testapi.Default = testapi.Groups[""] + f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Version: ""}, + NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) return nil, nil }), } tf.Namespace = "test" - out := new(bytes.Buffer) - cmd := NewCmdServiceAccount(f, out, out) - cmd.SetOutput(out) - cmd.Flags().Set("output", "yaml") + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}} + + buf := bytes.NewBuffer([]byte{}) + cmd := NewCmdServiceAccount(f, buf, buf) + cmd.SetOutput(buf) + cmd.Flags().Set("output", "name") cmd.Flags().Set("local", "true") - for _, input := range inputs { - testapi.Default = testapi.Groups[input.apiGroup] - tf.Printer = printers.NewVersionedPrinter(&printers.YAMLPrinter{}, testapi.Default.Converter(), *testapi.Default.GroupVersion()) - saConfig := serviceAccountConfig{fileNameOptions: resource.FilenameOptions{ - Filenames: []string{input.yaml}}, - out: out, - local: true} - err := saConfig.Complete(f, cmd, []string{serviceAccount}) - assert.NoError(t, err) - err = saConfig.Run() - assert.NoError(t, err) - assert.Contains(t, out.String(), "serviceAccountName: "+serviceAccount, fmt.Sprintf("serviceaccount not updated for %s", input.yaml)) + mapper, typer := f.Object() + tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{codec}, Typer: typer, Mapper: mapper} + opts := serviceAccountConfig{fileNameOptions: resource.FilenameOptions{ + Filenames: []string{"../../../../test/fixtures/pkg/kubectl/cmd/set/multi-resource-yaml.yaml"}}, + out: buf, + local: true} + + err := opts.Complete(f, cmd, []string{serviceAccount}) + if err == nil { + err = opts.Run() + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + expectedOut := "replicationcontrollers/first-rc\nreplicationcontrollers/second-rc\n" + if buf.String() != expectedOut { + t.Errorf("expected out:\n%s\nbut got:\n%s", expectedOut, buf.String()) } } -func TestServiceAccountRemote(t *testing.T) { +func TestSetServiceAccountRemote(t *testing.T) { inputs := []struct { - object runtime.Object - apiPrefix, apiGroup string - args []string + object runtime.Object + apiPrefix, apiGroup, apiVersion string + testAPIGroup string + args []string }{ { - object: &extensions.ReplicaSet{ - TypeMeta: metav1.TypeMeta{Kind: "ReplicaSet", APIVersion: api.Registry.GroupOrDie(extensions.GroupName).GroupVersion.String()}, + object: &extensionsv1beta1.ReplicaSet{ ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, }, - apiPrefix: "/apis", apiGroup: extensions.GroupName, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "extensions", apiVersion: "v1beta1", args: []string{"replicaset", "nginx", serviceAccount}, }, { - object: &extensions.DaemonSet{ - TypeMeta: metav1.TypeMeta{Kind: "DaemonSet", APIVersion: api.Registry.GroupOrDie(extensions.GroupName).GroupVersion.String()}, + object: &appsv1beta2.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.ReplicaSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"replicaset", "nginx", serviceAccount}, + }, + { + object: &appsv1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.ReplicaSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"replicaset", "nginx", serviceAccount}, + }, + { + object: &extensionsv1beta1.DaemonSet{ ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, }, - apiPrefix: "/apis", apiGroup: extensions.GroupName, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "extensions", apiVersion: "v1beta1", args: []string{"daemonset", "nginx", serviceAccount}, }, { - object: &api.ReplicationController{ - TypeMeta: metav1.TypeMeta{Kind: "ReplicationController", APIVersion: api.Registry.GroupOrDie(api.GroupName).GroupVersion.String()}, + object: &appsv1beta2.DaemonSet{ ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, }, - apiPrefix: "/api", apiGroup: api.GroupName, - args: []string{"replicationcontroller", "nginx", serviceAccount}}, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"daemonset", "nginx", serviceAccount}, + }, { - object: &extensions.Deployment{ - TypeMeta: metav1.TypeMeta{Kind: "Deployment", APIVersion: api.Registry.GroupOrDie(extensions.GroupName).GroupVersion.String()}, + object: &appsv1.DaemonSet{ ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, }, - apiPrefix: "/apis", apiGroup: extensions.GroupName, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"daemonset", "nginx", serviceAccount}, + }, + { + object: &extensionsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "extensions", apiVersion: "v1beta1", args: []string{"deployment", "nginx", serviceAccount}, }, { - object: &batch.Job{ - TypeMeta: metav1.TypeMeta{Kind: "Job", APIVersion: api.Registry.GroupOrDie(batch.GroupName).GroupVersion.String()}, + object: &appsv1beta1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, }, - apiPrefix: "/apis", apiGroup: batch.GroupName, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta1", + args: []string{"deployment", "nginx", serviceAccount}, + }, + { + object: &appsv1beta2.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"deployment", "nginx", serviceAccount}, + }, + { + object: &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"deployment", "nginx", serviceAccount}, + }, + { + object: &appsv1beta1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + }, + testAPIGroup: "apps", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta1", + args: []string{"statefulset", "nginx", serviceAccount}, + }, + { + object: &appsv1beta2.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + }, + testAPIGroup: "apps", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"statefulset", "nginx", serviceAccount}, + }, + { + object: &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.StatefulSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "apps", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"statefulset", "nginx", serviceAccount}, + }, + { + object: &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + }, + testAPIGroup: "batch", + apiPrefix: "/apis", apiGroup: "batch", apiVersion: "v1", args: []string{"job", "nginx", serviceAccount}, }, { - object: &apps.StatefulSet{ - TypeMeta: metav1.TypeMeta{Kind: "StatefulSet", APIVersion: api.Registry.GroupOrDie(apps.GroupName).GroupVersion.String()}, + object: &v1.ReplicationController{ ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, }, - apiPrefix: "/apis", apiGroup: apps.GroupName, - args: []string{"statefulset", "nginx", serviceAccount}, + testAPIGroup: "", + apiPrefix: "/api", apiGroup: "", apiVersion: "v1", + args: []string{"replicationcontroller", "nginx", serviceAccount}, }, } for _, input := range inputs { - - groupVersion := api.Registry.GroupOrDie(input.apiGroup).GroupVersion - testapi.Default = testapi.Groups[input.apiGroup] - f, tf, codec, _ := cmdtesting.NewAPIFactory() + groupVersion := schema.GroupVersion{Group: input.apiGroup, Version: input.apiVersion} + testapi.Default = testapi.Groups[input.testAPIGroup] + f, tf, _, ns := cmdtesting.NewAPIFactory() + codec := scheme.Codecs.CodecForVersions(scheme.Codecs.LegacyCodec(groupVersion), scheme.Codecs.UniversalDecoder(groupVersion), groupVersion, groupVersion) tf.Printer = printers.NewVersionedPrinter(&printers.YAMLPrinter{}, testapi.Default.Converter(), *testapi.Default.GroupVersion()) tf.Namespace = "test" - tf.CategoryExpander = resource.LegacyCategoryExpander + tf.CategoryExpander = categories.LegacyCategoryExpander tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, - NegotiatedSerializer: testapi.Default.NegotiatedSerializer(), + GroupVersion: groupVersion, + NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { resourcePath := testapi.Default.ResourcePath(input.args[0]+"s", tf.Namespace, input.args[1]) switch p, m := req.URL.Path, req.Method; { @@ -177,14 +344,12 @@ func TestServiceAccountRemote(t *testing.T) { return nil, fmt.Errorf("unexpected request") } }), - VersionedAPIPath: path.Join(input.apiPrefix, groupVersion.String()), - GroupName: input.apiGroup, + VersionedAPIPath: path.Join(input.apiPrefix, testapi.Default.GroupVersion().String()), } out := new(bytes.Buffer) cmd := NewCmdServiceAccount(f, out, out) cmd.SetOutput(out) cmd.Flags().Set("output", "yaml") - saConfig := serviceAccountConfig{ out: out, local: false} @@ -206,7 +371,7 @@ func TestServiceAccountValidation(t *testing.T) { for _, input := range inputs { f, tf, _, _ := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: schema.GroupVersion{Version: "v1"}, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) return nil, nil diff --git a/pkg/kubectl/cmd/set/set_subject.go b/pkg/kubectl/cmd/set/set_subject.go index a9120654cce..c71a664b00e 100644 --- a/pkg/kubectl/cmd/set/set_subject.go +++ b/pkg/kubectl/cmd/set/set_subject.go @@ -28,7 +28,6 @@ import ( "k8s.io/apimachinery/pkg/types" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/rbac" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -101,7 +100,7 @@ func NewCmdSubject(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Co cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage) cmd.Flags().BoolVar(&options.All, "all", false, "Select all resources, including uninitialized ones, in the namespace of the specified resource types") cmd.Flags().StringVarP(&options.Selector, "selector", "l", "", "Selector (label query) to filter on, not including uninitialized ones, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") - cmd.Flags().BoolVar(&options.Local, "local", false, "If true, set resources will NOT contact api-server but run locally.") + cmd.Flags().BoolVar(&options.Local, "local", false, "If true, set subject will NOT contact api-server but run locally.") cmdutil.AddDryRunFlag(cmd) cmd.Flags().StringArrayVar(&options.Users, "user", []string{}, "Usernames to bind to the role") cmd.Flags().StringArrayVar(&options.Groups, "group", []string{}, "Groups to bind to the role") @@ -127,26 +126,26 @@ func (o *SubjectOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [] includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false) builder := f.NewBuilder(). + Internal(). + LocalParam(o.Local). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &o.FilenameOptions). IncludeUninitialized(includeUninitialized). Flatten() - if !o.Local { - builder = builder. - SelectorParam(o.Selector). - ResourceTypeOrNameArgs(o.All, args...). - Latest() - } else { + if o.Local { // if a --local flag was provided, and a resource was specified in the form // /, fail immediately as --local cannot query the api server // for the specified resource. if len(args) > 0 { return resource.LocalResourceError } - - builder = builder.Local(f.ClientForMapping) + } else { + builder = builder. + LabelSelectorParam(o.Selector). + ResourceTypeOrNameArgs(o.All, args...). + Latest() } o.Infos, err = builder.Do().Infos() @@ -219,9 +218,8 @@ func (o *SubjectOptions) Run(f cmdutil.Factory, fn updateSubjects) error { transformed, err := updateSubjectForObject(info.Object, subjects, fn) if transformed && err == nil { - // TODO: switch UpdatePodSpecForObject to work on v1.PodSpec, use info.VersionedObject, and avoid conversion completely - versionedEncoder := api.Codecs.EncoderForVersion(o.Encoder, info.Mapping.GroupVersionKind.GroupVersion()) - return runtime.Encode(versionedEncoder, info.Object) + // TODO: switch UpdatePodSpecForObject to work on v1.PodSpec + return runtime.Encode(o.Encoder, info.AsVersioned()) } return nil, err }) @@ -241,7 +239,10 @@ func (o *SubjectOptions) Run(f cmdutil.Factory, fn updateSubjects) error { } if o.Local || o.DryRun { - return o.PrintObject(o.Mapper, info.Object, o.Out) + if err := o.PrintObject(o.Mapper, info.Object, o.Out); err != nil { + return err + } + continue } obj, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch) @@ -253,9 +254,9 @@ func (o *SubjectOptions) Run(f cmdutil.Factory, fn updateSubjects) error { shortOutput := o.Output == "name" if len(o.Output) > 0 && !shortOutput { - return o.PrintObject(o.Mapper, info.Object, o.Out) + return o.PrintObject(o.Mapper, info.AsVersioned(), o.Out) } - cmdutil.PrintSuccess(o.Mapper, shortOutput, o.Out, info.Mapping.Resource, info.Name, false, "subjects updated") + f.PrintSuccess(o.Mapper, shortOutput, o.Out, info.Mapping.Resource, info.Name, false, "subjects updated") } return utilerrors.NewAggregate(allErrs) } diff --git a/pkg/kubectl/cmd/taint.go b/pkg/kubectl/cmd/taint.go index 04578bbee95..239aba71634 100644 --- a/pkg/kubectl/cmd/taint.go +++ b/pkg/kubectl/cmd/taint.go @@ -58,6 +58,7 @@ var ( * A taint consists of a key, value, and effect. As an argument here, it is expressed as key=value:effect. * The key must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[1]d characters. + * Optionally, the key can begin with a DNS subdomain prefix and a single '/', like example.com/my-app * The value must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[2]d characters. * The effect must be NoSchedule, PreferNoSchedule or NoExecute. * Currently taint can only apply to node.`)) @@ -149,10 +150,11 @@ func (o *TaintOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.Com return cmdutil.UsageErrorf(cmd, err.Error()) } o.builder = f.NewBuilder(). + Internal(). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace() if o.selector != "" { - o.builder = o.builder.SelectorParam(o.selector).ResourceTypes("node") + o.builder = o.builder.LabelSelectorParam(o.selector).ResourceTypes("node") } if o.all { o.builder = o.builder.SelectAllParam(o.all).ResourceTypes("node").Flatten().Latest() @@ -160,7 +162,7 @@ func (o *TaintOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.Com if !o.all && o.selector == "" && len(o.resources) >= 2 { o.builder = o.builder.ResourceNames("node", o.resources[1:]...) } - o.builder = o.builder.SelectorParam(o.selector). + o.builder = o.builder.LabelSelectorParam(o.selector). Flatten(). Latest() o.f = f @@ -277,7 +279,7 @@ func (o TaintOptions) RunTaint() error { return o.f.PrintObject(o.cmd, false, mapper, outputObj, o.out) } - cmdutil.PrintSuccess(mapper, false, o.out, info.Mapping.Resource, info.Name, false, operation) + o.f.PrintSuccess(mapper, false, o.out, info.Mapping.Resource, info.Name, false, operation) return nil }) } diff --git a/pkg/kubectl/cmd/taint_test.go b/pkg/kubectl/cmd/taint_test.go index 0bdf71409ac..8e901364943 100644 --- a/pkg/kubectl/cmd/taint_test.go +++ b/pkg/kubectl/cmd/taint_test.go @@ -29,7 +29,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) @@ -242,7 +241,6 @@ func TestTaint(t *testing.T) { f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { m := &MyReq{req} diff --git a/pkg/kubectl/cmd/testdata/edit/BUILD b/pkg/kubectl/cmd/testdata/edit/BUILD index ce8c860c43c..18cfd71b784 100644 --- a/pkg/kubectl/cmd/testdata/edit/BUILD +++ b/pkg/kubectl/cmd/testdata/edit/BUILD @@ -6,8 +6,8 @@ load( go_binary( name = "edit", + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/testdata/edit", - library = ":go_default_library", visibility = ["//visibility:public"], ) diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/0.request b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/0.request new file mode 100755 index 00000000000..e69de29bb2d diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/0.response b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/0.response new file mode 100755 index 00000000000..75d223aadcc --- /dev/null +++ b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/0.response @@ -0,0 +1,21 @@ +{ + "kind": "ConfigMap", + "apiVersion": "v1", + "metadata": { + "name": "cm1", + "namespace": "myproject", + "selfLink": "/api/v1/namespaces/myproject/configmaps/cm1", + "uid": "cc08a131-3d6f-11e7-8ef0-c85b76034b7b", + "resourceVersion": "3518", + "creationTimestamp": "2017-05-20T15:20:03Z", + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"data\":{\"baz\":\"qux\",\"foo\":\"changed-value\",\"new-data\":\"new-value\",\"new-data2\":\"new-value\"},\"kind\":\"ConfigMap\",\"metadata\":{\"annotations\":{},\"name\":\"cm1\",\"namespace\":\"myproject\"}}\n" + } + }, + "data": { + "baz": "qux", + "foo": "changed-value", + "new-data": "new-value", + "new-data2": "new-value" + } +} \ No newline at end of file diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/1.request b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/1.request new file mode 100755 index 00000000000..e69de29bb2d diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/1.response b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/1.response new file mode 100755 index 00000000000..9703a466ab8 --- /dev/null +++ b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/1.response @@ -0,0 +1,35 @@ +{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "svc1", + "namespace": "myproject", + "selfLink": "/api/v1/namespaces/myproject/services/svc1", + "uid": "d8b96f0b-3d6f-11e7-8ef0-c85b76034b7b", + "resourceVersion": "3525", + "creationTimestamp": "2017-05-20T15:20:24Z", + "labels": { + "app": "svc1", + "new-label": "foo" + }, + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"svc1\",\"new-label\":\"foo\"},\"name\":\"svc1\",\"namespace\":\"myproject\"},\"spec\":{\"ports\":[{\"name\":\"80\",\"port\":81,\"protocol\":\"TCP\",\"targetPort\":81}],\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n" + } + }, + "spec": { + "ports": [ + { + "name": "80", + "protocol": "TCP", + "port": 81, + "targetPort": 81 + } + ], + "clusterIP": "172.30.32.183", + "type": "ClusterIP", + "sessionAffinity": "None" + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/2.edited b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/2.edited new file mode 100755 index 00000000000..1769ec8faed --- /dev/null +++ b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/2.edited @@ -0,0 +1,38 @@ +# Please edit the 'last-applied-configuration' annotations below. +# Lines beginning with a '#' will be ignored, and an empty file will abort the edit. +# +apiVersion: v1 +items: +- apiVersion: v1 + data: + baz: qux + foo: changed-value + new-data: new-value + new-data2: new-value + new-data3: newivalue + kind: ConfigMap + metadata: + annotations: {} + name: cm1 + namespace: myproject +- kind: Service + metadata: + annotations: {} + labels: + app: svc1 + new-label: foo + new-label2: foo2 + name: svc1 + namespace: myproject + spec: + ports: + - name: "80" + port: 82 + protocol: TCP + targetPort: 81 + sessionAffinity: None + type: ClusterIP + status: + loadBalancer: {} +kind: List +metadata: {} diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/2.original b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/2.original new file mode 100755 index 00000000000..82770327ca1 --- /dev/null +++ b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/2.original @@ -0,0 +1,36 @@ +# Please edit the 'last-applied-configuration' annotations below. +# Lines beginning with a '#' will be ignored, and an empty file will abort the edit. +# +apiVersion: v1 +items: +- apiVersion: v1 + data: + baz: qux + foo: changed-value + new-data: new-value + new-data2: new-value + kind: ConfigMap + metadata: + annotations: {} + name: cm1 + namespace: myproject +- kind: Service + metadata: + annotations: {} + labels: + app: svc1 + new-label: foo + name: svc1 + namespace: myproject + spec: + ports: + - name: "80" + port: 81 + protocol: TCP + targetPort: 81 + sessionAffinity: None + type: ClusterIP + status: + loadBalancer: {} +kind: List +metadata: {} diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/3.edited b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/3.edited new file mode 100644 index 00000000000..e5a7d241ecf --- /dev/null +++ b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/3.edited @@ -0,0 +1,41 @@ +# Please edit the 'last-applied-configuration' annotations below. +# Lines beginning with a '#' will be ignored, and an empty file will abort the edit. +# +# The edited file had a syntax error: error converting YAML to JSON: yaml: line 12: could not find expected ':' +# +apiVersion: v1 +items: +- apiVersion: v1 + data: + baz: qux + foo: changed-value + new-data: new-value + new-data2: new-value + new-data3: newivalue + kind: ConfigMap + metadata: + annotations: {} + name: cm1 + namespace: myproject +- kind: Service + apiVersion: v1 + metadata: + annotations: {} + labels: + app: svc1 + new-label: foo + new-label2: foo2 + name: svc1 + namespace: myproject + spec: + ports: + - name: "80" + port: 82 + protocol: TCP + targetPort: 81 + sessionAffinity: None + type: ClusterIP + status: + loadBalancer: {} +kind: List +metadata: {} diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/3.original b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/3.original new file mode 100644 index 00000000000..ede23048bd7 --- /dev/null +++ b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/3.original @@ -0,0 +1,40 @@ +# Please edit the 'last-applied-configuration' annotations below. +# Lines beginning with a '#' will be ignored, and an empty file will abort the edit. +# +# The edited file had a syntax error: unable to get type info from the object "*unstructured.Unstructured": Object 'apiVersion' is missing in 'object has no apiVersion field' +# +apiVersion: v1 +items: +- apiVersion: v1 + data: + baz: qux + foo: changed-value + new-data: new-value + new-data2: new-value + new-data3: newivalue + kind: ConfigMap + metadata: + annotations: {} + name: cm1 + namespace: myproject +- kind: Service + metadata: + annotations: {} + labels: + app: svc1 + new-label: foo + new-label2: foo2 + name: svc1 + namespace: myproject + spec: + ports: + - name: "80" + port: 82 + protocol: TCP + targetPort: 81 + sessionAffinity: None + type: ClusterIP + status: + loadBalancer: {} +kind: List +metadata: {} diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/4.request b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/4.request new file mode 100755 index 00000000000..162c97e69ec --- /dev/null +++ b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/4.request @@ -0,0 +1,7 @@ +{ + "metadata": { + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"data\":{\"baz\":\"qux\",\"foo\":\"changed-value\",\"new-data\":\"new-value\",\"new-data2\":\"new-value\",\"new-data3\":\"newivalue\"},\"kind\":\"ConfigMap\",\"metadata\":{\"annotations\":{},\"name\":\"cm1\",\"namespace\":\"myproject\"}}\n" + } + } +} \ No newline at end of file diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/4.response b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/4.response new file mode 100755 index 00000000000..e2115785efb --- /dev/null +++ b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/4.response @@ -0,0 +1,21 @@ +{ + "kind": "ConfigMap", + "apiVersion": "v1", + "metadata": { + "name": "cm1", + "namespace": "myproject", + "selfLink": "/api/v1/namespaces/myproject/configmaps/cm1", + "uid": "cc08a131-3d6f-11e7-8ef0-c85b76034b7b", + "resourceVersion": "3554", + "creationTimestamp": "2017-05-20T15:20:03Z", + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"data\":{\"baz\":\"qux\",\"foo\":\"changed-value\",\"new-data\":\"new-value\",\"new-data2\":\"new-value\",\"new-data3\":\"newivalue\"},\"kind\":\"ConfigMap\",\"metadata\":{\"annotations\":{},\"name\":\"cm1\",\"namespace\":\"myproject\"}}\n" + } + }, + "data": { + "baz": "qux", + "foo": "changed-value", + "new-data": "new-value", + "new-data2": "new-value" + } +} \ No newline at end of file diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/5.request b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/5.request new file mode 100755 index 00000000000..a4768bcf4dc --- /dev/null +++ b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/5.request @@ -0,0 +1,7 @@ +{ + "metadata": { + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"svc1\",\"new-label\":\"foo\",\"new-label2\":\"foo2\"},\"name\":\"svc1\",\"namespace\":\"myproject\"},\"spec\":{\"ports\":[{\"name\":\"80\",\"port\":82,\"protocol\":\"TCP\",\"targetPort\":81}],\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n" + } + } +} \ No newline at end of file diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/5.response b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/5.response new file mode 100755 index 00000000000..c538b4333a4 --- /dev/null +++ b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/5.response @@ -0,0 +1,35 @@ +{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "svc1", + "namespace": "myproject", + "selfLink": "/api/v1/namespaces/myproject/services/svc1", + "uid": "d8b96f0b-3d6f-11e7-8ef0-c85b76034b7b", + "resourceVersion": "3555", + "creationTimestamp": "2017-05-20T15:20:24Z", + "labels": { + "app": "svc1", + "new-label": "foo" + }, + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"svc1\",\"new-label\":\"foo\",\"new-label2\":\"foo2\"},\"name\":\"svc1\",\"namespace\":\"myproject\"},\"spec\":{\"ports\":[{\"name\":\"80\",\"port\":82,\"protocol\":\"TCP\",\"targetPort\":81}],\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n" + } + }, + "spec": { + "ports": [ + { + "name": "80", + "protocol": "TCP", + "port": 81, + "targetPort": 81 + } + ], + "clusterIP": "172.30.32.183", + "type": "ClusterIP", + "sessionAffinity": "None" + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/test.yaml b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/test.yaml new file mode 100755 index 00000000000..56b93abd66b --- /dev/null +++ b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list-fail/test.yaml @@ -0,0 +1,43 @@ +description: if the user omits an API version, edit will fail +mode: edit-last-applied +args: +- configmaps/cm1 +- service/svc1 +namespace: "myproject" +expectedStdout: +- configmap "cm1" edited +- service "svc1" edited +expectedExitCode: 0 +steps: +- type: request + expectedMethod: GET + expectedPath: /api/v1/namespaces/myproject/configmaps/cm1 + expectedInput: 0.request + resultingStatusCode: 200 + resultingOutput: 0.response +- type: request + expectedMethod: GET + expectedPath: /api/v1/namespaces/myproject/services/svc1 + expectedInput: 1.request + resultingStatusCode: 200 + resultingOutput: 1.response +- type: edit + expectedInput: 2.original + resultingOutput: 2.edited +- type: edit + expectedInput: 3.original + resultingOutput: 3.edited +- type: request + expectedMethod: PATCH + expectedPath: /api/v1/namespaces/myproject/configmaps/cm1 + expectedContentType: application/merge-patch+json + expectedInput: 4.request + resultingStatusCode: 200 + resultingOutput: 4.response +- type: request + expectedMethod: PATCH + expectedPath: /api/v1/namespaces/myproject/services/svc1 + expectedContentType: application/merge-patch+json + expectedInput: 5.request + resultingStatusCode: 200 + resultingOutput: 5.response diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/2.edited b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/2.edited index 1769ec8faed..490d5fbb04d 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/2.edited +++ b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/2.edited @@ -16,6 +16,7 @@ items: name: cm1 namespace: myproject - kind: Service + apiVersion: v1 metadata: annotations: {} labels: diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/4.request b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/4.request index 5c9c261898c..5c8be23d1b6 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/4.request +++ b/pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/4.request @@ -1,7 +1,7 @@ { - "metadata": { - "annotations": { - "kubectl.kubernetes.io/last-applied-configuration": "{\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"svc1\",\"new-label\":\"foo\",\"new-label2\":\"foo2\"},\"name\":\"svc1\",\"namespace\":\"myproject\"},\"spec\":{\"ports\":[{\"name\":\"80\",\"port\":82,\"protocol\":\"TCP\",\"targetPort\":81}],\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n" - } - } + "metadata": { + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"svc1\",\"new-label\":\"foo\",\"new-label2\":\"foo2\"},\"name\":\"svc1\",\"namespace\":\"myproject\"},\"spec\":{\"ports\":[{\"name\":\"80\",\"port\":82,\"protocol\":\"TCP\",\"targetPort\":81}],\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n" + } + } } \ No newline at end of file diff --git a/pkg/kubectl/cmd/testing/BUILD b/pkg/kubectl/cmd/testing/BUILD index 1bfd4b3f818..90fe0a0872b 100644 --- a/pkg/kubectl/cmd/testing/BUILD +++ b/pkg/kubectl/cmd/testing/BUILD @@ -14,22 +14,25 @@ go_library( "//build/visible_to:pkg_kubectl_cmd_testing_CONSUMERS", ], deps = [ - "//federation/client/clientset_generated/federation_clientset:go_default_library", - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/testapi:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/kubectl:go_default_library", + "//pkg/kubectl/categories:go_default_library", "//pkg/kubectl/cmd/util:go_default_library", "//pkg/kubectl/cmd/util/openapi:go_default_library", + "//pkg/kubectl/cmd/util/openapi/testing:go_default_library", "//pkg/kubectl/plugins:go_default_library", "//pkg/kubectl/resource:go_default_library", "//pkg/kubectl/validation:go_default_library", "//pkg/printers:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", diff --git a/pkg/kubectl/cmd/testing/fake.go b/pkg/kubectl/cmd/testing/fake.go index d5c0fd4d077..d57706d4c1b 100644 --- a/pkg/kubectl/cmd/testing/fake.go +++ b/pkg/kubectl/cmd/testing/fake.go @@ -26,8 +26,10 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" @@ -35,13 +37,15 @@ import ( "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" - fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/testapi" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/categories" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" + openapitesting "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing" "k8s.io/kubernetes/pkg/kubectl/plugins" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/validation" @@ -173,7 +177,7 @@ func versionErrIfFalse(b bool) error { return versionErr } -var ValidVersion = api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version +var ValidVersion = legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.Version var InternalGV = schema.GroupVersion{Group: "apitest", Version: runtime.APIVersionInternal} var UnlikelyGV = schema.GroupVersion{Group: "apitest", Version: "unlikelyversion"} var ValidVersionGV = schema.GroupVersion{Group: "apitest", Version: ValidVersion} @@ -239,7 +243,8 @@ type TestFactory struct { Err error Command string TmpDir string - CategoryExpander resource.CategoryExpander + CategoryExpander categories.CategoryExpander + SkipDiscovery bool ClientForMappingFunc func(mapping *meta.RESTMapping) (resource.RESTClient, error) UnstructuredClientForMappingFunc func(mapping *meta.RESTMapping) (resource.RESTClient, error) @@ -278,21 +283,20 @@ func (f *FakeFactory) FlagSet() *pflag.FlagSet { } func (f *FakeFactory) Object() (meta.RESTMapper, runtime.ObjectTyper) { - return api.Registry.RESTMapper(), f.tf.Typer -} - -func (f *FakeFactory) UnstructuredObject() (meta.RESTMapper, runtime.ObjectTyper, error) { + if f.tf.SkipDiscovery { + return legacyscheme.Registry.RESTMapper(), f.tf.Typer + } groupResources := testDynamicResources() - mapper := discovery.NewRESTMapper(groupResources, meta.InterfacesForUnstructured) - typer := discovery.NewUnstructuredObjectTyper(groupResources) + mapper := discovery.NewRESTMapper(groupResources, meta.InterfacesForUnstructuredConversion(legacyscheme.Registry.InterfacesFor)) + typer := discovery.NewUnstructuredObjectTyper(groupResources, legacyscheme.Scheme) fakeDs := &fakeCachedDiscoveryClient{} - expander, err := cmdutil.NewShortcutExpander(mapper, fakeDs) - return expander, typer, err + expander := cmdutil.NewShortcutExpander(mapper, fakeDs) + return expander, typer } -func (f *FakeFactory) CategoryExpander() resource.CategoryExpander { - return resource.LegacyCategoryExpander +func (f *FakeFactory) CategoryExpander() categories.CategoryExpander { + return categories.LegacyCategoryExpander } func (f *FakeFactory) Decoder(bool) runtime.Decoder { @@ -330,12 +334,6 @@ func (f *FakeFactory) ClientForMapping(mapping *meta.RESTMapping) (resource.REST return f.tf.Client, f.tf.Err } -func (f *FakeFactory) FederationClientSetForVersion(version *schema.GroupVersion) (fedclientset.Interface, error) { - return nil, nil -} -func (f *FakeFactory) FederationClientForVersion(version *schema.GroupVersion) (*restclient.RESTClient, error) { - return nil, nil -} func (f *FakeFactory) ClientSetForVersion(requiredVersion *schema.GroupVersion) (internalclientset.Interface, error) { return nil, nil } @@ -354,10 +352,47 @@ func (f *FakeFactory) Describer(*meta.RESTMapping) (printers.Describer, error) { return f.tf.Describer, f.tf.Err } -func (f *FakeFactory) PrinterForCommand(cmd *cobra.Command, isLocal bool, outputOpts *printers.OutputOptions, options printers.PrintOptions) (printers.ResourcePrinter, error) { +func (f *FakeFactory) PrinterForOptions(options *printers.PrintOptions) (printers.ResourcePrinter, error) { return f.tf.Printer, f.tf.Err } +func (f *FakeFactory) PrintResourceInfoForCommand(cmd *cobra.Command, info *resource.Info, out io.Writer) error { + printer, err := f.PrinterForOptions(&printers.PrintOptions{}) + if err != nil { + return err + } + if !printer.IsGeneric() { + printer, err = f.PrinterForMapping(&printers.PrintOptions{}, nil) + if err != nil { + return err + } + } + return printer.PrintObj(info.Object, out) +} + +func (f *FakeFactory) PrintSuccess(mapper meta.RESTMapper, shortOutput bool, out io.Writer, resource, name string, dryRun bool, operation string) { + resource, _ = mapper.ResourceSingularizer(resource) + dryRunMsg := "" + if dryRun { + dryRunMsg = " (dry run)" + } + if shortOutput { + // -o name: prints resource/name + if len(resource) > 0 { + fmt.Fprintf(out, "%s/%s\n", resource, name) + } else { + fmt.Fprintf(out, "%s\n", name) + } + } else { + // understandable output by default + if len(resource) > 0 { + fmt.Fprintf(out, "%s \"%s\" %s%s\n", resource, name, operation, dryRunMsg) + } else { + fmt.Fprintf(out, "\"%s\" %s%s\n", name, operation, dryRunMsg) + } + } +} + func (f *FakeFactory) Printer(mapping *meta.RESTMapping, options printers.PrintOptions) (printers.ResourcePrinter, error) { return f.tf.Printer, f.tf.Err } @@ -453,7 +488,7 @@ func (f *FakeFactory) ApproximatePodTemplateForObject(obj runtime.Object) (*api. return f.ApproximatePodTemplateForObject(obj) } -func (f *FakeFactory) UpdatePodSpecForObject(obj runtime.Object, fn func(*api.PodSpec) error) (bool, error) { +func (f *FakeFactory) UpdatePodSpecForObject(obj runtime.Object, fn func(*v1.PodSpec) error) (bool, error) { return false, nil } @@ -478,13 +513,28 @@ func (f *FakeFactory) PrintObject(cmd *cobra.Command, isLocal bool, mapper meta. return nil } -func (f *FakeFactory) PrinterForMapping(cmd *cobra.Command, isLocal bool, outputOpts *printers.OutputOptions, mapping *meta.RESTMapping, withNamespace bool) (printers.ResourcePrinter, error) { +func (f *FakeFactory) PrinterForMapping(printOpts *printers.PrintOptions, mapping *meta.RESTMapping) (printers.ResourcePrinter, error) { return f.tf.Printer, f.tf.Err } func (f *FakeFactory) NewBuilder() *resource.Builder { mapper, typer := f.Object() - return resource.NewBuilder(mapper, f.CategoryExpander(), typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)) + + return resource.NewBuilder( + &resource.Mapper{ + RESTMapper: mapper, + ObjectTyper: typer, + ClientMapper: resource.ClientMapperFunc(f.ClientForMapping), + Decoder: f.Decoder(true), + }, + &resource.Mapper{ + RESTMapper: mapper, + ObjectTyper: typer, + ClientMapper: resource.ClientMapperFunc(f.UnstructuredClientForMapping), + Decoder: unstructured.UnstructuredJSONScheme, + }, + f.CategoryExpander(), + ) } func (f *FakeFactory) DefaultResourceFilterOptions(cmd *cobra.Command, withNamespace bool) *printers.PrintOptions { @@ -526,11 +576,11 @@ func (f *fakeMixedFactory) Object() (meta.RESTMapper, runtime.ObjectTyper) { {Group: meta.AnyGroup, Version: "v1", Kind: meta.AnyKind}, }, } - return priorityRESTMapper, runtime.MultiObjectTyper{f.tf.Typer, api.Scheme} + return priorityRESTMapper, runtime.MultiObjectTyper{f.tf.Typer, legacyscheme.Scheme} } func (f *fakeMixedFactory) ClientForMapping(m *meta.RESTMapping) (resource.RESTClient, error) { - if m.ObjectConvertor == api.Scheme { + if m.ObjectConvertor == legacyscheme.Scheme { return f.apiClient, f.tf.Err } if f.tf.ClientForMappingFunc != nil { @@ -554,16 +604,40 @@ type fakeAPIFactory struct { } func (f *fakeAPIFactory) Object() (meta.RESTMapper, runtime.ObjectTyper) { - return testapi.Default.RESTMapper(), api.Scheme -} - -func (f *fakeAPIFactory) UnstructuredObject() (meta.RESTMapper, runtime.ObjectTyper, error) { + if f.tf.SkipDiscovery { + return testapi.Default.RESTMapper(), legacyscheme.Scheme + } groupResources := testDynamicResources() - mapper := discovery.NewRESTMapper(groupResources, meta.InterfacesForUnstructured) - typer := discovery.NewUnstructuredObjectTyper(groupResources) + mapper := discovery.NewRESTMapper( + groupResources, + meta.InterfacesForUnstructuredConversion(func(version schema.GroupVersion) (*meta.VersionInterfaces, error) { + switch version { + // provide typed objects for these two versions + case ValidVersionGV, UnlikelyGV: + return &meta.VersionInterfaces{ + ObjectConvertor: f.tf.Typer.(*runtime.Scheme), + MetadataAccessor: meta.NewAccessor(), + }, nil + // otherwise fall back to the legacy scheme + default: + return legacyscheme.Registry.InterfacesFor(version) + } + }), + ) + // for backwards compatibility with existing tests, allow rest mappings from the scheme to show up + // TODO: make this opt-in? + mapper = meta.FirstHitRESTMapper{ + MultiRESTMapper: meta.MultiRESTMapper{ + mapper, + legacyscheme.Registry.RESTMapper(), + }, + } + + // TODO: should probably be the external scheme + typer := discovery.NewUnstructuredObjectTyper(groupResources, legacyscheme.Scheme) fakeDs := &fakeCachedDiscoveryClient{} - expander, err := cmdutil.NewShortcutExpander(mapper, fakeDs) - return expander, typer, err + expander := cmdutil.NewShortcutExpander(mapper, fakeDs) + return expander, typer } func (f *fakeAPIFactory) Decoder(bool) runtime.Decoder { @@ -641,7 +715,7 @@ func (f *fakeAPIFactory) DiscoveryClient() (discovery.CachedDiscoveryInterface, return cmdutil.NewCachedDiscoveryClient(discoveryClient, cacheDir, time.Duration(10*time.Minute)), nil } -func (f *fakeAPIFactory) CategoryExpander() resource.CategoryExpander { +func (f *fakeAPIFactory) CategoryExpander() categories.CategoryExpander { if f.tf.CategoryExpander != nil { return f.tf.CategoryExpander } @@ -670,10 +744,47 @@ func (f *fakeAPIFactory) UnstructuredClientForMapping(m *meta.RESTMapping) (reso return f.tf.UnstructuredClient, f.tf.Err } -func (f *fakeAPIFactory) PrinterForCommand(cmd *cobra.Command, isLocal bool, outputOpts *printers.OutputOptions, options printers.PrintOptions) (printers.ResourcePrinter, error) { +func (f *fakeAPIFactory) PrinterForOptions(options *printers.PrintOptions) (printers.ResourcePrinter, error) { return f.tf.Printer, f.tf.Err } +func (f *fakeAPIFactory) PrintResourceInfoForCommand(cmd *cobra.Command, info *resource.Info, out io.Writer) error { + printer, err := f.PrinterForOptions(&printers.PrintOptions{}) + if err != nil { + return err + } + if !printer.IsGeneric() { + printer, err = f.PrinterForMapping(&printers.PrintOptions{}, nil) + if err != nil { + return err + } + } + return printer.PrintObj(info.Object, out) +} + +func (f *fakeAPIFactory) PrintSuccess(mapper meta.RESTMapper, shortOutput bool, out io.Writer, resource, name string, dryRun bool, operation string) { + resource, _ = mapper.ResourceSingularizer(resource) + dryRunMsg := "" + if dryRun { + dryRunMsg = " (dry run)" + } + if shortOutput { + // -o name: prints resource/name + if len(resource) > 0 { + fmt.Fprintf(out, "%s/%s\n", resource, name) + } else { + fmt.Fprintf(out, "%s\n", name) + } + } else { + // understandable output by default + if len(resource) > 0 { + fmt.Fprintf(out, "%s \"%s\" %s%s\n", resource, name, operation, dryRunMsg) + } else { + fmt.Fprintf(out, "\"%s\" %s%s\n", name, operation, dryRunMsg) + } + } +} + func (f *fakeAPIFactory) Describer(*meta.RESTMapping) (printers.Describer, error) { return f.tf.Describer, f.tf.Err } @@ -696,11 +807,7 @@ func (f *fakeAPIFactory) LogsForObject(object, options runtime.Object, timeout t } return c.Core().Pods(f.tf.Namespace).GetLogs(t.Name, opts), nil default: - fqKinds, _, err := api.Scheme.ObjectKinds(object) - if err != nil { - return nil, err - } - return nil, fmt.Errorf("cannot get the logs from %v", fqKinds[0]) + return nil, fmt.Errorf("cannot get the logs from %T", object) } } @@ -709,11 +816,7 @@ func (f *fakeAPIFactory) AttachablePodForObject(object runtime.Object, timeout t case *api.Pod: return t, nil default: - gvks, _, err := api.Scheme.ObjectKinds(object) - if err != nil { - return nil, err - } - return nil, fmt.Errorf("cannot attach to %v: not implemented", gvks[0]) + return nil, fmt.Errorf("cannot attach to %T: not implemented", object) } } @@ -738,7 +841,7 @@ func (f *fakeAPIFactory) Generators(cmdName string) map[string]kubectl.Generator } func (f *fakeAPIFactory) PrintObject(cmd *cobra.Command, isLocal bool, mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error { - gvks, _, err := api.Scheme.ObjectKinds(obj) + gvks, _, err := legacyscheme.Scheme.ObjectKinds(obj) if err != nil { return err } @@ -748,20 +851,35 @@ func (f *fakeAPIFactory) PrintObject(cmd *cobra.Command, isLocal bool, mapper me return err } - printer, err := f.PrinterForMapping(cmd, isLocal, nil, mapping, false) + printer, err := f.PrinterForMapping(&printers.PrintOptions{}, mapping) if err != nil { return err } return printer.PrintObj(obj, out) } -func (f *fakeAPIFactory) PrinterForMapping(cmd *cobra.Command, isLocal bool, outputOpts *printers.OutputOptions, mapping *meta.RESTMapping, withNamespace bool) (printers.ResourcePrinter, error) { +func (f *fakeAPIFactory) PrinterForMapping(outputOpts *printers.PrintOptions, mapping *meta.RESTMapping) (printers.ResourcePrinter, error) { return f.tf.Printer, f.tf.Err } func (f *fakeAPIFactory) NewBuilder() *resource.Builder { mapper, typer := f.Object() - return resource.NewBuilder(mapper, f.CategoryExpander(), typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)) + + return resource.NewBuilder( + &resource.Mapper{ + RESTMapper: mapper, + ObjectTyper: typer, + ClientMapper: resource.ClientMapperFunc(f.ClientForMapping), + Decoder: f.Decoder(true), + }, + &resource.Mapper{ + RESTMapper: mapper, + ObjectTyper: typer, + ClientMapper: resource.ClientMapperFunc(f.UnstructuredClientForMapping), + Decoder: unstructured.UnstructuredJSONScheme, + }, + f.CategoryExpander(), + ) } func (f *fakeAPIFactory) SuggestedPodTemplateResources() []schema.GroupResource { @@ -772,7 +890,7 @@ func (f *fakeAPIFactory) OpenAPISchema() (openapi.Resources, error) { if f.tf.OpenAPISchemaFunc != nil { return f.tf.OpenAPISchemaFunc() } - return nil, nil + return openapitesting.EmptyResources{}, nil } func NewAPIFactory() (cmdutil.Factory, *TestFactory, runtime.Codec, runtime.NegotiatedSerializer) { @@ -786,6 +904,17 @@ func NewAPIFactory() (cmdutil.Factory, *TestFactory, runtime.Codec, runtime.Nego }, t, testapi.Default.Codec(), testapi.Default.NegotiatedSerializer() } +func (f *TestFactory) WithCustomScheme() *TestFactory { + scheme, _, _ := newExternalScheme() + f.Typer = scheme + return f +} + +func (f *TestFactory) WithLegacyScheme() *TestFactory { + f.Typer = legacyscheme.Scheme + return f +} + func testDynamicResources() []*discovery.APIGroupResources { return []*discovery.APIGroupResources{ { @@ -804,8 +933,9 @@ func testDynamicResources() []*discovery.APIGroupResources { {Name: "nodes", Namespaced: false, Kind: "Node"}, {Name: "secrets", Namespaced: true, Kind: "Secret"}, {Name: "configmaps", Namespaced: true, Kind: "ConfigMap"}, - {Name: "type", Namespaced: false, Kind: "Type"}, {Name: "namespacedtype", Namespaced: true, Kind: "NamespacedType"}, + {Name: "namespaces", Namespaced: false, Kind: "Namespace"}, + {Name: "resourcequotas", Namespaced: true, Kind: "ResourceQuota"}, }, }, }, @@ -820,6 +950,49 @@ func testDynamicResources() []*discovery.APIGroupResources { VersionedResources: map[string][]metav1.APIResource{ "v1beta1": { {Name: "deployments", Namespaced: true, Kind: "Deployment"}, + {Name: "replicasets", Namespaced: true, Kind: "ReplicaSet"}, + }, + }, + }, + { + Group: metav1.APIGroup{ + Name: "apps", + Versions: []metav1.GroupVersionForDiscovery{ + {Version: "v1beta1"}, + {Version: "v1beta2"}, + {Version: "v1"}, + }, + PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v1"}, + }, + VersionedResources: map[string][]metav1.APIResource{ + "v1beta1": { + {Name: "deployments", Namespaced: true, Kind: "Deployment"}, + {Name: "replicasets", Namespaced: true, Kind: "ReplicaSet"}, + }, + "v1beta2": { + {Name: "deployments", Namespaced: true, Kind: "Deployment"}, + }, + "v1": { + {Name: "deployments", Namespaced: true, Kind: "Deployment"}, + {Name: "replicasets", Namespaced: true, Kind: "ReplicaSet"}, + }, + }, + }, + { + Group: metav1.APIGroup{ + Name: "autoscaling", + Versions: []metav1.GroupVersionForDiscovery{ + {Version: "v1"}, + {Version: "v2beta1"}, + }, + PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v2beta1"}, + }, + VersionedResources: map[string][]metav1.APIResource{ + "v1": { + {Name: "horizontalpodautoscalers", Namespaced: true, Kind: "HorizontalPodAutoscaler"}, + }, + "v2beta1": { + {Name: "horizontalpodautoscalers", Namespaced: true, Kind: "HorizontalPodAutoscaler"}, }, }, }, @@ -842,6 +1015,24 @@ func testDynamicResources() []*discovery.APIGroupResources { }, }, }, + { + Group: metav1.APIGroup{ + Name: "rbac.authorization.k8s.io", + Versions: []metav1.GroupVersionForDiscovery{ + {Version: "v1beta1"}, + {Version: "v1"}, + }, + PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v1"}, + }, + VersionedResources: map[string][]metav1.APIResource{ + "v1": { + {Name: "clusterroles", Namespaced: false, Kind: "ClusterRole"}, + }, + "v1beta1": { + {Name: "clusterrolebindings", Namespaced: false, Kind: "ClusterRoleBinding"}, + }, + }, + }, { Group: metav1.APIGroup{ Name: "company.com", @@ -872,5 +1063,21 @@ func testDynamicResources() []*discovery.APIGroupResources { }, }, }, + { + Group: metav1.APIGroup{ + Name: "apitest", + Versions: []metav1.GroupVersionForDiscovery{ + {GroupVersion: "apitest/unlikelyversion", Version: "unlikelyversion"}, + }, + PreferredVersion: metav1.GroupVersionForDiscovery{ + GroupVersion: "apitest/unlikelyversion", + Version: "unlikelyversion"}, + }, + VersionedResources: map[string][]metav1.APIResource{ + "unlikelyversion": { + {Name: "types", SingularName: "type", Namespaced: false, Kind: "Type"}, + }, + }, + }, } } diff --git a/pkg/kubectl/cmd/testing/zz_generated.deepcopy.go b/pkg/kubectl/cmd/testing/zz_generated.deepcopy.go index 08c8d321cc3..f9b1342384a 100644 --- a/pkg/kubectl/cmd/testing/zz_generated.deepcopy.go +++ b/pkg/kubectl/cmd/testing/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,43 +21,9 @@ limitations under the License. package testing import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -// GetGeneratedDeepCopyFuncs returns the generated funcs, since we aren't registering them. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func GetGeneratedDeepCopyFuncs() []conversion.GeneratedDeepCopyFunc { - return []conversion.GeneratedDeepCopyFunc{ - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExternalNamespacedType).DeepCopyInto(out.(*ExternalNamespacedType)) - return nil - }, InType: reflect.TypeOf(&ExternalNamespacedType{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExternalNamespacedType2).DeepCopyInto(out.(*ExternalNamespacedType2)) - return nil - }, InType: reflect.TypeOf(&ExternalNamespacedType2{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExternalType).DeepCopyInto(out.(*ExternalType)) - return nil - }, InType: reflect.TypeOf(&ExternalType{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExternalType2).DeepCopyInto(out.(*ExternalType2)) - return nil - }, InType: reflect.TypeOf(&ExternalType2{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*InternalNamespacedType).DeepCopyInto(out.(*InternalNamespacedType)) - return nil - }, InType: reflect.TypeOf(&InternalNamespacedType{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*InternalType).DeepCopyInto(out.(*InternalType)) - return nil - }, InType: reflect.TypeOf(&InternalType{})}, - } -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExternalNamespacedType) DeepCopyInto(out *ExternalNamespacedType) { *out = *in diff --git a/pkg/kubectl/cmd/top_node.go b/pkg/kubectl/cmd/top_node.go index f535b734e97..dace2918d69 100644 --- a/pkg/kubectl/cmd/top_node.go +++ b/pkg/kubectl/cmd/top_node.go @@ -22,10 +22,10 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/kubernetes/pkg/api" - coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" + corev1 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/metricsutil" @@ -36,7 +36,7 @@ import ( type TopNodeOptions struct { ResourceName string Selector string - NodeClient coreclient.NodesGetter + NodeClient corev1.CoreV1Interface HeapsterOptions HeapsterTopOptions Client *metricsutil.HeapsterMetricsClient Printer *metricsutil.TopCmdPrinter @@ -118,12 +118,12 @@ func (o *TopNodeOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [] return cmdutil.UsageErrorf(cmd, "%s", cmd.Use) } - clientset, err := f.ClientSet() + clientset, err := f.KubernetesClientSet() if err != nil { return err } - o.NodeClient = clientset.Core() - o.Client = metricsutil.NewHeapsterMetricsClient(clientset.Core(), o.HeapsterOptions.Namespace, o.HeapsterOptions.Scheme, o.HeapsterOptions.Service, o.HeapsterOptions.Port) + o.NodeClient = clientset.CoreV1() + o.Client = metricsutil.NewHeapsterMetricsClient(clientset.CoreV1(), o.HeapsterOptions.Namespace, o.HeapsterOptions.Scheme, o.HeapsterOptions.Service, o.HeapsterOptions.Port) o.Printer = metricsutil.NewTopCmdPrinter(out) return nil } @@ -149,7 +149,7 @@ func (o TopNodeOptions) RunTopNode() error { return errors.New("metrics not available yet") } - var nodes []api.Node + var nodes []v1.Node if len(o.ResourceName) > 0 { node, err := o.NodeClient.Nodes().Get(o.ResourceName, metav1.GetOptions{}) if err != nil { @@ -166,7 +166,7 @@ func (o TopNodeOptions) RunTopNode() error { nodes = append(nodes, nodeList.Items...) } - allocatable := make(map[string]api.ResourceList) + allocatable := make(map[string]v1.ResourceList) for _, n := range nodes { allocatable[n.Name] = n.Status.Allocatable diff --git a/pkg/kubectl/cmd/top_node_test.go b/pkg/kubectl/cmd/top_node_test.go index ba292a63bb1..7afc783da11 100644 --- a/pkg/kubectl/cmd/top_node_test.go +++ b/pkg/kubectl/cmd/top_node_test.go @@ -25,8 +25,8 @@ import ( "net/url" + "k8s.io/api/core/v1" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" "k8s.io/metrics/pkg/apis/metrics/v1alpha1" ) @@ -45,7 +45,6 @@ func TestTopNodeAllMetrics(t *testing.T) { f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -91,7 +90,6 @@ func TestTopNodeAllMetricsCustomDefaults(t *testing.T) { f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -147,7 +145,6 @@ func TestTopNodeWithNameMetrics(t *testing.T) { f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -191,7 +188,7 @@ func TestTopNodeWithLabelSelectorMetrics(t *testing.T) { ListMeta: metrics.ListMeta, Items: metrics.Items[0:1], } - expectedNodes := api.NodeList{ + expectedNodes := v1.NodeList{ ListMeta: nodes.ListMeta, Items: nodes.Items[0:1], } @@ -207,7 +204,6 @@ func TestTopNodeWithLabelSelectorMetrics(t *testing.T) { f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m, q := req.URL.Path, req.Method, req.URL.RawQuery; { diff --git a/pkg/kubectl/cmd/top_pod.go b/pkg/kubectl/cmd/top_pod.go index 6e975bd5fcc..2ffdc6dff8e 100644 --- a/pkg/kubectl/cmd/top_pod.go +++ b/pkg/kubectl/cmd/top_pod.go @@ -22,10 +22,10 @@ import ( "io" "time" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/kubernetes/pkg/api" - coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" + corev1 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/metricsutil" @@ -41,7 +41,7 @@ type TopPodOptions struct { Selector string AllNamespaces bool PrintContainers bool - PodClient coreclient.PodsGetter + PodClient corev1.PodsGetter HeapsterOptions HeapsterTopOptions Client *metricsutil.HeapsterMetricsClient Printer *metricsutil.TopCmdPrinter @@ -114,12 +114,12 @@ func (o *TopPodOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []s if err != nil { return err } - clientset, err := f.ClientSet() + clientset, err := f.KubernetesClientSet() if err != nil { return err } - o.PodClient = clientset.Core() - o.Client = metricsutil.NewHeapsterMetricsClient(clientset.Core(), o.HeapsterOptions.Namespace, o.HeapsterOptions.Scheme, o.HeapsterOptions.Service, o.HeapsterOptions.Port) + o.PodClient = clientset.CoreV1() + o.Client = metricsutil.NewHeapsterMetricsClient(clientset.CoreV1(), o.HeapsterOptions.Namespace, o.HeapsterOptions.Scheme, o.HeapsterOptions.Service, o.HeapsterOptions.Port) o.Printer = metricsutil.NewTopCmdPrinter(out) return nil } @@ -185,7 +185,7 @@ func verifyEmptyMetrics(o TopPodOptions, selector labels.Selector) error { return errors.New("metrics not available yet") } -func checkPodAge(pod *api.Pod) error { +func checkPodAge(pod *v1.Pod) error { age := time.Since(pod.CreationTimestamp.Time) if age > metricsCreationDelay { message := fmt.Sprintf("Metrics not available for pod %s/%s, age: %s", pod.Namespace, pod.Name, age.String()) diff --git a/pkg/kubectl/cmd/top_pod_test.go b/pkg/kubectl/cmd/top_pod_test.go index 3766123fb79..94bca7ec3ed 100644 --- a/pkg/kubectl/cmd/top_pod_test.go +++ b/pkg/kubectl/cmd/top_pod_test.go @@ -29,7 +29,6 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" metricsapi "k8s.io/metrics/pkg/apis/metrics/v1alpha1" ) @@ -118,7 +117,6 @@ func TestTopPod(t *testing.T) { f, tf, _, ns := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m, q := req.URL.Path, req.Method, req.URL.RawQuery; { @@ -254,7 +252,6 @@ func TestTopPodCustomDefaults(t *testing.T) { f, tf, _, ns := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - APIRegistry: api.Registry, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m, q := req.URL.Path, req.Method, req.URL.RawQuery; { diff --git a/pkg/kubectl/cmd/top_test.go b/pkg/kubectl/cmd/top_test.go index 4b469fa5c39..ce168b649d0 100644 --- a/pkg/kubectl/cmd/top_test.go +++ b/pkg/kubectl/cmd/top_test.go @@ -28,7 +28,6 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" metricsapi "k8s.io/metrics/pkg/apis/metrics/v1alpha1" ) @@ -59,7 +58,7 @@ func marshallBody(metrics interface{}) (io.ReadCloser, error) { return ioutil.NopCloser(bytes.NewReader(result)), nil } -func testNodeMetricsData() (*metricsapi.NodeMetricsList, *api.NodeList) { +func testNodeMetricsData() (*metricsapi.NodeMetricsList, *v1.NodeList) { metrics := &metricsapi.NodeMetricsList{ ListMeta: metav1.ListMeta{ ResourceVersion: "1", @@ -85,28 +84,28 @@ func testNodeMetricsData() (*metricsapi.NodeMetricsList, *api.NodeList) { }, }, } - nodes := &api.NodeList{ + nodes := &v1.NodeList{ ListMeta: metav1.ListMeta{ ResourceVersion: "15", }, - Items: []api.Node{ + Items: []v1.Node{ { ObjectMeta: metav1.ObjectMeta{Name: "node1", ResourceVersion: "10"}, - Status: api.NodeStatus{ - Allocatable: api.ResourceList{ - api.ResourceCPU: *resource.NewMilliQuantity(10, resource.DecimalSI), - api.ResourceMemory: *resource.NewQuantity(20*(1024*1024), resource.DecimalSI), - api.ResourceStorage: *resource.NewQuantity(30*(1024*1024), resource.DecimalSI), + Status: v1.NodeStatus{ + Allocatable: v1.ResourceList{ + v1.ResourceCPU: *resource.NewMilliQuantity(10, resource.DecimalSI), + v1.ResourceMemory: *resource.NewQuantity(20*(1024*1024), resource.DecimalSI), + v1.ResourceStorage: *resource.NewQuantity(30*(1024*1024), resource.DecimalSI), }, }, }, { ObjectMeta: metav1.ObjectMeta{Name: "node2", ResourceVersion: "11"}, - Status: api.NodeStatus{ - Allocatable: api.ResourceList{ - api.ResourceCPU: *resource.NewMilliQuantity(50, resource.DecimalSI), - api.ResourceMemory: *resource.NewQuantity(60*(1024*1024), resource.DecimalSI), - api.ResourceStorage: *resource.NewQuantity(70*(1024*1024), resource.DecimalSI), + Status: v1.NodeStatus{ + Allocatable: v1.ResourceList{ + v1.ResourceCPU: *resource.NewMilliQuantity(50, resource.DecimalSI), + v1.ResourceMemory: *resource.NewQuantity(60*(1024*1024), resource.DecimalSI), + v1.ResourceStorage: *resource.NewQuantity(70*(1024*1024), resource.DecimalSI), }, }, }, diff --git a/pkg/kubectl/cmd/util/BUILD b/pkg/kubectl/cmd/util/BUILD index a48f77e24b5..282666a1be4 100644 --- a/pkg/kubectl/cmd/util/BUILD +++ b/pkg/kubectl/cmd/util/BUILD @@ -22,22 +22,24 @@ go_library( "//build/visible_to:pkg_kubectl_cmd_util_CONSUMERS", ], deps = [ - "//federation/apis/federation:go_default_library", - "//federation/client/clientset_generated/federation_clientset:go_default_library", - "//pkg/api:go_default_library", - "//pkg/api/v1:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/apps:go_default_library", "//pkg/apis/batch:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/v1:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library", "//pkg/client/unversioned:go_default_library", "//pkg/controller:go_default_library", "//pkg/kubectl:go_default_library", + "//pkg/kubectl/categories:go_default_library", + "//pkg/kubectl/cmd/templates:go_default_library", "//pkg/kubectl/cmd/util/openapi:go_default_library", "//pkg/kubectl/cmd/util/openapi/validation:go_default_library", "//pkg/kubectl/plugins:go_default_library", "//pkg/kubectl/resource:go_default_library", + "//pkg/kubectl/scheme:go_default_library", "//pkg/kubectl/validation:go_default_library", "//pkg/printers:go_default_library", "//pkg/printers/internalversion:go_default_library", @@ -47,9 +49,14 @@ go_library( "//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", + "//vendor/k8s.io/api/apps/v1:go_default_library", "//vendor/k8s.io/api/apps/v1beta1:go_default_library", + "//vendor/k8s.io/api/apps/v1beta2:go_default_library", + "//vendor/k8s.io/api/batch/v1:go_default_library", + "//vendor/k8s.io/api/batch/v1beta1:go_default_library", "//vendor/k8s.io/api/batch/v2alpha1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -88,22 +95,24 @@ go_test( data = [ "//api/swagger-spec", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/util", - library = ":go_default_library", visibility = [ "//build/visible_to:COMMON_testing", ], deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/testapi:go_default_library", "//pkg/api/testing:go_default_library", "//pkg/apis/apps:go_default_library", "//pkg/apis/batch:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", "//pkg/controller:go_default_library", "//pkg/kubectl:go_default_library", + "//pkg/kubectl/categories:go_default_library", "//pkg/kubectl/resource:go_default_library", "//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", diff --git a/pkg/kubectl/cmd/util/cached_discovery.go b/pkg/kubectl/cmd/util/cached_discovery.go index 8150b00ef94..0224ebbe9be 100644 --- a/pkg/kubectl/cmd/util/cached_discovery.go +++ b/pkg/kubectl/cmd/util/cached_discovery.go @@ -32,7 +32,7 @@ import ( "k8s.io/apimachinery/pkg/version" "k8s.io/client-go/discovery" restclient "k8s.io/client-go/rest" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/kubectl/scheme" ) // CachedDiscoveryClient implements the functions that discovery server-supported API groups, @@ -66,7 +66,7 @@ func (d *CachedDiscoveryClient) ServerResourcesForGroupVersion(groupVersion stri // don't fail on errors, we either don't have a file or won't be able to run the cached check. Either way we can fallback. if err == nil { cachedResources := &metav1.APIResourceList{} - if err := runtime.DecodeInto(api.Codecs.UniversalDecoder(), cachedBytes, cachedResources); err == nil { + if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), cachedBytes, cachedResources); err == nil { glog.V(10).Infof("returning cached discovery info from %v", filename) return cachedResources, nil } @@ -113,7 +113,7 @@ func (d *CachedDiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) { // don't fail on errors, we either don't have a file or won't be able to run the cached check. Either way we can fallback. if err == nil { cachedGroups := &metav1.APIGroupList{} - if err := runtime.DecodeInto(api.Codecs.UniversalDecoder(), cachedBytes, cachedGroups); err == nil { + if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), cachedBytes, cachedGroups); err == nil { glog.V(10).Infof("returning cached discovery info from %v", filename) return cachedGroups, nil } @@ -179,7 +179,7 @@ func (d *CachedDiscoveryClient) writeCachedFile(filename string, obj runtime.Obj return err } - bytes, err := runtime.Encode(api.Codecs.LegacyCodec(), obj) + bytes, err := runtime.Encode(scheme.Codecs.LegacyCodec(), obj) if err != nil { return err } diff --git a/pkg/kubectl/cmd/util/clientcache.go b/pkg/kubectl/cmd/util/clientcache.go index 5312cb3095c..124216d2e8f 100644 --- a/pkg/kubectl/cmd/util/clientcache.go +++ b/pkg/kubectl/cmd/util/clientcache.go @@ -24,7 +24,6 @@ import ( "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" - fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" oldclient "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/version" @@ -34,7 +33,6 @@ func NewClientCache(loader clientcmd.ClientConfig, discoveryClientFactory Discov return &ClientCache{ clientsets: make(map[schema.GroupVersion]internalclientset.Interface), configs: make(map[schema.GroupVersion]*restclient.Config), - fedClientSets: make(map[schema.GroupVersion]fedclientset.Interface), loader: loader, discoveryClientFactory: discoveryClientFactory, } @@ -43,10 +41,9 @@ func NewClientCache(loader clientcmd.ClientConfig, discoveryClientFactory Discov // ClientCache caches previously loaded clients for reuse, and ensures MatchServerVersion // is invoked only once type ClientCache struct { - loader clientcmd.ClientConfig - clientsets map[schema.GroupVersion]internalclientset.Interface - fedClientSets map[schema.GroupVersion]fedclientset.Interface - configs map[schema.GroupVersion]*restclient.Config + loader clientcmd.ClientConfig + clientsets map[schema.GroupVersion]internalclientset.Interface + configs map[schema.GroupVersion]*restclient.Config // noVersionConfig provides a cached config for the case of no required version specified noVersionConfig *restclient.Config @@ -211,51 +208,3 @@ func (c *ClientCache) ClientSetForVersion(requiredVersion *schema.GroupVersion) return clientset, nil } - -func (c *ClientCache) FederationClientSetForVersion(version *schema.GroupVersion) (fedclientset.Interface, error) { - c.lock.Lock() - defer c.lock.Unlock() - - return c.federationClientSetForVersion(version) -} - -func (c *ClientCache) federationClientSetForVersion(version *schema.GroupVersion) (fedclientset.Interface, error) { - if version != nil { - if clientSet, found := c.fedClientSets[*version]; found { - return clientSet, nil - } - } - config, err := c.clientConfigForVersion(version) - if err != nil { - return nil, err - } - - // TODO: support multi versions of client with clientset - clientSet, err := fedclientset.NewForConfig(config) - if err != nil { - return nil, err - } - c.fedClientSets[*config.GroupVersion] = clientSet - - if version != nil { - configCopy := *config - clientSet, err := fedclientset.NewForConfig(&configCopy) - if err != nil { - return nil, err - } - c.fedClientSets[*version] = clientSet - } - - return clientSet, nil -} - -func (c *ClientCache) FederationClientForVersion(version *schema.GroupVersion) (*restclient.RESTClient, error) { - c.lock.Lock() - defer c.lock.Unlock() - - fedClientSet, err := c.federationClientSetForVersion(version) - if err != nil { - return nil, err - } - return fedClientSet.Federation().RESTClient().(*restclient.RESTClient), nil -} diff --git a/pkg/kubectl/cmd/util/editor/BUILD b/pkg/kubectl/cmd/util/editor/BUILD index d131c5d9bd1..7ccb044444e 100644 --- a/pkg/kubectl/cmd/util/editor/BUILD +++ b/pkg/kubectl/cmd/util/editor/BUILD @@ -15,10 +15,11 @@ go_library( "//build/visible_to:pkg_kubectl_cmd_util_editor_CONSUMERS", ], deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/kubectl:go_default_library", "//pkg/kubectl/cmd/util:go_default_library", "//pkg/kubectl/resource:go_default_library", + "//pkg/kubectl/scheme:go_default_library", "//pkg/kubectl/util/crlf:go_default_library", "//pkg/kubectl/util/term:go_default_library", "//pkg/printers:go_default_library", @@ -41,8 +42,8 @@ go_library( go_test( name = "go_default_test", srcs = ["editor_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/util/editor", - library = ":go_default_library", ) filegroup( diff --git a/pkg/kubectl/cmd/util/editor/editoptions.go b/pkg/kubectl/cmd/util/editor/editoptions.go index f125efa60bb..5ec4bfe6679 100644 --- a/pkg/kubectl/cmd/util/editor/editoptions.go +++ b/pkg/kubectl/cmd/util/editor/editoptions.go @@ -41,10 +41,11 @@ import ( "k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/yaml" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/kubectl" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/kubectl/util/crlf" "k8s.io/kubernetes/pkg/printers" ) @@ -106,12 +107,9 @@ func (o *EditOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args [] if err != nil { return err } - mapper, typer, err := f.UnstructuredObject() - if err != nil { - return err - } - - b := f.NewBuilder().Unstructured(f.UnstructuredClientForMapping, mapper, typer) + mapper, _ := f.Object() + b := f.NewBuilder(). + Unstructured() if o.EditMode == NormalEditMode || o.EditMode == ApplyEditMode { // when do normal edit or apply edit we need to always retrieve the latest resource from server b = b.ResourceTypeOrNameArgs(true, args...).Latest() @@ -131,7 +129,8 @@ func (o *EditOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args [] o.updatedResultGetter = func(data []byte) *resource.Result { // resource builder to read objects from edited data - return resource.NewBuilder(mapper, f.CategoryExpander(), typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme). + return f.NewBuilder(). + Unstructured(). Stream(bytes.NewReader(data), "edited-file"). IncludeUninitialized(includeUninitialized). ContinueOnError(). @@ -409,14 +408,14 @@ func (o *EditOptions) visitToApplyEditPatch(originalInfos []*resource.Info, patc } if reflect.DeepEqual(originalJS, editedJS) { - cmdutil.PrintSuccess(o.Mapper, false, o.Out, info.Mapping.Resource, info.Name, false, "skipped") + o.f.PrintSuccess(o.Mapper, false, o.Out, info.Mapping.Resource, info.Name, false, "skipped") return nil } else { err := o.annotationPatch(info) if err != nil { return err } - cmdutil.PrintSuccess(o.Mapper, false, o.Out, info.Mapping.Resource, info.Name, false, "edited") + o.f.PrintSuccess(o.Mapper, false, o.Out, info.Mapping.Resource, info.Name, false, "edited") return nil } }) @@ -535,7 +534,7 @@ func (o *EditOptions) visitToPatch(originalInfos []*resource.Info, patchVisitor if reflect.DeepEqual(originalJS, editedJS) { // no edit, so just skip it. - cmdutil.PrintSuccess(o.Mapper, false, o.Out, info.Mapping.Resource, info.Name, false, "skipped") + o.f.PrintSuccess(o.Mapper, false, o.Out, info.Mapping.Resource, info.Name, false, "skipped") return nil } @@ -547,7 +546,7 @@ func (o *EditOptions) visitToPatch(originalInfos []*resource.Info, patchVisitor // Create the versioned struct from the type defined in the mapping // (which is the API version we'll be submitting the patch to) - versionedObject, err := api.Scheme.New(info.Mapping.GroupVersionKind) + versionedObject, err := scheme.Scheme.New(info.Mapping.GroupVersionKind) var patchType types.PatchType var patch []byte switch { @@ -589,7 +588,7 @@ func (o *EditOptions) visitToPatch(originalInfos []*resource.Info, patchVisitor return nil } info.Refresh(patched, true) - cmdutil.PrintSuccess(o.Mapper, false, o.Out, info.Mapping.Resource, info.Name, false, "edited") + o.f.PrintSuccess(o.Mapper, false, o.Out, info.Mapping.Resource, info.Name, false, "edited") return nil }) return err @@ -600,7 +599,7 @@ func (o *EditOptions) visitToCreate(createVisitor resource.Visitor) error { if err := resource.CreateAndRefresh(info); err != nil { return err } - cmdutil.PrintSuccess(o.Mapper, false, o.Out, info.Mapping.Resource, info.Name, false, "created") + o.f.PrintSuccess(o.Mapper, false, o.Out, info.Mapping.Resource, info.Name, false, "created") return nil }) return err diff --git a/pkg/kubectl/cmd/util/env/BUILD b/pkg/kubectl/cmd/util/env/BUILD index ccd5b4b20bb..076e0b3a046 100644 --- a/pkg/kubectl/cmd/util/env/BUILD +++ b/pkg/kubectl/cmd/util/env/BUILD @@ -1,21 +1,22 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = [ + "doc.go", "env_parse.go", "env_resolve.go", ], importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/util/env", visibility = ["//visibility:public"], deps = [ - "//pkg/api:go_default_library", - "//pkg/api/resource:go_default_library", - "//pkg/client/clientset_generated/internalclientset:go_default_library", + "//pkg/api/v1/resource:go_default_library", "//pkg/fieldpath:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", ], ) @@ -32,3 +33,10 @@ filegroup( tags = ["automanaged"], visibility = ["//visibility:public"], ) + +go_test( + name = "go_default_test", + srcs = ["env_parse_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/util/env", +) diff --git a/pkg/kubectl/cmd/util/env/doc.go b/pkg/kubectl/cmd/util/env/doc.go new file mode 100644 index 00000000000..39adb0adf0c --- /dev/null +++ b/pkg/kubectl/cmd/util/env/doc.go @@ -0,0 +1,18 @@ +/* +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 env provides functions to incorporate environment variables into kubectl commands. +package env // import "k8s.io/kubernetes/pkg/kubectl/cmd/util/env" diff --git a/pkg/kubectl/cmd/util/env/env_parse.go b/pkg/kubectl/cmd/util/env/env_parse.go index d9c15123dc2..1d1686af647 100644 --- a/pkg/kubectl/cmd/util/env/env_parse.go +++ b/pkg/kubectl/cmd/util/env/env_parse.go @@ -24,11 +24,11 @@ import ( "regexp" "strings" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api" ) -// Env returns an environment variable or a default value if not specified. +// Env returns an environment variable if not nil, or a default value. func Env(key string, defaultValue string) string { val := os.Getenv(key) if len(val) == 0 { @@ -37,7 +37,7 @@ func Env(key string, defaultValue string) string { return val } -// GetEnv returns an environment value if specified +// GetEnv returns an environment value if not nil, and an ok boolean. func GetEnv(key string) (string, bool) { val := os.Getenv(key) if len(val) == 0 { @@ -49,17 +49,18 @@ func GetEnv(key string) (string, bool) { var argumentEnvironment = regexp.MustCompile("(?ms)^(.+)\\=(.*)$") var validArgumentEnvironment = regexp.MustCompile("(?ms)^(\\w+)\\=(.*)$") -// IsEnvironmentArgument check str is env args +// IsEnvironmentArgument checks whether a string is an environment argument, that is, whether it matches the "anycharacters=anycharacters" pattern. func IsEnvironmentArgument(s string) bool { return argumentEnvironment.MatchString(s) } -// IsValidEnvironmentArgument check str is valid env +// IsValidEnvironmentArgument checks whether a string is a valid environment argument, that is, whether it matches the "wordcharacters=anycharacters" pattern. Word characters can be letters, numbers, and underscores. func IsValidEnvironmentArgument(s string) bool { return validArgumentEnvironment.MatchString(s) } -// SplitEnvironmentFromResources returns resources and envargs +// SplitEnvironmentFromResources separates resources from environment arguments. +// Resources must come first. Arguments may have the "DASH-" syntax. func SplitEnvironmentFromResources(args []string) (resources, envArgs []string, ok bool) { first := true for _, s := range args { @@ -82,8 +83,8 @@ func SplitEnvironmentFromResources(args []string) (resources, envArgs []string, // parseIntoEnvVar parses the list of key-value pairs into kubernetes EnvVar. // envVarType is for making errors more specific to user intentions. -func parseIntoEnvVar(spec []string, defaultReader io.Reader, envVarType string) ([]api.EnvVar, []string, error) { - env := []api.EnvVar{} +func parseIntoEnvVar(spec []string, defaultReader io.Reader, envVarType string) ([]v1.EnvVar, []string, error) { + env := []v1.EnvVar{} exists := sets.NewString() var remove []string for _, envSpec := range spec { @@ -105,7 +106,7 @@ func parseIntoEnvVar(spec []string, defaultReader io.Reader, envVarType string) return nil, nil, fmt.Errorf("invalid %s: %v", envVarType, envSpec) } exists.Insert(parts[0]) - env = append(env, api.EnvVar{ + env = append(env, v1.EnvVar{ Name: parts[0], Value: parts[1], }) @@ -123,13 +124,14 @@ func parseIntoEnvVar(spec []string, defaultReader io.Reader, envVarType string) return env, remove, nil } -// ParseEnv parse env from reader -func ParseEnv(spec []string, defaultReader io.Reader) ([]api.EnvVar, []string, error) { +// ParseEnv parses the elements of the first argument looking for environment variables in key=value form and, if one of those values is "-", it also scans the reader. +// The same environment variable cannot be both modified and removed in the same command. +func ParseEnv(spec []string, defaultReader io.Reader) ([]v1.EnvVar, []string, error) { return parseIntoEnvVar(spec, defaultReader, "environment variable") } -func readEnv(r io.Reader, envVarType string) ([]api.EnvVar, error) { - env := []api.EnvVar{} +func readEnv(r io.Reader, envVarType string) ([]v1.EnvVar, error) { + env := []v1.EnvVar{} scanner := bufio.NewScanner(r) for scanner.Scan() { envSpec := scanner.Text() @@ -141,7 +143,7 @@ func readEnv(r io.Reader, envVarType string) ([]api.EnvVar, error) { if len(parts) != 2 { return nil, fmt.Errorf("invalid %s: %v", envVarType, envSpec) } - env = append(env, api.EnvVar{ + env = append(env, v1.EnvVar{ Name: parts[0], Value: parts[1], }) diff --git a/pkg/kubectl/cmd/util/env/env_parse_test.go b/pkg/kubectl/cmd/util/env/env_parse_test.go new file mode 100644 index 00000000000..32be9833c76 --- /dev/null +++ b/pkg/kubectl/cmd/util/env/env_parse_test.go @@ -0,0 +1,96 @@ +/* +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 env + +import ( + "fmt" + "io" + "os" + "strings" +) + +func ExampleEnv_defaultValue() { + fmt.Println(Env("TESTENVVAR", "default")) + // Output: default +} + +func ExampleEnv_variableExists() { + os.Setenv("TESTENVVAR", "test value") + defer os.Unsetenv("TESTENVVAR") + fmt.Println(Env("TESTENVVAR", "default")) + // Output: test value +} + +func ExampleGetEnv_variableExists() { + os.Setenv("THISVAREXISTS", "value") + defer os.Unsetenv("THISVAREXISTS") + fmt.Println(GetEnv("THISVAREXISTS")) + // Output: + // value true +} + +func ExampleGetEnv_variableDoesNotExist() { + fmt.Println(GetEnv("THISVARDOESNOTEXIST")) + // Output: + // false +} + +func ExampleIsEnvironmentArgument_true() { + test := "returns=true" + fmt.Println(IsEnvironmentArgument(test)) + // Output: true +} + +func ExampleIsEnvironmentArgument_false() { + test := "returnsfalse" + fmt.Println(IsEnvironmentArgument(test)) + // Output: false +} + +func ExampleIsValidEnvironmentArgument_true() { + test := "wordcharacters=true" + fmt.Println(IsValidEnvironmentArgument(test)) + // Output: true +} + +func ExampleIsValidEnvironmentArgument_false() { + test := "not$word^characters=test" + fmt.Println(IsValidEnvironmentArgument(test)) + // Output: false +} + +func ExampleSplitEnvironmentFromResources() { + args := []string{`resource`, "ENV\\=ARG", `ONE\=MORE`, `DASH-`} + fmt.Println(SplitEnvironmentFromResources(args)) + // Output: [resource] [ENV\=ARG ONE\=MORE DASH-] true +} + +func ExampleParseEnv_good() { + r := strings.NewReader("FROM=READER") + ss := []string{"ENV=VARIABLE", "AND=ANOTHER", "REMOVE-", "-"} + fmt.Println(ParseEnv(ss, r)) + // Output: + // [{ENV VARIABLE nil} {AND ANOTHER nil} {FROM READER nil}] [REMOVE] +} + +func ExampleParseEnv_bad() { + var r io.Reader + bad := []string{"This not in the key=value format."} + fmt.Println(ParseEnv(bad, r)) + // Output: + // [] [] environment variables must be of the form key=value and can only contain letters, numbers, and underscores +} diff --git a/pkg/kubectl/cmd/util/env/env_resolve.go b/pkg/kubectl/cmd/util/env/env_resolve.go index 8955fb21999..ede70215ad4 100644 --- a/pkg/kubectl/cmd/util/env/env_resolve.go +++ b/pkg/kubectl/cmd/util/env/env_resolve.go @@ -19,34 +19,34 @@ package env import ( "fmt" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/resource" - clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/client-go/kubernetes" + "k8s.io/kubernetes/pkg/api/v1/resource" "k8s.io/kubernetes/pkg/fieldpath" ) -// ResourceStore defines a new resource store data structure +// ResourceStore defines a new resource store data structure. type ResourceStore struct { - SecretStore map[string]*api.Secret - ConfigMapStore map[string]*api.ConfigMap + SecretStore map[string]*v1.Secret + ConfigMapStore map[string]*v1.ConfigMap } -// NewResourceStore returns a pointer to a new resource store data structure +// NewResourceStore returns a pointer to a new resource store data structure. func NewResourceStore() *ResourceStore { return &ResourceStore{ - SecretStore: make(map[string]*api.Secret), - ConfigMapStore: make(map[string]*api.ConfigMap), + SecretStore: make(map[string]*v1.Secret), + ConfigMapStore: make(map[string]*v1.ConfigMap), } } // getSecretRefValue returns the value of a secret in the supplied namespace -func getSecretRefValue(client clientset.Interface, namespace string, store *ResourceStore, secretSelector *api.SecretKeySelector) (string, error) { +func getSecretRefValue(client kubernetes.Interface, namespace string, store *ResourceStore, secretSelector *v1.SecretKeySelector) (string, error) { secret, ok := store.SecretStore[secretSelector.Name] if !ok { var err error - secret, err = client.Core().Secrets(namespace).Get(secretSelector.Name, metav1.GetOptions{}) + secret, err = client.CoreV1().Secrets(namespace).Get(secretSelector.Name, metav1.GetOptions{}) if err != nil { return "", err } @@ -60,11 +60,11 @@ func getSecretRefValue(client clientset.Interface, namespace string, store *Reso } // getConfigMapRefValue returns the value of a configmap in the supplied namespace -func getConfigMapRefValue(client clientset.Interface, namespace string, store *ResourceStore, configMapSelector *api.ConfigMapKeySelector) (string, error) { +func getConfigMapRefValue(client kubernetes.Interface, namespace string, store *ResourceStore, configMapSelector *v1.ConfigMapKeySelector) (string, error) { configMap, ok := store.ConfigMapStore[configMapSelector.Name] if !ok { var err error - configMap, err = client.Core().ConfigMaps(namespace).Get(configMapSelector.Name, metav1.GetOptions{}) + configMap, err = client.CoreV1().ConfigMaps(namespace).Get(configMapSelector.Name, metav1.GetOptions{}) if err != nil { return "", err } @@ -77,17 +77,17 @@ func getConfigMapRefValue(client clientset.Interface, namespace string, store *R } // getFieldRef returns the value of the supplied path in the given object -func getFieldRef(obj runtime.Object, from *api.EnvVarSource) (string, error) { +func getFieldRef(obj runtime.Object, from *v1.EnvVarSource) (string, error) { return fieldpath.ExtractFieldPathAsString(obj, from.FieldRef.FieldPath) } // getResourceFieldRef returns the value of a resource in the given container -func getResourceFieldRef(from *api.EnvVarSource, c *api.Container) (string, error) { +func getResourceFieldRef(from *v1.EnvVarSource, c *v1.Container) (string, error) { return resource.ExtractContainerResourceValue(from.ResourceFieldRef, c) } -// GetEnvVarRefValue returns the value referenced by the supplied envvarsource given the other supplied information -func GetEnvVarRefValue(kc clientset.Interface, ns string, store *ResourceStore, from *api.EnvVarSource, obj runtime.Object, c *api.Container) (string, error) { +// GetEnvVarRefValue returns the value referenced by the supplied EnvVarSource given the other supplied information. +func GetEnvVarRefValue(kc kubernetes.Interface, ns string, store *ResourceStore, from *v1.EnvVarSource, obj runtime.Object, c *v1.Container) (string, error) { if from.SecretKeyRef != nil { return getSecretRefValue(kc, ns, store, from.SecretKeyRef) } @@ -107,8 +107,8 @@ func GetEnvVarRefValue(kc clientset.Interface, ns string, store *ResourceStore, return "", fmt.Errorf("invalid valueFrom") } -// GetEnvVarRefString returns a text description of the supplied envvarsource -func GetEnvVarRefString(from *api.EnvVarSource) string { +// GetEnvVarRefString returns a text description of whichever field is set within the supplied EnvVarSource argument. +func GetEnvVarRefString(from *v1.EnvVarSource) string { if from.ConfigMapKeyRef != nil { return fmt.Sprintf("configmap %s, key %s", from.ConfigMapKeyRef.Name, from.ConfigMapKeyRef.Key) } diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go index 2191fbc9e09..f4c0799596f 100644 --- a/pkg/kubectl/cmd/util/factory.go +++ b/pkg/kubectl/cmd/util/factory.go @@ -37,12 +37,12 @@ import ( "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" - fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - "k8s.io/kubernetes/pkg/api" - apiv1 "k8s.io/kubernetes/pkg/api/v1" + api "k8s.io/kubernetes/pkg/apis/core" + apiv1 "k8s.io/kubernetes/pkg/apis/core/v1" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/categories" "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" "k8s.io/kubernetes/pkg/kubectl/plugins" "k8s.io/kubernetes/pkg/kubectl/resource" @@ -103,11 +103,6 @@ type ClientAccessFactory interface { // just directions to the server. People use this to build RESTMappers on top of BareClientConfig() (*restclient.Config, error) - // TODO this should probably be removed and collapsed into whatever we want to use long term - // probably returning a restclient for a version and leaving contruction up to someone else - FederationClientSetForVersion(version *schema.GroupVersion) (fedclientset.Interface, error) - // TODO remove this should be rolled into restclient with the right version - FederationClientForVersion(version *schema.GroupVersion) (*restclient.RESTClient, error) // TODO remove. This should be rolled into `ClientSet` ClientSetForVersion(requiredVersion *schema.GroupVersion) (internalclientset.Interface, error) // TODO remove. This should be rolled into `ClientConfig` @@ -122,7 +117,7 @@ type ClientAccessFactory interface { // UpdatePodSpecForObject will call the provided function on the pod spec this object supports, // return false if no pod spec is supported, or return an error. - UpdatePodSpecForObject(obj runtime.Object, fn func(*api.PodSpec) error) (bool, error) + UpdatePodSpecForObject(obj runtime.Object, fn func(*v1.PodSpec) error) (bool, error) // MapBasedSelectorForObject returns the map-based selector associated with the provided object. If a // new set-based selector is provided, an error is returned if the selector cannot be converted to a @@ -145,8 +140,6 @@ type ClientAccessFactory interface { // BindExternalFlags adds any flags defined by external projects (not part of pflags) BindExternalFlags(flags *pflag.FlagSet) - // TODO: Break the dependency on cmd here. - DefaultResourceFilterOptions(cmd *cobra.Command, withNamespace bool) *printers.PrintOptions // DefaultResourceFilterFunc returns a collection of FilterFuncs suitable for filtering specific resource types. DefaultResourceFilterFunc() kubectl.Filters @@ -194,11 +187,8 @@ type ClientAccessFactory interface { type ObjectMappingFactory interface { // Returns interfaces for dealing with arbitrary runtime.Objects. Object() (meta.RESTMapper, runtime.ObjectTyper) - // Returns interfaces for dealing with arbitrary - // runtime.Unstructured. This performs API calls to discover types. - UnstructuredObject() (meta.RESTMapper, runtime.ObjectTyper, error) // Returns interface for expanding categories like `all`. - CategoryExpander() resource.CategoryExpander + CategoryExpander() categories.CategoryExpander // Returns a RESTClient for working with the specified RESTMapping or an error. This is intended // for working with arbitrary resources and is not guaranteed to point to a Kubernetes APIServer. ClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error) @@ -240,16 +230,23 @@ type BuilderFactory interface { // PrinterForCommand returns the default printer for the command. It requires that certain options // are declared on the command (see AddPrinterFlags). Returns a printer, or an error if a printer // could not be found. - // TODO: Break the dependency on cmd here. - PrinterForCommand(cmd *cobra.Command, isLocal bool, outputOpts *printers.OutputOptions, options printers.PrintOptions) (printers.ResourcePrinter, error) + PrinterForOptions(options *printers.PrintOptions) (printers.ResourcePrinter, error) // PrinterForMapping returns a printer suitable for displaying the provided resource type. // Requires that printer flags have been added to cmd (see AddPrinterFlags). // Returns a printer, true if the printer is generic (is not internal), or // an error if a printer could not be found. - PrinterForMapping(cmd *cobra.Command, isLocal bool, outputOpts *printers.OutputOptions, mapping *meta.RESTMapping, withNamespace bool) (printers.ResourcePrinter, error) + PrinterForMapping(options *printers.PrintOptions, mapping *meta.RESTMapping) (printers.ResourcePrinter, error) // PrintObject prints an api object given command line flags to modify the output format PrintObject(cmd *cobra.Command, isLocal bool, mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error - // One stop shopping for a Builder + // PrintResourceInfoForCommand receives a *cobra.Command and a *resource.Info and + // attempts to print an info object based on the specified output format. If the + // object passed is non-generic, it attempts to print the object using a HumanReadablePrinter. + // Requires that printer flags have been added to cmd (see AddPrinterFlags). + PrintResourceInfoForCommand(cmd *cobra.Command, info *resource.Info, out io.Writer) error + // PrintSuccess prints message after finishing mutating operations + PrintSuccess(mapper meta.RESTMapper, shortOutput bool, out io.Writer, resource, name string, dryRun bool, operation string) + // NewBuilder returns an object that assists in loading objects from both disk and the server + // and which implements the common patterns for CLI interactions with generic resources. NewBuilder() *resource.Builder // PluginLoader provides the implementation to be used to load cli plugins. PluginLoader() plugins.PluginLoader @@ -301,13 +298,13 @@ func GetFirstPod(client coreclient.PodsGetter, namespace string, selector string for i := range podList.Items { pod := podList.Items[i] externalPod := &v1.Pod{} - apiv1.Convert_api_Pod_To_v1_Pod(&pod, externalPod, nil) + apiv1.Convert_core_Pod_To_v1_Pod(&pod, externalPod, nil) pods = append(pods, externalPod) } if len(pods) > 0 { sort.Sort(sortBy(pods)) internalPod := &api.Pod{} - apiv1.Convert_v1_Pod_To_api_Pod(pods[0], internalPod, nil) + apiv1.Convert_v1_Pod_To_core_Pod(pods[0], internalPod, nil) return internalPod, len(podList.Items), nil } diff --git a/pkg/kubectl/cmd/util/factory_builder.go b/pkg/kubectl/cmd/util/factory_builder.go index b9e0fda2bb7..4d7e99b5ca0 100644 --- a/pkg/kubectl/cmd/util/factory_builder.go +++ b/pkg/kubectl/cmd/util/factory_builder.go @@ -28,7 +28,6 @@ import ( "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/kubectl/plugins" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/printers" @@ -48,44 +47,20 @@ func NewBuilderFactory(clientAccessFactory ClientAccessFactory, objectMappingFac return f } -func (f *ring2Factory) PrinterForCommand(cmd *cobra.Command, isLocal bool, outputOpts *printers.OutputOptions, options printers.PrintOptions) (printers.ResourcePrinter, error) { +func (f *ring2Factory) PrinterForOptions(options *printers.PrintOptions) (printers.ResourcePrinter, error) { var mapper meta.RESTMapper var typer runtime.ObjectTyper - var err error - if isLocal { - mapper = api.Registry.RESTMapper() - typer = api.Scheme - } else { - mapper, typer, err = f.objectMappingFactory.UnstructuredObject() - if err != nil { - return nil, err - } - } + mapper, typer = f.objectMappingFactory.Object() + // TODO: used by the custom column implementation and the name implementation, break this dependency decoders := []runtime.Decoder{f.clientAccessFactory.Decoder(true), unstructured.UnstructuredJSONScheme} encoder := f.clientAccessFactory.JSONEncoder() - return PrinterForCommand(cmd, outputOpts, mapper, typer, encoder, decoders, options) + return printerForOptions(mapper, typer, encoder, decoders, options) } -func (f *ring2Factory) PrinterForMapping(cmd *cobra.Command, isLocal bool, outputOpts *printers.OutputOptions, mapping *meta.RESTMapping, withNamespace bool) (printers.ResourcePrinter, error) { - // Some callers do not have "label-columns" so we can't use the GetFlagStringSlice() helper - columnLabel, err := cmd.Flags().GetStringSlice("label-columns") - if err != nil { - columnLabel = []string{} - } - - options := printers.PrintOptions{ - NoHeaders: GetFlagBool(cmd, "no-headers"), - WithNamespace: withNamespace, - Wide: GetWideFlag(cmd), - ShowAll: GetFlagBool(cmd, "show-all"), - ShowLabels: GetFlagBool(cmd, "show-labels"), - AbsoluteTimestamps: isWatch(cmd), - ColumnLabels: columnLabel, - } - - printer, err := f.PrinterForCommand(cmd, isLocal, outputOpts, options) +func (f *ring2Factory) PrinterForMapping(options *printers.PrintOptions, mapping *meta.RESTMapping) (printers.ResourcePrinter, error) { + printer, err := f.PrinterForOptions(options) if err != nil { return nil, err } @@ -107,46 +82,94 @@ func (f *ring2Factory) PrinterForMapping(cmd *cobra.Command, isLocal bool, outpu return printer, nil } +func (f *ring2Factory) PrintSuccess(mapper meta.RESTMapper, shortOutput bool, out io.Writer, resource, name string, dryRun bool, operation string) { + resource, _ = mapper.ResourceSingularizer(resource) + dryRunMsg := "" + if dryRun { + dryRunMsg = " (dry run)" + } + if shortOutput { + // -o name: prints resource/name + if len(resource) > 0 { + fmt.Fprintf(out, "%s/%s\n", resource, name) + } else { + fmt.Fprintf(out, "%s\n", name) + } + } else { + // understandable output by default + if len(resource) > 0 { + fmt.Fprintf(out, "%s \"%s\" %s%s\n", resource, name, operation, dryRunMsg) + } else { + fmt.Fprintf(out, "\"%s\" %s%s\n", name, operation, dryRunMsg) + } + } +} + func (f *ring2Factory) PrintObject(cmd *cobra.Command, isLocal bool, mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error { // try to get a typed object _, typer := f.objectMappingFactory.Object() gvks, _, err := typer.ObjectKinds(obj) - // fall back to an unstructured object if we get something unregistered - if runtime.IsNotRegisteredError(err) { - _, typer, unstructuredErr := f.objectMappingFactory.UnstructuredObject() - if unstructuredErr != nil { - // if we can't get an unstructured typer, return the original error - return err - } - gvks, _, err = typer.ObjectKinds(obj) + if err != nil { + return err + } + // Prefer the existing external version if specified + var preferredVersion []string + if gvks[0].Version != "" && gvks[0].Version != runtime.APIVersionInternal { + preferredVersion = []string{gvks[0].Version} } + mapping, err := mapper.RESTMapping(gvks[0].GroupKind(), preferredVersion...) if err != nil { return err } - mapping, err := mapper.RESTMapping(gvks[0].GroupKind()) - if err != nil { - return err - } - - printer, err := f.PrinterForMapping(cmd, isLocal, nil, mapping, false) + printer, err := f.PrinterForMapping(ExtractCmdPrintOptions(cmd, false), mapping) if err != nil { return err } return printer.PrintObj(obj, out) } -// NewBuilder returns a new resource builder. -// Receives a bool flag and avoids remote calls if set to false +func (f *ring2Factory) PrintResourceInfoForCommand(cmd *cobra.Command, info *resource.Info, out io.Writer) error { + printOpts := ExtractCmdPrintOptions(cmd, false) + printer, err := f.PrinterForOptions(printOpts) + if err != nil { + return err + } + if !printer.IsGeneric() { + printer, err = f.PrinterForMapping(printOpts, nil) + if err != nil { + return err + } + } + return printer.PrintObj(info.Object, out) +} + +// NewBuilder returns a new resource builder for structured api objects. func (f *ring2Factory) NewBuilder() *resource.Builder { clientMapperFunc := resource.ClientMapperFunc(f.objectMappingFactory.ClientForMapping) - mapper, typer := f.objectMappingFactory.Object() + + unstructuredClientMapperFunc := resource.ClientMapperFunc(f.objectMappingFactory.UnstructuredClientForMapping) + categoryExpander := f.objectMappingFactory.CategoryExpander() - return resource.NewBuilder(mapper, categoryExpander, typer, clientMapperFunc, f.clientAccessFactory.Decoder(true)) + return resource.NewBuilder( + &resource.Mapper{ + RESTMapper: mapper, + ObjectTyper: typer, + ClientMapper: clientMapperFunc, + Decoder: f.clientAccessFactory.Decoder(true), + }, + &resource.Mapper{ + RESTMapper: mapper, + ObjectTyper: typer, + ClientMapper: unstructuredClientMapperFunc, + Decoder: unstructured.UnstructuredJSONScheme, + }, + categoryExpander, + ) } // PluginLoader loads plugins from a path set by the KUBECTL_PLUGINS_PATH env var. diff --git a/pkg/kubectl/cmd/util/factory_client_access.go b/pkg/kubectl/cmd/util/factory_client_access.go index 7035a76b9ec..d03ebdc165c 100644 --- a/pkg/kubectl/cmd/util/factory_client_access.go +++ b/pkg/kubectl/cmd/util/factory_client_access.go @@ -29,11 +29,18 @@ import ( "strings" "time" + "k8s.io/api/core/v1" + "github.com/spf13/cobra" "github.com/spf13/pflag" + appsv1 "k8s.io/api/apps/v1" appsv1beta1 "k8s.io/api/apps/v1beta1" + appsv1beta2 "k8s.io/api/apps/v1beta2" + batchv1 "k8s.io/api/batch/v1" + batchv1beta1 "k8s.io/api/batch/v1beta1" batchv2alpha1 "k8s.io/api/batch/v2alpha1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -45,10 +52,9 @@ import ( restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" - fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/apps" - "k8s.io/kubernetes/pkg/apis/batch" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/kubectl" @@ -212,48 +218,69 @@ func (f *ring0Factory) RESTClient() (*restclient.RESTClient, error) { return restclient.RESTClientFor(clientConfig) } -func (f *ring0Factory) FederationClientSetForVersion(version *schema.GroupVersion) (fedclientset.Interface, error) { - return f.clientCache.FederationClientSetForVersion(version) -} - -func (f *ring0Factory) FederationClientForVersion(version *schema.GroupVersion) (*restclient.RESTClient, error) { - return f.clientCache.FederationClientForVersion(version) -} - func (f *ring0Factory) Decoder(toInternal bool) runtime.Decoder { var decoder runtime.Decoder if toInternal { - decoder = api.Codecs.UniversalDecoder() + decoder = legacyscheme.Codecs.UniversalDecoder() } else { - decoder = api.Codecs.UniversalDeserializer() + decoder = legacyscheme.Codecs.UniversalDeserializer() } return decoder } func (f *ring0Factory) JSONEncoder() runtime.Encoder { - return api.Codecs.LegacyCodec(api.Registry.EnabledVersions()...) + return legacyscheme.Codecs.LegacyCodec(legacyscheme.Registry.EnabledVersions()...) } -func (f *ring0Factory) UpdatePodSpecForObject(obj runtime.Object, fn func(*api.PodSpec) error) (bool, error) { +func (f *ring0Factory) UpdatePodSpecForObject(obj runtime.Object, fn func(*v1.PodSpec) error) (bool, error) { // TODO: replace with a swagger schema based approach (identify pod template via schema introspection) switch t := obj.(type) { - case *api.Pod: + case *v1.Pod: return true, fn(&t.Spec) - case *api.ReplicationController: + // ReplicationController + case *v1.ReplicationController: if t.Spec.Template == nil { - t.Spec.Template = &api.PodTemplateSpec{} + t.Spec.Template = &v1.PodTemplateSpec{} } return true, fn(&t.Spec.Template.Spec) - case *extensions.Deployment: + // Deployment + case *extensionsv1beta1.Deployment: return true, fn(&t.Spec.Template.Spec) - case *extensions.DaemonSet: + case *appsv1beta1.Deployment: return true, fn(&t.Spec.Template.Spec) - case *extensions.ReplicaSet: + case *appsv1beta2.Deployment: return true, fn(&t.Spec.Template.Spec) - case *apps.StatefulSet: + case *appsv1.Deployment: return true, fn(&t.Spec.Template.Spec) - case *batch.Job: + // DaemonSet + case *extensionsv1beta1.DaemonSet: return true, fn(&t.Spec.Template.Spec) + case *appsv1beta2.DaemonSet: + return true, fn(&t.Spec.Template.Spec) + case *appsv1.DaemonSet: + return true, fn(&t.Spec.Template.Spec) + // ReplicaSet + case *extensionsv1beta1.ReplicaSet: + return true, fn(&t.Spec.Template.Spec) + case *appsv1beta2.ReplicaSet: + return true, fn(&t.Spec.Template.Spec) + case *appsv1.ReplicaSet: + return true, fn(&t.Spec.Template.Spec) + // StatefulSet + case *appsv1beta1.StatefulSet: + return true, fn(&t.Spec.Template.Spec) + case *appsv1beta2.StatefulSet: + return true, fn(&t.Spec.Template.Spec) + case *appsv1.StatefulSet: + return true, fn(&t.Spec.Template.Spec) + // Job + case *batchv1.Job: + return true, fn(&t.Spec.Template.Spec) + // CronJob + case *batchv1beta1.CronJob: + return true, fn(&t.Spec.JobTemplate.Spec.Template.Spec) + case *batchv2alpha1.CronJob: + return true, fn(&t.Spec.JobTemplate.Spec.Template.Spec) default: return false, fmt.Errorf("the object is not a pod or does not have a pod template") } @@ -289,11 +316,7 @@ func (f *ring0Factory) MapBasedSelectorForObject(object runtime.Object) (string, } return kubectl.MakeLabels(t.Spec.Selector.MatchLabels), nil default: - gvks, _, err := api.Scheme.ObjectKinds(object) - if err != nil { - return "", err - } - return "", fmt.Errorf("cannot extract pod selector from %v", gvks[0]) + return "", fmt.Errorf("cannot extract pod selector from %T", object) } } @@ -311,11 +334,7 @@ func (f *ring0Factory) PortsForObject(object runtime.Object) ([]string, error) { case *extensions.ReplicaSet: return getPorts(t.Spec.Template.Spec), nil default: - gvks, _, err := api.Scheme.ObjectKinds(object) - if err != nil { - return nil, err - } - return nil, fmt.Errorf("cannot extract ports from %v", gvks[0]) + return nil, fmt.Errorf("cannot extract ports from %T", object) } } @@ -333,11 +352,7 @@ func (f *ring0Factory) ProtocolsForObject(object runtime.Object) (map[string]str case *extensions.ReplicaSet: return getProtocols(t.Spec.Template.Spec), nil default: - gvks, _, err := api.Scheme.ObjectKinds(object) - if err != nil { - return nil, err - } - return nil, fmt.Errorf("cannot extract protocols from %v", gvks[0]) + return nil, fmt.Errorf("cannot extract protocols from %T", object) } } @@ -402,24 +417,6 @@ func (f *ring0Factory) BindExternalFlags(flags *pflag.FlagSet) { flags.AddGoFlagSet(flag.CommandLine) } -func (f *ring0Factory) DefaultResourceFilterOptions(cmd *cobra.Command, withNamespace bool) *printers.PrintOptions { - columnLabel, err := cmd.Flags().GetStringSlice("label-columns") - if err != nil { - columnLabel = []string{} - } - opts := &printers.PrintOptions{ - NoHeaders: GetFlagBool(cmd, "no-headers"), - WithNamespace: withNamespace, - Wide: GetWideFlag(cmd), - ShowAll: GetFlagBool(cmd, "show-all"), - ShowLabels: GetFlagBool(cmd, "show-labels"), - AbsoluteTimestamps: isWatch(cmd), - ColumnLabels: columnLabel, - } - - return opts -} - func (f *ring0Factory) DefaultResourceFilterFunc() kubectl.Filters { return kubectl.NewResourceFilter() } @@ -506,6 +503,7 @@ const ( ClusterV1Beta1GeneratorName = "cluster/v1beta1" PodDisruptionBudgetV1GeneratorName = "poddisruptionbudget/v1beta1" PodDisruptionBudgetV2GeneratorName = "poddisruptionbudget/v1beta1/v2" + PriorityClassV1Alpha1GeneratorName = "priorityclass/v1alpha1" ) // DefaultGenerators returns the set of default generators for use in Factory instances @@ -546,10 +544,6 @@ func DefaultGenerators(cmdName string) map[string]kubectl.Generator { CronJobV2Alpha1GeneratorName: kubectl.CronJobV2Alpha1{}, CronJobV1Beta1GeneratorName: kubectl.CronJobV1Beta1{}, } - case "autoscale": - generator = map[string]kubectl.Generator{ - HorizontalPodAutoscalerV1GeneratorName: kubectl.HorizontalPodAutoscalerV1{}, - } case "namespace": generator = map[string]kubectl.Generator{ NamespaceV1GeneratorName: kubectl.NamespaceGeneratorV1{}, diff --git a/pkg/kubectl/cmd/util/factory_object_mapping.go b/pkg/kubectl/cmd/util/factory_object_mapping.go index 7ebd2740b10..dfd82d406c5 100644 --- a/pkg/kubectl/cmd/util/factory_object_mapping.go +++ b/pkg/kubectl/cmd/util/factory_object_mapping.go @@ -27,6 +27,8 @@ import ( "sync" "time" + "github.com/golang/glog" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -35,14 +37,15 @@ import ( "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" restclient "k8s.io/client-go/rest" - "k8s.io/kubernetes/federation/apis/federation" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/batch" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/categories" "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" openapivalidation "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/validation" "k8s.io/kubernetes/pkg/kubectl/resource" @@ -70,60 +73,51 @@ func NewObjectMappingFactory(clientAccessFactory ClientAccessFactory) ObjectMapp return f } -// TODO: This method should return an error now that it can fail. Alternatively, it needs to -// return lazy implementations of mapper and typer that don't hit the wire until they are -// invoked. -func (f *ring1Factory) Object() (meta.RESTMapper, runtime.ObjectTyper) { - mapper := api.Registry.RESTMapper() - discoveryClient, err := f.clientAccessFactory.DiscoveryClient() - if err == nil { - mapper = meta.FirstHitRESTMapper{ - MultiRESTMapper: meta.MultiRESTMapper{ - discovery.NewDeferredDiscoveryRESTMapper(discoveryClient, api.Registry.InterfacesFor), - api.Registry.RESTMapper(), // hardcoded fall back - }, - } - - // wrap with shortcuts, they require a discoveryClient - mapper, err = NewShortcutExpander(mapper, discoveryClient) - // you only have an error on missing discoveryClient, so this shouldn't fail. Check anyway. - CheckErr(err) - } - - return mapper, api.Scheme -} - -func (f *ring1Factory) UnstructuredObject() (meta.RESTMapper, runtime.ObjectTyper, error) { +// objectLoader attempts to perform discovery against the server, and will fall back to +// the built in mapper if necessary. It supports unstructured objects either way, since +// the underlying Scheme supports Unstructured. The mapper will return converters that can +// convert versioned types to unstructured and back. +func (f *ring1Factory) objectLoader() (meta.RESTMapper, runtime.ObjectTyper, error) { discoveryClient, err := f.clientAccessFactory.DiscoveryClient() if err != nil { - return nil, nil, err + glog.V(3).Infof("Unable to get a discovery client to find server resources, falling back to hardcoded types: %v", err) + return legacyscheme.Registry.RESTMapper(), legacyscheme.Scheme, nil } + groupResources, err := discovery.GetAPIGroupResources(discoveryClient) if err != nil && !discoveryClient.Fresh() { discoveryClient.Invalidate() groupResources, err = discovery.GetAPIGroupResources(discoveryClient) } if err != nil { - return nil, nil, err + glog.V(3).Infof("Unable to retrieve API resources, falling back to hardcoded types: %v", err) + return legacyscheme.Registry.RESTMapper(), legacyscheme.Scheme, nil } - mapper := discovery.NewDeferredDiscoveryRESTMapper(discoveryClient, meta.InterfacesForUnstructured) - typer := discovery.NewUnstructuredObjectTyper(groupResources) - expander, err := NewShortcutExpander(mapper, discoveryClient) + // allow conversion between typed and unstructured objects + interfaces := meta.InterfacesForUnstructuredConversion(legacyscheme.Registry.InterfacesFor) + mapper := discovery.NewDeferredDiscoveryRESTMapper(discoveryClient, meta.VersionInterfacesFunc(interfaces)) + // TODO: should this also indicate it recognizes typed objects? + typer := discovery.NewUnstructuredObjectTyper(groupResources, legacyscheme.Scheme) + expander := NewShortcutExpander(mapper, discoveryClient) return expander, typer, err } -func (f *ring1Factory) CategoryExpander() resource.CategoryExpander { - legacyExpander := resource.LegacyCategoryExpander +func (f *ring1Factory) Object() (meta.RESTMapper, runtime.ObjectTyper) { + return meta.NewLazyObjectLoader(f.objectLoader) +} + +func (f *ring1Factory) CategoryExpander() categories.CategoryExpander { + legacyExpander := categories.LegacyCategoryExpander discoveryClient, err := f.clientAccessFactory.DiscoveryClient() if err == nil { // fallback is the legacy expander wrapped with discovery based filtering - fallbackExpander, err := resource.NewDiscoveryFilteredExpander(legacyExpander, discoveryClient) + fallbackExpander, err := categories.NewDiscoveryFilteredExpander(legacyExpander, discoveryClient) CheckErr(err) // by default use the expander that discovers based on "categories" field from the API - discoveryCategoryExpander, err := resource.NewDiscoveryCategoryExpander(fallbackExpander, discoveryClient) + discoveryCategoryExpander, err := categories.NewDiscoveryCategoryExpander(fallbackExpander, discoveryClient) CheckErr(err) return discoveryCategoryExpander @@ -142,9 +136,6 @@ func (f *ring1Factory) ClientForMapping(mapping *meta.RESTMapping) (resource.RES } gvk := mapping.GroupVersionKind switch gvk.Group { - case federation.GroupName: - mappingVersion := mapping.GroupVersionKind.GroupVersion() - return f.clientAccessFactory.FederationClientForVersion(&mappingVersion) case api.GroupName: cfg.APIPath = "/api" default: @@ -175,15 +166,6 @@ func (f *ring1Factory) UnstructuredClientForMapping(mapping *meta.RESTMapping) ( func (f *ring1Factory) Describer(mapping *meta.RESTMapping) (printers.Describer, error) { mappingVersion := mapping.GroupVersionKind.GroupVersion() - if mapping.GroupVersionKind.Group == federation.GroupName { - fedClientSet, err := f.clientAccessFactory.FederationClientSetForVersion(&mappingVersion) - if err != nil { - return nil, err - } - if mapping.GroupVersionKind.Kind == "Cluster" { - return &printersinternal.ClusterDescriber{Interface: fedClientSet}, nil - } - } clientset, err := f.clientAccessFactory.ClientSetForVersion(&mappingVersion) if err != nil { @@ -284,11 +266,7 @@ func (f *ring1Factory) LogsForObject(object, options runtime.Object, timeout tim } default: - gvks, _, err := api.Scheme.ObjectKinds(object) - if err != nil { - return nil, err - } - return nil, fmt.Errorf("cannot get the logs from %v", gvks[0]) + return nil, fmt.Errorf("cannot get the logs from %T", object) } sortBy := func(pods []*v1.Pod) sort.Interface { return controller.ByLogging(pods) } @@ -326,26 +304,23 @@ func (f *ring1Factory) Reaper(mapping *meta.RESTMapping) (kubectl.Reaper, error) } func (f *ring1Factory) HistoryViewer(mapping *meta.RESTMapping) (kubectl.HistoryViewer, error) { - mappingVersion := mapping.GroupVersionKind.GroupVersion() - clientset, err := f.clientAccessFactory.ClientSetForVersion(&mappingVersion) + external, err := f.clientAccessFactory.KubernetesClientSet() if err != nil { return nil, err } - return kubectl.HistoryViewerFor(mapping.GroupVersionKind.GroupKind(), clientset) + return kubectl.HistoryViewerFor(mapping.GroupVersionKind.GroupKind(), external) } func (f *ring1Factory) Rollbacker(mapping *meta.RESTMapping) (kubectl.Rollbacker, error) { - mappingVersion := mapping.GroupVersionKind.GroupVersion() - clientset, err := f.clientAccessFactory.ClientSetForVersion(&mappingVersion) + external, err := f.clientAccessFactory.KubernetesClientSet() if err != nil { return nil, err } - return kubectl.RollbackerFor(mapping.GroupVersionKind.GroupKind(), clientset) + return kubectl.RollbackerFor(mapping.GroupVersionKind.GroupKind(), external) } func (f *ring1Factory) StatusViewer(mapping *meta.RESTMapping) (kubectl.StatusViewer, error) { - mappingVersion := mapping.GroupVersionKind.GroupVersion() - clientset, err := f.clientAccessFactory.ClientSetForVersion(&mappingVersion) + clientset, err := f.clientAccessFactory.KubernetesClientSet() if err != nil { return nil, err } @@ -415,11 +390,7 @@ func (f *ring1Factory) AttachablePodForObject(object runtime.Object, timeout tim return t, nil default: - gvks, _, err := api.Scheme.ObjectKinds(object) - if err != nil { - return nil, err - } - return nil, fmt.Errorf("cannot attach to %v: not implemented", gvks[0]) + return nil, fmt.Errorf("cannot attach to %T: not implemented", object) } sortBy := func(pods []*v1.Pod) sort.Interface { return sort.Reverse(controller.ActivePods(pods)) } diff --git a/pkg/kubectl/cmd/util/factory_object_mapping_test.go b/pkg/kubectl/cmd/util/factory_object_mapping_test.go index 80622d793b5..3da414c6ec6 100644 --- a/pkg/kubectl/cmd/util/factory_object_mapping_test.go +++ b/pkg/kubectl/cmd/util/factory_object_mapping_test.go @@ -26,9 +26,9 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/diff" testclient "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/batch" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" diff --git a/pkg/kubectl/cmd/util/factory_test.go b/pkg/kubectl/cmd/util/factory_test.go index 37bc6ee1a18..6acfe4c005c 100644 --- a/pkg/kubectl/cmd/util/factory_test.go +++ b/pkg/kubectl/cmd/util/factory_test.go @@ -38,11 +38,13 @@ import ( testcore "k8s.io/client-go/testing" "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/testapi" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/categories" "k8s.io/kubernetes/pkg/kubectl/resource" ) @@ -537,11 +539,17 @@ func TestDiscoveryReplaceAliases(t *testing.T) { } ds := &fakeDiscoveryClient{} - mapper, err := NewShortcutExpander(testapi.Default.RESTMapper(), ds) - if err != nil { - t.Fatalf("Unable to create shortcut expander, err = %s", err.Error()) - } - b := resource.NewBuilder(mapper, resource.LegacyCategoryExpander, api.Scheme, fakeClient(), testapi.Default.Codec()) + mapper := NewShortcutExpander(testapi.Default.RESTMapper(), ds) + b := resource.NewBuilder( + &resource.Mapper{ + RESTMapper: mapper, + ObjectTyper: legacyscheme.Scheme, + ClientMapper: fakeClient(), + Decoder: testapi.Default.Codec(), + }, + nil, + categories.LegacyCategoryExpander, + ) for _, test := range tests { replaced := b.ReplaceAliases(test.arg) diff --git a/pkg/kubectl/cmd/util/helpers.go b/pkg/kubectl/cmd/util/helpers.go index 16ba707cd92..b982d86b873 100644 --- a/pkg/kubectl/cmd/util/helpers.go +++ b/pkg/kubectl/cmd/util/helpers.go @@ -27,7 +27,7 @@ import ( "strings" "time" - jsonpatch "github.com/evanphx/json-patch" + "github.com/evanphx/json-patch" "github.com/golang/glog" "github.com/spf13/cobra" @@ -43,7 +43,7 @@ import ( "k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/client-go/tools/clientcmd" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/printers" @@ -362,6 +362,15 @@ func GetFlagInt(cmd *cobra.Command, flag string) int { return i } +// Assumes the flag has a default value. +func GetFlagInt32(cmd *cobra.Command, flag string) int32 { + i, err := cmd.Flags().GetInt32(flag) + if err != nil { + glog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err) + } + return i +} + // Assumes the flag has a default value. func GetFlagInt64(cmd *cobra.Command, flag string) int64 { i, err := cmd.Flags().GetInt64(flag) @@ -687,7 +696,7 @@ func FilterResourceList(obj runtime.Object, filterFuncs kubectl.Filters, filterO if err != nil { return 0, []runtime.Object{obj}, utilerrors.NewAggregate([]error{err}) } - if errs := runtime.DecodeList(items, api.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme); len(errs) > 0 { + if errs := runtime.DecodeList(items, legacyscheme.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme); len(errs) > 0 { return 0, []runtime.Object{obj}, utilerrors.NewAggregate(errs) } diff --git a/pkg/kubectl/cmd/util/helpers_test.go b/pkg/kubectl/cmd/util/helpers_test.go index 9b5b86c4418..db3d09ae392 100644 --- a/pkg/kubectl/cmd/util/helpers_test.go +++ b/pkg/kubectl/cmd/util/helpers_test.go @@ -32,9 +32,10 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/testapi" apitesting "k8s.io/kubernetes/pkg/api/testing" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/utils/exec" ) @@ -52,7 +53,7 @@ func TestMerge(t *testing.T) { Name: "foo", }, }, - fragment: fmt.Sprintf(`{ "apiVersion": "%s" }`, api.Registry.GroupOrDie(api.GroupName).GroupVersion.String()), + fragment: fmt.Sprintf(`{ "apiVersion": "%s" }`, legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.String()), expected: &api.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -80,7 +81,7 @@ func TestMerge(t *testing.T) { }, }, }, - fragment: fmt.Sprintf(`{ "apiVersion": "%s", "spec": { "containers": [ { "name": "c1", "image": "green-image" } ] } }`, api.Registry.GroupOrDie(api.GroupName).GroupVersion.String()), + fragment: fmt.Sprintf(`{ "apiVersion": "%s", "spec": { "containers": [ { "name": "c1", "image": "green-image" } ] } }`, legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.String()), expected: &api.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -105,7 +106,7 @@ func TestMerge(t *testing.T) { Name: "foo", }, }, - fragment: fmt.Sprintf(`{ "apiVersion": "%s", "spec": { "volumes": [ {"name": "v1"}, {"name": "v2"} ] } }`, api.Registry.GroupOrDie(api.GroupName).GroupVersion.String()), + fragment: fmt.Sprintf(`{ "apiVersion": "%s", "spec": { "volumes": [ {"name": "v1"}, {"name": "v2"} ] } }`, legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.String()), expected: &api.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -144,7 +145,7 @@ func TestMerge(t *testing.T) { obj: &api.Service{ Spec: api.ServiceSpec{}, }, - fragment: fmt.Sprintf(`{ "apiVersion": "%s", "spec": { "ports": [ { "port": 0 } ] } }`, api.Registry.GroupOrDie(api.GroupName).GroupVersion.String()), + fragment: fmt.Sprintf(`{ "apiVersion": "%s", "spec": { "ports": [ { "port": 0 } ] } }`, legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.String()), expected: &api.Service{ Spec: api.ServiceSpec{ SessionAffinity: "None", @@ -166,7 +167,7 @@ func TestMerge(t *testing.T) { }, }, }, - fragment: fmt.Sprintf(`{ "apiVersion": "%s", "spec": { "selector": { "version": "v2" } } }`, api.Registry.GroupOrDie(api.GroupName).GroupVersion.String()), + fragment: fmt.Sprintf(`{ "apiVersion": "%s", "spec": { "selector": { "version": "v2" } } }`, legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.String()), expected: &api.Service{ Spec: api.ServiceSpec{ SessionAffinity: "None", diff --git a/pkg/kubectl/cmd/util/openapi/BUILD b/pkg/kubectl/cmd/util/openapi/BUILD index 4ffa5e23856..149567c293e 100644 --- a/pkg/kubectl/cmd/util/openapi/BUILD +++ b/pkg/kubectl/cmd/util/openapi/BUILD @@ -10,7 +10,6 @@ go_library( name = "go_default_library", srcs = [ "doc.go", - "document.go", "extensions.go", "openapi.go", "openapi_getter.go", @@ -19,9 +18,9 @@ go_library( deps = [ "//vendor/github.com/go-openapi/spec:go_default_library", "//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library", - "//vendor/gopkg.in/yaml.v2:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/client-go/discovery:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", ], ) @@ -43,6 +42,7 @@ go_test( "//vendor/github.com/onsi/ginkgo/types:go_default_library", "//vendor/github.com/onsi/gomega:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", ], ) diff --git a/pkg/kubectl/cmd/util/openapi/OWNERS b/pkg/kubectl/cmd/util/openapi/OWNERS new file mode 100644 index 00000000000..7eb2d06fe1a --- /dev/null +++ b/pkg/kubectl/cmd/util/openapi/OWNERS @@ -0,0 +1,4 @@ +approvers: +- apelisse +reviewers: +- apelisse diff --git a/pkg/kubectl/cmd/util/openapi/document.go b/pkg/kubectl/cmd/util/openapi/document.go deleted file mode 100644 index 6b2f6782b94..00000000000 --- a/pkg/kubectl/cmd/util/openapi/document.go +++ /dev/null @@ -1,330 +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 openapi - -import ( - "fmt" - "strings" - - openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2" - yaml "gopkg.in/yaml.v2" - - "k8s.io/apimachinery/pkg/runtime/schema" -) - -func newSchemaError(path *Path, format string, a ...interface{}) error { - err := fmt.Sprintf(format, a...) - if path.Len() == 0 { - return fmt.Errorf("SchemaError: %v", err) - } - return fmt.Errorf("SchemaError(%v): %v", path, err) -} - -// groupVersionKindExtensionKey is the key used to lookup the -// GroupVersionKind value for an object definition from the -// definition's "extensions" map. -const groupVersionKindExtensionKey = "x-kubernetes-group-version-kind" - -func vendorExtensionToMap(e []*openapi_v2.NamedAny) map[string]interface{} { - values := map[string]interface{}{} - - for _, na := range e { - if na.GetName() == "" || na.GetValue() == nil { - continue - } - if na.GetValue().GetYaml() == "" { - continue - } - var value interface{} - err := yaml.Unmarshal([]byte(na.GetValue().GetYaml()), &value) - if err != nil { - continue - } - - values[na.GetName()] = value - } - - return values -} - -// Get and parse GroupVersionKind from the extension. Returns empty if it doesn't have one. -func parseGroupVersionKind(s *openapi_v2.Schema) schema.GroupVersionKind { - extensionMap := vendorExtensionToMap(s.GetVendorExtension()) - - // Get the extensions - gvkExtension, ok := extensionMap[groupVersionKindExtensionKey] - if !ok { - return schema.GroupVersionKind{} - } - - // gvk extension must be a list of 1 element. - gvkList, ok := gvkExtension.([]interface{}) - if !ok { - return schema.GroupVersionKind{} - } - if len(gvkList) != 1 { - return schema.GroupVersionKind{} - - } - gvk := gvkList[0] - - // gvk extension list must be a map with group, version, and - // kind fields - gvkMap, ok := gvk.(map[interface{}]interface{}) - if !ok { - return schema.GroupVersionKind{} - } - group, ok := gvkMap["group"].(string) - if !ok { - return schema.GroupVersionKind{} - } - version, ok := gvkMap["version"].(string) - if !ok { - return schema.GroupVersionKind{} - } - kind, ok := gvkMap["kind"].(string) - if !ok { - return schema.GroupVersionKind{} - } - - return schema.GroupVersionKind{ - Group: group, - Version: version, - Kind: kind, - } -} - -// Definitions is an implementation of `Resources`. It looks for -// resources in an openapi Schema. -type Definitions struct { - models map[string]Schema - resources map[schema.GroupVersionKind]string -} - -var _ Resources = &Definitions{} - -// NewOpenAPIData creates a new `Resources` out of the openapi document. -func NewOpenAPIData(doc *openapi_v2.Document) (Resources, error) { - definitions := Definitions{ - models: map[string]Schema{}, - resources: map[schema.GroupVersionKind]string{}, - } - - // Save the list of all models first. This will allow us to - // validate that we don't have any dangling reference. - for _, namedSchema := range doc.GetDefinitions().GetAdditionalProperties() { - definitions.models[namedSchema.GetName()] = nil - } - - // Now, parse each model. We can validate that references exists. - for _, namedSchema := range doc.GetDefinitions().GetAdditionalProperties() { - path := NewPath(namedSchema.GetName()) - schema, err := definitions.ParseSchema(namedSchema.GetValue(), &path) - if err != nil { - return nil, err - } - definitions.models[namedSchema.GetName()] = schema - gvk := parseGroupVersionKind(namedSchema.GetValue()) - if len(gvk.Kind) > 0 { - definitions.resources[gvk] = namedSchema.GetName() - } - } - - return &definitions, nil -} - -// We believe the schema is a reference, verify that and returns a new -// Schema -func (d *Definitions) parseReference(s *openapi_v2.Schema, path *Path) (Schema, error) { - if len(s.GetProperties().GetAdditionalProperties()) > 0 { - return nil, newSchemaError(path, "unallowed embedded type definition") - } - if len(s.GetType().GetValue()) > 0 { - return nil, newSchemaError(path, "definition reference can't have a type") - } - - if !strings.HasPrefix(s.GetXRef(), "#/definitions/") { - return nil, newSchemaError(path, "unallowed reference to non-definition %q", s.GetXRef()) - } - reference := strings.TrimPrefix(s.GetXRef(), "#/definitions/") - if _, ok := d.models[reference]; !ok { - return nil, newSchemaError(path, "unknown model in reference: %q", reference) - } - return &Ref{ - BaseSchema: d.parseBaseSchema(s, path), - reference: reference, - definitions: d, - }, nil -} - -func (d *Definitions) parseBaseSchema(s *openapi_v2.Schema, path *Path) BaseSchema { - return BaseSchema{ - Description: s.GetDescription(), - Extensions: vendorExtensionToMap(s.GetVendorExtension()), - Path: *path, - } -} - -// We believe the schema is a map, verify and return a new schema -func (d *Definitions) parseMap(s *openapi_v2.Schema, path *Path) (Schema, error) { - if len(s.GetType().GetValue()) != 0 && s.GetType().GetValue()[0] != object { - return nil, newSchemaError(path, "invalid object type") - } - if s.GetAdditionalProperties().GetSchema() == nil { - return nil, newSchemaError(path, "invalid object doesn't have additional properties") - } - sub, err := d.ParseSchema(s.GetAdditionalProperties().GetSchema(), path) - if err != nil { - return nil, err - } - return &Map{ - BaseSchema: d.parseBaseSchema(s, path), - SubType: sub, - }, nil -} - -func (d *Definitions) parsePrimitive(s *openapi_v2.Schema, path *Path) (Schema, error) { - var t string - if len(s.GetType().GetValue()) > 1 { - return nil, newSchemaError(path, "primitive can't have more than 1 type") - } - if len(s.GetType().GetValue()) == 1 { - t = s.GetType().GetValue()[0] - } - switch t { - case String: - case Number: - case Integer: - case Boolean: - case "": // Some models are completely empty, and can be safely ignored. - // Do nothing - default: - return nil, newSchemaError(path, "Unknown primitive type: %q", t) - } - return &Primitive{ - BaseSchema: d.parseBaseSchema(s, path), - Type: t, - Format: s.GetFormat(), - }, nil -} - -func (d *Definitions) parseArray(s *openapi_v2.Schema, path *Path) (Schema, error) { - if len(s.GetType().GetValue()) != 1 { - return nil, newSchemaError(path, "array should have exactly one type") - } - if s.GetType().GetValue()[0] != array { - return nil, newSchemaError(path, `array should have type "array"`) - } - if len(s.GetItems().GetSchema()) != 1 { - return nil, newSchemaError(path, "array should have exactly one sub-item") - } - sub, err := d.ParseSchema(s.GetItems().GetSchema()[0], path) - if err != nil { - return nil, err - } - return &Array{ - BaseSchema: d.parseBaseSchema(s, path), - SubType: sub, - }, nil -} - -func (d *Definitions) parseKind(s *openapi_v2.Schema, path *Path) (Schema, error) { - if len(s.GetType().GetValue()) != 0 && s.GetType().GetValue()[0] != object { - return nil, newSchemaError(path, "invalid object type") - } - if s.GetProperties() == nil { - return nil, newSchemaError(path, "object doesn't have properties") - } - - fields := map[string]Schema{} - - for _, namedSchema := range s.GetProperties().GetAdditionalProperties() { - var err error - path := path.FieldPath(namedSchema.GetName()) - fields[namedSchema.GetName()], err = d.ParseSchema(namedSchema.GetValue(), &path) - if err != nil { - return nil, err - } - } - - return &Kind{ - BaseSchema: d.parseBaseSchema(s, path), - RequiredFields: s.GetRequired(), - Fields: fields, - }, nil -} - -// ParseSchema creates a walkable Schema from an openapi schema. While -// this function is public, it doesn't leak through the interface. -func (d *Definitions) ParseSchema(s *openapi_v2.Schema, path *Path) (Schema, error) { - if len(s.GetType().GetValue()) == 1 { - t := s.GetType().GetValue()[0] - switch t { - case object: - return d.parseMap(s, path) - case array: - return d.parseArray(s, path) - } - - } - if s.GetXRef() != "" { - return d.parseReference(s, path) - } - if s.GetProperties() != nil { - return d.parseKind(s, path) - } - return d.parsePrimitive(s, path) -} - -// LookupResource is public through the interface of Resources. It -// returns a visitable schema from the given group-version-kind. -func (d *Definitions) LookupResource(gvk schema.GroupVersionKind) Schema { - modelName, found := d.resources[gvk] - if !found { - return nil - } - model, found := d.models[modelName] - if !found { - return nil - } - return model -} - -type Ref struct { - BaseSchema - - reference string - definitions *Definitions -} - -var _ Reference = &Ref{} - -func (r *Ref) Reference() string { - return r.reference -} - -func (r *Ref) SubSchema() Schema { - return r.definitions.models[r.reference] -} - -func (r *Ref) Accept(v SchemaVisitor) { - v.VisitReference(r) -} - -func (r *Ref) GetName() string { - return fmt.Sprintf("Reference to %q", r.reference) -} diff --git a/pkg/kubectl/cmd/util/openapi/openapi.go b/pkg/kubectl/cmd/util/openapi/openapi.go index bc479f66b52..fef6a187ba6 100644 --- a/pkg/kubectl/cmd/util/openapi/openapi.go +++ b/pkg/kubectl/cmd/util/openapi/openapi.go @@ -17,236 +17,108 @@ limitations under the License. package openapi import ( - "fmt" - "sort" - "strings" + openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2" "k8s.io/apimachinery/pkg/runtime/schema" -) - -// Defines openapi types. -const ( - Integer = "integer" - Number = "number" - String = "string" - Boolean = "boolean" - - // These types are private as they should never leak, and are - // represented by actual structs. - array = "array" - object = "object" + "k8s.io/kube-openapi/pkg/util/proto" ) // Resources interface describe a resources provider, that can give you // resource based on group-version-kind. type Resources interface { - LookupResource(gvk schema.GroupVersionKind) Schema + LookupResource(gvk schema.GroupVersionKind) proto.Schema } -// SchemaVisitor is an interface that you need to implement if you want -// to "visit" an openapi schema. A dispatch on the Schema type will call -// the appropriate function based on its actual type: -// - Array is a list of one and only one given subtype -// - Map is a map of string to one and only one given subtype -// - Primitive can be string, integer, number and boolean. -// - Kind is an object with specific fields mapping to specific types. -// - Reference is a link to another definition. -type SchemaVisitor interface { - VisitArray(*Array) - VisitMap(*Map) - VisitPrimitive(*Primitive) - VisitKind(*Kind) - VisitReference(Reference) +// groupVersionKindExtensionKey is the key used to lookup the +// GroupVersionKind value for an object definition from the +// definition's "extensions" map. +const groupVersionKindExtensionKey = "x-kubernetes-group-version-kind" + +// document is an implementation of `Resources`. It looks for +// resources in an openapi Schema. +type document struct { + // Maps gvk to model name + resources map[schema.GroupVersionKind]string + models proto.Models } -// Schema is the base definition of an openapi type. -type Schema interface { - // Giving a visitor here will let you visit the actual type. - Accept(SchemaVisitor) +var _ Resources = &document{} - // Pretty print the name of the type. - GetName() string - // Describes how to access this field. - GetPath() *Path - // Describes the field. - GetDescription() string - // Returns type extensions. - GetExtensions() map[string]interface{} -} - -// Path helps us keep track of type paths -type Path struct { - parent *Path - key string -} - -func NewPath(key string) Path { - return Path{key: key} -} - -func (p *Path) Get() []string { - if p == nil { - return []string{} +func NewOpenAPIData(doc *openapi_v2.Document) (Resources, error) { + models, err := proto.NewOpenAPIData(doc) + if err != nil { + return nil, err } - if p.key == "" { - return p.parent.Get() - } - return append(p.parent.Get(), p.key) -} -func (p *Path) Len() int { - return len(p.Get()) -} - -func (p *Path) String() string { - return strings.Join(p.Get(), "") -} - -// ArrayPath appends an array index and creates a new path -func (p *Path) ArrayPath(i int) Path { - return Path{ - parent: p, - key: fmt.Sprintf("[%d]", i), - } -} - -// FieldPath appends a field name and creates a new path -func (p *Path) FieldPath(field string) Path { - return Path{ - parent: p, - key: fmt.Sprintf(".%s", field), - } -} - -// BaseSchema holds data used by each types of schema. -type BaseSchema struct { - Description string - Extensions map[string]interface{} - - Path Path -} - -func (b *BaseSchema) GetDescription() string { - return b.Description -} - -func (b *BaseSchema) GetExtensions() map[string]interface{} { - return b.Extensions -} - -func (b *BaseSchema) GetPath() *Path { - return &b.Path -} - -// Array must have all its element of the same `SubType`. -type Array struct { - BaseSchema - - SubType Schema -} - -var _ Schema = &Array{} - -func (a *Array) Accept(v SchemaVisitor) { - v.VisitArray(a) -} - -func (a *Array) GetName() string { - return fmt.Sprintf("Array of %s", a.SubType.GetName()) -} - -// Kind is a complex object. It can have multiple different -// subtypes for each field, as defined in the `Fields` field. Mandatory -// fields are listed in `RequiredFields`. The key of the object is -// always of type `string`. -type Kind struct { - BaseSchema - - // Lists names of required fields. - RequiredFields []string - // Maps field names to types. - Fields map[string]Schema -} - -var _ Schema = &Kind{} - -func (k *Kind) Accept(v SchemaVisitor) { - v.VisitKind(k) -} - -func (k *Kind) GetName() string { - properties := []string{} - for key := range k.Fields { - properties = append(properties, key) - } - return fmt.Sprintf("Kind(%v)", properties) -} - -// IsRequired returns true if `field` is a required field for this type. -func (k *Kind) IsRequired(field string) bool { - for _, f := range k.RequiredFields { - if f == field { - return true + resources := map[schema.GroupVersionKind]string{} + for _, modelName := range models.ListModels() { + model := models.LookupModel(modelName) + if model == nil { + panic("ListModels returns a model that can't be looked-up.") + } + gvk := parseGroupVersionKind(model) + if len(gvk.Kind) > 0 { + resources[gvk] = modelName } } - return false + + return &document{ + resources: resources, + models: models, + }, nil } -// Keys returns a alphabetically sorted list of keys. -func (k *Kind) Keys() []string { - keys := make([]string, 0) - for key := range k.Fields { - keys = append(keys, key) +func (d *document) LookupResource(gvk schema.GroupVersionKind) proto.Schema { + modelName, found := d.resources[gvk] + if !found { + return nil } - sort.Strings(keys) - return keys + return d.models.LookupModel(modelName) } -// Map is an object who values must all be of the same `SubType`. -// The key of the object is always of type `string`. -type Map struct { - BaseSchema +// Get and parse GroupVersionKind from the extension. Returns empty if it doesn't have one. +func parseGroupVersionKind(s proto.Schema) schema.GroupVersionKind { + extensions := s.GetExtensions() - SubType Schema -} - -var _ Schema = &Map{} - -func (m *Map) Accept(v SchemaVisitor) { - v.VisitMap(m) -} - -func (m *Map) GetName() string { - return fmt.Sprintf("Map of %s", m.SubType.GetName()) -} - -// Primitive is a literal. There can be multiple types of primitives, -// and this subtype can be visited through the `subType` field. -type Primitive struct { - BaseSchema - - // Type of a primitive must be one of: integer, number, string, boolean. - Type string - Format string -} - -var _ Schema = &Primitive{} - -func (p *Primitive) Accept(v SchemaVisitor) { - v.VisitPrimitive(p) -} - -func (p *Primitive) GetName() string { - if p.Format == "" { - return p.Type + // Get the extensions + gvkExtension, ok := extensions[groupVersionKindExtensionKey] + if !ok { + return schema.GroupVersionKind{} } - return fmt.Sprintf("%s (%s)", p.Type, p.Format) -} -// Reference implementation depends on the type of document. -type Reference interface { - Schema + // gvk extension must be a list of 1 element. + gvkList, ok := gvkExtension.([]interface{}) + if !ok { + return schema.GroupVersionKind{} + } + if len(gvkList) != 1 { + return schema.GroupVersionKind{} - Reference() string - SubSchema() Schema + } + gvk := gvkList[0] + + // gvk extension list must be a map with group, version, and + // kind fields + gvkMap, ok := gvk.(map[interface{}]interface{}) + if !ok { + return schema.GroupVersionKind{} + } + group, ok := gvkMap["group"].(string) + if !ok { + return schema.GroupVersionKind{} + } + version, ok := gvkMap["version"].(string) + if !ok { + return schema.GroupVersionKind{} + } + kind, ok := gvkMap["kind"].(string) + if !ok { + return schema.GroupVersionKind{} + } + + return schema.GroupVersionKind{ + Group: group, + Version: version, + Kind: kind, + } } diff --git a/pkg/kubectl/cmd/util/openapi/openapi_test.go b/pkg/kubectl/cmd/util/openapi/openapi_test.go index 93736c93ca7..5ee7302f1bc 100644 --- a/pkg/kubectl/cmd/util/openapi/openapi_test.go +++ b/pkg/kubectl/cmd/util/openapi/openapi_test.go @@ -23,6 +23,7 @@ import ( . "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kube-openapi/pkg/util/proto" "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" tst "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing" ) @@ -44,98 +45,17 @@ var _ = Describe("Reading apps/v1beta1/Deployment from openAPIData", func() { Group: "apps", } - var schema openapi.Schema + var schema proto.Schema It("should lookup the Schema by its GroupVersionKind", func() { schema = resources.LookupResource(gvk) Expect(schema).ToNot(BeNil()) }) - var deployment *openapi.Kind + var deployment *proto.Kind It("should be a Kind", func() { - deployment = schema.(*openapi.Kind) + deployment = schema.(*proto.Kind) Expect(deployment).ToNot(BeNil()) }) - - It("should have a path", func() { - Expect(deployment.GetPath().Get()).To(Equal([]string{"io.k8s.api.apps.v1beta1.Deployment"})) - }) - - It("should have a kind key of type string", func() { - Expect(deployment.Fields).To(HaveKey("kind")) - key := deployment.Fields["kind"].(*openapi.Primitive) - Expect(key).ToNot(BeNil()) - Expect(key.Type).To(Equal("string")) - Expect(key.GetPath().Get()).To(Equal([]string{"io.k8s.api.apps.v1beta1.Deployment", ".kind"})) - }) - - It("should have a apiVersion key of type string", func() { - Expect(deployment.Fields).To(HaveKey("apiVersion")) - key := deployment.Fields["apiVersion"].(*openapi.Primitive) - Expect(key).ToNot(BeNil()) - Expect(key.Type).To(Equal("string")) - Expect(key.GetPath().Get()).To(Equal([]string{"io.k8s.api.apps.v1beta1.Deployment", ".apiVersion"})) - }) - - It("should have a metadata key of type Reference", func() { - Expect(deployment.Fields).To(HaveKey("metadata")) - key := deployment.Fields["metadata"].(openapi.Reference) - Expect(key).ToNot(BeNil()) - Expect(key.Reference()).To(Equal("io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta")) - subSchema := key.SubSchema().(*openapi.Kind) - Expect(subSchema).ToNot(BeNil()) - }) - - var status *openapi.Kind - It("should have a status key of type Reference", func() { - Expect(deployment.Fields).To(HaveKey("status")) - key := deployment.Fields["status"].(openapi.Reference) - Expect(key).ToNot(BeNil()) - Expect(key.Reference()).To(Equal("io.k8s.api.apps.v1beta1.DeploymentStatus")) - status = key.SubSchema().(*openapi.Kind) - Expect(status).ToNot(BeNil()) - }) - - It("should have a valid DeploymentStatus", func() { - By("having availableReplicas key") - Expect(status.Fields).To(HaveKey("availableReplicas")) - replicas := status.Fields["availableReplicas"].(*openapi.Primitive) - Expect(replicas).ToNot(BeNil()) - Expect(replicas.Type).To(Equal("integer")) - - By("having conditions key") - Expect(status.Fields).To(HaveKey("conditions")) - conditions := status.Fields["conditions"].(*openapi.Array) - Expect(conditions).ToNot(BeNil()) - Expect(conditions.GetName()).To(Equal(`Array of Reference to "io.k8s.api.apps.v1beta1.DeploymentCondition"`)) - Expect(conditions.GetExtensions()).To(Equal(map[string]interface{}{ - "x-kubernetes-patch-merge-key": "type", - "x-kubernetes-patch-strategy": "merge", - })) - condition := conditions.SubType.(openapi.Reference) - Expect(condition.Reference()).To(Equal("io.k8s.api.apps.v1beta1.DeploymentCondition")) - }) - - var spec *openapi.Kind - It("should have a spec key of type Reference", func() { - Expect(deployment.Fields).To(HaveKey("spec")) - key := deployment.Fields["spec"].(openapi.Reference) - Expect(key).ToNot(BeNil()) - Expect(key.Reference()).To(Equal("io.k8s.api.apps.v1beta1.DeploymentSpec")) - spec = key.SubSchema().(*openapi.Kind) - Expect(spec).ToNot(BeNil()) - }) - - It("should have a spec with no gvk", func() { - _, found := spec.GetExtensions()["x-kubernetes-group-version-kind"] - Expect(found).To(BeFalse()) - }) - - It("should have a spec with a PodTemplateSpec sub-field", func() { - Expect(spec.Fields).To(HaveKey("template")) - key := spec.Fields["template"].(openapi.Reference) - Expect(key).ToNot(BeNil()) - Expect(key.Reference()).To(Equal("io.k8s.api.core.v1.PodTemplateSpec")) - }) }) var _ = Describe("Reading authorization.k8s.io/v1/SubjectAccessReview from openAPIData", func() { @@ -153,66 +73,21 @@ var _ = Describe("Reading authorization.k8s.io/v1/SubjectAccessReview from openA Group: "authorization.k8s.io", } - var schema openapi.Schema + var schema proto.Schema It("should lookup the Schema by its GroupVersionKind", func() { schema = resources.LookupResource(gvk) Expect(schema).ToNot(BeNil()) }) - var sarspec *openapi.Kind + var sarspec *proto.Kind It("should be a Kind and have a spec", func() { - sar := schema.(*openapi.Kind) + sar := schema.(*proto.Kind) Expect(sar).ToNot(BeNil()) Expect(sar.Fields).To(HaveKey("spec")) - specRef := sar.Fields["spec"].(openapi.Reference) + specRef := sar.Fields["spec"].(proto.Reference) Expect(specRef).ToNot(BeNil()) Expect(specRef.Reference()).To(Equal("io.k8s.api.authorization.v1.SubjectAccessReviewSpec")) - sarspec = specRef.SubSchema().(*openapi.Kind) + sarspec = specRef.SubSchema().(*proto.Kind) Expect(sarspec).ToNot(BeNil()) }) - - It("should have a valid SubjectAccessReviewSpec", func() { - Expect(sarspec.Fields).To(HaveKey("extra")) - extra := sarspec.Fields["extra"].(*openapi.Map) - Expect(extra).ToNot(BeNil()) - Expect(extra.GetName()).To(Equal("Map of Array of string")) - Expect(extra.GetPath().Get()).To(Equal([]string{"io.k8s.api.authorization.v1.SubjectAccessReviewSpec", ".extra"})) - array := extra.SubType.(*openapi.Array) - Expect(array).ToNot(BeNil()) - Expect(array.GetName()).To(Equal("Array of string")) - Expect(array.GetPath().Get()).To(Equal([]string{"io.k8s.api.authorization.v1.SubjectAccessReviewSpec", ".extra"})) - str := array.SubType.(*openapi.Primitive) - Expect(str).ToNot(BeNil()) - Expect(str.Type).To(Equal("string")) - Expect(str.GetName()).To(Equal("string")) - Expect(str.GetPath().Get()).To(Equal([]string{"io.k8s.api.authorization.v1.SubjectAccessReviewSpec", ".extra"})) - }) -}) - -var _ = Describe("Path", func() { - It("can be created by NewPath", func() { - path := openapi.NewPath("key") - Expect(path.String()).To(Equal("key")) - }) - It("can create and print complex paths", func() { - key := openapi.NewPath("key") - array := key.ArrayPath(12) - field := array.FieldPath("subKey") - - Expect(field.String()).To(Equal("key[12].subKey")) - }) - It("has a length", func() { - key := openapi.NewPath("key") - array := key.ArrayPath(12) - field := array.FieldPath("subKey") - - Expect(field.Len()).To(Equal(3)) - }) - It("can look like an array", func() { - key := openapi.NewPath("key") - array := key.ArrayPath(12) - field := array.FieldPath("subKey") - - Expect(field.Get()).To(Equal([]string{"key", "[12]", ".subKey"})) - }) }) diff --git a/pkg/kubectl/cmd/util/openapi/testing/BUILD b/pkg/kubectl/cmd/util/openapi/testing/BUILD index dbe051cacf2..26a7b346737 100644 --- a/pkg/kubectl/cmd/util/openapi/testing/BUILD +++ b/pkg/kubectl/cmd/util/openapi/testing/BUILD @@ -15,6 +15,7 @@ go_library( "//vendor/github.com/googleapis/gnostic/compiler:go_default_library", "//vendor/gopkg.in/yaml.v2:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", ], ) diff --git a/pkg/kubectl/cmd/util/openapi/testing/openapi.go b/pkg/kubectl/cmd/util/openapi/testing/openapi.go index e08f1defcd0..1ccf47c25a5 100644 --- a/pkg/kubectl/cmd/util/openapi/testing/openapi.go +++ b/pkg/kubectl/cmd/util/openapi/testing/openapi.go @@ -22,6 +22,7 @@ import ( "sync" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kube-openapi/pkg/util/proto" "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" yaml "gopkg.in/yaml.v2" @@ -109,7 +110,7 @@ func NewFakeResources(path string) *FakeResources { // LookupResource will read the schema, parse it and return the // resources. It doesn't return errors and will panic instead. -func (f *FakeResources) LookupResource(gvk schema.GroupVersionKind) openapi.Schema { +func (f *FakeResources) LookupResource(gvk schema.GroupVersionKind) proto.Schema { s, err := f.fake.OpenAPISchema() if err != nil { panic(err) @@ -120,3 +121,20 @@ func (f *FakeResources) LookupResource(gvk schema.GroupVersionKind) openapi.Sche } return resources.LookupResource(gvk) } + +// EmptyResources implement a Resources that just doesn't have any resources. +type EmptyResources struct{} + +var _ openapi.Resources = EmptyResources{} + +// LookupResource will always return nil. It doesn't have any resources. +func (f EmptyResources) LookupResource(gvk schema.GroupVersionKind) proto.Schema { + return nil +} + +// CreateOpenAPISchemaFunc returns a function useful for the TestFactory. +func CreateOpenAPISchemaFunc(path string) func() (openapi.Resources, error) { + return func() (openapi.Resources, error) { + return NewFakeResources(path), nil + } +} diff --git a/pkg/kubectl/cmd/util/openapi/validation/BUILD b/pkg/kubectl/cmd/util/openapi/validation/BUILD index cb52520d38c..c0a473ebff4 100644 --- a/pkg/kubectl/cmd/util/openapi/validation/BUILD +++ b/pkg/kubectl/cmd/util/openapi/validation/BUILD @@ -8,11 +8,7 @@ load( go_library( name = "go_default_library", - srcs = [ - "errors.go", - "types.go", - "validation.go", - ], + srcs = ["validation.go"], importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/validation", deps = [ "//pkg/kubectl/cmd/util/openapi:go_default_library", @@ -20,19 +16,20 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/util/proto/validation:go_default_library", ], ) go_test( - name = "go_default_xtest", + name = "go_default_test", srcs = [ "validation_suite_test.go", "validation_test.go", ], data = ["//api/openapi-spec:swagger-spec"], - importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/validation_test", + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/validation", deps = [ - ":go_default_library", "//pkg/api/testapi:go_default_library", "//pkg/kubectl/cmd/util/openapi:go_default_library", "//pkg/kubectl/cmd/util/openapi/testing:go_default_library", @@ -41,6 +38,7 @@ go_test( "//vendor/github.com/onsi/ginkgo/types:go_default_library", "//vendor/github.com/onsi/gomega:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/util/proto/validation:go_default_library", ], ) diff --git a/pkg/kubectl/cmd/util/openapi/validation/errors.go b/pkg/kubectl/cmd/util/openapi/validation/errors.go deleted file mode 100644 index 4f4ab9e3bd1..00000000000 --- a/pkg/kubectl/cmd/util/openapi/validation/errors.go +++ /dev/null @@ -1,79 +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 validation - -import ( - "fmt" -) - -type Errors struct { - errors []error -} - -func (e *Errors) Errors() []error { - return e.errors -} - -func (e *Errors) AppendErrors(err ...error) { - e.errors = append(e.errors, err...) -} - -type ValidationError struct { - Path string - Err error -} - -func (e ValidationError) Error() string { - return fmt.Sprintf("ValidationError(%s): %v", e.Path, e.Err) -} - -type InvalidTypeError struct { - Path string - Expected string - Actual string -} - -func (e InvalidTypeError) Error() string { - return fmt.Sprintf("invalid type for %s: got %q, expected %q", e.Path, e.Actual, e.Expected) -} - -type MissingRequiredFieldError struct { - Path string - Field string -} - -func (e MissingRequiredFieldError) Error() string { - return fmt.Sprintf("missing required field %q in %s", e.Field, e.Path) -} - -type UnknownFieldError struct { - Path string - Field string -} - -func (e UnknownFieldError) Error() string { - return fmt.Sprintf("unknown field %q in %s", e.Field, e.Path) -} - -type InvalidObjectTypeError struct { - Path string - Type string -} - -func (e InvalidObjectTypeError) Error() string { - return fmt.Sprintf("unknown object type %q in %s", e.Type, e.Path) -} diff --git a/pkg/kubectl/cmd/util/openapi/validation/types.go b/pkg/kubectl/cmd/util/openapi/validation/types.go deleted file mode 100644 index 2c16c2b16fc..00000000000 --- a/pkg/kubectl/cmd/util/openapi/validation/types.go +++ /dev/null @@ -1,289 +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 validation - -import ( - "reflect" - "sort" - - "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" -) - -type ValidationItem interface { - openapi.SchemaVisitor - - Errors() []error - Path() *openapi.Path -} - -type baseItem struct { - errors Errors - path openapi.Path -} - -// Errors returns the list of errors found for this item. -func (item *baseItem) Errors() []error { - return item.errors.Errors() -} - -// AddValidationError wraps the given error into a ValidationError and -// attaches it to this item. -func (item *baseItem) AddValidationError(err error) { - item.errors.AppendErrors(ValidationError{Path: item.path.String(), Err: err}) -} - -// AddError adds a regular (non-validation related) error to the list. -func (item *baseItem) AddError(err error) { - item.errors.AppendErrors(err) -} - -// CopyErrors adds a list of errors to this item. This is useful to copy -// errors from subitems. -func (item *baseItem) CopyErrors(errs []error) { - item.errors.AppendErrors(errs...) -} - -// Path returns the path of this item, helps print useful errors. -func (item *baseItem) Path() *openapi.Path { - return &item.path -} - -// mapItem represents a map entry in the yaml. -type mapItem struct { - baseItem - - Map map[string]interface{} -} - -func (item *mapItem) sortedKeys() []string { - sortedKeys := []string{} - for key := range item.Map { - sortedKeys = append(sortedKeys, key) - } - sort.Strings(sortedKeys) - return sortedKeys -} - -var _ ValidationItem = &mapItem{} - -func (item *mapItem) VisitPrimitive(schema *openapi.Primitive) { - item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: schema.Type, Actual: "map"}) -} - -func (item *mapItem) VisitArray(schema *openapi.Array) { - item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "array", Actual: "map"}) -} - -func (item *mapItem) VisitMap(schema *openapi.Map) { - for _, key := range item.sortedKeys() { - subItem, err := itemFactory(item.Path().FieldPath(key), item.Map[key]) - if err != nil { - item.AddError(err) - continue - } - schema.SubType.Accept(subItem) - item.CopyErrors(subItem.Errors()) - } -} - -func (item *mapItem) VisitKind(schema *openapi.Kind) { - // Verify each sub-field. - for _, key := range item.sortedKeys() { - if item.Map[key] == nil { - continue - } - subItem, err := itemFactory(item.Path().FieldPath(key), item.Map[key]) - if err != nil { - item.AddError(err) - continue - } - if _, ok := schema.Fields[key]; !ok { - item.AddValidationError(UnknownFieldError{Path: schema.GetPath().String(), Field: key}) - continue - } - schema.Fields[key].Accept(subItem) - item.CopyErrors(subItem.Errors()) - } - - // Verify that all required fields are present. - for _, required := range schema.RequiredFields { - if v, ok := item.Map[required]; !ok || v == nil { - item.AddValidationError(MissingRequiredFieldError{Path: schema.GetPath().String(), Field: required}) - } - } -} - -func (item *mapItem) VisitReference(schema openapi.Reference) { - // passthrough - schema.SubSchema().Accept(item) -} - -// arrayItem represents a yaml array. -type arrayItem struct { - baseItem - - Array []interface{} -} - -var _ ValidationItem = &arrayItem{} - -func (item *arrayItem) VisitPrimitive(schema *openapi.Primitive) { - item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: schema.Type, Actual: "array"}) -} - -func (item *arrayItem) VisitArray(schema *openapi.Array) { - for i, v := range item.Array { - path := item.Path().ArrayPath(i) - if v == nil { - item.AddValidationError(InvalidObjectTypeError{Type: "nil", Path: path.String()}) - continue - } - subItem, err := itemFactory(path, v) - if err != nil { - item.AddError(err) - continue - } - schema.SubType.Accept(subItem) - item.CopyErrors(subItem.Errors()) - } -} - -func (item *arrayItem) VisitMap(schema *openapi.Map) { - item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "array", Actual: "map"}) -} - -func (item *arrayItem) VisitKind(schema *openapi.Kind) { - item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "array", Actual: "map"}) -} - -func (item *arrayItem) VisitReference(schema openapi.Reference) { - // passthrough - schema.SubSchema().Accept(item) -} - -// primitiveItem represents a yaml value. -type primitiveItem struct { - baseItem - - Value interface{} - Kind string -} - -var _ ValidationItem = &primitiveItem{} - -func (item *primitiveItem) VisitPrimitive(schema *openapi.Primitive) { - // Some types of primitives can match more than one (a number - // can be a string, but not the other way around). Return from - // the switch if we have a valid possible type conversion - // NOTE(apelisse): This logic is blindly copied from the - // existing swagger logic, and I'm not sure I agree with it. - switch schema.Type { - case openapi.Boolean: - switch item.Kind { - case openapi.Boolean: - return - } - case openapi.Integer: - switch item.Kind { - case openapi.Integer, openapi.Number: - return - } - case openapi.Number: - switch item.Kind { - case openapi.Number: - return - } - case openapi.String: - return - } - - item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: schema.Type, Actual: item.Kind}) -} - -func (item *primitiveItem) VisitArray(schema *openapi.Array) { - item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "array", Actual: item.Kind}) -} - -func (item *primitiveItem) VisitMap(schema *openapi.Map) { - item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "map", Actual: item.Kind}) -} - -func (item *primitiveItem) VisitKind(schema *openapi.Kind) { - item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "map", Actual: item.Kind}) -} - -func (item *primitiveItem) VisitReference(schema openapi.Reference) { - // passthrough - schema.SubSchema().Accept(item) -} - -// itemFactory creates the relevant item type/visitor based on the current yaml type. -func itemFactory(path openapi.Path, v interface{}) (ValidationItem, error) { - // We need to special case for no-type fields in yaml (e.g. empty item in list) - if v == nil { - return nil, InvalidObjectTypeError{Type: "nil", Path: path.String()} - } - kind := reflect.TypeOf(v).Kind() - switch kind { - case reflect.Bool: - return &primitiveItem{ - baseItem: baseItem{path: path}, - Value: v, - Kind: openapi.Boolean, - }, nil - case reflect.Int, - reflect.Int8, - reflect.Int16, - reflect.Int32, - reflect.Int64, - reflect.Uint, - reflect.Uint8, - reflect.Uint16, - reflect.Uint32, - reflect.Uint64: - return &primitiveItem{ - baseItem: baseItem{path: path}, - Value: v, - Kind: openapi.Integer, - }, nil - case reflect.Float32, - reflect.Float64: - return &primitiveItem{ - baseItem: baseItem{path: path}, - Value: v, - Kind: openapi.Number, - }, nil - case reflect.String: - return &primitiveItem{ - baseItem: baseItem{path: path}, - Value: v, - Kind: openapi.String, - }, nil - case reflect.Array, - reflect.Slice: - return &arrayItem{ - baseItem: baseItem{path: path}, - Array: v.([]interface{}), - }, nil - case reflect.Map: - return &mapItem{ - baseItem: baseItem{path: path}, - Map: v.(map[string]interface{}), - }, nil - } - return nil, InvalidObjectTypeError{Type: kind.String(), Path: path.String()} -} diff --git a/pkg/kubectl/cmd/util/openapi/validation/validation.go b/pkg/kubectl/cmd/util/openapi/validation/validation.go index 212012293fc..80239e0229f 100644 --- a/pkg/kubectl/cmd/util/openapi/validation/validation.go +++ b/pkg/kubectl/cmd/util/openapi/validation/validation.go @@ -24,28 +24,34 @@ import ( utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/kube-openapi/pkg/util/proto/validation" "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" ) +// SchemaValidation validates the object against an OpenAPI schema. type SchemaValidation struct { resources openapi.Resources } +// NewSchemaValidation creates a new SchemaValidation that can be used +// to validate objects. func NewSchemaValidation(resources openapi.Resources) *SchemaValidation { return &SchemaValidation{ resources: resources, } } +// ValidateBytes will validates the object against using the Resources +// object. func (v *SchemaValidation) ValidateBytes(data []byte) error { obj, err := parse(data) if err != nil { return err } - gvk, err := getObjectKind(obj) - if err != nil { - return err + gvk, errs := getObjectKind(obj) + if errs != nil { + return utilerrors.NewAggregate(errs) } if strings.HasSuffix(gvk.Kind, "List") { @@ -56,20 +62,23 @@ func (v *SchemaValidation) ValidateBytes(data []byte) error { } func (v *SchemaValidation) validateList(object interface{}) []error { - fields := object.(map[string]interface{}) - if fields == nil { + fields, ok := object.(map[string]interface{}) + if !ok || fields == nil { return []error{errors.New("invalid object to validate")} } - errs := []error{} + allErrors := []error{} + if _, ok := fields["items"].([]interface{}); !ok { + return []error{errors.New("invalid object to validate")} + } for _, item := range fields["items"].([]interface{}) { - if gvk, err := getObjectKind(item); err != nil { - errs = append(errs, err) + if gvk, errs := getObjectKind(item); errs != nil { + allErrors = append(allErrors, errs...) } else { - errs = append(errs, v.validateResource(item, gvk)...) + allErrors = append(allErrors, v.validateResource(item, gvk)...) } } - return errs + return allErrors } func (v *SchemaValidation) validateResource(obj interface{}, gvk schema.GroupVersionKind) []error { @@ -79,12 +88,7 @@ func (v *SchemaValidation) validateResource(obj interface{}, gvk schema.GroupVer return nil } - rootValidation, err := itemFactory(openapi.NewPath(gvk.Kind), obj) - if err != nil { - return []error{err} - } - resource.Accept(rootValidation) - return rootValidation.Errors() + return validation.ValidateModel(obj, resource, gvk.Kind) } func parse(data []byte) (interface{}, error) { @@ -99,29 +103,39 @@ func parse(data []byte) (interface{}, error) { return obj, nil } -func getObjectKind(object interface{}) (schema.GroupVersionKind, error) { - fields := object.(map[string]interface{}) - if fields == nil { - return schema.GroupVersionKind{}, errors.New("invalid object to validate") +func getObjectKind(object interface{}) (schema.GroupVersionKind, []error) { + var listErrors []error + fields, ok := object.(map[string]interface{}) + if !ok || fields == nil { + listErrors = append(listErrors, errors.New("invalid object to validate")) + return schema.GroupVersionKind{}, listErrors } + + var group string + var version string apiVersion := fields["apiVersion"] if apiVersion == nil { - return schema.GroupVersionKind{}, errors.New("apiVersion not set") - } - if _, ok := apiVersion.(string); !ok { - return schema.GroupVersionKind{}, errors.New("apiVersion isn't string type") - } - gv, err := schema.ParseGroupVersion(apiVersion.(string)) - if err != nil { - return schema.GroupVersionKind{}, err + listErrors = append(listErrors, errors.New("apiVersion not set")) + } else if _, ok := apiVersion.(string); !ok { + listErrors = append(listErrors, errors.New("apiVersion isn't string type")) + } else { + gv, err := schema.ParseGroupVersion(apiVersion.(string)) + if err != nil { + listErrors = append(listErrors, err) + } else { + group = gv.Group + version = gv.Version + } } kind := fields["kind"] if kind == nil { - return schema.GroupVersionKind{}, errors.New("kind not set") + listErrors = append(listErrors, errors.New("kind not set")) + } else if _, ok := kind.(string); !ok { + listErrors = append(listErrors, errors.New("kind isn't string type")) } - if _, ok := kind.(string); !ok { - return schema.GroupVersionKind{}, errors.New("kind isn't string type") + if listErrors != nil { + return schema.GroupVersionKind{}, listErrors } - return schema.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: kind.(string)}, nil + return schema.GroupVersionKind{Group: group, Version: version, Kind: kind.(string)}, nil } diff --git a/pkg/kubectl/cmd/util/openapi/validation/validation_suite_test.go b/pkg/kubectl/cmd/util/openapi/validation/validation_suite_test.go index bfc62c3ca29..30e4381f9a0 100644 --- a/pkg/kubectl/cmd/util/openapi/validation/validation_suite_test.go +++ b/pkg/kubectl/cmd/util/openapi/validation/validation_suite_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package validation_test +package validation import ( . "github.com/onsi/ginkgo" diff --git a/pkg/kubectl/cmd/util/openapi/validation/validation_test.go b/pkg/kubectl/cmd/util/openapi/validation/validation_test.go index c1f86f256a3..c038682a3a2 100644 --- a/pkg/kubectl/cmd/util/openapi/validation/validation_test.go +++ b/pkg/kubectl/cmd/util/openapi/validation/validation_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package validation_test +package validation import ( "path/filepath" @@ -23,23 +23,23 @@ import ( . "github.com/onsi/gomega" utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/kube-openapi/pkg/util/proto/validation" // This dependency is needed to register API types. _ "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" tst "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing" - "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/validation" ) var fakeSchema = tst.Fake{Path: filepath.Join("..", "..", "..", "..", "..", "..", "api", "openapi-spec", "swagger.json")} var _ = Describe("resource validation using OpenAPI Schema", func() { - var validator *validation.SchemaValidation + var validator *SchemaValidation BeforeEach(func() { s, err := fakeSchema.OpenAPISchema() Expect(err).To(BeNil()) resources, err := openapi.NewOpenAPIData(s) Expect(err).To(BeNil()) - validator = validation.NewSchemaValidation(resources) + validator = NewSchemaValidation(resources) Expect(validator).ToNot(BeNil()) }) @@ -335,4 +335,63 @@ items: Expect(err).To(BeNil()) }) + + It("fails because apiVersion is not provided", func() { + err := validator.ValidateBytes([]byte(` +kind: Pod +metadata: + name: name +spec: + containers: + - name: name + image: image +`)) + Expect(err.Error()).To(Equal("apiVersion not set")) + }) + + It("fails because apiVersion type is not string and kind is not provided", func() { + err := validator.ValidateBytes([]byte(` +apiVersion: 1 +metadata: + name: name +spec: + containers: + - name: name + image: image +`)) + Expect(err.Error()).To(Equal("[apiVersion isn't string type, kind not set]")) + }) + + It("fails because List first item is missing kind and second item is missing apiVersion", func() { + err := validator.ValidateBytes([]byte(` +apiVersion: v1 +kind: List +items: +- apiVersion: v1 + metadata: + name: name + spec: + replicas: 1 + template: + metadata: + labels: + name: name + spec: + containers: + - name: name + image: image +- kind: Service + metadata: + name: name + spec: + type: NodePort + ports: + - port: 123 + targetPort: 1234 + name: name + selector: + name: name +`)) + Expect(err.Error()).To(Equal("[kind not set, apiVersion not set]")) + }) }) diff --git a/pkg/kubectl/cmd/util/printing.go b/pkg/kubectl/cmd/util/printing.go index 8e26c255999..8d06f8d3713 100644 --- a/pkg/kubectl/cmd/util/printing.go +++ b/pkg/kubectl/cmd/util/printing.go @@ -18,13 +18,12 @@ package util import ( "fmt" - "io" "strings" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" "k8s.io/kubernetes/pkg/kubectl" - "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/printers" printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" @@ -73,30 +72,6 @@ func AddNoHeadersFlags(cmd *cobra.Command) { cmd.Flags().Bool("no-headers", false, "When using the default or custom-column output format, don't print headers (default print headers).") } -// PrintSuccess prints message after finishing mutating operations -func PrintSuccess(mapper meta.RESTMapper, shortOutput bool, out io.Writer, resource string, name string, dryRun bool, operation string) { - resource, _ = mapper.ResourceSingularizer(resource) - dryRunMsg := "" - if dryRun { - dryRunMsg = " (dry run)" - } - if shortOutput { - // -o name: prints resource/name - if len(resource) > 0 { - fmt.Fprintf(out, "%s/%s\n", resource, name) - } else { - fmt.Fprintf(out, "%s\n", name) - } - } else { - // understandable output by default - if len(resource) > 0 { - fmt.Fprintf(out, "%s \"%s\" %s%s\n", resource, name, operation, dryRunMsg) - } else { - fmt.Fprintf(out, "\"%s\" %s%s\n", name, operation, dryRunMsg) - } - } -} - // ValidateOutputArgs validates -o flag args for mutations func ValidateOutputArgs(cmd *cobra.Command) error { outputMode := GetFlagString(cmd, "output") @@ -106,23 +81,11 @@ func ValidateOutputArgs(cmd *cobra.Command) error { return nil } -// PrinterForCommand returns the printer for the outputOptions (if given) or +// printerForOptions returns the printer for the outputOptions (if given) or // returns the default printer for the command. Requires that printer flags have // been added to cmd (see AddPrinterFlags). -// TODO: remove the dependency on cmd object -func PrinterForCommand(cmd *cobra.Command, outputOpts *printers.OutputOptions, mapper meta.RESTMapper, typer runtime.ObjectTyper, encoder runtime.Encoder, decoders []runtime.Decoder, options printers.PrintOptions) (printers.ResourcePrinter, error) { - - if outputOpts == nil { - outputOpts = extractOutputOptions(cmd) - } - - // this function may be invoked by a command that did not call AddPrinterFlags first, so we need - // to be safe about how we access the no-headers flag - noHeaders := false - if cmd.Flags().Lookup("no-headers") != nil { - noHeaders = GetFlagBool(cmd, "no-headers") - } - printer, err := printers.GetStandardPrinter(outputOpts, noHeaders, mapper, typer, encoder, decoders, options) +func printerForOptions(mapper meta.RESTMapper, typer runtime.ObjectTyper, encoder runtime.Encoder, decoders []runtime.Decoder, options *printers.PrintOptions) (printers.ResourcePrinter, error) { + printer, err := printers.GetStandardPrinter(mapper, typer, encoder, decoders, *options) if err != nil { return nil, err } @@ -130,41 +93,43 @@ func PrinterForCommand(cmd *cobra.Command, outputOpts *printers.OutputOptions, m // we try to convert to HumanReadablePrinter, if return ok, it must be no generic // we execute AddHandlers() here before maybeWrapSortingPrinter so that we don't // need to convert to delegatePrinter again then invoke AddHandlers() - if humanReadablePrinter, ok := printer.(*printers.HumanReadablePrinter); ok { + if humanReadablePrinter, ok := printer.(printers.PrintHandler); ok { printersinternal.AddHandlers(humanReadablePrinter) } - return maybeWrapSortingPrinter(cmd, printer), nil + return maybeWrapSortingPrinter(printer, *options), nil } -// PrintResourceInfoForCommand receives a *cobra.Command and a *resource.Info and -// attempts to print an info object based on the specified output format. If the -// object passed is non-generic, it attempts to print the object using a HumanReadablePrinter. -// Requires that printer flags have been added to cmd (see AddPrinterFlags). -func PrintResourceInfoForCommand(cmd *cobra.Command, info *resource.Info, f Factory, out io.Writer) error { - printer, err := f.PrinterForCommand(cmd, false, nil, printers.PrintOptions{}) - if err != nil { - return err - } - if !printer.IsGeneric() { - printer, err = f.PrinterForMapping(cmd, false, nil, nil, false) - if err != nil { - return err - } - } - return printer.PrintObj(info.Object, out) -} - -// extractOutputOptions parses printer specific commandline args and returns -// printers.OutputsOptions object. -func extractOutputOptions(cmd *cobra.Command) *printers.OutputOptions { +// ExtractCmdPrintOptions parses printer specific commandline args and +// returns a PrintOptions object. +// Requires that printer flags have been added to cmd (see AddPrinterFlags) +func ExtractCmdPrintOptions(cmd *cobra.Command, withNamespace bool) *printers.PrintOptions { flags := cmd.Flags() + columnLabel, err := flags.GetStringSlice("label-columns") + if err != nil { + columnLabel = []string{} + } + + options := &printers.PrintOptions{ + NoHeaders: GetFlagBool(cmd, "no-headers"), + Wide: GetWideFlag(cmd), + ShowAll: GetFlagBool(cmd, "show-all"), + ShowLabels: GetFlagBool(cmd, "show-labels"), + AbsoluteTimestamps: isWatch(cmd), + ColumnLabels: columnLabel, + WithNamespace: withNamespace, + } + var outputFormat string if flags.Lookup("output") != nil { outputFormat = GetFlagString(cmd, "output") } + if flags.Lookup("sort-by") != nil { + options.SortBy = GetFlagString(cmd, "sort-by") + } + // templates are logically optional for specifying a format. // TODO once https://github.com/kubernetes/kubernetes/issues/12668 is fixed, this should fall back to GetFlagString var templateFile string @@ -189,30 +154,72 @@ func extractOutputOptions(cmd *cobra.Command) *printers.OutputOptions { // this function may be invoked by a command that did not call AddPrinterFlags first, so we need // to be safe about how we access the allow-missing-template-keys flag - allowMissingTemplateKeys := false if flags.Lookup("allow-missing-template-keys") != nil { - allowMissingTemplateKeys = GetFlagBool(cmd, "allow-missing-template-keys") + options.AllowMissingKeys = GetFlagBool(cmd, "allow-missing-template-keys") } - return &printers.OutputOptions{ - FmtType: outputFormat, - FmtArg: templateFile, - AllowMissingKeys: allowMissingTemplateKeys, - } + options.OutputFormatType = outputFormat + options.OutputFormatArgument = templateFile + + return options } -func maybeWrapSortingPrinter(cmd *cobra.Command, printer printers.ResourcePrinter) printers.ResourcePrinter { - sorting, err := cmd.Flags().GetString("sort-by") - if err != nil { - // error can happen on missing flag or bad flag type. In either case, this command didn't intent to sort - return printer - } - - if len(sorting) != 0 { +func maybeWrapSortingPrinter(printer printers.ResourcePrinter, printOpts printers.PrintOptions) printers.ResourcePrinter { + if len(printOpts.SortBy) != 0 { return &kubectl.SortingPrinter{ Delegate: printer, - SortField: fmt.Sprintf("{%s}", sorting), + SortField: fmt.Sprintf("{%s}", printOpts.SortBy), } } return printer } + +// ValidResourceTypeList returns a multi-line string containing the valid resources. May +// be called before the factory is initialized. +// TODO: This function implementation should be replaced with a real implementation from the +// discovery service. +func ValidResourceTypeList(f ClientAccessFactory) string { + // TODO: Should attempt to use the cached discovery list or fallback to a static list + // that is calculated from code compiled into the factory. + return templates.LongDesc(`Valid resource types include: + + * all + * certificatesigningrequests (aka 'csr') + * clusterrolebindings + * clusterroles + * componentstatuses (aka 'cs') + * configmaps (aka 'cm') + * controllerrevisions + * cronjobs + * customresourcedefinition (aka 'crd') + * daemonsets (aka 'ds') + * deployments (aka 'deploy') + * endpoints (aka 'ep') + * events (aka 'ev') + * horizontalpodautoscalers (aka 'hpa') + * ingresses (aka 'ing') + * jobs + * limitranges (aka 'limits') + * namespaces (aka 'ns') + * networkpolicies (aka 'netpol') + * nodes (aka 'no') + * persistentvolumeclaims (aka 'pvc') + * persistentvolumes (aka 'pv') + * poddisruptionbudgets (aka 'pdb') + * podpreset + * pods (aka 'po') + * podsecuritypolicies (aka 'psp') + * podtemplates + * replicasets (aka 'rs') + * replicationcontrollers (aka 'rc') + * resourcequotas (aka 'quota') + * rolebindings + * roles + * secrets + * serviceaccounts (aka 'sa') + * services (aka 'svc') + * statefulsets (aka 'sts') + * storageclasses (aka 'sc') + + `) +} diff --git a/pkg/kubectl/cmd/util/shortcut_restmapper.go b/pkg/kubectl/cmd/util/shortcut_restmapper.go index 8d71af02ceb..c5ecfa84b12 100644 --- a/pkg/kubectl/cmd/util/shortcut_restmapper.go +++ b/pkg/kubectl/cmd/util/shortcut_restmapper.go @@ -17,7 +17,6 @@ limitations under the License. package util import ( - "errors" "strings" "github.com/golang/glog" @@ -37,11 +36,8 @@ type shortcutExpander struct { var _ meta.RESTMapper = &shortcutExpander{} -func NewShortcutExpander(delegate meta.RESTMapper, client discovery.DiscoveryInterface) (shortcutExpander, error) { - if client == nil { - return shortcutExpander{}, errors.New("Please provide discovery client to shortcut expander") - } - return shortcutExpander{RESTMapper: delegate, discoveryClient: client}, nil +func NewShortcutExpander(delegate meta.RESTMapper, client discovery.DiscoveryInterface) shortcutExpander { + return shortcutExpander{RESTMapper: delegate, discoveryClient: client} } func (e shortcutExpander) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) { @@ -79,22 +75,24 @@ func (e shortcutExpander) RESTMappings(gk schema.GroupKind, versions ...string) func (e shortcutExpander) getShortcutMappings() ([]kubectl.ResourceShortcuts, error) { res := []kubectl.ResourceShortcuts{} // get server resources + // This can return an error *and* the results it was able to find. We don't need to fail on the error. apiResList, err := e.discoveryClient.ServerResources() - if err == nil { - for _, apiResources := range apiResList { - for _, apiRes := range apiResources.APIResources { - for _, shortName := range apiRes.ShortNames { - gv, err := schema.ParseGroupVersion(apiResources.GroupVersion) - if err != nil { - glog.V(1).Infof("Unable to parse groupversion = %s due to = %s", apiResources.GroupVersion, err.Error()) - continue - } - rs := kubectl.ResourceShortcuts{ - ShortForm: schema.GroupResource{Group: gv.Group, Resource: shortName}, - LongForm: schema.GroupResource{Group: gv.Group, Resource: apiRes.Name}, - } - res = append(res, rs) + if err != nil { + glog.V(1).Infof("Error loading discovery information: %v", err) + } + for _, apiResources := range apiResList { + for _, apiRes := range apiResources.APIResources { + for _, shortName := range apiRes.ShortNames { + gv, err := schema.ParseGroupVersion(apiResources.GroupVersion) + if err != nil { + glog.V(1).Infof("Unable to parse groupversion = %s due to = %s", apiResources.GroupVersion, err.Error()) + continue } + rs := kubectl.ResourceShortcuts{ + ShortForm: schema.GroupResource{Group: gv.Group, Resource: shortName}, + LongForm: schema.GroupResource{Group: gv.Group, Resource: apiRes.Name}, + } + res = append(res, rs) } } } diff --git a/pkg/kubectl/cmd/util/shortcut_restmapper_test.go b/pkg/kubectl/cmd/util/shortcut_restmapper_test.go index 689f06994ac..41903643fd4 100644 --- a/pkg/kubectl/cmd/util/shortcut_restmapper_test.go +++ b/pkg/kubectl/cmd/util/shortcut_restmapper_test.go @@ -71,10 +71,7 @@ func TestReplaceAliases(t *testing.T) { } ds := &fakeDiscoveryClient{} - mapper, err := NewShortcutExpander(testapi.Default.RESTMapper(), ds) - if err != nil { - t.Fatalf("Unable to create shortcut expander, err %s", err.Error()) - } + mapper := NewShortcutExpander(testapi.Default.RESTMapper(), ds) for _, test := range tests { ds.serverResourcesHandler = func() ([]*metav1.APIResourceList, error) { @@ -126,10 +123,7 @@ func TestKindFor(t *testing.T) { } ds := &fakeDiscoveryClient{} - mapper, err := NewShortcutExpander(testapi.Default.RESTMapper(), ds) - if err != nil { - t.Fatalf("Unable to create shortcut expander, err %s", err.Error()) - } + mapper := NewShortcutExpander(testapi.Default.RESTMapper(), ds) for i, test := range tests { ds.serverResourcesHandler = func() ([]*metav1.APIResourceList, error) { diff --git a/pkg/kubectl/configmap.go b/pkg/kubectl/configmap.go index cc73d690adb..8d1ef5281ed 100644 --- a/pkg/kubectl/configmap.go +++ b/pkg/kubectl/configmap.go @@ -23,9 +23,9 @@ import ( "path" "strings" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/kubectl/util" "k8s.io/kubernetes/pkg/kubectl/util/hash" ) @@ -127,7 +127,7 @@ func (s ConfigMapGeneratorV1) StructuredGenerate() (runtime.Object, error) { if err := s.validate(); err != nil { return nil, err } - configMap := &api.ConfigMap{} + configMap := &v1.ConfigMap{} configMap.Name = s.Name configMap.Data = map[string]string{} if len(s.FileSources) > 0 { @@ -168,7 +168,7 @@ func (s ConfigMapGeneratorV1) validate() error { // handleConfigMapFromLiteralSources adds the specified literal source // information into the provided configMap. -func handleConfigMapFromLiteralSources(configMap *api.ConfigMap, literalSources []string) error { +func handleConfigMapFromLiteralSources(configMap *v1.ConfigMap, literalSources []string) error { for _, literalSource := range literalSources { keyName, value, err := util.ParseLiteralSource(literalSource) if err != nil { @@ -184,7 +184,7 @@ func handleConfigMapFromLiteralSources(configMap *api.ConfigMap, literalSources // handleConfigMapFromFileSources adds the specified file source information // into the provided configMap -func handleConfigMapFromFileSources(configMap *api.ConfigMap, fileSources []string) error { +func handleConfigMapFromFileSources(configMap *v1.ConfigMap, fileSources []string) error { for _, fileSource := range fileSources { keyName, filePath, err := util.ParseFileSource(fileSource) if err != nil { @@ -229,7 +229,7 @@ func handleConfigMapFromFileSources(configMap *api.ConfigMap, fileSources []stri // handleConfigMapFromEnvFileSource adds the specified env file source information // into the provided configMap -func handleConfigMapFromEnvFileSource(configMap *api.ConfigMap, envFileSource string) error { +func handleConfigMapFromEnvFileSource(configMap *v1.ConfigMap, envFileSource string) error { info, err := os.Stat(envFileSource) if err != nil { switch err := err.(type) { @@ -250,7 +250,7 @@ func handleConfigMapFromEnvFileSource(configMap *api.ConfigMap, envFileSource st // addKeyFromFileToConfigMap adds a key with the given name to a ConfigMap, populating // the value with the content of the given file path, or returns an error. -func addKeyFromFileToConfigMap(configMap *api.ConfigMap, keyName, filePath string) error { +func addKeyFromFileToConfigMap(configMap *v1.ConfigMap, keyName, filePath string) error { data, err := ioutil.ReadFile(filePath) if err != nil { return err @@ -260,7 +260,7 @@ func addKeyFromFileToConfigMap(configMap *api.ConfigMap, keyName, filePath strin // addKeyFromLiteralToConfigMap adds the given key and data to the given config map, // returning an error if the key is not valid or if the key already exists. -func addKeyFromLiteralToConfigMap(configMap *api.ConfigMap, keyName, data string) error { +func addKeyFromLiteralToConfigMap(configMap *v1.ConfigMap, keyName, data string) error { // Note, the rules for ConfigMap keys are the exact same as the ones for SecretKeys. if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 { return fmt.Errorf("%q is not a valid key name for a ConfigMap: %s", keyName, strings.Join(errs, ";")) diff --git a/pkg/kubectl/configmap_test.go b/pkg/kubectl/configmap_test.go index 74a85a89eb9..05007dfb4af 100644 --- a/pkg/kubectl/configmap_test.go +++ b/pkg/kubectl/configmap_test.go @@ -22,22 +22,22 @@ import ( "reflect" "testing" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" ) func TestConfigMapGenerate(t *testing.T) { tests := []struct { setup func(t *testing.T, params map[string]interface{}) func() params map[string]interface{} - expected *api.ConfigMap + expected *v1.ConfigMap expectErr bool }{ { params: map[string]interface{}{ "name": "foo", }, - expected: &api.ConfigMap{ + expected: &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, @@ -50,7 +50,7 @@ func TestConfigMapGenerate(t *testing.T) { "name": "foo", "append-hash": true, }, - expected: &api.ConfigMap{ + expected: &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "foo-867km9574f", }, @@ -63,7 +63,7 @@ func TestConfigMapGenerate(t *testing.T) { "name": "foo", "type": "my-type", }, - expected: &api.ConfigMap{ + expected: &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, @@ -77,7 +77,7 @@ func TestConfigMapGenerate(t *testing.T) { "type": "my-type", "append-hash": true, }, - expected: &api.ConfigMap{ + expected: &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "foo-867km9574f", }, @@ -90,7 +90,7 @@ func TestConfigMapGenerate(t *testing.T) { "name": "foo", "from-literal": []string{"key1=value1", "key2=value2"}, }, - expected: &api.ConfigMap{ + expected: &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, @@ -107,7 +107,7 @@ func TestConfigMapGenerate(t *testing.T) { "from-literal": []string{"key1=value1", "key2=value2"}, "append-hash": true, }, - expected: &api.ConfigMap{ + expected: &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "foo-gcb75dd9gb", }, @@ -144,7 +144,7 @@ func TestConfigMapGenerate(t *testing.T) { "name": "foo", "from-literal": []string{"key1==value1"}, }, - expected: &api.ConfigMap{ + expected: &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, @@ -160,7 +160,7 @@ func TestConfigMapGenerate(t *testing.T) { "from-literal": []string{"key1==value1"}, "append-hash": true, }, - expected: &api.ConfigMap{ + expected: &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "foo-bdgk9ttt7m", }, @@ -176,7 +176,7 @@ func TestConfigMapGenerate(t *testing.T) { "name": "valid_env", "from-env-file": "file.env", }, - expected: &api.ConfigMap{ + expected: &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "valid_env", }, @@ -194,7 +194,7 @@ func TestConfigMapGenerate(t *testing.T) { "from-env-file": "file.env", "append-hash": true, }, - expected: &api.ConfigMap{ + expected: &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "valid_env-2cgh8552ch", }, @@ -215,7 +215,7 @@ func TestConfigMapGenerate(t *testing.T) { "name": "getenv", "from-env-file": "file.env", }, - expected: &api.ConfigMap{ + expected: &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "getenv", }, @@ -237,7 +237,7 @@ func TestConfigMapGenerate(t *testing.T) { "from-env-file": "file.env", "append-hash": true, }, - expected: &api.ConfigMap{ + expected: &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "getenv-b4hh92hgdk", }, @@ -270,7 +270,7 @@ func TestConfigMapGenerate(t *testing.T) { "name": "with_spaces", "from-env-file": "file.env", }, - expected: &api.ConfigMap{ + expected: &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "with_spaces", }, @@ -287,7 +287,7 @@ func TestConfigMapGenerate(t *testing.T) { "from-env-file": "file.env", "append-hash": true, }, - expected: &api.ConfigMap{ + expected: &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "with_spaces-bfc558b4ct", }, @@ -312,8 +312,8 @@ func TestConfigMapGenerate(t *testing.T) { if test.expectErr && err != nil { continue } - if !reflect.DeepEqual(obj.(*api.ConfigMap), test.expected) { - t.Errorf("\ncase %d, expected:\n%#v\nsaw:\n%#v", i, test.expected, obj.(*api.ConfigMap)) + if !reflect.DeepEqual(obj.(*v1.ConfigMap), test.expected) { + t.Errorf("\ncase %d, expected:\n%#v\nsaw:\n%#v", i, test.expected, obj.(*v1.ConfigMap)) } } } diff --git a/pkg/kubectl/delete.go b/pkg/kubectl/delete.go index 3982bc17e7e..1522f71b68e 100644 --- a/pkg/kubectl/delete.go +++ b/pkg/kubectl/delete.go @@ -28,9 +28,9 @@ import ( utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/uuid" "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/batch" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" appsclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion" diff --git a/pkg/kubectl/delete_test.go b/pkg/kubectl/delete_test.go index 66803de1470..d250d1e5483 100644 --- a/pkg/kubectl/delete_test.go +++ b/pkg/kubectl/delete_test.go @@ -30,8 +30,8 @@ import ( "k8s.io/apimachinery/pkg/util/uuid" "k8s.io/apimachinery/pkg/watch" testcore "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/batch" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" diff --git a/pkg/kubectl/explain/BUILD b/pkg/kubectl/explain/BUILD index 68c95738f56..c2a86e3b8e2 100644 --- a/pkg/kubectl/explain/BUILD +++ b/pkg/kubectl/explain/BUILD @@ -15,8 +15,9 @@ go_library( importpath = "k8s.io/kubernetes/pkg/kubectl/explain", visibility = ["//visibility:public"], deps = [ - "//pkg/kubectl/cmd/util/openapi:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", ], ) @@ -37,6 +38,7 @@ filegroup( go_test( name = "go_default_test", srcs = [ + "explain_test.go", "field_lookup_test.go", "fields_printer_test.go", "formatter_test.go", @@ -45,10 +47,12 @@ go_test( "typename_test.go", ], data = ["test-swagger.json"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubectl/explain", - library = ":go_default_library", deps = [ + "//pkg/kubectl/cmd/testing:go_default_library", "//pkg/kubectl/cmd/util/openapi/testing:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], ) diff --git a/pkg/kubectl/explain/OWNERS b/pkg/kubectl/explain/OWNERS new file mode 100644 index 00000000000..7eb2d06fe1a --- /dev/null +++ b/pkg/kubectl/explain/OWNERS @@ -0,0 +1,4 @@ +approvers: +- apelisse +reviewers: +- apelisse diff --git a/pkg/kubectl/explain/explain.go b/pkg/kubectl/explain/explain.go index d794ea70e3d..816628d4254 100644 --- a/pkg/kubectl/explain/explain.go +++ b/pkg/kubectl/explain/explain.go @@ -21,15 +21,20 @@ import ( "strings" "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kube-openapi/pkg/util/proto" ) type fieldsPrinter interface { - PrintFields(openapi.Schema) error + PrintFields(proto.Schema) error } func splitDotNotation(model string) (string, []string) { var fieldsPath []string + + // ignore trailing period + model = strings.TrimSuffix(model, ".") + dotModel := strings.Split(model, ".") if len(dotModel) >= 1 { fieldsPath = dotModel[1:] @@ -47,7 +52,7 @@ func SplitAndParseResourceRequest(inResource string, mapper meta.RESTMapper) (st // PrintModelDescription prints the description of a specific model or dot path. // If recursive, all components nested within the fields of the schema will be // printed. -func PrintModelDescription(fieldsPath []string, w io.Writer, schema openapi.Schema, recursive bool) error { +func PrintModelDescription(fieldsPath []string, w io.Writer, schema proto.Schema, gvk schema.GroupVersionKind, recursive bool) error { fieldName := "" if len(fieldsPath) != 0 { fieldName = fieldsPath[len(fieldsPath)-1] @@ -60,5 +65,5 @@ func PrintModelDescription(fieldsPath []string, w io.Writer, schema openapi.Sche } b := fieldsPrinterBuilder{Recursive: recursive} f := &Formatter{Writer: w, Wrap: 80} - return PrintModel(fieldName, f, b, schema) + return PrintModel(fieldName, f, b, schema, gvk) } diff --git a/pkg/kubectl/explain/explain_test.go b/pkg/kubectl/explain/explain_test.go new file mode 100644 index 00000000000..29862fa6383 --- /dev/null +++ b/pkg/kubectl/explain/explain_test.go @@ -0,0 +1,81 @@ +/* +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 explain + +import ( + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/api/meta" + cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" +) + +func TestSplitAndParseResourceRequest(t *testing.T) { + tests := []struct { + name string + inresource string + + expectedInResource string + expectedFieldsPath []string + expectedErr bool + }{ + { + name: "no trailing period", + inresource: "field1.field2.field3", + + expectedInResource: "field1", + expectedFieldsPath: []string{"field2", "field3"}, + }, + { + name: "trailing period with correct fieldsPath", + inresource: "field1.field2.field3.", + + expectedInResource: "field1", + expectedFieldsPath: []string{"field2", "field3"}, + }, + { + name: "trailing period with incorrect fieldsPath", + inresource: "field1.field2.field3.", + + expectedInResource: "field1", + expectedFieldsPath: []string{"field2", "field3", ""}, + expectedErr: true, + }, + } + + mapper := getMapper() + for _, test := range tests { + gotInResource, gotFieldsPath, err := SplitAndParseResourceRequest(test.inresource, mapper) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if !reflect.DeepEqual(test.expectedInResource, gotInResource) && !test.expectedErr { + t.Errorf("%s: expected inresource: %s, got: %s", test.name, test.expectedInResource, gotInResource) + } + + if !reflect.DeepEqual(test.expectedFieldsPath, gotFieldsPath) && !test.expectedErr { + t.Errorf("%s: expected fieldsPath: %s, got: %s", test.name, test.expectedFieldsPath, gotFieldsPath) + } + } +} + +func getMapper() meta.RESTMapper { + f, _, _, _ := cmdtesting.NewTestFactory() + mapper, _ := f.Object() + return mapper +} diff --git a/pkg/kubectl/explain/field_lookup.go b/pkg/kubectl/explain/field_lookup.go index b766fbf3b40..82288642d43 100644 --- a/pkg/kubectl/explain/field_lookup.go +++ b/pkg/kubectl/explain/field_lookup.go @@ -19,7 +19,7 @@ package explain import ( "fmt" - "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" + "k8s.io/kube-openapi/pkg/util/proto" ) // fieldLookup walks through a schema by following a path, and returns @@ -29,13 +29,13 @@ type fieldLookup struct { Path []string // Return information: Schema found, or error. - Schema openapi.Schema + Schema proto.Schema Error error } // SaveLeafSchema is used to detect if we are done walking the path, and // saves the schema as a match. -func (f *fieldLookup) SaveLeafSchema(schema openapi.Schema) bool { +func (f *fieldLookup) SaveLeafSchema(schema proto.Schema) bool { if len(f.Path) != 0 { return false } @@ -46,7 +46,7 @@ func (f *fieldLookup) SaveLeafSchema(schema openapi.Schema) bool { } // VisitArray is mostly a passthrough. -func (f *fieldLookup) VisitArray(a *openapi.Array) { +func (f *fieldLookup) VisitArray(a *proto.Array) { if f.SaveLeafSchema(a) { return } @@ -56,7 +56,7 @@ func (f *fieldLookup) VisitArray(a *openapi.Array) { } // VisitMap is mostly a passthrough. -func (f *fieldLookup) VisitMap(m *openapi.Map) { +func (f *fieldLookup) VisitMap(m *proto.Map) { if f.SaveLeafSchema(m) { return } @@ -67,14 +67,14 @@ func (f *fieldLookup) VisitMap(m *openapi.Map) { // VisitPrimitive stops the operation and returns itself as the found // schema, even if it had more path to walk. -func (f *fieldLookup) VisitPrimitive(p *openapi.Primitive) { +func (f *fieldLookup) VisitPrimitive(p *proto.Primitive) { // Even if Path is not empty (we're not expecting a leaf), // return that primitive. f.Schema = p } // VisitKind unstacks fields as it finds them. -func (f *fieldLookup) VisitKind(k *openapi.Kind) { +func (f *fieldLookup) VisitKind(k *proto.Kind) { if f.SaveLeafSchema(k) { return } @@ -90,7 +90,7 @@ func (f *fieldLookup) VisitKind(k *openapi.Kind) { } // VisitReference is mostly a passthrough. -func (f *fieldLookup) VisitReference(r openapi.Reference) { +func (f *fieldLookup) VisitReference(r proto.Reference) { if f.SaveLeafSchema(r) { return } @@ -100,7 +100,7 @@ func (f *fieldLookup) VisitReference(r openapi.Reference) { } // LookupSchemaForField looks for the schema of a given path in a base schema. -func LookupSchemaForField(schema openapi.Schema, path []string) (openapi.Schema, error) { +func LookupSchemaForField(schema proto.Schema, path []string) (proto.Schema, error) { f := &fieldLookup{Path: path} schema.Accept(f) return f.Schema, f.Error diff --git a/pkg/kubectl/explain/field_lookup_test.go b/pkg/kubectl/explain/field_lookup_test.go index 4120c31f336..a2b99212b44 100644 --- a/pkg/kubectl/explain/field_lookup_test.go +++ b/pkg/kubectl/explain/field_lookup_test.go @@ -54,6 +54,10 @@ func TestFindField(t *testing.T) { path: []string{"field1", "what?"}, err: `field "what?" does not exist`, }, + { + path: []string{"field1", ""}, + err: `field "" does not exist`, + }, } for _, test := range tests { @@ -69,9 +73,9 @@ func TestFindField(t *testing.T) { gotPath = path.GetPath().String() } - if gotErr != test.err && gotPath != test.expectedPath { - t.Errorf("LookupSchemaForField(schema, %v) = (%v, %v), expected (%s, %v)", - test.path, gotErr, gotPath, test.expectedPath, test.err) + if gotErr != test.err || gotPath != test.expectedPath { + t.Errorf("LookupSchemaForField(schema, %v) = (path: %q, err: %q), expected (path: %q, err: %q)", + test.path, gotPath, gotErr, test.expectedPath, test.err) } } } diff --git a/pkg/kubectl/explain/fields_printer.go b/pkg/kubectl/explain/fields_printer.go index 8da066085bc..bb25241a452 100644 --- a/pkg/kubectl/explain/fields_printer.go +++ b/pkg/kubectl/explain/fields_printer.go @@ -16,9 +16,7 @@ limitations under the License. package explain -import ( - "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" -) +import "k8s.io/kube-openapi/pkg/util/proto" // indentDesc is the level of indentation for descriptions. const indentDesc = 2 @@ -29,17 +27,17 @@ type regularFieldsPrinter struct { Error error } -var _ openapi.SchemaVisitor = ®ularFieldsPrinter{} +var _ proto.SchemaVisitor = ®ularFieldsPrinter{} var _ fieldsPrinter = ®ularFieldsPrinter{} // VisitArray prints a Array type. It is just a passthrough. -func (f *regularFieldsPrinter) VisitArray(a *openapi.Array) { +func (f *regularFieldsPrinter) VisitArray(a *proto.Array) { a.SubType.Accept(f) } // VisitKind prints a Kind type. It prints each key in the kind, with // the type, the required flag, and the description. -func (f *regularFieldsPrinter) VisitKind(k *openapi.Kind) { +func (f *regularFieldsPrinter) VisitKind(k *proto.Kind) { for _, key := range k.Keys() { v := k.Fields[key] required := "" @@ -63,22 +61,22 @@ func (f *regularFieldsPrinter) VisitKind(k *openapi.Kind) { } // VisitMap prints a Map type. It is just a passthrough. -func (f *regularFieldsPrinter) VisitMap(m *openapi.Map) { +func (f *regularFieldsPrinter) VisitMap(m *proto.Map) { m.SubType.Accept(f) } // VisitPrimitive prints a Primitive type. It stops the recursion. -func (f *regularFieldsPrinter) VisitPrimitive(p *openapi.Primitive) { +func (f *regularFieldsPrinter) VisitPrimitive(p *proto.Primitive) { // Nothing to do. Shouldn't really happen. } // VisitReference prints a Reference type. It is just a passthrough. -func (f *regularFieldsPrinter) VisitReference(r openapi.Reference) { +func (f *regularFieldsPrinter) VisitReference(r proto.Reference) { r.SubSchema().Accept(f) } // PrintFields will write the types from schema. -func (f *regularFieldsPrinter) PrintFields(schema openapi.Schema) error { +func (f *regularFieldsPrinter) PrintFields(schema proto.Schema) error { schema.Accept(f) return f.Error } diff --git a/pkg/kubectl/explain/formatter.go b/pkg/kubectl/explain/formatter.go index 5543dbf1bb9..ab9c53e9ed1 100644 --- a/pkg/kubectl/explain/formatter.go +++ b/pkg/kubectl/explain/formatter.go @@ -105,10 +105,10 @@ func wrapString(str string, wrap int) []string { l := line{wrap: wrap} for _, word := range words { - if l.Add(word) == false { + if !l.Add(word) { wrapped = append(wrapped, l.String()) l = line{wrap: wrap} - if l.Add(word) == false { + if !l.Add(word) { panic("Couldn't add to empty line.") } } diff --git a/pkg/kubectl/explain/model_printer.go b/pkg/kubectl/explain/model_printer.go index 1f8d9208695..64f5bb4487a 100644 --- a/pkg/kubectl/explain/model_printer.go +++ b/pkg/kubectl/explain/model_printer.go @@ -17,7 +17,8 @@ limitations under the License. package explain import ( - "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kube-openapi/pkg/util/proto" ) // fieldIndentLevel is the level of indentation for fields. @@ -35,15 +36,23 @@ type modelPrinter struct { Descriptions []string Writer *Formatter Builder fieldsPrinterBuilder + GVK schema.GroupVersionKind Error error } -var _ openapi.SchemaVisitor = &modelPrinter{} +var _ proto.SchemaVisitor = &modelPrinter{} + +func (m *modelPrinter) PrintKindAndVersion() error { + if err := m.Writer.Write("KIND: %s", m.GVK.Kind); err != nil { + return err + } + return m.Writer.Write("VERSION: %s\n", m.GVK.GroupVersion()) +} // PrintDescription prints the description for a given schema. There // might be multiple description, since we collect descriptions when we // go through references, arrays and maps. -func (m *modelPrinter) PrintDescription(schema openapi.Schema) error { +func (m *modelPrinter) PrintDescription(schema proto.Schema) error { if err := m.Writer.Write("DESCRIPTION:"); err != nil { return err } @@ -65,7 +74,7 @@ func (m *modelPrinter) PrintDescription(schema openapi.Schema) error { // VisitArray recurses inside the subtype, while collecting the type if // not done yet, and the description. -func (m *modelPrinter) VisitArray(a *openapi.Array) { +func (m *modelPrinter) VisitArray(a *proto.Array) { m.Descriptions = append(m.Descriptions, a.GetDescription()) if m.Type == "" { m.Type = GetTypeName(a) @@ -74,7 +83,12 @@ func (m *modelPrinter) VisitArray(a *openapi.Array) { } // VisitKind prints a full resource with its fields. -func (m *modelPrinter) VisitKind(k *openapi.Kind) { +func (m *modelPrinter) VisitKind(k *proto.Kind) { + if err := m.PrintKindAndVersion(); err != nil { + m.Error = err + return + } + if m.Type == "" { m.Type = GetTypeName(k) } @@ -95,7 +109,7 @@ func (m *modelPrinter) VisitKind(k *openapi.Kind) { // VisitMap recurses inside the subtype, while collecting the type if // not done yet, and the description. -func (m *modelPrinter) VisitMap(om *openapi.Map) { +func (m *modelPrinter) VisitMap(om *proto.Map) { m.Descriptions = append(m.Descriptions, om.GetDescription()) if m.Type == "" { m.Type = GetTypeName(om) @@ -104,11 +118,16 @@ func (m *modelPrinter) VisitMap(om *openapi.Map) { } // VisitPrimitive prints a field type and its description. -func (m *modelPrinter) VisitPrimitive(p *openapi.Primitive) { +func (m *modelPrinter) VisitPrimitive(p *proto.Primitive) { + if err := m.PrintKindAndVersion(); err != nil { + m.Error = err + return + } + if m.Type == "" { m.Type = GetTypeName(p) } - if err := m.Writer.Write("FIELD: %s <%s>\n", m.Name, m.Type); err != nil { + if err := m.Writer.Write("FIELD: %s <%s>\n", m.Name, m.Type); err != nil { m.Error = err return } @@ -116,14 +135,14 @@ func (m *modelPrinter) VisitPrimitive(p *openapi.Primitive) { } // VisitReference recurses inside the subtype, while collecting the description. -func (m *modelPrinter) VisitReference(r openapi.Reference) { +func (m *modelPrinter) VisitReference(r proto.Reference) { m.Descriptions = append(m.Descriptions, r.GetDescription()) r.SubSchema().Accept(m) } // PrintModel prints the description of a schema in writer. -func PrintModel(name string, writer *Formatter, builder fieldsPrinterBuilder, schema openapi.Schema) error { - m := &modelPrinter{Name: name, Writer: writer, Builder: builder} +func PrintModel(name string, writer *Formatter, builder fieldsPrinterBuilder, schema proto.Schema, gvk schema.GroupVersionKind) error { + m := &modelPrinter{Name: name, Writer: writer, Builder: builder, GVK: gvk} schema.Accept(m) return m.Error } diff --git a/pkg/kubectl/explain/model_printer_test.go b/pkg/kubectl/explain/model_printer_test.go index 66b9b07eafc..42c0f424723 100644 --- a/pkg/kubectl/explain/model_printer_test.go +++ b/pkg/kubectl/explain/model_printer_test.go @@ -24,11 +24,12 @@ import ( ) func TestModel(t *testing.T) { - schema := resources.LookupResource(schema.GroupVersionKind{ + gvk := schema.GroupVersionKind{ Group: "", Version: "v1", Kind: "OneKind", - }) + } + schema := resources.LookupResource(gvk) if schema == nil { t.Fatal("Couldn't find schema v1.OneKind") } @@ -38,7 +39,10 @@ func TestModel(t *testing.T) { want string }{ { - want: `DESCRIPTION: + want: `KIND: OneKind +VERSION: v1 + +DESCRIPTION: OneKind has a short description FIELDS: @@ -58,7 +62,10 @@ FIELDS: path: []string{}, }, { - want: `RESOURCE: field1 + want: `KIND: OneKind +VERSION: v1 + +RESOURCE: field1 DESCRIPTION: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla ut lacus ac @@ -90,7 +97,10 @@ FIELDS: path: []string{"field1"}, }, { - want: `FIELD: string + want: `KIND: OneKind +VERSION: v1 + +FIELD: string DESCRIPTION: This string must be a string @@ -98,7 +108,10 @@ DESCRIPTION: path: []string{"field1", "string"}, }, { - want: `FIELD: array <[]integer> + want: `KIND: OneKind +VERSION: v1 + +FIELD: array <[]integer> DESCRIPTION: This array must be an array of int @@ -111,7 +124,7 @@ DESCRIPTION: for _, test := range tests { buf := bytes.Buffer{} - if err := PrintModelDescription(test.path, &buf, schema, false); err != nil { + if err := PrintModelDescription(test.path, &buf, schema, gvk, false); err != nil { t.Fatalf("Failed to PrintModelDescription for path %v: %v", test.path, err) } got := buf.String() diff --git a/pkg/kubectl/explain/recursive_fields_printer.go b/pkg/kubectl/explain/recursive_fields_printer.go index dfa37931c05..95638c85adf 100644 --- a/pkg/kubectl/explain/recursive_fields_printer.go +++ b/pkg/kubectl/explain/recursive_fields_printer.go @@ -16,9 +16,7 @@ limitations under the License. package explain -import ( - "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" -) +import "k8s.io/kube-openapi/pkg/util/proto" // indentPerLevel is the level of indentation for each field recursion. const indentPerLevel = 3 @@ -30,17 +28,17 @@ type recursiveFieldsPrinter struct { Error error } -var _ openapi.SchemaVisitor = &recursiveFieldsPrinter{} +var _ proto.SchemaVisitor = &recursiveFieldsPrinter{} var _ fieldsPrinter = &recursiveFieldsPrinter{} // VisitArray is just a passthrough. -func (f *recursiveFieldsPrinter) VisitArray(a *openapi.Array) { +func (f *recursiveFieldsPrinter) VisitArray(a *proto.Array) { a.SubType.Accept(f) } // VisitKind prints all its fields with their type, and then recurses // inside each of these (pre-order). -func (f *recursiveFieldsPrinter) VisitKind(k *openapi.Kind) { +func (f *recursiveFieldsPrinter) VisitKind(k *proto.Kind) { for _, key := range k.Keys() { v := k.Fields[key] f.Writer.Write("%s\t<%s>", key, GetTypeName(v)) @@ -55,23 +53,23 @@ func (f *recursiveFieldsPrinter) VisitKind(k *openapi.Kind) { } // VisitMap is just a passthrough. -func (f *recursiveFieldsPrinter) VisitMap(m *openapi.Map) { +func (f *recursiveFieldsPrinter) VisitMap(m *proto.Map) { m.SubType.Accept(f) } // VisitPrimitive does nothing, since it doesn't have sub-fields. -func (f *recursiveFieldsPrinter) VisitPrimitive(p *openapi.Primitive) { +func (f *recursiveFieldsPrinter) VisitPrimitive(p *proto.Primitive) { // Nothing to do. } // VisitReference is just a passthrough. -func (f *recursiveFieldsPrinter) VisitReference(r openapi.Reference) { +func (f *recursiveFieldsPrinter) VisitReference(r proto.Reference) { r.SubSchema().Accept(f) } // PrintFields will recursively print all the fields for the given // schema. -func (f *recursiveFieldsPrinter) PrintFields(schema openapi.Schema) error { +func (f *recursiveFieldsPrinter) PrintFields(schema proto.Schema) error { schema.Accept(f) return f.Error } diff --git a/pkg/kubectl/explain/typename.go b/pkg/kubectl/explain/typename.go index b90580e614f..b9d71b69a5c 100644 --- a/pkg/kubectl/explain/typename.go +++ b/pkg/kubectl/explain/typename.go @@ -19,7 +19,7 @@ package explain import ( "fmt" - "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" + "k8s.io/kube-openapi/pkg/util/proto" ) // typeName finds the name of a schema @@ -27,39 +27,39 @@ type typeName struct { Name string } -var _ openapi.SchemaVisitor = &typeName{} +var _ proto.SchemaVisitor = &typeName{} // VisitArray adds the [] prefix and recurses. -func (t *typeName) VisitArray(a *openapi.Array) { +func (t *typeName) VisitArray(a *proto.Array) { s := &typeName{} a.SubType.Accept(s) t.Name = fmt.Sprintf("[]%s", s.Name) } // VisitKind just returns "Object". -func (t *typeName) VisitKind(k *openapi.Kind) { +func (t *typeName) VisitKind(k *proto.Kind) { t.Name = "Object" } // VisitMap adds the map[string] prefix and recurses. -func (t *typeName) VisitMap(m *openapi.Map) { +func (t *typeName) VisitMap(m *proto.Map) { s := &typeName{} m.SubType.Accept(s) t.Name = fmt.Sprintf("map[string]%s", s.Name) } // VisitPrimitive returns the name of the primitive. -func (t *typeName) VisitPrimitive(p *openapi.Primitive) { +func (t *typeName) VisitPrimitive(p *proto.Primitive) { t.Name = p.Type } // VisitReference is just a passthrough. -func (t *typeName) VisitReference(r openapi.Reference) { +func (t *typeName) VisitReference(r proto.Reference) { r.SubSchema().Accept(t) } // GetTypeName returns the type of a schema. -func GetTypeName(schema openapi.Schema) string { +func GetTypeName(schema proto.Schema) string { t := &typeName{} schema.Accept(t) return t.Name diff --git a/pkg/kubectl/history.go b/pkg/kubectl/history.go index 5464686291d..36f95776d23 100644 --- a/pkg/kubectl/history.go +++ b/pkg/kubectl/history.go @@ -27,18 +27,18 @@ import ( extensionsv1beta1 "k8s.io/api/extensions/v1beta1" "k8s.io/apimachinery/pkg/api/meta" 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/json" "k8s.io/apimachinery/pkg/util/strategicpatch" + "k8s.io/client-go/kubernetes" clientappsv1beta1 "k8s.io/client-go/kubernetes/typed/apps/v1beta1" - clientextensionsv1beta1 "k8s.io/client-go/kubernetes/typed/extensions/v1beta1" - "k8s.io/kubernetes/pkg/api" - apiv1 "k8s.io/kubernetes/pkg/api/v1" - "k8s.io/kubernetes/pkg/apis/apps" - "k8s.io/kubernetes/pkg/apis/extensions" - clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + clientextv1beta1 "k8s.io/client-go/kubernetes/typed/extensions/v1beta1" + api "k8s.io/kubernetes/pkg/apis/core" + apiv1 "k8s.io/kubernetes/pkg/apis/core/v1" deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util" + kapps "k8s.io/kubernetes/pkg/kubectl/apps" sliceutil "k8s.io/kubernetes/pkg/kubectl/util/slice" printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" ) @@ -52,26 +52,58 @@ type HistoryViewer interface { ViewHistory(namespace, name string, revision int64) (string, error) } -func HistoryViewerFor(kind schema.GroupKind, c clientset.Interface) (HistoryViewer, error) { - switch kind { - case extensions.Kind("Deployment"), apps.Kind("Deployment"): - return &DeploymentHistoryViewer{c}, nil - case apps.Kind("StatefulSet"): - return &StatefulSetHistoryViewer{c}, nil - case extensions.Kind("DaemonSet"), apps.Kind("DaemonSet"): - return &DaemonSetHistoryViewer{c}, nil +type HistoryVisitor struct { + clientset kubernetes.Interface + result HistoryViewer +} + +func (v *HistoryVisitor) VisitDeployment(elem kapps.GroupKindElement) { + v.result = &DeploymentHistoryViewer{v.clientset} +} + +func (v *HistoryVisitor) VisitStatefulSet(kind kapps.GroupKindElement) { + v.result = &StatefulSetHistoryViewer{v.clientset} +} + +func (v *HistoryVisitor) VisitDaemonSet(kind kapps.GroupKindElement) { + v.result = &DaemonSetHistoryViewer{v.clientset} +} + +func (v *HistoryVisitor) VisitJob(kind kapps.GroupKindElement) {} +func (v *HistoryVisitor) VisitPod(kind kapps.GroupKindElement) {} +func (v *HistoryVisitor) VisitReplicaSet(kind kapps.GroupKindElement) {} +func (v *HistoryVisitor) VisitReplicationController(kind kapps.GroupKindElement) {} +func (v *HistoryVisitor) VisitCronJob(kind kapps.GroupKindElement) {} + +// HistoryViewerFor returns an implementation of HistoryViewer interface for the given schema kind +func HistoryViewerFor(kind schema.GroupKind, c kubernetes.Interface) (HistoryViewer, error) { + elem := kapps.GroupKindElement(kind) + visitor := &HistoryVisitor{ + clientset: c, } - return nil, fmt.Errorf("no history viewer has been implemented for %q", kind) + + // Determine which HistoryViewer we need here + err := elem.Accept(visitor) + + if err != nil { + return nil, fmt.Errorf("error retrieving history for %q, %v", kind.String(), err) + } + + if visitor.result == nil { + return nil, fmt.Errorf("no history viewer has been implemented for %q", kind.String()) + } + + return visitor.result, nil } type DeploymentHistoryViewer struct { - c clientset.Interface + c kubernetes.Interface } // ViewHistory returns a revision-to-replicaset map as the revision history of a deployment // TODO: this should be a describer func (h *DeploymentHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) { - versionedExtensionsClient := versionedExtensionsClientV1beta1(h.c) + versionedExtensionsClient := h.c.ExtensionsV1beta1() deployment, err := versionedExtensionsClient.Deployments(namespace).Get(name, metav1.GetOptions{}) if err != nil { return "", fmt.Errorf("failed to retrieve deployment %s: %v", name, err) @@ -138,7 +170,7 @@ func (h *DeploymentHistoryViewer) ViewHistory(namespace, name string, revision i func printTemplate(template *v1.PodTemplateSpec) (string, error) { buf := bytes.NewBuffer([]byte{}) internalTemplate := &api.PodTemplateSpec{} - if err := apiv1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(template, internalTemplate, nil); err != nil { + if err := apiv1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(template, internalTemplate, nil); err != nil { return "", fmt.Errorf("failed to convert podtemplate, %v", err) } w := printersinternal.NewPrefixWriter(buf) @@ -147,24 +179,21 @@ func printTemplate(template *v1.PodTemplateSpec) (string, error) { } type DaemonSetHistoryViewer struct { - c clientset.Interface + c kubernetes.Interface } // ViewHistory returns a revision-to-history map as the revision history of a deployment // TODO: this should be a describer func (h *DaemonSetHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) { - versionedAppsClient := versionedAppsClientV1beta1(h.c) - versionedExtensionsClient := versionedExtensionsClientV1beta1(h.c) - versionedObj, allHistory, err := controlledHistories(versionedAppsClient, versionedExtensionsClient, namespace, name, "DaemonSet") + ds, history, err := daemonSetHistory(h.c.ExtensionsV1beta1(), h.c.AppsV1beta1(), namespace, name) if err != nil { - return "", fmt.Errorf("unable to find history controlled by DaemonSet %s: %v", name, err) + return "", err } historyInfo := make(map[int64]*appsv1beta1.ControllerRevision) - for _, history := range allHistory { + for _, history := range history { // TODO: for now we assume revisions don't overlap, we may need to handle it historyInfo[history.Revision] = history } - if len(historyInfo) == 0 { return "No rollout history found.", nil } @@ -175,13 +204,7 @@ func (h *DaemonSetHistoryViewer) ViewHistory(namespace, name string, revision in if !ok { return "", fmt.Errorf("unable to find the specified revision") } - - versionedDS, ok := versionedObj.(*extensionsv1beta1.DaemonSet) - if !ok { - return "", fmt.Errorf("unexpected non-DaemonSet object returned: %v", versionedDS) - } - - dsOfHistory, err := applyHistory(versionedDS, history) + dsOfHistory, err := applyDaemonSetHistory(ds, history) if err != nil { return "", fmt.Errorf("unable to parse history %s", history.Name) } @@ -211,93 +234,46 @@ func (h *DaemonSetHistoryViewer) ViewHistory(namespace, name string, revision in } type StatefulSetHistoryViewer struct { - c clientset.Interface -} - -func getOwner(revision apps.ControllerRevision) *metav1.OwnerReference { - ownerRefs := revision.GetOwnerReferences() - for i := range ownerRefs { - owner := &ownerRefs[i] - if owner.Controller != nil && *owner.Controller == true { - return owner - } - } - return nil + c kubernetes.Interface } // ViewHistory returns a list of the revision history of a statefulset // TODO: this should be a describer // TODO: needs to implement detailed revision view func (h *StatefulSetHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) { + _, history, err := statefulSetHistory(h.c.AppsV1beta1(), namespace, name) + if err != nil { + return "", err + } - sts, err := h.c.Apps().StatefulSets(namespace).Get(name, metav1.GetOptions{}) - if err != nil { - return "", fmt.Errorf("failed to retrieve statefulset %s", err) - } - selector, err := metav1.LabelSelectorAsSelector(sts.Spec.Selector) - if err != nil { - return "", fmt.Errorf("failed to retrieve statefulset history %s", err) - } - revisions, err := h.c.Apps().ControllerRevisions(namespace).List(metav1.ListOptions{LabelSelector: selector.String()}) - if err != nil { - return "", fmt.Errorf("failed to retrieve statefulset history %s", err) - } - if len(revisions.Items) <= 0 { + if len(history) <= 0 { return "No rollout history found.", nil } - revisionNumbers := make([]int64, len(revisions.Items)) - for i := range revisions.Items { - if owner := getOwner(revisions.Items[i]); owner != nil && owner.UID == sts.UID { - revisionNumbers[i] = revisions.Items[i].Revision - } + revisions := make([]int64, len(history)) + for _, revision := range history { + revisions = append(revisions, revision.Revision) } - sliceutil.SortInts64(revisionNumbers) + sliceutil.SortInts64(revisions) return tabbedString(func(out io.Writer) error { fmt.Fprintf(out, "REVISION\n") - for _, r := range revisionNumbers { + for _, r := range revisions { fmt.Fprintf(out, "%d\n", r) } return nil }) } -// controlledHistories returns all ControllerRevisions controlled by the given API object -func controlledHistories(apps clientappsv1beta1.AppsV1beta1Interface, extensions clientextensionsv1beta1.ExtensionsV1beta1Interface, namespace, name, kind string) (runtime.Object, []*appsv1beta1.ControllerRevision, error) { - var obj runtime.Object - var labelSelector *metav1.LabelSelector - - switch kind { - case "DaemonSet": - ds, err := extensions.DaemonSets(namespace).Get(name, metav1.GetOptions{}) - if err != nil { - return nil, nil, fmt.Errorf("failed to retrieve DaemonSet %s: %v", name, err) - } - labelSelector = ds.Spec.Selector - obj = ds - case "StatefulSet": - ss, err := apps.StatefulSets(namespace).Get(name, metav1.GetOptions{}) - if err != nil { - return nil, nil, fmt.Errorf("failed to retrieve StatefulSet %s: %v", name, err) - } - labelSelector = ss.Spec.Selector - obj = ss - default: - return nil, nil, fmt.Errorf("unsupported API object kind: %s", kind) - } - +// controlledHistories returns all ControllerRevisions in namespace that selected by selector and owned by accessor +func controlledHistory( + apps clientappsv1beta1.AppsV1beta1Interface, + namespace string, + selector labels.Selector, + accessor metav1.Object) ([]*appsv1beta1.ControllerRevision, error) { var result []*appsv1beta1.ControllerRevision - selector, err := metav1.LabelSelectorAsSelector(labelSelector) - if err != nil { - return nil, nil, err - } historyList, err := apps.ControllerRevisions(namespace).List(metav1.ListOptions{LabelSelector: selector.String()}) if err != nil { - return nil, nil, err - } - accessor, err := meta.Accessor(obj) - if err != nil { - return nil, nil, fmt.Errorf("failed to obtain accessor for %s named %s: %v", kind, name, err) + return nil, err } for i := range historyList.Items { history := historyList.Items[i] @@ -306,16 +282,59 @@ func controlledHistories(apps clientappsv1beta1.AppsV1beta1Interface, extensions result = append(result, &history) } } - return obj, result, nil + return result, nil } -// applyHistory returns a specific revision of DaemonSet by applying the given history to a copy of the given DaemonSet -func applyHistory(ds *extensionsv1beta1.DaemonSet, history *appsv1beta1.ControllerRevision) (*extensionsv1beta1.DaemonSet, error) { - obj, err := api.Scheme.New(ds.GroupVersionKind()) +// daemonSetHistory returns the DaemonSet named name in namespace and all ControllerRevisions in its history. +func daemonSetHistory( + ext clientextv1beta1.ExtensionsV1beta1Interface, + apps clientappsv1beta1.AppsV1beta1Interface, + namespace, name string) (*extensionsv1beta1.DaemonSet, []*appsv1beta1.ControllerRevision, error) { + ds, err := ext.DaemonSets(namespace).Get(name, metav1.GetOptions{}) if err != nil { - return nil, err + return nil, nil, fmt.Errorf("failed to retrieve DaemonSet %s: %v", name, err) } - clone := obj.(*extensionsv1beta1.DaemonSet) + selector, err := metav1.LabelSelectorAsSelector(ds.Spec.Selector) + if err != nil { + return nil, nil, fmt.Errorf("failed to create selector for DaemonSet %s: %v", ds.Name, err) + } + accessor, err := meta.Accessor(ds) + if err != nil { + return nil, nil, fmt.Errorf("failed to create accessor for DaemonSet %s: %v", ds.Name, err) + } + history, err := controlledHistory(apps, ds.Namespace, selector, accessor) + if err != nil { + return nil, nil, fmt.Errorf("unable to find history controlled by DaemonSet %s: %v", ds.Name, err) + } + return ds, history, nil +} + +// statefulSetHistory returns the StatefulSet named name in namespace and all ControllerRevisions in its history. +func statefulSetHistory( + apps clientappsv1beta1.AppsV1beta1Interface, + namespace, name string) (*appsv1beta1.StatefulSet, []*appsv1beta1.ControllerRevision, error) { + sts, err := apps.StatefulSets(namespace).Get(name, metav1.GetOptions{}) + if err != nil { + return nil, nil, fmt.Errorf("failed to retrieve Statefulset %s: %s", name, err.Error()) + } + selector, err := metav1.LabelSelectorAsSelector(sts.Spec.Selector) + if err != nil { + return nil, nil, fmt.Errorf("failed to create selector for StatefulSet %s: %s", name, err.Error()) + } + accessor, err := meta.Accessor(sts) + if err != nil { + return nil, nil, fmt.Errorf("failed to obtain accessor for StatefulSet %s: %s", name, err.Error()) + } + history, err := controlledHistory(apps, namespace, selector, accessor) + if err != nil { + return nil, nil, fmt.Errorf("unable to find history controlled by StatefulSet %s: %v", name, err) + } + return sts, history, nil +} + +// applyDaemonSetHistory returns a specific revision of DaemonSet by applying the given history to a copy of the given DaemonSet +func applyDaemonSetHistory(ds *extensionsv1beta1.DaemonSet, history *appsv1beta1.ControllerRevision) (*extensionsv1beta1.DaemonSet, error) { + clone := ds.DeepCopy() cloneBytes, err := json.Marshal(clone) if err != nil { return nil, err diff --git a/pkg/kubectl/history_test.go b/pkg/kubectl/history_test.go new file mode 100644 index 00000000000..55a266935d8 --- /dev/null +++ b/pkg/kubectl/history_test.go @@ -0,0 +1,46 @@ +/* +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 kubectl + +import ( + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/kubernetes/fake" +) + +var historytests = map[schema.GroupKind]reflect.Type{ + {Group: "apps", Kind: "DaemonSet"}: reflect.TypeOf(&DaemonSetHistoryViewer{}), + {Group: "apps", Kind: "StatefulSet"}: reflect.TypeOf(&StatefulSetHistoryViewer{}), + {Group: "apps", Kind: "Deployment"}: reflect.TypeOf(&DeploymentHistoryViewer{}), +} + +func TestHistoryViewerFor(t *testing.T) { + fakeClientset := &fake.Clientset{} + + for kind, expectedType := range historytests { + result, err := HistoryViewerFor(kind, fakeClientset) + if err != nil { + t.Fatalf("error getting HistoryViewer for a %v: %v", kind.String(), err) + } + + if reflect.TypeOf(result) != expectedType { + t.Fatalf("unexpected output type (%v was expected but got %v)", expectedType, reflect.TypeOf(result)) + } + } +} diff --git a/pkg/kubectl/metricsutil/BUILD b/pkg/kubectl/metricsutil/BUILD index 66464edb199..84fc55a01d7 100644 --- a/pkg/kubectl/metricsutil/BUILD +++ b/pkg/kubectl/metricsutil/BUILD @@ -14,14 +14,15 @@ go_library( "//build/visible_to:pkg_kubectl_metricsutil_CONSUMERS", ], deps = [ - "//pkg/api:go_default_library", - "//pkg/api/validation:go_default_library", - "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//pkg/printers:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", "//vendor/k8s.io/metrics/pkg/apis/metrics/v1alpha1:go_default_library", ], ) diff --git a/pkg/kubectl/metricsutil/metrics_client.go b/pkg/kubectl/metricsutil/metrics_client.go index fb0781c0f38..909f91f67e8 100644 --- a/pkg/kubectl/metricsutil/metrics_client.go +++ b/pkg/kubectl/metricsutil/metrics_client.go @@ -24,8 +24,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/kubernetes/pkg/api/validation" - coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" + corev1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/kubernetes/pkg/apis/core/validation" metricsapi "k8s.io/metrics/pkg/apis/metrics/v1alpha1" ) @@ -46,14 +46,14 @@ var ( ) type HeapsterMetricsClient struct { - SVCClient coreclient.ServicesGetter + SVCClient corev1.ServicesGetter HeapsterNamespace string HeapsterScheme string HeapsterService string HeapsterPort string } -func NewHeapsterMetricsClient(svcClient coreclient.ServicesGetter, namespace, scheme, service, port string) *HeapsterMetricsClient { +func NewHeapsterMetricsClient(svcClient corev1.ServicesGetter, namespace, scheme, service, port string) *HeapsterMetricsClient { return &HeapsterMetricsClient{ SVCClient: svcClient, HeapsterNamespace: namespace, @@ -63,7 +63,7 @@ func NewHeapsterMetricsClient(svcClient coreclient.ServicesGetter, namespace, sc } } -func DefaultHeapsterMetricsClient(svcClient coreclient.ServicesGetter) *HeapsterMetricsClient { +func DefaultHeapsterMetricsClient(svcClient corev1.ServicesGetter) *HeapsterMetricsClient { return NewHeapsterMetricsClient(svcClient, DefaultHeapsterNamespace, DefaultHeapsterScheme, DefaultHeapsterService, DefaultHeapsterPort) } diff --git a/pkg/kubectl/metricsutil/metrics_printer.go b/pkg/kubectl/metricsutil/metrics_printer.go index 67833076669..b1bd5451bfe 100644 --- a/pkg/kubectl/metricsutil/metrics_printer.go +++ b/pkg/kubectl/metricsutil/metrics_printer.go @@ -21,16 +21,17 @@ import ( "io" "sort" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/printers" metricsapi "k8s.io/metrics/pkg/apis/metrics/v1alpha1" ) var ( - MeasuredResources = []api.ResourceName{ - api.ResourceCPU, - api.ResourceMemory, + MeasuredResources = []v1.ResourceName{ + v1.ResourceCPU, + v1.ResourceMemory, } NodeColumns = []string{"NAME", "CPU(cores)", "CPU%", "MEMORY(bytes)", "MEMORY%"} PodColumns = []string{"NAME", "CPU(cores)", "MEMORY(bytes)"} @@ -40,8 +41,8 @@ var ( type ResourceMetricsInfo struct { Name string - Metrics api.ResourceList - Available api.ResourceList + Metrics v1.ResourceList + Available v1.ResourceList } type TopCmdPrinter struct { @@ -52,7 +53,7 @@ func NewTopCmdPrinter(out io.Writer) *TopCmdPrinter { return &TopCmdPrinter{out: out} } -func (printer *TopCmdPrinter) PrintNodeMetrics(metrics []metricsapi.NodeMetrics, availableResources map[string]api.ResourceList) error { +func (printer *TopCmdPrinter) PrintNodeMetrics(metrics []metricsapi.NodeMetrics, availableResources map[string]v1.ResourceList) error { if len(metrics) == 0 { return nil } @@ -64,9 +65,9 @@ func (printer *TopCmdPrinter) PrintNodeMetrics(metrics []metricsapi.NodeMetrics, }) printColumnNames(w, NodeColumns) - var usage api.ResourceList + var usage v1.ResourceList for _, m := range metrics { - err := api.Scheme.Convert(&m.Usage, &usage, nil) + err := legacyscheme.Scheme.Convert(&m.Usage, &usage, nil) if err != nil { return err } @@ -118,15 +119,15 @@ func printColumnNames(out io.Writer, names []string) { } func printSinglePodMetrics(out io.Writer, m *metricsapi.PodMetrics, printContainersOnly bool, withNamespace bool) error { - containers := make(map[string]api.ResourceList) - podMetrics := make(api.ResourceList) + containers := make(map[string]v1.ResourceList) + podMetrics := make(v1.ResourceList) for _, res := range MeasuredResources { podMetrics[res], _ = resource.ParseQuantity("0") } for _, c := range m.Containers { - var usage api.ResourceList - err := api.Scheme.Convert(&c.Usage, &usage, nil) + var usage v1.ResourceList + err := legacyscheme.Scheme.Convert(&c.Usage, &usage, nil) if err != nil { return err } @@ -148,7 +149,7 @@ func printSinglePodMetrics(out io.Writer, m *metricsapi.PodMetrics, printContain printMetricsLine(out, &ResourceMetricsInfo{ Name: contName, Metrics: containers[contName], - Available: api.ResourceList{}, + Available: v1.ResourceList{}, }) } } else { @@ -158,7 +159,7 @@ func printSinglePodMetrics(out io.Writer, m *metricsapi.PodMetrics, printContain printMetricsLine(out, &ResourceMetricsInfo{ Name: m.Name, Metrics: podMetrics, - Available: api.ResourceList{}, + Available: v1.ResourceList{}, }) } return nil @@ -186,11 +187,11 @@ func printAllResourceUsages(out io.Writer, metrics *ResourceMetricsInfo) { } } -func printSingleResourceUsage(out io.Writer, resourceType api.ResourceName, quantity resource.Quantity) { +func printSingleResourceUsage(out io.Writer, resourceType v1.ResourceName, quantity resource.Quantity) { switch resourceType { - case api.ResourceCPU: + case v1.ResourceCPU: fmt.Fprintf(out, "%vm", quantity.MilliValue()) - case api.ResourceMemory: + case v1.ResourceMemory: fmt.Fprintf(out, "%vMi", quantity.Value()/(1024*1024)) default: fmt.Fprintf(out, "%v", quantity.Value()) diff --git a/pkg/kubectl/namespace.go b/pkg/kubectl/namespace.go index a649ced7ff2..03723cdaa84 100644 --- a/pkg/kubectl/namespace.go +++ b/pkg/kubectl/namespace.go @@ -19,8 +19,8 @@ package kubectl import ( "fmt" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" ) // NamespaceGeneratorV1 supports stable generation of a namespace @@ -65,7 +65,7 @@ func (g *NamespaceGeneratorV1) StructuredGenerate() (runtime.Object, error) { if err := g.validate(); err != nil { return nil, err } - namespace := &api.Namespace{} + namespace := &v1.Namespace{} namespace.Name = g.Name return namespace, nil } diff --git a/pkg/kubectl/namespace_test.go b/pkg/kubectl/namespace_test.go index 165a6220286..2a017ba806f 100644 --- a/pkg/kubectl/namespace_test.go +++ b/pkg/kubectl/namespace_test.go @@ -20,14 +20,14 @@ import ( "reflect" "testing" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" ) func TestNamespaceGenerate(t *testing.T) { tests := []struct { params map[string]interface{} - expected *api.Namespace + expected *v1.Namespace expectErr bool index int }{ @@ -35,7 +35,7 @@ func TestNamespaceGenerate(t *testing.T) { params: map[string]interface{}{ "name": "foo", }, - expected: &api.Namespace{ + expected: &v1.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, @@ -92,8 +92,8 @@ func TestNamespaceGenerate(t *testing.T) { case !test.expectErr && err == nil: // do nothing and drop through } - if !reflect.DeepEqual(obj.(*api.Namespace), test.expected) { - t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*api.Namespace)) + if !reflect.DeepEqual(obj.(*v1.Namespace), test.expected) { + t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*v1.Namespace)) } } } diff --git a/pkg/kubectl/pdb.go b/pkg/kubectl/pdb.go index 08d8a74b875..df2cc8f53b3 100644 --- a/pkg/kubectl/pdb.go +++ b/pkg/kubectl/pdb.go @@ -18,12 +18,11 @@ package kubectl import ( "fmt" - "os" + policy "k8s.io/api/policy/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/kubernetes/pkg/apis/policy" ) // PodDisruptionBudgetV1Generator supports stable generation of a pod disruption budget. @@ -39,7 +38,7 @@ var _ StructuredGenerator = &PodDisruptionBudgetV1Generator{} func (PodDisruptionBudgetV1Generator) ParamNames() []GeneratorParam { return []GeneratorParam{ {"name", true}, - {"min-available", true}, + {"min-available", false}, {"selector", true}, } } @@ -51,15 +50,15 @@ func (s PodDisruptionBudgetV1Generator) Generate(params map[string]interface{}) } name, isString := params["name"].(string) if !isString { - return nil, fmt.Errorf("expected string, saw %v for 'name'", name) + return nil, fmt.Errorf("expected string, found %T for 'name'", params["name"]) } minAvailable, isString := params["min-available"].(string) if !isString { - return nil, fmt.Errorf("expected string, found %v", minAvailable) + return nil, fmt.Errorf("expected string, found %T for 'min-available'", params["min-available"]) } selector, isString := params["selector"].(string) if !isString { - return nil, fmt.Errorf("expected string, found %v", selector) + return nil, fmt.Errorf("expected string, found %T for 'selector'", params["selector"]) } delegate := &PodDisruptionBudgetV1Generator{Name: name, MinAvailable: minAvailable, Selector: selector} return delegate.StructuredGenerate() @@ -123,7 +122,7 @@ func (PodDisruptionBudgetV2Generator) ParamNames() []GeneratorParam { {"name", true}, {"min-available", false}, {"max-unavailable", false}, - {"selector", false}, + {"selector", true}, } } @@ -135,22 +134,22 @@ func (s PodDisruptionBudgetV2Generator) Generate(params map[string]interface{}) name, isString := params["name"].(string) if !isString { - return nil, fmt.Errorf("expected string, saw %v for 'name'", name) + return nil, fmt.Errorf("expected string, found %T for 'name'", params["name"]) } minAvailable, isString := params["min-available"].(string) if !isString { - return nil, fmt.Errorf("expected string, found %v", minAvailable) + return nil, fmt.Errorf("expected string, found %T for 'min-available'", params["min-available"]) } - maxUnavailable, isString := params["max-available"].(string) + maxUnavailable, isString := params["max-unavailable"].(string) if !isString { - return nil, fmt.Errorf("expected string, found %v", maxUnavailable) + return nil, fmt.Errorf("expected string, found %T for 'max-unavailable'", params["max-unavailable"]) } selector, isString := params["selector"].(string) if !isString { - return nil, fmt.Errorf("expected string, found %v", selector) + return nil, fmt.Errorf("expected string, found %T for 'selector'", params["selector"]) } delegate := &PodDisruptionBudgetV2Generator{Name: name, MinAvailable: minAvailable, MaxUnavailable: maxUnavailable, Selector: selector} return delegate.StructuredGenerate() @@ -167,15 +166,6 @@ func (s *PodDisruptionBudgetV2Generator) StructuredGenerate() (runtime.Object, e return nil, err } - if len(s.MaxUnavailable) == 0 && len(s.MinAvailable) == 0 { - s.MinAvailable = "1" - - // This behavior is intended for backward compatibility. - // TODO: remove in Kubernetes 1.8 - fmt.Fprintln(os.Stderr, "Deprecated behavior in kubectl create pdb: Defaulting min-available to 1. "+ - "Kubernetes 1.8 will remove this default, and one of min-available/max-available must be specified. ") - } - if len(s.MaxUnavailable) > 0 { maxUnavailable := intstr.Parse(s.MaxUnavailable) return &policy.PodDisruptionBudget{ @@ -213,6 +203,9 @@ func (s *PodDisruptionBudgetV2Generator) validate() error { if len(s.Selector) == 0 { return fmt.Errorf("a selector must be specified") } + if len(s.MaxUnavailable) == 0 && len(s.MinAvailable) == 0 { + return fmt.Errorf("one of min-available or max-unavailable must be specified") + } if len(s.MaxUnavailable) > 0 && len(s.MinAvailable) > 0 { return fmt.Errorf("min-available and max-unavailable cannot be both specified") } diff --git a/pkg/kubectl/pdb_test.go b/pkg/kubectl/pdb_test.go new file mode 100644 index 00000000000..35860dbdfdf --- /dev/null +++ b/pkg/kubectl/pdb_test.go @@ -0,0 +1,338 @@ +/* +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 kubectl + +import ( + "reflect" + "testing" + + policy "k8s.io/api/policy/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" +) + +func TestPodDisruptionBudgetV1Generate(t *testing.T) { + name := "foo" + minAvailable := "5" + minAvailableIS := intstr.Parse(minAvailable) + defaultMinAvailableIS := intstr.Parse("1") + selector := "app=foo" + labelSelector, err := metav1.ParseToLabelSelector(selector) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + tests := map[string]struct { + params map[string]interface{} + expectErrMsg string + expectPDB *policy.PodDisruptionBudget + }{ + "test-valid-use": { + params: map[string]interface{}{ + "name": name, + "min-available": minAvailable, + "selector": selector, + }, + expectPDB: &policy.PodDisruptionBudget{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: policy.PodDisruptionBudgetSpec{ + MinAvailable: &minAvailableIS, + Selector: labelSelector, + }, + }, + }, + "test-missing-name-param": { + params: map[string]interface{}{ + "min-available": minAvailable, + "selector": selector, + }, + expectErrMsg: "Parameter: name is required", + }, + "test-blank-name-param": { + params: map[string]interface{}{ + "name": "", + "min-available": minAvailable, + "selector": selector, + }, + expectErrMsg: "Parameter: name is required", + }, + "test-invalid-name-param": { + params: map[string]interface{}{ + "name": 1, + "min-available": minAvailable, + "selector": selector, + }, + expectErrMsg: "expected string, found int for 'name'", + }, + "test-missing-min-available-param": { + params: map[string]interface{}{ + "name": name, + "selector": selector, + }, + expectErrMsg: "expected string, found for 'min-available'", + }, + "test-blank-min-available-param": { + params: map[string]interface{}{ + "name": name, + "min-available": "", + "selector": selector, + }, + expectPDB: &policy.PodDisruptionBudget{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: policy.PodDisruptionBudgetSpec{ + MinAvailable: &defaultMinAvailableIS, + Selector: labelSelector, + }, + }, + }, + "test-invalid-min-available-param": { + params: map[string]interface{}{ + "name": name, + "min-available": 1, + "selector": selector, + }, + expectErrMsg: "expected string, found int for 'min-available'", + }, + "test-missing-selector-param": { + params: map[string]interface{}{ + "name": name, + "min-available": minAvailable, + }, + expectErrMsg: "Parameter: selector is required", + }, + "test-blank-selector-param": { + params: map[string]interface{}{ + "name": name, + "min-available": minAvailable, + "selector": "", + }, + expectErrMsg: "Parameter: selector is required", + }, + "test-invalid-selector-param": { + params: map[string]interface{}{ + "name": name, + "min-available": minAvailable, + "selector": 1, + }, + expectErrMsg: "expected string, found int for 'selector'", + }, + } + + generator := PodDisruptionBudgetV1Generator{} + for name, test := range tests { + obj, err := generator.Generate(test.params) + switch { + case test.expectErrMsg != "" && err != nil: + if err.Error() != test.expectErrMsg { + t.Errorf("test '%s': expect error '%s', but saw '%s'", name, test.expectErrMsg, err.Error()) + } + continue + case test.expectErrMsg != "" && err == nil: + t.Errorf("test '%s': expected error '%s' and didn't get one", name, test.expectErrMsg) + continue + case test.expectErrMsg == "" && err != nil: + t.Errorf("test '%s': unexpected error %s", name, err.Error()) + continue + } + if !reflect.DeepEqual(obj.(*policy.PodDisruptionBudget), test.expectPDB) { + t.Errorf("test '%s': expected:\n%#v\nsaw:\n%#v", name, test.expectPDB, obj.(*policy.PodDisruptionBudget)) + } + } +} + +func TestPodDisruptionBudgetV2Generate(t *testing.T) { + name := "foo" + minAvailable := "1" + minAvailableIS := intstr.Parse(minAvailable) + maxUnavailable := "5%" + maxUnavailableIS := intstr.Parse(maxUnavailable) + selector := "app=foo" + labelSelector, err := metav1.ParseToLabelSelector(selector) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + tests := map[string]struct { + params map[string]interface{} + expectErrMsg string + expectPDB *policy.PodDisruptionBudget + }{ + "test-valid-min-available": { + params: map[string]interface{}{ + "name": name, + "min-available": minAvailable, + "max-unavailable": "", + "selector": selector, + }, + expectPDB: &policy.PodDisruptionBudget{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: policy.PodDisruptionBudgetSpec{ + MinAvailable: &minAvailableIS, + Selector: labelSelector, + }, + }, + }, + "test-valid-max-available": { + params: map[string]interface{}{ + "name": name, + "min-available": "", + "max-unavailable": maxUnavailable, + "selector": selector, + }, + expectPDB: &policy.PodDisruptionBudget{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: policy.PodDisruptionBudgetSpec{ + MaxUnavailable: &maxUnavailableIS, + Selector: labelSelector, + }, + }, + }, + "test-missing-name-param": { + params: map[string]interface{}{ + "min-available": "", + "max-unavailable": "", + "selector": selector, + }, + expectErrMsg: "Parameter: name is required", + }, + "test-blank-name-param": { + params: map[string]interface{}{ + "name": "", + "min-available": "", + "max-unavailable": "", + "selector": selector, + }, + expectErrMsg: "Parameter: name is required", + }, + "test-invalid-name-param": { + params: map[string]interface{}{ + "name": 1, + "min-available": "", + "max-unavailable": "", + "selector": selector, + }, + expectErrMsg: "expected string, found int for 'name'", + }, + "test-missing-min-available-param": { + params: map[string]interface{}{ + "name": name, + "max-unavailable": "", + "selector": selector, + }, + expectErrMsg: "expected string, found for 'min-available'", + }, + "test-invalid-min-available-param": { + params: map[string]interface{}{ + "name": name, + "min-available": 1, + "max-unavailable": "", + "selector": selector, + }, + expectErrMsg: "expected string, found int for 'min-available'", + }, + "test-missing-max-available-param": { + params: map[string]interface{}{ + "name": name, + "min-available": "", + "selector": selector, + }, + expectErrMsg: "expected string, found for 'max-unavailable'", + }, + "test-invalid-max-available-param": { + params: map[string]interface{}{ + "name": name, + "min-available": "", + "max-unavailable": 1, + "selector": selector, + }, + expectErrMsg: "expected string, found int for 'max-unavailable'", + }, + "test-blank-min-available-max-unavailable-param": { + params: map[string]interface{}{ + "name": name, + "min-available": "", + "max-unavailable": "", + "selector": selector, + }, + expectErrMsg: "one of min-available or max-unavailable must be specified", + }, + "test-min-available-max-unavailable-param": { + params: map[string]interface{}{ + "name": name, + "min-available": minAvailable, + "max-unavailable": maxUnavailable, + "selector": selector, + }, + expectErrMsg: "min-available and max-unavailable cannot be both specified", + }, + "test-missing-selector-param": { + params: map[string]interface{}{ + "name": name, + "min-available": "", + "max-unavailable": "", + }, + expectErrMsg: "Parameter: selector is required", + }, + "test-blank-selector-param": { + params: map[string]interface{}{ + "name": name, + "min-available": "", + "max-unavailable": "", + "selector": "", + }, + expectErrMsg: "Parameter: selector is required", + }, + "test-invalid-selector-param": { + params: map[string]interface{}{ + "name": name, + "min-available": "", + "max-unavailable": "", + "selector": 1, + }, + expectErrMsg: "expected string, found int for 'selector'", + }, + } + + generator := PodDisruptionBudgetV2Generator{} + for name, test := range tests { + obj, err := generator.Generate(test.params) + switch { + case test.expectErrMsg != "" && err != nil: + if err.Error() != test.expectErrMsg { + t.Errorf("test '%s': expect error '%s', but saw '%s'", name, test.expectErrMsg, err.Error()) + } + continue + case test.expectErrMsg != "" && err == nil: + t.Errorf("test '%s': expected error '%s' and didn't get one", name, test.expectErrMsg) + continue + case test.expectErrMsg == "" && err != nil: + t.Errorf("test '%s': unexpected error %s", name, err.Error()) + continue + } + if !reflect.DeepEqual(obj.(*policy.PodDisruptionBudget), test.expectPDB) { + t.Errorf("test '%s': expected:\n%#v\nsaw:\n%#v", name, test.expectPDB, obj.(*policy.PodDisruptionBudget)) + } + } +} diff --git a/pkg/kubectl/plugins/BUILD b/pkg/kubectl/plugins/BUILD index 4c497cac940..a2762649a90 100644 --- a/pkg/kubectl/plugins/BUILD +++ b/pkg/kubectl/plugins/BUILD @@ -44,7 +44,7 @@ go_test( "plugins_test.go", "runner_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubectl/plugins", - library = ":go_default_library", deps = ["//vendor/github.com/spf13/pflag:go_default_library"], ) diff --git a/pkg/kubectl/plugins/examples/aging/aging.rb b/pkg/kubectl/plugins/examples/aging/aging.rb index ed9a87fffc6..3a60e94c431 100755 --- a/pkg/kubectl/plugins/examples/aging/aging.rb +++ b/pkg/kubectl/plugins/examples/aging/aging.rb @@ -22,7 +22,8 @@ class Numeric end end -pods_json = `kubectl get pods -o json` +namespace = ENV['KUBECTL_PLUGINS_CURRENT_NAMESPACE'] || 'default' +pods_json = `kubectl --namespace #{namespace} get pods -o json` pods_parsed = JSON.parse(pods_json) puts "The Magnificent Aging Plugin." diff --git a/pkg/kubectl/plugins/examples/aging/plugin.yaml b/pkg/kubectl/plugins/examples/aging/plugin.yaml index 07dc2022a68..87275dc095b 100644 --- a/pkg/kubectl/plugins/examples/aging/plugin.yaml +++ b/pkg/kubectl/plugins/examples/aging/plugin.yaml @@ -2,7 +2,4 @@ name: "aging" shortDesc: "Aging shows pods by age" longDesc: > Aging shows pods from the current namespace by age. - Once we have plugin support for global flags through - env vars (planned for V1) we'll be able to switch - between namespaces using the --namespace flag. command: ./aging.rb diff --git a/pkg/kubectl/plugins/plugins.go b/pkg/kubectl/plugins/plugins.go index f2cc17847b3..7eca6a2f35f 100644 --- a/pkg/kubectl/plugins/plugins.go +++ b/pkg/kubectl/plugins/plugins.go @@ -65,7 +65,7 @@ func (p Plugin) Validate() error { if len(p.Name) == 0 || len(p.ShortDesc) == 0 || (len(p.Command) == 0 && len(p.Tree) == 0) { return ErrIncompletePlugin } - if strings.Index(p.Name, " ") > -1 { + if strings.Contains(p.Name, " ") { return ErrInvalidPluginName } for _, flag := range p.Flags { @@ -102,7 +102,7 @@ func (f Flag) Validate() error { if len(f.Name) == 0 || len(f.Desc) == 0 { return ErrIncompleteFlag } - if strings.Index(f.Name, " ") > -1 { + if strings.Contains(f.Name, " ") { return ErrInvalidFlagName } return f.ValidateShorthand() diff --git a/pkg/kubectl/priorityclass.go b/pkg/kubectl/priorityclass.go new file mode 100644 index 00000000000..fd600ae3253 --- /dev/null +++ b/pkg/kubectl/priorityclass.go @@ -0,0 +1,85 @@ +/* +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 kubectl + +import ( + "fmt" + + scheduling "k8s.io/api/scheduling/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// PriorityClassV1Generator supports stable generation of a priorityClass. +type PriorityClassV1Generator struct { + Name string + Value int32 + GlobalDefault bool + Description string +} + +// Ensure it supports the generator pattern that uses parameters specified during construction. +var _ StructuredGenerator = &PriorityClassV1Generator{} + +func (PriorityClassV1Generator) ParamNames() []GeneratorParam { + return []GeneratorParam{ + {"name", true}, + {"value", true}, + {"global-default", false}, + {"description", false}, + } +} + +func (s PriorityClassV1Generator) Generate(params map[string]interface{}) (runtime.Object, error) { + if err := ValidateParams(s.ParamNames(), params); err != nil { + return nil, err + } + + name, found := params["name"].(string) + if !found { + return nil, fmt.Errorf("expected string, saw %v for 'name'", name) + } + + value, found := params["value"].(int32) + if !found { + return nil, fmt.Errorf("expected int32, found %v", value) + } + + globalDefault, found := params["global-default"].(bool) + if !found { + return nil, fmt.Errorf("expected bool, found %v", globalDefault) + } + + description, found := params["description"].(string) + if !found { + return nil, fmt.Errorf("expected string, found %v", description) + } + delegate := &PriorityClassV1Generator{Name: name, Value: value, GlobalDefault: globalDefault, Description: description} + return delegate.StructuredGenerate() +} + +// StructuredGenerate outputs a priorityClass object using the configured fields. +func (s *PriorityClassV1Generator) StructuredGenerate() (runtime.Object, error) { + return &scheduling.PriorityClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: s.Name, + }, + Value: s.Value, + GlobalDefault: s.GlobalDefault, + Description: s.Description, + }, nil +} diff --git a/pkg/kubectl/priorityclass_test.go b/pkg/kubectl/priorityclass_test.go new file mode 100644 index 00000000000..2702ce33bb3 --- /dev/null +++ b/pkg/kubectl/priorityclass_test.go @@ -0,0 +1,90 @@ +/* +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 kubectl + +import ( + scheduling "k8s.io/api/scheduling/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "reflect" + "testing" +) + +func TestPriorityClassV1Generator(t *testing.T) { + tests := map[string]struct { + params map[string]interface{} + expected *scheduling.PriorityClass + expectErr bool + }{ + "test valid case": { + params: map[string]interface{}{ + "name": "foo", + "value": int32(1000), + "global-default": false, + "description": "high priority class", + }, + expected: &scheduling.PriorityClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Value: int32(1000), + GlobalDefault: false, + Description: "high priority class", + }, + expectErr: false, + }, + "test valid case that as default priority": { + params: map[string]interface{}{ + "name": "foo", + "value": int32(1000), + "global-default": true, + "description": "high priority class", + }, + expected: &scheduling.PriorityClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Value: int32(1000), + GlobalDefault: true, + Description: "high priority class", + }, + expectErr: false, + }, + "test missing required param": { + params: map[string]interface{}{ + "name": "foo", + "global-default": true, + "description": "high priority class", + }, + expectErr: true, + }, + } + + generator := PriorityClassV1Generator{} + for name, test := range tests { + obj, err := generator.Generate(test.params) + if !test.expectErr && err != nil { + t.Errorf("%s: unexpected error: %v", name, err) + } + if test.expectErr && err != nil { + continue + } + if !reflect.DeepEqual(obj.(*scheduling.PriorityClass), test.expected) { + t.Errorf("%s:\nexpected:\n%#v\nsaw:\n%#v", name, test.expected, obj.(*scheduling.PriorityClass)) + } + } +} diff --git a/pkg/kubectl/proxy/BUILD b/pkg/kubectl/proxy/BUILD index 58a68c07a6b..a1b5e471bf0 100644 --- a/pkg/kubectl/proxy/BUILD +++ b/pkg/kubectl/proxy/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["proxy_server_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubectl/proxy", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/util/proxy:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", diff --git a/pkg/kubectl/quota.go b/pkg/kubectl/quota.go index 173f568c671..d51b74af951 100644 --- a/pkg/kubectl/quota.go +++ b/pkg/kubectl/quota.go @@ -20,8 +20,8 @@ import ( "fmt" "strings" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" ) // ResourceQuotaGeneratorV1 supports stable generation of a resource quota @@ -79,7 +79,7 @@ func (g *ResourceQuotaGeneratorV1) StructuredGenerate() (runtime.Object, error) return nil, err } - resourceList, err := populateResourceList(g.Hard) + resourceList, err := populateResourceListV1(g.Hard) if err != nil { return nil, err } @@ -89,7 +89,7 @@ func (g *ResourceQuotaGeneratorV1) StructuredGenerate() (runtime.Object, error) return nil, err } - resourceQuota := &api.ResourceQuota{} + resourceQuota := &v1.ResourceQuota{} resourceQuota.Name = g.Name resourceQuota.Spec.Hard = resourceList resourceQuota.Spec.Scopes = scopes @@ -104,14 +104,14 @@ func (r *ResourceQuotaGeneratorV1) validate() error { return nil } -func parseScopes(spec string) ([]api.ResourceQuotaScope, error) { +func parseScopes(spec string) ([]v1.ResourceQuotaScope, error) { // empty input gets a nil response to preserve generator test expected behaviors if spec == "" { return nil, nil } scopes := strings.Split(spec, ",") - result := make([]api.ResourceQuotaScope, 0, len(scopes)) + result := make([]v1.ResourceQuotaScope, 0, len(scopes)) for _, scope := range scopes { // intentionally do not verify the scope against the valid scope list. This is done by the apiserver anyway. @@ -119,7 +119,7 @@ func parseScopes(spec string) ([]api.ResourceQuotaScope, error) { return nil, fmt.Errorf("invalid resource quota scope \"\"") } - result = append(result, api.ResourceQuotaScope(scope)) + result = append(result, v1.ResourceQuotaScope(scope)) } return result, nil } diff --git a/pkg/kubectl/quota_test.go b/pkg/kubectl/quota_test.go index 8529dd676a2..990a9379d7e 100644 --- a/pkg/kubectl/quota_test.go +++ b/pkg/kubectl/quota_test.go @@ -20,20 +20,20 @@ import ( "reflect" "testing" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" ) func TestQuotaGenerate(t *testing.T) { hard := "cpu=10,memory=5G,pods=10,services=7" - resourceQuotaSpecList, err := populateResourceList(hard) + resourceQuotaSpecList, err := populateResourceListV1(hard) if err != nil { t.Errorf("unexpected error: %v", err) } tests := map[string]struct { params map[string]interface{} - expected *api.ResourceQuota + expected *v1.ResourceQuota expectErr bool }{ "test-valid-use": { @@ -41,11 +41,11 @@ func TestQuotaGenerate(t *testing.T) { "name": "foo", "hard": hard, }, - expected: &api.ResourceQuota{ + expected: &v1.ResourceQuota{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, - Spec: api.ResourceQuotaSpec{Hard: resourceQuotaSpecList}, + Spec: v1.ResourceQuotaSpec{Hard: resourceQuotaSpecList}, }, expectErr: false, }, @@ -61,15 +61,15 @@ func TestQuotaGenerate(t *testing.T) { "hard": hard, "scopes": "BestEffort,NotTerminating", }, - expected: &api.ResourceQuota{ + expected: &v1.ResourceQuota{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, - Spec: api.ResourceQuotaSpec{ + Spec: v1.ResourceQuotaSpec{ Hard: resourceQuotaSpecList, - Scopes: []api.ResourceQuotaScope{ - api.ResourceQuotaScopeBestEffort, - api.ResourceQuotaScopeNotTerminating, + Scopes: []v1.ResourceQuotaScope{ + v1.ResourceQuotaScopeBestEffort, + v1.ResourceQuotaScopeNotTerminating, }, }, }, @@ -81,11 +81,11 @@ func TestQuotaGenerate(t *testing.T) { "hard": hard, "scopes": "", }, - expected: &api.ResourceQuota{ + expected: &v1.ResourceQuota{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, - Spec: api.ResourceQuotaSpec{Hard: resourceQuotaSpecList}, + Spec: v1.ResourceQuotaSpec{Hard: resourceQuotaSpecList}, }, expectErr: false, }, @@ -108,8 +108,8 @@ func TestQuotaGenerate(t *testing.T) { if test.expectErr && err != nil { continue } - if !reflect.DeepEqual(obj.(*api.ResourceQuota), test.expected) { - t.Errorf("%s:\nexpected:\n%#v\nsaw:\n%#v", name, test.expected, obj.(*api.ResourceQuota)) + if !reflect.DeepEqual(obj.(*v1.ResourceQuota), test.expected) { + t.Errorf("%s:\nexpected:\n%#v\nsaw:\n%#v", name, test.expected, obj.(*v1.ResourceQuota)) } } } diff --git a/pkg/kubectl/resource/BUILD b/pkg/kubectl/resource/BUILD index cfacc3dacfb..e10135dcda6 100644 --- a/pkg/kubectl/resource/BUILD +++ b/pkg/kubectl/resource/BUILD @@ -8,7 +8,6 @@ go_library( name = "go_default_library", srcs = [ "builder.go", - "categories.go", "doc.go", "helper.go", "interfaces.go", @@ -22,10 +21,11 @@ go_library( "//build/visible_to:pkg_kubectl_resource_CONSUMERS", ], deps = [ - "//pkg/api:go_default_library", + "//pkg/kubectl/categories:go_default_library", "//pkg/kubectl/validation:go_default_library", "//vendor/golang.org/x/text/encoding/unicode:go_default_library", "//vendor/golang.org/x/text/transform:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -39,7 +39,6 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/discovery:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", ], ) @@ -48,7 +47,6 @@ go_test( name = "go_default_test", srcs = [ "builder_test.go", - "categories_test.go", "helper_test.go", "visitor_test.go", ], @@ -56,14 +54,12 @@ go_test( "//examples:config", "//test/fixtures", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubectl/resource", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/testapi:go_default_library", - "//pkg/api/testing:go_default_library", + "//pkg/kubectl/categories:go_default_library", + "//pkg/kubectl/scheme:go_default_library", "//vendor/github.com/ghodss/yaml:go_default_library", - "//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", @@ -73,11 +69,11 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer/streaming:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/version:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/discovery:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/rest/fake:go_default_library", "//vendor/k8s.io/client-go/rest/watch:go_default_library", diff --git a/pkg/kubectl/resource/builder.go b/pkg/kubectl/resource/builder.go index 51d82a84b31..53669138114 100644 --- a/pkg/kubectl/resource/builder.go +++ b/pkg/kubectl/resource/builder.go @@ -26,12 +26,11 @@ import ( "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/kubernetes/pkg/kubectl/categories" "k8s.io/kubernetes/pkg/kubectl/validation" ) @@ -44,8 +43,12 @@ const defaultHttpGetAttempts int = 3 // from the command line and converting them to a list of resources to iterate // over using the Visitor interface. type Builder struct { - mapper *Mapper - categoryExpander CategoryExpander + categoryExpander categories.CategoryExpander + + // mapper is set explicitly by resource builders + mapper *Mapper + internal *Mapper + unstructured *Mapper errs []error @@ -53,9 +56,12 @@ type Builder struct { stream bool dir bool - selector *string + labelSelector *string + fieldSelector *string selectAll bool includeUninitialized bool + limitChunks int64 + requestTransforms []RequestTransform resources []string @@ -68,9 +74,6 @@ type Builder struct { defaultNamespace bool requireNamespace bool - isLocal bool - isUnstructured bool - flatten bool latest bool @@ -86,8 +89,6 @@ type Builder struct { schema validation.Schema } -var LocalUnstructuredBuilderError = fmt.Errorf("Unstructured objects cannot be handled with a local builder - Local and Unstructured attributes cannot be used in conjunction") - var missingResourceError = fmt.Errorf(`You must provide one or more resources by argument or filename. Example resource specifications include: '-f rsrc.yaml' @@ -118,10 +119,13 @@ type resourceTuple struct { Name string } -// NewBuilder creates a builder that operates on generic objects. -func NewBuilder(mapper meta.RESTMapper, categoryExpander CategoryExpander, typer runtime.ObjectTyper, clientMapper ClientMapper, decoder runtime.Decoder) *Builder { +// NewBuilder creates a builder that operates on generic objects. At least one of +// internal or unstructured must be specified. +// TODO: Add versioned client (although versioned is still lossy) +func NewBuilder(internal, unstructured *Mapper, categoryExpander categories.CategoryExpander) *Builder { return &Builder{ - mapper: &Mapper{typer, mapper, clientMapper, decoder}, + internal: internal, + unstructured: unstructured, categoryExpander: categoryExpander, requireObject: true, } @@ -167,35 +171,64 @@ func (b *Builder) FilenameParam(enforceNamespace bool, filenameOptions *Filename return b } -// Local wraps the builder's clientMapper in a DisabledClientMapperForMapping -func (b *Builder) Local(mapperFunc ClientMapperFunc) *Builder { - if b.isUnstructured { - b.errs = append(b.errs, LocalUnstructuredBuilderError) +// Unstructured updates the builder so that it will request and send unstructured +// objects. Unstructured objects preserve all fields sent by the server in a map format +// based on the object's JSON structure which means no data is lost when the client +// reads and then writes an object. Use this mode in preference to Internal unless you +// are working with Go types directly. +func (b *Builder) Unstructured() *Builder { + if b.unstructured == nil { + b.errs = append(b.errs, fmt.Errorf("no unstructured mapper provided")) return b } - - b.isLocal = true - b.mapper.ClientMapper = DisabledClientForMapping{ClientMapper: ClientMapperFunc(mapperFunc)} + if b.mapper != nil { + b.errs = append(b.errs, fmt.Errorf("another mapper was already selected, cannot use unstructured types")) + return b + } + b.mapper = b.unstructured return b } -// Unstructured updates the builder's ClientMapper, RESTMapper, -// ObjectTyper, and codec for working with unstructured api objects -func (b *Builder) Unstructured(mapperFunc ClientMapperFunc, mapper meta.RESTMapper, typer runtime.ObjectTyper) *Builder { - if b.isLocal { - b.errs = append(b.errs, LocalUnstructuredBuilderError) +// Internal updates the builder so that it will convert objects off the wire +// into the internal form if necessary. Using internal types is lossy - fields added +// to the server will not be seen by the client code and may result in failure. Only +// use this mode when working offline, or when generating patches to send to the server. +// Use Unstructured if you are reading an object and performing a POST or PUT. +func (b *Builder) Internal() *Builder { + if b.internal == nil { + b.errs = append(b.errs, fmt.Errorf("no internal mapper provided")) return b } - - b.isUnstructured = true - b.mapper.RESTMapper = mapper - b.mapper.ObjectTyper = typer - b.mapper.Decoder = unstructured.UnstructuredJSONScheme - b.mapper.ClientMapper = ClientMapperFunc(mapperFunc) - + if b.mapper != nil { + b.errs = append(b.errs, fmt.Errorf("another mapper was already selected, cannot use internal types")) + return b + } + b.mapper = b.internal return b } +// LocalParam calls Local() if local is true. +func (b *Builder) LocalParam(local bool) *Builder { + if local { + b.Local() + } + return b +} + +// Local will avoid asking the server for results. +func (b *Builder) Local() *Builder { + mapper := *b.mapper + mapper.ClientMapper = DisabledClientForMapping{ClientMapper: mapper.ClientMapper} + b.mapper = &mapper + return b +} + +// Mapper returns a copy of the current mapper. +func (b *Builder) Mapper() *Mapper { + mapper := *b.mapper + return &mapper +} + // URL accepts a number of URLs directly. func (b *Builder) URL(httpAttemptCount int, urls ...*url.URL) *Builder { for _, u := range urls { @@ -290,24 +323,45 @@ func (b *Builder) ResourceNames(resource string, names ...string) *Builder { return b } -// SelectorParam defines a selector that should be applied to the object types to load. +// LabelSelectorParam defines a selector that should be applied to the object types to load. // This will not affect files loaded from disk or URL. If the parameter is empty it is -// a no-op - to select all resources invoke `b.Selector(labels.Everything.String)`. -func (b *Builder) SelectorParam(s string) *Builder { +// a no-op - to select all resources invoke `b.LabelSelector(labels.Everything.String)`. +func (b *Builder) LabelSelectorParam(s string) *Builder { selector := strings.TrimSpace(s) if len(selector) == 0 { return b } if b.selectAll { - b.errs = append(b.errs, fmt.Errorf("found non empty selector %q with previously set 'all' parameter. ", s)) + b.errs = append(b.errs, fmt.Errorf("found non-empty label selector %q with previously set 'all' parameter. ", s)) return b } - return b.Selector(selector) + return b.LabelSelector(selector) } -// Selector accepts a selector directly, and if non nil will trigger a list action. -func (b *Builder) Selector(selector string) *Builder { - b.selector = &selector +// LabelSelector accepts a selector directly and will filter the resulting list by that object. +// Use LabelSelectorParam instead for user input. +func (b *Builder) LabelSelector(selector string) *Builder { + if len(selector) == 0 { + return b + } + + b.labelSelector = &selector + return b +} + +// FieldSelectorParam defines a selector that should be applied to the object types to load. +// This will not affect files loaded from disk or URL. If the parameter is empty it is +// a no-op - to select all resources. +func (b *Builder) FieldSelectorParam(s string) *Builder { + s = strings.TrimSpace(s) + if len(s) == 0 { + return b + } + if b.selectAll { + b.errs = append(b.errs, fmt.Errorf("found non-empty field selector %q with previously set 'all' parameter. ", s)) + return b + } + b.fieldSelector = &s return b } @@ -355,9 +409,24 @@ func (b *Builder) RequireNamespace() *Builder { return b } +// RequestChunksOf attempts to load responses from the server in batches of size limit +// to avoid long delays loading and transferring very large lists. If unset defaults to +// no chunking. +func (b *Builder) RequestChunksOf(chunkSize int64) *Builder { + b.limitChunks = chunkSize + return b +} + +// TransformRequests alters API calls made by clients requested from this builder. Pass +// an empty list to clear modifiers. +func (b *Builder) TransformRequests(opts ...RequestTransform) *Builder { + b.requestTransforms = opts + return b +} + // SelectEverythingParam func (b *Builder) SelectAllParam(selectAll bool) *Builder { - if selectAll && b.selector != nil { + if selectAll && (b.labelSelector != nil || b.fieldSelector != nil) { b.errs = append(b.errs, fmt.Errorf("setting 'all' parameter but found a non empty selector. ")) return b } @@ -402,9 +471,9 @@ func (b *Builder) ResourceTypeOrNameArgs(allowEmptySelector bool, args ...string b.ResourceTypes(SplitResourceArgument(args[0])...) case len(args) == 1: b.ResourceTypes(SplitResourceArgument(args[0])...) - if b.selector == nil && allowEmptySelector { + if b.labelSelector == nil && allowEmptySelector { selector := labels.Everything().String() - b.selector = &selector + b.labelSelector = &selector } case len(args) == 0: default: @@ -593,7 +662,7 @@ func (b *Builder) visitorResult() *Result { if b.selectAll { selector := labels.Everything().String() - b.selector = &selector + b.labelSelector = &selector } // visit items specified by paths @@ -602,7 +671,7 @@ func (b *Builder) visitorResult() *Result { } // visit selectors - if b.selector != nil { + if b.labelSelector != nil || b.fieldSelector != nil { return b.visitBySelector() } @@ -642,6 +711,14 @@ func (b *Builder) visitBySelector() *Result { return result } + var labelSelector, fieldSelector string + if b.labelSelector != nil { + labelSelector = *b.labelSelector + } + if b.fieldSelector != nil { + fieldSelector = *b.fieldSelector + } + visitors := []Visitor{} for _, mapping := range mappings { client, err := b.mapper.ClientForMapping(mapping) @@ -649,11 +726,12 @@ func (b *Builder) visitBySelector() *Result { result.err = err return result } + client = NewClientWithOptions(client, b.requestTransforms...) selectorNamespace := b.namespace if mapping.Scope.Name() != meta.RESTScopeNameNamespace { selectorNamespace = "" } - visitors = append(visitors, NewSelector(client, mapping, selectorNamespace, *b.selector, b.export, b.includeUninitialized)) + visitors = append(visitors, NewSelector(client, mapping, selectorNamespace, labelSelector, fieldSelector, b.export, b.includeUninitialized, b.limitChunks)) } if b.continueOnError { result.visitor = EagerVisitorList(visitors) @@ -698,6 +776,7 @@ func (b *Builder) visitByResource() *Result { result.err = err return result } + client = NewClientWithOptions(client, b.requestTransforms...) clients[s] = client } @@ -726,7 +805,13 @@ func (b *Builder) visitByResource() *Result { } } - info := NewInfo(client, mapping, selectorNamespace, tuple.Name, b.export) + info := &Info{ + Client: client, + Mapping: mapping, + Namespace: selectorNamespace, + Name: tuple.Name, + Export: b.export, + } items = append(items, info) } @@ -785,7 +870,13 @@ func (b *Builder) visitByName() *Result { visitors := []Visitor{} for _, name := range b.names { - info := NewInfo(client, mapping, selectorNamespace, name, b.export) + info := &Info{ + Client: client, + Mapping: mapping, + Namespace: selectorNamespace, + Name: name, + Export: b.export, + } visitors = append(visitors, info) } result.visitor = VisitorList(visitors) @@ -828,12 +919,12 @@ func (b *Builder) visitByPaths() *Result { } visitors = NewDecoratedVisitor(visitors, RetrieveLatest) } - if b.selector != nil { - selector, err := labels.Parse(*b.selector) + if b.labelSelector != nil { + selector, err := labels.Parse(*b.labelSelector) if err != nil { - return result.withError(fmt.Errorf("the provided selector %q is not valid: %v", b.selector, err)) + return result.withError(fmt.Errorf("the provided selector %q is not valid: %v", b.labelSelector, err)) } - visitors = NewFilteredVisitor(visitors, FilterBySelector(selector)) + visitors = NewFilteredVisitor(visitors, FilterByLabelSelector(selector)) } result.visitor = visitors result.sources = b.paths @@ -846,6 +937,7 @@ func (b *Builder) visitByPaths() *Result { // for further iteration. func (b *Builder) Do() *Result { r := b.visitorResult() + r.mapper = b.Mapper() if r.err != nil { return r } diff --git a/pkg/kubectl/resource/builder_test.go b/pkg/kubectl/resource/builder_test.go index d2614c3a485..90c4f6be741 100644 --- a/pkg/kubectl/resource/builder_test.go +++ b/pkg/kubectl/resource/builder_test.go @@ -37,15 +37,24 @@ import ( "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" + "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/runtime/serializer/streaming" 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" utiltesting "k8s.io/client-go/util/testing" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/testapi" - apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/kubectl/categories" + "k8s.io/kubernetes/pkg/kubectl/scheme" +) + +var ( + corev1GV = schema.GroupVersion{Version: "v1"} + corev1Codec = scheme.Codecs.CodecForVersions(scheme.Codecs.LegacyCodec(corev1GV), scheme.Codecs.UniversalDecoder(corev1GV), corev1GV, corev1GV) + metaAccessor = meta.NewAccessor() + restmapper = scheme.Registry.RESTMapper() ) func stringBody(body string) io.ReadCloser { @@ -54,7 +63,7 @@ func stringBody(body string) io.ReadCloser { func watchBody(events ...watch.Event) string { buf := &bytes.Buffer{} - codec := testapi.Default.Codec() + codec := corev1Codec enc := restclientwatch.NewEncoder(streaming.NewEncoder(buf, codec), codec) for _, e := range events { enc.Encode(&e) @@ -71,8 +80,8 @@ func fakeClient() ClientMapper { func fakeClientWith(testName string, t *testing.T, data map[string]string) ClientMapper { return ClientMapperFunc(func(*meta.RESTMapping) (RESTClient, error) { return &fake.RESTClient{ - APIRegistry: api.Registry, - NegotiatedSerializer: testapi.Default.NegotiatedSerializer(), + GroupVersion: corev1GV, + NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { p := req.URL.Path q := req.URL.RawQuery @@ -95,30 +104,30 @@ func fakeClientWith(testName string, t *testing.T, data map[string]string) Clien }) } -func testData() (*api.PodList, *api.ServiceList) { - pods := &api.PodList{ +func testData() (*v1.PodList, *v1.ServiceList) { + pods := &v1.PodList{ ListMeta: metav1.ListMeta{ ResourceVersion: "15", }, - Items: []api.Pod{ + Items: []v1.Pod{ { ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"}, - Spec: apitesting.DeepEqualSafePodSpec(), + Spec: V1DeepEqualSafePodSpec(), }, { ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "test", ResourceVersion: "11"}, - Spec: apitesting.DeepEqualSafePodSpec(), + Spec: V1DeepEqualSafePodSpec(), }, }, } - svc := &api.ServiceList{ + svc := &v1.ServiceList{ ListMeta: metav1.ListMeta{ ResourceVersion: "16", }, - Items: []api.Service{ + Items: []v1.Service{ { ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"}, - Spec: api.ServiceSpec{ + Spec: v1.ServiceSpec{ Type: "ClusterIP", SessionAffinity: "None", }, @@ -128,13 +137,13 @@ func testData() (*api.PodList, *api.ServiceList) { return pods, svc } -func streamTestData() (io.Reader, *api.PodList, *api.ServiceList) { +func streamTestData() (io.Reader, *v1.PodList, *v1.ServiceList) { pods, svc := testData() r, w := io.Pipe() go func() { defer w.Close() - w.Write([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), pods))) - w.Write([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), svc))) + w.Write([]byte(runtime.EncodeOrDie(corev1Codec, pods))) + w.Write([]byte(runtime.EncodeOrDie(corev1Codec, svc))) }() return r, pods, svc } @@ -147,14 +156,14 @@ func JSONToYAMLOrDie(in []byte) []byte { return data } -func streamYAMLTestData() (io.Reader, *api.PodList, *api.ServiceList) { +func streamYAMLTestData() (io.Reader, *v1.PodList, *v1.ServiceList) { pods, svc := testData() r, w := io.Pipe() go func() { defer w.Close() - w.Write(JSONToYAMLOrDie([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), pods)))) + w.Write(JSONToYAMLOrDie([]byte(runtime.EncodeOrDie(corev1Codec, pods)))) w.Write([]byte("\n---\n")) - w.Write(JSONToYAMLOrDie([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), svc)))) + w.Write(JSONToYAMLOrDie([]byte(runtime.EncodeOrDie(corev1Codec, svc)))) }() return r, pods, svc } @@ -163,7 +172,7 @@ func streamTestObject(obj runtime.Object) io.Reader { r, w := io.Pipe() go func() { defer w.Close() - w.Write([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), obj))) + w.Write([]byte(runtime.EncodeOrDie(corev1Codec, obj))) }() return r } @@ -192,7 +201,7 @@ func (v *testVisitor) Objects() []runtime.Object { var aPod string = ` { "kind": "Pod", - "apiVersion": "` + api.Registry.GroupOrDie(api.GroupName).GroupVersion.String() + `", + "apiVersion": "` + corev1GV.String() + `", "metadata": { "name": "busybox{id}", "labels": { @@ -219,7 +228,7 @@ var aPod string = ` var aRC string = ` { "kind": "ReplicationController", - "apiVersion": "` + api.Registry.GroupOrDie(api.GroupName).GroupVersion.String() + `", + "apiVersion": "` + corev1GV.String() + `", "metadata": { "name": "busybox{id}", "labels": { @@ -254,8 +263,25 @@ var aRC string = ` } ` +func newDefaultBuilder() *Builder { + return newDefaultBuilderWith(fakeClient()) +} + +func newDefaultBuilderWith(client ClientMapper) *Builder { + return NewBuilder( + &Mapper{ + RESTMapper: restmapper, + ObjectTyper: scheme.Scheme, + ClientMapper: client, + Decoder: corev1Codec, + }, + nil, + categories.LegacyCategoryExpander, + ).Internal() +} + func TestPathBuilderAndVersionedObjectNotDefaulted(t *testing.T) { - b := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClient(), testapi.Default.Codec()). + b := newDefaultBuilder(). FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../../test/fixtures/pkg/kubectl/builder/kitten-rc.yaml"}}) test := &testVisitor{} @@ -270,32 +296,32 @@ func TestPathBuilderAndVersionedObjectNotDefaulted(t *testing.T) { if info.Name != "update-demo-kitten" || info.Namespace != "" || info.Object == nil { t.Errorf("unexpected info: %#v", info) } - version, ok := info.VersionedObject.(*v1.ReplicationController) + obj := info.AsVersioned() + version, ok := obj.(*v1.ReplicationController) // versioned object does not have defaulting applied - if info.VersionedObject == nil || !ok || version.Spec.Replicas != nil { - t.Errorf("unexpected versioned object: %#v", info.VersionedObject) + if obj == nil || !ok || version.Spec.Replicas != nil { + t.Errorf("unexpected versioned object: %#v", obj) } } func TestNodeBuilder(t *testing.T) { - node := &api.Node{ + node := &v1.Node{ ObjectMeta: metav1.ObjectMeta{Name: "node1", Namespace: "should-not-have", ResourceVersion: "10"}, - Spec: api.NodeSpec{}, - Status: api.NodeStatus{ - Capacity: api.ResourceList{ - api.ResourceCPU: resource.MustParse("1000m"), - api.ResourceMemory: resource.MustParse("1Mi"), + Spec: v1.NodeSpec{}, + Status: v1.NodeStatus{ + Capacity: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1000m"), + v1.ResourceMemory: resource.MustParse("1Mi"), }, }, } r, w := io.Pipe() go func() { defer w.Close() - w.Write([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), node))) + w.Write([]byte(runtime.EncodeOrDie(corev1Codec, node))) }() - b := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClient(), testapi.Default.Codec()). - NamespaceParam("test").Stream(r, "STDIN") + b := newDefaultBuilder().NamespaceParam("test").Stream(r, "STDIN") test := &testVisitor{} @@ -349,16 +375,16 @@ func TestPathBuilderWithMultiple(t *testing.T) { directory string expectedNames []string }{ - {"pod", &api.Pod{}, false, "../../../examples/pod", []string{"nginx"}}, - {"recursive-pod", &api.Pod{}, true, fmt.Sprintf("%s/recursive/pod", tmpDir), []string{"busybox0", "busybox1"}}, - {"rc", &api.ReplicationController{}, false, "../../../examples/guestbook/legacy/redis-master-controller.yaml", []string{"redis-master"}}, - {"recursive-rc", &api.ReplicationController{}, true, fmt.Sprintf("%s/recursive/rc", tmpDir), []string{"busybox0", "busybox1"}}, - {"hardlink", &api.Pod{}, false, fmt.Sprintf("%s/inode/hardlink/busybox-link.json", tmpDir), []string{"busybox0"}}, - {"hardlink", &api.Pod{}, true, fmt.Sprintf("%s/inode/hardlink/busybox-link.json", tmpDir), []string{"busybox0"}}, + {"pod", &v1.Pod{}, false, "../../../examples/pod", []string{"nginx"}}, + {"recursive-pod", &v1.Pod{}, true, fmt.Sprintf("%s/recursive/pod", tmpDir), []string{"busybox0", "busybox1"}}, + {"rc", &v1.ReplicationController{}, false, "../../../examples/guestbook/legacy/redis-master-controller.yaml", []string{"redis-master"}}, + {"recursive-rc", &v1.ReplicationController{}, true, fmt.Sprintf("%s/recursive/rc", tmpDir), []string{"busybox0", "busybox1"}}, + {"hardlink", &v1.Pod{}, false, fmt.Sprintf("%s/inode/hardlink/busybox-link.json", tmpDir), []string{"busybox0"}}, + {"hardlink", &v1.Pod{}, true, fmt.Sprintf("%s/inode/hardlink/busybox-link.json", tmpDir), []string{"busybox0"}}, } for _, test := range tests { - b := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClient(), testapi.Default.Codec()). + b := newDefaultBuilder(). FilenameParam(false, &FilenameOptions{Recursive: test.recursive, Filenames: []string{test.directory}}). NamespaceParam("test").DefaultNamespace() @@ -374,12 +400,12 @@ func TestPathBuilderWithMultiple(t *testing.T) { for i, v := range info { switch test.object.(type) { - case *api.Pod: - if _, ok := v.Object.(*api.Pod); !ok || v.Name != test.expectedNames[i] || v.Namespace != "test" { + case *v1.Pod: + if _, ok := v.Object.(*v1.Pod); !ok || v.Name != test.expectedNames[i] || v.Namespace != "test" { t.Errorf("unexpected info: %#v", v) } - case *api.ReplicationController: - if _, ok := v.Object.(*api.ReplicationController); !ok || v.Name != test.expectedNames[i] || v.Namespace != "test" { + case *v1.ReplicationController: + if _, ok := v.Object.(*v1.ReplicationController); !ok || v.Name != test.expectedNames[i] || v.Namespace != "test" { t.Errorf("unexpected info: %#v", v) } } @@ -417,7 +443,7 @@ func TestPathBuilderWithMultipleInvalid(t *testing.T) { } for _, test := range tests { - b := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClient(), testapi.Default.Codec()). + b := newDefaultBuilder(). FilenameParam(false, &FilenameOptions{Recursive: test.recursive, Filenames: []string{test.directory}}). NamespaceParam("test").DefaultNamespace() @@ -432,7 +458,7 @@ func TestPathBuilderWithMultipleInvalid(t *testing.T) { } func TestDirectoryBuilder(t *testing.T) { - b := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClient(), testapi.Default.Codec()). + b := newDefaultBuilder(). FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../../examples/guestbook/legacy"}}). NamespaceParam("test").DefaultNamespace() @@ -459,11 +485,11 @@ func TestDirectoryBuilder(t *testing.T) { func TestNamespaceOverride(t *testing.T) { s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusOK) - w.Write([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), &api.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "test"}}))) + w.Write([]byte(runtime.EncodeOrDie(corev1Codec, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "test"}}))) })) defer s.Close() - b := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClient(), testapi.Default.Codec()). + b := newDefaultBuilder(). FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{s.URL}}). NamespaceParam("test") @@ -474,7 +500,7 @@ func TestNamespaceOverride(t *testing.T) { t.Fatalf("unexpected response: %v %#v", err, test.Infos) } - b = NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClient(), testapi.Default.Codec()). + b = newDefaultBuilder(). FilenameParam(true, &FilenameOptions{Recursive: false, Filenames: []string{s.URL}}). NamespaceParam("test") @@ -489,12 +515,12 @@ func TestNamespaceOverride(t *testing.T) { func TestURLBuilder(t *testing.T) { s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusOK) - w.Write([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), &api.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "test"}}))) - w.Write([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), &api.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "test1"}}))) + w.Write([]byte(runtime.EncodeOrDie(corev1Codec, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "test"}}))) + w.Write([]byte(runtime.EncodeOrDie(corev1Codec, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "test1"}}))) })) defer s.Close() - b := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClient(), testapi.Default.Codec()). + b := newDefaultBuilder(). FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{s.URL}}). NamespaceParam("foo") @@ -519,11 +545,11 @@ func TestURLBuilder(t *testing.T) { func TestURLBuilderRequireNamespace(t *testing.T) { s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusOK) - w.Write([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), &api.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "test"}}))) + w.Write([]byte(runtime.EncodeOrDie(corev1Codec, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "test"}}))) })) defer s.Close() - b := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClient(), testapi.Default.Codec()). + b := newDefaultBuilder(). FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{s.URL}}). NamespaceParam("test").RequireNamespace() @@ -538,10 +564,9 @@ func TestURLBuilderRequireNamespace(t *testing.T) { func TestResourceByName(t *testing.T) { pods, _ := testData() - b := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClientWith("", t, map[string]string{ - "/namespaces/test/pods/foo": runtime.EncodeOrDie(testapi.Default.Codec(), &pods.Items[0]), - }), testapi.Default.Codec()). - NamespaceParam("test") + b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{ + "/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]), + })).NamespaceParam("test") test := &testVisitor{} singleItemImplied := false @@ -571,12 +596,12 @@ func TestResourceByName(t *testing.T) { func TestMultipleResourceByTheSameName(t *testing.T) { pods, svcs := testData() - b := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClientWith("", t, map[string]string{ - "/namespaces/test/pods/foo": runtime.EncodeOrDie(testapi.Default.Codec(), &pods.Items[0]), - "/namespaces/test/pods/baz": runtime.EncodeOrDie(testapi.Default.Codec(), &pods.Items[1]), - "/namespaces/test/services/foo": runtime.EncodeOrDie(testapi.Default.Codec(), &svcs.Items[0]), - "/namespaces/test/services/baz": runtime.EncodeOrDie(testapi.Default.Codec(), &svcs.Items[0]), - }), testapi.Default.Codec()). + b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{ + "/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]), + "/namespaces/test/pods/baz": runtime.EncodeOrDie(corev1Codec, &pods.Items[1]), + "/namespaces/test/services/foo": runtime.EncodeOrDie(corev1Codec, &svcs.Items[0]), + "/namespaces/test/services/baz": runtime.EncodeOrDie(corev1Codec, &svcs.Items[0]), + })). NamespaceParam("test") test := &testVisitor{} @@ -601,13 +626,32 @@ func TestMultipleResourceByTheSameName(t *testing.T) { } } +func TestRequestModifier(t *testing.T) { + var got *rest.Request + b := newDefaultBuilderWith(fakeClientWith("test", t, nil)). + NamespaceParam("foo"). + TransformRequests(func(req *rest.Request) { + got = req + }). + ResourceNames("", "services/baz"). + RequireObject(false) + + i, err := b.Do().Infos() + if err != nil { + t.Fatal(err) + } + req := i[0].Client.Get() + if got != req { + t.Fatalf("request was not received by modifier: %#v", req) + } +} + func TestResourceNames(t *testing.T) { pods, svc := testData() - b := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClientWith("", t, map[string]string{ - "/namespaces/test/pods/foo": runtime.EncodeOrDie(testapi.Default.Codec(), &pods.Items[0]), - "/namespaces/test/services/baz": runtime.EncodeOrDie(testapi.Default.Codec(), &svc.Items[0]), - }), testapi.Default.Codec()). - NamespaceParam("test") + b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{ + "/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]), + "/namespaces/test/services/baz": runtime.EncodeOrDie(corev1Codec, &svc.Items[0]), + })).NamespaceParam("test") test := &testVisitor{} @@ -631,11 +675,10 @@ func TestResourceNames(t *testing.T) { func TestResourceNamesWithoutResource(t *testing.T) { pods, svc := testData() - b := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClientWith("", t, map[string]string{ - "/namespaces/test/pods/foo": runtime.EncodeOrDie(testapi.Default.Codec(), &pods.Items[0]), - "/namespaces/test/services/baz": runtime.EncodeOrDie(testapi.Default.Codec(), &svc.Items[0]), - }), testapi.Default.Codec()). - NamespaceParam("test") + b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{ + "/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]), + "/namespaces/test/services/baz": runtime.EncodeOrDie(corev1Codec, &svc.Items[0]), + })).NamespaceParam("test") test := &testVisitor{} @@ -652,8 +695,7 @@ func TestResourceNamesWithoutResource(t *testing.T) { } func TestResourceByNameWithoutRequireObject(t *testing.T) { - b := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClientWith("", t, map[string]string{}), testapi.Default.Codec()). - NamespaceParam("test") + b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{})).NamespaceParam("test") test := &testVisitor{} singleItemImplied := false @@ -686,11 +728,11 @@ func TestResourceByNameWithoutRequireObject(t *testing.T) { func TestResourceByNameAndEmptySelector(t *testing.T) { pods, _ := testData() - b := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClientWith("", t, map[string]string{ - "/namespaces/test/pods/foo": runtime.EncodeOrDie(testapi.Default.Codec(), &pods.Items[0]), - }), testapi.Default.Codec()). + b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{ + "/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]), + })). NamespaceParam("test"). - SelectorParam(""). + LabelSelectorParam(""). ResourceTypeOrNameArgs(true, "pods", "foo") singleItemImplied := false @@ -711,14 +753,14 @@ func TestResourceByNameAndEmptySelector(t *testing.T) { } } -func TestSelector(t *testing.T) { +func TestLabelSelector(t *testing.T) { pods, svc := testData() - labelKey := metav1.LabelSelectorQueryParam(api.Registry.GroupOrDie(api.GroupName).GroupVersion.String()) - b := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClientWith("", t, map[string]string{ - "/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(testapi.Default.Codec(), pods), - "/namespaces/test/services?" + labelKey + "=a%3Db": runtime.EncodeOrDie(testapi.Default.Codec(), svc), - }), testapi.Default.Codec()). - SelectorParam("a=b"). + labelKey := metav1.LabelSelectorQueryParam(corev1GV.String()) + b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{ + "/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, pods), + "/namespaces/test/services?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, svc), + })). + LabelSelectorParam("a=b"). NamespaceParam("test"). Flatten() @@ -744,9 +786,53 @@ func TestSelector(t *testing.T) { } } -func TestSelectorRequiresKnownTypes(t *testing.T) { - b := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClient(), testapi.Default.Codec()). - SelectorParam("a=b"). +func TestLabelSelectorRequiresKnownTypes(t *testing.T) { + b := newDefaultBuilder(). + LabelSelectorParam("a=b"). + NamespaceParam("test"). + ResourceTypes("unknown") + + if b.Do().Err() == nil { + t.Errorf("unexpected non-error") + } +} + +func TestFieldSelector(t *testing.T) { + pods, svc := testData() + fieldKey := metav1.FieldSelectorQueryParam(corev1GV.String()) + b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{ + "/namespaces/test/pods?" + fieldKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, pods), + "/namespaces/test/services?" + fieldKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, svc), + })). + FieldSelectorParam("a=b"). + NamespaceParam("test"). + Flatten() + + test := &testVisitor{} + singleItemImplied := false + + if b.Do().Err() == nil { + t.Errorf("unexpected non-error") + } + + b.ResourceTypeOrNameArgs(true, "pods,service") + + err := b.Do().IntoSingleItemImplied(&singleItemImplied).Visit(test.Handle) + if err != nil || singleItemImplied || len(test.Infos) != 3 { + t.Fatalf("unexpected response: %v %t %#v", err, singleItemImplied, test.Infos) + } + if !apiequality.Semantic.DeepDerivative([]runtime.Object{&pods.Items[0], &pods.Items[1], &svc.Items[0]}, test.Objects()) { + t.Errorf("unexpected visited objects: %#v", test.Objects()) + } + + if _, err := b.Do().ResourceMapping(); err == nil { + t.Errorf("unexpected non-error") + } +} + +func TestFieldSelectorRequiresKnownTypes(t *testing.T) { + b := newDefaultBuilder(). + FieldSelectorParam("a=b"). NamespaceParam("test"). ResourceTypes("unknown") @@ -756,8 +842,8 @@ func TestSelectorRequiresKnownTypes(t *testing.T) { } func TestSingleResourceType(t *testing.T) { - b := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClient(), testapi.Default.Codec()). - SelectorParam("a=b"). + b := newDefaultBuilder(). + LabelSelectorParam("a=b"). SingleResourceType(). ResourceTypeOrNameArgs(true, "pods,services") @@ -820,13 +906,12 @@ func TestResourceTuple(t *testing.T) { if requireObject { pods, _ := testData() expectedRequests = map[string]string{ - "/namespaces/test/pods/foo": runtime.EncodeOrDie(testapi.Default.Codec(), &pods.Items[0]), - "/namespaces/test/pods/bar": runtime.EncodeOrDie(testapi.Default.Codec(), &pods.Items[0]), - "/nodes/foo": runtime.EncodeOrDie(testapi.Default.Codec(), &api.Node{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}), + "/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]), + "/namespaces/test/pods/bar": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]), + "/nodes/foo": runtime.EncodeOrDie(corev1Codec, &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}), } } - - b := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClientWith(k, t, expectedRequests), testapi.Default.Codec()). + b := newDefaultBuilderWith(fakeClientWith(k, t, expectedRequests)). NamespaceParam("test").DefaultNamespace(). ResourceTypeOrNameArgs(true, testCase.args...).RequireObject(requireObject) @@ -857,7 +942,7 @@ func TestResourceTuple(t *testing.T) { func TestStream(t *testing.T) { r, pods, rc := streamTestData() - b := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClient(), testapi.Default.Codec()). + b := newDefaultBuilder(). NamespaceParam("test").Stream(r, "STDIN").Flatten() test := &testVisitor{} @@ -874,7 +959,7 @@ func TestStream(t *testing.T) { func TestYAMLStream(t *testing.T) { r, pods, rc := streamYAMLTestData() - b := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClient(), testapi.Default.Codec()). + b := newDefaultBuilder(). NamespaceParam("test").Stream(r, "STDIN").Flatten() test := &testVisitor{} @@ -891,7 +976,7 @@ func TestYAMLStream(t *testing.T) { func TestMultipleObject(t *testing.T) { r, pods, svc := streamTestData() - obj, err := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClient(), testapi.Default.Codec()). + obj, err := newDefaultBuilder(). NamespaceParam("test").Stream(r, "STDIN").Flatten(). Do().Object() @@ -899,11 +984,11 @@ func TestMultipleObject(t *testing.T) { t.Fatalf("unexpected error: %v", err) } - expected := &api.List{ - Items: []runtime.Object{ - &pods.Items[0], - &pods.Items[1], - &svc.Items[0], + expected := &v1.List{ + Items: []runtime.RawExtension{ + {Object: &pods.Items[0]}, + {Object: &pods.Items[1]}, + {Object: &svc.Items[0]}, }, } if !apiequality.Semantic.DeepDerivative(expected, obj) { @@ -913,7 +998,7 @@ func TestMultipleObject(t *testing.T) { func TestContinueOnErrorVisitor(t *testing.T) { r, _, _ := streamTestData() - req := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClient(), testapi.Default.Codec()). + req := newDefaultBuilder(). ContinueOnError(). NamespaceParam("test").Stream(r, "STDIN").Flatten(). Do() @@ -942,7 +1027,7 @@ func TestContinueOnErrorVisitor(t *testing.T) { } func TestSingleItemImpliedObject(t *testing.T) { - obj, err := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClient(), testapi.Default.Codec()). + obj, err := newDefaultBuilder(). NamespaceParam("test").DefaultNamespace(). FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../../examples/guestbook/legacy/redis-master-controller.yaml"}}). Flatten(). @@ -952,7 +1037,7 @@ func TestSingleItemImpliedObject(t *testing.T) { t.Fatalf("unexpected error: %v", err) } - rc, ok := obj.(*api.ReplicationController) + rc, ok := obj.(*v1.ReplicationController) if !ok { t.Fatalf("unexpected object: %#v", obj) } @@ -962,7 +1047,7 @@ func TestSingleItemImpliedObject(t *testing.T) { } func TestSingleItemImpliedObjectNoExtension(t *testing.T) { - obj, err := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClient(), testapi.Default.Codec()). + obj, err := newDefaultBuilder(). NamespaceParam("test").DefaultNamespace(). FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../../examples/pod"}}). Flatten(). @@ -972,7 +1057,7 @@ func TestSingleItemImpliedObjectNoExtension(t *testing.T) { t.Fatalf("unexpected error: %v", err) } - pod, ok := obj.(*api.Pod) + pod, ok := obj.(*v1.Pod) if !ok { t.Fatalf("unexpected object: %#v", obj) } @@ -982,9 +1067,9 @@ func TestSingleItemImpliedObjectNoExtension(t *testing.T) { } func TestSingleItemImpliedRootScopedObject(t *testing.T) { - node := &api.Node{ObjectMeta: metav1.ObjectMeta{Name: "test"}, Spec: api.NodeSpec{ExternalID: "test"}} + node := &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "test"}, Spec: v1.NodeSpec{ExternalID: "test"}} r := streamTestObject(node) - infos, err := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClient(), testapi.Default.Codec()). + infos, err := newDefaultBuilder(). NamespaceParam("test").DefaultNamespace(). Stream(r, "STDIN"). Flatten(). @@ -997,7 +1082,7 @@ func TestSingleItemImpliedRootScopedObject(t *testing.T) { if infos[0].Namespace != "" { t.Errorf("namespace should be empty: %#v", infos[0]) } - n, ok := infos[0].Object.(*api.Node) + n, ok := infos[0].Object.(*v1.Node) if !ok { t.Fatalf("unexpected object: %#v", infos[0].Object) } @@ -1008,11 +1093,11 @@ func TestSingleItemImpliedRootScopedObject(t *testing.T) { func TestListObject(t *testing.T) { pods, _ := testData() - labelKey := metav1.LabelSelectorQueryParam(api.Registry.GroupOrDie(api.GroupName).GroupVersion.String()) - b := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClientWith("", t, map[string]string{ - "/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(testapi.Default.Codec(), pods), - }), testapi.Default.Codec()). - SelectorParam("a=b"). + labelKey := metav1.LabelSelectorQueryParam(corev1GV.String()) + b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{ + "/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, pods), + })). + LabelSelectorParam("a=b"). NamespaceParam("test"). ResourceTypeOrNameArgs(true, "pods"). Flatten() @@ -1022,7 +1107,7 @@ func TestListObject(t *testing.T) { t.Fatalf("unexpected error: %v", err) } - list, ok := obj.(*api.List) + list, ok := obj.(*v1.List) if !ok { t.Fatalf("unexpected object: %#v", obj) } @@ -1041,12 +1126,12 @@ func TestListObject(t *testing.T) { func TestListObjectWithDifferentVersions(t *testing.T) { pods, svc := testData() - labelKey := metav1.LabelSelectorQueryParam(api.Registry.GroupOrDie(api.GroupName).GroupVersion.String()) - obj, err := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClientWith("", t, map[string]string{ - "/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(testapi.Default.Codec(), pods), - "/namespaces/test/services?" + labelKey + "=a%3Db": runtime.EncodeOrDie(testapi.Default.Codec(), svc), - }), testapi.Default.Codec()). - SelectorParam("a=b"). + labelKey := metav1.LabelSelectorQueryParam(corev1GV.String()) + obj, err := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{ + "/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, pods), + "/namespaces/test/services?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, svc), + })). + LabelSelectorParam("a=b"). NamespaceParam("test"). ResourceTypeOrNameArgs(true, "pods,services"). Flatten(). @@ -1056,7 +1141,7 @@ func TestListObjectWithDifferentVersions(t *testing.T) { t.Fatalf("unexpected error: %v", err) } - list, ok := obj.(*api.List) + list, ok := obj.(*v1.List) if !ok { t.Fatalf("unexpected object: %#v", obj) } @@ -1068,12 +1153,12 @@ func TestListObjectWithDifferentVersions(t *testing.T) { func TestWatch(t *testing.T) { _, svc := testData() - w, err := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClientWith("", t, map[string]string{ + w, err := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{ "/namespaces/test/services?fieldSelector=metadata.name%3Dredis-master&resourceVersion=12&watch=true": watchBody(watch.Event{ Type: watch.Added, Object: &svc.Items[0], }), - }), testapi.Default.Codec()). + })). NamespaceParam("test").DefaultNamespace(). FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../../examples/guestbook/redis-master-service.yaml"}}).Flatten(). Do().Watch("12") @@ -1089,7 +1174,7 @@ func TestWatch(t *testing.T) { if obj.Type != watch.Added { t.Fatalf("unexpected watch event %#v", obj) } - service, ok := obj.Object.(*api.Service) + service, ok := obj.Object.(*v1.Service) if !ok { t.Fatalf("unexpected object: %#v", obj) } @@ -1100,7 +1185,7 @@ func TestWatch(t *testing.T) { } func TestWatchMultipleError(t *testing.T) { - _, err := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClient(), testapi.Default.Codec()). + _, err := newDefaultBuilder(). NamespaceParam("test").DefaultNamespace(). FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../../examples/guestbook/legacy/redis-master-controller.yaml"}}).Flatten(). FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../../examples/guestbook/legacy/redis-master-controller.yaml"}}).Flatten(). @@ -1113,21 +1198,21 @@ func TestWatchMultipleError(t *testing.T) { func TestLatest(t *testing.T) { r, _, _ := streamTestData() - newPod := &api.Pod{ + newPod := &v1.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "13"}, } - newPod2 := &api.Pod{ + newPod2 := &v1.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "test", ResourceVersion: "14"}, } - newSvc := &api.Service{ + newSvc := &v1.Service{ ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "15"}, } - b := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClientWith("", t, map[string]string{ - "/namespaces/test/pods/foo": runtime.EncodeOrDie(testapi.Default.Codec(), newPod), - "/namespaces/test/pods/bar": runtime.EncodeOrDie(testapi.Default.Codec(), newPod2), - "/namespaces/test/services/baz": runtime.EncodeOrDie(testapi.Default.Codec(), newSvc), - }), testapi.Default.Codec()). + b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{ + "/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, newPod), + "/namespaces/test/pods/bar": runtime.EncodeOrDie(corev1Codec, newPod2), + "/namespaces/test/services/baz": runtime.EncodeOrDie(corev1Codec, newSvc), + })). NamespaceParam("other").Stream(r, "STDIN").Flatten().Latest() test := &testVisitor{} @@ -1149,17 +1234,17 @@ func TestReceiveMultipleErrors(t *testing.T) { go func() { defer w.Close() w.Write([]byte(`{}`)) - w.Write([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), &pods.Items[0]))) + w.Write([]byte(runtime.EncodeOrDie(corev1Codec, &pods.Items[0]))) }() r2, w2 := io.Pipe() go func() { defer w2.Close() w2.Write([]byte(`{}`)) - w2.Write([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), &svc.Items[0]))) + w2.Write([]byte(runtime.EncodeOrDie(corev1Codec, &svc.Items[0]))) }() - b := NewBuilder(testapi.Default.RESTMapper(), LegacyCategoryExpander, api.Scheme, fakeClient(), testapi.Default.Codec()). + b := newDefaultBuilder(). Stream(r, "1").Stream(r2, "2"). ContinueOnError() diff --git a/pkg/kubectl/resource/helper.go b/pkg/kubectl/resource/helper.go index 7cb7bf4a39f..e5ca36221ed 100644 --- a/pkg/kubectl/resource/helper.go +++ b/pkg/kubectl/resource/helper.go @@ -63,33 +63,24 @@ func (m *Helper) Get(namespace, name string, export bool) (runtime.Object, error return req.Do().Get() } -// TODO: add field selector -func (m *Helper) List(namespace, apiVersion string, selector string, export, includeUninitialized bool) (runtime.Object, error) { +func (m *Helper) List(namespace, apiVersion string, export bool, options *metav1.ListOptions) (runtime.Object, error) { req := m.RESTClient.Get(). NamespaceIfScoped(namespace, m.NamespaceScoped). Resource(m.Resource). - VersionedParams(&metav1.ListOptions{ - LabelSelector: selector, - }, metav1.ParameterCodec) + VersionedParams(options, metav1.ParameterCodec) if export { // TODO: I should be part of ListOptions req.Param("export", strconv.FormatBool(export)) } - if includeUninitialized { - req.Param("includeUninitialized", strconv.FormatBool(includeUninitialized)) - } return req.Do().Get() } -func (m *Helper) Watch(namespace, resourceVersion, apiVersion string, labelSelector string) (watch.Interface, error) { +func (m *Helper) Watch(namespace, apiVersion string, options *metav1.ListOptions) (watch.Interface, error) { + options.Watch = true return m.RESTClient.Get(). NamespaceIfScoped(namespace, m.NamespaceScoped). Resource(m.Resource). - VersionedParams(&metav1.ListOptions{ - ResourceVersion: resourceVersion, - Watch: true, - LabelSelector: labelSelector, - }, metav1.ParameterCodec). + VersionedParams(options, metav1.ParameterCodec). Watch() } diff --git a/pkg/kubectl/resource/helper_test.go b/pkg/kubectl/resource/helper_test.go index 7ce9a3aad93..96a2d7789ca 100644 --- a/pkg/kubectl/resource/helper_test.go +++ b/pkg/kubectl/resource/helper_test.go @@ -26,17 +26,17 @@ import ( "strings" "testing" + 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/runtime/serializer" + "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/testapi" - apitesting "k8s.io/kubernetes/pkg/api/testing" ) func objBody(obj runtime.Object) io.ReadCloser { - return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), obj)))) + return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(corev1Codec, obj)))) } func header() http.Header { @@ -54,6 +54,17 @@ func splitPath(path string) []string { return strings.Split(path, "/") } +// V1DeepEqualSafePodSpec returns a PodSpec which is ready to be used with apiequality.Semantic.DeepEqual +func V1DeepEqualSafePodSpec() corev1.PodSpec { + grace := int64(30) + return corev1.PodSpec{ + RestartPolicy: corev1.RestartPolicyAlways, + DNSPolicy: corev1.DNSClusterFirst, + TerminationGracePeriodSeconds: &grace, + SecurityContext: &corev1.PodSecurityContext{}, + } +} + func TestHelperDelete(t *testing.T) { tests := []struct { Err bool @@ -103,8 +114,7 @@ func TestHelperDelete(t *testing.T) { } for _, test := range tests { client := &fake.RESTClient{ - APIRegistry: api.Registry, - NegotiatedSerializer: testapi.Default.NegotiatedSerializer(), + NegotiatedSerializer: scheme.Codecs, Resp: test.Resp, Err: test.HttpErr, } @@ -167,26 +177,26 @@ func TestHelperCreate(t *testing.T) { Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess}), }, - Object: &api.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}, - ExpectObject: &api.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}, + Object: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}, + ExpectObject: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}, Req: expectPost, }, { Modify: false, - Object: &api.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}}, - ExpectObject: &api.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}}, + Object: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}}, + ExpectObject: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}}, Resp: &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})}, Req: expectPost, }, { Modify: true, - Object: &api.Pod{ + Object: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}, - Spec: apitesting.DeepEqualSafePodSpec(), + Spec: V1DeepEqualSafePodSpec(), }, - ExpectObject: &api.Pod{ + ExpectObject: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: apitesting.DeepEqualSafePodSpec(), + Spec: V1DeepEqualSafePodSpec(), }, Resp: &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})}, Req: expectPost, @@ -194,14 +204,14 @@ func TestHelperCreate(t *testing.T) { } for i, test := range tests { client := &fake.RESTClient{ - APIRegistry: api.Registry, - NegotiatedSerializer: testapi.Default.NegotiatedSerializer(), + GroupVersion: corev1GV, + NegotiatedSerializer: scheme.Codecs, Resp: test.Resp, Err: test.HttpErr, } modifier := &Helper{ RESTClient: client, - Versioner: testapi.Default.MetadataAccessor(), + Versioner: metaAccessor, NamespaceScoped: true, } _, err := modifier.Create("bar", test.Modify, test.Object) @@ -221,7 +231,7 @@ func TestHelperCreate(t *testing.T) { t.Logf("got body: %s", string(body)) expect := []byte{} if test.ExpectObject != nil { - expect = []byte(runtime.EncodeOrDie(testapi.Default.Codec(), test.ExpectObject)) + expect = []byte(runtime.EncodeOrDie(corev1Codec, test.ExpectObject)) } if !reflect.DeepEqual(expect, body) { t.Errorf("%d: unexpected body: %s (expected %s)", i, string(body), string(expect)) @@ -253,7 +263,7 @@ func TestHelperGet(t *testing.T) { Resp: &http.Response{ StatusCode: http.StatusOK, Header: header(), - Body: objBody(&api.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}), + Body: objBody(&corev1.Pod{TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"}, ObjectMeta: metav1.ObjectMeta{Name: "foo"}}), }, Req: func(req *http.Request) bool { if req.Method != "GET" { @@ -273,10 +283,10 @@ func TestHelperGet(t *testing.T) { }, }, } - for _, test := range tests { + for i, test := range tests { client := &fake.RESTClient{ - APIRegistry: api.Registry, - NegotiatedSerializer: testapi.Default.NegotiatedSerializer(), + GroupVersion: corev1GV, + NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}, Resp: test.Resp, Err: test.HttpErr, } @@ -285,13 +295,14 @@ func TestHelperGet(t *testing.T) { NamespaceScoped: true, } obj, err := modifier.Get("bar", "foo", false) + if (err != nil) != test.Err { - t.Errorf("unexpected error: %t %v", test.Err, err) + t.Errorf("unexpected error: %d %t %v", i, test.Err, err) } if err != nil { continue } - if obj.(*api.Pod).Name != "foo" { + if obj.(*corev1.Pod).Name != "foo" { t.Errorf("unexpected object: %#v", obj) } if test.Req != nil && !test.Req(client.Req) { @@ -323,8 +334,8 @@ func TestHelperList(t *testing.T) { Resp: &http.Response{ StatusCode: http.StatusOK, Header: header(), - Body: objBody(&api.PodList{ - Items: []api.Pod{{ + Body: objBody(&corev1.PodList{ + Items: []corev1.Pod{{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, }, }, @@ -339,7 +350,7 @@ func TestHelperList(t *testing.T) { t.Errorf("url doesn't contain name: %#v", req.URL) return false } - if req.URL.Query().Get(metav1.LabelSelectorQueryParam(api.Registry.GroupOrDie(api.GroupName).GroupVersion.String())) != labels.SelectorFromSet(labels.Set{"foo": "baz"}).String() { + if req.URL.Query().Get(metav1.LabelSelectorQueryParam(corev1GV.String())) != labels.SelectorFromSet(labels.Set{"foo": "baz"}).String() { t.Errorf("url doesn't contain query parameters: %#v", req.URL) return false } @@ -349,8 +360,8 @@ func TestHelperList(t *testing.T) { } for _, test := range tests { client := &fake.RESTClient{ - APIRegistry: api.Registry, - NegotiatedSerializer: testapi.Default.NegotiatedSerializer(), + GroupVersion: corev1GV, + NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}, Resp: test.Resp, Err: test.HttpErr, } @@ -358,14 +369,14 @@ func TestHelperList(t *testing.T) { RESTClient: client, NamespaceScoped: true, } - obj, err := modifier.List("bar", api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), "foo=baz", false, false) + obj, err := modifier.List("bar", corev1GV.String(), false, &metav1.ListOptions{LabelSelector: "foo=baz"}) if (err != nil) != test.Err { t.Errorf("unexpected error: %t %v", test.Err, err) } if err != nil { continue } - if obj.(*api.PodList).Items[0].Name != "foo" { + if obj.(*corev1.PodList).Items[0].Name != "foo" { t.Errorf("unexpected object: %#v", obj) } if test.Req != nil && !test.Req(client.Req) { @@ -374,6 +385,72 @@ func TestHelperList(t *testing.T) { } } +func TestHelperListSelectorCombination(t *testing.T) { + tests := []struct { + Name string + Err bool + ErrMsg string + FieldSelector string + LabelSelector string + }{ + { + Name: "No selector", + Err: false, + }, + { + Name: "Only Label Selector", + Err: false, + LabelSelector: "foo=baz", + }, + { + Name: "Only Field Selector", + Err: false, + FieldSelector: "xyz=zyx", + }, + { + Name: "Both Label and Field Selector", + Err: false, + LabelSelector: "foo=baz", + FieldSelector: "xyz=zyx", + }, + } + + resp := &http.Response{ + StatusCode: http.StatusOK, + Header: header(), + Body: objBody(&corev1.PodList{ + Items: []corev1.Pod{{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + }, + }, + }), + } + client := &fake.RESTClient{ + NegotiatedSerializer: scheme.Codecs, + Resp: resp, + Err: nil, + } + modifier := &Helper{ + RESTClient: client, + NamespaceScoped: true, + } + + for _, test := range tests { + _, err := modifier.List("bar", + corev1GV.String(), + false, + &metav1.ListOptions{LabelSelector: test.LabelSelector, FieldSelector: test.FieldSelector}) + if test.Err { + if err == nil { + t.Errorf("%q expected error: %q", test.Name, test.ErrMsg) + } + if err != nil && err.Error() != test.ErrMsg { + t.Errorf("%q expected error: %q", test.Name, test.ErrMsg) + } + } + } +} + func TestHelperReplace(t *testing.T) { expectPut := func(path string, req *http.Request) bool { if req.Method != "PUT" { @@ -410,7 +487,7 @@ func TestHelperReplace(t *testing.T) { { Namespace: "bar", NamespaceScoped: true, - Object: &api.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}, + Object: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}, Resp: &http.Response{ StatusCode: http.StatusNotFound, Header: header(), @@ -421,9 +498,9 @@ func TestHelperReplace(t *testing.T) { { Namespace: "bar", NamespaceScoped: true, - Object: &api.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}, + Object: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}, ExpectPath: "/namespaces/bar/foo", - ExpectObject: &api.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}, + ExpectObject: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}, Resp: &http.Response{ StatusCode: http.StatusOK, Header: header(), @@ -435,30 +512,30 @@ func TestHelperReplace(t *testing.T) { { Namespace: "bar", NamespaceScoped: true, - Object: &api.Pod{ + Object: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - Spec: apitesting.DeepEqualSafePodSpec(), + Spec: V1DeepEqualSafePodSpec(), }, ExpectPath: "/namespaces/bar/foo", - ExpectObject: &api.Pod{ + ExpectObject: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}, - Spec: apitesting.DeepEqualSafePodSpec(), + Spec: V1DeepEqualSafePodSpec(), }, Overwrite: true, HTTPClient: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { if req.Method == "PUT" { return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})}, nil } - return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&api.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}})}, nil + return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}})}, nil }), Req: expectPut, }, // cluster scoped resource { - Object: &api.Node{ + Object: &corev1.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, }, - ExpectObject: &api.Node{ + ExpectObject: &corev1.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}, }, Overwrite: true, @@ -467,31 +544,31 @@ func TestHelperReplace(t *testing.T) { if req.Method == "PUT" { return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})}, nil } - return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&api.Node{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}})}, nil + return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}})}, nil }), Req: expectPut, }, { Namespace: "bar", NamespaceScoped: true, - Object: &api.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}}, + Object: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}}, ExpectPath: "/namespaces/bar/foo", - ExpectObject: &api.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}}, + ExpectObject: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}}, Resp: &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})}, Req: expectPut, }, } for i, test := range tests { client := &fake.RESTClient{ - APIRegistry: api.Registry, - NegotiatedSerializer: testapi.Default.NegotiatedSerializer(), + GroupVersion: corev1GV, + NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}, Client: test.HTTPClient, Resp: test.Resp, Err: test.HttpErr, } modifier := &Helper{ RESTClient: client, - Versioner: testapi.Default.MetadataAccessor(), + Versioner: metaAccessor, NamespaceScoped: test.NamespaceScoped, } _, err := modifier.Replace(test.Namespace, "foo", test.Overwrite, test.Object) @@ -510,7 +587,7 @@ func TestHelperReplace(t *testing.T) { } expect := []byte{} if test.ExpectObject != nil { - expect = []byte(runtime.EncodeOrDie(testapi.Default.Codec(), test.ExpectObject)) + expect = []byte(runtime.EncodeOrDie(corev1Codec, test.ExpectObject)) } if !reflect.DeepEqual(expect, body) { t.Errorf("%d: unexpected body: %s", i, string(body)) diff --git a/pkg/kubectl/resource/interfaces.go b/pkg/kubectl/resource/interfaces.go index bb7a956cde2..8b75d41dbf9 100644 --- a/pkg/kubectl/resource/interfaces.go +++ b/pkg/kubectl/resource/interfaces.go @@ -44,3 +44,41 @@ type ClientMapperFunc func(mapping *meta.RESTMapping) (RESTClient, error) func (f ClientMapperFunc) ClientForMapping(mapping *meta.RESTMapping) (RESTClient, error) { return f(mapping) } + +// RequestTransform is a function that is given a chance to modify the outgoing request. +type RequestTransform func(*client.Request) + +// NewClientWithOptions wraps the provided RESTClient and invokes each transform on each +// newly created request. +func NewClientWithOptions(c RESTClient, transforms ...RequestTransform) RESTClient { + return &clientOptions{c: c, transforms: transforms} +} + +type clientOptions struct { + c RESTClient + transforms []RequestTransform +} + +func (c *clientOptions) modify(req *client.Request) *client.Request { + for _, transform := range c.transforms { + transform(req) + } + return req +} + +func (c *clientOptions) Get() *client.Request { + return c.modify(c.c.Get()) +} + +func (c *clientOptions) Post() *client.Request { + return c.modify(c.c.Post()) +} +func (c *clientOptions) Patch(t types.PatchType) *client.Request { + return c.modify(c.c.Patch(t)) +} +func (c *clientOptions) Delete() *client.Request { + return c.modify(c.c.Delete()) +} +func (c *clientOptions) Put() *client.Request { + return c.modify(c.c.Put()) +} diff --git a/pkg/kubectl/resource/mapper.go b/pkg/kubectl/resource/mapper.go index 81377358d4f..6739736805d 100644 --- a/pkg/kubectl/resource/mapper.go +++ b/pkg/kubectl/resource/mapper.go @@ -25,17 +25,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" ) -// DisabledClientForMapping allows callers to avoid allowing remote calls when handling -// resources. -type DisabledClientForMapping struct { - ClientMapper -} - -func (f DisabledClientForMapping) ClientForMapping(mapping *meta.RESTMapping) (RESTClient, error) { - return nil, nil -} - -// Mapper is a convenience struct for holding references to the three interfaces +// Mapper is a convenience struct for holding references to the interfaces // needed to create Info for arbitrary objects. type Mapper struct { runtime.ObjectTyper @@ -44,17 +34,27 @@ type Mapper struct { runtime.Decoder } +// AcceptUnrecognizedObjects will return a mapper that will tolerate objects +// that are not recognized by the RESTMapper, returning mappings that can +// perform minimal transformation. Allows working in disconnected mode, or with +// objects that the server does not recognize. Returned resource.Info objects +// may have empty resource fields and nil clients. +func (m *Mapper) AcceptUnrecognizedObjects() *Mapper { + copied := *m + copied.RESTMapper = NewRelaxedRESTMapper(m.RESTMapper) + copied.ClientMapper = NewRelaxedClientMapper(m.ClientMapper) + return &copied +} + // InfoForData creates an Info object for the given data. An error is returned // if any of the decoding or client lookup steps fail. Name and namespace will be // set into Info if the mapping's MetadataAccessor can retrieve them. func (m *Mapper) InfoForData(data []byte, source string) (*Info, error) { - versions := &runtime.VersionedObjects{} - _, gvk, err := m.Decode(data, nil, versions) + obj, gvk, err := m.Decode(data, nil, nil) if err != nil { return nil, fmt.Errorf("unable to decode %q: %v", source, err) } - obj, versioned := versions.Last(), versions.First() mapping, err := m.RESTMapping(gvk.GroupKind(), gvk.Version) if err != nil { return nil, fmt.Errorf("unable to recognize %q: %v", source, err) @@ -70,14 +70,15 @@ func (m *Mapper) InfoForData(data []byte, source string) (*Info, error) { resourceVersion, _ := mapping.MetadataAccessor.ResourceVersion(obj) return &Info{ - Mapping: mapping, - Client: client, + Client: client, + Mapping: mapping, + + Source: source, Namespace: namespace, Name: name, - Source: source, - VersionedObject: versioned, - Object: obj, ResourceVersion: resourceVersion, + + Object: obj, }, nil } @@ -99,6 +100,7 @@ func (m *Mapper) InfoForObject(obj runtime.Object, preferredGVKs []schema.GroupV if err != nil { return nil, fmt.Errorf("unable to recognize %v: %v", groupVersionKind, err) } + client, err := m.ClientForMapping(mapping) if err != nil { return nil, fmt.Errorf("unable to connect to a server to handle %q: %v", mapping.Resource, err) @@ -107,13 +109,14 @@ func (m *Mapper) InfoForObject(obj runtime.Object, preferredGVKs []schema.GroupV namespace, _ := mapping.MetadataAccessor.Namespace(obj) resourceVersion, _ := mapping.MetadataAccessor.ResourceVersion(obj) return &Info{ - Mapping: mapping, - Client: client, - Namespace: namespace, - Name: name, + Client: client, + Mapping: mapping, - Object: obj, + Namespace: namespace, + Name: name, ResourceVersion: resourceVersion, + + Object: obj, }, nil } @@ -152,3 +155,83 @@ func preferredObjectKind(possibilities []schema.GroupVersionKind, preferences [] // Just pick the first return possibilities[0] } + +// DisabledClientForMapping allows callers to avoid allowing remote calls when handling +// resources. +type DisabledClientForMapping struct { + ClientMapper +} + +func (f DisabledClientForMapping) ClientForMapping(mapping *meta.RESTMapping) (RESTClient, error) { + return nil, nil +} + +// NewRelaxedClientMapper will return a nil mapping if the object is not a recognized resource. +func NewRelaxedClientMapper(mapper ClientMapper) ClientMapper { + return relaxedClientMapper{mapper} +} + +type relaxedClientMapper struct { + ClientMapper +} + +func (f relaxedClientMapper) ClientForMapping(mapping *meta.RESTMapping) (RESTClient, error) { + if len(mapping.Resource) == 0 { + return nil, nil + } + return f.ClientMapper.ClientForMapping(mapping) +} + +// NewRelaxedRESTMapper returns a RESTMapper that will tolerate mappings that don't exist in provided +// RESTMapper, returning a mapping that is a best effort against the current server. This allows objects +// that the server does not recognize to still be loaded. +func NewRelaxedRESTMapper(mapper meta.RESTMapper) meta.RESTMapper { + return relaxedMapper{mapper} +} + +type relaxedMapper struct { + meta.RESTMapper +} + +func (m relaxedMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*meta.RESTMapping, error) { + mapping, err := m.RESTMapper.RESTMapping(gk, versions...) + if err != nil && meta.IsNoMatchError(err) && len(versions) > 0 { + return &meta.RESTMapping{ + GroupVersionKind: gk.WithVersion(versions[0]), + MetadataAccessor: meta.NewAccessor(), + Scope: meta.RESTScopeRoot, + ObjectConvertor: identityConvertor{}, + }, nil + } + return mapping, err +} +func (m relaxedMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*meta.RESTMapping, error) { + mappings, err := m.RESTMapper.RESTMappings(gk, versions...) + if err != nil && meta.IsNoMatchError(err) && len(versions) > 0 { + return []*meta.RESTMapping{ + { + GroupVersionKind: gk.WithVersion(versions[0]), + MetadataAccessor: meta.NewAccessor(), + Scope: meta.RESTScopeRoot, + ObjectConvertor: identityConvertor{}, + }, + }, nil + } + return mappings, err +} + +type identityConvertor struct{} + +var _ runtime.ObjectConvertor = identityConvertor{} + +func (c identityConvertor) Convert(in interface{}, out interface{}, context interface{}) error { + return fmt.Errorf("unable to convert objects across pointers") +} + +func (c identityConvertor) ConvertToVersion(in runtime.Object, gv runtime.GroupVersioner) (out runtime.Object, err error) { + return in, nil +} + +func (c identityConvertor) ConvertFieldLabel(version string, kind string, label string, value string) (string, string, error) { + return "", "", fmt.Errorf("unable to convert field labels") +} diff --git a/pkg/kubectl/resource/result.go b/pkg/kubectl/resource/result.go index 46578a5730b..611471ca7c7 100644 --- a/pkg/kubectl/resource/result.go +++ b/pkg/kubectl/resource/result.go @@ -20,13 +20,13 @@ import ( "fmt" "reflect" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/watch" - "k8s.io/kubernetes/pkg/api" ) // ErrMatchFunc can be used to filter errors that may not be true failures. @@ -41,6 +41,7 @@ type Result struct { singleItemImplied bool targetsSingleItems bool + mapper *Mapper ignoreErrors []utilerrors.Matcher // populated by a call to Infos @@ -74,6 +75,11 @@ func (r *Result) IgnoreErrors(fns ...ErrMatchFunc) *Result { return r } +// Mapper returns a copy of the builder's mapper. +func (r *Result) Mapper() *Mapper { + return r.mapper +} + // Err returns one or more errors (via a util.ErrorList) that occurred prior // to visiting the elements in the visitor. To see all errors including those // that occur during visitation, invoke Infos(). @@ -129,7 +135,7 @@ func (r *Result) Infos() ([]*Info, error) { // found resources. If the Builder was a singular context (expected to return a // single resource by user input) and only a single resource was found, the resource // will be returned as is. Otherwise, the returned resources will be part of an -// api.List. The ResourceVersion of the api.List will be set only if it is identical +// v1.List. The ResourceVersion of the v1.List will be set only if it is identical // across all infos returned. func (r *Result) Object() (runtime.Object, error) { infos, err := r.Infos() @@ -160,12 +166,27 @@ func (r *Result) Object() (runtime.Object, error) { if len(versions) == 1 { version = versions.List()[0] } - return &api.List{ + + return toV1List(objects, version), err +} + +// Compile time check to enforce that list implements the necessary interface +var _ metav1.ListInterface = &v1.List{} +var _ metav1.ListMetaAccessor = &v1.List{} + +// toV1List takes a slice of Objects + their version, and returns +// a v1.List Object containing the objects in the Items field +func toV1List(objects []runtime.Object, version string) runtime.Object { + raw := []runtime.RawExtension{} + for _, o := range objects { + raw = append(raw, runtime.RawExtension{Object: o}) + } + return &v1.List{ ListMeta: metav1.ListMeta{ ResourceVersion: version, }, - Items: objects, - }, err + Items: raw, + } } // ResourceMapping returns a single meta.RESTMapping representing the diff --git a/pkg/kubectl/resource/selector.go b/pkg/kubectl/resource/selector.go index 1afa4d1f7ba..66e13ad0810 100644 --- a/pkg/kubectl/resource/selector.go +++ b/pkg/kubectl/resource/selector.go @@ -21,6 +21,7 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/watch" ) @@ -29,60 +30,90 @@ type Selector struct { Client RESTClient Mapping *meta.RESTMapping Namespace string - Selector string + LabelSelector string + FieldSelector string Export bool IncludeUninitialized bool + LimitChunks int64 } // NewSelector creates a resource selector which hides details of getting items by their label selector. -func NewSelector(client RESTClient, mapping *meta.RESTMapping, namespace string, selector string, export, includeUninitialized bool) *Selector { +func NewSelector(client RESTClient, mapping *meta.RESTMapping, namespace, labelSelector, fieldSelector string, export, includeUninitialized bool, limitChunks int64) *Selector { return &Selector{ Client: client, Mapping: mapping, Namespace: namespace, - Selector: selector, + LabelSelector: labelSelector, + FieldSelector: fieldSelector, Export: export, IncludeUninitialized: includeUninitialized, + LimitChunks: limitChunks, } } -// Visit implements Visitor +// Visit implements Visitor and uses request chunking by default. func (r *Selector) Visit(fn VisitorFunc) error { - list, err := NewHelper(r.Client, r.Mapping).List(r.Namespace, r.ResourceMapping().GroupVersionKind.GroupVersion().String(), r.Selector, r.Export, r.IncludeUninitialized) - if err != nil { - if errors.IsBadRequest(err) || errors.IsNotFound(err) { - if se, ok := err.(*errors.StatusError); ok { - // modify the message without hiding this is an API error - if len(r.Selector) == 0 { - se.ErrStatus.Message = fmt.Sprintf("Unable to list %q: %v", r.Mapping.Resource, se.ErrStatus.Message) - } else { - se.ErrStatus.Message = fmt.Sprintf("Unable to find %q that match the selector %q: %v", r.Mapping.Resource, r.Selector, se.ErrStatus.Message) + var continueToken string + for { + list, err := NewHelper(r.Client, r.Mapping).List( + r.Namespace, + r.ResourceMapping().GroupVersionKind.GroupVersion().String(), + r.Export, + &metav1.ListOptions{ + LabelSelector: r.LabelSelector, + FieldSelector: r.FieldSelector, + IncludeUninitialized: r.IncludeUninitialized, + Limit: r.LimitChunks, + Continue: continueToken, + }, + ) + if err != nil { + if errors.IsResourceExpired(err) { + return err + } + if errors.IsBadRequest(err) || errors.IsNotFound(err) { + if se, ok := err.(*errors.StatusError); ok { + // modify the message without hiding this is an API error + if len(r.LabelSelector) == 0 && len(r.FieldSelector) == 0 { + se.ErrStatus.Message = fmt.Sprintf("Unable to list %q: %v", r.Mapping.Resource, se.ErrStatus.Message) + } else { + se.ErrStatus.Message = fmt.Sprintf("Unable to find %q that match label selector %q, field selector %q: %v", r.Mapping.Resource, r.LabelSelector, r.FieldSelector, se.ErrStatus.Message) + } + return se } - return se - } - if len(r.Selector) == 0 { - return fmt.Errorf("Unable to list %q: %v", r.Mapping.Resource, err) - } else { - return fmt.Errorf("Unable to find %q that match the selector %q: %v", r.Mapping.Resource, r.Selector, err) + if len(r.LabelSelector) == 0 && len(r.FieldSelector) == 0 { + return fmt.Errorf("Unable to list %q: %v", r.Mapping.Resource, err) + } + return fmt.Errorf("Unable to find %q that match label selector %q, field selector %q: %v", r.Mapping.Resource, r.LabelSelector, r.FieldSelector, err) } + return err } - return err - } - accessor := r.Mapping.MetadataAccessor - resourceVersion, _ := accessor.ResourceVersion(list) - info := &Info{ - Client: r.Client, - Mapping: r.Mapping, - Namespace: r.Namespace, + accessor := r.Mapping.MetadataAccessor + resourceVersion, _ := accessor.ResourceVersion(list) + nextContinueToken, _ := accessor.Continue(list) + info := &Info{ + Client: r.Client, + Mapping: r.Mapping, - Object: list, - ResourceVersion: resourceVersion, + Namespace: r.Namespace, + ResourceVersion: resourceVersion, + + Object: list, + } + + if err := fn(info, nil); err != nil { + return err + } + if len(nextContinueToken) == 0 { + return nil + } + continueToken = nextContinueToken } - return fn(info, nil) } func (r *Selector) Watch(resourceVersion string) (watch.Interface, error) { - return NewHelper(r.Client, r.Mapping).Watch(r.Namespace, resourceVersion, r.ResourceMapping().GroupVersionKind.GroupVersion().String(), r.Selector) + return NewHelper(r.Client, r.Mapping).Watch(r.Namespace, r.ResourceMapping().GroupVersionKind.GroupVersion().String(), + &metav1.ListOptions{ResourceVersion: resourceVersion, LabelSelector: r.LabelSelector, FieldSelector: r.FieldSelector}) } // ResourceMapping returns the mapping for this resource and implements ResourceMapping diff --git a/pkg/kubectl/resource/visitor.go b/pkg/kubectl/resource/visitor.go index 5e5a73a3160..554fa4a6611 100644 --- a/pkg/kubectl/resource/visitor.go +++ b/pkg/kubectl/resource/visitor.go @@ -32,6 +32,7 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -73,19 +74,22 @@ type ResourceMapping interface { // Info contains temporary info to execute a REST call, or show the results // of an already completed REST call. type Info struct { - Client RESTClient - Mapping *meta.RESTMapping + Client RESTClient + // Mapping may be nil if the object has no available metadata, but is still parseable + // from disk. + Mapping *meta.RESTMapping + // Namespace will be set if the object is namespaced and has a specified value. Namespace string Name string // Optional, Source is the filename or URL to template file (.json or .yaml), // or stdin to use to handle the resource Source string - // Optional, this is the provided object in a versioned type before defaulting - // and conversions into its corresponding internal type. This is useful for - // reflecting on user intent which may be lost after defaulting and conversions. - VersionedObject runtime.Object - // Optional, this is the most recent value returned by the server if available + // Optional, this is the most recent value returned by the server if available. It will + // typically be in unstructured or internal forms, depending on how the Builder was + // defined. If retrieved from the server, the Builder expects the mapping client to + // decide the final form. Use the AsVersioned, AsUnstructured, and AsInternal helpers + // to alter the object versions. Object runtime.Object // Optional, this is the most recent resource version the server knows about for // this type of resource. It may not match the resource version of the object, @@ -96,17 +100,6 @@ type Info struct { Export bool } -// NewInfo returns a new info object -func NewInfo(client RESTClient, mapping *meta.RESTMapping, namespace, name string, export bool) *Info { - return &Info{ - Client: client, - Mapping: mapping, - Namespace: namespace, - Name: name, - Export: export, - } -} - // Visit implements Visitor func (i *Info) Visit(fn VisitorFunc) error { return fn(i, nil) @@ -176,6 +169,61 @@ func (i *Info) ResourceMapping() *meta.RESTMapping { return i.Mapping } +// Internal attempts to convert the provided object to an internal type or returns an error. +func (i *Info) Internal() (runtime.Object, error) { + return i.Mapping.ConvertToVersion(i.Object, i.Mapping.GroupVersionKind.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion()) +} + +// AsInternal returns the object in internal form if possible, or i.Object if it cannot be +// converted. +func (i *Info) AsInternal() runtime.Object { + if obj, err := i.Internal(); err == nil { + return obj + } + return i.Object +} + +// Versioned returns the object as a Go type in the mapping's version or returns an error. +func (i *Info) Versioned() (runtime.Object, error) { + return i.Mapping.ConvertToVersion(i.Object, i.Mapping.GroupVersionKind.GroupVersion()) +} + +// AsVersioned returns the object as a Go object in the external form if possible (matching the +// group version kind of the mapping, or i.Object if it cannot be converted. +func (i *Info) AsVersioned() runtime.Object { + if obj, err := i.Versioned(); err == nil { + return obj + } + return i.Object +} + +// Unstructured returns the current object in unstructured form (as a runtime.Unstructured) +func (i *Info) Unstructured() (runtime.Unstructured, error) { + switch t := i.Object.(type) { + case runtime.Unstructured: + return t, nil + case *runtime.Unknown: + gvk := i.Mapping.GroupVersionKind + out, _, err := unstructured.UnstructuredJSONScheme.Decode(t.Raw, &gvk, nil) + return out.(runtime.Unstructured), err + default: + out := &unstructured.Unstructured{} + if err := i.Mapping.Convert(i.Object, out, nil); err != nil { + return nil, err + } + return out, nil + } +} + +// AsUnstructured returns the object as a Go object in external form as a runtime.Unstructured +// (map of JSON equivalent values) or as i.Object if it cannot be converted. +func (i *Info) AsUnstructured() runtime.Object { + if out, err := i.Unstructured(); err == nil { + return out + } + return i.Object +} + // VisitorList implements Visit for the sub visitors it contains. The first error // returned from a child Visitor will terminate iteration. type VisitorList []Visitor @@ -388,10 +436,7 @@ func (v FlattenListVisitor) Visit(fn VisitorFunc) error { if err != nil { return fn(info, nil) } - if errs := runtime.DecodeList(items, struct { - runtime.ObjectTyper - runtime.Decoder - }{v.Mapper, v.Mapper.Decoder}); len(errs) > 0 { + if errs := runtime.DecodeList(items, v.Mapper.Decoder); len(errs) > 0 { return utilerrors.NewAggregate(errs) } @@ -691,7 +736,7 @@ func (v FilteredVisitor) Visit(fn VisitorFunc) error { }) } -func FilterBySelector(s labels.Selector) FilterFunc { +func FilterByLabelSelector(s labels.Selector) FilterFunc { return func(info *Info, err error) (bool, error) { if err != nil { return false, err diff --git a/pkg/kubectl/resource_filter.go b/pkg/kubectl/resource_filter.go index a85d1454b9a..b873748f688 100644 --- a/pkg/kubectl/resource_filter.go +++ b/pkg/kubectl/resource_filter.go @@ -19,7 +19,7 @@ package kubectl import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/printers" ) @@ -56,10 +56,6 @@ func filterPods(obj runtime.Object, options printers.PrintOptions) bool { // Filter loops through a collection of FilterFuncs until it finds one that can filter the given resource func (f Filters) Filter(obj runtime.Object, opts *printers.PrintOptions) (bool, error) { - // check if the object is unstructured. If so, let's attempt to convert it to a type we can understand - // before apply filter func. - obj, _ = DecodeUnknownObject(obj) - for _, filter := range f { if ok := filter(obj, *opts); ok { return true, nil @@ -67,19 +63,3 @@ func (f Filters) Filter(obj runtime.Object, opts *printers.PrintOptions) (bool, } return false, nil } - -// check if the object is unstructured. If so, let's attempt to convert it to a type we can understand. -func DecodeUnknownObject(obj runtime.Object) (runtime.Object, error) { - var err error - - switch obj.(type) { - case runtime.Unstructured, *runtime.Unknown: - if objBytes, err := runtime.Encode(api.Codecs.LegacyCodec(), obj); err == nil { - if decodedObj, err := runtime.Decode(api.Codecs.UniversalDecoder(), objBytes); err == nil { - obj = decodedObj - } - } - } - - return obj, err -} diff --git a/pkg/kubectl/resource_filter_test.go b/pkg/kubectl/resource_filter_test.go index a4593363357..7c3335bdf9a 100644 --- a/pkg/kubectl/resource_filter_test.go +++ b/pkg/kubectl/resource_filter_test.go @@ -21,7 +21,7 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/printers" ) diff --git a/pkg/kubectl/rolebinding.go b/pkg/kubectl/rolebinding.go index 6d1ae4ddaa0..0eddb1e0342 100644 --- a/pkg/kubectl/rolebinding.go +++ b/pkg/kubectl/rolebinding.go @@ -21,9 +21,9 @@ import ( "strings" + rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/apis/rbac" ) // RoleBindingGeneratorV1 supports stable generation of a roleBinding. @@ -113,35 +113,35 @@ func (s RoleBindingGeneratorV1) StructuredGenerate() (runtime.Object, error) { if err := s.validate(); err != nil { return nil, err } - roleBinding := &rbac.RoleBinding{} + roleBinding := &rbacv1.RoleBinding{} roleBinding.Name = s.Name switch { case len(s.Role) > 0: - roleBinding.RoleRef = rbac.RoleRef{ - APIGroup: rbac.GroupName, + roleBinding.RoleRef = rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, Kind: "Role", Name: s.Role, } case len(s.ClusterRole) > 0: - roleBinding.RoleRef = rbac.RoleRef{ - APIGroup: rbac.GroupName, + roleBinding.RoleRef = rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, Kind: "ClusterRole", Name: s.ClusterRole, } } for _, user := range sets.NewString(s.Users...).List() { - roleBinding.Subjects = append(roleBinding.Subjects, rbac.Subject{ - Kind: rbac.UserKind, - APIGroup: rbac.GroupName, + roleBinding.Subjects = append(roleBinding.Subjects, rbacv1.Subject{ + Kind: rbacv1.UserKind, + APIGroup: rbacv1.GroupName, Name: user, }) } for _, group := range sets.NewString(s.Groups...).List() { - roleBinding.Subjects = append(roleBinding.Subjects, rbac.Subject{ - Kind: rbac.GroupKind, - APIGroup: rbac.GroupName, + roleBinding.Subjects = append(roleBinding.Subjects, rbacv1.Subject{ + Kind: rbacv1.GroupKind, + APIGroup: rbacv1.GroupName, Name: group, }) } @@ -150,8 +150,8 @@ func (s RoleBindingGeneratorV1) StructuredGenerate() (runtime.Object, error) { if len(tokens) != 2 || tokens[1] == "" { return nil, fmt.Errorf("serviceaccount must be :") } - roleBinding.Subjects = append(roleBinding.Subjects, rbac.Subject{ - Kind: rbac.ServiceAccountKind, + roleBinding.Subjects = append(roleBinding.Subjects, rbacv1.Subject{ + Kind: rbacv1.ServiceAccountKind, APIGroup: "", Namespace: tokens[0], Name: tokens[1], diff --git a/pkg/kubectl/rolebinding_test.go b/pkg/kubectl/rolebinding_test.go index 8fedb1e00cb..01504f26805 100644 --- a/pkg/kubectl/rolebinding_test.go +++ b/pkg/kubectl/rolebinding_test.go @@ -20,8 +20,8 @@ import ( "reflect" "testing" + rbac "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/apis/rbac" ) func TestRoleBindingGenerate(t *testing.T) { diff --git a/pkg/kubectl/rollback.go b/pkg/kubectl/rollback.go index 31f74d81c06..864d5a290dc 100644 --- a/pkg/kubectl/rollback.go +++ b/pkg/kubectl/rollback.go @@ -26,20 +26,22 @@ import ( appsv1beta1 "k8s.io/api/apps/v1beta1" "k8s.io/api/core/v1" - externalextensions "k8s.io/api/extensions/v1beta1" + extv1beta1 "k8s.io/api/extensions/v1beta1" + "k8s.io/apimachinery/pkg/api/meta" 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/watch" - "k8s.io/kubernetes/pkg/api" - apiv1 "k8s.io/kubernetes/pkg/api/v1" - "k8s.io/kubernetes/pkg/apis/apps" + "k8s.io/client-go/kubernetes" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" + apiv1 "k8s.io/kubernetes/pkg/apis/core/v1" "k8s.io/kubernetes/pkg/apis/extensions" - clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/controller/daemon" deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util" "k8s.io/kubernetes/pkg/controller/statefulset" + kapps "k8s.io/kubernetes/pkg/kubectl/apps" sliceutil "k8s.io/kubernetes/pkg/kubectl/util/slice" printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" ) @@ -54,20 +56,51 @@ type Rollbacker interface { Rollback(obj runtime.Object, updatedAnnotations map[string]string, toRevision int64, dryRun bool) (string, error) } -func RollbackerFor(kind schema.GroupKind, c clientset.Interface) (Rollbacker, error) { - switch kind { - case extensions.Kind("Deployment"), apps.Kind("Deployment"): - return &DeploymentRollbacker{c}, nil - case extensions.Kind("DaemonSet"), apps.Kind("DaemonSet"): - return &DaemonSetRollbacker{c}, nil - case apps.Kind("StatefulSet"): - return &StatefulSetRollbacker{c}, nil +type RollbackVisitor struct { + clientset kubernetes.Interface + result Rollbacker +} + +func (v *RollbackVisitor) VisitDeployment(elem kapps.GroupKindElement) { + v.result = &DeploymentRollbacker{v.clientset} +} + +func (v *RollbackVisitor) VisitStatefulSet(kind kapps.GroupKindElement) { + v.result = &StatefulSetRollbacker{v.clientset} +} + +func (v *RollbackVisitor) VisitDaemonSet(kind kapps.GroupKindElement) { + v.result = &DaemonSetRollbacker{v.clientset} +} + +func (v *RollbackVisitor) VisitJob(kind kapps.GroupKindElement) {} +func (v *RollbackVisitor) VisitPod(kind kapps.GroupKindElement) {} +func (v *RollbackVisitor) VisitReplicaSet(kind kapps.GroupKindElement) {} +func (v *RollbackVisitor) VisitReplicationController(kind kapps.GroupKindElement) {} +func (v *RollbackVisitor) VisitCronJob(kind kapps.GroupKindElement) {} + +// RollbackerFor returns an implementation of Rollbacker interface for the given schema kind +func RollbackerFor(kind schema.GroupKind, c kubernetes.Interface) (Rollbacker, error) { + elem := kapps.GroupKindElement(kind) + visitor := &RollbackVisitor{ + clientset: c, } - return nil, fmt.Errorf("no rollbacker has been implemented for %q", kind) + + err := elem.Accept(visitor) + + if err != nil { + return nil, fmt.Errorf("error retrieving rollbacker for %q, %v", kind.String(), err) + } + + if visitor.result == nil { + return nil, fmt.Errorf("no rollbacker has been implemented for %q", kind) + } + + return visitor.result, nil } type DeploymentRollbacker struct { - c clientset.Interface + c kubernetes.Interface } func (r *DeploymentRollbacker) Rollback(obj runtime.Object, updatedAnnotations map[string]string, toRevision int64, dryRun bool) (string, error) { @@ -81,26 +114,26 @@ func (r *DeploymentRollbacker) Rollback(obj runtime.Object, updatedAnnotations m if d.Spec.Paused { return "", fmt.Errorf("you cannot rollback a paused deployment; resume it first with 'kubectl rollout resume deployment/%s' and try again", d.Name) } - deploymentRollback := &extensions.DeploymentRollback{ + deploymentRollback := &extv1beta1.DeploymentRollback{ Name: d.Name, UpdatedAnnotations: updatedAnnotations, - RollbackTo: extensions.RollbackConfig{ + RollbackTo: extv1beta1.RollbackConfig{ Revision: toRevision, }, } result := "" // Get current events - events, err := r.c.Core().Events(d.Namespace).List(metav1.ListOptions{}) + events, err := r.c.CoreV1().Events(d.Namespace).List(metav1.ListOptions{}) if err != nil { return result, err } // Do the rollback - if err := r.c.Extensions().Deployments(d.Namespace).Rollback(deploymentRollback); err != nil { + if err := r.c.ExtensionsV1beta1().Deployments(d.Namespace).Rollback(deploymentRollback); err != nil { return result, err } // Watch for the changes of events - watch, err := r.c.Core().Events(d.Namespace).Watch(metav1.ListOptions{Watch: true, ResourceVersion: events.ResourceVersion}) + watch, err := r.c.CoreV1().Events(d.Namespace).Watch(metav1.ListOptions{Watch: true, ResourceVersion: events.ResourceVersion}) if err != nil { return result, err } @@ -149,13 +182,13 @@ func isRollbackEvent(e *api.Event) (bool, string) { return false, "" } -func simpleDryRun(deployment *extensions.Deployment, c clientset.Interface, toRevision int64) (string, error) { - externalDeployment := &externalextensions.Deployment{} - if err := api.Scheme.Convert(deployment, externalDeployment, nil); err != nil { +func simpleDryRun(deployment *extensions.Deployment, c kubernetes.Interface, toRevision int64) (string, error) { + externalDeployment := &extv1beta1.Deployment{} + if err := legacyscheme.Scheme.Convert(deployment, externalDeployment, nil); err != nil { return "", fmt.Errorf("failed to convert deployment, %v", err) } - versionedExtensionsClient := versionedExtensionsClientV1beta1(c) - _, allOldRSs, newRS, err := deploymentutil.GetAllReplicaSets(externalDeployment, versionedExtensionsClient) + + _, allOldRSs, newRS, err := deploymentutil.GetAllReplicaSets(externalDeployment, c.ExtensionsV1beta1()) if err != nil { return "", fmt.Errorf("failed to retrieve replica sets from deployment %s: %v", deployment.Name, err) } @@ -184,7 +217,7 @@ func simpleDryRun(deployment *extensions.Deployment, c clientset.Interface, toRe } buf := bytes.NewBuffer([]byte{}) internalTemplate := &api.PodTemplateSpec{} - if err := apiv1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(template, internalTemplate, nil); err != nil { + if err := apiv1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(template, internalTemplate, nil); err != nil { return "", fmt.Errorf("failed to convert podtemplate, %v", err) } w := printersinternal.NewPrefixWriter(buf) @@ -203,7 +236,7 @@ func simpleDryRun(deployment *extensions.Deployment, c clientset.Interface, toRe buf := bytes.NewBuffer([]byte{}) buf.WriteString("\n") internalTemplate := &api.PodTemplateSpec{} - if err := apiv1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(template, internalTemplate, nil); err != nil { + if err := apiv1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(template, internalTemplate, nil); err != nil { return "", fmt.Errorf("failed to convert podtemplate, %v", err) } w := printersinternal.NewPrefixWriter(buf) @@ -212,40 +245,32 @@ func simpleDryRun(deployment *extensions.Deployment, c clientset.Interface, toRe } type DaemonSetRollbacker struct { - c clientset.Interface + c kubernetes.Interface } func (r *DaemonSetRollbacker) Rollback(obj runtime.Object, updatedAnnotations map[string]string, toRevision int64, dryRun bool) (string, error) { if toRevision < 0 { return "", revisionNotFoundErr(toRevision) } - - ds, ok := obj.(*extensions.DaemonSet) - if !ok { - return "", fmt.Errorf("passed object is not a DaemonSet: %#v", obj) - } - versionedAppsClient := versionedAppsClientV1beta1(r.c) - versionedExtensionsClient := versionedExtensionsClientV1beta1(r.c) - versionedObj, allHistory, err := controlledHistories(versionedAppsClient, versionedExtensionsClient, ds.Namespace, ds.Name, "DaemonSet") + accessor, err := meta.Accessor(obj) if err != nil { - return "", fmt.Errorf("unable to find history controlled by DaemonSet %s: %v", ds.Name, err) + return "", fmt.Errorf("failed to create accessor for kind %v: %s", obj.GetObjectKind(), err.Error()) } - versionedDS, ok := versionedObj.(*externalextensions.DaemonSet) - if !ok { - return "", fmt.Errorf("unexpected non-DaemonSet object returned: %v", versionedDS) + ds, history, err := daemonSetHistory(r.c.ExtensionsV1beta1(), r.c.AppsV1beta1(), accessor.GetNamespace(), accessor.GetName()) + if err != nil { + return "", err } - - if toRevision == 0 && len(allHistory) <= 1 { + if toRevision == 0 && len(history) <= 1 { return "", fmt.Errorf("no last revision to roll back to") } - toHistory := findHistory(toRevision, allHistory) + toHistory := findHistory(toRevision, history) if toHistory == nil { return "", revisionNotFoundErr(toRevision) } if dryRun { - appliedDS, err := applyHistory(versionedDS, toHistory) + appliedDS, err := applyDaemonSetHistory(ds, toHistory) if err != nil { return "", err } @@ -253,7 +278,7 @@ func (r *DaemonSetRollbacker) Rollback(obj runtime.Object, updatedAnnotations ma } // Skip if the revision already matches current DaemonSet - done, err := daemon.Match(versionedDS, toHistory) + done, err := daemon.Match(ds, toHistory) if err != nil { return "", err } @@ -262,7 +287,7 @@ func (r *DaemonSetRollbacker) Rollback(obj runtime.Object, updatedAnnotations ma } // Restore revision - if _, err = versionedExtensionsClient.DaemonSets(ds.Namespace).Patch(ds.Name, types.StrategicMergePatchType, toHistory.Data.Raw); err != nil { + if _, err = r.c.ExtensionsV1beta1().DaemonSets(accessor.GetNamespace()).Patch(accessor.GetName(), types.StrategicMergePatchType, toHistory.Data.Raw); err != nil { return "", fmt.Errorf("failed restoring revision %d: %v", toRevision, err) } @@ -270,7 +295,7 @@ func (r *DaemonSetRollbacker) Rollback(obj runtime.Object, updatedAnnotations ma } type StatefulSetRollbacker struct { - c clientset.Interface + c kubernetes.Interface } // toRevision is a non-negative integer, with 0 being reserved to indicate rolling back to previous configuration @@ -278,34 +303,25 @@ func (r *StatefulSetRollbacker) Rollback(obj runtime.Object, updatedAnnotations if toRevision < 0 { return "", revisionNotFoundErr(toRevision) } - - ss, ok := obj.(*apps.StatefulSet) - if !ok { - return "", fmt.Errorf("passed object is not a StatefulSet: %#v", obj) - } - versionedAppsClient := versionedAppsClientV1beta1(r.c) - versionedExtensionsClient := versionedExtensionsClientV1beta1(r.c) - versionedObj, allHistory, err := controlledHistories(versionedAppsClient, versionedExtensionsClient, ss.Namespace, ss.Name, "StatefulSet") + accessor, err := meta.Accessor(obj) if err != nil { - return "", fmt.Errorf("unable to find history controlled by StatefulSet %s: %v", ss.Name, err) + return "", fmt.Errorf("failed to create accessor for kind %v: %s", obj.GetObjectKind(), err.Error()) } - - versionedSS, ok := versionedObj.(*appsv1beta1.StatefulSet) - if !ok { - return "", fmt.Errorf("unexpected non-StatefulSet object returned: %v", versionedSS) + sts, history, err := statefulSetHistory(r.c.AppsV1beta1(), accessor.GetNamespace(), accessor.GetName()) + if err != nil { + return "", err } - - if toRevision == 0 && len(allHistory) <= 1 { + if toRevision == 0 && len(history) <= 1 { return "", fmt.Errorf("no last revision to roll back to") } - toHistory := findHistory(toRevision, allHistory) + toHistory := findHistory(toRevision, history) if toHistory == nil { return "", revisionNotFoundErr(toRevision) } if dryRun { - appliedSS, err := statefulset.ApplyRevision(versionedSS, toHistory) + appliedSS, err := statefulset.ApplyRevision(sts, toHistory) if err != nil { return "", err } @@ -313,7 +329,7 @@ func (r *StatefulSetRollbacker) Rollback(obj runtime.Object, updatedAnnotations } // Skip if the revision already matches current StatefulSet - done, err := statefulset.Match(versionedSS, toHistory) + done, err := statefulset.Match(sts, toHistory) if err != nil { return "", err } @@ -322,7 +338,7 @@ func (r *StatefulSetRollbacker) Rollback(obj runtime.Object, updatedAnnotations } // Restore revision - if _, err = versionedAppsClient.StatefulSets(ss.Namespace).Patch(ss.Name, types.StrategicMergePatchType, toHistory.Data.Raw); err != nil { + if _, err = r.c.AppsV1beta1().StatefulSets(sts.Namespace).Patch(sts.Name, types.StrategicMergePatchType, toHistory.Data.Raw); err != nil { return "", fmt.Errorf("failed restoring revision %d: %v", toRevision, err) } @@ -360,7 +376,7 @@ func printPodTemplate(specTemplate *v1.PodTemplateSpec) (string, error) { content := bytes.NewBuffer([]byte{}) w := printersinternal.NewPrefixWriter(content) internalTemplate := &api.PodTemplateSpec{} - if err := apiv1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(specTemplate, internalTemplate, nil); err != nil { + if err := apiv1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(specTemplate, internalTemplate, nil); err != nil { return "", fmt.Errorf("failed to convert podtemplate while printing: %v", err) } printersinternal.DescribePodTemplate(internalTemplate, w) diff --git a/pkg/kubectl/rollback_test.go b/pkg/kubectl/rollback_test.go new file mode 100644 index 00000000000..1d41650ba39 --- /dev/null +++ b/pkg/kubectl/rollback_test.go @@ -0,0 +1,46 @@ +/* +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 kubectl + +import ( + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/kubernetes/fake" +) + +var rollbacktests = map[schema.GroupKind]reflect.Type{ + {Group: "apps", Kind: "DaemonSet"}: reflect.TypeOf(&DaemonSetRollbacker{}), + {Group: "apps", Kind: "StatefulSet"}: reflect.TypeOf(&StatefulSetRollbacker{}), + {Group: "apps", Kind: "Deployment"}: reflect.TypeOf(&DeploymentRollbacker{}), +} + +func TestRollbackerFor(t *testing.T) { + fakeClientset := &fake.Clientset{} + + for kind, expectedType := range rollbacktests { + result, err := RollbackerFor(kind, fakeClientset) + if err != nil { + t.Fatalf("error getting Rollbacker for a %v: %v", kind.String(), err) + } + + if reflect.TypeOf(result) != expectedType { + t.Fatalf("unexpected output type (%v was expected but got %v)", expectedType, reflect.TypeOf(result)) + } + } +} diff --git a/pkg/kubectl/rolling_updater.go b/pkg/kubectl/rolling_updater.go index 395213d6405..1a1f8dfb330 100644 --- a/pkg/kubectl/rolling_updater.go +++ b/pkg/kubectl/rolling_updater.go @@ -32,9 +32,9 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/util/integer" "k8s.io/client-go/util/retry" - "k8s.io/kubernetes/pkg/api" - apiv1 "k8s.io/kubernetes/pkg/api/v1" podutil "k8s.io/kubernetes/pkg/api/v1/pod" + api "k8s.io/kubernetes/pkg/apis/core" + apiv1 "k8s.io/kubernetes/pkg/apis/core/v1" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" client "k8s.io/kubernetes/pkg/client/unversioned" deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util" @@ -425,7 +425,7 @@ func (r *RollingUpdater) readyPods(oldRc, newRc *api.ReplicationController, minR } for _, pod := range pods.Items { v1Pod := &v1.Pod{} - if err := apiv1.Convert_api_Pod_To_v1_Pod(&pod, v1Pod, nil); err != nil { + if err := apiv1.Convert_core_Pod_To_v1_Pod(&pod, v1Pod, nil); err != nil { return 0, 0, err } // Do not count deleted pods as ready diff --git a/pkg/kubectl/rolling_updater_test.go b/pkg/kubectl/rolling_updater_test.go index 2070df1d554..0bd04c6b184 100644 --- a/pkg/kubectl/rolling_updater_test.go +++ b/pkg/kubectl/rolling_updater_test.go @@ -36,9 +36,10 @@ import ( restclient "k8s.io/client-go/rest" manualfake "k8s.io/client-go/rest/fake" testcore "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/testapi" apitesting "k8s.io/kubernetes/pkg/api/testing" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" "k8s.io/kubernetes/pkg/kubectl/util" @@ -1468,7 +1469,7 @@ func TestUpdateRcWithRetries(t *testing.T) { {StatusCode: 200, Header: header, Body: objBody(codec, rc)}, } fakeClient := &manualfake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion, NegotiatedSerializer: testapi.Default.NegotiatedSerializer(), Client: manualfake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { @@ -1495,7 +1496,7 @@ func TestUpdateRcWithRetries(t *testing.T) { } }), } - clientConfig := &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: api.Codecs, GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}} + clientConfig := &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs, GroupVersion: &legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion}} restClient, _ := restclient.RESTClientFor(clientConfig) restClient.Client = fakeClient.Client clientset := internalclientset.New(restClient) @@ -1561,7 +1562,7 @@ func TestAddDeploymentHash(t *testing.T) { seen := sets.String{} updatedRc := false fakeClient := &manualfake.RESTClient{ - APIRegistry: api.Registry, + GroupVersion: legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion, NegotiatedSerializer: testapi.Default.NegotiatedSerializer(), Client: manualfake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { header := http.Header{} @@ -1596,7 +1597,7 @@ func TestAddDeploymentHash(t *testing.T) { } }), } - clientConfig := &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: api.Codecs, GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}} + clientConfig := &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs, GroupVersion: &legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion}} restClient, _ := restclient.RESTClientFor(clientConfig) restClient.Client = fakeClient.Client clientset := internalclientset.New(restClient) diff --git a/pkg/kubectl/rollout_status.go b/pkg/kubectl/rollout_status.go index d7d40af1601..24fcdf568f5 100644 --- a/pkg/kubectl/rollout_status.go +++ b/pkg/kubectl/rollout_status.go @@ -19,13 +19,13 @@ package kubectl import ( "fmt" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/kubernetes" + clientappsv1beta1 "k8s.io/client-go/kubernetes/typed/apps/v1beta1" + clientextensionsv1beta1 "k8s.io/client-go/kubernetes/typed/extensions/v1beta1" "k8s.io/kubernetes/pkg/apis/apps" - "k8s.io/kubernetes/pkg/apis/extensions" - "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" - appsclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion" - extensionsclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion" "k8s.io/kubernetes/pkg/controller/deployment/util" ) @@ -35,31 +35,31 @@ type StatusViewer interface { } // StatusViewerFor returns a StatusViewer for the resource specified by kind. -func StatusViewerFor(kind schema.GroupKind, c internalclientset.Interface) (StatusViewer, error) { +func StatusViewerFor(kind schema.GroupKind, c kubernetes.Interface) (StatusViewer, error) { switch kind { - case extensions.Kind("Deployment"), apps.Kind("Deployment"): - return &DeploymentStatusViewer{c.Extensions()}, nil - case extensions.Kind("DaemonSet"), apps.Kind("DaemonSet"): - return &DaemonSetStatusViewer{c.Extensions()}, nil + case extensionsv1beta1.SchemeGroupVersion.WithKind("Deployment").GroupKind(), apps.Kind("Deployment"): + return &DeploymentStatusViewer{c.ExtensionsV1beta1()}, nil + case extensionsv1beta1.SchemeGroupVersion.WithKind("DaemonSet").GroupKind(), apps.Kind("DaemonSet"): + return &DaemonSetStatusViewer{c.ExtensionsV1beta1()}, nil case apps.Kind("StatefulSet"): - return &StatefulSetStatusViewer{c.Apps()}, nil + return &StatefulSetStatusViewer{c.AppsV1beta1()}, nil } return nil, fmt.Errorf("no status viewer has been implemented for %v", kind) } // DeploymentStatusViewer implements the StatusViewer interface. type DeploymentStatusViewer struct { - c extensionsclient.DeploymentsGetter + c clientextensionsv1beta1.DeploymentsGetter } // DaemonSetStatusViewer implements the StatusViewer interface. type DaemonSetStatusViewer struct { - c extensionsclient.DaemonSetsGetter + c clientextensionsv1beta1.DaemonSetsGetter } // StatefulSetStatusViewer implements the StatusViewer interface. type StatefulSetStatusViewer struct { - c appsclient.StatefulSetsGetter + c clientappsv1beta1.StatefulSetsGetter } // Status returns a message describing deployment status, and a bool value indicating if the status is considered done. @@ -78,12 +78,12 @@ func (s *DeploymentStatusViewer) Status(namespace, name string, revision int64) } } if deployment.Generation <= deployment.Status.ObservedGeneration { - cond := util.GetDeploymentConditionInternal(deployment.Status, extensions.DeploymentProgressing) + cond := util.GetDeploymentCondition(deployment.Status, extensionsv1beta1.DeploymentProgressing) if cond != nil && cond.Reason == util.TimedOutReason { return "", false, fmt.Errorf("deployment %q exceeded its progress deadline", name) } - if deployment.Status.UpdatedReplicas < deployment.Spec.Replicas { - return fmt.Sprintf("Waiting for rollout to finish: %d out of %d new replicas have been updated...\n", deployment.Status.UpdatedReplicas, deployment.Spec.Replicas), false, nil + if deployment.Spec.Replicas != nil && deployment.Status.UpdatedReplicas < *deployment.Spec.Replicas { + return fmt.Sprintf("Waiting for rollout to finish: %d out of %d new replicas have been updated...\n", deployment.Status.UpdatedReplicas, *deployment.Spec.Replicas), false, nil } if deployment.Status.Replicas > deployment.Status.UpdatedReplicas { return fmt.Sprintf("Waiting for rollout to finish: %d old replicas are pending termination...\n", deployment.Status.Replicas-deployment.Status.UpdatedReplicas), false, nil @@ -104,7 +104,7 @@ func (s *DaemonSetStatusViewer) Status(namespace, name string, revision int64) ( if err != nil { return "", false, err } - if daemon.Spec.UpdateStrategy.Type != extensions.RollingUpdateDaemonSetStrategyType { + if daemon.Spec.UpdateStrategy.Type != extensionsv1beta1.RollingUpdateDaemonSetStrategyType { return "", true, fmt.Errorf("Status is available only for RollingUpdate strategy type") } if daemon.Generation <= daemon.Status.ObservedGeneration { @@ -131,13 +131,15 @@ func (s *StatefulSetStatusViewer) Status(namespace, name string, revision int64) if sts.Status.ObservedGeneration == nil || sts.Generation > *sts.Status.ObservedGeneration { return "Waiting for statefulset spec update to be observed...\n", false, nil } - if sts.Status.ReadyReplicas < sts.Spec.Replicas { - return fmt.Sprintf("Waiting for %d pods to be ready...\n", sts.Spec.Replicas-sts.Status.ReadyReplicas), false, nil + if sts.Spec.Replicas != nil && sts.Status.ReadyReplicas < *sts.Spec.Replicas { + return fmt.Sprintf("Waiting for %d pods to be ready...\n", *sts.Spec.Replicas-sts.Status.ReadyReplicas), false, nil } if sts.Spec.UpdateStrategy.Type == apps.RollingUpdateStatefulSetStrategyType && sts.Spec.UpdateStrategy.RollingUpdate != nil { - if sts.Status.UpdatedReplicas < (sts.Spec.Replicas - sts.Spec.UpdateStrategy.RollingUpdate.Partition) { - return fmt.Sprintf("Waiting for partitioned roll out to finish: %d out of %d new pods have been updated...\n", - sts.Status.UpdatedReplicas, (sts.Spec.Replicas - sts.Spec.UpdateStrategy.RollingUpdate.Partition)), false, nil + if sts.Spec.Replicas != nil && sts.Spec.UpdateStrategy.RollingUpdate.Partition != nil { + if sts.Status.UpdatedReplicas < (*sts.Spec.Replicas - *sts.Spec.UpdateStrategy.RollingUpdate.Partition) { + return fmt.Sprintf("Waiting for partitioned roll out to finish: %d out of %d new pods have been updated...\n", + sts.Status.UpdatedReplicas, (*sts.Spec.Replicas - *sts.Spec.UpdateStrategy.RollingUpdate.Partition)), false, nil + } } return fmt.Sprintf("partitioned roll out complete: %d new pods have been updated...\n", sts.Status.UpdatedReplicas), true, nil diff --git a/pkg/kubectl/rollout_status_test.go b/pkg/kubectl/rollout_status_test.go index ae13d9b0965..38fd2348f6d 100644 --- a/pkg/kubectl/rollout_status_test.go +++ b/pkg/kubectl/rollout_status_test.go @@ -20,11 +20,11 @@ import ( "fmt" "testing" + apps "k8s.io/api/apps/v1beta1" + api "k8s.io/api/core/v1" + extensions "k8s.io/api/extensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/apps" - "k8s.io/kubernetes/pkg/apis/extensions" - "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + "k8s.io/client-go/kubernetes/fake" ) func TestDeploymentStatusViewerStatus(t *testing.T) { @@ -116,7 +116,7 @@ func TestDeploymentStatusViewerStatus(t *testing.T) { Generation: test.generation, }, Spec: extensions.DeploymentSpec{ - Replicas: test.specReplicas, + Replicas: &test.specReplicas, }, Status: test.status, } @@ -306,7 +306,8 @@ func TestStatefulSetStatusViewerStatus(t *testing.T) { generation: 1, strategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType, RollingUpdate: func() *apps.RollingUpdateStatefulSetStrategy { - return &apps.RollingUpdateStatefulSetStrategy{Partition: 2} + partition := int32(2) + return &apps.RollingUpdateStatefulSetStrategy{Partition: &partition} }()}, status: apps.StatefulSetStatus{ ObservedGeneration: func() *int64 { @@ -328,7 +329,8 @@ func TestStatefulSetStatusViewerStatus(t *testing.T) { generation: 1, strategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType, RollingUpdate: func() *apps.RollingUpdateStatefulSetStrategy { - return &apps.RollingUpdateStatefulSetStrategy{Partition: 2} + partition := int32(2) + return &apps.RollingUpdateStatefulSetStrategy{Partition: &partition} }()}, status: apps.StatefulSetStatus{ ObservedGeneration: func() *int64 { @@ -373,7 +375,7 @@ func TestStatefulSetStatusViewerStatus(t *testing.T) { s.Status = test.status s.Spec.UpdateStrategy = test.strategy s.Generation = test.generation - client := fake.NewSimpleClientset(s).Apps() + client := fake.NewSimpleClientset(s).AppsV1beta1() dsv := &StatefulSetStatusViewer{c: client} msg, done, err := dsv.Status(s.Namespace, s.Name, 0) if test.err && err == nil { @@ -439,7 +441,7 @@ func newStatefulSet(replicas int32) *apps.StatefulSet { DNSPolicy: api.DNSClusterFirst, }, }, - Replicas: replicas, + Replicas: &replicas, UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, }, Status: apps.StatefulSetStatus{}, diff --git a/pkg/kubectl/run.go b/pkg/kubectl/run.go index 7dd77f1f391..e78bfd4c319 100644 --- a/pkg/kubectl/run.go +++ b/pkg/kubectl/run.go @@ -31,7 +31,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation" - "k8s.io/kubernetes/pkg/api" ) type DeploymentV1Beta1 struct{} @@ -102,8 +101,6 @@ func (DeploymentV1Beta1) Generate(genericParams map[string]interface{}) (runtime return nil, err } - // TODO: use versioned types for generators so that we don't need to - // set default values manually (see issue #17384) count32 := int32(count) deployment := extensionsv1beta1.Deployment{ ObjectMeta: metav1.ObjectMeta{ @@ -608,31 +605,6 @@ func (BasicReplicationController) ParamNames() []GeneratorParam { } } -// populateResourceList takes strings of form =,= -// and returns ResourceList. -func populateResourceList(spec string) (api.ResourceList, error) { - // empty input gets a nil response to preserve generator test expected behaviors - if spec == "" { - return nil, nil - } - - result := api.ResourceList{} - resourceStatements := strings.Split(spec, ",") - for _, resourceStatement := range resourceStatements { - parts := strings.Split(resourceStatement, "=") - if len(parts) != 2 { - return nil, fmt.Errorf("Invalid argument syntax %v, expected =", resourceStatement) - } - resourceName := api.ResourceName(parts[0]) - resourceQuantity, err := resource.ParseQuantity(parts[1]) - if err != nil { - return nil, err - } - result[resourceName] = resourceQuantity - } - return result, nil -} - // populateResourceListV1 takes strings of form =,= // and returns ResourceList. func populateResourceListV1(spec string) (v1.ResourceList, error) { @@ -658,23 +630,6 @@ func populateResourceListV1(spec string) (v1.ResourceList, error) { return result, nil } -// HandleResourceRequirements parses the limits and requests parameters if specified -// and returns ResourceRequirements. -func HandleResourceRequirements(params map[string]string) (api.ResourceRequirements, error) { - result := api.ResourceRequirements{} - limits, err := populateResourceList(params["limits"]) - if err != nil { - return result, err - } - result.Limits = limits - requests, err := populateResourceList(params["requests"]) - if err != nil { - return result, err - } - result.Requests = requests - return result, nil -} - // HandleResourceRequirementsV1 parses the limits and requests parameters if specified // and returns ResourceRequirements. func HandleResourceRequirementsV1(params map[string]string) (v1.ResourceRequirements, error) { diff --git a/pkg/kubectl/scale.go b/pkg/kubectl/scale.go index 20734bc5f77..1d4165f9626 100644 --- a/pkg/kubectl/scale.go +++ b/pkg/kubectl/scale.go @@ -21,16 +21,19 @@ import ( "strconv" "time" + autoscalingapi "k8s.io/api/autoscaling/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/watch" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/batch" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" + + scaleclient "k8s.io/client-go/scale" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" appsclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion" batchclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion" @@ -516,3 +519,93 @@ func (scaler *DeploymentScaler) Scale(namespace, name string, newSize uint, prec } return nil } + +// validateGeneric ensures that the preconditions match. Returns nil if they are valid, otherwise an error +// TODO(p0lyn0mial): when the work on GenericScaler is done, rename validateGeneric to validate +func (precondition *ScalePrecondition) validateGeneric(scale *autoscalingapi.Scale) error { + if precondition.Size != -1 && int(scale.Spec.Replicas) != precondition.Size { + return PreconditionError{"replicas", strconv.Itoa(precondition.Size), strconv.Itoa(int(scale.Spec.Replicas))} + } + if len(precondition.ResourceVersion) > 0 && scale.ResourceVersion != precondition.ResourceVersion { + return PreconditionError{"resource version", precondition.ResourceVersion, scale.ResourceVersion} + } + return nil +} + +// GenericScaler can update scales for resources in a particular namespace +// TODO(o0lyn0mial): when the work on GenericScaler is done, don't +// export the GenericScaler. Instead use ScalerFor method for getting the Scaler +// also update the UTs +type GenericScaler struct { + scaleNamespacer scaleclient.ScalesGetter + targetGR schema.GroupResource +} + +var _ Scaler = &GenericScaler{} + +// ScaleSimple updates a scale of a given resource. It returns the resourceVersion of the scale if the update was successful. +func (s *GenericScaler) ScaleSimple(namespace, name string, preconditions *ScalePrecondition, newSize uint) (updatedResourceVersion string, err error) { + scale, err := s.scaleNamespacer.Scales(namespace).Get(s.targetGR, name) + if err != nil { + return "", ScaleError{ScaleGetFailure, "", err} + } + if preconditions != nil { + if err := preconditions.validateGeneric(scale); err != nil { + return "", err + } + } + + scale.Spec.Replicas = int32(newSize) + updatedScale, err := s.scaleNamespacer.Scales(namespace).Update(s.targetGR, scale) + if err != nil { + if errors.IsConflict(err) { + return "", ScaleError{ScaleUpdateConflictFailure, scale.ResourceVersion, err} + } + return "", ScaleError{ScaleUpdateFailure, scale.ResourceVersion, err} + } + return updatedScale.ResourceVersion, nil +} + +// Scale updates a scale of a given resource to a new size, with optional precondition check (if preconditions is not nil), +// optional retries (if retry is not nil), and then optionally waits for the status to reach desired count. +func (s *GenericScaler) Scale(namespace, resourceName string, newSize uint, preconditions *ScalePrecondition, retry, waitForReplicas *RetryParams) error { + if preconditions == nil { + preconditions = &ScalePrecondition{-1, ""} + } + if retry == nil { + // make it try only once, immediately + retry = &RetryParams{Interval: time.Millisecond, Timeout: time.Millisecond} + } + cond := ScaleCondition(s, preconditions, namespace, resourceName, newSize, nil) + if err := wait.PollImmediate(retry.Interval, retry.Timeout, cond); err != nil { + return err + } + if waitForReplicas != nil { + err := wait.PollImmediate( + waitForReplicas.Interval, + waitForReplicas.Timeout, + scaleHasDesiredReplicas(s.scaleNamespacer, s.targetGR, resourceName, namespace, int32(newSize))) + if err == wait.ErrWaitTimeout { + return fmt.Errorf("timed out waiting for %q to be synced", resourceName) + } + return err + } + return nil +} + +// scaleHasDesiredReplicas returns a condition that will be true if and only if the desired replica +// count for a scale (Spec) equals its updated replicas count (Status) +func scaleHasDesiredReplicas(sClient scaleclient.ScalesGetter, gr schema.GroupResource, resourceName string, namespace string, desiredReplicas int32) wait.ConditionFunc { + return func() (bool, error) { + actualScale, err := sClient.Scales(namespace).Get(gr, resourceName) + if err != nil { + return false, err + } + // this means the desired scale target has been reset by something else + if actualScale.Spec.Replicas != desiredReplicas { + return true, nil + } + return actualScale.Spec.Replicas == actualScale.Status.Replicas && + desiredReplicas == actualScale.Status.Replicas, nil + } +} diff --git a/pkg/kubectl/scale_test.go b/pkg/kubectl/scale_test.go index 6e6cd62ed46..1d9b5119eab 100644 --- a/pkg/kubectl/scale_test.go +++ b/pkg/kubectl/scale_test.go @@ -17,16 +17,35 @@ limitations under the License. package kubectl import ( + "bytes" + "encoding/json" "errors" + "fmt" + "io" + "io/ioutil" + "net/http" "testing" + "time" + appsv1beta2 "k8s.io/api/apps/v1beta2" kerrors "k8s.io/apimachinery/pkg/api/errors" + apimeta "k8s.io/apimachinery/pkg/api/meta" 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/client-go/discovery" + fakedisco "k8s.io/client-go/discovery/fake" + "k8s.io/client-go/dynamic" + fakerest "k8s.io/client-go/rest/fake" + "k8s.io/client-go/scale" testcore "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/batch" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + appsclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion" batchclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" extensionsclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion" @@ -321,7 +340,7 @@ func TestJobScaleRetry(t *testing.T) { scaleFunc = ScaleCondition(&scaler, &preconditions, namespace, name, count, nil) pass, err = scaleFunc() if err == nil { - t.Errorf("Expected error on precondition failure") + t.Error("Expected error on precondition failure") } } @@ -347,7 +366,7 @@ func TestJobScale(t *testing.T) { t.Errorf("unexpected actions: %v, expected 2 actions (get, update)", actions) } if action, ok := actions[0].(testcore.GetAction); !ok || action.GetResource().GroupResource() != batch.Resource("jobs") || action.GetName() != name { - t.Errorf("unexpected action: %v, expected get-replicationController %s", actions[0], name) + t.Errorf("unexpected action: %v, expected get-job %s", actions[0], name) } if action, ok := actions[1].(testcore.UpdateAction); !ok || action.GetResource().GroupResource() != batch.Resource("jobs") || *action.GetObject().(*batch.Job).Spec.Parallelism != int32(count) { t.Errorf("unexpected action %v, expected update-job with parallelism = %d", actions[1], count) @@ -583,7 +602,7 @@ func TestDeploymentScaleRetry(t *testing.T) { scaleFunc = ScaleCondition(scaler, preconditions, namespace, name, count, nil) pass, err = scaleFunc() if err == nil { - t.Errorf("Expected error on precondition failure") + t.Error("Expected error on precondition failure") } } @@ -609,7 +628,7 @@ func TestDeploymentScale(t *testing.T) { t.Errorf("unexpected actions: %v, expected 2 actions (get, update)", actions) } if action, ok := actions[0].(testcore.GetAction); !ok || action.GetResource().GroupResource() != extensions.Resource("deployments") || action.GetName() != name { - t.Errorf("unexpected action: %v, expected get-replicationController %s", actions[0], name) + t.Errorf("unexpected action: %v, expected get-deployment %s", actions[0], name) } if action, ok := actions[1].(testcore.UpdateAction); !ok || action.GetResource().GroupResource() != extensions.Resource("deployments") || action.GetObject().(*extensions.Deployment).Spec.Replicas != int32(count) { t.Errorf("unexpected action %v, expected update-deployment with replicas = %d", actions[1], count) @@ -785,3 +804,806 @@ func TestValidateDeployment(t *testing.T) { } } } + +type ErrorStatefulSets struct { + appsclient.StatefulSetInterface + conflict bool + invalid bool +} + +func (c *ErrorStatefulSets) Update(statefulSet *apps.StatefulSet) (*apps.StatefulSet, error) { + switch { + case c.invalid: + return nil, kerrors.NewInvalid(api.Kind(statefulSet.Kind), statefulSet.Name, nil) + case c.conflict: + return nil, kerrors.NewConflict(api.Resource(statefulSet.Kind), statefulSet.Name, nil) + } + return nil, errors.New("statefulSet update failure") +} + +func (c *ErrorStatefulSets) Get(name string, options metav1.GetOptions) (*apps.StatefulSet, error) { + return &apps.StatefulSet{ + Spec: apps.StatefulSetSpec{ + Replicas: 0, + }, + }, nil +} + +type ErrorStatefulSetClient struct { + appsclient.StatefulSetsGetter + conflict bool + invalid bool +} + +func (c *ErrorStatefulSetClient) StatefulSets(namespace string) appsclient.StatefulSetInterface { + return &ErrorStatefulSets{ + StatefulSetInterface: c.StatefulSetsGetter.StatefulSets(namespace), + invalid: c.invalid, + conflict: c.conflict, + } +} + +func statefulSet() *apps.StatefulSet { + return &apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: metav1.NamespaceDefault, + Name: "foo", + }, + } +} + +func TestStatefulSetScale(t *testing.T) { + fake := fake.NewSimpleClientset(statefulSet()) + scaler := StatefulSetScaler{fake.Apps()} + preconditions := ScalePrecondition{-1, ""} + count := uint(3) + name := "foo" + scaler.Scale("default", name, count, &preconditions, nil, nil) + + actions := fake.Actions() + if len(actions) != 2 { + t.Errorf("unexpected actions: %v, expected 2 actions (get, update)", actions) + } + + if action, ok := actions[0].(testcore.GetAction); !ok || action.GetResource().GroupResource() != apps.Resource("statefulsets") || action.GetName() != name { + t.Errorf("unexpected action: %v, expected get-statefulsets %s", actions[0], name) + } + if action, ok := actions[1].(testcore.UpdateAction); !ok || action.GetResource().GroupResource() != apps.Resource("statefulsets") || action.GetObject().(*apps.StatefulSet).Spec.Replicas != int32(count) { + t.Errorf("unexpected action %v, expected update-statefulset with replicas = %d", actions[1], count) + } +} + +func TestStatefulSetScaleRetry(t *testing.T) { + fake := &ErrorStatefulSetClient{StatefulSetsGetter: fake.NewSimpleClientset().Apps(), conflict: true} + scaler := &StatefulSetScaler{fake} + preconditions := &ScalePrecondition{-1, ""} + count := uint(3) + name := "foo" + namespace := "default" + + scaleFunc := ScaleCondition(scaler, preconditions, namespace, name, count, nil) + pass, err := scaleFunc() + if pass != false { + t.Errorf("Expected an update failure to return pass = false, got pass = %v", pass) + } + if err != nil { + t.Errorf("Did not expect an error on update failure, got %v", err) + } + preconditions = &ScalePrecondition{3, ""} + scaleFunc = ScaleCondition(scaler, preconditions, namespace, name, count, nil) + pass, err = scaleFunc() + if err == nil { + t.Error("Expected error on precondition failure") + } +} + +func TestStatefulSetScaleInvalid(t *testing.T) { + fake := &ErrorStatefulSetClient{StatefulSetsGetter: fake.NewSimpleClientset().Apps(), invalid: true} + scaler := StatefulSetScaler{fake} + preconditions := ScalePrecondition{-1, ""} + count := uint(3) + name := "foo" + namespace := "default" + + scaleFunc := ScaleCondition(&scaler, &preconditions, namespace, name, count, nil) + pass, err := scaleFunc() + if pass { + t.Errorf("Expected an update failure to return pass = false, got pass = %v", pass) + } + e, ok := err.(ScaleError) + if err == nil || !ok || e.FailureType != ScaleUpdateFailure { + t.Errorf("Expected error on invalid update failure, got %v", err) + } +} + +func TestStatefulSetScaleFailsPreconditions(t *testing.T) { + fake := fake.NewSimpleClientset(&apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: metav1.NamespaceDefault, + Name: "foo", + }, + Spec: apps.StatefulSetSpec{ + Replicas: 10, + }, + }) + scaler := StatefulSetScaler{fake.Apps()} + preconditions := ScalePrecondition{2, ""} + count := uint(3) + name := "foo" + scaler.Scale("default", name, count, &preconditions, nil, nil) + + actions := fake.Actions() + if len(actions) != 1 { + t.Errorf("unexpected actions: %v, expected 1 actions (get)", actions) + } + if action, ok := actions[0].(testcore.GetAction); !ok || action.GetResource().GroupResource() != apps.Resource("statefulsets") || action.GetName() != name { + t.Errorf("unexpected action: %v, expected get-statefulset %s", actions[0], name) + } +} + +func TestValidateStatefulSet(t *testing.T) { + zero, ten, twenty := int32(0), int32(10), int32(20) + tests := []struct { + preconditions ScalePrecondition + statefulset apps.StatefulSet + expectError bool + test string + }{ + { + preconditions: ScalePrecondition{-1, ""}, + expectError: false, + test: "defaults", + }, + { + preconditions: ScalePrecondition{-1, ""}, + statefulset: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "foo", + }, + Spec: apps.StatefulSetSpec{ + Replicas: ten, + }, + }, + expectError: false, + test: "defaults 2", + }, + { + preconditions: ScalePrecondition{0, ""}, + statefulset: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "foo", + }, + Spec: apps.StatefulSetSpec{ + Replicas: zero, + }, + }, + expectError: false, + test: "size matches", + }, + { + preconditions: ScalePrecondition{-1, "foo"}, + statefulset: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "foo", + }, + Spec: apps.StatefulSetSpec{ + Replicas: ten, + }, + }, + expectError: false, + test: "resource version matches", + }, + { + preconditions: ScalePrecondition{10, "foo"}, + statefulset: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "foo", + }, + Spec: apps.StatefulSetSpec{ + Replicas: ten, + }, + }, + expectError: false, + test: "both match", + }, + { + preconditions: ScalePrecondition{10, "foo"}, + statefulset: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "foo", + }, + Spec: apps.StatefulSetSpec{ + Replicas: twenty, + }, + }, + expectError: true, + test: "size different", + }, + { + preconditions: ScalePrecondition{10, "foo"}, + statefulset: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "foo", + }, + }, + expectError: true, + test: "no replicas", + }, + { + preconditions: ScalePrecondition{10, "foo"}, + statefulset: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "bar", + }, + Spec: apps.StatefulSetSpec{ + Replicas: ten, + }, + }, + expectError: true, + test: "version different", + }, + { + preconditions: ScalePrecondition{10, "foo"}, + statefulset: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "bar", + }, + Spec: apps.StatefulSetSpec{ + Replicas: twenty, + }, + }, + expectError: true, + test: "both different", + }, + } + for _, test := range tests { + err := test.preconditions.ValidateStatefulSet(&test.statefulset) + if err != nil && !test.expectError { + t.Errorf("unexpected error: %v (%s)", err, test.test) + } + if err == nil && test.expectError { + t.Errorf("expected an error: %v (%s)", err, test.test) + } + } +} + +type ErrorReplicaSets struct { + extensionsclient.ReplicaSetInterface + conflict bool + invalid bool +} + +func (c *ErrorReplicaSets) Update(replicaSets *extensions.ReplicaSet) (*extensions.ReplicaSet, error) { + switch { + case c.invalid: + return nil, kerrors.NewInvalid(api.Kind(replicaSets.Kind), replicaSets.Name, nil) + case c.conflict: + return nil, kerrors.NewConflict(api.Resource(replicaSets.Kind), replicaSets.Name, nil) + } + return nil, errors.New("replicaSets update failure") +} + +func (c *ErrorReplicaSets) Get(name string, options metav1.GetOptions) (*extensions.ReplicaSet, error) { + return &extensions.ReplicaSet{ + Spec: extensions.ReplicaSetSpec{ + Replicas: 0, + }, + }, nil +} + +type ErrorReplicaSetClient struct { + extensionsclient.ReplicaSetsGetter + conflict bool + invalid bool +} + +func (c *ErrorReplicaSetClient) ReplicaSets(namespace string) extensionsclient.ReplicaSetInterface { + return &ErrorReplicaSets{ + ReplicaSetInterface: c.ReplicaSetsGetter.ReplicaSets(namespace), + invalid: c.invalid, + conflict: c.conflict, + } +} + +func replicaSet() *extensions.ReplicaSet { + return &extensions.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: metav1.NamespaceDefault, + Name: "foo", + }, + } +} + +func TestReplicaSetScale(t *testing.T) { + fake := fake.NewSimpleClientset(replicaSet()) + scaler := ReplicaSetScaler{fake.Extensions()} + preconditions := ScalePrecondition{-1, ""} + count := uint(3) + name := "foo" + scaler.Scale("default", name, count, &preconditions, nil, nil) + + actions := fake.Actions() + if len(actions) != 2 { + t.Errorf("unexpected actions: %v, expected 2 actions (get, update)", actions) + } + if action, ok := actions[0].(testcore.GetAction); !ok || action.GetResource().GroupResource() != extensions.Resource("replicasets") || action.GetName() != name { + t.Errorf("unexpected action: %v, expected get-replicationSet %s", actions[0], name) + } + if action, ok := actions[1].(testcore.UpdateAction); !ok || action.GetResource().GroupResource() != extensions.Resource("replicasets") || action.GetObject().(*extensions.ReplicaSet).Spec.Replicas != int32(count) { + t.Errorf("unexpected action %v, expected update-replicaSet with replicas = %d", actions[1], count) + } +} + +func TestReplicaSetScaleRetry(t *testing.T) { + fake := &ErrorReplicaSetClient{ReplicaSetsGetter: fake.NewSimpleClientset().Extensions(), conflict: true} + scaler := &ReplicaSetScaler{fake} + preconditions := &ScalePrecondition{-1, ""} + count := uint(3) + name := "foo" + namespace := "default" + + scaleFunc := ScaleCondition(scaler, preconditions, namespace, name, count, nil) + pass, err := scaleFunc() + if pass != false { + t.Errorf("Expected an update failure to return pass = false, got pass = %v", pass) + } + if err != nil { + t.Errorf("Did not expect an error on update failure, got %v", err) + } + preconditions = &ScalePrecondition{3, ""} + scaleFunc = ScaleCondition(scaler, preconditions, namespace, name, count, nil) + pass, err = scaleFunc() + if err == nil { + t.Error("Expected error on precondition failure") + } +} + +func TestReplicaSetScaleInvalid(t *testing.T) { + fake := &ErrorReplicaSetClient{ReplicaSetsGetter: fake.NewSimpleClientset().Extensions(), invalid: true} + scaler := ReplicaSetScaler{fake} + preconditions := ScalePrecondition{-1, ""} + count := uint(3) + name := "foo" + namespace := "default" + + scaleFunc := ScaleCondition(&scaler, &preconditions, namespace, name, count, nil) + pass, err := scaleFunc() + if pass { + t.Errorf("Expected an update failure to return pass = false, got pass = %v", pass) + } + e, ok := err.(ScaleError) + if err == nil || !ok || e.FailureType != ScaleUpdateFailure { + t.Errorf("Expected error on invalid update failure, got %v", err) + } +} + +func TestReplicaSetsGetterFailsPreconditions(t *testing.T) { + fake := fake.NewSimpleClientset(&extensions.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: metav1.NamespaceDefault, + Name: "foo", + }, + Spec: extensions.ReplicaSetSpec{ + Replicas: 10, + }, + }) + scaler := ReplicaSetScaler{fake.Extensions()} + preconditions := ScalePrecondition{2, ""} + count := uint(3) + name := "foo" + scaler.Scale("default", name, count, &preconditions, nil, nil) + + actions := fake.Actions() + if len(actions) != 1 { + t.Errorf("unexpected actions: %v, expected 1 actions (get)", actions) + } + if action, ok := actions[0].(testcore.GetAction); !ok || action.GetResource().GroupResource() != extensions.Resource("replicasets") || action.GetName() != name { + t.Errorf("unexpected action: %v, expected get-replicaSets %s", actions[0], name) + } +} + +func TestValidateReplicaSets(t *testing.T) { + zero, ten, twenty := int32(0), int32(10), int32(20) + tests := []struct { + preconditions ScalePrecondition + replicaSets extensions.ReplicaSet + expectError bool + test string + }{ + { + preconditions: ScalePrecondition{-1, ""}, + expectError: false, + test: "defaults", + }, + { + preconditions: ScalePrecondition{-1, ""}, + replicaSets: extensions.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "foo", + }, + Spec: extensions.ReplicaSetSpec{ + Replicas: ten, + }, + }, + expectError: false, + test: "defaults 2", + }, + { + preconditions: ScalePrecondition{0, ""}, + replicaSets: extensions.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "foo", + }, + Spec: extensions.ReplicaSetSpec{ + Replicas: zero, + }, + }, + expectError: false, + test: "size matches", + }, + { + preconditions: ScalePrecondition{-1, "foo"}, + replicaSets: extensions.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "foo", + }, + Spec: extensions.ReplicaSetSpec{ + Replicas: ten, + }, + }, + expectError: false, + test: "resource version matches", + }, + { + preconditions: ScalePrecondition{10, "foo"}, + replicaSets: extensions.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "foo", + }, + Spec: extensions.ReplicaSetSpec{ + Replicas: ten, + }, + }, + expectError: false, + test: "both match", + }, + { + preconditions: ScalePrecondition{10, "foo"}, + replicaSets: extensions.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "foo", + }, + Spec: extensions.ReplicaSetSpec{ + Replicas: twenty, + }, + }, + expectError: true, + test: "size different", + }, + { + preconditions: ScalePrecondition{10, "foo"}, + replicaSets: extensions.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "foo", + }, + }, + expectError: true, + test: "no replicas", + }, + { + preconditions: ScalePrecondition{10, "foo"}, + replicaSets: extensions.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "bar", + }, + Spec: extensions.ReplicaSetSpec{ + Replicas: ten, + }, + }, + expectError: true, + test: "version different", + }, + { + preconditions: ScalePrecondition{10, "foo"}, + replicaSets: extensions.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "bar", + }, + Spec: extensions.ReplicaSetSpec{ + Replicas: twenty, + }, + }, + expectError: true, + test: "both different", + }, + } + for _, test := range tests { + err := test.preconditions.ValidateReplicaSet(&test.replicaSets) + if err != nil && !test.expectError { + t.Errorf("unexpected error: %v (%s)", err, test.test) + } + if err == nil && test.expectError { + t.Errorf("expected an error: %v (%s)", err, test.test) + } + } +} + +// TestGenericScaleSimple exercises GenericScaler.ScaleSimple method +func TestGenericScaleSimple(t *testing.T) { + // test data + discoveryResources := []*metav1.APIResourceList{ + { + GroupVersion: appsv1beta2.SchemeGroupVersion.String(), + APIResources: []metav1.APIResource{ + {Name: "deployments", Namespaced: true, Kind: "Deployment"}, + {Name: "deployments/scale", Namespaced: true, Kind: "Scale", Group: "apps", Version: "v1beta2"}, + }, + }, + } + appsV1beta2Scale := &appsv1beta2.Scale{ + TypeMeta: metav1.TypeMeta{ + Kind: "Scale", + APIVersion: appsv1beta2.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + }, + Spec: appsv1beta2.ScaleSpec{Replicas: 10}, + Status: appsv1beta2.ScaleStatus{ + Replicas: 10, + }, + } + pathsResources := map[string]runtime.Object{ + "/apis/apps/v1beta2/namespaces/default/deployments/abc/scale": appsV1beta2Scale, + } + + scaleClient, err := fakeScaleClient(discoveryResources, pathsResources) + if err != nil { + t.Fatal(err) + } + + // test scenarios + scenarios := []struct { + name string + precondition ScalePrecondition + newSize int + targetGR schema.GroupResource + resName string + scaleGetter scale.ScalesGetter + expectError bool + }{ + // scenario 1: scale up the "abc" deployment + { + name: "scale up the \"abc\" deployment", + precondition: ScalePrecondition{10, ""}, + newSize: 20, + targetGR: schema.GroupResource{Group: "apps", Resource: "deployment"}, + resName: "abc", + scaleGetter: scaleClient, + }, + // scenario 2: scale down the "abc" deployment + { + name: "scale down the \"abs\" deplyment", + precondition: ScalePrecondition{20, ""}, + newSize: 5, + targetGR: schema.GroupResource{Group: "apps", Resource: "deployment"}, + resName: "abc", + scaleGetter: scaleClient, + }, + // scenario 3: precondition error, expected size is 1, + // note that the previous scenario (2) set the size to 5 + { + name: "precondition error, expected size is 1", + precondition: ScalePrecondition{1, ""}, + newSize: 5, + targetGR: schema.GroupResource{Group: "apps", Resource: "deployment"}, + resName: "abc", + scaleGetter: scaleClient, + expectError: true, + }, + // scenario 4: precondition is not validated when the precondition size is set to -1 + { + name: "precondition is not validated when the size is set to -1", + precondition: ScalePrecondition{-1, ""}, + newSize: 5, + targetGR: schema.GroupResource{Group: "apps", Resource: "deployment"}, + resName: "abc", + scaleGetter: scaleClient, + }, + // scenario 5: precondition error, resource version mismatch + { + name: "precondition error, resource version mismatch", + precondition: ScalePrecondition{5, "v1"}, + newSize: 5, + targetGR: schema.GroupResource{Group: "apps", Resource: "deployment"}, + resName: "abc", + scaleGetter: scaleClient, + expectError: true, + }, + } + + // act + for index, scenario := range scenarios { + t.Run(fmt.Sprintf("running scenario %d: %s", index+1, scenario.name), func(t *testing.T) { + target := GenericScaler{scenario.scaleGetter, scenario.targetGR} + + resVersion, err := target.ScaleSimple("default", scenario.resName, &scenario.precondition, uint(scenario.newSize)) + + if scenario.expectError && err == nil { + t.Fatal("expeced an error but was not returned") + } + if !scenario.expectError && err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resVersion != "" { + t.Fatalf("unexpected resource version returned = %s, wanted = %s", resVersion, "") + } + }) + } +} + +// TestGenericScale exercises GenericScaler.Scale method +func TestGenericScale(t *testing.T) { + // test data + discoveryResources := []*metav1.APIResourceList{ + { + GroupVersion: appsv1beta2.SchemeGroupVersion.String(), + APIResources: []metav1.APIResource{ + {Name: "deployments", Namespaced: true, Kind: "Deployment"}, + {Name: "deployments/scale", Namespaced: true, Kind: "Scale", Group: "apps", Version: "v1beta2"}, + }, + }, + } + appsV1beta2Scale := &appsv1beta2.Scale{ + TypeMeta: metav1.TypeMeta{ + Kind: "Scale", + APIVersion: appsv1beta2.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + }, + Spec: appsv1beta2.ScaleSpec{Replicas: 10}, + Status: appsv1beta2.ScaleStatus{ + Replicas: 10, + }, + } + pathsResources := map[string]runtime.Object{ + "/apis/apps/v1beta2/namespaces/default/deployments/abc/scale": appsV1beta2Scale, + } + + scaleClient, err := fakeScaleClient(discoveryResources, pathsResources) + if err != nil { + t.Fatal(err) + } + + // test scenarios + scenarios := []struct { + name string + precondition ScalePrecondition + newSize int + targetGR schema.GroupResource + resName string + scaleGetter scale.ScalesGetter + waitForReplicas *RetryParams + expectError bool + }{ + // scenario 1: scale up the "abc" deployment + { + name: "scale up the \"abc\" deployment", + precondition: ScalePrecondition{10, ""}, + newSize: 20, + targetGR: schema.GroupResource{Group: "apps", Resource: "deployment"}, + resName: "abc", + scaleGetter: scaleClient, + }, + // scenario 2: a resource name cannot be empty + { + name: "a resource name cannot be empty", + precondition: ScalePrecondition{10, ""}, + newSize: 20, + targetGR: schema.GroupResource{Group: "apps", Resource: "deployment"}, + resName: "", + scaleGetter: scaleClient, + expectError: true, + }, + // scenario 3: wait for replicas error due to status.Replicas != spec.Replicas + { + name: "wait for replicas error due to status.Replicas != spec.Replicas", + precondition: ScalePrecondition{10, ""}, + newSize: 20, + targetGR: schema.GroupResource{Group: "apps", Resource: "deployment"}, + resName: "abc", + scaleGetter: scaleClient, + waitForReplicas: &RetryParams{time.Duration(5 * time.Second), time.Duration(5 * time.Second)}, + expectError: true, + }, + } + + // act + for index, scenario := range scenarios { + t.Run(fmt.Sprintf("running scenario %d: %s", index+1, scenario.name), func(t *testing.T) { + target := GenericScaler{scenario.scaleGetter, scenario.targetGR} + + err := target.Scale("default", scenario.resName, uint(scenario.newSize), &scenario.precondition, nil, scenario.waitForReplicas) + + if scenario.expectError && err == nil { + t.Fatal("expeced an error but was not returned") + } + if !scenario.expectError && err != nil { + t.Fatalf("unexpected error: %v", err) + } + }) + } +} + +func fakeScaleClient(discoveryResources []*metav1.APIResourceList, pathsResources map[string]runtime.Object) (scale.ScalesGetter, error) { + fakeDiscoveryClient := &fakedisco.FakeDiscovery{Fake: &testcore.Fake{}} + fakeDiscoveryClient.Resources = discoveryResources + restMapperRes, err := discovery.GetAPIGroupResources(fakeDiscoveryClient) + if err != nil { + return nil, err + } + restMapper := discovery.NewRESTMapper(restMapperRes, apimeta.InterfacesForUnstructured) + codecs := serializer.NewCodecFactory(scale.NewScaleConverter().Scheme()) + fakeReqHandler := func(req *http.Request) (*http.Response, error) { + path := req.URL.Path + scale, isScalePath := pathsResources[path] + if !isScalePath { + return nil, fmt.Errorf("unexpected request for URL %q with method %q", req.URL.String(), req.Method) + } + + switch req.Method { + case "GET": + res, err := json.Marshal(scale) + if err != nil { + return nil, err + } + return &http.Response{StatusCode: 200, Header: defaultHeaders(), Body: bytesBody(res)}, nil + case "PUT": + decoder := codecs.UniversalDeserializer() + body, err := ioutil.ReadAll(req.Body) + if err != nil { + return nil, err + } + newScale, newScaleGVK, err := decoder.Decode(body, nil, nil) + if err != nil { + return nil, fmt.Errorf("unexpected request body: %v", err) + } + if *newScaleGVK != scale.GetObjectKind().GroupVersionKind() { + return nil, fmt.Errorf("unexpected scale API version %s (expected %s)", newScaleGVK.String(), scale.GetObjectKind().GroupVersionKind().String()) + } + res, err := json.Marshal(newScale) + if err != nil { + return nil, err + } + + pathsResources[path] = newScale + return &http.Response{StatusCode: 200, Header: defaultHeaders(), Body: bytesBody(res)}, nil + default: + return nil, fmt.Errorf("unexpected request for URL %q with method %q", req.URL.String(), req.Method) + } + } + + fakeClient := &fakerest.RESTClient{ + Client: fakerest.CreateHTTPClient(fakeReqHandler), + NegotiatedSerializer: serializer.DirectCodecFactory{ + CodecFactory: serializer.NewCodecFactory(scale.NewScaleConverter().Scheme()), + }, + GroupVersion: schema.GroupVersion{}, + VersionedAPIPath: "/not/a/real/path", + } + + resolver := scale.NewDiscoveryScaleKindResolver(fakeDiscoveryClient) + client := scale.New(fakeClient, restMapper, dynamic.LegacyAPIPathResolverFunc, resolver) + return client, nil +} + +func bytesBody(bodyBytes []byte) io.ReadCloser { + return ioutil.NopCloser(bytes.NewReader(bodyBytes)) +} + +func defaultHeaders() http.Header { + header := http.Header{} + header.Set("Content-Type", runtime.ContentTypeJSON) + return header +} diff --git a/pkg/kubectl/scheme/BUILD b/pkg/kubectl/scheme/BUILD new file mode 100644 index 00000000000..cdd9333404b --- /dev/null +++ b/pkg/kubectl/scheme/BUILD @@ -0,0 +1,62 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "install.go", + "scheme.go", + ], + importpath = "k8s.io/kubernetes/pkg/kubectl/scheme", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/api/admission/v1beta1:go_default_library", + "//vendor/k8s.io/api/admissionregistration/v1alpha1:go_default_library", + "//vendor/k8s.io/api/apps/v1:go_default_library", + "//vendor/k8s.io/api/apps/v1beta1:go_default_library", + "//vendor/k8s.io/api/apps/v1beta2:go_default_library", + "//vendor/k8s.io/api/authentication/v1:go_default_library", + "//vendor/k8s.io/api/authentication/v1beta1:go_default_library", + "//vendor/k8s.io/api/authorization/v1:go_default_library", + "//vendor/k8s.io/api/authorization/v1beta1:go_default_library", + "//vendor/k8s.io/api/autoscaling/v1:go_default_library", + "//vendor/k8s.io/api/autoscaling/v2beta1:go_default_library", + "//vendor/k8s.io/api/batch/v1:go_default_library", + "//vendor/k8s.io/api/batch/v1beta1:go_default_library", + "//vendor/k8s.io/api/batch/v2alpha1:go_default_library", + "//vendor/k8s.io/api/certificates/v1beta1:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", + "//vendor/k8s.io/api/imagepolicy/v1alpha1:go_default_library", + "//vendor/k8s.io/api/networking/v1:go_default_library", + "//vendor/k8s.io/api/policy/v1beta1:go_default_library", + "//vendor/k8s.io/api/rbac/v1:go_default_library", + "//vendor/k8s.io/api/rbac/v1alpha1:go_default_library", + "//vendor/k8s.io/api/rbac/v1beta1:go_default_library", + "//vendor/k8s.io/api/scheduling/v1alpha1:go_default_library", + "//vendor/k8s.io/api/settings/v1alpha1:go_default_library", + "//vendor/k8s.io/api/storage/v1:go_default_library", + "//vendor/k8s.io/api/storage/v1beta1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/kubectl/scheme/install.go b/pkg/kubectl/scheme/install.go new file mode 100644 index 00000000000..51d4e16f18a --- /dev/null +++ b/pkg/kubectl/scheme/install.go @@ -0,0 +1,304 @@ +/* +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 scheme + +import ( + admissionv1alpha1 "k8s.io/api/admission/v1beta1" + admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1" + appsv1 "k8s.io/api/apps/v1" + appsv1beta1 "k8s.io/api/apps/v1beta1" + appsv1beta2 "k8s.io/api/apps/v1beta2" + authenticationv1 "k8s.io/api/authentication/v1" + authenticationv1beta1 "k8s.io/api/authentication/v1beta1" + authorizationv1 "k8s.io/api/authorization/v1" + authorizationv1beta1 "k8s.io/api/authorization/v1beta1" + autoscalingv1 "k8s.io/api/autoscaling/v1" + autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1" + batchv1 "k8s.io/api/batch/v1" + batchv1beta1 "k8s.io/api/batch/v1beta1" + batchv2alpha1 "k8s.io/api/batch/v2alpha1" + certificatesv1beta1 "k8s.io/api/certificates/v1beta1" + corev1 "k8s.io/api/core/v1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + imagepolicyv1alpha1 "k8s.io/api/imagepolicy/v1alpha1" + networkingv1 "k8s.io/api/networking/v1" + policyv1beta1 "k8s.io/api/policy/v1beta1" + rbacv1 "k8s.io/api/rbac/v1" + rbacv1alpha1 "k8s.io/api/rbac/v1alpha1" + rbacv1beta1 "k8s.io/api/rbac/v1beta1" + schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1" + settingsv1alpha1 "k8s.io/api/settings/v1alpha1" + storagev1 "k8s.io/api/storage/v1" + storagev1beta1 "k8s.io/api/storage/v1beta1" + "k8s.io/apimachinery/pkg/apimachinery/announced" + "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/kubernetes/scheme" +) + +// Register all groups in the kubectl's registry, but no componentconfig group since it's not in k8s.io/api +// The code in this file mostly duplicate the install under k8s.io/kubernetes/pkg/api and k8s.io/kubernetes/pkg/apis, +// but does NOT register the internal types. +func init() { + // Register external types for Scheme + v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) + scheme.AddToScheme(Scheme) + + // Register external types for Registry + if err := announced.NewGroupMetaFactory( + &announced.GroupMetaFactoryArgs{ + GroupName: corev1.GroupName, + VersionPreferenceOrder: []string{corev1.SchemeGroupVersion.Version}, + RootScopedKinds: sets.NewString( + "Node", + "Namespace", + "PersistentVolume", + "ComponentStatus", + ), + IgnoredKinds: sets.NewString( + "ListOptions", + "DeleteOptions", + "Status", + "PodLogOptions", + "PodExecOptions", + "PodAttachOptions", + "PodPortForwardOptions", + "PodProxyOptions", + "NodeProxyOptions", + "ServiceProxyOptions", + ), + }, + announced.VersionToSchemeFunc{ + corev1.SchemeGroupVersion.Version: corev1.AddToScheme, + }, + ).Announce(GroupFactoryRegistry).RegisterAndEnable(Registry, Scheme); err != nil { + panic(err) + } + + if err := announced.NewGroupMetaFactory( + &announced.GroupMetaFactoryArgs{ + GroupName: admissionv1alpha1.GroupName, + VersionPreferenceOrder: []string{admissionv1alpha1.SchemeGroupVersion.Version}, + RootScopedKinds: sets.NewString("AdmissionReview"), + }, + announced.VersionToSchemeFunc{ + admissionv1alpha1.SchemeGroupVersion.Version: admissionv1alpha1.AddToScheme, + }, + ).Announce(GroupFactoryRegistry).RegisterAndEnable(Registry, Scheme); err != nil { + panic(err) + } + + if err := announced.NewGroupMetaFactory( + &announced.GroupMetaFactoryArgs{ + GroupName: admissionregistrationv1alpha1.GroupName, + RootScopedKinds: sets.NewString("InitializerConfiguration", "ValidatingWebhookConfiguration", "MutatingWebhookConfiguration"), + VersionPreferenceOrder: []string{admissionregistrationv1alpha1.SchemeGroupVersion.Version}, + }, + announced.VersionToSchemeFunc{ + admissionregistrationv1alpha1.SchemeGroupVersion.Version: admissionregistrationv1alpha1.AddToScheme, + }, + ).Announce(GroupFactoryRegistry).RegisterAndEnable(Registry, Scheme); err != nil { + panic(err) + } + + if err := announced.NewGroupMetaFactory( + &announced.GroupMetaFactoryArgs{ + GroupName: appsv1.GroupName, + VersionPreferenceOrder: []string{appsv1beta1.SchemeGroupVersion.Version, appsv1beta2.SchemeGroupVersion.Version, appsv1.SchemeGroupVersion.Version}, + }, + announced.VersionToSchemeFunc{ + appsv1beta1.SchemeGroupVersion.Version: appsv1beta1.AddToScheme, + appsv1beta2.SchemeGroupVersion.Version: appsv1beta2.AddToScheme, + appsv1.SchemeGroupVersion.Version: appsv1.AddToScheme, + }, + ).Announce(GroupFactoryRegistry).RegisterAndEnable(Registry, Scheme); err != nil { + panic(err) + } + + if err := announced.NewGroupMetaFactory( + &announced.GroupMetaFactoryArgs{ + GroupName: authenticationv1beta1.GroupName, + VersionPreferenceOrder: []string{authenticationv1.SchemeGroupVersion.Version, authenticationv1beta1.SchemeGroupVersion.Version}, + RootScopedKinds: sets.NewString("TokenReview"), + }, + announced.VersionToSchemeFunc{ + authenticationv1beta1.SchemeGroupVersion.Version: authenticationv1beta1.AddToScheme, + authenticationv1.SchemeGroupVersion.Version: authenticationv1.AddToScheme, + }, + ).Announce(GroupFactoryRegistry).RegisterAndEnable(Registry, Scheme); err != nil { + panic(err) + } + + if err := announced.NewGroupMetaFactory( + &announced.GroupMetaFactoryArgs{ + GroupName: authorizationv1.GroupName, + VersionPreferenceOrder: []string{authorizationv1.SchemeGroupVersion.Version, authorizationv1beta1.SchemeGroupVersion.Version}, + RootScopedKinds: sets.NewString("SubjectAccessReview", "SelfSubjectAccessReview", "SelfSubjectRulesReview"), + }, + announced.VersionToSchemeFunc{ + authorizationv1beta1.SchemeGroupVersion.Version: authorizationv1beta1.AddToScheme, + authorizationv1.SchemeGroupVersion.Version: authorizationv1.AddToScheme, + }, + ).Announce(GroupFactoryRegistry).RegisterAndEnable(Registry, Scheme); err != nil { + panic(err) + } + + if err := announced.NewGroupMetaFactory( + &announced.GroupMetaFactoryArgs{ + GroupName: autoscalingv1.GroupName, + VersionPreferenceOrder: []string{autoscalingv1.SchemeGroupVersion.Version, autoscalingv2beta1.SchemeGroupVersion.Version}, + }, + announced.VersionToSchemeFunc{ + autoscalingv1.SchemeGroupVersion.Version: autoscalingv1.AddToScheme, + autoscalingv2beta1.SchemeGroupVersion.Version: autoscalingv2beta1.AddToScheme, + }, + ).Announce(GroupFactoryRegistry).RegisterAndEnable(Registry, Scheme); err != nil { + panic(err) + } + + if err := announced.NewGroupMetaFactory( + &announced.GroupMetaFactoryArgs{ + GroupName: batchv1.GroupName, + VersionPreferenceOrder: []string{batchv1.SchemeGroupVersion.Version, batchv1beta1.SchemeGroupVersion.Version, batchv2alpha1.SchemeGroupVersion.Version}, + }, + announced.VersionToSchemeFunc{ + batchv1.SchemeGroupVersion.Version: batchv1.AddToScheme, + batchv1beta1.SchemeGroupVersion.Version: batchv1beta1.AddToScheme, + batchv2alpha1.SchemeGroupVersion.Version: batchv2alpha1.AddToScheme, + }, + ).Announce(GroupFactoryRegistry).RegisterAndEnable(Registry, Scheme); err != nil { + panic(err) + } + + if err := announced.NewGroupMetaFactory( + &announced.GroupMetaFactoryArgs{ + GroupName: certificatesv1beta1.GroupName, + VersionPreferenceOrder: []string{certificatesv1beta1.SchemeGroupVersion.Version}, + RootScopedKinds: sets.NewString("CertificateSigningRequest"), + }, + announced.VersionToSchemeFunc{ + certificatesv1beta1.SchemeGroupVersion.Version: certificatesv1beta1.AddToScheme, + }, + ).Announce(GroupFactoryRegistry).RegisterAndEnable(Registry, Scheme); err != nil { + panic(err) + } + + if err := announced.NewGroupMetaFactory( + &announced.GroupMetaFactoryArgs{ + GroupName: extensionsv1beta1.GroupName, + VersionPreferenceOrder: []string{extensionsv1beta1.SchemeGroupVersion.Version}, + RootScopedKinds: sets.NewString("PodSecurityPolicy"), + }, + announced.VersionToSchemeFunc{ + extensionsv1beta1.SchemeGroupVersion.Version: extensionsv1beta1.AddToScheme, + }, + ).Announce(GroupFactoryRegistry).RegisterAndEnable(Registry, Scheme); err != nil { + panic(err) + } + + if err := announced.NewGroupMetaFactory( + &announced.GroupMetaFactoryArgs{ + GroupName: imagepolicyv1alpha1.GroupName, + VersionPreferenceOrder: []string{imagepolicyv1alpha1.SchemeGroupVersion.Version}, + RootScopedKinds: sets.NewString("ImageReview"), + }, + announced.VersionToSchemeFunc{ + imagepolicyv1alpha1.SchemeGroupVersion.Version: imagepolicyv1alpha1.AddToScheme, + }, + ).Announce(GroupFactoryRegistry).RegisterAndEnable(Registry, Scheme); err != nil { + panic(err) + } + + if err := announced.NewGroupMetaFactory( + &announced.GroupMetaFactoryArgs{ + GroupName: networkingv1.GroupName, + VersionPreferenceOrder: []string{networkingv1.SchemeGroupVersion.Version}, + }, + announced.VersionToSchemeFunc{ + networkingv1.SchemeGroupVersion.Version: networkingv1.AddToScheme, + }, + ).Announce(GroupFactoryRegistry).RegisterAndEnable(Registry, Scheme); err != nil { + panic(err) + } + + if err := announced.NewGroupMetaFactory( + &announced.GroupMetaFactoryArgs{ + GroupName: policyv1beta1.GroupName, + VersionPreferenceOrder: []string{policyv1beta1.SchemeGroupVersion.Version}, + }, + announced.VersionToSchemeFunc{ + policyv1beta1.SchemeGroupVersion.Version: policyv1beta1.AddToScheme, + }, + ).Announce(GroupFactoryRegistry).RegisterAndEnable(Registry, Scheme); err != nil { + panic(err) + } + + if err := announced.NewGroupMetaFactory( + &announced.GroupMetaFactoryArgs{ + GroupName: rbacv1.GroupName, + VersionPreferenceOrder: []string{rbacv1.SchemeGroupVersion.Version, rbacv1beta1.SchemeGroupVersion.Version, rbacv1alpha1.SchemeGroupVersion.Version}, + RootScopedKinds: sets.NewString("ClusterRole", "ClusterRoleBinding"), + }, + announced.VersionToSchemeFunc{ + rbacv1.SchemeGroupVersion.Version: rbacv1.AddToScheme, + rbacv1beta1.SchemeGroupVersion.Version: rbacv1beta1.AddToScheme, + rbacv1alpha1.SchemeGroupVersion.Version: rbacv1alpha1.AddToScheme, + }, + ).Announce(GroupFactoryRegistry).RegisterAndEnable(Registry, Scheme); err != nil { + panic(err) + } + + if err := announced.NewGroupMetaFactory( + &announced.GroupMetaFactoryArgs{ + GroupName: schedulingv1alpha1.GroupName, + VersionPreferenceOrder: []string{schedulingv1alpha1.SchemeGroupVersion.Version}, + RootScopedKinds: sets.NewString("PriorityClass"), + }, + announced.VersionToSchemeFunc{ + schedulingv1alpha1.SchemeGroupVersion.Version: schedulingv1alpha1.AddToScheme, + }, + ).Announce(GroupFactoryRegistry).RegisterAndEnable(Registry, Scheme); err != nil { + panic(err) + } + + if err := announced.NewGroupMetaFactory( + &announced.GroupMetaFactoryArgs{ + GroupName: settingsv1alpha1.GroupName, + VersionPreferenceOrder: []string{settingsv1alpha1.SchemeGroupVersion.Version}, + }, + announced.VersionToSchemeFunc{ + settingsv1alpha1.SchemeGroupVersion.Version: settingsv1alpha1.AddToScheme, + }, + ).Announce(GroupFactoryRegistry).RegisterAndEnable(Registry, Scheme); err != nil { + panic(err) + } + + if err := announced.NewGroupMetaFactory( + &announced.GroupMetaFactoryArgs{ + GroupName: storagev1.GroupName, + VersionPreferenceOrder: []string{storagev1.SchemeGroupVersion.Version, storagev1beta1.SchemeGroupVersion.Version}, + RootScopedKinds: sets.NewString("StorageClass"), + }, + announced.VersionToSchemeFunc{ + storagev1.SchemeGroupVersion.Version: storagev1.AddToScheme, + storagev1beta1.SchemeGroupVersion.Version: storagev1beta1.AddToScheme, + }, + ).Announce(GroupFactoryRegistry).RegisterAndEnable(Registry, Scheme); err != nil { + panic(err) + } +} diff --git a/pkg/kubectl/scheme/scheme.go b/pkg/kubectl/scheme/scheme.go new file mode 100644 index 00000000000..cd38d22a183 --- /dev/null +++ b/pkg/kubectl/scheme/scheme.go @@ -0,0 +1,43 @@ +/* +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 scheme + +import ( + "os" + + "k8s.io/apimachinery/pkg/apimachinery/announced" + "k8s.io/apimachinery/pkg/apimachinery/registered" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" +) + +// All kubectl code should eventually switch to use this Registry and Scheme instead of the global ones. + +// GroupFactoryRegistry is the APIGroupFactoryRegistry (overlaps a bit with Registry, see comments in package for details) +var GroupFactoryRegistry = make(announced.APIGroupFactoryRegistry) + +// Registry is an instance of an API registry. +var Registry = registered.NewOrDie(os.Getenv("KUBE_API_VERSIONS")) + +// Scheme is the default instance of runtime.Scheme to which types in the Kubernetes API are already registered. +var Scheme = runtime.NewScheme() + +// Codecs provides access to encoding and decoding for the scheme +var Codecs = serializer.NewCodecFactory(Scheme) + +// ParameterCodec handles versioning of objects that are converted to query parameters. +var ParameterCodec = runtime.NewParameterCodec(Scheme) diff --git a/pkg/kubectl/secret.go b/pkg/kubectl/secret.go index 6ad8e7a2593..04c5ced232e 100644 --- a/pkg/kubectl/secret.go +++ b/pkg/kubectl/secret.go @@ -23,9 +23,9 @@ import ( "path" "strings" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/kubectl/util" "k8s.io/kubernetes/pkg/kubectl/util/hash" ) @@ -129,11 +129,11 @@ func (s SecretGeneratorV1) StructuredGenerate() (runtime.Object, error) { if err := s.validate(); err != nil { return nil, err } - secret := &api.Secret{} + secret := &v1.Secret{} secret.Name = s.Name secret.Data = map[string][]byte{} if len(s.Type) > 0 { - secret.Type = api.SecretType(s.Type) + secret.Type = v1.SecretType(s.Type) } if len(s.FileSources) > 0 { if err := handleFromFileSources(secret, s.FileSources); err != nil { @@ -172,7 +172,7 @@ func (s SecretGeneratorV1) validate() error { } // handleFromLiteralSources adds the specified literal source information into the provided secret -func handleFromLiteralSources(secret *api.Secret, literalSources []string) error { +func handleFromLiteralSources(secret *v1.Secret, literalSources []string) error { for _, literalSource := range literalSources { keyName, value, err := util.ParseLiteralSource(literalSource) if err != nil { @@ -186,7 +186,7 @@ func handleFromLiteralSources(secret *api.Secret, literalSources []string) error } // handleFromFileSources adds the specified file source information into the provided secret -func handleFromFileSources(secret *api.Secret, fileSources []string) error { +func handleFromFileSources(secret *v1.Secret, fileSources []string) error { for _, fileSource := range fileSources { keyName, filePath, err := util.ParseFileSource(fileSource) if err != nil { @@ -230,7 +230,7 @@ func handleFromFileSources(secret *api.Secret, fileSources []string) error { // handleFromEnvFileSource adds the specified env file source information // into the provided secret -func handleFromEnvFileSource(secret *api.Secret, envFileSource string) error { +func handleFromEnvFileSource(secret *v1.Secret, envFileSource string) error { info, err := os.Stat(envFileSource) if err != nil { switch err := err.(type) { @@ -249,7 +249,7 @@ func handleFromEnvFileSource(secret *api.Secret, envFileSource string) error { }) } -func addKeyFromFileToSecret(secret *api.Secret, keyName, filePath string) error { +func addKeyFromFileToSecret(secret *v1.Secret, keyName, filePath string) error { data, err := ioutil.ReadFile(filePath) if err != nil { return err @@ -257,7 +257,7 @@ func addKeyFromFileToSecret(secret *api.Secret, keyName, filePath string) error return addKeyFromLiteralToSecret(secret, keyName, data) } -func addKeyFromLiteralToSecret(secret *api.Secret, keyName string, data []byte) error { +func addKeyFromLiteralToSecret(secret *v1.Secret, keyName string, data []byte) error { if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 { return fmt.Errorf("%q is not a valid key name for a Secret: %s", keyName, strings.Join(errs, ";")) } diff --git a/pkg/kubectl/secret_for_docker_registry.go b/pkg/kubectl/secret_for_docker_registry.go index d39ebb5b32b..cad90cfe1a2 100644 --- a/pkg/kubectl/secret_for_docker_registry.go +++ b/pkg/kubectl/secret_for_docker_registry.go @@ -20,8 +20,8 @@ import ( "encoding/json" "fmt" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/credentialprovider" "k8s.io/kubernetes/pkg/kubectl/util/hash" ) @@ -85,15 +85,15 @@ func (s SecretForDockerRegistryGeneratorV1) StructuredGenerate() (runtime.Object if err := s.validate(); err != nil { return nil, err } - dockercfgContent, err := handleDockercfgContent(s.Username, s.Password, s.Email, s.Server) + dockercfgJsonContent, err := handleDockerCfgJsonContent(s.Username, s.Password, s.Email, s.Server) if err != nil { return nil, err } - secret := &api.Secret{} + secret := &v1.Secret{} secret.Name = s.Name - secret.Type = api.SecretTypeDockercfg + secret.Type = v1.SecretTypeDockerConfigJson secret.Data = map[string][]byte{} - secret.Data[api.DockerConfigKey] = dockercfgContent + secret.Data[v1.DockerConfigJsonKey] = dockercfgJsonContent if s.AppendHash { h, err := hash.SecretHash(secret) if err != nil { @@ -133,15 +133,17 @@ func (s SecretForDockerRegistryGeneratorV1) validate() error { return nil } -// handleDockercfgContent serializes a dockercfg json file -func handleDockercfgContent(username, password, email, server string) ([]byte, error) { +// handleDockerCfgJsonContent serializes a ~/.docker/config.json file +func handleDockerCfgJsonContent(username, password, email, server string) ([]byte, error) { dockercfgAuth := credentialprovider.DockerConfigEntry{ Username: username, Password: password, Email: email, } - dockerCfg := map[string]credentialprovider.DockerConfigEntry{server: dockercfgAuth} + dockerCfgJson := credentialprovider.DockerConfigJson{ + Auths: map[string]credentialprovider.DockerConfigEntry{server: dockercfgAuth}, + } - return json.Marshal(dockerCfg) + return json.Marshal(dockerCfgJson) } diff --git a/pkg/kubectl/secret_for_docker_registry_test.go b/pkg/kubectl/secret_for_docker_registry_test.go index e1621cbefa5..844acc3bccc 100644 --- a/pkg/kubectl/secret_for_docker_registry_test.go +++ b/pkg/kubectl/secret_for_docker_registry_test.go @@ -20,24 +20,24 @@ import ( "reflect" "testing" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" ) func TestSecretForDockerRegistryGenerate(t *testing.T) { username, password, email, server := "test-user", "test-password", "test-user@example.org", "https://index.docker.io/v1/" - secretData, err := handleDockercfgContent(username, password, email, server) + secretData, err := handleDockerCfgJsonContent(username, password, email, server) if err != nil { t.Errorf("unexpected error: %v", err) } - secretDataNoEmail, err := handleDockercfgContent(username, password, "", server) + secretDataNoEmail, err := handleDockerCfgJsonContent(username, password, "", server) if err != nil { t.Errorf("unexpected error: %v", err) } tests := map[string]struct { params map[string]interface{} - expected *api.Secret + expected *v1.Secret expectErr bool }{ "test-valid-use": { @@ -48,14 +48,14 @@ func TestSecretForDockerRegistryGenerate(t *testing.T) { "docker-password": password, "docker-email": email, }, - expected: &api.Secret{ + expected: &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, Data: map[string][]byte{ - api.DockerConfigKey: secretData, + v1.DockerConfigJsonKey: secretData, }, - Type: api.SecretTypeDockercfg, + Type: v1.SecretTypeDockerConfigJson, }, expectErr: false, }, @@ -68,14 +68,14 @@ func TestSecretForDockerRegistryGenerate(t *testing.T) { "docker-email": email, "append-hash": true, }, - expected: &api.Secret{ + expected: &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: "foo-gb4kftc655", + Name: "foo-548cm7fgdh", }, Data: map[string][]byte{ - api.DockerConfigKey: secretData, + v1.DockerConfigJsonKey: secretData, }, - Type: api.SecretTypeDockercfg, + Type: v1.SecretTypeDockerConfigJson, }, expectErr: false, }, @@ -86,14 +86,14 @@ func TestSecretForDockerRegistryGenerate(t *testing.T) { "docker-username": username, "docker-password": password, }, - expected: &api.Secret{ + expected: &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, Data: map[string][]byte{ - api.DockerConfigKey: secretDataNoEmail, + v1.DockerConfigJsonKey: secretDataNoEmail, }, - Type: api.SecretTypeDockercfg, + Type: v1.SecretTypeDockerConfigJson, }, expectErr: false, }, @@ -117,8 +117,8 @@ func TestSecretForDockerRegistryGenerate(t *testing.T) { if test.expectErr && err != nil { continue } - if !reflect.DeepEqual(obj.(*api.Secret), test.expected) { - t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*api.Secret)) + if !reflect.DeepEqual(obj.(*v1.Secret), test.expected) { + t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*v1.Secret)) } } } diff --git a/pkg/kubectl/secret_for_tls.go b/pkg/kubectl/secret_for_tls.go index aeb6d8863a9..1c3fffd0456 100644 --- a/pkg/kubectl/secret_for_tls.go +++ b/pkg/kubectl/secret_for_tls.go @@ -21,8 +21,8 @@ import ( "fmt" "io/ioutil" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/kubectl/util/hash" ) @@ -87,12 +87,12 @@ func (s SecretForTLSGeneratorV1) StructuredGenerate() (runtime.Object, error) { if err != nil { return nil, err } - secret := &api.Secret{} + secret := &v1.Secret{} secret.Name = s.Name - secret.Type = api.SecretTypeTLS + secret.Type = v1.SecretTypeTLS secret.Data = map[string][]byte{} - secret.Data[api.TLSCertKey] = []byte(tlsCrt) - secret.Data[api.TLSPrivateKeyKey] = []byte(tlsKey) + secret.Data[v1.TLSCertKey] = []byte(tlsCrt) + secret.Data[v1.TLSPrivateKeyKey] = []byte(tlsKey) if s.AppendHash { h, err := hash.SecretHash(secret) if err != nil { diff --git a/pkg/kubectl/secret_for_tls_test.go b/pkg/kubectl/secret_for_tls_test.go index 252f84913e0..9f296a88840 100644 --- a/pkg/kubectl/secret_for_tls_test.go +++ b/pkg/kubectl/secret_for_tls_test.go @@ -23,9 +23,9 @@ import ( "reflect" "testing" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utiltesting "k8s.io/client-go/util/testing" - "k8s.io/kubernetes/pkg/api" ) var rsaCertPEM = `-----BEGIN CERTIFICATE----- @@ -124,7 +124,7 @@ func TestSecretForTLSGenerate(t *testing.T) { tests := map[string]struct { params map[string]interface{} - expected *api.Secret + expected *v1.Secret expectErr bool }{ "test-valid-tls-secret": { @@ -133,15 +133,15 @@ func TestSecretForTLSGenerate(t *testing.T) { "key": validKeyPath, "cert": validCertPath, }, - expected: &api.Secret{ + expected: &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, Data: map[string][]byte{ - api.TLSCertKey: []byte(rsaCertPEM), - api.TLSPrivateKeyKey: []byte(rsaKeyPEM), + v1.TLSCertKey: []byte(rsaCertPEM), + v1.TLSPrivateKeyKey: []byte(rsaKeyPEM), }, - Type: api.SecretTypeTLS, + Type: v1.SecretTypeTLS, }, expectErr: false, }, @@ -152,15 +152,15 @@ func TestSecretForTLSGenerate(t *testing.T) { "cert": validCertPath, "append-hash": true, }, - expected: &api.Secret{ + expected: &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "foo-272h6tt825", }, Data: map[string][]byte{ - api.TLSCertKey: []byte(rsaCertPEM), - api.TLSPrivateKeyKey: []byte(rsaKeyPEM), + v1.TLSCertKey: []byte(rsaCertPEM), + v1.TLSPrivateKeyKey: []byte(rsaKeyPEM), }, - Type: api.SecretTypeTLS, + Type: v1.SecretTypeTLS, }, expectErr: false, }, @@ -170,15 +170,15 @@ func TestSecretForTLSGenerate(t *testing.T) { "key": invalidKeyPath, "cert": invalidCertPath, }, - expected: &api.Secret{ + expected: &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, Data: map[string][]byte{ - api.TLSCertKey: []byte("test"), - api.TLSPrivateKeyKey: []byte("test"), + v1.TLSCertKey: []byte("test"), + v1.TLSPrivateKeyKey: []byte("test"), }, - Type: api.SecretTypeTLS, + Type: v1.SecretTypeTLS, }, expectErr: true, }, @@ -188,15 +188,15 @@ func TestSecretForTLSGenerate(t *testing.T) { "key": mismatchKeyPath, "cert": mismatchCertPath, }, - expected: &api.Secret{ + expected: &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, Data: map[string][]byte{ - api.TLSCertKey: []byte(rsaCertPEM), - api.TLSPrivateKeyKey: []byte(mismatchRSAKeyPEM), + v1.TLSCertKey: []byte(rsaCertPEM), + v1.TLSPrivateKeyKey: []byte(mismatchRSAKeyPEM), }, - Type: api.SecretTypeTLS, + Type: v1.SecretTypeTLS, }, expectErr: true, }, @@ -218,8 +218,8 @@ func TestSecretForTLSGenerate(t *testing.T) { if test.expectErr && err != nil { continue } - if !reflect.DeepEqual(obj.(*api.Secret), test.expected) { - t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*api.Secret)) + if !reflect.DeepEqual(obj.(*v1.Secret), test.expected) { + t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*v1.Secret)) } } } diff --git a/pkg/kubectl/secret_test.go b/pkg/kubectl/secret_test.go index a9777cd1bac..15f3db0b4ff 100644 --- a/pkg/kubectl/secret_test.go +++ b/pkg/kubectl/secret_test.go @@ -21,22 +21,22 @@ import ( "reflect" "testing" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" ) func TestSecretGenerate(t *testing.T) { tests := []struct { setup func(t *testing.T, params map[string]interface{}) func() params map[string]interface{} - expected *api.Secret + expected *v1.Secret expectErr bool }{ { params: map[string]interface{}{ "name": "foo", }, - expected: &api.Secret{ + expected: &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, @@ -49,7 +49,7 @@ func TestSecretGenerate(t *testing.T) { "name": "foo", "append-hash": true, }, - expected: &api.Secret{ + expected: &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "foo-949tdgdkgg", }, @@ -62,7 +62,7 @@ func TestSecretGenerate(t *testing.T) { "name": "foo", "type": "my-type", }, - expected: &api.Secret{ + expected: &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, @@ -77,7 +77,7 @@ func TestSecretGenerate(t *testing.T) { "type": "my-type", "append-hash": true, }, - expected: &api.Secret{ + expected: &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "foo-dg474f9t76", }, @@ -91,7 +91,7 @@ func TestSecretGenerate(t *testing.T) { "name": "foo", "from-literal": []string{"key1=value1", "key2=value2"}, }, - expected: &api.Secret{ + expected: &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, @@ -108,7 +108,7 @@ func TestSecretGenerate(t *testing.T) { "from-literal": []string{"key1=value1", "key2=value2"}, "append-hash": true, }, - expected: &api.Secret{ + expected: &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "foo-tf72c228m4", }, @@ -145,7 +145,7 @@ func TestSecretGenerate(t *testing.T) { "name": "foo", "from-literal": []string{"key1==value1"}, }, - expected: &api.Secret{ + expected: &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, @@ -161,7 +161,7 @@ func TestSecretGenerate(t *testing.T) { "from-literal": []string{"key1==value1"}, "append-hash": true, }, - expected: &api.Secret{ + expected: &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "foo-fdcc8tkhh5", }, @@ -177,7 +177,7 @@ func TestSecretGenerate(t *testing.T) { "name": "valid_env", "from-env-file": "file.env", }, - expected: &api.Secret{ + expected: &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "valid_env", }, @@ -195,7 +195,7 @@ func TestSecretGenerate(t *testing.T) { "from-env-file": "file.env", "append-hash": true, }, - expected: &api.Secret{ + expected: &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "valid_env-bkb2m2965h", }, @@ -216,7 +216,7 @@ func TestSecretGenerate(t *testing.T) { "name": "getenv", "from-env-file": "file.env", }, - expected: &api.Secret{ + expected: &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "getenv", }, @@ -238,7 +238,7 @@ func TestSecretGenerate(t *testing.T) { "from-env-file": "file.env", "append-hash": true, }, - expected: &api.Secret{ + expected: &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "getenv-m7kg2khdb4", }, @@ -271,7 +271,7 @@ func TestSecretGenerate(t *testing.T) { "name": "with_spaces", "from-env-file": "file.env", }, - expected: &api.Secret{ + expected: &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "with_spaces", }, @@ -288,7 +288,7 @@ func TestSecretGenerate(t *testing.T) { "from-env-file": "file.env", "append-hash": true, }, - expected: &api.Secret{ + expected: &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "with_spaces-4488d5b57d", }, @@ -314,8 +314,8 @@ func TestSecretGenerate(t *testing.T) { if test.expectErr && err != nil { continue } - if !reflect.DeepEqual(obj.(*api.Secret), test.expected) { - t.Errorf("\ncase %d, expected:\n%#v\nsaw:\n%#v", i, test.expected, obj.(*api.Secret)) + if !reflect.DeepEqual(obj.(*v1.Secret), test.expected) { + t.Errorf("\ncase %d, expected:\n%#v\nsaw:\n%#v", i, test.expected, obj.(*v1.Secret)) } } } diff --git a/pkg/kubectl/service.go b/pkg/kubectl/service.go index 072bddbeb03..7f0e0d869f7 100644 --- a/pkg/kubectl/service.go +++ b/pkg/kubectl/service.go @@ -21,10 +21,10 @@ import ( "strconv" "strings" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/kubernetes/pkg/api" ) // The only difference between ServiceGeneratorV1 and V2 is that the service port is named "default" in V1, while it is left unnamed in V2. @@ -113,7 +113,7 @@ func generate(genericParams map[string]interface{}) (runtime.Object, error) { isHeadlessService := params["cluster-ip"] == "None" - ports := []api.ServicePort{} + ports := []v1.ServicePort{} servicePortName, found := params["port-name"] if !found { // Leave the port unnamed. @@ -135,7 +135,7 @@ func generate(genericParams map[string]interface{}) (runtime.Object, error) { if portString, found = params["ports"]; !found { portString, found = params["port"] if !found && !isHeadlessService { - return nil, fmt.Errorf("'port' is a required parameter.") + return nil, fmt.Errorf("'ports' or 'port' is a required parameter.") } } @@ -168,20 +168,20 @@ func generate(genericParams map[string]interface{}) (runtime.Object, error) { protocol = exposeProtocol } } - ports = append(ports, api.ServicePort{ + ports = append(ports, v1.ServicePort{ Name: name, Port: int32(port), - Protocol: api.Protocol(protocol), + Protocol: v1.Protocol(protocol), }) } } - service := api.Service{ + service := v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: name, Labels: labels, }, - Spec: api.ServiceSpec{ + Spec: v1.ServiceSpec{ Selector: selector, Ports: ports, }, @@ -213,24 +213,24 @@ func generate(genericParams map[string]interface{}) (runtime.Object, error) { service.Spec.ExternalIPs = []string{params["external-ip"]} } if len(params["type"]) != 0 { - service.Spec.Type = api.ServiceType(params["type"]) + service.Spec.Type = v1.ServiceType(params["type"]) } - if service.Spec.Type == api.ServiceTypeLoadBalancer { + if service.Spec.Type == v1.ServiceTypeLoadBalancer { service.Spec.LoadBalancerIP = params["load-balancer-ip"] } if len(params["session-affinity"]) != 0 { - switch api.ServiceAffinity(params["session-affinity"]) { - case api.ServiceAffinityNone: - service.Spec.SessionAffinity = api.ServiceAffinityNone - case api.ServiceAffinityClientIP: - service.Spec.SessionAffinity = api.ServiceAffinityClientIP + switch v1.ServiceAffinity(params["session-affinity"]) { + case v1.ServiceAffinityNone: + service.Spec.SessionAffinity = v1.ServiceAffinityNone + case v1.ServiceAffinityClientIP: + service.Spec.SessionAffinity = v1.ServiceAffinityClientIP default: return nil, fmt.Errorf("unknown session affinity: %s", params["session-affinity"]) } } if len(params["cluster-ip"]) != 0 { if params["cluster-ip"] == "None" { - service.Spec.ClusterIP = api.ClusterIPNone + service.Spec.ClusterIP = v1.ClusterIPNone } else { service.Spec.ClusterIP = params["cluster-ip"] } diff --git a/pkg/kubectl/service_basic.go b/pkg/kubectl/service_basic.go index d53cc60fcd2..1f31bd09a2a 100644 --- a/pkg/kubectl/service_basic.go +++ b/pkg/kubectl/service_basic.go @@ -21,17 +21,17 @@ import ( "strconv" "strings" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/validation" - "k8s.io/kubernetes/pkg/api" ) type ServiceCommonGeneratorV1 struct { Name string TCP []string - Type api.ServiceType + Type v1.ServiceType ClusterIP string NodePort int ExternalName string @@ -89,14 +89,25 @@ func parsePorts(portString string) (int32, intstr.IntOrString, error) { if err != nil { return 0, intstr.FromInt(0), err } + + if errs := validation.IsValidPortNum(port); len(errs) != 0 { + return 0, intstr.FromInt(0), fmt.Errorf(strings.Join(errs, ",")) + } + if len(portStringSlice) == 1 { return int32(port), intstr.FromInt(int(port)), nil } var targetPort intstr.IntOrString if portNum, err := strconv.Atoi(portStringSlice[1]); err != nil { + if errs := validation.IsValidPortName(portStringSlice[1]); len(errs) != 0 { + return 0, intstr.FromInt(0), fmt.Errorf(strings.Join(errs, ",")) + } targetPort = intstr.FromString(portStringSlice[1]) } else { + if errs := validation.IsValidPortNum(portNum); len(errs) != 0 { + return 0, intstr.FromInt(0), fmt.Errorf(strings.Join(errs, ",")) + } targetPort = intstr.FromInt(portNum) } return int32(port), targetPort, nil @@ -131,7 +142,7 @@ func (s ServiceLoadBalancerGeneratorV1) Generate(params map[string]interface{}) if err != nil { return nil, err } - delegate := &ServiceCommonGeneratorV1{Type: api.ServiceTypeLoadBalancer, ClusterIP: ""} + delegate := &ServiceCommonGeneratorV1{Type: v1.ServiceTypeLoadBalancer, ClusterIP: ""} err = delegate.GenerateCommon(params) if err != nil { return nil, err @@ -144,7 +155,7 @@ func (s ServiceNodePortGeneratorV1) Generate(params map[string]interface{}) (run if err != nil { return nil, err } - delegate := &ServiceCommonGeneratorV1{Type: api.ServiceTypeNodePort, ClusterIP: ""} + delegate := &ServiceCommonGeneratorV1{Type: v1.ServiceTypeNodePort, ClusterIP: ""} err = delegate.GenerateCommon(params) if err != nil { return nil, err @@ -157,7 +168,7 @@ func (s ServiceClusterIPGeneratorV1) Generate(params map[string]interface{}) (ru if err != nil { return nil, err } - delegate := &ServiceCommonGeneratorV1{Type: api.ServiceTypeClusterIP, ClusterIP: ""} + delegate := &ServiceCommonGeneratorV1{Type: v1.ServiceTypeClusterIP, ClusterIP: ""} err = delegate.GenerateCommon(params) if err != nil { return nil, err @@ -170,7 +181,7 @@ func (s ServiceExternalNameGeneratorV1) Generate(params map[string]interface{}) if err != nil { return nil, err } - delegate := &ServiceCommonGeneratorV1{Type: api.ServiceTypeExternalName, ClusterIP: ""} + delegate := &ServiceCommonGeneratorV1{Type: v1.ServiceTypeExternalName, ClusterIP: ""} err = delegate.GenerateCommon(params) if err != nil { return nil, err @@ -187,13 +198,13 @@ func (s ServiceCommonGeneratorV1) validate() error { if len(s.Type) == 0 { return fmt.Errorf("type must be specified") } - if s.ClusterIP == api.ClusterIPNone && s.Type != api.ServiceTypeClusterIP { + if s.ClusterIP == v1.ClusterIPNone && s.Type != v1.ServiceTypeClusterIP { return fmt.Errorf("ClusterIP=None can only be used with ClusterIP service type") } - if s.ClusterIP != api.ClusterIPNone && len(s.TCP) == 0 && s.Type != api.ServiceTypeExternalName { + if s.ClusterIP != v1.ClusterIPNone && len(s.TCP) == 0 && s.Type != v1.ServiceTypeExternalName { return fmt.Errorf("at least one tcp port specifier must be provided") } - if s.Type == api.ServiceTypeExternalName { + if s.Type == v1.ServiceTypeExternalName { if errs := validation.IsDNS1123Subdomain(s.ExternalName); len(errs) != 0 { return fmt.Errorf("invalid service external name %s", s.ExternalName) } @@ -206,7 +217,7 @@ func (s ServiceCommonGeneratorV1) StructuredGenerate() (runtime.Object, error) { if err != nil { return nil, err } - ports := []api.ServicePort{} + ports := []v1.ServicePort{} for _, tcpString := range s.TCP { port, targetPort, err := parsePorts(tcpString) if err != nil { @@ -214,11 +225,11 @@ func (s ServiceCommonGeneratorV1) StructuredGenerate() (runtime.Object, error) { } portName := strings.Replace(tcpString, ":", "-", -1) - ports = append(ports, api.ServicePort{ + ports = append(ports, v1.ServicePort{ Name: portName, Port: port, TargetPort: targetPort, - Protocol: api.Protocol("TCP"), + Protocol: v1.Protocol("TCP"), NodePort: int32(s.NodePort), }) } @@ -229,13 +240,13 @@ func (s ServiceCommonGeneratorV1) StructuredGenerate() (runtime.Object, error) { selector := map[string]string{} selector["app"] = s.Name - service := api.Service{ + service := v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: s.Name, Labels: labels, }, - Spec: api.ServiceSpec{ - Type: api.ServiceType(s.Type), + Spec: v1.ServiceSpec{ + Type: v1.ServiceType(s.Type), Selector: selector, Ports: ports, ExternalName: s.ExternalName, diff --git a/pkg/kubectl/service_basic_test.go b/pkg/kubectl/service_basic_test.go index a7fea486085..c7f9ff71780 100644 --- a/pkg/kubectl/service_basic_test.go +++ b/pkg/kubectl/service_basic_test.go @@ -20,32 +20,32 @@ import ( "reflect" "testing" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/kubernetes/pkg/api" ) func TestServiceBasicGenerate(t *testing.T) { tests := []struct { name string - serviceType api.ServiceType + serviceType v1.ServiceType tcp []string clusterip string - expected *api.Service + expected *v1.Service expectErr bool }{ { name: "clusterip-ok", tcp: []string{"456", "321:908"}, clusterip: "", - serviceType: api.ServiceTypeClusterIP, - expected: &api.Service{ + serviceType: v1.ServiceTypeClusterIP, + expected: &v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "clusterip-ok", Labels: map[string]string{"app": "clusterip-ok"}, }, - Spec: api.ServiceSpec{Type: "ClusterIP", - Ports: []api.ServicePort{{Name: "456", Protocol: "TCP", Port: 456, TargetPort: intstr.IntOrString{Type: 0, IntVal: 456, StrVal: ""}, NodePort: 0}, + Spec: v1.ServiceSpec{Type: "ClusterIP", + Ports: []v1.ServicePort{{Name: "456", Protocol: "TCP", Port: 456, TargetPort: intstr.IntOrString{Type: 0, IntVal: 456, StrVal: ""}, NodePort: 0}, {Name: "321-908", Protocol: "TCP", Port: 321, TargetPort: intstr.IntOrString{Type: 0, IntVal: 908, StrVal: ""}, NodePort: 0}}, Selector: map[string]string{"app": "clusterip-ok"}, ClusterIP: "", ExternalIPs: []string(nil), LoadBalancerIP: ""}, @@ -54,28 +54,28 @@ func TestServiceBasicGenerate(t *testing.T) { }, { name: "clusterip-missing", - serviceType: api.ServiceTypeClusterIP, + serviceType: v1.ServiceTypeClusterIP, expectErr: true, }, { name: "clusterip-none-wrong-type", tcp: []string{}, clusterip: "None", - serviceType: api.ServiceTypeNodePort, + serviceType: v1.ServiceTypeNodePort, expectErr: true, }, { name: "clusterip-none-ok", tcp: []string{}, clusterip: "None", - serviceType: api.ServiceTypeClusterIP, - expected: &api.Service{ + serviceType: v1.ServiceTypeClusterIP, + expected: &v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "clusterip-none-ok", Labels: map[string]string{"app": "clusterip-none-ok"}, }, - Spec: api.ServiceSpec{Type: "ClusterIP", - Ports: []api.ServicePort{}, + Spec: v1.ServiceSpec{Type: "ClusterIP", + Ports: []v1.ServicePort{}, Selector: map[string]string{"app": "clusterip-none-ok"}, ClusterIP: "None", ExternalIPs: []string(nil), LoadBalancerIP: ""}, }, @@ -85,14 +85,14 @@ func TestServiceBasicGenerate(t *testing.T) { name: "clusterip-none-and-port-mapping", tcp: []string{"456:9898"}, clusterip: "None", - serviceType: api.ServiceTypeClusterIP, - expected: &api.Service{ + serviceType: v1.ServiceTypeClusterIP, + expected: &v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "clusterip-none-and-port-mapping", Labels: map[string]string{"app": "clusterip-none-and-port-mapping"}, }, - Spec: api.ServiceSpec{Type: "ClusterIP", - Ports: []api.ServicePort{{Name: "456-9898", Protocol: "TCP", Port: 456, TargetPort: intstr.IntOrString{Type: 0, IntVal: 9898, StrVal: ""}, NodePort: 0}}, + Spec: v1.ServiceSpec{Type: "ClusterIP", + Ports: []v1.ServicePort{{Name: "456-9898", Protocol: "TCP", Port: 456, TargetPort: intstr.IntOrString{Type: 0, IntVal: 9898, StrVal: ""}, NodePort: 0}}, Selector: map[string]string{"app": "clusterip-none-and-port-mapping"}, ClusterIP: "None", ExternalIPs: []string(nil), LoadBalancerIP: ""}, }, @@ -102,19 +102,33 @@ func TestServiceBasicGenerate(t *testing.T) { name: "loadbalancer-ok", tcp: []string{"456:9898"}, clusterip: "", - serviceType: api.ServiceTypeLoadBalancer, - expected: &api.Service{ + serviceType: v1.ServiceTypeLoadBalancer, + expected: &v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "loadbalancer-ok", Labels: map[string]string{"app": "loadbalancer-ok"}, }, - Spec: api.ServiceSpec{Type: "LoadBalancer", - Ports: []api.ServicePort{{Name: "456-9898", Protocol: "TCP", Port: 456, TargetPort: intstr.IntOrString{Type: 0, IntVal: 9898, StrVal: ""}, NodePort: 0}}, + Spec: v1.ServiceSpec{Type: "LoadBalancer", + Ports: []v1.ServicePort{{Name: "456-9898", Protocol: "TCP", Port: 456, TargetPort: intstr.IntOrString{Type: 0, IntVal: 9898, StrVal: ""}, NodePort: 0}}, Selector: map[string]string{"app": "loadbalancer-ok"}, ClusterIP: "", ExternalIPs: []string(nil), LoadBalancerIP: ""}, }, expectErr: false, }, + { + name: "invalid-port", + tcp: []string{"65536"}, + clusterip: "None", + serviceType: v1.ServiceTypeClusterIP, + expectErr: true, + }, + { + name: "invalid-port-mapping", + tcp: []string{"8080:-abc"}, + clusterip: "None", + serviceType: v1.ServiceTypeClusterIP, + expectErr: true, + }, { expectErr: true, }, @@ -133,8 +147,8 @@ func TestServiceBasicGenerate(t *testing.T) { if test.expectErr && err != nil { continue } - if !reflect.DeepEqual(obj.(*api.Service), test.expected) { - t.Errorf("test: %v\nexpected:\n%#v\nsaw:\n%#v", test.name, test.expected, obj.(*api.Service)) + if !reflect.DeepEqual(obj.(*v1.Service), test.expected) { + t.Errorf("test: %v\nexpected:\n%#v\nsaw:\n%#v", test.name, test.expected, obj.(*v1.Service)) } } } diff --git a/pkg/kubectl/service_test.go b/pkg/kubectl/service_test.go index bd80422ea31..0120c3557b5 100644 --- a/pkg/kubectl/service_test.go +++ b/pkg/kubectl/service_test.go @@ -20,16 +20,16 @@ import ( "reflect" "testing" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/kubernetes/pkg/api" ) func TestGenerateService(t *testing.T) { tests := []struct { generator Generator params map[string]interface{} - expected api.Service + expected v1.Service }{ { generator: ServiceGeneratorV2{}, @@ -40,16 +40,16 @@ func TestGenerateService(t *testing.T) { "protocol": "TCP", "container-port": "1234", }, - expected: api.Service{ + expected: v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "test", }, - Spec: api.ServiceSpec{ + Spec: v1.ServiceSpec{ Selector: map[string]string{ "foo": "bar", "baz": "blah", }, - Ports: []api.ServicePort{ + Ports: []v1.ServicePort{ { Port: 80, Protocol: "TCP", @@ -69,16 +69,16 @@ func TestGenerateService(t *testing.T) { "protocol": "UDP", "container-port": "foobar", }, - expected: api.Service{ + expected: v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "test", }, - Spec: api.ServiceSpec{ + Spec: v1.ServiceSpec{ Selector: map[string]string{ "foo": "bar", "baz": "blah", }, - Ports: []api.ServicePort{ + Ports: []v1.ServicePort{ { Port: 80, Protocol: "UDP", @@ -98,7 +98,7 @@ func TestGenerateService(t *testing.T) { "protocol": "TCP", "container-port": "1234", }, - expected: api.Service{ + expected: v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "test", Labels: map[string]string{ @@ -106,12 +106,12 @@ func TestGenerateService(t *testing.T) { "key2": "value2", }, }, - Spec: api.ServiceSpec{ + Spec: v1.ServiceSpec{ Selector: map[string]string{ "foo": "bar", "baz": "blah", }, - Ports: []api.ServicePort{ + Ports: []v1.ServicePort{ { Port: 80, Protocol: "TCP", @@ -131,16 +131,16 @@ func TestGenerateService(t *testing.T) { "container-port": "foobar", "external-ip": "1.2.3.4", }, - expected: api.Service{ + expected: v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "test", }, - Spec: api.ServiceSpec{ + Spec: v1.ServiceSpec{ Selector: map[string]string{ "foo": "bar", "baz": "blah", }, - Ports: []api.ServicePort{ + Ports: []v1.ServicePort{ { Port: 80, Protocol: "UDP", @@ -162,23 +162,23 @@ func TestGenerateService(t *testing.T) { "external-ip": "1.2.3.4", "type": "LoadBalancer", }, - expected: api.Service{ + expected: v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "test", }, - Spec: api.ServiceSpec{ + Spec: v1.ServiceSpec{ Selector: map[string]string{ "foo": "bar", "baz": "blah", }, - Ports: []api.ServicePort{ + Ports: []v1.ServicePort{ { Port: 80, Protocol: "UDP", TargetPort: intstr.FromString("foobar"), }, }, - Type: api.ServiceTypeLoadBalancer, + Type: v1.ServiceTypeLoadBalancer, ExternalIPs: []string{"1.2.3.4"}, }, }, @@ -191,25 +191,25 @@ func TestGenerateService(t *testing.T) { "port": "80", "protocol": "UDP", "container-port": "foobar", - "type": string(api.ServiceTypeNodePort), + "type": string(v1.ServiceTypeNodePort), }, - expected: api.Service{ + expected: v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "test", }, - Spec: api.ServiceSpec{ + Spec: v1.ServiceSpec{ Selector: map[string]string{ "foo": "bar", "baz": "blah", }, - Ports: []api.ServicePort{ + Ports: []v1.ServicePort{ { Port: 80, Protocol: "UDP", TargetPort: intstr.FromString("foobar"), }, }, - Type: api.ServiceTypeNodePort, + Type: v1.ServiceTypeNodePort, }, }, }, @@ -222,25 +222,25 @@ func TestGenerateService(t *testing.T) { "protocol": "UDP", "container-port": "foobar", "create-external-load-balancer": "true", // ignored when type is present - "type": string(api.ServiceTypeNodePort), + "type": string(v1.ServiceTypeNodePort), }, - expected: api.Service{ + expected: v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "test", }, - Spec: api.ServiceSpec{ + Spec: v1.ServiceSpec{ Selector: map[string]string{ "foo": "bar", "baz": "blah", }, - Ports: []api.ServicePort{ + Ports: []v1.ServicePort{ { Port: 80, Protocol: "UDP", TargetPort: intstr.FromString("foobar"), }, }, - Type: api.ServiceTypeNodePort, + Type: v1.ServiceTypeNodePort, }, }, }, @@ -253,16 +253,16 @@ func TestGenerateService(t *testing.T) { "protocol": "TCP", "container-port": "1234", }, - expected: api.Service{ + expected: v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "test", }, - Spec: api.ServiceSpec{ + Spec: v1.ServiceSpec{ Selector: map[string]string{ "foo": "bar", "baz": "blah", }, - Ports: []api.ServicePort{ + Ports: []v1.ServicePort{ { Name: "default", Port: 80, @@ -283,16 +283,16 @@ func TestGenerateService(t *testing.T) { "container-port": "1234", "session-affinity": "ClientIP", }, - expected: api.Service{ + expected: v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "test", }, - Spec: api.ServiceSpec{ + Spec: v1.ServiceSpec{ Selector: map[string]string{ "foo": "bar", "baz": "blah", }, - Ports: []api.ServicePort{ + Ports: []v1.ServicePort{ { Name: "default", Port: 80, @@ -300,7 +300,7 @@ func TestGenerateService(t *testing.T) { TargetPort: intstr.FromInt(1234), }, }, - SessionAffinity: api.ServiceAffinityClientIP, + SessionAffinity: v1.ServiceAffinityClientIP, }, }, }, @@ -314,16 +314,16 @@ func TestGenerateService(t *testing.T) { "container-port": "1234", "cluster-ip": "10.10.10.10", }, - expected: api.Service{ + expected: v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "test", }, - Spec: api.ServiceSpec{ + Spec: v1.ServiceSpec{ Selector: map[string]string{ "foo": "bar", "baz": "blah", }, - Ports: []api.ServicePort{ + Ports: []v1.ServicePort{ { Port: 80, Protocol: "TCP", @@ -344,23 +344,23 @@ func TestGenerateService(t *testing.T) { "container-port": "1234", "cluster-ip": "None", }, - expected: api.Service{ + expected: v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "test", }, - Spec: api.ServiceSpec{ + Spec: v1.ServiceSpec{ Selector: map[string]string{ "foo": "bar", "baz": "blah", }, - Ports: []api.ServicePort{ + Ports: []v1.ServicePort{ { Port: 80, Protocol: "TCP", TargetPort: intstr.FromInt(1234), }, }, - ClusterIP: api.ClusterIPNone, + ClusterIP: v1.ClusterIPNone, }, }, }, @@ -373,25 +373,25 @@ func TestGenerateService(t *testing.T) { "protocol": "TCP", "container-port": "foobar", }, - expected: api.Service{ + expected: v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "test", }, - Spec: api.ServiceSpec{ + Spec: v1.ServiceSpec{ Selector: map[string]string{ "foo": "bar", }, - Ports: []api.ServicePort{ + Ports: []v1.ServicePort{ { Name: "port-1", Port: 80, - Protocol: api.ProtocolTCP, + Protocol: v1.ProtocolTCP, TargetPort: intstr.FromString("foobar"), }, { Name: "port-2", Port: 443, - Protocol: api.ProtocolTCP, + Protocol: v1.ProtocolTCP, TargetPort: intstr.FromString("foobar"), }, }, @@ -407,25 +407,25 @@ func TestGenerateService(t *testing.T) { "protocol": "UDP", "target-port": "1234", }, - expected: api.Service{ + expected: v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "test", }, - Spec: api.ServiceSpec{ + Spec: v1.ServiceSpec{ Selector: map[string]string{ "foo": "bar", }, - Ports: []api.ServicePort{ + Ports: []v1.ServicePort{ { Name: "port-1", Port: 80, - Protocol: api.ProtocolUDP, + Protocol: v1.ProtocolUDP, TargetPort: intstr.FromInt(1234), }, { Name: "port-2", Port: 443, - Protocol: api.ProtocolUDP, + Protocol: v1.ProtocolUDP, TargetPort: intstr.FromInt(1234), }, }, @@ -440,25 +440,25 @@ func TestGenerateService(t *testing.T) { "ports": "80,443", "protocol": "TCP", }, - expected: api.Service{ + expected: v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "test", }, - Spec: api.ServiceSpec{ + Spec: v1.ServiceSpec{ Selector: map[string]string{ "foo": "bar", }, - Ports: []api.ServicePort{ + Ports: []v1.ServicePort{ { Name: "port-1", Port: 80, - Protocol: api.ProtocolTCP, + Protocol: v1.ProtocolTCP, TargetPort: intstr.FromInt(80), }, { Name: "port-2", Port: 443, - Protocol: api.ProtocolTCP, + Protocol: v1.ProtocolTCP, TargetPort: intstr.FromInt(443), }, }, @@ -473,25 +473,25 @@ func TestGenerateService(t *testing.T) { "ports": "80,8080", "protocols": "8080/UDP", }, - expected: api.Service{ + expected: v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "test", }, - Spec: api.ServiceSpec{ + Spec: v1.ServiceSpec{ Selector: map[string]string{ "foo": "bar", }, - Ports: []api.ServicePort{ + Ports: []v1.ServicePort{ { Name: "port-1", Port: 80, - Protocol: api.ProtocolTCP, + Protocol: v1.ProtocolTCP, TargetPort: intstr.FromInt(80), }, { Name: "port-2", Port: 8080, - Protocol: api.ProtocolUDP, + Protocol: v1.ProtocolUDP, TargetPort: intstr.FromInt(8080), }, }, @@ -506,31 +506,31 @@ func TestGenerateService(t *testing.T) { "ports": "80,8080,8081", "protocols": "8080/UDP,8081/TCP", }, - expected: api.Service{ + expected: v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "test", }, - Spec: api.ServiceSpec{ + Spec: v1.ServiceSpec{ Selector: map[string]string{ "foo": "bar", }, - Ports: []api.ServicePort{ + Ports: []v1.ServicePort{ { Name: "port-1", Port: 80, - Protocol: api.ProtocolTCP, + Protocol: v1.ProtocolTCP, TargetPort: intstr.FromInt(80), }, { Name: "port-2", Port: 8080, - Protocol: api.ProtocolUDP, + Protocol: v1.ProtocolUDP, TargetPort: intstr.FromInt(8080), }, { Name: "port-3", Port: 8081, - Protocol: api.ProtocolTCP, + Protocol: v1.ProtocolTCP, TargetPort: intstr.FromInt(8081), }, }, @@ -546,17 +546,17 @@ func TestGenerateService(t *testing.T) { "container-port": "1234", "cluster-ip": "None", }, - expected: api.Service{ + expected: v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "test", }, - Spec: api.ServiceSpec{ + Spec: v1.ServiceSpec{ Selector: map[string]string{ "foo": "bar", "baz": "blah", }, - Ports: []api.ServicePort{}, - ClusterIP: api.ClusterIPNone, + Ports: []v1.ServicePort{}, + ClusterIP: v1.ClusterIPNone, }, }, }, @@ -567,16 +567,16 @@ func TestGenerateService(t *testing.T) { "name": "test", "cluster-ip": "None", }, - expected: api.Service{ + expected: v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "test", }, - Spec: api.ServiceSpec{ + Spec: v1.ServiceSpec{ Selector: map[string]string{ "foo": "bar", }, - Ports: []api.ServicePort{}, - ClusterIP: api.ClusterIPNone, + Ports: []v1.ServicePort{}, + ClusterIP: v1.ClusterIPNone, }, }, }, diff --git a/pkg/kubectl/serviceaccount.go b/pkg/kubectl/serviceaccount.go index 08b84fb6fda..fa701e140a2 100644 --- a/pkg/kubectl/serviceaccount.go +++ b/pkg/kubectl/serviceaccount.go @@ -19,8 +19,8 @@ package kubectl import ( "fmt" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" ) // ServiceAccountGeneratorV1 supports stable generation of a service account @@ -37,7 +37,7 @@ func (g *ServiceAccountGeneratorV1) StructuredGenerate() (runtime.Object, error) if err := g.validate(); err != nil { return nil, err } - serviceAccount := &api.ServiceAccount{} + serviceAccount := &v1.ServiceAccount{} serviceAccount.Name = g.Name return serviceAccount, nil } diff --git a/pkg/kubectl/serviceaccount_test.go b/pkg/kubectl/serviceaccount_test.go index e8223914269..ec232f4a6d3 100644 --- a/pkg/kubectl/serviceaccount_test.go +++ b/pkg/kubectl/serviceaccount_test.go @@ -20,19 +20,19 @@ import ( "reflect" "testing" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" ) func TestServiceAccountGenerate(t *testing.T) { tests := []struct { name string - expected *api.ServiceAccount + expected *v1.ServiceAccount expectErr bool }{ { name: "foo", - expected: &api.ServiceAccount{ + expected: &v1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, @@ -54,8 +54,8 @@ func TestServiceAccountGenerate(t *testing.T) { if test.expectErr && err != nil { continue } - if !reflect.DeepEqual(obj.(*api.ServiceAccount), test.expected) { - t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*api.ServiceAccount)) + if !reflect.DeepEqual(obj.(*v1.ServiceAccount), test.expected) { + t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*v1.ServiceAccount)) } } } diff --git a/pkg/kubectl/sorting_printer_test.go b/pkg/kubectl/sorting_printer_test.go index 28b96e6609e..7f6b0ad5c4d 100644 --- a/pkg/kubectl/sorting_printer_test.go +++ b/pkg/kubectl/sorting_printer_test.go @@ -25,11 +25,11 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" - internal "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" ) func encodeOrDie(obj runtime.Object) []byte { - data, err := runtime.Encode(internal.Codecs.LegacyCodec(api.SchemeGroupVersion), obj) + data, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(api.SchemeGroupVersion), obj) if err != nil { panic(err.Error()) } @@ -389,7 +389,7 @@ func TestSortingPrinter(t *testing.T) { }, } for _, test := range tests { - sort := &SortingPrinter{SortField: test.field, Decoder: internal.Codecs.UniversalDecoder()} + sort := &SortingPrinter{SortField: test.field, Decoder: legacyscheme.Codecs.UniversalDecoder()} err := sort.sortObj(test.obj) if err != nil { if len(test.expectedErr) > 0 { diff --git a/pkg/kubectl/testing/BUILD b/pkg/kubectl/testing/BUILD deleted file mode 100644 index 99e7bf90a13..00000000000 --- a/pkg/kubectl/testing/BUILD +++ /dev/null @@ -1,31 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "go_default_library", - srcs = [ - "doc.go", - "types.go", - "zz_generated.deepcopy.go", - ], - importpath = "k8s.io/kubernetes/pkg/kubectl/testing", - visibility = ["//visibility:public"], - deps = [ - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], - visibility = ["//visibility:public"], -) diff --git a/pkg/kubectl/testing/doc.go b/pkg/kubectl/testing/doc.go deleted file mode 100644 index 0ea50660981..00000000000 --- a/pkg/kubectl/testing/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. -*/ - -// +k8s:deepcopy-gen=package - -package testing diff --git a/pkg/kubectl/testing/types.go b/pkg/kubectl/testing/types.go deleted file mode 100644 index 23e417e130a..00000000000 --- a/pkg/kubectl/testing/types.go +++ /dev/null @@ -1,33 +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 testing - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -type TestStruct struct { - metav1.TypeMeta `json:",inline"` - // +optional - metav1.ObjectMeta `json:"metadata,omitempty"` - Key string `json:"Key"` - Map map[string]int `json:"Map"` - StringList []string `json:"StringList"` - IntList []int `json:"IntList"` -} diff --git a/pkg/kubectl/testing/zz_generated.deepcopy.go b/pkg/kubectl/testing/zz_generated.deepcopy.go deleted file mode 100644 index 79c1b4003db..00000000000 --- a/pkg/kubectl/testing/zz_generated.deepcopy.go +++ /dev/null @@ -1,83 +0,0 @@ -// +build !ignore_autogenerated - -/* -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. -*/ - -// This file was autogenerated by deepcopy-gen. Do not edit it manually! - -package testing - -import ( - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" -) - -// GetGeneratedDeepCopyFuncs returns the generated funcs, since we aren't registering them. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func GetGeneratedDeepCopyFuncs() []conversion.GeneratedDeepCopyFunc { - return []conversion.GeneratedDeepCopyFunc{ - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*TestStruct).DeepCopyInto(out.(*TestStruct)) - return nil - }, InType: reflect.TypeOf(&TestStruct{})}, - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TestStruct) DeepCopyInto(out *TestStruct) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.Map != nil { - in, out := &in.Map, &out.Map - *out = make(map[string]int, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.StringList != nil { - in, out := &in.StringList, &out.StringList - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.IntList != nil { - in, out := &in.IntList, &out.IntList - *out = make([]int, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TestStruct. -func (in *TestStruct) DeepCopy() *TestStruct { - if in == nil { - return nil - } - out := new(TestStruct) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TestStruct) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} diff --git a/pkg/kubectl/util/BUILD b/pkg/kubectl/util/BUILD index 535db1587a2..96457f550ef 100644 --- a/pkg/kubectl/util/BUILD +++ b/pkg/kubectl/util/BUILD @@ -7,10 +7,39 @@ load( go_library( name = "go_default_library", srcs = [ - "umask.go", "util.go", ] + select({ - "@io_bazel_rules_go//go/platform:windows_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "umask.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "umask.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "umask.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "umask.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "umask.go", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "umask.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "umask.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "umask.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "umask.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "umask.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ "umask_windows.go", ], "//conditions:default": [], @@ -18,10 +47,41 @@ go_library( importpath = "k8s.io/kubernetes/pkg/kubectl/util", visibility = ["//build/visible_to:pkg_kubectl_util_CONSUMERS"], deps = [ - "//vendor/golang.org/x/sys/unix:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - ], + ] + select({ + "@io_bazel_rules_go//go/platform:android": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "//conditions:default": [], + }), ) filegroup( @@ -49,6 +109,6 @@ filegroup( go_test( name = "go_default_test", srcs = ["util_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubectl/util", - library = ":go_default_library", ) diff --git a/pkg/kubectl/util/hash/BUILD b/pkg/kubectl/util/hash/BUILD index b302b079767..6e6804a3844 100644 --- a/pkg/kubectl/util/hash/BUILD +++ b/pkg/kubectl/util/hash/BUILD @@ -9,16 +9,16 @@ load( go_test( name = "go_default_test", srcs = ["hash_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubectl/util/hash", - library = ":go_default_library", - deps = ["//pkg/api:go_default_library"], + deps = ["//vendor/k8s.io/api/core/v1:go_default_library"], ) go_library( name = "go_default_library", srcs = ["hash.go"], importpath = "k8s.io/kubernetes/pkg/kubectl/util/hash", - deps = ["//pkg/api:go_default_library"], + deps = ["//vendor/k8s.io/api/core/v1:go_default_library"], ) filegroup( diff --git a/pkg/kubectl/util/hash/hash.go b/pkg/kubectl/util/hash/hash.go index 137e1804540..d7afaab7d7c 100644 --- a/pkg/kubectl/util/hash/hash.go +++ b/pkg/kubectl/util/hash/hash.go @@ -21,12 +21,12 @@ import ( "encoding/json" "fmt" - "k8s.io/kubernetes/pkg/api" + "k8s.io/api/core/v1" ) // ConfigMapHash returns a hash of the ConfigMap. // The Data, Kind, and Name are taken into account. -func ConfigMapHash(cm *api.ConfigMap) (string, error) { +func ConfigMapHash(cm *v1.ConfigMap) (string, error) { encoded, err := encodeConfigMap(cm) if err != nil { return "", err @@ -40,7 +40,7 @@ func ConfigMapHash(cm *api.ConfigMap) (string, error) { // SecretHash returns a hash of the Secret. // The Data, Kind, Name, and Type are taken into account. -func SecretHash(sec *api.Secret) (string, error) { +func SecretHash(sec *v1.Secret) (string, error) { encoded, err := encodeSecret(sec) if err != nil { return "", err @@ -54,7 +54,7 @@ func SecretHash(sec *api.Secret) (string, error) { // encodeConfigMap encodes a ConfigMap. // Data, Kind, and Name are taken into account. -func encodeConfigMap(cm *api.ConfigMap) (string, error) { +func encodeConfigMap(cm *v1.ConfigMap) (string, error) { // json.Marshal sorts the keys in a stable order in the encoding data, err := json.Marshal(map[string]interface{}{"kind": "ConfigMap", "name": cm.Name, "data": cm.Data}) if err != nil { @@ -65,7 +65,7 @@ func encodeConfigMap(cm *api.ConfigMap) (string, error) { // encodeSecret encodes a Secret. // Data, Kind, Name, and Type are taken into account. -func encodeSecret(sec *api.Secret) (string, error) { +func encodeSecret(sec *v1.Secret) (string, error) { // json.Marshal sorts the keys in a stable order in the encoding data, err := json.Marshal(map[string]interface{}{"kind": "Secret", "type": sec.Type, "name": sec.Name, "data": sec.Data}) if err != nil { diff --git a/pkg/kubectl/util/hash/hash_test.go b/pkg/kubectl/util/hash/hash_test.go index ee344a35428..15a0bd20e14 100644 --- a/pkg/kubectl/util/hash/hash_test.go +++ b/pkg/kubectl/util/hash/hash_test.go @@ -21,22 +21,22 @@ import ( "strings" "testing" - "k8s.io/kubernetes/pkg/api" + "k8s.io/api/core/v1" ) func TestConfigMapHash(t *testing.T) { cases := []struct { desc string - cm *api.ConfigMap + cm *v1.ConfigMap hash string err string }{ // empty map - {"empty data", &api.ConfigMap{Data: map[string]string{}}, "42745tchd9", ""}, + {"empty data", &v1.ConfigMap{Data: map[string]string{}}, "42745tchd9", ""}, // one key - {"one key", &api.ConfigMap{Data: map[string]string{"one": ""}}, "9g67k2htb6", ""}, + {"one key", &v1.ConfigMap{Data: map[string]string{"one": ""}}, "9g67k2htb6", ""}, // three keys (tests sorting order) - {"three keys", &api.ConfigMap{Data: map[string]string{"two": "2", "one": "", "three": "3"}}, "f5h7t85m9b", ""}, + {"three keys", &v1.ConfigMap{Data: map[string]string{"two": "2", "one": "", "three": "3"}}, "f5h7t85m9b", ""}, } for _, c := range cases { @@ -53,16 +53,16 @@ func TestConfigMapHash(t *testing.T) { func TestSecretHash(t *testing.T) { cases := []struct { desc string - secret *api.Secret + secret *v1.Secret hash string err string }{ // empty map - {"empty data", &api.Secret{Type: "my-type", Data: map[string][]byte{}}, "t75bgf6ctb", ""}, + {"empty data", &v1.Secret{Type: "my-type", Data: map[string][]byte{}}, "t75bgf6ctb", ""}, // one key - {"one key", &api.Secret{Type: "my-type", Data: map[string][]byte{"one": []byte("")}}, "74bd68bm66", ""}, + {"one key", &v1.Secret{Type: "my-type", Data: map[string][]byte{"one": []byte("")}}, "74bd68bm66", ""}, // three keys (tests sorting order) - {"three keys", &api.Secret{Type: "my-type", Data: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")}}, "dgcb6h9tmk", ""}, + {"three keys", &v1.Secret{Type: "my-type", Data: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")}}, "dgcb6h9tmk", ""}, } for _, c := range cases { @@ -79,16 +79,16 @@ func TestSecretHash(t *testing.T) { func TestEncodeConfigMap(t *testing.T) { cases := []struct { desc string - cm *api.ConfigMap + cm *v1.ConfigMap expect string err string }{ // empty map - {"empty data", &api.ConfigMap{Data: map[string]string{}}, `{"data":{},"kind":"ConfigMap","name":""}`, ""}, + {"empty data", &v1.ConfigMap{Data: map[string]string{}}, `{"data":{},"kind":"ConfigMap","name":""}`, ""}, // one key - {"one key", &api.ConfigMap{Data: map[string]string{"one": ""}}, `{"data":{"one":""},"kind":"ConfigMap","name":""}`, ""}, + {"one key", &v1.ConfigMap{Data: map[string]string{"one": ""}}, `{"data":{"one":""},"kind":"ConfigMap","name":""}`, ""}, // three keys (tests sorting order) - {"three keys", &api.ConfigMap{Data: map[string]string{"two": "2", "one": "", "three": "3"}}, `{"data":{"one":"","three":"3","two":"2"},"kind":"ConfigMap","name":""}`, ""}, + {"three keys", &v1.ConfigMap{Data: map[string]string{"two": "2", "one": "", "three": "3"}}, `{"data":{"one":"","three":"3","two":"2"},"kind":"ConfigMap","name":""}`, ""}, } for _, c := range cases { s, err := encodeConfigMap(c.cm) @@ -104,16 +104,16 @@ func TestEncodeConfigMap(t *testing.T) { func TestEncodeSecret(t *testing.T) { cases := []struct { desc string - secret *api.Secret + secret *v1.Secret expect string err string }{ // empty map - {"empty data", &api.Secret{Type: "my-type", Data: map[string][]byte{}}, `{"data":{},"kind":"Secret","name":"","type":"my-type"}`, ""}, + {"empty data", &v1.Secret{Type: "my-type", Data: map[string][]byte{}}, `{"data":{},"kind":"Secret","name":"","type":"my-type"}`, ""}, // one key - {"one key", &api.Secret{Type: "my-type", Data: map[string][]byte{"one": []byte("")}}, `{"data":{"one":""},"kind":"Secret","name":"","type":"my-type"}`, ""}, + {"one key", &v1.Secret{Type: "my-type", Data: map[string][]byte{"one": []byte("")}}, `{"data":{"one":""},"kind":"Secret","name":"","type":"my-type"}`, ""}, // three keys (tests sorting order) - note json.Marshal base64 encodes the values because they come in as []byte - {"three keys", &api.Secret{Type: "my-type", Data: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")}}, `{"data":{"one":"","three":"Mw==","two":"Mg=="},"kind":"Secret","name":"","type":"my-type"}`, ""}, + {"three keys", &v1.Secret{Type: "my-type", Data: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")}}, `{"data":{"one":"","three":"Mw==","two":"Mg=="},"kind":"Secret","name":"","type":"my-type"}`, ""}, } for _, c := range cases { s, err := encodeSecret(c.secret) @@ -148,8 +148,8 @@ not their metadata (e.g. the Data of a ConfigMap, but nothing in ObjectMeta). obj interface{} expect int }{ - {"ConfigMap", api.ConfigMap{}, 3}, - {"Secret", api.Secret{}, 4}, + {"ConfigMap", v1.ConfigMap{}, 3}, + {"Secret", v1.Secret{}, 5}, } for _, c := range cases { val := reflect.ValueOf(c.obj) diff --git a/pkg/kubectl/util/i18n/BUILD b/pkg/kubectl/util/i18n/BUILD index 30168608640..365ff2d7e12 100644 --- a/pkg/kubectl/util/i18n/BUILD +++ b/pkg/kubectl/util/i18n/BUILD @@ -20,8 +20,8 @@ go_library( go_test( name = "go_default_test", srcs = ["i18n_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubectl/util/i18n", - library = ":go_default_library", ) filegroup( diff --git a/pkg/kubectl/util/slice/BUILD b/pkg/kubectl/util/slice/BUILD index 03567ebbaf9..2e7cd8e01ee 100644 --- a/pkg/kubectl/util/slice/BUILD +++ b/pkg/kubectl/util/slice/BUILD @@ -15,8 +15,8 @@ go_library( go_test( name = "go_default_test", srcs = ["slice_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubectl/util/slice", - library = ":go_default_library", ) filegroup( diff --git a/pkg/kubectl/util/term/BUILD b/pkg/kubectl/util/term/BUILD index d05dc402edd..8d0e6cfcc67 100644 --- a/pkg/kubectl/util/term/BUILD +++ b/pkg/kubectl/util/term/BUILD @@ -10,11 +10,40 @@ go_library( name = "go_default_library", srcs = [ "resize.go", - "resizeevents.go", "term.go", "term_writer.go", ] + select({ - "@io_bazel_rules_go//go/platform:windows_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "resizeevents.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "resizeevents.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "resizeevents.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "resizeevents.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "resizeevents.go", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "resizeevents.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "resizeevents.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "resizeevents.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "resizeevents.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "resizeevents.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ "resizeevents_windows.go", ], "//conditions:default": [], @@ -24,17 +53,48 @@ go_library( "//pkg/util/interrupt:go_default_library", "//vendor/github.com/docker/docker/pkg/term:go_default_library", "//vendor/github.com/mitchellh/go-wordwrap:go_default_library", - "//vendor/golang.org/x/sys/unix:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//vendor/k8s.io/client-go/tools/remotecommand:go_default_library", - ], + ] + select({ + "@io_bazel_rules_go//go/platform:android": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "//conditions:default": [], + }), ) go_test( name = "go_default_test", srcs = ["term_writer_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubectl/util/term", - library = ":go_default_library", ) filegroup( diff --git a/pkg/kubectl/validation/BUILD b/pkg/kubectl/validation/BUILD index 0854e64950c..647f0a8e203 100644 --- a/pkg/kubectl/validation/BUILD +++ b/pkg/kubectl/validation/BUILD @@ -17,8 +17,8 @@ go_test( data = [ ":testdata", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubectl/validation", - library = ":go_default_library", ) go_library( diff --git a/pkg/kubectl/versioned_client.go b/pkg/kubectl/versioned_client.go deleted file mode 100644 index f381aed69d4..00000000000 --- a/pkg/kubectl/versioned_client.go +++ /dev/null @@ -1,39 +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 kubectl - -import ( - clientappsv1beta1 "k8s.io/client-go/kubernetes/typed/apps/v1beta1" - clientextensionsv1beta1 "k8s.io/client-go/kubernetes/typed/extensions/v1beta1" - internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" -) - -// TODO: get rid of this and plumb the caller correctly -func versionedExtensionsClientV1beta1(internalClient internalclientset.Interface) clientextensionsv1beta1.ExtensionsV1beta1Interface { - if internalClient == nil { - return &clientextensionsv1beta1.ExtensionsV1beta1Client{} - } - return clientextensionsv1beta1.New(internalClient.Extensions().RESTClient()) -} - -// TODO: get rid of this and plumb the caller correctly -func versionedAppsClientV1beta1(internalClient internalclientset.Interface) clientappsv1beta1.AppsV1beta1Interface { - if internalClient == nil { - return &clientappsv1beta1.AppsV1beta1Client{} - } - return clientappsv1beta1.New(internalClient.Apps().RESTClient()) -} diff --git a/pkg/kubelet/BUILD b/pkg/kubelet/BUILD index f5f5f13f121..1a55be0ba0f 100644 --- a/pkg/kubelet/BUILD +++ b/pkg/kubelet/BUILD @@ -18,7 +18,6 @@ go_library( "kubelet_pods.go", "kubelet_resources.go", "kubelet_volumes.go", - "networks.go", "oom_watcher.go", "pod_container_deletor.go", "pod_workers.go", @@ -30,13 +29,13 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/kubelet", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/v1:go_default_library", - "//pkg/api/v1/helper:go_default_library", - "//pkg/api/v1/helper/qos:go_default_library", "//pkg/api/v1/pod:go_default_library", "//pkg/api/v1/resource:go_default_library", - "//pkg/api/v1/validation:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/pods:go_default_library", + "//pkg/apis/core/v1:go_default_library", + "//pkg/apis/core/v1/helper:go_default_library", + "//pkg/apis/core/v1/helper/qos:go_default_library", "//pkg/capabilities:go_default_library", "//pkg/cloudprovider:go_default_library", "//pkg/features:go_default_library", @@ -45,7 +44,6 @@ go_library( "//pkg/kubelet/apis/cri:go_default_library", "//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library", "//pkg/kubelet/apis/kubeletconfig:go_default_library", - "//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library", "//pkg/kubelet/cadvisor:go_default_library", "//pkg/kubelet/certificate:go_default_library", "//pkg/kubelet/cm:go_default_library", @@ -53,7 +51,6 @@ go_library( "//pkg/kubelet/configmap:go_default_library", "//pkg/kubelet/container:go_default_library", "//pkg/kubelet/dockershim:go_default_library", - "//pkg/kubelet/dockershim/libdocker:go_default_library", "//pkg/kubelet/dockershim/remote:go_default_library", "//pkg/kubelet/envvars:go_default_library", "//pkg/kubelet/events:go_default_library", @@ -65,7 +62,9 @@ go_library( "//pkg/kubelet/kuberuntime:go_default_library", "//pkg/kubelet/lifecycle:go_default_library", "//pkg/kubelet/metrics:go_default_library", + "//pkg/kubelet/mountpod:go_default_library", "//pkg/kubelet/network:go_default_library", + "//pkg/kubelet/network/dns:go_default_library", "//pkg/kubelet/pleg:go_default_library", "//pkg/kubelet/pod:go_default_library", "//pkg/kubelet/preemption:go_default_library", @@ -88,6 +87,8 @@ go_library( "//pkg/kubelet/util/queue:go_default_library", "//pkg/kubelet/util/sliceutils:go_default_library", "//pkg/kubelet/volumemanager:go_default_library", + "//pkg/scheduler/algorithm:go_default_library", + "//pkg/scheduler/algorithm/predicates:go_default_library", "//pkg/security/apparmor:go_default_library", "//pkg/securitycontext:go_default_library", "//pkg/util/dbus:go_default_library", @@ -104,8 +105,6 @@ go_library( "//pkg/volume/util/types:go_default_library", "//pkg/volume/util/volumehelper:go_default_library", "//pkg/volume/validation:go_default_library", - "//plugin/pkg/scheduler/algorithm:go_default_library", - "//plugin/pkg/scheduler/algorithm/predicates:go_default_library", "//third_party/forked/golang/expansion:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/golang/groupcache/lru:go_default_library", @@ -125,7 +124,6 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", @@ -152,23 +150,22 @@ go_test( "kubelet_resources_test.go", "kubelet_test.go", "kubelet_volumes_test.go", - "networks_test.go", "oom_watcher_test.go", "pod_container_deletor_test.go", "pod_workers_test.go", "reason_cache_test.go", "runonce_test.go", ] + select({ - "@io_bazel_rules_go//go/platform:windows_amd64": [ + "@io_bazel_rules_go//go/platform:windows": [ "kubelet_pods_windows_test.go", ], "//conditions:default": [], }), + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core/install:go_default_library", "//pkg/capabilities:go_default_library", "//pkg/cloudprovider/providers/fake:go_default_library", "//pkg/kubelet/apis:go_default_library", @@ -202,6 +199,7 @@ go_test( "//pkg/kubelet/util/queue:go_default_library", "//pkg/kubelet/util/sliceutils:go_default_library", "//pkg/kubelet/volumemanager:go_default_library", + "//pkg/scheduler/schedulercache:go_default_library", "//pkg/util/mount:go_default_library", "//pkg/version:go_default_library", "//pkg/volume:go_default_library", @@ -253,13 +251,13 @@ filegroup( "//pkg/kubelet/apis:all-srcs", "//pkg/kubelet/cadvisor:all-srcs", "//pkg/kubelet/certificate:all-srcs", + "//pkg/kubelet/checkpoint:all-srcs", "//pkg/kubelet/client:all-srcs", "//pkg/kubelet/cm:all-srcs", "//pkg/kubelet/config:all-srcs", "//pkg/kubelet/configmap:all-srcs", "//pkg/kubelet/container:all-srcs", "//pkg/kubelet/custommetrics:all-srcs", - "//pkg/kubelet/deviceplugin:all-srcs", "//pkg/kubelet/dockershim:all-srcs", "//pkg/kubelet/envvars:all-srcs", "//pkg/kubelet/events:all-srcs", @@ -271,6 +269,7 @@ filegroup( "//pkg/kubelet/leaky:all-srcs", "//pkg/kubelet/lifecycle:all-srcs", "//pkg/kubelet/metrics:all-srcs", + "//pkg/kubelet/mountpod:all-srcs", "//pkg/kubelet/network:all-srcs", "//pkg/kubelet/pleg:all-srcs", "//pkg/kubelet/pod:all-srcs", diff --git a/pkg/kubelet/apis/BUILD b/pkg/kubelet/apis/BUILD index 00352f578d7..490bbd581f9 100644 --- a/pkg/kubelet/apis/BUILD +++ b/pkg/kubelet/apis/BUILD @@ -26,7 +26,7 @@ filegroup( srcs = [ ":package-srcs", "//pkg/kubelet/apis/cri:all-srcs", - "//pkg/kubelet/apis/deviceplugin/v1alpha1:all-srcs", + "//pkg/kubelet/apis/deviceplugin/v1alpha:all-srcs", "//pkg/kubelet/apis/kubeletconfig:all-srcs", "//pkg/kubelet/apis/stats/v1alpha1:all-srcs", ], diff --git a/pkg/kubelet/apis/cri/testing/fake_runtime_service.go b/pkg/kubelet/apis/cri/testing/fake_runtime_service.go index 69a0b773bfe..284467f07d9 100644 --- a/pkg/kubelet/apis/cri/testing/fake_runtime_service.go +++ b/pkg/kubelet/apis/cri/testing/fake_runtime_service.go @@ -26,7 +26,7 @@ import ( ) var ( - version = "0.1.0" + FakeVersion = "0.1.0" FakeRuntimeName = "fakeRuntime" FakePodSandboxIP = "192.168.192.168" @@ -117,10 +117,10 @@ func (r *FakeRuntimeService) Version(apiVersion string) (*runtimeapi.VersionResp r.Called = append(r.Called, "Version") return &runtimeapi.VersionResponse{ - Version: version, + Version: FakeVersion, RuntimeName: FakeRuntimeName, - RuntimeVersion: version, - RuntimeApiVersion: version, + RuntimeVersion: FakeVersion, + RuntimeApiVersion: FakeVersion, }, nil } diff --git a/pkg/kubelet/apis/cri/v1alpha1/runtime/api.pb.go b/pkg/kubelet/apis/cri/v1alpha1/runtime/api.pb.go index 3264e2a1dcd..9df3390c369 100644 --- a/pkg/kubelet/apis/cri/v1alpha1/runtime/api.pb.go +++ b/pkg/kubelet/apis/cri/v1alpha1/runtime/api.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -860,6 +860,8 @@ func (*RemovePodSandboxResponse) Descriptor() ([]byte, []int) { return fileDescr type PodSandboxStatusRequest struct { // ID of the PodSandbox for which to retrieve status. PodSandboxId string `protobuf:"bytes,1,opt,name=pod_sandbox_id,json=podSandboxId,proto3" json:"pod_sandbox_id,omitempty"` + // Verbose indicates whether to return extra information about the pod sandbox. + Verbose bool `protobuf:"varint,2,opt,name=verbose,proto3" json:"verbose,omitempty"` } func (m *PodSandboxStatusRequest) Reset() { *m = PodSandboxStatusRequest{} } @@ -873,6 +875,13 @@ func (m *PodSandboxStatusRequest) GetPodSandboxId() string { return "" } +func (m *PodSandboxStatusRequest) GetVerbose() bool { + if m != nil { + return m.Verbose + } + return false +} + // PodSandboxNetworkStatus is the status of the network for a PodSandbox. type PodSandboxNetworkStatus struct { // IP address of the PodSandbox. @@ -1010,6 +1019,11 @@ func (m *PodSandboxStatus) GetAnnotations() map[string]string { type PodSandboxStatusResponse struct { // Status of the PodSandbox. Status *PodSandboxStatus `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"` + // Info is extra information of the PodSandbox. The key could be abitrary string, and + // value should be in json format. The information could include anything useful for + // debug, e.g. network namespace for linux container based container runtime. + // It should only be returned non-empty when Verbose is true. + Info map[string]string `protobuf:"bytes,2,rep,name=info" json:"info,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (m *PodSandboxStatusResponse) Reset() { *m = PodSandboxStatusResponse{} } @@ -1023,6 +1037,13 @@ func (m *PodSandboxStatusResponse) GetStatus() *PodSandboxStatus { return nil } +func (m *PodSandboxStatusResponse) GetInfo() map[string]string { + if m != nil { + return m.Info + } + return nil +} + // PodSandboxStateValue is the wrapper of PodSandboxState. type PodSandboxStateValue struct { // State of the sandbox. @@ -2088,6 +2109,8 @@ func (m *ListContainersResponse) GetContainers() []*Container { type ContainerStatusRequest struct { // ID of the container for which to retrieve status. ContainerId string `protobuf:"bytes,1,opt,name=container_id,json=containerId,proto3" json:"container_id,omitempty"` + // Verbose indicates whether to return extra information about the container. + Verbose bool `protobuf:"varint,2,opt,name=verbose,proto3" json:"verbose,omitempty"` } func (m *ContainerStatusRequest) Reset() { *m = ContainerStatusRequest{} } @@ -2101,6 +2124,13 @@ func (m *ContainerStatusRequest) GetContainerId() string { return "" } +func (m *ContainerStatusRequest) GetVerbose() bool { + if m != nil { + return m.Verbose + } + return false +} + // ContainerStatus represents the status of a container. type ContainerStatus struct { // ID of the container. @@ -2252,6 +2282,11 @@ func (m *ContainerStatus) GetLogPath() string { type ContainerStatusResponse struct { // Status of the container. Status *ContainerStatus `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"` + // Info is extra information of the Container. The key could be abitrary string, and + // value should be in json format. The information could include anything useful for + // debug, e.g. pid for linux container based container runtime. + // It should only be returned non-empty when Verbose is true. + Info map[string]string `protobuf:"bytes,2,rep,name=info" json:"info,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (m *ContainerStatusResponse) Reset() { *m = ContainerStatusResponse{} } @@ -2265,6 +2300,13 @@ func (m *ContainerStatusResponse) GetStatus() *ContainerStatus { return nil } +func (m *ContainerStatusResponse) GetInfo() map[string]string { + if m != nil { + return m.Info + } + return nil +} + type UpdateContainerResourcesRequest struct { // ID of the container to update. ContainerId string `protobuf:"bytes,1,opt,name=container_id,json=containerId,proto3" json:"container_id,omitempty"` @@ -2684,6 +2726,8 @@ func (m *ListImagesResponse) GetImages() []*Image { type ImageStatusRequest struct { // Spec of the image. Image *ImageSpec `protobuf:"bytes,1,opt,name=image" json:"image,omitempty"` + // Verbose indicates whether to return extra information about the image. + Verbose bool `protobuf:"varint,2,opt,name=verbose,proto3" json:"verbose,omitempty"` } func (m *ImageStatusRequest) Reset() { *m = ImageStatusRequest{} } @@ -2697,9 +2741,21 @@ func (m *ImageStatusRequest) GetImage() *ImageSpec { return nil } +func (m *ImageStatusRequest) GetVerbose() bool { + if m != nil { + return m.Verbose + } + return false +} + type ImageStatusResponse struct { // Status of the image. Image *Image `protobuf:"bytes,1,opt,name=image" json:"image,omitempty"` + // Info is extra information of the Image. The key could be abitrary string, and + // value should be in json format. The information could include anything useful + // for debug, e.g. image config for oci image based container runtime. + // It should only be returned non-empty when Verbose is true. + Info map[string]string `protobuf:"bytes,2,rep,name=info" json:"info,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (m *ImageStatusResponse) Reset() { *m = ImageStatusResponse{} } @@ -2713,6 +2769,13 @@ func (m *ImageStatusResponse) GetImage() *Image { return nil } +func (m *ImageStatusResponse) GetInfo() map[string]string { + if m != nil { + return m.Info + } + return nil +} + // AuthConfig contains authorization information for connecting to a registry. type AuthConfig struct { Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` @@ -2973,15 +3036,29 @@ func (m *RuntimeStatus) GetConditions() []*RuntimeCondition { } type StatusRequest struct { + // Verbose indicates whether to return extra information about the runtime. + Verbose bool `protobuf:"varint,1,opt,name=verbose,proto3" json:"verbose,omitempty"` } func (m *StatusRequest) Reset() { *m = StatusRequest{} } func (*StatusRequest) ProtoMessage() {} func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{81} } +func (m *StatusRequest) GetVerbose() bool { + if m != nil { + return m.Verbose + } + return false +} + type StatusResponse struct { // Status of the Runtime. Status *RuntimeStatus `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"` + // Info is extra information of the Runtime. The key could be abitrary string, and + // value should be in json format. The information could include anything useful for + // debug, e.g. plugins used by the container runtime. + // It should only be returned non-empty when Verbose is true. + Info map[string]string `protobuf:"bytes,2,rep,name=info" json:"info,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (m *StatusResponse) Reset() { *m = StatusResponse{} } @@ -2995,6 +3072,13 @@ func (m *StatusResponse) GetStatus() *RuntimeStatus { return nil } +func (m *StatusResponse) GetInfo() map[string]string { + if m != nil { + return m.Info + } + return nil +} + type ImageFsInfoRequest struct { } @@ -5220,6 +5304,16 @@ func (m *PodSandboxStatusRequest) MarshalTo(dAtA []byte) (int, error) { i = encodeVarintApi(dAtA, i, uint64(len(m.PodSandboxId))) i += copy(dAtA[i:], m.PodSandboxId) } + if m.Verbose { + dAtA[i] = 0x10 + i++ + if m.Verbose { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + } return i, nil } @@ -5426,6 +5520,23 @@ func (m *PodSandboxStatusResponse) MarshalTo(dAtA []byte) (int, error) { } i += n16 } + if len(m.Info) > 0 { + for k := range m.Info { + dAtA[i] = 0x12 + i++ + v := m.Info[k] + mapSize := 1 + len(k) + sovApi(uint64(len(k))) + 1 + len(v) + sovApi(uint64(len(v))) + i = encodeVarintApi(dAtA, i, uint64(mapSize)) + dAtA[i] = 0xa + i++ + i = encodeVarintApi(dAtA, i, uint64(len(k))) + i += copy(dAtA[i:], k) + dAtA[i] = 0x12 + i++ + i = encodeVarintApi(dAtA, i, uint64(len(v))) + i += copy(dAtA[i:], v) + } + } return i, nil } @@ -6713,6 +6824,16 @@ func (m *ContainerStatusRequest) MarshalTo(dAtA []byte) (int, error) { i = encodeVarintApi(dAtA, i, uint64(len(m.ContainerId))) i += copy(dAtA[i:], m.ContainerId) } + if m.Verbose { + dAtA[i] = 0x10 + i++ + if m.Verbose { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + } return i, nil } @@ -6880,6 +7001,23 @@ func (m *ContainerStatusResponse) MarshalTo(dAtA []byte) (int, error) { } i += n39 } + if len(m.Info) > 0 { + for k := range m.Info { + dAtA[i] = 0x12 + i++ + v := m.Info[k] + mapSize := 1 + len(k) + sovApi(uint64(len(k))) + 1 + len(v) + sovApi(uint64(len(v))) + i = encodeVarintApi(dAtA, i, uint64(mapSize)) + dAtA[i] = 0xa + i++ + i = encodeVarintApi(dAtA, i, uint64(len(k))) + i += copy(dAtA[i:], k) + dAtA[i] = 0x12 + i++ + i = encodeVarintApi(dAtA, i, uint64(len(v))) + i += copy(dAtA[i:], v) + } + } return i, nil } @@ -7457,6 +7595,16 @@ func (m *ImageStatusRequest) MarshalTo(dAtA []byte) (int, error) { } i += n46 } + if m.Verbose { + dAtA[i] = 0x10 + i++ + if m.Verbose { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + } return i, nil } @@ -7485,6 +7633,23 @@ func (m *ImageStatusResponse) MarshalTo(dAtA []byte) (int, error) { } i += n47 } + if len(m.Info) > 0 { + for k := range m.Info { + dAtA[i] = 0x12 + i++ + v := m.Info[k] + mapSize := 1 + len(k) + sovApi(uint64(len(k))) + 1 + len(v) + sovApi(uint64(len(v))) + i = encodeVarintApi(dAtA, i, uint64(mapSize)) + dAtA[i] = 0xa + i++ + i = encodeVarintApi(dAtA, i, uint64(len(k))) + i += copy(dAtA[i:], k) + dAtA[i] = 0x12 + i++ + i = encodeVarintApi(dAtA, i, uint64(len(v))) + i += copy(dAtA[i:], v) + } + } return i, nil } @@ -7849,6 +8014,16 @@ func (m *StatusRequest) MarshalTo(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.Verbose { + dAtA[i] = 0x8 + i++ + if m.Verbose { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + } return i, nil } @@ -7877,6 +8052,23 @@ func (m *StatusResponse) MarshalTo(dAtA []byte) (int, error) { } i += n54 } + if len(m.Info) > 0 { + for k := range m.Info { + dAtA[i] = 0x12 + i++ + v := m.Info[k] + mapSize := 1 + len(k) + sovApi(uint64(len(k))) + 1 + len(v) + sovApi(uint64(len(v))) + i = encodeVarintApi(dAtA, i, uint64(mapSize)) + dAtA[i] = 0xa + i++ + i = encodeVarintApi(dAtA, i, uint64(len(k))) + i += copy(dAtA[i:], k) + dAtA[i] = 0x12 + i++ + i = encodeVarintApi(dAtA, i, uint64(len(v))) + i += copy(dAtA[i:], v) + } + } return i, nil } @@ -8711,6 +8903,9 @@ func (m *PodSandboxStatusRequest) Size() (n int) { if l > 0 { n += 1 + l + sovApi(uint64(l)) } + if m.Verbose { + n += 2 + } return n } @@ -8795,6 +8990,14 @@ func (m *PodSandboxStatusResponse) Size() (n int) { l = m.Status.Size() n += 1 + l + sovApi(uint64(l)) } + if len(m.Info) > 0 { + for k, v := range m.Info { + _ = k + _ = v + mapEntrySize := 1 + len(k) + sovApi(uint64(len(k))) + 1 + len(v) + sovApi(uint64(len(v))) + n += mapEntrySize + 1 + sovApi(uint64(mapEntrySize)) + } + } return n } @@ -9347,6 +9550,9 @@ func (m *ContainerStatusRequest) Size() (n int) { if l > 0 { n += 1 + l + sovApi(uint64(l)) } + if m.Verbose { + n += 2 + } return n } @@ -9428,6 +9634,14 @@ func (m *ContainerStatusResponse) Size() (n int) { l = m.Status.Size() n += 1 + l + sovApi(uint64(l)) } + if len(m.Info) > 0 { + for k, v := range m.Info { + _ = k + _ = v + mapEntrySize := 1 + len(k) + sovApi(uint64(len(k))) + 1 + len(v) + sovApi(uint64(len(v))) + n += mapEntrySize + 1 + sovApi(uint64(mapEntrySize)) + } + } return n } @@ -9656,6 +9870,9 @@ func (m *ImageStatusRequest) Size() (n int) { l = m.Image.Size() n += 1 + l + sovApi(uint64(l)) } + if m.Verbose { + n += 2 + } return n } @@ -9666,6 +9883,14 @@ func (m *ImageStatusResponse) Size() (n int) { l = m.Image.Size() n += 1 + l + sovApi(uint64(l)) } + if len(m.Info) > 0 { + for k, v := range m.Info { + _ = k + _ = v + mapEntrySize := 1 + len(k) + sovApi(uint64(len(k))) + 1 + len(v) + sovApi(uint64(len(v))) + n += mapEntrySize + 1 + sovApi(uint64(mapEntrySize)) + } + } return n } @@ -9815,6 +10040,9 @@ func (m *RuntimeStatus) Size() (n int) { func (m *StatusRequest) Size() (n int) { var l int _ = l + if m.Verbose { + n += 2 + } return n } @@ -9825,6 +10053,14 @@ func (m *StatusResponse) Size() (n int) { l = m.Status.Size() n += 1 + l + sovApi(uint64(l)) } + if len(m.Info) > 0 { + for k, v := range m.Info { + _ = k + _ = v + mapEntrySize := 1 + len(k) + sovApi(uint64(len(k))) + 1 + len(v) + sovApi(uint64(len(v))) + n += mapEntrySize + 1 + sovApi(uint64(mapEntrySize)) + } + } return n } @@ -10277,6 +10513,7 @@ func (this *PodSandboxStatusRequest) String() string { } s := strings.Join([]string{`&PodSandboxStatusRequest{`, `PodSandboxId:` + fmt.Sprintf("%v", this.PodSandboxId) + `,`, + `Verbose:` + fmt.Sprintf("%v", this.Verbose) + `,`, `}`, }, "") return s @@ -10352,8 +10589,19 @@ func (this *PodSandboxStatusResponse) String() string { if this == nil { return "nil" } + keysForInfo := make([]string, 0, len(this.Info)) + for k := range this.Info { + keysForInfo = append(keysForInfo, k) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForInfo) + mapStringForInfo := "map[string]string{" + for _, k := range keysForInfo { + mapStringForInfo += fmt.Sprintf("%v: %v,", k, this.Info[k]) + } + mapStringForInfo += "}" s := strings.Join([]string{`&PodSandboxStatusResponse{`, `Status:` + strings.Replace(fmt.Sprintf("%v", this.Status), "PodSandboxStatus", "PodSandboxStatus", 1) + `,`, + `Info:` + mapStringForInfo + `,`, `}`, }, "") return s @@ -10781,6 +11029,7 @@ func (this *ContainerStatusRequest) String() string { } s := strings.Join([]string{`&ContainerStatusRequest{`, `ContainerId:` + fmt.Sprintf("%v", this.ContainerId) + `,`, + `Verbose:` + fmt.Sprintf("%v", this.Verbose) + `,`, `}`, }, "") return s @@ -10833,8 +11082,19 @@ func (this *ContainerStatusResponse) String() string { if this == nil { return "nil" } + keysForInfo := make([]string, 0, len(this.Info)) + for k := range this.Info { + keysForInfo = append(keysForInfo, k) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForInfo) + mapStringForInfo := "map[string]string{" + for _, k := range keysForInfo { + mapStringForInfo += fmt.Sprintf("%v: %v,", k, this.Info[k]) + } + mapStringForInfo += "}" s := strings.Join([]string{`&ContainerStatusResponse{`, `Status:` + strings.Replace(fmt.Sprintf("%v", this.Status), "ContainerStatus", "ContainerStatus", 1) + `,`, + `Info:` + mapStringForInfo + `,`, `}`, }, "") return s @@ -11004,6 +11264,7 @@ func (this *ImageStatusRequest) String() string { } s := strings.Join([]string{`&ImageStatusRequest{`, `Image:` + strings.Replace(fmt.Sprintf("%v", this.Image), "ImageSpec", "ImageSpec", 1) + `,`, + `Verbose:` + fmt.Sprintf("%v", this.Verbose) + `,`, `}`, }, "") return s @@ -11012,8 +11273,19 @@ func (this *ImageStatusResponse) String() string { if this == nil { return "nil" } + keysForInfo := make([]string, 0, len(this.Info)) + for k := range this.Info { + keysForInfo = append(keysForInfo, k) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForInfo) + mapStringForInfo := "map[string]string{" + for _, k := range keysForInfo { + mapStringForInfo += fmt.Sprintf("%v: %v,", k, this.Info[k]) + } + mapStringForInfo += "}" s := strings.Join([]string{`&ImageStatusResponse{`, `Image:` + strings.Replace(fmt.Sprintf("%v", this.Image), "Image", "Image", 1) + `,`, + `Info:` + mapStringForInfo + `,`, `}`, }, "") return s @@ -11141,6 +11413,7 @@ func (this *StatusRequest) String() string { return "nil" } s := strings.Join([]string{`&StatusRequest{`, + `Verbose:` + fmt.Sprintf("%v", this.Verbose) + `,`, `}`, }, "") return s @@ -11149,8 +11422,19 @@ func (this *StatusResponse) String() string { if this == nil { return "nil" } + keysForInfo := make([]string, 0, len(this.Info)) + for k := range this.Info { + keysForInfo = append(keysForInfo, k) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForInfo) + mapStringForInfo := "map[string]string{" + for _, k := range keysForInfo { + mapStringForInfo += fmt.Sprintf("%v: %v,", k, this.Info[k]) + } + mapStringForInfo += "}" s := strings.Join([]string{`&StatusResponse{`, `Status:` + strings.Replace(fmt.Sprintf("%v", this.Status), "RuntimeStatus", "RuntimeStatus", 1) + `,`, + `Info:` + mapStringForInfo + `,`, `}`, }, "") return s @@ -13821,6 +14105,26 @@ func (m *PodSandboxStatusRequest) Unmarshal(dAtA []byte) error { } m.PodSandboxId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Verbose", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Verbose = bool(v != 0) default: iNdEx = preIndex skippy, err := skipApi(dAtA[iNdEx:]) @@ -14597,6 +14901,122 @@ func (m *PodSandboxStatusResponse) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Info", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthApi + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var keykey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + keykey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + 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 ErrInvalidLengthApi + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey := string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + if m.Info == nil { + m.Info = make(map[string]string) + } + if iNdEx < postIndex { + var valuekey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + valuekey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + var stringLenmapvalue uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapvalue |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapvalue := int(stringLenmapvalue) + if intStringLenmapvalue < 0 { + return ErrInvalidLengthApi + } + postStringIndexmapvalue := iNdEx + intStringLenmapvalue + if postStringIndexmapvalue > l { + return io.ErrUnexpectedEOF + } + mapvalue := string(dAtA[iNdEx:postStringIndexmapvalue]) + iNdEx = postStringIndexmapvalue + m.Info[mapkey] = mapvalue + } else { + var mapvalue string + m.Info[mapkey] = mapvalue + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipApi(dAtA[iNdEx:]) @@ -19168,6 +19588,26 @@ func (m *ContainerStatusRequest) Unmarshal(dAtA []byte) error { } m.ContainerId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Verbose", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Verbose = bool(v != 0) default: iNdEx = preIndex skippy, err := skipApi(dAtA[iNdEx:]) @@ -19870,6 +20310,122 @@ func (m *ContainerStatusResponse) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Info", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthApi + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var keykey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + keykey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + 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 ErrInvalidLengthApi + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey := string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + if m.Info == nil { + m.Info = make(map[string]string) + } + if iNdEx < postIndex { + var valuekey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + valuekey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + var stringLenmapvalue uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapvalue |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapvalue := int(stringLenmapvalue) + if intStringLenmapvalue < 0 { + return ErrInvalidLengthApi + } + postStringIndexmapvalue := iNdEx + intStringLenmapvalue + if postStringIndexmapvalue > l { + return io.ErrUnexpectedEOF + } + mapvalue := string(dAtA[iNdEx:postStringIndexmapvalue]) + iNdEx = postStringIndexmapvalue + m.Info[mapkey] = mapvalue + } else { + var mapvalue string + m.Info[mapkey] = mapvalue + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipApi(dAtA[iNdEx:]) @@ -21563,6 +22119,26 @@ func (m *ImageStatusRequest) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Verbose", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Verbose = bool(v != 0) default: iNdEx = preIndex skippy, err := skipApi(dAtA[iNdEx:]) @@ -21646,6 +22222,122 @@ func (m *ImageStatusResponse) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Info", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthApi + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var keykey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + keykey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + 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 ErrInvalidLengthApi + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey := string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + if m.Info == nil { + m.Info = make(map[string]string) + } + if iNdEx < postIndex { + var valuekey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + valuekey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + var stringLenmapvalue uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapvalue |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapvalue := int(stringLenmapvalue) + if intStringLenmapvalue < 0 { + return ErrInvalidLengthApi + } + postStringIndexmapvalue := iNdEx + intStringLenmapvalue + if postStringIndexmapvalue > l { + return io.ErrUnexpectedEOF + } + mapvalue := string(dAtA[iNdEx:postStringIndexmapvalue]) + iNdEx = postStringIndexmapvalue + m.Info[mapkey] = mapvalue + } else { + var mapvalue string + m.Info[mapkey] = mapvalue + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipApi(dAtA[iNdEx:]) @@ -22814,6 +23506,26 @@ func (m *StatusRequest) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: StatusRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Verbose", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Verbose = bool(v != 0) default: iNdEx = preIndex skippy, err := skipApi(dAtA[iNdEx:]) @@ -22897,6 +23609,122 @@ func (m *StatusResponse) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Info", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthApi + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var keykey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + keykey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + 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 ErrInvalidLengthApi + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey := string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + if m.Info == nil { + m.Info = make(map[string]string) + } + if iNdEx < postIndex { + var valuekey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + valuekey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + var stringLenmapvalue uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapvalue |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapvalue := int(stringLenmapvalue) + if intStringLenmapvalue < 0 { + return ErrInvalidLengthApi + } + postStringIndexmapvalue := iNdEx + intStringLenmapvalue + if postStringIndexmapvalue > l { + return io.ErrUnexpectedEOF + } + mapvalue := string(dAtA[iNdEx:postStringIndexmapvalue]) + iNdEx = postStringIndexmapvalue + m.Info[mapkey] = mapvalue + } else { + var mapvalue string + m.Info[mapkey] = mapvalue + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipApi(dAtA[iNdEx:]) @@ -24753,274 +25581,280 @@ var ( func init() { proto.RegisterFile("api.proto", fileDescriptorApi) } var fileDescriptorApi = []byte{ - // 4300 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x3b, 0x4d, 0x6f, 0x1b, 0x49, - 0x76, 0x22, 0xa9, 0x0f, 0xf2, 0x51, 0xa4, 0xa8, 0x92, 0x2c, 0x51, 0xb4, 0x2d, 0xcb, 0xed, 0xf1, - 0x8c, 0xed, 0x5d, 0x7b, 0x3c, 0x9a, 0x1d, 0x4f, 0xc6, 0xf3, 0x65, 0x8e, 0x24, 0x3b, 0x1a, 0xdb, - 0x94, 0xb6, 0x29, 0xcd, 0xee, 0x62, 0x03, 0x74, 0x5a, 0xec, 0x12, 0xd5, 0x33, 0x64, 0x77, 0x4f, - 0x77, 0xb5, 0x6d, 0x05, 0x39, 0x24, 0x97, 0x20, 0x08, 0x10, 0x60, 0x73, 0x4c, 0x4e, 0x39, 0x04, - 0x58, 0xe4, 0x92, 0x43, 0x0e, 0x41, 0x7e, 0x41, 0xb0, 0x97, 0x05, 0x02, 0x04, 0x08, 0x92, 0x5b, - 0xd6, 0x39, 0xe4, 0x10, 0x20, 0xbf, 0x21, 0xa8, 0xaf, 0xee, 0xea, 0x2f, 0x5a, 0xf2, 0x0c, 0x76, - 0x7d, 0x62, 0xd7, 0xab, 0x57, 0xaf, 0x5e, 0xbd, 0xf7, 0xea, 0xd5, 0x7b, 0xaf, 0x8a, 0x50, 0x33, - 0x3d, 0xfb, 0x8e, 0xe7, 0xbb, 0xc4, 0x45, 0x73, 0x7e, 0xe8, 0x10, 0x7b, 0x8c, 0x3b, 0xb7, 0x87, - 0x36, 0x39, 0x09, 0x8f, 0xee, 0x0c, 0xdc, 0xf1, 0xbb, 0x43, 0x77, 0xe8, 0xbe, 0xcb, 0xfa, 0x8f, - 0xc2, 0x63, 0xd6, 0x62, 0x0d, 0xf6, 0xc5, 0xc7, 0x69, 0xb7, 0xa0, 0xf9, 0x15, 0xf6, 0x03, 0xdb, - 0x75, 0x74, 0xfc, 0x6d, 0x88, 0x03, 0x82, 0xda, 0x30, 0xf7, 0x8c, 0x43, 0xda, 0xa5, 0x8d, 0xd2, - 0x8d, 0x9a, 0x2e, 0x9b, 0xda, 0x2f, 0x4b, 0xb0, 0x10, 0x21, 0x07, 0x9e, 0xeb, 0x04, 0xb8, 0x18, - 0x1b, 0x5d, 0x85, 0x79, 0xc1, 0x93, 0xe1, 0x98, 0x63, 0xdc, 0x2e, 0xb3, 0xee, 0xba, 0x80, 0xf5, - 0xcc, 0x31, 0x46, 0xef, 0xc0, 0x82, 0x44, 0x91, 0x44, 0x2a, 0x0c, 0xab, 0x29, 0xc0, 0x62, 0x36, - 0x74, 0x07, 0x96, 0x24, 0xa2, 0xe9, 0xd9, 0x11, 0xf2, 0x34, 0x43, 0x5e, 0x14, 0x5d, 0x5d, 0xcf, - 0x16, 0xf8, 0xda, 0xcf, 0xa1, 0xb6, 0xdd, 0xeb, 0x6f, 0xb9, 0xce, 0xb1, 0x3d, 0xa4, 0x2c, 0x06, - 0xd8, 0xa7, 0x63, 0xda, 0xa5, 0x8d, 0x0a, 0x65, 0x51, 0x34, 0x51, 0x07, 0xaa, 0x01, 0x36, 0xfd, - 0xc1, 0x09, 0x0e, 0xda, 0x65, 0xd6, 0x15, 0xb5, 0xe9, 0x28, 0xd7, 0x23, 0xb6, 0xeb, 0x04, 0xed, - 0x0a, 0x1f, 0x25, 0x9a, 0xda, 0x5f, 0x97, 0xa0, 0xbe, 0xef, 0xfa, 0xe4, 0xa9, 0xe9, 0x79, 0xb6, - 0x33, 0x44, 0xb7, 0xa1, 0xca, 0x64, 0x39, 0x70, 0x47, 0x4c, 0x06, 0xcd, 0xcd, 0xc5, 0x3b, 0x82, - 0xa5, 0x3b, 0xfb, 0xa2, 0x43, 0x8f, 0x50, 0xd0, 0x75, 0x68, 0x0e, 0x5c, 0x87, 0x98, 0xb6, 0x83, - 0x7d, 0xc3, 0x73, 0x7d, 0xc2, 0x24, 0x33, 0xa3, 0x37, 0x22, 0x28, 0x25, 0x8e, 0x2e, 0x42, 0xed, - 0xc4, 0x0d, 0x08, 0xc7, 0xa8, 0x30, 0x8c, 0x2a, 0x05, 0xb0, 0xce, 0x55, 0x98, 0x63, 0x9d, 0xb6, - 0x27, 0x64, 0x30, 0x4b, 0x9b, 0xbb, 0x9e, 0xf6, 0xeb, 0x12, 0xcc, 0x3c, 0x75, 0x43, 0x87, 0xa4, - 0xa6, 0x31, 0xc9, 0x89, 0xd0, 0x8f, 0x32, 0x8d, 0x49, 0x4e, 0xe2, 0x69, 0x28, 0x06, 0x57, 0x11, - 0x9f, 0x86, 0x76, 0x76, 0xa0, 0xea, 0x63, 0xd3, 0x72, 0x9d, 0xd1, 0x29, 0x63, 0xa1, 0xaa, 0x47, - 0x6d, 0xaa, 0xbb, 0x00, 0x8f, 0x6c, 0x27, 0x7c, 0x61, 0xf8, 0x78, 0x64, 0x1e, 0xe1, 0x11, 0x63, - 0xa5, 0xaa, 0x37, 0x05, 0x58, 0xe7, 0x50, 0xf4, 0x31, 0xd4, 0x3d, 0xdf, 0xf5, 0xcc, 0xa1, 0x49, - 0xc5, 0xd7, 0x9e, 0x61, 0x12, 0x5a, 0x8b, 0x24, 0xc4, 0xb8, 0xdd, 0x8f, 0x11, 0x74, 0x15, 0x5b, - 0xfb, 0x1a, 0x16, 0xa8, 0xa5, 0x04, 0x9e, 0x39, 0xc0, 0x7b, 0x4c, 0xfe, 0xd4, 0xae, 0x18, 0xc7, - 0x0e, 0x26, 0xcf, 0x5d, 0xff, 0x1b, 0xb6, 0xac, 0xaa, 0x5e, 0xa7, 0xb0, 0x1e, 0x07, 0xa1, 0x35, - 0xa8, 0xf2, 0x45, 0xd9, 0x16, 0x5b, 0x53, 0x55, 0x67, 0xe2, 0xda, 0xb7, 0xad, 0xa8, 0xcb, 0xf6, - 0x06, 0x62, 0x49, 0x73, 0x5c, 0x74, 0x03, 0x4d, 0x03, 0xd8, 0x75, 0xc8, 0xbd, 0x1f, 0x7d, 0x65, - 0x8e, 0x42, 0x8c, 0x96, 0x61, 0xe6, 0x19, 0xfd, 0x60, 0xf4, 0x2b, 0x3a, 0x6f, 0x68, 0x7f, 0x56, - 0x81, 0x8b, 0x4f, 0xe8, 0xea, 0xfa, 0xa6, 0x63, 0x1d, 0xb9, 0x2f, 0xfa, 0x78, 0x10, 0xfa, 0x36, - 0x39, 0xdd, 0x72, 0x1d, 0x82, 0x5f, 0x10, 0xb4, 0x03, 0x8b, 0x8e, 0xe4, 0xd7, 0x90, 0xf6, 0x43, - 0x29, 0xd4, 0x37, 0xdb, 0xd1, 0x92, 0x53, 0x2b, 0xd2, 0x5b, 0x4e, 0x12, 0x10, 0xa0, 0xcf, 0x63, - 0xe1, 0x4a, 0x22, 0x65, 0x46, 0x64, 0x25, 0x22, 0xd2, 0xdf, 0x61, 0x7c, 0x08, 0x12, 0x52, 0xe8, - 0x92, 0xc0, 0xfb, 0x40, 0x37, 0x9a, 0x61, 0x06, 0x46, 0x18, 0x60, 0x9f, 0xad, 0xb4, 0xbe, 0xb9, - 0x14, 0x0d, 0x8e, 0xd7, 0xa9, 0xd7, 0xfc, 0xd0, 0xe9, 0x06, 0x87, 0x01, 0xf6, 0xd9, 0x76, 0x14, - 0xea, 0x35, 0x7c, 0xd7, 0x25, 0xc7, 0x81, 0x54, 0xa9, 0x04, 0xeb, 0x0c, 0x8a, 0xde, 0x85, 0xa5, - 0x20, 0xf4, 0xbc, 0x11, 0x1e, 0x63, 0x87, 0x98, 0x23, 0x63, 0xe8, 0xbb, 0xa1, 0x17, 0xb4, 0x67, - 0x36, 0x2a, 0x37, 0x2a, 0x3a, 0x52, 0xbb, 0x1e, 0xb1, 0x1e, 0xb4, 0x0e, 0xe0, 0xf9, 0xf6, 0x33, - 0x7b, 0x84, 0x87, 0xd8, 0x6a, 0xcf, 0x32, 0xa2, 0x0a, 0x04, 0xdd, 0x85, 0xe5, 0x00, 0x0f, 0x06, - 0xee, 0xd8, 0x33, 0x3c, 0xdf, 0x3d, 0xb6, 0x47, 0x98, 0x1b, 0xe4, 0x1c, 0x33, 0x48, 0x24, 0xfa, - 0xf6, 0x79, 0x17, 0x35, 0x4d, 0xed, 0x17, 0x65, 0xb8, 0xc0, 0x04, 0xb0, 0xef, 0x5a, 0x42, 0x17, - 0x62, 0xbb, 0x5f, 0x83, 0xc6, 0x80, 0x31, 0x64, 0x78, 0xa6, 0x8f, 0x1d, 0x22, 0xec, 0x7e, 0x9e, - 0x03, 0xf7, 0x19, 0x0c, 0xed, 0x41, 0x2b, 0x10, 0xaa, 0x33, 0x06, 0x5c, 0x77, 0x42, 0xc2, 0x6f, - 0x45, 0x42, 0x9a, 0xa0, 0x67, 0x7d, 0x21, 0xc8, 0x28, 0x7e, 0x2e, 0x38, 0x0d, 0x06, 0x64, 0xc4, - 0xdd, 0x45, 0x7d, 0xf3, 0x07, 0x49, 0x3a, 0x69, 0x36, 0xef, 0xf4, 0x39, 0xf6, 0x8e, 0x43, 0xfc, - 0x53, 0x5d, 0x8e, 0xed, 0xdc, 0x87, 0x79, 0xb5, 0x03, 0xb5, 0xa0, 0xf2, 0x0d, 0x3e, 0x15, 0x4b, - 0xa0, 0x9f, 0xb1, 0x5d, 0xf2, 0xcd, 0xca, 0x1b, 0xf7, 0xcb, 0xbf, 0x57, 0xd2, 0x7c, 0x40, 0xf1, - 0x2c, 0x4f, 0x31, 0x31, 0x2d, 0x93, 0x98, 0x08, 0xc1, 0x34, 0x73, 0xbf, 0x9c, 0x04, 0xfb, 0xa6, - 0x54, 0x43, 0xb1, 0x35, 0x6a, 0x3a, 0xfd, 0x44, 0x97, 0xa0, 0x16, 0x19, 0xa1, 0xf0, 0xc1, 0x31, - 0x80, 0xfa, 0x42, 0x93, 0x10, 0x3c, 0xf6, 0x08, 0x33, 0x88, 0x86, 0x2e, 0x9b, 0xda, 0x3f, 0x4f, - 0x43, 0x2b, 0xa3, 0x81, 0x0f, 0xa1, 0x3a, 0x16, 0xd3, 0x0b, 0xdb, 0xbf, 0x18, 0x3b, 0xc4, 0x0c, - 0x87, 0x7a, 0x84, 0x4c, 0xfd, 0x0d, 0xdd, 0x8c, 0xca, 0x71, 0x11, 0xb5, 0xa9, 0x5a, 0x47, 0xee, - 0xd0, 0xb0, 0x6c, 0x1f, 0x0f, 0x88, 0xeb, 0x9f, 0x0a, 0x2e, 0xe7, 0x47, 0xee, 0x70, 0x5b, 0xc2, - 0xd0, 0x7b, 0x00, 0x96, 0x13, 0x50, 0x8d, 0x1e, 0xdb, 0x43, 0xc6, 0x6b, 0x7d, 0x13, 0x45, 0x73, - 0x47, 0x47, 0x82, 0x5e, 0xb3, 0x9c, 0x40, 0x30, 0xfb, 0x11, 0x34, 0xa8, 0x8b, 0x35, 0xc6, 0xdc, - 0x9b, 0x73, 0x2b, 0xae, 0x6f, 0x2e, 0x2b, 0x1c, 0x47, 0xae, 0x5e, 0x9f, 0xf7, 0xe2, 0x46, 0x80, - 0x3e, 0x85, 0x59, 0xe6, 0xe2, 0x82, 0xf6, 0x2c, 0x1b, 0x73, 0x3d, 0x67, 0x95, 0x42, 0xdb, 0x4f, - 0x18, 0x1e, 0x57, 0xb6, 0x18, 0x84, 0x9e, 0x40, 0xdd, 0x74, 0x1c, 0x97, 0x98, 0x7c, 0x83, 0xcf, - 0x31, 0x1a, 0xb7, 0x8a, 0x69, 0x74, 0x63, 0x64, 0x4e, 0x48, 0x1d, 0x8e, 0x7e, 0x04, 0x33, 0xcc, - 0x03, 0xb4, 0xab, 0x6c, 0xd5, 0xeb, 0x93, 0xcd, 0x4f, 0xe7, 0xc8, 0x9d, 0x8f, 0xa0, 0xae, 0xb0, - 0x76, 0x1e, 0x73, 0xeb, 0x7c, 0x06, 0xad, 0x34, 0x47, 0xe7, 0x32, 0xd7, 0x5d, 0x58, 0xd6, 0x43, - 0x27, 0x66, 0x4c, 0xc6, 0x1f, 0xef, 0xc1, 0xac, 0xd0, 0x1f, 0xb7, 0x9d, 0xb5, 0x42, 0x89, 0xe8, - 0x02, 0x51, 0xfb, 0x14, 0x2e, 0xa4, 0x48, 0x89, 0xe8, 0xe4, 0x2d, 0x68, 0x7a, 0xae, 0x65, 0x04, - 0x1c, 0x6c, 0xd8, 0x96, 0x74, 0x06, 0x5e, 0x84, 0xbb, 0x6b, 0xd1, 0xe1, 0x7d, 0xe2, 0x7a, 0x59, - 0x56, 0xce, 0x36, 0xbc, 0x0d, 0x2b, 0xe9, 0xe1, 0x7c, 0x7a, 0xed, 0x73, 0x58, 0xd5, 0xf1, 0xd8, - 0x7d, 0x86, 0x5f, 0x97, 0x74, 0x07, 0xda, 0x59, 0x02, 0x31, 0xf1, 0x18, 0xda, 0x27, 0x26, 0x09, - 0x83, 0xf3, 0x11, 0xbf, 0xa9, 0x12, 0x10, 0x47, 0x27, 0xa7, 0x83, 0x9a, 0x50, 0xb6, 0x3d, 0x31, - 0xa8, 0x6c, 0x7b, 0xda, 0xe7, 0x50, 0x8b, 0x0e, 0x2d, 0xb4, 0x19, 0x47, 0x46, 0xe5, 0x57, 0x9c, - 0x6c, 0x51, 0xcc, 0xf4, 0x38, 0xe3, 0xad, 0xc5, 0x4c, 0x9b, 0x00, 0x91, 0x9f, 0x91, 0x27, 0x25, - 0xca, 0xd2, 0xd3, 0x15, 0x2c, 0xed, 0xef, 0x12, 0x4e, 0x47, 0x61, 0xd9, 0x8a, 0x58, 0xb6, 0x12, - 0x4e, 0xa8, 0x7c, 0x1e, 0x27, 0x74, 0x07, 0x66, 0x02, 0x62, 0x12, 0xee, 0x06, 0x9b, 0xca, 0xe2, - 0x92, 0x53, 0x62, 0x9d, 0xa3, 0xa1, 0xcb, 0x00, 0x03, 0x1f, 0x9b, 0x04, 0x5b, 0x86, 0xc9, 0xfd, - 0x63, 0x45, 0xaf, 0x09, 0x48, 0x97, 0xa0, 0xfb, 0x30, 0x27, 0x23, 0x95, 0x19, 0xc6, 0xc6, 0x46, - 0x0e, 0xc1, 0x84, 0xf4, 0x75, 0x39, 0x20, 0xde, 0xd3, 0xb3, 0x93, 0xf7, 0xb4, 0x18, 0xc7, 0x91, - 0x15, 0xb7, 0x34, 0x57, 0xe8, 0x96, 0xf8, 0x88, 0xb3, 0xb8, 0xa5, 0x6a, 0xa1, 0x5b, 0x12, 0x34, - 0x26, 0xba, 0xa5, 0xdf, 0xa5, 0x83, 0x79, 0x0a, 0xed, 0xec, 0x06, 0x11, 0x8e, 0xe1, 0x3d, 0x98, - 0x0d, 0x18, 0x64, 0x82, 0x93, 0x11, 0x43, 0x04, 0xa2, 0xf6, 0x10, 0x96, 0x53, 0x16, 0xc0, 0x03, - 0xc5, 0xc8, 0x5e, 0x4a, 0x67, 0xb2, 0x17, 0xed, 0xff, 0x4a, 0xaa, 0xf5, 0x3e, 0xb4, 0x47, 0x04, - 0xfb, 0x19, 0xeb, 0x7d, 0x5f, 0x12, 0xe5, 0xa6, 0x7b, 0xb9, 0x88, 0x28, 0x8f, 0xe1, 0x84, 0x25, - 0xf6, 0xa1, 0xc9, 0x74, 0x68, 0x04, 0x78, 0xc4, 0x0e, 0x44, 0x11, 0x8a, 0xfc, 0x30, 0x67, 0x34, - 0x9f, 0x97, 0x1b, 0x40, 0x5f, 0xa0, 0x73, 0xf5, 0x35, 0x46, 0x2a, 0xac, 0xf3, 0x00, 0x50, 0x16, - 0xe9, 0x5c, 0x7a, 0xf8, 0x92, 0xee, 0x7d, 0x9a, 0xb8, 0xe4, 0x78, 0xfa, 0x63, 0xc6, 0xc6, 0x04, - 0x25, 0x70, 0x3e, 0x75, 0x81, 0xa8, 0xfd, 0x6d, 0x05, 0x20, 0xee, 0x7c, 0x63, 0x37, 0xfd, 0x87, - 0xd1, 0x16, 0xe4, 0xd1, 0xc4, 0x95, 0x1c, 0x7a, 0xb9, 0x9b, 0xef, 0x61, 0x72, 0xf3, 0xf1, 0xb8, - 0xe2, 0xad, 0xbc, 0xd1, 0x6f, 0xec, 0xb6, 0xdb, 0x82, 0x95, 0xb4, 0xba, 0xc5, 0xa6, 0xbb, 0x09, - 0x33, 0x36, 0xc1, 0x63, 0x9e, 0x86, 0xab, 0xe9, 0x88, 0x82, 0xcb, 0x31, 0xb4, 0xab, 0x50, 0xdb, - 0x1d, 0x9b, 0x43, 0xdc, 0xf7, 0xf0, 0x80, 0xce, 0x65, 0xd3, 0x86, 0x98, 0x9f, 0x37, 0xb4, 0x4d, - 0xa8, 0x3e, 0xc6, 0xa7, 0x7c, 0x0f, 0x9e, 0x91, 0x3f, 0xed, 0x2f, 0xcb, 0xb0, 0xca, 0x7c, 0xe7, - 0x96, 0x4c, 0x82, 0x75, 0x1c, 0xb8, 0xa1, 0x3f, 0xc0, 0x01, 0x53, 0xa9, 0x17, 0x1a, 0x1e, 0xf6, - 0x6d, 0xd7, 0x12, 0x59, 0x5f, 0x6d, 0xe0, 0x85, 0xfb, 0x0c, 0x40, 0x13, 0x65, 0xda, 0xfd, 0x6d, - 0xe8, 0x0a, 0xdb, 0xaa, 0xe8, 0xd5, 0x81, 0x17, 0xfe, 0x98, 0xb6, 0xe5, 0xd8, 0xe0, 0xc4, 0xf4, - 0x71, 0xc0, 0x6c, 0x88, 0x8f, 0xed, 0x33, 0x00, 0x7a, 0x0f, 0x2e, 0x8c, 0xf1, 0xd8, 0xf5, 0x4f, - 0x8d, 0x91, 0x3d, 0xb6, 0x89, 0x61, 0x3b, 0xc6, 0xd1, 0x29, 0xc1, 0x81, 0x30, 0x1c, 0xc4, 0x3b, - 0x9f, 0xd0, 0xbe, 0x5d, 0xe7, 0x0b, 0xda, 0x83, 0x34, 0x68, 0xb8, 0xee, 0xd8, 0x08, 0x06, 0xae, - 0x8f, 0x0d, 0xd3, 0xfa, 0x9a, 0x1d, 0x1e, 0x15, 0xbd, 0xee, 0xba, 0xe3, 0x3e, 0x85, 0x75, 0xad, - 0xaf, 0xd1, 0x15, 0xa8, 0x0f, 0xbc, 0x30, 0xc0, 0xc4, 0xa0, 0x3f, 0xec, 0x90, 0xa8, 0xe9, 0xc0, - 0x41, 0x5b, 0x5e, 0x18, 0x28, 0x08, 0x63, 0x2a, 0xf6, 0x39, 0x15, 0xe1, 0x29, 0x15, 0xb3, 0x09, - 0x8d, 0x44, 0x1e, 0x49, 0xb3, 0x05, 0x96, 0x30, 0x8a, 0x6c, 0x81, 0x7e, 0x53, 0x98, 0xef, 0x8e, - 0xa4, 0x24, 0xd9, 0x37, 0x85, 0x91, 0x53, 0x4f, 0xa6, 0x0a, 0xec, 0x9b, 0x8a, 0x7c, 0x84, 0x9f, - 0x89, 0x3a, 0x40, 0x4d, 0xe7, 0x0d, 0xcd, 0x02, 0xd8, 0x32, 0x3d, 0xf3, 0xc8, 0x1e, 0xd9, 0xe4, - 0x14, 0xdd, 0x84, 0x96, 0x69, 0x59, 0xc6, 0x40, 0x42, 0x6c, 0x2c, 0x8b, 0x32, 0x0b, 0xa6, 0x65, - 0x6d, 0x29, 0x60, 0xf4, 0x03, 0x58, 0xb4, 0x7c, 0xd7, 0x4b, 0xe2, 0xf2, 0x2a, 0x4d, 0x8b, 0x76, - 0xa8, 0xc8, 0xda, 0x3f, 0x4d, 0xc3, 0xe5, 0xa4, 0x62, 0xd3, 0x99, 0xf9, 0x87, 0x30, 0x9f, 0x9a, - 0x35, 0x99, 0x12, 0xc7, 0x4c, 0xea, 0x09, 0xc4, 0x54, 0xee, 0x5a, 0xce, 0xe4, 0xae, 0xb9, 0x29, - 0x7f, 0xe5, 0xfb, 0x48, 0xf9, 0xa7, 0xbf, 0x4b, 0xca, 0x3f, 0x73, 0xa6, 0x94, 0xff, 0x6d, 0x56, - 0x81, 0x93, 0x83, 0x58, 0xe2, 0xc5, 0xcd, 0xa8, 0x11, 0xe1, 0x38, 0xb2, 0x52, 0x97, 0x2a, 0x0d, - 0xcc, 0x9d, 0xa7, 0x34, 0x50, 0x2d, 0x2c, 0x0d, 0x50, 0x8b, 0xf0, 0x3c, 0xd3, 0x1f, 0xbb, 0xbe, - 0xcc, 0xfd, 0xdb, 0x35, 0xc6, 0xc2, 0x82, 0x84, 0x8b, 0xbc, 0xbf, 0xb0, 0x4a, 0x00, 0x45, 0x55, - 0x02, 0xb4, 0x01, 0xf3, 0x8e, 0x6b, 0x38, 0xf8, 0xb9, 0x41, 0x15, 0x16, 0xb4, 0xeb, 0x5c, 0x7b, - 0x8e, 0xdb, 0xc3, 0xcf, 0xf7, 0x29, 0x44, 0xfb, 0xfb, 0x12, 0x2c, 0x27, 0x0d, 0x47, 0xe4, 0x85, - 0x9f, 0x41, 0xcd, 0x97, 0xbe, 0x41, 0x18, 0xcb, 0x46, 0x32, 0xfe, 0xca, 0xfa, 0x10, 0x3d, 0x1e, - 0x82, 0x7e, 0x5c, 0x58, 0x61, 0x78, 0xbb, 0x80, 0xcc, 0xab, 0x6a, 0x0c, 0x5a, 0x17, 0x16, 0x23, - 0xe4, 0x89, 0xf9, 0xbd, 0x92, 0xaf, 0x97, 0x93, 0xf9, 0xba, 0x03, 0xb3, 0xdb, 0xf8, 0x99, 0x3d, - 0xc0, 0xdf, 0x4b, 0x7d, 0x70, 0x03, 0xea, 0x1e, 0xf6, 0xc7, 0x76, 0x10, 0x44, 0x46, 0x5f, 0xd3, - 0x55, 0x90, 0xf6, 0x9f, 0x33, 0xb0, 0x90, 0x96, 0xec, 0xbd, 0x4c, 0x79, 0xa0, 0x13, 0xef, 0xc2, - 0xf4, 0xfa, 0x94, 0x33, 0xfa, 0x86, 0x3c, 0x06, 0xca, 0xa9, 0x2c, 0x21, 0x3a, 0x29, 0xc4, 0xd1, - 0x40, 0xd7, 0x3f, 0x70, 0xc7, 0x63, 0xd3, 0xb1, 0x64, 0xed, 0x56, 0x34, 0xa9, 0xb4, 0x4c, 0x7f, - 0x48, 0xb7, 0x16, 0x05, 0xb3, 0x6f, 0xea, 0x25, 0x69, 0xb4, 0x6d, 0x3b, 0xac, 0xba, 0xc0, 0x36, - 0x4e, 0x4d, 0x07, 0x01, 0xda, 0xb6, 0x7d, 0x74, 0x1d, 0xa6, 0xb1, 0xf3, 0x4c, 0x9e, 0xc6, 0x71, - 0x71, 0x57, 0x1e, 0x3f, 0x3a, 0xeb, 0x46, 0x6f, 0xc3, 0xec, 0xd8, 0x0d, 0x1d, 0x22, 0xe3, 0xee, - 0x66, 0xb2, 0xc6, 0xa9, 0x8b, 0x5e, 0x74, 0x13, 0xe6, 0x2c, 0xa6, 0x03, 0x19, 0x5c, 0x2f, 0xc4, - 0x15, 0x0a, 0x06, 0xd7, 0x65, 0x3f, 0xfa, 0x24, 0x8a, 0x23, 0x6a, 0xa9, 0x48, 0x20, 0x25, 0xd4, - 0xdc, 0x60, 0xe2, 0x71, 0x32, 0x98, 0x00, 0x46, 0xe2, 0x66, 0x21, 0x89, 0xc9, 0xf5, 0x85, 0x35, - 0xa8, 0x8e, 0xdc, 0x21, 0xb7, 0x83, 0x3a, 0xaf, 0xf4, 0x8f, 0xdc, 0x21, 0x33, 0x83, 0x65, 0x1a, - 0x3c, 0x59, 0xb6, 0xd3, 0x9e, 0x67, 0xdb, 0x8b, 0x37, 0xe8, 0x99, 0xc8, 0x3e, 0x0c, 0xd7, 0x19, - 0xe0, 0x76, 0x83, 0x75, 0xd5, 0x18, 0x64, 0xcf, 0x19, 0xb0, 0x23, 0x9b, 0x90, 0xd3, 0x76, 0x93, - 0xc1, 0xe9, 0x27, 0x8d, 0x79, 0x79, 0xb6, 0xb3, 0x90, 0x8a, 0x79, 0xf3, 0xf6, 0xe7, 0x1b, 0x50, - 0xc0, 0xf8, 0xc7, 0x12, 0xac, 0x6c, 0xb1, 0x90, 0x4f, 0xf1, 0x04, 0xe7, 0x48, 0xc0, 0xd1, 0xdd, - 0xa8, 0xd2, 0x91, 0xce, 0xa3, 0xd3, 0x8b, 0x15, 0x78, 0xe8, 0x01, 0x34, 0x25, 0x4d, 0x31, 0xb2, - 0xf2, 0xaa, 0x1a, 0x49, 0x23, 0x50, 0x9b, 0xda, 0x27, 0xb0, 0x9a, 0xe1, 0x59, 0x84, 0x67, 0x57, - 0x61, 0x3e, 0xf6, 0x08, 0x11, 0xcb, 0xf5, 0x08, 0xb6, 0x6b, 0x69, 0xf7, 0xe1, 0x42, 0x9f, 0x98, - 0x3e, 0xc9, 0x2c, 0xf8, 0x0c, 0x63, 0x59, 0x99, 0x24, 0x39, 0x56, 0x54, 0x32, 0xfa, 0xb0, 0xdc, - 0x27, 0xae, 0xf7, 0x1a, 0x44, 0xe9, 0x4e, 0xa7, 0xcb, 0x76, 0x43, 0x22, 0x62, 0x32, 0xd9, 0xd4, - 0x56, 0x79, 0x51, 0x27, 0x3b, 0xdb, 0xc7, 0xb0, 0xc2, 0x6b, 0x2a, 0xaf, 0xb3, 0x88, 0x35, 0x59, - 0xd1, 0xc9, 0xd2, 0xdd, 0x86, 0xa5, 0xd8, 0x95, 0xc7, 0xe9, 0xe1, 0xed, 0x64, 0x7a, 0xb8, 0x9a, - 0xd5, 0x71, 0x22, 0x3b, 0xfc, 0xab, 0xb2, 0xe2, 0x30, 0x0b, 0x92, 0xc3, 0xcd, 0x64, 0x72, 0x78, - 0xa9, 0x80, 0x64, 0x22, 0x37, 0xcc, 0x5a, 0x64, 0x25, 0xc7, 0x22, 0xf5, 0x4c, 0x06, 0x39, 0x9d, - 0x2a, 0x66, 0xa7, 0x78, 0xfb, 0xad, 0x24, 0x90, 0xbb, 0x3c, 0x81, 0x8c, 0xa6, 0x8e, 0xea, 0x5c, - 0x77, 0x53, 0x09, 0x64, 0xbb, 0x88, 0xcd, 0x28, 0x7f, 0xfc, 0xf3, 0x69, 0xa8, 0x45, 0x7d, 0x19, - 0xc1, 0x66, 0x85, 0x54, 0xce, 0x11, 0x92, 0x7a, 0x7e, 0x55, 0x5e, 0xe7, 0xfc, 0x9a, 0x7e, 0xd5, - 0xf9, 0x75, 0x11, 0x6a, 0xec, 0xc3, 0xf0, 0xf1, 0xb1, 0x38, 0x8f, 0xaa, 0x0c, 0xa0, 0xe3, 0xe3, - 0xd8, 0xa0, 0x66, 0xcf, 0x62, 0x50, 0xa9, 0x4c, 0x75, 0x2e, 0x9d, 0xa9, 0xde, 0x8b, 0x4e, 0x18, - 0x7e, 0x16, 0xad, 0x67, 0xc9, 0xe5, 0x9e, 0x2d, 0x3b, 0xc9, 0xb3, 0x85, 0x1f, 0x4f, 0xd7, 0x72, - 0x06, 0xbf, 0xb1, 0x79, 0xea, 0x13, 0x9e, 0xa7, 0xaa, 0x56, 0x25, 0x1c, 0xe1, 0x26, 0x40, 0xb4, - 0xe7, 0x65, 0xb2, 0x8a, 0xb2, 0x4b, 0xd3, 0x15, 0x2c, 0xea, 0x55, 0x12, 0xf2, 0x8f, 0x8b, 0xb1, - 0x67, 0xf0, 0x2a, 0xff, 0xa2, 0x46, 0x49, 0x05, 0xf5, 0xcc, 0x7b, 0x99, 0xd2, 0xc6, 0xd9, 0xac, - 0xee, 0x76, 0xb2, 0xb2, 0x71, 0x3e, 0x73, 0xc9, 0x14, 0x36, 0xd8, 0xa1, 0x6e, 0xfa, 0xa2, 0x9b, - 0xe7, 0xa4, 0x35, 0x01, 0xe9, 0x12, 0x1a, 0x4a, 0x1d, 0xdb, 0x8e, 0x1d, 0x9c, 0xf0, 0xfe, 0x59, - 0xd6, 0x0f, 0x12, 0xd4, 0x65, 0xb7, 0xda, 0xf8, 0x85, 0x4d, 0x8c, 0x81, 0x6b, 0x61, 0x66, 0x8c, - 0x33, 0x7a, 0x95, 0x02, 0xb6, 0x5c, 0x0b, 0xc7, 0x1b, 0xa4, 0x7a, 0xae, 0x0d, 0x52, 0x4b, 0x6d, - 0x90, 0x15, 0x98, 0xf5, 0xb1, 0x19, 0xb8, 0x8e, 0x48, 0x0c, 0x44, 0x8b, 0x9e, 0x15, 0x63, 0x1c, - 0x04, 0x74, 0x02, 0x11, 0xc0, 0x88, 0xa6, 0x12, 0x66, 0xcd, 0x17, 0x85, 0x59, 0x13, 0x0a, 0xa6, - 0xa9, 0x30, 0xab, 0x51, 0x14, 0x66, 0x9d, 0xa5, 0x5e, 0xaa, 0x04, 0x91, 0xcd, 0x89, 0x41, 0xa4, - 0x1a, 0x8e, 0x2d, 0x24, 0xc2, 0xb1, 0xdf, 0xe5, 0x9e, 0x7a, 0x0c, 0xab, 0x99, 0x5d, 0x20, 0x36, - 0xd5, 0xdd, 0x54, 0xc5, 0xb5, 0x5d, 0x24, 0xa0, 0xa8, 0xe0, 0xfa, 0xc7, 0x70, 0xe5, 0xd0, 0xb3, - 0x52, 0xa1, 0x8a, 0x48, 0xb4, 0xce, 0x1e, 0x21, 0xdc, 0x93, 0x51, 0x65, 0xf9, 0x8c, 0x39, 0x1c, - 0x47, 0xd7, 0x34, 0xd8, 0x28, 0x9e, 0x5d, 0x1c, 0xf9, 0x7f, 0x08, 0x0b, 0x3b, 0x2f, 0xf0, 0xa0, - 0x7f, 0xea, 0x0c, 0xce, 0xc1, 0x51, 0x0b, 0x2a, 0x83, 0xb1, 0x25, 0x4a, 0x19, 0xf4, 0x53, 0x8d, - 0x62, 0x2a, 0xc9, 0x28, 0xc6, 0x80, 0x56, 0x3c, 0x83, 0x90, 0xe4, 0x0a, 0x95, 0xa4, 0x45, 0x91, - 0x29, 0xf1, 0x79, 0x5d, 0xb4, 0x04, 0x1c, 0xfb, 0x3e, 0x5b, 0x2a, 0x87, 0x63, 0xdf, 0x4f, 0xee, - 0xb9, 0x4a, 0x72, 0xcf, 0x69, 0x7f, 0x53, 0x82, 0x3a, 0x9d, 0xe1, 0x3b, 0xf1, 0x2f, 0x62, 0xf9, - 0x4a, 0x1c, 0xcb, 0x47, 0x29, 0xc1, 0xb4, 0x9a, 0x12, 0xc4, 0x9c, 0xcf, 0x30, 0x70, 0x96, 0xf3, - 0xd9, 0x08, 0x8e, 0x7d, 0x5f, 0xdb, 0x80, 0x79, 0xce, 0x9b, 0x58, 0x79, 0x0b, 0x2a, 0xa1, 0x3f, - 0x92, 0xa6, 0x18, 0xfa, 0x23, 0xed, 0x2f, 0x4a, 0xd0, 0xe8, 0x12, 0x62, 0x0e, 0x4e, 0xce, 0xb1, - 0x80, 0x88, 0xb9, 0xb2, 0xca, 0x5c, 0x76, 0x11, 0x31, 0xbb, 0xd3, 0x05, 0xec, 0xce, 0x24, 0xd8, - 0xd5, 0xa0, 0x29, 0x79, 0x29, 0x64, 0xb8, 0x07, 0x68, 0xdf, 0xf5, 0xc9, 0x43, 0xd7, 0x7f, 0x6e, - 0xfa, 0xd6, 0xf9, 0xf2, 0x05, 0x04, 0xd3, 0xe2, 0xbd, 0x50, 0xe5, 0xc6, 0x8c, 0xce, 0xbe, 0xb5, - 0x77, 0x60, 0x29, 0x41, 0xaf, 0x70, 0xe2, 0x0f, 0xa1, 0xce, 0xdc, 0xa8, 0x88, 0x29, 0x6f, 0xa8, - 0x35, 0xd5, 0x49, 0xbe, 0x56, 0xeb, 0xc2, 0x22, 0x3d, 0x27, 0x19, 0x3c, 0xda, 0x78, 0x3f, 0x4c, - 0x45, 0x5e, 0xcb, 0xc9, 0xf1, 0xa9, 0xa8, 0xeb, 0x1f, 0x4a, 0x30, 0xc3, 0xe0, 0x99, 0x53, 0xed, - 0x22, 0xd4, 0x7c, 0xec, 0xb9, 0x06, 0x31, 0x87, 0xd1, 0x13, 0x2c, 0x0a, 0x38, 0x30, 0x87, 0x01, - 0x7b, 0x41, 0x46, 0x3b, 0x2d, 0x7b, 0x88, 0x03, 0x22, 0xdf, 0x61, 0xd5, 0x29, 0x6c, 0x9b, 0x83, - 0xa8, 0x48, 0x02, 0xfb, 0x8f, 0x78, 0x48, 0x35, 0xad, 0xb3, 0x6f, 0x74, 0x9d, 0xbf, 0x6e, 0x98, - 0x50, 0x00, 0x63, 0x4f, 0x1e, 0x3a, 0x50, 0x4d, 0xd5, 0xbc, 0xa2, 0xb6, 0xf6, 0x09, 0x20, 0x75, - 0xcd, 0x42, 0xa8, 0x6f, 0xc3, 0x2c, 0x13, 0x89, 0x8c, 0x09, 0x9a, 0xc9, 0x45, 0xeb, 0xa2, 0x57, - 0xfb, 0x0c, 0x10, 0x97, 0x62, 0x22, 0x0e, 0x38, 0xbb, 0xc4, 0x3f, 0x86, 0xa5, 0xc4, 0xf8, 0xe8, - 0x32, 0x3b, 0x41, 0x20, 0x3d, 0xbb, 0x18, 0xfc, 0xeb, 0x12, 0x40, 0x37, 0x24, 0x27, 0xa2, 0xd8, - 0xa2, 0xae, 0xb2, 0x94, 0x5c, 0x25, 0xed, 0xf3, 0xcc, 0x20, 0x78, 0xee, 0xfa, 0x32, 0xd0, 0x8d, - 0xda, 0xac, 0x50, 0x12, 0x92, 0x13, 0x59, 0xe0, 0xa5, 0xdf, 0xe8, 0x3a, 0x34, 0xf9, 0xcb, 0x39, - 0xc3, 0xb4, 0x2c, 0x1f, 0x07, 0x81, 0xa8, 0xf4, 0x36, 0x38, 0xb4, 0xcb, 0x81, 0x14, 0xcd, 0xb6, - 0xb0, 0x43, 0x6c, 0x72, 0x6a, 0x10, 0xf7, 0x1b, 0xec, 0x88, 0x10, 0xb6, 0x21, 0xa1, 0x07, 0x14, - 0x48, 0xd1, 0x7c, 0x3c, 0xb4, 0x03, 0xe2, 0x4b, 0x34, 0x59, 0x79, 0x14, 0x50, 0x86, 0xa6, 0xfd, - 0xb2, 0x04, 0xad, 0xfd, 0x70, 0x34, 0xe2, 0x8b, 0x3c, 0xaf, 0x2c, 0xd1, 0x3b, 0x62, 0x1d, 0xe5, - 0x94, 0x35, 0xc4, 0x22, 0x12, 0x8b, 0xfb, 0xee, 0xa9, 0xf5, 0x5d, 0x58, 0x54, 0x18, 0x15, 0x4a, - 0x4b, 0x44, 0x2a, 0xa5, 0x64, 0xa4, 0x42, 0x0d, 0x85, 0x67, 0x93, 0xaf, 0xb7, 0x38, 0xed, 0x02, - 0x2c, 0x25, 0xc6, 0x8b, 0x63, 0xe9, 0x16, 0x34, 0xc4, 0x85, 0xb2, 0x30, 0x82, 0x35, 0xa8, 0x52, - 0xf7, 0x32, 0xb0, 0x2d, 0x59, 0xd9, 0x9f, 0xf3, 0x5c, 0x6b, 0xcb, 0xb6, 0x7c, 0xad, 0x07, 0x0d, - 0x9d, 0x93, 0x17, 0xb8, 0x9f, 0x42, 0x53, 0x5c, 0x3f, 0x1b, 0x89, 0x67, 0x18, 0x71, 0x19, 0x3a, - 0x41, 0x5b, 0x6f, 0x38, 0x6a, 0x53, 0xfb, 0x39, 0x74, 0xf8, 0xb1, 0x99, 0xa0, 0x2a, 0x97, 0xf6, - 0x29, 0xc8, 0x97, 0x9d, 0x45, 0xc4, 0x93, 0xc3, 0x1a, 0xbe, 0xda, 0xd4, 0x2e, 0xc3, 0xc5, 0x5c, - 0xe2, 0x62, 0xdd, 0x1e, 0xb4, 0xe2, 0x0e, 0xcb, 0x96, 0x17, 0x1a, 0xec, 0xa2, 0xa2, 0xa4, 0x5c, - 0x54, 0xac, 0x44, 0xa1, 0x48, 0x59, 0xfa, 0x6f, 0x16, 0x72, 0xc7, 0x81, 0x63, 0xa5, 0x28, 0x70, - 0x9c, 0x4e, 0x04, 0x8e, 0xda, 0x97, 0x91, 0xf4, 0x44, 0xd4, 0xfe, 0x11, 0x4b, 0x1d, 0xf8, 0xdc, - 0xd2, 0x4d, 0xac, 0xe5, 0x2c, 0x8e, 0x63, 0xe8, 0x0a, 0xb2, 0xb6, 0x00, 0x8d, 0x84, 0xc3, 0xd0, - 0x1e, 0x40, 0x33, 0xe5, 0x01, 0xee, 0xa4, 0x62, 0xa8, 0x8c, 0xd8, 0x52, 0x11, 0xd4, 0xb2, 0x70, - 0x44, 0x0f, 0x83, 0x5d, 0xe7, 0xd8, 0x95, 0x74, 0xaf, 0x41, 0xfd, 0xb0, 0xe8, 0xa1, 0xe3, 0xb4, - 0xbc, 0x29, 0x7b, 0x07, 0x16, 0xfb, 0xc4, 0xf5, 0xcd, 0x21, 0xde, 0x65, 0xbb, 0xf6, 0xd8, 0xe6, - 0x37, 0x41, 0x61, 0x18, 0xf9, 0x6f, 0xf6, 0xad, 0xfd, 0x7b, 0x09, 0x16, 0x1e, 0xda, 0x23, 0x1c, - 0x9c, 0x06, 0x04, 0x8f, 0x0f, 0x59, 0x3c, 0x7d, 0x09, 0x6a, 0x94, 0x9b, 0x80, 0x98, 0x63, 0x4f, - 0xde, 0xa4, 0x45, 0x00, 0x2a, 0xa3, 0x80, 0x93, 0x96, 0x19, 0xb6, 0x9a, 0xcb, 0x64, 0x66, 0xa5, - 0xf9, 0x85, 0x00, 0xa1, 0xf7, 0x01, 0xc2, 0x00, 0x5b, 0xe2, 0xf6, 0xac, 0x92, 0x3a, 0x7a, 0x0e, - 0xd5, 0x3b, 0x0e, 0x8a, 0xc7, 0xaf, 0xd2, 0x3e, 0x80, 0xba, 0xed, 0xb8, 0x16, 0x66, 0x77, 0x1c, - 0x96, 0xc8, 0xbe, 0xf3, 0x47, 0x01, 0x47, 0x3c, 0x0c, 0xb0, 0xa5, 0xfd, 0x81, 0xf0, 0xc2, 0x52, - 0x78, 0x42, 0x07, 0x3b, 0xb0, 0xc8, 0x37, 0xf4, 0x71, 0xb4, 0x68, 0xa9, 0xe8, 0x38, 0xa4, 0x4d, - 0x09, 0x44, 0x6f, 0xd9, 0xe2, 0x54, 0x94, 0x23, 0xb4, 0xfb, 0x70, 0x21, 0x11, 0xf7, 0x9e, 0x27, - 0x5d, 0x7c, 0x94, 0xca, 0x35, 0x63, 0x03, 0x11, 0xc9, 0x9e, 0xb4, 0x8f, 0x82, 0x64, 0x2f, 0xe0, - 0xc9, 0x5e, 0xa0, 0xe9, 0xb0, 0x96, 0x48, 0x81, 0x13, 0x8c, 0x7c, 0x90, 0x3a, 0xe2, 0x2f, 0x17, - 0x10, 0x4b, 0x9d, 0xf5, 0xff, 0x53, 0x82, 0xe5, 0x3c, 0x84, 0xd7, 0x2c, 0xb6, 0xfc, 0xa4, 0xe0, - 0x4d, 0xc3, 0xdd, 0x89, 0xdc, 0xfc, 0x56, 0xca, 0x52, 0x8f, 0xa1, 0x93, 0x27, 0xbd, 0xac, 0x2a, - 0x2a, 0x67, 0x50, 0xc5, 0xff, 0x96, 0x95, 0xf2, 0x61, 0x97, 0x10, 0xdf, 0x3e, 0x0a, 0xa9, 0xf1, - 0x7e, 0x5f, 0x65, 0x80, 0x07, 0x51, 0x8a, 0xcb, 0xe5, 0x77, 0x23, 0x3b, 0x2a, 0x9e, 0x35, 0x37, - 0xcd, 0xdd, 0x4b, 0xa6, 0xb9, 0xbc, 0x30, 0x78, 0x7b, 0x22, 0x99, 0x37, 0xb6, 0xf6, 0xf3, 0xb2, - 0x04, 0xcd, 0xa4, 0x1e, 0xd0, 0x27, 0x00, 0x66, 0xc4, 0xb9, 0x30, 0xf9, 0x4b, 0x93, 0x56, 0xa7, - 0x2b, 0xf8, 0xe8, 0x1a, 0x54, 0x06, 0x5e, 0x28, 0x34, 0x12, 0xdf, 0x10, 0x6d, 0x79, 0x21, 0x77, - 0x00, 0xb4, 0x97, 0x06, 0xcd, 0xfc, 0xa6, 0x3f, 0xe3, 0xb9, 0x9e, 0x32, 0x30, 0x47, 0x15, 0x38, - 0xe8, 0x73, 0x68, 0x3e, 0xf7, 0x6d, 0x62, 0x1e, 0x8d, 0xb0, 0x31, 0x32, 0x4f, 0xb1, 0x2f, 0x3c, - 0x57, 0xb1, 0x97, 0x69, 0x48, 0xfc, 0x27, 0x14, 0x5d, 0x0b, 0xa1, 0x2a, 0xe7, 0x7f, 0x85, 0x47, - 0x7e, 0x0c, 0xab, 0x21, 0x45, 0x33, 0xd8, 0x6b, 0x03, 0xc7, 0x74, 0x5c, 0x23, 0xc0, 0xf4, 0x68, - 0x92, 0x2f, 0xfc, 0xf2, 0xbd, 0xe5, 0x32, 0x1b, 0xb4, 0xe5, 0xfa, 0xb8, 0x67, 0x3a, 0x6e, 0x9f, - 0x8f, 0xd0, 0xc6, 0x50, 0x57, 0x96, 0xf3, 0x8a, 0x99, 0x1f, 0xc0, 0xa2, 0xbc, 0x7b, 0x0b, 0x30, - 0x11, 0x7e, 0x7d, 0xd2, 0x9c, 0x0b, 0x02, 0xbd, 0x8f, 0x09, 0xf3, 0xee, 0xb7, 0x2e, 0x41, 0x55, - 0xfe, 0xc9, 0x02, 0xcd, 0x41, 0xe5, 0x60, 0x6b, 0xbf, 0x35, 0x45, 0x3f, 0x0e, 0xb7, 0xf7, 0x5b, - 0xa5, 0x5b, 0x63, 0x68, 0xa5, 0xff, 0x60, 0x80, 0x56, 0x61, 0x69, 0x5f, 0xdf, 0xdb, 0xef, 0x3e, - 0xea, 0x1e, 0xec, 0xee, 0xf5, 0x8c, 0x7d, 0x7d, 0xf7, 0xab, 0xee, 0xc1, 0x4e, 0x6b, 0x0a, 0x5d, - 0x85, 0xcb, 0x6a, 0xc7, 0xef, 0xef, 0xf5, 0x0f, 0x8c, 0x83, 0x3d, 0x63, 0x6b, 0xaf, 0x77, 0xd0, - 0xdd, 0xed, 0xed, 0xe8, 0xad, 0x12, 0xba, 0x0c, 0x6b, 0x2a, 0xca, 0x17, 0xbb, 0xdb, 0xbb, 0xfa, - 0xce, 0x16, 0xfd, 0xee, 0x3e, 0x69, 0x95, 0x6f, 0xdd, 0x87, 0x85, 0xd4, 0x83, 0x21, 0xb4, 0x08, - 0x8d, 0x7e, 0xb7, 0xb7, 0xfd, 0xc5, 0xde, 0x4f, 0x0d, 0x7d, 0xa7, 0xbb, 0xfd, 0xb3, 0xd6, 0x14, - 0x5a, 0x86, 0x96, 0x04, 0xf5, 0xf6, 0x0e, 0x38, 0xb4, 0x74, 0xeb, 0x9b, 0x94, 0x49, 0x62, 0x74, - 0x01, 0x16, 0xa3, 0xb9, 0x8d, 0x2d, 0x7d, 0xa7, 0x7b, 0xb0, 0xb3, 0xdd, 0x9a, 0x4a, 0x82, 0xf5, - 0xc3, 0x5e, 0x6f, 0xb7, 0xf7, 0xa8, 0x55, 0xa2, 0x54, 0x63, 0xf0, 0xce, 0x4f, 0x77, 0x29, 0x72, - 0x39, 0x89, 0x7c, 0xd8, 0x7b, 0xdc, 0xdb, 0xfb, 0x49, 0xaf, 0x55, 0xd9, 0xfc, 0xb7, 0x06, 0x34, - 0x65, 0xcc, 0x80, 0x7d, 0x76, 0x21, 0xfc, 0x19, 0xcc, 0xc9, 0xbf, 0xdb, 0xc4, 0xce, 0x2a, 0xf9, - 0xdf, 0xa0, 0x4e, 0x3b, 0xdb, 0x21, 0x62, 0xaf, 0x29, 0xb4, 0xcf, 0x62, 0x21, 0xe5, 0x71, 0xd6, - 0x65, 0x35, 0x3a, 0xc9, 0xbc, 0xfe, 0xea, 0xac, 0x17, 0x75, 0x47, 0x14, 0xfb, 0x34, 0x00, 0x52, - 0x1f, 0xd6, 0xa2, 0x75, 0x35, 0x4c, 0xc8, 0x3e, 0xd8, 0xed, 0x5c, 0x29, 0xec, 0x8f, 0x88, 0xfe, - 0x0c, 0x5a, 0xe9, 0x27, 0xb5, 0x28, 0x2e, 0x0a, 0x15, 0x3c, 0xd7, 0xed, 0x5c, 0x9d, 0x80, 0xa1, - 0x92, 0xce, 0x3c, 0x4b, 0xdd, 0x28, 0x7e, 0x58, 0x98, 0x21, 0x5d, 0xf4, 0x5a, 0x91, 0x8b, 0x22, - 0xf9, 0xa8, 0x0a, 0xa9, 0x8f, 0x41, 0x73, 0x1e, 0xd7, 0x29, 0xa2, 0xc8, 0x7f, 0x8d, 0xa5, 0x4d, - 0xa1, 0xaf, 0x60, 0x21, 0x75, 0x17, 0x88, 0xe2, 0x51, 0xf9, 0x37, 0x9b, 0x9d, 0x8d, 0x62, 0x84, - 0xa4, 0xde, 0xd4, 0x9b, 0xbe, 0x84, 0xde, 0x72, 0xae, 0x0f, 0x13, 0x7a, 0xcb, 0xbd, 0x22, 0x64, - 0xe6, 0x95, 0xb8, 0xcf, 0x53, 0xcc, 0x2b, 0xef, 0xf2, 0xb0, 0xb3, 0x5e, 0xd4, 0xad, 0x2e, 0x3f, - 0x75, 0x97, 0xa7, 0x2c, 0x3f, 0xff, 0x8a, 0xb0, 0xb3, 0x51, 0x8c, 0x90, 0xd6, 0x55, 0x7c, 0xb1, - 0x90, 0xd2, 0x55, 0xe6, 0x1e, 0x2b, 0xa5, 0xab, 0xec, 0x8d, 0x84, 0xd0, 0x55, 0xea, 0x86, 0xe0, - 0x4a, 0x61, 0x05, 0x35, 0xab, 0xab, 0xfc, 0xa2, 0xac, 0x36, 0x85, 0xbe, 0x85, 0x76, 0x51, 0x99, - 0x13, 0xc5, 0x31, 0xc2, 0x2b, 0xea, 0xb0, 0x9d, 0x9b, 0x67, 0xc0, 0x8c, 0xa6, 0xec, 0x42, 0x55, - 0xd6, 0x34, 0x51, 0xec, 0x50, 0x52, 0x85, 0xd4, 0xce, 0x5a, 0x4e, 0x4f, 0x44, 0xe2, 0x03, 0x98, - 0xa6, 0x50, 0xb4, 0x9c, 0x40, 0x92, 0x43, 0x2f, 0xa4, 0xa0, 0xd1, 0xb0, 0x8f, 0x61, 0x96, 0x17, - 0xe8, 0x50, 0x9c, 0x39, 0x25, 0xaa, 0x87, 0x9d, 0xd5, 0x0c, 0x3c, 0x1a, 0xfc, 0x25, 0xff, 0xd7, - 0x9f, 0xa8, 0xb4, 0xa1, 0x8b, 0x89, 0x3f, 0x88, 0x24, 0xeb, 0x79, 0x9d, 0x4b, 0xf9, 0x9d, 0xaa, - 0x89, 0xa4, 0xc2, 0x8f, 0xf5, 0xa2, 0xf8, 0x30, 0x63, 0x22, 0xf9, 0xf1, 0xa6, 0x36, 0x85, 0x0c, - 0x5e, 0xb4, 0x4a, 0x11, 0xd6, 0xf2, 0x6d, 0x2b, 0x41, 0xfc, 0xda, 0x44, 0x9c, 0x68, 0x82, 0x23, - 0x58, 0xca, 0x49, 0xbf, 0xd1, 0xb5, 0x94, 0xf2, 0xf3, 0x32, 0xff, 0xce, 0x5b, 0x93, 0x91, 0x54, - 0x15, 0x09, 0xf3, 0x5e, 0x51, 0x7d, 0x82, 0x62, 0xd5, 0xab, 0x19, 0xb8, 0x1c, 0xbc, 0xf9, 0xa7, - 0x15, 0x98, 0xe7, 0x45, 0x12, 0x71, 0xa6, 0x3d, 0x02, 0x88, 0xeb, 0x78, 0xa8, 0x93, 0x58, 0x66, - 0xa2, 0xa0, 0xd9, 0xb9, 0x98, 0xdb, 0xa7, 0x2a, 0x5f, 0x29, 0xc9, 0x29, 0xca, 0xcf, 0x16, 0xfa, - 0x14, 0xe5, 0xe7, 0x54, 0xf1, 0xb4, 0x29, 0xb4, 0x0d, 0xb5, 0xa8, 0x4e, 0x84, 0x94, 0xf2, 0x52, - 0xaa, 0xc8, 0xd5, 0xe9, 0xe4, 0x75, 0xa9, 0x1c, 0x29, 0xb5, 0x1f, 0x85, 0xa3, 0x6c, 0x45, 0x49, - 0xe1, 0x28, 0xaf, 0x5c, 0x14, 0xaf, 0x8e, 0xa7, 0xba, 0xe9, 0xd5, 0x25, 0xaa, 0x07, 0xe9, 0xd5, - 0x25, 0xb3, 0x63, 0x6d, 0xea, 0x8b, 0x4b, 0xbf, 0xfa, 0xcd, 0x7a, 0xe9, 0x3f, 0x7e, 0xb3, 0x3e, - 0xf5, 0x27, 0x2f, 0xd7, 0x4b, 0xbf, 0x7a, 0xb9, 0x5e, 0xfa, 0xd7, 0x97, 0xeb, 0xa5, 0xff, 0x7a, - 0xb9, 0x5e, 0xfa, 0xc5, 0x7f, 0xaf, 0x4f, 0x1d, 0xcd, 0xb2, 0xbf, 0xc1, 0xbe, 0xff, 0xff, 0x01, - 0x00, 0x00, 0xff, 0xff, 0x61, 0x12, 0x07, 0xa4, 0xba, 0x3c, 0x00, 0x00, + // 4392 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x5b, 0xcd, 0x6f, 0x1c, 0x47, + 0x76, 0xe7, 0xcc, 0xf0, 0x63, 0xe6, 0x0d, 0x67, 0x38, 0x2c, 0x51, 0xe4, 0x70, 0x24, 0x51, 0x54, + 0xcb, 0x92, 0x25, 0xed, 0x4a, 0x96, 0xe9, 0xb5, 0x14, 0xcb, 0xb6, 0xe4, 0x31, 0x49, 0x29, 0xb4, + 0x24, 0x92, 0xdb, 0x43, 0x7a, 0x6d, 0x6c, 0x80, 0x4e, 0x73, 0xba, 0x38, 0x6c, 0x7b, 0xa6, 0xab, + 0xdd, 0x1f, 0x92, 0x18, 0xe4, 0x90, 0x5c, 0x82, 0x20, 0x40, 0x80, 0xcd, 0x31, 0x39, 0xe5, 0x10, + 0x60, 0x91, 0x4b, 0x10, 0xe4, 0x10, 0xe4, 0x0f, 0x08, 0x92, 0xbd, 0x2c, 0x10, 0x20, 0x40, 0x90, + 0xdc, 0xb2, 0xca, 0x21, 0x87, 0x00, 0xf9, 0x1b, 0x16, 0xf5, 0xd5, 0x5d, 0xfd, 0x35, 0x22, 0x65, + 0xef, 0x5a, 0xa7, 0xe9, 0x7a, 0xf5, 0xea, 0xd5, 0xab, 0xaa, 0x57, 0xaf, 0xde, 0xfb, 0x55, 0x0d, + 0xd4, 0x4c, 0xd7, 0xbe, 0xe5, 0x7a, 0x24, 0x20, 0x68, 0xc6, 0x0b, 0x9d, 0xc0, 0x1e, 0xe1, 0xce, + 0xcd, 0x81, 0x1d, 0x1c, 0x85, 0x07, 0xb7, 0xfa, 0x64, 0xf4, 0xce, 0x80, 0x0c, 0xc8, 0x3b, 0xac, + 0xfe, 0x20, 0x3c, 0x64, 0x25, 0x56, 0x60, 0x5f, 0xbc, 0x9d, 0x76, 0x03, 0x9a, 0x9f, 0x63, 0xcf, + 0xb7, 0x89, 0xa3, 0xe3, 0x6f, 0x42, 0xec, 0x07, 0xa8, 0x0d, 0x33, 0xcf, 0x38, 0xa5, 0x5d, 0x5a, + 0x2d, 0x5d, 0xab, 0xe9, 0xb2, 0xa8, 0xfd, 0xbc, 0x04, 0x73, 0x11, 0xb3, 0xef, 0x12, 0xc7, 0xc7, + 0xc5, 0xdc, 0xe8, 0x12, 0xcc, 0x0a, 0x9d, 0x0c, 0xc7, 0x1c, 0xe1, 0x76, 0x99, 0x55, 0xd7, 0x05, + 0x6d, 0xdb, 0x1c, 0x61, 0xf4, 0x36, 0xcc, 0x49, 0x16, 0x29, 0xa4, 0xc2, 0xb8, 0x9a, 0x82, 0x2c, + 0x7a, 0x43, 0xb7, 0xe0, 0x8c, 0x64, 0x34, 0x5d, 0x3b, 0x62, 0x9e, 0x64, 0xcc, 0xf3, 0xa2, 0xaa, + 0xeb, 0xda, 0x82, 0x5f, 0xfb, 0x29, 0xd4, 0x36, 0xb6, 0x7b, 0xeb, 0xc4, 0x39, 0xb4, 0x07, 0x54, + 0x45, 0x1f, 0x7b, 0xb4, 0x4d, 0xbb, 0xb4, 0x5a, 0xa1, 0x2a, 0x8a, 0x22, 0xea, 0x40, 0xd5, 0xc7, + 0xa6, 0xd7, 0x3f, 0xc2, 0x7e, 0xbb, 0xcc, 0xaa, 0xa2, 0x32, 0x6d, 0x45, 0xdc, 0xc0, 0x26, 0x8e, + 0xdf, 0xae, 0xf0, 0x56, 0xa2, 0xa8, 0xfd, 0x65, 0x09, 0xea, 0xbb, 0xc4, 0x0b, 0x9e, 0x9a, 0xae, + 0x6b, 0x3b, 0x03, 0x74, 0x13, 0xaa, 0x6c, 0x2e, 0xfb, 0x64, 0xc8, 0xe6, 0xa0, 0xb9, 0x36, 0x7f, + 0x4b, 0xa8, 0x74, 0x6b, 0x57, 0x54, 0xe8, 0x11, 0x0b, 0xba, 0x02, 0xcd, 0x3e, 0x71, 0x02, 0xd3, + 0x76, 0xb0, 0x67, 0xb8, 0xc4, 0x0b, 0xd8, 0xcc, 0x4c, 0xe9, 0x8d, 0x88, 0x4a, 0x85, 0xa3, 0x73, + 0x50, 0x3b, 0x22, 0x7e, 0xc0, 0x39, 0x2a, 0x8c, 0xa3, 0x4a, 0x09, 0xac, 0x72, 0x09, 0x66, 0x58, + 0xa5, 0xed, 0x8a, 0x39, 0x98, 0xa6, 0xc5, 0x2d, 0x57, 0xfb, 0x65, 0x09, 0xa6, 0x9e, 0x92, 0xd0, + 0x09, 0x52, 0xdd, 0x98, 0xc1, 0x91, 0x58, 0x1f, 0xa5, 0x1b, 0x33, 0x38, 0x8a, 0xbb, 0xa1, 0x1c, + 0x7c, 0x89, 0x78, 0x37, 0xb4, 0xb2, 0x03, 0x55, 0x0f, 0x9b, 0x16, 0x71, 0x86, 0xc7, 0x4c, 0x85, + 0xaa, 0x1e, 0x95, 0xe9, 0xda, 0xf9, 0x78, 0x68, 0x3b, 0xe1, 0x0b, 0xc3, 0xc3, 0x43, 0xf3, 0x00, + 0x0f, 0x99, 0x2a, 0x55, 0xbd, 0x29, 0xc8, 0x3a, 0xa7, 0xa2, 0x0f, 0xa1, 0xee, 0x7a, 0xc4, 0x35, + 0x07, 0x26, 0x9d, 0xbe, 0xf6, 0x14, 0x9b, 0xa1, 0xe5, 0x68, 0x86, 0x98, 0xb6, 0xbb, 0x31, 0x83, + 0xae, 0x72, 0x6b, 0x5f, 0xc1, 0x1c, 0xb5, 0x14, 0xdf, 0x35, 0xfb, 0x78, 0x87, 0xcd, 0x3f, 0xb5, + 0x2b, 0xa6, 0xb1, 0x83, 0x83, 0xe7, 0xc4, 0xfb, 0x9a, 0x0d, 0xab, 0xaa, 0xd7, 0x29, 0x6d, 0x9b, + 0x93, 0xd0, 0x32, 0x54, 0xf9, 0xa0, 0x6c, 0x8b, 0x8d, 0xa9, 0xaa, 0xb3, 0xe9, 0xda, 0xb5, 0xad, + 0xa8, 0xca, 0x76, 0xfb, 0x62, 0x48, 0x33, 0x7c, 0xea, 0xfa, 0x9a, 0x06, 0xb0, 0xe5, 0x04, 0x77, + 0x7e, 0xf4, 0xb9, 0x39, 0x0c, 0x31, 0x5a, 0x80, 0xa9, 0x67, 0xf4, 0x83, 0xc9, 0xaf, 0xe8, 0xbc, + 0xa0, 0xfd, 0x49, 0x05, 0xce, 0x3d, 0xa1, 0xa3, 0xeb, 0x99, 0x8e, 0x75, 0x40, 0x5e, 0xf4, 0x70, + 0x3f, 0xf4, 0xec, 0xe0, 0x78, 0x9d, 0x38, 0x01, 0x7e, 0x11, 0xa0, 0x4d, 0x98, 0x77, 0xa4, 0xbe, + 0x86, 0xb4, 0x1f, 0x2a, 0xa1, 0xbe, 0xd6, 0x8e, 0x86, 0x9c, 0x1a, 0x91, 0xde, 0x72, 0x92, 0x04, + 0x1f, 0x3d, 0x88, 0x27, 0x57, 0x0a, 0x29, 0x33, 0x21, 0x8b, 0x91, 0x90, 0xde, 0x26, 0xd3, 0x43, + 0x88, 0x90, 0x93, 0x2e, 0x05, 0xbc, 0x07, 0x74, 0xa3, 0x19, 0xa6, 0x6f, 0x84, 0x3e, 0xf6, 0xd8, + 0x48, 0xeb, 0x6b, 0x67, 0xa2, 0xc6, 0xf1, 0x38, 0xf5, 0x9a, 0x17, 0x3a, 0x5d, 0x7f, 0xdf, 0xc7, + 0x1e, 0xdb, 0x8e, 0x62, 0x79, 0x0d, 0x8f, 0x90, 0xe0, 0xd0, 0x97, 0x4b, 0x2a, 0xc9, 0x3a, 0xa3, + 0xa2, 0x77, 0xe0, 0x8c, 0x1f, 0xba, 0xee, 0x10, 0x8f, 0xb0, 0x13, 0x98, 0x43, 0x63, 0xe0, 0x91, + 0xd0, 0xf5, 0xdb, 0x53, 0xab, 0x95, 0x6b, 0x15, 0x1d, 0xa9, 0x55, 0x8f, 0x58, 0x0d, 0x5a, 0x01, + 0x70, 0x3d, 0xfb, 0x99, 0x3d, 0xc4, 0x03, 0x6c, 0xb5, 0xa7, 0x99, 0x50, 0x85, 0x82, 0x6e, 0xc3, + 0x82, 0x8f, 0xfb, 0x7d, 0x32, 0x72, 0x0d, 0xd7, 0x23, 0x87, 0xf6, 0x10, 0x73, 0x83, 0x9c, 0x61, + 0x06, 0x89, 0x44, 0xdd, 0x2e, 0xaf, 0xa2, 0xa6, 0xa9, 0xfd, 0xac, 0x0c, 0x67, 0xd9, 0x04, 0xec, + 0x12, 0x4b, 0xac, 0x85, 0xd8, 0xee, 0x97, 0xa1, 0xd1, 0x67, 0x0a, 0x19, 0xae, 0xe9, 0x61, 0x27, + 0x10, 0x76, 0x3f, 0xcb, 0x89, 0xbb, 0x8c, 0x86, 0x76, 0xa0, 0xe5, 0x8b, 0xa5, 0x33, 0xfa, 0x7c, + 0xed, 0xc4, 0x0c, 0xbf, 0x15, 0x4d, 0xd2, 0x98, 0x75, 0xd6, 0xe7, 0xfc, 0xcc, 0xc2, 0xcf, 0xf8, + 0xc7, 0x7e, 0x3f, 0x18, 0x72, 0x77, 0x51, 0x5f, 0xfb, 0x41, 0x52, 0x4e, 0x5a, 0xcd, 0x5b, 0x3d, + 0xce, 0xbd, 0xe9, 0x04, 0xde, 0xb1, 0x2e, 0xdb, 0x76, 0xee, 0xc1, 0xac, 0x5a, 0x81, 0x5a, 0x50, + 0xf9, 0x1a, 0x1f, 0x8b, 0x21, 0xd0, 0xcf, 0xd8, 0x2e, 0xf9, 0x66, 0xe5, 0x85, 0x7b, 0xe5, 0xdf, + 0x29, 0x69, 0x1e, 0xa0, 0xb8, 0x97, 0xa7, 0x38, 0x30, 0x2d, 0x33, 0x30, 0x11, 0x82, 0x49, 0xe6, + 0x7e, 0xb9, 0x08, 0xf6, 0x4d, 0xa5, 0x86, 0x62, 0x6b, 0xd4, 0x74, 0xfa, 0x89, 0xce, 0x43, 0x2d, + 0x32, 0x42, 0xe1, 0x83, 0x63, 0x02, 0xf5, 0x85, 0x66, 0x10, 0xe0, 0x91, 0x1b, 0x30, 0x83, 0x68, + 0xe8, 0xb2, 0xa8, 0xfd, 0xd3, 0x24, 0xb4, 0x32, 0x2b, 0x70, 0x17, 0xaa, 0x23, 0xd1, 0xbd, 0xb0, + 0xfd, 0x73, 0xb1, 0x43, 0xcc, 0x68, 0xa8, 0x47, 0xcc, 0xd4, 0xdf, 0xd0, 0xcd, 0xa8, 0x1c, 0x17, + 0x51, 0x99, 0x2e, 0xeb, 0x90, 0x0c, 0x0c, 0xcb, 0xf6, 0x70, 0x3f, 0x20, 0xde, 0xb1, 0xd0, 0x72, + 0x76, 0x48, 0x06, 0x1b, 0x92, 0x86, 0xde, 0x05, 0xb0, 0x1c, 0x9f, 0xae, 0xe8, 0xa1, 0x3d, 0x60, + 0xba, 0xd6, 0xd7, 0x50, 0xd4, 0x77, 0x74, 0x24, 0xe8, 0x35, 0xcb, 0xf1, 0x85, 0xb2, 0x1f, 0x40, + 0x83, 0xba, 0x58, 0x63, 0xc4, 0xbd, 0x39, 0xb7, 0xe2, 0xfa, 0xda, 0x82, 0xa2, 0x71, 0xe4, 0xea, + 0xf5, 0x59, 0x37, 0x2e, 0xf8, 0xe8, 0x63, 0x98, 0x66, 0x2e, 0xce, 0x6f, 0x4f, 0xb3, 0x36, 0x57, + 0x72, 0x46, 0x29, 0x56, 0xfb, 0x09, 0xe3, 0xe3, 0x8b, 0x2d, 0x1a, 0xa1, 0x27, 0x50, 0x37, 0x1d, + 0x87, 0x04, 0x26, 0xdf, 0xe0, 0x33, 0x4c, 0xc6, 0x8d, 0x62, 0x19, 0xdd, 0x98, 0x99, 0x0b, 0x52, + 0x9b, 0xa3, 0x1f, 0xc1, 0x14, 0xf3, 0x00, 0xed, 0x2a, 0x1b, 0xf5, 0xca, 0x78, 0xf3, 0xd3, 0x39, + 0x73, 0xe7, 0x03, 0xa8, 0x2b, 0xaa, 0x9d, 0xc6, 0xdc, 0x3a, 0xf7, 0xa1, 0x95, 0xd6, 0xe8, 0x54, + 0xe6, 0xba, 0x05, 0x0b, 0x7a, 0xe8, 0xc4, 0x8a, 0xc9, 0xf8, 0xe3, 0x5d, 0x98, 0x16, 0xeb, 0xc7, + 0x6d, 0x67, 0xb9, 0x70, 0x46, 0x74, 0xc1, 0xa8, 0x7d, 0x0c, 0x67, 0x53, 0xa2, 0x44, 0x74, 0xf2, + 0x16, 0x34, 0x5d, 0x62, 0x19, 0x3e, 0x27, 0x1b, 0xb6, 0x25, 0x9d, 0x81, 0x1b, 0xf1, 0x6e, 0x59, + 0xb4, 0x79, 0x2f, 0x20, 0x6e, 0x56, 0x95, 0x93, 0x35, 0x6f, 0xc3, 0x62, 0xba, 0x39, 0xef, 0x5e, + 0x7b, 0x00, 0x4b, 0x3a, 0x1e, 0x91, 0x67, 0xf8, 0x75, 0x45, 0x77, 0xa0, 0x9d, 0x15, 0x20, 0x84, + 0x7f, 0x09, 0x4b, 0x31, 0xb5, 0x17, 0x98, 0x41, 0xe8, 0x9f, 0x4a, 0xb8, 0x08, 0xdd, 0x0e, 0x88, + 0x8f, 0xe5, 0x21, 0x29, 0x8a, 0xda, 0x75, 0x55, 0xb4, 0x38, 0x54, 0x79, 0x0f, 0xa8, 0x09, 0x65, + 0xdb, 0x15, 0xe2, 0xca, 0xb6, 0xab, 0x3d, 0x80, 0x5a, 0x74, 0x9c, 0xa1, 0xb5, 0x38, 0x66, 0x2a, + 0xbf, 0xe2, 0xcc, 0x8b, 0xa2, 0xa9, 0xc7, 0x19, 0x3f, 0x2e, 0x7a, 0x5a, 0x03, 0x88, 0x3c, 0x90, + 0x3c, 0x43, 0x51, 0x56, 0x9e, 0xae, 0x70, 0x69, 0x7f, 0x93, 0x70, 0x47, 0x8a, 0xca, 0x56, 0xa4, + 0xb2, 0x95, 0x70, 0x4f, 0xe5, 0xd3, 0xb8, 0xa7, 0x5b, 0x30, 0xe5, 0x07, 0x66, 0xc0, 0x1d, 0x64, + 0x53, 0x19, 0x5c, 0xb2, 0x4b, 0xac, 0x73, 0x36, 0x74, 0x01, 0xa0, 0xef, 0x61, 0x33, 0xc0, 0x96, + 0x61, 0x72, 0xcf, 0x59, 0xd1, 0x6b, 0x82, 0xd2, 0x0d, 0xd0, 0x3d, 0x98, 0x91, 0x31, 0xcc, 0x14, + 0x53, 0x63, 0x35, 0x47, 0x60, 0x62, 0xf6, 0x75, 0xd9, 0x20, 0xde, 0xed, 0xd3, 0xe3, 0x77, 0xbb, + 0x68, 0xc7, 0x99, 0x15, 0x87, 0x35, 0x53, 0xe8, 0xb0, 0x78, 0x8b, 0x93, 0x38, 0xac, 0x6a, 0xa1, + 0xc3, 0x12, 0x32, 0xc6, 0x3a, 0xac, 0xef, 0xd3, 0xf5, 0xfc, 0x6b, 0x09, 0xda, 0xd9, 0xbd, 0x23, + 0x7c, 0xc6, 0xbb, 0x30, 0xed, 0x33, 0xca, 0x18, 0xff, 0x23, 0x9a, 0x08, 0x46, 0xf4, 0x00, 0x26, + 0x6d, 0xe7, 0x90, 0xb0, 0x1c, 0x42, 0x3d, 0xf9, 0x8b, 0xfa, 0xb8, 0xb5, 0xe5, 0x1c, 0x12, 0x3e, + 0x25, 0xac, 0x61, 0xe7, 0x2e, 0xd4, 0x22, 0xd2, 0xa9, 0x46, 0xf2, 0x10, 0x16, 0x52, 0xc6, 0xc7, + 0xa3, 0xd7, 0xc8, 0x54, 0x4b, 0x27, 0x32, 0x55, 0xed, 0xff, 0x4b, 0xea, 0xc6, 0x79, 0x68, 0x0f, + 0x03, 0xec, 0x65, 0x36, 0xce, 0x7b, 0x52, 0x28, 0xdf, 0x35, 0x17, 0x8a, 0x84, 0xf2, 0xc0, 0x52, + 0x6c, 0x82, 0x1e, 0x34, 0x99, 0xf9, 0x18, 0x3e, 0x1e, 0xb2, 0x53, 0x5a, 0xc4, 0x47, 0x3f, 0xcc, + 0x69, 0xcd, 0xfb, 0xe5, 0xb6, 0xd7, 0x13, 0xec, 0x7c, 0x9a, 0x1a, 0x43, 0x95, 0xd6, 0xf9, 0x04, + 0x50, 0x96, 0xe9, 0x54, 0x13, 0xf7, 0x19, 0x75, 0x3b, 0x34, 0x9b, 0xca, 0x39, 0x7e, 0x0e, 0x99, + 0x1a, 0x63, 0x96, 0x9f, 0xeb, 0xa9, 0x0b, 0x46, 0xed, 0xaf, 0x2b, 0x00, 0x71, 0xe5, 0x1b, 0xeb, + 0x6f, 0xee, 0x46, 0xbb, 0x9f, 0x87, 0x38, 0x17, 0x73, 0xe4, 0xe5, 0xee, 0xfb, 0x87, 0xc9, 0x7d, + 0xcf, 0x83, 0x9d, 0xb7, 0xf2, 0x5a, 0xbf, 0xb1, 0x3b, 0x7e, 0x1d, 0x16, 0xd3, 0xcb, 0x2d, 0xb6, + 0xfb, 0x75, 0x98, 0xb2, 0x03, 0x3c, 0xe2, 0xd8, 0x80, 0x9a, 0x23, 0x29, 0xbc, 0x9c, 0x43, 0xbb, + 0x04, 0xb5, 0xad, 0x91, 0x39, 0xc0, 0x3d, 0x17, 0xf7, 0x69, 0x5f, 0x36, 0x2d, 0x88, 0xfe, 0x79, + 0x41, 0x5b, 0x83, 0xea, 0x63, 0x7c, 0xcc, 0xf7, 0xe0, 0x09, 0xf5, 0xd3, 0xfe, 0xbc, 0x0c, 0x4b, + 0xcc, 0x6d, 0xaf, 0xcb, 0xcc, 0x5c, 0xc7, 0x3e, 0x09, 0xbd, 0x3e, 0xf6, 0xd9, 0x92, 0xba, 0xa1, + 0xe1, 0x62, 0xcf, 0x26, 0x96, 0x48, 0x45, 0x6b, 0x7d, 0x37, 0xdc, 0x65, 0x04, 0x9a, 0xbd, 0xd3, + 0xea, 0x6f, 0x42, 0x22, 0x6c, 0xab, 0xa2, 0x57, 0xfb, 0x6e, 0xf8, 0x63, 0x5a, 0x96, 0x6d, 0xfd, + 0x23, 0xd3, 0xc3, 0x3e, 0xb3, 0x21, 0xde, 0xb6, 0xc7, 0x08, 0xe8, 0x5d, 0x38, 0x3b, 0xc2, 0x23, + 0xe2, 0x1d, 0x1b, 0x43, 0x7b, 0x64, 0x07, 0x86, 0xed, 0x18, 0x07, 0xc7, 0x01, 0xf6, 0x85, 0xe1, + 0x20, 0x5e, 0xf9, 0x84, 0xd6, 0x6d, 0x39, 0x9f, 0xd2, 0x1a, 0xa4, 0x41, 0x83, 0x90, 0x91, 0xe1, + 0xf7, 0x89, 0x87, 0x0d, 0xd3, 0xfa, 0x8a, 0x9d, 0x5b, 0x15, 0xbd, 0x4e, 0xc8, 0xa8, 0x47, 0x69, + 0x5d, 0xeb, 0x2b, 0x74, 0x11, 0xea, 0x7d, 0x37, 0xf4, 0x71, 0x60, 0xd0, 0x1f, 0x76, 0x3e, 0xd5, + 0x74, 0xe0, 0xa4, 0x75, 0x37, 0xf4, 0x15, 0x86, 0x11, 0x9d, 0xf6, 0x19, 0x95, 0xe1, 0x29, 0x9d, + 0x66, 0x13, 0x1a, 0x89, 0xe4, 0x96, 0xa6, 0x30, 0x2c, 0x8b, 0x15, 0x29, 0x0c, 0xfd, 0xa6, 0x34, + 0x8f, 0x0c, 0xe5, 0x4c, 0xb2, 0x6f, 0x4a, 0x0b, 0x8e, 0x5d, 0x99, 0xbf, 0xb0, 0x6f, 0x3a, 0xe5, + 0x43, 0xfc, 0x4c, 0x80, 0x13, 0x35, 0x9d, 0x17, 0x34, 0x0b, 0x60, 0xdd, 0x74, 0xcd, 0x03, 0x7b, + 0x68, 0x07, 0xc7, 0xe8, 0x3a, 0xb4, 0x4c, 0xcb, 0x32, 0xfa, 0x92, 0x62, 0x63, 0x89, 0x14, 0xcd, + 0x99, 0x96, 0xb5, 0xae, 0x90, 0xd1, 0x0f, 0x60, 0xde, 0xf2, 0x88, 0x9b, 0xe4, 0xe5, 0xd0, 0x51, + 0x8b, 0x56, 0xa8, 0xcc, 0xda, 0x3f, 0x4e, 0xc2, 0x85, 0xe4, 0xc2, 0xa6, 0xe1, 0x82, 0xbb, 0x30, + 0x9b, 0xea, 0x35, 0x99, 0xa7, 0xc7, 0x4a, 0xea, 0x09, 0xc6, 0x54, 0x42, 0x5d, 0xce, 0x24, 0xd4, + 0xb9, 0x38, 0x44, 0xe5, 0xbb, 0xc0, 0x21, 0x26, 0xbf, 0x0d, 0x0e, 0x31, 0x75, 0x22, 0x1c, 0xe2, + 0x2a, 0x83, 0x05, 0x65, 0x23, 0x96, 0x0d, 0x72, 0x33, 0x6a, 0x44, 0x3c, 0x8e, 0x84, 0x0f, 0x53, + 0x78, 0xc5, 0xcc, 0x69, 0xf0, 0x8a, 0x6a, 0x21, 0x5e, 0x41, 0x2d, 0xc2, 0x75, 0x4d, 0x6f, 0x44, + 0x3c, 0x09, 0x48, 0xb4, 0x6b, 0x4c, 0x85, 0x39, 0x49, 0x17, 0x60, 0x44, 0x21, 0x74, 0x01, 0x45, + 0xd0, 0x05, 0x5a, 0x85, 0x59, 0x87, 0x18, 0x0e, 0x7e, 0x6e, 0xd0, 0x05, 0xf3, 0xdb, 0x75, 0xbe, + 0x7a, 0x0e, 0xd9, 0xc6, 0xcf, 0x77, 0x29, 0x45, 0xfb, 0xdb, 0x12, 0x2c, 0x24, 0x0d, 0x47, 0x24, + 0xab, 0xf7, 0xa1, 0xe6, 0x49, 0xdf, 0x20, 0x8c, 0x65, 0x35, 0x19, 0xfa, 0x65, 0x7d, 0x88, 0x1e, + 0x37, 0x41, 0x3f, 0x2e, 0x84, 0x3d, 0xae, 0x16, 0x88, 0x79, 0x15, 0xf0, 0xa1, 0x75, 0x61, 0x3e, + 0x62, 0x1e, 0x0b, 0x3a, 0x28, 0x20, 0x42, 0x39, 0x09, 0x22, 0x38, 0x30, 0xbd, 0x81, 0x9f, 0xd9, + 0x7d, 0xfc, 0x9d, 0x80, 0x96, 0xab, 0x50, 0x77, 0xb1, 0x37, 0xb2, 0x7d, 0x3f, 0x32, 0xfa, 0x9a, + 0xae, 0x92, 0xb4, 0xff, 0x9a, 0x82, 0xb9, 0xf4, 0xcc, 0xde, 0xc9, 0x60, 0x16, 0x9d, 0x78, 0x17, + 0xa6, 0xc7, 0xa7, 0x9c, 0xd1, 0xd7, 0xe4, 0x31, 0x50, 0x4e, 0x25, 0x28, 0xd1, 0x49, 0x21, 0x8e, + 0x06, 0x3a, 0xfe, 0x3e, 0x19, 0x8d, 0x4c, 0xc7, 0x92, 0x80, 0xb2, 0x28, 0xd2, 0xd9, 0x32, 0xbd, + 0x01, 0xdd, 0x5a, 0x94, 0xcc, 0xbe, 0xa9, 0x97, 0xa4, 0x81, 0xbe, 0xed, 0x30, 0xc8, 0x83, 0x6d, + 0x9c, 0x9a, 0x0e, 0x82, 0xb4, 0x61, 0x7b, 0xe8, 0x0a, 0x4c, 0x62, 0xe7, 0x99, 0x3c, 0x8d, 0x63, + 0xc4, 0x59, 0x1e, 0x3f, 0x3a, 0xab, 0x46, 0x57, 0x61, 0x7a, 0x44, 0x42, 0x27, 0x90, 0x21, 0x7f, + 0x33, 0x09, 0xbc, 0xea, 0xa2, 0x16, 0x5d, 0x87, 0x19, 0x8b, 0xad, 0x81, 0x8c, 0xeb, 0xe7, 0x62, + 0xd8, 0x84, 0xd1, 0x75, 0x59, 0x8f, 0x3e, 0x8a, 0xe2, 0x88, 0x5a, 0x2a, 0x12, 0x48, 0x4d, 0x6a, + 0x6e, 0x30, 0xf1, 0x38, 0x19, 0x4c, 0x00, 0x13, 0x71, 0xbd, 0x50, 0xc4, 0x78, 0xd0, 0x63, 0x19, + 0xaa, 0x43, 0x32, 0xe0, 0x76, 0x50, 0xe7, 0xd7, 0x0f, 0x43, 0x32, 0x60, 0x66, 0xb0, 0x40, 0x83, + 0x27, 0xcb, 0x76, 0xda, 0xb3, 0x6c, 0x7b, 0xf1, 0x02, 0x3d, 0x13, 0xd9, 0x87, 0x41, 0x9c, 0x3e, + 0x6e, 0x37, 0x58, 0x55, 0x8d, 0x51, 0x76, 0x9c, 0x3e, 0x3b, 0xb2, 0x83, 0xe0, 0xb8, 0xdd, 0x64, + 0x74, 0xfa, 0x49, 0x63, 0x5e, 0x9e, 0x68, 0xcd, 0xa5, 0x62, 0xde, 0xbc, 0xfd, 0xf9, 0x06, 0xa0, + 0x2a, 0xff, 0x50, 0x82, 0xc5, 0x75, 0x16, 0xf2, 0x29, 0x9e, 0xe0, 0x34, 0xa8, 0xc0, 0xed, 0x08, + 0x7e, 0x49, 0xa7, 0xf0, 0xe9, 0xc1, 0x0a, 0x3e, 0xf4, 0x09, 0x34, 0xa5, 0x4c, 0xd1, 0xb2, 0xf2, + 0x2a, 0xe0, 0xa6, 0xe1, 0xab, 0x45, 0xed, 0x23, 0x58, 0xca, 0xe8, 0x2c, 0xc2, 0xb3, 0x4b, 0x30, + 0x1b, 0x7b, 0x84, 0x48, 0xe5, 0x7a, 0x44, 0xdb, 0xb2, 0xb4, 0x7b, 0x70, 0xb6, 0x17, 0x98, 0x5e, + 0x90, 0x19, 0xf0, 0x09, 0xda, 0x32, 0xec, 0x26, 0xd9, 0x56, 0xc0, 0x2b, 0x3d, 0x58, 0xe8, 0x05, + 0xc4, 0x7d, 0x0d, 0xa1, 0x74, 0xa7, 0xd3, 0x61, 0x93, 0x30, 0x10, 0x31, 0x99, 0x2c, 0x6a, 0x4b, + 0x1c, 0x69, 0xca, 0xf6, 0xf6, 0x21, 0x2c, 0x72, 0xa0, 0xe7, 0x75, 0x06, 0xb1, 0x2c, 0x61, 0xa6, + 0xac, 0xdc, 0x0d, 0x38, 0x13, 0xbb, 0xf2, 0x38, 0x3d, 0xbc, 0x99, 0x4c, 0x0f, 0x97, 0xb2, 0x6b, + 0x9c, 0xc8, 0x0e, 0xff, 0xa2, 0xac, 0x38, 0xcc, 0x82, 0xe4, 0x70, 0x2d, 0x99, 0x1c, 0x9e, 0x2f, + 0x10, 0x99, 0xc8, 0x0d, 0xb3, 0x16, 0x59, 0xc9, 0xb1, 0x48, 0x3d, 0x93, 0x41, 0x4e, 0xa6, 0xf2, + 0xec, 0x94, 0x6e, 0xbf, 0x95, 0x04, 0x72, 0x8b, 0x27, 0x90, 0x51, 0xd7, 0x11, 0xf8, 0x76, 0x3b, + 0x95, 0x40, 0xb6, 0x8b, 0xd4, 0x8c, 0xf2, 0xc7, 0x3f, 0x9d, 0x84, 0x5a, 0x54, 0x97, 0x99, 0xd8, + 0xec, 0x24, 0x95, 0x73, 0x26, 0x49, 0x3d, 0xbf, 0x2a, 0xaf, 0x73, 0x7e, 0x4d, 0xbe, 0xea, 0xfc, + 0x3a, 0x07, 0x35, 0xf6, 0x61, 0x78, 0xf8, 0x50, 0x9c, 0x47, 0x55, 0x46, 0xd0, 0xf1, 0x61, 0x6c, + 0x50, 0xd3, 0x27, 0x31, 0xa8, 0x54, 0xa6, 0x3a, 0x93, 0xce, 0x54, 0xef, 0x44, 0x27, 0x0c, 0x3f, + 0x8b, 0x56, 0xb2, 0xe2, 0x72, 0xcf, 0x96, 0xcd, 0xe4, 0xd9, 0xc2, 0x8f, 0xa7, 0xcb, 0x39, 0x8d, + 0xdf, 0xd8, 0x3c, 0xf5, 0x09, 0xcf, 0x53, 0x55, 0xab, 0x12, 0x8e, 0x70, 0x0d, 0x20, 0xda, 0xf3, + 0x32, 0x59, 0x45, 0xd9, 0xa1, 0xe9, 0x0a, 0x97, 0xb6, 0x0f, 0x8b, 0x89, 0xf9, 0x8f, 0x11, 0xe2, + 0x93, 0x79, 0xb1, 0x02, 0x78, 0xf8, 0x5f, 0xd4, 0xf8, 0xa9, 0x00, 0x64, 0xbd, 0x93, 0x01, 0x3d, + 0x4e, 0x66, 0x8f, 0x37, 0x93, 0x98, 0xc7, 0xe9, 0x0c, 0x29, 0x03, 0x79, 0xb0, 0xe3, 0xde, 0xf4, + 0x44, 0x35, 0xcf, 0x56, 0x6b, 0x82, 0xd2, 0x0d, 0x68, 0x90, 0x75, 0x68, 0x3b, 0xb6, 0x7f, 0xc4, + 0xeb, 0xa7, 0x59, 0x3d, 0x48, 0x52, 0x97, 0x5d, 0xc2, 0xe3, 0x17, 0x76, 0x60, 0xf4, 0x89, 0x85, + 0x99, 0x99, 0x4e, 0xe9, 0x55, 0x4a, 0x58, 0x27, 0x16, 0x8e, 0xb7, 0x4e, 0xf5, 0x54, 0x5b, 0xa7, + 0x96, 0xda, 0x3a, 0x8b, 0x30, 0xed, 0x61, 0xd3, 0x27, 0x8e, 0x48, 0x19, 0x44, 0x89, 0xce, 0xff, + 0x08, 0xfb, 0x3e, 0xed, 0x40, 0x84, 0x36, 0xa2, 0xa8, 0x04, 0x60, 0xb3, 0x45, 0x01, 0xd8, 0x18, + 0x14, 0x37, 0x15, 0x80, 0x35, 0x8a, 0x02, 0xb0, 0x93, 0x80, 0xb8, 0x4a, 0x78, 0xd9, 0x1c, 0x1b, + 0x5e, 0xaa, 0x81, 0xda, 0x5c, 0x22, 0x50, 0xfb, 0x3e, 0x77, 0xdb, 0x3f, 0x97, 0x60, 0x29, 0xb3, + 0x41, 0xc4, 0x7e, 0xbb, 0x9d, 0x82, 0x81, 0xdb, 0x45, 0x33, 0x14, 0xa1, 0xc0, 0xf7, 0x13, 0x28, + 0xf0, 0x8d, 0x42, 0xfe, 0xef, 0x1c, 0x04, 0xfe, 0x43, 0xb8, 0xb8, 0xef, 0x5a, 0xa9, 0xf0, 0x49, + 0x24, 0x7f, 0x27, 0xdf, 0xef, 0x77, 0x64, 0xa4, 0x5b, 0x3e, 0x61, 0x5e, 0xc9, 0xd9, 0x35, 0x0d, + 0x56, 0x8b, 0x7b, 0x17, 0x61, 0xc8, 0xef, 0xc3, 0xdc, 0xe6, 0x0b, 0xdc, 0xef, 0x1d, 0x3b, 0xfd, + 0x53, 0x68, 0xd4, 0x82, 0x4a, 0x7f, 0x64, 0x09, 0x78, 0x85, 0x7e, 0xaa, 0x91, 0x55, 0x25, 0x19, + 0x59, 0x19, 0xd0, 0x8a, 0x7b, 0x10, 0x4b, 0xb8, 0x48, 0x97, 0xd0, 0xa2, 0xcc, 0x54, 0xf8, 0xac, + 0x2e, 0x4a, 0x82, 0x8e, 0x3d, 0x8f, 0x0d, 0x95, 0xd3, 0xb1, 0xe7, 0x25, 0x77, 0x7b, 0x25, 0xb9, + 0xdb, 0xb5, 0xbf, 0x2a, 0x41, 0x9d, 0xf6, 0xf0, 0xad, 0xf4, 0x17, 0xf9, 0x45, 0x25, 0xce, 0x2f, + 0xa2, 0x34, 0x65, 0x52, 0x4d, 0x53, 0x62, 0xcd, 0xa7, 0x18, 0x39, 0xab, 0xf9, 0x74, 0x44, 0xc7, + 0x9e, 0xa7, 0xad, 0xc2, 0x2c, 0xd7, 0x4d, 0x8c, 0xbc, 0x05, 0x95, 0xd0, 0x1b, 0x4a, 0xeb, 0x09, + 0xbd, 0xa1, 0xf6, 0x67, 0x25, 0x68, 0x74, 0x83, 0xc0, 0xec, 0x1f, 0x9d, 0x62, 0x00, 0x91, 0x72, + 0x65, 0x55, 0xb9, 0xec, 0x20, 0x62, 0x75, 0x27, 0x0b, 0xd4, 0x9d, 0x4a, 0xa8, 0xab, 0x41, 0x53, + 0xea, 0x52, 0xa8, 0xf0, 0x36, 0xa0, 0x5d, 0xe2, 0x05, 0x0f, 0x89, 0xf7, 0xdc, 0xf4, 0xac, 0xd3, + 0xe5, 0x30, 0x08, 0x26, 0xc5, 0xc3, 0xaa, 0xca, 0xb5, 0x29, 0x9d, 0x7d, 0x6b, 0x6f, 0xc3, 0x99, + 0x84, 0xbc, 0xc2, 0x8e, 0xef, 0x42, 0x9d, 0x39, 0x70, 0x11, 0xe7, 0x5e, 0x53, 0x71, 0xde, 0x71, + 0x5e, 0x5e, 0xeb, 0xc2, 0x3c, 0x3d, 0xbb, 0x19, 0x3d, 0xda, 0x78, 0x3f, 0x4c, 0x45, 0x83, 0x0b, + 0xc9, 0xf6, 0xa9, 0x48, 0xf0, 0xef, 0x4a, 0x30, 0xc5, 0xe8, 0x99, 0xf3, 0xf4, 0x1c, 0xd4, 0x3c, + 0xec, 0x12, 0x23, 0x30, 0x07, 0xd1, 0x5b, 0x35, 0x4a, 0xd8, 0x33, 0x07, 0x3e, 0x7b, 0x6a, 0x47, + 0x2b, 0x2d, 0x7b, 0x80, 0xfd, 0x40, 0x3e, 0x58, 0xab, 0x53, 0xda, 0x06, 0x27, 0xd1, 0x29, 0xf1, + 0xed, 0x3f, 0xe0, 0x61, 0xde, 0xa4, 0xce, 0xbe, 0xd1, 0x15, 0xfe, 0x0c, 0x64, 0x0c, 0x28, 0xc7, + 0xde, 0x86, 0x74, 0xa0, 0x9a, 0xc2, 0xe1, 0xa2, 0xb2, 0xf6, 0x11, 0x20, 0x75, 0xcc, 0x62, 0x52, + 0xaf, 0xc2, 0x34, 0x9b, 0x12, 0x19, 0xa7, 0x34, 0x93, 0x83, 0xd6, 0x45, 0xad, 0xf6, 0x05, 0x20, + 0x3e, 0x8b, 0x89, 0xd8, 0xe4, 0xc4, 0x33, 0x3e, 0x26, 0x44, 0xf9, 0xfb, 0x12, 0x9c, 0x49, 0x88, + 0x8e, 0x1e, 0x04, 0x24, 0x64, 0xa7, 0x15, 0x13, 0x72, 0xef, 0x25, 0x3c, 0xf9, 0xd5, 0x94, 0x02, + 0xbf, 0x21, 0x2f, 0xfe, 0xcb, 0x12, 0x40, 0x37, 0x0c, 0x8e, 0x04, 0x20, 0xa5, 0xce, 0x7a, 0x29, + 0x39, 0xeb, 0xb4, 0xce, 0x35, 0x7d, 0xff, 0x39, 0xf1, 0x64, 0x32, 0x10, 0x95, 0x19, 0x98, 0x14, + 0x06, 0x47, 0x12, 0x04, 0xa7, 0xdf, 0xe8, 0x0a, 0x34, 0xf9, 0x93, 0x47, 0xc3, 0xb4, 0x2c, 0x0f, + 0xfb, 0xbe, 0x40, 0xc3, 0x1b, 0x9c, 0xda, 0xe5, 0x44, 0xca, 0x66, 0x5b, 0xd8, 0x09, 0xec, 0xe0, + 0xd8, 0x08, 0xc8, 0xd7, 0xd8, 0x11, 0x61, 0x7e, 0x43, 0x52, 0xf7, 0x28, 0x91, 0xb2, 0x79, 0x78, + 0x60, 0xfb, 0x81, 0x27, 0xd9, 0x24, 0x3a, 0x2b, 0xa8, 0x8c, 0x4d, 0xfb, 0x79, 0x09, 0x5a, 0xbb, + 0xe1, 0x70, 0xc8, 0x67, 0xf6, 0xd4, 0x6b, 0xfb, 0xb6, 0x18, 0x47, 0x39, 0x65, 0x9d, 0xf1, 0x14, + 0x89, 0xc1, 0x7d, 0x7b, 0xf8, 0xe1, 0x36, 0xcc, 0x2b, 0x8a, 0x0a, 0x4b, 0x49, 0xc4, 0x6c, 0xa5, + 0x64, 0xcc, 0xa6, 0xdd, 0x07, 0xc4, 0x33, 0xee, 0xd7, 0x1b, 0x9c, 0x76, 0x16, 0xce, 0x24, 0xda, + 0x8b, 0x63, 0xf2, 0x06, 0x34, 0xc4, 0x7d, 0xbf, 0x30, 0x82, 0x65, 0xa8, 0x52, 0x77, 0xd7, 0xb7, + 0x2d, 0x79, 0xfb, 0x31, 0xe3, 0x12, 0x6b, 0xdd, 0xb6, 0x3c, 0x6d, 0x1b, 0x1a, 0x3a, 0x17, 0x2f, + 0x78, 0x3f, 0x86, 0xa6, 0x78, 0x1d, 0x60, 0x24, 0xde, 0xcf, 0xc4, 0x50, 0x7d, 0x42, 0xb6, 0xde, + 0x70, 0xd4, 0xa2, 0xf6, 0x53, 0xe8, 0xf0, 0x63, 0x3c, 0x21, 0x55, 0x0e, 0xed, 0x63, 0x90, 0x4f, + 0x72, 0x8b, 0x84, 0x27, 0x9b, 0x35, 0x3c, 0xb5, 0xa8, 0x5d, 0x80, 0x73, 0xb9, 0xc2, 0xc5, 0xb8, + 0x5d, 0x68, 0xc5, 0x15, 0x96, 0x2d, 0x2f, 0x7d, 0xd8, 0x65, 0x4e, 0x49, 0xb9, 0xcc, 0x59, 0x8c, + 0x62, 0xb2, 0xb2, 0x3c, 0x4f, 0x58, 0xe4, 0x15, 0x87, 0xd0, 0x95, 0xa2, 0x10, 0x7a, 0x32, 0x11, + 0x42, 0x6b, 0x9f, 0x45, 0xb3, 0x27, 0xf2, 0x97, 0x0f, 0x58, 0x7a, 0xc5, 0xfb, 0x96, 0x6e, 0x6b, + 0x39, 0x67, 0x70, 0x9c, 0x43, 0x57, 0x98, 0xb5, 0xeb, 0xd0, 0x48, 0x3a, 0x30, 0xc5, 0x2d, 0x95, + 0x32, 0x6e, 0xa9, 0x99, 0xf2, 0x48, 0xb7, 0x52, 0x71, 0x66, 0x66, 0x46, 0x53, 0x51, 0xe6, 0xfb, + 0x09, 0xdf, 0x74, 0x29, 0xbe, 0x87, 0xf9, 0x0d, 0xb9, 0xa5, 0x05, 0xe1, 0xa3, 0x1f, 0xfa, 0xb4, + 0xbd, 0x18, 0xa2, 0x76, 0x19, 0xea, 0xfb, 0x45, 0x8f, 0x65, 0x27, 0xe5, 0xc5, 0xe6, 0xdb, 0x30, + 0xdf, 0x0b, 0x88, 0x67, 0x0e, 0xf0, 0x16, 0x73, 0x20, 0x87, 0x36, 0xbf, 0xb8, 0x0b, 0xc3, 0xe8, + 0x68, 0x63, 0xdf, 0xda, 0x7f, 0x94, 0x60, 0xee, 0xa1, 0x3d, 0xc4, 0xfe, 0xb1, 0x1f, 0xe0, 0xd1, + 0x3e, 0x4b, 0x72, 0xce, 0x43, 0x8d, 0x8e, 0xcb, 0x0f, 0xcc, 0x91, 0x2b, 0x2f, 0x3e, 0x23, 0x02, + 0x5d, 0x2e, 0x9f, 0x8b, 0x96, 0x80, 0x88, 0x9a, 0x60, 0x66, 0x7a, 0xa5, 0x49, 0x9f, 0x20, 0xa1, + 0xf7, 0x00, 0x42, 0x1f, 0x5b, 0xe2, 0xb2, 0xb3, 0x92, 0x3a, 0x95, 0xf7, 0xd5, 0x2b, 0x29, 0xca, + 0xc7, 0x6f, 0x3e, 0xdf, 0x87, 0xba, 0xed, 0x10, 0x0b, 0xb3, 0x2b, 0x29, 0x4b, 0x80, 0x25, 0xf9, + 0xad, 0x80, 0x33, 0xee, 0xfb, 0xd8, 0xd2, 0x7e, 0x4f, 0x9c, 0x42, 0x72, 0xf2, 0xc4, 0x9a, 0x6f, + 0xc2, 0x3c, 0xf7, 0x2d, 0x87, 0xd1, 0xa0, 0xa5, 0xcd, 0xc5, 0x69, 0x46, 0x6a, 0x42, 0xf4, 0x96, + 0x2d, 0x02, 0x06, 0xd9, 0x42, 0xbb, 0x07, 0x67, 0x13, 0xb9, 0xc5, 0x29, 0xa2, 0x7d, 0xed, 0x51, + 0x0a, 0x1a, 0x88, 0x0d, 0x52, 0x64, 0xe0, 0xd2, 0x1e, 0x0b, 0x32, 0x70, 0x9f, 0x67, 0xe0, 0xbe, + 0xa6, 0xc3, 0x72, 0x02, 0xb1, 0x48, 0x28, 0xf2, 0x7e, 0x2a, 0xfa, 0xb9, 0x50, 0x20, 0x2c, 0x15, + 0x06, 0xfd, 0x6f, 0x09, 0x16, 0xf2, 0x18, 0x5e, 0x13, 0x1b, 0xfb, 0x49, 0xc1, 0x13, 0x94, 0xdb, + 0x63, 0xb5, 0xf9, 0xad, 0xa0, 0x88, 0x8f, 0xa1, 0x93, 0x37, 0x7b, 0xd9, 0xa5, 0xa8, 0x9c, 0x60, + 0x29, 0xfe, 0xaf, 0xac, 0xa0, 0xbd, 0xdd, 0x20, 0xf0, 0xec, 0x83, 0x90, 0x1a, 0xef, 0x77, 0x85, + 0xcd, 0x7c, 0x12, 0xe1, 0x0e, 0x7c, 0xfe, 0xae, 0x65, 0x5b, 0xc5, 0xbd, 0xe6, 0x62, 0x0f, 0x3b, + 0x49, 0xec, 0x81, 0xe3, 0xb8, 0x37, 0xc7, 0x8a, 0x79, 0x63, 0xa1, 0xba, 0x97, 0x25, 0x68, 0x26, + 0xd7, 0x01, 0x7d, 0x04, 0x60, 0x46, 0x9a, 0x0b, 0x93, 0x3f, 0x3f, 0x6e, 0x74, 0xba, 0xc2, 0x8f, + 0x2e, 0x43, 0xa5, 0xef, 0x86, 0x62, 0x45, 0xe2, 0x0b, 0xbd, 0x75, 0x37, 0xe4, 0x0e, 0x80, 0xd6, + 0xd2, 0x7c, 0x82, 0x3f, 0xcc, 0xc8, 0x78, 0xae, 0xa7, 0x8c, 0xcc, 0x59, 0x05, 0x0f, 0x7a, 0x00, + 0xcd, 0xe7, 0x9e, 0x1d, 0x98, 0x07, 0x43, 0x6c, 0x0c, 0xcd, 0x63, 0xec, 0x09, 0xcf, 0x55, 0xec, + 0x65, 0x1a, 0x92, 0xff, 0x09, 0x65, 0xd7, 0x42, 0xa8, 0xca, 0xfe, 0x5f, 0xe1, 0x91, 0x1f, 0xc3, + 0x52, 0x48, 0xd9, 0x0c, 0xf6, 0x38, 0xc4, 0x31, 0x1d, 0x62, 0xf8, 0x98, 0x9e, 0x92, 0xf2, 0x2d, + 0x68, 0xbe, 0xb7, 0x5c, 0x60, 0x8d, 0xd6, 0x89, 0x87, 0xb7, 0x4d, 0x87, 0xf4, 0x78, 0x0b, 0x6d, + 0x04, 0x75, 0x65, 0x38, 0xaf, 0xe8, 0xf9, 0x13, 0x98, 0x97, 0x57, 0xa5, 0x3e, 0x0e, 0x84, 0x5f, + 0x1f, 0xd7, 0xe7, 0x9c, 0x60, 0xef, 0xe1, 0x80, 0x79, 0xf7, 0x1b, 0xe7, 0xa1, 0x2a, 0xff, 0xa8, + 0x83, 0x66, 0xa0, 0xb2, 0xb7, 0xbe, 0xdb, 0x9a, 0xa0, 0x1f, 0xfb, 0x1b, 0xbb, 0xad, 0xd2, 0x8d, + 0x11, 0xb4, 0xd2, 0x7f, 0x52, 0x41, 0x4b, 0x70, 0x66, 0x57, 0xdf, 0xd9, 0xed, 0x3e, 0xea, 0xee, + 0x6d, 0xed, 0x6c, 0x1b, 0xbb, 0xfa, 0xd6, 0xe7, 0xdd, 0xbd, 0xcd, 0xd6, 0x04, 0xba, 0x04, 0x17, + 0xd4, 0x8a, 0xdf, 0xdd, 0xe9, 0xed, 0x19, 0x7b, 0x3b, 0xc6, 0xfa, 0xce, 0xf6, 0x5e, 0x77, 0x6b, + 0x7b, 0x53, 0x6f, 0x95, 0xd0, 0x05, 0x58, 0x56, 0x59, 0x3e, 0xdd, 0xda, 0xd8, 0xd2, 0x37, 0xd7, + 0xe9, 0x77, 0xf7, 0x49, 0xab, 0x7c, 0xe3, 0x1e, 0xcc, 0xa5, 0xde, 0x77, 0xa1, 0x79, 0x68, 0xf4, + 0xba, 0xdb, 0x1b, 0x9f, 0xee, 0x7c, 0x61, 0xe8, 0x9b, 0xdd, 0x8d, 0x2f, 0x5b, 0x13, 0x68, 0x01, + 0x5a, 0x92, 0xb4, 0xbd, 0xb3, 0xc7, 0xa9, 0xa5, 0x1b, 0x5f, 0xa7, 0x4c, 0x12, 0xa3, 0xb3, 0x30, + 0x1f, 0xf5, 0x6d, 0xac, 0xeb, 0x9b, 0xdd, 0xbd, 0xcd, 0x8d, 0xd6, 0x44, 0x92, 0xac, 0xef, 0x6f, + 0x6f, 0x6f, 0x6d, 0x3f, 0x6a, 0x95, 0xa8, 0xd4, 0x98, 0xbc, 0xf9, 0xc5, 0x16, 0x65, 0x2e, 0x27, + 0x99, 0xf7, 0xb7, 0x1f, 0x6f, 0xef, 0xfc, 0x64, 0xbb, 0x55, 0x59, 0xfb, 0xf7, 0x06, 0x34, 0x65, + 0x8c, 0x82, 0x3d, 0x76, 0x7f, 0x7f, 0x1f, 0x66, 0xe4, 0x5f, 0xb6, 0x62, 0x67, 0x95, 0xfc, 0x7f, + 0x59, 0xa7, 0x9d, 0xad, 0x10, 0x61, 0xe0, 0x04, 0xda, 0x65, 0x61, 0x99, 0xf2, 0x96, 0xee, 0x82, + 0x1a, 0x0d, 0x65, 0x1e, 0xeb, 0x75, 0x56, 0x8a, 0xaa, 0x23, 0x89, 0x3d, 0x1a, 0x70, 0xa9, 0x8f, + 0xb3, 0xd1, 0x8a, 0x1a, 0x26, 0x64, 0x1f, 0x7d, 0x77, 0x2e, 0x16, 0xd6, 0x47, 0x42, 0xbf, 0x84, + 0x56, 0xfa, 0x59, 0x36, 0x8a, 0xf1, 0xb2, 0x82, 0x27, 0xdf, 0x9d, 0x4b, 0x63, 0x38, 0x54, 0xd1, + 0x99, 0x07, 0xcc, 0xab, 0x63, 0x1e, 0x94, 0xa6, 0x45, 0x17, 0x3d, 0x39, 0xe5, 0x53, 0x91, 0x7c, + 0x03, 0x87, 0xd4, 0x67, 0xc3, 0x39, 0x6f, 0x21, 0x95, 0xa9, 0xc8, 0x7f, 0x3c, 0xa7, 0x4d, 0xa0, + 0xcf, 0x61, 0x2e, 0x75, 0x75, 0x8b, 0xe2, 0x56, 0xf9, 0x17, 0xd1, 0x9d, 0xd5, 0x62, 0x86, 0xe4, + 0xba, 0xa9, 0x17, 0xb3, 0x89, 0x75, 0xcb, 0xb9, 0xed, 0x4d, 0xac, 0x5b, 0xee, 0x8d, 0x2e, 0x33, + 0xaf, 0xc4, 0xf5, 0xab, 0x62, 0x5e, 0x79, 0x77, 0xbd, 0x9d, 0x95, 0xa2, 0x6a, 0x75, 0xf8, 0xa9, + 0xab, 0x57, 0x65, 0xf8, 0xf9, 0x37, 0xba, 0x9d, 0xd5, 0x62, 0x86, 0xf4, 0x5a, 0xc5, 0xf7, 0x40, + 0xa9, 0xb5, 0xca, 0x5c, 0x3b, 0xa6, 0xd6, 0x2a, 0x7b, 0x81, 0x24, 0xd6, 0x2a, 0x75, 0x6d, 0x73, + 0xb1, 0x18, 0xa5, 0xce, 0xac, 0x55, 0x3e, 0x8c, 0xad, 0x4d, 0xa0, 0x6f, 0xa0, 0x5d, 0x84, 0x00, + 0xa3, 0x38, 0x46, 0x78, 0x05, 0x44, 0xdd, 0xb9, 0x7e, 0x02, 0xce, 0xa8, 0xcb, 0x2e, 0x54, 0x25, + 0xdc, 0x8b, 0x62, 0x87, 0x92, 0xc2, 0x98, 0x3b, 0xcb, 0x39, 0x35, 0x91, 0x88, 0xf7, 0x61, 0x92, + 0x52, 0xd1, 0x42, 0x82, 0x49, 0x36, 0x3d, 0x9b, 0xa2, 0x46, 0xcd, 0x3e, 0x84, 0x69, 0x8e, 0x5d, + 0xa2, 0x38, 0x53, 0x4b, 0x00, 0xab, 0x9d, 0xa5, 0x0c, 0x3d, 0x6a, 0xfc, 0x19, 0xff, 0xe7, 0xa8, + 0x00, 0x21, 0xd1, 0xb9, 0xc4, 0x9f, 0x8c, 0x92, 0x50, 0x67, 0xe7, 0x7c, 0x7e, 0xa5, 0x6a, 0x22, + 0xa9, 0xf0, 0x63, 0xa5, 0x28, 0x3e, 0xcc, 0x98, 0x48, 0x7e, 0xbc, 0xa9, 0x4d, 0x20, 0x83, 0xe3, + 0x79, 0x29, 0xc1, 0x5a, 0xbe, 0x6d, 0x25, 0x84, 0x5f, 0x1e, 0xcb, 0x13, 0x75, 0x70, 0x00, 0x67, + 0x72, 0x90, 0x00, 0x74, 0x39, 0xb5, 0xf8, 0x79, 0x20, 0x44, 0xe7, 0xad, 0xf1, 0x4c, 0xea, 0x12, + 0x09, 0xf3, 0x5e, 0xcc, 0xa4, 0xc7, 0xe9, 0x25, 0x4a, 0x1b, 0xf3, 0xda, 0x1f, 0x57, 0x60, 0x96, + 0xe3, 0x35, 0xe2, 0x4c, 0x7b, 0x04, 0x10, 0x43, 0x9c, 0xa8, 0x93, 0x18, 0x66, 0x02, 0xeb, 0xed, + 0x9c, 0xcb, 0xad, 0x53, 0x17, 0x5f, 0x01, 0x10, 0x95, 0xc5, 0xcf, 0x62, 0xa0, 0xca, 0xe2, 0xe7, + 0x60, 0x8e, 0xda, 0x04, 0xda, 0x80, 0x5a, 0x04, 0x59, 0x21, 0x05, 0xe9, 0x4a, 0xe1, 0x6d, 0x9d, + 0x4e, 0x5e, 0x95, 0xaa, 0x91, 0x02, 0x43, 0x29, 0x1a, 0x65, 0xc1, 0x2d, 0x45, 0xa3, 0x3c, 0xe4, + 0x2a, 0x1e, 0x1d, 0x4f, 0x75, 0xd3, 0xa3, 0x4b, 0xa0, 0x07, 0xe9, 0xd1, 0x25, 0xb3, 0x63, 0x6d, + 0xe2, 0xd3, 0xf3, 0xbf, 0xf8, 0xd5, 0x4a, 0xe9, 0x3f, 0x7f, 0xb5, 0x32, 0xf1, 0x47, 0x2f, 0x57, + 0x4a, 0xbf, 0x78, 0xb9, 0x52, 0xfa, 0xb7, 0x97, 0x2b, 0xa5, 0xff, 0x7e, 0xb9, 0x52, 0xfa, 0xd9, + 0xff, 0xac, 0x4c, 0x1c, 0x4c, 0xb3, 0xbf, 0x52, 0xbf, 0xf7, 0xeb, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xa6, 0x04, 0x77, 0x58, 0xfe, 0x3e, 0x00, 0x00, } diff --git a/pkg/kubelet/apis/cri/v1alpha1/runtime/api.proto b/pkg/kubelet/apis/cri/v1alpha1/runtime/api.proto index 31a238b28e3..93428edc8af 100644 --- a/pkg/kubelet/apis/cri/v1alpha1/runtime/api.proto +++ b/pkg/kubelet/apis/cri/v1alpha1/runtime/api.proto @@ -331,6 +331,8 @@ message RemovePodSandboxResponse {} message PodSandboxStatusRequest { // ID of the PodSandbox for which to retrieve status. string pod_sandbox_id = 1; + // Verbose indicates whether to return extra information about the pod sandbox. + bool verbose = 2; } // PodSandboxNetworkStatus is the status of the network for a PodSandbox. @@ -382,6 +384,11 @@ message PodSandboxStatus { message PodSandboxStatusResponse { // Status of the PodSandbox. PodSandboxStatus status = 1; + // Info is extra information of the PodSandbox. The key could be abitrary string, and + // value should be in json format. The information could include anything useful for + // debug, e.g. network namespace for linux container based container runtime. + // It should only be returned non-empty when Verbose is true. + map info = 2; } // PodSandboxStateValue is the wrapper of PodSandboxState. @@ -747,6 +754,8 @@ message ListContainersResponse { message ContainerStatusRequest { // ID of the container for which to retrieve status. string container_id = 1; + // Verbose indicates whether to return extra information about the container. + bool verbose = 2; } // ContainerStatus represents the status of a container. @@ -791,6 +800,11 @@ message ContainerStatus { message ContainerStatusResponse { // Status of the container. ContainerStatus status = 1; + // Info is extra information of the Container. The key could be abitrary string, and + // value should be in json format. The information could include anything useful for + // debug, e.g. pid for linux container based container runtime. + // It should only be returned non-empty when Verbose is true. + map info = 2; } message UpdateContainerResourcesRequest { @@ -920,11 +934,18 @@ message ListImagesResponse { message ImageStatusRequest { // Spec of the image. ImageSpec image = 1; + // Verbose indicates whether to return extra information about the image. + bool verbose = 2; } message ImageStatusResponse { // Status of the image. Image image = 1; + // Info is extra information of the Image. The key could be abitrary string, and + // value should be in json format. The information could include anything useful + // for debug, e.g. image config for oci image based container runtime. + // It should only be returned non-empty when Verbose is true. + map info = 2; } // AuthConfig contains authorization information for connecting to a registry. @@ -1007,11 +1028,19 @@ message RuntimeStatus { repeated RuntimeCondition conditions = 1; } -message StatusRequest {} +message StatusRequest { + // Verbose indicates whether to return extra information about the runtime. + bool verbose = 1; +} message StatusResponse { // Status of the Runtime. RuntimeStatus status = 1; + // Info is extra information of the Runtime. The key could be abitrary string, and + // value should be in json format. The information could include anything useful for + // debug, e.g. plugins used by the container runtime. + // It should only be returned non-empty when Verbose is true. + map info = 2; } message ImageFsInfoRequest {} diff --git a/pkg/kubelet/apis/cri/v1alpha1/runtime/constants.go b/pkg/kubelet/apis/cri/v1alpha1/runtime/constants.go index 27c42c5c516..04cac264d14 100644 --- a/pkg/kubelet/apis/cri/v1alpha1/runtime/constants.go +++ b/pkg/kubelet/apis/cri/v1alpha1/runtime/constants.go @@ -25,3 +25,31 @@ const ( // NetworkReady means the runtime network is up and ready to accept containers which require network. NetworkReady = "NetworkReady" ) + +// LogStreamType is the type of the stream in CRI container log. +type LogStreamType string + +const ( + // Stdout is the stream type for stdout. + Stdout LogStreamType = "stdout" + // Stderr is the stream type for stderr. + Stderr LogStreamType = "stderr" +) + +// LogTag is the tag of a log line in CRI container log. +// Currently defined log tags: +// * First tag: Partial/End - P/E. +// The field in the container log format can be extended to include multiple +// tags by using a delimiter, but changes should be rare. If it becomes clear +// that better extensibility is desired, a more extensible format (e.g., json) +// should be adopted as a replacement and/or addition. +type LogTag string + +const ( + // LogTagPartial means the line is part of multiple lines. + LogTagPartial LogTag = "P" + // LogTagFull means the line is a single full line or the end of multiple lines. + LogTagFull LogTag = "F" + // LogTagDelimiter is the delimiter for different log tags. + LogTagDelimiter = ":" +) diff --git a/pkg/kubelet/apis/deviceplugin/v1alpha/BUILD b/pkg/kubelet/apis/deviceplugin/v1alpha/BUILD new file mode 100644 index 00000000000..b60d55569c9 --- /dev/null +++ b/pkg/kubelet/apis/deviceplugin/v1alpha/BUILD @@ -0,0 +1,41 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = [ + "api.pb.go", + "constants.go", + ], + importpath = "k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1alpha", + deps = [ + "//vendor/github.com/gogo/protobuf/gogoproto:go_default_library", + "//vendor/github.com/gogo/protobuf/proto:go_default_library", + "//vendor/github.com/gogo/protobuf/sortkeys:go_default_library", + "//vendor/golang.org/x/net/context:go_default_library", + "//vendor/google.golang.org/grpc:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) + +filegroup( + name = "go_default_library_protos", + srcs = ["api.proto"], + visibility = ["//visibility:public"], +) diff --git a/pkg/kubelet/apis/deviceplugin/v1alpha1/api.pb.go b/pkg/kubelet/apis/deviceplugin/v1alpha/api.pb.go similarity index 85% rename from pkg/kubelet/apis/deviceplugin/v1alpha1/api.pb.go rename to pkg/kubelet/apis/deviceplugin/v1alpha/api.pb.go index 2d73929bdf1..c29447ccf97 100644 --- a/pkg/kubelet/apis/deviceplugin/v1alpha1/api.pb.go +++ b/pkg/kubelet/apis/deviceplugin/v1alpha/api.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -31,7 +31,6 @@ limitations under the License. Device AllocateRequest AllocateResponse - DeviceRuntimeSpec Mount DeviceSpec */ @@ -177,64 +176,42 @@ func (m *AllocateRequest) GetDevicesIDs() []string { return nil } +// AllocateResponse includes the artifacts that needs to be injected into +// a container for accessing 'deviceIDs' that were mentioned as part of +// 'AllocateRequest'. // Failure Handling: // if Kubelet sends an allocation request for dev1 and dev2. // Allocation on dev1 succeeds but allocation on dev2 fails. // The Device plugin should send a ListAndWatch update and fail the // Allocation request type AllocateResponse struct { - Spec []*DeviceRuntimeSpec `protobuf:"bytes,1,rep,name=spec" json:"spec,omitempty"` + // List of environment variable to be set in the container to access one of more devices. + Envs map[string]string `protobuf:"bytes,1,rep,name=envs" json:"envs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // Mounts for the container. + Mounts []*Mount `protobuf:"bytes,2,rep,name=mounts" json:"mounts,omitempty"` + // Devices for the container. + Devices []*DeviceSpec `protobuf:"bytes,3,rep,name=devices" json:"devices,omitempty"` } func (m *AllocateResponse) Reset() { *m = AllocateResponse{} } func (*AllocateResponse) ProtoMessage() {} func (*AllocateResponse) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{5} } -func (m *AllocateResponse) GetSpec() []*DeviceRuntimeSpec { - if m != nil { - return m.Spec - } - return nil -} - -// The list to be added to the CRI spec -type DeviceRuntimeSpec struct { - // ID of the Device - ID string `protobuf:"bytes,1,opt,name=ID,json=iD,proto3" json:"ID,omitempty"` - // List of environment variable to set in the container. - Envs map[string]string `protobuf:"bytes,2,rep,name=envs" json:"envs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - // Mounts for the container. - Mounts []*Mount `protobuf:"bytes,3,rep,name=mounts" json:"mounts,omitempty"` - // Devices for the container - Devices []*DeviceSpec `protobuf:"bytes,4,rep,name=devices" json:"devices,omitempty"` -} - -func (m *DeviceRuntimeSpec) Reset() { *m = DeviceRuntimeSpec{} } -func (*DeviceRuntimeSpec) ProtoMessage() {} -func (*DeviceRuntimeSpec) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{6} } - -func (m *DeviceRuntimeSpec) GetID() string { - if m != nil { - return m.ID - } - return "" -} - -func (m *DeviceRuntimeSpec) GetEnvs() map[string]string { +func (m *AllocateResponse) GetEnvs() map[string]string { if m != nil { return m.Envs } return nil } -func (m *DeviceRuntimeSpec) GetMounts() []*Mount { +func (m *AllocateResponse) GetMounts() []*Mount { if m != nil { return m.Mounts } return nil } -func (m *DeviceRuntimeSpec) GetDevices() []*DeviceSpec { +func (m *AllocateResponse) GetDevices() []*DeviceSpec { if m != nil { return m.Devices } @@ -254,7 +231,7 @@ type Mount struct { func (m *Mount) Reset() { *m = Mount{} } func (*Mount) ProtoMessage() {} -func (*Mount) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{7} } +func (*Mount) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{6} } func (m *Mount) GetContainerPath() string { if m != nil { @@ -292,7 +269,7 @@ type DeviceSpec struct { func (m *DeviceSpec) Reset() { *m = DeviceSpec{} } func (*DeviceSpec) ProtoMessage() {} -func (*DeviceSpec) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{8} } +func (*DeviceSpec) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{7} } func (m *DeviceSpec) GetContainerPath() string { if m != nil { @@ -322,7 +299,6 @@ func init() { proto.RegisterType((*Device)(nil), "deviceplugin.Device") proto.RegisterType((*AllocateRequest)(nil), "deviceplugin.AllocateRequest") proto.RegisterType((*AllocateResponse)(nil), "deviceplugin.AllocateResponse") - proto.RegisterType((*DeviceRuntimeSpec)(nil), "deviceplugin.DeviceRuntimeSpec") proto.RegisterType((*Mount)(nil), "deviceplugin.Mount") proto.RegisterType((*DeviceSpec)(nil), "deviceplugin.DeviceSpec") } @@ -698,45 +674,9 @@ func (m *AllocateResponse) MarshalTo(dAtA []byte) (int, error) { _ = i var l int _ = l - if len(m.Spec) > 0 { - for _, msg := range m.Spec { - dAtA[i] = 0xa - i++ - i = encodeVarintApi(dAtA, i, uint64(msg.Size())) - n, err := msg.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n - } - } - return i, nil -} - -func (m *DeviceRuntimeSpec) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *DeviceRuntimeSpec) MarshalTo(dAtA []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.ID) > 0 { - dAtA[i] = 0xa - i++ - i = encodeVarintApi(dAtA, i, uint64(len(m.ID))) - i += copy(dAtA[i:], m.ID) - } if len(m.Envs) > 0 { for k := range m.Envs { - dAtA[i] = 0x12 + dAtA[i] = 0xa i++ v := m.Envs[k] mapSize := 1 + len(k) + sovApi(uint64(len(k))) + 1 + len(v) + sovApi(uint64(len(v))) @@ -753,7 +693,7 @@ func (m *DeviceRuntimeSpec) MarshalTo(dAtA []byte) (int, error) { } if len(m.Mounts) > 0 { for _, msg := range m.Mounts { - dAtA[i] = 0x1a + dAtA[i] = 0x12 i++ i = encodeVarintApi(dAtA, i, uint64(msg.Size())) n, err := msg.MarshalTo(dAtA[i:]) @@ -765,7 +705,7 @@ func (m *DeviceRuntimeSpec) MarshalTo(dAtA []byte) (int, error) { } if len(m.Devices) > 0 { for _, msg := range m.Devices { - dAtA[i] = 0x22 + dAtA[i] = 0x1a i++ i = encodeVarintApi(dAtA, i, uint64(msg.Size())) n, err := msg.MarshalTo(dAtA[i:]) @@ -946,22 +886,6 @@ func (m *AllocateRequest) Size() (n int) { func (m *AllocateResponse) Size() (n int) { var l int _ = l - if len(m.Spec) > 0 { - for _, e := range m.Spec { - l = e.Size() - n += 1 + l + sovApi(uint64(l)) - } - } - return n -} - -func (m *DeviceRuntimeSpec) Size() (n int) { - var l int - _ = l - l = len(m.ID) - if l > 0 { - n += 1 + l + sovApi(uint64(l)) - } if len(m.Envs) > 0 { for k, v := range m.Envs { _ = k @@ -1086,16 +1010,6 @@ func (this *AllocateRequest) String() string { return s } func (this *AllocateResponse) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&AllocateResponse{`, - `Spec:` + strings.Replace(fmt.Sprintf("%v", this.Spec), "DeviceRuntimeSpec", "DeviceRuntimeSpec", 1) + `,`, - `}`, - }, "") - return s -} -func (this *DeviceRuntimeSpec) String() string { if this == nil { return "nil" } @@ -1109,8 +1023,7 @@ func (this *DeviceRuntimeSpec) String() string { mapStringForEnvs += fmt.Sprintf("%v: %v,", k, this.Envs[k]) } mapStringForEnvs += "}" - s := strings.Join([]string{`&DeviceRuntimeSpec{`, - `ID:` + fmt.Sprintf("%v", this.ID) + `,`, + s := strings.Join([]string{`&AllocateResponse{`, `Envs:` + mapStringForEnvs + `,`, `Mounts:` + strings.Replace(fmt.Sprintf("%v", this.Mounts), "Mount", "Mount", 1) + `,`, `Devices:` + strings.Replace(fmt.Sprintf("%v", this.Devices), "DeviceSpec", "DeviceSpec", 1) + `,`, @@ -1635,116 +1548,6 @@ func (m *AllocateResponse) Unmarshal(dAtA []byte) error { } switch fieldNum { case 1: - 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 ErrIntOverflowApi - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthApi - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Spec = append(m.Spec, &DeviceRuntimeSpec{}) - if err := m.Spec[len(m.Spec)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipApi(dAtA[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthApi - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *DeviceRuntimeSpec) 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 ErrIntOverflowApi - } - 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: DeviceRuntimeSpec: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: DeviceRuntimeSpec: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ID", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowApi - } - 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 ErrInvalidLengthApi - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.ID = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Envs", wireType) } @@ -1860,7 +1663,7 @@ func (m *DeviceRuntimeSpec) Unmarshal(dAtA []byte) error { m.Envs[mapkey] = mapvalue } iNdEx = postIndex - case 3: + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Mounts", wireType) } @@ -1891,7 +1694,7 @@ func (m *DeviceRuntimeSpec) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 4: + case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Devices", wireType) } @@ -2316,42 +2119,41 @@ var ( func init() { proto.RegisterFile("api.proto", fileDescriptorApi) } var fileDescriptorApi = []byte{ - // 590 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0xcd, 0x6e, 0xd3, 0x40, - 0x10, 0xce, 0x26, 0x6d, 0x9a, 0x4c, 0xd3, 0x1f, 0x96, 0x0a, 0x59, 0x01, 0x4c, 0x65, 0x84, 0x54, - 0x84, 0x70, 0x4b, 0x7a, 0x00, 0x21, 0x21, 0x51, 0x94, 0x82, 0xaa, 0xf2, 0x53, 0x99, 0x03, 0xc7, - 0x6a, 0xeb, 0x0c, 0xf1, 0x0a, 0x7b, 0xd7, 0x78, 0xd7, 0x91, 0x72, 0xe3, 0x11, 0x78, 0x0c, 0x1e, - 0xa5, 0x47, 0x8e, 0x1c, 0x69, 0x78, 0x0d, 0x0e, 0xc8, 0xeb, 0x75, 0x9b, 0xa6, 0x41, 0x5c, 0xb8, - 0x79, 0xbe, 0xf9, 0x66, 0xe6, 0x9b, 0xcd, 0x7c, 0x81, 0x36, 0x4b, 0xb9, 0x9f, 0x66, 0x52, 0x4b, - 0xda, 0x19, 0xe0, 0x88, 0x87, 0x98, 0xc6, 0xf9, 0x90, 0x8b, 0xee, 0xc3, 0x21, 0xd7, 0x51, 0x7e, - 0xe2, 0x87, 0x32, 0xd9, 0x1e, 0xca, 0xa1, 0xdc, 0x36, 0xa4, 0x93, 0xfc, 0xa3, 0x89, 0x4c, 0x60, - 0xbe, 0xca, 0x62, 0x2f, 0x86, 0xb5, 0x00, 0x87, 0x5c, 0x69, 0xcc, 0x02, 0xfc, 0x9c, 0xa3, 0xd2, - 0xd4, 0x81, 0xa5, 0x11, 0x66, 0x8a, 0x4b, 0xe1, 0x90, 0x4d, 0xb2, 0xd5, 0x0e, 0xaa, 0x90, 0x76, - 0xa1, 0x85, 0x62, 0x90, 0x4a, 0x2e, 0xb4, 0x53, 0x37, 0xa9, 0xf3, 0x98, 0xde, 0x85, 0x95, 0x0c, - 0x95, 0xcc, 0xb3, 0x10, 0x8f, 0x05, 0x4b, 0xd0, 0x69, 0x18, 0x42, 0xa7, 0x02, 0xdf, 0xb2, 0x04, - 0xbd, 0x25, 0x58, 0xdc, 0x4f, 0x52, 0x3d, 0xf6, 0x5e, 0xc2, 0xc6, 0x6b, 0xae, 0xf4, 0x9e, 0x18, - 0x7c, 0x60, 0x3a, 0x8c, 0x02, 0x54, 0xa9, 0x14, 0x0a, 0xa9, 0x0f, 0x4b, 0xe5, 0x36, 0xca, 0x21, - 0x9b, 0x8d, 0xad, 0xe5, 0xde, 0x86, 0x3f, 0xbd, 0x9d, 0xdf, 0x37, 0x41, 0x50, 0x91, 0xbc, 0x1d, - 0x68, 0x96, 0x10, 0x5d, 0x85, 0xfa, 0x41, 0xdf, 0x0a, 0xae, 0xf3, 0x3e, 0xbd, 0x01, 0xcd, 0x08, - 0x59, 0xac, 0x23, 0xab, 0xd4, 0x46, 0xde, 0x23, 0x58, 0xdb, 0x8b, 0x63, 0x19, 0x32, 0x8d, 0xd5, - 0xc2, 0x2e, 0x80, 0xed, 0x77, 0xd0, 0x2f, 0xe7, 0xb6, 0x83, 0x29, 0xc4, 0x7b, 0x05, 0xeb, 0x17, - 0x25, 0x56, 0xe8, 0x2e, 0x2c, 0xa8, 0x14, 0x43, 0xab, 0xf2, 0xce, 0x5c, 0x95, 0xb9, 0xd0, 0x3c, - 0xc1, 0xf7, 0x29, 0x86, 0x81, 0x21, 0x7b, 0xbf, 0x09, 0x5c, 0xbb, 0x92, 0xbb, 0xa2, 0xfc, 0x19, - 0x2c, 0xa0, 0x18, 0x29, 0xa7, 0x6e, 0x5a, 0xdf, 0xff, 0x47, 0x6b, 0x7f, 0x5f, 0x8c, 0xd4, 0xbe, - 0xd0, 0xd9, 0x38, 0x30, 0x65, 0xf4, 0x01, 0x34, 0x13, 0x99, 0x0b, 0xad, 0x9c, 0x86, 0x69, 0x70, - 0xfd, 0x72, 0x83, 0x37, 0x45, 0x2e, 0xb0, 0x14, 0xda, 0xbb, 0x78, 0xef, 0x05, 0xc3, 0x76, 0xe6, - 0x8d, 0x33, 0x2b, 0x54, 0xc4, 0xee, 0x63, 0x68, 0x9f, 0xcf, 0xa4, 0xeb, 0xd0, 0xf8, 0x84, 0x63, - 0xab, 0xbe, 0xf8, 0xa4, 0x1b, 0xb0, 0x38, 0x62, 0x71, 0x8e, 0xf6, 0xdd, 0xcb, 0xe0, 0x69, 0xfd, - 0x09, 0xf1, 0x22, 0x58, 0x34, 0xd3, 0xe9, 0x3d, 0x58, 0x0d, 0xa5, 0xd0, 0x8c, 0x0b, 0xcc, 0x8e, - 0x53, 0xa6, 0x23, 0x5b, 0xbf, 0x72, 0x8e, 0x1e, 0x31, 0x1d, 0xd1, 0x9b, 0xd0, 0x8e, 0xa4, 0xd2, - 0x25, 0xc3, 0xde, 0x5b, 0x01, 0x54, 0xc9, 0x0c, 0xd9, 0xe0, 0x58, 0x8a, 0x78, 0x6c, 0x6e, 0xad, - 0x15, 0xb4, 0x0a, 0xe0, 0x9d, 0x88, 0xc7, 0x5e, 0x06, 0x70, 0xa1, 0xfc, 0xbf, 0x8c, 0xdb, 0x84, - 0xe5, 0x14, 0xb3, 0x84, 0xab, 0xc2, 0x08, 0xca, 0x1e, 0xf7, 0x34, 0xd4, 0x3b, 0x82, 0x4e, 0xe9, - 0xa4, 0x8c, 0xe9, 0xc2, 0x2c, 0xcf, 0xa1, 0x55, 0x39, 0x8b, 0xde, 0xbe, 0xfc, 0xaa, 0x33, 0x8e, - 0xeb, 0xce, 0xfc, 0x44, 0xa5, 0x45, 0x6a, 0xbd, 0x6f, 0x04, 0x3a, 0xe5, 0x1a, 0x47, 0x26, 0x41, - 0x0f, 0xa1, 0x33, 0xed, 0x1a, 0x3a, 0xaf, 0xae, 0xeb, 0x5d, 0x06, 0xe7, 0xd9, 0xcc, 0xab, 0xed, - 0x10, 0x7a, 0x08, 0xad, 0xea, 0xaa, 0x67, 0xf5, 0xcd, 0x18, 0xa4, 0xeb, 0xfe, 0x2d, 0x5d, 0xb5, - 0x7b, 0x71, 0xeb, 0xf4, 0xcc, 0x25, 0x3f, 0xce, 0xdc, 0xda, 0x97, 0x89, 0x4b, 0x4e, 0x27, 0x2e, - 0xf9, 0x3e, 0x71, 0xc9, 0xcf, 0x89, 0x4b, 0xbe, 0xfe, 0x72, 0x6b, 0x27, 0x4d, 0xf3, 0x5f, 0xb3, - 0xfb, 0x27, 0x00, 0x00, 0xff, 0xff, 0x99, 0xda, 0x39, 0xd8, 0xb5, 0x04, 0x00, 0x00, + // 562 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0xcb, 0x8e, 0xd3, 0x4a, + 0x10, 0x4d, 0x27, 0x77, 0xf2, 0xa8, 0xc9, 0x3c, 0xd4, 0x37, 0x42, 0x96, 0x01, 0x2b, 0x32, 0x42, + 0x8a, 0x84, 0xf0, 0x0c, 0x61, 0x01, 0x42, 0x2c, 0x18, 0x94, 0x20, 0x8d, 0x86, 0x47, 0x64, 0x16, + 0x2c, 0xa3, 0x8e, 0x53, 0xc4, 0x16, 0x76, 0xb7, 0x71, 0xb7, 0x23, 0x65, 0xc7, 0x27, 0xf0, 0x19, + 0x7c, 0xca, 0x2c, 0x59, 0xb2, 0x64, 0xc2, 0x8e, 0xaf, 0x40, 0x6e, 0xdb, 0x79, 0x29, 0x62, 0xc5, + 0xce, 0x75, 0xea, 0x9c, 0xd4, 0xa9, 0xca, 0xb1, 0xa1, 0xc5, 0xe2, 0xc0, 0x89, 0x13, 0xa1, 0x04, + 0x6d, 0x4f, 0x71, 0x1e, 0x78, 0x18, 0x87, 0xe9, 0x2c, 0xe0, 0xe6, 0xc3, 0x59, 0xa0, 0xfc, 0x74, + 0xe2, 0x78, 0x22, 0x3a, 0x9b, 0x89, 0x99, 0x38, 0xd3, 0xa4, 0x49, 0xfa, 0x51, 0x57, 0xba, 0xd0, + 0x4f, 0xb9, 0xd8, 0x0e, 0xe1, 0xc4, 0xc5, 0x59, 0x20, 0x15, 0x26, 0x2e, 0x7e, 0x4e, 0x51, 0x2a, + 0x6a, 0x40, 0x63, 0x8e, 0x89, 0x0c, 0x04, 0x37, 0x48, 0x97, 0xf4, 0x5a, 0x6e, 0x59, 0x52, 0x13, + 0x9a, 0xc8, 0xa7, 0xb1, 0x08, 0xb8, 0x32, 0xaa, 0xba, 0xb5, 0xaa, 0xe9, 0x3d, 0x38, 0x4a, 0x50, + 0x8a, 0x34, 0xf1, 0x70, 0xcc, 0x59, 0x84, 0x46, 0x4d, 0x13, 0xda, 0x25, 0xf8, 0x96, 0x45, 0x68, + 0x37, 0xe0, 0x60, 0x18, 0xc5, 0x6a, 0x61, 0xbf, 0x82, 0xce, 0xeb, 0x40, 0xaa, 0x0b, 0x3e, 0xfd, + 0xc0, 0x94, 0xe7, 0xbb, 0x28, 0x63, 0xc1, 0x25, 0x52, 0x07, 0x1a, 0xf9, 0x36, 0xd2, 0x20, 0xdd, + 0x5a, 0xef, 0xb0, 0xdf, 0x71, 0x36, 0xb7, 0x73, 0x06, 0xba, 0x70, 0x4b, 0x92, 0x7d, 0x0e, 0xf5, + 0x1c, 0xa2, 0xc7, 0x50, 0xbd, 0x1c, 0x14, 0x86, 0xab, 0xc1, 0x80, 0xde, 0x82, 0xba, 0x8f, 0x2c, + 0x54, 0x7e, 0xe1, 0xb4, 0xa8, 0xec, 0x47, 0x70, 0x72, 0x11, 0x86, 0xc2, 0x63, 0x0a, 0xcb, 0x85, + 0x2d, 0x80, 0xe2, 0xf7, 0x2e, 0x07, 0xf9, 0xdc, 0x96, 0xbb, 0x81, 0xd8, 0xbf, 0x09, 0x9c, 0xae, + 0x35, 0x85, 0xd3, 0xe7, 0xf0, 0x1f, 0xf2, 0x79, 0x69, 0xb3, 0xb7, 0x6d, 0x73, 0x97, 0xed, 0x0c, + 0xf9, 0x5c, 0x0e, 0xb9, 0x4a, 0x16, 0xae, 0x56, 0xd1, 0x07, 0x50, 0x8f, 0x44, 0xca, 0x95, 0x34, + 0xaa, 0x5a, 0xff, 0xff, 0xb6, 0xfe, 0x4d, 0xd6, 0x73, 0x0b, 0x0a, 0xed, 0xaf, 0x8f, 0x52, 0xd3, + 0x6c, 0x63, 0xdf, 0x51, 0xde, 0xc7, 0xe8, 0xad, 0x0e, 0x63, 0x3e, 0x81, 0xd6, 0x6a, 0x26, 0x3d, + 0x85, 0xda, 0x27, 0x5c, 0x14, 0xc7, 0xc9, 0x1e, 0x69, 0x07, 0x0e, 0xe6, 0x2c, 0x4c, 0xb1, 0x38, + 0x4e, 0x5e, 0x3c, 0xab, 0x3e, 0x25, 0xb6, 0x0f, 0x07, 0x7a, 0x3a, 0xbd, 0x0f, 0xc7, 0x9e, 0xe0, + 0x8a, 0x05, 0x1c, 0x93, 0x71, 0xcc, 0x94, 0x5f, 0xe8, 0x8f, 0x56, 0xe8, 0x88, 0x29, 0x9f, 0xde, + 0x86, 0x96, 0x2f, 0xa4, 0xca, 0x19, 0x45, 0x28, 0x32, 0xa0, 0x6c, 0x26, 0xc8, 0xa6, 0x63, 0xc1, + 0xc3, 0x85, 0x0e, 0x44, 0xd3, 0x6d, 0x66, 0xc0, 0x3b, 0x1e, 0x2e, 0xec, 0x04, 0x60, 0xed, 0xfc, + 0x9f, 0x8c, 0xeb, 0xc2, 0x61, 0x8c, 0x49, 0x14, 0xc8, 0x2c, 0xad, 0xb2, 0x48, 0xe0, 0x26, 0xd4, + 0x1f, 0x41, 0x3b, 0x8f, 0x7b, 0xc2, 0x54, 0x96, 0xe8, 0x17, 0xd0, 0x2c, 0xe3, 0x4f, 0xef, 0x6e, + 0x5f, 0x75, 0xe7, 0xb5, 0x30, 0x77, 0xfe, 0xa2, 0x3c, 0xc7, 0x95, 0xfe, 0x37, 0x02, 0xed, 0x7c, + 0x8d, 0x91, 0x6e, 0xd0, 0x2b, 0x68, 0x6f, 0x46, 0x9b, 0xee, 0xd3, 0x99, 0xf6, 0x36, 0xb8, 0xef, + 0x5d, 0xb0, 0x2b, 0xe7, 0x84, 0x5e, 0x41, 0xb3, 0xcc, 0xd2, 0xae, 0xbf, 0x9d, 0x14, 0x9b, 0xd6, + 0xdf, 0x23, 0x68, 0x57, 0x5e, 0xde, 0xb9, 0xbe, 0xb1, 0xc8, 0x8f, 0x1b, 0xab, 0xf2, 0x65, 0x69, + 0x91, 0xeb, 0xa5, 0x45, 0xbe, 0x2f, 0x2d, 0xf2, 0x73, 0x69, 0x91, 0xaf, 0xbf, 0xac, 0xca, 0xa4, + 0xae, 0x3f, 0x08, 0x8f, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0x17, 0x55, 0xaf, 0xe1, 0x5a, 0x04, + 0x00, 0x00, } diff --git a/pkg/kubelet/apis/deviceplugin/v1alpha1/api.proto b/pkg/kubelet/apis/deviceplugin/v1alpha/api.proto similarity index 91% rename from pkg/kubelet/apis/deviceplugin/v1alpha1/api.proto rename to pkg/kubelet/apis/deviceplugin/v1alpha/api.proto index e222d14a76f..05408cbb1fa 100644 --- a/pkg/kubelet/apis/deviceplugin/v1alpha1/api.proto +++ b/pkg/kubelet/apis/deviceplugin/v1alpha/api.proto @@ -81,25 +81,21 @@ message AllocateRequest { repeated string devicesIDs = 1; } +// AllocateResponse includes the artifacts that needs to be injected into +// a container for accessing 'deviceIDs' that were mentioned as part of +// 'AllocateRequest'. // Failure Handling: // if Kubelet sends an allocation request for dev1 and dev2. // Allocation on dev1 succeeds but allocation on dev2 fails. // The Device plugin should send a ListAndWatch update and fail the // Allocation request message AllocateResponse { - repeated DeviceRuntimeSpec spec = 1; -} - -// The list to be added to the CRI spec -message DeviceRuntimeSpec { - // ID of the Device - string ID = 1; - // List of environment variable to set in the container. - map envs = 2; + // List of environment variable to be set in the container to access one of more devices. + map envs = 1; // Mounts for the container. - repeated Mount mounts = 3; - // Devices for the container - repeated DeviceSpec devices = 4; + repeated Mount mounts = 2; + // Devices for the container. + repeated DeviceSpec devices = 3; } // Mount specifies a host volume to mount into a container. diff --git a/pkg/kubelet/apis/deviceplugin/v1alpha1/constants.go b/pkg/kubelet/apis/deviceplugin/v1alpha/constants.go similarity index 93% rename from pkg/kubelet/apis/deviceplugin/v1alpha1/constants.go rename to pkg/kubelet/apis/deviceplugin/v1alpha/constants.go index fb0440cce59..896354ab901 100644 --- a/pkg/kubelet/apis/deviceplugin/v1alpha1/constants.go +++ b/pkg/kubelet/apis/deviceplugin/v1alpha/constants.go @@ -22,8 +22,8 @@ const ( // UnHealthy means that the device is unhealty Unhealthy = "Unhealthy" - // Version is the API version - Version = "0.1" + // Current version of the API supported by kubelet + Version = "v1alpha2" // DevicePluginPath is the folder the Device Plugin is expecting sockets to be on // Only privileged pods have access to this path // Note: Placeholder until we find a "standard path" diff --git a/pkg/kubelet/apis/deviceplugin/v1alpha1/BUILD b/pkg/kubelet/apis/deviceplugin/v1alpha1/BUILD deleted file mode 100644 index f9677feacf2..00000000000 --- a/pkg/kubelet/apis/deviceplugin/v1alpha1/BUILD +++ /dev/null @@ -1,41 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "api.pb.go", - "constants.go", - ], - importpath = "k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1alpha1", - deps = [ - "//vendor/github.com/gogo/protobuf/gogoproto:go_default_library", - "//vendor/github.com/gogo/protobuf/proto:go_default_library", - "//vendor/github.com/gogo/protobuf/sortkeys:go_default_library", - "//vendor/golang.org/x/net/context:go_default_library", - "//vendor/google.golang.org/grpc:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) - -filegroup( - name = "go_default_library_protos", - srcs = ["api.proto"], - visibility = ["//visibility:public"], -) diff --git a/pkg/kubelet/apis/kubeletconfig/BUILD b/pkg/kubelet/apis/kubeletconfig/BUILD index 252d7c2aabd..bb8b5d591d3 100644 --- a/pkg/kubelet/apis/kubeletconfig/BUILD +++ b/pkg/kubelet/apis/kubeletconfig/BUILD @@ -3,21 +3,21 @@ package(default_visibility = ["//visibility:public"]) load( "@io_bazel_rules_go//go:def.bzl", "go_library", + "go_test", ) go_library( name = "go_default_library", srcs = [ "doc.go", + "helpers.go", "register.go", "types.go", "zz_generated.deepcopy.go", ], importpath = "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig", deps = [ - "//pkg/api:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], @@ -34,9 +34,21 @@ filegroup( name = "all-srcs", srcs = [ ":package-srcs", + "//pkg/kubelet/apis/kubeletconfig/fuzzer:all-srcs", "//pkg/kubelet/apis/kubeletconfig/scheme:all-srcs", "//pkg/kubelet/apis/kubeletconfig/v1alpha1:all-srcs", "//pkg/kubelet/apis/kubeletconfig/validation:all-srcs", ], tags = ["automanaged"], ) + +go_test( + name = "go_default_test", + srcs = ["helpers_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig", + deps = [ + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + ], +) diff --git a/pkg/kubelet/apis/kubeletconfig/doc.go b/pkg/kubelet/apis/kubeletconfig/doc.go index 831e600619a..bd856d83117 100644 --- a/pkg/kubelet/apis/kubeletconfig/doc.go +++ b/pkg/kubelet/apis/kubeletconfig/doc.go @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package package kubeletconfig // import "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" diff --git a/pkg/kubelet/apis/kubeletconfig/fuzzer/BUILD b/pkg/kubelet/apis/kubeletconfig/fuzzer/BUILD new file mode 100644 index 00000000000..2790330cff3 --- /dev/null +++ b/pkg/kubelet/apis/kubeletconfig/fuzzer/BUILD @@ -0,0 +1,32 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["fuzzer.go"], + importpath = "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/fuzzer", + visibility = ["//visibility:public"], + deps = [ + "//pkg/kubelet/apis/kubeletconfig:go_default_library", + "//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library", + "//pkg/kubelet/qos:go_default_library", + "//pkg/kubelet/types:go_default_library", + "//pkg/master/ports:go_default_library", + "//vendor/github.com/google/gofuzz:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/kubelet/apis/kubeletconfig/fuzzer/fuzzer.go b/pkg/kubelet/apis/kubeletconfig/fuzzer/fuzzer.go new file mode 100644 index 00000000000..8fb0ca7ca5b --- /dev/null +++ b/pkg/kubelet/apis/kubeletconfig/fuzzer/fuzzer.go @@ -0,0 +1,100 @@ +/* +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 fuzzer + +import ( + "time" + + "github.com/google/gofuzz" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" + "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" + "k8s.io/kubernetes/pkg/kubelet/qos" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/pkg/master/ports" +) + +// Funcs returns the fuzzer functions for the kubeletconfig apis. +func Funcs(codecs runtimeserializer.CodecFactory) []interface{} { + return []interface{}{ + // provide non-empty values for fields with defaults, so the defaulter doesn't change values during round-trip + func(obj *kubeletconfig.KubeletConfiguration, c fuzz.Continue) { + c.FuzzNoCustom(obj) + obj.ConfigTrialDuration = &metav1.Duration{Duration: 10 * time.Minute} + obj.Authentication.Anonymous.Enabled = true + obj.Authentication.Webhook.Enabled = false + obj.Authentication.Webhook.CacheTTL = metav1.Duration{Duration: 2 * time.Minute} + obj.Authorization.Mode = kubeletconfig.KubeletAuthorizationModeAlwaysAllow + obj.Authorization.Webhook.CacheAuthorizedTTL = metav1.Duration{Duration: 5 * time.Minute} + obj.Authorization.Webhook.CacheUnauthorizedTTL = metav1.Duration{Duration: 30 * time.Second} + obj.Address = "0.0.0.0" + obj.CAdvisorPort = 4194 + obj.VolumeStatsAggPeriod = metav1.Duration{Duration: time.Minute} + obj.RuntimeRequestTimeout = metav1.Duration{Duration: 2 * time.Minute} + obj.CPUCFSQuota = true + obj.EventBurst = 10 + obj.EventRecordQPS = 5 + obj.EnableControllerAttachDetach = true + obj.EnableDebuggingHandlers = true + obj.EnableServer = true + obj.FileCheckFrequency = metav1.Duration{Duration: 20 * time.Second} + obj.HealthzBindAddress = "127.0.0.1" + obj.HealthzPort = 10248 + obj.HostNetworkSources = []string{kubetypes.AllSource} + obj.HostPIDSources = []string{kubetypes.AllSource} + obj.HostIPCSources = []string{kubetypes.AllSource} + obj.HTTPCheckFrequency = metav1.Duration{Duration: 20 * time.Second} + obj.ImageMinimumGCAge = metav1.Duration{Duration: 2 * time.Minute} + obj.ImageGCHighThresholdPercent = 85 + obj.ImageGCLowThresholdPercent = 80 + obj.MaxOpenFiles = 1000000 + obj.MaxPods = 110 + obj.NodeStatusUpdateFrequency = metav1.Duration{Duration: 10 * time.Second} + obj.CPUManagerPolicy = "none" + obj.CPUManagerReconcilePeriod = obj.NodeStatusUpdateFrequency + obj.OOMScoreAdj = int32(qos.KubeletOOMScoreAdj) + obj.Port = ports.KubeletPort + obj.ReadOnlyPort = ports.KubeletReadOnlyPort + obj.RegistryBurst = 10 + obj.RegistryPullQPS = 5 + obj.ResolverConfig = kubetypes.ResolvConfDefault + obj.SerializeImagePulls = true + obj.StreamingConnectionIdleTimeout = metav1.Duration{Duration: 4 * time.Hour} + obj.SyncFrequency = metav1.Duration{Duration: 1 * time.Minute} + obj.ContentType = "application/vnd.kubernetes.protobuf" + obj.KubeAPIQPS = 5 + obj.KubeAPIBurst = 10 + obj.HairpinMode = v1alpha1.PromiscuousBridge + obj.EvictionHard = map[string]string{ + "memory.available": "100Mi", + "nodefs.available": "10%", + "nodefs.inodesFree": "5%", + "imagefs.available": "15%", + } + obj.EvictionPressureTransitionPeriod = metav1.Duration{Duration: 5 * time.Minute} + obj.MakeIPTablesUtilChains = true + obj.IPTablesMasqueradeBit = v1alpha1.DefaultIPTablesMasqueradeBit + obj.IPTablesDropBit = v1alpha1.DefaultIPTablesDropBit + obj.CgroupsPerQOS = true + obj.CgroupDriver = "cgroupfs" + obj.EnforceNodeAllocatable = v1alpha1.DefaultNodeAllocatableEnforcement + obj.ManifestURLHeader = make(map[string][]string) + }, + } +} diff --git a/pkg/kubelet/apis/kubeletconfig/helpers.go b/pkg/kubelet/apis/kubeletconfig/helpers.go new file mode 100644 index 00000000000..392dd4ea2c5 --- /dev/null +++ b/pkg/kubelet/apis/kubeletconfig/helpers.go @@ -0,0 +1,30 @@ +/* +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 kubeletconfig + +// KubeletConfigurationPathRefs returns pointers to all of the KubeletConfiguration fields that contain filepaths. +// You might use this, for example, to resolve all relative paths against some common root before +// passing the configuration to the application. This method must be kept up to date as new fields are added. +func KubeletConfigurationPathRefs(kc *KubeletConfiguration) []*string { + paths := []*string{} + paths = append(paths, &kc.PodManifestPath) + paths = append(paths, &kc.Authentication.X509.ClientCAFile) + paths = append(paths, &kc.TLSCertFile) + paths = append(paths, &kc.TLSPrivateKeyFile) + paths = append(paths, &kc.ResolverConfig) + return paths +} diff --git a/pkg/kubelet/apis/kubeletconfig/helpers_test.go b/pkg/kubelet/apis/kubeletconfig/helpers_test.go new file mode 100644 index 00000000000..5bf10e67a3a --- /dev/null +++ b/pkg/kubelet/apis/kubeletconfig/helpers_test.go @@ -0,0 +1,217 @@ +/* +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 kubeletconfig + +import ( + "reflect" + "strings" + "testing" + + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestKubeletConfigurationPathFields(t *testing.T) { + // ensure the intersection of kubeletConfigurationPathFieldPaths and KubeletConfigurationNonPathFields is empty + if i := kubeletConfigurationPathFieldPaths.Intersection(kubeletConfigurationNonPathFieldPaths); len(i) > 0 { + t.Fatalf("expect the intersection of kubeletConfigurationPathFieldPaths and "+ + "KubeletConfigurationNonPathFields to be emtpy, got:\n%s", + strings.Join(i.List(), "\n")) + } + + // ensure that kubeletConfigurationPathFields U kubeletConfigurationNonPathFields == allPrimitiveFieldPaths(KubeletConfiguration) + expect := sets.NewString().Union(kubeletConfigurationPathFieldPaths).Union(kubeletConfigurationNonPathFieldPaths) + result := allPrimitiveFieldPaths(t, reflect.TypeOf(&KubeletConfiguration{}), nil) + if !expect.Equal(result) { + // expected fields missing from result + missing := expect.Difference(result) + // unexpected fields in result but not specified in expect + unexpected := result.Difference(expect) + if len(missing) > 0 { + t.Errorf("the following fields were expected, but missing from the result. "+ + "If the field has been removed, please remove it from the kubeletConfigurationPathFieldPaths set "+ + "and the KubeletConfigurationPathRefs function, "+ + "or remove it from the kubeletConfigurationNonPathFieldPaths set, as appropriate:\n%s", + strings.Join(missing.List(), "\n")) + } + if len(unexpected) > 0 { + t.Errorf("the following fields were in the result, but unexpected. "+ + "If the field is new, please add it to the kubeletConfigurationPathFieldPaths set "+ + "and the KubeletConfigurationPathRefs function, "+ + "or add it to the kubeletConfigurationNonPathFieldPaths set, as appropriate:\n%s", + strings.Join(unexpected.List(), "\n")) + } + } +} + +func allPrimitiveFieldPaths(t *testing.T, tp reflect.Type, path *field.Path) sets.String { + paths := sets.NewString() + switch tp.Kind() { + case reflect.Ptr: + paths.Insert(allPrimitiveFieldPaths(t, tp.Elem(), path).List()...) + case reflect.Struct: + for i := 0; i < tp.NumField(); i++ { + field := tp.Field(i) + paths.Insert(allPrimitiveFieldPaths(t, field.Type, path.Child(field.Name)).List()...) + } + case reflect.Map, reflect.Slice: + paths.Insert(allPrimitiveFieldPaths(t, tp.Elem(), path.Key("*")).List()...) + case reflect.Interface: + t.Fatalf("unexpected interface{} field %s", path.String()) + default: + // if we hit a primitive type, we're at a leaf + paths.Insert(path.String()) + } + return paths +} + +// dummy helper types +type foo struct { + foo int +} +type bar struct { + str string + strptr *string + + ints []int + stringMap map[string]string + + foo foo + fooptr *foo + + bars []foo + barMap map[string]foo +} + +func TestAllPrimitiveFieldPaths(t *testing.T) { + expect := sets.NewString( + "str", + "strptr", + "ints[*]", + "stringMap[*]", + "foo.foo", + "fooptr.foo", + "bars[*].foo", + "barMap[*].foo", + ) + result := allPrimitiveFieldPaths(t, reflect.TypeOf(&bar{}), nil) + if !expect.Equal(result) { + // expected fields missing from result + missing := expect.Difference(result) + + // unexpected fields in result but not specified in expect + unexpected := result.Difference(expect) + + if len(missing) > 0 { + t.Errorf("the following fields were exepcted, but missing from the result:\n%s", strings.Join(missing.List(), "\n")) + } + if len(unexpected) > 0 { + t.Errorf("the following fields were in the result, but unexpected:\n%s", strings.Join(unexpected.List(), "\n")) + } + } +} + +var ( + // KubeletConfiguration fields that contain file paths. If you update this, also update KubeletConfigurationPathRefs! + kubeletConfigurationPathFieldPaths = sets.NewString( + "PodManifestPath", + "Authentication.X509.ClientCAFile", + "TLSCertFile", + "TLSPrivateKeyFile", + "ResolverConfig", + ) + + // KubeletConfiguration fields that do not contain file paths. + kubeletConfigurationNonPathFieldPaths = sets.NewString( + "Address", + "AllowPrivileged", + "Authentication.Anonymous.Enabled", + "Authentication.Webhook.CacheTTL.Duration", + "Authentication.Webhook.Enabled", + "Authorization.Mode", + "Authorization.Webhook.CacheAuthorizedTTL.Duration", + "Authorization.Webhook.CacheUnauthorizedTTL.Duration", + "CAdvisorPort", + "CPUCFSQuota", + "CPUManagerPolicy", + "CPUManagerReconcilePeriod.Duration", + "CgroupDriver", + "CgroupRoot", + "CgroupsPerQOS", + "ClusterDNS[*]", + "ClusterDomain", + "ConfigTrialDuration.Duration", + "ContentType", + "EnableContentionProfiling", + "EnableControllerAttachDetach", + "EnableDebuggingHandlers", + "EnableServer", + "EnforceNodeAllocatable[*]", + "EventBurst", + "EventRecordQPS", + "EvictionHard[*]", + "EvictionMaxPodGracePeriod", + "EvictionMinimumReclaim[*]", + "EvictionPressureTransitionPeriod.Duration", + "EvictionSoft[*]", + "EvictionSoftGracePeriod[*]", + "FailSwapOn", + "FeatureGates[*]", + "FileCheckFrequency.Duration", + "HTTPCheckFrequency.Duration", + "HairpinMode", + "HealthzBindAddress", + "HealthzPort", + "HostIPCSources[*]", + "HostNetworkSources[*]", + "HostPIDSources[*]", + "IPTablesDropBit", + "IPTablesMasqueradeBit", + "ImageGCHighThresholdPercent", + "ImageGCLowThresholdPercent", + "ImageMinimumGCAge.Duration", + "KubeAPIBurst", + "KubeAPIQPS", + "KubeReservedCgroup", + "KubeReserved[*]", + "KubeletCgroups", + "MakeIPTablesUtilChains", + "ManifestURL", + "ManifestURLHeader[*][*]", + "MaxOpenFiles", + "MaxPods", + "NodeStatusUpdateFrequency.Duration", + "OOMScoreAdj", + "PodCIDR", + "PodsPerCore", + "Port", + "ProtectKernelDefaults", + "ReadOnlyPort", + "RegistryBurst", + "RegistryPullQPS", + "RuntimeRequestTimeout.Duration", + "SerializeImagePulls", + "StreamingConnectionIdleTimeout.Duration", + "SyncFrequency.Duration", + "SystemCgroups", + "SystemReservedCgroup", + "SystemReserved[*]", + "TypeMeta.APIVersion", + "TypeMeta.Kind", + "VolumeStatsAggPeriod.Duration", + ) +) diff --git a/pkg/kubelet/apis/kubeletconfig/scheme/BUILD b/pkg/kubelet/apis/kubeletconfig/scheme/BUILD index ae301abdedf..1b318508c9a 100644 --- a/pkg/kubelet/apis/kubeletconfig/scheme/BUILD +++ b/pkg/kubelet/apis/kubeletconfig/scheme/BUILD @@ -1,4 +1,4 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", @@ -26,3 +26,14 @@ filegroup( tags = ["automanaged"], visibility = ["//visibility:public"], ) + +go_test( + name = "go_default_test", + srcs = ["scheme_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme", + deps = [ + "//pkg/kubelet/apis/kubeletconfig/fuzzer:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/testing/roundtrip:go_default_library", + ], +) diff --git a/pkg/kubelet/apis/kubeletconfig/scheme/scheme_test.go b/pkg/kubelet/apis/kubeletconfig/scheme/scheme_test.go new file mode 100644 index 00000000000..0f593107098 --- /dev/null +++ b/pkg/kubelet/apis/kubeletconfig/scheme/scheme_test.go @@ -0,0 +1,32 @@ +/* +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 scheme + +import ( + "testing" + + "k8s.io/apimachinery/pkg/api/testing/roundtrip" + "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/fuzzer" +) + +func TestRoundTripTypes(t *testing.T) { + scheme, _, err := NewSchemeAndCodecs() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + roundtrip.RoundTripTestForScheme(t, scheme, fuzzer.Funcs) +} diff --git a/pkg/kubelet/apis/kubeletconfig/types.go b/pkg/kubelet/apis/kubeletconfig/types.go index 73fb46334de..f4392f6ed6f 100644 --- a/pkg/kubelet/apis/kubeletconfig/types.go +++ b/pkg/kubelet/apis/kubeletconfig/types.go @@ -17,12 +17,7 @@ limitations under the License. package kubeletconfig import ( - "fmt" - "sort" - "strings" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" ) // HairpinMode denotes how the kubelet should configure networking to handle @@ -70,7 +65,7 @@ type KubeletConfiguration struct { ManifestURL string // manifestURLHeader is the HTTP header to use when accessing the manifest // URL, with the key separated from the value with a ':', as in 'key:value' - ManifestURLHeader string + ManifestURLHeader map[string][]string // enableServer enables the Kubelet's server EnableServer bool // address is the IP address for the Kubelet to serve on (set to 0.0.0.0 @@ -94,8 +89,6 @@ type KubeletConfiguration struct { Authentication KubeletAuthentication // authorization specifies how requests to the Kubelet's server are authorized Authorization KubeletAuthorization - // seccompProfileRoot is the directory path for seccomp profiles. - SeccompProfileRoot string // allowPrivileged enables containers to request privileged mode. // Defaults to false. AllowPrivileged bool @@ -138,8 +131,6 @@ type KubeletConfiguration struct { // oomScoreAdj is The oom-score-adj value for kubelet process. Values // must be within the range [-1000, 1000]. OOMScoreAdj int32 - // registerNode enables automatic registration with the apiserver. - RegisterNode bool // clusterDomain is the DNS domain for this cluster. If set, kubelet will // configure all containers to search this domain in addition to the // host's search domains. @@ -167,9 +158,6 @@ type KubeletConfiguration struct { ImageGCLowThresholdPercent int32 // How frequently to calculate and cache volume disk usage for all pods VolumeStatsAggPeriod metav1.Duration - // volumePluginDir is the full path of the directory in which to search - // for additional third party volume plugins - VolumePluginDir string // KubeletCgroups is the absolute name of cgroups to isolate the kubelet in. // +optional KubeletCgroups string @@ -181,9 +169,6 @@ type KubeletConfiguration struct { // driver that the kubelet uses to manipulate cgroups on the host (cgroupfs or systemd) // +optional CgroupDriver string - // Cgroups that container runtime is expected to be isolated in. - // +optional - RuntimeCgroups string // SystemCgroups is absolute name of cgroups in which to place // all non-kernel processes that are not already in a container. Empty // for no container. Rolling back the flag requires a reboot. @@ -193,8 +178,6 @@ type KubeletConfiguration struct { // If CgroupsPerQOS is enabled, this is the root of the QoS cgroup hierarchy. // +optional CgroupRoot string - // containerRuntime is the container runtime to use. - ContainerRuntime string // CPUManagerPolicy is the name of the policy to use. CPUManagerPolicy string // CPU Manager reconciliation period. @@ -203,22 +186,13 @@ type KubeletConfiguration struct { // requests - pull, logs, exec and attach. // +optional RuntimeRequestTimeout metav1.Duration - // lockFilePath is the path that kubelet will use to as a lock file. - // It uses this file as a lock to synchronize with other kubelet processes - // that may be running. - LockFilePath string - // ExitOnLockContention is a flag that signifies to the kubelet that it is running - // in "bootstrap" mode. This requires that 'LockFilePath' has been set. - // This will cause the kubelet to listen to inotify events on the lock file, - // releasing it and exiting when another process tries to open that file. - ExitOnLockContention bool // How should the kubelet configure the container bridge for hairpin packets. // Setting this flag allows endpoints in a Service to loadbalance back to // themselves if they should try to access their own Service. Values: // "promiscuous-bridge": make the container bridge promiscuous. // "hairpin-veth": set the hairpin flag on container veth interfaces. // "none": do nothing. - // Generally, one must set --hairpin-mode=veth-flag to achieve hairpin NAT, + // Generally, one must set --hairpin-mode=hairpin-veth to achieve hairpin NAT, // because promiscous-bridge assumes the existence of a container bridge named cbr0. HairpinMode string // maxPods is the number of pods that can run on this Kubelet. @@ -227,17 +201,13 @@ type KubeletConfiguration struct { // In cluster mode, this is obtained from the master. PodCIDR string // ResolverConfig is the resolver configuration file used as the basis - // for the container DNS resolution configuration."), [] + // for the container DNS resolution configuration. ResolverConfig string // cpuCFSQuota is Enable CPU CFS quota enforcement for containers that // specify CPU limits CPUCFSQuota bool // maxOpenFiles is Number of files that can be opened by Kubelet process. MaxOpenFiles int64 - // 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. - RegisterWithTaints []api.Taint // contentType is contentType of requests sent to apiserver. ContentType string // kubeAPIQPS is the QPS to use while talking with kubernetes apiserver @@ -250,28 +220,26 @@ type KubeletConfiguration struct { // run docker daemon with version < 1.9 or an Aufs storage backend. // Issue #10959 has more details. SerializeImagePulls bool - // nodeLabels to add when registering the node in the cluster. - NodeLabels map[string]string - // enable gathering custom metrics. - EnableCustomMetrics bool - // Comma-delimited list of hard eviction expressions. For example, 'memory.available<300Mi'. + // Map of signal names to quantities that defines hard eviction thresholds. For example: {"memory.available": "300Mi"}. // +optional - EvictionHard string - // Comma-delimited list of soft eviction expressions. For example, 'memory.available<300Mi'. + EvictionHard map[string]string + // Map of signal names to quantities that defines soft eviction thresholds. For example: {"memory.available": "300Mi"}. // +optional - EvictionSoft string - // Comma-delimeted list of grace periods for each soft eviction signal. For example, 'memory.available=30s'. + EvictionSoft map[string]string + // Map of signal names to quantities that defines grace periods for each soft eviction signal. For example: {"memory.available": "30s"}. // +optional - EvictionSoftGracePeriod string + EvictionSoftGracePeriod map[string]string // Duration for which the kubelet has to wait before transitioning out of an eviction pressure condition. // +optional EvictionPressureTransitionPeriod metav1.Duration // Maximum allowed grace period (in seconds) to use when terminating pods in response to a soft eviction threshold being met. // +optional EvictionMaxPodGracePeriod int32 - // Comma-delimited list of minimum reclaims (e.g. imagefs.available=2Gi) that describes the minimum amount of resource the kubelet will reclaim when performing a pod eviction if that resource is under pressure. + // Map of signal names to quantities that defines minimum reclaims, which describe the minimum + // amount of a given resource the kubelet will reclaim when performing a pod eviction while + // that resource is under pressure. For example: {"imagefs.available": "2Gi"} // +optional - EvictionMinimumReclaim string + EvictionMinimumReclaim map[string]string // Maximum number of pods per core. Cannot exceed MaxPods PodsPerCore int32 // enableControllerAttachDetach enables the Attach/Detach controller to @@ -303,12 +271,12 @@ type KubeletConfiguration struct { // that describe resources reserved for non-kubernetes components. // Currently only cpu and memory are supported. [default=none] // See http://kubernetes.io/docs/user-guide/compute-resources for more detail. - SystemReserved ConfigurationMap + SystemReserved map[string]string // A set of ResourceName=ResourceQuantity (e.g. cpu=200m,memory=150G) pairs // that describe resources reserved for kubernetes system components. // Currently cpu, memory and local ephemeral storage for root file system are supported. [default=none] // See http://kubernetes.io/docs/user-guide/compute-resources for more detail. - KubeReserved ConfigurationMap + KubeReserved map[string]string // This flag helps kubelet identify absolute name of top level cgroup used to enforce `SystemReserved` compute resource reservation for OS system daemons. // Refer to [Node Allocatable](https://git.k8s.io/community/contributors/design-proposals/node/node-allocatable.md) doc for more information. SystemReservedCgroup string @@ -376,33 +344,3 @@ type KubeletAnonymousAuthentication struct { // Anonymous requests have a username of system:anonymous, and a group name of system:unauthenticated. Enabled bool } - -type ConfigurationMap map[string]string - -func (m *ConfigurationMap) String() string { - pairs := []string{} - for k, v := range *m { - pairs = append(pairs, fmt.Sprintf("%s=%s", k, v)) - } - sort.Strings(pairs) - return strings.Join(pairs, ",") -} - -func (m *ConfigurationMap) Set(value string) error { - for _, s := range strings.Split(value, ",") { - if len(s) == 0 { - continue - } - arr := strings.SplitN(s, "=", 2) - if len(arr) == 2 { - (*m)[strings.TrimSpace(arr[0])] = strings.TrimSpace(arr[1]) - } else { - (*m)[strings.TrimSpace(arr[0])] = "" - } - } - return nil -} - -func (*ConfigurationMap) Type() string { - return "mapStringString" -} diff --git a/pkg/kubelet/apis/kubeletconfig/v1alpha1/BUILD b/pkg/kubelet/apis/kubeletconfig/v1alpha1/BUILD index f72ddde62d6..bfab5b2758d 100644 --- a/pkg/kubelet/apis/kubeletconfig/v1alpha1/BUILD +++ b/pkg/kubelet/apis/kubeletconfig/v1alpha1/BUILD @@ -18,13 +18,11 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1", deps = [ - "//pkg/api:go_default_library", "//pkg/kubelet/apis/kubeletconfig:go_default_library", "//pkg/kubelet/qos:go_default_library", "//pkg/kubelet/types:go_default_library", "//pkg/master/ports:go_default_library", "//pkg/util/pointer:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/pkg/kubelet/apis/kubeletconfig/v1alpha1/defaults.go b/pkg/kubelet/apis/kubeletconfig/v1alpha1/defaults.go index 15eff10c0ca..030892ea908 100644 --- a/pkg/kubelet/apis/kubeletconfig/v1alpha1/defaults.go +++ b/pkg/kubelet/apis/kubeletconfig/v1alpha1/defaults.go @@ -17,7 +17,6 @@ limitations under the License. package v1alpha1 import ( - "path/filepath" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -31,20 +30,14 @@ import ( const ( DefaultRootDir = "/var/lib/kubelet" - // DEPRECATED: auto detecting cloud providers goes against the initiative - // for out-of-tree cloud providers as we'll now depend on cAdvisor integrations - // with cloud providers instead of in the core repo. - // More details here: https://github.com/kubernetes/kubernetes/issues/50986 - AutoDetectCloudProvider = "auto-detect" - - defaultIPTablesMasqueradeBit = 14 - defaultIPTablesDropBit = 15 + DefaultIPTablesMasqueradeBit = 14 + DefaultIPTablesDropBit = 15 ) var ( zeroDuration = metav1.Duration{} // Refer to [Node Allocatable](https://git.k8s.io/community/contributors/design-proposals/node/node-allocatable.md) doc for more information. - defaultNodeAllocatableEnforcement = []string{"pods"} + DefaultNodeAllocatableEnforcement = []string{"pods"} ) func addDefaultingFuncs(scheme *kruntime.Scheme) error { @@ -84,9 +77,6 @@ func SetDefaults_KubeletConfiguration(obj *KubeletConfiguration) { if obj.VolumeStatsAggPeriod == zeroDuration { obj.VolumeStatsAggPeriod = metav1.Duration{Duration: time.Minute} } - if obj.ContainerRuntime == "" { - obj.ContainerRuntime = kubetypes.DockerContainerRuntime - } if obj.RuntimeRequestTimeout == zeroDuration { obj.RuntimeRequestTimeout = metav1.Duration{Duration: 2 * time.Minute} } @@ -148,9 +138,6 @@ func SetDefaults_KubeletConfiguration(obj *KubeletConfiguration) { if obj.MaxPods == 0 { obj.MaxPods = 110 } - if obj.VolumePluginDir == "" { - obj.VolumePluginDir = "/usr/libexec/kubernetes/kubelet-plugins/volume/exec/" - } if obj.NodeStatusUpdateFrequency == zeroDuration { obj.NodeStatusUpdateFrequency = metav1.Duration{Duration: 10 * time.Second} } @@ -170,9 +157,6 @@ func SetDefaults_KubeletConfiguration(obj *KubeletConfiguration) { if obj.ReadOnlyPort == nil { obj.ReadOnlyPort = utilpointer.Int32Ptr(ports.KubeletReadOnlyPort) } - if obj.RegisterNode == nil { - obj.RegisterNode = boolVar(true) - } if obj.RegistryBurst == 0 { obj.RegistryBurst = 10 } @@ -186,9 +170,6 @@ func SetDefaults_KubeletConfiguration(obj *KubeletConfiguration) { if obj.SerializeImagePulls == nil { obj.SerializeImagePulls = boolVar(true) } - if obj.SeccompProfileRoot == "" { - obj.SeccompProfileRoot = filepath.Join(DefaultRootDir, "seccomp") - } if obj.StreamingConnectionIdleTimeout == zeroDuration { obj.StreamingConnectionIdleTimeout = metav1.Duration{Duration: 4 * time.Hour} } @@ -209,29 +190,30 @@ func SetDefaults_KubeletConfiguration(obj *KubeletConfiguration) { obj.HairpinMode = PromiscuousBridge } if obj.EvictionHard == nil { - temp := "memory.available<100Mi,nodefs.available<10%,nodefs.inodesFree<5%,imagefs.available<15%" - obj.EvictionHard = &temp + obj.EvictionHard = map[string]string{ + "memory.available": "100Mi", + "nodefs.available": "10%", + "nodefs.inodesFree": "5%", + "imagefs.available": "15%", + } } if obj.EvictionPressureTransitionPeriod == zeroDuration { obj.EvictionPressureTransitionPeriod = metav1.Duration{Duration: 5 * time.Minute} } - if obj.SystemReserved == nil { - obj.SystemReserved = make(map[string]string) - } - if obj.KubeReserved == nil { - obj.KubeReserved = make(map[string]string) - } if obj.MakeIPTablesUtilChains == nil { obj.MakeIPTablesUtilChains = boolVar(true) } if obj.IPTablesMasqueradeBit == nil { - temp := int32(defaultIPTablesMasqueradeBit) + temp := int32(DefaultIPTablesMasqueradeBit) obj.IPTablesMasqueradeBit = &temp } if obj.IPTablesDropBit == nil { - temp := int32(defaultIPTablesDropBit) + temp := int32(DefaultIPTablesDropBit) obj.IPTablesDropBit = &temp } + if obj.FailSwapOn == nil { + obj.FailSwapOn = utilpointer.BoolPtr(true) + } if obj.CgroupsPerQOS == nil { temp := true obj.CgroupsPerQOS = &temp @@ -240,10 +222,7 @@ func SetDefaults_KubeletConfiguration(obj *KubeletConfiguration) { obj.CgroupDriver = "cgroupfs" } if obj.EnforceNodeAllocatable == nil { - obj.EnforceNodeAllocatable = defaultNodeAllocatableEnforcement - } - if obj.FeatureGates == nil { - obj.FeatureGates = make(map[string]bool) + obj.EnforceNodeAllocatable = DefaultNodeAllocatableEnforcement } } diff --git a/pkg/kubelet/apis/kubeletconfig/v1alpha1/doc.go b/pkg/kubelet/apis/kubeletconfig/v1alpha1/doc.go index dc1a1953629..eb9b3913698 100644 --- a/pkg/kubelet/apis/kubeletconfig/v1alpha1/doc.go +++ b/pkg/kubelet/apis/kubeletconfig/v1alpha1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:conversion-gen=k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig // +k8s:openapi-gen=true // +k8s:defaulter-gen=TypeMeta diff --git a/pkg/kubelet/apis/kubeletconfig/v1alpha1/types.go b/pkg/kubelet/apis/kubeletconfig/v1alpha1/types.go index c90fbff0427..bc49f3361a2 100644 --- a/pkg/kubelet/apis/kubeletconfig/v1alpha1/types.go +++ b/pkg/kubelet/apis/kubeletconfig/v1alpha1/types.go @@ -17,7 +17,6 @@ limitations under the License. package v1alpha1 import ( - "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -66,7 +65,7 @@ type KubeletConfiguration struct { ManifestURL string `json:"manifestURL"` // manifestURLHeader is the HTTP header to use when accessing the manifest // URL, with the key separated from the value with a ':', as in 'key:value' - ManifestURLHeader string `json:"manifestURLHeader"` + ManifestURLHeader map[string][]string `json:"manifestURLHeader"` // enableServer enables the Kubelet's server EnableServer *bool `json:"enableServer"` // address is the IP address for the Kubelet to serve on (set to 0.0.0.0 @@ -90,8 +89,6 @@ type KubeletConfiguration struct { Authentication KubeletAuthentication `json:"authentication"` // authorization specifies how requests to the Kubelet's server are authorized Authorization KubeletAuthorization `json:"authorization"` - // seccompProfileRoot is the directory path for seccomp profiles. - SeccompProfileRoot string `json:"seccompProfileRoot"` // allowPrivileged enables containers to request privileged mode. // Defaults to false. AllowPrivileged *bool `json:"allowPrivileged"` @@ -134,8 +131,6 @@ type KubeletConfiguration struct { // oomScoreAdj is The oom-score-adj value for kubelet process. Values // must be within the range [-1000, 1000]. OOMScoreAdj *int32 `json:"oomScoreAdj"` - // registerNode enables automatic registration with the apiserver. - RegisterNode *bool `json:"registerNode"` // clusterDomain is the DNS domain for this cluster. If set, kubelet will // configure all containers to search this domain in addition to the // host's search domains. @@ -164,13 +159,8 @@ type KubeletConfiguration struct { ImageGCLowThresholdPercent *int32 `json:"imageGCLowThresholdPercent"` // How frequently to calculate and cache volume disk usage for all pods VolumeStatsAggPeriod metav1.Duration `json:"volumeStatsAggPeriod"` - // volumePluginDir is the full path of the directory in which to search - // for additional third party volume plugins - VolumePluginDir string `json:"volumePluginDir"` // kubeletCgroups is the absolute name of cgroups to isolate the kubelet in. KubeletCgroups string `json:"kubeletCgroups"` - // runtimeCgroups are cgroups that container runtime is expected to be isolated in. - RuntimeCgroups string `json:"runtimeCgroups"` // systemCgroups is absolute name of cgroups in which to place // all non-kernel processes that are not already in a container. Empty // for no container. Rolling back the flag requires a reboot. @@ -186,8 +176,6 @@ type KubeletConfiguration struct { // driver that the kubelet uses to manipulate cgroups on the host (cgroupfs or systemd) // +optional CgroupDriver string `json:"cgroupDriver,omitempty"` - // containerRuntime is the container runtime to use. - ContainerRuntime string `json:"containerRuntime"` // CPUManagerPolicy is the name of the policy to use. CPUManagerPolicy string `json:"cpuManagerPolicy"` // CPU Manager reconciliation period. @@ -195,22 +183,13 @@ type KubeletConfiguration struct { // runtimeRequestTimeout is the timeout for all runtime requests except long running // requests - pull, logs, exec and attach. RuntimeRequestTimeout metav1.Duration `json:"runtimeRequestTimeout"` - // lockFilePath is the path that kubelet will use to as a lock file. - // It uses this file as a lock to synchronize with other kubelet processes - // that may be running. - LockFilePath *string `json:"lockFilePath"` - // ExitOnLockContention is a flag that signifies to the kubelet that it is running - // in "bootstrap" mode. This requires that 'LockFilePath' has been set. - // This will cause the kubelet to listen to inotify events on the lock file, - // releasing it and exiting when another process tries to open that file. - ExitOnLockContention bool `json:"exitOnLockContention"` // How should the kubelet configure the container bridge for hairpin packets. // Setting this flag allows endpoints in a Service to loadbalance back to // themselves if they should try to access their own Service. Values: // "promiscuous-bridge": make the container bridge promiscuous. // "hairpin-veth": set the hairpin flag on container veth interfaces. // "none": do nothing. - // Generally, one must set --hairpin-mode=veth-flag to achieve hairpin NAT, + // Generally, one must set --hairpin-mode=hairpin-veth to achieve hairpin NAT, // because promiscous-bridge assumes the existence of a container bridge named cbr0. HairpinMode string `json:"hairpinMode"` // maxPods is the number of pods that can run on this Kubelet. @@ -219,17 +198,13 @@ type KubeletConfiguration struct { // In cluster mode, this is obtained from the master. PodCIDR string `json:"podCIDR"` // ResolverConfig is the resolver configuration file used as the basis - // for the container DNS resolution configuration."), [] + // for the container DNS resolution configuration. ResolverConfig string `json:"resolvConf"` // cpuCFSQuota is Enable CPU CFS quota enforcement for containers that // specify CPU limits CPUCFSQuota *bool `json:"cpuCFSQuota"` // maxOpenFiles is Number of files that can be opened by Kubelet process. MaxOpenFiles int64 `json:"maxOpenFiles"` - // 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. - RegisterWithTaints []v1.Taint `json:"registerWithTaints"` // contentType is contentType of requests sent to apiserver. ContentType string `json:"contentType"` // kubeAPIQPS is the QPS to use while talking with kubernetes apiserver @@ -242,22 +217,24 @@ type KubeletConfiguration struct { // run docker daemon with version < 1.9 or an Aufs storage backend. // Issue #10959 has more details. SerializeImagePulls *bool `json:"serializeImagePulls"` - // nodeLabels to add when registering the node in the cluster. - NodeLabels map[string]string `json:"nodeLabels"` - // enable gathering custom metrics. - EnableCustomMetrics bool `json:"enableCustomMetrics"` - // Comma-delimited list of hard eviction expressions. For example, 'memory.available<300Mi'. - EvictionHard *string `json:"evictionHard"` - // Comma-delimited list of soft eviction expressions. For example, 'memory.available<300Mi'. - EvictionSoft string `json:"evictionSoft"` - // Comma-delimeted list of grace periods for each soft eviction signal. For example, 'memory.available=30s'. - EvictionSoftGracePeriod string `json:"evictionSoftGracePeriod"` + // Map of signal names to quantities that defines hard eviction thresholds. For example: {"memory.available": "300Mi"}. + // +optional + EvictionHard map[string]string `json:"evictionHard"` + // Map of signal names to quantities that defines soft eviction thresholds. For example: {"memory.available": "300Mi"}. + // +optional + EvictionSoft map[string]string `json:"evictionSoft"` + // Map of signal names to quantities that defines grace periods for each soft eviction signal. For example: {"memory.available": "30s"}. + // +optional + EvictionSoftGracePeriod map[string]string `json:"evictionSoftGracePeriod"` // Duration for which the kubelet has to wait before transitioning out of an eviction pressure condition. EvictionPressureTransitionPeriod metav1.Duration `json:"evictionPressureTransitionPeriod"` // Maximum allowed grace period (in seconds) to use when terminating pods in response to a soft eviction threshold being met. EvictionMaxPodGracePeriod int32 `json:"evictionMaxPodGracePeriod"` - // Comma-delimited list of minimum reclaims (e.g. imagefs.available=2Gi) that describes the minimum amount of resource the kubelet will reclaim when performing a pod eviction if that resource is under pressure. - EvictionMinimumReclaim string `json:"evictionMinimumReclaim"` + // Map of signal names to quantities that defines minimum reclaims, which describe the minimum + // amount of a given resource the kubelet will reclaim when performing a pod eviction while + // that resource is under pressure. For example: {"imagefs.available": "2Gi"} + // +optional + EvictionMinimumReclaim map[string]string `json:"evictionMinimumReclaim"` // Maximum number of pods per core. Cannot exceed MaxPods PodsPerCore int32 `json:"podsPerCore"` // enableControllerAttachDetach enables the Attach/Detach controller to @@ -281,7 +258,7 @@ type KubeletConfiguration struct { // featureGates is a map of feature names to bools that enable or disable alpha/experimental features. FeatureGates map[string]bool `json:"featureGates,omitempty"` // Tells the Kubelet to fail to start if swap is enabled on the node. - FailSwapOn bool `json:"failSwapOn,omitempty"` + FailSwapOn *bool `json:"failSwapOn,omitempty"` /* following flags are meant for Node Allocatable */ diff --git a/pkg/kubelet/apis/kubeletconfig/v1alpha1/zz_generated.conversion.go b/pkg/kubelet/apis/kubeletconfig/v1alpha1/zz_generated.conversion.go index 5b4aeef5542..b8d27ea68e7 100644 --- a/pkg/kubelet/apis/kubeletconfig/v1alpha1/zz_generated.conversion.go +++ b/pkg/kubelet/apis/kubeletconfig/v1alpha1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,13 +21,12 @@ limitations under the License. package v1alpha1 import ( - core_v1 "k8s.io/api/core/v1" + unsafe "unsafe" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - api "k8s.io/kubernetes/pkg/api" kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" - unsafe "unsafe" ) func init() { @@ -148,7 +147,7 @@ func autoConvert_v1alpha1_KubeletConfiguration_To_kubeletconfig_KubeletConfigura out.FileCheckFrequency = in.FileCheckFrequency out.HTTPCheckFrequency = in.HTTPCheckFrequency out.ManifestURL = in.ManifestURL - out.ManifestURLHeader = in.ManifestURLHeader + out.ManifestURLHeader = *(*map[string][]string)(unsafe.Pointer(&in.ManifestURLHeader)) if err := v1.Convert_Pointer_bool_To_bool(&in.EnableServer, &out.EnableServer, s); err != nil { return err } @@ -165,7 +164,6 @@ func autoConvert_v1alpha1_KubeletConfiguration_To_kubeletconfig_KubeletConfigura if err := Convert_v1alpha1_KubeletAuthorization_To_kubeletconfig_KubeletAuthorization(&in.Authorization, &out.Authorization, s); err != nil { return err } - out.SeccompProfileRoot = in.SeccompProfileRoot if err := v1.Convert_Pointer_bool_To_bool(&in.AllowPrivileged, &out.AllowPrivileged, s); err != nil { return err } @@ -194,9 +192,6 @@ func autoConvert_v1alpha1_KubeletConfiguration_To_kubeletconfig_KubeletConfigura if err := v1.Convert_Pointer_int32_To_int32(&in.OOMScoreAdj, &out.OOMScoreAdj, s); err != nil { return err } - if err := v1.Convert_Pointer_bool_To_bool(&in.RegisterNode, &out.RegisterNode, s); err != nil { - return err - } out.ClusterDomain = in.ClusterDomain out.ClusterDNS = *(*[]string)(unsafe.Pointer(&in.ClusterDNS)) out.StreamingConnectionIdleTimeout = in.StreamingConnectionIdleTimeout @@ -209,23 +204,16 @@ func autoConvert_v1alpha1_KubeletConfiguration_To_kubeletconfig_KubeletConfigura return err } out.VolumeStatsAggPeriod = in.VolumeStatsAggPeriod - out.VolumePluginDir = in.VolumePluginDir out.KubeletCgroups = in.KubeletCgroups - out.RuntimeCgroups = in.RuntimeCgroups out.SystemCgroups = in.SystemCgroups out.CgroupRoot = in.CgroupRoot if err := v1.Convert_Pointer_bool_To_bool(&in.CgroupsPerQOS, &out.CgroupsPerQOS, s); err != nil { return err } out.CgroupDriver = in.CgroupDriver - out.ContainerRuntime = in.ContainerRuntime out.CPUManagerPolicy = in.CPUManagerPolicy out.CPUManagerReconcilePeriod = in.CPUManagerReconcilePeriod out.RuntimeRequestTimeout = in.RuntimeRequestTimeout - if err := v1.Convert_Pointer_string_To_string(&in.LockFilePath, &out.LockFilePath, s); err != nil { - return err - } - out.ExitOnLockContention = in.ExitOnLockContention out.HairpinMode = in.HairpinMode out.MaxPods = in.MaxPods out.PodCIDR = in.PodCIDR @@ -234,7 +222,6 @@ func autoConvert_v1alpha1_KubeletConfiguration_To_kubeletconfig_KubeletConfigura return err } out.MaxOpenFiles = in.MaxOpenFiles - out.RegisterWithTaints = *(*[]api.Taint)(unsafe.Pointer(&in.RegisterWithTaints)) out.ContentType = in.ContentType if err := v1.Convert_Pointer_int32_To_int32(&in.KubeAPIQPS, &out.KubeAPIQPS, s); err != nil { return err @@ -243,16 +230,12 @@ func autoConvert_v1alpha1_KubeletConfiguration_To_kubeletconfig_KubeletConfigura if err := v1.Convert_Pointer_bool_To_bool(&in.SerializeImagePulls, &out.SerializeImagePulls, s); err != nil { return err } - out.NodeLabels = *(*map[string]string)(unsafe.Pointer(&in.NodeLabels)) - out.EnableCustomMetrics = in.EnableCustomMetrics - if err := v1.Convert_Pointer_string_To_string(&in.EvictionHard, &out.EvictionHard, s); err != nil { - return err - } - out.EvictionSoft = in.EvictionSoft - out.EvictionSoftGracePeriod = in.EvictionSoftGracePeriod + out.EvictionHard = *(*map[string]string)(unsafe.Pointer(&in.EvictionHard)) + out.EvictionSoft = *(*map[string]string)(unsafe.Pointer(&in.EvictionSoft)) + out.EvictionSoftGracePeriod = *(*map[string]string)(unsafe.Pointer(&in.EvictionSoftGracePeriod)) out.EvictionPressureTransitionPeriod = in.EvictionPressureTransitionPeriod out.EvictionMaxPodGracePeriod = in.EvictionMaxPodGracePeriod - out.EvictionMinimumReclaim = in.EvictionMinimumReclaim + out.EvictionMinimumReclaim = *(*map[string]string)(unsafe.Pointer(&in.EvictionMinimumReclaim)) out.PodsPerCore = in.PodsPerCore if err := v1.Convert_Pointer_bool_To_bool(&in.EnableControllerAttachDetach, &out.EnableControllerAttachDetach, s); err != nil { return err @@ -268,9 +251,11 @@ func autoConvert_v1alpha1_KubeletConfiguration_To_kubeletconfig_KubeletConfigura return err } out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates)) - out.FailSwapOn = in.FailSwapOn - out.SystemReserved = *(*kubeletconfig.ConfigurationMap)(unsafe.Pointer(&in.SystemReserved)) - out.KubeReserved = *(*kubeletconfig.ConfigurationMap)(unsafe.Pointer(&in.KubeReserved)) + if err := v1.Convert_Pointer_bool_To_bool(&in.FailSwapOn, &out.FailSwapOn, s); err != nil { + return err + } + out.SystemReserved = *(*map[string]string)(unsafe.Pointer(&in.SystemReserved)) + out.KubeReserved = *(*map[string]string)(unsafe.Pointer(&in.KubeReserved)) out.SystemReservedCgroup = in.SystemReservedCgroup out.KubeReservedCgroup = in.KubeReservedCgroup out.EnforceNodeAllocatable = *(*[]string)(unsafe.Pointer(&in.EnforceNodeAllocatable)) @@ -289,7 +274,7 @@ func autoConvert_kubeletconfig_KubeletConfiguration_To_v1alpha1_KubeletConfigura out.FileCheckFrequency = in.FileCheckFrequency out.HTTPCheckFrequency = in.HTTPCheckFrequency out.ManifestURL = in.ManifestURL - out.ManifestURLHeader = in.ManifestURLHeader + out.ManifestURLHeader = *(*map[string][]string)(unsafe.Pointer(&in.ManifestURLHeader)) if err := v1.Convert_bool_To_Pointer_bool(&in.EnableServer, &out.EnableServer, s); err != nil { return err } @@ -306,7 +291,6 @@ func autoConvert_kubeletconfig_KubeletConfiguration_To_v1alpha1_KubeletConfigura if err := Convert_kubeletconfig_KubeletAuthorization_To_v1alpha1_KubeletAuthorization(&in.Authorization, &out.Authorization, s); err != nil { return err } - out.SeccompProfileRoot = in.SeccompProfileRoot if err := v1.Convert_bool_To_Pointer_bool(&in.AllowPrivileged, &out.AllowPrivileged, s); err != nil { return err } @@ -335,9 +319,6 @@ func autoConvert_kubeletconfig_KubeletConfiguration_To_v1alpha1_KubeletConfigura if err := v1.Convert_int32_To_Pointer_int32(&in.OOMScoreAdj, &out.OOMScoreAdj, s); err != nil { return err } - if err := v1.Convert_bool_To_Pointer_bool(&in.RegisterNode, &out.RegisterNode, s); err != nil { - return err - } out.ClusterDomain = in.ClusterDomain out.ClusterDNS = *(*[]string)(unsafe.Pointer(&in.ClusterDNS)) out.StreamingConnectionIdleTimeout = in.StreamingConnectionIdleTimeout @@ -350,23 +331,16 @@ func autoConvert_kubeletconfig_KubeletConfiguration_To_v1alpha1_KubeletConfigura return err } out.VolumeStatsAggPeriod = in.VolumeStatsAggPeriod - out.VolumePluginDir = in.VolumePluginDir out.KubeletCgroups = in.KubeletCgroups if err := v1.Convert_bool_To_Pointer_bool(&in.CgroupsPerQOS, &out.CgroupsPerQOS, s); err != nil { return err } out.CgroupDriver = in.CgroupDriver - out.RuntimeCgroups = in.RuntimeCgroups out.SystemCgroups = in.SystemCgroups out.CgroupRoot = in.CgroupRoot - out.ContainerRuntime = in.ContainerRuntime out.CPUManagerPolicy = in.CPUManagerPolicy out.CPUManagerReconcilePeriod = in.CPUManagerReconcilePeriod out.RuntimeRequestTimeout = in.RuntimeRequestTimeout - if err := v1.Convert_string_To_Pointer_string(&in.LockFilePath, &out.LockFilePath, s); err != nil { - return err - } - out.ExitOnLockContention = in.ExitOnLockContention out.HairpinMode = in.HairpinMode out.MaxPods = in.MaxPods out.PodCIDR = in.PodCIDR @@ -375,7 +349,6 @@ func autoConvert_kubeletconfig_KubeletConfiguration_To_v1alpha1_KubeletConfigura return err } out.MaxOpenFiles = in.MaxOpenFiles - out.RegisterWithTaints = *(*[]core_v1.Taint)(unsafe.Pointer(&in.RegisterWithTaints)) out.ContentType = in.ContentType if err := v1.Convert_int32_To_Pointer_int32(&in.KubeAPIQPS, &out.KubeAPIQPS, s); err != nil { return err @@ -384,16 +357,12 @@ func autoConvert_kubeletconfig_KubeletConfiguration_To_v1alpha1_KubeletConfigura if err := v1.Convert_bool_To_Pointer_bool(&in.SerializeImagePulls, &out.SerializeImagePulls, s); err != nil { return err } - out.NodeLabels = *(*map[string]string)(unsafe.Pointer(&in.NodeLabels)) - out.EnableCustomMetrics = in.EnableCustomMetrics - if err := v1.Convert_string_To_Pointer_string(&in.EvictionHard, &out.EvictionHard, s); err != nil { - return err - } - out.EvictionSoft = in.EvictionSoft - out.EvictionSoftGracePeriod = in.EvictionSoftGracePeriod + out.EvictionHard = *(*map[string]string)(unsafe.Pointer(&in.EvictionHard)) + out.EvictionSoft = *(*map[string]string)(unsafe.Pointer(&in.EvictionSoft)) + out.EvictionSoftGracePeriod = *(*map[string]string)(unsafe.Pointer(&in.EvictionSoftGracePeriod)) out.EvictionPressureTransitionPeriod = in.EvictionPressureTransitionPeriod out.EvictionMaxPodGracePeriod = in.EvictionMaxPodGracePeriod - out.EvictionMinimumReclaim = in.EvictionMinimumReclaim + out.EvictionMinimumReclaim = *(*map[string]string)(unsafe.Pointer(&in.EvictionMinimumReclaim)) out.PodsPerCore = in.PodsPerCore if err := v1.Convert_bool_To_Pointer_bool(&in.EnableControllerAttachDetach, &out.EnableControllerAttachDetach, s); err != nil { return err @@ -409,7 +378,9 @@ func autoConvert_kubeletconfig_KubeletConfiguration_To_v1alpha1_KubeletConfigura return err } out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates)) - out.FailSwapOn = in.FailSwapOn + if err := v1.Convert_bool_To_Pointer_bool(&in.FailSwapOn, &out.FailSwapOn, s); err != nil { + return err + } out.SystemReserved = *(*map[string]string)(unsafe.Pointer(&in.SystemReserved)) out.KubeReserved = *(*map[string]string)(unsafe.Pointer(&in.KubeReserved)) out.SystemReservedCgroup = in.SystemReservedCgroup diff --git a/pkg/kubelet/apis/kubeletconfig/v1alpha1/zz_generated.deepcopy.go b/pkg/kubelet/apis/kubeletconfig/v1alpha1/zz_generated.deepcopy.go index df855d99c01..6165a73880b 100644 --- a/pkg/kubelet/apis/kubeletconfig/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/kubelet/apis/kubeletconfig/v1alpha1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,54 +21,10 @@ limitations under the License. package v1alpha1 import ( - core_v1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KubeletAnonymousAuthentication).DeepCopyInto(out.(*KubeletAnonymousAuthentication)) - return nil - }, InType: reflect.TypeOf(&KubeletAnonymousAuthentication{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KubeletAuthentication).DeepCopyInto(out.(*KubeletAuthentication)) - return nil - }, InType: reflect.TypeOf(&KubeletAuthentication{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KubeletAuthorization).DeepCopyInto(out.(*KubeletAuthorization)) - return nil - }, InType: reflect.TypeOf(&KubeletAuthorization{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KubeletConfiguration).DeepCopyInto(out.(*KubeletConfiguration)) - return nil - }, InType: reflect.TypeOf(&KubeletConfiguration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KubeletWebhookAuthentication).DeepCopyInto(out.(*KubeletWebhookAuthentication)) - return nil - }, InType: reflect.TypeOf(&KubeletWebhookAuthentication{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KubeletWebhookAuthorization).DeepCopyInto(out.(*KubeletWebhookAuthorization)) - return nil - }, InType: reflect.TypeOf(&KubeletWebhookAuthorization{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KubeletX509Authentication).DeepCopyInto(out.(*KubeletX509Authentication)) - return nil - }, InType: reflect.TypeOf(&KubeletX509Authentication{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KubeletAnonymousAuthentication) DeepCopyInto(out *KubeletAnonymousAuthentication) { *out = *in @@ -146,6 +102,18 @@ func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) { out.SyncFrequency = in.SyncFrequency out.FileCheckFrequency = in.FileCheckFrequency out.HTTPCheckFrequency = in.HTTPCheckFrequency + if in.ManifestURLHeader != nil { + in, out := &in.ManifestURLHeader, &out.ManifestURLHeader + *out = make(map[string][]string, len(*in)) + for key, val := range *in { + if val == nil { + (*out)[key] = nil + } else { + (*out)[key] = make([]string, len(val)) + copy((*out)[key], val) + } + } + } if in.EnableServer != nil { in, out := &in.EnableServer, &out.EnableServer if *in == nil { @@ -244,15 +212,6 @@ func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) { **out = **in } } - if in.RegisterNode != nil { - in, out := &in.RegisterNode, &out.RegisterNode - if *in == nil { - *out = nil - } else { - *out = new(bool) - **out = **in - } - } if in.ClusterDNS != nil { in, out := &in.ClusterDNS, &out.ClusterDNS *out = make([]string, len(*in)) @@ -291,15 +250,6 @@ func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) { } out.CPUManagerReconcilePeriod = in.CPUManagerReconcilePeriod out.RuntimeRequestTimeout = in.RuntimeRequestTimeout - if in.LockFilePath != nil { - in, out := &in.LockFilePath, &out.LockFilePath - if *in == nil { - *out = nil - } else { - *out = new(string) - **out = **in - } - } if in.CPUCFSQuota != nil { in, out := &in.CPUCFSQuota, &out.CPUCFSQuota if *in == nil { @@ -309,13 +259,6 @@ func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) { **out = **in } } - if in.RegisterWithTaints != nil { - in, out := &in.RegisterWithTaints, &out.RegisterWithTaints - *out = make([]core_v1.Taint, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } if in.KubeAPIQPS != nil { in, out := &in.KubeAPIQPS, &out.KubeAPIQPS if *in == nil { @@ -334,23 +277,35 @@ func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) { **out = **in } } - if in.NodeLabels != nil { - in, out := &in.NodeLabels, &out.NodeLabels + if in.EvictionHard != nil { + in, out := &in.EvictionHard, &out.EvictionHard *out = make(map[string]string, len(*in)) for key, val := range *in { (*out)[key] = val } } - if in.EvictionHard != nil { - in, out := &in.EvictionHard, &out.EvictionHard - if *in == nil { - *out = nil - } else { - *out = new(string) - **out = **in + if in.EvictionSoft != nil { + in, out := &in.EvictionSoft, &out.EvictionSoft + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.EvictionSoftGracePeriod != nil { + in, out := &in.EvictionSoftGracePeriod, &out.EvictionSoftGracePeriod + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val } } out.EvictionPressureTransitionPeriod = in.EvictionPressureTransitionPeriod + if in.EvictionMinimumReclaim != nil { + in, out := &in.EvictionMinimumReclaim, &out.EvictionMinimumReclaim + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } if in.EnableControllerAttachDetach != nil { in, out := &in.EnableControllerAttachDetach, &out.EnableControllerAttachDetach if *in == nil { @@ -394,6 +349,15 @@ func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) { (*out)[key] = val } } + if in.FailSwapOn != nil { + in, out := &in.FailSwapOn, &out.FailSwapOn + if *in == nil { + *out = nil + } else { + *out = new(bool) + **out = **in + } + } if in.SystemReserved != nil { in, out := &in.SystemReserved, &out.SystemReserved *out = make(map[string]string, len(*in)) diff --git a/pkg/kubelet/apis/kubeletconfig/v1alpha1/zz_generated.defaults.go b/pkg/kubelet/apis/kubeletconfig/v1alpha1/zz_generated.defaults.go index c23f8622ecb..739dadbd606 100644 --- a/pkg/kubelet/apis/kubeletconfig/v1alpha1/zz_generated.defaults.go +++ b/pkg/kubelet/apis/kubeletconfig/v1alpha1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/pkg/kubelet/apis/kubeletconfig/validation/BUILD b/pkg/kubelet/apis/kubeletconfig/validation/BUILD index 7383b8a0c22..cbcbb789c81 100644 --- a/pkg/kubelet/apis/kubeletconfig/validation/BUILD +++ b/pkg/kubelet/apis/kubeletconfig/validation/BUILD @@ -12,7 +12,7 @@ go_library( importpath = "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/validation", deps = [ "//pkg/kubelet/apis/kubeletconfig:go_default_library", - "//pkg/kubelet/cm:go_default_library", + "//pkg/kubelet/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", ], @@ -34,8 +34,8 @@ filegroup( go_test( name = "go_default_test", srcs = ["validation_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/validation", - library = ":go_default_library", deps = [ "//pkg/kubelet/apis/kubeletconfig:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", diff --git a/pkg/kubelet/apis/kubeletconfig/validation/validation.go b/pkg/kubelet/apis/kubeletconfig/validation/validation.go index f0b243081bb..cb4d65bd782 100644 --- a/pkg/kubelet/apis/kubeletconfig/validation/validation.go +++ b/pkg/kubelet/apis/kubeletconfig/validation/validation.go @@ -22,7 +22,7 @@ import ( utilerrors "k8s.io/apimachinery/pkg/util/errors" utilvalidation "k8s.io/apimachinery/pkg/util/validation" "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" - containermanager "k8s.io/kubernetes/pkg/kubelet/cm" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" ) // ValidateKubeletConfiguration validates `kc` and returns an error if it is invalid @@ -91,14 +91,22 @@ func ValidateKubeletConfiguration(kc *kubeletconfig.KubeletConfiguration) error } for _, val := range kc.EnforceNodeAllocatable { switch val { - case containermanager.NodeAllocatableEnforcementKey: - case containermanager.SystemReservedEnforcementKey: - case containermanager.KubeReservedEnforcementKey: + case kubetypes.NodeAllocatableEnforcementKey: + case kubetypes.SystemReservedEnforcementKey: + case kubetypes.KubeReservedEnforcementKey: continue default: allErrors = append(allErrors, fmt.Errorf("Invalid option %q specified for EnforceNodeAllocatable (--enforce-node-allocatable) setting. Valid options are %q, %q or %q", - val, containermanager.NodeAllocatableEnforcementKey, containermanager.SystemReservedEnforcementKey, containermanager.KubeReservedEnforcementKey)) + val, kubetypes.NodeAllocatableEnforcementKey, kubetypes.SystemReservedEnforcementKey, kubetypes.KubeReservedEnforcementKey)) } } + switch kc.HairpinMode { + case kubeletconfig.HairpinNone: + case kubeletconfig.HairpinVeth: + case kubeletconfig.PromiscuousBridge: + default: + allErrors = append(allErrors, fmt.Errorf("Invalid option %q specified for HairpinMode (--hairpin-mode) setting. Valid options are %q, %q or %q", + kc.HairpinMode, kubeletconfig.HairpinNone, kubeletconfig.HairpinVeth, kubeletconfig.PromiscuousBridge)) + } return utilerrors.NewAggregate(allErrors) } diff --git a/pkg/kubelet/apis/kubeletconfig/validation/validation_test.go b/pkg/kubelet/apis/kubeletconfig/validation/validation_test.go index d9bff809d4d..134771fcfb0 100644 --- a/pkg/kubelet/apis/kubeletconfig/validation/validation_test.go +++ b/pkg/kubelet/apis/kubeletconfig/validation/validation_test.go @@ -26,7 +26,7 @@ import ( func TestValidateKubeletConfiguration(t *testing.T) { successCase := &kubeletconfig.KubeletConfiguration{ CgroupsPerQOS: true, - EnforceNodeAllocatable: []string{"pods"}, + EnforceNodeAllocatable: []string{"pods", "system-reserved", "kube-reserved"}, SystemCgroups: "", CgroupRoot: "", CAdvisorPort: 0, @@ -47,6 +47,7 @@ func TestValidateKubeletConfiguration(t *testing.T) { ReadOnlyPort: 0, RegistryBurst: 10, RegistryPullQPS: 5, + HairpinMode: kubeletconfig.PromiscuousBridge, } if allErrors := ValidateKubeletConfiguration(successCase); allErrors != nil { t.Errorf("expect no errors got %v", allErrors) @@ -54,7 +55,7 @@ func TestValidateKubeletConfiguration(t *testing.T) { errorCase := &kubeletconfig.KubeletConfiguration{ CgroupsPerQOS: false, - EnforceNodeAllocatable: []string{"pods"}, + EnforceNodeAllocatable: []string{"pods", "system-reserved", "kube-reserved", "illegal-key"}, SystemCgroups: "/", CgroupRoot: "", CAdvisorPort: -10, @@ -75,8 +76,9 @@ func TestValidateKubeletConfiguration(t *testing.T) { ReadOnlyPort: -10, RegistryBurst: -10, RegistryPullQPS: -10, + HairpinMode: "foo", } - if allErrors := ValidateKubeletConfiguration(errorCase); len(allErrors.(utilerrors.Aggregate).Errors()) != 20 { - t.Errorf("expect 20 errors got %v", len(allErrors.(utilerrors.Aggregate).Errors())) + if allErrors := ValidateKubeletConfiguration(errorCase); len(allErrors.(utilerrors.Aggregate).Errors()) != 22 { + t.Errorf("expect 22 errors got %v", len(allErrors.(utilerrors.Aggregate).Errors())) } } diff --git a/pkg/kubelet/apis/kubeletconfig/zz_generated.deepcopy.go b/pkg/kubelet/apis/kubeletconfig/zz_generated.deepcopy.go index 9be3d24a47c..8a6b1a2a515 100644 --- a/pkg/kubelet/apis/kubeletconfig/zz_generated.deepcopy.go +++ b/pkg/kubelet/apis/kubeletconfig/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,53 +22,9 @@ package kubeletconfig import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - api "k8s.io/kubernetes/pkg/api" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KubeletAnonymousAuthentication).DeepCopyInto(out.(*KubeletAnonymousAuthentication)) - return nil - }, InType: reflect.TypeOf(&KubeletAnonymousAuthentication{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KubeletAuthentication).DeepCopyInto(out.(*KubeletAuthentication)) - return nil - }, InType: reflect.TypeOf(&KubeletAuthentication{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KubeletAuthorization).DeepCopyInto(out.(*KubeletAuthorization)) - return nil - }, InType: reflect.TypeOf(&KubeletAuthorization{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KubeletConfiguration).DeepCopyInto(out.(*KubeletConfiguration)) - return nil - }, InType: reflect.TypeOf(&KubeletConfiguration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KubeletWebhookAuthentication).DeepCopyInto(out.(*KubeletWebhookAuthentication)) - return nil - }, InType: reflect.TypeOf(&KubeletWebhookAuthentication{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KubeletWebhookAuthorization).DeepCopyInto(out.(*KubeletWebhookAuthorization)) - return nil - }, InType: reflect.TypeOf(&KubeletWebhookAuthorization{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KubeletX509Authentication).DeepCopyInto(out.(*KubeletX509Authentication)) - return nil - }, InType: reflect.TypeOf(&KubeletX509Authentication{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KubeletAnonymousAuthentication) DeepCopyInto(out *KubeletAnonymousAuthentication) { *out = *in @@ -137,6 +93,18 @@ func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) { out.SyncFrequency = in.SyncFrequency out.FileCheckFrequency = in.FileCheckFrequency out.HTTPCheckFrequency = in.HTTPCheckFrequency + if in.ManifestURLHeader != nil { + in, out := &in.ManifestURLHeader, &out.ManifestURLHeader + *out = make(map[string][]string, len(*in)) + for key, val := range *in { + if val == nil { + (*out)[key] = nil + } else { + (*out)[key] = make([]string, len(val)) + copy((*out)[key], val) + } + } + } out.Authentication = in.Authentication out.Authorization = in.Authorization if in.HostNetworkSources != nil { @@ -165,21 +133,35 @@ func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) { out.VolumeStatsAggPeriod = in.VolumeStatsAggPeriod out.CPUManagerReconcilePeriod = in.CPUManagerReconcilePeriod out.RuntimeRequestTimeout = in.RuntimeRequestTimeout - if in.RegisterWithTaints != nil { - in, out := &in.RegisterWithTaints, &out.RegisterWithTaints - *out = make([]api.Taint, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) + if in.EvictionHard != nil { + in, out := &in.EvictionHard, &out.EvictionHard + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val } } - if in.NodeLabels != nil { - in, out := &in.NodeLabels, &out.NodeLabels + if in.EvictionSoft != nil { + in, out := &in.EvictionSoft, &out.EvictionSoft + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.EvictionSoftGracePeriod != nil { + in, out := &in.EvictionSoftGracePeriod, &out.EvictionSoftGracePeriod *out = make(map[string]string, len(*in)) for key, val := range *in { (*out)[key] = val } } out.EvictionPressureTransitionPeriod = in.EvictionPressureTransitionPeriod + if in.EvictionMinimumReclaim != nil { + in, out := &in.EvictionMinimumReclaim, &out.EvictionMinimumReclaim + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } if in.FeatureGates != nil { in, out := &in.FeatureGates, &out.FeatureGates *out = make(map[string]bool, len(*in)) @@ -189,14 +171,14 @@ func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) { } if in.SystemReserved != nil { in, out := &in.SystemReserved, &out.SystemReserved - *out = make(ConfigurationMap, len(*in)) + *out = make(map[string]string, len(*in)) for key, val := range *in { (*out)[key] = val } } if in.KubeReserved != nil { in, out := &in.KubeReserved, &out.KubeReserved - *out = make(ConfigurationMap, len(*in)) + *out = make(map[string]string, len(*in)) for key, val := range *in { (*out)[key] = val } diff --git a/pkg/kubelet/apis/stats/v1alpha1/types.go b/pkg/kubelet/apis/stats/v1alpha1/types.go index e08128658d2..916ea822742 100644 --- a/pkg/kubelet/apis/stats/v1alpha1/types.go +++ b/pkg/kubelet/apis/stats/v1alpha1/types.go @@ -86,6 +86,12 @@ type PodStats struct { // +patchMergeKey=name // +patchStrategy=merge Containers []ContainerStats `json:"containers" patchStrategy:"merge" patchMergeKey:"name"` + // Stats pertaining to CPU resources consumed by pod cgroup (which includes all containers' resource usage and pod overhead). + // +optional + CPU *CPUStats `json:"cpu,omitempty"` + // Stats pertaining to memory (RAM) resources consumed by pod cgroup (which includes all containers' resource usage and pod overhead). + // +optional + Memory *MemoryStats `json:"memory,omitempty"` // Stats pertaining to network resources. // +optional Network *NetworkStats `json:"network,omitempty"` @@ -95,6 +101,9 @@ type PodStats struct { // +patchMergeKey=name // +patchStrategy=merge VolumeStats []VolumeStats `json:"volume,omitempty" patchStrategy:"merge" patchMergeKey:"name"` + // EphemeralStorage reports the total filesystem usage for the containers and emptyDir-backed volumes in the measured Pod. + // +optional + EphemeralStorage *FsStats `json:"ephemeral-storage,omitempty"` } // ContainerStats holds container-level unprocessed sample stats. @@ -109,6 +118,8 @@ type ContainerStats struct { // Stats pertaining to memory (RAM) resources. // +optional Memory *MemoryStats `json:"memory,omitempty"` + // Metrics for Accelerators. Each Accelerator corresponds to one element in the array. + Accelerators []AcceleratorStats `json:"accelerators,omitempty"` // Stats pertaining to container rootfs usage of filesystem resources. // Rootfs.UsedBytes is the number of bytes used for the container write layer. // +optional @@ -130,10 +141,10 @@ type PodReference struct { UID string `json:"uid"` } -// NetworkStats contains data about network resources. -type NetworkStats struct { - // The time at which these stats were updated. - Time metav1.Time `json:"time"` +// InterfaceStats contains resource value data about interface. +type InterfaceStats struct { + // The name of the interface + Name string `json:"name"` // Cumulative count of bytes received. // +optional RxBytes *uint64 `json:"rxBytes,omitempty"` @@ -148,6 +159,17 @@ type NetworkStats struct { TxErrors *uint64 `json:"txErrors,omitempty"` } +// NetworkStats contains data about network resources. +type NetworkStats struct { + // The time at which these stats were updated. + Time metav1.Time `json:"time"` + + // Stats for the default interface, if found + InterfaceStats `json:",inline"` + + Interfaces []InterfaceStats `json:"interfaces,omitempty"` +} + // CPUStats contains data about CPU usage. type CPUStats struct { // The time at which these stats were updated. @@ -188,6 +210,30 @@ type MemoryStats struct { MajorPageFaults *uint64 `json:"majorPageFaults,omitempty"` } +// AcceleratorStats contains stats for accelerators attached to the container. +type AcceleratorStats struct { + // Make of the accelerator (nvidia, amd, google etc.) + Make string `json:"make"` + + // Model of the accelerator (tesla-p100, tesla-k80 etc.) + Model string `json:"model"` + + // ID of the accelerator. + ID string `json:"id"` + + // Total accelerator memory. + // unit: bytes + MemoryTotal uint64 `json:"memoryTotal"` + + // Total accelerator memory allocated. + // unit: bytes + MemoryUsed uint64 `json:"memoryUsed"` + + // Percent of time over the past sample period (10s) during which + // the accelerator was actively processing. + DutyCycle uint64 `json:"dutyCycle"` +} + // VolumeStats contains data about Volume filesystem usage. type VolumeStats struct { // Embedded FsStats diff --git a/pkg/kubelet/cadvisor/BUILD b/pkg/kubelet/cadvisor/BUILD index c64cad6afc5..3c430e1ed6b 100644 --- a/pkg/kubelet/cadvisor/BUILD +++ b/pkg/kubelet/cadvisor/BUILD @@ -9,25 +9,63 @@ load( go_library( name = "go_default_library", srcs = [ - "cadvisor_unsupported.go", "doc.go", - "helpers_unsupported.go", "types.go", "util.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ - "cadvisor_linux.go", - "helpers_linux.go", + "@io_bazel_rules_go//go/platform:android": [ + "cadvisor_unsupported.go", + "helpers_unsupported.go", ], - "@io_bazel_rules_go//go/platform:windows_amd64": [ + "@io_bazel_rules_go//go/platform:darwin": [ + "cadvisor_unsupported.go", + "helpers_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "cadvisor_unsupported.go", + "helpers_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "cadvisor_unsupported.go", + "helpers_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "cadvisor_linux.go", + "cadvisor_unsupported.go", + "helpers_linux.go", + "helpers_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "cadvisor_unsupported.go", + "helpers_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "cadvisor_unsupported.go", + "helpers_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "cadvisor_unsupported.go", + "helpers_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "cadvisor_unsupported.go", + "helpers_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "cadvisor_unsupported.go", + "helpers_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ "cadvisor_windows.go", + "helpers_unsupported.go", ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/pkg/kubelet/cadvisor", deps = [ - "//pkg/api/v1/helper:go_default_library", + "//pkg/apis/core/v1/helper:go_default_library", "//pkg/features:go_default_library", + "//pkg/kubelet/types:go_default_library", "//vendor/github.com/google/cadvisor/events:go_default_library", "//vendor/github.com/google/cadvisor/info/v1:go_default_library", "//vendor/github.com/google/cadvisor/info/v2:go_default_library", @@ -35,8 +73,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ - "//pkg/kubelet/types:go_default_library", + "@io_bazel_rules_go//go/platform:linux": [ "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/google/cadvisor/cache/memory:go_default_library", "//vendor/github.com/google/cadvisor/container:go_default_library", @@ -47,7 +84,7 @@ go_library( "//vendor/github.com/google/cadvisor/utils/sysfs:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", ], - "@io_bazel_rules_go//go/platform:windows_amd64": [ + "@io_bazel_rules_go//go/platform:windows": [ "//pkg/kubelet/winstats:go_default_library", ], "//conditions:default": [], @@ -57,15 +94,15 @@ go_library( go_test( name = "go_default_test", srcs = select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "cadvisor_linux_test.go", ], "//conditions:default": [], }), + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/cadvisor", - library = ":go_default_library", deps = select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//pkg/kubelet/types:go_default_library", "//vendor/github.com/google/cadvisor/info/v1:go_default_library", "//vendor/github.com/google/cadvisor/metrics:go_default_library", diff --git a/pkg/kubelet/cadvisor/cadvisor_linux.go b/pkg/kubelet/cadvisor/cadvisor_linux.go index 4749016a49a..948cb651b30 100644 --- a/pkg/kubelet/cadvisor/cadvisor_linux.go +++ b/pkg/kubelet/cadvisor/cadvisor_linux.go @@ -105,11 +105,16 @@ func containerLabels(c *cadvisorapi.ContainerInfo) map[string]string { } // New creates a cAdvisor and exports its API on the specified port if port > 0. -func New(address string, port uint, imageFsInfoProvider ImageFsInfoProvider, rootPath string) (Interface, error) { +func New(address string, port uint, imageFsInfoProvider ImageFsInfoProvider, rootPath string, usingLegacyStats bool) (Interface, error) { sysFs := sysfs.NewRealSysFs() + ignoreMetrics := cadvisormetrics.MetricSet{cadvisormetrics.NetworkTcpUsageMetrics: struct{}{}, cadvisormetrics.NetworkUdpUsageMetrics: struct{}{}} + if !usingLegacyStats { + ignoreMetrics[cadvisormetrics.DiskUsageMetrics] = struct{}{} + } + // Create and start the cAdvisor container manager. - m, err := manager.New(memory.New(statsCacheDuration, nil), sysFs, maxHousekeepingInterval, allowDynamicHousekeeping, cadvisormetrics.MetricSet{cadvisormetrics.NetworkTcpUsageMetrics: struct{}{}, cadvisormetrics.NetworkUdpUsageMetrics: struct{}{}}, http.DefaultClient) + m, err := manager.New(memory.New(statsCacheDuration, nil), sysFs, maxHousekeepingInterval, allowDynamicHousekeeping, ignoreMetrics, http.DefaultClient) if err != nil { return nil, err } diff --git a/pkg/kubelet/cadvisor/cadvisor_unsupported.go b/pkg/kubelet/cadvisor/cadvisor_unsupported.go index 0b653e69e6b..f1ae9486d87 100644 --- a/pkg/kubelet/cadvisor/cadvisor_unsupported.go +++ b/pkg/kubelet/cadvisor/cadvisor_unsupported.go @@ -31,7 +31,7 @@ type cadvisorUnsupported struct { var _ Interface = new(cadvisorUnsupported) -func New(address string, port uint, imageFsInfoProvider ImageFsInfoProvider, rootPath string) (Interface, error) { +func New(address string, port uint, imageFsInfoProvider ImageFsInfoProvider, rootPath string, usingLegacyStats bool) (Interface, error) { return &cadvisorUnsupported{}, nil } diff --git a/pkg/kubelet/cadvisor/cadvisor_windows.go b/pkg/kubelet/cadvisor/cadvisor_windows.go index 52d3cd56050..6ce1f8d1b38 100644 --- a/pkg/kubelet/cadvisor/cadvisor_windows.go +++ b/pkg/kubelet/cadvisor/cadvisor_windows.go @@ -32,7 +32,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(address string, port uint, imageFsInfoProvider ImageFsInfoProvider, rootPath string) (Interface, error) { +func New(address string, port uint, imageFsInfoProvider ImageFsInfoProvider, rootPath string, usingLegacyStats bool) (Interface, error) { client, err := winstats.NewPerfCounterClient() return &cadvisorClient{winStatsClient: client}, err } diff --git a/pkg/kubelet/cadvisor/helpers_linux.go b/pkg/kubelet/cadvisor/helpers_linux.go index ea10588c8c7..bff89894c5e 100644 --- a/pkg/kubelet/cadvisor/helpers_linux.go +++ b/pkg/kubelet/cadvisor/helpers_linux.go @@ -22,6 +22,7 @@ import ( "fmt" cadvisorfs "github.com/google/cadvisor/fs" + "k8s.io/kubernetes/pkg/kubelet/types" ) // imageFsInfoProvider knows how to translate the configured runtime @@ -35,15 +36,15 @@ type imageFsInfoProvider struct { // For remote runtimes, it handles additional runtimes natively understood by cAdvisor. func (i *imageFsInfoProvider) ImageFsInfoLabel() (string, error) { switch i.runtime { - case "docker": + case types.DockerContainerRuntime: return cadvisorfs.LabelDockerImages, nil - case "rkt": + case types.RktContainerRuntime: return cadvisorfs.LabelRktImages, nil - case "remote": + case types.RemoteContainerRuntime: // This is a temporary workaround to get stats for cri-o from cadvisor // and should be removed. // Related to https://github.com/kubernetes/kubernetes/issues/51798 - if i.runtimeEndpoint == "/var/run/crio.sock" { + if i.runtimeEndpoint == CrioSocket { return cadvisorfs.LabelCrioImages, nil } } diff --git a/pkg/kubelet/cadvisor/util.go b/pkg/kubelet/cadvisor/util.go index dd4d7c4b1fd..e4107d5b4a7 100644 --- a/pkg/kubelet/cadvisor/util.go +++ b/pkg/kubelet/cadvisor/util.go @@ -17,13 +17,22 @@ limitations under the License. package cadvisor import ( + goruntime "runtime" + cadvisorapi "github.com/google/cadvisor/info/v1" cadvisorapi2 "github.com/google/cadvisor/info/v2" "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" utilfeature "k8s.io/apiserver/pkg/util/feature" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" "k8s.io/kubernetes/pkg/features" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" +) + +const ( + // Please keep this in sync with the one in: + // github.com/google/cadvisor/container/crio/client.go + CrioSocket = "/var/run/crio/crio.sock" ) func CapacityFromMachineInfo(info *cadvisorapi.MachineInfo) v1.ResourceList { @@ -57,3 +66,16 @@ func EphemeralStorageCapacityFromFsInfo(info cadvisorapi2.FsInfo) v1.ResourceLis } return c } + +// CRI integrations should get container metrics via CRI. Docker +// uses the built-in cadvisor to gather such metrics on Linux for +// historical reasons. +// cri-o relies on cadvisor as a temporary workaround. The code should +// be removed. Related issue: +// https://github.com/kubernetes/kubernetes/issues/51798 +// UsingLegacyCadvisorStats returns true if container stats are provided by cadvisor instead of through the CRI +func UsingLegacyCadvisorStats(runtime, runtimeEndpoint string) bool { + return runtime == kubetypes.RktContainerRuntime || + (runtime == kubetypes.DockerContainerRuntime && goruntime.GOOS == "linux") || + runtimeEndpoint == CrioSocket +} diff --git a/pkg/kubelet/certificate/BUILD b/pkg/kubelet/certificate/BUILD index 4d30fec92ec..ae9d656c342 100644 --- a/pkg/kubelet/certificate/BUILD +++ b/pkg/kubelet/certificate/BUILD @@ -32,8 +32,8 @@ go_library( go_test( name = "go_default_test", srcs = ["transport_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/certificate", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", diff --git a/pkg/kubelet/certificate/bootstrap/BUILD b/pkg/kubelet/certificate/bootstrap/BUILD index 05d24e0c0f4..ac22f80b2cc 100644 --- a/pkg/kubelet/certificate/bootstrap/BUILD +++ b/pkg/kubelet/certificate/bootstrap/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["bootstrap_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/certificate/bootstrap", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", diff --git a/pkg/kubelet/certificate/kubelet.go b/pkg/kubelet/certificate/kubelet.go index 097266c7f4d..b8530074c44 100644 --- a/pkg/kubelet/certificate/kubelet.go +++ b/pkg/kubelet/certificate/kubelet.go @@ -37,8 +37,8 @@ import ( // or returns an error. func NewKubeletServerCertificateManager(kubeClient clientset.Interface, kubeCfg *kubeletconfig.KubeletConfiguration, nodeName types.NodeName, ips []net.IP, hostnames []string, certDirectory string) (certificate.Manager, error) { var certSigningRequestClient clientcertificates.CertificateSigningRequestInterface - if kubeClient != nil && kubeClient.Certificates() != nil { - certSigningRequestClient = kubeClient.Certificates().CertificateSigningRequests() + if kubeClient != nil && kubeClient.CertificatesV1beta1() != nil { + certSigningRequestClient = kubeClient.CertificatesV1beta1().CertificateSigningRequests() } certificateStore, err := certificate.NewFileStore( "kubelet-server", diff --git a/pkg/kubelet/checkpoint/BUILD b/pkg/kubelet/checkpoint/BUILD new file mode 100644 index 00000000000..8886a834dcd --- /dev/null +++ b/pkg/kubelet/checkpoint/BUILD @@ -0,0 +1,42 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["checkpoint.go"], + importpath = "k8s.io/kubernetes/pkg/kubelet/checkpoint", + visibility = ["//visibility:public"], + deps = [ + "//pkg/apis/core:go_default_library", + "//pkg/volume/util:go_default_library", + "//vendor/github.com/dchest/safefile:go_default_library", + "//vendor/github.com/ghodss/yaml:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["checkpoint_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/kubelet/checkpoint", + deps = [ + "//pkg/apis/core:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/kubelet/checkpoint/checkpoint.go b/pkg/kubelet/checkpoint/checkpoint.go new file mode 100644 index 00000000000..d4b30f902d9 --- /dev/null +++ b/pkg/kubelet/checkpoint/checkpoint.go @@ -0,0 +1,151 @@ +/* +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 checkpoint + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "sync" + + "github.com/dchest/safefile" + "github.com/ghodss/yaml" + "github.com/golang/glog" + + "k8s.io/api/core/v1" + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/volume/util" +) + +const ( + // Delimiter used on checkpoints written to disk + delimiter = "_" + podPrefix = "Pod" +) + +// Manager is the interface used to manage checkpoints +// which involves writing resources to disk to recover +// during restart or failure scenarios. +// https://github.com/kubernetes/community/pull/1241/files +type Manager interface { + // LoadPods will load checkpointed Pods from disk + LoadPods() ([]*v1.Pod, error) + + // WritePod will serialize a Pod to disk + WritePod(pod *v1.Pod) error + + // Deletes the checkpoint of the given pod from disk + DeletePod(pod *v1.Pod) error +} + +var instance Manager +var mutex = &sync.Mutex{} + +// fileCheckPointManager - is a checkpointer that writes contents to disk +// The type information of the resource objects are encoded in the name +type fileCheckPointManager struct { + path string +} + +// NewCheckpointManager will create a Manager that points to the following path +func NewCheckpointManager(path string) Manager { + // NOTE: This is a precaution; current implementation should not run + // multiple checkpoint managers. + mutex.Lock() + defer mutex.Unlock() + instance = &fileCheckPointManager{path: path} + return instance +} + +// GetInstance will return the current Manager, there should be only one. +func GetInstance() Manager { + mutex.Lock() + defer mutex.Unlock() + return instance +} + +// loadPod will load Pod Checkpoint yaml file. +func (fcp *fileCheckPointManager) loadPod(file string) (*v1.Pod, error) { + return util.LoadPodFromFile(file) +} + +// checkAnnotations will validate the checkpoint annotations exist on the Pod +func (fcp *fileCheckPointManager) checkAnnotations(pod *v1.Pod) bool { + if podAnnotations := pod.GetAnnotations(); podAnnotations != nil { + if podAnnotations[core.BootstrapCheckpointAnnotationKey] == "true" { + return true + } + } + return false +} + +// getPodPath returns the full qualified path for the pod checkpoint +func (fcp *fileCheckPointManager) getPodPath(pod *v1.Pod) string { + return fmt.Sprintf("%v/Pod%v%v.yaml", fcp.path, delimiter, pod.GetUID()) +} + +// LoadPods Loads All Checkpoints from disk +func (fcp *fileCheckPointManager) LoadPods() ([]*v1.Pod, error) { + checkpoints := make([]*v1.Pod, 0) + files, err := ioutil.ReadDir(fcp.path) + if err != nil { + return nil, err + } + for _, f := range files { + // get just the filename + _, fname := filepath.Split(f.Name()) + // Get just the Resource from "Resource_Name" + fnfields := strings.Split(fname, delimiter) + switch fnfields[0] { + case podPrefix: + pod, err := fcp.loadPod(fmt.Sprintf("%s/%s", fcp.path, f.Name())) + if err != nil { + return nil, err + } + checkpoints = append(checkpoints, pod) + default: + glog.Warningf("Unsupported checkpoint file detected %v", f) + } + } + return checkpoints, nil +} + +// Writes a checkpoint to a file on disk if annotation is present +func (fcp *fileCheckPointManager) WritePod(pod *v1.Pod) error { + var err error + if fcp.checkAnnotations(pod) { + if blob, err := yaml.Marshal(pod); err == nil { + err = safefile.WriteFile(fcp.getPodPath(pod), blob, 0644) + } + } else { + // This is to handle an edge where a pod update could remove + // an annotation and the checkpoint should then be removed. + err = fcp.DeletePod(pod) + } + return err +} + +// Deletes a checkpoint from disk if present +func (fcp *fileCheckPointManager) DeletePod(pod *v1.Pod) error { + podPath := fcp.getPodPath(pod) + if err := os.Remove(podPath); !os.IsNotExist(err) { + return err + } + return nil +} diff --git a/pkg/kubelet/checkpoint/checkpoint_test.go b/pkg/kubelet/checkpoint/checkpoint_test.go new file mode 100644 index 00000000000..23c35c2c019 --- /dev/null +++ b/pkg/kubelet/checkpoint/checkpoint_test.go @@ -0,0 +1,120 @@ +/* +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 checkpoint + +import ( + "io/ioutil" + "os" + "reflect" + "testing" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/pkg/apis/core" +) + +// TestWriteLoadDeletePods validates all combinations of write, load, and delete +func TestWriteLoadDeletePods(t *testing.T) { + testPods := []struct { + pod *v1.Pod + written bool + }{ + { + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "Foo", + Annotations: map[string]string{core.BootstrapCheckpointAnnotationKey: "true"}, + UID: "1", + }, + }, + written: true, + }, + { + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "Foo2", + Annotations: map[string]string{core.BootstrapCheckpointAnnotationKey: "true"}, + UID: "2", + }, + }, + written: true, + }, + { + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "Bar", + UID: "3", + }, + }, + written: false, + }, + } + + dir, err := ioutil.TempDir("", "checkpoint") + if err != nil { + t.Errorf("Failed to allocate temp directory for TestWriteLoadDeletePods error=%v", err) + } + defer os.RemoveAll(dir) + + cp := NewCheckpointManager(dir) + for _, p := range testPods { + // Write pods should always pass unless there is an fs error + if err := cp.WritePod(p.pod); err != nil { + t.Errorf("Failed to Write Pod: %v", err) + } + } + // verify the correct written files are loaded from disk + pods, err := cp.LoadPods() + if err != nil { + t.Errorf("Failed to Load Pods: %v", err) + } + // loop through contents and check make sure + // what was loaded matched the expected results. + for _, p := range testPods { + pname := p.pod.GetName() + var lpod *v1.Pod + for _, check := range pods { + if check.GetName() == pname { + lpod = check + break + } + } + if p.written { + if lpod != nil { + if !reflect.DeepEqual(p.pod, lpod) { + t.Errorf("expected %#v, \ngot %#v", p.pod, lpod) + } + } else { + t.Errorf("Got unexpected result for %v, should have been loaded", pname) + } + } else if lpod != nil { + t.Errorf("Got unexpected result for %v, should not have been loaded", pname) + } + err = cp.DeletePod(p.pod) + if err != nil { + t.Errorf("Failed to delete pod %v", pname) + } + } + // finally validate the contents of the directory is empty. + files, err := ioutil.ReadDir(dir) + if err != nil { + t.Errorf("Failed to read directory %v", dir) + } + if len(files) > 0 { + t.Errorf("Directory %v should be empty but found %#v", dir, files) + } +} diff --git a/pkg/kubelet/client/BUILD b/pkg/kubelet/client/BUILD index 3bf79dc1f08..036817bf99c 100644 --- a/pkg/kubelet/client/BUILD +++ b/pkg/kubelet/client/BUILD @@ -25,8 +25,8 @@ go_test( name = "go_default_test", srcs = ["kubelet_client_test.go"], data = ["//pkg/client/testdata"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/client", - library = ":go_default_library", deps = [ "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", diff --git a/pkg/kubelet/cm/BUILD b/pkg/kubelet/cm/BUILD index d046325f2b9..32a81750635 100644 --- a/pkg/kubelet/cm/BUILD +++ b/pkg/kubelet/cm/BUILD @@ -3,20 +3,38 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = [ - "cgroup_manager_unsupported.go", "container_manager.go", "container_manager_stub.go", - "container_manager_unsupported.go", - "device_plugin_handler.go", - "device_plugin_handler_stub.go", "fake_internal_container_lifecycle.go", - "helpers_unsupported.go", "internal_container_lifecycle.go", "pod_container_manager_stub.go", - "pod_container_manager_unsupported.go", "types.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "cgroup_manager_unsupported.go", + "container_manager_unsupported.go", + "helpers_unsupported.go", + "pod_container_manager_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "cgroup_manager_unsupported.go", + "container_manager_unsupported.go", + "helpers_unsupported.go", + "pod_container_manager_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "cgroup_manager_unsupported.go", + "container_manager_unsupported.go", + "helpers_unsupported.go", + "pod_container_manager_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "cgroup_manager_unsupported.go", + "container_manager_unsupported.go", + "helpers_unsupported.go", + "pod_container_manager_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ "cgroup_manager_linux.go", "container_manager_linux.go", "helpers_linux.go", @@ -24,8 +42,41 @@ go_library( "pod_container_manager_linux.go", "qos_container_manager_linux.go", ], - "@io_bazel_rules_go//go/platform:windows_amd64": [ + "@io_bazel_rules_go//go/platform:nacl": [ + "cgroup_manager_unsupported.go", + "container_manager_unsupported.go", + "helpers_unsupported.go", + "pod_container_manager_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "cgroup_manager_unsupported.go", + "container_manager_unsupported.go", + "helpers_unsupported.go", + "pod_container_manager_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "cgroup_manager_unsupported.go", + "container_manager_unsupported.go", + "helpers_unsupported.go", + "pod_container_manager_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "cgroup_manager_unsupported.go", + "container_manager_unsupported.go", + "helpers_unsupported.go", + "pod_container_manager_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "cgroup_manager_unsupported.go", + "container_manager_unsupported.go", + "helpers_unsupported.go", + "pod_container_manager_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "cgroup_manager_unsupported.go", "container_manager_windows.go", + "helpers_unsupported.go", + "pod_container_manager_unsupported.go", ], "//conditions:default": [], }), @@ -34,32 +85,52 @@ go_library( deps = [ "//pkg/features:go_default_library", "//pkg/kubelet/apis/cri:go_default_library", - "//pkg/kubelet/apis/deviceplugin/v1alpha1:go_default_library", - "//pkg/kubelet/apis/kubeletconfig:go_default_library", - "//pkg/kubelet/cadvisor:go_default_library", "//pkg/kubelet/cm/cpumanager:go_default_library", + "//pkg/kubelet/config:go_default_library", "//pkg/kubelet/container:go_default_library", - "//pkg/kubelet/deviceplugin:go_default_library", "//pkg/kubelet/eviction/api:go_default_library", + "//pkg/kubelet/lifecycle:go_default_library", "//pkg/kubelet/status:go_default_library", - "//pkg/util/mount:go_default_library", + "//pkg/scheduler/schedulercache:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", - "//vendor/k8s.io/client-go/tools/record:go_default_library", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ - "//pkg/api/v1/helper:go_default_library", - "//pkg/api/v1/helper/qos:go_default_library", + "@io_bazel_rules_go//go/platform:android": [ + "//pkg/kubelet/cadvisor:go_default_library", + "//pkg/util/mount:go_default_library", + "//vendor/k8s.io/client-go/tools/record:go_default_library", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "//pkg/kubelet/cadvisor:go_default_library", + "//pkg/util/mount:go_default_library", + "//vendor/k8s.io/client-go/tools/record:go_default_library", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "//pkg/kubelet/cadvisor:go_default_library", + "//pkg/util/mount:go_default_library", + "//vendor/k8s.io/client-go/tools/record:go_default_library", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "//pkg/kubelet/cadvisor:go_default_library", + "//pkg/util/mount:go_default_library", + "//vendor/k8s.io/client-go/tools/record:go_default_library", + ], + "@io_bazel_rules_go//go/platform:linux": [ "//pkg/api/v1/resource:go_default_library", + "//pkg/apis/core/v1/helper:go_default_library", + "//pkg/apis/core/v1/helper/qos:go_default_library", + "//pkg/kubelet/cadvisor:go_default_library", + "//pkg/kubelet/cm/deviceplugin:go_default_library", "//pkg/kubelet/cm/util:go_default_library", "//pkg/kubelet/events:go_default_library", "//pkg/kubelet/metrics:go_default_library", "//pkg/kubelet/qos:go_default_library", + "//pkg/kubelet/types:go_default_library", "//pkg/util/file:go_default_library", + "//pkg/util/mount:go_default_library", "//pkg/util/oom:go_default_library", "//pkg/util/procfs:go_default_library", "//pkg/util/sysctl:go_default_library", @@ -69,8 +140,40 @@ go_library( "//vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs:go_default_library", "//vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd:go_default_library", "//vendor/github.com/opencontainers/runc/libcontainer/configs:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/client-go/tools/record:go_default_library", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "//pkg/kubelet/cadvisor:go_default_library", + "//pkg/util/mount:go_default_library", + "//vendor/k8s.io/client-go/tools/record:go_default_library", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "//pkg/kubelet/cadvisor:go_default_library", + "//pkg/util/mount:go_default_library", + "//vendor/k8s.io/client-go/tools/record:go_default_library", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "//pkg/kubelet/cadvisor:go_default_library", + "//pkg/util/mount:go_default_library", + "//vendor/k8s.io/client-go/tools/record:go_default_library", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "//pkg/kubelet/cadvisor:go_default_library", + "//pkg/util/mount:go_default_library", + "//vendor/k8s.io/client-go/tools/record:go_default_library", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "//pkg/kubelet/cadvisor:go_default_library", + "//pkg/util/mount:go_default_library", + "//vendor/k8s.io/client-go/tools/record:go_default_library", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "//pkg/kubelet/cadvisor:go_default_library", + "//pkg/util/mount:go_default_library", + "//vendor/k8s.io/client-go/tools/record:go_default_library", ], "//conditions:default": [], }), @@ -78,11 +181,8 @@ go_library( go_test( name = "go_default_test", - srcs = [ - "container_manager_unsupported_test.go", - "device_plugin_handler_test.go", - ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + srcs = select({ + "@io_bazel_rules_go//go/platform:linux": [ "cgroup_manager_linux_test.go", "cgroup_manager_test.go", "container_manager_linux_test.go", @@ -91,22 +191,16 @@ go_test( ], "//conditions:default": [], }), + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/cm", - library = ":go_default_library", - deps = [ - "//pkg/kubelet/apis/deviceplugin/v1alpha1:go_default_library", - "//pkg/util/mount:go_default_library", - "//vendor/github.com/stretchr/testify/assert:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library", - ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ - "//pkg/kubelet/apis/kubeletconfig:go_default_library", + deps = select({ + "@io_bazel_rules_go//go/platform:linux": [ "//pkg/kubelet/eviction/api:go_default_library", + "//pkg/util/mount:go_default_library", + "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/github.com/stretchr/testify/require:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", ], "//conditions:default": [], }), @@ -125,6 +219,7 @@ filegroup( ":package-srcs", "//pkg/kubelet/cm/cpumanager:all-srcs", "//pkg/kubelet/cm/cpuset:all-srcs", + "//pkg/kubelet/cm/deviceplugin:all-srcs", "//pkg/kubelet/cm/util:all-srcs", ], tags = ["automanaged"], diff --git a/pkg/kubelet/cm/OWNERS b/pkg/kubelet/cm/OWNERS new file mode 100644 index 00000000000..307f9436c00 --- /dev/null +++ b/pkg/kubelet/cm/OWNERS @@ -0,0 +1,10 @@ +approvers: +- Random-Liu +- dchen1107 +- derekwaynecarr +- tallclair +- vishh +- yujuhong +- ConnorDoyle +reviewers: +- sig-node-reviewers diff --git a/pkg/kubelet/cm/cgroup_manager_linux.go b/pkg/kubelet/cm/cgroup_manager_linux.go index 62894e5a3dd..d1d99713429 100644 --- a/pkg/kubelet/cm/cgroup_manager_linux.go +++ b/pkg/kubelet/cm/cgroup_manager_linux.go @@ -45,6 +45,8 @@ const ( libcontainerCgroupfs libcontainerCgroupManagerType = "cgroupfs" // libcontainerSystemd means use libcontainer with systemd libcontainerSystemd libcontainerCgroupManagerType = "systemd" + // systemdSuffix is the cgroup name suffix for systemd + systemdSuffix string = ".slice" ) // hugePageSizeList is useful for converting to the hugetlb canonical unit @@ -68,8 +70,8 @@ func ConvertCgroupNameToSystemd(cgroupName CgroupName, outputToCgroupFs bool) st } // detect if we are given a systemd style name. // if so, we do not want to do double encoding. - if strings.HasSuffix(part, ".slice") { - part = strings.TrimSuffix(part, ".slice") + if IsSystemdStyleName(part) { + part = strings.TrimSuffix(part, systemdSuffix) separatorIndex := strings.LastIndex(part, "-") if separatorIndex >= 0 && separatorIndex < len(part) { part = part[separatorIndex+1:] @@ -87,8 +89,8 @@ func ConvertCgroupNameToSystemd(cgroupName CgroupName, outputToCgroupFs bool) st result = "-" } // always have a .slice suffix - if !strings.HasSuffix(result, ".slice") { - result = result + ".slice" + if !IsSystemdStyleName(result) { + result = result + systemdSuffix } // if the caller desired the result in cgroupfs format... @@ -114,6 +116,13 @@ func ConvertCgroupFsNameToSystemd(cgroupfsName string) (string, error) { return path.Base(cgroupfsName), nil } +func IsSystemdStyleName(name string) bool { + if strings.HasSuffix(name, systemdSuffix) { + return true + } + return false +} + // libcontainerAdapter provides a simplified interface to libcontainer based on libcontainer type. type libcontainerAdapter struct { // cgroupManagerType defines how to interface with libcontainer @@ -151,15 +160,18 @@ func (l *libcontainerAdapter) revertName(name string) CgroupName { if l.cgroupManagerType != libcontainerSystemd { return CgroupName(name) } + return CgroupName(RevertFromSystemdToCgroupStyleName(name)) +} +func RevertFromSystemdToCgroupStyleName(name string) string { driverName, err := ConvertCgroupFsNameToSystemd(name) if err != nil { panic(err) } - driverName = strings.TrimSuffix(driverName, ".slice") + driverName = strings.TrimSuffix(driverName, systemdSuffix) driverName = strings.Replace(driverName, "-", "/", -1) driverName = strings.Replace(driverName, "_", "-", -1) - return CgroupName(driverName) + return driverName } // adaptName converts a CgroupName identifier to a driver specific conversion value. diff --git a/pkg/kubelet/cm/cgroup_manager_linux_test.go b/pkg/kubelet/cm/cgroup_manager_linux_test.go index ed60c4d50e8..36c5a7c30c8 100644 --- a/pkg/kubelet/cm/cgroup_manager_linux_test.go +++ b/pkg/kubelet/cm/cgroup_manager_linux_test.go @@ -99,3 +99,35 @@ func TestLibcontainerAdapterAdaptToSystemdAsCgroupFs(t *testing.T) { } } } + +func TestLibcontainerAdapterNotAdaptToSystemd(t *testing.T) { + cgroupfs := newLibcontainerAdapter(libcontainerCgroupfs) + otherAdatper := newLibcontainerAdapter(libcontainerCgroupManagerType("test")) + + testCases := []struct { + input string + expected string + }{ + { + input: "/", + expected: "/", + }, + { + input: "/Burstable", + expected: "/Burstable", + }, + { + input: "", + expected: "", + }, + } + for _, testCase := range testCases { + if actual := cgroupfs.adaptName(CgroupName(testCase.input), true); actual != testCase.expected { + t.Errorf("Unexpected result, input: %v, expected: %v, actual: %v", testCase.input, testCase.expected, actual) + } + + if actual := otherAdatper.adaptName(CgroupName(testCase.input), true); actual != testCase.expected { + t.Errorf("Unexpected result, input: %v, expected: %v, actual: %v", testCase.input, testCase.expected, actual) + } + } +} diff --git a/pkg/kubelet/cm/cgroup_manager_test.go b/pkg/kubelet/cm/cgroup_manager_test.go index cad32008866..3b356f539bd 100644 --- a/pkg/kubelet/cm/cgroup_manager_test.go +++ b/pkg/kubelet/cm/cgroup_manager_test.go @@ -23,52 +23,49 @@ import ( "testing" "k8s.io/api/core/v1" - "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" ) func Test(t *testing.T) { tests := []struct { - input string + input map[string]string expected *map[v1.ResourceName]int64 }{ { - input: "memory", + input: map[string]string{"memory": ""}, expected: nil, }, { - input: "memory=a", + input: map[string]string{"memory": "a"}, expected: nil, }, { - input: "memory=a%", + input: map[string]string{"memory": "a%"}, expected: nil, }, { - input: "memory=200%", + input: map[string]string{"memory": "200%"}, expected: nil, }, { - input: "memory=0%", + input: map[string]string{"memory": "0%"}, expected: &map[v1.ResourceName]int64{ v1.ResourceMemory: 0, }, }, { - input: "memory=100%", + input: map[string]string{"memory": "100%"}, expected: &map[v1.ResourceName]int64{ v1.ResourceMemory: 100, }, }, { // need to change this when CPU is added as a supported resource - input: "memory=100%,cpu=50%", + input: map[string]string{"memory": "100%", "cpu": "50%"}, expected: nil, }, } for _, test := range tests { - m := kubeletconfig.ConfigurationMap{} - m.Set(test.input) - actual, err := ParseQOSReserved(m) + actual, err := ParseQOSReserved(test.input) if actual != nil && test.expected == nil { t.Errorf("Unexpected success, input: %v, expected: %v, actual: %v, err: %v", test.input, test.expected, actual, err) } diff --git a/pkg/kubelet/cm/cgroup_manager_unsupported.go b/pkg/kubelet/cm/cgroup_manager_unsupported.go index 6a567e94b15..34345985cf9 100644 --- a/pkg/kubelet/cm/cgroup_manager_unsupported.go +++ b/pkg/kubelet/cm/cgroup_manager_unsupported.go @@ -77,3 +77,11 @@ func ConvertCgroupFsNameToSystemd(cgroupfsName string) (string, error) { func ConvertCgroupNameToSystemd(cgroupName CgroupName, outputToCgroupFs bool) string { return "" } + +func RevertFromSystemdToCgroupStyleName(name string) string { + return "" +} + +func IsSystemdStyleName(name string) bool { + return false +} diff --git a/pkg/kubelet/cm/container_manager.go b/pkg/kubelet/cm/container_manager.go index eb556ef001e..fd61f2a751b 100644 --- a/pkg/kubelet/cm/container_manager.go +++ b/pkg/kubelet/cm/container_manager.go @@ -23,10 +23,12 @@ import ( // TODO: Migrate kubelet to either use its own internal objects or client library. "k8s.io/api/core/v1" internalapi "k8s.io/kubernetes/pkg/kubelet/apis/cri" - "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" + "k8s.io/kubernetes/pkg/kubelet/config" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" evictionapi "k8s.io/kubernetes/pkg/kubelet/eviction/api" + "k8s.io/kubernetes/pkg/kubelet/lifecycle" "k8s.io/kubernetes/pkg/kubelet/status" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" "fmt" "strconv" @@ -40,7 +42,7 @@ type ContainerManager interface { // Runs the container manager's housekeeping. // - Ensures that the Docker daemon is in a container. // - Creates the system container where all non-containerized processes run. - Start(*v1.Node, ActivePodsFunc, status.PodStatusProvider, internalapi.RuntimeService) error + Start(*v1.Node, ActivePodsFunc, config.SourcesReady, status.PodStatusProvider, internalapi.RuntimeService) error // SystemCgroupsLimit returns resources allocated to system cgroups in the machine. // These cgroups include the system and Kubernetes services. @@ -68,13 +70,24 @@ type ContainerManager interface { // GetCapacity returns the amount of compute resources tracked by container manager available on the node. GetCapacity() v1.ResourceList + // GetDevicePluginResourceCapacity returns the amount of device plugin resources available on the node + // and inactive device plugin resources previously registered on the node. + GetDevicePluginResourceCapacity() (v1.ResourceList, []string) + // UpdateQOSCgroups performs housekeeping updates to ensure that the top // level QoS containers have their desired state in a thread-safe way UpdateQOSCgroups() error // GetResources returns RunContainerOptions with devices, mounts, and env fields populated for // extended resources required by container. - GetResources(pod *v1.Pod, container *v1.Container, activePods []*v1.Pod) (*kubecontainer.RunContainerOptions, error) + GetResources(pod *v1.Pod, container *v1.Container) (*kubecontainer.RunContainerOptions, error) + + // UpdatePluginResources calls Allocate of device plugin handler for potential + // requests for device plugin resources, and returns an error if fails. + // Otherwise, it updates allocatableResource in nodeInfo if necessary, + // to make sure it is at least equal to the pod's requested capacity for + // any registered device plugin resource + UpdatePluginResources(*schedulercache.NodeInfo, *lifecycle.PodAdmitAttributes) error InternalContainerLifecycle() InternalContainerLifecycle } @@ -87,6 +100,7 @@ type NodeConfig struct { CgroupsPerQOS bool CgroupRoot string CgroupDriver string + KubeletRootDir string ProtectKernelDefaults bool NodeAllocatableConfig ExperimentalQOSReserved map[v1.ResourceName]int64 @@ -108,18 +122,7 @@ type Status struct { SoftRequirements error } -const ( - // Uer visible keys for managing node allocatable enforcement on the node. - NodeAllocatableEnforcementKey = "pods" - SystemReservedEnforcementKey = "system-reserved" - KubeReservedEnforcementKey = "kube-reserved" -) - -// containerManager for the kubelet is currently an injected dependency. -// We need to parse the --qos-reserve-requests option in -// cmd/kubelet/app/server.go and there isn't really a good place to put -// the code. If/When the kubelet dependency injection gets worked out, -// maybe there will be a better place for it. +// parsePercentage parses the percentage string to numeric value. func parsePercentage(v string) (int64, error) { if !strings.HasSuffix(v, "%") { return 0, fmt.Errorf("percentage expected, got '%s'", v) @@ -135,7 +138,7 @@ func parsePercentage(v string) (int64, error) { } // ParseQOSReserved parses the --qos-reserve-requests option -func ParseQOSReserved(m kubeletconfig.ConfigurationMap) (*map[v1.ResourceName]int64, error) { +func ParseQOSReserved(m map[string]string) (*map[v1.ResourceName]int64, error) { reservations := make(map[v1.ResourceName]int64) for k, v := range m { switch v1.ResourceName(k) { diff --git a/pkg/kubelet/cm/container_manager_linux.go b/pkg/kubelet/cm/container_manager_linux.go index 61a34fc079a..f72ec699c96 100644 --- a/pkg/kubelet/cm/container_manager_linux.go +++ b/pkg/kubelet/cm/container_manager_linux.go @@ -45,10 +45,14 @@ import ( internalapi "k8s.io/kubernetes/pkg/kubelet/apis/cri" "k8s.io/kubernetes/pkg/kubelet/cadvisor" "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager" + "k8s.io/kubernetes/pkg/kubelet/cm/deviceplugin" cmutil "k8s.io/kubernetes/pkg/kubelet/cm/util" + "k8s.io/kubernetes/pkg/kubelet/config" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/lifecycle" "k8s.io/kubernetes/pkg/kubelet/qos" "k8s.io/kubernetes/pkg/kubelet/status" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" utilfile "k8s.io/kubernetes/pkg/util/file" "k8s.io/kubernetes/pkg/util/mount" "k8s.io/kubernetes/pkg/util/oom" @@ -110,7 +114,7 @@ type containerManagerImpl struct { qosContainers QOSContainersInfo // Tasks that are run periodically periodicTasks []func() - // holds all the mounted cgroup subsystems + // Holds all the mounted cgroup subsystems subsystems *CgroupSubsystems nodeInfo *v1.Node // Interface for cgroup management @@ -125,7 +129,7 @@ type containerManagerImpl struct { // Interface for QoS cgroup management qosContainerManager QOSContainerManager // Interface for exporting and allocating devices reported by device plugins. - devicePluginHandler DevicePluginHandler + devicePluginManager deviceplugin.Manager // Interface for CPU affinity management. cpuManager cpumanager.Manager } @@ -242,7 +246,7 @@ func NewContainerManager(mountUtil mount.Interface, cadvisorInterface cadvisor.I } glog.Infof("Creating Container Manager object based on Node Config: %+v", nodeConfig) - qosContainerManager, err := NewQOSContainerManager(subsystems, cgroupRoot, nodeConfig) + qosContainerManager, err := NewQOSContainerManager(subsystems, cgroupRoot, nodeConfig, cgroupManager) if err != nil { return nil, err } @@ -259,23 +263,11 @@ func NewContainerManager(mountUtil mount.Interface, cadvisorInterface cadvisor.I qosContainerManager: qosContainerManager, } - updateDeviceCapacityFunc := func(updates v1.ResourceList) { - cm.Lock() - defer cm.Unlock() - for k, v := range updates { - if v.Value() <= 0 { - delete(cm.capacity, k) - } else { - cm.capacity[k] = v - } - } - } - - glog.Infof("Creating device plugin handler: %t", devicePluginEnabled) + glog.Infof("Creating device plugin manager: %t", devicePluginEnabled) if devicePluginEnabled { - cm.devicePluginHandler, err = NewDevicePluginHandlerImpl(updateDeviceCapacityFunc) + cm.devicePluginManager, err = deviceplugin.NewManagerImpl() } else { - cm.devicePluginHandler, err = NewDevicePluginHandlerStub() + cm.devicePluginManager, err = deviceplugin.NewManagerStub() } if err != nil { return nil, err @@ -288,6 +280,7 @@ func NewContainerManager(mountUtil mount.Interface, cadvisorInterface cadvisor.I nodeConfig.ExperimentalCPUManagerReconcilePeriod, machineInfo, cm.GetNodeAllocatableReservation(), + nodeConfig.KubeletRootDir, ) if err != nil { glog.Errorf("failed to initialize cpu manager: %v", err) @@ -526,6 +519,7 @@ func (cm *containerManagerImpl) Status() Status { func (cm *containerManagerImpl) Start(node *v1.Node, activePods ActivePodsFunc, + sourcesReady config.SourcesReady, podStatusProvider status.PodStatusProvider, runtimeService internalapi.RuntimeService) error { @@ -593,7 +587,7 @@ func (cm *containerManagerImpl) Start(node *v1.Node, }, time.Second, stopChan) // Starts device plugin manager. - if err := cm.devicePluginHandler.Start(); err != nil { + if err := cm.devicePluginManager.Start(deviceplugin.ActivePodsFunc(activePods), sourcesReady); err != nil { return err } return nil @@ -614,75 +608,24 @@ func (cm *containerManagerImpl) setFsCapacity() error { } // TODO: move the GetResources logic to PodContainerManager. -func (cm *containerManagerImpl) GetResources(pod *v1.Pod, container *v1.Container, activePods []*v1.Pod) (*kubecontainer.RunContainerOptions, error) { +func (cm *containerManagerImpl) GetResources(pod *v1.Pod, container *v1.Container) (*kubecontainer.RunContainerOptions, error) { opts := &kubecontainer.RunContainerOptions{} - // Gets devices, mounts, and envs from device plugin handler. - glog.V(3).Infof("Calling devicePluginHandler AllocateDevices") - // Maps to detect duplicate settings. - devsMap := make(map[string]string) - mountsMap := make(map[string]string) - envsMap := make(map[string]string) - allocResps, err := cm.devicePluginHandler.Allocate(pod, container, activePods) - if err != nil { - return opts, err - } - // Loops through AllocationResponses of all required extended resources. - for _, resp := range allocResps { - // Loops through runtime spec of all devices of the given resource. - for _, devRuntime := range resp.Spec { - // Updates RunContainerOptions.Devices. - for _, dev := range devRuntime.Devices { - if d, ok := devsMap[dev.ContainerPath]; ok { - glog.V(3).Infof("skip existing device %s %s", dev.ContainerPath, dev.HostPath) - if d != dev.HostPath { - glog.Errorf("Container device %s has conflicting mapping host devices: %s and %s", - dev.ContainerPath, d, dev.HostPath) - } - continue - } - devsMap[dev.ContainerPath] = dev.HostPath - opts.Devices = append(opts.Devices, kubecontainer.DeviceInfo{ - PathOnHost: dev.HostPath, - PathInContainer: dev.ContainerPath, - Permissions: dev.Permissions, - }) - } - // Updates RunContainerOptions.Mounts. - for _, mount := range devRuntime.Mounts { - if m, ok := mountsMap[mount.ContainerPath]; ok { - glog.V(3).Infof("skip existing mount %s %s", mount.ContainerPath, mount.HostPath) - if m != mount.HostPath { - glog.Errorf("Container mount %s has conflicting mapping host mounts: %s and %s", - mount.ContainerPath, m, mount.HostPath) - } - continue - } - mountsMap[mount.ContainerPath] = mount.HostPath - opts.Mounts = append(opts.Mounts, kubecontainer.Mount{ - Name: mount.ContainerPath, - ContainerPath: mount.ContainerPath, - HostPath: mount.HostPath, - ReadOnly: mount.ReadOnly, - SELinuxRelabel: false, - }) - } - // Updates RunContainerOptions.Envs. - for k, v := range devRuntime.Envs { - if e, ok := envsMap[k]; ok { - glog.V(3).Infof("skip existing envs %s %s", k, v) - if e != v { - glog.Errorf("Environment variable %s has conflicting setting: %s and %s", k, e, v) - } - continue - } - envsMap[k] = v - opts.Envs = append(opts.Envs, kubecontainer.EnvVar{Name: k, Value: v}) - } - } + // Allocate should already be called during predicateAdmitHandler.Admit(), + // just try to fetch device runtime information from cached state here + devOpts := cm.devicePluginManager.GetDeviceRunContainerOptions(pod, container) + if devOpts == nil { + return opts, nil } + opts.Devices = append(opts.Devices, devOpts.Devices...) + opts.Mounts = append(opts.Mounts, devOpts.Mounts...) + opts.Envs = append(opts.Envs, devOpts.Envs...) return opts, nil } +func (cm *containerManagerImpl) UpdatePluginResources(node *schedulercache.NodeInfo, attrs *lifecycle.PodAdmitAttributes) error { + return cm.devicePluginManager.Allocate(node, attrs) +} + func (cm *containerManagerImpl) SystemCgroupsLimit() v1.ResourceList { cpuLimit := int64(0) @@ -943,3 +886,7 @@ func (cm *containerManagerImpl) GetCapacity() v1.ResourceList { defer cm.RUnlock() return cm.capacity } + +func (cm *containerManagerImpl) GetDevicePluginResourceCapacity() (v1.ResourceList, []string) { + return cm.devicePluginManager.GetCapacity() +} diff --git a/pkg/kubelet/cm/container_manager_stub.go b/pkg/kubelet/cm/container_manager_stub.go index ee5d7c73bf6..83df4cfaad8 100644 --- a/pkg/kubelet/cm/container_manager_stub.go +++ b/pkg/kubelet/cm/container_manager_stub.go @@ -22,15 +22,18 @@ import ( internalapi "k8s.io/kubernetes/pkg/kubelet/apis/cri" "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager" + "k8s.io/kubernetes/pkg/kubelet/config" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/lifecycle" "k8s.io/kubernetes/pkg/kubelet/status" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" ) type containerManagerStub struct{} var _ ContainerManager = &containerManagerStub{} -func (cm *containerManagerStub) Start(_ *v1.Node, _ ActivePodsFunc, _ status.PodStatusProvider, _ internalapi.RuntimeService) error { +func (cm *containerManagerStub) Start(_ *v1.Node, _ ActivePodsFunc, _ config.SourcesReady, _ status.PodStatusProvider, _ internalapi.RuntimeService) error { glog.V(2).Infof("Starting stub container manager") return nil } @@ -67,14 +70,22 @@ func (cm *containerManagerStub) GetCapacity() v1.ResourceList { return nil } +func (cm *containerManagerStub) GetDevicePluginResourceCapacity() (v1.ResourceList, []string) { + return nil, []string{} +} + func (cm *containerManagerStub) NewPodContainerManager() PodContainerManager { return &podContainerManagerStub{} } -func (cm *containerManagerStub) GetResources(pod *v1.Pod, container *v1.Container, activePods []*v1.Pod) (*kubecontainer.RunContainerOptions, error) { +func (cm *containerManagerStub) GetResources(pod *v1.Pod, container *v1.Container) (*kubecontainer.RunContainerOptions, error) { return &kubecontainer.RunContainerOptions{}, nil } +func (cm *containerManagerStub) UpdatePluginResources(*schedulercache.NodeInfo, *lifecycle.PodAdmitAttributes) error { + return nil +} + func (cm *containerManagerStub) InternalContainerLifecycle() InternalContainerLifecycle { return &internalContainerLifecycleImpl{cpumanager.NewFakeManager()} } diff --git a/pkg/kubelet/cm/container_manager_unsupported.go b/pkg/kubelet/cm/container_manager_unsupported.go index 0602bb5de97..97a3a3a6337 100644 --- a/pkg/kubelet/cm/container_manager_unsupported.go +++ b/pkg/kubelet/cm/container_manager_unsupported.go @@ -25,65 +25,21 @@ import ( "k8s.io/client-go/tools/record" internalapi "k8s.io/kubernetes/pkg/kubelet/apis/cri" "k8s.io/kubernetes/pkg/kubelet/cadvisor" - "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager" - kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/config" "k8s.io/kubernetes/pkg/kubelet/status" "k8s.io/kubernetes/pkg/util/mount" ) type unsupportedContainerManager struct { + containerManagerStub } var _ ContainerManager = &unsupportedContainerManager{} -func (unsupportedContainerManager) Start(_ *v1.Node, _ ActivePodsFunc, _ status.PodStatusProvider, _ internalapi.RuntimeService) error { +func (unsupportedContainerManager) Start(_ *v1.Node, _ ActivePodsFunc, _ config.SourcesReady, _ status.PodStatusProvider, _ internalapi.RuntimeService) error { return fmt.Errorf("Container Manager is unsupported in this build") } -func (unsupportedContainerManager) SystemCgroupsLimit() v1.ResourceList { - return v1.ResourceList{} -} - -func (unsupportedContainerManager) GetNodeConfig() NodeConfig { - return NodeConfig{} -} - -func (unsupportedContainerManager) GetMountedSubsystems() *CgroupSubsystems { - return &CgroupSubsystems{} -} - -func (unsupportedContainerManager) GetQOSContainersInfo() QOSContainersInfo { - return QOSContainersInfo{} -} - -func (unsupportedContainerManager) UpdateQOSCgroups() error { - return nil -} - -func (cm *unsupportedContainerManager) Status() Status { - return Status{} -} - -func (cm *unsupportedContainerManager) GetNodeAllocatableReservation() v1.ResourceList { - return nil -} - -func (cm *unsupportedContainerManager) GetCapacity() v1.ResourceList { - return nil -} - -func (cm *unsupportedContainerManager) NewPodContainerManager() PodContainerManager { - return &unsupportedPodContainerManager{} -} - -func (cm *unsupportedContainerManager) GetResources(pod *v1.Pod, container *v1.Container, activePods []*v1.Pod) (*kubecontainer.RunContainerOptions, error) { - return &kubecontainer.RunContainerOptions{}, nil -} - -func (cm *unsupportedContainerManager) InternalContainerLifecycle() InternalContainerLifecycle { - return &internalContainerLifecycleImpl{cpumanager.NewFakeManager()} -} - func NewContainerManager(_ mount.Interface, _ cadvisor.Interface, _ NodeConfig, failSwapOn bool, devicePluginEnabled bool, recorder record.EventRecorder) (ContainerManager, error) { return &unsupportedContainerManager{}, nil } diff --git a/pkg/kubelet/cm/container_manager_unsupported_test.go b/pkg/kubelet/cm/container_manager_unsupported_test.go deleted file mode 100644 index ed8a4fb8701..00000000000 --- a/pkg/kubelet/cm/container_manager_unsupported_test.go +++ /dev/null @@ -1,101 +0,0 @@ -// +build !linux,!windows - -/* -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 cm - -import ( - "fmt" - - "k8s.io/kubernetes/pkg/util/mount" -) - -type fakeMountInterface struct { - mountPoints []mount.MountPoint -} - -func (mi *fakeMountInterface) Mount(source string, target string, fstype string, options []string) error { - return fmt.Errorf("unsupported") -} - -func (mi *fakeMountInterface) Unmount(target string) error { - return fmt.Errorf("unsupported") -} - -func (mi *fakeMountInterface) List() ([]mount.MountPoint, error) { - return mi.mountPoints, nil -} - -func (f *fakeMountInterface) IsMountPointMatch(mp mount.MountPoint, dir string) bool { - return (mp.Path == dir) -} - -func (f *fakeMountInterface) IsNotMountPoint(dir string) (bool, error) { - return false, fmt.Errorf("unsupported") -} - -func (mi *fakeMountInterface) IsLikelyNotMountPoint(file string) (bool, error) { - return false, fmt.Errorf("unsupported") -} - -func (mi *fakeMountInterface) DeviceOpened(pathname string) (bool, error) { - for _, mp := range mi.mountPoints { - if mp.Device == pathname { - return true, nil - } - } - return false, nil -} - -func (mi *fakeMountInterface) PathIsDevice(pathname string) (bool, error) { - return true, nil -} - -func (mi *fakeMountInterface) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) { - return "", nil -} - -func (mi *fakeMountInterface) MakeRShared(path string) error { - return nil -} - -func fakeContainerMgrMountInt() mount.Interface { - return &fakeMountInterface{ - []mount.MountPoint{ - { - Device: "cgroup", - Type: "cgroup", - Opts: []string{"rw", "relatime", "cpuset"}, - }, - { - Device: "cgroup", - Type: "cgroup", - Opts: []string{"rw", "relatime", "cpu"}, - }, - { - Device: "cgroup", - Type: "cgroup", - Opts: []string{"rw", "relatime", "cpuacct"}, - }, - { - Device: "cgroup", - Type: "cgroup", - Opts: []string{"rw", "relatime", "memory"}, - }, - }, - } -} diff --git a/pkg/kubelet/cm/container_manager_windows.go b/pkg/kubelet/cm/container_manager_windows.go index 8102ceca2dd..56bdbb9d363 100644 --- a/pkg/kubelet/cm/container_manager_windows.go +++ b/pkg/kubelet/cm/container_manager_windows.go @@ -25,6 +25,7 @@ import ( "k8s.io/client-go/tools/record" internalapi "k8s.io/kubernetes/pkg/kubelet/apis/cri" "k8s.io/kubernetes/pkg/kubelet/cadvisor" + "k8s.io/kubernetes/pkg/kubelet/config" "k8s.io/kubernetes/pkg/kubelet/status" "k8s.io/kubernetes/pkg/util/mount" ) @@ -35,7 +36,7 @@ type containerManagerImpl struct { var _ ContainerManager = &containerManagerImpl{} -func (cm *containerManagerImpl) Start(_ *v1.Node, _ ActivePodsFunc, _ status.PodStatusProvider, _ internalapi.RuntimeService) error { +func (cm *containerManagerImpl) Start(_ *v1.Node, _ ActivePodsFunc, _ config.SourcesReady, _ status.PodStatusProvider, _ internalapi.RuntimeService) error { glog.V(2).Infof("Starting Windows stub container manager") return nil } diff --git a/pkg/kubelet/cm/cpumanager/BUILD b/pkg/kubelet/cm/cpumanager/BUILD index 64a5f7e1bbc..d3330084825 100644 --- a/pkg/kubelet/cm/cpumanager/BUILD +++ b/pkg/kubelet/cm/cpumanager/BUILD @@ -13,7 +13,7 @@ go_library( importpath = "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager", visibility = ["//visibility:public"], deps = [ - "//pkg/api/v1/helper/qos:go_default_library", + "//pkg/apis/core/v1/helper/qos:go_default_library", "//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library", "//pkg/kubelet/cm/cpumanager/state:go_default_library", "//pkg/kubelet/cm/cpumanager/topology:go_default_library", @@ -36,13 +36,14 @@ go_test( "policy_static_test.go", "policy_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager", - library = ":go_default_library", deps = [ "//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library", "//pkg/kubelet/cm/cpumanager/state:go_default_library", "//pkg/kubelet/cm/cpumanager/topology:go_default_library", "//pkg/kubelet/cm/cpuset:go_default_library", + "//vendor/github.com/google/cadvisor/info/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/kubelet/cm/cpumanager/cpu_manager.go b/pkg/kubelet/cm/cpumanager/cpu_manager.go index 353f5ee1e0c..6c59dca18d4 100644 --- a/pkg/kubelet/cm/cpumanager/cpu_manager.go +++ b/pkg/kubelet/cm/cpumanager/cpu_manager.go @@ -33,6 +33,7 @@ import ( "k8s.io/kubernetes/pkg/kubelet/cm/cpuset" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/kubelet/status" + "path" ) // ActivePodsFunc is a function that returns a list of pods to reconcile. @@ -44,6 +45,9 @@ type runtimeService interface { type policyName string +// CPUManagerStateFileName is the name file name where cpu manager stores it's state +const CPUManagerStateFileName = "cpu_manager_state" + // Manager interface provides methods for Kubelet to manage pod cpus. type Manager interface { // Start is called during Kubelet initialization. @@ -94,12 +98,7 @@ type manager struct { var _ Manager = &manager{} // NewManager creates new cpu manager based on provided policy -func NewManager( - cpuPolicyName string, - reconcilePeriod time.Duration, - machineInfo *cadvisorapi.MachineInfo, - nodeAllocatableReservation v1.ResourceList, -) (Manager, error) { +func NewManager(cpuPolicyName string, reconcilePeriod time.Duration, machineInfo *cadvisorapi.MachineInfo, nodeAllocatableReservation v1.ResourceList, stateFileDirecory string) (Manager, error) { var policy Policy switch policyName(cpuPolicyName) { @@ -115,18 +114,16 @@ func NewManager( glog.Infof("[cpumanager] detected CPU topology: %v", topo) reservedCPUs, ok := nodeAllocatableReservation[v1.ResourceCPU] if !ok { - // The static policy cannot initialize without this information. Panic! - panic("[cpumanager] unable to determine reserved CPU resources for static policy") + // The static policy cannot initialize without this information. + return nil, fmt.Errorf("[cpumanager] unable to determine reserved CPU resources for static policy") } if reservedCPUs.IsZero() { - // Panic! - // // The static policy requires this to be nonzero. Zero CPU reservation // would allow the shared pool to be completely exhausted. At that point // either we would violate our guarantee of exclusivity or need to evict // any pod that has at least one container that requires zero CPUs. // See the comments in policy_static.go for more details. - panic("[cpumanager] the static policy requires systemreserved.cpu + kubereserved.cpu to be greater than zero") + return nil, fmt.Errorf("[cpumanager] the static policy requires systemreserved.cpu + kubereserved.cpu to be greater than zero") } // Take the ceiling of the reservation, since fractional CPUs cannot be @@ -140,10 +137,14 @@ func NewManager( policy = NewNonePolicy() } + stateImpl := state.NewFileState( + path.Join(stateFileDirecory, CPUManagerStateFileName), + policy.Name()) + manager := &manager{ policy: policy, reconcilePeriod: reconcilePeriod, - state: state.NewMemoryState(), + state: stateImpl, machineInfo: machineInfo, nodeAllocatableReservation: nodeAllocatableReservation, } @@ -151,8 +152,8 @@ func NewManager( } func (m *manager) Start(activePods ActivePodsFunc, podStatusProvider status.PodStatusProvider, containerRuntime runtimeService) { - glog.Infof("[cpumanger] starting with %s policy", m.policy.Name()) - glog.Infof("[cpumanger] reconciling every %v", m.reconcilePeriod) + glog.Infof("[cpumanager] starting with %s policy", m.policy.Name()) + glog.Infof("[cpumanager] reconciling every %v", m.reconcilePeriod) m.activePods = activePods m.podStatusProvider = podStatusProvider @@ -233,6 +234,25 @@ func (m *manager) reconcileState() (success []reconciledContainer, failure []rec continue } + // Check whether container is present in state, there may be 3 reasons why it's not present: + // - policy does not want to track the container + // - kubelet has just been restarted - and there is no previous state file + // - container has been removed from state by RemoveContainer call (DeletionTimestamp is set) + if _, ok := m.state.GetCPUSet(containerID); !ok { + if status.Phase == v1.PodRunning && pod.DeletionTimestamp == nil { + glog.V(4).Infof("[cpumanager] reconcileState: container is not present in state - trying to add (pod: %s, container: %s, container id: %s)", pod.Name, container.Name, containerID) + err := m.AddContainer(pod, &container, containerID) + if err != nil { + glog.Errorf("[cpumanager] reconcileState: failed to add container (pod: %s, container: %s, container id: %s, error: %v)", pod.Name, container.Name, containerID, err) + failure = append(failure, reconciledContainer{pod.Name, container.Name, containerID}) + } + } else { + // if DeletionTimestamp is set, pod has already been removed from state + // skip the pod/container since it's not running and will be deleted soon + continue + } + } + cset := m.state.GetCPUSetOrDefault(containerID) if cset.IsEmpty() { // NOTE: This should not happen outside of tests. @@ -241,7 +261,7 @@ func (m *manager) reconcileState() (success []reconciledContainer, failure []rec continue } - glog.Infof("[cpumanager] reconcileState: updating container (pod: %s, container: %s, container id: %s, cpuset: \"%v\")", pod.Name, container.Name, containerID, cset) + glog.V(4).Infof("[cpumanager] reconcileState: updating container (pod: %s, container: %s, container id: %s, cpuset: \"%v\")", pod.Name, container.Name, containerID, cset) err = m.updateContainerCPUSet(containerID, cset) if err != nil { glog.Errorf("[cpumanager] reconcileState: failed to update container (pod: %s, container: %s, container id: %s, cpuset: \"%v\", error: %v)", pod.Name, container.Name, containerID, cset, err) diff --git a/pkg/kubelet/cm/cpumanager/cpu_manager_test.go b/pkg/kubelet/cm/cpumanager/cpu_manager_test.go index a763bbfaf36..9381ea470bc 100644 --- a/pkg/kubelet/cm/cpumanager/cpu_manager_test.go +++ b/pkg/kubelet/cm/cpumanager/cpu_manager_test.go @@ -19,8 +19,12 @@ package cpumanager import ( "fmt" "reflect" + "strings" "testing" + "time" + cadvisorapi "github.com/google/cadvisor/info/v1" + "io/ioutil" "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -29,10 +33,11 @@ import ( runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/state" "k8s.io/kubernetes/pkg/kubelet/cm/cpuset" + "os" ) type mockState struct { - assignments map[string]cpuset.CPUSet + assignments state.ContainerCPUAssignments defaultCPUSet cpuset.CPUSet } @@ -64,6 +69,19 @@ func (s *mockState) Delete(containerID string) { delete(s.assignments, containerID) } +func (s *mockState) ClearState() { + s.defaultCPUSet = cpuset.CPUSet{} + s.assignments = make(state.ContainerCPUAssignments) +} + +func (s *mockState) SetCPUAssignments(a state.ContainerCPUAssignments) { + s.assignments = a.Clone() +} + +func (s *mockState) GetCPUAssignments() state.ContainerCPUAssignments { + return s.assignments.Clone() +} + type mockPolicy struct { err error } @@ -190,7 +208,7 @@ func TestCPUManagerAdd(t *testing.T) { err: testCase.regErr, }, state: &mockState{ - assignments: map[string]cpuset.CPUSet{}, + assignments: state.ContainerCPUAssignments{}, defaultCPUSet: cpuset.NewCPUSet(), }, containerRuntime: mockRuntimeService{ @@ -210,13 +228,120 @@ func TestCPUManagerAdd(t *testing.T) { } } +func TestCPUManagerGenerate(t *testing.T) { + testCases := []struct { + description string + cpuPolicyName string + nodeAllocatableReservation v1.ResourceList + isTopologyBroken bool + expectedPolicy string + expectedError error + skipIfPermissionsError bool + }{ + { + description: "set none policy", + cpuPolicyName: "none", + nodeAllocatableReservation: nil, + expectedPolicy: "none", + }, + { + description: "invalid policy name", + cpuPolicyName: "invalid", + nodeAllocatableReservation: nil, + expectedPolicy: "none", + }, + { + description: "static policy", + cpuPolicyName: "static", + nodeAllocatableReservation: v1.ResourceList{v1.ResourceCPU: *resource.NewQuantity(3, resource.DecimalSI)}, + expectedPolicy: "static", + skipIfPermissionsError: true, + }, + { + description: "static policy - broken topology", + cpuPolicyName: "static", + nodeAllocatableReservation: v1.ResourceList{}, + isTopologyBroken: true, + expectedError: fmt.Errorf("could not detect number of cpus"), + skipIfPermissionsError: true, + }, + { + description: "static policy - broken reservation", + cpuPolicyName: "static", + nodeAllocatableReservation: v1.ResourceList{}, + expectedError: fmt.Errorf("unable to determine reserved CPU resources for static policy"), + skipIfPermissionsError: true, + }, + { + description: "static policy - no CPU resources", + cpuPolicyName: "static", + nodeAllocatableReservation: v1.ResourceList{v1.ResourceCPU: *resource.NewQuantity(0, resource.DecimalSI)}, + expectedError: fmt.Errorf("the static policy requires systemreserved.cpu + kubereserved.cpu to be greater than zero"), + skipIfPermissionsError: true, + }, + } + + mockedMachineInfo := 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}, + }, + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.description, func(t *testing.T) { + machineInfo := &mockedMachineInfo + if testCase.isTopologyBroken { + machineInfo = &cadvisorapi.MachineInfo{} + } + sDir, err := ioutil.TempDir("/tmp/", "cpu_manager_test") + if err != nil { + t.Errorf("cannot create state file: %s", err.Error()) + } + defer os.RemoveAll(sDir) + + mgr, err := NewManager(testCase.cpuPolicyName, 5*time.Second, machineInfo, testCase.nodeAllocatableReservation, sDir) + if testCase.expectedError != nil { + if !strings.Contains(err.Error(), testCase.expectedError.Error()) { + t.Errorf("Unexpected error message. Have: %s wants %s", err.Error(), testCase.expectedError.Error()) + } + } else { + rawMgr := mgr.(*manager) + if rawMgr.policy.Name() != testCase.expectedPolicy { + t.Errorf("Unexpected policy name. Have: %q wants %q", rawMgr.policy.Name(), testCase.expectedPolicy) + } + } + }) + + } +} + func TestCPUManagerRemove(t *testing.T) { mgr := &manager{ policy: &mockPolicy{ err: nil, }, state: &mockState{ - assignments: map[string]cpuset.CPUSet{}, + assignments: state.ContainerCPUAssignments{}, defaultCPUSet: cpuset.NewCPUSet(), }, containerRuntime: mockRuntimeService{}, @@ -251,7 +376,7 @@ func TestReconcileState(t *testing.T) { activePods []*v1.Pod pspPS v1.PodStatus pspFound bool - stAssignments map[string]cpuset.CPUSet + stAssignments state.ContainerCPUAssignments stDefaultCPUSet cpuset.CPUSet updateErr error expectFailedContainerName string @@ -282,7 +407,7 @@ func TestReconcileState(t *testing.T) { }, }, pspFound: true, - stAssignments: map[string]cpuset.CPUSet{ + stAssignments: state.ContainerCPUAssignments{ "fakeID": cpuset.NewCPUSet(1, 2), }, stDefaultCPUSet: cpuset.NewCPUSet(3, 4, 5, 6, 7), @@ -308,7 +433,7 @@ func TestReconcileState(t *testing.T) { }, pspPS: v1.PodStatus{}, pspFound: false, - stAssignments: map[string]cpuset.CPUSet{}, + stAssignments: state.ContainerCPUAssignments{}, stDefaultCPUSet: cpuset.NewCPUSet(), updateErr: nil, expectFailedContainerName: "fakeName", @@ -339,7 +464,7 @@ func TestReconcileState(t *testing.T) { }, }, pspFound: true, - stAssignments: map[string]cpuset.CPUSet{}, + stAssignments: state.ContainerCPUAssignments{}, stDefaultCPUSet: cpuset.NewCPUSet(), updateErr: nil, expectFailedContainerName: "fakeName", @@ -370,7 +495,7 @@ func TestReconcileState(t *testing.T) { }, }, pspFound: true, - stAssignments: map[string]cpuset.CPUSet{ + stAssignments: state.ContainerCPUAssignments{ "fakeID": cpuset.NewCPUSet(), }, stDefaultCPUSet: cpuset.NewCPUSet(1, 2, 3, 4, 5, 6, 7), @@ -403,7 +528,7 @@ func TestReconcileState(t *testing.T) { }, }, pspFound: true, - stAssignments: map[string]cpuset.CPUSet{ + stAssignments: state.ContainerCPUAssignments{ "fakeID": cpuset.NewCPUSet(1, 2), }, stDefaultCPUSet: cpuset.NewCPUSet(3, 4, 5, 6, 7), diff --git a/pkg/kubelet/cm/cpumanager/policy.go b/pkg/kubelet/cm/cpumanager/policy.go index 39eb76316b1..c79091659e3 100644 --- a/pkg/kubelet/cm/cpumanager/policy.go +++ b/pkg/kubelet/cm/cpumanager/policy.go @@ -25,6 +25,8 @@ import ( type Policy interface { Name() string Start(s state.State) + // AddContainer call is idempotent AddContainer(s state.State, pod *v1.Pod, container *v1.Container, containerID string) error + // RemoveContainer call is idempotent RemoveContainer(s state.State, containerID string) error } diff --git a/pkg/kubelet/cm/cpumanager/policy_none_test.go b/pkg/kubelet/cm/cpumanager/policy_none_test.go index e20f9bdbbc1..99e61fdbbe2 100644 --- a/pkg/kubelet/cm/cpumanager/policy_none_test.go +++ b/pkg/kubelet/cm/cpumanager/policy_none_test.go @@ -19,6 +19,7 @@ package cpumanager import ( "testing" + "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/state" "k8s.io/kubernetes/pkg/kubelet/cm/cpuset" ) @@ -36,7 +37,7 @@ func TestNonePolicyAdd(t *testing.T) { policy := &nonePolicy{} st := &mockState{ - assignments: map[string]cpuset.CPUSet{}, + assignments: state.ContainerCPUAssignments{}, defaultCPUSet: cpuset.NewCPUSet(1, 2, 3, 4, 5, 6, 7), } @@ -53,7 +54,7 @@ func TestNonePolicyRemove(t *testing.T) { policy := &nonePolicy{} st := &mockState{ - assignments: map[string]cpuset.CPUSet{}, + assignments: state.ContainerCPUAssignments{}, defaultCPUSet: cpuset.NewCPUSet(1, 2, 3, 4, 5, 6, 7), } diff --git a/pkg/kubelet/cm/cpumanager/policy_static.go b/pkg/kubelet/cm/cpumanager/policy_static.go index 1eca50b91f4..9a461bacb63 100644 --- a/pkg/kubelet/cm/cpumanager/policy_static.go +++ b/pkg/kubelet/cm/cpumanager/policy_static.go @@ -21,7 +21,7 @@ import ( "github.com/golang/glog" "k8s.io/api/core/v1" - v1qos "k8s.io/kubernetes/pkg/api/v1/helper/qos" + v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos" "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/state" "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/topology" "k8s.io/kubernetes/pkg/kubelet/cm/cpuset" @@ -109,9 +109,45 @@ func (p *staticPolicy) Name() string { } func (p *staticPolicy) Start(s state.State) { - // Configure the shared pool to include all detected CPU IDs. - allCPUs := p.topology.CPUDetails.CPUs() - s.SetDefaultCPUSet(allCPUs) + if err := p.validateState(s); err != nil { + glog.Errorf("[cpumanager] static policy invalid state: %s\n", err.Error()) + panic("[cpumanager] - please drain node and remove policy state file") + } +} + +func (p *staticPolicy) validateState(s state.State) error { + tmpAssignments := s.GetCPUAssignments() + tmpDefaultCPUset := s.GetDefaultCPUSet() + + // Default cpuset cannot be empty when assignments exist + if tmpDefaultCPUset.IsEmpty() { + if len(tmpAssignments) != 0 { + return fmt.Errorf("default cpuset cannot be empty") + } + // state is empty initialize + allCPUs := p.topology.CPUDetails.CPUs() + s.SetDefaultCPUSet(allCPUs) + return nil + } + + // State has already been initialized from file (is not empty) + // 1 Check if the reserved cpuset is not part of default cpuset because: + // - kube/system reserved have changed (increased) - may lead to some containers not being able to start + // - user tampered with file + if !p.reserved.Intersection(tmpDefaultCPUset).Equals(p.reserved) { + return fmt.Errorf("not all reserved cpus: \"%s\" are present in defaultCpuSet: \"%s\"", + p.reserved.String(), tmpDefaultCPUset.String()) + } + + // 2. Check if state for static policy is consistent + for cID, cset := range tmpAssignments { + // None of the cpu in DEFAULT cset should be in s.assignments + if !tmpDefaultCPUset.Intersection(cset).IsEmpty() { + return fmt.Errorf("container id: %s cpuset: \"%s\" overlaps with default cpuset \"%s\"", + cID, cset.String(), tmpDefaultCPUset.String()) + } + } + return nil } // assignableCPUs returns the set of unassigned CPUs minus the reserved set. @@ -120,9 +156,15 @@ func (p *staticPolicy) assignableCPUs(s state.State) cpuset.CPUSet { } func (p *staticPolicy) AddContainer(s state.State, pod *v1.Pod, container *v1.Container, containerID string) error { - glog.Infof("[cpumanager] static policy: AddContainer (pod: %s, container: %s, container id: %s)", pod.Name, container.Name, containerID) if numCPUs := guaranteedCPUs(pod, container); numCPUs != 0 { + glog.Infof("[cpumanager] static policy: AddContainer (pod: %s, container: %s, container id: %s)", pod.Name, container.Name, containerID) // container belongs in an exclusively allocated pool + + if _, ok := s.GetCPUSet(containerID); ok { + glog.Infof("[cpumanager] static policy: container already present in state, skipping (container: %s, container id: %s)", container.Name, containerID) + return nil + } + cpuset, err := p.allocateCPUs(s, numCPUs) if err != nil { glog.Errorf("[cpumanager] unable to allocate %d CPUs (container id: %s, error: %v)", numCPUs, containerID, err) diff --git a/pkg/kubelet/cm/cpumanager/policy_static_test.go b/pkg/kubelet/cm/cpumanager/policy_static_test.go index bf6b53f326d..f0e592ffd11 100644 --- a/pkg/kubelet/cm/cpumanager/policy_static_test.go +++ b/pkg/kubelet/cm/cpumanager/policy_static_test.go @@ -22,6 +22,7 @@ import ( "testing" "k8s.io/api/core/v1" + "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/state" "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/topology" "k8s.io/kubernetes/pkg/kubelet/cm/cpuset" ) @@ -31,12 +32,13 @@ type staticPolicyTest struct { topo *topology.CPUTopology numReservedCPUs int containerID string - stAssignments map[string]cpuset.CPUSet + stAssignments state.ContainerCPUAssignments stDefaultCPUSet cpuset.CPUSet pod *v1.Pod expErr error expCPUAlloc bool expCSet cpuset.CPUSet + expPanic bool } func TestStaticPolicyName(t *testing.T) { @@ -50,18 +52,73 @@ func TestStaticPolicyName(t *testing.T) { } func TestStaticPolicyStart(t *testing.T) { - policy := NewStaticPolicy(topoSingleSocketHT, 1).(*staticPolicy) - - st := &mockState{ - assignments: map[string]cpuset.CPUSet{}, - defaultCPUSet: cpuset.NewCPUSet(), + testCases := []staticPolicyTest{ + { + description: "non-corrupted state", + topo: topoDualSocketHT, + stAssignments: state.ContainerCPUAssignments{ + "0": cpuset.NewCPUSet(0), + }, + stDefaultCPUSet: cpuset.NewCPUSet(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), + expCSet: cpuset.NewCPUSet(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), + }, + { + description: "empty cpuset", + topo: topoDualSocketHT, + numReservedCPUs: 1, + stAssignments: state.ContainerCPUAssignments{}, + stDefaultCPUSet: cpuset.NewCPUSet(), + expCSet: cpuset.NewCPUSet(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), + }, + { + description: "reserved cores 0 & 6 are not present in available cpuset", + topo: topoDualSocketHT, + numReservedCPUs: 2, + stAssignments: state.ContainerCPUAssignments{}, + stDefaultCPUSet: cpuset.NewCPUSet(0, 1), + expPanic: true, + }, + { + description: "assigned core 2 is still present in available cpuset", + topo: topoDualSocketHT, + stAssignments: state.ContainerCPUAssignments{ + "0": cpuset.NewCPUSet(0, 1, 2), + }, + stDefaultCPUSet: cpuset.NewCPUSet(2, 3, 4, 5, 6, 7, 8, 9, 10, 11), + expPanic: true, + }, } + for _, testCase := range testCases { + t.Run(testCase.description, func(t *testing.T) { + defer func() { + if err := recover(); err != nil { + if !testCase.expPanic { + t.Errorf("unexpected panic occured: %q", err) + } + } else if testCase.expPanic { + t.Error("expected panic doesn't occured") + } + }() + policy := NewStaticPolicy(testCase.topo, testCase.numReservedCPUs).(*staticPolicy) + st := &mockState{ + assignments: testCase.stAssignments, + defaultCPUSet: testCase.stDefaultCPUSet, + } + policy.Start(st) - policy.Start(st) - for cpuid := 1; cpuid < policy.topology.NumCPUs; cpuid++ { - if !st.defaultCPUSet.Contains(cpuid) { - t.Errorf("StaticPolicy Start() error. expected cpuid %d to be present in defaultCPUSet", cpuid) - } + if !testCase.stDefaultCPUSet.IsEmpty() { + for cpuid := 1; cpuid < policy.topology.NumCPUs; cpuid++ { + if !st.defaultCPUSet.Contains(cpuid) { + t.Errorf("StaticPolicy Start() error. expected cpuid %d to be present in defaultCPUSet", cpuid) + } + } + } + if !st.GetDefaultCPUSet().Equals(testCase.expCSet) { + t.Errorf("State CPUSet is different than expected. Have %q wants: %q", st.GetDefaultCPUSet(), + testCase.expCSet) + } + + }) } } @@ -88,7 +145,7 @@ func TestStaticPolicyAdd(t *testing.T) { topo: topoSingleSocketHT, numReservedCPUs: 1, containerID: "fakeID2", - stAssignments: map[string]cpuset.CPUSet{}, + stAssignments: state.ContainerCPUAssignments{}, stDefaultCPUSet: cpuset.NewCPUSet(0, 1, 2, 3, 4, 5, 6, 7), pod: makePod("8000m", "8000m"), expErr: fmt.Errorf("not enough cpus available to satisfy request"), @@ -100,7 +157,7 @@ func TestStaticPolicyAdd(t *testing.T) { topo: topoSingleSocketHT, numReservedCPUs: 1, containerID: "fakeID2", - stAssignments: map[string]cpuset.CPUSet{}, + stAssignments: state.ContainerCPUAssignments{}, stDefaultCPUSet: cpuset.NewCPUSet(0, 1, 2, 3, 4, 5, 6, 7), pod: makePod("1000m", "1000m"), expErr: nil, @@ -112,7 +169,7 @@ func TestStaticPolicyAdd(t *testing.T) { topo: topoSingleSocketHT, numReservedCPUs: 1, containerID: "fakeID3", - stAssignments: map[string]cpuset.CPUSet{ + stAssignments: state.ContainerCPUAssignments{ "fakeID100": cpuset.NewCPUSet(2, 3, 6, 7), }, stDefaultCPUSet: cpuset.NewCPUSet(0, 1, 4, 5), @@ -126,7 +183,7 @@ func TestStaticPolicyAdd(t *testing.T) { topo: topoDualSocketHT, numReservedCPUs: 1, containerID: "fakeID3", - stAssignments: map[string]cpuset.CPUSet{ + stAssignments: state.ContainerCPUAssignments{ "fakeID100": cpuset.NewCPUSet(2), }, stDefaultCPUSet: cpuset.NewCPUSet(0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11), @@ -140,7 +197,7 @@ func TestStaticPolicyAdd(t *testing.T) { topo: topoDualSocketHT, numReservedCPUs: 1, containerID: "fakeID3", - stAssignments: map[string]cpuset.CPUSet{ + stAssignments: state.ContainerCPUAssignments{ "fakeID100": cpuset.NewCPUSet(1, 5), }, stDefaultCPUSet: cpuset.NewCPUSet(0, 2, 3, 4, 6, 7, 8, 9, 10, 11), @@ -154,7 +211,7 @@ func TestStaticPolicyAdd(t *testing.T) { topo: topoDualSocketNoHT, numReservedCPUs: 1, containerID: "fakeID1", - stAssignments: map[string]cpuset.CPUSet{ + stAssignments: state.ContainerCPUAssignments{ "fakeID100": cpuset.NewCPUSet(), }, stDefaultCPUSet: cpuset.NewCPUSet(0, 1, 3, 4, 5, 6, 7), @@ -168,7 +225,7 @@ func TestStaticPolicyAdd(t *testing.T) { topo: topoDualSocketNoHT, numReservedCPUs: 1, containerID: "fakeID1", - stAssignments: map[string]cpuset.CPUSet{ + stAssignments: state.ContainerCPUAssignments{ "fakeID100": cpuset.NewCPUSet(4, 5), }, stDefaultCPUSet: cpuset.NewCPUSet(0, 1, 3, 6, 7), @@ -182,7 +239,7 @@ func TestStaticPolicyAdd(t *testing.T) { topo: topoDualSocketHT, numReservedCPUs: 1, containerID: "fakeID3", - stAssignments: map[string]cpuset.CPUSet{ + stAssignments: state.ContainerCPUAssignments{ "fakeID100": cpuset.NewCPUSet(2), }, stDefaultCPUSet: cpuset.NewCPUSet(0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11), @@ -196,7 +253,7 @@ func TestStaticPolicyAdd(t *testing.T) { topo: topoSingleSocketHT, numReservedCPUs: 1, containerID: "fakeID1", - stAssignments: map[string]cpuset.CPUSet{}, + stAssignments: state.ContainerCPUAssignments{}, stDefaultCPUSet: cpuset.NewCPUSet(0, 1, 2, 3, 4, 5, 6, 7), pod: makePod("1000m", "2000m"), expErr: nil, @@ -208,7 +265,7 @@ func TestStaticPolicyAdd(t *testing.T) { topo: topoSingleSocketHT, numReservedCPUs: 1, containerID: "fakeID4", - stAssignments: map[string]cpuset.CPUSet{}, + stAssignments: state.ContainerCPUAssignments{}, stDefaultCPUSet: cpuset.NewCPUSet(0, 1, 2, 3, 4, 5, 6, 7), pod: makePod("977m", "977m"), expErr: nil, @@ -220,7 +277,7 @@ func TestStaticPolicyAdd(t *testing.T) { topo: topoSingleSocketHT, numReservedCPUs: 1, containerID: "fakeID5", - stAssignments: map[string]cpuset.CPUSet{ + stAssignments: state.ContainerCPUAssignments{ "fakeID100": cpuset.NewCPUSet(1, 2, 3, 4, 5, 6), }, stDefaultCPUSet: cpuset.NewCPUSet(0, 7), @@ -234,7 +291,7 @@ func TestStaticPolicyAdd(t *testing.T) { topo: topoDualSocketHT, numReservedCPUs: 1, containerID: "fakeID5", - stAssignments: map[string]cpuset.CPUSet{ + stAssignments: state.ContainerCPUAssignments{ "fakeID100": cpuset.NewCPUSet(1, 2, 3), }, stDefaultCPUSet: cpuset.NewCPUSet(0, 4, 5, 6, 7, 8, 9, 10, 11), @@ -250,7 +307,7 @@ func TestStaticPolicyAdd(t *testing.T) { description: "GuPodMultipleCores, topoQuadSocketFourWayHT, ExpectAllocSock0", topo: topoQuadSocketFourWayHT, containerID: "fakeID5", - stAssignments: map[string]cpuset.CPUSet{ + stAssignments: state.ContainerCPUAssignments{ "fakeID100": cpuset.NewCPUSet(3, 11, 4, 5, 6, 7), }, stDefaultCPUSet: largeTopoCPUSet.Difference(cpuset.NewCPUSet(3, 11, 4, 5, 6, 7)), @@ -265,7 +322,7 @@ func TestStaticPolicyAdd(t *testing.T) { description: "GuPodMultipleCores, topoQuadSocketFourWayHT, ExpectAllocAllFullCoresFromThreeSockets", topo: topoQuadSocketFourWayHT, containerID: "fakeID5", - stAssignments: map[string]cpuset.CPUSet{ + stAssignments: state.ContainerCPUAssignments{ "fakeID100": largeTopoCPUSet.Difference(cpuset.NewCPUSet(1, 25, 13, 38, 2, 9, 11, 35, 23, 48, 12, 51, 53, 173, 113, 233, 54, 61)), }, @@ -281,7 +338,7 @@ func TestStaticPolicyAdd(t *testing.T) { description: "GuPodMultipleCores, topoQuadSocketFourWayHT, ExpectAllocAllSock1+FullCore", topo: topoQuadSocketFourWayHT, containerID: "fakeID5", - stAssignments: map[string]cpuset.CPUSet{ + stAssignments: state.ContainerCPUAssignments{ "fakeID100": largeTopoCPUSet.Difference(largeTopoSock1CPUSet.Union(cpuset.NewCPUSet(10, 34, 22, 47, 53, 173, 61, 181, 108, 228, 115, 235))), }, @@ -298,7 +355,7 @@ func TestStaticPolicyAdd(t *testing.T) { description: "GuPodMultipleCores, topoQuadSocketFourWayHT, ExpectAllocCPUs", topo: topoQuadSocketFourWayHT, containerID: "fakeID5", - stAssignments: map[string]cpuset.CPUSet{ + stAssignments: state.ContainerCPUAssignments{ "fakeID100": largeTopoCPUSet.Difference(cpuset.NewCPUSet(10, 11, 53, 37, 55, 67, 52)), }, stDefaultCPUSet: cpuset.NewCPUSet(10, 11, 53, 67, 52), @@ -314,7 +371,7 @@ func TestStaticPolicyAdd(t *testing.T) { description: "GuPodMultipleCores, topoQuadSocketFourWayHT, NoAlloc", topo: topoQuadSocketFourWayHT, containerID: "fakeID5", - stAssignments: map[string]cpuset.CPUSet{ + stAssignments: state.ContainerCPUAssignments{ "fakeID100": largeTopoCPUSet.Difference(cpuset.NewCPUSet(10, 11, 53, 37, 55, 67, 52)), }, stDefaultCPUSet: cpuset.NewCPUSet(10, 11, 53, 37, 55, 67, 52), @@ -374,7 +431,7 @@ func TestStaticPolicyRemove(t *testing.T) { description: "SingleSocketHT, DeAllocOneContainer", topo: topoSingleSocketHT, containerID: "fakeID1", - stAssignments: map[string]cpuset.CPUSet{ + stAssignments: state.ContainerCPUAssignments{ "fakeID1": cpuset.NewCPUSet(1, 2, 3), }, stDefaultCPUSet: cpuset.NewCPUSet(4, 5, 6, 7), @@ -384,7 +441,7 @@ func TestStaticPolicyRemove(t *testing.T) { description: "SingleSocketHT, DeAllocOneContainer, BeginEmpty", topo: topoSingleSocketHT, containerID: "fakeID1", - stAssignments: map[string]cpuset.CPUSet{ + stAssignments: state.ContainerCPUAssignments{ "fakeID1": cpuset.NewCPUSet(1, 2, 3), "fakeID2": cpuset.NewCPUSet(4, 5, 6, 7), }, @@ -395,7 +452,7 @@ func TestStaticPolicyRemove(t *testing.T) { description: "SingleSocketHT, DeAllocTwoContainer", topo: topoSingleSocketHT, containerID: "fakeID1", - stAssignments: map[string]cpuset.CPUSet{ + stAssignments: state.ContainerCPUAssignments{ "fakeID1": cpuset.NewCPUSet(1, 3, 5), "fakeID2": cpuset.NewCPUSet(2, 4), }, @@ -406,7 +463,7 @@ func TestStaticPolicyRemove(t *testing.T) { description: "SingleSocketHT, NoDeAlloc", topo: topoSingleSocketHT, containerID: "fakeID2", - stAssignments: map[string]cpuset.CPUSet{ + stAssignments: state.ContainerCPUAssignments{ "fakeID1": cpuset.NewCPUSet(1, 3, 5), }, stDefaultCPUSet: cpuset.NewCPUSet(2, 4, 6, 7), diff --git a/pkg/kubelet/cm/cpumanager/state/BUILD b/pkg/kubelet/cm/cpumanager/state/BUILD index 6393bdec4d0..31f18d0a3a0 100644 --- a/pkg/kubelet/cm/cpumanager/state/BUILD +++ b/pkg/kubelet/cm/cpumanager/state/BUILD @@ -1,9 +1,10 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = [ "state.go", + "state_file.go", "state_mem.go", ], importpath = "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/state", @@ -14,6 +15,14 @@ go_library( ], ) +go_test( + name = "go_default_test", + srcs = ["state_file_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/state", + deps = ["//pkg/kubelet/cm/cpuset:go_default_library"], +) + filegroup( name = "package-srcs", srcs = glob(["**"]), diff --git a/pkg/kubelet/cm/cpumanager/state/state.go b/pkg/kubelet/cm/cpumanager/state/state.go index 98f7f7dc240..0550b644d57 100644 --- a/pkg/kubelet/cm/cpumanager/state/state.go +++ b/pkg/kubelet/cm/cpumanager/state/state.go @@ -20,17 +20,32 @@ import ( "k8s.io/kubernetes/pkg/kubelet/cm/cpuset" ) +// ContainerCPUAssignments type used in cpu manger state +type ContainerCPUAssignments map[string]cpuset.CPUSet + +// Clone returns a copy of ContainerCPUAssignments +func (as ContainerCPUAssignments) Clone() ContainerCPUAssignments { + ret := make(ContainerCPUAssignments) + for key, val := range as { + ret[key] = val + } + return ret +} + // Reader interface used to read current cpu/pod assignment state type Reader interface { GetCPUSet(containerID string) (cpuset.CPUSet, bool) GetDefaultCPUSet() cpuset.CPUSet GetCPUSetOrDefault(containerID string) cpuset.CPUSet + GetCPUAssignments() ContainerCPUAssignments } type writer interface { SetCPUSet(containerID string, cpuset cpuset.CPUSet) SetDefaultCPUSet(cpuset cpuset.CPUSet) + SetCPUAssignments(ContainerCPUAssignments) Delete(containerID string) + ClearState() } // State interface provides methods for tracking and setting cpu/pod assignment diff --git a/pkg/kubelet/cm/cpumanager/state/state_file.go b/pkg/kubelet/cm/cpumanager/state/state_file.go new file mode 100644 index 00000000000..6c2353cf10f --- /dev/null +++ b/pkg/kubelet/cm/cpumanager/state/state_file.go @@ -0,0 +1,211 @@ +/* +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 state + +import ( + "encoding/json" + "fmt" + "github.com/golang/glog" + "io/ioutil" + "k8s.io/kubernetes/pkg/kubelet/cm/cpuset" + "os" + "sync" +) + +type stateFileData struct { + PolicyName string `json:"policyName"` + DefaultCPUSet string `json:"defaultCpuSet"` + Entries map[string]string `json:"entries,omitempty"` +} + +var _ State = &stateFile{} + +type stateFile struct { + sync.RWMutex + stateFilePath string + policyName string + cache State +} + +// NewFileState creates new State for keeping track of cpu/pod assignment with file backend +func NewFileState(filePath string, policyName string) State { + stateFile := &stateFile{ + stateFilePath: filePath, + cache: NewMemoryState(), + policyName: policyName, + } + + if err := stateFile.tryRestoreState(); err != nil { + // could not restore state, init new state file + msg := fmt.Sprintf("[cpumanager] state file: unable to restore state from disk (%s)\n", err.Error()) + + "Panicking because we cannot guarantee sane CPU affinity for existing containers.\n" + + fmt.Sprintf("Please drain this node and delete the CPU manager state file \"%s\" before restarting Kubelet.", stateFile.stateFilePath) + panic(msg) + } + + return stateFile +} + +// tryRestoreState tries to read state file, upon any error, +// err message is logged and state is left clean. un-initialized +func (sf *stateFile) tryRestoreState() error { + sf.Lock() + defer sf.Unlock() + var err error + + // used when all parsing is ok + tmpAssignments := make(ContainerCPUAssignments) + tmpDefaultCPUSet := cpuset.NewCPUSet() + tmpContainerCPUSet := cpuset.NewCPUSet() + + var content []byte + + content, err = ioutil.ReadFile(sf.stateFilePath) + + // If the state file does not exist or has zero length, write a new file. + if os.IsNotExist(err) || len(content) == 0 { + sf.storeState() + glog.Infof("[cpumanager] state file: created new state file \"%s\"", sf.stateFilePath) + return nil + } + + // Fail on any other file read error. + if err != nil { + return err + } + + // File exists; try to read it. + var readState stateFileData + + if err = json.Unmarshal(content, &readState); err != nil { + glog.Errorf("[cpumanager] state file: could not unmarshal, corrupted state file - \"%s\"", sf.stateFilePath) + return err + } + + if sf.policyName != readState.PolicyName { + return fmt.Errorf("policy configured \"%s\" != policy from state file \"%s\"", sf.policyName, readState.PolicyName) + } + + if tmpDefaultCPUSet, err = cpuset.Parse(readState.DefaultCPUSet); err != nil { + glog.Errorf("[cpumanager] state file: could not parse state file - [defaultCpuSet:\"%s\"]", readState.DefaultCPUSet) + return err + } + + for containerID, cpuString := range readState.Entries { + if tmpContainerCPUSet, err = cpuset.Parse(cpuString); err != nil { + glog.Errorf("[cpumanager] state file: could not parse state file - container id: %s, cpuset: \"%s\"", containerID, cpuString) + return err + } + tmpAssignments[containerID] = tmpContainerCPUSet + } + + sf.cache.SetDefaultCPUSet(tmpDefaultCPUSet) + sf.cache.SetCPUAssignments(tmpAssignments) + + glog.V(2).Infof("[cpumanager] state file: restored state from state file \"%s\"", sf.stateFilePath) + glog.V(2).Infof("[cpumanager] state file: defaultCPUSet: %s", tmpDefaultCPUSet.String()) + + return nil +} + +// saves state to a file, caller is responsible for locking +func (sf *stateFile) storeState() { + var content []byte + var err error + + data := stateFileData{ + PolicyName: sf.policyName, + DefaultCPUSet: sf.cache.GetDefaultCPUSet().String(), + Entries: map[string]string{}, + } + + for containerID, cset := range sf.cache.GetCPUAssignments() { + data.Entries[containerID] = cset.String() + } + + if content, err = json.Marshal(data); err != nil { + panic("[cpumanager] state file: could not serialize state to json") + } + + if err = ioutil.WriteFile(sf.stateFilePath, content, 0644); err != nil { + panic("[cpumanager] state file not written") + } + return +} + +func (sf *stateFile) GetCPUSet(containerID string) (cpuset.CPUSet, bool) { + sf.RLock() + defer sf.RUnlock() + + res, ok := sf.cache.GetCPUSet(containerID) + return res, ok +} + +func (sf *stateFile) GetDefaultCPUSet() cpuset.CPUSet { + sf.RLock() + defer sf.RUnlock() + + return sf.cache.GetDefaultCPUSet() +} + +func (sf *stateFile) GetCPUSetOrDefault(containerID string) cpuset.CPUSet { + sf.RLock() + defer sf.RUnlock() + + return sf.cache.GetCPUSetOrDefault(containerID) +} + +func (sf *stateFile) GetCPUAssignments() ContainerCPUAssignments { + sf.RLock() + defer sf.RUnlock() + return sf.cache.GetCPUAssignments() +} + +func (sf *stateFile) SetCPUSet(containerID string, cset cpuset.CPUSet) { + sf.Lock() + defer sf.Unlock() + sf.cache.SetCPUSet(containerID, cset) + sf.storeState() +} + +func (sf *stateFile) SetDefaultCPUSet(cset cpuset.CPUSet) { + sf.Lock() + defer sf.Unlock() + sf.cache.SetDefaultCPUSet(cset) + sf.storeState() +} + +func (sf *stateFile) SetCPUAssignments(a ContainerCPUAssignments) { + sf.Lock() + defer sf.Unlock() + sf.cache.SetCPUAssignments(a) + sf.storeState() +} + +func (sf *stateFile) Delete(containerID string) { + sf.Lock() + defer sf.Unlock() + sf.cache.Delete(containerID) + sf.storeState() +} + +func (sf *stateFile) ClearState() { + sf.Lock() + defer sf.Unlock() + sf.cache.ClearState() + sf.storeState() +} diff --git a/pkg/kubelet/cm/cpumanager/state/state_file_test.go b/pkg/kubelet/cm/cpumanager/state/state_file_test.go new file mode 100644 index 00000000000..93dd3910ad2 --- /dev/null +++ b/pkg/kubelet/cm/cpumanager/state/state_file_test.go @@ -0,0 +1,484 @@ +/* +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 state + +import ( + "bytes" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "path" + "reflect" + "strings" + "testing" + + "k8s.io/kubernetes/pkg/kubelet/cm/cpuset" +) + +func writeToStateFile(statefile string, content string) { + ioutil.WriteFile(statefile, []byte(content), 0644) +} + +func stateEqual(t *testing.T, sf State, sm State) { + cpusetSf := sf.GetDefaultCPUSet() + cpusetSm := sm.GetDefaultCPUSet() + if !cpusetSf.Equals(cpusetSm) { + t.Errorf("State CPUSet mismatch. Have %v, want %v", cpusetSf, cpusetSm) + } + + cpuassignmentSf := sf.GetCPUAssignments() + cpuassignmentSm := sm.GetCPUAssignments() + if !reflect.DeepEqual(cpuassignmentSf, cpuassignmentSm) { + t.Errorf("State CPU assigments mismatch. Have %s, want %s", cpuassignmentSf, cpuassignmentSm) + } +} + +func stderrCapture(t *testing.T, f func() State) (bytes.Buffer, State) { + stderr := os.Stderr + + readBuffer, writeBuffer, err := os.Pipe() + if err != nil { + t.Errorf("cannot create pipe: %v", err.Error()) + } + + os.Stderr = writeBuffer + var outputBuffer bytes.Buffer + + state := f() + writeBuffer.Close() + io.Copy(&outputBuffer, readBuffer) + os.Stderr = stderr + + return outputBuffer, state +} + +func TestFileStateTryRestore(t *testing.T) { + flag.Set("alsologtostderr", "true") + flag.Parse() + + testCases := []struct { + description string + stateFileContent string + policyName string + expErr string + expPanic bool + expectedState *stateMemory + }{ + { + "Invalid JSON - one byte file", + "\n", + "none", + "[cpumanager] state file: unable to restore state from disk (unexpected end of JSON input)", + true, + &stateMemory{}, + }, + { + "Invalid JSON - invalid content", + "{", + "none", + "[cpumanager] state file: unable to restore state from disk (unexpected end of JSON input)", + true, + &stateMemory{}, + }, + { + "Try restore defaultCPUSet only", + `{"policyName": "none", "defaultCpuSet": "4-6"}`, + "none", + "", + false, + &stateMemory{ + assignments: ContainerCPUAssignments{}, + defaultCPUSet: cpuset.NewCPUSet(4, 5, 6), + }, + }, + { + "Try restore defaultCPUSet only - invalid name", + `{"policyName": "none", "defaultCpuSet" "4-6"}`, + "none", + `[cpumanager] state file: unable to restore state from disk (invalid character '"' after object key)`, + true, + &stateMemory{}, + }, + { + "Try restore assignments only", + `{ + "policyName": "none", + "entries": { + "container1": "4-6", + "container2": "1-3" + } + }`, + "none", + "", + false, + &stateMemory{ + assignments: ContainerCPUAssignments{ + "container1": cpuset.NewCPUSet(4, 5, 6), + "container2": cpuset.NewCPUSet(1, 2, 3), + }, + defaultCPUSet: cpuset.NewCPUSet(), + }, + }, + { + "Try restore invalid policy name", + `{ + "policyName": "A", + "defaultCpuSet": "0-7", + "entries": {} + }`, + "B", + `[cpumanager] state file: unable to restore state from disk (policy configured "B" != policy from state file "A")`, + true, + &stateMemory{}, + }, + { + "Try restore invalid assignments", + `{"entries": }`, + "none", + "[cpumanager] state file: unable to restore state from disk (invalid character '}' looking for beginning of value)", + true, + &stateMemory{}, + }, + { + "Try restore valid file", + `{ + "policyName": "none", + "defaultCpuSet": "23-24", + "entries": { + "container1": "4-6", + "container2": "1-3" + } + }`, + "none", + "", + false, + &stateMemory{ + assignments: ContainerCPUAssignments{ + "container1": cpuset.NewCPUSet(4, 5, 6), + "container2": cpuset.NewCPUSet(1, 2, 3), + }, + defaultCPUSet: cpuset.NewCPUSet(23, 24), + }, + }, + { + "Try restore un-parsable defaultCPUSet ", + `{ + "policyName": "none", + "defaultCpuSet": "2-sd" + }`, + "none", + `[cpumanager] state file: unable to restore state from disk (strconv.Atoi: parsing "sd": invalid syntax)`, + true, + &stateMemory{}, + }, + { + "Try restore un-parsable assignments", + `{ + "policyName": "none", + "defaultCpuSet": "23-24", + "entries": { + "container1": "p-6", + "container2": "1-3" + } + }`, + "none", + `[cpumanager] state file: unable to restore state from disk (strconv.Atoi: parsing "p": invalid syntax)`, + true, + &stateMemory{}, + }, + { + "tryRestoreState creates empty state file", + "", + "none", + "", + false, + &stateMemory{ + assignments: ContainerCPUAssignments{}, + defaultCPUSet: cpuset.NewCPUSet(), + }, + }, + } + + for idx, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + defer func() { + if tc.expPanic { + r := recover() + panicMsg := r.(string) + if !strings.HasPrefix(panicMsg, tc.expErr) { + t.Fatalf(`expected panic "%s" but got "%s"`, tc.expErr, panicMsg) + } else { + t.Logf(`got expected panic "%s"`, panicMsg) + } + } + }() + + sfilePath, err := ioutil.TempFile("/tmp", fmt.Sprintf("cpumanager_state_file_test_%d", idx)) + if err != nil { + t.Errorf("cannot create temporary file: %q", err.Error()) + } + // Don't create state file, let tryRestoreState figure out that is should create + if tc.stateFileContent != "" { + writeToStateFile(sfilePath.Name(), tc.stateFileContent) + } + + // Always remove file - regardless of who created + defer os.Remove(sfilePath.Name()) + + logData, fileState := stderrCapture(t, func() State { + return NewFileState(sfilePath.Name(), tc.policyName) + }) + + if tc.expErr != "" { + if logData.String() != "" { + if !strings.Contains(logData.String(), tc.expErr) { + t.Errorf("tryRestoreState() error = %v, wantErr %v", logData.String(), tc.expErr) + return + } + } else { + t.Errorf("tryRestoreState() error = nil, wantErr %v", tc.expErr) + return + } + } + + stateEqual(t, fileState, tc.expectedState) + }) + } +} + +func TestFileStateTryRestorePanic(t *testing.T) { + + testCase := struct { + description string + wantPanic bool + panicMessage string + }{ + "Panic creating file", + true, + "[cpumanager] state file not written", + } + + t.Run(testCase.description, func(t *testing.T) { + sfilePath := path.Join("/invalid_path/to_some_dir", "cpumanager_state_file_test") + defer func() { + if err := recover(); err != nil { + if testCase.wantPanic { + if testCase.panicMessage == err { + t.Logf("tryRestoreState() got expected panic = %v", err) + return + } + t.Errorf("tryRestoreState() unexpected panic = %v, wantErr %v", err, testCase.panicMessage) + } + } + }() + NewFileState(sfilePath, "static") + }) +} + +func TestUpdateStateFile(t *testing.T) { + flag.Set("alsologtostderr", "true") + flag.Parse() + + testCases := []struct { + description string + expErr string + expectedState *stateMemory + }{ + { + "Save empty state", + "", + &stateMemory{ + assignments: ContainerCPUAssignments{}, + defaultCPUSet: cpuset.NewCPUSet(), + }, + }, + { + "Save defaultCPUSet only", + "", + &stateMemory{ + assignments: ContainerCPUAssignments{}, + defaultCPUSet: cpuset.NewCPUSet(1, 6), + }, + }, + { + "Save assignments only", + "", + &stateMemory{ + assignments: ContainerCPUAssignments{ + "container1": cpuset.NewCPUSet(4, 5, 6), + "container2": cpuset.NewCPUSet(1, 2, 3), + }, + defaultCPUSet: cpuset.NewCPUSet(), + }, + }, + } + + for idx, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + + sfilePath, err := ioutil.TempFile("/tmp", fmt.Sprintf("cpumanager_state_file_test_%d", idx)) + defer os.Remove(sfilePath.Name()) + if err != nil { + t.Errorf("cannot create temporary file: %q", err.Error()) + } + fileState := stateFile{ + stateFilePath: sfilePath.Name(), + policyName: "static", + cache: NewMemoryState(), + } + + fileState.SetDefaultCPUSet(tc.expectedState.defaultCPUSet) + fileState.SetCPUAssignments(tc.expectedState.assignments) + + logData, _ := stderrCapture(t, func() State { + fileState.storeState() + return &stateFile{} + }) + + errMsg := logData.String() + + if tc.expErr != "" { + if errMsg != "" { + if errMsg != tc.expErr { + t.Errorf("UpdateStateFile() error = %v, wantErr %v", errMsg, tc.expErr) + return + } + } else { + t.Errorf("UpdateStateFile() error = nil, wantErr %v", tc.expErr) + return + } + } else { + if errMsg != "" { + t.Errorf("UpdateStateFile() error = %v, wantErr nil", errMsg) + return + } + } + newFileState := NewFileState(sfilePath.Name(), "static") + stateEqual(t, newFileState, tc.expectedState) + }) + } +} + +func TestHelpersStateFile(t *testing.T) { + testCases := []struct { + description string + defaultCPUset cpuset.CPUSet + containers map[string]cpuset.CPUSet + }{ + { + description: "one container", + defaultCPUset: cpuset.NewCPUSet(0, 1, 2, 3, 4, 5, 6, 7, 8), + containers: map[string]cpuset.CPUSet{ + "c1": cpuset.NewCPUSet(0, 1), + }, + }, + { + description: "two containers", + defaultCPUset: cpuset.NewCPUSet(0, 1, 2, 3, 4, 5, 6, 7, 8), + containers: map[string]cpuset.CPUSet{ + "c1": cpuset.NewCPUSet(0, 1), + "c2": cpuset.NewCPUSet(2, 3, 4, 5), + }, + }, + { + description: "container with more cpus than is possible", + defaultCPUset: cpuset.NewCPUSet(0, 1, 2, 3, 4, 5, 6, 7, 8), + containers: map[string]cpuset.CPUSet{ + "c1": cpuset.NewCPUSet(0, 10), + }, + }, + { + description: "container without assigned cpus", + defaultCPUset: cpuset.NewCPUSet(0, 1, 2, 3, 4, 5, 6, 7, 8), + containers: map[string]cpuset.CPUSet{ + "c1": cpuset.NewCPUSet(), + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + sfFile, err := ioutil.TempFile("/tmp", "testHelpersStateFile") + defer os.Remove(sfFile.Name()) + if err != nil { + t.Errorf("cannot create temporary test file: %q", err.Error()) + } + + state := NewFileState(sfFile.Name(), "static") + state.SetDefaultCPUSet(tc.defaultCPUset) + + for containerName, containerCPUs := range tc.containers { + state.SetCPUSet(containerName, containerCPUs) + if cpus, _ := state.GetCPUSet(containerName); !cpus.Equals(containerCPUs) { + t.Errorf("state is inconsistant. Wants = %q Have = %q", containerCPUs, cpus) + } + state.Delete(containerName) + if cpus := state.GetCPUSetOrDefault(containerName); !cpus.Equals(tc.defaultCPUset) { + t.Error("deleted container still existing in state") + } + + } + + }) + } +} + +func TestClearStateStateFile(t *testing.T) { + testCases := []struct { + description string + defaultCPUset cpuset.CPUSet + containers map[string]cpuset.CPUSet + }{ + { + description: "valid file", + defaultCPUset: cpuset.NewCPUSet(0, 1, 2, 3, 4, 5, 6, 7, 8), + containers: map[string]cpuset.CPUSet{ + "c1": cpuset.NewCPUSet(0, 1), + "c2": cpuset.NewCPUSet(2, 3), + "c3": cpuset.NewCPUSet(4, 5), + }, + }, + } + for _, testCase := range testCases { + t.Run(testCase.description, func(t *testing.T) { + sfFile, err := ioutil.TempFile("/tmp", "testHelpersStateFile") + defer os.Remove(sfFile.Name()) + if err != nil { + t.Errorf("cannot create temporary test file: %q", err.Error()) + } + + state := NewFileState(sfFile.Name(), "static") + state.SetDefaultCPUSet(testCase.defaultCPUset) + for containerName, containerCPUs := range testCase.containers { + state.SetCPUSet(containerName, containerCPUs) + } + + state.ClearState() + if !cpuset.NewCPUSet().Equals(state.GetDefaultCPUSet()) { + t.Error("cleared state shoudn't has got information about available cpuset") + } + for containerName := range testCase.containers { + if !cpuset.NewCPUSet().Equals(state.GetCPUSetOrDefault(containerName)) { + t.Error("cleared state shoudn't has got information about containers") + } + } + + }) + } +} diff --git a/pkg/kubelet/cm/cpumanager/state/state_mem.go b/pkg/kubelet/cm/cpumanager/state/state_mem.go index 751b1726aae..797cdb15b2b 100644 --- a/pkg/kubelet/cm/cpumanager/state/state_mem.go +++ b/pkg/kubelet/cm/cpumanager/state/state_mem.go @@ -25,7 +25,7 @@ import ( type stateMemory struct { sync.RWMutex - assignments map[string]cpuset.CPUSet + assignments ContainerCPUAssignments defaultCPUSet cpuset.CPUSet } @@ -35,7 +35,7 @@ var _ State = &stateMemory{} func NewMemoryState() State { glog.Infof("[cpumanager] initializing new in-memory state store") return &stateMemory{ - assignments: map[string]cpuset.CPUSet{}, + assignments: ContainerCPUAssignments{}, defaultCPUSet: cpuset.NewCPUSet(), } } @@ -65,6 +65,12 @@ func (s *stateMemory) GetCPUSetOrDefault(containerID string) cpuset.CPUSet { return s.GetDefaultCPUSet() } +func (s *stateMemory) GetCPUAssignments() ContainerCPUAssignments { + s.RLock() + defer s.RUnlock() + return s.assignments.Clone() +} + func (s *stateMemory) SetCPUSet(containerID string, cset cpuset.CPUSet) { s.Lock() defer s.Unlock() @@ -81,6 +87,14 @@ func (s *stateMemory) SetDefaultCPUSet(cset cpuset.CPUSet) { glog.Infof("[cpumanager] updated default cpuset: \"%s\"", cset) } +func (s *stateMemory) SetCPUAssignments(a ContainerCPUAssignments) { + s.Lock() + defer s.Unlock() + + s.assignments = a.Clone() + glog.Infof("[cpumanager] updated cpuset assignments: \"%v\"", a) +} + func (s *stateMemory) Delete(containerID string) { s.Lock() defer s.Unlock() @@ -88,3 +102,12 @@ func (s *stateMemory) Delete(containerID string) { delete(s.assignments, containerID) glog.V(2).Infof("[cpumanager] deleted cpuset assignment (container id: %s)", containerID) } + +func (s *stateMemory) ClearState() { + s.Lock() + defer s.Unlock() + + s.defaultCPUSet = cpuset.CPUSet{} + s.assignments = make(ContainerCPUAssignments) + glog.V(2).Infof("[cpumanager] cleared state") +} diff --git a/pkg/kubelet/cm/cpumanager/topology/BUILD b/pkg/kubelet/cm/cpumanager/topology/BUILD index eb6ba3d6e63..e50c09ffd00 100644 --- a/pkg/kubelet/cm/cpumanager/topology/BUILD +++ b/pkg/kubelet/cm/cpumanager/topology/BUILD @@ -32,7 +32,7 @@ filegroup( go_test( name = "go_default_test", srcs = ["topology_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/topology", - library = ":go_default_library", deps = ["//vendor/github.com/google/cadvisor/info/v1:go_default_library"], ) diff --git a/pkg/kubelet/cm/cpuset/BUILD b/pkg/kubelet/cm/cpuset/BUILD index 7ed863a5ee6..eadcb49b2b8 100644 --- a/pkg/kubelet/cm/cpuset/BUILD +++ b/pkg/kubelet/cm/cpuset/BUILD @@ -11,8 +11,8 @@ go_library( go_test( name = "go_default_test", srcs = ["cpuset_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/cm/cpuset", - library = ":go_default_library", ) filegroup( diff --git a/pkg/kubelet/cm/device_plugin_handler.go b/pkg/kubelet/cm/device_plugin_handler.go deleted file mode 100644 index 790f720eeca..00000000000 --- a/pkg/kubelet/cm/device_plugin_handler.go +++ /dev/null @@ -1,294 +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 cm - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "sync" - - "github.com/golang/glog" - - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/apimachinery/pkg/util/sets" - pluginapi "k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1alpha1" - "k8s.io/kubernetes/pkg/kubelet/deviceplugin" -) - -// podDevices represents a list of pod to device Id mappings. -type containerDevices map[string]sets.String -type podDevices map[string]containerDevices - -func (pdev podDevices) pods() sets.String { - ret := sets.NewString() - for k := range pdev { - ret.Insert(k) - } - return ret -} - -func (pdev podDevices) insert(podUID, contName string, device string) { - if _, exists := pdev[podUID]; !exists { - pdev[podUID] = make(containerDevices) - } - if _, exists := pdev[podUID][contName]; !exists { - pdev[podUID][contName] = sets.NewString() - } - pdev[podUID][contName].Insert(device) -} - -func (pdev podDevices) getDevices(podUID, contName string) sets.String { - containers, exists := pdev[podUID] - if !exists { - return nil - } - devices, exists := containers[contName] - if !exists { - return nil - } - return devices -} - -func (pdev podDevices) delete(pods []string) { - for _, uid := range pods { - delete(pdev, uid) - } -} - -func (pdev podDevices) devices() sets.String { - ret := sets.NewString() - for _, containerDevices := range pdev { - for _, deviceSet := range containerDevices { - ret = ret.Union(deviceSet) - } - } - return ret -} - -type DevicePluginHandler interface { - // Start starts device plugin registration service. - Start() error - // Devices returns all of registered devices keyed by resourceName. - Devices() map[string][]*pluginapi.Device - // Allocate attempts to allocate all of required extended resources for - // the input container, issues an Allocate rpc request for each of such - // resources, and returns their AllocateResponses on success. - Allocate(pod *v1.Pod, container *v1.Container, activePods []*v1.Pod) ([]*pluginapi.AllocateResponse, error) -} - -type DevicePluginHandlerImpl struct { - sync.Mutex - devicePluginManager deviceplugin.Manager - // devicePluginManagerMonitorCallback is used for testing only. - devicePluginManagerMonitorCallback deviceplugin.MonitorCallback - // allDevices contains all of registered resourceNames and their exported device IDs. - allDevices map[string]sets.String - // allocatedDevices contains pod to allocated device mapping, keyed by resourceName. - allocatedDevices map[string]podDevices -} - -// NewDevicePluginHandler create a DevicePluginHandler -// updateCapacityFunc is called to update ContainerManager capacity when -// device capacity changes. -func NewDevicePluginHandlerImpl(updateCapacityFunc func(v1.ResourceList)) (*DevicePluginHandlerImpl, error) { - glog.V(2).Infof("Creating Device Plugin Handler") - handler := &DevicePluginHandlerImpl{ - allDevices: make(map[string]sets.String), - allocatedDevices: make(map[string]podDevices), - } - - deviceManagerMonitorCallback := func(resourceName string, added, updated, deleted []*pluginapi.Device) { - var capacity = v1.ResourceList{} - kept := append(updated, added...) - if _, ok := handler.allDevices[resourceName]; !ok { - handler.allDevices[resourceName] = sets.NewString() - } - // For now, DevicePluginHandler only keeps track of healthy devices. - // We can revisit this later when the need comes to track unhealthy devices here. - for _, dev := range kept { - if dev.Health == pluginapi.Healthy { - handler.allDevices[resourceName].Insert(dev.ID) - } else { - handler.allDevices[resourceName].Delete(dev.ID) - } - } - for _, dev := range deleted { - handler.allDevices[resourceName].Delete(dev.ID) - } - capacity[v1.ResourceName(resourceName)] = *resource.NewQuantity(int64(handler.allDevices[resourceName].Len()), resource.DecimalSI) - updateCapacityFunc(capacity) - } - - mgr, err := deviceplugin.NewManagerImpl(pluginapi.KubeletSocket, deviceManagerMonitorCallback) - if err != nil { - return nil, fmt.Errorf("Failed to initialize device plugin manager: %+v", err) - } - - handler.devicePluginManager = mgr - handler.devicePluginManagerMonitorCallback = deviceManagerMonitorCallback - // Loads in allocatedDevices information from disk. - err = handler.readCheckpoint() - if err != nil { - glog.Warningf("Continue after failing to read checkpoint file. Device allocation info may NOT be up-to-date. Err: %v", err) - } - return handler, nil -} - -func (h *DevicePluginHandlerImpl) Start() error { - return h.devicePluginManager.Start() -} - -func (h *DevicePluginHandlerImpl) Devices() map[string][]*pluginapi.Device { - return h.devicePluginManager.Devices() -} - -func (h *DevicePluginHandlerImpl) Allocate(pod *v1.Pod, container *v1.Container, activePods []*v1.Pod) ([]*pluginapi.AllocateResponse, error) { - var ret []*pluginapi.AllocateResponse - h.updateAllocatedDevices(activePods) - for k, v := range container.Resources.Limits { - resource := string(k) - needed := int(v.Value()) - glog.V(3).Infof("needs %d %s", needed, resource) - _, registeredResource := h.allDevices[resource] - if !registeredResource || needed == 0 { - continue - } - h.Lock() - // Gets list of devices that have already been allocated. - // This can happen if a container restarts for example. - if h.allocatedDevices[resource] == nil { - h.allocatedDevices[resource] = make(podDevices) - } - devices := h.allocatedDevices[resource].getDevices(string(pod.UID), container.Name) - if devices != nil { - glog.V(3).Infof("Found pre-allocated devices for resource %s container %q in Pod %q: %v", resource, container.Name, pod.UID, devices.List()) - needed = needed - devices.Len() - } - // Get Devices in use. - devicesInUse := h.allocatedDevices[resource].devices() - // Get a list of available devices. - available := h.allDevices[resource].Difference(devicesInUse) - if int(available.Len()) < needed { - h.Unlock() - return nil, fmt.Errorf("requested number of devices unavailable for %s. Requested: %d, Available: %d", resource, needed, available.Len()) - } - allocated := available.UnsortedList()[:needed] - for _, device := range allocated { - // Update internal allocated device cache. - h.allocatedDevices[resource].insert(string(pod.UID), container.Name, device) - } - h.Unlock() - // devicePluginManager.Allocate involves RPC calls to device plugin, which - // could be heavy-weight. Therefore we want to perform this operation outside - // mutex lock. Note if Allcate call fails, we may leave container resources - // partially allocated for the failed container. We rely on updateAllocatedDevices() - // to garbage collect these resources later. Another side effect is that if - // we have X resource A and Y resource B in total, and two containers, container1 - // and container2 both require X resource A and Y resource B. Both allocation - // requests may fail if we serve them in mixed order. - // TODO: may revisit this part later if we see inefficient resource allocation - // in real use as the result of this. - resp, err := h.devicePluginManager.Allocate(resource, append(devices.UnsortedList(), allocated...)) - if err != nil { - return nil, err - } - ret = append(ret, resp) - } - // Checkpoints device to container allocation information. - if err := h.writeCheckpoint(); err != nil { - return nil, err - } - return ret, nil -} - -// updateAllocatedDevices updates the list of GPUs in use. -// It gets a list of active pods and then frees any GPUs that are bound to -// terminated pods. Returns error on failure. -func (h *DevicePluginHandlerImpl) updateAllocatedDevices(activePods []*v1.Pod) { - h.Lock() - defer h.Unlock() - activePodUids := sets.NewString() - for _, pod := range activePods { - activePodUids.Insert(string(pod.UID)) - } - for _, podDevs := range h.allocatedDevices { - allocatedPodUids := podDevs.pods() - podsToBeRemoved := allocatedPodUids.Difference(activePodUids) - glog.V(5).Infof("pods to be removed: %v", podsToBeRemoved.List()) - podDevs.delete(podsToBeRemoved.List()) - } -} - -type checkpointEntry struct { - PodUID string - ContainerName string - ResourceName string - DeviceID string -} - -// checkpointData struct is used to store pod to device allocation information -// in a checkpoint file. -// TODO: add version control when we need to change checkpoint format. -type checkpointData struct { - Entries []checkpointEntry -} - -// Checkpoints device to container allocation information to disk. -func (h *DevicePluginHandlerImpl) writeCheckpoint() error { - filepath := h.devicePluginManager.CheckpointFile() - var data checkpointData - for resourceName, podDev := range h.allocatedDevices { - for podUID, conDev := range podDev { - for conName, devs := range conDev { - for _, devId := range devs.UnsortedList() { - data.Entries = append(data.Entries, checkpointEntry{podUID, conName, resourceName, devId}) - } - } - } - } - dataJson, err := json.Marshal(data) - if err != nil { - return err - } - return ioutil.WriteFile(filepath, dataJson, 0644) -} - -// Reads device to container allocation information from disk, and populates -// h.allocatedDevices accordingly. -func (h *DevicePluginHandlerImpl) readCheckpoint() error { - filepath := h.devicePluginManager.CheckpointFile() - content, err := ioutil.ReadFile(filepath) - if err != nil && !os.IsNotExist(err) { - return fmt.Errorf("failed to read checkpoint file %q: %v", filepath, err) - } - glog.V(2).Infof("Read checkpoint file %s\n", filepath) - var data checkpointData - if err := json.Unmarshal(content, &data); err != nil { - return fmt.Errorf("failed to unmarshal checkpoint data: %v", err) - } - for _, entry := range data.Entries { - glog.V(2).Infof("Get checkpoint entry: %v %v %v %v\n", entry.PodUID, entry.ContainerName, entry.ResourceName, entry.DeviceID) - if h.allocatedDevices[entry.ResourceName] == nil { - h.allocatedDevices[entry.ResourceName] = make(podDevices) - } - h.allocatedDevices[entry.ResourceName].insert(entry.PodUID, entry.ContainerName, entry.DeviceID) - } - return nil -} diff --git a/pkg/kubelet/cm/device_plugin_handler_stub.go b/pkg/kubelet/cm/device_plugin_handler_stub.go deleted file mode 100644 index a70c281086c..00000000000 --- a/pkg/kubelet/cm/device_plugin_handler_stub.go +++ /dev/null @@ -1,42 +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 cm - -import ( - "k8s.io/api/core/v1" - pluginapi "k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1alpha1" -) - -// A simple stub implementation for DevicePluginHandler. -type DevicePluginHandlerStub struct{} - -func NewDevicePluginHandlerStub() (*DevicePluginHandlerStub, error) { - return &DevicePluginHandlerStub{}, nil -} - -func (h *DevicePluginHandlerStub) Start() error { - return nil -} - -func (h *DevicePluginHandlerStub) Devices() map[string][]*pluginapi.Device { - return make(map[string][]*pluginapi.Device) -} - -func (h *DevicePluginHandlerStub) Allocate(pod *v1.Pod, container *v1.Container, activePods []*v1.Pod) ([]*pluginapi.AllocateResponse, error) { - var ret []*pluginapi.AllocateResponse - return ret, nil -} diff --git a/pkg/kubelet/cm/device_plugin_handler_test.go b/pkg/kubelet/cm/device_plugin_handler_test.go deleted file mode 100644 index e9df09ef2cc..00000000000 --- a/pkg/kubelet/cm/device_plugin_handler_test.go +++ /dev/null @@ -1,285 +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 cm - -import ( - "flag" - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/uuid" - pluginapi "k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1alpha1" -) - -func TestUpdateCapacity(t *testing.T) { - var expected = v1.ResourceList{} - as := assert.New(t) - verifyCapacityFunc := func(updates v1.ResourceList) { - as.Equal(expected, updates) - } - testDevicePluginHandler, err := NewDevicePluginHandlerImpl(verifyCapacityFunc) - as.NotNil(testDevicePluginHandler) - as.Nil(err) - - devs := []*pluginapi.Device{ - {ID: "Device1", Health: pluginapi.Healthy}, - {ID: "Device2", Health: pluginapi.Healthy}, - {ID: "Device3", Health: pluginapi.Unhealthy}, - } - - resourceName := "resource1" - // Adds three devices for resource1, two healthy and one unhealthy. - // Expects capacity for resource1 to be 2. - expected[v1.ResourceName(resourceName)] = *resource.NewQuantity(int64(2), resource.DecimalSI) - testDevicePluginHandler.devicePluginManagerMonitorCallback(resourceName, devs, []*pluginapi.Device{}, []*pluginapi.Device{}) - // Deletes an unhealthy device should NOT change capacity. - testDevicePluginHandler.devicePluginManagerMonitorCallback(resourceName, []*pluginapi.Device{}, []*pluginapi.Device{}, []*pluginapi.Device{devs[2]}) - // Updates a healthy device to unhealthy should reduce capacity by 1. - expected[v1.ResourceName(resourceName)] = *resource.NewQuantity(int64(1), resource.DecimalSI) - // Deletes a healthy device should reduce capacity by 1. - expected[v1.ResourceName(resourceName)] = *resource.NewQuantity(int64(0), resource.DecimalSI) - // Tests adding another resource. - delete(expected, v1.ResourceName(resourceName)) - resourceName2 := "resource2" - expected[v1.ResourceName(resourceName2)] = *resource.NewQuantity(int64(2), resource.DecimalSI) - testDevicePluginHandler.devicePluginManagerMonitorCallback(resourceName2, devs, []*pluginapi.Device{}, []*pluginapi.Device{}) -} - -type stringPairType struct { - value1 string - value2 string -} - -// DevicePluginManager stub to test device Allocation behavior. -type DevicePluginManagerTestStub struct { - // All data structs are keyed by resourceName+DevId - devRuntimeDevices map[string][]stringPairType - devRuntimeMounts map[string][]stringPairType - devRuntimeEnvs map[string][]stringPairType -} - -func NewDevicePluginManagerTestStub() (*DevicePluginManagerTestStub, error) { - return &DevicePluginManagerTestStub{ - devRuntimeDevices: make(map[string][]stringPairType), - devRuntimeMounts: make(map[string][]stringPairType), - devRuntimeEnvs: make(map[string][]stringPairType), - }, nil -} - -func (m *DevicePluginManagerTestStub) Start() error { - return nil -} - -func (m *DevicePluginManagerTestStub) Devices() map[string][]*pluginapi.Device { - return make(map[string][]*pluginapi.Device) -} - -func (m *DevicePluginManagerTestStub) Allocate(resourceName string, devIds []string) (*pluginapi.AllocateResponse, error) { - resp := new(pluginapi.AllocateResponse) - for _, id := range devIds { - key := resourceName + id - fmt.Printf("Alloc device %q for resource %q\n", id, resourceName) - devRuntime := new(pluginapi.DeviceRuntimeSpec) - for _, dev := range m.devRuntimeDevices[key] { - devRuntime.Devices = append(devRuntime.Devices, &pluginapi.DeviceSpec{ - ContainerPath: dev.value1, - HostPath: dev.value2, - Permissions: "mrw", - }) - } - for _, mount := range m.devRuntimeMounts[key] { - fmt.Printf("Add mount %q %q\n", mount.value1, mount.value2) - devRuntime.Mounts = append(devRuntime.Mounts, &pluginapi.Mount{ - ContainerPath: mount.value1, - HostPath: mount.value2, - ReadOnly: true, - }) - } - devRuntime.Envs = make(map[string]string) - for _, env := range m.devRuntimeEnvs[key] { - devRuntime.Envs[env.value1] = env.value2 - } - resp.Spec = append(resp.Spec, devRuntime) - } - return resp, nil -} - -func (m *DevicePluginManagerTestStub) Stop() error { - return nil -} - -func (m *DevicePluginManagerTestStub) CheckpointFile() string { - return "/tmp/device-plugin-checkpoint" -} - -func TestCheckpoint(t *testing.T) { - resourceName1 := "domain1.com/resource1" - resourceName2 := "domain2.com/resource2" - - m, err := NewDevicePluginManagerTestStub() - as := assert.New(t) - as.Nil(err) - - testDevicePluginHandler := &DevicePluginHandlerImpl{ - devicePluginManager: m, - allDevices: make(map[string]sets.String), - allocatedDevices: make(map[string]podDevices), - } - testDevicePluginHandler.allocatedDevices[resourceName1] = make(podDevices) - testDevicePluginHandler.allocatedDevices[resourceName1].insert("pod1", "con1", "dev1") - testDevicePluginHandler.allocatedDevices[resourceName1].insert("pod1", "con1", "dev2") - testDevicePluginHandler.allocatedDevices[resourceName1].insert("pod1", "con2", "dev1") - testDevicePluginHandler.allocatedDevices[resourceName1].insert("pod2", "con1", "dev1") - testDevicePluginHandler.allocatedDevices[resourceName2] = make(podDevices) - testDevicePluginHandler.allocatedDevices[resourceName2].insert("pod1", "con1", "dev3") - testDevicePluginHandler.allocatedDevices[resourceName2].insert("pod1", "con1", "dev4") - - err = testDevicePluginHandler.writeCheckpoint() - as.Nil(err) - expected := testDevicePluginHandler.allocatedDevices - testDevicePluginHandler.allocatedDevices = make(map[string]podDevices) - err = testDevicePluginHandler.readCheckpoint() - as.Nil(err) - as.Equal(expected, testDevicePluginHandler.allocatedDevices) -} - -func TestPodContainerDeviceAllocation(t *testing.T) { - flag.Set("alsologtostderr", fmt.Sprintf("%t", true)) - var logLevel string - flag.StringVar(&logLevel, "logLevel", "4", "test") - flag.Lookup("v").Value.Set(logLevel) - - var activePods []*v1.Pod - resourceName1 := "domain1.com/resource1" - resourceQuantity1 := *resource.NewQuantity(int64(2), resource.DecimalSI) - devId1 := "dev1" - devId2 := "dev2" - resourceName2 := "domain2.com/resource2" - resourceQuantity2 := *resource.NewQuantity(int64(1), resource.DecimalSI) - devId3 := "dev3" - devId4 := "dev4" - - m, err := NewDevicePluginManagerTestStub() - as := assert.New(t) - as.Nil(err) - monitorCallback := func(resourceName string, added, updated, deleted []*pluginapi.Device) {} - - testDevicePluginHandler := &DevicePluginHandlerImpl{ - devicePluginManager: m, - devicePluginManagerMonitorCallback: monitorCallback, - allDevices: make(map[string]sets.String), - allocatedDevices: make(map[string]podDevices), - } - testDevicePluginHandler.allDevices[resourceName1] = sets.NewString() - testDevicePluginHandler.allDevices[resourceName1].Insert(devId1) - testDevicePluginHandler.allDevices[resourceName1].Insert(devId2) - testDevicePluginHandler.allDevices[resourceName2] = sets.NewString() - testDevicePluginHandler.allDevices[resourceName2].Insert(devId3) - testDevicePluginHandler.allDevices[resourceName2].Insert(devId4) - - m.devRuntimeDevices[resourceName1+devId1] = append(m.devRuntimeDevices[resourceName1+devId1], stringPairType{"/dev/aaa", "/dev/aaa"}) - m.devRuntimeDevices[resourceName1+devId1] = append(m.devRuntimeDevices[resourceName1+devId1], stringPairType{"/dev/bbb", "/dev/bbb"}) - m.devRuntimeDevices[resourceName1+devId2] = append(m.devRuntimeDevices[resourceName1+devId2], stringPairType{"/dev/ccc", "/dev/ccc"}) - m.devRuntimeMounts[resourceName1+devId1] = append(m.devRuntimeMounts[resourceName1+devId1], stringPairType{"/container_dir1/file1", "host_dir1/file1"}) - m.devRuntimeMounts[resourceName1+devId2] = append(m.devRuntimeMounts[resourceName1+devId2], stringPairType{"/container_dir1/file1", "host_dir1/file1"}) - m.devRuntimeEnvs[resourceName1+devId2] = append(m.devRuntimeEnvs[resourceName1+devId2], stringPairType{"key1", "val1"}) - m.devRuntimeEnvs[resourceName2+devId3] = append(m.devRuntimeEnvs[resourceName2+devId3], stringPairType{"key2", "val2"}) - m.devRuntimeEnvs[resourceName2+devId4] = append(m.devRuntimeEnvs[resourceName2+devId4], stringPairType{"key2", "val2"}) - - pod := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - UID: uuid.NewUUID(), - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: string(uuid.NewUUID()), - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceName(resourceName1): resourceQuantity1, - v1.ResourceName("cpu"): resourceQuantity1, - v1.ResourceName(resourceName2): resourceQuantity2, - }, - }, - }, - }, - }, - } - - cm := &containerManagerImpl{ - devicePluginHandler: testDevicePluginHandler, - } - activePods = append(activePods, pod) - runContainerOpts, err := cm.GetResources(pod, &pod.Spec.Containers[0], activePods) - as.Equal(len(runContainerOpts.Devices), 3) - // Two devices require to mount the same path. Expects a single mount entry to be created. - as.Equal(len(runContainerOpts.Mounts), 1) - as.Equal(runContainerOpts.Mounts[0].ContainerPath, "/container_dir1/file1") - as.Equal(len(runContainerOpts.Envs), 2) - - // Requesting to create a pod without enough resources should fail. - failPod := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - UID: uuid.NewUUID(), - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: string(uuid.NewUUID()), - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceName(resourceName1): resourceQuantity1, - }, - }, - }, - }, - }, - } - runContainerOpts2, err := cm.GetResources(failPod, &failPod.Spec.Containers[0], activePods) - as.NotNil(err) - as.Equal(len(runContainerOpts2.Devices), 0) - as.Equal(len(runContainerOpts2.Mounts), 0) - as.Equal(len(runContainerOpts2.Envs), 0) - - // Requesting to create a new pod with a single resourceName2 should succeed. - newPod := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - UID: uuid.NewUUID(), - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: string(uuid.NewUUID()), - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceName(resourceName2): resourceQuantity2, - }, - }, - }, - }, - }, - } - runContainerOpts3, err := cm.GetResources(newPod, &newPod.Spec.Containers[0], activePods) - as.Nil(err) - as.Equal(len(runContainerOpts3.Envs), 1) -} diff --git a/pkg/kubelet/cm/deviceplugin/BUILD b/pkg/kubelet/cm/deviceplugin/BUILD new file mode 100644 index 00000000000..11df63bea10 --- /dev/null +++ b/pkg/kubelet/cm/deviceplugin/BUILD @@ -0,0 +1,74 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_library( + name = "go_default_library", + srcs = [ + "device_plugin_stub.go", + "endpoint.go", + "manager.go", + "manager_stub.go", + "pod_devices.go", + "types.go", + ], + importpath = "k8s.io/kubernetes/pkg/kubelet/cm/deviceplugin", + deps = [ + "//pkg/apis/core/v1/helper:go_default_library", + "//pkg/kubelet/apis/deviceplugin/v1alpha:go_default_library", + "//pkg/kubelet/config:go_default_library", + "//pkg/kubelet/container:go_default_library", + "//pkg/kubelet/lifecycle:go_default_library", + "//pkg/kubelet/metrics:go_default_library", + "//pkg/kubelet/util/store:go_default_library", + "//pkg/scheduler/schedulercache:go_default_library", + "//pkg/util/filesystem:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/golang.org/x/net/context:go_default_library", + "//vendor/google.golang.org/grpc:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) + +go_test( + name = "go_default_test", + srcs = [ + "endpoint_test.go", + "manager_test.go", + ], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/kubelet/cm/deviceplugin", + deps = [ + "//pkg/kubelet/apis/deviceplugin/v1alpha:go_default_library", + "//pkg/kubelet/lifecycle:go_default_library", + "//pkg/kubelet/util/store:go_default_library", + "//pkg/scheduler/schedulercache:go_default_library", + "//pkg/util/filesystem:go_default_library", + "//vendor/github.com/stretchr/testify/assert:go_default_library", + "//vendor/github.com/stretchr/testify/require:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library", + ], +) diff --git a/pkg/kubelet/cm/deviceplugin/OWNERS b/pkg/kubelet/cm/deviceplugin/OWNERS new file mode 100644 index 00000000000..a374cd52454 --- /dev/null +++ b/pkg/kubelet/cm/deviceplugin/OWNERS @@ -0,0 +1,7 @@ +approvers: +- jiayingz +- vishh +reviewers: +- mindprince +- RenaudWasTaken +- vikaschoudhary16 diff --git a/pkg/kubelet/deviceplugin/device_plugin_stub.go b/pkg/kubelet/cm/deviceplugin/device_plugin_stub.go similarity index 99% rename from pkg/kubelet/deviceplugin/device_plugin_stub.go rename to pkg/kubelet/cm/deviceplugin/device_plugin_stub.go index a0f103d03a9..01f08c15987 100644 --- a/pkg/kubelet/deviceplugin/device_plugin_stub.go +++ b/pkg/kubelet/cm/deviceplugin/device_plugin_stub.go @@ -26,7 +26,7 @@ import ( "golang.org/x/net/context" "google.golang.org/grpc" - pluginapi "k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1alpha1" + pluginapi "k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1alpha" ) // Stub implementation for DevicePlugin. diff --git a/pkg/kubelet/cm/deviceplugin/endpoint.go b/pkg/kubelet/cm/deviceplugin/endpoint.go new file mode 100644 index 00000000000..523922d9dc0 --- /dev/null +++ b/pkg/kubelet/cm/deviceplugin/endpoint.go @@ -0,0 +1,202 @@ +/* +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 deviceplugin + +import ( + "fmt" + "net" + "sync" + "time" + + "github.com/golang/glog" + "golang.org/x/net/context" + "google.golang.org/grpc" + + pluginapi "k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1alpha" +) + +// endpoint maps to a single registered device plugin. It is responsible +// for managing gRPC communications with the device plugin and caching +// device states reported by the device plugin. +type endpoint interface { + run() + stop() + allocate(devs []string) (*pluginapi.AllocateResponse, error) + getDevices() []pluginapi.Device + callback(resourceName string, added, updated, deleted []pluginapi.Device) +} + +type endpointImpl struct { + client pluginapi.DevicePluginClient + clientConn *grpc.ClientConn + + socketPath string + resourceName string + + devices map[string]pluginapi.Device + mutex sync.Mutex + + cb monitorCallback +} + +// newEndpoint creates a new endpoint for the given resourceName. +func newEndpointImpl(socketPath, resourceName string, devices map[string]pluginapi.Device, callback monitorCallback) (*endpointImpl, error) { + client, c, err := dial(socketPath) + if err != nil { + glog.Errorf("Can't create new endpoint with path %s err %v", socketPath, err) + return nil, err + } + + return &endpointImpl{ + client: client, + clientConn: c, + + socketPath: socketPath, + resourceName: resourceName, + + devices: devices, + cb: callback, + }, nil +} + +func (e *endpointImpl) callback(resourceName string, added, updated, deleted []pluginapi.Device) { + e.cb(resourceName, added, updated, deleted) +} + +func (e *endpointImpl) getDevices() []pluginapi.Device { + e.mutex.Lock() + defer e.mutex.Unlock() + var devs []pluginapi.Device + + for _, d := range e.devices { + devs = append(devs, d) + } + + return devs +} + +// run initializes ListAndWatch gRPC call for the device plugin and +// blocks on receiving ListAndWatch gRPC stream updates. Each ListAndWatch +// stream update contains a new list of device states. listAndWatch compares the new +// device states with its cached states to get list of new, updated, and deleted devices. +// It then issues a callback to pass this information to the device manager which +// will adjust the resource available information accordingly. +func (e *endpointImpl) run() { + stream, err := e.client.ListAndWatch(context.Background(), &pluginapi.Empty{}) + if err != nil { + glog.Errorf(errListAndWatch, e.resourceName, err) + + return + } + + devices := make(map[string]pluginapi.Device) + + e.mutex.Lock() + for _, d := range e.devices { + devices[d.ID] = d + } + e.mutex.Unlock() + + for { + response, err := stream.Recv() + if err != nil { + glog.Errorf(errListAndWatch, e.resourceName, err) + return + } + + devs := response.Devices + glog.V(2).Infof("State pushed for device plugin %s", e.resourceName) + + newDevs := make(map[string]*pluginapi.Device) + var added, updated []pluginapi.Device + + for _, d := range devs { + dOld, ok := devices[d.ID] + newDevs[d.ID] = d + + if !ok { + glog.V(2).Infof("New device for Endpoint %s: %v", e.resourceName, d) + + devices[d.ID] = *d + added = append(added, *d) + + continue + } + + if d.Health == dOld.Health { + continue + } + + if d.Health == pluginapi.Unhealthy { + glog.Errorf("Device %s is now Unhealthy", d.ID) + } else if d.Health == pluginapi.Healthy { + glog.V(2).Infof("Device %s is now Healthy", d.ID) + } + + devices[d.ID] = *d + updated = append(updated, *d) + } + + var deleted []pluginapi.Device + for id, d := range devices { + if _, ok := newDevs[id]; ok { + continue + } + + glog.Errorf("Device %s was deleted", d.ID) + + deleted = append(deleted, d) + delete(devices, id) + } + + e.mutex.Lock() + // NOTE: Return a copy of 'devices' instead of returning a direct reference to local 'devices' + e.devices = make(map[string]pluginapi.Device) + for _, d := range devices { + e.devices[d.ID] = d + } + e.mutex.Unlock() + + e.callback(e.resourceName, added, updated, deleted) + } +} + +// allocate issues Allocate gRPC call to the device plugin. +func (e *endpointImpl) allocate(devs []string) (*pluginapi.AllocateResponse, error) { + return e.client.Allocate(context.Background(), &pluginapi.AllocateRequest{ + DevicesIDs: devs, + }) +} + +func (e *endpointImpl) stop() { + e.clientConn.Close() +} + +// dial establishes the gRPC communication with the registered device plugin. +func dial(unixSocketPath string) (pluginapi.DevicePluginClient, *grpc.ClientConn, error) { + c, err := grpc.Dial(unixSocketPath, grpc.WithInsecure(), + grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) { + return net.DialTimeout("unix", addr, timeout) + }), + ) + + if err != nil { + return nil, nil, fmt.Errorf(errFailedToDialDevicePlugin+" %v", err) + } + + return pluginapi.NewDevicePluginClient(c), c, nil +} diff --git a/pkg/kubelet/cm/deviceplugin/endpoint_test.go b/pkg/kubelet/cm/deviceplugin/endpoint_test.go new file mode 100644 index 00000000000..226148a6b06 --- /dev/null +++ b/pkg/kubelet/cm/deviceplugin/endpoint_test.go @@ -0,0 +1,114 @@ +/* +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 deviceplugin + +import ( + "path" + "testing" + "time" + + "github.com/stretchr/testify/require" + + pluginapi "k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1alpha" +) + +var ( + esocketName = "mock.sock" +) + +func TestNewEndpoint(t *testing.T) { + socket := path.Join("/tmp", esocketName) + + devs := []*pluginapi.Device{ + {ID: "ADeviceId", Health: pluginapi.Healthy}, + } + + p, e := esetup(t, devs, socket, "mock", func(n string, a, u, r []pluginapi.Device) {}) + defer ecleanup(t, p, e) +} + +func TestRun(t *testing.T) { + socket := path.Join("/tmp", esocketName) + + devs := []*pluginapi.Device{ + {ID: "ADeviceId", Health: pluginapi.Healthy}, + {ID: "AnotherDeviceId", Health: pluginapi.Healthy}, + } + + updated := []*pluginapi.Device{ + {ID: "ADeviceId", Health: pluginapi.Unhealthy}, + {ID: "AThirdDeviceId", Health: pluginapi.Healthy}, + } + + p, e := esetup(t, devs, socket, "mock", func(n string, a, u, r []pluginapi.Device) { + require.Len(t, a, 1) + require.Len(t, u, 1) + require.Len(t, r, 1) + + require.Equal(t, a[0].ID, updated[1].ID) + + require.Equal(t, u[0].ID, updated[0].ID) + require.Equal(t, u[0].Health, updated[0].Health) + + require.Equal(t, r[0].ID, devs[1].ID) + }) + defer ecleanup(t, p, e) + + go e.run() + p.Update(updated) + time.Sleep(time.Second) + + e.mutex.Lock() + defer e.mutex.Unlock() + + require.Len(t, e.devices, 2) + for _, dref := range updated { + d, ok := e.devices[dref.ID] + + require.True(t, ok) + require.Equal(t, d.ID, dref.ID) + require.Equal(t, d.Health, dref.Health) + } + +} + +func TestGetDevices(t *testing.T) { + e := endpointImpl{ + devices: map[string]pluginapi.Device{ + "ADeviceId": {ID: "ADeviceId", Health: pluginapi.Healthy}, + }, + } + devs := e.getDevices() + require.Len(t, devs, 1) +} + +func esetup(t *testing.T, devs []*pluginapi.Device, socket, resourceName string, callback monitorCallback) (*Stub, *endpointImpl) { + p := NewDevicePluginStub(devs, socket) + + err := p.Start() + require.NoError(t, err) + + e, err := newEndpointImpl(socket, "mock", make(map[string]pluginapi.Device), func(n string, a, u, r []pluginapi.Device) {}) + require.NoError(t, err) + + return p, e +} + +func ecleanup(t *testing.T, p *Stub, e *endpointImpl) { + p.Stop() + e.stop() +} diff --git a/pkg/kubelet/cm/deviceplugin/manager.go b/pkg/kubelet/cm/deviceplugin/manager.go new file mode 100644 index 00000000000..646dd658793 --- /dev/null +++ b/pkg/kubelet/cm/deviceplugin/manager.go @@ -0,0 +1,662 @@ +/* +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 deviceplugin + +import ( + "encoding/json" + "fmt" + "net" + "os" + "path/filepath" + "sync" + "time" + + "github.com/golang/glog" + "golang.org/x/net/context" + "google.golang.org/grpc" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/util/sets" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" + pluginapi "k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1alpha" + "k8s.io/kubernetes/pkg/kubelet/config" + "k8s.io/kubernetes/pkg/kubelet/lifecycle" + "k8s.io/kubernetes/pkg/kubelet/metrics" + utilstore "k8s.io/kubernetes/pkg/kubelet/util/store" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" + utilfs "k8s.io/kubernetes/pkg/util/filesystem" +) + +// ActivePodsFunc is a function that returns a list of pods to reconcile. +type ActivePodsFunc func() []*v1.Pod + +// monitorCallback is the function called when a device's health state changes, +// or new devices are reported, or old devices are deleted. +// Updated contains the most recent state of the Device. +type monitorCallback func(resourceName string, added, updated, deleted []pluginapi.Device) + +// ManagerImpl is the structure in charge of managing Device Plugins. +type ManagerImpl struct { + socketname string + socketdir string + + endpoints map[string]endpoint // Key is ResourceName + mutex sync.Mutex + + server *grpc.Server + + // activePods is a method for listing active pods on the node + // so the amount of pluginResources requested by existing pods + // could be counted when updating allocated devices + activePods ActivePodsFunc + + // sourcesReady provides the readiness of kubelet configuration sources such as apiserver update readiness. + // We use it to determine when we can purge inactive pods from checkpointed state. + sourcesReady config.SourcesReady + + // callback is used for updating devices' states in one time call. + // e.g. a new device is advertised, two old devices are deleted and a running device fails. + callback monitorCallback + + // allDevices contains all of registered resourceNames and their exported device IDs. + allDevices map[string]sets.String + + // allocatedDevices contains allocated deviceIds, keyed by resourceName. + allocatedDevices map[string]sets.String + + // podDevices contains pod to allocated device mapping. + podDevices podDevices + store utilstore.Store +} + +type sourcesReadyStub struct{} + +func (s *sourcesReadyStub) AddSource(source string) {} +func (s *sourcesReadyStub) AllReady() bool { return true } + +// NewManagerImpl creates a new manager. +func NewManagerImpl() (*ManagerImpl, error) { + return newManagerImpl(pluginapi.KubeletSocket) +} + +func newManagerImpl(socketPath string) (*ManagerImpl, error) { + glog.V(2).Infof("Creating Device Plugin manager at %s", socketPath) + + if socketPath == "" || !filepath.IsAbs(socketPath) { + return nil, fmt.Errorf(errBadSocket+" %v", socketPath) + } + + dir, file := filepath.Split(socketPath) + manager := &ManagerImpl{ + endpoints: make(map[string]endpoint), + socketname: file, + socketdir: dir, + allDevices: make(map[string]sets.String), + allocatedDevices: make(map[string]sets.String), + podDevices: make(podDevices), + } + manager.callback = manager.genericDeviceUpdateCallback + + // The following structs are populated with real implementations in manager.Start() + // Before that, initializes them to perform no-op operations. + manager.activePods = func() []*v1.Pod { return []*v1.Pod{} } + manager.sourcesReady = &sourcesReadyStub{} + var err error + manager.store, err = utilstore.NewFileStore(dir, utilfs.DefaultFs{}) + if err != nil { + return nil, fmt.Errorf("failed to initialize device plugin checkpointing store: %+v", err) + } + + return manager, nil +} + +func (m *ManagerImpl) genericDeviceUpdateCallback(resourceName string, added, updated, deleted []pluginapi.Device) { + kept := append(updated, added...) + m.mutex.Lock() + if _, ok := m.allDevices[resourceName]; !ok { + m.allDevices[resourceName] = sets.NewString() + } + // For now, Manager only keeps track of healthy devices. + // TODO: adds support to track unhealthy devices. + for _, dev := range kept { + if dev.Health == pluginapi.Healthy { + m.allDevices[resourceName].Insert(dev.ID) + } else { + m.allDevices[resourceName].Delete(dev.ID) + } + } + for _, dev := range deleted { + m.allDevices[resourceName].Delete(dev.ID) + } + m.mutex.Unlock() + m.writeCheckpoint() +} + +func (m *ManagerImpl) removeContents(dir string) error { + d, err := os.Open(dir) + if err != nil { + return err + } + defer d.Close() + names, err := d.Readdirnames(-1) + if err != nil { + return err + } + for _, name := range names { + filePath := filepath.Join(dir, name) + if filePath == m.checkpointFile() { + continue + } + stat, err := os.Stat(filePath) + if err != nil { + glog.Errorf("Failed to stat file %v: %v", filePath, err) + continue + } + if stat.IsDir() { + continue + } + err = os.RemoveAll(filePath) + if err != nil { + return err + } + } + return nil +} + +const ( + // kubeletDevicePluginCheckpoint is the file name of device plugin checkpoint + kubeletDevicePluginCheckpoint = "kubelet_internal_checkpoint" +) + +// checkpointFile returns device plugin checkpoint file path. +func (m *ManagerImpl) checkpointFile() string { + return filepath.Join(m.socketdir, kubeletDevicePluginCheckpoint) +} + +// Start starts the Device Plugin Manager amd start initialization of +// podDevices and allocatedDevices information from checkpoint-ed state and +// starts device plugin registration service. +func (m *ManagerImpl) Start(activePods ActivePodsFunc, sourcesReady config.SourcesReady) error { + glog.V(2).Infof("Starting Device Plugin manager") + + m.activePods = activePods + m.sourcesReady = sourcesReady + + // Loads in allocatedDevices information from disk. + err := m.readCheckpoint() + if err != nil { + glog.Warningf("Continue after failing to read checkpoint file. Device allocation info may NOT be up-to-date. Err: %v", err) + } + + socketPath := filepath.Join(m.socketdir, m.socketname) + os.MkdirAll(m.socketdir, 0755) + + // Removes all stale sockets in m.socketdir. Device plugins can monitor + // this and use it as a signal to re-register with the new Kubelet. + if err := m.removeContents(m.socketdir); err != nil { + glog.Errorf("Fail to clean up stale contents under %s: %+v", m.socketdir, err) + } + + s, err := net.Listen("unix", socketPath) + if err != nil { + glog.Errorf(errListenSocket+" %+v", err) + return err + } + + m.server = grpc.NewServer([]grpc.ServerOption{}...) + + pluginapi.RegisterRegistrationServer(m.server, m) + go m.server.Serve(s) + + glog.V(2).Infof("Serving device plugin registration server on %q", socketPath) + + return nil +} + +// Devices is the map of devices that are known by the Device +// Plugin manager with the kind of the devices as key +func (m *ManagerImpl) Devices() map[string][]pluginapi.Device { + m.mutex.Lock() + defer m.mutex.Unlock() + + devs := make(map[string][]pluginapi.Device) + for k, e := range m.endpoints { + glog.V(3).Infof("Endpoint: %+v: %p", k, e) + devs[k] = e.getDevices() + } + + return devs +} + +// Allocate is the call that you can use to allocate a set of devices +// from the registered device plugins. +func (m *ManagerImpl) Allocate(node *schedulercache.NodeInfo, attrs *lifecycle.PodAdmitAttributes) error { + pod := attrs.Pod + devicesToReuse := make(map[string]sets.String) + // TODO: Reuse devices between init containers and regular containers. + for _, container := range pod.Spec.InitContainers { + if err := m.allocateContainerResources(pod, &container, devicesToReuse); err != nil { + return err + } + m.podDevices.addContainerAllocatedResources(string(pod.UID), container.Name, devicesToReuse) + } + for _, container := range pod.Spec.Containers { + if err := m.allocateContainerResources(pod, &container, devicesToReuse); err != nil { + return err + } + m.podDevices.removeContainerAllocatedResources(string(pod.UID), container.Name, devicesToReuse) + } + + m.mutex.Lock() + defer m.mutex.Unlock() + + // quick return if no pluginResources requested + if _, podRequireDevicePluginResource := m.podDevices[string(pod.UID)]; !podRequireDevicePluginResource { + return nil + } + + m.sanitizeNodeAllocatable(node) + return nil +} + +// Register registers a device plugin. +func (m *ManagerImpl) Register(ctx context.Context, r *pluginapi.RegisterRequest) (*pluginapi.Empty, error) { + glog.Infof("Got registration request from device plugin with resource name %q", r.ResourceName) + metrics.DevicePluginRegistrationCount.WithLabelValues(r.ResourceName).Inc() + if r.Version != pluginapi.Version { + errorString := fmt.Sprintf(errUnsuportedVersion, r.Version, pluginapi.Version) + glog.Infof("Bad registration request from device plugin with resource name %q: %v", r.ResourceName, errorString) + return &pluginapi.Empty{}, fmt.Errorf(errorString) + } + + if !v1helper.IsExtendedResourceName(v1.ResourceName(r.ResourceName)) { + errorString := fmt.Sprintf(errInvalidResourceName, r.ResourceName) + glog.Infof("Bad registration request from device plugin: %v", errorString) + return &pluginapi.Empty{}, fmt.Errorf(errorString) + } + + // TODO: for now, always accepts newest device plugin. Later may consider to + // add some policies here, e.g., verify whether an old device plugin with the + // same resource name is still alive to determine whether we want to accept + // the new registration. + go m.addEndpoint(r) + + return &pluginapi.Empty{}, nil +} + +// Stop is the function that can stop the gRPC server. +func (m *ManagerImpl) Stop() error { + m.mutex.Lock() + defer m.mutex.Unlock() + for _, e := range m.endpoints { + e.stop() + } + + m.server.Stop() + return nil +} + +func (m *ManagerImpl) addEndpoint(r *pluginapi.RegisterRequest) { + existingDevs := make(map[string]pluginapi.Device) + m.mutex.Lock() + old, ok := m.endpoints[r.ResourceName] + if ok && old != nil { + // Pass devices of previous endpoint into re-registered one, + // to avoid potential orphaned devices upon re-registration + devices := make(map[string]pluginapi.Device) + for _, device := range old.getDevices() { + devices[device.ID] = device + } + existingDevs = devices + } + m.mutex.Unlock() + + socketPath := filepath.Join(m.socketdir, r.Endpoint) + e, err := newEndpointImpl(socketPath, r.ResourceName, existingDevs, m.callback) + if err != nil { + glog.Errorf("Failed to dial device plugin with request %v: %v", r, err) + return + } + + m.mutex.Lock() + // Check for potential re-registration during the initialization of new endpoint, + // and skip updating if re-registration happens. + // TODO: simplify the part once we have a better way to handle registered devices + ext := m.endpoints[r.ResourceName] + if ext != old { + glog.Warningf("Some other endpoint %v is added while endpoint %v is initialized", ext, e) + m.mutex.Unlock() + e.stop() + return + } + // Associates the newly created endpoint with the corresponding resource name. + // Stops existing endpoint if there is any. + m.endpoints[r.ResourceName] = e + glog.V(2).Infof("Registered endpoint %v", e) + m.mutex.Unlock() + + if old != nil { + old.stop() + } + + go func() { + e.run() + e.stop() + + m.mutex.Lock() + if old, ok := m.endpoints[r.ResourceName]; ok && old == e { + glog.V(2).Infof("Delete resource for endpoint %v", e) + delete(m.endpoints, r.ResourceName) + } + + glog.V(2).Infof("Unregistered endpoint %v", e) + m.mutex.Unlock() + }() +} + +// GetCapacity is expected to be called when Kubelet updates its node status. +// The first returned variable contains the registered device plugin resource capacity. +// The second returned variable contains previously registered resources that are no longer active. +// Kubelet uses this information to update resource capacity/allocatable in its node status. +// After the call, device plugin can remove the inactive resources from its internal list as the +// change is already reflected in Kubelet node status. +// Note in the special case after Kubelet restarts, device plugin resource capacities can +// temporarily drop to zero till corresponding device plugins re-register. This is OK because +// cm.UpdatePluginResource() run during predicate Admit guarantees we adjust nodeinfo +// capacity for already allocated pods so that they can continue to run. However, new pods +// requiring device plugin resources will not be scheduled till device plugin re-registers. +func (m *ManagerImpl) GetCapacity() (v1.ResourceList, []string) { + needsUpdateCheckpoint := false + var capacity = v1.ResourceList{} + var deletedResources []string + m.mutex.Lock() + for resourceName, devices := range m.allDevices { + if _, ok := m.endpoints[resourceName]; !ok { + delete(m.allDevices, resourceName) + deletedResources = append(deletedResources, resourceName) + needsUpdateCheckpoint = true + } else { + capacity[v1.ResourceName(resourceName)] = *resource.NewQuantity(int64(devices.Len()), resource.DecimalSI) + } + } + m.mutex.Unlock() + if needsUpdateCheckpoint { + m.writeCheckpoint() + } + return capacity, deletedResources +} + +// checkpointData struct is used to store pod to device allocation information +// and registered device information in a checkpoint file. +// TODO: add version control when we need to change checkpoint format. +type checkpointData struct { + PodDeviceEntries []podDevicesCheckpointEntry + RegisteredDevices map[string][]string +} + +// Checkpoints device to container allocation information to disk. +func (m *ManagerImpl) writeCheckpoint() error { + m.mutex.Lock() + data := checkpointData{ + PodDeviceEntries: m.podDevices.toCheckpointData(), + RegisteredDevices: make(map[string][]string), + } + for resource, devices := range m.allDevices { + data.RegisteredDevices[resource] = devices.UnsortedList() + } + m.mutex.Unlock() + + dataJSON, err := json.Marshal(data) + if err != nil { + return err + } + err = m.store.Write(kubeletDevicePluginCheckpoint, dataJSON) + if err != nil { + return fmt.Errorf("failed to write deviceplugin checkpoint file %q: %v", kubeletDevicePluginCheckpoint, err) + } + return nil +} + +// Reads device to container allocation information from disk, and populates +// m.allocatedDevices accordingly. +func (m *ManagerImpl) readCheckpoint() error { + content, err := m.store.Read(kubeletDevicePluginCheckpoint) + if err != nil { + if err == utilstore.ErrKeyNotFound { + return nil + } + return fmt.Errorf("failed to read checkpoint file %q: %v", kubeletDevicePluginCheckpoint, err) + } + glog.V(4).Infof("Read checkpoint file %s\n", kubeletDevicePluginCheckpoint) + var data checkpointData + if err := json.Unmarshal(content, &data); err != nil { + return fmt.Errorf("failed to unmarshal deviceplugin checkpoint data: %v", err) + } + + m.mutex.Lock() + defer m.mutex.Unlock() + m.podDevices.fromCheckpointData(data.PodDeviceEntries) + m.allocatedDevices = m.podDevices.devices() + for resource, devices := range data.RegisteredDevices { + m.allDevices[resource] = sets.NewString() + for _, dev := range devices { + m.allDevices[resource].Insert(dev) + } + } + return nil +} + +// updateAllocatedDevices gets a list of active pods and then frees any Devices that are bound to +// terminated pods. Returns error on failure. +func (m *ManagerImpl) updateAllocatedDevices(activePods []*v1.Pod) { + if !m.sourcesReady.AllReady() { + return + } + m.mutex.Lock() + defer m.mutex.Unlock() + activePodUids := sets.NewString() + for _, pod := range activePods { + activePodUids.Insert(string(pod.UID)) + } + allocatedPodUids := m.podDevices.pods() + podsToBeRemoved := allocatedPodUids.Difference(activePodUids) + if len(podsToBeRemoved) <= 0 { + return + } + glog.V(3).Infof("pods to be removed: %v", podsToBeRemoved.List()) + m.podDevices.delete(podsToBeRemoved.List()) + // Regenerated allocatedDevices after we update pod allocation information. + m.allocatedDevices = m.podDevices.devices() +} + +// Returns list of device Ids we need to allocate with Allocate rpc call. +// Returns empty list in case we don't need to issue the Allocate rpc call. +func (m *ManagerImpl) devicesToAllocate(podUID, contName, resource string, required int, reusableDevices sets.String) (sets.String, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + needed := required + // Gets list of devices that have already been allocated. + // This can happen if a container restarts for example. + devices := m.podDevices.containerDevices(podUID, contName, resource) + if devices != nil { + glog.V(3).Infof("Found pre-allocated devices for resource %s container %q in Pod %q: %v", resource, contName, podUID, devices.List()) + needed = needed - devices.Len() + // A pod's resource is not expected to change once admitted by the API server, + // so just fail loudly here. We can revisit this part if this no longer holds. + if needed != 0 { + return nil, fmt.Errorf("pod %v container %v changed request for resource %v from %v to %v", podUID, contName, resource, devices.Len(), required) + } + } + if needed == 0 { + // No change, no work. + return nil, nil + } + glog.V(3).Infof("Needs to allocate %v %v for pod %q container %q", needed, resource, podUID, contName) + // Needs to allocate additional devices. + if _, ok := m.allDevices[resource]; !ok { + return nil, fmt.Errorf("can't allocate unregistered device %v", resource) + } + devices = sets.NewString() + // Allocates from reusableDevices list first. + for device := range reusableDevices { + devices.Insert(device) + needed-- + if needed == 0 { + return devices, nil + } + } + // Needs to allocate additional devices. + if m.allocatedDevices[resource] == nil { + m.allocatedDevices[resource] = sets.NewString() + } + // Gets Devices in use. + devicesInUse := m.allocatedDevices[resource] + // Gets a list of available devices. + available := m.allDevices[resource].Difference(devicesInUse) + if int(available.Len()) < needed { + return nil, fmt.Errorf("requested number of devices unavailable for %s. Requested: %d, Available: %d", resource, needed, available.Len()) + } + allocated := available.UnsortedList()[:needed] + // Updates m.allocatedDevices with allocated devices to prevent them + // from being allocated to other pods/containers, given that we are + // not holding lock during the rpc call. + for _, device := range allocated { + m.allocatedDevices[resource].Insert(device) + devices.Insert(device) + } + return devices, nil +} + +// allocateContainerResources attempts to allocate all of required device +// plugin resources for the input container, issues an Allocate rpc request +// for each new device resource requirement, processes their AllocateResponses, +// and updates the cached containerDevices on success. +func (m *ManagerImpl) allocateContainerResources(pod *v1.Pod, container *v1.Container, devicesToReuse map[string]sets.String) error { + podUID := string(pod.UID) + contName := container.Name + allocatedDevicesUpdated := false + // Extended resources are not allowed to be overcommitted. + // Since device plugin advertises extended resources, + // therefore Requests must be equal to Limits and iterating + // over the Limits should be sufficient. + for k, v := range container.Resources.Limits { + resource := string(k) + needed := int(v.Value()) + glog.V(3).Infof("needs %d %s", needed, resource) + _, registeredResource := m.allDevices[resource] + _, allocatedResource := m.allocatedDevices[resource] + // Continues if this is neither an active device plugin resource nor + // a resource we have previously allocated. + if !registeredResource && !allocatedResource { + continue + } + // Updates allocatedDevices to garbage collect any stranded resources + // before doing the device plugin allocation. + if !allocatedDevicesUpdated { + m.updateAllocatedDevices(m.activePods()) + allocatedDevicesUpdated = true + } + allocDevices, err := m.devicesToAllocate(podUID, contName, resource, needed, devicesToReuse[resource]) + if err != nil { + return err + } + if allocDevices == nil || len(allocDevices) <= 0 { + continue + } + startRPCTime := time.Now() + // devicePluginManager.Allocate involves RPC calls to device plugin, which + // could be heavy-weight. Therefore we want to perform this operation outside + // mutex lock. Note if Allocate call fails, we may leave container resources + // partially allocated for the failed container. We rely on updateAllocatedDevices() + // to garbage collect these resources later. Another side effect is that if + // we have X resource A and Y resource B in total, and two containers, container1 + // and container2 both require X resource A and Y resource B. Both allocation + // requests may fail if we serve them in mixed order. + // TODO: may revisit this part later if we see inefficient resource allocation + // in real use as the result of this. Should also consider to parallize device + // plugin Allocate grpc calls if it becomes common that a container may require + // resources from multiple device plugins. + m.mutex.Lock() + e, ok := m.endpoints[resource] + m.mutex.Unlock() + if !ok { + m.mutex.Lock() + m.allocatedDevices = m.podDevices.devices() + m.mutex.Unlock() + return fmt.Errorf("Unknown Device Plugin %s", resource) + } + + devs := allocDevices.UnsortedList() + glog.V(3).Infof("Making allocation request for devices %v for device plugin %s", devs, resource) + resp, err := e.allocate(devs) + metrics.DevicePluginAllocationLatency.WithLabelValues(resource).Observe(metrics.SinceInMicroseconds(startRPCTime)) + if err != nil { + // In case of allocation failure, we want to restore m.allocatedDevices + // to the actual allocated state from m.podDevices. + m.mutex.Lock() + m.allocatedDevices = m.podDevices.devices() + m.mutex.Unlock() + return err + } + + // Update internal cached podDevices state. + m.mutex.Lock() + m.podDevices.insert(podUID, contName, resource, allocDevices, resp) + m.mutex.Unlock() + } + + // Checkpoints device to container allocation information. + return m.writeCheckpoint() +} + +// GetDeviceRunContainerOptions checks whether we have cached containerDevices +// for the passed-in and returns its DeviceRunContainerOptions +// for the found one. An empty struct is returned in case no cached state is found. +func (m *ManagerImpl) GetDeviceRunContainerOptions(pod *v1.Pod, container *v1.Container) *DeviceRunContainerOptions { + m.mutex.Lock() + defer m.mutex.Unlock() + return m.podDevices.deviceRunContainerOptions(string(pod.UID), container.Name) +} + +// sanitizeNodeAllocatable scans through allocatedDevices in the device manager +// and if necessary, updates allocatableResource in nodeInfo to at least equal to +// the allocated capacity. This allows pods that have already been scheduled on +// the node to pass GeneralPredicates admission checking even upon device plugin failure. +func (m *ManagerImpl) sanitizeNodeAllocatable(node *schedulercache.NodeInfo) { + var newAllocatableResource *schedulercache.Resource + allocatableResource := node.AllocatableResource() + if allocatableResource.ScalarResources == nil { + allocatableResource.ScalarResources = make(map[v1.ResourceName]int64) + } + for resource, devices := range m.allocatedDevices { + needed := devices.Len() + quant, ok := allocatableResource.ScalarResources[v1.ResourceName(resource)] + if ok && int(quant) >= needed { + continue + } + // Needs to update nodeInfo.AllocatableResource to make sure + // NodeInfo.allocatableResource at least equal to the capacity already allocated. + if newAllocatableResource == nil { + newAllocatableResource = allocatableResource.Clone() + } + newAllocatableResource.ScalarResources[v1.ResourceName(resource)] = int64(needed) + } + if newAllocatableResource != nil { + node.SetAllocatableResource(newAllocatableResource) + } +} diff --git a/pkg/kubelet/cm/deviceplugin/manager_stub.go b/pkg/kubelet/cm/deviceplugin/manager_stub.go new file mode 100644 index 00000000000..5d7a4b74c5d --- /dev/null +++ b/pkg/kubelet/cm/deviceplugin/manager_stub.go @@ -0,0 +1,63 @@ +/* +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 deviceplugin + +import ( + "k8s.io/api/core/v1" + pluginapi "k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1alpha" + "k8s.io/kubernetes/pkg/kubelet/config" + "k8s.io/kubernetes/pkg/kubelet/lifecycle" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" +) + +// ManagerStub provides a simple stub implementation for the Device Manager. +type ManagerStub struct{} + +// NewManagerStub creates a ManagerStub. +func NewManagerStub() (*ManagerStub, error) { + return &ManagerStub{}, nil +} + +// Start simply returns nil. +func (h *ManagerStub) Start(activePods ActivePodsFunc, sourcesReady config.SourcesReady) error { + return nil +} + +// Stop simply returns nil. +func (h *ManagerStub) Stop() error { + return nil +} + +// Devices returns an empty map. +func (h *ManagerStub) Devices() map[string][]pluginapi.Device { + return make(map[string][]pluginapi.Device) +} + +// Allocate simply returns nil. +func (h *ManagerStub) Allocate(node *schedulercache.NodeInfo, attrs *lifecycle.PodAdmitAttributes) error { + return nil +} + +// GetDeviceRunContainerOptions simply returns nil. +func (h *ManagerStub) GetDeviceRunContainerOptions(pod *v1.Pod, container *v1.Container) *DeviceRunContainerOptions { + return nil +} + +// GetCapacity simply returns nil capacity and empty removed resource list. +func (h *ManagerStub) GetCapacity() (v1.ResourceList, []string) { + return nil, []string{} +} diff --git a/pkg/kubelet/cm/deviceplugin/manager_test.go b/pkg/kubelet/cm/deviceplugin/manager_test.go new file mode 100644 index 00000000000..88147077c3a --- /dev/null +++ b/pkg/kubelet/cm/deviceplugin/manager_test.go @@ -0,0 +1,707 @@ +/* +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 deviceplugin + +import ( + "flag" + "fmt" + "io/ioutil" + "os" + "reflect" + "sync/atomic" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/uuid" + pluginapi "k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1alpha" + "k8s.io/kubernetes/pkg/kubelet/lifecycle" + utilstore "k8s.io/kubernetes/pkg/kubelet/util/store" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" + utilfs "k8s.io/kubernetes/pkg/util/filesystem" +) + +const ( + socketName = "/tmp/device_plugin/server.sock" + pluginSocketName = "/tmp/device_plugin/device-plugin.sock" + testResourceName = "fake-domain/resource" +) + +func TestNewManagerImpl(t *testing.T) { + _, err := newManagerImpl(socketName) + require.NoError(t, err) +} + +func TestNewManagerImplStart(t *testing.T) { + m, p := setup(t, []*pluginapi.Device{}, func(n string, a, u, r []pluginapi.Device) {}) + cleanup(t, m, p) +} + +// Tests that the device plugin manager correctly handles registration and re-registration by +// making sure that after registration, devices are correctly updated and if a re-registration +// happens, we will NOT delete devices; and no orphaned devices left. +func TestDevicePluginReRegistration(t *testing.T) { + devs := []*pluginapi.Device{ + {ID: "Dev1", Health: pluginapi.Healthy}, + {ID: "Dev2", Health: pluginapi.Healthy}, + } + devsForRegistration := []*pluginapi.Device{ + {ID: "Dev3", Health: pluginapi.Healthy}, + } + + expCallbackCount := int32(0) + callbackCount := int32(0) + callbackChan := make(chan int32) + callback := func(n string, a, u, r []pluginapi.Device) { + callbackCount++ + if callbackCount > atomic.LoadInt32(&expCallbackCount) { + t.FailNow() + } + callbackChan <- callbackCount + } + m, p1 := setup(t, devs, callback) + atomic.StoreInt32(&expCallbackCount, 1) + p1.Register(socketName, testResourceName) + // Wait for the first callback to be issued. + + <-callbackChan + devices := m.Devices() + require.Equal(t, 2, len(devices[testResourceName]), "Devices are not updated.") + + p2 := NewDevicePluginStub(devs, pluginSocketName+".new") + err := p2.Start() + require.NoError(t, err) + atomic.StoreInt32(&expCallbackCount, 2) + p2.Register(socketName, testResourceName) + // Wait for the second callback to be issued. + <-callbackChan + + devices2 := m.Devices() + require.Equal(t, 2, len(devices2[testResourceName]), "Devices shouldn't change.") + + // Test the scenario that a plugin re-registers with different devices. + p3 := NewDevicePluginStub(devsForRegistration, pluginSocketName+".third") + err = p3.Start() + require.NoError(t, err) + atomic.StoreInt32(&expCallbackCount, 3) + p3.Register(socketName, testResourceName) + // Wait for the second callback to be issued. + <-callbackChan + + devices3 := m.Devices() + require.Equal(t, 1, len(devices3[testResourceName]), "Devices of plugin previously registered should be removed.") + p2.Stop() + p3.Stop() + cleanup(t, m, p1) + close(callbackChan) +} + +func setup(t *testing.T, devs []*pluginapi.Device, callback monitorCallback) (Manager, *Stub) { + m, err := newManagerImpl(socketName) + require.NoError(t, err) + + m.callback = callback + + activePods := func() []*v1.Pod { + return []*v1.Pod{} + } + err = m.Start(activePods, &sourcesReadyStub{}) + require.NoError(t, err) + + p := NewDevicePluginStub(devs, pluginSocketName) + err = p.Start() + require.NoError(t, err) + + return m, p +} + +func cleanup(t *testing.T, m Manager, p *Stub) { + p.Stop() + m.Stop() +} + +func TestUpdateCapacity(t *testing.T) { + testManager, err := newManagerImpl(socketName) + as := assert.New(t) + as.NotNil(testManager) + as.Nil(err) + + devs := []pluginapi.Device{ + {ID: "Device1", Health: pluginapi.Healthy}, + {ID: "Device2", Health: pluginapi.Healthy}, + {ID: "Device3", Health: pluginapi.Unhealthy}, + } + callback := testManager.genericDeviceUpdateCallback + + // Adds three devices for resource1, two healthy and one unhealthy. + // Expects capacity for resource1 to be 2. + resourceName1 := "domain1.com/resource1" + testManager.endpoints[resourceName1] = &endpointImpl{devices: make(map[string]pluginapi.Device)} + callback(resourceName1, devs, []pluginapi.Device{}, []pluginapi.Device{}) + capacity, removedResources := testManager.GetCapacity() + resource1Capacity, ok := capacity[v1.ResourceName(resourceName1)] + as.True(ok) + as.Equal(int64(2), resource1Capacity.Value()) + as.Equal(0, len(removedResources)) + + // Deletes an unhealthy device should NOT change capacity. + callback(resourceName1, []pluginapi.Device{}, []pluginapi.Device{}, []pluginapi.Device{devs[2]}) + capacity, removedResources = testManager.GetCapacity() + resource1Capacity, ok = capacity[v1.ResourceName(resourceName1)] + as.True(ok) + as.Equal(int64(2), resource1Capacity.Value()) + as.Equal(0, len(removedResources)) + + // Updates a healthy device to unhealthy should reduce capacity by 1. + dev2 := devs[1] + dev2.Health = pluginapi.Unhealthy + callback(resourceName1, []pluginapi.Device{}, []pluginapi.Device{dev2}, []pluginapi.Device{}) + capacity, removedResources = testManager.GetCapacity() + resource1Capacity, ok = capacity[v1.ResourceName(resourceName1)] + as.True(ok) + as.Equal(int64(1), resource1Capacity.Value()) + as.Equal(0, len(removedResources)) + + // Deletes a healthy device should reduce capacity by 1. + callback(resourceName1, []pluginapi.Device{}, []pluginapi.Device{}, []pluginapi.Device{devs[0]}) + capacity, removedResources = testManager.GetCapacity() + resource1Capacity, ok = capacity[v1.ResourceName(resourceName1)] + as.True(ok) + as.Equal(int64(0), resource1Capacity.Value()) + as.Equal(0, len(removedResources)) + + // Tests adding another resource. + resourceName2 := "resource2" + testManager.endpoints[resourceName2] = &endpointImpl{devices: make(map[string]pluginapi.Device)} + callback(resourceName2, devs, []pluginapi.Device{}, []pluginapi.Device{}) + capacity, removedResources = testManager.GetCapacity() + as.Equal(2, len(capacity)) + resource2Capacity, ok := capacity[v1.ResourceName(resourceName2)] + as.True(ok) + as.Equal(int64(2), resource2Capacity.Value()) + as.Equal(0, len(removedResources)) + + // Removes resourceName1 endpoint. Verifies testManager.GetCapacity() reports that resourceName1 + // is removed from capacity and it no longer exists in allDevices after the call. + delete(testManager.endpoints, resourceName1) + capacity, removed := testManager.GetCapacity() + as.Equal([]string{resourceName1}, removed) + _, ok = capacity[v1.ResourceName(resourceName1)] + as.False(ok) + val, ok := capacity[v1.ResourceName(resourceName2)] + as.True(ok) + as.Equal(int64(2), val.Value()) + _, ok = testManager.allDevices[resourceName1] + as.False(ok) +} + +type stringPairType struct { + value1 string + value2 string +} + +func constructDevices(devices []string) sets.String { + ret := sets.NewString() + for _, dev := range devices { + ret.Insert(dev) + } + return ret +} + +func constructAllocResp(devices, mounts, envs map[string]string) *pluginapi.AllocateResponse { + resp := &pluginapi.AllocateResponse{} + for k, v := range devices { + resp.Devices = append(resp.Devices, &pluginapi.DeviceSpec{ + HostPath: k, + ContainerPath: v, + Permissions: "mrw", + }) + } + for k, v := range mounts { + resp.Mounts = append(resp.Mounts, &pluginapi.Mount{ + ContainerPath: k, + HostPath: v, + ReadOnly: true, + }) + } + resp.Envs = make(map[string]string) + for k, v := range envs { + resp.Envs[k] = v + } + return resp +} + +func TestCheckpoint(t *testing.T) { + resourceName1 := "domain1.com/resource1" + resourceName2 := "domain2.com/resource2" + + as := assert.New(t) + tmpDir, err := ioutil.TempDir("", "checkpoint") + as.Nil(err) + defer os.RemoveAll(tmpDir) + testManager := &ManagerImpl{ + socketdir: tmpDir, + allDevices: make(map[string]sets.String), + allocatedDevices: make(map[string]sets.String), + podDevices: make(podDevices), + } + testManager.store, _ = utilstore.NewFileStore("/tmp/", utilfs.DefaultFs{}) + + testManager.podDevices.insert("pod1", "con1", resourceName1, + constructDevices([]string{"dev1", "dev2"}), + constructAllocResp(map[string]string{"/dev/r1dev1": "/dev/r1dev1", "/dev/r1dev2": "/dev/r1dev2"}, + map[string]string{"/home/r1lib1": "/usr/r1lib1"}, map[string]string{})) + testManager.podDevices.insert("pod1", "con1", resourceName2, + constructDevices([]string{"dev1", "dev2"}), + constructAllocResp(map[string]string{"/dev/r2dev1": "/dev/r2dev1", "/dev/r2dev2": "/dev/r2dev2"}, + map[string]string{"/home/r2lib1": "/usr/r2lib1"}, + map[string]string{"r2devices": "dev1 dev2"})) + testManager.podDevices.insert("pod1", "con2", resourceName1, + constructDevices([]string{"dev3"}), + constructAllocResp(map[string]string{"/dev/r1dev3": "/dev/r1dev3"}, + map[string]string{"/home/r1lib1": "/usr/r1lib1"}, map[string]string{})) + testManager.podDevices.insert("pod2", "con1", resourceName1, + constructDevices([]string{"dev4"}), + constructAllocResp(map[string]string{"/dev/r1dev4": "/dev/r1dev4"}, + map[string]string{"/home/r1lib1": "/usr/r1lib1"}, map[string]string{})) + + testManager.allDevices[resourceName1] = sets.NewString() + testManager.allDevices[resourceName1].Insert("dev1") + testManager.allDevices[resourceName1].Insert("dev2") + testManager.allDevices[resourceName1].Insert("dev3") + testManager.allDevices[resourceName1].Insert("dev4") + testManager.allDevices[resourceName1].Insert("dev5") + testManager.allDevices[resourceName2] = sets.NewString() + testManager.allDevices[resourceName2].Insert("dev1") + testManager.allDevices[resourceName2].Insert("dev2") + + expectedPodDevices := testManager.podDevices + expectedAllocatedDevices := testManager.podDevices.devices() + expectedAllDevices := testManager.allDevices + + err = testManager.writeCheckpoint() + + as.Nil(err) + testManager.podDevices = make(podDevices) + err = testManager.readCheckpoint() + as.Nil(err) + + as.Equal(len(expectedPodDevices), len(testManager.podDevices)) + for podUID, containerDevices := range expectedPodDevices { + for conName, resources := range containerDevices { + for resource := range resources { + as.True(reflect.DeepEqual( + expectedPodDevices.containerDevices(podUID, conName, resource), + testManager.podDevices.containerDevices(podUID, conName, resource))) + opts1 := expectedPodDevices.deviceRunContainerOptions(podUID, conName) + opts2 := testManager.podDevices.deviceRunContainerOptions(podUID, conName) + as.Equal(len(opts1.Envs), len(opts2.Envs)) + as.Equal(len(opts1.Mounts), len(opts2.Mounts)) + as.Equal(len(opts1.Devices), len(opts2.Devices)) + } + } + } + as.True(reflect.DeepEqual(expectedAllocatedDevices, testManager.allocatedDevices)) + as.True(reflect.DeepEqual(expectedAllDevices, testManager.allDevices)) +} + +type activePodsStub struct { + activePods []*v1.Pod +} + +func (a *activePodsStub) getActivePods() []*v1.Pod { + return a.activePods +} + +func (a *activePodsStub) updateActivePods(newPods []*v1.Pod) { + a.activePods = newPods +} + +type MockEndpoint struct { + allocateFunc func(devs []string) (*pluginapi.AllocateResponse, error) +} + +func (m *MockEndpoint) stop() {} +func (m *MockEndpoint) run() {} + +func (m *MockEndpoint) getDevices() []pluginapi.Device { + return []pluginapi.Device{} +} + +func (m *MockEndpoint) callback(resourceName string, added, updated, deleted []pluginapi.Device) {} + +func (m *MockEndpoint) allocate(devs []string) (*pluginapi.AllocateResponse, error) { + if m.allocateFunc != nil { + return m.allocateFunc(devs) + } + return nil, nil +} + +func makePod(limits v1.ResourceList) *v1.Pod { + return &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + UID: uuid.NewUUID(), + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Limits: limits, + }, + }, + }, + }, + } +} + +func getTestManager(tmpDir string, activePods ActivePodsFunc, testRes []TestResource) *ManagerImpl { + monitorCallback := func(resourceName string, added, updated, deleted []pluginapi.Device) {} + testManager := &ManagerImpl{ + socketdir: tmpDir, + callback: monitorCallback, + allDevices: make(map[string]sets.String), + allocatedDevices: make(map[string]sets.String), + endpoints: make(map[string]endpoint), + podDevices: make(podDevices), + activePods: activePods, + sourcesReady: &sourcesReadyStub{}, + } + testManager.store, _ = utilstore.NewFileStore("/tmp/", utilfs.DefaultFs{}) + for _, res := range testRes { + testManager.allDevices[res.resourceName] = sets.NewString() + for _, dev := range res.devs { + testManager.allDevices[res.resourceName].Insert(dev) + } + if res.resourceName == "domain1.com/resource1" { + testManager.endpoints[res.resourceName] = &MockEndpoint{ + allocateFunc: func(devs []string) (*pluginapi.AllocateResponse, error) { + resp := new(pluginapi.AllocateResponse) + resp.Envs = make(map[string]string) + for _, dev := range devs { + switch dev { + case "dev1": + resp.Devices = append(resp.Devices, &pluginapi.DeviceSpec{ + ContainerPath: "/dev/aaa", + HostPath: "/dev/aaa", + Permissions: "mrw", + }) + + resp.Devices = append(resp.Devices, &pluginapi.DeviceSpec{ + ContainerPath: "/dev/bbb", + HostPath: "/dev/bbb", + Permissions: "mrw", + }) + + resp.Mounts = append(resp.Mounts, &pluginapi.Mount{ + ContainerPath: "/container_dir1/file1", + HostPath: "host_dir1/file1", + ReadOnly: true, + }) + + case "dev2": + resp.Devices = append(resp.Devices, &pluginapi.DeviceSpec{ + ContainerPath: "/dev/ccc", + HostPath: "/dev/ccc", + Permissions: "mrw", + }) + + resp.Mounts = append(resp.Mounts, &pluginapi.Mount{ + ContainerPath: "/container_dir1/file2", + HostPath: "host_dir1/file2", + ReadOnly: true, + }) + + resp.Envs["key1"] = "val1" + } + } + return resp, nil + }, + } + } + if res.resourceName == "domain2.com/resource2" { + testManager.endpoints[res.resourceName] = &MockEndpoint{ + allocateFunc: func(devs []string) (*pluginapi.AllocateResponse, error) { + resp := new(pluginapi.AllocateResponse) + resp.Envs = make(map[string]string) + for _, dev := range devs { + switch dev { + case "dev3": + resp.Envs["key2"] = "val2" + + case "dev4": + resp.Envs["key2"] = "val3" + } + } + return resp, nil + }, + } + } + } + return testManager +} + +func getTestNodeInfo(allocatable v1.ResourceList) *schedulercache.NodeInfo { + cachedNode := &v1.Node{ + Status: v1.NodeStatus{ + Allocatable: allocatable, + }, + } + nodeInfo := &schedulercache.NodeInfo{} + nodeInfo.SetNode(cachedNode) + return nodeInfo +} + +type TestResource struct { + resourceName string + resourceQuantity resource.Quantity + devs []string +} + +func TestPodContainerDeviceAllocation(t *testing.T) { + flag.Set("alsologtostderr", fmt.Sprintf("%t", true)) + var logLevel string + flag.StringVar(&logLevel, "logLevel", "4", "test") + flag.Lookup("v").Value.Set(logLevel) + res1 := TestResource{ + resourceName: "domain1.com/resource1", + resourceQuantity: *resource.NewQuantity(int64(2), resource.DecimalSI), + devs: []string{"dev1", "dev2"}, + } + res2 := TestResource{ + resourceName: "domain2.com/resource2", + resourceQuantity: *resource.NewQuantity(int64(1), resource.DecimalSI), + devs: []string{"dev3", "dev4"}, + } + testResources := make([]TestResource, 2) + testResources = append(testResources, res1) + testResources = append(testResources, res2) + as := require.New(t) + podsStub := activePodsStub{ + activePods: []*v1.Pod{}, + } + tmpDir, err := ioutil.TempDir("", "checkpoint") + as.Nil(err) + defer os.RemoveAll(tmpDir) + nodeInfo := getTestNodeInfo(v1.ResourceList{}) + testManager := getTestManager(tmpDir, podsStub.getActivePods, testResources) + + testPods := []*v1.Pod{ + makePod(v1.ResourceList{ + v1.ResourceName(res1.resourceName): res1.resourceQuantity, + v1.ResourceName("cpu"): res1.resourceQuantity, + v1.ResourceName(res2.resourceName): res2.resourceQuantity}), + makePod(v1.ResourceList{ + v1.ResourceName(res1.resourceName): res2.resourceQuantity}), + makePod(v1.ResourceList{ + v1.ResourceName(res2.resourceName): res2.resourceQuantity}), + } + testCases := []struct { + description string + testPod *v1.Pod + expectedContainerOptsLen []int + expectedAllocatedResName1 int + expectedAllocatedResName2 int + expErr error + }{ + { + description: "Successfull allocation of two Res1 resources and one Res2 resource", + testPod: testPods[0], + expectedContainerOptsLen: []int{3, 2, 2}, + expectedAllocatedResName1: 2, + expectedAllocatedResName2: 1, + expErr: nil, + }, + { + description: "Requesting to create a pod without enough resources should fail", + testPod: testPods[1], + expectedContainerOptsLen: nil, + expectedAllocatedResName1: 2, + expectedAllocatedResName2: 1, + expErr: fmt.Errorf("requested number of devices unavailable for domain1.com/resource1. Requested: 1, Available: 0"), + }, + { + description: "Successfull allocation of all available Res1 resources and Res2 resources", + testPod: testPods[2], + expectedContainerOptsLen: []int{0, 0, 1}, + expectedAllocatedResName1: 2, + expectedAllocatedResName2: 2, + expErr: nil, + }, + } + activePods := []*v1.Pod{} + for _, testCase := range testCases { + pod := testCase.testPod + activePods = append(activePods, pod) + podsStub.updateActivePods(activePods) + err := testManager.Allocate(nodeInfo, &lifecycle.PodAdmitAttributes{Pod: pod}) + if !reflect.DeepEqual(err, testCase.expErr) { + t.Errorf("DevicePluginManager error (%v). expected error: %v but got: %v", + testCase.description, testCase.expErr, err) + } + runContainerOpts := testManager.GetDeviceRunContainerOptions(pod, &pod.Spec.Containers[0]) + 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.Equal(testCase.expectedAllocatedResName1, testManager.allocatedDevices[res1.resourceName].Len()) + as.Equal(testCase.expectedAllocatedResName2, testManager.allocatedDevices[res2.resourceName].Len()) + } + +} + +func TestInitContainerDeviceAllocation(t *testing.T) { + // Requesting to create a pod that requests resourceName1 in init containers and normal containers + // should succeed with devices allocated to init containers reallocated to normal containers. + res1 := TestResource{ + resourceName: "domain1.com/resource1", + resourceQuantity: *resource.NewQuantity(int64(2), resource.DecimalSI), + devs: []string{"dev1", "dev2"}, + } + res2 := TestResource{ + resourceName: "domain2.com/resource2", + resourceQuantity: *resource.NewQuantity(int64(1), resource.DecimalSI), + devs: []string{"dev3", "dev4"}, + } + testResources := make([]TestResource, 2) + testResources = append(testResources, res1) + testResources = append(testResources, res2) + as := require.New(t) + podsStub := activePodsStub{ + activePods: []*v1.Pod{}, + } + nodeInfo := getTestNodeInfo(v1.ResourceList{}) + tmpDir, err := ioutil.TempDir("", "checkpoint") + as.Nil(err) + defer os.RemoveAll(tmpDir) + testManager := getTestManager(tmpDir, podsStub.getActivePods, testResources) + + podWithPluginResourcesInInitContainers := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + UID: uuid.NewUUID(), + }, + Spec: v1.PodSpec{ + InitContainers: []v1.Container{ + { + Name: string(uuid.NewUUID()), + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceName(res1.resourceName): res2.resourceQuantity, + }, + }, + }, + { + Name: string(uuid.NewUUID()), + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceName(res1.resourceName): res1.resourceQuantity, + }, + }, + }, + }, + Containers: []v1.Container{ + { + Name: string(uuid.NewUUID()), + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceName(res1.resourceName): res2.resourceQuantity, + v1.ResourceName(res2.resourceName): res2.resourceQuantity, + }, + }, + }, + { + Name: string(uuid.NewUUID()), + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceName(res1.resourceName): res2.resourceQuantity, + v1.ResourceName(res2.resourceName): res2.resourceQuantity, + }, + }, + }, + }, + }, + } + podsStub.updateActivePods([]*v1.Pod{podWithPluginResourcesInInitContainers}) + err = testManager.Allocate(nodeInfo, &lifecycle.PodAdmitAttributes{Pod: podWithPluginResourcesInInitContainers}) + as.Nil(err) + podUID := string(podWithPluginResourcesInInitContainers.UID) + initCont1 := podWithPluginResourcesInInitContainers.Spec.InitContainers[0].Name + initCont2 := podWithPluginResourcesInInitContainers.Spec.InitContainers[1].Name + normalCont1 := podWithPluginResourcesInInitContainers.Spec.Containers[0].Name + normalCont2 := podWithPluginResourcesInInitContainers.Spec.Containers[1].Name + initCont1Devices := testManager.podDevices.containerDevices(podUID, initCont1, res1.resourceName) + initCont2Devices := testManager.podDevices.containerDevices(podUID, initCont2, res1.resourceName) + normalCont1Devices := testManager.podDevices.containerDevices(podUID, normalCont1, res1.resourceName) + normalCont2Devices := testManager.podDevices.containerDevices(podUID, normalCont2, res1.resourceName) + as.Equal(1, initCont1Devices.Len()) + as.Equal(2, initCont2Devices.Len()) + as.Equal(1, normalCont1Devices.Len()) + as.Equal(1, normalCont2Devices.Len()) + as.True(initCont2Devices.IsSuperset(initCont1Devices)) + as.True(initCont2Devices.IsSuperset(normalCont1Devices)) + as.True(initCont2Devices.IsSuperset(normalCont2Devices)) + as.Equal(0, normalCont1Devices.Intersection(normalCont2Devices).Len()) +} + +func TestSanitizeNodeAllocatable(t *testing.T) { + resourceName1 := "domain1.com/resource1" + devID1 := "dev1" + + resourceName2 := "domain2.com/resource2" + devID2 := "dev2" + + as := assert.New(t) + monitorCallback := func(resourceName string, added, updated, deleted []pluginapi.Device) {} + + testManager := &ManagerImpl{ + callback: monitorCallback, + allDevices: make(map[string]sets.String), + allocatedDevices: make(map[string]sets.String), + podDevices: make(podDevices), + } + testManager.store, _ = utilstore.NewFileStore("/tmp/", utilfs.DefaultFs{}) + // require one of resource1 and one of resource2 + testManager.allocatedDevices[resourceName1] = sets.NewString() + testManager.allocatedDevices[resourceName1].Insert(devID1) + testManager.allocatedDevices[resourceName2] = sets.NewString() + testManager.allocatedDevices[resourceName2].Insert(devID2) + + cachedNode := &v1.Node{ + Status: v1.NodeStatus{ + Allocatable: v1.ResourceList{ + // has no resource1 and two of resource2 + v1.ResourceName(resourceName2): *resource.NewQuantity(int64(2), resource.DecimalSI), + }, + }, + } + nodeInfo := &schedulercache.NodeInfo{} + nodeInfo.SetNode(cachedNode) + + testManager.sanitizeNodeAllocatable(nodeInfo) + + allocatableScalarResources := nodeInfo.AllocatableResource().ScalarResources + // allocatable in nodeInfo is less than needed, should update + as.Equal(1, int(allocatableScalarResources[v1.ResourceName(resourceName1)])) + // allocatable in nodeInfo is more than needed, should skip updating + as.Equal(2, int(allocatableScalarResources[v1.ResourceName(resourceName2)])) +} diff --git a/pkg/kubelet/cm/deviceplugin/pod_devices.go b/pkg/kubelet/cm/deviceplugin/pod_devices.go new file mode 100644 index 00000000000..311c8d0c60f --- /dev/null +++ b/pkg/kubelet/cm/deviceplugin/pod_devices.go @@ -0,0 +1,257 @@ +/* +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 deviceplugin + +import ( + "github.com/golang/glog" + + "k8s.io/apimachinery/pkg/util/sets" + pluginapi "k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1alpha" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" +) + +type deviceAllocateInfo struct { + // deviceIds contains device Ids allocated to this container for the given resourceName. + deviceIds sets.String + // allocResp contains cached rpc AllocateResponse. + allocResp *pluginapi.AllocateResponse +} + +type resourceAllocateInfo map[string]deviceAllocateInfo // Keyed by resourceName. +type containerDevices map[string]resourceAllocateInfo // Keyed by containerName. +type podDevices map[string]containerDevices // Keyed by podUID. + +func (pdev podDevices) pods() sets.String { + ret := sets.NewString() + for k := range pdev { + ret.Insert(k) + } + return ret +} + +func (pdev podDevices) insert(podUID, contName, resource string, devices sets.String, resp *pluginapi.AllocateResponse) { + if _, podExists := pdev[podUID]; !podExists { + pdev[podUID] = make(containerDevices) + } + if _, contExists := pdev[podUID][contName]; !contExists { + pdev[podUID][contName] = make(resourceAllocateInfo) + } + pdev[podUID][contName][resource] = deviceAllocateInfo{ + deviceIds: devices, + allocResp: resp, + } +} + +func (pdev podDevices) delete(pods []string) { + for _, uid := range pods { + delete(pdev, uid) + } +} + +// Returns list of device Ids allocated to the given container for the given resource. +// Returns nil if we don't have cached state for the given . +func (pdev podDevices) containerDevices(podUID, contName, resource string) sets.String { + if _, podExists := pdev[podUID]; !podExists { + return nil + } + if _, contExists := pdev[podUID][contName]; !contExists { + return nil + } + devs, resourceExists := pdev[podUID][contName][resource] + if !resourceExists { + return nil + } + return devs.deviceIds +} + +// Populates allocatedResources with the device resources allocated to the specified . +func (pdev podDevices) addContainerAllocatedResources(podUID, contName string, allocatedResources map[string]sets.String) { + containers, exists := pdev[podUID] + if !exists { + return + } + resources, exists := containers[contName] + if !exists { + return + } + for resource, devices := range resources { + allocatedResources[resource] = allocatedResources[resource].Union(devices.deviceIds) + } +} + +// Removes the device resources allocated to the specified from allocatedResources. +func (pdev podDevices) removeContainerAllocatedResources(podUID, contName string, allocatedResources map[string]sets.String) { + containers, exists := pdev[podUID] + if !exists { + return + } + resources, exists := containers[contName] + if !exists { + return + } + for resource, devices := range resources { + allocatedResources[resource] = allocatedResources[resource].Difference(devices.deviceIds) + } +} + +// Returns all of devices allocated to the pods being tracked, keyed by resourceName. +func (pdev podDevices) devices() map[string]sets.String { + ret := make(map[string]sets.String) + for _, containerDevices := range pdev { + for _, resources := range containerDevices { + for resource, devices := range resources { + if _, exists := ret[resource]; !exists { + ret[resource] = sets.NewString() + } + ret[resource] = ret[resource].Union(devices.deviceIds) + } + } + } + return ret +} + +// podDevicesCheckpointEntry is used to record to device allocation information. +type podDevicesCheckpointEntry struct { + PodUID string + ContainerName string + ResourceName string + DeviceIDs []string + AllocResp []byte +} + +// Turns podDevices to checkpointData. +func (pdev podDevices) toCheckpointData() []podDevicesCheckpointEntry { + var data []podDevicesCheckpointEntry + for podUID, containerDevices := range pdev { + for conName, resources := range containerDevices { + for resource, devices := range resources { + devIds := devices.deviceIds.UnsortedList() + if devices.allocResp == nil { + glog.Errorf("Can't marshal allocResp for %v %v %v: allocation response is missing", podUID, conName, resource) + continue + } + + allocResp, err := devices.allocResp.Marshal() + if err != nil { + glog.Errorf("Can't marshal allocResp for %v %v %v: %v", podUID, conName, resource, err) + continue + } + data = append(data, podDevicesCheckpointEntry{podUID, conName, resource, devIds, allocResp}) + } + } + } + return data +} + +// Populates podDevices from the passed in checkpointData. +func (pdev podDevices) fromCheckpointData(data []podDevicesCheckpointEntry) { + for _, entry := range data { + glog.V(2).Infof("Get checkpoint entry: %v %v %v %v %v\n", + entry.PodUID, entry.ContainerName, entry.ResourceName, entry.DeviceIDs, entry.AllocResp) + devIDs := sets.NewString() + for _, devID := range entry.DeviceIDs { + devIDs.Insert(devID) + } + allocResp := &pluginapi.AllocateResponse{} + err := allocResp.Unmarshal(entry.AllocResp) + if err != nil { + glog.Errorf("Can't unmarshal allocResp for %v %v %v: %v", entry.PodUID, entry.ContainerName, entry.ResourceName, err) + continue + } + pdev.insert(entry.PodUID, entry.ContainerName, entry.ResourceName, devIDs, allocResp) + } +} + +// Returns combined container runtime settings to consume the container's allocated devices. +func (pdev podDevices) deviceRunContainerOptions(podUID, contName string) *DeviceRunContainerOptions { + containers, exists := pdev[podUID] + if !exists { + return nil + } + resources, exists := containers[contName] + if !exists { + return nil + } + opts := &DeviceRunContainerOptions{} + // Maps to detect duplicate settings. + devsMap := make(map[string]string) + mountsMap := make(map[string]string) + envsMap := make(map[string]string) + // Loops through AllocationResponses of all cached device resources. + for _, devices := range resources { + resp := devices.allocResp + // Each Allocate response has the following artifacts. + // Environment variables + // Mount points + // Device files + // These artifacts are per resource per container. + // Updates RunContainerOptions.Envs. + for k, v := range resp.Envs { + if e, ok := envsMap[k]; ok { + glog.V(3).Infof("skip existing env %s %s", k, v) + if e != v { + glog.Errorf("Environment variable %s has conflicting setting: %s and %s", k, e, v) + } + continue + } + glog.V(4).Infof("add env %s %s", k, v) + envsMap[k] = v + opts.Envs = append(opts.Envs, kubecontainer.EnvVar{Name: k, Value: v}) + } + + // Updates RunContainerOptions.Devices. + for _, dev := range resp.Devices { + if d, ok := devsMap[dev.ContainerPath]; ok { + glog.V(3).Infof("skip existing device %s %s", dev.ContainerPath, dev.HostPath) + if d != dev.HostPath { + glog.Errorf("Container device %s has conflicting mapping host devices: %s and %s", + dev.ContainerPath, d, dev.HostPath) + } + continue + } + glog.V(4).Infof("add device %s %s", dev.ContainerPath, dev.HostPath) + devsMap[dev.ContainerPath] = dev.HostPath + opts.Devices = append(opts.Devices, kubecontainer.DeviceInfo{ + PathOnHost: dev.HostPath, + PathInContainer: dev.ContainerPath, + Permissions: dev.Permissions, + }) + } + // Updates RunContainerOptions.Mounts. + for _, mount := range resp.Mounts { + if m, ok := mountsMap[mount.ContainerPath]; ok { + glog.V(3).Infof("skip existing mount %s %s", mount.ContainerPath, mount.HostPath) + if m != mount.HostPath { + glog.Errorf("Container mount %s has conflicting mapping host mounts: %s and %s", + mount.ContainerPath, m, mount.HostPath) + } + continue + } + glog.V(4).Infof("add mount %s %s", mount.ContainerPath, mount.HostPath) + mountsMap[mount.ContainerPath] = mount.HostPath + opts.Mounts = append(opts.Mounts, kubecontainer.Mount{ + Name: mount.ContainerPath, + ContainerPath: mount.ContainerPath, + HostPath: mount.HostPath, + ReadOnly: mount.ReadOnly, + // TODO: This may need to be part of Device plugin API. + SELinuxRelabel: false, + }) + } + } + return opts +} diff --git a/pkg/kubelet/cm/deviceplugin/types.go b/pkg/kubelet/cm/deviceplugin/types.go new file mode 100644 index 00000000000..3c6b30206f1 --- /dev/null +++ b/pkg/kubelet/cm/deviceplugin/types.go @@ -0,0 +1,96 @@ +/* +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 deviceplugin + +import ( + "k8s.io/api/core/v1" + pluginapi "k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1alpha" + "k8s.io/kubernetes/pkg/kubelet/config" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/lifecycle" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" +) + +// Manager manages all the Device Plugins running on a node. +type Manager interface { + // Start starts device plugin registration service. + Start(activePods ActivePodsFunc, sourcesReady config.SourcesReady) error + + // Devices is the map of devices that have registered themselves + // against the manager. + // The map key is the ResourceName of the device plugins. + Devices() map[string][]pluginapi.Device + + // Allocate configures and assigns devices to pods. The pods are provided + // through the pod admission attributes in the attrs argument. From the + // requested device resources, Allocate will communicate with the owning + // device plugin to allow setup procedures to take place, and for the + // device plugin to provide runtime settings to use the device (environment + // variables, mount points and device files). The node object is provided + // for the device manager to update the node capacity to reflect the + // currently available devices. + Allocate(node *schedulercache.NodeInfo, attrs *lifecycle.PodAdmitAttributes) error + + // Stop stops the manager. + Stop() error + + // GetDeviceRunContainerOptions checks whether we have cached containerDevices + // for the passed-in and returns its DeviceRunContainerOptions + // for the found one. An empty struct is returned in case no cached state is found. + GetDeviceRunContainerOptions(pod *v1.Pod, container *v1.Container) *DeviceRunContainerOptions + + // GetCapacity returns the amount of available device plugin resource capacity + // and inactive device plugin resources previously registered on the node. + GetCapacity() (v1.ResourceList, []string) +} + +// DeviceRunContainerOptions contains the combined container runtime settings to consume its allocated devices. +type DeviceRunContainerOptions struct { + // The environment variables list. + Envs []kubecontainer.EnvVar + // The mounts for the container. + Mounts []kubecontainer.Mount + // The host devices mapped into the container. + Devices []kubecontainer.DeviceInfo +} + +// TODO: evaluate whether we need these error definitions. +const ( + // errFailedToDialDevicePlugin is the error raised when the device plugin could not be + // reached on the registered socket + errFailedToDialDevicePlugin = "failed to dial device plugin:" + // errUnsuportedVersion is the error raised when the device plugin uses an API version not + // supported by the Kubelet registry + errUnsuportedVersion = "requested API version %q is not supported by kubelet. Supported version is %q" + // errDevicePluginAlreadyExists is the error raised when a device plugin with the + // same Resource Name tries to register itself + errDevicePluginAlreadyExists = "another device plugin already registered this Resource Name" + // errInvalidResourceName is the error raised when a device plugin is registering + // itself with an invalid ResourceName + errInvalidResourceName = "the ResourceName %q is invalid" + // errEmptyResourceName is the error raised when the resource name field is empty + errEmptyResourceName = "invalid Empty ResourceName" + + // errBadSocket is the error raised when the registry socket path is not absolute + errBadSocket = "bad socketPath, must be an absolute path:" + // errRemoveSocket is the error raised when the registry could not remove the existing socket + errRemoveSocket = "failed to remove socket while starting device plugin registry, with error" + // errListenSocket is the error raised when the registry could not listen on the socket + errListenSocket = "failed to listen to socket while starting device plugin registry, with error" + // errListAndWatch is the error raised when ListAndWatch ended unsuccessfully + errListAndWatch = "listAndWatch ended unexpectedly for device plugin %s with error %v" +) diff --git a/pkg/kubelet/cm/helpers_linux.go b/pkg/kubelet/cm/helpers_linux.go index 6525bb98439..935fb6c8060 100644 --- a/pkg/kubelet/cm/helpers_linux.go +++ b/pkg/kubelet/cm/helpers_linux.go @@ -26,9 +26,10 @@ import ( libcontainercgroups "github.com/opencontainers/runc/libcontainer/cgroups" "k8s.io/api/core/v1" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" - v1qos "k8s.io/kubernetes/pkg/api/v1/helper/qos" + "k8s.io/apimachinery/pkg/types" "k8s.io/kubernetes/pkg/api/v1/resource" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" + v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos" ) const ( @@ -222,3 +223,8 @@ func getCgroupProcs(dir string) ([]int, error) { } return out, nil } + +// GetPodCgroupNameSuffix returns the last element of the pod CgroupName identifier +func GetPodCgroupNameSuffix(podUID types.UID) string { + return podCgroupNamePrefix + string(podUID) +} diff --git a/pkg/kubelet/cm/helpers_unsupported.go b/pkg/kubelet/cm/helpers_unsupported.go index 3b88c7360c6..b572f3456f3 100644 --- a/pkg/kubelet/cm/helpers_unsupported.go +++ b/pkg/kubelet/cm/helpers_unsupported.go @@ -18,7 +18,10 @@ limitations under the License. package cm -import "k8s.io/api/core/v1" +import ( + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" +) const ( MinShares = 0 @@ -52,3 +55,8 @@ func GetCgroupSubsystems() (*CgroupSubsystems, error) { func getCgroupProcs(dir string) ([]int, error) { return nil, nil } + +// GetPodCgroupNameSuffix returns the last element of the pod CgroupName identifier +func GetPodCgroupNameSuffix(podUID types.UID) string { + return "" +} diff --git a/pkg/kubelet/cm/node_container_manager.go b/pkg/kubelet/cm/node_container_manager.go index 66e0d82467e..04e5acdd1a5 100644 --- a/pkg/kubelet/cm/node_container_manager.go +++ b/pkg/kubelet/cm/node_container_manager.go @@ -32,6 +32,7 @@ import ( kubefeatures "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/events" evictionapi "k8s.io/kubernetes/pkg/kubelet/eviction/api" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" ) const ( @@ -62,7 +63,7 @@ func (cm *containerManagerImpl) enforceNodeAllocatableCgroups() error { // default cpu shares on cgroups are low and can cause cpu starvation. nodeAllocatable := cm.capacity // Use Node Allocatable limits instead of capacity if the user requested enforcing node allocatable. - if cm.CgroupsPerQOS && nc.EnforceNodeAllocatable.Has(NodeAllocatableEnforcementKey) { + if cm.CgroupsPerQOS && nc.EnforceNodeAllocatable.Has(kubetypes.NodeAllocatableEnforcementKey) { nodeAllocatable = cm.getNodeAllocatableAbsolute() } @@ -101,7 +102,7 @@ func (cm *containerManagerImpl) enforceNodeAllocatableCgroups() error { }() } // Now apply kube reserved and system reserved limits if required. - if nc.EnforceNodeAllocatable.Has(SystemReservedEnforcementKey) { + if nc.EnforceNodeAllocatable.Has(kubetypes.SystemReservedEnforcementKey) { glog.V(2).Infof("Enforcing System reserved on cgroup %q with limits: %+v", nc.SystemReservedCgroupName, nc.SystemReserved) if err := enforceExistingCgroup(cm.cgroupManager, nc.SystemReservedCgroupName, nc.SystemReserved); err != nil { message := fmt.Sprintf("Failed to enforce System Reserved Cgroup Limits on %q: %v", nc.SystemReservedCgroupName, err) @@ -110,7 +111,7 @@ func (cm *containerManagerImpl) enforceNodeAllocatableCgroups() error { } cm.recorder.Eventf(nodeRef, v1.EventTypeNormal, events.SuccessfulNodeAllocatableEnforcement, "Updated limits on system reserved cgroup %v", nc.SystemReservedCgroupName) } - if nc.EnforceNodeAllocatable.Has(KubeReservedEnforcementKey) { + if nc.EnforceNodeAllocatable.Has(kubetypes.KubeReservedEnforcementKey) { glog.V(2).Infof("Enforcing kube reserved on cgroup %q with limits: %+v", nc.KubeReservedCgroupName, nc.KubeReserved) if err := enforceExistingCgroup(cm.cgroupManager, nc.KubeReservedCgroupName, nc.KubeReserved); err != nil { message := fmt.Sprintf("Failed to enforce Kube Reserved Cgroup Limits on %q: %v", nc.KubeReservedCgroupName, err) diff --git a/pkg/kubelet/cm/pod_container_manager_linux.go b/pkg/kubelet/cm/pod_container_manager_linux.go index 7180a73c49c..e62d192891d 100644 --- a/pkg/kubelet/cm/pod_container_manager_linux.go +++ b/pkg/kubelet/cm/pod_container_manager_linux.go @@ -27,7 +27,7 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" utilerrors "k8s.io/apimachinery/pkg/util/errors" - v1qos "k8s.io/kubernetes/pkg/api/v1/helper/qos" + v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos" ) const ( @@ -104,7 +104,7 @@ func (m *podContainerManagerImpl) GetPodContainerName(pod *v1.Pod) (CgroupName, case v1.PodQOSBestEffort: parentContainer = m.qosContainersInfo.BestEffort } - podContainer := podCgroupNamePrefix + string(pod.UID) + podContainer := GetPodCgroupNameSuffix(pod.UID) // Get the absolute path of the cgroup cgroupName := (CgroupName)(path.Join(parentContainer, podContainer)) diff --git a/pkg/kubelet/cm/pod_container_manager_unsupported.go b/pkg/kubelet/cm/pod_container_manager_unsupported.go index e69542b1823..d62eb7fa4e8 100644 --- a/pkg/kubelet/cm/pod_container_manager_unsupported.go +++ b/pkg/kubelet/cm/pod_container_manager_unsupported.go @@ -18,36 +18,8 @@ limitations under the License. package cm -import ( - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" -) - type unsupportedPodContainerManager struct { + podContainerManagerStub } var _ PodContainerManager = &unsupportedPodContainerManager{} - -func (m *unsupportedPodContainerManager) Exists(_ *v1.Pod) bool { - return true -} - -func (m *unsupportedPodContainerManager) EnsureExists(_ *v1.Pod) error { - return nil -} - -func (m *unsupportedPodContainerManager) GetPodContainerName(_ *v1.Pod) (CgroupName, string) { - return "", "" -} - -func (m *unsupportedPodContainerManager) ReduceCPULimits(_ CgroupName) error { - return nil -} - -func (m *unsupportedPodContainerManager) GetAllPodsFromCgroups() (map[types.UID]CgroupName, error) { - return nil, nil -} - -func (m *unsupportedPodContainerManager) Destroy(name CgroupName) error { - return nil -} diff --git a/pkg/kubelet/cm/qos_container_manager_linux.go b/pkg/kubelet/cm/qos_container_manager_linux.go index 12bf9a2d8d8..0cd8559998e 100644 --- a/pkg/kubelet/cm/qos_container_manager_linux.go +++ b/pkg/kubelet/cm/qos_container_manager_linux.go @@ -31,8 +31,8 @@ import ( cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs" "k8s.io/api/core/v1" utilfeature "k8s.io/apiserver/pkg/util/feature" - v1qos "k8s.io/kubernetes/pkg/api/v1/helper/qos" "k8s.io/kubernetes/pkg/api/v1/resource" + v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos" kubefeatures "k8s.io/kubernetes/pkg/features" ) @@ -56,21 +56,21 @@ type qosContainerManagerImpl struct { cgroupManager CgroupManager activePods ActivePodsFunc getNodeAllocatable func() v1.ResourceList - cgroupRoot string + cgroupRoot CgroupName qosReserved map[v1.ResourceName]int64 } -func NewQOSContainerManager(subsystems *CgroupSubsystems, cgroupRoot string, nodeConfig NodeConfig) (QOSContainerManager, error) { +func NewQOSContainerManager(subsystems *CgroupSubsystems, cgroupRoot string, nodeConfig NodeConfig, cgroupManager CgroupManager) (QOSContainerManager, error) { if !nodeConfig.CgroupsPerQOS { return &qosContainerManagerNoop{ - cgroupRoot: CgroupName(nodeConfig.CgroupRoot), + cgroupRoot: CgroupName(cgroupRoot), }, nil } return &qosContainerManagerImpl{ subsystems: subsystems, - cgroupManager: NewCgroupManager(subsystems, nodeConfig.CgroupDriver), - cgroupRoot: cgroupRoot, + cgroupManager: cgroupManager, + cgroupRoot: CgroupName(cgroupRoot), qosReserved: nodeConfig.ExperimentalQOSReserved, }, nil } @@ -81,7 +81,7 @@ func (m *qosContainerManagerImpl) GetQOSContainersInfo() QOSContainersInfo { func (m *qosContainerManagerImpl) Start(getNodeAllocatable func() v1.ResourceList, activePods ActivePodsFunc) error { cm := m.cgroupManager - rootContainer := m.cgroupRoot + rootContainer := string(m.cgroupRoot) if !cm.Exists(CgroupName(rootContainer)) { return fmt.Errorf("root container %s doesn't exist", rootContainer) } diff --git a/pkg/kubelet/cm/util/BUILD b/pkg/kubelet/cm/util/BUILD index 9de2c5ba011..e344dc6e66e 100644 --- a/pkg/kubelet/cm/util/BUILD +++ b/pkg/kubelet/cm/util/BUILD @@ -7,17 +7,45 @@ load( go_library( name = "go_default_library", - srcs = [ - "cgroups_unsupported.go", - ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + srcs = select({ + "@io_bazel_rules_go//go/platform:android": [ + "cgroups_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "cgroups_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "cgroups_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "cgroups_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ "cgroups_linux.go", ], + "@io_bazel_rules_go//go/platform:nacl": [ + "cgroups_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "cgroups_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "cgroups_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "cgroups_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "cgroups_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "cgroups_unsupported.go", + ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/pkg/kubelet/cm/util", deps = select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//vendor/github.com/opencontainers/runc/libcontainer/cgroups:go_default_library", "//vendor/github.com/opencontainers/runc/libcontainer/utils:go_default_library", ], diff --git a/pkg/kubelet/config/BUILD b/pkg/kubelet/config/BUILD index c11924e0b3a..f7023cb08c6 100644 --- a/pkg/kubelet/config/BUILD +++ b/pkg/kubelet/config/BUILD @@ -1,10 +1,4 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", @@ -15,23 +9,55 @@ go_library( "defaults.go", "doc.go", "file.go", - "file_unsupported.go", "flags.go", "http.go", "sources.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "file_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "file_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "file_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "file_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ "file_linux.go", ], + "@io_bazel_rules_go//go/platform:nacl": [ + "file_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "file_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "file_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "file_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "file_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "file_unsupported.go", + ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/pkg/kubelet/config", + visibility = ["//visibility:public"], deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", - "//pkg/api/install:go_default_library", - "//pkg/api/v1:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", + "//pkg/apis/core/install:go_default_library", + "//pkg/apis/core/v1:go_default_library", + "//pkg/apis/core/validation:go_default_library", + "//pkg/kubelet/checkpoint:go_default_library", "//pkg/kubelet/container:go_default_library", "//pkg/kubelet/events:go_default_library", "//pkg/kubelet/types:go_default_library", @@ -46,14 +72,13 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", "//vendor/k8s.io/client-go/tools/record:go_default_library", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//vendor/golang.org/x/exp/inotify:go_default_library", ], "//conditions:default": [], @@ -68,18 +93,19 @@ go_test( "config_test.go", "http_test.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "file_linux_test.go", ], "//conditions:default": [], }), + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/config", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/testapi:go_default_library", - "//pkg/api/v1:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/v1:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//pkg/kubelet/types:go_default_library", "//pkg/securitycontext:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", @@ -93,7 +119,7 @@ go_test( "//vendor/k8s.io/client-go/tools/record:go_default_library", "//vendor/k8s.io/client-go/util/testing:go_default_library", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", ], "//conditions:default": [], @@ -111,4 +137,5 @@ filegroup( name = "all-srcs", srcs = [":package-srcs"], tags = ["automanaged"], + visibility = ["//visibility:public"], ) diff --git a/pkg/kubelet/config/apiserver.go b/pkg/kubelet/config/apiserver.go index 016c33bf720..a8afc6e345c 100644 --- a/pkg/kubelet/config/apiserver.go +++ b/pkg/kubelet/config/apiserver.go @@ -25,13 +25,13 @@ import ( "k8s.io/apimachinery/pkg/util/wait" clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" kubetypes "k8s.io/kubernetes/pkg/kubelet/types" ) // NewSourceApiserver creates a config source that watches and pulls from the apiserver. func NewSourceApiserver(c clientset.Interface, nodeName types.NodeName, updates chan<- interface{}) { - lw := cache.NewListWatchFromClient(c.Core().RESTClient(), "pods", metav1.NamespaceAll, fields.OneTermEqualSelector(api.PodHostField, string(nodeName))) + lw := cache.NewListWatchFromClient(c.CoreV1().RESTClient(), "pods", metav1.NamespaceAll, fields.OneTermEqualSelector(api.PodHostField, string(nodeName))) newSourceApiserverFromLW(lw, updates) } diff --git a/pkg/kubelet/config/common.go b/pkg/kubelet/config/common.go index 8203efa57ce..02a4f476126 100644 --- a/pkg/kubelet/config/common.go +++ b/pkg/kubelet/config/common.go @@ -27,14 +27,15 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" utilyaml "k8s.io/apimachinery/pkg/util/yaml" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" // TODO: remove this import if // api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String() is changed // to "v1"? - _ "k8s.io/kubernetes/pkg/api/install" - k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" - "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/api/legacyscheme" + _ "k8s.io/kubernetes/pkg/apis/core/install" + k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" + "k8s.io/kubernetes/pkg/apis/core/validation" kubetypes "k8s.io/kubernetes/pkg/kubelet/types" "k8s.io/kubernetes/pkg/util/hash" @@ -98,7 +99,7 @@ func getSelfLink(name, namespace string) string { if len(namespace) == 0 { namespace = metav1.NamespaceDefault } - selfLink = fmt.Sprintf("/api/"+api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version+"/namespaces/%s/pods/%s", namespace, name) + selfLink = fmt.Sprintf("/api/"+legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.Version+"/namespaces/%s/pods/%s", namespace, name) return selfLink } @@ -110,7 +111,7 @@ func tryDecodeSinglePod(data []byte, defaultFn defaultFunc) (parsed bool, pod *v if err != nil { return false, nil, err } - obj, err := runtime.Decode(api.Codecs.UniversalDecoder(), json) + obj, err := runtime.Decode(legacyscheme.Codecs.UniversalDecoder(), json) if err != nil { return false, pod, err } @@ -118,8 +119,7 @@ func tryDecodeSinglePod(data []byte, defaultFn defaultFunc) (parsed bool, pod *v newPod, ok := obj.(*api.Pod) // Check whether the object could be converted to single pod. if !ok { - err = fmt.Errorf("invalid pod: %#v", obj) - return false, pod, err + return false, pod, fmt.Errorf("invalid pod: %#v", obj) } // Apply default values and validate the pod. @@ -127,18 +127,18 @@ func tryDecodeSinglePod(data []byte, defaultFn defaultFunc) (parsed bool, pod *v return true, pod, err } if errs := validation.ValidatePod(newPod); len(errs) > 0 { - err = fmt.Errorf("invalid pod: %v", errs) - return true, pod, err + return true, pod, fmt.Errorf("invalid pod: %v", errs) } v1Pod := &v1.Pod{} - if err := k8s_api_v1.Convert_api_Pod_To_v1_Pod(newPod, v1Pod, nil); err != nil { + if err := k8s_api_v1.Convert_core_Pod_To_v1_Pod(newPod, v1Pod, nil); err != nil { + glog.Errorf("Pod %q failed to convert to v1", newPod.Name) return true, nil, err } return true, v1Pod, nil } func tryDecodePodList(data []byte, defaultFn defaultFunc) (parsed bool, pods v1.PodList, err error) { - obj, err := runtime.Decode(api.Codecs.UniversalDecoder(), data) + obj, err := runtime.Decode(legacyscheme.Codecs.UniversalDecoder(), data) if err != nil { return false, pods, err } @@ -162,7 +162,7 @@ func tryDecodePodList(data []byte, defaultFn defaultFunc) (parsed bool, pods v1. } } v1Pods := &v1.PodList{} - if err := k8s_api_v1.Convert_api_PodList_To_v1_PodList(newPods, v1Pods, nil); err != nil { + if err := k8s_api_v1.Convert_core_PodList_To_v1_PodList(newPods, v1Pods, nil); err != nil { return true, pods, err } return true, *v1Pods, err diff --git a/pkg/kubelet/config/common_test.go b/pkg/kubelet/config/common_test.go index 8f2dfbdaf71..9dd694669f1 100644 --- a/pkg/kubelet/config/common_test.go +++ b/pkg/kubelet/config/common_test.go @@ -23,8 +23,9 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/testapi" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/securitycontext" ) @@ -72,9 +73,9 @@ func TestDecodeSinglePod(t *testing.T) { t.Errorf("expected:\n%#v\ngot:\n%#v\n%s", pod, podOut, string(json)) } - for _, gv := range api.Registry.EnabledVersionsForGroup(v1.GroupName) { - info, _ := runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), "application/yaml") - encoder := api.Codecs.EncoderForVersion(info.Serializer, gv) + for _, gv := range legacyscheme.Registry.EnabledVersionsForGroup(v1.GroupName) { + info, _ := runtime.SerializerInfoForMediaType(legacyscheme.Codecs.SupportedMediaTypes(), "application/yaml") + encoder := legacyscheme.Codecs.EncoderForVersion(info.Serializer, gv) yaml, err := runtime.Encode(encoder, pod) if err != nil { t.Errorf("unexpected error: %v", err) @@ -138,9 +139,9 @@ func TestDecodePodList(t *testing.T) { t.Errorf("expected:\n%#v\ngot:\n%#v\n%s", podList, &podListOut, string(json)) } - for _, gv := range api.Registry.EnabledVersionsForGroup(v1.GroupName) { - info, _ := runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), "application/yaml") - encoder := api.Codecs.EncoderForVersion(info.Serializer, gv) + for _, gv := range legacyscheme.Registry.EnabledVersionsForGroup(v1.GroupName) { + info, _ := runtime.SerializerInfoForMediaType(legacyscheme.Codecs.SupportedMediaTypes(), "application/yaml") + encoder := legacyscheme.Codecs.EncoderForVersion(info.Serializer, gv) yaml, err := runtime.Encode(encoder, podList) if err != nil { t.Errorf("unexpected error: %v", err) diff --git a/pkg/kubelet/config/config.go b/pkg/kubelet/config/config.go index ed7869dfb01..d131c6ce395 100644 --- a/pkg/kubelet/config/config.go +++ b/pkg/kubelet/config/config.go @@ -25,11 +25,8 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/client-go/tools/record" - "k8s.io/kubernetes/pkg/api" - k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" - "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/kubelet/checkpoint" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/kubelet/events" kubetypes "k8s.io/kubernetes/pkg/kubelet/types" @@ -65,8 +62,9 @@ type PodConfig struct { updates chan kubetypes.PodUpdate // contains the list of all configured sources - sourcesLock sync.Mutex - sources sets.String + sourcesLock sync.Mutex + sources sets.String + checkpointManager checkpoint.Manager } // NewPodConfig creates an object that can merge many configuration sources into a stream @@ -112,6 +110,19 @@ func (c *PodConfig) Sync() { c.pods.Sync() } +// Restore restores pods from the checkpoint path, *once* +func (c *PodConfig) Restore(path string, updates chan<- interface{}) error { + var err error + if c.checkpointManager == nil { + c.checkpointManager = checkpoint.NewCheckpointManager(path) + pods, err := c.checkpointManager.LoadPods() + if err == nil { + updates <- kubetypes.PodUpdate{Pods: pods, Op: kubetypes.RESTORE, Source: kubetypes.ApiserverSource} + } + } + return err +} + // 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 @@ -156,7 +167,7 @@ func (s *podStorage) Merge(source string, change interface{}) error { defer s.updateLock.Unlock() seenBefore := s.sourcesSeen.Has(source) - adds, updates, deletes, removes, reconciles := s.merge(source, change) + adds, updates, deletes, removes, reconciles, restores := s.merge(source, change) firstSet := !seenBefore && s.sourcesSeen.Has(source) // deliver update notifications @@ -174,6 +185,9 @@ func (s *podStorage) Merge(source string, change interface{}) error { if len(deletes.Pods) > 0 { s.updates <- *deletes } + if len(restores.Pods) > 0 { + s.updates <- *restores + } 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 @@ -210,7 +224,7 @@ func (s *podStorage) Merge(source string, change interface{}) error { return nil } -func (s *podStorage) merge(source string, change interface{}) (adds, updates, deletes, removes, reconciles *kubetypes.PodUpdate) { +func (s *podStorage) merge(source string, change interface{}) (adds, updates, deletes, removes, reconciles, restores *kubetypes.PodUpdate) { s.podLock.Lock() defer s.podLock.Unlock() @@ -219,6 +233,7 @@ func (s *podStorage) merge(source string, change interface{}) (adds, updates, de deletePods := []*v1.Pod{} removePods := []*v1.Pod{} reconcilePods := []*v1.Pod{} + restorePods := []*v1.Pod{} pods := s.pods[source] if pods == nil { @@ -291,6 +306,8 @@ func (s *podStorage) merge(source string, change interface{}) (adds, updates, de removePods = append(removePods, existing) } } + case kubetypes.RESTORE: + glog.V(4).Infof("Restoring pods for source %s", source) default: glog.Warningf("Received invalid update type: %v", update) @@ -304,8 +321,9 @@ func (s *podStorage) merge(source string, change interface{}) (adds, updates, de deletes = &kubetypes.PodUpdate{Op: kubetypes.DELETE, Pods: copyPods(deletePods), Source: source} removes = &kubetypes.PodUpdate{Op: kubetypes.REMOVE, Pods: copyPods(removePods), Source: source} reconciles = &kubetypes.PodUpdate{Op: kubetypes.RECONCILE, Pods: copyPods(reconcilePods), Source: source} + restores = &kubetypes.PodUpdate{Op: kubetypes.RESTORE, Pods: copyPods(restorePods), Source: source} - return adds, updates, deletes, removes, reconciles + return adds, updates, deletes, removes, reconciles, restores } func (s *podStorage) markSourceSet(source string) { @@ -323,34 +341,17 @@ func (s *podStorage) seenSources(sources ...string) bool { func filterInvalidPods(pods []*v1.Pod, source string, recorder record.EventRecorder) (filtered []*v1.Pod) { names := sets.String{} for i, pod := range pods { - var errlist field.ErrorList - // TODO: remove the conversion when validation is performed on versioned objects. - internalPod := &api.Pod{} - if err := k8s_api_v1.Convert_v1_Pod_To_api_Pod(pod, internalPod, nil); err != nil { - glog.Warningf("Pod[%d] (%s) from %s failed to convert to v1, ignoring: %v", i+1, format.Pod(pod), source, err) - recorder.Eventf(pod, v1.EventTypeWarning, "FailedConversion", "Error converting pod %s from %s, ignoring: %v", format.Pod(pod), source, err) + // Pods from each source are assumed to have passed validation individually. + // This function only checks if there is any naming conflict. + name := kubecontainer.GetPodFullName(pod) + if names.Has(name) { + glog.Warningf("Pod[%d] (%s) from %s failed validation due to duplicate pod name %q, ignoring", i+1, format.Pod(pod), source, pod.Name) + 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) continue - } - if errs := validation.ValidatePod(internalPod); len(errs) != 0 { - errlist = append(errlist, errs...) - // If validation fails, don't trust it any further - - // even Name could be bad. } else { - name := kubecontainer.GetPodFullName(pod) - if names.Has(name) { - // TODO: when validation becomes versioned, this gets a bit - // more complicated. - errlist = append(errlist, field.Duplicate(field.NewPath("metadata", "name"), pod.Name)) - } else { - names.Insert(name) - } - } - if len(errlist) > 0 { - err := errlist.ToAggregate() - glog.Warningf("Pod[%d] (%s) from %s failed validation, ignoring: %v", i+1, format.Pod(pod), source, err) - recorder.Eventf(pod, v1.EventTypeWarning, events.FailedValidation, "Error validating pod %s from %s, ignoring: %v", format.Pod(pod), source, err) - continue + names.Insert(name) } + filtered = append(filtered, pod) } return diff --git a/pkg/kubelet/config/config_test.go b/pkg/kubelet/config/config_test.go index 1e93e5f2ab0..deb1f4dcaf3 100644 --- a/pkg/kubelet/config/config_test.go +++ b/pkg/kubelet/config/config_test.go @@ -146,8 +146,10 @@ func TestNewPodAddedInvalidNamespace(t *testing.T) { // see an update podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "")) channel <- podUpdate + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", ""))) + config.Sync() - expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource)) + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", ""))) } func TestNewPodAddedDefaultNamespace(t *testing.T) { diff --git a/pkg/kubelet/config/defaults.go b/pkg/kubelet/config/defaults.go index b000b60b11f..b90306968f1 100644 --- a/pkg/kubelet/config/defaults.go +++ b/pkg/kubelet/config/defaults.go @@ -17,8 +17,10 @@ limitations under the License. package config const ( - DefaultKubeletPodsDirName = "pods" - DefaultKubeletVolumesDirName = "volumes" - DefaultKubeletPluginsDirName = "plugins" - DefaultKubeletContainersDirName = "containers" + DefaultKubeletPodsDirName = "pods" + DefaultKubeletVolumesDirName = "volumes" + DefaultKubeletVolumeDevicesDirName = "volumeDevices" + DefaultKubeletPluginsDirName = "plugins" + DefaultKubeletContainersDirName = "containers" + DefaultKubeletPluginContainersDirName = "plugin-containers" ) diff --git a/pkg/kubelet/config/file.go b/pkg/kubelet/config/file.go index f825c2eb526..aeabec55987 100644 --- a/pkg/kubelet/config/file.go +++ b/pkg/kubelet/config/file.go @@ -32,7 +32,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/tools/cache" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" kubetypes "k8s.io/kubernetes/pkg/kubelet/types" ) @@ -48,12 +48,12 @@ func NewSourceFile(path string, nodeName types.NodeName, period time.Duration, u // "golang.org/x/exp/inotify" requires a path without trailing "/" path = strings.TrimRight(path, string(os.PathSeparator)) - config := new(path, nodeName, period, updates) + config := newSourceFile(path, nodeName, period, updates) glog.V(1).Infof("Watching path %q", path) go wait.Forever(config.run, period) } -func new(path string, nodeName types.NodeName, period time.Duration, updates chan<- interface{}) *sourceFile { +func newSourceFile(path string, nodeName types.NodeName, period time.Duration, updates chan<- interface{}) *sourceFile { send := func(objs []interface{}) { var pods []*v1.Pod for _, o := range objs { diff --git a/pkg/kubelet/config/file_linux_test.go b/pkg/kubelet/config/file_linux_test.go index c17d33fc51e..64ca4943a55 100644 --- a/pkg/kubelet/config/file_linux_test.go +++ b/pkg/kubelet/config/file_linux_test.go @@ -36,17 +36,17 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" utiltesting "k8s.io/client-go/util/testing" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" - k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" - "k8s.io/kubernetes/pkg/api/validation" + api "k8s.io/kubernetes/pkg/apis/core" + k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" + "k8s.io/kubernetes/pkg/apis/core/validation" kubetypes "k8s.io/kubernetes/pkg/kubelet/types" "k8s.io/kubernetes/pkg/securitycontext" ) func TestExtractFromNonExistentFile(t *testing.T) { ch := make(chan interface{}, 1) - c := new("/some/fake/file", "localhost", time.Millisecond, ch) + c := newSourceFile("/some/fake/file", "localhost", time.Millisecond, ch) err := c.watch() if err == nil { t.Errorf("Expected error") @@ -90,7 +90,7 @@ func TestReadPodsFromFileExistAlready(t *testing.T) { for _, pod := range update.Pods { // TODO: remove the conversion when validation is performed on versioned objects. internalPod := &api.Pod{} - if err := k8s_api_v1.Convert_v1_Pod_To_api_Pod(pod, internalPod, nil); err != nil { + if err := k8s_api_v1.Convert_v1_Pod_To_core_Pod(pod, internalPod, nil); err != nil { t.Fatalf("%s: Cannot convert pod %#v, %#v", testCase.desc, pod, err) } if errs := validation.ValidatePod(internalPod); len(errs) > 0 { @@ -137,7 +137,7 @@ func TestExtractFromBadDataFile(t *testing.T) { } ch := make(chan interface{}, 1) - c := new(fileName, "localhost", time.Millisecond, ch) + c := newSourceFile(fileName, "localhost", time.Millisecond, ch) err = c.resetStoreFromPath() if err == nil { t.Fatalf("expected error, got nil") @@ -153,7 +153,7 @@ func TestExtractFromEmptyDir(t *testing.T) { defer os.RemoveAll(dirName) ch := make(chan interface{}, 1) - c := new(dirName, "localhost", time.Millisecond, ch) + c := newSourceFile(dirName, "localhost", time.Millisecond, ch) err = c.resetStoreFromPath() if err != nil { t.Fatalf("unexpected error: %v", err) @@ -373,7 +373,7 @@ func expectUpdate(t *testing.T, ch chan interface{}, testCase *testCase) { for _, pod := range update.Pods { // TODO: remove the conversion when validation is performed on versioned objects. internalPod := &api.Pod{} - if err := k8s_api_v1.Convert_v1_Pod_To_api_Pod(pod, internalPod, nil); err != nil { + if err := k8s_api_v1.Convert_v1_Pod_To_core_Pod(pod, internalPod, nil); err != nil { t.Fatalf("%s: Cannot convert pod %#v, %#v", testCase.desc, pod, err) } if errs := validation.ValidatePod(internalPod); len(errs) > 0 { diff --git a/pkg/kubelet/config/flags.go b/pkg/kubelet/config/flags.go index e63b2322547..705b8babfd2 100644 --- a/pkg/kubelet/config/flags.go +++ b/pkg/kubelet/config/flags.go @@ -22,6 +22,14 @@ import ( ) type ContainerRuntimeOptions struct { + + // General options. + + // ContainerRuntime is the container runtime to use. + ContainerRuntime string + // RuntimeCgroups that container runtime is expected to be isolated in. + RuntimeCgroups string + // Docker-specific options. // DockershimRootDirectory is the path to the dockershim root directory. Defaults to @@ -53,9 +61,6 @@ type ContainerRuntimeOptions struct { // and overrides the default MTU for cases where it cannot be automatically // computed (such as IPSEC). NetworkPluginMTU int32 - // NetworkPluginDir is the full path of the directory in which to search - // for network plugins (and, for backwards-compat, CNI config files) - NetworkPluginDir string // CNIConfDir is the full path of the directory in which to search for // CNI config files CNIConfDir string @@ -75,6 +80,10 @@ type ContainerRuntimeOptions struct { } func (s *ContainerRuntimeOptions) AddFlags(fs *pflag.FlagSet) { + // General settings. + fs.StringVar(&s.ContainerRuntime, "container-runtime", s.ContainerRuntime, "The container runtime to use. Possible values: 'docker', 'rkt'.") + fs.StringVar(&s.RuntimeCgroups, "runtime-cgroups", s.RuntimeCgroups, "Optional absolute name of cgroups to create and run the runtime in.") + // Docker-specific settings. fs.BoolVar(&s.ExperimentalDockershim, "experimental-dockershim", s.ExperimentalDockershim, "Enable dockershim only mode. In this mode, kubelet will only start dockershim without any other functionalities. This flag only serves test purpose, please do not use it unless you are conscious of what you are doing. [default=false]") fs.MarkHidden("experimental-dockershim") @@ -87,9 +96,6 @@ func (s *ContainerRuntimeOptions) AddFlags(fs *pflag.FlagSet) { // Network plugin settings. Shared by both docker and rkt. fs.StringVar(&s.NetworkPluginName, "network-plugin", s.NetworkPluginName, " The name of the network plugin to be invoked for various events in kubelet/pod lifecycle") - //TODO(#46410): Remove the network-plugin-dir flag. - fs.StringVar(&s.NetworkPluginDir, "network-plugin-dir", s.NetworkPluginDir, " The full path of the directory in which to search for network plugins or CNI config") - fs.MarkDeprecated("network-plugin-dir", "Use --cni-bin-dir instead. This flag will be removed in a future version.") fs.StringVar(&s.CNIConfDir, "cni-conf-dir", s.CNIConfDir, " The full path of the directory in which to search for CNI config files. Default: /etc/cni/net.d") fs.StringVar(&s.CNIBinDir, "cni-bin-dir", s.CNIBinDir, " The full path of the directory in which to search for CNI plugin binaries. Default: /opt/cni/bin") fs.Int32Var(&s.NetworkPluginMTU, "network-plugin-mtu", s.NetworkPluginMTU, " The MTU to be passed to the network plugin, to override the default. Set to 0 to use the default 1460 MTU.") diff --git a/pkg/kubelet/config/http.go b/pkg/kubelet/config/http.go index 077fdb4447f..0ef43e062a5 100644 --- a/pkg/kubelet/config/http.go +++ b/pkg/kubelet/config/http.go @@ -26,7 +26,7 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" kubetypes "k8s.io/kubernetes/pkg/kubelet/types" "github.com/golang/glog" diff --git a/pkg/kubelet/config/http_test.go b/pkg/kubelet/config/http_test.go index 843a6bd5052..edb25dda965 100644 --- a/pkg/kubelet/config/http_test.go +++ b/pkg/kubelet/config/http_test.go @@ -29,10 +29,11 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" utiltesting "k8s.io/client-go/util/testing" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/testapi" - k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" - "k8s.io/kubernetes/pkg/api/validation" + api "k8s.io/kubernetes/pkg/apis/core" + k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" + "k8s.io/kubernetes/pkg/apis/core/validation" kubetypes "k8s.io/kubernetes/pkg/kubelet/types" ) @@ -71,7 +72,7 @@ func TestExtractInvalidPods(t *testing.T) { { desc: "Invalid volume name", pod: &v1.Pod{ - TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, + TypeMeta: metav1.TypeMeta{APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, Spec: v1.PodSpec{ Volumes: []v1.Volume{{Name: "_INVALID_"}}, }, @@ -80,7 +81,7 @@ func TestExtractInvalidPods(t *testing.T) { { desc: "Duplicate volume names", pod: &v1.Pod{ - TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, + TypeMeta: metav1.TypeMeta{APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, Spec: v1.PodSpec{ Volumes: []v1.Volume{{Name: "repeated"}, {Name: "repeated"}}, }, @@ -89,7 +90,7 @@ func TestExtractInvalidPods(t *testing.T) { { desc: "Unspecified container name", pod: &v1.Pod{ - TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, + TypeMeta: metav1.TypeMeta{APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, Spec: v1.PodSpec{ Containers: []v1.Container{{Name: ""}}, }, @@ -98,7 +99,7 @@ func TestExtractInvalidPods(t *testing.T) { { desc: "Invalid container name", pod: &v1.Pod{ - TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, + TypeMeta: metav1.TypeMeta{APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()}, Spec: v1.PodSpec{ Containers: []v1.Container{{Name: "_INVALID_"}}, }, @@ -317,7 +318,7 @@ func TestExtractPodsFromHTTP(t *testing.T) { for _, pod := range update.Pods { // TODO: remove the conversion when validation is performed on versioned objects. internalPod := &api.Pod{} - if err := k8s_api_v1.Convert_v1_Pod_To_api_Pod(pod, internalPod, nil); err != nil { + if err := k8s_api_v1.Convert_v1_Pod_To_core_Pod(pod, internalPod, nil); err != nil { t.Fatalf("%s: Cannot convert pod %#v, %#v", testCase.desc, pod, err) } if errs := validation.ValidatePod(internalPod); len(errs) != 0 { @@ -330,7 +331,7 @@ func TestExtractPodsFromHTTP(t *testing.T) { func TestURLWithHeader(t *testing.T) { pod := &v1.Pod{ TypeMeta: metav1.TypeMeta{ - APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), + APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), Kind: "Pod", }, ObjectMeta: metav1.ObjectMeta{ diff --git a/pkg/kubelet/configmap/BUILD b/pkg/kubelet/configmap/BUILD index 27612f889f2..f7ad67e8ee8 100644 --- a/pkg/kubelet/configmap/BUILD +++ b/pkg/kubelet/configmap/BUILD @@ -42,8 +42,8 @@ filegroup( go_test( name = "go_default_test", srcs = ["configmap_manager_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/configmap", - library = ":go_default_library", deps = [ "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/kubelet/configmap/configmap_manager.go b/pkg/kubelet/configmap/configmap_manager.go index cf5d263bdf0..71a7ae14854 100644 --- a/pkg/kubelet/configmap/configmap_manager.go +++ b/pkg/kubelet/configmap/configmap_manager.go @@ -64,7 +64,7 @@ func NewSimpleConfigMapManager(kubeClient clientset.Interface) Manager { } func (s *simpleConfigMapManager) GetConfigMap(namespace, name string) (*v1.ConfigMap, error) { - return s.kubeClient.Core().ConfigMaps(namespace).Get(name, metav1.GetOptions{}) + return s.kubeClient.CoreV1().ConfigMaps(namespace).Get(name, metav1.GetOptions{}) } func (s *simpleConfigMapManager) RegisterPod(pod *v1.Pod) { @@ -216,7 +216,7 @@ func (s *configMapStore) Get(namespace, name string) (*v1.ConfigMap, error) { // etcd and apiserver (the cache is eventually consistent). util.FromApiserverCache(&opts) } - configMap, err := s.kubeClient.Core().ConfigMaps(namespace).Get(name, opts) + configMap, err := s.kubeClient.CoreV1().ConfigMaps(namespace).Get(name, opts) if err != nil && !apierrors.IsNotFound(err) && data.configMap == nil && data.err == nil { // Couldn't fetch the latest configmap, but there is no cached data to return. // Return the fetch result instead. diff --git a/pkg/kubelet/container/BUILD b/pkg/kubelet/container/BUILD index e44be4442c5..00c403b2ca6 100644 --- a/pkg/kubelet/container/BUILD +++ b/pkg/kubelet/container/BUILD @@ -8,7 +8,6 @@ go_library( "container_reference_manager.go", "helpers.go", "os.go", - "pty_unsupported.go", "ref.go", "resize.go", "runtime.go", @@ -16,15 +15,45 @@ go_library( "runtime_cache_fake.go", "sync_result.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "pty_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "pty_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "pty_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "pty_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ "pty_linux.go", ], + "@io_bazel_rules_go//go/platform:nacl": [ + "pty_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "pty_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "pty_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "pty_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "pty_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "pty_unsupported.go", + ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/pkg/kubelet/container", visibility = ["//visibility:public"], deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library", "//pkg/kubelet/util/format:go_default_library", "//pkg/kubelet/util/ioutils:go_default_library", @@ -43,7 +72,7 @@ go_library( "//vendor/k8s.io/client-go/tools/remotecommand:go_default_library", "//vendor/k8s.io/client-go/util/flowcontrol:go_default_library", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//vendor/github.com/kr/pty:go_default_library", ], "//conditions:default": [], @@ -58,11 +87,11 @@ go_test( "ref_test.go", "sync_result_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/container", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core/install:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/kubelet/container/helpers.go b/pkg/kubelet/container/helpers.go index df1b274f598..32dbc745b30 100644 --- a/pkg/kubelet/container/helpers.go +++ b/pkg/kubelet/container/helpers.go @@ -46,8 +46,8 @@ type HandlerRunner interface { // RuntimeHelper wraps kubelet to make container runtime // able to get necessary informations like the RunContainerOptions, DNS settings, Host IP. type RuntimeHelper interface { - GenerateRunContainerOptions(pod *v1.Pod, container *v1.Container, podIP string) (contOpts *RunContainerOptions, useClusterFirstPolicy bool, err error) - GetClusterDNS(pod *v1.Pod) (dnsServers []string, dnsSearches []string, useClusterFirstPolicy bool, err error) + GenerateRunContainerOptions(pod *v1.Pod, container *v1.Container, podIP string) (contOpts *RunContainerOptions, err error) + GetPodDNS(pod *v1.Pod) (dnsConfig *runtimeapi.DNSConfig, err error) // GetPodCgroupParent returns the CgroupName identifer, and its literal cgroupfs form on the host // of a pod. GetPodCgroupParent(pod *v1.Pod) string diff --git a/pkg/kubelet/container/ref.go b/pkg/kubelet/container/ref.go index b15f7702ef4..f61c0fc4a3b 100644 --- a/pkg/kubelet/container/ref.go +++ b/pkg/kubelet/container/ref.go @@ -21,7 +21,7 @@ import ( "k8s.io/api/core/v1" ref "k8s.io/client-go/tools/reference" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" ) var ImplicitContainerPrefix string = "implicitly required container " @@ -39,7 +39,7 @@ func GenerateContainerRef(pod *v1.Pod, container *v1.Container) (*v1.ObjectRefer // start (like the pod infra container). This is not a good way, ugh. fieldPath = ImplicitContainerPrefix + container.Name } - ref, err := ref.GetPartialReference(api.Scheme, pod, fieldPath) + ref, err := ref.GetPartialReference(legacyscheme.Scheme, pod, fieldPath) if err != nil { return nil, err } diff --git a/pkg/kubelet/container/ref_test.go b/pkg/kubelet/container/ref_test.go index b25e740ded2..705f202fda2 100644 --- a/pkg/kubelet/container/ref_test.go +++ b/pkg/kubelet/container/ref_test.go @@ -21,8 +21,8 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" - _ "k8s.io/kubernetes/pkg/api/install" + "k8s.io/kubernetes/pkg/api/legacyscheme" + _ "k8s.io/kubernetes/pkg/apis/core/install" ) func TestFieldPath(t *testing.T) { @@ -68,14 +68,14 @@ func TestGenerateContainerRef(t *testing.T) { okPod = v1.Pod{ TypeMeta: metav1.TypeMeta{ Kind: "Pod", - APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), + APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), }, ObjectMeta: metav1.ObjectMeta{ Name: "ok", Namespace: "test-ns", UID: "bar", ResourceVersion: "42", - SelfLink: "/api/" + api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String() + "/pods/foo", + SelfLink: "/api/" + legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String() + "/pods/foo", }, Spec: v1.PodSpec{ Containers: []v1.Container{ @@ -92,7 +92,7 @@ func TestGenerateContainerRef(t *testing.T) { noSelfLinkPod.Kind = "" noSelfLinkPod.APIVersion = "" noSelfLinkPod.ObjectMeta.SelfLink = "" - defaultedSelfLinkPod.ObjectMeta.SelfLink = "/api/" + api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String() + "/pods/ok" + defaultedSelfLinkPod.ObjectMeta.SelfLink = "/api/" + legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String() + "/pods/ok" cases := []struct { name string @@ -109,7 +109,7 @@ func TestGenerateContainerRef(t *testing.T) { }, expected: &v1.ObjectReference{ Kind: "Pod", - APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), + APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), Name: "ok", Namespace: "test-ns", UID: "bar", @@ -124,7 +124,7 @@ func TestGenerateContainerRef(t *testing.T) { container: &v1.Container{}, expected: &v1.ObjectReference{ Kind: "Pod", - APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), + APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), Name: "ok", Namespace: "test-ns", UID: "bar", @@ -148,7 +148,7 @@ func TestGenerateContainerRef(t *testing.T) { }, expected: &v1.ObjectReference{ Kind: "Pod", - APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), + APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), Name: "ok", Namespace: "test-ns", UID: "bar", @@ -165,7 +165,7 @@ func TestGenerateContainerRef(t *testing.T) { }, expected: &v1.ObjectReference{ Kind: "Pod", - APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), + APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), Name: "ok", Namespace: "test-ns", UID: "bar", diff --git a/pkg/kubelet/container/runtime.go b/pkg/kubelet/container/runtime.go index f545b41a76b..2f5f4e3d188 100644 --- a/pkg/kubelet/container/runtime.go +++ b/pkg/kubelet/container/runtime.go @@ -428,10 +428,6 @@ type RunContainerOptions struct { // this directory will be used to create and mount the log file to // container.TerminationMessagePath PodContainerDir string - // The list of DNS servers for the container to use. - DNS []string - // The list of DNS search domains. - DNSSearch []string // The parent cgroup to pass to Docker CgroupParent string // The type of container rootfs @@ -450,9 +446,14 @@ type RunContainerOptions struct { type VolumeInfo struct { // Mounter is the volume's mounter Mounter volume.Mounter + // BlockVolumeMapper is the Block volume's mapper + BlockVolumeMapper volume.BlockVolumeMapper // SELinuxLabeled indicates whether this volume has had the // pod's SELinux label applied to it or not SELinuxLabeled bool + // Whether the volume permission is set to read-only or not + // This value is passed from volume.spec + ReadOnly bool } type VolumeMap map[string]VolumeInfo diff --git a/pkg/kubelet/container/testing/BUILD b/pkg/kubelet/container/testing/BUILD index 2c8109e6271..c56ccc4d8fd 100644 --- a/pkg/kubelet/container/testing/BUILD +++ b/pkg/kubelet/container/testing/BUILD @@ -14,6 +14,7 @@ go_library( importpath = "k8s.io/kubernetes/pkg/kubelet/container/testing", visibility = ["//visibility:public"], deps = [ + "//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library", "//pkg/kubelet/container:go_default_library", "//pkg/volume:go_default_library", "//vendor/github.com/golang/mock/gomock:go_default_library", diff --git a/pkg/kubelet/container/testing/fake_runtime_helper.go b/pkg/kubelet/container/testing/fake_runtime_helper.go index 97b77a56d4a..9d41bcd2870 100644 --- a/pkg/kubelet/container/testing/fake_runtime_helper.go +++ b/pkg/kubelet/container/testing/fake_runtime_helper.go @@ -19,6 +19,7 @@ package testing import ( "k8s.io/api/core/v1" kubetypes "k8s.io/apimachinery/pkg/types" + runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" ) @@ -26,26 +27,30 @@ import ( type FakeRuntimeHelper struct { DNSServers []string DNSSearches []string + DNSOptions []string HostName string HostDomain string PodContainerDir string Err error } -func (f *FakeRuntimeHelper) GenerateRunContainerOptions(pod *v1.Pod, container *v1.Container, podIP string) (*kubecontainer.RunContainerOptions, bool, error) { +func (f *FakeRuntimeHelper) GenerateRunContainerOptions(pod *v1.Pod, container *v1.Container, podIP string) (*kubecontainer.RunContainerOptions, error) { var opts kubecontainer.RunContainerOptions if len(container.TerminationMessagePath) != 0 { opts.PodContainerDir = f.PodContainerDir } - return &opts, false, nil + return &opts, nil } func (f *FakeRuntimeHelper) GetPodCgroupParent(pod *v1.Pod) string { return "" } -func (f *FakeRuntimeHelper) GetClusterDNS(pod *v1.Pod) ([]string, []string, bool, error) { - return f.DNSServers, f.DNSSearches, false, f.Err +func (f *FakeRuntimeHelper) GetPodDNS(pod *v1.Pod) (*runtimeapi.DNSConfig, error) { + return &runtimeapi.DNSConfig{ + Servers: f.DNSServers, + Searches: f.DNSSearches, + Options: f.DNSOptions}, f.Err } // This is not used by docker runtime. diff --git a/pkg/kubelet/custommetrics/BUILD b/pkg/kubelet/custommetrics/BUILD index 2e1efaed2f9..4ef23949667 100644 --- a/pkg/kubelet/custommetrics/BUILD +++ b/pkg/kubelet/custommetrics/BUILD @@ -16,8 +16,8 @@ go_library( go_test( name = "go_default_test", srcs = ["custom_metrics_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/custommetrics", - library = ":go_default_library", deps = [ "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/kubelet/deviceplugin/BUILD b/pkg/kubelet/deviceplugin/BUILD deleted file mode 100644 index beaaaaebd82..00000000000 --- a/pkg/kubelet/deviceplugin/BUILD +++ /dev/null @@ -1,55 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = [ - "device_plugin_stub.go", - "endpoint.go", - "manager.go", - "types.go", - "utils.go", - ], - importpath = "k8s.io/kubernetes/pkg/kubelet/deviceplugin", - deps = [ - "//pkg/api/v1/helper:go_default_library", - "//pkg/kubelet/apis/deviceplugin/v1alpha1:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/golang.org/x/net/context:go_default_library", - "//vendor/google.golang.org/grpc:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) - -go_test( - name = "go_default_test", - srcs = [ - "endpoint_test.go", - "manager_test.go", - "utils_test.go", - ], - importpath = "k8s.io/kubernetes/pkg/kubelet/deviceplugin", - library = ":go_default_library", - deps = [ - "//pkg/kubelet/apis/deviceplugin/v1alpha1:go_default_library", - "//vendor/github.com/stretchr/testify/require:go_default_library", - ], -) diff --git a/pkg/kubelet/deviceplugin/OWNERS b/pkg/kubelet/deviceplugin/OWNERS deleted file mode 100644 index f16238fc928..00000000000 --- a/pkg/kubelet/deviceplugin/OWNERS +++ /dev/null @@ -1,5 +0,0 @@ -reviewers: -- jiayingz -- mindprince -- RenaudWasTaken -- vishh diff --git a/pkg/kubelet/deviceplugin/endpoint.go b/pkg/kubelet/deviceplugin/endpoint.go deleted file mode 100644 index f0523471a38..00000000000 --- a/pkg/kubelet/deviceplugin/endpoint.go +++ /dev/null @@ -1,214 +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 deviceplugin - -import ( - "fmt" - "net" - "sync" - "time" - - "github.com/golang/glog" - "golang.org/x/net/context" - "google.golang.org/grpc" - - pluginapi "k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1alpha1" -) - -// endpoint maps to a single registered device plugin. It is responsible -// for managing gRPC communications with the device plugin and caching -// device states reported by the device plugin. -type endpoint struct { - client pluginapi.DevicePluginClient - - socketPath string - resourceName string - - devices map[string]*pluginapi.Device - mutex sync.Mutex - - callback MonitorCallback - - cancel context.CancelFunc - ctx context.Context -} - -// newEndpoint creates a new endpoint for the given resourceName. -func newEndpoint(socketPath, resourceName string, callback MonitorCallback) (*endpoint, error) { - client, err := dial(socketPath) - if err != nil { - glog.Errorf("Can't create new endpoint with path %s err %v", socketPath, err) - return nil, err - } - - ctx, stop := context.WithCancel(context.Background()) - - return &endpoint{ - client: client, - - socketPath: socketPath, - resourceName: resourceName, - - devices: nil, - callback: callback, - - cancel: stop, - ctx: ctx, - }, nil -} - -func (e *endpoint) getDevices() []*pluginapi.Device { - e.mutex.Lock() - defer e.mutex.Unlock() - return copyDevices(e.devices) -} - -// list initializes ListAndWatch gRPC call for the device plugin and gets the -// initial list of the devices. Returns ListAndWatch gRPC stream on success. -func (e *endpoint) list() (pluginapi.DevicePlugin_ListAndWatchClient, error) { - glog.V(3).Infof("Starting List") - stream, err := e.client.ListAndWatch(e.ctx, &pluginapi.Empty{}) - if err != nil { - glog.Errorf(errListAndWatch, e.resourceName, err) - - return nil, err - } - - devs, err := stream.Recv() - if err != nil { - glog.Errorf(errListAndWatch, e.resourceName, err) - return nil, err - } - - devices := make(map[string]*pluginapi.Device) - var added, updated, deleted []*pluginapi.Device - for _, d := range devs.Devices { - devices[d.ID] = d - added = append(added, cloneDevice(d)) - } - - e.mutex.Lock() - e.devices = devices - e.mutex.Unlock() - - e.callback(e.resourceName, added, updated, deleted) - - return stream, nil -} - -// listAndWatch blocks on receiving ListAndWatch gRPC stream updates. Each ListAndWatch -// stream update contains a new list of device states. listAndWatch compares the new -// device states with its cached states to get list of new, updated, and deleted devices. -// It then issues a callback to pass this information to the device_plugin_handler which -// will adjust the resource available information accordingly. -func (e *endpoint) listAndWatch(stream pluginapi.DevicePlugin_ListAndWatchClient) { - glog.V(3).Infof("Starting ListAndWatch") - - devices := make(map[string]*pluginapi.Device) - - e.mutex.Lock() - for _, d := range e.devices { - devices[d.ID] = cloneDevice(d) - } - e.mutex.Unlock() - - for { - response, err := stream.Recv() - if err != nil { - glog.Errorf(errListAndWatch, e.resourceName, err) - return - } - - devs := response.Devices - glog.V(2).Infof("State pushed for device plugin %s", e.resourceName) - - newDevs := make(map[string]*pluginapi.Device) - var added, updated []*pluginapi.Device - - for _, d := range devs { - dOld, ok := devices[d.ID] - newDevs[d.ID] = d - - if !ok { - glog.V(2).Infof("New device for Endpoint %s: %v", e.resourceName, d) - - devices[d.ID] = d - added = append(added, cloneDevice(d)) - - continue - } - - if d.Health == dOld.Health { - continue - } - - if d.Health == pluginapi.Unhealthy { - glog.Errorf("Device %s is now Unhealthy", d.ID) - } else if d.Health == pluginapi.Healthy { - glog.V(2).Infof("Device %s is now Healthy", d.ID) - } - - devices[d.ID] = d - updated = append(updated, cloneDevice(d)) - } - - var deleted []*pluginapi.Device - for id, d := range devices { - if _, ok := newDevs[id]; ok { - continue - } - - glog.Errorf("Device %s was deleted", d.ID) - - deleted = append(deleted, cloneDevice(d)) - delete(devices, id) - } - - e.mutex.Lock() - e.devices = devices - e.mutex.Unlock() - - e.callback(e.resourceName, added, updated, deleted) - } - -} - -// allocate issues Allocate gRPC call to the device plugin. -func (e *endpoint) allocate(devs []string) (*pluginapi.AllocateResponse, error) { - return e.client.Allocate(context.Background(), &pluginapi.AllocateRequest{ - DevicesIDs: devs, - }) -} - -func (e *endpoint) stop() { - e.cancel() -} - -// dial establishes the gRPC communication with the registered device plugin. -func dial(unixSocketPath string) (pluginapi.DevicePluginClient, error) { - c, err := grpc.Dial(unixSocketPath, grpc.WithInsecure(), - grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) { - return net.DialTimeout("unix", addr, timeout) - }), - ) - - if err != nil { - return nil, fmt.Errorf(errFailedToDialDevicePlugin+" %v", err) - } - - return pluginapi.NewDevicePluginClient(c), nil -} diff --git a/pkg/kubelet/deviceplugin/endpoint_test.go b/pkg/kubelet/deviceplugin/endpoint_test.go deleted file mode 100644 index a1786711de9..00000000000 --- a/pkg/kubelet/deviceplugin/endpoint_test.go +++ /dev/null @@ -1,132 +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 deviceplugin - -import ( - "path" - "testing" - "time" - - "github.com/stretchr/testify/require" - - pluginapi "k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1alpha1" -) - -var ( - esocketName = "mock.sock" -) - -func TestNewEndpoint(t *testing.T) { - socket := path.Join("/tmp", esocketName) - - devs := []*pluginapi.Device{ - {ID: "ADeviceId", Health: pluginapi.Healthy}, - } - - p, e := esetup(t, devs, socket, "mock", func(n string, a, u, r []*pluginapi.Device) {}) - defer ecleanup(t, p, e) -} - -func TestList(t *testing.T) { - socket := path.Join("/tmp", esocketName) - - devs := []*pluginapi.Device{ - {ID: "ADeviceId", Health: pluginapi.Healthy}, - } - - p, e := esetup(t, devs, socket, "mock", func(n string, a, u, r []*pluginapi.Device) {}) - defer ecleanup(t, p, e) - - _, err := e.list() - require.NoError(t, err) - - e.mutex.Lock() - defer e.mutex.Unlock() - - require.Len(t, e.devices, 1) - - d, ok := e.devices[devs[0].ID] - require.True(t, ok) - - require.Equal(t, d.ID, devs[0].ID) - require.Equal(t, d.Health, devs[0].Health) -} - -func TestListAndWatch(t *testing.T) { - socket := path.Join("/tmp", esocketName) - - devs := []*pluginapi.Device{ - {ID: "ADeviceId", Health: pluginapi.Healthy}, - {ID: "AnotherDeviceId", Health: pluginapi.Healthy}, - } - - updated := []*pluginapi.Device{ - {ID: "ADeviceId", Health: pluginapi.Unhealthy}, - {ID: "AThirdDeviceId", Health: pluginapi.Healthy}, - } - - p, e := esetup(t, devs, socket, "mock", func(n string, a, u, r []*pluginapi.Device) { - require.Len(t, a, 1) - require.Len(t, u, 1) - require.Len(t, r, 1) - - require.Equal(t, a[0].ID, updated[1].ID) - - require.Equal(t, u[0].ID, updated[0].ID) - require.Equal(t, u[0].Health, updated[0].Health) - - require.Equal(t, r[0].ID, devs[1].ID) - }) - defer ecleanup(t, p, e) - - s, err := e.list() - require.NoError(t, err) - - go e.listAndWatch(s) - p.Update(updated) - time.Sleep(time.Second) - - e.mutex.Lock() - defer e.mutex.Unlock() - - require.Len(t, e.devices, 2) - for _, dref := range updated { - d, ok := e.devices[dref.ID] - - require.True(t, ok) - require.Equal(t, d.ID, dref.ID) - require.Equal(t, d.Health, dref.Health) - } - -} - -func esetup(t *testing.T, devs []*pluginapi.Device, socket, resourceName string, callback MonitorCallback) (*Stub, *endpoint) { - p := NewDevicePluginStub(devs, socket) - - err := p.Start() - require.NoError(t, err) - - e, err := newEndpoint(socket, "mock", func(n string, a, u, r []*pluginapi.Device) {}) - require.NoError(t, err) - - return p, e -} - -func ecleanup(t *testing.T, p *Stub, e *endpoint) { - p.Stop() - e.stop() -} diff --git a/pkg/kubelet/deviceplugin/manager.go b/pkg/kubelet/deviceplugin/manager.go deleted file mode 100644 index 5c05a972894..00000000000 --- a/pkg/kubelet/deviceplugin/manager.go +++ /dev/null @@ -1,239 +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 deviceplugin - -import ( - "fmt" - "net" - "os" - "path/filepath" - "sync" - - "github.com/golang/glog" - "golang.org/x/net/context" - "google.golang.org/grpc" - - pluginapi "k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1alpha1" -) - -// ManagerImpl is the structure in charge of managing Device Plugins. -type ManagerImpl struct { - socketname string - socketdir string - - endpoints map[string]*endpoint // Key is ResourceName - mutex sync.Mutex - - callback MonitorCallback - - server *grpc.Server -} - -// NewManagerImpl creates a new manager on the socket `socketPath`. -// f is the callback that is called when a device becomes unhealthy. -// socketPath is present for testing purposes in production this is pluginapi.KubeletSocket -func NewManagerImpl(socketPath string, f MonitorCallback) (*ManagerImpl, error) { - glog.V(2).Infof("Creating Device Plugin manager at %s", socketPath) - - if socketPath == "" || !filepath.IsAbs(socketPath) { - return nil, fmt.Errorf(errBadSocket+" %v", socketPath) - } - - dir, file := filepath.Split(socketPath) - return &ManagerImpl{ - endpoints: make(map[string]*endpoint), - - socketname: file, - socketdir: dir, - callback: f, - }, nil -} - -func (m *ManagerImpl) removeContents(dir string) error { - d, err := os.Open(dir) - if err != nil { - return err - } - defer d.Close() - names, err := d.Readdirnames(-1) - if err != nil { - return err - } - for _, name := range names { - filePath := filepath.Join(dir, name) - if filePath == m.CheckpointFile() { - continue - } - stat, err := os.Stat(filePath) - if err != nil { - glog.Errorf("Failed to stat file %v: %v", filePath, err) - continue - } - if stat.IsDir() { - continue - } - err = os.RemoveAll(filePath) - if err != nil { - return err - } - } - return nil -} - -// CheckpointFile returns device plugin checkpoint file path. -func (m *ManagerImpl) CheckpointFile() string { - return filepath.Join(m.socketdir, "kubelet_internal_checkpoint") -} - -// Start starts the Device Plugin Manager -func (m *ManagerImpl) Start() error { - glog.V(2).Infof("Starting Device Plugin manager") - - socketPath := filepath.Join(m.socketdir, m.socketname) - os.MkdirAll(m.socketdir, 0755) - - // Removes all stale sockets in m.socketdir. Device plugins can monitor - // this and use it as a signal to re-register with the new Kubelet. - if err := m.removeContents(m.socketdir); err != nil { - glog.Errorf("Fail to clean up stale contents under %s: %+v", m.socketdir, err) - } - - if err := os.Remove(socketPath); err != nil && !os.IsNotExist(err) { - glog.Errorf(errRemoveSocket+" %+v", err) - return err - } - - s, err := net.Listen("unix", socketPath) - if err != nil { - glog.Errorf(errListenSocket+" %+v", err) - return err - } - - m.server = grpc.NewServer([]grpc.ServerOption{}...) - - pluginapi.RegisterRegistrationServer(m.server, m) - go m.server.Serve(s) - - return nil -} - -// Devices is the map of devices that are known by the Device -// Plugin manager with the kind of the devices as key -func (m *ManagerImpl) Devices() map[string][]*pluginapi.Device { - m.mutex.Lock() - defer m.mutex.Unlock() - - devs := make(map[string][]*pluginapi.Device) - for k, e := range m.endpoints { - glog.V(3).Infof("Endpoint: %+v: %+v", k, e) - devs[k] = e.getDevices() - } - - return devs -} - -// Allocate is the call that you can use to allocate a set of devices -// from the registered device plugins. -func (m *ManagerImpl) Allocate(resourceName string, devs []string) (*pluginapi.AllocateResponse, error) { - - if len(devs) == 0 { - return nil, nil - } - - glog.V(3).Infof("Recieved allocation request for devices %v for device plugin %s", - devs, resourceName) - m.mutex.Lock() - e, ok := m.endpoints[resourceName] - m.mutex.Unlock() - if !ok { - return nil, fmt.Errorf("Unknown Device Plugin %s", resourceName) - } - - return e.allocate(devs) -} - -// Register registers a device plugin. -func (m *ManagerImpl) Register(ctx context.Context, - r *pluginapi.RegisterRequest) (*pluginapi.Empty, error) { - glog.V(2).Infof("Got request for Device Plugin %s", r.ResourceName) - if r.Version != pluginapi.Version { - return &pluginapi.Empty{}, fmt.Errorf(errUnsuportedVersion) - } - - if err := IsResourceNameValid(r.ResourceName); err != nil { - return &pluginapi.Empty{}, err - } - - // TODO: for now, always accepts newest device plugin. Later may consider to - // add some policies here, e.g., verify whether an old device plugin with the - // same resource name is still alive to determine whether we want to accept - // the new registration. - go m.addEndpoint(r) - - return &pluginapi.Empty{}, nil -} - -// Stop is the function that can stop the gRPC server. -func (m *ManagerImpl) Stop() error { - m.mutex.Lock() - defer m.mutex.Unlock() - for _, e := range m.endpoints { - e.stop() - } - - m.server.Stop() - return nil -} - -func (m *ManagerImpl) addEndpoint(r *pluginapi.RegisterRequest) { - socketPath := filepath.Join(m.socketdir, r.Endpoint) - e, err := newEndpoint(socketPath, r.ResourceName, m.callback) - if err != nil { - glog.Errorf("Failed to dial device plugin with request %v: %v", r, err) - return - } - stream, err := e.list() - if err != nil { - glog.Errorf("Failed to List devices for plugin %v: %v", r.ResourceName, err) - return - } - - // Associates the newly created endpoint with the corresponding resource name. - // Stops existing endpoint if there is any. - m.mutex.Lock() - old, ok := m.endpoints[r.ResourceName] - m.endpoints[r.ResourceName] = e - m.mutex.Unlock() - glog.V(2).Infof("Registered endpoint %v", e) - if ok && old != nil { - old.stop() - } - - go func() { - e.listAndWatch(stream) - - m.mutex.Lock() - if old, ok := m.endpoints[r.ResourceName]; ok && old == e { - glog.V(2).Infof("Delete resource for endpoint %v", e) - delete(m.endpoints, r.ResourceName) - // Issues callback to delete all of devices. - e.callback(e.resourceName, []*pluginapi.Device{}, []*pluginapi.Device{}, e.getDevices()) - } - glog.V(2).Infof("Unregistered endpoint %v", e) - m.mutex.Unlock() - }() -} diff --git a/pkg/kubelet/deviceplugin/manager_test.go b/pkg/kubelet/deviceplugin/manager_test.go deleted file mode 100644 index cdc43a0b255..00000000000 --- a/pkg/kubelet/deviceplugin/manager_test.go +++ /dev/null @@ -1,116 +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 deviceplugin - -import ( - "sync/atomic" - "testing" - "time" - - "github.com/stretchr/testify/require" - - pluginapi "k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1alpha1" -) - -const ( - socketName = "/tmp/device_plugin/server.sock" - pluginSocketName = "/tmp/device_plugin/device-plugin.sock" - testResourceName = "fake-domain/resource" -) - -func TestNewManagerImpl(t *testing.T) { - _, err := NewManagerImpl("", func(n string, a, u, r []*pluginapi.Device) {}) - require.Error(t, err) - - _, err = NewManagerImpl(socketName, func(n string, a, u, r []*pluginapi.Device) {}) - require.NoError(t, err) -} - -func TestNewManagerImplStart(t *testing.T) { - m, p := setup(t, []*pluginapi.Device{}, func(n string, a, u, r []*pluginapi.Device) {}) - cleanup(t, m, p) -} - -// Tests that the device plugin manager correctly handles registration and re-registration by -// making sure that after registration, devices are correctly updated and if a re-registration -// happens, we will NOT delete devices. -func TestDevicePluginReRegistration(t *testing.T) { - devs := []*pluginapi.Device{ - {ID: "Dev1", Health: pluginapi.Healthy}, - {ID: "Dev2", Health: pluginapi.Healthy}, - } - - callbackCount := 0 - callbackChan := make(chan int) - var stopping int32 - stopping = 0 - callback := func(n string, a, u, r []*pluginapi.Device) { - // Should be called twice, one for each plugin registration, till we are stopping. - if callbackCount > 1 && atomic.LoadInt32(&stopping) <= 0 { - t.FailNow() - } - callbackCount++ - callbackChan <- callbackCount - } - m, p1 := setup(t, devs, callback) - p1.Register(socketName, testResourceName) - // Wait for the first callback to be issued. - <-callbackChan - // Wait till the endpoint is added to the manager. - for i := 0; i < 20; i++ { - if len(m.Devices()) > 0 { - break - } - time.Sleep(1) - } - devices := m.Devices() - require.Equal(t, 2, len(devices[testResourceName]), "Devices are not updated.") - - p2 := NewDevicePluginStub(devs, pluginSocketName+".new") - err := p2.Start() - require.NoError(t, err) - p2.Register(socketName, testResourceName) - // Wait for the second callback to be issued. - <-callbackChan - - devices2 := m.Devices() - require.Equal(t, 2, len(devices2[testResourceName]), "Devices shouldn't change.") - // Wait long enough to catch unexpected callbacks. - time.Sleep(5 * time.Second) - - atomic.StoreInt32(&stopping, 1) - cleanup(t, m, p1) - p2.Stop() -} - -func setup(t *testing.T, devs []*pluginapi.Device, callback MonitorCallback) (Manager, *Stub) { - m, err := NewManagerImpl(socketName, callback) - require.NoError(t, err) - err = m.Start() - require.NoError(t, err) - - p := NewDevicePluginStub(devs, pluginSocketName) - err = p.Start() - require.NoError(t, err) - - return m, p -} - -func cleanup(t *testing.T, m Manager, p *Stub) { - p.Stop() - m.Stop() -} diff --git a/pkg/kubelet/deviceplugin/types.go b/pkg/kubelet/deviceplugin/types.go deleted file mode 100644 index 3a2eae6fee1..00000000000 --- a/pkg/kubelet/deviceplugin/types.go +++ /dev/null @@ -1,74 +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 deviceplugin - -import ( - pluginapi "k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1alpha1" -) - -// MonitorCallback is the function called when a device's health state changes, -// or new devices are reported, or old devices are deleted. -// Updated contains the most recent state of the Device. -type MonitorCallback func(resourceName string, added, updated, deleted []*pluginapi.Device) - -// Manager manages all the Device Plugins running on a node. -type Manager interface { - // Start starts the gRPC Registration service. - Start() error - - // Devices is the map of devices that have registered themselves - // against the manager. - // The map key is the ResourceName of the device plugins. - Devices() map[string][]*pluginapi.Device - - // Allocate takes resourceName and list of device Ids, and calls the - // gRPC Allocate on the device plugin matching the resourceName. - Allocate(string, []string) (*pluginapi.AllocateResponse, error) - - // Stop stops the manager. - Stop() error - - // Returns checkpoint file path. - CheckpointFile() string -} - -// TODO: evaluate whether we need these error definitions. -const ( - // errFailedToDialDevicePlugin is the error raised when the device plugin could not be - // reached on the registered socket - errFailedToDialDevicePlugin = "failed to dial device plugin:" - // errUnsuportedVersion is the error raised when the device plugin uses an API version not - // supported by the Kubelet registry - errUnsuportedVersion = "unsupported API version by the Kubelet registry" - // errDevicePluginAlreadyExists is the error raised when a device plugin with the - // same Resource Name tries to register itself - errDevicePluginAlreadyExists = "another device plugin already registered this Resource Name" - // errInvalidResourceName is the error raised when a device plugin is registering - // itself with an invalid ResourceName - errInvalidResourceName = "the ResourceName is invalid" - // errEmptyResourceName is the error raised when the resource name field is empty - errEmptyResourceName = "invalid Empty ResourceName" - - // errBadSocket is the error raised when the registry socket path is not absolute - errBadSocket = "bad socketPath, must be an absolute path:" - // errRemoveSocket is the error raised when the registry could not remove the existing socket - errRemoveSocket = "failed to remove socket while starting device plugin registry, with error" - // errListenSocket is the error raised when the registry could not listen on the socket - errListenSocket = "failed to listen to socket while starting device plugin registry, with error" - // errListAndWatch is the error raised when ListAndWatch ended unsuccessfully - errListAndWatch = "listAndWatch ended unexpectedly for device plugin %s with error %v" -) diff --git a/pkg/kubelet/deviceplugin/utils.go b/pkg/kubelet/deviceplugin/utils.go deleted file mode 100644 index b30ac2f640e..00000000000 --- a/pkg/kubelet/deviceplugin/utils.go +++ /dev/null @@ -1,61 +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 deviceplugin - -import ( - "fmt" - - "k8s.io/api/core/v1" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" - pluginapi "k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1alpha1" -) - -func cloneDevice(d *pluginapi.Device) *pluginapi.Device { - return &pluginapi.Device{ - ID: d.ID, - Health: d.Health, - } - -} - -func copyDevices(devs map[string]*pluginapi.Device) []*pluginapi.Device { - var clones []*pluginapi.Device - - for _, d := range devs { - clones = append(clones, cloneDevice(d)) - } - - return clones -} - -// IsResourceNameValid returns an error if the resource is invalid or is not an -// extended resource name. -func IsResourceNameValid(resourceName string) error { - if resourceName == "" { - return fmt.Errorf(errEmptyResourceName) - } - if !IsDeviceName(v1.ResourceName(resourceName)) { - return fmt.Errorf(errInvalidResourceName) - } - return nil -} - -// IsDeviceName returns whether the ResourceName points to an extended resource -// name exported by a device plugin. -func IsDeviceName(k v1.ResourceName) bool { - return v1helper.IsExtendedResourceName(k) -} diff --git a/pkg/kubelet/deviceplugin/utils_test.go b/pkg/kubelet/deviceplugin/utils_test.go deleted file mode 100644 index 33924605c5c..00000000000 --- a/pkg/kubelet/deviceplugin/utils_test.go +++ /dev/null @@ -1,53 +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 deviceplugin - -import ( - "testing" - - "github.com/stretchr/testify/require" - - pluginapi "k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1alpha1" -) - -func TestCloneDevice(t *testing.T) { - d := cloneDevice(&pluginapi.Device{ID: "ADeviceId", Health: pluginapi.Healthy}) - - require.Equal(t, d.ID, "ADeviceId") - require.Equal(t, d.Health, pluginapi.Healthy) -} - -func TestCopyDevices(t *testing.T) { - d := map[string]*pluginapi.Device{ - "ADeviceId": {ID: "ADeviceId", Health: pluginapi.Healthy}, - } - - devs := copyDevices(d) - require.Len(t, devs, 1) -} - -func TestIsResourceName(t *testing.T) { - require.NotNil(t, IsResourceNameValid("")) - require.NotNil(t, IsResourceNameValid("cpu")) - require.NotNil(t, IsResourceNameValid("name1")) - require.NotNil(t, IsResourceNameValid("alpha.kubernetes.io/name1")) - require.NotNil(t, IsResourceNameValid("beta.kubernetes.io/name1")) - require.NotNil(t, IsResourceNameValid("kubernetes.io/name1")) - require.Nil(t, IsResourceNameValid("domain1.io/name1")) - require.Nil(t, IsResourceNameValid("alpha.domain1.io/name1")) - require.Nil(t, IsResourceNameValid("beta.domain1.io/name1")) -} diff --git a/pkg/kubelet/dockershim/BUILD b/pkg/kubelet/dockershim/BUILD index af8c7ea335d..a201ddd57e6 100644 --- a/pkg/kubelet/dockershim/BUILD +++ b/pkg/kubelet/dockershim/BUILD @@ -9,30 +9,71 @@ load( go_library( name = "go_default_library", srcs = [ - "checkpoint_store.go", "convert.go", "doc.go", "docker_checkpoint.go", "docker_container.go", "docker_image.go", - "docker_image_unsupported.go", "docker_sandbox.go", "docker_service.go", - "docker_stats_unsupported.go", "docker_streaming.go", "exec.go", "helpers.go", - "helpers_unsupported.go", "naming.go", "security_context.go", "selinux_util.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "docker_image_unsupported.go", + "docker_stats_unsupported.go", + "helpers_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "docker_image_unsupported.go", + "docker_stats_unsupported.go", + "helpers_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "docker_image_unsupported.go", + "docker_stats_unsupported.go", + "helpers_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "docker_image_unsupported.go", + "docker_stats_unsupported.go", + "helpers_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ "docker_image_linux.go", "docker_stats_linux.go", "helpers_linux.go", ], - "@io_bazel_rules_go//go/platform:windows_amd64": [ + "@io_bazel_rules_go//go/platform:nacl": [ + "docker_image_unsupported.go", + "docker_stats_unsupported.go", + "helpers_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "docker_image_unsupported.go", + "docker_stats_unsupported.go", + "helpers_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "docker_image_unsupported.go", + "docker_stats_unsupported.go", + "helpers_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "docker_image_unsupported.go", + "docker_stats_unsupported.go", + "helpers_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "docker_image_unsupported.go", + "docker_stats_unsupported.go", + "helpers_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ "docker_image_windows.go", "docker_stats_windows.go", "helpers_windows.go", @@ -48,7 +89,6 @@ go_library( "//pkg/kubelet/cm:go_default_library", "//pkg/kubelet/container:go_default_library", "//pkg/kubelet/dockershim/cm:go_default_library", - "//pkg/kubelet/dockershim/errors:go_default_library", "//pkg/kubelet/dockershim/libdocker:go_default_library", "//pkg/kubelet/dockershim/metrics:go_default_library", "//pkg/kubelet/leaky:go_default_library", @@ -61,7 +101,9 @@ go_library( "//pkg/kubelet/types:go_default_library", "//pkg/kubelet/util/cache:go_default_library", "//pkg/kubelet/util/ioutils:go_default_library", + "//pkg/kubelet/util/store:go_default_library", "//pkg/security/apparmor:go_default_library", + "//pkg/util/filesystem:go_default_library", "//pkg/util/hash:go_default_library", "//pkg/util/parsers:go_default_library", "//vendor/github.com/armon/circbuf:go_default_library", @@ -84,7 +126,6 @@ go_library( go_test( name = "go_default_test", srcs = [ - "checkpoint_store_test.go", "convert_test.go", "docker_checkpoint_test.go", "docker_container_test.go", @@ -96,22 +137,19 @@ go_test( "security_context_test.go", "selinux_util_test.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "helpers_linux_test.go", ], "//conditions:default": [], }), data = [ - "fixtures/seccomp/sub/subtest", - "fixtures/seccomp/test", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/dockershim", - library = ":go_default_library", deps = [ "//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library", "//pkg/kubelet/container:go_default_library", "//pkg/kubelet/container/testing:go_default_library", - "//pkg/kubelet/dockershim/errors:go_default_library", "//pkg/kubelet/dockershim/libdocker:go_default_library", "//pkg/kubelet/dockershim/testing:go_default_library", "//pkg/kubelet/network:go_default_library", @@ -143,7 +181,6 @@ filegroup( srcs = [ ":package-srcs", "//pkg/kubelet/dockershim/cm:all-srcs", - "//pkg/kubelet/dockershim/errors:all-srcs", "//pkg/kubelet/dockershim/libdocker:all-srcs", "//pkg/kubelet/dockershim/metrics:all-srcs", "//pkg/kubelet/dockershim/remote:all-srcs", diff --git a/pkg/kubelet/dockershim/checkpoint_store.go b/pkg/kubelet/dockershim/checkpoint_store.go deleted file mode 100644 index b8160bbd9bd..00000000000 --- a/pkg/kubelet/dockershim/checkpoint_store.go +++ /dev/null @@ -1,156 +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 dockershim - -import ( - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "regexp" - "strings" - - "k8s.io/kubernetes/pkg/kubelet/dockershim/errors" -) - -const ( - tmpPrefix = "." - tmpSuffix = ".tmp" - keyMaxLength = 250 -) - -var keyRegex = regexp.MustCompile("^[a-zA-Z0-9]+$") - -// CheckpointStore provides the interface for checkpoint storage backend. -// CheckpointStore must be thread-safe -type CheckpointStore interface { - // key must contain one or more characters in [A-Za-z0-9] - // Write persists a checkpoint with key - Write(key string, data []byte) error - // Read retrieves a checkpoint with key - // Read must return CheckpointNotFoundError if checkpoint is not found - Read(key string) ([]byte, error) - // Delete deletes a checkpoint with key - // Delete must not return error if checkpoint does not exist - Delete(key string) error - // List lists all keys of existing checkpoints - List() ([]string, error) -} - -// FileStore is an implementation of CheckpointStore interface which stores checkpoint in files. -type FileStore struct { - // path to the base directory for storing checkpoint files - path string -} - -func NewFileStore(path string) (CheckpointStore, error) { - if err := ensurePath(path); err != nil { - return nil, err - } - return &FileStore{path: path}, nil -} - -// writeFileAndSync is copied from ioutil.WriteFile, with the extra File.Sync -// at the end to ensure file is written on the disk. -func writeFileAndSync(filename string, data []byte, perm os.FileMode) error { - f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) - if err != nil { - return err - } - n, err := f.Write(data) - if err == nil && n < len(data) { - err = io.ErrShortWrite - } - if err == nil { - // Only sync if the Write completed successfully. - err = f.Sync() - } - if err1 := f.Close(); err == nil { - err = err1 - } - return err -} - -func (fstore *FileStore) Write(key string, data []byte) error { - if err := validateKey(key); err != nil { - return err - } - if err := ensurePath(fstore.path); err != nil { - return err - } - tmpfile := filepath.Join(fstore.path, fmt.Sprintf("%s%s%s", tmpPrefix, key, tmpSuffix)) - if err := writeFileAndSync(tmpfile, data, 0644); err != nil { - return err - } - return os.Rename(tmpfile, fstore.getCheckpointPath(key)) -} - -func (fstore *FileStore) Read(key string) ([]byte, error) { - if err := validateKey(key); err != nil { - return nil, err - } - bytes, err := ioutil.ReadFile(fstore.getCheckpointPath(key)) - if os.IsNotExist(err) { - return bytes, errors.CheckpointNotFoundError - } - return bytes, err -} - -func (fstore *FileStore) Delete(key string) error { - if err := validateKey(key); err != nil { - return err - } - if err := os.Remove(fstore.getCheckpointPath(key)); err != nil && !os.IsNotExist(err) { - return err - } - return nil -} - -func (fstore *FileStore) List() ([]string, error) { - keys := make([]string, 0) - files, err := ioutil.ReadDir(fstore.path) - if err != nil { - return keys, err - } - for _, f := range files { - if !strings.HasPrefix(f.Name(), tmpPrefix) { - keys = append(keys, f.Name()) - } - } - return keys, nil -} - -func (fstore *FileStore) getCheckpointPath(key string) string { - return filepath.Join(fstore.path, key) -} - -// ensurePath creates input directory if it does not exist -func ensurePath(path string) error { - if _, err := os.Stat(path); err != nil { - // MkdirAll returns nil if directory already exists - return os.MkdirAll(path, 0755) - } - return nil -} - -func validateKey(key string) error { - if len(key) <= keyMaxLength && keyRegex.MatchString(key) { - return nil - } - return fmt.Errorf("checkpoint key %q is not valid.", key) -} diff --git a/pkg/kubelet/dockershim/checkpoint_store_test.go b/pkg/kubelet/dockershim/checkpoint_store_test.go deleted file mode 100644 index 97d7a5a239d..00000000000 --- a/pkg/kubelet/dockershim/checkpoint_store_test.go +++ /dev/null @@ -1,158 +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 dockershim - -import ( - "io/ioutil" - "os" - "sort" - "testing" - - "github.com/stretchr/testify/assert" - "k8s.io/kubernetes/pkg/kubelet/dockershim/errors" -) - -func TestFileStore(t *testing.T) { - path, err := ioutil.TempDir("", "FileStore") - assert.NoError(t, err) - defer cleanUpTestPath(t, path) - store, err := NewFileStore(path) - assert.NoError(t, err) - - Checkpoints := []struct { - key string - data string - expectErr bool - }{ - { - "id1", - "data1", - false, - }, - { - "id2", - "data2", - false, - }, - { - "/id1", - "data1", - true, - }, - { - ".id1", - "data1", - true, - }, - { - " ", - "data2", - true, - }, - { - "___", - "data2", - true, - }, - } - - // Test Add Checkpoint - for _, c := range Checkpoints { - _, err = store.Read(c.key) - assert.Error(t, err) - - err = store.Write(c.key, []byte(c.data)) - if c.expectErr { - assert.Error(t, err) - continue - } else { - assert.NoError(t, err) - } - - // Test Read Checkpoint - data, err := store.Read(c.key) - assert.NoError(t, err) - assert.Equal(t, string(data), c.data) - } - - // Test list checkpoints. - keys, err := store.List() - assert.NoError(t, err) - sort.Strings(keys) - assert.Equal(t, keys, []string{"id1", "id2"}) - - // Test Delete Checkpoint - for _, c := range Checkpoints { - if c.expectErr { - continue - } - - err = store.Delete(c.key) - assert.NoError(t, err) - _, err = store.Read(c.key) - assert.EqualValues(t, errors.CheckpointNotFoundError, err) - } - - // Test delete non existed checkpoint - err = store.Delete("id1") - assert.NoError(t, err) - - // Test list checkpoints. - keys, err = store.List() - assert.NoError(t, err) - assert.Equal(t, len(keys), 0) -} - -func TestIsValidKey(t *testing.T) { - testcases := []struct { - key string - valid bool - }{ - { - " ", - false, - }, - { - "/foo/bar", - false, - }, - { - ".foo", - false, - }, - { - "a78768279290d33d0b82eaea43cb8346f500057cb5bd250e88c97a5585385d66", - true, - }, - } - - for _, tc := range testcases { - if tc.valid { - assert.NoError(t, validateKey(tc.key)) - } else { - assert.Error(t, validateKey(tc.key)) - } - } -} - -func cleanUpTestPath(t *testing.T, path string) { - if _, err := os.Stat(path); !os.IsNotExist(err) { - if err := os.RemoveAll(path); err != nil { - assert.NoError(t, err, "Failed to delete test directory: %v", err) - } - } -} diff --git a/pkg/kubelet/dockershim/cm/BUILD b/pkg/kubelet/dockershim/cm/BUILD index fb5661cd50a..871d6f9b1be 100644 --- a/pkg/kubelet/dockershim/cm/BUILD +++ b/pkg/kubelet/dockershim/cm/BUILD @@ -9,22 +9,59 @@ go_library( name = "go_default_library", srcs = [ "container_manager.go", - "container_manager_unsupported.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "container_manager_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "container_manager_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "container_manager_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "container_manager_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ "container_manager_linux.go", ], - "@io_bazel_rules_go//go/platform:windows_amd64": [ + "@io_bazel_rules_go//go/platform:nacl": [ + "container_manager_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "container_manager_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "container_manager_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "container_manager_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "container_manager_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ "container_manager_windows.go", ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/pkg/kubelet/dockershim/cm", - deps = [ - "//pkg/kubelet/dockershim/libdocker:go_default_library", - ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + deps = select({ + "@io_bazel_rules_go//go/platform:android": [ + "//pkg/kubelet/dockershim/libdocker:go_default_library", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "//pkg/kubelet/dockershim/libdocker:go_default_library", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "//pkg/kubelet/dockershim/libdocker:go_default_library", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "//pkg/kubelet/dockershim/libdocker:go_default_library", + ], + "@io_bazel_rules_go//go/platform:linux": [ "//pkg/kubelet/cm:go_default_library", + "//pkg/kubelet/dockershim/libdocker:go_default_library", "//pkg/kubelet/qos:go_default_library", "//pkg/util/version:go_default_library", "//vendor/github.com/golang/glog:go_default_library", @@ -32,6 +69,24 @@ go_library( "//vendor/github.com/opencontainers/runc/libcontainer/configs:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", ], + "@io_bazel_rules_go//go/platform:nacl": [ + "//pkg/kubelet/dockershim/libdocker:go_default_library", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "//pkg/kubelet/dockershim/libdocker:go_default_library", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "//pkg/kubelet/dockershim/libdocker:go_default_library", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "//pkg/kubelet/dockershim/libdocker:go_default_library", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "//pkg/kubelet/dockershim/libdocker:go_default_library", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "//pkg/kubelet/dockershim/libdocker:go_default_library", + ], "//conditions:default": [], }), ) diff --git a/pkg/kubelet/dockershim/cm/container_manager_linux.go b/pkg/kubelet/dockershim/cm/container_manager_linux.go index b820a8126a7..74d00be6622 100644 --- a/pkg/kubelet/dockershim/cm/container_manager_linux.go +++ b/pkg/kubelet/dockershim/cm/container_manager_linux.go @@ -91,7 +91,7 @@ func (m *containerManager) doWork() { glog.Errorf("Unable to parse docker version %q: %v", v.APIVersion, err) return } - // EnsureDockerInConatiner does two things. + // EnsureDockerInContainer does two things. // 1. Ensure processes run in the cgroups if m.cgroupsManager is not nil. // 2. Ensure processes have the OOM score applied. if err := kubecm.EnsureDockerInContainer(version, dockerOOMScoreAdj, m.cgroupsManager); err != nil { diff --git a/pkg/kubelet/dockershim/docker_checkpoint.go b/pkg/kubelet/dockershim/docker_checkpoint.go index 6ad1d794169..f474696995b 100644 --- a/pkg/kubelet/dockershim/docker_checkpoint.go +++ b/pkg/kubelet/dockershim/docker_checkpoint.go @@ -23,7 +23,8 @@ import ( "path/filepath" "github.com/golang/glog" - "k8s.io/kubernetes/pkg/kubelet/dockershim/errors" + utilstore "k8s.io/kubernetes/pkg/kubelet/util/store" + utilfs "k8s.io/kubernetes/pkg/util/filesystem" hashutil "k8s.io/kubernetes/pkg/util/hash" ) @@ -82,11 +83,11 @@ type CheckpointHandler interface { // PersistentCheckpointHandler is an implementation of CheckpointHandler. It persists checkpoint in CheckpointStore type PersistentCheckpointHandler struct { - store CheckpointStore + store utilstore.Store } func NewPersistentCheckpointHandler(dockershimRootDir string) (CheckpointHandler, error) { - fstore, err := NewFileStore(filepath.Join(dockershimRootDir, sandboxCheckpointDir)) + fstore, err := utilstore.NewFileStore(filepath.Join(dockershimRootDir, sandboxCheckpointDir), utilfs.DefaultFs{}) if err != nil { return nil, err } @@ -111,12 +112,14 @@ func (handler *PersistentCheckpointHandler) GetCheckpoint(podSandboxID string) ( //TODO: unmarhsal into a struct with just Version, check version, unmarshal into versioned type. err = json.Unmarshal(blob, &checkpoint) if err != nil { - glog.Errorf("Failed to unmarshal checkpoint %q. Checkpoint content: %q. ErrMsg: %v", podSandboxID, string(blob), err) - return &checkpoint, errors.CorruptCheckpointError + glog.Errorf("Failed to unmarshal checkpoint %q, removing checkpoint. Checkpoint content: %q. ErrMsg: %v", podSandboxID, string(blob), err) + handler.RemoveCheckpoint(podSandboxID) + return nil, fmt.Errorf("failed to unmarshal checkpoint") } if checkpoint.CheckSum != calculateChecksum(checkpoint) { - glog.Errorf("Checksum of checkpoint %q is not valid", podSandboxID) - return &checkpoint, errors.CorruptCheckpointError + glog.Errorf("Checksum of checkpoint %q is not valid, removing checkpoint", podSandboxID) + handler.RemoveCheckpoint(podSandboxID) + return nil, fmt.Errorf("checkpoint is corrupted") } return &checkpoint, nil } diff --git a/pkg/kubelet/dockershim/docker_container.go b/pkg/kubelet/dockershim/docker_container.go index 61401ee1fa4..453c18c7566 100644 --- a/pkg/kubelet/dockershim/docker_container.go +++ b/pkg/kubelet/dockershim/docker_container.go @@ -197,7 +197,7 @@ func (ds *dockerService) createContainerLogSymlink(containerID string) error { path, realPath, containerID, err) } } else { - supported, err := IsCRISupportedLogDriver(ds.client) + supported, err := ds.IsCRISupportedLogDriver() if err != nil { glog.Warningf("Failed to check supported logging driver by CRI: %v", err) return nil diff --git a/pkg/kubelet/dockershim/docker_image.go b/pkg/kubelet/dockershim/docker_image.go index b1171c6f054..1b39522f862 100644 --- a/pkg/kubelet/dockershim/docker_image.go +++ b/pkg/kubelet/dockershim/docker_image.go @@ -21,6 +21,7 @@ import ( "net/http" dockertypes "github.com/docker/docker/api/types" + dockerfilters "github.com/docker/docker/api/types/filters" "github.com/docker/docker/pkg/jsonmessage" runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" @@ -33,8 +34,9 @@ import ( func (ds *dockerService) ListImages(filter *runtimeapi.ImageFilter) ([]*runtimeapi.Image, error) { opts := dockertypes.ImageListOptions{} if filter != nil { - if imgSpec := filter.GetImage(); imgSpec != nil { - opts.Filters.Add("reference", imgSpec.Image) + if filter.GetImage().GetImage() != "" { + opts.Filters = dockerfilters.NewArgs() + opts.Filters.Add("reference", filter.GetImage().GetImage()) } } diff --git a/pkg/kubelet/dockershim/docker_sandbox.go b/pkg/kubelet/dockershim/docker_sandbox.go index 3cc23eee9b7..be478d5e88e 100644 --- a/pkg/kubelet/dockershim/docker_sandbox.go +++ b/pkg/kubelet/dockershim/docker_sandbox.go @@ -30,14 +30,13 @@ import ( utilerrors "k8s.io/apimachinery/pkg/util/errors" runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" - "k8s.io/kubernetes/pkg/kubelet/dockershim/errors" "k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker" "k8s.io/kubernetes/pkg/kubelet/qos" "k8s.io/kubernetes/pkg/kubelet/types" ) const ( - defaultSandboxImage = "gcr.io/google_containers/pause-amd64:3.0" + defaultSandboxImage = "gcr.io/google_containers/pause-amd64:3.1" // Various default sandbox resources requests/limits. defaultSandboxCPUshares int64 = 2 @@ -193,20 +192,10 @@ func (ds *dockerService) StopPodSandbox(podSandboxID string) error { // actions will only have sandbox ID and not have pod namespace and name information. // Return error if encounter any unexpected error. if checkpointErr != nil { - if libdocker.IsContainerNotFoundError(statusErr) && checkpointErr == errors.CheckpointNotFoundError { + if libdocker.IsContainerNotFoundError(statusErr) { glog.Warningf("Both sandbox container and checkpoint for id %q could not be found. "+ "Proceed without further sandbox information.", podSandboxID) } else { - if checkpointErr == errors.CorruptCheckpointError { - // Remove the corrupted checkpoint so that the next - // StopPodSandbox call can proceed. This may indicate that - // some resources won't be reclaimed. - // TODO (#43021): Fix this properly. - glog.Warningf("Removing corrupted checkpoint %q: %+v", podSandboxID, *checkpoint) - if err := ds.checkpointHandler.RemoveCheckpoint(podSandboxID); err != nil { - glog.Warningf("Unable to remove corrupted checkpoint %q: %v", podSandboxID, err) - } - } return utilerrors.NewAggregate([]error{ fmt.Errorf("failed to get checkpoint for sandbox %q: %v", podSandboxID, checkpointErr), fmt.Errorf("failed to get sandbox status: %v", statusErr)}) @@ -239,10 +228,13 @@ func (ds *dockerService) StopPodSandbox(podSandboxID string) error { } } if err := ds.client.StopContainer(podSandboxID, defaultSandboxGracePeriod); err != nil { - glog.Errorf("Failed to stop sandbox %q: %v", podSandboxID, err) // Do not return error if the container does not exist if !libdocker.IsContainerNotFoundError(err) { + glog.Errorf("Failed to stop sandbox %q: %v", podSandboxID, err) errList = append(errList, err) + } else { + // remove the checkpoint for any sandbox that is not found in the runtime + ds.checkpointHandler.RemoveCheckpoint(podSandboxID) } } return utilerrors.NewAggregate(errList) @@ -485,13 +477,6 @@ func (ds *dockerService) ListPodSandbox(filter *runtimeapi.PodSandboxFilter) ([] checkpoint, err := ds.checkpointHandler.GetCheckpoint(id) if err != nil { glog.Errorf("Failed to retrieve checkpoint for sandbox %q: %v", id, err) - - if err == errors.CorruptCheckpointError { - glog.Warningf("Removing corrupted checkpoint %q: %+v", id, *checkpoint) - if err := ds.checkpointHandler.RemoveCheckpoint(id); err != nil { - glog.Warningf("Unable to remove corrupted checkpoint %q: %v", id, err) - } - } continue } result = append(result, checkpointToRuntimeAPISandbox(id, checkpoint)) @@ -502,20 +487,34 @@ func (ds *dockerService) ListPodSandbox(filter *runtimeapi.PodSandboxFilter) ([] // applySandboxLinuxOptions applies LinuxPodSandboxConfig to dockercontainer.HostConfig and dockercontainer.ContainerCreateConfig. func (ds *dockerService) applySandboxLinuxOptions(hc *dockercontainer.HostConfig, lc *runtimeapi.LinuxPodSandboxConfig, createConfig *dockertypes.ContainerCreateConfig, image string, separator rune) error { - // Apply Cgroup options. - cgroupParent, err := ds.GenerateExpectedCgroupParent(lc.CgroupParent) - if err != nil { - return err + if lc == nil { + return nil } - hc.CgroupParent = cgroupParent // Apply security context. - if err = applySandboxSecurityContext(lc, createConfig.Config, hc, ds.network, separator); err != nil { + if err := applySandboxSecurityContext(lc, createConfig.Config, hc, ds.network, separator); err != nil { return err } // Set sysctls. hc.Sysctls = lc.Sysctls + return nil +} +func (ds *dockerService) applySandboxResources(hc *dockercontainer.HostConfig, lc *runtimeapi.LinuxPodSandboxConfig) error { + hc.Resources = dockercontainer.Resources{ + MemorySwap: DefaultMemorySwap(), + CPUShares: defaultSandboxCPUshares, + // Use docker's default cpu quota/period. + } + + if lc != nil { + // Apply Cgroup options. + cgroupParent, err := ds.GenerateExpectedCgroupParent(lc.CgroupParent) + if err != nil { + return err + } + hc.CgroupParent = cgroupParent + } return nil } @@ -548,10 +547,8 @@ func (ds *dockerService) makeSandboxDockerConfig(c *runtimeapi.PodSandboxConfig, } // Apply linux-specific options. - if lc := c.GetLinux(); lc != nil { - if err := ds.applySandboxLinuxOptions(hc, lc, createConfig, image, securityOptSep); err != nil { - return nil, err - } + if err := ds.applySandboxLinuxOptions(hc, c.GetLinux(), createConfig, image, securityOptSep); err != nil { + return nil, err } // Set port mappings. @@ -559,17 +556,12 @@ func (ds *dockerService) makeSandboxDockerConfig(c *runtimeapi.PodSandboxConfig, createConfig.Config.ExposedPorts = exposedPorts hc.PortBindings = portBindings - // Apply resource options. - setSandboxResources(hc) + // TODO: Get rid of the dependency on kubelet internal package. + hc.OomScoreAdj = qos.PodInfraOOMAdj - // Apply cgroupsParent derived from the sandbox config. - if lc := c.GetLinux(); lc != nil { - // Apply Cgroup options. - cgroupParent, err := ds.GenerateExpectedCgroupParent(lc.CgroupParent) - if err != nil { - return nil, fmt.Errorf("failed to generate cgroup parent in expected syntax for container %q: %v", c.Metadata.Name, err) - } - hc.CgroupParent = cgroupParent + // Apply resource options. + if err := ds.applySandboxResources(hc, c.GetLinux()); err != nil { + return nil, err } // Set security options. @@ -608,16 +600,6 @@ func sharesHostIpc(container *dockertypes.ContainerJSON) bool { return false } -func setSandboxResources(hc *dockercontainer.HostConfig) { - hc.Resources = dockercontainer.Resources{ - MemorySwap: DefaultMemorySwap(), - CPUShares: defaultSandboxCPUshares, - // Use docker's default cpu quota/period. - } - // TODO: Get rid of the dependency on kubelet internal package. - hc.OomScoreAdj = qos.PodInfraOOMAdj -} - func constructPodSandboxCheckpoint(config *runtimeapi.PodSandboxConfig) *PodSandboxCheckpoint { checkpoint := NewPodSandboxCheckpoint(config.Metadata.Namespace, config.Metadata.Name) for _, pm := range config.GetPortMappings() { diff --git a/pkg/kubelet/dockershim/docker_service.go b/pkg/kubelet/dockershim/docker_service.go index b7ef158a5d4..6fa1106f4fd 100644 --- a/pkg/kubelet/dockershim/docker_service.go +++ b/pkg/kubelet/dockershim/docker_service.go @@ -38,13 +38,13 @@ import ( kubecm "k8s.io/kubernetes/pkg/kubelet/cm" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/kubelet/dockershim/cm" - "k8s.io/kubernetes/pkg/kubelet/dockershim/errors" "k8s.io/kubernetes/pkg/kubelet/network" "k8s.io/kubernetes/pkg/kubelet/network/cni" "k8s.io/kubernetes/pkg/kubelet/network/hostport" "k8s.io/kubernetes/pkg/kubelet/network/kubenet" "k8s.io/kubernetes/pkg/kubelet/server/streaming" "k8s.io/kubernetes/pkg/kubelet/util/cache" + utilstore "k8s.io/kubernetes/pkg/kubelet/util/store" "k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker" "k8s.io/kubernetes/pkg/kubelet/dockershim/metrics" @@ -148,9 +148,41 @@ type dockerNetworkHost struct { var internalLabelKeys []string = []string{containerTypeLabelKey, containerLogPathLabelKey, sandboxIDLabelKey} +// ClientConfig is parameters used to initialize docker client +type ClientConfig struct { + DockerEndpoint string + RuntimeRequestTimeout time.Duration + ImagePullProgressDeadline time.Duration + + // Configuration for fake docker client + EnableSleep bool + WithTraceDisabled bool +} + +// NewDockerClientFromConfig create a docker client from given configure +// return nil if nil configure is given. +func NewDockerClientFromConfig(config *ClientConfig) libdocker.Interface { + if config != nil { + // Create docker client. + client := libdocker.ConnectToDockerOrDie( + config.DockerEndpoint, + config.RuntimeRequestTimeout, + config.ImagePullProgressDeadline, + config.WithTraceDisabled, + config.EnableSleep, + ) + return client + } + + return nil +} + // NOTE: Anything passed to DockerService should be eventually handled in another way when we switch to running the shim as a different process. -func NewDockerService(client libdocker.Interface, podSandboxImage string, streamingConfig *streaming.Config, +func NewDockerService(config *ClientConfig, podSandboxImage string, streamingConfig *streaming.Config, pluginSettings *NetworkPluginSettings, cgroupsName string, kubeCgroupDriver string, dockershimRootDir string, disableSharedPID bool) (DockerService, error) { + + client := NewDockerClientFromConfig(config) + c := libdocker.NewInstrumentedInterface(client) checkpointHandler, err := NewPersistentCheckpointHandler(dockershimRootDir) if err != nil { @@ -202,6 +234,7 @@ func NewDockerService(client libdocker.Interface, podSandboxImage string, stream // NOTE: cgroup driver is only detectable in docker 1.11+ cgroupDriver := defaultCgroupDriver dockerInfo, err := ds.client.Info() + glog.Infof("Docker Info: %+v", dockerInfo) if err != nil { glog.Errorf("Failed to execute Info() call to the Docker client: %v", err) glog.Warningf("Falling back to use the default driver: %q", cgroupDriver) @@ -237,6 +270,15 @@ type DockerService interface { Start() error // For serving streaming calls. http.Handler + + // IsCRISupportedLogDriver checks whether the logging driver used by docker is + // suppoted by native CRI integration. + // TODO(resouer): remove this when deprecating unsupported log driver + IsCRISupportedLogDriver() (bool, error) + + // NewDockerLegacyService created docker legacy service when log driver is not supported. + // TODO(resouer): remove this when deprecating unsupported log driver + NewDockerLegacyService() DockerLegacyService } type dockerService struct { @@ -324,8 +366,7 @@ func (ds *dockerService) GetPodPortMappings(podSandboxID string) ([]*hostport.Po checkpoint, err := ds.checkpointHandler.GetCheckpoint(podSandboxID) // Return empty portMappings if checkpoint is not found if err != nil { - if err == errors.CheckpointNotFoundError { - glog.Warningf("Failed to retrieve checkpoint for sandbox %q: %v", podSandboxID, err) + if err == utilstore.ErrKeyNotFound { return nil, nil } return nil, err @@ -479,8 +520,10 @@ type dockerLegacyService struct { client libdocker.Interface } -func NewDockerLegacyService(client libdocker.Interface) DockerLegacyService { - return &dockerLegacyService{client: client} +// NewDockerLegacyService created docker legacy service when log driver is not supported. +// TODO(resouer): remove this when deprecating unsupported log driver +func (d *dockerService) NewDockerLegacyService() DockerLegacyService { + return &dockerLegacyService{client: d.client} } // GetContainerLogs get container logs directly from docker daemon. @@ -552,8 +595,8 @@ var criSupportedLogDrivers = []string{"json-file"} // IsCRISupportedLogDriver checks whether the logging driver used by docker is // suppoted by native CRI integration. -func IsCRISupportedLogDriver(client libdocker.Interface) (bool, error) { - info, err := client.Info() +func (d *dockerService) IsCRISupportedLogDriver() (bool, error) { + info, err := d.client.Info() if err != nil { return false, fmt.Errorf("failed to get docker info: %v", err) } diff --git a/pkg/kubelet/dockershim/errors/BUILD b/pkg/kubelet/dockershim/errors/BUILD deleted file mode 100644 index 24142c75e7d..00000000000 --- a/pkg/kubelet/dockershim/errors/BUILD +++ /dev/null @@ -1,25 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["errors.go"], - importpath = "k8s.io/kubernetes/pkg/kubelet/dockershim/errors", -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/pkg/kubelet/dockershim/errors/errors.go b/pkg/kubelet/dockershim/errors/errors.go deleted file mode 100644 index 25462ffd9c8..00000000000 --- a/pkg/kubelet/dockershim/errors/errors.go +++ /dev/null @@ -1,22 +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 errors - -import "fmt" - -var CorruptCheckpointError = fmt.Errorf("checkpoint is corrupted.") -var CheckpointNotFoundError = fmt.Errorf("checkpoint is not found.") diff --git a/pkg/kubelet/dockershim/fixtures/seccomp/sub/subtest b/pkg/kubelet/dockershim/fixtures/seccomp/sub/subtest deleted file mode 100644 index 5dc0574216c..00000000000 --- a/pkg/kubelet/dockershim/fixtures/seccomp/sub/subtest +++ /dev/null @@ -1,3 +0,0 @@ -{ - "abc": "def" -} diff --git a/pkg/kubelet/dockershim/fixtures/seccomp/test b/pkg/kubelet/dockershim/fixtures/seccomp/test deleted file mode 100644 index e63d37b65a8..00000000000 --- a/pkg/kubelet/dockershim/fixtures/seccomp/test +++ /dev/null @@ -1,3 +0,0 @@ -{ - "foo": "bar" -} diff --git a/pkg/kubelet/dockershim/helpers.go b/pkg/kubelet/dockershim/helpers.go index c6f442c78af..595263a840b 100644 --- a/pkg/kubelet/dockershim/helpers.go +++ b/pkg/kubelet/dockershim/helpers.go @@ -223,14 +223,6 @@ func getApparmorSecurityOpts(sc *runtimeapi.LinuxContainerSecurityContext, separ return fmtOpts, nil } -func getNetworkNamespace(c *dockertypes.ContainerJSON) (string, error) { - if c.State.Pid == 0 { - // Docker reports pid 0 for an exited container. - return "", fmt.Errorf("Cannot find network namespace for the terminated container %q", c.ID) - } - return fmt.Sprintf(dockerNetNSFmt, c.State.Pid), nil -} - // dockerFilter wraps around dockerfilters.Args and provides methods to modify // the filter easily. type dockerFilter struct { diff --git a/pkg/kubelet/dockershim/helpers_linux.go b/pkg/kubelet/dockershim/helpers_linux.go index 18a223d755d..f8a9a1fbbe0 100644 --- a/pkg/kubelet/dockershim/helpers_linux.go +++ b/pkg/kubelet/dockershim/helpers_linux.go @@ -62,7 +62,11 @@ func getSeccompDockerOpts(seccompProfile string) ([]dockerOpt, error) { return nil, fmt.Errorf("unknown seccomp profile option: %s", seccompProfile) } - fname := strings.TrimPrefix(seccompProfile, "localhost/") // by pod annotation validation, name is a valid subpath + // get the full path of seccomp profile when prefixed with 'localhost/'. + fname := strings.TrimPrefix(seccompProfile, "localhost/") + if !filepath.IsAbs(fname) { + return nil, fmt.Errorf("seccomp profile path must be absolute, but got relative path %q", fname) + } file, err := ioutil.ReadFile(filepath.FromSlash(fname)) if err != nil { return nil, fmt.Errorf("cannot load seccomp profile %q: %v", fname, err) @@ -133,3 +137,11 @@ func (ds *dockerService) updateCreateConfig( func (ds *dockerService) determinePodIPBySandboxID(uid string) string { return "" } + +func getNetworkNamespace(c *dockertypes.ContainerJSON) (string, error) { + if c.State.Pid == 0 { + // Docker reports pid 0 for an exited container. + return "", fmt.Errorf("cannot find network namespace for the terminated container %q", c.ID) + } + return fmt.Sprintf(dockerNetNSFmt, c.State.Pid), nil +} diff --git a/pkg/kubelet/dockershim/helpers_linux_test.go b/pkg/kubelet/dockershim/helpers_linux_test.go index 7dc8dbe0bde..c7aa670831e 100644 --- a/pkg/kubelet/dockershim/helpers_linux_test.go +++ b/pkg/kubelet/dockershim/helpers_linux_test.go @@ -20,9 +20,13 @@ package dockershim import ( "fmt" + "io/ioutil" + "os" + "path/filepath" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestGetSeccompSecurityOpts(t *testing.T) { @@ -55,26 +59,31 @@ func TestGetSeccompSecurityOpts(t *testing.T) { } func TestLoadSeccompLocalhostProfiles(t *testing.T) { + tmpdir, err := ioutil.TempDir("", "seccomp-local-profile-test") + require.NoError(t, err) + defer os.RemoveAll(tmpdir) + testProfile := `{"foo": "bar"}` + err = ioutil.WriteFile(filepath.Join(tmpdir, "test"), []byte(testProfile), 0644) + require.NoError(t, err) + tests := []struct { msg string seccompProfile string expectedOpts []string expectErr bool }{{ - msg: "Seccomp localhost/test profile", - // We are abusing localhost for loading test seccomp profiles. - // The profile should be an absolute path while we are using a relative one. - seccompProfile: "localhost/fixtures/seccomp/test", + msg: "Seccomp localhost/test profile should return correct seccomp profiles", + seccompProfile: "localhost/" + filepath.Join(tmpdir, "test"), expectedOpts: []string{`seccomp={"foo":"bar"}`}, expectErr: false, }, { - msg: "Seccomp localhost/sub/subtest profile", - seccompProfile: "localhost/fixtures/seccomp/sub/subtest", - expectedOpts: []string{`seccomp={"abc":"def"}`}, - expectErr: false, + msg: "Non-existent profile should return error", + seccompProfile: "localhost/" + filepath.Join(tmpdir, "fixtures/non-existent"), + expectedOpts: nil, + expectErr: true, }, { - msg: "Seccomp non-existent", - seccompProfile: "localhost/fixtures/seccomp/non-existent", + msg: "Relative profile path should return error", + seccompProfile: "localhost/fixtures/test", expectedOpts: nil, expectErr: true, }} diff --git a/pkg/kubelet/dockershim/helpers_unsupported.go b/pkg/kubelet/dockershim/helpers_unsupported.go index db24ae7afb6..dfe3a097a49 100644 --- a/pkg/kubelet/dockershim/helpers_unsupported.go +++ b/pkg/kubelet/dockershim/helpers_unsupported.go @@ -19,6 +19,8 @@ limitations under the License. package dockershim import ( + "fmt" + "github.com/blang/semver" dockertypes "github.com/docker/docker/api/types" "github.com/golang/glog" @@ -47,3 +49,7 @@ func (ds *dockerService) determinePodIPBySandboxID(uid string) string { glog.Warningf("determinePodIPBySandboxID is unsupported in this build") return "" } + +func getNetworkNamespace(c *dockertypes.ContainerJSON) (string, error) { + return "", fmt.Errorf("unsupported platform") +} diff --git a/pkg/kubelet/dockershim/helpers_windows.go b/pkg/kubelet/dockershim/helpers_windows.go index b9ddfd2c649..c93515daab2 100644 --- a/pkg/kubelet/dockershim/helpers_windows.go +++ b/pkg/kubelet/dockershim/helpers_windows.go @@ -47,6 +47,9 @@ func (ds *dockerService) updateCreateConfig( podSandboxID string, securityOptSep rune, apiVersion *semver.Version) error { if networkMode := os.Getenv("CONTAINER_NETWORK"); networkMode != "" { createConfig.HostConfig.NetworkMode = dockercontainer.NetworkMode(networkMode) + } else { + // Todo: Refactor this call in future for calling methods directly in security_context.go + modifyHostNetworkOptionForContainer(false, podSandboxID, createConfig.HostConfig) } return nil @@ -71,14 +74,49 @@ func (ds *dockerService) determinePodIPBySandboxID(sandboxID string) string { if err != nil { continue } - if containerIP := getContainerIP(r); containerIP != "" { - return containerIP + + // Versions and feature support + // ============================ + // Windows version == Windows Server, Version 1709,, Supports both sandbox and non-sandbox case + // Windows version == Windows Server 2016 Support only non-sandbox case + // Windows version < Windows Server 2016 is Not Supported + + // Sandbox support in Windows mandates CNI Plugin. + // Presense of CONTAINER_NETWORK flag is considered as non-Sandbox cases here + + // Todo: Add a kernel version check for more validation + + if networkMode := os.Getenv("CONTAINER_NETWORK"); networkMode == "" { + // Do not return any IP, so that we would continue and get the IP of the Sandbox + ds.getIP(sandboxID, r) + } else { + // On Windows, every container that is created in a Sandbox, needs to invoke CNI plugin again for adding the Network, + // with the shared container name as NetNS info, + // This is passed down to the platform to replicate some necessary information to the new container + + // + // This place is chosen as a hack for now, since getContainerIP would end up calling CNI's addToNetwork + // That is why addToNetwork is required to be idempotent + + // Instead of relying on this call, an explicit call to addToNetwork should be + // done immediately after ContainerCreation, in case of Windows only. TBD Issue # to handle this + + if containerIP := getContainerIP(r); containerIP != "" { + return containerIP + } } } return "" } +func getNetworkNamespace(c *dockertypes.ContainerJSON) (string, error) { + // Currently in windows there is no identifier exposed for network namespace + // Like docker, the referenced container id is used to figure out the network namespace id internally by the platform + // so returning the docker networkMode (which holds container: for network namespace here + return string(c.HostConfig.NetworkMode), nil +} + func getContainerIP(container *dockertypes.ContainerJSON) string { if container.NetworkSettings != nil { for _, network := range container.NetworkSettings.Networks { diff --git a/pkg/kubelet/dockershim/libdocker/BUILD b/pkg/kubelet/dockershim/libdocker/BUILD index 574ca6eca9d..364600ea0b2 100644 --- a/pkg/kubelet/dockershim/libdocker/BUILD +++ b/pkg/kubelet/dockershim/libdocker/BUILD @@ -12,8 +12,8 @@ go_test( "helpers_test.go", "kube_docker_client_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker", - library = ":go_default_library", deps = [ "//vendor/github.com/docker/docker/api/types:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", diff --git a/pkg/kubelet/dockershim/libdocker/client.go b/pkg/kubelet/dockershim/libdocker/client.go index 0b5dcdd8da8..0400bbb9179 100644 --- a/pkg/kubelet/dockershim/libdocker/client.go +++ b/pkg/kubelet/dockershim/libdocker/client.go @@ -40,6 +40,9 @@ const ( // This is only used by GetKubeletDockerContainers(), and should be removed // along with the function. containerNamePrefix = "k8s" + + // Fake docker endpoint + FakeDockerEndpoint = "fake://" ) // Interface is an abstract interface for testability. It abstracts the interface of docker client. @@ -86,9 +89,18 @@ func getDockerClient(dockerEndpoint string) (*dockerapi.Client, error) { // is the timeout for docker requests. If timeout is exceeded, the request // will be cancelled and throw out an error. If requestTimeout is 0, a default // value will be applied. -func ConnectToDockerOrDie(dockerEndpoint string, requestTimeout, imagePullProgressDeadline time.Duration) Interface { - if dockerEndpoint == "fake://" { - return NewFakeDockerClient() +func ConnectToDockerOrDie(dockerEndpoint string, requestTimeout, imagePullProgressDeadline time.Duration, + withTraceDisabled bool, enableSleep bool) Interface { + if dockerEndpoint == FakeDockerEndpoint { + fakeClient := NewFakeDockerClient() + if withTraceDisabled { + fakeClient = fakeClient.WithTraceDisabled() + } + + if enableSleep { + fakeClient.EnableSleep = true + } + return fakeClient } client, err := getDockerClient(dockerEndpoint) if err != nil { diff --git a/pkg/kubelet/dockershim/libdocker/fake_client.go b/pkg/kubelet/dockershim/libdocker/fake_client.go index 529c9141991..21e27eba8fc 100644 --- a/pkg/kubelet/dockershim/libdocker/fake_client.go +++ b/pkg/kubelet/dockershim/libdocker/fake_client.go @@ -410,7 +410,7 @@ func (f *FakeDockerClient) ListContainers(options dockertypes.ContainerListOptio var filtered []dockertypes.Container for _, container := range containerList { for _, statusFilter := range statusFilters { - if container.Status == statusFilter { + if toDockerContainerStatus(container.Status) == statusFilter { filtered = append(filtered, container) break } @@ -443,6 +443,19 @@ func (f *FakeDockerClient) ListContainers(options dockertypes.ContainerListOptio return containerList, err } +func toDockerContainerStatus(state string) string { + switch { + case strings.HasPrefix(state, StatusCreatedPrefix): + return "created" + case strings.HasPrefix(state, StatusRunningPrefix): + return "running" + case strings.HasPrefix(state, StatusExitedPrefix): + return "exited" + default: + return "unknown" + } +} + // InspectContainer is a test-spy implementation of Interface.InspectContainer. // It adds an entry "inspect" to the internal method call record. func (f *FakeDockerClient) InspectContainer(id string) (*dockertypes.ContainerJSON, error) { @@ -565,6 +578,18 @@ func (f *FakeDockerClient) StartContainer(id string) error { } f.appendContainerTrace("Started", id) container, ok := f.ContainerMap[id] + if container.HostConfig.NetworkMode.IsContainer() { + hostContainerID := container.HostConfig.NetworkMode.ConnectedContainer() + found := false + for _, container := range f.RunningContainerList { + if container.ID == hostContainerID { + found = true + } + } + if !found { + return fmt.Errorf("failed to start container \"%s\": Error response from daemon: cannot join network of a non running container: %s", id, hostContainerID) + } + } timestamp := f.Clock.Now() if !ok { container = convertFakeContainer(&FakeContainer{ID: id, Name: id, CreatedAt: timestamp}) @@ -636,6 +661,15 @@ func (f *FakeDockerClient) RemoveContainer(id string, opts dockertypes.Container } } + for i := range f.RunningContainerList { + // allow removal of running containers which are not running + if f.RunningContainerList[i].ID == id && !f.ContainerMap[id].State.Running { + delete(f.ContainerMap, id) + f.RunningContainerList = append(f.RunningContainerList[:i], f.RunningContainerList[i+1:]...) + f.appendContainerTrace("Removed", id) + return nil + } + } // To be a good fake, report error if container is not stopped. return fmt.Errorf("container not stopped") } diff --git a/pkg/kubelet/dockershim/testing/BUILD b/pkg/kubelet/dockershim/testing/BUILD index dc709aa5cd1..0e57c9b081b 100644 --- a/pkg/kubelet/dockershim/testing/BUILD +++ b/pkg/kubelet/dockershim/testing/BUILD @@ -9,7 +9,6 @@ go_library( name = "go_default_library", srcs = ["util.go"], importpath = "k8s.io/kubernetes/pkg/kubelet/dockershim/testing", - deps = ["//pkg/kubelet/dockershim/errors:go_default_library"], ) filegroup( diff --git a/pkg/kubelet/dockershim/testing/util.go b/pkg/kubelet/dockershim/testing/util.go index 02195b0266a..34bbb5303ef 100644 --- a/pkg/kubelet/dockershim/testing/util.go +++ b/pkg/kubelet/dockershim/testing/util.go @@ -17,9 +17,8 @@ limitations under the License. package testing import ( + "fmt" "sync" - - "k8s.io/kubernetes/pkg/kubelet/dockershim/errors" ) // MemStore is an implementation of CheckpointStore interface which stores checkpoint in memory. @@ -44,7 +43,7 @@ func (mstore *MemStore) Read(key string) ([]byte, error) { defer mstore.Unlock() data, ok := mstore.mem[key] if !ok { - return nil, errors.CheckpointNotFoundError + return nil, fmt.Errorf("checkpoint is not found") } return data, nil } diff --git a/pkg/kubelet/envvars/BUILD b/pkg/kubelet/envvars/BUILD index c65ecdd4c83..0583ebdf13d 100644 --- a/pkg/kubelet/envvars/BUILD +++ b/pkg/kubelet/envvars/BUILD @@ -14,7 +14,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/kubelet/envvars", deps = [ - "//pkg/api/v1/helper:go_default_library", + "//pkg/apis/core/v1/helper:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", ], ) diff --git a/pkg/kubelet/envvars/envvars.go b/pkg/kubelet/envvars/envvars.go index 9dcb7ebbb4d..789c20820cd 100644 --- a/pkg/kubelet/envvars/envvars.go +++ b/pkg/kubelet/envvars/envvars.go @@ -23,7 +23,7 @@ import ( "strings" "k8s.io/api/core/v1" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" ) // FromServices builds environment variables that a container is started with, diff --git a/pkg/kubelet/events/event.go b/pkg/kubelet/events/event.go index 5ef68d9b448..6ec06a2ee99 100644 --- a/pkg/kubelet/events/event.go +++ b/pkg/kubelet/events/event.go @@ -52,9 +52,14 @@ const ( FailedDetachVolume = "FailedDetachVolume" FailedMountVolume = "FailedMount" VolumeResizeFailed = "VolumeResizeFailed" + FileSystemResizeFailed = "FileSystemResizeFailed" + FileSystemResizeSuccess = "FileSystemResizeSuccessful" FailedUnMountVolume = "FailedUnMount" + FailedMapVolume = "FailedMapVolume" + FailedUnmapDevice = "FailedUnmapDevice" WarnAlreadyMountedVolume = "AlreadyMountedVolume" SuccessfulDetachVolume = "SuccessfulDetachVolume" + SuccessfulAttachVolume = "SuccessfulAttachVolume" SuccessfulMountVolume = "SuccessfulMountVolume" SuccessfulUnMountVolume = "SuccessfulUnMountVolume" HostPortConflict = "HostPortConflict" diff --git a/pkg/kubelet/eviction/BUILD b/pkg/kubelet/eviction/BUILD index 880cdbf0db6..33c570f3b43 100644 --- a/pkg/kubelet/eviction/BUILD +++ b/pkg/kubelet/eviction/BUILD @@ -12,13 +12,12 @@ go_test( "eviction_manager_test.go", "helpers_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/eviction", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/features:go_default_library", "//pkg/kubelet/apis/stats/v1alpha1:go_default_library", - "//pkg/kubelet/cm:go_default_library", "//pkg/kubelet/eviction/api:go_default_library", "//pkg/kubelet/lifecycle:go_default_library", "//pkg/kubelet/types:go_default_library", @@ -39,18 +38,47 @@ go_library( "doc.go", "eviction_manager.go", "helpers.go", - "threshold_notifier_unsupported.go", "types.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "threshold_notifier_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "threshold_notifier_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "threshold_notifier_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "threshold_notifier_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ "threshold_notifier_linux.go", ], + "@io_bazel_rules_go//go/platform:nacl": [ + "threshold_notifier_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "threshold_notifier_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "threshold_notifier_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "threshold_notifier_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "threshold_notifier_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "threshold_notifier_unsupported.go", + ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/pkg/kubelet/eviction", deps = [ - "//pkg/api/v1/helper/qos:go_default_library", "//pkg/api/v1/resource:go_default_library", + "//pkg/apis/core/v1/helper/qos:go_default_library", "//pkg/features:go_default_library", "//pkg/kubelet/apis/stats/v1alpha1:go_default_library", "//pkg/kubelet/cm:go_default_library", @@ -61,18 +89,17 @@ go_library( "//pkg/kubelet/server/stats:go_default_library", "//pkg/kubelet/types:go_default_library", "//pkg/kubelet/util/format:go_default_library", - "//plugin/pkg/scheduler/util:go_default_library", + "//pkg/scheduler/util:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", "//vendor/k8s.io/client-go/tools/record:go_default_library", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//vendor/golang.org/x/sys/unix:go_default_library", ], "//conditions:default": [], diff --git a/pkg/kubelet/eviction/api/types.go b/pkg/kubelet/eviction/api/types.go index 9e40ac62460..f0e5e416c30 100644 --- a/pkg/kubelet/eviction/api/types.go +++ b/pkg/kubelet/eviction/api/types.go @@ -50,6 +50,23 @@ const ( OpLessThan ThresholdOperator = "LessThan" ) +// OpForSignal maps Signals to ThresholdOperators. +// Today, the only supported operator is "LessThan". This may change in the future, +// for example if "consumed" (as opposed to "available") type signals are added. +// In both cases the directionality of the threshold is implicit to the signal type +// (for a given signal, the decision to evict will be made when crossing the threshold +// from either above or below, never both). There is thus no reason to expose the +// operator in the Kubelet's public API. Instead, we internally map signal types to operators. +var OpForSignal = map[Signal]ThresholdOperator{ + SignalMemoryAvailable: OpLessThan, + SignalNodeFsAvailable: OpLessThan, + SignalNodeFsInodesFree: OpLessThan, + SignalImageFsAvailable: OpLessThan, + SignalImageFsInodesFree: OpLessThan, + SignalAllocatableMemoryAvailable: OpLessThan, + SignalAllocatableNodeFsAvailable: OpLessThan, +} + // ThresholdValue is a value holder that abstracts literal versus percentage based quantity type ThresholdValue struct { // The following fields are exclusive. Only the topmost non-zero field is used. diff --git a/pkg/kubelet/eviction/eviction_manager.go b/pkg/kubelet/eviction/eviction_manager.go index 14596188f6d..90b1038cf15 100644 --- a/pkg/kubelet/eviction/eviction_manager.go +++ b/pkg/kubelet/eviction/eviction_manager.go @@ -30,8 +30,8 @@ import ( "k8s.io/apimachinery/pkg/util/wait" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/tools/record" - v1qos "k8s.io/kubernetes/pkg/api/v1/helper/qos" apiv1resource "k8s.io/kubernetes/pkg/api/v1/resource" + v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos" "k8s.io/kubernetes/pkg/features" statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1" "k8s.io/kubernetes/pkg/kubelet/cm" @@ -233,7 +233,7 @@ func (m *managerImpl) synchronize(diskInfoProvider DiskInfoProvider, podFunc Act activePods := podFunc() // make observations and get a function to derive pod usage stats relative to those observations. - observations, statsFunc, err := makeSignalObservations(m.summaryProvider, capacityProvider, activePods, *m.dedicatedImageFs) + observations, statsFunc, err := makeSignalObservations(m.summaryProvider, capacityProvider, activePods) if err != nil { glog.Errorf("eviction manager: unexpected err: %v", err) return nil diff --git a/pkg/kubelet/eviction/eviction_manager_test.go b/pkg/kubelet/eviction/eviction_manager_test.go index d8474814283..aee07089fc7 100644 --- a/pkg/kubelet/eviction/eviction_manager_test.go +++ b/pkg/kubelet/eviction/eviction_manager_test.go @@ -26,7 +26,8 @@ import ( "k8s.io/apimachinery/pkg/util/clock" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/tools/record" - kubeapi "k8s.io/kubernetes/pkg/api" + kubeapi "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/features" statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1" evictionapi "k8s.io/kubernetes/pkg/kubelet/eviction/api" "k8s.io/kubernetes/pkg/kubelet/lifecycle" @@ -184,15 +185,15 @@ type podToMake struct { // TestMemoryPressure func TestMemoryPressure(t *testing.T) { - enablePodPriority(true) + utilfeature.DefaultFeatureGate.SetFromMap(map[string]bool{string(features.PodPriority): true}) podMaker := makePodWithMemoryStats summaryStatsMaker := makeMemoryStats podsToMake := []podToMake{ - {name: "guaranteed-low-priority-high-usage", priority: lowPriority, requests: newResourceList("100m", "1Gi"), limits: newResourceList("100m", "1Gi"), memoryWorkingSet: "900Mi"}, - {name: "burstable-below-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), memoryWorkingSet: "50Mi"}, - {name: "burstable-above-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), memoryWorkingSet: "400Mi"}, - {name: "best-effort-high-priority-high-usage", priority: highPriority, requests: newResourceList("", ""), limits: newResourceList("", ""), memoryWorkingSet: "400Mi"}, - {name: "best-effort-low-priority-low-usage", priority: lowPriority, requests: newResourceList("", ""), limits: newResourceList("", ""), memoryWorkingSet: "100Mi"}, + {name: "guaranteed-low-priority-high-usage", priority: lowPriority, requests: newResourceList("100m", "1Gi", ""), limits: newResourceList("100m", "1Gi", ""), memoryWorkingSet: "900Mi"}, + {name: "burstable-below-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi", ""), limits: newResourceList("200m", "1Gi", ""), memoryWorkingSet: "50Mi"}, + {name: "burstable-above-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi", ""), limits: newResourceList("200m", "1Gi", ""), memoryWorkingSet: "400Mi"}, + {name: "best-effort-high-priority-high-usage", priority: highPriority, requests: newResourceList("", "", ""), limits: newResourceList("", "", ""), memoryWorkingSet: "400Mi"}, + {name: "best-effort-low-priority-low-usage", priority: lowPriority, requests: newResourceList("", "", ""), limits: newResourceList("", "", ""), memoryWorkingSet: "100Mi"}, } pods := []*v1.Pod{} podStats := map[*v1.Pod]statsapi.PodStats{} @@ -248,8 +249,8 @@ func TestMemoryPressure(t *testing.T) { } // create a best effort pod to test admission - bestEffortPodToAdmit, _ := podMaker("best-admit", defaultPriority, newResourceList("", ""), newResourceList("", ""), "0Gi") - burstablePodToAdmit, _ := podMaker("burst-admit", defaultPriority, newResourceList("100m", "100Mi"), newResourceList("200m", "200Mi"), "0Gi") + bestEffortPodToAdmit, _ := podMaker("best-admit", defaultPriority, newResourceList("", "", ""), newResourceList("", "", ""), "0Gi") + burstablePodToAdmit, _ := podMaker("burst-admit", defaultPriority, newResourceList("100m", "100Mi", ""), newResourceList("200m", "200Mi", ""), "0Gi") // synchronize manager.synchronize(diskInfoProvider, activePodsFunc, capacityProvider) @@ -402,15 +403,18 @@ func parseQuantity(value string) resource.Quantity { } func TestDiskPressureNodeFs(t *testing.T) { - enablePodPriority(true) + utilfeature.DefaultFeatureGate.SetFromMap(map[string]bool{ + string(features.LocalStorageCapacityIsolation): true, + string(features.PodPriority): true, + }) podMaker := makePodWithDiskStats summaryStatsMaker := makeDiskStats podsToMake := []podToMake{ - {name: "low-priority-high-usage", priority: lowPriority, requests: newResourceList("100m", "1Gi"), limits: newResourceList("100m", "1Gi"), rootFsUsed: "900Mi"}, - {name: "below-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), logsFsUsed: "50Mi"}, - {name: "above-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), rootFsUsed: "400Mi"}, - {name: "high-priority-high-usage", priority: highPriority, requests: newResourceList("", ""), limits: newResourceList("", ""), perLocalVolumeUsed: "400Mi"}, - {name: "low-priority-low-usage", priority: lowPriority, requests: newResourceList("", ""), limits: newResourceList("", ""), rootFsUsed: "100Mi"}, + {name: "low-priority-high-usage", priority: lowPriority, requests: newResourceList("100m", "1Gi", ""), limits: newResourceList("100m", "1Gi", ""), rootFsUsed: "900Mi"}, + {name: "below-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi", ""), limits: newResourceList("200m", "1Gi", ""), logsFsUsed: "50Mi"}, + {name: "above-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi", ""), limits: newResourceList("200m", "1Gi", ""), rootFsUsed: "400Mi"}, + {name: "high-priority-high-usage", priority: highPriority, requests: newResourceList("", "", ""), limits: newResourceList("", "", ""), perLocalVolumeUsed: "400Mi"}, + {name: "low-priority-low-usage", priority: lowPriority, requests: newResourceList("", "", ""), limits: newResourceList("", "", ""), rootFsUsed: "100Mi"}, } pods := []*v1.Pod{} podStats := map[*v1.Pod]statsapi.PodStats{} @@ -467,7 +471,7 @@ func TestDiskPressureNodeFs(t *testing.T) { } // create a best effort pod to test admission - podToAdmit, _ := podMaker("pod-to-admit", defaultPriority, newResourceList("", ""), newResourceList("", ""), "0Gi", "0Gi", "0Gi") + podToAdmit, _ := podMaker("pod-to-admit", defaultPriority, newResourceList("", "", ""), newResourceList("", "", ""), "0Gi", "0Gi", "0Gi") // synchronize manager.synchronize(diskInfoProvider, activePodsFunc, capacityProvider) @@ -601,15 +605,15 @@ func TestDiskPressureNodeFs(t *testing.T) { // TestMinReclaim verifies that min-reclaim works as desired. func TestMinReclaim(t *testing.T) { - enablePodPriority(true) + utilfeature.DefaultFeatureGate.SetFromMap(map[string]bool{string(features.PodPriority): true}) podMaker := makePodWithMemoryStats summaryStatsMaker := makeMemoryStats podsToMake := []podToMake{ - {name: "guaranteed-low-priority-high-usage", priority: lowPriority, requests: newResourceList("100m", "1Gi"), limits: newResourceList("100m", "1Gi"), memoryWorkingSet: "900Mi"}, - {name: "burstable-below-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), memoryWorkingSet: "50Mi"}, - {name: "burstable-above-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), memoryWorkingSet: "400Mi"}, - {name: "best-effort-high-priority-high-usage", priority: highPriority, requests: newResourceList("", ""), limits: newResourceList("", ""), memoryWorkingSet: "400Mi"}, - {name: "best-effort-low-priority-low-usage", priority: lowPriority, requests: newResourceList("", ""), limits: newResourceList("", ""), memoryWorkingSet: "100Mi"}, + {name: "guaranteed-low-priority-high-usage", priority: lowPriority, requests: newResourceList("100m", "1Gi", ""), limits: newResourceList("100m", "1Gi", ""), memoryWorkingSet: "900Mi"}, + {name: "burstable-below-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi", ""), limits: newResourceList("200m", "1Gi", ""), memoryWorkingSet: "50Mi"}, + {name: "burstable-above-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi", ""), limits: newResourceList("200m", "1Gi", ""), memoryWorkingSet: "400Mi"}, + {name: "best-effort-high-priority-high-usage", priority: highPriority, requests: newResourceList("", "", ""), limits: newResourceList("", "", ""), memoryWorkingSet: "400Mi"}, + {name: "best-effort-low-priority-low-usage", priority: lowPriority, requests: newResourceList("", "", ""), limits: newResourceList("", "", ""), memoryWorkingSet: "100Mi"}, } pods := []*v1.Pod{} podStats := map[*v1.Pod]statsapi.PodStats{} @@ -741,15 +745,18 @@ func TestMinReclaim(t *testing.T) { } func TestNodeReclaimFuncs(t *testing.T) { - enablePodPriority(true) + utilfeature.DefaultFeatureGate.SetFromMap(map[string]bool{ + string(features.PodPriority): true, + string(features.LocalStorageCapacityIsolation): true, + }) podMaker := makePodWithDiskStats summaryStatsMaker := makeDiskStats podsToMake := []podToMake{ - {name: "low-priority-high-usage", priority: lowPriority, requests: newResourceList("100m", "1Gi"), limits: newResourceList("100m", "1Gi"), rootFsUsed: "900Mi"}, - {name: "below-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), logsFsUsed: "50Mi"}, - {name: "above-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), rootFsUsed: "400Mi"}, - {name: "high-priority-high-usage", priority: highPriority, requests: newResourceList("", ""), limits: newResourceList("", ""), perLocalVolumeUsed: "400Mi"}, - {name: "low-priority-low-usage", priority: lowPriority, requests: newResourceList("", ""), limits: newResourceList("", ""), rootFsUsed: "100Mi"}, + {name: "low-priority-high-usage", priority: lowPriority, requests: newResourceList("100m", "1Gi", ""), limits: newResourceList("100m", "1Gi", ""), rootFsUsed: "900Mi"}, + {name: "below-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi", ""), limits: newResourceList("200m", "1Gi", ""), logsFsUsed: "50Mi"}, + {name: "above-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi", ""), limits: newResourceList("200m", "1Gi", ""), rootFsUsed: "400Mi"}, + {name: "high-priority-high-usage", priority: highPriority, requests: newResourceList("", "", ""), limits: newResourceList("", "", ""), perLocalVolumeUsed: "400Mi"}, + {name: "low-priority-low-usage", priority: lowPriority, requests: newResourceList("", "", ""), limits: newResourceList("", "", ""), rootFsUsed: "100Mi"}, } pods := []*v1.Pod{} podStats := map[*v1.Pod]statsapi.PodStats{} @@ -915,7 +922,7 @@ func TestNodeReclaimFuncs(t *testing.T) { } func TestInodePressureNodeFsInodes(t *testing.T) { - enablePodPriority(true) + utilfeature.DefaultFeatureGate.SetFromMap(map[string]bool{string(features.PodPriority): true}) podMaker := func(name string, priority int32, requests v1.ResourceList, limits v1.ResourceList, rootInodes, logInodes, volumeInodes string) (*v1.Pod, statsapi.PodStats) { pod := newPod(name, priority, []v1.Container{ newContainer(name, requests, limits), @@ -943,11 +950,11 @@ func TestInodePressureNodeFsInodes(t *testing.T) { return result } podsToMake := []podToMake{ - {name: "low-priority-high-usage", priority: lowPriority, requests: newResourceList("100m", "1Gi"), limits: newResourceList("100m", "1Gi"), rootFsInodesUsed: "900Mi"}, - {name: "below-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), rootFsInodesUsed: "50Mi"}, - {name: "above-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), rootFsInodesUsed: "400Mi"}, - {name: "high-priority-high-usage", priority: highPriority, requests: newResourceList("", ""), limits: newResourceList("", ""), rootFsInodesUsed: "400Mi"}, - {name: "low-priority-low-usage", priority: lowPriority, requests: newResourceList("", ""), limits: newResourceList("", ""), rootFsInodesUsed: "100Mi"}, + {name: "low-priority-high-usage", priority: lowPriority, requests: newResourceList("100m", "1Gi", ""), limits: newResourceList("100m", "1Gi", ""), rootFsInodesUsed: "900Mi"}, + {name: "below-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi", ""), limits: newResourceList("200m", "1Gi", ""), rootFsInodesUsed: "50Mi"}, + {name: "above-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi", ""), limits: newResourceList("200m", "1Gi", ""), rootFsInodesUsed: "400Mi"}, + {name: "high-priority-high-usage", priority: highPriority, requests: newResourceList("", "", ""), limits: newResourceList("", "", ""), rootFsInodesUsed: "400Mi"}, + {name: "low-priority-low-usage", priority: lowPriority, requests: newResourceList("", "", ""), limits: newResourceList("", "", ""), rootFsInodesUsed: "100Mi"}, } pods := []*v1.Pod{} podStats := map[*v1.Pod]statsapi.PodStats{} @@ -1004,7 +1011,7 @@ func TestInodePressureNodeFsInodes(t *testing.T) { } // create a best effort pod to test admission - podToAdmit, _ := podMaker("pod-to-admit", defaultPriority, newResourceList("", ""), newResourceList("", ""), "0", "0", "0") + podToAdmit, _ := podMaker("pod-to-admit", defaultPriority, newResourceList("", "", ""), newResourceList("", "", ""), "0", "0", "0") // synchronize manager.synchronize(diskInfoProvider, activePodsFunc, capacityProvider) @@ -1138,11 +1145,11 @@ func TestInodePressureNodeFsInodes(t *testing.T) { // TestCriticalPodsAreNotEvicted func TestCriticalPodsAreNotEvicted(t *testing.T) { - enablePodPriority(true) + utilfeature.DefaultFeatureGate.SetFromMap(map[string]bool{string(features.PodPriority): true}) podMaker := makePodWithMemoryStats summaryStatsMaker := makeMemoryStats podsToMake := []podToMake{ - {name: "critical", priority: defaultPriority, requests: newResourceList("100m", "1Gi"), limits: newResourceList("100m", "1Gi"), memoryWorkingSet: "800Mi"}, + {name: "critical", priority: defaultPriority, requests: newResourceList("100m", "1Gi", ""), limits: newResourceList("100m", "1Gi", ""), memoryWorkingSet: "800Mi"}, } pods := []*v1.Pod{} podStats := map[*v1.Pod]statsapi.PodStats{} @@ -1209,7 +1216,7 @@ func TestCriticalPodsAreNotEvicted(t *testing.T) { } // Enable critical pod annotation feature gate - utilfeature.DefaultFeatureGate.Set("ExperimentalCriticalPodAnnotation=True") + utilfeature.DefaultFeatureGate.SetFromMap(map[string]bool{string(features.ExperimentalCriticalPodAnnotation): true}) // induce soft threshold fakeClock.Step(1 * time.Minute) summaryProvider.result = summaryStatsMaker("1500Mi", podStats) @@ -1254,7 +1261,7 @@ func TestCriticalPodsAreNotEvicted(t *testing.T) { } // Disable critical pod annotation feature gate - utilfeature.DefaultFeatureGate.Set("ExperimentalCriticalPodAnnotation=False") + utilfeature.DefaultFeatureGate.SetFromMap(map[string]bool{string(features.ExperimentalCriticalPodAnnotation): false}) // induce memory pressure! fakeClock.Step(1 * time.Minute) @@ -1274,16 +1281,16 @@ func TestCriticalPodsAreNotEvicted(t *testing.T) { // TestAllocatableMemoryPressure func TestAllocatableMemoryPressure(t *testing.T) { - enablePodPriority(true) + utilfeature.DefaultFeatureGate.SetFromMap(map[string]bool{string(features.PodPriority): true}) podMaker := makePodWithMemoryStats summaryStatsMaker := makeMemoryStats constantCapacity := "4Gi" podsToMake := []podToMake{ - {name: "guaranteed-low-priority-high-usage", priority: lowPriority, requests: newResourceList("100m", "1Gi"), limits: newResourceList("100m", "1Gi"), memoryWorkingSet: "900Mi"}, - {name: "burstable-below-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), memoryWorkingSet: "50Mi"}, - {name: "burstable-above-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), memoryWorkingSet: "400Mi"}, - {name: "best-effort-high-priority-high-usage", priority: highPriority, requests: newResourceList("", ""), limits: newResourceList("", ""), memoryWorkingSet: "400Mi"}, - {name: "best-effort-low-priority-low-usage", priority: lowPriority, requests: newResourceList("", ""), limits: newResourceList("", ""), memoryWorkingSet: "100Mi"}, + {name: "guaranteed-low-priority-high-usage", priority: lowPriority, requests: newResourceList("100m", "1Gi", ""), limits: newResourceList("100m", "1Gi", ""), memoryWorkingSet: "900Mi"}, + {name: "burstable-below-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi", ""), limits: newResourceList("200m", "1Gi", ""), memoryWorkingSet: "50Mi"}, + {name: "burstable-above-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi", ""), limits: newResourceList("200m", "1Gi", ""), memoryWorkingSet: "400Mi"}, + {name: "best-effort-high-priority-high-usage", priority: highPriority, requests: newResourceList("", "", ""), limits: newResourceList("", "", ""), memoryWorkingSet: "400Mi"}, + {name: "best-effort-low-priority-low-usage", priority: lowPriority, requests: newResourceList("", "", ""), limits: newResourceList("", "", ""), memoryWorkingSet: "100Mi"}, } pods := []*v1.Pod{} podStats := map[*v1.Pod]statsapi.PodStats{} @@ -1332,8 +1339,8 @@ func TestAllocatableMemoryPressure(t *testing.T) { } // create a best effort pod to test admission - bestEffortPodToAdmit, _ := podMaker("best-admit", defaultPriority, newResourceList("", ""), newResourceList("", ""), "0Gi") - burstablePodToAdmit, _ := podMaker("burst-admit", defaultPriority, newResourceList("100m", "100Mi"), newResourceList("200m", "200Mi"), "0Gi") + bestEffortPodToAdmit, _ := podMaker("best-admit", defaultPriority, newResourceList("", "", ""), newResourceList("", "", ""), "0Gi") + burstablePodToAdmit, _ := podMaker("burst-admit", defaultPriority, newResourceList("100m", "100Mi", ""), newResourceList("200m", "200Mi", ""), "0Gi") // synchronize manager.synchronize(diskInfoProvider, activePodsFunc, capacityProvider) @@ -1353,7 +1360,7 @@ func TestAllocatableMemoryPressure(t *testing.T) { // induce memory pressure! fakeClock.Step(1 * time.Minute) - pod, podStat := podMaker("guaranteed-high-2", defaultPriority, newResourceList("100m", "1Gi"), newResourceList("100m", "1Gi"), "1Gi") + pod, podStat := podMaker("guaranteed-high-2", defaultPriority, newResourceList("100m", "1Gi", ""), newResourceList("100m", "1Gi", ""), "1Gi") podStats[pod] = podStat summaryProvider.result = summaryStatsMaker(constantCapacity, podStats) manager.synchronize(diskInfoProvider, activePodsFunc, capacityProvider) @@ -1436,297 +1443,3 @@ func TestAllocatableMemoryPressure(t *testing.T) { } } } - -// TestAllocatableNodeFsPressure -func TestAllocatableNodeFsPressure(t *testing.T) { - utilfeature.DefaultFeatureGate.Set("LocalStorageCapacityIsolation=True") - enablePodPriority(true) - podMaker := makePodWithDiskStats - summaryStatsMaker := makeDiskStats - - podsToMake := []podToMake{ - {name: "low-priority-high-usage", priority: lowPriority, requests: newResourceList("100m", "1Gi"), limits: newResourceList("100m", "1Gi"), rootFsUsed: "900Mi"}, - {name: "below-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), logsFsUsed: "50Mi"}, - {name: "above-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "2Gi"), rootFsUsed: "1750Mi"}, - {name: "high-priority-high-usage", priority: highPriority, requests: newResourceList("", ""), limits: newResourceList("", ""), rootFsUsed: "400Mi"}, - {name: "low-priority-low-usage", priority: lowPriority, requests: newResourceList("", ""), limits: newResourceList("", ""), rootFsUsed: "100Mi"}, - } - pods := []*v1.Pod{} - podStats := map[*v1.Pod]statsapi.PodStats{} - for _, podToMake := range podsToMake { - pod, podStat := podMaker(podToMake.name, podToMake.priority, podToMake.requests, podToMake.limits, podToMake.rootFsUsed, podToMake.logsFsUsed, podToMake.perLocalVolumeUsed) - pods = append(pods, pod) - podStats[pod] = podStat - } - podToEvict := pods[0] - activePodsFunc := func() []*v1.Pod { - return pods - } - - fakeClock := clock.NewFakeClock(time.Now()) - podKiller := &mockPodKiller{} - diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false} - capacityProvider := newMockCapacityProvider(newEphemeralStorageResourceList("6Gi", "1000m", "10Gi"), newEphemeralStorageResourceList("1Gi", "1000m", "10Gi")) - diskGC := &mockDiskGC{imageBytesFreed: int64(0), err: nil} - nodeRef := &v1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""} - - config := Config{ - MaxPodGracePeriodSeconds: 5, - PressureTransitionPeriod: time.Minute * 5, - Thresholds: []evictionapi.Threshold{ - { - Signal: evictionapi.SignalAllocatableNodeFsAvailable, - Operator: evictionapi.OpLessThan, - Value: evictionapi.ThresholdValue{ - Quantity: quantityMustParse("1Ki"), - }, - }, - }, - } - summaryProvider := &fakeSummaryProvider{result: summaryStatsMaker("3Gi", "6Gi", podStats)} - manager := &managerImpl{ - clock: fakeClock, - killPodFunc: podKiller.killPodNow, - imageGC: diskGC, - containerGC: diskGC, - config: config, - recorder: &record.FakeRecorder{}, - summaryProvider: summaryProvider, - nodeRef: nodeRef, - nodeConditionsLastObservedAt: nodeConditionsObservedAt{}, - thresholdsFirstObservedAt: thresholdsObservedAt{}, - } - - // create a best effort pod to test admission - bestEffortPodToAdmit, _ := podMaker("best-admit", defaultPriority, newEphemeralStorageResourceList("", "", ""), newEphemeralStorageResourceList("", "", ""), "0Gi", "", "") - burstablePodToAdmit, _ := podMaker("burst-admit", defaultPriority, newEphemeralStorageResourceList("1Gi", "", ""), newEphemeralStorageResourceList("1Gi", "", ""), "1Gi", "", "") - - // synchronize - manager.synchronize(diskInfoProvider, activePodsFunc, capacityProvider) - - // we should not have disk pressure - if manager.IsUnderDiskPressure() { - t.Fatalf("Manager should not report disk pressure") - } - - // try to admit our pods (they should succeed) - expected := []bool{true, true} - for i, pod := range []*v1.Pod{bestEffortPodToAdmit, burstablePodToAdmit} { - if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: pod}); expected[i] != result.Admit { - t.Fatalf("Admit pod: %v, expected: %v, actual: %v", pod, expected[i], result.Admit) - } - } - - // induce disk pressure! - fakeClock.Step(1 * time.Minute) - pod, podStat := podMaker("guaranteed-high-2", defaultPriority, newEphemeralStorageResourceList("2000Mi", "100m", "1Gi"), newEphemeralStorageResourceList("2000Mi", "100m", "1Gi"), "2000Mi", "", "") - podStats[pod] = podStat - pods = append(pods, pod) - summaryProvider.result = summaryStatsMaker("6Gi", "6Gi", podStats) - manager.synchronize(diskInfoProvider, activePodsFunc, capacityProvider) - - // we should have disk pressure - if !manager.IsUnderDiskPressure() { - t.Fatalf("Manager should report disk pressure") - } - - // check the right pod was killed - if podKiller.pod != podToEvict { - t.Fatalf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod, podToEvict.Name) - } - observedGracePeriod := *podKiller.gracePeriodOverride - if observedGracePeriod != int64(0) { - t.Fatalf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", 0, observedGracePeriod) - } - // reset state - podKiller.pod = nil - podKiller.gracePeriodOverride = nil - - // try to admit our pod (should fail) - expected = []bool{false, false} - for i, pod := range []*v1.Pod{bestEffortPodToAdmit, burstablePodToAdmit} { - if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: pod}); expected[i] != result.Admit { - t.Fatalf("Admit pod: %v, expected: %v, actual: %v", pod, expected[i], result.Admit) - } - } - - // reduce disk pressure - fakeClock.Step(1 * time.Minute) - pods[5] = pods[len(pods)-1] - pods = pods[:len(pods)-1] - - // we should have disk pressure (because transition period not yet met) - if !manager.IsUnderDiskPressure() { - t.Fatalf("Manager should report disk pressure") - } - - // try to admit our pod (should fail) - expected = []bool{false, false} - for i, pod := range []*v1.Pod{bestEffortPodToAdmit, burstablePodToAdmit} { - if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: pod}); expected[i] != result.Admit { - t.Fatalf("Admit pod: %v, expected: %v, actual: %v", pod, expected[i], result.Admit) - } - } - - // move the clock past transition period to ensure that we stop reporting pressure - fakeClock.Step(5 * time.Minute) - manager.synchronize(diskInfoProvider, activePodsFunc, capacityProvider) - - // we should not have disk pressure (because transition period met) - if manager.IsUnderDiskPressure() { - t.Fatalf("Manager should not report disk pressure") - } - - // no pod should have been killed - if podKiller.pod != nil { - t.Fatalf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod.Name) - } - - // all pods should admit now - expected = []bool{true, true} - for i, pod := range []*v1.Pod{bestEffortPodToAdmit, burstablePodToAdmit} { - if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: pod}); expected[i] != result.Admit { - t.Fatalf("Admit pod: %v, expected: %v, actual: %v", pod, expected[i], result.Admit) - } - } -} - -func TestNodeReclaimForAllocatableFuncs(t *testing.T) { - utilfeature.DefaultFeatureGate.Set("LocalStorageCapacityIsolation=True") - enablePodPriority(true) - podMaker := makePodWithDiskStats - summaryStatsMaker := makeDiskStats - podsToMake := []podToMake{ - {name: "low-priority-high-usage", priority: lowPriority, requests: newResourceList("100m", "1Gi"), limits: newResourceList("100m", "1Gi"), rootFsUsed: "900Mi"}, - {name: "below-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), logsFsUsed: "50Mi"}, - {name: "above-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "2Gi"), rootFsUsed: "1750Mi"}, - {name: "high-priority-high-usage", priority: highPriority, requests: newResourceList("", ""), limits: newResourceList("", ""), rootFsUsed: "400Mi"}, - {name: "low-priority-low-usage", priority: lowPriority, requests: newResourceList("", ""), limits: newResourceList("", ""), rootFsUsed: "100Mi"}, - } - pods := []*v1.Pod{} - podStats := map[*v1.Pod]statsapi.PodStats{} - for _, podToMake := range podsToMake { - pod, podStat := podMaker(podToMake.name, podToMake.priority, podToMake.requests, podToMake.limits, podToMake.rootFsUsed, podToMake.logsFsUsed, podToMake.perLocalVolumeUsed) - pods = append(pods, pod) - podStats[pod] = podStat - } - podToEvict := pods[0] - activePodsFunc := func() []*v1.Pod { - return pods - } - - fakeClock := clock.NewFakeClock(time.Now()) - podKiller := &mockPodKiller{} - diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false} - capacityProvider := newMockCapacityProvider(newEphemeralStorageResourceList("6Gi", "1000m", "10Gi"), newEphemeralStorageResourceList("1Gi", "1000m", "10Gi")) - imageGcFree := resource.MustParse("800Mi") - diskGC := &mockDiskGC{imageBytesFreed: imageGcFree.Value(), err: nil} - nodeRef := &v1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""} - - config := Config{ - MaxPodGracePeriodSeconds: 5, - PressureTransitionPeriod: time.Minute * 5, - Thresholds: []evictionapi.Threshold{ - { - Signal: evictionapi.SignalAllocatableNodeFsAvailable, - Operator: evictionapi.OpLessThan, - Value: evictionapi.ThresholdValue{ - Quantity: quantityMustParse("10Mi"), - }, - }, - }, - } - summaryProvider := &fakeSummaryProvider{result: summaryStatsMaker("6Gi", "6Gi", podStats)} - manager := &managerImpl{ - clock: fakeClock, - killPodFunc: podKiller.killPodNow, - imageGC: diskGC, - containerGC: diskGC, - config: config, - recorder: &record.FakeRecorder{}, - summaryProvider: summaryProvider, - nodeRef: nodeRef, - nodeConditionsLastObservedAt: nodeConditionsObservedAt{}, - thresholdsFirstObservedAt: thresholdsObservedAt{}, - } - - // synchronize - manager.synchronize(diskInfoProvider, activePodsFunc, capacityProvider) - - // we should not have disk pressure - if manager.IsUnderDiskPressure() { - t.Errorf("Manager should not report disk pressure") - } - - // induce hard threshold - fakeClock.Step(1 * time.Minute) - - pod, podStat := podMaker("guaranteed-high-2", defaultPriority, newEphemeralStorageResourceList("2000Mi", "100m", "1Gi"), newEphemeralStorageResourceList("2000Mi", "100m", "1Gi"), "2000Mi", "", "") - podStats[pod] = podStat - pods = append(pods, pod) - summaryProvider.result = summaryStatsMaker("6Gi", "6Gi", podStats) - manager.synchronize(diskInfoProvider, activePodsFunc, capacityProvider) - - // we should have disk pressure - if !manager.IsUnderDiskPressure() { - t.Fatalf("Manager should report disk pressure since soft threshold was met") - } - - // verify image gc was invoked - if !diskGC.imageGCInvoked || !diskGC.containerGCInvoked { - t.Fatalf("Manager should have invoked image gc") - } - - // verify no pod was killed because image gc was sufficient - if podKiller.pod == nil { - t.Fatalf("Manager should have killed a pod, but not killed") - } - // check the right pod was killed - if podKiller.pod != podToEvict { - t.Fatalf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod, podToEvict.Name) - } - observedGracePeriod := *podKiller.gracePeriodOverride - if observedGracePeriod != int64(0) { - t.Fatalf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", 0, observedGracePeriod) - } - - // reset state - diskGC.imageGCInvoked = false - diskGC.containerGCInvoked = false - podKiller.pod = nil - podKiller.gracePeriodOverride = nil - - // reduce disk pressure - fakeClock.Step(1 * time.Minute) - pods[5] = pods[len(pods)-1] - pods = pods[:len(pods)-1] - - // we should have disk pressure (because transition period not yet met) - if !manager.IsUnderDiskPressure() { - t.Fatalf("Manager should report disk pressure") - } - - // move the clock past transition period to ensure that we stop reporting pressure - fakeClock.Step(5 * time.Minute) - manager.synchronize(diskInfoProvider, activePodsFunc, capacityProvider) - - // we should not have disk pressure (because transition period met) - if manager.IsUnderDiskPressure() { - t.Fatalf("Manager should not report disk pressure") - } - - // no pod should have been killed - if podKiller.pod != nil { - t.Fatalf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod.Name) - } - - // no image gc should have occurred - if diskGC.imageGCInvoked || diskGC.containerGCInvoked { - t.Errorf("Manager chose to perform image gc when it was not neeed") - } - - // no pod should have been killed - if podKiller.pod != nil { - t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod.Name) - } -} diff --git a/pkg/kubelet/eviction/helpers.go b/pkg/kubelet/eviction/helpers.go index 6778ce530db..3ae9c0306a6 100644 --- a/pkg/kubelet/eviction/helpers.go +++ b/pkg/kubelet/eviction/helpers.go @@ -26,14 +26,13 @@ import ( "github.com/golang/glog" "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/features" statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1" - "k8s.io/kubernetes/pkg/kubelet/cm" evictionapi "k8s.io/kubernetes/pkg/kubelet/eviction/api" "k8s.io/kubernetes/pkg/kubelet/server/stats" - schedulerutils "k8s.io/kubernetes/plugin/pkg/scheduler/util" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" + schedulerutils "k8s.io/kubernetes/pkg/scheduler/util" ) const ( @@ -74,13 +73,11 @@ func init() { signalToNodeCondition[evictionapi.SignalNodeFsAvailable] = v1.NodeDiskPressure signalToNodeCondition[evictionapi.SignalImageFsInodesFree] = v1.NodeDiskPressure signalToNodeCondition[evictionapi.SignalNodeFsInodesFree] = v1.NodeDiskPressure - signalToNodeCondition[evictionapi.SignalAllocatableNodeFsAvailable] = v1.NodeDiskPressure // map signals to resources (and vice-versa) signalToResource = map[evictionapi.Signal]v1.ResourceName{} signalToResource[evictionapi.SignalMemoryAvailable] = v1.ResourceMemory signalToResource[evictionapi.SignalAllocatableMemoryAvailable] = v1.ResourceMemory - signalToResource[evictionapi.SignalAllocatableNodeFsAvailable] = resourceNodeFs signalToResource[evictionapi.SignalImageFsAvailable] = resourceImageFs signalToResource[evictionapi.SignalImageFsInodesFree] = resourceImageFsInodes signalToResource[evictionapi.SignalNodeFsAvailable] = resourceNodeFs @@ -101,7 +98,7 @@ func validSignal(signal evictionapi.Signal) bool { } // ParseThresholdConfig parses the flags for thresholds. -func ParseThresholdConfig(allocatableConfig []string, evictionHard, evictionSoft, evictionSoftGracePeriod, evictionMinimumReclaim string) ([]evictionapi.Threshold, error) { +func ParseThresholdConfig(allocatableConfig []string, evictionHard, evictionSoft, evictionSoftGracePeriod, evictionMinimumReclaim map[string]string) ([]evictionapi.Threshold, error) { results := []evictionapi.Threshold{} allocatableThresholds := getAllocatableThreshold(allocatableConfig) results = append(results, allocatableThresholds...) @@ -145,60 +142,34 @@ func ParseThresholdConfig(allocatableConfig []string, evictionHard, evictionSoft } // parseThresholdStatements parses the input statements into a list of Threshold objects. -func parseThresholdStatements(expr string) ([]evictionapi.Threshold, error) { - if len(expr) == 0 { +func parseThresholdStatements(statements map[string]string) ([]evictionapi.Threshold, error) { + if len(statements) == 0 { return nil, nil } results := []evictionapi.Threshold{} - statements := strings.Split(expr, ",") - signalsFound := sets.NewString() - for _, statement := range statements { - result, err := parseThresholdStatement(statement) + for signal, val := range statements { + result, err := parseThresholdStatement(evictionapi.Signal(signal), val) if err != nil { return nil, err } - if signalsFound.Has(string(result.Signal)) { - return nil, fmt.Errorf("found duplicate eviction threshold for signal %v", result.Signal) - } - signalsFound.Insert(string(result.Signal)) results = append(results, result) } return results, nil } // parseThresholdStatement parses a threshold statement. -func parseThresholdStatement(statement string) (evictionapi.Threshold, error) { - tokens2Operator := map[string]evictionapi.ThresholdOperator{ - "<": evictionapi.OpLessThan, - } - var ( - operator evictionapi.ThresholdOperator - parts []string - ) - for token := range tokens2Operator { - parts = strings.Split(statement, token) - // if we got a token, we know this was the operator... - if len(parts) > 1 { - operator = tokens2Operator[token] - break - } - } - if len(operator) == 0 || len(parts) != 2 { - return evictionapi.Threshold{}, fmt.Errorf("invalid eviction threshold syntax %v, expected ", statement) - } - signal := evictionapi.Signal(parts[0]) +func parseThresholdStatement(signal evictionapi.Signal, val string) (evictionapi.Threshold, error) { if !validSignal(signal) { return evictionapi.Threshold{}, fmt.Errorf(unsupportedEvictionSignal, signal) } - - quantityValue := parts[1] - if strings.HasSuffix(quantityValue, "%") { - percentage, err := parsePercentage(quantityValue) + operator := evictionapi.OpForSignal[signal] + if strings.HasSuffix(val, "%") { + percentage, err := parsePercentage(val) if err != nil { return evictionapi.Threshold{}, err } if percentage <= 0 { - return evictionapi.Threshold{}, fmt.Errorf("eviction percentage threshold %v must be positive: %s", signal, quantityValue) + return evictionapi.Threshold{}, fmt.Errorf("eviction percentage threshold %v must be positive: %s", signal, val) } return evictionapi.Threshold{ Signal: signal, @@ -208,7 +179,7 @@ func parseThresholdStatement(statement string) (evictionapi.Threshold, error) { }, }, nil } - quantity, err := resource.ParseQuantity(quantityValue) + quantity, err := resource.ParseQuantity(val) if err != nil { return evictionapi.Threshold{}, err } @@ -227,7 +198,7 @@ func parseThresholdStatement(statement string) (evictionapi.Threshold, error) { // getAllocatableThreshold returns the thresholds applicable for the allocatable configuration func getAllocatableThreshold(allocatableConfig []string) []evictionapi.Threshold { for _, key := range allocatableConfig { - if key == cm.NodeAllocatableEnforcementKey { + if key == kubetypes.NodeAllocatableEnforcementKey { return []evictionapi.Threshold{ { Signal: evictionapi.SignalAllocatableMemoryAvailable, @@ -239,16 +210,6 @@ func getAllocatableThreshold(allocatableConfig []string) []evictionapi.Threshold Quantity: resource.NewQuantity(int64(0), resource.BinarySI), }, }, - { - Signal: evictionapi.SignalAllocatableNodeFsAvailable, - Operator: evictionapi.OpLessThan, - Value: evictionapi.ThresholdValue{ - Quantity: resource.NewQuantity(int64(0), resource.BinarySI), - }, - MinReclaim: &evictionapi.ThresholdValue{ - Quantity: resource.NewQuantity(int64(0), resource.BinarySI), - }, - }, } } } @@ -265,33 +226,22 @@ func parsePercentage(input string) (float32, error) { } // parseGracePeriods parses the grace period statements -func parseGracePeriods(expr string) (map[evictionapi.Signal]time.Duration, error) { - if len(expr) == 0 { +func parseGracePeriods(statements map[string]string) (map[evictionapi.Signal]time.Duration, error) { + if len(statements) == 0 { return nil, nil } results := map[evictionapi.Signal]time.Duration{} - statements := strings.Split(expr, ",") - for _, statement := range statements { - parts := strings.Split(statement, "=") - if len(parts) != 2 { - return nil, fmt.Errorf("invalid eviction grace period syntax %v, expected =", statement) - } - signal := evictionapi.Signal(parts[0]) + for signal, val := range statements { + signal := evictionapi.Signal(signal) if !validSignal(signal) { return nil, fmt.Errorf(unsupportedEvictionSignal, signal) } - - gracePeriod, err := time.ParseDuration(parts[1]) + gracePeriod, err := time.ParseDuration(val) if err != nil { return nil, err } if gracePeriod < 0 { - return nil, fmt.Errorf("invalid eviction grace period specified: %v, must be a positive value", parts[1]) - } - - // check against duplicate statements - if _, found := results[signal]; found { - return nil, fmt.Errorf("duplicate eviction grace period specified for %v", signal) + return nil, fmt.Errorf("invalid eviction grace period specified: %v, must be a positive value", val) } results[signal] = gracePeriod } @@ -299,51 +249,36 @@ func parseGracePeriods(expr string) (map[evictionapi.Signal]time.Duration, error } // parseMinimumReclaims parses the minimum reclaim statements -func parseMinimumReclaims(expr string) (map[evictionapi.Signal]evictionapi.ThresholdValue, error) { - if len(expr) == 0 { +func parseMinimumReclaims(statements map[string]string) (map[evictionapi.Signal]evictionapi.ThresholdValue, error) { + if len(statements) == 0 { return nil, nil } results := map[evictionapi.Signal]evictionapi.ThresholdValue{} - statements := strings.Split(expr, ",") - for _, statement := range statements { - parts := strings.Split(statement, "=") - if len(parts) != 2 { - return nil, fmt.Errorf("invalid eviction minimum reclaim syntax: %v, expected =", statement) - } - signal := evictionapi.Signal(parts[0]) + for signal, val := range statements { + signal := evictionapi.Signal(signal) if !validSignal(signal) { return nil, fmt.Errorf(unsupportedEvictionSignal, signal) } - - quantityValue := parts[1] - if strings.HasSuffix(quantityValue, "%") { - percentage, err := parsePercentage(quantityValue) + if strings.HasSuffix(val, "%") { + percentage, err := parsePercentage(val) if err != nil { return nil, err } if percentage <= 0 { - return nil, fmt.Errorf("eviction percentage minimum reclaim %v must be positive: %s", signal, quantityValue) - } - // check against duplicate statements - if _, found := results[signal]; found { - return nil, fmt.Errorf("duplicate eviction minimum reclaim specified for %v", signal) + return nil, fmt.Errorf("eviction percentage minimum reclaim %v must be positive: %s", signal, val) } results[signal] = evictionapi.ThresholdValue{ Percentage: percentage, } continue } - // check against duplicate statements - if _, found := results[signal]; found { - return nil, fmt.Errorf("duplicate eviction minimum reclaim specified for %v", signal) - } - quantity, err := resource.ParseQuantity(parts[1]) - if quantity.Sign() < 0 { - return nil, fmt.Errorf("negative eviction minimum reclaim specified for %v", signal) - } + quantity, err := resource.ParseQuantity(val) if err != nil { return nil, err } + if quantity.Sign() < 0 { + return nil, fmt.Errorf("negative eviction minimum reclaim specified for %v", signal) + } results[signal] = evictionapi.ThresholdValue{ Quantity: &quantity, } @@ -455,20 +390,12 @@ func podDiskUsage(podStats statsapi.PodStats, pod *v1.Pod, statsToMeasure []fsSt // podMemoryUsage aggregates pod memory usage. func podMemoryUsage(podStats statsapi.PodStats) (v1.ResourceList, error) { - disk := resource.Quantity{Format: resource.BinarySI} memory := resource.Quantity{Format: resource.BinarySI} for _, container := range podStats.Containers { - // disk usage (if known) - for _, fsStats := range []*statsapi.FsStats{container.Rootfs, container.Logs} { - disk.Add(*diskUsage(fsStats)) - } // memory usage (if known) memory.Add(*memoryUsage(container.Memory)) } - return v1.ResourceList{ - v1.ResourceMemory: memory, - resourceDisk: disk, - }, nil + return v1.ResourceList{v1.ResourceMemory: memory}, nil } // localEphemeralVolumeNames returns the set of ephemeral volumes for the pod that are local @@ -608,89 +535,84 @@ func priority(p1, p2 *v1.Pod) int { // exceedMemoryRequests compares whether or not pods' memory usage exceeds their requests func exceedMemoryRequests(stats statsFunc) cmpFunc { return func(p1, p2 *v1.Pod) int { - p1Stats, found := stats(p1) - // if we have no usage stats for p1, we want p2 first - if !found { - return -1 + p1Stats, p1Found := stats(p1) + p2Stats, p2Found := stats(p2) + if !p1Found || !p2Found { + // prioritize evicting the pod for which no stats were found + return cmpBool(!p1Found, !p2Found) } - // if we have no usage stats for p2, but p1 has usage, we want p1 first. - p2Stats, found := stats(p2) - if !found { - return 1 - } - // if we cant get usage for p1 measured, we want p2 first - p1Usage, err := podMemoryUsage(p1Stats) - if err != nil { - return -1 - } - // if we cant get usage for p2 measured, we want p1 first - p2Usage, err := podMemoryUsage(p2Stats) - if err != nil { - return 1 + + p1Usage, p1Err := podMemoryUsage(p1Stats) + p2Usage, p2Err := podMemoryUsage(p2Stats) + if p1Err != nil || p2Err != nil { + // prioritize evicting the pod which had an error getting stats + return cmpBool(p1Err != nil, p2Err != nil) } + p1Memory := p1Usage[v1.ResourceMemory] p2Memory := p2Usage[v1.ResourceMemory] - p1ExceedsRequests := p1Memory.Cmp(podMemoryRequest(p1)) == 1 - p2ExceedsRequests := p2Memory.Cmp(podMemoryRequest(p2)) == 1 - if p1ExceedsRequests == p2ExceedsRequests { - return 0 - } - if p1ExceedsRequests && !p2ExceedsRequests { - // if p1 exceeds its requests, but p2 does not, then we want p2 first - return -1 - } - return 1 + p1ExceedsRequests := p1Memory.Cmp(podRequest(p1, v1.ResourceMemory)) == 1 + p2ExceedsRequests := p2Memory.Cmp(podRequest(p2, v1.ResourceMemory)) == 1 + // prioritize evicting the pod which exceeds its requests + return cmpBool(p1ExceedsRequests, p2ExceedsRequests) } } // memory compares pods by largest consumer of memory relative to request. func memory(stats statsFunc) cmpFunc { return func(p1, p2 *v1.Pod) int { - p1Stats, found := stats(p1) - // if we have no usage stats for p1, we want p2 first - if !found { - return -1 + p1Stats, p1Found := stats(p1) + p2Stats, p2Found := stats(p2) + if !p1Found || !p2Found { + // prioritize evicting the pod for which no stats were found + return cmpBool(!p1Found, !p2Found) } - // if we have no usage stats for p2, but p1 has usage, we want p1 first. - p2Stats, found := stats(p2) - if !found { - return 1 - } - // if we cant get usage for p1 measured, we want p2 first - p1Usage, err := podMemoryUsage(p1Stats) - if err != nil { - return -1 - } - // if we cant get usage for p2 measured, we want p1 first - p2Usage, err := podMemoryUsage(p2Stats) - if err != nil { - return 1 + + p1Usage, p1Err := podMemoryUsage(p1Stats) + p2Usage, p2Err := podMemoryUsage(p2Stats) + if p1Err != nil || p2Err != nil { + // prioritize evicting the pod which had an error getting stats + return cmpBool(p1Err != nil, p2Err != nil) } // adjust p1, p2 usage relative to the request (if any) p1Memory := p1Usage[v1.ResourceMemory] - p1Request := podMemoryRequest(p1) + p1Request := podRequest(p1, v1.ResourceMemory) p1Memory.Sub(p1Request) p2Memory := p2Usage[v1.ResourceMemory] - p2Request := podMemoryRequest(p2) + p2Request := podRequest(p2, v1.ResourceMemory) p2Memory.Sub(p2Request) - // if p2 is using more than p1, we want p2 first + // prioritize evicting the pod which has the larger consumption of memory return p2Memory.Cmp(p1Memory) } } -// podMemoryRequest returns the total memory request of a pod which is the +// podRequest returns the total resource request of a pod which is the // max(sum of init container requests, sum of container requests) -func podMemoryRequest(pod *v1.Pod) resource.Quantity { +func podRequest(pod *v1.Pod, resourceName v1.ResourceName) resource.Quantity { containerValue := resource.Quantity{Format: resource.BinarySI} + if resourceName == resourceDisk && !utilfeature.DefaultFeatureGate.Enabled(features.LocalStorageCapacityIsolation) { + // if the local storage capacity isolation feature gate is disabled, pods request 0 disk + return containerValue + } for i := range pod.Spec.Containers { - containerValue.Add(*pod.Spec.Containers[i].Resources.Requests.Memory()) + switch resourceName { + case v1.ResourceMemory: + containerValue.Add(*pod.Spec.Containers[i].Resources.Requests.Memory()) + case resourceDisk: + containerValue.Add(*pod.Spec.Containers[i].Resources.Requests.StorageEphemeral()) + } } initValue := resource.Quantity{Format: resource.BinarySI} for i := range pod.Spec.InitContainers { - initValue.Add(*pod.Spec.InitContainers[i].Resources.Requests.Memory()) + switch resourceName { + case v1.ResourceMemory: + containerValue.Add(*pod.Spec.Containers[i].Resources.Requests.Memory()) + case resourceDisk: + containerValue.Add(*pod.Spec.Containers[i].Resources.Requests.StorageEphemeral()) + } } if containerValue.Cmp(initValue) > 0 { return containerValue @@ -698,39 +620,71 @@ func podMemoryRequest(pod *v1.Pod) resource.Quantity { return initValue } +// exceedDiskRequests compares whether or not pods' disk usage exceeds their requests +func exceedDiskRequests(stats statsFunc, fsStatsToMeasure []fsStatsType, diskResource v1.ResourceName) cmpFunc { + return func(p1, p2 *v1.Pod) int { + p1Stats, p1Found := stats(p1) + p2Stats, p2Found := stats(p2) + if !p1Found || !p2Found { + // prioritize evicting the pod for which no stats were found + return cmpBool(!p1Found, !p2Found) + } + + p1Usage, p1Err := podDiskUsage(p1Stats, p1, fsStatsToMeasure) + p2Usage, p2Err := podDiskUsage(p2Stats, p2, fsStatsToMeasure) + if p1Err != nil || p2Err != nil { + // prioritize evicting the pod which had an error getting stats + return cmpBool(p1Err != nil, p2Err != nil) + } + + p1Disk := p1Usage[diskResource] + p2Disk := p2Usage[diskResource] + p1ExceedsRequests := p1Disk.Cmp(podRequest(p1, diskResource)) == 1 + p2ExceedsRequests := p2Disk.Cmp(podRequest(p2, diskResource)) == 1 + // prioritize evicting the pod which exceeds its requests + return cmpBool(p1ExceedsRequests, p2ExceedsRequests) + } +} + // disk compares pods by largest consumer of disk relative to request for the specified disk resource. func disk(stats statsFunc, fsStatsToMeasure []fsStatsType, diskResource v1.ResourceName) cmpFunc { return func(p1, p2 *v1.Pod) int { - p1Stats, found := stats(p1) - // if we have no usage stats for p1, we want p2 first - if !found { - return -1 + p1Stats, p1Found := stats(p1) + p2Stats, p2Found := stats(p2) + if !p1Found || !p2Found { + // prioritize evicting the pod for which no stats were found + return cmpBool(!p1Found, !p2Found) } - // if we have no usage stats for p2, but p1 has usage, we want p1 first. - p2Stats, found := stats(p2) - if !found { - return 1 - } - // if we cant get usage for p1 measured, we want p2 first - p1Usage, err := podDiskUsage(p1Stats, p1, fsStatsToMeasure) - if err != nil { - return -1 - } - // if we cant get usage for p2 measured, we want p1 first - p2Usage, err := podDiskUsage(p2Stats, p2, fsStatsToMeasure) - if err != nil { - return 1 + p1Usage, p1Err := podDiskUsage(p1Stats, p1, fsStatsToMeasure) + p2Usage, p2Err := podDiskUsage(p2Stats, p2, fsStatsToMeasure) + if p1Err != nil || p2Err != nil { + // prioritize evicting the pod which had an error getting stats + return cmpBool(p1Err != nil, p2Err != nil) } - // disk is best effort, so we don't measure relative to a request. - // TODO: add disk as a guaranteed resource + // adjust p1, p2 usage relative to the request (if any) p1Disk := p1Usage[diskResource] p2Disk := p2Usage[diskResource] - // if p2 is using more than p1, we want p2 first + p1Request := podRequest(p1, resourceDisk) + p1Disk.Sub(p1Request) + p2Request := podRequest(p2, resourceDisk) + p2Disk.Sub(p2Request) + // prioritize evicting the pod which has the larger consumption of disk return p2Disk.Cmp(p1Disk) } } +// cmpBool compares booleans, placing true before false +func cmpBool(a, b bool) int { + if a == b { + return 0 + } + if !b { + return -1 + } + return 1 +} + // rankMemoryPressure orders the input pods for eviction in response to memory pressure. // It ranks by whether or not the pod's usage exceeds its requests, then by priority, and // finally by memory usage above requests. @@ -741,7 +695,7 @@ func rankMemoryPressure(pods []*v1.Pod, stats statsFunc) { // rankDiskPressureFunc returns a rankFunc that measures the specified fs stats. func rankDiskPressureFunc(fsStatsToMeasure []fsStatsType, diskResource v1.ResourceName) rankFunc { return func(pods []*v1.Pod, stats statsFunc) { - orderedBy(priority, disk(stats, fsStatsToMeasure, diskResource)).Sort(pods) + orderedBy(exceedDiskRequests(stats, fsStatsToMeasure, diskResource), priority, disk(stats, fsStatsToMeasure, diskResource)).Sort(pods) } } @@ -757,7 +711,7 @@ func (a byEvictionPriority) Less(i, j int) bool { } // makeSignalObservations derives observations using the specified summary provider. -func makeSignalObservations(summaryProvider stats.SummaryProvider, capacityProvider CapacityProvider, pods []*v1.Pod, withImageFs bool) (signalObservations, statsFunc, error) { +func makeSignalObservations(summaryProvider stats.SummaryProvider, capacityProvider CapacityProvider, pods []*v1.Pod) (signalObservations, statsFunc, error) { summary, err := summaryProvider.Get() if err != nil { return nil, nil, err @@ -809,11 +763,11 @@ func makeSignalObservations(summaryProvider stats.SummaryProvider, capacityProvi } } - nodeCapacity := capacityProvider.GetCapacity() - allocatableReservation := capacityProvider.GetNodeAllocatableReservation() - - memoryAllocatableCapacity, memoryAllocatableAvailable, exist := getResourceAllocatable(nodeCapacity, allocatableReservation, v1.ResourceMemory) - if exist { + if memoryAllocatableCapacity, ok := capacityProvider.GetCapacity()[v1.ResourceMemory]; ok { + memoryAllocatableAvailable := memoryAllocatableCapacity.Copy() + if reserved, exists := capacityProvider.GetNodeAllocatableReservation()[v1.ResourceMemory]; exists { + memoryAllocatableAvailable.Sub(reserved) + } for _, pod := range summary.Pods { mu, err := podMemoryUsage(pod) if err == nil { @@ -822,55 +776,15 @@ func makeSignalObservations(summaryProvider stats.SummaryProvider, capacityProvi } result[evictionapi.SignalAllocatableMemoryAvailable] = signalObservation{ available: memoryAllocatableAvailable, - capacity: memoryAllocatableCapacity, - } - } - - if utilfeature.DefaultFeatureGate.Enabled(features.LocalStorageCapacityIsolation) { - ephemeralStorageCapacity, ephemeralStorageAllocatable, exist := getResourceAllocatable(nodeCapacity, allocatableReservation, v1.ResourceEphemeralStorage) - if exist { - for _, pod := range pods { - podStat, ok := statsFunc(pod) - if !ok { - continue - } - - fsStatsSet := []fsStatsType{} - if withImageFs { - fsStatsSet = []fsStatsType{fsStatsLogs, fsStatsLocalVolumeSource} - } else { - fsStatsSet = []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource} - } - - usage, err := podDiskUsage(podStat, pod, fsStatsSet) - if err != nil { - glog.Warningf("eviction manager: error getting pod disk usage %v", err) - continue - } - ephemeralStorageAllocatable.Sub(usage[resourceDisk]) - } - result[evictionapi.SignalAllocatableNodeFsAvailable] = signalObservation{ - available: ephemeralStorageAllocatable, - capacity: ephemeralStorageCapacity, - } + capacity: &memoryAllocatableCapacity, } + } else { + glog.Errorf("Could not find capacity information for resource %v", v1.ResourceMemory) } return result, statsFunc, nil } -func getResourceAllocatable(capacity v1.ResourceList, reservation v1.ResourceList, resourceName v1.ResourceName) (*resource.Quantity, *resource.Quantity, bool) { - if capacity, ok := capacity[resourceName]; ok { - allocate := capacity.Copy() - if reserved, exists := reservation[resourceName]; exists { - allocate.Sub(reserved) - } - return capacity.Copy(), allocate, true - } - glog.Errorf("Could not find capacity information for resource %v", resourceName) - return nil, nil, false -} - // thresholdsMet returns the set of thresholds that were met independent of grace period func thresholdsMet(thresholds []evictionapi.Threshold, observations signalObservations, enforceMinReclaim bool) []evictionapi.Threshold { results := []evictionapi.Threshold{} diff --git a/pkg/kubelet/eviction/helpers_test.go b/pkg/kubelet/eviction/helpers_test.go index 448c89a4ba0..5a794112051 100644 --- a/pkg/kubelet/eviction/helpers_test.go +++ b/pkg/kubelet/eviction/helpers_test.go @@ -27,11 +27,11 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/features" statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1" - "k8s.io/kubernetes/pkg/kubelet/cm" evictionapi "k8s.io/kubernetes/pkg/kubelet/eviction/api" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" "k8s.io/kubernetes/pkg/quota" ) @@ -44,28 +44,28 @@ func TestParseThresholdConfig(t *testing.T) { gracePeriod, _ := time.ParseDuration("30s") testCases := map[string]struct { allocatableConfig []string - evictionHard string - evictionSoft string - evictionSoftGracePeriod string - evictionMinReclaim string + evictionHard map[string]string + evictionSoft map[string]string + evictionSoftGracePeriod map[string]string + evictionMinReclaim map[string]string expectErr bool expectThresholds []evictionapi.Threshold }{ "no values": { allocatableConfig: []string{}, - evictionHard: "", - evictionSoft: "", - evictionSoftGracePeriod: "", - evictionMinReclaim: "", + evictionHard: map[string]string{}, + evictionSoft: map[string]string{}, + evictionSoftGracePeriod: map[string]string{}, + evictionMinReclaim: map[string]string{}, expectErr: false, expectThresholds: []evictionapi.Threshold{}, }, - "all flag values": { - allocatableConfig: []string{cm.NodeAllocatableEnforcementKey}, - evictionHard: "memory.available<150Mi", - evictionSoft: "memory.available<300Mi", - evictionSoftGracePeriod: "memory.available=30s", - evictionMinReclaim: "memory.available=0", + "all memory eviction values": { + allocatableConfig: []string{kubetypes.NodeAllocatableEnforcementKey}, + evictionHard: map[string]string{"memory.available": "150Mi"}, + evictionSoft: map[string]string{"memory.available": "300Mi"}, + evictionSoftGracePeriod: map[string]string{"memory.available": "30s"}, + evictionMinReclaim: map[string]string{"memory.available": "0"}, expectErr: false, expectThresholds: []evictionapi.Threshold{ { @@ -78,16 +78,6 @@ func TestParseThresholdConfig(t *testing.T) { Quantity: quantityMustParse("0"), }, }, - { - Signal: evictionapi.SignalAllocatableNodeFsAvailable, - Operator: evictionapi.OpLessThan, - Value: evictionapi.ThresholdValue{ - Quantity: quantityMustParse("0"), - }, - MinReclaim: &evictionapi.ThresholdValue{ - Quantity: quantityMustParse("0"), - }, - }, { Signal: evictionapi.SignalMemoryAvailable, Operator: evictionapi.OpLessThan, @@ -111,12 +101,12 @@ func TestParseThresholdConfig(t *testing.T) { }, }, }, - "all flag values in percentages": { + "all memory eviction values in percentages": { allocatableConfig: []string{}, - evictionHard: "memory.available<10%", - evictionSoft: "memory.available<30%", - evictionSoftGracePeriod: "memory.available=30s", - evictionMinReclaim: "memory.available=5%", + evictionHard: map[string]string{"memory.available": "10%"}, + evictionSoft: map[string]string{"memory.available": "30%"}, + evictionSoftGracePeriod: map[string]string{"memory.available": "30s"}, + evictionMinReclaim: map[string]string{"memory.available": "5%"}, expectErr: false, expectThresholds: []evictionapi.Threshold{ { @@ -142,12 +132,12 @@ func TestParseThresholdConfig(t *testing.T) { }, }, }, - "disk flag values": { + "disk eviction values": { allocatableConfig: []string{}, - evictionHard: "imagefs.available<150Mi,nodefs.available<100Mi", - evictionSoft: "imagefs.available<300Mi,nodefs.available<200Mi", - evictionSoftGracePeriod: "imagefs.available=30s,nodefs.available=30s", - evictionMinReclaim: "imagefs.available=2Gi,nodefs.available=1Gi", + evictionHard: map[string]string{"imagefs.available": "150Mi", "nodefs.available": "100Mi"}, + evictionSoft: map[string]string{"imagefs.available": "300Mi", "nodefs.available": "200Mi"}, + evictionSoftGracePeriod: map[string]string{"imagefs.available": "30s", "nodefs.available": "30s"}, + evictionMinReclaim: map[string]string{"imagefs.available": "2Gi", "nodefs.available": "1Gi"}, expectErr: false, expectThresholds: []evictionapi.Threshold{ { @@ -194,12 +184,12 @@ func TestParseThresholdConfig(t *testing.T) { }, }, }, - "disk flag values in percentages": { + "disk eviction values in percentages": { allocatableConfig: []string{}, - evictionHard: "imagefs.available<15%,nodefs.available<10.5%", - evictionSoft: "imagefs.available<30%,nodefs.available<20.5%", - evictionSoftGracePeriod: "imagefs.available=30s,nodefs.available=30s", - evictionMinReclaim: "imagefs.available=10%,nodefs.available=5%", + evictionHard: map[string]string{"imagefs.available": "15%", "nodefs.available": "10.5%"}, + evictionSoft: map[string]string{"imagefs.available": "30%", "nodefs.available": "20.5%"}, + evictionSoftGracePeriod: map[string]string{"imagefs.available": "30s", "nodefs.available": "30s"}, + evictionMinReclaim: map[string]string{"imagefs.available": "10%", "nodefs.available": "5%"}, expectErr: false, expectThresholds: []evictionapi.Threshold{ { @@ -246,12 +236,12 @@ func TestParseThresholdConfig(t *testing.T) { }, }, }, - "inode flag values": { + "inode eviction values": { allocatableConfig: []string{}, - evictionHard: "imagefs.inodesFree<150Mi,nodefs.inodesFree<100Mi", - evictionSoft: "imagefs.inodesFree<300Mi,nodefs.inodesFree<200Mi", - evictionSoftGracePeriod: "imagefs.inodesFree=30s,nodefs.inodesFree=30s", - evictionMinReclaim: "imagefs.inodesFree=2Gi,nodefs.inodesFree=1Gi", + evictionHard: map[string]string{"imagefs.inodesFree": "150Mi", "nodefs.inodesFree": "100Mi"}, + evictionSoft: map[string]string{"imagefs.inodesFree": "300Mi", "nodefs.inodesFree": "200Mi"}, + evictionSoftGracePeriod: map[string]string{"imagefs.inodesFree": "30s", "nodefs.inodesFree": "30s"}, + evictionMinReclaim: map[string]string{"imagefs.inodesFree": "2Gi", "nodefs.inodesFree": "1Gi"}, expectErr: false, expectThresholds: []evictionapi.Threshold{ { @@ -300,91 +290,73 @@ func TestParseThresholdConfig(t *testing.T) { }, "invalid-signal": { allocatableConfig: []string{}, - evictionHard: "mem.available<150Mi", - evictionSoft: "", - evictionSoftGracePeriod: "", - evictionMinReclaim: "", + evictionHard: map[string]string{"mem.available": "150Mi"}, + evictionSoft: map[string]string{}, + evictionSoftGracePeriod: map[string]string{}, + evictionMinReclaim: map[string]string{}, expectErr: true, expectThresholds: []evictionapi.Threshold{}, }, "hard-signal-negative": { allocatableConfig: []string{}, - evictionHard: "memory.available<-150Mi", - evictionSoft: "", - evictionSoftGracePeriod: "", - evictionMinReclaim: "", + evictionHard: map[string]string{"memory.available": "-150Mi"}, + evictionSoft: map[string]string{}, + evictionSoftGracePeriod: map[string]string{}, + evictionMinReclaim: map[string]string{}, expectErr: true, expectThresholds: []evictionapi.Threshold{}, }, "hard-signal-negative-percentage": { allocatableConfig: []string{}, - evictionHard: "memory.available<-15%", - evictionSoft: "", - evictionSoftGracePeriod: "", - evictionMinReclaim: "", + evictionHard: map[string]string{"memory.available": "-15%"}, + evictionSoft: map[string]string{}, + evictionSoftGracePeriod: map[string]string{}, + evictionMinReclaim: map[string]string{}, expectErr: true, expectThresholds: []evictionapi.Threshold{}, }, "soft-signal-negative": { allocatableConfig: []string{}, - evictionHard: "", - evictionSoft: "memory.available<-150Mi", - evictionSoftGracePeriod: "", - evictionMinReclaim: "", - expectErr: true, - expectThresholds: []evictionapi.Threshold{}, - }, - "duplicate-signal": { - allocatableConfig: []string{}, - evictionHard: "memory.available<150Mi,memory.available<100Mi", - evictionSoft: "", - evictionSoftGracePeriod: "", - evictionMinReclaim: "", + evictionHard: map[string]string{}, + evictionSoft: map[string]string{"memory.available": "-150Mi"}, + evictionSoftGracePeriod: map[string]string{}, + evictionMinReclaim: map[string]string{}, expectErr: true, expectThresholds: []evictionapi.Threshold{}, }, "valid-and-invalid-signal": { allocatableConfig: []string{}, - evictionHard: "memory.available<150Mi,invalid.foo<150Mi", - evictionSoft: "", - evictionSoftGracePeriod: "", - evictionMinReclaim: "", + evictionHard: map[string]string{"memory.available": "150Mi", "invalid.foo": "150Mi"}, + evictionSoft: map[string]string{}, + evictionSoftGracePeriod: map[string]string{}, + evictionMinReclaim: map[string]string{}, expectErr: true, expectThresholds: []evictionapi.Threshold{}, }, "soft-no-grace-period": { allocatableConfig: []string{}, - evictionHard: "", - evictionSoft: "memory.available<150Mi", - evictionSoftGracePeriod: "", - evictionMinReclaim: "", + evictionHard: map[string]string{}, + evictionSoft: map[string]string{"memory.available": "150Mi"}, + evictionSoftGracePeriod: map[string]string{}, + evictionMinReclaim: map[string]string{}, expectErr: true, expectThresholds: []evictionapi.Threshold{}, }, - "soft-neg-grace-period": { + "soft-negative-grace-period": { allocatableConfig: []string{}, - evictionHard: "", - evictionSoft: "memory.available<150Mi", - evictionSoftGracePeriod: "memory.available=-30s", - evictionMinReclaim: "", + evictionHard: map[string]string{}, + evictionSoft: map[string]string{"memory.available": "150Mi"}, + evictionSoftGracePeriod: map[string]string{"memory.available": "-30s"}, + evictionMinReclaim: map[string]string{}, expectErr: true, expectThresholds: []evictionapi.Threshold{}, }, - "neg-reclaim": { + "negative-reclaim": { allocatableConfig: []string{}, - evictionHard: "", - evictionSoft: "", - evictionSoftGracePeriod: "", - evictionMinReclaim: "memory.available=-300Mi", - expectErr: true, - expectThresholds: []evictionapi.Threshold{}, - }, - "duplicate-reclaim": { - allocatableConfig: []string{}, - evictionHard: "", - evictionSoft: "", - evictionSoftGracePeriod: "", - evictionMinReclaim: "memory.available=-300Mi,memory.available=-100Mi", + evictionHard: map[string]string{}, + evictionSoft: map[string]string{}, + evictionSoftGracePeriod: map[string]string{}, + evictionMinReclaim: map[string]string{"memory.available": "-300Mi"}, expectErr: true, expectThresholds: []evictionapi.Threshold{}, }, @@ -437,17 +409,71 @@ func thresholdEqual(a evictionapi.Threshold, b evictionapi.Threshold) bool { compareThresholdValue(a.Value, b.Value) } -// TestOrderedByPriority ensures we order BestEffort < Burstable < Guaranteed +func TestOrderedByExceedsRequestMemory(t *testing.T) { + utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%s=true", features.PodPriority)) + below := newPod("below-requests", -1, []v1.Container{ + newContainer("below-requests", newResourceList("", "200Mi", ""), newResourceList("", "", "")), + }, nil) + exceeds := newPod("exceeds-requests", 1, []v1.Container{ + newContainer("exceeds-requests", newResourceList("", "100Mi", ""), newResourceList("", "", "")), + }, nil) + stats := map[*v1.Pod]statsapi.PodStats{ + below: newPodMemoryStats(below, resource.MustParse("199Mi")), // -1 relative to request + exceeds: newPodMemoryStats(exceeds, resource.MustParse("101Mi")), // 1 relative to request + } + statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) { + result, found := stats[pod] + return result, found + } + pods := []*v1.Pod{below, exceeds} + orderedBy(exceedMemoryRequests(statsFn)).Sort(pods) + + expected := []*v1.Pod{exceeds, below} + for i := range expected { + if pods[i] != expected[i] { + t.Errorf("Expected pod: %s, but got: %s", expected[i].Name, pods[i].Name) + } + } +} + +func TestOrderedByExceedsRequestDisk(t *testing.T) { + utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%s=true", features.PodPriority)) + utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%s=true", features.LocalStorageCapacityIsolation)) + below := newPod("below-requests", -1, []v1.Container{ + newContainer("below-requests", v1.ResourceList{v1.ResourceEphemeralStorage: resource.MustParse("200Mi")}, newResourceList("", "", "")), + }, nil) + exceeds := newPod("exceeds-requests", 1, []v1.Container{ + newContainer("exceeds-requests", v1.ResourceList{v1.ResourceEphemeralStorage: resource.MustParse("100Mi")}, newResourceList("", "", "")), + }, nil) + stats := map[*v1.Pod]statsapi.PodStats{ + below: newPodDiskStats(below, resource.MustParse("100Mi"), resource.MustParse("99Mi"), resource.MustParse("0Mi")), // -1 relative to request + exceeds: newPodDiskStats(exceeds, resource.MustParse("90Mi"), resource.MustParse("11Mi"), resource.MustParse("0Mi")), // 1 relative to request + } + statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) { + result, found := stats[pod] + return result, found + } + pods := []*v1.Pod{below, exceeds} + orderedBy(exceedDiskRequests(statsFn, []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, resourceDisk)).Sort(pods) + + expected := []*v1.Pod{exceeds, below} + for i := range expected { + if pods[i] != expected[i] { + t.Errorf("Expected pod: %s, but got: %s", expected[i].Name, pods[i].Name) + } + } +} + func TestOrderedByPriority(t *testing.T) { - enablePodPriority(true) + utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%s=true", features.PodPriority)) low := newPod("low-priority", -134, []v1.Container{ - newContainer("low-priority", newResourceList("", ""), newResourceList("", "")), + newContainer("low-priority", newResourceList("", "", ""), newResourceList("", "", "")), }, nil) medium := newPod("medium-priority", 1, []v1.Container{ - newContainer("medium-priority", newResourceList("100m", "100Mi"), newResourceList("200m", "200Mi")), + newContainer("medium-priority", newResourceList("100m", "100Mi", ""), newResourceList("200m", "200Mi", "")), }, nil) high := newPod("high-priority", 12534, []v1.Container{ - newContainer("high-priority", newResourceList("200m", "200Mi"), newResourceList("200m", "200Mi")), + newContainer("high-priority", newResourceList("200m", "200Mi", ""), newResourceList("200m", "200Mi", "")), }, nil) pods := []*v1.Pod{high, medium, low} @@ -461,17 +487,16 @@ func TestOrderedByPriority(t *testing.T) { } } -// TestOrderedByPriority ensures we order BestEffort < Burstable < Guaranteed func TestOrderedByPriorityDisabled(t *testing.T) { - enablePodPriority(false) + utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%s=false", features.PodPriority)) low := newPod("low-priority", lowPriority, []v1.Container{ - newContainer("low-priority", newResourceList("", ""), newResourceList("", "")), + newContainer("low-priority", newResourceList("", "", ""), newResourceList("", "", "")), }, nil) medium := newPod("medium-priority", defaultPriority, []v1.Container{ - newContainer("medium-priority", newResourceList("100m", "100Mi"), newResourceList("200m", "200Mi")), + newContainer("medium-priority", newResourceList("100m", "100Mi", ""), newResourceList("200m", "200Mi", "")), }, nil) high := newPod("high-priority", highPriority, []v1.Container{ - newContainer("high-priority", newResourceList("200m", "200Mi"), newResourceList("200m", "200Mi")), + newContainer("high-priority", newResourceList("200m", "200Mi", ""), newResourceList("200m", "200Mi", "")), }, nil) pods := []*v1.Pod{high, medium, low} @@ -487,74 +512,64 @@ func TestOrderedByPriorityDisabled(t *testing.T) { } func TestOrderedbyDisk(t *testing.T) { - testOrderedByResource(t, resourceDisk, newPodDiskStats) -} - -func TestOrderedbyInodes(t *testing.T) { - testOrderedByResource(t, resourceInodes, newPodInodeStats) -} - -// testOrderedByDisk ensures we order pods by greediest resource consumer -func testOrderedByResource(t *testing.T, orderedByResource v1.ResourceName, - newPodStatsFunc func(pod *v1.Pod, rootFsUsed, logsUsed, perLocalVolumeUsed resource.Quantity) statsapi.PodStats) { - enablePodPriority(true) + utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%s=true", features.LocalStorageCapacityIsolation)) pod1 := newPod("best-effort-high", defaultPriority, []v1.Container{ - newContainer("best-effort-high", newResourceList("", ""), newResourceList("", "")), + newContainer("best-effort-high", newResourceList("", "", ""), newResourceList("", "", "")), }, []v1.Volume{ newVolume("local-volume", v1.VolumeSource{ EmptyDir: &v1.EmptyDirVolumeSource{}, }), }) pod2 := newPod("best-effort-low", defaultPriority, []v1.Container{ - newContainer("best-effort-low", newResourceList("", ""), newResourceList("", "")), + newContainer("best-effort-low", newResourceList("", "", ""), newResourceList("", "", "")), }, []v1.Volume{ newVolume("local-volume", v1.VolumeSource{ EmptyDir: &v1.EmptyDirVolumeSource{}, }), }) pod3 := newPod("burstable-high", defaultPriority, []v1.Container{ - newContainer("burstable-high", newResourceList("100m", "100Mi"), newResourceList("200m", "1Gi")), + newContainer("burstable-high", newResourceList("", "", "100Mi"), newResourceList("", "", "400Mi")), }, []v1.Volume{ newVolume("local-volume", v1.VolumeSource{ EmptyDir: &v1.EmptyDirVolumeSource{}, }), }) pod4 := newPod("burstable-low", defaultPriority, []v1.Container{ - newContainer("burstable-low", newResourceList("100m", "100Mi"), newResourceList("200m", "1Gi")), + newContainer("burstable-low", newResourceList("", "", "100Mi"), newResourceList("", "", "400Mi")), }, []v1.Volume{ newVolume("local-volume", v1.VolumeSource{ EmptyDir: &v1.EmptyDirVolumeSource{}, }), }) pod5 := newPod("guaranteed-high", defaultPriority, []v1.Container{ - newContainer("guaranteed-high", newResourceList("100m", "1Gi"), newResourceList("100m", "1Gi")), + newContainer("guaranteed-high", newResourceList("", "", "400Mi"), newResourceList("", "", "400Mi")), }, []v1.Volume{ newVolume("local-volume", v1.VolumeSource{ EmptyDir: &v1.EmptyDirVolumeSource{}, }), }) pod6 := newPod("guaranteed-low", defaultPriority, []v1.Container{ - newContainer("guaranteed-low", newResourceList("100m", "1Gi"), newResourceList("100m", "1Gi")), + newContainer("guaranteed-low", newResourceList("", "", "400Mi"), newResourceList("", "", "400Mi")), }, []v1.Volume{ newVolume("local-volume", v1.VolumeSource{ EmptyDir: &v1.EmptyDirVolumeSource{}, }), }) stats := map[*v1.Pod]statsapi.PodStats{ - pod1: newPodStatsFunc(pod1, resource.MustParse("50Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 200Mi - pod2: newPodStatsFunc(pod2, resource.MustParse("100Mi"), resource.MustParse("150Mi"), resource.MustParse("50Mi")), // 300Mi - pod3: newPodStatsFunc(pod3, resource.MustParse("200Mi"), resource.MustParse("150Mi"), resource.MustParse("50Mi")), // 400Mi - pod4: newPodStatsFunc(pod4, resource.MustParse("300Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 450Mi - pod5: newPodStatsFunc(pod5, resource.MustParse("400Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 550Mi - pod6: newPodStatsFunc(pod6, resource.MustParse("500Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 650Mi + pod1: newPodDiskStats(pod1, resource.MustParse("50Mi"), resource.MustParse("100Mi"), resource.MustParse("150Mi")), // 300Mi - 0 = 300Mi + pod2: newPodDiskStats(pod2, resource.MustParse("25Mi"), resource.MustParse("25Mi"), resource.MustParse("50Mi")), // 100Mi - 0 = 100Mi + pod3: newPodDiskStats(pod3, resource.MustParse("150Mi"), resource.MustParse("150Mi"), resource.MustParse("50Mi")), // 350Mi - 100Mi = 250Mi + pod4: newPodDiskStats(pod4, resource.MustParse("25Mi"), resource.MustParse("35Mi"), resource.MustParse("50Mi")), // 110Mi - 100Mi = 10Mi + pod5: newPodDiskStats(pod5, resource.MustParse("225Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 375Mi - 400Mi = -25Mi + pod6: newPodDiskStats(pod6, resource.MustParse("25Mi"), resource.MustParse("45Mi"), resource.MustParse("50Mi")), // 120Mi - 400Mi = -280Mi } statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) { result, found := stats[pod] return result, found } pods := []*v1.Pod{pod1, pod2, pod3, pod4, pod5, pod6} - orderedBy(disk(statsFn, []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, orderedByResource)).Sort(pods) - expected := []*v1.Pod{pod6, pod5, pod4, pod3, pod2, pod1} + orderedBy(disk(statsFn, []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, resourceDisk)).Sort(pods) + expected := []*v1.Pod{pod1, pod3, pod2, pod4, pod5, pod6} for i := range expected { if pods[i] != expected[i] { t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name) @@ -562,58 +577,244 @@ func testOrderedByResource(t *testing.T, orderedByResource v1.ResourceName, } } -func TestOrderedbyPriorityDisk(t *testing.T) { - testOrderedByPriorityResource(t, resourceDisk, newPodDiskStats) -} - -func TestOrderedbyPriorityInodes(t *testing.T) { - testOrderedByPriorityResource(t, resourceInodes, newPodInodeStats) -} - -// testOrderedByPriorityDisk ensures we order pods by qos and then greediest resource consumer -func testOrderedByPriorityResource(t *testing.T, orderedByResource v1.ResourceName, - newPodStatsFunc func(pod *v1.Pod, rootFsUsed, logsUsed, perLocalVolumeUsed resource.Quantity) statsapi.PodStats) { - enablePodPriority(true) - pod1 := newPod("low-priority-high-usage", lowPriority, []v1.Container{ - newContainer("low-priority-high-usage", newResourceList("", ""), newResourceList("", "")), +// Tests that we correctly ignore disk requests when the local storage feature gate is disabled. +func TestOrderedbyDiskDisableLocalStorage(t *testing.T) { + utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%s=false", features.LocalStorageCapacityIsolation)) + pod1 := newPod("best-effort-high", defaultPriority, []v1.Container{ + newContainer("best-effort-high", newResourceList("", "", ""), newResourceList("", "", "")), }, []v1.Volume{ newVolume("local-volume", v1.VolumeSource{ EmptyDir: &v1.EmptyDirVolumeSource{}, }), }) - pod2 := newPod("low-priority-low-usage", lowPriority, []v1.Container{ - newContainer("low-priority-low-usage", newResourceList("", ""), newResourceList("", "")), + pod2 := newPod("best-effort-low", defaultPriority, []v1.Container{ + newContainer("best-effort-low", newResourceList("", "", ""), newResourceList("", "", "")), }, []v1.Volume{ newVolume("local-volume", v1.VolumeSource{ EmptyDir: &v1.EmptyDirVolumeSource{}, }), }) - pod3 := newPod("high-priority-high-usage", highPriority, []v1.Container{ - newContainer("high-priority-high-usage", newResourceList("100m", "100Mi"), newResourceList("200m", "1Gi")), + pod3 := newPod("burstable-high", defaultPriority, []v1.Container{ + newContainer("burstable-high", newResourceList("", "", "100Mi"), newResourceList("", "", "400Mi")), }, []v1.Volume{ newVolume("local-volume", v1.VolumeSource{ EmptyDir: &v1.EmptyDirVolumeSource{}, }), }) - pod4 := newPod("high-priority-low-usage", highPriority, []v1.Container{ - newContainer("high-priority-low-usage", newResourceList("100m", "100Mi"), newResourceList("200m", "1Gi")), + pod4 := newPod("burstable-low", defaultPriority, []v1.Container{ + newContainer("burstable-low", newResourceList("", "", "100Mi"), newResourceList("", "", "400Mi")), + }, []v1.Volume{ + newVolume("local-volume", v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }), + }) + pod5 := newPod("guaranteed-high", defaultPriority, []v1.Container{ + newContainer("guaranteed-high", newResourceList("", "", "400Mi"), newResourceList("", "", "400Mi")), + }, []v1.Volume{ + newVolume("local-volume", v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }), + }) + pod6 := newPod("guaranteed-low", defaultPriority, []v1.Container{ + newContainer("guaranteed-low", newResourceList("", "", "400Mi"), newResourceList("", "", "400Mi")), }, []v1.Volume{ newVolume("local-volume", v1.VolumeSource{ EmptyDir: &v1.EmptyDirVolumeSource{}, }), }) stats := map[*v1.Pod]statsapi.PodStats{ - pod1: newPodStatsFunc(pod1, resource.MustParse("50Mi"), resource.MustParse("100Mi"), resource.MustParse("250Mi")), // 400Mi - pod2: newPodStatsFunc(pod2, resource.MustParse("60Mi"), resource.MustParse("30Mi"), resource.MustParse("10Mi")), // 100Mi - pod3: newPodStatsFunc(pod3, resource.MustParse("150Mi"), resource.MustParse("150Mi"), resource.MustParse("50Mi")), // 350Mi - pod4: newPodStatsFunc(pod4, resource.MustParse("10Mi"), resource.MustParse("40Mi"), resource.MustParse("100Mi")), // 150Mi + pod1: newPodDiskStats(pod1, resource.MustParse("50Mi"), resource.MustParse("100Mi"), resource.MustParse("150Mi")), // 300Mi + pod2: newPodDiskStats(pod2, resource.MustParse("25Mi"), resource.MustParse("25Mi"), resource.MustParse("50Mi")), // 100Mi + pod3: newPodDiskStats(pod3, resource.MustParse("150Mi"), resource.MustParse("150Mi"), resource.MustParse("50Mi")), // 350Mi + pod4: newPodDiskStats(pod4, resource.MustParse("25Mi"), resource.MustParse("35Mi"), resource.MustParse("50Mi")), // 110Mi + pod5: newPodDiskStats(pod5, resource.MustParse("225Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 375Mi + pod6: newPodDiskStats(pod6, resource.MustParse("25Mi"), resource.MustParse("45Mi"), resource.MustParse("50Mi")), // 120Mi + } + statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) { + result, found := stats[pod] + return result, found + } + pods := []*v1.Pod{pod1, pod3, pod2, pod4, pod5, pod6} + orderedBy(disk(statsFn, []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, resourceDisk)).Sort(pods) + expected := []*v1.Pod{pod5, pod3, pod1, pod6, pod4, pod2} + for i := range expected { + if pods[i] != expected[i] { + t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name) + } + } +} + +func TestOrderedbyInodes(t *testing.T) { + utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%s=true", features.PodPriority)) + utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%s=true", features.LocalStorageCapacityIsolation)) + low := newPod("low", defaultPriority, []v1.Container{ + newContainer("low", newResourceList("", "", ""), newResourceList("", "", "")), + }, []v1.Volume{ + newVolume("local-volume", v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }), + }) + medium := newPod("medium", defaultPriority, []v1.Container{ + newContainer("medium", newResourceList("", "", ""), newResourceList("", "", "")), + }, []v1.Volume{ + newVolume("local-volume", v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }), + }) + high := newPod("high", defaultPriority, []v1.Container{ + newContainer("high", newResourceList("", "", ""), newResourceList("", "", "")), + }, []v1.Volume{ + newVolume("local-volume", v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }), + }) + stats := map[*v1.Pod]statsapi.PodStats{ + low: newPodInodeStats(low, resource.MustParse("50000"), resource.MustParse("100000"), resource.MustParse("50000")), // 200000 + medium: newPodInodeStats(medium, resource.MustParse("100000"), resource.MustParse("150000"), resource.MustParse("50000")), // 300000 + high: newPodInodeStats(high, resource.MustParse("200000"), resource.MustParse("150000"), resource.MustParse("50000")), // 400000 + } + statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) { + result, found := stats[pod] + return result, found + } + pods := []*v1.Pod{low, medium, high} + orderedBy(disk(statsFn, []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, resourceInodes)).Sort(pods) + expected := []*v1.Pod{high, medium, low} + for i := range expected { + if pods[i] != expected[i] { + t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name) + } + } +} + +// TestOrderedByPriorityDisk ensures we order pods by priority and then greediest resource consumer +func TestOrderedByPriorityDisk(t *testing.T) { + utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%s=true", features.PodPriority)) + utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%s=true", features.LocalStorageCapacityIsolation)) + pod1 := newPod("above-requests-low-priority-high-usage", lowPriority, []v1.Container{ + newContainer("above-requests-low-priority-high-usage", newResourceList("", "", ""), newResourceList("", "", "")), + }, []v1.Volume{ + newVolume("local-volume", v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }), + }) + pod2 := newPod("above-requests-low-priority-low-usage", lowPriority, []v1.Container{ + newContainer("above-requests-low-priority-low-usage", newResourceList("", "", ""), newResourceList("", "", "")), + }, []v1.Volume{ + newVolume("local-volume", v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }), + }) + pod3 := newPod("above-requests-high-priority-high-usage", highPriority, []v1.Container{ + newContainer("above-requests-high-priority-high-usage", newResourceList("", "", "100Mi"), newResourceList("", "", "")), + }, []v1.Volume{ + newVolume("local-volume", v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }), + }) + pod4 := newPod("above-requests-high-priority-low-usage", highPriority, []v1.Container{ + newContainer("above-requests-high-priority-low-usage", newResourceList("", "", "100Mi"), newResourceList("", "", "")), + }, []v1.Volume{ + newVolume("local-volume", v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }), + }) + pod5 := newPod("below-requests-low-priority-high-usage", lowPriority, []v1.Container{ + newContainer("below-requests-low-priority-high-usage", newResourceList("", "", "1Gi"), newResourceList("", "", "")), + }, []v1.Volume{ + newVolume("local-volume", v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }), + }) + pod6 := newPod("below-requests-low-priority-low-usage", lowPriority, []v1.Container{ + newContainer("below-requests-low-priority-low-usage", newResourceList("", "", "1Gi"), newResourceList("", "", "")), + }, []v1.Volume{ + newVolume("local-volume", v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }), + }) + pod7 := newPod("below-requests-high-priority-high-usage", highPriority, []v1.Container{ + newContainer("below-requests-high-priority-high-usage", newResourceList("", "", "1Gi"), newResourceList("", "", "")), + }, []v1.Volume{ + newVolume("local-volume", v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }), + }) + pod8 := newPod("below-requests-high-priority-low-usage", highPriority, []v1.Container{ + newContainer("below-requests-high-priority-low-usage", newResourceList("", "", "1Gi"), newResourceList("", "", "")), + }, []v1.Volume{ + newVolume("local-volume", v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }), + }) + stats := map[*v1.Pod]statsapi.PodStats{ + pod1: newPodDiskStats(pod1, resource.MustParse("200Mi"), resource.MustParse("100Mi"), resource.MustParse("200Mi")), // 500 relative to request + pod2: newPodDiskStats(pod2, resource.MustParse("10Mi"), resource.MustParse("10Mi"), resource.MustParse("30Mi")), // 50 relative to request + pod3: newPodDiskStats(pod3, resource.MustParse("200Mi"), resource.MustParse("150Mi"), resource.MustParse("250Mi")), // 500 relative to request + pod4: newPodDiskStats(pod4, resource.MustParse("90Mi"), resource.MustParse("50Mi"), resource.MustParse("10Mi")), // 50 relative to request + pod5: newPodDiskStats(pod5, resource.MustParse("500Mi"), resource.MustParse("200Mi"), resource.MustParse("100Mi")), // -200 relative to request + pod6: newPodDiskStats(pod6, resource.MustParse("50Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // -800 relative to request + pod7: newPodDiskStats(pod7, resource.MustParse("250Mi"), resource.MustParse("500Mi"), resource.MustParse("50Mi")), // -200 relative to request + pod8: newPodDiskStats(pod8, resource.MustParse("100Mi"), resource.MustParse("60Mi"), resource.MustParse("40Mi")), // -800 relative to request + } + statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) { + result, found := stats[pod] + return result, found + } + pods := []*v1.Pod{pod8, pod7, pod6, pod5, pod4, pod3, pod2, pod1} + expected := []*v1.Pod{pod1, pod2, pod3, pod4, pod5, pod6, pod7, pod8} + fsStatsToMeasure := []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource} + orderedBy(exceedDiskRequests(statsFn, fsStatsToMeasure, resourceDisk), priority, disk(statsFn, fsStatsToMeasure, resourceDisk)).Sort(pods) + for i := range expected { + if pods[i] != expected[i] { + t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name) + } + } +} + +// TestOrderedByPriorityInodes ensures we order pods by priority and then greediest resource consumer +func TestOrderedByPriorityInodes(t *testing.T) { + utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%s=true", features.PodPriority)) + pod1 := newPod("low-priority-high-usage", lowPriority, []v1.Container{ + newContainer("low-priority-high-usage", newResourceList("", "", ""), newResourceList("", "", "")), + }, []v1.Volume{ + newVolume("local-volume", v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }), + }) + pod2 := newPod("low-priority-low-usage", lowPriority, []v1.Container{ + newContainer("low-priority-low-usage", newResourceList("", "", ""), newResourceList("", "", "")), + }, []v1.Volume{ + newVolume("local-volume", v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }), + }) + pod3 := newPod("high-priority-high-usage", highPriority, []v1.Container{ + newContainer("high-priority-high-usage", newResourceList("", "", ""), newResourceList("", "", "")), + }, []v1.Volume{ + newVolume("local-volume", v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }), + }) + pod4 := newPod("high-priority-low-usage", highPriority, []v1.Container{ + newContainer("high-priority-low-usage", newResourceList("", "", ""), newResourceList("", "", "")), + }, []v1.Volume{ + newVolume("local-volume", v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }), + }) + stats := map[*v1.Pod]statsapi.PodStats{ + pod1: newPodInodeStats(pod1, resource.MustParse("50000"), resource.MustParse("100000"), resource.MustParse("250000")), // 400000 + pod2: newPodInodeStats(pod2, resource.MustParse("60000"), resource.MustParse("30000"), resource.MustParse("10000")), // 100000 + pod3: newPodInodeStats(pod3, resource.MustParse("150000"), resource.MustParse("150000"), resource.MustParse("50000")), // 350000 + pod4: newPodInodeStats(pod4, resource.MustParse("10000"), resource.MustParse("40000"), resource.MustParse("100000")), // 150000 } statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) { result, found := stats[pod] return result, found } pods := []*v1.Pod{pod4, pod3, pod2, pod1} - orderedBy(priority, disk(statsFn, []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, orderedByResource)).Sort(pods) + orderedBy(priority, disk(statsFn, []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, resourceInodes)).Sort(pods) expected := []*v1.Pod{pod1, pod2, pod3, pod4} for i := range expected { if pods[i] != expected[i] { @@ -625,22 +826,22 @@ func testOrderedByPriorityResource(t *testing.T, orderedByResource v1.ResourceNa // TestOrderedByMemory ensures we order pods by greediest memory consumer relative to request. func TestOrderedByMemory(t *testing.T) { pod1 := newPod("best-effort-high", defaultPriority, []v1.Container{ - newContainer("best-effort-high", newResourceList("", ""), newResourceList("", "")), + newContainer("best-effort-high", newResourceList("", "", ""), newResourceList("", "", "")), }, nil) pod2 := newPod("best-effort-low", defaultPriority, []v1.Container{ - newContainer("best-effort-low", newResourceList("", ""), newResourceList("", "")), + newContainer("best-effort-low", newResourceList("", "", ""), newResourceList("", "", "")), }, nil) pod3 := newPod("burstable-high", defaultPriority, []v1.Container{ - newContainer("burstable-high", newResourceList("100m", "100Mi"), newResourceList("200m", "1Gi")), + newContainer("burstable-high", newResourceList("", "100Mi", ""), newResourceList("", "1Gi", "")), }, nil) pod4 := newPod("burstable-low", defaultPriority, []v1.Container{ - newContainer("burstable-low", newResourceList("100m", "100Mi"), newResourceList("200m", "1Gi")), + newContainer("burstable-low", newResourceList("", "100Mi", ""), newResourceList("", "1Gi", "")), }, nil) pod5 := newPod("guaranteed-high", defaultPriority, []v1.Container{ - newContainer("guaranteed-high", newResourceList("100m", "1Gi"), newResourceList("100m", "1Gi")), + newContainer("guaranteed-high", newResourceList("", "1Gi", ""), newResourceList("", "1Gi", "")), }, nil) pod6 := newPod("guaranteed-low", defaultPriority, []v1.Container{ - newContainer("guaranteed-low", newResourceList("100m", "1Gi"), newResourceList("100m", "1Gi")), + newContainer("guaranteed-low", newResourceList("", "1Gi", ""), newResourceList("", "1Gi", "")), }, nil) stats := map[*v1.Pod]statsapi.PodStats{ pod1: newPodMemoryStats(pod1, resource.MustParse("500Mi")), // 500 relative to request @@ -666,30 +867,30 @@ func TestOrderedByMemory(t *testing.T) { // TestOrderedByPriorityMemory ensures we order by priority and then memory consumption relative to request. func TestOrderedByPriorityMemory(t *testing.T) { - enablePodPriority(true) + utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%s=true", features.PodPriority)) pod1 := newPod("above-requests-low-priority-high-usage", lowPriority, []v1.Container{ - newContainer("above-requests-low-priority-high-usage", newResourceList("", ""), newResourceList("", "")), + newContainer("above-requests-low-priority-high-usage", newResourceList("", "", ""), newResourceList("", "", "")), }, nil) pod2 := newPod("above-requests-low-priority-low-usage", lowPriority, []v1.Container{ - newContainer("above-requests-low-priority-low-usage", newResourceList("", ""), newResourceList("", "")), + newContainer("above-requests-low-priority-low-usage", newResourceList("", "", ""), newResourceList("", "", "")), }, nil) pod3 := newPod("above-requests-high-priority-high-usage", highPriority, []v1.Container{ - newContainer("above-requests-high-priority-high-usage", newResourceList("", "100Mi"), newResourceList("", "")), + newContainer("above-requests-high-priority-high-usage", newResourceList("", "100Mi", ""), newResourceList("", "", "")), }, nil) pod4 := newPod("above-requests-high-priority-low-usage", highPriority, []v1.Container{ - newContainer("above-requests-high-priority-low-usage", newResourceList("", "100Mi"), newResourceList("", "")), + newContainer("above-requests-high-priority-low-usage", newResourceList("", "100Mi", ""), newResourceList("", "", "")), }, nil) pod5 := newPod("below-requests-low-priority-high-usage", lowPriority, []v1.Container{ - newContainer("below-requests-low-priority-high-usage", newResourceList("", "1Gi"), newResourceList("", "")), + newContainer("below-requests-low-priority-high-usage", newResourceList("", "1Gi", ""), newResourceList("", "", "")), }, nil) pod6 := newPod("below-requests-low-priority-low-usage", lowPriority, []v1.Container{ - newContainer("below-requests-low-priority-low-usage", newResourceList("", "1Gi"), newResourceList("", "")), + newContainer("below-requests-low-priority-low-usage", newResourceList("", "1Gi", ""), newResourceList("", "", "")), }, nil) pod7 := newPod("below-requests-high-priority-high-usage", highPriority, []v1.Container{ - newContainer("below-requests-high-priority-high-usage", newResourceList("", "1Gi"), newResourceList("", "")), + newContainer("below-requests-high-priority-high-usage", newResourceList("", "1Gi", ""), newResourceList("", "", "")), }, nil) pod8 := newPod("below-requests-high-priority-low-usage", highPriority, []v1.Container{ - newContainer("below-requests-high-priority-low-usage", newResourceList("", "1Gi"), newResourceList("", "")), + newContainer("below-requests-high-priority-low-usage", newResourceList("", "1Gi", ""), newResourceList("", "", "")), }, nil) stats := map[*v1.Pod]statsapi.PodStats{ pod1: newPodMemoryStats(pod1, resource.MustParse("500Mi")), // 500 relative to request @@ -706,7 +907,6 @@ func TestOrderedByPriorityMemory(t *testing.T) { return result, found } pods := []*v1.Pod{pod8, pod7, pod6, pod5, pod4, pod3, pod2, pod1} - // pods := []*v1.Pod{pod1, pod2, pod3, pod4, pod5, pod6, pod7, pod8} expected := []*v1.Pod{pod1, pod2, pod3, pod4, pod5, pod6, pod7, pod8} orderedBy(exceedMemoryRequests(statsFn), priority, memory(statsFn)).Sort(pods) for i := range expected { @@ -811,7 +1011,7 @@ func TestMakeSignalObservations(t *testing.T) { if res.CmpInt64(int64(allocatableMemoryCapacity)) != 0 { t.Errorf("Expected Threshold %v to be equal to value %v", res.Value(), allocatableMemoryCapacity) } - actualObservations, statsFunc, err := makeSignalObservations(provider, capacityProvider, pods, false) + actualObservations, statsFunc, err := makeSignalObservations(provider, capacityProvider, pods) if err != nil { t.Errorf("Unexpected err: %v", err) } @@ -1617,7 +1817,7 @@ func newPodMemoryStats(pod *v1.Pod, workingSet resource.Quantity) statsapi.PodSt return result } -func newResourceList(cpu, memory string) v1.ResourceList { +func newResourceList(cpu, memory, disk string) v1.ResourceList { res := v1.ResourceList{} if cpu != "" { res[v1.ResourceCPU] = resource.MustParse(cpu) @@ -1625,6 +1825,9 @@ func newResourceList(cpu, memory string) v1.ResourceList { if memory != "" { res[v1.ResourceMemory] = resource.MustParse(memory) } + if disk != "" { + res[v1.ResourceEphemeralStorage] = resource.MustParse(disk) + } return res } @@ -1709,7 +1912,3 @@ func (s1 thresholdList) Equal(s2 thresholdList) bool { } return true } - -func enablePodPriority(enabled bool) { - utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%s=%t", features.PodPriority, enabled)) -} diff --git a/pkg/kubelet/gpu/nvidia/BUILD b/pkg/kubelet/gpu/nvidia/BUILD index 2b31e7eb6b6..8f8dd72a4c4 100644 --- a/pkg/kubelet/gpu/nvidia/BUILD +++ b/pkg/kubelet/gpu/nvidia/BUILD @@ -14,6 +14,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/kubelet/gpu/nvidia", deps = [ + "//pkg/kubelet/dockershim:go_default_library", "//pkg/kubelet/dockershim/libdocker:go_default_library", "//pkg/kubelet/gpu:go_default_library", "//vendor/github.com/golang/glog:go_default_library", @@ -39,9 +40,10 @@ filegroup( go_test( name = "go_default_test", srcs = ["nvidia_gpu_manager_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/gpu/nvidia", - library = ":go_default_library", deps = [ + "//pkg/kubelet/dockershim:go_default_library", "//pkg/kubelet/dockershim/libdocker:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/kubelet/gpu/nvidia/nvidia_gpu_manager.go b/pkg/kubelet/gpu/nvidia/nvidia_gpu_manager.go index c347cd2addf..37c30d14b55 100644 --- a/pkg/kubelet/gpu/nvidia/nvidia_gpu_manager.go +++ b/pkg/kubelet/gpu/nvidia/nvidia_gpu_manager.go @@ -30,6 +30,7 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/kubernetes/pkg/kubelet/dockershim" "k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker" "k8s.io/kubernetes/pkg/kubelet/gpu" ) @@ -43,8 +44,11 @@ const ( // Optional device. nvidiaUVMToolsDevice string = "/dev/nvidia-uvm-tools" devDirectory = "/dev" - nvidiaDeviceRE = `^nvidia[0-9]*$` - nvidiaFullpathRE = `^/dev/nvidia[0-9]*$` +) + +var ( + nvidiaDeviceRE = regexp.MustCompile(`^nvidia[0-9]*$`) + nvidiaFullpathRE = regexp.MustCompile(`^/dev/nvidia[0-9]*$`) ) type activePodsLister interface { @@ -67,10 +71,12 @@ type nvidiaGPUManager struct { // NewNvidiaGPUManager returns a GPUManager that manages local Nvidia GPUs. // TODO: Migrate to use pod level cgroups and make it generic to all runtimes. -func NewNvidiaGPUManager(activePodsLister activePodsLister, dockerClient libdocker.Interface) (gpu.GPUManager, error) { +func NewNvidiaGPUManager(activePodsLister activePodsLister, config *dockershim.ClientConfig) (gpu.GPUManager, error) { + dockerClient := dockershim.NewDockerClientFromConfig(config) if dockerClient == nil { - return nil, fmt.Errorf("invalid docker client specified") + return nil, fmt.Errorf("invalid docker client configure specified") } + return &nvidiaGPUManager{ allGPUs: sets.NewString(), dockerClient: dockerClient, @@ -191,7 +197,6 @@ func (ngm *nvidiaGPUManager) updateAllocatedGPUs() { // we want more features, features like schedule containers according to GPU family // name. func (ngm *nvidiaGPUManager) discoverGPUs() error { - reg := regexp.MustCompile(nvidiaDeviceRE) files, err := ioutil.ReadDir(devDirectory) if err != nil { return err @@ -200,7 +205,7 @@ func (ngm *nvidiaGPUManager) discoverGPUs() error { if f.IsDir() { continue } - if reg.MatchString(f.Name()) { + if nvidiaDeviceRE.MatchString(f.Name()) { glog.V(2).Infof("Found Nvidia GPU %q", f.Name()) ngm.allGPUs.Insert(path.Join(devDirectory, f.Name())) } @@ -271,5 +276,5 @@ func (ngm *nvidiaGPUManager) gpusInUse() *podGPUs { } func isValidPath(path string) bool { - return regexp.MustCompile(nvidiaFullpathRE).MatchString(path) + return nvidiaFullpathRE.MatchString(path) } diff --git a/pkg/kubelet/gpu/nvidia/nvidia_gpu_manager_test.go b/pkg/kubelet/gpu/nvidia/nvidia_gpu_manager_test.go index dc7253f0940..8dc2cd6e672 100644 --- a/pkg/kubelet/gpu/nvidia/nvidia_gpu_manager_test.go +++ b/pkg/kubelet/gpu/nvidia/nvidia_gpu_manager_test.go @@ -28,6 +28,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/uuid" + "k8s.io/kubernetes/pkg/kubelet/dockershim" "k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker" ) @@ -73,8 +74,9 @@ func TestNewNvidiaGPUManager(t *testing.T) { as.NotNil(err) // Expects a GPUManager to be created with non-nil dockerClient. - fakeDocker := libdocker.NewFakeDockerClient() - testGpuManager2, err := NewNvidiaGPUManager(podLister, fakeDocker) + testGpuManager2, err := NewNvidiaGPUManager(podLister, &dockershim.ClientConfig{ + DockerEndpoint: libdocker.FakeDockerEndpoint, + }) as.NotNil(testGpuManager2) as.Nil(err) diff --git a/pkg/kubelet/images/BUILD b/pkg/kubelet/images/BUILD index 8a8684920f4..c47aabd43fc 100644 --- a/pkg/kubelet/images/BUILD +++ b/pkg/kubelet/images/BUILD @@ -39,8 +39,8 @@ go_test( "image_gc_manager_test.go", "image_manager_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/images", - library = ":go_default_library", deps = [ "//pkg/kubelet/apis/stats/v1alpha1:go_default_library", "//pkg/kubelet/container:go_default_library", diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index b95ca191855..2ed2dd9c7ac 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -19,14 +19,11 @@ package kubelet import ( "crypto/tls" "fmt" - "io/ioutil" "net" "net/http" "net/url" "os" "path" - "path/filepath" - goruntime "runtime" "sort" "strings" "sync" @@ -35,8 +32,6 @@ import ( "github.com/golang/glog" - clientgoclientset "k8s.io/client-go/kubernetes" - cadvisorapi "github.com/google/cadvisor/info/v1" cadvisorapiv2 "github.com/google/cadvisor/info/v2" "k8s.io/api/core/v1" @@ -57,12 +52,11 @@ import ( "k8s.io/client-go/util/certificate" "k8s.io/client-go/util/flowcontrol" "k8s.io/client-go/util/integer" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/features" internalapi "k8s.io/kubernetes/pkg/kubelet/apis/cri" kubeletconfiginternal "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" - kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" "k8s.io/kubernetes/pkg/kubelet/cadvisor" kubeletcertificate "k8s.io/kubernetes/pkg/kubelet/certificate" "k8s.io/kubernetes/pkg/kubelet/cm" @@ -70,7 +64,6 @@ import ( "k8s.io/kubernetes/pkg/kubelet/configmap" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/kubelet/dockershim" - "k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker" dockerremote "k8s.io/kubernetes/pkg/kubelet/dockershim/remote" "k8s.io/kubernetes/pkg/kubelet/events" "k8s.io/kubernetes/pkg/kubelet/eviction" @@ -82,6 +75,7 @@ import ( "k8s.io/kubernetes/pkg/kubelet/lifecycle" "k8s.io/kubernetes/pkg/kubelet/metrics" "k8s.io/kubernetes/pkg/kubelet/network" + "k8s.io/kubernetes/pkg/kubelet/network/dns" "k8s.io/kubernetes/pkg/kubelet/pleg" kubepod "k8s.io/kubernetes/pkg/kubelet/pod" "k8s.io/kubernetes/pkg/kubelet/preemption" @@ -101,6 +95,7 @@ import ( "k8s.io/kubernetes/pkg/kubelet/util/queue" "k8s.io/kubernetes/pkg/kubelet/util/sliceutils" "k8s.io/kubernetes/pkg/kubelet/volumemanager" + "k8s.io/kubernetes/pkg/scheduler/algorithm/predicates" "k8s.io/kubernetes/pkg/security/apparmor" utildbus "k8s.io/kubernetes/pkg/util/dbus" kubeio "k8s.io/kubernetes/pkg/util/io" @@ -109,7 +104,6 @@ import ( nodeutil "k8s.io/kubernetes/pkg/util/node" "k8s.io/kubernetes/pkg/util/oom" "k8s.io/kubernetes/pkg/volume" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates" utilexec "k8s.io/utils/exec" ) @@ -197,14 +191,17 @@ type Bootstrap interface { type Builder func(kubeCfg *kubeletconfiginternal.KubeletConfiguration, kubeDeps *Dependencies, crOptions *config.ContainerRuntimeOptions, + containerRuntime string, + runtimeCgroups string, hostnameOverride string, nodeIP string, providerID string, cloudProvider string, certDirectory string, rootDirectory string, + registerNode bool, + registerWithTaints []api.Taint, allowedUnsafeSysctls []string, - containerized bool, remoteRuntimeEndpoint string, remoteImageEndpoint string, experimentalMounterPath string, @@ -217,44 +214,27 @@ type Builder func(kubeCfg *kubeletconfiginternal.KubeletConfiguration, masterServiceNamespace string, registerSchedulable bool, nonMasqueradeCIDR string, - keepTerminatedPodVolumes bool) (Bootstrap, error) + keepTerminatedPodVolumes bool, + nodeLabels map[string]string, + seccompProfileRoot string, + bootstrapCheckpointPath string) (Bootstrap, error) // Dependencies is a bin for things we might consider "injected dependencies" -- objects constructed // at runtime that are necessary for running the Kubelet. This is a temporary solution for grouping // these objects while we figure out a more comprehensive dependency injection story for the Kubelet. type Dependencies struct { - // TODO(mtaufen): KubeletBuilder: - // Mesos currently uses this as a hook to let them make their own call to - // let them wrap the KubeletBootstrap that CreateAndInitKubelet returns with - // their own KubeletBootstrap. It's a useful hook. I need to think about what - // a nice home for it would be. There seems to be a trend, between this and - // the Options fields below, of providing hooks where you can add extra functionality - // to the Kubelet for your solution. Maybe we should centralize these sorts of things? - Builder Builder - - // TODO(mtaufen): ContainerRuntimeOptions and Options: - // Arrays of functions that can do arbitrary things to the Kubelet and the Runtime - // seem like a difficult path to trace when it's time to debug something. - // I'm leaving these fields here for now, but there is likely an easier-to-follow - // way to support their intended use cases. E.g. ContainerRuntimeOptions - // is used by Mesos to set an environment variable in containers which has - // some connection to their container GC. It seems that Mesos intends to use - // Options to add additional node conditions that are updated as part of the - // Kubelet lifecycle (see https://github.com/kubernetes/kubernetes/pull/21521). - // We should think about providing more explicit ways of doing these things. - ContainerRuntimeOptions []kubecontainer.Option - Options []Option + Options []Option // Injected Dependencies Auth server.AuthInterface CAdvisorInterface cadvisor.Interface Cloud cloudprovider.Interface ContainerManager cm.ContainerManager - DockerClient libdocker.Interface + DockerClientConfig *dockershim.ClientConfig EventClient v1core.EventsGetter HeartbeatClient v1core.CoreV1Interface KubeClient clientset.Interface - ExternalKubeClient clientgoclientset.Interface + ExternalKubeClient clientset.Interface Mounter mount.Interface NetworkPlugins []network.NetworkPlugin OOMAdjuster *oom.OOMAdjuster @@ -270,14 +250,14 @@ type Dependencies struct { // makePodSourceConfig creates a config.PodConfig from the given // KubeletConfiguration or returns an error. -func makePodSourceConfig(kubeCfg *kubeletconfiginternal.KubeletConfiguration, kubeDeps *Dependencies, nodeName types.NodeName) (*config.PodConfig, error) { +func makePodSourceConfig(kubeCfg *kubeletconfiginternal.KubeletConfiguration, kubeDeps *Dependencies, nodeName types.NodeName, bootstrapCheckpointPath string) (*config.PodConfig, error) { manifestURLHeader := make(http.Header) - if kubeCfg.ManifestURLHeader != "" { - pieces := strings.Split(kubeCfg.ManifestURLHeader, ":") - if len(pieces) != 2 { - return nil, fmt.Errorf("manifest-url-header must have a single ':' key-value separator, got %q", kubeCfg.ManifestURLHeader) + if len(kubeCfg.ManifestURLHeader) > 0 { + for k, v := range kubeCfg.ManifestURLHeader { + for i := range v { + manifestURLHeader.Add(k, v[i]) + } } - manifestURLHeader.Set(pieces[0], pieces[1]) } // source of all configuration @@ -285,7 +265,7 @@ func makePodSourceConfig(kubeCfg *kubeletconfiginternal.KubeletConfiguration, ku // define file config source if kubeCfg.PodManifestPath != "" { - glog.Infof("Adding manifest file: %v", kubeCfg.PodManifestPath) + glog.Infof("Adding manifest path: %v", kubeCfg.PodManifestPath) config.NewSourceFile(kubeCfg.PodManifestPath, nodeName, kubeCfg.FileCheckFrequency.Duration, cfg.Channel(kubetypes.FileSource)) } @@ -294,9 +274,22 @@ func makePodSourceConfig(kubeCfg *kubeletconfiginternal.KubeletConfiguration, ku glog.Infof("Adding manifest url %q with HTTP header %v", kubeCfg.ManifestURL, manifestURLHeader) config.NewSourceURL(kubeCfg.ManifestURL, manifestURLHeader, nodeName, kubeCfg.HTTPCheckFrequency.Duration, cfg.Channel(kubetypes.HTTPSource)) } + + // Restore from the checkpoint path + // NOTE: This MUST happen before creating the apiserver source + // below, or the checkpoint would override the source of truth. + updatechannel := cfg.Channel(kubetypes.ApiserverSource) + if bootstrapCheckpointPath != "" { + glog.Infof("Adding checkpoint path: %v", bootstrapCheckpointPath) + err := cfg.Restore(bootstrapCheckpointPath, updatechannel) + if err != nil { + return nil, err + } + } + if kubeDeps.KubeClient != nil { glog.Infof("Watching apiserver") - config.NewSourceApiserver(kubeDeps.KubeClient, nodeName, cfg.Channel(kubetypes.ApiserverSource)) + config.NewSourceApiserver(kubeDeps.KubeClient, nodeName, updatechannel) } return cfg, nil } @@ -318,14 +311,17 @@ func getRuntimeAndImageServices(remoteRuntimeEndpoint string, remoteImageEndpoin func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, kubeDeps *Dependencies, crOptions *config.ContainerRuntimeOptions, + containerRuntime string, + runtimeCgroups string, hostnameOverride string, nodeIP string, providerID string, cloudProvider string, certDirectory string, rootDirectory string, + registerNode bool, + registerWithTaints []api.Taint, allowedUnsafeSysctls []string, - containerized bool, remoteRuntimeEndpoint string, remoteImageEndpoint string, experimentalMounterPath string, @@ -338,7 +334,10 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, masterServiceNamespace string, registerSchedulable bool, nonMasqueradeCIDR string, - keepTerminatedPodVolumes bool) (*Kubelet, error) { + keepTerminatedPodVolumes bool, + nodeLabels map[string]string, + seccompProfileRoot string, + bootstrapCheckpointPath string) (*Kubelet, error) { if rootDirectory == "" { return nil, fmt.Errorf("invalid root directory %q", rootDirectory) } @@ -399,7 +398,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, if kubeDeps.PodConfig == nil { var err error - kubeDeps.PodConfig, err = makePodSourceConfig(kubeCfg, kubeDeps, nodeName) + kubeDeps.PodConfig, err = makePodSourceConfig(kubeCfg, kubeDeps, nodeName, bootstrapCheckpointPath) if err != nil { return nil, err } @@ -439,7 +438,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, serviceIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) if kubeDeps.KubeClient != nil { - serviceLW := cache.NewListWatchFromClient(kubeDeps.KubeClient.Core().RESTClient(), "services", metav1.NamespaceAll, fields.Everything()) + serviceLW := cache.NewListWatchFromClient(kubeDeps.KubeClient.CoreV1().RESTClient(), "services", metav1.NamespaceAll, fields.Everything()) r := cache.NewReflector(serviceLW, &v1.Service{}, serviceIndexer, 0) go r.Run(wait.NeverStop) } @@ -448,7 +447,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, nodeIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{}) if kubeDeps.KubeClient != nil { fieldSelector := fields.Set{api.ObjectNameField: string(nodeName)}.AsSelector() - nodeLW := cache.NewListWatchFromClient(kubeDeps.KubeClient.Core().RESTClient(), "nodes", metav1.NamespaceAll, fieldSelector) + nodeLW := cache.NewListWatchFromClient(kubeDeps.KubeClient.CoreV1().RESTClient(), "nodes", metav1.NamespaceAll, fieldSelector) r := cache.NewReflector(nodeLW, &v1.Node{}, nodeIndexer, 0) go r.Run(wait.NeverStop) } @@ -478,6 +477,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, } } httpClient := &http.Client{} + parsedNodeIP := net.ParseIP(nodeIP) klet := &Kubelet{ hostname: hostname, @@ -487,10 +487,10 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, rootDirectory: rootDirectory, resyncInterval: kubeCfg.SyncFrequency.Duration, sourcesReady: config.NewSourcesReady(kubeDeps.PodConfig.SeenAllSources), - registerNode: kubeCfg.RegisterNode, + registerNode: registerNode, + registerWithTaints: registerWithTaints, registerSchedulable: registerSchedulable, - clusterDomain: kubeCfg.ClusterDomain, - clusterDNS: clusterDNS, + dnsConfigurer: dns.NewConfigurer(kubeDeps.Recorder, nodeRef, parsedNodeIP, clusterDNS, kubeCfg.ClusterDomain, kubeCfg.ResolverConfig), serviceLister: serviceLister, nodeInfo: nodeInfo, masterServiceNamespace: masterServiceNamespace, @@ -498,26 +498,25 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, recorder: kubeDeps.Recorder, cadvisor: kubeDeps.CAdvisorInterface, cloud: kubeDeps.Cloud, - autoDetectCloudProvider: (kubeletconfigv1alpha1.AutoDetectCloudProvider == cloudProvider), externalCloudProvider: cloudprovider.IsExternal(cloudProvider), providerID: providerID, nodeRef: nodeRef, - nodeLabels: kubeCfg.NodeLabels, + nodeLabels: nodeLabels, nodeStatusUpdateFrequency: kubeCfg.NodeStatusUpdateFrequency.Duration, - os: kubeDeps.OSInterface, - oomWatcher: oomWatcher, - cgroupsPerQOS: kubeCfg.CgroupsPerQOS, - cgroupRoot: kubeCfg.CgroupRoot, - mounter: kubeDeps.Mounter, - writer: kubeDeps.Writer, - maxPods: int(kubeCfg.MaxPods), - podsPerCore: int(kubeCfg.PodsPerCore), - syncLoopMonitor: atomic.Value{}, - resolverConfig: kubeCfg.ResolverConfig, - daemonEndpoints: daemonEndpoints, - containerManager: kubeDeps.ContainerManager, - nodeIP: net.ParseIP(nodeIP), - clock: clock.RealClock{}, + os: kubeDeps.OSInterface, + oomWatcher: oomWatcher, + cgroupsPerQOS: kubeCfg.CgroupsPerQOS, + cgroupRoot: kubeCfg.CgroupRoot, + mounter: kubeDeps.Mounter, + writer: kubeDeps.Writer, + maxPods: int(kubeCfg.MaxPods), + podsPerCore: int(kubeCfg.PodsPerCore), + syncLoopMonitor: atomic.Value{}, + daemonEndpoints: daemonEndpoints, + containerManager: kubeDeps.ContainerManager, + containerRuntimeName: containerRuntime, + nodeIP: parsedNodeIP, + clock: clock.RealClock{}, enableControllerAttachDetach: kubeCfg.EnableControllerAttachDetach, iptClient: utilipt.New(utilexec.New(), utildbus.New(), utilipt.ProtocolIpv4), makeIPTablesUtilChains: kubeCfg.MakeIPTablesUtilChains, @@ -539,7 +538,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, glog.Infof("Experimental host user namespace defaulting is enabled.") } - hairpinMode, err := effectiveHairpinMode(kubeletconfiginternal.HairpinMode(kubeCfg.HairpinMode), kubeCfg.ContainerRuntime, crOptions.NetworkPluginName) + hairpinMode, err := effectiveHairpinMode(kubeletconfiginternal.HairpinMode(kubeCfg.HairpinMode), containerRuntime, crOptions.NetworkPluginName) if err != nil { // This is a non-recoverable error. Returning it up the callstack will just // lead to retries of the same failure, so just fail hard. @@ -574,16 +573,12 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, } // TODO: These need to become arguments to a standalone docker shim. - binDir := crOptions.CNIBinDir - if binDir == "" { - binDir = crOptions.NetworkPluginDir - } pluginSettings := dockershim.NetworkPluginSettings{ HairpinMode: hairpinMode, NonMasqueradeCIDR: nonMasqueradeCIDR, PluginName: crOptions.NetworkPluginName, PluginConfDir: crOptions.CNIConfDir, - PluginBinDir: binDir, + PluginBinDir: crOptions.CNIBinDir, MTU: int(crOptions.NetworkPluginMTU), } @@ -596,7 +591,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, pluginSettings.LegacyRuntimeHost = nl // rktnetes cannot be run with CRI. - if kubeCfg.ContainerRuntime != kubetypes.RktContainerRuntime { + if containerRuntime != kubetypes.RktContainerRuntime { // kubelet defers to the runtime shim to setup networking. Setting // this to nil will prevent it from trying to invoke the plugin. // It's easier to always probe and initialize plugins till cri @@ -605,12 +600,12 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, // if left at nil, that means it is unneeded var legacyLogProvider kuberuntime.LegacyLogProvider - switch kubeCfg.ContainerRuntime { + switch containerRuntime { case kubetypes.DockerContainerRuntime: // Create and start the CRI shim running as a grpc server. streamingConfig := getStreamingConfig(kubeCfg, kubeDeps) - ds, err := dockershim.NewDockerService(kubeDeps.DockerClient, crOptions.PodSandboxImage, streamingConfig, - &pluginSettings, kubeCfg.RuntimeCgroups, kubeCfg.CgroupDriver, crOptions.DockershimRootDirectory, + ds, err := dockershim.NewDockerService(kubeDeps.DockerClientConfig, crOptions.PodSandboxImage, streamingConfig, + &pluginSettings, runtimeCgroups, kubeCfg.CgroupDriver, crOptions.DockershimRootDirectory, crOptions.DockerDisableSharedPID) if err != nil { return nil, err @@ -633,19 +628,19 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, } // Create dockerLegacyService when the logging driver is not supported. - supported, err := dockershim.IsCRISupportedLogDriver(kubeDeps.DockerClient) + supported, err := ds.IsCRISupportedLogDriver() if err != nil { return nil, err } if !supported { - klet.dockerLegacyService = dockershim.NewDockerLegacyService(kubeDeps.DockerClient) + klet.dockerLegacyService = ds.NewDockerLegacyService() legacyLogProvider = dockershim.NewLegacyLogProvider(klet.dockerLegacyService) } case kubetypes.RemoteContainerRuntime: // No-op. break default: - return nil, fmt.Errorf("unsupported CRI runtime: %q", kubeCfg.ContainerRuntime) + return nil, fmt.Errorf("unsupported CRI runtime: %q", containerRuntime) } runtimeService, imageService, err := getRuntimeAndImageServices(remoteRuntimeEndpoint, remoteImageEndpoint, kubeCfg.RuntimeRequestTimeout) if err != nil { @@ -655,7 +650,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, runtime, err := kuberuntime.NewKubeGenericRuntimeManager( kubecontainer.FilterEventRecorder(kubeDeps.Recorder), klet.livenessManager, - kubeCfg.SeccompProfileRoot, + seccompProfileRoot, containerRefManager, machineInfo, klet, @@ -678,14 +673,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, klet.containerRuntime = runtime klet.runner = runtime - // CRI integrations should get container metrics via CRI. Docker - // uses the built-in cadvisor to gather such metrics on Linux for - // historical reasons. - // cri-o relies on cadvisor as a temporary workaround. The code should - // be removed. Related issue: - // https://github.com/kubernetes/kubernetes/issues/51798 - if (kubeCfg.ContainerRuntime == kubetypes.DockerContainerRuntime && - goruntime.GOOS == "linux") || remoteRuntimeEndpoint == "/var/run/crio.sock" { + if cadvisor.UsingLegacyCadvisorStats(containerRuntime, remoteRuntimeEndpoint) { klet.StatsProvider = stats.NewCadvisorStatsProvider( klet.cadvisor, klet.resourceAnalyzer, @@ -810,7 +798,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, experimentalCheckNodeCapabilitiesBeforeMount = false // Replace the nameserver in containerized-mounter's rootfs/etc/resolve.conf with kubelet.ClusterDNS // so that service name could be resolved - klet.setupDNSinContainerizedMounter(experimentalMounterPath) + klet.dnsConfigurer.SetupDNSinContainerizedMounter(experimentalMounterPath) } // setup volumeManager @@ -876,18 +864,19 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, klet.AddPodSyncHandler(activeDeadlineHandler) criticalPodAdmissionHandler := preemption.NewCriticalPodAdmissionHandler(klet.GetActivePods, killPodNow(klet.podWorkers, kubeDeps.Recorder), kubeDeps.Recorder) - klet.admitHandlers.AddPodAdmitHandler(lifecycle.NewPredicateAdmitHandler(klet.getNodeAnyWay, criticalPodAdmissionHandler)) + klet.admitHandlers.AddPodAdmitHandler(lifecycle.NewPredicateAdmitHandler(klet.getNodeAnyWay, criticalPodAdmissionHandler, klet.containerManager.UpdatePluginResources)) // apply functional Option's for _, opt := range kubeDeps.Options { opt(klet) } - klet.appArmorValidator = apparmor.NewValidator(kubeCfg.ContainerRuntime) + klet.appArmorValidator = apparmor.NewValidator(containerRuntime) klet.softAdmitHandlers.AddPodAdmitHandler(lifecycle.NewAppArmorAdmitHandler(klet.appArmorValidator)) klet.softAdmitHandlers.AddPodAdmitHandler(lifecycle.NewNoNewPrivsAdmitHandler(klet.containerRuntime)) if utilfeature.DefaultFeatureGate.Enabled(features.Accelerators) { - if kubeCfg.ContainerRuntime == kubetypes.DockerContainerRuntime { - if klet.gpuManager, err = nvidia.NewNvidiaGPUManager(klet, kubeDeps.DockerClient); err != nil { + if containerRuntime == kubetypes.DockerContainerRuntime { + glog.Warningln("Accelerators feature is deprecated and will be removed in v1.11. Please use device plugins instead. They can be enabled using the DevicePlugins feature gate.") + if klet.gpuManager, err = nvidia.NewNvidiaGPUManager(klet, kubeDeps.DockerClientConfig); err != nil { return nil, err } } else { @@ -947,16 +936,15 @@ type Kubelet struct { // Set to true to have the node register itself with the apiserver. registerNode bool + // List of taints to add to a node object when the kubelet registers itself. + registerWithTaints []api.Taint // Set to true to have the node register itself as schedulable. registerSchedulable bool // for internal book keeping; access only from within registerWithApiserver registrationCompleted bool - // If non-empty, use this for container DNS search. - clusterDomain string - - // If non-nil, use this for container DNS server. - clusterDNS []net.IP + // dnsConfigurer is used for setting up DNS resolver configuration when launching pods. + dnsConfigurer *dns.Configurer // masterServiceNamespace is the namespace that the master service is exposed in. masterServiceNamespace string @@ -1021,16 +1009,15 @@ type Kubelet struct { // Cloud provider interface. cloud cloudprovider.Interface - // DEPRECATED: auto detecting cloud providers goes against the initiative - // for out-of-tree cloud providers as we'll now depend on cAdvisor integrations - // with cloud providers instead of in the core repo. - // More details here: https://github.com/kubernetes/kubernetes/issues/50986 - autoDetectCloudProvider bool + // Indicates that the node initialization happens in an external cloud controller externalCloudProvider bool // Reference to this node. nodeRef *v1.ObjectReference + // The name of the container runtime + containerRuntimeName string + // Container runtime. containerRuntime kubecontainer.Runtime @@ -1098,11 +1085,6 @@ type Kubelet struct { // Channel for sending pods to kill. podKillingCh chan *kubecontainer.PodPair - // The configuration file used as the base to generate the container's - // DNS resolver configuration file. This can be used in conjunction with - // clusterDomain and clusterDNS. - resolverConfig string - // Information about the ports which are opened by daemons on Node running this Kubelet server. daemonEndpoints *v1.NodeDaemonEndpoints @@ -1310,7 +1292,7 @@ func (kl *Kubelet) initializeModules() error { return fmt.Errorf("Kubelet failed to get node info: %v", err) } - if err := kl.containerManager.Start(node, kl.GetActivePods, kl.statusManager, kl.runtimeService); err != nil { + if err := kl.containerManager.Start(node, kl.GetActivePods, kl.sourcesReady, kl.statusManager, kl.runtimeService); err != nil { return fmt.Errorf("Failed to start ContainerManager %v", err) } @@ -1376,8 +1358,8 @@ func (kl *Kubelet) Run(updates <-chan kubetypes.PodUpdate) { go wait.Until(kl.podKiller, 1*time.Second, wait.NeverStop) // Start gorouting responsible for checking limits in resolv.conf - if kl.resolverConfig != "" { - go wait.Until(func() { kl.checkLimitsForResolvConf() }, 30*time.Second, wait.NeverStop) + if kl.dnsConfigurer.ResolverConfig != "" { + go wait.Until(func() { kl.dnsConfigurer.CheckLimitsForResolvConf() }, 30*time.Second, wait.NeverStop) } // Start component sync loops. @@ -1396,64 +1378,6 @@ func (kl *Kubelet) GetKubeClient() clientset.Interface { return kl.kubeClient } -// GetClusterDNS returns a list of the DNS servers and a list of the DNS search -// domains of the cluster. -func (kl *Kubelet) GetClusterDNS(pod *v1.Pod) ([]string, []string, bool, error) { - var hostDNS, hostSearch []string - // Get host DNS settings - if kl.resolverConfig != "" { - f, err := os.Open(kl.resolverConfig) - if err != nil { - return nil, nil, false, err - } - defer f.Close() - - hostDNS, hostSearch, err = kl.parseResolvConf(f) - if err != nil { - return nil, nil, false, err - } - } - useClusterFirstPolicy := ((pod.Spec.DNSPolicy == v1.DNSClusterFirst && !kubecontainer.IsHostNetworkPod(pod)) || pod.Spec.DNSPolicy == v1.DNSClusterFirstWithHostNet) - if useClusterFirstPolicy && len(kl.clusterDNS) == 0 { - // clusterDNS is not known. - // pod with ClusterDNSFirst Policy cannot be created - kl.recorder.Eventf(pod, v1.EventTypeWarning, "MissingClusterDNS", "kubelet does not have ClusterDNS IP configured and cannot create Pod using %q policy. Falling back to DNSDefault policy.", pod.Spec.DNSPolicy) - log := fmt.Sprintf("kubelet does not have ClusterDNS IP configured and cannot create Pod using %q policy. pod: %q. Falling back to DNSDefault policy.", pod.Spec.DNSPolicy, format.Pod(pod)) - kl.recorder.Eventf(kl.nodeRef, v1.EventTypeWarning, "MissingClusterDNS", log) - - // fallback to DNSDefault - useClusterFirstPolicy = false - } - - if !useClusterFirstPolicy { - // When the kubelet --resolv-conf flag is set to the empty string, use - // DNS settings that override the docker default (which is to use - // /etc/resolv.conf) and effectively disable DNS lookups. According to - // the bind documentation, the behavior of the DNS client library when - // "nameservers" are not specified is to "use the nameserver on the - // local machine". A nameserver setting of localhost is equivalent to - // this documented behavior. - if kl.resolverConfig == "" { - hostDNS = []string{"127.0.0.1"} - hostSearch = []string{"."} - } else { - hostSearch = kl.formDNSSearchForDNSDefault(hostSearch, pod) - } - return hostDNS, hostSearch, useClusterFirstPolicy, nil - } - - // for a pod with DNSClusterFirst policy, the cluster DNS server is the only nameserver configured for - // the pod. The cluster DNS server itself will forward queries to other nameservers that is configured to use, - // in case the cluster DNS server cannot resolve the DNS query itself - dns := make([]string, len(kl.clusterDNS)) - for i, ip := range kl.clusterDNS { - dns[i] = ip.String() - } - dnsSearch := kl.formDNSSearch(hostSearch, pod) - - return dns, dnsSearch, useClusterFirstPolicy, nil -} - // syncPod is the transaction script for the sync of a single pod. // // Arguments: @@ -1901,17 +1825,28 @@ func (kl *Kubelet) syncLoopIteration(configCh <-chan kubetypes.PodUpdate, handle glog.V(2).Infof("SyncLoop (DELETE, %q): %q", u.Source, format.Pods(u.Pods)) // DELETE is treated as a UPDATE because of graceful deletion. handler.HandlePodUpdates(u.Pods) + case kubetypes.RESTORE: + glog.V(2).Infof("SyncLoop (RESTORE, %q): %q", u.Source, format.Pods(u.Pods)) + // These are pods restored from the checkpoint. Treat them as new + // pods. + handler.HandlePodAdditions(u.Pods) case kubetypes.SET: // TODO: Do we want to support this? glog.Errorf("Kubelet does not support snapshot update") } - // Mark the source ready after receiving at least one update from the - // source. Once all the sources are marked ready, various cleanup - // routines will start reclaiming resources. It is important that this - // takes place only after kubelet calls the update handler to process - // the update to ensure the internal pod cache is up-to-date. - kl.sourcesReady.AddSource(u.Source) + if u.Op != kubetypes.RESTORE { + // If the update type is RESTORE, it means that the update is from + // the pod checkpoints and may be incomplete. Do not mark the + // source as ready. + + // Mark the source ready after receiving at least one update from the + // source. Once all the sources are marked ready, various cleanup + // routines will start reclaiming resources. It is important that this + // takes place only after kubelet calls the update handler to process + // the update to ensure the internal pod cache is up-to-date. + kl.sourcesReady.AddSource(u.Source) + } case e := <-plegCh: if isSyncPodWorthy(e) { // PLEG event for a pod; sync it. @@ -2129,7 +2064,7 @@ func (kl *Kubelet) updateRuntimeUp() { } // rkt uses the legacy, non-CRI integration. Don't check the runtime // conditions for it. - if kl.kubeletConfiguration.ContainerRuntime != kubetypes.RktContainerRuntime { + if kl.containerRuntimeName != kubetypes.RktContainerRuntime { if s == nil { glog.Errorf("Container runtime status is nil") return @@ -2219,36 +2154,6 @@ func (kl *Kubelet) cleanUpContainersInPod(podID types.UID, exitedContainerID str } } -// Replace the nameserver in containerized-mounter's rootfs/etc/resolve.conf with kubelet.ClusterDNS -func (kl *Kubelet) setupDNSinContainerizedMounter(mounterPath string) { - resolvePath := filepath.Join(strings.TrimSuffix(mounterPath, "/mounter"), "rootfs", "etc", "resolv.conf") - dnsString := "" - for _, dns := range kl.clusterDNS { - dnsString = dnsString + fmt.Sprintf("nameserver %s\n", dns) - } - if kl.resolverConfig != "" { - f, err := os.Open(kl.resolverConfig) - defer f.Close() - if err != nil { - glog.Error("Could not open resolverConf file") - } else { - _, hostSearch, err := kl.parseResolvConf(f) - if err != nil { - glog.Errorf("Error for parsing the reslov.conf file: %v", err) - } else { - dnsString = dnsString + "search" - for _, search := range hostSearch { - dnsString = dnsString + fmt.Sprintf(" %s", search) - } - dnsString = dnsString + "\n" - } - } - } - if err := ioutil.WriteFile(resolvePath, []byte(dnsString), 0600); err != nil { - glog.Errorf("Could not write dns nameserver in file %s, with error %v", resolvePath, err) - } -} - // isSyncPodWorthy filters out events that are not worthy of pod syncing func isSyncPodWorthy(event *pleg.PodLifecycleEvent) bool { // ContatnerRemoved doesn't affect pod state diff --git a/pkg/kubelet/kubelet_getters.go b/pkg/kubelet/kubelet_getters.go index 525f94cb717..1b98f9189fb 100644 --- a/pkg/kubelet/kubelet_getters.go +++ b/pkg/kubelet/kubelet_getters.go @@ -64,6 +64,21 @@ func (kl *Kubelet) getPluginDir(pluginName string) string { return filepath.Join(kl.getPluginsDir(), pluginName) } +// getVolumeDevicePluginsDir returns the full path to the directory under which plugin +// directories are created. Plugins can use these directories for data that +// they need to persist. Plugins should create subdirectories under this named +// after their own names. +func (kl *Kubelet) getVolumeDevicePluginsDir() string { + return filepath.Join(kl.getRootDir(), config.DefaultKubeletPluginsDirName) +} + +// getVolumeDevicePluginDir returns a data directory name for a given plugin name. +// Plugins can use these directories to store data that they need to persist. +// For per-pod plugin data, see getVolumeDevicePluginsDir. +func (kl *Kubelet) getVolumeDevicePluginDir(pluginName string) string { + return filepath.Join(kl.getVolumeDevicePluginsDir(), pluginName, config.DefaultKubeletVolumeDevicesDirName) +} + // GetPodDir returns the full path to the per-pod data directory for the // specified pod. This directory may not exist if the pod does not exist. func (kl *Kubelet) GetPodDir(podUID types.UID) string { @@ -90,6 +105,19 @@ func (kl *Kubelet) getPodVolumeDir(podUID types.UID, pluginName string, volumeNa return filepath.Join(kl.getPodVolumesDir(podUID), pluginName, volumeName) } +// getPodVolumeDevicesDir returns the full path to the per-pod data directory under +// which volumes are created for the specified pod. This directory may not +// exist if the pod does not exist. +func (kl *Kubelet) getPodVolumeDevicesDir(podUID types.UID) string { + return filepath.Join(kl.getPodDir(podUID), config.DefaultKubeletVolumeDevicesDirName) +} + +// getPodVolumeDeviceDir returns the full path to the directory which represents the +// named plugin for specified pod. This directory may not exist if the pod does not exist. +func (kl *Kubelet) getPodVolumeDeviceDir(podUID types.UID, pluginName string) string { + return filepath.Join(kl.getPodVolumeDevicesDir(podUID), pluginName) +} + // getPodPluginsDir returns the full path to the per-pod data directory under // which plugins may store data for the specified pod. This directory may not // exist if the pod does not exist. @@ -151,9 +179,8 @@ func (kl *Kubelet) GetHostname() string { return kl.hostname } -// GetRuntime returns the current Runtime implementation in use by the kubelet. This func -// is exported to simplify integration with third party kubelet extensions (e.g. kubernetes-mesos). -func (kl *Kubelet) GetRuntime() kubecontainer.Runtime { +// getRuntime returns the current Runtime implementation in use by the kubelet. +func (kl *Kubelet) getRuntime() kubecontainer.Runtime { return kl.containerRuntime } @@ -219,7 +246,7 @@ func (kl *Kubelet) getPodVolumePathListFromDisk(podUID types.UID) ([]string, err if pathExists, pathErr := volumeutil.PathExists(podVolDir); pathErr != nil { return volumes, fmt.Errorf("Error checking if path %q exists: %v", podVolDir, pathErr) } else if !pathExists { - glog.Warningf("Warning: path %q does not exist: %q", podVolDir) + glog.Warningf("Path %q does not exist", podVolDir) return volumes, nil } diff --git a/pkg/kubelet/kubelet_network.go b/pkg/kubelet/kubelet_network.go index e8f0edab723..73fddb56e50 100644 --- a/pkg/kubelet/kubelet_network.go +++ b/pkg/kubelet/kubelet_network.go @@ -18,14 +18,13 @@ package kubelet import ( "fmt" - "io" - "io/ioutil" - "os" - "strings" "github.com/golang/glog" "k8s.io/api/core/v1" + clientset "k8s.io/client-go/kubernetes" + runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/kubelet/network" kubetypes "k8s.io/kubernetes/pkg/kubelet/types" utiliptables "k8s.io/kubernetes/pkg/util/iptables" @@ -46,6 +45,77 @@ const ( KubeFirewallChain utiliptables.Chain = "KUBE-FIREWALL" ) +// This just exports required functions from kubelet proper, for use by network +// plugins. +// TODO(#35457): get rid of this backchannel to the kubelet. The scope of +// the back channel is restricted to host-ports/testing, and restricted +// to kubenet. No other network plugin wrapper needs it. Other plugins +// only require a way to access namespace information, which they can do +// directly through the methods implemented by criNetworkHost. +type networkHost struct { + kubelet *Kubelet +} + +func (nh *networkHost) GetPodByName(name, namespace string) (*v1.Pod, bool) { + return nh.kubelet.GetPodByName(name, namespace) +} + +func (nh *networkHost) GetKubeClient() clientset.Interface { + return nh.kubelet.kubeClient +} + +func (nh *networkHost) GetRuntime() kubecontainer.Runtime { + return nh.kubelet.getRuntime() +} + +func (nh *networkHost) SupportsLegacyFeatures() bool { + return true +} + +// criNetworkHost implements the part of network.Host required by the +// cri (NamespaceGetter). It leechs off networkHost for all other +// methods, because networkHost is slated for deletion. +type criNetworkHost struct { + *networkHost + // criNetworkHost currently support legacy features. Hence no need to support PortMappingGetter + *network.NoopPortMappingGetter +} + +// GetNetNS returns the network namespace of the given containerID. +// This method satisfies the network.NamespaceGetter interface for +// networkHost. It's only meant to be used from network plugins +// that are directly invoked by the kubelet (aka: legacy, pre-cri). +// Any network plugin invoked by a cri must implement NamespaceGetter +// to talk directly to the runtime instead. +func (c *criNetworkHost) GetNetNS(containerID string) (string, error) { + return c.kubelet.getRuntime().GetNetNS(kubecontainer.ContainerID{Type: "", ID: containerID}) +} + +// NoOpLegacyHost implements the network.LegacyHost interface for the remote +// runtime shim by just returning empties. It doesn't support legacy features +// like host port and bandwidth shaping. +type NoOpLegacyHost struct{} + +// GetPodByName always returns "nil, true" for 'NoOpLegacyHost' +func (n *NoOpLegacyHost) GetPodByName(namespace, name string) (*v1.Pod, bool) { + return nil, true +} + +// GetKubeClient always returns "nil" for 'NoOpLegacyHost' +func (n *NoOpLegacyHost) GetKubeClient() clientset.Interface { + return nil +} + +// getRuntime always returns "nil" for 'NoOpLegacyHost' +func (n *NoOpLegacyHost) GetRuntime() kubecontainer.Runtime { + return nil +} + +// SupportsLegacyFeatures always returns "false" for 'NoOpLegacyHost' +func (n *NoOpLegacyHost) SupportsLegacyFeatures() bool { + return false +} + // effectiveHairpinMode determines the effective hairpin mode given the // configured mode, container runtime, and whether cbr0 should be configured. func effectiveHairpinMode(hairpinMode kubeletconfig.HairpinMode, containerRuntime string, networkPlugin string) (kubeletconfig.HairpinMode, error) { @@ -89,156 +159,6 @@ func (kl *Kubelet) providerRequiresNetworkingConfiguration() bool { return supported } -func omitDuplicates(kl *Kubelet, pod *v1.Pod, combinedSearch []string) []string { - uniqueDomains := map[string]bool{} - - for _, dnsDomain := range combinedSearch { - if _, exists := uniqueDomains[dnsDomain]; !exists { - combinedSearch[len(uniqueDomains)] = dnsDomain - uniqueDomains[dnsDomain] = true - } - } - return combinedSearch[:len(uniqueDomains)] -} - -func formDNSSearchFitsLimits(kl *Kubelet, pod *v1.Pod, composedSearch []string) []string { - // resolver file Search line current limitations - resolvSearchLineDNSDomainsLimit := 6 - resolvSearchLineLenLimit := 255 - limitsExceeded := false - - if len(composedSearch) > resolvSearchLineDNSDomainsLimit { - composedSearch = composedSearch[:resolvSearchLineDNSDomainsLimit] - limitsExceeded = true - } - - if resolvSearchhLineStrLen := len(strings.Join(composedSearch, " ")); resolvSearchhLineStrLen > resolvSearchLineLenLimit { - cutDomainsNum := 0 - cutDoaminsLen := 0 - for i := len(composedSearch) - 1; i >= 0; i-- { - cutDoaminsLen += len(composedSearch[i]) + 1 - cutDomainsNum++ - - if (resolvSearchhLineStrLen - cutDoaminsLen) <= resolvSearchLineLenLimit { - break - } - } - - composedSearch = composedSearch[:(len(composedSearch) - cutDomainsNum)] - limitsExceeded = true - } - - if limitsExceeded { - log := fmt.Sprintf("Search Line limits were exceeded, some dns names have been omitted, the applied search line is: %s", strings.Join(composedSearch, " ")) - kl.recorder.Event(pod, v1.EventTypeWarning, "DNSSearchForming", log) - glog.Error(log) - } - return composedSearch -} - -func (kl *Kubelet) formDNSSearchForDNSDefault(hostSearch []string, pod *v1.Pod) []string { - return formDNSSearchFitsLimits(kl, pod, hostSearch) -} - -func (kl *Kubelet) formDNSSearch(hostSearch []string, pod *v1.Pod) []string { - if kl.clusterDomain == "" { - formDNSSearchFitsLimits(kl, pod, hostSearch) - return hostSearch - } - - nsSvcDomain := fmt.Sprintf("%s.svc.%s", pod.Namespace, kl.clusterDomain) - svcDomain := fmt.Sprintf("svc.%s", kl.clusterDomain) - dnsSearch := []string{nsSvcDomain, svcDomain, kl.clusterDomain} - - combinedSearch := append(dnsSearch, hostSearch...) - - combinedSearch = omitDuplicates(kl, pod, combinedSearch) - return formDNSSearchFitsLimits(kl, pod, combinedSearch) -} - -func (kl *Kubelet) checkLimitsForResolvConf() { - // resolver file Search line current limitations - resolvSearchLineDNSDomainsLimit := 6 - resolvSearchLineLenLimit := 255 - - f, err := os.Open(kl.resolverConfig) - if err != nil { - kl.recorder.Event(kl.nodeRef, v1.EventTypeWarning, "checkLimitsForResolvConf", err.Error()) - glog.Error("checkLimitsForResolvConf: " + err.Error()) - return - } - defer f.Close() - - _, hostSearch, err := kl.parseResolvConf(f) - if err != nil { - kl.recorder.Event(kl.nodeRef, v1.EventTypeWarning, "checkLimitsForResolvConf", err.Error()) - glog.Error("checkLimitsForResolvConf: " + err.Error()) - return - } - - domainCntLimit := resolvSearchLineDNSDomainsLimit - - if kl.clusterDomain != "" { - domainCntLimit -= 3 - } - - if len(hostSearch) > domainCntLimit { - log := fmt.Sprintf("Resolv.conf file '%s' contains search line consisting of more than %d domains!", kl.resolverConfig, domainCntLimit) - kl.recorder.Event(kl.nodeRef, v1.EventTypeWarning, "checkLimitsForResolvConf", log) - glog.Error("checkLimitsForResolvConf: " + log) - return - } - - if len(strings.Join(hostSearch, " ")) > resolvSearchLineLenLimit { - log := fmt.Sprintf("Resolv.conf file '%s' contains search line which length is more than allowed %d chars!", kl.resolverConfig, resolvSearchLineLenLimit) - kl.recorder.Event(kl.nodeRef, v1.EventTypeWarning, "checkLimitsForResolvConf", log) - glog.Error("checkLimitsForResolvConf: " + log) - return - } - - return -} - -// parseResolveConf reads a resolv.conf file from the given reader, and parses -// it into nameservers and searches, possibly returning an error. -// TODO: move to utility package -func (kl *Kubelet) parseResolvConf(reader io.Reader) (nameservers []string, searches []string, err error) { - file, err := ioutil.ReadAll(reader) - if err != nil { - return nil, nil, err - } - - // Lines of the form "nameserver 1.2.3.4" accumulate. - nameservers = []string{} - - // Lines of the form "search example.com" overrule - last one wins. - searches = []string{} - - lines := strings.Split(string(file), "\n") - for l := range lines { - trimmed := strings.TrimSpace(lines[l]) - if strings.HasPrefix(trimmed, "#") { - continue - } - fields := strings.Fields(trimmed) - if len(fields) == 0 { - continue - } - if fields[0] == "nameserver" && len(fields) >= 2 { - nameservers = append(nameservers, fields[1]) - } - if fields[0] == "search" { - searches = fields[1:] - } - } - - // There used to be code here to scrub DNS for each cloud, but doesn't - // make sense anymore since cloudproviders are being factored out. - // contact @thockin or @wlan0 for more information - - return nameservers, searches, nil -} - // syncNetworkStatus updates the network state func (kl *Kubelet) syncNetworkStatus() { // For cri integration, network state will be updated in updateRuntimeUp, @@ -268,7 +188,7 @@ func (kl *Kubelet) updatePodCIDR(cidr string) { // kubelet -> generic runtime -> runtime shim -> network plugin // docker/rkt non-cri implementations have a passthrough UpdatePodCIDR - if err := kl.GetRuntime().UpdatePodCIDR(cidr); err != nil { + if err := kl.getRuntime().UpdatePodCIDR(cidr); err != nil { glog.Errorf("Failed to update pod CIDR: %v", err) return } @@ -361,3 +281,10 @@ func getIPTablesMark(bit int) string { value := 1 << uint(bit) return fmt.Sprintf("%#08x/%#08x", value, value) } + +// GetPodDNS returns DNS setttings 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) { + return kl.dnsConfigurer.GetPodDNS(pod) +} diff --git a/pkg/kubelet/kubelet_network_test.go b/pkg/kubelet/kubelet_network_test.go index 227a139e8a8..edc91af2b23 100644 --- a/pkg/kubelet/kubelet_network_test.go +++ b/pkg/kubelet/kubelet_network_test.go @@ -19,38 +19,132 @@ package kubelet import ( "fmt" "net" - "strings" "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "k8s.io/api/core/v1" - "k8s.io/client-go/tools/record" ) -func TestNodeIPParam(t *testing.T) { - testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) +func TestNetworkHostGetsPodNotFound(t *testing.T) { + testKubelet := newTestKubelet(t, true) defer testKubelet.Cleanup() - kubelet := testKubelet.kubelet - tests := []struct { + nh := networkHost{testKubelet.kubelet} + + actualPod, _ := nh.GetPodByName("", "") + if actualPod != nil { + t.Fatalf("Was expected nil, received %v instead", actualPod) + } +} + +func TestNetworkHostGetsKubeClient(t *testing.T) { + testKubelet := newTestKubelet(t, true) + defer testKubelet.Cleanup() + nh := networkHost{testKubelet.kubelet} + + if nh.GetKubeClient() != testKubelet.fakeKubeClient { + t.Fatalf("NetworkHost client does not match testKubelet's client") + } +} + +func TestNetworkHostGetsRuntime(t *testing.T) { + testKubelet := newTestKubelet(t, true) + defer testKubelet.Cleanup() + nh := networkHost{testKubelet.kubelet} + + if nh.GetRuntime() != testKubelet.fakeRuntime { + t.Fatalf("NetworkHost runtime does not match testKubelet's runtime") + } +} + +func TestNetworkHostSupportsLegacyFeatures(t *testing.T) { + testKubelet := newTestKubelet(t, true) + defer testKubelet.Cleanup() + nh := networkHost{testKubelet.kubelet} + + if nh.SupportsLegacyFeatures() == false { + t.Fatalf("SupportsLegacyFeatures should not be false") + } +} + +func TestNoOpHostGetsName(t *testing.T) { + nh := NoOpLegacyHost{} + pod, err := nh.GetPodByName("", "") + if pod != nil && err != true { + t.Fatalf("noOpLegacyHost getpodbyname expected to be nil and true") + } +} + +func TestNoOpHostGetsKubeClient(t *testing.T) { + nh := NoOpLegacyHost{} + if nh.GetKubeClient() != nil { + t.Fatalf("noOpLegacyHost client expected to be nil") + } +} + +func TestNoOpHostGetsRuntime(t *testing.T) { + nh := NoOpLegacyHost{} + if nh.GetRuntime() != nil { + t.Fatalf("noOpLegacyHost runtime expected to be nil") + } +} + +func TestNoOpHostSupportsLegacyFeatures(t *testing.T) { + nh := NoOpLegacyHost{} + if nh.SupportsLegacyFeatures() != false { + t.Fatalf("noOpLegacyHost legacy features expected to be false") + } +} + +func TestNodeIPParam(t *testing.T) { + type test struct { nodeIP string success bool testName string - }{ + } + tests := []test{ { nodeIP: "", - success: true, + success: false, testName: "IP not set", }, { nodeIP: "127.0.0.1", success: false, - testName: "loopback address", + testName: "IPv4 loopback address", }, { - nodeIP: "FE80::0202:B3FF:FE1E:8329", + nodeIP: "::1", success: false, - testName: "IPv6 address", + testName: "IPv6 loopback address", + }, + { + nodeIP: "224.0.0.1", + success: false, + testName: "multicast IPv4 address", + }, + { + nodeIP: "ff00::1", + success: false, + testName: "multicast IPv6 address", + }, + { + nodeIP: "169.254.0.1", + success: false, + testName: "IPv4 link-local unicast address", + }, + { + nodeIP: "fe80::0202:b3ff:fe1e:8329", + success: false, + testName: "IPv6 link-local unicast address", + }, + { + nodeIP: "0.0.0.0", + success: false, + testName: "Unspecified IPv4 address", + }, + { + nodeIP: "::", + success: false, + testName: "Unspecified IPv6 address", }, { nodeIP: "1.2.3.4", @@ -58,9 +152,31 @@ func TestNodeIPParam(t *testing.T) { testName: "IPv4 address that doesn't belong to host", }, } + addrs, err := net.InterfaceAddrs() + if err != nil { + assert.Error(t, err, fmt.Sprintf( + "Unable to obtain a list of the node's unicast interface addresses.")) + } + for _, addr := range addrs { + var ip net.IP + switch v := addr.(type) { + case *net.IPNet: + ip = v.IP + case *net.IPAddr: + ip = v.IP + } + if ip.IsLoopback() || ip.IsLinkLocalUnicast() { + break + } + successTest := test{ + nodeIP: ip.String(), + success: true, + testName: fmt.Sprintf("Success test case for address %s", ip.String()), + } + tests = append(tests, successTest) + } for _, test := range tests { - kubelet.nodeIP = net.ParseIP(test.nodeIP) - err := kubelet.validateNodeIP() + err := validateNodeIP(net.ParseIP(test.nodeIP)) if test.success { assert.NoError(t, err, "test %s", test.testName) } else { @@ -69,115 +185,6 @@ func TestNodeIPParam(t *testing.T) { } } -func TestParseResolvConf(t *testing.T) { - testCases := []struct { - data string - nameservers []string - searches []string - }{ - {"", []string{}, []string{}}, - {" ", []string{}, []string{}}, - {"\n", []string{}, []string{}}, - {"\t\n\t", []string{}, []string{}}, - {"#comment\n", []string{}, []string{}}, - {" #comment\n", []string{}, []string{}}, - {"#comment\n#comment", []string{}, []string{}}, - {"#comment\nnameserver", []string{}, []string{}}, - {"#comment\nnameserver\nsearch", []string{}, []string{}}, - {"nameserver 1.2.3.4", []string{"1.2.3.4"}, []string{}}, - {" nameserver 1.2.3.4", []string{"1.2.3.4"}, []string{}}, - {"\tnameserver 1.2.3.4", []string{"1.2.3.4"}, []string{}}, - {"nameserver\t1.2.3.4", []string{"1.2.3.4"}, []string{}}, - {"nameserver \t 1.2.3.4", []string{"1.2.3.4"}, []string{}}, - {"nameserver 1.2.3.4\nnameserver 5.6.7.8", []string{"1.2.3.4", "5.6.7.8"}, []string{}}, - {"nameserver 1.2.3.4 #comment", []string{"1.2.3.4"}, []string{}}, - {"search foo", []string{}, []string{"foo"}}, - {"search foo bar", []string{}, []string{"foo", "bar"}}, - {"search foo bar bat\n", []string{}, []string{"foo", "bar", "bat"}}, - {"search foo\nsearch bar", []string{}, []string{"bar"}}, - {"nameserver 1.2.3.4\nsearch foo bar", []string{"1.2.3.4"}, []string{"foo", "bar"}}, - {"nameserver 1.2.3.4\nsearch foo\nnameserver 5.6.7.8\nsearch bar", []string{"1.2.3.4", "5.6.7.8"}, []string{"bar"}}, - {"#comment\nnameserver 1.2.3.4\n#comment\nsearch foo\ncomment", []string{"1.2.3.4"}, []string{"foo"}}, - } - testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) - defer testKubelet.Cleanup() - kubelet := testKubelet.kubelet - for i, tc := range testCases { - ns, srch, err := kubelet.parseResolvConf(strings.NewReader(tc.data)) - require.NoError(t, err) - assert.EqualValues(t, tc.nameservers, ns, "test case [%d]: name servers", i) - assert.EqualValues(t, tc.searches, srch, "test case [%d] searches", i) - } -} - -func TestComposeDNSSearch(t *testing.T) { - testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) - defer testKubelet.Cleanup() - kubelet := testKubelet.kubelet - - recorder := record.NewFakeRecorder(20) - kubelet.recorder = recorder - - pod := podWithUIDNameNs("", "test_pod", "testNS") - kubelet.clusterDomain = "TEST" - - testCases := []struct { - dnsNames []string - hostNames []string - resultSearch []string - events []string - }{ - { - []string{"testNS.svc.TEST", "svc.TEST", "TEST"}, - []string{}, - []string{"testNS.svc.TEST", "svc.TEST", "TEST"}, - []string{}, - }, - - { - []string{"testNS.svc.TEST", "svc.TEST", "TEST"}, - []string{"AAA", "svc.TEST", "BBB", "TEST"}, - []string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA", "BBB"}, - []string{}, - }, - - { - []string{"testNS.svc.TEST", "svc.TEST", "TEST"}, - []string{"AAA", strings.Repeat("B", 256), "BBB"}, - []string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA"}, - []string{"Search Line limits were exceeded, some dns names have been omitted, the applied search line is: testNS.svc.TEST svc.TEST TEST AAA"}, - }, - - { - []string{"testNS.svc.TEST", "svc.TEST", "TEST"}, - []string{"AAA", "TEST", "BBB", "TEST", "CCC", "DDD"}, - []string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA", "BBB", "CCC"}, - []string{ - "Search Line limits were exceeded, some dns names have been omitted, the applied search line is: testNS.svc.TEST svc.TEST TEST AAA BBB CCC", - }, - }, - } - - fetchEvent := func(recorder *record.FakeRecorder) string { - select { - case event := <-recorder.Events: - return event - default: - return "No more events!" - } - } - - for i, tc := range testCases { - dnsSearch := kubelet.formDNSSearch(tc.hostNames, pod) - assert.EqualValues(t, tc.resultSearch, dnsSearch, "test [%d]", i) - for _, expectedEvent := range tc.events { - expected := fmt.Sprintf("%s %s %s", v1.EventTypeWarning, "DNSSearchForming", expectedEvent) - event := fetchEvent(recorder) - assert.Equal(t, expected, event, "test [%d]", i) - } - } -} - func TestGetIPTablesMark(t *testing.T) { tests := []struct { bit int diff --git a/pkg/kubelet/kubelet_node_status.go b/pkg/kubelet/kubelet_node_status.go index f87d14b8c38..7094b66f996 100644 --- a/pkg/kubelet/kubelet_node_status.go +++ b/pkg/kubelet/kubelet_node_status.go @@ -33,8 +33,8 @@ import ( "k8s.io/apimachinery/pkg/types" utilnet "k8s.io/apimachinery/pkg/util/net" utilfeature "k8s.io/apiserver/pkg/util/feature" - k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" + k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" "k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/features" kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis" @@ -42,10 +42,10 @@ import ( "k8s.io/kubernetes/pkg/kubelet/events" "k8s.io/kubernetes/pkg/kubelet/util" "k8s.io/kubernetes/pkg/kubelet/util/sliceutils" + "k8s.io/kubernetes/pkg/scheduler/algorithm" nodeutil "k8s.io/kubernetes/pkg/util/node" "k8s.io/kubernetes/pkg/version" "k8s.io/kubernetes/pkg/volume/util/volumehelper" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" ) const ( @@ -97,7 +97,7 @@ func (kl *Kubelet) registerWithAPIServer() { // a different externalID value, it attempts to delete that node so that a // later attempt can recreate it. func (kl *Kubelet) tryRegisterWithAPIServer(node *v1.Node) bool { - _, err := kl.kubeClient.Core().Nodes().Create(node) + _, err := kl.kubeClient.CoreV1().Nodes().Create(node) if err == nil { return true } @@ -107,7 +107,7 @@ func (kl *Kubelet) tryRegisterWithAPIServer(node *v1.Node) bool { return false } - existingNode, err := kl.kubeClient.Core().Nodes().Get(string(kl.nodeName), metav1.GetOptions{}) + existingNode, err := kl.kubeClient.CoreV1().Nodes().Get(string(kl.nodeName), metav1.GetOptions{}) if err != nil { glog.Errorf("Unable to register node %q with API server: error getting existing node: %v", kl.nodeName, err) return false @@ -132,8 +132,7 @@ func (kl *Kubelet) tryRegisterWithAPIServer(node *v1.Node) bool { requiresUpdate := kl.reconcileCMADAnnotationWithExistingNode(node, existingNode) requiresUpdate = kl.updateDefaultLabels(node, existingNode) || requiresUpdate if requiresUpdate { - if _, err := nodeutil.PatchNodeStatus(kl.kubeClient.CoreV1(), types.NodeName(kl.nodeName), - originalNode, existingNode); err != nil { + if _, _, err := nodeutil.PatchNodeStatus(kl.kubeClient.CoreV1(), types.NodeName(kl.nodeName), originalNode, existingNode); err != nil { glog.Errorf("Unable to reconcile node %q with API server: error updating node: %v", kl.nodeName, err) return false } @@ -142,11 +141,10 @@ func (kl *Kubelet) tryRegisterWithAPIServer(node *v1.Node) bool { return true } - glog.Errorf( - "Previously node %q had externalID %q; now it is %q; will delete and recreate.", + glog.Errorf("Previously node %q had externalID %q; now it is %q; will delete and recreate.", kl.nodeName, node.Spec.ExternalID, existingNode.Spec.ExternalID, ) - if err := kl.kubeClient.Core().Nodes().Delete(node.Name, nil); err != nil { + if err := kl.kubeClient.CoreV1().Nodes().Delete(node.Name, nil); err != nil { glog.Errorf("Unable to register node %q with API server: error deleting old node: %v", kl.nodeName, err) } else { glog.Infof("Deleted old node object %q", kl.nodeName) @@ -233,10 +231,10 @@ func (kl *Kubelet) initialNode() (*v1.Node, error) { }, } nodeTaints := make([]v1.Taint, 0) - if len(kl.kubeletConfiguration.RegisterWithTaints) > 0 { - taints := make([]v1.Taint, len(kl.kubeletConfiguration.RegisterWithTaints)) - for i := range kl.kubeletConfiguration.RegisterWithTaints { - if err := k8s_api_v1.Convert_api_Taint_To_v1_Taint(&kl.kubeletConfiguration.RegisterWithTaints[i], &taints[i], nil); err != nil { + if len(kl.registerWithTaints) > 0 { + taints := make([]v1.Taint, len(kl.registerWithTaints)) + for i := range kl.registerWithTaints { + if err := k8s_api_v1.Convert_core_Taint_To_v1_Taint(&kl.registerWithTaints[i], &taints[i], nil); err != nil { return nil, err } } @@ -347,13 +345,6 @@ func (kl *Kubelet) initialNode() (*v1.Node, error) { } } else { node.Spec.ExternalID = kl.hostname - if kl.autoDetectCloudProvider { - // If no cloud provider is defined - use the one detected by cadvisor - info, err := kl.GetCachedMachineInfo() - if err == nil { - kl.updateCloudProviderFromMachineInfo(node, info) - } - } } kl.setNodeStatus(node) @@ -415,7 +406,7 @@ func (kl *Kubelet) tryUpdateNodeStatus(tryNumber int) error { kl.setNodeStatus(node) // Patch the current status on the API server - updatedNode, err := nodeutil.PatchNodeStatus(kl.heartbeatClient, types.NodeName(kl.nodeName), originalNode, node) + updatedNode, _, err := nodeutil.PatchNodeStatus(kl.heartbeatClient, types.NodeName(kl.nodeName), originalNode, node) if err != nil { return err } @@ -437,7 +428,7 @@ func (kl *Kubelet) recordNodeStatusEvent(eventType, event string) { // Set IP and hostname addresses for the node. func (kl *Kubelet) setNodeAddress(node *v1.Node) error { if kl.nodeIP != nil { - if err := kl.validateNodeIP(); err != nil { + if err := validateNodeIP(kl.nodeIP); err != nil { return fmt.Errorf("failed to validate nodeIP: %v", err) } glog.V(2).Infof("Using node IP: %q", kl.nodeIP.String()) @@ -445,6 +436,9 @@ func (kl *Kubelet) setNodeAddress(node *v1.Node) error { if kl.externalCloudProvider { if kl.nodeIP != nil { + if node.ObjectMeta.Annotations == nil { + node.ObjectMeta.Annotations = make(map[string]string) + } node.ObjectMeta.Annotations[kubeletapis.AnnotationProvidedIPAddr] = kl.nodeIP.String() } // We rely on the external cloud provider to supply the addresses. @@ -500,7 +494,8 @@ func (kl *Kubelet) setNodeAddress(node *v1.Node) error { // 1) Use nodeIP if set // 2) If the user has specified an IP to HostnameOverride, use it - // 3) Lookup the IP from node name by DNS and use the first non-loopback ipv4 address + // 3) Lookup the IP from node name by DNS and use the first valid IPv4 address. + // If the node does not have a valid IPv4 address, use the first valid IPv6 address. // 4) Try to get the IP from the network interface used as default gateway if kl.nodeIP != nil { ipAddr = kl.nodeIP @@ -508,11 +503,16 @@ func (kl *Kubelet) setNodeAddress(node *v1.Node) error { ipAddr = addr } else { var addrs []net.IP - addrs, err = net.LookupIP(node.Name) + addrs, _ = net.LookupIP(node.Name) for _, addr := range addrs { - if !addr.IsLoopback() && addr.To4() != nil { - ipAddr = addr - break + if err = validateNodeIP(addr); err == nil { + if addr.To4() != nil { + ipAddr = addr + break + } + if addr.To16() != nil && ipAddr == nil { + ipAddr = addr + } } } @@ -592,15 +592,17 @@ func (kl *Kubelet) setNodeStatusMachineInfo(node *v1.Node) { } } - currentCapacity := kl.containerManager.GetCapacity() - if currentCapacity != nil { - for k, v := range currentCapacity { - if v1helper.IsExtendedResourceName(k) { - glog.V(2).Infof("Update capacity for %s to %d", k, v.Value()) - node.Status.Capacity[k] = v - } + devicePluginCapacity, removedDevicePlugins := kl.containerManager.GetDevicePluginResourceCapacity() + if devicePluginCapacity != nil { + for k, v := range devicePluginCapacity { + glog.V(2).Infof("Update capacity for %s to %d", k, v.Value()) + node.Status.Capacity[k] = v } } + for _, removedResource := range removedDevicePlugins { + glog.V(2).Infof("Remove capacity for %s", removedResource) + delete(node.Status.Capacity, v1.ResourceName(removedResource)) + } } // Set Allocatable. @@ -988,17 +990,22 @@ func (kl *Kubelet) defaultNodeStatusFuncs() []func(*v1.Node) error { } // Validate given node IP belongs to the current host -func (kl *Kubelet) validateNodeIP() error { - if kl.nodeIP == nil { - return nil - } - +func validateNodeIP(nodeIP net.IP) error { // Honor IP limitations set in setNodeStatus() - if kl.nodeIP.IsLoopback() { + if nodeIP.To4() == nil && nodeIP.To16() == nil { + return fmt.Errorf("nodeIP must be a valid IP address") + } + if nodeIP.IsLoopback() { return fmt.Errorf("nodeIP can't be loopback address") } - if kl.nodeIP.To4() == nil { - return fmt.Errorf("nodeIP must be IPv4 address") + if nodeIP.IsMulticast() { + return fmt.Errorf("nodeIP can't be a multicast address") + } + if nodeIP.IsLinkLocalUnicast() { + return fmt.Errorf("nodeIP can't be a link-local unicast address") + } + if nodeIP.IsUnspecified() { + return fmt.Errorf("nodeIP can't be an all zeros address") } addrs, err := net.InterfaceAddrs() @@ -1013,9 +1020,9 @@ func (kl *Kubelet) validateNodeIP() error { case *net.IPAddr: ip = v.IP } - if ip != nil && ip.Equal(kl.nodeIP) { + if ip != nil && ip.Equal(nodeIP) { return nil } } - return fmt.Errorf("Node IP: %q not found in the host's network interfaces", kl.nodeIP.String()) + return fmt.Errorf("Node IP: %q not found in the host's network interfaces", nodeIP.String()) } diff --git a/pkg/kubelet/kubelet_node_status_test.go b/pkg/kubelet/kubelet_node_status_test.go index 87172759441..38636f70e1d 100644 --- a/pkg/kubelet/kubelet_node_status_test.go +++ b/pkg/kubelet/kubelet_node_status_test.go @@ -144,7 +144,7 @@ func TestNodeStatusWithCloudProviderNodeIP(t *testing.T) { Spec: v1.NodeSpec{}, } - // TODO : is it possible to mock kubelet.validateNodeIP() to avoid relying on the host interface addresses ? + // TODO : is it possible to mock validateNodeIP() to avoid relying on the host interface addresses ? addrs, err := net.InterfaceAddrs() assert.NoError(t, err) for _, addr := range addrs { diff --git a/pkg/kubelet/kubelet_pods.go b/pkg/kubelet/kubelet_pods.go index c213af0b5c5..40340934920 100644 --- a/pkg/kubelet/kubelet_pods.go +++ b/pkg/kubelet/kubelet_pods.go @@ -40,15 +40,13 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" utilvalidation "k8s.io/apimachinery/pkg/util/validation" - "k8s.io/apimachinery/pkg/util/validation/field" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/tools/remotecommand" - "k8s.io/kubernetes/pkg/api" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" - v1qos "k8s.io/kubernetes/pkg/api/v1/helper/qos" podutil "k8s.io/kubernetes/pkg/api/v1/pod" "k8s.io/kubernetes/pkg/api/v1/resource" - "k8s.io/kubernetes/pkg/api/v1/validation" + podshelper "k8s.io/kubernetes/pkg/apis/core/pods" + v1helper "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/fieldpath" runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" @@ -64,6 +62,7 @@ import ( "k8s.io/kubernetes/pkg/kubelet/util/format" utilfile "k8s.io/kubernetes/pkg/util/file" "k8s.io/kubernetes/pkg/volume" + volumeutil "k8s.io/kubernetes/pkg/volume/util" "k8s.io/kubernetes/pkg/volume/util/volumehelper" volumevalidation "k8s.io/kubernetes/pkg/volume/validation" "k8s.io/kubernetes/third_party/forked/golang/expansion" @@ -91,9 +90,9 @@ func (kl *Kubelet) GetActivePods() []*v1.Pod { return activePods } -// makeDevices determines the devices for the given container. +// makeGPUDevices determines the devices for the given container. // Experimental. -func (kl *Kubelet) makeDevices(pod *v1.Pod, container *v1.Container) ([]kubecontainer.DeviceInfo, error) { +func (kl *Kubelet) makeGPUDevices(pod *v1.Pod, container *v1.Container) ([]kubecontainer.DeviceInfo, error) { if container.Resources.Limits.NvidiaGPU().IsZero() { return nil, nil } @@ -111,6 +110,56 @@ func (kl *Kubelet) makeDevices(pod *v1.Pod, container *v1.Container) ([]kubecont return devices, nil } +func makeAbsolutePath(goos, path string) string { + if goos != "windows" { + return "/" + path + } + // These are all for windows + // If there is a colon, give up. + if strings.Contains(path, ":") { + return path + } + // If there is a slash, but no drive, add 'c:' + if strings.HasPrefix(path, "/") || strings.HasPrefix(path, "\\") { + return "c:" + path + } + // Otherwise, add 'c:\' + return "c:\\" + path +} + +// 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 volumeutil.BlockVolumePathHandler) ([]kubecontainer.DeviceInfo, error) { + var devices []kubecontainer.DeviceInfo + for _, device := range container.VolumeDevices { + // check path is absolute + if !filepath.IsAbs(device.DevicePath) { + return nil, fmt.Errorf("error DevicePath `%s` must be an absolute path", device.DevicePath) + } + vol, ok := podVolumes[device.Name] + if !ok || vol.BlockVolumeMapper == nil { + glog.Errorf("Block volume cannot be satisfied for container %q, because the volume is missing or the volume mapper is nil: %+v", container.Name, 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 + dirPath, volName := vol.BlockVolumeMapper.GetPodDeviceMapPath() + symlinkPath := path.Join(dirPath, volName) + if islinkExist, checkErr := blkutil.IsSymlinkExist(symlinkPath); checkErr != nil { + return nil, checkErr + } else if islinkExist { + // Check readOnly in PVCVolumeSource and set read only permission if it's true. + permission := "mrw" + if vol.ReadOnly { + permission = "r" + } + glog.V(4).Infof("Device will be attached to container %q. Path on host: %v", container.Name, symlinkPath) + devices = append(devices, kubecontainer.DeviceInfo{PathOnHost: symlinkPath, PathInContainer: device.DevicePath, Permissions: permission}) + } + } + + return devices, nil +} + // makeMounts determines the mount points for the given container. func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, hostDomain, podIP string, podVolumes kubecontainer.VolumeMap) ([]kubecontainer.Mount, error) { // Kubernetes only mounts on /etc/hosts if: @@ -187,9 +236,9 @@ func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, h if (strings.HasPrefix(hostPath, "/") || strings.HasPrefix(hostPath, "\\")) && !strings.Contains(hostPath, ":") { hostPath = "c:" + hostPath } - if (strings.HasPrefix(containerPath, "/") || strings.HasPrefix(containerPath, "\\")) && !strings.Contains(containerPath, ":") { - containerPath = "c:" + containerPath - } + } + if !filepath.IsAbs(containerPath) { + containerPath = makeAbsolutePath(runtime.GOOS, containerPath) } propagation, err := translateMountPropagation(mount.MountPropagation) @@ -346,7 +395,7 @@ func truncatePodHostnameIfNeeded(podName, hostname string) (string, error) { // given that pod's spec and annotations or returns an error. func (kl *Kubelet) GeneratePodHostNameAndDomain(pod *v1.Pod) (string, string, error) { // TODO(vmarmol): Handle better. - clusterDomain := kl.clusterDomain + clusterDomain := kl.dnsConfigurer.ClusterDomain hostname := pod.Name if len(pod.Spec.Hostname) > 0 { @@ -381,18 +430,17 @@ 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(pod *v1.Pod, container *v1.Container, podIP string) (*kubecontainer.RunContainerOptions, bool, error) { - useClusterFirstPolicy := false - opts, err := kl.containerManager.GetResources(pod, container, kl.GetActivePods()) +func (kl *Kubelet) GenerateRunContainerOptions(pod *v1.Pod, container *v1.Container, podIP string) (*kubecontainer.RunContainerOptions, error) { + opts, err := kl.containerManager.GetResources(pod, container) if err != nil { - return nil, false, err + return nil, err } cgroupParent := kl.GetPodCgroupParent(pod) opts.CgroupParent = cgroupParent hostname, hostDomainName, err := kl.GeneratePodHostNameAndDomain(pod) if err != nil { - return nil, false, err + return nil, err } opts.Hostname = hostname podName := volumehelper.GetUniquePodName(pod) @@ -400,21 +448,31 @@ func (kl *Kubelet) GenerateRunContainerOptions(pod *v1.Pod, container *v1.Contai opts.PortMappings = kubecontainer.MakePortMappings(container) // TODO(random-liu): Move following convert functions into pkg/kubelet/container - devices, err := kl.makeDevices(pod, container) + devices, err := kl.makeGPUDevices(pod, container) if err != nil { - return nil, false, err + return nil, err } opts.Devices = append(opts.Devices, devices...) + // TODO: remove feature gate check after no longer needed + if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + blkutil := volumeutil.NewBlockVolumePathHandler() + blkVolumes, err := kl.makeBlockVolumes(pod, container, volumes, blkutil) + if err != nil { + return nil, err + } + opts.Devices = append(opts.Devices, blkVolumes...) + } + mounts, err := makeMounts(pod, kl.getPodDir(pod.UID), container, hostname, hostDomainName, podIP, volumes) if err != nil { - return nil, false, err + return nil, err } opts.Mounts = append(opts.Mounts, mounts...) envs, err := kl.makeEnvironmentVariables(pod, container, podIP) if err != nil { - return nil, false, err + return nil, err } opts.Envs = append(opts.Envs, envs...) @@ -429,17 +487,12 @@ func (kl *Kubelet) GenerateRunContainerOptions(pod *v1.Pod, container *v1.Contai } } - opts.DNS, opts.DNSSearch, useClusterFirstPolicy, err = kl.GetClusterDNS(pod) - if err != nil { - return nil, false, err - } - // only do this check if the experimental behavior is enabled, otherwise allow it to default to false if kl.experimentalHostUserNamespaceDefaulting { opts.EnableHostUserNamespace = kl.enableHostUserNamespace(pod) } - return opts, useClusterFirstPolicy, nil + return opts, nil } var masterServices = sets.NewString("kubernetes") @@ -722,7 +775,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) (string, error) { - internalFieldPath, _, err := api.Scheme.ConvertFieldLabel(fs.APIVersion, "Pod", fs.FieldPath, "") + internalFieldPath, _, err := podshelper.ConvertDownwardAPIFieldLabel(fs.APIVersion, fs.FieldPath, "") if err != nil { return "", err } @@ -759,7 +812,7 @@ func (kl *Kubelet) killPod(pod *v1.Pod, runningPod *kubecontainer.Pod, status *k if runningPod != nil { p = *runningPod } else if status != nil { - p = kubecontainer.ConvertPodStatusToRunningPod(kl.GetRuntime().Type(), status) + p = kubecontainer.ConvertPodStatusToRunningPod(kl.getRuntime().Type(), status) } else { return fmt.Errorf("one of the two arguments must be non-nil: runningPod, status") } @@ -1054,22 +1107,6 @@ func (kl *Kubelet) podKiller() { } } -// hasHostPortConflicts detects pods with conflicted host ports. -func hasHostPortConflicts(pods []*v1.Pod) bool { - ports := sets.String{} - for _, pod := range pods { - if errs := validation.AccumulateUniqueHostPorts(pod.Spec.Containers, &ports, field.NewPath("spec", "containers")); len(errs) > 0 { - glog.Errorf("Pod %q: HostPort is already allocated, ignoring: %v", format.Pod(pod), errs) - return true - } - if errs := validation.AccumulateUniqueHostPorts(pod.Spec.InitContainers, &ports, field.NewPath("spec", "initContainers")); len(errs) > 0 { - glog.Errorf("Pod %q: HostPort is already allocated, ignoring: %v", format.Pod(pod), errs) - return true - } - } - return false -} - // validateContainerLogStatus returns the container ID for the desired container to retrieve logs for, based on the state // of the container. The previous flag will only return the logs for the last terminated container, otherwise, the current // running container is preferred over a previous termination. If info about the container is not available then a specific @@ -1176,10 +1213,8 @@ func (kl *Kubelet) GetKubeletContainerLogs(podFullName, containerName string, lo return kl.containerRuntime.GetContainerLogs(pod, containerID, logOptions, stdout, stderr) } -// GetPhase returns the phase of a pod given its container info. -// This func is exported to simplify integration with 3rd party kubelet -// integrations like kubernetes-mesos. -func GetPhase(spec *v1.PodSpec, info []v1.ContainerStatus) v1.PodPhase { +// getPhase returns the phase of a pod given its container info. +func getPhase(spec *v1.PodSpec, info []v1.ContainerStatus) v1.PodPhase { initialized := 0 pendingInitialization := 0 failedInitialization := 0 @@ -1309,7 +1344,7 @@ func (kl *Kubelet) generateAPIPodStatus(pod *v1.Pod, podStatus *kubecontainer.Po // Assume info is ready to process spec := &pod.Spec allStatus := append(append([]v1.ContainerStatus{}, s.ContainerStatuses...), s.InitContainerStatuses...) - s.Phase = GetPhase(spec, allStatus) + s.Phase = getPhase(spec, allStatus) kl.probeManager.UpdatePodStatus(pod.UID, s) s.Conditions = append(s.Conditions, status.GeneratePodInitializedCondition(spec, s.InitContainerStatuses, s.Phase)) s.Conditions = append(s.Conditions, status.GeneratePodReadyCondition(spec, s.ContainerStatuses, s.Phase)) @@ -1348,16 +1383,21 @@ func (kl *Kubelet) convertStatusToAPIStatus(pod *v1.Pod, podStatus *kubecontaine // set status for Pods created on versions of kube older than 1.6 apiPodStatus.QOSClass = v1qos.GetPodQOS(pod) + oldPodStatus, found := kl.statusManager.GetPodStatus(pod.UID) + if !found { + oldPodStatus = pod.Status + } + apiPodStatus.ContainerStatuses = kl.convertToAPIContainerStatuses( pod, podStatus, - pod.Status.ContainerStatuses, + oldPodStatus.ContainerStatuses, pod.Spec.Containers, len(pod.Spec.InitContainers) > 0, false, ) apiPodStatus.InitContainerStatuses = kl.convertToAPIContainerStatuses( pod, podStatus, - pod.Status.InitContainerStatuses, + oldPodStatus.InitContainerStatuses, pod.Spec.InitContainers, len(pod.Spec.InitContainers) > 0, true, @@ -1424,7 +1464,7 @@ func (kl *Kubelet) convertToAPIContainerStatuses(pod *v1.Pod, podStatus *kubecon } oldStatus, found := oldStatuses[container.Name] if found { - if isInitContainer && oldStatus.State.Terminated != nil { + if oldStatus.State.Terminated != nil { // Do not update status on terminated init containers as // they be removed at any time. status = &oldStatus @@ -1772,13 +1812,13 @@ func hasHostNamespace(pod *v1.Pod) bool { func (kl *Kubelet) hasHostMountPVC(pod *v1.Pod) bool { for _, volume := range pod.Spec.Volumes { if volume.PersistentVolumeClaim != nil { - pvc, err := kl.kubeClient.Core().PersistentVolumeClaims(pod.Namespace).Get(volume.PersistentVolumeClaim.ClaimName, metav1.GetOptions{}) + pvc, err := kl.kubeClient.CoreV1().PersistentVolumeClaims(pod.Namespace).Get(volume.PersistentVolumeClaim.ClaimName, metav1.GetOptions{}) if err != nil { glog.Warningf("unable to retrieve pvc %s:%s - %v", pod.Namespace, volume.PersistentVolumeClaim.ClaimName, err) continue } if pvc != nil { - referencedVolume, err := kl.kubeClient.Core().PersistentVolumes().Get(pvc.Spec.VolumeName, metav1.GetOptions{}) + referencedVolume, err := kl.kubeClient.CoreV1().PersistentVolumes().Get(pvc.Spec.VolumeName, metav1.GetOptions{}) if err != nil { glog.Warningf("unable to retrieve pv %s - %v", pvc.Spec.VolumeName, err) continue diff --git a/pkg/kubelet/kubelet_pods_test.go b/pkg/kubelet/kubelet_pods_test.go index a2c47fee4b6..a993ca2a845 100644 --- a/pkg/kubelet/kubelet_pods_test.go +++ b/pkg/kubelet/kubelet_pods_test.go @@ -21,7 +21,6 @@ import ( "errors" "fmt" "io/ioutil" - "net" "os" "path/filepath" "sort" @@ -38,18 +37,65 @@ import ( utilfeature "k8s.io/apiserver/pkg/util/feature" core "k8s.io/client-go/testing" "k8s.io/client-go/tools/record" - "k8s.io/kubernetes/pkg/api" // TODO: remove this import if // api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String() is changed // to "v1"? - _ "k8s.io/kubernetes/pkg/api/install" + "k8s.io/kubernetes/pkg/api/legacyscheme" + _ "k8s.io/kubernetes/pkg/apis/core/install" runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" "k8s.io/kubernetes/pkg/kubelet/server/portforward" "k8s.io/kubernetes/pkg/kubelet/server/remotecommand" + volumetest "k8s.io/kubernetes/pkg/volume/testing" ) +func TestMakeAbsolutePath(t *testing.T) { + tests := []struct { + goos string + path string + expectedPath string + name string + }{ + { + goos: "linux", + path: "non-absolute/path", + expectedPath: "/non-absolute/path", + name: "basic linux", + }, + { + goos: "windows", + path: "some\\path", + expectedPath: "c:\\some\\path", + name: "basic windows", + }, + { + goos: "windows", + path: "/some/path", + expectedPath: "c:/some/path", + name: "linux path on windows", + }, + { + goos: "windows", + path: "\\some\\path", + expectedPath: "c:\\some\\path", + name: "windows path no drive", + }, + { + goos: "windows", + path: "\\:\\some\\path", + expectedPath: "\\:\\some\\path", + name: "windows path with colon", + }, + } + for _, test := range tests { + path := makeAbsolutePath(test.goos, test.path) + if path != test.expectedPath { + t.Errorf("[%s] Expected %s saw %s", test.name, test.expectedPath, path) + } + } +} + func TestMakeMounts(t *testing.T) { bTrue := true propagationHostToContainer := v1.MountPropagationHostToContainer @@ -307,6 +353,139 @@ func TestMakeMounts(t *testing.T) { } } +func TestMakeBlockVolumes(t *testing.T) { + testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) + defer testKubelet.Cleanup() + kubelet := testKubelet.kubelet + testCases := map[string]struct { + container v1.Container + podVolumes kubecontainer.VolumeMap + expectErr bool + expectedErrMsg string + expectedDevices []kubecontainer.DeviceInfo + }{ + "valid volumeDevices in container": { + podVolumes: kubecontainer.VolumeMap{ + "disk1": kubecontainer.VolumeInfo{BlockVolumeMapper: &stubBlockVolume{dirPath: "/dev/", volName: "sda"}}, + "disk2": kubecontainer.VolumeInfo{BlockVolumeMapper: &stubBlockVolume{dirPath: "/dev/disk/by-path/", volName: "diskPath"}, ReadOnly: true}, + "disk3": kubecontainer.VolumeInfo{BlockVolumeMapper: &stubBlockVolume{dirPath: "/dev/disk/by-id/", volName: "diskUuid"}}, + "disk4": kubecontainer.VolumeInfo{BlockVolumeMapper: &stubBlockVolume{dirPath: "/var/lib/", volName: "rawdisk"}, ReadOnly: true}, + }, + container: v1.Container{ + Name: "container1", + VolumeDevices: []v1.VolumeDevice{ + { + DevicePath: "/dev/sda", + Name: "disk1", + }, + { + DevicePath: "/dev/xvda", + Name: "disk2", + }, + { + DevicePath: "/dev/xvdb", + Name: "disk3", + }, + { + DevicePath: "/mnt/rawdisk", + Name: "disk4", + }, + }, + }, + expectedDevices: []kubecontainer.DeviceInfo{ + { + PathInContainer: "/dev/sda", + PathOnHost: "/dev/sda", + Permissions: "mrw", + }, + { + PathInContainer: "/dev/xvda", + PathOnHost: "/dev/disk/by-path/diskPath", + Permissions: "r", + }, + { + PathInContainer: "/dev/xvdb", + PathOnHost: "/dev/disk/by-id/diskUuid", + Permissions: "mrw", + }, + { + PathInContainer: "/mnt/rawdisk", + PathOnHost: "/var/lib/rawdisk", + Permissions: "r", + }, + }, + expectErr: false, + }, + "invalid absolute Path": { + podVolumes: kubecontainer.VolumeMap{ + "disk": kubecontainer.VolumeInfo{BlockVolumeMapper: &stubBlockVolume{dirPath: "/dev/", volName: "sda"}}, + }, + container: v1.Container{ + VolumeDevices: []v1.VolumeDevice{ + { + DevicePath: "must/be/absolute", + Name: "disk", + }, + }, + }, + expectErr: true, + expectedErrMsg: "error DevicePath `must/be/absolute` must be an absolute path", + }, + "volume doesn't exist": { + podVolumes: kubecontainer.VolumeMap{}, + container: v1.Container{ + VolumeDevices: []v1.VolumeDevice{ + { + DevicePath: "/dev/sdaa", + Name: "disk", + }, + }, + }, + expectErr: true, + expectedErrMsg: "cannot find volume \"disk\" to pass into container \"\"", + }, + "volume BlockVolumeMapper is nil": { + podVolumes: kubecontainer.VolumeMap{ + "disk": kubecontainer.VolumeInfo{}, + }, + container: v1.Container{ + VolumeDevices: []v1.VolumeDevice{ + { + DevicePath: "/dev/sdzz", + Name: "disk", + }, + }, + }, + expectErr: true, + expectedErrMsg: "cannot find volume \"disk\" to pass into container \"\"", + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + pod := v1.Pod{ + Spec: v1.PodSpec{ + HostNetwork: true, + }, + } + blkutil := volumetest.NewBlockVolumePathHandler() + blkVolumes, err := kubelet.makeBlockVolumes(&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 { + t.Fatalf("expected error message `%s` but got `%v`", tc.expectedErrMsg, err) + } + return + } + // otherwise validate the devices + if err != nil { + t.Fatal(err) + } + assert.Equal(t, tc.expectedDevices, blkVolumes, "devices of container %+v", tc.container) + }) + } +} + func TestNodeHostsFileContent(t *testing.T) { testCases := []struct { hostsFileName string @@ -591,90 +770,6 @@ func TestRunInContainer(t *testing.T) { } } -func TestGenerateRunContainerOptions_DNSConfigurationParams(t *testing.T) { - testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) - defer testKubelet.Cleanup() - kubelet := testKubelet.kubelet - - clusterNS := "203.0.113.1" - kubelet.clusterDomain = "kubernetes.io" - kubelet.clusterDNS = []net.IP{net.ParseIP(clusterNS)} - - pods := newTestPods(4) - pods[0].Spec.DNSPolicy = v1.DNSClusterFirstWithHostNet - pods[1].Spec.DNSPolicy = v1.DNSClusterFirst - pods[2].Spec.DNSPolicy = v1.DNSClusterFirst - pods[2].Spec.HostNetwork = false - pods[3].Spec.DNSPolicy = v1.DNSDefault - - options := make([]*kubecontainer.RunContainerOptions, 4) - for i, pod := range pods { - var err error - options[i], _, err = kubelet.GenerateRunContainerOptions(pod, &v1.Container{}, "") - if err != nil { - t.Fatalf("failed to generate container options: %v", err) - } - } - if len(options[0].DNS) != 1 || options[0].DNS[0] != clusterNS { - t.Errorf("expected nameserver %s, got %+v", clusterNS, options[0].DNS) - } - if len(options[0].DNSSearch) == 0 || options[0].DNSSearch[0] != ".svc."+kubelet.clusterDomain { - t.Errorf("expected search %s, got %+v", ".svc."+kubelet.clusterDomain, options[0].DNSSearch) - } - if len(options[1].DNS) != 1 || options[1].DNS[0] != "127.0.0.1" { - t.Errorf("expected nameserver 127.0.0.1, got %+v", options[1].DNS) - } - if len(options[1].DNSSearch) != 1 || options[1].DNSSearch[0] != "." { - t.Errorf("expected search \".\", got %+v", options[1].DNSSearch) - } - if len(options[2].DNS) != 1 || options[2].DNS[0] != clusterNS { - t.Errorf("expected nameserver %s, got %+v", clusterNS, options[2].DNS) - } - if len(options[2].DNSSearch) == 0 || options[2].DNSSearch[0] != ".svc."+kubelet.clusterDomain { - t.Errorf("expected search %s, got %+v", ".svc."+kubelet.clusterDomain, options[2].DNSSearch) - } - if len(options[3].DNS) != 1 || options[3].DNS[0] != "127.0.0.1" { - t.Errorf("expected nameserver 127.0.0.1, got %+v", options[3].DNS) - } - if len(options[3].DNSSearch) != 1 || options[3].DNSSearch[0] != "." { - t.Errorf("expected search \".\", got %+v", options[3].DNSSearch) - } - - kubelet.resolverConfig = "/etc/resolv.conf" - for i, pod := range pods { - var err error - options[i], _, err = kubelet.GenerateRunContainerOptions(pod, &v1.Container{}, "") - if err != nil { - t.Fatalf("failed to generate container options: %v", err) - } - } - t.Logf("nameservers %+v", options[1].DNS) - if len(options[0].DNS) != 1 { - t.Errorf("expected cluster nameserver only, got %+v", options[0].DNS) - } else if options[0].DNS[0] != clusterNS { - t.Errorf("expected nameserver %s, got %v", clusterNS, options[0].DNS[0]) - } - expLength := len(options[1].DNSSearch) + 3 - if expLength > 6 { - expLength = 6 - } - if len(options[0].DNSSearch) != expLength { - t.Errorf("expected prepend of cluster domain, got %+v", options[0].DNSSearch) - } else if options[0].DNSSearch[0] != ".svc."+kubelet.clusterDomain { - t.Errorf("expected domain %s, got %s", ".svc."+kubelet.clusterDomain, options[0].DNSSearch) - } - if len(options[2].DNS) != 1 { - t.Errorf("expected cluster nameserver only, got %+v", options[2].DNS) - } else if options[2].DNS[0] != clusterNS { - t.Errorf("expected nameserver %s, got %v", clusterNS, options[2].DNS[0]) - } - if len(options[2].DNSSearch) != expLength { - t.Errorf("expected prepend of cluster domain, got %+v", options[2].DNSSearch) - } else if options[2].DNSSearch[0] != ".svc."+kubelet.clusterDomain { - t.Errorf("expected domain %s, got %s", ".svc."+kubelet.clusterDomain, options[0].DNSSearch) - } -} - type testServiceLister struct { services []*v1.Service } @@ -884,7 +979,7 @@ func TestMakeEnvironmentVariables(t *testing.T) { Name: "POD_NAME", ValueFrom: &v1.EnvVarSource{ FieldRef: &v1.ObjectFieldSelector{ - APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), + APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), FieldPath: "metadata.name", }, }, @@ -893,7 +988,7 @@ func TestMakeEnvironmentVariables(t *testing.T) { Name: "POD_NAMESPACE", ValueFrom: &v1.EnvVarSource{ FieldRef: &v1.ObjectFieldSelector{ - APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), + APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), FieldPath: "metadata.namespace", }, }, @@ -902,7 +997,7 @@ func TestMakeEnvironmentVariables(t *testing.T) { Name: "POD_NODE_NAME", ValueFrom: &v1.EnvVarSource{ FieldRef: &v1.ObjectFieldSelector{ - APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), + APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), FieldPath: "spec.nodeName", }, }, @@ -911,7 +1006,7 @@ func TestMakeEnvironmentVariables(t *testing.T) { Name: "POD_SERVICE_ACCOUNT_NAME", ValueFrom: &v1.EnvVarSource{ FieldRef: &v1.ObjectFieldSelector{ - APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), + APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), FieldPath: "spec.serviceAccountName", }, }, @@ -920,7 +1015,7 @@ func TestMakeEnvironmentVariables(t *testing.T) { Name: "POD_IP", ValueFrom: &v1.EnvVarSource{ FieldRef: &v1.ObjectFieldSelector{ - APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), + APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), FieldPath: "status.podIP", }, }, @@ -929,7 +1024,7 @@ func TestMakeEnvironmentVariables(t *testing.T) { Name: "HOST_IP", ValueFrom: &v1.EnvVarSource{ FieldRef: &v1.ObjectFieldSelector{ - APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), + APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), FieldPath: "status.hostIP", }, }, @@ -960,7 +1055,7 @@ func TestMakeEnvironmentVariables(t *testing.T) { Name: "POD_NAME", ValueFrom: &v1.EnvVarSource{ FieldRef: &v1.ObjectFieldSelector{ - APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), + APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(), FieldPath: "metadata.name", }, }, @@ -1750,7 +1845,7 @@ func TestPodPhaseWithRestartAlways(t *testing.T) { }, } for _, test := range tests { - status := GetPhase(&test.pod.Spec, test.pod.Status.ContainerStatuses) + status := getPhase(&test.pod.Spec, test.pod.Status.ContainerStatuses) assert.Equal(t, test.status, status, "[test %s]", test.test) } } @@ -1850,7 +1945,7 @@ func TestPodPhaseWithRestartNever(t *testing.T) { }, } for _, test := range tests { - status := GetPhase(&test.pod.Spec, test.pod.Status.ContainerStatuses) + status := getPhase(&test.pod.Spec, test.pod.Status.ContainerStatuses) assert.Equal(t, test.status, status, "[test %s]", test.test) } } @@ -1963,7 +2058,7 @@ func TestPodPhaseWithRestartOnFailure(t *testing.T) { }, } for _, test := range tests { - status := GetPhase(&test.pod.Spec, test.pod.Status.ContainerStatuses) + status := getPhase(&test.pod.Spec, test.pod.Status.ContainerStatuses) assert.Equal(t, test.status, status, "[test %s]", test.test) } } @@ -2175,25 +2270,6 @@ func TestPortForward(t *testing.T) { } } -// Tests that identify the host port conflicts are detected correctly. -func TestGetHostPortConflicts(t *testing.T) { - pods := []*v1.Pod{ - {Spec: v1.PodSpec{Containers: []v1.Container{{Ports: []v1.ContainerPort{{HostPort: 80}}}}}}, - {Spec: v1.PodSpec{Containers: []v1.Container{{Ports: []v1.ContainerPort{{HostPort: 81}}}}}}, - {Spec: v1.PodSpec{Containers: []v1.Container{{Ports: []v1.ContainerPort{{HostPort: 82}}}}}}, - {Spec: v1.PodSpec{Containers: []v1.Container{{Ports: []v1.ContainerPort{{HostPort: 83}}}}}}, - } - // Pods should not cause any conflict. - assert.False(t, hasHostPortConflicts(pods), "Should not have port conflicts") - - expected := &v1.Pod{ - Spec: v1.PodSpec{Containers: []v1.Container{{Ports: []v1.ContainerPort{{HostPort: 81}}}}}, - } - // The new pod should cause conflict and be reported. - pods = append(pods, expected) - assert.True(t, hasHostPortConflicts(pods), "Should have port conflicts") -} - func TestHasHostMountPVC(t *testing.T) { tests := map[string]struct { pvError error diff --git a/pkg/kubelet/kubelet_test.go b/pkg/kubelet/kubelet_test.go index 70e2dd6eb02..c1061355baa 100644 --- a/pkg/kubelet/kubelet_test.go +++ b/pkg/kubelet/kubelet_test.go @@ -67,6 +67,7 @@ import ( kubetypes "k8s.io/kubernetes/pkg/kubelet/types" "k8s.io/kubernetes/pkg/kubelet/util/queue" kubeletvolume "k8s.io/kubernetes/pkg/kubelet/volumemanager" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" "k8s.io/kubernetes/pkg/util/mount" "k8s.io/kubernetes/pkg/volume" _ "k8s.io/kubernetes/pkg/volume/host_path" @@ -284,7 +285,7 @@ func newTestKubeletWithImageList( kubelet.evictionManager = evictionManager kubelet.admitHandlers.AddPodAdmitHandler(evictionAdmitHandler) // Add this as cleanup predicate pod admitter - kubelet.admitHandlers.AddPodAdmitHandler(lifecycle.NewPredicateAdmitHandler(kubelet.getNodeAnyWay, lifecycle.NewAdmissionFailureHandlerStub())) + kubelet.admitHandlers.AddPodAdmitHandler(lifecycle.NewPredicateAdmitHandler(kubelet.getNodeAnyWay, lifecycle.NewAdmissionFailureHandlerStub(), kubelet.containerManager.UpdatePluginResources)) plug := &volumetest.FakeVolumePlugin{PluginName: "fake", Host: nil} var prober volume.DynamicPluginProber = nil // TODO (#51147) inject mock @@ -573,6 +574,116 @@ func TestHandleMemExceeded(t *testing.T) { checkPodStatus(t, kl, fittingPod, v1.PodPending) } +// Tests that we handle result of interface UpdatePluginResources correctly +// by setting corresponding status in status map. +func TestHandlePluginResources(t *testing.T) { + testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) + defer testKubelet.Cleanup() + testKubelet.chainMock() + kl := testKubelet.kubelet + + adjustedResource := v1.ResourceName("domain1.com/adjustedResource") + unadjustedResouce := v1.ResourceName("domain2.com/unadjustedResouce") + failedResource := v1.ResourceName("domain2.com/failedResource") + resourceQuantity1 := *resource.NewQuantity(int64(1), resource.DecimalSI) + resourceQuantity2 := *resource.NewQuantity(int64(2), resource.DecimalSI) + resourceQuantityInvalid := *resource.NewQuantity(int64(-1), resource.DecimalSI) + allowedPodQuantity := *resource.NewQuantity(int64(10), resource.DecimalSI) + nodes := []*v1.Node{ + {ObjectMeta: metav1.ObjectMeta{Name: testKubeletHostname}, + Status: v1.NodeStatus{Capacity: v1.ResourceList{}, Allocatable: v1.ResourceList{ + adjustedResource: resourceQuantity1, + unadjustedResouce: resourceQuantity1, + v1.ResourcePods: allowedPodQuantity, + }}}, + } + kl.nodeInfo = testNodeInfo{nodes: nodes} + + updatePluginResourcesFunc := func(node *schedulercache.NodeInfo, attrs *lifecycle.PodAdmitAttributes) error { + // Maps from resourceName to the value we use to set node.allocatableResource[resourceName]. + // A resource with invalid value (< 0) causes the function to return an error + // to emulate resource Allocation failure. + // Resources not contained in this map will have their node.allocatableResource + // quantity unchanged. + updateResourceMap := map[v1.ResourceName]resource.Quantity{ + adjustedResource: resourceQuantity2, + failedResource: resourceQuantityInvalid, + } + pod := attrs.Pod + allocatableResource := node.AllocatableResource() + newAllocatableResource := allocatableResource.Clone() + for _, container := range pod.Spec.Containers { + for resource := range container.Resources.Requests { + newQuantity, exist := updateResourceMap[resource] + if !exist { + continue + } + if newQuantity.Value() < 0 { + return fmt.Errorf("Allocation failed") + } + newAllocatableResource.ScalarResources[resource] = newQuantity.Value() + } + } + node.SetAllocatableResource(newAllocatableResource) + return nil + } + + // add updatePluginResourcesFunc to admission handler, to test it's behavior. + kl.admitHandlers = lifecycle.PodAdmitHandlers{} + kl.admitHandlers.AddPodAdmitHandler(lifecycle.NewPredicateAdmitHandler(kl.getNodeAnyWay, lifecycle.NewAdmissionFailureHandlerStub(), updatePluginResourcesFunc)) + + // pod requiring adjustedResource can be successfully allocated because updatePluginResourcesFunc + // adjusts node.allocatableResource for this resource to a sufficient value. + fittingPodspec := v1.PodSpec{NodeName: string(kl.nodeName), + Containers: []v1.Container{{Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + adjustedResource: resourceQuantity2, + }, + Requests: v1.ResourceList{ + adjustedResource: resourceQuantity2, + }, + }}}, + } + // pod requiring unadjustedResouce with insufficient quantity will still fail PredicateAdmit. + exceededPodSpec := v1.PodSpec{NodeName: string(kl.nodeName), + Containers: []v1.Container{{Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + unadjustedResouce: resourceQuantity2, + }, + Requests: v1.ResourceList{ + unadjustedResouce: resourceQuantity2, + }, + }}}, + } + // pod requiring failedResource will fail with the resource failed to be allocated. + failedPodSpec := v1.PodSpec{NodeName: string(kl.nodeName), + Containers: []v1.Container{{Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + failedResource: resourceQuantity1, + }, + Requests: v1.ResourceList{ + failedResource: resourceQuantity1, + }, + }}}, + } + pods := []*v1.Pod{ + podWithUIDNameNsSpec("123", "fittingpod", "foo", fittingPodspec), + podWithUIDNameNsSpec("456", "exceededpod", "foo", exceededPodSpec), + podWithUIDNameNsSpec("789", "failedpod", "foo", failedPodSpec), + } + // The latter two pod should be rejected. + fittingPod := pods[0] + exceededPod := pods[1] + failedPod := pods[2] + + kl.HandlePodAdditions(pods) + + // Check pod status stored in the status map. + checkPodStatus(t, kl, fittingPod, v1.PodPending) + checkPodStatus(t, kl, exceededPod, v1.PodFailed) + checkPodStatus(t, kl, failedPod, v1.PodFailed) +} + // TODO(filipg): This test should be removed once StatusSyncer can do garbage collection without external signal. func TestPurgingObsoleteStatusMapEntries(t *testing.T) { testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) diff --git a/pkg/kubelet/kubelet_volumes_test.go b/pkg/kubelet/kubelet_volumes_test.go index c3b2d53c290..60f1e70287d 100644 --- a/pkg/kubelet/kubelet_volumes_test.go +++ b/pkg/kubelet/kubelet_volumes_test.go @@ -448,3 +448,23 @@ func (f *stubVolume) SetUp(fsGroup *int64) error { func (f *stubVolume) SetUpAt(dir string, fsGroup *int64) error { return nil } + +type stubBlockVolume struct { + dirPath string + volName string +} + +func (f *stubBlockVolume) GetGlobalMapPath(spec *volume.Spec) (string, error) { + return "", nil +} + +func (f *stubBlockVolume) GetPodDeviceMapPath() (string, string) { + return f.dirPath, f.volName +} + +func (f *stubBlockVolume) SetUpDevice() (string, error) { + return "", nil +} +func (f *stubBlockVolume) TearDownDevice(mapPath string, devicePath string) error { + return nil +} diff --git a/pkg/kubelet/kubeletconfig/BUILD b/pkg/kubelet/kubeletconfig/BUILD index 71bbe9fb145..d5ea255591a 100644 --- a/pkg/kubelet/kubeletconfig/BUILD +++ b/pkg/kubelet/kubeletconfig/BUILD @@ -10,7 +10,6 @@ go_library( srcs = [ "configsync.go", "controller.go", - "rollback.go", "watch.go", ], importpath = "k8s.io/kubernetes/pkg/kubelet/kubeletconfig", @@ -25,13 +24,16 @@ go_library( "//pkg/kubelet/kubeletconfig/util/log:go_default_library", "//pkg/kubelet/kubeletconfig/util/panic:go_default_library", "//pkg/util/filesystem:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", ], ) diff --git a/pkg/kubelet/kubeletconfig/checkpoint/BUILD b/pkg/kubelet/kubeletconfig/checkpoint/BUILD index d34a5b9a445..96ac5db59d5 100644 --- a/pkg/kubelet/kubeletconfig/checkpoint/BUILD +++ b/pkg/kubelet/kubeletconfig/checkpoint/BUILD @@ -13,8 +13,8 @@ go_test( "configmap_test.go", "download_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/checkpoint", - library = ":go_default_library", deps = [ "//pkg/kubelet/apis/kubeletconfig:go_default_library", "//pkg/kubelet/apis/kubeletconfig/scheme:go_default_library", @@ -40,7 +40,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/checkpoint", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/kubelet/apis/kubeletconfig:go_default_library", "//pkg/kubelet/apis/kubeletconfig/scheme:go_default_library", "//pkg/kubelet/kubeletconfig/status:go_default_library", diff --git a/pkg/kubelet/kubeletconfig/checkpoint/checkpoint.go b/pkg/kubelet/kubeletconfig/checkpoint/checkpoint.go index 852ea714889..be44aaaa39c 100644 --- a/pkg/kubelet/kubeletconfig/checkpoint/checkpoint.go +++ b/pkg/kubelet/kubeletconfig/checkpoint/checkpoint.go @@ -22,7 +22,7 @@ import ( apiv1 "k8s.io/api/core/v1" apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" ) @@ -43,7 +43,7 @@ type Checkpoint interface { // DecodeCheckpoint is a helper for using the apimachinery to decode serialized checkpoints func DecodeCheckpoint(data []byte) (Checkpoint, error) { // decode the checkpoint - obj, err := runtime.Decode(api.Codecs.UniversalDecoder(), data) + obj, err := runtime.Decode(legacyscheme.Codecs.UniversalDecoder(), data) if err != nil { return nil, fmt.Errorf("failed to decode, error: %v", err) } @@ -52,7 +52,7 @@ func DecodeCheckpoint(data []byte) (Checkpoint, error) { // convert it to the external ConfigMap type, so we're consistently working with the external type outside of the on-disk representation cm := &apiv1.ConfigMap{} - err = api.Scheme.Convert(obj, cm, nil) + err = legacyscheme.Scheme.Convert(obj, cm, nil) if err != nil { return nil, fmt.Errorf("failed to convert decoded object into a v1 ConfigMap, error: %v", err) } diff --git a/pkg/kubelet/kubeletconfig/checkpoint/download.go b/pkg/kubelet/kubeletconfig/checkpoint/download.go index 841d9434531..9b516a0eb72 100644 --- a/pkg/kubelet/kubeletconfig/checkpoint/download.go +++ b/pkg/kubelet/kubeletconfig/checkpoint/download.go @@ -24,7 +24,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" clientset "k8s.io/client-go/kubernetes" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/status" utilcodec "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/codec" utillog "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/log" @@ -34,6 +34,8 @@ import ( type RemoteConfigSource interface { // UID returns the UID of the remote config source object UID() string + // APIPath returns the API path to the remote resource, e.g. its SelfLink + APIPath() string // Download downloads the remote config source object returns a Checkpoint backed by the object, // or a sanitized failure reason and error if the download fails Download(client clientset.Interface) (Checkpoint, string, error) @@ -71,7 +73,7 @@ func NewRemoteConfigSource(source *apiv1.NodeConfigSource) (RemoteConfigSource, // e.g. the objects stored in the .cur and .lkg files by checkpoint/store/fsstore.go func DecodeRemoteConfigSource(data []byte) (RemoteConfigSource, error) { // decode the remote config source - obj, err := runtime.Decode(api.Codecs.UniversalDecoder(), data) + obj, err := runtime.Decode(legacyscheme.Codecs.UniversalDecoder(), data) if err != nil { return nil, fmt.Errorf("failed to decode, error: %v", err) } @@ -81,7 +83,7 @@ func DecodeRemoteConfigSource(data []byte) (RemoteConfigSource, error) { // convert it to the external NodeConfigSource type, so we're consistently working with the external type outside of the on-disk representation cs := &apiv1.NodeConfigSource{} - err = api.Scheme.Convert(obj, cs, nil) + err = legacyscheme.Scheme.Convert(obj, cs, nil) if err != nil { return nil, fmt.Errorf("failed to convert decoded object into a v1 NodeConfigSource, error: %v", err) } @@ -110,6 +112,13 @@ func (r *remoteConfigMap) UID() string { return string(r.source.ConfigMapRef.UID) } +const configMapAPIPathFmt = "/api/v1/namespaces/%s/configmaps/%s" + +func (r *remoteConfigMap) APIPath() string { + ref := r.source.ConfigMapRef + return fmt.Sprintf(configMapAPIPathFmt, ref.Namespace, ref.Name) +} + func (r *remoteConfigMap) Download(client clientset.Interface) (Checkpoint, string, error) { var reason string uid := string(r.source.ConfigMapRef.UID) @@ -119,13 +128,13 @@ func (r *remoteConfigMap) Download(client clientset.Interface) (Checkpoint, stri // get the ConfigMap via namespace/name, there doesn't seem to be a way to get it by UID cm, err := client.CoreV1().ConfigMaps(r.source.ConfigMapRef.Namespace).Get(r.source.ConfigMapRef.Name, metav1.GetOptions{}) if err != nil { - reason = fmt.Sprintf(status.FailSyncReasonDownloadFmt, r.source.ConfigMapRef.Name, r.source.ConfigMapRef.Namespace) + reason = fmt.Sprintf(status.FailSyncReasonDownloadFmt, r.APIPath()) return nil, reason, fmt.Errorf("%s, error: %v", reason, err) } // ensure that UID matches the UID on the reference, the ObjectReference must be unambiguous if r.source.ConfigMapRef.UID != cm.UID { - reason = fmt.Sprintf(status.FailSyncReasonUIDMismatchFmt, r.source.ConfigMapRef.UID, cm.UID) + reason = fmt.Sprintf(status.FailSyncReasonUIDMismatchFmt, r.source.ConfigMapRef.UID, r.APIPath(), cm.UID) return nil, reason, fmt.Errorf(reason) } diff --git a/pkg/kubelet/kubeletconfig/checkpoint/download_test.go b/pkg/kubelet/kubeletconfig/checkpoint/download_test.go index a902b6384e4..345319fd37c 100644 --- a/pkg/kubelet/kubeletconfig/checkpoint/download_test.go +++ b/pkg/kubelet/kubeletconfig/checkpoint/download_test.go @@ -17,6 +17,7 @@ limitations under the License. package checkpoint import ( + "fmt" "testing" "github.com/davecgh/go-spew/spew" @@ -92,6 +93,20 @@ func TestRemoteConfigMapUID(t *testing.T) { } } +func TestRemoteConfigMapAPIPath(t *testing.T) { + name := "name" + namespace := "namespace" + cpt := &remoteConfigMap{ + &apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{Name: name, Namespace: namespace, UID: ""}}, + } + expect := fmt.Sprintf(configMapAPIPathFmt, cpt.source.ConfigMapRef.Namespace, cpt.source.ConfigMapRef.Name) + // APIPath() method should return the correct path to the referenced resource + path := cpt.APIPath() + if expect != path { + t.Errorf("expect APIPath() to return %q, but got %q", expect, namespace) + } +} + func TestRemoteConfigMapDownload(t *testing.T) { _, kubeletCodecs, err := kubeletscheme.NewSchemeAndCodecs() if err != nil { @@ -116,11 +131,11 @@ func TestRemoteConfigMapDownload(t *testing.T) { // object doesn't exist {"object doesn't exist", &remoteConfigMap{&apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{Name: "bogus", Namespace: "namespace", UID: "bogus"}}}, - nil, "failed to download ConfigMap"}, + nil, "not found"}, // UID of downloaded object doesn't match UID of referent found via namespace/name {"UID is incorrect for namespace/name", &remoteConfigMap{&apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{Name: "name", Namespace: "namespace", UID: "bogus"}}}, - nil, "does not match UID"}, + nil, "does not match"}, // successful download {"object exists and reference is correct", &remoteConfigMap{&apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{Name: "name", Namespace: "namespace", UID: "uid"}}}, diff --git a/pkg/kubelet/kubeletconfig/checkpoint/store/BUILD b/pkg/kubelet/kubeletconfig/checkpoint/store/BUILD index 37529c46abb..1c61c026a2c 100644 --- a/pkg/kubelet/kubeletconfig/checkpoint/store/BUILD +++ b/pkg/kubelet/kubeletconfig/checkpoint/store/BUILD @@ -12,8 +12,8 @@ go_test( "fsstore_test.go", "store_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/checkpoint/store", - library = ":go_default_library", deps = [ "//pkg/kubelet/kubeletconfig/checkpoint:go_default_library", "//pkg/kubelet/kubeletconfig/util/files:go_default_library", diff --git a/pkg/kubelet/kubeletconfig/checkpoint/store/fsstore.go b/pkg/kubelet/kubeletconfig/checkpoint/store/fsstore.go index a56c4ec1659..1026b97a842 100644 --- a/pkg/kubelet/kubeletconfig/checkpoint/store/fsstore.go +++ b/pkg/kubelet/kubeletconfig/checkpoint/store/fsstore.go @@ -56,10 +56,7 @@ func (s *fsStore) Initialize() error { if err := utilfiles.EnsureFile(s.fs, filepath.Join(s.checkpointsDir, curFile)); err != nil { return err } - if err := utilfiles.EnsureFile(s.fs, filepath.Join(s.checkpointsDir, lkgFile)); err != nil { - return err - } - return nil + return utilfiles.EnsureFile(s.fs, filepath.Join(s.checkpointsDir, lkgFile)) } func (s *fsStore) Exists(uid string) (bool, error) { @@ -77,10 +74,7 @@ func (s *fsStore) Save(c checkpoint.Checkpoint) error { return err } // save the file - if err := utilfiles.ReplaceFile(s.fs, filepath.Join(s.checkpointsDir, c.UID()), data); err != nil { - return err - } - return nil + return utilfiles.ReplaceFile(s.fs, filepath.Join(s.checkpointsDir, c.UID()), data) } func (s *fsStore) Load(uid string) (checkpoint.Checkpoint, error) { diff --git a/pkg/kubelet/kubeletconfig/configfiles/BUILD b/pkg/kubelet/kubeletconfig/configfiles/BUILD index 2c6e70a5072..db3b6cd8242 100644 --- a/pkg/kubelet/kubeletconfig/configfiles/BUILD +++ b/pkg/kubelet/kubeletconfig/configfiles/BUILD @@ -35,8 +35,8 @@ filegroup( go_test( name = "go_default_test", srcs = ["configfiles_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/configfiles", - library = ":go_default_library", deps = [ "//pkg/kubelet/apis/kubeletconfig:go_default_library", "//pkg/kubelet/apis/kubeletconfig/scheme:go_default_library", @@ -44,7 +44,6 @@ go_test( "//pkg/kubelet/kubeletconfig/util/files:go_default_library", "//pkg/kubelet/kubeletconfig/util/test:go_default_library", "//pkg/util/filesystem:go_default_library", - "//vendor/github.com/davecgh/go-spew/spew:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", ], ) diff --git a/pkg/kubelet/kubeletconfig/configfiles/configfiles.go b/pkg/kubelet/kubeletconfig/configfiles/configfiles.go index b3c763c162c..c3b5fc9b479 100644 --- a/pkg/kubelet/kubeletconfig/configfiles/configfiles.go +++ b/pkg/kubelet/kubeletconfig/configfiles/configfiles.go @@ -27,8 +27,6 @@ import ( utilfs "k8s.io/kubernetes/pkg/util/filesystem" ) -const kubeletFile = "kubelet" - // Loader loads configuration from a storage layer type Loader interface { // Load loads and returns the KubeletConfiguration from the storage layer, or an error if a configuration could not be loaded @@ -41,12 +39,12 @@ type fsLoader struct { fs utilfs.Filesystem // kubeletCodecs is the scheme used to decode config files kubeletCodecs *serializer.CodecFactory - // configDir is the absolute path to the directory containing the configuration files - configDir string + // kubeletFile is an absolute path to the file containing a serialized KubeletConfiguration + kubeletFile string } -// NewFsLoader returns a Loader that loads a KubeletConfiguration from the files in `configDir` -func NewFsLoader(fs utilfs.Filesystem, configDir string) (Loader, error) { +// NewFsLoader returns a Loader that loads a KubeletConfiguration from the `kubeletFile` +func NewFsLoader(fs utilfs.Filesystem, kubeletFile string) (Loader, error) { _, kubeletCodecs, err := kubeletscheme.NewSchemeAndCodecs() if err != nil { return nil, err @@ -55,22 +53,38 @@ func NewFsLoader(fs utilfs.Filesystem, configDir string) (Loader, error) { return &fsLoader{ fs: fs, kubeletCodecs: kubeletCodecs, - configDir: configDir, + kubeletFile: kubeletFile, }, nil } func (loader *fsLoader) Load() (*kubeletconfig.KubeletConfiguration, error) { - // require the config be in a file called "kubelet" - path := filepath.Join(loader.configDir, kubeletFile) - data, err := loader.fs.ReadFile(path) + data, err := loader.fs.ReadFile(loader.kubeletFile) if err != nil { - return nil, fmt.Errorf("failed to read init config file %q, error: %v", path, err) + return nil, fmt.Errorf("failed to read kubelet config file %q, error: %v", loader.kubeletFile, err) } // no configuration is an error, some parameters are required if len(data) == 0 { - return nil, fmt.Errorf("init config file %q was empty, but some parameters are required", path) + return nil, fmt.Errorf("kubelet config file %q was empty", loader.kubeletFile) } - return utilcodec.DecodeKubeletConfiguration(loader.kubeletCodecs, data) + kc, err := utilcodec.DecodeKubeletConfiguration(loader.kubeletCodecs, data) + if err != nil { + return nil, err + } + + // make all paths absolute + resolveRelativePaths(kubeletconfig.KubeletConfigurationPathRefs(kc), filepath.Dir(loader.kubeletFile)) + return kc, nil +} + +// resolveRelativePaths makes relative paths absolute by resolving them against `root` +func resolveRelativePaths(paths []*string, root string) { + for _, path := range paths { + // leave empty paths alone, "no path" is a valid input + // do not attempt to resolve paths that are already absolute + if len(*path) > 0 && !filepath.IsAbs(*path) { + *path = filepath.Join(root, *path) + } + } } diff --git a/pkg/kubelet/kubeletconfig/configfiles/configfiles_test.go b/pkg/kubelet/kubeletconfig/configfiles/configfiles_test.go index af354907f3d..77f20373ead 100644 --- a/pkg/kubelet/kubeletconfig/configfiles/configfiles_test.go +++ b/pkg/kubelet/kubeletconfig/configfiles/configfiles_test.go @@ -19,11 +19,8 @@ package configfiles import ( "fmt" "path/filepath" - "strings" "testing" - "github.com/davecgh/go-spew/spew" - apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" kubeletscheme "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme" @@ -33,83 +30,186 @@ import ( utilfs "k8s.io/kubernetes/pkg/util/filesystem" ) +const configDir = "/test-config-dir" +const relativePath = "relative/path/test" +const kubeletFile = "kubelet" + +func TestLoad(t *testing.T) { + cases := []struct { + desc string + file *string + expect *kubeletconfig.KubeletConfiguration + err string + }{ + // missing file + { + "missing file", + nil, + nil, + "failed to read", + }, + // empty file + { + "empty file", + newString(``), + nil, + "was empty", + }, + // invalid format + { + "invalid yaml", + newString(`*`), + nil, + "failed to decode", + }, + { + "invalid json", + newString(`{*`), + nil, + "failed to decode", + }, + // invalid object + { + "missing kind", + newString(`{"apiVersion":"kubeletconfig/v1alpha1"}`), + nil, + "failed to decode", + }, + { + "missing version", + newString(`{"kind":"KubeletConfiguration"}`), + nil, + "failed to decode", + }, + { + "unregistered kind", + newString(`{"kind":"BogusKind","apiVersion":"kubeletconfig/v1alpha1"}`), + nil, + "failed to decode", + }, + { + "unregistered version", + newString(`{"kind":"KubeletConfiguration","apiVersion":"bogusversion"}`), + nil, + "failed to decode", + }, + + // empty object with correct kind and version should result in the defaults for that kind and version + { + "default from yaml", + newString(`kind: KubeletConfiguration +apiVersion: kubeletconfig/v1alpha1`), + newConfig(t), + "", + }, + { + "default from json", + newString(`{"kind":"KubeletConfiguration","apiVersion":"kubeletconfig/v1alpha1"}`), + newConfig(t), + "", + }, + + // relative path + { + "yaml, relative path is resolved", + newString(fmt.Sprintf(`kind: KubeletConfiguration +apiVersion: kubeletconfig/v1alpha1 +podManifestPath: %s`, relativePath)), + func() *kubeletconfig.KubeletConfiguration { + kc := newConfig(t) + kc.PodManifestPath = filepath.Join(configDir, relativePath) + return kc + }(), + "", + }, + { + "json, relative path is resolved", + newString(fmt.Sprintf(`{"kind":"KubeletConfiguration","apiVersion":"kubeletconfig/v1alpha1","podManifestPath":"%s"}`, relativePath)), + func() *kubeletconfig.KubeletConfiguration { + kc := newConfig(t) + kc.PodManifestPath = filepath.Join(configDir, relativePath) + return kc + }(), + "", + }, + } + + for _, c := range cases { + t.Run(c.desc, func(t *testing.T) { + fs := utilfs.NewFakeFs() + path := filepath.Join(configDir, kubeletFile) + if c.file != nil { + if err := addFile(fs, path, *c.file); err != nil { + t.Fatalf("unexpected error: %v", err) + } + } + loader, err := NewFsLoader(fs, path) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + kc, err := loader.Load() + if utiltest.SkipRest(t, c.desc, err, c.err) { + return + } + if !apiequality.Semantic.DeepEqual(c.expect, kc) { + t.Fatalf("expect %#v but got %#v", *c.expect, *kc) + } + }) + + } +} + +func TestResolveRelativePaths(t *testing.T) { + absolutePath := filepath.Join(configDir, "absolute") + cases := []struct { + desc string + path string + expect string + }{ + {"empty path", "", ""}, + {"absolute path", absolutePath, absolutePath}, + {"relative path", relativePath, filepath.Join(configDir, relativePath)}, + } + + paths := kubeletconfig.KubeletConfigurationPathRefs(newConfig(t)) + if len(paths) == 0 { + t.Fatalf("requires at least one path field to exist in the KubeletConfiguration type") + } + for _, c := range cases { + t.Run(c.desc, func(t *testing.T) { + // set the path, resolve it, and check if it resolved as we would expect + *(paths[0]) = c.path + resolveRelativePaths(paths, configDir) + if *(paths[0]) != c.expect { + t.Fatalf("expect %s but got %s", c.expect, *(paths[0])) + } + }) + } +} + +func newString(s string) *string { + return &s +} + func addFile(fs utilfs.Filesystem, path string, file string) error { if err := utilfiles.EnsureDir(fs, filepath.Dir(path)); err != nil { return err } - if err := utilfiles.ReplaceFile(fs, path, []byte(file)); err != nil { - return err - } - return nil + return utilfiles.ReplaceFile(fs, path, []byte(file)) } -func TestLoad(t *testing.T) { +func newConfig(t *testing.T) *kubeletconfig.KubeletConfiguration { kubeletScheme, _, err := kubeletscheme.NewSchemeAndCodecs() if err != nil { t.Fatalf("unexpected error: %v", err) } - // get the built-in default configuration external := &kubeletconfigv1alpha1.KubeletConfiguration{} kubeletScheme.Default(external) - defaultConfig := &kubeletconfig.KubeletConfiguration{} - err = kubeletScheme.Convert(external, defaultConfig, nil) + kc := &kubeletconfig.KubeletConfiguration{} + err = kubeletScheme.Convert(external, kc, nil) if err != nil { t.Fatalf("unexpected error: %v", err) } - - cases := []struct { - desc string - file string - expect *kubeletconfig.KubeletConfiguration - err string - }{ - {"empty data", ``, nil, "was empty"}, - // invalid format - {"invalid yaml", `*`, nil, "failed to decode"}, - {"invalid json", `{*`, nil, "failed to decode"}, - // invalid object - {"missing kind", `{"apiVersion":"kubeletconfig/v1alpha1"}`, nil, "failed to decode"}, - {"missing version", `{"kind":"KubeletConfiguration"}`, nil, "failed to decode"}, - {"unregistered kind", `{"kind":"BogusKind","apiVersion":"kubeletconfig/v1alpha1"}`, nil, "failed to decode"}, - {"unregistered version", `{"kind":"KubeletConfiguration","apiVersion":"bogusversion"}`, nil, "failed to decode"}, - // empty object with correct kind and version should result in the defaults for that kind and version - {"default from yaml", `kind: KubeletConfiguration -apiVersion: kubeletconfig/v1alpha1`, defaultConfig, ""}, - {"default from json", `{"kind":"KubeletConfiguration","apiVersion":"kubeletconfig/v1alpha1"}`, defaultConfig, ""}, - } - - fs := utilfs.NewFakeFs() - for i := range cases { - dir := fmt.Sprintf("/%d", i) - if err := addFile(fs, filepath.Join(dir, kubeletFile), cases[i].file); err != nil { - t.Fatalf("unexpected error: %v", err) - } - loader, err := NewFsLoader(fs, dir) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - kc, err := loader.Load() - if utiltest.SkipRest(t, cases[i].desc, err, cases[i].err) { - continue - } - // we expect the parsed configuration to match what we described in the ConfigMap - if !apiequality.Semantic.DeepEqual(cases[i].expect, kc) { - t.Errorf("case %q, expect config %s but got %s", cases[i].desc, spew.Sdump(cases[i].expect), spew.Sdump(kc)) - } - } - - // finally test for a missing file - desc := "missing kubelet file" - contains := "failed to read" - loader, err := NewFsLoader(fs, "/fake") - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - _, err = loader.Load() - if err == nil { - t.Errorf("case %q, expect error to contain %q but got nil error", desc, contains) - } else if !strings.Contains(err.Error(), contains) { - t.Errorf("case %q, expect error to contain %q but got %q", desc, contains, err.Error()) - } + return kc } diff --git a/pkg/kubelet/kubeletconfig/configsync.go b/pkg/kubelet/kubeletconfig/configsync.go index ac7e40656f9..b128e95b92b 100644 --- a/pkg/kubelet/kubeletconfig/configsync.go +++ b/pkg/kubelet/kubeletconfig/configsync.go @@ -19,15 +19,30 @@ package kubeletconfig import ( "fmt" "os" + "time" + + "github.com/golang/glog" apiv1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" clientset "k8s.io/client-go/kubernetes" + v1core "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/tools/cache" "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/checkpoint" "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/status" utillog "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/log" ) +const ( + // KubeletConfigChangedEventReason identifies an event as a change of Kubelet configuration + KubeletConfigChangedEventReason = "KubeletConfigChanged" + // EventMessageFmt is the message format for Kubelet config change events + EventMessageFmt = "Kubelet will restart to use: %s" + // LocalConfigMessage is the text to apply to EventMessageFmt when the Kubelet has been configured to use its local config (init or defaults) + LocalConfigMessage = "local config" +) + // pokeConfiSourceWorker tells the worker thread that syncs config sources that work needs to be done func (cc *Controller) pokeConfigSourceWorker() { select { @@ -37,7 +52,7 @@ func (cc *Controller) pokeConfigSourceWorker() { } // syncConfigSource checks if work needs to be done to use a new configuration, and does that work if necessary -func (cc *Controller) syncConfigSource(client clientset.Interface, nodeName string) { +func (cc *Controller) syncConfigSource(client clientset.Interface, eventClient v1core.EventsGetter, nodeName string) { select { case <-cc.pendingConfigSource: default: @@ -62,13 +77,22 @@ func (cc *Controller) syncConfigSource(client clientset.Interface, nodeName stri } // check the Node and download any new config - if updated, reason, err := cc.doSyncConfigSource(client, node.Spec.ConfigSource); err != nil { + if updated, cur, reason, err := cc.doSyncConfigSource(client, node.Spec.ConfigSource); err != nil { cc.configOK.SetFailSyncCondition(reason) syncerr = fmt.Errorf("%s, error: %v", reason, err) return } else if updated { - // TODO(mtaufen): Consider adding a "currently restarting kubelet" ConfigOK message for this case - utillog.Infof("config updated, Kubelet will restart to begin using new config") + path := LocalConfigMessage + if cur != nil { + path = cur.APIPath() + } + // we directly log and send the event, instead of using the event recorder, + // because the event recorder won't flush its queue before we exit (we'd lose the event) + event := eventf(nodeName, apiv1.EventTypeNormal, KubeletConfigChangedEventReason, EventMessageFmt, path) + glog.V(3).Infof("Event(%#v): type: '%v' reason: '%v' %v", event.InvolvedObject, event.Type, event.Reason, event.Message) + if _, err := eventClient.Events(apiv1.NamespaceDefault).Create(event); err != nil { + utillog.Errorf("failed to send event, error: %v", err) + } os.Exit(0) } @@ -81,31 +105,31 @@ func (cc *Controller) syncConfigSource(client clientset.Interface, nodeName stri // doSyncConfigSource checkpoints and sets the store's current config to the new config or resets config, // depending on the `source`, and returns whether the current config in the checkpoint store was updated as a result -func (cc *Controller) doSyncConfigSource(client clientset.Interface, source *apiv1.NodeConfigSource) (bool, string, error) { +func (cc *Controller) doSyncConfigSource(client clientset.Interface, source *apiv1.NodeConfigSource) (bool, checkpoint.RemoteConfigSource, string, error) { if source == nil { utillog.Infof("Node.Spec.ConfigSource is empty, will reset current and last-known-good to defaults") updated, reason, err := cc.resetConfig() if err != nil { - return false, reason, err + return false, nil, reason, err } - return updated, "", nil + return updated, nil, "", nil } // if the NodeConfigSource is non-nil, download the config utillog.Infof("Node.Spec.ConfigSource is non-empty, will checkpoint source and update config if necessary") remote, reason, err := checkpoint.NewRemoteConfigSource(source) if err != nil { - return false, reason, err + return false, nil, reason, err } reason, err = cc.checkpointConfigSource(client, remote) if err != nil { - return false, reason, err + return false, nil, reason, err } updated, reason, err := cc.setCurrentConfig(remote) if err != nil { - return false, reason, err + return false, nil, reason, err } - return updated, "", nil + return updated, remote, "", nil } // checkpointConfigSource downloads and checkpoints the object referred to by `source` if the checkpoint does not already exist, @@ -115,7 +139,7 @@ func (cc *Controller) checkpointConfigSource(client clientset.Interface, source // if the checkpoint already exists, skip downloading if ok, err := cc.checkpointStore.Exists(uid); err != nil { - reason := fmt.Sprintf(status.FailSyncReasonCheckpointExistenceFmt, uid) + reason := fmt.Sprintf(status.FailSyncReasonCheckpointExistenceFmt, source.APIPath(), uid) return reason, fmt.Errorf("%s, error: %v", reason, err) } else if ok { utillog.Infof("checkpoint already exists for object with UID %q, skipping download", uid) @@ -131,7 +155,7 @@ func (cc *Controller) checkpointConfigSource(client clientset.Interface, source // save err = cc.checkpointStore.Save(checkpoint) if err != nil { - reason := fmt.Sprintf(status.FailSyncReasonSaveCheckpointFmt, checkpoint.UID()) + reason := fmt.Sprintf(status.FailSyncReasonSaveCheckpointFmt, source.APIPath(), checkpoint.UID()) return reason, fmt.Errorf("%s, error: %v", reason, err) } @@ -144,9 +168,9 @@ func (cc *Controller) setCurrentConfig(source checkpoint.RemoteConfigSource) (bo updated, err := cc.checkpointStore.SetCurrentUpdated(source) if err != nil { if source == nil { - return false, status.FailSyncReasonSetCurrentDefault, err + return false, status.FailSyncReasonSetCurrentLocal, err } - return false, fmt.Sprintf(status.FailSyncReasonSetCurrentUIDFmt, source.UID()), err + return false, fmt.Sprintf(status.FailSyncReasonSetCurrentUIDFmt, source.APIPath(), source.UID()), err } return updated, "", nil } @@ -181,3 +205,43 @@ func latestNode(store cache.Store, nodeName string) (*apiv1.Node, error) { } return node, nil } + +// eventf constructs and returns an event containing a formatted message +// similar to k8s.io/client-go/tools/record/event.go +func eventf(nodeName, eventType, reason, messageFmt string, args ...interface{}) *apiv1.Event { + return makeEvent(nodeName, eventType, reason, fmt.Sprintf(messageFmt, args...)) +} + +// makeEvent constructs an event +// similar to makeEvent in k8s.io/client-go/tools/record/event.go +func makeEvent(nodeName, eventtype, reason, message string) *apiv1.Event { + const componentKubelet = "kubelet" + // NOTE(mtaufen): This is consistent with pkg/kubelet/kubelet.go. Even though setting the node + // name as the UID looks strange, it appears to be conventional for events sent by the Kubelet. + ref := apiv1.ObjectReference{ + Kind: "Node", + Name: nodeName, + UID: types.UID(nodeName), + Namespace: "", + } + + t := metav1.Time{Time: time.Now()} + namespace := ref.Namespace + if namespace == "" { + namespace = metav1.NamespaceDefault + } + return &apiv1.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%v.%x", ref.Name, t.UnixNano()), + Namespace: namespace, + }, + InvolvedObject: ref, + Reason: reason, + Message: message, + FirstTimestamp: t, + LastTimestamp: t, + Count: 1, + Type: eventtype, + Source: apiv1.EventSource{Component: componentKubelet, Host: string(nodeName)}, + } +} diff --git a/pkg/kubelet/kubeletconfig/controller.go b/pkg/kubelet/kubeletconfig/controller.go index edfb62a03bb..b8b344b95cf 100644 --- a/pkg/kubelet/kubeletconfig/controller.go +++ b/pkg/kubelet/kubeletconfig/controller.go @@ -24,10 +24,12 @@ import ( apiv1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/wait" clientset "k8s.io/client-go/kubernetes" + v1core "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/tools/cache" "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/validation" + "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/checkpoint" "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/checkpoint/store" "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/configfiles" "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/status" @@ -38,7 +40,6 @@ import ( const ( checkpointsDir = "checkpoints" - initConfigDir = "init" ) // Controller is the controller which, among other things: @@ -55,11 +56,8 @@ type Controller struct { // defaultConfig is the configuration to use if no initConfig is provided defaultConfig *kubeletconfig.KubeletConfiguration - // initConfig is the unmarshaled init config, this will be loaded by the Controller if an initConfigDir is provided - initConfig *kubeletconfig.KubeletConfiguration - - // initLoader is for loading the Kubelet's init configuration files from disk - initLoader configfiles.Loader + // fileLoader is for loading the Kubelet's local config files from disk + fileLoader configfiles.Loader // pendingConfigSource; write to this channel to indicate that the config source needs to be synced from the API server pendingConfigSource chan bool @@ -75,19 +73,19 @@ type Controller struct { } // NewController constructs a new Controller object and returns it. Directory paths must be absolute. -// If the `initConfigDir` is an empty string, skips trying to load the init config. +// If the `kubeletConfigFile` is an empty string, skips trying to load the kubelet config file. // If the `dynamicConfigDir` is an empty string, skips trying to load checkpoints or download new config, // but will still sync the ConfigOK condition if you call StartSync with a non-nil client. func NewController(defaultConfig *kubeletconfig.KubeletConfiguration, - initConfigDir string, + kubeletConfigFile string, dynamicConfigDir string) (*Controller, error) { var err error fs := utilfs.DefaultFs{} - var initLoader configfiles.Loader - if len(initConfigDir) > 0 { - initLoader, err = configfiles.NewFsLoader(fs, initConfigDir) + var fileLoader configfiles.Loader + if len(kubeletConfigFile) > 0 { + fileLoader, err = configfiles.NewFsLoader(fs, kubeletConfigFile) if err != nil { return nil, err } @@ -104,7 +102,7 @@ func NewController(defaultConfig *kubeletconfig.KubeletConfiguration, pendingConfigSource: make(chan bool, 1), configOK: status.NewConfigOKCondition(), checkpointStore: store.NewFsStore(fs, filepath.Join(dynamicConfigDir, checkpointsDir)), - initLoader: initLoader, + fileLoader: fileLoader, }, nil } @@ -113,95 +111,74 @@ func NewController(defaultConfig *kubeletconfig.KubeletConfiguration, func (cc *Controller) Bootstrap() (*kubeletconfig.KubeletConfiguration, error) { utillog.Infof("starting controller") - // ALWAYS validate the local (default and init) configs. This makes incorrectly provisioned nodes an error. - // These must be valid because they are the foundational last-known-good configs. - utillog.Infof("validating combination of defaults and flags") - if err := validation.ValidateKubeletConfiguration(cc.defaultConfig); err != nil { - return nil, fmt.Errorf("combination of defaults and flags failed validation, error: %v", err) - } - // only attempt to load and validate the init config if the user provided a path - if cc.initLoader != nil { - utillog.Infof("loading init config") - kc, err := cc.initLoader.Load() - if err != nil { - return nil, err - } - // validate the init config - utillog.Infof("validating init config") - if err := validation.ValidateKubeletConfiguration(kc); err != nil { - return nil, fmt.Errorf("failed to validate the init config, error: %v", err) - } - cc.initConfig = kc - } - // Assert: the default and init configs are both valid + // Load and validate the local config (defaults + flags, file) + local, err := cc.loadLocalConfig() + if err != nil { + return nil, err + } // Assert: the default and file configs are both valid - // if dynamic config is disabled, skip trying to load any checkpoints because they won't exist + // if dynamic config is disabled, we just stop here if !cc.dynamicConfig { - return cc.localConfig(), nil - } + // NOTE(mtaufen): We still need to update the status. + // We expect to be able to disable dynamic config but still get a status update about the config. + // This is because the feature gate covers dynamic config AND config status reporting, while the + // --dynamic-config-dir flag just covers dynamic config. + cc.configOK.Set(status.NotDynamicLocalMessage, status.NotDynamicLocalReason, apiv1.ConditionTrue) + return local, nil + } // Assert: dynamic config is enabled - // assert: now we know that a dynamicConfigDir was provided, and we can rely on that existing - - // make sure the filesystem is set up properly - // TODO(mtaufen): rename this to initializeDynamicConfigDir - if err := cc.initialize(); err != nil { + // ensure the filesystem is initialized + if err := cc.initializeDynamicConfigDir(); err != nil { return nil, err } - // determine UID of the current config source - curUID := "" - if curSource, err := cc.checkpointStore.Current(); err != nil { - return nil, err - } else if curSource != nil { - curUID = curSource.UID() - } + assigned, curSource, reason, err := cc.loadAssignedConfig(local) + if err == nil { + // set the status to indicate we will use the assigned config + if curSource != nil { + cc.configOK.Set(fmt.Sprintf(status.CurRemoteMessageFmt, curSource.APIPath()), reason, apiv1.ConditionTrue) + } else { + cc.configOK.Set(status.CurLocalMessage, reason, apiv1.ConditionTrue) + } - // if curUID indicates the local config should be used, return the correct one of those - if len(curUID) == 0 { - return cc.localConfig(), nil - } // Assert: we will not use the local configurations, unless we roll back to lkg; curUID is non-empty + // update the last-known-good config if necessary, and start a timer that + // periodically checks whether the last-known good needs to be updated + // we only do this when the assigned config loads and passes validation + // wait.Forever will call the func once before starting the timer + go wait.Forever(func() { cc.checkTrial(assigned.ConfigTrialDuration.Duration) }, 10*time.Second) - // TODO(mtaufen): consider re-verifying integrity and re-attempting download when a load/verify/parse/validate + return assigned, nil + } // Assert: the assigned config failed to load, parse, or validate + + // TODO(mtaufen): consider re-attempting download when a load/verify/parse/validate // error happens outside trial period, we already made it past the trial so it's probably filesystem corruption // or something else scary (unless someone is using a 0-length trial period) + // load from checkpoint - // load the current config - checkpoint, err := cc.checkpointStore.Load(curUID) + // log the reason and error details for the failure to load the assigned config + utillog.Errorf(fmt.Sprintf("%s, error: %v", reason, err)) + + // load the last-known-good config + lkg, lkgSource, err := cc.loadLastKnownGoodConfig(local) if err != nil { - // TODO(mtaufen): rollback for now, but this could reasonably be handled by re-attempting a download, - // it probably indicates some sort of corruption - return cc.lkgRollback(fmt.Sprintf(status.CurFailLoadReasonFmt, curUID), fmt.Sprintf("error: %v", err)) - } - - // parse the checkpoint into a KubeletConfiguration - cur, err := checkpoint.Parse() - if err != nil { - return cc.lkgRollback(fmt.Sprintf(status.CurFailParseReasonFmt, curUID), fmt.Sprintf("error: %v", err)) - } - - // validate current config - if err := validation.ValidateKubeletConfiguration(cur); err != nil { - return cc.lkgRollback(fmt.Sprintf(status.CurFailValidateReasonFmt, curUID), fmt.Sprintf("error: %v", err)) - } - - // when the trial period is over, the current config becomes the last-known-good - if trial, err := cc.inTrial(cur.ConfigTrialDuration.Duration); err != nil { return nil, err - } else if !trial { - if err := cc.graduateCurrentToLastKnownGood(); err != nil { - return nil, err - } } - // update the status to note that we will use the current config - cc.configOK.Set(fmt.Sprintf(status.CurRemoteMessageFmt, curUID), status.CurRemoteOKReason, apiv1.ConditionTrue) - return cur, nil + // set the status to indicate that we had to roll back to the lkg for the reason reported when we tried to load the assigned config + if lkgSource != nil { + cc.configOK.Set(fmt.Sprintf(status.LkgRemoteMessageFmt, lkgSource.APIPath()), reason, apiv1.ConditionFalse) + } else { + cc.configOK.Set(status.LkgLocalMessage, reason, apiv1.ConditionFalse) + } + + // return the last-known-good config + return lkg, nil } // StartSync launches the controller's sync loops if `client` is non-nil and `nodeName` is non-empty. // It will always start the Node condition reporting loop, and will also start the dynamic conifg sync loops // if dynamic config is enabled on the controller. If `nodeName` is empty but `client` is non-nil, an error is logged. -func (cc *Controller) StartSync(client clientset.Interface, nodeName string) { +func (cc *Controller) StartSync(client clientset.Interface, eventClient v1core.EventsGetter, nodeName string) { if client == nil { utillog.Infof("nil client, will not start sync loops") return @@ -236,7 +213,7 @@ func (cc *Controller) StartSync(client clientset.Interface, nodeName string) { go utilpanic.HandlePanic(func() { utillog.Infof("starting config source sync loop") wait.JitterUntil(func() { - cc.syncConfigSource(client, nodeName) + cc.syncConfigSource(client, eventClient, nodeName) }, 10*time.Second, 0.2, true, wait.NeverStop) })() } else { @@ -244,25 +221,111 @@ func (cc *Controller) StartSync(client clientset.Interface, nodeName string) { } } -// initialize makes sure that the storage layers for various controller components are set up correctly -func (cc *Controller) initialize() error { - utillog.Infof("ensuring filesystem is set up correctly") - // initialize local checkpoint storage location - if err := cc.checkpointStore.Initialize(); err != nil { - return err +// loadLocalConfig returns the local config: either the defaults provided to the controller or +// a local config file, if the Kubelet is configured to use the local file +func (cc *Controller) loadLocalConfig() (*kubeletconfig.KubeletConfiguration, error) { + // ALWAYS validate the local configs. This makes incorrectly provisioned nodes an error. + // These must be valid because they are the default last-known-good configs. + utillog.Infof("validating combination of defaults and flags") + if err := validation.ValidateKubeletConfiguration(cc.defaultConfig); err != nil { + return nil, fmt.Errorf("combination of defaults and flags failed validation, error: %v", err) } - return nil + // only attempt to load and validate the Kubelet config file if the user provided a path + if cc.fileLoader != nil { + utillog.Infof("loading Kubelet config file") + kc, err := cc.fileLoader.Load() + if err != nil { + return nil, err + } + // validate the Kubelet config file config + utillog.Infof("validating Kubelet config file") + if err := validation.ValidateKubeletConfiguration(kc); err != nil { + return nil, fmt.Errorf("failed to validate the Kubelet config file, error: %v", err) + } + return kc, nil + } + // if no Kubelet config file config, just return the default + return cc.defaultConfig, nil } -// localConfig returns the initConfig if it is loaded, otherwise returns the defaultConfig. -// It also sets the local configOK condition to match the returned config. -func (cc *Controller) localConfig() *kubeletconfig.KubeletConfiguration { - if cc.initConfig != nil { - cc.configOK.Set(status.CurInitMessage, status.CurInitOKReason, apiv1.ConditionTrue) - return cc.initConfig +// loadAssignedConfig loads the Kubelet's currently assigned config, +// based on the setting in the local checkpoint store. +// It returns the loaded configuration, the checkpoint store's config source record, +// a clean success or failure reason that can be reported in the status, and any error that occurs. +// If the local config should be used, it will be returned. You should validate local before passing it to this function. +func (cc *Controller) loadAssignedConfig(local *kubeletconfig.KubeletConfiguration) (*kubeletconfig.KubeletConfiguration, checkpoint.RemoteConfigSource, string, error) { + src, err := cc.checkpointStore.Current() + if err != nil { + return nil, nil, fmt.Sprintf(status.CurFailLoadReasonFmt, "unknown"), err + } + // nil source is the signal to use the local config + if src == nil { + return local, src, status.CurLocalOkayReason, nil + } + curUID := src.UID() + // load from checkpoint + checkpoint, err := cc.checkpointStore.Load(curUID) + if err != nil { + return nil, src, fmt.Sprintf(status.CurFailLoadReasonFmt, src.APIPath()), err + } + cur, err := checkpoint.Parse() + if err != nil { + return nil, src, fmt.Sprintf(status.CurFailParseReasonFmt, src.APIPath()), err + } + if err := validation.ValidateKubeletConfiguration(cur); err != nil { + return nil, src, fmt.Sprintf(status.CurFailValidateReasonFmt, src.APIPath()), err + } + return cur, src, status.CurRemoteOkayReason, nil +} + +// loadLastKnownGoodConfig loads the Kubelet's last-known-good config, +// based on the setting in the local checkpoint store. +// It returns the loaded configuration, the checkpoint store's config source record, +// and any error that occurs. +// If the local config should be used, it will be returned. You should validate local before passing it to this function. +func (cc *Controller) loadLastKnownGoodConfig(local *kubeletconfig.KubeletConfiguration) (*kubeletconfig.KubeletConfiguration, checkpoint.RemoteConfigSource, error) { + src, err := cc.checkpointStore.LastKnownGood() + if err != nil { + return nil, nil, fmt.Errorf("unable to determine last-known-good config, error: %v", err) + } + // nil source is the signal to use the local config + if src == nil { + return local, src, nil + } + lkgUID := src.UID() + // load from checkpoint + checkpoint, err := cc.checkpointStore.Load(lkgUID) + if err != nil { + return nil, src, fmt.Errorf("%s, error: %v", fmt.Sprintf(status.LkgFailLoadReasonFmt, src.APIPath()), err) + } + lkg, err := checkpoint.Parse() + if err != nil { + return nil, src, fmt.Errorf("%s, error: %v", fmt.Sprintf(status.LkgFailParseReasonFmt, src.APIPath()), err) + } + if err := validation.ValidateKubeletConfiguration(lkg); err != nil { + return nil, src, fmt.Errorf("%s, error: %v", fmt.Sprintf(status.LkgFailValidateReasonFmt, src.APIPath()), err) + } + return lkg, src, nil +} + +// initializeDynamicConfigDir makes sure that the storage layers for various controller components are set up correctly +func (cc *Controller) initializeDynamicConfigDir() error { + utillog.Infof("ensuring filesystem is set up correctly") + // initializeDynamicConfigDir local checkpoint storage location + return cc.checkpointStore.Initialize() +} + +// checkTrial checks whether the trial duration has passed, and updates the last-known-good config if necessary +func (cc *Controller) checkTrial(duration time.Duration) { + // when the trial period is over, the assigned config becomes the last-known-good + if trial, err := cc.inTrial(duration); err != nil { + utillog.Errorf("failed to check trial period for assigned config, error: %v", err) + } else if !trial { + utillog.Infof("assigned config passed trial period, will set as last-known-good") + if err := cc.graduateAssignedToLastKnownGood(); err != nil { + utillog.Errorf("failed to set last-known-good to assigned config, error: %v", err) + } } - cc.configOK.Set(status.CurDefaultMessage, status.CurDefaultOKReason, apiv1.ConditionTrue) - return cc.defaultConfig } // inTrial returns true if the time elapsed since the last modification of the current config does not exceed `trialDur`, false otherwise @@ -278,16 +341,16 @@ func (cc *Controller) inTrial(trialDur time.Duration) (bool, error) { return false, nil } -// graduateCurrentToLastKnownGood sets the last-known-good UID on the checkpointStore +// graduateAssignedToLastKnownGood sets the last-known-good UID on the checkpointStore // to the same value as the current UID maintained by the checkpointStore -func (cc *Controller) graduateCurrentToLastKnownGood() error { +func (cc *Controller) graduateAssignedToLastKnownGood() error { curUID, err := cc.checkpointStore.Current() if err != nil { - return fmt.Errorf("could not graduate last-known-good config to current config, error: %v", err) + return err } err = cc.checkpointStore.SetLastKnownGood(curUID) if err != nil { - return fmt.Errorf("could not graduate last-known-good config to current config, error: %v", err) + return err } return nil } diff --git a/pkg/kubelet/kubeletconfig/rollback.go b/pkg/kubelet/kubeletconfig/rollback.go deleted file mode 100644 index a2789566345..00000000000 --- a/pkg/kubelet/kubeletconfig/rollback.go +++ /dev/null @@ -1,70 +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 kubeletconfig - -import ( - "fmt" - - apiv1 "k8s.io/api/core/v1" - "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" - "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/validation" - "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/status" - utillog "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/log" -) - -// lkgRollback returns a valid last-known-good configuration, and updates the `cc.configOK` condition -// regarding the `reason` for the rollback, or returns an error if a valid last-known-good could not be produced -func (cc *Controller) lkgRollback(reason, detail string) (*kubeletconfig.KubeletConfiguration, error) { - utillog.Errorf(fmt.Sprintf("%s, %s", reason, detail)) - - lkgUID := "" - if lkgSource, err := cc.checkpointStore.LastKnownGood(); err != nil { - return nil, fmt.Errorf("unable to determine last-known-good config, error: %v", err) - } else if lkgSource != nil { - lkgUID = lkgSource.UID() - } - - // if lkgUID indicates the default should be used, return initConfig or defaultConfig - if len(lkgUID) == 0 { - if cc.initConfig != nil { - cc.configOK.Set(status.LkgInitMessage, reason, apiv1.ConditionFalse) - return cc.initConfig, nil - } - cc.configOK.Set(status.LkgDefaultMessage, reason, apiv1.ConditionFalse) - return cc.defaultConfig, nil - } - - // load - checkpoint, err := cc.checkpointStore.Load(lkgUID) - if err != nil { - return nil, fmt.Errorf("%s, error: %v", fmt.Sprintf(status.LkgFailLoadReasonFmt, lkgUID), err) - } - - // parse - lkg, err := checkpoint.Parse() - if err != nil { - return nil, fmt.Errorf("%s, error: %v", fmt.Sprintf(status.LkgFailParseReasonFmt, lkgUID), err) - } - - // validate - if err := validation.ValidateKubeletConfiguration(lkg); err != nil { - return nil, fmt.Errorf("%s, error: %v", fmt.Sprintf(status.LkgFailValidateReasonFmt, lkgUID), err) - } - - cc.configOK.Set(fmt.Sprintf(status.LkgRemoteMessageFmt, lkgUID), reason, apiv1.ConditionFalse) - return lkg, nil -} diff --git a/pkg/kubelet/kubeletconfig/status/BUILD b/pkg/kubelet/kubeletconfig/status/BUILD index d5ba25c151b..6de1a105e30 100644 --- a/pkg/kubelet/kubeletconfig/status/BUILD +++ b/pkg/kubelet/kubeletconfig/status/BUILD @@ -10,7 +10,8 @@ go_library( srcs = ["status.go"], importpath = "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/status", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/kubelet/kubeletconfig/util/equal:go_default_library", "//pkg/kubelet/kubeletconfig/util/log:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/kubelet/kubeletconfig/status/status.go b/pkg/kubelet/kubeletconfig/status/status.go index 22d0d1865ce..797f7bbcb5a 100644 --- a/pkg/kubelet/kubeletconfig/status/status.go +++ b/pkg/kubelet/kubeletconfig/status/status.go @@ -26,51 +26,49 @@ import ( kuberuntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/strategicpatch" clientset "k8s.io/client-go/kubernetes" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" utilequal "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/equal" utillog "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/log" ) +// TODO(mtaufen): s/current/assigned, as this is more accurate e.g. if you are using lkg, you aren't currently using "current" :) const ( - // CurDefaultMessage indicates the Kubelet is using it's current config, which is the default - CurDefaultMessage = "using current (default)" - // LkgDefaultMessage indicates the Kubelet is using it's last-known-good config, which is the default - LkgDefaultMessage = "using last-known-good (default)" + // NotDynamicLocalMessage indicates that the Kubelet is using its local config - we send this when dynamic Kubelet config is disabled by omitting the --dynamic-config-dir flag + NotDynamicLocalMessage = "using local config" + // NotDynamicLocalReason indicates that the Kubelet is using its local config - we send this when dynamic Kubelet config is disabled by omitting the --dynamic-config-dir flag + NotDynamicLocalReason = "dynamic config is currently disabled by omission of --dynamic-config-dir Kubelet flag" - // CurInitMessage indicates the Kubelet is using it's current config, which is from the init config files - CurInitMessage = "using current (init)" - // LkgInitMessage indicates the Kubelet is using it's last-known-good config, which is from the init config files - LkgInitMessage = "using last-known-good (init)" + // CurLocalMessage indicates that the Kubelet is using its local config, which consists of defaults, flags, and/or local files + CurLocalMessage = "using current: local" + // LkgLocalMessage indicates that the Kubelet is using its local config, which consists of defaults, flags, and/or local files + LkgLocalMessage = "using last-known-good: local" - // CurRemoteMessageFmt indicates the Kubelet is usin it's current config, which is from an API source - CurRemoteMessageFmt = "using current (UID: %q)" - // LkgRemoteMessageFmt indicates the Kubelet is using it's last-known-good config, which is from an API source - LkgRemoteMessageFmt = "using last-known-good (UID: %q)" + // CurRemoteMessageFmt indicates the Kubelet is using its current config, which is from an API source + CurRemoteMessageFmt = "using current: %s" + // LkgRemoteMessageFmt indicates the Kubelet is using its last-known-good config, which is from an API source + LkgRemoteMessageFmt = "using last-known-good: %s" - // CurDefaultOKReason indicates that no init config files were provided - CurDefaultOKReason = "current is set to the local default, and no init config was provided" - // CurInitOKReason indicates that init config files were provided - CurInitOKReason = "current is set to the local default, and an init config was provided" - // CurRemoteOKReason indicates that the config referenced by Node.ConfigSource is currently passing all checks - CurRemoteOKReason = "passing all checks" + // CurLocalOkayReason indicates that the Kubelet is using its local config + CurLocalOkayReason = "when the config source is nil, the Kubelet uses its local config" + // CurRemoteOkayReason indicates that the config referenced by Node.ConfigSource is currently passing all checks + CurRemoteOkayReason = "passing all checks" // CurFailLoadReasonFmt indicates that the Kubelet failed to load the current config checkpoint for an API source - CurFailLoadReasonFmt = "failed to load current (UID: %q)" + CurFailLoadReasonFmt = "failed to load current: %s" // CurFailParseReasonFmt indicates that the Kubelet failed to parse the current config checkpoint for an API source - CurFailParseReasonFmt = "failed to parse current (UID: %q)" + CurFailParseReasonFmt = "failed to parse current: %s" // CurFailValidateReasonFmt indicates that the Kubelet failed to validate the current config checkpoint for an API source - CurFailValidateReasonFmt = "failed to validate current (UID: %q)" - // CurFailCrashLoopReasonFmt indicates that the Kubelet experienced a crash loop while using the current config checkpoint for an API source - CurFailCrashLoopReasonFmt = "current failed trial period due to crash loop (UID: %q)" + CurFailValidateReasonFmt = "failed to validate current: %s" // LkgFail*ReasonFmt reasons are currently used to print errors in the Kubelet log, but do not appear in Node.Status.Conditions // LkgFailLoadReasonFmt indicates that the Kubelet failed to load the last-known-good config checkpoint for an API source - LkgFailLoadReasonFmt = "failed to load last-known-good (UID: %q)" + LkgFailLoadReasonFmt = "failed to load last-known-good: %s" // LkgFailParseReasonFmt indicates that the Kubelet failed to parse the last-known-good config checkpoint for an API source - LkgFailParseReasonFmt = "failed to parse last-known-good (UID: %q)" + LkgFailParseReasonFmt = "failed to parse last-known-good: %s" // LkgFailValidateReasonFmt indicates that the Kubelet failed to validate the last-known-good config checkpoint for an API source - LkgFailValidateReasonFmt = "failed to validate last-known-good (UID: %q)" + LkgFailValidateReasonFmt = "failed to validate last-known-good: %s" // FailSyncReasonFmt is used when the system couldn't sync the config, due to a malformed Node.Spec.ConfigSource, a download failure, etc. FailSyncReasonFmt = "failed to sync, reason: %s" @@ -80,21 +78,21 @@ const ( FailSyncReasonPartialObjectReference = "invalid ObjectReference, all of UID, Name, and Namespace must be specified" // FailSyncReasonUIDMismatchFmt is used when there is a UID mismatch between the referenced and downloaded ConfigMaps, // this can happen because objects must be downloaded by namespace/name, rather than by UID - FailSyncReasonUIDMismatchFmt = "invalid ObjectReference, UID %q does not match UID of downloaded ConfigMap %q" + FailSyncReasonUIDMismatchFmt = "invalid ConfigSource.ConfigMapRef.UID: %s does not match %s.UID: %s" // FailSyncReasonDownloadFmt is used when the download fails, e.g. due to network issues - FailSyncReasonDownloadFmt = "failed to download ConfigMap with name %q from namespace %q" + FailSyncReasonDownloadFmt = "failed to download: %s" // FailSyncReasonInformer is used when the informer fails to report the Node object FailSyncReasonInformer = "failed to read Node from informer object cache" // FailSyncReasonReset is used when we can't reset the local configuration references, e.g. due to filesystem issues - FailSyncReasonReset = "failed to reset to local (default or init) config" + FailSyncReasonReset = "failed to reset to local config" // FailSyncReasonCheckpointExistenceFmt is used when we can't determine if a checkpoint already exists, e.g. due to filesystem issues - FailSyncReasonCheckpointExistenceFmt = "failed to determine whether object with UID %q was already checkpointed" + FailSyncReasonCheckpointExistenceFmt = "failed to determine whether object %s with UID %s was already checkpointed" // FailSyncReasonSaveCheckpointFmt is used when we can't save a checkpoint, e.g. due to filesystem issues - FailSyncReasonSaveCheckpointFmt = "failed to save config checkpoint for object with UID %q" + FailSyncReasonSaveCheckpointFmt = "failed to save config checkpoint for object %s with UID %s" // FailSyncReasonSetCurrentDefault is used when we can't set the current config checkpoint to the local default, e.g. due to filesystem issues - FailSyncReasonSetCurrentDefault = "failed to set current config checkpoint to default" + FailSyncReasonSetCurrentLocal = "failed to set current config checkpoint to local config" // FailSyncReasonSetCurrentUIDFmt is used when we can't set the current config checkpoint to a checkpointed object, e.g. due to filesystem issues - FailSyncReasonSetCurrentUIDFmt = "failed to set current config checkpoint to object with UID %q" + FailSyncReasonSetCurrentUIDFmt = "failed to set current config checkpoint to object %s with UID %s" // EmptyMessage is a placeholder in the case that we accidentally set the condition's message to the empty string. // Doing so can result in a partial patch, and thus a confusing status; this makes it clear that the message was not provided. @@ -257,18 +255,18 @@ func (c *configOKCondition) Sync(client clientset.Interface, nodeName string) { // generate the patch mediaType := "application/json" - info, ok := kuberuntime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), mediaType) + info, ok := kuberuntime.SerializerInfoForMediaType(legacyscheme.Codecs.SupportedMediaTypes(), mediaType) if !ok { err = fmt.Errorf("unsupported media type %q", mediaType) return } - versions := api.Registry.EnabledVersionsForGroup(api.GroupName) + versions := legacyscheme.Registry.EnabledVersionsForGroup(api.GroupName) if len(versions) == 0 { err = fmt.Errorf("no enabled versions for group %q", api.GroupName) return } // the "best" version supposedly comes first in the list returned from apiv1.Registry.EnabledVersionsForGroup - encoder := api.Codecs.EncoderForVersion(info.Serializer, versions[0]) + encoder := legacyscheme.Codecs.EncoderForVersion(info.Serializer, versions[0]) before, err := kuberuntime.Encode(encoder, node) if err != nil { diff --git a/pkg/kubelet/kubeletconfig/util/codec/BUILD b/pkg/kubelet/kubeletconfig/util/codec/BUILD index cc704d9b56e..50b123e7a3a 100644 --- a/pkg/kubelet/kubeletconfig/util/codec/BUILD +++ b/pkg/kubelet/kubeletconfig/util/codec/BUILD @@ -10,8 +10,8 @@ go_library( srcs = ["codec.go"], importpath = "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/codec", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core/install:go_default_library", "//pkg/kubelet/apis/kubeletconfig:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", diff --git a/pkg/kubelet/kubeletconfig/util/codec/codec.go b/pkg/kubelet/kubeletconfig/util/codec/codec.go index 148967caba8..3316f955af2 100644 --- a/pkg/kubelet/kubeletconfig/util/codec/codec.go +++ b/pkg/kubelet/kubeletconfig/util/codec/codec.go @@ -20,11 +20,11 @@ import ( "fmt" // ensure the core apis are installed - _ "k8s.io/kubernetes/pkg/api/install" + _ "k8s.io/kubernetes/pkg/apis/core/install" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" ) @@ -34,18 +34,18 @@ import ( func NewJSONEncoder(groupName string) (runtime.Encoder, error) { // encode to json mediaType := "application/json" - info, ok := runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), mediaType) + info, ok := runtime.SerializerInfoForMediaType(legacyscheme.Codecs.SupportedMediaTypes(), mediaType) if !ok { return nil, fmt.Errorf("unsupported media type %q", mediaType) } - versions := api.Registry.EnabledVersionsForGroup(groupName) + versions := legacyscheme.Registry.EnabledVersionsForGroup(groupName) if len(versions) == 0 { return nil, fmt.Errorf("no enabled versions for group %q", groupName) } - // the "best" version supposedly comes first in the list returned from api.Registry.EnabledVersionsForGroup - return api.Codecs.EncoderForVersion(info.Serializer, versions[0]), nil + // the "best" version supposedly comes first in the list returned from legacyscheme.Registry.EnabledVersionsForGroup + return legacyscheme.Codecs.EncoderForVersion(info.Serializer, versions[0]), nil } // DecodeKubeletConfiguration decodes a serialized KubeletConfiguration to the internal type diff --git a/pkg/kubelet/kubeletconfig/util/files/files.go b/pkg/kubelet/kubeletconfig/util/files/files.go index a732bbcce81..aa76e151d23 100644 --- a/pkg/kubelet/kubeletconfig/util/files/files.go +++ b/pkg/kubelet/kubeletconfig/util/files/files.go @@ -109,10 +109,7 @@ func ReplaceFile(fs utilfs.Filesystem, path string, data []byte) error { return err } // rename over existing file - if err := fs.Rename(tmpPath, path); err != nil { - return err - } - return nil + return fs.Rename(tmpPath, path) } // DirExists returns true if a directory exists at `path`, false if `path` does not exist, otherwise an error @@ -138,8 +135,5 @@ func EnsureDir(fs utilfs.Filesystem, path string) error { } // Assert: dir does not exist // create the dir - if err := fs.MkdirAll(path, defaultPerm); err != nil { - return err - } - return nil + return fs.MkdirAll(path, defaultPerm) } diff --git a/pkg/kubelet/kubeletconfig/watch.go b/pkg/kubelet/kubeletconfig/watch.go index 2c0363c7cab..ecc255b962f 100644 --- a/pkg/kubelet/kubeletconfig/watch.go +++ b/pkg/kubelet/kubeletconfig/watch.go @@ -47,12 +47,12 @@ func newSharedNodeInformer(client clientset.Interface, nodeName string, lw := &cache.ListWatch{ ListFunc: func(options metav1.ListOptions) (kuberuntime.Object, error) { - return client.Core().Nodes().List(metav1.ListOptions{ + return client.CoreV1().Nodes().List(metav1.ListOptions{ FieldSelector: fieldselector.String(), }) }, WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return client.Core().Nodes().Watch(metav1.ListOptions{ + return client.CoreV1().Nodes().Watch(metav1.ListOptions{ FieldSelector: fieldselector.String(), ResourceVersion: options.ResourceVersion, }) diff --git a/pkg/kubelet/kuberuntime/BUILD b/pkg/kubelet/kuberuntime/BUILD index 7bc557b6773..9438ec410d3 100644 --- a/pkg/kubelet/kuberuntime/BUILD +++ b/pkg/kubelet/kuberuntime/BUILD @@ -25,15 +25,17 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/kubelet/kuberuntime", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/v1/helper:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core/v1/helper:go_default_library", "//pkg/credentialprovider:go_default_library", + "//pkg/credentialprovider/secrets:go_default_library", "//pkg/kubelet/apis/cri:go_default_library", "//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library", "//pkg/kubelet/cm:go_default_library", "//pkg/kubelet/container:go_default_library", "//pkg/kubelet/events:go_default_library", "//pkg/kubelet/images:go_default_library", + "//pkg/kubelet/kuberuntime/logs:go_default_library", "//pkg/kubelet/lifecycle:go_default_library", "//pkg/kubelet/metrics:go_default_library", "//pkg/kubelet/prober/results:go_default_library", @@ -48,8 +50,6 @@ go_library( "//pkg/util/tail:go_default_library", "//pkg/util/version:go_default_library", "//vendor/github.com/armon/circbuf:go_default_library", - "//vendor/github.com/docker/docker/pkg/jsonlog:go_default_library", - "//vendor/github.com/fsnotify/fsnotify:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/google/cadvisor/info/v1:go_default_library", "//vendor/google.golang.org/grpc:go_default_library", @@ -73,15 +73,14 @@ go_test( "kuberuntime_container_test.go", "kuberuntime_gc_test.go", "kuberuntime_image_test.go", - "kuberuntime_logs_test.go", "kuberuntime_manager_test.go", "kuberuntime_sandbox_test.go", "labels_test.go", "legacy_test.go", "security_context_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/kuberuntime", - library = ":go_default_library", deps = [ "//pkg/credentialprovider:go_default_library", "//pkg/kubelet/apis/cri/testing:go_default_library", @@ -113,6 +112,9 @@ filegroup( filegroup( name = "all-srcs", - srcs = [":package-srcs"], + srcs = [ + ":package-srcs", + "//pkg/kubelet/kuberuntime/logs:all-srcs", + ], tags = ["automanaged"], ) diff --git a/pkg/kubelet/kuberuntime/fake_kuberuntime_manager.go b/pkg/kubelet/kuberuntime/fake_kuberuntime_manager.go index 0dd2ddeafb4..a630588e2e9 100644 --- a/pkg/kubelet/kuberuntime/fake_kuberuntime_manager.go +++ b/pkg/kubelet/kuberuntime/fake_kuberuntime_manager.go @@ -33,6 +33,10 @@ import ( proberesults "k8s.io/kubernetes/pkg/kubelet/prober/results" ) +const ( + fakeSeccompProfileRoot = "/fakeSeccompProfileRoot" +) + type fakeHTTP struct { url string err error @@ -78,6 +82,7 @@ func NewFakeKubeRuntimeManager(runtimeService internalapi.RuntimeService, imageS runtimeService: runtimeService, imageService: imageService, keyring: keyring, + seccompProfileRoot: fakeSeccompProfileRoot, internalLifecycle: cm.NewFakeInternalContainerLifecycle(), } diff --git a/pkg/kubelet/kuberuntime/helpers.go b/pkg/kubelet/kuberuntime/helpers.go index 2ecc2e81ed4..d1ee8de3f98 100644 --- a/pkg/kubelet/kuberuntime/helpers.go +++ b/pkg/kubelet/kuberuntime/helpers.go @@ -25,7 +25,7 @@ import ( "github.com/golang/glog" "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" ) @@ -41,11 +41,6 @@ const ( minQuotaPeriod = 1000 ) -var ( - // The default dns opt strings - defaultDNSOptions = []string{"ndots:5"} -) - type podsByID []*kubecontainer.Pod func (b podsByID) Len() int { return len(b) } @@ -278,7 +273,7 @@ func (m *kubeGenericRuntimeManager) getSeccompProfileFromAnnotations(annotations if strings.HasPrefix(profile, "localhost/") { name := strings.TrimPrefix(profile, "localhost/") fname := filepath.Join(m.seccompProfileRoot, filepath.FromSlash(name)) - return fname + return "localhost/" + fname } return profile diff --git a/pkg/kubelet/kuberuntime/helpers_test.go b/pkg/kubelet/kuberuntime/helpers_test.go index 9de0544825f..8690749fa1c 100644 --- a/pkg/kubelet/kuberuntime/helpers_test.go +++ b/pkg/kubelet/kuberuntime/helpers_test.go @@ -17,9 +17,12 @@ limitations under the License. package kuberuntime import ( + "path/filepath" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtimetesting "k8s.io/kubernetes/pkg/kubelet/apis/cri/testing" @@ -205,3 +208,100 @@ func TestGetImageUser(t *testing.T) { assert.Equal(t, test.expectedImageUserValues.username, username, "TestCase[%d]", j) } } + +func TestGetSeccompProfileFromAnnotations(t *testing.T) { + _, _, m, err := createTestRuntimeManager() + require.NoError(t, err) + + tests := []struct { + description string + annotation map[string]string + containerName string + expectedProfile string + }{ + { + description: "no seccomp should return empty string", + expectedProfile: "", + }, + { + description: "no seccomp with containerName should return exmpty string", + containerName: "container1", + expectedProfile: "", + }, + { + description: "pod docker/default seccomp profile should return docker/default", + annotation: map[string]string{ + v1.SeccompPodAnnotationKey: "docker/default", + }, + expectedProfile: "docker/default", + }, + { + description: "pod docker/default seccomp profile with containerName should return docker/default", + annotation: map[string]string{ + v1.SeccompPodAnnotationKey: "docker/default", + }, + containerName: "container1", + expectedProfile: "docker/default", + }, + { + description: "pod unconfined seccomp profile should return unconfined", + annotation: map[string]string{ + v1.SeccompPodAnnotationKey: "unconfined", + }, + expectedProfile: "unconfined", + }, + { + description: "pod unconfined seccomp profile with containerName should return unconfined", + annotation: map[string]string{ + v1.SeccompPodAnnotationKey: "unconfined", + }, + containerName: "container1", + expectedProfile: "unconfined", + }, + { + description: "pod localhost seccomp profile should return local profile path", + annotation: map[string]string{ + v1.SeccompPodAnnotationKey: "localhost/chmod.json", + }, + expectedProfile: "localhost/" + filepath.Join(fakeSeccompProfileRoot, "chmod.json"), + }, + { + description: "pod localhost seccomp profile with containerName should return local profile path", + annotation: map[string]string{ + v1.SeccompPodAnnotationKey: "localhost/chmod.json", + }, + containerName: "container1", + expectedProfile: "localhost/" + filepath.Join(fakeSeccompProfileRoot, "chmod.json"), + }, + { + description: "container localhost seccomp profile with containerName should return local profile path", + annotation: map[string]string{ + v1.SeccompContainerAnnotationKeyPrefix + "container1": "localhost/chmod.json", + }, + containerName: "container1", + expectedProfile: "localhost/" + filepath.Join(fakeSeccompProfileRoot, "chmod.json"), + }, + { + description: "container localhost seccomp profile should override pod profile", + annotation: map[string]string{ + v1.SeccompPodAnnotationKey: "unconfined", + v1.SeccompContainerAnnotationKeyPrefix + "container1": "localhost/chmod.json", + }, + containerName: "container1", + expectedProfile: "localhost/" + filepath.Join(fakeSeccompProfileRoot, "chmod.json"), + }, + { + description: "container localhost seccomp profile with unmatched containerName should return empty string", + annotation: map[string]string{ + v1.SeccompContainerAnnotationKeyPrefix + "container1": "localhost/chmod.json", + }, + containerName: "container2", + expectedProfile: "", + }, + } + + for i, test := range tests { + seccompProfile := m.getSeccompProfileFromAnnotations(test.annotation, test.containerName) + assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]", i) + } +} diff --git a/pkg/kubelet/kuberuntime/instrumented_services_test.go b/pkg/kubelet/kuberuntime/instrumented_services_test.go index fab1470070a..933fc299599 100644 --- a/pkg/kubelet/kuberuntime/instrumented_services_test.go +++ b/pkg/kubelet/kuberuntime/instrumented_services_test.go @@ -17,13 +17,15 @@ limitations under the License. package kuberuntime import ( - "github.com/prometheus/client_golang/prometheus" - "github.com/stretchr/testify/assert" - "k8s.io/kubernetes/pkg/kubelet/metrics" "net" "net/http" "testing" "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/assert" + runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" + "k8s.io/kubernetes/pkg/kubelet/metrics" ) func TestRecordOperation(t *testing.T) { @@ -59,3 +61,31 @@ func TestRecordOperation(t *testing.T) { mux.ServeHTTP(w, r) }), "GET", prometheusUrl, nil, runtimeOperationsLatencyExpected) } + +func TestInstrumentedVersion(t *testing.T) { + fakeRuntime, _, _, _ := createTestRuntimeManager() + irs := newInstrumentedRuntimeService(fakeRuntime) + vr, err := irs.Version("1") + assert.NoError(t, err) + assert.Equal(t, kubeRuntimeAPIVersion, vr.Version) +} + +func TestStatus(t *testing.T) { + fakeRuntime, _, _, _ := createTestRuntimeManager() + fakeRuntime.FakeStatus = &runtimeapi.RuntimeStatus{ + Conditions: []*runtimeapi.RuntimeCondition{ + {Type: runtimeapi.RuntimeReady, Status: false}, + {Type: runtimeapi.NetworkReady, Status: true}, + }, + } + irs := newInstrumentedRuntimeService(fakeRuntime) + actural, err := irs.Status() + assert.NoError(t, err) + expected := &runtimeapi.RuntimeStatus{ + Conditions: []*runtimeapi.RuntimeCondition{ + {Type: runtimeapi.RuntimeReady, Status: false}, + {Type: runtimeapi.NetworkReady, Status: true}, + }, + } + assert.Equal(t, expected, actural) +} diff --git a/pkg/kubelet/kuberuntime/kuberuntime_container.go b/pkg/kubelet/kuberuntime/kuberuntime_container.go index 5e803b9cc9b..de0b6fdd7f3 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_container.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_container.go @@ -174,7 +174,7 @@ func (m *kubeGenericRuntimeManager) startContainer(podSandboxID string, podSandb // generateContainerConfig generates container config for kubelet runtime v1. func (m *kubeGenericRuntimeManager) generateContainerConfig(container *v1.Container, pod *v1.Pod, restartCount int, podIP, imageRef string) (*runtimeapi.ContainerConfig, error) { - opts, _, err := m.runtimeHelper.GenerateRunContainerOptions(pod, container, podIP) + opts, err := m.runtimeHelper.GenerateRunContainerOptions(pod, container, podIP) if err != nil { return nil, err } @@ -183,13 +183,10 @@ func (m *kubeGenericRuntimeManager) generateContainerConfig(container *v1.Contai if err != nil { return nil, err } - if uid != nil { - // Verify RunAsNonRoot. Non-root verification only supports numeric user. - if err := verifyRunAsNonRoot(pod, container, *uid); err != nil { - return nil, err - } - } else if username != "" { - glog.Warningf("Non-root verification doesn't support non-numeric user (%s)", username) + + // Verify RunAsNonRoot. Non-root verification only supports numeric user. + if err := verifyRunAsNonRoot(pod, container, uid, username); err != nil { + return nil, err } command, args := kubecontainer.ExpandContainerCommandAndArgs(container, opts.Envs) diff --git a/pkg/kubelet/kuberuntime/kuberuntime_container_test.go b/pkg/kubelet/kuberuntime/kuberuntime_container_test.go index 525b48ead85..c91a3a82e3f 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_container_test.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_container_test.go @@ -207,7 +207,7 @@ func makeExpectedConfig(m *kubeGenericRuntimeManager, pod *v1.Pod, containerInde container := &pod.Spec.Containers[containerIndex] podIP := "" restartCount := 0 - opts, _, _ := m.runtimeHelper.GenerateRunContainerOptions(pod, container, podIP) + opts, _ := m.runtimeHelper.GenerateRunContainerOptions(pod, container, podIP) containerLogsPath := buildContainerLogsPath(container.Name, restartCount) restartCountUint32 := uint32(restartCount) envs := make([]*runtimeapi.KeyValue, len(opts.Envs)) @@ -236,7 +236,7 @@ func makeExpectedConfig(m *kubeGenericRuntimeManager, pod *v1.Pod, containerInde } func TestGenerateContainerConfig(t *testing.T) { - _, _, m, err := createTestRuntimeManager() + _, imageService, m, err := createTestRuntimeManager() assert.NoError(t, err) pod := &v1.Pod{ @@ -290,6 +290,18 @@ func TestGenerateContainerConfig(t *testing.T) { _, err = m.generateContainerConfig(&podWithContainerSecurityContext.Spec.Containers[0], podWithContainerSecurityContext, 0, "", podWithContainerSecurityContext.Spec.Containers[0].Image) assert.Error(t, err) + + imageId, _ := imageService.PullImage(&runtimeapi.ImageSpec{Image: "busybox"}, nil) + image, _ := imageService.ImageStatus(&runtimeapi.ImageSpec{Image: imageId}) + + image.Uid = nil + image.Username = "test" + + podWithContainerSecurityContext.Spec.Containers[0].SecurityContext.RunAsUser = nil + podWithContainerSecurityContext.Spec.Containers[0].SecurityContext.RunAsNonRoot = &runAsNonRootTrue + + _, err = m.generateContainerConfig(&podWithContainerSecurityContext.Spec.Containers[0], podWithContainerSecurityContext, 0, "", podWithContainerSecurityContext.Spec.Containers[0].Image) + assert.Error(t, err, "RunAsNonRoot should fail for non-numeric username") } func TestLifeCycleHook(t *testing.T) { diff --git a/pkg/kubelet/kuberuntime/kuberuntime_image.go b/pkg/kubelet/kuberuntime/kuberuntime_image.go index 66ca5c7145a..7d7249034b5 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_image.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_image.go @@ -21,6 +21,7 @@ import ( "k8s.io/api/core/v1" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/kubernetes/pkg/credentialprovider" + credentialprovidersecrets "k8s.io/kubernetes/pkg/credentialprovider/secrets" runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/util/parsers" @@ -35,7 +36,7 @@ func (m *kubeGenericRuntimeManager) PullImage(image kubecontainer.ImageSpec, pul return "", err } - keyring, err := credentialprovider.MakeDockerKeyring(pullSecrets, m.keyring) + keyring, err := credentialprovidersecrets.MakeDockerKeyring(pullSecrets, m.keyring) if err != nil { return "", err } diff --git a/pkg/kubelet/kuberuntime/kuberuntime_logs.go b/pkg/kubelet/kuberuntime/kuberuntime_logs.go index 752e41b76e7..ee8dc4c81fe 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_logs.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_logs.go @@ -17,361 +17,19 @@ limitations under the License. package kuberuntime import ( - "bufio" - "bytes" - "encoding/json" - "errors" - "fmt" "io" - "math" - "os" "time" - "github.com/docker/docker/pkg/jsonlog" - "github.com/fsnotify/fsnotify" - "github.com/golang/glog" - "k8s.io/api/core/v1" - runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" - "k8s.io/kubernetes/pkg/util/tail" + "k8s.io/kubernetes/pkg/kubelet/kuberuntime/logs" ) -// Notice that the current kuberuntime logs implementation doesn't handle -// log rotation. -// * It will not retrieve logs in rotated log file. -// * If log rotation happens when following the log: -// * If the rotation is using create mode, we'll still follow the old file. -// * If the rotation is using copytruncate, we'll be reading at the original position and get nothing. -// TODO(random-liu): Support log rotation. - -// streamType is the type of the stream. -type streamType string - -const ( - stderrType streamType = "stderr" - stdoutType streamType = "stdout" - - // timeFormat is the time format used in the log. - timeFormat = time.RFC3339Nano - // blockSize is the block size used in tail. - blockSize = 1024 - - // stateCheckPeriod is the period to check container state while following - // the container log. Kubelet should not keep following the log when the - // container is not running. - stateCheckPeriod = 5 * time.Second -) - -var ( - // eol is the end-of-line sign in the log. - eol = []byte{'\n'} - // delimiter is the delimiter for timestamp and streamtype in log line. - delimiter = []byte{' '} -) - -// logMessage is the internal log type. -type logMessage struct { - timestamp time.Time - stream streamType - log []byte -} - -// reset resets the log to nil. -func (l *logMessage) reset() { - l.timestamp = time.Time{} - l.stream = "" - l.log = nil -} - -// logOptions is the internal type of all log options. -type logOptions struct { - tail int64 - bytes int64 - since time.Time - follow bool - timestamp bool -} - -// newLogOptions convert the v1.PodLogOptions to internal logOptions. -func newLogOptions(apiOpts *v1.PodLogOptions, now time.Time) *logOptions { - opts := &logOptions{ - tail: -1, // -1 by default which means read all logs. - bytes: -1, // -1 by default which means read all logs. - follow: apiOpts.Follow, - timestamp: apiOpts.Timestamps, - } - if apiOpts.TailLines != nil { - opts.tail = *apiOpts.TailLines - } - if apiOpts.LimitBytes != nil { - opts.bytes = *apiOpts.LimitBytes - } - 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 opts -} - // ReadLogs read the container log and redirect into stdout and stderr. // Note that containerID is only needed when following the log, or else // just pass in empty string "". func (m *kubeGenericRuntimeManager) ReadLogs(path, containerID string, apiOpts *v1.PodLogOptions, stdout, stderr io.Writer) error { - f, err := os.Open(path) - if err != nil { - return fmt.Errorf("failed to open log file %q: %v", path, err) - } - defer f.Close() - // Convert v1.PodLogOptions into internal log options. - opts := newLogOptions(apiOpts, time.Now()) + opts := logs.NewLogOptions(apiOpts, time.Now()) - // Search start point based on tail line. - start, err := tail.FindTailLineStartIndex(f, opts.tail) - if err != nil { - return fmt.Errorf("failed to tail %d lines of log file %q: %v", opts.tail, path, err) - } - if _, err := f.Seek(start, os.SEEK_SET); err != nil { - return fmt.Errorf("failed to seek %d in log file %q: %v", start, path, err) - } - - // Start parsing the logs. - r := bufio.NewReader(f) - // Do not create watcher here because it is not needed if `Follow` is false. - var watcher *fsnotify.Watcher - var parse parseFunc - writer := newLogWriter(stdout, stderr, opts) - msg := &logMessage{} - for { - l, err := r.ReadBytes(eol[0]) - if err != nil { - if err != io.EOF { // This is an real error - return fmt.Errorf("failed to read log file %q: %v", path, err) - } - if !opts.follow { - // Return directly when reading to the end if not follow. - if len(l) > 0 { - glog.Warningf("Incomplete line in log file %q: %q", path, l) - } - glog.V(2).Infof("Finish parsing log file %q", path) - return nil - } - // Reset seek so that if this is an incomplete line, - // it will be read again. - if _, err := f.Seek(-int64(len(l)), os.SEEK_CUR); err != nil { - return fmt.Errorf("failed to reset seek in log file %q: %v", path, err) - } - if watcher == nil { - // Intialize the watcher if it has not been initialized yet. - if watcher, err = fsnotify.NewWatcher(); err != nil { - return fmt.Errorf("failed to create fsnotify watcher: %v", err) - } - defer watcher.Close() - if err := watcher.Add(f.Name()); err != nil { - return fmt.Errorf("failed to watch file %q: %v", f.Name(), err) - } - } - // Wait until the next log change. - if found, err := m.waitLogs(containerID, watcher); !found { - return err - } - continue - } - if parse == nil { - // Intialize the log parsing function. - parse, err = getParseFunc(l) - if err != nil { - return fmt.Errorf("failed to get parse function: %v", err) - } - } - // Parse the log line. - msg.reset() - if err := parse(l, msg); err != nil { - glog.Errorf("Failed with err %v when parsing log for log file %q: %q", err, path, l) - continue - } - // Write the log line into the stream. - if err := writer.write(msg); err != nil { - if err == errMaximumWrite { - glog.V(2).Infof("Finish parsing log file %q, hit bytes limit %d(bytes)", path, opts.bytes) - return nil - } - glog.Errorf("Failed with err %v when writing log for log file %q: %+v", err, path, msg) - return err - } - } -} - -// waitLogs wait for the next log write. It returns a boolean and an error. The boolean -// indicates whether a new log is found; the error is error happens during waiting new logs. -func (m *kubeGenericRuntimeManager) waitLogs(id string, w *fsnotify.Watcher) (bool, error) { - errRetry := 5 - for { - select { - case e := <-w.Events: - switch e.Op { - case fsnotify.Write: - return true, nil - default: - glog.Errorf("Unexpected fsnotify event: %v, retrying...", e) - } - case err := <-w.Errors: - glog.Errorf("Fsnotify watch error: %v, %d error retries remaining", err, errRetry) - if errRetry == 0 { - return false, err - } - errRetry-- - case <-time.After(stateCheckPeriod): - s, err := m.runtimeService.ContainerStatus(id) - if err != nil { - return false, err - } - // Only keep following container log when it is running. - if s.State != runtimeapi.ContainerState_CONTAINER_RUNNING { - glog.Errorf("Container %q is not running (state=%q)", id, s.State) - // Do not return error because it's normal that the container stops - // during waiting. - return false, nil - } - } - } -} - -// parseFunc is a function parsing one log line to the internal log type. -// Notice that the caller must make sure logMessage is not nil. -type parseFunc func([]byte, *logMessage) error - -var parseFuncs []parseFunc = []parseFunc{ - parseCRILog, // CRI log format parse function - parseDockerJSONLog, // Docker JSON log format parse function -} - -// parseCRILog parses logs in CRI log format. CRI Log format example: -// 2016-10-06T00:17:09.669794202Z stdout log content 1 -// 2016-10-06T00:17:09.669794203Z stderr log content 2 -func parseCRILog(log []byte, msg *logMessage) error { - var err error - // Parse timestamp - idx := bytes.Index(log, delimiter) - if idx < 0 { - return fmt.Errorf("timestamp is not found") - } - msg.timestamp, err = time.Parse(timeFormat, string(log[:idx])) - if err != nil { - return fmt.Errorf("unexpected timestamp format %q: %v", timeFormat, err) - } - - // Parse stream type - log = log[idx+1:] - idx = bytes.Index(log, delimiter) - if idx < 0 { - return fmt.Errorf("stream type is not found") - } - msg.stream = streamType(log[:idx]) - if msg.stream != stdoutType && msg.stream != stderrType { - return fmt.Errorf("unexpected stream type %q", msg.stream) - } - - // Get log content - msg.log = log[idx+1:] - - return nil -} - -// parseDockerJSONLog parses logs in Docker JSON log format. Docker JSON log format -// example: -// {"log":"content 1","stream":"stdout","time":"2016-10-20T18:39:20.57606443Z"} -// {"log":"content 2","stream":"stderr","time":"2016-10-20T18:39:20.57606444Z"} -func parseDockerJSONLog(log []byte, msg *logMessage) error { - var l = &jsonlog.JSONLog{} - l.Reset() - - // TODO: JSON decoding is fairly expensive, we should evaluate this. - if err := json.Unmarshal(log, l); err != nil { - return fmt.Errorf("failed with %v to unmarshal log %q", err, l) - } - msg.timestamp = l.Created - msg.stream = streamType(l.Stream) - msg.log = []byte(l.Log) - return nil -} - -// getParseFunc returns proper parse function based on the sample log line passed in. -func getParseFunc(log []byte) (parseFunc, error) { - for _, p := range parseFuncs { - if err := p(log, &logMessage{}); err == nil { - return p, nil - } - } - return nil, fmt.Errorf("unsupported log format: %q", log) -} - -// logWriter controls the writing into the stream based on the log options. -type logWriter struct { - stdout io.Writer - stderr io.Writer - opts *logOptions - remain int64 -} - -// errMaximumWrite is returned when all bytes have been written. -var errMaximumWrite = errors.New("maximum write") - -// errShortWrite is returned when the message is not fully written. -var errShortWrite = errors.New("short write") - -func newLogWriter(stdout io.Writer, stderr io.Writer, opts *logOptions) *logWriter { - w := &logWriter{ - stdout: stdout, - stderr: stderr, - opts: opts, - remain: math.MaxInt64, // initialize it as infinity - } - if opts.bytes >= 0 { - w.remain = opts.bytes - } - return w -} - -// writeLogs writes logs into stdout, stderr. -func (w *logWriter) write(msg *logMessage) error { - if msg.timestamp.Before(w.opts.since) { - // Skip the line because it's older than since - return nil - } - line := msg.log - if w.opts.timestamp { - prefix := append([]byte(msg.timestamp.Format(timeFormat)), delimiter[0]) - line = append(prefix, line...) - } - // If the line is longer than the remaining bytes, cut it. - if int64(len(line)) > w.remain { - line = line[:w.remain] - } - // Get the proper stream to write to. - var stream io.Writer - switch msg.stream { - case stdoutType: - stream = w.stdout - case stderrType: - stream = w.stderr - default: - return fmt.Errorf("unexpected stream type %q", msg.stream) - } - n, err := stream.Write(line) - w.remain -= int64(n) - if err != nil { - return err - } - // If the line has not been fully written, return errShortWrite - if n < len(line) { - return errShortWrite - } - // If there are no more bytes left, return errMaximumWrite - if w.remain <= 0 { - return errMaximumWrite - } - return nil + return logs.ReadLogs(path, containerID, opts, m.runtimeService, stdout, stderr) } diff --git a/pkg/kubelet/kuberuntime/kuberuntime_logs_test.go b/pkg/kubelet/kuberuntime/kuberuntime_logs_test.go deleted file mode 100644 index 19e2cdb2f3b..00000000000 --- a/pkg/kubelet/kuberuntime/kuberuntime_logs_test.go +++ /dev/null @@ -1,243 +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 kuberuntime - -import ( - "bytes" - "testing" - "time" - - "github.com/stretchr/testify/assert" - - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func TestLogOptions(t *testing.T) { - var ( - line = int64(8) - bytes = int64(64) - timestamp = metav1.Now() - sinceseconds = int64(10) - ) - for c, test := range []struct { - apiOpts *v1.PodLogOptions - expect *logOptions - }{ - { // empty options - apiOpts: &v1.PodLogOptions{}, - expect: &logOptions{tail: -1, bytes: -1}, - }, - { // test tail lines - apiOpts: &v1.PodLogOptions{TailLines: &line}, - expect: &logOptions{tail: line, bytes: -1}, - }, - { // test limit bytes - apiOpts: &v1.PodLogOptions{LimitBytes: &bytes}, - expect: &logOptions{tail: -1, bytes: bytes}, - }, - { // test since timestamp - apiOpts: &v1.PodLogOptions{SinceTime: ×tamp}, - expect: &logOptions{tail: -1, bytes: -1, since: timestamp.Time}, - }, - { // test since seconds - apiOpts: &v1.PodLogOptions{SinceSeconds: &sinceseconds}, - expect: &logOptions{tail: -1, bytes: -1, since: timestamp.Add(-10 * time.Second)}, - }, - } { - t.Logf("TestCase #%d: %+v", c, test) - opts := newLogOptions(test.apiOpts, timestamp.Time) - assert.Equal(t, test.expect, opts) - } -} - -func TestParseLog(t *testing.T) { - timestamp, err := time.Parse(timeFormat, "2016-10-20T18:39:20.57606443Z") - assert.NoError(t, err) - msg := &logMessage{} - for c, test := range []struct { - line string - msg *logMessage - err bool - }{ - { // Docker log format stdout - line: `{"log":"docker stdout test log","stream":"stdout","time":"2016-10-20T18:39:20.57606443Z"}` + "\n", - msg: &logMessage{ - timestamp: timestamp, - stream: stdoutType, - log: []byte("docker stdout test log"), - }, - }, - { // Docker log format stderr - line: `{"log":"docker stderr test log","stream":"stderr","time":"2016-10-20T18:39:20.57606443Z"}` + "\n", - msg: &logMessage{ - timestamp: timestamp, - stream: stderrType, - log: []byte("docker stderr test log"), - }, - }, - { // CRI log format stdout - line: "2016-10-20T18:39:20.57606443Z stdout cri stdout test log\n", - msg: &logMessage{ - timestamp: timestamp, - stream: stdoutType, - log: []byte("cri stdout test log\n"), - }, - }, - { // CRI log format stderr - line: "2016-10-20T18:39:20.57606443Z stderr cri stderr test log\n", - msg: &logMessage{ - timestamp: timestamp, - stream: stderrType, - log: []byte("cri stderr test log\n"), - }, - }, - { // Unsupported Log format - line: "unsupported log format test log\n", - msg: &logMessage{}, - err: true, - }, - } { - t.Logf("TestCase #%d: %+v", c, test) - parse, err := getParseFunc([]byte(test.line)) - if test.err { - assert.Error(t, err) - continue - } - assert.NoError(t, err) - err = parse([]byte(test.line), msg) - assert.NoError(t, err) - assert.Equal(t, test.msg, msg) - } -} - -func TestWriteLogs(t *testing.T) { - timestamp := time.Unix(1234, 4321) - log := "abcdefg\n" - - for c, test := range []struct { - stream streamType - since time.Time - timestamp bool - expectStdout string - expectStderr string - }{ - { // stderr log - stream: stderrType, - expectStderr: log, - }, - { // stdout log - stream: stdoutType, - expectStdout: log, - }, - { // since is after timestamp - stream: stdoutType, - since: timestamp.Add(1 * time.Second), - }, - { // timestamp enabled - stream: stderrType, - timestamp: true, - expectStderr: timestamp.Format(timeFormat) + " " + log, - }, - } { - t.Logf("TestCase #%d: %+v", c, test) - msg := &logMessage{ - timestamp: timestamp, - stream: test.stream, - log: []byte(log), - } - stdoutBuf := bytes.NewBuffer(nil) - stderrBuf := bytes.NewBuffer(nil) - w := newLogWriter(stdoutBuf, stderrBuf, &logOptions{since: test.since, timestamp: test.timestamp, bytes: -1}) - err := w.write(msg) - assert.NoError(t, err) - assert.Equal(t, test.expectStdout, stdoutBuf.String()) - assert.Equal(t, test.expectStderr, stderrBuf.String()) - } -} - -func TestWriteLogsWithBytesLimit(t *testing.T) { - timestamp := time.Unix(1234, 4321) - timestampStr := timestamp.Format(timeFormat) - log := "abcdefg\n" - - for c, test := range []struct { - stdoutLines int - stderrLines int - bytes int - timestamp bool - expectStdout string - expectStderr string - }{ - { // limit bytes less than one line - stdoutLines: 3, - bytes: 3, - expectStdout: "abc", - }, - { // limit bytes across lines - stdoutLines: 3, - bytes: len(log) + 3, - expectStdout: "abcdefg\nabc", - }, - { // limit bytes more than all lines - stdoutLines: 3, - bytes: 3 * len(log), - expectStdout: "abcdefg\nabcdefg\nabcdefg\n", - }, - { // limit bytes for stderr - stderrLines: 3, - bytes: len(log) + 3, - expectStderr: "abcdefg\nabc", - }, - { // limit bytes for both stdout and stderr, stdout first. - stdoutLines: 1, - stderrLines: 2, - bytes: len(log) + 3, - expectStdout: "abcdefg\n", - expectStderr: "abc", - }, - { // limit bytes with timestamp - stdoutLines: 3, - timestamp: true, - bytes: len(timestampStr) + 1 + len(log) + 2, - expectStdout: timestampStr + " " + log + timestampStr[:2], - }, - } { - t.Logf("TestCase #%d: %+v", c, test) - msg := &logMessage{ - timestamp: timestamp, - log: []byte(log), - } - stdoutBuf := bytes.NewBuffer(nil) - stderrBuf := bytes.NewBuffer(nil) - w := newLogWriter(stdoutBuf, stderrBuf, &logOptions{timestamp: test.timestamp, bytes: int64(test.bytes)}) - for i := 0; i < test.stdoutLines; i++ { - msg.stream = stdoutType - if err := w.write(msg); err != nil { - assert.EqualError(t, err, errMaximumWrite.Error()) - } - } - for i := 0; i < test.stderrLines; i++ { - msg.stream = stderrType - if err := w.write(msg); err != nil { - assert.EqualError(t, err, errMaximumWrite.Error()) - } - } - assert.Equal(t, test.expectStdout, stdoutBuf.String()) - assert.Equal(t, test.expectStderr, stderrBuf.String()) - } -} diff --git a/pkg/kubelet/kuberuntime/kuberuntime_manager.go b/pkg/kubelet/kuberuntime/kuberuntime_manager.go index 0e8da97581d..c63e02dc8bc 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_manager.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_manager.go @@ -32,7 +32,7 @@ import ( "k8s.io/client-go/tools/record" ref "k8s.io/client-go/tools/reference" "k8s.io/client-go/util/flowcontrol" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/credentialprovider" internalapi "k8s.io/kubernetes/pkg/kubelet/apis/cri" runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" @@ -570,7 +570,7 @@ func (m *kubeGenericRuntimeManager) SyncPod(pod *v1.Pod, _ v1.PodStatus, podStat podContainerChanges := m.computePodActions(pod, podStatus) glog.V(3).Infof("computePodActions got %+v for pod %q", podContainerChanges, format.Pod(pod)) if podContainerChanges.CreateSandbox { - ref, err := ref.GetReference(api.Scheme, pod) + ref, err := ref.GetReference(legacyscheme.Scheme, pod) if err != nil { glog.Errorf("Couldn't make a ref to pod %q: '%v'", format.Pod(pod), err) } @@ -645,20 +645,20 @@ func (m *kubeGenericRuntimeManager) SyncPod(pod *v1.Pod, _ v1.PodStatus, podStat if err != nil { createSandboxResult.Fail(kubecontainer.ErrCreatePodSandbox, msg) glog.Errorf("createPodSandbox for pod %q failed: %v", format.Pod(pod), err) - ref, err := ref.GetReference(api.Scheme, pod) - if err != nil { - glog.Errorf("Couldn't make a ref to pod %q: '%v'", format.Pod(pod), err) + ref, referr := ref.GetReference(legacyscheme.Scheme, pod) + if referr != nil { + glog.Errorf("Couldn't make a ref to pod %q: '%v'", format.Pod(pod), referr) } - m.recorder.Eventf(ref, v1.EventTypeWarning, events.FailedCreatePodSandBox, "Failed create pod sandbox.") + m.recorder.Eventf(ref, v1.EventTypeWarning, events.FailedCreatePodSandBox, "Failed create pod sandbox: %v", err) return } glog.V(4).Infof("Created PodSandbox %q for pod %q", podSandboxID, format.Pod(pod)) podSandboxStatus, err := m.runtimeService.PodSandboxStatus(podSandboxID) if err != nil { - ref, err := ref.GetReference(api.Scheme, pod) - if err != nil { - glog.Errorf("Couldn't make a ref to pod %q: '%v'", format.Pod(pod), err) + ref, referr := ref.GetReference(legacyscheme.Scheme, pod) + if referr != nil { + glog.Errorf("Couldn't make a ref to pod %q: '%v'", format.Pod(pod), referr) } m.recorder.Eventf(ref, v1.EventTypeWarning, events.FailedStatusPodSandBox, "Unable to get pod sandbox status: %v", err) glog.Errorf("Failed to get pod sandbox status: %v; Skipping pod %q", err, format.Pod(pod)) diff --git a/pkg/kubelet/kuberuntime/kuberuntime_sandbox.go b/pkg/kubelet/kuberuntime/kuberuntime_sandbox.go index 670181b3199..2773270e2fa 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_sandbox.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_sandbox.go @@ -74,17 +74,11 @@ func (m *kubeGenericRuntimeManager) generatePodSandboxConfig(pod *v1.Pod, attemp Annotations: newPodAnnotations(pod), } - dnsServers, dnsSearches, useClusterFirstPolicy, err := m.runtimeHelper.GetClusterDNS(pod) + dnsConfig, err := m.runtimeHelper.GetPodDNS(pod) if err != nil { return nil, err } - podSandboxConfig.DnsConfig = &runtimeapi.DNSConfig{ - Servers: dnsServers, - Searches: dnsSearches, - } - if useClusterFirstPolicy { - podSandboxConfig.DnsConfig.Options = defaultDNSOptions - } + podSandboxConfig.DnsConfig = dnsConfig if !kubecontainer.IsHostNetworkPod(pod) { // TODO: Add domain support in new runtime interface diff --git a/pkg/kubelet/kuberuntime/logs/BUILD b/pkg/kubelet/kuberuntime/logs/BUILD new file mode 100644 index 00000000000..6385e448859 --- /dev/null +++ b/pkg/kubelet/kuberuntime/logs/BUILD @@ -0,0 +1,44 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["logs.go"], + importpath = "k8s.io/kubernetes/pkg/kubelet/kuberuntime/logs", + visibility = ["//visibility:public"], + deps = [ + "//pkg/kubelet/apis/cri:go_default_library", + "//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library", + "//pkg/util/tail:go_default_library", + "//vendor/github.com/docker/docker/pkg/jsonlog:go_default_library", + "//vendor/github.com/fsnotify/fsnotify:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["logs_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/kubelet/kuberuntime/logs", + deps = [ + "//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library", + "//vendor/github.com/stretchr/testify/assert:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/kubelet/kuberuntime/logs/logs.go b/pkg/kubelet/kuberuntime/logs/logs.go new file mode 100644 index 00000000000..e029d40d6c0 --- /dev/null +++ b/pkg/kubelet/kuberuntime/logs/logs.go @@ -0,0 +1,390 @@ +/* +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 logs + +import ( + "bufio" + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "math" + "os" + "time" + + "github.com/docker/docker/pkg/jsonlog" + "github.com/fsnotify/fsnotify" + "github.com/golang/glog" + + "k8s.io/api/core/v1" + internalapi "k8s.io/kubernetes/pkg/kubelet/apis/cri" + runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" + "k8s.io/kubernetes/pkg/util/tail" +) + +// Notice that the current CRI logs implementation doesn't handle +// log rotation. +// * It will not retrieve logs in rotated log file. +// * If log rotation happens when following the log: +// * If the rotation is using create mode, we'll still follow the old file. +// * If the rotation is using copytruncate, we'll be reading at the original position and get nothing. +// TODO(random-liu): Support log rotation. + +const ( + // timeFormat is the time format used in the log. + timeFormat = time.RFC3339Nano + // blockSize is the block size used in tail. + blockSize = 1024 + + // stateCheckPeriod is the period to check container state while following + // the container log. Kubelet should not keep following the log when the + // container is not running. + stateCheckPeriod = 5 * time.Second +) + +var ( + // eol is the end-of-line sign in the log. + eol = []byte{'\n'} + // delimiter is the delimiter for timestamp and stream type in log line. + delimiter = []byte{' '} + // tagDelimiter is the delimiter for log tags. + tagDelimiter = []byte(runtimeapi.LogTagDelimiter) +) + +// logMessage is the CRI internal log type. +type logMessage struct { + timestamp time.Time + stream runtimeapi.LogStreamType + log []byte +} + +// reset resets the log to nil. +func (l *logMessage) reset() { + l.timestamp = time.Time{} + l.stream = "" + l.log = nil +} + +// LogOptions is the CRI internal type of all log options. +type LogOptions struct { + tail int64 + bytes int64 + since time.Time + follow bool + timestamp bool +} + +// NewLogOptions convert the v1.PodLogOptions to CRI internal LogOptions. +func NewLogOptions(apiOpts *v1.PodLogOptions, now time.Time) *LogOptions { + opts := &LogOptions{ + tail: -1, // -1 by default which means read all logs. + bytes: -1, // -1 by default which means read all logs. + follow: apiOpts.Follow, + timestamp: apiOpts.Timestamps, + } + if apiOpts.TailLines != nil { + opts.tail = *apiOpts.TailLines + } + if apiOpts.LimitBytes != nil { + opts.bytes = *apiOpts.LimitBytes + } + 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 opts +} + +// parseFunc is a function parsing one log line to the internal log type. +// Notice that the caller must make sure logMessage is not nil. +type parseFunc func([]byte, *logMessage) error + +var parseFuncs = []parseFunc{ + parseCRILog, // CRI log format parse function + parseDockerJSONLog, // Docker JSON log format parse function +} + +// parseCRILog parses logs in CRI log format. CRI Log format example: +// 2016-10-06T00:17:09.669794202Z stdout P log content 1 +// 2016-10-06T00:17:09.669794203Z stderr F log content 2 +func parseCRILog(log []byte, msg *logMessage) error { + var err error + // Parse timestamp + idx := bytes.Index(log, delimiter) + if idx < 0 { + return fmt.Errorf("timestamp is not found") + } + msg.timestamp, err = time.Parse(timeFormat, string(log[:idx])) + if err != nil { + return fmt.Errorf("unexpected timestamp format %q: %v", timeFormat, err) + } + + // Parse stream type + log = log[idx+1:] + idx = bytes.Index(log, delimiter) + if idx < 0 { + return fmt.Errorf("stream type is not found") + } + msg.stream = runtimeapi.LogStreamType(log[:idx]) + if msg.stream != runtimeapi.Stdout && msg.stream != runtimeapi.Stderr { + return fmt.Errorf("unexpected stream type %q", msg.stream) + } + + // Parse log tag + log = log[idx+1:] + idx = bytes.Index(log, delimiter) + if idx < 0 { + return fmt.Errorf("log tag is not found") + } + // Keep this forward compatible. + tags := bytes.Split(log[:idx], tagDelimiter) + partial := (runtimeapi.LogTag(tags[0]) == runtimeapi.LogTagPartial) + // Trim the tailing new line if this is a partial line. + if partial && len(log) > 0 && log[len(log)-1] == '\n' { + log = log[:len(log)-1] + } + + // Get log content + msg.log = log[idx+1:] + + return nil +} + +// parseDockerJSONLog parses logs in Docker JSON log format. Docker JSON log format +// example: +// {"log":"content 1","stream":"stdout","time":"2016-10-20T18:39:20.57606443Z"} +// {"log":"content 2","stream":"stderr","time":"2016-10-20T18:39:20.57606444Z"} +func parseDockerJSONLog(log []byte, msg *logMessage) error { + var l = &jsonlog.JSONLog{} + l.Reset() + + // TODO: JSON decoding is fairly expensive, we should evaluate this. + if err := json.Unmarshal(log, l); err != nil { + return fmt.Errorf("failed with %v to unmarshal log %q", err, l) + } + msg.timestamp = l.Created + msg.stream = runtimeapi.LogStreamType(l.Stream) + msg.log = []byte(l.Log) + return nil +} + +// getParseFunc returns proper parse function based on the sample log line passed in. +func getParseFunc(log []byte) (parseFunc, error) { + for _, p := range parseFuncs { + if err := p(log, &logMessage{}); err == nil { + return p, nil + } + } + return nil, fmt.Errorf("unsupported log format: %q", log) +} + +// logWriter controls the writing into the stream based on the log options. +type logWriter struct { + stdout io.Writer + stderr io.Writer + opts *LogOptions + remain int64 +} + +// errMaximumWrite is returned when all bytes have been written. +var errMaximumWrite = errors.New("maximum write") + +// errShortWrite is returned when the message is not fully written. +var errShortWrite = errors.New("short write") + +func newLogWriter(stdout io.Writer, stderr io.Writer, opts *LogOptions) *logWriter { + w := &logWriter{ + stdout: stdout, + stderr: stderr, + opts: opts, + remain: math.MaxInt64, // initialize it as infinity + } + if opts.bytes >= 0 { + w.remain = opts.bytes + } + return w +} + +// writeLogs writes logs into stdout, stderr. +func (w *logWriter) write(msg *logMessage) error { + if msg.timestamp.Before(w.opts.since) { + // Skip the line because it's older than since + return nil + } + line := msg.log + if w.opts.timestamp { + prefix := append([]byte(msg.timestamp.Format(timeFormat)), delimiter[0]) + line = append(prefix, line...) + } + // If the line is longer than the remaining bytes, cut it. + if int64(len(line)) > w.remain { + line = line[:w.remain] + } + // Get the proper stream to write to. + var stream io.Writer + switch msg.stream { + case runtimeapi.Stdout: + stream = w.stdout + case runtimeapi.Stderr: + stream = w.stderr + default: + return fmt.Errorf("unexpected stream type %q", msg.stream) + } + n, err := stream.Write(line) + w.remain -= int64(n) + if err != nil { + return err + } + // If the line has not been fully written, return errShortWrite + if n < len(line) { + return errShortWrite + } + // If there are no more bytes left, return errMaximumWrite + if w.remain <= 0 { + return errMaximumWrite + } + return nil +} + +// ReadLogs read the container log and redirect into stdout and stderr. +// Note that containerID is only needed when following the log, or else +// just pass in empty string "". +func ReadLogs(path, containerID string, opts *LogOptions, runtimeService internalapi.RuntimeService, stdout, stderr io.Writer) error { + f, err := os.Open(path) + if err != nil { + return fmt.Errorf("failed to open log file %q: %v", path, err) + } + defer f.Close() + + // Search start point based on tail line. + start, err := tail.FindTailLineStartIndex(f, opts.tail) + if err != nil { + return fmt.Errorf("failed to tail %d lines of log file %q: %v", opts.tail, path, err) + } + if _, err := f.Seek(start, os.SEEK_SET); err != nil { + return fmt.Errorf("failed to seek %d in log file %q: %v", start, path, err) + } + + // Start parsing the logs. + r := bufio.NewReader(f) + // Do not create watcher here because it is not needed if `Follow` is false. + var watcher *fsnotify.Watcher + var parse parseFunc + var stop bool + writer := newLogWriter(stdout, stderr, opts) + msg := &logMessage{} + for { + if stop { + glog.V(2).Infof("Finish parsing log file %q", path) + return nil + } + l, err := r.ReadBytes(eol[0]) + if err != nil { + if err != io.EOF { // This is an real error + return fmt.Errorf("failed to read log file %q: %v", path, err) + } + if opts.follow { + // Reset seek so that if this is an incomplete line, + // it will be read again. + if _, err := f.Seek(-int64(len(l)), os.SEEK_CUR); err != nil { + return fmt.Errorf("failed to reset seek in log file %q: %v", path, err) + } + if watcher == nil { + // Intialize the watcher if it has not been initialized yet. + if watcher, err = fsnotify.NewWatcher(); err != nil { + return fmt.Errorf("failed to create fsnotify watcher: %v", err) + } + defer watcher.Close() + if err := watcher.Add(f.Name()); err != nil { + return fmt.Errorf("failed to watch file %q: %v", f.Name(), err) + } + } + // Wait until the next log change. + if found, err := waitLogs(containerID, watcher, runtimeService); !found { + return err + } + continue + } + // Should stop after writing the remaining content. + stop = true + if len(l) == 0 { + continue + } + glog.Warningf("Incomplete line in log file %q: %q", path, l) + } + if parse == nil { + // Intialize the log parsing function. + parse, err = getParseFunc(l) + if err != nil { + return fmt.Errorf("failed to get parse function: %v", err) + } + } + // Parse the log line. + msg.reset() + if err := parse(l, msg); err != nil { + glog.Errorf("Failed with err %v when parsing log for log file %q: %q", err, path, l) + continue + } + // Write the log line into the stream. + if err := writer.write(msg); err != nil { + if err == errMaximumWrite { + glog.V(2).Infof("Finish parsing log file %q, hit bytes limit %d(bytes)", path, opts.bytes) + return nil + } + glog.Errorf("Failed with err %v when writing log for log file %q: %+v", err, path, msg) + return err + } + } +} + +// waitLogs wait for the next log write. It returns a boolean and an error. The boolean +// indicates whether a new log is found; the error is error happens during waiting new logs. +func waitLogs(id string, w *fsnotify.Watcher, runtimeService internalapi.RuntimeService) (bool, error) { + errRetry := 5 + for { + select { + case e := <-w.Events: + switch e.Op { + case fsnotify.Write: + return true, nil + default: + glog.Errorf("Unexpected fsnotify event: %v, retrying...", e) + } + case err := <-w.Errors: + glog.Errorf("Fsnotify watch error: %v, %d error retries remaining", err, errRetry) + if errRetry == 0 { + return false, err + } + errRetry-- + case <-time.After(stateCheckPeriod): + s, err := runtimeService.ContainerStatus(id) + if err != nil { + return false, err + } + // Only keep following container log when it is running. + if s.State != runtimeapi.ContainerState_CONTAINER_RUNNING { + glog.Errorf("Container %q is not running (state=%q)", id, s.State) + // Do not return error because it's normal that the container stops + // during waiting. + return false, nil + } + } + } +} diff --git a/pkg/kubelet/kuberuntime/logs/logs_test.go b/pkg/kubelet/kuberuntime/logs/logs_test.go new file mode 100644 index 00000000000..17b074a01fd --- /dev/null +++ b/pkg/kubelet/kuberuntime/logs/logs_test.go @@ -0,0 +1,260 @@ +/* +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 logs + +import ( + "bytes" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" +) + +func TestLogOptions(t *testing.T) { + var ( + line = int64(8) + bytes = int64(64) + timestamp = metav1.Now() + sinceseconds = int64(10) + ) + for c, test := range []struct { + apiOpts *v1.PodLogOptions + expect *LogOptions + }{ + { // empty options + apiOpts: &v1.PodLogOptions{}, + expect: &LogOptions{tail: -1, bytes: -1}, + }, + { // test tail lines + apiOpts: &v1.PodLogOptions{TailLines: &line}, + expect: &LogOptions{tail: line, bytes: -1}, + }, + { // test limit bytes + apiOpts: &v1.PodLogOptions{LimitBytes: &bytes}, + expect: &LogOptions{tail: -1, bytes: bytes}, + }, + { // test since timestamp + apiOpts: &v1.PodLogOptions{SinceTime: ×tamp}, + expect: &LogOptions{tail: -1, bytes: -1, since: timestamp.Time}, + }, + { // test since seconds + apiOpts: &v1.PodLogOptions{SinceSeconds: &sinceseconds}, + expect: &LogOptions{tail: -1, bytes: -1, since: timestamp.Add(-10 * time.Second)}, + }, + } { + t.Logf("TestCase #%d: %+v", c, test) + opts := NewLogOptions(test.apiOpts, timestamp.Time) + assert.Equal(t, test.expect, opts) + } +} + +func TestParseLog(t *testing.T) { + timestamp, err := time.Parse(timeFormat, "2016-10-20T18:39:20.57606443Z") + assert.NoError(t, err) + msg := &logMessage{} + for c, test := range []struct { + line string + msg *logMessage + err bool + }{ + { // Docker log format stdout + line: `{"log":"docker stdout test log","stream":"stdout","time":"2016-10-20T18:39:20.57606443Z"}` + "\n", + msg: &logMessage{ + timestamp: timestamp, + stream: runtimeapi.Stdout, + log: []byte("docker stdout test log"), + }, + }, + { // Docker log format stderr + line: `{"log":"docker stderr test log","stream":"stderr","time":"2016-10-20T18:39:20.57606443Z"}` + "\n", + msg: &logMessage{ + timestamp: timestamp, + stream: runtimeapi.Stderr, + log: []byte("docker stderr test log"), + }, + }, + { // CRI log format stdout + line: "2016-10-20T18:39:20.57606443Z stdout F cri stdout test log\n", + msg: &logMessage{ + timestamp: timestamp, + stream: runtimeapi.Stdout, + log: []byte("cri stdout test log\n"), + }, + }, + { // CRI log format stderr + line: "2016-10-20T18:39:20.57606443Z stderr F cri stderr test log\n", + msg: &logMessage{ + timestamp: timestamp, + stream: runtimeapi.Stderr, + log: []byte("cri stderr test log\n"), + }, + }, + { // Unsupported Log format + line: "unsupported log format test log\n", + msg: &logMessage{}, + err: true, + }, + { // Partial CRI log line + line: "2016-10-20T18:39:20.57606443Z stdout P cri stdout partial test log\n", + msg: &logMessage{ + timestamp: timestamp, + stream: runtimeapi.Stdout, + log: []byte("cri stdout partial test log"), + }, + }, + { // Partial CRI log line with multiple log tags. + line: "2016-10-20T18:39:20.57606443Z stdout P:TAG1:TAG2 cri stdout partial test log\n", + msg: &logMessage{ + timestamp: timestamp, + stream: runtimeapi.Stdout, + log: []byte("cri stdout partial test log"), + }, + }, + } { + t.Logf("TestCase #%d: %+v", c, test) + parse, err := getParseFunc([]byte(test.line)) + if test.err { + assert.Error(t, err) + continue + } + assert.NoError(t, err) + err = parse([]byte(test.line), msg) + assert.NoError(t, err) + assert.Equal(t, test.msg, msg) + } +} + +func TestWriteLogs(t *testing.T) { + timestamp := time.Unix(1234, 4321) + log := "abcdefg\n" + + for c, test := range []struct { + stream runtimeapi.LogStreamType + since time.Time + timestamp bool + expectStdout string + expectStderr string + }{ + { // stderr log + stream: runtimeapi.Stderr, + expectStderr: log, + }, + { // stdout log + stream: runtimeapi.Stdout, + expectStdout: log, + }, + { // since is after timestamp + stream: runtimeapi.Stdout, + since: timestamp.Add(1 * time.Second), + }, + { // timestamp enabled + stream: runtimeapi.Stderr, + timestamp: true, + expectStderr: timestamp.Format(timeFormat) + " " + log, + }, + } { + t.Logf("TestCase #%d: %+v", c, test) + msg := &logMessage{ + timestamp: timestamp, + stream: test.stream, + log: []byte(log), + } + stdoutBuf := bytes.NewBuffer(nil) + stderrBuf := bytes.NewBuffer(nil) + w := newLogWriter(stdoutBuf, stderrBuf, &LogOptions{since: test.since, timestamp: test.timestamp, bytes: -1}) + err := w.write(msg) + assert.NoError(t, err) + assert.Equal(t, test.expectStdout, stdoutBuf.String()) + assert.Equal(t, test.expectStderr, stderrBuf.String()) + } +} + +func TestWriteLogsWithBytesLimit(t *testing.T) { + timestamp := time.Unix(1234, 4321) + timestampStr := timestamp.Format(timeFormat) + log := "abcdefg\n" + + for c, test := range []struct { + stdoutLines int + stderrLines int + bytes int + timestamp bool + expectStdout string + expectStderr string + }{ + { // limit bytes less than one line + stdoutLines: 3, + bytes: 3, + expectStdout: "abc", + }, + { // limit bytes across lines + stdoutLines: 3, + bytes: len(log) + 3, + expectStdout: "abcdefg\nabc", + }, + { // limit bytes more than all lines + stdoutLines: 3, + bytes: 3 * len(log), + expectStdout: "abcdefg\nabcdefg\nabcdefg\n", + }, + { // limit bytes for stderr + stderrLines: 3, + bytes: len(log) + 3, + expectStderr: "abcdefg\nabc", + }, + { // limit bytes for both stdout and stderr, stdout first. + stdoutLines: 1, + stderrLines: 2, + bytes: len(log) + 3, + expectStdout: "abcdefg\n", + expectStderr: "abc", + }, + { // limit bytes with timestamp + stdoutLines: 3, + timestamp: true, + bytes: len(timestampStr) + 1 + len(log) + 2, + expectStdout: timestampStr + " " + log + timestampStr[:2], + }, + } { + t.Logf("TestCase #%d: %+v", c, test) + msg := &logMessage{ + timestamp: timestamp, + log: []byte(log), + } + stdoutBuf := bytes.NewBuffer(nil) + stderrBuf := bytes.NewBuffer(nil) + w := newLogWriter(stdoutBuf, stderrBuf, &LogOptions{timestamp: test.timestamp, bytes: int64(test.bytes)}) + for i := 0; i < test.stdoutLines; i++ { + msg.stream = runtimeapi.Stdout + if err := w.write(msg); err != nil { + assert.EqualError(t, err, errMaximumWrite.Error()) + } + } + for i := 0; i < test.stderrLines; i++ { + msg.stream = runtimeapi.Stderr + if err := w.write(msg); err != nil { + assert.EqualError(t, err, errMaximumWrite.Error()) + } + } + assert.Equal(t, test.expectStdout, stdoutBuf.String()) + assert.Equal(t, test.expectStderr, stderrBuf.String()) + } +} diff --git a/pkg/kubelet/kuberuntime/security_context.go b/pkg/kubelet/kuberuntime/security_context.go index c0d4ce7347a..3d48d372ac4 100644 --- a/pkg/kubelet/kuberuntime/security_context.go +++ b/pkg/kubelet/kuberuntime/security_context.go @@ -75,7 +75,7 @@ func (m *kubeGenericRuntimeManager) determineEffectiveSecurityContext(pod *v1.Po } // verifyRunAsNonRoot verifies RunAsNonRoot. -func verifyRunAsNonRoot(pod *v1.Pod, container *v1.Container, uid int64) error { +func verifyRunAsNonRoot(pod *v1.Pod, container *v1.Container, uid *int64, username string) error { effectiveSc := securitycontext.DetermineEffectiveSecurityContext(pod, container) // If the option is not set, or if running as root is allowed, return nil. if effectiveSc == nil || effectiveSc.RunAsNonRoot == nil || !*effectiveSc.RunAsNonRoot { @@ -89,11 +89,14 @@ func verifyRunAsNonRoot(pod *v1.Pod, container *v1.Container, uid int64) error { return nil } - if uid == 0 { + switch { + case uid != nil && *uid == 0: return fmt.Errorf("container has runAsNonRoot and image will run as root") + case uid == nil && len(username) > 0: + return fmt.Errorf("container has runAsNonRoot and image has non-numeric user (%s), cannot verify user is non-root", username) + default: + return nil } - - return nil } // convertToRuntimeSecurityContext converts v1.SecurityContext to runtimeapi.SecurityContext. diff --git a/pkg/kubelet/kuberuntime/security_context_test.go b/pkg/kubelet/kuberuntime/security_context_test.go index ae724ab314a..d0aa922d5f5 100644 --- a/pkg/kubelet/kuberuntime/security_context_test.go +++ b/pkg/kubelet/kuberuntime/security_context_test.go @@ -45,37 +45,37 @@ func TestVerifyRunAsNonRoot(t *testing.T) { } rootUser := int64(0) + anyUser := int64(1000) runAsNonRootTrue := true runAsNonRootFalse := false - imageRootUser := int64(0) - imageNonRootUser := int64(123) for _, test := range []struct { - desc string - sc *v1.SecurityContext - imageUser int64 - fail bool + desc string + sc *v1.SecurityContext + uid *int64 + username string + fail bool }{ { - desc: "Pass if SecurityContext is not set", - sc: nil, - imageUser: imageRootUser, - fail: false, + desc: "Pass if SecurityContext is not set", + sc: nil, + uid: &rootUser, + fail: false, }, { desc: "Pass if RunAsNonRoot is not set", sc: &v1.SecurityContext{ RunAsUser: &rootUser, }, - imageUser: imageRootUser, - fail: false, + uid: &rootUser, + fail: false, }, { desc: "Pass if RunAsNonRoot is false (image user is root)", sc: &v1.SecurityContext{ RunAsNonRoot: &runAsNonRootFalse, }, - imageUser: imageRootUser, - fail: false, + uid: &rootUser, + fail: false, }, { desc: "Pass if RunAsNonRoot is false (RunAsUser is root)", @@ -83,8 +83,8 @@ func TestVerifyRunAsNonRoot(t *testing.T) { RunAsNonRoot: &runAsNonRootFalse, RunAsUser: &rootUser, }, - imageUser: imageNonRootUser, - fail: false, + uid: &rootUser, + fail: false, }, { desc: "Fail if container's RunAsUser is root and RunAsNonRoot is true", @@ -92,20 +92,43 @@ func TestVerifyRunAsNonRoot(t *testing.T) { RunAsNonRoot: &runAsNonRootTrue, RunAsUser: &rootUser, }, - imageUser: imageNonRootUser, - fail: true, + uid: &rootUser, + fail: true, }, { desc: "Fail if image's user is root and RunAsNonRoot is true", sc: &v1.SecurityContext{ RunAsNonRoot: &runAsNonRootTrue, }, - imageUser: imageRootUser, - fail: true, + uid: &rootUser, + fail: true, + }, + { + desc: "Fail if image's username is set and RunAsNonRoot is true", + sc: &v1.SecurityContext{ + RunAsNonRoot: &runAsNonRootTrue, + }, + username: "test", + fail: true, + }, + { + desc: "Pass if image's user is non-root and RunAsNonRoot is true", + sc: &v1.SecurityContext{ + RunAsNonRoot: &runAsNonRootTrue, + }, + uid: &anyUser, + fail: false, + }, + { + desc: "Pass if container's user and image's user aren't set and RunAsNonRoot is true", + sc: &v1.SecurityContext{ + RunAsNonRoot: &runAsNonRootTrue, + }, + fail: false, }, } { pod.Spec.Containers[0].SecurityContext = test.sc - err := verifyRunAsNonRoot(pod, &pod.Spec.Containers[0], int64(0)) + err := verifyRunAsNonRoot(pod, &pod.Spec.Containers[0], test.uid, test.username) if test.fail { assert.Error(t, err, test.desc) } else { diff --git a/pkg/kubelet/lifecycle/BUILD b/pkg/kubelet/lifecycle/BUILD index cd4997cdd2b..a1a64a03e14 100644 --- a/pkg/kubelet/lifecycle/BUILD +++ b/pkg/kubelet/lifecycle/BUILD @@ -21,10 +21,10 @@ go_library( "//pkg/kubelet/container:go_default_library", "//pkg/kubelet/types:go_default_library", "//pkg/kubelet/util/format:go_default_library", + "//pkg/scheduler/algorithm:go_default_library", + "//pkg/scheduler/algorithm/predicates:go_default_library", + "//pkg/scheduler/schedulercache:go_default_library", "//pkg/security/apparmor:go_default_library", - "//plugin/pkg/scheduler/algorithm:go_default_library", - "//plugin/pkg/scheduler/algorithm/predicates:go_default_library", - "//plugin/pkg/scheduler/schedulercache:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", @@ -35,10 +35,11 @@ go_library( go_test( name = "go_default_test", srcs = ["handlers_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/lifecycle", - library = ":go_default_library", deps = [ "//pkg/kubelet/container:go_default_library", + "//pkg/kubelet/util/format:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", ], diff --git a/pkg/kubelet/lifecycle/admission_failure_handler_stub.go b/pkg/kubelet/lifecycle/admission_failure_handler_stub.go index c98339782e4..58e675e9a9b 100644 --- a/pkg/kubelet/lifecycle/admission_failure_handler_stub.go +++ b/pkg/kubelet/lifecycle/admission_failure_handler_stub.go @@ -18,7 +18,7 @@ package lifecycle import ( "k8s.io/api/core/v1" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" + "k8s.io/kubernetes/pkg/scheduler/algorithm" ) // AdmissionFailureHandlerStub is an AdmissionFailureHandler that does not perform any handling of admission failure. diff --git a/pkg/kubelet/lifecycle/handlers.go b/pkg/kubelet/lifecycle/handlers.go index 450c985e2a4..b941d85537d 100644 --- a/pkg/kubelet/lifecycle/handlers.go +++ b/pkg/kubelet/lifecycle/handlers.go @@ -58,14 +58,14 @@ func (hr *HandlerRunner) Run(containerID kubecontainer.ContainerID, pod *v1.Pod, // TODO(tallclair): Pass a proper timeout value. output, err := hr.commandRunner.RunInContainer(containerID, handler.Exec.Command, 0) if err != nil { - msg := fmt.Sprintf("Exec lifecycle hook (%v) for Container %q in Pod %q failed - error: %v, message: %q", handler.Exec.Command, container.Name, format.Pod(pod), err, string(output)) + msg = fmt.Sprintf("Exec lifecycle hook (%v) for Container %q in Pod %q failed - error: %v, message: %q", handler.Exec.Command, container.Name, format.Pod(pod), err, string(output)) glog.V(1).Infof(msg) } return msg, err case handler.HTTPGet != nil: msg, err := hr.runHTTPHandler(pod, container, handler) if err != nil { - msg := fmt.Sprintf("Http lifecycle hook (%s) for Container %q in Pod %q failed - error: %v, message: %q", handler.HTTPGet.Path, container.Name, format.Pod(pod), err, msg) + msg = fmt.Sprintf("Http lifecycle hook (%s) for Container %q in Pod %q failed - error: %v, message: %q", handler.HTTPGet.Path, container.Name, format.Pod(pod), err, msg) glog.V(1).Infof(msg) } return msg, err diff --git a/pkg/kubelet/lifecycle/handlers_test.go b/pkg/kubelet/lifecycle/handlers_test.go index 009d8609272..900bd393688 100644 --- a/pkg/kubelet/lifecycle/handlers_test.go +++ b/pkg/kubelet/lifecycle/handlers_test.go @@ -28,6 +28,7 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/intstr" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/util/format" ) func TestResolvePortInt(t *testing.T) { @@ -78,12 +79,14 @@ func TestResolvePortStringUnknown(t *testing.T) { type fakeContainerCommandRunner struct { Cmd []string ID kubecontainer.ContainerID + Err error + Msg string } func (f *fakeContainerCommandRunner) RunInContainer(id kubecontainer.ContainerID, cmd []string, timeout time.Duration) ([]byte, error) { f.Cmd = cmd f.ID = id - return nil, nil + return []byte(f.Msg), f.Err } func TestRunHandlerExec(t *testing.T) { @@ -185,6 +188,40 @@ func TestRunHandlerNil(t *testing.T) { } } +func TestRunHandlerExecFailure(t *testing.T) { + expectedErr := fmt.Errorf("invalid command") + fakeCommandRunner := fakeContainerCommandRunner{Err: expectedErr, Msg: expectedErr.Error()} + handlerRunner := NewHandlerRunner(&fakeHTTP{}, &fakeCommandRunner, nil) + + containerID := kubecontainer.ContainerID{Type: "test", ID: "abc1234"} + containerName := "containerFoo" + command := []string{"ls", "--a"} + + container := v1.Container{ + Name: containerName, + Lifecycle: &v1.Lifecycle{ + PostStart: &v1.Handler{ + Exec: &v1.ExecAction{ + Command: command, + }, + }, + }, + } + + pod := v1.Pod{} + pod.ObjectMeta.Name = "podFoo" + pod.ObjectMeta.Namespace = "nsFoo" + pod.Spec.Containers = []v1.Container{container} + expectedErrMsg := fmt.Sprintf("Exec lifecycle hook (%s) for Container %q in Pod %q failed - error: %v, message: %q", command, containerName, format.Pod(&pod), expectedErr, expectedErr.Error()) + msg, err := handlerRunner.Run(containerID, &pod, &container, container.Lifecycle.PostStart) + if err == nil { + t.Errorf("expected error: %v", expectedErr) + } + if msg != expectedErrMsg { + t.Errorf("unexpected error message: %q; expected %q", msg, expectedErrMsg) + } +} + func TestRunHandlerHttpFailure(t *testing.T) { expectedErr := fmt.Errorf("fake http error") expectedResp := http.Response{ @@ -210,12 +247,13 @@ func TestRunHandlerHttpFailure(t *testing.T) { pod.ObjectMeta.Name = "podFoo" pod.ObjectMeta.Namespace = "nsFoo" pod.Spec.Containers = []v1.Container{container} + expectedErrMsg := fmt.Sprintf("Http lifecycle hook (%s) for Container %q in Pod %q failed - error: %v, message: %q", "bar", containerName, format.Pod(&pod), expectedErr, expectedErr.Error()) msg, err := handlerRunner.Run(containerID, &pod, &container, container.Lifecycle.PostStart) if err == nil { t.Errorf("expected error: %v", expectedErr) } - if msg != expectedErr.Error() { - t.Errorf("unexpected error message: %q; expected %q", msg, expectedErr) + if msg != expectedErrMsg { + t.Errorf("unexpected error message: %q; expected %q", msg, expectedErrMsg) } if fakeHttp.url != "http://foo:8080/bar" { t.Errorf("unexpected url: %s", fakeHttp.url) diff --git a/pkg/kubelet/lifecycle/predicate.go b/pkg/kubelet/lifecycle/predicate.go index 75c7663dcf4..e3890055f13 100644 --- a/pkg/kubelet/lifecycle/predicate.go +++ b/pkg/kubelet/lifecycle/predicate.go @@ -22,13 +22,15 @@ import ( "github.com/golang/glog" "k8s.io/api/core/v1" "k8s.io/kubernetes/pkg/kubelet/util/format" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + "k8s.io/kubernetes/pkg/scheduler/algorithm/predicates" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" ) type getNodeAnyWayFuncType func() (*v1.Node, error) +type pluginResourceUpdateFuncType func(*schedulercache.NodeInfo, *PodAdmitAttributes) error + // AdmissionFailureHandler is an interface which defines how to deal with a failure to admit a pod. // This allows for the graceful handling of pod admission failure. type AdmissionFailureHandler interface { @@ -36,15 +38,17 @@ type AdmissionFailureHandler interface { } type predicateAdmitHandler struct { - getNodeAnyWayFunc getNodeAnyWayFuncType - admissionFailureHandler AdmissionFailureHandler + getNodeAnyWayFunc getNodeAnyWayFuncType + pluginResourceUpdateFunc pluginResourceUpdateFuncType + admissionFailureHandler AdmissionFailureHandler } var _ PodAdmitHandler = &predicateAdmitHandler{} -func NewPredicateAdmitHandler(getNodeAnyWayFunc getNodeAnyWayFuncType, admissionFailureHandler AdmissionFailureHandler) *predicateAdmitHandler { +func NewPredicateAdmitHandler(getNodeAnyWayFunc getNodeAnyWayFuncType, admissionFailureHandler AdmissionFailureHandler, pluginResourceUpdateFunc pluginResourceUpdateFuncType) *predicateAdmitHandler { return &predicateAdmitHandler{ getNodeAnyWayFunc, + pluginResourceUpdateFunc, admissionFailureHandler, } } @@ -63,6 +67,16 @@ func (w *predicateAdmitHandler) Admit(attrs *PodAdmitAttributes) PodAdmitResult pods := attrs.OtherPods nodeInfo := schedulercache.NewNodeInfo(pods...) nodeInfo.SetNode(node) + // ensure the node has enough plugin resources for that required in pods + if err = w.pluginResourceUpdateFunc(nodeInfo, attrs); err != nil { + message := fmt.Sprintf("Update plugin resources failed due to %v, which is unexpected.", err) + glog.Warningf("Failed to admit pod %v - %s", format.Pod(pod), message) + return PodAdmitResult{ + Admit: false, + Reason: "UnexpectedAdmissionError", + Message: message, + } + } fit, reasons, err := predicates.GeneralPredicates(pod, nil, nodeInfo) if err != nil { message := fmt.Sprintf("GeneralPredicates failed due to %v, which is unexpected.", err) diff --git a/pkg/kubelet/metrics/metrics.go b/pkg/kubelet/metrics/metrics.go index f7399101186..0fc10dc189a 100644 --- a/pkg/kubelet/metrics/metrics.go +++ b/pkg/kubelet/metrics/metrics.go @@ -44,6 +44,9 @@ const ( RuntimeOperationsKey = "runtime_operations" RuntimeOperationsLatencyKey = "runtime_operations_latency_microseconds" RuntimeOperationsErrorsKey = "runtime_operations_errors" + // Metrics keys of device plugin operations + DevicePluginRegistrationCountKey = "device_plugin_registration_count" + DevicePluginAllocationLatencyKey = "device_plugin_alloc_latency_microseconds" ) var ( @@ -179,6 +182,22 @@ var ( }, []string{"namespace", "persistentvolumeclaim"}, ) + DevicePluginRegistrationCount = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Subsystem: KubeletSubsystem, + Name: DevicePluginRegistrationCountKey, + Help: "Cumulative number of device plugin registrations. Broken down by resource name.", + }, + []string{"resource_name"}, + ) + DevicePluginAllocationLatency = prometheus.NewSummaryVec( + prometheus.SummaryOpts{ + Subsystem: KubeletSubsystem, + Name: DevicePluginAllocationLatencyKey, + Help: "Latency in microseconds to serve a device plugin Allocation request. Broken down by resource name.", + }, + []string{"resource_name"}, + ) ) var registerMetrics sync.Once @@ -205,6 +224,8 @@ func Register(containerCache kubecontainer.RuntimeCache) { prometheus.MustRegister(VolumeStatsInodes) prometheus.MustRegister(VolumeStatsInodesFree) prometheus.MustRegister(VolumeStatsInodesUsed) + prometheus.MustRegister(DevicePluginRegistrationCount) + prometheus.MustRegister(DevicePluginAllocationLatency) }) } diff --git a/pkg/kubelet/mountpod/BUILD b/pkg/kubelet/mountpod/BUILD new file mode 100644 index 00000000000..cd187e515bd --- /dev/null +++ b/pkg/kubelet/mountpod/BUILD @@ -0,0 +1,45 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["mount_pod.go"], + importpath = "k8s.io/kubernetes/pkg/kubelet/mountpod", + visibility = ["//visibility:public"], + deps = [ + "//pkg/kubelet/config:go_default_library", + "//pkg/kubelet/pod:go_default_library", + "//pkg/util/strings:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["mount_pod_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/kubelet/mountpod", + deps = [ + "//pkg/kubelet/configmap:go_default_library", + "//pkg/kubelet/pod:go_default_library", + "//pkg/kubelet/pod/testing:go_default_library", + "//pkg/kubelet/secret:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/client-go/util/testing:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/kubelet/mountpod/mount_pod.go b/pkg/kubelet/mountpod/mount_pod.go new file mode 100644 index 00000000000..6c7afda4810 --- /dev/null +++ b/pkg/kubelet/mountpod/mount_pod.go @@ -0,0 +1,120 @@ +/* +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 mountpod + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path" + + "k8s.io/api/core/v1" + "k8s.io/kubernetes/pkg/kubelet/config" + kubepod "k8s.io/kubernetes/pkg/kubelet/pod" + "k8s.io/kubernetes/pkg/util/strings" +) + +// Manager is an interface that tracks pods with mount utilities for individual +// volume plugins. +type Manager interface { + GetMountPod(pluginName string) (pod *v1.Pod, container string, err error) +} + +// basicManager is simple implementation of Manager. Pods with mount utilities +// are registered by placing a JSON file into +// /var/lib/kubelet/plugin-containers/.json and this manager just +// finds them there. +type basicManager struct { + registrationDirectory string + podManager kubepod.Manager +} + +// volumePluginRegistration specified format of the json files placed in +// /var/lib/kubelet/plugin-containers/ +type volumePluginRegistration struct { + PodName string `json:"podName"` + PodNamespace string `json:"podNamespace"` + PodUID string `json:"podUID"` + ContainerName string `json:"containerName"` +} + +// NewManager returns a new mount pod manager. +func NewManager(rootDirectory string, podManager kubepod.Manager) (Manager, error) { + regPath := path.Join(rootDirectory, config.DefaultKubeletPluginContainersDirName) + + // Create the directory on startup + os.MkdirAll(regPath, 0700) + + return &basicManager{ + registrationDirectory: regPath, + podManager: podManager, + }, nil +} + +func (m *basicManager) getVolumePluginRegistrationPath(pluginName string) string { + // sanitize plugin name so it does not escape directory + safePluginName := strings.EscapePluginName(pluginName) + ".json" + return path.Join(m.registrationDirectory, safePluginName) +} + +func (m *basicManager) GetMountPod(pluginName string) (pod *v1.Pod, containerName string, err error) { + // Read /var/lib/kubelet/plugin-containers/.json + regPath := m.getVolumePluginRegistrationPath(pluginName) + regBytes, err := ioutil.ReadFile(regPath) + if err != nil { + if os.IsNotExist(err) { + // No pod is registered for this plugin + return nil, "", nil + } + return nil, "", fmt.Errorf("cannot read %s: %v", regPath, err) + } + + // Parse json + var reg volumePluginRegistration + if err := json.Unmarshal(regBytes, ®); err != nil { + return nil, "", fmt.Errorf("unable to parse %s: %s", regPath, err) + } + if len(reg.ContainerName) == 0 { + return nil, "", fmt.Errorf("unable to parse %s: \"containerName\" is not set", regPath) + } + if len(reg.PodUID) == 0 { + return nil, "", fmt.Errorf("unable to parse %s: \"podUID\" is not set", regPath) + } + if len(reg.PodNamespace) == 0 { + return nil, "", fmt.Errorf("unable to parse %s: \"podNamespace\" is not set", regPath) + } + if len(reg.PodName) == 0 { + return nil, "", fmt.Errorf("unable to parse %s: \"podName\" is not set", regPath) + } + + pod, ok := m.podManager.GetPodByName(reg.PodNamespace, reg.PodName) + if !ok { + return nil, "", fmt.Errorf("unable to process %s: pod %s/%s not found", regPath, reg.PodNamespace, reg.PodName) + } + if string(pod.UID) != reg.PodUID { + return nil, "", fmt.Errorf("unable to process %s: pod %s/%s has unexpected UID", regPath, reg.PodNamespace, reg.PodName) + } + // make sure that reg.ContainerName exists in the pod + for i := range pod.Spec.Containers { + if pod.Spec.Containers[i].Name == reg.ContainerName { + return pod, reg.ContainerName, nil + } + } + return nil, "", fmt.Errorf("unable to process %s: pod %s/%s has no container named %q", regPath, reg.PodNamespace, reg.PodName, reg.ContainerName) + +} diff --git a/pkg/kubelet/mountpod/mount_pod_test.go b/pkg/kubelet/mountpod/mount_pod_test.go new file mode 100644 index 00000000000..3f84739fff9 --- /dev/null +++ b/pkg/kubelet/mountpod/mount_pod_test.go @@ -0,0 +1,160 @@ +/* +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 mountpod + +import ( + "io/ioutil" + "os" + "path" + "testing" + + "github.com/golang/glog" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utiltesting "k8s.io/client-go/util/testing" + "k8s.io/kubernetes/pkg/kubelet/configmap" + kubepod "k8s.io/kubernetes/pkg/kubelet/pod" + podtest "k8s.io/kubernetes/pkg/kubelet/pod/testing" + "k8s.io/kubernetes/pkg/kubelet/secret" +) + +func TestGetVolumeExec(t *testing.T) { + // prepare PodManager + pods := []*v1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "bar", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + {Name: "baz"}, + }, + }, + }, + } + fakeSecretManager := secret.NewFakeManager() + fakeConfigMapManager := configmap.NewFakeManager() + podManager := kubepod.NewBasicPodManager( + podtest.NewFakeMirrorClient(), fakeSecretManager, fakeConfigMapManager) + podManager.SetPods(pods) + + // Prepare fake /var/lib/kubelet + basePath, err := utiltesting.MkTmpdir("kubelet") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(basePath) + regPath := path.Join(basePath, "plugin-containers") + + mgr, err := NewManager(basePath, podManager) + if err != nil { + t.Fatal(err) + } + + tests := []struct { + name string + json string + expectError bool + }{ + { + "invalid json", + "{{{}", + true, + }, + { + "missing json", + "", // this means no json file should be created + false, + }, + { + "missing podNamespace", + `{"podName": "foo", "podUID": "87654321", "containerName": "baz"}`, + true, + }, + { + "missing podName", + `{"podNamespace": "bar", "podUID": "87654321", "containerName": "baz"}`, + true, + }, + { + "missing containerName", + `{"podNamespace": "bar", "podName": "foo", "podUID": "87654321"}`, + true, + }, + { + "missing podUID", + `{"podNamespace": "bar", "podName": "foo", "containerName": "baz"}`, + true, + }, + { + "missing pod", + `{"podNamespace": "bar", "podName": "non-existing-pod", "podUID": "12345678", "containerName": "baz"}`, + true, + }, + { + "invalid uid", + `{"podNamespace": "bar", "podName": "foo", "podUID": "87654321", "containerName": "baz"}`, + true, + }, + { + "invalid container", + `{"podNamespace": "bar", "podName": "foo", "podUID": "12345678", "containerName": "invalid"}`, + true, + }, + { + "valid pod", + `{"podNamespace": "bar", "podName": "foo", "podUID": "12345678", "containerName": "baz"}`, + false, + }, + } + for _, test := range tests { + p := path.Join(regPath, "kubernetes.io~glusterfs.json") + if len(test.json) > 0 { + if err := ioutil.WriteFile(p, []byte(test.json), 0600); err != nil { + t.Errorf("test %q: error writing %s: %v", test.name, p, err) + continue + } + } else { + // "" means no JSON file + os.Remove(p) + } + pod, container, err := mgr.GetMountPod("kubernetes.io/glusterfs") + if err != nil { + glog.V(5).Infof("test %q returned error %s", test.name, err) + } + if err == nil && test.expectError { + t.Errorf("test %q: expected error, got none", test.name) + } + if err != nil && !test.expectError { + t.Errorf("test %q: unexpected error: %v", test.name, err) + } + + if err == nil { + // Pod must be returned when the json file was not empty + if pod == nil && len(test.json) != 0 { + t.Errorf("test %q: expected exec, got nil", test.name) + } + // Both pod and container must be returned + if pod != nil && len(container) == 0 { + t.Errorf("test %q: expected container name, got %q", test.name, container) + } + } + } +} diff --git a/pkg/kubelet/network/BUILD b/pkg/kubelet/network/BUILD index a0c4f00dfb2..51b32deeb27 100644 --- a/pkg/kubelet/network/BUILD +++ b/pkg/kubelet/network/BUILD @@ -41,6 +41,7 @@ filegroup( srcs = [ ":package-srcs", "//pkg/kubelet/network/cni:all-srcs", + "//pkg/kubelet/network/dns:all-srcs", "//pkg/kubelet/network/hairpin:all-srcs", "//pkg/kubelet/network/hostport:all-srcs", "//pkg/kubelet/network/kubenet:all-srcs", diff --git a/pkg/kubelet/network/cni/BUILD b/pkg/kubelet/network/cni/BUILD index 0ccb25e75e4..ce04e2d465b 100644 --- a/pkg/kubelet/network/cni/BUILD +++ b/pkg/kubelet/network/cni/BUILD @@ -8,7 +8,44 @@ load( go_library( name = "go_default_library", - srcs = ["cni.go"], + srcs = [ + "cni.go", + ] + select({ + "@io_bazel_rules_go//go/platform:android": [ + "cni_others.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "cni_others.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "cni_others.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "cni_others.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "cni_others.go", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "cni_others.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "cni_others.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "cni_others.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "cni_others.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "cni_others.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "cni_windows.go", + ], + "//conditions:default": [], + }), importpath = "k8s.io/kubernetes/pkg/kubelet/network/cni", deps = [ "//pkg/kubelet/apis/kubeletconfig:go_default_library", @@ -18,21 +55,26 @@ go_library( "//vendor/github.com/containernetworking/cni/pkg/types:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library", - ], + ] + select({ + "@io_bazel_rules_go//go/platform:windows": [ + "//vendor/github.com/containernetworking/cni/pkg/types/020:go_default_library", + ], + "//conditions:default": [], + }), ) go_test( name = "go_default_test", srcs = select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "cni_test.go", ], "//conditions:default": [], }), + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/network/cni", - library = ":go_default_library", deps = select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//pkg/kubelet/apis/kubeletconfig:go_default_library", "//pkg/kubelet/container:go_default_library", "//pkg/kubelet/container/testing:go_default_library", diff --git a/pkg/kubelet/network/cni/cni.go b/pkg/kubelet/network/cni/cni.go index 0ae1edc801d..15724afc486 100644 --- a/pkg/kubelet/network/cni/cni.go +++ b/pkg/kubelet/network/cni/cni.go @@ -153,37 +153,12 @@ func vendorCNIDir(prefix, pluginType string) string { return fmt.Sprintf(VendorCNIDirTemplate, prefix, pluginType) } -func getLoNetwork(binDir, vendorDirPrefix string) *cniNetwork { - loConfig, err := libcni.ConfListFromBytes([]byte(`{ - "cniVersion": "0.2.0", - "name": "cni-loopback", - "plugins":[{ - "type": "loopback" - }] -}`)) - if err != nil { - // The hardcoded config above should always be valid and unit tests will - // catch this - panic(err) - } - cninet := &libcni.CNIConfig{ - Path: []string{vendorCNIDir(vendorDirPrefix, "loopback"), binDir}, - } - loNetwork := &cniNetwork{ - name: "lo", - NetworkConfig: loConfig, - CNIConfig: cninet, - } - - return loNetwork -} - func (plugin *cniNetworkPlugin) Init(host network.Host, hairpinMode kubeletconfig.HairpinMode, nonMasqueradeCIDR string, mtu int) error { - var err error - plugin.nsenterPath, err = plugin.execer.LookPath("nsenter") + err := plugin.platformInit() if err != nil { return err } + plugin.host = host plugin.syncNetworkConfig() @@ -239,10 +214,12 @@ func (plugin *cniNetworkPlugin) SetUpPod(namespace string, name string, id kubec return fmt.Errorf("CNI failed to retrieve network namespace path: %v", err) } - _, err = plugin.addToNetwork(plugin.loNetwork, name, namespace, id, netnsPath) - if err != nil { - glog.Errorf("Error while adding to cni lo network: %s", err) - return err + // Windows doesn't have loNetwork. It comes only with Linux + if plugin.loNetwork != nil { + if _, err = plugin.addToNetwork(plugin.loNetwork, name, namespace, id, netnsPath); err != nil { + glog.Errorf("Error while adding to cni lo network: %s", err) + return err + } } _, err = plugin.addToNetwork(plugin.getDefaultNetwork(), name, namespace, id, netnsPath) @@ -268,25 +245,6 @@ func (plugin *cniNetworkPlugin) TearDownPod(namespace string, name string, id ku return plugin.deleteFromNetwork(plugin.getDefaultNetwork(), name, namespace, id, netnsPath) } -// TODO: Use the addToNetwork function to obtain the IP of the Pod. That will assume idempotent ADD call to the plugin. -// Also fix the runtime's call to Status function to be done only in the case that the IP is lost, no need to do periodic calls -func (plugin *cniNetworkPlugin) GetPodNetworkStatus(namespace string, name string, id kubecontainer.ContainerID) (*network.PodNetworkStatus, error) { - netnsPath, err := plugin.host.GetNetNS(id.ID) - if err != nil { - return nil, fmt.Errorf("CNI failed to retrieve network namespace path: %v", err) - } - if netnsPath == "" { - return nil, fmt.Errorf("Cannot find the network namespace, skipping pod network status for container %q", id) - } - - ip, err := network.GetPodIP(plugin.execer, plugin.nsenterPath, netnsPath, network.DefaultInterfaceName) - if err != nil { - return nil, err - } - - return &network.PodNetworkStatus{IP: ip}, nil -} - func (plugin *cniNetworkPlugin) addToNetwork(network *cniNetwork, podName string, podNamespace string, podSandboxID kubecontainer.ContainerID, podNetnsPath string) (cnitypes.Result, error) { rt, err := plugin.buildCNIRuntimeConf(podName, podNamespace, podSandboxID, podNetnsPath) if err != nil { @@ -315,7 +273,9 @@ func (plugin *cniNetworkPlugin) deleteFromNetwork(network *cniNetwork, podName s netConf, cniNet := network.NetworkConfig, network.CNIConfig glog.V(4).Infof("About to del CNI network %v (type=%v)", netConf.Name, netConf.Plugins[0].Network.Type) err = cniNet.DelNetworkList(netConf, rt) - if err != nil { + // The pod may not get deleted successfully at the first time. + // Ignore "no such file or directory" error in case the network has already been deleted in previous attempts. + if err != nil && !strings.Contains(err.Error(), "no such file or directory") { glog.Errorf("Error deleting network: %v", err) return err } @@ -324,7 +284,7 @@ func (plugin *cniNetworkPlugin) deleteFromNetwork(network *cniNetwork, podName s func (plugin *cniNetworkPlugin) buildCNIRuntimeConf(podName string, podNs string, podSandboxID kubecontainer.ContainerID, podNetnsPath string) (*libcni.RuntimeConf, error) { glog.V(4).Infof("Got netns path %v", podNetnsPath) - glog.V(4).Infof("Using netns path %v", podNs) + glog.V(4).Infof("Using podns path %v", podNs) rt := &libcni.RuntimeConf{ ContainerID: podSandboxID.ID, diff --git a/pkg/kubelet/network/cni/cni_others.go b/pkg/kubelet/network/cni/cni_others.go new file mode 100644 index 00000000000..735db9cc69c --- /dev/null +++ b/pkg/kubelet/network/cni/cni_others.go @@ -0,0 +1,80 @@ +// +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 cni + +import ( + "fmt" + + "github.com/containernetworking/cni/libcni" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/network" +) + +func getLoNetwork(binDir, vendorDirPrefix string) *cniNetwork { + loConfig, err := libcni.ConfListFromBytes([]byte(`{ + "cniVersion": "0.2.0", + "name": "cni-loopback", + "plugins":[{ + "type": "loopback" + }] +}`)) + if err != nil { + // The hardcoded config above should always be valid and unit tests will + // catch this + panic(err) + } + cninet := &libcni.CNIConfig{ + Path: []string{vendorCNIDir(vendorDirPrefix, "loopback"), binDir}, + } + loNetwork := &cniNetwork{ + name: "lo", + NetworkConfig: loConfig, + CNIConfig: cninet, + } + + return loNetwork +} + +func (plugin *cniNetworkPlugin) platformInit() error { + var err error + plugin.nsenterPath, err = plugin.execer.LookPath("nsenter") + if err != nil { + return err + } + return nil +} + +// TODO: Use the addToNetwork function to obtain the IP of the Pod. That will assume idempotent ADD call to the plugin. +// Also fix the runtime's call to Status function to be done only in the case that the IP is lost, no need to do periodic calls +func (plugin *cniNetworkPlugin) GetPodNetworkStatus(namespace string, name string, id kubecontainer.ContainerID) (*network.PodNetworkStatus, error) { + netnsPath, err := plugin.host.GetNetNS(id.ID) + if err != nil { + return nil, fmt.Errorf("CNI failed to retrieve network namespace path: %v", err) + } + if netnsPath == "" { + return nil, fmt.Errorf("Cannot find the network namespace, skipping pod network status for container %q", id) + } + + ip, err := network.GetPodIP(plugin.execer, plugin.nsenterPath, netnsPath, network.DefaultInterfaceName) + if err != nil { + return nil, err + } + + return &network.PodNetworkStatus{IP: ip}, nil +} diff --git a/pkg/kubelet/network/cni/cni_windows.go b/pkg/kubelet/network/cni/cni_windows.go new file mode 100644 index 00000000000..9a0b17c8f11 --- /dev/null +++ b/pkg/kubelet/network/cni/cni_windows.go @@ -0,0 +1,61 @@ +// +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 cni + +import ( + "fmt" + + cniTypes020 "github.com/containernetworking/cni/pkg/types/020" + "github.com/golang/glog" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/network" +) + +func getLoNetwork(binDir, vendorDirPrefix string) *cniNetwork { + return nil +} + +func (plugin *cniNetworkPlugin) platformInit() error { + return nil +} + +// GetPodNetworkStatus : Assuming addToNetwork is idempotent, we can call this API as many times as required to get the IPAddress +func (plugin *cniNetworkPlugin) GetPodNetworkStatus(namespace string, name string, id kubecontainer.ContainerID) (*network.PodNetworkStatus, error) { + netnsPath, err := plugin.host.GetNetNS(id.ID) + if err != nil { + return nil, fmt.Errorf("CNI failed to retrieve network namespace path: %v", err) + } + + result, err := plugin.addToNetwork(plugin.getDefaultNetwork(), name, namespace, id, netnsPath) + + glog.V(5).Infof("GetPodNetworkStatus result %+v", result) + if err != nil { + glog.Errorf("error while adding to cni network: %s", err) + return nil, err + } + + // Parse the result and get the IPAddress + var result020 *cniTypes020.Result + result020, err = cniTypes020.GetResult(result) + if err != nil { + glog.Errorf("error while cni parsing result: %s", err) + return nil, err + } + return &network.PodNetworkStatus{IP: result020.IP4.IP.IP}, nil +} diff --git a/pkg/kubelet/network/dns/BUILD b/pkg/kubelet/network/dns/BUILD new file mode 100644 index 00000000000..3088a32b803 --- /dev/null +++ b/pkg/kubelet/network/dns/BUILD @@ -0,0 +1,51 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["dns.go"], + importpath = "k8s.io/kubernetes/pkg/kubelet/network/dns", + visibility = ["//visibility:public"], + deps = [ + "//pkg/apis/core/validation:go_default_library", + "//pkg/features:go_default_library", + "//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library", + "//pkg/kubelet/container:go_default_library", + "//pkg/kubelet/util/format:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + "//vendor/k8s.io/client-go/tools/record:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["dns_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/kubelet/network/dns", + deps = [ + "//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library", + "//vendor/github.com/stretchr/testify/assert:go_default_library", + "//vendor/github.com/stretchr/testify/require:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + "//vendor/k8s.io/client-go/tools/record:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/kubelet/network/dns/OWNERS b/pkg/kubelet/network/dns/OWNERS new file mode 100644 index 00000000000..b5c11e8c5ad --- /dev/null +++ b/pkg/kubelet/network/dns/OWNERS @@ -0,0 +1,4 @@ +approvers: +- sig-network-approvers +reviewers: +- sig-network-reviewers diff --git a/pkg/kubelet/network/dns/dns.go b/pkg/kubelet/network/dns/dns.go new file mode 100644 index 00000000000..bcc717e01c1 --- /dev/null +++ b/pkg/kubelet/network/dns/dns.go @@ -0,0 +1,414 @@ +/* +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 dns + +import ( + "fmt" + "io" + "io/ioutil" + "net" + "os" + "path/filepath" + "strings" + + "k8s.io/api/core/v1" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/client-go/tools/record" + "k8s.io/kubernetes/pkg/apis/core/validation" + "k8s.io/kubernetes/pkg/features" + runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/util/format" + + "github.com/golang/glog" +) + +var ( + // The default dns opt strings. + defaultDNSOptions = []string{"ndots:5"} +) + +type podDNSType int + +const ( + podDNSCluster podDNSType = iota + podDNSHost + podDNSNone +) + +// Configurer is used for setting up DNS resolver configuration when launching pods. +type Configurer struct { + recorder record.EventRecorder + nodeRef *v1.ObjectReference + nodeIP net.IP + + // If non-nil, use this for container DNS server. + clusterDNS []net.IP + // If non-empty, use this for container DNS search. + ClusterDomain string + // The path to the DNS resolver configuration file used as the base to generate + // the container's DNS resolver configuration file. This can be used in + // conjunction with clusterDomain and clusterDNS. + ResolverConfig string +} + +// NewConfigurer returns a DNS configurer for launching pods. +func NewConfigurer(recorder record.EventRecorder, nodeRef *v1.ObjectReference, nodeIP net.IP, clusterDNS []net.IP, clusterDomain, resolverConfig string) *Configurer { + return &Configurer{ + recorder: recorder, + nodeRef: nodeRef, + nodeIP: nodeIP, + clusterDNS: clusterDNS, + ClusterDomain: clusterDomain, + ResolverConfig: resolverConfig, + } +} + +func omitDuplicates(strs []string) []string { + uniqueStrs := make(map[string]bool) + + var ret []string + for _, str := range strs { + if !uniqueStrs[str] { + ret = append(ret, str) + uniqueStrs[str] = true + } + } + return ret +} + +func (c *Configurer) formDNSSearchFitsLimits(composedSearch []string, pod *v1.Pod) []string { + limitsExceeded := false + + if len(composedSearch) > validation.MaxDNSSearchPaths { + composedSearch = composedSearch[:validation.MaxDNSSearchPaths] + limitsExceeded = true + } + + if resolvSearchLineStrLen := len(strings.Join(composedSearch, " ")); resolvSearchLineStrLen > validation.MaxDNSSearchListChars { + cutDomainsNum := 0 + cutDomainsLen := 0 + for i := len(composedSearch) - 1; i >= 0; i-- { + cutDomainsLen += len(composedSearch[i]) + 1 + cutDomainsNum++ + + if (resolvSearchLineStrLen - cutDomainsLen) <= validation.MaxDNSSearchListChars { + break + } + } + + composedSearch = composedSearch[:(len(composedSearch) - cutDomainsNum)] + limitsExceeded = true + } + + if limitsExceeded { + log := fmt.Sprintf("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", log) + glog.Error(log) + } + return composedSearch +} + +func (c *Configurer) formDNSNameserversFitsLimits(nameservers []string, pod *v1.Pod) []string { + if len(nameservers) > validation.MaxDNSNameservers { + nameservers = nameservers[0:validation.MaxDNSNameservers] + log := fmt.Sprintf("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", log) + glog.Error(log) + } + return nameservers +} + +func (c *Configurer) formDNSConfigFitsLimits(dnsConfig *runtimeapi.DNSConfig, pod *v1.Pod) *runtimeapi.DNSConfig { + dnsConfig.Servers = c.formDNSNameserversFitsLimits(dnsConfig.Servers, pod) + dnsConfig.Searches = c.formDNSSearchFitsLimits(dnsConfig.Searches, pod) + return dnsConfig +} + +func (c *Configurer) generateSearchesForDNSClusterFirst(hostSearch []string, pod *v1.Pod) []string { + if c.ClusterDomain == "" { + return hostSearch + } + + nsSvcDomain := fmt.Sprintf("%s.svc.%s", pod.Namespace, c.ClusterDomain) + svcDomain := fmt.Sprintf("svc.%s", c.ClusterDomain) + clusterSearch := []string{nsSvcDomain, svcDomain, c.ClusterDomain} + + return omitDuplicates(append(clusterSearch, hostSearch...)) +} + +// CheckLimitsForResolvConf checks limits in resolv.conf. +func (c *Configurer) CheckLimitsForResolvConf() { + f, err := os.Open(c.ResolverConfig) + if err != nil { + c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", err.Error()) + glog.Error("CheckLimitsForResolvConf: " + err.Error()) + return + } + defer f.Close() + + _, hostSearch, _, err := parseResolvConf(f) + if err != nil { + c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", err.Error()) + glog.Error("CheckLimitsForResolvConf: " + err.Error()) + return + } + + domainCountLimit := validation.MaxDNSSearchPaths + + if c.ClusterDomain != "" { + domainCountLimit -= 3 + } + + 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) + glog.Error("CheckLimitsForResolvConf: " + log) + return + } + + if len(strings.Join(hostSearch, " ")) > validation.MaxDNSSearchListChars { + log := fmt.Sprintf("Resolv.conf file '%s' contains search line which length is more than allowed %d chars!", c.ResolverConfig, validation.MaxDNSSearchListChars) + c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", log) + glog.Error("CheckLimitsForResolvConf: " + log) + return + } + + return +} + +// parseResolveConf reads a resolv.conf file from the given reader, and parses +// it into nameservers, searches and options, possibly returning an error. +func parseResolvConf(reader io.Reader) (nameservers []string, searches []string, options []string, err error) { + file, err := ioutil.ReadAll(reader) + if err != nil { + return nil, nil, nil, err + } + + // Lines of the form "nameserver 1.2.3.4" accumulate. + nameservers = []string{} + + // Lines of the form "search example.com" overrule - last one wins. + searches = []string{} + + // Lines of the form "option ndots:5 attempts:2" overrule - last one wins. + // Each option is recorded as an element in the array. + options = []string{} + + lines := strings.Split(string(file), "\n") + for l := range lines { + trimmed := strings.TrimSpace(lines[l]) + if strings.HasPrefix(trimmed, "#") { + continue + } + fields := strings.Fields(trimmed) + if len(fields) == 0 { + continue + } + if fields[0] == "nameserver" && len(fields) >= 2 { + nameservers = append(nameservers, fields[1]) + } + if fields[0] == "search" { + searches = fields[1:] + } + if fields[0] == "options" { + options = fields[1:] + } + } + + return nameservers, searches, options, nil +} + +func (c *Configurer) getHostDNSConfig(pod *v1.Pod) (*runtimeapi.DNSConfig, error) { + var hostDNS, hostSearch, hostOptions []string + // Get host DNS settings + if c.ResolverConfig != "" { + f, err := os.Open(c.ResolverConfig) + if err != nil { + return nil, err + } + defer f.Close() + + hostDNS, hostSearch, hostOptions, err = parseResolvConf(f) + if err != nil { + return nil, err + } + } + return &runtimeapi.DNSConfig{ + Servers: hostDNS, + Searches: hostSearch, + Options: hostOptions, + }, nil +} + +func getPodDNSType(pod *v1.Pod) (podDNSType, error) { + dnsPolicy := pod.Spec.DNSPolicy + switch dnsPolicy { + case v1.DNSNone: + if utilfeature.DefaultFeatureGate.Enabled(features.CustomPodDNS) { + return podDNSNone, nil + } + // This should not happen as kube-apiserver should have rejected + // setting dnsPolicy to DNSNone when feature gate is disabled. + return podDNSCluster, fmt.Errorf(fmt.Sprintf("invalid DNSPolicy=%v: custom pod DNS is disabled", dnsPolicy)) + case v1.DNSClusterFirstWithHostNet: + return podDNSCluster, nil + case v1.DNSClusterFirst: + if !kubecontainer.IsHostNetworkPod(pod) { + return podDNSCluster, nil + } + // Fallback to DNSDefault for pod on hostnetowrk. + fallthrough + case v1.DNSDefault: + return podDNSHost, nil + } + // This should not happen as kube-apiserver should have rejected + // invalid dnsPolicy. + return podDNSCluster, fmt.Errorf(fmt.Sprintf("invalid DNSPolicy=%v", dnsPolicy)) +} + +// Merge DNS options. If duplicated, entries given by PodDNSConfigOption will +// overwrite the existing ones. +func mergeDNSOptions(existingDNSConfigOptions []string, dnsConfigOptions []v1.PodDNSConfigOption) []string { + optionsMap := make(map[string]string) + for _, op := range existingDNSConfigOptions { + if index := strings.Index(op, ":"); index != -1 { + optionsMap[op[:index]] = op[index+1:] + } else { + optionsMap[op] = "" + } + } + for _, op := range dnsConfigOptions { + if op.Value != nil { + optionsMap[op.Name] = *op.Value + } else { + optionsMap[op.Name] = "" + } + } + // Reconvert DNS options into a string array. + options := []string{} + for opName, opValue := range optionsMap { + op := opName + if opValue != "" { + op = op + ":" + opValue + } + options = append(options, op) + } + return options +} + +// appendDNSConfig appends DNS servers, search paths and options given by +// PodDNSConfig to the existing DNS config. Duplicated entries will be merged. +// This assumes existingDNSConfig and dnsConfig are not nil. +func appendDNSConfig(existingDNSConfig *runtimeapi.DNSConfig, dnsConfig *v1.PodDNSConfig) *runtimeapi.DNSConfig { + existingDNSConfig.Servers = omitDuplicates(append(existingDNSConfig.Servers, dnsConfig.Nameservers...)) + existingDNSConfig.Searches = omitDuplicates(append(existingDNSConfig.Searches, dnsConfig.Searches...)) + existingDNSConfig.Options = mergeDNSOptions(existingDNSConfig.Options, dnsConfig.Options) + return existingDNSConfig +} + +// GetPodDNS returns DNS setttings for the pod. +func (c *Configurer) GetPodDNS(pod *v1.Pod) (*runtimeapi.DNSConfig, error) { + dnsConfig, err := c.getHostDNSConfig(pod) + if err != nil { + return nil, err + } + + dnsType, err := getPodDNSType(pod) + if err != nil { + glog.Errorf("Failed to get DNS type for pod %q: %v. Falling back to DNSClusterFirst policy.", format.Pod(pod), err) + dnsType = podDNSCluster + } + switch dnsType { + case podDNSNone: + // DNSNone should use empty DNS settings as the base. + dnsConfig = &runtimeapi.DNSConfig{} + case podDNSCluster: + if len(c.clusterDNS) != 0 { + // For a pod with DNSClusterFirst policy, the cluster DNS server is + // the only nameserver configured for the pod. The cluster DNS server + // itself will forward queries to other nameservers that is configured + // to use, in case the cluster DNS server cannot resolve the DNS query + // itself. + dnsConfig.Servers = []string{} + for _, ip := range c.clusterDNS { + dnsConfig.Servers = append(dnsConfig.Servers, ip.String()) + } + dnsConfig.Searches = c.generateSearchesForDNSClusterFirst(dnsConfig.Searches, pod) + dnsConfig.Options = defaultDNSOptions + break + } + // 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) + // Fallback to DNSDefault. + fallthrough + case podDNSHost: + // When the kubelet --resolv-conf flag is set to the empty string, use + // DNS settings that override the docker default (which is to use + // /etc/resolv.conf) and effectively disable DNS lookups. According to + // the bind documentation, the behavior of the DNS client library when + // "nameservers" are not specified is to "use the nameserver on the + // local machine". A nameserver setting of localhost is equivalent to + // this documented behavior. + if c.ResolverConfig == "" { + switch { + case c.nodeIP == nil || c.nodeIP.To4() != nil: + dnsConfig.Servers = []string{"127.0.0.1"} + case c.nodeIP.To16() != nil: + dnsConfig.Servers = []string{"::1"} + } + dnsConfig.Searches = []string{"."} + } + } + + if utilfeature.DefaultFeatureGate.Enabled(features.CustomPodDNS) && pod.Spec.DNSConfig != nil { + dnsConfig = appendDNSConfig(dnsConfig, pod.Spec.DNSConfig) + } + return c.formDNSConfigFitsLimits(dnsConfig, pod), nil +} + +// SetupDNSinContainerizedMounter replaces the nameserver in containerized-mounter's rootfs/etc/resolve.conf with kubelet.ClusterDNS +func (c *Configurer) SetupDNSinContainerizedMounter(mounterPath string) { + resolvePath := filepath.Join(strings.TrimSuffix(mounterPath, "/mounter"), "rootfs", "etc", "resolv.conf") + dnsString := "" + for _, dns := range c.clusterDNS { + dnsString = dnsString + fmt.Sprintf("nameserver %s\n", dns) + } + if c.ResolverConfig != "" { + f, err := os.Open(c.ResolverConfig) + defer f.Close() + if err != nil { + glog.Error("Could not open resolverConf file") + } else { + _, hostSearch, _, err := parseResolvConf(f) + if err != nil { + glog.Errorf("Error for parsing the reslov.conf file: %v", err) + } else { + dnsString = dnsString + "search" + for _, search := range hostSearch { + dnsString = dnsString + fmt.Sprintf(" %s", search) + } + dnsString = dnsString + "\n" + } + } + } + if err := ioutil.WriteFile(resolvePath, []byte(dnsString), 0600); err != nil { + glog.Errorf("Could not write dns nameserver in file %s, with error %v", resolvePath, err) + } +} diff --git a/pkg/kubelet/network/dns/dns_test.go b/pkg/kubelet/network/dns/dns_test.go new file mode 100644 index 00000000000..3ff551f4a6e --- /dev/null +++ b/pkg/kubelet/network/dns/dns_test.go @@ -0,0 +1,601 @@ +/* +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 dns + +import ( + "fmt" + "net" + "strings" + "testing" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/client-go/tools/record" + runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + fetchEvent = func(recorder *record.FakeRecorder) string { + select { + case event := <-recorder.Events: + return event + default: + return "" + } + } +) + +func TestParseResolvConf(t *testing.T) { + testCases := []struct { + data string + nameservers []string + searches []string + options []string + }{ + {"", []string{}, []string{}, []string{}}, + {" ", []string{}, []string{}, []string{}}, + {"\n", []string{}, []string{}, []string{}}, + {"\t\n\t", []string{}, []string{}, []string{}}, + {"#comment\n", []string{}, []string{}, []string{}}, + {" #comment\n", []string{}, []string{}, []string{}}, + {"#comment\n#comment", []string{}, []string{}, []string{}}, + {"#comment\nnameserver", []string{}, []string{}, []string{}}, + {"#comment\nnameserver\nsearch", []string{}, []string{}, []string{}}, + {"nameserver 1.2.3.4", []string{"1.2.3.4"}, []string{}, []string{}}, + {" nameserver 1.2.3.4", []string{"1.2.3.4"}, []string{}, []string{}}, + {"\tnameserver 1.2.3.4", []string{"1.2.3.4"}, []string{}, []string{}}, + {"nameserver\t1.2.3.4", []string{"1.2.3.4"}, []string{}, []string{}}, + {"nameserver \t 1.2.3.4", []string{"1.2.3.4"}, []string{}, []string{}}, + {"nameserver 1.2.3.4\nnameserver 5.6.7.8", []string{"1.2.3.4", "5.6.7.8"}, []string{}, []string{}}, + {"nameserver 1.2.3.4 #comment", []string{"1.2.3.4"}, []string{}, []string{}}, + {"search foo", []string{}, []string{"foo"}, []string{}}, + {"search foo bar", []string{}, []string{"foo", "bar"}, []string{}}, + {"search foo bar bat\n", []string{}, []string{"foo", "bar", "bat"}, []string{}}, + {"search foo\nsearch bar", []string{}, []string{"bar"}, []string{}}, + {"nameserver 1.2.3.4\nsearch foo bar", []string{"1.2.3.4"}, []string{"foo", "bar"}, []string{}}, + {"nameserver 1.2.3.4\nsearch foo\nnameserver 5.6.7.8\nsearch bar", []string{"1.2.3.4", "5.6.7.8"}, []string{"bar"}, []string{}}, + {"#comment\nnameserver 1.2.3.4\n#comment\nsearch foo\ncomment", []string{"1.2.3.4"}, []string{"foo"}, []string{}}, + {"options ndots:5 attempts:2", []string{}, []string{}, []string{"ndots:5", "attempts:2"}}, + {"options ndots:1\noptions ndots:5 attempts:3", []string{}, []string{}, []string{"ndots:5", "attempts:3"}}, + {"nameserver 1.2.3.4\nsearch foo\nnameserver 5.6.7.8\nsearch bar\noptions ndots:5 attempts:4", []string{"1.2.3.4", "5.6.7.8"}, []string{"bar"}, []string{"ndots:5", "attempts:4"}}, + } + for i, tc := range testCases { + ns, srch, opts, err := parseResolvConf(strings.NewReader(tc.data)) + require.NoError(t, err) + assert.EqualValues(t, tc.nameservers, ns, "test case [%d]: name servers", i) + assert.EqualValues(t, tc.searches, srch, "test case [%d] searches", i) + assert.EqualValues(t, tc.options, opts, "test case [%d] options", i) + } +} + +func TestFormDNSSearchFitsLimits(t *testing.T) { + recorder := record.NewFakeRecorder(20) + nodeRef := &v1.ObjectReference{ + Kind: "Node", + Name: string("testNode"), + UID: types.UID("testNode"), + Namespace: "", + } + testClusterDNSDomain := "TEST" + + configurer := NewConfigurer(recorder, nodeRef, nil, nil, testClusterDNSDomain, "") + + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + UID: "", + Name: "test_pod", + Namespace: "testNS", + Annotations: map[string]string{}, + }, + } + + testCases := []struct { + hostNames []string + resultSearch []string + events []string + }{ + { + []string{"testNS.svc.TEST", "svc.TEST", "TEST"}, + []string{"testNS.svc.TEST", "svc.TEST", "TEST"}, + []string{}, + }, + + { + []string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA", "BBB"}, + []string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA", "BBB"}, + []string{}, + }, + + { + []string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA", strings.Repeat("B", 256), "BBB"}, + []string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA"}, + []string{"Search Line limits were exceeded, some search paths have been omitted, the applied search line is: testNS.svc.TEST svc.TEST TEST AAA"}, + }, + + { + []string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA", "BBB", "CCC", "DDD"}, + []string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA", "BBB", "CCC"}, + []string{"Search Line limits were exceeded, some search paths have been omitted, the applied search line is: testNS.svc.TEST svc.TEST TEST AAA BBB CCC"}, + }, + } + + for i, tc := range testCases { + dnsSearch := configurer.formDNSSearchFitsLimits(tc.hostNames, pod) + assert.EqualValues(t, tc.resultSearch, dnsSearch, "test [%d]", i) + for _, expectedEvent := range tc.events { + expected := fmt.Sprintf("%s %s %s", v1.EventTypeWarning, "DNSConfigForming", expectedEvent) + event := fetchEvent(recorder) + assert.Equal(t, expected, event, "test [%d]", i) + } + } +} + +func TestFormDNSNameserversFitsLimits(t *testing.T) { + recorder := record.NewFakeRecorder(20) + nodeRef := &v1.ObjectReference{ + Kind: "Node", + Name: string("testNode"), + UID: types.UID("testNode"), + Namespace: "", + } + testClusterDNSDomain := "TEST" + + configurer := NewConfigurer(recorder, nodeRef, nil, nil, testClusterDNSDomain, "") + + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + UID: "", + Name: "test_pod", + Namespace: "testNS", + Annotations: map[string]string{}, + }, + } + + testCases := []struct { + desc string + nameservers []string + expectedNameserver []string + expectedEvent bool + }{ + { + desc: "valid: 1 nameserver", + nameservers: []string{"127.0.0.1"}, + expectedNameserver: []string{"127.0.0.1"}, + expectedEvent: false, + }, + { + desc: "valid: 3 nameservers", + nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"}, + expectedNameserver: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"}, + expectedEvent: false, + }, + { + desc: "invalid: 4 nameservers, trimmed to 3", + nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8", "1.2.3.4"}, + expectedNameserver: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"}, + expectedEvent: true, + }, + } + + for _, tc := range testCases { + appliedNameservers := configurer.formDNSNameserversFitsLimits(tc.nameservers, pod) + assert.EqualValues(t, tc.expectedNameserver, appliedNameservers, tc.desc) + event := fetchEvent(recorder) + if tc.expectedEvent && len(event) == 0 { + t.Errorf("%s: formDNSNameserversFitsLimits(%v) expected event, got no event.", tc.desc, tc.nameservers) + } else if !tc.expectedEvent && len(event) > 0 { + t.Errorf("%s: formDNSNameserversFitsLimits(%v) expected no event, got event: %v", tc.desc, tc.nameservers, event) + } + } +} + +func TestMergeDNSOptions(t *testing.T) { + testOptionValue := "3" + + testCases := []struct { + desc string + existingDNSConfigOptions []string + dnsConfigOptions []v1.PodDNSConfigOption + expectedOptions []string + }{ + { + desc: "Empty dnsConfigOptions", + existingDNSConfigOptions: []string{"ndots:5", "debug"}, + dnsConfigOptions: nil, + expectedOptions: []string{"ndots:5", "debug"}, + }, + { + desc: "No duplicated entries", + existingDNSConfigOptions: []string{"ndots:5", "debug"}, + dnsConfigOptions: []v1.PodDNSConfigOption{ + {Name: "single-request"}, + {Name: "attempts", Value: &testOptionValue}, + }, + expectedOptions: []string{"ndots:5", "debug", "single-request", "attempts:3"}, + }, + { + desc: "Overwrite duplicated entries", + existingDNSConfigOptions: []string{"ndots:5", "debug"}, + dnsConfigOptions: []v1.PodDNSConfigOption{ + {Name: "ndots", Value: &testOptionValue}, + {Name: "debug"}, + {Name: "single-request"}, + {Name: "attempts", Value: &testOptionValue}, + }, + expectedOptions: []string{"ndots:3", "debug", "single-request", "attempts:3"}, + }, + } + + for _, tc := range testCases { + options := mergeDNSOptions(tc.existingDNSConfigOptions, tc.dnsConfigOptions) + // Options order may be changed after conversion. + if !sets.NewString(options...).Equal(sets.NewString(tc.expectedOptions...)) { + t.Errorf("%s: mergeDNSOptions(%v, %v)=%v, want %v", tc.desc, tc.existingDNSConfigOptions, tc.dnsConfigOptions, options, tc.expectedOptions) + } + } +} + +func TestGetPodDNSType(t *testing.T) { + customDNSEnabled := utilfeature.DefaultFeatureGate.Enabled("CustomPodDNS") + defer func() { + // Restoring the old value. + if err := utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("CustomPodDNS=%v", customDNSEnabled)); err != nil { + t.Errorf("Failed to set CustomPodDNS feature gate: %v", err) + } + }() + + recorder := record.NewFakeRecorder(20) + nodeRef := &v1.ObjectReference{ + Kind: "Node", + Name: string("testNode"), + UID: types.UID("testNode"), + Namespace: "", + } + testClusterDNSDomain := "TEST" + clusterNS := "203.0.113.1" + testClusterDNS := []net.IP{net.ParseIP(clusterNS)} + + configurer := NewConfigurer(recorder, nodeRef, nil, nil, testClusterDNSDomain, "") + + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + UID: "", + Name: "test_pod", + Namespace: "testNS", + Annotations: map[string]string{}, + }, + } + + testCases := []struct { + desc string + customPodDNSFeatureGate bool + hasClusterDNS bool + hostNetwork bool + dnsPolicy v1.DNSPolicy + expectedDNSType podDNSType + expectedError bool + }{ + { + desc: "valid DNSClusterFirst without hostnetwork", + hasClusterDNS: true, + dnsPolicy: v1.DNSClusterFirst, + expectedDNSType: podDNSCluster, + }, + { + desc: "valid DNSClusterFirstWithHostNet with hostnetwork", + hasClusterDNS: true, + hostNetwork: true, + dnsPolicy: v1.DNSClusterFirstWithHostNet, + expectedDNSType: podDNSCluster, + }, + { + desc: "valid DNSClusterFirstWithHostNet without hostnetwork", + hasClusterDNS: true, + dnsPolicy: v1.DNSClusterFirstWithHostNet, + expectedDNSType: podDNSCluster, + }, + { + desc: "valid DNSDefault without hostnetwork", + dnsPolicy: v1.DNSDefault, + expectedDNSType: podDNSHost, + }, + { + desc: "valid DNSDefault with hostnetwork", + hostNetwork: true, + dnsPolicy: v1.DNSDefault, + expectedDNSType: podDNSHost, + }, + { + desc: "DNSClusterFirst with hostnetwork, fallback to DNSDefault", + hasClusterDNS: true, + hostNetwork: true, + dnsPolicy: v1.DNSClusterFirst, + expectedDNSType: podDNSHost, + }, + { + desc: "valid DNSNone with feature gate", + customPodDNSFeatureGate: true, + dnsPolicy: v1.DNSNone, + expectedDNSType: podDNSNone, + }, + { + desc: "DNSNone without feature gate, should return error", + dnsPolicy: v1.DNSNone, + expectedError: true, + }, + { + desc: "invalid DNS policy, should return error", + dnsPolicy: "invalidPolicy", + expectedError: true, + }, + } + + for _, tc := range testCases { + if err := utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("CustomPodDNS=%v", tc.customPodDNSFeatureGate)); err != nil { + t.Errorf("Failed to set CustomPodDNS feature gate: %v", err) + } + + if tc.hasClusterDNS { + configurer.clusterDNS = testClusterDNS + } else { + configurer.clusterDNS = nil + } + pod.Spec.DNSPolicy = tc.dnsPolicy + pod.Spec.HostNetwork = tc.hostNetwork + + resType, err := getPodDNSType(pod) + if tc.expectedError { + if err == nil { + t.Errorf("%s: GetPodDNSType(%v) got no error, want error", tc.desc, pod) + } + continue + } + if resType != tc.expectedDNSType { + t.Errorf("%s: GetPodDNSType(%v)=%v, want %v", tc.desc, pod, resType, tc.expectedDNSType) + } + } +} + +func TestGetPodDNS(t *testing.T) { + recorder := record.NewFakeRecorder(20) + nodeRef := &v1.ObjectReference{ + Kind: "Node", + Name: string("testNode"), + UID: types.UID("testNode"), + Namespace: "", + } + clusterNS := "203.0.113.1" + testClusterDNSDomain := "kubernetes.io" + testClusterDNS := []net.IP{net.ParseIP(clusterNS)} + + configurer := NewConfigurer(recorder, nodeRef, nil, testClusterDNS, testClusterDNSDomain, "") + + pods := newTestPods(4) + pods[0].Spec.DNSPolicy = v1.DNSClusterFirstWithHostNet + pods[1].Spec.DNSPolicy = v1.DNSClusterFirst + pods[2].Spec.DNSPolicy = v1.DNSClusterFirst + pods[2].Spec.HostNetwork = false + pods[3].Spec.DNSPolicy = v1.DNSDefault + + options := make([]struct { + DNS []string + DNSSearch []string + }, 4) + for i, pod := range pods { + var err error + dnsConfig, err := configurer.GetPodDNS(pod) + if err != nil { + t.Fatalf("failed to generate container options: %v", err) + } + options[i].DNS, options[i].DNSSearch = dnsConfig.Servers, dnsConfig.Searches + } + if len(options[0].DNS) != 1 || options[0].DNS[0] != clusterNS { + t.Errorf("expected nameserver %s, got %+v", clusterNS, options[0].DNS) + } + if len(options[0].DNSSearch) == 0 || options[0].DNSSearch[0] != ".svc."+configurer.ClusterDomain { + t.Errorf("expected search %s, got %+v", ".svc."+configurer.ClusterDomain, options[0].DNSSearch) + } + if len(options[1].DNS) != 1 || options[1].DNS[0] != "127.0.0.1" { + t.Errorf("expected nameserver 127.0.0.1, got %+v", options[1].DNS) + } + if len(options[1].DNSSearch) != 1 || options[1].DNSSearch[0] != "." { + t.Errorf("expected search \".\", got %+v", options[1].DNSSearch) + } + if len(options[2].DNS) != 1 || options[2].DNS[0] != clusterNS { + t.Errorf("expected nameserver %s, got %+v", clusterNS, options[2].DNS) + } + if len(options[2].DNSSearch) == 0 || options[2].DNSSearch[0] != ".svc."+configurer.ClusterDomain { + t.Errorf("expected search %s, got %+v", ".svc."+configurer.ClusterDomain, options[2].DNSSearch) + } + if len(options[3].DNS) != 1 || options[3].DNS[0] != "127.0.0.1" { + t.Errorf("expected nameserver 127.0.0.1, got %+v", options[3].DNS) + } + if len(options[3].DNSSearch) != 1 || options[3].DNSSearch[0] != "." { + t.Errorf("expected search \".\", got %+v", options[3].DNSSearch) + } + + testResolverConfig := "/etc/resolv.conf" + configurer = NewConfigurer(recorder, nodeRef, nil, testClusterDNS, testClusterDNSDomain, testResolverConfig) + for i, pod := range pods { + var err error + dnsConfig, err := configurer.GetPodDNS(pod) + if err != nil { + t.Fatalf("failed to generate container options: %v", err) + } + options[i].DNS, options[i].DNSSearch = dnsConfig.Servers, dnsConfig.Searches + } + t.Logf("nameservers %+v", options[1].DNS) + if len(options[0].DNS) != 1 { + t.Errorf("expected cluster nameserver only, got %+v", options[0].DNS) + } else if options[0].DNS[0] != clusterNS { + t.Errorf("expected nameserver %s, got %v", clusterNS, options[0].DNS[0]) + } + expLength := len(options[1].DNSSearch) + 3 + if expLength > 6 { + expLength = 6 + } + if len(options[0].DNSSearch) != expLength { + t.Errorf("expected prepend of cluster domain, got %+v", options[0].DNSSearch) + } else if options[0].DNSSearch[0] != ".svc."+configurer.ClusterDomain { + t.Errorf("expected domain %s, got %s", ".svc."+configurer.ClusterDomain, options[0].DNSSearch) + } + if len(options[2].DNS) != 1 { + t.Errorf("expected cluster nameserver only, got %+v", options[2].DNS) + } else if options[2].DNS[0] != clusterNS { + t.Errorf("expected nameserver %s, got %v", clusterNS, options[2].DNS[0]) + } + if len(options[2].DNSSearch) != expLength { + t.Errorf("expected prepend of cluster domain, got %+v", options[2].DNSSearch) + } else if options[2].DNSSearch[0] != ".svc."+configurer.ClusterDomain { + t.Errorf("expected domain %s, got %s", ".svc."+configurer.ClusterDomain, options[0].DNSSearch) + } +} + +func TestGetPodDNSCustom(t *testing.T) { + customDNSEnabled := utilfeature.DefaultFeatureGate.Enabled("CustomPodDNS") + defer func() { + // Restoring the old value. + if err := utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("CustomPodDNS=%v", customDNSEnabled)); err != nil { + t.Errorf("Failed to set CustomPodDNS feature gate: %v", err) + } + }() + + recorder := record.NewFakeRecorder(20) + nodeRef := &v1.ObjectReference{ + Kind: "Node", + Name: string("testNode"), + UID: types.UID("testNode"), + Namespace: "", + } + clusterNS := "203.0.113.1" + testClusterDNSDomain := "kubernetes.io" + testClusterDNS := []net.IP{net.ParseIP(clusterNS)} + testOptionValue := "3" + + configurer := NewConfigurer(recorder, nodeRef, nil, testClusterDNS, testClusterDNSDomain, "") + + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + UID: "", + Name: "test_pod", + Namespace: "testNS", + Annotations: map[string]string{}, + }, + Spec: v1.PodSpec{ + DNSPolicy: v1.DNSClusterFirst, + }, + } + clusterFirstDNSConfig, err := configurer.GetPodDNS(pod) + if err != nil { + t.Fatalf("Preparing clusterFirstDNSConfig: GetPodDNS(%v), unexpected error: %v", pod, err) + } + + // Overwrite DNSPolicy for testing. + pod.Spec.DNSPolicy = v1.DNSNone + + testCases := []struct { + desc string + customPodDNSFeatureGate bool + dnsConfig *v1.PodDNSConfig + expectedDNSConfig *runtimeapi.DNSConfig + }{ + { + desc: "feature gate is disabled, DNSNone should fallback to DNSClusterFirst", + expectedDNSConfig: clusterFirstDNSConfig, + }, + { + desc: "feature gate is enabled, DNSNone without DNSConfig should have empty DNS settings", + customPodDNSFeatureGate: true, + expectedDNSConfig: &runtimeapi.DNSConfig{}, + }, + { + desc: "feature gate is enabled, DNSNone with DNSConfig should have a merged DNS settings", + customPodDNSFeatureGate: true, + dnsConfig: &v1.PodDNSConfig{ + Nameservers: []string{"10.0.0.10"}, + Searches: []string{"my.domain", "second.domain"}, + Options: []v1.PodDNSConfigOption{ + {Name: "ndots", Value: &testOptionValue}, + {Name: "debug"}, + }, + }, + expectedDNSConfig: &runtimeapi.DNSConfig{ + Servers: []string{"10.0.0.10"}, + Searches: []string{"my.domain", "second.domain"}, + Options: []string{"ndots:3", "debug"}, + }, + }, + } + + for _, tc := range testCases { + if err := utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("CustomPodDNS=%v", tc.customPodDNSFeatureGate)); err != nil { + t.Errorf("Failed to set CustomPodDNS feature gate: %v", err) + } + + pod.Spec.DNSConfig = tc.dnsConfig + + resDNSConfig, err := configurer.GetPodDNS(pod) + if err != nil { + t.Errorf("%s: GetPodDNS(%v), unexpected error: %v", tc.desc, pod, err) + } + if !dnsConfigsAreEqual(resDNSConfig, tc.expectedDNSConfig) { + t.Errorf("%s: GetPodDNS(%v)=%v, want %v", tc.desc, pod, resDNSConfig, tc.expectedDNSConfig) + } + } +} + +func dnsConfigsAreEqual(resConfig, expectedConfig *runtimeapi.DNSConfig) bool { + if len(resConfig.Servers) != len(expectedConfig.Servers) || + len(resConfig.Searches) != len(expectedConfig.Searches) || + len(resConfig.Options) != len(expectedConfig.Options) { + return false + } + for i, server := range resConfig.Servers { + if expectedConfig.Servers[i] != server { + return false + } + } + for i, search := range resConfig.Searches { + if expectedConfig.Searches[i] != search { + return false + } + } + // Options order may be changed after conversion. + return sets.NewString(resConfig.Options...).Equal(sets.NewString(expectedConfig.Options...)) +} + +func newTestPods(count int) []*v1.Pod { + pods := make([]*v1.Pod, count) + for i := 0; i < count; i++ { + pods[i] = &v1.Pod{ + Spec: v1.PodSpec{ + HostNetwork: true, + }, + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID(10000 + i), + Name: fmt.Sprintf("pod%d", i), + }, + } + } + return pods +} diff --git a/pkg/kubelet/network/hairpin/BUILD b/pkg/kubelet/network/hairpin/BUILD index d10f32a5c86..8ef0197c84b 100644 --- a/pkg/kubelet/network/hairpin/BUILD +++ b/pkg/kubelet/network/hairpin/BUILD @@ -19,8 +19,8 @@ go_library( go_test( name = "go_default_test", srcs = ["hairpin_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/network/hairpin", - library = ":go_default_library", deps = [ "//vendor/k8s.io/utils/exec:go_default_library", "//vendor/k8s.io/utils/exec/testing:go_default_library", diff --git a/pkg/kubelet/network/hostport/BUILD b/pkg/kubelet/network/hostport/BUILD index ed448dc8be5..24864a3a538 100644 --- a/pkg/kubelet/network/hostport/BUILD +++ b/pkg/kubelet/network/hostport/BUILD @@ -21,18 +21,20 @@ go_library( "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", ], ) go_test( name = "go_default_test", srcs = [ + "fake_iptables_test.go", "hostport_manager_test.go", "hostport_syncer_test.go", "hostport_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/network/hostport", - library = ":go_default_library", deps = [ "//pkg/util/iptables:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", diff --git a/pkg/kubelet/network/hostport/fake_iptables.go b/pkg/kubelet/network/hostport/fake_iptables.go index 42f7c681164..ab28a1d8747 100644 --- a/pkg/kubelet/network/hostport/fake_iptables.go +++ b/pkg/kubelet/network/hostport/fake_iptables.go @@ -22,6 +22,7 @@ import ( "net" "strings" + "k8s.io/apimachinery/pkg/util/sets" utiliptables "k8s.io/kubernetes/pkg/util/iptables" ) @@ -36,12 +37,18 @@ type fakeTable struct { } type fakeIPTables struct { - tables map[string]*fakeTable + tables map[string]*fakeTable + builtinChains map[string]sets.String } func NewFakeIPTables() *fakeIPTables { return &fakeIPTables{ tables: make(map[string]*fakeTable, 0), + builtinChains: map[string]sets.String{ + string(utiliptables.TableFilter): sets.NewString("INPUT", "FORWARD", "OUTPUT"), + string(utiliptables.TableNAT): sets.NewString("PREROUTING", "INPUT", "OUTPUT", "POSTROUTING"), + string(utiliptables.TableMangle): sets.NewString("PREROUTING", "INPUT", "FORWARD", "OUTPUT", "POSTROUTING"), + }, } } @@ -246,6 +253,7 @@ func (f *fakeIPTables) SaveInto(tableName utiliptables.Table, buffer *bytes.Buff } func (f *fakeIPTables) restore(restoreTableName utiliptables.Table, data []byte, flush utiliptables.FlushFlag) error { + allLines := string(data) buf := bytes.NewBuffer(data) var tableName utiliptables.Table for { @@ -274,6 +282,13 @@ func (f *fakeIPTables) restore(restoreTableName utiliptables.Table, data []byte, } } _, _ = f.ensureChain(tableName, chainName) + // The --noflush option for iptables-restore doesn't work for user-defined chains, only builtin chains. + // We should flush user-defined chains if the chain is not to be deleted + if !f.isBuiltinChain(tableName, chainName) && !strings.Contains(allLines, "-X "+string(chainName)) { + if err := f.FlushChain(tableName, chainName); err != nil { + return err + } + } } else if strings.HasPrefix(line, "-A") { parts := strings.Split(line, " ") if len(parts) < 3 { @@ -329,3 +344,10 @@ func (f *fakeIPTables) AddReloadFunc(reloadFunc func()) { func (f *fakeIPTables) Destroy() { } + +func (f *fakeIPTables) isBuiltinChain(tableName utiliptables.Table, chainName utiliptables.Chain) bool { + if builtinChains, ok := f.builtinChains[string(tableName)]; ok && builtinChains.Has(string(chainName)) { + return true + } + return false +} diff --git a/pkg/kubelet/network/hostport/fake_iptables_test.go b/pkg/kubelet/network/hostport/fake_iptables_test.go new file mode 100644 index 00000000000..bda4c024736 --- /dev/null +++ b/pkg/kubelet/network/hostport/fake_iptables_test.go @@ -0,0 +1,56 @@ +/* +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 hostport + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + utiliptables "k8s.io/kubernetes/pkg/util/iptables" +) + +func TestRestoreFlushRules(t *testing.T) { + iptables := NewFakeIPTables() + rules := [][]string{ + {"-A", "KUBE-HOSTPORTS", "-m comment --comment \"pod3_ns1 hostport 8443\" -m tcp -p tcp --dport 8443 -j KUBE-HP-5N7UH5JAXCVP5UJR"}, + {"-A", "POSTROUTING", "-m comment --comment \"SNAT for localhost access to hostports\" -o cbr0 -s 127.0.0.0/8 -j MASQUERADE"}, + } + natRules := bytes.NewBuffer(nil) + writeLine(natRules, "*nat") + for _, rule := range rules { + _, err := iptables.EnsureChain(utiliptables.TableNAT, utiliptables.Chain(rule[1])) + assert.NoError(t, err) + _, err = iptables.ensureRule(utiliptables.RulePosition(rule[0]), utiliptables.TableNAT, utiliptables.Chain(rule[1]), rule[2]) + assert.NoError(t, err) + + writeLine(natRules, utiliptables.MakeChainLine(utiliptables.Chain(rule[1]))) + } + writeLine(natRules, "COMMIT") + assert.NoError(t, iptables.Restore(utiliptables.TableNAT, natRules.Bytes(), utiliptables.NoFlushTables, utiliptables.RestoreCounters)) + natTable, ok := iptables.tables[string(utiliptables.TableNAT)] + assert.True(t, ok) + // check KUBE-HOSTPORTS chain, should have been cleaned up + hostportChain, ok := natTable.chains["KUBE-HOSTPORTS"] + assert.True(t, ok) + assert.Equal(t, 0, len(hostportChain.rules)) + + // check builtin chains, should not been cleaned up + postroutingChain, ok := natTable.chains["POSTROUTING"] + assert.True(t, ok, string(postroutingChain.name)) + assert.Equal(t, 1, len(postroutingChain.rules)) +} diff --git a/pkg/kubelet/network/hostport/hostport_manager.go b/pkg/kubelet/network/hostport/hostport_manager.go index ea53c9c8499..a31b4da0842 100644 --- a/pkg/kubelet/network/hostport/hostport_manager.go +++ b/pkg/kubelet/network/hostport/hostport_manager.go @@ -21,6 +21,7 @@ import ( "crypto/sha256" "encoding/base32" "fmt" + "strconv" "strings" "sync" @@ -177,6 +178,8 @@ func (hm *hostportManager) Remove(id string, podPortMapping *PodPortMapping) (er chainsToRemove := []utiliptables.Chain{} for _, pm := range hostportMappings { chainsToRemove = append(chainsToRemove, getHostportChain(id, pm)) + // TODO remove this after release 1.9, please refer https://github.com/kubernetes/kubernetes/pull/55153 + chainsToRemove = append(chainsToRemove, getBuggyHostportChain(id, pm)) } // remove rules that consists of target chains @@ -247,6 +250,16 @@ func (hm *hostportManager) closeHostports(hostportMappings []*PortMapping) error // WARNING: Please do not change this function. Otherwise, HostportManager may not be able to // identify existing iptables chains. func getHostportChain(id string, pm *PortMapping) utiliptables.Chain { + hash := sha256.Sum256([]byte(id + strconv.Itoa(int(pm.HostPort)) + string(pm.Protocol))) + encoded := base32.StdEncoding.EncodeToString(hash[:]) + return utiliptables.Chain(kubeHostportChainPrefix + encoded[:16]) +} + +// This bugy func does bad conversion on HostPort from int32 to string. +// It may generates same chain names for different ports of the same pod, e.g. port 57119/55429/56833. +// `getHostportChain` fixed this bug. In order to cleanup the legacy chains/rules, it is temporarily left. +// TODO remove this after release 1.9, please refer https://github.com/kubernetes/kubernetes/pull/55153 +func getBuggyHostportChain(id string, pm *PortMapping) utiliptables.Chain { hash := sha256.Sum256([]byte(id + string(pm.HostPort) + string(pm.Protocol))) encoded := base32.StdEncoding.EncodeToString(hash[:]) return utiliptables.Chain(kubeHostportChainPrefix + encoded[:16]) diff --git a/pkg/kubelet/network/hostport/hostport_manager_test.go b/pkg/kubelet/network/hostport/hostport_manager_test.go index 61a2c65db91..1537d274940 100644 --- a/pkg/kubelet/network/hostport/hostport_manager_test.go +++ b/pkg/kubelet/network/hostport/hostport_manager_test.go @@ -19,12 +19,12 @@ package hostport import ( "bytes" "net" + "strings" "testing" "github.com/stretchr/testify/assert" "k8s.io/api/core/v1" utiliptables "k8s.io/kubernetes/pkg/util/iptables" - "strings" ) func NewFakeHostportManager() HostPortManager { @@ -144,21 +144,21 @@ func TestHostportManager(t *testing.T) { `:OUTPUT - [0:0]`: true, `:PREROUTING - [0:0]`: true, `:POSTROUTING - [0:0]`: true, - `:KUBE-HP-4YVONL46AKYWSKS3 - [0:0]`: true, - `:KUBE-HP-7THKRFSEH4GIIXK7 - [0:0]`: true, - `:KUBE-HP-5N7UH5JAXCVP5UJR - [0:0]`: true, - "-A KUBE-HOSTPORTS -m comment --comment \"pod3_ns1 hostport 8443\" -m tcp -p tcp --dport 8443 -j KUBE-HP-5N7UH5JAXCVP5UJR": true, - "-A KUBE-HOSTPORTS -m comment --comment \"pod1_ns1 hostport 8081\" -m udp -p udp --dport 8081 -j KUBE-HP-7THKRFSEH4GIIXK7": true, - "-A KUBE-HOSTPORTS -m comment --comment \"pod1_ns1 hostport 8080\" -m tcp -p tcp --dport 8080 -j KUBE-HP-4YVONL46AKYWSKS3": true, + `:KUBE-HP-IJHALPHTORMHHPPK - [0:0]`: true, + `:KUBE-HP-63UPIDJXVRSZGSUZ - [0:0]`: true, + `:KUBE-HP-WFBOALXEP42XEMJK - [0:0]`: true, + "-A KUBE-HOSTPORTS -m comment --comment \"pod3_ns1 hostport 8443\" -m tcp -p tcp --dport 8443 -j KUBE-HP-WFBOALXEP42XEMJK": true, + "-A KUBE-HOSTPORTS -m comment --comment \"pod1_ns1 hostport 8081\" -m udp -p udp --dport 8081 -j KUBE-HP-63UPIDJXVRSZGSUZ": true, + "-A KUBE-HOSTPORTS -m comment --comment \"pod1_ns1 hostport 8080\" -m tcp -p tcp --dport 8080 -j KUBE-HP-IJHALPHTORMHHPPK": true, "-A OUTPUT -m comment --comment \"kube hostport portals\" -m addrtype --dst-type LOCAL -j KUBE-HOSTPORTS": true, "-A PREROUTING -m comment --comment \"kube hostport portals\" -m addrtype --dst-type LOCAL -j KUBE-HOSTPORTS": true, "-A POSTROUTING -m comment --comment \"SNAT for localhost access to hostports\" -o cbr0 -s 127.0.0.0/8 -j MASQUERADE": true, - "-A KUBE-HP-4YVONL46AKYWSKS3 -m comment --comment \"pod1_ns1 hostport 8080\" -s 10.1.1.2/32 -j KUBE-MARK-MASQ": true, - "-A KUBE-HP-4YVONL46AKYWSKS3 -m comment --comment \"pod1_ns1 hostport 8080\" -m tcp -p tcp -j DNAT --to-destination 10.1.1.2:80": true, - "-A KUBE-HP-7THKRFSEH4GIIXK7 -m comment --comment \"pod1_ns1 hostport 8081\" -s 10.1.1.2/32 -j KUBE-MARK-MASQ": true, - "-A KUBE-HP-7THKRFSEH4GIIXK7 -m comment --comment \"pod1_ns1 hostport 8081\" -m udp -p udp -j DNAT --to-destination 10.1.1.2:81": true, - "-A KUBE-HP-5N7UH5JAXCVP5UJR -m comment --comment \"pod3_ns1 hostport 8443\" -s 10.1.1.4/32 -j KUBE-MARK-MASQ": true, - "-A KUBE-HP-5N7UH5JAXCVP5UJR -m comment --comment \"pod3_ns1 hostport 8443\" -m tcp -p tcp -j DNAT --to-destination 10.1.1.4:443": true, + "-A KUBE-HP-IJHALPHTORMHHPPK -m comment --comment \"pod1_ns1 hostport 8080\" -s 10.1.1.2/32 -j KUBE-MARK-MASQ": true, + "-A KUBE-HP-IJHALPHTORMHHPPK -m comment --comment \"pod1_ns1 hostport 8080\" -m tcp -p tcp -j DNAT --to-destination 10.1.1.2:80": true, + "-A KUBE-HP-63UPIDJXVRSZGSUZ -m comment --comment \"pod1_ns1 hostport 8081\" -s 10.1.1.2/32 -j KUBE-MARK-MASQ": true, + "-A KUBE-HP-63UPIDJXVRSZGSUZ -m comment --comment \"pod1_ns1 hostport 8081\" -m udp -p udp -j DNAT --to-destination 10.1.1.2:81": true, + "-A KUBE-HP-WFBOALXEP42XEMJK -m comment --comment \"pod3_ns1 hostport 8443\" -s 10.1.1.4/32 -j KUBE-MARK-MASQ": true, + "-A KUBE-HP-WFBOALXEP42XEMJK -m comment --comment \"pod3_ns1 hostport 8443\" -m tcp -p tcp -j DNAT --to-destination 10.1.1.4:443": true, `COMMIT`: true, } for _, line := range lines { @@ -198,3 +198,92 @@ func TestHostportManager(t *testing.T) { assert.EqualValues(t, true, port.closed) } } + +func TestGetHostportChain(t *testing.T) { + m := make(map[string]int) + chain := getHostportChain("testrdma-2", &PortMapping{HostPort: 57119, Protocol: "TCP", ContainerPort: 57119}) + m[string(chain)] = 1 + chain = getHostportChain("testrdma-2", &PortMapping{HostPort: 55429, Protocol: "TCP", ContainerPort: 55429}) + m[string(chain)] = 1 + chain = getHostportChain("testrdma-2", &PortMapping{HostPort: 56833, Protocol: "TCP", ContainerPort: 56833}) + m[string(chain)] = 1 + if len(m) != 3 { + t.Fatal(m) + } +} + +func TestHostPortManagerRemoveLegacyRules(t *testing.T) { + iptables := NewFakeIPTables() + legacyRules := [][]string{ + {"-A", "KUBE-HOSTPORTS", "-m comment --comment \"pod3_ns1 hostport 8443\" -m tcp -p tcp --dport 8443 -j KUBE-HP-5N7UH5JAXCVP5UJR"}, + {"-A", "KUBE-HOSTPORTS", "-m comment --comment \"pod1_ns1 hostport 8081\" -m udp -p udp --dport 8081 -j KUBE-HP-7THKRFSEH4GIIXK7"}, + {"-A", "KUBE-HOSTPORTS", "-m comment --comment \"pod1_ns1 hostport 8080\" -m tcp -p tcp --dport 8080 -j KUBE-HP-4YVONL46AKYWSKS3"}, + {"-A", "OUTPUT", "-m comment --comment \"kube hostport portals\" -m addrtype --dst-type LOCAL -j KUBE-HOSTPORTS"}, + {"-A", "PREROUTING", "-m comment --comment \"kube hostport portals\" -m addrtype --dst-type LOCAL -j KUBE-HOSTPORTS"}, + {"-A", "POSTROUTING", "-m comment --comment \"SNAT for localhost access to hostports\" -o cbr0 -s 127.0.0.0/8 -j MASQUERADE"}, + {"-A", "KUBE-HP-4YVONL46AKYWSKS3", "-m comment --comment \"pod1_ns1 hostport 8080\" -s 10.1.1.2/32 -j KUBE-MARK-MASQ"}, + {"-A", "KUBE-HP-4YVONL46AKYWSKS3", "-m comment --comment \"pod1_ns1 hostport 8080\" -m tcp -p tcp -j DNAT --to-destination 10.1.1.2:80"}, + {"-A", "KUBE-HP-7THKRFSEH4GIIXK7", "-m comment --comment \"pod1_ns1 hostport 8081\" -s 10.1.1.2/32 -j KUBE-MARK-MASQ"}, + {"-A", "KUBE-HP-7THKRFSEH4GIIXK7", "-m comment --comment \"pod1_ns1 hostport 8081\" -m udp -p udp -j DNAT --to-destination 10.1.1.2:81"}, + {"-A", "KUBE-HP-5N7UH5JAXCVP5UJR", "-m comment --comment \"pod3_ns1 hostport 8443\" -s 10.1.1.4/32 -j KUBE-MARK-MASQ"}, + {"-A", "KUBE-HP-5N7UH5JAXCVP5UJR", "-m comment --comment \"pod3_ns1 hostport 8443\" -m tcp -p tcp -j DNAT --to-destination 10.1.1.4:443"}, + } + for _, rule := range legacyRules { + _, err := iptables.EnsureChain(utiliptables.TableNAT, utiliptables.Chain(rule[1])) + assert.NoError(t, err) + _, err = iptables.ensureRule(utiliptables.RulePosition(rule[0]), utiliptables.TableNAT, utiliptables.Chain(rule[1]), rule[2]) + assert.NoError(t, err) + } + portOpener := NewFakeSocketManager() + manager := &hostportManager{ + hostPortMap: make(map[hostport]closeable), + iptables: iptables, + portOpener: portOpener.openFakeSocket, + } + err := manager.Remove("id", &PodPortMapping{ + Name: "pod1", + Namespace: "ns1", + IP: net.ParseIP("10.1.1.2"), + HostNetwork: false, + PortMappings: []*PortMapping{ + { + HostPort: 8080, + ContainerPort: 80, + Protocol: v1.ProtocolTCP, + }, + { + HostPort: 8081, + ContainerPort: 81, + Protocol: v1.ProtocolUDP, + }, + }, + }) + assert.NoError(t, err) + + err = manager.Remove("id", &PodPortMapping{ + Name: "pod3", + Namespace: "ns1", + IP: net.ParseIP("10.1.1.4"), + HostNetwork: false, + PortMappings: []*PortMapping{ + { + HostPort: 8443, + ContainerPort: 443, + Protocol: v1.ProtocolTCP, + }, + }, + }) + assert.NoError(t, err) + + natTable, ok := iptables.tables[string(utiliptables.TableNAT)] + assert.True(t, ok) + // check KUBE-HOSTPORTS chain should be cleaned up + hostportChain, ok := natTable.chains["KUBE-HOSTPORTS"] + assert.True(t, ok, string(hostportChain.name)) + assert.Equal(t, 0, len(hostportChain.rules), "%v", hostportChain.rules) + // check KUBE-HP-* chains should be deleted + for _, name := range []string{"KUBE-HP-4YVONL46AKYWSKS3", "KUBE-HP-7THKRFSEH4GIIXK7", "KUBE-HP-5N7UH5JAXCVP5UJR"} { + _, ok := natTable.chains[name] + assert.False(t, ok) + } +} diff --git a/pkg/kubelet/network/hostport/hostport_syncer.go b/pkg/kubelet/network/hostport/hostport_syncer.go index 0086b74561d..3d7bfd6e4dc 100644 --- a/pkg/kubelet/network/hostport/hostport_syncer.go +++ b/pkg/kubelet/network/hostport/hostport_syncer.go @@ -21,6 +21,7 @@ import ( "crypto/sha256" "encoding/base32" "fmt" + "strconv" "strings" "time" @@ -142,7 +143,7 @@ func writeLine(buf *bytes.Buffer, words ...string) { // this because IPTables Chain Names must be <= 28 chars long, and the longer // they are the harder they are to read. func hostportChainName(pm *PortMapping, podFullName string) utiliptables.Chain { - hash := sha256.Sum256([]byte(string(pm.HostPort) + string(pm.Protocol) + podFullName)) + hash := sha256.Sum256([]byte(strconv.Itoa(int(pm.HostPort)) + string(pm.Protocol) + podFullName)) encoded := base32.StdEncoding.EncodeToString(hash[:]) return utiliptables.Chain(kubeHostportChainPrefix + encoded[:16]) } diff --git a/pkg/kubelet/network/hostport/hostport_syncer_test.go b/pkg/kubelet/network/hostport/hostport_syncer_test.go index b5ffed47417..5cd3349ca82 100644 --- a/pkg/kubelet/network/hostport/hostport_syncer_test.go +++ b/pkg/kubelet/network/hostport/hostport_syncer_test.go @@ -22,6 +22,7 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" "k8s.io/api/core/v1" utiliptables "k8s.io/kubernetes/pkg/util/iptables" ) @@ -223,3 +224,89 @@ func matchRule(chain *fakeChain, match string) bool { } return false } + +func TestHostportChainName(t *testing.T) { + m := make(map[string]int) + chain := hostportChainName(&PortMapping{HostPort: 57119, Protocol: "TCP", ContainerPort: 57119}, "testrdma-2") + m[string(chain)] = 1 + chain = hostportChainName(&PortMapping{HostPort: 55429, Protocol: "TCP", ContainerPort: 55429}, "testrdma-2") + m[string(chain)] = 1 + chain = hostportChainName(&PortMapping{HostPort: 56833, Protocol: "TCP", ContainerPort: 56833}, "testrdma-2") + m[string(chain)] = 1 + if len(m) != 3 { + t.Fatal(m) + } +} + +func TestHostPortSyncerRemoveLegacyRules(t *testing.T) { + iptables := NewFakeIPTables() + legacyRules := [][]string{ + {"-A", "KUBE-HOSTPORTS", "-m comment --comment \"pod3_ns1 hostport 8443\" -m tcp -p tcp --dport 8443 -j KUBE-HP-5N7UH5JAXCVP5UJR"}, + {"-A", "KUBE-HOSTPORTS", "-m comment --comment \"pod1_ns1 hostport 8081\" -m udp -p udp --dport 8081 -j KUBE-HP-7THKRFSEH4GIIXK7"}, + {"-A", "KUBE-HOSTPORTS", "-m comment --comment \"pod1_ns1 hostport 8080\" -m tcp -p tcp --dport 8080 -j KUBE-HP-4YVONL46AKYWSKS3"}, + {"-A", "OUTPUT", "-m comment --comment \"kube hostport portals\" -m addrtype --dst-type LOCAL -j KUBE-HOSTPORTS"}, + {"-A", "PREROUTING", "-m comment --comment \"kube hostport portals\" -m addrtype --dst-type LOCAL -j KUBE-HOSTPORTS"}, + {"-A", "POSTROUTING", "-m comment --comment \"SNAT for localhost access to hostports\" -o cbr0 -s 127.0.0.0/8 -j MASQUERADE"}, + {"-A", "KUBE-HP-4YVONL46AKYWSKS3", "-m comment --comment \"pod1_ns1 hostport 8080\" -s 10.1.1.2/32 -j KUBE-MARK-MASQ"}, + {"-A", "KUBE-HP-4YVONL46AKYWSKS3", "-m comment --comment \"pod1_ns1 hostport 8080\" -m tcp -p tcp -j DNAT --to-destination 10.1.1.2:80"}, + {"-A", "KUBE-HP-7THKRFSEH4GIIXK7", "-m comment --comment \"pod1_ns1 hostport 8081\" -s 10.1.1.2/32 -j KUBE-MARK-MASQ"}, + {"-A", "KUBE-HP-7THKRFSEH4GIIXK7", "-m comment --comment \"pod1_ns1 hostport 8081\" -m udp -p udp -j DNAT --to-destination 10.1.1.2:81"}, + {"-A", "KUBE-HP-5N7UH5JAXCVP5UJR", "-m comment --comment \"pod3_ns1 hostport 8443\" -s 10.1.1.4/32 -j KUBE-MARK-MASQ"}, + {"-A", "KUBE-HP-5N7UH5JAXCVP5UJR", "-m comment --comment \"pod3_ns1 hostport 8443\" -m tcp -p tcp -j DNAT --to-destination 10.1.1.4:443"}, + } + for _, rule := range legacyRules { + _, err := iptables.EnsureChain(utiliptables.TableNAT, utiliptables.Chain(rule[1])) + assert.NoError(t, err) + _, err = iptables.ensureRule(utiliptables.RulePosition(rule[0]), utiliptables.TableNAT, utiliptables.Chain(rule[1]), rule[2]) + assert.NoError(t, err) + } + portOpener := NewFakeSocketManager() + h := &hostportSyncer{ + hostPortMap: make(map[hostport]closeable), + iptables: iptables, + portOpener: portOpener.openFakeSocket, + } + // check preserve pod3's rules and remove pod1's rules + pod3PortMapping := &PodPortMapping{ + Name: "pod3", + Namespace: "ns1", + IP: net.ParseIP("10.1.1.4"), + HostNetwork: false, + PortMappings: []*PortMapping{ + { + HostPort: 8443, + ContainerPort: 443, + Protocol: v1.ProtocolTCP, + }, + }, + } + h.SyncHostports("cbr0", []*PodPortMapping{pod3PortMapping}) + + newChainName := string(hostportChainName(pod3PortMapping.PortMappings[0], getPodFullName(pod3PortMapping))) + expectRules := [][]string{ + {"KUBE-HOSTPORTS", "-m comment --comment \"pod3_ns1 hostport 8443\" -m tcp -p tcp --dport 8443 -j " + newChainName}, + {newChainName, "-m comment --comment \"pod3_ns1 hostport 8443\" -s 10.1.1.4/32 -j KUBE-MARK-MASQ"}, + {newChainName, "-m comment --comment \"pod3_ns1 hostport 8443\" -m tcp -p tcp -j DNAT --to-destination 10.1.1.4:443"}, + } + + natTable, ok := iptables.tables[string(utiliptables.TableNAT)] + assert.True(t, ok) + // check pod1's rules in KUBE-HOSTPORTS chain should be cleaned up + hostportChain, ok := natTable.chains["KUBE-HOSTPORTS"] + assert.True(t, ok, string(hostportChain.name)) + assert.Equal(t, 1, len(hostportChain.rules), "%v", hostportChain.rules) + + // check pod3's rules left + assert.Equal(t, expectRules[0][1], hostportChain.rules[0]) + chain, ok := natTable.chains[newChainName] + assert.True(t, ok) + assert.Equal(t, 2, len(chain.rules)) + assert.Equal(t, expectRules[1][1], chain.rules[0]) + assert.Equal(t, expectRules[2][1], chain.rules[1]) + + // check legacy KUBE-HP-* chains should be deleted + for _, name := range []string{"KUBE-HP-4YVONL46AKYWSKS3", "KUBE-HP-7THKRFSEH4GIIXK7", "KUBE-HP-5N7UH5JAXCVP5UJR"} { + _, ok := natTable.chains[name] + assert.False(t, ok) + } +} diff --git a/pkg/kubelet/network/kubenet/BUILD b/pkg/kubelet/network/kubenet/BUILD index 13c4bce3db3..7b451dae9b0 100644 --- a/pkg/kubelet/network/kubenet/BUILD +++ b/pkg/kubelet/network/kubenet/BUILD @@ -10,20 +10,68 @@ go_library( name = "go_default_library", srcs = [ "kubenet.go", - "kubenet_unsupported.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "kubenet_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "kubenet_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "kubenet_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "kubenet_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ "kubenet_linux.go", ], + "@io_bazel_rules_go//go/platform:nacl": [ + "kubenet_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "kubenet_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "kubenet_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "kubenet_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "kubenet_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "kubenet_unsupported.go", + ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/pkg/kubelet/network/kubenet", - deps = [ - "//pkg/kubelet/apis/kubeletconfig:go_default_library", - "//pkg/kubelet/container:go_default_library", - "//pkg/kubelet/network:go_default_library", - ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + deps = select({ + "@io_bazel_rules_go//go/platform:android": [ + "//pkg/kubelet/apis/kubeletconfig:go_default_library", + "//pkg/kubelet/container:go_default_library", + "//pkg/kubelet/network:go_default_library", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "//pkg/kubelet/apis/kubeletconfig:go_default_library", + "//pkg/kubelet/container:go_default_library", + "//pkg/kubelet/network:go_default_library", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "//pkg/kubelet/apis/kubeletconfig:go_default_library", + "//pkg/kubelet/container:go_default_library", + "//pkg/kubelet/network:go_default_library", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "//pkg/kubelet/apis/kubeletconfig:go_default_library", + "//pkg/kubelet/container:go_default_library", + "//pkg/kubelet/network:go_default_library", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "//pkg/kubelet/apis/kubeletconfig:go_default_library", + "//pkg/kubelet/container:go_default_library", + "//pkg/kubelet/network:go_default_library", "//pkg/kubelet/network/hostport:go_default_library", "//pkg/util/bandwidth:go_default_library", "//pkg/util/dbus:go_default_library", @@ -42,6 +90,36 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library", ], + "@io_bazel_rules_go//go/platform:nacl": [ + "//pkg/kubelet/apis/kubeletconfig:go_default_library", + "//pkg/kubelet/container:go_default_library", + "//pkg/kubelet/network:go_default_library", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "//pkg/kubelet/apis/kubeletconfig:go_default_library", + "//pkg/kubelet/container:go_default_library", + "//pkg/kubelet/network:go_default_library", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "//pkg/kubelet/apis/kubeletconfig:go_default_library", + "//pkg/kubelet/container:go_default_library", + "//pkg/kubelet/network:go_default_library", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "//pkg/kubelet/apis/kubeletconfig:go_default_library", + "//pkg/kubelet/container:go_default_library", + "//pkg/kubelet/network:go_default_library", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "//pkg/kubelet/apis/kubeletconfig:go_default_library", + "//pkg/kubelet/container:go_default_library", + "//pkg/kubelet/network:go_default_library", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "//pkg/kubelet/apis/kubeletconfig:go_default_library", + "//pkg/kubelet/container:go_default_library", + "//pkg/kubelet/network:go_default_library", + ], "//conditions:default": [], }), ) @@ -49,15 +127,15 @@ go_library( go_test( name = "go_default_test", srcs = select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "kubenet_linux_test.go", ], "//conditions:default": [], }), + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/network/kubenet", - library = ":go_default_library", deps = select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//pkg/kubelet/apis/kubeletconfig:go_default_library", "//pkg/kubelet/container:go_default_library", "//pkg/kubelet/network:go_default_library", diff --git a/pkg/kubelet/network/kubenet/kubenet_linux.go b/pkg/kubelet/network/kubenet/kubenet_linux.go index 8f0a9f19e80..35e0a1b0cc7 100644 --- a/pkg/kubelet/network/kubenet/kubenet_linux.go +++ b/pkg/kubelet/network/kubenet/kubenet_linux.go @@ -23,7 +23,6 @@ import ( "io/ioutil" "net" "path/filepath" - "strconv" "strings" "sync" "time" @@ -59,12 +58,6 @@ const ( // fallbackMTU is used if an MTU is not specified, and we cannot determine the MTU fallbackMTU = 1460 - // private mac prefix safe to use - // Universally administered and locally administered addresses are distinguished by setting the second-least-significant - // bit of the first octet of the address. If it is 1, the address is locally administered. For example, for address 0a:00:00:00:00:00, - // the first cotet is 0a(hex), the binary form of which is 00001010, where the second-least-significant bit is 1. - privateMACPrefix = "0a:58" - // ebtables Chain to store dedup rules dedupChain = utilebtables.Chain("KUBE-DEDUP") @@ -98,7 +91,7 @@ type kubenetNetworkPlugin struct { iptables utiliptables.Interface sysctl utilsysctl.Interface ebtables utilebtables.Interface - // vendorDir is passed by kubelet network-plugin-dir parameter. + // vendorDir is passed by kubelet cni-bin-dir parameter. // kubenet will search for cni binaries in DefaultCNIDir first, then continue to vendorDir. vendorDir string nonMasqueradeCIDR string @@ -311,6 +304,11 @@ func (plugin *kubenetNetworkPlugin) Capabilities() utilsets.Int { // TODO: Don't pass the pod to this method, it only needs it for bandwidth // shaping and hostport management. func (plugin *kubenetNetworkPlugin) setup(namespace string, name string, id kubecontainer.ContainerID, pod *v1.Pod, annotations map[string]string) error { + // Disable DAD so we skip the kernel delay on bringing up new interfaces. + if err := plugin.disableContainerDAD(id); err != nil { + glog.V(3).Infof("Failed to disable DAD in container: %v", err) + } + // Bring up container loopback interface if _, err := plugin.addContainerToNetwork(plugin.loConfig, "lo", namespace, name, id); err != nil { return err @@ -334,35 +332,23 @@ func (plugin *kubenetNetworkPlugin) setup(namespace string, name string, id kube return fmt.Errorf("CNI plugin reported an invalid IPv4 address for container %v: %+v.", id, res.IP4) } - // Explicitly assign mac address to cbr0. If bridge mac address is not explicitly set will adopt the lowest MAC address of the attached veths. - // TODO: Remove this once upstream cni bridge plugin handles this - link, err := netlink.LinkByName(BridgeName) - if err != nil { - return fmt.Errorf("failed to lookup %q: %v", BridgeName, err) - } - macAddr, err := generateHardwareAddr(plugin.gateway) - if err != nil { - return err - } - glog.V(3).Infof("Configure %q mac address to %v", BridgeName, macAddr) - err = netlink.LinkSetHardwareAddr(link, macAddr) - if err != nil { - return fmt.Errorf("Failed to configure %q mac address to %q: %v", BridgeName, macAddr, err) - } - // Put the container bridge into promiscuous mode to force it to accept hairpin packets. // TODO: Remove this once the kernel bug (#20096) is fixed. - // TODO: check and set promiscuous mode with netlink once vishvananda/netlink supports it if plugin.hairpinMode == kubeletconfig.PromiscuousBridge { - output, err := plugin.execer.Command("ip", "link", "show", "dev", BridgeName).CombinedOutput() - if err != nil || strings.Index(string(output), "PROMISC") < 0 { - _, err := plugin.execer.Command("ip", "link", "set", BridgeName, "promisc", "on").CombinedOutput() + link, err := netlink.LinkByName(BridgeName) + if err != nil { + return fmt.Errorf("failed to lookup %q: %v", BridgeName, err) + } + if link.Attrs().Promisc != 1 { + // promiscuous mode is not on, then turn it on. + err := netlink.SetPromiscOn(link) if err != nil { return fmt.Errorf("Error setting promiscuous mode on %s: %v", BridgeName, err) } } + // configure the ebtables rules to eliminate duplicate packets by best effort - plugin.syncEbtablesDedupRules(macAddr) + plugin.syncEbtablesDedupRules(link.Attrs().HardwareAddr) } plugin.podIPs[id] = ip4.String() @@ -761,7 +747,11 @@ func (plugin *kubenetNetworkPlugin) addContainerToNetwork(config *libcni.Network } glog.V(3).Infof("Adding %s/%s to '%s' with CNI '%s' plugin and runtime: %+v", namespace, name, config.Network.Name, config.Network.Type, rt) + // The network plugin can take up to 3 seconds to execute, + // so yield the lock while it runs. + plugin.mu.Unlock() res, err := plugin.cniConfig.AddNetwork(config, rt) + plugin.mu.Lock() if err != nil { return nil, fmt.Errorf("Error adding container to network: %v", err) } @@ -775,7 +765,10 @@ func (plugin *kubenetNetworkPlugin) delContainerFromNetwork(config *libcni.Netwo } glog.V(3).Infof("Removing %s/%s from '%s' with CNI '%s' plugin and runtime: %+v", namespace, name, config.Network.Name, config.Network.Type, rt) - if err := plugin.cniConfig.DelNetwork(config, rt); err != nil { + err = plugin.cniConfig.DelNetwork(config, rt) + // The pod may not get deleted successfully at the first time. + // Ignore "no such file or directory" error in case the network has already been deleted in previous attempts. + if err != nil && !strings.Contains(err.Error(), "no such file or directory") { return fmt.Errorf("Error removing container from network: %v", err) } return nil @@ -834,20 +827,42 @@ func (plugin *kubenetNetworkPlugin) syncEbtablesDedupRules(macAddr net.HardwareA } } -// generateHardwareAddr generates 48 bit virtual mac addresses based on the IP input. -func generateHardwareAddr(ip net.IP) (net.HardwareAddr, error) { - if ip.To4() == nil { - return nil, fmt.Errorf("generateHardwareAddr only support valid ipv4 address as input") - } - mac := privateMACPrefix - sections := strings.Split(ip.String(), ".") - for _, s := range sections { - i, _ := strconv.Atoi(s) - mac = mac + ":" + fmt.Sprintf("%02x", i) - } - hwAddr, err := net.ParseMAC(mac) +// disableContainerDAD disables duplicate address detection in the container. +// DAD has a negative affect on pod creation latency, since we have to wait +// a second or more for the addresses to leave the "tentative" state. Since +// we're sure there won't be an address conflict (since we manage them manually), +// this is safe. See issue 54651. +// +// This sets net.ipv6.conf.default.dad_transmits to 0. It must be run *before* +// the CNI plugins are run. +func (plugin *kubenetNetworkPlugin) disableContainerDAD(id kubecontainer.ContainerID) error { + key := "net/ipv6/conf/default/dad_transmits" + + sysctlBin, err := plugin.execer.LookPath("sysctl") if err != nil { - return nil, fmt.Errorf("Failed to parse mac address %s generated based on ip %s due to: %v", mac, ip, err) + return fmt.Errorf("Could not find sysctl binary: %s", err) } - return hwAddr, nil + + netnsPath, err := plugin.host.GetNetNS(id.ID) + if err != nil { + return fmt.Errorf("Failed to get netns: %v", err) + } + if netnsPath == "" { + return fmt.Errorf("Pod has no network namespace") + } + + // If the sysctl doesn't exist, it means ipv6 is disabled; log and move on + if _, err := plugin.sysctl.GetSysctl(key); err != nil { + return fmt.Errorf("Ipv6 not enabled: %v", err) + } + + output, err := plugin.execer.Command(plugin.nsenterPath, + fmt.Sprintf("--net=%s", netnsPath), "-F", "--", + sysctlBin, "-w", fmt.Sprintf("%s=%s", key, "0"), + ).CombinedOutput() + if err != nil { + return fmt.Errorf("Failed to write sysctl: output: %s error: %s", + output, err) + } + return nil } diff --git a/pkg/kubelet/network/kubenet/kubenet_linux_test.go b/pkg/kubelet/network/kubenet/kubenet_linux_test.go index 60037286643..01f6a04b331 100644 --- a/pkg/kubelet/network/kubenet/kubenet_linux_test.go +++ b/pkg/kubelet/network/kubenet/kubenet_linux_test.go @@ -18,7 +18,6 @@ package kubenet import ( "fmt" - "net" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -200,36 +199,6 @@ func TestInit_MTU(t *testing.T) { assert.Equal(t, 1, sysctl.Settings["net/bridge/bridge-nf-call-iptables"], "net/bridge/bridge-nf-call-iptables sysctl should have been set") } -func TestGenerateMacAddress(t *testing.T) { - testCases := []struct { - ip net.IP - expectedMAC string - }{ - { - ip: net.ParseIP("10.0.0.2"), - expectedMAC: privateMACPrefix + ":0a:00:00:02", - }, - { - ip: net.ParseIP("10.250.0.244"), - expectedMAC: privateMACPrefix + ":0a:fa:00:f4", - }, - { - ip: net.ParseIP("172.17.0.2"), - expectedMAC: privateMACPrefix + ":ac:11:00:02", - }, - } - - for _, tc := range testCases { - mac, err := generateHardwareAddr(tc.ip) - if err != nil { - t.Errorf("Did not expect error: %v", err) - } - if mac.String() != tc.expectedMAC { - t.Errorf("generated mac: %q, expecting: %q", mac.String(), tc.expectedMAC) - } - } -} - // TestInvocationWithoutRuntime invokes the plugin without a runtime. // This is how kubenet is invoked from the cri. func TestTearDownWithoutRuntime(t *testing.T) { diff --git a/pkg/kubelet/network/plugins.go b/pkg/kubelet/network/plugins.go index 1714af8005d..24e358943e9 100644 --- a/pkg/kubelet/network/plugins.go +++ b/pkg/kubelet/network/plugins.go @@ -157,6 +157,7 @@ func InitNetworkPlugin(plugins []NetworkPlugin, networkPluginName string, host H if networkPluginName == "" { // default to the no_op plugin plug := &NoopNetworkPlugin{} + plug.Sysctl = utilsysctl.New() if err := plug.Init(host, hairpinMode, nonMasqueradeCIDR, mtu); err != nil { return nil, err } @@ -200,9 +201,11 @@ func UnescapePluginName(in string) string { } type NoopNetworkPlugin struct { + Sysctl utilsysctl.Interface } const sysctlBridgeCallIPTables = "net/bridge/bridge-nf-call-iptables" +const sysctlBridgeCallIP6Tables = "net/bridge/bridge-nf-call-ip6tables" func (plugin *NoopNetworkPlugin) Init(host Host, hairpinMode kubeletconfig.HairpinMode, nonMasqueradeCIDR string, mtu int) error { // Set bridge-nf-call-iptables=1 to maintain compatibility with older @@ -214,9 +217,16 @@ func (plugin *NoopNetworkPlugin) Init(host Host, hairpinMode kubeletconfig.Hairp // Ensure the netfilter module is loaded on kernel >= 3.18; previously // it was built-in. utilexec.New().Command("modprobe", "br-netfilter").CombinedOutput() - if err := utilsysctl.New().SetSysctl(sysctlBridgeCallIPTables, 1); err != nil { + if err := plugin.Sysctl.SetSysctl(sysctlBridgeCallIPTables, 1); err != nil { glog.Warningf("can't set sysctl %s: %v", sysctlBridgeCallIPTables, err) } + if val, err := plugin.Sysctl.GetSysctl(sysctlBridgeCallIP6Tables); err == nil { + if val != 1 { + if err = plugin.Sysctl.SetSysctl(sysctlBridgeCallIP6Tables, 1); err != nil { + glog.Warningf("can't set sysctl %s: %v", sysctlBridgeCallIP6Tables, err) + } + } + } return nil } diff --git a/pkg/kubelet/network/testing/BUILD b/pkg/kubelet/network/testing/BUILD index f0aa262005c..2e4525df632 100644 --- a/pkg/kubelet/network/testing/BUILD +++ b/pkg/kubelet/network/testing/BUILD @@ -29,13 +29,15 @@ go_library( go_test( name = "go_default_test", srcs = ["plugins_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/network/testing", - library = ":go_default_library", deps = [ "//pkg/kubelet/apis/kubeletconfig:go_default_library", "//pkg/kubelet/container:go_default_library", "//pkg/kubelet/network:go_default_library", + "//pkg/util/sysctl/testing:go_default_library", "//vendor/github.com/golang/mock/gomock:go_default_library", + "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", ], ) diff --git a/pkg/kubelet/network/testing/plugins_test.go b/pkg/kubelet/network/testing/plugins_test.go index 07ba256a250..6398948fa3c 100644 --- a/pkg/kubelet/network/testing/plugins_test.go +++ b/pkg/kubelet/network/testing/plugins_test.go @@ -26,8 +26,10 @@ import ( "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/kubelet/network" + sysctltest "k8s.io/kubernetes/pkg/util/sysctl/testing" "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" ) func TestSelectDefaultPlugin(t *testing.T) { @@ -44,6 +46,35 @@ func TestSelectDefaultPlugin(t *testing.T) { } } +func TestInit(t *testing.T) { + tests := []struct { + setting string + expectedLen int + }{ + { + setting: "net/bridge/bridge-nf-call-iptables", + expectedLen: 1, + }, + { + setting: "net/bridge/bridge-nf-call-ip6tables", + expectedLen: 2, + }, + } + for _, tt := range tests { + sysctl := sysctltest.NewFake() + sysctl.Settings[tt.setting] = 0 + plug := &network.NoopNetworkPlugin{} + plug.Sysctl = sysctl + plug.Init(NewFakeHost(nil), kubeletconfig.HairpinNone, "10.0.0.0/8", network.UseDefaultMTU) + // Verify the sysctl specified is set + assert.Equal(t, 1, sysctl.Settings[tt.setting], tt.setting+" sysctl should have been set") + // Verify iptables is always set + assert.Equal(t, 1, sysctl.Settings["net/bridge/bridge-nf-call-iptables"], "net/bridge/bridge-nf-call-iptables sysctl should have been set") + // Verify ip6tables is only set if it existed + assert.Len(t, sysctl.Settings, tt.expectedLen, "length wrong for "+tt.setting) + } +} + func TestPluginManager(t *testing.T) { ctrl := gomock.NewController(t) fnp := NewMockNetworkPlugin(ctrl) diff --git a/pkg/kubelet/networks.go b/pkg/kubelet/networks.go deleted file mode 100644 index 84bcdb8fa0d..00000000000 --- a/pkg/kubelet/networks.go +++ /dev/null @@ -1,95 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES 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 ( - "k8s.io/api/core/v1" - clientset "k8s.io/client-go/kubernetes" - kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" - "k8s.io/kubernetes/pkg/kubelet/network" -) - -// This just exports required functions from kubelet proper, for use by network -// plugins. -// TODO(#35457): get rid of this backchannel to the kubelet. The scope of -// the back channel is restricted to host-ports/testing, and restricted -// to kubenet. No other network plugin wrapper needs it. Other plugins -// only require a way to access namespace information, which they can do -// directly through the methods implemented by criNetworkHost. -type networkHost struct { - kubelet *Kubelet -} - -func (nh *networkHost) GetPodByName(name, namespace string) (*v1.Pod, bool) { - return nh.kubelet.GetPodByName(name, namespace) -} - -func (nh *networkHost) GetKubeClient() clientset.Interface { - return nh.kubelet.kubeClient -} - -func (nh *networkHost) GetRuntime() kubecontainer.Runtime { - return nh.kubelet.GetRuntime() -} - -func (nh *networkHost) SupportsLegacyFeatures() bool { - return true -} - -// criNetworkHost implements the part of network.Host required by the -// cri (NamespaceGetter). It leechs off networkHost for all other -// methods, because networkHost is slated for deletion. -type criNetworkHost struct { - *networkHost - // criNetworkHost currently support legacy features. Hence no need to support PortMappingGetter - *network.NoopPortMappingGetter -} - -// GetNetNS returns the network namespace of the given containerID. -// This method satisfies the network.NamespaceGetter interface for -// networkHost. It's only meant to be used from network plugins -// that are directly invoked by the kubelet (aka: legacy, pre-cri). -// Any network plugin invoked by a cri must implement NamespaceGetter -// to talk directly to the runtime instead. -func (c *criNetworkHost) GetNetNS(containerID string) (string, error) { - return c.kubelet.GetRuntime().GetNetNS(kubecontainer.ContainerID{Type: "", ID: containerID}) -} - -// NoOpLegacyHost implements the network.LegacyHost interface for the remote -// runtime shim by just returning empties. It doesn't support legacy features -// like host port and bandwidth shaping. -type NoOpLegacyHost struct{} - -// GetPodByName always returns "nil, true" for 'NoOpLegacyHost' -func (n *NoOpLegacyHost) GetPodByName(namespace, name string) (*v1.Pod, bool) { - return nil, true -} - -// GetKubeClient always returns "nil" for 'NoOpLegacyHost' -func (n *NoOpLegacyHost) GetKubeClient() clientset.Interface { - return nil -} - -// GetRuntime always returns "nil" for 'NoOpLegacyHost' -func (n *NoOpLegacyHost) GetRuntime() kubecontainer.Runtime { - return nil -} - -// SupportsLegacyFeatures always returns "false" for 'NoOpLegacyHost' -func (n *NoOpLegacyHost) SupportsLegacyFeatures() bool { - return false -} diff --git a/pkg/kubelet/networks_test.go b/pkg/kubelet/networks_test.go deleted file mode 100644 index 6c7c68f8a08..00000000000 --- a/pkg/kubelet/networks_test.go +++ /dev/null @@ -1,91 +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 kubelet - -import ( - "testing" -) - -func TestNetworkHostGetsPodNotFound(t *testing.T) { - testKubelet := newTestKubelet(t, true) - defer testKubelet.Cleanup() - nh := networkHost{testKubelet.kubelet} - - actualPod, _ := nh.GetPodByName("", "") - if actualPod != nil { - t.Fatalf("Was expected nil, received %v instead", actualPod) - } -} - -func TestNetworkHostGetsKubeClient(t *testing.T) { - testKubelet := newTestKubelet(t, true) - defer testKubelet.Cleanup() - nh := networkHost{testKubelet.kubelet} - - if nh.GetKubeClient() != testKubelet.fakeKubeClient { - t.Fatalf("NetworkHost client does not match testKubelet's client") - } -} - -func TestNetworkHostGetsRuntime(t *testing.T) { - testKubelet := newTestKubelet(t, true) - defer testKubelet.Cleanup() - nh := networkHost{testKubelet.kubelet} - - if nh.GetRuntime() != testKubelet.fakeRuntime { - t.Fatalf("NetworkHost runtime does not match testKubelet's runtime") - } -} - -func TestNetworkHostSupportsLegacyFeatures(t *testing.T) { - testKubelet := newTestKubelet(t, true) - defer testKubelet.Cleanup() - nh := networkHost{testKubelet.kubelet} - - if nh.SupportsLegacyFeatures() == false { - t.Fatalf("SupportsLegacyFeatures should not be false") - } -} - -func TestNoOpHostGetsName(t *testing.T) { - nh := NoOpLegacyHost{} - pod, err := nh.GetPodByName("", "") - if pod != nil && err != true { - t.Fatalf("noOpLegacyHost getpodbyname expected to be nil and true") - } -} - -func TestNoOpHostGetsKubeClient(t *testing.T) { - nh := NoOpLegacyHost{} - if nh.GetKubeClient() != nil { - t.Fatalf("noOpLegacyHost client expected to be nil") - } -} - -func TestNoOpHostGetsRuntime(t *testing.T) { - nh := NoOpLegacyHost{} - if nh.GetRuntime() != nil { - t.Fatalf("noOpLegacyHost runtime expected to be nil") - } -} - -func TestNoOpHostSupportsLegacyFeatures(t *testing.T) { - nh := NoOpLegacyHost{} - if nh.SupportsLegacyFeatures() != false { - t.Fatalf("noOpLegacyHost legacy features expected to be false") - } -} diff --git a/pkg/kubelet/pleg/BUILD b/pkg/kubelet/pleg/BUILD index 122fd02436d..2033ea4be74 100644 --- a/pkg/kubelet/pleg/BUILD +++ b/pkg/kubelet/pleg/BUILD @@ -29,8 +29,8 @@ go_library( go_test( name = "go_default_test", srcs = ["generic_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/pleg", - library = ":go_default_library", deps = [ "//pkg/kubelet/container:go_default_library", "//pkg/kubelet/container/testing:go_default_library", diff --git a/pkg/kubelet/pleg/generic.go b/pkg/kubelet/pleg/generic.go index 2d8a9a0c13d..b873efb005b 100644 --- a/pkg/kubelet/pleg/generic.go +++ b/pkg/kubelet/pleg/generic.go @@ -173,7 +173,7 @@ func (g *GenericPLEG) getRelistTime() time.Time { return val.(time.Time) } -func (g *GenericPLEG) updateRelisTime(timestamp time.Time) { +func (g *GenericPLEG) updateRelistTime(timestamp time.Time) { g.relistTime.Store(timestamp) } @@ -198,8 +198,7 @@ func (g *GenericPLEG) relist() { return } - // Update the relist time. - g.updateRelisTime(timestamp) + g.updateRelistTime(timestamp) pods := kubecontainer.Pods(podList) g.podRecords.setCurrent(pods) diff --git a/pkg/kubelet/pod/BUILD b/pkg/kubelet/pod/BUILD index 34621f090c4..aa3f1feecb1 100644 --- a/pkg/kubelet/pod/BUILD +++ b/pkg/kubelet/pod/BUILD @@ -14,6 +14,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/kubelet/pod", deps = [ + "//pkg/kubelet/checkpoint:go_default_library", "//pkg/kubelet/configmap:go_default_library", "//pkg/kubelet/container:go_default_library", "//pkg/kubelet/secret:go_default_library", @@ -33,8 +34,8 @@ go_test( "mirror_client_test.go", "pod_manager_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/pod", - library = ":go_default_library", deps = [ "//pkg/kubelet/configmap:go_default_library", "//pkg/kubelet/container:go_default_library", diff --git a/pkg/kubelet/pod/mirror_client.go b/pkg/kubelet/pod/mirror_client.go index fc81acec2b1..9b8add5bc5a 100644 --- a/pkg/kubelet/pod/mirror_client.go +++ b/pkg/kubelet/pod/mirror_client.go @@ -63,7 +63,7 @@ func (mc *basicMirrorClient) CreateMirrorPod(pod *v1.Pod) error { } hash := getPodHash(pod) copyPod.Annotations[kubetypes.ConfigMirrorAnnotationKey] = hash - apiPod, err := mc.apiserverClient.Core().Pods(copyPod.Namespace).Create(©Pod) + apiPod, err := mc.apiserverClient.CoreV1().Pods(copyPod.Namespace).Create(©Pod) if err != nil && errors.IsAlreadyExists(err) { // Check if the existing pod is the same as the pod we want to create. if h, ok := apiPod.Annotations[kubetypes.ConfigMirrorAnnotationKey]; ok && h == hash { @@ -84,7 +84,7 @@ func (mc *basicMirrorClient) DeleteMirrorPod(podFullName string) error { } glog.V(2).Infof("Deleting a mirror pod %q", podFullName) // TODO(random-liu): Delete the mirror pod with uid precondition in mirror pod manager - if err := mc.apiserverClient.Core().Pods(namespace).Delete(name, metav1.NewDeleteOptions(0)); err != nil && !errors.IsNotFound(err) { + if err := mc.apiserverClient.CoreV1().Pods(namespace).Delete(name, metav1.NewDeleteOptions(0)); err != nil && !errors.IsNotFound(err) { glog.Errorf("Failed deleting a mirror pod %q: %v", podFullName, err) } return nil diff --git a/pkg/kubelet/pod/pod_manager.go b/pkg/kubelet/pod/pod_manager.go index 20940a7dc5b..be15616a9ea 100644 --- a/pkg/kubelet/pod/pod_manager.go +++ b/pkg/kubelet/pod/pod_manager.go @@ -19,8 +19,11 @@ package pod import ( "sync" + "github.com/golang/glog" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/kubernetes/pkg/kubelet/checkpoint" "k8s.io/kubernetes/pkg/kubelet/configmap" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/kubelet/secret" @@ -116,8 +119,9 @@ type basicManager struct { translationByUID map[kubetypes.MirrorPodUID]kubetypes.ResolvedPodUID // basicManager is keeping secretManager and configMapManager up-to-date. - secretManager secret.Manager - configMapManager configmap.Manager + secretManager secret.Manager + configMapManager configmap.Manager + checkpointManager checkpoint.Manager // A mirror pod client to create/delete mirror pods. MirrorClient @@ -128,6 +132,7 @@ func NewBasicPodManager(client MirrorClient, secretManager secret.Manager, confi pm := &basicManager{} pm.secretManager = secretManager pm.configMapManager = configMapManager + pm.checkpointManager = checkpoint.GetInstance() pm.MirrorClient = client pm.SetPods(nil) return pm @@ -155,6 +160,11 @@ func (pm *basicManager) UpdatePod(pod *v1.Pod) { pm.lock.Lock() defer pm.lock.Unlock() pm.updatePodsInternal(pod) + if pm.checkpointManager != nil { + if err := pm.checkpointManager.WritePod(pod); err != nil { + glog.Errorf("Error writing checkpoint for pod: %v", pod.GetName()) + } + } } // updatePodsInternal replaces the given pods in the current state of the @@ -213,6 +223,11 @@ func (pm *basicManager) DeletePod(pod *v1.Pod) { delete(pm.podByUID, kubetypes.ResolvedPodUID(pod.UID)) delete(pm.podByFullName, podFullName) } + if pm.checkpointManager != nil { + if err := pm.checkpointManager.DeletePod(pod); err != nil { + glog.Errorf("Error deleting checkpoint for pod: %v", pod.GetName()) + } + } } func (pm *basicManager) GetPods() []*v1.Pod { diff --git a/pkg/kubelet/preemption/BUILD b/pkg/kubelet/preemption/BUILD index 68129778183..58899d1cd0e 100644 --- a/pkg/kubelet/preemption/BUILD +++ b/pkg/kubelet/preemption/BUILD @@ -11,16 +11,16 @@ go_library( srcs = ["preemption.go"], importpath = "k8s.io/kubernetes/pkg/kubelet/preemption", deps = [ - "//pkg/api/v1/helper/qos:go_default_library", "//pkg/api/v1/resource:go_default_library", + "//pkg/apis/core/v1/helper/qos:go_default_library", "//pkg/features:go_default_library", "//pkg/kubelet/events:go_default_library", "//pkg/kubelet/eviction:go_default_library", "//pkg/kubelet/lifecycle:go_default_library", "//pkg/kubelet/types:go_default_library", "//pkg/kubelet/util/format:go_default_library", - "//plugin/pkg/scheduler/algorithm:go_default_library", - "//plugin/pkg/scheduler/algorithm/predicates:go_default_library", + "//pkg/scheduler/algorithm:go_default_library", + "//pkg/scheduler/algorithm/predicates:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", @@ -44,10 +44,10 @@ filegroup( go_test( name = "go_default_test", srcs = ["preemption_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/preemption", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/kubelet/types:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", diff --git a/pkg/kubelet/preemption/preemption.go b/pkg/kubelet/preemption/preemption.go index fd144632fe2..96d829e4f8c 100644 --- a/pkg/kubelet/preemption/preemption.go +++ b/pkg/kubelet/preemption/preemption.go @@ -24,16 +24,16 @@ import ( "k8s.io/api/core/v1" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/tools/record" - v1qos "k8s.io/kubernetes/pkg/api/v1/helper/qos" "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/events" "k8s.io/kubernetes/pkg/kubelet/eviction" "k8s.io/kubernetes/pkg/kubelet/lifecycle" kubetypes "k8s.io/kubernetes/pkg/kubelet/types" "k8s.io/kubernetes/pkg/kubelet/util/format" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + "k8s.io/kubernetes/pkg/scheduler/algorithm/predicates" ) const message = "Preempted in order to admit critical pod" diff --git a/pkg/kubelet/preemption/preemption_test.go b/pkg/kubelet/preemption/preemption_test.go index 331b4f72a57..d142240c296 100644 --- a/pkg/kubelet/preemption/preemption_test.go +++ b/pkg/kubelet/preemption/preemption_test.go @@ -24,7 +24,7 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/record" - kubeapi "k8s.io/kubernetes/pkg/api" + kubeapi "k8s.io/kubernetes/pkg/apis/core" kubetypes "k8s.io/kubernetes/pkg/kubelet/types" ) diff --git a/pkg/kubelet/prober/BUILD b/pkg/kubelet/prober/BUILD index ef21ca9173f..4e0d70e290a 100644 --- a/pkg/kubelet/prober/BUILD +++ b/pkg/kubelet/prober/BUILD @@ -45,8 +45,8 @@ go_test( "prober_test.go", "worker_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/prober", - library = ":go_default_library", deps = [ "//pkg/kubelet/container:go_default_library", "//pkg/kubelet/container/testing:go_default_library", diff --git a/pkg/kubelet/prober/results/BUILD b/pkg/kubelet/prober/results/BUILD index 2e73fb74483..7aaf9701dc9 100644 --- a/pkg/kubelet/prober/results/BUILD +++ b/pkg/kubelet/prober/results/BUILD @@ -20,8 +20,8 @@ go_library( go_test( name = "go_default_test", srcs = ["results_manager_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/prober/results", - library = ":go_default_library", deps = [ "//pkg/kubelet/container:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", diff --git a/pkg/kubelet/prober/results/results_manager.go b/pkg/kubelet/prober/results/results_manager.go index 03261e353d2..2b01be7ba2f 100644 --- a/pkg/kubelet/prober/results/results_manager.go +++ b/pkg/kubelet/prober/results/results_manager.go @@ -77,7 +77,7 @@ type manager struct { var _ Manager = &manager{} -// NewManager creates ane returns an empty results manager. +// NewManager creates and returns an empty results manager. func NewManager() Manager { return &manager{ cache: make(map[kubecontainer.ContainerID]Result), diff --git a/pkg/kubelet/qos/BUILD b/pkg/kubelet/qos/BUILD index 7807bc798dc..30fd70d5a2b 100644 --- a/pkg/kubelet/qos/BUILD +++ b/pkg/kubelet/qos/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["policy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/qos", - library = ":go_default_library", deps = [ "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", @@ -25,7 +25,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/kubelet/qos", deps = [ - "//pkg/api/v1/helper/qos:go_default_library", + "//pkg/apis/core/v1/helper/qos:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", ], ) diff --git a/pkg/kubelet/qos/policy.go b/pkg/kubelet/qos/policy.go index 01125dd7708..b7177d2a4fd 100644 --- a/pkg/kubelet/qos/policy.go +++ b/pkg/kubelet/qos/policy.go @@ -18,7 +18,7 @@ package qos import ( "k8s.io/api/core/v1" - v1qos "k8s.io/kubernetes/pkg/api/v1/helper/qos" + v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos" ) const ( diff --git a/pkg/kubelet/remote/BUILD b/pkg/kubelet/remote/BUILD index bd7dd565d0d..ad83fa72b9c 100644 --- a/pkg/kubelet/remote/BUILD +++ b/pkg/kubelet/remote/BUILD @@ -3,6 +3,7 @@ package(default_visibility = ["//visibility:public"]) load( "@io_bazel_rules_go//go:def.bzl", "go_library", + "go_test", ) go_library( @@ -34,6 +35,23 @@ filegroup( filegroup( name = "all-srcs", - srcs = [":package-srcs"], + srcs = [ + ":package-srcs", + "//pkg/kubelet/remote/fake:all-srcs", + ], tags = ["automanaged"], ) + +go_test( + name = "go_default_test", + srcs = ["remote_runtime_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/kubelet/remote", + tags = ["automanaged"], + deps = [ + "//pkg/kubelet/apis/cri:go_default_library", + "//pkg/kubelet/apis/cri/testing:go_default_library", + "//pkg/kubelet/remote/fake:go_default_library", + "//vendor/github.com/stretchr/testify/assert:go_default_library", + ], +) diff --git a/pkg/kubelet/remote/fake/BUILD b/pkg/kubelet/remote/fake/BUILD new file mode 100644 index 00000000000..909c88edb2b --- /dev/null +++ b/pkg/kubelet/remote/fake/BUILD @@ -0,0 +1,75 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "fake_image_service.go", + "fake_runtime.go", + ] + select({ + "@io_bazel_rules_go//go/platform:android": [ + "endpoint.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "endpoint.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "endpoint.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "endpoint.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "endpoint.go", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "endpoint.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "endpoint.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "endpoint.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "endpoint.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "endpoint.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "endpoint_windows.go", + ], + "//conditions:default": [], + }), + importpath = "k8s.io/kubernetes/pkg/kubelet/remote/fake", + tags = ["automanaged"], + deps = [ + "//pkg/kubelet/apis/cri/testing:go_default_library", + "//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library", + "//pkg/kubelet/util:go_default_library", + "//vendor/golang.org/x/net/context:go_default_library", + "//vendor/google.golang.org/grpc:go_default_library", + "//vendor/k8s.io/utils/exec:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/pkg/kubelet/remote/fake/doc.go b/pkg/kubelet/remote/fake/doc.go new file mode 100644 index 00000000000..fc92f3c7690 --- /dev/null +++ b/pkg/kubelet/remote/fake/doc.go @@ -0,0 +1,19 @@ +/* +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 fake containers a fake gRPC implementation of internalapi.RuntimeService +// and internalapi.ImageManagerService. +package fake diff --git a/pkg/kubelet/remote/fake/endpoint.go b/pkg/kubelet/remote/fake/endpoint.go new file mode 100644 index 00000000000..12e4bda2559 --- /dev/null +++ b/pkg/kubelet/remote/fake/endpoint.go @@ -0,0 +1,28 @@ +// +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 fake + +const ( + defaultUnixEndpoint = "unix:///tmp/kubelet_remote.sock" +) + +// GenerateEndpoint generates a new unix socket server of grpc server. +func GenerateEndpoint() (string, error) { + return defaultUnixEndpoint, nil +} diff --git a/pkg/kubelet/remote/fake/endpoint_windows.go b/pkg/kubelet/remote/fake/endpoint_windows.go new file mode 100644 index 00000000000..cb43296bccb --- /dev/null +++ b/pkg/kubelet/remote/fake/endpoint_windows.go @@ -0,0 +1,40 @@ +// +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 fake + +import ( + "fmt" + "net" +) + +// GenerateEndpoint generates a new tcp endpoint of grpc server. +func GenerateEndpoint() (string, error) { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + if err != nil { + return "", nil + } + + l, err := net.ListenTCP("tcp", addr) + if err != nil { + return "", err + } + + defer l.Close() + return fmt.Sprintf("tcp://127.0.0.1:%d", l.Addr().(*net.TCPAddr).Port), nil +} diff --git a/pkg/kubelet/remote/fake/fake_image_service.go b/pkg/kubelet/remote/fake/fake_image_service.go new file mode 100644 index 00000000000..09e0df35d13 --- /dev/null +++ b/pkg/kubelet/remote/fake/fake_image_service.go @@ -0,0 +1,80 @@ +/* +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 fake + +import ( + "golang.org/x/net/context" + kubeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" +) + +// ListImages lists existing images. +func (f *RemoteRuntime) ListImages(ctx context.Context, req *kubeapi.ListImagesRequest) (*kubeapi.ListImagesResponse, error) { + images, err := f.ImageService.ListImages(req.Filter) + if err != nil { + return nil, err + } + + return &kubeapi.ListImagesResponse{ + Images: images, + }, nil +} + +// ImageStatus returns the status of the image. If the image is not +// present, returns a response with ImageStatusResponse.Image set to +// nil. +func (f *RemoteRuntime) ImageStatus(ctx context.Context, req *kubeapi.ImageStatusRequest) (*kubeapi.ImageStatusResponse, error) { + status, err := f.ImageService.ImageStatus(req.Image) + if err != nil { + return nil, err + } + + return &kubeapi.ImageStatusResponse{Image: status}, nil +} + +// PullImage pulls an image with authentication config. +func (f *RemoteRuntime) PullImage(ctx context.Context, req *kubeapi.PullImageRequest) (*kubeapi.PullImageResponse, error) { + image, err := f.ImageService.PullImage(req.Image, req.Auth) + if err != nil { + return nil, err + } + + return &kubeapi.PullImageResponse{ + ImageRef: image, + }, nil +} + +// RemoveImage removes the image. +// This call is idempotent, and must not return an error if the image has +// already been removed. +func (f *RemoteRuntime) RemoveImage(ctx context.Context, req *kubeapi.RemoveImageRequest) (*kubeapi.RemoveImageResponse, error) { + err := f.ImageService.RemoveImage(req.Image) + if err != nil { + return nil, err + } + + return &kubeapi.RemoveImageResponse{}, nil +} + +// ImageFsInfo returns information of the filesystem that is used to store images. +func (f *RemoteRuntime) ImageFsInfo(ctx context.Context, req *kubeapi.ImageFsInfoRequest) (*kubeapi.ImageFsInfoResponse, error) { + fsUsage, err := f.ImageService.ImageFsInfo() + if err != nil { + return nil, err + } + + return &kubeapi.ImageFsInfoResponse{ImageFilesystems: fsUsage}, nil +} diff --git a/pkg/kubelet/remote/fake/fake_runtime.go b/pkg/kubelet/remote/fake/fake_runtime.go new file mode 100644 index 00000000000..36d8dbfd8fe --- /dev/null +++ b/pkg/kubelet/remote/fake/fake_runtime.go @@ -0,0 +1,285 @@ +/* +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 fake + +import ( + "fmt" + "time" + + "golang.org/x/net/context" + "google.golang.org/grpc" + apitest "k8s.io/kubernetes/pkg/kubelet/apis/cri/testing" + kubeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" + "k8s.io/kubernetes/pkg/kubelet/util" + utilexec "k8s.io/utils/exec" +) + +// RemoteRuntime represents a fake remote container runtime. +type RemoteRuntime struct { + server *grpc.Server + // Fake runtime service. + RuntimeService *apitest.FakeRuntimeService + // Fake image service. + ImageService *apitest.FakeImageService +} + +// NewFakeRemoteRuntime creates a new RemoteRuntime. +func NewFakeRemoteRuntime() *RemoteRuntime { + fakeRuntimeService := apitest.NewFakeRuntimeService() + fakeImageService := apitest.NewFakeImageService() + + f := &RemoteRuntime{ + server: grpc.NewServer(), + RuntimeService: fakeRuntimeService, + ImageService: fakeImageService, + } + kubeapi.RegisterRuntimeServiceServer(f.server, f) + kubeapi.RegisterImageServiceServer(f.server, f) + + return f +} + +// Start starts the fake remote runtime. +func (f *RemoteRuntime) Start(endpoint string) error { + l, err := util.CreateListener(endpoint) + if err != nil { + return fmt.Errorf("failed to listen on %q: %v", endpoint, err) + } + + return f.server.Serve(l) +} + +// Stop stops the fake remote runtime. +func (f *RemoteRuntime) Stop() { + f.server.Stop() +} + +// Version returns the runtime name, runtime version, and runtime API version. +func (f *RemoteRuntime) Version(ctx context.Context, req *kubeapi.VersionRequest) (*kubeapi.VersionResponse, error) { + return f.RuntimeService.Version(req.Version) +} + +// RunPodSandbox creates and starts a pod-level sandbox. Runtimes must ensure +// the sandbox is in the ready state on success. +func (f *RemoteRuntime) RunPodSandbox(ctx context.Context, req *kubeapi.RunPodSandboxRequest) (*kubeapi.RunPodSandboxResponse, error) { + sandboxID, err := f.RuntimeService.RunPodSandbox(req.Config) + if err != nil { + return nil, err + } + + return &kubeapi.RunPodSandboxResponse{PodSandboxId: sandboxID}, nil +} + +// StopPodSandbox stops any running process that is part of the sandbox and +// reclaims network resources (e.g., IP addresses) allocated to the sandbox. +// If there are any running containers in the sandbox, they must be forcibly +// terminated. +func (f *RemoteRuntime) StopPodSandbox(ctx context.Context, req *kubeapi.StopPodSandboxRequest) (*kubeapi.StopPodSandboxResponse, error) { + err := f.RuntimeService.StopPodSandbox(req.PodSandboxId) + if err != nil { + return nil, err + } + + return &kubeapi.StopPodSandboxResponse{}, nil +} + +// RemovePodSandbox removes the sandbox. If there are any running containers +// in the sandbox, they must be forcibly terminated and removed. +// This call is idempotent, and must not return an error if the sandbox has +// already been removed. +func (f *RemoteRuntime) RemovePodSandbox(ctx context.Context, req *kubeapi.RemovePodSandboxRequest) (*kubeapi.RemovePodSandboxResponse, error) { + err := f.RuntimeService.StopPodSandbox(req.PodSandboxId) + if err != nil { + return nil, err + } + + return &kubeapi.RemovePodSandboxResponse{}, nil +} + +// PodSandboxStatus returns the status of the PodSandbox. If the PodSandbox is not +// present, returns an error. +func (f *RemoteRuntime) PodSandboxStatus(ctx context.Context, req *kubeapi.PodSandboxStatusRequest) (*kubeapi.PodSandboxStatusResponse, error) { + podStatus, err := f.RuntimeService.PodSandboxStatus(req.PodSandboxId) + if err != nil { + return nil, err + } + + return &kubeapi.PodSandboxStatusResponse{Status: podStatus}, nil +} + +// ListPodSandbox returns a list of PodSandboxes. +func (f *RemoteRuntime) ListPodSandbox(ctx context.Context, req *kubeapi.ListPodSandboxRequest) (*kubeapi.ListPodSandboxResponse, error) { + items, err := f.RuntimeService.ListPodSandbox(req.Filter) + if err != nil { + return nil, err + } + + return &kubeapi.ListPodSandboxResponse{Items: items}, nil +} + +// CreateContainer creates a new container in specified PodSandbox +func (f *RemoteRuntime) CreateContainer(ctx context.Context, req *kubeapi.CreateContainerRequest) (*kubeapi.CreateContainerResponse, error) { + containerID, err := f.RuntimeService.CreateContainer(req.PodSandboxId, req.Config, req.SandboxConfig) + if err != nil { + return nil, err + } + + return &kubeapi.CreateContainerResponse{ContainerId: containerID}, nil +} + +// StartContainer starts the container. +func (f *RemoteRuntime) StartContainer(ctx context.Context, req *kubeapi.StartContainerRequest) (*kubeapi.StartContainerResponse, error) { + err := f.RuntimeService.StartContainer(req.ContainerId) + if err != nil { + return nil, err + } + + return &kubeapi.StartContainerResponse{}, nil +} + +// StopContainer stops a running container with a grace period (i.e., timeout). +// This call is idempotent, and must not return an error if the container has +// already been stopped. +func (f *RemoteRuntime) StopContainer(ctx context.Context, req *kubeapi.StopContainerRequest) (*kubeapi.StopContainerResponse, error) { + err := f.RuntimeService.StopContainer(req.ContainerId, req.Timeout) + if err != nil { + return nil, err + } + + return &kubeapi.StopContainerResponse{}, nil +} + +// RemoveContainer removes the container. If the container is running, the +// container must be forcibly removed. +// This call is idempotent, and must not return an error if the container has +// already been removed. +func (f *RemoteRuntime) RemoveContainer(ctx context.Context, req *kubeapi.RemoveContainerRequest) (*kubeapi.RemoveContainerResponse, error) { + err := f.RuntimeService.RemoveContainer(req.ContainerId) + if err != nil { + return nil, err + } + + return &kubeapi.RemoveContainerResponse{}, nil +} + +// ListContainers lists all containers by filters. +func (f *RemoteRuntime) ListContainers(ctx context.Context, req *kubeapi.ListContainersRequest) (*kubeapi.ListContainersResponse, error) { + items, err := f.RuntimeService.ListContainers(req.Filter) + if err != nil { + return nil, err + } + + return &kubeapi.ListContainersResponse{Containers: items}, nil +} + +// ContainerStatus returns status of the container. If the container is not +// present, returns an error. +func (f *RemoteRuntime) ContainerStatus(ctx context.Context, req *kubeapi.ContainerStatusRequest) (*kubeapi.ContainerStatusResponse, error) { + status, err := f.RuntimeService.ContainerStatus(req.ContainerId) + if err != nil { + return nil, err + } + + return &kubeapi.ContainerStatusResponse{Status: status}, nil +} + +// ExecSync runs a command in a container synchronously. +func (f *RemoteRuntime) ExecSync(ctx context.Context, req *kubeapi.ExecSyncRequest) (*kubeapi.ExecSyncResponse, error) { + var exitCode int32 + stdout, stderr, err := f.RuntimeService.ExecSync(req.ContainerId, req.Cmd, time.Duration(req.Timeout)*time.Second) + if err != nil { + exitError, ok := err.(utilexec.ExitError) + if !ok { + return nil, err + } + exitCode = int32(exitError.ExitStatus()) + + return nil, err + } + + return &kubeapi.ExecSyncResponse{ + Stdout: stdout, + Stderr: stderr, + ExitCode: exitCode, + }, nil +} + +// Exec prepares a streaming endpoint to execute a command in the container. +func (f *RemoteRuntime) Exec(ctx context.Context, req *kubeapi.ExecRequest) (*kubeapi.ExecResponse, error) { + return f.RuntimeService.Exec(req) +} + +// Attach prepares a streaming endpoint to attach to a running container. +func (f *RemoteRuntime) Attach(ctx context.Context, req *kubeapi.AttachRequest) (*kubeapi.AttachResponse, error) { + return f.RuntimeService.Attach(req) +} + +// PortForward prepares a streaming endpoint to forward ports from a PodSandbox. +func (f *RemoteRuntime) PortForward(ctx context.Context, req *kubeapi.PortForwardRequest) (*kubeapi.PortForwardResponse, error) { + return f.RuntimeService.PortForward(req) +} + +// ContainerStats returns stats of the container. If the container does not +// exist, the call returns an error. +func (f *RemoteRuntime) ContainerStats(ctx context.Context, req *kubeapi.ContainerStatsRequest) (*kubeapi.ContainerStatsResponse, error) { + stats, err := f.RuntimeService.ContainerStats(req.ContainerId) + if err != nil { + return nil, err + } + + return &kubeapi.ContainerStatsResponse{Stats: stats}, nil +} + +// ListContainerStats returns stats of all running containers. +func (f *RemoteRuntime) ListContainerStats(ctx context.Context, req *kubeapi.ListContainerStatsRequest) (*kubeapi.ListContainerStatsResponse, error) { + stats, err := f.RuntimeService.ListContainerStats(req.Filter) + if err != nil { + return nil, err + } + + return &kubeapi.ListContainerStatsResponse{Stats: stats}, nil +} + +// UpdateRuntimeConfig updates the runtime configuration based on the given request. +func (f *RemoteRuntime) UpdateRuntimeConfig(ctx context.Context, req *kubeapi.UpdateRuntimeConfigRequest) (*kubeapi.UpdateRuntimeConfigResponse, error) { + err := f.RuntimeService.UpdateRuntimeConfig(req.RuntimeConfig) + if err != nil { + return nil, err + } + + return &kubeapi.UpdateRuntimeConfigResponse{}, nil +} + +// Status returns the status of the runtime. +func (f *RemoteRuntime) Status(ctx context.Context, req *kubeapi.StatusRequest) (*kubeapi.StatusResponse, error) { + status, err := f.RuntimeService.Status() + if err != nil { + return nil, err + } + + return &kubeapi.StatusResponse{Status: status}, nil +} + +// UpdateContainerResources updates ContainerConfig of the container. +func (f *RemoteRuntime) UpdateContainerResources(ctx context.Context, req *kubeapi.UpdateContainerResourcesRequest) (*kubeapi.UpdateContainerResourcesResponse, error) { + err := f.RuntimeService.UpdateContainerResources(req.ContainerId, req.Linux) + if err != nil { + return nil, err + } + + return &kubeapi.UpdateContainerResourcesResponse{}, nil +} diff --git a/pkg/kubelet/remote/remote_runtime_test.go b/pkg/kubelet/remote/remote_runtime_test.go new file mode 100644 index 00000000000..db5b31b6bf6 --- /dev/null +++ b/pkg/kubelet/remote/remote_runtime_test.go @@ -0,0 +1,69 @@ +/* +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 remote + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + internalapi "k8s.io/kubernetes/pkg/kubelet/apis/cri" + apitest "k8s.io/kubernetes/pkg/kubelet/apis/cri/testing" + fakeremote "k8s.io/kubernetes/pkg/kubelet/remote/fake" +) + +const ( + defaultConnectionTimeout = 15 * time.Second +) + +// createAndStartFakeRemoteRuntime creates and starts fakeremote.RemoteRuntime. +// It returns the RemoteRuntime, endpoint on success. +// Users should call fakeRuntime.Stop() to cleanup the server. +func createAndStartFakeRemoteRuntime(t *testing.T) (*fakeremote.RemoteRuntime, string) { + endpoint, err := fakeremote.GenerateEndpoint() + assert.NoError(t, err) + + fakeRuntime := fakeremote.NewFakeRemoteRuntime() + go fakeRuntime.Start(endpoint) + + return fakeRuntime, endpoint +} + +func createRemoteRuntimeService(endpoint string, t *testing.T) internalapi.RuntimeService { + runtimeService, err := NewRemoteRuntimeService(endpoint, defaultConnectionTimeout) + assert.NoError(t, err) + + return runtimeService +} + +func createRemoteImageService(endpoint string, t *testing.T) internalapi.ImageManagerService { + imageService, err := NewRemoteImageService(endpoint, defaultConnectionTimeout) + assert.NoError(t, err) + + return imageService +} + +func TestVersion(t *testing.T) { + fakeRuntime, endpoint := createAndStartFakeRemoteRuntime(t) + defer fakeRuntime.Stop() + + r := createRemoteRuntimeService(endpoint, t) + version, err := r.Version(apitest.FakeVersion) + assert.NoError(t, err) + assert.Equal(t, apitest.FakeVersion, version.Version) + assert.Equal(t, apitest.FakeRuntimeName, version.RuntimeName) +} diff --git a/pkg/kubelet/rkt/BUILD b/pkg/kubelet/rkt/BUILD index 3b687934fd3..40d41ba48e6 100644 --- a/pkg/kubelet/rkt/BUILD +++ b/pkg/kubelet/rkt/BUILD @@ -22,6 +22,7 @@ go_library( importpath = "k8s.io/kubernetes/pkg/kubelet/rkt", deps = [ "//pkg/credentialprovider:go_default_library", + "//pkg/credentialprovider/secrets:go_default_library", "//pkg/kubelet/container:go_default_library", "//pkg/kubelet/events:go_default_library", "//pkg/kubelet/images:go_default_library", @@ -66,8 +67,8 @@ go_test( "fake_rkt_interface_test.go", "rkt_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/rkt", - library = ":go_default_library", deps = [ "//pkg/kubelet/container:go_default_library", "//pkg/kubelet/container/testing:go_default_library", diff --git a/pkg/kubelet/rkt/image.go b/pkg/kubelet/rkt/image.go index 225604c6129..180dcfa51a3 100644 --- a/pkg/kubelet/rkt/image.go +++ b/pkg/kubelet/rkt/image.go @@ -35,6 +35,7 @@ import ( "golang.org/x/net/context" "k8s.io/api/core/v1" "k8s.io/kubernetes/pkg/credentialprovider" + credentialprovidersecrets "k8s.io/kubernetes/pkg/credentialprovider/secrets" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/util/parsers" ) @@ -54,7 +55,7 @@ func (r *Runtime) PullImage(image kubecontainer.ImageSpec, pullSecrets []v1.Secr return "", err } - keyring, err := credentialprovider.MakeDockerKeyring(pullSecrets, r.dockerKeyring) + keyring, err := credentialprovidersecrets.MakeDockerKeyring(pullSecrets, r.dockerKeyring) if err != nil { return "", err } diff --git a/pkg/kubelet/rkt/rkt.go b/pkg/kubelet/rkt/rkt.go index 70838fa4c19..b35288b4aab 100644 --- a/pkg/kubelet/rkt/rkt.go +++ b/pkg/kubelet/rkt/rkt.go @@ -842,7 +842,7 @@ func (r *Runtime) newAppcRuntimeApp(pod *v1.Pod, podIP string, c v1.Container, r } // TODO: determine how this should be handled for rkt - opts, _, err := r.runtimeHelper.GenerateRunContainerOptions(pod, &c, podIP) + opts, err := r.runtimeHelper.GenerateRunContainerOptions(pod, &c, podIP) if err != nil { return err } @@ -1041,17 +1041,17 @@ func (r *Runtime) generateRunCommand(pod *v1.Pod, uuid, networkNamespaceID strin } } else { // Setup DNS. - dnsServers, dnsSearches, _, err := r.runtimeHelper.GetClusterDNS(pod) + dnsConfig, err := r.runtimeHelper.GetPodDNS(pod) if err != nil { return "", err } - for _, server := range dnsServers { + for _, server := range dnsConfig.Servers { runPrepared = append(runPrepared, fmt.Sprintf("--dns=%s", server)) } - for _, search := range dnsSearches { + for _, search := range dnsConfig.Searches { runPrepared = append(runPrepared, fmt.Sprintf("--dns-search=%s", search)) } - if len(dnsServers) > 0 || len(dnsSearches) > 0 { + if len(dnsConfig.Servers) > 0 || len(dnsConfig.Searches) > 0 { runPrepared = append(runPrepared, fmt.Sprintf("--dns-opt=%s", defaultDNSOption)) } diff --git a/pkg/kubelet/rkt/rkt_test.go b/pkg/kubelet/rkt/rkt_test.go index de48c30add9..d235fe289c2 100644 --- a/pkg/kubelet/rkt/rkt_test.go +++ b/pkg/kubelet/rkt/rkt_test.go @@ -41,7 +41,6 @@ import ( utiltesting "k8s.io/client-go/util/testing" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" containertesting "k8s.io/kubernetes/pkg/kubelet/container/testing" - kubetesting "k8s.io/kubernetes/pkg/kubelet/container/testing" "k8s.io/kubernetes/pkg/kubelet/lifecycle" "k8s.io/kubernetes/pkg/kubelet/network" "k8s.io/kubernetes/pkg/kubelet/network/kubenet" @@ -1398,7 +1397,7 @@ func TestGenerateRunCommand(t *testing.T) { rkt := &Runtime{ nsenterPath: "/usr/bin/nsenter", - os: &kubetesting.FakeOS{HostName: hostName}, + os: &containertesting.FakeOS{HostName: hostName}, config: &Config{ Path: "/bin/rkt/rkt", Stage1Image: "/bin/rkt/stage1-coreos.aci", @@ -1635,7 +1634,7 @@ func TestGarbageCollect(t *testing.T) { fr := newFakeRktInterface() fs := newFakeSystemd() cli := newFakeRktCli() - fakeOS := kubetesting.NewFakeOS() + fakeOS := containertesting.NewFakeOS() deletionProvider := newFakePodDeletionProvider() fug := newfakeUnitGetter() frh := &containertesting.FakeRuntimeHelper{} @@ -2030,7 +2029,7 @@ func TestConstructSyslogIdentifier(t *testing.T) { } func TestGetPodSystemdServiceFiles(t *testing.T) { - fs := kubetesting.NewFakeOS() + fs := containertesting.NewFakeOS() r := &Runtime{os: fs} testCases := []struct { diff --git a/pkg/kubelet/rktshim/BUILD b/pkg/kubelet/rktshim/BUILD index 403d4dfdd89..cf6baa7dcda 100644 --- a/pkg/kubelet/rktshim/BUILD +++ b/pkg/kubelet/rktshim/BUILD @@ -26,8 +26,8 @@ go_library( go_test( name = "go_default_test", srcs = ["imagestore_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/rktshim", - library = ":go_default_library", deps = ["//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library"], ) diff --git a/pkg/kubelet/secret/BUILD b/pkg/kubelet/secret/BUILD index 26a879d8fa6..6ece4b21f3a 100644 --- a/pkg/kubelet/secret/BUILD +++ b/pkg/kubelet/secret/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["secret_manager_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/secret", - library = ":go_default_library", deps = [ "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/kubelet/secret/secret_manager.go b/pkg/kubelet/secret/secret_manager.go index 7fd8008784e..3de230a9032 100644 --- a/pkg/kubelet/secret/secret_manager.go +++ b/pkg/kubelet/secret/secret_manager.go @@ -64,7 +64,7 @@ func NewSimpleSecretManager(kubeClient clientset.Interface) Manager { } func (s *simpleSecretManager) GetSecret(namespace, name string) (*v1.Secret, error) { - return s.kubeClient.Core().Secrets(namespace).Get(name, metav1.GetOptions{}) + return s.kubeClient.CoreV1().Secrets(namespace).Get(name, metav1.GetOptions{}) } func (s *simpleSecretManager) RegisterPod(pod *v1.Pod) { @@ -216,7 +216,7 @@ func (s *secretStore) Get(namespace, name string) (*v1.Secret, error) { // etcd and apiserver (the cache is eventually consistent). util.FromApiserverCache(&opts) } - secret, err := s.kubeClient.Core().Secrets(namespace).Get(name, opts) + secret, err := s.kubeClient.CoreV1().Secrets(namespace).Get(name, opts) if err != nil && !apierrors.IsNotFound(err) && data.secret == nil && data.err == nil { // Couldn't fetch the latest secret, but there is no cached data to return. // Return the fetch result instead. diff --git a/pkg/kubelet/server/BUILD b/pkg/kubelet/server/BUILD index b1c32b7c9b9..bfc788686a2 100644 --- a/pkg/kubelet/server/BUILD +++ b/pkg/kubelet/server/BUILD @@ -15,8 +15,9 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/kubelet/server", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/v1/validation:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/v1/validation:go_default_library", "//pkg/kubelet/container:go_default_library", "//pkg/kubelet/server/portforward:go_default_library", "//pkg/kubelet/server/remotecommand:go_default_library", @@ -55,11 +56,11 @@ go_test( "server_test.go", "server_websocket_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/server", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/install:go_default_library", "//pkg/kubelet/apis/stats/v1alpha1:go_default_library", "//pkg/kubelet/cm:go_default_library", "//pkg/kubelet/container:go_default_library", diff --git a/pkg/kubelet/server/portforward/BUILD b/pkg/kubelet/server/portforward/BUILD index 062f972a0a7..b2ceea570ee 100644 --- a/pkg/kubelet/server/portforward/BUILD +++ b/pkg/kubelet/server/portforward/BUILD @@ -16,7 +16,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/kubelet/server/portforward", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/httpstream:go_default_library", @@ -33,10 +33,10 @@ go_test( "httpstream_test.go", "websocket_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/server/portforward", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/httpstream:go_default_library", ], ) diff --git a/pkg/kubelet/server/portforward/httpstream.go b/pkg/kubelet/server/portforward/httpstream.go index 5f872c82010..919ed5a7868 100644 --- a/pkg/kubelet/server/portforward/httpstream.go +++ b/pkg/kubelet/server/portforward/httpstream.go @@ -28,7 +28,7 @@ import ( "k8s.io/apimachinery/pkg/util/httpstream" "k8s.io/apimachinery/pkg/util/httpstream/spdy" utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "github.com/golang/glog" ) diff --git a/pkg/kubelet/server/portforward/httpstream_test.go b/pkg/kubelet/server/portforward/httpstream_test.go index ee5696d5a17..7f815036b9f 100644 --- a/pkg/kubelet/server/portforward/httpstream_test.go +++ b/pkg/kubelet/server/portforward/httpstream_test.go @@ -22,7 +22,7 @@ import ( "time" "k8s.io/apimachinery/pkg/util/httpstream" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestHTTPStreamReceived(t *testing.T) { diff --git a/pkg/kubelet/server/portforward/websocket.go b/pkg/kubelet/server/portforward/websocket.go index cb4bca04558..b445c922f60 100644 --- a/pkg/kubelet/server/portforward/websocket.go +++ b/pkg/kubelet/server/portforward/websocket.go @@ -32,7 +32,7 @@ import ( "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apiserver/pkg/server/httplog" "k8s.io/apiserver/pkg/util/wsstream" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) const ( diff --git a/pkg/kubelet/server/remotecommand/BUILD b/pkg/kubelet/server/remotecommand/BUILD index 8e340aeb5ff..97da39d617a 100644 --- a/pkg/kubelet/server/remotecommand/BUILD +++ b/pkg/kubelet/server/remotecommand/BUILD @@ -16,7 +16,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/kubelet/server/remotecommand", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/kubelet/server/remotecommand/httpstream.go b/pkg/kubelet/server/remotecommand/httpstream.go index 9d4883212d9..74f0595cf7e 100644 --- a/pkg/kubelet/server/remotecommand/httpstream.go +++ b/pkg/kubelet/server/remotecommand/httpstream.go @@ -32,7 +32,7 @@ import ( "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apiserver/pkg/util/wsstream" "k8s.io/client-go/tools/remotecommand" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "github.com/golang/glog" ) diff --git a/pkg/kubelet/server/server.go b/pkg/kubelet/server/server.go index 285a0d376ce..d22096e48d2 100644 --- a/pkg/kubelet/server/server.go +++ b/pkg/kubelet/server/server.go @@ -50,8 +50,9 @@ import ( "k8s.io/apiserver/pkg/server/httplog" "k8s.io/apiserver/pkg/util/flushwriter" "k8s.io/client-go/tools/remotecommand" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/v1/validation" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/v1/validation" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/kubelet/server/portforward" remotecommandserver "k8s.io/kubernetes/pkg/kubelet/server/remotecommand" @@ -210,6 +211,8 @@ func NewServer( if enableContentionProfiling { goruntime.SetBlockProfileRate(1) } + } else { + server.InstallDebuggingDisabledHandlers() } return server } @@ -233,14 +236,14 @@ func (s *Server) InstallAuthFilter() { attrs := s.auth.GetRequestAttributes(u, req.Request) // Authorize - authorized, _, err := s.auth.Authorize(attrs) + decision, _, err := s.auth.Authorize(attrs) if err != nil { msg := fmt.Sprintf("Authorization error (user=%s, verb=%s, resource=%s, subresource=%s)", u.GetName(), attrs.GetVerb(), attrs.GetResource(), attrs.GetSubresource()) glog.Errorf(msg, err) resp.WriteErrorString(http.StatusInternalServerError, msg) return } - if !authorized { + if decision != authorizer.DecisionAllow { msg := fmt.Sprintf("Forbidden (user=%s, verb=%s, resource=%s, subresource=%s)", u.GetName(), attrs.GetVerb(), attrs.GetResource(), attrs.GetSubresource()) glog.V(2).Info(msg) resp.WriteErrorString(http.StatusForbidden, msg) @@ -417,6 +420,20 @@ func (s *Server) InstallDebuggingHandlers(criHandler http.Handler) { } } +// InstallDebuggingDisabledHandlers registers the HTTP request patterns that provide better error message +func (s *Server) InstallDebuggingDisabledHandlers() { + h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "Debug endpoints are disabled.", http.StatusMethodNotAllowed) + }) + + paths := []string{ + "/run/", "/exec/", "/attach/", "/portForward/", "/containerLogs/", + "/runningpods/", pprofBasePath, logsPath} + for _, p := range paths { + s.restfulCont.Handle(p, h) + } +} + // Checks if kubelet's sync loop that updates containers is working. func (s *Server) syncLoopHealthCheck(req *http.Request) error { duration := s.host.ResyncInterval() * 2 @@ -465,7 +482,7 @@ func (s *Server) getContainerLogs(request *restful.Request, response *restful.Re } // container logs on the kubelet are locked to the v1 API version of PodLogOptions logOptions := &v1.PodLogOptions{} - if err := api.ParameterCodec.DecodeParameters(query, v1.SchemeGroupVersion, logOptions); err != nil { + if err := legacyscheme.ParameterCodec.DecodeParameters(query, v1.SchemeGroupVersion, logOptions); err != nil { response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Unable to decode query."}`)) return } @@ -531,7 +548,7 @@ func encodePods(pods []*v1.Pod) (data []byte, err error) { // TODO: this needs to be parameterized to the kubelet, not hardcoded. Depends on Kubelet // as API server refactor. // TODO: Locked to v1, needs to be made generic - codec := api.Codecs.LegacyCodec(schema.GroupVersion{Group: v1.GroupName, Version: "v1"}) + codec := legacyscheme.Codecs.LegacyCodec(schema.GroupVersion{Group: v1.GroupName, Version: "v1"}) return runtime.Encode(codec, podList) } diff --git a/pkg/kubelet/server/server_test.go b/pkg/kubelet/server/server_test.go index 85e62b2aae1..c1b068f2318 100644 --- a/pkg/kubelet/server/server_test.go +++ b/pkg/kubelet/server/server_test.go @@ -47,10 +47,10 @@ import ( "k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/client-go/tools/remotecommand" utiltesting "k8s.io/client-go/util/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1" // Do some initialization to decode the query parameters correctly. - _ "k8s.io/kubernetes/pkg/api/install" + _ "k8s.io/kubernetes/pkg/apis/core/install" "k8s.io/kubernetes/pkg/kubelet/cm" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" kubecontainertesting "k8s.io/kubernetes/pkg/kubelet/container/testing" @@ -183,7 +183,7 @@ func (_ *fakeKubelet) GetCgroupStats(cgroupName string) (*statsapi.ContainerStat type fakeAuth struct { authenticateFunc func(*http.Request) (user.Info, bool, error) attributesFunc func(user.Info, *http.Request) authorizer.Attributes - authorizeFunc func(authorizer.Attributes) (authorized bool, reason string, err error) + authorizeFunc func(authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) } func (f *fakeAuth) AuthenticateRequest(req *http.Request) (user.Info, bool, error) { @@ -192,7 +192,7 @@ func (f *fakeAuth) AuthenticateRequest(req *http.Request) (user.Info, bool, erro func (f *fakeAuth) GetRequestAttributes(u user.Info, req *http.Request) authorizer.Attributes { return f.attributesFunc(u, req) } -func (f *fakeAuth) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) { +func (f *fakeAuth) Authorize(a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) { return f.authorizeFunc(a) } @@ -205,6 +205,10 @@ type serverTestFramework struct { } func newServerTest() *serverTestFramework { + return newServerTestWithDebug(true) +} + +func newServerTestWithDebug(enableDebugging bool) *serverTestFramework { fw := &serverTestFramework{} fw.fakeKubelet = &fakeKubelet{ hostnameFunc: func() string { @@ -228,8 +232,8 @@ func newServerTest() *serverTestFramework { attributesFunc: func(u user.Info, req *http.Request) authorizer.Attributes { return &authorizer.AttributesRecord{User: u} }, - authorizeFunc: func(a authorizer.Attributes) (authorized bool, reason string, err error) { - return true, "", nil + authorizeFunc: func(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) { + return authorizer.DecisionAllow, "", nil }, } fw.criHandler = &utiltesting.FakeHandler{ @@ -239,7 +243,7 @@ func newServerTest() *serverTestFramework { fw.fakeKubelet, stats.NewResourceAnalyzer(fw.fakeKubelet, time.Minute), fw.fakeAuth, - true, + enableDebugging, false, &kubecontainertesting.Mock{}, fw.criHandler) @@ -684,12 +688,12 @@ Otherwise, add it to the expected list of paths that map to the "proxy" subresou } return attributesGetter.GetRequestAttributes(u, req) } - fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (authorized bool, reason string, err error) { + fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) { calledAuthorize = true if a != expectedAttributes { t.Fatalf("%s: expected attributes\n\t%#v\ngot\n\t%#v", tc.Path, expectedAttributes, a) } - return false, "", nil + return authorizer.DecisionNoOpinion, "", nil } req, err := http.NewRequest(tc.Method, fw.testHTTPServer.URL+tc.Path, nil) @@ -743,9 +747,9 @@ func TestAuthenticationError(t *testing.T) { calledAttributes = true return expectedAttributes } - fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (authorized bool, reason string, err error) { + fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) { calledAuthorize = true - return false, "", errors.New("Failed") + return authorizer.DecisionNoOpinion, "", errors.New("Failed") } assertHealthFails(t, fw.testHTTPServer.URL+"/healthz", http.StatusInternalServerError) @@ -781,9 +785,9 @@ func TestAuthenticationFailure(t *testing.T) { calledAttributes = true return expectedAttributes } - fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (authorized bool, reason string, err error) { + fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) { calledAuthorize = true - return false, "", nil + return authorizer.DecisionNoOpinion, "", nil } assertHealthFails(t, fw.testHTTPServer.URL+"/healthz", http.StatusUnauthorized) @@ -819,9 +823,9 @@ func TestAuthorizationSuccess(t *testing.T) { calledAttributes = true return expectedAttributes } - fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (authorized bool, reason string, err error) { + fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) { calledAuthorize = true - return true, "", nil + return authorizer.DecisionAllow, "", nil } assertHealthIsOk(t, fw.testHTTPServer.URL+"/healthz") @@ -1635,3 +1639,59 @@ func TestCRIHandler(t *testing.T) { assert.Equal(t, path, fw.criHandler.RequestReceived.URL.Path) assert.Equal(t, query, fw.criHandler.RequestReceived.URL.RawQuery) } + +func TestDebuggingDisabledHandlers(t *testing.T) { + fw := newServerTestWithDebug(false) + defer fw.testHTTPServer.Close() + + paths := []string{ + "/run", "/exec", "/attach", "/portForward", "/containerLogs", "/runningpods", + "/run/", "/exec/", "/attach/", "/portForward/", "/containerLogs/", "/runningpods/", + "/run/xxx", "/exec/xxx", "/attach/xxx", "/debug/pprof/profile", "/logs/kubelet.log", + } + + for _, p := range paths { + resp, err := http.Get(fw.testHTTPServer.URL + p) + require.NoError(t, err) + assert.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode) + body, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + assert.Equal(t, "Debug endpoints are disabled.\n", string(body)) + + resp, err = http.Post(fw.testHTTPServer.URL+p, "", nil) + require.NoError(t, err) + assert.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode) + body, err = ioutil.ReadAll(resp.Body) + require.NoError(t, err) + assert.Equal(t, "Debug endpoints are disabled.\n", string(body)) + } + + // test some other paths, make sure they're working + containerInfo := &cadvisorapi.ContainerInfo{ + ContainerReference: cadvisorapi.ContainerReference{ + Name: "/", + }, + } + fw.fakeKubelet.rawInfoFunc = func(req *cadvisorapi.ContainerInfoRequest) (map[string]*cadvisorapi.ContainerInfo, error) { + return map[string]*cadvisorapi.ContainerInfo{ + containerInfo.Name: containerInfo, + }, nil + } + + resp, err := http.Get(fw.testHTTPServer.URL + "/stats") + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + + machineInfo := &cadvisorapi.MachineInfo{ + NumCores: 4, + MemoryCapacity: 1024, + } + fw.fakeKubelet.machineInfoFunc = func() (*cadvisorapi.MachineInfo, error) { + return machineInfo, nil + } + + resp, err = http.Get(fw.testHTTPServer.URL + "/spec") + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + +} diff --git a/pkg/kubelet/server/stats/BUILD b/pkg/kubelet/server/stats/BUILD index 00140b342bf..d6d400af483 100644 --- a/pkg/kubelet/server/stats/BUILD +++ b/pkg/kubelet/server/stats/BUILD @@ -35,8 +35,8 @@ go_test( "summary_test.go", "volume_stat_calculator_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/server/stats", - library = ":go_default_library", deps = [ "//pkg/kubelet/apis/stats/v1alpha1:go_default_library", "//pkg/kubelet/cm:go_default_library", diff --git a/pkg/kubelet/server/stats/summary_test.go b/pkg/kubelet/server/stats/summary_test.go index 22e38f7f679..422688446f9 100644 --- a/pkg/kubelet/server/stats/summary_test.go +++ b/pkg/kubelet/server/stats/summary_test.go @@ -46,7 +46,7 @@ func TestSummaryProvider(t *testing.T) { node = &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "test-node"}} nodeConfig = cm.NodeConfig{ RuntimeCgroupsName: "/runtime", - SystemCgroupsName: "/system", + SystemCgroupsName: "/misc", KubeletCgroupsName: "/kubelet", } cgroupStatsMap = map[string]struct { @@ -55,7 +55,7 @@ func TestSummaryProvider(t *testing.T) { }{ "/": {cs: getContainerStats(), ns: getNetworkStats()}, "/runtime": {cs: getContainerStats(), ns: getNetworkStats()}, - "/system": {cs: getContainerStats(), ns: getNetworkStats()}, + "/misc": {cs: getContainerStats(), ns: getNetworkStats()}, "/kubelet": {cs: getContainerStats(), ns: getNetworkStats()}, } ) @@ -71,7 +71,7 @@ func TestSummaryProvider(t *testing.T) { On("RootFsStats").Return(rootFsStats, nil). On("GetCgroupStats", "/").Return(cgroupStatsMap["/"].cs, cgroupStatsMap["/"].ns, nil). On("GetCgroupStats", "/runtime").Return(cgroupStatsMap["/runtime"].cs, cgroupStatsMap["/runtime"].ns, nil). - On("GetCgroupStats", "/system").Return(cgroupStatsMap["/system"].cs, cgroupStatsMap["/system"].ns, nil). + On("GetCgroupStats", "/misc").Return(cgroupStatsMap["/misc"].cs, cgroupStatsMap["/misc"].ns, nil). On("GetCgroupStats", "/kubelet").Return(cgroupStatsMap["/kubelet"].cs, cgroupStatsMap["/kubelet"].ns, nil) provider := NewSummaryProvider(mockStatsProvider) @@ -87,29 +87,30 @@ func TestSummaryProvider(t *testing.T) { assert.Equal(summary.Node.Runtime, &statsapi.RuntimeStats{ImageFs: imageFsStats}) assert.Equal(len(summary.Node.SystemContainers), 3) - assert.Contains(summary.Node.SystemContainers, - statsapi.ContainerStats{ - Name: "kubelet", - StartTime: cgroupStatsMap["/kubelet"].cs.StartTime, - CPU: cgroupStatsMap["/kubelet"].cs.CPU, - Memory: cgroupStatsMap["/kubelet"].cs.Memory, - UserDefinedMetrics: cgroupStatsMap["/kubelet"].cs.UserDefinedMetrics, - }, - statsapi.ContainerStats{ - Name: "system", - StartTime: cgroupStatsMap["/system"].cs.StartTime, - CPU: cgroupStatsMap["/system"].cs.CPU, - Memory: cgroupStatsMap["/system"].cs.Memory, - UserDefinedMetrics: cgroupStatsMap["/system"].cs.UserDefinedMetrics, - }, - statsapi.ContainerStats{ - Name: "runtime", - StartTime: cgroupStatsMap["/runtime"].cs.StartTime, - CPU: cgroupStatsMap["/runtime"].cs.CPU, - Memory: cgroupStatsMap["/runtime"].cs.Memory, - UserDefinedMetrics: cgroupStatsMap["/runtime"].cs.UserDefinedMetrics, - }, - ) + assert.Contains(summary.Node.SystemContainers, statsapi.ContainerStats{ + Name: "kubelet", + StartTime: cgroupStatsMap["/kubelet"].cs.StartTime, + CPU: cgroupStatsMap["/kubelet"].cs.CPU, + Memory: cgroupStatsMap["/kubelet"].cs.Memory, + Accelerators: cgroupStatsMap["/kubelet"].cs.Accelerators, + UserDefinedMetrics: cgroupStatsMap["/kubelet"].cs.UserDefinedMetrics, + }) + assert.Contains(summary.Node.SystemContainers, statsapi.ContainerStats{ + Name: "misc", + StartTime: cgroupStatsMap["/misc"].cs.StartTime, + CPU: cgroupStatsMap["/misc"].cs.CPU, + Memory: cgroupStatsMap["/misc"].cs.Memory, + Accelerators: cgroupStatsMap["/misc"].cs.Accelerators, + UserDefinedMetrics: cgroupStatsMap["/misc"].cs.UserDefinedMetrics, + }) + assert.Contains(summary.Node.SystemContainers, statsapi.ContainerStats{ + Name: "runtime", + StartTime: cgroupStatsMap["/runtime"].cs.StartTime, + CPU: cgroupStatsMap["/runtime"].cs.CPU, + Memory: cgroupStatsMap["/runtime"].cs.Memory, + Accelerators: cgroupStatsMap["/runtime"].cs.Accelerators, + UserDefinedMetrics: cgroupStatsMap["/runtime"].cs.UserDefinedMetrics, + }) assert.Equal(summary.Pods, podStats) } diff --git a/pkg/kubelet/server/stats/volume_stat_calculator.go b/pkg/kubelet/server/stats/volume_stat_calculator.go index e151ab83d2d..87355870cc4 100644 --- a/pkg/kubelet/server/stats/volume_stat_calculator.go +++ b/pkg/kubelet/server/stats/volume_stat_calculator.go @@ -42,9 +42,11 @@ type volumeStatCalculator struct { latest atomic.Value } -// PodVolumeStats encapsulates all VolumeStats for a pod +// PodVolumeStats encapsulates the VolumeStats for a pod. +// It consists of two lists, for local ephemeral volumes, and for persistent volumes respectively. type PodVolumeStats struct { - Volumes []stats.VolumeStats + EphemeralVolumes []stats.VolumeStats + PersistentVolumes []stats.VolumeStats } // newVolumeStatCalculator creates a new VolumeStatCalculator @@ -101,7 +103,8 @@ func (s *volumeStatCalculator) calcAndStoreStats() { } // Call GetMetrics on each Volume and copy the result to a new VolumeStats.FsStats - fsStats := make([]stats.VolumeStats, 0, len(volumes)) + ephemeralStats := []stats.VolumeStats{} + persistentStats := []stats.VolumeStats{} for name, v := range volumes { metric, err := v.GetMetrics() if err != nil { @@ -113,31 +116,38 @@ func (s *volumeStatCalculator) calcAndStoreStats() { } // Lookup the volume spec and add a 'PVCReference' for volumes that reference a PVC volSpec := volumesSpec[name] + var pvcRef *stats.PVCReference if pvcSource := volSpec.PersistentVolumeClaim; pvcSource != nil { - pvcRef := stats.PVCReference{ + pvcRef = &stats.PVCReference{ Name: pvcSource.ClaimName, Namespace: s.pod.GetNamespace(), } - fsStats = append(fsStats, s.parsePodVolumeStats(name, &pvcRef, metric)) // Set the PVC's prometheus metrics - s.setPVCMetrics(&pvcRef, metric) - } else { - fsStats = append(fsStats, s.parsePodVolumeStats(name, nil, metric)) + s.setPVCMetrics(pvcRef, metric) } + volumeStats := s.parsePodVolumeStats(name, pvcRef, metric, volSpec) + if isVolumeEphemeral(volSpec) { + ephemeralStats = append(ephemeralStats, volumeStats) + } else { + persistentStats = append(persistentStats, volumeStats) + } + } // Store the new stats - s.latest.Store(PodVolumeStats{Volumes: fsStats}) + s.latest.Store(PodVolumeStats{EphemeralVolumes: ephemeralStats, + PersistentVolumes: persistentStats}) } // parsePodVolumeStats converts (internal) volume.Metrics to (external) stats.VolumeStats structures -func (s *volumeStatCalculator) parsePodVolumeStats(podName string, pvcRef *stats.PVCReference, metric *volume.Metrics) stats.VolumeStats { +func (s *volumeStatCalculator) parsePodVolumeStats(podName string, pvcRef *stats.PVCReference, metric *volume.Metrics, volSpec v1.Volume) stats.VolumeStats { available := uint64(metric.Available.Value()) capacity := uint64(metric.Capacity.Value()) used := uint64(metric.Used.Value()) inodes := uint64(metric.Inodes.Value()) inodesFree := uint64(metric.InodesFree.Value()) inodesUsed := uint64(metric.InodesUsed.Value()) + return stats.VolumeStats{ Name: podName, PVCRef: pvcRef, @@ -146,6 +156,14 @@ func (s *volumeStatCalculator) parsePodVolumeStats(podName string, pvcRef *stats } } +func isVolumeEphemeral(volume v1.Volume) bool { + if (volume.EmptyDir != nil && volume.EmptyDir.Medium == v1.StorageMediumDefault) || + volume.ConfigMap != nil || volume.GitRepo != nil { + return true + } + return false +} + // setPVCMetrics sets the given PVC's prometheus metrics to match the given volume.Metrics func (s *volumeStatCalculator) setPVCMetrics(pvcRef *stats.PVCReference, metric *volume.Metrics) { metrics.VolumeStatsAvailableBytes.WithLabelValues(pvcRef.Namespace, pvcRef.Name).Set(float64(metric.Available.Value())) diff --git a/pkg/kubelet/server/stats/volume_stat_calculator_test.go b/pkg/kubelet/server/stats/volume_stat_calculator_test.go index 42777ae09c8..6b54ea6f9fd 100644 --- a/pkg/kubelet/server/stats/volume_stat_calculator_test.go +++ b/pkg/kubelet/server/stats/volume_stat_calculator_test.go @@ -17,10 +17,11 @@ limitations under the License. package stats import ( - "github.com/stretchr/testify/assert" "testing" "time" + "github.com/stretchr/testify/assert" + k8sv1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -84,14 +85,14 @@ func TestPVCRef(t *testing.T) { statsCalculator.calcAndStoreStats() vs, _ := statsCalculator.GetLatest() - assert.Len(t, vs.Volumes, 2) + assert.Len(t, append(vs.EphemeralVolumes, vs.PersistentVolumes...), 2) // Verify 'vol0' doesn't have a PVC reference - assert.Contains(t, vs.Volumes, kubestats.VolumeStats{ + assert.Contains(t, append(vs.EphemeralVolumes, vs.PersistentVolumes...), kubestats.VolumeStats{ Name: vol0, FsStats: expectedFSStats(), }) // Verify 'vol1' has a PVC reference - assert.Contains(t, vs.Volumes, kubestats.VolumeStats{ + assert.Contains(t, append(vs.EphemeralVolumes, vs.PersistentVolumes...), kubestats.VolumeStats{ Name: vol1, PVCRef: &kubestats.PVCReference{ Name: pvcClaimName, diff --git a/pkg/kubelet/server/streaming/BUILD b/pkg/kubelet/server/streaming/BUILD index f8d34f18566..1f0e2949ec9 100644 --- a/pkg/kubelet/server/streaming/BUILD +++ b/pkg/kubelet/server/streaming/BUILD @@ -34,10 +34,10 @@ go_test( "request_cache_test.go", "server_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/server/streaming", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library", "//pkg/kubelet/server/portforward:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", diff --git a/pkg/kubelet/server/streaming/server.go b/pkg/kubelet/server/streaming/server.go index dd10287743d..ba9f12ff103 100644 --- a/pkg/kubelet/server/streaming/server.go +++ b/pkg/kubelet/server/streaming/server.go @@ -362,11 +362,11 @@ var _ remotecommandserver.Attacher = &criAdapter{} var _ portforward.PortForwarder = &criAdapter{} func (a *criAdapter) ExecInContainer(podName string, podUID types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error { - return a.Exec(container, cmd, in, out, err, tty, resize) + return a.Runtime.Exec(container, cmd, in, out, err, tty, resize) } func (a *criAdapter) AttachContainer(podName string, podUID types.UID, container string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error { - return a.Attach(container, in, out, err, tty, resize) + return a.Runtime.Attach(container, in, out, err, tty, resize) } func (a *criAdapter) PortForward(podName string, podUID types.UID, port int32, stream io.ReadWriteCloser) error { diff --git a/pkg/kubelet/server/streaming/server_test.go b/pkg/kubelet/server/streaming/server_test.go index 835125151f2..77ccc721cd0 100644 --- a/pkg/kubelet/server/streaming/server_test.go +++ b/pkg/kubelet/server/streaming/server_test.go @@ -33,7 +33,7 @@ import ( restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/remotecommand" "k8s.io/client-go/transport/spdy" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" kubeletportforward "k8s.io/kubernetes/pkg/kubelet/server/portforward" ) diff --git a/pkg/kubelet/stats/BUILD b/pkg/kubelet/stats/BUILD index dd180f3d921..e272ed4f00c 100644 --- a/pkg/kubelet/stats/BUILD +++ b/pkg/kubelet/stats/BUILD @@ -15,6 +15,7 @@ go_library( "//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library", "//pkg/kubelet/apis/stats/v1alpha1:go_default_library", "//pkg/kubelet/cadvisor:go_default_library", + "//pkg/kubelet/cm:go_default_library", "//pkg/kubelet/container:go_default_library", "//pkg/kubelet/leaky:go_default_library", "//pkg/kubelet/network:go_default_library", @@ -22,6 +23,7 @@ go_library( "//pkg/kubelet/server/stats:go_default_library", "//pkg/kubelet/types:go_default_library", "//vendor/github.com/golang/glog:go_default_library", + "//vendor/github.com/golang/protobuf/proto:go_default_library", "//vendor/github.com/google/cadvisor/fs:go_default_library", "//vendor/github.com/google/cadvisor/info/v1:go_default_library", "//vendor/github.com/google/cadvisor/info/v2:go_default_library", @@ -52,8 +54,8 @@ go_test( "helper_test.go", "stats_provider_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/stats", - library = ":go_default_library", deps = [ "//pkg/kubelet/apis/cri/testing:go_default_library", "//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library", diff --git a/pkg/kubelet/stats/cadvisor_stats_provider.go b/pkg/kubelet/stats/cadvisor_stats_provider.go index 27538240fdd..17a10df2f6a 100644 --- a/pkg/kubelet/stats/cadvisor_stats_provider.go +++ b/pkg/kubelet/stats/cadvisor_stats_provider.go @@ -18,6 +18,7 @@ package stats import ( "fmt" + "path" "sort" "strings" @@ -28,6 +29,7 @@ import ( "k8s.io/apimachinery/pkg/types" statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1" "k8s.io/kubernetes/pkg/kubelet/cadvisor" + "k8s.io/kubernetes/pkg/kubelet/cm" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/kubelet/leaky" "k8s.io/kubernetes/pkg/kubelet/server/stats" @@ -74,24 +76,13 @@ func (p *cadvisorStatsProvider) ListPodStats() ([]statsapi.PodStats, error) { if err != nil { return nil, fmt.Errorf("failed to get imageFs info: %v", err) } - - infos, err := p.cadvisor.ContainerInfoV2("/", cadvisorapiv2.RequestOptions{ - IdType: cadvisorapiv2.TypeName, - Count: 2, // 2 samples are needed to compute "instantaneous" CPU - Recursive: true, - }) + infos, err := getCadvisorContainerInfo(p.cadvisor) if err != nil { - if _, ok := infos["/"]; ok { - // If the failure is partial, log it and return a best-effort - // response. - glog.Errorf("Partial failure issuing cadvisor.ContainerInfoV2: %v", err) - } else { - return nil, fmt.Errorf("failed to get root cgroup stats: %v", err) - } + return nil, fmt.Errorf("failed to get container info from cadvisor: %v", err) } - + // removeTerminatedContainerInfo will also remove pod level cgroups, so save the infos into allInfos first + allInfos := infos infos = removeTerminatedContainerInfo(infos) - // Map each container to a pod and update the PodStats with container data. podToStats := map[statsapi.PodReference]*statsapi.PodStats{} for key, cinfo := range infos { @@ -106,7 +97,7 @@ func (p *cadvisorStatsProvider) ListPodStats() ([]statsapi.PodStats, error) { if !isPodManagedContainer(&cinfo) { continue } - ref := buildPodRef(&cinfo) + ref := buildPodRef(cinfo.Spec.Labels) // Lookup the PodStats for the pod using the PodRef. If none exists, // initialize a new entry. @@ -134,8 +125,19 @@ func (p *cadvisorStatsProvider) ListPodStats() ([]statsapi.PodStats, error) { for _, podStats := range podToStats { // Lookup the volume stats for each pod. podUID := types.UID(podStats.PodRef.UID) + var ephemeralStats []statsapi.VolumeStats if vstats, found := p.resourceAnalyzer.GetPodVolumeStats(podUID); found { - podStats.VolumeStats = vstats.Volumes + ephemeralStats = make([]statsapi.VolumeStats, len(vstats.EphemeralVolumes)) + copy(ephemeralStats, vstats.EphemeralVolumes) + podStats.VolumeStats = append(vstats.EphemeralVolumes, vstats.PersistentVolumes...) + } + podStats.EphemeralStorage = calcEphemeralStorage(podStats.Containers, ephemeralStats, &rootFsInfo) + // Lookup the pod-level cgroup's CPU and memory stats + podInfo := getcadvisorPodInfoFromPodUID(podUID, allInfos) + if podInfo != nil { + cpu, memory := cadvisorInfoToCPUandMemoryStats(podInfo) + podStats.CPU = cpu + podStats.Memory = memory } result = append(result, *podStats) } @@ -143,6 +145,53 @@ func (p *cadvisorStatsProvider) ListPodStats() ([]statsapi.PodStats, error) { return result, nil } +func calcEphemeralStorage(containers []statsapi.ContainerStats, volumes []statsapi.VolumeStats, rootFsInfo *cadvisorapiv2.FsInfo) *statsapi.FsStats { + result := &statsapi.FsStats{ + Time: metav1.NewTime(rootFsInfo.Timestamp), + AvailableBytes: &rootFsInfo.Available, + CapacityBytes: &rootFsInfo.Capacity, + InodesFree: rootFsInfo.InodesFree, + Inodes: rootFsInfo.Inodes, + } + for _, container := range containers { + addContainerUsage(result, &container) + } + for _, volume := range volumes { + result.UsedBytes = addUsage(result.UsedBytes, volume.FsStats.UsedBytes) + result.InodesUsed = addUsage(result.InodesUsed, volume.InodesUsed) + result.Time = maxUpdateTime(&result.Time, &volume.FsStats.Time) + } + return result +} + +func addContainerUsage(stat *statsapi.FsStats, container *statsapi.ContainerStats) { + if rootFs := container.Rootfs; rootFs != nil { + stat.Time = maxUpdateTime(&stat.Time, &rootFs.Time) + stat.InodesUsed = addUsage(stat.InodesUsed, rootFs.InodesUsed) + stat.UsedBytes = addUsage(stat.UsedBytes, rootFs.UsedBytes) + if logs := container.Logs; logs != nil { + stat.UsedBytes = addUsage(stat.UsedBytes, logs.UsedBytes) + stat.Time = maxUpdateTime(&stat.Time, &logs.Time) + } + } +} + +func maxUpdateTime(first, second *metav1.Time) metav1.Time { + if first.Before(second) { + return *second + } + return *first +} +func addUsage(first, second *uint64) *uint64 { + if first == nil { + return second + } else if second == nil { + return first + } + total := *first + *second + return &total +} + // ImageFsStats returns the stats of the filesystem for storing images. func (p *cadvisorStatsProvider) ImageFsStats() (*statsapi.FsStats, error) { imageFsInfo, err := p.cadvisor.ImagesFsInfo() @@ -172,10 +221,10 @@ func (p *cadvisorStatsProvider) ImageFsStats() (*statsapi.FsStats, error) { } // buildPodRef returns a PodReference that identifies the Pod managing cinfo -func buildPodRef(cinfo *cadvisorapiv2.ContainerInfo) statsapi.PodReference { - podName := kubetypes.GetPodName(cinfo.Spec.Labels) - podNamespace := kubetypes.GetPodNamespace(cinfo.Spec.Labels) - podUID := kubetypes.GetPodUID(cinfo.Spec.Labels) +func buildPodRef(containerLabels map[string]string) statsapi.PodReference { + podName := kubetypes.GetPodName(containerLabels) + podNamespace := kubetypes.GetPodNamespace(containerLabels) + podUID := kubetypes.GetPodUID(containerLabels) return statsapi.PodReference{Name: podName, Namespace: podNamespace, UID: podUID} } @@ -192,6 +241,19 @@ func isPodManagedContainer(cinfo *cadvisorapiv2.ContainerInfo) bool { return managed } +// getcadvisorPodInfoFromPodUID returns a pod cgroup information by matching the podUID with its CgroupName identifier base name +func getcadvisorPodInfoFromPodUID(podUID types.UID, infos map[string]cadvisorapiv2.ContainerInfo) *cadvisorapiv2.ContainerInfo { + for key, info := range infos { + if cm.IsSystemdStyleName(key) { + key = cm.RevertFromSystemdToCgroupStyleName(key) + } + if cm.GetPodCgroupNameSuffix(podUID) == path.Base(key) { + return &info + } + } + return nil +} + // removeTerminatedContainerInfo returns the specified containerInfo but with // the stats of the terminated containers removed. // @@ -204,7 +266,7 @@ func removeTerminatedContainerInfo(containerInfo map[string]cadvisorapiv2.Contai continue } cinfoID := containerID{ - podRef: buildPodRef(&cinfo), + podRef: buildPodRef(cinfo.Spec.Labels), containerName: kubetypes.GetContainerName(cinfo.Spec.Labels), } cinfoMap[cinfoID] = append(cinfoMap[cinfoID], containerInfoWithCgroup{ @@ -279,3 +341,21 @@ func hasMemoryAndCPUInstUsage(info *cadvisorapiv2.ContainerInfo) bool { } return cstat.CpuInst.Usage.Total != 0 && cstat.Memory.RSS != 0 } + +func getCadvisorContainerInfo(ca cadvisor.Interface) (map[string]cadvisorapiv2.ContainerInfo, error) { + infos, err := ca.ContainerInfoV2("/", cadvisorapiv2.RequestOptions{ + IdType: cadvisorapiv2.TypeName, + Count: 2, // 2 samples are needed to compute "instantaneous" CPU + Recursive: true, + }) + if err != nil { + if _, ok := infos["/"]; ok { + // If the failure is partial, log it and return a best-effort + // response. + glog.Errorf("Partial failure issuing cadvisor.ContainerInfoV2: %v", err) + } else { + return nil, fmt.Errorf("failed to get root cgroup stats: %v", err) + } + } + return infos, nil +} diff --git a/pkg/kubelet/stats/cadvisor_stats_provider_test.go b/pkg/kubelet/stats/cadvisor_stats_provider_test.go index bb3df5f4313..2eb9d4a25c9 100644 --- a/pkg/kubelet/stats/cadvisor_stats_provider_test.go +++ b/pkg/kubelet/stats/cadvisor_stats_provider_test.go @@ -27,6 +27,7 @@ import ( kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" "k8s.io/kubernetes/pkg/kubelet/leaky" + serverstats "k8s.io/kubernetes/pkg/kubelet/server/stats" ) func TestRemoveTerminatedContainerInfo(t *testing.T) { @@ -79,17 +80,21 @@ func TestCadvisorListPodStats(t *testing.T) { namespace2 = "test2" ) const ( - seedRoot = 0 - seedRuntime = 100 - seedKubelet = 200 - seedMisc = 300 - seedPod0Infra = 1000 - seedPod0Container0 = 2000 - seedPod0Container1 = 2001 - seedPod1Infra = 3000 - seedPod1Container = 4000 - seedPod2Infra = 5000 - seedPod2Container = 6000 + seedRoot = 0 + seedRuntime = 100 + seedKubelet = 200 + seedMisc = 300 + seedPod0Infra = 1000 + seedPod0Container0 = 2000 + seedPod0Container1 = 2001 + seedPod1Infra = 3000 + seedPod1Container = 4000 + seedPod2Infra = 5000 + seedPod2Container = 6000 + seedEphemeralVolume1 = 10000 + seedEphemeralVolume2 = 10001 + seedPersistentVolume1 = 20000 + seedPersistentVolume2 = 20001 ) const ( pName0 = "pod0" @@ -129,8 +134,10 @@ func TestCadvisorListPodStats(t *testing.T) { "/pod1-i": getTestContainerInfo(seedPod1Infra, pName1, namespace0, leaky.PodInfraContainerName), "/pod1-c0": getTestContainerInfo(seedPod1Container, pName1, namespace0, cName10), // Pod2 - Namespace2 - "/pod2-i": getTestContainerInfo(seedPod2Infra, pName2, namespace2, leaky.PodInfraContainerName), - "/pod2-c0": getTestContainerInfo(seedPod2Container, pName2, namespace2, cName20), + "/pod2-i": getTestContainerInfo(seedPod2Infra, pName2, namespace2, leaky.PodInfraContainerName), + "/pod2-c0": getTestContainerInfo(seedPod2Container, pName2, namespace2, cName20), + "/kubepods/burstable/podUIDpod0": getTestContainerInfo(seedPod0Infra, pName0, namespace0, leaky.PodInfraContainerName), + "/kubepods/podUIDpod1": getTestContainerInfo(seedPod1Infra, pName1, namespace0, leaky.PodInfraContainerName), } freeRootfsInodes := rootfsInodesFree @@ -181,7 +188,16 @@ func TestCadvisorListPodStats(t *testing.T) { mockRuntime. On("ImageStats").Return(&kubecontainer.ImageStats{TotalStorageBytes: 123}, nil) - resourceAnalyzer := &fakeResourceAnalyzer{} + ephemeralVolumes := []statsapi.VolumeStats{getPodVolumeStats(seedEphemeralVolume1, "ephemeralVolume1"), + getPodVolumeStats(seedEphemeralVolume2, "ephemeralVolume2")} + persistentVolumes := []statsapi.VolumeStats{getPodVolumeStats(seedPersistentVolume1, "persistentVolume1"), + getPodVolumeStats(seedPersistentVolume2, "persistentVolume2")} + volumeStats := serverstats.PodVolumeStats{ + EphemeralVolumes: ephemeralVolumes, + PersistentVolumes: persistentVolumes, + } + + resourceAnalyzer := &fakeResourceAnalyzer{podVolumeStats: volumeStats} p := NewCadvisorStatsProvider(mockCadvisor, resourceAnalyzer, nil, nil, mockRuntime) pods, err := p.ListPodStats() @@ -213,6 +229,9 @@ func TestCadvisorListPodStats(t *testing.T) { assert.EqualValues(t, testTime(creationTime, seedPod0Infra).Unix(), ps.StartTime.Time.Unix()) checkNetworkStats(t, "Pod0", seedPod0Infra, ps.Network) + checkEphemeralStats(t, "Pod0", []int{seedPod0Container0, seedPod0Container1}, []int{seedEphemeralVolume1, seedEphemeralVolume2}, ps.EphemeralStorage) + checkCPUStats(t, "Pod0", seedPod0Infra, ps.CPU) + checkMemoryStats(t, "Pod0", seedPod0Infra, infos["/pod0-i"], ps.Memory) // Validate Pod1 Results ps, found = indexPods[prf1] diff --git a/pkg/kubelet/stats/cri_stats_provider.go b/pkg/kubelet/stats/cri_stats_provider.go index f855e0db41a..5934c2962bf 100644 --- a/pkg/kubelet/stats/cri_stats_provider.go +++ b/pkg/kubelet/stats/cri_stats_provider.go @@ -18,9 +18,13 @@ package stats import ( "fmt" + "path" + "sort" + "strings" "time" "github.com/golang/glog" + "github.com/golang/protobuf/proto" cadvisorfs "github.com/google/cadvisor/fs" cadvisorapiv2 "github.com/google/cadvisor/info/v2" @@ -31,6 +35,7 @@ import ( statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1" "k8s.io/kubernetes/pkg/kubelet/cadvisor" "k8s.io/kubernetes/pkg/kubelet/server/stats" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" ) // criStatsProvider implements the containerStatsProvider interface by getting @@ -74,15 +79,10 @@ func (p *criStatsProvider) ListPodStats() ([]statsapi.PodStats, error) { return nil, fmt.Errorf("failed to get rootFs info: %v", err) } - // Creates container map. - containerMap := make(map[string]*runtimeapi.Container) containers, err := p.runtimeService.ListContainers(&runtimeapi.ContainerFilter{}) if err != nil { return nil, fmt.Errorf("failed to list all containers: %v", err) } - for _, c := range containers { - containerMap[c.Id] = c - } // Creates pod sandbox map. podSandboxMap := make(map[string]*runtimeapi.PodSandbox) @@ -106,6 +106,19 @@ func (p *criStatsProvider) ListPodStats() ([]statsapi.PodStats, error) { if err != nil { return nil, fmt.Errorf("failed to list all container stats: %v", err) } + + containers = removeTerminatedContainer(containers) + // Creates container map. + containerMap := make(map[string]*runtimeapi.Container) + for _, c := range containers { + containerMap[c.Id] = c + } + + caInfos, err := getCRICadvisorStats(p.cadvisor) + if err != nil { + return nil, fmt.Errorf("failed to get container info from cadvisor: %v", err) + } + for _, stats := range resp { containerID := stats.Attributes.Id container, found := containerMap[containerID] @@ -125,14 +138,31 @@ func (p *criStatsProvider) ListPodStats() ([]statsapi.PodStats, error) { // container belongs to. ps, found := sandboxIDToPodStats[podSandboxID] if !found { - ps = p.makePodStats(podSandbox) + ps = buildPodStats(podSandbox) + // Fill stats from cadvisor is available for full set of required pod stats + caPodSandbox, found := caInfos[podSandboxID] + if !found { + glog.V(4).Info("Unable to find cadvisor stats for sandbox %q", podSandboxID) + } else { + p.addCadvisorPodStats(ps, &caPodSandbox) + } sandboxIDToPodStats[podSandboxID] = ps } - ps.Containers = append(ps.Containers, *p.makeContainerStats(stats, container, &rootFsInfo, uuidToFsInfo)) + cs := p.makeContainerStats(stats, container, &rootFsInfo, uuidToFsInfo) + // If cadvisor stats is available for the container, use it to populate + // container stats + caStats, caFound := caInfos[containerID] + if !caFound { + glog.V(4).Info("Unable to find cadvisor stats for %q", containerID) + } else { + p.addCadvisorContainerStats(cs, &caStats) + } + ps.Containers = append(ps.Containers, *cs) } result := make([]statsapi.PodStats, 0, len(sandboxIDToPodStats)) for _, s := range sandboxIDToPodStats { + p.makePodStorageStats(s, &rootFsInfo) result = append(result, *s) } return result, nil @@ -193,8 +223,9 @@ func (p *criStatsProvider) getFsInfo(storageID *runtimeapi.StorageIdentifier) *c return &fsInfo } -func (p *criStatsProvider) makePodStats(podSandbox *runtimeapi.PodSandbox) *statsapi.PodStats { - s := &statsapi.PodStats{ +// buildPodStats returns a PodStats that identifies the Pod managing cinfo +func buildPodStats(podSandbox *runtimeapi.PodSandbox) *statsapi.PodStats { + return &statsapi.PodStats{ PodRef: statsapi.PodReference{ Name: podSandbox.Metadata.Name, UID: podSandbox.Metadata.Uid, @@ -202,15 +233,27 @@ func (p *criStatsProvider) makePodStats(podSandbox *runtimeapi.PodSandbox) *stat }, // The StartTime in the summary API is the pod creation time. StartTime: metav1.NewTime(time.Unix(0, podSandbox.CreatedAt)), - // Network stats are not supported by CRI. } +} + +func (p *criStatsProvider) makePodStorageStats(s *statsapi.PodStats, rootFsInfo *cadvisorapiv2.FsInfo) *statsapi.PodStats { podUID := types.UID(s.PodRef.UID) if vstats, found := p.resourceAnalyzer.GetPodVolumeStats(podUID); found { - s.VolumeStats = vstats.Volumes + ephemeralStats := make([]statsapi.VolumeStats, len(vstats.EphemeralVolumes)) + copy(ephemeralStats, vstats.EphemeralVolumes) + s.VolumeStats = append(vstats.EphemeralVolumes, vstats.PersistentVolumes...) + s.EphemeralStorage = calcEphemeralStorage(s.Containers, ephemeralStats, rootFsInfo) } return s } +func (p *criStatsProvider) addCadvisorPodStats( + ps *statsapi.PodStats, + caPodSandbox *cadvisorapiv2.ContainerInfo, +) { + ps.Network = cadvisorInfoToNetworkStats(ps.PodRef.Name, caPodSandbox) +} + func (p *criStatsProvider) makeContainerStats( stats *runtimeapi.ContainerStats, container *runtimeapi.Container, @@ -221,9 +264,15 @@ func (p *criStatsProvider) makeContainerStats( Name: stats.Attributes.Metadata.Name, // The StartTime in the summary API is the container creation time. StartTime: metav1.NewTime(time.Unix(0, container.CreatedAt)), - CPU: &statsapi.CPUStats{}, - Memory: &statsapi.MemoryStats{}, - Rootfs: &statsapi.FsStats{}, + // Work around heapster bug. https://github.com/kubernetes/kubernetes/issues/54962 + // TODO(random-liu): Remove this after heapster is updated to newer than 1.5.0-beta.0. + CPU: &statsapi.CPUStats{ + UsageNanoCores: proto.Uint64(0), + }, + Memory: &statsapi.MemoryStats{ + RSSBytes: proto.Uint64(0), + }, + Rootfs: &statsapi.FsStats{}, Logs: &statsapi.FsStats{ Time: metav1.NewTime(rootFsInfo.Timestamp), AvailableBytes: &rootFsInfo.Available, @@ -279,3 +328,80 @@ func (p *criStatsProvider) makeContainerStats( return result } + +// removeTerminatedContainer returns the specified container but with +// the stats of the terminated containers removed. +func removeTerminatedContainer(containers []*runtimeapi.Container) []*runtimeapi.Container { + containerMap := make(map[containerID][]*runtimeapi.Container) + // Sort order by create time + sort.Slice(containers, func(i, j int) bool { + return containers[i].CreatedAt < containers[j].CreatedAt + }) + for _, container := range containers { + refID := containerID{ + podRef: buildPodRef(container.Labels), + containerName: kubetypes.GetContainerName(container.Labels), + } + containerMap[refID] = append(containerMap[refID], container) + } + + result := make([]*runtimeapi.Container, 0) + for _, refs := range containerMap { + if len(refs) == 1 { + result = append(result, refs[0]) + continue + } + found := false + for i := 0; i < len(refs); i++ { + if refs[i].State == runtimeapi.ContainerState_CONTAINER_RUNNING { + found = true + result = append(result, refs[i]) + } + } + if !found { + result = append(result, refs[len(refs)-1]) + } + } + return result +} + +func (p *criStatsProvider) addCadvisorContainerStats( + cs *statsapi.ContainerStats, + caPodStats *cadvisorapiv2.ContainerInfo, +) { + if caPodStats.Spec.HasCustomMetrics { + cs.UserDefinedMetrics = cadvisorInfoToUserDefinedMetrics(caPodStats) + } + + cpu, memory := cadvisorInfoToCPUandMemoryStats(caPodStats) + if cpu != nil { + cs.CPU = cpu + } + if memory != nil { + cs.Memory = memory + } +} + +func getCRICadvisorStats(ca cadvisor.Interface) (map[string]cadvisorapiv2.ContainerInfo, error) { + stats := make(map[string]cadvisorapiv2.ContainerInfo) + infos, err := getCadvisorContainerInfo(ca) + if err != nil { + return nil, fmt.Errorf("failed to fetch cadvisor stats: %v", err) + } + infos = removeTerminatedContainerInfo(infos) + for key, info := range infos { + // On systemd using devicemapper each mount into the container has an + // associated cgroup. We ignore them to ensure we do not get duplicate + // entries in our summary. For details on .mount units: + // http://man7.org/linux/man-pages/man5/systemd.mount.5.html + if strings.HasSuffix(key, ".mount") { + continue + } + // Build the Pod key if this container is managed by a Pod + if !isPodManagedContainer(&info) { + continue + } + stats[path.Base(key)] = info + } + return stats, nil +} diff --git a/pkg/kubelet/stats/cri_stats_provider_test.go b/pkg/kubelet/stats/cri_stats_provider_test.go index 8e4fdd881cf..5cff0ac7e90 100644 --- a/pkg/kubelet/stats/cri_stats_provider_test.go +++ b/pkg/kubelet/stats/cri_stats_provider_test.go @@ -22,18 +22,47 @@ import ( "time" cadvisorfs "github.com/google/cadvisor/fs" - "github.com/stretchr/testify/assert" - cadvisorapiv2 "github.com/google/cadvisor/info/v2" + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" critest "k8s.io/kubernetes/pkg/kubelet/apis/cri/testing" runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1" cadvisortest "k8s.io/kubernetes/pkg/kubelet/cadvisor/testing" kubecontainertest "k8s.io/kubernetes/pkg/kubelet/container/testing" + "k8s.io/kubernetes/pkg/kubelet/leaky" kubepodtest "k8s.io/kubernetes/pkg/kubelet/pod/testing" + serverstats "k8s.io/kubernetes/pkg/kubelet/server/stats" ) func TestCRIListPodStats(t *testing.T) { + const ( + seedRoot = 0 + seedRuntime = 100 + seedKubelet = 200 + seedMisc = 300 + seedSandbox0 = 1000 + seedContainer0 = 2000 + seedSandbox1 = 3000 + seedContainer1 = 4000 + seedContainer2 = 5000 + seedSandbox2 = 6000 + seedContainer3 = 7000 + ) + + const ( + pName0 = "pod0" + pName1 = "pod1" + pName2 = "pod2" + ) + + const ( + cName0 = "container0-name" + cName1 = "container1-name" + cName2 = "container2-name" + cName3 = "container3-name" + ) + var ( imageFsStorageUUID = "imagefs-storage-uuid" unknownStorageUUID = "unknown-storage-uuid" @@ -41,14 +70,20 @@ func TestCRIListPodStats(t *testing.T) { rootFsInfo = getTestFsInfo(1000) sandbox0 = makeFakePodSandbox("sandbox0-name", "sandbox0-uid", "sandbox0-ns") - container0 = makeFakeContainer(sandbox0, "container0-name") + container0 = makeFakeContainer(sandbox0, cName0, 0, false) containerStats0 = makeFakeContainerStats(container0, imageFsStorageUUID) - container1 = makeFakeContainer(sandbox0, "container1-name") + container1 = makeFakeContainer(sandbox0, cName1, 0, false) containerStats1 = makeFakeContainerStats(container1, unknownStorageUUID) sandbox1 = makeFakePodSandbox("sandbox1-name", "sandbox1-uid", "sandbox1-ns") - container2 = makeFakeContainer(sandbox1, "container2-name") + container2 = makeFakeContainer(sandbox1, cName2, 0, false) containerStats2 = makeFakeContainerStats(container2, imageFsStorageUUID) + + sandbox2 = makeFakePodSandbox("sandbox2-name", "sandbox2-uid", "sandbox2-ns") + container3 = makeFakeContainer(sandbox2, cName3, 0, true) + containerStats3 = makeFakeContainerStats(container3, imageFsStorageUUID) + container4 = makeFakeContainer(sandbox2, cName3, 1, false) + containerStats4 = makeFakeContainerStats(container4, imageFsStorageUUID) ) var ( @@ -60,20 +95,47 @@ func TestCRIListPodStats(t *testing.T) { fakeImageService = critest.NewFakeImageService() ) + infos := map[string]cadvisorapiv2.ContainerInfo{ + "/": getTestContainerInfo(seedRoot, "", "", ""), + "/kubelet": getTestContainerInfo(seedKubelet, "", "", ""), + "/system": getTestContainerInfo(seedMisc, "", "", ""), + sandbox0.PodSandboxStatus.Id: getTestContainerInfo(seedSandbox0, pName0, sandbox0.PodSandboxStatus.Metadata.Namespace, leaky.PodInfraContainerName), + container0.ContainerStatus.Id: getTestContainerInfo(seedContainer0, pName0, sandbox0.PodSandboxStatus.Metadata.Namespace, cName0), + container1.ContainerStatus.Id: getTestContainerInfo(seedContainer1, pName0, sandbox0.PodSandboxStatus.Metadata.Namespace, cName1), + sandbox1.PodSandboxStatus.Id: getTestContainerInfo(seedSandbox1, pName1, sandbox1.PodSandboxStatus.Metadata.Namespace, leaky.PodInfraContainerName), + container2.ContainerStatus.Id: getTestContainerInfo(seedContainer2, pName1, sandbox1.PodSandboxStatus.Metadata.Namespace, cName2), + sandbox2.PodSandboxStatus.Id: getTestContainerInfo(seedSandbox2, pName2, sandbox2.PodSandboxStatus.Metadata.Namespace, leaky.PodInfraContainerName), + container4.ContainerStatus.Id: getTestContainerInfo(seedContainer3, pName2, sandbox2.PodSandboxStatus.Metadata.Namespace, cName3), + } + + options := cadvisorapiv2.RequestOptions{ + IdType: cadvisorapiv2.TypeName, + Count: 2, + Recursive: true, + } + mockCadvisor. + On("ContainerInfoV2", "/", options).Return(infos, nil). On("RootFsInfo").Return(rootFsInfo, nil). On("GetFsInfoByFsUUID", imageFsStorageUUID).Return(imageFsInfo, nil). On("GetFsInfoByFsUUID", unknownStorageUUID).Return(cadvisorapiv2.FsInfo{}, cadvisorfs.ErrNoSuchDevice) fakeRuntimeService.SetFakeSandboxes([]*critest.FakePodSandbox{ - sandbox0, sandbox1, + sandbox0, sandbox1, sandbox2, }) fakeRuntimeService.SetFakeContainers([]*critest.FakeContainer{ - container0, container1, container2, + container0, container1, container2, container3, container4, }) fakeRuntimeService.SetFakeContainerStats([]*runtimeapi.ContainerStats{ - containerStats0, containerStats1, containerStats2, + containerStats0, containerStats1, containerStats2, containerStats3, containerStats4, }) + ephemeralVolumes := makeFakeVolumeStats([]string{"ephVolume1, ephVolumes2"}) + persistentVolumes := makeFakeVolumeStats([]string{"persisVolume1, persisVolumes2"}) + resourceAnalyzer.podVolumeStats = serverstats.PodVolumeStats{ + EphemeralVolumes: ephemeralVolumes, + PersistentVolumes: persistentVolumes, + } + provider := NewCRIStatsProvider( mockCadvisor, resourceAnalyzer, @@ -85,7 +147,7 @@ func TestCRIListPodStats(t *testing.T) { stats, err := provider.ListPodStats() assert := assert.New(t) assert.NoError(err) - assert.Equal(2, len(stats)) + assert.Equal(3, len(stats)) podStatsMap := make(map[statsapi.PodReference]statsapi.PodStats) for _, s := range stats { @@ -96,31 +158,52 @@ func TestCRIListPodStats(t *testing.T) { assert.Equal(sandbox0.CreatedAt, p0.StartTime.UnixNano()) assert.Equal(2, len(p0.Containers)) + checkEphemeralStorageStats(assert, p0, ephemeralVolumes, []*runtimeapi.ContainerStats{containerStats0, containerStats1}) + containerStatsMap := make(map[string]statsapi.ContainerStats) for _, s := range p0.Containers { containerStatsMap[s.Name] = s } - c1 := containerStatsMap["container0-name"] - assert.Equal(container0.CreatedAt, c1.StartTime.UnixNano()) - checkCRICPUAndMemoryStats(assert, c1, containerStats0) - checkCRIRootfsStats(assert, c1, containerStats0, &imageFsInfo) + + c0 := containerStatsMap[cName0] + assert.Equal(container0.CreatedAt, c0.StartTime.UnixNano()) + checkCRICPUAndMemoryStats(assert, c0, infos[container0.ContainerStatus.Id].Stats[0]) + checkCRIRootfsStats(assert, c0, containerStats0, &imageFsInfo) + checkCRILogsStats(assert, c0, &rootFsInfo) + c1 := containerStatsMap[cName1] + assert.Equal(container1.CreatedAt, c1.StartTime.UnixNano()) + checkCRICPUAndMemoryStats(assert, c1, infos[container1.ContainerStatus.Id].Stats[0]) + checkCRIRootfsStats(assert, c1, containerStats1, nil) checkCRILogsStats(assert, c1, &rootFsInfo) - c2 := containerStatsMap["container1-name"] - assert.Equal(container1.CreatedAt, c2.StartTime.UnixNano()) - checkCRICPUAndMemoryStats(assert, c2, containerStats1) - checkCRIRootfsStats(assert, c2, containerStats1, nil) - checkCRILogsStats(assert, c2, &rootFsInfo) + checkCRINetworkStats(assert, p0.Network, infos[sandbox0.PodSandboxStatus.Id].Stats[0].Network) p1 := podStatsMap[statsapi.PodReference{Name: "sandbox1-name", UID: "sandbox1-uid", Namespace: "sandbox1-ns"}] assert.Equal(sandbox1.CreatedAt, p1.StartTime.UnixNano()) assert.Equal(1, len(p1.Containers)) - c3 := p1.Containers[0] - assert.Equal("container2-name", c3.Name) - assert.Equal(container2.CreatedAt, c3.StartTime.UnixNano()) - checkCRICPUAndMemoryStats(assert, c3, containerStats2) - checkCRIRootfsStats(assert, c3, containerStats2, &imageFsInfo) + checkEphemeralStorageStats(assert, p1, ephemeralVolumes, []*runtimeapi.ContainerStats{containerStats2}) + c2 := p1.Containers[0] + assert.Equal(cName2, c2.Name) + assert.Equal(container2.CreatedAt, c2.StartTime.UnixNano()) + checkCRICPUAndMemoryStats(assert, c2, infos[container2.ContainerStatus.Id].Stats[0]) + checkCRIRootfsStats(assert, c2, containerStats2, &imageFsInfo) + checkCRILogsStats(assert, c2, &rootFsInfo) + checkCRINetworkStats(assert, p1.Network, infos[sandbox1.PodSandboxStatus.Id].Stats[0].Network) + + p2 := podStatsMap[statsapi.PodReference{Name: "sandbox2-name", UID: "sandbox2-uid", Namespace: "sandbox2-ns"}] + assert.Equal(sandbox2.CreatedAt, p2.StartTime.UnixNano()) + assert.Equal(1, len(p2.Containers)) + + checkEphemeralStorageStats(assert, p2, ephemeralVolumes, []*runtimeapi.ContainerStats{containerStats4}) + + c3 := p2.Containers[0] + assert.Equal(cName3, c3.Name) + assert.Equal(container4.CreatedAt, c3.StartTime.UnixNano()) + checkCRICPUAndMemoryStats(assert, c3, infos[container4.ContainerStatus.Id].Stats[0]) + checkCRIRootfsStats(assert, c3, containerStats4, &imageFsInfo) + checkCRILogsStats(assert, c3, &rootFsInfo) + checkCRINetworkStats(assert, p2.Network, infos[sandbox2.PodSandboxStatus.Id].Stats[0].Network) mockCadvisor.AssertExpectations(t) } @@ -184,43 +267,59 @@ func makeFakePodSandbox(name, uid, namespace string) *critest.FakePodSandbox { return p } -func makeFakeContainer(sandbox *critest.FakePodSandbox, name string) *critest.FakeContainer { +func makeFakeContainer(sandbox *critest.FakePodSandbox, name string, attempt uint32, terminated bool) *critest.FakeContainer { sandboxID := sandbox.PodSandboxStatus.Id c := &critest.FakeContainer{ SandboxID: sandboxID, ContainerStatus: runtimeapi.ContainerStatus{ - Metadata: &runtimeapi.ContainerMetadata{Name: name}, + Metadata: &runtimeapi.ContainerMetadata{Name: name, Attempt: attempt}, Image: &runtimeapi.ImageSpec{}, ImageRef: "fake-image-ref", CreatedAt: time.Now().UnixNano(), - State: runtimeapi.ContainerState_CONTAINER_RUNNING, }, } + c.ContainerStatus.Labels = map[string]string{ + "io.kubernetes.pod.name": sandbox.Metadata.Name, + "io.kubernetes.pod.uid": sandbox.Metadata.Uid, + "io.kubernetes.pod.namespace": sandbox.Metadata.Namespace, + "io.kubernetes.container.name": name, + } + if terminated { + c.ContainerStatus.State = runtimeapi.ContainerState_CONTAINER_EXITED + } else { + c.ContainerStatus.State = runtimeapi.ContainerState_CONTAINER_RUNNING + } c.ContainerStatus.Id = critest.BuildContainerName(c.ContainerStatus.Metadata, sandboxID) return c } func makeFakeContainerStats(container *critest.FakeContainer, imageFsUUID string) *runtimeapi.ContainerStats { - return &runtimeapi.ContainerStats{ + containerStats := &runtimeapi.ContainerStats{ Attributes: &runtimeapi.ContainerAttributes{ Id: container.ContainerStatus.Id, Metadata: container.ContainerStatus.Metadata, }, - Cpu: &runtimeapi.CpuUsage{ - Timestamp: time.Now().UnixNano(), - UsageCoreNanoSeconds: &runtimeapi.UInt64Value{Value: rand.Uint64()}, - }, - Memory: &runtimeapi.MemoryUsage{ - Timestamp: time.Now().UnixNano(), - WorkingSetBytes: &runtimeapi.UInt64Value{Value: rand.Uint64()}, - }, WritableLayer: &runtimeapi.FilesystemUsage{ Timestamp: time.Now().UnixNano(), StorageId: &runtimeapi.StorageIdentifier{Uuid: imageFsUUID}, - UsedBytes: &runtimeapi.UInt64Value{Value: rand.Uint64()}, - InodesUsed: &runtimeapi.UInt64Value{Value: rand.Uint64()}, + UsedBytes: &runtimeapi.UInt64Value{Value: rand.Uint64() / 100}, + InodesUsed: &runtimeapi.UInt64Value{Value: rand.Uint64() / 100}, }, } + if container.State == runtimeapi.ContainerState_CONTAINER_EXITED { + containerStats.Cpu = nil + containerStats.Memory = nil + } else { + containerStats.Cpu = &runtimeapi.CpuUsage{ + Timestamp: time.Now().UnixNano(), + UsageCoreNanoSeconds: &runtimeapi.UInt64Value{Value: rand.Uint64()}, + } + containerStats.Memory = &runtimeapi.MemoryUsage{ + Timestamp: time.Now().UnixNano(), + WorkingSetBytes: &runtimeapi.UInt64Value{Value: rand.Uint64()}, + } + } + return containerStats } func makeFakeImageFsUsage(fsUUID string) *runtimeapi.FilesystemUsage { @@ -232,18 +331,42 @@ func makeFakeImageFsUsage(fsUUID string) *runtimeapi.FilesystemUsage { } } -func checkCRICPUAndMemoryStats(assert *assert.Assertions, actual statsapi.ContainerStats, cs *runtimeapi.ContainerStats) { - assert.Equal(cs.Cpu.Timestamp, actual.CPU.Time.UnixNano()) - assert.Equal(cs.Cpu.UsageCoreNanoSeconds.Value, *actual.CPU.UsageCoreNanoSeconds) - assert.Nil(actual.CPU.UsageNanoCores) +func makeFakeVolumeStats(volumeNames []string) []statsapi.VolumeStats { + volumes := make([]statsapi.VolumeStats, len(volumeNames)) + availableBytes := rand.Uint64() + capacityBytes := rand.Uint64() + usedBytes := rand.Uint64() / 100 + inodes := rand.Uint64() + inodesFree := rand.Uint64() + inodesUsed := rand.Uint64() / 100 + for i, name := range volumeNames { + fsStats := statsapi.FsStats{ + Time: metav1.NewTime(time.Now()), + AvailableBytes: &availableBytes, + CapacityBytes: &capacityBytes, + UsedBytes: &usedBytes, + Inodes: &inodes, + InodesFree: &inodesFree, + InodesUsed: &inodesUsed, + } + volumes[i] = statsapi.VolumeStats{ + FsStats: fsStats, + Name: name, + } + } + return volumes +} - assert.Equal(cs.Memory.Timestamp, actual.Memory.Time.UnixNano()) - assert.Nil(actual.Memory.AvailableBytes) - assert.Nil(actual.Memory.UsageBytes) - assert.Equal(cs.Memory.WorkingSetBytes.Value, *actual.Memory.WorkingSetBytes) - assert.Nil(actual.Memory.RSSBytes) - assert.Nil(actual.Memory.PageFaults) - assert.Nil(actual.Memory.MajorPageFaults) +func checkCRICPUAndMemoryStats(assert *assert.Assertions, actual statsapi.ContainerStats, cs *cadvisorapiv2.ContainerStats) { + assert.Equal(cs.Timestamp.UnixNano(), actual.CPU.Time.UnixNano()) + assert.Equal(cs.Cpu.Usage.Total, *actual.CPU.UsageCoreNanoSeconds) + assert.Equal(cs.CpuInst.Usage.Total, *actual.CPU.UsageNanoCores) + + assert.Equal(cs.Memory.Usage, *actual.Memory.UsageBytes) + assert.Equal(cs.Memory.WorkingSet, *actual.Memory.WorkingSetBytes) + assert.Equal(cs.Memory.RSS, *actual.Memory.RSSBytes) + assert.Equal(cs.Memory.ContainerData.Pgfault, *actual.Memory.PageFaults) + assert.Equal(cs.Memory.ContainerData.Pgmajfault, *actual.Memory.MajorPageFaults) } func checkCRIRootfsStats(assert *assert.Assertions, actual statsapi.ContainerStats, cs *runtimeapi.ContainerStats, imageFsInfo *cadvisorapiv2.FsInfo) { @@ -272,3 +395,25 @@ func checkCRILogsStats(assert *assert.Assertions, actual statsapi.ContainerStats assert.Nil(actual.Logs.UsedBytes) assert.Nil(actual.Logs.InodesUsed) } + +func checkEphemeralStorageStats(assert *assert.Assertions, actual statsapi.PodStats, volumes []statsapi.VolumeStats, containers []*runtimeapi.ContainerStats) { + var totalUsed, inodesUsed uint64 + for _, container := range containers { + totalUsed = totalUsed + container.WritableLayer.UsedBytes.Value + inodesUsed = inodesUsed + container.WritableLayer.InodesUsed.Value + } + + for _, volume := range volumes { + totalUsed = totalUsed + *volume.FsStats.UsedBytes + inodesUsed = inodesUsed + *volume.FsStats.InodesUsed + } + assert.Equal(int(*actual.EphemeralStorage.UsedBytes), int(totalUsed)) + assert.Equal(int(*actual.EphemeralStorage.InodesUsed), int(inodesUsed)) +} + +func checkCRINetworkStats(assert *assert.Assertions, actual *statsapi.NetworkStats, expected *cadvisorapiv2.NetworkStats) { + assert.Equal(expected.Interfaces[0].RxBytes, *actual.RxBytes) + assert.Equal(expected.Interfaces[0].RxErrors, *actual.RxErrors) + assert.Equal(expected.Interfaces[0].TxBytes, *actual.TxBytes) + assert.Equal(expected.Interfaces[0].TxErrors, *actual.TxErrors) +} diff --git a/pkg/kubelet/stats/helper.go b/pkg/kubelet/stats/helper.go index ae19faa78a0..6856a8c76da 100644 --- a/pkg/kubelet/stats/helper.go +++ b/pkg/kubelet/stats/helper.go @@ -30,21 +30,15 @@ import ( "k8s.io/kubernetes/pkg/kubelet/network" ) -// cadvisorInfoToContainerStats returns the statsapi.ContainerStats converted -// from the container and filesystem info. -func cadvisorInfoToContainerStats(name string, info *cadvisorapiv2.ContainerInfo, rootFs, imageFs *cadvisorapiv2.FsInfo) *statsapi.ContainerStats { - result := &statsapi.ContainerStats{ - StartTime: metav1.NewTime(info.Spec.CreationTime), - Name: name, - } - +func cadvisorInfoToCPUandMemoryStats(info *cadvisorapiv2.ContainerInfo) (*statsapi.CPUStats, *statsapi.MemoryStats) { cstat, found := latestContainerStats(info) if !found { - return result + return nil, nil } - + var cpuStats *statsapi.CPUStats + var memoryStats *statsapi.MemoryStats if info.Spec.HasCpu { - cpuStats := statsapi.CPUStats{ + cpuStats = &statsapi.CPUStats{ Time: metav1.NewTime(cstat.Timestamp), } if cstat.CpuInst != nil { @@ -53,13 +47,11 @@ func cadvisorInfoToContainerStats(name string, info *cadvisorapiv2.ContainerInfo if cstat.Cpu != nil { cpuStats.UsageCoreNanoSeconds = &cstat.Cpu.Usage.Total } - result.CPU = &cpuStats } - if info.Spec.HasMemory { pageFaults := cstat.Memory.ContainerData.Pgfault majorPageFaults := cstat.Memory.ContainerData.Pgmajfault - result.Memory = &statsapi.MemoryStats{ + memoryStats = &statsapi.MemoryStats{ Time: metav1.NewTime(cstat.Timestamp), UsageBytes: &cstat.Memory.Usage, WorkingSetBytes: &cstat.Memory.WorkingSet, @@ -70,35 +62,36 @@ func cadvisorInfoToContainerStats(name string, info *cadvisorapiv2.ContainerInfo // availableBytes = memory limit (if known) - workingset if !isMemoryUnlimited(info.Spec.Memory.Limit) { availableBytes := info.Spec.Memory.Limit - cstat.Memory.WorkingSet - result.Memory.AvailableBytes = &availableBytes + memoryStats.AvailableBytes = &availableBytes } } + return cpuStats, memoryStats +} + +// cadvisorInfoToContainerStats returns the statsapi.ContainerStats converted +// from the container and filesystem info. +func cadvisorInfoToContainerStats(name string, info *cadvisorapiv2.ContainerInfo, rootFs, imageFs *cadvisorapiv2.FsInfo) *statsapi.ContainerStats { + result := &statsapi.ContainerStats{ + StartTime: metav1.NewTime(info.Spec.CreationTime), + Name: name, + } + cstat, found := latestContainerStats(info) + if !found { + return result + } + + cpu, memory := cadvisorInfoToCPUandMemoryStats(info) + result.CPU = cpu + result.Memory = memory if rootFs != nil { // The container logs live on the node rootfs device - result.Logs = &statsapi.FsStats{ - Time: metav1.NewTime(cstat.Timestamp), - AvailableBytes: &rootFs.Available, - CapacityBytes: &rootFs.Capacity, - InodesFree: rootFs.InodesFree, - Inodes: rootFs.Inodes, - } - - if rootFs.Inodes != nil && rootFs.InodesFree != nil { - logsInodesUsed := *rootFs.Inodes - *rootFs.InodesFree - result.Logs.InodesUsed = &logsInodesUsed - } + result.Logs = buildLogsStats(cstat, rootFs) } if imageFs != nil { // The container rootFs lives on the imageFs devices (which may not be the node root fs) - result.Rootfs = &statsapi.FsStats{ - Time: metav1.NewTime(cstat.Timestamp), - AvailableBytes: &imageFs.Available, - CapacityBytes: &imageFs.Capacity, - InodesFree: imageFs.InodesFree, - Inodes: imageFs.Inodes, - } + result.Rootfs = buildRootfsStats(cstat, imageFs) } cfs := cstat.Filesystem @@ -119,6 +112,17 @@ func cadvisorInfoToContainerStats(name string, info *cadvisorapiv2.ContainerInfo } } + for _, acc := range cstat.Accelerators { + result.Accelerators = append(result.Accelerators, statsapi.AcceleratorStats{ + Make: acc.Make, + Model: acc.Model, + ID: acc.ID, + MemoryTotal: acc.MemoryTotal, + MemoryUsed: acc.MemoryUsed, + DutyCycle: acc.DutyCycle, + }) + } + result.UserDefinedMetrics = cadvisorInfoToUserDefinedMetrics(info) return result @@ -134,19 +138,29 @@ func cadvisorInfoToNetworkStats(name string, info *cadvisorapiv2.ContainerInfo) if !found { return nil } - for _, inter := range cstat.Network.Interfaces { - if inter.Name == network.DefaultInterfaceName { - return &statsapi.NetworkStats{ - Time: metav1.NewTime(cstat.Timestamp), - RxBytes: &inter.RxBytes, - RxErrors: &inter.RxErrors, - TxBytes: &inter.TxBytes, - TxErrors: &inter.TxErrors, - } - } + + iStats := statsapi.NetworkStats{ + Time: metav1.NewTime(cstat.Timestamp), } - glog.V(4).Infof("Missing default interface %q for %s", network.DefaultInterfaceName, name) - return nil + + for i := range cstat.Network.Interfaces { + inter := cstat.Network.Interfaces[i] + iStat := statsapi.InterfaceStats{ + Name: inter.Name, + RxBytes: &inter.RxBytes, + RxErrors: &inter.RxErrors, + TxBytes: &inter.TxBytes, + TxErrors: &inter.TxErrors, + } + + if inter.Name == network.DefaultInterfaceName { + iStats.InterfaceStats = iStat + } + + iStats.Interfaces = append(iStats.Interfaces, iStat) + } + + return &iStats } // cadvisorInfoToUserDefinedMetrics returns the statsapi.UserDefinedMetric @@ -253,3 +267,29 @@ func getCgroupStats(cadvisor cadvisor.Interface, containerName string) (*cadviso } return stats, nil } + +func buildLogsStats(cstat *cadvisorapiv2.ContainerStats, rootFs *cadvisorapiv2.FsInfo) *statsapi.FsStats { + fsStats := &statsapi.FsStats{ + Time: metav1.NewTime(cstat.Timestamp), + AvailableBytes: &rootFs.Available, + CapacityBytes: &rootFs.Capacity, + InodesFree: rootFs.InodesFree, + Inodes: rootFs.Inodes, + } + + if rootFs.Inodes != nil && rootFs.InodesFree != nil { + logsInodesUsed := *rootFs.Inodes - *rootFs.InodesFree + fsStats.InodesUsed = &logsInodesUsed + } + return fsStats +} + +func buildRootfsStats(cstat *cadvisorapiv2.ContainerStats, imageFs *cadvisorapiv2.FsInfo) *statsapi.FsStats { + return &statsapi.FsStats{ + Time: metav1.NewTime(cstat.Timestamp), + AvailableBytes: &imageFs.Available, + CapacityBytes: &imageFs.Capacity, + InodesFree: imageFs.InodesFree, + Inodes: imageFs.Inodes, + } +} diff --git a/pkg/kubelet/stats/stats_provider_test.go b/pkg/kubelet/stats/stats_provider_test.go index 8f8bef5e7a7..20616f39025 100644 --- a/pkg/kubelet/stats/stats_provider_test.go +++ b/pkg/kubelet/stats/stats_provider_test.go @@ -59,6 +59,7 @@ const ( offsetFsTotalUsageBytes offsetFsBaseUsageBytes offsetFsInodeUsage + offsetVolume ) var ( @@ -456,6 +457,28 @@ func getTestFsInfo(seed int) cadvisorapiv2.FsInfo { } } +func getPodVolumeStats(seed int, volumeName string) statsapi.VolumeStats { + availableBytes := uint64(seed + offsetFsAvailable) + capacityBytes := uint64(seed + offsetFsCapacity) + usedBytes := uint64(seed + offsetFsUsage) + inodes := uint64(seed + offsetFsInodes) + inodesFree := uint64(seed + offsetFsInodesFree) + inodesUsed := uint64(seed + offsetFsInodeUsage) + fsStats := statsapi.FsStats{ + Time: metav1.NewTime(time.Now()), + AvailableBytes: &availableBytes, + CapacityBytes: &capacityBytes, + UsedBytes: &usedBytes, + Inodes: &inodes, + InodesFree: &inodesFree, + InodesUsed: &inodesUsed, + } + return statsapi.VolumeStats{ + FsStats: fsStats, + Name: volumeName, + } +} + func generateCustomMetricSpec() []cadvisorapiv1.MetricSpec { f := fuzz.New().NilChance(0).Funcs( func(e *cadvisorapiv1.MetricSpec, c fuzz.Continue) { @@ -508,10 +531,26 @@ func testTime(base time.Time, seed int) time.Time { func checkNetworkStats(t *testing.T, label string, seed int, stats *statsapi.NetworkStats) { assert.NotNil(t, stats) assert.EqualValues(t, testTime(timestamp, seed).Unix(), stats.Time.Time.Unix(), label+".Net.Time") + assert.EqualValues(t, "eth0", stats.Name, "default interface name is not eth0") assert.EqualValues(t, seed+offsetNetRxBytes, *stats.RxBytes, label+".Net.RxBytes") assert.EqualValues(t, seed+offsetNetRxErrors, *stats.RxErrors, label+".Net.RxErrors") assert.EqualValues(t, seed+offsetNetTxBytes, *stats.TxBytes, label+".Net.TxBytes") assert.EqualValues(t, seed+offsetNetTxErrors, *stats.TxErrors, label+".Net.TxErrors") + + assert.EqualValues(t, 2, len(stats.Interfaces), "network interfaces should contain 2 elements") + + assert.EqualValues(t, "eth0", stats.Interfaces[0].Name, "default interface name is ont eth0") + assert.EqualValues(t, seed+offsetNetRxBytes, *stats.Interfaces[0].RxBytes, label+".Net.TxErrors") + assert.EqualValues(t, seed+offsetNetRxErrors, *stats.Interfaces[0].RxErrors, label+".Net.TxErrors") + assert.EqualValues(t, seed+offsetNetTxBytes, *stats.Interfaces[0].TxBytes, label+".Net.TxErrors") + assert.EqualValues(t, seed+offsetNetTxErrors, *stats.Interfaces[0].TxErrors, label+".Net.TxErrors") + + assert.EqualValues(t, "cbr0", stats.Interfaces[1].Name, "cbr0 interface name is ont cbr0") + assert.EqualValues(t, 100, *stats.Interfaces[1].RxBytes, label+".Net.TxErrors") + assert.EqualValues(t, 100, *stats.Interfaces[1].RxErrors, label+".Net.TxErrors") + assert.EqualValues(t, 100, *stats.Interfaces[1].TxBytes, label+".Net.TxErrors") + assert.EqualValues(t, 100, *stats.Interfaces[1].TxErrors, label+".Net.TxErrors") + } func checkCPUStats(t *testing.T, label string, seed int, stats *statsapi.CPUStats) { @@ -542,6 +581,20 @@ func checkFsStats(t *testing.T, label string, seed int, stats *statsapi.FsStats) assert.EqualValues(t, seed+offsetFsInodesFree, *stats.InodesFree, label+".InodesFree") } +func checkEphemeralStats(t *testing.T, label string, containerSeeds []int, volumeSeeds []int, stats *statsapi.FsStats) { + var usedBytes, inodeUsage int + for _, cseed := range containerSeeds { + usedBytes = usedBytes + cseed + offsetFsTotalUsageBytes + inodeUsage += cseed + offsetFsInodeUsage + } + for _, vseed := range volumeSeeds { + usedBytes = usedBytes + vseed + offsetFsUsage + inodeUsage += vseed + offsetFsInodeUsage + } + assert.EqualValues(t, usedBytes, int(*stats.UsedBytes), label+".UsedBytes") + assert.EqualValues(t, inodeUsage, int(*stats.InodesUsed), label+".InodesUsed") +} + type fakeResourceAnalyzer struct { podVolumeStats serverstats.PodVolumeStats } diff --git a/pkg/kubelet/status/BUILD b/pkg/kubelet/status/BUILD index 52cc538bcf2..99f5a3ad36c 100644 --- a/pkg/kubelet/status/BUILD +++ b/pkg/kubelet/status/BUILD @@ -37,11 +37,11 @@ go_test( "generate_test.go", "status_manager_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/status", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", "//pkg/api/v1/pod:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/kubelet/configmap:go_default_library", "//pkg/kubelet/container:go_default_library", "//pkg/kubelet/pod:go_default_library", diff --git a/pkg/kubelet/status/status_manager.go b/pkg/kubelet/status/status_manager.go index dafd0a09174..d9a027d544e 100644 --- a/pkg/kubelet/status/status_manager.go +++ b/pkg/kubelet/status/status_manager.go @@ -17,6 +17,7 @@ limitations under the License. package status import ( + "fmt" "sort" "sync" "time" @@ -264,6 +265,32 @@ func (m *manager) TerminatePod(pod *v1.Pod) { m.updateStatusInternal(pod, status, true) } +// checkContainerStateTransition ensures that no container is trying to transition +// from a terminated to non-terminated state, which is illegal and indicates a +// logical error in the kubelet. +func checkContainerStateTransition(oldStatuses, newStatuses []v1.ContainerStatus, restartPolicy v1.RestartPolicy) error { + // If we should always restart, containers are allowed to leave the terminated state + if restartPolicy == v1.RestartPolicyAlways { + return nil + } + for _, oldStatus := range oldStatuses { + // Skip any container that wasn't terminated + if oldStatus.State.Terminated == nil { + continue + } + // Skip any container that failed but is allowed to restart + if oldStatus.State.Terminated.ExitCode != 0 && restartPolicy == v1.RestartPolicyOnFailure { + continue + } + for _, newStatus := range newStatuses { + if oldStatus.Name == newStatus.Name && newStatus.State.Terminated == nil { + return fmt.Errorf("terminated container %v attempted illegal transition to non-terminated state", newStatus.Name) + } + } + } + return nil +} + // updateStatusInternal updates the internal status cache, and queues an update to the api server if // necessary. Returns whether an update was triggered. // This method IS NOT THREAD SAFE and must be called from a locked function. @@ -278,6 +305,16 @@ func (m *manager) updateStatusInternal(pod *v1.Pod, status v1.PodStatus, forceUp oldStatus = pod.Status } + // Check for illegal state transition in containers + if err := checkContainerStateTransition(oldStatus.ContainerStatuses, status.ContainerStatuses, pod.Spec.RestartPolicy); err != nil { + glog.Errorf("Status update on pod %v/%v aborted: %v", pod.Namespace, pod.Name, err) + return false + } + if err := checkContainerStateTransition(oldStatus.InitContainerStatuses, status.InitContainerStatuses, pod.Spec.RestartPolicy); err != nil { + glog.Errorf("Status update on pod %v/%v aborted: %v", pod.Namespace, pod.Name, err) + return false + } + // Set ReadyCondition.LastTransitionTime. if _, readyCondition := podutil.GetPodCondition(&status, v1.PodReady); readyCondition != nil { // Need to set LastTransitionTime. @@ -411,7 +448,7 @@ func (m *manager) syncPod(uid types.UID, status versionedPodStatus) { } // TODO: make me easier to express from client code - pod, err := m.kubeClient.Core().Pods(status.podNamespace).Get(status.podName, metav1.GetOptions{}) + pod, err := m.kubeClient.CoreV1().Pods(status.podNamespace).Get(status.podName, metav1.GetOptions{}) if errors.IsNotFound(err) { glog.V(3).Infof("Pod %q (%s) does not exist on the server", status.podName, uid) // If the Pod is deleted the status will be cleared in @@ -432,7 +469,7 @@ func (m *manager) syncPod(uid types.UID, status versionedPodStatus) { } pod.Status = status.status // TODO: handle conflict as a retry, make that easier too. - newPod, err := m.kubeClient.Core().Pods(pod.Namespace).UpdateStatus(pod) + newPod, err := m.kubeClient.CoreV1().Pods(pod.Namespace).UpdateStatus(pod) if err != nil { glog.Warningf("Failed to update status for pod %q: %v", format.Pod(pod), err) return @@ -447,7 +484,7 @@ func (m *manager) syncPod(uid types.UID, status versionedPodStatus) { deleteOptions := metav1.NewDeleteOptions(0) // Use the pod UID as the precondition for deletion to prevent deleting a newly created pod with the same name and namespace. deleteOptions.Preconditions = metav1.NewUIDPreconditions(string(pod.UID)) - err = m.kubeClient.Core().Pods(pod.Namespace).Delete(pod.Name, deleteOptions) + err = m.kubeClient.CoreV1().Pods(pod.Namespace).Delete(pod.Name, deleteOptions) if err != nil { glog.Warningf("Failed to delete status for pod %q: %v", format.Pod(pod), err) return diff --git a/pkg/kubelet/status/status_manager_test.go b/pkg/kubelet/status/status_manager_test.go index dff67680e03..1d332b221fc 100644 --- a/pkg/kubelet/status/status_manager_test.go +++ b/pkg/kubelet/status/status_manager_test.go @@ -34,8 +34,8 @@ import ( clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" core "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" podutil "k8s.io/kubernetes/pkg/api/v1/pod" + api "k8s.io/kubernetes/pkg/apis/core" kubeconfigmap "k8s.io/kubernetes/pkg/kubelet/configmap" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" kubepod "k8s.io/kubernetes/pkg/kubelet/pod" diff --git a/pkg/kubelet/sysctl/BUILD b/pkg/kubelet/sysctl/BUILD index b5d2a4e8479..d36e7675b50 100644 --- a/pkg/kubelet/sysctl/BUILD +++ b/pkg/kubelet/sysctl/BUILD @@ -15,8 +15,8 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/kubelet/sysctl", deps = [ - "//pkg/api/v1/helper:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/apis/core/v1/helper:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//pkg/apis/extensions/validation:go_default_library", "//pkg/kubelet/container:go_default_library", "//pkg/kubelet/lifecycle:go_default_library", @@ -30,8 +30,8 @@ go_test( "namespace_test.go", "whitelist_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/sysctl", - library = ":go_default_library", deps = ["//vendor/k8s.io/api/core/v1:go_default_library"], ) diff --git a/pkg/kubelet/sysctl/runtime.go b/pkg/kubelet/sysctl/runtime.go index 36d8e1db3ab..cd8e34f7c58 100644 --- a/pkg/kubelet/sysctl/runtime.go +++ b/pkg/kubelet/sysctl/runtime.go @@ -19,7 +19,7 @@ package sysctl import ( "fmt" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/kubelet/lifecycle" ) diff --git a/pkg/kubelet/sysctl/whitelist.go b/pkg/kubelet/sysctl/whitelist.go index d78c7b7d103..9bb1086a558 100644 --- a/pkg/kubelet/sysctl/whitelist.go +++ b/pkg/kubelet/sysctl/whitelist.go @@ -21,8 +21,8 @@ import ( "strings" "k8s.io/api/core/v1" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" - "k8s.io/kubernetes/pkg/api/validation" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" + "k8s.io/kubernetes/pkg/apis/core/validation" extvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation" "k8s.io/kubernetes/pkg/kubelet/lifecycle" ) diff --git a/pkg/kubelet/types/BUILD b/pkg/kubelet/types/BUILD index 42d594fe072..bebe62b0937 100644 --- a/pkg/kubelet/types/BUILD +++ b/pkg/kubelet/types/BUILD @@ -17,7 +17,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/kubelet/types", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", @@ -31,8 +31,8 @@ go_test( "pod_update_test.go", "types_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/types", - library = ":go_default_library", deps = [ "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/github.com/stretchr/testify/require:go_default_library", diff --git a/pkg/kubelet/types/constants.go b/pkg/kubelet/types/constants.go index 65f17c4a7a6..b76b70a7578 100644 --- a/pkg/kubelet/types/constants.go +++ b/pkg/kubelet/types/constants.go @@ -24,4 +24,9 @@ const ( DockerContainerRuntime = "docker" RktContainerRuntime = "rkt" RemoteContainerRuntime = "remote" + + // User visible keys for managing node allocatable enforcement on the node. + NodeAllocatableEnforcementKey = "pods" + SystemReservedEnforcementKey = "system-reserved" + KubeReservedEnforcementKey = "kube-reserved" ) diff --git a/pkg/kubelet/types/pod_update.go b/pkg/kubelet/types/pod_update.go index 2b95b3b1879..6bfc3dac63f 100644 --- a/pkg/kubelet/types/pod_update.go +++ b/pkg/kubelet/types/pod_update.go @@ -21,7 +21,7 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - kubeapi "k8s.io/kubernetes/pkg/api" + kubeapi "k8s.io/kubernetes/pkg/apis/core" ) const ( @@ -49,6 +49,8 @@ const ( // Pods with the given ids have unexpected status in this source, // kubelet should reconcile status with this source RECONCILE + // Pods with the given ids have been restored from a checkpoint. + RESTORE // These constants identify the sources of pods // Updates from a file diff --git a/pkg/kubelet/util/BUILD b/pkg/kubelet/util/BUILD index fdb78bddfba..0d67713211d 100644 --- a/pkg/kubelet/util/BUILD +++ b/pkg/kubelet/util/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["util_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/util", - library = ":go_default_library", deps = ["//vendor/github.com/stretchr/testify/assert:go_default_library"], ) @@ -19,12 +19,38 @@ go_library( srcs = [ "doc.go", "util.go", - "util_unsupported.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "util_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ "util_unix.go", ], - "@io_bazel_rules_go//go/platform:windows_amd64": [ + "@io_bazel_rules_go//go/platform:dragonfly": [ + "util_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "util_unix.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "util_unix.go", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "util_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "util_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "util_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "util_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "util_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ "util_windows.go", ], "//conditions:default": [], @@ -33,7 +59,15 @@ go_library( deps = [ "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:darwin": [ + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:linux": [ "//vendor/github.com/golang/glog:go_default_library", "//vendor/golang.org/x/sys/unix:go_default_library", ], @@ -57,6 +91,7 @@ filegroup( "//pkg/kubelet/util/ioutils:all-srcs", "//pkg/kubelet/util/queue:all-srcs", "//pkg/kubelet/util/sliceutils:all-srcs", + "//pkg/kubelet/util/store:all-srcs", ], tags = ["automanaged"], ) diff --git a/pkg/kubelet/util/cache/BUILD b/pkg/kubelet/util/cache/BUILD index 47910609800..5fc8634d1f9 100644 --- a/pkg/kubelet/util/cache/BUILD +++ b/pkg/kubelet/util/cache/BUILD @@ -16,8 +16,8 @@ go_library( go_test( name = "go_default_test", srcs = ["object_cache_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/util/cache", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", diff --git a/pkg/kubelet/util/format/BUILD b/pkg/kubelet/util/format/BUILD index 887330b96aa..7c008a863cd 100644 --- a/pkg/kubelet/util/format/BUILD +++ b/pkg/kubelet/util/format/BUILD @@ -22,8 +22,8 @@ go_library( go_test( name = "go_default_test", srcs = ["resources_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/util/format", - library = ":go_default_library", deps = [ "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", diff --git a/pkg/kubelet/util/queue/BUILD b/pkg/kubelet/util/queue/BUILD index 0b43effc87a..b241e4687b7 100644 --- a/pkg/kubelet/util/queue/BUILD +++ b/pkg/kubelet/util/queue/BUILD @@ -19,8 +19,8 @@ go_library( go_test( name = "go_default_test", srcs = ["work_queue_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/util/queue", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library", diff --git a/pkg/kubelet/util/sliceutils/BUILD b/pkg/kubelet/util/sliceutils/BUILD index 4134706c80e..04c56005ca8 100644 --- a/pkg/kubelet/util/sliceutils/BUILD +++ b/pkg/kubelet/util/sliceutils/BUILD @@ -32,8 +32,8 @@ filegroup( go_test( name = "go_default_test", srcs = ["sliceutils_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/util/sliceutils", - library = ":go_default_library", deps = [ "//pkg/kubelet/container:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/kubelet/util/store/BUILD b/pkg/kubelet/util/store/BUILD new file mode 100644 index 00000000000..f056a5ae692 --- /dev/null +++ b/pkg/kubelet/util/store/BUILD @@ -0,0 +1,42 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "filestore.go", + "store.go", + ], + importpath = "k8s.io/kubernetes/pkg/kubelet/util/store", + visibility = ["//visibility:public"], + deps = ["//pkg/util/filesystem:go_default_library"], +) + +go_test( + name = "go_default_test", + srcs = [ + "filestore_test.go", + "store_test.go", + ], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/kubelet/util/store", + deps = [ + "//pkg/util/filesystem:go_default_library", + "//vendor/github.com/stretchr/testify/assert:go_default_library", + "//vendor/github.com/stretchr/testify/require:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/kubelet/util/store/doc.go b/pkg/kubelet/util/store/doc.go new file mode 100644 index 00000000000..b4d8523a097 --- /dev/null +++ b/pkg/kubelet/util/store/doc.go @@ -0,0 +1,18 @@ +/* +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 store hosts a Store interface and its implementations. +package store // import "k8s.io/kubernetes/pkg/kubelet/util/store" diff --git a/pkg/kubelet/util/store/filestore.go b/pkg/kubelet/util/store/filestore.go new file mode 100644 index 00000000000..d22226637ba --- /dev/null +++ b/pkg/kubelet/util/store/filestore.go @@ -0,0 +1,167 @@ +/* +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 store + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + utilfs "k8s.io/kubernetes/pkg/util/filesystem" +) + +const ( + // Name prefix for the temporary files. + tmpPrefix = "." +) + +// FileStore is an implementation of the Store interface which stores data in files. +type FileStore struct { + // Absolute path to the base directory for storing data files. + directoryPath string + + // filesystem to use. + filesystem utilfs.Filesystem +} + +// NewFileStore returns an instance of FileStore. +func NewFileStore(path string, fs utilfs.Filesystem) (Store, error) { + if err := ensureDirectory(fs, path); err != nil { + return nil, err + } + return &FileStore{directoryPath: path, filesystem: fs}, nil +} + +// Write writes the given data to a file named key. +func (f *FileStore) Write(key string, data []byte) error { + if err := ValidateKey(key); err != nil { + return err + } + if err := ensureDirectory(f.filesystem, f.directoryPath); err != nil { + return err + } + + return writeFile(f.filesystem, f.getPathByKey(key), data) +} + +// Read reads the data from the file named key. +func (f *FileStore) Read(key string) ([]byte, error) { + if err := ValidateKey(key); err != nil { + return nil, err + } + bytes, err := f.filesystem.ReadFile(f.getPathByKey(key)) + if os.IsNotExist(err) { + return bytes, ErrKeyNotFound + } + return bytes, err +} + +// Delete deletes the key file. +func (f *FileStore) Delete(key string) error { + if err := ValidateKey(key); err != nil { + return err + } + return removePath(f.filesystem, f.getPathByKey(key)) +} + +// List returns all keys in the store. +func (f *FileStore) List() ([]string, error) { + keys := make([]string, 0) + files, err := f.filesystem.ReadDir(f.directoryPath) + if err != nil { + return keys, err + } + for _, f := range files { + if !strings.HasPrefix(f.Name(), tmpPrefix) { + keys = append(keys, f.Name()) + } + } + return keys, nil +} + +// getPathByKey returns the full path of the file for the key. +func (f *FileStore) getPathByKey(key string) string { + return filepath.Join(f.directoryPath, key) +} + +// ensureDirectory creates the directory if it does not exist. +func ensureDirectory(fs utilfs.Filesystem, path string) error { + if _, err := fs.Stat(path); err != nil { + // MkdirAll returns nil if directory already exists. + return fs.MkdirAll(path, 0755) + } + return nil +} + +// writeFile writes data to path in a single transaction. +func writeFile(fs utilfs.Filesystem, path string, data []byte) (retErr error) { + // Create a temporary file in the base directory of `path` with a prefix. + tmpFile, err := fs.TempFile(filepath.Dir(path), tmpPrefix) + if err != nil { + return err + } + + tmpPath := tmpFile.Name() + shouldClose := true + + defer func() { + // Close the file. + if shouldClose { + if err := tmpFile.Close(); err != nil { + if retErr == nil { + retErr = fmt.Errorf("close error: %v", err) + } else { + retErr = fmt.Errorf("failed to close temp file after error %v; close error: %v", retErr, err) + } + } + } + + // Clean up the temp file on error. + if retErr != nil && tmpPath != "" { + if err := removePath(fs, tmpPath); err != nil { + retErr = fmt.Errorf("failed to remove the temporary file (%q) after error %v; remove error: %v", tmpPath, retErr, err) + } + } + }() + + // Write data. + if _, err := tmpFile.Write(data); err != nil { + return err + } + + // Sync file. + if err := tmpFile.Sync(); err != nil { + return err + } + + // Closing the file before renaming. + err = tmpFile.Close() + shouldClose = false + if err != nil { + return err + } + + return fs.Rename(tmpPath, path) +} + +func removePath(fs utilfs.Filesystem, path string) error { + if err := fs.Remove(path); err != nil && !os.IsNotExist(err) { + return err + } + return nil +} diff --git a/pkg/kubelet/util/store/filestore_test.go b/pkg/kubelet/util/store/filestore_test.go new file mode 100644 index 00000000000..8d2352222b2 --- /dev/null +++ b/pkg/kubelet/util/store/filestore_test.go @@ -0,0 +1,126 @@ +/* +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 store + +import ( + "io/ioutil" + "sort" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/kubernetes/pkg/util/filesystem" +) + +func TestFileStore(t *testing.T) { + path, err := ioutil.TempDir("", "FileStore") + assert.NoError(t, err) + store, err := NewFileStore(path, filesystem.DefaultFs{}) + assert.NoError(t, err) + testStore(t, store) +} + +func TestFakeFileStore(t *testing.T) { + store, err := NewFileStore("/tmp/test-fake-file-store", filesystem.NewFakeFs()) + assert.NoError(t, err) + testStore(t, store) +} + +func testStore(t *testing.T, store Store) { + testCases := []struct { + key string + data string + expectErr bool + }{ + { + "id1", + "data1", + false, + }, + { + "id2", + "data2", + false, + }, + { + "/id1", + "data1", + true, + }, + { + ".id1", + "data1", + true, + }, + { + " ", + "data2", + true, + }, + { + "___", + "data2", + true, + }, + } + + // Test add data. + for _, c := range testCases { + t.Log("test case: ", c) + _, err := store.Read(c.key) + assert.Error(t, err) + + err = store.Write(c.key, []byte(c.data)) + if c.expectErr { + assert.Error(t, err) + continue + } + + require.NoError(t, err) + // Test read data by key. + data, err := store.Read(c.key) + require.NoError(t, err) + assert.Equal(t, string(data), c.data) + } + + // Test list keys. + keys, err := store.List() + assert.NoError(t, err) + sort.Strings(keys) + assert.Equal(t, keys, []string{"id1", "id2"}) + + // Test Delete data + for _, c := range testCases { + if c.expectErr { + continue + } + + err = store.Delete(c.key) + require.NoError(t, err) + _, err = store.Read(c.key) + assert.EqualValues(t, ErrKeyNotFound, err) + } + + // Test delete non-existent key. + err = store.Delete("id1") + assert.NoError(t, err) + + // Test list keys. + keys, err = store.List() + require.NoError(t, err) + assert.Equal(t, len(keys), 0) +} diff --git a/pkg/kubelet/util/store/store.go b/pkg/kubelet/util/store/store.go new file mode 100644 index 00000000000..e797fd812b4 --- /dev/null +++ b/pkg/kubelet/util/store/store.go @@ -0,0 +1,64 @@ +/* +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 store + +import ( + "fmt" + "regexp" +) + +const ( + keyMaxLength = 250 + + keyCharFmt string = "[A-Za-z0-9]" + keyExtCharFmt string = "[-A-Za-z0-9_.]" + qualifiedKeyFmt string = "(" + keyCharFmt + keyExtCharFmt + "*)?" + keyCharFmt +) + +var ( + // Key must consist of alphanumeric characters, '-', '_' or '.', and must start + // and end with an alphanumeric character. + keyRegex = regexp.MustCompile("^" + qualifiedKeyFmt + "$") + + // ErrKeyNotFound is the error returned if key is not found in Store. + ErrKeyNotFound = fmt.Errorf("key is not found") +) + +// Store provides the interface for storing keyed data. +// Store must be thread-safe +type Store interface { + // key must contain one or more characters in [A-Za-z0-9] + // Write writes data with key. + Write(key string, data []byte) error + // Read retrieves data with key + // Read must return ErrKeyNotFound if key is not found. + Read(key string) ([]byte, error) + // Delete deletes data by key + // Delete must not return error if key does not exist + Delete(key string) error + // List lists all existing keys. + List() ([]string, error) +} + +// ValidateKey returns an error if the given key does not meet the requirement +// of the key format and length. +func ValidateKey(key string) error { + if len(key) <= keyMaxLength && keyRegex.MatchString(key) { + return nil + } + return fmt.Errorf("invalid key: %q", key) +} diff --git a/pkg/kubelet/util/store/store_test.go b/pkg/kubelet/util/store/store_test.go new file mode 100644 index 00000000000..a9c6e9c14b7 --- /dev/null +++ b/pkg/kubelet/util/store/store_test.go @@ -0,0 +1,63 @@ +/* +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 store + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsValidKey(t *testing.T) { + testcases := []struct { + key string + valid bool + }{ + { + " ", + false, + }, + { + "/foo/bar", + false, + }, + { + ".foo", + false, + }, + { + "a78768279290d33d0b82eaea43cb8346f500057cb5bd250e88c97a5585385d66", + true, + }, + { + "a7.87-6_8", + true, + }, + { + "a7.87-677-", + false, + }, + } + + for _, tc := range testcases { + if tc.valid { + assert.NoError(t, ValidateKey(tc.key)) + } else { + assert.Error(t, ValidateKey(tc.key)) + } + } +} diff --git a/pkg/kubelet/util/util_unix.go b/pkg/kubelet/util/util_unix.go index c0c70759974..0b8fea9c84a 100644 --- a/pkg/kubelet/util/util_unix.go +++ b/pkg/kubelet/util/util_unix.go @@ -1,4 +1,4 @@ -// +build freebsd linux +// +build freebsd linux darwin /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/kubelet/util/util_unsupported.go b/pkg/kubelet/util/util_unsupported.go index ffc4d642d70..5fd2e9c6896 100644 --- a/pkg/kubelet/util/util_unsupported.go +++ b/pkg/kubelet/util/util_unsupported.go @@ -1,4 +1,4 @@ -// +build !freebsd,!linux,!windows +// +build !freebsd,!linux,!windows,!darwin /* Copyright 2017 The Kubernetes Authors. diff --git a/pkg/kubelet/volume_host.go b/pkg/kubelet/volume_host.go index 26323b7d848..de71e3c4b0f 100644 --- a/pkg/kubelet/volume_host.go +++ b/pkg/kubelet/volume_host.go @@ -19,12 +19,19 @@ package kubelet import ( "fmt" "net" + "runtime" + + "github.com/golang/glog" "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + utilfeature "k8s.io/apiserver/pkg/util/feature" clientset "k8s.io/client-go/kubernetes" "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/configmap" + "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/mountpod" "k8s.io/kubernetes/pkg/kubelet/secret" "k8s.io/kubernetes/pkg/util/io" "k8s.io/kubernetes/pkg/util/mount" @@ -43,11 +50,17 @@ func NewInitializedVolumePluginMgr( configMapManager configmap.Manager, plugins []volume.VolumePlugin, prober volume.DynamicPluginProber) (*volume.VolumePluginMgr, error) { + + mountPodManager, err := mountpod.NewManager(kubelet.getRootDir(), kubelet.podManager) + if err != nil { + return nil, err + } kvh := &kubeletVolumeHost{ kubelet: kubelet, volumePluginMgr: volume.VolumePluginMgr{}, secretManager: secretManager, configMapManager: configMapManager, + mountPodManager: mountPodManager, } if err := kvh.volumePluginMgr.InitPlugins(plugins, prober, kvh); err != nil { @@ -71,10 +84,23 @@ type kubeletVolumeHost struct { volumePluginMgr volume.VolumePluginMgr secretManager secret.Manager configMapManager configmap.Manager + mountPodManager mountpod.Manager +} + +func (kvh *kubeletVolumeHost) GetVolumeDevicePluginDir(pluginName string) string { + return kvh.kubelet.getVolumeDevicePluginDir(pluginName) } func (kvh *kubeletVolumeHost) GetPodVolumeDir(podUID types.UID, pluginName string, volumeName string) string { - return kvh.kubelet.getPodVolumeDir(podUID, pluginName, volumeName) + dir := kvh.kubelet.getPodVolumeDir(podUID, pluginName, volumeName) + if runtime.GOOS == "windows" { + dir = volume.GetWindowsPath(dir) + } + return dir +} + +func (kvh *kubeletVolumeHost) GetPodVolumeDeviceDir(podUID types.UID, pluginName string) string { + return kvh.kubelet.getPodVolumeDeviceDir(podUID, pluginName) } func (kvh *kubeletVolumeHost) GetPodPluginDir(podUID types.UID, pluginName string) string { @@ -119,7 +145,16 @@ func (kvh *kubeletVolumeHost) GetCloudProvider() cloudprovider.Interface { } func (kvh *kubeletVolumeHost) GetMounter(pluginName string) mount.Interface { - return kvh.kubelet.mounter + exec, err := kvh.getMountExec(pluginName) + if err != nil { + glog.V(2).Info("Error finding mount pod for plugin %s: %s", pluginName, err.Error()) + // Use the default mounter + exec = nil + } + if exec == nil { + return kvh.kubelet.mounter + } + return mount.NewExecMounter(exec, kvh.kubelet.mounter) } func (kvh *kubeletVolumeHost) GetWriter() io.Writer { @@ -158,6 +193,61 @@ func (kvh *kubeletVolumeHost) GetNodeLabels() (map[string]string, error) { return node.Labels, nil } -func (kvh *kubeletVolumeHost) GetExec(pluginName string) mount.Exec { - return mount.NewOsExec() +func (kvh *kubeletVolumeHost) GetNodeName() types.NodeName { + return kvh.kubelet.nodeName +} + +func (kvh *kubeletVolumeHost) GetExec(pluginName string) mount.Exec { + exec, err := kvh.getMountExec(pluginName) + if err != nil { + glog.V(2).Info("Error finding mount pod for plugin %s: %s", pluginName, err.Error()) + // Use the default exec + exec = nil + } + if exec == nil { + return mount.NewOsExec() + } + return exec +} + +// getMountExec returns mount.Exec implementation that leads to pod with mount +// utilities. It returns nil,nil when there is no such pod and default mounter / +// os.Exec should be used. +func (kvh *kubeletVolumeHost) getMountExec(pluginName string) (mount.Exec, error) { + if !utilfeature.DefaultFeatureGate.Enabled(features.MountContainers) { + glog.V(5).Infof("using default mounter/exec for %s", pluginName) + return nil, nil + } + + pod, container, err := kvh.mountPodManager.GetMountPod(pluginName) + if err != nil { + return nil, err + } + if pod == nil { + // Use default mounter/exec for this plugin + glog.V(5).Infof("using default mounter/exec for %s", pluginName) + return nil, nil + } + glog.V(5).Infof("using container %s/%s/%s to execute mount utilites for %s", pod.Namespace, pod.Name, container, pluginName) + return &containerExec{ + pod: pod, + containerName: container, + kl: kvh.kubelet, + }, nil +} + +// containerExec is implementation of mount.Exec that executes commands in given +// container in given pod. +type containerExec struct { + pod *v1.Pod + containerName string + kl *Kubelet +} + +var _ mount.Exec = &containerExec{} + +func (e *containerExec) Run(cmd string, args ...string) ([]byte, error) { + cmdline := append([]string{cmd}, args...) + glog.V(5).Infof("Exec mounter running in pod %s/%s/%s: %v", e.pod.Namespace, e.pod.Name, e.containerName, cmdline) + return e.kl.RunInContainer(container.GetPodFullName(e.pod), e.pod.UID, e.containerName, cmdline) } diff --git a/pkg/kubelet/volumemanager/BUILD b/pkg/kubelet/volumemanager/BUILD index 3d2ed85c425..5393d2becfc 100644 --- a/pkg/kubelet/volumemanager/BUILD +++ b/pkg/kubelet/volumemanager/BUILD @@ -21,6 +21,7 @@ go_library( "//pkg/kubelet/volumemanager/reconciler:go_default_library", "//pkg/util/mount:go_default_library", "//pkg/volume:go_default_library", + "//pkg/volume/util:go_default_library", "//pkg/volume/util/operationexecutor:go_default_library", "//pkg/volume/util/types:go_default_library", "//pkg/volume/util/volumehelper:go_default_library", @@ -38,8 +39,8 @@ go_library( go_test( name = "go_default_test", srcs = ["volume_manager_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/volumemanager", - library = ":go_default_library", deps = [ "//pkg/kubelet/config:go_default_library", "//pkg/kubelet/configmap:go_default_library", diff --git a/pkg/kubelet/volumemanager/OWNERS b/pkg/kubelet/volumemanager/OWNERS index 72077ea4cf7..0f0dc72d717 100644 --- a/pkg/kubelet/volumemanager/OWNERS +++ b/pkg/kubelet/volumemanager/OWNERS @@ -6,3 +6,5 @@ reviewers: - rootfs - jingxu97 - msau42 +- verult +- davidz627 diff --git a/pkg/kubelet/volumemanager/cache/BUILD b/pkg/kubelet/volumemanager/cache/BUILD index cf5e351aa3b..dc2899147a5 100644 --- a/pkg/kubelet/volumemanager/cache/BUILD +++ b/pkg/kubelet/volumemanager/cache/BUILD @@ -30,8 +30,8 @@ go_test( "actual_state_of_world_test.go", "desired_state_of_world_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/volumemanager/cache", - library = ":go_default_library", deps = [ "//pkg/volume:go_default_library", "//pkg/volume/testing:go_default_library", diff --git a/pkg/kubelet/volumemanager/cache/actual_state_of_world.go b/pkg/kubelet/volumemanager/cache/actual_state_of_world.go index ff3b8925ca3..c22049e7bca 100644 --- a/pkg/kubelet/volumemanager/cache/actual_state_of_world.go +++ b/pkg/kubelet/volumemanager/cache/actual_state_of_world.go @@ -58,7 +58,7 @@ type ActualStateOfWorld interface { // volume, reset the pod's remountRequired value. // If a volume with the name volumeName does not exist in the list of // attached volumes, an error is returned. - AddPodToVolume(podName volumetypes.UniquePodName, podUID types.UID, volumeName v1.UniqueVolumeName, mounter volume.Mounter, outerVolumeSpecName string, volumeGidValue string) error + AddPodToVolume(podName volumetypes.UniquePodName, podUID types.UID, volumeName v1.UniqueVolumeName, mounter volume.Mounter, blockVolumeMapper volume.BlockVolumeMapper, outerVolumeSpecName string, volumeGidValue string) error // MarkRemountRequired marks each volume that is successfully attached and // mounted for the specified pod as requiring remount (if the plugin for the @@ -254,6 +254,9 @@ type mountedPod struct { // mounter used to mount mounter volume.Mounter + // mapper used to block volumes support + blockVolumeMapper volume.BlockVolumeMapper + // outerVolumeSpecName is the volume.Spec.Name() of the volume as referenced // directly in the pod. If the volume was referenced through a persistent // volume claim, this contains the volume.Spec.Name() of the persistent @@ -287,6 +290,7 @@ func (asw *actualStateOfWorld) MarkVolumeAsMounted( podUID types.UID, volumeName v1.UniqueVolumeName, mounter volume.Mounter, + blockVolumeMapper volume.BlockVolumeMapper, outerVolumeSpecName string, volumeGidValue string) error { return asw.AddPodToVolume( @@ -294,6 +298,7 @@ func (asw *actualStateOfWorld) MarkVolumeAsMounted( podUID, volumeName, mounter, + blockVolumeMapper, outerVolumeSpecName, volumeGidValue) } @@ -385,6 +390,7 @@ func (asw *actualStateOfWorld) AddPodToVolume( podUID types.UID, volumeName v1.UniqueVolumeName, mounter volume.Mounter, + blockVolumeMapper volume.BlockVolumeMapper, outerVolumeSpecName string, volumeGidValue string) error { asw.Lock() @@ -403,6 +409,7 @@ func (asw *actualStateOfWorld) AddPodToVolume( podName: podName, podUID: podUID, mounter: mounter, + blockVolumeMapper: blockVolumeMapper, outerVolumeSpecName: outerVolumeSpecName, volumeGidValue: volumeGidValue, } @@ -682,5 +689,7 @@ func getMountedVolume( PluginName: attachedVolume.pluginName, PodUID: mountedPod.podUID, Mounter: mountedPod.mounter, - VolumeGidValue: mountedPod.volumeGidValue}} + BlockVolumeMapper: mountedPod.blockVolumeMapper, + VolumeGidValue: mountedPod.volumeGidValue, + VolumeSpec: attachedVolume.spec}} } diff --git a/pkg/kubelet/volumemanager/cache/actual_state_of_world_test.go b/pkg/kubelet/volumemanager/cache/actual_state_of_world_test.go index 376be8461f2..e2c51812d44 100644 --- a/pkg/kubelet/volumemanager/cache/actual_state_of_world_test.go +++ b/pkg/kubelet/volumemanager/cache/actual_state_of_world_test.go @@ -204,9 +204,14 @@ func Test_AddPodToVolume_Positive_ExistingVolumeNewNode(t *testing.T) { t.Fatalf("NewMounter failed. Expected: Actual: <%v>", err) } + mapper, err := plugin.NewBlockVolumeMapper(volumeSpec, pod, volume.VolumeOptions{}) + if err != nil { + t.Fatalf("NewBlockVolumeMapper failed. Expected: Actual: <%v>", err) + } + // Act err = asw.AddPodToVolume( - podName, pod.UID, generatedVolumeName, mounter, volumeSpec.Name(), "" /* volumeGidValue */) + podName, pod.UID, generatedVolumeName, mounter, mapper, volumeSpec.Name(), "" /* volumeGidValue */) // Assert if err != nil { @@ -263,15 +268,20 @@ func Test_AddPodToVolume_Positive_ExistingVolumeExistingNode(t *testing.T) { t.Fatalf("NewMounter failed. Expected: Actual: <%v>", err) } + mapper, err := plugin.NewBlockVolumeMapper(volumeSpec, pod, volume.VolumeOptions{}) + if err != nil { + t.Fatalf("NewBlockVolumeMapper failed. Expected: Actual: <%v>", err) + } + err = asw.AddPodToVolume( - podName, pod.UID, generatedVolumeName, mounter, volumeSpec.Name(), "" /* volumeGidValue */) + podName, pod.UID, generatedVolumeName, mounter, mapper, volumeSpec.Name(), "" /* volumeGidValue */) if err != nil { t.Fatalf("AddPodToVolume failed. Expected: Actual: <%v>", err) } // Act err = asw.AddPodToVolume( - podName, pod.UID, generatedVolumeName, mounter, volumeSpec.Name(), "" /* volumeGidValue */) + podName, pod.UID, generatedVolumeName, mounter, mapper, volumeSpec.Name(), "" /* volumeGidValue */) // Assert if err != nil { @@ -318,6 +328,15 @@ func Test_AddPodToVolume_Negative_VolumeDoesntExist(t *testing.T) { volumeSpec, err) } + + blockplugin, err := volumePluginMgr.FindMapperPluginBySpec(volumeSpec) + if err != nil { + t.Fatalf( + "volumePluginMgr.FindMapperPluginBySpec failed to find volume plugin for %#v with: %v", + volumeSpec, + err) + } + volumeName, err := volumehelper.GetUniqueVolumeNameFromSpec( plugin, volumeSpec) @@ -328,9 +347,14 @@ func Test_AddPodToVolume_Negative_VolumeDoesntExist(t *testing.T) { t.Fatalf("NewMounter failed. Expected: Actual: <%v>", err) } + mapper, err := blockplugin.NewBlockVolumeMapper(volumeSpec, pod, volume.VolumeOptions{}) + if err != nil { + t.Fatalf("NewBlockVolumeMapper failed. Expected: Actual: <%v>", err) + } + // Act err = asw.AddPodToVolume( - podName, pod.UID, volumeName, mounter, volumeSpec.Name(), "" /* volumeGidValue */) + podName, pod.UID, volumeName, mounter, mapper, volumeSpec.Name(), "" /* volumeGidValue */) // Assert if err == nil { diff --git a/pkg/kubelet/volumemanager/populator/BUILD b/pkg/kubelet/volumemanager/populator/BUILD index 4de5e457a7d..78639a6f749 100644 --- a/pkg/kubelet/volumemanager/populator/BUILD +++ b/pkg/kubelet/volumemanager/populator/BUILD @@ -11,6 +11,7 @@ go_library( srcs = ["desired_state_of_world_populator.go"], importpath = "k8s.io/kubernetes/pkg/kubelet/volumemanager/populator", deps = [ + "//pkg/features:go_default_library", "//pkg/kubelet/config:go_default_library", "//pkg/kubelet/container:go_default_library", "//pkg/kubelet/pod:go_default_library", @@ -25,6 +26,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", ], ) @@ -45,8 +47,8 @@ filegroup( go_test( name = "go_default_test", srcs = ["desired_state_of_world_populator_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/volumemanager/populator", - library = ":go_default_library", deps = [ "//pkg/kubelet/configmap:go_default_library", "//pkg/kubelet/container/testing:go_default_library", @@ -61,6 +63,9 @@ go_test( "//pkg/volume/util/volumehelper:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", + "//vendor/k8s.io/client-go/testing:go_default_library", ], ) diff --git a/pkg/kubelet/volumemanager/populator/desired_state_of_world_populator.go b/pkg/kubelet/volumemanager/populator/desired_state_of_world_populator.go index d4a13c58a8e..f696becd3c4 100644 --- a/pkg/kubelet/volumemanager/populator/desired_state_of_world_populator.go +++ b/pkg/kubelet/volumemanager/populator/desired_state_of_world_populator.go @@ -31,7 +31,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" + utilfeature "k8s.io/apiserver/pkg/util/feature" clientset "k8s.io/client-go/kubernetes" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/config" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/kubelet/pod" @@ -260,11 +262,12 @@ func (dswp *desiredStateOfWorldPopulator) processPodVolumes(pod *v1.Pod) { } allVolumesAdded := true + mountsMap, devicesMap := dswp.makeVolumeMap(pod.Spec.Containers) // Process volume spec for each volume defined in pod for _, podVolume := range pod.Spec.Volumes { volumeSpec, volumeGidValue, err := - dswp.createVolumeSpec(podVolume, pod.Namespace) + dswp.createVolumeSpec(podVolume, pod.Name, pod.Namespace, mountsMap, devicesMap) if err != nil { glog.Errorf( "Error processing volume %q for pod %q: %v", @@ -336,7 +339,7 @@ func (dswp *desiredStateOfWorldPopulator) deleteProcessedPod( // specified volume. It dereference any PVC to get PV objects, if needed. // Returns an error if unable to obtain the volume at this time. func (dswp *desiredStateOfWorldPopulator) createVolumeSpec( - podVolume v1.Volume, podNamespace string) (*volume.Spec, string, error) { + podVolume v1.Volume, podName string, podNamespace string, mountsMap map[string]bool, devicesMap map[string]bool) (*volume.Spec, string, error) { if pvcSource := podVolume.VolumeSource.PersistentVolumeClaim; pvcSource != nil { glog.V(10).Infof( @@ -381,6 +384,31 @@ func (dswp *desiredStateOfWorldPopulator) createVolumeSpec( pvcSource.ClaimName, pvcUID) + // TODO: remove feature gate check after no longer needed + if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + volumeMode, err := volumehelper.GetVolumeMode(volumeSpec) + if err != nil { + return nil, "", err + } + // Error if a container has volumeMounts but the volumeMode of PVC isn't Filesystem + if mountsMap[podVolume.Name] && volumeMode != v1.PersistentVolumeFilesystem { + return nil, "", fmt.Errorf( + "Volume %q has volumeMode %q, but is specified in volumeMounts for pod %q/%q", + podVolume.Name, + volumeMode, + podNamespace, + podName) + } + // Error if a container has volumeDevices but the volumeMode of PVC isn't Block + if devicesMap[podVolume.Name] && volumeMode != v1.PersistentVolumeBlock { + return nil, "", fmt.Errorf( + "Volume %q has volumeMode %q, but is specified in volumeDevices for pod %q/%q", + podVolume.Name, + volumeMode, + podNamespace, + podName) + } + } return volumeSpec, volumeGidValue, nil } @@ -391,12 +419,13 @@ func (dswp *desiredStateOfWorldPopulator) createVolumeSpec( } // getPVCExtractPV fetches the PVC object with the given namespace and name from -// the API server extracts the name of the PV it is pointing to and returns it. +// the API server, checks whether PVC is being deleted, extracts the name of the PV +// it is pointing to and returns it. // An error is returned if the PVC object's phase is not "Bound". func (dswp *desiredStateOfWorldPopulator) getPVCExtractPV( namespace string, claimName string) (string, types.UID, error) { pvc, err := - dswp.kubeClient.Core().PersistentVolumeClaims(namespace).Get(claimName, metav1.GetOptions{}) + dswp.kubeClient.CoreV1().PersistentVolumeClaims(namespace).Get(claimName, metav1.GetOptions{}) if err != nil || pvc == nil { return "", "", fmt.Errorf( "failed to fetch PVC %s/%s from API server. err=%v", @@ -405,6 +434,23 @@ func (dswp *desiredStateOfWorldPopulator) getPVCExtractPV( err) } + if utilfeature.DefaultFeatureGate.Enabled(features.PVCProtection) { + // Pods that uses a PVC that is being deleted must not be started. + // + // In case an old kubelet is running without this check or some kubelets + // have this feature disabled, the worst that can happen is that such + // pod is scheduled. This was the default behavior in 1.8 and earlier + // and users should not be that surprised. + // It should happen only in very rare case when scheduler schedules + // a pod and user deletes a PVC that's used by it at the same time. + if pvc.ObjectMeta.DeletionTimestamp != nil { + return "", "", fmt.Errorf( + "can't start pod because PVC %s/%s is being deleted", + namespace, + claimName) + } + } + if pvc.Status.Phase != v1.ClaimBound || pvc.Spec.VolumeName == "" { return "", "", fmt.Errorf( @@ -425,7 +471,7 @@ func (dswp *desiredStateOfWorldPopulator) getPVSpec( name string, pvcReadOnly bool, expectedClaimUID types.UID) (*volume.Spec, string, error) { - pv, err := dswp.kubeClient.Core().PersistentVolumes().Get(name, metav1.GetOptions{}) + pv, err := dswp.kubeClient.CoreV1().PersistentVolumes().Get(name, metav1.GetOptions{}) if err != nil || pv == nil { return nil, "", fmt.Errorf( "failed to fetch PV %q from API server. err=%v", name, err) @@ -449,6 +495,28 @@ func (dswp *desiredStateOfWorldPopulator) getPVSpec( return volume.NewSpecFromPersistentVolume(pv, pvcReadOnly), volumeGidValue, nil } +func (dswp *desiredStateOfWorldPopulator) makeVolumeMap(containers []v1.Container) (map[string]bool, map[string]bool) { + volumeDevicesMap := make(map[string]bool) + volumeMountsMap := make(map[string]bool) + + for _, container := range containers { + if container.VolumeMounts != nil { + for _, mount := range container.VolumeMounts { + volumeMountsMap[mount.Name] = true + } + } + // TODO: remove feature gate check after no longer needed + if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) && + container.VolumeDevices != nil { + for _, device := range container.VolumeDevices { + volumeDevicesMap[device.Name] = true + } + } + } + + return volumeMountsMap, volumeDevicesMap +} + func getPVVolumeGidAnnotationValue(pv *v1.PersistentVolume) string { if volumeGid, ok := pv.Annotations[volumehelper.VolumeGidAnnotationKey]; ok { return volumeGid 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 94a65f2b41f..e9f27af810b 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 @@ -22,7 +22,10 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/kubernetes/fake" + core "k8s.io/client-go/testing" "k8s.io/kubernetes/pkg/kubelet/configmap" containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" kubepod "k8s.io/kubernetes/pkg/kubelet/pod" @@ -37,54 +40,37 @@ import ( ) func TestFindAndAddNewPods_FindAndRemoveDeletedPods(t *testing.T) { - fakeVolumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t) - fakeClient := &fake.Clientset{} - - fakeSecretManager := secret.NewFakeManager() - fakeConfigMapManager := configmap.NewFakeManager() - fakePodManager := kubepod.NewBasicPodManager( - podtest.NewFakeMirrorClient(), fakeSecretManager, fakeConfigMapManager) - - fakesDSW := cache.NewDesiredStateOfWorld(fakeVolumePluginMgr) - fakeRuntime := &containertest.FakeRuntime{} - - fakeStatusManager := status.NewManager(fakeClient, fakePodManager, &statustest.FakePodDeletionSafetyProvider{}) - - dswp := &desiredStateOfWorldPopulator{ - kubeClient: fakeClient, - loopSleepDuration: 100 * time.Millisecond, - getPodStatusRetryDuration: 2 * time.Second, - podManager: fakePodManager, - podStatusProvider: fakeStatusManager, - desiredStateOfWorld: fakesDSW, - pods: processedPods{ - processedPods: make(map[types.UniquePodName]bool)}, - kubeContainerRuntime: fakeRuntime, - keepTerminatedPodVolumes: false, - } - - pod := &v1.Pod{ + // create dswp + pv := &v1.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ - Name: "dswp-test-pod", - UID: "dswp-test-pod-uid", - Namespace: "dswp-test", + Name: "dswp-test-volume-name", }, - Spec: v1.PodSpec{ - Volumes: []v1.Volume{ + Spec: v1.PersistentVolumeSpec{ + ClaimRef: &v1.ObjectReference{Namespace: "ns", Name: "file-bound"}, + }, + } + pvc := &v1.PersistentVolumeClaim{ + Spec: v1.PersistentVolumeClaimSpec{ + VolumeName: "dswp-test-volume-name", + }, + Status: v1.PersistentVolumeClaimStatus{ + Phase: v1.ClaimBound, + }, + } + dswp, fakePodManager, fakesDSW := createDswpWithVolume(t, pv, pvc) + + // create pod + containers := []v1.Container{ + { + VolumeMounts: []v1.VolumeMount{ { - Name: "dswp-test-volume-name", - VolumeSource: v1.VolumeSource{ - GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{ - PDName: "dswp-test-fake-device", - }, - }, + Name: "dswp-test-volume-name", + MountPath: "/mnt", }, }, }, - Status: v1.PodStatus{ - Phase: v1.PodPhase("Running"), - }, } + pod := createPodWithVolume("dswp-test-pod", "dswp-test-volume-name", "file-bound", containers) fakePodManager.AddPod(pod) @@ -158,6 +144,320 @@ func TestFindAndAddNewPods_FindAndRemoveDeletedPods(t *testing.T) { } +func TestFindAndAddNewPods_FindAndRemoveDeletedPods_Valid_Block_VolumeDevices(t *testing.T) { + // Enable BlockVolume feature gate + utilfeature.DefaultFeatureGate.Set("BlockVolume=true") + + // create dswp + mode := v1.PersistentVolumeBlock + pv := &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dswp-test-volume-name", + }, + Spec: v1.PersistentVolumeSpec{ + ClaimRef: &v1.ObjectReference{Namespace: "ns", Name: "block-bound"}, + VolumeMode: &mode, + }, + } + pvc := &v1.PersistentVolumeClaim{ + Spec: v1.PersistentVolumeClaimSpec{ + VolumeName: "dswp-test-volume-name", + }, + Status: v1.PersistentVolumeClaimStatus{ + Phase: v1.ClaimBound, + }, + } + dswp, fakePodManager, fakesDSW := createDswpWithVolume(t, pv, pvc) + + // create pod + containers := []v1.Container{ + { + VolumeDevices: []v1.VolumeDevice{ + { + Name: "dswp-test-volume-name", + DevicePath: "/dev/sdb", + }, + }, + }, + } + pod := createPodWithVolume("dswp-test-pod", "dswp-test-volume-name", "block-bound", containers) + + fakePodManager.AddPod(pod) + + podName := volumehelper.GetUniquePodName(pod) + + generatedVolumeName := "fake-plugin/" + pod.Spec.Volumes[0].Name + + dswp.findAndAddNewPods() + + if !dswp.pods.processedPods[podName] { + t.Fatalf("Failed to record that the volumes for the specified pod: %s have been processed by the populator", podName) + } + + expectedVolumeName := v1.UniqueVolumeName(generatedVolumeName) + + volumeExists := fakesDSW.VolumeExists(expectedVolumeName) + if !volumeExists { + t.Fatalf( + "VolumeExists(%q) failed. Expected: Actual: <%v>", + expectedVolumeName, + volumeExists) + } + + if podExistsInVolume := fakesDSW.PodExistsInVolume( + podName, expectedVolumeName); !podExistsInVolume { + t.Fatalf( + "DSW PodExistsInVolume returned incorrect value. Expected: Actual: <%v>", + podExistsInVolume) + } + + verifyVolumeExistsInVolumesToMount( + t, v1.UniqueVolumeName(generatedVolumeName), false /* expectReportedInUse */, fakesDSW) + + //let the pod be terminated + podGet, exist := fakePodManager.GetPodByName(pod.Namespace, pod.Name) + if !exist { + t.Fatalf("Failed to get pod by pod name: %s and namespace: %s", pod.Name, pod.Namespace) + } + podGet.Status.Phase = v1.PodFailed + + //pod is added to fakePodManager but fakeRuntime can not get the pod,so here findAndRemoveDeletedPods() will remove the pod and volumes it is mounted + dswp.findAndRemoveDeletedPods() + + if dswp.pods.processedPods[podName] { + t.Fatalf("Failed to remove pods from desired state of world since they no longer exist") + } + + volumeExists = fakesDSW.VolumeExists(expectedVolumeName) + if volumeExists { + t.Fatalf( + "VolumeExists(%q) failed. Expected: Actual: <%v>", + expectedVolumeName, + volumeExists) + } + + if podExistsInVolume := fakesDSW.PodExistsInVolume( + podName, expectedVolumeName); podExistsInVolume { + t.Fatalf( + "DSW PodExistsInVolume returned incorrect value. Expected: Actual: <%v>", + podExistsInVolume) + } + + volumesToMount := fakesDSW.GetVolumesToMount() + for _, volume := range volumesToMount { + if volume.VolumeName == expectedVolumeName { + t.Fatalf( + "Found volume %v in the list of desired state of world volumes to mount. Expected not", + expectedVolumeName) + } + } + + // Rollback feature gate to false. + utilfeature.DefaultFeatureGate.Set("BlockVolume=false") +} + +func TestCreateVolumeSpec_Valid_File_VolumeMounts(t *testing.T) { + // create dswp + mode := v1.PersistentVolumeFilesystem + pv := &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dswp-test-volume-name", + }, + Spec: v1.PersistentVolumeSpec{ + ClaimRef: &v1.ObjectReference{Namespace: "ns", Name: "file-bound"}, + VolumeMode: &mode, + }, + } + pvc := &v1.PersistentVolumeClaim{ + Spec: v1.PersistentVolumeClaimSpec{ + VolumeName: "dswp-test-volume-name", + }, + Status: v1.PersistentVolumeClaimStatus{ + Phase: v1.ClaimBound, + }, + } + dswp, fakePodManager, _ := createDswpWithVolume(t, pv, pvc) + + // create pod + containers := []v1.Container{ + { + VolumeMounts: []v1.VolumeMount{ + { + Name: "dswp-test-volume-name", + MountPath: "/mnt", + }, + }, + }, + } + pod := createPodWithVolume("dswp-test-pod", "dswp-test-volume-name", "file-bound", containers) + + fakePodManager.AddPod(pod) + mountsMap, devicesMap := dswp.makeVolumeMap(pod.Spec.Containers) + volumeSpec, _, err := + dswp.createVolumeSpec(pod.Spec.Volumes[0], pod.Name, pod.Namespace, mountsMap, devicesMap) + + // Assert + if volumeSpec == nil || err != nil { + t.Fatalf("Failed to create volumeSpec with combination of filesystem mode and volumeMounts. err: %v", err) + } +} + +func TestCreateVolumeSpec_Valid_Block_VolumeDevices(t *testing.T) { + // Enable BlockVolume feature gate + utilfeature.DefaultFeatureGate.Set("BlockVolume=true") + + // create dswp + mode := v1.PersistentVolumeBlock + pv := &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dswp-test-volume-name", + }, + Spec: v1.PersistentVolumeSpec{ + ClaimRef: &v1.ObjectReference{Namespace: "ns", Name: "block-bound"}, + VolumeMode: &mode, + }, + } + pvc := &v1.PersistentVolumeClaim{ + Spec: v1.PersistentVolumeClaimSpec{ + VolumeName: "dswp-test-volume-name", + }, + Status: v1.PersistentVolumeClaimStatus{ + Phase: v1.ClaimBound, + }, + } + dswp, fakePodManager, _ := createDswpWithVolume(t, pv, pvc) + + // create pod + containers := []v1.Container{ + { + VolumeDevices: []v1.VolumeDevice{ + { + Name: "dswp-test-volume-name", + DevicePath: "/dev/sdb", + }, + }, + }, + } + pod := createPodWithVolume("dswp-test-pod", "dswp-test-volume-name", "block-bound", containers) + + fakePodManager.AddPod(pod) + mountsMap, devicesMap := dswp.makeVolumeMap(pod.Spec.Containers) + volumeSpec, _, err := + dswp.createVolumeSpec(pod.Spec.Volumes[0], pod.Name, pod.Namespace, mountsMap, devicesMap) + + // Assert + if volumeSpec == nil || err != nil { + t.Fatalf("Failed to create volumeSpec with combination of block mode and volumeDevices. err: %v", err) + } + + // Rollback feature gate to false. + utilfeature.DefaultFeatureGate.Set("BlockVolume=false") +} + +func TestCreateVolumeSpec_Invalid_File_VolumeDevices(t *testing.T) { + // Enable BlockVolume feature gate + utilfeature.DefaultFeatureGate.Set("BlockVolume=true") + + // create dswp + mode := v1.PersistentVolumeFilesystem + pv := &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dswp-test-volume-name", + }, + Spec: v1.PersistentVolumeSpec{ + ClaimRef: &v1.ObjectReference{Namespace: "ns", Name: "file-bound"}, + VolumeMode: &mode, + }, + } + pvc := &v1.PersistentVolumeClaim{ + Spec: v1.PersistentVolumeClaimSpec{ + VolumeName: "dswp-test-volume-name", + }, + Status: v1.PersistentVolumeClaimStatus{ + Phase: v1.ClaimBound, + }, + } + dswp, fakePodManager, _ := createDswpWithVolume(t, pv, pvc) + + // create pod + containers := []v1.Container{ + { + VolumeDevices: []v1.VolumeDevice{ + { + Name: "dswp-test-volume-name", + DevicePath: "/dev/sdb", + }, + }, + }, + } + pod := createPodWithVolume("dswp-test-pod", "dswp-test-volume-name", "file-bound", containers) + + fakePodManager.AddPod(pod) + mountsMap, devicesMap := dswp.makeVolumeMap(pod.Spec.Containers) + volumeSpec, _, err := + dswp.createVolumeSpec(pod.Spec.Volumes[0], pod.Name, pod.Namespace, mountsMap, devicesMap) + + // Assert + if volumeSpec != nil || err == nil { + t.Fatalf("Unexpected volumeMode and volumeMounts/volumeDevices combination is accepted") + } + + // Rollback feature gate to false. + utilfeature.DefaultFeatureGate.Set("BlockVolume=false") +} + +func TestCreateVolumeSpec_Invalid_Block_VolumeMounts(t *testing.T) { + // Enable BlockVolume feature gate + utilfeature.DefaultFeatureGate.Set("BlockVolume=true") + + // create dswp + mode := v1.PersistentVolumeBlock + pv := &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dswp-test-volume-name", + }, + Spec: v1.PersistentVolumeSpec{ + ClaimRef: &v1.ObjectReference{Namespace: "ns", Name: "block-bound"}, + VolumeMode: &mode, + }, + } + pvc := &v1.PersistentVolumeClaim{ + Spec: v1.PersistentVolumeClaimSpec{ + VolumeName: "dswp-test-volume-name", + }, + Status: v1.PersistentVolumeClaimStatus{ + Phase: v1.ClaimBound, + }, + } + dswp, fakePodManager, _ := createDswpWithVolume(t, pv, pvc) + + // create pod + containers := []v1.Container{ + { + VolumeMounts: []v1.VolumeMount{ + { + Name: "dswp-test-volume-name", + MountPath: "/mnt", + }, + }, + }, + } + pod := createPodWithVolume("dswp-test-pod", "dswp-test-volume-name", "block-bound", containers) + + fakePodManager.AddPod(pod) + mountsMap, devicesMap := dswp.makeVolumeMap(pod.Spec.Containers) + volumeSpec, _, err := + dswp.createVolumeSpec(pod.Spec.Volumes[0], pod.Name, pod.Namespace, mountsMap, devicesMap) + + // Assert + if volumeSpec != nil || err == nil { + t.Fatalf("Unexpected volumeMode and volumeMounts/volumeDevices combination is accepted") + } + + // Rollback feature gate to false. + utilfeature.DefaultFeatureGate.Set("BlockVolume=false") +} + func verifyVolumeExistsInVolumesToMount(t *testing.T, expectedVolumeName v1.UniqueVolumeName, expectReportedInUse bool, dsw cache.DesiredStateOfWorld) { volumesToMount := dsw.GetVolumesToMount() for _, volume := range volumesToMount { @@ -179,3 +479,67 @@ func verifyVolumeExistsInVolumesToMount(t *testing.T, expectedVolumeName v1.Uniq expectedVolumeName, volumesToMount) } + +func createPodWithVolume(pod, pv, pvc string, containers []v1.Container) *v1.Pod { + return &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: pod, + UID: "dswp-test-pod-uid", + Namespace: "dswp-test", + }, + Spec: v1.PodSpec{ + Volumes: []v1.Volume{ + { + Name: pv, + VolumeSource: v1.VolumeSource{ + GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{ + PDName: "dswp-test-fake-device", + }, + PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ + ClaimName: pvc, + }, + }, + }, + }, + Containers: containers, + }, + Status: v1.PodStatus{ + Phase: v1.PodPhase("Running"), + }, + } +} + +func createDswpWithVolume(t *testing.T, pv *v1.PersistentVolume, pvc *v1.PersistentVolumeClaim) (*desiredStateOfWorldPopulator, kubepod.Manager, cache.DesiredStateOfWorld) { + fakeVolumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t) + fakeClient := &fake.Clientset{} + fakeClient.AddReactor("get", "persistentvolumeclaims", func(action core.Action) (bool, runtime.Object, error) { + return true, pvc, nil + }) + fakeClient.AddReactor("get", "persistentvolumes", func(action core.Action) (bool, runtime.Object, error) { + return true, pv, nil + }) + + fakeSecretManager := secret.NewFakeManager() + fakeConfigMapManager := configmap.NewFakeManager() + fakePodManager := kubepod.NewBasicPodManager( + podtest.NewFakeMirrorClient(), fakeSecretManager, fakeConfigMapManager) + + fakesDSW := cache.NewDesiredStateOfWorld(fakeVolumePluginMgr) + fakeRuntime := &containertest.FakeRuntime{} + + fakeStatusManager := status.NewManager(fakeClient, fakePodManager, &statustest.FakePodDeletionSafetyProvider{}) + + dswp := &desiredStateOfWorldPopulator{ + kubeClient: fakeClient, + loopSleepDuration: 100 * time.Millisecond, + getPodStatusRetryDuration: 2 * time.Second, + podManager: fakePodManager, + podStatusProvider: fakeStatusManager, + desiredStateOfWorld: fakesDSW, + pods: processedPods{ + processedPods: make(map[types.UniquePodName]bool)}, + kubeContainerRuntime: fakeRuntime, + keepTerminatedPodVolumes: false, + } + return dswp, fakePodManager, fakesDSW +} diff --git a/pkg/kubelet/volumemanager/reconciler/BUILD b/pkg/kubelet/volumemanager/reconciler/BUILD index 5164a1fe8ba..23b243dd796 100644 --- a/pkg/kubelet/volumemanager/reconciler/BUILD +++ b/pkg/kubelet/volumemanager/reconciler/BUILD @@ -11,6 +11,7 @@ go_library( srcs = ["reconciler.go"], importpath = "k8s.io/kubernetes/pkg/kubelet/volumemanager/reconciler", deps = [ + "//pkg/features:go_default_library", "//pkg/kubelet/config:go_default_library", "//pkg/kubelet/volumemanager/cache:go_default_library", "//pkg/util/file:go_default_library", @@ -27,6 +28,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", ], ) @@ -34,8 +36,8 @@ go_library( go_test( name = "go_default_test", srcs = ["reconciler_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/volumemanager/reconciler", - library = ":go_default_library", deps = [ "//pkg/kubelet/volumemanager/cache:go_default_library", "//pkg/util/mount:go_default_library", @@ -45,10 +47,12 @@ go_test( "//pkg/volume/util/volumehelper:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", "//vendor/k8s.io/client-go/testing:go_default_library", "//vendor/k8s.io/client-go/tools/record:go_default_library", diff --git a/pkg/kubelet/volumemanager/reconciler/reconciler.go b/pkg/kubelet/volumemanager/reconciler/reconciler.go index 81642fb6a29..2cb07477c74 100644 --- a/pkg/kubelet/volumemanager/reconciler/reconciler.go +++ b/pkg/kubelet/volumemanager/reconciler/reconciler.go @@ -22,6 +22,7 @@ package reconciler import ( "fmt" "io/ioutil" + "os" "path" "time" @@ -30,13 +31,15 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" + utilfeature "k8s.io/apiserver/pkg/util/feature" clientset "k8s.io/client-go/kubernetes" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/config" "k8s.io/kubernetes/pkg/kubelet/volumemanager/cache" utilfile "k8s.io/kubernetes/pkg/util/file" "k8s.io/kubernetes/pkg/util/goroutinemap/exponentialbackoff" "k8s.io/kubernetes/pkg/util/mount" - "k8s.io/kubernetes/pkg/util/strings" + utilstrings "k8s.io/kubernetes/pkg/util/strings" volumepkg "k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume/util/nestedpendingoperations" "k8s.io/kubernetes/pkg/volume/util/operationexecutor" @@ -171,10 +174,12 @@ func (rc *reconciler) reconcile() { // Ensure volumes that should be unmounted are unmounted. for _, mountedVolume := range rc.actualStateOfWorld.GetMountedVolumes() { if !rc.desiredStateOfWorld.PodExistsInVolume(mountedVolume.PodName, mountedVolume.VolumeName) { - // Volume is mounted, unmount it - glog.V(12).Infof(mountedVolume.GenerateMsgDetailed("Starting operationExecutor.UnmountVolume", "")) - err := rc.operationExecutor.UnmountVolume( - mountedVolume.MountedVolume, rc.actualStateOfWorld) + volumeHandler, err := operationexecutor.NewVolumeHandler(mountedVolume.VolumeSpec, rc.operationExecutor) + if err != nil { + glog.Errorf(mountedVolume.GenerateErrorDetailed(fmt.Sprintf("operationExecutor.NewVolumeHandler for UnmountVolume failed"), err).Error()) + continue + } + err = volumeHandler.UnmountVolumeHandler(mountedVolume.MountedVolume, rc.actualStateOfWorld) if err != nil && !nestedpendingoperations.IsAlreadyExists(err) && !exponentialbackoff.IsExponentialBackoff(err) { @@ -239,12 +244,12 @@ func (rc *reconciler) reconcile() { if isRemount { remountingLogStr = "Volume is already mounted to pod, but remount was requested." } - glog.V(12).Infof(volumeToMount.GenerateMsgDetailed("Starting operationExecutor.MountVolume", remountingLogStr)) - err := rc.operationExecutor.MountVolume( - rc.waitForAttachTimeout, - volumeToMount.VolumeToMount, - rc.actualStateOfWorld, - isRemount) + volumeHandler, err := operationexecutor.NewVolumeHandler(volumeToMount.VolumeSpec, rc.operationExecutor) + if err != nil { + glog.Errorf(volumeToMount.GenerateErrorDetailed(fmt.Sprintf("operationExecutor.NewVolumeHandler for MountVolume failed"), err).Error()) + continue + } + err = volumeHandler.MountVolumeHandler(rc.waitForAttachTimeout, volumeToMount.VolumeToMount, rc.actualStateOfWorld, isRemount, remountingLogStr) if err != nil && !nestedpendingoperations.IsAlreadyExists(err) && !exponentialbackoff.IsExponentialBackoff(err) { @@ -268,10 +273,12 @@ func (rc *reconciler) reconcile() { if !rc.desiredStateOfWorld.VolumeExists(attachedVolume.VolumeName) && !rc.operationExecutor.IsOperationPending(attachedVolume.VolumeName, nestedpendingoperations.EmptyUniquePodName) { if attachedVolume.GloballyMounted { - // Volume is globally mounted to device, unmount it - glog.V(12).Infof(attachedVolume.GenerateMsgDetailed("Starting operationExecutor.UnmountDevice", "")) - err := rc.operationExecutor.UnmountDevice( - attachedVolume.AttachedVolume, rc.actualStateOfWorld, rc.mounter) + volumeHandler, err := operationexecutor.NewVolumeHandler(attachedVolume.VolumeSpec, rc.operationExecutor) + if err != nil { + glog.Errorf(attachedVolume.GenerateErrorDetailed(fmt.Sprintf("operationExecutor.NewVolumeHandler for UnmountDevice failed"), err).Error()) + continue + } + err = volumeHandler.UnmountDeviceHandler(attachedVolume.AttachedVolume, rc.actualStateOfWorld, rc.mounter) if err != nil && !nestedpendingoperations.IsAlreadyExists(err) && !exponentialbackoff.IsExponentialBackoff(err) { @@ -332,6 +339,7 @@ type podVolume struct { volumeSpecName string mountPath string pluginName string + volumeMode v1.PersistentVolumeMode } type reconstructedVolume struct { @@ -345,6 +353,7 @@ type reconstructedVolume struct { devicePath string reportedInUse bool mounter volumepkg.Mounter + blockVolumeMapper volumepkg.BlockVolumeMapper } // reconstructFromDisk scans the volume directories under the given pod directory. If the volume is not @@ -419,20 +428,44 @@ func (rc *reconciler) syncStates(podsDir string) { // Reconstruct Volume object and reconstructedVolume data structure by reading the pod's volume directories func (rc *reconciler) reconstructVolume(volume podVolume) (*reconstructedVolume, error) { + // plugin initializations plugin, err := rc.volumePluginMgr.FindPluginByName(volume.pluginName) if err != nil { return nil, err } - volumeSpec, err := plugin.ConstructVolumeSpec(volume.volumeSpecName, volume.mountPath) + attachablePlugin, err := rc.volumePluginMgr.FindAttachablePluginByName(volume.pluginName) if err != nil { return nil, err } + + // Create volumeSpec pod := &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ UID: types.UID(volume.podName), }, } - attachablePlugin, err := rc.volumePluginMgr.FindAttachablePluginByName(volume.pluginName) + // TODO: remove feature gate check after no longer needed + var mapperPlugin volumepkg.BlockVolumePlugin + tmpSpec := &volumepkg.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{}}} + if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + mapperPlugin, err = rc.volumePluginMgr.FindMapperPluginByName(volume.pluginName) + if err != nil { + return nil, err + } + tmpSpec = &volumepkg.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{VolumeMode: &volume.volumeMode}}} + } + volumeHandler, err := operationexecutor.NewVolumeHandler(tmpSpec, rc.operationExecutor) + if err != nil { + return nil, err + } + volumeSpec, err := volumeHandler.ReconstructVolumeHandler( + plugin, + mapperPlugin, + pod.UID, + volume.podName, + volume.volumeSpecName, + volume.mountPath, + volume.pluginName) if err != nil { return nil, err } @@ -448,17 +481,14 @@ func (rc *reconciler) reconstructVolume(volume podVolume) (*reconstructedVolume, uniqueVolumeName = volumehelper.GetUniqueVolumeNameForNonAttachableVolume(volume.podName, plugin, volumeSpec) } - if attachablePlugin != nil { - if isNotMount, mountCheckErr := rc.mounter.IsLikelyNotMountPoint(volume.mountPath); mountCheckErr != nil { - return nil, fmt.Errorf("Could not check whether the volume %q (spec.Name: %q) pod %q (UID: %q) is mounted with: %v", - uniqueVolumeName, - volumeSpec.Name(), - volume.podName, - pod.UID, - mountCheckErr) - } else if isNotMount { - return nil, fmt.Errorf("Volume: %q is not mounted", uniqueVolumeName) - } + // Check existence of mount point for filesystem volume or symbolic link for block volume + isExist, checkErr := volumeHandler.CheckVolumeExistence(volume.mountPath, volumeSpec.Name(), rc.mounter, uniqueVolumeName, volume.podName, pod.UID, attachablePlugin) + if checkErr != nil { + return nil, err + } + // If mount or symlink doesn't exist, volume reconstruction should be failed + if !isExist { + return nil, fmt.Errorf("Volume: %q is not mounted", uniqueVolumeName) } volumeMounter, newMounterErr := plugin.NewMounter( @@ -467,7 +497,7 @@ func (rc *reconciler) reconstructVolume(volume podVolume) (*reconstructedVolume, volumepkg.VolumeOptions{}) if newMounterErr != nil { return nil, fmt.Errorf( - "MountVolume.NewMounter failed for volume %q (spec.Name: %q) pod %q (UID: %q) with: %v", + "reconstructVolume.NewMounter failed for volume %q (spec.Name: %q) pod %q (UID: %q) with: %v", uniqueVolumeName, volumeSpec.Name(), volume.podName, @@ -475,6 +505,27 @@ func (rc *reconciler) reconstructVolume(volume podVolume) (*reconstructedVolume, newMounterErr) } + // TODO: remove feature gate check after no longer needed + var volumeMapper volumepkg.BlockVolumeMapper + if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + var newMapperErr error + if mapperPlugin != nil { + volumeMapper, newMapperErr = mapperPlugin.NewBlockVolumeMapper( + volumeSpec, + pod, + volumepkg.VolumeOptions{}) + if newMapperErr != nil { + return nil, fmt.Errorf( + "reconstructVolume.NewBlockVolumeMapper failed for volume %q (spec.Name: %q) pod %q (UID: %q) with: %v", + uniqueVolumeName, + volumeSpec.Name(), + volume.podName, + pod.UID, + newMapperErr) + } + } + } + reconstructedVolume := &reconstructedVolume{ volumeName: uniqueVolumeName, podName: volume.podName, @@ -489,13 +540,14 @@ func (rc *reconciler) reconstructVolume(volume podVolume) (*reconstructedVolume, volumeGidValue: "", devicePath: "", mounter: volumeMounter, + blockVolumeMapper: volumeMapper, } return reconstructedVolume, nil } func (rc *reconciler) updateStates(volumesNeedUpdate map[v1.UniqueVolumeName]*reconstructedVolume) error { // Get the node status to retrieve volume device path information. - node, fetchErr := rc.kubeClient.Core().Nodes().Get(string(rc.nodeName), metav1.GetOptions{}) + node, fetchErr := rc.kubeClient.CoreV1().Nodes().Get(string(rc.nodeName), metav1.GetOptions{}) if fetchErr != nil { glog.Errorf("updateStates in reconciler: could not get node status with error %v", fetchErr) } else { @@ -518,7 +570,6 @@ func (rc *reconciler) updateStates(volumesNeedUpdate map[v1.UniqueVolumeName]*re volumeToMount.VolumeName, volume.outerVolumeSpecName) } } - for _, volume := range volumesNeedUpdate { err := rc.actualStateOfWorld.MarkVolumeAsAttached( volume.volumeName, volume.volumeSpec, "" /* nodeName */, volume.devicePath) @@ -532,6 +583,7 @@ func (rc *reconciler) updateStates(volumesNeedUpdate map[v1.UniqueVolumeName]*re types.UID(volume.podName), volume.volumeName, volume.mounter, + volume.blockVolumeMapper, volume.outerVolumeSpecName, volume.volumeGidValue) if err != nil { @@ -574,33 +626,45 @@ func getVolumesFromPodDir(podDir string) ([]podVolume, error) { } podName := podsDirInfo[i].Name() podDir := path.Join(podDir, podName) - volumesDir := path.Join(podDir, config.DefaultKubeletVolumesDirName) - volumesDirInfo, err := ioutil.ReadDir(volumesDir) - if err != nil { - glog.Errorf("Could not read volume directory %q: %v", volumesDir, err) - continue - } - for _, volumeDir := range volumesDirInfo { - pluginName := volumeDir.Name() - volumePluginPath := path.Join(volumesDir, pluginName) - volumePluginDirs, err := utilfile.ReadDirNoStat(volumePluginPath) - if err != nil { - glog.Errorf("Could not read volume plugin directory %q: %v", volumePluginPath, err) + // Find filesystem volume information + // ex. filesystem volume: /pods/{podUid}/volume/{escapeQualifiedPluginName}/{volumeName} + volumesDirs := map[v1.PersistentVolumeMode]string{ + v1.PersistentVolumeFilesystem: path.Join(podDir, config.DefaultKubeletVolumesDirName), + } + // TODO: remove feature gate check after no longer needed + if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + // Find block volume information + // ex. block volume: /pods/{podUid}/volumeDevices/{escapeQualifiedPluginName}/{volumeName} + volumesDirs[v1.PersistentVolumeBlock] = path.Join(podDir, config.DefaultKubeletVolumeDevicesDirName) + } + for volumeMode, volumesDir := range volumesDirs { + var volumesDirInfo []os.FileInfo + if volumesDirInfo, err = ioutil.ReadDir(volumesDir); err != nil { + // Just skip the loop becuase given volumesDir doesn't exist depending on volumeMode continue } - - unescapePluginName := strings.UnescapeQualifiedNameForDisk(pluginName) - for _, volumeName := range volumePluginDirs { - mountPath := path.Join(volumePluginPath, volumeName) - volumes = append(volumes, podVolume{ - podName: volumetypes.UniquePodName(podName), - volumeSpecName: volumeName, - mountPath: mountPath, - pluginName: unescapePluginName, - }) + for _, volumeDir := range volumesDirInfo { + pluginName := volumeDir.Name() + volumePluginPath := path.Join(volumesDir, pluginName) + volumePluginDirs, err := utilfile.ReadDirNoStat(volumePluginPath) + if err != nil { + glog.Errorf("Could not read volume plugin directory %q: %v", volumePluginPath, err) + continue + } + unescapePluginName := utilstrings.UnescapeQualifiedNameForDisk(pluginName) + for _, volumeName := range volumePluginDirs { + mountPath := path.Join(volumePluginPath, volumeName) + glog.V(5).Infof("podName: %v, mount path from volume plugin directory: %v, ", podName, mountPath) + volumes = append(volumes, podVolume{ + podName: volumetypes.UniquePodName(podName), + volumeSpecName: volumeName, + mountPath: mountPath, + pluginName: unescapePluginName, + volumeMode: volumeMode, + }) + } } - } } glog.V(10).Infof("Get volumes from pod directory %q %+v", podDir, volumes) diff --git a/pkg/kubelet/volumemanager/reconciler/reconciler_test.go b/pkg/kubelet/volumemanager/reconciler/reconciler_test.go index ca3b1d00071..32cd954d2ec 100644 --- a/pkg/kubelet/volumemanager/reconciler/reconciler_test.go +++ b/pkg/kubelet/volumemanager/reconciler/reconciler_test.go @@ -23,10 +23,12 @@ import ( "github.com/stretchr/testify/assert" "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" k8stypes "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/kubernetes/fake" core "k8s.io/client-go/testing" "k8s.io/client-go/tools/record" @@ -61,7 +63,14 @@ func Test_Run_Positive_DoNothing(t *testing.T) { asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr) kubeClient := createTestClient() fakeRecorder := &record.FakeRecorder{} - oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(kubeClient, volumePluginMgr, fakeRecorder, false /* checkNodeCapabilitiesBeforeMount */)) + fakeHandler := volumetesting.NewBlockVolumePathHandler() + oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( + kubeClient, + volumePluginMgr, + fakeRecorder, + false, /* checkNodeCapabilitiesBeforeMount */ + fakeHandler, + )) reconciler := NewReconciler( kubeClient, false, /* controllerAttachDetachEnabled */ @@ -99,7 +108,13 @@ func Test_Run_Positive_VolumeAttachAndMount(t *testing.T) { asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr) kubeClient := createTestClient() fakeRecorder := &record.FakeRecorder{} - oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(kubeClient, volumePluginMgr, fakeRecorder, false /* checkNodeCapabilitiesBeforeMount */)) + fakeHandler := volumetesting.NewBlockVolumePathHandler() + oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( + kubeClient, + volumePluginMgr, + fakeRecorder, + false, /* checkNodeCapabilitiesBeforeMount */ + fakeHandler)) reconciler := NewReconciler( kubeClient, false, /* controllerAttachDetachEnabled */ @@ -171,7 +186,13 @@ func Test_Run_Positive_VolumeMountControllerAttachEnabled(t *testing.T) { asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr) kubeClient := createTestClient() fakeRecorder := &record.FakeRecorder{} - oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(kubeClient, volumePluginMgr, fakeRecorder, false /* checkNodeCapabilitiesBeforeMount */)) + fakeHandler := volumetesting.NewBlockVolumePathHandler() + oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( + kubeClient, + volumePluginMgr, + fakeRecorder, + false, /* checkNodeCapabilitiesBeforeMount */ + fakeHandler)) reconciler := NewReconciler( kubeClient, true, /* controllerAttachDetachEnabled */ @@ -244,7 +265,13 @@ func Test_Run_Positive_VolumeAttachMountUnmountDetach(t *testing.T) { asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr) kubeClient := createTestClient() fakeRecorder := &record.FakeRecorder{} - oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(kubeClient, volumePluginMgr, fakeRecorder, false /* checkNodeCapabilitiesBeforeMount */)) + fakeHandler := volumetesting.NewBlockVolumePathHandler() + oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( + kubeClient, + volumePluginMgr, + fakeRecorder, + false, /* checkNodeCapabilitiesBeforeMount */ + fakeHandler)) reconciler := NewReconciler( kubeClient, false, /* controllerAttachDetachEnabled */ @@ -328,7 +355,13 @@ func Test_Run_Positive_VolumeUnmountControllerAttachEnabled(t *testing.T) { asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr) kubeClient := createTestClient() fakeRecorder := &record.FakeRecorder{} - oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(kubeClient, volumePluginMgr, fakeRecorder, false /* checkNodeCapabilitiesBeforeMount */)) + fakeHandler := volumetesting.NewBlockVolumePathHandler() + oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( + kubeClient, + volumePluginMgr, + fakeRecorder, + false, /* checkNodeCapabilitiesBeforeMount */ + fakeHandler)) reconciler := NewReconciler( kubeClient, true, /* controllerAttachDetachEnabled */ @@ -399,6 +432,517 @@ func Test_Run_Positive_VolumeUnmountControllerAttachEnabled(t *testing.T) { assert.NoError(t, volumetesting.VerifyZeroDetachCallCount(fakePlugin)) } +// Populates desiredStateOfWorld cache with one volume/pod. +// Calls Run() +// Verifies there are attach/get map paths/setupDevice calls and +// no detach/teardownDevice calls. +func Test_Run_Positive_VolumeAttachAndMap(t *testing.T) { + // Enable BlockVolume feature gate + utilfeature.DefaultFeatureGate.Set("BlockVolume=true") + // Arrange + volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t) + dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) + asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr) + kubeClient := createTestClient() + fakeRecorder := &record.FakeRecorder{} + fakeHandler := volumetesting.NewBlockVolumePathHandler() + oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( + kubeClient, + volumePluginMgr, + fakeRecorder, + false, /* checkNodeCapabilitiesBeforeMount */ + fakeHandler)) + reconciler := NewReconciler( + kubeClient, + false, /* controllerAttachDetachEnabled */ + reconcilerLoopSleepDuration, + reconcilerSyncStatesSleepPeriod, + waitForAttachTimeout, + nodeName, + dsw, + asw, + hasAddedPods, + oex, + &mount.FakeMounter{}, + volumePluginMgr, + kubeletPodsDir) + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + UID: "pod1uid", + }, + Spec: v1.PodSpec{}, + } + + mode := v1.PersistentVolumeBlock + gcepv := &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{UID: "001", Name: "volume-name"}, + Spec: v1.PersistentVolumeSpec{ + Capacity: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G")}, + PersistentVolumeSource: v1.PersistentVolumeSource{GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{}}, + AccessModes: []v1.PersistentVolumeAccessMode{ + v1.ReadWriteOnce, + v1.ReadOnlyMany, + }, + VolumeMode: &mode, + }, + } + + volumeSpec := &volume.Spec{ + PersistentVolume: gcepv, + } + podName := volumehelper.GetUniquePodName(pod) + generatedVolumeName, err := dsw.AddPodToVolume( + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) + + // Assert + if err != nil { + t.Fatalf("AddPodToVolume failed. Expected: Actual: <%v>", err) + } + + // Act + runReconciler(reconciler) + waitForMount(t, fakePlugin, generatedVolumeName, asw) + // Assert + assert.NoError(t, volumetesting.VerifyAttachCallCount( + 1 /* expectedAttachCallCount */, fakePlugin)) + assert.NoError(t, volumetesting.VerifyWaitForAttachCallCount( + 1 /* expectedWaitForAttachCallCount */, fakePlugin)) + assert.NoError(t, volumetesting.VerifyGetGlobalMapPathCallCount( + 1 /* expectedGetGlobalMapPathCallCount */, fakePlugin)) + assert.NoError(t, volumetesting.VerifyGetPodDeviceMapPathCallCount( + 1 /* expectedPodDeviceMapPathCallCount */, fakePlugin)) + assert.NoError(t, volumetesting.VerifySetUpDeviceCallCount( + 1 /* expectedSetUpDeviceCallCount */, fakePlugin)) + assert.NoError(t, volumetesting.VerifyZeroTearDownDeviceCallCount(fakePlugin)) + assert.NoError(t, volumetesting.VerifyZeroDetachCallCount(fakePlugin)) + + // Rollback feature gate to false. + utilfeature.DefaultFeatureGate.Set("BlockVolume=false") +} + +// Populates desiredStateOfWorld cache with one volume/pod. +// Enables controllerAttachDetachEnabled. +// Calls Run() +// Verifies there are two get map path calls, a setupDevice call +// and no teardownDevice call. +// Verifies there are no attach/detach calls. +func Test_Run_Positive_BlockVolumeMapControllerAttachEnabled(t *testing.T) { + // Enable BlockVolume feature gate + utilfeature.DefaultFeatureGate.Set("BlockVolume=true") + // Arrange + volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t) + dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) + asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr) + kubeClient := createTestClient() + fakeRecorder := &record.FakeRecorder{} + fakeHandler := volumetesting.NewBlockVolumePathHandler() + oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( + kubeClient, + volumePluginMgr, + fakeRecorder, + false, /* checkNodeCapabilitiesBeforeMount */ + fakeHandler)) + reconciler := NewReconciler( + kubeClient, + true, /* controllerAttachDetachEnabled */ + reconcilerLoopSleepDuration, + reconcilerSyncStatesSleepPeriod, + waitForAttachTimeout, + nodeName, + dsw, + asw, + hasAddedPods, + oex, + &mount.FakeMounter{}, + volumePluginMgr, + kubeletPodsDir) + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + UID: "pod1uid", + }, + Spec: v1.PodSpec{}, + } + + mode := v1.PersistentVolumeBlock + gcepv := &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{UID: "001", Name: "volume-name"}, + Spec: v1.PersistentVolumeSpec{ + Capacity: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G")}, + PersistentVolumeSource: v1.PersistentVolumeSource{GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{}}, + AccessModes: []v1.PersistentVolumeAccessMode{ + v1.ReadWriteOnce, + v1.ReadOnlyMany, + }, + VolumeMode: &mode, + }, + } + + volumeSpec := &volume.Spec{ + PersistentVolume: gcepv, + } + podName := volumehelper.GetUniquePodName(pod) + generatedVolumeName, err := dsw.AddPodToVolume( + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) + dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{generatedVolumeName}) + + // Assert + if err != nil { + t.Fatalf("AddPodToVolume failed. Expected: Actual: <%v>", err) + } + + // Act + runReconciler(reconciler) + waitForMount(t, fakePlugin, generatedVolumeName, asw) + + // Assert + assert.NoError(t, volumetesting.VerifyZeroAttachCalls(fakePlugin)) + assert.NoError(t, volumetesting.VerifyWaitForAttachCallCount( + 1 /* expectedWaitForAttachCallCount */, fakePlugin)) + assert.NoError(t, volumetesting.VerifyGetGlobalMapPathCallCount( + 1 /* expectedGetGlobalMapPathCallCount */, fakePlugin)) + assert.NoError(t, volumetesting.VerifyGetPodDeviceMapPathCallCount( + 1 /* expectedPodDeviceMapPathCallCount */, fakePlugin)) + assert.NoError(t, volumetesting.VerifySetUpDeviceCallCount( + 1 /* expectedSetUpCallCount */, fakePlugin)) + assert.NoError(t, volumetesting.VerifyZeroTearDownDeviceCallCount(fakePlugin)) + assert.NoError(t, volumetesting.VerifyZeroDetachCallCount(fakePlugin)) + + // Rollback feature gate to false. + utilfeature.DefaultFeatureGate.Set("BlockVolume=false") +} + +// Populates desiredStateOfWorld cache with one volume/pod. +// Calls Run() +// Verifies there is one attach call, two get map path calls, +// setupDevice call and no detach calls. +// Deletes volume/pod from desired state of world. +// Verifies one detach/teardownDevice calls are issued. +func Test_Run_Positive_BlockVolumeAttachMapUnmapDetach(t *testing.T) { + // Enable BlockVolume feature gate + utilfeature.DefaultFeatureGate.Set("BlockVolume=true") + // Arrange + volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t) + dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) + asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr) + kubeClient := createTestClient() + fakeRecorder := &record.FakeRecorder{} + fakeHandler := volumetesting.NewBlockVolumePathHandler() + oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( + kubeClient, + volumePluginMgr, + fakeRecorder, + false, /* checkNodeCapabilitiesBeforeMount */ + fakeHandler)) + reconciler := NewReconciler( + kubeClient, + false, /* controllerAttachDetachEnabled */ + reconcilerLoopSleepDuration, + reconcilerSyncStatesSleepPeriod, + waitForAttachTimeout, + nodeName, + dsw, + asw, + hasAddedPods, + oex, + &mount.FakeMounter{}, + volumePluginMgr, + kubeletPodsDir) + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + UID: "pod1uid", + }, + Spec: v1.PodSpec{}, + } + + mode := v1.PersistentVolumeBlock + gcepv := &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{UID: "001", Name: "volume-name"}, + Spec: v1.PersistentVolumeSpec{ + Capacity: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G")}, + PersistentVolumeSource: v1.PersistentVolumeSource{GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{}}, + AccessModes: []v1.PersistentVolumeAccessMode{ + v1.ReadWriteOnce, + v1.ReadOnlyMany, + }, + VolumeMode: &mode, + }, + } + + volumeSpec := &volume.Spec{ + PersistentVolume: gcepv, + } + podName := volumehelper.GetUniquePodName(pod) + generatedVolumeName, err := dsw.AddPodToVolume( + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) + + // Assert + if err != nil { + t.Fatalf("AddPodToVolume failed. Expected: Actual: <%v>", err) + } + + // Act + runReconciler(reconciler) + waitForMount(t, fakePlugin, generatedVolumeName, asw) + // Assert + assert.NoError(t, volumetesting.VerifyAttachCallCount( + 1 /* expectedAttachCallCount */, fakePlugin)) + assert.NoError(t, volumetesting.VerifyWaitForAttachCallCount( + 1 /* expectedWaitForAttachCallCount */, fakePlugin)) + assert.NoError(t, volumetesting.VerifyGetGlobalMapPathCallCount( + 1 /* expectedGetGlobalMapPathCallCount */, fakePlugin)) + assert.NoError(t, volumetesting.VerifyGetPodDeviceMapPathCallCount( + 1 /* expectedPodDeviceMapPathCallCount */, fakePlugin)) + assert.NoError(t, volumetesting.VerifySetUpDeviceCallCount( + 1 /* expectedSetUpCallCount */, fakePlugin)) + assert.NoError(t, volumetesting.VerifyZeroTearDownDeviceCallCount(fakePlugin)) + assert.NoError(t, volumetesting.VerifyZeroDetachCallCount(fakePlugin)) + + // Act + dsw.DeletePodFromVolume(podName, generatedVolumeName) + waitForDetach(t, fakePlugin, generatedVolumeName, asw) + + // Assert + assert.NoError(t, volumetesting.VerifyTearDownDeviceCallCount( + 1 /* expectedTearDownDeviceCallCount */, fakePlugin)) + assert.NoError(t, volumetesting.VerifyDetachCallCount( + 1 /* expectedDetachCallCount */, fakePlugin)) + + // Rollback feature gate to false. + utilfeature.DefaultFeatureGate.Set("BlockVolume=false") +} + +// Populates desiredStateOfWorld cache with one volume/pod. +// Enables controllerAttachDetachEnabled. +// Calls Run() +// Verifies two map path calls are made and no teardownDevice/detach calls. +// Deletes volume/pod from desired state of world. +// Verifies one teardownDevice call is made. +// Verifies there are no attach/detach calls made. +func Test_Run_Positive_VolumeUnmapControllerAttachEnabled(t *testing.T) { + // Enable BlockVolume feature gate + utilfeature.DefaultFeatureGate.Set("BlockVolume=true") + // Arrange + volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t) + dsw := cache.NewDesiredStateOfWorld(volumePluginMgr) + asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr) + kubeClient := createTestClient() + fakeRecorder := &record.FakeRecorder{} + fakeHandler := volumetesting.NewBlockVolumePathHandler() + oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( + kubeClient, + volumePluginMgr, + fakeRecorder, + false, /* checkNodeCapabilitiesBeforeMount */ + fakeHandler)) + reconciler := NewReconciler( + kubeClient, + true, /* controllerAttachDetachEnabled */ + reconcilerLoopSleepDuration, + reconcilerSyncStatesSleepPeriod, + waitForAttachTimeout, + nodeName, + dsw, + asw, + hasAddedPods, + oex, + &mount.FakeMounter{}, + volumePluginMgr, + kubeletPodsDir) + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + UID: "pod1uid", + }, + Spec: v1.PodSpec{}, + } + + mode := v1.PersistentVolumeBlock + gcepv := &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{UID: "001", Name: "volume-name"}, + Spec: v1.PersistentVolumeSpec{ + Capacity: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G")}, + PersistentVolumeSource: v1.PersistentVolumeSource{GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{}}, + AccessModes: []v1.PersistentVolumeAccessMode{ + v1.ReadWriteOnce, + v1.ReadOnlyMany, + }, + VolumeMode: &mode, + }, + } + + volumeSpec := &volume.Spec{ + PersistentVolume: gcepv, + } + podName := volumehelper.GetUniquePodName(pod) + generatedVolumeName, err := dsw.AddPodToVolume( + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) + + // Assert + if err != nil { + t.Fatalf("AddPodToVolume failed. Expected: Actual: <%v>", err) + } + + // Act + runReconciler(reconciler) + + dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{generatedVolumeName}) + waitForMount(t, fakePlugin, generatedVolumeName, asw) + + // Assert + assert.NoError(t, volumetesting.VerifyZeroAttachCalls(fakePlugin)) + assert.NoError(t, volumetesting.VerifyWaitForAttachCallCount( + 1 /* expectedWaitForAttachCallCount */, fakePlugin)) + assert.NoError(t, volumetesting.VerifyGetGlobalMapPathCallCount( + 1 /* expectedGetGlobalMapPathCallCount */, fakePlugin)) + assert.NoError(t, volumetesting.VerifyGetPodDeviceMapPathCallCount( + 1 /* expectedPodDeviceMapPathCallCount */, fakePlugin)) + assert.NoError(t, volumetesting.VerifySetUpDeviceCallCount( + 1 /* expectedSetUpCallCount */, fakePlugin)) + assert.NoError(t, volumetesting.VerifyZeroTearDownDeviceCallCount(fakePlugin)) + assert.NoError(t, volumetesting.VerifyZeroDetachCallCount(fakePlugin)) + + // Act + dsw.DeletePodFromVolume(podName, generatedVolumeName) + waitForDetach(t, fakePlugin, generatedVolumeName, asw) + + // Assert + assert.NoError(t, volumetesting.VerifyTearDownDeviceCallCount( + 1 /* expectedTearDownDeviceCallCount */, fakePlugin)) + assert.NoError(t, volumetesting.VerifyZeroDetachCallCount(fakePlugin)) + + // Rollback feature gate to false. + utilfeature.DefaultFeatureGate.Set("BlockVolume=false") +} + +func Test_GenerateMapVolumeFunc_Plugin_Not_Found(t *testing.T) { + testCases := map[string]struct { + volumePlugins []volume.VolumePlugin + expectErr bool + expectedErrMsg string + }{ + "volumePlugin is nil": { + volumePlugins: []volume.VolumePlugin{}, + expectErr: true, + expectedErrMsg: "MapVolume.FindMapperPluginBySpec failed", + }, + "blockVolumePlugin is nil": { + volumePlugins: volumetesting.NewFakeFileVolumePlugin(), + expectErr: true, + expectedErrMsg: "MapVolume.FindMapperPluginBySpec failed to find BlockVolumeMapper plugin. Volume plugin is nil.", + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + volumePluginMgr := &volume.VolumePluginMgr{} + volumePluginMgr.InitPlugins(tc.volumePlugins, nil, nil) + asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr) + oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( + nil, /* kubeClient */ + volumePluginMgr, + nil, /* fakeRecorder */ + false, /* checkNodeCapabilitiesBeforeMount */ + nil)) + + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + UID: "pod1uid", + }, + Spec: v1.PodSpec{}, + } + volumeToMount := operationexecutor.VolumeToMount{Pod: pod, VolumeSpec: &volume.Spec{}} + err := oex.MapVolume(waitForAttachTimeout, volumeToMount, asw) + // Assert + if assert.Error(t, err) { + assert.Contains(t, err.Error(), tc.expectedErrMsg) + } + }) + } +} + +func Test_GenerateUnmapVolumeFunc_Plugin_Not_Found(t *testing.T) { + testCases := map[string]struct { + volumePlugins []volume.VolumePlugin + expectErr bool + expectedErrMsg string + }{ + "volumePlugin is nil": { + volumePlugins: []volume.VolumePlugin{}, + expectErr: true, + expectedErrMsg: "UnmapVolume.FindMapperPluginByName failed", + }, + "blockVolumePlugin is nil": { + volumePlugins: volumetesting.NewFakeFileVolumePlugin(), + expectErr: true, + expectedErrMsg: "UnmapVolume.FindMapperPluginByName failed to find BlockVolumeMapper plugin. Volume plugin is nil.", + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + volumePluginMgr := &volume.VolumePluginMgr{} + volumePluginMgr.InitPlugins(tc.volumePlugins, nil, nil) + asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr) + oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( + nil, /* kubeClient */ + volumePluginMgr, + nil, /* fakeRecorder */ + false, /* checkNodeCapabilitiesBeforeMount */ + nil)) + volumeToUnmount := operationexecutor.MountedVolume{PluginName: "fake-file-plugin"} + err := oex.UnmapVolume(volumeToUnmount, asw) + // Assert + if assert.Error(t, err) { + assert.Contains(t, err.Error(), tc.expectedErrMsg) + } + }) + } +} + +func Test_GenerateUnmapDeviceFunc_Plugin_Not_Found(t *testing.T) { + testCases := map[string]struct { + volumePlugins []volume.VolumePlugin + expectErr bool + expectedErrMsg string + }{ + "volumePlugin is nil": { + volumePlugins: []volume.VolumePlugin{}, + expectErr: true, + expectedErrMsg: "UnmapDevice.FindMapperPluginBySpec failed", + }, + "blockVolumePlugin is nil": { + volumePlugins: volumetesting.NewFakeFileVolumePlugin(), + expectErr: true, + expectedErrMsg: "UnmapDevice.FindMapperPluginBySpec failed to find BlockVolumeMapper plugin. Volume plugin is nil.", + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + volumePluginMgr := &volume.VolumePluginMgr{} + volumePluginMgr.InitPlugins(tc.volumePlugins, nil, nil) + asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr) + oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( + nil, /* kubeClient */ + volumePluginMgr, + nil, /* fakeRecorder */ + false, /* checkNodeCapabilitiesBeforeMount */ + nil)) + var mounter mount.Interface + deviceToDetach := operationexecutor.AttachedVolume{VolumeSpec: &volume.Spec{}} + err := oex.UnmapDevice(deviceToDetach, asw, mounter) + // Assert + if assert.Error(t, err) { + assert.Contains(t, err.Error(), tc.expectedErrMsg) + } + }) + } +} + func waitForMount( t *testing.T, fakePlugin *volumetesting.FakeVolumePlugin, diff --git a/pkg/kubelet/volumemanager/volume_manager.go b/pkg/kubelet/volumemanager/volume_manager.go index d48266f52a3..88027ce5fb3 100644 --- a/pkg/kubelet/volumemanager/volume_manager.go +++ b/pkg/kubelet/volumemanager/volume_manager.go @@ -41,6 +41,7 @@ import ( "k8s.io/kubernetes/pkg/kubelet/volumemanager/reconciler" "k8s.io/kubernetes/pkg/util/mount" "k8s.io/kubernetes/pkg/volume" + "k8s.io/kubernetes/pkg/volume/util" "k8s.io/kubernetes/pkg/volume/util/operationexecutor" "k8s.io/kubernetes/pkg/volume/util/types" "k8s.io/kubernetes/pkg/volume/util/volumehelper" @@ -168,8 +169,8 @@ func NewVolumeManager( kubeClient, volumePluginMgr, recorder, - checkNodeCapabilitiesBeforeMount), - ), + checkNodeCapabilitiesBeforeMount, + util.NewBlockVolumePathHandler())), } vm.desiredStateOfWorldPopulator = populator.NewDesiredStateOfWorldPopulator( @@ -253,7 +254,11 @@ func (vm *volumeManager) Run(sourcesReady config.SourcesReady, stopCh <-chan str func (vm *volumeManager) GetMountedVolumesForPod(podName types.UniquePodName) container.VolumeMap { podVolumes := make(container.VolumeMap) for _, mountedVolume := range vm.actualStateOfWorld.GetMountedVolumesForPod(podName) { - podVolumes[mountedVolume.OuterVolumeSpecName] = container.VolumeInfo{Mounter: mountedVolume.Mounter} + podVolumes[mountedVolume.OuterVolumeSpecName] = container.VolumeInfo{ + Mounter: mountedVolume.Mounter, + BlockVolumeMapper: mountedVolume.BlockVolumeMapper, + ReadOnly: mountedVolume.VolumeSpec.ReadOnly, + } } return podVolumes } @@ -352,21 +357,38 @@ func (vm *volumeManager) WaitForAttachAndMount(pod *v1.Pod) error { // Timeout expired unmountedVolumes := vm.getUnmountedVolumes(uniquePodName, expectedVolumes) + // Also get unattached volumes for error message + unattachedVolumes := + vm.getUnattachedVolumes(expectedVolumes) + if len(unmountedVolumes) == 0 { return nil } return fmt.Errorf( - "timeout expired waiting for volumes to attach/mount for pod %q/%q. list of unattached/unmounted volumes=%v", + "timeout expired waiting for volumes to attach or mount for pod %q/%q. list of unmounted volumes=%v. list of unattached volumes=%v", pod.Namespace, pod.Name, - unmountedVolumes) + unmountedVolumes, + unattachedVolumes) } glog.V(3).Infof("All volumes are attached and mounted for pod %q", format.Pod(pod)) return nil } +// getUnattachedVolumes returns a list of the volumes that are expected to be attached but +// are not currently attached to the node +func (vm *volumeManager) getUnattachedVolumes(expectedVolumes []string) []string { + unattachedVolumes := []string{} + for _, volume := range expectedVolumes { + if !vm.actualStateOfWorld.VolumeExists(v1.UniqueVolumeName(volume)) { + unattachedVolumes = append(unattachedVolumes, volume) + } + } + return unattachedVolumes +} + // verifyVolumesMountedFunc returns a method that returns true when all expected // volumes are mounted. func (vm *volumeManager) verifyVolumesMountedFunc(podName types.UniquePodName, expectedVolumes []string) wait.ConditionFunc { diff --git a/pkg/kubelet/volumemanager/volume_manager_test.go b/pkg/kubelet/volumemanager/volume_manager_test.go index 6eb81cb7f74..7632a5f9430 100644 --- a/pkg/kubelet/volumemanager/volume_manager_test.go +++ b/pkg/kubelet/volumemanager/volume_manager_test.go @@ -324,11 +324,11 @@ func delayClaimBecomesBound( ) { time.Sleep(500 * time.Millisecond) volumeClaim, _ := - kubeClient.Core().PersistentVolumeClaims(namespace).Get(claimName, metav1.GetOptions{}) + kubeClient.CoreV1().PersistentVolumeClaims(namespace).Get(claimName, metav1.GetOptions{}) volumeClaim.Status = v1.PersistentVolumeClaimStatus{ Phase: v1.ClaimBound, } - kubeClient.Core().PersistentVolumeClaims(namespace).Update(volumeClaim) + kubeClient.CoreV1().PersistentVolumeClaims(namespace).Update(volumeClaim) return } diff --git a/pkg/kubelet/winstats/BUILD b/pkg/kubelet/winstats/BUILD index 4cc59997c97..34211f896b0 100644 --- a/pkg/kubelet/winstats/BUILD +++ b/pkg/kubelet/winstats/BUILD @@ -22,9 +22,10 @@ go_library( srcs = [ "winstats.go", ] + select({ - "@io_bazel_rules_go//go/platform:windows_amd64": [ + "@io_bazel_rules_go//go/platform:windows": [ "perfcounter_nodestats.go", "perfcounters.go", + "version.go", ], "//conditions:default": [], }), @@ -33,9 +34,10 @@ go_library( "//vendor/github.com/google/cadvisor/info/v1:go_default_library", "//vendor/github.com/google/cadvisor/info/v2:go_default_library", ] + select({ - "@io_bazel_rules_go//go/platform:windows_amd64": [ + "@io_bazel_rules_go//go/platform:windows": [ + "//vendor/github.com/JeffAshton/win_pdh:go_default_library", "//vendor/github.com/golang/glog:go_default_library", - "//vendor/github.com/lxn/win:go_default_library", + "//vendor/golang.org/x/sys/windows:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", ], "//conditions:default": [], @@ -45,8 +47,8 @@ go_library( go_test( name = "go_default_test", srcs = ["winstats_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/winstats", - library = ":go_default_library", deps = [ "//vendor/github.com/google/cadvisor/info/v1:go_default_library", "//vendor/github.com/google/cadvisor/info/v2:go_default_library", diff --git a/pkg/kubelet/winstats/perfcounter_nodestats.go b/pkg/kubelet/winstats/perfcounter_nodestats.go index 9d0a3e92a22..b6855df4013 100644 --- a/pkg/kubelet/winstats/perfcounter_nodestats.go +++ b/pkg/kubelet/winstats/perfcounter_nodestats.go @@ -21,18 +21,22 @@ package winstats import ( "errors" "os" - "os/exec" "runtime" - "strings" "sync" + "syscall" "time" + "unsafe" "github.com/golang/glog" cadvisorapi "github.com/google/cadvisor/info/v1" - "github.com/lxn/win" "k8s.io/apimachinery/pkg/util/wait" ) +var ( + modkernel32 = syscall.NewLazyDLL("kernel32.dll") + procGetPhysicallyInstalledSystemMemory = modkernel32.NewProc("GetPhysicallyInstalledSystemMemory") +) + // NewPerfCounterClient creates a client using perf counters func NewPerfCounterClient() (Client, error) { return newClient(&perfCounterNodeStatsClient{}) @@ -51,13 +55,16 @@ func (p *perfCounterNodeStatsClient) startMonitoring() error { return err } - version, err := exec.Command("cmd", "/C", "ver").Output() + kernelVersion, err := getKernelVersion() + if err != nil { + return err + } + + osImageVersion, err := getOSImageVersion() if err != nil { return err } - osImageVersion := strings.TrimSpace(string(version)) - kernelVersion := extractVersionNumber(osImageVersion) p.nodeInfo = nodeInfo{ kernelVersion: kernelVersion, osImageVersion: osImageVersion, @@ -158,9 +165,18 @@ func (p *perfCounterNodeStatsClient) convertCPUValue(cpuValue uint64) uint64 { func getPhysicallyInstalledSystemMemoryBytes() (uint64, error) { var physicalMemoryKiloBytes uint64 - if ok := win.GetPhysicallyInstalledSystemMemory(&physicalMemoryKiloBytes); !ok { + if ok := getPhysicallyInstalledSystemMemory(&physicalMemoryKiloBytes); !ok { return 0, errors.New("unable to read physical memory") } return physicalMemoryKiloBytes * 1024, nil // convert kilobytes to bytes } + +func getPhysicallyInstalledSystemMemory(totalMemoryInKilobytes *uint64) bool { + ret, _, _ := syscall.Syscall(procGetPhysicallyInstalledSystemMemory.Addr(), 1, + uintptr(unsafe.Pointer(totalMemoryInKilobytes)), + 0, + 0) + + return ret != 0 +} diff --git a/pkg/kubelet/winstats/perfcounters.go b/pkg/kubelet/winstats/perfcounters.go index dd77103bd0a..e5ed450b78e 100644 --- a/pkg/kubelet/winstats/perfcounters.go +++ b/pkg/kubelet/winstats/perfcounters.go @@ -24,7 +24,7 @@ import ( "time" "unsafe" - "github.com/lxn/win" + "github.com/JeffAshton/win_pdh" ) const ( @@ -37,31 +37,31 @@ const ( ) type perfCounter struct { - queryHandle win.PDH_HQUERY - counterHandle win.PDH_HCOUNTER + queryHandle win_pdh.PDH_HQUERY + counterHandle win_pdh.PDH_HCOUNTER } func newPerfCounter(counter string) (*perfCounter, error) { - var queryHandle win.PDH_HQUERY - var counterHandle win.PDH_HCOUNTER + var queryHandle win_pdh.PDH_HQUERY + var counterHandle win_pdh.PDH_HCOUNTER - ret := win.PdhOpenQuery(0, 0, &queryHandle) - if ret != win.ERROR_SUCCESS { + ret := win_pdh.PdhOpenQuery(0, 0, &queryHandle) + if ret != win_pdh.ERROR_SUCCESS { return nil, errors.New("unable to open query through DLL call") } - ret = win.PdhValidatePath(counter) - if ret != win.ERROR_SUCCESS { + ret = win_pdh.PdhValidatePath(counter) + if ret != win_pdh.ERROR_SUCCESS { return nil, fmt.Errorf("unable to valid path to counter. Error code is %x", ret) } - ret = win.PdhAddEnglishCounter(queryHandle, counter, 0, &counterHandle) - if ret != win.ERROR_SUCCESS { + ret = win_pdh.PdhAddEnglishCounter(queryHandle, counter, 0, &counterHandle) + if ret != win_pdh.ERROR_SUCCESS { return nil, fmt.Errorf("unable to add process counter. Error code is %x", ret) } - ret = win.PdhCollectQueryData(queryHandle) - if ret != win.ERROR_SUCCESS { + ret = win_pdh.PdhCollectQueryData(queryHandle) + if ret != win_pdh.ERROR_SUCCESS { return nil, fmt.Errorf("unable to collect data from counter. Error code is %x", ret) } @@ -72,24 +72,24 @@ func newPerfCounter(counter string) (*perfCounter, error) { } func (p *perfCounter) getData() (uint64, error) { - ret := win.PdhCollectQueryData(p.queryHandle) - if ret != win.ERROR_SUCCESS { + ret := win_pdh.PdhCollectQueryData(p.queryHandle) + if ret != win_pdh.ERROR_SUCCESS { return 0, fmt.Errorf("unable to collect data from counter. Error code is %x", ret) } var bufSize, bufCount uint32 - var size = uint32(unsafe.Sizeof(win.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE{})) - var emptyBuf [1]win.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE // need at least 1 addressable null ptr. + var size = uint32(unsafe.Sizeof(win_pdh.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE{})) + var emptyBuf [1]win_pdh.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE // need at least 1 addressable null ptr. var data uint64 - ret = win.PdhGetFormattedCounterArrayDouble(p.counterHandle, &bufSize, &bufCount, &emptyBuf[0]) - if ret != win.PDH_MORE_DATA { + ret = win_pdh.PdhGetFormattedCounterArrayDouble(p.counterHandle, &bufSize, &bufCount, &emptyBuf[0]) + if ret != win_pdh.PDH_MORE_DATA { return 0, fmt.Errorf("unable to collect data from counter. Error code is %x", ret) } - filledBuf := make([]win.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE, bufCount*size) - ret = win.PdhGetFormattedCounterArrayDouble(p.counterHandle, &bufSize, &bufCount, &filledBuf[0]) - if ret != win.ERROR_SUCCESS { + filledBuf := make([]win_pdh.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE, bufCount*size) + ret = win_pdh.PdhGetFormattedCounterArrayDouble(p.counterHandle, &bufSize, &bufCount, &filledBuf[0]) + if ret != win_pdh.ERROR_SUCCESS { return 0, fmt.Errorf("unable to collect data from counter. Error code is %x", ret) } diff --git a/pkg/kubelet/winstats/version.go b/pkg/kubelet/winstats/version.go new file mode 100644 index 00000000000..e6cc097f5b1 --- /dev/null +++ b/pkg/kubelet/winstats/version.go @@ -0,0 +1,96 @@ +// +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 winstats + +import ( + "fmt" + "unsafe" + + "golang.org/x/sys/windows" +) + +// getCurrentVersionVal gets value of speficied key from registry. +func getCurrentVersionVal(key string) (string, error) { + var h windows.Handle + if err := windows.RegOpenKeyEx(windows.HKEY_LOCAL_MACHINE, + windows.StringToUTF16Ptr(`SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\`), + 0, + windows.KEY_READ, + &h); err != nil { + return "", err + } + defer windows.RegCloseKey(h) + + var buf [128]uint16 + var typ uint32 + n := uint32(len(buf) * int(unsafe.Sizeof(buf[0]))) // api expects array of bytes, not uint16 + if err := windows.RegQueryValueEx(h, + windows.StringToUTF16Ptr(key), + nil, + &typ, + (*byte)(unsafe.Pointer(&buf[0])), + &n); err != nil { + return "", err + } + + return windows.UTF16ToString(buf[:]), nil +} + +// getVersionRevision gets revision from UBR registry. +func getVersionRevision() (uint16, error) { + revisionString, err := getCurrentVersionVal("UBR") + if err != nil { + return 0, err + } + + revision, err := windows.UTF16FromString(revisionString) + if err != nil { + return 0, err + } + + return revision[0], nil +} + +// getKernelVersion gets the version of windows kernel. +func getKernelVersion() (string, error) { + ver, err := windows.GetVersion() + if err != nil { + return "", err + } + + revision, err := getVersionRevision() + if err != nil { + return "", err + } + + major := ver & 0xFF + minor := (ver >> 8) & 0xFF + build := (ver >> 16) & 0xFFFF + return fmt.Sprintf("%d.%d.%05d.%d\n", major, minor, build, revision), nil +} + +// getOSImageVersion gets the osImage name and version. +func getOSImageVersion() (string, error) { + productName, err := getCurrentVersionVal("ProductName") + if err != nil { + return "", nil + } + + return productName, nil +} diff --git a/pkg/kubelet/winstats/winstats.go b/pkg/kubelet/winstats/winstats.go index 123870b8968..b02bab4f6bd 100644 --- a/pkg/kubelet/winstats/winstats.go +++ b/pkg/kubelet/winstats/winstats.go @@ -18,7 +18,6 @@ limitations under the License. package winstats import ( - "regexp" "time" cadvisorapi "github.com/google/cadvisor/info/v1" @@ -135,11 +134,3 @@ func (c *statsClient) createRootContainerInfo() (*cadvisorapiv2.ContainerInfo, e return &rootInfo, nil } - -// extractVersionNumber gets the version number from the full version string on Windows -// e.g. extracts "10.0.14393" from "Microsoft Windows [Version 10.0.14393]" -func extractVersionNumber(fullVersion string) string { - re := regexp.MustCompile("[^0-9.]") - version := re.ReplaceAllString(fullVersion, "") - return version -} diff --git a/pkg/kubelet/winstats/winstats_test.go b/pkg/kubelet/winstats/winstats_test.go index c1710750708..42df2bb2d80 100644 --- a/pkg/kubelet/winstats/winstats_test.go +++ b/pkg/kubelet/winstats/winstats_test.go @@ -116,13 +116,6 @@ func TestWinVersionInfo(t *testing.T) { KernelVersion: "v42"}) } -func TestExtractVersionNumber(t *testing.T) { - fullVersion := "Microsoft Windows [Version 10.0.14393]" - versionNumber := extractVersionNumber(fullVersion) - expected := "10.0.14393" - assert.Equal(t, expected, versionNumber) -} - func getClient(t *testing.T) Client { f := fakeWinNodeStatsClient{} c, err := newClient(f) diff --git a/pkg/kubemark/BUILD b/pkg/kubemark/BUILD index 4cb0026ab59..a11620af86f 100644 --- a/pkg/kubemark/BUILD +++ b/pkg/kubemark/BUILD @@ -17,7 +17,7 @@ go_library( "//cmd/kube-proxy/app:go_default_library", "//cmd/kubelet/app:go_default_library", "//cmd/kubelet/app/options:go_default_library", - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/controller:go_default_library", "//pkg/kubelet:go_default_library", @@ -25,7 +25,7 @@ go_library( "//pkg/kubelet/cadvisor:go_default_library", "//pkg/kubelet/cm:go_default_library", "//pkg/kubelet/container/testing:go_default_library", - "//pkg/kubelet/dockershim/libdocker:go_default_library", + "//pkg/kubelet/dockershim:go_default_library", "//pkg/kubelet/types:go_default_library", "//pkg/proxy:go_default_library", "//pkg/proxy/config:go_default_library", diff --git a/pkg/kubemark/hollow_kubelet.go b/pkg/kubemark/hollow_kubelet.go index 671d19912d8..f087ebfea3e 100644 --- a/pkg/kubemark/hollow_kubelet.go +++ b/pkg/kubemark/hollow_kubelet.go @@ -28,7 +28,7 @@ import ( "k8s.io/kubernetes/pkg/kubelet/cadvisor" "k8s.io/kubernetes/pkg/kubelet/cm" containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" - "k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker" + "k8s.io/kubernetes/pkg/kubelet/dockershim" kubetypes "k8s.io/kubernetes/pkg/kubelet/types" kubeio "k8s.io/kubernetes/pkg/util/io" "k8s.io/kubernetes/pkg/util/mount" @@ -50,7 +50,7 @@ func NewHollowKubelet( nodeName string, client *clientset.Clientset, cadvisorInterface cadvisor.Interface, - dockerClient libdocker.Interface, + dockerClientConfig *dockershim.ClientConfig, kubeletPort, kubeletReadOnlyPort int, containerManager cm.ContainerManager, maxPods int, podsPerCore int, @@ -66,18 +66,18 @@ func NewHollowKubelet( volumePlugins := empty_dir.ProbeVolumePlugins() volumePlugins = append(volumePlugins, secret.ProbeVolumePlugins()...) d := &kubelet.Dependencies{ - KubeClient: client, - HeartbeatClient: client.CoreV1(), - DockerClient: dockerClient, - CAdvisorInterface: cadvisorInterface, - Cloud: nil, - OSInterface: &containertest.FakeOS{}, - ContainerManager: containerManager, - VolumePlugins: volumePlugins, - TLSOptions: nil, - OOMAdjuster: oom.NewFakeOOMAdjuster(), - Writer: &kubeio.StdWriter{}, - Mounter: mount.New("" /* default mount path */), + KubeClient: client, + HeartbeatClient: client.CoreV1(), + DockerClientConfig: dockerClientConfig, + CAdvisorInterface: cadvisorInterface, + Cloud: nil, + OSInterface: &containertest.FakeOS{}, + ContainerManager: containerManager, + VolumePlugins: volumePlugins, + TLSOptions: nil, + OOMAdjuster: oom.NewFakeOOMAdjuster(), + Writer: &kubeio.StdWriter{}, + Mounter: mount.New("" /* default mount path */), } return &HollowKubelet{ @@ -115,6 +115,7 @@ func GetHollowKubeletConfig( f.MinimumGCAge = metav1.Duration{Duration: 1 * time.Minute} f.MaxContainerCount = 100 f.MaxPerPodContainerCount = 2 + f.RegisterNode = true f.RegisterSchedulable = true // Config struct @@ -140,11 +141,8 @@ func GetHollowKubeletConfig( c.ImageGCLowThresholdPercent = 80 c.VolumeStatsAggPeriod.Duration = time.Minute c.CgroupRoot = "" - c.ContainerRuntime = kubetypes.DockerContainerRuntime c.CPUCFSQuota = true - c.RuntimeCgroups = "" c.EnableControllerAttachDetach = false - c.EnableCustomMetrics = false c.EnableDebuggingHandlers = true c.EnableServer = true c.CgroupsPerQOS = false @@ -153,7 +151,6 @@ func GetHollowKubeletConfig( // set promiscuous mode on docker0. c.HairpinMode = kubeletconfig.HairpinVeth c.MaxOpenFiles = 1024 - c.RegisterNode = true c.RegistryBurst = 10 c.RegistryPullQPS = 5.0 c.ResolverConfig = kubetypes.ResolvConfDefault diff --git a/pkg/kubemark/hollow_proxy.go b/pkg/kubemark/hollow_proxy.go index 861509cc1ff..27ca1128926 100644 --- a/pkg/kubemark/hollow_proxy.go +++ b/pkg/kubemark/hollow_proxy.go @@ -27,7 +27,7 @@ import ( v1core "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/tools/record" proxyapp "k8s.io/kubernetes/cmd/kube-proxy/app" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/proxy" proxyconfig "k8s.io/kubernetes/pkg/proxy/config" diff --git a/pkg/master/BUILD b/pkg/master/BUILD index 5101fd1219a..3162a764c33 100644 --- a/pkg/master/BUILD +++ b/pkg/master/BUILD @@ -18,8 +18,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/master", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/admission/install:go_default_library", "//pkg/apis/admissionregistration/install:go_default_library", "//pkg/apis/apps/install:go_default_library", @@ -29,6 +28,9 @@ go_library( "//pkg/apis/batch/install:go_default_library", "//pkg/apis/certificates/install:go_default_library", "//pkg/apis/componentconfig/install:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/install:go_default_library", + "//pkg/apis/events/install:go_default_library", "//pkg/apis/extensions/install:go_default_library", "//pkg/apis/imagepolicy/install:go_default_library", "//pkg/apis/networking/install:go_default_library", @@ -56,6 +58,7 @@ go_library( "//pkg/registry/core/service/ipallocator:go_default_library", "//pkg/registry/core/service/ipallocator/controller:go_default_library", "//pkg/registry/core/service/portallocator/controller:go_default_library", + "//pkg/registry/events/rest:go_default_library", "//pkg/registry/extensions/rest:go_default_library", "//pkg/registry/networking/rest:go_default_library", "//pkg/registry/policy/rest:go_default_library", @@ -68,6 +71,7 @@ go_library( "//pkg/util/node:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", + "//vendor/k8s.io/api/admissionregistration/v1beta1:go_default_library", "//vendor/k8s.io/api/apps/v1:go_default_library", "//vendor/k8s.io/api/apps/v1beta1:go_default_library", "//vendor/k8s.io/api/apps/v1beta2:go_default_library", @@ -81,6 +85,7 @@ go_library( "//vendor/k8s.io/api/batch/v1beta1:go_default_library", "//vendor/k8s.io/api/certificates/v1beta1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/events/v1beta1:go_default_library", "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", "//vendor/k8s.io/api/networking/v1:go_default_library", "//vendor/k8s.io/api/policy/v1beta1:go_default_library", @@ -114,16 +119,17 @@ go_test( "master_openapi_test.go", "master_test.go", ], - features = ["-race"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/master", - library = ":go_default_library", + race = "off", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/testapi:go_default_library", "//pkg/apis/apps:go_default_library", "//pkg/apis/autoscaling:go_default_library", "//pkg/apis/batch:go_default_library", "//pkg/apis/certificates:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/apis/rbac:go_default_library", "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", diff --git a/pkg/master/client_ca_hook.go b/pkg/master/client_ca_hook.go index 3a2780c92a7..fedfdca35ef 100644 --- a/pkg/master/client_ca_hook.go +++ b/pkg/master/client_ca_hook.go @@ -26,7 +26,7 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" genericapiserver "k8s.io/apiserver/pkg/server" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" ) diff --git a/pkg/master/client_ca_hook_test.go b/pkg/master/client_ca_hook_test.go index 02dff52c132..64aa61712c1 100644 --- a/pkg/master/client_ca_hook_test.go +++ b/pkg/master/client_ca_hook_test.go @@ -24,7 +24,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/diff" clienttesting "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" ) diff --git a/pkg/master/controller.go b/pkg/master/controller.go index 158b5d1f6f0..291078b2710 100644 --- a/pkg/master/controller.go +++ b/pkg/master/controller.go @@ -29,7 +29,7 @@ import ( "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" genericapiserver "k8s.io/apiserver/pkg/server" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" "k8s.io/kubernetes/pkg/master/reconcilers" "k8s.io/kubernetes/pkg/registry/core/rangeallocation" @@ -48,6 +48,7 @@ const kubernetesServiceName = "kubernetes" type Controller struct { ServiceClient coreclient.ServicesGetter NamespaceClient coreclient.NamespacesGetter + EventClient coreclient.EventsGetter ServiceClusterIPRegistry rangeallocation.RangeRegistry ServiceClusterIPInterval time.Duration @@ -77,10 +78,11 @@ type Controller struct { } // NewBootstrapController returns a controller for watching the core capabilities of the master -func (c *completedConfig) NewBootstrapController(legacyRESTStorage corerest.LegacyRESTStorage, serviceClient coreclient.ServicesGetter, nsClient coreclient.NamespacesGetter) *Controller { +func (c *completedConfig) NewBootstrapController(legacyRESTStorage corerest.LegacyRESTStorage, serviceClient coreclient.ServicesGetter, nsClient coreclient.NamespacesGetter, eventClient coreclient.EventsGetter) *Controller { return &Controller{ ServiceClient: serviceClient, NamespaceClient: nsClient, + EventClient: eventClient, EndpointReconciler: c.ExtraConfig.EndpointReconcilerConfig.Reconciler, EndpointInterval: c.ExtraConfig.EndpointReconcilerConfig.Interval, @@ -112,6 +114,11 @@ func (c *Controller) PostStartHook(hookContext genericapiserver.PostStartHookCon return nil } +func (c *Controller) PreShutdownHook() error { + c.Stop() + return nil +} + // Start begins the core controller loops that must exist for bootstrapping // a cluster. func (c *Controller) Start() { @@ -119,8 +126,8 @@ func (c *Controller) Start() { return } - repairClusterIPs := servicecontroller.NewRepair(c.ServiceClusterIPInterval, c.ServiceClient, &c.ServiceClusterIPRange, c.ServiceClusterIPRegistry) - repairNodePorts := portallocatorcontroller.NewRepair(c.ServiceNodePortInterval, c.ServiceClient, c.ServiceNodePortRange, c.ServiceNodePortRegistry) + repairClusterIPs := servicecontroller.NewRepair(c.ServiceClusterIPInterval, c.ServiceClient, c.EventClient, &c.ServiceClusterIPRange, c.ServiceClusterIPRegistry) + repairNodePorts := portallocatorcontroller.NewRepair(c.ServiceNodePortInterval, c.ServiceClient, c.EventClient, c.ServiceNodePortRange, c.ServiceNodePortRegistry) // run all of the controllers once prior to returning from Start. if err := repairClusterIPs.RunOnce(); err != nil { @@ -140,6 +147,14 @@ func (c *Controller) Start() { c.runner.Start() } +func (c *Controller) Stop() { + if c.runner != nil { + c.runner.Stop() + } + endpointPorts := createEndpointPortSpec(c.PublicServicePort, "https", c.ExtraEndpointPorts) + c.EndpointReconciler.StopReconciling("kubernetes", c.PublicIP, endpointPorts) +} + // RunKubernetesNamespaces periodically makes sure that all internal namespaces exist func (c *Controller) RunKubernetesNamespaces(ch chan struct{}) { wait.Until(func() { diff --git a/pkg/master/controller/crdregistration/BUILD b/pkg/master/controller/crdregistration/BUILD index b902953bb81..ad9b5c2ea59 100644 --- a/pkg/master/controller/crdregistration/BUILD +++ b/pkg/master/controller/crdregistration/BUILD @@ -43,8 +43,8 @@ filegroup( go_test( name = "go_default_test", srcs = ["crdregistration_controller_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/master/controller/crdregistration", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion:go_default_library", diff --git a/pkg/master/controller_test.go b/pkg/master/controller_test.go index 439f6f698b4..ec7d63158e8 100644 --- a/pkg/master/controller_test.go +++ b/pkg/master/controller_test.go @@ -24,7 +24,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" core "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" "k8s.io/kubernetes/pkg/master/reconcilers" ) diff --git a/pkg/master/import_known_versions.go b/pkg/master/import_known_versions.go index 93388ad9b0f..baf2502e92e 100644 --- a/pkg/master/import_known_versions.go +++ b/pkg/master/import_known_versions.go @@ -20,8 +20,8 @@ package master import ( "fmt" - "k8s.io/kubernetes/pkg/api" - _ "k8s.io/kubernetes/pkg/api/install" + "k8s.io/kubernetes/pkg/api/legacyscheme" + _ "k8s.io/kubernetes/pkg/apis/admission/install" _ "k8s.io/kubernetes/pkg/apis/admissionregistration/install" _ "k8s.io/kubernetes/pkg/apis/apps/install" @@ -31,6 +31,8 @@ import ( _ "k8s.io/kubernetes/pkg/apis/batch/install" _ "k8s.io/kubernetes/pkg/apis/certificates/install" _ "k8s.io/kubernetes/pkg/apis/componentconfig/install" + _ "k8s.io/kubernetes/pkg/apis/core/install" + _ "k8s.io/kubernetes/pkg/apis/events/install" _ "k8s.io/kubernetes/pkg/apis/extensions/install" _ "k8s.io/kubernetes/pkg/apis/imagepolicy/install" _ "k8s.io/kubernetes/pkg/apis/networking/install" @@ -42,7 +44,7 @@ import ( ) func init() { - if missingVersions := api.Registry.ValidateEnvRequestedVersions(); len(missingVersions) != 0 { + if missingVersions := legacyscheme.Registry.ValidateEnvRequestedVersions(); len(missingVersions) != 0 { panic(fmt.Sprintf("KUBE_API_VERSIONS contains versions that are not installed: %q.", missingVersions)) } } diff --git a/pkg/master/import_known_versions_test.go b/pkg/master/import_known_versions_test.go index 1ee024a33f7..e6a69e7d6f6 100644 --- a/pkg/master/import_known_versions_test.go +++ b/pkg/master/import_known_versions_test.go @@ -28,7 +28,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" ) func TestGroupVersions(t *testing.T) { @@ -41,16 +41,15 @@ func TestGroupVersions(t *testing.T) { "batch", "componentconfig", "extensions", - "federation", "policy", ) // No new groups should be added to the legacyUnsuffixedGroups exclusion list - if len(legacyUnsuffixedGroups) != 8 { + if len(legacyUnsuffixedGroups) != 7 { t.Errorf("No additional unnamespaced groups should be created") } - for _, gv := range api.Registry.RegisteredGroupVersions() { + for _, gv := range legacyscheme.Registry.RegisteredGroupVersions() { if !strings.HasSuffix(gv.Group, ".k8s.io") && !legacyUnsuffixedGroups.Has(gv.Group) { t.Errorf("Group %s does not have the standard kubernetes API group suffix of .k8s.io", gv.Group) } @@ -58,7 +57,7 @@ func TestGroupVersions(t *testing.T) { } func TestTypeTags(t *testing.T) { - for gvk, knownType := range api.Scheme.AllKnownTypes() { + for gvk, knownType := range legacyscheme.Scheme.AllKnownTypes() { if gvk.Version == runtime.APIVersionInternal { ensureNoTags(t, gvk, knownType, nil) } else { @@ -100,7 +99,7 @@ func ensureNoTags(t *testing.T, gvk schema.GroupVersionKind, tp reflect.Type, pa case reflect.Map, reflect.Slice, reflect.Ptr: ensureNoTags(t, gvk, tp.Elem(), parents) - case reflect.String, reflect.Bool, reflect.Float32, reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uintptr, reflect.Uint32, reflect.Uint64, reflect.Interface: + case reflect.String, reflect.Bool, reflect.Float32, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uintptr, reflect.Uint32, reflect.Uint64, reflect.Interface: // no-op case reflect.Struct: diff --git a/pkg/master/master.go b/pkg/master/master.go index b5a559dfd05..808072b023d 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -24,6 +24,7 @@ import ( "strconv" "time" + admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" appsv1 "k8s.io/api/apps/v1" appsv1beta1 "k8s.io/api/apps/v1beta1" appsv1beta2 "k8s.io/api/apps/v1beta2" @@ -37,6 +38,7 @@ import ( batchapiv1beta1 "k8s.io/api/batch/v1beta1" certificatesapiv1beta1 "k8s.io/api/certificates/v1beta1" apiv1 "k8s.io/api/core/v1" + eventsv1beta1 "k8s.io/api/events/v1beta1" extensionsapiv1beta1 "k8s.io/api/extensions/v1beta1" networkingapiv1 "k8s.io/api/networking/v1" policyapiv1beta1 "k8s.io/api/policy/v1beta1" @@ -52,9 +54,9 @@ import ( "k8s.io/apiserver/pkg/server/healthz" serverstorage "k8s.io/apiserver/pkg/server/storage" storagefactory "k8s.io/apiserver/pkg/storage/storagebackend/factory" + "k8s.io/client-go/informers" corev1client "k8s.io/client-go/kubernetes/typed/core/v1" - "k8s.io/kubernetes/pkg/api" - kapi "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" kubeoptions "k8s.io/kubernetes/pkg/kubeapiserver/options" kubeletclient "k8s.io/kubernetes/pkg/kubelet/client" @@ -69,7 +71,6 @@ import ( "github.com/prometheus/client_golang/prometheus" // RESTStorage installers - "k8s.io/client-go/informers" admissionregistrationrest "k8s.io/kubernetes/pkg/registry/admissionregistration/rest" appsrest "k8s.io/kubernetes/pkg/registry/apps/rest" authenticationrest "k8s.io/kubernetes/pkg/registry/authentication/rest" @@ -78,6 +79,7 @@ import ( batchrest "k8s.io/kubernetes/pkg/registry/batch/rest" certificatesrest "k8s.io/kubernetes/pkg/registry/certificates/rest" corerest "k8s.io/kubernetes/pkg/registry/core/rest" + eventsrest "k8s.io/kubernetes/pkg/registry/events/rest" extensionsrest "k8s.io/kubernetes/pkg/registry/extensions/rest" networkingrest "k8s.io/kubernetes/pkg/registry/networking/rest" policyrest "k8s.io/kubernetes/pkg/registry/policy/rest" @@ -93,8 +95,6 @@ const ( DefaultEndpointReconcilerInterval = 10 * time.Second // DefaultEndpointReconcilerTTL is the default TTL timeout for the storage layer DefaultEndpointReconcilerTTL = 15 * time.Second - // DefaultStorageEndpoint is the default storage endpoint for the lease controller - DefaultStorageEndpoint = "kube-apiserver-endpoint" ) type ExtraConfig struct { @@ -198,7 +198,7 @@ func (c *Config) createNoneReconciler() reconcilers.EndpointReconciler { func (c *Config) createLeaseReconciler() reconcilers.EndpointReconciler { ttl := c.ExtraConfig.MasterEndpointReconcileTTL - config, err := c.ExtraConfig.StorageFactory.NewConfig(kapi.Resource("apiServerIPInfo")) + config, err := c.ExtraConfig.StorageFactory.NewConfig(api.Resource("apiServerIPInfo")) if err != nil { glog.Fatalf("Error determining service IP ranges: %v", err) } @@ -206,7 +206,7 @@ func (c *Config) createLeaseReconciler() reconcilers.EndpointReconciler { if err != nil { glog.Fatalf("Error creating storage factory: %v", err) } - endpointConfig, err := c.ExtraConfig.StorageFactory.NewConfig(kapi.Resource(DefaultStorageEndpoint)) + endpointConfig, err := c.ExtraConfig.StorageFactory.NewConfig(api.Resource("endpoints")) if err != nil { glog.Fatalf("Error getting storage config: %v", err) } @@ -214,7 +214,7 @@ func (c *Config) createLeaseReconciler() reconcilers.EndpointReconciler { StorageConfig: endpointConfig, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 0, - ResourcePrefix: c.ExtraConfig.StorageFactory.ResourcePrefix(kapi.Resource(DefaultStorageEndpoint)), + ResourcePrefix: c.ExtraConfig.StorageFactory.ResourcePrefix(api.Resource("endpoints")), }) endpointRegistry := endpoint.NewRegistry(endpointsStorage) masterLeases := reconcilers.NewLeases(leaseStorage, "/masterleases/", ttl) @@ -353,6 +353,7 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) // See https://github.com/kubernetes/kubernetes/issues/42392 appsrest.RESTStorageProvider{}, admissionregistrationrest.RESTStorageProvider{}, + eventsrest.RESTStorageProvider{TTL: c.ExtraConfig.EventTTL}, } m.InstallAPIs(c.ExtraConfig.APIResourceConfigSource, c.GenericConfig.RESTOptionsGetter, restStorageProviders...) @@ -372,9 +373,11 @@ func (m *Master) InstallLegacyAPI(c *completedConfig, restOptionsGetter generic. } if c.ExtraConfig.EnableCoreControllers { + controllerName := "bootstrap-controller" coreClient := coreclient.NewForConfigOrDie(c.GenericConfig.LoopbackClientConfig) - bootstrapController := c.NewBootstrapController(legacyRESTStorage, coreClient, coreClient) - m.GenericAPIServer.AddPostStartHookOrDie("bootstrap-controller", bootstrapController.PostStartHook) + bootstrapController := c.NewBootstrapController(legacyRESTStorage, coreClient, coreClient, coreClient) + m.GenericAPIServer.AddPostStartHookOrDie(controllerName, bootstrapController.PostStartHook) + m.GenericAPIServer.AddPreShutdownHookOrDie(controllerName, bootstrapController.PreShutdownHook) } if err := m.GenericAPIServer.InstallLegacyAPIGroup(genericapiserver.DefaultLegacyAPIPrefix, &apiGroupInfo); err != nil { @@ -480,6 +483,8 @@ func DefaultAPIResourceConfigSource() *serverstorage.ResourceConfig { authorizationapiv1.SchemeGroupVersion, authorizationapiv1beta1.SchemeGroupVersion, networkingapiv1.SchemeGroupVersion, + eventsv1beta1.SchemeGroupVersion, + admissionregistrationv1beta1.SchemeGroupVersion, ) // all extensions resources except these are disabled by default diff --git a/pkg/master/master_openapi_test.go b/pkg/master/master_openapi_test.go index 92501ca40f5..d640582a3ef 100644 --- a/pkg/master/master_openapi_test.go +++ b/pkg/master/master_openapi_test.go @@ -29,7 +29,7 @@ import ( apirequest "k8s.io/apiserver/pkg/endpoints/request" genericapiserver "k8s.io/apiserver/pkg/server" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" openapigen "k8s.io/kubernetes/pkg/generated/openapi" "github.com/go-openapi/loads" @@ -45,7 +45,7 @@ func TestValidOpenAPISpec(t *testing.T) { defer etcdserver.Terminate(t) config.GenericConfig.EnableIndex = true - config.GenericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(openapigen.GetOpenAPIDefinitions, api.Scheme) + config.GenericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(openapigen.GetOpenAPIDefinitions, legacyscheme.Scheme) config.GenericConfig.OpenAPIConfig.Info = &spec.Info{ InfoProps: spec.InfoProps{ Title: "Kubernetes", diff --git a/pkg/master/master_test.go b/pkg/master/master_test.go index 2e971c39275..506b81f9f6e 100644 --- a/pkg/master/master_test.go +++ b/pkg/master/master_test.go @@ -49,12 +49,13 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" restclient "k8s.io/client-go/rest" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/autoscaling" "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/certificates" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/rbac" kubeletclient "k8s.io/kubernetes/pkg/kubelet/client" @@ -69,10 +70,10 @@ import ( // setUp is a convience function for setting up for (most) tests. func setUp(t *testing.T) (*etcdtesting.EtcdTestServer, Config, informers.SharedInformerFactory, *assert.Assertions) { - server, storageConfig := etcdtesting.NewUnsecuredEtcd3TestClientServer(t, api.Scheme) + server, storageConfig := etcdtesting.NewUnsecuredEtcd3TestClientServer(t) config := &Config{ - GenericConfig: genericapiserver.NewConfig(api.Codecs), + GenericConfig: genericapiserver.NewConfig(legacyscheme.Codecs), ExtraConfig: ExtraConfig{ APIResourceConfigSource: DefaultAPIResourceConfigSource(), APIServerServicePort: 443, @@ -81,8 +82,8 @@ func setUp(t *testing.T) (*etcdtesting.EtcdTestServer, Config, informers.SharedI }, } - resourceEncoding := serverstorage.NewDefaultResourceEncodingConfig(api.Registry) - resourceEncoding.SetVersionEncoding(api.GroupName, api.Registry.GroupOrDie(api.GroupName).GroupVersion, schema.GroupVersion{Group: api.GroupName, Version: runtime.APIVersionInternal}) + resourceEncoding := serverstorage.NewDefaultResourceEncodingConfig(legacyscheme.Registry) + resourceEncoding.SetVersionEncoding(api.GroupName, legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion, schema.GroupVersion{Group: api.GroupName, Version: runtime.APIVersionInternal}) resourceEncoding.SetVersionEncoding(autoscaling.GroupName, *testapi.Autoscaling.GroupVersion(), schema.GroupVersion{Group: autoscaling.GroupName, Version: runtime.APIVersionInternal}) resourceEncoding.SetVersionEncoding(batch.GroupName, *testapi.Batch.GroupVersion(), schema.GroupVersion{Group: batch.GroupName, Version: runtime.APIVersionInternal}) // FIXME (soltysh): this GroupVersionResource override should be configurable @@ -91,7 +92,7 @@ func setUp(t *testing.T) (*etcdtesting.EtcdTestServer, Config, informers.SharedI resourceEncoding.SetVersionEncoding(extensions.GroupName, *testapi.Extensions.GroupVersion(), schema.GroupVersion{Group: extensions.GroupName, Version: runtime.APIVersionInternal}) resourceEncoding.SetVersionEncoding(rbac.GroupName, *testapi.Rbac.GroupVersion(), schema.GroupVersion{Group: rbac.GroupName, Version: runtime.APIVersionInternal}) resourceEncoding.SetVersionEncoding(certificates.GroupName, *testapi.Certificates.GroupVersion(), schema.GroupVersion{Group: certificates.GroupName, Version: runtime.APIVersionInternal}) - storageFactory := serverstorage.NewDefaultStorageFactory(*storageConfig, testapi.StorageMediaType(), api.Codecs, resourceEncoding, DefaultAPIResourceConfigSource(), nil) + storageFactory := serverstorage.NewDefaultStorageFactory(*storageConfig, testapi.StorageMediaType(), legacyscheme.Codecs, resourceEncoding, DefaultAPIResourceConfigSource(), nil) err := options.NewEtcdOptions(storageConfig).ApplyWithStorageFactoryTo(storageFactory, config.GenericConfig) if err != nil { @@ -101,11 +102,11 @@ func setUp(t *testing.T) (*etcdtesting.EtcdTestServer, Config, informers.SharedI kubeVersion := kubeversion.Get() config.GenericConfig.Version = &kubeVersion config.ExtraConfig.StorageFactory = storageFactory - config.GenericConfig.LoopbackClientConfig = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: api.Codecs}} + config.GenericConfig.LoopbackClientConfig = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs}} config.GenericConfig.PublicAddress = net.ParseIP("192.168.10.4") config.GenericConfig.LegacyAPIGroupPrefixes = sets.NewString("/api") config.GenericConfig.RequestContextMapper = genericapirequest.NewRequestContextMapper() - config.GenericConfig.LoopbackClientConfig = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: api.Codecs}} + config.GenericConfig.LoopbackClientConfig = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs}} config.GenericConfig.EnableMetrics = true config.ExtraConfig.EnableCoreControllers = false config.ExtraConfig.KubeletClientConfig = kubeletclient.KubeletClientConfig{Port: 10250} diff --git a/pkg/master/reconcilers/BUILD b/pkg/master/reconcilers/BUILD index f24446b9798..95f7d7ec631 100644 --- a/pkg/master/reconcilers/BUILD +++ b/pkg/master/reconcilers/BUILD @@ -12,8 +12,8 @@ go_library( importpath = "k8s.io/kubernetes/pkg/master/reconcilers", visibility = ["//visibility:public"], deps = [ - "//pkg/api:go_default_library", "//pkg/api/endpoints:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library", "//pkg/registry/core/endpoint:go_default_library", "//vendor/github.com/golang/glog:go_default_library", @@ -21,17 +21,19 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage:go_default_library", + "//vendor/k8s.io/client-go/util/retry:go_default_library", ], ) go_test( name = "go_default_test", srcs = ["lease_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/master/reconcilers", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", ], diff --git a/pkg/master/reconcilers/lease.go b/pkg/master/reconcilers/lease.go index 75e8d64c4a0..934ece29003 100644 --- a/pkg/master/reconcilers/lease.go +++ b/pkg/master/reconcilers/lease.go @@ -24,6 +24,8 @@ https://github.com/openshift/origin/blob/bb340c5dd5ff72718be86fb194dedc0faed7f4c import ( "fmt" "net" + "path" + "sync" "time" "github.com/golang/glog" @@ -31,9 +33,10 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kruntime "k8s.io/apimachinery/pkg/runtime" apirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/endpoints" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/core/endpoint" ) @@ -44,6 +47,9 @@ type Leases interface { // UpdateLease adds or refreshes a master's lease UpdateLease(ip string) error + + // RemoveLease removes a master's lease + RemoveLease(ip string) error } type storageLeases struct { @@ -73,7 +79,8 @@ func (s *storageLeases) ListLeases() ([]string, error) { // UpdateLease resets the TTL on a master IP in storage func (s *storageLeases) UpdateLease(ip string) error { - return s.storage.GuaranteedUpdate(apirequest.NewDefaultContext(), s.baseKey+"/"+ip, &api.Endpoints{}, true, nil, func(input kruntime.Object, respMeta storage.ResponseMeta) (kruntime.Object, *uint64, error) { + key := path.Join(s.baseKey, ip) + return s.storage.GuaranteedUpdate(apirequest.NewDefaultContext(), key, &api.Endpoints{}, true, nil, func(input kruntime.Object, respMeta storage.ResponseMeta) (kruntime.Object, *uint64, error) { // just make sure we've got the right IP set, and then refresh the TTL existing := input.(*api.Endpoints) existing.Subsets = []api.EndpointSubset{ @@ -82,7 +89,8 @@ func (s *storageLeases) UpdateLease(ip string) error { }, } - leaseTime := uint64(s.leaseTime) + // leaseTime needs to be in seconds + leaseTime := uint64(s.leaseTime / time.Second) // NB: GuaranteedUpdate does not perform the store operation unless // something changed between load and store (not including resource @@ -96,6 +104,11 @@ func (s *storageLeases) UpdateLease(ip string) error { }) } +// RemoveLease removes the lease on a master IP in storage +func (s *storageLeases) RemoveLease(ip string) error { + return s.storage.Delete(apirequest.NewDefaultContext(), s.baseKey+"/"+ip, &api.Endpoints{}, nil) +} + // NewLeases creates a new etcd-based Leases implementation. func NewLeases(storage storage.Interface, baseKey string, leaseTime time.Duration) Leases { return &storageLeases{ @@ -106,15 +119,18 @@ func NewLeases(storage storage.Interface, baseKey string, leaseTime time.Duratio } type leaseEndpointReconciler struct { - endpointRegistry endpoint.Registry - masterLeases Leases + endpointRegistry endpoint.Registry + masterLeases Leases + stopReconcilingCalled bool + reconcilingLock sync.Mutex } // NewLeaseEndpointReconciler creates a new LeaseEndpoint reconciler func NewLeaseEndpointReconciler(endpointRegistry endpoint.Registry, masterLeases Leases) EndpointReconciler { return &leaseEndpointReconciler{ - endpointRegistry: endpointRegistry, - masterLeases: masterLeases, + endpointRegistry: endpointRegistry, + masterLeases: masterLeases, + stopReconcilingCalled: false, } } @@ -126,7 +142,12 @@ func NewLeaseEndpointReconciler(endpointRegistry endpoint.Registry, masterLeases // different from the directory listing, and update the endpoints object // accordingly. func (r *leaseEndpointReconciler) ReconcileEndpoints(serviceName string, ip net.IP, endpointPorts []api.EndpointPort, reconcilePorts bool) error { - ctx := apirequest.NewDefaultContext() + r.reconcilingLock.Lock() + defer r.reconcilingLock.Unlock() + + if r.stopReconcilingCalled { + return nil + } // Refresh the TTL on our key, independently of whether any error or // update conflict happens below. This makes sure that at least some of @@ -135,6 +156,12 @@ func (r *leaseEndpointReconciler) ReconcileEndpoints(serviceName string, ip net. return err } + return r.doReconcile(serviceName, endpointPorts, reconcilePorts) +} + +func (r *leaseEndpointReconciler) doReconcile(serviceName string, endpointPorts []api.EndpointPort, reconcilePorts bool) error { + ctx := apirequest.NewDefaultContext() + // Retrieve the current list of endpoints... e, err := r.endpointRegistry.GetEndpoints(ctx, serviceName, &metav1.GetOptions{}) if err != nil { @@ -194,7 +221,7 @@ func (r *leaseEndpointReconciler) ReconcileEndpoints(serviceName string, ip net. } glog.Warningf("Resetting endpoints for master service %q to %v", serviceName, masterIPs) - return r.endpointRegistry.UpdateEndpoints(ctx, e) + return r.endpointRegistry.UpdateEndpoints(ctx, e, rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc) } // checkEndpointSubsetFormatWithLease determines if the endpoint is in the @@ -249,3 +276,15 @@ func checkEndpointSubsetFormatWithLease(e *api.Endpoints, expectedIPs []string, return true, ipsCorrect, portsCorrect } + +func (r *leaseEndpointReconciler) StopReconciling(serviceName string, ip net.IP, endpointPorts []api.EndpointPort) error { + r.reconcilingLock.Lock() + defer r.reconcilingLock.Unlock() + r.stopReconcilingCalled = true + + if err := r.masterLeases.RemoveLease(ip.String()); err != nil { + return err + } + + return r.doReconcile(serviceName, endpointPorts, true) +} diff --git a/pkg/master/reconcilers/lease_test.go b/pkg/master/reconcilers/lease_test.go index 5f2c66ee1e4..e16612e255e 100644 --- a/pkg/master/reconcilers/lease_test.go +++ b/pkg/master/reconcilers/lease_test.go @@ -27,7 +27,7 @@ import ( "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -54,6 +54,11 @@ func (f *fakeLeases) UpdateLease(ip string) error { return nil } +func (f *fakeLeases) RemoveLease(ip string) error { + delete(f.keys, ip) + return nil +} + func (f *fakeLeases) SetKeys(keys []string) { for _, ip := range keys { f.keys[ip] = false @@ -529,3 +534,100 @@ func TestLeaseEndpointReconciler(t *testing.T) { } } } + +func TestLeaseStopReconciling(t *testing.T) { + ns := api.NamespaceDefault + om := func(name string) metav1.ObjectMeta { + return metav1.ObjectMeta{Namespace: ns, Name: name} + } + stopTests := []struct { + testName string + serviceName string + ip string + endpointPorts []api.EndpointPort + endpointKeys []string + endpoints *api.EndpointsList + expectUpdate *api.Endpoints // nil means none expected + }{ + { + testName: "successful stop reconciling", + serviceName: "foo", + ip: "1.2.3.4", + endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + endpointKeys: []string{"1.2.3.4", "4.3.2.2", "4.3.2.3", "4.3.2.4"}, + endpoints: &api.EndpointsList{ + Items: []api.Endpoints{{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{ + {IP: "1.2.3.4"}, + {IP: "4.3.2.2"}, + {IP: "4.3.2.3"}, + {IP: "4.3.2.4"}, + }, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }}, + }, + expectUpdate: &api.Endpoints{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{ + {IP: "4.3.2.2"}, + {IP: "4.3.2.3"}, + {IP: "4.3.2.4"}, + }, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }, + }, + { + testName: "stop reconciling with ip not in endpoint ip list", + serviceName: "foo", + ip: "5.6.7.8", + endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + endpointKeys: []string{"1.2.3.4", "4.3.2.2", "4.3.2.3", "4.3.2.4"}, + endpoints: &api.EndpointsList{ + Items: []api.Endpoints{{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{ + {IP: "1.2.3.4"}, + {IP: "4.3.2.2"}, + {IP: "4.3.2.3"}, + {IP: "4.3.2.4"}, + }, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }}, + }, + }, + } + for _, test := range stopTests { + fakeLeases := newFakeLeases() + fakeLeases.SetKeys(test.endpointKeys) + registry := ®istrytest.EndpointRegistry{ + Endpoints: test.endpoints, + } + r := NewLeaseEndpointReconciler(registry, fakeLeases) + err := r.StopReconciling(test.serviceName, net.ParseIP(test.ip), test.endpointPorts) + if err != nil { + t.Errorf("case %q: unexpected error: %v", test.testName, err) + } + if test.expectUpdate != nil { + if len(registry.Updates) != 1 { + t.Errorf("case %q: unexpected updates: %v", test.testName, registry.Updates) + } else if e, a := test.expectUpdate, ®istry.Updates[0]; !reflect.DeepEqual(e, a) { + t.Errorf("case %q: expected update:\n%#v\ngot:\n%#v\n", test.testName, e, a) + } + } + if test.expectUpdate == nil && len(registry.Updates) > 0 { + t.Errorf("case %q: no update expected, yet saw: %v", test.testName, registry.Updates) + } + for _, key := range fakeLeases.GetUpdatedKeys() { + if key == test.ip { + t.Errorf("case %q: Found ip %s in leases but shouldn't be there", test.testName, key) + } + } + } +} diff --git a/pkg/master/reconcilers/mastercount.go b/pkg/master/reconcilers/mastercount.go index 5984fec2d60..d7aa4c77185 100644 --- a/pkg/master/reconcilers/mastercount.go +++ b/pkg/master/reconcilers/mastercount.go @@ -19,20 +19,24 @@ package reconcilers import ( "net" + "sync" "github.com/golang/glog" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" + "k8s.io/client-go/util/retry" "k8s.io/kubernetes/pkg/api/endpoints" + api "k8s.io/kubernetes/pkg/apis/core" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" ) // masterCountEndpointReconciler reconciles endpoints based on a specified expected number of // masters. masterCountEndpointReconciler implements EndpointReconciler. type masterCountEndpointReconciler struct { - masterCount int - endpointClient coreclient.EndpointsGetter + masterCount int + endpointClient coreclient.EndpointsGetter + stopReconcilingCalled bool + reconcilingLock sync.Mutex } // NewMasterCountEndpointReconciler creates a new EndpointReconciler that reconciles based on a @@ -57,6 +61,13 @@ func NewMasterCountEndpointReconciler(masterCount int, endpointClient coreclient // to be running (c.masterCount). // * ReconcileEndpoints is called periodically from all apiservers. func (r *masterCountEndpointReconciler) ReconcileEndpoints(serviceName string, ip net.IP, endpointPorts []api.EndpointPort, reconcilePorts bool) error { + r.reconcilingLock.Lock() + defer r.reconcilingLock.Unlock() + + if r.stopReconcilingCalled { + return nil + } + e, err := r.endpointClient.Endpoints(metav1.NamespaceDefault).Get(serviceName, metav1.GetOptions{}) if err != nil { e = &api.Endpoints{ @@ -126,6 +137,36 @@ func (r *masterCountEndpointReconciler) ReconcileEndpoints(serviceName string, i return err } +func (r *masterCountEndpointReconciler) StopReconciling(serviceName string, ip net.IP, endpointPorts []api.EndpointPort) error { + r.reconcilingLock.Lock() + defer r.reconcilingLock.Unlock() + r.stopReconcilingCalled = true + + e, err := r.endpointClient.Endpoints(metav1.NamespaceDefault).Get(serviceName, metav1.GetOptions{}) + if err != nil { + if errors.IsNotFound(err) { + // Endpoint doesn't exist + return nil + } + return err + } + + // Remove our IP from the list of addresses + new := []api.EndpointAddress{} + for _, addr := range e.Subsets[0].Addresses { + if addr.IP != ip.String() { + new = append(new, addr) + } + } + e.Subsets[0].Addresses = new + e.Subsets = endpoints.RepackSubsets(e.Subsets) + err = retry.RetryOnConflict(retry.DefaultBackoff, func() error { + _, err := r.endpointClient.Endpoints(metav1.NamespaceDefault).Update(e) + return err + }) + return err +} + // Determine if the endpoint is in the format ReconcileEndpoints expects. // // Return values: diff --git a/pkg/master/reconcilers/none.go b/pkg/master/reconcilers/none.go index 33e65ab7db0..dce38c2a66a 100644 --- a/pkg/master/reconcilers/none.go +++ b/pkg/master/reconcilers/none.go @@ -18,9 +18,8 @@ limitations under the License. package reconcilers import ( + api "k8s.io/kubernetes/pkg/apis/core" "net" - - "k8s.io/kubernetes/pkg/api" ) // NoneEndpointReconciler allows for the endpoint reconciler to be disabled @@ -36,3 +35,8 @@ func NewNoneEndpointReconciler() EndpointReconciler { func (r *noneEndpointReconciler) ReconcileEndpoints(serviceName string, ip net.IP, endpointPorts []api.EndpointPort, reconcilePorts bool) error { return nil } + +// StopReconciling noop reconcile +func (r *noneEndpointReconciler) StopReconciling(serviceName string, ip net.IP, endpointPorts []api.EndpointPort) error { + return nil +} diff --git a/pkg/master/reconcilers/reconcilers.go b/pkg/master/reconcilers/reconcilers.go index 346fa8fb529..8bbabc65901 100644 --- a/pkg/master/reconcilers/reconcilers.go +++ b/pkg/master/reconcilers/reconcilers.go @@ -18,9 +18,8 @@ limitations under the License. package reconcilers import ( + api "k8s.io/kubernetes/pkg/apis/core" "net" - - "k8s.io/kubernetes/pkg/api" ) // EndpointReconciler knows how to reconcile the endpoints for the apiserver service. @@ -36,6 +35,7 @@ type EndpointReconciler interface { // endpoints for their {rw, ro} services. // * ReconcileEndpoints is called periodically from all apiservers. ReconcileEndpoints(serviceName string, ip net.IP, endpointPorts []api.EndpointPort, reconcilePorts bool) error + StopReconciling(serviceName string, ip net.IP, endpointPorts []api.EndpointPort) error } // Type the reconciler type diff --git a/pkg/master/tunneler/BUILD b/pkg/master/tunneler/BUILD index dcaa51b8ea7..6e817fe31c3 100644 --- a/pkg/master/tunneler/BUILD +++ b/pkg/master/tunneler/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["ssh_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/master/tunneler", - library = ":go_default_library", deps = [ "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library", diff --git a/pkg/printers/BUILD b/pkg/printers/BUILD index 928ef3f8bd8..f4b90a98054 100644 --- a/pkg/printers/BUILD +++ b/pkg/printers/BUILD @@ -42,7 +42,8 @@ go_test( importpath = "k8s.io/kubernetes/pkg/printers_test", deps = [ ":go_default_library", - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", @@ -65,3 +66,16 @@ filegroup( ], tags = ["automanaged"], ) + +go_test( + name = "go_default_test", + srcs = ["humanreadable_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/printers", + deps = [ + "//pkg/apis/core:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1alpha1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + ], +) diff --git a/pkg/printers/customcolumn_test.go b/pkg/printers/customcolumn_test.go index 509aab3bada..cb67a4ef701 100644 --- a/pkg/printers/customcolumn_test.go +++ b/pkg/printers/customcolumn_test.go @@ -25,7 +25,8 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/printers" ) @@ -112,7 +113,7 @@ func TestNewColumnPrinterFromSpec(t *testing.T) { }, } for _, test := range tests { - printer, err := printers.NewCustomColumnsPrinterFromSpec(test.spec, api.Codecs.UniversalDecoder(), test.noHeaders) + printer, err := printers.NewCustomColumnsPrinterFromSpec(test.spec, legacyscheme.Codecs.UniversalDecoder(), test.noHeaders) if test.expectErr { if err == nil { t.Errorf("[%s] unexpected non-error", test.name) @@ -215,7 +216,7 @@ func TestNewColumnPrinterFromTemplate(t *testing.T) { } for _, test := range tests { reader := bytes.NewBufferString(test.spec) - printer, err := printers.NewCustomColumnsPrinterFromTemplate(reader, api.Codecs.UniversalDecoder()) + printer, err := printers.NewCustomColumnsPrinterFromTemplate(reader, legacyscheme.Codecs.UniversalDecoder()) if test.expectErr { if err == nil { t.Errorf("[%s] unexpected non-error", test.name) @@ -311,7 +312,7 @@ foo baz for _, test := range tests { printer := &printers.CustomColumnsPrinter{ Columns: test.columns, - Decoder: api.Codecs.UniversalDecoder(), + Decoder: legacyscheme.Codecs.UniversalDecoder(), } buffer := &bytes.Buffer{} if err := printer.PrintObj(test.obj, buffer); err != nil { diff --git a/pkg/printers/humanreadable.go b/pkg/printers/humanreadable.go index e75bdce9f2b..73ef967b150 100644 --- a/pkg/printers/humanreadable.go +++ b/pkg/printers/humanreadable.go @@ -525,6 +525,16 @@ func (h *HumanReadablePrinter) PrintTable(obj runtime.Object, options PrintOptio ColumnDefinitions: columns, Rows: results[0].Interface().([]metav1alpha1.TableRow), } + if m, err := meta.ListAccessor(obj); err == nil { + table.ResourceVersion = m.GetResourceVersion() + table.SelfLink = m.GetSelfLink() + table.Continue = m.GetContinue() + } else { + if m, err := meta.CommonAccessor(obj); err == nil { + table.ResourceVersion = m.GetResourceVersion() + table.SelfLink = m.GetSelfLink() + } + } if err := DecorateTable(table, options); err != nil { return nil, err } @@ -535,6 +545,15 @@ func (h *HumanReadablePrinter) PrintTable(obj runtime.Object, options PrintOptio // different from lastType) including all the rows in the object. It returns the current type // or an error, if any. func printRowsForHandlerEntry(output io.Writer, handler *handlerEntry, obj runtime.Object, options PrintOptions, includeHeaders bool) error { + var results []reflect.Value + if handler.printRows { + args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(options)} + results = handler.printFunc.Call(args) + if !results[1].IsNil() { + return results[1].Interface().(error) + } + } + if includeHeaders { var headers []string for _, column := range handler.columnDefinitions { @@ -562,15 +581,12 @@ func printRowsForHandlerEntry(output io.Writer, handler *handlerEntry, obj runti return resultValue.Interface().(error) } - args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(options)} - results := handler.printFunc.Call(args) if results[1].IsNil() { rows := results[0].Interface().([]metav1alpha1.TableRow) printRows(output, rows, options) return nil } return results[1].Interface().(error) - } // printRows writes the provided rows to output. diff --git a/pkg/printers/humanreadable_test.go b/pkg/printers/humanreadable_test.go new file mode 100644 index 00000000000..7c45b868e3a --- /dev/null +++ b/pkg/printers/humanreadable_test.go @@ -0,0 +1,138 @@ +/* +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 printers + +import ( + "bytes" + "fmt" + "reflect" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + metav1alpha1 "k8s.io/apimachinery/pkg/apis/meta/v1alpha1" + "k8s.io/apimachinery/pkg/runtime" + api "k8s.io/kubernetes/pkg/apis/core" +) + +var testNamespaceColumnDefinitions = []metav1alpha1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, + {Name: "Status", Type: "string", Description: "The status of the namespace"}, + {Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]}, +} + +func testPrintNamespace(obj *api.Namespace, options PrintOptions) ([]metav1alpha1.TableRow, error) { + if options.WithNamespace { + return nil, fmt.Errorf("namespace is not namespaced") + } + row := metav1alpha1.TableRow{ + Object: runtime.RawExtension{Object: obj}, + } + row.Cells = append(row.Cells, obj.Name, obj.Status.Phase, "") + return []metav1alpha1.TableRow{row}, nil +} + +func testPrintNamespaceList(list *api.NamespaceList, options PrintOptions) ([]metav1alpha1.TableRow, error) { + rows := make([]metav1alpha1.TableRow, 0, len(list.Items)) + for i := range list.Items { + r, err := testPrintNamespace(&list.Items[i], options) + if err != nil { + return nil, err + } + rows = append(rows, r...) + } + return rows, nil +} +func TestPrintRowsForHandlerEntry(t *testing.T) { + printFunc := reflect.ValueOf(testPrintNamespace) + + testCase := map[string]struct { + h *handlerEntry + opt PrintOptions + obj runtime.Object + includeHeader bool + expectOut string + expectErr string + }{ + "no tablecolumndefinition and includeheader flase": { + h: &handlerEntry{ + columnDefinitions: []metav1alpha1.TableColumnDefinition{}, + printRows: true, + printFunc: printFunc, + }, + opt: PrintOptions{}, + obj: &api.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + }, + includeHeader: false, + expectOut: "test\t\t\n", + }, + "no tablecolumndefinition and includeheader true": { + h: &handlerEntry{ + columnDefinitions: []metav1alpha1.TableColumnDefinition{}, + printRows: true, + printFunc: printFunc, + }, + opt: PrintOptions{}, + obj: &api.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + }, + includeHeader: true, + expectOut: "\ntest\t\t\n", + }, + "have tablecolumndefinition and includeheader true": { + h: &handlerEntry{ + columnDefinitions: testNamespaceColumnDefinitions, + printRows: true, + printFunc: printFunc, + }, + opt: PrintOptions{}, + obj: &api.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + }, + includeHeader: true, + expectOut: "NAME\tSTATUS\tAGE\ntest\t\t\n", + }, + "print namespace and withnamespace true, should not print header": { + h: &handlerEntry{ + columnDefinitions: testNamespaceColumnDefinitions, + printRows: true, + printFunc: printFunc, + }, + opt: PrintOptions{ + WithNamespace: true, + }, + obj: &api.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + }, + includeHeader: true, + expectOut: "", + expectErr: "namespace is not namespaced", + }, + } + for name, test := range testCase { + buffer := &bytes.Buffer{} + err := printRowsForHandlerEntry(buffer, test.h, test.obj, test.opt, test.includeHeader) + if err != nil { + if err.Error() != test.expectErr { + t.Errorf("[%s]expect:\n %v\n but got:\n %v\n", name, test.expectErr, err) + } + } + if test.expectOut != buffer.String() { + t.Errorf("[%s]expect:\n %v\n but got:\n %v\n", name, test.expectOut, buffer.String()) + } + } +} diff --git a/pkg/printers/interface.go b/pkg/printers/interface.go index 52712158646..5bda6c61753 100644 --- a/pkg/printers/interface.go +++ b/pkg/printers/interface.go @@ -57,6 +57,10 @@ func (fn ResourcePrinterFunc) IsGeneric() bool { } type PrintOptions struct { + // supported Format types can be found in pkg/printers/printers.go + OutputFormatType string + OutputFormatArgument string + NoHeaders bool WithNamespace bool WithKind bool @@ -66,6 +70,11 @@ type PrintOptions struct { AbsoluteTimestamps bool Kind string ColumnLabels []string + + SortBy string + + // indicates if it is OK to ignore missing keys for rendering an output template. + AllowMissingKeys bool } // Describer generates output for the named resource or an error @@ -100,13 +109,3 @@ type ErrNoDescriber struct { func (e ErrNoDescriber) Error() string { return fmt.Sprintf("no describer has been defined for %v", e.Types) } - -// OutputOptions represents resource output options which is used to generate a resource printer. -type OutputOptions struct { - // supported Format types can be found in pkg/printers/printers.go - FmtType string - FmtArg string - - // indicates if it is OK to ignore missing keys for rendering an output template. - AllowMissingKeys bool -} diff --git a/pkg/printers/internalversion/BUILD b/pkg/printers/internalversion/BUILD index 1422aa13911..edbd167520d 100644 --- a/pkg/printers/internalversion/BUILD +++ b/pkg/printers/internalversion/BUILD @@ -13,23 +13,21 @@ go_test( "printers_test.go", "sorted_resource_name_list_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/printers/internalversion", - library = ":go_default_library", deps = [ - "//federation/apis/federation:go_default_library", - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/client/clientset_generated/federation_clientset/fake:go_default_library", - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/testapi:go_default_library", "//pkg/apis/apps:go_default_library", "//pkg/apis/autoscaling:go_default_library", "//pkg/apis/batch:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", + "//pkg/apis/networking:go_default_library", "//pkg/apis/policy:go_default_library", "//pkg/apis/storage:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", - "//pkg/kubectl/testing:go_default_library", "//pkg/printers:go_default_library", "//pkg/util/pointer:go_default_library", "//vendor/github.com/ghodss/yaml:go_default_library", @@ -58,19 +56,17 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/printers/internalversion", deps = [ - "//federation/apis/federation:go_default_library", - "//federation/apis/federation/v1beta1:go_default_library", - "//federation/client/clientset_generated/federation_clientset:go_default_library", - "//pkg/api:go_default_library", "//pkg/api/events:go_default_library", - "//pkg/api/helper:go_default_library", - "//pkg/api/helper/qos:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/ref:go_default_library", "//pkg/api/resource:go_default_library", "//pkg/apis/apps:go_default_library", "//pkg/apis/autoscaling:go_default_library", "//pkg/apis/batch:go_default_library", "//pkg/apis/certificates:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", + "//pkg/apis/core/helper/qos:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/apis/networking:go_default_library", "//pkg/apis/policy:go_default_library", diff --git a/pkg/printers/internalversion/describe.go b/pkg/printers/internalversion/describe.go index 59c2407237b..43bc2b98113 100644 --- a/pkg/printers/internalversion/describe.go +++ b/pkg/printers/internalversion/describe.go @@ -19,7 +19,6 @@ package internalversion import ( "bytes" "crypto/x509" - "encoding/json" "fmt" "io" "net" @@ -47,18 +46,17 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/dynamic" clientextensionsv1beta1 "k8s.io/client-go/kubernetes/typed/extensions/v1beta1" - federation "k8s.io/kubernetes/federation/apis/federation/v1beta1" - fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/events" - "k8s.io/kubernetes/pkg/api/helper" - "k8s.io/kubernetes/pkg/api/helper/qos" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/ref" resourcehelper "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/autoscaling" "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/certificates" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" + "k8s.io/kubernetes/pkg/apis/core/helper/qos" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/networking" "k8s.io/kubernetes/pkg/apis/policy" @@ -211,7 +209,7 @@ func (g *genericDescriber) Describe(namespace, name string, describerSettings pr var events *api.EventList if describerSettings.ShowEvents { - events, _ = g.events.Events(namespace).Search(api.Scheme, obj) + events, _ = g.events.Events(namespace).Search(legacyscheme.Scheme, obj) } return tabbedString(func(out io.Writer) error { @@ -607,11 +605,11 @@ func (d *PodDescriber) Describe(namespace, name string, describerSettings printe var events *api.EventList if describerSettings.ShowEvents { - if ref, err := ref.GetReference(api.Scheme, pod); err != nil { + if ref, err := ref.GetReference(legacyscheme.Scheme, pod); err != nil { glog.Errorf("Unable to construct reference to '%#v': %v", pod, err) } else { ref.Kind = "" - events, _ = d.Core().Events(namespace).Search(api.Scheme, ref) + events, _ = d.Core().Events(namespace).Search(legacyscheme.Scheme, ref) } } @@ -623,6 +621,10 @@ func describePod(pod *api.Pod, events *api.EventList) (string, error) { w := NewPrefixWriter(out) w.Write(LEVEL_0, "Name:\t%s\n", pod.Name) w.Write(LEVEL_0, "Namespace:\t%s\n", pod.Namespace) + if pod.Spec.Priority != nil { + w.Write(LEVEL_0, "Priority:\t%d\n", *pod.Spec.Priority) + w.Write(LEVEL_0, "PriorityClassName:\t%s\n", stringOrNone(pod.Spec.PriorityClassName)) + } if pod.Spec.NodeName == "" { w.Write(LEVEL_0, "Node:\t\n") } else { @@ -646,9 +648,6 @@ func describePod(pod *api.Pod, events *api.EventList) (string, error) { w.Write(LEVEL_0, "Message:\t%s\n", pod.Status.Message) } w.Write(LEVEL_0, "IP:\t%s\n", pod.Status.PodIP) - if createdBy := printCreator(pod.Annotations); len(createdBy) > 0 { - w.Write(LEVEL_0, "Created By:\t%s\n", createdBy) - } if controlledBy := printController(pod); len(controlledBy) > 0 { w.Write(LEVEL_0, "Controlled By:\t%s\n", controlledBy) } @@ -687,18 +686,6 @@ func printController(controllee metav1.Object) string { return "" } -func printCreator(annotation map[string]string) string { - value, ok := annotation[api.CreatedByAnnotation] - if ok { - var r api.SerializedReference - err := json.Unmarshal([]byte(value), &r) - if err == nil { - return fmt.Sprintf("%s/%s", r.Reference.Kind, r.Reference.Name) - } - } - return "" -} - func describeVolumes(volumes []api.Volume, w PrefixWriter, space string) { if volumes == nil || len(volumes) == 0 { w.Write(LEVEL_0, "%sVolumes:\t\n", space) @@ -869,6 +856,26 @@ func printISCSIVolumeSource(iscsi *api.ISCSIVolumeSource, w PrefixWriter) { iscsi.TargetPortal, iscsi.IQN, iscsi.Lun, iscsi.ISCSIInterface, iscsi.FSType, iscsi.ReadOnly, iscsi.Portals, iscsi.DiscoveryCHAPAuth, iscsi.SessionCHAPAuth, iscsi.SecretRef, initiator) } +func printISCSIPersistentVolumeSource(iscsi *api.ISCSIPersistentVolumeSource, w PrefixWriter) { + initiatorName := "" + if iscsi.InitiatorName != nil { + initiatorName = *iscsi.InitiatorName + } + w.Write(LEVEL_2, "Type:\tISCSI (an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod)\n"+ + " TargetPortal:\t%v\n"+ + " IQN:\t%v\n"+ + " Lun:\t%v\n"+ + " ISCSIInterface\t%v\n"+ + " FSType:\t%v\n"+ + " ReadOnly:\t%v\n"+ + " Portals:\t%v\n"+ + " DiscoveryCHAPAuth:\t%v\n"+ + " SessionCHAPAuth:\t%v\n"+ + " SecretRef:\t%v\n"+ + " InitiatorName:\t%v\n", + iscsi.TargetPortal, iscsi.IQN, iscsi.Lun, iscsi.ISCSIInterface, iscsi.FSType, iscsi.ReadOnly, iscsi.Portals, iscsi.DiscoveryCHAPAuth, iscsi.SessionCHAPAuth, iscsi.SecretRef, initiatorName) +} + func printGlusterfsVolumeSource(glusterfs *api.GlusterfsVolumeSource, w PrefixWriter) { w.Write(LEVEL_2, "Type:\tGlusterfs (a Glusterfs mount on the host that shares a pod's lifetime)\n"+ " EndpointsName:\t%v\n"+ @@ -897,6 +904,19 @@ func printRBDVolumeSource(rbd *api.RBDVolumeSource, w PrefixWriter) { rbd.CephMonitors, rbd.RBDImage, rbd.FSType, rbd.RBDPool, rbd.RadosUser, rbd.Keyring, rbd.SecretRef, rbd.ReadOnly) } +func printRBDPersistentVolumeSource(rbd *api.RBDPersistentVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tRBD (a Rados Block Device mount on the host that shares a pod's lifetime)\n"+ + " CephMonitors:\t%v\n"+ + " RBDImage:\t%v\n"+ + " FSType:\t%v\n"+ + " RBDPool:\t%v\n"+ + " RadosUser:\t%v\n"+ + " Keyring:\t%v\n"+ + " SecretRef:\t%v\n"+ + " ReadOnly:\t%v\n", + rbd.CephMonitors, rbd.RBDImage, rbd.FSType, rbd.RBDPool, rbd.RadosUser, rbd.Keyring, rbd.SecretRef, rbd.ReadOnly) +} + func printDownwardAPIVolumeSource(d *api.DownwardAPIVolumeSource, w PrefixWriter) { w.Write(LEVEL_2, "Type:\tDownwardAPI (a volume populated by information about the pod)\n Items:\n") for _, mapping := range d.Items { @@ -923,7 +943,7 @@ func printAzureDiskVolumeSource(d *api.AzureDiskVolumeSource, w PrefixWriter) { func printVsphereVolumeSource(vsphere *api.VsphereVirtualDiskVolumeSource, w PrefixWriter) { w.Write(LEVEL_2, "Type:\tvSphereVolume (a Persistent Disk resource in vSphere)\n"+ " VolumePath:\t%v\n"+ - " FSType:\t%v\n", + " FSType:\t%v\n"+ " StoragePolicyName:\t%v\n", vsphere.VolumePath, vsphere.FSType, vsphere.StoragePolicyName) } @@ -956,6 +976,26 @@ func printScaleIOVolumeSource(sio *api.ScaleIOVolumeSource, w PrefixWriter) { sio.Gateway, sio.System, sio.ProtectionDomain, sio.StoragePool, sio.StorageMode, sio.VolumeName, sio.FSType, sio.ReadOnly) } +func printScaleIOPersistentVolumeSource(sio *api.ScaleIOPersistentVolumeSource, w PrefixWriter) { + var secretNS, secretName string + if sio.SecretRef != nil { + secretName = sio.SecretRef.Name + secretNS = sio.SecretRef.Namespace + } + w.Write(LEVEL_2, "Type:\tScaleIO (a persistent volume backed by a block device in ScaleIO)\n"+ + " Gateway:\t%v\n"+ + " System:\t%v\n"+ + " Protection Domain:\t%v\n"+ + " Storage Pool:\t%v\n"+ + " Storage Mode:\t%v\n"+ + " VolumeName:\t%v\n"+ + " SecretName:\t%v\n"+ + " SecretNamespace:\t%v\n"+ + " FSType:\t%v\n"+ + " ReadOnly:\t%v\n", + sio.Gateway, sio.System, sio.ProtectionDomain, sio.StoragePool, sio.StorageMode, sio.VolumeName, secretName, secretNS, sio.FSType, sio.ReadOnly) +} + func printLocalVolumeSource(ls *api.LocalVolumeSource, w PrefixWriter) { w.Write(LEVEL_2, "Type:\tLocalVolume (a persistent volume backed by local storage on a node)\n"+ " Path:\t%v\n", @@ -1036,12 +1076,22 @@ func printAzureFilePersistentVolumeSource(azureFile *api.AzureFilePersistentVolu azureFile.SecretName, ns, azureFile.ShareName, azureFile.ReadOnly) } +func printFlexPersistentVolumeSource(flex *api.FlexPersistentVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tFlexVolume (a generic volume resource that is provisioned/attached using an exec based plugin)\n"+ + " Driver:\t%v\n"+ + " FSType:\t%v\n"+ + " SecretRef:\t%v\n"+ + " ReadOnly:\t%v\n"+ + " Options:\t%v\n", + flex.Driver, flex.FSType, flex.SecretRef, flex.ReadOnly, flex.Options) +} + func printFlexVolumeSource(flex *api.FlexVolumeSource, w PrefixWriter) { w.Write(LEVEL_2, "Type:\tFlexVolume (a generic volume resource that is provisioned/attached using an exec based plugin)\n"+ " Driver:\t%v\n"+ " FSType:\t%v\n"+ " SecretRef:\t%v\n"+ - " ReadOnly:\t%v\n", + " ReadOnly:\t%v\n"+ " Options:\t%v\n", flex.Driver, flex.FSType, flex.SecretRef, flex.ReadOnly, flex.Options) } @@ -1053,6 +1103,14 @@ func printFlockerVolumeSource(flocker *api.FlockerVolumeSource, w PrefixWriter) flocker.DatasetName, flocker.DatasetUUID) } +func printCSIPersistentVolumeSource(csi *api.CSIPersistentVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tCSI (a Container Storage Interface (CSI) volume source)\n"+ + " Driver:\t%v\n"+ + " VolumeHandle:\t%v\n"+ + " ReadOnly:\t%v\n", + csi.Driver, csi.VolumeHandle, csi.ReadOnly) +} + type PersistentVolumeDescriber struct { clientset.Interface } @@ -1067,7 +1125,7 @@ func (d *PersistentVolumeDescriber) Describe(namespace, name string, describerSe var events *api.EventList if describerSettings.ShowEvents { - events, _ = d.Core().Events(namespace).Search(api.Scheme, pv) + events, _ = d.Core().Events(namespace).Search(legacyscheme.Scheme, pv) } return describePersistentVolume(pv, events) @@ -1088,6 +1146,9 @@ func describePersistentVolume(pv *api.PersistentVolume, events *api.EventList) ( } w.Write(LEVEL_0, "Reclaim Policy:\t%v\n", pv.Spec.PersistentVolumeReclaimPolicy) w.Write(LEVEL_0, "Access Modes:\t%s\n", helper.GetAccessModesAsString(pv.Spec.AccessModes)) + if pv.Spec.VolumeMode != nil { + w.Write(LEVEL_0, "VolumeMode:\t%v\n", *pv.Spec.VolumeMode) + } storage := pv.Spec.Capacity[api.ResourceStorage] w.Write(LEVEL_0, "Capacity:\t%s\n", storage.String()) w.Write(LEVEL_0, "Message:\t%s\n", pv.Status.Message) @@ -1103,11 +1164,11 @@ func describePersistentVolume(pv *api.PersistentVolume, events *api.EventList) ( case pv.Spec.NFS != nil: printNFSVolumeSource(pv.Spec.NFS, w) case pv.Spec.ISCSI != nil: - printISCSIVolumeSource(pv.Spec.ISCSI, w) + printISCSIPersistentVolumeSource(pv.Spec.ISCSI, w) case pv.Spec.Glusterfs != nil: printGlusterfsVolumeSource(pv.Spec.Glusterfs, w) case pv.Spec.RBD != nil: - printRBDVolumeSource(pv.Spec.RBD, w) + printRBDPersistentVolumeSource(pv.Spec.RBD, w) case pv.Spec.Quobyte != nil: printQuobyteVolumeSource(pv.Spec.Quobyte, w) case pv.Spec.VsphereVolume != nil: @@ -1121,7 +1182,7 @@ func describePersistentVolume(pv *api.PersistentVolume, events *api.EventList) ( case pv.Spec.PortworxVolume != nil: printPortworxVolumeSource(pv.Spec.PortworxVolume, w) case pv.Spec.ScaleIO != nil: - printScaleIOVolumeSource(pv.Spec.ScaleIO, w) + printScaleIOPersistentVolumeSource(pv.Spec.ScaleIO, w) case pv.Spec.Local != nil: printLocalVolumeSource(pv.Spec.Local, w) case pv.Spec.CephFS != nil: @@ -1133,9 +1194,11 @@ func describePersistentVolume(pv *api.PersistentVolume, events *api.EventList) ( case pv.Spec.AzureFile != nil: printAzureFilePersistentVolumeSource(pv.Spec.AzureFile, w) case pv.Spec.FlexVolume != nil: - printFlexVolumeSource(pv.Spec.FlexVolume, w) + printFlexPersistentVolumeSource(pv.Spec.FlexVolume, w) case pv.Spec.Flocker != nil: printFlockerVolumeSource(pv.Spec.Flocker, w) + case pv.Spec.CSI != nil: + printCSIPersistentVolumeSource(pv.Spec.CSI, w) default: w.Write(LEVEL_1, "\n") } @@ -1160,7 +1223,7 @@ func (d *PersistentVolumeClaimDescriber) Describe(namespace, name string, descri return "", err } - events, _ := d.Core().Events(namespace).Search(api.Scheme, pvc) + events, _ := d.Core().Events(namespace).Search(legacyscheme.Scheme, pvc) return describePersistentVolumeClaim(pvc, events) } @@ -1171,10 +1234,15 @@ func describePersistentVolumeClaim(pvc *api.PersistentVolumeClaim, events *api.E w.Write(LEVEL_0, "Name:\t%s\n", pvc.Name) w.Write(LEVEL_0, "Namespace:\t%s\n", pvc.Namespace) w.Write(LEVEL_0, "StorageClass:\t%s\n", helper.GetPersistentVolumeClaimClass(pvc)) - w.Write(LEVEL_0, "Status:\t%v\n", pvc.Status.Phase) + if pvc.ObjectMeta.DeletionTimestamp != nil { + w.Write(LEVEL_0, "Status:\tTerminating (since %s)\n", pvc.ObjectMeta.DeletionTimestamp.Time.Format(time.RFC1123Z)) + } else { + w.Write(LEVEL_0, "Status:\t%v\n", pvc.Status.Phase) + } w.Write(LEVEL_0, "Volume:\t%s\n", pvc.Spec.VolumeName) printLabelsMultiline(w, "Labels", pvc.Labels) printAnnotationsMultiline(w, "Annotations", pvc.Annotations) + w.Write(LEVEL_0, "Finalizers:\t%v\n", pvc.ObjectMeta.Finalizers) storage := pvc.Spec.Resources.Requests[api.ResourceStorage] capacity := "" accessModes := "" @@ -1185,6 +1253,9 @@ func describePersistentVolumeClaim(pvc *api.PersistentVolumeClaim, events *api.E } w.Write(LEVEL_0, "Capacity:\t%s\n", capacity) w.Write(LEVEL_0, "Access Modes:\t%s\n", accessModes) + if pvc.Spec.VolumeMode != nil { + w.Write(LEVEL_0, "VolumeMode:\t%v\n", *pvc.Spec.VolumeMode) + } if events != nil { DescribeEvents(events, w) } @@ -1315,6 +1386,7 @@ func describeContainerProbe(container api.Container, w PrefixWriter) { } func describeContainerVolumes(container api.Container, w PrefixWriter) { + // Show volumeMounts none := "" if len(container.VolumeMounts) == 0 { none = "\t" @@ -1333,6 +1405,14 @@ func describeContainerVolumes(container api.Container, w PrefixWriter) { } w.Write(LEVEL_3, "%s from %s (%s)\n", mount.MountPath, mount.Name, strings.Join(flags, ",")) } + // Show volumeDevices if exists + if len(container.VolumeDevices) > 0 { + w.Write(LEVEL_2, "Devices:%s\n", none) + sort.Sort(SortableVolumeDevices(container.VolumeDevices)) + for _, device := range container.VolumeDevices { + w.Write(LEVEL_3, "%s from %s\n", device.DevicePath, device.Name) + } + } } func describeContainerEnvVars(container api.Container, resolverFn EnvVarResolverFunc, w PrefixWriter) { @@ -1430,7 +1510,7 @@ type EnvVarResolverFunc func(e api.EnvVar) string // EnvValueFrom is exported for use by describers in other packages func EnvValueRetriever(pod *api.Pod) EnvVarResolverFunc { return func(e api.EnvVar) string { - internalFieldPath, _, err := api.Scheme.ConvertFieldLabel(e.ValueFrom.FieldRef.APIVersion, "Pod", e.ValueFrom.FieldRef.FieldPath, "") + internalFieldPath, _, err := legacyscheme.Scheme.ConvertFieldLabel(e.ValueFrom.FieldRef.APIVersion, "Pod", e.ValueFrom.FieldRef.FieldPath, "") if err != nil { return "" // pod validation should catch this on create } @@ -1531,7 +1611,7 @@ func (d *ReplicationControllerDescriber) Describe(namespace, name string, descri var events *api.EventList if describerSettings.ShowEvents { - events, _ = d.Core().Events(namespace).Search(api.Scheme, controller) + events, _ = d.Core().Events(namespace).Search(legacyscheme.Scheme, controller) } return describeReplicationController(controller, events, running, waiting, succeeded, failed) @@ -1605,7 +1685,7 @@ func (d *ReplicaSetDescriber) Describe(namespace, name string, describerSettings var events *api.EventList if describerSettings.ShowEvents { - events, _ = d.Core().Events(namespace).Search(api.Scheme, rs) + events, _ = d.Core().Events(namespace).Search(legacyscheme.Scheme, rs) } return describeReplicaSet(rs, events, running, waiting, succeeded, failed, getPodErr) @@ -1657,7 +1737,7 @@ func (d *JobDescriber) Describe(namespace, name string, describerSettings printe var events *api.EventList if describerSettings.ShowEvents { - events, _ = d.Core().Events(namespace).Search(api.Scheme, job) + events, _ = d.Core().Events(namespace).Search(legacyscheme.Scheme, job) } return describeJob(job, events) @@ -1672,8 +1752,8 @@ func describeJob(job *batch.Job, events *api.EventList) (string, error) { w.Write(LEVEL_0, "Selector:\t%s\n", selector) printLabelsMultiline(w, "Labels", job.Labels) printAnnotationsMultiline(w, "Annotations", job.Annotations) - if createdBy := printCreator(job.Annotations); len(createdBy) > 0 { - w.Write(LEVEL_0, "Created By:\t%s\n", createdBy) + if controlledBy := printController(job); len(controlledBy) > 0 { + w.Write(LEVEL_0, "Controlled By:\t%s\n", controlledBy) } w.Write(LEVEL_0, "Parallelism:\t%d\n", *job.Spec.Parallelism) if job.Spec.Completions != nil { @@ -1709,7 +1789,7 @@ func (d *CronJobDescriber) Describe(namespace, name string, describerSettings pr var events *api.EventList if describerSettings.ShowEvents { - events, _ = d.Core().Events(namespace).Search(api.Scheme, cronJob) + events, _ = d.Core().Events(namespace).Search(legacyscheme.Scheme, cronJob) } return describeCronJob(cronJob, events) @@ -1808,7 +1888,7 @@ func (d *DaemonSetDescriber) Describe(namespace, name string, describerSettings var events *api.EventList if describerSettings.ShowEvents { - events, _ = d.Core().Events(namespace).Search(api.Scheme, daemon) + events, _ = d.Core().Events(namespace).Search(legacyscheme.Scheme, daemon) } return describeDaemonSet(daemon, events, running, waiting, succeeded, failed) @@ -1959,7 +2039,7 @@ func (i *IngressDescriber) describeIngress(ing *extensions.Ingress, describerSet describeIngressAnnotations(w, ing.Annotations) if describerSettings.ShowEvents { - events, _ := i.Core().Events(ing.Namespace).Search(api.Scheme, ing) + events, _ := i.Core().Events(ing.Namespace).Search(legacyscheme.Scheme, ing) if events != nil { DescribeEvents(events, w) } @@ -2010,7 +2090,7 @@ func (d *ServiceDescriber) Describe(namespace, name string, describerSettings pr endpoints, _ := d.Core().Endpoints(namespace).Get(name, metav1.GetOptions{}) var events *api.EventList if describerSettings.ShowEvents { - events, _ = d.Core().Events(namespace).Search(api.Scheme, service) + events, _ = d.Core().Events(namespace).Search(legacyscheme.Scheme, service) } return describeService(service, endpoints, events) } @@ -2068,7 +2148,7 @@ func describeService(service *api.Service, endpoints *api.Endpoints, events *api if sp.TargetPort.Type == intstr.Type(intstr.Int) { w.Write(LEVEL_0, "TargetPort:\t%d/%s\n", sp.TargetPort.IntVal, sp.Protocol) } else { - w.Write(LEVEL_0, "TargetPort:\t%d/%s\n", sp.TargetPort.StrVal, sp.Protocol) + w.Write(LEVEL_0, "TargetPort:\t%s/%s\n", sp.TargetPort.StrVal, sp.Protocol) } if sp.NodePort != 0 { w.Write(LEVEL_0, "NodePort:\t%s\t%d/%s\n", name, sp.NodePort, sp.Protocol) @@ -2107,7 +2187,7 @@ func (d *EndpointsDescriber) Describe(namespace, name string, describerSettings var events *api.EventList if describerSettings.ShowEvents { - events, _ = d.Core().Events(namespace).Search(api.Scheme, ep) + events, _ = d.Core().Events(namespace).Search(legacyscheme.Scheme, ep) } return describeEndpoints(ep, events) @@ -2219,7 +2299,7 @@ func (d *ServiceAccountDescriber) Describe(namespace, name string, describerSett var events *api.EventList if describerSettings.ShowEvents { - events, _ = d.Core().Events(namespace).Search(api.Scheme, serviceAccount) + events, _ = d.Core().Events(namespace).Search(legacyscheme.Scheme, serviceAccount) } return describeServiceAccount(serviceAccount, tokens, missingSecrets, events) @@ -2232,7 +2312,6 @@ func describeServiceAccount(serviceAccount *api.ServiceAccount, tokens []api.Sec w.Write(LEVEL_0, "Namespace:\t%s\n", serviceAccount.Namespace) printLabelsMultiline(w, "Labels", serviceAccount.Labels) printAnnotationsMultiline(w, "Annotations", serviceAccount.Annotations) - w.WriteLine() var ( emptyHeader = " " @@ -2260,7 +2339,8 @@ func describeServiceAccount(serviceAccount *api.ServiceAccount, tokens []api.Sec mountHeader: mountSecretNames, tokenHeader: tokenSecretNames, } - for header, names := range types { + for _, header := range sets.StringKeySet(types).List() { + names := types[header] if len(names) == 0 { w.Write(LEVEL_0, "%s\t\n", header) } else { @@ -2274,7 +2354,6 @@ func describeServiceAccount(serviceAccount *api.ServiceAccount, tokens []api.Sec prefix = emptyHeader } } - w.WriteLine() } if events != nil { @@ -2473,12 +2552,12 @@ func (d *NodeDescriber) Describe(namespace, name string, describerSettings print var events *api.EventList if describerSettings.ShowEvents { - if ref, err := ref.GetReference(api.Scheme, node); err != nil { + if ref, err := ref.GetReference(legacyscheme.Scheme, node); err != nil { glog.Errorf("Unable to construct reference to '%#v': %v", node, err) } else { // TODO: We haven't decided the namespace for Node object yet. ref.UID = types.UID(ref.Name) - events, _ = d.Core().Events("").Search(api.Scheme, ref) + events, _ = d.Core().Events("").Search(legacyscheme.Scheme, ref) } } @@ -2556,10 +2635,11 @@ func describeNode(node *api.Node, nodeNonTerminatedPodsList *api.PodList, events if len(node.Spec.ExternalID) > 0 { w.Write(LEVEL_0, "ExternalID:\t%s\n", node.Spec.ExternalID) } + if len(node.Spec.ProviderID) > 0 { + w.Write(LEVEL_0, "ProviderID:\t%s\n", node.Spec.ProviderID) + } if canViewPods && nodeNonTerminatedPodsList != nil { - if err := describeNodeResource(nodeNonTerminatedPodsList, node, w); err != nil { - return err - } + describeNodeResource(nodeNonTerminatedPodsList, node, w) } else { w.Write(LEVEL_0, "Pods:\tnot authorized\n") } @@ -2593,7 +2673,7 @@ func (p *StatefulSetDescriber) Describe(namespace, name string, describerSetting var events *api.EventList if describerSettings.ShowEvents { - events, _ = p.client.Core().Events(namespace).Search(api.Scheme, ps) + events, _ = p.client.Core().Events(namespace).Search(legacyscheme.Scheme, ps) } return describeStatefulSet(ps, selector, events, running, waiting, succeeded, failed) @@ -2641,7 +2721,7 @@ func (p *CertificateSigningRequestDescriber) Describe(namespace, name string, de var events *api.EventList if describerSettings.ShowEvents { - events, _ = p.client.Core().Events(namespace).Search(api.Scheme, csr) + events, _ = p.client.Core().Events(namespace).Search(legacyscheme.Scheme, csr) } return describeCertificateSigningRequest(csr, cr, status, events) @@ -2709,7 +2789,7 @@ func (d *HorizontalPodAutoscalerDescriber) Describe(namespace, name string, desc var events *api.EventList if describerSettings.ShowEvents { - events, _ = d.client.Core().Events(namespace).Search(api.Scheme, hpa) + events, _ = d.client.Core().Events(namespace).Search(legacyscheme.Scheme, hpa) } return describeHorizontalPodAutoscaler(hpa, events, d) @@ -2799,7 +2879,7 @@ func describeHorizontalPodAutoscaler(hpa *autoscaling.HorizontalPodAutoscaler, e }) } -func describeNodeResource(nodeNonTerminatedPodsList *api.PodList, node *api.Node, w PrefixWriter) error { +func describeNodeResource(nodeNonTerminatedPodsList *api.PodList, node *api.Node, w PrefixWriter) { w.Write(LEVEL_0, "Non-terminated Pods:\t(%d in total)\n", len(nodeNonTerminatedPodsList.Items)) w.Write(LEVEL_1, "Namespace\tName\t\tCPU Requests\tCPU Limits\tMemory Requests\tMemory Limits\n") w.Write(LEVEL_1, "---------\t----\t\t------------\t----------\t---------------\t-------------\n") @@ -2809,10 +2889,7 @@ func describeNodeResource(nodeNonTerminatedPodsList *api.PodList, node *api.Node } for _, pod := range nodeNonTerminatedPodsList.Items { - req, limit, err := resourcehelper.PodRequestsAndLimits(&pod) - if err != nil { - return err - } + req, limit := resourcehelper.PodRequestsAndLimits(&pod) cpuReq, cpuLimit, memoryReq, memoryLimit := req[api.ResourceCPU], limit[api.ResourceCPU], req[api.ResourceMemory], limit[api.ResourceMemory] fractionCpuReq := float64(cpuReq.MilliValue()) / float64(allocatable.Cpu().MilliValue()) * 100 fractionCpuLimit := float64(cpuLimit.MilliValue()) / float64(allocatable.Cpu().MilliValue()) * 100 @@ -2825,10 +2902,7 @@ func describeNodeResource(nodeNonTerminatedPodsList *api.PodList, node *api.Node w.Write(LEVEL_0, "Allocated resources:\n (Total limits may be over 100 percent, i.e., overcommitted.)\n CPU Requests\tCPU Limits\tMemory Requests\tMemory Limits\n") w.Write(LEVEL_1, "------------\t----------\t---------------\t-------------\n") - reqs, limits, err := getPodsTotalRequestsAndLimits(nodeNonTerminatedPodsList) - if err != nil { - return err - } + reqs, limits := getPodsTotalRequestsAndLimits(nodeNonTerminatedPodsList) cpuReqs, cpuLimits, memoryReqs, memoryLimits := reqs[api.ResourceCPU], limits[api.ResourceCPU], reqs[api.ResourceMemory], limits[api.ResourceMemory] fractionCpuReqs := float64(cpuReqs.MilliValue()) / float64(allocatable.Cpu().MilliValue()) * 100 fractionCpuLimits := float64(cpuLimits.MilliValue()) / float64(allocatable.Cpu().MilliValue()) * 100 @@ -2837,16 +2911,12 @@ func describeNodeResource(nodeNonTerminatedPodsList *api.PodList, node *api.Node w.Write(LEVEL_1, "%s (%d%%)\t%s (%d%%)\t%s (%d%%)\t%s (%d%%)\n", cpuReqs.String(), int64(fractionCpuReqs), cpuLimits.String(), int64(fractionCpuLimits), memoryReqs.String(), int64(fractionMemoryReqs), memoryLimits.String(), int64(fractionMemoryLimits)) - return nil } -func getPodsTotalRequestsAndLimits(podList *api.PodList) (reqs map[api.ResourceName]resource.Quantity, limits map[api.ResourceName]resource.Quantity, err error) { +func getPodsTotalRequestsAndLimits(podList *api.PodList) (reqs map[api.ResourceName]resource.Quantity, limits map[api.ResourceName]resource.Quantity) { reqs, limits = map[api.ResourceName]resource.Quantity{}, map[api.ResourceName]resource.Quantity{} for _, pod := range podList.Items { - podReqs, podLimits, err := resourcehelper.PodRequestsAndLimits(&pod) - if err != nil { - return nil, nil, err - } + podReqs, podLimits := resourcehelper.PodRequestsAndLimits(&pod) for podReqName, podReqValue := range podReqs { if value, ok := reqs[podReqName]; !ok { reqs[podReqName] = *podReqValue.Copy() @@ -2909,13 +2979,13 @@ func (dd *DeploymentDescriber) Describe(namespace, name string, describerSetting return "", err } internalDeployment := &extensions.Deployment{} - if err := api.Scheme.Convert(d, internalDeployment, extensions.SchemeGroupVersion); err != nil { + if err := legacyscheme.Scheme.Convert(d, internalDeployment, extensions.SchemeGroupVersion); err != nil { return "", err } var events *api.EventList if describerSettings.ShowEvents { - events, _ = dd.Core().Events(namespace).Search(api.Scheme, d) + events, _ = dd.Core().Events(namespace).Search(legacyscheme.Scheme, d) } return describeDeployment(d, selector, internalDeployment, events, dd) @@ -3028,7 +3098,7 @@ func (d *ConfigMapDescriber) Describe(namespace, name string, describerSettings w.Write(LEVEL_0, "%s\n", string(v)) } if describerSettings.ShowEvents { - events, err := d.Core().Events(namespace).Search(api.Scheme, configMap) + events, err := d.Core().Events(namespace).Search(legacyscheme.Scheme, configMap) if err != nil { return err } @@ -3040,47 +3110,6 @@ func (d *ConfigMapDescriber) Describe(namespace, name string, describerSettings }) } -type ClusterDescriber struct { - fedclientset.Interface -} - -func (d *ClusterDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - cluster, err := d.Federation().Clusters().Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - return describeCluster(cluster) -} - -func describeCluster(cluster *federation.Cluster) (string, error) { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", cluster.Name) - w.Write(LEVEL_0, "Labels:\t%s\n", labels.FormatLabels(cluster.Labels)) - - w.Write(LEVEL_0, "ServerAddressByClientCIDRs:\n ClientCIDR\tServerAddress\n") - w.Write(LEVEL_1, "----\t----\n") - for _, cidrAddr := range cluster.Spec.ServerAddressByClientCIDRs { - w.Write(LEVEL_1, "%v \t%v\n\n", cidrAddr.ClientCIDR, cidrAddr.ServerAddress) - } - - if len(cluster.Status.Conditions) > 0 { - w.Write(LEVEL_0, "Conditions:\n Type\tStatus\tLastUpdateTime\tLastTransitionTime\tReason\tMessage\n") - w.Write(LEVEL_1, "----\t------\t-----------------\t------------------\t------\t-------\n") - for _, c := range cluster.Status.Conditions { - w.Write(LEVEL_1, "%v \t%v \t%s \t%s \t%v \t%v\n", - c.Type, - c.Status, - c.LastProbeTime.Time.Format(time.RFC1123Z), - c.LastTransitionTime.Time.Format(time.RFC1123Z), - c.Reason, - c.Message) - } - } - return nil - }) -} - // NetworkPolicyDescriber generates information about a networking.NetworkPolicy type NetworkPolicyDescriber struct { clientset.Interface @@ -3102,13 +3131,111 @@ func describeNetworkPolicy(networkPolicy *networking.NetworkPolicy) (string, err w := NewPrefixWriter(out) w.Write(LEVEL_0, "Name:\t%s\n", networkPolicy.Name) w.Write(LEVEL_0, "Namespace:\t%s\n", networkPolicy.Namespace) + w.Write(LEVEL_0, "Created on:\t%s\n", networkPolicy.CreationTimestamp) printLabelsMultiline(w, "Labels", networkPolicy.Labels) printAnnotationsMultiline(w, "Annotations", networkPolicy.Annotations) - + describeNetworkPolicySpec(networkPolicy.Spec, w) return nil }) } +func describeNetworkPolicySpec(nps networking.NetworkPolicySpec, w PrefixWriter) { + w.Write(LEVEL_0, "Spec:\n") + w.Write(LEVEL_1, "PodSelector: ") + if len(nps.PodSelector.MatchLabels) == 0 && len(nps.PodSelector.MatchExpressions) == 0 { + w.Write(LEVEL_2, " (Allowing the specific traffic to all pods in this namespace)\n") + } else { + w.Write(LEVEL_2, "%s\n", metav1.FormatLabelSelector(&nps.PodSelector)) + } + w.Write(LEVEL_1, "Allowing ingress traffic:\n") + printNetworkPolicySpecIngressFrom(nps.Ingress, " ", w) + w.Write(LEVEL_1, "Allowing egress traffic:\n") + printNetworkPolicySpecEgressTo(nps.Egress, " ", w) + w.Write(LEVEL_1, "Policy Types: %v\n", policyTypesToString(nps.PolicyTypes)) +} + +func printNetworkPolicySpecIngressFrom(npirs []networking.NetworkPolicyIngressRule, initialIndent string, w PrefixWriter) { + if len(npirs) == 0 { + w.Write(LEVEL_0, "%s%s\n", initialIndent, " (Selected pods are isolated for ingress connectivity)") + return + } + for i, npir := range npirs { + if len(npir.Ports) == 0 { + w.Write(LEVEL_0, "%s%s\n", initialIndent, "To Port: (traffic allowed to all ports)") + } else { + for _, port := range npir.Ports { + var proto api.Protocol + if port.Protocol != nil { + proto = *port.Protocol + } else { + proto = api.ProtocolTCP + } + w.Write(LEVEL_0, "%s%s: %s/%s\n", initialIndent, "To Port", port.Port, proto) + } + } + if len(npir.From) == 0 { + w.Write(LEVEL_0, "%s%s\n", initialIndent, "From: (traffic not restricted by source)") + } else { + for _, from := range npir.From { + w.Write(LEVEL_0, "%s", initialIndent) + if from.PodSelector != nil { + w.Write(LEVEL_0, "%s: %s\n", "From PodSelector", metav1.FormatLabelSelector(from.PodSelector)) + } else if from.NamespaceSelector != nil { + w.Write(LEVEL_0, "%s: %s\n", "From NamespaceSelector", metav1.FormatLabelSelector(from.NamespaceSelector)) + } else if from.IPBlock != nil { + w.Write(LEVEL_0, "From IPBlock:\n") + w.Write(LEVEL_0, "%s%sCIDR: %s\n", initialIndent, initialIndent, from.IPBlock.CIDR) + w.Write(LEVEL_0, "%s%sExcept: %v\n", initialIndent, initialIndent, strings.Join(from.IPBlock.Except, ", ")) + } + } + } + if i != len(npirs)-1 { + w.Write(LEVEL_0, "%s%s\n", initialIndent, "----------") + } + } +} + +func printNetworkPolicySpecEgressTo(npers []networking.NetworkPolicyEgressRule, initialIndent string, w PrefixWriter) { + if len(npers) == 0 { + w.Write(LEVEL_0, "%s%s\n", initialIndent, " (Selected pods are isolated for egress connectivity)") + return + } + for i, nper := range npers { + if len(nper.Ports) == 0 { + w.Write(LEVEL_0, "%s%s\n", initialIndent, "To Port: (traffic allowed to all ports)") + } else { + for _, port := range nper.Ports { + var proto api.Protocol + if port.Protocol != nil { + proto = *port.Protocol + } else { + proto = api.ProtocolTCP + } + w.Write(LEVEL_0, "%s%s: %s/%s\n", initialIndent, "To Port", port.Port, proto) + } + } + if len(nper.To) == 0 { + w.Write(LEVEL_0, "%s%s\n", initialIndent, "To: (traffic not restricted by source)") + } else { + for _, to := range nper.To { + w.Write(LEVEL_0, "%s", initialIndent) + if to.PodSelector != nil { + w.Write(LEVEL_0, "%s: %s\n", "To PodSelector", metav1.FormatLabelSelector(to.PodSelector)) + } else if to.NamespaceSelector != nil { + w.Write(LEVEL_0, "%s: %s\n", "To NamespaceSelector", metav1.FormatLabelSelector(to.NamespaceSelector)) + } else if to.IPBlock != nil { + w.Write(LEVEL_0, "To IPBlock:\n") + w.Write(LEVEL_0, "%s%sCIDR: %s\n", initialIndent, initialIndent, to.IPBlock.CIDR) + w.Write(LEVEL_0, "%s%sExcept: %v\n", initialIndent, initialIndent, strings.Join(to.IPBlock.Except, ", ")) + } + } + } + if i != len(npers)-1 { + w.Write(LEVEL_0, "%s%s\n", initialIndent, "----------") + } + } +} + type StorageClassDescriber struct { clientset.Interface } @@ -3121,7 +3248,7 @@ func (s *StorageClassDescriber) Describe(namespace, name string, describerSettin var events *api.EventList if describerSettings.ShowEvents { - events, _ = s.Core().Events(namespace).Search(api.Scheme, sc) + events, _ = s.Core().Events(namespace).Search(legacyscheme.Scheme, sc) } return describeStorageClass(sc, events) @@ -3138,6 +3265,9 @@ func describeStorageClass(sc *storage.StorageClass, events *api.EventList) (stri if sc.ReclaimPolicy != nil { w.Write(LEVEL_0, "ReclaimPolicy:\t%s\n", *sc.ReclaimPolicy) } + if sc.VolumeBindingMode != nil { + w.Write(LEVEL_0, "VolumeBindingMode:\t%s\n", *sc.VolumeBindingMode) + } if events != nil { DescribeEvents(events, w) } @@ -3158,7 +3288,7 @@ func (p *PodDisruptionBudgetDescriber) Describe(namespace, name string, describe var events *api.EventList if describerSettings.ShowEvents { - events, _ = p.Core().Events(namespace).Search(api.Scheme, pdb) + events, _ = p.Core().Events(namespace).Search(legacyscheme.Scheme, pdb) } return describePodDisruptionBudget(pdb, events) @@ -3207,7 +3337,7 @@ func (s *PriorityClassDescriber) Describe(namespace, name string, describerSetti var events *api.EventList if describerSettings.ShowEvents { - events, _ = s.Core().Events(namespace).Search(api.Scheme, pc) + events, _ = s.Core().Events(namespace).Search(legacyscheme.Scheme, pc) } return describePriorityClass(pc, events) @@ -3257,6 +3387,9 @@ func describePodSecurityPolicy(psp *extensions.PodSecurityPolicy) (string, error w.Write(LEVEL_1, "Allowed Capabilities:\t%s\n", capsToString(psp.Spec.AllowedCapabilities)) w.Write(LEVEL_1, "Allowed Volume Types:\t%s\n", fsTypeToString(psp.Spec.Volumes)) + if len(psp.Spec.AllowedFlexVolumes) > 0 { + w.Write(LEVEL_1, "Allowed FlexVolume Types:\t%s\n", flexVolumesToString(psp.Spec.AllowedFlexVolumes)) + } w.Write(LEVEL_1, "Allow Host Network:\t%t\n", psp.Spec.HostNetwork) w.Write(LEVEL_1, "Allow Host Ports:\t%s\n", hostPortRangeToString(psp.Spec.HostPorts)) w.Write(LEVEL_1, "Allow Host PID:\t%t\n", psp.Spec.HostPID) @@ -3289,18 +3422,15 @@ func describePodSecurityPolicy(psp *extensions.PodSecurityPolicy) (string, error }) } -func stringOrAll(s string) string { - if len(s) > 0 { - return s - } - return "*" +func stringOrNone(s string) string { + return stringOrDefaultValue(s, "") } -func stringOrNone(s string) string { +func stringOrDefaultValue(s, defaultValue string) string { if len(s) > 0 { return s } - return "" + return defaultValue } func fsTypeToString(volumes []extensions.FSType) string { @@ -3311,6 +3441,14 @@ func fsTypeToString(volumes []extensions.FSType) string { return stringOrNone(strings.Join(strVolumes, ",")) } +func flexVolumesToString(flexVolumes []extensions.AllowedFlexVolume) string { + volumes := []string{} + for _, flexVolume := range flexVolumes { + volumes = append(volumes, "driver="+flexVolume.Driver) + } + return stringOrDefaultValue(strings.Join(volumes, ","), "") +} + func hostPortRangeToString(ranges []extensions.HostPortRange) string { formattedString := "" if ranges != nil { @@ -3359,6 +3497,18 @@ func capsToString(caps []api.Capability) string { return stringOrNone(formattedString) } +func policyTypesToString(pts []networking.PolicyType) string { + formattedString := "" + if pts != nil { + strPts := []string{} + for _, p := range pts { + strPts = append(strPts, string(p)) + } + formattedString = strings.Join(strPts, ", ") + } + return stringOrNone(formattedString) +} + // newErrNoDescriber creates a new ErrNoDescriber with the names of the provided types. func newErrNoDescriber(types ...reflect.Type) error { names := make([]string, 0, len(types)) @@ -3689,6 +3839,20 @@ func (list SortableVolumeMounts) Less(i, j int) bool { return list[i].MountPath < list[j].MountPath } +type SortableVolumeDevices []api.VolumeDevice + +func (list SortableVolumeDevices) Len() int { + return len(list) +} + +func (list SortableVolumeDevices) Swap(i, j int) { + list[i], list[j] = list[j], list[i] +} + +func (list SortableVolumeDevices) Less(i, j int) bool { + return list[i].DevicePath < list[j].DevicePath +} + // TODO: get rid of this and plumb the caller correctly func versionedExtensionsClientV1beta1(internalClient clientset.Interface) clientextensionsv1beta1.ExtensionsV1beta1Interface { if internalClient == nil { diff --git a/pkg/printers/internalversion/describe_test.go b/pkg/printers/internalversion/describe_test.go index 5fb87952868..71dfd3f05f4 100644 --- a/pkg/printers/internalversion/describe_test.go +++ b/pkg/printers/internalversion/describe_test.go @@ -33,11 +33,10 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/intstr" versionedfake "k8s.io/client-go/kubernetes/fake" - federation "k8s.io/kubernetes/federation/apis/federation/v1beta1" - fedfake "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/fake" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/autoscaling" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/networking" "k8s.io/kubernetes/pkg/apis/policy" "k8s.io/kubernetes/pkg/apis/storage" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" @@ -127,6 +126,31 @@ func TestDescribePodTolerations(t *testing.T) { } } +func TestDescribeSecret(t *testing.T) { + fake := fake.NewSimpleClientset(&api.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + Namespace: "foo", + }, + Data: map[string][]byte{ + "username": []byte("YWRtaW4="), + "password": []byte("MWYyZDFlMmU2N2Rm"), + }, + }) + c := &describeClient{T: t, Namespace: "foo", Interface: fake} + d := SecretDescriber{c} + out, err := d.Describe("foo", "bar", printers.DescriberSettings{}) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if !strings.Contains(out, "bar") || !strings.Contains(out, "foo") || !strings.Contains(out, "username") || !strings.Contains(out, "8 bytes") || !strings.Contains(out, "password") || !strings.Contains(out, "16 bytes") { + t.Errorf("unexpected out: %s", out) + } + if strings.Contains(out, "YWRtaW4=") || strings.Contains(out, "MWYyZDFlMmU2N2Rm") { + t.Errorf("sensitive data should not be shown, unexpected out: %s", out) + } +} + func TestDescribeNamespace(t *testing.T) { fake := fake.NewSimpleClientset(&api.Namespace{ ObjectMeta: metav1.ObjectMeta{ @@ -144,6 +168,28 @@ func TestDescribeNamespace(t *testing.T) { } } +func TestDescribePodPriority(t *testing.T) { + priority := int32(1000) + fake := fake.NewSimpleClientset(&api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + }, + Spec: api.PodSpec{ + PriorityClassName: "high-priority", + Priority: &priority, + }, + }) + c := &describeClient{T: t, Namespace: "", Interface: fake} + d := PodDescriber{c} + out, err := d.Describe("", "bar", printers.DescriberSettings{ShowEvents: true}) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if !strings.Contains(out, "high-priority") || !strings.Contains(out, "1000") { + t.Errorf("unexpected out: %s", out) + } +} + func TestDescribeConfigMap(t *testing.T) { fake := fake.NewSimpleClientset(&api.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ @@ -231,50 +277,97 @@ func getResourceList(cpu, memory string) api.ResourceList { } func TestDescribeService(t *testing.T) { - fake := fake.NewSimpleClientset(&api.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bar", - Namespace: "foo", + testCases := []struct { + service *api.Service + expect []string + }{ + { + service: &api.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + Namespace: "foo", + }, + Spec: api.ServiceSpec{ + Type: api.ServiceTypeLoadBalancer, + Ports: []api.ServicePort{{ + Name: "port-tcp", + Port: 8080, + Protocol: api.ProtocolTCP, + TargetPort: intstr.FromInt(9527), + NodePort: 31111, + }}, + Selector: map[string]string{"blah": "heh"}, + ClusterIP: "1.2.3.4", + LoadBalancerIP: "5.6.7.8", + SessionAffinity: "None", + ExternalTrafficPolicy: "Local", + HealthCheckNodePort: 32222, + }, + }, + expect: []string{ + "Name", "bar", + "Namespace", "foo", + "Selector", "blah=heh", + "Type", "LoadBalancer", + "IP", "1.2.3.4", + "Port", "port-tcp", "8080/TCP", + "TargetPort", "9527/TCP", + "NodePort", "port-tcp", "31111/TCP", + "Session Affinity", "None", + "External Traffic Policy", "Local", + "HealthCheck NodePort", "32222", + }, }, - Spec: api.ServiceSpec{ - Type: api.ServiceTypeLoadBalancer, - Ports: []api.ServicePort{{ - Name: "port-tcp", - Port: 8080, - Protocol: api.ProtocolTCP, - TargetPort: intstr.FromInt(9527), - NodePort: 31111, - }}, - Selector: map[string]string{"blah": "heh"}, - ClusterIP: "1.2.3.4", - LoadBalancerIP: "5.6.7.8", - SessionAffinity: "None", - ExternalTrafficPolicy: "Local", - HealthCheckNodePort: 32222, + { + service: &api.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + Namespace: "foo", + }, + Spec: api.ServiceSpec{ + Type: api.ServiceTypeLoadBalancer, + Ports: []api.ServicePort{{ + Name: "port-tcp", + Port: 8080, + Protocol: api.ProtocolTCP, + TargetPort: intstr.FromString("targetPort"), + NodePort: 31111, + }}, + Selector: map[string]string{"blah": "heh"}, + ClusterIP: "1.2.3.4", + LoadBalancerIP: "5.6.7.8", + SessionAffinity: "None", + ExternalTrafficPolicy: "Local", + HealthCheckNodePort: 32222, + }, + }, + expect: []string{ + "Name", "bar", + "Namespace", "foo", + "Selector", "blah=heh", + "Type", "LoadBalancer", + "IP", "1.2.3.4", + "Port", "port-tcp", "8080/TCP", + "TargetPort", "targetPort/TCP", + "NodePort", "port-tcp", "31111/TCP", + "Session Affinity", "None", + "External Traffic Policy", "Local", + "HealthCheck NodePort", "32222", + }, }, - }) - expectedElements := []string{ - "Name", "bar", - "Namespace", "foo", - "Selector", "blah=heh", - "Type", "LoadBalancer", - "IP", "1.2.3.4", - "Port", "port-tcp", "8080/TCP", - "TargetPort", "9527/TCP", - "NodePort", "port-tcp", "31111/TCP", - "Session Affinity", "None", - "External Traffic Policy", "Local", - "HealthCheck NodePort", "32222", } - c := &describeClient{T: t, Namespace: "foo", Interface: fake} - d := ServiceDescriber{c} - out, err := d.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true}) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - for _, expected := range expectedElements { - if !strings.Contains(out, expected) { - t.Errorf("expected to find %q in output: %q", expected, out) + for _, testCase := range testCases { + fake := fake.NewSimpleClientset(testCase.service) + c := &describeClient{T: t, Namespace: "foo", Interface: fake} + d := ServiceDescriber{c} + out, err := d.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true}) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + for _, expected := range testCase.expect { + if !strings.Contains(out, expected) { + t.Errorf("expected to find %q in output: %q", expected, out) + } } } } @@ -541,6 +634,50 @@ func TestDescribeContainers(t *testing.T) { }, expectedElements: []string{"cpu", "1k", "memory", "4G", "storage", "20G"}, }, + // volumeMounts read/write + { + container: api.Container{ + Name: "test", + Image: "image", + VolumeMounts: []api.VolumeMount{ + { + Name: "mounted-volume", + MountPath: "/opt/", + }, + }, + }, + expectedElements: []string{"mounted-volume", "/opt/", "(rw)"}, + }, + // volumeMounts readonly + { + container: api.Container{ + Name: "test", + Image: "image", + VolumeMounts: []api.VolumeMount{ + { + Name: "mounted-volume", + MountPath: "/opt/", + ReadOnly: true, + }, + }, + }, + expectedElements: []string{"Mounts", "mounted-volume", "/opt/", "(ro)"}, + }, + + // volumeDevices + { + container: api.Container{ + Name: "test", + Image: "image", + VolumeDevices: []api.VolumeDevice{ + { + Name: "volume-device", + DevicePath: "/dev/xvda", + }, + }, + }, + expectedElements: []string{"Devices", "volume-device", "/dev/xvda"}, + }, } for i, testCase := range testCases { @@ -589,7 +726,7 @@ func TestDescribers(t *testing.T) { t.Errorf("unexpected result: %s %v", out, err) } else { if noDescriber, ok := err.(printers.ErrNoDescriber); ok { - if !reflect.DeepEqual(noDescriber.Types, []string{"*api.Event", "*api.Pod", "*api.Pod"}) { + if !reflect.DeepEqual(noDescriber.Types, []string{"*core.Event", "*core.Pod", "*core.Pod"}) { t.Errorf("unexpected describer: %v", err) } } else { @@ -711,10 +848,7 @@ func TestGetPodsTotalRequests(t *testing.T) { } for _, testCase := range testCases { - reqs, _, err := getPodsTotalRequestsAndLimits(testCase.pods) - if err != nil { - t.Errorf("Unexpected error %v", err) - } + reqs, _ := getPodsTotalRequestsAndLimits(testCase.pods) if !apiequality.Semantic.DeepEqual(reqs, testCase.expectedReqs) { t.Errorf("Expected %v, got %v", testCase.expectedReqs, reqs) } @@ -722,99 +856,237 @@ func TestGetPodsTotalRequests(t *testing.T) { } func TestPersistentVolumeDescriber(t *testing.T) { - tests := map[string]*api.PersistentVolume{ - - "hostpath": { - ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - HostPath: &api.HostPathVolumeSource{Type: new(api.HostPathType)}, + block := api.PersistentVolumeBlock + file := api.PersistentVolumeFilesystem + testCases := []struct { + plugin string + pv *api.PersistentVolume + expectedElements []string + unexpectedElements []string + }{ + { + plugin: "hostpath", + pv: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{Name: "bar"}, + Spec: api.PersistentVolumeSpec{ + PersistentVolumeSource: api.PersistentVolumeSource{ + HostPath: &api.HostPathVolumeSource{Type: new(api.HostPathType)}, + }, }, }, + unexpectedElements: []string{"VolumeMode", "Filesystem"}, }, - "gce": { - ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{}, + { + plugin: "gce", + pv: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{Name: "bar"}, + Spec: api.PersistentVolumeSpec{ + PersistentVolumeSource: api.PersistentVolumeSource{ + GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{}, + }, + VolumeMode: &file, }, }, + expectedElements: []string{"VolumeMode", "Filesystem"}, }, - "ebs": { - ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{}, + { + plugin: "ebs", + pv: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{Name: "bar"}, + Spec: api.PersistentVolumeSpec{ + PersistentVolumeSource: api.PersistentVolumeSource{ + AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{}, + }, }, }, + unexpectedElements: []string{"VolumeMode", "Filesystem"}, }, - "nfs": { - ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - NFS: &api.NFSVolumeSource{}, + { + plugin: "nfs", + pv: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{Name: "bar"}, + Spec: api.PersistentVolumeSpec{ + PersistentVolumeSource: api.PersistentVolumeSource{ + NFS: &api.NFSVolumeSource{}, + }, }, }, + unexpectedElements: []string{"VolumeMode", "Filesystem"}, }, - "iscsi": { - ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - ISCSI: &api.ISCSIVolumeSource{}, + { + plugin: "iscsi", + pv: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{Name: "bar"}, + Spec: api.PersistentVolumeSpec{ + PersistentVolumeSource: api.PersistentVolumeSource{ + ISCSI: &api.ISCSIPersistentVolumeSource{}, + }, + VolumeMode: &block, }, }, + expectedElements: []string{"VolumeMode", "Block"}, }, - "gluster": { - ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - Glusterfs: &api.GlusterfsVolumeSource{}, + { + plugin: "gluster", + pv: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{Name: "bar"}, + Spec: api.PersistentVolumeSpec{ + PersistentVolumeSource: api.PersistentVolumeSource{ + Glusterfs: &api.GlusterfsVolumeSource{}, + }, }, }, + unexpectedElements: []string{"VolumeMode", "Filesystem"}, }, - "rbd": { - ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - RBD: &api.RBDVolumeSource{}, + { + plugin: "rbd", + pv: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{Name: "bar"}, + Spec: api.PersistentVolumeSpec{ + PersistentVolumeSource: api.PersistentVolumeSource{ + RBD: &api.RBDPersistentVolumeSource{}, + }, }, }, + unexpectedElements: []string{"VolumeMode", "Filesystem"}, }, - "quobyte": { - ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - Quobyte: &api.QuobyteVolumeSource{}, + { + plugin: "quobyte", + pv: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{Name: "bar"}, + Spec: api.PersistentVolumeSpec{ + PersistentVolumeSource: api.PersistentVolumeSource{ + Quobyte: &api.QuobyteVolumeSource{}, + }, }, }, + unexpectedElements: []string{"VolumeMode", "Filesystem"}, }, - "cinder": { - ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - Cinder: &api.CinderVolumeSource{}, + { + plugin: "cinder", + pv: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{Name: "bar"}, + Spec: api.PersistentVolumeSpec{ + PersistentVolumeSource: api.PersistentVolumeSource{ + Cinder: &api.CinderVolumeSource{}, + }, }, }, + unexpectedElements: []string{"VolumeMode", "Filesystem"}, }, - "fc": { - ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - FC: &api.FCVolumeSource{}, + { + plugin: "fc", + pv: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{Name: "bar"}, + Spec: api.PersistentVolumeSpec{ + PersistentVolumeSource: api.PersistentVolumeSource{ + FC: &api.FCVolumeSource{}, + }, + VolumeMode: &block, }, }, + expectedElements: []string{"VolumeMode", "Block"}, }, } - for name, pv := range tests { - fake := fake.NewSimpleClientset(pv) + for _, test := range testCases { + fake := fake.NewSimpleClientset(test.pv) c := PersistentVolumeDescriber{fake} str, err := c.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true}) if err != nil { - t.Errorf("Unexpected error for test %s: %v", name, err) + t.Errorf("Unexpected error for test %s: %v", test.plugin, err) } if str == "" { - t.Errorf("Unexpected empty string for test %s. Expected PV Describer output", name) + t.Errorf("Unexpected empty string for test %s. Expected PV Describer output", test.plugin) + } + for _, expected := range test.expectedElements { + if !strings.Contains(str, expected) { + t.Errorf("expected to find %q in output: %q", expected, str) + } + } + for _, unexpected := range test.unexpectedElements { + if strings.Contains(str, unexpected) { + t.Errorf("unexpected to find %q in output: %q", unexpected, str) + } + } + } +} + +func TestPersistentVolumeClaimDescriber(t *testing.T) { + block := api.PersistentVolumeBlock + file := api.PersistentVolumeFilesystem + goldClassName := "gold" + testCases := []struct { + name string + pvc *api.PersistentVolumeClaim + expectedElements []string + unexpectedElements []string + }{ + { + name: "default", + pvc: &api.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}, + Spec: api.PersistentVolumeClaimSpec{ + VolumeName: "volume1", + StorageClassName: &goldClassName, + }, + Status: api.PersistentVolumeClaimStatus{ + Phase: api.ClaimBound, + }, + }, + unexpectedElements: []string{"VolumeMode", "Filesystem"}, + }, + { + name: "filesystem", + pvc: &api.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}, + Spec: api.PersistentVolumeClaimSpec{ + VolumeName: "volume2", + StorageClassName: &goldClassName, + VolumeMode: &file, + }, + Status: api.PersistentVolumeClaimStatus{ + Phase: api.ClaimBound, + }, + }, + expectedElements: []string{"VolumeMode", "Filesystem"}, + }, + { + name: "block", + pvc: &api.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}, + Spec: api.PersistentVolumeClaimSpec{ + VolumeName: "volume3", + StorageClassName: &goldClassName, + VolumeMode: &block, + }, + Status: api.PersistentVolumeClaimStatus{ + Phase: api.ClaimBound, + }, + }, + expectedElements: []string{"VolumeMode", "Block"}, + }, + } + + for _, test := range testCases { + fake := fake.NewSimpleClientset(test.pvc) + c := PersistentVolumeClaimDescriber{fake} + str, err := c.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true}) + if err != nil { + t.Errorf("Unexpected error for test %s: %v", test.name, err) + } + if str == "" { + t.Errorf("Unexpected empty string for test %s. Expected PVC Describer output", test.name) + } + for _, expected := range test.expectedElements { + if !strings.Contains(str, expected) { + t.Errorf("expected to find %q in output: %q", expected, str) + } + } + for _, unexpected := range test.unexpectedElements { + if strings.Contains(str, unexpected) { + t.Errorf("unexpected to find %q in output: %q", unexpected, str) + } } } } @@ -848,42 +1120,9 @@ func TestDescribeDeployment(t *testing.T) { } } -func TestDescribeCluster(t *testing.T) { - cluster := federation.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - ResourceVersion: "4", - Labels: map[string]string{ - "name": "foo", - }, - }, - Spec: federation.ClusterSpec{ - ServerAddressByClientCIDRs: []federation.ServerAddressByClientCIDR{ - { - ClientCIDR: "0.0.0.0/0", - ServerAddress: "localhost:8888", - }, - }, - }, - Status: federation.ClusterStatus{ - Conditions: []federation.ClusterCondition{ - {Type: federation.ClusterReady, Status: v1.ConditionTrue}, - }, - }, - } - fake := fedfake.NewSimpleClientset(&cluster) - d := ClusterDescriber{Interface: fake} - out, err := d.Describe("any", "foo", printers.DescriberSettings{ShowEvents: true}) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - if !strings.Contains(out, "foo") { - t.Errorf("unexpected out: %s", out) - } -} - func TestDescribeStorageClass(t *testing.T) { reclaimPolicy := api.PersistentVolumeReclaimRetain + bindingMode := storage.VolumeBindingMode("bindingmode") f := fake.NewSimpleClientset(&storage.StorageClass{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -897,14 +1136,22 @@ func TestDescribeStorageClass(t *testing.T) { "param1": "value1", "param2": "value2", }, - ReclaimPolicy: &reclaimPolicy, + ReclaimPolicy: &reclaimPolicy, + VolumeBindingMode: &bindingMode, }) s := StorageClassDescriber{f} out, err := s.Describe("", "foo", printers.DescriberSettings{ShowEvents: true}) if err != nil { t.Errorf("unexpected error: %v", err) } - if !strings.Contains(out, "foo") { + if !strings.Contains(out, "foo") || + !strings.Contains(out, "my-provisioner") || + !strings.Contains(out, "param1") || + !strings.Contains(out, "param2") || + !strings.Contains(out, "value1") || + !strings.Contains(out, "value2") || + !strings.Contains(out, "Retain") || + !strings.Contains(out, "bindingmode") { t.Errorf("unexpected out: %s", out) } } @@ -1659,6 +1906,204 @@ func TestDescribeResourceQuota(t *testing.T) { } } +func TestDescribeNetworkPolicies(t *testing.T) { + expectedTime, err := time.Parse("2006-01-02 15:04:05 Z0700 MST", "2017-06-04 21:45:56 -0700 PDT") + if err != nil { + t.Errorf("unable to parse time %q error: %s", "2017-06-04 21:45:56 -0700 PDT", err) + } + expectedOut := `Name: network-policy-1 +Namespace: default +Created on: 2017-06-04 21:45:56 -0700 PDT +Labels: +Annotations: +Spec: + PodSelector: foo in (bar1,bar2),foo2 notin (bar1,bar2),id1=app1,id2=app2 + Allowing ingress traffic: + To Port: 80/TCP + To Port: 82/TCP + From PodSelector: id=app2,id2=app3 + From NamespaceSelector: id=app2,id2=app3 + From NamespaceSelector: foo in (bar1,bar2),id=app2,id2=app3 + From IPBlock: + CIDR: 192.168.0.0/16 + Except: 192.168.3.0/24, 192.168.4.0/24 + ---------- + To Port: (traffic allowed to all ports) + From: (traffic not restricted by source) + Allowing egress traffic: + To Port: 80/TCP + To Port: 82/TCP + To PodSelector: id=app2,id2=app3 + To NamespaceSelector: id=app2,id2=app3 + To NamespaceSelector: foo in (bar1,bar2),id=app2,id2=app3 + To IPBlock: + CIDR: 192.168.0.0/16 + Except: 192.168.3.0/24, 192.168.4.0/24 + ---------- + To Port: (traffic allowed to all ports) + To: (traffic not restricted by source) + Policy Types: Ingress, Egress +` + + port80 := intstr.FromInt(80) + port82 := intstr.FromInt(82) + protoTCP := api.ProtocolTCP + + versionedFake := fake.NewSimpleClientset(&networking.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "network-policy-1", + Namespace: "default", + CreationTimestamp: metav1.NewTime(expectedTime), + }, + Spec: networking.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "id1": "app1", + "id2": "app2", + }, + MatchExpressions: []metav1.LabelSelectorRequirement{ + {Key: "foo", Operator: "In", Values: []string{"bar1", "bar2"}}, + {Key: "foo2", Operator: "NotIn", Values: []string{"bar1", "bar2"}}, + }, + }, + Ingress: []networking.NetworkPolicyIngressRule{ + { + Ports: []networking.NetworkPolicyPort{ + {Port: &port80}, + {Port: &port82, Protocol: &protoTCP}, + }, + From: []networking.NetworkPolicyPeer{ + { + PodSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "id": "app2", + "id2": "app3", + }, + }, + }, + { + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "id": "app2", + "id2": "app3", + }, + }, + }, + { + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "id": "app2", + "id2": "app3", + }, + MatchExpressions: []metav1.LabelSelectorRequirement{ + {Key: "foo", Operator: "In", Values: []string{"bar1", "bar2"}}, + }, + }, + }, + { + IPBlock: &networking.IPBlock{ + CIDR: "192.168.0.0/16", + Except: []string{"192.168.3.0/24", "192.168.4.0/24"}, + }, + }, + }, + }, + {}, + }, + Egress: []networking.NetworkPolicyEgressRule{ + { + Ports: []networking.NetworkPolicyPort{ + {Port: &port80}, + {Port: &port82, Protocol: &protoTCP}, + }, + To: []networking.NetworkPolicyPeer{ + { + PodSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "id": "app2", + "id2": "app3", + }, + }, + }, + { + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "id": "app2", + "id2": "app3", + }, + }, + }, + { + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "id": "app2", + "id2": "app3", + }, + MatchExpressions: []metav1.LabelSelectorRequirement{ + {Key: "foo", Operator: "In", Values: []string{"bar1", "bar2"}}, + }, + }, + }, + { + IPBlock: &networking.IPBlock{ + CIDR: "192.168.0.0/16", + Except: []string{"192.168.3.0/24", "192.168.4.0/24"}, + }, + }, + }, + }, + {}, + }, + PolicyTypes: []networking.PolicyType{networking.PolicyTypeIngress, networking.PolicyTypeEgress}, + }, + }) + d := NetworkPolicyDescriber{versionedFake} + out, err := d.Describe("", "network-policy-1", printers.DescriberSettings{}) + if err != nil { + t.Errorf("unexpected error: %s", err) + } + if out != expectedOut { + t.Errorf("want:\n%s\ngot:\n%s", expectedOut, out) + } +} + +func TestDescribeServiceAccount(t *testing.T) { + fake := fake.NewSimpleClientset(&api.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + Namespace: "foo", + }, + Secrets: []api.ObjectReference{ + { + Name: "test-objectref", + }, + }, + ImagePullSecrets: []api.LocalObjectReference{ + { + Name: "test-local-ref", + }, + }, + }) + c := &describeClient{T: t, Namespace: "foo", Interface: fake} + d := ServiceAccountDescriber{c} + out, err := d.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true}) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + expectedOut := `Name: bar +Namespace: foo +Labels: +Annotations: +Image pull secrets: test-local-ref (not found) +Mountable secrets: test-objectref (not found) +Tokens: +Events: ` + "\n" + if out != expectedOut { + t.Errorf("expected : %q\n but got output:\n %q", expectedOut, out) + } + +} + // boolPtr returns a pointer to a bool func boolPtr(b bool) *bool { o := b diff --git a/pkg/printers/internalversion/printers.go b/pkg/printers/internalversion/printers.go index c9ba06c8df0..0b8d1b19216 100644 --- a/pkg/printers/internalversion/printers.go +++ b/pkg/printers/internalversion/printers.go @@ -41,14 +41,13 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/federation/apis/federation" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/events" - "k8s.io/kubernetes/pkg/api/helper" "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/autoscaling" "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/certificates" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/networking" "k8s.io/kubernetes/pkg/apis/policy" @@ -304,15 +303,6 @@ func AddHandlers(h printers.PrintHandler) { h.TableHandler(componentStatusColumnDefinitions, printComponentStatus) h.TableHandler(componentStatusColumnDefinitions, printComponentStatusList) - thirdPartyResourceColumnDefinitions := []metav1alpha1.TableColumnDefinition{ - {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, - {Name: "Description", Type: "string", Description: extensionsv1beta1.ThirdPartyResource{}.SwaggerDoc()["description"]}, - {Name: "Version(s)", Type: "string", Description: extensionsv1beta1.ThirdPartyResource{}.SwaggerDoc()["versions"]}, - } - - h.TableHandler(thirdPartyResourceColumnDefinitions, printThirdPartyResource) - h.TableHandler(thirdPartyResourceColumnDefinitions, printThirdPartyResourceList) - deploymentColumnDefinitions := []metav1alpha1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, {Name: "Desired", Type: "string", Description: extensionsv1beta1.DeploymentSpec{}.SwaggerDoc()["replicas"]}, @@ -361,15 +351,6 @@ func AddHandlers(h printers.PrintHandler) { h.TableHandler(podSecurityPolicyColumnDefinitions, printPodSecurityPolicy) h.TableHandler(podSecurityPolicyColumnDefinitions, printPodSecurityPolicyList) - clusterColumnDefinitions := []metav1alpha1.TableColumnDefinition{ - {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, - {Name: "Status", Type: "string", Description: "Status of the cluster"}, - {Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]}, - {Name: "Labels", Type: "string", Description: "The labels of the cluster"}, - } - h.TableHandler(clusterColumnDefinitions, printCluster) - h.TableHandler(clusterColumnDefinitions, printClusterList) - networkPolicyColumnDefinitioins := []metav1alpha1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, {Name: "Pod-Selector", Type: "string", Description: extensionsv1beta1.NetworkPolicySpec{}.SwaggerDoc()["podSelector"]}, @@ -748,41 +729,6 @@ func printReplicaSetList(list *extensions.ReplicaSetList, options printers.Print return rows, nil } -func printCluster(obj *federation.Cluster, options printers.PrintOptions) ([]metav1alpha1.TableRow, error) { - row := metav1alpha1.TableRow{ - Object: runtime.RawExtension{Object: obj}, - } - - var statuses []string - for _, condition := range obj.Status.Conditions { - if condition.Status == api.ConditionTrue { - statuses = append(statuses, string(condition.Type)) - } else { - statuses = append(statuses, "Not"+string(condition.Type)) - } - } - if len(statuses) == 0 { - statuses = append(statuses, "Unknown") - } - row.Cells = append(row.Cells, obj.Name, strings.Join(statuses, ","), translateTimestamp(obj.CreationTimestamp)) - if options.ShowLabels { - row.Cells = append(row.Cells, labels.FormatLabels(obj.Labels)) - } - return []metav1alpha1.TableRow{row}, nil -} - -func printClusterList(list *federation.ClusterList, options printers.PrintOptions) ([]metav1alpha1.TableRow, error) { - rows := make([]metav1alpha1.TableRow, 0, len(list.Items)) - for i := range list.Items { - r, err := printCluster(&list.Items[i], options) - if err != nil { - return nil, err - } - rows = append(rows, r...) - } - return rows, nil -} - func printJob(obj *batch.Job, options printers.PrintOptions) ([]metav1alpha1.TableRow, error) { row := metav1alpha1.TableRow{ Object: runtime.RawExtension{Object: obj}, @@ -1280,6 +1226,10 @@ func printPersistentVolumeClaim(obj *api.PersistentVolumeClaim, options printers } phase := obj.Status.Phase + if obj.ObjectMeta.DeletionTimestamp != nil { + phase = "Terminating" + } + storage := obj.Spec.Resources.Requests[api.ResourceStorage] capacity := "" accessModes := "" @@ -1480,33 +1430,6 @@ func printComponentStatusList(list *api.ComponentStatusList, options printers.Pr return rows, nil } -func printThirdPartyResource(obj *extensions.ThirdPartyResource, options printers.PrintOptions) ([]metav1alpha1.TableRow, error) { - row := metav1alpha1.TableRow{ - Object: runtime.RawExtension{Object: obj}, - } - - versions := make([]string, len(obj.Versions)) - for ix := range obj.Versions { - version := &obj.Versions[ix] - versions[ix] = fmt.Sprintf("%s", version.Name) - } - versionsString := strings.Join(versions, ",") - row.Cells = append(row.Cells, obj.Name, obj.Description, versionsString) - return []metav1alpha1.TableRow{row}, nil -} - -func printThirdPartyResourceList(list *extensions.ThirdPartyResourceList, options printers.PrintOptions) ([]metav1alpha1.TableRow, error) { - rows := make([]metav1alpha1.TableRow, 0, len(list.Items)) - for i := range list.Items { - r, err := printThirdPartyResource(&list.Items[i], options) - if err != nil { - return nil, err - } - rows = append(rows, r...) - } - return rows, nil -} - func truncate(str string, maxLen int) string { if len(str) > maxLen { return str[0:maxLen] + "..." @@ -1753,10 +1676,7 @@ func layoutContainers(containers []api.Container, w io.Writer) error { } } _, err := fmt.Fprintf(w, "\t%s\t%s", namesBuffer.String(), imagesBuffer.String()) - if err != nil { - return err - } - return nil + return err } // Lay out all the containers on one line if use wide output. diff --git a/pkg/printers/internalversion/printers_test.go b/pkg/printers/internalversion/printers_test.go index d74822cd4a1..0f2c530f863 100644 --- a/pkg/printers/internalversion/printers_test.go +++ b/pkg/printers/internalversion/printers_test.go @@ -41,45 +41,57 @@ import ( "k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/federation/apis/federation" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/autoscaling" "k8s.io/kubernetes/pkg/apis/batch" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/policy" "k8s.io/kubernetes/pkg/apis/storage" - kubectltesting "k8s.io/kubernetes/pkg/kubectl/testing" "k8s.io/kubernetes/pkg/printers" ) func init() { - api.Scheme.AddKnownTypes(testapi.Default.InternalGroupVersion(), &kubectltesting.TestStruct{}) - api.Scheme.AddKnownTypes(api.Registry.GroupOrDie(api.GroupName).GroupVersion, &kubectltesting.TestStruct{}) + legacyscheme.Scheme.AddKnownTypes(testapi.Default.InternalGroupVersion(), &TestPrintType{}) + legacyscheme.Scheme.AddKnownTypes(legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion, &TestPrintType{}) } -var testData = kubectltesting.TestStruct{ +var testData = TestStruct{ Key: "testValue", Map: map[string]int{"TestSubkey": 1}, StringList: []string{"a", "b", "c"}, IntList: []int{1, 2, 3}, } +type TestStruct struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Key string `json:"Key"` + Map map[string]int `json:"Map"` + StringList []string `json:"StringList"` + IntList []int `json:"IntList"` +} + +func (in *TestStruct) DeepCopyObject() runtime.Object { + panic("never called") +} + func TestVersionedPrinter(t *testing.T) { - original := &kubectltesting.TestStruct{Key: "value"} + original := &TestPrintType{Data: "value"} p := printers.NewVersionedPrinter( printers.ResourcePrinterFunc(func(obj runtime.Object, w io.Writer) error { if obj == original { t.Fatalf("object should not be identical: %#v", obj) } - if obj.(*kubectltesting.TestStruct).Key != "value" { + if obj.(*TestPrintType).Data != "value" { t.Fatalf("object was not converted: %#v", obj) } return nil }), - api.Scheme, - api.Registry.GroupOrDie(api.GroupName).GroupVersion, + legacyscheme.Scheme, + legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion, ) if err := p.PrintObj(original, nil); err != nil { t.Errorf("unexpected error: %v", err) @@ -96,7 +108,7 @@ func TestPrintDefault(t *testing.T) { } for _, test := range printerTests { - printer, err := printers.GetStandardPrinter(&printers.OutputOptions{AllowMissingKeys: false}, false, nil, nil, api.Codecs.LegacyCodec(api.Registry.EnabledVersions()...), []runtime.Decoder{api.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, printers.PrintOptions{}) + printer, err := printers.GetStandardPrinter(nil, nil, legacyscheme.Codecs.LegacyCodec(legacyscheme.Registry.EnabledVersions()...), []runtime.Decoder{legacyscheme.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, printers.PrintOptions{AllowMissingKeys: false}) if err != nil { t.Errorf("in %s, unexpected error: %#v", test.Name, err) } @@ -247,36 +259,36 @@ func TestPrinter(t *testing.T) { }, } emptyListTest := &api.PodList{} - testapi, err := api.Scheme.ConvertToVersion(podTest, api.Registry.GroupOrDie(api.GroupName).GroupVersion) + testapi, err := legacyscheme.Scheme.ConvertToVersion(podTest, legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion) if err != nil { t.Fatalf("unexpected error: %v", err) } printerTests := []struct { Name string - OutputOpts *printers.OutputOptions + PrintOpts *printers.PrintOptions Input runtime.Object OutputVersions []schema.GroupVersion Expect string }{ - {"test json", &printers.OutputOptions{FmtType: "json", AllowMissingKeys: true}, simpleTest, nil, "{\n \"Data\": \"foo\"\n}\n"}, - {"test yaml", &printers.OutputOptions{FmtType: "yaml", AllowMissingKeys: true}, simpleTest, nil, "Data: foo\n"}, - {"test template", &printers.OutputOptions{FmtType: "template", FmtArg: "{{if .id}}{{.id}}{{end}}{{if .metadata.name}}{{.metadata.name}}{{end}}", AllowMissingKeys: true}, + {"test json", &printers.PrintOptions{OutputFormatType: "json", AllowMissingKeys: true}, simpleTest, nil, "{\n \"Data\": \"foo\"\n}\n"}, + {"test yaml", &printers.PrintOptions{OutputFormatType: "yaml", AllowMissingKeys: true}, simpleTest, nil, "Data: foo\n"}, + {"test template", &printers.PrintOptions{OutputFormatType: "template", OutputFormatArgument: "{{if .id}}{{.id}}{{end}}{{if .metadata.name}}{{.metadata.name}}{{end}}", AllowMissingKeys: true}, podTest, []schema.GroupVersion{v1.SchemeGroupVersion}, "foo"}, - {"test jsonpath", &printers.OutputOptions{FmtType: "jsonpath", FmtArg: "{.metadata.name}", AllowMissingKeys: true}, podTest, []schema.GroupVersion{v1.SchemeGroupVersion}, "foo"}, - {"test jsonpath list", &printers.OutputOptions{FmtType: "jsonpath", FmtArg: "{.items[*].metadata.name}", AllowMissingKeys: true}, podListTest, []schema.GroupVersion{v1.SchemeGroupVersion}, "foo bar"}, - {"test jsonpath empty list", &printers.OutputOptions{FmtType: "jsonpath", FmtArg: "{.items[*].metadata.name}", AllowMissingKeys: true}, emptyListTest, []schema.GroupVersion{v1.SchemeGroupVersion}, ""}, - {"test name", &printers.OutputOptions{FmtType: "name", AllowMissingKeys: true}, podTest, []schema.GroupVersion{v1.SchemeGroupVersion}, "pods/foo\n"}, - {"emits versioned objects", &printers.OutputOptions{FmtType: "template", FmtArg: "{{.kind}}", AllowMissingKeys: true}, testapi, []schema.GroupVersion{v1.SchemeGroupVersion}, "Pod"}, + {"test jsonpath", &printers.PrintOptions{OutputFormatType: "jsonpath", OutputFormatArgument: "{.metadata.name}", AllowMissingKeys: true}, podTest, []schema.GroupVersion{v1.SchemeGroupVersion}, "foo"}, + {"test jsonpath list", &printers.PrintOptions{OutputFormatType: "jsonpath", OutputFormatArgument: "{.items[*].metadata.name}", AllowMissingKeys: true}, podListTest, []schema.GroupVersion{v1.SchemeGroupVersion}, "foo bar"}, + {"test jsonpath empty list", &printers.PrintOptions{OutputFormatType: "jsonpath", OutputFormatArgument: "{.items[*].metadata.name}", AllowMissingKeys: true}, emptyListTest, []schema.GroupVersion{v1.SchemeGroupVersion}, ""}, + {"test name", &printers.PrintOptions{OutputFormatType: "name", AllowMissingKeys: true}, podTest, []schema.GroupVersion{v1.SchemeGroupVersion}, "pods/foo\n"}, + {"emits versioned objects", &printers.PrintOptions{OutputFormatType: "template", OutputFormatArgument: "{{.kind}}", AllowMissingKeys: true}, testapi, []schema.GroupVersion{v1.SchemeGroupVersion}, "Pod"}, } for _, test := range printerTests { buf := bytes.NewBuffer([]byte{}) - printer, err := printers.GetStandardPrinter(test.OutputOpts, false, api.Registry.RESTMapper(api.Registry.EnabledVersions()...), api.Scheme, api.Codecs.LegacyCodec(api.Registry.EnabledVersions()...), []runtime.Decoder{api.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, printers.PrintOptions{}) + printer, err := printers.GetStandardPrinter(legacyscheme.Registry.RESTMapper(legacyscheme.Registry.EnabledVersions()...), legacyscheme.Scheme, legacyscheme.Codecs.LegacyCodec(legacyscheme.Registry.EnabledVersions()...), []runtime.Decoder{legacyscheme.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, *test.PrintOpts) if err != nil { t.Errorf("in %s, unexpected error: %#v", test.Name, err) } if printer.IsGeneric() && len(test.OutputVersions) > 0 { - printer = printers.NewVersionedPrinter(printer, api.Scheme, test.OutputVersions...) + printer = printers.NewVersionedPrinter(printer, legacyscheme.Scheme, test.OutputVersions...) } if err := printer.PrintObj(test.Input, buf); err != nil { t.Errorf("in %s, unexpected error: %#v", test.Name, err) @@ -290,18 +302,18 @@ func TestPrinter(t *testing.T) { func TestBadPrinter(t *testing.T) { badPrinterTests := []struct { - Name string - OutputOpts *printers.OutputOptions - Error error + Name string + PrintOpts *printers.PrintOptions + Error error }{ - {"empty template", &printers.OutputOptions{FmtType: "template", AllowMissingKeys: false}, fmt.Errorf("template format specified but no template given")}, - {"bad template", &printers.OutputOptions{FmtType: "template", FmtArg: "{{ .Name", AllowMissingKeys: false}, fmt.Errorf("error parsing template {{ .Name, template: output:1: unclosed action\n")}, - {"bad templatefile", &printers.OutputOptions{FmtType: "templatefile", AllowMissingKeys: false}, fmt.Errorf("templatefile format specified but no template file given")}, - {"bad jsonpath", &printers.OutputOptions{FmtType: "jsonpath", FmtArg: "{.Name", AllowMissingKeys: false}, fmt.Errorf("error parsing jsonpath {.Name, unclosed action\n")}, - {"unknown format", &printers.OutputOptions{FmtType: "anUnknownFormat", FmtArg: "", AllowMissingKeys: false}, fmt.Errorf("output format \"anUnknownFormat\" not recognized")}, + {"empty template", &printers.PrintOptions{OutputFormatType: "template", AllowMissingKeys: false}, fmt.Errorf("template format specified but no template given")}, + {"bad template", &printers.PrintOptions{OutputFormatType: "template", OutputFormatArgument: "{{ .Name", AllowMissingKeys: false}, fmt.Errorf("error parsing template {{ .Name, template: output:1: unclosed action\n")}, + {"bad templatefile", &printers.PrintOptions{OutputFormatType: "templatefile", AllowMissingKeys: false}, fmt.Errorf("templatefile format specified but no template file given")}, + {"bad jsonpath", &printers.PrintOptions{OutputFormatType: "jsonpath", OutputFormatArgument: "{.Name", AllowMissingKeys: false}, fmt.Errorf("error parsing jsonpath {.Name, unclosed action\n")}, + {"unknown format", &printers.PrintOptions{OutputFormatType: "anUnknownFormat", OutputFormatArgument: "", AllowMissingKeys: false}, fmt.Errorf("output format \"anUnknownFormat\" not recognized")}, } for _, test := range badPrinterTests { - _, err := printers.GetStandardPrinter(test.OutputOpts, false, api.Registry.RESTMapper(api.Registry.EnabledVersions()...), api.Scheme, api.Codecs.LegacyCodec(api.Registry.EnabledVersions()...), []runtime.Decoder{api.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, printers.PrintOptions{}) + _, err := printers.GetStandardPrinter(legacyscheme.Registry.RESTMapper(legacyscheme.Registry.EnabledVersions()...), legacyscheme.Scheme, legacyscheme.Codecs.LegacyCodec(legacyscheme.Registry.EnabledVersions()...), []runtime.Decoder{legacyscheme.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, *test.PrintOpts) if err == nil || err.Error() != test.Error.Error() { t.Errorf("in %s, expect %s, got %s", test.Name, test.Error, err) } @@ -315,14 +327,14 @@ func testPrinter(t *testing.T, printer printers.ResourcePrinter, unmarshalFunc f if err != nil { t.Fatal(err) } - var poutput kubectltesting.TestStruct + var poutput TestStruct // Verify that given function runs without error. err = unmarshalFunc(buf.Bytes(), &poutput) if err != nil { t.Fatal(err) } // Use real decode function to undo the versioning process. - poutput = kubectltesting.TestStruct{} + poutput = TestStruct{} s := yamlserializer.NewDecodingSerializer(testapi.Default.Codec()) if err := runtime.DecodeInto(s, buf.Bytes(), &poutput); err != nil { t.Fatal(err) @@ -494,8 +506,8 @@ func TestNamePrinter(t *testing.T) { }, "pods/foo\npods/bar\n"}, } - outputOpts := &printers.OutputOptions{FmtType: "name", AllowMissingKeys: false} - printer, _ := printers.GetStandardPrinter(outputOpts, false, api.Registry.RESTMapper(api.Registry.EnabledVersions()...), api.Scheme, api.Codecs.LegacyCodec(api.Registry.EnabledVersions()...), []runtime.Decoder{api.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, printers.PrintOptions{}) + printOpts := &printers.PrintOptions{OutputFormatType: "name", AllowMissingKeys: false} + printer, _ := printers.GetStandardPrinter(legacyscheme.Registry.RESTMapper(legacyscheme.Registry.EnabledVersions()...), legacyscheme.Scheme, legacyscheme.Codecs.LegacyCodec(legacyscheme.Registry.EnabledVersions()...), []runtime.Decoder{legacyscheme.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, *printOpts) for name, item := range tests { buff := &bytes.Buffer{} err := printer.PrintObj(item.obj, buff) @@ -610,7 +622,7 @@ func TestTemplateStrings(t *testing.T) { t.Fatalf("tmpl fail: %v", err) } - printer := printers.NewVersionedPrinter(p, api.Scheme, api.Registry.GroupOrDie(api.GroupName).GroupVersion) + printer := printers.NewVersionedPrinter(p, legacyscheme.Scheme, legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion) for name, item := range table { buffer := &bytes.Buffer{} @@ -643,19 +655,19 @@ func TestPrinters(t *testing.T) { if err != nil { t.Fatal(err) } - templatePrinter = printers.NewVersionedPrinter(templatePrinter, api.Scheme, v1.SchemeGroupVersion) + templatePrinter = printers.NewVersionedPrinter(templatePrinter, legacyscheme.Scheme, v1.SchemeGroupVersion) templatePrinter2, err = printers.NewTemplatePrinter([]byte("{{len .items}}")) if err != nil { t.Fatal(err) } - templatePrinter2 = printers.NewVersionedPrinter(templatePrinter2, api.Scheme, v1.SchemeGroupVersion) + templatePrinter2 = printers.NewVersionedPrinter(templatePrinter2, legacyscheme.Scheme, v1.SchemeGroupVersion) jsonpathPrinter, err = printers.NewJSONPathPrinter("{.metadata.name}") if err != nil { t.Fatal(err) } - jsonpathPrinter = printers.NewVersionedPrinter(jsonpathPrinter, api.Scheme, v1.SchemeGroupVersion) + jsonpathPrinter = printers.NewVersionedPrinter(jsonpathPrinter, legacyscheme.Scheme, v1.SchemeGroupVersion) allPrinters := map[string]printers.ResourcePrinter{ "humanReadable": printers.NewHumanReadablePrinter(nil, nil, printers.PrintOptions{ @@ -668,9 +680,9 @@ func TestPrinters(t *testing.T) { "template2": templatePrinter2, "jsonpath": jsonpathPrinter, "name": &printers.NamePrinter{ - Typer: api.Scheme, - Decoders: []runtime.Decoder{api.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, - Mapper: api.Registry.RESTMapper(api.Registry.EnabledVersions()...), + Typer: legacyscheme.Scheme, + Decoders: []runtime.Decoder{legacyscheme.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, + Mapper: legacyscheme.Registry.RESTMapper(legacyscheme.Registry.EnabledVersions()...), }, } AddHandlers((allPrinters["humanReadable"]).(*printers.HumanReadablePrinter)) @@ -1672,6 +1684,65 @@ func TestPrintPod(t *testing.T) { } } +func TestPrintPodwide(t *testing.T) { + tests := []struct { + pod api.Pod + expect []metav1alpha1.TableRow + }{ + { + // Test when the NodeName and PodIP are not none + api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "test1"}, + Spec: api.PodSpec{ + Containers: make([]api.Container, 2), + NodeName: "test1", + }, + Status: api.PodStatus{ + Phase: "podPhase", + PodIP: "1.1.1.1", + ContainerStatuses: []api.ContainerStatus{ + {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, + {RestartCount: 3}, + }, + }, + }, + []metav1alpha1.TableRow{{Cells: []interface{}{"test1", "1/2", "podPhase", 6, "", "1.1.1.1", "test1"}}}, + }, + { + // Test when the NodeName and PodIP are none + api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "test2"}, + Spec: api.PodSpec{ + Containers: make([]api.Container, 2), + NodeName: "", + }, + Status: api.PodStatus{ + Phase: "podPhase", + PodIP: "", + ContainerStatuses: []api.ContainerStatus{ + {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, + {State: api.ContainerState{Waiting: &api.ContainerStateWaiting{Reason: "ContainerWaitingReason"}}, RestartCount: 3}, + }, + }, + }, + []metav1alpha1.TableRow{{Cells: []interface{}{"test2", "1/2", "ContainerWaitingReason", 6, "", "", ""}}}, + }, + } + + for i, test := range tests { + rows, err := printPod(&test.pod, printers.PrintOptions{Wide: true}) + if err != nil { + t.Fatal(err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(test.expect, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expect, rows)) + } + } +} + func TestPrintPodList(t *testing.T) { tests := []struct { pods api.PodList @@ -2734,20 +2805,20 @@ func TestPrintPodDisruptionBudget(t *testing.T) { func TestAllowMissingKeys(t *testing.T) { tests := []struct { - Name string - OutputOpts *printers.OutputOptions - Input runtime.Object - Expect string - Error string + Name string + PrintOpts *printers.PrintOptions + Input runtime.Object + Expect string + Error string }{ - {"test template, allow missing keys", &printers.OutputOptions{FmtType: "template", FmtArg: "{{.blarg}}", AllowMissingKeys: true}, &api.Pod{}, "", ""}, - {"test template, strict", &printers.OutputOptions{FmtType: "template", FmtArg: "{{.blarg}}", AllowMissingKeys: false}, &api.Pod{}, "", `error executing template "{{.blarg}}": template: output:1:2: executing "output" at <.blarg>: map has no entry for key "blarg"`}, - {"test jsonpath, allow missing keys", &printers.OutputOptions{FmtType: "jsonpath", FmtArg: "{.blarg}", AllowMissingKeys: true}, &api.Pod{}, "", ""}, - {"test jsonpath, strict", &printers.OutputOptions{FmtType: "jsonpath", FmtArg: "{.blarg}", AllowMissingKeys: false}, &api.Pod{}, "", "error executing jsonpath \"{.blarg}\": blarg is not found\n"}, + {"test template, allow missing keys", &printers.PrintOptions{OutputFormatType: "template", OutputFormatArgument: "{{.blarg}}", AllowMissingKeys: true}, &api.Pod{}, "", ""}, + {"test template, strict", &printers.PrintOptions{OutputFormatType: "template", OutputFormatArgument: "{{.blarg}}", AllowMissingKeys: false}, &api.Pod{}, "", `error executing template "{{.blarg}}": template: output:1:2: executing "output" at <.blarg>: map has no entry for key "blarg"`}, + {"test jsonpath, allow missing keys", &printers.PrintOptions{OutputFormatType: "jsonpath", OutputFormatArgument: "{.blarg}", AllowMissingKeys: true}, &api.Pod{}, "", ""}, + {"test jsonpath, strict", &printers.PrintOptions{OutputFormatType: "jsonpath", OutputFormatArgument: "{.blarg}", AllowMissingKeys: false}, &api.Pod{}, "", "error executing jsonpath \"{.blarg}\": blarg is not found\n"}, } for _, test := range tests { buf := bytes.NewBuffer([]byte{}) - printer, err := printers.GetStandardPrinter(test.OutputOpts, false, api.Registry.RESTMapper(api.Registry.EnabledVersions()...), api.Scheme, api.Codecs.LegacyCodec(api.Registry.EnabledVersions()...), []runtime.Decoder{api.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, printers.PrintOptions{}) + printer, err := printers.GetStandardPrinter(legacyscheme.Registry.RESTMapper(legacyscheme.Registry.EnabledVersions()...), legacyscheme.Scheme, legacyscheme.Codecs.LegacyCodec(legacyscheme.Registry.EnabledVersions()...), []runtime.Decoder{legacyscheme.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, *test.PrintOpts) if err != nil { t.Errorf("in %s, unexpected error: %#v", test.Name, err) } @@ -3133,124 +3204,3 @@ func TestPrintStorageClass(t *testing.T) { buf.Reset() } } - -func TestPrintCluster(t *testing.T) { - tests := []struct { - cluster federation.Cluster - expect []metav1alpha1.TableRow - showLabels bool - }{ - { - federation.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cluster1", - CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, - }, - }, - []metav1alpha1.TableRow{{Cells: []interface{}{"cluster1", "Unknown", "0s"}}}, - false, - }, - { - federation.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cluster2", - CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, - Labels: map[string]string{"label1": "", "label2": "cluster"}, - }, - Status: federation.ClusterStatus{ - Conditions: []federation.ClusterCondition{ - { - Status: api.ConditionTrue, - Type: federation.ClusterReady, - }, - { - Status: api.ConditionFalse, - Type: federation.ClusterOffline, - }, - }, - }, - }, - []metav1alpha1.TableRow{{Cells: []interface{}{"cluster2", "Ready,NotOffline", "0s", "label1=,label2=cluster"}}}, - true, - }, - } - - for i, test := range tests { - rows, err := printCluster(&test.cluster, printers.PrintOptions{ShowLabels: test.showLabels}) - if err != nil { - t.Fatal(err) - } - for i := range rows { - rows[i].Object.Object = nil - } - if !reflect.DeepEqual(test.expect, rows) { - t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expect, rows)) - } - } -} - -func TestPrintClusterList(t *testing.T) { - tests := []struct { - clusters federation.ClusterList - expect []metav1alpha1.TableRow - showLabels bool - }{ - // Test podList's pod: name, num of containers, restarts, container ready status - { - federation.ClusterList{ - Items: []federation.Cluster{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "cluster1", - CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, - }, - Status: federation.ClusterStatus{ - Conditions: []federation.ClusterCondition{ - { - Status: api.ConditionTrue, - Type: federation.ClusterReady, - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "cluster2", - CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, - Labels: map[string]string{"label1": "", "label2": "cluster2"}, - }, - Status: federation.ClusterStatus{ - Conditions: []federation.ClusterCondition{ - { - Status: api.ConditionTrue, - Type: federation.ClusterReady, - }, - { - Status: api.ConditionFalse, - Type: federation.ClusterOffline, - }, - }, - }, - }, - }, - }, - []metav1alpha1.TableRow{{Cells: []interface{}{"cluster1", "Ready", "0s", ""}}, - {Cells: []interface{}{"cluster2", "Ready,NotOffline", "0s", "label1=,label2=cluster2"}}}, - true, - }, - } - - for _, test := range tests { - rows, err := printClusterList(&test.clusters, printers.PrintOptions{ShowLabels: test.showLabels}) - - if err != nil { - t.Fatal(err) - } - for i := range rows { - rows[i].Object.Object = nil - } - if !reflect.DeepEqual(test.expect, rows) { - t.Errorf("mismatch: %s", diff.ObjectReflectDiff(test.expect, rows)) - } - } -} diff --git a/pkg/printers/internalversion/sorted_resource_name_list_test.go b/pkg/printers/internalversion/sorted_resource_name_list_test.go index 8989691e707..175c0cec711 100644 --- a/pkg/printers/internalversion/sorted_resource_name_list_test.go +++ b/pkg/printers/internalversion/sorted_resource_name_list_test.go @@ -17,11 +17,10 @@ limitations under the License. package internalversion import ( + api "k8s.io/kubernetes/pkg/apis/core" "reflect" "sort" "testing" - - "k8s.io/kubernetes/pkg/api" ) func TestSortableResourceNamesSorting(t *testing.T) { diff --git a/pkg/printers/printers.go b/pkg/printers/printers.go index c956170355b..3ef696ed9b8 100644 --- a/pkg/printers/printers.go +++ b/pkg/printers/printers.go @@ -29,12 +29,8 @@ import ( // a printer or an error. The printer is agnostic to schema versions, so you must // send arguments to PrintObj in the version you wish them to be shown using a // VersionedPrinter (typically when generic is true). -func GetStandardPrinter(outputOpts *OutputOptions, noHeaders bool, mapper meta.RESTMapper, typer runtime.ObjectTyper, encoder runtime.Encoder, decoders []runtime.Decoder, options PrintOptions) (ResourcePrinter, error) { - if outputOpts == nil { - return nil, fmt.Errorf("no output options specified") - } - - format, formatArgument, allowMissingTemplateKeys := outputOpts.FmtType, outputOpts.FmtArg, outputOpts.AllowMissingKeys +func GetStandardPrinter(mapper meta.RESTMapper, typer runtime.ObjectTyper, encoder runtime.Encoder, decoders []runtime.Decoder, options PrintOptions) (ResourcePrinter, error) { + format, formatArgument, allowMissingTemplateKeys := options.OutputFormatType, options.OutputFormatArgument, options.AllowMissingKeys var printer ResourcePrinter switch format { @@ -106,7 +102,7 @@ func GetStandardPrinter(outputOpts *OutputOptions, noHeaders bool, mapper meta.R case "custom-columns": var err error - if printer, err = NewCustomColumnsPrinterFromSpec(formatArgument, decoders[0], noHeaders); err != nil { + if printer, err = NewCustomColumnsPrinterFromSpec(formatArgument, decoders[0], options.NoHeaders); err != nil { return nil, err } diff --git a/pkg/probe/exec/BUILD b/pkg/probe/exec/BUILD index 7d69fa359ec..223945cf080 100644 --- a/pkg/probe/exec/BUILD +++ b/pkg/probe/exec/BUILD @@ -20,8 +20,8 @@ go_library( go_test( name = "go_default_test", srcs = ["exec_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/probe/exec", - library = ":go_default_library", deps = ["//pkg/probe:go_default_library"], ) diff --git a/pkg/probe/http/BUILD b/pkg/probe/http/BUILD index 7ef26023546..a86e7f981e0 100644 --- a/pkg/probe/http/BUILD +++ b/pkg/probe/http/BUILD @@ -21,8 +21,8 @@ go_library( go_test( name = "go_default_test", srcs = ["http_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/probe/http", - library = ":go_default_library", deps = ["//pkg/probe:go_default_library"], ) diff --git a/pkg/probe/http/http_test.go b/pkg/probe/http/http_test.go index 6e9efb86d29..d06b8937569 100644 --- a/pkg/probe/http/http_test.go +++ b/pkg/probe/http/http_test.go @@ -52,6 +52,16 @@ func TestHTTPProbeChecker(t *testing.T) { w.Write([]byte(output)) } + redirectHandler := func(s int, bad bool) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/" { + http.Redirect(w, r, "/new", s) + } else if bad && r.URL.Path == "/new" { + w.WriteHeader(http.StatusInternalServerError) + } + } + } + prober := New() testCases := []struct { handler func(w http.ResponseWriter, r *http.Request) @@ -122,6 +132,38 @@ func TestHTTPProbeChecker(t *testing.T) { }, health: probe.Failure, }, + { + handler: redirectHandler(http.StatusMovedPermanently, false), // 301 + health: probe.Success, + }, + { + handler: redirectHandler(http.StatusMovedPermanently, true), // 301 + health: probe.Failure, + }, + { + handler: redirectHandler(http.StatusFound, false), // 302 + health: probe.Success, + }, + { + handler: redirectHandler(http.StatusFound, true), // 302 + health: probe.Failure, + }, + { + handler: redirectHandler(http.StatusTemporaryRedirect, false), // 307 + health: probe.Success, + }, + { + handler: redirectHandler(http.StatusTemporaryRedirect, true), // 307 + health: probe.Failure, + }, + { + handler: redirectHandler(http.StatusPermanentRedirect, false), // 308 + health: probe.Success, + }, + { + handler: redirectHandler(http.StatusPermanentRedirect, true), // 308 + health: probe.Failure, + }, } for i, test := range testCases { func() { diff --git a/pkg/probe/tcp/BUILD b/pkg/probe/tcp/BUILD index 5238534016f..6ff7817654d 100644 --- a/pkg/probe/tcp/BUILD +++ b/pkg/probe/tcp/BUILD @@ -19,8 +19,8 @@ go_library( go_test( name = "go_default_test", srcs = ["tcp_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/probe/tcp", - library = ":go_default_library", deps = ["//pkg/probe:go_default_library"], ) diff --git a/pkg/proxy/BUILD b/pkg/proxy/BUILD index a0b80defb6c..597c49c4654 100644 --- a/pkg/proxy/BUILD +++ b/pkg/proxy/BUILD @@ -26,10 +26,12 @@ filegroup( name = "all-srcs", srcs = [ ":package-srcs", + "//pkg/proxy/apis/kubeproxyconfig:all-srcs", "//pkg/proxy/config:all-srcs", "//pkg/proxy/healthcheck:all-srcs", "//pkg/proxy/iptables:all-srcs", "//pkg/proxy/ipvs:all-srcs", + "//pkg/proxy/metrics:all-srcs", "//pkg/proxy/userspace:all-srcs", "//pkg/proxy/util:all-srcs", "//pkg/proxy/winkernel:all-srcs", diff --git a/pkg/proxy/OWNERS b/pkg/proxy/OWNERS index f1f0145ed00..311f4413163 100644 --- a/pkg/proxy/OWNERS +++ b/pkg/proxy/OWNERS @@ -10,3 +10,4 @@ reviewers: - justinsb - freehan - dcbw +- m1093782566 diff --git a/pkg/proxy/apis/kubeproxyconfig/BUILD b/pkg/proxy/apis/kubeproxyconfig/BUILD new file mode 100644 index 00000000000..d9887d9f990 --- /dev/null +++ b/pkg/proxy/apis/kubeproxyconfig/BUILD @@ -0,0 +1,40 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "register.go", + "types.go", + "zz_generated.deepcopy.go", + ], + importpath = "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig", + deps = [ + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//pkg/proxy/apis/kubeproxyconfig/scheme:all-srcs", + "//pkg/proxy/apis/kubeproxyconfig/v1alpha1:all-srcs", + "//pkg/proxy/apis/kubeproxyconfig/validation:all-srcs", + ], + tags = ["automanaged"], +) diff --git a/pkg/proxy/apis/kubeproxyconfig/OWNERS b/pkg/proxy/apis/kubeproxyconfig/OWNERS new file mode 100644 index 00000000000..34823356d22 --- /dev/null +++ b/pkg/proxy/apis/kubeproxyconfig/OWNERS @@ -0,0 +1,4 @@ +approvers: +- thockin +reviewers: +- sig-network-reviewers diff --git a/pkg/proxy/apis/kubeproxyconfig/doc.go b/pkg/proxy/apis/kubeproxyconfig/doc.go new file mode 100644 index 00000000000..4a6a6ef9600 --- /dev/null +++ b/pkg/proxy/apis/kubeproxyconfig/doc.go @@ -0,0 +1,19 @@ +/* +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 + +package kubeproxyconfig // import "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig" diff --git a/pkg/proxy/apis/kubeproxyconfig/register.go b/pkg/proxy/apis/kubeproxyconfig/register.go new file mode 100644 index 00000000000..d543b07a4d4 --- /dev/null +++ b/pkg/proxy/apis/kubeproxyconfig/register.go @@ -0,0 +1,51 @@ +/* +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 kubeproxyconfig + +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 = "kubeproxy.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 { + // TODO this will get cleaned up with the scheme types are fixed + scheme.AddKnownTypes(SchemeGroupVersion, + &KubeProxyConfiguration{}, + ) + return nil +} diff --git a/pkg/proxy/apis/kubeproxyconfig/scheme/BUILD b/pkg/proxy/apis/kubeproxyconfig/scheme/BUILD new file mode 100644 index 00000000000..2e5b9e8f206 --- /dev/null +++ b/pkg/proxy/apis/kubeproxyconfig/scheme/BUILD @@ -0,0 +1,28 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["scheme.go"], + importpath = "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/scheme", + visibility = ["//visibility:public"], + deps = [ + "//pkg/proxy/apis/kubeproxyconfig:go_default_library", + "//pkg/proxy/apis/kubeproxyconfig/v1alpha1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/proxy/apis/kubeproxyconfig/scheme/scheme.go b/pkg/proxy/apis/kubeproxyconfig/scheme/scheme.go new file mode 100644 index 00000000000..fbc3bdd47e7 --- /dev/null +++ b/pkg/proxy/apis/kubeproxyconfig/scheme/scheme.go @@ -0,0 +1,42 @@ +/* +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 scheme + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig" + "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1" +) + +var ( + // Scheme defines methods for serializing and deserializing API objects. + Scheme = runtime.NewScheme() + // Codecs provides methods for retrieving codecs and serializers for specific + // versions and content types. + Codecs = serializer.NewCodecFactory(Scheme) +) + +func init() { + AddToScheme(Scheme) +} + +// AddToScheme adds the types of this group into the given scheme. +func AddToScheme(scheme *runtime.Scheme) { + v1alpha1.AddToScheme(scheme) + kubeproxyconfig.AddToScheme(scheme) +} diff --git a/pkg/proxy/apis/kubeproxyconfig/types.go b/pkg/proxy/apis/kubeproxyconfig/types.go new file mode 100644 index 00000000000..2a971c120d7 --- /dev/null +++ b/pkg/proxy/apis/kubeproxyconfig/types.go @@ -0,0 +1,257 @@ +/* +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 kubeproxyconfig + +import ( + "fmt" + "sort" + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// ClientConnectionConfiguration contains details for constructing a client. +type ClientConnectionConfiguration struct { + // kubeConfigFile is the path to a kubeconfig file. + KubeConfigFile string + // acceptContentTypes defines the Accept header sent by clients when connecting to a server, overriding the + // default value of 'application/json'. This field will control all connections to the server used by a particular + // client. + AcceptContentTypes string + // contentType is the content type used when sending data to the server from this client. + ContentType string + // qps controls the number of queries per second allowed for this connection. + QPS float32 + // burst allows extra queries to accumulate when a client is exceeding its rate. + Burst int32 +} + +// KubeProxyIPTablesConfiguration contains iptables-related configuration +// details for the Kubernetes proxy server. +type KubeProxyIPTablesConfiguration struct { + // masqueradeBit is the bit of the iptables fwmark space to use for SNAT if using + // the pure iptables proxy mode. Values must be within the range [0, 31]. + MasqueradeBit *int32 + // masqueradeAll tells kube-proxy to SNAT everything if using the pure iptables proxy mode. + MasqueradeAll bool + // syncPeriod is the period that iptables rules are refreshed (e.g. '5s', '1m', + // '2h22m'). Must be greater than 0. + SyncPeriod metav1.Duration + // minSyncPeriod is the minimum period that iptables rules are refreshed (e.g. '5s', '1m', + // '2h22m'). + MinSyncPeriod metav1.Duration +} + +// KubeProxyIPVSConfiguration contains ipvs-related configuration +// details for the Kubernetes proxy server. +type KubeProxyIPVSConfiguration struct { + // syncPeriod is the period that ipvs rules are refreshed (e.g. '5s', '1m', + // '2h22m'). Must be greater than 0. + SyncPeriod metav1.Duration + // minSyncPeriod is the minimum period that ipvs rules are refreshed (e.g. '5s', '1m', + // '2h22m'). + MinSyncPeriod metav1.Duration + // ipvs scheduler + Scheduler string +} + +// KubeProxyConntrackConfiguration contains conntrack settings for +// the Kubernetes proxy server. +type KubeProxyConntrackConfiguration struct { + // max is the maximum number of NAT connections to track (0 to + // leave as-is). This takes precedence over maxPerCore and min. + Max *int32 + // maxPerCore is the maximum number of NAT connections to track + // per CPU core (0 to leave the limit as-is and ignore min). + MaxPerCore *int32 + // min is the minimum value of connect-tracking records to allocate, + // regardless of maxPerCore (set maxPerCore=0 to leave the limit as-is). + Min *int32 + // tcpEstablishedTimeout is how long an idle TCP connection will be kept open + // (e.g. '2s'). Must be greater than 0 to set. + TCPEstablishedTimeout *metav1.Duration + // tcpCloseWaitTimeout is how long an idle conntrack entry + // in CLOSE_WAIT state will remain in the conntrack + // table. (e.g. '60s'). Must be greater than 0 to set. + TCPCloseWaitTimeout *metav1.Duration +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// KubeProxyConfiguration contains everything necessary to configure the +// Kubernetes proxy server. +type KubeProxyConfiguration struct { + metav1.TypeMeta + + // featureGates is a comma-separated list of key=value pairs that control + // which alpha/beta features are enabled. + // + // TODO this really should be a map but that requires refactoring all + // components to use config files because local-up-cluster.sh only supports + // the --feature-gates flag right now, which is comma-separated key=value + // pairs. + FeatureGates string + + // bindAddress is the IP address for the proxy server to serve on (set to 0.0.0.0 + // for all interfaces) + BindAddress string + // healthzBindAddress is the IP address and port for the health check server to serve on, + // defaulting to 0.0.0.0:10256 + HealthzBindAddress string + // metricsBindAddress is the IP address and port for the metrics server to serve on, + // defaulting to 127.0.0.1:10249 (set to 0.0.0.0 for all interfaces) + MetricsBindAddress string + // enableProfiling enables profiling via web interface on /debug/pprof handler. + // Profiling handlers will be handled by metrics server. + EnableProfiling bool + // clusterCIDR is the CIDR range of the pods in the cluster. It is used to + // bridge traffic coming from outside of the cluster. If not provided, + // no off-cluster bridging will be performed. + ClusterCIDR string + // hostnameOverride, if non-empty, will be used as the identity instead of the actual hostname. + HostnameOverride string + // clientConnection specifies the kubeconfig file and client connection settings for the proxy + // server to use when communicating with the apiserver. + ClientConnection ClientConnectionConfiguration + // iptables contains iptables-related configuration options. + IPTables KubeProxyIPTablesConfiguration + // ipvs contains ipvs-related configuration options. + IPVS KubeProxyIPVSConfiguration + // oomScoreAdj is the oom-score-adj value for kube-proxy process. Values must be within + // the range [-1000, 1000] + OOMScoreAdj *int32 + // mode specifies which proxy mode to use. + Mode ProxyMode + // portRange is the range of host ports (beginPort-endPort, inclusive) that may be consumed + // in order to proxy service traffic. If unspecified (0-0) then ports will be randomly chosen. + PortRange string + // resourceContainer is the absolute name of the resource-only container to create and run + // the Kube-proxy in (Default: /kube-proxy). + ResourceContainer string + // udpIdleTimeout is how long an idle UDP connection will be kept open (e.g. '250ms', '2s'). + // Must be greater than 0. Only applicable for proxyMode=userspace. + UDPIdleTimeout metav1.Duration + // conntrack contains conntrack-related configuration options. + Conntrack KubeProxyConntrackConfiguration + // configSyncPeriod is how often configuration from the apiserver is refreshed. Must be greater + // than 0. + ConfigSyncPeriod metav1.Duration +} + +// Currently, four modes of proxying are available total: 'userspace' (older, stable), 'iptables' +// (newer, faster), 'ipvs', and 'kernelspace' (Windows only, newer). +// +// If blank, use the best-available proxy (currently iptables, but may change in +// future versions). If the iptables proxy is selected, regardless of how, but +// the system's kernel or iptables versions are insufficient, this always falls +// back to the userspace proxy. +type ProxyMode string + +const ( + ProxyModeUserspace ProxyMode = "userspace" + ProxyModeIPTables ProxyMode = "iptables" + ProxyModeIPVS ProxyMode = "ipvs" + ProxyModeKernelspace ProxyMode = "kernelspace" +) + +// IPVSSchedulerMethod is the algorithm for allocating TCP connections and +// UDP datagrams to real servers. Scheduling algorithms are imple- +//wanted as kernel modules. Ten are shipped with the Linux Virtual Server. +type IPVSSchedulerMethod string + +const ( + // RoundRobin distributes jobs equally amongst the available real servers. + RoundRobin IPVSSchedulerMethod = "rr" + // WeightedRoundRobin assigns jobs to real servers proportionally to there real servers' weight. + // Servers with higher weights receive new jobs first and get more jobs than servers with lower weights. + // Servers with equal weights get an equal distribution of new jobs. + WeightedRoundRobin IPVSSchedulerMethod = "wrr" + // LeastConnection assigns more jobs to real servers with fewer active jobs. + LeastConnection IPVSSchedulerMethod = "lc" + // WeightedLeastConnection assigns more jobs to servers with fewer jobs and + // relative to the real servers' weight(Ci/Wi). + WeightedLeastConnection IPVSSchedulerMethod = "wlc" + // LocalityBasedLeastConnection assigns jobs destined for the same IP address to the same server if + // the server is not overloaded and available; otherwise assigns jobs to servers with fewer jobs, + // and keep it for future assignment. + LocalityBasedLeastConnection IPVSSchedulerMethod = "lblc" + // LocalityBasedLeastConnectionWithReplication with Replication assigns jobs destined for the same IP address to the + // least-connection node in the server set for the IP address. If all the node in the server set are overloaded, + // it picks up a node with fewer jobs in the cluster and adds it to the sever set for the target. + // If the server set has not been modified for the specified time, the most loaded node is removed from the server set, + // in order to avoid high degree of replication. + LocalityBasedLeastConnectionWithReplication IPVSSchedulerMethod = "lblcr" + // SourceHashing assigns jobs to servers through looking up a statically assigned hash table + // by their source IP addresses. + SourceHashing IPVSSchedulerMethod = "sh" + // DestinationHashing assigns jobs to servers through looking up a statically assigned hash table + // by their destination IP addresses. + DestinationHashing IPVSSchedulerMethod = "dh" + // ShortestExpectedDelay assigns an incoming job to the server with the shortest expected delay. + // The expected delay that the job will experience is (Ci + 1) / Ui if sent to the ith server, in which + // Ci is the number of jobs on the the ith server and Ui is the fixed service rate (weight) of the ith server. + ShortestExpectedDelay IPVSSchedulerMethod = "sed" + // NeverQueue assigns an incoming job to an idle server if there is, instead of waiting for a fast one; + // if all the servers are busy, it adopts the ShortestExpectedDelay policy to assign the job. + NeverQueue IPVSSchedulerMethod = "nq" +) + +func (m *ProxyMode) Set(s string) error { + *m = ProxyMode(s) + return nil +} + +func (m *ProxyMode) String() string { + if m != nil { + return string(*m) + } + return "" +} + +func (m *ProxyMode) Type() string { + return "ProxyMode" +} + +type ConfigurationMap map[string]string + +func (m *ConfigurationMap) String() string { + pairs := []string{} + for k, v := range *m { + pairs = append(pairs, fmt.Sprintf("%s=%s", k, v)) + } + sort.Strings(pairs) + return strings.Join(pairs, ",") +} + +func (m *ConfigurationMap) Set(value string) error { + for _, s := range strings.Split(value, ",") { + if len(s) == 0 { + continue + } + arr := strings.SplitN(s, "=", 2) + if len(arr) == 2 { + (*m)[strings.TrimSpace(arr[0])] = strings.TrimSpace(arr[1]) + } else { + (*m)[strings.TrimSpace(arr[0])] = "" + } + } + return nil +} + +func (*ConfigurationMap) Type() string { + return "mapStringString" +} diff --git a/pkg/proxy/apis/kubeproxyconfig/v1alpha1/BUILD b/pkg/proxy/apis/kubeproxyconfig/v1alpha1/BUILD new file mode 100644 index 00000000000..5ddeb90140b --- /dev/null +++ b/pkg/proxy/apis/kubeproxyconfig/v1alpha1/BUILD @@ -0,0 +1,43 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = [ + "defaults.go", + "doc.go", + "register.go", + "types.go", + "zz_generated.conversion.go", + "zz_generated.deepcopy.go", + "zz_generated.defaults.go", + ], + importpath = "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1", + deps = [ + "//pkg/kubelet/qos:go_default_library", + "//pkg/master/ports:go_default_library", + "//pkg/proxy/apis/kubeproxyconfig:go_default_library", + "//pkg/util/pointer:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/pkg/proxy/apis/kubeproxyconfig/v1alpha1/defaults.go b/pkg/proxy/apis/kubeproxyconfig/v1alpha1/defaults.go new file mode 100644 index 00000000000..af74a142482 --- /dev/null +++ b/pkg/proxy/apis/kubeproxyconfig/v1alpha1/defaults.go @@ -0,0 +1,119 @@ +/* +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 v1alpha1 + +import ( + "fmt" + "strings" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/kubernetes/pkg/kubelet/qos" + "k8s.io/kubernetes/pkg/master/ports" + "k8s.io/kubernetes/pkg/util/pointer" +) + +func addDefaultingFuncs(scheme *kruntime.Scheme) error { + return RegisterDefaults(scheme) +} + +func SetDefaults_KubeProxyConfiguration(obj *KubeProxyConfiguration) { + if len(obj.BindAddress) == 0 { + obj.BindAddress = "0.0.0.0" + } + if obj.HealthzBindAddress == "" { + obj.HealthzBindAddress = fmt.Sprintf("0.0.0.0:%v", ports.ProxyHealthzPort) + } else if !strings.Contains(obj.HealthzBindAddress, ":") { + obj.HealthzBindAddress += fmt.Sprintf(":%v", ports.ProxyHealthzPort) + } + if obj.MetricsBindAddress == "" { + obj.MetricsBindAddress = fmt.Sprintf("127.0.0.1:%v", ports.ProxyStatusPort) + } else if !strings.Contains(obj.MetricsBindAddress, ":") { + obj.MetricsBindAddress += fmt.Sprintf(":%v", ports.ProxyStatusPort) + } + if obj.OOMScoreAdj == nil { + temp := int32(qos.KubeProxyOOMScoreAdj) + obj.OOMScoreAdj = &temp + } + if obj.ResourceContainer == "" { + obj.ResourceContainer = "/kube-proxy" + } + if obj.IPTables.SyncPeriod.Duration == 0 { + obj.IPTables.SyncPeriod = metav1.Duration{Duration: 30 * time.Second} + } + if obj.IPVS.SyncPeriod.Duration == 0 { + obj.IPVS.SyncPeriod = metav1.Duration{Duration: 30 * time.Second} + } + zero := metav1.Duration{} + if obj.UDPIdleTimeout == zero { + obj.UDPIdleTimeout = metav1.Duration{Duration: 250 * time.Millisecond} + } + // If ConntrackMax is set, respect it. + if obj.Conntrack.Max == nil { + // If ConntrackMax is *not* set, use per-core scaling. + if obj.Conntrack.MaxPerCore == nil { + obj.Conntrack.MaxPerCore = pointer.Int32Ptr(32 * 1024) + } + if obj.Conntrack.Min == nil { + obj.Conntrack.Min = pointer.Int32Ptr(128 * 1024) + } + } + if obj.IPTables.MasqueradeBit == nil { + temp := int32(14) + obj.IPTables.MasqueradeBit = &temp + } + if obj.Conntrack.TCPEstablishedTimeout == nil { + obj.Conntrack.TCPEstablishedTimeout = &metav1.Duration{Duration: 24 * time.Hour} // 1 day (1/5 default) + } + if obj.Conntrack.TCPCloseWaitTimeout == nil { + // See https://github.com/kubernetes/kubernetes/issues/32551. + // + // CLOSE_WAIT conntrack state occurs when the Linux kernel + // sees a FIN from the remote server. Note: this is a half-close + // condition that persists as long as the local side keeps the + // socket open. The condition is rare as it is typical in most + // protocols for both sides to issue a close; this typically + // occurs when the local socket is lazily garbage collected. + // + // If the CLOSE_WAIT conntrack entry expires, then FINs from the + // local socket will not be properly SNAT'd and will not reach the + // remote server (if the connection was subject to SNAT). If the + // remote timeouts for FIN_WAIT* states exceed the CLOSE_WAIT + // timeout, then there will be an inconsistency in the state of + // the connection and a new connection reusing the SNAT (src, + // port) pair may be rejected by the remote side with RST. This + // can cause new calls to connect(2) to return with ECONNREFUSED. + // + // We set CLOSE_WAIT to one hour by default to better match + // typical server timeouts. + obj.Conntrack.TCPCloseWaitTimeout = &metav1.Duration{Duration: 1 * time.Hour} + } + if obj.ConfigSyncPeriod.Duration == 0 { + obj.ConfigSyncPeriod.Duration = 15 * time.Minute + } + + if len(obj.ClientConnection.ContentType) == 0 { + obj.ClientConnection.ContentType = "application/vnd.kubernetes.protobuf" + } + if obj.ClientConnection.QPS == 0.0 { + obj.ClientConnection.QPS = 5.0 + } + if obj.ClientConnection.Burst == 0 { + obj.ClientConnection.Burst = 10 + } +} diff --git a/pkg/proxy/apis/kubeproxyconfig/v1alpha1/doc.go b/pkg/proxy/apis/kubeproxyconfig/v1alpha1/doc.go new file mode 100644 index 00000000000..0aea3998851 --- /dev/null +++ b/pkg/proxy/apis/kubeproxyconfig/v1alpha1/doc.go @@ -0,0 +1,22 @@ +/* +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/kubernetes/pkg/proxy/apis/kubeproxyconfig +// +k8s:openapi-gen=true +// +k8s:defaulter-gen=TypeMeta + +package v1alpha1 // import "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1" diff --git a/pkg/proxy/apis/kubeproxyconfig/v1alpha1/register.go b/pkg/proxy/apis/kubeproxyconfig/v1alpha1/register.go new file mode 100644 index 00000000000..0c387fbc2e1 --- /dev/null +++ b/pkg/proxy/apis/kubeproxyconfig/v1alpha1/register.go @@ -0,0 +1,50 @@ +/* +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 v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName is the group name use in this package +const GroupName = "kubeproxy.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, addDefaultingFuncs) +} + +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &KubeProxyConfiguration{}, + ) + return nil +} diff --git a/pkg/proxy/apis/kubeproxyconfig/v1alpha1/types.go b/pkg/proxy/apis/kubeproxyconfig/v1alpha1/types.go new file mode 100644 index 00000000000..add86cdcbf0 --- /dev/null +++ b/pkg/proxy/apis/kubeproxyconfig/v1alpha1/types.go @@ -0,0 +1,161 @@ +/* +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" +) + +// ClientConnectionConfiguration contains details for constructing a client. +type ClientConnectionConfiguration struct { + // kubeConfigFile is the path to a kubeconfig file. + KubeConfigFile string `json:"kubeconfig"` + // acceptContentTypes defines the Accept header sent by clients when connecting to a server, overriding the + // default value of 'application/json'. This field will control all connections to the server used by a particular + // client. + AcceptContentTypes string `json:"acceptContentTypes"` + // contentType is the content type used when sending data to the server from this client. + ContentType string `json:"contentType"` + // cps controls the number of queries per second allowed for this connection. + QPS float32 `json:"qps"` + // burst allows extra queries to accumulate when a client is exceeding its rate. + Burst int `json:"burst"` +} + +// KubeProxyIPTablesConfiguration contains iptables-related configuration +// details for the Kubernetes proxy server. +type KubeProxyIPTablesConfiguration struct { + // masqueradeBit is the bit of the iptables fwmark space to use for SNAT if using + // the pure iptables proxy mode. Values must be within the range [0, 31]. + MasqueradeBit *int32 `json:"masqueradeBit"` + // masqueradeAll tells kube-proxy to SNAT everything if using the pure iptables proxy mode. + MasqueradeAll bool `json:"masqueradeAll"` + // syncPeriod is the period that iptables rules are refreshed (e.g. '5s', '1m', + // '2h22m'). Must be greater than 0. + SyncPeriod metav1.Duration `json:"syncPeriod"` + // minSyncPeriod is the minimum period that iptables rules are refreshed (e.g. '5s', '1m', + // '2h22m'). + MinSyncPeriod metav1.Duration `json:"minSyncPeriod"` +} + +// KubeProxyIPVSConfiguration contains ipvs-related configuration +// details for the Kubernetes proxy server. +type KubeProxyIPVSConfiguration struct { + // syncPeriod is the period that ipvs rules are refreshed (e.g. '5s', '1m', + // '2h22m'). Must be greater than 0. + SyncPeriod metav1.Duration `json:"syncPeriod"` + // minSyncPeriod is the minimum period that ipvs rules are refreshed (e.g. '5s', '1m', + // '2h22m'). + MinSyncPeriod metav1.Duration `json:"minSyncPeriod"` + // ipvs scheduler + Scheduler string `json:"scheduler"` +} + +// KubeProxyConntrackConfiguration contains conntrack settings for +// the Kubernetes proxy server. +type KubeProxyConntrackConfiguration struct { + // max is the maximum number of NAT connections to track (0 to + // leave as-is). This takes precedence over maxPerCore and min. + Max *int32 `json:"max"` + // maxPerCore is the maximum number of NAT connections to track + // per CPU core (0 to leave the limit as-is and ignore min). + MaxPerCore *int32 `json:"maxPerCore"` + // min is the minimum value of connect-tracking records to allocate, + // regardless of conntrackMaxPerCore (set maxPerCore=0 to leave the limit as-is). + Min *int32 `json:"min"` + // tcpEstablishedTimeout is how long an idle TCP connection will be kept open + // (e.g. '2s'). Must be greater than 0 to set. + TCPEstablishedTimeout *metav1.Duration `json:"tcpEstablishedTimeout"` + // tcpCloseWaitTimeout is how long an idle conntrack entry + // in CLOSE_WAIT state will remain in the conntrack + // table. (e.g. '60s'). Must be greater than 0 to set. + TCPCloseWaitTimeout *metav1.Duration `json:"tcpCloseWaitTimeout"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// KubeProxyConfiguration contains everything necessary to configure the +// Kubernetes proxy server. +type KubeProxyConfiguration struct { + metav1.TypeMeta `json:",inline"` + + // featureGates is a comma-separated list of key=value pairs that control + // which alpha/beta features are enabled. + // + // TODO this really should be a map but that requires refactoring all + // components to use config files because local-up-cluster.sh only supports + // the --feature-gates flag right now, which is comma-separated key=value + // pairs. + FeatureGates string `json:"featureGates"` + + // bindAddress is the IP address for the proxy server to serve on (set to 0.0.0.0 + // for all interfaces) + BindAddress string `json:"bindAddress"` + // healthzBindAddress is the IP address and port for the health check server to serve on, + // defaulting to 0.0.0.0:10256 + HealthzBindAddress string `json:"healthzBindAddress"` + // metricsBindAddress is the IP address and port for the metrics server to serve on, + // defaulting to 127.0.0.1:10249 (set to 0.0.0.0 for all interfaces) + MetricsBindAddress string `json:"metricsBindAddress"` + // enableProfiling enables profiling via web interface on /debug/pprof handler. + // Profiling handlers will be handled by metrics server. + EnableProfiling bool `json:"enableProfiling"` + // clusterCIDR is the CIDR range of the pods in the cluster. It is used to + // bridge traffic coming from outside of the cluster. If not provided, + // no off-cluster bridging will be performed. + ClusterCIDR string `json:"clusterCIDR"` + // hostnameOverride, if non-empty, will be used as the identity instead of the actual hostname. + HostnameOverride string `json:"hostnameOverride"` + // clientConnection specifies the kubeconfig file and client connection settings for the proxy + // server to use when communicating with the apiserver. + ClientConnection ClientConnectionConfiguration `json:"clientConnection"` + // iptables contains iptables-related configuration options. + IPTables KubeProxyIPTablesConfiguration `json:"iptables"` + // ipvs contains ipvs-related configuration options. + IPVS KubeProxyIPVSConfiguration `json:"ipvs"` + // oomScoreAdj is the oom-score-adj value for kube-proxy process. Values must be within + // the range [-1000, 1000] + OOMScoreAdj *int32 `json:"oomScoreAdj"` + // mode specifies which proxy mode to use. + Mode ProxyMode `json:"mode"` + // portRange is the range of host ports (beginPort-endPort, inclusive) that may be consumed + // in order to proxy service traffic. If unspecified (0-0) then ports will be randomly chosen. + PortRange string `json:"portRange"` + // resourceContainer is the bsolute name of the resource-only container to create and run + // the Kube-proxy in (Default: /kube-proxy). + ResourceContainer string `json:"resourceContainer"` + // udpIdleTimeout is how long an idle UDP connection will be kept open (e.g. '250ms', '2s'). + // Must be greater than 0. Only applicable for proxyMode=userspace. + UDPIdleTimeout metav1.Duration `json:"udpTimeoutMilliseconds"` + // conntrack contains conntrack-related configuration options. + Conntrack KubeProxyConntrackConfiguration `json:"conntrack"` + // configSyncPeriod is how often configuration from the apiserver is refreshed. Must be greater + // than 0. + ConfigSyncPeriod metav1.Duration `json:"configSyncPeriod"` +} + +// Currently two modes of proxying are available: 'userspace' (older, stable) or 'iptables' +// (newer, faster). If blank, use the best-available proxy (currently iptables, but may +// change in future versions). If the iptables proxy is selected, regardless of how, but +// the system's kernel or iptables versions are insufficient, this always falls back to the +// userspace proxy. +type ProxyMode string + +const ( + ProxyModeUserspace ProxyMode = "userspace" + ProxyModeIPTables ProxyMode = "iptables" +) diff --git a/pkg/proxy/apis/kubeproxyconfig/v1alpha1/zz_generated.conversion.go b/pkg/proxy/apis/kubeproxyconfig/v1alpha1/zz_generated.conversion.go new file mode 100644 index 00000000000..ae2742d277c --- /dev/null +++ b/pkg/proxy/apis/kubeproxyconfig/v1alpha1/zz_generated.conversion.go @@ -0,0 +1,225 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by conversion-gen. Do not edit it manually! + +package v1alpha1 + +import ( + unsafe "unsafe" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + conversion "k8s.io/apimachinery/pkg/conversion" + runtime "k8s.io/apimachinery/pkg/runtime" + kubeproxyconfig "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig" +) + +func init() { + localSchemeBuilder.Register(RegisterConversions) +} + +// RegisterConversions adds conversion functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterConversions(scheme *runtime.Scheme) error { + return scheme.AddGeneratedConversionFuncs( + Convert_v1alpha1_ClientConnectionConfiguration_To_kubeproxyconfig_ClientConnectionConfiguration, + Convert_kubeproxyconfig_ClientConnectionConfiguration_To_v1alpha1_ClientConnectionConfiguration, + Convert_v1alpha1_KubeProxyConfiguration_To_kubeproxyconfig_KubeProxyConfiguration, + Convert_kubeproxyconfig_KubeProxyConfiguration_To_v1alpha1_KubeProxyConfiguration, + Convert_v1alpha1_KubeProxyConntrackConfiguration_To_kubeproxyconfig_KubeProxyConntrackConfiguration, + Convert_kubeproxyconfig_KubeProxyConntrackConfiguration_To_v1alpha1_KubeProxyConntrackConfiguration, + Convert_v1alpha1_KubeProxyIPTablesConfiguration_To_kubeproxyconfig_KubeProxyIPTablesConfiguration, + Convert_kubeproxyconfig_KubeProxyIPTablesConfiguration_To_v1alpha1_KubeProxyIPTablesConfiguration, + Convert_v1alpha1_KubeProxyIPVSConfiguration_To_kubeproxyconfig_KubeProxyIPVSConfiguration, + Convert_kubeproxyconfig_KubeProxyIPVSConfiguration_To_v1alpha1_KubeProxyIPVSConfiguration, + ) +} + +func autoConvert_v1alpha1_ClientConnectionConfiguration_To_kubeproxyconfig_ClientConnectionConfiguration(in *ClientConnectionConfiguration, out *kubeproxyconfig.ClientConnectionConfiguration, s conversion.Scope) error { + out.KubeConfigFile = in.KubeConfigFile + out.AcceptContentTypes = in.AcceptContentTypes + out.ContentType = in.ContentType + out.QPS = in.QPS + out.Burst = int32(in.Burst) + return nil +} + +// Convert_v1alpha1_ClientConnectionConfiguration_To_kubeproxyconfig_ClientConnectionConfiguration is an autogenerated conversion function. +func Convert_v1alpha1_ClientConnectionConfiguration_To_kubeproxyconfig_ClientConnectionConfiguration(in *ClientConnectionConfiguration, out *kubeproxyconfig.ClientConnectionConfiguration, s conversion.Scope) error { + return autoConvert_v1alpha1_ClientConnectionConfiguration_To_kubeproxyconfig_ClientConnectionConfiguration(in, out, s) +} + +func autoConvert_kubeproxyconfig_ClientConnectionConfiguration_To_v1alpha1_ClientConnectionConfiguration(in *kubeproxyconfig.ClientConnectionConfiguration, out *ClientConnectionConfiguration, s conversion.Scope) error { + out.KubeConfigFile = in.KubeConfigFile + out.AcceptContentTypes = in.AcceptContentTypes + out.ContentType = in.ContentType + out.QPS = in.QPS + out.Burst = int(in.Burst) + return nil +} + +// Convert_kubeproxyconfig_ClientConnectionConfiguration_To_v1alpha1_ClientConnectionConfiguration is an autogenerated conversion function. +func Convert_kubeproxyconfig_ClientConnectionConfiguration_To_v1alpha1_ClientConnectionConfiguration(in *kubeproxyconfig.ClientConnectionConfiguration, out *ClientConnectionConfiguration, s conversion.Scope) error { + return autoConvert_kubeproxyconfig_ClientConnectionConfiguration_To_v1alpha1_ClientConnectionConfiguration(in, out, s) +} + +func autoConvert_v1alpha1_KubeProxyConfiguration_To_kubeproxyconfig_KubeProxyConfiguration(in *KubeProxyConfiguration, out *kubeproxyconfig.KubeProxyConfiguration, s conversion.Scope) error { + out.FeatureGates = in.FeatureGates + out.BindAddress = in.BindAddress + out.HealthzBindAddress = in.HealthzBindAddress + out.MetricsBindAddress = in.MetricsBindAddress + out.EnableProfiling = in.EnableProfiling + out.ClusterCIDR = in.ClusterCIDR + out.HostnameOverride = in.HostnameOverride + if err := Convert_v1alpha1_ClientConnectionConfiguration_To_kubeproxyconfig_ClientConnectionConfiguration(&in.ClientConnection, &out.ClientConnection, s); err != nil { + return err + } + if err := Convert_v1alpha1_KubeProxyIPTablesConfiguration_To_kubeproxyconfig_KubeProxyIPTablesConfiguration(&in.IPTables, &out.IPTables, s); err != nil { + return err + } + if err := Convert_v1alpha1_KubeProxyIPVSConfiguration_To_kubeproxyconfig_KubeProxyIPVSConfiguration(&in.IPVS, &out.IPVS, s); err != nil { + return err + } + out.OOMScoreAdj = (*int32)(unsafe.Pointer(in.OOMScoreAdj)) + out.Mode = kubeproxyconfig.ProxyMode(in.Mode) + out.PortRange = in.PortRange + out.ResourceContainer = in.ResourceContainer + out.UDPIdleTimeout = in.UDPIdleTimeout + if err := Convert_v1alpha1_KubeProxyConntrackConfiguration_To_kubeproxyconfig_KubeProxyConntrackConfiguration(&in.Conntrack, &out.Conntrack, s); err != nil { + return err + } + out.ConfigSyncPeriod = in.ConfigSyncPeriod + return nil +} + +// Convert_v1alpha1_KubeProxyConfiguration_To_kubeproxyconfig_KubeProxyConfiguration is an autogenerated conversion function. +func Convert_v1alpha1_KubeProxyConfiguration_To_kubeproxyconfig_KubeProxyConfiguration(in *KubeProxyConfiguration, out *kubeproxyconfig.KubeProxyConfiguration, s conversion.Scope) error { + return autoConvert_v1alpha1_KubeProxyConfiguration_To_kubeproxyconfig_KubeProxyConfiguration(in, out, s) +} + +func autoConvert_kubeproxyconfig_KubeProxyConfiguration_To_v1alpha1_KubeProxyConfiguration(in *kubeproxyconfig.KubeProxyConfiguration, out *KubeProxyConfiguration, s conversion.Scope) error { + out.FeatureGates = in.FeatureGates + out.BindAddress = in.BindAddress + out.HealthzBindAddress = in.HealthzBindAddress + out.MetricsBindAddress = in.MetricsBindAddress + out.EnableProfiling = in.EnableProfiling + out.ClusterCIDR = in.ClusterCIDR + out.HostnameOverride = in.HostnameOverride + if err := Convert_kubeproxyconfig_ClientConnectionConfiguration_To_v1alpha1_ClientConnectionConfiguration(&in.ClientConnection, &out.ClientConnection, s); err != nil { + return err + } + if err := Convert_kubeproxyconfig_KubeProxyIPTablesConfiguration_To_v1alpha1_KubeProxyIPTablesConfiguration(&in.IPTables, &out.IPTables, s); err != nil { + return err + } + if err := Convert_kubeproxyconfig_KubeProxyIPVSConfiguration_To_v1alpha1_KubeProxyIPVSConfiguration(&in.IPVS, &out.IPVS, s); err != nil { + return err + } + out.OOMScoreAdj = (*int32)(unsafe.Pointer(in.OOMScoreAdj)) + out.Mode = ProxyMode(in.Mode) + out.PortRange = in.PortRange + out.ResourceContainer = in.ResourceContainer + out.UDPIdleTimeout = in.UDPIdleTimeout + if err := Convert_kubeproxyconfig_KubeProxyConntrackConfiguration_To_v1alpha1_KubeProxyConntrackConfiguration(&in.Conntrack, &out.Conntrack, s); err != nil { + return err + } + out.ConfigSyncPeriod = in.ConfigSyncPeriod + return nil +} + +// Convert_kubeproxyconfig_KubeProxyConfiguration_To_v1alpha1_KubeProxyConfiguration is an autogenerated conversion function. +func Convert_kubeproxyconfig_KubeProxyConfiguration_To_v1alpha1_KubeProxyConfiguration(in *kubeproxyconfig.KubeProxyConfiguration, out *KubeProxyConfiguration, s conversion.Scope) error { + return autoConvert_kubeproxyconfig_KubeProxyConfiguration_To_v1alpha1_KubeProxyConfiguration(in, out, s) +} + +func autoConvert_v1alpha1_KubeProxyConntrackConfiguration_To_kubeproxyconfig_KubeProxyConntrackConfiguration(in *KubeProxyConntrackConfiguration, out *kubeproxyconfig.KubeProxyConntrackConfiguration, s conversion.Scope) error { + out.Max = (*int32)(unsafe.Pointer(in.Max)) + out.MaxPerCore = (*int32)(unsafe.Pointer(in.MaxPerCore)) + out.Min = (*int32)(unsafe.Pointer(in.Min)) + out.TCPEstablishedTimeout = (*v1.Duration)(unsafe.Pointer(in.TCPEstablishedTimeout)) + out.TCPCloseWaitTimeout = (*v1.Duration)(unsafe.Pointer(in.TCPCloseWaitTimeout)) + return nil +} + +// Convert_v1alpha1_KubeProxyConntrackConfiguration_To_kubeproxyconfig_KubeProxyConntrackConfiguration is an autogenerated conversion function. +func Convert_v1alpha1_KubeProxyConntrackConfiguration_To_kubeproxyconfig_KubeProxyConntrackConfiguration(in *KubeProxyConntrackConfiguration, out *kubeproxyconfig.KubeProxyConntrackConfiguration, s conversion.Scope) error { + return autoConvert_v1alpha1_KubeProxyConntrackConfiguration_To_kubeproxyconfig_KubeProxyConntrackConfiguration(in, out, s) +} + +func autoConvert_kubeproxyconfig_KubeProxyConntrackConfiguration_To_v1alpha1_KubeProxyConntrackConfiguration(in *kubeproxyconfig.KubeProxyConntrackConfiguration, out *KubeProxyConntrackConfiguration, s conversion.Scope) error { + out.Max = (*int32)(unsafe.Pointer(in.Max)) + out.MaxPerCore = (*int32)(unsafe.Pointer(in.MaxPerCore)) + out.Min = (*int32)(unsafe.Pointer(in.Min)) + out.TCPEstablishedTimeout = (*v1.Duration)(unsafe.Pointer(in.TCPEstablishedTimeout)) + out.TCPCloseWaitTimeout = (*v1.Duration)(unsafe.Pointer(in.TCPCloseWaitTimeout)) + return nil +} + +// Convert_kubeproxyconfig_KubeProxyConntrackConfiguration_To_v1alpha1_KubeProxyConntrackConfiguration is an autogenerated conversion function. +func Convert_kubeproxyconfig_KubeProxyConntrackConfiguration_To_v1alpha1_KubeProxyConntrackConfiguration(in *kubeproxyconfig.KubeProxyConntrackConfiguration, out *KubeProxyConntrackConfiguration, s conversion.Scope) error { + return autoConvert_kubeproxyconfig_KubeProxyConntrackConfiguration_To_v1alpha1_KubeProxyConntrackConfiguration(in, out, s) +} + +func autoConvert_v1alpha1_KubeProxyIPTablesConfiguration_To_kubeproxyconfig_KubeProxyIPTablesConfiguration(in *KubeProxyIPTablesConfiguration, out *kubeproxyconfig.KubeProxyIPTablesConfiguration, s conversion.Scope) error { + out.MasqueradeBit = (*int32)(unsafe.Pointer(in.MasqueradeBit)) + out.MasqueradeAll = in.MasqueradeAll + out.SyncPeriod = in.SyncPeriod + out.MinSyncPeriod = in.MinSyncPeriod + return nil +} + +// Convert_v1alpha1_KubeProxyIPTablesConfiguration_To_kubeproxyconfig_KubeProxyIPTablesConfiguration is an autogenerated conversion function. +func Convert_v1alpha1_KubeProxyIPTablesConfiguration_To_kubeproxyconfig_KubeProxyIPTablesConfiguration(in *KubeProxyIPTablesConfiguration, out *kubeproxyconfig.KubeProxyIPTablesConfiguration, s conversion.Scope) error { + return autoConvert_v1alpha1_KubeProxyIPTablesConfiguration_To_kubeproxyconfig_KubeProxyIPTablesConfiguration(in, out, s) +} + +func autoConvert_kubeproxyconfig_KubeProxyIPTablesConfiguration_To_v1alpha1_KubeProxyIPTablesConfiguration(in *kubeproxyconfig.KubeProxyIPTablesConfiguration, out *KubeProxyIPTablesConfiguration, s conversion.Scope) error { + out.MasqueradeBit = (*int32)(unsafe.Pointer(in.MasqueradeBit)) + out.MasqueradeAll = in.MasqueradeAll + out.SyncPeriod = in.SyncPeriod + out.MinSyncPeriod = in.MinSyncPeriod + return nil +} + +// Convert_kubeproxyconfig_KubeProxyIPTablesConfiguration_To_v1alpha1_KubeProxyIPTablesConfiguration is an autogenerated conversion function. +func Convert_kubeproxyconfig_KubeProxyIPTablesConfiguration_To_v1alpha1_KubeProxyIPTablesConfiguration(in *kubeproxyconfig.KubeProxyIPTablesConfiguration, out *KubeProxyIPTablesConfiguration, s conversion.Scope) error { + return autoConvert_kubeproxyconfig_KubeProxyIPTablesConfiguration_To_v1alpha1_KubeProxyIPTablesConfiguration(in, out, s) +} + +func autoConvert_v1alpha1_KubeProxyIPVSConfiguration_To_kubeproxyconfig_KubeProxyIPVSConfiguration(in *KubeProxyIPVSConfiguration, out *kubeproxyconfig.KubeProxyIPVSConfiguration, s conversion.Scope) error { + out.SyncPeriod = in.SyncPeriod + out.MinSyncPeriod = in.MinSyncPeriod + out.Scheduler = in.Scheduler + return nil +} + +// Convert_v1alpha1_KubeProxyIPVSConfiguration_To_kubeproxyconfig_KubeProxyIPVSConfiguration is an autogenerated conversion function. +func Convert_v1alpha1_KubeProxyIPVSConfiguration_To_kubeproxyconfig_KubeProxyIPVSConfiguration(in *KubeProxyIPVSConfiguration, out *kubeproxyconfig.KubeProxyIPVSConfiguration, s conversion.Scope) error { + return autoConvert_v1alpha1_KubeProxyIPVSConfiguration_To_kubeproxyconfig_KubeProxyIPVSConfiguration(in, out, s) +} + +func autoConvert_kubeproxyconfig_KubeProxyIPVSConfiguration_To_v1alpha1_KubeProxyIPVSConfiguration(in *kubeproxyconfig.KubeProxyIPVSConfiguration, out *KubeProxyIPVSConfiguration, s conversion.Scope) error { + out.SyncPeriod = in.SyncPeriod + out.MinSyncPeriod = in.MinSyncPeriod + out.Scheduler = in.Scheduler + return nil +} + +// Convert_kubeproxyconfig_KubeProxyIPVSConfiguration_To_v1alpha1_KubeProxyIPVSConfiguration is an autogenerated conversion function. +func Convert_kubeproxyconfig_KubeProxyIPVSConfiguration_To_v1alpha1_KubeProxyIPVSConfiguration(in *kubeproxyconfig.KubeProxyIPVSConfiguration, out *KubeProxyIPVSConfiguration, s conversion.Scope) error { + return autoConvert_kubeproxyconfig_KubeProxyIPVSConfiguration_To_v1alpha1_KubeProxyIPVSConfiguration(in, out, s) +} diff --git a/pkg/proxy/apis/kubeproxyconfig/v1alpha1/zz_generated.deepcopy.go b/pkg/proxy/apis/kubeproxyconfig/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..65ba9af1ba5 --- /dev/null +++ b/pkg/proxy/apis/kubeproxyconfig/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,189 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +package v1alpha1 + +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 *ClientConnectionConfiguration) DeepCopyInto(out *ClientConnectionConfiguration) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientConnectionConfiguration. +func (in *ClientConnectionConfiguration) DeepCopy() *ClientConnectionConfiguration { + if in == nil { + return nil + } + out := new(ClientConnectionConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeProxyConfiguration) DeepCopyInto(out *KubeProxyConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ClientConnection = in.ClientConnection + in.IPTables.DeepCopyInto(&out.IPTables) + out.IPVS = in.IPVS + if in.OOMScoreAdj != nil { + in, out := &in.OOMScoreAdj, &out.OOMScoreAdj + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + out.UDPIdleTimeout = in.UDPIdleTimeout + in.Conntrack.DeepCopyInto(&out.Conntrack) + out.ConfigSyncPeriod = in.ConfigSyncPeriod + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeProxyConfiguration. +func (in *KubeProxyConfiguration) DeepCopy() *KubeProxyConfiguration { + if in == nil { + return nil + } + out := new(KubeProxyConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *KubeProxyConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeProxyConntrackConfiguration) DeepCopyInto(out *KubeProxyConntrackConfiguration) { + *out = *in + if in.Max != nil { + in, out := &in.Max, &out.Max + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + if in.MaxPerCore != nil { + in, out := &in.MaxPerCore, &out.MaxPerCore + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + if in.Min != nil { + in, out := &in.Min, &out.Min + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + if in.TCPEstablishedTimeout != nil { + in, out := &in.TCPEstablishedTimeout, &out.TCPEstablishedTimeout + if *in == nil { + *out = nil + } else { + *out = new(v1.Duration) + **out = **in + } + } + if in.TCPCloseWaitTimeout != nil { + in, out := &in.TCPCloseWaitTimeout, &out.TCPCloseWaitTimeout + if *in == nil { + *out = nil + } else { + *out = new(v1.Duration) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeProxyConntrackConfiguration. +func (in *KubeProxyConntrackConfiguration) DeepCopy() *KubeProxyConntrackConfiguration { + if in == nil { + return nil + } + out := new(KubeProxyConntrackConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeProxyIPTablesConfiguration) DeepCopyInto(out *KubeProxyIPTablesConfiguration) { + *out = *in + if in.MasqueradeBit != nil { + in, out := &in.MasqueradeBit, &out.MasqueradeBit + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + out.SyncPeriod = in.SyncPeriod + out.MinSyncPeriod = in.MinSyncPeriod + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeProxyIPTablesConfiguration. +func (in *KubeProxyIPTablesConfiguration) DeepCopy() *KubeProxyIPTablesConfiguration { + if in == nil { + return nil + } + out := new(KubeProxyIPTablesConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeProxyIPVSConfiguration) DeepCopyInto(out *KubeProxyIPVSConfiguration) { + *out = *in + out.SyncPeriod = in.SyncPeriod + out.MinSyncPeriod = in.MinSyncPeriod + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeProxyIPVSConfiguration. +func (in *KubeProxyIPVSConfiguration) DeepCopy() *KubeProxyIPVSConfiguration { + if in == nil { + return nil + } + out := new(KubeProxyIPVSConfiguration) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/proxy/apis/kubeproxyconfig/v1alpha1/zz_generated.defaults.go b/pkg/proxy/apis/kubeproxyconfig/v1alpha1/zz_generated.defaults.go new file mode 100644 index 00000000000..93d5f80fec4 --- /dev/null +++ b/pkg/proxy/apis/kubeproxyconfig/v1alpha1/zz_generated.defaults.go @@ -0,0 +1,37 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by defaulter-gen. Do not edit it manually! + +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 { + scheme.AddTypeDefaultingFunc(&KubeProxyConfiguration{}, func(obj interface{}) { SetObjectDefaults_KubeProxyConfiguration(obj.(*KubeProxyConfiguration)) }) + return nil +} + +func SetObjectDefaults_KubeProxyConfiguration(in *KubeProxyConfiguration) { + SetDefaults_KubeProxyConfiguration(in) +} diff --git a/pkg/proxy/apis/kubeproxyconfig/validation/BUILD b/pkg/proxy/apis/kubeproxyconfig/validation/BUILD new file mode 100644 index 00000000000..319f8034534 --- /dev/null +++ b/pkg/proxy/apis/kubeproxyconfig/validation/BUILD @@ -0,0 +1,46 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_library( + name = "go_default_library", + srcs = ["validation.go"], + importpath = "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/validation", + deps = [ + "//pkg/apis/core/validation:go_default_library", + "//pkg/proxy/apis/kubeproxyconfig:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) + +go_test( + name = "go_default_test", + srcs = ["validation_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/validation", + deps = [ + "//pkg/proxy/apis/kubeproxyconfig:go_default_library", + "//pkg/util/pointer:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + ], +) diff --git a/pkg/proxy/apis/kubeproxyconfig/validation/validation.go b/pkg/proxy/apis/kubeproxyconfig/validation/validation.go new file mode 100644 index 00000000000..e2453af72c4 --- /dev/null +++ b/pkg/proxy/apis/kubeproxyconfig/validation/validation.go @@ -0,0 +1,240 @@ +/* +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 validation + +import ( + "fmt" + "net" + "runtime" + "strconv" + "strings" + + utilnet "k8s.io/apimachinery/pkg/util/net" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation/field" + apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" + "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig" +) + +// Validate validates the configuration of kube-proxy +func Validate(config *kubeproxyconfig.KubeProxyConfiguration) field.ErrorList { + allErrs := field.ErrorList{} + + newPath := field.NewPath("KubeProxyConfiguration") + + allErrs = append(allErrs, validateKubeProxyIPTablesConfiguration(config.IPTables, newPath.Child("KubeProxyIPTablesConfiguration"))...) + if config.Mode == kubeproxyconfig.ProxyModeIPVS { + allErrs = append(allErrs, validateKubeProxyIPVSConfiguration(config.IPVS, newPath.Child("KubeProxyIPVSConfiguration"))...) + } + allErrs = append(allErrs, validateKubeProxyConntrackConfiguration(config.Conntrack, newPath.Child("KubeProxyConntrackConfiguration"))...) + allErrs = append(allErrs, validateProxyMode(config.Mode, newPath.Child("Mode"))...) + allErrs = append(allErrs, validateClientConnectionConfiguration(config.ClientConnection, newPath.Child("ClientConnection"))...) + + if config.OOMScoreAdj != nil && (*config.OOMScoreAdj < -1000 || *config.OOMScoreAdj > 1000) { + allErrs = append(allErrs, field.Invalid(newPath.Child("OOMScoreAdj"), *config.OOMScoreAdj, "must be within the range [-1000, 1000]")) + } + + if config.UDPIdleTimeout.Duration <= 0 { + allErrs = append(allErrs, field.Invalid(newPath.Child("UDPIdleTimeout"), config.UDPIdleTimeout, "must be greater than 0")) + } + + if config.ConfigSyncPeriod.Duration <= 0 { + allErrs = append(allErrs, field.Invalid(newPath.Child("ConfigSyncPeriod"), config.ConfigSyncPeriod, "must be greater than 0")) + } + + if net.ParseIP(config.BindAddress) == nil { + allErrs = append(allErrs, field.Invalid(newPath.Child("BindAddress"), config.BindAddress, "not a valid textual representation of an IP address")) + } + + allErrs = append(allErrs, validateHostPort(config.HealthzBindAddress, newPath.Child("HealthzBindAddress"))...) + allErrs = append(allErrs, validateHostPort(config.MetricsBindAddress, newPath.Child("MetricsBindAddress"))...) + + if config.ClusterCIDR != "" { + if _, _, err := net.ParseCIDR(config.ClusterCIDR); err != nil { + allErrs = append(allErrs, field.Invalid(newPath.Child("ClusterCIDR"), config.ClusterCIDR, "must be a valid CIDR block (e.g. 10.100.0.0/16)")) + } + } + + if _, err := utilnet.ParsePortRange(config.PortRange); err != nil { + allErrs = append(allErrs, field.Invalid(newPath.Child("PortRange"), config.PortRange, "must be a valid port range (e.g. 300-2000)")) + } + + return allErrs +} + +func validateKubeProxyIPTablesConfiguration(config kubeproxyconfig.KubeProxyIPTablesConfiguration, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if config.MasqueradeBit != nil && (*config.MasqueradeBit < 0 || *config.MasqueradeBit > 31) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("MasqueradeBit"), config.MasqueradeBit, "must be within the range [0, 31]")) + } + + if config.SyncPeriod.Duration <= 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("SyncPeriod"), config.SyncPeriod, "must be greater than 0")) + } + + if config.MinSyncPeriod.Duration < 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("MinSyncPeriod"), config.MinSyncPeriod, "must be greater than or equal to 0")) + } + + if config.MinSyncPeriod.Duration > config.SyncPeriod.Duration { + allErrs = append(allErrs, field.Invalid(fldPath.Child("SyncPeriod"), config.MinSyncPeriod, fmt.Sprintf("must be greater than or equal to %s", fldPath.Child("MinSyncPeriod").String()))) + } + + return allErrs +} + +func validateKubeProxyIPVSConfiguration(config kubeproxyconfig.KubeProxyIPVSConfiguration, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if config.SyncPeriod.Duration <= 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("SyncPeriod"), config.SyncPeriod, "must be greater than 0")) + } + + if config.MinSyncPeriod.Duration < 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("MinSyncPeriod"), config.MinSyncPeriod, "must be greater than or equal to 0")) + } + + if config.MinSyncPeriod.Duration > config.SyncPeriod.Duration { + allErrs = append(allErrs, field.Invalid(fldPath.Child("SyncPeriod"), config.MinSyncPeriod, fmt.Sprintf("must be greater than or equal to %s", fldPath.Child("MinSyncPeriod").String()))) + } + + allErrs = append(allErrs, validateIPVSSchedulerMethod(kubeproxyconfig.IPVSSchedulerMethod(config.Scheduler), fldPath.Child("Scheduler"))...) + + return allErrs +} + +func validateKubeProxyConntrackConfiguration(config kubeproxyconfig.KubeProxyConntrackConfiguration, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if config.Max != nil && *config.Max < 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("Max"), config.Max, "must be greater than or equal to 0")) + } + + if config.MaxPerCore != nil && *config.MaxPerCore < 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("MaxPerCore"), config.MaxPerCore, "must be greater than or equal to 0")) + } + + if config.Min != nil && *config.Min < 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("Min"), config.Min, "must be greater than or equal to 0")) + } + + if config.TCPEstablishedTimeout.Duration < 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("TCPEstablishedTimeout"), config.TCPEstablishedTimeout, "must be greater than or equal to 0")) + } + + if config.TCPCloseWaitTimeout.Duration < 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("TCPCloseWaitTimeout"), config.TCPCloseWaitTimeout, "must be greater than or equal to 0")) + } + + return allErrs +} + +func validateProxyMode(mode kubeproxyconfig.ProxyMode, fldPath *field.Path) field.ErrorList { + if runtime.GOOS == "windows" { + return validateProxyModeWindows(mode, fldPath) + } + + return validateProxyModeLinux(mode, fldPath) +} + +func validateProxyModeLinux(mode kubeproxyconfig.ProxyMode, fldPath *field.Path) field.ErrorList { + validModes := sets.NewString( + string(kubeproxyconfig.ProxyModeUserspace), + string(kubeproxyconfig.ProxyModeIPTables), + string(kubeproxyconfig.ProxyModeIPVS), + ) + + if mode == "" || validModes.Has(string(mode)) { + return nil + } + + errMsg := fmt.Sprintf("must be %s or blank (blank means the best-available proxy [currently iptables])", strings.Join(validModes.List(), ",")) + return field.ErrorList{field.Invalid(fldPath.Child("ProxyMode"), string(mode), errMsg)} +} + +func validateProxyModeWindows(mode kubeproxyconfig.ProxyMode, fldPath *field.Path) field.ErrorList { + validModes := sets.NewString( + string(kubeproxyconfig.ProxyModeUserspace), + string(kubeproxyconfig.ProxyModeKernelspace), + ) + + if mode == "" || validModes.Has(string(mode)) { + return nil + } + + errMsg := fmt.Sprintf("must be %s or blank (blank means the most-available proxy [currently userspace])", strings.Join(validModes.List(), ",")) + return field.ErrorList{field.Invalid(fldPath.Child("ProxyMode"), string(mode), errMsg)} +} + +func validateClientConnectionConfiguration(config kubeproxyconfig.ClientConnectionConfiguration, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(config.Burst), fldPath.Child("Burst"))...) + return allErrs +} + +func validateHostPort(input string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + hostIP, port, err := net.SplitHostPort(input) + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath, input, "must be IP:port")) + return allErrs + } + + if ip := net.ParseIP(hostIP); ip == nil { + allErrs = append(allErrs, field.Invalid(fldPath, hostIP, "must be a valid IP")) + } + + if p, err := strconv.Atoi(port); err != nil { + allErrs = append(allErrs, field.Invalid(fldPath, port, "must be a valid port")) + } else if p < 1 || p > 65535 { + allErrs = append(allErrs, field.Invalid(fldPath, port, "must be a valid port")) + } + + return allErrs +} + +func validateIPVSSchedulerMethod(scheduler kubeproxyconfig.IPVSSchedulerMethod, fldPath *field.Path) field.ErrorList { + supportedMethod := []kubeproxyconfig.IPVSSchedulerMethod{ + kubeproxyconfig.RoundRobin, + kubeproxyconfig.WeightedRoundRobin, + kubeproxyconfig.LeastConnection, + kubeproxyconfig.WeightedLeastConnection, + kubeproxyconfig.LocalityBasedLeastConnection, + kubeproxyconfig.LocalityBasedLeastConnectionWithReplication, + kubeproxyconfig.SourceHashing, + kubeproxyconfig.DestinationHashing, + kubeproxyconfig.ShortestExpectedDelay, + kubeproxyconfig.NeverQueue, + "", + } + allErrs := field.ErrorList{} + var found bool + for i := range supportedMethod { + if scheduler == supportedMethod[i] { + found = true + break + } + } + // Not found + if !found { + errMsg := fmt.Sprintf("must be in %v, blank means the default algorithm method (currently rr)", supportedMethod) + allErrs = append(allErrs, field.Invalid(fldPath.Child("Scheduler"), string(scheduler), errMsg)) + } + return allErrs +} diff --git a/pkg/proxy/apis/kubeproxyconfig/validation/validation_test.go b/pkg/proxy/apis/kubeproxyconfig/validation/validation_test.go new file mode 100644 index 00000000000..aaf5e9f4a91 --- /dev/null +++ b/pkg/proxy/apis/kubeproxyconfig/validation/validation_test.go @@ -0,0 +1,675 @@ +/* +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 validation + +import ( + "fmt" + "runtime" + "strings" + "testing" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig" + "k8s.io/kubernetes/pkg/util/pointer" +) + +func TestValidateKubeProxyConfiguration(t *testing.T) { + successCases := []kubeproxyconfig.KubeProxyConfiguration{ + { + BindAddress: "192.168.59.103", + HealthzBindAddress: "0.0.0.0:10256", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24", + UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + Mode: kubeproxyconfig.ProxyModeIPVS, + IPVS: kubeproxyconfig.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + }, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + Max: pointer.Int32Ptr(2), + MaxPerCore: pointer.Int32Ptr(1), + Min: pointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + { + BindAddress: "192.168.59.103", + HealthzBindAddress: "0.0.0.0:10256", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24", + UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + Max: pointer.Int32Ptr(2), + MaxPerCore: pointer.Int32Ptr(1), + Min: pointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + } + + for _, successCase := range successCases { + if errs := Validate(&successCase); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + + errorCases := []struct { + config kubeproxyconfig.KubeProxyConfiguration + msg string + }{ + { + config: kubeproxyconfig.KubeProxyConfiguration{ + // only BindAddress is invalid + BindAddress: "10.10.12.11:2000", + HealthzBindAddress: "0.0.0.0:10256", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24", + UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + Max: pointer.Int32Ptr(2), + MaxPerCore: pointer.Int32Ptr(1), + Min: pointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + msg: "not a valid textual representation of an IP address", + }, + { + config: kubeproxyconfig.KubeProxyConfiguration{ + BindAddress: "10.10.12.11", + // only HealthzBindAddress is invalid + HealthzBindAddress: "0.0.0.0", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24", + UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + Max: pointer.Int32Ptr(2), + MaxPerCore: pointer.Int32Ptr(1), + Min: pointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + msg: "must be IP:port", + }, + { + config: kubeproxyconfig.KubeProxyConfiguration{ + BindAddress: "10.10.12.11", + HealthzBindAddress: "0.0.0.0:12345", + // only MetricsBindAddress is invalid + MetricsBindAddress: "127.0.0.1", + ClusterCIDR: "192.168.59.0/24", + UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + Max: pointer.Int32Ptr(2), + MaxPerCore: pointer.Int32Ptr(1), + Min: pointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + msg: "must be IP:port", + }, + { + config: kubeproxyconfig.KubeProxyConfiguration{ + BindAddress: "10.10.12.11", + HealthzBindAddress: "0.0.0.0:12345", + MetricsBindAddress: "127.0.0.1:10249", + // only ClusterCIDR is invalid + ClusterCIDR: "192.168.59.0", + UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + Max: pointer.Int32Ptr(2), + MaxPerCore: pointer.Int32Ptr(1), + Min: pointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + msg: "must be a valid CIDR block (e.g. 10.100.0.0/16)", + }, + { + config: kubeproxyconfig.KubeProxyConfiguration{ + BindAddress: "10.10.12.11", + HealthzBindAddress: "0.0.0.0:12345", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24", + // only UDPIdleTimeout is invalid + UDPIdleTimeout: metav1.Duration{Duration: -1 * time.Second}, + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + Max: pointer.Int32Ptr(2), + MaxPerCore: pointer.Int32Ptr(1), + Min: pointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + msg: "must be greater than 0", + }, + { + config: kubeproxyconfig.KubeProxyConfiguration{ + BindAddress: "10.10.12.11", + HealthzBindAddress: "0.0.0.0:12345", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24", + UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, + // only ConfigSyncPeriod is invalid + ConfigSyncPeriod: metav1.Duration{Duration: -1 * time.Second}, + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + Max: pointer.Int32Ptr(2), + MaxPerCore: pointer.Int32Ptr(1), + Min: pointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + msg: "must be greater than 0", + }, + { + config: kubeproxyconfig.KubeProxyConfiguration{ + BindAddress: "192.168.59.103", + HealthzBindAddress: "0.0.0.0:10256", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24", + UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + // not specifying valid period in IPVS mode. + Mode: kubeproxyconfig.ProxyModeIPVS, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + Max: pointer.Int32Ptr(2), + MaxPerCore: pointer.Int32Ptr(1), + Min: pointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + msg: "must be greater than 0", + }, + } + + for _, errorCase := range errorCases { + if errs := Validate(&errorCase.config); len(errs) == 0 { + t.Errorf("expected failure for %s", errorCase.msg) + } else if !strings.Contains(errs[0].Error(), errorCase.msg) { + t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg) + } + } +} + +func TestValidateKubeProxyIPTablesConfiguration(t *testing.T) { + valid := int32(5) + successCases := []kubeproxyconfig.KubeProxyIPTablesConfiguration{ + { + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + { + MasqueradeBit: &valid, + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + } + newPath := field.NewPath("KubeProxyConfiguration") + for _, successCase := range successCases { + if errs := validateKubeProxyIPTablesConfiguration(successCase, newPath.Child("KubeProxyIPTablesConfiguration")); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + + invalid := int32(-10) + errorCases := []struct { + config kubeproxyconfig.KubeProxyIPTablesConfiguration + msg string + }{ + { + config: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: -5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + msg: "must be greater than 0", + }, + { + config: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeBit: &valid, + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: -1 * time.Second}, + }, + msg: "must be greater than or equal to 0", + }, + { + config: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeBit: &invalid, + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + msg: "must be within the range [0, 31]", + }, + // SyncPeriod must be >= MinSyncPeriod + { + config: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeBit: &valid, + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + }, + msg: fmt.Sprintf("must be greater than or equal to %s", newPath.Child("KubeProxyIPTablesConfiguration").Child("MinSyncPeriod").String()), + }, + } + + for _, errorCase := range errorCases { + if errs := validateKubeProxyIPTablesConfiguration(errorCase.config, newPath.Child("KubeProxyIPTablesConfiguration")); len(errs) == 0 { + t.Errorf("expected failure for %s", errorCase.msg) + } else if !strings.Contains(errs[0].Error(), errorCase.msg) { + t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg) + } + } +} + +func TestValidateKubeProxyIPVSConfiguration(t *testing.T) { + newPath := field.NewPath("KubeProxyConfiguration") + testCases := []struct { + config kubeproxyconfig.KubeProxyIPVSConfiguration + expectErr bool + reason string + }{ + { + config: kubeproxyconfig.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: -5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + expectErr: true, + reason: "SyncPeriod must be greater than 0", + }, + { + config: kubeproxyconfig.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: 0 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second}, + }, + expectErr: true, + reason: "SyncPeriod must be greater than 0", + }, + { + config: kubeproxyconfig.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: -1 * time.Second}, + }, + expectErr: true, + reason: "MinSyncPeriod must be greater than or equal to 0", + }, + { + config: kubeproxyconfig.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + }, + expectErr: true, + reason: "SyncPeriod must be greater than or equal to MinSyncPeriod", + }, + // SyncPeriod == MinSyncPeriod + { + config: kubeproxyconfig.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second}, + }, + expectErr: false, + }, + // SyncPeriod > MinSyncPeriod + { + config: kubeproxyconfig.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + }, + expectErr: false, + }, + // SyncPeriod can be 0 + { + config: kubeproxyconfig.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 0 * time.Second}, + }, + expectErr: false, + }, + } + + for _, test := range testCases { + errs := validateKubeProxyIPVSConfiguration(test.config, newPath.Child("KubeProxyIPVSConfiguration")) + if len(errs) == 0 && test.expectErr { + t.Errorf("Expect error, got nil, reason: %s", test.reason) + } + if len(errs) > 0 && !test.expectErr { + t.Errorf("Unexpected error: %v", errs) + } + } +} + +func TestValidateKubeProxyConntrackConfiguration(t *testing.T) { + successCases := []kubeproxyconfig.KubeProxyConntrackConfiguration{ + { + Max: pointer.Int32Ptr(2), + MaxPerCore: pointer.Int32Ptr(1), + Min: pointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + { + Max: pointer.Int32Ptr(0), + MaxPerCore: pointer.Int32Ptr(0), + Min: pointer.Int32Ptr(0), + TCPEstablishedTimeout: &metav1.Duration{Duration: 0 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 0 * time.Second}, + }, + } + newPath := field.NewPath("KubeProxyConfiguration") + for _, successCase := range successCases { + if errs := validateKubeProxyConntrackConfiguration(successCase, newPath.Child("KubeProxyConntrackConfiguration")); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + + errorCases := []struct { + config kubeproxyconfig.KubeProxyConntrackConfiguration + msg string + }{ + { + config: kubeproxyconfig.KubeProxyConntrackConfiguration{ + Max: pointer.Int32Ptr(-1), + MaxPerCore: pointer.Int32Ptr(1), + Min: pointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + msg: "must be greater than or equal to 0", + }, + { + config: kubeproxyconfig.KubeProxyConntrackConfiguration{ + Max: pointer.Int32Ptr(2), + MaxPerCore: pointer.Int32Ptr(-1), + Min: pointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + msg: "must be greater than or equal to 0", + }, + { + config: kubeproxyconfig.KubeProxyConntrackConfiguration{ + Max: pointer.Int32Ptr(2), + MaxPerCore: pointer.Int32Ptr(1), + Min: pointer.Int32Ptr(-1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + msg: "must be greater than or equal to 0", + }, + { + config: kubeproxyconfig.KubeProxyConntrackConfiguration{ + Max: pointer.Int32Ptr(4), + MaxPerCore: pointer.Int32Ptr(1), + Min: pointer.Int32Ptr(3), + TCPEstablishedTimeout: &metav1.Duration{Duration: -5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + msg: "must be greater than or equal to 0", + }, + { + config: kubeproxyconfig.KubeProxyConntrackConfiguration{ + Max: pointer.Int32Ptr(4), + MaxPerCore: pointer.Int32Ptr(1), + Min: pointer.Int32Ptr(3), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: -5 * time.Second}, + }, + msg: "must be greater than or equal to 0", + }, + } + + for _, errorCase := range errorCases { + if errs := validateKubeProxyConntrackConfiguration(errorCase.config, newPath.Child("KubeProxyConntrackConfiguration")); len(errs) == 0 { + t.Errorf("expected failure for %s", errorCase.msg) + } else if !strings.Contains(errs[0].Error(), errorCase.msg) { + t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg) + } + } +} + +func TestValidateProxyMode(t *testing.T) { + newPath := field.NewPath("KubeProxyConfiguration") + successCases := []kubeproxyconfig.ProxyMode{ + kubeproxyconfig.ProxyModeUserspace, + kubeproxyconfig.ProxyMode(""), + } + + if runtime.GOOS == "windows" { + successCases = append(successCases, kubeproxyconfig.ProxyModeKernelspace) + } else { + successCases = append(successCases, kubeproxyconfig.ProxyModeIPTables, kubeproxyconfig.ProxyModeIPVS) + } + + for _, successCase := range successCases { + if errs := validateProxyMode(successCase, newPath.Child("ProxyMode")); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + + errorCases := []struct { + mode kubeproxyconfig.ProxyMode + msg string + }{ + { + mode: kubeproxyconfig.ProxyMode("non-existing"), + msg: "or blank (blank means the", + }, + } + + for _, errorCase := range errorCases { + if errs := validateProxyMode(errorCase.mode, newPath.Child("ProxyMode")); len(errs) == 0 { + t.Errorf("expected failure %s for %v", errorCase.msg, errorCase.mode) + } else if !strings.Contains(errs[0].Error(), errorCase.msg) { + t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg) + } + } +} + +func TestValidateClientConnectionConfiguration(t *testing.T) { + newPath := field.NewPath("KubeProxyConfiguration") + + successCases := []kubeproxyconfig.ClientConnectionConfiguration{ + { + Burst: 0, + }, + { + Burst: 5, + }, + } + + for _, successCase := range successCases { + if errs := validateClientConnectionConfiguration(successCase, newPath.Child("Burst")); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + + errorCases := []struct { + ccc kubeproxyconfig.ClientConnectionConfiguration + msg string + }{ + { + ccc: kubeproxyconfig.ClientConnectionConfiguration{Burst: -5}, + msg: "must be greater than or equal to 0", + }, + } + + for _, errorCase := range errorCases { + if errs := validateClientConnectionConfiguration(errorCase.ccc, newPath.Child("Burst")); len(errs) == 0 { + t.Errorf("expected failure for %s", errorCase.msg) + } else if !strings.Contains(errs[0].Error(), errorCase.msg) { + t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg) + } + } +} + +func TestValidateHostPort(t *testing.T) { + newPath := field.NewPath("KubeProxyConfiguration") + + successCases := []string{ + "0.0.0.0:10256", + "127.0.0.1:10256", + "10.10.10.10:10256", + } + + for _, successCase := range successCases { + if errs := validateHostPort(successCase, newPath.Child("HealthzBindAddress")); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + + errorCases := []struct { + ccc string + msg string + }{ + { + ccc: "10.10.10.10", + msg: "must be IP:port", + }, + { + ccc: "123.456.789.10:12345", + msg: "must be a valid IP", + }, + { + ccc: "10.10.10.10:foo", + msg: "must be a valid port", + }, + { + ccc: "10.10.10.10:0", + msg: "must be a valid port", + }, + { + ccc: "10.10.10.10:65536", + msg: "must be a valid port", + }, + } + + for _, errorCase := range errorCases { + if errs := validateHostPort(errorCase.ccc, newPath.Child("HealthzBindAddress")); len(errs) == 0 { + t.Errorf("expected failure for %s", errorCase.msg) + } else if !strings.Contains(errs[0].Error(), errorCase.msg) { + t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg) + } + } +} + +func TestValidateIPVSSchedulerMethod(t *testing.T) { + newPath := field.NewPath("KubeProxyConfiguration") + + successCases := []kubeproxyconfig.IPVSSchedulerMethod{ + kubeproxyconfig.RoundRobin, + kubeproxyconfig.WeightedRoundRobin, + kubeproxyconfig.LeastConnection, + kubeproxyconfig.WeightedLeastConnection, + kubeproxyconfig.LocalityBasedLeastConnection, + kubeproxyconfig.LocalityBasedLeastConnectionWithReplication, + kubeproxyconfig.SourceHashing, + kubeproxyconfig.DestinationHashing, + kubeproxyconfig.ShortestExpectedDelay, + kubeproxyconfig.NeverQueue, + "", + } + + for _, successCase := range successCases { + if errs := validateIPVSSchedulerMethod(successCase, newPath.Child("Scheduler")); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + + errorCases := []struct { + mode kubeproxyconfig.IPVSSchedulerMethod + msg string + }{ + { + mode: kubeproxyconfig.IPVSSchedulerMethod("non-existing"), + msg: "blank means the default algorithm method (currently rr)", + }, + } + + for _, errorCase := range errorCases { + if errs := validateIPVSSchedulerMethod(errorCase.mode, newPath.Child("ProxyMode")); len(errs) == 0 { + t.Errorf("expected failure for %s", errorCase.msg) + } else if !strings.Contains(errs[0].Error(), errorCase.msg) { + t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg) + } + } +} diff --git a/pkg/proxy/apis/kubeproxyconfig/zz_generated.deepcopy.go b/pkg/proxy/apis/kubeproxyconfig/zz_generated.deepcopy.go new file mode 100644 index 00000000000..989455bcd8e --- /dev/null +++ b/pkg/proxy/apis/kubeproxyconfig/zz_generated.deepcopy.go @@ -0,0 +1,189 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +package kubeproxyconfig + +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 *ClientConnectionConfiguration) DeepCopyInto(out *ClientConnectionConfiguration) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientConnectionConfiguration. +func (in *ClientConnectionConfiguration) DeepCopy() *ClientConnectionConfiguration { + if in == nil { + return nil + } + out := new(ClientConnectionConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeProxyConfiguration) DeepCopyInto(out *KubeProxyConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ClientConnection = in.ClientConnection + in.IPTables.DeepCopyInto(&out.IPTables) + out.IPVS = in.IPVS + if in.OOMScoreAdj != nil { + in, out := &in.OOMScoreAdj, &out.OOMScoreAdj + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + out.UDPIdleTimeout = in.UDPIdleTimeout + in.Conntrack.DeepCopyInto(&out.Conntrack) + out.ConfigSyncPeriod = in.ConfigSyncPeriod + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeProxyConfiguration. +func (in *KubeProxyConfiguration) DeepCopy() *KubeProxyConfiguration { + if in == nil { + return nil + } + out := new(KubeProxyConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *KubeProxyConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeProxyConntrackConfiguration) DeepCopyInto(out *KubeProxyConntrackConfiguration) { + *out = *in + if in.Max != nil { + in, out := &in.Max, &out.Max + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + if in.MaxPerCore != nil { + in, out := &in.MaxPerCore, &out.MaxPerCore + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + if in.Min != nil { + in, out := &in.Min, &out.Min + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + if in.TCPEstablishedTimeout != nil { + in, out := &in.TCPEstablishedTimeout, &out.TCPEstablishedTimeout + if *in == nil { + *out = nil + } else { + *out = new(v1.Duration) + **out = **in + } + } + if in.TCPCloseWaitTimeout != nil { + in, out := &in.TCPCloseWaitTimeout, &out.TCPCloseWaitTimeout + if *in == nil { + *out = nil + } else { + *out = new(v1.Duration) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeProxyConntrackConfiguration. +func (in *KubeProxyConntrackConfiguration) DeepCopy() *KubeProxyConntrackConfiguration { + if in == nil { + return nil + } + out := new(KubeProxyConntrackConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeProxyIPTablesConfiguration) DeepCopyInto(out *KubeProxyIPTablesConfiguration) { + *out = *in + if in.MasqueradeBit != nil { + in, out := &in.MasqueradeBit, &out.MasqueradeBit + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + out.SyncPeriod = in.SyncPeriod + out.MinSyncPeriod = in.MinSyncPeriod + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeProxyIPTablesConfiguration. +func (in *KubeProxyIPTablesConfiguration) DeepCopy() *KubeProxyIPTablesConfiguration { + if in == nil { + return nil + } + out := new(KubeProxyIPTablesConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeProxyIPVSConfiguration) DeepCopyInto(out *KubeProxyIPVSConfiguration) { + *out = *in + out.SyncPeriod = in.SyncPeriod + out.MinSyncPeriod = in.MinSyncPeriod + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeProxyIPVSConfiguration. +func (in *KubeProxyIPVSConfiguration) DeepCopy() *KubeProxyIPVSConfiguration { + if in == nil { + return nil + } + out := new(KubeProxyIPVSConfiguration) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/proxy/config/BUILD b/pkg/proxy/config/BUILD index 48ab0cb49f3..4585bccddad 100644 --- a/pkg/proxy/config/BUILD +++ b/pkg/proxy/config/BUILD @@ -14,7 +14,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/proxy/config", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/informers/informers_generated/internalversion/core/internalversion:go_default_library", "//pkg/client/listers/core/internalversion:go_default_library", "//pkg/controller:go_default_library", @@ -30,10 +30,10 @@ go_test( "api_test.go", "config_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/proxy/config", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/proxy/config/api_test.go b/pkg/proxy/config/api_test.go index afc42817d65..234129b18ff 100644 --- a/pkg/proxy/config/api_test.go +++ b/pkg/proxy/config/api_test.go @@ -26,7 +26,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/watch" ktesting "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" ) diff --git a/pkg/proxy/config/config.go b/pkg/proxy/config/config.go index 1c79fdf6f24..33e0bd169ef 100644 --- a/pkg/proxy/config/config.go +++ b/pkg/proxy/config/config.go @@ -23,7 +23,7 @@ import ( "github.com/golang/glog" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/tools/cache" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" coreinformers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/core/internalversion" listers "k8s.io/kubernetes/pkg/client/listers/core/internalversion" "k8s.io/kubernetes/pkg/controller" diff --git a/pkg/proxy/config/config_test.go b/pkg/proxy/config/config_test.go index c64072d2345..e6a3a6cb53a 100644 --- a/pkg/proxy/config/config_test.go +++ b/pkg/proxy/config/config_test.go @@ -28,7 +28,7 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/watch" ktesting "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" ) diff --git a/pkg/proxy/healthcheck/BUILD b/pkg/proxy/healthcheck/BUILD index d195692a091..badc9fdb7fd 100644 --- a/pkg/proxy/healthcheck/BUILD +++ b/pkg/proxy/healthcheck/BUILD @@ -14,7 +14,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/proxy/healthcheck", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/renstrom/dedent:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", @@ -28,8 +28,8 @@ go_library( go_test( name = "go_default_test", srcs = ["healthcheck_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/proxy/healthcheck", - library = ":go_default_library", deps = [ "//vendor/github.com/davecgh/go-spew/spew:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", diff --git a/pkg/proxy/healthcheck/healthcheck.go b/pkg/proxy/healthcheck/healthcheck.go index 7ea31d0a4ed..7ee1da6ff4e 100644 --- a/pkg/proxy/healthcheck/healthcheck.go +++ b/pkg/proxy/healthcheck/healthcheck.go @@ -33,7 +33,7 @@ import ( "k8s.io/apimachinery/pkg/util/clock" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/tools/record" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) var nodeHealthzRetryInterval = 60 * time.Second diff --git a/pkg/proxy/iptables/BUILD b/pkg/proxy/iptables/BUILD index ed39d5e5ce4..64e1dc78a5c 100644 --- a/pkg/proxy/iptables/BUILD +++ b/pkg/proxy/iptables/BUILD @@ -9,24 +9,23 @@ load( go_library( name = "go_default_library", srcs = [ - "metrics.go", "proxier.go", ], importpath = "k8s.io/kubernetes/pkg/proxy/iptables", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", "//pkg/api/service:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", "//pkg/features:go_default_library", "//pkg/proxy:go_default_library", "//pkg/proxy/healthcheck:go_default_library", + "//pkg/proxy/metrics:go_default_library", "//pkg/proxy/util:go_default_library", "//pkg/util/async:go_default_library", "//pkg/util/iptables:go_default_library", "//pkg/util/sysctl:go_default_library", "//pkg/util/version:go_default_library", "//vendor/github.com/golang/glog:go_default_library", - "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", @@ -40,16 +39,17 @@ go_library( go_test( name = "go_default_test", srcs = ["proxier_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/proxy/iptables", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/proxy:go_default_library", "//pkg/proxy/util:go_default_library", "//pkg/util/async:go_default_library", "//pkg/util/iptables:go_default_library", "//pkg/util/iptables/testing:go_default_library", "//vendor/github.com/davecgh/go-spew/spew:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", diff --git a/pkg/proxy/iptables/metrics.go b/pkg/proxy/iptables/metrics.go deleted file mode 100644 index fabe6a59569..00000000000 --- a/pkg/proxy/iptables/metrics.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 iptables - -import ( - "sync" - "time" - - "github.com/prometheus/client_golang/prometheus" -) - -const kubeProxySubsystem = "kubeproxy" - -var ( - SyncProxyRulesLatency = prometheus.NewHistogram( - prometheus.HistogramOpts{ - Subsystem: kubeProxySubsystem, - Name: "sync_proxy_rules_latency_microseconds", - Help: "SyncProxyRules latency", - Buckets: prometheus.ExponentialBuckets(1000, 2, 15), - }, - ) -) - -var registerMetricsOnce sync.Once - -func RegisterMetrics() { - registerMetricsOnce.Do(func() { - prometheus.MustRegister(SyncProxyRulesLatency) - }) -} - -// Gets the time since the specified start in microseconds. -func sinceInMicroseconds(start time.Time) float64 { - return float64(time.Since(start).Nanoseconds() / time.Microsecond.Nanoseconds()) -} diff --git a/pkg/proxy/iptables/proxier.go b/pkg/proxy/iptables/proxier.go index 23920a55cb4..1710d989f97 100644 --- a/pkg/proxy/iptables/proxier.go +++ b/pkg/proxy/iptables/proxier.go @@ -41,12 +41,13 @@ import ( "k8s.io/apimachinery/pkg/util/wait" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/tools/record" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper" apiservice "k8s.io/kubernetes/pkg/api/service" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/proxy" "k8s.io/kubernetes/pkg/proxy/healthcheck" + "k8s.io/kubernetes/pkg/proxy/metrics" utilproxy "k8s.io/kubernetes/pkg/proxy/util" "k8s.io/kubernetes/pkg/util/async" utiliptables "k8s.io/kubernetes/pkg/util/iptables" @@ -79,6 +80,9 @@ const ( // the mark-for-drop chain KubeMarkDropChain utiliptables.Chain = "KUBE-MARK-DROP" + + // the kubernetes forward chain + kubeForwardChain utiliptables.Chain = "KUBE-FORWARD" ) // IPTablesVersioner can query the current iptables version. @@ -439,11 +443,6 @@ func NewProxier(ipt utiliptables.Interface, recorder record.EventRecorder, healthzServer healthcheck.HealthzUpdater, ) (*Proxier, error) { - // check valid user input - if minSyncPeriod > syncPeriod { - return nil, fmt.Errorf("minSyncPeriod (%v) must be <= syncPeriod (%v)", minSyncPeriod, syncPeriod) - } - // Set the route_localnet sysctl we need for if err := sysctl.SetSysctl(sysctlRouteLocalnet, 1); err != nil { return nil, fmt.Errorf("can't set sysctl %s: %v", sysctlRouteLocalnet, err) @@ -457,9 +456,6 @@ func NewProxier(ipt utiliptables.Interface, } // Generate the masquerade mark to use for SNAT rules. - if masqueradeBit < 0 || masqueradeBit > 31 { - return nil, fmt.Errorf("invalid iptables-masquerade-bit %v not in [0, 31]", masqueradeBit) - } masqueradeValue := 1 << uint(masqueradeBit) masqueradeMark := fmt.Sprintf("%#08x/%#08x", masqueradeValue, masqueradeValue) @@ -542,6 +538,18 @@ func CleanupLeftovers(ipt utiliptables.Interface) (encounteredError bool) { } } + // Unlink the forwarding chain. + args = []string{ + "-m", "comment", "--comment", "kubernetes forwarding rules", + "-j", string(kubeForwardChain), + } + if err := ipt.DeleteRule(utiliptables.TableFilter, utiliptables.ChainForward, args...); err != nil { + if !utiliptables.IsNotFoundError(err) { + glog.Errorf("Error removing pure-iptables proxy rule: %v", err) + encounteredError = true + } + } + // Flush and remove all of our chains. iptablesData := bytes.NewBuffer(nil) if err := ipt.SaveInto(utiliptables.TableNAT, iptablesData); err != nil { @@ -577,14 +585,28 @@ func CleanupLeftovers(ipt utiliptables.Interface) (encounteredError bool) { encounteredError = true } } - { - filterBuf := bytes.NewBuffer(nil) - writeLine(filterBuf, "*filter") - writeLine(filterBuf, fmt.Sprintf(":%s - [0:0]", kubeServicesChain)) - writeLine(filterBuf, fmt.Sprintf("-X %s", kubeServicesChain)) - writeLine(filterBuf, "COMMIT") + + // Flush and remove all of our chains. + iptablesData = bytes.NewBuffer(nil) + if err := ipt.SaveInto(utiliptables.TableFilter, iptablesData); err != nil { + glog.Errorf("Failed to execute iptables-save for %s: %v", utiliptables.TableFilter, err) + encounteredError = true + } else { + existingFilterChains := utiliptables.GetChainLines(utiliptables.TableFilter, iptablesData.Bytes()) + filterChains := bytes.NewBuffer(nil) + filterRules := bytes.NewBuffer(nil) + writeLine(filterChains, "*filter") + for _, chain := range []utiliptables.Chain{kubeServicesChain, kubeForwardChain} { + if _, found := existingFilterChains[chain]; found { + chainString := string(chain) + writeLine(filterChains, existingFilterChains[chain]) + writeLine(filterRules, "-X", chainString) + } + } + writeLine(filterRules, "COMMIT") + filterLines := append(filterChains.Bytes(), filterRules.Bytes()...) // Write it. - if err := ipt.Restore(utiliptables.TableFilter, filterBuf.Bytes(), utiliptables.NoFlushTables, utiliptables.RestoreCounters); err != nil { + if err := ipt.Restore(utiliptables.TableFilter, filterLines, utiliptables.NoFlushTables, utiliptables.RestoreCounters); err != nil { glog.Errorf("Failed to execute iptables-restore for %s: %v", utiliptables.TableFilter, err) encounteredError = true } @@ -797,7 +819,7 @@ func getLocalIPs(endpointsMap proxyEndpointsMap) map[types.NamespacedName]sets.S for svcPortName := range endpointsMap { for _, ep := range endpointsMap[svcPortName] { if ep.isLocal { - // If the endpoint has a bad format, ipPart() will log an + // If the endpoint has a bad format, utilproxy.IPPart() will log an // error and ep.IPPart() will return a null string. if ip := ep.IPPart(); ip != "" { nsn := svcPortName.NamespacedName @@ -954,7 +976,7 @@ func (proxier *Proxier) syncProxyRules() { start := time.Now() defer func() { - SyncProxyRulesLatency.Observe(sinceInMicroseconds(start)) + metrics.SyncProxyRulesLatency.Observe(metrics.SinceInMicroseconds(start)) glog.V(4).Infof("syncProxyRules took %v", time.Since(start)) }() // don't sync rules till we've received services and endpoints @@ -1026,6 +1048,21 @@ func (proxier *Proxier) syncProxyRules() { } } + // Create and link the kube forward chain. + { + if _, err := proxier.iptables.EnsureChain(utiliptables.TableFilter, kubeForwardChain); err != nil { + glog.Errorf("Failed to ensure that %s chain %s exists: %v", utiliptables.TableFilter, kubeForwardChain, err) + return + } + + comment := "kubernetes forward rules" + args := []string{"-m", "comment", "--comment", comment, "-j", string(kubeForwardChain)} + if _, err := proxier.iptables.EnsureRule(utiliptables.Prepend, utiliptables.TableFilter, utiliptables.ChainForward, args...); err != nil { + glog.Errorf("Failed to ensure that %s chain %s jumps to %s: %v", utiliptables.TableFilter, utiliptables.ChainForward, kubeForwardChain, err) + return + } + } + // // Below this point we will not return until we try to write the iptables rules. // @@ -1068,6 +1105,11 @@ func (proxier *Proxier) syncProxyRules() { } else { writeLine(proxier.filterChains, utiliptables.MakeChainLine(kubeServicesChain)) } + if chain, ok := existingFilterChains[kubeForwardChain]; ok { + writeLine(proxier.filterChains, chain) + } else { + writeLine(proxier.filterChains, utiliptables.MakeChainLine(kubeForwardChain)) + } if chain, ok := existingNATChains[kubeServicesChain]; ok { writeLine(proxier.natChains, chain) } else { @@ -1342,7 +1384,7 @@ func (proxier *Proxier) syncProxyRules() { // This is very low impact. The NodePort range is intentionally obscure, and unlikely to actually collide with real Services. // This only affects UDP connections, which are not common. // See issue: https://github.com/kubernetes/kubernetes/issues/49881 - isIPv6 := svcInfo.clusterIP.To4() != nil + isIPv6 := utilproxy.IsIPv6(svcInfo.clusterIP) err := utilproxy.ClearUDPConntrackForPort(proxier.exec, lp.Port, isIPv6) if err != nil { glog.Errorf("Failed to clear udp conntrack for port %d, error: %v", lp.Port, err) @@ -1515,6 +1557,18 @@ func (proxier *Proxier) syncProxyRules() { ) writeLine(proxier.natRules, args...) } else { + // First write session affinity rules only over local endpoints, if applicable. + if svcInfo.sessionAffinityType == api.ServiceAffinityClientIP { + for _, endpointChain := range localEndpointChains { + writeLine(proxier.natRules, + "-A", string(svcXlbChain), + "-m", "comment", "--comment", svcNameString, + "-m", "recent", "--name", string(endpointChain), + "--rcheck", "--seconds", strconv.Itoa(svcInfo.stickyMaxAgeSeconds), "--reap", + "-j", string(endpointChain)) + } + } + // Setup probability filter rules only over local endpoints for i, endpointChain := range localEndpointChains { // Balancing rules in the per-service chain. @@ -1561,6 +1615,40 @@ func (proxier *Proxier) syncProxyRules() { "-m", "addrtype", "--dst-type", "LOCAL", "-j", string(kubeNodePortsChain)) + // If the masqueradeMark has been added then we want to forward that same + // traffic, this allows NodePort traffic to be forwarded even if the default + // FORWARD policy is not accept. + writeLine(proxier.filterRules, + "-A", string(kubeForwardChain), + "-m", "comment", "--comment", `"kubernetes forwarding rules"`, + "-m", "mark", "--mark", proxier.masqueradeMark, + "-j", "ACCEPT", + ) + + // The following rules can only be set if clusterCIDR has been defined. + if len(proxier.clusterCIDR) != 0 { + // The following two rules ensure the traffic after the initial packet + // accepted by the "kubernetes forwarding rules" rule above will be + // accepted, to be as specific as possible the traffic must be sourced + // or destined to the clusterCIDR (to/from a pod). + writeLine(proxier.filterRules, + "-A", string(kubeForwardChain), + "-s", proxier.clusterCIDR, + "-m", "comment", "--comment", `"kubernetes forwarding conntrack pod source rule"`, + "-m", "conntrack", + "--ctstate", "RELATED,ESTABLISHED", + "-j", "ACCEPT", + ) + writeLine(proxier.filterRules, + "-A", string(kubeForwardChain), + "-m", "comment", "--comment", `"kubernetes forwarding conntrack pod destination rule"`, + "-d", proxier.clusterCIDR, + "-m", "conntrack", + "--ctstate", "RELATED,ESTABLISHED", + "-j", "ACCEPT", + ) + } + // Write the end-of-table markers. writeLine(proxier.filterRules, "COMMIT") writeLine(proxier.natRules, "COMMIT") @@ -1577,20 +1665,6 @@ func (proxier *Proxier) syncProxyRules() { err = proxier.iptables.RestoreAll(proxier.iptablesData.Bytes(), utiliptables.NoFlushTables, utiliptables.RestoreCounters) if err != nil { glog.Errorf("Failed to execute iptables-restore: %v", err) - // ~rough approximation, assume ~100 chars per line - // we log first 1000 bytes, but full list at higher levels - rules := proxier.iptablesData.Bytes() - if len(rules) > 1000 { - abridgedRules := rules[:1000] - if glog.V(4) { - glog.V(4).Infof("Rules:\n%s", rules) - } else { - glog.V(2).Infof("Rules (abridged):\n%s", abridgedRules) - } - } else { - glog.V(2).Infof("Rules:\n%s", rules) - } - // Revert new local ports. glog.V(2).Infof("Closing local ports after iptables-restore failure") utilproxy.RevertPorts(replacementPortsMap, proxier.portsMap) @@ -1622,7 +1696,7 @@ func (proxier *Proxier) syncProxyRules() { // Finish housekeeping. // TODO: these could be made more consistent. - for _, svcIP := range staleServices.List() { + for _, svcIP := range staleServices.UnsortedList() { if err := utilproxy.ClearUDPConntrackForIP(proxier.exec, svcIP); err != nil { glog.Errorf("Failed to delete stale service IP %s connections, error: %v", svcIP, err) } diff --git a/pkg/proxy/iptables/proxier_test.go b/pkg/proxy/iptables/proxier_test.go index aba1b5d58ee..e9cb5b634de 100644 --- a/pkg/proxy/iptables/proxier_test.go +++ b/pkg/proxy/iptables/proxier_test.go @@ -24,6 +24,7 @@ import ( "time" "github.com/davecgh/go-spew/spew" + "github.com/golang/glog" "fmt" "net" @@ -33,7 +34,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/proxy" utilproxy "k8s.io/kubernetes/pkg/proxy/util" "k8s.io/kubernetes/pkg/util/async" @@ -89,7 +90,7 @@ func TestReadLinesFromByteBuffer(t *testing.T) { } func TestGetChainLines(t *testing.T) { - iptables_save := `# Generated by iptables-save v1.4.7 on Wed Oct 29 14:56:01 2014 + iptablesSave := `# Generated by iptables-save v1.4.7 on Wed Oct 29 14:56:01 2014 *nat :PREROUTING ACCEPT [2136997:197881818] :POSTROUTING ACCEPT [4284525:258542680] @@ -102,11 +103,11 @@ func TestGetChainLines(t *testing.T) { utiliptables.ChainPostrouting: ":POSTROUTING ACCEPT [4284525:258542680]", utiliptables.ChainOutput: ":OUTPUT ACCEPT [5901660:357267963]", } - checkAllLines(t, utiliptables.TableNAT, []byte(iptables_save), expected) + checkAllLines(t, utiliptables.TableNAT, []byte(iptablesSave), expected) } func TestGetChainLinesMultipleTables(t *testing.T) { - iptables_save := `# Generated by iptables-save v1.4.21 on Fri Aug 7 14:47:37 2015 + iptablesSave := `# Generated by iptables-save v1.4.21 on Fri Aug 7 14:47:37 2015 *nat :PREROUTING ACCEPT [2:138] :INPUT ACCEPT [0:0] @@ -174,7 +175,7 @@ func TestGetChainLinesMultipleTables(t *testing.T) { utiliptables.Chain("KUBE-SVC-5555555555555555"): ":KUBE-SVC-5555555555555555 - [0:0]", utiliptables.Chain("KUBE-SVC-6666666666666666"): ":KUBE-SVC-6666666666666666 - [0:0]", } - checkAllLines(t, utiliptables.TableNAT, []byte(iptables_save), expected) + checkAllLines(t, utiliptables.TableNAT, []byte(iptablesSave), expected) } func newFakeServiceInfo(service proxy.ServicePortName, ip net.IP, port int, protocol api.Protocol, onlyNodeLocalEndpoints bool) *serviceInfo { @@ -189,67 +190,153 @@ func newFakeServiceInfo(service proxy.ServicePortName, ip net.IP, port int, prot } func TestDeleteEndpointConnections(t *testing.T) { - fcmd := fakeexec.FakeCmd{ - CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ - func() ([]byte, error) { return []byte("1 flow entries have been deleted"), nil }, - func() ([]byte, error) { - return []byte(""), fmt.Errorf("conntrack v1.4.2 (conntrack-tools): 0 flow entries have been deleted.") - }, + const ( + UDP = api.ProtocolUDP + TCP = api.ProtocolTCP + ) + testCases := []struct { + description string + svcName string + svcIP string + svcPort int + protocol api.Protocol + endpoint string // IP:port endpoint + epSvcPair endpointServicePair // Will be generated by test + simulatedErr string + }{ + { + description: "V4 UDP", + svcName: "v4-udp", + svcIP: "10.96.1.1", + svcPort: 80, + protocol: UDP, + endpoint: "10.240.0.3:80", + }, { + description: "V4 TCP", + svcName: "v4-tcp", + svcIP: "10.96.2.2", + svcPort: 80, + protocol: TCP, + endpoint: "10.240.0.4:80", + }, { + description: "V4 UDP, nothing to delete, benign error", + svcName: "v4-udp-nothing-to-delete", + svcIP: "10.96.1.1", + svcPort: 80, + protocol: UDP, + endpoint: "10.240.0.3:80", + simulatedErr: utilproxy.NoConnectionToDelete, + }, { + description: "V4 UDP, unexpected error, should be glogged", + svcName: "v4-udp-simulated-error", + svcIP: "10.96.1.1", + svcPort: 80, + protocol: UDP, + endpoint: "10.240.0.3:80", + simulatedErr: "simulated error", + }, { + description: "V6 UDP", + svcName: "v6-udp", + svcIP: "fd00:1234::20", + svcPort: 80, + protocol: UDP, + endpoint: "[2001:db8::2]:80", + }, { + description: "V6 TCP", + svcName: "v6-tcp", + svcIP: "fd00:1234::30", + svcPort: 80, + protocol: TCP, + endpoint: "[2001:db8::3]:80", }, } + + // Create a service map that has service info entries for all test cases + // and generate an endpoint service pair for each test case + serviceMap := make(map[proxy.ServicePortName]*serviceInfo) + for i, tc := range testCases { + svc := proxy.ServicePortName{ + NamespacedName: types.NamespacedName{Namespace: "ns1", Name: tc.svcName}, + Port: "p80", + } + serviceMap[svc] = newFakeServiceInfo(svc, net.ParseIP(tc.svcIP), 80, tc.protocol, false) + testCases[i].epSvcPair = endpointServicePair{ + endpoint: tc.endpoint, + servicePortName: svc, + } + } + + // Create a fake executor for the conntrack utility. This should only be + // invoked for UDP connections, since no conntrack cleanup is needed for TCP + fcmd := fakeexec.FakeCmd{} fexec := fakeexec.FakeExec{ - CommandScript: []fakeexec.FakeCommandAction{ - func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, - func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, - }, LookPathFunc: func(cmd string) (string, error) { return cmd, nil }, } - - serviceMap := make(map[proxy.ServicePortName]*serviceInfo) - svc1 := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "ns1", Name: "svc1"}, Port: "p80"} - svc2 := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "ns1", Name: "svc2"}, Port: "p80"} - serviceMap[svc1] = newFakeServiceInfo(svc1, net.IPv4(10, 20, 30, 40), 80, api.ProtocolUDP, false) - serviceMap[svc2] = newFakeServiceInfo(svc1, net.IPv4(10, 20, 30, 41), 80, api.ProtocolTCP, false) - - fakeProxier := Proxier{exec: &fexec, serviceMap: serviceMap} - - testCases := []endpointServicePair{ - { - endpoint: "10.240.0.3:80", - servicePortName: svc1, - }, - { - endpoint: "10.240.0.4:80", - servicePortName: svc1, - }, - { - endpoint: "10.240.0.5:80", - servicePortName: svc2, - }, - { - endpoint: "[fd00:1::5]:8080", - servicePortName: svc2, - }, + execFunc := func(cmd string, args ...string) exec.Cmd { + return fakeexec.InitFakeCmd(&fcmd, cmd, args...) + } + for _, tc := range testCases { + if tc.protocol == UDP { + var cmdOutput string + var simErr error + if tc.simulatedErr == "" { + cmdOutput = "1 flow entries have been deleted" + } else { + simErr = fmt.Errorf(tc.simulatedErr) + } + cmdFunc := func() ([]byte, error) { return []byte(cmdOutput), simErr } + fcmd.CombinedOutputScript = append(fcmd.CombinedOutputScript, cmdFunc) + fexec.CommandScript = append(fexec.CommandScript, execFunc) + } } - expectCommandExecCount := 0 - for i := range testCases { - input := map[endpointServicePair]bool{testCases[i]: true} + // Create a proxier using the fake conntrack executor and service map + fakeProxier := Proxier{exec: &fexec, serviceMap: serviceMap} + + // Run the test cases + for _, tc := range testCases { + priorExecs := fexec.CommandCalls + priorGlogErrs := glog.Stats.Error.Lines() + + input := map[endpointServicePair]bool{tc.epSvcPair: true} fakeProxier.deleteEndpointConnections(input) - svcInfo := fakeProxier.serviceMap[testCases[i].servicePortName] - if svcInfo.protocol == api.ProtocolUDP { - svcIp := svcInfo.clusterIP.String() - endpointIp := utilproxy.IPPart(testCases[i].endpoint) - expectCommand := fmt.Sprintf("conntrack -D --orig-dst %s --dst-nat %s -p udp", svcIp, endpointIp) - execCommand := strings.Join(fcmd.CombinedOutputLog[expectCommandExecCount], " ") - if expectCommand != execCommand { - t.Errorf("Exepect comand: %s, but executed %s", expectCommand, execCommand) + + // For UDP connections, check the executed conntrack command + var expExecs int + if tc.protocol == UDP { + isIPv6 := func(ip string) bool { + netIP := net.ParseIP(ip) + if netIP.To4() == nil { + return true + } + return false } - expectCommandExecCount += 1 + endpointIP := utilproxy.IPPart(tc.endpoint) + expectCommand := fmt.Sprintf("conntrack -D --orig-dst %s --dst-nat %s -p udp", tc.svcIP, endpointIP) + if isIPv6(endpointIP) { + expectCommand += " -f ipv6" + } + actualCommand := strings.Join(fcmd.CombinedOutputLog[fexec.CommandCalls-1], " ") + if actualCommand != expectCommand { + t.Errorf("%s: Expected command: %s, but executed %s", tc.description, expectCommand, actualCommand) + } + expExecs = 1 } - if expectCommandExecCount != fexec.CommandCalls { - t.Errorf("Exepect comand executed %d times, but got %d", expectCommandExecCount, fexec.CommandCalls) + // Check the number of times conntrack was executed + execs := fexec.CommandCalls - priorExecs + if execs != expExecs { + t.Errorf("%s: Expected conntrack to be executed %d times, but got %d", tc.description, expExecs, execs) + } + + // Check the number of new glog errors + var expGlogErrs int64 + if tc.simulatedErr != "" && tc.simulatedErr != utilproxy.NoConnectionToDelete { + expGlogErrs = 1 + } + glogErrs := glog.Stats.Error.Lines() - priorGlogErrs + if glogErrs != expGlogErrs { + t.Errorf("%s: Expected %d glogged errors, but got %d", tc.description, expGlogErrs, glogErrs) } } } @@ -325,6 +412,15 @@ func NewFakeProxier(ipt utiliptables.Interface) *Proxier { return p } +func hasSessionAffinityRule(rules []iptablestest.Rule) bool { + for _, r := range rules { + if _, ok := r[iptablestest.Recent]; ok { + return true + } + } + return false +} + func hasJump(rules []iptablestest.Rule, destChain, destIP string, destPort int) bool { destPortStr := strconv.Itoa(destPort) match := false @@ -769,6 +865,7 @@ func TestOnlyLocalLoadBalancing(t *testing.T) { NamespacedName: makeNSN("ns1", "svc1"), Port: "p80", } + svcSessionAffinityTimeout := int32(10800) makeServiceMap(fp, makeTestService(svcPortName.Namespace, svcPortName.Name, func(svc *api.Service) { @@ -784,6 +881,10 @@ func TestOnlyLocalLoadBalancing(t *testing.T) { IP: svcLBIP, }} svc.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyTypeLocal + svc.Spec.SessionAffinity = api.ServiceAffinityClientIP + svc.Spec.SessionAffinityConfig = &api.SessionAffinityConfig{ + ClientIP: &api.ClientIPConfig{TimeoutSeconds: &svcSessionAffinityTimeout}, + } }), ) @@ -838,6 +939,9 @@ func TestOnlyLocalLoadBalancing(t *testing.T) { if !hasJump(lbRules, localEpChain, "", 0) { errorf(fmt.Sprintf("Didn't find jump from lb chain %v to local ep %v", lbChain, epStrNonLocal), lbRules, t) } + if !hasSessionAffinityRule(lbRules) { + errorf(fmt.Sprintf("Didn't find session affinity rule from lb chain %v", lbChain), lbRules, t) + } } func TestOnlyLocalNodePortsNoClusterCIDR(t *testing.T) { @@ -1050,7 +1154,7 @@ func TestBuildServiceMapAddRemove(t *testing.T) { // the not-deleted service, because one of it's ServicePorts was deleted. expectedStaleUDPServices := []string{"172.16.55.10", "172.16.55.4", "172.16.55.11", "172.16.55.12"} if len(result.staleServices) != len(expectedStaleUDPServices) { - t.Errorf("expected stale UDP services length %d, got %v", len(expectedStaleUDPServices), result.staleServices.List()) + t.Errorf("expected stale UDP services length %d, got %v", len(expectedStaleUDPServices), result.staleServices.UnsortedList()) } for _, ip := range expectedStaleUDPServices { if !result.staleServices.Has(ip) { @@ -1166,7 +1270,7 @@ func TestBuildServiceMapServiceUpdate(t *testing.T) { t.Errorf("expected healthcheck ports length 1, got %v", result.hcServices) } if len(result.staleServices) != 0 { - t.Errorf("expected stale UDP services length 0, got %v", result.staleServices.List()) + t.Errorf("expected stale UDP services length 0, got %v", result.staleServices.UnsortedList()) } // No change; make sure the service map stays the same and there are @@ -1180,7 +1284,7 @@ func TestBuildServiceMapServiceUpdate(t *testing.T) { t.Errorf("expected healthcheck ports length 1, got %v", result.hcServices) } if len(result.staleServices) != 0 { - t.Errorf("expected stale UDP services length 0, got %v", result.staleServices.List()) + t.Errorf("expected stale UDP services length 0, got %v", result.staleServices.UnsortedList()) } // And back to ClusterIP @@ -1264,6 +1368,14 @@ func Test_getLocalIPs(t *testing.T) { {Namespace: "ns2", Name: "ep2"}: sets.NewString("2.2.2.2", "2.2.2.22", "2.2.2.3"), {Namespace: "ns4", Name: "ep4"}: sets.NewString("4.4.4.4", "4.4.4.6"), }, + }, { + // Case[5]: named port local and bad endpoints IP + endpointsMap: map[proxy.ServicePortName][]*endpointsInfo{ + makeServicePortName("ns1", "ep1", "p11"): { + {endpoint: "bad ip:11", isLocal: true}, + }, + }, + expected: map[types.NamespacedName]sets.String{}, }} for tci, tc := range testCases { diff --git a/pkg/proxy/ipvs/BUILD b/pkg/proxy/ipvs/BUILD index 18c63568d59..01b3fd4d6bc 100644 --- a/pkg/proxy/ipvs/BUILD +++ b/pkg/proxy/ipvs/BUILD @@ -8,14 +8,19 @@ load( go_test( name = "go_default_test", - srcs = ["proxier_test.go"], + srcs = [ + "ipset_test.go", + "proxier_test.go", + ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/proxy/ipvs", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/proxy:go_default_library", "//pkg/proxy/ipvs/testing:go_default_library", "//pkg/proxy/util:go_default_library", + "//pkg/util/ipset:go_default_library", + "//pkg/util/ipset/testing:go_default_library", "//pkg/util/iptables:go_default_library", "//pkg/util/iptables/testing:go_default_library", "//pkg/util/ipvs:go_default_library", @@ -33,40 +38,73 @@ go_test( go_library( name = "go_default_library", srcs = [ + "ipset.go", "netlink.go", - "netlink_unsupported.go", "proxier.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "netlink_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "netlink_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "netlink_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "netlink_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ "netlink_linux.go", ], + "@io_bazel_rules_go//go/platform:nacl": [ + "netlink_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "netlink_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "netlink_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "netlink_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "netlink_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "netlink_unsupported.go", + ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/pkg/proxy/ipvs", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", "//pkg/api/service:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", "//pkg/features:go_default_library", "//pkg/proxy:go_default_library", "//pkg/proxy/healthcheck:go_default_library", + "//pkg/proxy/metrics:go_default_library", "//pkg/proxy/util:go_default_library", "//pkg/util/async:go_default_library", + "//pkg/util/ipset:go_default_library", "//pkg/util/iptables:go_default_library", "//pkg/util/ipvs:go_default_library", "//pkg/util/sysctl:go_default_library", + "//pkg/util/version:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", "//vendor/k8s.io/client-go/tools/record:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//vendor/github.com/vishvananda/netlink:go_default_library", + "//vendor/golang.org/x/sys/unix:go_default_library", ], "//conditions:default": [], }), diff --git a/pkg/proxy/ipvs/README.md b/pkg/proxy/ipvs/README.md index b6f769d25bb..52297b7c90b 100644 --- a/pkg/proxy/ipvs/README.md +++ b/pkg/proxy/ipvs/README.md @@ -2,54 +2,53 @@ This document shows how to use kube-proxy ipvs mode. -### What is IPVS +## What is IPVS **IPVS (IP Virtual Server)** implements transport-layer load balancing, usually called Layer 4 LAN switching, as part of Linux kernel. IPVS runs on a host and acts as a load balancer in front of a cluster of real servers. IPVS can direct requests for TCP -and UDP-based services to the real servers, and make services of real servers appear as irtual services on a single IP address. +and UDP-based services to the real servers, and make services of real servers appear as virtual services on a single IP address. -### How to use +## Run kube-proxy in ipvs mode -##### Load IPVS kernel modules +Currently, local-up scripts and kubeadm support switching IPVS proxy mode via exporting environment variables or specifying flags. -Currently the IPVS kernel module can't be loaded automatically, so first we should use the following command to load IPVS kernel -modules manually. +### Local UP Cluster -```shell -modprobe ip_vs -modprobe ip_vs_rr -modprobe ip_vs_wrr -modprobe ip_vs_sh -modprobe nf_conntrack_ipv4 +Kube-proxy will run in iptables mode by default in a [local-up cluster](https://github.com/kubernetes/community/blob/master/contributors/devel/running-locally.md). + +Users should export the env `KUBEPROXY_MODE=ipvs` to specify the ipvs mode before deploying the cluster if want to run kube-proxy in ipvs mode. + +### Cluster Created by Kubeadm + +Kube-proxy will run in iptables mode by default in a cluster deployed by [kubeadm](https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/). + +Since IPVS mode is still feature-gated, users should add the flag `--feature-gates=SupportIPVSProxyMode=true` in `kubeadm init` command + +``` +kubeadm init --feature-gates=SupportIPVSProxyMode=true ``` -After that, use `lsmod | grep ip_vs` to make sure kernel modules are loaded. +to specify the ipvs mode before deploying the cluster if want to run kube-proxy in ipvs mode. -##### Run kube-proxy in ipvs mode +If you are using kubeadm with a configuration file, you can specify the ipvs mode adding `SupportIPVSProxyMode: true` below the `featureGates` field. +Then the configuration file is similar to: -First, [run cluster locally](https://github.com/kubernetes/community/blob/master/contributors/devel/running-locally.md). - -By default kube-proxy will run in iptables mode, with configuration file `/tmp/kube-proxy.yaml`. so we need to change the -configuration file and restart it. Here is a yaml file for reference. - -```yaml -apiVersion: componentconfig/v1alpha1 -kind: KubeProxyConfiguration -clientConnection: - kubeconfig: /var/run/kubernetes/kube-proxy.kubeconfig -hostnameOverride: 127.0.0.1 +```json +kind: MasterConfiguration +apiVersion: kubeadm.k8s.io/v1alpha1 +... +featureGates: + SupportIPVSProxyMode: true mode: ipvs -featureGates: AllAlpha=true -ipvs: - minSyncPeriod: 10s - syncPeriod: 60s ``` -##### Test +## Debug -Use `ipvsadm` tool to test whether the kube-proxy start succeed. By default we may get result like: +### Check IPVS proxy rules + +People can use `ipvsadm` tool to check whether kube-proxy are maintaining IPVS rules correctly. For example, we may get IPVS proxy rules like: ```shell # ipvsadm -ln @@ -62,3 +61,28 @@ TCP 10.0.0.10:53 rr UDP 10.0.0.10:53 rr ``` +### Why kube-proxy can't start IPVS mode + +People can do the following check list step by step: + +**1. Enable IPVS feature gateway** + +Currently IPVS-based kube-proxy is still in alpha phase, people need to enable `--feature-gates=SupportIPVSProxyMode=true` explicitly. + +**2. Specify proxy-mode=ipvs** + +Tell kube-proxy that proxy-mode=ipvs, please. + +**3. Load ipvs required kernel modules** + +The following kernel modules are required by IPVS-based kube-proxy: + +```shell +ip_vs +ip_vs_rr +ip_vs_wrr +ip_vs_sh +nf_conntrack_ipv4 +``` + +IPVS-based kube-proxy will load them automatically. If it fails to load them, please check whether they are compiled into your kernel. diff --git a/pkg/proxy/ipvs/ipset.go b/pkg/proxy/ipvs/ipset.go new file mode 100644 index 00000000000..d61992125ae --- /dev/null +++ b/pkg/proxy/ipvs/ipset.go @@ -0,0 +1,159 @@ +/* +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 ( + "k8s.io/apimachinery/pkg/util/sets" + utilipset "k8s.io/kubernetes/pkg/util/ipset" + utilversion "k8s.io/kubernetes/pkg/util/version" + + "github.com/golang/glog" +) + +const ( + // MinIPSetCheckVersion is the min ipset version we need. IPv6 is supported in ipset 6.x + MinIPSetCheckVersion = "6.0" + + // KubeLoopBackIPSet is used to store endpoints dst ip:port, source ip for solving hairpin purpose. + KubeLoopBackIPSet = "KUBE-LOOP-BACK" + + // KubeClusterIPSet is used to store service cluster ip + port for masquerade purpose. + KubeClusterIPSet = "KUBE-CLUSTER-IP" + + // KubeExternalIPSet is used to store service external ip + port for masquerade and filter purpose. + KubeExternalIPSet = "KUBE-EXTERNAL-IP" + + // KubeLoadBalancerSet is used to store service load balancer ingress ip + port, it is the service lb portal. + KubeLoadBalancerSet = "KUBE-LOAD-BALANCER" + + // KubeLoadBalancerMasqSet is used to store service load balancer ingress ip + port for masquerade purpose. + KubeLoadBalancerMasqSet = "KUBE-LOAD-BALANCER-MASQ" + + // KubeLoadBalancerSourceIPSet is used to store service load balancer ingress ip + port + source IP for packet filter purpose. + KubeLoadBalancerSourceIPSet = "KUBE-LOAD-BALANCER-SOURCE-IP" + + // KubeLoadBalancerSourceCIDRSet is used to store service load balancer ingress ip + port + source cidr for packet filter purpose. + KubeLoadBalancerSourceCIDRSet = "KUBE-LOAD-BALANCER-SOURCE-CIDR" + + // KubeNodePortSetTCP is used to store nodeport TCP port for masquerade purpose. + KubeNodePortSetTCP = "KUBE-NODE-PORT-TCP" + + // KubeNodePortSetUDP is used to store nodeport UDP port for masquerade purpose. + KubeNodePortSetUDP = "KUBE-NODE-PORT-UDP" +) + +// IPSetVersioner can query the current ipset version. +type IPSetVersioner interface { + // returns "X.Y" + GetVersion() (string, error) +} + +// IPSet wraps util/ipset which is used by IPVS proxier. +type IPSet struct { + utilipset.IPSet + // activeEntries is the current active entries of the ipset. + activeEntries sets.String + // handle is the util ipset interface handle. + handle utilipset.Interface +} + +// NewIPSet initialize a new IPSet struct +func NewIPSet(handle utilipset.Interface, name string, setType utilipset.Type, isIPv6 bool) *IPSet { + hashFamily := utilipset.ProtocolFamilyIPV4 + if isIPv6 { + hashFamily = utilipset.ProtocolFamilyIPV6 + } + set := &IPSet{ + IPSet: utilipset.IPSet{ + Name: name, + SetType: setType, + HashFamily: hashFamily, + }, + activeEntries: sets.NewString(), + handle: handle, + } + return set +} + +func (set *IPSet) isEmpty() bool { + return len(set.activeEntries.UnsortedList()) == 0 +} + +func (set *IPSet) resetEntries() { + set.activeEntries = sets.NewString() +} + +func (set *IPSet) syncIPSetEntries() { + appliedEntries, err := set.handle.ListEntries(set.Name) + if err != nil { + glog.Errorf("Failed to list ip set entries, error: %v", err) + return + } + + // currentIPSetEntries represents Endpoints watched from API Server. + currentIPSetEntries := sets.NewString() + for _, appliedEntry := range appliedEntries { + currentIPSetEntries.Insert(appliedEntry) + } + + if !set.activeEntries.Equal(currentIPSetEntries) { + // Clean legacy entries + for _, entry := range currentIPSetEntries.Difference(set.activeEntries).List() { + if err := set.handle.DelEntry(entry, set.Name); err != nil { + if !utilipset.IsNotFoundError(err) { + glog.Errorf("Failed to delete ip set entry: %s from ip set: %s, error: %v", entry, set.Name, err) + } + } else { + glog.V(3).Infof("Successfully delete legacy ip set entry: %s from ip set: %s", entry, set.Name) + } + } + // Create active entries + for _, entry := range set.activeEntries.Difference(currentIPSetEntries).List() { + if err := set.handle.AddEntry(entry, set.Name, true); err != nil { + glog.Errorf("Failed to add entry: %v to ip set: %s, error: %v", entry, set.Name, err) + } else { + glog.V(3).Infof("Successfully add entry: %v to ip set: %s", entry, set.Name) + } + } + } +} + +func ensureIPSets(ipSets ...*IPSet) error { + for _, set := range ipSets { + if err := set.handle.CreateSet(&set.IPSet, true); err != nil { + glog.Errorf("Failed to make sure ip set: %v exist, error: %v", set, err) + return err + } + } + return nil +} + +// checkMinVersion checks if ipset current version satisfies required min version +func checkMinVersion(vstring string) bool { + version, err := utilversion.ParseGeneric(vstring) + if err != nil { + glog.Errorf("vstring (%s) is not a valid version string: %v", vstring, err) + return false + } + + minVersion, err := utilversion.ParseGeneric(MinIPSetCheckVersion) + if err != nil { + glog.Errorf("MinCheckVersion (%s) is not a valid version string: %v", MinIPSetCheckVersion, err) + return false + } + return !version.LessThan(minVersion) +} diff --git a/pkg/proxy/ipvs/ipset_test.go b/pkg/proxy/ipvs/ipset_test.go new file mode 100644 index 00000000000..d6a0dc7a926 --- /dev/null +++ b/pkg/proxy/ipvs/ipset_test.go @@ -0,0 +1,182 @@ +/* +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 ( + "testing" + + utilipset "k8s.io/kubernetes/pkg/util/ipset" + fakeipset "k8s.io/kubernetes/pkg/util/ipset/testing" +) + +func TestCheckIPSetVersion(t *testing.T) { + testCases := []struct { + vstring string + valid bool + }{ + // version less than "6.0" is not valid. + {"4.0", false}, + {"5.1", false}, + {"5.1.2", false}, + // "7" is not a valid version string. + {"7", false}, + {"6.0", true}, + {"6.1", true}, + {"6.19", true}, + {"7.0", true}, + {"8.1.2", true}, + {"9.3.4.0", true}, + {"total junk", false}, + } + + for i := range testCases { + valid := checkMinVersion(testCases[i].vstring) + if testCases[i].valid != valid { + t.Errorf("Expected result: %v, Got result: %v", testCases[i].valid, valid) + } + } +} + +const testIPSetVersion = "v6.19" + +func TestSyncIPSetEntries(t *testing.T) { + testCases := []struct { + setName string + setType utilipset.Type + ipv6 bool + activeEntries []string + currentEntries []string + expectedEntries []string + }{ + { // case 0 + setName: "foo", + setType: utilipset.HashIPPort, + ipv6: false, + activeEntries: []string{"172.17.0.4,tcp:80"}, + currentEntries: nil, + expectedEntries: []string{"172.17.0.4,tcp:80"}, + }, + { // case 1 + setName: "abz", + setType: utilipset.HashIPPort, + ipv6: true, + activeEntries: []string{"FE80::0202:B3FF:FE1E:8329,tcp:80"}, + currentEntries: []string{"FE80::0202:B3FF:FE1E:8329,tcp:80"}, + expectedEntries: []string{"FE80::0202:B3FF:FE1E:8329,tcp:80"}, + }, + { // case 2 + setName: "bca", + setType: utilipset.HashIPPort, + ipv6: false, + activeEntries: []string{"172.17.0.4,tcp:80", "172.17.0.5,tcp:80"}, + currentEntries: []string{"172.17.0.5,udp:53"}, + expectedEntries: []string{"172.17.0.4,tcp:80", "172.17.0.5,tcp:80"}, + }, + { // case 3 + setName: "bar", + setType: utilipset.HashIPPortIP, + ipv6: false, + activeEntries: []string{"172.17.0.4,tcp:80:172.17.0.4"}, + currentEntries: []string{"172.17.0.4,tcp:80:172.17.0.4"}, + expectedEntries: []string{"172.17.0.4,tcp:80:172.17.0.4"}, + }, + { // case 4 + setName: "baz", + setType: utilipset.HashIPPortIP, + ipv6: true, + activeEntries: []string{"FE80:0000:0000:0000:0202:B3FF:FE1E:8329,tcp:8080:FE80:0000:0000:0000:0202:B3FF:FE1E:8329"}, + currentEntries: []string{"1111:0000:0000:0000:0202:B3FF:FE1E:8329,tcp:8081:1111:0000:0000:0000:0202:B3FF:FE1E:8329:8081"}, + expectedEntries: []string{"FE80:0000:0000:0000:0202:B3FF:FE1E:8329,tcp:8080:FE80:0000:0000:0000:0202:B3FF:FE1E:8329"}, + }, + { // case 5 + setName: "NOPE", + setType: utilipset.HashIPPortIP, + ipv6: false, + activeEntries: []string{"172.17.0.4,tcp:80,172.17.0.9", "172.17.0.5,tcp:80,172.17.0.10"}, + currentEntries: nil, + expectedEntries: []string{"172.17.0.4,tcp:80,172.17.0.9", "172.17.0.5,tcp:80,172.17.0.10"}, + }, + { // case 6 + setName: "ABC-DEF", + setType: utilipset.HashIPPortNet, + ipv6: false, + activeEntries: []string{"172.17.0.4,tcp:80,172.17.0.0/16", "172.17.0.5,tcp:80,172.17.0.0/16"}, + currentEntries: nil, + expectedEntries: []string{"172.17.0.4,tcp:80,172.17.0.0/16", "172.17.0.5,tcp:80,172.17.0.0/16"}, + }, + { // case 7 + setName: "zar", + setType: utilipset.HashIPPortNet, + ipv6: true, + activeEntries: []string{"FE80::8329,tcp:8800,2001:db8::/32"}, + currentEntries: []string{"FE80::8329,tcp:8800,2001:db8::/32"}, + expectedEntries: []string{"FE80::8329,tcp:8800,2001:db8::/32"}, + }, + { // case 8 + setName: "bbb", + setType: utilipset.HashIPPortNet, + ipv6: true, + activeEntries: nil, + currentEntries: []string{"FE80::8329,udp:8801,2001:db8::/32"}, + expectedEntries: nil, + }, + { // case 9 + setName: "AAA", + setType: utilipset.BitmapPort, + activeEntries: nil, + currentEntries: []string{"80"}, + expectedEntries: nil, + }, + { // case 10 + setName: "c-c-c", + setType: utilipset.BitmapPort, + activeEntries: []string{"8080", "9090"}, + currentEntries: []string{"80"}, + expectedEntries: []string{"8080", "9090"}, + }, + { // case 11 + setName: "NODE-PORT", + setType: utilipset.BitmapPort, + activeEntries: []string{"8080"}, + currentEntries: []string{"80", "9090", "8081", "8082"}, + expectedEntries: []string{"8080"}, + }, + } + + for i := range testCases { + set := NewIPSet(fakeipset.NewFake(testIPSetVersion), testCases[i].setName, testCases[i].setType, testCases[i].ipv6) + + if err := set.handle.CreateSet(&set.IPSet, true); err != nil { + t.Errorf("Unexpected error: %v", err) + } + for _, entry := range testCases[i].expectedEntries { + set.handle.AddEntry(entry, testCases[i].setName, true) + } + + set.activeEntries.Insert(testCases[i].activeEntries...) + set.syncIPSetEntries() + for _, entry := range testCases[i].expectedEntries { + found, err := set.handle.TestEntry(entry, testCases[i].setName) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if !found { + t.Errorf("Unexpected entry 172.17.0.4,tcp:80 not found in set foo") + } + } + } +} diff --git a/pkg/proxy/ipvs/netlink.go b/pkg/proxy/ipvs/netlink.go index a4e859f2b29..45551da36ad 100644 --- a/pkg/proxy/ipvs/netlink.go +++ b/pkg/proxy/ipvs/netlink.go @@ -16,10 +16,21 @@ limitations under the License. package ipvs +import ( + "k8s.io/apimachinery/pkg/util/sets" +) + // NetLinkHandle for revoke netlink interface type NetLinkHandle interface { - // EnsureAddressBind checks if address is bound to the interface and, if not, binds it. If the address is already bound, return true. + // EnsureAddressBind checks if address is bound to the interface and, if not, binds it. If the address is already bound, return true. EnsureAddressBind(address, devName string) (exist bool, err error) // UnbindAddress unbind address from the interface UnbindAddress(address, devName string) error + // EnsureDummyDevice checks if dummy device is exist and, if not, create one. If the dummy device is already exist, return true. + EnsureDummyDevice(devName string) (exist bool, err error) + // DeleteDummyDevice deletes the given dummy device by name. + DeleteDummyDevice(devName string) error + // GetLocalAddresses returns all unique local type IP addresses based on filter device interface. If filter device is not given, + // it will list all unique local type addresses. + GetLocalAddresses(filterDev string) (sets.String, error) } diff --git a/pkg/proxy/ipvs/netlink_linux.go b/pkg/proxy/ipvs/netlink_linux.go index df5b2f779a3..44b08646a22 100644 --- a/pkg/proxy/ipvs/netlink_linux.go +++ b/pkg/proxy/ipvs/netlink_linux.go @@ -21,16 +21,18 @@ package ipvs import ( "fmt" "net" - "syscall" + + "k8s.io/apimachinery/pkg/util/sets" "github.com/vishvananda/netlink" + "golang.org/x/sys/unix" ) type netlinkHandle struct { netlink.Handle } -// NewNetLinkHandle will crate a new netlinkHandle +// NewNetLinkHandle will crate a new NetLinkHandle func NewNetLinkHandle() NetLinkHandle { return &netlinkHandle{netlink.Handle{}} } @@ -47,7 +49,7 @@ func (h *netlinkHandle) EnsureAddressBind(address, devName string) (exist bool, } if err := h.AddrAdd(dev, &netlink.Addr{IPNet: netlink.NewIPNet(addr)}); err != nil { // "EEXIST" will be returned if the address is already bound to device - if err == syscall.Errno(syscall.EEXIST) { + if err == unix.EEXIST { return true, nil } return false, fmt.Errorf("error bind address: %s to interface: %s, err: %v", address, devName, err) @@ -70,3 +72,85 @@ func (h *netlinkHandle) UnbindAddress(address, devName string) error { } return nil } + +// EnsureDummyDevice is part of interface +func (h *netlinkHandle) EnsureDummyDevice(devName string) (bool, error) { + _, err := h.LinkByName(devName) + if err == nil { + // found dummy device + return true, nil + } + dummy := &netlink.Dummy{ + LinkAttrs: netlink.LinkAttrs{Name: devName}, + } + return false, h.LinkAdd(dummy) +} + +// DeleteDummyDevice is part of interface. +func (h *netlinkHandle) DeleteDummyDevice(devName string) error { + link, err := h.LinkByName(devName) + if err != nil { + return fmt.Errorf("error deleting a non-exist dummy device: %s", devName) + } + dummy, ok := link.(*netlink.Dummy) + if !ok { + return fmt.Errorf("expect dummy device, got device type: %s", link.Type()) + } + return h.LinkDel(dummy) +} + +// GetLocalAddresses lists all LOCAL type IP addresses from host based on filter device. +// If filter device is not specified, it's equivalent to exec: +// $ ip route show table local type local proto kernel +// 10.0.0.1 dev kube-ipvs0 scope host src 10.0.0.1 +// 10.0.0.10 dev kube-ipvs0 scope host src 10.0.0.10 +// 10.0.0.252 dev kube-ipvs0 scope host src 10.0.0.252 +// 100.106.89.164 dev eth0 scope host src 100.106.89.164 +// 127.0.0.0/8 dev lo scope host src 127.0.0.1 +// 127.0.0.1 dev lo scope host src 127.0.0.1 +// 172.17.0.1 dev docker0 scope host src 172.17.0.1 +// 192.168.122.1 dev virbr0 scope host src 192.168.122.1 +// Then cut the unique src IP fields, +// --> result set: [10.0.0.1, 10.0.0.10, 10.0.0.252, 100.106.89.164, 127.0.0.1, 192.168.122.1] + +// If filter device is specified, it's equivalent to exec: +// $ ip route show table local type local proto kernel dev kube-ipvs0 +// 10.0.0.1 scope host src 10.0.0.1 +// 10.0.0.10 scope host src 10.0.0.10 +// Then cut the unique src IP fields, +// --> result set: [10.0.0.1, 10.0.0.10] +func (h *netlinkHandle) GetLocalAddresses(filterDev string) (sets.String, error) { + linkIndex := -1 + if len(filterDev) != 0 { + link, err := h.LinkByName(filterDev) + if err != nil { + return nil, fmt.Errorf("error get filter device %s, err: %v", filterDev, err) + } + linkIndex = link.Attrs().Index + } + + routeFilter := &netlink.Route{ + Table: unix.RT_TABLE_LOCAL, + Type: unix.RTN_LOCAL, + Protocol: unix.RTPROT_KERNEL, + } + filterMask := netlink.RT_FILTER_TABLE | netlink.RT_FILTER_TYPE | netlink.RT_FILTER_PROTOCOL + + // find filter device + if linkIndex != -1 { + routeFilter.LinkIndex = linkIndex + filterMask |= netlink.RT_FILTER_OIF + } + + routes, err := h.RouteListFiltered(netlink.FAMILY_ALL, routeFilter, filterMask) + if err != nil { + return nil, fmt.Errorf("error list route table, err: %v", err) + } + res := sets.NewString() + for _, route := range routes { + if route.Src != nil { + res.Insert(route.Src.String()) + } + } + return res, nil +} diff --git a/pkg/proxy/ipvs/netlink_unsupported.go b/pkg/proxy/ipvs/netlink_unsupported.go index 6fec7de4a6e..b70550387ad 100644 --- a/pkg/proxy/ipvs/netlink_unsupported.go +++ b/pkg/proxy/ipvs/netlink_unsupported.go @@ -20,6 +20,8 @@ package ipvs import ( "fmt" + + "k8s.io/apimachinery/pkg/util/sets" ) type emptyHandle struct { @@ -39,3 +41,18 @@ func (h *emptyHandle) EnsureAddressBind(address, devName string) (exist bool, er func (h *emptyHandle) UnbindAddress(address, devName string) error { return fmt.Errorf("netlink not supported for this platform") } + +// EnsureDummyDevice is part of interface +func (h *emptyHandle) EnsureDummyDevice(devName string) (bool, error) { + return false, fmt.Errorf("netlink is not supported in this platform") +} + +// DeleteDummyDevice is part of interface. +func (h *emptyHandle) DeleteDummyDevice(devName string) error { + return fmt.Errorf("netlink is not supported in this platform") +} + +// GetLocalAddresses is part of interface. +func (h *emptyHandle) GetLocalAddresses(filterDev string) (sets.String, error) { + return nil, fmt.Errorf("netlink is not supported in this platform") +} diff --git a/pkg/proxy/ipvs/proxier.go b/pkg/proxy/ipvs/proxier.go index 60a04eb1332..e7cf693e6fe 100644 --- a/pkg/proxy/ipvs/proxier.go +++ b/pkg/proxy/ipvs/proxier.go @@ -35,19 +35,20 @@ import ( clientv1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/tools/record" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper" apiservice "k8s.io/kubernetes/pkg/api/service" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/proxy" "k8s.io/kubernetes/pkg/proxy/healthcheck" + "k8s.io/kubernetes/pkg/proxy/metrics" utilproxy "k8s.io/kubernetes/pkg/proxy/util" "k8s.io/kubernetes/pkg/util/async" + utilipset "k8s.io/kubernetes/pkg/util/ipset" utiliptables "k8s.io/kubernetes/pkg/util/iptables" utilipvs "k8s.io/kubernetes/pkg/util/ipvs" utilsysctl "k8s.io/kubernetes/pkg/util/sysctl" @@ -58,6 +59,12 @@ const ( // kubeServicesChain is the services portal chain kubeServicesChain utiliptables.Chain = "KUBE-SERVICES" + // KubeServiceIPSetsChain is the services access IP chain + KubeServiceIPSetsChain utiliptables.Chain = "KUBE-SVC-IPSETS" + + // KubeFireWallChain is the kubernetes firewall chain. + KubeFireWallChain utiliptables.Chain = "KUBE-FIRE-WALL" + // kubePostroutingChain is the kubernetes postrouting chain kubePostroutingChain utiliptables.Chain = "KUBE-POSTROUTING" @@ -66,11 +73,10 @@ const ( // KubeMarkDropChain is the mark-for-drop chain KubeMarkDropChain utiliptables.Chain = "KUBE-MARK-DROP" -) -const ( // DefaultScheduler is the default ipvs scheduler algorithm - round robin. DefaultScheduler = "rr" + // DefaultDummyDevice is the default dummy interface where ipvs service address will bind to it. DefaultDummyDevice = "kube-ipvs0" ) @@ -116,6 +122,7 @@ type Proxier struct { minSyncPeriod time.Duration iptables utiliptables.Interface ipvs utilipvs.Interface + ipset utilipset.Interface exec utilexec.Interface masqueradeAll bool masqueradeMark string @@ -136,6 +143,26 @@ type Proxier struct { natRules *bytes.Buffer // Added as a member to the struct to allow injection for testing. netlinkHandle NetLinkHandle + // loopbackSet is the ipset where stores all endpoints IP:Port,IP for solving hairpin mode purpose. + loopbackSet *IPSet + // clusterIPSet is the ipset where stores all service ClusterIP:Port + clusterIPSet *IPSet + // nodePortSetTCP is the bitmap:port type ipset where stores all TCP node port + nodePortSetTCP *IPSet + // nodePortSetTCP is the bitmap:port type ipset where stores all UDP node port + nodePortSetUDP *IPSet + // externalIPSet is the hash:ip,port type ipset where stores all service ExternalIP:Port + externalIPSet *IPSet + // lbIngressSet is the hash:ip,port type ipset where stores all service load balancer ingress IP:Port. + lbIngressSet *IPSet + // lbMasqSet is the hash:ip,port type ipset where stores all service load balancer ingress IP:Port which needs masquerade. + lbMasqSet *IPSet + // lbWhiteListIPSet is the hash:ip,port,ip type ipset where stores all service load balancer ingress IP:Port,sourceIP pair, any packets + // with the source IP visit ingress IP:Port can pass through. + lbWhiteListIPSet *IPSet + // lbWhiteListIPSet is the hash:ip,port,net type ipset where stores all service load balancer ingress IP:Port,sourceCIDR pair, any packets + // from the source CIDR visit ingress IP:Port can pass through. + lbWhiteListCIDRSet *IPSet } // IPGetter helps get node network interface IP @@ -143,36 +170,57 @@ type IPGetter interface { NodeIPs() ([]net.IP, error) } -type realIPGetter struct{} +// realIPGetter is a real NodeIP handler, it implements IPGetter. +type realIPGetter struct { + // nl is a handle for revoking netlink interface + nl NetLinkHandle +} +// NodeIPs returns all LOCAL type IP addresses from host which are taken as the Node IPs of NodePort service. +// Firstly, it will list source IP exists in local route table with `kernel` protocol type. For example, +// $ ip route show table local type local proto kernel +// 10.0.0.1 dev kube-ipvs0 scope host src 10.0.0.1 +// 10.0.0.10 dev kube-ipvs0 scope host src 10.0.0.10 +// 10.0.0.252 dev kube-ipvs0 scope host src 10.0.0.252 +// 100.106.89.164 dev eth0 scope host src 100.106.89.164 +// 127.0.0.0/8 dev lo scope host src 127.0.0.1 +// 127.0.0.1 dev lo scope host src 127.0.0.1 +// 172.17.0.1 dev docker0 scope host src 172.17.0.1 +// 192.168.122.1 dev virbr0 scope host src 192.168.122.1 +// Then cut the unique src IP fields, +// --> result set1: [10.0.0.1, 10.0.0.10, 10.0.0.252, 100.106.89.164, 127.0.0.1, 192.168.122.1] + +// NOTE: For cases where an LB acts as a VIP (e.g. Google cloud), the VIP IP is considered LOCAL, but the protocol +// of the entry is 66, e.g. `10.128.0.6 dev ens4 proto 66 scope host`. Therefore, the rule mentioned above will +// filter these entries out. + +// Secondly, as we bind Cluster IPs to the dummy interface in IPVS proxier, we need to filter the them out so that +// we can eventually get the Node IPs. Fortunately, the dummy interface created by IPVS proxier is known as `kube-ipvs0`, +// so we just need to specify the `dev kube-ipvs0` argument in ip route command, for example, +// $ ip route show table local type local proto kernel dev kube-ipvs0 +// 10.0.0.1 scope host src 10.0.0.1 +// 10.0.0.10 scope host src 10.0.0.10 +// Then cut the unique src IP fields, +// --> result set2: [10.0.0.1, 10.0.0.10] + +// Finally, Node IP set = set1 - set2 func (r *realIPGetter) NodeIPs() (ips []net.IP, err error) { - interfaces, err := net.Interfaces() + // Pass in empty filter device name for list all LOCAL type addresses. + allAddress, err := r.nl.GetLocalAddresses("") if err != nil { - return nil, err + return nil, fmt.Errorf("error listing LOCAL type addresses from host, error: %v", err) } - for i := range interfaces { - name := interfaces[i].Name - // We assume node ip bind to eth{x} - if !strings.HasPrefix(name, "eth") { - continue - } - intf, err := net.InterfaceByName(name) - if err != nil { - utilruntime.HandleError(fmt.Errorf("Failed to get interface by name: %s, error: %v", name, err)) - continue - } - addrs, err := intf.Addrs() - if err != nil { - utilruntime.HandleError(fmt.Errorf("Failed to get addresses from interface: %s, error: %v", name, err)) - continue - } - for _, a := range addrs { - if ipnet, ok := a.(*net.IPNet); ok { - ips = append(ips, ipnet.IP) - } - } + dummyAddress, err := r.nl.GetLocalAddresses(DefaultDummyDevice) + if err != nil { + return nil, fmt.Errorf("error listing LOCAL type addresses from device: %s, error: %v", DefaultDummyDevice, err) } - return + // exclude ip address from dummy interface created by IPVS proxier - they are all Cluster IPs. + nodeAddress := allAddress.Difference(dummyAddress) + // translate ip string to IP + for _, ipStr := range nodeAddress.UnsortedList() { + ips = append(ips, net.ParseIP(ipStr)) + } + return ips, nil } // Proxier implements ProxyProvider @@ -183,7 +231,9 @@ var _ proxy.ProxyProvider = &Proxier{} // An error will be returned if it fails to update or acquire the initial lock. // Once a proxier is created, it will keep iptables and ipvs rules up to date in the background and // will not terminate if a particular iptables or ipvs call fails. -func NewProxier(ipt utiliptables.Interface, ipvs utilipvs.Interface, +func NewProxier(ipt utiliptables.Interface, + ipvs utilipvs.Interface, + ipset utilipset.Interface, sysctl utilsysctl.Interface, exec utilexec.Interface, syncPeriod time.Duration, @@ -197,11 +247,6 @@ func NewProxier(ipt utiliptables.Interface, ipvs utilipvs.Interface, healthzServer healthcheck.HealthzUpdater, scheduler string, ) (*Proxier, error) { - // check valid user input - if minSyncPeriod > syncPeriod { - return nil, fmt.Errorf("min-sync (%v) must be < sync(%v)", minSyncPeriod, syncPeriod) - } - // Set the route_localnet sysctl we need for if err := sysctl.SetSysctl(sysctlRouteLocalnet, 1); err != nil { return nil, fmt.Errorf("can't set sysctl %s: %v", sysctlRouteLocalnet, err) @@ -225,9 +270,6 @@ func NewProxier(ipt utiliptables.Interface, ipvs utilipvs.Interface, } // Generate the masquerade mark to use for SNAT rules. - if masqueradeBit < 0 || masqueradeBit > 31 { - return nil, fmt.Errorf("invalid iptables-masquerade-bit %v not in [0, 31]", masqueradeBit) - } masqueradeValue := 1 << uint(masqueradeBit) masqueradeMark := fmt.Sprintf("%#08x/%#08x", masqueradeValue, masqueradeValue) @@ -247,32 +289,46 @@ func NewProxier(ipt utiliptables.Interface, ipvs utilipvs.Interface, healthChecker := healthcheck.NewServer(hostname, recorder, nil, nil) // use default implementations of deps + isIPv6 := utilproxy.IsIPv6(nodeIP) + + glog.V(2).Infof("nodeIP: %v, isIPv6: %v", nodeIP, isIPv6) + proxier := &Proxier{ - portsMap: make(map[utilproxy.LocalPort]utilproxy.Closeable), - serviceMap: make(proxyServiceMap), - serviceChanges: newServiceChangeMap(), - endpointsMap: make(proxyEndpointsMap), - endpointsChanges: newEndpointsChangeMap(hostname), - syncPeriod: syncPeriod, - minSyncPeriod: minSyncPeriod, - iptables: ipt, - masqueradeAll: masqueradeAll, - masqueradeMark: masqueradeMark, - exec: exec, - clusterCIDR: clusterCIDR, - hostname: hostname, - nodeIP: nodeIP, - portMapper: &listenPortOpener{}, - recorder: recorder, - healthChecker: healthChecker, - healthzServer: healthzServer, - ipvs: ipvs, - ipvsScheduler: scheduler, - ipGetter: &realIPGetter{}, - iptablesData: bytes.NewBuffer(nil), - natChains: bytes.NewBuffer(nil), - natRules: bytes.NewBuffer(nil), - netlinkHandle: NewNetLinkHandle(), + portsMap: make(map[utilproxy.LocalPort]utilproxy.Closeable), + serviceMap: make(proxyServiceMap), + serviceChanges: newServiceChangeMap(), + endpointsMap: make(proxyEndpointsMap), + endpointsChanges: newEndpointsChangeMap(hostname), + syncPeriod: syncPeriod, + minSyncPeriod: minSyncPeriod, + iptables: ipt, + masqueradeAll: masqueradeAll, + masqueradeMark: masqueradeMark, + exec: exec, + clusterCIDR: clusterCIDR, + hostname: hostname, + nodeIP: nodeIP, + portMapper: &listenPortOpener{}, + recorder: recorder, + healthChecker: healthChecker, + healthzServer: healthzServer, + ipvs: ipvs, + ipvsScheduler: scheduler, + ipGetter: &realIPGetter{nl: NewNetLinkHandle()}, + iptablesData: bytes.NewBuffer(nil), + natChains: bytes.NewBuffer(nil), + natRules: bytes.NewBuffer(nil), + netlinkHandle: NewNetLinkHandle(), + ipset: ipset, + loopbackSet: NewIPSet(ipset, KubeLoopBackIPSet, utilipset.HashIPPortIP, isIPv6), + clusterIPSet: NewIPSet(ipset, KubeClusterIPSet, utilipset.HashIPPort, isIPv6), + externalIPSet: NewIPSet(ipset, KubeExternalIPSet, utilipset.HashIPPort, isIPv6), + lbIngressSet: NewIPSet(ipset, KubeLoadBalancerSet, utilipset.HashIPPort, isIPv6), + lbMasqSet: NewIPSet(ipset, KubeLoadBalancerMasqSet, utilipset.HashIPPort, isIPv6), + lbWhiteListIPSet: NewIPSet(ipset, KubeLoadBalancerSourceIPSet, utilipset.HashIPPortIP, isIPv6), + lbWhiteListCIDRSet: NewIPSet(ipset, KubeLoadBalancerSourceCIDRSet, utilipset.HashIPPortNet, isIPv6), + nodePortSetTCP: NewIPSet(ipset, KubeNodePortSetTCP, utilipset.BitmapPort, false), + nodePortSetUDP: NewIPSet(ipset, KubeNodePortSetUDP, utilipset.BitmapPort, false), } burstSyncs := 2 glog.V(3).Infof("minSyncPeriod: %v, syncPeriod: %v, burstSyncs: %d", minSyncPeriod, syncPeriod, burstSyncs) @@ -484,6 +540,11 @@ func (e *endpointsInfo) IPPart() string { return utilproxy.IPPart(e.endpoint) } +// PortPart returns just the Port part of the endpoint. +func (e *endpointsInfo) PortPart() (int, error) { + return utilproxy.PortPart(e.endpoint) +} + type endpointServicePair struct { endpoint string servicePortName proxy.ServicePortName @@ -647,25 +708,69 @@ func (em proxyEndpointsMap) unmerge(other proxyEndpointsMap) { } } -// CanUseIPVSProxier returns true if we can use the ipvs Proxier. -// This is determined by checking if all the required kernel modules are loaded. It may -// return an error if it fails to get the kernel modules information without error, in which -// case it will also return false. -func CanUseIPVSProxier() (bool, error) { +// KernelHandler can handle the current installed kernel modules. +type KernelHandler interface { + GetModules() ([]string, error) +} + +// LinuxKernelHandler implements KernelHandler interface. +type LinuxKernelHandler struct { + executor utilexec.Interface +} + +// NewLinuxKernelHandler initializes LinuxKernelHandler with exec. +func NewLinuxKernelHandler() *LinuxKernelHandler { + return &LinuxKernelHandler{ + executor: utilexec.New(), + } +} + +// GetModules returns all installed kernel modules. +func (handle *LinuxKernelHandler) GetModules() ([]string, error) { + // Try to load IPVS required kernel modules using modprobe first + for _, kmod := range ipvsModules { + err := handle.executor.Command("modprobe", "--", kmod).Run() + if err != nil { + glog.Warningf("Failed to load kernel module %v with modprobe. "+ + "You can ignore this message when kube-proxy is running inside container without mounting /lib/modules", kmod) + } + } + // Find out loaded kernel modules - out, err := utilexec.New().Command("cut", "-f1", "-d", " ", "/proc/modules").CombinedOutput() + out, err := handle.executor.Command("cut", "-f1", "-d", " ", "/proc/modules").CombinedOutput() if err != nil { - return false, err + return nil, err } mods := strings.Split(string(out), "\n") + return mods, nil +} + +// CanUseIPVSProxier returns true if we can use the ipvs Proxier. +// This is determined by checking if all the required kernel modules can be loaded. It may +// return an error if it fails to get the kernel modules information without error, in which +// case it will also return false. +func CanUseIPVSProxier(handle KernelHandler, ipsetver IPSetVersioner) (bool, error) { + mods, err := handle.GetModules() + if err != nil { + return false, fmt.Errorf("error getting installed ipvs required kernel modules: %v", err) + } wantModules := sets.NewString() loadModules := sets.NewString() wantModules.Insert(ipvsModules...) loadModules.Insert(mods...) - modules := wantModules.Difference(loadModules).List() + modules := wantModules.Difference(loadModules).UnsortedList() if len(modules) != 0 { - return false, fmt.Errorf("Failed to load kernel modules: %v", modules) + return false, fmt.Errorf("IPVS proxier will not be used because the following required kernel modules are not loaded: %v", modules) + } + + // Check ipset version + versionString, err := ipsetver.GetVersion() + if err != nil { + return false, fmt.Errorf("error getting ipset version, error: %v", err) + } + if !checkMinVersion(versionString) { + return false, fmt.Errorf("ipset version: %s is less than min required version: %s", versionString, MinIPSetCheckVersion) } return true, nil } @@ -718,7 +823,7 @@ func cleanupIptablesLeftovers(ipt utiliptables.Interface) (encounteredError bool natRules := bytes.NewBuffer(nil) writeLine(natChains, "*nat") // Start with chains we know we need to remove. - for _, chain := range []utiliptables.Chain{kubeServicesChain, kubePostroutingChain, KubeMarkMasqChain} { + for _, chain := range []utiliptables.Chain{kubeServicesChain, kubePostroutingChain, KubeMarkMasqChain, KubeServiceIPSetsChain} { if _, found := existingNATChains[chain]; found { chainString := string(chain) writeLine(natChains, existingNATChains[chain]) // flush @@ -738,25 +843,41 @@ func cleanupIptablesLeftovers(ipt utiliptables.Interface) (encounteredError bool } // CleanupLeftovers clean up all ipvs and iptables rules created by ipvs Proxier. -func CleanupLeftovers(execer utilexec.Interface, ipvs utilipvs.Interface, ipt utiliptables.Interface) (encounteredError bool) { - // Return immediately when ipvs interface is nil - Probably initialization failed in somewhere. - if ipvs == nil { - return true - } - encounteredError = false - // Currently we assume only ipvs proxier will create ipvs rules, ipvs proxier will flush all ipvs rules when clean up. - // Users do this operation should be with caution. - err := ipvs.Flush() - if err != nil { - encounteredError = true +func CleanupLeftovers(ipvs utilipvs.Interface, ipt utiliptables.Interface, ipset utilipset.Interface, cleanupIPVS bool) (encounteredError bool) { + if cleanupIPVS { + // Return immediately when ipvs interface is nil - Probably initialization failed in somewhere. + if ipvs == nil { + return true + } + encounteredError = false + err := ipvs.Flush() + if err != nil { + glog.Errorf("Error flushing IPVS rules: %v", err) + encounteredError = true + } } // Delete dummy interface created by ipvs Proxier. - err = deleteDummyDevice(execer, DefaultDummyDevice) + nl := NewNetLinkHandle() + err := nl.DeleteDummyDevice(DefaultDummyDevice) if err != nil { + glog.Errorf("Error deleting dummy device %s created by IPVS proxier: %v", DefaultDummyDevice, err) encounteredError = true } // Clear iptables created by ipvs Proxier. encounteredError = cleanupIptablesLeftovers(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. + ipSetsToDestroy := []string{KubeLoopBackIPSet, KubeClusterIPSet, KubeLoadBalancerSet, KubeNodePortSetTCP, KubeNodePortSetUDP, + KubeExternalIPSet, KubeLoadBalancerSourceIPSet, KubeLoadBalancerSourceCIDRSet, KubeLoadBalancerMasqSet} + for _, set := range ipSetsToDestroy { + err = ipset.DestroySet(set) + if err != nil { + if !utilipset.IsNotFoundError(err) { + glog.Errorf("Error removing ipset %s, error: %v", set, err) + encounteredError = true + } + } + } return encounteredError } @@ -854,12 +975,6 @@ func (proxier *Proxier) OnEndpointsSynced() { proxier.syncProxyRules() } -type syncReason string - -const syncReasonServices syncReason = "ServicesUpdate" -const syncReasonEndpoints syncReason = "EndpointsUpdate" -const syncReasonForce syncReason = "Force" - // This is where all of the ipvs calls happen. // assumes proxier.mu is held func (proxier *Proxier) syncProxyRules() { @@ -868,6 +983,7 @@ func (proxier *Proxier) syncProxyRules() { start := time.Now() defer func() { + metrics.SyncProxyRulesLatency.Observe(metrics.SinceInMicroseconds(start)) glog.V(4).Infof("syncProxyRules took %v", time.Since(start)) }() // don't sync rules till we've received services and endpoints @@ -945,12 +1061,22 @@ func (proxier *Proxier) syncProxyRules() { // End install iptables // make sure dummy interface exists in the system where ipvs Proxier will bind service address on it - _, err = ensureDummyDevice(proxier.exec, DefaultDummyDevice) + _, err = proxier.netlinkHandle.EnsureDummyDevice(DefaultDummyDevice) if err != nil { glog.Errorf("Failed to create dummy interface: %s, error: %v", DefaultDummyDevice, err) return } + // make sure ip sets exists in the system. + ipSets := []*IPSet{proxier.loopbackSet, proxier.clusterIPSet, proxier.externalIPSet, proxier.nodePortSetUDP, proxier.nodePortSetTCP, + proxier.lbIngressSet, proxier.lbMasqSet, proxier.lbWhiteListCIDRSet, proxier.lbWhiteListIPSet} + if err := ensureIPSets(ipSets...); err != nil { + return + } + for i := range ipSets { + ipSets[i].resetEntries() + } + // Accumulate the set of local ports that we will be holding open once this update is complete replacementPortsMap := map[utilproxy.LocalPort]utilproxy.Closeable{} // activeIPVSServices represents IPVS service successfully created in this round of sync @@ -970,6 +1096,17 @@ func (proxier *Proxier) syncProxyRules() { // is just for efficiency, not correctness. args := make([]string, 64) + // Kube service portal + if err := proxier.linkKubeServiceChain(existingNATChains, proxier.natChains); err != nil { + glog.Errorf("Failed to link KUBE-SERVICES chain: %v", err) + return + } + // Kube service ipset + if err := proxier.createKubeFireWallChain(existingNATChains, proxier.natChains); err != nil { + glog.Errorf("Failed to create KUBE-FIRE-WALL chain: %v", err) + return + } + // Build IPVS rules for each service. for svcName, svcInfo := range proxier.serviceMap { protocol := strings.ToLower(string(svcInfo.protocol)) @@ -977,7 +1114,41 @@ func (proxier *Proxier) syncProxyRules() { // to ServicePortName.String() show up in CPU profiles. svcNameString := svcName.String() + // Handle traffic that loops back to the originator with SNAT. + for _, ep := range proxier.endpointsMap[svcName] { + epIP := ep.IPPart() + epPort, err := ep.PortPart() + // Error parsing this endpoint has been logged. Skip to next endpoint. + if epIP == "" || err != nil { + continue + } + entry := &utilipset.Entry{ + IP: epIP, + Port: epPort, + Protocol: protocol, + IP2: epIP, + SetType: utilipset.HashIPPortIP, + } + proxier.loopbackSet.activeEntries.Insert(entry.String()) + } + // Capture the clusterIP. + // ipset call + entry := &utilipset.Entry{ + IP: svcInfo.clusterIP.String(), + Port: svcInfo.port, + Protocol: protocol, + SetType: utilipset.HashIPPort, + } + // add service Cluster IP:Port to kubeServiceAccess ip set for the purpose of solving hairpin. + // proxier.kubeServiceAccessSet.activeEntries.Insert(entry.String()) + // Install masquerade rules if 'masqueradeAll' or 'clusterCIDR' is specified. + if proxier.masqueradeAll { + proxier.clusterIPSet.activeEntries.Insert(entry.String()) + } else if len(proxier.clusterCIDR) > 0 { + proxier.clusterIPSet.activeEntries.Insert(entry.String()) + } + // ipvs call serv := &utilipvs.VirtualServer{ Address: svcInfo.clusterIP, Port: uint16(svcInfo.port), @@ -992,38 +1163,14 @@ func (proxier *Proxier) syncProxyRules() { // We need to bind ClusterIP to dummy interface, so set `bindAddr` parameter to `true` in syncService() if err := proxier.syncService(svcNameString, serv, true); err == nil { activeIPVSServices[serv.String()] = true - if err := proxier.syncEndpoint(svcName, svcInfo.onlyNodeLocalEndpoints, serv); err != nil { + // ExternalTrafficPolicy only works for NodePort and external LB traffic, does not affect ClusterIP + // So we still need clusterIP rules in onlyNodeLocalEndpoints mode. + if err := proxier.syncEndpoint(svcName, false, serv); err != nil { glog.Errorf("Failed to sync endpoint for service: %v, err: %v", serv, err) } } else { glog.Errorf("Failed to sync service: %v, err: %v", serv, err) } - // Install masquerade rules if 'masqueradeAll' or 'clusterCIDR' is specified. - args = append(args[:0], - "-A", string(kubeServicesChain), - "-m", "comment", "--comment", fmt.Sprintf(`"%s cluster IP"`, svcNameString), - "-m", protocol, "-p", protocol, - "-d", fmt.Sprintf("%s/32", svcInfo.clusterIP.String()), - "--dport", strconv.Itoa(svcInfo.port), - ) - if proxier.masqueradeAll { - err = proxier.linkKubeServiceChain(existingNATChains, proxier.natChains) - if err != nil { - glog.Errorf("Failed to link KUBE-SERVICES chain: %v", err) - } - writeLine(proxier.natRules, append(args, "-j", string(KubeMarkMasqChain))...) - } else if len(proxier.clusterCIDR) > 0 { - // 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. - // If/when we support "Local" policy for VIPs, we should update this. - err = proxier.linkKubeServiceChain(existingNATChains, proxier.natChains) - if err != nil { - glog.Errorf("Failed to link KUBE-SERVICES chain: %v", err) - } - writeLine(proxier.natRules, append(args, "! -s", proxier.clusterCIDR, "-j", string(KubeMarkMasqChain))...) - } // Capture externalIPs. for _, externalIP := range svcInfo.externalIPs { @@ -1058,6 +1205,17 @@ func (proxier *Proxier) syncProxyRules() { } } // We're holding the port, so it's OK to install IPVS rules. + // ipset call + entry := &utilipset.Entry{ + IP: externalIP, + Port: svcInfo.port, + Protocol: protocol, + SetType: utilipset.HashIPPort, + } + // We have to SNAT packets to external IPs. + proxier.externalIPSet.activeEntries.Insert(entry.String()) + + // ipvs call serv := &utilipvs.VirtualServer{ Address: net.ParseIP(externalIP), Port: uint16(svcInfo.port), @@ -1082,25 +1240,39 @@ func (proxier *Proxier) syncProxyRules() { // Capture load-balancer ingress. for _, ingress := range svcInfo.loadBalancerStatus.Ingress { if ingress.IP != "" { + // ipset call + entry = &utilipset.Entry{ + IP: ingress.IP, + Port: svcInfo.port, + Protocol: protocol, + SetType: utilipset.HashIPPort, + } + // add service load balancer ingressIP:Port to kubeServiceAccess ip set for the purpose of solving hairpin. + // proxier.kubeServiceAccessSet.activeEntries.Insert(entry.String()) + // If we are proxying globally, we need to masquerade in case we cross nodes. + // If we are proxying only locally, we can retain the source IP. + if !svcInfo.onlyNodeLocalEndpoints { + proxier.lbMasqSet.activeEntries.Insert(entry.String()) + } if len(svcInfo.loadBalancerSourceRanges) != 0 { - err = proxier.linkKubeServiceChain(existingNATChains, proxier.natChains) - if err != nil { - glog.Errorf("Failed to link KUBE-SERVICES chain: %v", err) - } // The service firewall rules are created based on ServiceSpec.loadBalancerSourceRanges field. // This currently works for loadbalancers that preserves source ips. // For loadbalancers which direct traffic to service NodePort, the firewall rules will not apply. - args = append(args[:0], - "-A", string(kubeServicesChain), - "-m", "comment", "--comment", fmt.Sprintf(`"%s loadbalancer IP"`, svcNameString), - "-m", string(svcInfo.protocol), "-p", string(svcInfo.protocol), - "-d", fmt.Sprintf("%s/32", ingress.IP), - "--dport", fmt.Sprintf("%d", svcInfo.port), - ) + proxier.lbIngressSet.activeEntries.Insert(entry.String()) allowFromNode := false for _, src := range svcInfo.loadBalancerSourceRanges { - writeLine(proxier.natRules, append(args, "-s", src, "-j", "ACCEPT")...) + // ipset call + entry = &utilipset.Entry{ + IP: ingress.IP, + Port: svcInfo.port, + Protocol: protocol, + Net: src, + SetType: utilipset.HashIPPortNet, + } + // enumerate all white list source cidr + proxier.lbWhiteListCIDRSet.activeEntries.Insert(entry.String()) + // ignore error because it has been validated _, cidr, _ := net.ParseCIDR(src) if cidr.Contains(proxier.nodeIP) { @@ -1111,14 +1283,19 @@ func (proxier *Proxier) syncProxyRules() { // loadbalancer's backend hosts. In this case, request will not hit the loadbalancer but loop back directly. // Need to add the following rule to allow request on host. if allowFromNode { - writeLine(proxier.natRules, append(args, "-s", fmt.Sprintf("%s/32", ingress.IP), "-j", "ACCEPT")...) + entry = &utilipset.Entry{ + IP: ingress.IP, + Port: svcInfo.port, + Protocol: protocol, + IP2: ingress.IP, + SetType: utilipset.HashIPPortIP, + } + // enumerate all white list source ip + proxier.lbWhiteListIPSet.activeEntries.Insert(entry.String()) } - - // If the packet was able to reach the end of firewall chain, then it did not get DNATed. - // It means the packet cannot go through the firewall, then DROP it. - writeLine(proxier.natRules, append(args, "-j", string(KubeMarkDropChain))...) } + // ipvs call serv := &utilipvs.VirtualServer{ Address: net.ParseIP(ingress.IP), Port: uint16(svcInfo.port), @@ -1158,18 +1335,39 @@ func (proxier *Proxier) syncProxyRules() { continue } if lp.Protocol == "udp" { - isIPv6 := svcInfo.clusterIP.To4() != nil + isIPv6 := utilproxy.IsIPv6(svcInfo.clusterIP) utilproxy.ClearUDPConntrackForPort(proxier.exec, lp.Port, isIPv6) } replacementPortsMap[lp] = socket } // We're holding the port, so it's OK to install ipvs rules. + // Nodeports need SNAT, unless they're local. + // ipset call + if !svcInfo.onlyNodeLocalEndpoints { + entry = &utilipset.Entry{ + // No need to provide ip info + Port: svcInfo.nodePort, + Protocol: protocol, + SetType: utilipset.BitmapPort, + } + switch protocol { + case "tcp": + proxier.nodePortSetTCP.activeEntries.Insert(entry.String()) + case "udp": + proxier.nodePortSetUDP.activeEntries.Insert(entry.String()) + default: + // It should never hit + glog.Errorf("Unsupported protocol type: %s", protocol) + } + } + // Build ipvs kernel routes for each node ip address nodeIPs, err := proxier.ipGetter.NodeIPs() if err != nil { glog.Errorf("Failed to get node IP, err: %v", err) } else { for _, nodeIP := range nodeIPs { + // ipvs call serv := &utilipvs.VirtualServer{ Address: nodeIP, Port: uint16(svcInfo.nodePort), @@ -1194,6 +1392,119 @@ func (proxier *Proxier) syncProxyRules() { } } + // sync ipset entries + ipsetsToSync := []*IPSet{proxier.loopbackSet, proxier.clusterIPSet, proxier.lbIngressSet, proxier.lbMasqSet, proxier.nodePortSetTCP, + proxier.nodePortSetUDP, proxier.externalIPSet, proxier.lbWhiteListIPSet, proxier.lbWhiteListCIDRSet} + for i := range ipsetsToSync { + ipsetsToSync[i].syncIPSetEntries() + } + + // Tail call iptables rules for ipset, make sure only call iptables once + // in a single loop per ip set. + if !proxier.loopbackSet.isEmpty() { + args = append(args[:0], + "-A", string(kubePostroutingChain), + "-m", "set", "--match-set", proxier.loopbackSet.Name, + "dst,dst,src", + ) + writeLine(proxier.natRules, append(args, "-j", "MASQUERADE")...) + } + if !proxier.clusterIPSet.isEmpty() { + args = append(args[:0], + "-A", string(kubeServicesChain), + "-m", "set", "--match-set", proxier.clusterIPSet.Name, + "dst,dst", + ) + if proxier.masqueradeAll { + writeLine(proxier.natRules, append(args, "-j", string(KubeMarkMasqChain))...) + } else if len(proxier.clusterCIDR) > 0 { + // 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. + // If/when we support "Local" policy for VIPs, we should update this. + writeLine(proxier.natRules, append(args, "! -s", proxier.clusterCIDR, "-j", string(KubeMarkMasqChain))...) + } + } + if !proxier.externalIPSet.isEmpty() { + // Build masquerade rules for packets to external IPs. + args = append(args[:0], + "-A", string(kubeServicesChain), + "-m", "set", "--match-set", proxier.externalIPSet.Name, + "dst,dst", + ) + writeLine(proxier.natRules, append(args, "-j", string(KubeMarkMasqChain))...) + // Allow traffic for external IPs that does not come from a bridge (i.e. not from a container) + // nor from a local process to be forwarded to the service. + // This rule roughly translates to "all traffic from off-machine". + // This is imperfect in the face of network plugins that might not use a bridge, but we can revisit that later. + externalTrafficOnlyArgs := append(args, + "-m", "physdev", "!", "--physdev-is-in", + "-m", "addrtype", "!", "--src-type", "LOCAL") + writeLine(proxier.natRules, append(externalTrafficOnlyArgs, "-j", "ACCEPT")...) + dstLocalOnlyArgs := append(args, "-m", "addrtype", "--dst-type", "LOCAL") + // Allow traffic bound for external IPs that happen to be recognized as local IPs to stay local. + // This covers cases like GCE load-balancers which get added to the local routing table. + writeLine(proxier.natRules, append(dstLocalOnlyArgs, "-j", "ACCEPT")...) + } + if !proxier.lbMasqSet.isEmpty() { + // Build masquerade rules for packets which cross node visit load balancer ingress IPs. + args = append(args[:0], + "-A", string(kubeServicesChain), + "-m", "set", "--match-set", proxier.lbMasqSet.Name, + "dst,dst", + ) + writeLine(proxier.natRules, append(args, "-j", string(KubeMarkMasqChain))...) + } + if !proxier.lbWhiteListCIDRSet.isEmpty() || !proxier.lbWhiteListIPSet.isEmpty() { + // link kube-services chain -> kube-fire-wall chain + args := []string{"-m", "set", "--match-set", proxier.lbIngressSet.Name, "dst,dst", "-j", string(KubeFireWallChain)} + if _, err := proxier.iptables.EnsureRule(utiliptables.Append, utiliptables.TableNAT, kubeServicesChain, args...); err != nil { + glog.Errorf("Failed to ensure that ipset %s chain %s jumps to %s: %v", proxier.lbIngressSet.Name, kubeServicesChain, KubeFireWallChain, err) + } + if !proxier.lbWhiteListCIDRSet.isEmpty() { + args = append(args[:0], + "-A", string(KubeFireWallChain), + "-m", "set", "--match-set", proxier.lbWhiteListCIDRSet.Name, + "dst,dst,src", + ) + writeLine(proxier.natRules, append(args, "-j", "ACCEPT")...) + } + if !proxier.lbWhiteListIPSet.isEmpty() { + args = append(args[:0], + "-A", string(KubeFireWallChain), + "-m", "set", "--match-set", proxier.lbWhiteListIPSet.Name, + "dst,dst,src", + ) + writeLine(proxier.natRules, append(args, "-j", "ACCEPT")...) + } + args = append(args[:0], + "-A", string(KubeFireWallChain), + ) + // If the packet was able to reach the end of firewall chain, then it did not get DNATed. + // It means the packet cannot go thru the firewall, then mark it for DROP + writeLine(proxier.natRules, append(args, "-j", string(KubeMarkDropChain))...) + } + if !proxier.nodePortSetTCP.isEmpty() { + // Build masquerade rules for packets which cross node visit nodeport. + args = append(args[:0], + "-A", string(kubeServicesChain), + "-m", "tcp", "-p", "tcp", + "-m", "set", "--match-set", proxier.nodePortSetTCP.Name, + "dst", + ) + writeLine(proxier.natRules, append(args, "-j", string(KubeMarkMasqChain))...) + } + if !proxier.nodePortSetUDP.isEmpty() { + args = append(args[:0], + "-A", string(kubeServicesChain), + "-m", "udp", "-p", "udp", + "-m", "set", "--match-set", proxier.nodePortSetUDP.Name, + "dst", + ) + writeLine(proxier.natRules, append(args, "-j", string(KubeMarkMasqChain))...) + } + // Write the end-of-table markers. writeLine(proxier.natRules, "COMMIT") @@ -1248,7 +1559,7 @@ func (proxier *Proxier) syncProxyRules() { // Finish housekeeping. // TODO: these could be made more consistent. - for _, svcIP := range staleServices.List() { + for _, svcIP := range staleServices.UnsortedList() { if err := utilproxy.ClearUDPConntrackForIP(proxier.exec, svcIP); err != nil { glog.Errorf("Failed to delete stale service IP %s connections, error: %v", svcIP, err) } @@ -1285,7 +1596,7 @@ func (proxier *Proxier) syncService(svcName string, vs *utilipvs.VirtualServer, // IPVS service was changed, update the existing one // During updates, service VIP will not go down glog.V(3).Infof("IPVS service %s was changed", svcName) - if err := proxier.ipvs.UpdateVirtualServer(appliedVirtualServer); err != nil { + if err := proxier.ipvs.UpdateVirtualServer(vs); err != nil { glog.Errorf("Failed to update IPVS service, err:%v", err) return err } @@ -1333,7 +1644,7 @@ func (proxier *Proxier) syncEndpoint(svcPortName proxy.ServicePortName, onlyNode if !curEndpoints.Equal(newEndpoints) { // Create new endpoints - for _, ep := range newEndpoints.Difference(curEndpoints).List() { + for _, ep := range newEndpoints.Difference(curEndpoints).UnsortedList() { ip, port, err := net.SplitHostPort(ep) if err != nil { glog.Errorf("Failed to parse endpoint: %v, error: %v", ep, err) @@ -1357,7 +1668,7 @@ func (proxier *Proxier) syncEndpoint(svcPortName proxy.ServicePortName, onlyNode } } // Delete old endpoints - for _, ep := range curEndpoints.Difference(newEndpoints).List() { + for _, ep := range curEndpoints.Difference(newEndpoints).UnsortedList() { ip, port, err := net.SplitHostPort(ep) if err != nil { glog.Errorf("Failed to parse endpoint: %v, error: %v", ep, err) @@ -1405,7 +1716,6 @@ func (proxier *Proxier) cleanLegacyService(atciveServices map[string]bool, curre } // linkKubeServiceChain will Create chain KUBE-SERVICES and link the chin in PREROUTING and OUTPUT -// If not specify masqueradeAll or clusterCIDR or LB source range, won't create them. // Chain PREROUTING (policy ACCEPT) // target prot opt source destination @@ -1445,6 +1755,21 @@ func (proxier *Proxier) linkKubeServiceChain(existingNATChains map[utiliptables. return nil } +func (proxier *Proxier) createKubeFireWallChain(existingNATChains map[utiliptables.Chain]string, natChains *bytes.Buffer) error { + // `iptables -t nat -N KUBE-FIRE-WALL` + if _, err := proxier.iptables.EnsureChain(utiliptables.TableNAT, KubeFireWallChain); err != nil { + return fmt.Errorf("Failed to ensure that %s chain %s exists: %v", utiliptables.TableNAT, KubeFireWallChain, err) + } + + // write `:KUBE-FIRE-WALL - [0:0]` in nat table + if chain, ok := existingNATChains[KubeFireWallChain]; ok { + writeLine(natChains, chain) + } else { + writeLine(natChains, utiliptables.MakeChainLine(KubeFireWallChain)) + } + return nil +} + // Join all words with spaces, terminate with newline and write to buff. func writeLine(buf *bytes.Buffer, words ...string) { // We avoid strings.Join for performance reasons. @@ -1460,14 +1785,18 @@ func writeLine(buf *bytes.Buffer, words ...string) { func getLocalIPs(endpointsMap proxyEndpointsMap) map[types.NamespacedName]sets.String { localIPs := make(map[types.NamespacedName]sets.String) - for svcPort := range endpointsMap { - for _, ep := range endpointsMap[svcPort] { + for svcPortName := range endpointsMap { + for _, ep := range endpointsMap[svcPortName] { if ep.isLocal { - nsn := svcPort.NamespacedName - if localIPs[nsn] == nil { - localIPs[nsn] = sets.NewString() + // If the endpoint has a bad format, utilproxy.IPPart() will log an + // error and ep.IPPart() will return a null string. + if ip := ep.IPPart(); ip != "" { + nsn := svcPortName.NamespacedName + if localIPs[nsn] == nil { + localIPs[nsn] = sets.NewString() + } + localIPs[nsn].Insert(ip) } - localIPs[nsn].Insert(ep.IPPart()) // just the IP part } } } @@ -1520,32 +1849,6 @@ func openLocalPort(lp *utilproxy.LocalPort) (utilproxy.Closeable, error) { return socket, nil } -const cmdIP = "ip" - -func ensureDummyDevice(execer utilexec.Interface, dummyDev string) (exist bool, err error) { - args := []string{"link", "add", dummyDev, "type", "dummy"} - out, err := execer.Command(cmdIP, args...).CombinedOutput() - if err != nil { - // "exit status code 2" will be returned if the device already exists - if ee, ok := err.(utilexec.ExitError); ok { - if ee.Exited() && ee.ExitStatus() == 2 { - return true, nil - } - } - return false, fmt.Errorf("error creating dummy interface %q: %v: %s", dummyDev, err, out) - } - return false, nil -} - -func deleteDummyDevice(execer utilexec.Interface, dummyDev string) error { - args := []string{"link", "del", dummyDev} - out, err := execer.Command(cmdIP, args...).CombinedOutput() - if err != nil { - return fmt.Errorf("error deleting dummy interface %q: %v: %s", dummyDev, err, out) - } - return nil -} - // ipvs Proxier fall back on iptables when it needs to do SNAT for engress packets // It will only operate iptables *nat table. // Create and link the kube postrouting chain for SNAT packets. diff --git a/pkg/proxy/ipvs/proxier_test.go b/pkg/proxy/ipvs/proxier_test.go index 8119f6d5acb..88f7df51897 100644 --- a/pkg/proxy/ipvs/proxier_test.go +++ b/pkg/proxy/ipvs/proxier_test.go @@ -18,6 +18,7 @@ package ipvs import ( "bytes" + "fmt" "net" "reflect" "testing" @@ -25,7 +26,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/proxy" "k8s.io/utils/exec" fakeexec "k8s.io/utils/exec/testing" @@ -33,6 +34,8 @@ import ( "k8s.io/apimachinery/pkg/util/sets" netlinktest "k8s.io/kubernetes/pkg/proxy/ipvs/testing" proxyutil "k8s.io/kubernetes/pkg/proxy/util" + utilipset "k8s.io/kubernetes/pkg/util/ipset" + ipsettest "k8s.io/kubernetes/pkg/util/ipset/testing" utiliptables "k8s.io/kubernetes/pkg/util/iptables" iptablestest "k8s.io/kubernetes/pkg/util/iptables/testing" utilipvs "k8s.io/kubernetes/pkg/util/ipvs" @@ -85,7 +88,26 @@ func (fake *fakeHealthChecker) SyncEndpoints(newEndpoints map[types.NamespacedNa return nil } -func NewFakeProxier(ipt utiliptables.Interface, ipvs utilipvs.Interface, nodeIPs []net.IP) *Proxier { +// fakeKernelHandler implements KernelHandler. +type fakeKernelHandler struct { + modules []string +} + +func (fake *fakeKernelHandler) GetModules() ([]string, error) { + return fake.modules, nil +} + +// fakeKernelHandler implements KernelHandler. +type fakeIPSetVersioner struct { + version string + err error +} + +func (fake *fakeIPSetVersioner) GetVersion() (string, error) { + return fake.version, fake.err +} + +func NewFakeProxier(ipt utiliptables.Interface, ipvs utilipvs.Interface, ipset utilipset.Interface, nodeIPs []net.IP) *Proxier { fcmd := fakeexec.FakeCmd{ CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ func() ([]byte, error) { return []byte("dummy device have been created"), nil }, @@ -98,24 +120,34 @@ func NewFakeProxier(ipt utiliptables.Interface, ipvs utilipvs.Interface, nodeIPs LookPathFunc: func(cmd string) (string, error) { return cmd, nil }, } return &Proxier{ - exec: fexec, - serviceMap: make(proxyServiceMap), - serviceChanges: newServiceChangeMap(), - endpointsMap: make(proxyEndpointsMap), - endpointsChanges: newEndpointsChangeMap(testHostname), - iptables: ipt, - ipvs: ipvs, - clusterCIDR: "10.0.0.0/24", - hostname: testHostname, - portsMap: make(map[proxyutil.LocalPort]proxyutil.Closeable), - portMapper: &fakePortOpener{[]*proxyutil.LocalPort{}}, - healthChecker: newFakeHealthChecker(), - ipvsScheduler: DefaultScheduler, - ipGetter: &fakeIPGetter{nodeIPs: nodeIPs}, - iptablesData: bytes.NewBuffer(nil), - natChains: bytes.NewBuffer(nil), - natRules: bytes.NewBuffer(nil), - netlinkHandle: netlinktest.NewFakeNetlinkHandle(), + exec: fexec, + serviceMap: make(proxyServiceMap), + serviceChanges: newServiceChangeMap(), + endpointsMap: make(proxyEndpointsMap), + endpointsChanges: newEndpointsChangeMap(testHostname), + iptables: ipt, + ipvs: ipvs, + ipset: ipset, + clusterCIDR: "10.0.0.0/24", + hostname: testHostname, + portsMap: make(map[proxyutil.LocalPort]proxyutil.Closeable), + portMapper: &fakePortOpener{[]*proxyutil.LocalPort{}}, + healthChecker: newFakeHealthChecker(), + ipvsScheduler: DefaultScheduler, + ipGetter: &fakeIPGetter{nodeIPs: nodeIPs}, + iptablesData: bytes.NewBuffer(nil), + natChains: bytes.NewBuffer(nil), + natRules: bytes.NewBuffer(nil), + netlinkHandle: netlinktest.NewFakeNetlinkHandle(), + loopbackSet: NewIPSet(ipset, KubeLoopBackIPSet, utilipset.HashIPPortIP, false), + clusterIPSet: NewIPSet(ipset, KubeClusterIPSet, utilipset.HashIPPort, false), + externalIPSet: NewIPSet(ipset, KubeExternalIPSet, utilipset.HashIPPort, false), + lbIngressSet: NewIPSet(ipset, KubeLoadBalancerSet, utilipset.HashIPPort, false), + lbMasqSet: NewIPSet(ipset, KubeLoadBalancerMasqSet, utilipset.HashIPPort, false), + lbWhiteListIPSet: NewIPSet(ipset, KubeLoadBalancerSourceIPSet, utilipset.HashIPPortIP, false), + lbWhiteListCIDRSet: NewIPSet(ipset, KubeLoadBalancerSourceCIDRSet, utilipset.HashIPPortNet, false), + nodePortSetTCP: NewIPSet(ipset, KubeNodePortSetTCP, utilipset.BitmapPort, false), + nodePortSetUDP: NewIPSet(ipset, KubeNodePortSetUDP, utilipset.BitmapPort, false), } } @@ -168,13 +200,155 @@ func makeTestEndpoints(namespace, name string, eptFunc func(*api.Endpoints)) *ap return ept } +func TestCanUseIPVSProxier(t *testing.T) { + testCases := []struct { + mods []string + kernelErr error + ipsetVersion string + ipsetErr error + ok bool + }{ + // case 0, kernel error + { + mods: []string{"foo", "bar", "baz"}, + kernelErr: fmt.Errorf("oops"), + ipsetVersion: "0.0", + ok: false, + }, + // case 1, ipset error + { + mods: []string{"foo", "bar", "baz"}, + ipsetVersion: MinIPSetCheckVersion, + ipsetErr: fmt.Errorf("oops"), + ok: false, + }, + // case 2, missing required kernel modules and ipset version too low + { + mods: []string{"foo", "bar", "baz"}, + ipsetVersion: "1.1", + ok: false, + }, + // case 3, missing required ip_vs_* kernel modules + { + mods: []string{"ip_vs", "a", "bc", "def"}, + ipsetVersion: MinIPSetCheckVersion, + ok: false, + }, + // case 4, ipset version too low + { + mods: []string{"ip_vs", "ip_vs_rr", "ip_vs_wrr", "ip_vs_sh", "nf_conntrack_ipv4"}, + ipsetVersion: "4.3.0", + ok: false, + }, + // case 5 + { + mods: []string{"ip_vs", "ip_vs_rr", "ip_vs_wrr", "ip_vs_sh", "nf_conntrack_ipv4"}, + ipsetVersion: MinIPSetCheckVersion, + ok: true, + }, + // case 6 + { + mods: []string{"ip_vs", "ip_vs_rr", "ip_vs_wrr", "ip_vs_sh", "nf_conntrack_ipv4", "foo", "bar"}, + ipsetVersion: "6.19", + ok: true, + }, + } + + for i := range testCases { + handle := &fakeKernelHandler{modules: testCases[i].mods} + versioner := &fakeIPSetVersioner{version: testCases[i].ipsetVersion, err: testCases[i].ipsetErr} + ok, _ := CanUseIPVSProxier(handle, versioner) + if ok != testCases[i].ok { + t.Errorf("Case [%d], expect %v, got %v", i, testCases[i].ok, ok) + } + } +} + +func TestGetNodeIPs(t *testing.T) { + testCases := []struct { + devAddresses map[string][]string + expectIPs []string + }{ + // case 0 + { + devAddresses: map[string][]string{"eth0": {"1.2.3.4"}, "lo": {"127.0.0.1"}}, + expectIPs: []string{"1.2.3.4", "127.0.0.1"}, + }, + // case 1 + { + devAddresses: map[string][]string{"lo": {"127.0.0.1"}}, + expectIPs: []string{"127.0.0.1"}, + }, + // case 2 + { + devAddresses: map[string][]string{}, + expectIPs: []string{}, + }, + // case 3 + { + devAddresses: map[string][]string{"encap0": {"10.20.30.40"}, "lo": {"127.0.0.1"}, "docker0": {"172.17.0.1"}}, + expectIPs: []string{"10.20.30.40", "127.0.0.1", "172.17.0.1"}, + }, + // case 4 + { + devAddresses: map[string][]string{"encaps9": {"10.20.30.40"}, "lo": {"127.0.0.1"}, "encap7": {"10.20.30.31"}}, + expectIPs: []string{"10.20.30.40", "127.0.0.1", "10.20.30.31"}, + }, + // case 5 + { + devAddresses: map[string][]string{"kube-ipvs0": {"1.2.3.4"}, "lo": {"127.0.0.1"}, "encap7": {"10.20.30.31"}}, + expectIPs: []string{"127.0.0.1", "10.20.30.31"}, + }, + // case 6 + { + devAddresses: map[string][]string{"kube-ipvs0": {"1.2.3.4", "2.3.4.5"}, "lo": {"127.0.0.1"}}, + expectIPs: []string{"127.0.0.1"}, + }, + // case 7 + { + devAddresses: map[string][]string{"kube-ipvs0": {"1.2.3.4", "2.3.4.5"}}, + expectIPs: []string{}, + }, + // case 8 + { + devAddresses: map[string][]string{"kube-ipvs0": {"1.2.3.4", "2.3.4.5"}, "eth5": {"3.4.5.6"}, "lo": {"127.0.0.1"}}, + expectIPs: []string{"127.0.0.1", "3.4.5.6"}, + }, + // case 9 + { + devAddresses: map[string][]string{"ipvs0": {"1.2.3.4"}, "lo": {"127.0.0.1"}, "encap7": {"10.20.30.31"}}, + expectIPs: []string{"127.0.0.1", "10.20.30.31", "1.2.3.4"}, + }, + } + + for i := range testCases { + fake := netlinktest.NewFakeNetlinkHandle() + for dev, addresses := range testCases[i].devAddresses { + fake.SetLocalAddresses(dev, addresses...) + } + r := realIPGetter{nl: fake} + ips, err := r.NodeIPs() + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + ipStrs := sets.NewString() + for _, ip := range ips { + ipStrs.Insert(ip.String()) + } + if !ipStrs.Equal(sets.NewString(testCases[i].expectIPs...)) { + t.Errorf("case[%d], unexpected mismatch, expected: %v, got: %v", i, testCases[i].expectIPs, ips) + } + } +} + func TestNodePort(t *testing.T) { ipt := iptablestest.NewFake() ipvs := ipvstest.NewFake() + ipset := ipsettest.NewFake(testIPSetVersion) nodeIPv4 := net.ParseIP("100.101.102.103") nodeIPv6 := net.ParseIP("2001:db8::1:1") nodeIPs := sets.NewString(nodeIPv4.String(), nodeIPv6.String()) - fp := NewFakeProxier(ipt, ipvs, []net.IP{nodeIPv4, nodeIPv6}) + fp := NewFakeProxier(ipt, ipvs, ipset, []net.IP{nodeIPv4, nodeIPv6}) svcIP := "10.20.30.41" svcPort := 80 svcNodePort := 3001 @@ -248,8 +422,9 @@ func TestNodePort(t *testing.T) { func TestNodePortNoEndpoint(t *testing.T) { ipt := iptablestest.NewFake() ipvs := ipvstest.NewFake() + ipset := ipsettest.NewFake(testIPSetVersion) nodeIP := net.ParseIP("100.101.102.103") - fp := NewFakeProxier(ipt, ipvs, []net.IP{nodeIP}) + fp := NewFakeProxier(ipt, ipvs, ipset, []net.IP{nodeIP}) svcIP := "10.20.30.41" svcPort := 80 svcNodePort := 3001 @@ -301,7 +476,8 @@ func TestNodePortNoEndpoint(t *testing.T) { func TestClusterIPNoEndpoint(t *testing.T) { ipt := iptablestest.NewFake() ipvs := ipvstest.NewFake() - fp := NewFakeProxier(ipt, ipvs, nil) + ipset := ipsettest.NewFake(testIPSetVersion) + fp := NewFakeProxier(ipt, ipvs, ipset, nil) svcIP := "10.20.30.41" svcPort := 80 svcPortName := proxy.ServicePortName{ @@ -344,7 +520,8 @@ func TestClusterIPNoEndpoint(t *testing.T) { func TestClusterIP(t *testing.T) { ipt := iptablestest.NewFake() ipvs := ipvstest.NewFake() - fp := NewFakeProxier(ipt, ipvs, nil) + ipset := ipsettest.NewFake(testIPSetVersion) + fp := NewFakeProxier(ipt, ipvs, ipset, nil) svcIPv4 := "10.20.30.41" svcPortV4 := 80 @@ -450,7 +627,8 @@ func TestClusterIP(t *testing.T) { func TestExternalIPsNoEndpoint(t *testing.T) { ipt := iptablestest.NewFake() ipvs := ipvstest.NewFake() - fp := NewFakeProxier(ipt, ipvs, nil) + ipset := ipsettest.NewFake(testIPSetVersion) + fp := NewFakeProxier(ipt, ipvs, ipset, nil) svcIP := "10.20.30.41" svcPort := 80 svcExternalIPs := "50.60.70.81" @@ -504,7 +682,8 @@ func TestExternalIPsNoEndpoint(t *testing.T) { func TestExternalIPs(t *testing.T) { ipt := iptablestest.NewFake() ipvs := ipvstest.NewFake() - fp := NewFakeProxier(ipt, ipvs, nil) + ipset := ipsettest.NewFake(testIPSetVersion) + fp := NewFakeProxier(ipt, ipvs, ipset, nil) svcIP := "10.20.30.41" svcPort := 80 svcExternalIPs := sets.NewString("50.60.70.81", "2012::51") @@ -573,7 +752,8 @@ func TestExternalIPs(t *testing.T) { func TestLoadBalancer(t *testing.T) { ipt := iptablestest.NewFake() ipvs := ipvstest.NewFake() - fp := NewFakeProxier(ipt, ipvs, nil) + ipset := ipsettest.NewFake(testIPSetVersion) + fp := NewFakeProxier(ipt, ipvs, ipset, nil) svcIP := "10.20.30.41" svcPort := 80 svcNodePort := 3001 @@ -624,8 +804,9 @@ func strPtr(s string) *string { func TestOnlyLocalNodePorts(t *testing.T) { ipt := iptablestest.NewFake() ipvs := ipvstest.NewFake() + ipset := ipsettest.NewFake(testIPSetVersion) nodeIP := net.ParseIP("100.101.102.103") - fp := NewFakeProxier(ipt, ipvs, []net.IP{nodeIP}) + fp := NewFakeProxier(ipt, ipvs, ipset, []net.IP{nodeIP}) svcIP := "10.20.30.41" svcPort := 80 svcNodePort := 3001 @@ -701,11 +882,11 @@ func TestOnlyLocalNodePorts(t *testing.T) { } } -// NO help func TestOnlyLocalLoadBalancing(t *testing.T) { ipt := iptablestest.NewFake() ipvs := ipvstest.NewFake() - fp := NewFakeProxier(ipt, ipvs, nil) + ipset := ipsettest.NewFake(testIPSetVersion) + fp := NewFakeProxier(ipt, ipvs, ipset, nil) svcIP := "10.20.30.41" svcPort := 80 svcNodePort := 3001 @@ -769,7 +950,8 @@ func addTestPort(array []api.ServicePort, name string, protocol api.Protocol, po func TestBuildServiceMapAddRemove(t *testing.T) { ipt := iptablestest.NewFake() ipvs := ipvstest.NewFake() - fp := NewFakeProxier(ipt, ipvs, nil) + ipset := ipsettest.NewFake(testIPSetVersion) + fp := NewFakeProxier(ipt, ipvs, ipset, nil) services := []*api.Service{ makeTestService("somewhere-else", "cluster-ip", func(svc *api.Service) { @@ -874,7 +1056,8 @@ func TestBuildServiceMapAddRemove(t *testing.T) { func TestBuildServiceMapServiceHeadless(t *testing.T) { ipt := iptablestest.NewFake() ipvs := ipvstest.NewFake() - fp := NewFakeProxier(ipt, ipvs, nil) + ipset := ipsettest.NewFake(testIPSetVersion) + fp := NewFakeProxier(ipt, ipvs, ipset, nil) makeServiceMap(fp, makeTestService("somewhere-else", "headless", func(svc *api.Service) { @@ -907,7 +1090,8 @@ func TestBuildServiceMapServiceHeadless(t *testing.T) { func TestBuildServiceMapServiceTypeExternalName(t *testing.T) { ipt := iptablestest.NewFake() ipvs := ipvstest.NewFake() - fp := NewFakeProxier(ipt, ipvs, nil) + ipset := ipsettest.NewFake(testIPSetVersion) + fp := NewFakeProxier(ipt, ipvs, ipset, nil) makeServiceMap(fp, makeTestService("somewhere-else", "external-name", func(svc *api.Service) { @@ -934,7 +1118,8 @@ func TestBuildServiceMapServiceTypeExternalName(t *testing.T) { func TestBuildServiceMapServiceUpdate(t *testing.T) { ipt := iptablestest.NewFake() ipvs := ipvstest.NewFake() - fp := NewFakeProxier(ipt, ipvs, nil) + ipset := ipsettest.NewFake(testIPSetVersion) + fp := NewFakeProxier(ipt, ipvs, ipset, nil) servicev1 := makeTestService("somewhere", "some-service", func(svc *api.Service) { svc.Spec.Type = api.ServiceTypeClusterIP @@ -1016,8 +1201,9 @@ func TestBuildServiceMapServiceUpdate(t *testing.T) { func TestSessionAffinity(t *testing.T) { ipt := iptablestest.NewFake() ipvs := ipvstest.NewFake() + ipset := ipsettest.NewFake(testIPSetVersion) nodeIP := net.ParseIP("100.101.102.103") - fp := NewFakeProxier(ipt, ipvs, []net.IP{nodeIP}) + fp := NewFakeProxier(ipt, ipvs, ipset, []net.IP{nodeIP}) svcIP := "10.20.30.41" svcPort := 80 svcNodePort := 3001 @@ -1879,7 +2065,8 @@ func Test_updateEndpointsMap(t *testing.T) { for tci, tc := range testCases { ipt := iptablestest.NewFake() ipvs := ipvstest.NewFake() - fp := NewFakeProxier(ipt, ipvs, nil) + ipset := ipsettest.NewFake(testIPSetVersion) + fp := NewFakeProxier(ipt, ipvs, ipset, nil) fp.hostname = nodeName // First check that after adding all previous versions of endpoints, @@ -2017,6 +2204,14 @@ func Test_getLocalIPs(t *testing.T) { {Namespace: "ns2", Name: "ep2"}: sets.NewString("2.2.2.2", "2.2.2.22", "2.2.2.3"), {Namespace: "ns4", Name: "ep4"}: sets.NewString("4.4.4.4", "4.4.4.6"), }, + }, { + // Case[5]: named port local and bad endpoints IP + endpointsMap: map[proxy.ServicePortName][]*endpointsInfo{ + makeServicePortName("ns1", "ep1", "p11"): { + {endpoint: "bad ip:11", isLocal: true}, + }, + }, + expected: map[types.NamespacedName]sets.String{}, }} for tci, tc := range testCases { @@ -2214,74 +2409,110 @@ func Test_endpointsToEndpointsMap(t *testing.T) { } } -func Test_ensureDummyDevice(t *testing.T) { - fcmd := fakeexec.FakeCmd{ - CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ - // Success. - func() ([]byte, error) { return []byte{}, nil }, - // Exists. - func() ([]byte, error) { return nil, &fakeexec.FakeExitError{Status: 2} }, +func Test_syncService(t *testing.T) { + testCases := []struct { + oldVirtualServer *utilipvs.VirtualServer + svcName string + newVirtualServer *utilipvs.VirtualServer + bindAddr bool + }{ + { + // case 0, old virtual server is same as new virtual server + oldVirtualServer: &utilipvs.VirtualServer{ + Address: net.ParseIP("1.2.3.4"), + Protocol: string(api.ProtocolTCP), + Port: 80, + Scheduler: "rr", + Flags: utilipvs.FlagHashed, + }, + svcName: "foo", + newVirtualServer: &utilipvs.VirtualServer{ + Address: net.ParseIP("1.2.3.4"), + Protocol: string(api.ProtocolTCP), + Port: 80, + Scheduler: "rr", + Flags: utilipvs.FlagHashed, + }, + bindAddr: false, + }, + { + // case 1, old virtual server is different from new virtual server + oldVirtualServer: &utilipvs.VirtualServer{ + Address: net.ParseIP("1.2.3.4"), + Protocol: string(api.ProtocolTCP), + Port: 8080, + Scheduler: "rr", + Flags: utilipvs.FlagHashed, + }, + svcName: "bar", + newVirtualServer: &utilipvs.VirtualServer{ + Address: net.ParseIP("1.2.3.4"), + Protocol: string(api.ProtocolTCP), + Port: 8080, + Scheduler: "rr", + Flags: utilipvs.FlagPersistent, + }, + bindAddr: false, + }, + { + // case 2, old virtual server is different from new virtual server + oldVirtualServer: &utilipvs.VirtualServer{ + Address: net.ParseIP("1.2.3.4"), + Protocol: string(api.ProtocolTCP), + Port: 8080, + Scheduler: "rr", + Flags: utilipvs.FlagHashed, + }, + svcName: "bar", + newVirtualServer: &utilipvs.VirtualServer{ + Address: net.ParseIP("1.2.3.4"), + Protocol: string(api.ProtocolTCP), + Port: 8080, + Scheduler: "wlc", + Flags: utilipvs.FlagHashed, + }, + bindAddr: false, + }, + { + // case 3, old virtual server is nil, and create new virtual server + oldVirtualServer: nil, + svcName: "baz", + newVirtualServer: &utilipvs.VirtualServer{ + Address: net.ParseIP("1.2.3.4"), + Protocol: string(api.ProtocolUDP), + Port: 53, + Scheduler: "rr", + Flags: utilipvs.FlagHashed, + }, + bindAddr: true, }, } - fexec := fakeexec.FakeExec{ - CommandScript: []fakeexec.FakeCommandAction{ - func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, - func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, - }, - } - // Success. - exists, err := ensureDummyDevice(&fexec, DefaultDummyDevice) - if err != nil { - t.Errorf("expected success, got %v", err) - } - if exists { - t.Errorf("expected exists = false") - } - if fcmd.CombinedOutputCalls != 1 { - t.Errorf("expected 1 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) - } - if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ip", "link", "add", "kube-ipvs0", "type", "dummy") { - t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0]) - } - // Exists. - exists, err = ensureDummyDevice(&fexec, DefaultDummyDevice) - if err != nil { - t.Errorf("expected success, got %v", err) - } - if !exists { - t.Errorf("expected exists = true") - } -} -func Test_deleteDummyDevice(t *testing.T) { - fcmd := fakeexec.FakeCmd{ - CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ - // Success. - func() ([]byte, error) { return []byte{}, nil }, - // Failure. - func() ([]byte, error) { return nil, &fakeexec.FakeExitError{Status: 1} }, - }, - } - fexec := fakeexec.FakeExec{ - CommandScript: []fakeexec.FakeCommandAction{ - func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, - func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, - }, - } - // Success. - err := deleteDummyDevice(&fexec, DefaultDummyDevice) - if err != nil { - t.Errorf("expected success, got %v", err) - } - if fcmd.CombinedOutputCalls != 1 { - t.Errorf("expected 1 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) - } - if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ip", "link", "del", "kube-ipvs0") { - t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0]) - } - // Failure. - err = deleteDummyDevice(&fexec, DefaultDummyDevice) - if err == nil { - t.Errorf("expected failure") + for i := range testCases { + ipt := iptablestest.NewFake() + ipvs := ipvstest.NewFake() + ipset := ipsettest.NewFake(testIPSetVersion) + proxier := NewFakeProxier(ipt, ipvs, ipset, nil) + + if testCases[i].oldVirtualServer != nil { + if err := proxier.ipvs.AddVirtualServer(testCases[i].oldVirtualServer); err != nil { + t.Errorf("Case [%d], unexpected add IPVS virtual server error: %v", i, err) + } + } + if err := proxier.syncService(testCases[i].svcName, testCases[i].newVirtualServer, testCases[i].bindAddr); err != nil { + t.Errorf("Case [%d], unexpected sync IPVS virutal server error: %v", i, err) + } + // check + list, err := proxier.ipvs.GetVirtualServers() + if err != nil { + t.Errorf("Case [%d], unexpected list IPVS virtual server error: %v", i, err) + } + if len(list) != 1 { + t.Errorf("Case [%d], expect %d virtual servers, got %d", i, 1, len(list)) + continue + } + if !list[0].Equal(testCases[i].newVirtualServer) { + t.Errorf("Case [%d], unexpected mismatch, expect: %#v, got: %#v", i, testCases[i].newVirtualServer, list[0]) + } } } diff --git a/pkg/proxy/ipvs/testing/BUILD b/pkg/proxy/ipvs/testing/BUILD index 80856e908de..0e5b33f4b8b 100644 --- a/pkg/proxy/ipvs/testing/BUILD +++ b/pkg/proxy/ipvs/testing/BUILD @@ -5,18 +5,15 @@ licenses(["notice"]) load( "@io_bazel_rules_go//go:def.bzl", "go_library", + "go_test", ) go_library( name = "go_default_library", - srcs = select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ - "fake.go", - ], - "//conditions:default": [], - }), + srcs = ["fake.go"], importpath = "k8s.io/kubernetes/pkg/proxy/ipvs/testing", tags = ["automanaged"], + deps = ["//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library"], ) filegroup( @@ -31,3 +28,11 @@ filegroup( srcs = [":package-srcs"], tags = ["automanaged"], ) + +go_test( + name = "go_default_test", + srcs = ["fake_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/proxy/ipvs/testing", + deps = ["//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library"], +) diff --git a/pkg/proxy/ipvs/testing/fake.go b/pkg/proxy/ipvs/testing/fake.go index e1dc62929e3..561e8b3c6b0 100644 --- a/pkg/proxy/ipvs/testing/fake.go +++ b/pkg/proxy/ipvs/testing/fake.go @@ -1,5 +1,3 @@ -// +build linux - /* Copyright 2017 The Kubernetes Authors. @@ -18,21 +16,76 @@ limitations under the License. package testing -//FakeNetlinkHandle mock implementation of proxy NetlinkHandle +import ( + "fmt" + + "k8s.io/apimachinery/pkg/util/sets" +) + +// FakeNetlinkHandle mock implementation of proxy NetlinkHandle type FakeNetlinkHandle struct { + // localAddresses is a network interface name to all of its IP addresses map, e.g. + // eth0 -> [1.2.3.4, 10.20.30.40] + localAddresses map[string][]string } -//NewFakeNetlinkHandle will create a new FakeNetlinkHandle +// NewFakeNetlinkHandle will create a new FakeNetlinkHandle func NewFakeNetlinkHandle() *FakeNetlinkHandle { - return &FakeNetlinkHandle{} + fake := &FakeNetlinkHandle{ + localAddresses: make(map[string][]string), + } + return fake } -//EnsureAddressBind is a mock implementation +// EnsureAddressBind is a mock implementation func (h *FakeNetlinkHandle) EnsureAddressBind(address, devName string) (exist bool, err error) { return false, nil } -//UnbindAddress is a mock implementation +// UnbindAddress is a mock implementation func (h *FakeNetlinkHandle) UnbindAddress(address, devName string) error { return nil } + +// EnsureDummyDevice is a mock implementation +func (h *FakeNetlinkHandle) EnsureDummyDevice(devName string) (bool, error) { + return false, nil +} + +// DeleteDummyDevice is a mock implementation +func (h *FakeNetlinkHandle) DeleteDummyDevice(devName string) error { + return nil +} + +// GetLocalAddresses is a mock implementation +func (h *FakeNetlinkHandle) GetLocalAddresses(filterDev string) (sets.String, error) { + res := sets.NewString() + if len(filterDev) != 0 { + // list all addresses from a given network interface. + for _, addr := range h.localAddresses[filterDev] { + res.Insert(addr) + } + return res, nil + } + // If filterDev is not given, will list all addresses from all available network interface. + for linkName := range h.localAddresses { + // list all addresses from a given network interface. + for _, addr := range h.localAddresses[linkName] { + res.Insert(addr) + } + } + return res, nil +} + +// SetLocalAddresses set IP addresses to the given interface device. It's not part of interface. +func (h *FakeNetlinkHandle) SetLocalAddresses(dev string, ips ...string) error { + if h.localAddresses == nil { + h.localAddresses = make(map[string][]string) + } + if len(dev) == 0 { + return fmt.Errorf("device name can't be empty") + } + h.localAddresses[dev] = make([]string, 0) + h.localAddresses[dev] = append(h.localAddresses[dev], ips...) + return nil +} diff --git a/pkg/proxy/ipvs/testing/fake_test.go b/pkg/proxy/ipvs/testing/fake_test.go new file mode 100644 index 00000000000..fabc30a584b --- /dev/null +++ b/pkg/proxy/ipvs/testing/fake_test.go @@ -0,0 +1,49 @@ +/* +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 testing + +import ( + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/util/sets" +) + +func TestSetGetLocalAddresses(t *testing.T) { + fake := NewFakeNetlinkHandle() + fake.SetLocalAddresses("eth0", "1.2.3.4") + expected := sets.NewString("1.2.3.4") + addr, _ := fake.GetLocalAddresses("eth0") + if !reflect.DeepEqual(expected, addr) { + t.Errorf("Unexpected mismatch, expected: %v, got: %v", expected, addr) + } + list, _ := fake.GetLocalAddresses("") + if !reflect.DeepEqual(expected, list) { + t.Errorf("Unexpected mismatch, expected: %v, got: %v", expected, list) + } + fake.SetLocalAddresses("lo", "127.0.0.1") + expected = sets.NewString("127.0.0.1") + addr, _ = fake.GetLocalAddresses("lo") + if !reflect.DeepEqual(expected, addr) { + t.Errorf("Unexpected mismatch, expected: %v, got: %v", expected, addr) + } + list, _ = fake.GetLocalAddresses("") + expected = sets.NewString("1.2.3.4", "127.0.0.1") + if !reflect.DeepEqual(expected, list) { + t.Errorf("Unexpected mismatch, expected: %v, got: %v", expected, list) + } +} diff --git a/pkg/proxy/metrics/BUILD b/pkg/proxy/metrics/BUILD new file mode 100644 index 00000000000..f3166915150 --- /dev/null +++ b/pkg/proxy/metrics/BUILD @@ -0,0 +1,23 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["metrics.go"], + importpath = "k8s.io/kubernetes/pkg/proxy/metrics", + visibility = ["//visibility:public"], + deps = ["//vendor/github.com/prometheus/client_golang/prometheus:go_default_library"], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/proxy/metrics/metrics.go b/pkg/proxy/metrics/metrics.go new file mode 100644 index 00000000000..d442f2da320 --- /dev/null +++ b/pkg/proxy/metrics/metrics.go @@ -0,0 +1,52 @@ +/* +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 metrics + +import ( + "sync" + "time" + + "github.com/prometheus/client_golang/prometheus" +) + +const kubeProxySubsystem = "kubeproxy" + +var ( + // SyncProxyRulesLatency is the latency of one round of kube-proxy syncing proxy rules. + SyncProxyRulesLatency = prometheus.NewHistogram( + prometheus.HistogramOpts{ + Subsystem: kubeProxySubsystem, + Name: "sync_proxy_rules_latency_microseconds", + Help: "SyncProxyRules latency", + Buckets: prometheus.ExponentialBuckets(1000, 2, 15), + }, + ) +) + +var registerMetricsOnce sync.Once + +// RegisterMetrics registers sync proxy rules latency metrics +func RegisterMetrics() { + registerMetricsOnce.Do(func() { + prometheus.MustRegister(SyncProxyRulesLatency) + }) +} + +// SinceInMicroseconds gets the time since the specified start in microseconds. +func SinceInMicroseconds(start time.Time) float64 { + return float64(time.Since(start).Nanoseconds() / time.Microsecond.Nanoseconds()) +} diff --git a/pkg/proxy/userspace/BUILD b/pkg/proxy/userspace/BUILD index 7e204340fc5..dc03a2daa02 100644 --- a/pkg/proxy/userspace/BUILD +++ b/pkg/proxy/userspace/BUILD @@ -13,25 +13,53 @@ go_library( "port_allocator.go", "proxier.go", "proxysocket.go", - "rlimit.go", "roundrobin.go", "udp_server.go", ] + select({ - "@io_bazel_rules_go//go/platform:windows_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "rlimit.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "rlimit.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "rlimit.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "rlimit.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "rlimit.go", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "rlimit.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "rlimit.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "rlimit.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "rlimit.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "rlimit.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ "rlimit_windows.go", ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/pkg/proxy/userspace", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", "//pkg/proxy:go_default_library", "//pkg/proxy/util:go_default_library", "//pkg/util/iptables:go_default_library", "//pkg/util/slice:go_default_library", "//vendor/github.com/golang/glog:go_default_library", - "//vendor/golang.org/x/sys/unix:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", @@ -39,7 +67,39 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library", - ], + ] + select({ + "@io_bazel_rules_go//go/platform:android": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "//conditions:default": [], + }), ) go_test( @@ -49,10 +109,10 @@ go_test( "proxier_test.go", "roundrobin_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/proxy/userspace", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/proxy:go_default_library", "//pkg/util/iptables/testing:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/proxy/userspace/loadbalancer.go b/pkg/proxy/userspace/loadbalancer.go index 369094e3296..ebaeb9f4db0 100644 --- a/pkg/proxy/userspace/loadbalancer.go +++ b/pkg/proxy/userspace/loadbalancer.go @@ -17,10 +17,9 @@ limitations under the License. package userspace import ( - "net" - - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/proxy" + "net" ) // LoadBalancer is an interface for distributing incoming requests to service endpoints. diff --git a/pkg/proxy/userspace/proxier.go b/pkg/proxy/userspace/proxier.go index 5712236cec7..8c4f622e115 100644 --- a/pkg/proxy/userspace/proxier.go +++ b/pkg/proxy/userspace/proxier.go @@ -28,8 +28,8 @@ import ( "github.com/golang/glog" "k8s.io/apimachinery/pkg/types" utilnet "k8s.io/apimachinery/pkg/util/net" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" "k8s.io/kubernetes/pkg/proxy" utilerrors "k8s.io/apimachinery/pkg/util/errors" @@ -503,7 +503,7 @@ func (proxier *Proxier) unmergeService(service *api.Service, existingPorts sets. } proxier.loadBalancer.DeleteService(serviceName) } - for _, svcIP := range staleUDPServices.List() { + for _, svcIP := range staleUDPServices.UnsortedList() { if err := utilproxy.ClearUDPConntrackForIP(proxier.exec, svcIP); err != nil { glog.Errorf("Failed to delete stale service IP %s connections, error: %v", svcIP, err) } diff --git a/pkg/proxy/userspace/proxier_test.go b/pkg/proxy/userspace/proxier_test.go index d3699a0a7ef..ef55b1ba07d 100644 --- a/pkg/proxy/userspace/proxier_test.go +++ b/pkg/proxy/userspace/proxier_test.go @@ -32,7 +32,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/proxy" ipttest "k8s.io/kubernetes/pkg/util/iptables/testing" "k8s.io/utils/exec" diff --git a/pkg/proxy/userspace/proxysocket.go b/pkg/proxy/userspace/proxysocket.go index a28b6011b6c..d65ac02a0cc 100644 --- a/pkg/proxy/userspace/proxysocket.go +++ b/pkg/proxy/userspace/proxysocket.go @@ -27,7 +27,7 @@ import ( "github.com/golang/glog" "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/proxy" ) diff --git a/pkg/proxy/userspace/roundrobin.go b/pkg/proxy/userspace/roundrobin.go index b32d69d8776..e2aca299133 100644 --- a/pkg/proxy/userspace/roundrobin.go +++ b/pkg/proxy/userspace/roundrobin.go @@ -27,7 +27,7 @@ import ( "github.com/golang/glog" "k8s.io/apimachinery/pkg/types" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/proxy" "k8s.io/kubernetes/pkg/util/slice" ) diff --git a/pkg/proxy/userspace/roundrobin_test.go b/pkg/proxy/userspace/roundrobin_test.go index 05ccbd37a81..8da691a6eaf 100644 --- a/pkg/proxy/userspace/roundrobin_test.go +++ b/pkg/proxy/userspace/roundrobin_test.go @@ -22,7 +22,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/proxy" ) diff --git a/pkg/proxy/util/BUILD b/pkg/proxy/util/BUILD index 1da2c3ad0f9..5d92739ec2d 100644 --- a/pkg/proxy/util/BUILD +++ b/pkg/proxy/util/BUILD @@ -11,8 +11,8 @@ go_library( importpath = "k8s.io/kubernetes/pkg/proxy/util", visibility = ["//visibility:public"], deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library", @@ -27,10 +27,10 @@ go_test( "port_test.go", "utils_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/proxy/util", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library", diff --git a/pkg/proxy/util/conntrack.go b/pkg/proxy/util/conntrack.go index 504085a5ae2..d99d61b5dbd 100644 --- a/pkg/proxy/util/conntrack.go +++ b/pkg/proxy/util/conntrack.go @@ -27,13 +27,17 @@ import ( // Utilities for dealing with conntrack -const noConnectionToDelete = "0 flow entries have been deleted" +const NoConnectionToDelete = "0 flow entries have been deleted" -func isIPv6(ip string) bool { - netIP := net.ParseIP(ip) +func IsIPv6(netIP net.IP) bool { return netIP != nil && netIP.To4() == nil } +func IsIPv6String(ip string) bool { + netIP := net.ParseIP(ip) + return IsIPv6(netIP) +} + func parametersWithFamily(isIPv6 bool, parameters ...string) []string { if isIPv6 { parameters = append(parameters, "-f", "ipv6") @@ -44,9 +48,9 @@ func parametersWithFamily(isIPv6 bool, parameters ...string) []string { // ClearUDPConntrackForIP uses the conntrack tool to delete the conntrack entries // for the UDP connections specified by the given service IP func ClearUDPConntrackForIP(execer exec.Interface, ip string) error { - parameters := parametersWithFamily(isIPv6(ip), "-D", "--orig-dst", ip, "-p", "udp") + parameters := parametersWithFamily(IsIPv6String(ip), "-D", "--orig-dst", ip, "-p", "udp") err := ExecConntrackTool(execer, parameters...) - if err != nil && !strings.Contains(err.Error(), noConnectionToDelete) { + if err != nil && !strings.Contains(err.Error(), NoConnectionToDelete) { // TODO: Better handling for deletion failure. When failure occur, stale udp connection may not get flushed. // These stale udp connection will keep black hole traffic. Making this a best effort operation for now, since it // is expensive to baby-sit all udp connections to kubernetes services. @@ -80,7 +84,7 @@ func ClearUDPConntrackForPort(execer exec.Interface, port int, isIPv6 bool) erro } parameters := parametersWithFamily(isIPv6, "-D", "-p", "udp", "--dport", strconv.Itoa(port)) err := ExecConntrackTool(execer, parameters...) - if err != nil && !strings.Contains(err.Error(), noConnectionToDelete) { + if err != nil && !strings.Contains(err.Error(), NoConnectionToDelete) { return fmt.Errorf("error deleting conntrack entries for UDP port: %d, error: %v", port, err) } return nil @@ -89,9 +93,9 @@ func ClearUDPConntrackForPort(execer exec.Interface, port int, isIPv6 bool) erro // ClearUDPConntrackForPeers uses the conntrack tool to delete the conntrack entries // for the UDP connections specified by the {origin, dest} IP pair. func ClearUDPConntrackForPeers(execer exec.Interface, origin, dest string) error { - parameters := parametersWithFamily(isIPv6(origin), "-D", "--orig-dst", origin, "--dst-nat", dest, "-p", "udp") + parameters := parametersWithFamily(IsIPv6String(origin), "-D", "--orig-dst", origin, "--dst-nat", dest, "-p", "udp") err := ExecConntrackTool(execer, parameters...) - if err != nil && !strings.Contains(err.Error(), noConnectionToDelete) { + if err != nil && !strings.Contains(err.Error(), NoConnectionToDelete) { // TODO: Better handling for deletion failure. When failure occur, stale udp connection may not get flushed. // These stale udp connection will keep black hole traffic. Making this a best effort operation for now, since it // is expensive to baby sit all udp connections to kubernetes services. diff --git a/pkg/proxy/util/conntrack_test.go b/pkg/proxy/util/conntrack_test.go index 6c0db300029..f3d8a145269 100644 --- a/pkg/proxy/util/conntrack_test.go +++ b/pkg/proxy/util/conntrack_test.go @@ -18,6 +18,7 @@ package util import ( "fmt" + "net" "strings" "testing" @@ -117,7 +118,7 @@ func TestClearUDPConntrackForIP(t *testing.T) { if err := ClearUDPConntrackForIP(&fexec, tc.ip); err != nil { t.Errorf("%s test case:, Unexpected error: %v", tc.name, err) } - expectCommand := fmt.Sprintf("conntrack -D --orig-dst %s -p udp", tc.ip) + familyParamStr(isIPv6(tc.ip)) + expectCommand := fmt.Sprintf("conntrack -D --orig-dst %s -p udp", tc.ip) + familyParamStr(IsIPv6String(tc.ip)) execCommand := strings.Join(fcmd.CombinedOutputLog[svcCount], " ") if expectCommand != execCommand { t.Errorf("%s test case: Expect command: %s, but executed %s", tc.name, expectCommand, execCommand) @@ -221,7 +222,7 @@ func TestDeleteUDPConnections(t *testing.T) { if err != nil { t.Errorf("%s test case: unexpected error: %v", tc.name, err) } - expectCommand := fmt.Sprintf("conntrack -D --orig-dst %s --dst-nat %s -p udp", tc.origin, tc.dest) + familyParamStr(isIPv6(tc.origin)) + expectCommand := fmt.Sprintf("conntrack -D --orig-dst %s --dst-nat %s -p udp", tc.origin, tc.dest) + familyParamStr(IsIPv6String(tc.origin)) execCommand := strings.Join(fcmd.CombinedOutputLog[i], " ") if expectCommand != execCommand { t.Errorf("%s test case: Expect command: %s, but executed %s", tc.name, expectCommand, execCommand) @@ -232,3 +233,99 @@ func TestDeleteUDPConnections(t *testing.T) { t.Errorf("Expect command executed %d times, but got %d", svcCount, fexec.CommandCalls) } } + +func TestIsIPv6String(t *testing.T) { + testCases := []struct { + ip string + expectIPv6 bool + }{ + { + ip: "127.0.0.1", + expectIPv6: false, + }, + { + ip: "192.168.0.0", + expectIPv6: false, + }, + { + ip: "1.2.3.4", + expectIPv6: false, + }, + { + ip: "bad ip", + expectIPv6: false, + }, + { + ip: "::1", + expectIPv6: true, + }, + { + ip: "fd00::600d:f00d", + expectIPv6: true, + }, + { + ip: "2001:db8::5", + expectIPv6: true, + }, + } + for i := range testCases { + isIPv6 := IsIPv6String(testCases[i].ip) + if isIPv6 != testCases[i].expectIPv6 { + t.Errorf("[%d] Expect ipv6 %v, got %v", i+1, testCases[i].expectIPv6, isIPv6) + } + } +} + +func TestIsIPv6(t *testing.T) { + testCases := []struct { + ip net.IP + expectIPv6 bool + }{ + { + ip: net.IPv4zero, + expectIPv6: false, + }, + { + ip: net.IPv4bcast, + expectIPv6: false, + }, + { + ip: net.ParseIP("127.0.0.1"), + expectIPv6: false, + }, + { + ip: net.ParseIP("10.20.40.40"), + expectIPv6: false, + }, + { + ip: net.ParseIP("172.17.3.0"), + expectIPv6: false, + }, + { + ip: nil, + expectIPv6: false, + }, + { + ip: net.IPv6loopback, + expectIPv6: true, + }, + { + ip: net.IPv6zero, + expectIPv6: true, + }, + { + ip: net.ParseIP("fd00::600d:f00d"), + expectIPv6: true, + }, + { + ip: net.ParseIP("2001:db8::5"), + expectIPv6: true, + }, + } + for i := range testCases { + isIPv6 := IsIPv6(testCases[i].ip) + if isIPv6 != testCases[i].expectIPv6 { + t.Errorf("[%d] Expect ipv6 %v, got %v", i+1, testCases[i].expectIPv6, isIPv6) + } + } +} diff --git a/pkg/proxy/util/endpoints.go b/pkg/proxy/util/endpoints.go index 32e770d4f94..449e112619d 100644 --- a/pkg/proxy/util/endpoints.go +++ b/pkg/proxy/util/endpoints.go @@ -19,6 +19,7 @@ package util import ( "fmt" "net" + "strconv" "github.com/golang/glog" ) @@ -32,12 +33,33 @@ func IPPart(s string) string { return s } // Must be IP:port - ip, _, err := net.SplitHostPort(s) + host, _, err := net.SplitHostPort(s) if err != nil { glog.Errorf("Error parsing '%s': %v", s, err) return "" } - return ip + // Check if host string is a valid IP address + if ip := net.ParseIP(host); ip != nil { + return ip.String() + } else { + glog.Errorf("invalid IP part '%s'", host) + } + return "" +} + +func PortPart(s string) (int, error) { + // Must be IP:port + _, port, err := net.SplitHostPort(s) + if err != nil { + glog.Errorf("Error parsing '%s': %v", s, err) + return -1, err + } + portNumber, err := strconv.Atoi(port) + if err != nil { + glog.Errorf("Error parsing '%s': %v", port, err) + return -1, err + } + return portNumber, nil } // ToCIDR returns a host address of the form /32 for diff --git a/pkg/proxy/util/endpoints_test.go b/pkg/proxy/util/endpoints_test.go index 618f59e96a8..bb28cbd714a 100644 --- a/pkg/proxy/util/endpoints_test.go +++ b/pkg/proxy/util/endpoints_test.go @@ -35,6 +35,7 @@ func TestIPPart(t *testing.T) { {"[2001:db8::2:2]:9999", "2001:db8::2:2", noError}, {"1.2.3.4::9999", "", "too many colons"}, {"1.2.3.4:[0]", "", "unexpected '[' in address"}, + {"1.2.3:8080", "", "invalid ip part"}, } for _, tc := range testCases { diff --git a/pkg/proxy/util/utils.go b/pkg/proxy/util/utils.go index 81734f54892..cac0140c386 100644 --- a/pkg/proxy/util/utils.go +++ b/pkg/proxy/util/utils.go @@ -20,8 +20,8 @@ import ( "net" "k8s.io/apimachinery/pkg/types" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" "github.com/golang/glog" ) diff --git a/pkg/proxy/util/utils_test.go b/pkg/proxy/util/utils_test.go index b57850ae5f9..5810788714c 100644 --- a/pkg/proxy/util/utils_test.go +++ b/pkg/proxy/util/utils_test.go @@ -21,7 +21,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestShouldSkipService(t *testing.T) { diff --git a/pkg/proxy/winkernel/BUILD b/pkg/proxy/winkernel/BUILD index d1ee2573039..427371d9331 100644 --- a/pkg/proxy/winkernel/BUILD +++ b/pkg/proxy/winkernel/BUILD @@ -1,11 +1,11 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") +load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ "metrics.go", ] + select({ - "@io_bazel_rules_go//go/platform:windows_amd64": [ + "@io_bazel_rules_go//go/platform:windows": [ "proxier.go", ], "//conditions:default": [], @@ -15,10 +15,10 @@ go_library( deps = [ "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", ] + select({ - "@io_bazel_rules_go//go/platform:windows_amd64": [ - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", + "@io_bazel_rules_go//go/platform:windows": [ "//pkg/api/service:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", "//pkg/features:go_default_library", "//pkg/proxy:go_default_library", "//pkg/proxy/healthcheck:go_default_library", @@ -36,33 +36,6 @@ go_library( }), ) -go_test( - name = "go_default_test", - srcs = select({ - "@io_bazel_rules_go//go/platform:windows_amd64": [ - "proxier_test.go", - ], - "//conditions:default": [], - }), - importpath = "k8s.io/kubernetes/pkg/proxy/winkernel", - library = ":go_default_library", - deps = select({ - "@io_bazel_rules_go//go/platform:windows_amd64": [ - "//pkg/api:go_default_library", - "//pkg/proxy:go_default_library", - "//pkg/util/async:go_default_library", - "//vendor/github.com/davecgh/go-spew/spew:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - "//vendor/k8s.io/utils/exec:go_default_library", - "//vendor/k8s.io/utils/exec/testing:go_default_library", - ], - "//conditions:default": [], - }), -) - filegroup( name = "package-srcs", srcs = glob(["**"]), diff --git a/pkg/proxy/winkernel/OWNERS b/pkg/proxy/winkernel/OWNERS new file mode 100644 index 00000000000..605001e0a11 --- /dev/null +++ b/pkg/proxy/winkernel/OWNERS @@ -0,0 +1,3 @@ +reviewers: +- dineshgovindasamy +- madhanrm diff --git a/pkg/proxy/winkernel/proxier.go b/pkg/proxy/winkernel/proxier.go index d1f3b55182e..5d56561c422 100644 --- a/pkg/proxy/winkernel/proxier.go +++ b/pkg/proxy/winkernel/proxier.go @@ -1,7 +1,7 @@ // +build windows /* -Copyright 2015 The Kubernetes Authors. +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. @@ -37,9 +37,9 @@ import ( "k8s.io/apimachinery/pkg/util/wait" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/tools/record" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper" apiservice "k8s.io/kubernetes/pkg/api/service" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/proxy" "k8s.io/kubernetes/pkg/proxy/healthcheck" @@ -93,7 +93,7 @@ type serviceInfo struct { targetPort int loadBalancerStatus api.LoadBalancerStatus sessionAffinityType api.ServiceAffinity - stickyMaxAgeMinutes int + stickyMaxAgeSeconds int externalIPs []*externalIPInfo loadBalancerIngressIPs []*loadBalancerIngressInfo loadBalancerSourceRanges []string @@ -165,6 +165,12 @@ func newServiceInfo(svcPortName proxy.ServicePortName, port *api.ServicePort, se onlyNodeLocalEndpoints = true } + // set default session sticky max age 180min=10800s + stickyMaxAgeSeconds := 10800 + if service.Spec.SessionAffinity == api.ServiceAffinityClientIP { + // Kube-apiserver side guarantees SessionAffinityConfig won't be nil when session affinity type is ClientIP + stickyMaxAgeSeconds = int(*service.Spec.SessionAffinityConfig.ClientIP.TimeoutSeconds) + } info := &serviceInfo{ clusterIP: net.ParseIP(service.Spec.ClusterIP), port: int(port.Port), @@ -174,7 +180,7 @@ func newServiceInfo(svcPortName proxy.ServicePortName, port *api.ServicePort, se // Deep-copy in case the service instance changes loadBalancerStatus: *helper.LoadBalancerStatusDeepCopy(&service.Status.LoadBalancer), sessionAffinityType: service.Spec.SessionAffinity, - stickyMaxAgeMinutes: 180, // TODO: paramaterize this in the API. + stickyMaxAgeSeconds: stickyMaxAgeSeconds, loadBalancerSourceRanges: make([]string, len(service.Spec.LoadBalancerSourceRanges)), onlyNodeLocalEndpoints: onlyNodeLocalEndpoints, } @@ -447,15 +453,6 @@ func NewProxier( recorder record.EventRecorder, healthzServer healthcheck.HealthzUpdater, ) (*Proxier, error) { - // check valid user input - if minSyncPeriod > syncPeriod { - return nil, fmt.Errorf("min-sync (%v) must be < sync(%v)", minSyncPeriod, syncPeriod) - } - - // Generate the masquerade mark to use for SNAT rules. - if masqueradeBit < 0 || masqueradeBit > 31 { - return nil, fmt.Errorf("invalid iptables-masquerade-bit %v not in [0, 31]", masqueradeBit) - } masqueradeValue := 1 << uint(masqueradeBit) masqueradeMark := fmt.Sprintf("%#08x/%#08x", masqueradeValue, masqueradeValue) @@ -925,8 +922,7 @@ func serviceToServiceMap(service *api.Service) proxyServiceMap { return serviceMap } -// This is where all of the hns -save/restore calls happen. -// The only other hns rules are those that are setup in iptablesInit() +// This is where all of the hns save/restore calls happen. // assumes proxier.mu is held func (proxier *Proxier) syncProxyRules() { proxier.mu.Lock() @@ -970,53 +966,52 @@ func (proxier *Proxier) syncProxyRules() { var hnsEndpoints []hcsshim.HNSEndpoint glog.V(4).Infof("====Applying Policy for %s====", svcName) // Create Remote endpoints for every endpoint, corresponding to the service - if len(proxier.endpointsMap[svcName]) > 0 { - for _, ep := range proxier.endpointsMap[svcName] { - var newHnsEndpoint *hcsshim.HNSEndpoint - hnsNetworkName := proxier.network.name - var err error - if len(ep.hnsID) > 0 { - newHnsEndpoint, err = hcsshim.GetHNSEndpointByID(ep.hnsID) - } - if newHnsEndpoint == nil { - // First check if an endpoint resource exists for this IP, on the current host - // A Local endpoint could exist here already - // A remote endpoint was already created and proxy was restarted - newHnsEndpoint, err = getHnsEndpointByIpAddress(net.ParseIP(ep.ip), hnsNetworkName) - } - - if newHnsEndpoint == nil { - if ep.isLocal { - glog.Errorf("Local endpoint not found for %v: err : %v on network %s", ep.ip, err, hnsNetworkName) - continue - } - // hns Endpoint resource was not found, create one - hnsnetwork, err := hcsshim.GetHNSNetworkByName(hnsNetworkName) - if err != nil { - glog.Errorf("%v", err) - continue - } - - hnsEndpoint := &hcsshim.HNSEndpoint{ - MacAddress: ep.macAddress, - IPAddress: net.ParseIP(ep.ip), - } - - newHnsEndpoint, err = hnsnetwork.CreateRemoteEndpoint(hnsEndpoint) - if err != nil { - glog.Errorf("Remote endpoint creation failed: %v", err) - continue - } - } - - // Save the hnsId for reference - LogJson(newHnsEndpoint, "Hns Endpoint resource", 1) - hnsEndpoints = append(hnsEndpoints, *newHnsEndpoint) - ep.hnsID = newHnsEndpoint.Id - ep.refCount++ - Log(ep, "Endpoint resource found", 3) + for _, ep := range proxier.endpointsMap[svcName] { + var newHnsEndpoint *hcsshim.HNSEndpoint + hnsNetworkName := proxier.network.name + var err error + if len(ep.hnsID) > 0 { + newHnsEndpoint, err = hcsshim.GetHNSEndpointByID(ep.hnsID) } + + if newHnsEndpoint == nil { + // First check if an endpoint resource exists for this IP, on the current host + // A Local endpoint could exist here already + // A remote endpoint was already created and proxy was restarted + newHnsEndpoint, err = getHnsEndpointByIpAddress(net.ParseIP(ep.ip), hnsNetworkName) + } + + if newHnsEndpoint == nil { + if ep.isLocal { + glog.Errorf("Local endpoint not found for %v: err: %v on network %s", ep.ip, err, hnsNetworkName) + continue + } + // hns Endpoint resource was not found, create one + hnsnetwork, err := hcsshim.GetHNSNetworkByName(hnsNetworkName) + if err != nil { + glog.Errorf("%v", err) + continue + } + + hnsEndpoint := &hcsshim.HNSEndpoint{ + MacAddress: ep.macAddress, + IPAddress: net.ParseIP(ep.ip), + } + + newHnsEndpoint, err = hnsnetwork.CreateRemoteEndpoint(hnsEndpoint) + if err != nil { + glog.Errorf("Remote endpoint creation failed: %v", err) + continue + } + } + + // Save the hnsId for reference + LogJson(newHnsEndpoint, "Hns Endpoint resource", 1) + hnsEndpoints = append(hnsEndpoints, *newHnsEndpoint) + ep.hnsID = newHnsEndpoint.Id + ep.refCount++ + Log(ep, "Endpoint resource found", 3) } glog.V(3).Infof("Associated endpoints [%s] for service [%s]", spew.Sdump(hnsEndpoints), svcName) @@ -1039,8 +1034,8 @@ func (proxier *Proxier) syncProxyRules() { false, svcInfo.clusterIP.String(), Enum(svcInfo.protocol), - uint16(svcInfo.port), uint16(svcInfo.targetPort), + uint16(svcInfo.port), ) if err != nil { glog.Errorf("Policy creation failed: %v", err) @@ -1050,7 +1045,7 @@ func (proxier *Proxier) syncProxyRules() { svcInfo.hnsID = hnsLoadBalancer.ID glog.V(3).Infof("Hns LoadBalancer resource created for cluster ip resources %v, Id [%s]", svcInfo.clusterIP, hnsLoadBalancer.ID) - // If nodePort is speficied, user should be able to use nodeIP:nodePort to reach the backend endpoints + // If nodePort is specified, user should be able to use nodeIP:nodePort to reach the backend endpoints if svcInfo.nodePort > 0 { hnsLoadBalancer, err := getHnsLoadBalancer( hnsEndpoints, @@ -1077,8 +1072,8 @@ func (proxier *Proxier) syncProxyRules() { false, externalIp.ip, Enum(svcInfo.protocol), - uint16(svcInfo.port), uint16(svcInfo.targetPort), + uint16(svcInfo.port), ) if err != nil { glog.Errorf("Policy creation failed: %v", err) @@ -1095,8 +1090,8 @@ func (proxier *Proxier) syncProxyRules() { false, lbIngressIp.ip, Enum(svcInfo.protocol), - uint16(svcInfo.port), uint16(svcInfo.targetPort), + uint16(svcInfo.port), ) if err != nil { glog.Errorf("Policy creation failed: %v", err) @@ -1126,7 +1121,7 @@ func (proxier *Proxier) syncProxyRules() { // Finish housekeeping. // TODO: these could be made more consistent. - for _, svcIP := range staleServices.List() { + for _, svcIP := range staleServices.UnsortedList() { // TODO : Check if this is required to cleanup stale services here glog.V(5).Infof("Pending delete stale service IP %s connections", svcIP) } diff --git a/pkg/proxy/winkernel/proxier_test.go b/pkg/proxy/winkernel/proxier_test.go deleted file mode 100644 index 24987bc7b5d..00000000000 --- a/pkg/proxy/winkernel/proxier_test.go +++ /dev/null @@ -1,2031 +0,0 @@ -// +build windows - -/* -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 winkernel - -import ( - "reflect" - "testing" - "time" - - "github.com/davecgh/go-spew/spew" - - "fmt" - "net" - "strings" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/proxy" - "k8s.io/kubernetes/pkg/util/async" - "k8s.io/utils/exec" - fakeexec "k8s.io/utils/exec/testing" -) - -func newFakeServiceInfo(service proxy.ServicePortName, ip net.IP, port int, protocol api.Protocol, onlyNodeLocalEndpoints bool) *serviceInfo { - return &serviceInfo{ - sessionAffinityType: api.ServiceAffinityNone, // default - stickyMaxAgeMinutes: 180, - clusterIP: ip, - port: port, - protocol: protocol, - onlyNodeLocalEndpoints: onlyNodeLocalEndpoints, - } -} - -func TestDeleteEndpointConnections(t *testing.T) { - fcmd := fakeexec.FakeCmd{ - CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ - func() ([]byte, error) { return []byte("1 flow entries have been deleted"), nil }, - func() ([]byte, error) { - return []byte(""), fmt.Errorf("conntrack v1.4.2 (conntrack-tools): 0 flow entries have been deleted.") - }, - }, - } - fexec := fakeexec.FakeExec{ - CommandScript: []fakeexec.FakeCommandAction{ - func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, - func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, - }, - LookPathFunc: func(cmd string) (string, error) { return cmd, nil }, - } - - serviceMap := make(map[proxy.ServicePortName]*serviceInfo) - svc1 := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "ns1", Name: "svc1"}, Port: "p80"} - svc2 := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "ns1", Name: "svc2"}, Port: "p80"} - serviceMap[svc1] = newFakeServiceInfo(svc1, net.IPv4(10, 20, 30, 40), 80, api.ProtocolUDP, false) - serviceMap[svc2] = newFakeServiceInfo(svc1, net.IPv4(10, 20, 30, 41), 80, api.ProtocolTCP, false) - - fakeProxier := Proxier{exec: &fexec, serviceMap: serviceMap} - - testCases := []endpointServicePair{ - { - endpoint: "10.240.0.3:80", - servicePortName: svc1, - }, - { - endpoint: "10.240.0.4:80", - servicePortName: svc1, - }, - { - endpoint: "10.240.0.5:80", - servicePortName: svc2, - }, - } - - expectCommandExecCount := 0 - for i := range testCases { - input := map[endpointServicePair]bool{testCases[i]: true} - fakeProxier.deleteEndpointConnections(input) - svcInfo := fakeProxier.serviceMap[testCases[i].servicePortName] - if svcInfo.protocol == api.ProtocolUDP { - svcIp := svcInfo.clusterIP.String() - endpointIp := strings.Split(testCases[i].endpoint, ":")[0] - expectCommand := fmt.Sprintf("conntrack -D --orig-dst %s --dst-nat %s -p udp", svcIp, endpointIp) - execCommand := strings.Join(fcmd.CombinedOutputLog[expectCommandExecCount], " ") - if expectCommand != execCommand { - t.Errorf("Exepect comand: %s, but executed %s", expectCommand, execCommand) - } - expectCommandExecCount += 1 - } - - if expectCommandExecCount != fexec.CommandCalls { - t.Errorf("Exepect comand executed %d times, but got %d", expectCommandExecCount, fexec.CommandCalls) - } - } -} - -type fakeClosable struct { - closed bool -} - -func (c *fakeClosable) Close() error { - c.closed = true - return nil -} - -func TestRevertPorts(t *testing.T) { - testCases := []struct { - replacementPorts []localPort - existingPorts []localPort - expectToBeClose []bool - }{ - { - replacementPorts: []localPort{ - {port: 5001}, - {port: 5002}, - {port: 5003}, - }, - existingPorts: []localPort{}, - expectToBeClose: []bool{true, true, true}, - }, - { - replacementPorts: []localPort{}, - existingPorts: []localPort{ - {port: 5001}, - {port: 5002}, - {port: 5003}, - }, - expectToBeClose: []bool{}, - }, - { - replacementPorts: []localPort{ - {port: 5001}, - {port: 5002}, - {port: 5003}, - }, - existingPorts: []localPort{ - {port: 5001}, - {port: 5002}, - {port: 5003}, - }, - expectToBeClose: []bool{false, false, false}, - }, - { - replacementPorts: []localPort{ - {port: 5001}, - {port: 5002}, - {port: 5003}, - }, - existingPorts: []localPort{ - {port: 5001}, - {port: 5003}, - }, - expectToBeClose: []bool{false, true, false}, - }, - { - replacementPorts: []localPort{ - {port: 5001}, - {port: 5002}, - {port: 5003}, - }, - existingPorts: []localPort{ - {port: 5001}, - {port: 5002}, - {port: 5003}, - {port: 5004}, - }, - expectToBeClose: []bool{false, false, false}, - }, - } - - for i, tc := range testCases { - replacementPortsMap := make(map[localPort]closeable) - for _, lp := range tc.replacementPorts { - replacementPortsMap[lp] = &fakeClosable{} - } - existingPortsMap := make(map[localPort]closeable) - for _, lp := range tc.existingPorts { - existingPortsMap[lp] = &fakeClosable{} - } - revertPorts(replacementPortsMap, existingPortsMap) - for j, expectation := range tc.expectToBeClose { - if replacementPortsMap[tc.replacementPorts[j]].(*fakeClosable).closed != expectation { - t.Errorf("Expect replacement localport %v to be %v in test case %v", tc.replacementPorts[j], expectation, i) - } - } - for _, lp := range tc.existingPorts { - if existingPortsMap[lp].(*fakeClosable).closed == true { - t.Errorf("Expect existing localport %v to be false in test case %v", lp, i) - } - } - } - -} - -// fakePortOpener implements portOpener. -type fakePortOpener struct { - openPorts []*localPort -} - -// OpenLocalPort fakes out the listen() and bind() used by syncProxyRules -// to lock a local port. -func (f *fakePortOpener) OpenLocalPort(lp *localPort) (closeable, error) { - f.openPorts = append(f.openPorts, lp) - return nil, nil -} - -type fakeHealthChecker struct { - services map[types.NamespacedName]uint16 - endpoints map[types.NamespacedName]int -} - -func newFakeHealthChecker() *fakeHealthChecker { - return &fakeHealthChecker{ - services: map[types.NamespacedName]uint16{}, - endpoints: map[types.NamespacedName]int{}, - } -} - -func (fake *fakeHealthChecker) SyncServices(newServices map[types.NamespacedName]uint16) error { - fake.services = newServices - return nil -} - -func (fake *fakeHealthChecker) SyncEndpoints(newEndpoints map[types.NamespacedName]int) error { - fake.endpoints = newEndpoints - return nil -} - -func getFakeHnsNetwork() *hnsNetworkInfo { - return &hnsNetworkInfo{ - id: "00000000-0000-0000-0000-000000000001", - name: "fakeNetwork", - }, nil -} - -const testHostname = "test-hostname" - -func NewFakeProxier() *Proxier { - fakeHnsNetwork := getFakeHnsNetwork() - // TODO: Call NewProxier after refactoring out the goroutine - // invocation into a Run() method. - p := &Proxier{ - serviceMap: make(proxyServiceMap), - serviceChanges: newServiceChangeMap(), - endpointsMap: make(proxyEndpointsMap), - endpointsChanges: newEndpointsChangeMap(testHostname), - clusterCIDR: "10.0.0.0/24", - hostname: testHostname, - portsMap: make(map[localPort]closeable), - healthChecker: newFakeHealthChecker(), - network: fakeHnsNetwork, - } - p.syncRunner = async.NewBoundedFrequencyRunner("test-sync-runner", p.syncProxyRules, 0, time.Minute, 1) - return p -} - -func errorf(msg string, rules []iptablestest.Rule, t *testing.T) { - for _, r := range rules { - t.Logf("%q", r) - } - t.Errorf("%v", msg) -} - -func TestLoadBalancer(t *testing.T) { - fp := NewFakeProxier() - svcIP := "10.20.30.41" - svcPort := 80 - svcNodePort := 3001 - svcLBIP := "1.2.3.4" - svcPortName := proxy.ServicePortName{ - NamespacedName: makeNSN("ns1", "svc1"), - Port: "p80", - } - - makeServiceMap(fp, - makeTestService(svcPortName.Namespace, svcPortName.Name, func(svc *api.Service) { - svc.Spec.Type = "LoadBalancer" - svc.Spec.ClusterIP = svcIP - svc.Spec.Ports = []api.ServicePort{{ - Name: svcPortName.Port, - Port: int32(svcPort), - Protocol: api.ProtocolTCP, - NodePort: int32(svcNodePort), - }} - svc.Status.LoadBalancer.Ingress = []api.LoadBalancerIngress{{ - IP: svcLBIP, - }} - }), - ) - - epIP := "10.180.0.1" - makeEndpointsMap(fp, - makeTestEndpoints(svcPortName.Namespace, svcPortName.Name, func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{{ - Addresses: []api.EndpointAddress{{ - IP: epIP, - }}, - Ports: []api.EndpointPort{{ - Name: svcPortName.Port, - Port: int32(svcPort), - }}, - }} - }), - ) - - fp.syncProxyRules() - - proto := strings.ToLower(string(api.ProtocolTCP)) - fwChain := string(serviceFirewallChainName(svcPortName.String(), proto)) - svcChain := string(servicePortChainName(svcPortName.String(), proto)) - //lbChain := string(serviceLBChainName(svcPortName.String(), proto)) - - // TODO - -} - -func TestNodePort(t *testing.T) { - - fp := NewFakeProxier() - 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 *api.Service) { - svc.Spec.Type = "NodePort" - svc.Spec.ClusterIP = svcIP - svc.Spec.Ports = []api.ServicePort{{ - Name: svcPortName.Port, - Port: int32(svcPort), - Protocol: api.ProtocolTCP, - NodePort: int32(svcNodePort), - }} - }), - ) - - epIP := "10.180.0.1" - makeEndpointsMap(fp, - makeTestEndpoints(svcPortName.Namespace, svcPortName.Name, func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{{ - Addresses: []api.EndpointAddress{{ - IP: epIP, - }}, - Ports: []api.EndpointPort{{ - Name: svcPortName.Port, - Port: int32(svcPort), - }}, - }} - }), - ) - - fp.syncProxyRules() - - proto := strings.ToLower(string(api.ProtocolTCP)) - svcChain := string(servicePortChainName(svcPortName.String(), proto)) - - // TODO -} - -func TestExternalIPsReject(t *testing.T) { - - fp := NewFakeProxier() - svcIP := "10.20.30.41" - svcPort := 80 - svcExternalIPs := "50.60.70.81" - svcPortName := proxy.ServicePortName{ - NamespacedName: makeNSN("ns1", "svc1"), - Port: "p80", - } - - makeServiceMap(fp, - makeTestService(svcPortName.Namespace, svcPortName.Name, func(svc *api.Service) { - svc.Spec.Type = "ClusterIP" - svc.Spec.ClusterIP = svcIP - svc.Spec.ExternalIPs = []string{svcExternalIPs} - svc.Spec.Ports = []api.ServicePort{{ - Name: svcPortName.Port, - Port: int32(svcPort), - Protocol: api.ProtocolTCP, - TargetPort: intstr.FromInt(svcPort), - }} - }), - ) - makeEndpointsMap(fp) - - fp.syncProxyRules() - -} - -func TestNodePortReject(t *testing.T) { - - fp := NewFakeProxier() - 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 *api.Service) { - svc.Spec.Type = "NodePort" - svc.Spec.ClusterIP = svcIP - svc.Spec.Ports = []api.ServicePort{{ - Name: svcPortName.Port, - Port: int32(svcPort), - Protocol: api.ProtocolTCP, - NodePort: int32(svcNodePort), - }} - }), - ) - makeEndpointsMap(fp) - - fp.syncProxyRules() - - // TODO -} - -func strPtr(s string) *string { - return &s -} - -func TestOnlyLocalLoadBalancing(t *testing.T) { - - fp := NewFakeProxier() - svcIP := "10.20.30.41" - svcPort := 80 - svcNodePort := 3001 - svcLBIP := "1.2.3.4" - svcPortName := proxy.ServicePortName{ - NamespacedName: makeNSN("ns1", "svc1"), - Port: "p80", - } - - makeServiceMap(fp, - makeTestService(svcPortName.Namespace, svcPortName.Name, func(svc *api.Service) { - svc.Spec.Type = "LoadBalancer" - svc.Spec.ClusterIP = svcIP - svc.Spec.Ports = []api.ServicePort{{ - Name: svcPortName.Port, - Port: int32(svcPort), - Protocol: api.ProtocolTCP, - NodePort: int32(svcNodePort), - }} - svc.Status.LoadBalancer.Ingress = []api.LoadBalancerIngress{{ - IP: svcLBIP, - }} - svc.Annotations[api.BetaAnnotationExternalTraffic] = api.AnnotationValueExternalTrafficLocal - }), - ) - - epIP1 := "10.180.0.1" - epIP2 := "10.180.2.1" - epStrLocal := fmt.Sprintf("%s:%d", epIP1, svcPort) - epStrNonLocal := fmt.Sprintf("%s:%d", epIP2, svcPort) - makeEndpointsMap(fp, - makeTestEndpoints(svcPortName.Namespace, svcPortName.Name, func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{{ - Addresses: []api.EndpointAddress{{ - IP: epIP1, - NodeName: nil, - }, { - IP: epIP2, - NodeName: strPtr(testHostname), - }}, - Ports: []api.EndpointPort{{ - Name: svcPortName.Port, - Port: int32(svcPort), - }}, - }} - }), - ) - - fp.syncProxyRules() - - proto := strings.ToLower(string(api.ProtocolTCP)) - fwChain := string(serviceFirewallChainName(svcPortName.String(), proto)) - lbChain := string(serviceLBChainName(svcPortName.String(), proto)) - - nonLocalEpChain := string(servicePortEndpointChainName(svcPortName.String(), strings.ToLower(string(api.ProtocolTCP)), epStrLocal)) - localEpChain := string(servicePortEndpointChainName(svcPortName.String(), strings.ToLower(string(api.ProtocolTCP)), epStrNonLocal)) - - // TODO -} - -func TestOnlyLocalNodePortsNoClusterCIDR(t *testing.T) { - - fp := NewFakeProxier() - // set cluster CIDR to empty before test - fp.clusterCIDR = "" - onlyLocalNodePorts(t, fp) -} - -func TestOnlyLocalNodePorts(t *testing.T) { - - fp := NewFakeProxier() - onlyLocalNodePorts(t, fp) -} - -func onlyLocalNodePorts(t *testing.T, fp *Proxier) { - shouldLBTOSVCRuleExist := len(fp.clusterCIDR) > 0 - 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 *api.Service) { - svc.Spec.Type = "NodePort" - svc.Spec.ClusterIP = svcIP - svc.Spec.Ports = []api.ServicePort{{ - Name: svcPortName.Port, - Port: int32(svcPort), - Protocol: api.ProtocolTCP, - NodePort: int32(svcNodePort), - }} - svc.Annotations[api.BetaAnnotationExternalTraffic] = api.AnnotationValueExternalTrafficLocal - }), - ) - - epIP1 := "10.180.0.1" - epIP2 := "10.180.2.1" - epStrLocal := fmt.Sprintf("%s:%d", epIP1, svcPort) - epStrNonLocal := fmt.Sprintf("%s:%d", epIP2, svcPort) - makeEndpointsMap(fp, - makeTestEndpoints(svcPortName.Namespace, svcPortName.Name, func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{{ - Addresses: []api.EndpointAddress{{ - IP: epIP1, - NodeName: nil, - }, { - IP: epIP2, - NodeName: strPtr(testHostname), - }}, - Ports: []api.EndpointPort{{ - Name: svcPortName.Port, - Port: int32(svcPort), - }}, - }} - }), - ) - - fp.syncProxyRules() - - // TODO -} - -func makeTestService(namespace, name string, svcFunc func(*api.Service)) *api.Service { - svc := &api.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Annotations: map[string]string{}, - }, - Spec: api.ServiceSpec{}, - Status: api.ServiceStatus{}, - } - svcFunc(svc) - return svc -} - -func addTestPort(array []api.ServicePort, name string, protocol api.Protocol, port, nodeport int32, targetPort int) []api.ServicePort { - svcPort := api.ServicePort{ - Name: name, - Protocol: protocol, - Port: port, - NodePort: nodeport, - TargetPort: intstr.FromInt(targetPort), - } - return append(array, svcPort) -} - -func TestBuildServiceMapAddRemove(t *testing.T) { - - fp := NewFakeProxier() - - services := []*api.Service{ - makeTestService("somewhere-else", "cluster-ip", func(svc *api.Service) { - svc.Spec.Type = api.ServiceTypeClusterIP - svc.Spec.ClusterIP = "172.16.55.4" - svc.Spec.Ports = addTestPort(svc.Spec.Ports, "something", "UDP", 1234, 4321, 0) - svc.Spec.Ports = addTestPort(svc.Spec.Ports, "somethingelse", "UDP", 1235, 5321, 0) - }), - makeTestService("somewhere-else", "node-port", func(svc *api.Service) { - svc.Spec.Type = api.ServiceTypeNodePort - svc.Spec.ClusterIP = "172.16.55.10" - svc.Spec.Ports = addTestPort(svc.Spec.Ports, "blahblah", "UDP", 345, 678, 0) - svc.Spec.Ports = addTestPort(svc.Spec.Ports, "moreblahblah", "TCP", 344, 677, 0) - }), - makeTestService("somewhere", "load-balancer", func(svc *api.Service) { - svc.Spec.Type = api.ServiceTypeLoadBalancer - svc.Spec.ClusterIP = "172.16.55.11" - svc.Spec.LoadBalancerIP = "5.6.7.8" - svc.Spec.Ports = addTestPort(svc.Spec.Ports, "foobar", "UDP", 8675, 30061, 7000) - svc.Spec.Ports = addTestPort(svc.Spec.Ports, "baz", "UDP", 8676, 30062, 7001) - svc.Status.LoadBalancer = api.LoadBalancerStatus{ - Ingress: []api.LoadBalancerIngress{ - {IP: "10.1.2.4"}, - }, - } - }), - makeTestService("somewhere", "only-local-load-balancer", func(svc *api.Service) { - svc.ObjectMeta.Annotations = map[string]string{ - api.BetaAnnotationExternalTraffic: api.AnnotationValueExternalTrafficLocal, - api.BetaAnnotationHealthCheckNodePort: "345", - } - svc.Spec.Type = api.ServiceTypeLoadBalancer - svc.Spec.ClusterIP = "172.16.55.12" - svc.Spec.LoadBalancerIP = "5.6.7.8" - svc.Spec.Ports = addTestPort(svc.Spec.Ports, "foobar2", "UDP", 8677, 30063, 7002) - svc.Spec.Ports = addTestPort(svc.Spec.Ports, "baz", "UDP", 8678, 30064, 7003) - svc.Status.LoadBalancer = api.LoadBalancerStatus{ - Ingress: []api.LoadBalancerIngress{ - {IP: "10.1.2.3"}, - }, - } - }), - } - - for i := range services { - fp.OnServiceAdd(services[i]) - } - result := updateServiceMap(fp.serviceMap, &fp.serviceChanges) - if len(fp.serviceMap) != 8 { - t.Errorf("expected service map length 8, got %v", fp.serviceMap) - } - - // The only-local-loadbalancer ones get added - if len(result.hcServices) != 1 { - t.Errorf("expected 1 healthcheck port, got %v", result.hcServices) - } else { - nsn := makeNSN("somewhere", "only-local-load-balancer") - if port, found := result.hcServices[nsn]; !found || port != 345 { - t.Errorf("expected healthcheck port [%q]=345: got %v", nsn, result.hcServices) - } - } - - if len(result.staleServices) != 0 { - // Services only added, so nothing stale yet - t.Errorf("expected stale UDP services length 0, got %d", len(result.staleServices)) - } - - // Remove some stuff - // oneService is a modification of services[0] with removed first port. - oneService := makeTestService("somewhere-else", "cluster-ip", func(svc *api.Service) { - svc.Spec.Type = api.ServiceTypeClusterIP - svc.Spec.ClusterIP = "172.16.55.4" - svc.Spec.Ports = addTestPort(svc.Spec.Ports, "somethingelse", "UDP", 1235, 5321, 0) - }) - - fp.OnServiceUpdate(services[0], oneService) - fp.OnServiceDelete(services[1]) - fp.OnServiceDelete(services[2]) - fp.OnServiceDelete(services[3]) - - result = updateServiceMap(fp.serviceMap, &fp.serviceChanges) - if len(fp.serviceMap) != 1 { - t.Errorf("expected service map length 1, got %v", fp.serviceMap) - } - - if len(result.hcServices) != 0 { - t.Errorf("expected 0 healthcheck ports, got %v", result.hcServices) - } - - // All services but one were deleted. While you'd expect only the ClusterIPs - // from the three deleted services here, we still have the ClusterIP for - // the not-deleted service, because one of it's ServicePorts was deleted. - expectedStaleUDPServices := []string{"172.16.55.10", "172.16.55.4", "172.16.55.11", "172.16.55.12"} - if len(result.staleServices) != len(expectedStaleUDPServices) { - t.Errorf("expected stale UDP services length %d, got %v", len(expectedStaleUDPServices), result.staleServices.List()) - } - for _, ip := range expectedStaleUDPServices { - if !result.staleServices.Has(ip) { - t.Errorf("expected stale UDP service service %s", ip) - } - } -} - -func TestBuildServiceMapServiceHeadless(t *testing.T) { - - fp := NewFakeProxier() - - makeServiceMap(fp, - makeTestService("somewhere-else", "headless", func(svc *api.Service) { - svc.Spec.Type = api.ServiceTypeClusterIP - svc.Spec.ClusterIP = api.ClusterIPNone - svc.Spec.Ports = addTestPort(svc.Spec.Ports, "rpc", "UDP", 1234, 0, 0) - }), - makeTestService("somewhere-else", "headless-without-port", func(svc *api.Service) { - svc.Spec.Type = api.ServiceTypeClusterIP - svc.Spec.ClusterIP = api.ClusterIPNone - }), - ) - - // Headless service should be ignored - result := updateServiceMap(fp.serviceMap, &fp.serviceChanges) - if len(fp.serviceMap) != 0 { - t.Errorf("expected service map length 0, got %d", len(fp.serviceMap)) - } - - // No proxied services, so no healthchecks - if len(result.hcServices) != 0 { - t.Errorf("expected healthcheck ports length 0, got %d", len(result.hcServices)) - } - - if len(result.staleServices) != 0 { - t.Errorf("expected stale UDP services length 0, got %d", len(result.staleServices)) - } -} - -func TestBuildServiceMapServiceTypeExternalName(t *testing.T) { - - fp := NewFakeProxier() - - makeServiceMap(fp, - makeTestService("somewhere-else", "external-name", func(svc *api.Service) { - svc.Spec.Type = api.ServiceTypeExternalName - svc.Spec.ClusterIP = "172.16.55.4" // Should be ignored - svc.Spec.ExternalName = "foo2.bar.com" - svc.Spec.Ports = addTestPort(svc.Spec.Ports, "blah", "UDP", 1235, 5321, 0) - }), - ) - - result := updateServiceMap(fp.serviceMap, &fp.serviceChanges) - if len(fp.serviceMap) != 0 { - t.Errorf("expected service map length 0, got %v", fp.serviceMap) - } - // No proxied services, so no healthchecks - if len(result.hcServices) != 0 { - t.Errorf("expected healthcheck ports length 0, got %v", result.hcServices) - } - if len(result.staleServices) != 0 { - t.Errorf("expected stale UDP services length 0, got %v", result.staleServices) - } -} - -func TestBuildServiceMapServiceUpdate(t *testing.T) { - fp := NewFakeProxier() - - servicev1 := makeTestService("somewhere", "some-service", func(svc *api.Service) { - svc.Spec.Type = api.ServiceTypeClusterIP - svc.Spec.ClusterIP = "172.16.55.4" - svc.Spec.Ports = addTestPort(svc.Spec.Ports, "something", "UDP", 1234, 4321, 0) - svc.Spec.Ports = addTestPort(svc.Spec.Ports, "somethingelse", "TCP", 1235, 5321, 0) - }) - servicev2 := makeTestService("somewhere", "some-service", func(svc *api.Service) { - svc.ObjectMeta.Annotations = map[string]string{ - api.BetaAnnotationExternalTraffic: api.AnnotationValueExternalTrafficLocal, - api.BetaAnnotationHealthCheckNodePort: "345", - } - svc.Spec.Type = api.ServiceTypeLoadBalancer - svc.Spec.ClusterIP = "172.16.55.4" - svc.Spec.LoadBalancerIP = "5.6.7.8" - svc.Spec.Ports = addTestPort(svc.Spec.Ports, "something", "UDP", 1234, 4321, 7002) - svc.Spec.Ports = addTestPort(svc.Spec.Ports, "somethingelse", "TCP", 1235, 5321, 7003) - svc.Status.LoadBalancer = api.LoadBalancerStatus{ - Ingress: []api.LoadBalancerIngress{ - {IP: "10.1.2.3"}, - }, - } - }) - - fp.OnServiceAdd(servicev1) - - result := updateServiceMap(fp.serviceMap, &fp.serviceChanges) - if len(fp.serviceMap) != 2 { - t.Errorf("expected service map length 2, got %v", fp.serviceMap) - } - if len(result.hcServices) != 0 { - t.Errorf("expected healthcheck ports length 0, got %v", result.hcServices) - } - if len(result.staleServices) != 0 { - // Services only added, so nothing stale yet - t.Errorf("expected stale UDP services length 0, got %d", len(result.staleServices)) - } - - // Change service to load-balancer - fp.OnServiceUpdate(servicev1, servicev2) - result = updateServiceMap(fp.serviceMap, &fp.serviceChanges) - if len(fp.serviceMap) != 2 { - t.Errorf("expected service map length 2, got %v", fp.serviceMap) - } - if len(result.hcServices) != 1 { - t.Errorf("expected healthcheck ports length 1, got %v", result.hcServices) - } - if len(result.staleServices) != 0 { - t.Errorf("expected stale UDP services length 0, got %v", result.staleServices.List()) - } - - // No change; make sure the service map stays the same and there are - // no health-check changes - fp.OnServiceUpdate(servicev2, servicev2) - result = updateServiceMap(fp.serviceMap, &fp.serviceChanges) - if len(fp.serviceMap) != 2 { - t.Errorf("expected service map length 2, got %v", fp.serviceMap) - } - if len(result.hcServices) != 1 { - t.Errorf("expected healthcheck ports length 1, got %v", result.hcServices) - } - if len(result.staleServices) != 0 { - t.Errorf("expected stale UDP services length 0, got %v", result.staleServices.List()) - } - - // And back to ClusterIP - fp.OnServiceUpdate(servicev2, servicev1) - result = updateServiceMap(fp.serviceMap, &fp.serviceChanges) - if len(fp.serviceMap) != 2 { - t.Errorf("expected service map length 2, got %v", fp.serviceMap) - } - if len(result.hcServices) != 0 { - t.Errorf("expected healthcheck ports length 0, got %v", result.hcServices) - } - if len(result.staleServices) != 0 { - // Services only added, so nothing stale yet - t.Errorf("expected stale UDP services length 0, got %d", len(result.staleServices)) - } -} - -func Test_getLocalIPs(t *testing.T) { - testCases := []struct { - endpointsMap map[proxy.ServicePortName][]*endpointsInfo - expected map[types.NamespacedName]sets.String - }{{ - // Case[0]: nothing - endpointsMap: map[proxy.ServicePortName][]*endpointsInfo{}, - expected: map[types.NamespacedName]sets.String{}, - }, { - // Case[1]: unnamed port - endpointsMap: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", ""): { - {endpoint: "1.1.1.1:11", isLocal: false}, - }, - }, - expected: map[types.NamespacedName]sets.String{}, - }, { - // Case[2]: unnamed port local - endpointsMap: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", ""): { - {endpoint: "1.1.1.1:11", isLocal: true}, - }, - }, - expected: map[types.NamespacedName]sets.String{ - {Namespace: "ns1", Name: "ep1"}: sets.NewString("1.1.1.1"), - }, - }, { - // Case[3]: named local and non-local ports for the same IP. - endpointsMap: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p11"): { - {endpoint: "1.1.1.1:11", isLocal: false}, - {endpoint: "1.1.1.2:11", isLocal: true}, - }, - makeServicePortName("ns1", "ep1", "p12"): { - {endpoint: "1.1.1.1:12", isLocal: false}, - {endpoint: "1.1.1.2:12", isLocal: true}, - }, - }, - expected: map[types.NamespacedName]sets.String{ - {Namespace: "ns1", Name: "ep1"}: sets.NewString("1.1.1.2"), - }, - }, { - // Case[4]: named local and non-local ports for different IPs. - endpointsMap: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p11"): { - {endpoint: "1.1.1.1:11", isLocal: false}, - }, - makeServicePortName("ns2", "ep2", "p22"): { - {endpoint: "2.2.2.2:22", isLocal: true}, - {endpoint: "2.2.2.22:22", isLocal: true}, - }, - makeServicePortName("ns2", "ep2", "p23"): { - {endpoint: "2.2.2.3:23", isLocal: true}, - }, - makeServicePortName("ns4", "ep4", "p44"): { - {endpoint: "4.4.4.4:44", isLocal: true}, - {endpoint: "4.4.4.5:44", isLocal: false}, - }, - makeServicePortName("ns4", "ep4", "p45"): { - {endpoint: "4.4.4.6:45", isLocal: true}, - }, - }, - expected: map[types.NamespacedName]sets.String{ - {Namespace: "ns2", Name: "ep2"}: sets.NewString("2.2.2.2", "2.2.2.22", "2.2.2.3"), - {Namespace: "ns4", Name: "ep4"}: sets.NewString("4.4.4.4", "4.4.4.6"), - }, - }} - - for tci, tc := range testCases { - // outputs - localIPs := getLocalIPs(tc.endpointsMap) - - if !reflect.DeepEqual(localIPs, tc.expected) { - t.Errorf("[%d] expected %#v, got %#v", tci, tc.expected, localIPs) - } - } -} - -// This is a coarse test, but it offers some modicum of confidence as the code is evolved. -func Test_endpointsToEndpointsMap(t *testing.T) { - testCases := []struct { - newEndpoints *api.Endpoints - expected map[proxy.ServicePortName][]*endpointsInfo - }{{ - // Case[0]: nothing - newEndpoints: makeTestEndpoints("ns1", "ep1", func(ept *api.Endpoints) {}), - expected: map[proxy.ServicePortName][]*endpointsInfo{}, - }, { - // Case[1]: no changes, unnamed port - newEndpoints: makeTestEndpoints("ns1", "ep1", func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{{ - IP: "1.1.1.1", - }}, - Ports: []api.EndpointPort{{ - Name: "", - Port: 11, - }}, - }, - } - }), - expected: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", ""): { - {endpoint: "1.1.1.1:11", isLocal: false}, - }, - }, - }, { - // Case[2]: no changes, named port - newEndpoints: makeTestEndpoints("ns1", "ep1", func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{{ - IP: "1.1.1.1", - }}, - Ports: []api.EndpointPort{{ - Name: "port", - Port: 11, - }}, - }, - } - }), - expected: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "port"): { - {endpoint: "1.1.1.1:11", isLocal: false}, - }, - }, - }, { - // Case[3]: new port - newEndpoints: makeTestEndpoints("ns1", "ep1", func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{{ - IP: "1.1.1.1", - }}, - Ports: []api.EndpointPort{{ - Port: 11, - }}, - }, - } - }), - expected: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", ""): { - {endpoint: "1.1.1.1:11", isLocal: false}, - }, - }, - }, { - // Case[4]: remove port - newEndpoints: makeTestEndpoints("ns1", "ep1", func(ept *api.Endpoints) {}), - expected: map[proxy.ServicePortName][]*endpointsInfo{}, - }, { - // Case[5]: new IP and port - newEndpoints: makeTestEndpoints("ns1", "ep1", func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{{ - IP: "1.1.1.1", - }, { - IP: "2.2.2.2", - }}, - Ports: []api.EndpointPort{{ - Name: "p1", - Port: 11, - }, { - Name: "p2", - Port: 22, - }}, - }, - } - }), - expected: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p1"): { - {endpoint: "1.1.1.1:11", isLocal: false}, - {endpoint: "2.2.2.2:11", isLocal: false}, - }, - makeServicePortName("ns1", "ep1", "p2"): { - {endpoint: "1.1.1.1:22", isLocal: false}, - {endpoint: "2.2.2.2:22", isLocal: false}, - }, - }, - }, { - // Case[6]: remove IP and port - newEndpoints: makeTestEndpoints("ns1", "ep1", func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{{ - IP: "1.1.1.1", - }}, - Ports: []api.EndpointPort{{ - Name: "p1", - Port: 11, - }}, - }, - } - }), - expected: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p1"): { - {endpoint: "1.1.1.1:11", isLocal: false}, - }, - }, - }, { - // Case[7]: rename port - newEndpoints: makeTestEndpoints("ns1", "ep1", func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{{ - IP: "1.1.1.1", - }}, - Ports: []api.EndpointPort{{ - Name: "p2", - Port: 11, - }}, - }, - } - }), - expected: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p2"): { - {endpoint: "1.1.1.1:11", isLocal: false}, - }, - }, - }, { - // Case[8]: renumber port - newEndpoints: makeTestEndpoints("ns1", "ep1", func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{{ - IP: "1.1.1.1", - }}, - Ports: []api.EndpointPort{{ - Name: "p1", - Port: 22, - }}, - }, - } - }), - expected: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p1"): { - {endpoint: "1.1.1.1:22", isLocal: false}, - }, - }, - }} - - for tci, tc := range testCases { - // outputs - newEndpoints := endpointsToEndpointsMap(tc.newEndpoints, "host") - - if len(newEndpoints) != len(tc.expected) { - t.Errorf("[%d] expected %d new, got %d: %v", tci, len(tc.expected), len(newEndpoints), spew.Sdump(newEndpoints)) - } - for x := range tc.expected { - if len(newEndpoints[x]) != len(tc.expected[x]) { - t.Errorf("[%d] expected %d endpoints for %v, got %d", tci, len(tc.expected[x]), x, len(newEndpoints[x])) - } else { - for i := range newEndpoints[x] { - if *(newEndpoints[x][i]) != *(tc.expected[x][i]) { - t.Errorf("[%d] expected new[%v][%d] to be %v, got %v", tci, x, i, tc.expected[x][i], *(newEndpoints[x][i])) - } - } - } - } - } -} - -func makeTestEndpoints(namespace, name string, eptFunc func(*api.Endpoints)) *api.Endpoints { - ept := &api.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - } - eptFunc(ept) - return ept -} - -func makeEndpointsMap(proxier *Proxier, allEndpoints ...*api.Endpoints) { - for i := range allEndpoints { - proxier.OnEndpointsAdd(allEndpoints[i]) - } - - proxier.mu.Lock() - defer proxier.mu.Unlock() - proxier.endpointsSynced = true -} - -func makeNSN(namespace, name string) types.NamespacedName { - return types.NamespacedName{Namespace: namespace, Name: name} -} - -func makeServicePortName(ns, name, port string) proxy.ServicePortName { - return proxy.ServicePortName{ - NamespacedName: makeNSN(ns, name), - Port: port, - } -} - -func makeServiceMap(proxier *Proxier, allServices ...*api.Service) { - for i := range allServices { - proxier.OnServiceAdd(allServices[i]) - } - - proxier.mu.Lock() - defer proxier.mu.Unlock() - proxier.servicesSynced = true -} - -func compareEndpointsMaps(t *testing.T, tci int, newMap, expected map[proxy.ServicePortName][]*endpointsInfo) { - if len(newMap) != len(expected) { - t.Errorf("[%d] expected %d results, got %d: %v", tci, len(expected), len(newMap), newMap) - } - for x := range expected { - if len(newMap[x]) != len(expected[x]) { - t.Errorf("[%d] expected %d endpoints for %v, got %d", tci, len(expected[x]), x, len(newMap[x])) - } else { - for i := range expected[x] { - if *(newMap[x][i]) != *(expected[x][i]) { - t.Errorf("[%d] expected new[%v][%d] to be %v, got %v", tci, x, i, expected[x][i], newMap[x][i]) - } - } - } - } -} - -func Test_updateEndpointsMap(t *testing.T) { - var nodeName = testHostname - - emptyEndpoint := func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{} - } - unnamedPort := func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{{ - Addresses: []api.EndpointAddress{{ - IP: "1.1.1.1", - }}, - Ports: []api.EndpointPort{{ - Port: 11, - }}, - }} - } - unnamedPortLocal := func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{{ - Addresses: []api.EndpointAddress{{ - IP: "1.1.1.1", - NodeName: &nodeName, - }}, - Ports: []api.EndpointPort{{ - Port: 11, - }}, - }} - } - namedPortLocal := func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{{ - Addresses: []api.EndpointAddress{{ - IP: "1.1.1.1", - NodeName: &nodeName, - }}, - Ports: []api.EndpointPort{{ - Name: "p11", - Port: 11, - }}, - }} - } - namedPort := func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{{ - Addresses: []api.EndpointAddress{{ - IP: "1.1.1.1", - }}, - Ports: []api.EndpointPort{{ - Name: "p11", - Port: 11, - }}, - }} - } - namedPortRenamed := func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{{ - Addresses: []api.EndpointAddress{{ - IP: "1.1.1.1", - }}, - Ports: []api.EndpointPort{{ - Name: "p11-2", - Port: 11, - }}, - }} - } - namedPortRenumbered := func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{{ - Addresses: []api.EndpointAddress{{ - IP: "1.1.1.1", - }}, - Ports: []api.EndpointPort{{ - Name: "p11", - Port: 22, - }}, - }} - } - namedPortsLocalNoLocal := func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{{ - Addresses: []api.EndpointAddress{{ - IP: "1.1.1.1", - }, { - IP: "1.1.1.2", - NodeName: &nodeName, - }}, - Ports: []api.EndpointPort{{ - Name: "p11", - Port: 11, - }, { - Name: "p12", - Port: 12, - }}, - }} - } - multipleSubsets := func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{{ - Addresses: []api.EndpointAddress{{ - IP: "1.1.1.1", - }}, - Ports: []api.EndpointPort{{ - Name: "p11", - Port: 11, - }}, - }, { - Addresses: []api.EndpointAddress{{ - IP: "1.1.1.2", - }}, - Ports: []api.EndpointPort{{ - Name: "p12", - Port: 12, - }}, - }} - } - multipleSubsetsWithLocal := func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{{ - Addresses: []api.EndpointAddress{{ - IP: "1.1.1.1", - }}, - Ports: []api.EndpointPort{{ - Name: "p11", - Port: 11, - }}, - }, { - Addresses: []api.EndpointAddress{{ - IP: "1.1.1.2", - NodeName: &nodeName, - }}, - Ports: []api.EndpointPort{{ - Name: "p12", - Port: 12, - }}, - }} - } - multipleSubsetsMultiplePortsLocal := func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{{ - Addresses: []api.EndpointAddress{{ - IP: "1.1.1.1", - NodeName: &nodeName, - }}, - Ports: []api.EndpointPort{{ - Name: "p11", - Port: 11, - }, { - Name: "p12", - Port: 12, - }}, - }, { - Addresses: []api.EndpointAddress{{ - IP: "1.1.1.3", - }}, - Ports: []api.EndpointPort{{ - Name: "p13", - Port: 13, - }}, - }} - } - multipleSubsetsIPsPorts1 := func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{{ - Addresses: []api.EndpointAddress{{ - IP: "1.1.1.1", - }, { - IP: "1.1.1.2", - NodeName: &nodeName, - }}, - Ports: []api.EndpointPort{{ - Name: "p11", - Port: 11, - }, { - Name: "p12", - Port: 12, - }}, - }, { - Addresses: []api.EndpointAddress{{ - IP: "1.1.1.3", - }, { - IP: "1.1.1.4", - NodeName: &nodeName, - }}, - Ports: []api.EndpointPort{{ - Name: "p13", - Port: 13, - }, { - Name: "p14", - Port: 14, - }}, - }} - } - multipleSubsetsIPsPorts2 := func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{{ - Addresses: []api.EndpointAddress{{ - IP: "2.2.2.1", - }, { - IP: "2.2.2.2", - NodeName: &nodeName, - }}, - Ports: []api.EndpointPort{{ - Name: "p21", - Port: 21, - }, { - Name: "p22", - Port: 22, - }}, - }} - } - complexBefore1 := func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{{ - Addresses: []api.EndpointAddress{{ - IP: "1.1.1.1", - }}, - Ports: []api.EndpointPort{{ - Name: "p11", - Port: 11, - }}, - }} - } - complexBefore2 := func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{{ - Addresses: []api.EndpointAddress{{ - IP: "2.2.2.2", - NodeName: &nodeName, - }, { - IP: "2.2.2.22", - NodeName: &nodeName, - }}, - Ports: []api.EndpointPort{{ - Name: "p22", - Port: 22, - }}, - }, { - Addresses: []api.EndpointAddress{{ - IP: "2.2.2.3", - NodeName: &nodeName, - }}, - Ports: []api.EndpointPort{{ - Name: "p23", - Port: 23, - }}, - }} - } - complexBefore4 := func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{{ - Addresses: []api.EndpointAddress{{ - IP: "4.4.4.4", - NodeName: &nodeName, - }, { - IP: "4.4.4.5", - NodeName: &nodeName, - }}, - Ports: []api.EndpointPort{{ - Name: "p44", - Port: 44, - }}, - }, { - Addresses: []api.EndpointAddress{{ - IP: "4.4.4.6", - NodeName: &nodeName, - }}, - Ports: []api.EndpointPort{{ - Name: "p45", - Port: 45, - }}, - }} - } - complexAfter1 := func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{{ - Addresses: []api.EndpointAddress{{ - IP: "1.1.1.1", - }, { - IP: "1.1.1.11", - }}, - Ports: []api.EndpointPort{{ - Name: "p11", - Port: 11, - }}, - }, { - Addresses: []api.EndpointAddress{{ - IP: "1.1.1.2", - }}, - Ports: []api.EndpointPort{{ - Name: "p12", - Port: 12, - }, { - Name: "p122", - Port: 122, - }}, - }} - } - complexAfter3 := func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{{ - Addresses: []api.EndpointAddress{{ - IP: "3.3.3.3", - }}, - Ports: []api.EndpointPort{{ - Name: "p33", - Port: 33, - }}, - }} - } - complexAfter4 := func(ept *api.Endpoints) { - ept.Subsets = []api.EndpointSubset{{ - Addresses: []api.EndpointAddress{{ - IP: "4.4.4.4", - NodeName: &nodeName, - }}, - Ports: []api.EndpointPort{{ - Name: "p44", - Port: 44, - }}, - }} - } - - testCases := []struct { - // previousEndpoints and currentEndpoints are used to call appropriate - // handlers OnEndpoints* (based on whether corresponding values are nil - // or non-nil) and must be of equal length. - previousEndpoints []*api.Endpoints - currentEndpoints []*api.Endpoints - oldEndpoints map[proxy.ServicePortName][]*endpointsInfo - expectedResult map[proxy.ServicePortName][]*endpointsInfo - expectedStaleEndpoints []endpointServicePair - expectedStaleServiceNames map[proxy.ServicePortName]bool - expectedHealthchecks map[types.NamespacedName]int - }{{ - // Case[0]: nothing - oldEndpoints: map[proxy.ServicePortName][]*endpointsInfo{}, - expectedResult: map[proxy.ServicePortName][]*endpointsInfo{}, - expectedStaleEndpoints: []endpointServicePair{}, - expectedStaleServiceNames: map[proxy.ServicePortName]bool{}, - expectedHealthchecks: map[types.NamespacedName]int{}, - }, { - // Case[1]: no change, unnamed port - previousEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", unnamedPort), - }, - currentEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", unnamedPort), - }, - oldEndpoints: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", ""): { - {endpoint: "1.1.1.1:11", isLocal: false}, - }, - }, - expectedResult: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", ""): { - {endpoint: "1.1.1.1:11", isLocal: false}, - }, - }, - expectedStaleEndpoints: []endpointServicePair{}, - expectedStaleServiceNames: map[proxy.ServicePortName]bool{}, - expectedHealthchecks: map[types.NamespacedName]int{}, - }, { - // Case[2]: no change, named port, local - previousEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", namedPortLocal), - }, - currentEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", namedPortLocal), - }, - oldEndpoints: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p11"): { - {endpoint: "1.1.1.1:11", isLocal: true}, - }, - }, - expectedResult: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p11"): { - {endpoint: "1.1.1.1:11", isLocal: true}, - }, - }, - expectedStaleEndpoints: []endpointServicePair{}, - expectedStaleServiceNames: map[proxy.ServicePortName]bool{}, - expectedHealthchecks: map[types.NamespacedName]int{ - makeNSN("ns1", "ep1"): 1, - }, - }, { - // Case[3]: no change, multiple subsets - previousEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", multipleSubsets), - }, - currentEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", multipleSubsets), - }, - oldEndpoints: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p11"): { - {endpoint: "1.1.1.1:11", isLocal: false}, - }, - makeServicePortName("ns1", "ep1", "p12"): { - {endpoint: "1.1.1.2:12", isLocal: false}, - }, - }, - expectedResult: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p11"): { - {endpoint: "1.1.1.1:11", isLocal: false}, - }, - makeServicePortName("ns1", "ep1", "p12"): { - {endpoint: "1.1.1.2:12", isLocal: false}, - }, - }, - expectedStaleEndpoints: []endpointServicePair{}, - expectedStaleServiceNames: map[proxy.ServicePortName]bool{}, - expectedHealthchecks: map[types.NamespacedName]int{}, - }, { - // Case[4]: no change, multiple subsets, multiple ports, local - previousEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", multipleSubsetsMultiplePortsLocal), - }, - currentEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", multipleSubsetsMultiplePortsLocal), - }, - oldEndpoints: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p11"): { - {endpoint: "1.1.1.1:11", isLocal: true}, - }, - makeServicePortName("ns1", "ep1", "p12"): { - {endpoint: "1.1.1.1:12", isLocal: true}, - }, - makeServicePortName("ns1", "ep1", "p13"): { - {endpoint: "1.1.1.3:13", isLocal: false}, - }, - }, - expectedResult: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p11"): { - {endpoint: "1.1.1.1:11", isLocal: true}, - }, - makeServicePortName("ns1", "ep1", "p12"): { - {endpoint: "1.1.1.1:12", isLocal: true}, - }, - makeServicePortName("ns1", "ep1", "p13"): { - {endpoint: "1.1.1.3:13", isLocal: false}, - }, - }, - expectedStaleEndpoints: []endpointServicePair{}, - expectedStaleServiceNames: map[proxy.ServicePortName]bool{}, - expectedHealthchecks: map[types.NamespacedName]int{ - makeNSN("ns1", "ep1"): 1, - }, - }, { - // Case[5]: no change, multiple endpoints, subsets, IPs, and ports - previousEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", multipleSubsetsIPsPorts1), - makeTestEndpoints("ns2", "ep2", multipleSubsetsIPsPorts2), - }, - currentEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", multipleSubsetsIPsPorts1), - makeTestEndpoints("ns2", "ep2", multipleSubsetsIPsPorts2), - }, - oldEndpoints: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p11"): { - {endpoint: "1.1.1.1:11", isLocal: false}, - {endpoint: "1.1.1.2:11", isLocal: true}, - }, - makeServicePortName("ns1", "ep1", "p12"): { - {endpoint: "1.1.1.1:12", isLocal: false}, - {endpoint: "1.1.1.2:12", isLocal: true}, - }, - makeServicePortName("ns1", "ep1", "p13"): { - {endpoint: "1.1.1.3:13", isLocal: false}, - {endpoint: "1.1.1.4:13", isLocal: true}, - }, - makeServicePortName("ns1", "ep1", "p14"): { - {endpoint: "1.1.1.3:14", isLocal: false}, - {endpoint: "1.1.1.4:14", isLocal: true}, - }, - makeServicePortName("ns2", "ep2", "p21"): { - {endpoint: "2.2.2.1:21", isLocal: false}, - {endpoint: "2.2.2.2:21", isLocal: true}, - }, - makeServicePortName("ns2", "ep2", "p22"): { - {endpoint: "2.2.2.1:22", isLocal: false}, - {endpoint: "2.2.2.2:22", isLocal: true}, - }, - }, - expectedResult: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p11"): { - {endpoint: "1.1.1.1:11", isLocal: false}, - {endpoint: "1.1.1.2:11", isLocal: true}, - }, - makeServicePortName("ns1", "ep1", "p12"): { - {endpoint: "1.1.1.1:12", isLocal: false}, - {endpoint: "1.1.1.2:12", isLocal: true}, - }, - makeServicePortName("ns1", "ep1", "p13"): { - {endpoint: "1.1.1.3:13", isLocal: false}, - {endpoint: "1.1.1.4:13", isLocal: true}, - }, - makeServicePortName("ns1", "ep1", "p14"): { - {endpoint: "1.1.1.3:14", isLocal: false}, - {endpoint: "1.1.1.4:14", isLocal: true}, - }, - makeServicePortName("ns2", "ep2", "p21"): { - {endpoint: "2.2.2.1:21", isLocal: false}, - {endpoint: "2.2.2.2:21", isLocal: true}, - }, - makeServicePortName("ns2", "ep2", "p22"): { - {endpoint: "2.2.2.1:22", isLocal: false}, - {endpoint: "2.2.2.2:22", isLocal: true}, - }, - }, - expectedStaleEndpoints: []endpointServicePair{}, - expectedStaleServiceNames: map[proxy.ServicePortName]bool{}, - expectedHealthchecks: map[types.NamespacedName]int{ - makeNSN("ns1", "ep1"): 2, - makeNSN("ns2", "ep2"): 1, - }, - }, { - // Case[6]: add an Endpoints - previousEndpoints: []*api.Endpoints{ - nil, - }, - currentEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", unnamedPortLocal), - }, - oldEndpoints: map[proxy.ServicePortName][]*endpointsInfo{}, - expectedResult: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", ""): { - {endpoint: "1.1.1.1:11", isLocal: true}, - }, - }, - expectedStaleEndpoints: []endpointServicePair{}, - expectedStaleServiceNames: map[proxy.ServicePortName]bool{ - makeServicePortName("ns1", "ep1", ""): true, - }, - expectedHealthchecks: map[types.NamespacedName]int{ - makeNSN("ns1", "ep1"): 1, - }, - }, { - // Case[7]: remove an Endpoints - previousEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", unnamedPortLocal), - }, - currentEndpoints: []*api.Endpoints{ - nil, - }, - oldEndpoints: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", ""): { - {endpoint: "1.1.1.1:11", isLocal: true}, - }, - }, - expectedResult: map[proxy.ServicePortName][]*endpointsInfo{}, - expectedStaleEndpoints: []endpointServicePair{{ - endpoint: "1.1.1.1:11", - servicePortName: makeServicePortName("ns1", "ep1", ""), - }}, - expectedStaleServiceNames: map[proxy.ServicePortName]bool{}, - expectedHealthchecks: map[types.NamespacedName]int{}, - }, { - // Case[8]: add an IP and port - previousEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", namedPort), - }, - currentEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", namedPortsLocalNoLocal), - }, - oldEndpoints: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p11"): { - {endpoint: "1.1.1.1:11", isLocal: false}, - }, - }, - expectedResult: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p11"): { - {endpoint: "1.1.1.1:11", isLocal: false}, - {endpoint: "1.1.1.2:11", isLocal: true}, - }, - makeServicePortName("ns1", "ep1", "p12"): { - {endpoint: "1.1.1.1:12", isLocal: false}, - {endpoint: "1.1.1.2:12", isLocal: true}, - }, - }, - expectedStaleEndpoints: []endpointServicePair{}, - expectedStaleServiceNames: map[proxy.ServicePortName]bool{ - makeServicePortName("ns1", "ep1", "p12"): true, - }, - expectedHealthchecks: map[types.NamespacedName]int{ - makeNSN("ns1", "ep1"): 1, - }, - }, { - // Case[9]: remove an IP and port - previousEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", namedPortsLocalNoLocal), - }, - currentEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", namedPort), - }, - oldEndpoints: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p11"): { - {endpoint: "1.1.1.1:11", isLocal: false}, - {endpoint: "1.1.1.2:11", isLocal: true}, - }, - makeServicePortName("ns1", "ep1", "p12"): { - {endpoint: "1.1.1.1:12", isLocal: false}, - {endpoint: "1.1.1.2:12", isLocal: true}, - }, - }, - expectedResult: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p11"): { - {endpoint: "1.1.1.1:11", isLocal: false}, - }, - }, - expectedStaleEndpoints: []endpointServicePair{{ - endpoint: "1.1.1.2:11", - servicePortName: makeServicePortName("ns1", "ep1", "p11"), - }, { - endpoint: "1.1.1.1:12", - servicePortName: makeServicePortName("ns1", "ep1", "p12"), - }, { - endpoint: "1.1.1.2:12", - servicePortName: makeServicePortName("ns1", "ep1", "p12"), - }}, - expectedStaleServiceNames: map[proxy.ServicePortName]bool{}, - expectedHealthchecks: map[types.NamespacedName]int{}, - }, { - // Case[10]: add a subset - previousEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", namedPort), - }, - currentEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", multipleSubsetsWithLocal), - }, - oldEndpoints: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p11"): { - {endpoint: "1.1.1.1:11", isLocal: false}, - }, - }, - expectedResult: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p11"): { - {endpoint: "1.1.1.1:11", isLocal: false}, - }, - makeServicePortName("ns1", "ep1", "p12"): { - {endpoint: "1.1.1.2:12", isLocal: true}, - }, - }, - expectedStaleEndpoints: []endpointServicePair{}, - expectedStaleServiceNames: map[proxy.ServicePortName]bool{ - makeServicePortName("ns1", "ep1", "p12"): true, - }, - expectedHealthchecks: map[types.NamespacedName]int{ - makeNSN("ns1", "ep1"): 1, - }, - }, { - // Case[11]: remove a subset - previousEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", multipleSubsets), - }, - currentEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", namedPort), - }, - oldEndpoints: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p11"): { - {endpoint: "1.1.1.1:11", isLocal: false}, - }, - makeServicePortName("ns1", "ep1", "p12"): { - {endpoint: "1.1.1.2:12", isLocal: false}, - }, - }, - expectedResult: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p11"): { - {endpoint: "1.1.1.1:11", isLocal: false}, - }, - }, - expectedStaleEndpoints: []endpointServicePair{{ - endpoint: "1.1.1.2:12", - servicePortName: makeServicePortName("ns1", "ep1", "p12"), - }}, - expectedStaleServiceNames: map[proxy.ServicePortName]bool{}, - expectedHealthchecks: map[types.NamespacedName]int{}, - }, { - // Case[12]: rename a port - previousEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", namedPort), - }, - currentEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", namedPortRenamed), - }, - oldEndpoints: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p11"): { - {endpoint: "1.1.1.1:11", isLocal: false}, - }, - }, - expectedResult: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p11-2"): { - {endpoint: "1.1.1.1:11", isLocal: false}, - }, - }, - expectedStaleEndpoints: []endpointServicePair{{ - endpoint: "1.1.1.1:11", - servicePortName: makeServicePortName("ns1", "ep1", "p11"), - }}, - expectedStaleServiceNames: map[proxy.ServicePortName]bool{ - makeServicePortName("ns1", "ep1", "p11-2"): true, - }, - expectedHealthchecks: map[types.NamespacedName]int{}, - }, { - // Case[13]: renumber a port - previousEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", namedPort), - }, - currentEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", namedPortRenumbered), - }, - oldEndpoints: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p11"): { - {endpoint: "1.1.1.1:11", isLocal: false}, - }, - }, - expectedResult: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p11"): { - {endpoint: "1.1.1.1:22", isLocal: false}, - }, - }, - expectedStaleEndpoints: []endpointServicePair{{ - endpoint: "1.1.1.1:11", - servicePortName: makeServicePortName("ns1", "ep1", "p11"), - }}, - expectedStaleServiceNames: map[proxy.ServicePortName]bool{}, - expectedHealthchecks: map[types.NamespacedName]int{}, - }, { - // Case[14]: complex add and remove - previousEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", complexBefore1), - makeTestEndpoints("ns2", "ep2", complexBefore2), - nil, - makeTestEndpoints("ns4", "ep4", complexBefore4), - }, - currentEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", complexAfter1), - nil, - makeTestEndpoints("ns3", "ep3", complexAfter3), - makeTestEndpoints("ns4", "ep4", complexAfter4), - }, - oldEndpoints: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p11"): { - {endpoint: "1.1.1.1:11", isLocal: false}, - }, - makeServicePortName("ns2", "ep2", "p22"): { - {endpoint: "2.2.2.2:22", isLocal: true}, - {endpoint: "2.2.2.22:22", isLocal: true}, - }, - makeServicePortName("ns2", "ep2", "p23"): { - {endpoint: "2.2.2.3:23", isLocal: true}, - }, - makeServicePortName("ns4", "ep4", "p44"): { - {endpoint: "4.4.4.4:44", isLocal: true}, - {endpoint: "4.4.4.5:44", isLocal: true}, - }, - makeServicePortName("ns4", "ep4", "p45"): { - {endpoint: "4.4.4.6:45", isLocal: true}, - }, - }, - expectedResult: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", "p11"): { - {endpoint: "1.1.1.1:11", isLocal: false}, - {endpoint: "1.1.1.11:11", isLocal: false}, - }, - makeServicePortName("ns1", "ep1", "p12"): { - {endpoint: "1.1.1.2:12", isLocal: false}, - }, - makeServicePortName("ns1", "ep1", "p122"): { - {endpoint: "1.1.1.2:122", isLocal: false}, - }, - makeServicePortName("ns3", "ep3", "p33"): { - {endpoint: "3.3.3.3:33", isLocal: false}, - }, - makeServicePortName("ns4", "ep4", "p44"): { - {endpoint: "4.4.4.4:44", isLocal: true}, - }, - }, - expectedStaleEndpoints: []endpointServicePair{{ - endpoint: "2.2.2.2:22", - servicePortName: makeServicePortName("ns2", "ep2", "p22"), - }, { - endpoint: "2.2.2.22:22", - servicePortName: makeServicePortName("ns2", "ep2", "p22"), - }, { - endpoint: "2.2.2.3:23", - servicePortName: makeServicePortName("ns2", "ep2", "p23"), - }, { - endpoint: "4.4.4.5:44", - servicePortName: makeServicePortName("ns4", "ep4", "p44"), - }, { - endpoint: "4.4.4.6:45", - servicePortName: makeServicePortName("ns4", "ep4", "p45"), - }}, - expectedStaleServiceNames: map[proxy.ServicePortName]bool{ - makeServicePortName("ns1", "ep1", "p12"): true, - makeServicePortName("ns1", "ep1", "p122"): true, - makeServicePortName("ns3", "ep3", "p33"): true, - }, - expectedHealthchecks: map[types.NamespacedName]int{ - makeNSN("ns4", "ep4"): 1, - }, - }, { - // Case[15]: change from 0 endpoint address to 1 unnamed port - previousEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", emptyEndpoint), - }, - currentEndpoints: []*api.Endpoints{ - makeTestEndpoints("ns1", "ep1", unnamedPort), - }, - oldEndpoints: map[proxy.ServicePortName][]*endpointsInfo{}, - expectedResult: map[proxy.ServicePortName][]*endpointsInfo{ - makeServicePortName("ns1", "ep1", ""): { - {endpoint: "1.1.1.1:11", isLocal: false}, - }, - }, - expectedStaleEndpoints: []endpointServicePair{}, - expectedStaleServiceNames: map[proxy.ServicePortName]bool{ - makeServicePortName("ns1", "ep1", ""): true, - }, - expectedHealthchecks: map[types.NamespacedName]int{}, - }, - } - - for tci, tc := range testCases { - - fp := NewFakeProxier() - fp.hostname = nodeName - - // First check that after adding all previous versions of endpoints, - // the fp.oldEndpoints is as we expect. - for i := range tc.previousEndpoints { - if tc.previousEndpoints[i] != nil { - fp.OnEndpointsAdd(tc.previousEndpoints[i]) - } - } - updateEndpointsMap(fp.endpointsMap, &fp.endpointsChanges, fp.hostname) - compareEndpointsMaps(t, tci, fp.endpointsMap, tc.oldEndpoints) - - // Now let's call appropriate handlers to get to state we want to be. - if len(tc.previousEndpoints) != len(tc.currentEndpoints) { - t.Fatalf("[%d] different lengths of previous and current endpoints", tci) - continue - } - - for i := range tc.previousEndpoints { - prev, curr := tc.previousEndpoints[i], tc.currentEndpoints[i] - switch { - case prev == nil: - fp.OnEndpointsAdd(curr) - case curr == nil: - fp.OnEndpointsDelete(prev) - default: - fp.OnEndpointsUpdate(prev, curr) - } - } - result := updateEndpointsMap(fp.endpointsMap, &fp.endpointsChanges, fp.hostname) - newMap := fp.endpointsMap - compareEndpointsMaps(t, tci, newMap, tc.expectedResult) - if len(result.staleEndpoints) != len(tc.expectedStaleEndpoints) { - t.Errorf("[%d] expected %d staleEndpoints, got %d: %v", tci, len(tc.expectedStaleEndpoints), len(result.staleEndpoints), result.staleEndpoints) - } - for _, x := range tc.expectedStaleEndpoints { - if result.staleEndpoints[x] != true { - t.Errorf("[%d] expected staleEndpoints[%v], but didn't find it: %v", tci, x, result.staleEndpoints) - } - } - if len(result.staleServiceNames) != len(tc.expectedStaleServiceNames) { - t.Errorf("[%d] expected %d staleServiceNames, got %d: %v", tci, len(tc.expectedStaleServiceNames), len(result.staleServiceNames), result.staleServiceNames) - } - for svcName := range tc.expectedStaleServiceNames { - if result.staleServiceNames[svcName] != true { - t.Errorf("[%d] expected staleServiceNames[%v], but didn't find it: %v", tci, svcName, result.staleServiceNames) - } - } - if !reflect.DeepEqual(result.hcEndpoints, tc.expectedHealthchecks) { - t.Errorf("[%d] expected healthchecks %v, got %v", tci, tc.expectedHealthchecks, result.hcEndpoints) - } - } -} - -// TODO(thockin): add *more* tests for syncProxyRules() or break it down further and test the pieces. diff --git a/pkg/proxy/winuserspace/BUILD b/pkg/proxy/winuserspace/BUILD index 163523d849b..94c05e18749 100644 --- a/pkg/proxy/winuserspace/BUILD +++ b/pkg/proxy/winuserspace/BUILD @@ -18,8 +18,8 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/proxy/winuserspace", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", "//pkg/proxy:go_default_library", "//pkg/util/ipconfig:go_default_library", "//pkg/util/netsh:go_default_library", @@ -39,10 +39,10 @@ go_test( "proxier_test.go", "roundrobin_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/proxy/winuserspace", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/proxy:go_default_library", "//pkg/util/netsh/testing:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/proxy/winuserspace/loadbalancer.go b/pkg/proxy/winuserspace/loadbalancer.go index b4a5bc2c253..eb0dc2dbf67 100644 --- a/pkg/proxy/winuserspace/loadbalancer.go +++ b/pkg/proxy/winuserspace/loadbalancer.go @@ -17,10 +17,9 @@ limitations under the License. package winuserspace import ( - "net" - - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/proxy" + "net" ) // LoadBalancer is an interface for distributing incoming requests to service endpoints. diff --git a/pkg/proxy/winuserspace/proxier.go b/pkg/proxy/winuserspace/proxier.go index 4e6382918c6..5b318164220 100644 --- a/pkg/proxy/winuserspace/proxier.go +++ b/pkg/proxy/winuserspace/proxier.go @@ -29,8 +29,8 @@ import ( "k8s.io/apimachinery/pkg/types" utilnet "k8s.io/apimachinery/pkg/util/net" "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" "k8s.io/kubernetes/pkg/proxy" "k8s.io/kubernetes/pkg/util/netsh" ) diff --git a/pkg/proxy/winuserspace/proxier_test.go b/pkg/proxy/winuserspace/proxier_test.go index fadc5e38bc2..f69e64c63f1 100644 --- a/pkg/proxy/winuserspace/proxier_test.go +++ b/pkg/proxy/winuserspace/proxier_test.go @@ -32,7 +32,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/proxy" netshtest "k8s.io/kubernetes/pkg/util/netsh/testing" ) diff --git a/pkg/proxy/winuserspace/proxysocket.go b/pkg/proxy/winuserspace/proxysocket.go index ced3a8b0cc7..ed87f197537 100644 --- a/pkg/proxy/winuserspace/proxysocket.go +++ b/pkg/proxy/winuserspace/proxysocket.go @@ -30,7 +30,7 @@ import ( "github.com/miekg/dns" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/proxy" "k8s.io/kubernetes/pkg/util/ipconfig" "k8s.io/utils/exec" diff --git a/pkg/proxy/winuserspace/roundrobin.go b/pkg/proxy/winuserspace/roundrobin.go index 86d7614c850..27b83555994 100644 --- a/pkg/proxy/winuserspace/roundrobin.go +++ b/pkg/proxy/winuserspace/roundrobin.go @@ -27,7 +27,7 @@ import ( "github.com/golang/glog" "k8s.io/apimachinery/pkg/types" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/proxy" "k8s.io/kubernetes/pkg/util/slice" ) diff --git a/pkg/proxy/winuserspace/roundrobin_test.go b/pkg/proxy/winuserspace/roundrobin_test.go index 1f4973ee1a9..c0dce2b512a 100644 --- a/pkg/proxy/winuserspace/roundrobin_test.go +++ b/pkg/proxy/winuserspace/roundrobin_test.go @@ -22,7 +22,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/proxy" ) diff --git a/pkg/quota/BUILD b/pkg/quota/BUILD index 5ee2463ee90..0ae4478e655 100644 --- a/pkg/quota/BUILD +++ b/pkg/quota/BUILD @@ -14,23 +14,24 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/quota", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + "//vendor/k8s.io/client-go/tools/cache:go_default_library", ], ) go_test( name = "go_default_test", srcs = ["resources_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/quota", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", ], ) diff --git a/pkg/quota/OWNERS b/pkg/quota/OWNERS index d2eabe9cd4e..7025d6d6eea 100644 --- a/pkg/quota/OWNERS +++ b/pkg/quota/OWNERS @@ -1,8 +1,9 @@ approvers: -- derekwaynecarr -- vishh -reviewers: -- smarterclayton - deads2k - derekwaynecarr - vishh +reviewers: +- deads2k +- derekwaynecarr +- smarterclayton +- vishh diff --git a/pkg/quota/evaluator/core/BUILD b/pkg/quota/evaluator/core/BUILD index 769d61a38ec..6d70d153955 100644 --- a/pkg/quota/evaluator/core/BUILD +++ b/pkg/quota/evaluator/core/BUILD @@ -9,42 +9,33 @@ load( go_library( name = "go_default_library", srcs = [ - "configmap.go", "doc.go", "persistent_volume_claims.go", "pods.go", "registry.go", - "replication_controllers.go", - "resource_quotas.go", - "secrets.go", "services.go", ], importpath = "k8s.io/kubernetes/pkg/quota/evaluator/core", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", - "//pkg/api/helper/qos:go_default_library", - "//pkg/api/v1:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", + "//pkg/apis/core/helper/qos:go_default_library", + "//pkg/apis/core/v1:go_default_library", "//pkg/features:go_default_library", "//pkg/kubeapiserver/admission/util:go_default_library", "//pkg/quota:go_default_library", "//pkg/quota/generic:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/initialization:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", "//vendor/k8s.io/apiserver/pkg/features:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", - "//vendor/k8s.io/client-go/informers:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", ], ) @@ -55,16 +46,17 @@ go_test( "pods_test.go", "services_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/quota/evaluator/core", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/quota:go_default_library", + "//pkg/quota/generic:go_default_library", "//pkg/util/node:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library", - "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", ], ) diff --git a/pkg/quota/evaluator/core/configmap.go b/pkg/quota/evaluator/core/configmap.go deleted file mode 100644 index bde20fc7247..00000000000 --- a/pkg/quota/evaluator/core/configmap.go +++ /dev/null @@ -1,61 +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 core - -import ( - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/informers" - clientset "k8s.io/client-go/kubernetes" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/quota" - "k8s.io/kubernetes/pkg/quota/generic" -) - -// listConfigMapsByNamespaceFuncUsingClient returns a configMap listing function based on the provided client. -func listConfigMapsByNamespaceFuncUsingClient(kubeClient clientset.Interface) generic.ListFuncByNamespace { - // TODO: ideally, we could pass dynamic client pool down into this code, and have one way of doing this. - // unfortunately, dynamic client works with Unstructured objects, and when we calculate Usage, we require - // structured objects. - return func(namespace string, options metav1.ListOptions) ([]runtime.Object, error) { - itemList, err := kubeClient.Core().ConfigMaps(namespace).List(options) - if err != nil { - return nil, err - } - results := make([]runtime.Object, 0, len(itemList.Items)) - for i := range itemList.Items { - results = append(results, &itemList.Items[i]) - } - return results, nil - } -} - -// NewConfigMapEvaluator returns an evaluator that can evaluate configMaps -// if the specified shared informer factory is not nil, evaluator may use it to support listing functions. -func NewConfigMapEvaluator(kubeClient clientset.Interface, f informers.SharedInformerFactory) quota.Evaluator { - listFuncByNamespace := listConfigMapsByNamespaceFuncUsingClient(kubeClient) - if f != nil { - listFuncByNamespace = generic.ListResourceUsingInformerFunc(f, v1.SchemeGroupVersion.WithResource("configmaps")) - } - return &generic.ObjectCountEvaluator{ - AllowCreateOnUpdate: false, - InternalGroupKind: api.Kind("ConfigMap"), - ResourceName: api.ResourceConfigMaps, - ListFuncByNamespace: listFuncByNamespace, - } -} diff --git a/pkg/quota/evaluator/core/persistent_volume_claims.go b/pkg/quota/evaluator/core/persistent_volume_claims.go index 9759c492e68..9032dcaf7b1 100644 --- a/pkg/quota/evaluator/core/persistent_volume_claims.go +++ b/pkg/quota/evaluator/core/persistent_volume_claims.go @@ -22,26 +22,25 @@ import ( "k8s.io/api/core/v1" "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" "k8s.io/apimachinery/pkg/util/initialization" utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/features" utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/client-go/informers" - clientset "k8s.io/client-go/kubernetes" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper" - k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" + k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" k8sfeatures "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubeapiserver/admission/util" "k8s.io/kubernetes/pkg/quota" "k8s.io/kubernetes/pkg/quota/generic" ) +// the name used for object count quota +var pvcObjectCountName = generic.ObjectCountQuotaResourceNameFor(v1.SchemeGroupVersion.WithResource("persistentvolumeclaims").GroupResource()) + // pvcResources are the set of static resources managed by quota associated with pvcs. // for each resouce in this list, it may be refined dynamically based on storage class. var pvcResources = []api.ResourceName{ @@ -67,34 +66,11 @@ func V1ResourceByStorageClass(storageClass string, resourceName v1.ResourceName) return v1.ResourceName(string(storageClass + storageClassSuffix + string(resourceName))) } -// listPersistentVolumeClaimsByNamespaceFuncUsingClient returns a pvc listing function based on the provided client. -func listPersistentVolumeClaimsByNamespaceFuncUsingClient(kubeClient clientset.Interface) generic.ListFuncByNamespace { - // TODO: ideally, we could pass dynamic client pool down into this code, and have one way of doing this. - // unfortunately, dynamic client works with Unstructured objects, and when we calculate Usage, we require - // structured objects. - return func(namespace string, options metav1.ListOptions) ([]runtime.Object, error) { - itemList, err := kubeClient.Core().PersistentVolumeClaims(namespace).List(options) - if err != nil { - return nil, err - } - results := make([]runtime.Object, 0, len(itemList.Items)) - for i := range itemList.Items { - results = append(results, &itemList.Items[i]) - } - return results, nil - } -} - // NewPersistentVolumeClaimEvaluator returns an evaluator that can evaluate persistent volume claims -// if the specified shared informer factory is not nil, evaluator may use it to support listing functions. -func NewPersistentVolumeClaimEvaluator(kubeClient clientset.Interface, f informers.SharedInformerFactory) quota.Evaluator { - listFuncByNamespace := listPersistentVolumeClaimsByNamespaceFuncUsingClient(kubeClient) - if f != nil { - listFuncByNamespace = generic.ListResourceUsingInformerFunc(f, v1.SchemeGroupVersion.WithResource("persistentvolumeclaims")) - } - return &pvcEvaluator{ - listFuncByNamespace: listFuncByNamespace, - } +func NewPersistentVolumeClaimEvaluator(f quota.ListerForResourceFunc) quota.Evaluator { + listFuncByNamespace := generic.ListResourceUsingListerFunc(f, v1.SchemeGroupVersion.WithResource("persistentvolumeclaims")) + pvcEvaluator := &pvcEvaluator{listFuncByNamespace: listFuncByNamespace} + return pvcEvaluator } // pvcEvaluator knows how to evaluate quota usage for persistent volume claims @@ -105,45 +81,13 @@ type pvcEvaluator struct { // Constraints verifies that all required resources are present on the item. func (p *pvcEvaluator) Constraints(required []api.ResourceName, item runtime.Object) error { - pvc, ok := item.(*api.PersistentVolumeClaim) - if !ok { - return fmt.Errorf("unexpected input object %v", item) - } - - // these are the items that we will be handling based on the objects actual storage-class - pvcRequiredSet := append([]api.ResourceName{}, pvcResources...) - if storageClassRef := helper.GetPersistentVolumeClaimClass(pvc); len(storageClassRef) > 0 { - pvcRequiredSet = append(pvcRequiredSet, ResourceByStorageClass(storageClassRef, api.ResourcePersistentVolumeClaims)) - pvcRequiredSet = append(pvcRequiredSet, ResourceByStorageClass(storageClassRef, api.ResourceRequestsStorage)) - } - - // in effect, this will remove things from the required set that are not tied to this pvcs storage class - // for example, if a quota has bronze and gold storage class items defined, we should not error a bronze pvc for not being gold. - // but we should error a bronze pvc if it doesn't make a storage request size... - requiredResources := quota.Intersection(required, pvcRequiredSet) - requiredSet := quota.ToSet(requiredResources) - - // usage for this pvc will only include global pvc items + this storage class specific items - pvcUsage, err := p.Usage(item) - if err != nil { - return err - } - - // determine what required resources were not tracked by usage. - missingSet := sets.NewString() - pvcSet := quota.ToSet(quota.ResourceNames(pvcUsage)) - if diff := requiredSet.Difference(pvcSet); len(diff) > 0 { - missingSet.Insert(diff.List()...) - } - if len(missingSet) == 0 { - return nil - } - return fmt.Errorf("must specify %s", strings.Join(missingSet.List(), ",")) + // no-op for persistent volume claims + return nil } -// GroupKind that this evaluator tracks -func (p *pvcEvaluator) GroupKind() schema.GroupKind { - return api.Kind("PersistentVolumeClaim") +// GroupResource that this evaluator tracks +func (p *pvcEvaluator) GroupResource() schema.GroupResource { + return v1.SchemeGroupVersion.WithResource("persistentvolumeclaims").GroupResource() } // Handles returns true if the evaluator should handle the specified operation. @@ -183,6 +127,12 @@ func (p *pvcEvaluator) Matches(resourceQuota *api.ResourceQuota, item runtime.Ob func (p *pvcEvaluator) MatchingResources(items []api.ResourceName) []api.ResourceName { result := []api.ResourceName{} for _, item := range items { + // match object count quota fields + if quota.Contains([]api.ResourceName{pvcObjectCountName}, item) { + result = append(result, item) + continue + } + // match pvc resources if quota.Contains(pvcResources, item) { result = append(result, item) continue @@ -208,7 +158,8 @@ func (p *pvcEvaluator) Usage(item runtime.Object) (api.ResourceList, error) { } // charge for claim - result[api.ResourcePersistentVolumeClaims] = resource.MustParse("1") + result[api.ResourcePersistentVolumeClaims] = *(resource.NewQuantity(1, resource.DecimalSI)) + result[pvcObjectCountName] = *(resource.NewQuantity(1, resource.DecimalSI)) if utilfeature.DefaultFeatureGate.Enabled(features.Initializers) { if !initialization.IsInitialized(pvc.Initializers) { // Only charge pvc count for uninitialized pvc. @@ -218,7 +169,7 @@ func (p *pvcEvaluator) Usage(item runtime.Object) (api.ResourceList, error) { storageClassRef := helper.GetPersistentVolumeClaimClass(pvc) if len(storageClassRef) > 0 { storageClassClaim := api.ResourceName(storageClassRef + storageClassSuffix + string(api.ResourcePersistentVolumeClaims)) - result[storageClassClaim] = resource.MustParse("1") + result[storageClassClaim] = *(resource.NewQuantity(1, resource.DecimalSI)) } // charge for storage @@ -245,7 +196,7 @@ func toInternalPersistentVolumeClaimOrError(obj runtime.Object) (*api.Persistent pvc := &api.PersistentVolumeClaim{} switch t := obj.(type) { case *v1.PersistentVolumeClaim: - if err := k8s_api_v1.Convert_v1_PersistentVolumeClaim_To_api_PersistentVolumeClaim(t, pvc, nil); err != nil { + if err := k8s_api_v1.Convert_v1_PersistentVolumeClaim_To_core_PersistentVolumeClaim(t, pvc, nil); err != nil { return nil, err } case *api.PersistentVolumeClaim: diff --git a/pkg/quota/evaluator/core/persistent_volume_claims_test.go b/pkg/quota/evaluator/core/persistent_volume_claims_test.go index a7d50f4b3d3..e2b1c69d98a 100644 --- a/pkg/quota/evaluator/core/persistent_volume_claims_test.go +++ b/pkg/quota/evaluator/core/persistent_volume_claims_test.go @@ -21,9 +21,10 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes/fake" - "k8s.io/kubernetes/pkg/api" + "k8s.io/apimachinery/pkg/runtime/schema" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/quota" + "k8s.io/kubernetes/pkg/quota/generic" ) func testVolumeClaim(name string, namespace string, spec api.PersistentVolumeClaimSpec) *api.PersistentVolumeClaim { @@ -33,168 +34,6 @@ func testVolumeClaim(name string, namespace string, spec api.PersistentVolumeCla } } -func TestPersistentVolumeClaimsConstraintsFunc(t *testing.T) { - classGold := "gold" - classBronze := "bronze" - - validClaim := testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{ - Selector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: "Exists", - }, - }, - }, - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadWriteOnce, - api.ReadOnlyMany, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), - }, - }, - }) - validClaimGoldStorageClass := testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{ - Selector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: "Exists", - }, - }, - }, - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadWriteOnce, - api.ReadOnlyMany, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10Gi"), - }, - }, - StorageClassName: &classGold, - }) - - validClaimBronzeStorageClass := testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{ - Selector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: "Exists", - }, - }, - }, - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadWriteOnce, - api.ReadOnlyMany, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10Gi"), - }, - }, - StorageClassName: &classBronze, - }) - - missingStorage := testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{ - Selector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: "Exists", - }, - }, - }, - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadWriteOnce, - api.ReadOnlyMany, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{}, - }, - }) - - missingGoldStorage := testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{ - Selector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "key2", - Operator: "Exists", - }, - }, - }, - AccessModes: []api.PersistentVolumeAccessMode{ - api.ReadWriteOnce, - api.ReadOnlyMany, - }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{}, - }, - StorageClassName: &classGold, - }) - - testCases := map[string]struct { - pvc *api.PersistentVolumeClaim - required []api.ResourceName - err string - }{ - "missing storage": { - pvc: missingStorage, - required: []api.ResourceName{api.ResourceRequestsStorage}, - err: `must specify requests.storage`, - }, - "missing gold storage": { - pvc: missingGoldStorage, - required: []api.ResourceName{ResourceByStorageClass(classGold, api.ResourceRequestsStorage)}, - err: `must specify gold.storageclass.storage.k8s.io/requests.storage`, - }, - "valid-claim-quota-storage": { - pvc: validClaim, - required: []api.ResourceName{api.ResourceRequestsStorage}, - }, - "valid-claim-quota-pvc": { - pvc: validClaim, - required: []api.ResourceName{api.ResourcePersistentVolumeClaims}, - }, - "valid-claim-quota-storage-and-pvc": { - pvc: validClaim, - required: []api.ResourceName{api.ResourceRequestsStorage, api.ResourcePersistentVolumeClaims}, - }, - "valid-claim-gold-quota-gold": { - pvc: validClaimGoldStorageClass, - required: []api.ResourceName{ - api.ResourceRequestsStorage, - api.ResourcePersistentVolumeClaims, - ResourceByStorageClass(classGold, api.ResourceRequestsStorage), - ResourceByStorageClass(classGold, api.ResourcePersistentVolumeClaims), - }, - }, - "valid-claim-bronze-with-quota-gold": { - pvc: validClaimBronzeStorageClass, - required: []api.ResourceName{ - api.ResourceRequestsStorage, - api.ResourcePersistentVolumeClaims, - ResourceByStorageClass(classGold, api.ResourceRequestsStorage), - ResourceByStorageClass(classGold, api.ResourcePersistentVolumeClaims), - }, - }, - } - - kubeClient := fake.NewSimpleClientset() - evaluator := NewPersistentVolumeClaimEvaluator(kubeClient, nil) - for testName, test := range testCases { - err := evaluator.Constraints(test.required, test.pvc) - switch { - case err != nil && len(test.err) == 0, - err == nil && len(test.err) != 0, - err != nil && test.err != err.Error(): - t.Errorf("%s unexpected error: %v", testName, err) - } - } -} - func TestPersistentVolumeClaimEvaluatorUsage(t *testing.T) { classGold := "gold" validClaim := testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{ @@ -237,8 +76,7 @@ func TestPersistentVolumeClaimEvaluatorUsage(t *testing.T) { StorageClassName: &classGold, }) - kubeClient := fake.NewSimpleClientset() - evaluator := NewPersistentVolumeClaimEvaluator(kubeClient, nil) + evaluator := NewPersistentVolumeClaimEvaluator(nil) testCases := map[string]struct { pvc *api.PersistentVolumeClaim usage api.ResourceList @@ -246,17 +84,19 @@ func TestPersistentVolumeClaimEvaluatorUsage(t *testing.T) { "pvc-usage": { pvc: validClaim, usage: api.ResourceList{ - api.ResourceRequestsStorage: resource.MustParse("10Gi"), - api.ResourcePersistentVolumeClaims: resource.MustParse("1"), + api.ResourceRequestsStorage: resource.MustParse("10Gi"), + api.ResourcePersistentVolumeClaims: resource.MustParse("1"), + generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "persistentvolumeclaims"}): resource.MustParse("1"), }, }, "pvc-usage-by-class": { pvc: validClaimByStorageClass, usage: api.ResourceList{ - api.ResourceRequestsStorage: resource.MustParse("10Gi"), - api.ResourcePersistentVolumeClaims: resource.MustParse("1"), - ResourceByStorageClass(classGold, api.ResourceRequestsStorage): resource.MustParse("10Gi"), - ResourceByStorageClass(classGold, api.ResourcePersistentVolumeClaims): resource.MustParse("1"), + api.ResourceRequestsStorage: resource.MustParse("10Gi"), + api.ResourcePersistentVolumeClaims: resource.MustParse("1"), + ResourceByStorageClass(classGold, api.ResourceRequestsStorage): resource.MustParse("10Gi"), + ResourceByStorageClass(classGold, api.ResourcePersistentVolumeClaims): resource.MustParse("1"), + generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "persistentvolumeclaims"}): resource.MustParse("1"), }, }, } diff --git a/pkg/quota/evaluator/core/pods.go b/pkg/quota/evaluator/core/pods.go index 7354c3d8bf2..be7d3307181 100644 --- a/pkg/quota/evaluator/core/pods.go +++ b/pkg/quota/evaluator/core/pods.go @@ -23,31 +23,27 @@ import ( "k8s.io/api/core/v1" "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" "k8s.io/apimachinery/pkg/util/clock" - "k8s.io/apimachinery/pkg/util/initialization" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apiserver/pkg/admission" - "k8s.io/apiserver/pkg/features" - utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/client-go/informers" - clientset "k8s.io/client-go/kubernetes" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper/qos" - k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" - "k8s.io/kubernetes/pkg/api/validation" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper/qos" + k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" "k8s.io/kubernetes/pkg/kubeapiserver/admission/util" "k8s.io/kubernetes/pkg/quota" "k8s.io/kubernetes/pkg/quota/generic" ) +// the name used for object count quota +var podObjectCountName = generic.ObjectCountQuotaResourceNameFor(v1.SchemeGroupVersion.WithResource("pods").GroupResource()) + // podResources are the set of resources managed by quota associated with pods. var podResources = []api.ResourceName{ + podObjectCountName, api.ResourceCPU, api.ResourceMemory, api.ResourceEphemeralStorage, @@ -60,35 +56,48 @@ var podResources = []api.ResourceName{ api.ResourcePods, } -// listPodsByNamespaceFuncUsingClient returns a pod listing function based on the provided client. -func listPodsByNamespaceFuncUsingClient(kubeClient clientset.Interface) generic.ListFuncByNamespace { - // TODO: ideally, we could pass dynamic client pool down into this code, and have one way of doing this. - // unfortunately, dynamic client works with Unstructured objects, and when we calculate Usage, we require - // structured objects. - return func(namespace string, options metav1.ListOptions) ([]runtime.Object, error) { - itemList, err := kubeClient.Core().Pods(namespace).List(options) - if err != nil { - return nil, err - } - results := make([]runtime.Object, 0, len(itemList.Items)) - for i := range itemList.Items { - results = append(results, &itemList.Items[i]) - } - return results, nil - } +// podResourcePrefixes are the set of prefixes for resources (Hugepages, and other +// potential extended reources with specific prefix) managed by quota associated with pods. +var podResourcePrefixes = []string{ + api.ResourceHugePagesPrefix, + api.ResourceRequestsHugePagesPrefix, } +// requestedResourcePrefixes are the set of prefixes for resources +// that might be declared in pod's Resources.Requests/Limits +var requestedResourcePrefixes = []string{ + api.ResourceHugePagesPrefix, +} + +const ( + requestsPrefix = "requests." + limitsPrefix = "limits." +) + +// maskResourceWithPrefix mask resource with certain prefix +// e.g. hugepages-XXX -> requests.hugepages-XXX +func maskResourceWithPrefix(resource api.ResourceName, prefix string) api.ResourceName { + return api.ResourceName(fmt.Sprintf("%s%s", prefix, string(resource))) +} + +// NOTE: it was a mistake, but if a quota tracks cpu or memory related resources, +// the incoming pod is required to have those values set. we should not repeat +// this mistake for other future resources (gpus, ephemeral-storage,etc). +// do not add more resources to this list! +var validationSet = sets.NewString( + string(api.ResourceCPU), + string(api.ResourceMemory), + string(api.ResourceRequestsCPU), + string(api.ResourceRequestsMemory), + string(api.ResourceLimitsCPU), + string(api.ResourceLimitsMemory), +) + // NewPodEvaluator returns an evaluator that can evaluate pods -// if the specified shared informer factory is not nil, evaluator may use it to support listing functions. -func NewPodEvaluator(kubeClient clientset.Interface, f informers.SharedInformerFactory, clock clock.Clock) quota.Evaluator { - listFuncByNamespace := listPodsByNamespaceFuncUsingClient(kubeClient) - if f != nil { - listFuncByNamespace = generic.ListResourceUsingInformerFunc(f, v1.SchemeGroupVersion.WithResource("pods")) - } - return &podEvaluator{ - listFuncByNamespace: listFuncByNamespace, - clock: clock, - } +func NewPodEvaluator(f quota.ListerForResourceFunc, clock clock.Clock) quota.Evaluator { + listFuncByNamespace := generic.ListResourceUsingListerFunc(f, v1.SchemeGroupVersion.WithResource("pods")) + podEvaluator := &podEvaluator{listFuncByNamespace: listFuncByNamespace, clock: clock} + return podEvaluator } // podEvaluator knows how to measure usage of pods. @@ -107,26 +116,11 @@ func (p *podEvaluator) Constraints(required []api.ResourceName, item runtime.Obj return fmt.Errorf("Unexpected input object %v", item) } - // Pod level resources are often set during admission control - // As a consequence, we want to verify that resources are valid prior - // to ever charging quota prematurely in case they are not. - allErrs := field.ErrorList{} - fldPath := field.NewPath("spec").Child("containers") - for i, ctr := range pod.Spec.Containers { - allErrs = append(allErrs, validation.ValidateResourceRequirements(&ctr.Resources, fldPath.Index(i).Child("resources"))...) - } - fldPath = field.NewPath("spec").Child("initContainers") - for i, ctr := range pod.Spec.InitContainers { - allErrs = append(allErrs, validation.ValidateResourceRequirements(&ctr.Resources, fldPath.Index(i).Child("resources"))...) - } - if len(allErrs) > 0 { - return allErrs.ToAggregate() - } - - // TODO: fix this when we have pod level resource requirements - // since we do not yet pod level requests/limits, we need to ensure each - // container makes an explict request or limit for a quota tracked resource - requiredSet := quota.ToSet(required) + // BACKWARD COMPATIBILITY REQUIREMENT: if we quota cpu or memory, then each container + // must make an explicit request for the resource. this was a mistake. it coupled + // validation with resource counting, but we did this before QoS was even defined. + // let's not make that mistake again with other resources now that QoS is defined. + requiredSet := quota.ToSet(required).Intersection(validationSet) missingSet := sets.NewString() for i := range pod.Spec.Containers { enforcePodContainerConstraints(&pod.Spec.Containers[i], requiredSet, missingSet) @@ -140,9 +134,9 @@ func (p *podEvaluator) Constraints(required []api.ResourceName, item runtime.Obj return fmt.Errorf("must specify %s", strings.Join(missingSet.List(), ",")) } -// GroupKind that this evaluator tracks -func (p *podEvaluator) GroupKind() schema.GroupKind { - return api.Kind("Pod") +// GroupResource that this evaluator tracks +func (p *podEvaluator) GroupResource() schema.GroupResource { + return v1.SchemeGroupVersion.WithResource("pods").GroupResource() } // Handles returns true if the evaluator should handle the specified attributes. @@ -168,7 +162,14 @@ func (p *podEvaluator) Matches(resourceQuota *api.ResourceQuota, item runtime.Ob // MatchingResources takes the input specified list of resources and returns the set of resources it matches. func (p *podEvaluator) MatchingResources(input []api.ResourceName) []api.ResourceName { - return quota.Intersection(input, podResources) + result := quota.Intersection(input, podResources) + for _, resource := range input { + if quota.ContainsPrefix(podResourcePrefixes, resource) { + result = append(result, resource) + } + } + + return result } // Usage knows how to measure usage associated with pods @@ -190,7 +191,7 @@ var _ quota.Evaluator = &podEvaluator{} func enforcePodContainerConstraints(container *api.Container, requiredSet, missingSet sets.String) { requests := container.Resources.Requests limits := container.Resources.Limits - containerUsage := podUsageHelper(requests, limits) + containerUsage := podComputeUsageHelper(requests, limits) containerSet := quota.ToSet(quota.ResourceNames(containerUsage)) if !containerSet.Equal(requiredSet) { difference := requiredSet.Difference(containerSet) @@ -198,8 +199,8 @@ func enforcePodContainerConstraints(container *api.Container, requiredSet, missi } } -// podUsageHelper can summarize the pod quota usage based on requests and limits -func podUsageHelper(requests api.ResourceList, limits api.ResourceList) api.ResourceList { +// podComputeUsageHelper can summarize the pod compute quota usage based on requests and limits +func podComputeUsageHelper(requests api.ResourceList, limits api.ResourceList) api.ResourceList { result := api.ResourceList{} result[api.ResourcePods] = resource.MustParse("1") if request, found := requests[api.ResourceCPU]; found { @@ -223,6 +224,18 @@ func podUsageHelper(requests api.ResourceList, limits api.ResourceList) api.Reso if limit, found := limits[api.ResourceEphemeralStorage]; found { result[api.ResourceLimitsEphemeralStorage] = limit } + for resource, request := range requests { + if quota.ContainsPrefix(requestedResourcePrefixes, resource) { + result[resource] = request + result[maskResourceWithPrefix(resource, requestsPrefix)] = request + } + } + for resource, limit := range limits { + if quota.ContainsPrefix(requestedResourcePrefixes, resource) { + result[maskResourceWithPrefix(resource, limitsPrefix)] = limit + } + } + return result } @@ -230,7 +243,7 @@ func toInternalPodOrError(obj runtime.Object) (*api.Pod, error) { pod := &api.Pod{} switch t := obj.(type) { case *v1.Pod: - if err := k8s_api_v1.Convert_v1_Pod_To_api_Pod(t, pod, nil); err != nil { + if err := k8s_api_v1.Convert_v1_Pod_To_core_Pod(t, pod, nil); err != nil { return nil, err } case *api.Pod: @@ -269,18 +282,21 @@ func PodUsageFunc(obj runtime.Object, clock clock.Clock) (api.ResourceList, erro if err != nil { return api.ResourceList{}, err } - // by convention, we do not quota pods that have reached end-of life + + // always quota the object count (even if the pod is end of life) + // object count quotas track all objects that are in storage. + // where "pods" tracks all pods that have not reached a terminal state, + // count/pods tracks all pods independent of state. + result := api.ResourceList{ + podObjectCountName: *(resource.NewQuantity(1, resource.DecimalSI)), + } + + // by convention, we do not quota compute resources that have reached end-of life + // note: the "pods" resource is considered a compute resource since it is tied to life-cycle. if !QuotaPod(pod, clock) { - return api.ResourceList{}, nil - } - // Only charge pod count for uninitialized pod. - if utilfeature.DefaultFeatureGate.Enabled(features.Initializers) { - if !initialization.IsInitialized(pod.Initializers) { - result := api.ResourceList{} - result[api.ResourcePods] = resource.MustParse("1") - return result, nil - } + return result, nil } + requests := api.ResourceList{} limits := api.ResourceList{} // TODO: ideally, we have pod level requests and limits in the future. @@ -296,7 +312,8 @@ func PodUsageFunc(obj runtime.Object, clock clock.Clock) (api.ResourceList, erro limits = quota.Max(limits, pod.Spec.InitContainers[i].Resources.Limits) } - return podUsageHelper(requests, limits), nil + result = quota.Add(result, podComputeUsageHelper(requests, limits)) + return result, nil } func isBestEffort(pod *api.Pod) bool { diff --git a/pkg/quota/evaluator/core/pods_test.go b/pkg/quota/evaluator/core/pods_test.go index bb3714f8abe..35febe9374e 100644 --- a/pkg/quota/evaluator/core/pods_test.go +++ b/pkg/quota/evaluator/core/pods_test.go @@ -22,10 +22,11 @@ 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/util/clock" - "k8s.io/client-go/kubernetes/fake" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/quota" + "k8s.io/kubernetes/pkg/quota/generic" "k8s.io/kubernetes/pkg/util/node" ) @@ -35,32 +36,6 @@ func TestPodConstraintsFunc(t *testing.T) { required []api.ResourceName err string }{ - "init container resource invalid": { - pod: &api.Pod{ - Spec: api.PodSpec{ - InitContainers: []api.Container{{ - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("2m")}, - Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("1m")}, - }, - }}, - }, - }, - err: `spec.initContainers[0].resources.requests: Invalid value: "2m": must be less than or equal to cpu limit`, - }, - "container resource invalid": { - pod: &api.Pod{ - Spec: api.PodSpec{ - Containers: []api.Container{{ - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("2m")}, - Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("1m")}, - }, - }}, - }, - }, - err: `spec.containers[0].resources.requests: Invalid value: "2m": must be less than or equal to cpu limit`, - }, "init container resource missing": { pod: &api.Pod{ Spec: api.PodSpec{ @@ -90,8 +65,7 @@ func TestPodConstraintsFunc(t *testing.T) { err: `must specify memory`, }, } - kubeClient := fake.NewSimpleClientset() - evaluator := NewPodEvaluator(kubeClient, nil, clock.RealClock{}) + evaluator := NewPodEvaluator(nil, clock.RealClock{}) for testName, test := range testCases { err := evaluator.Constraints(test.required, test.pod) switch { @@ -104,9 +78,8 @@ func TestPodConstraintsFunc(t *testing.T) { } func TestPodEvaluatorUsage(t *testing.T) { - kubeClient := fake.NewSimpleClientset() fakeClock := clock.NewFakeClock(time.Now()) - evaluator := NewPodEvaluator(kubeClient, nil, fakeClock) + evaluator := NewPodEvaluator(nil, fakeClock) // fields use to simulate a pod undergoing termination // note: we set the deletion time in the past @@ -135,6 +108,7 @@ func TestPodEvaluatorUsage(t *testing.T) { api.ResourceLimitsCPU: resource.MustParse("2m"), api.ResourcePods: resource.MustParse("1"), api.ResourceCPU: resource.MustParse("1m"), + generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), }, }, "init container MEM": { @@ -153,6 +127,7 @@ func TestPodEvaluatorUsage(t *testing.T) { api.ResourceLimitsMemory: resource.MustParse("2m"), api.ResourcePods: resource.MustParse("1"), api.ResourceMemory: resource.MustParse("1m"), + generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), }, }, "init container local ephemeral storage": { @@ -171,6 +146,24 @@ func TestPodEvaluatorUsage(t *testing.T) { api.ResourceRequestsEphemeralStorage: resource.MustParse("32Mi"), api.ResourceLimitsEphemeralStorage: resource.MustParse("64Mi"), api.ResourcePods: resource.MustParse("1"), + generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), + }, + }, + "init container hugepages": { + pod: &api.Pod{ + Spec: api.PodSpec{ + InitContainers: []api.Container{{ + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{api.ResourceName(api.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("100Mi")}, + }, + }}, + }, + }, + usage: api.ResourceList{ + api.ResourceName(api.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("100Mi"), + api.ResourceName(api.ResourceRequestsHugePagesPrefix + "2Mi"): resource.MustParse("100Mi"), + api.ResourcePods: resource.MustParse("1"), + generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), }, }, "container CPU": { @@ -189,6 +182,7 @@ func TestPodEvaluatorUsage(t *testing.T) { api.ResourceLimitsCPU: resource.MustParse("2m"), api.ResourcePods: resource.MustParse("1"), api.ResourceCPU: resource.MustParse("1m"), + generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), }, }, "container MEM": { @@ -207,6 +201,7 @@ func TestPodEvaluatorUsage(t *testing.T) { api.ResourceLimitsMemory: resource.MustParse("2m"), api.ResourcePods: resource.MustParse("1"), api.ResourceMemory: resource.MustParse("1m"), + generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), }, }, "container local ephemeral storage": { @@ -225,6 +220,24 @@ func TestPodEvaluatorUsage(t *testing.T) { api.ResourceRequestsEphemeralStorage: resource.MustParse("32Mi"), api.ResourceLimitsEphemeralStorage: resource.MustParse("64Mi"), api.ResourcePods: resource.MustParse("1"), + generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), + }, + }, + "container hugepages": { + pod: &api.Pod{ + Spec: api.PodSpec{ + Containers: []api.Container{{ + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{api.ResourceName(api.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("100Mi")}, + }, + }}, + }, + }, + usage: api.ResourceList{ + api.ResourceName(api.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("100Mi"), + api.ResourceName(api.ResourceRequestsHugePagesPrefix + "2Mi"): resource.MustParse("100Mi"), + api.ResourcePods: resource.MustParse("1"), + generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), }, }, "init container maximums override sum of containers": { @@ -292,6 +305,7 @@ func TestPodEvaluatorUsage(t *testing.T) { api.ResourcePods: resource.MustParse("1"), api.ResourceCPU: resource.MustParse("4"), api.ResourceMemory: resource.MustParse("100M"), + generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), }, }, "pod deletion timestamp exceeded": { @@ -321,7 +335,9 @@ func TestPodEvaluatorUsage(t *testing.T) { }, }, }, - usage: api.ResourceList{}, + usage: api.ResourceList{ + generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), + }, }, "pod deletion timestamp not exceeded": { pod: &api.Pod{ @@ -352,6 +368,7 @@ func TestPodEvaluatorUsage(t *testing.T) { api.ResourceLimitsCPU: resource.MustParse("2"), api.ResourcePods: resource.MustParse("1"), api.ResourceCPU: resource.MustParse("1"), + generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), }, }, } diff --git a/pkg/quota/evaluator/core/registry.go b/pkg/quota/evaluator/core/registry.go index e6278c731c6..5a642b386a9 100644 --- a/pkg/quota/evaluator/core/registry.go +++ b/pkg/quota/evaluator/core/registry.go @@ -17,33 +17,34 @@ limitations under the License. package core import ( + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/clock" - "k8s.io/client-go/informers" - clientset "k8s.io/client-go/kubernetes" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/quota" "k8s.io/kubernetes/pkg/quota/generic" ) -// NewRegistry returns a registry that knows how to deal with core kubernetes resources -// If an informer factory is provided, evaluators will use them. -func NewRegistry(kubeClient clientset.Interface, f informers.SharedInformerFactory) quota.Registry { - pod := NewPodEvaluator(kubeClient, f, clock.RealClock{}) - service := NewServiceEvaluator(kubeClient, f) - replicationController := NewReplicationControllerEvaluator(kubeClient, f) - resourceQuota := NewResourceQuotaEvaluator(kubeClient, f) - secret := NewSecretEvaluator(kubeClient, f) - configMap := NewConfigMapEvaluator(kubeClient, f) - persistentVolumeClaim := NewPersistentVolumeClaimEvaluator(kubeClient, f) - return &generic.GenericRegistry{ - InternalEvaluators: map[schema.GroupKind]quota.Evaluator{ - pod.GroupKind(): pod, - service.GroupKind(): service, - replicationController.GroupKind(): replicationController, - secret.GroupKind(): secret, - configMap.GroupKind(): configMap, - resourceQuota.GroupKind(): resourceQuota, - persistentVolumeClaim.GroupKind(): persistentVolumeClaim, - }, - } +// legacyObjectCountAliases are what we used to do simple object counting quota with mapped to alias +var legacyObjectCountAliases = map[schema.GroupVersionResource]api.ResourceName{ + v1.SchemeGroupVersion.WithResource("configmaps"): api.ResourceConfigMaps, + v1.SchemeGroupVersion.WithResource("resourcequotas"): api.ResourceQuotas, + v1.SchemeGroupVersion.WithResource("replicationcontrollers"): api.ResourceReplicationControllers, + v1.SchemeGroupVersion.WithResource("secrets"): api.ResourceSecrets, +} + +// NewEvaluators returns the list of static evaluators that manage more than counts +func NewEvaluators(f quota.ListerForResourceFunc) []quota.Evaluator { + // these evaluators have special logic + result := []quota.Evaluator{ + NewPodEvaluator(f, clock.RealClock{}), + NewServiceEvaluator(f), + NewPersistentVolumeClaimEvaluator(f), + } + // these evaluators require an alias for backwards compatibility + for gvr, alias := range legacyObjectCountAliases { + result = append(result, + generic.NewObjectCountEvaluator(false, gvr.GroupResource(), generic.ListResourceUsingListerFunc(f, gvr), alias)) + } + return result } diff --git a/pkg/quota/evaluator/core/replication_controllers.go b/pkg/quota/evaluator/core/replication_controllers.go deleted file mode 100644 index 06261460834..00000000000 --- a/pkg/quota/evaluator/core/replication_controllers.go +++ /dev/null @@ -1,61 +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 core - -import ( - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/informers" - clientset "k8s.io/client-go/kubernetes" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/quota" - "k8s.io/kubernetes/pkg/quota/generic" -) - -// listReplicationControllersByNamespaceFuncUsingClient returns a replicationController listing function based on the provided client. -func listReplicationControllersByNamespaceFuncUsingClient(kubeClient clientset.Interface) generic.ListFuncByNamespace { - // TODO: ideally, we could pass dynamic client pool down into this code, and have one way of doing this. - // unfortunately, dynamic client works with Unstructured objects, and when we calculate Usage, we require - // structured objects. - return func(namespace string, options metav1.ListOptions) ([]runtime.Object, error) { - itemList, err := kubeClient.Core().ReplicationControllers(namespace).List(options) - if err != nil { - return nil, err - } - results := make([]runtime.Object, 0, len(itemList.Items)) - for i := range itemList.Items { - results = append(results, &itemList.Items[i]) - } - return results, nil - } -} - -// NewReplicationControllerEvaluator returns an evaluator that can evaluate replicationControllers -// if the specified shared informer factory is not nil, evaluator may use it to support listing functions. -func NewReplicationControllerEvaluator(kubeClient clientset.Interface, f informers.SharedInformerFactory) quota.Evaluator { - listFuncByNamespace := listReplicationControllersByNamespaceFuncUsingClient(kubeClient) - if f != nil { - listFuncByNamespace = generic.ListResourceUsingInformerFunc(f, v1.SchemeGroupVersion.WithResource("replicationcontrollers")) - } - return &generic.ObjectCountEvaluator{ - AllowCreateOnUpdate: false, - InternalGroupKind: api.Kind("ReplicationController"), - ResourceName: api.ResourceReplicationControllers, - ListFuncByNamespace: listFuncByNamespace, - } -} diff --git a/pkg/quota/evaluator/core/resource_quotas.go b/pkg/quota/evaluator/core/resource_quotas.go deleted file mode 100644 index 50033e068c2..00000000000 --- a/pkg/quota/evaluator/core/resource_quotas.go +++ /dev/null @@ -1,61 +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 core - -import ( - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/informers" - clientset "k8s.io/client-go/kubernetes" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/quota" - "k8s.io/kubernetes/pkg/quota/generic" -) - -// listResourceQuotasByNamespaceFuncUsingClient returns a resourceQuota listing function based on the provided client. -func listResourceQuotasByNamespaceFuncUsingClient(kubeClient clientset.Interface) generic.ListFuncByNamespace { - // TODO: ideally, we could pass dynamic client pool down into this code, and have one way of doing this. - // unfortunately, dynamic client works with Unstructured objects, and when we calculate Usage, we require - // structured objects. - return func(namespace string, options metav1.ListOptions) ([]runtime.Object, error) { - itemList, err := kubeClient.Core().ResourceQuotas(namespace).List(options) - if err != nil { - return nil, err - } - results := make([]runtime.Object, 0, len(itemList.Items)) - for i := range itemList.Items { - results = append(results, &itemList.Items[i]) - } - return results, nil - } -} - -// NewResourceQuotaEvaluator returns an evaluator that can evaluate resourceQuotas -// if the specified shared informer factory is not nil, evaluator may use it to support listing functions. -func NewResourceQuotaEvaluator(kubeClient clientset.Interface, f informers.SharedInformerFactory) quota.Evaluator { - listFuncByNamespace := listResourceQuotasByNamespaceFuncUsingClient(kubeClient) - if f != nil { - listFuncByNamespace = generic.ListResourceUsingInformerFunc(f, v1.SchemeGroupVersion.WithResource("resourcequotas")) - } - return &generic.ObjectCountEvaluator{ - AllowCreateOnUpdate: false, - InternalGroupKind: api.Kind("ResourceQuota"), - ResourceName: api.ResourceQuotas, - ListFuncByNamespace: listFuncByNamespace, - } -} diff --git a/pkg/quota/evaluator/core/secrets.go b/pkg/quota/evaluator/core/secrets.go deleted file mode 100644 index bfd678c95aa..00000000000 --- a/pkg/quota/evaluator/core/secrets.go +++ /dev/null @@ -1,61 +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 core - -import ( - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/informers" - clientset "k8s.io/client-go/kubernetes" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/quota" - "k8s.io/kubernetes/pkg/quota/generic" -) - -// listSecretsByNamespaceFuncUsingClient returns a secret listing function based on the provided client. -func listSecretsByNamespaceFuncUsingClient(kubeClient clientset.Interface) generic.ListFuncByNamespace { - // TODO: ideally, we could pass dynamic client pool down into this code, and have one way of doing this. - // unfortunately, dynamic client works with Unstructured objects, and when we calculate Usage, we require - // structured objects. - return func(namespace string, options metav1.ListOptions) ([]runtime.Object, error) { - itemList, err := kubeClient.Core().Secrets(namespace).List(options) - if err != nil { - return nil, err - } - results := make([]runtime.Object, 0, len(itemList.Items)) - for i := range itemList.Items { - results = append(results, &itemList.Items[i]) - } - return results, nil - } -} - -// NewSecretEvaluator returns an evaluator that can evaluate secrets -// if the specified shared informer factory is not nil, evaluator may use it to support listing functions. -func NewSecretEvaluator(kubeClient clientset.Interface, f informers.SharedInformerFactory) quota.Evaluator { - listFuncByNamespace := listSecretsByNamespaceFuncUsingClient(kubeClient) - if f != nil { - listFuncByNamespace = generic.ListResourceUsingInformerFunc(f, v1.SchemeGroupVersion.WithResource("secrets")) - } - return &generic.ObjectCountEvaluator{ - AllowCreateOnUpdate: false, - InternalGroupKind: api.Kind("Secret"), - ResourceName: api.ResourceSecrets, - ListFuncByNamespace: listFuncByNamespace, - } -} diff --git a/pkg/quota/evaluator/core/services.go b/pkg/quota/evaluator/core/services.go index 91a5ef0a059..f4dec5973a2 100644 --- a/pkg/quota/evaluator/core/services.go +++ b/pkg/quota/evaluator/core/services.go @@ -18,58 +18,34 @@ package core import ( "fmt" - "strings" "k8s.io/api/core/v1" "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" - "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apiserver/pkg/admission" - "k8s.io/client-go/informers" - clientset "k8s.io/client-go/kubernetes" - "k8s.io/kubernetes/pkg/api" - k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" + api "k8s.io/kubernetes/pkg/apis/core" + k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" "k8s.io/kubernetes/pkg/quota" "k8s.io/kubernetes/pkg/quota/generic" ) +// the name used for object count quota +var serviceObjectCountName = generic.ObjectCountQuotaResourceNameFor(v1.SchemeGroupVersion.WithResource("services").GroupResource()) + // serviceResources are the set of resources managed by quota associated with services. var serviceResources = []api.ResourceName{ + serviceObjectCountName, api.ResourceServices, api.ResourceServicesNodePorts, api.ResourceServicesLoadBalancers, } -// listServicesByNamespaceFuncUsingClient returns a service listing function based on the provided client. -func listServicesByNamespaceFuncUsingClient(kubeClient clientset.Interface) generic.ListFuncByNamespace { - // TODO: ideally, we could pass dynamic client pool down into this code, and have one way of doing this. - // unfortunately, dynamic client works with Unstructured objects, and when we calculate Usage, we require - // structured objects. - return func(namespace string, options metav1.ListOptions) ([]runtime.Object, error) { - itemList, err := kubeClient.Core().Services(namespace).List(options) - if err != nil { - return nil, err - } - results := make([]runtime.Object, 0, len(itemList.Items)) - for i := range itemList.Items { - results = append(results, &itemList.Items[i]) - } - return results, nil - } -} - -// NewServiceEvaluator returns an evaluator that can evaluate services -// if the specified shared informer factory is not nil, evaluator may use it to support listing functions. -func NewServiceEvaluator(kubeClient clientset.Interface, f informers.SharedInformerFactory) quota.Evaluator { - listFuncByNamespace := listServicesByNamespaceFuncUsingClient(kubeClient) - if f != nil { - listFuncByNamespace = generic.ListResourceUsingInformerFunc(f, v1.SchemeGroupVersion.WithResource("services")) - } - return &serviceEvaluator{ - listFuncByNamespace: listFuncByNamespace, - } +// NewServiceEvaluator returns an evaluator that can evaluate services. +func NewServiceEvaluator(f quota.ListerForResourceFunc) quota.Evaluator { + listFuncByNamespace := generic.ListResourceUsingListerFunc(f, v1.SchemeGroupVersion.WithResource("services")) + serviceEvaluator := &serviceEvaluator{listFuncByNamespace: listFuncByNamespace} + return serviceEvaluator } // serviceEvaluator knows how to measure usage for services. @@ -80,31 +56,13 @@ type serviceEvaluator struct { // Constraints verifies that all required resources are present on the item func (p *serviceEvaluator) Constraints(required []api.ResourceName, item runtime.Object) error { - service, ok := item.(*api.Service) - if !ok { - return fmt.Errorf("unexpected input object %v", item) - } - - requiredSet := quota.ToSet(required) - missingSet := sets.NewString() - serviceUsage, err := p.Usage(service) - if err != nil { - return err - } - serviceSet := quota.ToSet(quota.ResourceNames(serviceUsage)) - if diff := requiredSet.Difference(serviceSet); len(diff) > 0 { - missingSet.Insert(diff.List()...) - } - - if len(missingSet) == 0 { - return nil - } - return fmt.Errorf("must specify %s", strings.Join(missingSet.List(), ",")) + // this is a no-op for services + return nil } -// GroupKind that this evaluator tracks -func (p *serviceEvaluator) GroupKind() schema.GroupKind { - return api.Kind("Service") +// GroupResource that this evaluator tracks +func (p *serviceEvaluator) GroupResource() schema.GroupResource { + return v1.SchemeGroupVersion.WithResource("services").GroupResource() } // Handles returns true of the evaluator should handle the specified operation. @@ -129,7 +87,7 @@ func toInternalServiceOrError(obj runtime.Object) (*api.Service, error) { svc := &api.Service{} switch t := obj.(type) { case *v1.Service: - if err := k8s_api_v1.Convert_v1_Service_To_api_Service(t, svc, nil); err != nil { + if err := k8s_api_v1.Convert_v1_Service_To_core_Service(t, svc, nil); err != nil { return nil, err } case *api.Service: @@ -149,6 +107,7 @@ func (p *serviceEvaluator) Usage(item runtime.Object) (api.ResourceList, error) } ports := len(svc.Spec.Ports) // default service usage + result[serviceObjectCountName] = *(resource.NewQuantity(1, resource.DecimalSI)) result[api.ResourceServices] = *(resource.NewQuantity(1, resource.DecimalSI)) result[api.ResourceServicesLoadBalancers] = resource.Quantity{Format: resource.DecimalSI} result[api.ResourceServicesNodePorts] = resource.Quantity{Format: resource.DecimalSI} diff --git a/pkg/quota/evaluator/core/services_test.go b/pkg/quota/evaluator/core/services_test.go index 3f42290824f..601397ce193 100644 --- a/pkg/quota/evaluator/core/services_test.go +++ b/pkg/quota/evaluator/core/services_test.go @@ -20,14 +20,14 @@ import ( "testing" "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/client-go/kubernetes/fake" - "k8s.io/kubernetes/pkg/api" + "k8s.io/apimachinery/pkg/runtime/schema" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/quota" + "k8s.io/kubernetes/pkg/quota/generic" ) func TestServiceEvaluatorMatchesResources(t *testing.T) { - kubeClient := fake.NewSimpleClientset() - evaluator := NewServiceEvaluator(kubeClient, nil) + evaluator := NewServiceEvaluator(nil) // we give a lot of resources input := []api.ResourceName{ api.ResourceConfigMaps, @@ -49,8 +49,7 @@ func TestServiceEvaluatorMatchesResources(t *testing.T) { } func TestServiceEvaluatorUsage(t *testing.T) { - kubeClient := fake.NewSimpleClientset() - evaluator := NewServiceEvaluator(kubeClient, nil) + evaluator := NewServiceEvaluator(nil) testCases := map[string]struct { service *api.Service usage api.ResourceList @@ -65,6 +64,7 @@ func TestServiceEvaluatorUsage(t *testing.T) { api.ResourceServicesNodePorts: resource.MustParse("0"), api.ResourceServicesLoadBalancers: resource.MustParse("1"), api.ResourceServices: resource.MustParse("1"), + generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "services"}): resource.MustParse("1"), }, }, "loadbalancer_ports": { @@ -82,6 +82,7 @@ func TestServiceEvaluatorUsage(t *testing.T) { api.ResourceServicesNodePorts: resource.MustParse("1"), api.ResourceServicesLoadBalancers: resource.MustParse("1"), api.ResourceServices: resource.MustParse("1"), + generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "services"}): resource.MustParse("1"), }, }, "clusterip": { @@ -91,9 +92,10 @@ func TestServiceEvaluatorUsage(t *testing.T) { }, }, usage: api.ResourceList{ - api.ResourceServices: resource.MustParse("1"), - api.ResourceServicesNodePorts: resource.MustParse("0"), - api.ResourceServicesLoadBalancers: resource.MustParse("0"), + api.ResourceServices: resource.MustParse("1"), + api.ResourceServicesNodePorts: resource.MustParse("0"), + api.ResourceServicesLoadBalancers: resource.MustParse("0"), + generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "services"}): resource.MustParse("1"), }, }, "nodeports": { @@ -108,9 +110,10 @@ func TestServiceEvaluatorUsage(t *testing.T) { }, }, usage: api.ResourceList{ - api.ResourceServices: resource.MustParse("1"), - api.ResourceServicesNodePorts: resource.MustParse("1"), - api.ResourceServicesLoadBalancers: resource.MustParse("0"), + api.ResourceServices: resource.MustParse("1"), + api.ResourceServicesNodePorts: resource.MustParse("1"), + api.ResourceServicesLoadBalancers: resource.MustParse("0"), + generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "services"}): resource.MustParse("1"), }, }, "multi-nodeports": { @@ -128,9 +131,10 @@ func TestServiceEvaluatorUsage(t *testing.T) { }, }, usage: api.ResourceList{ - api.ResourceServices: resource.MustParse("1"), - api.ResourceServicesNodePorts: resource.MustParse("2"), - api.ResourceServicesLoadBalancers: resource.MustParse("0"), + api.ResourceServices: resource.MustParse("1"), + api.ResourceServicesNodePorts: resource.MustParse("2"), + api.ResourceServicesLoadBalancers: resource.MustParse("0"), + generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "services"}): resource.MustParse("1"), }, }, } @@ -198,8 +202,7 @@ func TestServiceConstraintsFunc(t *testing.T) { }, } - kubeClient := fake.NewSimpleClientset() - evaluator := NewServiceEvaluator(kubeClient, nil) + evaluator := NewServiceEvaluator(nil) for testName, test := range testCases { err := evaluator.Constraints(test.required, test.service) switch { diff --git a/pkg/quota/generic/BUILD b/pkg/quota/generic/BUILD index 1eeaf7161b1..db871e85fa1 100644 --- a/pkg/quota/generic/BUILD +++ b/pkg/quota/generic/BUILD @@ -8,20 +8,21 @@ load( go_library( name = "go_default_library", srcs = [ + "configuration.go", "evaluator.go", "registry.go", ], importpath = "k8s.io/kubernetes/pkg/quota/generic", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/quota:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", "//vendor/k8s.io/client-go/informers:go_default_library", + "//vendor/k8s.io/client-go/tools/cache:go_default_library", ], ) diff --git a/pkg/quota/generic/configuration.go b/pkg/quota/generic/configuration.go new file mode 100644 index 00000000000..59c009e13d3 --- /dev/null +++ b/pkg/quota/generic/configuration.go @@ -0,0 +1,44 @@ +/* +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 generic + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kubernetes/pkg/quota" +) + +// implements a basic configuration +type simpleConfiguration struct { + evaluators []quota.Evaluator + ignoredResources map[schema.GroupResource]struct{} +} + +// NewConfiguration creates a quota configuration +func NewConfiguration(evaluators []quota.Evaluator, ignoredResources map[schema.GroupResource]struct{}) quota.Configuration { + return &simpleConfiguration{ + evaluators: evaluators, + ignoredResources: ignoredResources, + } +} + +func (c *simpleConfiguration) IgnoredResources() map[schema.GroupResource]struct{} { + return c.ignoredResources +} + +func (c *simpleConfiguration) Evaluators() []quota.Evaluator { + return c.evaluators +} diff --git a/pkg/quota/generic/evaluator.go b/pkg/quota/generic/evaluator.go index 1b574bb6b52..5bcb1cb20ff 100644 --- a/pkg/quota/generic/evaluator.go +++ b/pkg/quota/generic/evaluator.go @@ -20,33 +20,51 @@ import ( "fmt" "k8s.io/apimachinery/pkg/api/resource" - 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/apiserver/pkg/admission" "k8s.io/client-go/informers" - "k8s.io/kubernetes/pkg/api" + "k8s.io/client-go/tools/cache" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/quota" ) -// ListResourceUsingInformerFunc returns a listing function based on the shared informer factory for the specified resource. -func ListResourceUsingInformerFunc(f informers.SharedInformerFactory, resource schema.GroupVersionResource) ListFuncByNamespace { - return func(namespace string, options metav1.ListOptions) ([]runtime.Object, error) { - labelSelector, err := labels.Parse(options.LabelSelector) +// InformerForResourceFunc knows how to provision an informer +type InformerForResourceFunc func(schema.GroupVersionResource) (informers.GenericInformer, error) + +// ListerFuncForResourceFunc knows how to provision a lister from an informer func +func ListerFuncForResourceFunc(f InformerForResourceFunc) quota.ListerForResourceFunc { + return func(gvr schema.GroupVersionResource) (cache.GenericLister, error) { + informer, err := f(gvr) if err != nil { return nil, err } - informer, err := f.ForResource(resource) - if err != nil { - return nil, err - } - return informer.Lister().ByNamespace(namespace).List(labelSelector) + return informer.Lister(), nil } } +// ListResourceUsingListerFunc returns a listing function based on the shared informer factory for the specified resource. +func ListResourceUsingListerFunc(l quota.ListerForResourceFunc, resource schema.GroupVersionResource) ListFuncByNamespace { + return func(namespace string) ([]runtime.Object, error) { + lister, err := l(resource) + if err != nil { + return nil, err + } + return lister.ByNamespace(namespace).List(labels.Everything()) + } +} + +// ObjectCountQuotaResourceNameFor returns the object count quota name for specified groupResource +func ObjectCountQuotaResourceNameFor(groupResource schema.GroupResource) api.ResourceName { + if len(groupResource.Group) == 0 { + return api.ResourceName("count/" + groupResource.Resource) + } + return api.ResourceName("count/" + groupResource.Resource + "." + groupResource.Group) +} + // ListFuncByNamespace knows how to list resources in a namespace -type ListFuncByNamespace func(namespace string, options metav1.ListOptions) ([]runtime.Object, error) +type ListFuncByNamespace func(namespace string) ([]runtime.Object, error) // MatchesScopeFunc knows how to evaluate if an object matches a scope type MatchesScopeFunc func(scope api.ResourceQuotaScope, object runtime.Object) (bool, error) @@ -91,9 +109,7 @@ func CalculateUsageStats(options quota.UsageStatsOptions, for _, resourceName := range options.Resources { result.Used[resourceName] = resource.Quantity{Format: resource.DecimalSI} } - items, err := listFunc(options.Namespace, metav1.ListOptions{ - LabelSelector: labels.Everything().String(), - }) + items, err := listFunc(options.Namespace) if err != nil { return result, fmt.Errorf("failed to list content: %v", err) } @@ -121,63 +137,86 @@ func CalculateUsageStats(options quota.UsageStatsOptions, return result, nil } -// ObjectCountEvaluator provides an implementation for quota.Evaluator +// objectCountEvaluator provides an implementation for quota.Evaluator // that associates usage of the specified resource based on the number of items // returned by the specified listing function. -type ObjectCountEvaluator struct { - // AllowCreateOnUpdate if true will ensure the evaluator tracks create +type objectCountEvaluator struct { + // allowCreateOnUpdate if true will ensure the evaluator tracks create // and update operations. - AllowCreateOnUpdate bool - // GroupKind that this evaluator tracks. - InternalGroupKind schema.GroupKind + allowCreateOnUpdate bool + // GroupResource that this evaluator tracks. + // It is used to construct a generic object count quota name + groupResource schema.GroupResource // A function that knows how to list resources by namespace. // TODO move to dynamic client in future - ListFuncByNamespace ListFuncByNamespace - // Name associated with this resource in the quota. - ResourceName api.ResourceName + listFuncByNamespace ListFuncByNamespace + // Names associated with this resource in the quota for generic counting. + resourceNames []api.ResourceName } // Constraints returns an error if the configured resource name is not in the required set. -func (o *ObjectCountEvaluator) Constraints(required []api.ResourceName, item runtime.Object) error { - if !quota.Contains(required, o.ResourceName) { - return fmt.Errorf("missing %s", o.ResourceName) - } +func (o *objectCountEvaluator) Constraints(required []api.ResourceName, item runtime.Object) error { + // no-op for object counting return nil } -// GroupKind that this evaluator tracks -func (o *ObjectCountEvaluator) GroupKind() schema.GroupKind { - return o.InternalGroupKind -} - // Handles returns true if the object count evaluator needs to track this attributes. -func (o *ObjectCountEvaluator) Handles(a admission.Attributes) bool { +func (o *objectCountEvaluator) Handles(a admission.Attributes) bool { operation := a.GetOperation() - return operation == admission.Create || (o.AllowCreateOnUpdate && operation == admission.Update) + return operation == admission.Create || (o.allowCreateOnUpdate && operation == admission.Update) } // Matches returns true if the evaluator matches the specified quota with the provided input item -func (o *ObjectCountEvaluator) Matches(resourceQuota *api.ResourceQuota, item runtime.Object) (bool, error) { +func (o *objectCountEvaluator) Matches(resourceQuota *api.ResourceQuota, item runtime.Object) (bool, error) { return Matches(resourceQuota, item, o.MatchingResources, MatchesNoScopeFunc) } // MatchingResources takes the input specified list of resources and returns the set of resources it matches. -func (o *ObjectCountEvaluator) MatchingResources(input []api.ResourceName) []api.ResourceName { - return quota.Intersection(input, []api.ResourceName{o.ResourceName}) +func (o *objectCountEvaluator) MatchingResources(input []api.ResourceName) []api.ResourceName { + return quota.Intersection(input, o.resourceNames) } // Usage returns the resource usage for the specified object -func (o *ObjectCountEvaluator) Usage(object runtime.Object) (api.ResourceList, error) { +func (o *objectCountEvaluator) Usage(object runtime.Object) (api.ResourceList, error) { quantity := resource.NewQuantity(1, resource.DecimalSI) - return api.ResourceList{ - o.ResourceName: *quantity, - }, nil + resourceList := api.ResourceList{} + for _, resourceName := range o.resourceNames { + resourceList[resourceName] = *quantity + } + return resourceList, nil +} + +// GroupResource tracked by this evaluator +func (o *objectCountEvaluator) GroupResource() schema.GroupResource { + return o.groupResource } // UsageStats calculates aggregate usage for the object. -func (o *ObjectCountEvaluator) UsageStats(options quota.UsageStatsOptions) (quota.UsageStats, error) { - return CalculateUsageStats(options, o.ListFuncByNamespace, MatchesNoScopeFunc, o.Usage) +func (o *objectCountEvaluator) UsageStats(options quota.UsageStatsOptions) (quota.UsageStats, error) { + return CalculateUsageStats(options, o.listFuncByNamespace, MatchesNoScopeFunc, o.Usage) } // Verify implementation of interface at compile time. -var _ quota.Evaluator = &ObjectCountEvaluator{} +var _ quota.Evaluator = &objectCountEvaluator{} + +// NewObjectCountEvaluator returns an evaluator that can perform generic +// object quota counting. It allows an optional alias for backwards compatibilty +// purposes for the legacy object counting names in quota. Unless its supporting +// backward compatibility, alias should not be used. +func NewObjectCountEvaluator( + allowCreateOnUpdate bool, + groupResource schema.GroupResource, listFuncByNamespace ListFuncByNamespace, + alias api.ResourceName) quota.Evaluator { + + resourceNames := []api.ResourceName{ObjectCountQuotaResourceNameFor(groupResource)} + if len(alias) > 0 { + resourceNames = append(resourceNames, alias) + } + + return &objectCountEvaluator{ + allowCreateOnUpdate: allowCreateOnUpdate, + groupResource: groupResource, + listFuncByNamespace: listFuncByNamespace, + resourceNames: resourceNames, + } +} diff --git a/pkg/quota/generic/registry.go b/pkg/quota/generic/registry.go index 5b1241dfe22..fdc38e02b1c 100644 --- a/pkg/quota/generic/registry.go +++ b/pkg/quota/generic/registry.go @@ -17,20 +17,65 @@ limitations under the License. package generic import ( + "sync" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/kubernetes/pkg/quota" ) -// Ensure it implements the required interface -var _ quota.Registry = &GenericRegistry{} - -// GenericRegistry implements Registry -type GenericRegistry struct { - // internal evaluators by group kind - InternalEvaluators map[schema.GroupKind]quota.Evaluator +// implements a basic registry +type simpleRegistry struct { + lock sync.RWMutex + // evaluators tracked by the registry + evaluators map[schema.GroupResource]quota.Evaluator } -// Evaluators returns the map of evaluators by groupKind -func (r *GenericRegistry) Evaluators() map[schema.GroupKind]quota.Evaluator { - return r.InternalEvaluators +// NewRegistry creates a simple registry with initial list of evaluators +func NewRegistry(evaluators []quota.Evaluator) quota.Registry { + return &simpleRegistry{ + evaluators: evaluatorsByGroupResource(evaluators), + } +} + +func (r *simpleRegistry) Add(e quota.Evaluator) { + r.lock.Lock() + defer r.lock.Unlock() + r.evaluators[e.GroupResource()] = e +} + +func (r *simpleRegistry) Remove(e quota.Evaluator) { + r.lock.Lock() + defer r.lock.Unlock() + delete(r.evaluators, e.GroupResource()) +} + +func (r *simpleRegistry) Get(gr schema.GroupResource) quota.Evaluator { + r.lock.RLock() + defer r.lock.RUnlock() + return r.evaluators[gr] +} + +func (r *simpleRegistry) List() []quota.Evaluator { + r.lock.RLock() + defer r.lock.RUnlock() + + return evaluatorsList(r.evaluators) +} + +// evaluatorsByGroupResource converts a list of evaluators to a map by group resource. +func evaluatorsByGroupResource(items []quota.Evaluator) map[schema.GroupResource]quota.Evaluator { + result := map[schema.GroupResource]quota.Evaluator{} + for _, item := range items { + result[item.GroupResource()] = item + } + return result +} + +// evaluatorsList converts a map of evaluators to list +func evaluatorsList(input map[schema.GroupResource]quota.Evaluator) []quota.Evaluator { + var result []quota.Evaluator + for _, item := range input { + result = append(result, item) + } + return result } diff --git a/pkg/quota/install/BUILD b/pkg/quota/install/BUILD index c315583b49c..027c5835c0d 100644 --- a/pkg/quota/install/BUILD +++ b/pkg/quota/install/BUILD @@ -12,8 +12,8 @@ go_library( deps = [ "//pkg/quota:go_default_library", "//pkg/quota/evaluator/core:go_default_library", - "//vendor/k8s.io/client-go/informers:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//pkg/quota/generic:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], ) diff --git a/pkg/quota/install/registry.go b/pkg/quota/install/registry.go index a10dd964567..367a6a40164 100644 --- a/pkg/quota/install/registry.go +++ b/pkg/quota/install/registry.go @@ -17,15 +17,42 @@ limitations under the License. package install import ( - "k8s.io/client-go/informers" - clientset "k8s.io/client-go/kubernetes" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/kubernetes/pkg/quota" "k8s.io/kubernetes/pkg/quota/evaluator/core" + "k8s.io/kubernetes/pkg/quota/generic" ) -// NewRegistry returns a registry of quota evaluators. -// If a shared informer factory is provided, it is used by evaluators rather than performing direct queries. -func NewRegistry(kubeClient clientset.Interface, f informers.SharedInformerFactory) quota.Registry { - // TODO: when quota supports resources in other api groups, we will need to merge - return core.NewRegistry(kubeClient, f) +// NewQuotaConfigurationForAdmission returns a quota configuration for admission control. +func NewQuotaConfigurationForAdmission() quota.Configuration { + evaluators := core.NewEvaluators(nil) + return generic.NewConfiguration(evaluators, DefaultIgnoredResources()) +} + +// NewQuotaConfigurationForControllers returns a quota configuration for controllers. +func NewQuotaConfigurationForControllers(f quota.ListerForResourceFunc) quota.Configuration { + evaluators := core.NewEvaluators(f) + return generic.NewConfiguration(evaluators, DefaultIgnoredResources()) +} + +// ignoredResources are ignored by quota by default +var ignoredResources = map[schema.GroupResource]struct{}{ + {Group: "extensions", Resource: "replicationcontrollers"}: {}, + {Group: "extensions", Resource: "networkpolicies"}: {}, + {Group: "", Resource: "bindings"}: {}, + {Group: "", Resource: "componentstatuses"}: {}, + {Group: "", Resource: "events"}: {}, + {Group: "authentication.k8s.io", Resource: "tokenreviews"}: {}, + {Group: "authorization.k8s.io", Resource: "subjectaccessreviews"}: {}, + {Group: "authorization.k8s.io", Resource: "selfsubjectaccessreviews"}: {}, + {Group: "authorization.k8s.io", Resource: "localsubjectaccessreviews"}: {}, + {Group: "authorization.k8s.io", Resource: "selfsubjectrulesreviews"}: {}, + {Group: "apiregistration.k8s.io", Resource: "apiservices"}: {}, + {Group: "apiextensions.k8s.io", Resource: "customresourcedefinitions"}: {}, +} + +// DefaultIgnoredResources returns the default set of resources that quota system +// should ignore. This is exposed so downstream integrators can have access to them. +func DefaultIgnoredResources() map[schema.GroupResource]struct{} { + return ignoredResources } diff --git a/pkg/quota/interfaces.go b/pkg/quota/interfaces.go index 56eacfe2d47..8d7295ef0c9 100644 --- a/pkg/quota/interfaces.go +++ b/pkg/quota/interfaces.go @@ -20,7 +20,8 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" + "k8s.io/client-go/tools/cache" + api "k8s.io/kubernetes/pkg/apis/core" ) // UsageStatsOptions is an options structs that describes how stats should be calculated @@ -39,12 +40,12 @@ type UsageStats struct { Used api.ResourceList } -// Evaluator knows how to evaluate quota usage for a particular group kind +// Evaluator knows how to evaluate quota usage for a particular group resource type Evaluator interface { // Constraints ensures that each required resource is present on item Constraints(required []api.ResourceName, item runtime.Object) error - // GroupKind returns the groupKind that this object knows how to evaluate - GroupKind() schema.GroupKind + // GroupResource returns the groupResource that this object knows how to evaluate + GroupResource() schema.GroupResource // Handles determines if quota could be impacted by the specified attribute. // If true, admission control must perform quota processing for the operation, otherwise it is safe to ignore quota. Handles(operation admission.Attributes) bool @@ -58,25 +59,25 @@ type Evaluator interface { UsageStats(options UsageStatsOptions) (UsageStats, error) } -// Registry holds the list of evaluators associated to a particular group kind +// Configuration defines how the quota system is configured. +type Configuration interface { + // IgnoredResources are ignored by quota. + IgnoredResources() map[schema.GroupResource]struct{} + // Evaluators for quota evaluation. + Evaluators() []Evaluator +} + +// Registry maintains a list of evaluators type Registry interface { - // Evaluators returns the set Evaluator objects registered to a groupKind - Evaluators() map[schema.GroupKind]Evaluator + // Add to registry + Add(e Evaluator) + // Remove from registry + Remove(e Evaluator) + // Get by group resource + Get(gr schema.GroupResource) Evaluator + // List from registry + List() []Evaluator } -// UnionRegistry combines multiple registries. Order matters because first registry to claim a GroupKind -// is the "winner" -type UnionRegistry []Registry - -// Evaluators returns a mapping of evaluators by group kind. -func (r UnionRegistry) Evaluators() map[schema.GroupKind]Evaluator { - ret := map[schema.GroupKind]Evaluator{} - - for i := len(r) - 1; i >= 0; i-- { - for k, v := range r[i].Evaluators() { - ret[k] = v - } - } - - return ret -} +// ListerForResourceFunc knows how to get a lister for a specific resource +type ListerForResourceFunc func(schema.GroupVersionResource) (cache.GenericLister, error) diff --git a/pkg/quota/resources.go b/pkg/quota/resources.go index b9bd39bc83e..924ee41b946 100644 --- a/pkg/quota/resources.go +++ b/pkg/quota/resources.go @@ -17,10 +17,12 @@ limitations under the License. package quota import ( + "strings" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // Equals returns true if the two lists are equivalent @@ -196,6 +198,16 @@ func Contains(items []api.ResourceName, item api.ResourceName) bool { return ToSet(items).Has(string(item)) } +// ContainsPrefix returns true if the specified item has a prefix that contained in given prefix Set +func ContainsPrefix(prefixSet []string, item api.ResourceName) bool { + for _, prefix := range prefixSet { + if strings.HasPrefix(string(item), prefix) { + return true + } + } + return false +} + // Intersection returns the intersection of both list of resources func Intersection(a []api.ResourceName, b []api.ResourceName) []api.ResourceName { setA := ToSet(a) @@ -247,7 +259,7 @@ func CalculateUsage(namespaceName string, scopes []api.ResourceQuotaScope, hardL // look to measure updated usage stats for hardResources := ResourceNames(hardLimits) potentialResources := []api.ResourceName{} - evaluators := registry.Evaluators() + evaluators := registry.List() for _, evaluator := range evaluators { potentialResources = append(potentialResources, evaluator.MatchingResources(hardResources)...) } diff --git a/pkg/quota/resources_test.go b/pkg/quota/resources_test.go index 5e4c11a9c77..8cf26a767c5 100644 --- a/pkg/quota/resources_test.go +++ b/pkg/quota/resources_test.go @@ -20,7 +20,7 @@ import ( "testing" "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestEquals(t *testing.T) { @@ -226,6 +226,30 @@ func TestContains(t *testing.T) { } } +func TestContainsPrefix(t *testing.T) { + testCases := map[string]struct { + a []string + b api.ResourceName + expected bool + }{ + "does-not-contain": { + a: []string{api.ResourceHugePagesPrefix}, + b: api.ResourceCPU, + expected: false, + }, + "does-contain": { + a: []string{api.ResourceHugePagesPrefix}, + b: api.ResourceName(api.ResourceHugePagesPrefix + "2Mi"), + expected: true, + }, + } + for testName, testCase := range testCases { + if actual := ContainsPrefix(testCase.a, testCase.b); actual != testCase.expected { + t.Errorf("%s expected: %v, actual: %v", testName, testCase.expected, actual) + } + } +} + func TestIsZero(t *testing.T) { testCases := map[string]struct { a api.ResourceList diff --git a/pkg/registry/BUILD b/pkg/registry/BUILD index 317340e0168..f3d384739b9 100644 --- a/pkg/registry/BUILD +++ b/pkg/registry/BUILD @@ -22,9 +22,10 @@ filegroup( name = "all-srcs", srcs = [ ":package-srcs", - "//pkg/registry/admissionregistration/externaladmissionhookconfiguration:all-srcs", "//pkg/registry/admissionregistration/initializerconfiguration:all-srcs", + "//pkg/registry/admissionregistration/mutatingwebhookconfiguration:all-srcs", "//pkg/registry/admissionregistration/rest:all-srcs", + "//pkg/registry/admissionregistration/validatingwebhookconfiguration:all-srcs", "//pkg/registry/apps/controllerrevision:all-srcs", "//pkg/registry/apps/rest:all-srcs", "//pkg/registry/apps/statefulset:all-srcs", @@ -62,6 +63,8 @@ filegroup( "//pkg/registry/core/secret:all-srcs", "//pkg/registry/core/service:all-srcs", "//pkg/registry/core/serviceaccount:all-srcs", + "//pkg/registry/events/event:all-srcs", + "//pkg/registry/events/rest:all-srcs", "//pkg/registry/extensions/controller/storage:all-srcs", "//pkg/registry/extensions/daemonset:all-srcs", "//pkg/registry/extensions/deployment:all-srcs", @@ -81,6 +84,7 @@ filegroup( "//pkg/registry/settings/rest:all-srcs", "//pkg/registry/storage/rest:all-srcs", "//pkg/registry/storage/storageclass:all-srcs", + "//pkg/registry/storage/volumeattachment:all-srcs", ], tags = ["automanaged"], ) diff --git a/pkg/registry/admissionregistration/externaladmissionhookconfiguration/BUILD b/pkg/registry/admissionregistration/externaladmissionhookconfiguration/BUILD deleted file mode 100644 index a8290cc695d..00000000000 --- a/pkg/registry/admissionregistration/externaladmissionhookconfiguration/BUILD +++ /dev/null @@ -1,40 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "doc.go", - "strategy.go", - ], - importpath = "k8s.io/kubernetes/pkg/registry/admissionregistration/externaladmissionhookconfiguration", - deps = [ - "//pkg/api:go_default_library", - "//pkg/apis/admissionregistration:go_default_library", - "//pkg/apis/admissionregistration/validation:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", - "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", - "//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//pkg/registry/admissionregistration/externaladmissionhookconfiguration/storage:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/pkg/registry/admissionregistration/externaladmissionhookconfiguration/doc.go b/pkg/registry/admissionregistration/externaladmissionhookconfiguration/doc.go deleted file mode 100644 index dcbd2a048c1..00000000000 --- a/pkg/registry/admissionregistration/externaladmissionhookconfiguration/doc.go +++ /dev/null @@ -1,17 +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 externaladmissionhookconfiguration // import "k8s.io/kubernetes/pkg/registry/admissionregistration/externaladmissionhookconfiguration" diff --git a/pkg/registry/admissionregistration/externaladmissionhookconfiguration/storage/BUILD b/pkg/registry/admissionregistration/externaladmissionhookconfiguration/storage/BUILD deleted file mode 100644 index 14c2bdc1bea..00000000000 --- a/pkg/registry/admissionregistration/externaladmissionhookconfiguration/storage/BUILD +++ /dev/null @@ -1,32 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["storage.go"], - importpath = "k8s.io/kubernetes/pkg/registry/admissionregistration/externaladmissionhookconfiguration/storage", - deps = [ - "//pkg/apis/admissionregistration:go_default_library", - "//pkg/registry/admissionregistration/externaladmissionhookconfiguration:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", - "//vendor/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/pkg/registry/admissionregistration/externaladmissionhookconfiguration/storage/storage.go b/pkg/registry/admissionregistration/externaladmissionhookconfiguration/storage/storage.go deleted file mode 100644 index 08db5d8459f..00000000000 --- a/pkg/registry/admissionregistration/externaladmissionhookconfiguration/storage/storage.go +++ /dev/null @@ -1,51 +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 storage - -import ( - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apiserver/pkg/registry/generic" - genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" - "k8s.io/kubernetes/pkg/apis/admissionregistration" - "k8s.io/kubernetes/pkg/registry/admissionregistration/externaladmissionhookconfiguration" -) - -// rest implements a RESTStorage for pod disruption budgets against etcd -type REST struct { - *genericregistry.Store -} - -// NewREST returns a RESTStorage object that will work against pod disruption budgets. -func NewREST(optsGetter generic.RESTOptionsGetter) *REST { - store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &admissionregistration.ExternalAdmissionHookConfiguration{} }, - NewListFunc: func() runtime.Object { return &admissionregistration.ExternalAdmissionHookConfigurationList{} }, - ObjectNameFunc: func(obj runtime.Object) (string, error) { - return obj.(*admissionregistration.ExternalAdmissionHookConfiguration).Name, nil - }, - DefaultQualifiedResource: admissionregistration.Resource("externaladmissionhookconfigurations"), - - CreateStrategy: externaladmissionhookconfiguration.Strategy, - UpdateStrategy: externaladmissionhookconfiguration.Strategy, - DeleteStrategy: externaladmissionhookconfiguration.Strategy, - } - options := &generic.StoreOptions{RESTOptions: optsGetter} - if err := store.CompleteWithOptions(options); err != nil { - panic(err) // TODO: Propagate error up - } - return &REST{store} -} diff --git a/pkg/registry/admissionregistration/externaladmissionhookconfiguration/strategy.go b/pkg/registry/admissionregistration/externaladmissionhookconfiguration/strategy.go deleted file mode 100644 index d6d26379542..00000000000 --- a/pkg/registry/admissionregistration/externaladmissionhookconfiguration/strategy.go +++ /dev/null @@ -1,90 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package externaladmissionhookconfiguration - -import ( - "reflect" - - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/validation/field" - genericapirequest "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/admissionregistration" - "k8s.io/kubernetes/pkg/apis/admissionregistration/validation" -) - -// externaladmissionhookConfigurationStrategy implements verification logic for ExternalAdmissionHookConfiguration. -type externaladmissionhookConfigurationStrategy struct { - runtime.ObjectTyper - names.NameGenerator -} - -// Strategy is the default logic that applies when creating and updating ExternalAdmissionHookConfiguration objects. -var Strategy = externaladmissionhookConfigurationStrategy{api.Scheme, names.SimpleNameGenerator} - -// NamespaceScoped returns true because all ExternalAdmissionHookConfiguration' need to be within a namespace. -func (externaladmissionhookConfigurationStrategy) NamespaceScoped() bool { - return false -} - -// PrepareForCreate clears the status of an ExternalAdmissionHookConfiguration before creation. -func (externaladmissionhookConfigurationStrategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) { - ic := obj.(*admissionregistration.ExternalAdmissionHookConfiguration) - ic.Generation = 1 -} - -// PrepareForUpdate clears fields that are not allowed to be set by end users on update. -func (externaladmissionhookConfigurationStrategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime.Object) { - newIC := obj.(*admissionregistration.ExternalAdmissionHookConfiguration) - oldIC := old.(*admissionregistration.ExternalAdmissionHookConfiguration) - - // Any changes to the spec increment the generation number, any changes to the - // status should reflect the generation number of the corresponding object. - // See metav1.ObjectMeta description for more information on Generation. - if !reflect.DeepEqual(oldIC.ExternalAdmissionHooks, newIC.ExternalAdmissionHooks) { - newIC.Generation = oldIC.Generation + 1 - } -} - -// Validate validates a new ExternalAdmissionHookConfiguration. -func (externaladmissionhookConfigurationStrategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList { - ic := obj.(*admissionregistration.ExternalAdmissionHookConfiguration) - return validation.ValidateExternalAdmissionHookConfiguration(ic) -} - -// Canonicalize normalizes the object after validation. -func (externaladmissionhookConfigurationStrategy) Canonicalize(obj runtime.Object) { -} - -// AllowCreateOnUpdate is true for ExternalAdmissionHookConfiguration; this means you may create one with a PUT request. -func (externaladmissionhookConfigurationStrategy) AllowCreateOnUpdate() bool { - return false -} - -// ValidateUpdate is the default update validation for an end user. -func (externaladmissionhookConfigurationStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList { - validationErrorList := validation.ValidateExternalAdmissionHookConfiguration(obj.(*admissionregistration.ExternalAdmissionHookConfiguration)) - updateErrorList := validation.ValidateExternalAdmissionHookConfigurationUpdate(obj.(*admissionregistration.ExternalAdmissionHookConfiguration), old.(*admissionregistration.ExternalAdmissionHookConfiguration)) - return append(validationErrorList, updateErrorList...) -} - -// AllowUnconditionalUpdate is the default update policy for ExternalAdmissionHookConfiguration objects. Status update should -// only be allowed if version match. -func (externaladmissionhookConfigurationStrategy) AllowUnconditionalUpdate() bool { - return false -} diff --git a/pkg/registry/admissionregistration/initializerconfiguration/BUILD b/pkg/registry/admissionregistration/initializerconfiguration/BUILD index 03374be090c..29c56e75b65 100644 --- a/pkg/registry/admissionregistration/initializerconfiguration/BUILD +++ b/pkg/registry/admissionregistration/initializerconfiguration/BUILD @@ -13,7 +13,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/admissionregistration/initializerconfiguration", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/admissionregistration:go_default_library", "//pkg/apis/admissionregistration/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/pkg/registry/admissionregistration/initializerconfiguration/strategy.go b/pkg/registry/admissionregistration/initializerconfiguration/strategy.go index 3f0e73925bc..f32e4ea9dcb 100644 --- a/pkg/registry/admissionregistration/initializerconfiguration/strategy.go +++ b/pkg/registry/admissionregistration/initializerconfiguration/strategy.go @@ -23,7 +23,7 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/admissionregistration" "k8s.io/kubernetes/pkg/apis/admissionregistration/validation" ) @@ -35,7 +35,7 @@ type initializerConfigurationStrategy struct { } // Strategy is the default logic that applies when creating and updating InitializerConfiguration objects. -var Strategy = initializerConfigurationStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = initializerConfigurationStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} // NamespaceScoped returns true because all InitializerConfiguration' need to be within a namespace. func (initializerConfigurationStrategy) NamespaceScoped() bool { diff --git a/pkg/registry/admissionregistration/mutatingwebhookconfiguration/BUILD b/pkg/registry/admissionregistration/mutatingwebhookconfiguration/BUILD new file mode 100644 index 00000000000..92ae7b566fc --- /dev/null +++ b/pkg/registry/admissionregistration/mutatingwebhookconfiguration/BUILD @@ -0,0 +1,40 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "strategy.go", + ], + importpath = "k8s.io/kubernetes/pkg/registry/admissionregistration/mutatingwebhookconfiguration", + deps = [ + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/admissionregistration:go_default_library", + "//pkg/apis/admissionregistration/validation:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", + "//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//pkg/registry/admissionregistration/mutatingwebhookconfiguration/storage:all-srcs", + ], + tags = ["automanaged"], +) diff --git a/pkg/registry/admissionregistration/mutatingwebhookconfiguration/doc.go b/pkg/registry/admissionregistration/mutatingwebhookconfiguration/doc.go new file mode 100644 index 00000000000..1c56651af12 --- /dev/null +++ b/pkg/registry/admissionregistration/mutatingwebhookconfiguration/doc.go @@ -0,0 +1,17 @@ +/* +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 mutatingwebhookconfiguration // import "k8s.io/kubernetes/pkg/registry/admissionregistration/mutatingwebhookconfiguration" diff --git a/pkg/registry/admissionregistration/mutatingwebhookconfiguration/storage/BUILD b/pkg/registry/admissionregistration/mutatingwebhookconfiguration/storage/BUILD new file mode 100644 index 00000000000..7cb1a472f92 --- /dev/null +++ b/pkg/registry/admissionregistration/mutatingwebhookconfiguration/storage/BUILD @@ -0,0 +1,32 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = ["storage.go"], + importpath = "k8s.io/kubernetes/pkg/registry/admissionregistration/mutatingwebhookconfiguration/storage", + deps = [ + "//pkg/apis/admissionregistration:go_default_library", + "//pkg/registry/admissionregistration/mutatingwebhookconfiguration:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/pkg/registry/admissionregistration/mutatingwebhookconfiguration/storage/storage.go b/pkg/registry/admissionregistration/mutatingwebhookconfiguration/storage/storage.go new file mode 100644 index 00000000000..7f3be349278 --- /dev/null +++ b/pkg/registry/admissionregistration/mutatingwebhookconfiguration/storage/storage.go @@ -0,0 +1,51 @@ +/* +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 storage + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/generic" + genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" + "k8s.io/kubernetes/pkg/apis/admissionregistration" + "k8s.io/kubernetes/pkg/registry/admissionregistration/mutatingwebhookconfiguration" +) + +// rest implements a RESTStorage for pod disruption budgets against etcd +type REST struct { + *genericregistry.Store +} + +// NewREST returns a RESTStorage object that will work against pod disruption budgets. +func NewREST(optsGetter generic.RESTOptionsGetter) *REST { + store := &genericregistry.Store{ + NewFunc: func() runtime.Object { return &admissionregistration.MutatingWebhookConfiguration{} }, + NewListFunc: func() runtime.Object { return &admissionregistration.MutatingWebhookConfigurationList{} }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*admissionregistration.MutatingWebhookConfiguration).Name, nil + }, + DefaultQualifiedResource: admissionregistration.Resource("mutatingwebhookconfigurations"), + + CreateStrategy: mutatingwebhookconfiguration.Strategy, + UpdateStrategy: mutatingwebhookconfiguration.Strategy, + DeleteStrategy: mutatingwebhookconfiguration.Strategy, + } + options := &generic.StoreOptions{RESTOptions: optsGetter} + if err := store.CompleteWithOptions(options); err != nil { + panic(err) // TODO: Propagate error up + } + return &REST{store} +} diff --git a/pkg/registry/admissionregistration/mutatingwebhookconfiguration/strategy.go b/pkg/registry/admissionregistration/mutatingwebhookconfiguration/strategy.go new file mode 100644 index 00000000000..735980ac42f --- /dev/null +++ b/pkg/registry/admissionregistration/mutatingwebhookconfiguration/strategy.go @@ -0,0 +1,90 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES 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 ( + "reflect" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/storage/names" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/apis/admissionregistration" + "k8s.io/kubernetes/pkg/apis/admissionregistration/validation" +) + +// mutatingWebhookConfigurationStrategy implements verification logic for mutatingWebhookConfiguration. +type mutatingWebhookConfigurationStrategy struct { + runtime.ObjectTyper + names.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating mutatingWebhookConfiguration objects. +var Strategy = mutatingWebhookConfigurationStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} + +// NamespaceScoped returns true because all mutatingWebhookConfiguration' need to be within a namespace. +func (mutatingWebhookConfigurationStrategy) NamespaceScoped() bool { + return false +} + +// PrepareForCreate clears the status of an mutatingWebhookConfiguration before creation. +func (mutatingWebhookConfigurationStrategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) { + ic := obj.(*admissionregistration.MutatingWebhookConfiguration) + ic.Generation = 1 +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (mutatingWebhookConfigurationStrategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime.Object) { + newIC := obj.(*admissionregistration.MutatingWebhookConfiguration) + oldIC := old.(*admissionregistration.MutatingWebhookConfiguration) + + // Any changes to the spec increment the generation number, any changes to the + // status should reflect the generation number of the corresponding object. + // See metav1.ObjectMeta description for more information on Generation. + if !reflect.DeepEqual(oldIC.Webhooks, newIC.Webhooks) { + newIC.Generation = oldIC.Generation + 1 + } +} + +// Validate validates a new mutatingWebhookConfiguration. +func (mutatingWebhookConfigurationStrategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList { + ic := obj.(*admissionregistration.MutatingWebhookConfiguration) + return validation.ValidateMutatingWebhookConfiguration(ic) +} + +// Canonicalize normalizes the object after validation. +func (mutatingWebhookConfigurationStrategy) Canonicalize(obj runtime.Object) { +} + +// AllowCreateOnUpdate is true for mutatingWebhookConfiguration; this means you may create one with a PUT request. +func (mutatingWebhookConfigurationStrategy) AllowCreateOnUpdate() bool { + return false +} + +// ValidateUpdate is the default update validation for an end user. +func (mutatingWebhookConfigurationStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList { + validationErrorList := validation.ValidateMutatingWebhookConfiguration(obj.(*admissionregistration.MutatingWebhookConfiguration)) + updateErrorList := validation.ValidateMutatingWebhookConfigurationUpdate(obj.(*admissionregistration.MutatingWebhookConfiguration), old.(*admissionregistration.MutatingWebhookConfiguration)) + return append(validationErrorList, updateErrorList...) +} + +// AllowUnconditionalUpdate is the default update policy for mutatingWebhookConfiguration objects. Status update should +// only be allowed if version match. +func (mutatingWebhookConfigurationStrategy) AllowUnconditionalUpdate() bool { + return false +} diff --git a/pkg/registry/admissionregistration/rest/BUILD b/pkg/registry/admissionregistration/rest/BUILD index 8539548d4de..914682ae4a8 100644 --- a/pkg/registry/admissionregistration/rest/BUILD +++ b/pkg/registry/admissionregistration/rest/BUILD @@ -10,11 +10,13 @@ go_library( srcs = ["storage_apiserver.go"], importpath = "k8s.io/kubernetes/pkg/registry/admissionregistration/rest", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/admissionregistration:go_default_library", - "//pkg/registry/admissionregistration/externaladmissionhookconfiguration/storage:go_default_library", "//pkg/registry/admissionregistration/initializerconfiguration/storage:go_default_library", + "//pkg/registry/admissionregistration/mutatingwebhookconfiguration/storage:go_default_library", + "//pkg/registry/admissionregistration/validatingwebhookconfiguration/storage:go_default_library", "//vendor/k8s.io/api/admissionregistration/v1alpha1:go_default_library", + "//vendor/k8s.io/api/admissionregistration/v1beta1:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", "//vendor/k8s.io/apiserver/pkg/server:go_default_library", diff --git a/pkg/registry/admissionregistration/rest/storage_apiserver.go b/pkg/registry/admissionregistration/rest/storage_apiserver.go index 26d381a681f..5b26e830bd7 100644 --- a/pkg/registry/admissionregistration/rest/storage_apiserver.go +++ b/pkg/registry/admissionregistration/rest/storage_apiserver.go @@ -18,20 +18,22 @@ package rest import ( admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1" + admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" "k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" serverstorage "k8s.io/apiserver/pkg/server/storage" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/admissionregistration" - externaladmissionhookconfigurationstorage "k8s.io/kubernetes/pkg/registry/admissionregistration/externaladmissionhookconfiguration/storage" initializerconfigurationstorage "k8s.io/kubernetes/pkg/registry/admissionregistration/initializerconfiguration/storage" + mutatingwebhookconfigurationstorage "k8s.io/kubernetes/pkg/registry/admissionregistration/mutatingwebhookconfiguration/storage" + validatingwebhookconfigurationstorage "k8s.io/kubernetes/pkg/registry/admissionregistration/validatingwebhookconfiguration/storage" ) type RESTStorageProvider struct{} func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) { - apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(admissionregistration.GroupName, api.Registry, api.Scheme, api.ParameterCodec, api.Codecs) + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(admissionregistration.GroupName, legacyscheme.Registry, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs) // If you add a version here, be sure to add an entry in `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go with specific priorities. // TODO refactor the plumbing to provide the information in the APIGroupInfo @@ -39,6 +41,10 @@ func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorag apiGroupInfo.VersionedResourcesStorageMap[admissionregistrationv1alpha1.SchemeGroupVersion.Version] = p.v1alpha1Storage(apiResourceConfigSource, restOptionsGetter) apiGroupInfo.GroupMeta.GroupVersion = admissionregistrationv1alpha1.SchemeGroupVersion } + if apiResourceConfigSource.AnyResourcesForVersionEnabled(admissionregistrationv1beta1.SchemeGroupVersion) { + apiGroupInfo.VersionedResourcesStorageMap[admissionregistrationv1beta1.SchemeGroupVersion.Version] = p.v1beta1Storage(apiResourceConfigSource, restOptionsGetter) + apiGroupInfo.GroupMeta.GroupVersion = admissionregistrationv1beta1.SchemeGroupVersion + } return apiGroupInfo, true } @@ -49,9 +55,19 @@ func (p RESTStorageProvider) v1alpha1Storage(apiResourceConfigSource serverstora s := initializerconfigurationstorage.NewREST(restOptionsGetter) storage["initializerconfigurations"] = s } - if apiResourceConfigSource.ResourceEnabled(version.WithResource("externaladmissionhookconfigurations")) { - s := externaladmissionhookconfigurationstorage.NewREST(restOptionsGetter) - storage["externaladmissionhookconfigurations"] = s + return storage +} + +func (p RESTStorageProvider) v1beta1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) map[string]rest.Storage { + version := admissionregistrationv1beta1.SchemeGroupVersion + storage := map[string]rest.Storage{} + if apiResourceConfigSource.ResourceEnabled(version.WithResource("validatingwebhookconfigurations")) { + s := validatingwebhookconfigurationstorage.NewREST(restOptionsGetter) + storage["validatingwebhookconfigurations"] = s + } + if apiResourceConfigSource.ResourceEnabled(version.WithResource("mutatingwebhookconfigurations")) { + s := mutatingwebhookconfigurationstorage.NewREST(restOptionsGetter) + storage["mutatingwebhookconfigurations"] = s } return storage } diff --git a/pkg/registry/admissionregistration/validatingwebhookconfiguration/BUILD b/pkg/registry/admissionregistration/validatingwebhookconfiguration/BUILD new file mode 100644 index 00000000000..0b07d302867 --- /dev/null +++ b/pkg/registry/admissionregistration/validatingwebhookconfiguration/BUILD @@ -0,0 +1,40 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "strategy.go", + ], + importpath = "k8s.io/kubernetes/pkg/registry/admissionregistration/validatingwebhookconfiguration", + deps = [ + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/admissionregistration:go_default_library", + "//pkg/apis/admissionregistration/validation:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", + "//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//pkg/registry/admissionregistration/validatingwebhookconfiguration/storage:all-srcs", + ], + tags = ["automanaged"], +) diff --git a/pkg/registry/admissionregistration/validatingwebhookconfiguration/doc.go b/pkg/registry/admissionregistration/validatingwebhookconfiguration/doc.go new file mode 100644 index 00000000000..d23152a4b44 --- /dev/null +++ b/pkg/registry/admissionregistration/validatingwebhookconfiguration/doc.go @@ -0,0 +1,17 @@ +/* +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 validatingwebhookconfiguration // import "k8s.io/kubernetes/pkg/registry/admissionregistration/validatingwebhookconfiguration" diff --git a/pkg/registry/admissionregistration/validatingwebhookconfiguration/storage/BUILD b/pkg/registry/admissionregistration/validatingwebhookconfiguration/storage/BUILD new file mode 100644 index 00000000000..30da94ee1a8 --- /dev/null +++ b/pkg/registry/admissionregistration/validatingwebhookconfiguration/storage/BUILD @@ -0,0 +1,32 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = ["storage.go"], + importpath = "k8s.io/kubernetes/pkg/registry/admissionregistration/validatingwebhookconfiguration/storage", + deps = [ + "//pkg/apis/admissionregistration:go_default_library", + "//pkg/registry/admissionregistration/validatingwebhookconfiguration:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/pkg/registry/admissionregistration/validatingwebhookconfiguration/storage/storage.go b/pkg/registry/admissionregistration/validatingwebhookconfiguration/storage/storage.go new file mode 100644 index 00000000000..6a0bee2f068 --- /dev/null +++ b/pkg/registry/admissionregistration/validatingwebhookconfiguration/storage/storage.go @@ -0,0 +1,51 @@ +/* +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 storage + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/generic" + genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" + "k8s.io/kubernetes/pkg/apis/admissionregistration" + "k8s.io/kubernetes/pkg/registry/admissionregistration/validatingwebhookconfiguration" +) + +// rest implements a RESTStorage for pod disruption budgets against etcd +type REST struct { + *genericregistry.Store +} + +// NewREST returns a RESTStorage object that will work against pod disruption budgets. +func NewREST(optsGetter generic.RESTOptionsGetter) *REST { + store := &genericregistry.Store{ + NewFunc: func() runtime.Object { return &admissionregistration.ValidatingWebhookConfiguration{} }, + NewListFunc: func() runtime.Object { return &admissionregistration.ValidatingWebhookConfigurationList{} }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*admissionregistration.ValidatingWebhookConfiguration).Name, nil + }, + DefaultQualifiedResource: admissionregistration.Resource("validatingwebhookconfigurations"), + + CreateStrategy: validatingwebhookconfiguration.Strategy, + UpdateStrategy: validatingwebhookconfiguration.Strategy, + DeleteStrategy: validatingwebhookconfiguration.Strategy, + } + options := &generic.StoreOptions{RESTOptions: optsGetter} + if err := store.CompleteWithOptions(options); err != nil { + panic(err) // TODO: Propagate error up + } + return &REST{store} +} diff --git a/pkg/registry/admissionregistration/validatingwebhookconfiguration/strategy.go b/pkg/registry/admissionregistration/validatingwebhookconfiguration/strategy.go new file mode 100644 index 00000000000..e59ecc98950 --- /dev/null +++ b/pkg/registry/admissionregistration/validatingwebhookconfiguration/strategy.go @@ -0,0 +1,90 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package validatingwebhookconfiguration + +import ( + "reflect" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/storage/names" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/apis/admissionregistration" + "k8s.io/kubernetes/pkg/apis/admissionregistration/validation" +) + +// validatingWebhookConfigurationStrategy implements verification logic for validatingWebhookConfiguration. +type validatingWebhookConfigurationStrategy struct { + runtime.ObjectTyper + names.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating validatingWebhookConfiguration objects. +var Strategy = validatingWebhookConfigurationStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} + +// NamespaceScoped returns true because all validatingWebhookConfiguration' need to be within a namespace. +func (validatingWebhookConfigurationStrategy) NamespaceScoped() bool { + return false +} + +// PrepareForCreate clears the status of an validatingWebhookConfiguration before creation. +func (validatingWebhookConfigurationStrategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) { + ic := obj.(*admissionregistration.ValidatingWebhookConfiguration) + ic.Generation = 1 +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (validatingWebhookConfigurationStrategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime.Object) { + newIC := obj.(*admissionregistration.ValidatingWebhookConfiguration) + oldIC := old.(*admissionregistration.ValidatingWebhookConfiguration) + + // Any changes to the spec increment the generation number, any changes to the + // status should reflect the generation number of the corresponding object. + // See metav1.ObjectMeta description for more information on Generation. + if !reflect.DeepEqual(oldIC.Webhooks, newIC.Webhooks) { + newIC.Generation = oldIC.Generation + 1 + } +} + +// Validate validates a new validatingWebhookConfiguration. +func (validatingWebhookConfigurationStrategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList { + ic := obj.(*admissionregistration.ValidatingWebhookConfiguration) + return validation.ValidateValidatingWebhookConfiguration(ic) +} + +// Canonicalize normalizes the object after validation. +func (validatingWebhookConfigurationStrategy) Canonicalize(obj runtime.Object) { +} + +// AllowCreateOnUpdate is true for validatingWebhookConfiguration; this means you may create one with a PUT request. +func (validatingWebhookConfigurationStrategy) AllowCreateOnUpdate() bool { + return false +} + +// ValidateUpdate is the default update validation for an end user. +func (validatingWebhookConfigurationStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList { + validationErrorList := validation.ValidateValidatingWebhookConfiguration(obj.(*admissionregistration.ValidatingWebhookConfiguration)) + updateErrorList := validation.ValidateValidatingWebhookConfigurationUpdate(obj.(*admissionregistration.ValidatingWebhookConfiguration), old.(*admissionregistration.ValidatingWebhookConfiguration)) + return append(validationErrorList, updateErrorList...) +} + +// AllowUnconditionalUpdate is the default update policy for validatingWebhookConfiguration objects. Status update should +// only be allowed if version match. +func (validatingWebhookConfigurationStrategy) AllowUnconditionalUpdate() bool { + return false +} diff --git a/pkg/registry/apps/controllerrevision/BUILD b/pkg/registry/apps/controllerrevision/BUILD index ee8dda9d4b0..3d9b303ec03 100644 --- a/pkg/registry/apps/controllerrevision/BUILD +++ b/pkg/registry/apps/controllerrevision/BUILD @@ -9,11 +9,11 @@ load( go_test( name = "go_default_test", srcs = ["strategy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/apps/controllerrevision", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", "//pkg/apis/apps:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", @@ -28,7 +28,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/apps/controllerrevision", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/apps:go_default_library", "//pkg/apis/apps/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/pkg/registry/apps/controllerrevision/storage/BUILD b/pkg/registry/apps/controllerrevision/storage/BUILD index 53544f5dc3f..4e79bf9fec2 100644 --- a/pkg/registry/apps/controllerrevision/storage/BUILD +++ b/pkg/registry/apps/controllerrevision/storage/BUILD @@ -9,17 +9,18 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/apps/controllerrevision/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", "//pkg/apis/apps:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], ) diff --git a/pkg/registry/apps/controllerrevision/storage/storage_test.go b/pkg/registry/apps/controllerrevision/storage/storage_test.go index 05abb7a600b..b68fbefe1d7 100644 --- a/pkg/registry/apps/controllerrevision/storage/storage_test.go +++ b/pkg/registry/apps/controllerrevision/storage/storage_test.go @@ -24,9 +24,10 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/apps" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -34,7 +35,7 @@ func TestCreate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) var ( valid = stripObjectMeta(newControllerRevision("validname", metav1.NamespaceDefault, newObject(), 0)) badRevision = stripObjectMeta(newControllerRevision("validname", "validns", newObject(), -1)) @@ -58,7 +59,7 @@ func TestUpdate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) addLabel := func(obj runtime.Object) runtime.Object { rev := obj.(*apps.ControllerRevision) @@ -93,7 +94,7 @@ func TestGet(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestGet(newControllerRevision("valid", metav1.NamespaceDefault, newObject(), 0)) } @@ -101,7 +102,7 @@ func TestList(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestList(newControllerRevision("valid", metav1.NamespaceDefault, newObject(), 0)) } @@ -109,7 +110,7 @@ func TestDelete(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestDelete(newControllerRevision("valid", metav1.NamespaceDefault, newObject(), 0)) } @@ -117,7 +118,7 @@ func TestWatch(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestWatch( newControllerRevision("valid", metav1.NamespaceDefault, newObject(), 0), []labels.Set{ diff --git a/pkg/registry/apps/controllerrevision/strategy.go b/pkg/registry/apps/controllerrevision/strategy.go index f6f207b9053..08161d718ad 100644 --- a/pkg/registry/apps/controllerrevision/strategy.go +++ b/pkg/registry/apps/controllerrevision/strategy.go @@ -22,7 +22,7 @@ import ( genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/apps/validation" ) @@ -35,7 +35,7 @@ type strategy struct { // Strategy is the default logic that applies when creating and updating ControllerRevision // objects via the REST API. -var Strategy = strategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = strategy{legacyscheme.Scheme, names.SimpleNameGenerator} // Strategy should implement rest.RESTCreateStrategy var _ rest.RESTCreateStrategy = Strategy diff --git a/pkg/registry/apps/controllerrevision/strategy_test.go b/pkg/registry/apps/controllerrevision/strategy_test.go index 594e33277ac..ed9f886e892 100644 --- a/pkg/registry/apps/controllerrevision/strategy_test.go +++ b/pkg/registry/apps/controllerrevision/strategy_test.go @@ -22,8 +22,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/apps" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestStrategy_NamespaceScoped(t *testing.T) { diff --git a/pkg/registry/apps/rest/BUILD b/pkg/registry/apps/rest/BUILD index f7799721e9e..70032abad7b 100644 --- a/pkg/registry/apps/rest/BUILD +++ b/pkg/registry/apps/rest/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["storage_apps.go"], importpath = "k8s.io/kubernetes/pkg/registry/apps/rest", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/apps:go_default_library", "//pkg/registry/apps/controllerrevision/storage:go_default_library", "//pkg/registry/apps/statefulset/storage:go_default_library", diff --git a/pkg/registry/apps/rest/storage_apps.go b/pkg/registry/apps/rest/storage_apps.go index ac2e3cb3d7a..f77900e8813 100644 --- a/pkg/registry/apps/rest/storage_apps.go +++ b/pkg/registry/apps/rest/storage_apps.go @@ -24,7 +24,7 @@ import ( "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" serverstorage "k8s.io/apiserver/pkg/server/storage" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/apps" controllerrevisionsstore "k8s.io/kubernetes/pkg/registry/apps/controllerrevision/storage" statefulsetstore "k8s.io/kubernetes/pkg/registry/apps/statefulset/storage" @@ -36,7 +36,7 @@ import ( type RESTStorageProvider struct{} func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) { - apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(apps.GroupName, api.Registry, api.Scheme, api.ParameterCodec, api.Codecs) + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(apps.GroupName, legacyscheme.Registry, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs) // If you add a version here, be sure to add an entry in `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go with specific priorities. // TODO refactor the plumbing to provide the information in the APIGroupInfo @@ -118,11 +118,33 @@ func (p RESTStorageProvider) v1Storage(apiResourceConfigSource serverstorage.API version := appsapiv1.SchemeGroupVersion storage := map[string]rest.Storage{} + if apiResourceConfigSource.ResourceEnabled(version.WithResource("deployments")) { + deploymentStorage := deploymentstore.NewStorage(restOptionsGetter) + storage["deployments"] = deploymentStorage.Deployment + storage["deployments/status"] = deploymentStorage.Status + storage["deployments/scale"] = deploymentStorage.Scale + } + if apiResourceConfigSource.ResourceEnabled(version.WithResource("statefulsets")) { + statefulSetStorage := statefulsetstore.NewStorage(restOptionsGetter) + storage["statefulsets"] = statefulSetStorage.StatefulSet + storage["statefulsets/status"] = statefulSetStorage.Status + storage["statefulsets/scale"] = statefulSetStorage.Scale + } if apiResourceConfigSource.ResourceEnabled(version.WithResource("daemonsets")) { daemonSetStorage, daemonSetStatusStorage := daemonsetstore.NewREST(restOptionsGetter) storage["daemonsets"] = daemonSetStorage storage["daemonsets/status"] = daemonSetStatusStorage } + if apiResourceConfigSource.ResourceEnabled(version.WithResource("replicasets")) { + replicaSetStorage := replicasetstore.NewStorage(restOptionsGetter) + storage["replicasets"] = replicaSetStorage.ReplicaSet + storage["replicasets/status"] = replicaSetStorage.Status + storage["replicasets/scale"] = replicaSetStorage.Scale + } + if apiResourceConfigSource.ResourceEnabled(version.WithResource("controllerrevisions")) { + historyStorage := controllerrevisionsstore.NewREST(restOptionsGetter) + storage["controllerrevisions"] = historyStorage + } return storage } diff --git a/pkg/registry/apps/statefulset/BUILD b/pkg/registry/apps/statefulset/BUILD index d67edd35681..6b0b73a9c8b 100644 --- a/pkg/registry/apps/statefulset/BUILD +++ b/pkg/registry/apps/statefulset/BUILD @@ -15,15 +15,18 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/apps/statefulset", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/pod:go_default_library", "//pkg/apis/apps:go_default_library", "//pkg/apis/apps/validation:go_default_library", + "//vendor/k8s.io/api/apps/v1beta1:go_default_library", + "//vendor/k8s.io/api/apps/v1beta2:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", @@ -35,11 +38,11 @@ go_library( go_test( name = "go_default_test", srcs = ["strategy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/apps/statefulset", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", "//pkg/apis/apps:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", diff --git a/pkg/registry/apps/statefulset/registry.go b/pkg/registry/apps/statefulset/registry.go index beef2d75365..eb33b6e1ced 100644 --- a/pkg/registry/apps/statefulset/registry.go +++ b/pkg/registry/apps/statefulset/registry.go @@ -33,8 +33,8 @@ type Registry interface { ListStatefulSets(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*apps.StatefulSetList, error) WatchStatefulSets(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) GetStatefulSet(ctx genericapirequest.Context, statefulSetID string, options *metav1.GetOptions) (*apps.StatefulSet, error) - CreateStatefulSet(ctx genericapirequest.Context, statefulSet *apps.StatefulSet) (*apps.StatefulSet, error) - UpdateStatefulSet(ctx genericapirequest.Context, statefulSet *apps.StatefulSet) (*apps.StatefulSet, error) + CreateStatefulSet(ctx genericapirequest.Context, statefulSet *apps.StatefulSet, createValidation rest.ValidateObjectFunc) (*apps.StatefulSet, error) + UpdateStatefulSet(ctx genericapirequest.Context, statefulSet *apps.StatefulSet, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (*apps.StatefulSet, error) DeleteStatefulSet(ctx genericapirequest.Context, statefulSetID string) error } @@ -72,16 +72,16 @@ func (s *storage) GetStatefulSet(ctx genericapirequest.Context, statefulSetID st return obj.(*apps.StatefulSet), nil } -func (s *storage) CreateStatefulSet(ctx genericapirequest.Context, statefulSet *apps.StatefulSet) (*apps.StatefulSet, error) { - obj, err := s.Create(ctx, statefulSet, false) +func (s *storage) CreateStatefulSet(ctx genericapirequest.Context, statefulSet *apps.StatefulSet, createValidation rest.ValidateObjectFunc) (*apps.StatefulSet, error) { + obj, err := s.Create(ctx, statefulSet, rest.ValidateAllObjectFunc, false) if err != nil { return nil, err } return obj.(*apps.StatefulSet), nil } -func (s *storage) UpdateStatefulSet(ctx genericapirequest.Context, statefulSet *apps.StatefulSet) (*apps.StatefulSet, error) { - obj, _, err := s.Update(ctx, statefulSet.Name, rest.DefaultUpdatedObjectInfo(statefulSet)) +func (s *storage) UpdateStatefulSet(ctx genericapirequest.Context, statefulSet *apps.StatefulSet, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (*apps.StatefulSet, error) { + obj, _, err := s.Update(ctx, statefulSet.Name, rest.DefaultUpdatedObjectInfo(statefulSet), createValidation, updateValidation) if err != nil { return nil, err } diff --git a/pkg/registry/apps/statefulset/storage/BUILD b/pkg/registry/apps/statefulset/storage/BUILD index ccb4be21a0a..da6e4a68788 100644 --- a/pkg/registry/apps/statefulset/storage/BUILD +++ b/pkg/registry/apps/statefulset/storage/BUILD @@ -9,12 +9,12 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/apps/statefulset/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", "//pkg/apis/apps:go_default_library", - "//pkg/apis/extensions:go_default_library", + "//pkg/apis/autoscaling:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", @@ -24,6 +24,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], @@ -35,8 +36,12 @@ go_library( importpath = "k8s.io/kubernetes/pkg/registry/apps/statefulset/storage", deps = [ "//pkg/apis/apps:go_default_library", + "//pkg/apis/apps/v1beta1:go_default_library", + "//pkg/apis/apps/v1beta2:go_default_library", + "//pkg/apis/autoscaling:go_default_library", + "//pkg/apis/autoscaling/v1:go_default_library", + "//pkg/apis/autoscaling/validation:go_default_library", "//pkg/apis/extensions:go_default_library", - "//pkg/apis/extensions/validation:go_default_library", "//pkg/printers:go_default_library", "//pkg/printers/internalversion:go_default_library", "//pkg/printers/storage:go_default_library", diff --git a/pkg/registry/apps/statefulset/storage/storage.go b/pkg/registry/apps/statefulset/storage/storage.go index 94360f4c096..d97f17270e5 100644 --- a/pkg/registry/apps/statefulset/storage/storage.go +++ b/pkg/registry/apps/statefulset/storage/storage.go @@ -28,8 +28,12 @@ import ( genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/kubernetes/pkg/apis/apps" + appsv1beta1 "k8s.io/kubernetes/pkg/apis/apps/v1beta1" + appsv1beta2 "k8s.io/kubernetes/pkg/apis/apps/v1beta2" + "k8s.io/kubernetes/pkg/apis/autoscaling" + autoscalingv1 "k8s.io/kubernetes/pkg/apis/autoscaling/v1" + autoscalingvalidation "k8s.io/kubernetes/pkg/apis/autoscaling/validation" "k8s.io/kubernetes/pkg/apis/extensions" - extvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation" "k8s.io/kubernetes/pkg/printers" printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" printerstorage "k8s.io/kubernetes/pkg/printers/storage" @@ -105,8 +109,8 @@ func (r *StatusREST) Get(ctx genericapirequest.Context, name string, options *me } // Update alters the status subset of an object. -func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - return r.store.Update(ctx, name, objInfo) +func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation) } // Implement ShortNamesProvider @@ -125,13 +129,20 @@ type ScaleREST struct { var _ = rest.Patcher(&ScaleREST{}) var _ = rest.GroupVersionKindProvider(&ScaleREST{}) -func (r *ScaleREST) GroupVersionKind() schema.GroupVersionKind { - return schema.GroupVersionKind{Group: "extensions", Version: "v1beta1", Kind: "Scale"} +func (r *ScaleREST) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind { + switch containingGV { + case appsv1beta1.SchemeGroupVersion: + return appsv1beta1.SchemeGroupVersion.WithKind("Scale") + case appsv1beta2.SchemeGroupVersion: + return appsv1beta2.SchemeGroupVersion.WithKind("Scale") + default: + return autoscalingv1.SchemeGroupVersion.WithKind("Scale") + } } // New creates a new Scale object func (r *ScaleREST) New() runtime.Object { - return &extensions.Scale{} + return &autoscaling.Scale{} } func (r *ScaleREST) Get(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { @@ -146,7 +157,7 @@ func (r *ScaleREST) Get(ctx genericapirequest.Context, name string, options *met return scale, err } -func (r *ScaleREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { +func (r *ScaleREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { ss, err := r.registry.GetStatefulSet(ctx, name, &metav1.GetOptions{}) if err != nil { return nil, false, err @@ -164,18 +175,18 @@ func (r *ScaleREST) Update(ctx genericapirequest.Context, name string, objInfo r if obj == nil { return nil, false, errors.NewBadRequest(fmt.Sprintf("nil update passed to Scale")) } - scale, ok := obj.(*extensions.Scale) + scale, ok := obj.(*autoscaling.Scale) if !ok { return nil, false, errors.NewBadRequest(fmt.Sprintf("wrong object passed to Scale update: %v", obj)) } - if errs := extvalidation.ValidateScale(scale); len(errs) > 0 { + if errs := autoscalingvalidation.ValidateScale(scale); len(errs) > 0 { return nil, false, errors.NewInvalid(extensions.Kind("Scale"), scale.Name, errs) } ss.Spec.Replicas = scale.Spec.Replicas ss.ResourceVersion = scale.ResourceVersion - ss, err = r.registry.UpdateStatefulSet(ctx, ss) + ss, err = r.registry.UpdateStatefulSet(ctx, ss, createValidation, updateValidation) if err != nil { return nil, false, err } @@ -187,8 +198,12 @@ func (r *ScaleREST) Update(ctx genericapirequest.Context, name string, objInfo r } // scaleFromStatefulSet returns a scale subresource for a statefulset. -func scaleFromStatefulSet(ss *apps.StatefulSet) (*extensions.Scale, error) { - return &extensions.Scale{ +func scaleFromStatefulSet(ss *apps.StatefulSet) (*autoscaling.Scale, error) { + selector, err := metav1.LabelSelectorAsSelector(ss.Spec.Selector) + if err != nil { + return nil, err + } + return &autoscaling.Scale{ // TODO: Create a variant of ObjectMeta type that only contains the fields below. ObjectMeta: metav1.ObjectMeta{ Name: ss.Name, @@ -197,12 +212,12 @@ func scaleFromStatefulSet(ss *apps.StatefulSet) (*extensions.Scale, error) { ResourceVersion: ss.ResourceVersion, CreationTimestamp: ss.CreationTimestamp, }, - Spec: extensions.ScaleSpec{ + Spec: autoscaling.ScaleSpec{ Replicas: ss.Spec.Replicas, }, - Status: extensions.ScaleStatus{ + Status: autoscaling.ScaleStatus{ Replicas: ss.Status.Replicas, - Selector: ss.Spec.Selector, + Selector: selector.String(), }, }, nil } diff --git a/pkg/registry/apps/statefulset/storage/storage_test.go b/pkg/registry/apps/statefulset/storage/storage_test.go index 82961bbd1eb..1e2d13ed848 100644 --- a/pkg/registry/apps/statefulset/storage/storage_test.go +++ b/pkg/registry/apps/statefulset/storage/storage_test.go @@ -27,11 +27,12 @@ import ( "k8s.io/apimachinery/pkg/util/diff" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" "k8s.io/apiserver/pkg/registry/rest" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/apps" - "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/autoscaling" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -46,7 +47,7 @@ func newStorage(t *testing.T) (StatefulSetStorage, *etcdtesting.EtcdTestServer) // createStatefulSet is a helper function that returns a StatefulSet with the updated resource version. func createStatefulSet(storage *REST, ps apps.StatefulSet, t *testing.T) (apps.StatefulSet, error) { ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), ps.Namespace) - obj, err := storage.Create(ctx, &ps, false) + obj, err := storage.Create(ctx, &ps, rest.ValidateAllObjectFunc, false) if err != nil { t.Errorf("Failed to create StatefulSet, %v", err) } @@ -93,7 +94,7 @@ func TestCreate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.StatefulSet.Store.DestroyFunc() - test := registrytest.New(t, storage.StatefulSet.Store) + test := genericregistrytest.New(t, storage.StatefulSet.Store) ps := validNewStatefulSet() ps.ObjectMeta = metav1.ObjectMeta{} test.TestCreate( @@ -125,7 +126,7 @@ func TestStatusUpdate(t *testing.T) { }, } - if _, _, err := storage.Status.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update)); err != nil { + if _, _, err := storage.Status.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc); err != nil { t.Fatalf("unexpected error: %v", err) } obj, err := storage.StatefulSet.Get(ctx, "foo", &metav1.GetOptions{}) @@ -146,7 +147,7 @@ func TestGet(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.StatefulSet.Store.DestroyFunc() - test := registrytest.New(t, storage.StatefulSet.Store) + test := genericregistrytest.New(t, storage.StatefulSet.Store) test.TestGet(validNewStatefulSet()) } @@ -154,7 +155,7 @@ func TestList(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.StatefulSet.Store.DestroyFunc() - test := registrytest.New(t, storage.StatefulSet.Store) + test := genericregistrytest.New(t, storage.StatefulSet.Store) test.TestList(validNewStatefulSet()) } @@ -162,7 +163,7 @@ func TestDelete(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.StatefulSet.Store.DestroyFunc() - test := registrytest.New(t, storage.StatefulSet.Store) + test := genericregistrytest.New(t, storage.StatefulSet.Store) test.TestDelete(validNewStatefulSet()) } @@ -170,7 +171,7 @@ func TestWatch(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.StatefulSet.Store.DestroyFunc() - test := registrytest.New(t, storage.StatefulSet.Store) + test := genericregistrytest.New(t, storage.StatefulSet.Store) test.TestWatch( validNewStatefulSet(), // matching labels @@ -224,7 +225,11 @@ func TestScaleGet(t *testing.T) { t.Fatalf("error setting new statefulset (key: %s) %v: %v", key, validStatefulSet, err) } - want := &extensions.Scale{ + selector, err := metav1.LabelSelectorAsSelector(validStatefulSet.Spec.Selector) + if err != nil { + t.Fatal(err) + } + want := &autoscaling.Scale{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: metav1.NamespaceDefault, @@ -232,16 +237,16 @@ func TestScaleGet(t *testing.T) { ResourceVersion: sts.ResourceVersion, CreationTimestamp: sts.CreationTimestamp, }, - Spec: extensions.ScaleSpec{ + Spec: autoscaling.ScaleSpec{ Replicas: validStatefulSet.Spec.Replicas, }, - Status: extensions.ScaleStatus{ + Status: autoscaling.ScaleStatus{ Replicas: validStatefulSet.Status.Replicas, - Selector: validStatefulSet.Spec.Selector, + Selector: selector.String(), }, } obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{}) - got := obj.(*extensions.Scale) + got := obj.(*autoscaling.Scale) if err != nil { t.Fatalf("error fetching scale for %s: %v", name, err) } @@ -264,17 +269,17 @@ func TestScaleUpdate(t *testing.T) { t.Fatalf("error setting new statefulset (key: %s) %v: %v", key, validStatefulSet, err) } replicas := 12 - update := extensions.Scale{ + update := autoscaling.Scale{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: metav1.NamespaceDefault, }, - Spec: extensions.ScaleSpec{ + Spec: autoscaling.ScaleSpec{ Replicas: int32(replicas), }, } - if _, _, err := storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update)); err != nil { + if _, _, err := storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc); err != nil { t.Fatalf("error updating scale %v: %v", update, err) } @@ -282,7 +287,7 @@ func TestScaleUpdate(t *testing.T) { if err != nil { t.Fatalf("error fetching scale for %s: %v", name, err) } - scale := obj.(*extensions.Scale) + scale := obj.(*autoscaling.Scale) if scale.Spec.Replicas != int32(replicas) { t.Errorf("wrong replicas count expected: %d got: %d", replicas, scale.Spec.Replicas) } @@ -290,7 +295,7 @@ func TestScaleUpdate(t *testing.T) { update.ResourceVersion = sts.ResourceVersion update.Spec.Replicas = 15 - if _, _, err = storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update)); err != nil && !errors.IsConflict(err) { + if _, _, err = storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc); err != nil && !errors.IsConflict(err) { t.Fatalf("unexpected error, expecting an update conflict but got %v", err) } } diff --git a/pkg/registry/apps/statefulset/strategy.go b/pkg/registry/apps/statefulset/strategy.go index c070619c4c4..08ff765a34d 100644 --- a/pkg/registry/apps/statefulset/strategy.go +++ b/pkg/registry/apps/statefulset/strategy.go @@ -17,13 +17,16 @@ limitations under the License. package statefulset import ( + appsv1beta1 "k8s.io/api/apps/v1beta1" + appsv1beta2 "k8s.io/api/apps/v1beta2" apiequality "k8s.io/apimachinery/pkg/api/equality" "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" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/pod" "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/apps/validation" @@ -36,11 +39,20 @@ type statefulSetStrategy struct { } // Strategy is the default logic that applies when creating and updating Replication StatefulSet objects. -var Strategy = statefulSetStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = statefulSetStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} -// DefaultGarbageCollectionPolicy returns Orphan because that was the default -// behavior before the server-side garbage collection was implemented. -func (statefulSetStrategy) DefaultGarbageCollectionPolicy() rest.GarbageCollectionPolicy { +// DefaultGarbageCollectionPolicy returns OrphanDependents by default. For apps/v1, returns DeleteDependents. +func (statefulSetStrategy) DefaultGarbageCollectionPolicy(ctx genericapirequest.Context) rest.GarbageCollectionPolicy { + if requestInfo, found := genericapirequest.RequestInfoFrom(ctx); found { + groupVersion := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion} + switch groupVersion { + case appsv1beta1.SchemeGroupVersion, appsv1beta2.SchemeGroupVersion: + // for back compatibility + return rest.OrphanDependents + default: + return rest.DeleteDependents + } + } return rest.OrphanDependents } diff --git a/pkg/registry/apps/statefulset/strategy_test.go b/pkg/registry/apps/statefulset/strategy_test.go index 6c65b687e1f..aba7b35bc61 100644 --- a/pkg/registry/apps/statefulset/strategy_test.go +++ b/pkg/registry/apps/statefulset/strategy_test.go @@ -22,8 +22,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/apps" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestStatefulSetStrategy(t *testing.T) { @@ -91,12 +91,59 @@ func TestStatefulSetStrategy(t *testing.T) { if len(errs) == 0 { t.Errorf("expected a validation error since updates are disallowed on statefulsets.") } +} +func TestStatefulsetDefaultGarbageCollectionPolicy(t *testing.T) { // Make sure we correctly implement the interface. // Otherwise a typo could silently change the default. var gcds rest.GarbageCollectionDeleteStrategy = Strategy - if got, want := gcds.DefaultGarbageCollectionPolicy(), rest.OrphanDependents; got != want { - t.Errorf("DefaultGarbageCollectionPolicy() = %#v, want %#v", got, want) + tests := []struct { + requestInfo genericapirequest.RequestInfo + expectedGCPolicy rest.GarbageCollectionPolicy + isNilRequestInfo bool + }{ + { + genericapirequest.RequestInfo{ + APIGroup: "apps", + APIVersion: "v1beta1", + Resource: "statefulsets", + }, + rest.OrphanDependents, + false, + }, + { + genericapirequest.RequestInfo{ + APIGroup: "apps", + APIVersion: "v1beta2", + Resource: "statefulsets", + }, + rest.OrphanDependents, + false, + }, + { + genericapirequest.RequestInfo{ + APIGroup: "apps", + APIVersion: "v1", + Resource: "statefulsets", + }, + rest.DeleteDependents, + false, + }, + { + expectedGCPolicy: rest.OrphanDependents, + isNilRequestInfo: true, + }, + } + + for _, test := range tests { + context := genericapirequest.NewContext() + if !test.isNilRequestInfo { + context = genericapirequest.WithRequestInfo(context, &test.requestInfo) + } + if got, want := gcds.DefaultGarbageCollectionPolicy(context), test.expectedGCPolicy; got != want { + t.Errorf("%s/%s: DefaultGarbageCollectionPolicy() = %#v, want %#v", test.requestInfo.APIGroup, + test.requestInfo.APIVersion, got, want) + } } } diff --git a/pkg/registry/authentication/rest/BUILD b/pkg/registry/authentication/rest/BUILD index 8cfa241f519..bd8731b2473 100644 --- a/pkg/registry/authentication/rest/BUILD +++ b/pkg/registry/authentication/rest/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["storage_authentication.go"], importpath = "k8s.io/kubernetes/pkg/registry/authentication/rest", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/authentication:go_default_library", "//pkg/registry/authentication/tokenreview:go_default_library", "//vendor/k8s.io/api/authentication/v1:go_default_library", diff --git a/pkg/registry/authentication/rest/storage_authentication.go b/pkg/registry/authentication/rest/storage_authentication.go index f71f0d8360e..e3f1aa6da95 100644 --- a/pkg/registry/authentication/rest/storage_authentication.go +++ b/pkg/registry/authentication/rest/storage_authentication.go @@ -24,7 +24,7 @@ import ( "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" serverstorage "k8s.io/apiserver/pkg/server/storage" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/authentication" "k8s.io/kubernetes/pkg/registry/authentication/tokenreview" ) @@ -39,7 +39,7 @@ func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorag // return genericapiserver.APIGroupInfo{}, false // } - apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(authentication.GroupName, api.Registry, api.Scheme, api.ParameterCodec, api.Codecs) + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(authentication.GroupName, legacyscheme.Registry, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs) // If you add a version here, be sure to add an entry in `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go with specific priorities. // TODO refactor the plumbing to provide the information in the APIGroupInfo diff --git a/pkg/registry/authentication/tokenreview/BUILD b/pkg/registry/authentication/tokenreview/BUILD index 4644d795041..26eba385a93 100644 --- a/pkg/registry/authentication/tokenreview/BUILD +++ b/pkg/registry/authentication/tokenreview/BUILD @@ -15,6 +15,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", ], ) diff --git a/pkg/registry/authentication/tokenreview/storage.go b/pkg/registry/authentication/tokenreview/storage.go index f512907e6e5..28c1da7c8d9 100644 --- a/pkg/registry/authentication/tokenreview/storage.go +++ b/pkg/registry/authentication/tokenreview/storage.go @@ -24,6 +24,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/authentication/authenticator" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/rest" "k8s.io/kubernetes/pkg/apis/authentication" ) @@ -39,7 +40,7 @@ func (r *REST) New() runtime.Object { return &authentication.TokenReview{} } -func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { +func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) { tokenReview, ok := obj.(*authentication.TokenReview) if !ok { return nil, apierrors.NewBadRequest(fmt.Sprintf("not a TokenReview: %#v", obj)) diff --git a/pkg/registry/authorization/localsubjectaccessreview/BUILD b/pkg/registry/authorization/localsubjectaccessreview/BUILD index b2ba4af182f..6ed963cd2aa 100644 --- a/pkg/registry/authorization/localsubjectaccessreview/BUILD +++ b/pkg/registry/authorization/localsubjectaccessreview/BUILD @@ -17,6 +17,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", ], ) diff --git a/pkg/registry/authorization/localsubjectaccessreview/rest.go b/pkg/registry/authorization/localsubjectaccessreview/rest.go index d36f17582e1..af49b6bd4d9 100644 --- a/pkg/registry/authorization/localsubjectaccessreview/rest.go +++ b/pkg/registry/authorization/localsubjectaccessreview/rest.go @@ -23,6 +23,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/authorization/authorizer" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/rest" authorizationapi "k8s.io/kubernetes/pkg/apis/authorization" authorizationvalidation "k8s.io/kubernetes/pkg/apis/authorization/validation" authorizationutil "k8s.io/kubernetes/pkg/registry/authorization/util" @@ -40,7 +41,7 @@ func (r *REST) New() runtime.Object { return &authorizationapi.LocalSubjectAccessReview{} } -func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { +func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) { localSubjectAccessReview, ok := obj.(*authorizationapi.LocalSubjectAccessReview) if !ok { return nil, kapierrors.NewBadRequest(fmt.Sprintf("not a LocaLocalSubjectAccessReview: %#v", obj)) @@ -57,10 +58,11 @@ func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, include } authorizationAttributes := authorizationutil.AuthorizationAttributesFrom(localSubjectAccessReview.Spec) - allowed, reason, evaluationErr := r.authorizer.Authorize(authorizationAttributes) + decision, reason, evaluationErr := r.authorizer.Authorize(authorizationAttributes) localSubjectAccessReview.Status = authorizationapi.SubjectAccessReviewStatus{ - Allowed: allowed, + Allowed: (decision == authorizer.DecisionAllow), + Denied: (decision == authorizer.DecisionDeny), Reason: reason, } if evaluationErr != nil { diff --git a/pkg/registry/authorization/rest/BUILD b/pkg/registry/authorization/rest/BUILD index 39458494755..029c7848d02 100644 --- a/pkg/registry/authorization/rest/BUILD +++ b/pkg/registry/authorization/rest/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["storage_authorization.go"], importpath = "k8s.io/kubernetes/pkg/registry/authorization/rest", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/authorization:go_default_library", "//pkg/registry/authorization/localsubjectaccessreview:go_default_library", "//pkg/registry/authorization/selfsubjectaccessreview:go_default_library", diff --git a/pkg/registry/authorization/rest/storage_authorization.go b/pkg/registry/authorization/rest/storage_authorization.go index 19eeb2fd254..10f3293c6db 100644 --- a/pkg/registry/authorization/rest/storage_authorization.go +++ b/pkg/registry/authorization/rest/storage_authorization.go @@ -24,7 +24,7 @@ import ( "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" serverstorage "k8s.io/apiserver/pkg/server/storage" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/authorization" "k8s.io/kubernetes/pkg/registry/authorization/localsubjectaccessreview" "k8s.io/kubernetes/pkg/registry/authorization/selfsubjectaccessreview" @@ -42,7 +42,7 @@ func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorag return genericapiserver.APIGroupInfo{}, false } - apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(authorization.GroupName, api.Registry, api.Scheme, api.ParameterCodec, api.Codecs) + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(authorization.GroupName, legacyscheme.Registry, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs) // If you add a version here, be sure to add an entry in `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go with specific priorities. // TODO refactor the plumbing to provide the information in the APIGroupInfo diff --git a/pkg/registry/authorization/selfsubjectaccessreview/BUILD b/pkg/registry/authorization/selfsubjectaccessreview/BUILD index fa7f914485b..06b8845dbc4 100644 --- a/pkg/registry/authorization/selfsubjectaccessreview/BUILD +++ b/pkg/registry/authorization/selfsubjectaccessreview/BUILD @@ -17,6 +17,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", ], ) diff --git a/pkg/registry/authorization/selfsubjectaccessreview/rest.go b/pkg/registry/authorization/selfsubjectaccessreview/rest.go index 4498b5f5a70..38900a877f4 100644 --- a/pkg/registry/authorization/selfsubjectaccessreview/rest.go +++ b/pkg/registry/authorization/selfsubjectaccessreview/rest.go @@ -23,6 +23,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/authorization/authorizer" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/rest" authorizationapi "k8s.io/kubernetes/pkg/apis/authorization" authorizationvalidation "k8s.io/kubernetes/pkg/apis/authorization/validation" authorizationutil "k8s.io/kubernetes/pkg/registry/authorization/util" @@ -40,7 +41,7 @@ func (r *REST) New() runtime.Object { return &authorizationapi.SelfSubjectAccessReview{} } -func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { +func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) { selfSAR, ok := obj.(*authorizationapi.SelfSubjectAccessReview) if !ok { return nil, apierrors.NewBadRequest(fmt.Sprintf("not a SelfSubjectAccessReview: %#v", obj)) @@ -60,10 +61,11 @@ func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, include authorizationAttributes = authorizationutil.NonResourceAttributesFrom(userToCheck, *selfSAR.Spec.NonResourceAttributes) } - allowed, reason, evaluationErr := r.authorizer.Authorize(authorizationAttributes) + decision, reason, evaluationErr := r.authorizer.Authorize(authorizationAttributes) selfSAR.Status = authorizationapi.SubjectAccessReviewStatus{ - Allowed: allowed, + Allowed: (decision == authorizer.DecisionAllow), + Denied: (decision == authorizer.DecisionDeny), Reason: reason, } if evaluationErr != nil { diff --git a/pkg/registry/authorization/selfsubjectrulesreview/BUILD b/pkg/registry/authorization/selfsubjectrulesreview/BUILD index 6bc1c631b8a..2afdc2381bf 100644 --- a/pkg/registry/authorization/selfsubjectrulesreview/BUILD +++ b/pkg/registry/authorization/selfsubjectrulesreview/BUILD @@ -11,6 +11,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", ], ) diff --git a/pkg/registry/authorization/selfsubjectrulesreview/rest.go b/pkg/registry/authorization/selfsubjectrulesreview/rest.go index 062829faba5..2093f324ede 100644 --- a/pkg/registry/authorization/selfsubjectrulesreview/rest.go +++ b/pkg/registry/authorization/selfsubjectrulesreview/rest.go @@ -23,6 +23,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/authorization/authorizer" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/rest" authorizationapi "k8s.io/kubernetes/pkg/apis/authorization" ) @@ -42,7 +43,7 @@ func (r *REST) New() runtime.Object { } // Create attempts to get self subject rules in specific namespace. -func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { +func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) { selfSRR, ok := obj.(*authorizationapi.SelfSubjectRulesReview) if !ok { return nil, apierrors.NewBadRequest(fmt.Sprintf("not a SelfSubjectRulesReview: %#v", obj)) diff --git a/pkg/registry/authorization/subjectaccessreview/BUILD b/pkg/registry/authorization/subjectaccessreview/BUILD index ebc67716823..569a8065188 100644 --- a/pkg/registry/authorization/subjectaccessreview/BUILD +++ b/pkg/registry/authorization/subjectaccessreview/BUILD @@ -18,6 +18,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", ], ) @@ -37,12 +38,13 @@ filegroup( go_test( name = "go_default_test", srcs = ["rest_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/authorization/subjectaccessreview", - library = ":go_default_library", deps = [ "//pkg/apis/authorization:go_default_library", "//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library", "//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", ], ) diff --git a/pkg/registry/authorization/subjectaccessreview/rest.go b/pkg/registry/authorization/subjectaccessreview/rest.go index 1583a32ac04..92da3d34244 100644 --- a/pkg/registry/authorization/subjectaccessreview/rest.go +++ b/pkg/registry/authorization/subjectaccessreview/rest.go @@ -23,6 +23,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/authorization/authorizer" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/rest" authorizationapi "k8s.io/kubernetes/pkg/apis/authorization" authorizationvalidation "k8s.io/kubernetes/pkg/apis/authorization/validation" authorizationutil "k8s.io/kubernetes/pkg/registry/authorization/util" @@ -40,7 +41,7 @@ func (r *REST) New() runtime.Object { return &authorizationapi.SubjectAccessReview{} } -func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { +func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) { subjectAccessReview, ok := obj.(*authorizationapi.SubjectAccessReview) if !ok { return nil, kapierrors.NewBadRequest(fmt.Sprintf("not a SubjectAccessReview: %#v", obj)) @@ -50,10 +51,11 @@ func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, include } authorizationAttributes := authorizationutil.AuthorizationAttributesFrom(subjectAccessReview.Spec) - allowed, reason, evaluationErr := r.authorizer.Authorize(authorizationAttributes) + decision, reason, evaluationErr := r.authorizer.Authorize(authorizationAttributes) subjectAccessReview.Status = authorizationapi.SubjectAccessReviewStatus{ - Allowed: allowed, + Allowed: (decision == authorizer.DecisionAllow), + Denied: (decision == authorizer.DecisionDeny), Reason: reason, } if evaluationErr != nil { diff --git a/pkg/registry/authorization/subjectaccessreview/rest_test.go b/pkg/registry/authorization/subjectaccessreview/rest_test.go index b278538249b..ce882a8b4bf 100644 --- a/pkg/registry/authorization/subjectaccessreview/rest_test.go +++ b/pkg/registry/authorization/subjectaccessreview/rest_test.go @@ -26,28 +26,29 @@ import ( "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/rest" authorizationapi "k8s.io/kubernetes/pkg/apis/authorization" ) type fakeAuthorizer struct { attrs authorizer.Attributes - ok bool - reason string - err error + decision authorizer.Decision + reason string + err error } -func (f *fakeAuthorizer) Authorize(attrs authorizer.Attributes) (bool, string, error) { +func (f *fakeAuthorizer) Authorize(attrs authorizer.Attributes) (authorizer.Decision, string, error) { f.attrs = attrs - return f.ok, f.reason, f.err + return f.decision, f.reason, f.err } func TestCreate(t *testing.T) { testcases := map[string]struct { - spec authorizationapi.SubjectAccessReviewSpec - ok bool - reason string - err error + spec authorizationapi.SubjectAccessReviewSpec + decision authorizer.Decision + reason string + err error expectedErr string expectedAttrs authorizer.Attributes @@ -62,9 +63,9 @@ func TestCreate(t *testing.T) { User: "bob", NonResourceAttributes: &authorizationapi.NonResourceAttributes{Verb: "get", Path: "/mypath"}, }, - ok: false, - reason: "myreason", - err: errors.New("myerror"), + decision: authorizer.DecisionNoOpinion, + reason: "myreason", + err: errors.New("myerror"), expectedAttrs: authorizer.AttributesRecord{ User: &user.DefaultInfo{Name: "bob"}, Verb: "get", @@ -83,9 +84,9 @@ func TestCreate(t *testing.T) { User: "bob", NonResourceAttributes: &authorizationapi.NonResourceAttributes{Verb: "get", Path: "/mypath"}, }, - ok: true, - reason: "allowed", - err: nil, + decision: authorizer.DecisionAllow, + reason: "allowed", + err: nil, expectedAttrs: authorizer.AttributesRecord{ User: &user.DefaultInfo{Name: "bob"}, Verb: "get", @@ -112,9 +113,9 @@ func TestCreate(t *testing.T) { Name: "mydeployment", }, }, - ok: false, - reason: "myreason", - err: errors.New("myerror"), + decision: authorizer.DecisionNoOpinion, + reason: "myreason", + err: errors.New("myerror"), expectedAttrs: authorizer.AttributesRecord{ User: &user.DefaultInfo{Name: "bob"}, Namespace: "myns", @@ -128,6 +129,7 @@ func TestCreate(t *testing.T) { }, expectedStatus: authorizationapi.SubjectAccessReviewStatus{ Allowed: false, + Denied: false, Reason: "myreason", EvaluationError: "myerror", }, @@ -146,9 +148,9 @@ func TestCreate(t *testing.T) { Name: "mydeployment", }, }, - ok: true, - reason: "allowed", - err: nil, + decision: authorizer.DecisionAllow, + reason: "allowed", + err: nil, expectedAttrs: authorizer.AttributesRecord{ User: &user.DefaultInfo{Name: "bob"}, Namespace: "myns", @@ -162,21 +164,38 @@ func TestCreate(t *testing.T) { }, expectedStatus: authorizationapi.SubjectAccessReviewStatus{ Allowed: true, + Denied: false, Reason: "allowed", EvaluationError: "", }, }, + + "resource denied": { + spec: authorizationapi.SubjectAccessReviewSpec{ + User: "bob", + ResourceAttributes: &authorizationapi.ResourceAttributes{}, + }, + decision: authorizer.DecisionDeny, + expectedAttrs: authorizer.AttributesRecord{ + User: &user.DefaultInfo{Name: "bob"}, + ResourceRequest: true, + }, + expectedStatus: authorizationapi.SubjectAccessReviewStatus{ + Allowed: false, + Denied: true, + }, + }, } for k, tc := range testcases { auth := &fakeAuthorizer{ - ok: tc.ok, - reason: tc.reason, - err: tc.err, + decision: tc.decision, + reason: tc.reason, + err: tc.err, } - rest := NewREST(auth) + storage := NewREST(auth) - result, err := rest.Create(genericapirequest.NewContext(), &authorizationapi.SubjectAccessReview{Spec: tc.spec}, false) + result, err := storage.Create(genericapirequest.NewContext(), &authorizationapi.SubjectAccessReview{Spec: tc.spec}, rest.ValidateAllObjectFunc, false) if err != nil { if tc.expectedErr != "" { if !strings.Contains(err.Error(), tc.expectedErr) { diff --git a/pkg/registry/authorization/util/BUILD b/pkg/registry/authorization/util/BUILD index 0d1fc9be2de..285ea9dd9b3 100644 --- a/pkg/registry/authorization/util/BUILD +++ b/pkg/registry/authorization/util/BUILD @@ -33,8 +33,8 @@ filegroup( go_test( name = "go_default_test", srcs = ["helpers_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/authorization/util", - library = ":go_default_library", deps = [ "//pkg/apis/authorization:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", diff --git a/pkg/registry/autoscaling/horizontalpodautoscaler/BUILD b/pkg/registry/autoscaling/horizontalpodautoscaler/BUILD index 33fd9fa0d73..e5fef0856af 100644 --- a/pkg/registry/autoscaling/horizontalpodautoscaler/BUILD +++ b/pkg/registry/autoscaling/horizontalpodautoscaler/BUILD @@ -13,7 +13,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/autoscaling/horizontalpodautoscaler", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/autoscaling:go_default_library", "//pkg/apis/autoscaling/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/pkg/registry/autoscaling/horizontalpodautoscaler/storage/BUILD b/pkg/registry/autoscaling/horizontalpodautoscaler/storage/BUILD index 3ec7da1e046..6883f986284 100644 --- a/pkg/registry/autoscaling/horizontalpodautoscaler/storage/BUILD +++ b/pkg/registry/autoscaling/horizontalpodautoscaler/storage/BUILD @@ -9,11 +9,11 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/autoscaling/horizontalpodautoscaler/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", "//pkg/apis/autoscaling:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/api/autoscaling/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -21,6 +21,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], ) diff --git a/pkg/registry/autoscaling/horizontalpodautoscaler/storage/storage.go b/pkg/registry/autoscaling/horizontalpodautoscaler/storage/storage.go index 2988ebb4ef5..5d6602f5dd0 100644 --- a/pkg/registry/autoscaling/horizontalpodautoscaler/storage/storage.go +++ b/pkg/registry/autoscaling/horizontalpodautoscaler/storage/storage.go @@ -83,6 +83,6 @@ func (r *StatusREST) Get(ctx genericapirequest.Context, name string, options *me } // Update alters the status subset of an object. -func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - return r.store.Update(ctx, name, objInfo) +func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation) } diff --git a/pkg/registry/autoscaling/horizontalpodautoscaler/storage/storage_test.go b/pkg/registry/autoscaling/horizontalpodautoscaler/storage/storage_test.go index 6e4ed8b84aa..eb803bf71f0 100644 --- a/pkg/registry/autoscaling/horizontalpodautoscaler/storage/storage_test.go +++ b/pkg/registry/autoscaling/horizontalpodautoscaler/storage/storage_test.go @@ -20,14 +20,15 @@ import ( "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/autoscaling" + api "k8s.io/kubernetes/pkg/apis/core" // Ensure that autoscaling/v1 package is initialized. _ "k8s.io/api/autoscaling/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -74,7 +75,7 @@ func TestCreate(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) autoscaler := validNewHorizontalPodAutoscaler("foo") autoscaler.ObjectMeta = metav1.ObjectMeta{} test.TestCreate( @@ -89,7 +90,7 @@ func TestUpdate(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestUpdate( // valid validNewHorizontalPodAutoscaler("foo"), @@ -106,7 +107,7 @@ func TestDelete(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestDelete(validNewHorizontalPodAutoscaler("foo")) } @@ -114,7 +115,7 @@ func TestGet(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestGet(validNewHorizontalPodAutoscaler("foo")) } @@ -122,7 +123,7 @@ func TestList(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestList(validNewHorizontalPodAutoscaler("foo")) } @@ -130,7 +131,7 @@ func TestWatch(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestWatch( validNewHorizontalPodAutoscaler("foo"), // matching labels diff --git a/pkg/registry/autoscaling/horizontalpodautoscaler/strategy.go b/pkg/registry/autoscaling/horizontalpodautoscaler/strategy.go index d9c81ab07e5..3100a96982f 100644 --- a/pkg/registry/autoscaling/horizontalpodautoscaler/strategy.go +++ b/pkg/registry/autoscaling/horizontalpodautoscaler/strategy.go @@ -21,7 +21,7 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/autoscaling" "k8s.io/kubernetes/pkg/apis/autoscaling/validation" ) @@ -34,7 +34,7 @@ type autoscalerStrategy struct { // Strategy is the default logic that applies when creating and updating HorizontalPodAutoscaler // objects via the REST API. -var Strategy = autoscalerStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = autoscalerStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} // NamespaceScoped is true for autoscaler. func (autoscalerStrategy) NamespaceScoped() bool { diff --git a/pkg/registry/autoscaling/rest/BUILD b/pkg/registry/autoscaling/rest/BUILD index 0014cd38221..f0f62a9a8e9 100644 --- a/pkg/registry/autoscaling/rest/BUILD +++ b/pkg/registry/autoscaling/rest/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["storage_autoscaling.go"], importpath = "k8s.io/kubernetes/pkg/registry/autoscaling/rest", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/autoscaling:go_default_library", "//pkg/registry/autoscaling/horizontalpodautoscaler/storage:go_default_library", "//vendor/k8s.io/api/autoscaling/v1:go_default_library", diff --git a/pkg/registry/autoscaling/rest/storage_autoscaling.go b/pkg/registry/autoscaling/rest/storage_autoscaling.go index 0b24cd12262..e6ce55b483d 100644 --- a/pkg/registry/autoscaling/rest/storage_autoscaling.go +++ b/pkg/registry/autoscaling/rest/storage_autoscaling.go @@ -23,7 +23,7 @@ import ( "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" serverstorage "k8s.io/apiserver/pkg/server/storage" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/autoscaling" horizontalpodautoscalerstore "k8s.io/kubernetes/pkg/registry/autoscaling/horizontalpodautoscaler/storage" ) @@ -31,7 +31,7 @@ import ( type RESTStorageProvider struct{} func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) { - apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(autoscaling.GroupName, api.Registry, api.Scheme, api.ParameterCodec, api.Codecs) + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(autoscaling.GroupName, legacyscheme.Registry, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs) // If you add a version here, be sure to add an entry in `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go with specific priorities. // TODO refactor the plumbing to provide the information in the APIGroupInfo diff --git a/pkg/registry/batch/cronjob/BUILD b/pkg/registry/batch/cronjob/BUILD index 64455050cb4..794a014ff95 100644 --- a/pkg/registry/batch/cronjob/BUILD +++ b/pkg/registry/batch/cronjob/BUILD @@ -14,7 +14,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/batch/cronjob", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/pod:go_default_library", "//pkg/apis/batch:go_default_library", "//pkg/apis/batch/validation:go_default_library", @@ -29,11 +29,11 @@ go_library( go_test( name = "go_default_test", srcs = ["strategy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/batch/cronjob", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", "//pkg/apis/batch:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", diff --git a/pkg/registry/batch/cronjob/storage/BUILD b/pkg/registry/batch/cronjob/storage/BUILD index 0cb9584c3eb..0705136a135 100644 --- a/pkg/registry/batch/cronjob/storage/BUILD +++ b/pkg/registry/batch/cronjob/storage/BUILD @@ -9,12 +9,12 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/batch/cronjob/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", "//pkg/api/testapi:go_default_library", "//pkg/apis/batch:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/api/batch/v2alpha1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -22,6 +22,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], ) diff --git a/pkg/registry/batch/cronjob/storage/storage.go b/pkg/registry/batch/cronjob/storage/storage.go index 407a3ef40cb..d8ae47f3890 100644 --- a/pkg/registry/batch/cronjob/storage/storage.go +++ b/pkg/registry/batch/cronjob/storage/storage.go @@ -81,6 +81,6 @@ func (r *StatusREST) Get(ctx genericapirequest.Context, name string, options *me } // Update alters the status subset of an object. -func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - return r.store.Update(ctx, name, objInfo) +func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation) } diff --git a/pkg/registry/batch/cronjob/storage/storage_test.go b/pkg/registry/batch/cronjob/storage/storage_test.go index 780d0baaf52..2a78b522ea4 100644 --- a/pkg/registry/batch/cronjob/storage/storage_test.go +++ b/pkg/registry/batch/cronjob/storage/storage_test.go @@ -25,10 +25,11 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/apis/batch" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -73,7 +74,7 @@ func TestCreate(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) validCronJob := validNewCronJob() validCronJob.ObjectMeta = metav1.ObjectMeta{} test.TestCreate( @@ -95,7 +96,7 @@ func TestUpdate(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) schedule := "1 1 1 1 ?" test.TestUpdate( // valid @@ -124,7 +125,7 @@ func TestDelete(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestDelete(validNewCronJob()) } @@ -137,7 +138,7 @@ func TestGet(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestGet(validNewCronJob()) } @@ -150,7 +151,7 @@ func TestList(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestList(validNewCronJob()) } @@ -163,7 +164,7 @@ func TestWatch(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestWatch( validNewCronJob(), // matching labels diff --git a/pkg/registry/batch/cronjob/strategy.go b/pkg/registry/batch/cronjob/strategy.go index ac91c59c7c4..3544ca10b13 100644 --- a/pkg/registry/batch/cronjob/strategy.go +++ b/pkg/registry/batch/cronjob/strategy.go @@ -22,7 +22,7 @@ import ( genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/pod" "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/batch/validation" @@ -35,11 +35,11 @@ type cronJobStrategy struct { } // Strategy is the default logic that applies when creating and updating CronJob objects. -var Strategy = cronJobStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = cronJobStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} // DefaultGarbageCollectionPolicy returns Orphan because that was the default // behavior before the server-side garbage collection was implemented. -func (cronJobStrategy) DefaultGarbageCollectionPolicy() rest.GarbageCollectionPolicy { +func (cronJobStrategy) DefaultGarbageCollectionPolicy(ctx genericapirequest.Context) rest.GarbageCollectionPolicy { return rest.OrphanDependents } diff --git a/pkg/registry/batch/cronjob/strategy_test.go b/pkg/registry/batch/cronjob/strategy_test.go index 4dd34ee4ad7..6cf9b60b589 100644 --- a/pkg/registry/batch/cronjob/strategy_test.go +++ b/pkg/registry/batch/cronjob/strategy_test.go @@ -22,8 +22,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/batch" + api "k8s.io/kubernetes/pkg/apis/core" ) func newBool(a bool) *bool { @@ -96,7 +96,7 @@ func TestCronJobStrategy(t *testing.T) { // Make sure we correctly implement the interface. // Otherwise a typo could silently change the default. var gcds rest.GarbageCollectionDeleteStrategy = Strategy - if got, want := gcds.DefaultGarbageCollectionPolicy(), rest.OrphanDependents; got != want { + if got, want := gcds.DefaultGarbageCollectionPolicy(genericapirequest.NewContext()), rest.OrphanDependents; got != want { t.Errorf("DefaultGarbageCollectionPolicy() = %#v, want %#v", got, want) } } diff --git a/pkg/registry/batch/job/BUILD b/pkg/registry/batch/job/BUILD index 4ebab0261c0..c440213b87b 100644 --- a/pkg/registry/batch/job/BUILD +++ b/pkg/registry/batch/job/BUILD @@ -14,7 +14,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/batch/job", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/pod:go_default_library", "//pkg/apis/batch:go_default_library", "//pkg/apis/batch/validation:go_default_library", @@ -34,13 +34,13 @@ go_library( go_test( name = "go_default_test", srcs = ["strategy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/batch/job", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", "//pkg/api/testapi:go_default_library", "//pkg/api/testing:go_default_library", "//pkg/apis/batch:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", diff --git a/pkg/registry/batch/job/storage/BUILD b/pkg/registry/batch/job/storage/BUILD index 75abed92ded..393ecfa3d67 100644 --- a/pkg/registry/batch/job/storage/BUILD +++ b/pkg/registry/batch/job/storage/BUILD @@ -9,17 +9,18 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/batch/job/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", "//pkg/apis/batch:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], ) diff --git a/pkg/registry/batch/job/storage/storage.go b/pkg/registry/batch/job/storage/storage.go index 4a0ed987417..580cc5a7531 100644 --- a/pkg/registry/batch/job/storage/storage.go +++ b/pkg/registry/batch/job/storage/storage.go @@ -98,6 +98,6 @@ func (r *StatusREST) Get(ctx genericapirequest.Context, name string, options *me } // Update alters the status subset of an object. -func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - return r.store.Update(ctx, name, objInfo) +func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation) } diff --git a/pkg/registry/batch/job/storage/storage_test.go b/pkg/registry/batch/job/storage/storage_test.go index 963977eb253..f242afaddbf 100644 --- a/pkg/registry/batch/job/storage/storage_test.go +++ b/pkg/registry/batch/job/storage/storage_test.go @@ -24,9 +24,10 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/batch" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -82,7 +83,7 @@ func TestCreate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Job.Store.DestroyFunc() - test := registrytest.New(t, storage.Job.Store) + test := genericregistrytest.New(t, storage.Job.Store) validJob := validNewJob() validJob.ObjectMeta = metav1.ObjectMeta{} test.TestCreate( @@ -103,7 +104,7 @@ func TestUpdate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Job.Store.DestroyFunc() - test := registrytest.New(t, storage.Job.Store) + test := genericregistrytest.New(t, storage.Job.Store) two := int32(2) test.TestUpdate( // valid @@ -132,7 +133,7 @@ func TestDelete(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Job.Store.DestroyFunc() - test := registrytest.New(t, storage.Job.Store) + test := genericregistrytest.New(t, storage.Job.Store) test.TestDelete(validNewJob()) } @@ -140,7 +141,7 @@ func TestGet(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Job.Store.DestroyFunc() - test := registrytest.New(t, storage.Job.Store) + test := genericregistrytest.New(t, storage.Job.Store) test.TestGet(validNewJob()) } @@ -148,7 +149,7 @@ func TestList(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Job.Store.DestroyFunc() - test := registrytest.New(t, storage.Job.Store) + test := genericregistrytest.New(t, storage.Job.Store) test.TestList(validNewJob()) } @@ -156,7 +157,7 @@ func TestWatch(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Job.Store.DestroyFunc() - test := registrytest.New(t, storage.Job.Store) + test := genericregistrytest.New(t, storage.Job.Store) test.TestWatch( validNewJob(), // matching labels diff --git a/pkg/registry/batch/job/strategy.go b/pkg/registry/batch/job/strategy.go index b3f6ae33005..aecf38a36d9 100644 --- a/pkg/registry/batch/job/strategy.go +++ b/pkg/registry/batch/job/strategy.go @@ -30,7 +30,7 @@ import ( "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/pod" "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/batch/validation" @@ -43,11 +43,11 @@ type jobStrategy struct { } // Strategy is the default logic that applies when creating and updating Replication Controller objects. -var Strategy = jobStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = jobStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} // DefaultGarbageCollectionPolicy returns Orphan because that was the default // behavior before the server-side garbage collection was implemented. -func (jobStrategy) DefaultGarbageCollectionPolicy() rest.GarbageCollectionPolicy { +func (jobStrategy) DefaultGarbageCollectionPolicy(ctx genericapirequest.Context) rest.GarbageCollectionPolicy { return rest.OrphanDependents } diff --git a/pkg/registry/batch/job/strategy_test.go b/pkg/registry/batch/job/strategy_test.go index 1586c4994d1..f8cfc609196 100644 --- a/pkg/registry/batch/job/strategy_test.go +++ b/pkg/registry/batch/job/strategy_test.go @@ -24,10 +24,10 @@ import ( "k8s.io/apimachinery/pkg/types" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" apitesting "k8s.io/kubernetes/pkg/api/testing" "k8s.io/kubernetes/pkg/apis/batch" + api "k8s.io/kubernetes/pkg/apis/core" ) func newBool(a bool) *bool { @@ -105,7 +105,7 @@ func TestJobStrategy(t *testing.T) { // Make sure we correctly implement the interface. // Otherwise a typo could silently change the default. var gcds rest.GarbageCollectionDeleteStrategy = Strategy - if got, want := gcds.DefaultGarbageCollectionPolicy(), rest.OrphanDependents; got != want { + if got, want := gcds.DefaultGarbageCollectionPolicy(genericapirequest.NewContext()), rest.OrphanDependents; got != want { t.Errorf("DefaultGarbageCollectionPolicy() = %#v, want %#v", got, want) } } diff --git a/pkg/registry/batch/rest/BUILD b/pkg/registry/batch/rest/BUILD index 1d9d3268bf0..16011acb05b 100644 --- a/pkg/registry/batch/rest/BUILD +++ b/pkg/registry/batch/rest/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["storage_batch.go"], importpath = "k8s.io/kubernetes/pkg/registry/batch/rest", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/batch:go_default_library", "//pkg/registry/batch/cronjob/storage:go_default_library", "//pkg/registry/batch/job/storage:go_default_library", diff --git a/pkg/registry/batch/rest/storage_batch.go b/pkg/registry/batch/rest/storage_batch.go index 6cfd8f4a645..93c453de38d 100644 --- a/pkg/registry/batch/rest/storage_batch.go +++ b/pkg/registry/batch/rest/storage_batch.go @@ -24,7 +24,7 @@ import ( "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" serverstorage "k8s.io/apiserver/pkg/server/storage" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/batch" cronjobstore "k8s.io/kubernetes/pkg/registry/batch/cronjob/storage" jobstore "k8s.io/kubernetes/pkg/registry/batch/job/storage" @@ -33,7 +33,7 @@ import ( type RESTStorageProvider struct{} func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) { - apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(batch.GroupName, api.Registry, api.Scheme, api.ParameterCodec, api.Codecs) + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(batch.GroupName, legacyscheme.Registry, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs) // If you add a version here, be sure to add an entry in `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go with specific priorities. // TODO refactor the plumbing to provide the information in the APIGroupInfo diff --git a/pkg/registry/certificates/certificates/BUILD b/pkg/registry/certificates/certificates/BUILD index 0f5491502a2..b4800e75e9b 100644 --- a/pkg/registry/certificates/certificates/BUILD +++ b/pkg/registry/certificates/certificates/BUILD @@ -15,7 +15,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/certificates/certificates", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/certificates:go_default_library", "//pkg/apis/certificates/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", @@ -32,8 +32,8 @@ go_library( go_test( name = "go_default_test", srcs = ["strategy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/certificates/certificates", - library = ":go_default_library", deps = [ "//pkg/apis/certificates:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/pkg/registry/certificates/certificates/registry.go b/pkg/registry/certificates/certificates/registry.go index 4dfd9611452..d3eea018bce 100644 --- a/pkg/registry/certificates/certificates/registry.go +++ b/pkg/registry/certificates/certificates/registry.go @@ -28,8 +28,8 @@ import ( // Registry is an interface for things that know how to store CSRs. type Registry interface { ListCSRs(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*certificates.CertificateSigningRequestList, error) - CreateCSR(ctx genericapirequest.Context, csr *certificates.CertificateSigningRequest) error - UpdateCSR(ctx genericapirequest.Context, csr *certificates.CertificateSigningRequest) error + CreateCSR(ctx genericapirequest.Context, csr *certificates.CertificateSigningRequest, createValidation rest.ValidateObjectFunc) error + UpdateCSR(ctx genericapirequest.Context, csr *certificates.CertificateSigningRequest, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error GetCSR(ctx genericapirequest.Context, csrID string, options *metav1.GetOptions) (*certificates.CertificateSigningRequest, error) DeleteCSR(ctx genericapirequest.Context, csrID string) error WatchCSRs(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) @@ -55,13 +55,13 @@ func (s *storage) ListCSRs(ctx genericapirequest.Context, options *metainternalv return obj.(*certificates.CertificateSigningRequestList), nil } -func (s *storage) CreateCSR(ctx genericapirequest.Context, csr *certificates.CertificateSigningRequest) error { - _, err := s.Create(ctx, csr, false) +func (s *storage) CreateCSR(ctx genericapirequest.Context, csr *certificates.CertificateSigningRequest, createValidation rest.ValidateObjectFunc) error { + _, err := s.Create(ctx, csr, createValidation, false) return err } -func (s *storage) UpdateCSR(ctx genericapirequest.Context, csr *certificates.CertificateSigningRequest) error { - _, _, err := s.Update(ctx, csr.Name, rest.DefaultUpdatedObjectInfo(csr)) +func (s *storage) UpdateCSR(ctx genericapirequest.Context, csr *certificates.CertificateSigningRequest, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error { + _, _, err := s.Update(ctx, csr.Name, rest.DefaultUpdatedObjectInfo(csr), createValidation, updateValidation) return err } diff --git a/pkg/registry/certificates/certificates/storage/storage.go b/pkg/registry/certificates/certificates/storage/storage.go index 52bf2dbb791..4f69ac91189 100644 --- a/pkg/registry/certificates/certificates/storage/storage.go +++ b/pkg/registry/certificates/certificates/storage/storage.go @@ -78,8 +78,8 @@ func (r *StatusREST) New() runtime.Object { } // Update alters the status subset of an object. -func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - return r.store.Update(ctx, name, objInfo) +func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation) } // ApprovalREST implements the REST endpoint for changing the approval state of a CSR. @@ -92,6 +92,6 @@ func (r *ApprovalREST) New() runtime.Object { } // Update alters the approval subset of an object. -func (r *ApprovalREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - return r.store.Update(ctx, name, objInfo) +func (r *ApprovalREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation) } diff --git a/pkg/registry/certificates/certificates/strategy.go b/pkg/registry/certificates/certificates/strategy.go index 74286919526..eff6a8965ea 100644 --- a/pkg/registry/certificates/certificates/strategy.go +++ b/pkg/registry/certificates/certificates/strategy.go @@ -24,7 +24,7 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/certificates" "k8s.io/kubernetes/pkg/apis/certificates/validation" ) @@ -37,7 +37,7 @@ type csrStrategy struct { // csrStrategy is the default logic that applies when creating and updating // CSR objects. -var Strategy = csrStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = csrStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} // NamespaceScoped is true for CSRs. func (csrStrategy) NamespaceScoped() bool { diff --git a/pkg/registry/certificates/rest/BUILD b/pkg/registry/certificates/rest/BUILD index 40644aa287d..6ab6c64cf92 100644 --- a/pkg/registry/certificates/rest/BUILD +++ b/pkg/registry/certificates/rest/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["storage_certificates.go"], importpath = "k8s.io/kubernetes/pkg/registry/certificates/rest", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/certificates:go_default_library", "//pkg/registry/certificates/certificates/storage:go_default_library", "//vendor/k8s.io/api/certificates/v1beta1:go_default_library", diff --git a/pkg/registry/certificates/rest/storage_certificates.go b/pkg/registry/certificates/rest/storage_certificates.go index aeef7cf707d..3fe8915b577 100644 --- a/pkg/registry/certificates/rest/storage_certificates.go +++ b/pkg/registry/certificates/rest/storage_certificates.go @@ -22,7 +22,7 @@ import ( "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" serverstorage "k8s.io/apiserver/pkg/server/storage" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/certificates" certificatestore "k8s.io/kubernetes/pkg/registry/certificates/certificates/storage" ) @@ -30,7 +30,7 @@ import ( type RESTStorageProvider struct{} func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) { - apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(certificates.GroupName, api.Registry, api.Scheme, api.ParameterCodec, api.Codecs) + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(certificates.GroupName, legacyscheme.Registry, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs) // If you add a version here, be sure to add an entry in `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go with specific priorities. // TODO refactor the plumbing to provide the information in the APIGroupInfo diff --git a/pkg/registry/core/componentstatus/BUILD b/pkg/registry/core/componentstatus/BUILD index 5dbb6bf3404..640e3b9483e 100644 --- a/pkg/registry/core/componentstatus/BUILD +++ b/pkg/registry/core/componentstatus/BUILD @@ -15,7 +15,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/core/componentstatus", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/probe:go_default_library", "//pkg/probe/http:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", @@ -33,10 +33,10 @@ go_test( "rest_test.go", "validator_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/componentstatus", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/probe:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", diff --git a/pkg/registry/core/componentstatus/rest.go b/pkg/registry/core/componentstatus/rest.go index e0f0e18e4ec..c67b42ead71 100644 --- a/pkg/registry/core/componentstatus/rest.go +++ b/pkg/registry/core/componentstatus/rest.go @@ -25,7 +25,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/probe" ) diff --git a/pkg/registry/core/componentstatus/rest_test.go b/pkg/registry/core/componentstatus/rest_test.go index cf643d36906..3b2194f56ec 100644 --- a/pkg/registry/core/componentstatus/rest_test.go +++ b/pkg/registry/core/componentstatus/rest_test.go @@ -29,7 +29,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/diff" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/probe" ) diff --git a/pkg/registry/core/configmap/BUILD b/pkg/registry/core/configmap/BUILD index 4a7984b93c0..01985ff76c4 100644 --- a/pkg/registry/core/configmap/BUILD +++ b/pkg/registry/core/configmap/BUILD @@ -15,8 +15,9 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/core/configmap", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", @@ -31,10 +32,10 @@ go_library( go_test( name = "go_default_test", srcs = ["strategy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/configmap", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", ], diff --git a/pkg/registry/core/configmap/registry.go b/pkg/registry/core/configmap/registry.go index b26e6141e18..440300f2dcf 100644 --- a/pkg/registry/core/configmap/registry.go +++ b/pkg/registry/core/configmap/registry.go @@ -22,7 +22,7 @@ import ( "k8s.io/apimachinery/pkg/watch" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // Registry is an interface for things that know how to store ConfigMaps. @@ -30,8 +30,8 @@ type Registry interface { ListConfigMaps(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*api.ConfigMapList, error) WatchConfigMaps(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) GetConfigMap(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (*api.ConfigMap, error) - CreateConfigMap(ctx genericapirequest.Context, cfg *api.ConfigMap) (*api.ConfigMap, error) - UpdateConfigMap(ctx genericapirequest.Context, cfg *api.ConfigMap) (*api.ConfigMap, error) + CreateConfigMap(ctx genericapirequest.Context, cfg *api.ConfigMap, createValidation rest.ValidateObjectFunc) (*api.ConfigMap, error) + UpdateConfigMap(ctx genericapirequest.Context, cfg *api.ConfigMap, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (*api.ConfigMap, error) DeleteConfigMap(ctx genericapirequest.Context, name string) error } @@ -68,8 +68,8 @@ func (s *storage) GetConfigMap(ctx genericapirequest.Context, name string, optio return obj.(*api.ConfigMap), nil } -func (s *storage) CreateConfigMap(ctx genericapirequest.Context, cfg *api.ConfigMap) (*api.ConfigMap, error) { - obj, err := s.Create(ctx, cfg, false) +func (s *storage) CreateConfigMap(ctx genericapirequest.Context, cfg *api.ConfigMap, createValidation rest.ValidateObjectFunc) (*api.ConfigMap, error) { + obj, err := s.Create(ctx, cfg, createValidation, false) if err != nil { return nil, err } @@ -77,8 +77,8 @@ func (s *storage) CreateConfigMap(ctx genericapirequest.Context, cfg *api.Config return obj.(*api.ConfigMap), nil } -func (s *storage) UpdateConfigMap(ctx genericapirequest.Context, cfg *api.ConfigMap) (*api.ConfigMap, error) { - obj, _, err := s.Update(ctx, cfg.Name, rest.DefaultUpdatedObjectInfo(cfg)) +func (s *storage) UpdateConfigMap(ctx genericapirequest.Context, cfg *api.ConfigMap, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (*api.ConfigMap, error) { + obj, _, err := s.Update(ctx, cfg.Name, rest.DefaultUpdatedObjectInfo(cfg), createValidation, updateValidation) if err != nil { return nil, err } diff --git a/pkg/registry/core/configmap/storage/BUILD b/pkg/registry/core/configmap/storage/BUILD index a39c3941329..966388a5d23 100644 --- a/pkg/registry/core/configmap/storage/BUILD +++ b/pkg/registry/core/configmap/storage/BUILD @@ -9,16 +9,17 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/configmap/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], ) @@ -28,7 +29,7 @@ go_library( srcs = ["storage.go"], importpath = "k8s.io/kubernetes/pkg/registry/core/configmap/storage", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/core/configmap:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", diff --git a/pkg/registry/core/configmap/storage/storage.go b/pkg/registry/core/configmap/storage/storage.go index 14e3f815a73..50855e36407 100644 --- a/pkg/registry/core/configmap/storage/storage.go +++ b/pkg/registry/core/configmap/storage/storage.go @@ -21,7 +21,7 @@ import ( "k8s.io/apiserver/pkg/registry/generic" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/core/configmap" ) diff --git a/pkg/registry/core/configmap/storage/storage_test.go b/pkg/registry/core/configmap/storage/storage_test.go index 9daebb7b1c3..ae860a59b47 100644 --- a/pkg/registry/core/configmap/storage/storage_test.go +++ b/pkg/registry/core/configmap/storage/storage_test.go @@ -24,8 +24,9 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -60,7 +61,7 @@ func TestCreate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) validConfigMap := validNewConfigMap() validConfigMap.ObjectMeta = metav1.ObjectMeta{ @@ -88,7 +89,7 @@ func TestUpdate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestUpdate( // valid validNewConfigMap(), @@ -111,7 +112,7 @@ func TestDelete(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestDelete(validNewConfigMap()) } @@ -119,7 +120,7 @@ func TestGet(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestGet(validNewConfigMap()) } @@ -127,7 +128,7 @@ func TestList(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestList(validNewConfigMap()) } @@ -135,7 +136,7 @@ func TestWatch(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestWatch( validNewConfigMap(), // matching labels diff --git a/pkg/registry/core/configmap/strategy.go b/pkg/registry/core/configmap/strategy.go index b44da007423..58f57f4f9cb 100644 --- a/pkg/registry/core/configmap/strategy.go +++ b/pkg/registry/core/configmap/strategy.go @@ -22,8 +22,9 @@ import ( genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/validation" ) // strategy implements behavior for ConfigMap objects @@ -34,7 +35,7 @@ type strategy struct { // Strategy is the default logic that applies when creating and updating ConfigMap // objects via the REST API. -var Strategy = strategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = strategy{legacyscheme.Scheme, names.SimpleNameGenerator} // Strategy should implement rest.RESTCreateStrategy var _ rest.RESTCreateStrategy = Strategy diff --git a/pkg/registry/core/configmap/strategy_test.go b/pkg/registry/core/configmap/strategy_test.go index a6ec3254e19..26e931ccb71 100644 --- a/pkg/registry/core/configmap/strategy_test.go +++ b/pkg/registry/core/configmap/strategy_test.go @@ -21,7 +21,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestConfigMapStrategy(t *testing.T) { diff --git a/pkg/registry/core/endpoint/BUILD b/pkg/registry/core/endpoint/BUILD index 082dc168dc8..775b1958394 100644 --- a/pkg/registry/core/endpoint/BUILD +++ b/pkg/registry/core/endpoint/BUILD @@ -14,9 +14,10 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/core/endpoint", deps = [ - "//pkg/api:go_default_library", "//pkg/api/endpoints:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/pkg/registry/core/endpoint/registry.go b/pkg/registry/core/endpoint/registry.go index 1202cec9c16..3cb03c4f9c7 100644 --- a/pkg/registry/core/endpoint/registry.go +++ b/pkg/registry/core/endpoint/registry.go @@ -22,7 +22,7 @@ import ( "k8s.io/apimachinery/pkg/watch" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // Registry is an interface for things that know how to store endpoints. @@ -30,7 +30,7 @@ type Registry interface { ListEndpoints(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*api.EndpointsList, error) GetEndpoints(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (*api.Endpoints, error) WatchEndpoints(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) - UpdateEndpoints(ctx genericapirequest.Context, e *api.Endpoints) error + UpdateEndpoints(ctx genericapirequest.Context, e *api.Endpoints, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error DeleteEndpoints(ctx genericapirequest.Context, name string) error } @@ -65,8 +65,8 @@ func (s *storage) GetEndpoints(ctx genericapirequest.Context, name string, optio return obj.(*api.Endpoints), nil } -func (s *storage) UpdateEndpoints(ctx genericapirequest.Context, endpoints *api.Endpoints) error { - _, _, err := s.Update(ctx, endpoints.Name, rest.DefaultUpdatedObjectInfo(endpoints)) +func (s *storage) UpdateEndpoints(ctx genericapirequest.Context, endpoints *api.Endpoints, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error { + _, _, err := s.Update(ctx, endpoints.Name, rest.DefaultUpdatedObjectInfo(endpoints), createValidation, updateValidation) return err } diff --git a/pkg/registry/core/endpoint/storage/BUILD b/pkg/registry/core/endpoint/storage/BUILD index 468d93368c9..d4a4b4fcbc5 100644 --- a/pkg/registry/core/endpoint/storage/BUILD +++ b/pkg/registry/core/endpoint/storage/BUILD @@ -9,16 +9,17 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/endpoint/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], ) @@ -28,7 +29,7 @@ go_library( srcs = ["storage.go"], importpath = "k8s.io/kubernetes/pkg/registry/core/endpoint/storage", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/printers:go_default_library", "//pkg/printers/internalversion:go_default_library", "//pkg/printers/storage:go_default_library", diff --git a/pkg/registry/core/endpoint/storage/storage.go b/pkg/registry/core/endpoint/storage/storage.go index c12e3499ffd..902b7df0b55 100644 --- a/pkg/registry/core/endpoint/storage/storage.go +++ b/pkg/registry/core/endpoint/storage/storage.go @@ -21,7 +21,7 @@ import ( "k8s.io/apiserver/pkg/registry/generic" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/printers" printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" printerstorage "k8s.io/kubernetes/pkg/printers/storage" diff --git a/pkg/registry/core/endpoint/storage/storage_test.go b/pkg/registry/core/endpoint/storage/storage_test.go index fe5674b0c5f..a511424a098 100644 --- a/pkg/registry/core/endpoint/storage/storage_test.go +++ b/pkg/registry/core/endpoint/storage/storage_test.go @@ -24,8 +24,9 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -67,7 +68,7 @@ func TestCreate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) endpoints := validNewEndpoints() endpoints.ObjectMeta = metav1.ObjectMeta{} test.TestCreate( @@ -84,7 +85,7 @@ func TestUpdate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).AllowCreateOnUpdate() + test := genericregistrytest.New(t, storage.Store).AllowCreateOnUpdate() test.TestUpdate( // valid validNewEndpoints(), @@ -104,7 +105,7 @@ func TestDelete(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestDelete(validNewEndpoints()) } @@ -112,7 +113,7 @@ func TestGet(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestGet(validNewEndpoints()) } @@ -120,7 +121,7 @@ func TestList(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestList(validNewEndpoints()) } @@ -128,7 +129,7 @@ func TestWatch(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestWatch( validNewEndpoints(), // matching labels diff --git a/pkg/registry/core/endpoint/strategy.go b/pkg/registry/core/endpoint/strategy.go index b3a6d4ee78b..93204e6ca62 100644 --- a/pkg/registry/core/endpoint/strategy.go +++ b/pkg/registry/core/endpoint/strategy.go @@ -21,9 +21,10 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" endptspkg "k8s.io/kubernetes/pkg/api/endpoints" - "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/validation" ) // endpointsStrategy implements behavior for Endpoints @@ -34,7 +35,7 @@ type endpointsStrategy struct { // Strategy is the default logic that applies when creating and updating Endpoint // objects via the REST API. -var Strategy = endpointsStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = endpointsStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} // NamespaceScoped is true for endpoints. func (endpointsStrategy) NamespaceScoped() bool { diff --git a/pkg/registry/core/event/BUILD b/pkg/registry/core/event/BUILD index aa10a2230b3..9e8b3ea38d6 100644 --- a/pkg/registry/core/event/BUILD +++ b/pkg/registry/core/event/BUILD @@ -14,8 +14,9 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/core/event", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", @@ -31,12 +32,13 @@ go_library( go_test( name = "go_default_test", srcs = ["strategy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/event", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/testapi:go_default_library", "//pkg/api/testing:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", diff --git a/pkg/registry/core/event/storage/BUILD b/pkg/registry/core/event/storage/BUILD index 6f4dfec1e0a..990e1cfd2a8 100644 --- a/pkg/registry/core/event/storage/BUILD +++ b/pkg/registry/core/event/storage/BUILD @@ -9,14 +9,15 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/event/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], ) @@ -26,7 +27,7 @@ go_library( srcs = ["storage.go"], importpath = "k8s.io/kubernetes/pkg/registry/core/event/storage", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/core/event:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", diff --git a/pkg/registry/core/event/storage/storage.go b/pkg/registry/core/event/storage/storage.go index 8c3639aca91..21fa08b3b73 100644 --- a/pkg/registry/core/event/storage/storage.go +++ b/pkg/registry/core/event/storage/storage.go @@ -21,7 +21,7 @@ import ( "k8s.io/apiserver/pkg/registry/generic" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/core/event" ) diff --git a/pkg/registry/core/event/storage/storage_test.go b/pkg/registry/core/event/storage/storage_test.go index 3410c9d2df5..a7ed2cbe7ae 100644 --- a/pkg/registry/core/event/storage/storage_test.go +++ b/pkg/registry/core/event/storage/storage_test.go @@ -22,8 +22,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -58,7 +59,7 @@ func TestCreate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) event := validNewEvent(test.TestNamespace()) event.ObjectMeta = metav1.ObjectMeta{} test.TestCreate( @@ -73,7 +74,7 @@ func TestUpdate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).AllowCreateOnUpdate() + test := genericregistrytest.New(t, storage.Store).AllowCreateOnUpdate() test.TestUpdate( // valid validNewEvent(test.TestNamespace()), @@ -96,7 +97,7 @@ func TestDelete(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestDelete(validNewEvent(test.TestNamespace())) } diff --git a/pkg/registry/core/event/strategy.go b/pkg/registry/core/event/strategy.go index e70518af04b..0ca15dfa684 100644 --- a/pkg/registry/core/event/strategy.go +++ b/pkg/registry/core/event/strategy.go @@ -28,8 +28,9 @@ import ( "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/validation" ) type eventStrategy struct { @@ -39,9 +40,9 @@ type eventStrategy struct { // Strategy is the default logic that pplies when creating and updating // Event objects via the REST API. -var Strategy = eventStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = eventStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} -func (eventStrategy) DefaultGarbageCollectionPolicy() rest.GarbageCollectionPolicy { +func (eventStrategy) DefaultGarbageCollectionPolicy(ctx genericapirequest.Context) rest.GarbageCollectionPolicy { return rest.Unsupported } diff --git a/pkg/registry/core/event/strategy_test.go b/pkg/registry/core/event/strategy_test.go index be78b5aa0f8..e95df8a0f85 100644 --- a/pkg/registry/core/event/strategy_test.go +++ b/pkg/registry/core/event/strategy_test.go @@ -23,8 +23,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/util/diff" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" apitesting "k8s.io/kubernetes/pkg/api/testing" + api "k8s.io/kubernetes/pkg/apis/core" // install all api groups for testing _ "k8s.io/kubernetes/pkg/api/testapi" @@ -54,7 +55,7 @@ func TestGetAttrs(t *testing.T) { Name: "foo", Namespace: "baz", UID: "long uid string", - APIVersion: api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), + APIVersion: legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), ResourceVersion: "0", FieldPath: "", }, @@ -70,7 +71,7 @@ func TestGetAttrs(t *testing.T) { "involvedObject.name": "foo", "involvedObject.namespace": "baz", "involvedObject.uid": "long uid string", - "involvedObject.apiVersion": api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), + "involvedObject.apiVersion": legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), "involvedObject.resourceVersion": "0", "involvedObject.fieldPath": "", "reason": "ForTesting", @@ -85,7 +86,7 @@ func TestGetAttrs(t *testing.T) { func TestSelectableFieldLabelConversions(t *testing.T) { fset := EventToSelectableFields(&api.Event{}) apitesting.TestSelectableFieldLabelConversionsOfKind(t, - api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), + legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), "Event", fset, nil, diff --git a/pkg/registry/core/limitrange/BUILD b/pkg/registry/core/limitrange/BUILD index cad4a09c1e5..303471f795f 100644 --- a/pkg/registry/core/limitrange/BUILD +++ b/pkg/registry/core/limitrange/BUILD @@ -13,8 +13,9 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/core/limitrange", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", diff --git a/pkg/registry/core/limitrange/storage/BUILD b/pkg/registry/core/limitrange/storage/BUILD index 361531cca99..636d6244678 100644 --- a/pkg/registry/core/limitrange/storage/BUILD +++ b/pkg/registry/core/limitrange/storage/BUILD @@ -9,10 +9,10 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/limitrange/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -20,6 +20,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], ) @@ -29,7 +30,7 @@ go_library( srcs = ["storage.go"], importpath = "k8s.io/kubernetes/pkg/registry/core/limitrange/storage", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/core/limitrange:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", diff --git a/pkg/registry/core/limitrange/storage/storage.go b/pkg/registry/core/limitrange/storage/storage.go index b0462387e44..adb60f252db 100644 --- a/pkg/registry/core/limitrange/storage/storage.go +++ b/pkg/registry/core/limitrange/storage/storage.go @@ -21,7 +21,7 @@ import ( "k8s.io/apiserver/pkg/registry/generic" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/core/limitrange" ) diff --git a/pkg/registry/core/limitrange/storage/storage_test.go b/pkg/registry/core/limitrange/storage/storage_test.go index 403a3dfec7a..778132d4398 100644 --- a/pkg/registry/core/limitrange/storage/storage_test.go +++ b/pkg/registry/core/limitrange/storage/storage_test.go @@ -25,8 +25,9 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -69,7 +70,7 @@ func TestCreate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).GeneratesName() + test := genericregistrytest.New(t, storage.Store).GeneratesName() validLimitRange := validNewLimitRange() validLimitRange.ObjectMeta = metav1.ObjectMeta{} test.TestCreate( @@ -86,7 +87,7 @@ func TestUpdate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).AllowCreateOnUpdate() + test := genericregistrytest.New(t, storage.Store).AllowCreateOnUpdate() test.TestUpdate( // valid validNewLimitRange(), @@ -115,7 +116,7 @@ func TestDelete(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestDelete(validNewLimitRange()) } @@ -123,7 +124,7 @@ func TestGet(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestGet(validNewLimitRange()) } @@ -131,7 +132,7 @@ func TestList(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestList(validNewLimitRange()) } @@ -139,7 +140,7 @@ func TestWatch(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestWatch( validNewLimitRange(), // matching labels diff --git a/pkg/registry/core/limitrange/strategy.go b/pkg/registry/core/limitrange/strategy.go index 25b9f14135a..b4a55075b98 100644 --- a/pkg/registry/core/limitrange/strategy.go +++ b/pkg/registry/core/limitrange/strategy.go @@ -22,8 +22,9 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/validation" ) type limitrangeStrategy struct { @@ -33,7 +34,7 @@ type limitrangeStrategy struct { // Strategy is the default logic that applies when creating and updating // LimitRange objects via the REST API. -var Strategy = limitrangeStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = limitrangeStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} func (limitrangeStrategy) NamespaceScoped() bool { return true diff --git a/pkg/registry/core/namespace/BUILD b/pkg/registry/core/namespace/BUILD index 7799607f7c9..82f4203eb92 100644 --- a/pkg/registry/core/namespace/BUILD +++ b/pkg/registry/core/namespace/BUILD @@ -15,8 +15,9 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/core/namespace", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", @@ -35,12 +36,13 @@ go_library( go_test( name = "go_default_test", srcs = ["strategy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/namespace", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/testapi:go_default_library", "//pkg/api/testing:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", ], diff --git a/pkg/registry/core/namespace/registry.go b/pkg/registry/core/namespace/registry.go index 78bb723216a..cbd470d1519 100644 --- a/pkg/registry/core/namespace/registry.go +++ b/pkg/registry/core/namespace/registry.go @@ -22,7 +22,7 @@ import ( "k8s.io/apimachinery/pkg/watch" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // Registry is an interface implemented by things that know how to store Namespace objects. @@ -30,8 +30,8 @@ type Registry interface { ListNamespaces(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*api.NamespaceList, error) WatchNamespaces(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) GetNamespace(ctx genericapirequest.Context, namespaceID string, options *metav1.GetOptions) (*api.Namespace, error) - CreateNamespace(ctx genericapirequest.Context, namespace *api.Namespace) error - UpdateNamespace(ctx genericapirequest.Context, namespace *api.Namespace) error + CreateNamespace(ctx genericapirequest.Context, namespace *api.Namespace, createValidation rest.ValidateObjectFunc) error + UpdateNamespace(ctx genericapirequest.Context, namespace *api.Namespace, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error DeleteNamespace(ctx genericapirequest.Context, namespaceID string) error } @@ -66,13 +66,13 @@ func (s *storage) GetNamespace(ctx genericapirequest.Context, namespaceName stri return obj.(*api.Namespace), nil } -func (s *storage) CreateNamespace(ctx genericapirequest.Context, namespace *api.Namespace) error { - _, err := s.Create(ctx, namespace, false) +func (s *storage) CreateNamespace(ctx genericapirequest.Context, namespace *api.Namespace, createValidation rest.ValidateObjectFunc) error { + _, err := s.Create(ctx, namespace, createValidation, false) return err } -func (s *storage) UpdateNamespace(ctx genericapirequest.Context, namespace *api.Namespace) error { - _, _, err := s.Update(ctx, namespace.Name, rest.DefaultUpdatedObjectInfo(namespace)) +func (s *storage) UpdateNamespace(ctx genericapirequest.Context, namespace *api.Namespace, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error { + _, _, err := s.Update(ctx, namespace.Name, rest.DefaultUpdatedObjectInfo(namespace), createValidation, updateValidation) return err } diff --git a/pkg/registry/core/namespace/storage/BUILD b/pkg/registry/core/namespace/storage/BUILD index b21c6fa531c..1a3c6b2641c 100644 --- a/pkg/registry/core/namespace/storage/BUILD +++ b/pkg/registry/core/namespace/storage/BUILD @@ -9,16 +9,18 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/namespace/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], ) @@ -28,7 +30,7 @@ go_library( srcs = ["storage.go"], importpath = "k8s.io/kubernetes/pkg/registry/core/namespace/storage", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/core/namespace:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", diff --git a/pkg/registry/core/namespace/storage/storage.go b/pkg/registry/core/namespace/storage/storage.go index 05c6e180151..faa099a9510 100644 --- a/pkg/registry/core/namespace/storage/storage.go +++ b/pkg/registry/core/namespace/storage/storage.go @@ -30,7 +30,7 @@ import ( "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage" storageerr "k8s.io/apiserver/pkg/storage/errors" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/core/namespace" ) @@ -89,12 +89,12 @@ func (r *REST) List(ctx genericapirequest.Context, options *metainternalversion. return r.store.List(ctx, options) } -func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { - return r.store.Create(ctx, obj, includeUninitialized) +func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) { + return r.store.Create(ctx, obj, createValidation, includeUninitialized) } -func (r *REST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - return r.store.Update(ctx, name, objInfo) +func (r *REST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation) } func (r *REST) Get(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { @@ -219,8 +219,8 @@ func (r *StatusREST) Get(ctx genericapirequest.Context, name string, options *me } // Update alters the status subset of an object. -func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - return r.store.Update(ctx, name, objInfo) +func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation) } func (r *FinalizeREST) New() runtime.Object { @@ -228,6 +228,6 @@ func (r *FinalizeREST) New() runtime.Object { } // Update alters the status finalizers subset of an object. -func (r *FinalizeREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - return r.store.Update(ctx, name, objInfo) +func (r *FinalizeREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation) } diff --git a/pkg/registry/core/namespace/storage/storage_test.go b/pkg/registry/core/namespace/storage/storage_test.go index fdc57efe2b8..d18c106abf9 100644 --- a/pkg/registry/core/namespace/storage/storage_test.go +++ b/pkg/registry/core/namespace/storage/storage_test.go @@ -24,8 +24,10 @@ import ( "k8s.io/apimachinery/pkg/labels" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" + "k8s.io/apiserver/pkg/registry/rest" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -48,7 +50,7 @@ func TestCreate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.store.DestroyFunc() - test := registrytest.New(t, storage.store).ClusterScope() + test := genericregistrytest.New(t, storage.store).ClusterScope() namespace := validNewNamespace() namespace.ObjectMeta = metav1.ObjectMeta{GenerateName: "foo"} test.TestCreate( @@ -67,7 +69,7 @@ func TestCreateSetsFields(t *testing.T) { defer storage.store.DestroyFunc() namespace := validNewNamespace() ctx := genericapirequest.NewContext() - _, err := storage.Create(ctx, namespace, false) + _, err := storage.Create(ctx, namespace, rest.ValidateAllObjectFunc, false) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -92,7 +94,7 @@ func TestDelete(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.store.DestroyFunc() - test := registrytest.New(t, storage.store).ClusterScope().ReturnDeletedObject() + test := genericregistrytest.New(t, storage.store).ClusterScope().ReturnDeletedObject() test.TestDelete(validNewNamespace()) } @@ -100,7 +102,7 @@ func TestGet(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.store.DestroyFunc() - test := registrytest.New(t, storage.store).ClusterScope() + test := genericregistrytest.New(t, storage.store).ClusterScope() test.TestGet(validNewNamespace()) } @@ -108,7 +110,7 @@ func TestList(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.store.DestroyFunc() - test := registrytest.New(t, storage.store).ClusterScope() + test := genericregistrytest.New(t, storage.store).ClusterScope() test.TestList(validNewNamespace()) } @@ -116,7 +118,7 @@ func TestWatch(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.store.DestroyFunc() - test := registrytest.New(t, storage.store).ClusterScope() + test := genericregistrytest.New(t, storage.store).ClusterScope() test.TestWatch( validNewNamespace(), // matching labels diff --git a/pkg/registry/core/namespace/strategy.go b/pkg/registry/core/namespace/strategy.go index e0d05b18f8e..53a139e5223 100644 --- a/pkg/registry/core/namespace/strategy.go +++ b/pkg/registry/core/namespace/strategy.go @@ -27,8 +27,9 @@ import ( "k8s.io/apiserver/pkg/registry/generic" apistorage "k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/validation" ) // namespaceStrategy implements behavior for Namespaces @@ -39,7 +40,7 @@ type namespaceStrategy struct { // Strategy is the default logic that applies when creating and updating Namespace // objects via the REST API. -var Strategy = namespaceStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = namespaceStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} // NamespaceScoped is false for namespaces. func (namespaceStrategy) NamespaceScoped() bool { diff --git a/pkg/registry/core/namespace/strategy_test.go b/pkg/registry/core/namespace/strategy_test.go index 8e3dbc3e800..9ebac313385 100644 --- a/pkg/registry/core/namespace/strategy_test.go +++ b/pkg/registry/core/namespace/strategy_test.go @@ -21,8 +21,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" apitesting "k8s.io/kubernetes/pkg/api/testing" + api "k8s.io/kubernetes/pkg/apis/core" // install all api groups for testing _ "k8s.io/kubernetes/pkg/api/testapi" @@ -138,7 +139,7 @@ func TestNamespaceFinalizeStrategy(t *testing.T) { func TestSelectableFieldLabelConversions(t *testing.T) { apitesting.TestSelectableFieldLabelConversionsOfKind(t, - api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), + legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), "Namespace", NamespaceToSelectableFields(&api.Namespace{}), map[string]string{"name": "metadata.name"}, diff --git a/pkg/registry/core/node/BUILD b/pkg/registry/core/node/BUILD index 5c932442d3e..d82f0c41a47 100644 --- a/pkg/registry/core/node/BUILD +++ b/pkg/registry/core/node/BUILD @@ -15,8 +15,9 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/core/node", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//pkg/features:go_default_library", "//pkg/kubelet/client:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", @@ -41,12 +42,13 @@ go_library( go_test( name = "go_default_test", srcs = ["strategy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/node", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/testapi:go_default_library", "//pkg/api/testing:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", ], diff --git a/pkg/registry/core/node/registry.go b/pkg/registry/core/node/registry.go index 165cb6476c5..e4476ded4b9 100644 --- a/pkg/registry/core/node/registry.go +++ b/pkg/registry/core/node/registry.go @@ -22,14 +22,14 @@ import ( "k8s.io/apimachinery/pkg/watch" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // Registry is an interface for things that know how to store node. type Registry interface { ListNodes(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*api.NodeList, error) - CreateNode(ctx genericapirequest.Context, node *api.Node) error - UpdateNode(ctx genericapirequest.Context, node *api.Node) error + CreateNode(ctx genericapirequest.Context, node *api.Node, createValidation rest.ValidateObjectFunc) error + UpdateNode(ctx genericapirequest.Context, node *api.Node, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error GetNode(ctx genericapirequest.Context, nodeID string, options *metav1.GetOptions) (*api.Node, error) DeleteNode(ctx genericapirequest.Context, nodeID string) error WatchNodes(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) @@ -55,13 +55,13 @@ func (s *storage) ListNodes(ctx genericapirequest.Context, options *metainternal return obj.(*api.NodeList), nil } -func (s *storage) CreateNode(ctx genericapirequest.Context, node *api.Node) error { - _, err := s.Create(ctx, node, false) +func (s *storage) CreateNode(ctx genericapirequest.Context, node *api.Node, createValidation rest.ValidateObjectFunc) error { + _, err := s.Create(ctx, node, createValidation, false) return err } -func (s *storage) UpdateNode(ctx genericapirequest.Context, node *api.Node) error { - _, _, err := s.Update(ctx, node.Name, rest.DefaultUpdatedObjectInfo(node)) +func (s *storage) UpdateNode(ctx genericapirequest.Context, node *api.Node, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error { + _, _, err := s.Update(ctx, node.Name, rest.DefaultUpdatedObjectInfo(node), createValidation, updateValidation) return err } diff --git a/pkg/registry/core/node/rest/BUILD b/pkg/registry/core/node/rest/BUILD index 94d3f919987..1425ee92026 100644 --- a/pkg/registry/core/node/rest/BUILD +++ b/pkg/registry/core/node/rest/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["proxy.go"], importpath = "k8s.io/kubernetes/pkg/registry/core/node/rest", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/capabilities:go_default_library", "//pkg/kubelet/client:go_default_library", "//pkg/registry/core/node:go_default_library", diff --git a/pkg/registry/core/node/rest/proxy.go b/pkg/registry/core/node/rest/proxy.go index 61356d97b85..b314617f42e 100644 --- a/pkg/registry/core/node/rest/proxy.go +++ b/pkg/registry/core/node/rest/proxy.go @@ -27,7 +27,7 @@ import ( genericapirequest "k8s.io/apiserver/pkg/endpoints/request" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/capabilities" "k8s.io/kubernetes/pkg/kubelet/client" "k8s.io/kubernetes/pkg/registry/core/node" diff --git a/pkg/registry/core/node/storage/BUILD b/pkg/registry/core/node/storage/BUILD index 8c521581f4f..d444a05f464 100644 --- a/pkg/registry/core/node/storage/BUILD +++ b/pkg/registry/core/node/storage/BUILD @@ -9,10 +9,10 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/node/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/kubelet/client:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", @@ -21,6 +21,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], ) @@ -30,8 +31,8 @@ go_library( srcs = ["storage.go"], importpath = "k8s.io/kubernetes/pkg/registry/core/node/storage", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/v1:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/v1:go_default_library", "//pkg/kubelet/client:go_default_library", "//pkg/printers:go_default_library", "//pkg/printers/internalversion:go_default_library", diff --git a/pkg/registry/core/node/storage/storage.go b/pkg/registry/core/node/storage/storage.go index fcf5d237a61..a61e575885b 100644 --- a/pkg/registry/core/node/storage/storage.go +++ b/pkg/registry/core/node/storage/storage.go @@ -28,8 +28,8 @@ import ( "k8s.io/apiserver/pkg/registry/generic" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" - k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" + api "k8s.io/kubernetes/pkg/apis/core" + k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" "k8s.io/kubernetes/pkg/kubelet/client" "k8s.io/kubernetes/pkg/printers" printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" @@ -68,8 +68,8 @@ func (r *StatusREST) Get(ctx genericapirequest.Context, name string, options *me } // Update alters the status subset of an object. -func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - return r.store.Update(ctx, name, objInfo) +func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation) } // NewStorage returns a NodeStorage object that will work against nodes. @@ -112,7 +112,7 @@ func NewStorage(optsGetter generic.RESTOptionsGetter, kubeletClientConfig client } // TODO: Remove the conversion. Consider only return the NodeAddresses externalNode := &v1.Node{} - err = k8s_api_v1.Convert_api_Node_To_v1_Node(node, externalNode, nil) + err = k8s_api_v1.Convert_core_Node_To_v1_Node(node, externalNode, nil) if err != nil { return nil, fmt.Errorf("failed to convert to v1.Node: %v", err) } diff --git a/pkg/registry/core/node/storage/storage_test.go b/pkg/registry/core/node/storage/storage_test.go index e9ee3da6d29..1c0603d357c 100644 --- a/pkg/registry/core/node/storage/storage_test.go +++ b/pkg/registry/core/node/storage/storage_test.go @@ -25,8 +25,9 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" kubeletclient "k8s.io/kubernetes/pkg/kubelet/client" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -70,7 +71,7 @@ func TestCreate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() node := validNewNode() node.ObjectMeta = metav1.ObjectMeta{GenerateName: "foo"} test.TestCreate( @@ -87,7 +88,7 @@ func TestUpdate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() test.TestUpdate( // valid validNewNode(), @@ -104,7 +105,7 @@ func TestDelete(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() test.TestDelete(validNewNode()) } @@ -112,7 +113,7 @@ func TestGet(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() test.TestGet(validNewNode()) } @@ -120,7 +121,7 @@ func TestList(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() test.TestList(validNewNode()) } @@ -128,7 +129,7 @@ func TestWatch(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() test.TestWatch( validNewNode(), // matching labels diff --git a/pkg/registry/core/node/strategy.go b/pkg/registry/core/node/strategy.go index 9b959bb7ba6..4653b3f445d 100644 --- a/pkg/registry/core/node/strategy.go +++ b/pkg/registry/core/node/strategy.go @@ -35,8 +35,9 @@ import ( pkgstorage "k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage/names" utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/validation" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/client" ) @@ -49,7 +50,7 @@ type nodeStrategy struct { // Nodes is the default logic that applies when creating and updating Node // objects. -var Strategy = nodeStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = nodeStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} // NamespaceScoped is false for nodes. func (nodeStrategy) NamespaceScoped() bool { diff --git a/pkg/registry/core/node/strategy_test.go b/pkg/registry/core/node/strategy_test.go index ec740efdc2f..c32da10bb66 100644 --- a/pkg/registry/core/node/strategy_test.go +++ b/pkg/registry/core/node/strategy_test.go @@ -21,8 +21,9 @@ import ( "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" apitesting "k8s.io/kubernetes/pkg/api/testing" + api "k8s.io/kubernetes/pkg/apis/core" // install all api groups for testing _ "k8s.io/kubernetes/pkg/api/testapi" @@ -51,7 +52,7 @@ func TestMatchNode(t *testing.T) { func TestSelectableFieldLabelConversions(t *testing.T) { apitesting.TestSelectableFieldLabelConversionsOfKind(t, - api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), + legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), "Node", NodeToSelectableFields(&api.Node{}), nil, diff --git a/pkg/registry/core/persistentvolume/BUILD b/pkg/registry/core/persistentvolume/BUILD index 9d518e0c039..6f43c442fac 100644 --- a/pkg/registry/core/persistentvolume/BUILD +++ b/pkg/registry/core/persistentvolume/BUILD @@ -14,8 +14,10 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/core/persistentvolume", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/api/persistentvolume:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//pkg/volume/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", @@ -31,12 +33,13 @@ go_library( go_test( name = "go_default_test", srcs = ["strategy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/persistentvolume", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/testapi:go_default_library", "//pkg/api/testing:go_default_library", + "//pkg/apis/core:go_default_library", ], ) diff --git a/pkg/registry/core/persistentvolume/storage/BUILD b/pkg/registry/core/persistentvolume/storage/BUILD index d7e70a3b37e..7c2b907ee65 100644 --- a/pkg/registry/core/persistentvolume/storage/BUILD +++ b/pkg/registry/core/persistentvolume/storage/BUILD @@ -9,10 +9,10 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/persistentvolume/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", @@ -23,6 +23,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], @@ -33,7 +34,7 @@ go_library( srcs = ["storage.go"], importpath = "k8s.io/kubernetes/pkg/registry/core/persistentvolume/storage", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/core/persistentvolume:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/pkg/registry/core/persistentvolume/storage/storage.go b/pkg/registry/core/persistentvolume/storage/storage.go index 84fd2577910..b8f7948f9b6 100644 --- a/pkg/registry/core/persistentvolume/storage/storage.go +++ b/pkg/registry/core/persistentvolume/storage/storage.go @@ -23,7 +23,7 @@ import ( "k8s.io/apiserver/pkg/registry/generic" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/core/persistentvolume" ) @@ -78,6 +78,6 @@ func (r *StatusREST) Get(ctx genericapirequest.Context, name string, options *me } // Update alters the status subset of an object. -func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - return r.store.Update(ctx, name, objInfo) +func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation) } diff --git a/pkg/registry/core/persistentvolume/storage/storage_test.go b/pkg/registry/core/persistentvolume/storage/storage_test.go index bc98bbe09ba..95c90eede9c 100644 --- a/pkg/registry/core/persistentvolume/storage/storage_test.go +++ b/pkg/registry/core/persistentvolume/storage/storage_test.go @@ -28,9 +28,10 @@ import ( "k8s.io/apimachinery/pkg/util/diff" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" "k8s.io/apiserver/pkg/registry/rest" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -85,7 +86,7 @@ func TestCreate(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() pv := validNewPersistentVolume("foo") pv.ObjectMeta = metav1.ObjectMeta{GenerateName: "foo"} test.TestCreate( @@ -102,7 +103,7 @@ func TestUpdate(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() test.TestUpdate( // valid validNewPersistentVolume("foo"), @@ -121,7 +122,7 @@ func TestDelete(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope().ReturnDeletedObject() + test := genericregistrytest.New(t, storage.Store).ClusterScope().ReturnDeletedObject() test.TestDelete(validNewPersistentVolume("foo")) } @@ -129,7 +130,7 @@ func TestGet(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() test.TestGet(validNewPersistentVolume("foo")) } @@ -137,7 +138,7 @@ func TestList(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() test.TestList(validNewPersistentVolume("foo")) } @@ -145,7 +146,7 @@ func TestWatch(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() test.TestWatch( validNewPersistentVolume("foo"), // matching labels @@ -187,7 +188,7 @@ func TestUpdateStatus(t *testing.T) { }, } - _, _, err = statusStorage.Update(ctx, pvIn.Name, rest.DefaultUpdatedObjectInfo(pvIn)) + _, _, err = statusStorage.Update(ctx, pvIn.Name, rest.DefaultUpdatedObjectInfo(pvIn), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc) if err != nil { t.Fatalf("Unexpected error: %v", err) } diff --git a/pkg/registry/core/persistentvolume/strategy.go b/pkg/registry/core/persistentvolume/strategy.go index 33dfd905f75..477bca2bad5 100644 --- a/pkg/registry/core/persistentvolume/strategy.go +++ b/pkg/registry/core/persistentvolume/strategy.go @@ -27,8 +27,10 @@ import ( "k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/api/legacyscheme" + pvutil "k8s.io/kubernetes/pkg/api/persistentvolume" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/validation" volumevalidation "k8s.io/kubernetes/pkg/volume/validation" ) @@ -40,7 +42,7 @@ type persistentvolumeStrategy struct { // Strategy is the default logic that applies when creating and updating PersistentVolume // objects via the REST API. -var Strategy = persistentvolumeStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = persistentvolumeStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} func (persistentvolumeStrategy) NamespaceScoped() bool { return false @@ -50,6 +52,8 @@ func (persistentvolumeStrategy) NamespaceScoped() bool { func (persistentvolumeStrategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) { pv := obj.(*api.PersistentVolume) pv.Status = api.PersistentVolumeStatus{} + + pvutil.DropDisabledAlphaFields(&pv.Spec) } func (persistentvolumeStrategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList { @@ -71,6 +75,9 @@ func (persistentvolumeStrategy) PrepareForUpdate(ctx genericapirequest.Context, newPv := obj.(*api.PersistentVolume) oldPv := old.(*api.PersistentVolume) newPv.Status = oldPv.Status + + pvutil.DropDisabledAlphaFields(&newPv.Spec) + pvutil.DropDisabledAlphaFields(&oldPv.Spec) } func (persistentvolumeStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList { diff --git a/pkg/registry/core/persistentvolume/strategy_test.go b/pkg/registry/core/persistentvolume/strategy_test.go index 1bec01dac3c..3c5fbb7d9e3 100644 --- a/pkg/registry/core/persistentvolume/strategy_test.go +++ b/pkg/registry/core/persistentvolume/strategy_test.go @@ -19,8 +19,9 @@ package persistentvolume import ( "testing" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" apitesting "k8s.io/kubernetes/pkg/api/testing" + api "k8s.io/kubernetes/pkg/apis/core" // install all api groups for testing _ "k8s.io/kubernetes/pkg/api/testapi" @@ -28,7 +29,7 @@ import ( func TestSelectableFieldLabelConversions(t *testing.T) { apitesting.TestSelectableFieldLabelConversionsOfKind(t, - api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), + legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), "PersistentVolume", PersistentVolumeToSelectableFields(&api.PersistentVolume{}), map[string]string{"name": "metadata.name"}, diff --git a/pkg/registry/core/persistentvolumeclaim/BUILD b/pkg/registry/core/persistentvolumeclaim/BUILD index a8d8e1e3822..d4eba81e137 100644 --- a/pkg/registry/core/persistentvolumeclaim/BUILD +++ b/pkg/registry/core/persistentvolumeclaim/BUILD @@ -14,8 +14,10 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/core/persistentvolumeclaim", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/api/persistentvolumeclaim:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", @@ -30,12 +32,13 @@ go_library( go_test( name = "go_default_test", srcs = ["strategy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/persistentvolumeclaim", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/testapi:go_default_library", "//pkg/api/testing:go_default_library", + "//pkg/apis/core:go_default_library", ], ) diff --git a/pkg/registry/core/persistentvolumeclaim/storage/BUILD b/pkg/registry/core/persistentvolumeclaim/storage/BUILD index 45a0f4745cc..f8eb094f446 100644 --- a/pkg/registry/core/persistentvolumeclaim/storage/BUILD +++ b/pkg/registry/core/persistentvolumeclaim/storage/BUILD @@ -9,10 +9,10 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/persistentvolumeclaim/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", @@ -23,6 +23,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], @@ -33,7 +34,7 @@ go_library( srcs = ["storage.go"], importpath = "k8s.io/kubernetes/pkg/registry/core/persistentvolumeclaim/storage", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/core/persistentvolumeclaim:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/pkg/registry/core/persistentvolumeclaim/storage/storage.go b/pkg/registry/core/persistentvolumeclaim/storage/storage.go index 544ac1cf694..c361d59a47a 100644 --- a/pkg/registry/core/persistentvolumeclaim/storage/storage.go +++ b/pkg/registry/core/persistentvolumeclaim/storage/storage.go @@ -23,7 +23,7 @@ import ( "k8s.io/apiserver/pkg/registry/generic" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/core/persistentvolumeclaim" ) @@ -78,6 +78,6 @@ func (r *StatusREST) Get(ctx genericapirequest.Context, name string, options *me } // Update alters the status subset of an object. -func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - return r.store.Update(ctx, name, objInfo) +func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation) } diff --git a/pkg/registry/core/persistentvolumeclaim/storage/storage_test.go b/pkg/registry/core/persistentvolumeclaim/storage/storage_test.go index 916042829f4..a64df318bb3 100644 --- a/pkg/registry/core/persistentvolumeclaim/storage/storage_test.go +++ b/pkg/registry/core/persistentvolumeclaim/storage/storage_test.go @@ -28,9 +28,10 @@ import ( "k8s.io/apimachinery/pkg/util/diff" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" "k8s.io/apiserver/pkg/registry/rest" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -71,7 +72,7 @@ func TestCreate(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) pv := validNewPersistentVolumeClaim("foo", metav1.NamespaceDefault) pv.ObjectMeta = metav1.ObjectMeta{} test.TestCreate( @@ -88,7 +89,7 @@ func TestUpdate(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestUpdate( // valid validNewPersistentVolumeClaim("foo", metav1.NamespaceDefault), @@ -105,7 +106,7 @@ func TestDelete(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ReturnDeletedObject() + test := genericregistrytest.New(t, storage.Store).ReturnDeletedObject() test.TestDelete(validNewPersistentVolumeClaim("foo", metav1.NamespaceDefault)) } @@ -113,7 +114,7 @@ func TestGet(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestGet(validNewPersistentVolumeClaim("foo", metav1.NamespaceDefault)) } @@ -121,7 +122,7 @@ func TestList(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestList(validNewPersistentVolumeClaim("foo", metav1.NamespaceDefault)) } @@ -129,7 +130,7 @@ func TestWatch(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestWatch( validNewPersistentVolumeClaim("foo", metav1.NamespaceDefault), // matching labels @@ -178,7 +179,7 @@ func TestUpdateStatus(t *testing.T) { }, } - _, _, err = statusStorage.Update(ctx, pvc.Name, rest.DefaultUpdatedObjectInfo(pvc)) + _, _, err = statusStorage.Update(ctx, pvc.Name, rest.DefaultUpdatedObjectInfo(pvc), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc) if err != nil { t.Fatalf("Unexpected error: %v", err) } diff --git a/pkg/registry/core/persistentvolumeclaim/strategy.go b/pkg/registry/core/persistentvolumeclaim/strategy.go index c653d505c8e..1a63e1faea8 100644 --- a/pkg/registry/core/persistentvolumeclaim/strategy.go +++ b/pkg/registry/core/persistentvolumeclaim/strategy.go @@ -27,8 +27,10 @@ import ( "k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/api/legacyscheme" + pvcutil "k8s.io/kubernetes/pkg/api/persistentvolumeclaim" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/validation" ) // persistentvolumeclaimStrategy implements behavior for PersistentVolumeClaim objects @@ -39,7 +41,7 @@ type persistentvolumeclaimStrategy struct { // Strategy is the default logic that applies when creating and updating PersistentVolumeClaim // objects via the REST API. -var Strategy = persistentvolumeclaimStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = persistentvolumeclaimStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} func (persistentvolumeclaimStrategy) NamespaceScoped() bool { return true @@ -47,8 +49,10 @@ func (persistentvolumeclaimStrategy) NamespaceScoped() bool { // PrepareForCreate clears the Status field which is not allowed to be set by end users on creation. func (persistentvolumeclaimStrategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) { - pv := obj.(*api.PersistentVolumeClaim) - pv.Status = api.PersistentVolumeClaimStatus{} + pvc := obj.(*api.PersistentVolumeClaim) + pvc.Status = api.PersistentVolumeClaimStatus{} + + pvcutil.DropDisabledAlphaFields(&pvc.Spec) } func (persistentvolumeclaimStrategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList { @@ -69,6 +73,9 @@ func (persistentvolumeclaimStrategy) PrepareForUpdate(ctx genericapirequest.Cont newPvc := obj.(*api.PersistentVolumeClaim) oldPvc := old.(*api.PersistentVolumeClaim) newPvc.Status = oldPvc.Status + + pvcutil.DropDisabledAlphaFields(&newPvc.Spec) + pvcutil.DropDisabledAlphaFields(&oldPvc.Spec) } func (persistentvolumeclaimStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList { diff --git a/pkg/registry/core/persistentvolumeclaim/strategy_test.go b/pkg/registry/core/persistentvolumeclaim/strategy_test.go index 90824c4db2a..ae0ef997084 100644 --- a/pkg/registry/core/persistentvolumeclaim/strategy_test.go +++ b/pkg/registry/core/persistentvolumeclaim/strategy_test.go @@ -19,8 +19,9 @@ package persistentvolumeclaim import ( "testing" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" apitesting "k8s.io/kubernetes/pkg/api/testing" + api "k8s.io/kubernetes/pkg/apis/core" // install all api groups for testing _ "k8s.io/kubernetes/pkg/api/testapi" @@ -28,7 +29,7 @@ import ( func TestSelectableFieldLabelConversions(t *testing.T) { apitesting.TestSelectableFieldLabelConversionsOfKind(t, - api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), + legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), "PersistentVolumeClaim", PersistentVolumeClaimToSelectableFields(&api.PersistentVolumeClaim{}), map[string]string{"name": "metadata.name"}, diff --git a/pkg/registry/core/pod/BUILD b/pkg/registry/core/pod/BUILD index 5104ecf4a3a..e124ee08fcc 100644 --- a/pkg/registry/core/pod/BUILD +++ b/pkg/registry/core/pod/BUILD @@ -14,10 +14,11 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/core/pod", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper/qos:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/pod:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper/qos:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//pkg/kubelet/client:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", @@ -41,12 +42,13 @@ go_library( go_test( name = "go_default_test", srcs = ["strategy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/pod", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/testapi:go_default_library", "//pkg/api/testing:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/kubelet/client:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", diff --git a/pkg/registry/core/pod/rest/BUILD b/pkg/registry/core/pod/rest/BUILD index 71a13dc19c7..dcdc7a925b1 100644 --- a/pkg/registry/core/pod/rest/BUILD +++ b/pkg/registry/core/pod/rest/BUILD @@ -14,8 +14,8 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/core/pod/rest", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//pkg/capabilities:go_default_library", "//pkg/kubelet/client:go_default_library", "//pkg/registry/core/pod:go_default_library", @@ -35,10 +35,10 @@ go_library( go_test( name = "go_default_test", srcs = ["log_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/pod/rest", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", diff --git a/pkg/registry/core/pod/rest/log.go b/pkg/registry/core/pod/rest/log.go index cb637257a44..e33f2a7fe93 100644 --- a/pkg/registry/core/pod/rest/log.go +++ b/pkg/registry/core/pod/rest/log.go @@ -25,8 +25,8 @@ import ( genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" genericrest "k8s.io/apiserver/pkg/registry/generic/rest" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/validation" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/validation" "k8s.io/kubernetes/pkg/kubelet/client" "k8s.io/kubernetes/pkg/registry/core/pod" ) diff --git a/pkg/registry/core/pod/rest/log_test.go b/pkg/registry/core/pod/rest/log_test.go index e81197216b8..2b58ee25877 100644 --- a/pkg/registry/core/pod/rest/log_test.go +++ b/pkg/registry/core/pod/rest/log_test.go @@ -23,7 +23,7 @@ import ( genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/generic" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/registrytest" ) diff --git a/pkg/registry/core/pod/rest/subresources.go b/pkg/registry/core/pod/rest/subresources.go index b152688a101..2df82797683 100644 --- a/pkg/registry/core/pod/rest/subresources.go +++ b/pkg/registry/core/pod/rest/subresources.go @@ -29,7 +29,7 @@ import ( genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/rest" utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/capabilities" "k8s.io/kubernetes/pkg/kubelet/client" "k8s.io/kubernetes/pkg/registry/core/pod" diff --git a/pkg/registry/core/pod/storage/BUILD b/pkg/registry/core/pod/storage/BUILD index 79246a4d200..a005dc937f0 100644 --- a/pkg/registry/core/pod/storage/BUILD +++ b/pkg/registry/core/pod/storage/BUILD @@ -9,10 +9,10 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/pod/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/registrytest:go_default_library", "//pkg/securitycontext:go_default_library", "//vendor/golang.org/x/net/context:go_default_library", @@ -28,6 +28,7 @@ go_test( "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", "//vendor/k8s.io/apiserver/pkg/features:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/errors:go_default_library", @@ -45,9 +46,9 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/core/pod/storage", deps = [ - "//pkg/api:go_default_library", "//pkg/api/pod:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//pkg/apis/policy:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/policy/internalversion:go_default_library", "//pkg/kubelet/client:go_default_library", @@ -60,6 +61,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", diff --git a/pkg/registry/core/pod/storage/eviction.go b/pkg/registry/core/pod/storage/eviction.go index 36b97350ffc..ff03d96dd5e 100644 --- a/pkg/registry/core/pod/storage/eviction.go +++ b/pkg/registry/core/pod/storage/eviction.go @@ -24,12 +24,13 @@ import ( 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/wait" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/client-go/util/retry" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/policy" policyclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion" ) @@ -64,6 +65,12 @@ type EvictionREST struct { } var _ = rest.Creater(&EvictionREST{}) +var _ = rest.GroupVersionKindProvider(&EvictionREST{}) + +// GroupVersionKind specifies a particular GroupVersionKind to discovery +func (r *EvictionREST) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind { + return schema.GroupVersionKind{Group: "policy", Version: "v1beta1", Kind: "Eviction"} +} // New creates a new eviction resource func (r *EvictionREST) New() runtime.Object { @@ -71,7 +78,7 @@ func (r *EvictionREST) New() runtime.Object { } // Create attempts to create a new eviction. That is, it tries to evict a pod. -func (r *EvictionREST) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { +func (r *EvictionREST) Create(ctx genericapirequest.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) { eviction := obj.(*policy.Eviction) obj, err := r.store.Get(ctx, eviction.Name, &metav1.GetOptions{}) diff --git a/pkg/registry/core/pod/storage/storage.go b/pkg/registry/core/pod/storage/storage.go index 1416e50b3ec..129fd1a43d0 100644 --- a/pkg/registry/core/pod/storage/storage.go +++ b/pkg/registry/core/pod/storage/storage.go @@ -30,9 +30,9 @@ import ( "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage" storeerr "k8s.io/apiserver/pkg/storage/errors" - "k8s.io/kubernetes/pkg/api" podutil "k8s.io/kubernetes/pkg/api/pod" - "k8s.io/kubernetes/pkg/api/validation" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/validation" policyclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion" "k8s.io/kubernetes/pkg/kubelet/client" "k8s.io/kubernetes/pkg/printers" @@ -135,7 +135,7 @@ func (r *BindingREST) New() runtime.Object { var _ = rest.Creater(&BindingREST{}) // Create ensures a pod is bound to a specific host. -func (r *BindingREST) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (out runtime.Object, err error) { +func (r *BindingREST) Create(ctx genericapirequest.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (out runtime.Object, err error) { binding := obj.(*api.Binding) // TODO: move me to a binding strategy @@ -212,6 +212,6 @@ func (r *StatusREST) Get(ctx genericapirequest.Context, name string, options *me } // Update alters the status subset of an object. -func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - return r.store.Update(ctx, name, objInfo) +func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation) } diff --git a/pkg/registry/core/pod/storage/storage_test.go b/pkg/registry/core/pod/storage/storage_test.go index e76275c3191..1770b1802e1 100644 --- a/pkg/registry/core/pod/storage/storage_test.go +++ b/pkg/registry/core/pod/storage/storage_test.go @@ -34,13 +34,14 @@ import ( genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/features" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage" storeerr "k8s.io/apiserver/pkg/storage/errors" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/registrytest" "k8s.io/kubernetes/pkg/securitycontext" ) @@ -98,7 +99,7 @@ func TestCreate(t *testing.T) { storage, _, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) pod := validNewPod() pod.ObjectMeta = metav1.ObjectMeta{} // Make an invalid pod with an an incorrect label. @@ -125,7 +126,7 @@ func TestUpdate(t *testing.T) { storage, _, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestUpdate( // valid validNewPod(), @@ -142,7 +143,7 @@ func TestDelete(t *testing.T) { storage, _, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ReturnDeletedObject() + test := genericregistrytest.New(t, storage.Store).ReturnDeletedObject() test.TestDelete(validNewPod()) scheduledPod := validNewPod() @@ -188,7 +189,7 @@ func TestIgnoreDeleteNotFound(t *testing.T) { } // create pod - _, err = registry.Create(testContext, pod, false) + _, err = registry.Create(testContext, pod, rest.ValidateAllObjectFunc, false) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -225,7 +226,7 @@ func TestCreateSetsFields(t *testing.T) { defer server.Terminate(t) defer storage.Store.DestroyFunc() pod := validNewPod() - _, err := storage.Create(genericapirequest.NewDefaultContext(), pod, false) + _, err := storage.Create(genericapirequest.NewDefaultContext(), pod, rest.ValidateAllObjectFunc, false) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -366,7 +367,7 @@ func TestGet(t *testing.T) { storage, _, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestGet(validNewPod()) } @@ -374,7 +375,7 @@ func TestList(t *testing.T) { storage, _, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestList(validNewPod()) } @@ -382,7 +383,7 @@ func TestWatch(t *testing.T) { storage, _, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestWatch( validNewPod(), // matching labels @@ -489,7 +490,7 @@ func TestEtcdCreate(t *testing.T) { defer server.Terminate(t) defer storage.Store.DestroyFunc() ctx := genericapirequest.NewDefaultContext() - _, err := storage.Create(ctx, validNewPod(), false) + _, err := storage.Create(ctx, validNewPod(), rest.ValidateAllObjectFunc, false) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -498,7 +499,7 @@ func TestEtcdCreate(t *testing.T) { _, err = bindingStorage.Create(ctx, &api.Binding{ ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "foo"}, Target: api.ObjectReference{Name: "machine"}, - }, false) + }, rest.ValidateAllObjectFunc, false) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -524,7 +525,7 @@ func TestEtcdCreateBindingNoPod(t *testing.T) { _, err := bindingStorage.Create(ctx, &api.Binding{ ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "foo"}, Target: api.ObjectReference{Name: "machine"}, - }, false) + }, rest.ValidateAllObjectFunc, false) if err == nil { t.Fatalf("Expected not-found-error but got nothing") } @@ -547,7 +548,7 @@ func TestEtcdCreateFailsWithoutNamespace(t *testing.T) { defer storage.Store.DestroyFunc() pod := validNewPod() pod.Namespace = "" - _, err := storage.Create(genericapirequest.NewContext(), pod, false) + _, err := storage.Create(genericapirequest.NewContext(), pod, rest.ValidateAllObjectFunc, false) // Accept "namespace" or "Namespace". if err == nil || !strings.Contains(err.Error(), "amespace") { t.Fatalf("expected error that namespace was missing from context, got: %v", err) @@ -559,7 +560,7 @@ func TestEtcdCreateWithContainersNotFound(t *testing.T) { defer server.Terminate(t) defer storage.Store.DestroyFunc() ctx := genericapirequest.NewDefaultContext() - _, err := storage.Create(ctx, validNewPod(), false) + _, err := storage.Create(ctx, validNewPod(), rest.ValidateAllObjectFunc, false) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -572,7 +573,7 @@ func TestEtcdCreateWithContainersNotFound(t *testing.T) { Annotations: map[string]string{"label1": "value1"}, }, Target: api.ObjectReference{Name: "machine"}, - }, false) + }, rest.ValidateAllObjectFunc, false) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -594,7 +595,7 @@ func TestEtcdCreateWithConflict(t *testing.T) { defer storage.Store.DestroyFunc() ctx := genericapirequest.NewDefaultContext() - _, err := storage.Create(ctx, validNewPod(), false) + _, err := storage.Create(ctx, validNewPod(), rest.ValidateAllObjectFunc, false) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -608,12 +609,12 @@ func TestEtcdCreateWithConflict(t *testing.T) { }, Target: api.ObjectReference{Name: "machine"}, } - _, err = bindingStorage.Create(ctx, &binding, false) + _, err = bindingStorage.Create(ctx, &binding, rest.ValidateAllObjectFunc, false) if err != nil { t.Fatalf("unexpected error: %v", err) } - _, err = bindingStorage.Create(ctx, &binding, false) + _, err = bindingStorage.Create(ctx, &binding, rest.ValidateAllObjectFunc, false) if err == nil || !errors.IsConflict(err) { t.Fatalf("expected resource conflict error, not: %v", err) } @@ -624,7 +625,7 @@ func TestEtcdCreateWithExistingContainers(t *testing.T) { defer server.Terminate(t) defer storage.Store.DestroyFunc() ctx := genericapirequest.NewDefaultContext() - _, err := storage.Create(ctx, validNewPod(), false) + _, err := storage.Create(ctx, validNewPod(), rest.ValidateAllObjectFunc, false) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -633,7 +634,7 @@ func TestEtcdCreateWithExistingContainers(t *testing.T) { _, err = bindingStorage.Create(ctx, &api.Binding{ ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "foo"}, Target: api.ObjectReference{Name: "machine"}, - }, false) + }, rest.ValidateAllObjectFunc, false) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -683,10 +684,10 @@ func TestEtcdCreateBinding(t *testing.T) { for k, test := range testCases { storage, bindingStorage, _, server := newStorage(t) - if _, err := storage.Create(ctx, validNewPod(), false); err != nil { + if _, err := storage.Create(ctx, validNewPod(), rest.ValidateAllObjectFunc, false); err != nil { t.Fatalf("%s: unexpected error: %v", k, err) } - if _, err := bindingStorage.Create(ctx, &test.binding, false); !test.errOK(err) { + if _, err := bindingStorage.Create(ctx, &test.binding, rest.ValidateAllObjectFunc, false); !test.errOK(err) { t.Errorf("%s: unexpected error: %v", k, err) } else if err == nil { // If bind succeeded, verify Host field in pod's Spec. @@ -712,7 +713,7 @@ func TestEtcdUpdateUninitialized(t *testing.T) { pod := validNewPod() // add pending initializers to the pod pod.ObjectMeta.Initializers = &metav1.Initializers{Pending: []metav1.Initializer{{Name: "init.k8s.io"}}} - if _, err := storage.Create(ctx, pod, true); err != nil { + if _, err := storage.Create(ctx, pod, rest.ValidateAllObjectFunc, true); err != nil { t.Fatalf("unexpected error: %v", err) } podIn := *pod @@ -727,7 +728,7 @@ func TestEtcdUpdateUninitialized(t *testing.T) { }) podIn.ObjectMeta.Initializers = nil - _, _, err := storage.Update(ctx, podIn.Name, rest.DefaultUpdatedObjectInfo(&podIn)) + _, _, err := storage.Update(ctx, podIn.Name, rest.DefaultUpdatedObjectInfo(&podIn), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -754,7 +755,7 @@ func TestEtcdStatusUpdateUninitialized(t *testing.T) { pod := validNewPod() // add pending initializers to the pod pod.ObjectMeta.Initializers = &metav1.Initializers{Pending: []metav1.Initializer{{Name: "init.k8s.io"}}} - if _, err := storage.Create(ctx, pod, true); err != nil { + if _, err := storage.Create(ctx, pod, rest.ValidateAllObjectFunc, true); err != nil { t.Fatalf("unexpected error: %v", err) } podIn := *pod @@ -762,7 +763,7 @@ func TestEtcdStatusUpdateUninitialized(t *testing.T) { podIn.Status.Phase = api.PodRunning podIn.ObjectMeta.Initializers = nil - _, _, err := statusStorage.Update(ctx, podIn.Name, rest.DefaultUpdatedObjectInfo(&podIn)) + _, _, err := statusStorage.Update(ctx, podIn.Name, rest.DefaultUpdatedObjectInfo(&podIn), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc) expected := "Forbidden: must not update status when the object is uninitialized" if err == nil { t.Fatalf("Unexpected no err, expected %q", expected) @@ -778,12 +779,12 @@ func TestEtcdUpdateNotScheduled(t *testing.T) { defer storage.Store.DestroyFunc() ctx := genericapirequest.NewDefaultContext() - if _, err := storage.Create(ctx, validNewPod(), false); err != nil { + if _, err := storage.Create(ctx, validNewPod(), rest.ValidateAllObjectFunc, false); err != nil { t.Fatalf("unexpected error: %v", err) } podIn := validChangedPod() - _, _, err := storage.Update(ctx, podIn.Name, rest.DefaultUpdatedObjectInfo(podIn)) + _, _, err := storage.Update(ctx, podIn.Name, rest.DefaultUpdatedObjectInfo(podIn), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -853,7 +854,7 @@ func TestEtcdUpdateScheduled(t *testing.T) { SchedulerName: api.DefaultSchedulerName, }, } - _, _, err = storage.Update(ctx, podIn.Name, rest.DefaultUpdatedObjectInfo(&podIn)) + _, _, err = storage.Update(ctx, podIn.Name, rest.DefaultUpdatedObjectInfo(&podIn), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -937,7 +938,7 @@ func TestEtcdUpdateStatus(t *testing.T) { expected.Labels = podIn.Labels expected.Status = podIn.Status - _, _, err = statusStorage.Update(ctx, podIn.Name, rest.DefaultUpdatedObjectInfo(&podIn)) + _, _, err = statusStorage.Update(ctx, podIn.Name, rest.DefaultUpdatedObjectInfo(&podIn), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc) if err != nil { t.Fatalf("Unexpected error: %v", err) } diff --git a/pkg/registry/core/pod/strategy.go b/pkg/registry/core/pod/strategy.go index bb6e0afb10f..5cd0eb03cf7 100644 --- a/pkg/registry/core/pod/strategy.go +++ b/pkg/registry/core/pod/strategy.go @@ -41,10 +41,11 @@ import ( "k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage/names" utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper/qos" + "k8s.io/kubernetes/pkg/api/legacyscheme" podutil "k8s.io/kubernetes/pkg/api/pod" - "k8s.io/kubernetes/pkg/api/validation" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper/qos" + "k8s.io/kubernetes/pkg/apis/core/validation" "k8s.io/kubernetes/pkg/kubelet/client" ) @@ -56,7 +57,7 @@ type podStrategy struct { // Strategy is the default logic that applies when creating and updating Pod // objects via the REST API. -var Strategy = podStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = podStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} // NamespaceScoped is true for pods. func (podStrategy) NamespaceScoped() bool { diff --git a/pkg/registry/core/pod/strategy_test.go b/pkg/registry/core/pod/strategy_test.go index 4d602cb0656..8d10e6a6eeb 100644 --- a/pkg/registry/core/pod/strategy_test.go +++ b/pkg/registry/core/pod/strategy_test.go @@ -29,8 +29,9 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" apitesting "k8s.io/kubernetes/pkg/api/testing" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/kubelet/client" // install all api groups for testing @@ -361,7 +362,7 @@ func TestCheckLogLocation(t *testing.T) { func TestSelectableFieldLabelConversions(t *testing.T) { apitesting.TestSelectableFieldLabelConversionsOfKind(t, - api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), + legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), "Pod", PodToSelectableFields(&api.Pod{}), nil, diff --git a/pkg/registry/core/podtemplate/BUILD b/pkg/registry/core/podtemplate/BUILD index adbf6de90da..5841503ea28 100644 --- a/pkg/registry/core/podtemplate/BUILD +++ b/pkg/registry/core/podtemplate/BUILD @@ -13,9 +13,10 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/core/podtemplate", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/pod:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", diff --git a/pkg/registry/core/podtemplate/storage/BUILD b/pkg/registry/core/podtemplate/storage/BUILD index 5a3e812ecc0..5bce6101070 100644 --- a/pkg/registry/core/podtemplate/storage/BUILD +++ b/pkg/registry/core/podtemplate/storage/BUILD @@ -9,16 +9,17 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/podtemplate/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], ) @@ -28,7 +29,7 @@ go_library( srcs = ["storage.go"], importpath = "k8s.io/kubernetes/pkg/registry/core/podtemplate/storage", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/printers:go_default_library", "//pkg/printers/internalversion:go_default_library", "//pkg/printers/storage:go_default_library", diff --git a/pkg/registry/core/podtemplate/storage/storage.go b/pkg/registry/core/podtemplate/storage/storage.go index 5c35ac1bd05..1767186b999 100644 --- a/pkg/registry/core/podtemplate/storage/storage.go +++ b/pkg/registry/core/podtemplate/storage/storage.go @@ -20,7 +20,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/generic" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/printers" printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" printerstorage "k8s.io/kubernetes/pkg/printers/storage" diff --git a/pkg/registry/core/podtemplate/storage/storage_test.go b/pkg/registry/core/podtemplate/storage/storage_test.go index 432d88e08bf..2fd7c2a2a76 100644 --- a/pkg/registry/core/podtemplate/storage/storage_test.go +++ b/pkg/registry/core/podtemplate/storage/storage_test.go @@ -24,8 +24,9 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -72,7 +73,7 @@ func TestCreate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) pod := validNewPodTemplate("foo") pod.ObjectMeta = metav1.ObjectMeta{} test.TestCreate( @@ -89,7 +90,7 @@ func TestUpdate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestUpdate( //valid validNewPodTemplate("foo"), @@ -106,7 +107,7 @@ func TestDelete(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ReturnDeletedObject() + test := genericregistrytest.New(t, storage.Store).ReturnDeletedObject() test.TestDelete(validNewPodTemplate("foo")) } @@ -114,7 +115,7 @@ func TestGet(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestGet(validNewPodTemplate("foo")) } @@ -122,7 +123,7 @@ func TestList(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestList(validNewPodTemplate("foo")) } @@ -130,7 +131,7 @@ func TestWatch(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestWatch( validNewPodTemplate("foo"), // matching labels diff --git a/pkg/registry/core/podtemplate/strategy.go b/pkg/registry/core/podtemplate/strategy.go index 30ff5995509..0f19250d97f 100644 --- a/pkg/registry/core/podtemplate/strategy.go +++ b/pkg/registry/core/podtemplate/strategy.go @@ -21,9 +21,10 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/pod" - "k8s.io/kubernetes/pkg/api/validation" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/validation" ) // podTemplateStrategy implements behavior for PodTemplates @@ -34,7 +35,7 @@ type podTemplateStrategy struct { // Strategy is the default logic that applies when creating and updating PodTemplate // objects via the REST API. -var Strategy = podTemplateStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = podTemplateStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} // NamespaceScoped is true for pod templates. func (podTemplateStrategy) NamespaceScoped() bool { diff --git a/pkg/registry/core/rangeallocation/BUILD b/pkg/registry/core/rangeallocation/BUILD index 6f2289d4f5b..8b55dd09d80 100644 --- a/pkg/registry/core/rangeallocation/BUILD +++ b/pkg/registry/core/rangeallocation/BUILD @@ -12,7 +12,7 @@ go_library( "registry.go", ], importpath = "k8s.io/kubernetes/pkg/registry/core/rangeallocation", - deps = ["//pkg/api:go_default_library"], + deps = ["//pkg/apis/core:go_default_library"], ) filegroup( diff --git a/pkg/registry/core/rangeallocation/registry.go b/pkg/registry/core/rangeallocation/registry.go index 5fd3aa3af77..d67b52f86f7 100644 --- a/pkg/registry/core/rangeallocation/registry.go +++ b/pkg/registry/core/rangeallocation/registry.go @@ -17,7 +17,7 @@ limitations under the License. package rangeallocation import ( - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // RangeRegistry is a registry that can retrieve or persist a RangeAllocation object. diff --git a/pkg/registry/core/replicationcontroller/BUILD b/pkg/registry/core/replicationcontroller/BUILD index 0865720873f..2e0a9e0aa7a 100644 --- a/pkg/registry/core/replicationcontroller/BUILD +++ b/pkg/registry/core/replicationcontroller/BUILD @@ -15,10 +15,11 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/core/replicationcontroller", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/pod:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -38,12 +39,13 @@ go_library( go_test( name = "go_default_test", srcs = ["strategy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/replicationcontroller", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/testapi:go_default_library", "//pkg/api/testing:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", ], diff --git a/pkg/registry/core/replicationcontroller/registry.go b/pkg/registry/core/replicationcontroller/registry.go index 41a438c4e8b..199981db097 100644 --- a/pkg/registry/core/replicationcontroller/registry.go +++ b/pkg/registry/core/replicationcontroller/registry.go @@ -26,7 +26,7 @@ import ( "k8s.io/apimachinery/pkg/watch" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // Registry is an interface for things that know how to store ReplicationControllers. @@ -34,8 +34,8 @@ type Registry interface { ListControllers(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*api.ReplicationControllerList, error) WatchControllers(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) GetController(ctx genericapirequest.Context, controllerID string, options *metav1.GetOptions) (*api.ReplicationController, error) - CreateController(ctx genericapirequest.Context, controller *api.ReplicationController) (*api.ReplicationController, error) - UpdateController(ctx genericapirequest.Context, controller *api.ReplicationController) (*api.ReplicationController, error) + CreateController(ctx genericapirequest.Context, controller *api.ReplicationController, createValidation rest.ValidateObjectFunc) (*api.ReplicationController, error) + UpdateController(ctx genericapirequest.Context, controller *api.ReplicationController, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (*api.ReplicationController, error) DeleteController(ctx genericapirequest.Context, controllerID string) error } @@ -73,16 +73,16 @@ func (s *storage) GetController(ctx genericapirequest.Context, controllerID stri return obj.(*api.ReplicationController), nil } -func (s *storage) CreateController(ctx genericapirequest.Context, controller *api.ReplicationController) (*api.ReplicationController, error) { - obj, err := s.Create(ctx, controller, false) +func (s *storage) CreateController(ctx genericapirequest.Context, controller *api.ReplicationController, createValidation rest.ValidateObjectFunc) (*api.ReplicationController, error) { + obj, err := s.Create(ctx, controller, createValidation, false) if err != nil { return nil, err } return obj.(*api.ReplicationController), nil } -func (s *storage) UpdateController(ctx genericapirequest.Context, controller *api.ReplicationController) (*api.ReplicationController, error) { - obj, _, err := s.Update(ctx, controller.Name, rest.DefaultUpdatedObjectInfo(controller)) +func (s *storage) UpdateController(ctx genericapirequest.Context, controller *api.ReplicationController, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (*api.ReplicationController, error) { + obj, _, err := s.Update(ctx, controller.Name, rest.DefaultUpdatedObjectInfo(controller), createValidation, updateValidation) if err != nil { return nil, err } diff --git a/pkg/registry/core/replicationcontroller/storage/BUILD b/pkg/registry/core/replicationcontroller/storage/BUILD index 7dde8812ab6..3d75f1f6004 100644 --- a/pkg/registry/core/replicationcontroller/storage/BUILD +++ b/pkg/registry/core/replicationcontroller/storage/BUILD @@ -9,11 +9,11 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/replicationcontroller/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", "//pkg/apis/autoscaling:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", @@ -24,6 +24,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], @@ -34,9 +35,11 @@ go_library( srcs = ["storage.go"], importpath = "k8s.io/kubernetes/pkg/registry/core/replicationcontroller/storage", deps = [ - "//pkg/api:go_default_library", "//pkg/apis/autoscaling:go_default_library", + "//pkg/apis/autoscaling/v1:go_default_library", "//pkg/apis/autoscaling/validation:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/extensions/v1beta1:go_default_library", "//pkg/printers:go_default_library", "//pkg/printers/internalversion:go_default_library", "//pkg/printers/storage:go_default_library", diff --git a/pkg/registry/core/replicationcontroller/storage/storage.go b/pkg/registry/core/replicationcontroller/storage/storage.go index 65f1214690e..ead95842d96 100644 --- a/pkg/registry/core/replicationcontroller/storage/storage.go +++ b/pkg/registry/core/replicationcontroller/storage/storage.go @@ -30,9 +30,11 @@ import ( "k8s.io/apiserver/pkg/registry/generic" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/autoscaling" + autoscalingv1 "k8s.io/kubernetes/pkg/apis/autoscaling/v1" "k8s.io/kubernetes/pkg/apis/autoscaling/validation" + api "k8s.io/kubernetes/pkg/apis/core" + extensionsv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" "k8s.io/kubernetes/pkg/printers" printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" printerstorage "k8s.io/kubernetes/pkg/printers/storage" @@ -117,8 +119,8 @@ func (r *StatusREST) Get(ctx genericapirequest.Context, name string, options *me } // Update alters the status subset of an object. -func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - return r.store.Update(ctx, name, objInfo) +func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation) } type ScaleREST struct { @@ -129,8 +131,13 @@ type ScaleREST struct { var _ = rest.Patcher(&ScaleREST{}) var _ = rest.GroupVersionKindProvider(&ScaleREST{}) -func (r *ScaleREST) GroupVersionKind() schema.GroupVersionKind { - return schema.GroupVersionKind{Group: "autoscaling", Version: "v1", Kind: "Scale"} +func (r *ScaleREST) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind { + switch containingGV { + case extensionsv1beta1.SchemeGroupVersion: + return extensionsv1beta1.SchemeGroupVersion.WithKind("Scale") + default: + return autoscalingv1.SchemeGroupVersion.WithKind("Scale") + } } // New creates a new Scale object @@ -146,13 +153,14 @@ func (r *ScaleREST) Get(ctx genericapirequest.Context, name string, options *met return scaleFromRC(rc), nil } -func (r *ScaleREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { +func (r *ScaleREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { rc, err := r.registry.GetController(ctx, name, &metav1.GetOptions{}) if err != nil { return nil, false, errors.NewNotFound(autoscaling.Resource("replicationcontrollers/scale"), name) } oldScale := scaleFromRC(rc) + // TODO: should this pass validation? obj, err := objInfo.UpdatedObject(ctx, oldScale) if err != nil { return nil, false, err @@ -172,7 +180,7 @@ func (r *ScaleREST) Update(ctx genericapirequest.Context, name string, objInfo r rc.Spec.Replicas = scale.Spec.Replicas rc.ResourceVersion = scale.ResourceVersion - rc, err = r.registry.UpdateController(ctx, rc) + rc, err = r.registry.UpdateController(ctx, rc, createValidation, updateValidation) if err != nil { return nil, false, err } diff --git a/pkg/registry/core/replicationcontroller/storage/storage_test.go b/pkg/registry/core/replicationcontroller/storage/storage_test.go index c09e47055c2..04a846f8219 100644 --- a/pkg/registry/core/replicationcontroller/storage/storage_test.go +++ b/pkg/registry/core/replicationcontroller/storage/storage_test.go @@ -28,10 +28,11 @@ import ( "k8s.io/apimachinery/pkg/util/diff" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" "k8s.io/apiserver/pkg/registry/rest" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/autoscaling" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -55,7 +56,7 @@ func newStorage(t *testing.T) (ControllerStorage, *etcdtesting.EtcdTestServer) { // createController is a helper function that returns a controller with the updated resource version. func createController(storage *REST, rc api.ReplicationController, t *testing.T) (api.ReplicationController, error) { ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), rc.Namespace) - obj, err := storage.Create(ctx, &rc, false) + obj, err := storage.Create(ctx, &rc, rest.ValidateAllObjectFunc, false) if err != nil { t.Errorf("Failed to create controller, %v", err) } @@ -98,7 +99,7 @@ func TestCreate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Controller.Store.DestroyFunc() - test := registrytest.New(t, storage.Controller.Store) + test := genericregistrytest.New(t, storage.Controller.Store) controller := validNewController() controller.ObjectMeta = metav1.ObjectMeta{} test.TestCreate( @@ -119,7 +120,7 @@ func TestUpdate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Controller.Store.DestroyFunc() - test := registrytest.New(t, storage.Controller.Store) + test := genericregistrytest.New(t, storage.Controller.Store) test.TestUpdate( // valid validNewController(), @@ -147,7 +148,7 @@ func TestDelete(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Controller.Store.DestroyFunc() - test := registrytest.New(t, storage.Controller.Store) + test := genericregistrytest.New(t, storage.Controller.Store) test.TestDelete(validNewController()) } @@ -173,7 +174,7 @@ func TestGenerationNumber(t *testing.T) { // Updates to spec should increment the generation number controller.Spec.Replicas += 1 - storage.Controller.Update(ctx, controller.Name, rest.DefaultUpdatedObjectInfo(controller)) + storage.Controller.Update(ctx, controller.Name, rest.DefaultUpdatedObjectInfo(controller), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -188,7 +189,7 @@ func TestGenerationNumber(t *testing.T) { // Updates to status should not increment either spec or status generation numbers controller.Status.Replicas += 1 - storage.Controller.Update(ctx, controller.Name, rest.DefaultUpdatedObjectInfo(controller)) + storage.Controller.Update(ctx, controller.Name, rest.DefaultUpdatedObjectInfo(controller), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -206,7 +207,7 @@ func TestGet(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Controller.Store.DestroyFunc() - test := registrytest.New(t, storage.Controller.Store) + test := genericregistrytest.New(t, storage.Controller.Store) test.TestGet(validNewController()) } @@ -214,7 +215,7 @@ func TestList(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Controller.Store.DestroyFunc() - test := registrytest.New(t, storage.Controller.Store) + test := genericregistrytest.New(t, storage.Controller.Store) test.TestList(validNewController()) } @@ -222,7 +223,7 @@ func TestWatch(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Controller.Store.DestroyFunc() - test := registrytest.New(t, storage.Controller.Store) + test := genericregistrytest.New(t, storage.Controller.Store) test.TestWatch( validController, // matching labels @@ -308,7 +309,7 @@ func TestScaleUpdate(t *testing.T) { }, } - if _, _, err := storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update)); err != nil { + if _, _, err := storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc); err != nil { t.Fatalf("error updating scale %v: %v", update, err) } obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{}) @@ -323,7 +324,7 @@ func TestScaleUpdate(t *testing.T) { update.ResourceVersion = rc.ResourceVersion update.Spec.Replicas = 15 - if _, _, err = storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update)); err != nil && !errors.IsConflict(err) { + if _, _, err = storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc); err != nil && !errors.IsConflict(err) { t.Fatalf("unexpected error, expecting an update conflict but got %v", err) } } diff --git a/pkg/registry/core/replicationcontroller/strategy.go b/pkg/registry/core/replicationcontroller/strategy.go index 2047afb6d3c..ede6e672481 100644 --- a/pkg/registry/core/replicationcontroller/strategy.go +++ b/pkg/registry/core/replicationcontroller/strategy.go @@ -33,10 +33,11 @@ import ( "k8s.io/apiserver/pkg/registry/rest" apistorage "k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/pod" - "k8s.io/kubernetes/pkg/api/validation" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" + "k8s.io/kubernetes/pkg/apis/core/validation" ) // rcStrategy implements verification logic for Replication Controllers. @@ -46,11 +47,11 @@ type rcStrategy struct { } // Strategy is the default logic that applies when creating and updating Replication Controller objects. -var Strategy = rcStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = rcStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} // DefaultGarbageCollectionPolicy returns Orphan because that was the default // behavior before the server-side garbage collection was implemented. -func (rcStrategy) DefaultGarbageCollectionPolicy() rest.GarbageCollectionPolicy { +func (rcStrategy) DefaultGarbageCollectionPolicy(ctx genericapirequest.Context) rest.GarbageCollectionPolicy { return rest.OrphanDependents } diff --git a/pkg/registry/core/replicationcontroller/strategy_test.go b/pkg/registry/core/replicationcontroller/strategy_test.go index 926a4cb1338..1a89135ab85 100644 --- a/pkg/registry/core/replicationcontroller/strategy_test.go +++ b/pkg/registry/core/replicationcontroller/strategy_test.go @@ -22,8 +22,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" apitesting "k8s.io/kubernetes/pkg/api/testing" + api "k8s.io/kubernetes/pkg/apis/core" // install all api groups for testing _ "k8s.io/kubernetes/pkg/api/testapi" @@ -148,7 +149,7 @@ func TestControllerStatusStrategy(t *testing.T) { func TestSelectableFieldLabelConversions(t *testing.T) { apitesting.TestSelectableFieldLabelConversionsOfKind(t, - api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), + legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), "ReplicationController", ControllerToSelectableFields(&api.ReplicationController{}), nil, diff --git a/pkg/registry/core/resourcequota/BUILD b/pkg/registry/core/resourcequota/BUILD index 1b3f58bdf96..b4b79d00985 100644 --- a/pkg/registry/core/resourcequota/BUILD +++ b/pkg/registry/core/resourcequota/BUILD @@ -14,8 +14,9 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/core/resourcequota", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", @@ -26,10 +27,10 @@ go_library( go_test( name = "go_default_test", srcs = ["strategy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/resourcequota", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", diff --git a/pkg/registry/core/resourcequota/storage/BUILD b/pkg/registry/core/resourcequota/storage/BUILD index acc8d410338..f4781b7a963 100644 --- a/pkg/registry/core/resourcequota/storage/BUILD +++ b/pkg/registry/core/resourcequota/storage/BUILD @@ -9,10 +9,10 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/resourcequota/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", @@ -22,6 +22,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], @@ -32,7 +33,7 @@ go_library( srcs = ["storage.go"], importpath = "k8s.io/kubernetes/pkg/registry/core/resourcequota/storage", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/core/resourcequota:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/pkg/registry/core/resourcequota/storage/storage.go b/pkg/registry/core/resourcequota/storage/storage.go index 76f8a8f6a7a..391cb17f067 100644 --- a/pkg/registry/core/resourcequota/storage/storage.go +++ b/pkg/registry/core/resourcequota/storage/storage.go @@ -23,7 +23,7 @@ import ( "k8s.io/apiserver/pkg/registry/generic" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/core/resourcequota" ) @@ -77,6 +77,6 @@ func (r *StatusREST) Get(ctx genericapirequest.Context, name string, options *me } // Update alters the status subset of an object. -func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - return r.store.Update(ctx, name, objInfo) +func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation) } diff --git a/pkg/registry/core/resourcequota/storage/storage_test.go b/pkg/registry/core/resourcequota/storage/storage_test.go index 13e5d44cb89..a05cba92fde 100644 --- a/pkg/registry/core/resourcequota/storage/storage_test.go +++ b/pkg/registry/core/resourcequota/storage/storage_test.go @@ -27,9 +27,10 @@ import ( "k8s.io/apimachinery/pkg/util/diff" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" "k8s.io/apiserver/pkg/registry/rest" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -68,7 +69,7 @@ func TestCreate(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) resourcequota := validNewResourceQuota() resourcequota.ObjectMeta = metav1.ObjectMeta{} test.TestCreate( @@ -87,7 +88,7 @@ func TestCreateSetsFields(t *testing.T) { defer storage.Store.DestroyFunc() ctx := genericapirequest.NewDefaultContext() resourcequota := validNewResourceQuota() - _, err := storage.Create(genericapirequest.NewDefaultContext(), resourcequota, false) + _, err := storage.Create(genericapirequest.NewDefaultContext(), resourcequota, rest.ValidateAllObjectFunc, false) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -109,7 +110,7 @@ func TestDelete(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ReturnDeletedObject() + test := genericregistrytest.New(t, storage.Store).ReturnDeletedObject() test.TestDelete(validNewResourceQuota()) } @@ -117,7 +118,7 @@ func TestGet(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestGet(validNewResourceQuota()) } @@ -125,7 +126,7 @@ func TestList(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestList(validNewResourceQuota()) } @@ -133,7 +134,7 @@ func TestWatch(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestWatch( validNewResourceQuota(), // matching labels @@ -191,7 +192,7 @@ func TestUpdateStatus(t *testing.T) { }, } - _, _, err = status.Update(ctx, resourcequotaIn.Name, rest.DefaultUpdatedObjectInfo(resourcequotaIn)) + _, _, err = status.Update(ctx, resourcequotaIn.Name, rest.DefaultUpdatedObjectInfo(resourcequotaIn), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc) if err != nil { t.Fatalf("Unexpected error: %v", err) } diff --git a/pkg/registry/core/resourcequota/strategy.go b/pkg/registry/core/resourcequota/strategy.go index e9c26c009ca..eecfc59d936 100644 --- a/pkg/registry/core/resourcequota/strategy.go +++ b/pkg/registry/core/resourcequota/strategy.go @@ -21,8 +21,9 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/validation" ) // resourcequotaStrategy implements behavior for ResourceQuota objects @@ -33,7 +34,7 @@ type resourcequotaStrategy struct { // Strategy is the default logic that applies when creating and updating ResourceQuota // objects via the REST API. -var Strategy = resourcequotaStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = resourcequotaStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} // NamespaceScoped is true for resourcequotas. func (resourcequotaStrategy) NamespaceScoped() bool { diff --git a/pkg/registry/core/resourcequota/strategy_test.go b/pkg/registry/core/resourcequota/strategy_test.go index 6ec3816fc06..c571c2a336d 100644 --- a/pkg/registry/core/resourcequota/strategy_test.go +++ b/pkg/registry/core/resourcequota/strategy_test.go @@ -22,7 +22,7 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestResourceQuotaStrategy(t *testing.T) { diff --git a/pkg/registry/core/rest/BUILD b/pkg/registry/core/rest/BUILD index 9a8c92ed2f7..cde65909195 100644 --- a/pkg/registry/core/rest/BUILD +++ b/pkg/registry/core/rest/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["storage_core_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/rest", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apiserver/pkg/server/storage:go_default_library", @@ -23,7 +23,8 @@ go_library( srcs = ["storage_core.go"], importpath = "k8s.io/kubernetes/pkg/registry/core/rest", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/policy/internalversion:go_default_library", "//pkg/kubelet/client:go_default_library", "//pkg/master/ports:go_default_library", diff --git a/pkg/registry/core/rest/storage_core.go b/pkg/registry/core/rest/storage_core.go index e46a7cf3330..ad7a9a91ade 100644 --- a/pkg/registry/core/rest/storage_core.go +++ b/pkg/registry/core/rest/storage_core.go @@ -35,7 +35,8 @@ import ( serverstorage "k8s.io/apiserver/pkg/server/storage" etcdutil "k8s.io/apiserver/pkg/storage/etcd/util" restclient "k8s.io/client-go/rest" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" policyclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion" kubeletclient "k8s.io/kubernetes/pkg/kubelet/client" "k8s.io/kubernetes/pkg/master/ports" @@ -90,21 +91,15 @@ type LegacyRESTStorage struct { func (c LegacyRESTStorageProvider) NewLegacyRESTStorage(restOptionsGetter generic.RESTOptionsGetter) (LegacyRESTStorage, genericapiserver.APIGroupInfo, error) { apiGroupInfo := genericapiserver.APIGroupInfo{ - GroupMeta: *api.Registry.GroupOrDie(api.GroupName), + GroupMeta: *legacyscheme.Registry.GroupOrDie(api.GroupName), VersionedResourcesStorageMap: map[string]map[string]rest.Storage{}, - Scheme: api.Scheme, - ParameterCodec: api.ParameterCodec, - NegotiatedSerializer: api.Codecs, - SubresourceGroupVersionKind: map[string]schema.GroupVersionKind{}, - } - if autoscalingGroupVersion := (schema.GroupVersion{Group: "autoscaling", Version: "v1"}); api.Registry.IsEnabledVersion(autoscalingGroupVersion) { - apiGroupInfo.SubresourceGroupVersionKind["replicationcontrollers/scale"] = autoscalingGroupVersion.WithKind("Scale") + Scheme: legacyscheme.Scheme, + ParameterCodec: legacyscheme.ParameterCodec, + NegotiatedSerializer: legacyscheme.Codecs, } var podDisruptionClient policyclient.PodDisruptionBudgetsGetter - if policyGroupVersion := (schema.GroupVersion{Group: "policy", Version: "v1beta1"}); api.Registry.IsEnabledVersion(policyGroupVersion) { - apiGroupInfo.SubresourceGroupVersionKind["pods/eviction"] = policyGroupVersion.WithKind("Eviction") - + if policyGroupVersion := (schema.GroupVersion{Group: "policy", Version: "v1beta1"}); legacyscheme.Registry.IsEnabledVersion(policyGroupVersion) { var err error podDisruptionClient, err = policyclient.NewForConfig(c.LoopbackClientConfig) if err != nil { @@ -223,10 +218,10 @@ func (c LegacyRESTStorageProvider) NewLegacyRESTStorage(restOptionsGetter generi "componentStatuses": componentstatus.NewStorage(componentStatusStorage{c.StorageFactory}.serversToValidate), } - if api.Registry.IsEnabledVersion(schema.GroupVersion{Group: "autoscaling", Version: "v1"}) { + if legacyscheme.Registry.IsEnabledVersion(schema.GroupVersion{Group: "autoscaling", Version: "v1"}) { restStorageMap["replicationControllers/scale"] = controllerStorage.Scale } - if api.Registry.IsEnabledVersion(schema.GroupVersion{Group: "policy", Version: "v1beta1"}) { + if legacyscheme.Registry.IsEnabledVersion(schema.GroupVersion{Group: "policy", Version: "v1beta1"}) { restStorageMap["pods/eviction"] = podStorage.Eviction } apiGroupInfo.VersionedResourcesStorageMap["v1"] = restStorageMap diff --git a/pkg/registry/core/secret/BUILD b/pkg/registry/core/secret/BUILD index ad40dd5dff8..235615911ac 100644 --- a/pkg/registry/core/secret/BUILD +++ b/pkg/registry/core/secret/BUILD @@ -15,8 +15,9 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/core/secret", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -36,12 +37,13 @@ go_library( go_test( name = "go_default_test", srcs = ["strategy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/secret", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/testapi:go_default_library", "//pkg/api/testing:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", diff --git a/pkg/registry/core/secret/registry.go b/pkg/registry/core/secret/registry.go index a6d99683e00..8d214d4f1fe 100644 --- a/pkg/registry/core/secret/registry.go +++ b/pkg/registry/core/secret/registry.go @@ -22,7 +22,7 @@ import ( "k8s.io/apimachinery/pkg/watch" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // Registry is an interface implemented by things that know how to store Secret objects. @@ -30,8 +30,8 @@ type Registry interface { ListSecrets(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*api.SecretList, error) WatchSecrets(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) GetSecret(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (*api.Secret, error) - CreateSecret(ctx genericapirequest.Context, Secret *api.Secret) (*api.Secret, error) - UpdateSecret(ctx genericapirequest.Context, Secret *api.Secret) (*api.Secret, error) + CreateSecret(ctx genericapirequest.Context, Secret *api.Secret, createValidation rest.ValidateObjectFunc) (*api.Secret, error) + UpdateSecret(ctx genericapirequest.Context, Secret *api.Secret, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (*api.Secret, error) DeleteSecret(ctx genericapirequest.Context, name string) error } @@ -66,13 +66,13 @@ func (s *storage) GetSecret(ctx genericapirequest.Context, name string, options return obj.(*api.Secret), nil } -func (s *storage) CreateSecret(ctx genericapirequest.Context, secret *api.Secret) (*api.Secret, error) { - obj, err := s.Create(ctx, secret, false) +func (s *storage) CreateSecret(ctx genericapirequest.Context, secret *api.Secret, createValidation rest.ValidateObjectFunc) (*api.Secret, error) { + obj, err := s.Create(ctx, secret, createValidation, false) return obj.(*api.Secret), err } -func (s *storage) UpdateSecret(ctx genericapirequest.Context, secret *api.Secret) (*api.Secret, error) { - obj, _, err := s.Update(ctx, secret.Name, rest.DefaultUpdatedObjectInfo(secret)) +func (s *storage) UpdateSecret(ctx genericapirequest.Context, secret *api.Secret, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (*api.Secret, error) { + obj, _, err := s.Update(ctx, secret.Name, rest.DefaultUpdatedObjectInfo(secret), createValidation, updateValidation) return obj.(*api.Secret), err } diff --git a/pkg/registry/core/secret/storage/BUILD b/pkg/registry/core/secret/storage/BUILD index ee2c08ecc36..b1b981e1096 100644 --- a/pkg/registry/core/secret/storage/BUILD +++ b/pkg/registry/core/secret/storage/BUILD @@ -9,16 +9,17 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/secret/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], ) @@ -28,7 +29,7 @@ go_library( srcs = ["storage.go"], importpath = "k8s.io/kubernetes/pkg/registry/core/secret/storage", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/core/secret:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", diff --git a/pkg/registry/core/secret/storage/storage.go b/pkg/registry/core/secret/storage/storage.go index 9f70cad2fc4..13600daf056 100644 --- a/pkg/registry/core/secret/storage/storage.go +++ b/pkg/registry/core/secret/storage/storage.go @@ -20,7 +20,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/generic" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/core/secret" ) diff --git a/pkg/registry/core/secret/storage/storage_test.go b/pkg/registry/core/secret/storage/storage_test.go index 1071ee414a7..d9ef6475be4 100644 --- a/pkg/registry/core/secret/storage/storage_test.go +++ b/pkg/registry/core/secret/storage/storage_test.go @@ -24,8 +24,9 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -56,7 +57,7 @@ func TestCreate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) secret := validNewSecret("foo") secret.ObjectMeta = metav1.ObjectMeta{GenerateName: "foo-"} test.TestCreate( @@ -79,7 +80,7 @@ func TestUpdate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestUpdate( // valid validNewSecret("foo"), @@ -96,7 +97,7 @@ func TestDelete(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestDelete(validNewSecret("foo")) } @@ -104,7 +105,7 @@ func TestGet(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestGet(validNewSecret("foo")) } @@ -112,7 +113,7 @@ func TestList(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestList(validNewSecret("foo")) } @@ -120,7 +121,7 @@ func TestWatch(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestWatch( validNewSecret("foo"), // matching labels diff --git a/pkg/registry/core/secret/strategy.go b/pkg/registry/core/secret/strategy.go index 59150c6402d..fbdb05572de 100644 --- a/pkg/registry/core/secret/strategy.go +++ b/pkg/registry/core/secret/strategy.go @@ -29,8 +29,9 @@ import ( "k8s.io/apiserver/pkg/registry/rest" apistorage "k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/validation" ) // strategy implements behavior for Secret objects @@ -41,7 +42,7 @@ type strategy struct { // Strategy is the default logic that applies when creating and updating Secret // objects via the REST API. -var Strategy = strategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = strategy{legacyscheme.Scheme, names.SimpleNameGenerator} var _ = rest.RESTCreateStrategy(Strategy) diff --git a/pkg/registry/core/secret/strategy_test.go b/pkg/registry/core/secret/strategy_test.go index 1e284efa5b9..dda59ab1eee 100644 --- a/pkg/registry/core/secret/strategy_test.go +++ b/pkg/registry/core/secret/strategy_test.go @@ -23,8 +23,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" apitesting "k8s.io/kubernetes/pkg/api/testing" + api "k8s.io/kubernetes/pkg/apis/core" // install all api groups for testing _ "k8s.io/kubernetes/pkg/api/testapi" @@ -106,7 +107,7 @@ func TestExportSecret(t *testing.T) { func TestSelectableFieldLabelConversions(t *testing.T) { apitesting.TestSelectableFieldLabelConversionsOfKind(t, - api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), + legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), "Secret", SelectableFields(&api.Secret{}), nil, diff --git a/pkg/registry/core/service/BUILD b/pkg/registry/core/service/BUILD index bb54bd7b863..590aac0bd3d 100644 --- a/pkg/registry/core/service/BUILD +++ b/pkg/registry/core/service/BUILD @@ -17,10 +17,11 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/core/service", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/service:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//pkg/capabilities:go_default_library", "//pkg/features:go_default_library", "//pkg/registry/core/endpoint:go_default_library", @@ -49,12 +50,12 @@ go_test( "rest_test.go", "strategy_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/service", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", "//pkg/api/service:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", "//pkg/features:go_default_library", "//pkg/registry/core/service/ipallocator:go_default_library", "//pkg/registry/core/service/portallocator:go_default_library", diff --git a/pkg/registry/core/service/allocator/BUILD b/pkg/registry/core/service/allocator/BUILD index ef863f8c3a2..ff4bab7d02b 100644 --- a/pkg/registry/core/service/allocator/BUILD +++ b/pkg/registry/core/service/allocator/BUILD @@ -22,8 +22,8 @@ go_test( "bitmap_test.go", "utils_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/service/allocator", - library = ":go_default_library", deps = ["//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library"], ) diff --git a/pkg/registry/core/service/allocator/storage/BUILD b/pkg/registry/core/service/allocator/storage/BUILD index 18a6447916e..55ad4895e2c 100644 --- a/pkg/registry/core/service/allocator/storage/BUILD +++ b/pkg/registry/core/service/allocator/storage/BUILD @@ -9,10 +9,10 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/service/allocator/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/core/service/allocator:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/golang.org/x/net/context:go_default_library", @@ -26,7 +26,7 @@ go_library( srcs = ["storage.go"], importpath = "k8s.io/kubernetes/pkg/registry/core/service/allocator/storage", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/core/rangeallocation:go_default_library", "//pkg/registry/core/service/allocator:go_default_library", "//vendor/golang.org/x/net/context:go_default_library", @@ -34,6 +34,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/errors:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library", diff --git a/pkg/registry/core/service/allocator/storage/storage.go b/pkg/registry/core/service/allocator/storage/storage.go index 7c4d539718a..03f28431ada 100644 --- a/pkg/registry/core/service/allocator/storage/storage.go +++ b/pkg/registry/core/service/allocator/storage/storage.go @@ -25,10 +25,11 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/registry/generic" + "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/storage" storeerr "k8s.io/apiserver/pkg/storage/errors" "k8s.io/apiserver/pkg/storage/storagebackend" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/core/rangeallocation" "k8s.io/kubernetes/pkg/registry/core/service/allocator" @@ -61,7 +62,13 @@ var _ rangeallocation.RangeRegistry = &Etcd{} // NewEtcd returns an allocator that is backed by Etcd and can manage // persisting the snapshot state of allocation after each allocation is made. func NewEtcd(alloc allocator.Snapshottable, baseKey string, resource schema.GroupResource, config *storagebackend.Config) *Etcd { - storage, _ := generic.NewRawStorage(config) + storage, d := generic.NewRawStorage(config) + + // TODO : Remove RegisterStorageCleanup below when PR + // https://github.com/kubernetes/kubernetes/pull/50690 + // merges as that shuts down storage properly + registry.RegisterStorageCleanup(d) + return &Etcd{ alloc: alloc, storage: storage, diff --git a/pkg/registry/core/service/allocator/storage/storage_test.go b/pkg/registry/core/service/allocator/storage/storage_test.go index 4936b35f4db..a0f2cceb6a3 100644 --- a/pkg/registry/core/service/allocator/storage/storage_test.go +++ b/pkg/registry/core/service/allocator/storage/storage_test.go @@ -22,7 +22,7 @@ import ( etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" "k8s.io/apiserver/pkg/storage/storagebackend" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/core/service/allocator" "k8s.io/kubernetes/pkg/registry/registrytest" diff --git a/pkg/registry/core/service/ipallocator/BUILD b/pkg/registry/core/service/ipallocator/BUILD index 36e61632b74..eef62fec40b 100644 --- a/pkg/registry/core/service/ipallocator/BUILD +++ b/pkg/registry/core/service/ipallocator/BUILD @@ -11,7 +11,7 @@ go_library( srcs = ["allocator.go"], importpath = "k8s.io/kubernetes/pkg/registry/core/service/ipallocator", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/core/service/allocator:go_default_library", ], ) @@ -19,10 +19,10 @@ go_library( go_test( name = "go_default_test", srcs = ["allocator_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/service/ipallocator", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", ], ) diff --git a/pkg/registry/core/service/ipallocator/allocator.go b/pkg/registry/core/service/ipallocator/allocator.go index e8c28bf374b..b480418c2f3 100644 --- a/pkg/registry/core/service/ipallocator/allocator.go +++ b/pkg/registry/core/service/ipallocator/allocator.go @@ -19,11 +19,10 @@ package ipallocator import ( "errors" "fmt" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/registry/core/service/allocator" "math/big" "net" - - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/registry/core/service/allocator" ) // Interface manages the allocation of IP addresses out of a range. Interface diff --git a/pkg/registry/core/service/ipallocator/allocator_test.go b/pkg/registry/core/service/ipallocator/allocator_test.go index 481a4d41b60..fa29e4e1733 100644 --- a/pkg/registry/core/service/ipallocator/allocator_test.go +++ b/pkg/registry/core/service/ipallocator/allocator_test.go @@ -21,7 +21,7 @@ import ( "testing" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestAllocate(t *testing.T) { diff --git a/pkg/registry/core/service/ipallocator/controller/BUILD b/pkg/registry/core/service/ipallocator/controller/BUILD index a0723fa8ad5..9962ffb437e 100644 --- a/pkg/registry/core/service/ipallocator/controller/BUILD +++ b/pkg/registry/core/service/ipallocator/controller/BUILD @@ -11,15 +11,18 @@ go_library( srcs = ["repair.go"], importpath = "k8s.io/kubernetes/pkg/registry/core/service/ipallocator/controller", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library", "//pkg/registry/core/rangeallocation:go_default_library", "//pkg/registry/core/service/ipallocator:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/client-go/tools/record:go_default_library", "//vendor/k8s.io/client-go/util/retry:go_default_library", ], ) @@ -27,10 +30,10 @@ go_library( go_test( name = "go_default_test", srcs = ["repair_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/service/ipallocator/controller", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", "//pkg/registry/core/service/ipallocator:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/registry/core/service/ipallocator/controller/repair.go b/pkg/registry/core/service/ipallocator/controller/repair.go index 7e13115f1f0..b4aaf1c289c 100644 --- a/pkg/registry/core/service/ipallocator/controller/repair.go +++ b/pkg/registry/core/service/ipallocator/controller/repair.go @@ -21,13 +21,16 @@ import ( "net" "time" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/tools/record" "k8s.io/client-go/util/retry" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" "k8s.io/kubernetes/pkg/registry/core/rangeallocation" "k8s.io/kubernetes/pkg/registry/core/service/ipallocator" @@ -54,6 +57,7 @@ type Repair struct { network *net.IPNet alloc rangeallocation.RangeRegistry leaks map[string]int // counter per leaked IP + recorder record.EventRecorder } // How many times we need to detect a leak before we clean up. This is to @@ -62,13 +66,18 @@ const numRepairsBeforeLeakCleanup = 3 // NewRepair creates a controller that periodically ensures that all clusterIPs are uniquely allocated across the cluster // and generates informational warnings for a cluster that is not in sync. -func NewRepair(interval time.Duration, serviceClient coreclient.ServicesGetter, network *net.IPNet, alloc rangeallocation.RangeRegistry) *Repair { +func NewRepair(interval time.Duration, serviceClient coreclient.ServicesGetter, eventClient coreclient.EventsGetter, network *net.IPNet, alloc rangeallocation.RangeRegistry) *Repair { + eventBroadcaster := record.NewBroadcaster() + eventBroadcaster.StartRecordingToSink(&coreclient.EventSinkImpl{Interface: eventClient.Events("")}) + recorder := eventBroadcaster.NewRecorder(legacyscheme.Scheme, v1.EventSource{Component: "ipallocator-repair-controller"}) + return &Repair{ interval: interval, serviceClient: serviceClient, network: network, alloc: alloc, leaks: map[string]int{}, + recorder: recorder, } } @@ -136,6 +145,7 @@ func (c *Repair) runOnce() error { ip := net.ParseIP(svc.Spec.ClusterIP) if ip == nil { // cluster IP is corrupt + c.recorder.Eventf(&svc, v1.EventTypeWarning, "ClusterIPNotValid", "Cluster IP %s is not a valid IP; please recreate service", svc.Spec.ClusterIP) runtime.HandleError(fmt.Errorf("the cluster IP %s for service %s/%s is not a valid IP; please recreate", svc.Spec.ClusterIP, svc.Name, svc.Namespace)) continue } @@ -147,22 +157,24 @@ func (c *Repair) runOnce() error { stored.Release(ip) } else { // cluster IP doesn't seem to be allocated - runtime.HandleError(fmt.Errorf("the cluster IP %s for service %s/%s is not allocated; repairing", svc.Spec.ClusterIP, svc.Name, svc.Namespace)) + c.recorder.Eventf(&svc, v1.EventTypeWarning, "ClusterIPNotAllocated", "Cluster IP %s is not allocated; repairing", ip) + runtime.HandleError(fmt.Errorf("the cluster IP %s for service %s/%s is not allocated; repairing", ip, svc.Name, svc.Namespace)) } delete(c.leaks, ip.String()) // it is used, so it can't be leaked case ipallocator.ErrAllocated: - // TODO: send event // cluster IP is duplicate + c.recorder.Eventf(&svc, v1.EventTypeWarning, "ClusterIPAlreadyAllocated", "Cluster IP %s was assigned to multiple services; please recreate service", ip) runtime.HandleError(fmt.Errorf("the cluster IP %s for service %s/%s was assigned to multiple services; please recreate", ip, svc.Name, svc.Namespace)) case err.(*ipallocator.ErrNotInRange): - // TODO: send event // cluster IP is out of range + c.recorder.Eventf(&svc, v1.EventTypeWarning, "ClusterIPOutOfRange", "Cluster IP %s is not within the service CIDR %s; please recreate service", ip, c.network) runtime.HandleError(fmt.Errorf("the cluster IP %s for service %s/%s is not within the service CIDR %s; please recreate", ip, svc.Name, svc.Namespace, c.network)) case ipallocator.ErrFull: - // TODO: send event // somehow we are out of IPs - return fmt.Errorf("the service CIDR %v is full; you must widen the CIDR in order to create new services", rebuilt) + c.recorder.Eventf(&svc, v1.EventTypeWarning, "ServiceCIDRFull", "Service CIDR %s is full; you must widen the CIDR in order to create new services", c.network) + return fmt.Errorf("the service CIDR %s is full; you must widen the CIDR in order to create new services", c.network) default: + c.recorder.Eventf(&svc, v1.EventTypeWarning, "UnknownError", "Unable to allocate cluster IP %s due to an unknown error", ip) return fmt.Errorf("unable to allocate cluster IP %s for service %s/%s due to an unknown error, exiting: %v", ip, svc.Name, svc.Namespace, err) } } diff --git a/pkg/registry/core/service/ipallocator/controller/repair_test.go b/pkg/registry/core/service/ipallocator/controller/repair_test.go index c784f04cec9..0bbd6f5959c 100644 --- a/pkg/registry/core/service/ipallocator/controller/repair_test.go +++ b/pkg/registry/core/service/ipallocator/controller/repair_test.go @@ -23,7 +23,7 @@ import ( "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" "k8s.io/kubernetes/pkg/registry/core/service/ipallocator" ) @@ -55,7 +55,7 @@ func TestRepair(t *testing.T) { item: &api.RangeAllocation{Range: "192.168.1.0/24"}, } _, cidr, _ := net.ParseCIDR(ipregistry.item.Range) - r := NewRepair(0, fakeClient.Core(), cidr, ipregistry) + r := NewRepair(0, fakeClient.Core(), fakeClient.Core(), cidr, ipregistry) if err := r.RunOnce(); err != nil { t.Fatal(err) @@ -68,7 +68,7 @@ func TestRepair(t *testing.T) { item: &api.RangeAllocation{Range: "192.168.1.0/24"}, updateErr: fmt.Errorf("test error"), } - r = NewRepair(0, fakeClient.Core(), cidr, ipregistry) + r = NewRepair(0, fakeClient.Core(), fakeClient.Core(), cidr, ipregistry) if err := r.RunOnce(); !strings.Contains(err.Error(), ": test error") { t.Fatal(err) } @@ -96,7 +96,7 @@ func TestRepairLeak(t *testing.T) { }, } - r := NewRepair(0, fakeClient.Core(), cidr, ipregistry) + r := NewRepair(0, fakeClient.Core(), fakeClient.Core(), cidr, ipregistry) // Run through the "leak detection holdoff" loops. for i := 0; i < (numRepairsBeforeLeakCleanup - 1); i++ { if err := r.RunOnce(); err != nil { @@ -169,7 +169,7 @@ func TestRepairWithExisting(t *testing.T) { Data: dst.Data, }, } - r := NewRepair(0, fakeClient.Core(), cidr, ipregistry) + r := NewRepair(0, fakeClient.Core(), fakeClient.Core(), cidr, ipregistry) if err := r.RunOnce(); err != nil { t.Fatal(err) } diff --git a/pkg/registry/core/service/ipallocator/storage/BUILD b/pkg/registry/core/service/ipallocator/storage/BUILD index 303f8402cfc..76902d0e553 100644 --- a/pkg/registry/core/service/ipallocator/storage/BUILD +++ b/pkg/registry/core/service/ipallocator/storage/BUILD @@ -9,10 +9,10 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/service/ipallocator/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/core/service/allocator:go_default_library", "//pkg/registry/core/service/allocator/storage:go_default_library", "//pkg/registry/core/service/ipallocator:go_default_library", diff --git a/pkg/registry/core/service/ipallocator/storage/storage_test.go b/pkg/registry/core/service/ipallocator/storage/storage_test.go index cd4e34c3230..f70197d7298 100644 --- a/pkg/registry/core/service/ipallocator/storage/storage_test.go +++ b/pkg/registry/core/service/ipallocator/storage/storage_test.go @@ -25,7 +25,7 @@ import ( "k8s.io/apiserver/pkg/storage" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" "k8s.io/apiserver/pkg/storage/storagebackend/factory" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/core/service/allocator" allocatorstore "k8s.io/kubernetes/pkg/registry/core/service/allocator/storage" "k8s.io/kubernetes/pkg/registry/core/service/ipallocator" diff --git a/pkg/registry/core/service/portallocator/BUILD b/pkg/registry/core/service/portallocator/BUILD index fe22e770b02..e51791669ae 100644 --- a/pkg/registry/core/service/portallocator/BUILD +++ b/pkg/registry/core/service/portallocator/BUILD @@ -14,7 +14,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/core/service/portallocator", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/core/service/allocator:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", @@ -24,10 +24,10 @@ go_library( go_test( name = "go_default_test", srcs = ["allocator_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/service/portallocator", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", ], diff --git a/pkg/registry/core/service/portallocator/allocator.go b/pkg/registry/core/service/portallocator/allocator.go index c73253f2dcb..c31f4b045ea 100644 --- a/pkg/registry/core/service/portallocator/allocator.go +++ b/pkg/registry/core/service/portallocator/allocator.go @@ -21,7 +21,7 @@ import ( "fmt" "k8s.io/apimachinery/pkg/util/net" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/core/service/allocator" "github.com/golang/glog" diff --git a/pkg/registry/core/service/portallocator/allocator_test.go b/pkg/registry/core/service/portallocator/allocator_test.go index fb3ea1f4342..da393bc295e 100644 --- a/pkg/registry/core/service/portallocator/allocator_test.go +++ b/pkg/registry/core/service/portallocator/allocator_test.go @@ -23,7 +23,7 @@ import ( "k8s.io/apimachinery/pkg/util/net" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestAllocate(t *testing.T) { diff --git a/pkg/registry/core/service/portallocator/controller/BUILD b/pkg/registry/core/service/portallocator/controller/BUILD index 6054b2de9d2..39c66aefb7a 100644 --- a/pkg/registry/core/service/portallocator/controller/BUILD +++ b/pkg/registry/core/service/portallocator/controller/BUILD @@ -11,16 +11,19 @@ go_library( srcs = ["repair.go"], importpath = "k8s.io/kubernetes/pkg/registry/core/service/portallocator/controller", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library", "//pkg/registry/core/rangeallocation:go_default_library", "//pkg/registry/core/service:go_default_library", "//pkg/registry/core/service/portallocator:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/client-go/tools/record:go_default_library", "//vendor/k8s.io/client-go/util/retry:go_default_library", ], ) @@ -28,10 +31,10 @@ go_library( go_test( name = "go_default_test", srcs = ["repair_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/service/portallocator/controller", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", "//pkg/registry/core/service/portallocator:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/registry/core/service/portallocator/controller/repair.go b/pkg/registry/core/service/portallocator/controller/repair.go index 25de257036e..e7024ded053 100644 --- a/pkg/registry/core/service/portallocator/controller/repair.go +++ b/pkg/registry/core/service/portallocator/controller/repair.go @@ -20,13 +20,16 @@ import ( "fmt" "time" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/net" "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/tools/record" "k8s.io/client-go/util/retry" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" "k8s.io/kubernetes/pkg/registry/core/rangeallocation" "k8s.io/kubernetes/pkg/registry/core/service" @@ -40,6 +43,7 @@ type Repair struct { portRange net.PortRange alloc rangeallocation.RangeRegistry leaks map[int]int // counter per leaked port + recorder record.EventRecorder } // How many times we need to detect a leak before we clean up. This is to @@ -48,13 +52,18 @@ const numRepairsBeforeLeakCleanup = 3 // NewRepair creates a controller that periodically ensures that all ports are uniquely allocated across the cluster // and generates informational warnings for a cluster that is not in sync. -func NewRepair(interval time.Duration, serviceClient coreclient.ServicesGetter, portRange net.PortRange, alloc rangeallocation.RangeRegistry) *Repair { +func NewRepair(interval time.Duration, serviceClient coreclient.ServicesGetter, eventClient coreclient.EventsGetter, portRange net.PortRange, alloc rangeallocation.RangeRegistry) *Repair { + eventBroadcaster := record.NewBroadcaster() + eventBroadcaster.StartRecordingToSink(&coreclient.EventSinkImpl{Interface: eventClient.Events("")}) + recorder := eventBroadcaster.NewRecorder(legacyscheme.Scheme, v1.EventSource{Component: "portallocator-repair-controller"}) + return &Repair{ interval: interval, serviceClient: serviceClient, portRange: portRange, alloc: alloc, leaks: map[int]int{}, + recorder: recorder, } } @@ -130,22 +139,24 @@ func (c *Repair) runOnce() error { stored.Release(port) } else { // doesn't seem to be allocated + c.recorder.Eventf(svc, v1.EventTypeWarning, "PortNotAllocated", "Port %d is not allocated; repairing", port) runtime.HandleError(fmt.Errorf("the node port %d for service %s/%s is not allocated; repairing", port, svc.Name, svc.Namespace)) } delete(c.leaks, port) // it is used, so it can't be leaked case portallocator.ErrAllocated: - // TODO: send event // port is duplicate, reallocate + c.recorder.Eventf(svc, v1.EventTypeWarning, "PortAlreadyAllocated", "Port %d was assigned to multiple services; please recreate service", port) runtime.HandleError(fmt.Errorf("the node port %d for service %s/%s was assigned to multiple services; please recreate", port, svc.Name, svc.Namespace)) case err.(*portallocator.ErrNotInRange): - // TODO: send event // port is out of range, reallocate - runtime.HandleError(fmt.Errorf("the port %d for service %s/%s is not within the port range %v; please recreate", port, svc.Name, svc.Namespace, c.portRange)) + c.recorder.Eventf(svc, v1.EventTypeWarning, "PortOutOfRange", "Port %d is not within the port range %s; please recreate service", port, c.portRange) + runtime.HandleError(fmt.Errorf("the port %d for service %s/%s is not within the port range %s; please recreate", port, svc.Name, svc.Namespace, c.portRange)) case portallocator.ErrFull: - // TODO: send event // somehow we are out of ports - return fmt.Errorf("the port range %v is full; you must widen the port range in order to create new services", c.portRange) + c.recorder.Eventf(svc, v1.EventTypeWarning, "PortRangeFull", "Port range %s is full; you must widen the port range in order to create new services", c.portRange) + return fmt.Errorf("the port range %s is full; you must widen the port range in order to create new services", c.portRange) default: + c.recorder.Eventf(svc, v1.EventTypeWarning, "UnknownError", "Unable to allocate port %d due to an unknown error", port) return fmt.Errorf("unable to allocate port %d for service %s/%s due to an unknown error, exiting: %v", port, svc.Name, svc.Namespace, err) } } diff --git a/pkg/registry/core/service/portallocator/controller/repair_test.go b/pkg/registry/core/service/portallocator/controller/repair_test.go index beb5a4cfd32..151c791cc39 100644 --- a/pkg/registry/core/service/portallocator/controller/repair_test.go +++ b/pkg/registry/core/service/portallocator/controller/repair_test.go @@ -23,7 +23,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/net" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" "k8s.io/kubernetes/pkg/registry/core/service/portallocator" ) @@ -55,7 +55,7 @@ func TestRepair(t *testing.T) { item: &api.RangeAllocation{Range: "100-200"}, } pr, _ := net.ParsePortRange(registry.item.Range) - r := NewRepair(0, fakeClient.Core(), *pr, registry) + r := NewRepair(0, fakeClient.Core(), fakeClient.Core(), *pr, registry) if err := r.RunOnce(); err != nil { t.Fatal(err) @@ -68,7 +68,7 @@ func TestRepair(t *testing.T) { item: &api.RangeAllocation{Range: "100-200"}, updateErr: fmt.Errorf("test error"), } - r = NewRepair(0, fakeClient.Core(), *pr, registry) + r = NewRepair(0, fakeClient.Core(), fakeClient.Core(), *pr, registry) if err := r.RunOnce(); !strings.Contains(err.Error(), ": test error") { t.Fatal(err) } @@ -96,7 +96,7 @@ func TestRepairLeak(t *testing.T) { }, } - r := NewRepair(0, fakeClient.Core(), *pr, registry) + r := NewRepair(0, fakeClient.Core(), fakeClient.Core(), *pr, registry) // Run through the "leak detection holdoff" loops. for i := 0; i < (numRepairsBeforeLeakCleanup - 1); i++ { if err := r.RunOnce(); err != nil { @@ -175,7 +175,7 @@ func TestRepairWithExisting(t *testing.T) { Data: dst.Data, }, } - r := NewRepair(0, fakeClient.Core(), *pr, registry) + r := NewRepair(0, fakeClient.Core(), fakeClient.Core(), *pr, registry) if err := r.RunOnce(); err != nil { t.Fatal(err) } diff --git a/pkg/registry/core/service/proxy.go b/pkg/registry/core/service/proxy.go index 03074de9831..1956f276fb3 100644 --- a/pkg/registry/core/service/proxy.go +++ b/pkg/registry/core/service/proxy.go @@ -26,7 +26,7 @@ import ( "k8s.io/apimachinery/pkg/util/proxy" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/capabilities" ) diff --git a/pkg/registry/core/service/registry.go b/pkg/registry/core/service/registry.go index d63922f1e03..332d651e7dd 100644 --- a/pkg/registry/core/service/registry.go +++ b/pkg/registry/core/service/registry.go @@ -24,16 +24,16 @@ import ( "k8s.io/apimachinery/pkg/watch" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // Registry is an interface for things that know how to store services. type Registry interface { ListServices(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*api.ServiceList, error) - CreateService(ctx genericapirequest.Context, svc *api.Service) (*api.Service, error) + CreateService(ctx genericapirequest.Context, svc *api.Service, createValidation rest.ValidateObjectFunc) (*api.Service, error) GetService(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (*api.Service, error) DeleteService(ctx genericapirequest.Context, name string) error - UpdateService(ctx genericapirequest.Context, svc *api.Service) (*api.Service, error) + UpdateService(ctx genericapirequest.Context, svc *api.Service, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (*api.Service, error) WatchServices(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) ExportService(ctx genericapirequest.Context, name string, options metav1.ExportOptions) (*api.Service, error) } @@ -57,8 +57,8 @@ func (s *storage) ListServices(ctx genericapirequest.Context, options *metainter return obj.(*api.ServiceList), nil } -func (s *storage) CreateService(ctx genericapirequest.Context, svc *api.Service) (*api.Service, error) { - obj, err := s.Create(ctx, svc, false) +func (s *storage) CreateService(ctx genericapirequest.Context, svc *api.Service, createValidation rest.ValidateObjectFunc) (*api.Service, error) { + obj, err := s.Create(ctx, svc, createValidation, false) if err != nil { return nil, err } @@ -78,8 +78,8 @@ func (s *storage) DeleteService(ctx genericapirequest.Context, name string) erro return err } -func (s *storage) UpdateService(ctx genericapirequest.Context, svc *api.Service) (*api.Service, error) { - obj, _, err := s.Update(ctx, svc.Name, rest.DefaultUpdatedObjectInfo(svc)) +func (s *storage) UpdateService(ctx genericapirequest.Context, svc *api.Service, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (*api.Service, error) { + obj, _, err := s.Update(ctx, svc.Name, rest.DefaultUpdatedObjectInfo(svc), createValidation, updateValidation) if err != nil { return nil, err } diff --git a/pkg/registry/core/service/rest.go b/pkg/registry/core/service/rest.go index 3a353c34495..689850c1b86 100644 --- a/pkg/registry/core/service/rest.go +++ b/pkg/registry/core/service/rest.go @@ -36,10 +36,10 @@ import ( genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper" apiservice "k8s.io/kubernetes/pkg/api/service" - "k8s.io/kubernetes/pkg/api/validation" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" + "k8s.io/kubernetes/pkg/apis/core/validation" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/registry/core/endpoint" "k8s.io/kubernetes/pkg/registry/core/service/ipallocator" @@ -61,6 +61,16 @@ type REST struct { proxyTransport http.RoundTripper } +// ServiceNodePort includes protocol and port number of a service NodePort. +type ServiceNodePort struct { + // The IP protocol for this port. Supports "TCP" and "UDP". + Protocol api.Protocol + + // The port on each node on which this service is exposed. + // Default is to auto-allocate a port if the ServiceType of this Service requires one. + NodePort int32 +} + // NewStorage returns a new REST. func NewStorage(registry Registry, endpoints endpoint.Registry, serviceIPs ipallocator.Interface, serviceNodePorts portallocator.Interface, proxyTransport http.RoundTripper) *ServiceRest { @@ -88,7 +98,7 @@ func (rs *REST) Categories() []string { } // TODO: implement includeUninitialized by refactoring this to move to store -func (rs *REST) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { +func (rs *REST) Create(ctx genericapirequest.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) { service := obj.(*api.Service) if err := rest.BeforeCreate(Strategy, ctx, obj); err != nil { @@ -133,7 +143,7 @@ func (rs *REST) Create(ctx genericapirequest.Context, obj runtime.Object, includ } } - out, err := rs.registry.CreateService(ctx, service) + out, err := rs.registry.CreateService(ctx, service, createValidation) if err != nil { err = rest.CheckGeneratedNameError(Strategy, err, service) } @@ -284,7 +294,7 @@ func (rs *REST) healthCheckNodePortUpdate(oldService, service *api.Service, node return true, nil } -func (rs *REST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { +func (rs *REST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { oldService, err := rs.registry.GetService(ctx, name, &metav1.GetOptions{}) if err != nil { return nil, false, err @@ -360,7 +370,7 @@ func (rs *REST) Update(ctx genericapirequest.Context, name string, objInfo rest. } } - out, err := rs.registry.UpdateService(ctx, service) + out, err := rs.registry.UpdateService(ctx, service, createValidation, updateValidation) if err == nil { el := nodePortOp.Commit() if el != nil { @@ -437,7 +447,7 @@ func (rs *REST) ResourceLocation(ctx genericapirequest.Context, id string) (*url // This is O(N), but we expect haystack to be small; // so small that we expect a linear search to be faster -func contains(haystack []int, needle int) bool { +func containsNumber(haystack []int, needle int) bool { for _, v := range haystack { if v == needle { return true @@ -446,6 +456,17 @@ func contains(haystack []int, needle int) bool { return false } +// This is O(N), but we expect serviceNodePorts to be small; +// so small that we expect a linear search to be faster +func containsNodePort(serviceNodePorts []ServiceNodePort, serviceNodePort ServiceNodePort) bool { + for _, snp := range serviceNodePorts { + if snp == serviceNodePort { + return true + } + } + return false +} + func CollectServiceNodePorts(service *api.Service) []int { servicePorts := []int{} for i := range service.Spec.Ports { @@ -569,44 +590,48 @@ func (rs *REST) initNodePorts(service *api.Service, nodePortOp *portallocator.Po } func (rs *REST) updateNodePorts(oldService, newService *api.Service, nodePortOp *portallocator.PortAllocationOperation) error { - oldNodePorts := CollectServiceNodePorts(oldService) + oldNodePortsNumbers := CollectServiceNodePorts(oldService) + newNodePorts := []ServiceNodePort{} + portAllocated := map[int]bool{} - newNodePorts := []int{} for i := range newService.Spec.Ports { servicePort := &newService.Spec.Ports[i] - nodePort := int(servicePort.NodePort) - if nodePort != 0 { - if !contains(oldNodePorts, nodePort) { - err := nodePortOp.Allocate(nodePort) + nodePort := ServiceNodePort{Protocol: servicePort.Protocol, NodePort: servicePort.NodePort} + if nodePort.NodePort != 0 { + if !containsNumber(oldNodePortsNumbers, int(nodePort.NodePort)) && !portAllocated[int(nodePort.NodePort)] { + err := nodePortOp.Allocate(int(nodePort.NodePort)) if err != nil { - el := field.ErrorList{field.Invalid(field.NewPath("spec", "ports").Index(i).Child("nodePort"), nodePort, err.Error())} + el := field.ErrorList{field.Invalid(field.NewPath("spec", "ports").Index(i).Child("nodePort"), nodePort.NodePort, err.Error())} return errors.NewInvalid(api.Kind("Service"), newService.Name, el) } + portAllocated[int(nodePort.NodePort)] = true } } else { - nodePort, err := nodePortOp.AllocateNext() + nodePortNumber, err := nodePortOp.AllocateNext() if err != nil { // TODO: what error should be returned here? It's not a // field-level validation failure (the field is valid), and it's // not really an internal error. return errors.NewInternalError(fmt.Errorf("failed to allocate a nodePort: %v", err)) } - servicePort.NodePort = int32(nodePort) + servicePort.NodePort = int32(nodePortNumber) + nodePort.NodePort = servicePort.NodePort } - // Detect duplicate node ports; this should have been caught by validation, so we panic - if contains(newNodePorts, nodePort) { - panic("duplicate node port") + if containsNodePort(newNodePorts, nodePort) { + return fmt.Errorf("duplicate nodePort: %v", nodePort) } newNodePorts = append(newNodePorts, nodePort) } + newNodePortsNumbers := CollectServiceNodePorts(newService) + // The comparison loops are O(N^2), but we don't expect N to be huge // (there's a hard-limit at 2^16, because they're ports; and even 4 ports would be a lot) - for _, oldNodePort := range oldNodePorts { - if contains(newNodePorts, oldNodePort) { + for _, oldNodePortNumber := range oldNodePortsNumbers { + if containsNumber(newNodePortsNumbers, oldNodePortNumber) { continue } - nodePortOp.ReleaseDeferred(oldNodePort) + nodePortOp.ReleaseDeferred(int(oldNodePortNumber)) } return nil diff --git a/pkg/registry/core/service/rest_test.go b/pkg/registry/core/service/rest_test.go index 810c026b38a..edc1a334d44 100644 --- a/pkg/registry/core/service/rest_test.go +++ b/pkg/registry/core/service/rest_test.go @@ -32,9 +32,9 @@ import ( genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper" "k8s.io/kubernetes/pkg/api/service" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/registry/core/service/ipallocator" "k8s.io/kubernetes/pkg/registry/core/service/portallocator" @@ -111,7 +111,7 @@ func TestServiceRegistryCreate(t *testing.T) { }, } ctx := genericapirequest.NewDefaultContext() - created_svc, err := storage.Create(ctx, svc, false) + created_svc, err := storage.Create(ctx, svc, rest.ValidateAllObjectFunc, false) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -235,7 +235,7 @@ func TestServiceRegistryCreateMultiNodePortsService(t *testing.T) { ctx := genericapirequest.NewDefaultContext() for _, test := range testCases { - created_svc, err := storage.Create(ctx, test.svc, false) + created_svc, err := storage.Create(ctx, test.svc, rest.ValidateAllObjectFunc, false) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -311,7 +311,7 @@ func TestServiceStorageValidatesCreate(t *testing.T) { } ctx := genericapirequest.NewDefaultContext() for _, failureCase := range failureCases { - c, err := storage.Create(ctx, &failureCase, false) + c, err := storage.Create(ctx, &failureCase, rest.ValidateAllObjectFunc, false) if c != nil { t.Errorf("Expected nil object") } @@ -334,7 +334,7 @@ func TestServiceRegistryUpdate(t *testing.T) { TargetPort: intstr.FromInt(6502), }}, }, - }) + }, rest.ValidateAllObjectFunc) if err != nil { t.Fatalf("Expected no error: %v", err) @@ -353,7 +353,7 @@ func TestServiceRegistryUpdate(t *testing.T) { TargetPort: intstr.FromInt(6502), }}, }, - })) + }), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc) if err != nil { t.Fatalf("Expected no error: %v", err) } @@ -384,7 +384,7 @@ func TestServiceStorageValidatesUpdate(t *testing.T) { Protocol: api.ProtocolTCP, }}, }, - }) + }, rest.ValidateAllObjectFunc) failureCases := map[string]api.Service{ "empty ID": { ObjectMeta: metav1.ObjectMeta{Name: ""}, @@ -414,7 +414,7 @@ func TestServiceStorageValidatesUpdate(t *testing.T) { }, } for _, failureCase := range failureCases { - c, created, err := storage.Update(ctx, failureCase.Name, rest.DefaultUpdatedObjectInfo(&failureCase)) + c, created, err := storage.Update(ctx, failureCase.Name, rest.DefaultUpdatedObjectInfo(&failureCase), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc) if c != nil || created { t.Errorf("Expected nil object or created false") } @@ -440,7 +440,7 @@ func TestServiceRegistryExternalService(t *testing.T) { }}, }, } - _, err := storage.Create(ctx, svc, false) + _, err := storage.Create(ctx, svc, rest.ValidateAllObjectFunc, false) if err != nil { t.Errorf("Failed to create service: %#v", err) } @@ -477,7 +477,7 @@ func TestServiceRegistryDelete(t *testing.T) { }}, }, } - registry.CreateService(ctx, svc) + registry.CreateService(ctx, svc, rest.ValidateAllObjectFunc) storage.Delete(ctx, svc.Name) if e, a := "foo", registry.DeletedID; e != a { t.Errorf("Expected %v, but got %v", e, a) @@ -499,7 +499,7 @@ func TestServiceRegistryDeleteExternal(t *testing.T) { }}, }, } - registry.CreateService(ctx, svc) + registry.CreateService(ctx, svc, rest.ValidateAllObjectFunc) storage.Delete(ctx, svc.Name) if e, a := "foo", registry.DeletedID; e != a { t.Errorf("Expected %v, but got %v", e, a) @@ -524,14 +524,14 @@ func TestServiceRegistryUpdateExternalService(t *testing.T) { }}, }, } - if _, err := storage.Create(ctx, svc1, false); err != nil { + if _, err := storage.Create(ctx, svc1, rest.ValidateAllObjectFunc, false); err != nil { t.Fatalf("Unexpected error: %v", err) } // Modify load balancer to be external. svc2 := svc1.DeepCopy() svc2.Spec.Type = api.ServiceTypeLoadBalancer - if _, _, err := storage.Update(ctx, svc2.Name, rest.DefaultUpdatedObjectInfo(svc2)); err != nil { + if _, _, err := storage.Update(ctx, svc2.Name, rest.DefaultUpdatedObjectInfo(svc2), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc); err != nil { t.Fatalf("Unexpected error: %v", err) } defer releaseServiceNodePorts(t, ctx, svc2.Name, storage, registry) @@ -539,7 +539,7 @@ func TestServiceRegistryUpdateExternalService(t *testing.T) { // Change port. svc3 := svc2.DeepCopy() svc3.Spec.Ports[0].Port = 6504 - if _, _, err := storage.Update(ctx, svc3.Name, rest.DefaultUpdatedObjectInfo(svc3)); err != nil { + if _, _, err := storage.Update(ctx, svc3.Name, rest.DefaultUpdatedObjectInfo(svc3), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc); err != nil { t.Fatalf("Unexpected error: %v", err) } } @@ -568,7 +568,7 @@ func TestServiceRegistryUpdateMultiPortExternalService(t *testing.T) { }}, }, } - if _, err := storage.Create(ctx, svc1, false); err != nil { + if _, err := storage.Create(ctx, svc1, rest.ValidateAllObjectFunc, false); err != nil { t.Fatalf("Unexpected error: %v", err) } defer releaseServiceNodePorts(t, ctx, svc1.Name, storage, registry) @@ -576,7 +576,7 @@ func TestServiceRegistryUpdateMultiPortExternalService(t *testing.T) { // Modify ports svc2 := svc1.DeepCopy() svc2.Spec.Ports[1].Port = 8088 - if _, _, err := storage.Update(ctx, svc2.Name, rest.DefaultUpdatedObjectInfo(svc2)); err != nil { + if _, _, err := storage.Update(ctx, svc2.Name, rest.DefaultUpdatedObjectInfo(svc2), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc); err != nil { t.Fatalf("Unexpected error: %v", err) } } @@ -589,7 +589,7 @@ func TestServiceRegistryGet(t *testing.T) { Spec: api.ServiceSpec{ Selector: map[string]string{"bar": "baz"}, }, - }) + }, rest.ValidateAllObjectFunc) storage.Get(ctx, "foo", &metav1.GetOptions{}) if e, a := "foo", registry.GottenID; e != a { t.Errorf("Expected %v, but got %v", e, a) @@ -642,7 +642,7 @@ func TestServiceRegistryResourceLocation(t *testing.T) { {Name: "", Port: 93, TargetPort: intstr.FromInt(80)}, }, }, - }) + }, rest.ValidateAllObjectFunc) redirector := rest.Redirector(storage) // Test a simple id. @@ -725,13 +725,13 @@ func TestServiceRegistryList(t *testing.T) { Spec: api.ServiceSpec{ Selector: map[string]string{"bar": "baz"}, }, - }) + }, rest.ValidateAllObjectFunc) registry.CreateService(ctx, &api.Service{ ObjectMeta: metav1.ObjectMeta{Name: "foo2", Namespace: metav1.NamespaceDefault}, Spec: api.ServiceSpec{ Selector: map[string]string{"bar2": "baz2"}, }, - }) + }, rest.ValidateAllObjectFunc) registry.List.ResourceVersion = "1" s, _ := storage.List(ctx, nil) sl := s.(*api.ServiceList) @@ -766,7 +766,7 @@ func TestServiceRegistryIPAllocation(t *testing.T) { }, } ctx := genericapirequest.NewDefaultContext() - created_svc1, _ := storage.Create(ctx, svc1, false) + created_svc1, _ := storage.Create(ctx, svc1, rest.ValidateAllObjectFunc, false) created_service_1 := created_svc1.(*api.Service) if created_service_1.Name != "foo" { t.Errorf("Expected foo, but got %v", created_service_1.Name) @@ -788,7 +788,7 @@ func TestServiceRegistryIPAllocation(t *testing.T) { }}, }} ctx = genericapirequest.NewDefaultContext() - created_svc2, _ := storage.Create(ctx, svc2, false) + created_svc2, _ := storage.Create(ctx, svc2, rest.ValidateAllObjectFunc, false) created_service_2 := created_svc2.(*api.Service) if created_service_2.Name != "bar" { t.Errorf("Expected bar, but got %v", created_service_2.Name) @@ -821,7 +821,7 @@ func TestServiceRegistryIPAllocation(t *testing.T) { }, } ctx = genericapirequest.NewDefaultContext() - created_svc3, err := storage.Create(ctx, svc3, false) + created_svc3, err := storage.Create(ctx, svc3, rest.ValidateAllObjectFunc, false) if err != nil { t.Fatal(err) } @@ -848,7 +848,7 @@ func TestServiceRegistryIPReallocation(t *testing.T) { }, } ctx := genericapirequest.NewDefaultContext() - created_svc1, _ := storage.Create(ctx, svc1, false) + created_svc1, _ := storage.Create(ctx, svc1, rest.ValidateAllObjectFunc, false) created_service_1 := created_svc1.(*api.Service) if created_service_1.Name != "foo" { t.Errorf("Expected foo, but got %v", created_service_1.Name) @@ -876,7 +876,7 @@ func TestServiceRegistryIPReallocation(t *testing.T) { }, } ctx = genericapirequest.NewDefaultContext() - created_svc2, _ := storage.Create(ctx, svc2, false) + created_svc2, _ := storage.Create(ctx, svc2, rest.ValidateAllObjectFunc, false) created_service_2 := created_svc2.(*api.Service) if created_service_2.Name != "bar" { t.Errorf("Expected bar, but got %v", created_service_2.Name) @@ -903,7 +903,7 @@ func TestServiceRegistryIPUpdate(t *testing.T) { }, } ctx := genericapirequest.NewDefaultContext() - created_svc, _ := storage.Create(ctx, svc, false) + created_svc, _ := storage.Create(ctx, svc, rest.ValidateAllObjectFunc, false) created_service := created_svc.(*api.Service) if created_service.Spec.Ports[0].Port != 6502 { t.Errorf("Expected port 6502, but got %v", created_service.Spec.Ports[0].Port) @@ -915,7 +915,7 @@ func TestServiceRegistryIPUpdate(t *testing.T) { update := created_service.DeepCopy() update.Spec.Ports[0].Port = 6503 - updated_svc, _, _ := storage.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(update)) + updated_svc, _, _ := storage.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc) updated_service := updated_svc.(*api.Service) if updated_service.Spec.Ports[0].Port != 6503 { t.Errorf("Expected port 6503, but got %v", updated_service.Spec.Ports[0].Port) @@ -934,7 +934,7 @@ func TestServiceRegistryIPUpdate(t *testing.T) { update.Spec.Ports[0].Port = 6503 update.Spec.ClusterIP = testIP // Error: Cluster IP is immutable - _, _, err := storage.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(update)) + _, _, err := storage.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc) if err == nil || !errors.IsInvalid(err) { t.Errorf("Unexpected error type: %v", err) } @@ -957,7 +957,7 @@ func TestServiceRegistryIPLoadBalancer(t *testing.T) { }, } ctx := genericapirequest.NewDefaultContext() - created_svc, err := storage.Create(ctx, svc, false) + created_svc, err := storage.Create(ctx, svc, rest.ValidateAllObjectFunc, false) if created_svc == nil || err != nil { t.Errorf("Unexpected failure creating service %v", err) } @@ -973,7 +973,7 @@ func TestServiceRegistryIPLoadBalancer(t *testing.T) { update := created_service.DeepCopy() - _, _, err = storage.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(update)) + _, _, err = storage.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc) if err != nil { t.Errorf("Unexpected error %v", err) } @@ -986,7 +986,7 @@ func TestUpdateServiceWithConflictingNamespace(t *testing.T) { } ctx := genericapirequest.NewDefaultContext() - obj, created, err := storage.Update(ctx, service.Name, rest.DefaultUpdatedObjectInfo(service)) + obj, created, err := storage.Update(ctx, service.Name, rest.DefaultUpdatedObjectInfo(service), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc) if obj != nil || created { t.Error("Expected a nil object, but we got a value or created was true") } @@ -1016,7 +1016,7 @@ func TestServiceRegistryExternalTrafficHealthCheckNodePortAllocation(t *testing. ExternalTrafficPolicy: api.ServiceExternalTrafficPolicyTypeLocal, }, } - created_svc, err := storage.Create(ctx, svc, false) + created_svc, err := storage.Create(ctx, svc, rest.ValidateAllObjectFunc, false) if created_svc == nil || err != nil { t.Errorf("Unexpected failure creating service %v", err) } @@ -1056,7 +1056,7 @@ func TestServiceRegistryExternalTrafficHealthCheckNodePortUserAllocation(t *test HealthCheckNodePort: randomNodePort, }, } - created_svc, err := storage.Create(ctx, svc, false) + created_svc, err := storage.Create(ctx, svc, rest.ValidateAllObjectFunc, false) if created_svc == nil || err != nil { t.Fatalf("Unexpected failure creating service :%v", err) } @@ -1098,7 +1098,7 @@ func TestServiceRegistryExternalTrafficHealthCheckNodePortNegative(t *testing.T) HealthCheckNodePort: int32(-1), }, } - created_svc, err := storage.Create(ctx, svc, false) + created_svc, err := storage.Create(ctx, svc, rest.ValidateAllObjectFunc, false) if created_svc == nil || err != nil { return } @@ -1123,7 +1123,7 @@ func TestServiceRegistryExternalTrafficGlobal(t *testing.T) { ExternalTrafficPolicy: api.ServiceExternalTrafficPolicyTypeCluster, }, } - created_svc, err := storage.Create(ctx, svc, false) + created_svc, err := storage.Create(ctx, svc, rest.ValidateAllObjectFunc, false) if created_svc == nil || err != nil { t.Errorf("Unexpected failure creating service %v", err) } @@ -1570,6 +1570,92 @@ func TestUpdateNodePorts(t *testing.T) { }, expectSpecifiedNodePorts: []int{}, }, + { + name: "Add new ServicePort with a different protocol without changing port numbers", + oldService: &api.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + SessionAffinity: api.ServiceAffinityNone, + Type: api.ServiceTypeNodePort, + Ports: []api.ServicePort{ + { + Name: "port-tcp", + Port: 53, + TargetPort: intstr.FromInt(6502), + Protocol: api.ProtocolTCP, + NodePort: 30053, + }, + }, + }, + }, + newService: &api.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + SessionAffinity: api.ServiceAffinityNone, + Type: api.ServiceTypeNodePort, + Ports: []api.ServicePort{ + { + Name: "port-tcp", + Port: 53, + TargetPort: intstr.FromInt(6502), + Protocol: api.ProtocolTCP, + NodePort: 30053, + }, + { + Name: "port-udp", + Port: 53, + TargetPort: intstr.FromInt(6502), + Protocol: api.ProtocolUDP, + NodePort: 30053, + }, + }, + }, + }, + expectSpecifiedNodePorts: []int{30053, 30053}, + }, + { + name: "Change service type from ClusterIP to NodePort with same NodePort number but different protocols", + oldService: &api.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + SessionAffinity: api.ServiceAffinityNone, + Type: api.ServiceTypeClusterIP, + Ports: []api.ServicePort{{ + Port: 53, + Protocol: api.ProtocolTCP, + TargetPort: intstr.FromInt(6502), + }}, + }, + }, + newService: &api.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + SessionAffinity: api.ServiceAffinityNone, + Type: api.ServiceTypeNodePort, + Ports: []api.ServicePort{ + { + Name: "port-tcp", + Port: 53, + TargetPort: intstr.FromInt(6502), + Protocol: api.ProtocolTCP, + NodePort: 30053, + }, + { + Name: "port-udp", + Port: 53, + TargetPort: intstr.FromInt(6502), + Protocol: api.ProtocolUDP, + NodePort: 30053, + }, + }, + }, + }, + expectSpecifiedNodePorts: []int{30053, 30053}, + }, } for _, test := range testCases { diff --git a/pkg/registry/core/service/storage/BUILD b/pkg/registry/core/service/storage/BUILD index 4afd2f4b0bd..97af1d4f0be 100644 --- a/pkg/registry/core/service/storage/BUILD +++ b/pkg/registry/core/service/storage/BUILD @@ -9,10 +9,10 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/service/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", @@ -20,6 +20,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], ) @@ -29,7 +30,7 @@ go_library( srcs = ["storage.go"], importpath = "k8s.io/kubernetes/pkg/registry/core/service/storage", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/printers:go_default_library", "//pkg/printers/internalversion:go_default_library", "//pkg/printers/storage:go_default_library", diff --git a/pkg/registry/core/service/storage/storage.go b/pkg/registry/core/service/storage/storage.go index aaa0b52f601..0d492b7e881 100644 --- a/pkg/registry/core/service/storage/storage.go +++ b/pkg/registry/core/service/storage/storage.go @@ -23,7 +23,7 @@ import ( "k8s.io/apiserver/pkg/registry/generic" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/printers" printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" printerstorage "k8s.io/kubernetes/pkg/printers/storage" @@ -89,6 +89,6 @@ func (r *StatusREST) Get(ctx genericapirequest.Context, name string, options *me } // Update alters the status subset of an object. -func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - return r.store.Update(ctx, name, objInfo) +func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation) } diff --git a/pkg/registry/core/service/storage/storage_test.go b/pkg/registry/core/service/storage/storage_test.go index 9e2fab55e8c..5cfe71d7cc1 100644 --- a/pkg/registry/core/service/storage/storage_test.go +++ b/pkg/registry/core/service/storage/storage_test.go @@ -25,8 +25,9 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -66,7 +67,7 @@ func TestCreate(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) validService := validService() validService.ObjectMeta = metav1.ObjectMeta{} test.TestCreate( @@ -97,7 +98,7 @@ func TestUpdate(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).AllowCreateOnUpdate() + test := genericregistrytest.New(t, storage.Store).AllowCreateOnUpdate() test.TestUpdate( // valid validService(), @@ -124,7 +125,7 @@ func TestDelete(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).AllowCreateOnUpdate() + test := genericregistrytest.New(t, storage.Store).AllowCreateOnUpdate() test.TestDelete(validService()) } @@ -132,7 +133,7 @@ func TestGet(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).AllowCreateOnUpdate() + test := genericregistrytest.New(t, storage.Store).AllowCreateOnUpdate() test.TestGet(validService()) } @@ -140,7 +141,7 @@ func TestList(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).AllowCreateOnUpdate() + test := genericregistrytest.New(t, storage.Store).AllowCreateOnUpdate() test.TestList(validService()) } @@ -148,7 +149,7 @@ func TestWatch(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestWatch( validService(), // matching labels diff --git a/pkg/registry/core/service/strategy.go b/pkg/registry/core/service/strategy.go index e2bf47a545d..278da88ed6b 100644 --- a/pkg/registry/core/service/strategy.go +++ b/pkg/registry/core/service/strategy.go @@ -23,8 +23,9 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/validation" ) // svcStrategy implements behavior for Services @@ -35,7 +36,7 @@ type svcStrategy struct { // Services is the default logic that applies when creating and updating Service // objects. -var Strategy = svcStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = svcStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} // NamespaceScoped is true for services. func (svcStrategy) NamespaceScoped() bool { diff --git a/pkg/registry/core/service/strategy_test.go b/pkg/registry/core/service/strategy_test.go index 903af1ec03b..e5b4b4cc5d8 100644 --- a/pkg/registry/core/service/strategy_test.go +++ b/pkg/registry/core/service/strategy_test.go @@ -26,7 +26,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestExportService(t *testing.T) { diff --git a/pkg/registry/core/serviceaccount/BUILD b/pkg/registry/core/serviceaccount/BUILD index 3476ca58336..956de50f0d5 100644 --- a/pkg/registry/core/serviceaccount/BUILD +++ b/pkg/registry/core/serviceaccount/BUILD @@ -14,8 +14,9 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/core/serviceaccount", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/pkg/registry/core/serviceaccount/registry.go b/pkg/registry/core/serviceaccount/registry.go index 7979b29ac02..8d13600577f 100644 --- a/pkg/registry/core/serviceaccount/registry.go +++ b/pkg/registry/core/serviceaccount/registry.go @@ -22,7 +22,7 @@ import ( "k8s.io/apimachinery/pkg/watch" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // Registry is an interface implemented by things that know how to store ServiceAccount objects. @@ -30,8 +30,8 @@ type Registry interface { ListServiceAccounts(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*api.ServiceAccountList, error) WatchServiceAccounts(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) GetServiceAccount(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (*api.ServiceAccount, error) - CreateServiceAccount(ctx genericapirequest.Context, ServiceAccount *api.ServiceAccount) error - UpdateServiceAccount(ctx genericapirequest.Context, ServiceAccount *api.ServiceAccount) error + CreateServiceAccount(ctx genericapirequest.Context, ServiceAccount *api.ServiceAccount, createValidation rest.ValidateObjectFunc) error + UpdateServiceAccount(ctx genericapirequest.Context, ServiceAccount *api.ServiceAccount, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error DeleteServiceAccount(ctx genericapirequest.Context, name string) error } @@ -66,13 +66,13 @@ func (s *storage) GetServiceAccount(ctx genericapirequest.Context, name string, return obj.(*api.ServiceAccount), nil } -func (s *storage) CreateServiceAccount(ctx genericapirequest.Context, serviceAccount *api.ServiceAccount) error { - _, err := s.Create(ctx, serviceAccount, false) +func (s *storage) CreateServiceAccount(ctx genericapirequest.Context, serviceAccount *api.ServiceAccount, createValidation rest.ValidateObjectFunc) error { + _, err := s.Create(ctx, serviceAccount, createValidation, false) return err } -func (s *storage) UpdateServiceAccount(ctx genericapirequest.Context, serviceAccount *api.ServiceAccount) error { - _, _, err := s.Update(ctx, serviceAccount.Name, rest.DefaultUpdatedObjectInfo(serviceAccount)) +func (s *storage) UpdateServiceAccount(ctx genericapirequest.Context, serviceAccount *api.ServiceAccount, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error { + _, _, err := s.Update(ctx, serviceAccount.Name, rest.DefaultUpdatedObjectInfo(serviceAccount), createValidation, updateValidation) return err } diff --git a/pkg/registry/core/serviceaccount/storage/BUILD b/pkg/registry/core/serviceaccount/storage/BUILD index bd122cffa42..002a862e8f7 100644 --- a/pkg/registry/core/serviceaccount/storage/BUILD +++ b/pkg/registry/core/serviceaccount/storage/BUILD @@ -9,16 +9,17 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/core/serviceaccount/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], ) @@ -28,7 +29,7 @@ go_library( srcs = ["storage.go"], importpath = "k8s.io/kubernetes/pkg/registry/core/serviceaccount/storage", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/core/serviceaccount:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", diff --git a/pkg/registry/core/serviceaccount/storage/storage.go b/pkg/registry/core/serviceaccount/storage/storage.go index 47d76413350..1b889374add 100644 --- a/pkg/registry/core/serviceaccount/storage/storage.go +++ b/pkg/registry/core/serviceaccount/storage/storage.go @@ -21,7 +21,7 @@ import ( "k8s.io/apiserver/pkg/registry/generic" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/core/serviceaccount" ) diff --git a/pkg/registry/core/serviceaccount/storage/storage_test.go b/pkg/registry/core/serviceaccount/storage/storage_test.go index 2d96c74f78b..ef42b7191a4 100644 --- a/pkg/registry/core/serviceaccount/storage/storage_test.go +++ b/pkg/registry/core/serviceaccount/storage/storage_test.go @@ -24,8 +24,9 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -54,7 +55,7 @@ func TestCreate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) serviceAccount := validNewServiceAccount("foo") serviceAccount.ObjectMeta = metav1.ObjectMeta{GenerateName: "foo-"} test.TestCreate( @@ -72,7 +73,7 @@ func TestUpdate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestUpdate( // valid validNewServiceAccount("foo"), @@ -89,7 +90,7 @@ func TestDelete(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ReturnDeletedObject() + test := genericregistrytest.New(t, storage.Store).ReturnDeletedObject() test.TestDelete(validNewServiceAccount("foo")) } @@ -97,7 +98,7 @@ func TestGet(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestGet(validNewServiceAccount("foo")) } @@ -105,7 +106,7 @@ func TestList(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestList(validNewServiceAccount("foo")) } @@ -113,7 +114,7 @@ func TestWatch(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestWatch( validNewServiceAccount("foo"), // matching labels diff --git a/pkg/registry/core/serviceaccount/strategy.go b/pkg/registry/core/serviceaccount/strategy.go index 501e0436189..8a6a372070e 100644 --- a/pkg/registry/core/serviceaccount/strategy.go +++ b/pkg/registry/core/serviceaccount/strategy.go @@ -21,8 +21,9 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/validation" ) // strategy implements behavior for ServiceAccount objects @@ -33,7 +34,7 @@ type strategy struct { // Strategy is the default logic that applies when creating and updating ServiceAccount // objects via the REST API. -var Strategy = strategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = strategy{legacyscheme.Scheme, names.SimpleNameGenerator} func (strategy) NamespaceScoped() bool { return true diff --git a/pkg/registry/events/OWNERS b/pkg/registry/events/OWNERS new file mode 100644 index 00000000000..d9ecd88564b --- /dev/null +++ b/pkg/registry/events/OWNERS @@ -0,0 +1,8 @@ +reviewers: +- gmarek +- deads2k +- sttts +approvers: +- gmarek +- deads2k +- sttts diff --git a/pkg/registry/events/event/BUILD b/pkg/registry/events/event/BUILD new file mode 100644 index 00000000000..6f69eb73996 --- /dev/null +++ b/pkg/registry/events/event/BUILD @@ -0,0 +1,38 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "strategy.go", + ], + importpath = "k8s.io/kubernetes/pkg/registry/events/event", + visibility = ["//visibility:public"], + deps = [ + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/validation:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/storage:go_default_library", + "//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/registry/events/event/doc.go b/pkg/registry/events/event/doc.go new file mode 100644 index 00000000000..90cedb7aa99 --- /dev/null +++ b/pkg/registry/events/event/doc.go @@ -0,0 +1,17 @@ +/* +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 event // import "k8s.io/kubernetes/pkg/registry/events/event" diff --git a/pkg/registry/events/event/strategy.go b/pkg/registry/events/event/strategy.go new file mode 100644 index 00000000000..f2958c4b17f --- /dev/null +++ b/pkg/registry/events/event/strategy.go @@ -0,0 +1,104 @@ +/* +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 event + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/generic" + apistorage "k8s.io/apiserver/pkg/storage" + "k8s.io/apiserver/pkg/storage/names" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/validation" +) + +// eventStrategy implements verification logic for Pod Presets. +type eventStrategy struct { + runtime.ObjectTyper + names.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating Pod Preset objects. +var Strategy = eventStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} + +// NamespaceScoped returns true because all Events need to be within a namespace. +func (eventStrategy) NamespaceScoped() bool { + return true +} + +// PrepareForCreate clears the status of a Pod Preset before creation. +func (eventStrategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) { +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (eventStrategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime.Object) { +} + +// Validate validates a new Event. +func (eventStrategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList { + event := obj.(*api.Event) + return validation.ValidateEvent(event) +} + +// Canonicalize normalizes the object after validation. +// AllowCreateOnUpdate is false for Event; this means POST is needed to create one. +func (eventStrategy) AllowCreateOnUpdate() bool { + return false +} + +func (eventStrategy) Canonicalize(obj runtime.Object) {} + +// ValidateUpdate is the default update validation for an end user. +func (eventStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList { + event := obj.(*api.Event) + return validation.ValidateEvent(event) +} + +// AllowUnconditionalUpdate is the default update policy for Event objects. +func (eventStrategy) AllowUnconditionalUpdate() bool { + return true +} + +// SelectableFields returns a field set that represents the object. +func SelectableFields(pip *api.Event) fields.Set { + return generic.ObjectMetaFieldsSet(&pip.ObjectMeta, true) +} + +// GetAttrs returns labels and fields of a given object for filtering purposes. +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { + pip, ok := obj.(*api.Event) + if !ok { + return nil, nil, false, fmt.Errorf("given object is not a Event") + } + return labels.Set(pip.ObjectMeta.Labels), SelectableFields(pip), pip.Initializers != nil, nil +} + +// Matcher is the filter used by the generic etcd backend to watch events +// from etcd to clients of the apiserver only interested in specific labels/fields. +func Matcher(label labels.Selector, field fields.Selector) apistorage.SelectionPredicate { + return apistorage.SelectionPredicate{ + Label: label, + Field: field, + GetAttrs: GetAttrs, + } +} diff --git a/pkg/registry/events/rest/BUILD b/pkg/registry/events/rest/BUILD new file mode 100644 index 00000000000..dce67d6052b --- /dev/null +++ b/pkg/registry/events/rest/BUILD @@ -0,0 +1,32 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["storage_events.go"], + importpath = "k8s.io/kubernetes/pkg/registry/events/rest", + visibility = ["//visibility:public"], + deps = [ + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/events:go_default_library", + "//pkg/registry/core/event/storage:go_default_library", + "//vendor/k8s.io/api/events/v1beta1:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", + "//vendor/k8s.io/apiserver/pkg/server:go_default_library", + "//vendor/k8s.io/apiserver/pkg/server/storage:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/registry/events/rest/storage_events.go b/pkg/registry/events/rest/storage_events.go new file mode 100644 index 00000000000..03d6c661a52 --- /dev/null +++ b/pkg/registry/events/rest/storage_events.go @@ -0,0 +1,62 @@ +/* +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 rest + +import ( + "time" + + eventsapiv1beta1 "k8s.io/api/events/v1beta1" + "k8s.io/apiserver/pkg/registry/generic" + "k8s.io/apiserver/pkg/registry/rest" + genericapiserver "k8s.io/apiserver/pkg/server" + serverstorage "k8s.io/apiserver/pkg/server/storage" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/apis/events" + eventstore "k8s.io/kubernetes/pkg/registry/core/event/storage" +) + +type RESTStorageProvider struct { + TTL time.Duration +} + +func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) { + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(events.GroupName, legacyscheme.Registry, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs) + // If you add a version here, be sure to add an entry in `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go with specific priorities. + // TODO refactor the plumbing to provide the information in the APIGroupInfo + + if apiResourceConfigSource.AnyResourcesForVersionEnabled(eventsapiv1beta1.SchemeGroupVersion) { + apiGroupInfo.VersionedResourcesStorageMap[eventsapiv1beta1.SchemeGroupVersion.Version] = p.v1beta1Storage(apiResourceConfigSource, restOptionsGetter) + apiGroupInfo.GroupMeta.GroupVersion = eventsapiv1beta1.SchemeGroupVersion + } + + return apiGroupInfo, true +} + +func (p RESTStorageProvider) v1beta1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) map[string]rest.Storage { + version := eventsapiv1beta1.SchemeGroupVersion + + storage := map[string]rest.Storage{} + if apiResourceConfigSource.ResourceEnabled(version.WithResource("events")) { + eventsStorage := eventstore.NewREST(restOptionsGetter, uint64(p.TTL.Seconds())) + storage["events"] = eventsStorage + } + return storage +} + +func (p RESTStorageProvider) GroupName() string { + return events.GroupName +} diff --git a/pkg/registry/extensions/controller/storage/BUILD b/pkg/registry/extensions/controller/storage/BUILD index 6ddb41393ba..2f40b995f78 100644 --- a/pkg/registry/extensions/controller/storage/BUILD +++ b/pkg/registry/extensions/controller/storage/BUILD @@ -9,11 +9,11 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/extensions/controller/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", - "//pkg/apis/extensions:go_default_library", + "//pkg/apis/autoscaling:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", @@ -30,13 +30,15 @@ go_library( srcs = ["storage.go"], importpath = "k8s.io/kubernetes/pkg/registry/extensions/controller/storage", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/autoscaling:go_default_library", + "//pkg/apis/autoscaling/validation:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", - "//pkg/apis/extensions/validation:go_default_library", "//pkg/registry/core/replicationcontroller:go_default_library", "//pkg/registry/core/replicationcontroller/storage:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", diff --git a/pkg/registry/extensions/controller/storage/storage.go b/pkg/registry/extensions/controller/storage/storage.go index a3fa80c464b..774a78911f0 100644 --- a/pkg/registry/extensions/controller/storage/storage.go +++ b/pkg/registry/extensions/controller/storage/storage.go @@ -21,13 +21,15 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/autoscaling" + autoscalingvalidation "k8s.io/kubernetes/pkg/apis/autoscaling/validation" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" - extvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation" "k8s.io/kubernetes/pkg/registry/core/replicationcontroller" controllerstore "k8s.io/kubernetes/pkg/registry/core/replicationcontroller/storage" ) @@ -58,7 +60,7 @@ var _ = rest.Patcher(&ScaleREST{}) // New creates a new Scale object func (r *ScaleREST) New() runtime.Object { - return &extensions.Scale{} + return &autoscaling.Scale{} } func (r *ScaleREST) Get(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { @@ -69,7 +71,7 @@ func (r *ScaleREST) Get(ctx genericapirequest.Context, name string, options *met return scaleFromRC(rc), nil } -func (r *ScaleREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { +func (r *ScaleREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { rc, err := (*r.registry).GetController(ctx, name, &metav1.GetOptions{}) if err != nil { return nil, false, errors.NewNotFound(extensions.Resource("replicationcontrollers/scale"), name) @@ -81,18 +83,18 @@ func (r *ScaleREST) Update(ctx genericapirequest.Context, name string, objInfo r if obj == nil { return nil, false, errors.NewBadRequest(fmt.Sprintf("nil update passed to Scale")) } - scale, ok := obj.(*extensions.Scale) + scale, ok := obj.(*autoscaling.Scale) if !ok { return nil, false, errors.NewBadRequest(fmt.Sprintf("wrong object passed to Scale update: %v", obj)) } - if errs := extvalidation.ValidateScale(scale); len(errs) > 0 { + if errs := autoscalingvalidation.ValidateScale(scale); len(errs) > 0 { return nil, false, errors.NewInvalid(extensions.Kind("Scale"), scale.Name, errs) } rc.Spec.Replicas = scale.Spec.Replicas rc.ResourceVersion = scale.ResourceVersion - rc, err = (*r.registry).UpdateController(ctx, rc) + rc, err = (*r.registry).UpdateController(ctx, rc, createValidation, updateValidation) if err != nil { return nil, false, errors.NewConflict(extensions.Resource("replicationcontrollers/scale"), scale.Name, err) } @@ -100,8 +102,8 @@ func (r *ScaleREST) Update(ctx genericapirequest.Context, name string, objInfo r } // scaleFromRC returns a scale subresource for a replication controller. -func scaleFromRC(rc *api.ReplicationController) *extensions.Scale { - return &extensions.Scale{ +func scaleFromRC(rc *api.ReplicationController) *autoscaling.Scale { + return &autoscaling.Scale{ ObjectMeta: metav1.ObjectMeta{ Name: rc.Name, Namespace: rc.Namespace, @@ -109,14 +111,12 @@ func scaleFromRC(rc *api.ReplicationController) *extensions.Scale { ResourceVersion: rc.ResourceVersion, CreationTimestamp: rc.CreationTimestamp, }, - Spec: extensions.ScaleSpec{ + Spec: autoscaling.ScaleSpec{ Replicas: rc.Spec.Replicas, }, - Status: extensions.ScaleStatus{ + Status: autoscaling.ScaleStatus{ Replicas: rc.Status.Replicas, - Selector: &metav1.LabelSelector{ - MatchLabels: rc.Spec.Selector, - }, + Selector: labels.SelectorFromSet(labels.Set(rc.Spec.Selector)).String(), }, } } diff --git a/pkg/registry/extensions/controller/storage/storage_test.go b/pkg/registry/extensions/controller/storage/storage_test.go index 0d08f0d40ad..62689035fb4 100644 --- a/pkg/registry/extensions/controller/storage/storage_test.go +++ b/pkg/registry/extensions/controller/storage/storage_test.go @@ -26,8 +26,8 @@ import ( "k8s.io/apiserver/pkg/storage" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" "k8s.io/apiserver/pkg/storage/storagebackend/factory" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/autoscaling" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -74,16 +74,14 @@ var validController = api.ReplicationController{ Spec: validControllerSpec, } -var validScale = extensions.Scale{ +var validScale = autoscaling.Scale{ ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test"}, - Spec: extensions.ScaleSpec{ + Spec: autoscaling.ScaleSpec{ Replicas: validReplicas, }, - Status: extensions.ScaleStatus{ + Status: autoscaling.ScaleStatus{ Replicas: 0, - Selector: &metav1.LabelSelector{ - MatchLabels: validPodTemplate.Template.Labels, - }, + Selector: "a=b", }, } @@ -100,7 +98,7 @@ func TestGet(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %v", err) } - scale := obj.(*extensions.Scale) + scale := obj.(*autoscaling.Scale) if scale.Spec.Replicas != validReplicas { t.Errorf("wrong replicas count expected: %d got: %d", validReplicas, scale.Spec.Replicas) } @@ -116,14 +114,14 @@ func TestUpdate(t *testing.T) { t.Fatalf("unexpected error: %v", err) } replicas := int32(12) - update := extensions.Scale{ + update := autoscaling.Scale{ ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test"}, - Spec: extensions.ScaleSpec{ + Spec: autoscaling.ScaleSpec{ Replicas: replicas, }, } - if _, _, err := storage.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update)); err != nil { + if _, _, err := storage.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc); err != nil { t.Fatalf("unexpected error: %v", err) } obj, err := storage.Get(ctx, "foo", &metav1.GetOptions{}) @@ -131,7 +129,7 @@ func TestUpdate(t *testing.T) { t.Fatalf("unexpected error: %v", err) } - updated := obj.(*extensions.Scale) + updated := obj.(*autoscaling.Scale) if updated.Spec.Replicas != replicas { t.Errorf("wrong replicas count expected: %d got: %d", replicas, updated.Spec.Replicas) } diff --git a/pkg/registry/extensions/daemonset/BUILD b/pkg/registry/extensions/daemonset/BUILD index d9c86a4d8a9..9d2773c6eb3 100644 --- a/pkg/registry/extensions/daemonset/BUILD +++ b/pkg/registry/extensions/daemonset/BUILD @@ -14,7 +14,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/extensions/daemonset", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/pod:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/apis/extensions/validation:go_default_library", @@ -34,10 +34,10 @@ go_library( go_test( name = "go_default_test", srcs = ["strategy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/extensions/daemonset", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", diff --git a/pkg/registry/extensions/daemonset/storage/BUILD b/pkg/registry/extensions/daemonset/storage/BUILD index 9c4ae86778d..daeb8305f34 100644 --- a/pkg/registry/extensions/daemonset/storage/BUILD +++ b/pkg/registry/extensions/daemonset/storage/BUILD @@ -9,10 +9,10 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/extensions/daemonset/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -20,6 +20,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], ) diff --git a/pkg/registry/extensions/daemonset/storage/storage.go b/pkg/registry/extensions/daemonset/storage/storage.go index c5318901dfd..461e5a97ff2 100644 --- a/pkg/registry/extensions/daemonset/storage/storage.go +++ b/pkg/registry/extensions/daemonset/storage/storage.go @@ -89,6 +89,6 @@ func (r *StatusREST) Get(ctx genericapirequest.Context, name string, options *me } // Update alters the status subset of an object. -func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - return r.store.Update(ctx, name, objInfo) +func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation) } diff --git a/pkg/registry/extensions/daemonset/storage/storage_test.go b/pkg/registry/extensions/daemonset/storage/storage_test.go index 4f200901027..50dd81ca5a1 100644 --- a/pkg/registry/extensions/daemonset/storage/storage_test.go +++ b/pkg/registry/extensions/daemonset/storage/storage_test.go @@ -24,8 +24,9 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -80,7 +81,7 @@ func TestCreate(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) ds := newValidDaemonSet() ds.ObjectMeta = metav1.ObjectMeta{} test.TestCreate( @@ -107,7 +108,7 @@ func TestUpdate(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestUpdate( // valid newValidDaemonSet(), @@ -136,7 +137,7 @@ func TestDelete(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestDelete(newValidDaemonSet()) } @@ -144,7 +145,7 @@ func TestGet(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestGet(newValidDaemonSet()) } @@ -152,7 +153,7 @@ func TestList(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestList(newValidDaemonSet()) } @@ -160,7 +161,7 @@ func TestWatch(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestWatch( validDaemonSet, // matching labels diff --git a/pkg/registry/extensions/daemonset/strategy.go b/pkg/registry/extensions/daemonset/strategy.go index 0b42f3a9678..04723fdcf25 100644 --- a/pkg/registry/extensions/daemonset/strategy.go +++ b/pkg/registry/extensions/daemonset/strategy.go @@ -17,8 +17,6 @@ limitations under the License. package daemonset import ( - "fmt" - appsv1beta2 "k8s.io/api/apps/v1beta2" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" apiequality "k8s.io/apimachinery/pkg/api/equality" @@ -29,7 +27,7 @@ import ( genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/pod" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions/validation" @@ -42,11 +40,20 @@ type daemonSetStrategy struct { } // Strategy is the default logic that applies when creating and updating DaemonSet objects. -var Strategy = daemonSetStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = daemonSetStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} -// DefaultGarbageCollectionPolicy returns Orphan because that was the default -// behavior before the server-side garbage collection was implemented. -func (daemonSetStrategy) DefaultGarbageCollectionPolicy() rest.GarbageCollectionPolicy { +// DefaultGarbageCollectionPolicy returns OrphanDependents by default. For apps/v1, returns DeleteDependents. +func (daemonSetStrategy) DefaultGarbageCollectionPolicy(ctx genericapirequest.Context) rest.GarbageCollectionPolicy { + if requestInfo, found := genericapirequest.RequestInfoFrom(ctx); found { + groupVersion := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion} + switch groupVersion { + case extensionsv1beta1.SchemeGroupVersion, appsv1beta2.SchemeGroupVersion: + // for back compatibility + return rest.OrphanDependents + default: + return rest.DeleteDependents + } + } return rest.OrphanDependents } @@ -126,7 +133,7 @@ func (daemonSetStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old allErrs := validation.ValidateDaemonSet(obj.(*extensions.DaemonSet)) allErrs = append(allErrs, validation.ValidateDaemonSetUpdate(newDaemonSet, oldDaemonSet)...) - // Update is not allowed to set Spec.Selector for all groups/versions except extensions/v1beta1. + // Update is not allowed to set Spec.Selector for apps/v1 and apps/v1beta2 (allowed for extensions/v1beta1). // If RequestInfo is nil, it is better to revert to old behavior (i.e. allow update to set Spec.Selector) // to prevent unintentionally breaking users who may rely on the old behavior. // TODO(#50791): after extensions/v1beta1 is removed, move selector immutability check inside ValidateDaemonSetUpdate(). @@ -135,11 +142,9 @@ func (daemonSetStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old switch groupVersion { case extensionsv1beta1.SchemeGroupVersion: // no-op for compatibility - case appsv1beta2.SchemeGroupVersion: + default: // disallow mutation of selector allErrs = append(allErrs, apivalidation.ValidateImmutableField(newDaemonSet.Spec.Selector, oldDaemonSet.Spec.Selector, field.NewPath("spec").Child("selector"))...) - default: - panic(fmt.Sprintf("unexpected group/version: %v", groupVersion)) } } diff --git a/pkg/registry/extensions/daemonset/strategy_test.go b/pkg/registry/extensions/daemonset/strategy_test.go index 3dca4a72cb4..539a560cb86 100644 --- a/pkg/registry/extensions/daemonset/strategy_test.go +++ b/pkg/registry/extensions/daemonset/strategy_test.go @@ -24,7 +24,7 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" ) @@ -35,12 +35,57 @@ const ( namespace = "test-namespace" ) -func TestDefaultGarbageCollectionPolicy(t *testing.T) { +func TestDaemonsetDefaultGarbageCollectionPolicy(t *testing.T) { // Make sure we correctly implement the interface. // Otherwise a typo could silently change the default. var gcds rest.GarbageCollectionDeleteStrategy = Strategy - if got, want := gcds.DefaultGarbageCollectionPolicy(), rest.OrphanDependents; got != want { - t.Errorf("DefaultGarbageCollectionPolicy() = %#v, want %#v", got, want) + tests := []struct { + requestInfo genericapirequest.RequestInfo + expectedGCPolicy rest.GarbageCollectionPolicy + isNilRequestInfo bool + }{ + { + genericapirequest.RequestInfo{ + APIGroup: "extensions", + APIVersion: "v1beta1", + Resource: "daemonsets", + }, + rest.OrphanDependents, + false, + }, + { + genericapirequest.RequestInfo{ + APIGroup: "apps", + APIVersion: "v1beta2", + Resource: "daemonsets", + }, + rest.OrphanDependents, + false, + }, + { + genericapirequest.RequestInfo{ + APIGroup: "apps", + APIVersion: "v1", + Resource: "daemonsets", + }, + rest.DeleteDependents, + false, + }, + { + expectedGCPolicy: rest.OrphanDependents, + isNilRequestInfo: true, + }, + } + + for _, test := range tests { + context := genericapirequest.NewContext() + if !test.isNilRequestInfo { + context = genericapirequest.WithRequestInfo(context, &test.requestInfo) + } + if got, want := gcds.DefaultGarbageCollectionPolicy(context), test.expectedGCPolicy; got != want { + t.Errorf("%s/%s: DefaultGarbageCollectionPolicy() = %#v, want %#v", test.requestInfo.APIGroup, + test.requestInfo.APIVersion, got, want) + } } } @@ -51,6 +96,26 @@ func TestSelectorImmutability(t *testing.T) { newSelectorLabels map[string]string expectedErrorList field.ErrorList }{ + { + genericapirequest.RequestInfo{ + APIGroup: "apps", + APIVersion: "v1", + Resource: "daemonsets", + }, + map[string]string{"a": "b"}, + map[string]string{"c": "d"}, + field.ErrorList{ + &field.Error{ + Type: field.ErrorTypeInvalid, + Field: field.NewPath("spec").Child("selector").String(), + BadValue: &metav1.LabelSelector{ + MatchLabels: map[string]string{"c": "d"}, + MatchExpressions: []metav1.LabelSelectorRequirement{}, + }, + Detail: "field is immutable", + }, + }, + }, { genericapirequest.RequestInfo{ APIGroup: "apps", diff --git a/pkg/registry/extensions/deployment/BUILD b/pkg/registry/extensions/deployment/BUILD index b1a8ba953cb..8d365a15109 100644 --- a/pkg/registry/extensions/deployment/BUILD +++ b/pkg/registry/extensions/deployment/BUILD @@ -15,7 +15,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/extensions/deployment", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/pod:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/apis/extensions/validation:go_default_library", @@ -38,16 +38,17 @@ go_library( go_test( name = "go_default_test", srcs = ["strategy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/extensions/deployment", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", ], ) diff --git a/pkg/registry/extensions/deployment/registry.go b/pkg/registry/extensions/deployment/registry.go index e5b03be6e36..951e86863fb 100644 --- a/pkg/registry/extensions/deployment/registry.go +++ b/pkg/registry/extensions/deployment/registry.go @@ -30,8 +30,8 @@ import ( type Registry interface { ListDeployments(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*extensions.DeploymentList, error) GetDeployment(ctx genericapirequest.Context, deploymentID string, options *metav1.GetOptions) (*extensions.Deployment, error) - CreateDeployment(ctx genericapirequest.Context, deployment *extensions.Deployment) (*extensions.Deployment, error) - UpdateDeployment(ctx genericapirequest.Context, deployment *extensions.Deployment) (*extensions.Deployment, error) + CreateDeployment(ctx genericapirequest.Context, deployment *extensions.Deployment, createValidation rest.ValidateObjectFunc) (*extensions.Deployment, error) + UpdateDeployment(ctx genericapirequest.Context, deployment *extensions.Deployment, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (*extensions.Deployment, error) DeleteDeployment(ctx genericapirequest.Context, deploymentID string) error } @@ -64,16 +64,16 @@ func (s *storage) GetDeployment(ctx genericapirequest.Context, deploymentID stri return obj.(*extensions.Deployment), nil } -func (s *storage) CreateDeployment(ctx genericapirequest.Context, deployment *extensions.Deployment) (*extensions.Deployment, error) { - obj, err := s.Create(ctx, deployment, false) +func (s *storage) CreateDeployment(ctx genericapirequest.Context, deployment *extensions.Deployment, createValidation rest.ValidateObjectFunc) (*extensions.Deployment, error) { + obj, err := s.Create(ctx, deployment, createValidation, false) if err != nil { return nil, err } return obj.(*extensions.Deployment), nil } -func (s *storage) UpdateDeployment(ctx genericapirequest.Context, deployment *extensions.Deployment) (*extensions.Deployment, error) { - obj, _, err := s.Update(ctx, deployment.Name, rest.DefaultUpdatedObjectInfo(deployment)) +func (s *storage) UpdateDeployment(ctx genericapirequest.Context, deployment *extensions.Deployment, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (*extensions.Deployment, error) { + obj, _, err := s.Update(ctx, deployment.Name, rest.DefaultUpdatedObjectInfo(deployment), createValidation, updateValidation) if err != nil { return nil, err } diff --git a/pkg/registry/extensions/deployment/storage/BUILD b/pkg/registry/extensions/deployment/storage/BUILD index 1d8064b0dc1..3663653fc26 100644 --- a/pkg/registry/extensions/deployment/storage/BUILD +++ b/pkg/registry/extensions/deployment/storage/BUILD @@ -9,10 +9,11 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/extensions/deployment/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/autoscaling:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", @@ -25,6 +26,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/errors:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", @@ -36,7 +38,13 @@ go_library( srcs = ["storage.go"], importpath = "k8s.io/kubernetes/pkg/registry/extensions/deployment/storage", deps = [ + "//pkg/apis/apps/v1beta1:go_default_library", + "//pkg/apis/apps/v1beta2:go_default_library", + "//pkg/apis/autoscaling:go_default_library", + "//pkg/apis/autoscaling/v1:go_default_library", + "//pkg/apis/autoscaling/validation:go_default_library", "//pkg/apis/extensions:go_default_library", + "//pkg/apis/extensions/v1beta1:go_default_library", "//pkg/apis/extensions/validation:go_default_library", "//pkg/registry/extensions/deployment:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", diff --git a/pkg/registry/extensions/deployment/storage/storage.go b/pkg/registry/extensions/deployment/storage/storage.go index ec2f08744c2..6cf80f8dcc7 100644 --- a/pkg/registry/extensions/deployment/storage/storage.go +++ b/pkg/registry/extensions/deployment/storage/storage.go @@ -30,7 +30,13 @@ import ( "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage" storeerr "k8s.io/apiserver/pkg/storage/errors" + appsv1beta1 "k8s.io/kubernetes/pkg/apis/apps/v1beta1" + appsv1beta2 "k8s.io/kubernetes/pkg/apis/apps/v1beta2" + "k8s.io/kubernetes/pkg/apis/autoscaling" + autoscalingv1 "k8s.io/kubernetes/pkg/apis/autoscaling/v1" + autoscalingvalidation "k8s.io/kubernetes/pkg/apis/autoscaling/validation" "k8s.io/kubernetes/pkg/apis/extensions" + extensionsv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" extvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation" "k8s.io/kubernetes/pkg/registry/extensions/deployment" ) @@ -111,8 +117,8 @@ func (r *StatusREST) Get(ctx genericapirequest.Context, name string, options *me } // Update alters the status subset of an object. -func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - return r.store.Update(ctx, name, objInfo) +func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation) } // RollbackREST implements the REST endpoint for initiating the rollback of a deployment @@ -127,7 +133,7 @@ func (r *RollbackREST) New() runtime.Object { var _ = rest.Creater(&RollbackREST{}) -func (r *RollbackREST) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { +func (r *RollbackREST) Create(ctx genericapirequest.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) { rollback, ok := obj.(*extensions.DeploymentRollback) if !ok { return nil, errors.NewBadRequest(fmt.Sprintf("not a DeploymentRollback: %#v", obj)) @@ -193,13 +199,22 @@ type ScaleREST struct { var _ = rest.Patcher(&ScaleREST{}) var _ = rest.GroupVersionKindProvider(&ScaleREST{}) -func (r *ScaleREST) GroupVersionKind() schema.GroupVersionKind { - return schema.GroupVersionKind{Group: "extensions", Version: "v1beta1", Kind: "Scale"} +func (r *ScaleREST) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind { + switch containingGV { + case extensionsv1beta1.SchemeGroupVersion: + return extensionsv1beta1.SchemeGroupVersion.WithKind("Scale") + case appsv1beta1.SchemeGroupVersion: + return appsv1beta1.SchemeGroupVersion.WithKind("Scale") + case appsv1beta2.SchemeGroupVersion: + return appsv1beta2.SchemeGroupVersion.WithKind("Scale") + default: + return autoscalingv1.SchemeGroupVersion.WithKind("Scale") + } } // New creates a new Scale object func (r *ScaleREST) New() runtime.Object { - return &extensions.Scale{} + return &autoscaling.Scale{} } func (r *ScaleREST) Get(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { @@ -214,7 +229,7 @@ func (r *ScaleREST) Get(ctx genericapirequest.Context, name string, options *met return scale, nil } -func (r *ScaleREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { +func (r *ScaleREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { deployment, err := r.registry.GetDeployment(ctx, name, &metav1.GetOptions{}) if err != nil { return nil, false, errors.NewNotFound(extensions.Resource("deployments/scale"), name) @@ -232,18 +247,18 @@ func (r *ScaleREST) Update(ctx genericapirequest.Context, name string, objInfo r if obj == nil { return nil, false, errors.NewBadRequest(fmt.Sprintf("nil update passed to Scale")) } - scale, ok := obj.(*extensions.Scale) + scale, ok := obj.(*autoscaling.Scale) if !ok { return nil, false, errors.NewBadRequest(fmt.Sprintf("expected input object type to be Scale, but %T", obj)) } - if errs := extvalidation.ValidateScale(scale); len(errs) > 0 { + if errs := autoscalingvalidation.ValidateScale(scale); len(errs) > 0 { return nil, false, errors.NewInvalid(extensions.Kind("Scale"), name, errs) } deployment.Spec.Replicas = scale.Spec.Replicas deployment.ResourceVersion = scale.ResourceVersion - deployment, err = r.registry.UpdateDeployment(ctx, deployment) + deployment, err = r.registry.UpdateDeployment(ctx, deployment, createValidation, updateValidation) if err != nil { return nil, false, err } @@ -255,8 +270,12 @@ func (r *ScaleREST) Update(ctx genericapirequest.Context, name string, objInfo r } // scaleFromDeployment returns a scale subresource for a deployment. -func scaleFromDeployment(deployment *extensions.Deployment) (*extensions.Scale, error) { - return &extensions.Scale{ +func scaleFromDeployment(deployment *extensions.Deployment) (*autoscaling.Scale, error) { + selector, err := metav1.LabelSelectorAsSelector(deployment.Spec.Selector) + if err != nil { + return nil, err + } + return &autoscaling.Scale{ // TODO: Create a variant of ObjectMeta type that only contains the fields below. ObjectMeta: metav1.ObjectMeta{ Name: deployment.Name, @@ -265,12 +284,12 @@ func scaleFromDeployment(deployment *extensions.Deployment) (*extensions.Scale, ResourceVersion: deployment.ResourceVersion, CreationTimestamp: deployment.CreationTimestamp, }, - Spec: extensions.ScaleSpec{ + Spec: autoscaling.ScaleSpec{ Replicas: deployment.Spec.Replicas, }, - Status: extensions.ScaleStatus{ + Status: autoscaling.ScaleStatus{ Replicas: deployment.Status.Replicas, - Selector: deployment.Spec.Selector, + Selector: selector.String(), }, }, nil } diff --git a/pkg/registry/extensions/deployment/storage/storage_test.go b/pkg/registry/extensions/deployment/storage/storage_test.go index 09823f40c61..c7e348fc7b0 100644 --- a/pkg/registry/extensions/deployment/storage/storage_test.go +++ b/pkg/registry/extensions/deployment/storage/storage_test.go @@ -31,10 +31,12 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" "k8s.io/apiserver/pkg/registry/rest" storeerr "k8s.io/apiserver/pkg/storage/errors" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/autoscaling" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -97,7 +99,7 @@ func TestCreate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Deployment.Store.DestroyFunc() - test := registrytest.New(t, storage.Deployment.Store) + test := genericregistrytest.New(t, storage.Deployment.Store) deployment := validNewDeployment() deployment.ObjectMeta = metav1.ObjectMeta{} test.TestCreate( @@ -117,7 +119,7 @@ func TestUpdate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Deployment.Store.DestroyFunc() - test := registrytest.New(t, storage.Deployment.Store) + test := genericregistrytest.New(t, storage.Deployment.Store) test.TestUpdate( // valid validNewDeployment(), @@ -150,7 +152,7 @@ func TestDelete(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Deployment.Store.DestroyFunc() - test := registrytest.New(t, storage.Deployment.Store) + test := genericregistrytest.New(t, storage.Deployment.Store) test.TestDelete(validNewDeployment()) } @@ -158,7 +160,7 @@ func TestGet(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Deployment.Store.DestroyFunc() - test := registrytest.New(t, storage.Deployment.Store) + test := genericregistrytest.New(t, storage.Deployment.Store) test.TestGet(validNewDeployment()) } @@ -166,7 +168,7 @@ func TestList(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Deployment.Store.DestroyFunc() - test := registrytest.New(t, storage.Deployment.Store) + test := genericregistrytest.New(t, storage.Deployment.Store) test.TestList(validNewDeployment()) } @@ -174,7 +176,7 @@ func TestWatch(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Deployment.Store.DestroyFunc() - test := registrytest.New(t, storage.Deployment.Store) + test := genericregistrytest.New(t, storage.Deployment.Store) test.TestWatch( validNewDeployment(), // matching labels @@ -207,7 +209,11 @@ func TestScaleGet(t *testing.T) { t.Fatalf("error setting new deployment (key: %s) %v: %v", key, validDeployment, err) } - want := &extensions.Scale{ + selector, err := metav1.LabelSelectorAsSelector(validDeployment.Spec.Selector) + if err != nil { + t.Fatal(err) + } + want := &autoscaling.Scale{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, @@ -215,19 +221,19 @@ func TestScaleGet(t *testing.T) { ResourceVersion: deployment.ResourceVersion, CreationTimestamp: deployment.CreationTimestamp, }, - Spec: extensions.ScaleSpec{ + Spec: autoscaling.ScaleSpec{ Replicas: validDeployment.Spec.Replicas, }, - Status: extensions.ScaleStatus{ + Status: autoscaling.ScaleStatus{ Replicas: validDeployment.Status.Replicas, - Selector: validDeployment.Spec.Selector, + Selector: selector.String(), }, } obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{}) if err != nil { t.Fatalf("error fetching scale for %s: %v", name, err) } - got := obj.(*extensions.Scale) + got := obj.(*autoscaling.Scale) if !apiequality.Semantic.DeepEqual(want, got) { t.Errorf("unexpected scale: %s", diff.ObjectDiff(want, got)) } @@ -244,21 +250,21 @@ func TestScaleUpdate(t *testing.T) { t.Fatalf("error setting new deployment (key: %s) %v: %v", key, validDeployment, err) } replicas := int32(12) - update := extensions.Scale{ + update := autoscaling.Scale{ ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, - Spec: extensions.ScaleSpec{ + Spec: autoscaling.ScaleSpec{ Replicas: replicas, }, } - if _, _, err := storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update)); err != nil { + if _, _, err := storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc); err != nil { t.Fatalf("error updating scale %v: %v", update, err) } obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{}) if err != nil { t.Fatalf("error fetching scale for %s: %v", name, err) } - scale := obj.(*extensions.Scale) + scale := obj.(*autoscaling.Scale) if scale.Spec.Replicas != replicas { t.Errorf("wrong replicas count expected: %d got: %d", replicas, deployment.Spec.Replicas) } @@ -266,7 +272,7 @@ func TestScaleUpdate(t *testing.T) { update.ResourceVersion = deployment.ResourceVersion update.Spec.Replicas = 15 - if _, _, err = storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update)); err != nil && !errors.IsConflict(err) { + if _, _, err = storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc); err != nil && !errors.IsConflict(err) { t.Fatalf("unexpected error, expecting an update conflict but got %v", err) } } @@ -290,7 +296,7 @@ func TestStatusUpdate(t *testing.T) { }, } - if _, _, err := storage.Status.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update)); err != nil { + if _, _, err := storage.Status.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc); err != nil { t.Fatalf("unexpected error: %v", err) } obj, err := storage.Deployment.Get(ctx, name, &metav1.GetOptions{}) @@ -341,10 +347,10 @@ func TestEtcdCreateDeploymentRollback(t *testing.T) { storage, server := newStorage(t) rollbackStorage := storage.Rollback - if _, err := storage.Deployment.Create(ctx, validNewDeployment(), false); err != nil { + if _, err := storage.Deployment.Create(ctx, validNewDeployment(), rest.ValidateAllObjectFunc, false); err != nil { t.Fatalf("%s: unexpected error: %v", k, err) } - rollbackRespStatus, err := rollbackStorage.Create(ctx, &test.rollback, false) + rollbackRespStatus, err := rollbackStorage.Create(ctx, &test.rollback, rest.ValidateAllObjectFunc, false) if !test.errOK(err) { t.Errorf("%s: unexpected error: %v", k, err) } else if err == nil { @@ -381,7 +387,7 @@ func TestEtcdCreateDeploymentRollbackNoDeployment(t *testing.T) { Name: name, UpdatedAnnotations: map[string]string{}, RollbackTo: extensions.RollbackConfig{Revision: 1}, - }, false) + }, rest.ValidateAllObjectFunc, false) if err == nil { t.Fatalf("Expected not-found-error but got nothing") } diff --git a/pkg/registry/extensions/deployment/strategy.go b/pkg/registry/extensions/deployment/strategy.go index 0b898fcc044..29fcd750aac 100644 --- a/pkg/registry/extensions/deployment/strategy.go +++ b/pkg/registry/extensions/deployment/strategy.go @@ -17,8 +17,6 @@ limitations under the License. package deployment import ( - "fmt" - appsv1beta1 "k8s.io/api/apps/v1beta1" appsv1beta2 "k8s.io/api/apps/v1beta2" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" @@ -30,7 +28,7 @@ import ( genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/pod" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions/validation" @@ -44,11 +42,20 @@ type deploymentStrategy struct { // Strategy is the default logic that applies when creating and updating Deployment // objects via the REST API. -var Strategy = deploymentStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = deploymentStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} -// DefaultGarbageCollectionPolicy returns Orphan because that's the default -// behavior before the server-side garbage collection is implemented. -func (deploymentStrategy) DefaultGarbageCollectionPolicy() rest.GarbageCollectionPolicy { +// DefaultGarbageCollectionPolicy returns OrphanDependents by default. For apps/v1, returns DeleteDependents. +func (deploymentStrategy) DefaultGarbageCollectionPolicy(ctx genericapirequest.Context) rest.GarbageCollectionPolicy { + if requestInfo, found := genericapirequest.RequestInfoFrom(ctx); found { + groupVersion := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion} + switch groupVersion { + case extensionsv1beta1.SchemeGroupVersion, appsv1beta1.SchemeGroupVersion, appsv1beta2.SchemeGroupVersion: + // for back compatibility + return rest.OrphanDependents + default: + return rest.DeleteDependents + } + } return rest.OrphanDependents } @@ -113,15 +120,11 @@ func (deploymentStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old if requestInfo, found := genericapirequest.RequestInfoFrom(ctx); found { groupVersion := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion} switch groupVersion { - case appsv1beta1.SchemeGroupVersion: + case appsv1beta1.SchemeGroupVersion, extensionsv1beta1.SchemeGroupVersion: // no-op for compatibility - case extensionsv1beta1.SchemeGroupVersion: - // no-op for compatibility - case appsv1beta2.SchemeGroupVersion: + default: // disallow mutation of selector allErrs = append(allErrs, apivalidation.ValidateImmutableField(newDeployment.Spec.Selector, oldDeployment.Spec.Selector, field.NewPath("spec").Child("selector"))...) - default: - panic(fmt.Sprintf("unexpected group/version: %v", groupVersion)) } } diff --git a/pkg/registry/extensions/deployment/strategy_test.go b/pkg/registry/extensions/deployment/strategy_test.go index 2a53b347f23..02a95479c56 100644 --- a/pkg/registry/extensions/deployment/strategy_test.go +++ b/pkg/registry/extensions/deployment/strategy_test.go @@ -25,7 +25,8 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/validation/field" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/kubernetes/pkg/api" + "k8s.io/apiserver/pkg/registry/rest" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" ) @@ -183,3 +184,66 @@ func newDeploymentWithSelectorLabels(selectorLabels map[string]string) *extensio }, } } + +func TestDeploymentDefaultGarbageCollectionPolicy(t *testing.T) { + // Make sure we correctly implement the interface. + // Otherwise a typo could silently change the default. + var gcds rest.GarbageCollectionDeleteStrategy = Strategy + tests := []struct { + requestInfo genericapirequest.RequestInfo + expectedGCPolicy rest.GarbageCollectionPolicy + isNilRequestInfo bool + }{ + { + genericapirequest.RequestInfo{ + APIGroup: "extensions", + APIVersion: "v1beta1", + Resource: "deployments", + }, + rest.OrphanDependents, + false, + }, + { + genericapirequest.RequestInfo{ + APIGroup: "apps", + APIVersion: "v1beta1", + Resource: "deployments", + }, + rest.OrphanDependents, + false, + }, + { + genericapirequest.RequestInfo{ + APIGroup: "apps", + APIVersion: "v1beta2", + Resource: "deployments", + }, + rest.OrphanDependents, + false, + }, + { + genericapirequest.RequestInfo{ + APIGroup: "apps", + APIVersion: "v1", + Resource: "deployments", + }, + rest.DeleteDependents, + false, + }, + { + expectedGCPolicy: rest.OrphanDependents, + isNilRequestInfo: true, + }, + } + + for _, test := range tests { + context := genericapirequest.NewContext() + if !test.isNilRequestInfo { + context = genericapirequest.WithRequestInfo(context, &test.requestInfo) + } + if got, want := gcds.DefaultGarbageCollectionPolicy(context), test.expectedGCPolicy; got != want { + t.Errorf("%s/%s: DefaultGarbageCollectionPolicy() = %#v, want %#v", test.requestInfo.APIGroup, + test.requestInfo.APIVersion, got, want) + } + } +} diff --git a/pkg/registry/extensions/ingress/BUILD b/pkg/registry/extensions/ingress/BUILD index f6368525819..d93aabfddb0 100644 --- a/pkg/registry/extensions/ingress/BUILD +++ b/pkg/registry/extensions/ingress/BUILD @@ -14,7 +14,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/extensions/ingress", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/apis/extensions/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", @@ -28,10 +28,10 @@ go_library( go_test( name = "go_default_test", srcs = ["strategy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/extensions/ingress", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", diff --git a/pkg/registry/extensions/ingress/storage/BUILD b/pkg/registry/extensions/ingress/storage/BUILD index d1dc772f9c8..78b07c4abb1 100644 --- a/pkg/registry/extensions/ingress/storage/BUILD +++ b/pkg/registry/extensions/ingress/storage/BUILD @@ -9,10 +9,10 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/extensions/ingress/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -21,6 +21,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], ) diff --git a/pkg/registry/extensions/ingress/storage/storage.go b/pkg/registry/extensions/ingress/storage/storage.go index 9a8d6e253a1..0c7273d8578 100644 --- a/pkg/registry/extensions/ingress/storage/storage.go +++ b/pkg/registry/extensions/ingress/storage/storage.go @@ -81,6 +81,6 @@ func (r *StatusREST) Get(ctx genericapirequest.Context, name string, options *me } // Update alters the status subset of an object. -func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - return r.store.Update(ctx, name, objInfo) +func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation) } diff --git a/pkg/registry/extensions/ingress/storage/storage_test.go b/pkg/registry/extensions/ingress/storage/storage_test.go index 10f911952cc..f1643f59de6 100644 --- a/pkg/registry/extensions/ingress/storage/storage_test.go +++ b/pkg/registry/extensions/ingress/storage/storage_test.go @@ -25,8 +25,9 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -122,7 +123,7 @@ func TestCreate(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) ingress := validIngress() noDefaultBackendAndRules := validIngress() noDefaultBackendAndRules.Spec.Backend = &extensions.IngressBackend{} @@ -142,7 +143,7 @@ func TestUpdate(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestUpdate( // valid validIngress(), @@ -178,7 +179,7 @@ func TestDelete(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestDelete(validIngress()) } @@ -186,7 +187,7 @@ func TestGet(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestGet(validIngress()) } @@ -194,7 +195,7 @@ func TestList(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestList(validIngress()) } @@ -202,7 +203,7 @@ func TestWatch(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestWatch( validIngress(), // matching labels diff --git a/pkg/registry/extensions/ingress/strategy.go b/pkg/registry/extensions/ingress/strategy.go index 41edf5bc7f4..e970d105c2f 100644 --- a/pkg/registry/extensions/ingress/strategy.go +++ b/pkg/registry/extensions/ingress/strategy.go @@ -22,7 +22,7 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions/validation" ) @@ -34,7 +34,7 @@ type ingressStrategy struct { } // Strategy is the default logic that applies when creating and updating Replication Ingress objects. -var Strategy = ingressStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = ingressStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} // NamespaceScoped returns true because all Ingress' need to be within a namespace. func (ingressStrategy) NamespaceScoped() bool { diff --git a/pkg/registry/extensions/ingress/strategy_test.go b/pkg/registry/extensions/ingress/strategy_test.go index 15db6711ff5..e12b79ff912 100644 --- a/pkg/registry/extensions/ingress/strategy_test.go +++ b/pkg/registry/extensions/ingress/strategy_test.go @@ -22,7 +22,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" ) diff --git a/pkg/registry/extensions/podsecuritypolicy/BUILD b/pkg/registry/extensions/podsecuritypolicy/BUILD index b745113fcb9..eadcc39d2f6 100644 --- a/pkg/registry/extensions/podsecuritypolicy/BUILD +++ b/pkg/registry/extensions/podsecuritypolicy/BUILD @@ -13,7 +13,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/extensions/podsecuritypolicy", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/apis/extensions/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/pkg/registry/extensions/podsecuritypolicy/storage/BUILD b/pkg/registry/extensions/podsecuritypolicy/storage/BUILD index b37eef3a04e..fb7ecda243e 100644 --- a/pkg/registry/extensions/podsecuritypolicy/storage/BUILD +++ b/pkg/registry/extensions/podsecuritypolicy/storage/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/extensions/podsecuritypolicy/storage", - library = ":go_default_library", deps = [ "//pkg/apis/extensions:go_default_library", "//pkg/registry/registrytest:go_default_library", @@ -20,6 +20,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], ) diff --git a/pkg/registry/extensions/podsecuritypolicy/storage/storage_test.go b/pkg/registry/extensions/podsecuritypolicy/storage/storage_test.go index 9d6c08b45ea..d380c1ac032 100644 --- a/pkg/registry/extensions/podsecuritypolicy/storage/storage_test.go +++ b/pkg/registry/extensions/podsecuritypolicy/storage/storage_test.go @@ -27,6 +27,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -68,7 +69,7 @@ func TestCreate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() psp := validNewPodSecurityPolicy() psp.ObjectMeta = metav1.ObjectMeta{GenerateName: "foo-"} test.TestCreate( @@ -85,7 +86,7 @@ func TestUpdate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() test.TestUpdate( // valid validNewPodSecurityPolicy(), @@ -102,7 +103,7 @@ func TestDelete(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope().ReturnDeletedObject() + test := genericregistrytest.New(t, storage.Store).ClusterScope().ReturnDeletedObject() test.TestDelete(validNewPodSecurityPolicy()) } @@ -110,7 +111,7 @@ func TestGet(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() test.TestGet(validNewPodSecurityPolicy()) } @@ -118,7 +119,7 @@ func TestList(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() test.TestList(validNewPodSecurityPolicy()) } @@ -126,7 +127,7 @@ func TestWatch(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() test.TestWatch( validNewPodSecurityPolicy(), // matching labels diff --git a/pkg/registry/extensions/podsecuritypolicy/strategy.go b/pkg/registry/extensions/podsecuritypolicy/strategy.go index 130b90d338b..118b6914684 100644 --- a/pkg/registry/extensions/podsecuritypolicy/strategy.go +++ b/pkg/registry/extensions/podsecuritypolicy/strategy.go @@ -22,7 +22,7 @@ import ( genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions/validation" ) @@ -35,7 +35,7 @@ type strategy struct { // Strategy is the default logic that applies when creating and updating PodSecurityPolicy // objects via the REST API. -var Strategy = strategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = strategy{legacyscheme.Scheme, names.SimpleNameGenerator} var _ = rest.RESTCreateStrategy(Strategy) diff --git a/pkg/registry/extensions/replicaset/BUILD b/pkg/registry/extensions/replicaset/BUILD index b35fd84a166..5d7c8c4b814 100644 --- a/pkg/registry/extensions/replicaset/BUILD +++ b/pkg/registry/extensions/replicaset/BUILD @@ -15,7 +15,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/extensions/replicaset", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/pod:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/apis/extensions/validation:go_default_library", @@ -42,14 +42,15 @@ go_library( go_test( name = "go_default_test", srcs = ["strategy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/extensions/replicaset", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", ], ) diff --git a/pkg/registry/extensions/replicaset/registry.go b/pkg/registry/extensions/replicaset/registry.go index 155083e1fbc..029631eb68e 100644 --- a/pkg/registry/extensions/replicaset/registry.go +++ b/pkg/registry/extensions/replicaset/registry.go @@ -34,8 +34,8 @@ type Registry interface { ListReplicaSets(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*extensions.ReplicaSetList, error) WatchReplicaSets(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) GetReplicaSet(ctx genericapirequest.Context, replicaSetID string, options *metav1.GetOptions) (*extensions.ReplicaSet, error) - CreateReplicaSet(ctx genericapirequest.Context, replicaSet *extensions.ReplicaSet) (*extensions.ReplicaSet, error) - UpdateReplicaSet(ctx genericapirequest.Context, replicaSet *extensions.ReplicaSet) (*extensions.ReplicaSet, error) + CreateReplicaSet(ctx genericapirequest.Context, replicaSet *extensions.ReplicaSet, createValidation rest.ValidateObjectFunc) (*extensions.ReplicaSet, error) + UpdateReplicaSet(ctx genericapirequest.Context, replicaSet *extensions.ReplicaSet, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (*extensions.ReplicaSet, error) DeleteReplicaSet(ctx genericapirequest.Context, replicaSetID string) error } @@ -73,16 +73,16 @@ func (s *storage) GetReplicaSet(ctx genericapirequest.Context, replicaSetID stri return obj.(*extensions.ReplicaSet), nil } -func (s *storage) CreateReplicaSet(ctx genericapirequest.Context, replicaSet *extensions.ReplicaSet) (*extensions.ReplicaSet, error) { - obj, err := s.Create(ctx, replicaSet, false) +func (s *storage) CreateReplicaSet(ctx genericapirequest.Context, replicaSet *extensions.ReplicaSet, createValidation rest.ValidateObjectFunc) (*extensions.ReplicaSet, error) { + obj, err := s.Create(ctx, replicaSet, createValidation, false) if err != nil { return nil, err } return obj.(*extensions.ReplicaSet), nil } -func (s *storage) UpdateReplicaSet(ctx genericapirequest.Context, replicaSet *extensions.ReplicaSet) (*extensions.ReplicaSet, error) { - obj, _, err := s.Update(ctx, replicaSet.Name, rest.DefaultUpdatedObjectInfo(replicaSet)) +func (s *storage) UpdateReplicaSet(ctx genericapirequest.Context, replicaSet *extensions.ReplicaSet, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (*extensions.ReplicaSet, error) { + obj, _, err := s.Update(ctx, replicaSet.Name, rest.DefaultUpdatedObjectInfo(replicaSet), createValidation, updateValidation) if err != nil { return nil, err } diff --git a/pkg/registry/extensions/replicaset/storage/BUILD b/pkg/registry/extensions/replicaset/storage/BUILD index 842735674eb..4c65ff3644a 100644 --- a/pkg/registry/extensions/replicaset/storage/BUILD +++ b/pkg/registry/extensions/replicaset/storage/BUILD @@ -9,10 +9,11 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/extensions/replicaset/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/autoscaling:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", @@ -24,6 +25,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], @@ -34,8 +36,13 @@ go_library( srcs = ["storage.go"], importpath = "k8s.io/kubernetes/pkg/registry/extensions/replicaset/storage", deps = [ + "//pkg/apis/apps/v1beta1:go_default_library", + "//pkg/apis/apps/v1beta2:go_default_library", + "//pkg/apis/autoscaling:go_default_library", + "//pkg/apis/autoscaling/v1:go_default_library", + "//pkg/apis/autoscaling/validation:go_default_library", "//pkg/apis/extensions:go_default_library", - "//pkg/apis/extensions/validation:go_default_library", + "//pkg/apis/extensions/v1beta1:go_default_library", "//pkg/printers:go_default_library", "//pkg/printers/internalversion:go_default_library", "//pkg/printers/storage:go_default_library", diff --git a/pkg/registry/extensions/replicaset/storage/storage.go b/pkg/registry/extensions/replicaset/storage/storage.go index c443a89e072..893e66390da 100644 --- a/pkg/registry/extensions/replicaset/storage/storage.go +++ b/pkg/registry/extensions/replicaset/storage/storage.go @@ -29,8 +29,13 @@ import ( "k8s.io/apiserver/pkg/registry/generic" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/rest" + appsv1beta1 "k8s.io/kubernetes/pkg/apis/apps/v1beta1" + appsv1beta2 "k8s.io/kubernetes/pkg/apis/apps/v1beta2" + "k8s.io/kubernetes/pkg/apis/autoscaling" + autoscalingv1 "k8s.io/kubernetes/pkg/apis/autoscaling/v1" + autoscalingvalidation "k8s.io/kubernetes/pkg/apis/autoscaling/validation" "k8s.io/kubernetes/pkg/apis/extensions" - extvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation" + extensionsv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" "k8s.io/kubernetes/pkg/printers" printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" printerstorage "k8s.io/kubernetes/pkg/printers/storage" @@ -115,8 +120,8 @@ func (r *StatusREST) Get(ctx genericapirequest.Context, name string, options *me } // Update alters the status subset of an object. -func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - return r.store.Update(ctx, name, objInfo) +func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation) } type ScaleREST struct { @@ -127,13 +132,22 @@ type ScaleREST struct { var _ = rest.Patcher(&ScaleREST{}) var _ = rest.GroupVersionKindProvider(&ScaleREST{}) -func (r *ScaleREST) GroupVersionKind() schema.GroupVersionKind { - return schema.GroupVersionKind{Group: "extensions", Version: "v1beta1", Kind: "Scale"} +func (r *ScaleREST) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind { + switch containingGV { + case extensionsv1beta1.SchemeGroupVersion: + return extensionsv1beta1.SchemeGroupVersion.WithKind("Scale") + case appsv1beta1.SchemeGroupVersion: + return appsv1beta1.SchemeGroupVersion.WithKind("Scale") + case appsv1beta2.SchemeGroupVersion: + return appsv1beta2.SchemeGroupVersion.WithKind("Scale") + default: + return autoscalingv1.SchemeGroupVersion.WithKind("Scale") + } } // New creates a new Scale object func (r *ScaleREST) New() runtime.Object { - return &extensions.Scale{} + return &autoscaling.Scale{} } func (r *ScaleREST) Get(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { @@ -148,7 +162,7 @@ func (r *ScaleREST) Get(ctx genericapirequest.Context, name string, options *met return scale, err } -func (r *ScaleREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { +func (r *ScaleREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { rs, err := r.registry.GetReplicaSet(ctx, name, &metav1.GetOptions{}) if err != nil { return nil, false, errors.NewNotFound(extensions.Resource("replicasets/scale"), name) @@ -159,6 +173,7 @@ func (r *ScaleREST) Update(ctx genericapirequest.Context, name string, objInfo r return nil, false, err } + // TODO: should this pass admission? obj, err := objInfo.UpdatedObject(ctx, oldScale) if err != nil { return nil, false, err @@ -166,18 +181,18 @@ func (r *ScaleREST) Update(ctx genericapirequest.Context, name string, objInfo r if obj == nil { return nil, false, errors.NewBadRequest(fmt.Sprintf("nil update passed to Scale")) } - scale, ok := obj.(*extensions.Scale) + scale, ok := obj.(*autoscaling.Scale) if !ok { return nil, false, errors.NewBadRequest(fmt.Sprintf("wrong object passed to Scale update: %v", obj)) } - if errs := extvalidation.ValidateScale(scale); len(errs) > 0 { + if errs := autoscalingvalidation.ValidateScale(scale); len(errs) > 0 { return nil, false, errors.NewInvalid(extensions.Kind("Scale"), scale.Name, errs) } rs.Spec.Replicas = scale.Spec.Replicas rs.ResourceVersion = scale.ResourceVersion - rs, err = r.registry.UpdateReplicaSet(ctx, rs) + rs, err = r.registry.UpdateReplicaSet(ctx, rs, createValidation, updateValidation) if err != nil { return nil, false, err } @@ -189,8 +204,12 @@ func (r *ScaleREST) Update(ctx genericapirequest.Context, name string, objInfo r } // scaleFromReplicaSet returns a scale subresource for a replica set. -func scaleFromReplicaSet(rs *extensions.ReplicaSet) (*extensions.Scale, error) { - return &extensions.Scale{ +func scaleFromReplicaSet(rs *extensions.ReplicaSet) (*autoscaling.Scale, error) { + selector, err := metav1.LabelSelectorAsSelector(rs.Spec.Selector) + if err != nil { + return nil, err + } + return &autoscaling.Scale{ // TODO: Create a variant of ObjectMeta type that only contains the fields below. ObjectMeta: metav1.ObjectMeta{ Name: rs.Name, @@ -199,12 +218,12 @@ func scaleFromReplicaSet(rs *extensions.ReplicaSet) (*extensions.Scale, error) { ResourceVersion: rs.ResourceVersion, CreationTimestamp: rs.CreationTimestamp, }, - Spec: extensions.ScaleSpec{ + Spec: autoscaling.ScaleSpec{ Replicas: rs.Spec.Replicas, }, - Status: extensions.ScaleStatus{ + Status: autoscaling.ScaleStatus{ Replicas: rs.Status.Replicas, - Selector: rs.Spec.Selector, + Selector: selector.String(), }, }, nil } diff --git a/pkg/registry/extensions/replicaset/storage/storage_test.go b/pkg/registry/extensions/replicaset/storage/storage_test.go index ba8d4991288..31191c5f457 100644 --- a/pkg/registry/extensions/replicaset/storage/storage_test.go +++ b/pkg/registry/extensions/replicaset/storage/storage_test.go @@ -28,9 +28,11 @@ import ( "k8s.io/apimachinery/pkg/util/diff" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" "k8s.io/apiserver/pkg/registry/rest" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/autoscaling" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -47,7 +49,7 @@ func newStorage(t *testing.T) (*ReplicaSetStorage, *etcdtesting.EtcdTestServer) // createReplicaSet is a helper function that returns a ReplicaSet with the updated resource version. func createReplicaSet(storage *REST, rs extensions.ReplicaSet, t *testing.T) (extensions.ReplicaSet, error) { ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), rs.Namespace) - obj, err := storage.Create(ctx, &rs, false) + obj, err := storage.Create(ctx, &rs, rest.ValidateAllObjectFunc, false) if err != nil { t.Errorf("Failed to create ReplicaSet, %v", err) } @@ -94,7 +96,7 @@ func TestCreate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.ReplicaSet.Store.DestroyFunc() - test := registrytest.New(t, storage.ReplicaSet.Store) + test := genericregistrytest.New(t, storage.ReplicaSet.Store) rs := validNewReplicaSet() rs.ObjectMeta = metav1.ObjectMeta{} test.TestCreate( @@ -115,7 +117,7 @@ func TestUpdate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.ReplicaSet.Store.DestroyFunc() - test := registrytest.New(t, storage.ReplicaSet.Store) + test := genericregistrytest.New(t, storage.ReplicaSet.Store) test.TestUpdate( // valid validNewReplicaSet(), @@ -143,7 +145,7 @@ func TestDelete(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.ReplicaSet.Store.DestroyFunc() - test := registrytest.New(t, storage.ReplicaSet.Store) + test := genericregistrytest.New(t, storage.ReplicaSet.Store) test.TestDelete(validNewReplicaSet()) } @@ -169,7 +171,7 @@ func TestGenerationNumber(t *testing.T) { // Updates to spec should increment the generation number storedRS.Spec.Replicas += 1 - storage.ReplicaSet.Update(ctx, storedRS.Name, rest.DefaultUpdatedObjectInfo(storedRS)) + storage.ReplicaSet.Update(ctx, storedRS.Name, rest.DefaultUpdatedObjectInfo(storedRS), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -184,7 +186,7 @@ func TestGenerationNumber(t *testing.T) { // Updates to status should not increment either spec or status generation numbers storedRS.Status.Replicas += 1 - storage.ReplicaSet.Update(ctx, storedRS.Name, rest.DefaultUpdatedObjectInfo(storedRS)) + storage.ReplicaSet.Update(ctx, storedRS.Name, rest.DefaultUpdatedObjectInfo(storedRS), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -202,7 +204,7 @@ func TestGet(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.ReplicaSet.Store.DestroyFunc() - test := registrytest.New(t, storage.ReplicaSet.Store) + test := genericregistrytest.New(t, storage.ReplicaSet.Store) test.TestGet(validNewReplicaSet()) } @@ -210,7 +212,7 @@ func TestList(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.ReplicaSet.Store.DestroyFunc() - test := registrytest.New(t, storage.ReplicaSet.Store) + test := genericregistrytest.New(t, storage.ReplicaSet.Store) test.TestList(validNewReplicaSet()) } @@ -218,7 +220,7 @@ func TestWatch(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.ReplicaSet.Store.DestroyFunc() - test := registrytest.New(t, storage.ReplicaSet.Store) + test := genericregistrytest.New(t, storage.ReplicaSet.Store) test.TestWatch( validNewReplicaSet(), // matching labels @@ -261,7 +263,12 @@ func TestScaleGet(t *testing.T) { t.Fatalf("error setting new replica set (key: %s) %v: %v", key, validReplicaSet, err) } - want := &extensions.Scale{ + selector, err := metav1.LabelSelectorAsSelector(validReplicaSet.Spec.Selector) + if err != nil { + t.Fatal(err) + } + + want := &autoscaling.Scale{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: metav1.NamespaceDefault, @@ -269,16 +276,16 @@ func TestScaleGet(t *testing.T) { ResourceVersion: rs.ResourceVersion, CreationTimestamp: rs.CreationTimestamp, }, - Spec: extensions.ScaleSpec{ + Spec: autoscaling.ScaleSpec{ Replicas: validReplicaSet.Spec.Replicas, }, - Status: extensions.ScaleStatus{ + Status: autoscaling.ScaleStatus{ Replicas: validReplicaSet.Status.Replicas, - Selector: validReplicaSet.Spec.Selector, + Selector: selector.String(), }, } obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{}) - got := obj.(*extensions.Scale) + got := obj.(*autoscaling.Scale) if err != nil { t.Fatalf("error fetching scale for %s: %v", name, err) } @@ -301,17 +308,17 @@ func TestScaleUpdate(t *testing.T) { t.Fatalf("error setting new replica set (key: %s) %v: %v", key, validReplicaSet, err) } replicas := 12 - update := extensions.Scale{ + update := autoscaling.Scale{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: metav1.NamespaceDefault, }, - Spec: extensions.ScaleSpec{ + Spec: autoscaling.ScaleSpec{ Replicas: int32(replicas), }, } - if _, _, err := storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update)); err != nil { + if _, _, err := storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc); err != nil { t.Fatalf("error updating scale %v: %v", update, err) } @@ -319,7 +326,7 @@ func TestScaleUpdate(t *testing.T) { if err != nil { t.Fatalf("error fetching scale for %s: %v", name, err) } - scale := obj.(*extensions.Scale) + scale := obj.(*autoscaling.Scale) if scale.Spec.Replicas != int32(replicas) { t.Errorf("wrong replicas count expected: %d got: %d", replicas, scale.Spec.Replicas) } @@ -327,7 +334,7 @@ func TestScaleUpdate(t *testing.T) { update.ResourceVersion = rs.ResourceVersion update.Spec.Replicas = 15 - if _, _, err = storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update)); err != nil && !errors.IsConflict(err) { + if _, _, err = storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc); err != nil && !errors.IsConflict(err) { t.Fatalf("unexpected error, expecting an update conflict but got %v", err) } } @@ -352,7 +359,7 @@ func TestStatusUpdate(t *testing.T) { }, } - if _, _, err := storage.Status.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update)); err != nil { + if _, _, err := storage.Status.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc); err != nil { t.Fatalf("unexpected error: %v", err) } obj, err := storage.ReplicaSet.Get(ctx, "foo", &metav1.GetOptions{}) diff --git a/pkg/registry/extensions/replicaset/strategy.go b/pkg/registry/extensions/replicaset/strategy.go index 1bd5bf5181e..fac1f2a5a0b 100644 --- a/pkg/registry/extensions/replicaset/strategy.go +++ b/pkg/registry/extensions/replicaset/strategy.go @@ -36,7 +36,7 @@ import ( "k8s.io/apiserver/pkg/registry/rest" apistorage "k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/pod" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions/validation" @@ -49,11 +49,20 @@ type rsStrategy struct { } // Strategy is the default logic that applies when creating and updating ReplicaSet objects. -var Strategy = rsStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = rsStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} -// DefaultGarbageCollectionPolicy returns Orphan because that's the default -// behavior before the server-side garbage collection is implemented. -func (rsStrategy) DefaultGarbageCollectionPolicy() rest.GarbageCollectionPolicy { +// DefaultGarbageCollectionPolicy returns OrphanDependents by default. For apps/v1, returns DeleteDependents. +func (rsStrategy) DefaultGarbageCollectionPolicy(ctx genericapirequest.Context) rest.GarbageCollectionPolicy { + if requestInfo, found := genericapirequest.RequestInfoFrom(ctx); found { + groupVersion := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion} + switch groupVersion { + case extensionsv1beta1.SchemeGroupVersion, appsv1beta2.SchemeGroupVersion: + // for back compatibility + return rest.OrphanDependents + default: + return rest.DeleteDependents + } + } return rest.OrphanDependents } @@ -127,11 +136,9 @@ func (rsStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime switch groupVersion { case extensionsv1beta1.SchemeGroupVersion: // no-op for compatibility - case appsv1beta2.SchemeGroupVersion: + default: // disallow mutation of selector allErrs = append(allErrs, apivalidation.ValidateImmutableField(newReplicaSet.Spec.Selector, oldReplicaSet.Spec.Selector, field.NewPath("spec").Child("selector"))...) - default: - panic(fmt.Sprintf("unexpected group/version: %v", groupVersion)) } } diff --git a/pkg/registry/extensions/replicaset/strategy_test.go b/pkg/registry/extensions/replicaset/strategy_test.go index 27f7b9020e2..c2a98e3957f 100644 --- a/pkg/registry/extensions/replicaset/strategy_test.go +++ b/pkg/registry/extensions/replicaset/strategy_test.go @@ -23,7 +23,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/validation/field" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/kubernetes/pkg/api" + "k8s.io/apiserver/pkg/registry/rest" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" ) @@ -227,3 +228,57 @@ func newReplicaSetWithSelectorLabels(selectorLabels map[string]string) *extensio }, } } + +func TestReplicasetDefaultGarbageCollectionPolicy(t *testing.T) { + // Make sure we correctly implement the interface. + // Otherwise a typo could silently change the default. + var gcds rest.GarbageCollectionDeleteStrategy = Strategy + tests := []struct { + requestInfo genericapirequest.RequestInfo + expectedGCPolicy rest.GarbageCollectionPolicy + isNilRequestInfo bool + }{ + { + genericapirequest.RequestInfo{ + APIGroup: "extensions", + APIVersion: "v1beta1", + Resource: "replicasets", + }, + rest.OrphanDependents, + false, + }, + { + genericapirequest.RequestInfo{ + APIGroup: "apps", + APIVersion: "v1beta2", + Resource: "replicasets", + }, + rest.OrphanDependents, + false, + }, + { + genericapirequest.RequestInfo{ + APIGroup: "apps", + APIVersion: "v1", + Resource: "replicasets", + }, + rest.DeleteDependents, + false, + }, + { + expectedGCPolicy: rest.OrphanDependents, + isNilRequestInfo: true, + }, + } + + for _, test := range tests { + context := genericapirequest.NewContext() + if !test.isNilRequestInfo { + context = genericapirequest.WithRequestInfo(context, &test.requestInfo) + } + if got, want := gcds.DefaultGarbageCollectionPolicy(context), test.expectedGCPolicy; got != want { + t.Errorf("%s/%s: DefaultGarbageCollectionPolicy() = %#v, want %#v", test.requestInfo.APIGroup, + test.requestInfo.APIVersion, got, want) + } + } +} diff --git a/pkg/registry/extensions/rest/BUILD b/pkg/registry/extensions/rest/BUILD index 9348b41cf0e..c4e43a547e5 100644 --- a/pkg/registry/extensions/rest/BUILD +++ b/pkg/registry/extensions/rest/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["storage_extensions.go"], importpath = "k8s.io/kubernetes/pkg/registry/extensions/rest", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/registry/extensions/controller/storage:go_default_library", "//pkg/registry/extensions/daemonset/storage:go_default_library", diff --git a/pkg/registry/extensions/rest/storage_extensions.go b/pkg/registry/extensions/rest/storage_extensions.go index 57e85b3a6f8..6de5b96233a 100644 --- a/pkg/registry/extensions/rest/storage_extensions.go +++ b/pkg/registry/extensions/rest/storage_extensions.go @@ -22,7 +22,7 @@ import ( "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" serverstorage "k8s.io/apiserver/pkg/server/storage" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/extensions" expcontrollerstore "k8s.io/kubernetes/pkg/registry/extensions/controller/storage" daemonstore "k8s.io/kubernetes/pkg/registry/extensions/daemonset/storage" @@ -37,7 +37,7 @@ type RESTStorageProvider struct { } func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) { - apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(extensions.GroupName, api.Registry, api.Scheme, api.ParameterCodec, api.Codecs) + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(extensions.GroupName, legacyscheme.Registry, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs) // If you add a version here, be sure to add an entry in `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go with specific priorities. // TODO refactor the plumbing to provide the information in the APIGroupInfo diff --git a/pkg/registry/networking/networkpolicy/BUILD b/pkg/registry/networking/networkpolicy/BUILD index 91d65f40f7b..f5e83616620 100644 --- a/pkg/registry/networking/networkpolicy/BUILD +++ b/pkg/registry/networking/networkpolicy/BUILD @@ -14,7 +14,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/networking/networkpolicy", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/networking:go_default_library", "//pkg/apis/networking/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", diff --git a/pkg/registry/networking/networkpolicy/registry.go b/pkg/registry/networking/networkpolicy/registry.go index 42956e112b7..1ddcd2139f2 100644 --- a/pkg/registry/networking/networkpolicy/registry.go +++ b/pkg/registry/networking/networkpolicy/registry.go @@ -28,8 +28,8 @@ import ( // Registry is an interface for things that know how to store NetworkPolicies. type Registry interface { ListNetworkPolicies(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*networking.NetworkPolicyList, error) - CreateNetworkPolicy(ctx genericapirequest.Context, np *networking.NetworkPolicy) error - UpdateNetworkPolicy(ctx genericapirequest.Context, np *networking.NetworkPolicy) error + CreateNetworkPolicy(ctx genericapirequest.Context, np *networking.NetworkPolicy, createValidation rest.ValidateObjectFunc) error + UpdateNetworkPolicy(ctx genericapirequest.Context, np *networking.NetworkPolicy, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error GetNetworkPolicy(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (*networking.NetworkPolicy, error) DeleteNetworkPolicy(ctx genericapirequest.Context, name string) error WatchNetworkPolicies(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) @@ -55,13 +55,13 @@ func (s *storage) ListNetworkPolicies(ctx genericapirequest.Context, options *me return obj.(*networking.NetworkPolicyList), nil } -func (s *storage) CreateNetworkPolicy(ctx genericapirequest.Context, np *networking.NetworkPolicy) error { - _, err := s.Create(ctx, np, false) +func (s *storage) CreateNetworkPolicy(ctx genericapirequest.Context, np *networking.NetworkPolicy, createValidation rest.ValidateObjectFunc) error { + _, err := s.Create(ctx, np, createValidation, false) return err } -func (s *storage) UpdateNetworkPolicy(ctx genericapirequest.Context, np *networking.NetworkPolicy) error { - _, _, err := s.Update(ctx, np.Name, rest.DefaultUpdatedObjectInfo(np)) +func (s *storage) UpdateNetworkPolicy(ctx genericapirequest.Context, np *networking.NetworkPolicy, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error { + _, _, err := s.Update(ctx, np.Name, rest.DefaultUpdatedObjectInfo(np), createValidation, updateValidation) return err } diff --git a/pkg/registry/networking/networkpolicy/storage/BUILD b/pkg/registry/networking/networkpolicy/storage/BUILD index ceacb4ebed0..b8d6a19af8f 100644 --- a/pkg/registry/networking/networkpolicy/storage/BUILD +++ b/pkg/registry/networking/networkpolicy/storage/BUILD @@ -36,10 +36,10 @@ filegroup( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/networking/networkpolicy/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/networking:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -48,6 +48,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], ) diff --git a/pkg/registry/networking/networkpolicy/storage/storage_test.go b/pkg/registry/networking/networkpolicy/storage/storage_test.go index 89fff4c9e44..66e200dede8 100644 --- a/pkg/registry/networking/networkpolicy/storage/storage_test.go +++ b/pkg/registry/networking/networkpolicy/storage/storage_test.go @@ -25,8 +25,9 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/networking" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -71,7 +72,7 @@ func TestCreate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) np := validNetworkPolicy() np.ObjectMeta = metav1.ObjectMeta{GenerateName: "foo-"} test.TestCreate( @@ -89,7 +90,7 @@ func TestUpdate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestUpdate( // valid validNetworkPolicy(), @@ -132,7 +133,7 @@ func TestDelete(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestDelete(validNetworkPolicy()) } @@ -140,7 +141,7 @@ func TestGet(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestGet(validNetworkPolicy()) } @@ -148,7 +149,7 @@ func TestList(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestList(validNetworkPolicy()) } @@ -156,7 +157,7 @@ func TestWatch(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestWatch( validNetworkPolicy(), // matching labels diff --git a/pkg/registry/networking/networkpolicy/strategy.go b/pkg/registry/networking/networkpolicy/strategy.go index fe66f6f62e2..16e0b54a623 100644 --- a/pkg/registry/networking/networkpolicy/strategy.go +++ b/pkg/registry/networking/networkpolicy/strategy.go @@ -23,7 +23,7 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/networking" "k8s.io/kubernetes/pkg/apis/networking/validation" ) @@ -35,7 +35,7 @@ type networkPolicyStrategy struct { } // Strategy is the default logic that applies when creating and updating NetworkPolicy objects. -var Strategy = networkPolicyStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = networkPolicyStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} // NamespaceScoped returns true because all NetworkPolicies need to be within a namespace. func (networkPolicyStrategy) NamespaceScoped() bool { diff --git a/pkg/registry/networking/rest/BUILD b/pkg/registry/networking/rest/BUILD index 39308c04d35..699702bb157 100644 --- a/pkg/registry/networking/rest/BUILD +++ b/pkg/registry/networking/rest/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["storage_settings.go"], importpath = "k8s.io/kubernetes/pkg/registry/networking/rest", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/networking:go_default_library", "//pkg/registry/networking/networkpolicy/storage:go_default_library", "//vendor/k8s.io/api/networking/v1:go_default_library", diff --git a/pkg/registry/networking/rest/storage_settings.go b/pkg/registry/networking/rest/storage_settings.go index 4f6c42170af..e0489bcff57 100644 --- a/pkg/registry/networking/rest/storage_settings.go +++ b/pkg/registry/networking/rest/storage_settings.go @@ -22,7 +22,7 @@ import ( "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" serverstorage "k8s.io/apiserver/pkg/server/storage" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/networking" networkpolicystore "k8s.io/kubernetes/pkg/registry/networking/networkpolicy/storage" ) @@ -30,7 +30,7 @@ import ( type RESTStorageProvider struct{} func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) { - apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(networking.GroupName, api.Registry, api.Scheme, api.ParameterCodec, api.Codecs) + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(networking.GroupName, legacyscheme.Registry, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs) // If you add a version here, be sure to add an entry in `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go with specific priorities. // TODO refactor the plumbing to provide the information in the APIGroupInfo diff --git a/pkg/registry/policy/poddisruptionbudget/BUILD b/pkg/registry/policy/poddisruptionbudget/BUILD index 29a3d2e66dd..d7e8e00e6cb 100644 --- a/pkg/registry/policy/poddisruptionbudget/BUILD +++ b/pkg/registry/policy/poddisruptionbudget/BUILD @@ -14,7 +14,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/policy/poddisruptionbudget", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/policy:go_default_library", "//pkg/apis/policy/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", @@ -28,8 +28,8 @@ go_library( go_test( name = "go_default_test", srcs = ["strategy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/policy/poddisruptionbudget", - library = ":go_default_library", deps = [ "//pkg/apis/policy:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/registry/policy/poddisruptionbudget/storage/BUILD b/pkg/registry/policy/poddisruptionbudget/storage/BUILD index c78c833807d..fc91a4cde03 100644 --- a/pkg/registry/policy/poddisruptionbudget/storage/BUILD +++ b/pkg/registry/policy/poddisruptionbudget/storage/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/policy/poddisruptionbudget/storage", - library = ":go_default_library", deps = [ "//pkg/apis/policy:go_default_library", "//pkg/registry/registrytest:go_default_library", @@ -20,6 +20,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], diff --git a/pkg/registry/policy/poddisruptionbudget/storage/storage.go b/pkg/registry/policy/poddisruptionbudget/storage/storage.go index 9b3da8b8273..40aff589d1d 100644 --- a/pkg/registry/policy/poddisruptionbudget/storage/storage.go +++ b/pkg/registry/policy/poddisruptionbudget/storage/storage.go @@ -78,6 +78,6 @@ func (r *StatusREST) Get(ctx genericapirequest.Context, name string, options *me } // Update alters the status subset of an object. -func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - return r.store.Update(ctx, name, objInfo) +func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation) } diff --git a/pkg/registry/policy/poddisruptionbudget/storage/storage_test.go b/pkg/registry/policy/poddisruptionbudget/storage/storage_test.go index eb3acbdb924..4c67d4ca3da 100644 --- a/pkg/registry/policy/poddisruptionbudget/storage/storage_test.go +++ b/pkg/registry/policy/poddisruptionbudget/storage/storage_test.go @@ -25,6 +25,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" "k8s.io/apiserver/pkg/registry/rest" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" "k8s.io/kubernetes/pkg/apis/policy" @@ -41,7 +42,7 @@ func newStorage(t *testing.T) (*REST, *StatusREST, *etcdtesting.EtcdTestServer) // createPodDisruptionBudget is a helper function that returns a PodDisruptionBudget with the updated resource version. func createPodDisruptionBudget(storage *REST, pdb policy.PodDisruptionBudget, t *testing.T) (policy.PodDisruptionBudget, error) { ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), pdb.Namespace) - obj, err := storage.Create(ctx, &pdb, false) + obj, err := storage.Create(ctx, &pdb, rest.ValidateAllObjectFunc, false) if err != nil { t.Errorf("Failed to create PodDisruptionBudget, %v", err) } @@ -69,7 +70,7 @@ func TestCreate(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) pdb := validNewPodDisruptionBudget() pdb.ObjectMeta = metav1.ObjectMeta{} test.TestCreate( @@ -109,7 +110,7 @@ func TestStatusUpdate(t *testing.T) { }, } - if _, _, err := statusStorage.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update)); err != nil { + if _, _, err := statusStorage.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc); err != nil { t.Fatalf("unexpected error: %v", err) } obj, err = storage.Get(ctx, "foo", &metav1.GetOptions{}) @@ -130,7 +131,7 @@ func TestGet(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestGet(validNewPodDisruptionBudget()) } @@ -138,7 +139,7 @@ func TestList(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestList(validNewPodDisruptionBudget()) } @@ -146,7 +147,7 @@ func TestDelete(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestDelete(validNewPodDisruptionBudget()) } @@ -154,7 +155,7 @@ func TestWatch(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestWatch( validNewPodDisruptionBudget(), // matching labels diff --git a/pkg/registry/policy/poddisruptionbudget/strategy.go b/pkg/registry/policy/poddisruptionbudget/strategy.go index 43ef18c0a7a..382dc92ab2c 100644 --- a/pkg/registry/policy/poddisruptionbudget/strategy.go +++ b/pkg/registry/policy/poddisruptionbudget/strategy.go @@ -22,7 +22,7 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/policy" "k8s.io/kubernetes/pkg/apis/policy/validation" ) @@ -34,7 +34,7 @@ type podDisruptionBudgetStrategy struct { } // Strategy is the default logic that applies when creating and updating PodDisruptionBudget objects. -var Strategy = podDisruptionBudgetStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = podDisruptionBudgetStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} // NamespaceScoped returns true because all PodDisruptionBudget' need to be within a namespace. func (podDisruptionBudgetStrategy) NamespaceScoped() bool { diff --git a/pkg/registry/policy/rest/BUILD b/pkg/registry/policy/rest/BUILD index bc17898881b..95f5a480143 100644 --- a/pkg/registry/policy/rest/BUILD +++ b/pkg/registry/policy/rest/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["storage_policy.go"], importpath = "k8s.io/kubernetes/pkg/registry/policy/rest", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/policy:go_default_library", "//pkg/registry/policy/poddisruptionbudget/storage:go_default_library", "//vendor/k8s.io/api/policy/v1beta1:go_default_library", diff --git a/pkg/registry/policy/rest/storage_policy.go b/pkg/registry/policy/rest/storage_policy.go index a845f53e90f..2cb65949dc7 100644 --- a/pkg/registry/policy/rest/storage_policy.go +++ b/pkg/registry/policy/rest/storage_policy.go @@ -22,7 +22,7 @@ import ( "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" serverstorage "k8s.io/apiserver/pkg/server/storage" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/policy" poddisruptionbudgetstore "k8s.io/kubernetes/pkg/registry/policy/poddisruptionbudget/storage" ) @@ -30,7 +30,7 @@ import ( type RESTStorageProvider struct{} func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) { - apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(policy.GroupName, api.Registry, api.Scheme, api.ParameterCodec, api.Codecs) + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(policy.GroupName, legacyscheme.Registry, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs) // If you add a version here, be sure to add an entry in `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go with specific priorities. // TODO refactor the plumbing to provide the information in the APIGroupInfo diff --git a/pkg/registry/rbac/BUILD b/pkg/registry/rbac/BUILD index 432335889cb..353211bf0da 100644 --- a/pkg/registry/rbac/BUILD +++ b/pkg/registry/rbac/BUILD @@ -50,11 +50,11 @@ filegroup( go_test( name = "go_default_test", srcs = ["helpers_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/rbac", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", ], diff --git a/pkg/registry/rbac/clusterrole/BUILD b/pkg/registry/rbac/clusterrole/BUILD index d3fe5fb6fab..57fac87d96e 100644 --- a/pkg/registry/rbac/clusterrole/BUILD +++ b/pkg/registry/rbac/clusterrole/BUILD @@ -14,7 +14,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/rbac/clusterrole", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/rbac:go_default_library", "//pkg/apis/rbac/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", diff --git a/pkg/registry/rbac/clusterrole/policybased/BUILD b/pkg/registry/rbac/clusterrole/policybased/BUILD index 28a5d9a6a17..e58b32eac07 100644 --- a/pkg/registry/rbac/clusterrole/policybased/BUILD +++ b/pkg/registry/rbac/clusterrole/policybased/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["storage.go"], importpath = "k8s.io/kubernetes/pkg/registry/rbac/clusterrole/policybased", deps = [ - "//pkg/api/helper:go_default_library", + "//pkg/apis/core/helper:go_default_library", "//pkg/apis/rbac:go_default_library", "//pkg/registry/rbac:go_default_library", "//pkg/registry/rbac/validation:go_default_library", diff --git a/pkg/registry/rbac/clusterrole/policybased/storage.go b/pkg/registry/rbac/clusterrole/policybased/storage.go index 36650cbb789..0b32fdebf1c 100644 --- a/pkg/registry/rbac/clusterrole/policybased/storage.go +++ b/pkg/registry/rbac/clusterrole/policybased/storage.go @@ -18,11 +18,13 @@ limitations under the License. package policybased import ( - "k8s.io/apimachinery/pkg/api/errors" + "errors" + + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" - kapihelper "k8s.io/kubernetes/pkg/api/helper" + kapihelper "k8s.io/kubernetes/pkg/apis/core/helper" "k8s.io/kubernetes/pkg/apis/rbac" rbacregistry "k8s.io/kubernetes/pkg/registry/rbac" rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation" @@ -40,26 +42,39 @@ func NewStorage(s rest.StandardStorage, ruleResolver rbacregistryvalidation.Auth return &Storage{s, ruleResolver} } -func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { +var fullAuthority = []rbac.PolicyRule{ + rbac.NewRule("*").Groups("*").Resources("*").RuleOrDie(), + rbac.NewRule("*").URLs("*").RuleOrDie(), +} + +func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object, createValidatingAdmission rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) { if rbacregistry.EscalationAllowed(ctx) { - return s.StandardStorage.Create(ctx, obj, includeUninitialized) + return s.StandardStorage.Create(ctx, obj, createValidatingAdmission, includeUninitialized) } clusterRole := obj.(*rbac.ClusterRole) rules := clusterRole.Rules if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil { - return nil, errors.NewForbidden(groupResource, clusterRole.Name, err) + return nil, apierrors.NewForbidden(groupResource, clusterRole.Name, err) } - return s.StandardStorage.Create(ctx, obj, includeUninitialized) + // to set the aggregation rule, since it can gather anything, requires * on *.* + if hasAggregationRule(clusterRole) { + if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, fullAuthority); err != nil { + return nil, apierrors.NewForbidden(groupResource, clusterRole.Name, errors.New("must have cluster-admin privileges to use the aggregationRule")) + } + } + + return s.StandardStorage.Create(ctx, obj, createValidatingAdmission, includeUninitialized) } -func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.UpdatedObjectInfo) (runtime.Object, bool, error) { +func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { if rbacregistry.EscalationAllowed(ctx) { - return s.StandardStorage.Update(ctx, name, obj) + return s.StandardStorage.Update(ctx, name, obj, createValidation, updateValidation) } nonEscalatingInfo := rest.WrapUpdatedObjectInfo(obj, func(ctx genericapirequest.Context, obj runtime.Object, oldObj runtime.Object) (runtime.Object, error) { clusterRole := obj.(*rbac.ClusterRole) + oldClusterRole := oldObj.(*rbac.ClusterRole) // if we're only mutating fields needed for the GC to eventually delete this obj, return if rbacregistry.IsOnlyMutatingGCFields(obj, oldObj, kapihelper.Semantic) { @@ -68,10 +83,21 @@ func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.Up rules := clusterRole.Rules if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil { - return nil, errors.NewForbidden(groupResource, clusterRole.Name, err) + return nil, apierrors.NewForbidden(groupResource, clusterRole.Name, err) } + // to change the aggregation rule, since it can gather anything and prevent tightening, requires * on *.* + if hasAggregationRule(clusterRole) || hasAggregationRule(oldClusterRole) { + if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, fullAuthority); err != nil { + return nil, apierrors.NewForbidden(groupResource, clusterRole.Name, errors.New("must have cluster-admin privileges to use the aggregationRule")) + } + } + return obj, nil }) - return s.StandardStorage.Update(ctx, name, nonEscalatingInfo) + return s.StandardStorage.Update(ctx, name, nonEscalatingInfo, createValidation, updateValidation) +} + +func hasAggregationRule(clusterRole *rbac.ClusterRole) bool { + return clusterRole.AggregationRule != nil && len(clusterRole.AggregationRule.ClusterRoleSelectors) > 0 } diff --git a/pkg/registry/rbac/clusterrole/registry.go b/pkg/registry/rbac/clusterrole/registry.go index 639c04cf03b..b8b1a21b54a 100644 --- a/pkg/registry/rbac/clusterrole/registry.go +++ b/pkg/registry/rbac/clusterrole/registry.go @@ -28,8 +28,8 @@ import ( // Registry is an interface for things that know how to store ClusterRoles. type Registry interface { ListClusterRoles(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*rbac.ClusterRoleList, error) - CreateClusterRole(ctx genericapirequest.Context, clusterRole *rbac.ClusterRole) error - UpdateClusterRole(ctx genericapirequest.Context, clusterRole *rbac.ClusterRole) error + CreateClusterRole(ctx genericapirequest.Context, clusterRole *rbac.ClusterRole, createValidation rest.ValidateObjectFunc) error + UpdateClusterRole(ctx genericapirequest.Context, clusterRole *rbac.ClusterRole, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error GetClusterRole(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (*rbac.ClusterRole, error) DeleteClusterRole(ctx genericapirequest.Context, name string) error WatchClusterRoles(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) @@ -55,13 +55,13 @@ func (s *storage) ListClusterRoles(ctx genericapirequest.Context, options *metai return obj.(*rbac.ClusterRoleList), nil } -func (s *storage) CreateClusterRole(ctx genericapirequest.Context, clusterRole *rbac.ClusterRole) error { - _, err := s.Create(ctx, clusterRole, false) +func (s *storage) CreateClusterRole(ctx genericapirequest.Context, clusterRole *rbac.ClusterRole, createValidation rest.ValidateObjectFunc) error { + _, err := s.Create(ctx, clusterRole, createValidation, false) return err } -func (s *storage) UpdateClusterRole(ctx genericapirequest.Context, clusterRole *rbac.ClusterRole) error { - _, _, err := s.Update(ctx, clusterRole.Name, rest.DefaultUpdatedObjectInfo(clusterRole)) +func (s *storage) UpdateClusterRole(ctx genericapirequest.Context, clusterRole *rbac.ClusterRole, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error { + _, _, err := s.Update(ctx, clusterRole.Name, rest.DefaultUpdatedObjectInfo(clusterRole), createValidation, updateValidation) return err } diff --git a/pkg/registry/rbac/clusterrole/strategy.go b/pkg/registry/rbac/clusterrole/strategy.go index 301418a1411..e1be1f06cfd 100644 --- a/pkg/registry/rbac/clusterrole/strategy.go +++ b/pkg/registry/rbac/clusterrole/strategy.go @@ -22,7 +22,7 @@ import ( genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/rbac" "k8s.io/kubernetes/pkg/apis/rbac/validation" ) @@ -35,7 +35,7 @@ type strategy struct { // strategy is the default logic that applies when creating and updating // ClusterRole objects. -var Strategy = strategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = strategy{legacyscheme.Scheme, names.SimpleNameGenerator} // Strategy should implement rest.RESTCreateStrategy var _ rest.RESTCreateStrategy = Strategy diff --git a/pkg/registry/rbac/clusterrolebinding/BUILD b/pkg/registry/rbac/clusterrolebinding/BUILD index f6f2f8eff5f..3f7f3015b01 100644 --- a/pkg/registry/rbac/clusterrolebinding/BUILD +++ b/pkg/registry/rbac/clusterrolebinding/BUILD @@ -14,7 +14,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/rbac/clusterrolebinding", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/rbac:go_default_library", "//pkg/apis/rbac/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", diff --git a/pkg/registry/rbac/clusterrolebinding/policybased/BUILD b/pkg/registry/rbac/clusterrolebinding/policybased/BUILD index 5d18bdb26fd..2141978eec9 100644 --- a/pkg/registry/rbac/clusterrolebinding/policybased/BUILD +++ b/pkg/registry/rbac/clusterrolebinding/policybased/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["storage.go"], importpath = "k8s.io/kubernetes/pkg/registry/rbac/clusterrolebinding/policybased", deps = [ - "//pkg/api/helper:go_default_library", + "//pkg/apis/core/helper:go_default_library", "//pkg/apis/rbac:go_default_library", "//pkg/registry/rbac:go_default_library", "//pkg/registry/rbac/validation:go_default_library", diff --git a/pkg/registry/rbac/clusterrolebinding/policybased/storage.go b/pkg/registry/rbac/clusterrolebinding/policybased/storage.go index 95130b8c9c0..12264dba462 100644 --- a/pkg/registry/rbac/clusterrolebinding/policybased/storage.go +++ b/pkg/registry/rbac/clusterrolebinding/policybased/storage.go @@ -24,7 +24,7 @@ import ( "k8s.io/apiserver/pkg/authorization/authorizer" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" - kapihelper "k8s.io/kubernetes/pkg/api/helper" + kapihelper "k8s.io/kubernetes/pkg/apis/core/helper" "k8s.io/kubernetes/pkg/apis/rbac" rbacregistry "k8s.io/kubernetes/pkg/registry/rbac" rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation" @@ -44,14 +44,14 @@ func NewStorage(s rest.StandardStorage, authorizer authorizer.Authorizer, ruleRe return &Storage{s, authorizer, ruleResolver} } -func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { +func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) { if rbacregistry.EscalationAllowed(ctx) { - return s.StandardStorage.Create(ctx, obj, includeUninitialized) + return s.StandardStorage.Create(ctx, obj, createValidation, includeUninitialized) } clusterRoleBinding := obj.(*rbac.ClusterRoleBinding) if rbacregistry.BindingAuthorized(ctx, clusterRoleBinding.RoleRef, metav1.NamespaceNone, s.authorizer) { - return s.StandardStorage.Create(ctx, obj, includeUninitialized) + return s.StandardStorage.Create(ctx, obj, createValidation, includeUninitialized) } rules, err := s.ruleResolver.GetRoleReferenceRules(clusterRoleBinding.RoleRef, metav1.NamespaceNone) @@ -61,12 +61,12 @@ func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object, incl if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil { return nil, errors.NewForbidden(groupResource, clusterRoleBinding.Name, err) } - return s.StandardStorage.Create(ctx, obj, includeUninitialized) + return s.StandardStorage.Create(ctx, obj, createValidation, includeUninitialized) } -func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.UpdatedObjectInfo) (runtime.Object, bool, error) { +func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { if rbacregistry.EscalationAllowed(ctx) { - return s.StandardStorage.Update(ctx, name, obj) + return s.StandardStorage.Update(ctx, name, obj, createValidation, updateValidation) } nonEscalatingInfo := rest.WrapUpdatedObjectInfo(obj, func(ctx genericapirequest.Context, obj runtime.Object, oldObj runtime.Object) (runtime.Object, error) { @@ -93,5 +93,5 @@ func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.Up return obj, nil }) - return s.StandardStorage.Update(ctx, name, nonEscalatingInfo) + return s.StandardStorage.Update(ctx, name, nonEscalatingInfo, createValidation, updateValidation) } diff --git a/pkg/registry/rbac/clusterrolebinding/registry.go b/pkg/registry/rbac/clusterrolebinding/registry.go index 0a6b04895d1..2a3524e6ed3 100644 --- a/pkg/registry/rbac/clusterrolebinding/registry.go +++ b/pkg/registry/rbac/clusterrolebinding/registry.go @@ -28,8 +28,8 @@ import ( // Registry is an interface for things that know how to store ClusterRoleBindings. type Registry interface { ListClusterRoleBindings(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*rbac.ClusterRoleBindingList, error) - CreateClusterRoleBinding(ctx genericapirequest.Context, clusterRoleBinding *rbac.ClusterRoleBinding) error - UpdateClusterRoleBinding(ctx genericapirequest.Context, clusterRoleBinding *rbac.ClusterRoleBinding) error + CreateClusterRoleBinding(ctx genericapirequest.Context, clusterRoleBinding *rbac.ClusterRoleBinding, createValidation rest.ValidateObjectFunc) error + UpdateClusterRoleBinding(ctx genericapirequest.Context, clusterRoleBinding *rbac.ClusterRoleBinding, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error GetClusterRoleBinding(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (*rbac.ClusterRoleBinding, error) DeleteClusterRoleBinding(ctx genericapirequest.Context, name string) error WatchClusterRoleBindings(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) @@ -55,13 +55,13 @@ func (s *storage) ListClusterRoleBindings(ctx genericapirequest.Context, options return obj.(*rbac.ClusterRoleBindingList), nil } -func (s *storage) CreateClusterRoleBinding(ctx genericapirequest.Context, clusterRoleBinding *rbac.ClusterRoleBinding) error { - _, err := s.Create(ctx, clusterRoleBinding, false) +func (s *storage) CreateClusterRoleBinding(ctx genericapirequest.Context, clusterRoleBinding *rbac.ClusterRoleBinding, createValidation rest.ValidateObjectFunc) error { + _, err := s.Create(ctx, clusterRoleBinding, createValidation, false) return err } -func (s *storage) UpdateClusterRoleBinding(ctx genericapirequest.Context, clusterRoleBinding *rbac.ClusterRoleBinding) error { - _, _, err := s.Update(ctx, clusterRoleBinding.Name, rest.DefaultUpdatedObjectInfo(clusterRoleBinding)) +func (s *storage) UpdateClusterRoleBinding(ctx genericapirequest.Context, clusterRoleBinding *rbac.ClusterRoleBinding, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error { + _, _, err := s.Update(ctx, clusterRoleBinding.Name, rest.DefaultUpdatedObjectInfo(clusterRoleBinding), createValidation, updateValidation) return err } diff --git a/pkg/registry/rbac/clusterrolebinding/strategy.go b/pkg/registry/rbac/clusterrolebinding/strategy.go index e1d83ecf4ab..1b2e33772d4 100644 --- a/pkg/registry/rbac/clusterrolebinding/strategy.go +++ b/pkg/registry/rbac/clusterrolebinding/strategy.go @@ -22,7 +22,7 @@ import ( genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/rbac" "k8s.io/kubernetes/pkg/apis/rbac/validation" ) @@ -35,7 +35,7 @@ type strategy struct { // strategy is the default logic that applies when creating and updating // ClusterRoleBinding objects. -var Strategy = strategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = strategy{legacyscheme.Scheme, names.SimpleNameGenerator} // Strategy should implement rest.RESTCreateStrategy var _ rest.RESTCreateStrategy = Strategy diff --git a/pkg/registry/rbac/escalation_check.go b/pkg/registry/rbac/escalation_check.go index 4fa0d59ceaf..4ac1392fbbd 100644 --- a/pkg/registry/rbac/escalation_check.go +++ b/pkg/registry/rbac/escalation_check.go @@ -79,12 +79,12 @@ func BindingAuthorized(ctx genericapirequest.Context, roleRef rbac.RoleRef, bind return false } - ok, _, err := a.Authorize(attrs) + decision, _, err := a.Authorize(attrs) if err != nil { utilruntime.HandleError(fmt.Errorf( "error authorizing user %#v to bind %#v in namespace %s: %v", user, roleRef, bindingNamespace, err, )) } - return ok + return decision == authorizer.DecisionAllow } diff --git a/pkg/registry/rbac/helpers_test.go b/pkg/registry/rbac/helpers_test.go index a371c7964c5..755b82a026d 100644 --- a/pkg/registry/rbac/helpers_test.go +++ b/pkg/registry/rbac/helpers_test.go @@ -21,8 +21,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - kapi "k8s.io/kubernetes/pkg/api" - kapihelper "k8s.io/kubernetes/pkg/api/helper" + kapi "k8s.io/kubernetes/pkg/apis/core" + kapihelper "k8s.io/kubernetes/pkg/apis/core/helper" ) func newPod() *kapi.Pod { diff --git a/pkg/registry/rbac/reconciliation/BUILD b/pkg/registry/rbac/reconciliation/BUILD index a9d398dd8ee..f26d72e35de 100644 --- a/pkg/registry/rbac/reconciliation/BUILD +++ b/pkg/registry/rbac/reconciliation/BUILD @@ -12,12 +12,13 @@ go_test( "reconcile_role_test.go", "reconcile_rolebindings_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/rbac/reconciliation", - library = ":go_default_library", deps = [ - "//pkg/api/helper:go_default_library", + "//pkg/apis/core/helper:go_default_library", "//pkg/apis/rbac:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", ], ) @@ -34,14 +35,14 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/rbac/reconciliation", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/rbac:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion:go_default_library", "//pkg/registry/rbac/validation:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", ], diff --git a/pkg/registry/rbac/reconciliation/clusterrole_interfaces.go b/pkg/registry/rbac/reconciliation/clusterrole_interfaces.go index cd4f221f37d..419cc1df26b 100644 --- a/pkg/registry/rbac/reconciliation/clusterrole_interfaces.go +++ b/pkg/registry/rbac/reconciliation/clusterrole_interfaces.go @@ -66,6 +66,14 @@ func (o ClusterRoleRuleOwner) SetRules(in []rbac.PolicyRule) { o.ClusterRole.Rules = in } +func (o ClusterRoleRuleOwner) GetAggregationRule() *rbac.AggregationRule { + return o.ClusterRole.AggregationRule +} + +func (o ClusterRoleRuleOwner) SetAggregationRule(in *rbac.AggregationRule) { + o.ClusterRole.AggregationRule = in +} + type ClusterRoleModifier struct { Client internalversion.ClusterRoleInterface } diff --git a/pkg/registry/rbac/reconciliation/reconcile_role.go b/pkg/registry/rbac/reconciliation/reconcile_role.go index 873b329b0a6..b4605991011 100644 --- a/pkg/registry/rbac/reconciliation/reconcile_role.go +++ b/pkg/registry/rbac/reconciliation/reconcile_role.go @@ -20,7 +20,9 @@ import ( "fmt" "reflect" + "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/kubernetes/pkg/apis/rbac" "k8s.io/kubernetes/pkg/registry/rbac/validation" @@ -51,6 +53,8 @@ type RuleOwner interface { SetAnnotations(map[string]string) GetRules() []rbac.PolicyRule SetRules([]rbac.PolicyRule) + GetAggregationRule() *rbac.AggregationRule + SetAggregationRule(*rbac.AggregationRule) DeepCopyRuleOwner() RuleOwner } @@ -75,6 +79,11 @@ type ReconcileClusterRoleResult struct { // ExtraRules contains extra permissions the currently persisted role had ExtraRules []rbac.PolicyRule + // MissingAggregationRuleSelectors contains expected selectors that were missing from the currently persisted role + MissingAggregationRuleSelectors []metav1.LabelSelector + // ExtraAggregationRuleSelectors contains extra selectors the currently persisted role had + ExtraAggregationRuleSelectors []metav1.LabelSelector + // Operation is the API operation required to reconcile. // If no reconciliation was needed, it is set to ReconcileNone. // If options.Confirm == false, the reconcile was in dry-run mode, so the operation was not performed. @@ -101,10 +110,15 @@ func (o *ReconcileRoleOptions) run(attempts int) (*ReconcileClusterRoleResult, e existing, err := o.Client.Get(o.Role.GetNamespace(), o.Role.GetName()) switch { case errors.IsNotFound(err): + aggregationRule := o.Role.GetAggregationRule() + if aggregationRule == nil { + aggregationRule = &rbac.AggregationRule{} + } result = &ReconcileClusterRoleResult{ - Role: o.Role, - MissingRules: o.Role.GetRules(), - Operation: ReconcileCreate, + Role: o.Role, + MissingRules: o.Role.GetRules(), + MissingAggregationRuleSelectors: aggregationRule.ClusterRoleSelectors, + Operation: ReconcileCreate, } case err != nil: @@ -195,6 +209,26 @@ func computeReconciledRole(existing, expected RuleOwner, removeExtraPermissions result.Operation = ReconcileUpdate } + // Compute extra and missing rules + _, result.ExtraAggregationRuleSelectors = aggregationRuleCovers(expected.GetAggregationRule(), existing.GetAggregationRule()) + _, result.MissingAggregationRuleSelectors = aggregationRuleCovers(existing.GetAggregationRule(), expected.GetAggregationRule()) + + switch { + case !removeExtraPermissions && len(result.MissingAggregationRuleSelectors) > 0: + // add missing rules in the union case + aggregationRule := result.Role.GetAggregationRule() + if aggregationRule == nil { + aggregationRule = &rbac.AggregationRule{} + } + aggregationRule.ClusterRoleSelectors = append(aggregationRule.ClusterRoleSelectors, result.MissingAggregationRuleSelectors...) + result.Role.SetAggregationRule(aggregationRule) + result.Operation = ReconcileUpdate + + case removeExtraPermissions && (len(result.MissingAggregationRuleSelectors) > 0 || len(result.ExtraAggregationRuleSelectors) > 0): + result.Role.SetAggregationRule(expected.GetAggregationRule()) + result.Operation = ReconcileUpdate + } + return result, nil } @@ -211,3 +245,37 @@ func merge(maps ...map[string]string) map[string]string { } return output } + +// aggregationRuleCovers determines whether or not the ownerSelectors cover the servantSelectors in terms of semantically +// equal label selectors. +// It returns whether or not the ownerSelectors cover and a list of the rules that the ownerSelectors do not cover. +func aggregationRuleCovers(ownerRule, servantRule *rbac.AggregationRule) (bool, []metav1.LabelSelector) { + switch { + case ownerRule == nil && servantRule == nil: + return true, []metav1.LabelSelector{} + case ownerRule == nil && servantRule != nil: + return false, servantRule.ClusterRoleSelectors + case ownerRule != nil && servantRule == nil: + return true, []metav1.LabelSelector{} + + } + + ownerSelectors := ownerRule.ClusterRoleSelectors + servantSelectors := servantRule.ClusterRoleSelectors + uncoveredSelectors := []metav1.LabelSelector{} + + for _, servantSelector := range servantSelectors { + covered := false + for _, ownerSelector := range ownerSelectors { + if equality.Semantic.DeepEqual(ownerSelector, servantSelector) { + covered = true + break + } + } + if !covered { + uncoveredSelectors = append(uncoveredSelectors, servantSelector) + } + } + + return (len(uncoveredSelectors) == 0), uncoveredSelectors +} diff --git a/pkg/registry/rbac/reconciliation/reconcile_role_test.go b/pkg/registry/rbac/reconciliation/reconcile_role_test.go index 9cd146648fa..1d30c9cad64 100644 --- a/pkg/registry/rbac/reconciliation/reconcile_role_test.go +++ b/pkg/registry/rbac/reconciliation/reconcile_role_test.go @@ -20,12 +20,16 @@ import ( "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api/helper" + "k8s.io/apimachinery/pkg/util/diff" + "k8s.io/kubernetes/pkg/apis/core/helper" "k8s.io/kubernetes/pkg/apis/rbac" ) func role(rules []rbac.PolicyRule, labels map[string]string, annotations map[string]string) *rbac.ClusterRole { - return &rbac.ClusterRole{Rules: rules, ObjectMeta: metav1.ObjectMeta{Labels: labels, Annotations: annotations}} + return &rbac.ClusterRole{ + Rules: rules, + ObjectMeta: metav1.ObjectMeta{Labels: labels, Annotations: annotations}, + } } func rules(resources ...string) []rbac.PolicyRule { @@ -38,7 +42,7 @@ func rules(resources ...string) []rbac.PolicyRule { type ss map[string]string -func TestComputeReconciledRole(t *testing.T) { +func TestComputeReconciledRoleRules(t *testing.T) { tests := map[string]struct { expectedRole *rbac.ClusterRole actualRole *rbac.ClusterRole @@ -273,3 +277,96 @@ func TestComputeReconciledRole(t *testing.T) { } } } + +func aggregatedRole(aggregationRule *rbac.AggregationRule) *rbac.ClusterRole { + return &rbac.ClusterRole{ + AggregationRule: aggregationRule, + } +} + +func aggregationrule(selectors []map[string]string) *rbac.AggregationRule { + ret := &rbac.AggregationRule{} + for _, selector := range selectors { + ret.ClusterRoleSelectors = append(ret.ClusterRoleSelectors, + metav1.LabelSelector{MatchLabels: selector}) + } + return ret +} + +func TestComputeReconciledRoleAggregationRules(t *testing.T) { + tests := map[string]struct { + expectedRole *rbac.ClusterRole + actualRole *rbac.ClusterRole + removeExtraPermissions bool + + expectedReconciledRole *rbac.ClusterRole + expectedReconciliationNeeded bool + }{ + "empty": { + expectedRole: aggregatedRole(&rbac.AggregationRule{}), + actualRole: aggregatedRole(nil), + removeExtraPermissions: true, + + expectedReconciledRole: nil, + expectedReconciliationNeeded: false, + }, + "empty-2": { + expectedRole: aggregatedRole(&rbac.AggregationRule{}), + actualRole: aggregatedRole(&rbac.AggregationRule{}), + removeExtraPermissions: true, + + expectedReconciledRole: nil, + expectedReconciliationNeeded: false, + }, + "match without union": { + expectedRole: aggregatedRole(aggregationrule([]map[string]string{{"foo": "bar"}})), + actualRole: aggregatedRole(aggregationrule([]map[string]string{{"foo": "bar"}})), + removeExtraPermissions: true, + + expectedReconciledRole: nil, + expectedReconciliationNeeded: false, + }, + "match with union": { + expectedRole: aggregatedRole(aggregationrule([]map[string]string{{"foo": "bar"}})), + actualRole: aggregatedRole(aggregationrule([]map[string]string{{"foo": "bar"}})), + removeExtraPermissions: false, + + expectedReconciledRole: nil, + expectedReconciliationNeeded: false, + }, + "different rules without union": { + expectedRole: aggregatedRole(aggregationrule([]map[string]string{{"foo": "bar"}})), + actualRole: aggregatedRole(aggregationrule([]map[string]string{{"alpha": "bravo"}})), + removeExtraPermissions: true, + + expectedReconciledRole: aggregatedRole(aggregationrule([]map[string]string{{"foo": "bar"}})), + expectedReconciliationNeeded: true, + }, + "different rules with union": { + expectedRole: aggregatedRole(aggregationrule([]map[string]string{{"foo": "bar"}})), + actualRole: aggregatedRole(aggregationrule([]map[string]string{{"alpha": "bravo"}})), + removeExtraPermissions: false, + + expectedReconciledRole: aggregatedRole(aggregationrule([]map[string]string{{"alpha": "bravo"}, {"foo": "bar"}})), + expectedReconciliationNeeded: true, + }, + } + + for k, tc := range tests { + actualRole := ClusterRoleRuleOwner{ClusterRole: tc.actualRole} + expectedRole := ClusterRoleRuleOwner{ClusterRole: tc.expectedRole} + result, err := computeReconciledRole(actualRole, expectedRole, tc.removeExtraPermissions) + if err != nil { + t.Errorf("%s: %v", k, err) + continue + } + reconciliationNeeded := result.Operation != ReconcileNone + if reconciliationNeeded != tc.expectedReconciliationNeeded { + t.Errorf("%s: Expected\n\t%v\ngot\n\t%v", k, tc.expectedReconciliationNeeded, reconciliationNeeded) + continue + } + if reconciliationNeeded && !helper.Semantic.DeepEqual(result.Role.(ClusterRoleRuleOwner).ClusterRole, tc.expectedReconciledRole) { + t.Errorf("%s: %v", k, diff.ObjectDiff(tc.expectedReconciledRole, result.Role.(ClusterRoleRuleOwner).ClusterRole)) + } + } +} diff --git a/pkg/registry/rbac/reconciliation/reconcile_rolebindings_test.go b/pkg/registry/rbac/reconciliation/reconcile_rolebindings_test.go index b61b2540eeb..0cdd3cf46b8 100644 --- a/pkg/registry/rbac/reconciliation/reconcile_rolebindings_test.go +++ b/pkg/registry/rbac/reconciliation/reconcile_rolebindings_test.go @@ -19,7 +19,7 @@ package reconciliation import ( "testing" - "k8s.io/kubernetes/pkg/api/helper" + "k8s.io/kubernetes/pkg/apis/core/helper" "k8s.io/kubernetes/pkg/apis/rbac" ) diff --git a/pkg/registry/rbac/reconciliation/role_interfaces.go b/pkg/registry/rbac/reconciliation/role_interfaces.go index f671c8c43b5..b46e9e872ea 100644 --- a/pkg/registry/rbac/reconciliation/role_interfaces.go +++ b/pkg/registry/rbac/reconciliation/role_interfaces.go @@ -20,7 +20,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/rbac" core "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion" @@ -69,6 +69,13 @@ func (o RoleRuleOwner) SetRules(in []rbac.PolicyRule) { o.Role.Rules = in } +func (o RoleRuleOwner) GetAggregationRule() *rbac.AggregationRule { + return nil +} + +func (o RoleRuleOwner) SetAggregationRule(in *rbac.AggregationRule) { +} + type RoleModifier struct { Client internalversion.RolesGetter NamespaceClient core.NamespaceInterface diff --git a/pkg/registry/rbac/reconciliation/rolebinding_interfaces.go b/pkg/registry/rbac/reconciliation/rolebinding_interfaces.go index 2f26fc95413..126f479cb1e 100644 --- a/pkg/registry/rbac/reconciliation/rolebinding_interfaces.go +++ b/pkg/registry/rbac/reconciliation/rolebinding_interfaces.go @@ -21,7 +21,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/rbac" core "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion" diff --git a/pkg/registry/rbac/reconciliation/zz_generated.deepcopy.go b/pkg/registry/rbac/reconciliation/zz_generated.deepcopy.go index b99814e331f..d165cb3b1ec 100644 --- a/pkg/registry/rbac/reconciliation/zz_generated.deepcopy.go +++ b/pkg/registry/rbac/reconciliation/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,35 +21,9 @@ limitations under the License. package reconciliation import ( - conversion "k8s.io/apimachinery/pkg/conversion" rbac "k8s.io/kubernetes/pkg/apis/rbac" - reflect "reflect" ) -// GetGeneratedDeepCopyFuncs returns the generated funcs, since we aren't registering them. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func GetGeneratedDeepCopyFuncs() []conversion.GeneratedDeepCopyFunc { - return []conversion.GeneratedDeepCopyFunc{ - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterRoleBindingAdapter).DeepCopyInto(out.(*ClusterRoleBindingAdapter)) - return nil - }, InType: reflect.TypeOf(&ClusterRoleBindingAdapter{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterRoleRuleOwner).DeepCopyInto(out.(*ClusterRoleRuleOwner)) - return nil - }, InType: reflect.TypeOf(&ClusterRoleRuleOwner{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RoleBindingAdapter).DeepCopyInto(out.(*RoleBindingAdapter)) - return nil - }, InType: reflect.TypeOf(&RoleBindingAdapter{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RoleRuleOwner).DeepCopyInto(out.(*RoleRuleOwner)) - return nil - }, InType: reflect.TypeOf(&RoleRuleOwner{})}, - } -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterRoleBindingAdapter) DeepCopyInto(out *ClusterRoleBindingAdapter) { *out = *in diff --git a/pkg/registry/rbac/rest/BUILD b/pkg/registry/rbac/rest/BUILD index deed8ead29c..685a8334bf1 100644 --- a/pkg/registry/rbac/rest/BUILD +++ b/pkg/registry/rbac/rest/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["storage_rbac.go"], importpath = "k8s.io/kubernetes/pkg/registry/rbac/rest", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/rbac:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion:go_default_library", @@ -33,6 +33,7 @@ go_library( "//vendor/k8s.io/api/rbac/v1:go_default_library", "//vendor/k8s.io/api/rbac/v1alpha1:go_default_library", "//vendor/k8s.io/api/rbac/v1beta1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", diff --git a/pkg/registry/rbac/rest/storage_rbac.go b/pkg/registry/rbac/rest/storage_rbac.go index 9061b4c39e1..67f34604c49 100644 --- a/pkg/registry/rbac/rest/storage_rbac.go +++ b/pkg/registry/rbac/rest/storage_rbac.go @@ -26,6 +26,7 @@ import ( rbacapiv1 "k8s.io/api/rbac/v1" rbacapiv1alpha1 "k8s.io/api/rbac/v1alpha1" rbacapiv1beta1 "k8s.io/api/rbac/v1beta1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -36,7 +37,7 @@ import ( genericapiserver "k8s.io/apiserver/pkg/server" serverstorage "k8s.io/apiserver/pkg/server/storage" "k8s.io/client-go/util/retry" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/rbac" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" rbacclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion" @@ -66,7 +67,7 @@ type RESTStorageProvider struct { var _ genericapiserver.PostStartHookProvider = RESTStorageProvider{} func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) { - apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(rbac.GroupName, api.Registry, api.Scheme, api.ParameterCodec, api.Codecs) + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(rbac.GroupName, legacyscheme.Registry, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs) // If you add a version here, be sure to add an entry in `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go with specific priorities. // TODO refactor the plumbing to provide the information in the APIGroupInfo @@ -134,10 +135,11 @@ func (p RESTStorageProvider) storage(version schema.GroupVersion, apiResourceCon func (p RESTStorageProvider) PostStartHook() (string, genericapiserver.PostStartHookFunc, error) { policy := &PolicyData{ - ClusterRoles: append(bootstrappolicy.ClusterRoles(), bootstrappolicy.ControllerRoles()...), - ClusterRoleBindings: append(bootstrappolicy.ClusterRoleBindings(), bootstrappolicy.ControllerRoleBindings()...), - Roles: bootstrappolicy.NamespaceRoles(), - RoleBindings: bootstrappolicy.NamespaceRoleBindings(), + ClusterRoles: append(bootstrappolicy.ClusterRoles(), bootstrappolicy.ControllerRoles()...), + ClusterRoleBindings: append(bootstrappolicy.ClusterRoleBindings(), bootstrappolicy.ControllerRoleBindings()...), + Roles: bootstrappolicy.NamespaceRoles(), + RoleBindings: bootstrappolicy.NamespaceRoleBindings(), + ClusterRolesToAggregate: bootstrappolicy.ClusterRolesToAggregate(), } return PostStartHookName, policy.EnsureRBACPolicy(), nil } @@ -147,6 +149,8 @@ type PolicyData struct { ClusterRoleBindings []rbac.ClusterRoleBinding Roles map[string][]rbac.Role RoleBindings map[string][]rbac.RoleBinding + // ClusterRolesToAggregate maps from previous clusterrole name to the new clusterrole name + ClusterRolesToAggregate map[string]string } func (p *PolicyData) EnsureRBACPolicy() genericapiserver.PostStartHookFunc { @@ -176,6 +180,13 @@ func (p *PolicyData) EnsureRBACPolicy() genericapiserver.PostStartHookFunc { return false, nil } + // if the new cluster roles to aggregate do not yet exist, then we need to copy the old roles if they don't exist + // in new locations + if err := primeAggregatedClusterRoles(p.ClusterRolesToAggregate, clientset); err != nil { + utilruntime.HandleError(fmt.Errorf("unable to prime aggregated clusterroles: %v", err)) + return false, nil + } + // ensure bootstrap roles are created or reconciled for _, clusterRole := range p.ClusterRoles { opts := reconciliation.ReconcileRoleOptions{ @@ -253,7 +264,7 @@ func (p *PolicyData) EnsureRBACPolicy() genericapiserver.PostStartHookFunc { case result.Operation == reconciliation.ReconcileUpdate: glog.Infof("updated role.%s/%s in %v with additional permissions: %v", rbac.GroupName, role.Name, namespace, result.MissingRules) case result.Operation == reconciliation.ReconcileCreate: - glog.Infof("created role.%s/%s in %v ", rbac.GroupName, role.Name, namespace) + glog.Infof("created role.%s/%s in %v", rbac.GroupName, role.Name, namespace) } return nil }) @@ -310,3 +321,33 @@ func (p *PolicyData) EnsureRBACPolicy() genericapiserver.PostStartHookFunc { func (p RESTStorageProvider) GroupName() string { return rbac.GroupName } + +// primeAggregatedClusterRoles copies roles that have transitioned to aggregated roles and may need to pick up changes +// that were done to the legacy roles. +func primeAggregatedClusterRoles(clusterRolesToAggregate map[string]string, clusterRoleClient rbacclient.ClusterRolesGetter) error { + for oldName, newName := range clusterRolesToAggregate { + _, err := clusterRoleClient.ClusterRoles().Get(newName, metav1.GetOptions{}) + if err == nil { + continue + } + if !apierrors.IsNotFound(err) { + return err + } + + existingRole, err := clusterRoleClient.ClusterRoles().Get(oldName, metav1.GetOptions{}) + if apierrors.IsNotFound(err) { + continue + } + if err != nil { + return err + } + glog.V(1).Infof("migrating %v to %v", existingRole.Name, newName) + existingRole.Name = newName + existingRole.ResourceVersion = "" // clear this so the object can be created. + if _, err := clusterRoleClient.ClusterRoles().Create(existingRole); err != nil && !apierrors.IsAlreadyExists(err) { + return err + } + } + + return nil +} diff --git a/pkg/registry/rbac/role/BUILD b/pkg/registry/rbac/role/BUILD index 3f3d6c3bb90..a4fde9839b4 100644 --- a/pkg/registry/rbac/role/BUILD +++ b/pkg/registry/rbac/role/BUILD @@ -14,7 +14,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/rbac/role", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/rbac:go_default_library", "//pkg/apis/rbac/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", diff --git a/pkg/registry/rbac/role/policybased/BUILD b/pkg/registry/rbac/role/policybased/BUILD index 88098c0eb83..65fa12025f0 100644 --- a/pkg/registry/rbac/role/policybased/BUILD +++ b/pkg/registry/rbac/role/policybased/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["storage.go"], importpath = "k8s.io/kubernetes/pkg/registry/rbac/role/policybased", deps = [ - "//pkg/api/helper:go_default_library", + "//pkg/apis/core/helper:go_default_library", "//pkg/apis/rbac:go_default_library", "//pkg/registry/rbac:go_default_library", "//pkg/registry/rbac/validation:go_default_library", diff --git a/pkg/registry/rbac/role/policybased/storage.go b/pkg/registry/rbac/role/policybased/storage.go index 1f2443d2b09..d67e3777fe0 100644 --- a/pkg/registry/rbac/role/policybased/storage.go +++ b/pkg/registry/rbac/role/policybased/storage.go @@ -22,7 +22,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" - kapihelper "k8s.io/kubernetes/pkg/api/helper" + kapihelper "k8s.io/kubernetes/pkg/apis/core/helper" "k8s.io/kubernetes/pkg/apis/rbac" rbacregistry "k8s.io/kubernetes/pkg/registry/rbac" rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation" @@ -40,9 +40,9 @@ func NewStorage(s rest.StandardStorage, ruleResolver rbacregistryvalidation.Auth return &Storage{s, ruleResolver} } -func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { +func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) { if rbacregistry.EscalationAllowed(ctx) { - return s.StandardStorage.Create(ctx, obj, includeUninitialized) + return s.StandardStorage.Create(ctx, obj, createValidation, includeUninitialized) } role := obj.(*rbac.Role) @@ -50,12 +50,12 @@ func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object, incl if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil { return nil, errors.NewForbidden(groupResource, role.Name, err) } - return s.StandardStorage.Create(ctx, obj, includeUninitialized) + return s.StandardStorage.Create(ctx, obj, createValidation, includeUninitialized) } -func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.UpdatedObjectInfo) (runtime.Object, bool, error) { +func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { if rbacregistry.EscalationAllowed(ctx) { - return s.StandardStorage.Update(ctx, name, obj) + return s.StandardStorage.Update(ctx, name, obj, createValidation, updateValidation) } nonEscalatingInfo := rest.WrapUpdatedObjectInfo(obj, func(ctx genericapirequest.Context, obj runtime.Object, oldObj runtime.Object) (runtime.Object, error) { @@ -73,5 +73,5 @@ func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.Up return obj, nil }) - return s.StandardStorage.Update(ctx, name, nonEscalatingInfo) + return s.StandardStorage.Update(ctx, name, nonEscalatingInfo, createValidation, updateValidation) } diff --git a/pkg/registry/rbac/role/registry.go b/pkg/registry/rbac/role/registry.go index 7d7a47f8416..bbf26b5e23d 100644 --- a/pkg/registry/rbac/role/registry.go +++ b/pkg/registry/rbac/role/registry.go @@ -28,8 +28,8 @@ import ( // Registry is an interface for things that know how to store Roles. type Registry interface { ListRoles(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*rbac.RoleList, error) - CreateRole(ctx genericapirequest.Context, role *rbac.Role) error - UpdateRole(ctx genericapirequest.Context, role *rbac.Role) error + CreateRole(ctx genericapirequest.Context, role *rbac.Role, createValidation rest.ValidateObjectFunc) error + UpdateRole(ctx genericapirequest.Context, role *rbac.Role, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error GetRole(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (*rbac.Role, error) DeleteRole(ctx genericapirequest.Context, name string) error WatchRoles(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) @@ -55,13 +55,14 @@ func (s *storage) ListRoles(ctx genericapirequest.Context, options *metainternal return obj.(*rbac.RoleList), nil } -func (s *storage) CreateRole(ctx genericapirequest.Context, role *rbac.Role) error { - _, err := s.Create(ctx, role, false) +func (s *storage) CreateRole(ctx genericapirequest.Context, role *rbac.Role, createValidation rest.ValidateObjectFunc) error { + _, err := s.Create(ctx, role, createValidation, false) return err } -func (s *storage) UpdateRole(ctx genericapirequest.Context, role *rbac.Role) error { - _, _, err := s.Update(ctx, role.Name, rest.DefaultUpdatedObjectInfo(role)) +func (s *storage) UpdateRole(ctx genericapirequest.Context, role *rbac.Role, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error { + // TODO: any admission? + _, _, err := s.Update(ctx, role.Name, rest.DefaultUpdatedObjectInfo(role), createValidation, updateValidation) return err } diff --git a/pkg/registry/rbac/role/strategy.go b/pkg/registry/rbac/role/strategy.go index 619cfeed125..b815f98db40 100644 --- a/pkg/registry/rbac/role/strategy.go +++ b/pkg/registry/rbac/role/strategy.go @@ -22,7 +22,7 @@ import ( genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/rbac" "k8s.io/kubernetes/pkg/apis/rbac/validation" ) @@ -35,7 +35,7 @@ type strategy struct { // strategy is the default logic that applies when creating and updating // Role objects. -var Strategy = strategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = strategy{legacyscheme.Scheme, names.SimpleNameGenerator} // Strategy should implement rest.RESTCreateStrategy var _ rest.RESTCreateStrategy = Strategy diff --git a/pkg/registry/rbac/rolebinding/BUILD b/pkg/registry/rbac/rolebinding/BUILD index 25e1e543a3a..e3072531138 100644 --- a/pkg/registry/rbac/rolebinding/BUILD +++ b/pkg/registry/rbac/rolebinding/BUILD @@ -14,7 +14,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/rbac/rolebinding", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/rbac:go_default_library", "//pkg/apis/rbac/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", diff --git a/pkg/registry/rbac/rolebinding/policybased/BUILD b/pkg/registry/rbac/rolebinding/policybased/BUILD index 05e10263fb4..fd05f21b463 100644 --- a/pkg/registry/rbac/rolebinding/policybased/BUILD +++ b/pkg/registry/rbac/rolebinding/policybased/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["storage.go"], importpath = "k8s.io/kubernetes/pkg/registry/rbac/rolebinding/policybased", deps = [ - "//pkg/api/helper:go_default_library", + "//pkg/apis/core/helper:go_default_library", "//pkg/apis/rbac:go_default_library", "//pkg/registry/rbac:go_default_library", "//pkg/registry/rbac/validation:go_default_library", diff --git a/pkg/registry/rbac/rolebinding/policybased/storage.go b/pkg/registry/rbac/rolebinding/policybased/storage.go index 5f775d5c491..9e539a77aca 100644 --- a/pkg/registry/rbac/rolebinding/policybased/storage.go +++ b/pkg/registry/rbac/rolebinding/policybased/storage.go @@ -23,7 +23,7 @@ import ( "k8s.io/apiserver/pkg/authorization/authorizer" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" - kapihelper "k8s.io/kubernetes/pkg/api/helper" + kapihelper "k8s.io/kubernetes/pkg/apis/core/helper" "k8s.io/kubernetes/pkg/apis/rbac" rbacregistry "k8s.io/kubernetes/pkg/registry/rbac" rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation" @@ -43,9 +43,9 @@ func NewStorage(s rest.StandardStorage, authorizer authorizer.Authorizer, ruleRe return &Storage{s, authorizer, ruleResolver} } -func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { +func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) { if rbacregistry.EscalationAllowed(ctx) { - return s.StandardStorage.Create(ctx, obj, includeUninitialized) + return s.StandardStorage.Create(ctx, obj, createValidation, includeUninitialized) } // Get the namespace from the context (populated from the URL). @@ -57,7 +57,7 @@ func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object, incl roleBinding := obj.(*rbac.RoleBinding) if rbacregistry.BindingAuthorized(ctx, roleBinding.RoleRef, namespace, s.authorizer) { - return s.StandardStorage.Create(ctx, obj, includeUninitialized) + return s.StandardStorage.Create(ctx, obj, createValidation, includeUninitialized) } rules, err := s.ruleResolver.GetRoleReferenceRules(roleBinding.RoleRef, namespace) @@ -67,12 +67,12 @@ func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object, incl if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil { return nil, errors.NewForbidden(groupResource, roleBinding.Name, err) } - return s.StandardStorage.Create(ctx, obj, includeUninitialized) + return s.StandardStorage.Create(ctx, obj, createValidation, includeUninitialized) } -func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.UpdatedObjectInfo) (runtime.Object, bool, error) { +func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { if rbacregistry.EscalationAllowed(ctx) { - return s.StandardStorage.Update(ctx, name, obj) + return s.StandardStorage.Update(ctx, name, obj, createValidation, updateValidation) } nonEscalatingInfo := rest.WrapUpdatedObjectInfo(obj, func(ctx genericapirequest.Context, obj runtime.Object, oldObj runtime.Object) (runtime.Object, error) { @@ -106,5 +106,5 @@ func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.Up return obj, nil }) - return s.StandardStorage.Update(ctx, name, nonEscalatingInfo) + return s.StandardStorage.Update(ctx, name, nonEscalatingInfo, createValidation, updateValidation) } diff --git a/pkg/registry/rbac/rolebinding/registry.go b/pkg/registry/rbac/rolebinding/registry.go index 5c4a0270412..c708d088bb0 100644 --- a/pkg/registry/rbac/rolebinding/registry.go +++ b/pkg/registry/rbac/rolebinding/registry.go @@ -28,8 +28,8 @@ import ( // Registry is an interface for things that know how to store RoleBindings. type Registry interface { ListRoleBindings(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*rbac.RoleBindingList, error) - CreateRoleBinding(ctx genericapirequest.Context, roleBinding *rbac.RoleBinding) error - UpdateRoleBinding(ctx genericapirequest.Context, roleBinding *rbac.RoleBinding) error + CreateRoleBinding(ctx genericapirequest.Context, roleBinding *rbac.RoleBinding, createValidation rest.ValidateObjectFunc) error + UpdateRoleBinding(ctx genericapirequest.Context, roleBinding *rbac.RoleBinding, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error GetRoleBinding(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (*rbac.RoleBinding, error) DeleteRoleBinding(ctx genericapirequest.Context, name string) error WatchRoleBindings(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) @@ -55,14 +55,14 @@ func (s *storage) ListRoleBindings(ctx genericapirequest.Context, options *metai return obj.(*rbac.RoleBindingList), nil } -func (s *storage) CreateRoleBinding(ctx genericapirequest.Context, roleBinding *rbac.RoleBinding) error { +func (s *storage) CreateRoleBinding(ctx genericapirequest.Context, roleBinding *rbac.RoleBinding, createValidation rest.ValidateObjectFunc) error { // TODO(ericchiang): add additional validation - _, err := s.Create(ctx, roleBinding, false) + _, err := s.Create(ctx, roleBinding, createValidation, false) return err } -func (s *storage) UpdateRoleBinding(ctx genericapirequest.Context, roleBinding *rbac.RoleBinding) error { - _, _, err := s.Update(ctx, roleBinding.Name, rest.DefaultUpdatedObjectInfo(roleBinding)) +func (s *storage) UpdateRoleBinding(ctx genericapirequest.Context, roleBinding *rbac.RoleBinding, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error { + _, _, err := s.Update(ctx, roleBinding.Name, rest.DefaultUpdatedObjectInfo(roleBinding), createValidation, updateValidation) return err } diff --git a/pkg/registry/rbac/rolebinding/strategy.go b/pkg/registry/rbac/rolebinding/strategy.go index 9d4781a50c3..136cffd34b9 100644 --- a/pkg/registry/rbac/rolebinding/strategy.go +++ b/pkg/registry/rbac/rolebinding/strategy.go @@ -22,7 +22,7 @@ import ( genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/rbac" "k8s.io/kubernetes/pkg/apis/rbac/validation" ) @@ -35,7 +35,7 @@ type strategy struct { // strategy is the default logic that applies when creating and updating // RoleBinding objects. -var Strategy = strategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = strategy{legacyscheme.Scheme, names.SimpleNameGenerator} // Strategy should implement rest.RESTCreateStrategy var _ rest.RESTCreateStrategy = Strategy diff --git a/pkg/registry/rbac/validation/BUILD b/pkg/registry/rbac/validation/BUILD index 7618b8ff4ec..b48ca3d71c5 100644 --- a/pkg/registry/rbac/validation/BUILD +++ b/pkg/registry/rbac/validation/BUILD @@ -13,8 +13,8 @@ go_test( "policy_comparator_test.go", "rule_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/rbac/validation", - library = ":go_default_library", deps = [ "//pkg/apis/rbac:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/registry/rbac/validation/policy_comparator.go b/pkg/registry/rbac/validation/policy_comparator.go index 6c69c24fb22..4b2ba515814 100644 --- a/pkg/registry/rbac/validation/policy_comparator.go +++ b/pkg/registry/rbac/validation/policy_comparator.go @@ -105,6 +105,31 @@ func hasAll(set, contains []string) bool { return true } +func resourceCoversAll(setResources, coversResources []string) bool { + // if we have a star or an exact match on all resources, then we match + if has(setResources, rbac.ResourceAll) || hasAll(setResources, coversResources) { + return true + } + + for _, path := range coversResources { + // if we have an exact match, then we match. + if has(setResources, path) { + continue + } + // if we're not a subresource, then we definitely don't match. fail. + if !strings.Contains(path, "/") { + return false + } + tokens := strings.SplitN(path, "/", 2) + resourceToCheck := "*/" + tokens[1] + if !has(setResources, resourceToCheck) { + return false + } + } + + return true +} + func nonResourceURLsCoversAll(set, covers []string) bool { for _, path := range covers { covered := false @@ -133,7 +158,7 @@ func nonResourceURLCovers(ownerPath, subPath string) bool { func ruleCovers(ownerRule, subRule rbac.PolicyRule) bool { verbMatches := has(ownerRule.Verbs, rbac.VerbAll) || hasAll(ownerRule.Verbs, subRule.Verbs) groupMatches := has(ownerRule.APIGroups, rbac.APIGroupAll) || hasAll(ownerRule.APIGroups, subRule.APIGroups) - resourceMatches := has(ownerRule.Resources, rbac.ResourceAll) || hasAll(ownerRule.Resources, subRule.Resources) + resourceMatches := resourceCoversAll(ownerRule.Resources, subRule.Resources) nonResourceURLMatches := nonResourceURLsCoversAll(ownerRule.NonResourceURLs, subRule.NonResourceURLs) resourceNameMatches := false diff --git a/pkg/registry/rbac/validation/policy_comparator_test.go b/pkg/registry/rbac/validation/policy_comparator_test.go index fd892bf372e..b8b947f72ea 100644 --- a/pkg/registry/rbac/validation/policy_comparator_test.go +++ b/pkg/registry/rbac/validation/policy_comparator_test.go @@ -45,6 +45,20 @@ func TestCoversExactMatch(t *testing.T) { }.test(t) } +func TestCoversSubresourceWildcard(t *testing.T) { + escalationTest{ + ownerRules: []rbac.PolicyRule{ + {APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"*/scale"}}, + }, + servantRules: []rbac.PolicyRule{ + {APIGroups: []string{"v1"}, Verbs: []string{"get"}, Resources: []string{"foo/scale"}}, + }, + + expectedCovered: true, + expectedUncoveredRules: []rbac.PolicyRule{}, + }.test(t) +} + func TestCoversMultipleRulesCoveringSingleRule(t *testing.T) { escalationTest{ ownerRules: []rbac.PolicyRule{ diff --git a/pkg/registry/registrytest/BUILD b/pkg/registry/registrytest/BUILD index d1eab9003c5..e2601ad3998 100644 --- a/pkg/registry/registrytest/BUILD +++ b/pkg/registry/registrytest/BUILD @@ -19,25 +19,18 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/registrytest", deps = [ - "//pkg/api:go_default_library", "//pkg/api/testapi:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/util/slice:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", - "//vendor/k8s.io/apiserver/pkg/registry/rest/resttest:go_default_library", - "//vendor/k8s.io/apiserver/pkg/storage/etcd:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library", - "//vendor/k8s.io/apiserver/pkg/storage/testing:go_default_library", ], ) diff --git a/pkg/registry/registrytest/endpoint.go b/pkg/registry/registrytest/endpoint.go index cf0fc4877ad..170b9595542 100644 --- a/pkg/registry/registrytest/endpoint.go +++ b/pkg/registry/registrytest/endpoint.go @@ -25,7 +25,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/watch" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/kubernetes/pkg/api" + "k8s.io/apiserver/pkg/registry/rest" + api "k8s.io/kubernetes/pkg/apis/core" ) // Registry is an interface for things that know how to store endpoints. @@ -66,7 +67,7 @@ func (e *EndpointRegistry) WatchEndpoints(ctx genericapirequest.Context, options return nil, fmt.Errorf("unimplemented!") } -func (e *EndpointRegistry) UpdateEndpoints(ctx genericapirequest.Context, endpoints *api.Endpoints) error { +func (e *EndpointRegistry) UpdateEndpoints(ctx genericapirequest.Context, endpoints *api.Endpoints, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error { // TODO: support namespaces in this mock e.lock.Lock() defer e.lock.Unlock() diff --git a/pkg/registry/registrytest/etcd.go b/pkg/registry/registrytest/etcd.go index 4d8dcac1721..061959d9d2a 100644 --- a/pkg/registry/registrytest/etcd.go +++ b/pkg/registry/registrytest/etcd.go @@ -17,219 +17,15 @@ limitations under the License. package registrytest import ( - "fmt" "testing" - "k8s.io/apimachinery/pkg/api/errors" - "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" - genericapirequest "k8s.io/apiserver/pkg/endpoints/request" - genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" - "k8s.io/apiserver/pkg/registry/rest/resttest" - etcdstorage "k8s.io/apiserver/pkg/storage/etcd" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" "k8s.io/apiserver/pkg/storage/storagebackend" - storagetesting "k8s.io/apiserver/pkg/storage/testing" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" ) func NewEtcdStorage(t *testing.T, group string) (*storagebackend.Config, *etcdtesting.EtcdTestServer) { - server, config := etcdtesting.NewUnsecuredEtcd3TestClientServer(t, api.Scheme) + server, config := etcdtesting.NewUnsecuredEtcd3TestClientServer(t) config.Codec = testapi.Groups[group].StorageCodec() return config, server } - -type Tester struct { - tester *resttest.Tester - storage *genericregistry.Store -} -type UpdateFunc func(runtime.Object) runtime.Object - -func New(t *testing.T, storage *genericregistry.Store) *Tester { - return &Tester{ - tester: resttest.New(t, storage, api.Scheme), - storage: storage, - } -} - -func (t *Tester) TestNamespace() string { - return t.tester.TestNamespace() -} - -func (t *Tester) ClusterScope() *Tester { - t.tester = t.tester.ClusterScope() - return t -} - -func (t *Tester) Namer(namer func(int) string) *Tester { - t.tester = t.tester.Namer(namer) - return t -} - -func (t *Tester) AllowCreateOnUpdate() *Tester { - t.tester = t.tester.AllowCreateOnUpdate() - return t -} - -func (t *Tester) GeneratesName() *Tester { - t.tester = t.tester.GeneratesName() - return t -} - -func (t *Tester) ReturnDeletedObject() *Tester { - t.tester = t.tester.ReturnDeletedObject() - return t -} - -func (t *Tester) TestCreate(valid runtime.Object, invalid ...runtime.Object) { - t.tester.TestCreate( - valid, - t.createObject, - t.getObject, - invalid..., - ) -} - -func (t *Tester) TestUpdate(valid runtime.Object, validUpdateFunc UpdateFunc, invalidUpdateFunc ...UpdateFunc) { - var invalidFuncs []resttest.UpdateFunc - for _, f := range invalidUpdateFunc { - invalidFuncs = append(invalidFuncs, resttest.UpdateFunc(f)) - } - t.tester.TestUpdate( - valid, - t.createObject, - t.getObject, - resttest.UpdateFunc(validUpdateFunc), - invalidFuncs..., - ) -} - -func (t *Tester) TestDelete(valid runtime.Object) { - t.tester.TestDelete( - valid, - t.createObject, - t.getObject, - errors.IsNotFound, - ) -} - -func (t *Tester) TestDeleteGraceful(valid runtime.Object, expectedGrace int64) { - t.tester.TestDeleteGraceful( - valid, - t.createObject, - t.getObject, - expectedGrace, - ) -} - -func (t *Tester) TestGet(valid runtime.Object) { - t.tester.TestGet(valid) -} - -func (t *Tester) TestList(valid runtime.Object) { - t.tester.TestList( - valid, - t.setObjectsForList, - ) -} - -func (t *Tester) TestWatch(valid runtime.Object, labelsPass, labelsFail []labels.Set, fieldsPass, fieldsFail []fields.Set) { - t.tester.TestWatch( - valid, - t.emitObject, - labelsPass, - labelsFail, - fieldsPass, - fieldsFail, - // TODO: This should be filtered, the registry should not be aware of this level of detail - []string{etcdstorage.EtcdCreate, etcdstorage.EtcdDelete}, - ) -} - -// ============================================================================= -// get codec based on runtime.Object -func getCodec(obj runtime.Object) (runtime.Codec, error) { - fqKinds, _, err := api.Scheme.ObjectKinds(obj) - if err != nil { - return nil, fmt.Errorf("unexpected encoding error: %v", err) - } - fqKind := fqKinds[0] - // TODO: caesarxuchao: we should detect which group an object belongs to - // by using the version returned by Schem.ObjectVersionAndKind() once we - // split the schemes for internal objects. - // TODO: caesarxuchao: we should add a map from kind to group in Scheme. - var codec runtime.Codec - if api.Scheme.Recognizes(api.Registry.GroupOrDie(api.GroupName).GroupVersion.WithKind(fqKind.Kind)) { - codec = testapi.Default.Codec() - } else if api.Scheme.Recognizes(testapi.Extensions.GroupVersion().WithKind(fqKind.Kind)) { - codec = testapi.Extensions.Codec() - } else { - return nil, fmt.Errorf("unexpected kind: %v", fqKind) - } - return codec, nil -} - -// Helper functions - -func (t *Tester) getObject(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error) { - accessor, err := meta.Accessor(obj) - if err != nil { - return nil, err - } - - result, err := t.storage.Get(ctx, accessor.GetName(), &metav1.GetOptions{}) - if err != nil { - return nil, err - } - return result, nil -} - -func (t *Tester) createObject(ctx genericapirequest.Context, obj runtime.Object) error { - accessor, err := meta.Accessor(obj) - if err != nil { - return err - } - key, err := t.storage.KeyFunc(ctx, accessor.GetName()) - if err != nil { - return err - } - return t.storage.Storage.Create(ctx, key, obj, nil, 0) -} - -func (t *Tester) setObjectsForList(objects []runtime.Object) []runtime.Object { - key := t.storage.KeyRootFunc(t.tester.TestContext()) - if _, err := t.storage.DeleteCollection(t.tester.TestContext(), nil, nil); err != nil { - t.tester.Errorf("unable to clear collection: %v", err) - return nil - } - if err := storagetesting.CreateObjList(key, t.storage.Storage, objects); err != nil { - t.tester.Errorf("unexpected error: %v", err) - return nil - } - return objects -} - -func (t *Tester) emitObject(obj runtime.Object, action string) error { - ctx := t.tester.TestContext() - var err error - - switch action { - case etcdstorage.EtcdCreate: - err = t.createObject(ctx, obj) - case etcdstorage.EtcdDelete: - var accessor metav1.Object - accessor, err = meta.Accessor(obj) - if err != nil { - return err - } - _, _, err = t.storage.Delete(ctx, accessor.GetName(), nil) - default: - err = fmt.Errorf("unexpected action: %v", action) - } - - return err -} diff --git a/pkg/registry/registrytest/node.go b/pkg/registry/registrytest/node.go index e853d47a39f..e4d813dfe92 100644 --- a/pkg/registry/registrytest/node.go +++ b/pkg/registry/registrytest/node.go @@ -24,7 +24,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/watch" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // NodeRegistry implements node.Registry interface. diff --git a/pkg/registry/registrytest/service.go b/pkg/registry/registrytest/service.go index 8b5313a03fa..136d34153bd 100644 --- a/pkg/registry/registrytest/service.go +++ b/pkg/registry/registrytest/service.go @@ -23,7 +23,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/watch" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/kubernetes/pkg/api" + "k8s.io/apiserver/pkg/registry/rest" + api "k8s.io/kubernetes/pkg/apis/core" ) func NewServiceRegistry() *ServiceRegistry { @@ -72,7 +73,7 @@ func (r *ServiceRegistry) ListServices(ctx genericapirequest.Context, options *m return res, r.Err } -func (r *ServiceRegistry) CreateService(ctx genericapirequest.Context, svc *api.Service) (*api.Service, error) { +func (r *ServiceRegistry) CreateService(ctx genericapirequest.Context, svc *api.Service, createValidation rest.ValidateObjectFunc) (*api.Service, error) { r.mu.Lock() defer r.mu.Unlock() @@ -99,7 +100,7 @@ func (r *ServiceRegistry) DeleteService(ctx genericapirequest.Context, id string return r.Err } -func (r *ServiceRegistry) UpdateService(ctx genericapirequest.Context, svc *api.Service) (*api.Service, error) { +func (r *ServiceRegistry) UpdateService(ctx genericapirequest.Context, svc *api.Service, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (*api.Service, error) { r.mu.Lock() defer r.mu.Unlock() diff --git a/pkg/registry/scheduling/priorityclass/BUILD b/pkg/registry/scheduling/priorityclass/BUILD index e149d7c3fdc..f8b8351970b 100644 --- a/pkg/registry/scheduling/priorityclass/BUILD +++ b/pkg/registry/scheduling/priorityclass/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["strategy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/scheduling/priorityclass", - library = ":go_default_library", deps = [ "//pkg/apis/scheduling:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -27,7 +27,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/scheduling/priorityclass", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/scheduling:go_default_library", "//pkg/apis/scheduling/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", diff --git a/pkg/registry/scheduling/priorityclass/registry.go b/pkg/registry/scheduling/priorityclass/registry.go index 9c98af93026..5e4c44dc161 100644 --- a/pkg/registry/scheduling/priorityclass/registry.go +++ b/pkg/registry/scheduling/priorityclass/registry.go @@ -28,8 +28,8 @@ import ( // Registry is an interface for things that know how to store PriorityClass. type Registry interface { ListPriorityClasses(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*scheduling.PriorityClassList, error) - CreatePriorityClass(ctx genericapirequest.Context, pc *scheduling.PriorityClass) error - UpdatePriorityClass(ctx genericapirequest.Context, pc *scheduling.PriorityClass) error + CreatePriorityClass(ctx genericapirequest.Context, pc *scheduling.PriorityClass, createValidation rest.ValidateObjectFunc) error + UpdatePriorityClass(ctx genericapirequest.Context, pc *scheduling.PriorityClass, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error GetPriorityClass(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (*scheduling.PriorityClass, error) DeletePriorityClass(ctx genericapirequest.Context, name string) error WatchPriorityClasses(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) @@ -55,13 +55,13 @@ func (s *storage) ListPriorityClasses(ctx genericapirequest.Context, options *me return obj.(*scheduling.PriorityClassList), nil } -func (s *storage) CreatePriorityClass(ctx genericapirequest.Context, pc *scheduling.PriorityClass) error { - _, err := s.Create(ctx, pc, false) +func (s *storage) CreatePriorityClass(ctx genericapirequest.Context, pc *scheduling.PriorityClass, createValidation rest.ValidateObjectFunc) error { + _, err := s.Create(ctx, pc, createValidation, false) return err } -func (s *storage) UpdatePriorityClass(ctx genericapirequest.Context, pc *scheduling.PriorityClass) error { - _, _, err := s.Update(ctx, pc.Name, rest.DefaultUpdatedObjectInfo(pc)) +func (s *storage) UpdatePriorityClass(ctx genericapirequest.Context, pc *scheduling.PriorityClass, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error { + _, _, err := s.Update(ctx, pc.Name, rest.DefaultUpdatedObjectInfo(pc), createValidation, updateValidation) return err } diff --git a/pkg/registry/scheduling/priorityclass/storage/BUILD b/pkg/registry/scheduling/priorityclass/storage/BUILD index ce83faea11f..bf7a5ca6347 100644 --- a/pkg/registry/scheduling/priorityclass/storage/BUILD +++ b/pkg/registry/scheduling/priorityclass/storage/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/scheduling/priorityclass/storage", - library = ":go_default_library", deps = [ "//pkg/apis/scheduling:go_default_library", "//pkg/registry/registrytest:go_default_library", @@ -19,6 +19,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], ) diff --git a/pkg/registry/scheduling/priorityclass/storage/storage_test.go b/pkg/registry/scheduling/priorityclass/storage/storage_test.go index 75a99f690a5..8ef95fd59f5 100644 --- a/pkg/registry/scheduling/priorityclass/storage/storage_test.go +++ b/pkg/registry/scheduling/priorityclass/storage/storage_test.go @@ -24,6 +24,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" "k8s.io/kubernetes/pkg/apis/scheduling" "k8s.io/kubernetes/pkg/registry/registrytest" @@ -55,7 +56,7 @@ func TestCreate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() test.TestCreate( validNewPriorityClass(), // invalid cases @@ -74,7 +75,7 @@ func TestUpdate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() test.TestUpdate( // valid validNewPriorityClass(), @@ -100,7 +101,7 @@ func TestDelete(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() test.TestDelete(validNewPriorityClass()) } @@ -108,7 +109,7 @@ func TestGet(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() test.TestGet(validNewPriorityClass()) } @@ -116,7 +117,7 @@ func TestList(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() test.TestList(validNewPriorityClass()) } @@ -124,7 +125,7 @@ func TestWatch(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() test.TestWatch( validNewPriorityClass(), // matching labels diff --git a/pkg/registry/scheduling/priorityclass/strategy.go b/pkg/registry/scheduling/priorityclass/strategy.go index 6a6e060eef7..f39dce1df1a 100644 --- a/pkg/registry/scheduling/priorityclass/strategy.go +++ b/pkg/registry/scheduling/priorityclass/strategy.go @@ -21,7 +21,7 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/scheduling" "k8s.io/kubernetes/pkg/apis/scheduling/validation" ) @@ -33,7 +33,7 @@ type priorityClassStrategy struct { } // Strategy is the default logic that applies when creating and updating PriorityClass objects. -var Strategy = priorityClassStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = priorityClassStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} // NamespaceScoped returns true because all PriorityClasses are global. func (priorityClassStrategy) NamespaceScoped() bool { diff --git a/pkg/registry/scheduling/rest/BUILD b/pkg/registry/scheduling/rest/BUILD index 94c5f2dedbc..54e6dd25ce1 100644 --- a/pkg/registry/scheduling/rest/BUILD +++ b/pkg/registry/scheduling/rest/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["storage_scheduling.go"], importpath = "k8s.io/kubernetes/pkg/registry/scheduling/rest", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/scheduling:go_default_library", "//pkg/apis/scheduling/v1alpha1:go_default_library", "//pkg/registry/scheduling/priorityclass/storage:go_default_library", diff --git a/pkg/registry/scheduling/rest/storage_scheduling.go b/pkg/registry/scheduling/rest/storage_scheduling.go index 49418a645ba..f56a5865382 100644 --- a/pkg/registry/scheduling/rest/storage_scheduling.go +++ b/pkg/registry/scheduling/rest/storage_scheduling.go @@ -21,7 +21,7 @@ import ( "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" serverstorage "k8s.io/apiserver/pkg/server/storage" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/scheduling" schedulingapiv1alpha1 "k8s.io/kubernetes/pkg/apis/scheduling/v1alpha1" priorityclassstore "k8s.io/kubernetes/pkg/registry/scheduling/priorityclass/storage" @@ -30,7 +30,7 @@ import ( type RESTStorageProvider struct{} func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) { - apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(scheduling.GroupName, api.Registry, api.Scheme, api.ParameterCodec, api.Codecs) + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(scheduling.GroupName, legacyscheme.Registry, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs) if apiResourceConfigSource.AnyResourcesForVersionEnabled(schedulingapiv1alpha1.SchemeGroupVersion) { apiGroupInfo.VersionedResourcesStorageMap[schedulingapiv1alpha1.SchemeGroupVersion.Version] = p.v1alpha1Storage(apiResourceConfigSource, restOptionsGetter) diff --git a/pkg/registry/settings/podpreset/BUILD b/pkg/registry/settings/podpreset/BUILD index ce9b7143413..5d58bda6a7d 100644 --- a/pkg/registry/settings/podpreset/BUILD +++ b/pkg/registry/settings/podpreset/BUILD @@ -14,7 +14,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/settings/podpreset", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/pod:go_default_library", "//pkg/apis/settings:go_default_library", "//pkg/apis/settings/validation:go_default_library", diff --git a/pkg/registry/settings/podpreset/registry.go b/pkg/registry/settings/podpreset/registry.go index 5af80a3aa66..74b056d0e67 100644 --- a/pkg/registry/settings/podpreset/registry.go +++ b/pkg/registry/settings/podpreset/registry.go @@ -28,8 +28,8 @@ import ( // Registry is an interface for things that know how to store PodPresets. type Registry interface { ListPodPresets(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*settings.PodPresetList, error) - CreatePodPreset(ctx genericapirequest.Context, pp *settings.PodPreset) error - UpdatePodPreset(ctx genericapirequest.Context, pp *settings.PodPreset) error + CreatePodPreset(ctx genericapirequest.Context, pp *settings.PodPreset, createValidation rest.ValidateObjectFunc) error + UpdatePodPreset(ctx genericapirequest.Context, pp *settings.PodPreset, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error GetPodPreset(ctx genericapirequest.Context, ppID string, options *metav1.GetOptions) (*settings.PodPreset, error) DeletePodPreset(ctx genericapirequest.Context, ppID string) error WatchPodPresets(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) @@ -55,13 +55,13 @@ func (s *storage) ListPodPresets(ctx genericapirequest.Context, options *metaint return obj.(*settings.PodPresetList), nil } -func (s *storage) CreatePodPreset(ctx genericapirequest.Context, pp *settings.PodPreset) error { - _, err := s.Create(ctx, pp, false) +func (s *storage) CreatePodPreset(ctx genericapirequest.Context, pp *settings.PodPreset, createValidation rest.ValidateObjectFunc) error { + _, err := s.Create(ctx, pp, createValidation, false) return err } -func (s *storage) UpdatePodPreset(ctx genericapirequest.Context, pp *settings.PodPreset) error { - _, _, err := s.Update(ctx, pp.Name, rest.DefaultUpdatedObjectInfo(pp)) +func (s *storage) UpdatePodPreset(ctx genericapirequest.Context, pp *settings.PodPreset, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) error { + _, _, err := s.Update(ctx, pp.Name, rest.DefaultUpdatedObjectInfo(pp), createValidation, updateValidation) return err } diff --git a/pkg/registry/settings/podpreset/storage/BUILD b/pkg/registry/settings/podpreset/storage/BUILD index 584b94f53f0..a53d46c8639 100644 --- a/pkg/registry/settings/podpreset/storage/BUILD +++ b/pkg/registry/settings/podpreset/storage/BUILD @@ -35,10 +35,10 @@ filegroup( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/settings/podpreset/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/settings:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -46,6 +46,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], ) diff --git a/pkg/registry/settings/podpreset/storage/storage_test.go b/pkg/registry/settings/podpreset/storage/storage_test.go index e1b81eff893..bd10b6e7d89 100644 --- a/pkg/registry/settings/podpreset/storage/storage_test.go +++ b/pkg/registry/settings/podpreset/storage/storage_test.go @@ -24,8 +24,9 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/settings" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -102,7 +103,7 @@ func TestCreate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) invalidPodPreset := validNewPodPreset(test.TestNamespace()) invalidPodPreset.Spec.VolumeMounts[0].Name = "/cache/VolumeMounts" test.TestCreate( @@ -116,7 +117,7 @@ func TestUpdate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestUpdate( // valid validNewPodPreset(test.TestNamespace()), @@ -133,7 +134,7 @@ func TestDelete(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestDelete(validNewPodPreset(test.TestNamespace())) } @@ -141,7 +142,7 @@ func TestGet(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestGet(validNewPodPreset(test.TestNamespace())) } @@ -149,7 +150,7 @@ func TestList(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestList(validNewPodPreset(test.TestNamespace())) } @@ -157,7 +158,7 @@ func TestWatch(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store) + test := genericregistrytest.New(t, storage.Store) test.TestWatch( validNewPodPreset(test.TestNamespace()), // matching labels diff --git a/pkg/registry/settings/podpreset/strategy.go b/pkg/registry/settings/podpreset/strategy.go index 65db5988adf..eaeb927926b 100644 --- a/pkg/registry/settings/podpreset/strategy.go +++ b/pkg/registry/settings/podpreset/strategy.go @@ -21,7 +21,7 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/pod" "k8s.io/kubernetes/pkg/apis/settings" "k8s.io/kubernetes/pkg/apis/settings/validation" @@ -34,7 +34,7 @@ type podPresetStrategy struct { } // Strategy is the default logic that applies when creating and updating Pod Preset objects. -var Strategy = podPresetStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = podPresetStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} // NamespaceScoped returns true because all Pod Presets need to be within a namespace. func (podPresetStrategy) NamespaceScoped() bool { diff --git a/pkg/registry/settings/rest/BUILD b/pkg/registry/settings/rest/BUILD index 338ccbc9b8f..29fac254285 100644 --- a/pkg/registry/settings/rest/BUILD +++ b/pkg/registry/settings/rest/BUILD @@ -10,7 +10,7 @@ go_library( srcs = ["storage_settings.go"], importpath = "k8s.io/kubernetes/pkg/registry/settings/rest", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/settings:go_default_library", "//pkg/registry/settings/podpreset/storage:go_default_library", "//vendor/k8s.io/api/settings/v1alpha1:go_default_library", diff --git a/pkg/registry/settings/rest/storage_settings.go b/pkg/registry/settings/rest/storage_settings.go index 812b8be8df3..f976b3726a5 100644 --- a/pkg/registry/settings/rest/storage_settings.go +++ b/pkg/registry/settings/rest/storage_settings.go @@ -22,7 +22,7 @@ import ( "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" serverstorage "k8s.io/apiserver/pkg/server/storage" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/settings" podpresetstore "k8s.io/kubernetes/pkg/registry/settings/podpreset/storage" ) @@ -30,7 +30,7 @@ import ( type RESTStorageProvider struct{} func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) { - apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(settings.GroupName, api.Registry, api.Scheme, api.ParameterCodec, api.Codecs) + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(settings.GroupName, legacyscheme.Registry, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs) // If you add a version here, be sure to add an entry in `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go with specific priorities. // TODO refactor the plumbing to provide the information in the APIGroupInfo diff --git a/pkg/registry/storage/rest/BUILD b/pkg/registry/storage/rest/BUILD index c4794e0c6e7..7fabe3b7038 100644 --- a/pkg/registry/storage/rest/BUILD +++ b/pkg/registry/storage/rest/BUILD @@ -10,10 +10,12 @@ go_library( srcs = ["storage_storage.go"], importpath = "k8s.io/kubernetes/pkg/registry/storage/rest", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/storage:go_default_library", "//pkg/registry/storage/storageclass/storage:go_default_library", + "//pkg/registry/storage/volumeattachment/storage:go_default_library", "//vendor/k8s.io/api/storage/v1:go_default_library", + "//vendor/k8s.io/api/storage/v1alpha1:go_default_library", "//vendor/k8s.io/api/storage/v1beta1:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", diff --git a/pkg/registry/storage/rest/storage_storage.go b/pkg/registry/storage/rest/storage_storage.go index c4d03af4d41..c8ab31e4acd 100644 --- a/pkg/registry/storage/rest/storage_storage.go +++ b/pkg/registry/storage/rest/storage_storage.go @@ -18,24 +18,30 @@ package rest import ( storageapiv1 "k8s.io/api/storage/v1" + storageapiv1alpha1 "k8s.io/api/storage/v1alpha1" storageapiv1beta1 "k8s.io/api/storage/v1beta1" "k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" serverstorage "k8s.io/apiserver/pkg/server/storage" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" storageapi "k8s.io/kubernetes/pkg/apis/storage" storageclassstore "k8s.io/kubernetes/pkg/registry/storage/storageclass/storage" + volumeattachmentstore "k8s.io/kubernetes/pkg/registry/storage/volumeattachment/storage" ) type RESTStorageProvider struct { } func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) { - apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(storageapi.GroupName, api.Registry, api.Scheme, api.ParameterCodec, api.Codecs) + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(storageapi.GroupName, legacyscheme.Registry, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs) // If you add a version here, be sure to add an entry in `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go with specific priorities. // TODO refactor the plumbing to provide the information in the APIGroupInfo + if apiResourceConfigSource.AnyResourcesForVersionEnabled(storageapiv1alpha1.SchemeGroupVersion) { + apiGroupInfo.VersionedResourcesStorageMap[storageapiv1alpha1.SchemeGroupVersion.Version] = p.v1alpha1Storage(apiResourceConfigSource, restOptionsGetter) + apiGroupInfo.GroupMeta.GroupVersion = storageapiv1alpha1.SchemeGroupVersion + } if apiResourceConfigSource.AnyResourcesForVersionEnabled(storageapiv1beta1.SchemeGroupVersion) { apiGroupInfo.VersionedResourcesStorageMap[storageapiv1beta1.SchemeGroupVersion.Version] = p.v1beta1Storage(apiResourceConfigSource, restOptionsGetter) apiGroupInfo.GroupMeta.GroupVersion = storageapiv1beta1.SchemeGroupVersion @@ -48,6 +54,19 @@ func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorag return apiGroupInfo, true } +func (p RESTStorageProvider) v1alpha1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) map[string]rest.Storage { + version := storageapiv1alpha1.SchemeGroupVersion + + storage := map[string]rest.Storage{} + + if apiResourceConfigSource.ResourceEnabled(version.WithResource("volumeattachments")) { + volumeAttachmentStorage := volumeattachmentstore.NewREST(restOptionsGetter) + storage["volumeattachments"] = volumeAttachmentStorage + } + + return storage +} + func (p RESTStorageProvider) v1beta1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) map[string]rest.Storage { version := storageapiv1beta1.SchemeGroupVersion diff --git a/pkg/registry/storage/storageclass/BUILD b/pkg/registry/storage/storageclass/BUILD index 8ddd796b797..611efaee3ed 100644 --- a/pkg/registry/storage/storageclass/BUILD +++ b/pkg/registry/storage/storageclass/BUILD @@ -14,8 +14,9 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/registry/storage/storageclass", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/storage:go_default_library", + "//pkg/apis/storage/util:go_default_library", "//pkg/apis/storage/validation:go_default_library", "//pkg/features:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", @@ -29,10 +30,10 @@ go_library( go_test( name = "go_default_test", srcs = ["strategy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/storage/storageclass", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/storage:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", diff --git a/pkg/registry/storage/storageclass/storage/BUILD b/pkg/registry/storage/storageclass/storage/BUILD index 67d33541d60..a72f4a80875 100644 --- a/pkg/registry/storage/storageclass/storage/BUILD +++ b/pkg/registry/storage/storageclass/storage/BUILD @@ -9,10 +9,10 @@ load( go_test( name = "go_default_test", srcs = ["storage_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/registry/storage/storageclass/storage", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/storage:go_default_library", "//pkg/registry/registrytest:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -20,6 +20,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", ], ) diff --git a/pkg/registry/storage/storageclass/storage/storage_test.go b/pkg/registry/storage/storageclass/storage/storage_test.go index f9858b9a2bd..9a5dd866950 100644 --- a/pkg/registry/storage/storageclass/storage/storage_test.go +++ b/pkg/registry/storage/storageclass/storage/storage_test.go @@ -24,8 +24,9 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" storageapi "k8s.io/kubernetes/pkg/apis/storage" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -64,7 +65,7 @@ func TestCreate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() storageClass := validNewStorageClass("foo") storageClass.ObjectMeta = metav1.ObjectMeta{GenerateName: "foo"} deleteReclaimPolicy := api.PersistentVolumeReclaimDelete @@ -83,7 +84,7 @@ func TestUpdate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() test.TestUpdate( // valid validNewStorageClass("foo"), @@ -107,7 +108,7 @@ func TestDelete(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope().ReturnDeletedObject() + test := genericregistrytest.New(t, storage.Store).ClusterScope().ReturnDeletedObject() test.TestDelete(validNewStorageClass("foo")) } @@ -115,7 +116,7 @@ func TestGet(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() test.TestGet(validNewStorageClass("foo")) } @@ -123,7 +124,7 @@ func TestList(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() test.TestList(validNewStorageClass("foo")) } @@ -131,7 +132,7 @@ func TestWatch(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() - test := registrytest.New(t, storage.Store).ClusterScope() + test := genericregistrytest.New(t, storage.Store).ClusterScope() test.TestWatch( validNewStorageClass("foo"), // matching labels diff --git a/pkg/registry/storage/storageclass/strategy.go b/pkg/registry/storage/storageclass/strategy.go index 0da81213437..b1cbf49ebcf 100644 --- a/pkg/registry/storage/storageclass/strategy.go +++ b/pkg/registry/storage/storageclass/strategy.go @@ -22,8 +22,9 @@ import ( genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/storage/names" utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/storage" + storageutil "k8s.io/kubernetes/pkg/apis/storage/util" "k8s.io/kubernetes/pkg/apis/storage/validation" "k8s.io/kubernetes/pkg/features" ) @@ -36,7 +37,7 @@ type storageClassStrategy struct { // Strategy is the default logic that applies when creating and updating // StorageClass objects via the REST API. -var Strategy = storageClassStrategy{api.Scheme, names.SimpleNameGenerator} +var Strategy = storageClassStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} func (storageClassStrategy) NamespaceScoped() bool { return false @@ -49,6 +50,8 @@ func (storageClassStrategy) PrepareForCreate(ctx genericapirequest.Context, obj if !utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) { class.AllowVolumeExpansion = nil } + + storageutil.DropDisabledAlphaFields(class) } func (storageClassStrategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList { @@ -73,6 +76,8 @@ func (storageClassStrategy) PrepareForUpdate(ctx genericapirequest.Context, obj, newClass.AllowVolumeExpansion = nil oldClass.AllowVolumeExpansion = nil } + storageutil.DropDisabledAlphaFields(oldClass) + storageutil.DropDisabledAlphaFields(newClass) } func (storageClassStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList { diff --git a/pkg/registry/storage/storageclass/strategy_test.go b/pkg/registry/storage/storageclass/strategy_test.go index da57f0460c9..33842efa775 100644 --- a/pkg/registry/storage/storageclass/strategy_test.go +++ b/pkg/registry/storage/storageclass/strategy_test.go @@ -21,7 +21,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/storage" ) @@ -35,6 +35,7 @@ func TestStorageClassStrategy(t *testing.T) { } deleteReclaimPolicy := api.PersistentVolumeReclaimDelete + bindingMode := storage.VolumeBindingWaitForFirstConsumer storageClass := &storage.StorageClass{ ObjectMeta: metav1.ObjectMeta{ Name: "valid-class", @@ -43,7 +44,8 @@ func TestStorageClassStrategy(t *testing.T) { Parameters: map[string]string{ "foo": "bar", }, - ReclaimPolicy: &deleteReclaimPolicy, + ReclaimPolicy: &deleteReclaimPolicy, + VolumeBindingMode: &bindingMode, } Strategy.PrepareForCreate(ctx, storageClass) @@ -62,7 +64,8 @@ func TestStorageClassStrategy(t *testing.T) { Parameters: map[string]string{ "foo": "bar", }, - ReclaimPolicy: &deleteReclaimPolicy, + ReclaimPolicy: &deleteReclaimPolicy, + VolumeBindingMode: &bindingMode, } Strategy.PrepareForUpdate(ctx, newStorageClass, storageClass) diff --git a/pkg/registry/storage/volumeattachment/BUILD b/pkg/registry/storage/volumeattachment/BUILD new file mode 100644 index 00000000000..8170812f29d --- /dev/null +++ b/pkg/registry/storage/volumeattachment/BUILD @@ -0,0 +1,49 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "strategy.go", + ], + importpath = "k8s.io/kubernetes/pkg/registry/storage/volumeattachment", + visibility = ["//visibility:public"], + deps = [ + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/storage:go_default_library", + "//pkg/apis/storage/validation:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", + "//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["strategy_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/registry/storage/volumeattachment", + deps = [ + "//pkg/apis/storage:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//pkg/registry/storage/volumeattachment/storage:all-srcs", + ], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/registry/storage/volumeattachment/doc.go b/pkg/registry/storage/volumeattachment/doc.go new file mode 100644 index 00000000000..91bb452354e --- /dev/null +++ b/pkg/registry/storage/volumeattachment/doc.go @@ -0,0 +1,19 @@ +/* +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 volumeattachment provides Registry interface and its REST +// implementation for storing volumeattachment api objects. +package volumeattachment diff --git a/pkg/registry/storage/volumeattachment/storage/BUILD b/pkg/registry/storage/volumeattachment/storage/BUILD new file mode 100644 index 00000000000..1442fae44e6 --- /dev/null +++ b/pkg/registry/storage/volumeattachment/storage/BUILD @@ -0,0 +1,49 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["storage.go"], + importpath = "k8s.io/kubernetes/pkg/registry/storage/volumeattachment/storage", + visibility = ["//visibility:public"], + deps = [ + "//pkg/apis/storage:go_default_library", + "//pkg/registry/storage/volumeattachment:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["storage_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/registry/storage/volumeattachment/storage", + deps = [ + "//pkg/api/testapi:go_default_library", + "//pkg/apis/storage:go_default_library", + "//pkg/registry/registrytest:go_default_library", + "//vendor/k8s.io/api/storage/v1alpha1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", + "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/registry/storage/volumeattachment/storage/storage.go b/pkg/registry/storage/volumeattachment/storage/storage.go new file mode 100644 index 00000000000..28a3208e413 --- /dev/null +++ b/pkg/registry/storage/volumeattachment/storage/storage.go @@ -0,0 +1,50 @@ +/* +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 storage + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/generic" + genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" + storageapi "k8s.io/kubernetes/pkg/apis/storage" + "k8s.io/kubernetes/pkg/registry/storage/volumeattachment" +) + +// REST object that will work against persistent volumes. +type REST struct { + *genericregistry.Store +} + +// NewREST returns a RESTStorage object that will work against persistent volumes. +func NewREST(optsGetter generic.RESTOptionsGetter) *REST { + store := &genericregistry.Store{ + NewFunc: func() runtime.Object { return &storageapi.VolumeAttachment{} }, + NewListFunc: func() runtime.Object { return &storageapi.VolumeAttachmentList{} }, + DefaultQualifiedResource: storageapi.Resource("volumeattachments"), + + CreateStrategy: volumeattachment.Strategy, + UpdateStrategy: volumeattachment.Strategy, + DeleteStrategy: volumeattachment.Strategy, + ReturnDeletedObject: true, + } + options := &generic.StoreOptions{RESTOptions: optsGetter} + if err := store.CompleteWithOptions(options); err != nil { + panic(err) // TODO: Propagate error up + } + + return &REST{store} +} diff --git a/pkg/registry/storage/volumeattachment/storage/storage_test.go b/pkg/registry/storage/volumeattachment/storage/storage_test.go new file mode 100644 index 00000000000..669e319155c --- /dev/null +++ b/pkg/registry/storage/volumeattachment/storage/storage_test.go @@ -0,0 +1,191 @@ +/* +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 storage + +import ( + "testing" + + storageapiv1alpha1 "k8s.io/api/storage/v1alpha1" + 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/apiserver/pkg/registry/generic" + genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" + etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" + "k8s.io/kubernetes/pkg/api/testapi" + storageapi "k8s.io/kubernetes/pkg/apis/storage" + "k8s.io/kubernetes/pkg/registry/registrytest" +) + +func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, storageapi.GroupName) + restOptions := generic.RESTOptions{ + StorageConfig: etcdStorage, + Decorator: generic.UndecoratedStorage, + DeleteCollectionWorkers: 1, + ResourcePrefix: "volumeattachments", + } + volumeAttachmentStorage := NewREST(restOptions) + return volumeAttachmentStorage, server +} + +func validNewVolumeAttachment(name string) *storageapi.VolumeAttachment { + pvName := "foo" + return &storageapi.VolumeAttachment{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: storageapi.VolumeAttachmentSpec{ + Attacher: "valid-attacher", + Source: storageapi.VolumeAttachmentSource{ + PersistentVolumeName: &pvName, + }, + NodeName: "valid-node", + }, + } +} + +func validChangedVolumeAttachment() *storageapi.VolumeAttachment { + return validNewVolumeAttachment("foo") +} + +func TestCreate(t *testing.T) { + if *testapi.Storage.GroupVersion() != storageapiv1alpha1.SchemeGroupVersion { + // skip the test for all versions exception v1alpha1 + return + } + + storage, server := newStorage(t) + defer server.Terminate(t) + defer storage.Store.DestroyFunc() + test := genericregistrytest.New(t, storage.Store).ClusterScope() + volumeAttachment := validNewVolumeAttachment("foo") + volumeAttachment.ObjectMeta = metav1.ObjectMeta{GenerateName: "foo"} + pvName := "foo" + test.TestCreate( + // valid + volumeAttachment, + // invalid + &storageapi.VolumeAttachment{ + ObjectMeta: metav1.ObjectMeta{Name: "*BadName!"}, + Spec: storageapi.VolumeAttachmentSpec{ + Attacher: "invalid-attacher-!@#$%^&*()", + Source: storageapi.VolumeAttachmentSource{ + PersistentVolumeName: &pvName, + }, + NodeName: "invalid-node-!@#$%^&*()", + }, + }, + ) +} + +func TestUpdate(t *testing.T) { + if *testapi.Storage.GroupVersion() != storageapiv1alpha1.SchemeGroupVersion { + // skip the test for all versions except v1alpha1 + return + } + + storage, server := newStorage(t) + defer server.Terminate(t) + defer storage.Store.DestroyFunc() + test := genericregistrytest.New(t, storage.Store).ClusterScope() + test.TestUpdate( + // valid + validNewVolumeAttachment("foo"), + // updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*storageapi.VolumeAttachment) + object.Status.Attached = true + return object + }, + //invalid update + func(obj runtime.Object) runtime.Object { + object := obj.(*storageapi.VolumeAttachment) + object.Spec.Attacher = "invalid-attacher-!@#$%^&*()" + return object + }, + ) +} + +func TestDelete(t *testing.T) { + if *testapi.Storage.GroupVersion() != storageapiv1alpha1.SchemeGroupVersion { + // skip the test for all versions except v1alpha1 + return + } + + storage, server := newStorage(t) + defer server.Terminate(t) + defer storage.Store.DestroyFunc() + test := genericregistrytest.New(t, storage.Store).ClusterScope().ReturnDeletedObject() + test.TestDelete(validNewVolumeAttachment("foo")) +} + +func TestGet(t *testing.T) { + if *testapi.Storage.GroupVersion() != storageapiv1alpha1.SchemeGroupVersion { + // skip the test for all versions except v1alpha1 + return + } + + storage, server := newStorage(t) + defer server.Terminate(t) + defer storage.Store.DestroyFunc() + test := genericregistrytest.New(t, storage.Store).ClusterScope() + test.TestGet(validNewVolumeAttachment("foo")) +} + +func TestList(t *testing.T) { + if *testapi.Storage.GroupVersion() != storageapiv1alpha1.SchemeGroupVersion { + // skip the test for all versions except v1alpha1 + return + } + + storage, server := newStorage(t) + defer server.Terminate(t) + defer storage.Store.DestroyFunc() + test := genericregistrytest.New(t, storage.Store).ClusterScope() + test.TestList(validNewVolumeAttachment("foo")) +} + +func TestWatch(t *testing.T) { + if *testapi.Storage.GroupVersion() != storageapiv1alpha1.SchemeGroupVersion { + // skip the test for all versions except v1alpha1 + return + } + + storage, server := newStorage(t) + defer server.Terminate(t) + defer storage.Store.DestroyFunc() + test := genericregistrytest.New(t, storage.Store).ClusterScope() + test.TestWatch( + validNewVolumeAttachment("foo"), + // 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"}, + }, + ) +} diff --git a/pkg/registry/storage/volumeattachment/strategy.go b/pkg/registry/storage/volumeattachment/strategy.go new file mode 100644 index 00000000000..e319a0d878e --- /dev/null +++ b/pkg/registry/storage/volumeattachment/strategy.go @@ -0,0 +1,73 @@ +/* +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 volumeattachment + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/storage/names" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/apis/storage" + "k8s.io/kubernetes/pkg/apis/storage/validation" +) + +// volumeAttachmentStrategy implements behavior for VolumeAttachment objects +type volumeAttachmentStrategy struct { + runtime.ObjectTyper + names.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating +// VolumeAttachment objects via the REST API. +var Strategy = volumeAttachmentStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} + +func (volumeAttachmentStrategy) NamespaceScoped() bool { + return false +} + +// ResetBeforeCreate clears the Status field which is not allowed to be set by end users on creation. +func (volumeAttachmentStrategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) { +} + +func (volumeAttachmentStrategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList { + volumeAttachment := obj.(*storage.VolumeAttachment) + return validation.ValidateVolumeAttachment(volumeAttachment) +} + +// Canonicalize normalizes the object after validation. +func (volumeAttachmentStrategy) Canonicalize(obj runtime.Object) { +} + +func (volumeAttachmentStrategy) AllowCreateOnUpdate() bool { + return false +} + +// PrepareForUpdate sets the Status fields which is not allowed to be set by an end user updating a PV +func (volumeAttachmentStrategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime.Object) { +} + +func (volumeAttachmentStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList { + newVolumeAttachmentObj := obj.(*storage.VolumeAttachment) + oldVolumeAttachmentObj := old.(*storage.VolumeAttachment) + errorList := validation.ValidateVolumeAttachment(newVolumeAttachmentObj) + return append(errorList, validation.ValidateVolumeAttachmentUpdate(newVolumeAttachmentObj, oldVolumeAttachmentObj)...) +} + +func (volumeAttachmentStrategy) AllowUnconditionalUpdate() bool { + return false +} diff --git a/pkg/registry/storage/volumeattachment/strategy_test.go b/pkg/registry/storage/volumeattachment/strategy_test.go new file mode 100644 index 00000000000..33e8985e79a --- /dev/null +++ b/pkg/registry/storage/volumeattachment/strategy_test.go @@ -0,0 +1,77 @@ +/* +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 volumeattachment + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/kubernetes/pkg/apis/storage" +) + +func TestVolumeAttachmentStrategy(t *testing.T) { + ctx := genericapirequest.NewDefaultContext() + if Strategy.NamespaceScoped() { + t.Errorf("VolumeAttachment must not be namespace scoped") + } + if Strategy.AllowCreateOnUpdate() { + t.Errorf("VolumeAttachment should not allow create on update") + } + + pvName := "name" + volumeAttachment := &storage.VolumeAttachment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-attachment", + }, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "valid-attacher", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &pvName, + }, + NodeName: "valid-node", + }, + } + + Strategy.PrepareForCreate(ctx, volumeAttachment) + + errs := Strategy.Validate(ctx, volumeAttachment) + if len(errs) != 0 { + t.Errorf("unexpected error validating %v", errs) + } + + newVolumeAttachment := &storage.VolumeAttachment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-attachment-2", + }, + Spec: storage.VolumeAttachmentSpec{ + Attacher: "valid-attacher-2", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &pvName, + }, + NodeName: "valid-node-2", + }, + } + + Strategy.PrepareForUpdate(ctx, newVolumeAttachment, volumeAttachment) + + errs = Strategy.ValidateUpdate(ctx, newVolumeAttachment, volumeAttachment) + if len(errs) == 0 { + t.Errorf("Expected a validation error") + } + +} diff --git a/pkg/routes/ui.go b/pkg/routes/ui.go index 1f079c86e42..de6ca3c3abd 100644 --- a/pkg/routes/ui.go +++ b/pkg/routes/ui.go @@ -22,7 +22,7 @@ import ( "k8s.io/apiserver/pkg/server/mux" ) -const dashboardPath = "/api/v1/namespaces/kube-system/services/kubernetes-dashboard/proxy" +const dashboardPath = "/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/" // UIRedirect redirects /ui to the kube-ui proxy path. type UIRedirect struct{} diff --git a/pkg/scheduler/BUILD b/pkg/scheduler/BUILD new file mode 100644 index 00000000000..d1d7be40435 --- /dev/null +++ b/pkg/scheduler/BUILD @@ -0,0 +1,88 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_test( + name = "go_default_test", + srcs = ["scheduler_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/scheduler", + deps = [ + "//pkg/api/legacyscheme:go_default_library", + "//pkg/controller/volume/persistentvolume:go_default_library", + "//pkg/scheduler/algorithm:go_default_library", + "//pkg/scheduler/algorithm/predicates:go_default_library", + "//pkg/scheduler/core:go_default_library", + "//pkg/scheduler/schedulercache:go_default_library", + "//pkg/scheduler/testing:go_default_library", + "//pkg/scheduler/util:go_default_library", + "//pkg/scheduler/volumebinder:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + "//vendor/k8s.io/client-go/tools/cache:go_default_library", + "//vendor/k8s.io/client-go/tools/record:go_default_library", + ], +) + +go_library( + name = "go_default_library", + srcs = [ + "scheduler.go", + "testutil.go", + ], + importpath = "k8s.io/kubernetes/pkg/scheduler", + deps = [ + "//pkg/features:go_default_library", + "//pkg/scheduler/algorithm:go_default_library", + "//pkg/scheduler/algorithm/predicates:go_default_library", + "//pkg/scheduler/api:go_default_library", + "//pkg/scheduler/core:go_default_library", + "//pkg/scheduler/metrics:go_default_library", + "//pkg/scheduler/schedulercache:go_default_library", + "//pkg/scheduler/util:go_default_library", + "//pkg/scheduler/volumebinder:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/client-go/listers/core/v1:go_default_library", + "//vendor/k8s.io/client-go/tools/record:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//pkg/scheduler/algorithm:all-srcs", + "//pkg/scheduler/algorithmprovider:all-srcs", + "//pkg/scheduler/api:all-srcs", + "//pkg/scheduler/core:all-srcs", + "//pkg/scheduler/factory:all-srcs", + "//pkg/scheduler/metrics:all-srcs", + "//pkg/scheduler/schedulercache:all-srcs", + "//pkg/scheduler/testing:all-srcs", + "//pkg/scheduler/util:all-srcs", + "//pkg/scheduler/volumebinder:all-srcs", + ], + tags = ["automanaged"], +) diff --git a/plugin/pkg/scheduler/OWNERS b/pkg/scheduler/OWNERS similarity index 100% rename from plugin/pkg/scheduler/OWNERS rename to pkg/scheduler/OWNERS diff --git a/pkg/scheduler/algorithm/BUILD b/pkg/scheduler/algorithm/BUILD new file mode 100644 index 00000000000..30df762dd49 --- /dev/null +++ b/pkg/scheduler/algorithm/BUILD @@ -0,0 +1,58 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "scheduler_interface.go", + "types.go", + "well_known_labels.go", + ], + importpath = "k8s.io/kubernetes/pkg/scheduler/algorithm", + deps = [ + "//pkg/scheduler/api:go_default_library", + "//pkg/scheduler/schedulercache:go_default_library", + "//vendor/k8s.io/api/apps/v1beta1:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "scheduler_interface_test.go", + "types_test.go", + ], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/scheduler/algorithm", + deps = [ + "//pkg/scheduler/schedulercache:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//pkg/scheduler/algorithm/predicates:all-srcs", + "//pkg/scheduler/algorithm/priorities:all-srcs", + ], + tags = ["automanaged"], +) diff --git a/pkg/scheduler/algorithm/doc.go b/pkg/scheduler/algorithm/doc.go new file mode 100644 index 00000000000..59c2cc4aa7c --- /dev/null +++ b/pkg/scheduler/algorithm/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES 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 contains a generic Scheduler interface and several +// implementations. +package algorithm // import "k8s.io/kubernetes/pkg/scheduler/algorithm" diff --git a/pkg/scheduler/algorithm/predicates/BUILD b/pkg/scheduler/algorithm/predicates/BUILD new file mode 100644 index 00000000000..6c091d1381d --- /dev/null +++ b/pkg/scheduler/algorithm/predicates/BUILD @@ -0,0 +1,81 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_library( + name = "go_default_library", + srcs = [ + "error.go", + "metadata.go", + "predicates.go", + "testing_helper.go", + "utils.go", + ], + importpath = "k8s.io/kubernetes/pkg/scheduler/algorithm/predicates", + deps = [ + "//pkg/apis/core/v1/helper:go_default_library", + "//pkg/apis/core/v1/helper/qos:go_default_library", + "//pkg/features:go_default_library", + "//pkg/kubelet/apis:go_default_library", + "//pkg/scheduler/algorithm:go_default_library", + "//pkg/scheduler/algorithm/priorities/util:go_default_library", + "//pkg/scheduler/schedulercache:go_default_library", + "//pkg/scheduler/util:go_default_library", + "//pkg/scheduler/volumebinder:go_default_library", + "//pkg/volume/util:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/storage/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/rand:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + "//vendor/k8s.io/client-go/listers/core/v1:go_default_library", + "//vendor/k8s.io/client-go/listers/storage/v1:go_default_library", + "//vendor/k8s.io/client-go/util/workqueue:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "metadata_test.go", + "predicates_test.go", + "utils_test.go", + ], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/scheduler/algorithm/predicates", + deps = [ + "//pkg/apis/core/v1/helper:go_default_library", + "//pkg/kubelet/apis:go_default_library", + "//pkg/scheduler/algorithm:go_default_library", + "//pkg/scheduler/schedulercache:go_default_library", + "//pkg/scheduler/testing:go_default_library", + "//pkg/scheduler/util:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/storage/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/pkg/scheduler/algorithm/predicates/error.go b/pkg/scheduler/algorithm/predicates/error.go new file mode 100644 index 00000000000..a4450bc5643 --- /dev/null +++ b/pkg/scheduler/algorithm/predicates/error.go @@ -0,0 +1,118 @@ +/* +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 predicates + +import ( + "fmt" + + "k8s.io/api/core/v1" +) + +var ( + // The predicateName tries to be consistent as the predicate name used in DefaultAlgorithmProvider defined in + // defaults.go (which tend to be stable for backward compatibility) + + // NOTE: If you add a new predicate failure error for a predicate that can never + // be made to pass by removing pods, or you change an existing predicate so that + // it can never be made to pass by removing pods, you need to add the predicate + // failure error in nodesWherePreemptionMightHelp() in scheduler/core/generic_scheduler.go + ErrDiskConflict = newPredicateFailureError("NoDiskConflict") + ErrVolumeZoneConflict = newPredicateFailureError("NoVolumeZoneConflict") + ErrNodeSelectorNotMatch = newPredicateFailureError("MatchNodeSelector") + ErrPodAffinityNotMatch = newPredicateFailureError("MatchInterPodAffinity") + ErrPodAffinityRulesNotMatch = newPredicateFailureError("PodAffinityRulesNotMatch") + ErrPodAntiAffinityRulesNotMatch = newPredicateFailureError("PodAntiAffinityRulesNotMatch") + ErrExistingPodsAntiAffinityRulesNotMatch = newPredicateFailureError("ExistingPodsAntiAffinityRulesNotMatch") + ErrTaintsTolerationsNotMatch = newPredicateFailureError("PodToleratesNodeTaints") + ErrPodNotMatchHostName = newPredicateFailureError("HostName") + ErrPodNotFitsHostPorts = newPredicateFailureError("PodFitsHostPorts") + ErrNodeLabelPresenceViolated = newPredicateFailureError("CheckNodeLabelPresence") + ErrServiceAffinityViolated = newPredicateFailureError("CheckServiceAffinity") + ErrMaxVolumeCountExceeded = newPredicateFailureError("MaxVolumeCount") + ErrNodeUnderMemoryPressure = newPredicateFailureError("NodeUnderMemoryPressure") + ErrNodeUnderDiskPressure = newPredicateFailureError("NodeUnderDiskPressure") + ErrNodeOutOfDisk = newPredicateFailureError("NodeOutOfDisk") + ErrNodeNotReady = newPredicateFailureError("NodeNotReady") + ErrNodeNetworkUnavailable = newPredicateFailureError("NodeNetworkUnavailable") + ErrNodeUnschedulable = newPredicateFailureError("NodeUnschedulable") + ErrNodeUnknownCondition = newPredicateFailureError("NodeUnknownCondition") + ErrVolumeNodeConflict = newPredicateFailureError("VolumeNodeAffinityConflict") + ErrVolumeBindConflict = newPredicateFailureError("VolumeBindingNoMatch") + // ErrFakePredicate is used for test only. The fake predicates returning false also returns error + // as ErrFakePredicate. + ErrFakePredicate = newPredicateFailureError("FakePredicateError") +) + +// InsufficientResourceError is an error type that indicates what kind of resource limit is +// hit and caused the unfitting failure. +type InsufficientResourceError struct { + // resourceName is the name of the resource that is insufficient + ResourceName v1.ResourceName + requested int64 + used int64 + capacity int64 +} + +func NewInsufficientResourceError(resourceName v1.ResourceName, requested, used, capacity int64) *InsufficientResourceError { + return &InsufficientResourceError{ + ResourceName: resourceName, + requested: requested, + used: used, + capacity: capacity, + } +} + +func (e *InsufficientResourceError) Error() string { + return fmt.Sprintf("Node didn't have enough resource: %s, requested: %d, used: %d, capacity: %d", + e.ResourceName, e.requested, e.used, e.capacity) +} + +func (e *InsufficientResourceError) GetReason() string { + return fmt.Sprintf("Insufficient %v", e.ResourceName) +} + +func (e *InsufficientResourceError) GetInsufficientAmount() int64 { + return e.requested - (e.capacity - e.used) +} + +type PredicateFailureError struct { + PredicateName string +} + +func newPredicateFailureError(predicateName string) *PredicateFailureError { + return &PredicateFailureError{PredicateName: predicateName} +} + +func (e *PredicateFailureError) Error() string { + return fmt.Sprintf("Predicate %s failed", e.PredicateName) +} + +func (e *PredicateFailureError) GetReason() string { + return e.PredicateName +} + +type FailureReason struct { + reason string +} + +func NewFailureReason(msg string) *FailureReason { + return &FailureReason{reason: msg} +} + +func (e *FailureReason) GetReason() string { + return e.reason +} diff --git a/pkg/scheduler/algorithm/predicates/metadata.go b/pkg/scheduler/algorithm/predicates/metadata.go new file mode 100644 index 00000000000..af8c32e2c4c --- /dev/null +++ b/pkg/scheduler/algorithm/predicates/metadata.go @@ -0,0 +1,188 @@ +/* +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 predicates + +import ( + "fmt" + "sync" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" + schedutil "k8s.io/kubernetes/pkg/scheduler/util" + + "github.com/golang/glog" +) + +type PredicateMetadataFactory struct { + podLister algorithm.PodLister +} + +// Note that predicateMetadata and matchingPodAntiAffinityTerm need to be declared in the same file +// due to the way declarations are processed in predicate declaration unit tests. +type matchingPodAntiAffinityTerm struct { + term *v1.PodAffinityTerm + node *v1.Node +} + +// NOTE: When new fields are added/removed or logic is changed, please make sure that +// RemovePod, AddPod, and ShallowCopy functions are updated to work with the new changes. +type predicateMetadata struct { + pod *v1.Pod + podBestEffort bool + podRequest *schedulercache.Resource + podPorts map[string]bool + //key is a pod full name with the anti-affinity rules. + matchingAntiAffinityTerms map[string][]matchingPodAntiAffinityTerm + serviceAffinityInUse bool + serviceAffinityMatchingPodList []*v1.Pod + serviceAffinityMatchingPodServices []*v1.Service +} + +// Ensure that predicateMetadata implements algorithm.PredicateMetadata. +var _ algorithm.PredicateMetadata = &predicateMetadata{} + +// PredicateMetadataProducer: Helper types/variables... +type PredicateMetadataProducer func(pm *predicateMetadata) + +var predicateMetaProducerRegisterLock sync.Mutex +var predicateMetadataProducers map[string]PredicateMetadataProducer = make(map[string]PredicateMetadataProducer) + +func RegisterPredicateMetadataProducer(predicateName string, precomp PredicateMetadataProducer) { + predicateMetaProducerRegisterLock.Lock() + defer predicateMetaProducerRegisterLock.Unlock() + predicateMetadataProducers[predicateName] = precomp +} + +func NewPredicateMetadataFactory(podLister algorithm.PodLister) algorithm.PredicateMetadataProducer { + factory := &PredicateMetadataFactory{ + podLister, + } + return factory.GetMetadata +} + +// GetMetadata returns the predicateMetadata used which will be used by various predicates. +func (pfactory *PredicateMetadataFactory) GetMetadata(pod *v1.Pod, nodeNameToInfoMap map[string]*schedulercache.NodeInfo) algorithm.PredicateMetadata { + // If we cannot compute metadata, just return nil + if pod == nil { + return nil + } + matchingTerms, err := getMatchingAntiAffinityTerms(pod, nodeNameToInfoMap) + if err != nil { + return nil + } + predicateMetadata := &predicateMetadata{ + pod: pod, + podBestEffort: isPodBestEffort(pod), + podRequest: GetResourceRequest(pod), + podPorts: schedutil.GetUsedPorts(pod), + matchingAntiAffinityTerms: matchingTerms, + } + for predicateName, precomputeFunc := range predicateMetadataProducers { + glog.V(10).Infof("Precompute: %v", predicateName) + precomputeFunc(predicateMetadata) + } + return predicateMetadata +} + +// RemovePod changes predicateMetadata assuming that the given `deletedPod` is +// deleted from the system. +func (meta *predicateMetadata) RemovePod(deletedPod *v1.Pod) error { + deletedPodFullName := schedutil.GetPodFullName(deletedPod) + if deletedPodFullName == schedutil.GetPodFullName(meta.pod) { + return fmt.Errorf("deletedPod and meta.pod must not be the same.") + } + // Delete any anti-affinity rule from the deletedPod. + delete(meta.matchingAntiAffinityTerms, deletedPodFullName) + // All pods in the serviceAffinityMatchingPodList are in the same namespace. + // So, if the namespace of the first one is not the same as the namespace of the + // deletedPod, we don't need to check the list, as deletedPod isn't in the list. + if meta.serviceAffinityInUse && + len(meta.serviceAffinityMatchingPodList) > 0 && + deletedPod.Namespace == meta.serviceAffinityMatchingPodList[0].Namespace { + for i, pod := range meta.serviceAffinityMatchingPodList { + if schedutil.GetPodFullName(pod) == deletedPodFullName { + meta.serviceAffinityMatchingPodList = append( + meta.serviceAffinityMatchingPodList[:i], + meta.serviceAffinityMatchingPodList[i+1:]...) + break + } + } + } + return nil +} + +// AddPod changes predicateMetadata assuming that `newPod` is added to the +// system. +func (meta *predicateMetadata) AddPod(addedPod *v1.Pod, nodeInfo *schedulercache.NodeInfo) error { + addedPodFullName := schedutil.GetPodFullName(addedPod) + if addedPodFullName == schedutil.GetPodFullName(meta.pod) { + return fmt.Errorf("addedPod and meta.pod must not be the same.") + } + if nodeInfo.Node() == nil { + return fmt.Errorf("Invalid node in nodeInfo.") + } + // Add matching anti-affinity terms of the addedPod to the map. + podMatchingTerms, err := getMatchingAntiAffinityTermsOfExistingPod(meta.pod, addedPod, nodeInfo.Node()) + if err != nil { + return err + } + if len(podMatchingTerms) > 0 { + existingTerms, found := meta.matchingAntiAffinityTerms[addedPodFullName] + if found { + meta.matchingAntiAffinityTerms[addedPodFullName] = append(existingTerms, + podMatchingTerms...) + } else { + meta.matchingAntiAffinityTerms[addedPodFullName] = podMatchingTerms + } + } + // If addedPod is in the same namespace as the meta.pod, update the list + // of matching pods if applicable. + if meta.serviceAffinityInUse && addedPod.Namespace == meta.pod.Namespace { + selector := CreateSelectorFromLabels(meta.pod.Labels) + if selector.Matches(labels.Set(addedPod.Labels)) { + meta.serviceAffinityMatchingPodList = append(meta.serviceAffinityMatchingPodList, + addedPod) + } + } + return nil +} + +// ShallowCopy copies a metadata struct into a new struct and creates a copy of +// its maps and slices, but it does not copy the contents of pointer values. +func (meta *predicateMetadata) ShallowCopy() algorithm.PredicateMetadata { + newPredMeta := &predicateMetadata{ + pod: meta.pod, + podBestEffort: meta.podBestEffort, + podRequest: meta.podRequest, + serviceAffinityInUse: meta.serviceAffinityInUse, + } + newPredMeta.podPorts = map[string]bool{} + for k, v := range meta.podPorts { + newPredMeta.podPorts[k] = v + } + newPredMeta.matchingAntiAffinityTerms = map[string][]matchingPodAntiAffinityTerm{} + for k, v := range meta.matchingAntiAffinityTerms { + newPredMeta.matchingAntiAffinityTerms[k] = append([]matchingPodAntiAffinityTerm(nil), v...) + } + newPredMeta.serviceAffinityMatchingPodServices = append([]*v1.Service(nil), + meta.serviceAffinityMatchingPodServices...) + newPredMeta.serviceAffinityMatchingPodList = append([]*v1.Pod(nil), + meta.serviceAffinityMatchingPodList...) + return (algorithm.PredicateMetadata)(newPredMeta) +} diff --git a/pkg/scheduler/algorithm/predicates/metadata_test.go b/pkg/scheduler/algorithm/predicates/metadata_test.go new file mode 100644 index 00000000000..31b88411015 --- /dev/null +++ b/pkg/scheduler/algorithm/predicates/metadata_test.go @@ -0,0 +1,400 @@ +/* +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 predicates + +import ( + "fmt" + "reflect" + "sort" + "testing" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" + schedulertesting "k8s.io/kubernetes/pkg/scheduler/testing" +) + +// sortableAntiAffinityTerms lets us to sort anti-affinity terms. +type sortableAntiAffinityTerms []matchingPodAntiAffinityTerm + +// Less establishes some ordering between two matchingPodAntiAffinityTerms for +// sorting. +func (s sortableAntiAffinityTerms) Less(i, j int) bool { + t1, t2 := s[i], s[j] + if t1.node.Name != t2.node.Name { + return t1.node.Name < t2.node.Name + } + if len(t1.term.Namespaces) != len(t2.term.Namespaces) { + return len(t1.term.Namespaces) < len(t2.term.Namespaces) + } + if t1.term.TopologyKey != t2.term.TopologyKey { + return t1.term.TopologyKey < t2.term.TopologyKey + } + if len(t1.term.LabelSelector.MatchLabels) != len(t2.term.LabelSelector.MatchLabels) { + return len(t1.term.LabelSelector.MatchLabels) < len(t2.term.LabelSelector.MatchLabels) + } + return false +} +func (s sortableAntiAffinityTerms) Len() int { return len(s) } +func (s sortableAntiAffinityTerms) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +var _ = sort.Interface(sortableAntiAffinityTerms{}) + +func sortAntiAffinityTerms(terms map[string][]matchingPodAntiAffinityTerm) { + for k, v := range terms { + sortableTerms := sortableAntiAffinityTerms(v) + sort.Sort(sortableTerms) + terms[k] = sortableTerms + } +} + +// sortablePods lets us to sort pods. +type sortablePods []*v1.Pod + +func (s sortablePods) Less(i, j int) bool { + return s[i].Namespace < s[j].Namespace || + (s[i].Namespace == s[j].Namespace && s[i].Name < s[j].Name) +} +func (s sortablePods) Len() int { return len(s) } +func (s sortablePods) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +var _ = sort.Interface(&sortablePods{}) + +// sortableServices allows us to sort services. +type sortableServices []*v1.Service + +func (s sortableServices) Less(i, j int) bool { + return s[i].Namespace < s[j].Namespace || + (s[i].Namespace == s[j].Namespace && s[i].Name < s[j].Name) +} +func (s sortableServices) Len() int { return len(s) } +func (s sortableServices) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +var _ = sort.Interface(&sortableServices{}) + +// predicateMetadataEquivalent returns true if the two metadata are equivalent. +// Note: this function does not compare podRequest. +func predicateMetadataEquivalent(meta1, meta2 *predicateMetadata) error { + if !reflect.DeepEqual(meta1.pod, meta2.pod) { + return fmt.Errorf("pods are not the same.") + } + if meta1.podBestEffort != meta2.podBestEffort { + return fmt.Errorf("podBestEfforts are not equal.") + } + if meta1.serviceAffinityInUse != meta1.serviceAffinityInUse { + return fmt.Errorf("serviceAffinityInUses are not equal.") + } + if len(meta1.podPorts) != len(meta2.podPorts) { + return fmt.Errorf("podPorts are not equal.") + } + for !reflect.DeepEqual(meta1.podPorts, meta2.podPorts) { + return fmt.Errorf("podPorts are not equal.") + } + sortAntiAffinityTerms(meta1.matchingAntiAffinityTerms) + sortAntiAffinityTerms(meta2.matchingAntiAffinityTerms) + if !reflect.DeepEqual(meta1.matchingAntiAffinityTerms, meta2.matchingAntiAffinityTerms) { + return fmt.Errorf("matchingAntiAffinityTerms are not euqal.") + } + if meta1.serviceAffinityInUse { + sortablePods1 := sortablePods(meta1.serviceAffinityMatchingPodList) + sort.Sort(sortablePods1) + sortablePods2 := sortablePods(meta2.serviceAffinityMatchingPodList) + sort.Sort(sortablePods2) + if !reflect.DeepEqual(sortablePods1, sortablePods2) { + return fmt.Errorf("serviceAffinityMatchingPodLists are not euqal.") + } + + sortableServices1 := sortableServices(meta1.serviceAffinityMatchingPodServices) + sort.Sort(sortableServices1) + sortableServices2 := sortableServices(meta2.serviceAffinityMatchingPodServices) + sort.Sort(sortableServices2) + if !reflect.DeepEqual(sortableServices1, sortableServices2) { + return fmt.Errorf("serviceAffinityMatchingPodServices are not euqal.") + } + } + return nil +} + +func TestPredicateMetadata_AddRemovePod(t *testing.T) { + var label1 = map[string]string{ + "region": "r1", + "zone": "z11", + } + var label2 = map[string]string{ + "region": "r1", + "zone": "z12", + } + var label3 = map[string]string{ + "region": "r2", + "zone": "z21", + } + selector1 := map[string]string{"foo": "bar"} + antiAffinityFooBar := &v1.PodAntiAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ + { + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "foo", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"bar"}, + }, + }, + }, + TopologyKey: "region", + }, + }, + } + antiAffinityComplex := &v1.PodAntiAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ + { + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "foo", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"bar", "buzz"}, + }, + }, + }, + TopologyKey: "region", + }, + { + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "service", + Operator: metav1.LabelSelectorOpNotIn, + Values: []string{"bar", "security", "test"}, + }, + }, + }, + TopologyKey: "zone", + }, + }, + } + + tests := []struct { + description string + pendingPod *v1.Pod + addedPod *v1.Pod + existingPods []*v1.Pod + nodes []*v1.Node + services []*v1.Service + }{ + { + description: "no anti-affinity or service affinity exist", + pendingPod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pending", Labels: selector1}, + }, + existingPods: []*v1.Pod{ + {ObjectMeta: metav1.ObjectMeta{Name: "p1", Labels: selector1}, + Spec: v1.PodSpec{NodeName: "nodeA"}, + }, + {ObjectMeta: metav1.ObjectMeta{Name: "p2"}, + Spec: v1.PodSpec{NodeName: "nodeC"}, + }, + }, + addedPod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "addedPod", Labels: selector1}, + Spec: v1.PodSpec{NodeName: "nodeB"}, + }, + nodes: []*v1.Node{ + {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: label1}}, + {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: label2}}, + {ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: label3}}, + }, + }, + { + description: "metadata anti-affinity terms are updated correctly after adding and removing a pod", + pendingPod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pending", Labels: selector1}, + }, + existingPods: []*v1.Pod{ + {ObjectMeta: metav1.ObjectMeta{Name: "p1", Labels: selector1}, + Spec: v1.PodSpec{NodeName: "nodeA"}, + }, + {ObjectMeta: metav1.ObjectMeta{Name: "p2"}, + Spec: v1.PodSpec{ + NodeName: "nodeC", + Affinity: &v1.Affinity{ + PodAntiAffinity: antiAffinityFooBar, + }, + }, + }, + }, + addedPod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "addedPod", Labels: selector1}, + Spec: v1.PodSpec{ + NodeName: "nodeB", + Affinity: &v1.Affinity{ + PodAntiAffinity: antiAffinityFooBar, + }, + }, + }, + nodes: []*v1.Node{ + {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: label1}}, + {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: label2}}, + {ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: label3}}, + }, + }, + { + description: "metadata service-affinity data are updated correctly after adding and removing a pod", + pendingPod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pending", Labels: selector1}, + }, + existingPods: []*v1.Pod{ + {ObjectMeta: metav1.ObjectMeta{Name: "p1", Labels: selector1}, + Spec: v1.PodSpec{NodeName: "nodeA"}, + }, + {ObjectMeta: metav1.ObjectMeta{Name: "p2"}, + Spec: v1.PodSpec{NodeName: "nodeC"}, + }, + }, + addedPod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "addedPod", Labels: selector1}, + Spec: v1.PodSpec{NodeName: "nodeB"}, + }, + services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: selector1}}}, + nodes: []*v1.Node{ + {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: label1}}, + {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: label2}}, + {ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: label3}}, + }, + }, + { + description: "metadata anti-affinity terms and service affinity data are updated correctly after adding and removing a pod", + pendingPod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pending", Labels: selector1}, + }, + existingPods: []*v1.Pod{ + {ObjectMeta: metav1.ObjectMeta{Name: "p1", Labels: selector1}, + Spec: v1.PodSpec{NodeName: "nodeA"}, + }, + {ObjectMeta: metav1.ObjectMeta{Name: "p2"}, + Spec: v1.PodSpec{ + NodeName: "nodeC", + Affinity: &v1.Affinity{ + PodAntiAffinity: antiAffinityFooBar, + }, + }, + }, + }, + addedPod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "addedPod", Labels: selector1}, + Spec: v1.PodSpec{ + NodeName: "nodeA", + Affinity: &v1.Affinity{ + PodAntiAffinity: antiAffinityComplex, + }, + }, + }, + services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: selector1}}}, + nodes: []*v1.Node{ + {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: label1}}, + {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: label2}}, + {ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: label3}}, + }, + }, + } + + for _, test := range tests { + allPodLister := schedulertesting.FakePodLister(append(test.existingPods, test.addedPod)) + // getMeta creates predicate meta data given the list of pods. + getMeta := func(lister schedulertesting.FakePodLister) (*predicateMetadata, map[string]*schedulercache.NodeInfo) { + nodeInfoMap := schedulercache.CreateNodeNameToInfoMap(lister, test.nodes) + // nodeList is a list of non-pointer nodes to feed to FakeNodeListInfo. + nodeList := []v1.Node{} + for _, n := range test.nodes { + nodeList = append(nodeList, *n) + } + _, precompute := NewServiceAffinityPredicate(lister, schedulertesting.FakeServiceLister(test.services), FakeNodeListInfo(nodeList), nil) + RegisterPredicateMetadataProducer("ServiceAffinityMetaProducer", precompute) + pmf := PredicateMetadataFactory{lister} + meta := pmf.GetMetadata(test.pendingPod, nodeInfoMap) + return meta.(*predicateMetadata), nodeInfoMap + } + + // allPodsMeta is meta data produced when all pods, including test.addedPod + // are given to the metadata producer. + allPodsMeta, _ := getMeta(allPodLister) + // existingPodsMeta1 is meta data produced for test.existingPods (without test.addedPod). + existingPodsMeta1, nodeInfoMap := getMeta(schedulertesting.FakePodLister(test.existingPods)) + // Add test.addedPod to existingPodsMeta1 and make sure meta is equal to allPodsMeta + nodeInfo := nodeInfoMap[test.addedPod.Spec.NodeName] + if err := existingPodsMeta1.AddPod(test.addedPod, nodeInfo); err != nil { + t.Errorf("test [%v]: error adding pod to meta: %v", test.description, err) + } + if err := predicateMetadataEquivalent(allPodsMeta, existingPodsMeta1); err != nil { + t.Errorf("test [%v]: meta data are not equivalent: %v", test.description, err) + } + // Remove the added pod and from existingPodsMeta1 an make sure it is equal + // to meta generated for existing pods. + existingPodsMeta2, _ := getMeta(schedulertesting.FakePodLister(test.existingPods)) + if err := existingPodsMeta1.RemovePod(test.addedPod); err != nil { + t.Errorf("test [%v]: error removing pod from meta: %v", test.description, err) + } + if err := predicateMetadataEquivalent(existingPodsMeta1, existingPodsMeta2); err != nil { + t.Errorf("test [%v]: meta data are not equivalent: %v", test.description, err) + } + } +} + +// TestPredicateMetadata_ShallowCopy tests the ShallowCopy function. It is based +// on the idea that shallow-copy should produce an object that is deep-equal to the original +// object. +func TestPredicateMetadata_ShallowCopy(t *testing.T) { + source := predicateMetadata{ + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "testns", + }, + }, + podBestEffort: true, + podRequest: &schedulercache.Resource{ + MilliCPU: 1000, + Memory: 300, + AllowedPodNumber: 4, + }, + podPorts: map[string]bool{"1234": true, "456": false}, + matchingAntiAffinityTerms: map[string][]matchingPodAntiAffinityTerm{ + "term1": { + { + term: &v1.PodAffinityTerm{TopologyKey: "node"}, + node: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{Name: "machine1"}, + }, + }, + }, + }, + serviceAffinityInUse: true, + serviceAffinityMatchingPodList: []*v1.Pod{ + {ObjectMeta: metav1.ObjectMeta{Name: "pod1"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "pod2"}}, + }, + serviceAffinityMatchingPodServices: []*v1.Service{ + {ObjectMeta: metav1.ObjectMeta{Name: "service1"}}, + }, + } + + if !reflect.DeepEqual(source.ShallowCopy().(*predicateMetadata), &source) { + t.Errorf("Copy is not equal to source!") + } +} diff --git a/plugin/pkg/scheduler/algorithm/predicates/predicates.go b/pkg/scheduler/algorithm/predicates/predicates.go similarity index 83% rename from plugin/pkg/scheduler/algorithm/predicates/predicates.go rename to pkg/scheduler/algorithm/predicates/predicates.go index bfd9984c7e3..48d7b509285 100644 --- a/plugin/pkg/scheduler/algorithm/predicates/predicates.go +++ b/pkg/scheduler/algorithm/predicates/predicates.go @@ -19,42 +19,101 @@ package predicates import ( "errors" "fmt" + "os" + "strconv" "sync" "k8s.io/api/core/v1" + storagev1 "k8s.io/api/storage/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/util/rand" utilfeature "k8s.io/apiserver/pkg/util/feature" corelisters "k8s.io/client-go/listers/core/v1" + storagelisters "k8s.io/client-go/listers/storage/v1" "k8s.io/client-go/util/workqueue" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" - v1qos "k8s.io/kubernetes/pkg/api/v1/helper/qos" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" + v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos" "k8s.io/kubernetes/pkg/features" kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + priorityutil "k8s.io/kubernetes/pkg/scheduler/algorithm/priorities/util" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" + schedutil "k8s.io/kubernetes/pkg/scheduler/util" volumeutil "k8s.io/kubernetes/pkg/volume/util" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" - priorityutil "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/priorities/util" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" - schedutil "k8s.io/kubernetes/plugin/pkg/scheduler/util" - "k8s.io/metrics/pkg/client/clientset_generated/clientset" "github.com/golang/glog" + "k8s.io/kubernetes/pkg/scheduler/volumebinder" ) const ( - MatchInterPodAffinity = "MatchInterPodAffinity" + MatchInterPodAffinityPred = "MatchInterPodAffinity" + CheckVolumeBindingPred = "CheckVolumeBinding" + CheckNodeConditionPred = "CheckNodeCondition" + GeneralPred = "GeneralPredicates" + HostNamePred = "HostName" + PodFitsHostPortsPred = "PodFitsHostPorts" + MatchNodeSelectorPred = "MatchNodeSelector" + PodFitsResourcesPred = "PodFitsResources" + NoDiskConflictPred = "NoDiskConflict" + PodToleratesNodeTaintsPred = "PodToleratesNodeTaints" + PodToleratesNodeNoExecuteTaintsPred = "PodToleratesNodeNoExecuteTaints" + CheckNodeLabelPresencePred = "CheckNodeLabelPresence" + checkServiceAffinityPred = "checkServiceAffinity" + MaxEBSVolumeCountPred = "MaxEBSVolumeCount" + MaxGCEPDVolumeCountPred = "MaxGCEPDVolumeCount" + MaxAzureDiskVolumeCountPred = "MaxAzureDiskVolumeCount" + NoVolumeZoneConflictPred = "NoVolumeZoneConflict" + CheckNodeMemoryPressurePred = "CheckNodeMemoryPressure" + CheckNodeDiskPressurePred = "CheckNodeDiskPressure" + + // DefaultMaxEBSVolumes is the limit for volumes attached to an instance. + // Amazon recommends no more than 40; the system root volume uses at least one. + // See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/volume_limits.html#linux-specific-volume-limits + DefaultMaxEBSVolumes = 39 + // DefaultMaxGCEPDVolumes defines the maximum number of PD Volumes for GCE + // GCE instances can have up to 16 PD volumes attached. + DefaultMaxGCEPDVolumes = 16 + // DefaultMaxAzureDiskVolumes defines the maximum number of PD Volumes for Azure + // Larger Azure VMs can actually have much more disks attached. + // TODO We should determine the max based on VM size + DefaultMaxAzureDiskVolumes = 16 + + // KubeMaxPDVols defines the maximum number of PD Volumes per kubelet + KubeMaxPDVols = "KUBE_MAX_PD_VOLS" + + // for EBSVolumeFilter + EBSVolumeFilterType = "EBS" + // for GCEPDVolumeFilter + GCEPDVolumeFilterType = "GCE" + // for AzureDiskVolumeFilter + AzureDiskVolumeFilterType = "AzureDisk" ) // IMPORTANT NOTE for predicate developers: // We are using cached predicate result for pods belonging to the same equivalence class. -// So when updating a existing predicate, you should consider whether your change will introduce new +// So when updating an existing predicate, you should consider whether your change will introduce new // dependency to attributes of any API object like Pod, Node, Service etc. // If yes, you are expected to invalidate the cached predicate result for related API object change. // For example: // https://github.com/kubernetes/kubernetes/blob/36a218e/plugin/pkg/scheduler/factory/factory.go#L422 +// IMPORTANT NOTE: this list contains the ordering of the predicates, if you develop a new predicate +// it is mandatory to add its name to this list. +// Otherwise it won't be processed, see generic_scheduler#podFitsOnNode(). +// The order is based on the restrictiveness & complexity of predicates. +// Design doc: https://github.com/kubernetes/community/blob/master/contributors/design-proposals/scheduling/predicates-ordering.md +var ( + predicatesOrdering = []string{CheckNodeConditionPred, + GeneralPred, HostNamePred, PodFitsHostPortsPred, + MatchNodeSelectorPred, PodFitsResourcesPred, NoDiskConflictPred, + PodToleratesNodeTaintsPred, PodToleratesNodeNoExecuteTaintsPred, CheckNodeLabelPresencePred, + checkServiceAffinityPred, MaxEBSVolumeCountPred, MaxGCEPDVolumeCountPred, + MaxAzureDiskVolumeCountPred, CheckVolumeBindingPred, NoVolumeZoneConflictPred, + CheckNodeMemoryPressurePred, CheckNodeDiskPressurePred, MatchInterPodAffinityPred} +) + // NodeInfo: Other types for predicate functions... type NodeInfo interface { GetNodeInfo(nodeID string) (*v1.Node, error) @@ -69,6 +128,14 @@ type CachedPersistentVolumeInfo struct { corelisters.PersistentVolumeLister } +func PredicatesOrdering() []string { + return predicatesOrdering +} + +func SetPredicatesOrdering(names []string) { + predicatesOrdering = names +} + func (c *CachedPersistentVolumeInfo) GetPersistentVolumeInfo(pvID string) (*v1.PersistentVolume, error) { return c.Get(pvID) } @@ -96,7 +163,7 @@ func (c *CachedNodeInfo) GetNodeInfo(id string) (*v1.Node, error) { node, err := c.Get(id) if apierrors.IsNotFound(err) { - return nil, fmt.Errorf("node '%v' not found", id) + return nil, err } if err != nil { @@ -106,6 +173,19 @@ func (c *CachedNodeInfo) GetNodeInfo(id string) (*v1.Node, error) { return node, nil } +type StorageClassInfo interface { + GetStorageClassInfo(className string) (*storagev1.StorageClass, error) +} + +// CachedStorageClassInfo implements StorageClassInfo +type CachedStorageClassInfo struct { + storagelisters.StorageClassLister +} + +func (c *CachedStorageClassInfo) GetStorageClassInfo(className string) (*storagev1.StorageClass, error) { + return c.Get(className) +} + func isVolumeConflict(volume v1.Volume, pod *v1.Pod) bool { // fast path if there is no conflict checking targets. if volume.GCEPersistentDisk == nil && volume.AWSElasticBlockStore == nil && volume.RBD == nil && volume.ISCSI == nil { @@ -144,7 +224,7 @@ func isVolumeConflict(volume v1.Volume, pod *v1.Pod) bool { // two RBDs images are the same if they share the same Ceph monitor, are in the same RADOS Pool, and have the same image name // only one read-write mount is permitted for the same RBD image. // same RBD image mounted by multiple Pods conflicts unless all Pods mount the image read-only - if haveSame(mon, emon) && pool == epool && image == eimage && !(volume.RBD.ReadOnly && existingVolume.RBD.ReadOnly) { + if haveOverlap(mon, emon) && pool == epool && image == eimage && !(volume.RBD.ReadOnly && existingVolume.RBD.ReadOnly) { return true } } @@ -178,6 +258,11 @@ type MaxPDVolumeCountChecker struct { maxVolumes int pvInfo PersistentVolumeInfo pvcInfo PersistentVolumeClaimInfo + + // The string below is generated randomly during the struct's initialization. + // It is used to prefix volumeID generated inside the predicate() method to + // avoid conflicts with any real volume. + randomVolumeIDPrefix string } // VolumeFilter contains information on how to filter PD Volumes when checking PD Volume caps @@ -188,24 +273,61 @@ type VolumeFilter struct { } // NewMaxPDVolumeCountPredicate creates a predicate which evaluates whether a pod can fit based on the -// number of volumes which match a filter that it requests, and those that are already present. The -// maximum number is configurable to accommodate different systems. +// number of volumes which match a filter that it requests, and those that are already present. // // The predicate looks for both volumes used directly, as well as PVC volumes that are backed by relevant volume // types, counts the number of unique volumes, and rejects the new pod if it would place the total count over // the maximum. -func NewMaxPDVolumeCountPredicate(filter VolumeFilter, maxVolumes int, pvInfo PersistentVolumeInfo, pvcInfo PersistentVolumeClaimInfo) algorithm.FitPredicate { +func NewMaxPDVolumeCountPredicate(filterName string, pvInfo PersistentVolumeInfo, pvcInfo PersistentVolumeClaimInfo) algorithm.FitPredicate { + + var filter VolumeFilter + var maxVolumes int + + switch filterName { + + case EBSVolumeFilterType: + filter = EBSVolumeFilter + maxVolumes = getMaxVols(DefaultMaxEBSVolumes) + case GCEPDVolumeFilterType: + filter = GCEPDVolumeFilter + maxVolumes = getMaxVols(DefaultMaxGCEPDVolumes) + case AzureDiskVolumeFilterType: + filter = AzureDiskVolumeFilter + maxVolumes = getMaxVols(DefaultMaxAzureDiskVolumes) + default: + glog.Fatalf("Wrong filterName, Only Support %v %v %v ", EBSVolumeFilterType, + GCEPDVolumeFilterType, AzureDiskVolumeFilterType) + return nil + + } c := &MaxPDVolumeCountChecker{ - filter: filter, - maxVolumes: maxVolumes, - pvInfo: pvInfo, - pvcInfo: pvcInfo, + filter: filter, + maxVolumes: maxVolumes, + pvInfo: pvInfo, + pvcInfo: pvcInfo, + randomVolumeIDPrefix: rand.String(32), } return c.predicate } -func (c *MaxPDVolumeCountChecker) filterVolumes(volumes []v1.Volume, namespace string, randomPrefix string, filteredVolumes map[string]bool) error { +// getMaxVols checks the max PD volumes environment variable, otherwise returning a default value +func getMaxVols(defaultVal int) int { + if rawMaxVols := os.Getenv(KubeMaxPDVols); rawMaxVols != "" { + if parsedMaxVols, err := strconv.Atoi(rawMaxVols); err != nil { + glog.Errorf("Unable to parse maximum PD volumes value, using default of %v: %v", defaultVal, err) + } else if parsedMaxVols <= 0 { + glog.Errorf("Maximum PD volumes must be a positive value, using default of %v", defaultVal) + } else { + return parsedMaxVols + } + } + + return defaultVal +} + +func (c *MaxPDVolumeCountChecker) filterVolumes(volumes []v1.Volume, namespace string, filteredVolumes map[string]bool) error { + for i := range volumes { vol := &volumes[i] if id, ok := c.filter.FilterVolume(vol); ok { @@ -217,8 +339,9 @@ func (c *MaxPDVolumeCountChecker) filterVolumes(volumes []v1.Volume, namespace s } // Until we know real ID of the volume use namespace/pvcName as substitute - // With a random prefix so it can't conflict with existing volume ID. - pvId := fmt.Sprintf("%s-%s/%s", randomPrefix, namespace, pvcName) + // with a random prefix (calculated and stored inside 'c' during initialization) + // to avoid conflicts with existing volume IDs. + pvId := fmt.Sprintf("%s-%s/%s", c.randomVolumeIDPrefix, namespace, pvcName) pvc, err := c.pvcInfo.GetPersistentVolumeClaimInfo(namespace, pvcName) if err != nil || pvc == nil { @@ -228,7 +351,8 @@ func (c *MaxPDVolumeCountChecker) filterVolumes(volumes []v1.Volume, namespace s continue } - if pvc.Spec.VolumeName == "" { + pvName := pvc.Spec.VolumeName + if pvName == "" { // PVC is not bound. It was either deleted and created again or // it was forcefuly unbound by admin. The pod can still use the // original PV where it was bound to -> log the error and count @@ -238,7 +362,6 @@ func (c *MaxPDVolumeCountChecker) filterVolumes(volumes []v1.Volume, namespace s continue } - pvName := pvc.Spec.VolumeName pv, err := c.pvInfo.GetPersistentVolumeInfo(pvName) if err != nil || pv == nil { // if the PV is not found, log the error @@ -264,15 +387,8 @@ func (c *MaxPDVolumeCountChecker) predicate(pod *v1.Pod, meta algorithm.Predicat return true, nil, nil } - // randomPrefix is a prefix of auxiliary volume IDs when we don't know the - // real volume ID, e.g. because the corresponding PV or PVC was deleted. It - // is random to avoid conflicts with real volume IDs and it needs to be - // stable in whole predicate() call so a deleted PVC used by two pods is - // counted as one volume and not as two. - randomPrefix := rand.String(32) - newVolumes := make(map[string]bool) - if err := c.filterVolumes(pod.Spec.Volumes, pod.Namespace, randomPrefix, newVolumes); err != nil { + if err := c.filterVolumes(pod.Spec.Volumes, pod.Namespace, newVolumes); err != nil { return false, nil, err } @@ -284,7 +400,7 @@ func (c *MaxPDVolumeCountChecker) predicate(pod *v1.Pod, meta algorithm.Predicat // count unique volumes existingVolumes := make(map[string]bool) for _, existingPod := range nodeInfo.Pods() { - if err := c.filterVolumes(existingPod.Spec.Volumes, existingPod.Namespace, randomPrefix, existingVolumes); err != nil { + if err := c.filterVolumes(existingPod.Spec.Volumes, existingPod.Namespace, existingVolumes); err != nil { return false, nil, err } } @@ -359,8 +475,9 @@ var AzureDiskVolumeFilter VolumeFilter = VolumeFilter{ } type VolumeZoneChecker struct { - pvInfo PersistentVolumeInfo - pvcInfo PersistentVolumeClaimInfo + pvInfo PersistentVolumeInfo + pvcInfo PersistentVolumeClaimInfo + classInfo StorageClassInfo } // NewVolumeZonePredicate evaluates if a pod can fit due to the volumes it requests, given @@ -377,10 +494,11 @@ type VolumeZoneChecker struct { // determining the zone of a volume during scheduling, and that is likely to // require calling out to the cloud provider. It seems that we are moving away // from inline volume declarations anyway. -func NewVolumeZonePredicate(pvInfo PersistentVolumeInfo, pvcInfo PersistentVolumeClaimInfo) algorithm.FitPredicate { +func NewVolumeZonePredicate(pvInfo PersistentVolumeInfo, pvcInfo PersistentVolumeClaimInfo, classInfo StorageClassInfo) algorithm.FitPredicate { c := &VolumeZoneChecker{ - pvInfo: pvInfo, - pvcInfo: pvcInfo, + pvInfo: pvInfo, + pvcInfo: pvcInfo, + classInfo: classInfo, } return c.predicate } @@ -432,6 +550,21 @@ func (c *VolumeZoneChecker) predicate(pod *v1.Pod, meta algorithm.PredicateMetad pvName := pvc.Spec.VolumeName if pvName == "" { + if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) { + scName := pvc.Spec.StorageClassName + if scName != nil && len(*scName) > 0 { + class, _ := c.classInfo.GetStorageClassInfo(*scName) + if class != nil { + if class.VolumeBindingMode == nil { + return false, nil, fmt.Errorf("VolumeBindingMode not set for StorageClass %q", scName) + } + if *class.VolumeBindingMode == storagev1.VolumeBindingWaitForFirstConsumer { + // Skip unbound volumes + continue + } + } + } + } return false, nil, fmt.Errorf("PersistentVolumeClaim is not bound: %q", pvcName) } @@ -833,7 +966,7 @@ func (s *ServiceAffinity) checkServiceAffinity(pod *v1.Pod, meta algorithm.Predi // PodFitsHostPorts checks if a node has free ports for the requested pod ports. func PodFitsHostPorts(pod *v1.Pod, meta algorithm.PredicateMetadata, nodeInfo *schedulercache.NodeInfo) (bool, []algorithm.PredicateFailureReason, error) { - var wantPorts map[int]bool + var wantPorts map[string]bool if predicateMeta, ok := meta.(*predicateMetadata); ok { wantPorts = predicateMeta.podPorts } else { @@ -845,29 +978,28 @@ func PodFitsHostPorts(pod *v1.Pod, meta algorithm.PredicateMetadata, nodeInfo *s } existingPorts := nodeInfo.UsedPorts() - for wport := range wantPorts { - if wport != 0 && existingPorts[wport] { - return false, []algorithm.PredicateFailureReason{ErrPodNotFitsHostPorts}, nil - } + + // try to see whether existingPorts and wantPorts will conflict or not + if portsConflict(existingPorts, wantPorts) { + return false, []algorithm.PredicateFailureReason{ErrPodNotFitsHostPorts}, nil } + return true, nil, nil } // search two arrays and return true if they have at least one common element; return false otherwise -func haveSame(a1, a2 []string) bool { - m := map[string]int{} +func haveOverlap(a1, a2 []string) bool { + m := map[string]bool{} for _, val := range a1 { - m[val] = 1 + m[val] = true } for _, val := range a2 { - m[val] = m[val] + 1 - } - for _, val := range m { - if val > 1 { + if _, ok := m[val]; ok { return true } } + return false } @@ -988,7 +1120,7 @@ func (c *PodAffinityChecker) InterPodAffinityMatches(pod *v1.Pod, meta algorithm // First return value indicates whether a matching pod exists on a node that matches the topology key, // while the second return value indicates whether a matching pod exists anywhere. // TODO: Do we really need any pod matching, or all pods matching? I think the latter. -func (c *PodAffinityChecker) anyPodMatchesPodAffinityTerm(pod *v1.Pod, allPods []*v1.Pod, node *v1.Node, term *v1.PodAffinityTerm) (bool, bool, error) { +func (c *PodAffinityChecker) anyPodMatchesPodAffinityTerm(pod *v1.Pod, pods []*v1.Pod, nodeInfo *schedulercache.NodeInfo, term *v1.PodAffinityTerm) (bool, bool, error) { if len(term.TopologyKey) == 0 { return false, false, fmt.Errorf("empty topologyKey is not allowed except for PreferredDuringScheduling pod anti-affinity") } @@ -998,7 +1130,12 @@ func (c *PodAffinityChecker) anyPodMatchesPodAffinityTerm(pod *v1.Pod, allPods [ if err != nil { return false, false, err } - for _, existingPod := range allPods { + // Special case: When the topological domain is node, we can limit our + // search to pods on that node without searching the entire cluster. + if term.TopologyKey == kubeletapis.LabelHostname { + pods = nodeInfo.Pods() + } + for _, existingPod := range pods { match := priorityutil.PodMatchesTermsNamespaceAndSelector(existingPod, namespaces, selector) if match { matchingPodExists = true @@ -1006,7 +1143,7 @@ func (c *PodAffinityChecker) anyPodMatchesPodAffinityTerm(pod *v1.Pod, allPods [ if err != nil { return false, matchingPodExists, err } - if priorityutil.NodesHaveSameTopologyKey(node, existingPodNode, term.TopologyKey) { + if priorityutil.NodesHaveSameTopologyKey(nodeInfo.Node(), existingPodNode, term.TopologyKey) { return true, matchingPodExists, nil } } @@ -1014,7 +1151,7 @@ func (c *PodAffinityChecker) anyPodMatchesPodAffinityTerm(pod *v1.Pod, allPods [ return false, matchingPodExists, nil } -func getPodAffinityTerms(podAffinity *v1.PodAffinity) (terms []v1.PodAffinityTerm) { +func GetPodAffinityTerms(podAffinity *v1.PodAffinity) (terms []v1.PodAffinityTerm) { if podAffinity != nil { if len(podAffinity.RequiredDuringSchedulingIgnoredDuringExecution) != 0 { terms = podAffinity.RequiredDuringSchedulingIgnoredDuringExecution @@ -1027,7 +1164,7 @@ func getPodAffinityTerms(podAffinity *v1.PodAffinity) (terms []v1.PodAffinityTer return terms } -func getPodAntiAffinityTerms(podAntiAffinity *v1.PodAntiAffinity) (terms []v1.PodAffinityTerm) { +func GetPodAntiAffinityTerms(podAntiAffinity *v1.PodAntiAffinity) (terms []v1.PodAffinityTerm) { if podAntiAffinity != nil { if len(podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution) != 0 { terms = podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution @@ -1077,7 +1214,7 @@ func getMatchingAntiAffinityTerms(pod *v1.Pod, nodeInfoMap map[string]*scheduler if affinity == nil { continue } - for _, term := range getPodAntiAffinityTerms(affinity.PodAntiAffinity) { + for _, term := range GetPodAntiAffinityTerms(affinity.PodAntiAffinity) { namespaces := priorityutil.GetNamespacesFromPodAffinityTerm(existingPod, &term) selector, err := metav1.LabelSelectorAsSelector(term.LabelSelector) if err != nil { @@ -1104,7 +1241,7 @@ func getMatchingAntiAffinityTermsOfExistingPod(newPod *v1.Pod, existingPod *v1.P var result []matchingPodAntiAffinityTerm affinity := existingPod.Spec.Affinity if affinity != nil && affinity.PodAntiAffinity != nil { - for _, term := range getPodAntiAffinityTerms(affinity.PodAntiAffinity) { + for _, term := range GetPodAntiAffinityTerms(affinity.PodAntiAffinity) { namespaces := priorityutil.GetNamespacesFromPodAffinityTerm(existingPod, &term) selector, err := metav1.LabelSelectorAsSelector(term.LabelSelector) if err != nil { @@ -1125,6 +1262,10 @@ func (c *PodAffinityChecker) getMatchingAntiAffinityTerms(pod *v1.Pod, allPods [ if affinity != nil && affinity.PodAntiAffinity != nil { existingPodNode, err := c.info.GetNodeInfo(existingPod.Spec.NodeName) if err != nil { + if apierrors.IsNotFound(err) { + glog.Errorf("Node not found, %v", existingPod.Spec.NodeName) + continue + } return nil, err } existingPodMatchingTerms, err := getMatchingAntiAffinityTermsOfExistingPod(pod, existingPod, existingPodNode) @@ -1201,8 +1342,8 @@ func (c *PodAffinityChecker) satisfiesPodsAffinityAntiAffinity(pod *v1.Pod, node } // Check all affinity terms. - for _, term := range getPodAffinityTerms(affinity.PodAffinity) { - termMatches, matchingPodExists, err := c.anyPodMatchesPodAffinityTerm(pod, filteredPods, node, &term) + for _, term := range GetPodAffinityTerms(affinity.PodAffinity) { + termMatches, matchingPodExists, err := c.anyPodMatchesPodAffinityTerm(pod, filteredPods, nodeInfo, &term) if err != nil { errMessage := fmt.Sprintf("Cannot schedule pod %+v onto node %v, because of PodAffinityTerm %v, err: %v", podName(pod), node.Name, term, err) glog.Error(errMessage) @@ -1234,8 +1375,8 @@ func (c *PodAffinityChecker) satisfiesPodsAffinityAntiAffinity(pod *v1.Pod, node } // Check all anti-affinity terms. - for _, term := range getPodAntiAffinityTerms(affinity.PodAntiAffinity) { - termMatches, _, err := c.anyPodMatchesPodAffinityTerm(pod, filteredPods, node, &term) + for _, term := range GetPodAntiAffinityTerms(affinity.PodAntiAffinity) { + termMatches, _, err := c.anyPodMatchesPodAffinityTerm(pod, filteredPods, nodeInfo, &term) if err != nil || termMatches { glog.V(10).Infof("Cannot schedule pod %+v onto node %v, because of PodAntiAffinityTerm %v, err: %v", podName(pod), node.Name, term, err) @@ -1347,33 +1488,30 @@ func CheckNodeConditionPredicate(pod *v1.Pod, meta algorithm.PredicateMetadata, return len(reasons) == 0, reasons, nil } -type VolumeNodeChecker struct { - pvInfo PersistentVolumeInfo - pvcInfo PersistentVolumeClaimInfo - client clientset.Interface +type VolumeBindingChecker struct { + binder *volumebinder.VolumeBinder } -// NewVolumeNodePredicate evaluates if a pod can fit due to the volumes it requests, given -// that some volumes have node topology constraints, particularly when using Local PVs. -// The requirement is that any pod that uses a PVC that is bound to a PV with topology constraints -// must be scheduled to a node that satisfies the PV's topology labels. -func NewVolumeNodePredicate(pvInfo PersistentVolumeInfo, pvcInfo PersistentVolumeClaimInfo, client clientset.Interface) algorithm.FitPredicate { - c := &VolumeNodeChecker{ - pvInfo: pvInfo, - pvcInfo: pvcInfo, - client: client, +// NewVolumeBindingPredicate evaluates if a pod can fit due to the volumes it requests, +// for both bound and unbound PVCs. +// +// For PVCs that are bound, then it checks that the corresponding PV's node affinity is +// satisfied by the given node. +// +// For PVCs that are unbound, it tries to find available PVs that can satisfy the PVC requirements +// and that the PV node affinity is satisfied by the given node. +// +// The predicate returns true if all bound PVCs have compatible PVs with the node, and if all unbound +// PVCs can be matched with an available and node-compatible PV. +func NewVolumeBindingPredicate(binder *volumebinder.VolumeBinder) algorithm.FitPredicate { + c := &VolumeBindingChecker{ + binder: binder, } return c.predicate } -func (c *VolumeNodeChecker) predicate(pod *v1.Pod, meta algorithm.PredicateMetadata, nodeInfo *schedulercache.NodeInfo) (bool, []algorithm.PredicateFailureReason, error) { - if !utilfeature.DefaultFeatureGate.Enabled(features.PersistentLocalVolumes) { - return true, nil, nil - } - - // If a pod doesn't have any volume attached to it, the predicate will always be true. - // Thus we make a fast path for it, to avoid unnecessary computations in this case. - if len(pod.Spec.Volumes) == 0 { +func (c *VolumeBindingChecker) predicate(pod *v1.Pod, meta algorithm.PredicateMetadata, nodeInfo *schedulercache.NodeInfo) (bool, []algorithm.PredicateFailureReason, error) { + if !utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) { return true, nil, nil } @@ -1382,45 +1520,27 @@ func (c *VolumeNodeChecker) predicate(pod *v1.Pod, meta algorithm.PredicateMetad return false, nil, fmt.Errorf("node not found") } - glog.V(2).Infof("Checking for prebound volumes with node affinity") - namespace := pod.Namespace - manifest := &(pod.Spec) - for i := range manifest.Volumes { - volume := &manifest.Volumes[i] - if volume.PersistentVolumeClaim == nil { - continue - } - pvcName := volume.PersistentVolumeClaim.ClaimName - if pvcName == "" { - return false, nil, fmt.Errorf("PersistentVolumeClaim had no name") - } - pvc, err := c.pvcInfo.GetPersistentVolumeClaimInfo(namespace, pvcName) - if err != nil { - return false, nil, err - } - - if pvc == nil { - return false, nil, fmt.Errorf("PersistentVolumeClaim was not found: %q", pvcName) - } - pvName := pvc.Spec.VolumeName - if pvName == "" { - return false, nil, fmt.Errorf("PersistentVolumeClaim is not bound: %q", pvcName) - } - - pv, err := c.pvInfo.GetPersistentVolumeInfo(pvName) - if err != nil { - return false, nil, err - } - if pv == nil { - return false, nil, fmt.Errorf("PersistentVolume not found: %q", pvName) - } - - err = volumeutil.CheckNodeAffinity(pv, node.Labels) - if err != nil { - glog.V(2).Infof("Won't schedule pod %q onto node %q due to volume %q node mismatch: %v", pod.Name, node.Name, pvName, err.Error()) - return false, []algorithm.PredicateFailureReason{ErrVolumeNodeConflict}, nil - } - glog.V(4).Infof("VolumeNode predicate allows node %q for pod %q due to volume %q", node.Name, pod.Name, pvName) + unboundSatisfied, boundSatisfied, err := c.binder.Binder.FindPodVolumes(pod, node.Name) + if err != nil { + return false, nil, err } + + failReasons := []algorithm.PredicateFailureReason{} + if !boundSatisfied { + glog.V(5).Infof("Bound PVs not satisfied for pod %v/%v, node %q", pod.Namespace, pod.Name, node.Name) + failReasons = append(failReasons, ErrVolumeNodeConflict) + } + + if !unboundSatisfied { + glog.V(5).Infof("Couldn't find matching PVs for pod %v/%v, node %q", pod.Namespace, pod.Name, node.Name) + failReasons = append(failReasons, ErrVolumeBindConflict) + } + + if len(failReasons) > 0 { + return false, failReasons, nil + } + + // All volumes bound or matching PVs found for all unbound PVCs + glog.V(5).Infof("All PVCs found matches for pod %v/%v, node %q", pod.Namespace, pod.Name, node.Name) return true, nil, nil } diff --git a/plugin/pkg/scheduler/algorithm/predicates/predicates_test.go b/pkg/scheduler/algorithm/predicates/predicates_test.go similarity index 91% rename from plugin/pkg/scheduler/algorithm/predicates/predicates_test.go rename to pkg/scheduler/algorithm/predicates/predicates_test.go index e19a4a23a38..1b05d9e4fde 100644 --- a/plugin/pkg/scheduler/algorithm/predicates/predicates_test.go +++ b/pkg/scheduler/algorithm/predicates/predicates_test.go @@ -17,88 +17,51 @@ limitations under the License. package predicates import ( - "fmt" + "os" "reflect" + "strconv" "testing" "k8s.io/api/core/v1" + storagev1 "k8s.io/api/storage/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" + utilfeature "k8s.io/apiserver/pkg/util/feature" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" - schedulertesting "k8s.io/kubernetes/plugin/pkg/scheduler/testing" - schedutil "k8s.io/kubernetes/plugin/pkg/scheduler/util" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" + schedulertesting "k8s.io/kubernetes/pkg/scheduler/testing" + schedutil "k8s.io/kubernetes/pkg/scheduler/util" ) -type FakeNodeInfo v1.Node - -func (n FakeNodeInfo) GetNodeInfo(nodeName string) (*v1.Node, error) { - node := v1.Node(n) - return &node, nil -} - -type FakeNodeListInfo []v1.Node - -func (nodes FakeNodeListInfo) GetNodeInfo(nodeName string) (*v1.Node, error) { - for _, node := range nodes { - if node.Name == nodeName { - return &node, nil - } - } - return nil, fmt.Errorf("Unable to find node: %s", nodeName) -} - -type FakePersistentVolumeClaimInfo []v1.PersistentVolumeClaim - -func (pvcs FakePersistentVolumeClaimInfo) GetPersistentVolumeClaimInfo(namespace string, pvcID string) (*v1.PersistentVolumeClaim, error) { - for _, pvc := range pvcs { - if pvc.Name == pvcID && pvc.Namespace == namespace { - return &pvc, nil - } - } - return nil, fmt.Errorf("Unable to find persistent volume claim: %s/%s", namespace, pvcID) -} - -type FakePersistentVolumeInfo []v1.PersistentVolume - -func (pvs FakePersistentVolumeInfo) GetPersistentVolumeInfo(pvID string) (*v1.PersistentVolume, error) { - for _, pv := range pvs { - if pv.Name == pvID { - return &pv, nil - } - } - return nil, fmt.Errorf("Unable to find persistent volume: %s", pvID) -} - var ( - opaqueResourceA = v1helper.OpaqueIntResourceName("AAA") - opaqueResourceB = v1helper.OpaqueIntResourceName("BBB") + extendedResourceA = v1.ResourceName("example.com/aaa") + extendedResourceB = v1.ResourceName("example.com/bbb") hugePageResourceA = v1helper.HugePageResourceName(resource.MustParse("2Mi")) ) -func makeResources(milliCPU, memory, nvidiaGPUs, pods, opaqueA, storage, hugePageA int64) v1.NodeResources { +func makeResources(milliCPU, memory, nvidiaGPUs, pods, extendedA, storage, hugePageA int64) v1.NodeResources { return v1.NodeResources{ Capacity: v1.ResourceList{ v1.ResourceCPU: *resource.NewMilliQuantity(milliCPU, resource.DecimalSI), v1.ResourceMemory: *resource.NewQuantity(memory, resource.BinarySI), v1.ResourcePods: *resource.NewQuantity(pods, resource.DecimalSI), v1.ResourceNvidiaGPU: *resource.NewQuantity(nvidiaGPUs, resource.DecimalSI), - opaqueResourceA: *resource.NewQuantity(opaqueA, resource.DecimalSI), + extendedResourceA: *resource.NewQuantity(extendedA, resource.DecimalSI), v1.ResourceEphemeralStorage: *resource.NewQuantity(storage, resource.BinarySI), hugePageResourceA: *resource.NewQuantity(hugePageA, resource.BinarySI), }, } } -func makeAllocatableResources(milliCPU, memory, nvidiaGPUs, pods, opaqueA, storage, hugePageA int64) v1.ResourceList { +func makeAllocatableResources(milliCPU, memory, nvidiaGPUs, pods, extendedA, storage, hugePageA int64) v1.ResourceList { return v1.ResourceList{ v1.ResourceCPU: *resource.NewMilliQuantity(milliCPU, resource.DecimalSI), v1.ResourceMemory: *resource.NewQuantity(memory, resource.BinarySI), v1.ResourcePods: *resource.NewQuantity(pods, resource.DecimalSI), v1.ResourceNvidiaGPU: *resource.NewQuantity(nvidiaGPUs, resource.DecimalSI), - opaqueResourceA: *resource.NewQuantity(opaqueA, resource.DecimalSI), + extendedResourceA: *resource.NewQuantity(extendedA, resource.DecimalSI), v1.ResourceEphemeralStorage: *resource.NewQuantity(storage, resource.BinarySI), hugePageResourceA: *resource.NewQuantity(hugePageA, resource.BinarySI), } @@ -238,99 +201,99 @@ func TestPodFitsResources(t *testing.T) { test: "equal edge case for init container", }, { - pod: newResourcePod(schedulercache.Resource{ScalarResources: map[v1.ResourceName]int64{opaqueResourceA: 1}}), + pod: newResourcePod(schedulercache.Resource{ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 1}}), nodeInfo: schedulercache.NewNodeInfo(newResourcePod(schedulercache.Resource{})), fits: true, - test: "opaque resource fits", + test: "extended resource fits", }, { - pod: newResourceInitPod(newResourcePod(schedulercache.Resource{}), schedulercache.Resource{ScalarResources: map[v1.ResourceName]int64{opaqueResourceA: 1}}), + pod: newResourceInitPod(newResourcePod(schedulercache.Resource{}), schedulercache.Resource{ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 1}}), nodeInfo: schedulercache.NewNodeInfo(newResourcePod(schedulercache.Resource{})), fits: true, - test: "opaque resource fits for init container", + test: "extended resource fits for init container", }, { pod: newResourcePod( - schedulercache.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{opaqueResourceA: 10}}), + schedulercache.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 10}}), nodeInfo: schedulercache.NewNodeInfo( - newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, ScalarResources: map[v1.ResourceName]int64{opaqueResourceA: 0}})), + newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 0}})), fits: false, - test: "opaque resource capacity enforced", - reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(opaqueResourceA, 10, 0, 5)}, + test: "extended resource capacity enforced", + reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(extendedResourceA, 10, 0, 5)}, }, { pod: newResourceInitPod(newResourcePod(schedulercache.Resource{}), - schedulercache.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{opaqueResourceA: 10}}), + schedulercache.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 10}}), nodeInfo: schedulercache.NewNodeInfo( - newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, ScalarResources: map[v1.ResourceName]int64{opaqueResourceA: 0}})), + newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 0}})), fits: false, - test: "opaque resource capacity enforced for init container", - reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(opaqueResourceA, 10, 0, 5)}, + test: "extended resource capacity enforced for init container", + reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(extendedResourceA, 10, 0, 5)}, }, { pod: newResourcePod( - schedulercache.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{opaqueResourceA: 1}}), + schedulercache.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 1}}), nodeInfo: schedulercache.NewNodeInfo( - newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, ScalarResources: map[v1.ResourceName]int64{opaqueResourceA: 5}})), + newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 5}})), fits: false, - test: "opaque resource allocatable enforced", - reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(opaqueResourceA, 1, 5, 5)}, + test: "extended resource allocatable enforced", + reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(extendedResourceA, 1, 5, 5)}, }, { pod: newResourceInitPod(newResourcePod(schedulercache.Resource{}), - schedulercache.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{opaqueResourceA: 1}}), + schedulercache.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 1}}), nodeInfo: schedulercache.NewNodeInfo( - newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, ScalarResources: map[v1.ResourceName]int64{opaqueResourceA: 5}})), + newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 5}})), fits: false, - test: "opaque resource allocatable enforced for init container", - reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(opaqueResourceA, 1, 5, 5)}, + test: "extended resource allocatable enforced for init container", + reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(extendedResourceA, 1, 5, 5)}, }, { pod: newResourcePod( - schedulercache.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{opaqueResourceA: 3}}, - schedulercache.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{opaqueResourceA: 3}}), + schedulercache.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 3}}, + schedulercache.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 3}}), nodeInfo: schedulercache.NewNodeInfo( - newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, ScalarResources: map[v1.ResourceName]int64{opaqueResourceA: 2}})), + newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 2}})), fits: false, - test: "opaque resource allocatable enforced for multiple containers", - reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(opaqueResourceA, 6, 2, 5)}, + test: "extended resource allocatable enforced for multiple containers", + reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(extendedResourceA, 6, 2, 5)}, }, { pod: newResourceInitPod(newResourcePod(schedulercache.Resource{}), - schedulercache.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{opaqueResourceA: 3}}, - schedulercache.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{opaqueResourceA: 3}}), + schedulercache.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 3}}, + schedulercache.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 3}}), nodeInfo: schedulercache.NewNodeInfo( - newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, ScalarResources: map[v1.ResourceName]int64{opaqueResourceA: 2}})), + newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 2}})), fits: true, - test: "opaque resource allocatable admits multiple init containers", + test: "extended resource allocatable admits multiple init containers", }, { pod: newResourceInitPod(newResourcePod(schedulercache.Resource{}), - schedulercache.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{opaqueResourceA: 6}}, - schedulercache.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{opaqueResourceA: 3}}), + schedulercache.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 6}}, + schedulercache.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 3}}), nodeInfo: schedulercache.NewNodeInfo( - newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, ScalarResources: map[v1.ResourceName]int64{opaqueResourceA: 2}})), + newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, ScalarResources: map[v1.ResourceName]int64{extendedResourceA: 2}})), fits: false, - test: "opaque resource allocatable enforced for multiple init containers", - reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(opaqueResourceA, 6, 2, 5)}, + test: "extended resource allocatable enforced for multiple init containers", + reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(extendedResourceA, 6, 2, 5)}, }, { pod: newResourcePod( - schedulercache.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{opaqueResourceB: 1}}), + schedulercache.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{extendedResourceB: 1}}), nodeInfo: schedulercache.NewNodeInfo( newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0})), fits: false, - test: "opaque resource allocatable enforced for unknown resource", - reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(opaqueResourceB, 1, 0, 0)}, + test: "extended resource allocatable enforced for unknown resource", + reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(extendedResourceB, 1, 0, 0)}, }, { pod: newResourceInitPod(newResourcePod(schedulercache.Resource{}), - schedulercache.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{opaqueResourceB: 1}}), + schedulercache.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{extendedResourceB: 1}}), nodeInfo: schedulercache.NewNodeInfo( newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0})), fits: false, - test: "opaque resource allocatable enforced for unknown resource for init container", - reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(opaqueResourceB, 1, 0, 0)}, + test: "extended resource allocatable enforced for unknown resource for init container", + reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(extendedResourceB, 1, 0, 0)}, }, { pod: newResourcePod( @@ -552,10 +515,17 @@ func TestPodFitsHost(t *testing.T) { } } -func newPod(host string, hostPorts ...int) *v1.Pod { +func newPod(host string, hostPortInfos ...string) *v1.Pod { networkPorts := []v1.ContainerPort{} - for _, port := range hostPorts { - networkPorts = append(networkPorts, v1.ContainerPort{HostPort: int32(port)}) + for _, portInfo := range hostPortInfos { + hostPortInfo := decode(portInfo) + hostPort, _ := strconv.Atoi(hostPortInfo.hostPort) + + networkPorts = append(networkPorts, v1.ContainerPort{ + HostIP: hostPortInfo.hostIP, + HostPort: int32(hostPort), + Protocol: v1.Protocol(hostPortInfo.protocol), + }) } return &v1.Pod{ Spec: v1.PodSpec{ @@ -583,32 +553,88 @@ func TestPodFitsHostPorts(t *testing.T) { test: "nothing running", }, { - pod: newPod("m1", 8080), + pod: newPod("m1", "UDP/127.0.0.1/8080"), nodeInfo: schedulercache.NewNodeInfo( - newPod("m1", 9090)), + newPod("m1", "UDP/127.0.0.1/9090")), fits: true, test: "other port", }, { - pod: newPod("m1", 8080), + pod: newPod("m1", "UDP/127.0.0.1/8080"), nodeInfo: schedulercache.NewNodeInfo( - newPod("m1", 8080)), + newPod("m1", "UDP/127.0.0.1/8080")), fits: false, - test: "same port", + test: "same udp port", }, { - pod: newPod("m1", 8000, 8080), + pod: newPod("m1", "TCP/127.0.0.1/8080"), nodeInfo: schedulercache.NewNodeInfo( - newPod("m1", 8080)), + newPod("m1", "TCP/127.0.0.1/8080")), fits: false, - test: "second port", + test: "same tcp port", }, { - pod: newPod("m1", 8000, 8080), + pod: newPod("m1", "TCP/127.0.0.1/8080"), nodeInfo: schedulercache.NewNodeInfo( - newPod("m1", 8001, 8080)), + newPod("m1", "TCP/127.0.0.2/8080")), + fits: true, + test: "different host ip", + }, + { + pod: newPod("m1", "UDP/127.0.0.1/8080"), + nodeInfo: schedulercache.NewNodeInfo( + newPod("m1", "TCP/127.0.0.1/8080")), + fits: true, + test: "different protocol", + }, + { + pod: newPod("m1", "UDP/127.0.0.1/8000", "UDP/127.0.0.1/8080"), + nodeInfo: schedulercache.NewNodeInfo( + newPod("m1", "UDP/127.0.0.1/8080")), fits: false, - test: "second port", + test: "second udp port conflict", + }, + { + pod: newPod("m1", "TCP/127.0.0.1/8001", "UDP/127.0.0.1/8080"), + nodeInfo: schedulercache.NewNodeInfo( + newPod("m1", "TCP/127.0.0.1/8001", "UDP/127.0.0.1/8081")), + fits: false, + test: "first tcp port conflict", + }, + { + pod: newPod("m1", "TCP/0.0.0.0/8001"), + nodeInfo: schedulercache.NewNodeInfo( + newPod("m1", "TCP/127.0.0.1/8001")), + fits: false, + test: "first tcp port conflict due to 0.0.0.0 hostIP", + }, + { + pod: newPod("m1", "TCP/10.0.10.10/8001", "TCP/0.0.0.0/8001"), + nodeInfo: schedulercache.NewNodeInfo( + newPod("m1", "TCP/127.0.0.1/8001")), + fits: false, + test: "TCP hostPort conflict due to 0.0.0.0 hostIP", + }, + { + pod: newPod("m1", "TCP/127.0.0.1/8001"), + nodeInfo: schedulercache.NewNodeInfo( + newPod("m1", "TCP/0.0.0.0/8001")), + fits: false, + test: "second tcp port conflict to 0.0.0.0 hostIP", + }, + { + pod: newPod("m1", "UDP/127.0.0.1/8001"), + nodeInfo: schedulercache.NewNodeInfo( + newPod("m1", "TCP/0.0.0.0/8001")), + fits: true, + test: "second different protocol", + }, + { + pod: newPod("m1", "UDP/127.0.0.1/8001"), + nodeInfo: schedulercache.NewNodeInfo( + newPod("m1", "TCP/0.0.0.0/8001", "UDP/0.0.0.0/8001")), + fits: false, + test: "UDP hostPort conflict due to 0.0.0.0 hostIP", }, } expectedFailureReasons := []algorithm.PredicateFailureReason{ErrPodNotFitsHostPorts} @@ -629,29 +655,28 @@ func TestPodFitsHostPorts(t *testing.T) { func TestGetUsedPorts(t *testing.T) { tests := []struct { - pods []*v1.Pod - - ports map[int]bool + pods []*v1.Pod + ports map[string]bool }{ { []*v1.Pod{ - newPod("m1", 9090), + newPod("m1", "UDP/127.0.0.1/9090"), }, - map[int]bool{9090: true}, + map[string]bool{"UDP/127.0.0.1/9090": true}, }, { []*v1.Pod{ - newPod("m1", 9090), - newPod("m1", 9091), + newPod("m1", "UDP/127.0.0.1/9090"), + newPod("m1", "UDP/127.0.0.1/9091"), }, - map[int]bool{9090: true, 9091: true}, + map[string]bool{"UDP/127.0.0.1/9090": true, "UDP/127.0.0.1/9091": true}, }, { []*v1.Pod{ - newPod("m1", 9090), - newPod("m2", 9091), + newPod("m1", "TCP/0.0.0.0/9090"), + newPod("m2", "UDP/127.0.0.1/9091"), }, - map[int]bool{9090: true, 9091: true}, + map[string]bool{"TCP/0.0.0.0/9090": true, "UDP/127.0.0.1/9091": true}, }, } @@ -663,7 +688,7 @@ func TestGetUsedPorts(t *testing.T) { } } -func TestDiskConflicts(t *testing.T) { +func TestGCEDiskConflicts(t *testing.T) { volState := v1.PodSpec{ Volumes: []v1.Volume{ { @@ -1989,24 +2014,11 @@ func TestEBSVolumeCountConflicts(t *testing.T) { }, } - filter := VolumeFilter{ - FilterVolume: func(vol *v1.Volume) (string, bool) { - if vol.AWSElasticBlockStore != nil { - return vol.AWSElasticBlockStore.VolumeID, true - } - return "", false - }, - FilterPersistentVolume: func(pv *v1.PersistentVolume) (string, bool) { - if pv.Spec.AWSElasticBlockStore != nil { - return pv.Spec.AWSElasticBlockStore.VolumeID, true - } - return "", false - }, - } expectedFailureReasons := []algorithm.PredicateFailureReason{ErrMaxVolumeCountExceeded} for _, test := range tests { - pred := NewMaxPDVolumeCountPredicate(filter, test.maxVols, pvInfo, pvcInfo) + os.Setenv(KubeMaxPDVols, strconv.Itoa(test.maxVols)) + pred := NewMaxPDVolumeCountPredicate(EBSVolumeFilterType, pvInfo, pvcInfo) fits, reasons, err := pred(test.newPod, PredicateMetadata(test.newPod, nil), schedulercache.NewNodeInfo(test.existingPods...)) if err != nil { t.Errorf("%s: unexpected error: %v", test.test, err) @@ -3783,7 +3795,7 @@ func TestVolumeZonePredicate(t *testing.T) { expectedFailureReasons := []algorithm.PredicateFailureReason{ErrVolumeZoneConflict} for _, test := range tests { - fit := NewVolumeZonePredicate(pvInfo, pvcInfo) + fit := NewVolumeZonePredicate(pvInfo, pvcInfo, nil) node := &schedulercache.NodeInfo{} node.SetNode(test.Node) @@ -3876,7 +3888,7 @@ func TestVolumeZonePredicateMultiZone(t *testing.T) { expectedFailureReasons := []algorithm.PredicateFailureReason{ErrVolumeZoneConflict} for _, test := range tests { - fit := NewVolumeZonePredicate(pvInfo, pvcInfo) + fit := NewVolumeZonePredicate(pvInfo, pvcInfo, nil) node := &schedulercache.NodeInfo{} node.SetNode(test.Node) @@ -3893,3 +3905,167 @@ func TestVolumeZonePredicateMultiZone(t *testing.T) { } } + +func TestVolumeZonePredicateWithVolumeBinding(t *testing.T) { + var ( + modeWait = storagev1.VolumeBindingWaitForFirstConsumer + + class0 = "Class_0" + classWait = "Class_Wait" + classImmediate = "Class_Immediate" + ) + + classInfo := FakeStorageClassInfo{ + { + ObjectMeta: metav1.ObjectMeta{Name: classImmediate}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: classWait}, + VolumeBindingMode: &modeWait, + }, + } + + pvInfo := FakePersistentVolumeInfo{ + { + ObjectMeta: metav1.ObjectMeta{Name: "Vol_1", Labels: map[string]string{kubeletapis.LabelZoneFailureDomain: "us-west1-a"}}, + }, + } + + pvcInfo := FakePersistentVolumeClaimInfo{ + { + ObjectMeta: metav1.ObjectMeta{Name: "PVC_1", Namespace: "default"}, + Spec: v1.PersistentVolumeClaimSpec{VolumeName: "Vol_1"}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "PVC_NoSC", Namespace: "default"}, + Spec: v1.PersistentVolumeClaimSpec{StorageClassName: &class0}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "PVC_EmptySC", Namespace: "default"}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "PVC_WaitSC", Namespace: "default"}, + Spec: v1.PersistentVolumeClaimSpec{StorageClassName: &classWait}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "PVC_ImmediateSC", Namespace: "default"}, + Spec: v1.PersistentVolumeClaimSpec{StorageClassName: &classImmediate}, + }, + } + + testNode := &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "host1", + Labels: map[string]string{kubeletapis.LabelZoneFailureDomain: "us-west1-a", "uselessLabel": "none"}, + }, + } + + tests := []struct { + Name string + Pod *v1.Pod + Fits bool + Node *v1.Node + ExpectFailure bool + }{ + { + Name: "label zone failure domain matched", + Pod: createPodWithVolume("pod_1", "vol_1", "PVC_1"), + Node: testNode, + Fits: true, + }, + { + Name: "unbound volume empty storage class", + Pod: createPodWithVolume("pod_1", "vol_1", "PVC_EmptySC"), + Node: testNode, + Fits: false, + ExpectFailure: true, + }, + { + Name: "unbound volume no storage class", + Pod: createPodWithVolume("pod_1", "vol_1", "PVC_NoSC"), + Node: testNode, + Fits: false, + ExpectFailure: true, + }, + { + Name: "unbound volume immediate binding mode", + Pod: createPodWithVolume("pod_1", "vol_1", "PVC_ImmediateSC"), + Node: testNode, + Fits: false, + ExpectFailure: true, + }, + { + Name: "unbound volume wait binding mode", + Pod: createPodWithVolume("pod_1", "vol_1", "PVC_WaitSC"), + Node: testNode, + Fits: true, + }, + } + + err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true") + if err != nil { + t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err) + } + + for _, test := range tests { + fit := NewVolumeZonePredicate(pvInfo, pvcInfo, classInfo) + node := &schedulercache.NodeInfo{} + node.SetNode(test.Node) + + fits, _, err := fit(test.Pod, nil, node) + if !test.ExpectFailure && err != nil { + t.Errorf("%s: unexpected error: %v", test.Name, err) + } + if test.ExpectFailure && err == nil { + t.Errorf("%s: expected error, got success", test.Name) + } + if fits != test.Fits { + t.Errorf("%s: expected %v got %v", test.Name, test.Fits, fits) + } + } + + err = utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false") + if err != nil { + t.Fatalf("Failed to disable feature gate for VolumeScheduling: %v", err) + } +} + +func TestGetMaxVols(t *testing.T) { + previousValue := os.Getenv(KubeMaxPDVols) + defaultValue := 39 + + tests := []struct { + rawMaxVols string + expected int + test string + }{ + { + rawMaxVols: "invalid", + expected: defaultValue, + test: "Unable to parse maximum PD volumes value, using default value", + }, + { + rawMaxVols: "-2", + expected: defaultValue, + test: "Maximum PD volumes must be a positive value, using default value", + }, + { + rawMaxVols: "40", + expected: 40, + test: "Parse maximum PD volumes value from env", + }, + } + + for _, test := range tests { + os.Setenv(KubeMaxPDVols, test.rawMaxVols) + result := getMaxVols(defaultValue) + if result != test.expected { + t.Errorf("%s: expected %v got %v", test.test, test.expected, result) + } + } + + os.Unsetenv(KubeMaxPDVols) + if previousValue != "" { + os.Setenv(KubeMaxPDVols, previousValue) + } +} diff --git a/pkg/scheduler/algorithm/predicates/testing_helper.go b/pkg/scheduler/algorithm/predicates/testing_helper.go new file mode 100644 index 00000000000..57306c58aad --- /dev/null +++ b/pkg/scheduler/algorithm/predicates/testing_helper.go @@ -0,0 +1,75 @@ +/* +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 predicates + +import ( + "fmt" + + "k8s.io/api/core/v1" + storagev1 "k8s.io/api/storage/v1" +) + +type FakePersistentVolumeClaimInfo []v1.PersistentVolumeClaim + +func (pvcs FakePersistentVolumeClaimInfo) GetPersistentVolumeClaimInfo(namespace string, pvcID string) (*v1.PersistentVolumeClaim, error) { + for _, pvc := range pvcs { + if pvc.Name == pvcID && pvc.Namespace == namespace { + return &pvc, nil + } + } + return nil, fmt.Errorf("Unable to find persistent volume claim: %s/%s", namespace, pvcID) +} + +type FakeNodeInfo v1.Node + +func (n FakeNodeInfo) GetNodeInfo(nodeName string) (*v1.Node, error) { + node := v1.Node(n) + return &node, nil +} + +type FakeNodeListInfo []v1.Node + +func (nodes FakeNodeListInfo) GetNodeInfo(nodeName string) (*v1.Node, error) { + for _, node := range nodes { + if node.Name == nodeName { + return &node, nil + } + } + return nil, fmt.Errorf("Unable to find node: %s", nodeName) +} + +type FakePersistentVolumeInfo []v1.PersistentVolume + +func (pvs FakePersistentVolumeInfo) GetPersistentVolumeInfo(pvID string) (*v1.PersistentVolume, error) { + for _, pv := range pvs { + if pv.Name == pvID { + return &pv, nil + } + } + return nil, fmt.Errorf("Unable to find persistent volume: %s", pvID) +} + +type FakeStorageClassInfo []storagev1.StorageClass + +func (classes FakeStorageClassInfo) GetStorageClassInfo(name string) (*storagev1.StorageClass, error) { + for _, sc := range classes { + if sc.Name == name { + return &sc, nil + } + } + return nil, fmt.Errorf("Unable to find storage class: %s", name) +} diff --git a/pkg/scheduler/algorithm/predicates/utils.go b/pkg/scheduler/algorithm/predicates/utils.go new file mode 100644 index 00000000000..9a25c85d9ac --- /dev/null +++ b/pkg/scheduler/algorithm/predicates/utils.go @@ -0,0 +1,202 @@ +/* +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 predicates + +import ( + "strings" + + "github.com/golang/glog" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + schedutil "k8s.io/kubernetes/pkg/scheduler/util" +) + +// FindLabelsInSet gets as many key/value pairs as possible out of a label set. +func FindLabelsInSet(labelsToKeep []string, selector labels.Set) map[string]string { + aL := make(map[string]string) + for _, l := range labelsToKeep { + if selector.Has(l) { + aL[l] = selector.Get(l) + } + } + return aL +} + +// AddUnsetLabelsToMap backfills missing values with values we find in a map. +func AddUnsetLabelsToMap(aL map[string]string, labelsToAdd []string, labelSet labels.Set) { + for _, l := range labelsToAdd { + // if the label is already there, dont overwrite it. + if _, exists := aL[l]; exists { + continue + } + // otherwise, backfill this label. + if labelSet.Has(l) { + aL[l] = labelSet.Get(l) + } + } +} + +// FilterPodsByNamespace filters pods outside a namespace from the given list. +func FilterPodsByNamespace(pods []*v1.Pod, ns string) []*v1.Pod { + filtered := []*v1.Pod{} + for _, nsPod := range pods { + if nsPod.Namespace == ns { + filtered = append(filtered, nsPod) + } + } + return filtered +} + +// CreateSelectorFromLabels is used to define a selector that corresponds to the keys in a map. +func CreateSelectorFromLabels(aL map[string]string) labels.Selector { + if aL == nil || len(aL) == 0 { + return labels.Everything() + } + return labels.Set(aL).AsSelector() +} + +// EquivalencePodGenerator is a generator of equivalence class for pod with consideration of PVC info. +type EquivalencePodGenerator struct { + pvcInfo PersistentVolumeClaimInfo +} + +// NewEquivalencePodGenerator returns a getEquivalencePod method with consideration of PVC info. +func NewEquivalencePodGenerator(pvcInfo PersistentVolumeClaimInfo) algorithm.GetEquivalencePodFunc { + g := &EquivalencePodGenerator{ + pvcInfo: pvcInfo, + } + return g.getEquivalencePod +} + +// GetEquivalencePod returns a EquivalencePod which contains a group of pod attributes which can be reused. +func (e *EquivalencePodGenerator) getEquivalencePod(pod *v1.Pod) interface{} { + // For now we only consider pods: + // 1. OwnerReferences is Controller + // 2. with same OwnerReferences + // 3. with same PVC claim + // to be equivalent + for _, ref := range pod.OwnerReferences { + if ref.Controller != nil && *ref.Controller { + if pvcSet, err := e.getPVCSet(pod); err == nil { + // A pod can only belongs to one controller, so let's return. + return &EquivalencePod{ + ControllerRef: ref, + PVCSet: pvcSet, + } + } else { + // If error encountered, log warning and return nil (i.e. no equivalent pod found) + glog.Warningf("[EquivalencePodGenerator] for pod: %v failed due to: %v", pod.GetName(), err) + return nil + } + } + } + return nil +} + +// getPVCSet returns a set of PVC UIDs of given pod. +func (e *EquivalencePodGenerator) getPVCSet(pod *v1.Pod) (sets.String, error) { + result := sets.NewString() + for _, volume := range pod.Spec.Volumes { + if volume.PersistentVolumeClaim == nil { + continue + } + pvcName := volume.PersistentVolumeClaim.ClaimName + pvc, err := e.pvcInfo.GetPersistentVolumeClaimInfo(pod.GetNamespace(), pvcName) + if err != nil { + return nil, err + } + result.Insert(string(pvc.UID)) + } + + return result, nil +} + +// EquivalencePod is a group of pod attributes which can be reused as equivalence to schedule other pods. +type EquivalencePod struct { + ControllerRef metav1.OwnerReference + PVCSet sets.String +} + +type hostPortInfo struct { + protocol string + hostIP string + hostPort string +} + +// decode decodes string ("protocol/hostIP/hostPort") to *hostPortInfo object. +func decode(info string) *hostPortInfo { + hostPortInfoSlice := strings.Split(info, "/") + + protocol := hostPortInfoSlice[0] + hostIP := hostPortInfoSlice[1] + hostPort := hostPortInfoSlice[2] + + return &hostPortInfo{ + protocol: protocol, + hostIP: hostIP, + hostPort: hostPort, + } +} + +// specialPortConflictCheck detects whether specailHostPort(whose hostIP is 0.0.0.0) is conflict with otherHostPorts. +// return true if we have a conflict. +func specialPortConflictCheck(specialHostPort string, otherHostPorts map[string]bool) bool { + specialHostPortInfo := decode(specialHostPort) + + if specialHostPortInfo.hostIP == schedutil.DefaultBindAllHostIP { + // loop through all the otherHostPorts to see if there exists a conflict + for hostPortItem := range otherHostPorts { + hostPortInfo := decode(hostPortItem) + + // if there exists one hostPortItem which has the same hostPort and protocol with the specialHostPort, that will cause a conflict + if specialHostPortInfo.hostPort == hostPortInfo.hostPort && specialHostPortInfo.protocol == hostPortInfo.protocol { + return true + } + } + + } + + return false +} + +// portsConflict check whether existingPorts and wantPorts conflict with each other +// return true if we have a conflict +func portsConflict(existingPorts, wantPorts map[string]bool) bool { + + for existingPort := range existingPorts { + if specialPortConflictCheck(existingPort, wantPorts) { + return true + } + } + + for wantPort := range wantPorts { + if specialPortConflictCheck(wantPort, existingPorts) { + return true + } + + // general check hostPort conflict procedure for hostIP is not 0.0.0.0 + if existingPorts[wantPort] { + return true + } + } + + return false +} diff --git a/pkg/scheduler/algorithm/predicates/utils_test.go b/pkg/scheduler/algorithm/predicates/utils_test.go new file mode 100644 index 00000000000..308bd8da519 --- /dev/null +++ b/pkg/scheduler/algorithm/predicates/utils_test.go @@ -0,0 +1,263 @@ +/* +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 predicates + +import ( + "fmt" + "reflect" + "testing" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" +) + +// ExampleUtils is a https://blog.golang.org/examples styled unit test. +func ExampleFindLabelsInSet() { + labelSubset := labels.Set{} + labelSubset["label1"] = "value1" + labelSubset["label2"] = "value2" + // Lets make believe that these pods are on the cluster. + // Utility functions will inspect their labels, filter them, and so on. + nsPods := []*v1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Namespace: "ns1", + Labels: map[string]string{ + "label1": "wontSeeThis", + "label2": "wontSeeThis", + "label3": "will_see_this", + }, + }, + }, // first pod which will be used via the utilities + { + ObjectMeta: metav1.ObjectMeta{ + Name: "pod2", + Namespace: "ns1", + }, + }, + + { + ObjectMeta: metav1.ObjectMeta{ + Name: "pod3ThatWeWontSee", + }, + }, + } + fmt.Println(FindLabelsInSet([]string{"label1", "label2", "label3"}, nsPods[0].ObjectMeta.Labels)["label3"]) + AddUnsetLabelsToMap(labelSubset, []string{"label1", "label2", "label3"}, nsPods[0].ObjectMeta.Labels) + fmt.Println(labelSubset) + + for _, pod := range FilterPodsByNamespace(nsPods, "ns1") { + fmt.Print(pod.Name, ",") + } + // Output: + // will_see_this + // label1=value1,label2=value2,label3=will_see_this + // pod1,pod2, +} + +func Test_decode(t *testing.T) { + tests := []struct { + name string + args string + want *hostPortInfo + }{ + { + name: "test1", + args: "UDP/127.0.0.1/80", + want: &hostPortInfo{ + protocol: "UDP", + hostIP: "127.0.0.1", + hostPort: "80", + }, + }, + { + name: "test2", + args: "TCP/127.0.0.1/80", + want: &hostPortInfo{ + protocol: "TCP", + hostIP: "127.0.0.1", + hostPort: "80", + }, + }, + { + name: "test3", + args: "TCP/0.0.0.0/80", + want: &hostPortInfo{ + protocol: "TCP", + hostIP: "0.0.0.0", + hostPort: "80", + }, + }, + } + + for _, tt := range tests { + if got := decode(tt.args); !reflect.DeepEqual(got, tt.want) { + t.Errorf("test name = %v, decode() = %v, want %v", tt.name, got, tt.want) + } + + } +} + +func Test_specialPortConflictCheck(t *testing.T) { + type args struct { + specialHostPort string + otherHostPorts map[string]bool + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "test-1", + args: args{ + specialHostPort: "TCP/0.0.0.0/80", + otherHostPorts: map[string]bool{ + "TCP/127.0.0.2/8080": true, + "TCP/127.0.0.1/80": true, + "UDP/127.0.0.2/8080": true, + }, + }, + want: true, + }, + { + name: "test-2", + args: args{ + specialHostPort: "TCP/0.0.0.0/80", + otherHostPorts: map[string]bool{ + "TCP/127.0.0.2/8080": true, + "UDP/127.0.0.1/80": true, + "UDP/127.0.0.2/8080": true, + }, + }, + want: false, + }, + { + name: "test-3", + args: args{ + specialHostPort: "TCP/0.0.0.0/80", + otherHostPorts: map[string]bool{ + "TCP/127.0.0.2/8080": true, + "TCP/127.0.0.1/8090": true, + "UDP/127.0.0.2/8080": true, + }, + }, + want: false, + }, + { + name: "test-4", + args: args{ + specialHostPort: "TCP/0.0.0.0/80", + otherHostPorts: map[string]bool{ + "UDP/127.0.0.2/8080": true, + "UDP/127.0.0.1/8090": true, + "TCP/127.0.0.2/8080": true, + }, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := specialPortConflictCheck(tt.args.specialHostPort, tt.args.otherHostPorts); got != tt.want { + t.Errorf("specialPortConflictCheck() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_portsConflict(t *testing.T) { + type args struct { + existingPorts map[string]bool + wantPorts map[string]bool + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "test1", + args: args{ + existingPorts: map[string]bool{ + "UDP/127.0.0.1/8080": true, + }, + wantPorts: map[string]bool{ + "UDP/127.0.0.1/8080": true, + }, + }, + want: true, + }, + { + name: "test2", + args: args{ + existingPorts: map[string]bool{ + "UDP/127.0.0.2/8080": true, + }, + wantPorts: map[string]bool{ + "UDP/127.0.0.1/8080": true, + }, + }, + want: false, + }, + { + name: "test3", + args: args{ + existingPorts: map[string]bool{ + "TCP/127.0.0.1/8080": true, + }, + wantPorts: map[string]bool{ + "UDP/127.0.0.1/8080": true, + }, + }, + want: false, + }, + { + name: "test4", + args: args{ + existingPorts: map[string]bool{ + "TCP/0.0.0.0/8080": true, + }, + wantPorts: map[string]bool{ + "TCP/127.0.0.1/8080": true, + }, + }, + want: true, + }, + { + name: "test5", + args: args{ + existingPorts: map[string]bool{ + "TCP/127.0.0.1/8080": true, + }, + wantPorts: map[string]bool{ + "TCP/0.0.0.0/8080": true, + }, + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := portsConflict(tt.args.existingPorts, tt.args.wantPorts); got != tt.want { + t.Errorf("portsConflict() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/scheduler/algorithm/priorities/BUILD b/pkg/scheduler/algorithm/priorities/BUILD new file mode 100644 index 00000000000..19bb57476f0 --- /dev/null +++ b/pkg/scheduler/algorithm/priorities/BUILD @@ -0,0 +1,93 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_library( + name = "go_default_library", + srcs = [ + "balanced_resource_allocation.go", + "image_locality.go", + "interpod_affinity.go", + "least_requested.go", + "metadata.go", + "most_requested.go", + "node_affinity.go", + "node_label.go", + "node_prefer_avoid_pods.go", + "reduce.go", + "resource_allocation.go", + "resource_limits.go", + "selector_spreading.go", + "taint_toleration.go", + "test_util.go", + ], + importpath = "k8s.io/kubernetes/pkg/scheduler/algorithm/priorities", + deps = [ + "//pkg/apis/core/v1/helper:go_default_library", + "//pkg/scheduler/algorithm:go_default_library", + "//pkg/scheduler/algorithm/predicates:go_default_library", + "//pkg/scheduler/algorithm/priorities/util:go_default_library", + "//pkg/scheduler/api:go_default_library", + "//pkg/scheduler/schedulercache:go_default_library", + "//pkg/util/node:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/client-go/util/workqueue:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "balanced_resource_allocation_test.go", + "image_locality_test.go", + "interpod_affinity_test.go", + "least_requested_test.go", + "metadata_test.go", + "most_requested_test.go", + "node_affinity_test.go", + "node_label_test.go", + "node_prefer_avoid_pods_test.go", + "resource_limits_test.go", + "selector_spreading_test.go", + "taint_toleration_test.go", + ], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/scheduler/algorithm/priorities", + deps = [ + "//pkg/kubelet/apis:go_default_library", + "//pkg/scheduler/algorithm/priorities/util:go_default_library", + "//pkg/scheduler/api:go_default_library", + "//pkg/scheduler/schedulercache:go_default_library", + "//pkg/scheduler/testing:go_default_library", + "//vendor/k8s.io/api/apps/v1beta1:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//pkg/scheduler/algorithm/priorities/util:all-srcs", + ], + tags = ["automanaged"], +) diff --git a/pkg/scheduler/algorithm/priorities/balanced_resource_allocation.go b/pkg/scheduler/algorithm/priorities/balanced_resource_allocation.go new file mode 100644 index 00000000000..0f3d98f6385 --- /dev/null +++ b/pkg/scheduler/algorithm/priorities/balanced_resource_allocation.go @@ -0,0 +1,61 @@ +/* +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 priorities + +import ( + "math" + + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" +) + +var ( + balanceResourcePriority = &ResourceAllocationPriority{"BalanceResourceAllocation", balancedResourceScorer} + + // BalancedResourceAllocationMap favors nodes with balanced resource usage rate. + // BalancedResourceAllocationMap should **NOT** be used alone, and **MUST** be used together + // with LeastRequestedPriority. It calculates the difference between the cpu and memory fraction + // of capacity, and prioritizes the host based on how close the two metrics are to each other. + // Detail: score = 10 - abs(cpuFraction-memoryFraction)*10. The algorithm is partly inspired by: + // "Wei Huang et al. An Energy Efficient Virtual Machine Placement Algorithm with Balanced + // Resource Utilization" + BalancedResourceAllocationMap = balanceResourcePriority.PriorityMap +) + +func balancedResourceScorer(requested, allocable *schedulercache.Resource) int64 { + cpuFraction := fractionOfCapacity(requested.MilliCPU, allocable.MilliCPU) + memoryFraction := fractionOfCapacity(requested.Memory, allocable.Memory) + + if cpuFraction >= 1 || memoryFraction >= 1 { + // if requested >= capacity, the corresponding host should never be preferred. + return 0 + } + + // Upper and lower boundary of difference between cpuFraction and memoryFraction are -1 and 1 + // respectively. Multilying the absolute value of the difference by 10 scales the value to + // 0-10 with 0 representing well balanced allocation and 10 poorly balanced. Subtracting it from + // 10 leads to the score which also scales from 0 to 10 while 10 representing well balanced. + diff := math.Abs(cpuFraction - memoryFraction) + return int64((1 - diff) * float64(schedulerapi.MaxPriority)) +} + +func fractionOfCapacity(requested, capacity int64) float64 { + if capacity == 0 { + return 1 + } + return float64(requested) / float64(capacity) +} diff --git a/plugin/pkg/scheduler/algorithm/priorities/balanced_resource_allocation_test.go b/pkg/scheduler/algorithm/priorities/balanced_resource_allocation_test.go similarity index 98% rename from plugin/pkg/scheduler/algorithm/priorities/balanced_resource_allocation_test.go rename to pkg/scheduler/algorithm/priorities/balanced_resource_allocation_test.go index 777be1b1499..9b109d109c3 100644 --- a/plugin/pkg/scheduler/algorithm/priorities/balanced_resource_allocation_test.go +++ b/pkg/scheduler/algorithm/priorities/balanced_resource_allocation_test.go @@ -23,8 +23,8 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" ) func TestBalancedResourceAllocation(t *testing.T) { @@ -253,7 +253,7 @@ func TestBalancedResourceAllocation(t *testing.T) { for _, test := range tests { nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods, test.nodes) - list, err := priorityFunction(BalancedResourceAllocationMap, nil)(test.pod, nodeNameToInfo, test.nodes) + list, err := priorityFunction(BalancedResourceAllocationMap, nil, nil)(test.pod, nodeNameToInfo, test.nodes) if err != nil { t.Errorf("unexpected error: %v", err) } diff --git a/pkg/scheduler/algorithm/priorities/image_locality.go b/pkg/scheduler/algorithm/priorities/image_locality.go new file mode 100644 index 00000000000..5df5d35308d --- /dev/null +++ b/pkg/scheduler/algorithm/priorities/image_locality.go @@ -0,0 +1,88 @@ +/* +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 priorities + +import ( + "fmt" + + "k8s.io/api/core/v1" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" +) + +// This is a reasonable size range of all container images. 90%ile of images on dockerhub drops into this range. +const ( + mb int64 = 1024 * 1024 + minImgSize int64 = 23 * mb + maxImgSize int64 = 1000 * mb +) + +// ImageLocalityPriorityMap is a priority function that favors nodes that already have requested pod container's images. +// It will detect whether the requested images are present on a node, and then calculate a score ranging from 0 to 10 +// based on the total size of those images. +// - If none of the images are present, this node will be given the lowest priority. +// - If some of the images are present on a node, the larger their sizes' sum, the higher the node's priority. +func ImageLocalityPriorityMap(pod *v1.Pod, meta interface{}, nodeInfo *schedulercache.NodeInfo) (schedulerapi.HostPriority, error) { + node := nodeInfo.Node() + if node == nil { + return schedulerapi.HostPriority{}, fmt.Errorf("node not found") + } + + sumSize := totalImageSize(node, pod.Spec.Containers) + + return schedulerapi.HostPriority{ + Host: node.Name, + Score: calculateScoreFromSize(sumSize), + }, nil +} + +// calculateScoreFromSize calculates the priority of a node. sumSize is sum size of requested images on this node. +// 1. Split image size range into 10 buckets. +// 2. Decide the priority of a given sumSize based on which bucket it belongs to. +func calculateScoreFromSize(sumSize int64) int { + switch { + case sumSize == 0 || sumSize < minImgSize: + // 0 means none of the images required by this pod are present on this + // node or the total size of the images present is too small to be taken into further consideration. + return 0 + + case sumSize >= maxImgSize: + // If existing images' total size is larger than max, just make it highest priority. + return schedulerapi.MaxPriority + } + + return int((int64(schedulerapi.MaxPriority) * (sumSize - minImgSize) / (maxImgSize - minImgSize)) + 1) +} + +// totalImageSize returns the total image size of all the containers that are already on the node. +func totalImageSize(node *v1.Node, containers []v1.Container) int64 { + imageSizes := make(map[string]int64) + for _, image := range node.Status.Images { + for _, name := range image.Names { + imageSizes[name] = image.SizeBytes + } + } + + var total int64 + for _, container := range containers { + if size, ok := imageSizes[container.Image]; ok { + total += size + } + } + + return total +} diff --git a/plugin/pkg/scheduler/algorithm/priorities/image_locality_test.go b/pkg/scheduler/algorithm/priorities/image_locality_test.go similarity index 95% rename from plugin/pkg/scheduler/algorithm/priorities/image_locality_test.go rename to pkg/scheduler/algorithm/priorities/image_locality_test.go index c2e0feabbdc..5a3bb66e792 100644 --- a/plugin/pkg/scheduler/algorithm/priorities/image_locality_test.go +++ b/pkg/scheduler/algorithm/priorities/image_locality_test.go @@ -23,8 +23,8 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" ) func TestImageLocalityPriority(t *testing.T) { @@ -161,7 +161,7 @@ func TestImageLocalityPriority(t *testing.T) { for _, test := range tests { nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods, test.nodes) - list, err := priorityFunction(ImageLocalityPriorityMap, nil)(test.pod, nodeNameToInfo, test.nodes) + list, err := priorityFunction(ImageLocalityPriorityMap, nil, nil)(test.pod, nodeNameToInfo, test.nodes) if err != nil { t.Errorf("unexpected error: %v", err) } diff --git a/plugin/pkg/scheduler/algorithm/priorities/interpod_affinity.go b/pkg/scheduler/algorithm/priorities/interpod_affinity.go similarity index 83% rename from plugin/pkg/scheduler/algorithm/priorities/interpod_affinity.go rename to pkg/scheduler/algorithm/priorities/interpod_affinity.go index 7abe732d45c..59ed117f82e 100644 --- a/plugin/pkg/scheduler/algorithm/priorities/interpod_affinity.go +++ b/pkg/scheduler/algorithm/priorities/interpod_affinity.go @@ -17,18 +17,17 @@ limitations under the License. package priorities import ( - "strings" "sync" "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/util/workqueue" - kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates" - priorityutil "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/priorities/util" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + "k8s.io/kubernetes/pkg/scheduler/algorithm/predicates" + priorityutil "k8s.io/kubernetes/pkg/scheduler/algorithm/priorities/util" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" "github.com/golang/glog" ) @@ -37,14 +36,14 @@ type InterPodAffinity struct { info predicates.NodeInfo nodeLister algorithm.NodeLister podLister algorithm.PodLister - hardPodAffinityWeight int + hardPodAffinityWeight int32 } func NewInterPodAffinityPriority( info predicates.NodeInfo, nodeLister algorithm.NodeLister, podLister algorithm.PodLister, - hardPodAffinityWeight int) algorithm.PriorityFunction { + hardPodAffinityWeight int32) algorithm.PriorityFunction { interPodAffinity := &InterPodAffinity{ info: info, nodeLister: nodeLister, @@ -62,17 +61,14 @@ type podAffinityPriorityMap struct { // counts store the mapping from node name to so-far computed score of // the node. counts map[string]float64 - // failureDomains contain default failure domains keys - failureDomains priorityutil.Topologies // The first error that we faced. firstError error } func newPodAffinityPriorityMap(nodes []*v1.Node) *podAffinityPriorityMap { return &podAffinityPriorityMap{ - nodes: nodes, - counts: make(map[string]float64, len(nodes)), - failureDomains: priorityutil.Topologies{DefaultKeys: strings.Split(kubeletapis.DefaultFailureDomains, ",")}, + nodes: nodes, + counts: make(map[string]float64, len(nodes)), } } @@ -97,7 +93,7 @@ func (p *podAffinityPriorityMap) processTerm(term *v1.PodAffinityTerm, podDefini p.Lock() defer p.Unlock() for _, node := range p.nodes { - if p.failureDomains.NodesHaveSameTopologyKey(node, fixedNode, term.TopologyKey) { + if priorityutil.NodesHaveSameTopologyKey(node, fixedNode, term.TopologyKey) { p.counts[node.Name] += weight } } @@ -137,6 +133,10 @@ func (ipa *InterPodAffinity) CalculateInterPodAffinityPriority(pod *v1.Pod, node processPod := func(existingPod *v1.Pod) error { existingPodNode, err := ipa.info.GetNodeInfo(existingPod.Spec.NodeName) if err != nil { + if apierrors.IsNotFound(err) { + glog.Errorf("Node not found, %v", existingPod.Spec.NodeName) + return nil + } return err } existingPodAffinity := existingPod.Spec.Affinity @@ -189,19 +189,21 @@ func (ipa *InterPodAffinity) CalculateInterPodAffinityPriority(pod *v1.Pod, node } processNode := func(i int) { nodeInfo := nodeNameToInfo[allNodeNames[i]] - if hasAffinityConstraints || hasAntiAffinityConstraints { - // We need to process all the nodes. - for _, existingPod := range nodeInfo.Pods() { - if err := processPod(existingPod); err != nil { - pm.setError(err) + if nodeInfo.Node() != nil { + if hasAffinityConstraints || hasAntiAffinityConstraints { + // We need to process all the nodes. + for _, existingPod := range nodeInfo.Pods() { + if err := processPod(existingPod); err != nil { + pm.setError(err) + } } - } - } else { - // The pod doesn't have any constraints - we need to check only existing - // ones that have some. - for _, existingPod := range nodeInfo.PodsWithAffinity() { - if err := processPod(existingPod); err != nil { - pm.setError(err) + } else { + // The pod doesn't have any constraints - we need to check only existing + // ones that have some. + for _, existingPod := range nodeInfo.PodsWithAffinity() { + if err := processPod(existingPod); err != nil { + pm.setError(err) + } } } } @@ -229,9 +231,7 @@ func (ipa *InterPodAffinity) CalculateInterPodAffinityPriority(pod *v1.Pod, node } result = append(result, schedulerapi.HostPriority{Host: node.Name, Score: int(fScore)}) if glog.V(10) { - // We explicitly don't do glog.V(10).Infof() to avoid computing all the parameters if this is - // not logged. There is visible performance gain from it. - glog.V(10).Infof("%v -> %v: InterPodAffinityPriority, Score: (%d)", pod.Name, node.Name, int(fScore)) + glog.Infof("%v -> %v: InterPodAffinityPriority, Score: (%d)", pod.Name, node.Name, int(fScore)) } } return result, nil diff --git a/plugin/pkg/scheduler/algorithm/priorities/interpod_affinity_test.go b/pkg/scheduler/algorithm/priorities/interpod_affinity_test.go similarity index 99% rename from plugin/pkg/scheduler/algorithm/priorities/interpod_affinity_test.go rename to pkg/scheduler/algorithm/priorities/interpod_affinity_test.go index 7ccd963bcf8..6987e1d9ef4 100644 --- a/plugin/pkg/scheduler/algorithm/priorities/interpod_affinity_test.go +++ b/pkg/scheduler/algorithm/priorities/interpod_affinity_test.go @@ -23,9 +23,9 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" - schedulertesting "k8s.io/kubernetes/plugin/pkg/scheduler/testing" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" + schedulertesting "k8s.io/kubernetes/pkg/scheduler/testing" ) type FakeNodeListInfo []*v1.Node @@ -561,7 +561,7 @@ func TestHardPodAffinitySymmetricWeight(t *testing.T) { pod *v1.Pod pods []*v1.Pod nodes []*v1.Node - hardPodAffinityWeight int + hardPodAffinityWeight int32 expectedList schedulerapi.HostPriorityList test string }{ diff --git a/pkg/scheduler/algorithm/priorities/least_requested.go b/pkg/scheduler/algorithm/priorities/least_requested.go new file mode 100644 index 00000000000..0c2f0481f93 --- /dev/null +++ b/pkg/scheduler/algorithm/priorities/least_requested.go @@ -0,0 +1,53 @@ +/* +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 priorities + +import ( + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" +) + +var ( + leastResourcePriority = &ResourceAllocationPriority{"LeastResourceAllocation", leastResourceScorer} + + // LeastRequestedPriority is a priority function that favors nodes with fewer requested resources. + // It calculates the percentage of memory and CPU requested by pods scheduled on the node, and + // prioritizes based on the minimum of the average of the fraction of requested to capacity. + // + // Details: + // cpu((capacity-sum(requested))*10/capacity) + memory((capacity-sum(requested))*10/capacity)/2 + LeastRequestedPriorityMap = leastResourcePriority.PriorityMap +) + +func leastResourceScorer(requested, allocable *schedulercache.Resource) int64 { + return (leastRequestedScore(requested.MilliCPU, allocable.MilliCPU) + + leastRequestedScore(requested.Memory, allocable.Memory)) / 2 +} + +// The unused capacity is calculated on a scale of 0-10 +// 0 being the lowest priority and 10 being the highest. +// The more unused resources the higher the score is. +func leastRequestedScore(requested, capacity int64) int64 { + if capacity == 0 { + return 0 + } + if requested > capacity { + return 0 + } + + return ((capacity - requested) * int64(schedulerapi.MaxPriority)) / capacity +} diff --git a/plugin/pkg/scheduler/algorithm/priorities/least_requested_test.go b/pkg/scheduler/algorithm/priorities/least_requested_test.go similarity index 97% rename from plugin/pkg/scheduler/algorithm/priorities/least_requested_test.go rename to pkg/scheduler/algorithm/priorities/least_requested_test.go index 08e083361b4..3b5308d7ba1 100644 --- a/plugin/pkg/scheduler/algorithm/priorities/least_requested_test.go +++ b/pkg/scheduler/algorithm/priorities/least_requested_test.go @@ -23,8 +23,8 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" ) func TestLeastRequested(t *testing.T) { @@ -253,7 +253,7 @@ func TestLeastRequested(t *testing.T) { for _, test := range tests { nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods, test.nodes) - list, err := priorityFunction(LeastRequestedPriorityMap, nil)(test.pod, nodeNameToInfo, test.nodes) + list, err := priorityFunction(LeastRequestedPriorityMap, nil, nil)(test.pod, nodeNameToInfo, test.nodes) if err != nil { t.Errorf("unexpected error: %v", err) } diff --git a/pkg/scheduler/algorithm/priorities/metadata.go b/pkg/scheduler/algorithm/priorities/metadata.go new file mode 100644 index 00000000000..fe9dce79f47 --- /dev/null +++ b/pkg/scheduler/algorithm/priorities/metadata.go @@ -0,0 +1,112 @@ +/* +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 priorities + +import ( + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + priorityutil "k8s.io/kubernetes/pkg/scheduler/algorithm/priorities/util" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" +) + +type PriorityMetadataFactory struct { + serviceLister algorithm.ServiceLister + controllerLister algorithm.ControllerLister + replicaSetLister algorithm.ReplicaSetLister + statefulSetLister algorithm.StatefulSetLister +} + +func NewPriorityMetadataFactory(serviceLister algorithm.ServiceLister, controllerLister algorithm.ControllerLister, replicaSetLister algorithm.ReplicaSetLister, statefulSetLister algorithm.StatefulSetLister) algorithm.MetadataProducer { + factory := &PriorityMetadataFactory{ + serviceLister: serviceLister, + controllerLister: controllerLister, + replicaSetLister: replicaSetLister, + statefulSetLister: statefulSetLister, + } + return factory.PriorityMetadata +} + +// priorityMetadata is a type that is passed as metadata for priority functions +type priorityMetadata struct { + nonZeroRequest *schedulercache.Resource + podTolerations []v1.Toleration + affinity *v1.Affinity + podSelectors []labels.Selector + controllerRef *metav1.OwnerReference + podFirstServiceSelector labels.Selector +} + +// PriorityMetadata is a MetadataProducer. Node info can be nil. +func (pmf *PriorityMetadataFactory) PriorityMetadata(pod *v1.Pod, nodeNameToInfo map[string]*schedulercache.NodeInfo) interface{} { + // If we cannot compute metadata, just return nil + if pod == nil { + return nil + } + return &priorityMetadata{ + nonZeroRequest: getNonZeroRequests(pod), + podTolerations: getAllTolerationPreferNoSchedule(pod.Spec.Tolerations), + affinity: pod.Spec.Affinity, + podSelectors: getSelectors(pod, pmf.serviceLister, pmf.controllerLister, pmf.replicaSetLister, pmf.statefulSetLister), + controllerRef: priorityutil.GetControllerRef(pod), + podFirstServiceSelector: getFirstServiceSelector(pod, pmf.serviceLister), + } +} + +// getFirstServiceSelector returns one selector of services the given pod. +func getFirstServiceSelector(pod *v1.Pod, sl algorithm.ServiceLister) (firstServiceSelector labels.Selector) { + if services, err := sl.GetPodServices(pod); err == nil && len(services) > 0 { + return labels.SelectorFromSet(services[0].Spec.Selector) + } + return nil +} + +// getSelectors returns selectors of services, RCs and RSs matching the given pod. +func getSelectors(pod *v1.Pod, sl algorithm.ServiceLister, cl algorithm.ControllerLister, rsl algorithm.ReplicaSetLister, ssl algorithm.StatefulSetLister) []labels.Selector { + var selectors []labels.Selector + + if services, err := sl.GetPodServices(pod); err == nil { + for _, service := range services { + selectors = append(selectors, labels.SelectorFromSet(service.Spec.Selector)) + } + } + + if rcs, err := cl.GetPodControllers(pod); err == nil { + for _, rc := range rcs { + selectors = append(selectors, labels.SelectorFromSet(rc.Spec.Selector)) + } + } + + if rss, err := rsl.GetPodReplicaSets(pod); err == nil { + for _, rs := range rss { + if selector, err := metav1.LabelSelectorAsSelector(rs.Spec.Selector); err == nil { + selectors = append(selectors, selector) + } + } + } + + if sss, err := ssl.GetPodStatefulSets(pod); err == nil { + for _, ss := range sss { + if selector, err := metav1.LabelSelectorAsSelector(ss.Spec.Selector); err == nil { + selectors = append(selectors, selector) + } + } + } + + return selectors +} diff --git a/pkg/scheduler/algorithm/priorities/metadata_test.go b/pkg/scheduler/algorithm/priorities/metadata_test.go new file mode 100644 index 00000000000..ada1a3c46aa --- /dev/null +++ b/pkg/scheduler/algorithm/priorities/metadata_test.go @@ -0,0 +1,167 @@ +/* +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 priorities + +import ( + "reflect" + "testing" + + apps "k8s.io/api/apps/v1beta1" + "k8s.io/api/core/v1" + extensions "k8s.io/api/extensions/v1beta1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + priorityutil "k8s.io/kubernetes/pkg/scheduler/algorithm/priorities/util" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" + schedulertesting "k8s.io/kubernetes/pkg/scheduler/testing" +) + +func TestPriorityMetadata(t *testing.T) { + nonZeroReqs := &schedulercache.Resource{} + nonZeroReqs.MilliCPU = priorityutil.DefaultMilliCpuRequest + nonZeroReqs.Memory = priorityutil.DefaultMemoryRequest + + specifiedReqs := &schedulercache.Resource{} + specifiedReqs.MilliCPU = 200 + specifiedReqs.Memory = 2000 + + tolerations := []v1.Toleration{{ + Key: "foo", + Operator: v1.TolerationOpEqual, + Value: "bar", + Effect: v1.TaintEffectPreferNoSchedule, + }} + podAffinity := &v1.Affinity{ + PodAffinity: &v1.PodAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{ + { + Weight: 5, + PodAffinityTerm: v1.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "security", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"S1"}, + }, + }, + }, + TopologyKey: "region", + }, + }, + }, + }, + } + podWithTolerationsAndAffinity := &v1.Pod{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "container", + Image: "image", + ImagePullPolicy: "Always", + }, + }, + Affinity: podAffinity, + Tolerations: tolerations, + }, + } + podWithTolerationsAndRequests := &v1.Pod{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "container", + Image: "image", + ImagePullPolicy: "Always", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), + v1.ResourceMemory: resource.MustParse("2000"), + }, + }, + }, + }, + Tolerations: tolerations, + }, + } + podWithAffinityAndRequests := &v1.Pod{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "container", + Image: "image", + ImagePullPolicy: "Always", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), + v1.ResourceMemory: resource.MustParse("2000"), + }, + }, + }, + }, + Affinity: podAffinity, + }, + } + tests := []struct { + pod *v1.Pod + test string + expected interface{} + }{ + { + pod: nil, + expected: nil, + test: "pod is nil , priorityMetadata is nil", + }, + { + pod: podWithTolerationsAndAffinity, + expected: &priorityMetadata{ + nonZeroRequest: nonZeroReqs, + podTolerations: tolerations, + affinity: podAffinity, + }, + test: "Produce a priorityMetadata with default requests", + }, + { + pod: podWithTolerationsAndRequests, + expected: &priorityMetadata{ + nonZeroRequest: specifiedReqs, + podTolerations: tolerations, + affinity: nil, + }, + test: "Produce a priorityMetadata with specified requests", + }, + { + pod: podWithAffinityAndRequests, + expected: &priorityMetadata{ + nonZeroRequest: specifiedReqs, + podTolerations: nil, + affinity: podAffinity, + }, + test: "Produce a priorityMetadata with specified requests", + }, + } + mataDataProducer := NewPriorityMetadataFactory( + schedulertesting.FakeServiceLister([]*v1.Service{}), + schedulertesting.FakeControllerLister([]*v1.ReplicationController{}), + schedulertesting.FakeReplicaSetLister([]*extensions.ReplicaSet{}), + schedulertesting.FakeStatefulSetLister([]*apps.StatefulSet{})) + for _, test := range tests { + ptData := mataDataProducer(test.pod, nil) + if !reflect.DeepEqual(test.expected, ptData) { + t.Errorf("%s: expected %#v, got %#v", test.test, test.expected, ptData) + } + } +} diff --git a/pkg/scheduler/algorithm/priorities/most_requested.go b/pkg/scheduler/algorithm/priorities/most_requested.go new file mode 100644 index 00000000000..ed9053aa1e6 --- /dev/null +++ b/pkg/scheduler/algorithm/priorities/most_requested.go @@ -0,0 +1,55 @@ +/* +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 priorities + +import ( + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" +) + +var ( + mostResourcePriority = &ResourceAllocationPriority{"MostResourceAllocation", mostResourceScorer} + + // MostRequestedPriority is a priority function that favors nodes with most requested resources. + // It calculates the percentage of memory and CPU requested by pods scheduled on the node, and prioritizes + // based on the maximum of the average of the fraction of requested to capacity. + // Details: (cpu(10 * sum(requested) / capacity) + memory(10 * sum(requested) / capacity)) / 2 + MostRequestedPriorityMap = mostResourcePriority.PriorityMap +) + +func mostResourceScorer(requested, allocable *schedulercache.Resource) int64 { + return (mostRequestedScore(requested.MilliCPU, allocable.MilliCPU) + + mostRequestedScore(requested.Memory, allocable.Memory)) / 2 +} + +// The used capacity is calculated on a scale of 0-10 +// 0 being the lowest priority and 10 being the highest. +// The more resources are used the higher the score is. This function +// is almost a reversed version of least_requested_priority.calculatUnusedScore +// (10 - calculateUnusedScore). The main difference is in rounding. It was added to +// keep the final formula clean and not to modify the widely used (by users +// in their default scheduling policies) calculateUSedScore. +func mostRequestedScore(requested, capacity int64) int64 { + if capacity == 0 { + return 0 + } + if requested > capacity { + return 0 + } + + return (requested * schedulerapi.MaxPriority) / capacity +} diff --git a/plugin/pkg/scheduler/algorithm/priorities/most_requested_test.go b/pkg/scheduler/algorithm/priorities/most_requested_test.go similarity index 96% rename from plugin/pkg/scheduler/algorithm/priorities/most_requested_test.go rename to pkg/scheduler/algorithm/priorities/most_requested_test.go index a77692b4af9..4869ad85eb6 100644 --- a/plugin/pkg/scheduler/algorithm/priorities/most_requested_test.go +++ b/pkg/scheduler/algorithm/priorities/most_requested_test.go @@ -23,8 +23,8 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" ) func TestMostRequested(t *testing.T) { @@ -210,7 +210,7 @@ func TestMostRequested(t *testing.T) { for _, test := range tests { nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods, test.nodes) - list, err := priorityFunction(MostRequestedPriorityMap, nil)(test.pod, nodeNameToInfo, test.nodes) + list, err := priorityFunction(MostRequestedPriorityMap, nil, nil)(test.pod, nodeNameToInfo, test.nodes) if err != nil { t.Errorf("unexpected error: %v", err) } diff --git a/pkg/scheduler/algorithm/priorities/node_affinity.go b/pkg/scheduler/algorithm/priorities/node_affinity.go new file mode 100644 index 00000000000..d1c79353614 --- /dev/null +++ b/pkg/scheduler/algorithm/priorities/node_affinity.go @@ -0,0 +1,77 @@ +/* +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 priorities + +import ( + "fmt" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/labels" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" +) + +// CalculateNodeAffinityPriority prioritizes nodes according to node affinity scheduling preferences +// indicated in PreferredDuringSchedulingIgnoredDuringExecution. Each time a node match a preferredSchedulingTerm, +// it will a get an add of preferredSchedulingTerm.Weight. Thus, the more preferredSchedulingTerms +// the node satisfies and the more the preferredSchedulingTerm that is satisfied weights, the higher +// score the node gets. +func CalculateNodeAffinityPriorityMap(pod *v1.Pod, meta interface{}, nodeInfo *schedulercache.NodeInfo) (schedulerapi.HostPriority, error) { + node := nodeInfo.Node() + if node == nil { + return schedulerapi.HostPriority{}, fmt.Errorf("node not found") + } + + var affinity *v1.Affinity + if priorityMeta, ok := meta.(*priorityMetadata); ok { + affinity = priorityMeta.affinity + } else { + // We couldn't parse metadata - fallback to the podspec. + affinity = pod.Spec.Affinity + } + + var count int32 + // A nil element of PreferredDuringSchedulingIgnoredDuringExecution matches no objects. + // An element of PreferredDuringSchedulingIgnoredDuringExecution that refers to an + // empty PreferredSchedulingTerm matches all objects. + if affinity != nil && affinity.NodeAffinity != nil && affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil { + // Match PreferredDuringSchedulingIgnoredDuringExecution term by term. + for i := range affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution { + preferredSchedulingTerm := &affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[i] + if preferredSchedulingTerm.Weight == 0 { + continue + } + + // TODO: Avoid computing it for all nodes if this becomes a performance problem. + nodeSelector, err := v1helper.NodeSelectorRequirementsAsSelector(preferredSchedulingTerm.Preference.MatchExpressions) + if err != nil { + return schedulerapi.HostPriority{}, err + } + if nodeSelector.Matches(labels.Set(node.Labels)) { + count += preferredSchedulingTerm.Weight + } + } + } + + return schedulerapi.HostPriority{ + Host: node.Name, + Score: int(count), + }, nil +} + +var CalculateNodeAffinityPriorityReduce = NormalizeReduce(schedulerapi.MaxPriority, false) diff --git a/plugin/pkg/scheduler/algorithm/priorities/node_affinity_test.go b/pkg/scheduler/algorithm/priorities/node_affinity_test.go similarity index 97% rename from plugin/pkg/scheduler/algorithm/priorities/node_affinity_test.go rename to pkg/scheduler/algorithm/priorities/node_affinity_test.go index 9d425661a92..e7054b1514e 100644 --- a/plugin/pkg/scheduler/algorithm/priorities/node_affinity_test.go +++ b/pkg/scheduler/algorithm/priorities/node_affinity_test.go @@ -22,8 +22,8 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" ) func TestNodeAffinityPriority(t *testing.T) { @@ -167,7 +167,7 @@ func TestNodeAffinityPriority(t *testing.T) { for _, test := range tests { nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(nil, test.nodes) - nap := priorityFunction(CalculateNodeAffinityPriorityMap, CalculateNodeAffinityPriorityReduce) + nap := priorityFunction(CalculateNodeAffinityPriorityMap, CalculateNodeAffinityPriorityReduce, nil) list, err := nap(test.pod, nodeNameToInfo, test.nodes) if err != nil { t.Errorf("unexpected error: %v", err) diff --git a/plugin/pkg/scheduler/algorithm/priorities/node_label.go b/pkg/scheduler/algorithm/priorities/node_label.go similarity index 91% rename from plugin/pkg/scheduler/algorithm/priorities/node_label.go rename to pkg/scheduler/algorithm/priorities/node_label.go index 7eef5a3bd76..8c1a86f5900 100644 --- a/plugin/pkg/scheduler/algorithm/priorities/node_label.go +++ b/pkg/scheduler/algorithm/priorities/node_label.go @@ -21,9 +21,9 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" ) type NodeLabelPrioritizer struct { diff --git a/plugin/pkg/scheduler/algorithm/priorities/node_label_test.go b/pkg/scheduler/algorithm/priorities/node_label_test.go similarity index 93% rename from plugin/pkg/scheduler/algorithm/priorities/node_label_test.go rename to pkg/scheduler/algorithm/priorities/node_label_test.go index fbced34e336..416fc9cc092 100644 --- a/plugin/pkg/scheduler/algorithm/priorities/node_label_test.go +++ b/pkg/scheduler/algorithm/priorities/node_label_test.go @@ -23,8 +23,8 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" ) func TestNewNodeLabelPriority(t *testing.T) { @@ -108,7 +108,11 @@ func TestNewNodeLabelPriority(t *testing.T) { for _, test := range tests { nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(nil, test.nodes) - list, err := priorityFunction(NewNodeLabelPriority(test.label, test.presence))(nil, nodeNameToInfo, test.nodes) + labelPrioritizer := &NodeLabelPrioritizer{ + label: test.label, + presence: test.presence, + } + list, err := priorityFunction(labelPrioritizer.CalculateNodeLabelPriorityMap, nil, nil)(nil, nodeNameToInfo, test.nodes) if err != nil { t.Errorf("unexpected error: %v", err) } diff --git a/plugin/pkg/scheduler/algorithm/priorities/node_prefer_avoid_pods.go b/pkg/scheduler/algorithm/priorities/node_prefer_avoid_pods.go similarity index 77% rename from plugin/pkg/scheduler/algorithm/priorities/node_prefer_avoid_pods.go rename to pkg/scheduler/algorithm/priorities/node_prefer_avoid_pods.go index 985e323f466..c0f40490822 100644 --- a/plugin/pkg/scheduler/algorithm/priorities/node_prefer_avoid_pods.go +++ b/pkg/scheduler/algorithm/priorities/node_prefer_avoid_pods.go @@ -20,10 +20,11 @@ import ( "fmt" "k8s.io/api/core/v1" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" - priorityutil "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/priorities/util" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" + priorityutil "k8s.io/kubernetes/pkg/scheduler/algorithm/priorities/util" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" ) func CalculateNodePreferAvoidPodsPriorityMap(pod *v1.Pod, meta interface{}, nodeInfo *schedulercache.NodeInfo) (schedulerapi.HostPriority, error) { @@ -31,8 +32,14 @@ func CalculateNodePreferAvoidPodsPriorityMap(pod *v1.Pod, meta interface{}, node if node == nil { return schedulerapi.HostPriority{}, fmt.Errorf("node not found") } + var controllerRef *metav1.OwnerReference + if priorityMeta, ok := meta.(*priorityMetadata); ok { + controllerRef = priorityMeta.controllerRef + } else { + // We couldn't parse metadata - fallback to the podspec. + controllerRef = priorityutil.GetControllerRef(pod) + } - controllerRef := priorityutil.GetControllerRef(pod) if controllerRef != nil { // Ignore pods that are owned by other controller than ReplicationController // or ReplicaSet. diff --git a/plugin/pkg/scheduler/algorithm/priorities/node_prefer_avoid_pods_test.go b/pkg/scheduler/algorithm/priorities/node_prefer_avoid_pods_test.go similarity index 96% rename from plugin/pkg/scheduler/algorithm/priorities/node_prefer_avoid_pods_test.go rename to pkg/scheduler/algorithm/priorities/node_prefer_avoid_pods_test.go index a18ddcc03d6..8fb852fc6f5 100644 --- a/plugin/pkg/scheduler/algorithm/priorities/node_prefer_avoid_pods_test.go +++ b/pkg/scheduler/algorithm/priorities/node_prefer_avoid_pods_test.go @@ -23,8 +23,8 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" ) func TestNodePreferAvoidPriority(t *testing.T) { @@ -142,7 +142,7 @@ func TestNodePreferAvoidPriority(t *testing.T) { for _, test := range tests { nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(nil, test.nodes) - list, err := priorityFunction(CalculateNodePreferAvoidPodsPriorityMap, nil)(test.pod, nodeNameToInfo, test.nodes) + list, err := priorityFunction(CalculateNodePreferAvoidPodsPriorityMap, nil, nil)(test.pod, nodeNameToInfo, test.nodes) if err != nil { t.Errorf("unexpected error: %v", err) } diff --git a/pkg/scheduler/algorithm/priorities/reduce.go b/pkg/scheduler/algorithm/priorities/reduce.go new file mode 100644 index 00000000000..608a83355e9 --- /dev/null +++ b/pkg/scheduler/algorithm/priorities/reduce.go @@ -0,0 +1,64 @@ +/* +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 priorities + +import ( + "k8s.io/api/core/v1" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" +) + +// NormalizeReduce generates a PriorityReduceFunction that can normalize the result +// scores to [0, maxPriority]. If reverse is set to true, it reverses the scores by +// subtracting it from maxPriority. +func NormalizeReduce(maxPriority int, reverse bool) algorithm.PriorityReduceFunction { + return func( + _ *v1.Pod, + _ interface{}, + _ map[string]*schedulercache.NodeInfo, + result schedulerapi.HostPriorityList) error { + + var maxCount int + for i := range result { + if result[i].Score > maxCount { + maxCount = result[i].Score + } + } + + if maxCount == 0 { + if reverse { + for i := range result { + result[i].Score = maxPriority + } + } + return nil + } + + for i := range result { + score := result[i].Score + + score = maxPriority * score / maxCount + if reverse { + score = maxPriority - score + } + + result[i].Score = score + } + return nil + } +} diff --git a/pkg/scheduler/algorithm/priorities/resource_allocation.go b/pkg/scheduler/algorithm/priorities/resource_allocation.go new file mode 100644 index 00000000000..c938cedfddb --- /dev/null +++ b/pkg/scheduler/algorithm/priorities/resource_allocation.go @@ -0,0 +1,82 @@ +/* +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 priorities + +import ( + "fmt" + + "github.com/golang/glog" + "k8s.io/api/core/v1" + priorityutil "k8s.io/kubernetes/pkg/scheduler/algorithm/priorities/util" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" +) + +type ResourceAllocationPriority struct { + Name string + scorer func(requested, allocable *schedulercache.Resource) int64 +} + +func (r *ResourceAllocationPriority) PriorityMap( + pod *v1.Pod, + meta interface{}, + nodeInfo *schedulercache.NodeInfo) (schedulerapi.HostPriority, error) { + node := nodeInfo.Node() + if node == nil { + return schedulerapi.HostPriority{}, fmt.Errorf("node not found") + } + allocatable := nodeInfo.AllocatableResource() + + var requested schedulercache.Resource + if priorityMeta, ok := meta.(*priorityMetadata); ok { + requested = *priorityMeta.nonZeroRequest + } else { + // We couldn't parse metadatat - fallback to computing it. + requested = *getNonZeroRequests(pod) + } + + requested.MilliCPU += nodeInfo.NonZeroRequest().MilliCPU + requested.Memory += nodeInfo.NonZeroRequest().Memory + + score := r.scorer(&requested, &allocatable) + + if glog.V(10) { + glog.Infof( + "%v -> %v: %v, capacity %d millicores %d memory bytes, total request %d millicores %d memory bytes, score %d", + pod.Name, node.Name, r.Name, + allocatable.MilliCPU, allocatable.Memory, + requested.MilliCPU+allocatable.MilliCPU, requested.Memory+allocatable.Memory, + score, + ) + } + + return schedulerapi.HostPriority{ + Host: node.Name, + Score: int(score), + }, nil +} + +func getNonZeroRequests(pod *v1.Pod) *schedulercache.Resource { + result := &schedulercache.Resource{} + for i := range pod.Spec.Containers { + container := &pod.Spec.Containers[i] + cpu, memory := priorityutil.GetNonzeroRequests(&container.Resources.Requests) + result.MilliCPU += cpu + result.Memory += memory + } + return result +} diff --git a/pkg/scheduler/algorithm/priorities/resource_limits.go b/pkg/scheduler/algorithm/priorities/resource_limits.go new file mode 100644 index 00000000000..3267368d2f9 --- /dev/null +++ b/pkg/scheduler/algorithm/priorities/resource_limits.go @@ -0,0 +1,128 @@ +/* +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 priorities + +import ( + "fmt" + + "k8s.io/api/core/v1" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" + + "github.com/golang/glog" +) + +// ResourceLimitsPriorityMap is a priority function that increases score of input node by 1 if the node satisfies +// input pod's resource limits. In detail, this priority function works as follows: If a node does not publish its +// allocatable resources (cpu and memory both), the node score is not affected. If a pod does not specify +// its cpu and memory limits both, the node score is not affected. If one or both of cpu and memory limits +// of the pod are satisfied, the node is assigned a score of 1. +// Rationale of choosing the lowest score of 1 is that this is mainly selected to break ties between nodes that have +// same scores assigned by one of least and most requested priority functions. +func ResourceLimitsPriorityMap(pod *v1.Pod, meta interface{}, nodeInfo *schedulercache.NodeInfo) (schedulerapi.HostPriority, error) { + node := nodeInfo.Node() + if node == nil { + return schedulerapi.HostPriority{}, fmt.Errorf("node not found") + } + + allocatableResources := nodeInfo.AllocatableResource() + + // compute pod limits + podLimits := getResourceLimits(pod) + + cpuScore := computeScore(podLimits.MilliCPU, allocatableResources.MilliCPU) + memScore := computeScore(podLimits.Memory, allocatableResources.Memory) + + score := int(0) + if cpuScore == 1 || memScore == 1 { + score = 1 + } + + if glog.V(10) { + // We explicitly don't do glog.V(10).Infof() to avoid computing all the parameters if this is + // not logged. There is visible performance gain from it. + glog.Infof( + "%v -> %v: Resource Limits Priority, allocatable %d millicores %d memory bytes, pod limits %d millicores %d memory bytes, score %d", + pod.Name, node.Name, + allocatableResources.MilliCPU, allocatableResources.Memory, + podLimits.MilliCPU, podLimits.Memory, + score, + ) + } + + return schedulerapi.HostPriority{ + Host: node.Name, + Score: score, + }, nil +} + +// computeScore return 1 if limit value is less than or equal to allocable +// value, otherwise it returns 0. +func computeScore(limit, allocatable int64) int64 { + if limit != 0 && allocatable != 0 && limit <= allocatable { + return 1 + } + return 0 +} + +// getResourceLimits computes resource limits for input pod. +// The reason to create this new function is to be consistent with other +// priority functions because most or perhaps all priority functions work +// with schedulercache.Resource. +// TODO: cache it as part of metadata passed to priority functions. +func getResourceLimits(pod *v1.Pod) *schedulercache.Resource { + result := &schedulercache.Resource{} + for _, container := range pod.Spec.Containers { + result.Add(container.Resources.Limits) + } + + // take max_resource(sum_pod, any_init_container) + for _, container := range pod.Spec.InitContainers { + for rName, rQuantity := range container.Resources.Limits { + switch rName { + case v1.ResourceMemory: + if mem := rQuantity.Value(); mem > result.Memory { + result.Memory = mem + } + case v1.ResourceCPU: + if cpu := rQuantity.MilliValue(); cpu > result.MilliCPU { + result.MilliCPU = cpu + } + // keeping these resources though score computation in other priority functions and in this + // are only computed based on cpu and memory only. + case v1.ResourceEphemeralStorage: + if ephemeralStorage := rQuantity.Value(); ephemeralStorage > result.EphemeralStorage { + result.EphemeralStorage = ephemeralStorage + } + case v1.ResourceNvidiaGPU: + if gpu := rQuantity.Value(); gpu > result.NvidiaGPU { + result.NvidiaGPU = gpu + } + default: + if v1helper.IsScalarResourceName(rName) { + value := rQuantity.Value() + if value > result.ScalarResources[rName] { + result.SetScalar(rName, value) + } + } + } + } + } + + return result +} diff --git a/pkg/scheduler/algorithm/priorities/resource_limits_test.go b/pkg/scheduler/algorithm/priorities/resource_limits_test.go new file mode 100644 index 00000000000..e3056dcc6ba --- /dev/null +++ b/pkg/scheduler/algorithm/priorities/resource_limits_test.go @@ -0,0 +1,151 @@ +/* +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 priorities + +import ( + "reflect" + "testing" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + //metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" +) + +func TestResourceLimistPriority(t *testing.T) { + noResources := v1.PodSpec{ + Containers: []v1.Container{}, + } + + cpuOnly := v1.PodSpec{ + NodeName: "machine1", + Containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1000m"), + v1.ResourceMemory: resource.MustParse("0"), + }, + }, + }, + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2000m"), + v1.ResourceMemory: resource.MustParse("0"), + }, + }, + }, + }, + } + + memOnly := v1.PodSpec{ + NodeName: "machine2", + Containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("0"), + v1.ResourceMemory: resource.MustParse("2000"), + }, + }, + }, + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("0"), + v1.ResourceMemory: resource.MustParse("3000"), + }, + }, + }, + }, + } + + cpuAndMemory := v1.PodSpec{ + NodeName: "machine2", + Containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1000m"), + v1.ResourceMemory: resource.MustParse("2000"), + }, + }, + }, + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2000m"), + v1.ResourceMemory: resource.MustParse("3000"), + }, + }, + }, + }, + } + + tests := []struct { + // input pod + pod *v1.Pod + nodes []*v1.Node + expectedList schedulerapi.HostPriorityList + test string + }{ + { + pod: &v1.Pod{Spec: noResources}, + nodes: []*v1.Node{makeNode("machine1", 4000, 10000), makeNode("machine2", 4000, 0), makeNode("machine3", 0, 10000), makeNode("machine4", 0, 0)}, + expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 0}, {Host: "machine3", Score: 0}, {Host: "machine4", Score: 0}}, + test: "pod does not specify its resource limits", + }, + { + pod: &v1.Pod{Spec: cpuOnly}, + nodes: []*v1.Node{makeNode("machine1", 3000, 10000), makeNode("machine2", 2000, 10000)}, + expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 1}, {Host: "machine2", Score: 0}}, + test: "pod only specifies cpu limits", + }, + { + pod: &v1.Pod{Spec: memOnly}, + nodes: []*v1.Node{makeNode("machine1", 4000, 4000), makeNode("machine2", 5000, 10000)}, + expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 1}}, + test: "pod only specifies mem limits", + }, + { + pod: &v1.Pod{Spec: cpuAndMemory}, + nodes: []*v1.Node{makeNode("machine1", 4000, 4000), makeNode("machine2", 5000, 10000)}, + expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 1}, {Host: "machine2", Score: 1}}, + test: "pod specifies both cpu and mem limits", + }, + { + pod: &v1.Pod{Spec: cpuAndMemory}, + nodes: []*v1.Node{makeNode("machine1", 0, 0)}, + expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}}, + test: "node does not advertise its allocatables", + }, + } + + for _, test := range tests { + nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(nil, test.nodes) + list, err := priorityFunction(ResourceLimitsPriorityMap, nil, nil)(test.pod, nodeNameToInfo, test.nodes) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if !reflect.DeepEqual(test.expectedList, list) { + t.Errorf("%s: expected %#v, got %#v", test.test, test.expectedList, list) + } + } + +} diff --git a/pkg/scheduler/algorithm/priorities/selector_spreading.go b/pkg/scheduler/algorithm/priorities/selector_spreading.go new file mode 100644 index 00000000000..3b8eb609380 --- /dev/null +++ b/pkg/scheduler/algorithm/priorities/selector_spreading.go @@ -0,0 +1,279 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package priorities + +import ( + "fmt" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" + utilnode "k8s.io/kubernetes/pkg/util/node" + + "github.com/golang/glog" +) + +// When zone information is present, give 2/3 of the weighting to zone spreading, 1/3 to node spreading +// TODO: Any way to justify this weighting? +const zoneWeighting float64 = 2.0 / 3.0 + +type SelectorSpread struct { + serviceLister algorithm.ServiceLister + controllerLister algorithm.ControllerLister + replicaSetLister algorithm.ReplicaSetLister + statefulSetLister algorithm.StatefulSetLister +} + +func NewSelectorSpreadPriority( + serviceLister algorithm.ServiceLister, + controllerLister algorithm.ControllerLister, + replicaSetLister algorithm.ReplicaSetLister, + statefulSetLister algorithm.StatefulSetLister) (algorithm.PriorityMapFunction, algorithm.PriorityReduceFunction) { + selectorSpread := &SelectorSpread{ + serviceLister: serviceLister, + controllerLister: controllerLister, + replicaSetLister: replicaSetLister, + statefulSetLister: statefulSetLister, + } + return selectorSpread.CalculateSpreadPriorityMap, selectorSpread.CalculateSpreadPriorityReduce +} + +// CalculateSpreadPriorityMap spreads pods across hosts, considering pods +// belonging to the same service,RC,RS or StatefulSet. +// When a pod is scheduled, it looks for services, RCs,RSs and StatefulSets that match the pod, +// then finds existing pods that match those selectors. +// It favors nodes that have fewer existing matching pods. +// i.e. it pushes the scheduler towards a node where there's the smallest number of +// pods which match the same service, RC,RSs or StatefulSets selectors as the pod being scheduled. +func (s *SelectorSpread) CalculateSpreadPriorityMap(pod *v1.Pod, meta interface{}, nodeInfo *schedulercache.NodeInfo) (schedulerapi.HostPriority, error) { + var selectors []labels.Selector + node := nodeInfo.Node() + if node == nil { + return schedulerapi.HostPriority{}, fmt.Errorf("node not found") + } + + priorityMeta, ok := meta.(*priorityMetadata) + if ok { + selectors = priorityMeta.podSelectors + } else { + selectors = getSelectors(pod, s.serviceLister, s.controllerLister, s.replicaSetLister, s.statefulSetLister) + } + + if len(selectors) == 0 { + return schedulerapi.HostPriority{ + Host: node.Name, + Score: int(0), + }, nil + } + + count := int(0) + for _, nodePod := range nodeInfo.Pods() { + if pod.Namespace != nodePod.Namespace { + continue + } + // When we are replacing a failed pod, we often see the previous + // deleted version while scheduling the replacement. + // Ignore the previous deleted version for spreading purposes + // (it can still be considered for resource restrictions etc.) + if nodePod.DeletionTimestamp != nil { + glog.V(4).Infof("skipping pending-deleted pod: %s/%s", nodePod.Namespace, nodePod.Name) + continue + } + matches := false + for _, selector := range selectors { + if selector.Matches(labels.Set(nodePod.ObjectMeta.Labels)) { + matches = true + break + } + } + if matches { + count++ + } + } + return schedulerapi.HostPriority{ + Host: node.Name, + Score: int(count), + }, nil +} + +// CalculateSpreadPriorityReduce calculates the source of each node +// based on the number of existing matching pods on the node +// where zone information is included on the nodes, it favors nodes +// in zones with fewer existing matching pods. +func (s *SelectorSpread) CalculateSpreadPriorityReduce(pod *v1.Pod, meta interface{}, nodeNameToInfo map[string]*schedulercache.NodeInfo, result schedulerapi.HostPriorityList) error { + countsByZone := make(map[string]int, 10) + maxCountByZone := int(0) + maxCountByNodeName := int(0) + + for i := range result { + if result[i].Score > maxCountByNodeName { + maxCountByNodeName = result[i].Score + } + zoneId := utilnode.GetZoneKey(nodeNameToInfo[result[i].Host].Node()) + if zoneId == "" { + continue + } + countsByZone[zoneId] += result[i].Score + } + + for zoneId := range countsByZone { + if countsByZone[zoneId] > maxCountByZone { + maxCountByZone = countsByZone[zoneId] + } + } + + haveZones := len(countsByZone) != 0 + + maxCountByNodeNameFloat64 := float64(maxCountByNodeName) + maxCountByZoneFloat64 := float64(maxCountByZone) + MaxPriorityFloat64 := float64(schedulerapi.MaxPriority) + + for i := range result { + // initializing to the default/max node score of maxPriority + fScore := MaxPriorityFloat64 + if maxCountByNodeName > 0 { + fScore = MaxPriorityFloat64 * (float64(maxCountByNodeName-result[i].Score) / maxCountByNodeNameFloat64) + } + // If there is zone information present, incorporate it + if haveZones { + zoneId := utilnode.GetZoneKey(nodeNameToInfo[result[i].Host].Node()) + if zoneId != "" { + zoneScore := MaxPriorityFloat64 + if maxCountByZone > 0 { + zoneScore = MaxPriorityFloat64 * (float64(maxCountByZone-countsByZone[zoneId]) / maxCountByZoneFloat64) + } + fScore = (fScore * (1.0 - zoneWeighting)) + (zoneWeighting * zoneScore) + } + } + result[i].Score = int(fScore) + if glog.V(10) { + glog.Infof( + "%v -> %v: SelectorSpreadPriority, Score: (%d)", pod.Name, result[i].Host, int(fScore), + ) + } + } + return nil +} + +type ServiceAntiAffinity struct { + podLister algorithm.PodLister + serviceLister algorithm.ServiceLister + label string +} + +func NewServiceAntiAffinityPriority(podLister algorithm.PodLister, serviceLister algorithm.ServiceLister, label string) (algorithm.PriorityMapFunction, algorithm.PriorityReduceFunction) { + antiAffinity := &ServiceAntiAffinity{ + podLister: podLister, + serviceLister: serviceLister, + label: label, + } + return antiAffinity.CalculateAntiAffinityPriorityMap, antiAffinity.CalculateAntiAffinityPriorityReduce +} + +// Classifies nodes into ones with labels and without labels. +func (s *ServiceAntiAffinity) getNodeClassificationByLabels(nodes []*v1.Node) (map[string]string, []string) { + labeledNodes := map[string]string{} + nonLabeledNodes := []string{} + for _, node := range nodes { + if labels.Set(node.Labels).Has(s.label) { + label := labels.Set(node.Labels).Get(s.label) + labeledNodes[node.Name] = label + } else { + nonLabeledNodes = append(nonLabeledNodes, node.Name) + } + } + return labeledNodes, nonLabeledNodes +} + +// filteredPod get pods based on namespace and selector +func filteredPod(namespace string, selector labels.Selector, nodeInfo *schedulercache.NodeInfo) (pods []*v1.Pod) { + if nodeInfo.Pods() == nil || len(nodeInfo.Pods()) == 0 || selector == nil { + return []*v1.Pod{} + } + for _, pod := range nodeInfo.Pods() { + if namespace == pod.Namespace && selector.Matches(labels.Set(pod.Labels)) { + pods = append(pods, pod) + } + } + return +} + +// CalculateAntiAffinityPriorityMap spreads pods by minimizing the number of pods belonging to the same service +// on given machine +func (s *ServiceAntiAffinity) CalculateAntiAffinityPriorityMap(pod *v1.Pod, meta interface{}, nodeInfo *schedulercache.NodeInfo) (schedulerapi.HostPriority, error) { + var firstServiceSelector labels.Selector + + node := nodeInfo.Node() + if node == nil { + return schedulerapi.HostPriority{}, fmt.Errorf("node not found") + } + priorityMeta, ok := meta.(*priorityMetadata) + if ok { + firstServiceSelector = priorityMeta.podFirstServiceSelector + } else { + firstServiceSelector = getFirstServiceSelector(pod, s.serviceLister) + } + //pods matched namespace,selector on current node + matchedPodsOfNode := filteredPod(pod.Namespace, firstServiceSelector, nodeInfo) + + return schedulerapi.HostPriority{ + Host: node.Name, + Score: int(len(matchedPodsOfNode)), + }, nil +} + +// CalculateAntiAffinityPriorityReduce computes each node score with the same value for a particular label. +// The label to be considered is provided to the struct (ServiceAntiAffinity). +func (s *ServiceAntiAffinity) CalculateAntiAffinityPriorityReduce(pod *v1.Pod, meta interface{}, nodeNameToInfo map[string]*schedulercache.NodeInfo, result schedulerapi.HostPriorityList) error { + var numServicePods int + var label string + podCounts := map[string]int{} + labelNodesStatus := map[string]string{} + maxPriorityFloat64 := float64(schedulerapi.MaxPriority) + + for _, hostPriority := range result { + numServicePods += hostPriority.Score + if !labels.Set(nodeNameToInfo[hostPriority.Host].Node().Labels).Has(s.label) { + continue + } + label = labels.Set(nodeNameToInfo[hostPriority.Host].Node().Labels).Get(s.label) + labelNodesStatus[hostPriority.Host] = label + podCounts[label] += hostPriority.Score + } + + //score int - scale of 0-maxPriority + // 0 being the lowest priority and maxPriority being the highest + for i, hostPriority := range result { + label, ok := labelNodesStatus[hostPriority.Host] + if !ok { + result[i].Host = hostPriority.Host + result[i].Score = int(0) + continue + } + // initializing to the default/max node score of maxPriority + fScore := maxPriorityFloat64 + if numServicePods > 0 { + fScore = maxPriorityFloat64 * (float64(numServicePods-podCounts[label]) / float64(numServicePods)) + } + result[i].Host = hostPriority.Host + result[i].Score = int(fScore) + } + + return nil +} diff --git a/plugin/pkg/scheduler/algorithm/priorities/selector_spreading_test.go b/pkg/scheduler/algorithm/priorities/selector_spreading_test.go similarity index 91% rename from plugin/pkg/scheduler/algorithm/priorities/selector_spreading_test.go rename to pkg/scheduler/algorithm/priorities/selector_spreading_test.go index 9cef5403a54..e6eff8cc275 100644 --- a/plugin/pkg/scheduler/algorithm/priorities/selector_spreading_test.go +++ b/pkg/scheduler/algorithm/priorities/selector_spreading_test.go @@ -26,9 +26,9 @@ import ( extensions "k8s.io/api/extensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" - schedulertesting "k8s.io/kubernetes/plugin/pkg/scheduler/testing" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" + schedulertesting "k8s.io/kubernetes/pkg/scheduler/testing" ) func controllerRef(kind, name, uid string) []metav1.OwnerReference { @@ -338,17 +338,26 @@ func TestSelectorSpreadPriority(t *testing.T) { }, } - for _, test := range tests { - nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods, nil) + for i, test := range tests { + nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods, makeNodeList(test.nodes)) selectorSpread := SelectorSpread{ serviceLister: schedulertesting.FakeServiceLister(test.services), controllerLister: schedulertesting.FakeControllerLister(test.rcs), replicaSetLister: schedulertesting.FakeReplicaSetLister(test.rss), statefulSetLister: schedulertesting.FakeStatefulSetLister(test.sss), } - list, err := selectorSpread.CalculateSpreadPriority(test.pod, nodeNameToInfo, makeNodeList(test.nodes)) + + mataDataProducer := NewPriorityMetadataFactory( + schedulertesting.FakeServiceLister(test.services), + schedulertesting.FakeControllerLister(test.rcs), + schedulertesting.FakeReplicaSetLister(test.rss), + schedulertesting.FakeStatefulSetLister(test.sss)) + mataData := mataDataProducer(test.pod, nodeNameToInfo) + + ttp := priorityFunction(selectorSpread.CalculateSpreadPriorityMap, selectorSpread.CalculateSpreadPriorityReduce, mataData) + list, err := ttp(test.pod, nodeNameToInfo, makeNodeList(test.nodes)) if err != nil { - t.Errorf("unexpected error: %v", err) + t.Errorf("unexpected error: %v index : %d\n", err, i) } if !reflect.DeepEqual(test.expectedList, list) { t.Errorf("%s: expected %#v, got %#v", test.test, test.expectedList, list) @@ -445,6 +454,23 @@ func TestZoneSelectorSpreadPriority(t *testing.T) { }, test: "different services", }, + { + pod: buildPod("", labels1, nil), + pods: []*v1.Pod{ + buildPod(nodeMachine1Zone1, labels2, nil), + buildPod(nodeMachine1Zone2, labels2, nil), + }, + services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: labels1}}}, + expectedList: []schedulerapi.HostPriority{ + {Host: nodeMachine1Zone1, Score: schedulerapi.MaxPriority}, + {Host: nodeMachine1Zone2, Score: schedulerapi.MaxPriority}, + {Host: nodeMachine2Zone2, Score: schedulerapi.MaxPriority}, + {Host: nodeMachine1Zone3, Score: schedulerapi.MaxPriority}, + {Host: nodeMachine2Zone3, Score: schedulerapi.MaxPriority}, + {Host: nodeMachine3Zone3, Score: schedulerapi.MaxPriority}, + }, + test: "two pods, 0 matching", + }, { pod: buildPod("", labels1, nil), pods: []*v1.Pod{ @@ -547,17 +573,25 @@ func TestZoneSelectorSpreadPriority(t *testing.T) { }, } - for _, test := range tests { - nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods, nil) + for i, test := range tests { + nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods, makeLabeledNodeList(labeledNodes)) selectorSpread := SelectorSpread{ serviceLister: schedulertesting.FakeServiceLister(test.services), controllerLister: schedulertesting.FakeControllerLister(test.rcs), replicaSetLister: schedulertesting.FakeReplicaSetLister(test.rss), statefulSetLister: schedulertesting.FakeStatefulSetLister(test.sss), } - list, err := selectorSpread.CalculateSpreadPriority(test.pod, nodeNameToInfo, makeLabeledNodeList(labeledNodes)) + + mataDataProducer := NewPriorityMetadataFactory( + schedulertesting.FakeServiceLister(test.services), + schedulertesting.FakeControllerLister(test.rcs), + schedulertesting.FakeReplicaSetLister(test.rss), + schedulertesting.FakeStatefulSetLister(test.sss)) + mataData := mataDataProducer(test.pod, nodeNameToInfo) + ttp := priorityFunction(selectorSpread.CalculateSpreadPriorityMap, selectorSpread.CalculateSpreadPriorityReduce, mataData) + list, err := ttp(test.pod, nodeNameToInfo, makeLabeledNodeList(labeledNodes)) if err != nil { - t.Errorf("unexpected error: %v", err) + t.Errorf("unexpected error: %v index : %d", err, i) } // sort the two lists to avoid failures on account of different ordering sort.Sort(test.expectedList) @@ -723,19 +757,33 @@ func TestZoneSpreadPriority(t *testing.T) { test: "service pod on non-zoned node", }, } + // these local variables just make sure controllerLister\replicaSetLister\statefulSetLister not nil + // when construct mataDataProducer + sss := []*apps.StatefulSet{{Spec: apps.StatefulSetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}}}} + rcs := []*v1.ReplicationController{{Spec: v1.ReplicationControllerSpec{Selector: map[string]string{"foo": "bar"}}}} + rss := []*extensions.ReplicaSet{{Spec: extensions.ReplicaSetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}}}} - for _, test := range tests { - nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods, nil) + for i, test := range tests { + nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods, makeLabeledNodeList(test.nodes)) zoneSpread := ServiceAntiAffinity{podLister: schedulertesting.FakePodLister(test.pods), serviceLister: schedulertesting.FakeServiceLister(test.services), label: "zone"} - list, err := zoneSpread.CalculateAntiAffinityPriority(test.pod, nodeNameToInfo, makeLabeledNodeList(test.nodes)) + + mataDataProducer := NewPriorityMetadataFactory( + schedulertesting.FakeServiceLister(test.services), + schedulertesting.FakeControllerLister(rcs), + schedulertesting.FakeReplicaSetLister(rss), + schedulertesting.FakeStatefulSetLister(sss)) + mataData := mataDataProducer(test.pod, nodeNameToInfo) + ttp := priorityFunction(zoneSpread.CalculateAntiAffinityPriorityMap, zoneSpread.CalculateAntiAffinityPriorityReduce, mataData) + list, err := ttp(test.pod, nodeNameToInfo, makeLabeledNodeList(test.nodes)) if err != nil { - t.Errorf("unexpected error: %v", err) + t.Errorf("unexpected error: %v index : %d", err, i) } + // sort the two lists to avoid failures on account of different ordering sort.Sort(test.expectedList) sort.Sort(list) if !reflect.DeepEqual(test.expectedList, list) { - t.Errorf("%s: expected %#v, got %#v", test.test, test.expectedList, list) + t.Errorf("test index %d (%s): expected %#v, got %#v", i, test.test, test.expectedList, list) } } } diff --git a/pkg/scheduler/algorithm/priorities/taint_toleration.go b/pkg/scheduler/algorithm/priorities/taint_toleration.go new file mode 100644 index 00000000000..c6847c0ea1f --- /dev/null +++ b/pkg/scheduler/algorithm/priorities/taint_toleration.go @@ -0,0 +1,76 @@ +/* +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 priorities + +import ( + "fmt" + + "k8s.io/api/core/v1" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" +) + +// CountIntolerableTaintsPreferNoSchedule gives the count of intolerable taints of a pod with effect PreferNoSchedule +func countIntolerableTaintsPreferNoSchedule(taints []v1.Taint, tolerations []v1.Toleration) (intolerableTaints int) { + for _, taint := range taints { + // check only on taints that have effect PreferNoSchedule + if taint.Effect != v1.TaintEffectPreferNoSchedule { + continue + } + + if !v1helper.TolerationsTolerateTaint(tolerations, &taint) { + intolerableTaints++ + } + } + return +} + +// getAllTolerationEffectPreferNoSchedule gets the list of all Tolerations with Effect PreferNoSchedule or with no effect. +func getAllTolerationPreferNoSchedule(tolerations []v1.Toleration) (tolerationList []v1.Toleration) { + for _, toleration := range tolerations { + // Empty effect means all effects which includes PreferNoSchedule, so we need to collect it as well. + if len(toleration.Effect) == 0 || toleration.Effect == v1.TaintEffectPreferNoSchedule { + tolerationList = append(tolerationList, toleration) + } + } + return +} + +// ComputeTaintTolerationPriorityMap prepares the priority list for all the nodes based on the number of intolerable taints on the node +func ComputeTaintTolerationPriorityMap(pod *v1.Pod, meta interface{}, nodeInfo *schedulercache.NodeInfo) (schedulerapi.HostPriority, error) { + node := nodeInfo.Node() + if node == nil { + return schedulerapi.HostPriority{}, fmt.Errorf("node not found") + } + // To hold all the tolerations with Effect PreferNoSchedule + var tolerationsPreferNoSchedule []v1.Toleration + if priorityMeta, ok := meta.(*priorityMetadata); ok { + tolerationsPreferNoSchedule = priorityMeta.podTolerations + + } else { + tolerationsPreferNoSchedule = getAllTolerationPreferNoSchedule(pod.Spec.Tolerations) + } + + return schedulerapi.HostPriority{ + Host: node.Name, + Score: countIntolerableTaintsPreferNoSchedule(node.Spec.Taints, tolerationsPreferNoSchedule), + }, nil +} + +// ComputeTaintTolerationPriorityReduce calculates the source of each node based on the number of intolerable taints on the node +var ComputeTaintTolerationPriorityReduce = NormalizeReduce(schedulerapi.MaxPriority, true) diff --git a/plugin/pkg/scheduler/algorithm/priorities/taint_toleration_test.go b/pkg/scheduler/algorithm/priorities/taint_toleration_test.go similarity index 97% rename from plugin/pkg/scheduler/algorithm/priorities/taint_toleration_test.go rename to pkg/scheduler/algorithm/priorities/taint_toleration_test.go index 50e0b4d36f9..3ef61dd987e 100644 --- a/plugin/pkg/scheduler/algorithm/priorities/taint_toleration_test.go +++ b/pkg/scheduler/algorithm/priorities/taint_toleration_test.go @@ -22,8 +22,8 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" ) func nodeWithTaints(nodeName string, taints []v1.Taint) *v1.Node { @@ -227,7 +227,7 @@ func TestTaintAndToleration(t *testing.T) { } for _, test := range tests { nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(nil, test.nodes) - ttp := priorityFunction(ComputeTaintTolerationPriorityMap, ComputeTaintTolerationPriorityReduce) + ttp := priorityFunction(ComputeTaintTolerationPriorityMap, ComputeTaintTolerationPriorityReduce, nil) list, err := ttp(test.pod, nodeNameToInfo, test.nodes) if err != nil { t.Errorf("%s, unexpected error: %v", test.test, err) diff --git a/plugin/pkg/scheduler/algorithm/priorities/test_util.go b/pkg/scheduler/algorithm/priorities/test_util.go similarity index 81% rename from plugin/pkg/scheduler/algorithm/priorities/test_util.go rename to pkg/scheduler/algorithm/priorities/test_util.go index 9eb26f2d93c..d1756c0383c 100644 --- a/plugin/pkg/scheduler/algorithm/priorities/test_util.go +++ b/pkg/scheduler/algorithm/priorities/test_util.go @@ -20,9 +20,9 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" ) func makeNode(node string, milliCPU, memory int64) *v1.Node { @@ -41,18 +41,18 @@ func makeNode(node string, milliCPU, memory int64) *v1.Node { } } -func priorityFunction(mapFn algorithm.PriorityMapFunction, reduceFn algorithm.PriorityReduceFunction) algorithm.PriorityFunction { +func priorityFunction(mapFn algorithm.PriorityMapFunction, reduceFn algorithm.PriorityReduceFunction, mataData interface{}) algorithm.PriorityFunction { return func(pod *v1.Pod, nodeNameToInfo map[string]*schedulercache.NodeInfo, nodes []*v1.Node) (schedulerapi.HostPriorityList, error) { result := make(schedulerapi.HostPriorityList, 0, len(nodes)) for i := range nodes { - hostResult, err := mapFn(pod, nil, nodeNameToInfo[nodes[i].Name]) + hostResult, err := mapFn(pod, mataData, nodeNameToInfo[nodes[i].Name]) if err != nil { return nil, err } result = append(result, hostResult) } if reduceFn != nil { - if err := reduceFn(pod, nil, nodeNameToInfo, result); err != nil { + if err := reduceFn(pod, mataData, nodeNameToInfo, result); err != nil { return nil, err } } diff --git a/pkg/scheduler/algorithm/priorities/util/BUILD b/pkg/scheduler/algorithm/priorities/util/BUILD new file mode 100644 index 00000000000..d58c0e65b21 --- /dev/null +++ b/pkg/scheduler/algorithm/priorities/util/BUILD @@ -0,0 +1,56 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_test( + name = "go_default_test", + srcs = [ + "non_zero_test.go", + "topologies_test.go", + "util_test.go", + ], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/scheduler/algorithm/priorities/util", + deps = [ + "//vendor/github.com/stretchr/testify/assert:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/selection:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + ], +) + +go_library( + name = "go_default_library", + srcs = [ + "non_zero.go", + "topologies.go", + "util.go", + ], + importpath = "k8s.io/kubernetes/pkg/scheduler/algorithm/priorities/util", + deps = [ + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/plugin/pkg/scheduler/algorithm/priorities/util/non_zero.go b/pkg/scheduler/algorithm/priorities/util/non_zero.go similarity index 100% rename from plugin/pkg/scheduler/algorithm/priorities/util/non_zero.go rename to pkg/scheduler/algorithm/priorities/util/non_zero.go diff --git a/pkg/scheduler/algorithm/priorities/util/non_zero_test.go b/pkg/scheduler/algorithm/priorities/util/non_zero_test.go new file mode 100644 index 00000000000..d21c28054c4 --- /dev/null +++ b/pkg/scheduler/algorithm/priorities/util/non_zero_test.go @@ -0,0 +1,73 @@ +/* +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 util + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" +) + +func TestGetNonzeroRequests(t *testing.T) { + tds := []struct { + name string + requests v1.ResourceList + expectedCPU int64 + expectedMemory int64 + }{ + { + "cpu_and_memory_not_found", + v1.ResourceList{}, + DefaultMilliCpuRequest, + DefaultMemoryRequest, + }, + { + "only_cpu_exist", + v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), + }, + 200, + DefaultMemoryRequest, + }, + { + "only_memory_exist", + v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("400Mi"), + }, + DefaultMilliCpuRequest, + 400 * 1024 * 1024, + }, + { + "cpu_memory_exist", + v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200m"), + v1.ResourceMemory: resource.MustParse("400Mi"), + }, + 200, + 400 * 1024 * 1024, + }, + } + + for _, td := range tds { + realCPU, realMemory := GetNonzeroRequests(&td.requests) + assert.EqualValuesf(t, td.expectedCPU, realCPU, "Failed to test: %s", td.name) + assert.EqualValuesf(t, td.expectedMemory, realMemory, "Failed to test: %s", td.name) + } +} diff --git a/plugin/pkg/scheduler/algorithm/priorities/util/topologies.go b/pkg/scheduler/algorithm/priorities/util/topologies.go similarity index 100% rename from plugin/pkg/scheduler/algorithm/priorities/util/topologies.go rename to pkg/scheduler/algorithm/priorities/util/topologies.go diff --git a/pkg/scheduler/algorithm/priorities/util/topologies_test.go b/pkg/scheduler/algorithm/priorities/util/topologies_test.go new file mode 100644 index 00000000000..25e24299f87 --- /dev/null +++ b/pkg/scheduler/algorithm/priorities/util/topologies_test.go @@ -0,0 +1,254 @@ +/* +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 util + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/util/sets" +) + +func fakePod() *v1.Pod { + return &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "topologies_pod", + Namespace: metav1.NamespaceDefault, + UID: "551f5a43-9f2f-11e7-a589-fa163e148d75", + }, + } +} + +func TestGetNamespacesFromPodAffinityTerm(t *testing.T) { + tests := []struct { + name string + podAffinityTerm *v1.PodAffinityTerm + expectedValue sets.String + }{ + { + "podAffinityTerm_namespace_empty", + &v1.PodAffinityTerm{}, + sets.String{metav1.NamespaceDefault: sets.Empty{}}, + }, + { + "podAffinityTerm_namespace_not_empty", + &v1.PodAffinityTerm{ + Namespaces: []string{metav1.NamespacePublic, metav1.NamespaceSystem}, + }, + sets.String{metav1.NamespacePublic: sets.Empty{}, metav1.NamespaceSystem: sets.Empty{}}, + }, + } + + for _, test := range tests { + realValue := GetNamespacesFromPodAffinityTerm(fakePod(), test.podAffinityTerm) + assert.EqualValuesf(t, test.expectedValue, realValue, "Failed to test: %s", test.name) + } +} + +func TestPodMatchesTermsNamespaceAndSelector(t *testing.T) { + fakeNamespaces := sets.String{metav1.NamespacePublic: sets.Empty{}, metav1.NamespaceSystem: sets.Empty{}} + fakeRequirement, _ := labels.NewRequirement("service", selection.In, []string{"topologies_service1", "topologies_service2"}) + fakeSelector := labels.NewSelector().Add(*fakeRequirement) + + tests := []struct { + name string + podNamespaces string + podLabels map[string]string + expectedResult bool + }{ + { + "namespace_not_in", + metav1.NamespaceDefault, + map[string]string{"service": "topologies_service1"}, + false, + }, + { + "label_not_match", + metav1.NamespacePublic, + map[string]string{"service": "topologies_service3"}, + false, + }, + { + "normal_case", + metav1.NamespacePublic, + map[string]string{"service": "topologies_service1"}, + true, + }, + } + + for _, test := range tests { + fakeTestPod := fakePod() + fakeTestPod.Namespace = test.podNamespaces + fakeTestPod.Labels = test.podLabels + + realValue := PodMatchesTermsNamespaceAndSelector(fakeTestPod, fakeNamespaces, fakeSelector) + assert.EqualValuesf(t, test.expectedResult, realValue, "Faild to test: %s", test.name) + } + +} + +func TestNodesHaveSameTopologyKey(t *testing.T) { + tests := []struct { + name string + nodeA, nodeB *v1.Node + topologyKey string + expected bool + }{ + { + name: "nodeA{'a':'a'} vs. empty label in nodeB", + nodeA: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "a": "a", + }, + }, + }, + nodeB: &v1.Node{}, + expected: false, + topologyKey: "a", + }, + { + name: "nodeA{'a':'a'} vs. nodeB{'a':'a'}", + nodeA: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "a": "a", + }, + }, + }, + nodeB: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "a": "a", + }, + }, + }, + expected: true, + topologyKey: "a", + }, + { + name: "nodeA{'a':''} vs. empty label in nodeB", + nodeA: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "a": "", + }, + }, + }, + nodeB: &v1.Node{}, + expected: false, + topologyKey: "a", + }, + { + name: "nodeA{'a':''} vs. nodeB{'a':''}", + nodeA: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "a": "", + }, + }, + }, + nodeB: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "a": "", + }, + }, + }, + expected: true, + topologyKey: "a", + }, + { + name: "nodeA{'a':'a'} vs. nodeB{'a':'a'} by key{'b'}", + nodeA: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "a": "a", + }, + }, + }, + nodeB: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "a": "a", + }, + }, + }, + expected: false, + topologyKey: "b", + }, + { + name: "topologyKey empty", + nodeA: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "a": "", + }, + }, + }, + nodeB: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "a": "", + }, + }, + }, + expected: false, + topologyKey: "", + }, + { + name: "nodeA lable nil vs. nodeB{'a':''} by key('a')", + nodeA: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{}, + }, + nodeB: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "a": "", + }, + }, + }, + expected: false, + topologyKey: "a", + }, + { + name: "nodeA{'a':''} vs. nodeB label is nil by key('a')", + nodeA: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "a": "", + }, + }, + }, + nodeB: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{}, + }, + expected: false, + topologyKey: "a", + }, + } + + for _, test := range tests { + got := NodesHaveSameTopologyKey(test.nodeA, test.nodeB, test.topologyKey) + assert.Equalf(t, test.expected, got, "Failed to test: %s", test.name) + } +} diff --git a/plugin/pkg/scheduler/algorithm/priorities/util/util.go b/pkg/scheduler/algorithm/priorities/util/util.go similarity index 100% rename from plugin/pkg/scheduler/algorithm/priorities/util/util.go rename to pkg/scheduler/algorithm/priorities/util/util.go diff --git a/pkg/scheduler/algorithm/priorities/util/util_test.go b/pkg/scheduler/algorithm/priorities/util/util_test.go new file mode 100644 index 00000000000..fc79ebbfc4f --- /dev/null +++ b/pkg/scheduler/algorithm/priorities/util/util_test.go @@ -0,0 +1,119 @@ +/* +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 util + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestGetControllerRef(t *testing.T) { + fakeBlockOwnerDeletion := true + fakeFalseController := false + fakeTrueController := true + fakeEmptyOwnerReference := metav1.OwnerReference{} + + tds := []struct { + name string + pod v1.Pod + expectedNil bool + expectedOR metav1.OwnerReference + }{ + { + "ownerreference_not_exist", + v1.Pod{}, + true, + fakeEmptyOwnerReference, + }, + { + "ownerreference_controller_is_nil", + v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "extensions/v1beta1", + Kind: "ReplicaSet", + Name: "or-unit-test-5b9cffccff", + UID: "a46372ea-b254-11e7-8373-fa163e25bfb5", + BlockOwnerDeletion: &fakeBlockOwnerDeletion, + }, + }, + }, + }, + true, + fakeEmptyOwnerReference, + }, + { + "ownerreference_controller_is_false", + v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "extensions/v1beta1", + Kind: "ReplicaSet", + Name: "or-unit-test-5b9cffccff", + UID: "a46372ea-b254-11e7-8373-fa163e25bfb5", + Controller: &fakeFalseController, + BlockOwnerDeletion: &fakeBlockOwnerDeletion, + }, + }, + }, + }, + true, + fakeEmptyOwnerReference, + }, + { + "ownerreference_controller_is_true", + v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "extensions/v1beta1", + Kind: "ReplicaSet", + Name: "or-unit-test-5b9cffccff", + UID: "a46372ea-b254-11e7-8373-fa163e25bfb5", + BlockOwnerDeletion: &fakeBlockOwnerDeletion, + Controller: &fakeTrueController, + }, + }, + }, + }, + false, + metav1.OwnerReference{ + APIVersion: "extensions/v1beta1", + Kind: "ReplicaSet", + Name: "or-unit-test-5b9cffccff", + UID: "a46372ea-b254-11e7-8373-fa163e25bfb5", + BlockOwnerDeletion: &fakeBlockOwnerDeletion, + Controller: &fakeTrueController, + }, + }, + } + + for _, td := range tds { + realOR := GetControllerRef(&td.pod) + if td.expectedNil { + assert.Nilf(t, realOR, "Failed to test: %s", td.name) + } else { + assert.Equalf(t, &td.expectedOR, realOR, "Failed to test: %s", td.name) + } + } +} diff --git a/plugin/pkg/scheduler/algorithm/scheduler_interface.go b/pkg/scheduler/algorithm/scheduler_interface.go similarity index 90% rename from plugin/pkg/scheduler/algorithm/scheduler_interface.go rename to pkg/scheduler/algorithm/scheduler_interface.go index 41a6dbb48bb..d64e8842601 100644 --- a/plugin/pkg/scheduler/algorithm/scheduler_interface.go +++ b/pkg/scheduler/algorithm/scheduler_interface.go @@ -18,8 +18,8 @@ package algorithm import ( "k8s.io/api/core/v1" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" ) // SchedulerExtender is an interface for external processes to influence scheduling @@ -49,8 +49,9 @@ type ScheduleAlgorithm interface { Schedule(*v1.Pod, NodeLister) (selectedMachine string, err error) // Preempt receives scheduling errors for a pod and tries to create room for // the pod by preempting lower priority pods if possible. - // It returns the node where preemption happened, a list of preempted pods, and error if any. - Preempt(*v1.Pod, NodeLister, error) (selectedNode *v1.Node, preemptedPods []*v1.Pod, err error) + // It returns the node where preemption happened, a list of preempted pods, a + // list of pods whose nominated node name should be removed, and error if any. + Preempt(*v1.Pod, NodeLister, error) (selectedNode *v1.Node, preemptedPods []*v1.Pod, cleanupNominatedPods []*v1.Pod, err error) // Predicates() returns a pointer to a map of predicate functions. This is // exposed for testing. Predicates() map[string]FitPredicate diff --git a/plugin/pkg/scheduler/algorithm/scheduler_interface_test.go b/pkg/scheduler/algorithm/scheduler_interface_test.go similarity index 100% rename from plugin/pkg/scheduler/algorithm/scheduler_interface_test.go rename to pkg/scheduler/algorithm/scheduler_interface_test.go diff --git a/pkg/scheduler/algorithm/types.go b/pkg/scheduler/algorithm/types.go new file mode 100644 index 00000000000..5fb2981f110 --- /dev/null +++ b/pkg/scheduler/algorithm/types.go @@ -0,0 +1,165 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package algorithm + +import ( + apps "k8s.io/api/apps/v1beta1" + "k8s.io/api/core/v1" + extensions "k8s.io/api/extensions/v1beta1" + "k8s.io/apimachinery/pkg/labels" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" +) + +// FitPredicate is a function that indicates if a pod fits into an existing node. +// The failure information is given by the error. +type FitPredicate func(pod *v1.Pod, meta PredicateMetadata, nodeInfo *schedulercache.NodeInfo) (bool, []PredicateFailureReason, error) + +// PriorityMapFunction is a function that computes per-node results for a given node. +// TODO: Figure out the exact API of this method. +// TODO: Change interface{} to a specific type. +type PriorityMapFunction func(pod *v1.Pod, meta interface{}, nodeInfo *schedulercache.NodeInfo) (schedulerapi.HostPriority, error) + +// PriorityReduceFunction is a function that aggregated per-node results and computes +// final scores for all nodes. +// TODO: Figure out the exact API of this method. +// TODO: Change interface{} to a specific type. +type PriorityReduceFunction func(pod *v1.Pod, meta interface{}, nodeNameToInfo map[string]*schedulercache.NodeInfo, result schedulerapi.HostPriorityList) error + +// PredicateMetadataProducer is a function that computes predicate metadata for a given pod. +type PredicateMetadataProducer func(pod *v1.Pod, nodeNameToInfo map[string]*schedulercache.NodeInfo) PredicateMetadata + +// MetadataProducer is a function that computes metadata for a given pod. This +// is now used for only for priority functions. For predicates please use PredicateMetadataProducer. +// TODO: Rename this once we have a specific type for priority metadata producer. +type MetadataProducer func(pod *v1.Pod, nodeNameToInfo map[string]*schedulercache.NodeInfo) interface{} + +// DEPRECATED +// Use Map-Reduce pattern for priority functions. +type PriorityFunction func(pod *v1.Pod, nodeNameToInfo map[string]*schedulercache.NodeInfo, nodes []*v1.Node) (schedulerapi.HostPriorityList, error) + +type PriorityConfig struct { + Name string + Map PriorityMapFunction + Reduce PriorityReduceFunction + // TODO: Remove it after migrating all functions to + // Map-Reduce pattern. + Function PriorityFunction + Weight int +} + +// EmptyPredicateMetadataProducer returns a no-op MetadataProducer type. +func EmptyPredicateMetadataProducer(pod *v1.Pod, nodeNameToInfo map[string]*schedulercache.NodeInfo) PredicateMetadata { + return nil +} + +// EmptyMetadataProducer returns a no-op MetadataProducer type. +func EmptyMetadataProducer(pod *v1.Pod, nodeNameToInfo map[string]*schedulercache.NodeInfo) interface{} { + return nil +} + +type PredicateFailureReason interface { + GetReason() string +} + +type GetEquivalencePodFunc func(pod *v1.Pod) interface{} + +// NodeLister interface represents anything that can list nodes for a scheduler. +type NodeLister interface { + // We explicitly return []*v1.Node, instead of v1.NodeList, to avoid + // performing expensive copies that are unneeded. + List() ([]*v1.Node, error) +} + +// PodLister interface represents anything that can list pods for a scheduler. +type PodLister interface { + // We explicitly return []*v1.Pod, instead of v1.PodList, to avoid + // performing expensive copies that are unneeded. + List(labels.Selector) ([]*v1.Pod, error) + // This is similar to "List()", but the returned slice does not + // contain pods that don't pass `podFilter`. + FilteredList(podFilter schedulercache.PodFilter, selector labels.Selector) ([]*v1.Pod, error) +} + +// ServiceLister interface represents anything that can produce a list of services; the list is consumed by a scheduler. +type ServiceLister interface { + // Lists all the services + List(labels.Selector) ([]*v1.Service, error) + // Gets the services for the given pod + GetPodServices(*v1.Pod) ([]*v1.Service, error) +} + +// ControllerLister interface represents anything that can produce a list of ReplicationController; the list is consumed by a scheduler. +type ControllerLister interface { + // Lists all the replication controllers + List(labels.Selector) ([]*v1.ReplicationController, error) + // Gets the services for the given pod + GetPodControllers(*v1.Pod) ([]*v1.ReplicationController, error) +} + +// ReplicaSetLister interface represents anything that can produce a list of ReplicaSet; the list is consumed by a scheduler. +type ReplicaSetLister interface { + // Gets the replicasets for the given pod + GetPodReplicaSets(*v1.Pod) ([]*extensions.ReplicaSet, error) +} + +var _ ControllerLister = &EmptyControllerLister{} + +// EmptyControllerLister implements ControllerLister on []v1.ReplicationController returning empty data +type EmptyControllerLister struct{} + +// List returns nil +func (f EmptyControllerLister) List(labels.Selector) ([]*v1.ReplicationController, error) { + return nil, nil +} + +// GetPodControllers returns nil +func (f EmptyControllerLister) GetPodControllers(pod *v1.Pod) (controllers []*v1.ReplicationController, err error) { + return nil, nil +} + +var _ ReplicaSetLister = &EmptyReplicaSetLister{} + +// EmptyReplicaSetLister implements ReplicaSetLister on []extensions.ReplicaSet returning empty data +type EmptyReplicaSetLister struct{} + +// GetPodReplicaSets returns nil +func (f EmptyReplicaSetLister) GetPodReplicaSets(pod *v1.Pod) (rss []*extensions.ReplicaSet, err error) { + return nil, nil +} + +// StatefulSetLister interface represents anything that can produce a list of StatefulSet; the list is consumed by a scheduler. +type StatefulSetLister interface { + // Gets the StatefulSet for the given pod. + GetPodStatefulSets(*v1.Pod) ([]*apps.StatefulSet, error) +} + +var _ StatefulSetLister = &EmptyStatefulSetLister{} + +// EmptyStatefulSetLister implements StatefulSetLister on []apps.StatefulSet returning empty data. +type EmptyStatefulSetLister struct{} + +// GetPodStatefulSets of EmptyStatefulSetLister returns nil. +func (f EmptyStatefulSetLister) GetPodStatefulSets(pod *v1.Pod) (sss []*apps.StatefulSet, err error) { + return nil, nil +} + +type PredicateMetadata interface { + ShallowCopy() PredicateMetadata + AddPod(addedPod *v1.Pod, nodeInfo *schedulercache.NodeInfo) error + RemovePod(deletedPod *v1.Pod) error +} diff --git a/plugin/pkg/scheduler/algorithm/types_test.go b/pkg/scheduler/algorithm/types_test.go similarity index 97% rename from plugin/pkg/scheduler/algorithm/types_test.go rename to pkg/scheduler/algorithm/types_test.go index 30b322caff5..862425f7218 100644 --- a/plugin/pkg/scheduler/algorithm/types_test.go +++ b/pkg/scheduler/algorithm/types_test.go @@ -21,7 +21,7 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" ) // EmptyMetadataProducer should returns a no-op MetadataProducer type. diff --git a/pkg/scheduler/algorithm/well_known_labels.go b/pkg/scheduler/algorithm/well_known_labels.go new file mode 100644 index 00000000000..bffe40a0885 --- /dev/null +++ b/pkg/scheduler/algorithm/well_known_labels.go @@ -0,0 +1,64 @@ +/* +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 algorithm + +const ( + // When feature-gate for TaintBasedEvictions=true flag is enabled, + // TaintNodeNotReady would be automatically added by node controller + // when node is not ready, and removed when node becomes ready. + TaintNodeNotReady = "node.kubernetes.io/not-ready" + + // DeprecatedTaintNodeNotReady is the deprecated version of TaintNodeNotReady. + // It is deprecated since 1.9 + DeprecatedTaintNodeNotReady = "node.alpha.kubernetes.io/notReady" + + // When feature-gate for TaintBasedEvictions=true flag is enabled, + // TaintNodeUnreachable would be automatically added by node controller + // when node becomes unreachable (corresponding to NodeReady status ConditionUnknown) + // and removed when node becomes reachable (NodeReady status ConditionTrue). + TaintNodeUnreachable = "node.kubernetes.io/unreachable" + + // DeprecatedTaintNodeUnreachable is the deprecated version of TaintNodeUnreachable. + // It is deprecated since 1.9 + DeprecatedTaintNodeUnreachable = "node.alpha.kubernetes.io/unreachable" + + // When feature-gate for TaintBasedEvictions=true flag is enabled, + // TaintNodeOutOfDisk would be automatically added by node controller + // when node becomes out of disk, and removed when node has enough disk. + TaintNodeOutOfDisk = "node.kubernetes.io/out-of-disk" + + // When feature-gate for TaintBasedEvictions=true flag is enabled, + // TaintNodeMemoryPressure would be automatically added by node controller + // when node has memory pressure, and removed when node has enough memory. + TaintNodeMemoryPressure = "node.kubernetes.io/memory-pressure" + + // When feature-gate for TaintBasedEvictions=true flag is enabled, + // TaintNodeDiskPressure would be automatically added by node controller + // when node has disk pressure, and removed when node has enough disk. + TaintNodeDiskPressure = "node.kubernetes.io/disk-pressure" + + // When feature-gate for TaintBasedEvictions=true flag is enabled, + // TaintNodeNetworkUnavailable would be automatically added by node controller + // when node's network is unavailable, and removed when network becomes ready. + TaintNodeNetworkUnavailable = "node.kubernetes.io/network-unavailable" + + // When kubelet is started with the "external" cloud provider, then + // it sets this taint on a node to mark it as unusable, until a controller + // from the cloud-controller-manager intitializes this node, and then removes + // the taint + TaintExternalCloudProvider = "node.cloudprovider.kubernetes.io/uninitialized" +) diff --git a/pkg/scheduler/algorithmprovider/BUILD b/pkg/scheduler/algorithmprovider/BUILD new file mode 100644 index 00000000000..4e721f3c83f --- /dev/null +++ b/pkg/scheduler/algorithmprovider/BUILD @@ -0,0 +1,41 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_library( + name = "go_default_library", + srcs = ["plugins.go"], + importpath = "k8s.io/kubernetes/pkg/scheduler/algorithmprovider", + deps = ["//pkg/scheduler/algorithmprovider/defaults:go_default_library"], +) + +go_test( + name = "go_default_test", + srcs = ["plugins_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/scheduler/algorithmprovider", + deps = [ + "//pkg/scheduler/factory:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//pkg/scheduler/algorithmprovider/defaults:all-srcs", + ], + tags = ["automanaged"], +) diff --git a/pkg/scheduler/algorithmprovider/defaults/BUILD b/pkg/scheduler/algorithmprovider/defaults/BUILD new file mode 100644 index 00000000000..f58bc3b9706 --- /dev/null +++ b/pkg/scheduler/algorithmprovider/defaults/BUILD @@ -0,0 +1,62 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_library( + name = "go_default_library", + srcs = ["defaults.go"], + importpath = "k8s.io/kubernetes/pkg/scheduler/algorithmprovider/defaults", + deps = [ + "//pkg/features:go_default_library", + "//pkg/scheduler/algorithm:go_default_library", + "//pkg/scheduler/algorithm/predicates:go_default_library", + "//pkg/scheduler/algorithm/priorities:go_default_library", + "//pkg/scheduler/core:go_default_library", + "//pkg/scheduler/factory:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "compatibility_test.go", + "defaults_test.go", + ], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/scheduler/algorithmprovider/defaults", + deps = [ + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core/install:go_default_library", + "//pkg/scheduler/algorithm/predicates:go_default_library", + "//pkg/scheduler/api:go_default_library", + "//pkg/scheduler/api/latest:go_default_library", + "//pkg/scheduler/factory:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/client-go/informers:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + "//vendor/k8s.io/client-go/util/testing:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/plugin/pkg/scheduler/algorithmprovider/defaults/compatibility_test.go b/pkg/scheduler/algorithmprovider/defaults/compatibility_test.go similarity index 85% rename from plugin/pkg/scheduler/algorithmprovider/defaults/compatibility_test.go rename to pkg/scheduler/algorithmprovider/defaults/compatibility_test.go index 1aa45ef8bdb..77c421bc850 100644 --- a/plugin/pkg/scheduler/algorithmprovider/defaults/compatibility_test.go +++ b/pkg/scheduler/algorithmprovider/defaults/compatibility_test.go @@ -29,11 +29,11 @@ import ( clientset "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" utiltesting "k8s.io/client-go/util/testing" - "k8s.io/kubernetes/pkg/api" - _ "k8s.io/kubernetes/pkg/api/install" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - latestschedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api/latest" - "k8s.io/kubernetes/plugin/pkg/scheduler/factory" + "k8s.io/kubernetes/pkg/api/legacyscheme" + _ "k8s.io/kubernetes/pkg/apis/core/install" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + latestschedulerapi "k8s.io/kubernetes/pkg/scheduler/api/latest" + "k8s.io/kubernetes/pkg/scheduler/factory" ) const enableEquivalenceCache = true @@ -337,8 +337,7 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { {"name": "MatchInterPodAffinity"}, {"name": "GeneralPredicates"}, {"name": "TestServiceAffinity", "argument": {"serviceAffinity" : {"labels" : ["region"]}}}, - {"name": "TestLabelsPresence", "argument": {"labelsPresence" : {"labels" : ["foo"], "presence":true}}}, - {"name": "NoVolumeNodeConflict"} + {"name": "TestLabelsPresence", "argument": {"labelsPresence" : {"labels" : ["foo"], "presence":true}}} ],"priorities": [ {"name": "EqualPriority", "weight": 2}, {"name": "ImageLocalityPriority", "weight": 2}, @@ -370,7 +369,6 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { {Name: "GeneralPredicates"}, {Name: "TestServiceAffinity", Argument: &schedulerapi.PredicateArgument{ServiceAffinity: &schedulerapi.ServiceAffinity{Labels: []string{"region"}}}}, {Name: "TestLabelsPresence", Argument: &schedulerapi.PredicateArgument{LabelsPresence: &schedulerapi.LabelsPresence{Labels: []string{"foo"}, Presence: true}}}, - {Name: "NoVolumeNodeConflict"}, }, Priorities: []schedulerapi.PriorityPolicy{ {Name: "EqualPriority", Weight: 2}, @@ -409,8 +407,7 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { {"name": "MatchInterPodAffinity"}, {"name": "GeneralPredicates"}, {"name": "TestServiceAffinity", "argument": {"serviceAffinity" : {"labels" : ["region"]}}}, - {"name": "TestLabelsPresence", "argument": {"labelsPresence" : {"labels" : ["foo"], "presence":true}}}, - {"name": "NoVolumeNodeConflict"} + {"name": "TestLabelsPresence", "argument": {"labelsPresence" : {"labels" : ["foo"], "presence":true}}} ],"priorities": [ {"name": "EqualPriority", "weight": 2}, {"name": "ImageLocalityPriority", "weight": 2}, @@ -443,7 +440,80 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { {Name: "GeneralPredicates"}, {Name: "TestServiceAffinity", Argument: &schedulerapi.PredicateArgument{ServiceAffinity: &schedulerapi.ServiceAffinity{Labels: []string{"region"}}}}, {Name: "TestLabelsPresence", Argument: &schedulerapi.PredicateArgument{LabelsPresence: &schedulerapi.LabelsPresence{Labels: []string{"foo"}, Presence: true}}}, - {Name: "NoVolumeNodeConflict"}, + }, + Priorities: []schedulerapi.PriorityPolicy{ + {Name: "EqualPriority", Weight: 2}, + {Name: "ImageLocalityPriority", Weight: 2}, + {Name: "LeastRequestedPriority", Weight: 2}, + {Name: "BalancedResourceAllocation", Weight: 2}, + {Name: "SelectorSpreadPriority", Weight: 2}, + {Name: "NodePreferAvoidPodsPriority", Weight: 2}, + {Name: "NodeAffinityPriority", Weight: 2}, + {Name: "TaintTolerationPriority", Weight: 2}, + {Name: "InterPodAffinityPriority", Weight: 2}, + {Name: "MostRequestedPriority", Weight: 2}, + }, + }, + }, + // Do not change this JSON after the corresponding release has been tagged. + // A failure indicates backwards compatibility with the specified release was broken. + "1.9": { + JSON: `{ + "kind": "Policy", + "apiVersion": "v1", + "predicates": [ + {"name": "MatchNodeSelector"}, + {"name": "PodFitsResources"}, + {"name": "PodFitsHostPorts"}, + {"name": "HostName"}, + {"name": "NoDiskConflict"}, + {"name": "NoVolumeZoneConflict"}, + {"name": "PodToleratesNodeTaints"}, + {"name": "CheckNodeMemoryPressure"}, + {"name": "CheckNodeDiskPressure"}, + {"name": "CheckNodeCondition"}, + {"name": "MaxEBSVolumeCount"}, + {"name": "MaxGCEPDVolumeCount"}, + {"name": "MaxAzureDiskVolumeCount"}, + {"name": "MatchInterPodAffinity"}, + {"name": "GeneralPredicates"}, + {"name": "CheckVolumeBinding"}, + {"name": "TestServiceAffinity", "argument": {"serviceAffinity" : {"labels" : ["region"]}}}, + {"name": "TestLabelsPresence", "argument": {"labelsPresence" : {"labels" : ["foo"], "presence":true}}} + + ],"priorities": [ + {"name": "EqualPriority", "weight": 2}, + {"name": "ImageLocalityPriority", "weight": 2}, + {"name": "LeastRequestedPriority", "weight": 2}, + {"name": "BalancedResourceAllocation", "weight": 2}, + {"name": "SelectorSpreadPriority", "weight": 2}, + {"name": "NodePreferAvoidPodsPriority", "weight": 2}, + {"name": "NodeAffinityPriority", "weight": 2}, + {"name": "TaintTolerationPriority", "weight": 2}, + {"name": "InterPodAffinityPriority", "weight": 2}, + {"name": "MostRequestedPriority", "weight": 2} + ] + }`, + ExpectedPolicy: schedulerapi.Policy{ + Predicates: []schedulerapi.PredicatePolicy{ + {Name: "MatchNodeSelector"}, + {Name: "PodFitsResources"}, + {Name: "PodFitsHostPorts"}, + {Name: "HostName"}, + {Name: "NoDiskConflict"}, + {Name: "NoVolumeZoneConflict"}, + {Name: "PodToleratesNodeTaints"}, + {Name: "CheckNodeMemoryPressure"}, + {Name: "CheckNodeDiskPressure"}, + {Name: "CheckNodeCondition"}, + {Name: "MaxEBSVolumeCount"}, + {Name: "MaxGCEPDVolumeCount"}, + {Name: "MaxAzureDiskVolumeCount"}, + {Name: "MatchInterPodAffinity"}, + {Name: "GeneralPredicates"}, + {Name: "CheckVolumeBinding"}, + {Name: "TestServiceAffinity", Argument: &schedulerapi.PredicateArgument{ServiceAffinity: &schedulerapi.ServiceAffinity{Labels: []string{"region"}}}}, + {Name: "TestLabelsPresence", Argument: &schedulerapi.PredicateArgument{LabelsPresence: &schedulerapi.LabelsPresence{Labels: []string{"foo"}, Presence: true}}}, }, Priorities: []schedulerapi.PriorityPolicy{ {Name: "EqualPriority", Weight: 2}, @@ -491,7 +561,7 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { } server := httptest.NewServer(&handler) defer server.Close() - client := clientset.NewForConfigOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + client := clientset.NewForConfigOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) informerFactory := informers.NewSharedInformerFactory(client, 0) if _, err := factory.NewConfigFactory( @@ -505,6 +575,8 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { informerFactory.Extensions().V1beta1().ReplicaSets(), informerFactory.Apps().V1beta1().StatefulSets(), informerFactory.Core().V1().Services(), + informerFactory.Policy().V1beta1().PodDisruptionBudgets(), + informerFactory.Storage().V1().StorageClasses(), v1.DefaultHardPodAffinitySymmetricWeight, enableEquivalenceCache, ).CreateFromConfig(policy); err != nil { diff --git a/pkg/scheduler/algorithmprovider/defaults/defaults.go b/pkg/scheduler/algorithmprovider/defaults/defaults.go new file mode 100644 index 00000000000..6cbc772ac99 --- /dev/null +++ b/pkg/scheduler/algorithmprovider/defaults/defaults.go @@ -0,0 +1,263 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package defaults + +import ( + "k8s.io/apimachinery/pkg/util/sets" + utilfeature "k8s.io/apiserver/pkg/util/feature" + + "k8s.io/kubernetes/pkg/features" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + "k8s.io/kubernetes/pkg/scheduler/algorithm/predicates" + "k8s.io/kubernetes/pkg/scheduler/algorithm/priorities" + "k8s.io/kubernetes/pkg/scheduler/core" + "k8s.io/kubernetes/pkg/scheduler/factory" + + "github.com/golang/glog" +) + +const ( + // ClusterAutoscalerProvider defines the default autoscaler provider + ClusterAutoscalerProvider = "ClusterAutoscalerProvider" +) + +func init() { + // Register functions that extract metadata used by predicates and priorities computations. + factory.RegisterPredicateMetadataProducerFactory( + func(args factory.PluginFactoryArgs) algorithm.PredicateMetadataProducer { + return predicates.NewPredicateMetadataFactory(args.PodLister) + }) + factory.RegisterPriorityMetadataProducerFactory( + func(args factory.PluginFactoryArgs) algorithm.MetadataProducer { + return priorities.NewPriorityMetadataFactory(args.ServiceLister, args.ControllerLister, args.ReplicaSetLister, args.StatefulSetLister) + }) + + registerAlgorithmProvider(defaultPredicates(), defaultPriorities()) + + // IMPORTANT NOTES for predicate developers: + // We are using cached predicate result for pods belonging to the same equivalence class. + // So when implementing a new predicate, you are expected to check whether the result + // of your predicate function can be affected by related API object change (ADD/DELETE/UPDATE). + // If yes, you are expected to invalidate the cached predicate result for related API object change. + // For example: + // https://github.com/kubernetes/kubernetes/blob/36a218e/plugin/pkg/scheduler/factory/factory.go#L422 + + // Registers predicates and priorities that are not enabled by default, but user can pick when creating his + // own set of priorities/predicates. + + // PodFitsPorts has been replaced by PodFitsHostPorts for better user understanding. + // For backwards compatibility with 1.0, PodFitsPorts is registered as well. + factory.RegisterFitPredicate("PodFitsPorts", predicates.PodFitsHostPorts) + // Fit is defined based on the absence of port conflicts. + // This predicate is actually a default predicate, because it is invoked from + // predicates.GeneralPredicates() + factory.RegisterFitPredicate(predicates.PodFitsHostPortsPred, predicates.PodFitsHostPorts) + // Fit is determined by resource availability. + // This predicate is actually a default predicate, because it is invoked from + // predicates.GeneralPredicates() + factory.RegisterFitPredicate(predicates.PodFitsResourcesPred, predicates.PodFitsResources) + // Fit is determined by the presence of the Host parameter and a string match + // This predicate is actually a default predicate, because it is invoked from + // predicates.GeneralPredicates() + factory.RegisterFitPredicate(predicates.HostNamePred, predicates.PodFitsHost) + // Fit is determined by node selector query. + factory.RegisterFitPredicate(predicates.MatchNodeSelectorPred, predicates.PodMatchNodeSelector) + + // Use equivalence class to speed up heavy predicates phase. + factory.RegisterGetEquivalencePodFunction( + func(args factory.PluginFactoryArgs) algorithm.GetEquivalencePodFunc { + return predicates.NewEquivalencePodGenerator(args.PVCInfo) + }, + ) + + // ServiceSpreadingPriority is a priority config factory that spreads pods by minimizing + // the number of pods (belonging to the same service) on the same node. + // Register the factory so that it's available, but do not include it as part of the default priorities + // Largely replaced by "SelectorSpreadPriority", but registered for backward compatibility with 1.0 + factory.RegisterPriorityConfigFactory( + "ServiceSpreadingPriority", + factory.PriorityConfigFactory{ + MapReduceFunction: func(args factory.PluginFactoryArgs) (algorithm.PriorityMapFunction, algorithm.PriorityReduceFunction) { + return priorities.NewSelectorSpreadPriority(args.ServiceLister, algorithm.EmptyControllerLister{}, algorithm.EmptyReplicaSetLister{}, algorithm.EmptyStatefulSetLister{}) + }, + Weight: 1, + }, + ) + // EqualPriority is a prioritizer function that gives an equal weight of one to all nodes + // Register the priority function so that its available + // but do not include it as part of the default priorities + factory.RegisterPriorityFunction2("EqualPriority", core.EqualPriorityMap, nil, 1) + // ImageLocalityPriority prioritizes nodes based on locality of images requested by a pod. Nodes with larger size + // of already-installed packages required by the pod will be preferred over nodes with no already-installed + // packages required by the pod or a small total size of already-installed packages required by the pod. + factory.RegisterPriorityFunction2("ImageLocalityPriority", priorities.ImageLocalityPriorityMap, nil, 1) + // Optional, cluster-autoscaler friendly priority function - give used nodes higher priority. + factory.RegisterPriorityFunction2("MostRequestedPriority", priorities.MostRequestedPriorityMap, nil, 1) + // Prioritizes nodes that satisfy pod's resource limits + if utilfeature.DefaultFeatureGate.Enabled(features.ResourceLimitsPriorityFunction) { + factory.RegisterPriorityFunction2("ResourceLimitsPriority", priorities.ResourceLimitsPriorityMap, nil, 1) + } +} + +func defaultPredicates() sets.String { + return sets.NewString( + // Fit is determined by volume zone requirements. + factory.RegisterFitPredicateFactory( + predicates.NoVolumeZoneConflictPred, + func(args factory.PluginFactoryArgs) algorithm.FitPredicate { + return predicates.NewVolumeZonePredicate(args.PVInfo, args.PVCInfo, args.StorageClassInfo) + }, + ), + // Fit is determined by whether or not there would be too many AWS EBS volumes attached to the node + factory.RegisterFitPredicateFactory( + predicates.MaxEBSVolumeCountPred, + func(args factory.PluginFactoryArgs) algorithm.FitPredicate { + return predicates.NewMaxPDVolumeCountPredicate(predicates.EBSVolumeFilterType, args.PVInfo, args.PVCInfo) + }, + ), + // Fit is determined by whether or not there would be too many GCE PD volumes attached to the node + factory.RegisterFitPredicateFactory( + predicates.MaxGCEPDVolumeCountPred, + func(args factory.PluginFactoryArgs) algorithm.FitPredicate { + return predicates.NewMaxPDVolumeCountPredicate(predicates.GCEPDVolumeFilterType, args.PVInfo, args.PVCInfo) + }, + ), + // Fit is determined by whether or not there would be too many Azure Disk volumes attached to the node + factory.RegisterFitPredicateFactory( + predicates.MaxAzureDiskVolumeCountPred, + func(args factory.PluginFactoryArgs) algorithm.FitPredicate { + return predicates.NewMaxPDVolumeCountPredicate(predicates.AzureDiskVolumeFilterType, args.PVInfo, args.PVCInfo) + }, + ), + // Fit is determined by inter-pod affinity. + factory.RegisterFitPredicateFactory( + predicates.MatchInterPodAffinityPred, + func(args factory.PluginFactoryArgs) algorithm.FitPredicate { + return predicates.NewPodAffinityPredicate(args.NodeInfo, args.PodLister) + }, + ), + + // Fit is determined by non-conflicting disk volumes. + factory.RegisterFitPredicate(predicates.NoDiskConflictPred, predicates.NoDiskConflict), + + // GeneralPredicates are the predicates that are enforced by all Kubernetes components + // (e.g. kubelet and all schedulers) + factory.RegisterFitPredicate(predicates.GeneralPred, predicates.GeneralPredicates), + + // Fit is determined by node memory pressure condition. + factory.RegisterFitPredicate(predicates.CheckNodeMemoryPressurePred, predicates.CheckNodeMemoryPressurePredicate), + + // Fit is determined by node disk pressure condition. + factory.RegisterFitPredicate(predicates.CheckNodeDiskPressurePred, predicates.CheckNodeDiskPressurePredicate), + + // Fit is determined by node conditions: not ready, network unavailable or out of disk. + factory.RegisterMandatoryFitPredicate(predicates.CheckNodeConditionPred, predicates.CheckNodeConditionPredicate), + + // Fit is determined based on whether a pod can tolerate all of the node's taints + factory.RegisterFitPredicate(predicates.PodToleratesNodeTaintsPred, predicates.PodToleratesNodeTaints), + + // Fit is determined by volume topology requirements. + factory.RegisterFitPredicateFactory( + predicates.CheckVolumeBindingPred, + func(args factory.PluginFactoryArgs) algorithm.FitPredicate { + return predicates.NewVolumeBindingPredicate(args.VolumeBinder) + }, + ), + ) +} + +// ApplyFeatureGates applies algorithm by feature gates. +func ApplyFeatureGates() { + + if utilfeature.DefaultFeatureGate.Enabled(features.TaintNodesByCondition) { + // Remove "CheckNodeCondition" predicate + factory.RemoveFitPredicate(predicates.CheckNodeConditionPred) + // Remove Key "CheckNodeCondition" From All Algorithm Provider + // The key will be removed from all providers which in algorithmProviderMap[] + // if you just want remove specific provider, call func RemovePredicateKeyFromAlgoProvider() + factory.RemovePredicateKeyFromAlgorithmProviderMap(predicates.CheckNodeConditionPred) + + // Fit is determined based on whether a pod can tolerate all of the node's taints + factory.RegisterMandatoryFitPredicate(predicates.PodToleratesNodeTaintsPred, predicates.PodToleratesNodeTaints) + // Insert Key "PodToleratesNodeTaints" To All Algorithm Provider + // The key will insert to all providers which in algorithmProviderMap[] + // if you just want insert to specific provider, call func InsertPredicateKeyToAlgoProvider() + factory.InsertPredicateKeyToAlgorithmProviderMap(predicates.PodToleratesNodeTaintsPred) + + glog.Warningf("TaintNodesByCondition is enabled, PodToleratesNodeTaints predicate is mandatory") + } +} + +func registerAlgorithmProvider(predSet, priSet sets.String) { + // Registers algorithm providers. By default we use 'DefaultProvider', but user can specify one to be used + // by specifying flag. + factory.RegisterAlgorithmProvider(factory.DefaultProvider, predSet, priSet) + // Cluster autoscaler friendly scheduling algorithm. + factory.RegisterAlgorithmProvider(ClusterAutoscalerProvider, predSet, + copyAndReplace(priSet, "LeastRequestedPriority", "MostRequestedPriority")) +} + +func defaultPriorities() sets.String { + return sets.NewString( + // spreads pods by minimizing the number of pods (belonging to the same service or replication controller) on the same node. + factory.RegisterPriorityConfigFactory( + "SelectorSpreadPriority", + factory.PriorityConfigFactory{ + MapReduceFunction: func(args factory.PluginFactoryArgs) (algorithm.PriorityMapFunction, algorithm.PriorityReduceFunction) { + return priorities.NewSelectorSpreadPriority(args.ServiceLister, args.ControllerLister, args.ReplicaSetLister, args.StatefulSetLister) + }, + Weight: 1, + }, + ), + // pods should be placed in the same topological domain (e.g. same node, same rack, same zone, same power domain, etc.) + // as some other pods, or, conversely, should not be placed in the same topological domain as some other pods. + factory.RegisterPriorityConfigFactory( + "InterPodAffinityPriority", + factory.PriorityConfigFactory{ + Function: func(args factory.PluginFactoryArgs) algorithm.PriorityFunction { + return priorities.NewInterPodAffinityPriority(args.NodeInfo, args.NodeLister, args.PodLister, args.HardPodAffinitySymmetricWeight) + }, + Weight: 1, + }, + ), + + // Prioritize nodes by least requested utilization. + factory.RegisterPriorityFunction2("LeastRequestedPriority", priorities.LeastRequestedPriorityMap, nil, 1), + + // Prioritizes nodes to help achieve balanced resource usage + factory.RegisterPriorityFunction2("BalancedResourceAllocation", priorities.BalancedResourceAllocationMap, nil, 1), + + // Set this weight large enough to override all other priority functions. + // TODO: Figure out a better way to do this, maybe at same time as fixing #24720. + factory.RegisterPriorityFunction2("NodePreferAvoidPodsPriority", priorities.CalculateNodePreferAvoidPodsPriorityMap, nil, 10000), + + // Prioritizes nodes that have labels matching NodeAffinity + factory.RegisterPriorityFunction2("NodeAffinityPriority", priorities.CalculateNodeAffinityPriorityMap, priorities.CalculateNodeAffinityPriorityReduce, 1), + + // Prioritizes nodes that marked with taint which pod can tolerate. + factory.RegisterPriorityFunction2("TaintTolerationPriority", priorities.ComputeTaintTolerationPriorityMap, priorities.ComputeTaintTolerationPriorityReduce, 1), + ) +} + +func copyAndReplace(set sets.String, replaceWhat, replaceWith string) sets.String { + result := sets.NewString(set.List()...) + if result.Has(replaceWhat) { + result.Delete(replaceWhat) + result.Insert(replaceWith) + } + return result +} diff --git a/pkg/scheduler/algorithmprovider/defaults/defaults_test.go b/pkg/scheduler/algorithmprovider/defaults/defaults_test.go new file mode 100644 index 00000000000..d78bb62835a --- /dev/null +++ b/pkg/scheduler/algorithmprovider/defaults/defaults_test.go @@ -0,0 +1,87 @@ +/* +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 defaults + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/kubernetes/pkg/scheduler/algorithm/predicates" +) + +func TestCopyAndReplace(t *testing.T) { + testCases := []struct { + set sets.String + replaceWhat string + replaceWith string + expected sets.String + }{ + { + set: sets.String{"A": sets.Empty{}, "B": sets.Empty{}}, + replaceWhat: "A", + replaceWith: "C", + expected: sets.String{"B": sets.Empty{}, "C": sets.Empty{}}, + }, + { + set: sets.String{"A": sets.Empty{}, "B": sets.Empty{}}, + replaceWhat: "D", + replaceWith: "C", + expected: sets.String{"A": sets.Empty{}, "B": sets.Empty{}}, + }, + } + for _, testCase := range testCases { + result := copyAndReplace(testCase.set, testCase.replaceWhat, testCase.replaceWith) + if !result.Equal(testCase.expected) { + t.Errorf("expected %v got %v", testCase.expected, result) + } + } +} + +func TestDefaultPriorities(t *testing.T) { + result := sets.NewString( + "SelectorSpreadPriority", + "InterPodAffinityPriority", + "LeastRequestedPriority", + "BalancedResourceAllocation", + "NodePreferAvoidPodsPriority", + "NodeAffinityPriority", + "TaintTolerationPriority") + if expected := defaultPriorities(); !result.Equal(expected) { + t.Errorf("expected %v got %v", expected, result) + } +} + +func TestDefaultPredicates(t *testing.T) { + result := sets.NewString( + "NoVolumeZoneConflict", + "MaxEBSVolumeCount", + "MaxGCEPDVolumeCount", + "MaxAzureDiskVolumeCount", + "MatchInterPodAffinity", + "NoDiskConflict", + "GeneralPredicates", + "CheckNodeMemoryPressure", + "CheckNodeDiskPressure", + "CheckNodeCondition", + "PodToleratesNodeTaints", + predicates.CheckVolumeBindingPred, + ) + + if expected := defaultPredicates(); !result.Equal(expected) { + t.Errorf("expected %v got %v", expected, result) + } +} diff --git a/pkg/scheduler/algorithmprovider/plugins.go b/pkg/scheduler/algorithmprovider/plugins.go new file mode 100644 index 00000000000..e2784f62609 --- /dev/null +++ b/pkg/scheduler/algorithmprovider/plugins.go @@ -0,0 +1,26 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package algorithmprovider + +import ( + "k8s.io/kubernetes/pkg/scheduler/algorithmprovider/defaults" +) + +// ApplyFeatureGates applies algorithm by feature gates. +func ApplyFeatureGates() { + defaults.ApplyFeatureGates() +} diff --git a/pkg/scheduler/algorithmprovider/plugins_test.go b/pkg/scheduler/algorithmprovider/plugins_test.go new file mode 100644 index 00000000000..16a1e8f5ac2 --- /dev/null +++ b/pkg/scheduler/algorithmprovider/plugins_test.go @@ -0,0 +1,109 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package algorithmprovider + +import ( + "testing" + + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/scheduler/factory" +) + +var ( + algorithmProviderNames = []string{ + factory.DefaultProvider, + } +) + +func TestDefaultConfigExists(t *testing.T) { + p, err := factory.GetAlgorithmProvider(factory.DefaultProvider) + if err != nil { + t.Errorf("error retrieving default provider: %v", err) + } + if p == nil { + t.Error("algorithm provider config should not be nil") + } + if len(p.FitPredicateKeys) == 0 { + t.Error("default algorithm provider shouldn't have 0 fit predicates") + } +} + +func TestAlgorithmProviders(t *testing.T) { + for _, pn := range algorithmProviderNames { + p, err := factory.GetAlgorithmProvider(pn) + if err != nil { + t.Errorf("error retrieving '%s' provider: %v", pn, err) + break + } + if len(p.PriorityFunctionKeys) == 0 { + t.Errorf("%s algorithm provider shouldn't have 0 priority functions", pn) + } + for _, pf := range p.PriorityFunctionKeys.List() { + if !factory.IsPriorityFunctionRegistered(pf) { + t.Errorf("priority function %s is not registered but is used in the %s algorithm provider", pf, pn) + } + } + for _, fp := range p.FitPredicateKeys.List() { + if !factory.IsFitPredicateRegistered(fp) { + t.Errorf("fit predicate %s is not registered but is used in the %s algorithm provider", fp, pn) + } + } + } +} + +func TestApplyFeatureGates(t *testing.T) { + for _, pn := range algorithmProviderNames { + p, err := factory.GetAlgorithmProvider(pn) + if err != nil { + t.Errorf("Error retrieving '%s' provider: %v", pn, err) + break + } + + if !p.FitPredicateKeys.Has("CheckNodeCondition") { + t.Errorf("Failed to find predicate: 'CheckNodeCondition'") + break + } + + if !p.FitPredicateKeys.Has("PodToleratesNodeTaints") { + t.Errorf("Failed to find predicate: 'PodToleratesNodeTaints'") + break + } + } + + // Apply features for algorithm providers. + utilfeature.DefaultFeatureGate.Set("TaintNodesByCondition=True") + + ApplyFeatureGates() + + for _, pn := range algorithmProviderNames { + p, err := factory.GetAlgorithmProvider(pn) + if err != nil { + t.Errorf("Error retrieving '%s' provider: %v", pn, err) + break + } + + if !p.FitPredicateKeys.Has("PodToleratesNodeTaints") { + t.Errorf("Failed to find predicate: 'PodToleratesNodeTaints'") + break + } + + if p.FitPredicateKeys.Has("CheckNodeCondition") { + t.Errorf("Unexpected predicate: 'CheckNodeCondition'") + break + } + } +} diff --git a/pkg/scheduler/api/BUILD b/pkg/scheduler/api/BUILD new file mode 100644 index 00000000000..eeaef16a422 --- /dev/null +++ b/pkg/scheduler/api/BUILD @@ -0,0 +1,43 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "register.go", + "types.go", + "zz_generated.deepcopy.go", + ], + importpath = "k8s.io/kubernetes/pkg/scheduler/api", + deps = [ + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//pkg/scheduler/api/latest:all-srcs", + "//pkg/scheduler/api/v1:all-srcs", + "//pkg/scheduler/api/validation:all-srcs", + ], + tags = ["automanaged"], +) diff --git a/pkg/scheduler/api/doc.go b/pkg/scheduler/api/doc.go new file mode 100644 index 00000000000..c768a8c92cf --- /dev/null +++ b/pkg/scheduler/api/doc.go @@ -0,0 +1,20 @@ +/* +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 + +// Package api contains scheduler API objects. +package api // import "k8s.io/kubernetes/pkg/scheduler/api" diff --git a/pkg/scheduler/api/latest/BUILD b/pkg/scheduler/api/latest/BUILD new file mode 100644 index 00000000000..439f952b52a --- /dev/null +++ b/pkg/scheduler/api/latest/BUILD @@ -0,0 +1,33 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = ["latest.go"], + importpath = "k8s.io/kubernetes/pkg/scheduler/api/latest", + deps = [ + "//pkg/scheduler/api:go_default_library", + "//pkg/scheduler/api/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer/json:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer/versioning:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/plugin/pkg/scheduler/api/latest/latest.go b/pkg/scheduler/api/latest/latest.go similarity index 87% rename from plugin/pkg/scheduler/api/latest/latest.go rename to pkg/scheduler/api/latest/latest.go index cef40b6acb1..4fa4bfb6cc9 100644 --- a/plugin/pkg/scheduler/api/latest/latest.go +++ b/pkg/scheduler/api/latest/latest.go @@ -21,8 +21,8 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer/json" "k8s.io/apimachinery/pkg/runtime/serializer/versioning" - "k8s.io/kubernetes/plugin/pkg/scheduler/api" - _ "k8s.io/kubernetes/plugin/pkg/scheduler/api/v1" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + _ "k8s.io/kubernetes/pkg/scheduler/api/v1" ) // Version is the string that represents the current external default version. @@ -42,9 +42,9 @@ var Versions = []string{"v1"} var Codec runtime.Codec func init() { - jsonSerializer := json.NewSerializer(json.DefaultMetaFactory, api.Scheme, api.Scheme, true) + jsonSerializer := json.NewSerializer(json.DefaultMetaFactory, schedulerapi.Scheme, schedulerapi.Scheme, true) Codec = versioning.NewDefaultingCodecForScheme( - api.Scheme, + schedulerapi.Scheme, jsonSerializer, jsonSerializer, schema.GroupVersion{Version: Version}, diff --git a/plugin/pkg/scheduler/api/register.go b/pkg/scheduler/api/register.go similarity index 100% rename from plugin/pkg/scheduler/api/register.go rename to pkg/scheduler/api/register.go diff --git a/pkg/scheduler/api/types.go b/pkg/scheduler/api/types.go new file mode 100644 index 00000000000..080fc386db5 --- /dev/null +++ b/pkg/scheduler/api/types.go @@ -0,0 +1,227 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package api + +import ( + "time" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + restclient "k8s.io/client-go/rest" +) + +const ( + MaxUint = ^uint(0) + MaxInt = int(MaxUint >> 1) + MaxTotalPriority = MaxInt + MaxPriority = 10 + MaxWeight = MaxInt / MaxPriority +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type Policy struct { + metav1.TypeMeta + // Holds the information to configure the fit predicate functions + Predicates []PredicatePolicy + // Holds the information to configure the priority functions + Priorities []PriorityPolicy + // Holds the information to communicate with the extender(s) + ExtenderConfigs []ExtenderConfig + // RequiredDuringScheduling affinity is not symmetric, but there is an implicit PreferredDuringScheduling affinity rule + // corresponding to every RequiredDuringScheduling affinity rule. + // HardPodAffinitySymmetricWeight represents the weight of implicit PreferredDuringScheduling affinity rule, in the range 1-100. + HardPodAffinitySymmetricWeight int32 +} + +type PredicatePolicy struct { + // Identifier of the predicate policy + // For a custom predicate, the name can be user-defined + // For the Kubernetes provided predicates, the name is the identifier of the pre-defined predicate + Name string + // Holds the parameters to configure the given predicate + Argument *PredicateArgument +} + +type PriorityPolicy struct { + // Identifier of the priority policy + // For a custom priority, the name can be user-defined + // For the Kubernetes provided priority functions, the name is the identifier of the pre-defined priority function + Name string + // The numeric multiplier for the node scores that the priority function generates + // The weight should be a positive integer + Weight int + // Holds the parameters to configure the given priority function + Argument *PriorityArgument +} + +// PredicateArgument represents the arguments to configure predicate functions in scheduler policy configuration. +// Only one of its members may be specified +type PredicateArgument struct { + // The predicate that provides affinity for pods belonging to a service + // It uses a label to identify nodes that belong to the same "group" + ServiceAffinity *ServiceAffinity + // The predicate that checks whether a particular node has a certain label + // defined or not, regardless of value + LabelsPresence *LabelsPresence +} + +// PriorityArgument represents the arguments to configure priority functions in scheduler policy configuration. +// Only one of its members may be specified +type PriorityArgument struct { + // The priority function that ensures a good spread (anti-affinity) for pods belonging to a service + // It uses a label to identify nodes that belong to the same "group" + ServiceAntiAffinity *ServiceAntiAffinity + // The priority function that checks whether a particular node has a certain label + // defined or not, regardless of value + LabelPreference *LabelPreference +} + +// ServiceAffinity holds the parameters that are used to configure the corresponding predicate in scheduler policy configuration. +type ServiceAffinity struct { + // The list of labels that identify node "groups" + // All of the labels should match for the node to be considered a fit for hosting the pod + Labels []string +} + +// LabelsPresence holds the parameters that are used to configure the corresponding predicate in scheduler policy configuration. +type LabelsPresence struct { + // The list of labels that identify node "groups" + // All of the labels should be either present (or absent) for the node to be considered a fit for hosting the pod + Labels []string + // The boolean flag that indicates whether the labels should be present or absent from the node + Presence bool +} + +// ServiceAntiAffinity holds the parameters that are used to configure the corresponding priority function +type ServiceAntiAffinity struct { + // Used to identify node "groups" + Label string +} + +// LabelPreference holds the parameters that are used to configure the corresponding priority function +type LabelPreference struct { + // Used to identify node "groups" + Label string + // This is a boolean flag + // If true, higher priority is given to nodes that have the label + // If false, higher priority is given to nodes that do not have the label + Presence bool +} + +// ExtenderConfig holds the parameters used to communicate with the extender. If a verb is unspecified/empty, +// it is assumed that the extender chose not to provide that extension. +type ExtenderConfig struct { + // URLPrefix at which the extender is available + URLPrefix string + // Verb for the filter call, empty if not supported. This verb is appended to the URLPrefix when issuing the filter call to extender. + FilterVerb string + // Verb for the prioritize call, empty if not supported. This verb is appended to the URLPrefix when issuing the prioritize call to extender. + PrioritizeVerb string + // The numeric multiplier for the node scores that the prioritize call generates. + // The weight should be a positive integer + Weight int + // Verb for the bind call, empty if not supported. This verb is appended to the URLPrefix when issuing the bind call to extender. + // If this method is implemented by the extender, it is the extender's responsibility to bind the pod to apiserver. Only one extender + // can implement this function. + BindVerb string + // EnableHttps specifies whether https should be used to communicate with the extender + EnableHttps bool + // TLSConfig specifies the transport layer security config + TLSConfig *restclient.TLSClientConfig + // HTTPTimeout specifies the timeout duration for a call to the extender. Filter timeout fails the scheduling of the pod. Prioritize + // timeout is ignored, k8s/other extenders priorities are used to select the node. + HTTPTimeout time.Duration + // NodeCacheCapable specifies that the extender is capable of caching node information, + // so the scheduler should only send minimal information about the eligible nodes + // assuming that the extender already cached full details of all nodes in the cluster + NodeCacheCapable bool +} + +// ExtenderArgs represents the arguments needed by the extender to filter/prioritize +// nodes for a pod. +type ExtenderArgs struct { + // Pod being scheduled + Pod v1.Pod + // List of candidate nodes where the pod can be scheduled; to be populated + // only if ExtenderConfig.NodeCacheCapable == false + Nodes *v1.NodeList + // List of candidate node names where the pod can be scheduled; to be + // populated only if ExtenderConfig.NodeCacheCapable == true + NodeNames *[]string +} + +// FailedNodesMap represents the filtered out nodes, with node names and failure messages +type FailedNodesMap map[string]string + +// ExtenderFilterResult represents the results of a filter call to an extender +type ExtenderFilterResult struct { + // Filtered set of nodes where the pod can be scheduled; to be populated + // only if ExtenderConfig.NodeCacheCapable == false + Nodes *v1.NodeList + // Filtered set of nodes where the pod can be scheduled; to be populated + // only if ExtenderConfig.NodeCacheCapable == true + NodeNames *[]string + // Filtered out nodes where the pod can't be scheduled and the failure messages + FailedNodes FailedNodesMap + // Error message indicating failure + Error string +} + +// ExtenderBindingArgs represents the arguments to an extender for binding a pod to a node. +type ExtenderBindingArgs struct { + // PodName is the name of the pod being bound + PodName string + // PodNamespace is the namespace of the pod being bound + PodNamespace string + // PodUID is the UID of the pod being bound + PodUID types.UID + // Node selected by the scheduler + Node string +} + +// ExtenderBindingResult represents the result of binding of a pod to a node from an extender. +type ExtenderBindingResult struct { + // Error message indicating failure + Error string +} + +// HostPriority represents the priority of scheduling to a particular host, higher priority is better. +type HostPriority struct { + // Name of the host + Host string + // Score associated with the host + Score int +} + +type HostPriorityList []HostPriority + +func (h HostPriorityList) Len() int { + return len(h) +} + +func (h HostPriorityList) Less(i, j int) bool { + if h[i].Score == h[j].Score { + return h[i].Host < h[j].Host + } + return h[i].Score < h[j].Score +} + +func (h HostPriorityList) Swap(i, j int) { + h[i], h[j] = h[j], h[i] +} diff --git a/pkg/scheduler/api/v1/BUILD b/pkg/scheduler/api/v1/BUILD new file mode 100644 index 00000000000..35e291a27d5 --- /dev/null +++ b/pkg/scheduler/api/v1/BUILD @@ -0,0 +1,39 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "register.go", + "types.go", + "zz_generated.deepcopy.go", + ], + importpath = "k8s.io/kubernetes/pkg/scheduler/api/v1", + deps = [ + "//pkg/scheduler/api:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/pkg/scheduler/api/v1/doc.go b/pkg/scheduler/api/v1/doc.go new file mode 100644 index 00000000000..3386c4d8d21 --- /dev/null +++ b/pkg/scheduler/api/v1/doc.go @@ -0,0 +1,20 @@ +/* +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 + +// Package v1 contains scheduler API objects. +package v1 // import "k8s.io/kubernetes/pkg/scheduler/api/v1" diff --git a/pkg/scheduler/api/v1/register.go b/pkg/scheduler/api/v1/register.go new file mode 100644 index 00000000000..0b45a6a2d0d --- /dev/null +++ b/pkg/scheduler/api/v1/register.go @@ -0,0 +1,56 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES 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" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" +) + +// SchemeGroupVersion is group version used to register these objects +// TODO this should be in the "scheduler" group +var SchemeGroupVersion = schema.GroupVersion{Group: "", Version: "v1"} + +func init() { + if err := addKnownTypes(schedulerapi.Scheme); err != nil { + // Programmer error. + panic(err) + } +} + +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, + &Policy{}, + ) + return nil +} diff --git a/pkg/scheduler/api/v1/types.go b/pkg/scheduler/api/v1/types.go new file mode 100644 index 00000000000..3f6684a5f3c --- /dev/null +++ b/pkg/scheduler/api/v1/types.go @@ -0,0 +1,219 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES 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 ( + "time" + + apiv1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + restclient "k8s.io/client-go/rest" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type Policy struct { + metav1.TypeMeta `json:",inline"` + // Holds the information to configure the fit predicate functions + Predicates []PredicatePolicy `json:"predicates"` + // Holds the information to configure the priority functions + Priorities []PriorityPolicy `json:"priorities"` + // Holds the information to communicate with the extender(s) + ExtenderConfigs []ExtenderConfig `json:"extenders"` + // RequiredDuringScheduling affinity is not symmetric, but there is an implicit PreferredDuringScheduling affinity rule + // corresponding to every RequiredDuringScheduling affinity rule. + // HardPodAffinitySymmetricWeight represents the weight of implicit PreferredDuringScheduling affinity rule, in the range 1-100. + HardPodAffinitySymmetricWeight int `json:"hardPodAffinitySymmetricWeight"` +} + +type PredicatePolicy struct { + // Identifier of the predicate policy + // For a custom predicate, the name can be user-defined + // For the Kubernetes provided predicates, the name is the identifier of the pre-defined predicate + Name string `json:"name"` + // Holds the parameters to configure the given predicate + Argument *PredicateArgument `json:"argument"` +} + +type PriorityPolicy struct { + // Identifier of the priority policy + // For a custom priority, the name can be user-defined + // For the Kubernetes provided priority functions, the name is the identifier of the pre-defined priority function + Name string `json:"name"` + // The numeric multiplier for the node scores that the priority function generates + // The weight should be non-zero and can be a positive or a negative integer + Weight int `json:"weight"` + // Holds the parameters to configure the given priority function + Argument *PriorityArgument `json:"argument"` +} + +// PredicateArgument represents the arguments to configure predicate functions in scheduler policy configuration. +// Only one of its members may be specified +type PredicateArgument struct { + // The predicate that provides affinity for pods belonging to a service + // It uses a label to identify nodes that belong to the same "group" + ServiceAffinity *ServiceAffinity `json:"serviceAffinity"` + // The predicate that checks whether a particular node has a certain label + // defined or not, regardless of value + LabelsPresence *LabelsPresence `json:"labelsPresence"` +} + +// PriorityArgument represents the arguments to configure priority functions in scheduler policy configuration. +// Only one of its members may be specified +type PriorityArgument struct { + // The priority function that ensures a good spread (anti-affinity) for pods belonging to a service + // It uses a label to identify nodes that belong to the same "group" + ServiceAntiAffinity *ServiceAntiAffinity `json:"serviceAntiAffinity"` + // The priority function that checks whether a particular node has a certain label + // defined or not, regardless of value + LabelPreference *LabelPreference `json:"labelPreference"` +} + +// ServiceAffinity holds the parameters that are used to configure the corresponding predicate in scheduler policy configuration. +type ServiceAffinity struct { + // The list of labels that identify node "groups" + // All of the labels should match for the node to be considered a fit for hosting the pod + Labels []string `json:"labels"` +} + +// LabelsPresence holds the parameters that are used to configure the corresponding predicate in scheduler policy configuration. +type LabelsPresence struct { + // The list of labels that identify node "groups" + // All of the labels should be either present (or absent) for the node to be considered a fit for hosting the pod + Labels []string `json:"labels"` + // The boolean flag that indicates whether the labels should be present or absent from the node + Presence bool `json:"presence"` +} + +// ServiceAntiAffinity holds the parameters that are used to configure the corresponding priority function +type ServiceAntiAffinity struct { + // Used to identify node "groups" + Label string `json:"label"` +} + +// LabelPreference holds the parameters that are used to configure the corresponding priority function +type LabelPreference struct { + // Used to identify node "groups" + Label string `json:"label"` + // This is a boolean flag + // If true, higher priority is given to nodes that have the label + // If false, higher priority is given to nodes that do not have the label + Presence bool `json:"presence"` +} + +// ExtenderConfig holds the parameters used to communicate with the extender. If a verb is unspecified/empty, +// it is assumed that the extender chose not to provide that extension. +type ExtenderConfig struct { + // URLPrefix at which the extender is available + URLPrefix string `json:"urlPrefix"` + // Verb for the filter call, empty if not supported. This verb is appended to the URLPrefix when issuing the filter call to extender. + FilterVerb string `json:"filterVerb,omitempty"` + // Verb for the prioritize call, empty if not supported. This verb is appended to the URLPrefix when issuing the prioritize call to extender. + PrioritizeVerb string `json:"prioritizeVerb,omitempty"` + // The numeric multiplier for the node scores that the prioritize call generates. + // The weight should be a positive integer + Weight int `json:"weight,omitempty"` + // Verb for the bind call, empty if not supported. This verb is appended to the URLPrefix when issuing the bind call to extender. + // If this method is implemented by the extender, it is the extender's responsibility to bind the pod to apiserver. Only one extender + // can implement this function. + BindVerb string + // EnableHttps specifies whether https should be used to communicate with the extender + EnableHttps bool `json:"enableHttps,omitempty"` + // TLSConfig specifies the transport layer security config + TLSConfig *restclient.TLSClientConfig `json:"tlsConfig,omitempty"` + // HTTPTimeout specifies the timeout duration for a call to the extender. Filter timeout fails the scheduling of the pod. Prioritize + // timeout is ignored, k8s/other extenders priorities are used to select the node. + HTTPTimeout time.Duration `json:"httpTimeout,omitempty"` + // NodeCacheCapable specifies that the extender is capable of caching node information, + // so the scheduler should only send minimal information about the eligible nodes + // assuming that the extender already cached full details of all nodes in the cluster + NodeCacheCapable bool `json:"nodeCacheCapable,omitempty"` +} + +// ExtenderArgs represents the arguments needed by the extender to filter/prioritize +// nodes for a pod. +type ExtenderArgs struct { + // Pod being scheduled + Pod apiv1.Pod `json:"pod"` + // List of candidate nodes where the pod can be scheduled; to be populated + // only if ExtenderConfig.NodeCacheCapable == false + Nodes *apiv1.NodeList `json:"nodes,omitempty"` + // List of candidate node names where the pod can be scheduled; to be + // populated only if ExtenderConfig.NodeCacheCapable == true + NodeNames *[]string `json:"nodenames,omitempty"` +} + +// FailedNodesMap represents the filtered out nodes, with node names and failure messages +type FailedNodesMap map[string]string + +// ExtenderFilterResult represents the results of a filter call to an extender +type ExtenderFilterResult struct { + // Filtered set of nodes where the pod can be scheduled; to be populated + // only if ExtenderConfig.NodeCacheCapable == false + Nodes *apiv1.NodeList `json:"nodes,omitempty"` + // Filtered set of nodes where the pod can be scheduled; to be populated + // only if ExtenderConfig.NodeCacheCapable == true + NodeNames *[]string `json:"nodenames,omitempty"` + // Filtered out nodes where the pod can't be scheduled and the failure messages + FailedNodes FailedNodesMap `json:"failedNodes,omitempty"` + // Error message indicating failure + Error string `json:"error,omitempty"` +} + +// ExtenderBindingArgs represents the arguments to an extender for binding a pod to a node. +type ExtenderBindingArgs struct { + // PodName is the name of the pod being bound + PodName string + // PodNamespace is the namespace of the pod being bound + PodNamespace string + // PodUID is the UID of the pod being bound + PodUID types.UID + // Node selected by the scheduler + Node string +} + +// ExtenderBindingResult represents the result of binding of a pod to a node from an extender. +type ExtenderBindingResult struct { + // Error message indicating failure + Error string +} + +// HostPriority represents the priority of scheduling to a particular host, higher priority is better. +type HostPriority struct { + // Name of the host + Host string `json:"host"` + // Score associated with the host + Score int `json:"score"` +} + +type HostPriorityList []HostPriority + +func (h HostPriorityList) Len() int { + return len(h) +} + +func (h HostPriorityList) Less(i, j int) bool { + if h[i].Score == h[j].Score { + return h[i].Host < h[j].Host + } + return h[i].Score < h[j].Score +} + +func (h HostPriorityList) Swap(i, j int) { + h[i], h[j] = h[j], h[i] +} diff --git a/pkg/scheduler/api/v1/zz_generated.deepcopy.go b/pkg/scheduler/api/v1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..dab87e7e681 --- /dev/null +++ b/pkg/scheduler/api/v1/zz_generated.deepcopy.go @@ -0,0 +1,423 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +package v1 + +import ( + core_v1 "k8s.io/api/core/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + rest "k8s.io/client-go/rest" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtenderArgs) DeepCopyInto(out *ExtenderArgs) { + *out = *in + in.Pod.DeepCopyInto(&out.Pod) + if in.Nodes != nil { + in, out := &in.Nodes, &out.Nodes + if *in == nil { + *out = nil + } else { + *out = new(core_v1.NodeList) + (*in).DeepCopyInto(*out) + } + } + if in.NodeNames != nil { + in, out := &in.NodeNames, &out.NodeNames + if *in == nil { + *out = nil + } else { + *out = new([]string) + if **in != nil { + in, out := *in, *out + *out = make([]string, len(*in)) + copy(*out, *in) + } + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtenderArgs. +func (in *ExtenderArgs) DeepCopy() *ExtenderArgs { + if in == nil { + return nil + } + out := new(ExtenderArgs) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtenderBindingArgs) DeepCopyInto(out *ExtenderBindingArgs) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtenderBindingArgs. +func (in *ExtenderBindingArgs) DeepCopy() *ExtenderBindingArgs { + if in == nil { + return nil + } + out := new(ExtenderBindingArgs) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtenderBindingResult) DeepCopyInto(out *ExtenderBindingResult) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtenderBindingResult. +func (in *ExtenderBindingResult) DeepCopy() *ExtenderBindingResult { + if in == nil { + return nil + } + out := new(ExtenderBindingResult) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtenderConfig) DeepCopyInto(out *ExtenderConfig) { + *out = *in + if in.TLSConfig != nil { + in, out := &in.TLSConfig, &out.TLSConfig + if *in == nil { + *out = nil + } else { + *out = new(rest.TLSClientConfig) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtenderConfig. +func (in *ExtenderConfig) DeepCopy() *ExtenderConfig { + if in == nil { + return nil + } + out := new(ExtenderConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtenderFilterResult) DeepCopyInto(out *ExtenderFilterResult) { + *out = *in + if in.Nodes != nil { + in, out := &in.Nodes, &out.Nodes + if *in == nil { + *out = nil + } else { + *out = new(core_v1.NodeList) + (*in).DeepCopyInto(*out) + } + } + if in.NodeNames != nil { + in, out := &in.NodeNames, &out.NodeNames + if *in == nil { + *out = nil + } else { + *out = new([]string) + if **in != nil { + in, out := *in, *out + *out = make([]string, len(*in)) + copy(*out, *in) + } + } + } + if in.FailedNodes != nil { + in, out := &in.FailedNodes, &out.FailedNodes + *out = make(FailedNodesMap, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtenderFilterResult. +func (in *ExtenderFilterResult) DeepCopy() *ExtenderFilterResult { + if in == nil { + return nil + } + out := new(ExtenderFilterResult) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HostPriority) DeepCopyInto(out *HostPriority) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostPriority. +func (in *HostPriority) DeepCopy() *HostPriority { + if in == nil { + return nil + } + out := new(HostPriority) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LabelPreference) DeepCopyInto(out *LabelPreference) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LabelPreference. +func (in *LabelPreference) DeepCopy() *LabelPreference { + if in == nil { + return nil + } + out := new(LabelPreference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LabelsPresence) DeepCopyInto(out *LabelsPresence) { + *out = *in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LabelsPresence. +func (in *LabelsPresence) DeepCopy() *LabelsPresence { + if in == nil { + return nil + } + out := new(LabelsPresence) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Policy) DeepCopyInto(out *Policy) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Predicates != nil { + in, out := &in.Predicates, &out.Predicates + *out = make([]PredicatePolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Priorities != nil { + in, out := &in.Priorities, &out.Priorities + *out = make([]PriorityPolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ExtenderConfigs != nil { + in, out := &in.ExtenderConfigs, &out.ExtenderConfigs + *out = make([]ExtenderConfig, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Policy. +func (in *Policy) DeepCopy() *Policy { + if in == nil { + return nil + } + out := new(Policy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Policy) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PredicateArgument) DeepCopyInto(out *PredicateArgument) { + *out = *in + if in.ServiceAffinity != nil { + in, out := &in.ServiceAffinity, &out.ServiceAffinity + if *in == nil { + *out = nil + } else { + *out = new(ServiceAffinity) + (*in).DeepCopyInto(*out) + } + } + if in.LabelsPresence != nil { + in, out := &in.LabelsPresence, &out.LabelsPresence + if *in == nil { + *out = nil + } else { + *out = new(LabelsPresence) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PredicateArgument. +func (in *PredicateArgument) DeepCopy() *PredicateArgument { + if in == nil { + return nil + } + out := new(PredicateArgument) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PredicatePolicy) DeepCopyInto(out *PredicatePolicy) { + *out = *in + if in.Argument != nil { + in, out := &in.Argument, &out.Argument + if *in == nil { + *out = nil + } else { + *out = new(PredicateArgument) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PredicatePolicy. +func (in *PredicatePolicy) DeepCopy() *PredicatePolicy { + if in == nil { + return nil + } + out := new(PredicatePolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PriorityArgument) DeepCopyInto(out *PriorityArgument) { + *out = *in + if in.ServiceAntiAffinity != nil { + in, out := &in.ServiceAntiAffinity, &out.ServiceAntiAffinity + if *in == nil { + *out = nil + } else { + *out = new(ServiceAntiAffinity) + **out = **in + } + } + if in.LabelPreference != nil { + in, out := &in.LabelPreference, &out.LabelPreference + if *in == nil { + *out = nil + } else { + *out = new(LabelPreference) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PriorityArgument. +func (in *PriorityArgument) DeepCopy() *PriorityArgument { + if in == nil { + return nil + } + out := new(PriorityArgument) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PriorityPolicy) DeepCopyInto(out *PriorityPolicy) { + *out = *in + if in.Argument != nil { + in, out := &in.Argument, &out.Argument + if *in == nil { + *out = nil + } else { + *out = new(PriorityArgument) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PriorityPolicy. +func (in *PriorityPolicy) DeepCopy() *PriorityPolicy { + if in == nil { + return nil + } + out := new(PriorityPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceAffinity) DeepCopyInto(out *ServiceAffinity) { + *out = *in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAffinity. +func (in *ServiceAffinity) DeepCopy() *ServiceAffinity { + if in == nil { + return nil + } + out := new(ServiceAffinity) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceAntiAffinity) DeepCopyInto(out *ServiceAntiAffinity) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAntiAffinity. +func (in *ServiceAntiAffinity) DeepCopy() *ServiceAntiAffinity { + if in == nil { + return nil + } + out := new(ServiceAntiAffinity) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/scheduler/api/validation/BUILD b/pkg/scheduler/api/validation/BUILD new file mode 100644 index 00000000000..eaffc9a1a8b --- /dev/null +++ b/pkg/scheduler/api/validation/BUILD @@ -0,0 +1,38 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_library( + name = "go_default_library", + srcs = ["validation.go"], + importpath = "k8s.io/kubernetes/pkg/scheduler/api/validation", + deps = [ + "//pkg/scheduler/api:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["validation_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/scheduler/api/validation", + deps = ["//pkg/scheduler/api:go_default_library"], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/pkg/scheduler/api/validation/validation.go b/pkg/scheduler/api/validation/validation.go new file mode 100644 index 00000000000..d8eb954c5fc --- /dev/null +++ b/pkg/scheduler/api/validation/validation.go @@ -0,0 +1,50 @@ +/* +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 validation + +import ( + "fmt" + + utilerrors "k8s.io/apimachinery/pkg/util/errors" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" +) + +// ValidatePolicy checks for errors in the Config +// It does not return early so that it can find as many errors as possible +func ValidatePolicy(policy schedulerapi.Policy) error { + var validationErrors []error + + for _, priority := range policy.Priorities { + if priority.Weight <= 0 || priority.Weight >= schedulerapi.MaxWeight { + validationErrors = append(validationErrors, fmt.Errorf("Priority %s should have a positive weight applied to it or it has overflown", priority.Name)) + } + } + + binders := 0 + for _, extender := range policy.ExtenderConfigs { + if len(extender.PrioritizeVerb) > 0 && extender.Weight <= 0 { + validationErrors = append(validationErrors, fmt.Errorf("Priority for extender %s should have a positive weight applied to it", extender.URLPrefix)) + } + if extender.BindVerb != "" { + binders++ + } + } + if binders > 1 { + validationErrors = append(validationErrors, fmt.Errorf("Only one extender can implement bind, found %v", binders)) + } + return utilerrors.NewAggregate(validationErrors) +} diff --git a/pkg/scheduler/api/validation/validation_test.go b/pkg/scheduler/api/validation/validation_test.go new file mode 100644 index 00000000000..482bf92f665 --- /dev/null +++ b/pkg/scheduler/api/validation/validation_test.go @@ -0,0 +1,80 @@ +/* +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 validation + +import ( + "errors" + "fmt" + "testing" + + "k8s.io/kubernetes/pkg/scheduler/api" +) + +func TestValidatePolicy(t *testing.T) { + tests := []struct { + policy api.Policy + expected error + }{ + { + policy: api.Policy{Priorities: []api.PriorityPolicy{{Name: "NoWeightPriority"}}}, + expected: errors.New("Priority NoWeightPriority should have a positive weight applied to it or it has overflown"), + }, + { + policy: api.Policy{Priorities: []api.PriorityPolicy{{Name: "NoWeightPriority", Weight: 0}}}, + expected: errors.New("Priority NoWeightPriority should have a positive weight applied to it or it has overflown"), + }, + { + policy: api.Policy{Priorities: []api.PriorityPolicy{{Name: "WeightPriority", Weight: 2}}}, + expected: nil, + }, + { + policy: api.Policy{Priorities: []api.PriorityPolicy{{Name: "WeightPriority", Weight: -2}}}, + expected: errors.New("Priority WeightPriority should have a positive weight applied to it or it has overflown"), + }, + { + policy: api.Policy{Priorities: []api.PriorityPolicy{{Name: "WeightPriority", Weight: api.MaxWeight}}}, + expected: errors.New("Priority WeightPriority should have a positive weight applied to it or it has overflown"), + }, + { + policy: api.Policy{ExtenderConfigs: []api.ExtenderConfig{{URLPrefix: "http://127.0.0.1:8081/extender", PrioritizeVerb: "prioritize", Weight: 2}}}, + expected: nil, + }, + { + policy: api.Policy{ExtenderConfigs: []api.ExtenderConfig{{URLPrefix: "http://127.0.0.1:8081/extender", PrioritizeVerb: "prioritize", Weight: -2}}}, + expected: errors.New("Priority for extender http://127.0.0.1:8081/extender should have a positive weight applied to it"), + }, + { + policy: api.Policy{ExtenderConfigs: []api.ExtenderConfig{{URLPrefix: "http://127.0.0.1:8081/extender", FilterVerb: "filter"}}}, + expected: nil, + }, + { + policy: api.Policy{ + ExtenderConfigs: []api.ExtenderConfig{ + {URLPrefix: "http://127.0.0.1:8081/extender", BindVerb: "bind"}, + {URLPrefix: "http://127.0.0.1:8082/extender", BindVerb: "bind"}, + }}, + expected: errors.New("Only one extender can implement bind, found 2"), + }, + } + + for _, test := range tests { + actual := ValidatePolicy(test.policy) + if fmt.Sprint(test.expected) != fmt.Sprint(actual) { + t.Errorf("expected: %s, actual: %s", test.expected, actual) + } + } +} diff --git a/pkg/scheduler/api/zz_generated.deepcopy.go b/pkg/scheduler/api/zz_generated.deepcopy.go new file mode 100644 index 00000000000..ad8c0c23743 --- /dev/null +++ b/pkg/scheduler/api/zz_generated.deepcopy.go @@ -0,0 +1,423 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +package api + +import ( + v1 "k8s.io/api/core/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + rest "k8s.io/client-go/rest" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtenderArgs) DeepCopyInto(out *ExtenderArgs) { + *out = *in + in.Pod.DeepCopyInto(&out.Pod) + if in.Nodes != nil { + in, out := &in.Nodes, &out.Nodes + if *in == nil { + *out = nil + } else { + *out = new(v1.NodeList) + (*in).DeepCopyInto(*out) + } + } + if in.NodeNames != nil { + in, out := &in.NodeNames, &out.NodeNames + if *in == nil { + *out = nil + } else { + *out = new([]string) + if **in != nil { + in, out := *in, *out + *out = make([]string, len(*in)) + copy(*out, *in) + } + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtenderArgs. +func (in *ExtenderArgs) DeepCopy() *ExtenderArgs { + if in == nil { + return nil + } + out := new(ExtenderArgs) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtenderBindingArgs) DeepCopyInto(out *ExtenderBindingArgs) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtenderBindingArgs. +func (in *ExtenderBindingArgs) DeepCopy() *ExtenderBindingArgs { + if in == nil { + return nil + } + out := new(ExtenderBindingArgs) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtenderBindingResult) DeepCopyInto(out *ExtenderBindingResult) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtenderBindingResult. +func (in *ExtenderBindingResult) DeepCopy() *ExtenderBindingResult { + if in == nil { + return nil + } + out := new(ExtenderBindingResult) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtenderConfig) DeepCopyInto(out *ExtenderConfig) { + *out = *in + if in.TLSConfig != nil { + in, out := &in.TLSConfig, &out.TLSConfig + if *in == nil { + *out = nil + } else { + *out = new(rest.TLSClientConfig) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtenderConfig. +func (in *ExtenderConfig) DeepCopy() *ExtenderConfig { + if in == nil { + return nil + } + out := new(ExtenderConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtenderFilterResult) DeepCopyInto(out *ExtenderFilterResult) { + *out = *in + if in.Nodes != nil { + in, out := &in.Nodes, &out.Nodes + if *in == nil { + *out = nil + } else { + *out = new(v1.NodeList) + (*in).DeepCopyInto(*out) + } + } + if in.NodeNames != nil { + in, out := &in.NodeNames, &out.NodeNames + if *in == nil { + *out = nil + } else { + *out = new([]string) + if **in != nil { + in, out := *in, *out + *out = make([]string, len(*in)) + copy(*out, *in) + } + } + } + if in.FailedNodes != nil { + in, out := &in.FailedNodes, &out.FailedNodes + *out = make(FailedNodesMap, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtenderFilterResult. +func (in *ExtenderFilterResult) DeepCopy() *ExtenderFilterResult { + if in == nil { + return nil + } + out := new(ExtenderFilterResult) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HostPriority) DeepCopyInto(out *HostPriority) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostPriority. +func (in *HostPriority) DeepCopy() *HostPriority { + if in == nil { + return nil + } + out := new(HostPriority) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LabelPreference) DeepCopyInto(out *LabelPreference) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LabelPreference. +func (in *LabelPreference) DeepCopy() *LabelPreference { + if in == nil { + return nil + } + out := new(LabelPreference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LabelsPresence) DeepCopyInto(out *LabelsPresence) { + *out = *in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LabelsPresence. +func (in *LabelsPresence) DeepCopy() *LabelsPresence { + if in == nil { + return nil + } + out := new(LabelsPresence) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Policy) DeepCopyInto(out *Policy) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Predicates != nil { + in, out := &in.Predicates, &out.Predicates + *out = make([]PredicatePolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Priorities != nil { + in, out := &in.Priorities, &out.Priorities + *out = make([]PriorityPolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ExtenderConfigs != nil { + in, out := &in.ExtenderConfigs, &out.ExtenderConfigs + *out = make([]ExtenderConfig, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Policy. +func (in *Policy) DeepCopy() *Policy { + if in == nil { + return nil + } + out := new(Policy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Policy) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PredicateArgument) DeepCopyInto(out *PredicateArgument) { + *out = *in + if in.ServiceAffinity != nil { + in, out := &in.ServiceAffinity, &out.ServiceAffinity + if *in == nil { + *out = nil + } else { + *out = new(ServiceAffinity) + (*in).DeepCopyInto(*out) + } + } + if in.LabelsPresence != nil { + in, out := &in.LabelsPresence, &out.LabelsPresence + if *in == nil { + *out = nil + } else { + *out = new(LabelsPresence) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PredicateArgument. +func (in *PredicateArgument) DeepCopy() *PredicateArgument { + if in == nil { + return nil + } + out := new(PredicateArgument) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PredicatePolicy) DeepCopyInto(out *PredicatePolicy) { + *out = *in + if in.Argument != nil { + in, out := &in.Argument, &out.Argument + if *in == nil { + *out = nil + } else { + *out = new(PredicateArgument) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PredicatePolicy. +func (in *PredicatePolicy) DeepCopy() *PredicatePolicy { + if in == nil { + return nil + } + out := new(PredicatePolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PriorityArgument) DeepCopyInto(out *PriorityArgument) { + *out = *in + if in.ServiceAntiAffinity != nil { + in, out := &in.ServiceAntiAffinity, &out.ServiceAntiAffinity + if *in == nil { + *out = nil + } else { + *out = new(ServiceAntiAffinity) + **out = **in + } + } + if in.LabelPreference != nil { + in, out := &in.LabelPreference, &out.LabelPreference + if *in == nil { + *out = nil + } else { + *out = new(LabelPreference) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PriorityArgument. +func (in *PriorityArgument) DeepCopy() *PriorityArgument { + if in == nil { + return nil + } + out := new(PriorityArgument) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PriorityPolicy) DeepCopyInto(out *PriorityPolicy) { + *out = *in + if in.Argument != nil { + in, out := &in.Argument, &out.Argument + if *in == nil { + *out = nil + } else { + *out = new(PriorityArgument) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PriorityPolicy. +func (in *PriorityPolicy) DeepCopy() *PriorityPolicy { + if in == nil { + return nil + } + out := new(PriorityPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceAffinity) DeepCopyInto(out *ServiceAffinity) { + *out = *in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAffinity. +func (in *ServiceAffinity) DeepCopy() *ServiceAffinity { + if in == nil { + return nil + } + out := new(ServiceAffinity) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceAntiAffinity) DeepCopyInto(out *ServiceAntiAffinity) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAntiAffinity. +func (in *ServiceAntiAffinity) DeepCopy() *ServiceAntiAffinity { + if in == nil { + return nil + } + out := new(ServiceAntiAffinity) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/scheduler/core/BUILD b/pkg/scheduler/core/BUILD new file mode 100644 index 00000000000..6b652164e8c --- /dev/null +++ b/pkg/scheduler/core/BUILD @@ -0,0 +1,85 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_test( + name = "go_default_test", + srcs = [ + "equivalence_cache_test.go", + "extender_test.go", + "generic_scheduler_test.go", + "scheduling_queue_test.go", + ], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/scheduler/core", + deps = [ + "//pkg/scheduler/algorithm:go_default_library", + "//pkg/scheduler/algorithm/predicates:go_default_library", + "//pkg/scheduler/algorithm/priorities:go_default_library", + "//pkg/scheduler/algorithm/priorities/util:go_default_library", + "//pkg/scheduler/api:go_default_library", + "//pkg/scheduler/schedulercache:go_default_library", + "//pkg/scheduler/testing:go_default_library", + "//pkg/scheduler/util:go_default_library", + "//vendor/k8s.io/api/apps/v1beta1:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + ], +) + +go_library( + name = "go_default_library", + srcs = [ + "equivalence_cache.go", + "extender.go", + "generic_scheduler.go", + "scheduling_queue.go", + ], + importpath = "k8s.io/kubernetes/pkg/scheduler/core", + deps = [ + "//pkg/api/v1/pod:go_default_library", + "//pkg/scheduler/algorithm:go_default_library", + "//pkg/scheduler/algorithm/predicates:go_default_library", + "//pkg/scheduler/algorithm/priorities/util:go_default_library", + "//pkg/scheduler/api:go_default_library", + "//pkg/scheduler/schedulercache:go_default_library", + "//pkg/scheduler/util:go_default_library", + "//pkg/scheduler/volumebinder:go_default_library", + "//pkg/util/hash:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/github.com/golang/groupcache/lru:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/policy/v1beta1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/trace:go_default_library", + "//vendor/k8s.io/client-go/listers/core/v1:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + "//vendor/k8s.io/client-go/tools/cache:go_default_library", + "//vendor/k8s.io/client-go/util/workqueue:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/plugin/pkg/scheduler/core/equivalence_cache.go b/pkg/scheduler/core/equivalence_cache.go similarity index 91% rename from plugin/pkg/scheduler/core/equivalence_cache.go rename to pkg/scheduler/core/equivalence_cache.go index 9977fe18d30..5d9bda7eafe 100644 --- a/plugin/pkg/scheduler/core/equivalence_cache.go +++ b/pkg/scheduler/core/equivalence_cache.go @@ -22,8 +22,8 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/kubernetes/pkg/scheduler/algorithm" hashutil "k8s.io/kubernetes/pkg/util/hash" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" "github.com/golang/glog" "github.com/golang/groupcache/lru" @@ -173,7 +173,7 @@ func (ec *EquivalenceCache) InvalidateAllCachedPredicateItemOfNode(nodeName stri // InvalidateCachedPredicateItemForPodAdd is a wrapper of InvalidateCachedPredicateItem for pod add case func (ec *EquivalenceCache) InvalidateCachedPredicateItemForPodAdd(pod *v1.Pod, nodeName string) { - // MatchInterPodAffinity: we assume scheduler can make sure newly binded pod + // MatchInterPodAffinity: we assume scheduler can make sure newly bound pod // will not break the existing inter pod affinity. So we does not need to invalidate // MatchInterPodAffinity when pod added. // @@ -188,12 +188,29 @@ func (ec *EquivalenceCache) InvalidateCachedPredicateItemForPodAdd(pod *v1.Pod, // GeneralPredicates: will always be affected by adding a new pod invalidPredicates := sets.NewString("GeneralPredicates") + + // MaxPDVolumeCountPredicate: we check the volumes of pod to make decision. + for _, vol := range pod.Spec.Volumes { + if vol.PersistentVolumeClaim != nil { + invalidPredicates.Insert("MaxEBSVolumeCount", "MaxGCEPDVolumeCount", "MaxAzureDiskVolumeCount") + } else { + if vol.AWSElasticBlockStore != nil { + invalidPredicates.Insert("MaxEBSVolumeCount") + } + if vol.GCEPersistentDisk != nil { + invalidPredicates.Insert("MaxGCEPDVolumeCount") + } + if vol.AzureDisk != nil { + invalidPredicates.Insert("MaxAzureDiskVolumeCount") + } + } + } ec.InvalidateCachedPredicateItem(nodeName, invalidPredicates) } // getHashEquivalencePod returns the hash of equivalence pod. // 1. equivalenceHash -// 2. if equivalence pod is found +// 2. if equivalence hash is valid func (ec *EquivalenceCache) getHashEquivalencePod(pod *v1.Pod) (uint64, bool) { equivalencePod := ec.getEquivalencePod(pod) if equivalencePod != nil { diff --git a/pkg/scheduler/core/equivalence_cache_test.go b/pkg/scheduler/core/equivalence_cache_test.go new file mode 100644 index 00000000000..54b903e2fbd --- /dev/null +++ b/pkg/scheduler/core/equivalence_cache_test.go @@ -0,0 +1,643 @@ +/* +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 core + +import ( + "reflect" + "testing" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + "k8s.io/kubernetes/pkg/scheduler/algorithm/predicates" +) + +type predicateItemType struct { + fit bool + reasons []algorithm.PredicateFailureReason +} + +func TestUpdateCachedPredicateItem(t *testing.T) { + tests := []struct { + name string + pod string + predicateKey string + nodeName string + fit bool + reasons []algorithm.PredicateFailureReason + equivalenceHash uint64 + expectPredicateMap bool + expectCacheItem HostPredicate + }{ + { + name: "test 1", + pod: "testPod", + predicateKey: "GeneralPredicates", + nodeName: "node1", + fit: true, + equivalenceHash: 123, + expectPredicateMap: false, + expectCacheItem: HostPredicate{ + Fit: true, + }, + }, + { + name: "test 2", + pod: "testPod", + predicateKey: "GeneralPredicates", + nodeName: "node2", + fit: false, + equivalenceHash: 123, + expectPredicateMap: true, + expectCacheItem: HostPredicate{ + Fit: false, + }, + }, + } + for _, test := range tests { + // this case does not need to calculate equivalence hash, just pass an empty function + fakeGetEquivalencePodFunc := func(pod *v1.Pod) interface{} { return nil } + ecache := NewEquivalenceCache(fakeGetEquivalencePodFunc) + if test.expectPredicateMap { + ecache.algorithmCache[test.nodeName] = newAlgorithmCache() + predicateItem := HostPredicate{ + Fit: true, + } + ecache.algorithmCache[test.nodeName].predicatesCache.Add(test.predicateKey, + PredicateMap{ + test.equivalenceHash: predicateItem, + }) + } + ecache.UpdateCachedPredicateItem( + test.pod, + test.nodeName, + test.predicateKey, + test.fit, + test.reasons, + test.equivalenceHash, + ) + + value, ok := ecache.algorithmCache[test.nodeName].predicatesCache.Get(test.predicateKey) + if !ok { + t.Errorf("Failed: %s, can't find expected cache item: %v", + test.name, test.expectCacheItem) + } else { + cachedMapItem := value.(PredicateMap) + if !reflect.DeepEqual(cachedMapItem[test.equivalenceHash], test.expectCacheItem) { + t.Errorf("Failed: %s, expected cached item: %v, but got: %v", + test.name, test.expectCacheItem, cachedMapItem[test.equivalenceHash]) + } + } + } +} + +func TestPredicateWithECache(t *testing.T) { + tests := []struct { + name string + podName string + nodeName string + predicateKey string + equivalenceHashForUpdatePredicate uint64 + equivalenceHashForCalPredicate uint64 + cachedItem predicateItemType + expectedInvalidPredicateKey bool + expectedInvalidEquivalenceHash bool + expectedPredicateItem predicateItemType + }{ + { + name: "test 1", + podName: "testPod", + nodeName: "node1", + equivalenceHashForUpdatePredicate: 123, + equivalenceHashForCalPredicate: 123, + predicateKey: "GeneralPredicates", + cachedItem: predicateItemType{ + fit: false, + reasons: []algorithm.PredicateFailureReason{predicates.ErrPodNotFitsHostPorts}, + }, + expectedInvalidPredicateKey: true, + expectedPredicateItem: predicateItemType{ + fit: false, + reasons: []algorithm.PredicateFailureReason{}, + }, + }, + { + name: "test 2", + podName: "testPod", + nodeName: "node2", + equivalenceHashForUpdatePredicate: 123, + equivalenceHashForCalPredicate: 123, + predicateKey: "GeneralPredicates", + cachedItem: predicateItemType{ + fit: true, + }, + expectedInvalidPredicateKey: false, + expectedPredicateItem: predicateItemType{ + fit: true, + reasons: []algorithm.PredicateFailureReason{}, + }, + }, + { + name: "test 3", + podName: "testPod", + nodeName: "node3", + equivalenceHashForUpdatePredicate: 123, + equivalenceHashForCalPredicate: 123, + predicateKey: "GeneralPredicates", + cachedItem: predicateItemType{ + fit: false, + reasons: []algorithm.PredicateFailureReason{predicates.ErrPodNotFitsHostPorts}, + }, + expectedInvalidPredicateKey: false, + expectedPredicateItem: predicateItemType{ + fit: false, + reasons: []algorithm.PredicateFailureReason{predicates.ErrPodNotFitsHostPorts}, + }, + }, + { + name: "test 4", + podName: "testPod", + nodeName: "node4", + equivalenceHashForUpdatePredicate: 123, + equivalenceHashForCalPredicate: 456, + predicateKey: "GeneralPredicates", + cachedItem: predicateItemType{ + fit: false, + reasons: []algorithm.PredicateFailureReason{predicates.ErrPodNotFitsHostPorts}, + }, + expectedInvalidPredicateKey: false, + expectedInvalidEquivalenceHash: true, + expectedPredicateItem: predicateItemType{ + fit: false, + reasons: []algorithm.PredicateFailureReason{}, + }, + }, + } + + for _, test := range tests { + // this case does not need to calculate equivalence hash, just pass an empty function + fakeGetEquivalencePodFunc := func(pod *v1.Pod) interface{} { return nil } + ecache := NewEquivalenceCache(fakeGetEquivalencePodFunc) + // set cached item to equivalence cache + ecache.UpdateCachedPredicateItem( + test.podName, + test.nodeName, + test.predicateKey, + test.cachedItem.fit, + test.cachedItem.reasons, + test.equivalenceHashForUpdatePredicate, + ) + // if we want to do invalid, invalid the cached item + if test.expectedInvalidPredicateKey { + predicateKeys := sets.NewString() + predicateKeys.Insert(test.predicateKey) + ecache.InvalidateCachedPredicateItem(test.nodeName, predicateKeys) + } + // calculate predicate with equivalence cache + fit, reasons, invalid := ecache.PredicateWithECache(test.podName, + test.nodeName, + test.predicateKey, + test.equivalenceHashForCalPredicate, + ) + // returned invalid should match expectedInvalidPredicateKey or expectedInvalidEquivalenceHash + if test.equivalenceHashForUpdatePredicate != test.equivalenceHashForCalPredicate { + if invalid != test.expectedInvalidEquivalenceHash { + t.Errorf("Failed: %s, expected invalid: %v, but got: %v", + test.name, test.expectedInvalidEquivalenceHash, invalid) + } + } else { + if invalid != test.expectedInvalidPredicateKey { + t.Errorf("Failed: %s, expected invalid: %v, but got: %v", + test.name, test.expectedInvalidPredicateKey, invalid) + } + } + // returned predicate result should match expected predicate item + if fit != test.expectedPredicateItem.fit { + t.Errorf("Failed: %s, expected fit: %v, but got: %v", test.name, test.cachedItem.fit, fit) + } + if !reflect.DeepEqual(reasons, test.expectedPredicateItem.reasons) { + t.Errorf("Failed: %s, expected reasons: %v, but got: %v", + test.name, test.cachedItem.reasons, reasons) + } + } +} + +func TestGetHashEquivalencePod(t *testing.T) { + + testNamespace := "test" + + pvcInfo := predicates.FakePersistentVolumeClaimInfo{ + { + ObjectMeta: metav1.ObjectMeta{UID: "someEBSVol1", Name: "someEBSVol1", Namespace: testNamespace}, + Spec: v1.PersistentVolumeClaimSpec{VolumeName: "someEBSVol1"}, + }, + { + ObjectMeta: metav1.ObjectMeta{UID: "someEBSVol2", Name: "someEBSVol2", Namespace: testNamespace}, + Spec: v1.PersistentVolumeClaimSpec{VolumeName: "someNonEBSVol"}, + }, + { + ObjectMeta: metav1.ObjectMeta{UID: "someEBSVol3-0", Name: "someEBSVol3-0", Namespace: testNamespace}, + Spec: v1.PersistentVolumeClaimSpec{VolumeName: "pvcWithDeletedPV"}, + }, + { + ObjectMeta: metav1.ObjectMeta{UID: "someEBSVol3-1", Name: "someEBSVol3-1", Namespace: testNamespace}, + Spec: v1.PersistentVolumeClaimSpec{VolumeName: "anotherPVCWithDeletedPV"}, + }, + } + + // use default equivalence class generator + ecache := NewEquivalenceCache(predicates.NewEquivalencePodGenerator(pvcInfo)) + + isController := true + + pod1 := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Namespace: testNamespace, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "ReplicationController", + Name: "rc", + UID: "123", + Controller: &isController, + }, + }, + }, + Spec: v1.PodSpec{ + Volumes: []v1.Volume{ + { + VolumeSource: v1.VolumeSource{ + PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ + ClaimName: "someEBSVol1", + }, + }, + }, + { + VolumeSource: v1.VolumeSource{ + PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ + ClaimName: "someEBSVol2", + }, + }, + }, + }, + }, + } + + pod2 := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod2", + Namespace: testNamespace, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "ReplicationController", + Name: "rc", + UID: "123", + Controller: &isController, + }, + }, + }, + Spec: v1.PodSpec{ + Volumes: []v1.Volume{ + { + VolumeSource: v1.VolumeSource{ + PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ + ClaimName: "someEBSVol2", + }, + }, + }, + { + VolumeSource: v1.VolumeSource{ + PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ + ClaimName: "someEBSVol1", + }, + }, + }, + }, + }, + } + + pod3 := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod3", + Namespace: testNamespace, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "ReplicationController", + Name: "rc", + UID: "567", + Controller: &isController, + }, + }, + }, + Spec: v1.PodSpec{ + Volumes: []v1.Volume{ + { + VolumeSource: v1.VolumeSource{ + PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ + ClaimName: "someEBSVol3-1", + }, + }, + }, + }, + }, + } + + pod4 := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod4", + Namespace: testNamespace, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "ReplicationController", + Name: "rc", + UID: "567", + Controller: &isController, + }, + }, + }, + Spec: v1.PodSpec{ + Volumes: []v1.Volume{ + { + VolumeSource: v1.VolumeSource{ + PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ + ClaimName: "someEBSVol3-0", + }, + }, + }, + }, + }, + } + + pod5 := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod5", + Namespace: testNamespace, + }, + } + + pod6 := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod6", + Namespace: testNamespace, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "ReplicationController", + Name: "rc", + UID: "567", + Controller: &isController, + }, + }, + }, + Spec: v1.PodSpec{ + Volumes: []v1.Volume{ + { + VolumeSource: v1.VolumeSource{ + PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ + ClaimName: "no-exists-pvc", + }, + }, + }, + }, + }, + } + + pod7 := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod7", + Namespace: testNamespace, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "ReplicationController", + Name: "rc", + UID: "567", + Controller: &isController, + }, + }, + }, + } + + type podInfo struct { + pod *v1.Pod + hashIsValid bool + } + + tests := []struct { + podInfoList []podInfo + isEquivalent bool + }{ + // pods with same controllerRef and same pvc claim + { + podInfoList: []podInfo{ + {pod: pod1, hashIsValid: true}, + {pod: pod2, hashIsValid: true}, + }, + isEquivalent: true, + }, + // pods with same controllerRef but different pvc claim + { + podInfoList: []podInfo{ + {pod: pod3, hashIsValid: true}, + {pod: pod4, hashIsValid: true}, + }, + isEquivalent: false, + }, + // pod without controllerRef + { + podInfoList: []podInfo{ + {pod: pod5, hashIsValid: false}, + }, + isEquivalent: false, + }, + // pods with same controllerRef but one has non-exists pvc claim + { + podInfoList: []podInfo{ + {pod: pod6, hashIsValid: false}, + {pod: pod7, hashIsValid: true}, + }, + isEquivalent: false, + }, + } + + var ( + targetPodInfo podInfo + targetHash uint64 + ) + + for _, test := range tests { + for i, podInfo := range test.podInfoList { + testPod := podInfo.pod + hash, isValid := ecache.getHashEquivalencePod(testPod) + if isValid != podInfo.hashIsValid { + t.Errorf("Failed: pod %v is expected to have valid hash", testPod) + } + // NOTE(harry): the first element will be used as target so + // this logic can't verify more than two inequivalent pods + if i == 0 { + targetHash = hash + targetPodInfo = podInfo + } else { + if targetHash != hash { + if test.isEquivalent { + t.Errorf("Failed: pod: %v is expected to be equivalent to: %v", testPod, targetPodInfo.pod) + } + } + } + } + } +} + +func TestInvalidateCachedPredicateItemOfAllNodes(t *testing.T) { + testPredicate := "GeneralPredicates" + // tests is used to initialize all nodes + tests := []struct { + podName string + nodeName string + predicateKey string + equivalenceHashForUpdatePredicate uint64 + cachedItem predicateItemType + }{ + { + podName: "testPod", + nodeName: "node1", + equivalenceHashForUpdatePredicate: 123, + cachedItem: predicateItemType{ + fit: false, + reasons: []algorithm.PredicateFailureReason{ + predicates.ErrPodNotFitsHostPorts, + }, + }, + }, + { + podName: "testPod", + nodeName: "node2", + equivalenceHashForUpdatePredicate: 456, + cachedItem: predicateItemType{ + fit: false, + reasons: []algorithm.PredicateFailureReason{ + predicates.ErrPodNotFitsHostPorts, + }, + }, + }, + { + podName: "testPod", + nodeName: "node3", + equivalenceHashForUpdatePredicate: 123, + cachedItem: predicateItemType{ + fit: true, + }, + }, + } + // this case does not need to calculate equivalence hash, just pass an empty function + fakeGetEquivalencePodFunc := func(pod *v1.Pod) interface{} { return nil } + ecache := NewEquivalenceCache(fakeGetEquivalencePodFunc) + + for _, test := range tests { + // set cached item to equivalence cache + ecache.UpdateCachedPredicateItem( + test.podName, + test.nodeName, + testPredicate, + test.cachedItem.fit, + test.cachedItem.reasons, + test.equivalenceHashForUpdatePredicate, + ) + } + + // invalidate cached predicate for all nodes + ecache.InvalidateCachedPredicateItemOfAllNodes(sets.NewString(testPredicate)) + + // there should be no cached predicate any more + for _, test := range tests { + if algorithmCache, exist := ecache.algorithmCache[test.nodeName]; exist { + if _, exist := algorithmCache.predicatesCache.Get(testPredicate); exist { + t.Errorf("Failed: cached item for predicate key: %v on node: %v should be invalidated", + testPredicate, test.nodeName) + break + } + } + } +} + +func TestInvalidateAllCachedPredicateItemOfNode(t *testing.T) { + testPredicate := "GeneralPredicates" + // tests is used to initialize all nodes + tests := []struct { + podName string + nodeName string + predicateKey string + equivalenceHashForUpdatePredicate uint64 + cachedItem predicateItemType + }{ + { + podName: "testPod", + nodeName: "node1", + equivalenceHashForUpdatePredicate: 123, + cachedItem: predicateItemType{ + fit: false, + reasons: []algorithm.PredicateFailureReason{predicates.ErrPodNotFitsHostPorts}, + }, + }, + { + podName: "testPod", + nodeName: "node2", + equivalenceHashForUpdatePredicate: 456, + cachedItem: predicateItemType{ + fit: false, + reasons: []algorithm.PredicateFailureReason{predicates.ErrPodNotFitsHostPorts}, + }, + }, + { + podName: "testPod", + nodeName: "node3", + equivalenceHashForUpdatePredicate: 123, + cachedItem: predicateItemType{ + fit: true, + }, + }, + } + // this case does not need to calculate equivalence hash, just pass an empty function + fakeGetEquivalencePodFunc := func(pod *v1.Pod) interface{} { return nil } + ecache := NewEquivalenceCache(fakeGetEquivalencePodFunc) + + for _, test := range tests { + // set cached item to equivalence cache + ecache.UpdateCachedPredicateItem( + test.podName, + test.nodeName, + testPredicate, + test.cachedItem.fit, + test.cachedItem.reasons, + test.equivalenceHashForUpdatePredicate, + ) + } + + for _, test := range tests { + // invalidate cached predicate for all nodes + ecache.InvalidateAllCachedPredicateItemOfNode(test.nodeName) + if _, exist := ecache.algorithmCache[test.nodeName]; exist { + t.Errorf("Failed: cached item for node: %v should be invalidated", test.nodeName) + break + } + } +} diff --git a/plugin/pkg/scheduler/core/extender.go b/pkg/scheduler/core/extender.go similarity index 97% rename from plugin/pkg/scheduler/core/extender.go rename to pkg/scheduler/core/extender.go index 898ef4f4525..0eb1e0def5e 100644 --- a/plugin/pkg/scheduler/core/extender.go +++ b/pkg/scheduler/core/extender.go @@ -27,9 +27,9 @@ import ( "k8s.io/api/core/v1" utilnet "k8s.io/apimachinery/pkg/util/net" restclient "k8s.io/client-go/rest" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" ) const ( diff --git a/plugin/pkg/scheduler/core/extender_test.go b/pkg/scheduler/core/extender_test.go similarity index 95% rename from plugin/pkg/scheduler/core/extender_test.go rename to pkg/scheduler/core/extender_test.go index 8b26ccdfeba..23551a2415c 100644 --- a/plugin/pkg/scheduler/core/extender_test.go +++ b/pkg/scheduler/core/extender_test.go @@ -24,10 +24,10 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" - schedulertesting "k8s.io/kubernetes/plugin/pkg/scheduler/testing" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" + schedulertesting "k8s.io/kubernetes/pkg/scheduler/testing" ) type fitPredicate func(pod *v1.Pod, node *v1.Node) (bool, error) @@ -315,8 +315,9 @@ func TestGenericSchedulerWithExtenders(t *testing.T) { for _, name := range test.nodes { cache.AddNode(&v1.Node{ObjectMeta: metav1.ObjectMeta{Name: name}}) } + queue := NewSchedulingQueue() scheduler := NewGenericScheduler( - cache, nil, test.predicates, algorithm.EmptyPredicateMetadataProducer, test.prioritizers, algorithm.EmptyMetadataProducer, extenders) + cache, nil, queue, test.predicates, algorithm.EmptyPredicateMetadataProducer, test.prioritizers, algorithm.EmptyMetadataProducer, extenders, nil, schedulertesting.FakePersistentVolumeClaimLister{}) podIgnored := &v1.Pod{} machine, err := scheduler.Schedule(podIgnored, schedulertesting.FakeNodeLister(makeNodeList(test.nodes))) if test.expectsErr { diff --git a/pkg/scheduler/core/generic_scheduler.go b/pkg/scheduler/core/generic_scheduler.go new file mode 100644 index 00000000000..e1128c01cb8 --- /dev/null +++ b/pkg/scheduler/core/generic_scheduler.go @@ -0,0 +1,1057 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package core + +import ( + "fmt" + "math" + "sort" + "strings" + "sync" + "sync/atomic" + "time" + + "k8s.io/api/core/v1" + policy "k8s.io/api/policy/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/errors" + utiltrace "k8s.io/apiserver/pkg/util/trace" + corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/util/workqueue" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + "k8s.io/kubernetes/pkg/scheduler/algorithm/predicates" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" + "k8s.io/kubernetes/pkg/scheduler/util" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/scheduler/volumebinder" +) + +type FailedPredicateMap map[string][]algorithm.PredicateFailureReason + +type FitError struct { + Pod *v1.Pod + NumAllNodes int + FailedPredicates FailedPredicateMap +} + +type Victims struct { + pods []*v1.Pod + numPDBViolations int +} + +var ErrNoNodesAvailable = fmt.Errorf("no nodes available to schedule pods") + +const ( + NoNodeAvailableMsg = "0/%v nodes are available" + // NominatedNodeAnnotationKey is used to annotate a pod that has preempted other pods. + // The scheduler uses the annotation to find that the pod shouldn't preempt more pods + // when it gets to the head of scheduling queue again. + // See podEligibleToPreemptOthers() for more information. + NominatedNodeAnnotationKey = "scheduler.kubernetes.io/nominated-node-name" +) + +// Error returns detailed information of why the pod failed to fit on each node +func (f *FitError) Error() string { + reasons := make(map[string]int) + for _, predicates := range f.FailedPredicates { + for _, pred := range predicates { + reasons[pred.GetReason()] += 1 + } + } + + sortReasonsHistogram := func() []string { + reasonStrings := []string{} + for k, v := range reasons { + reasonStrings = append(reasonStrings, fmt.Sprintf("%v %v", v, k)) + } + sort.Strings(reasonStrings) + return reasonStrings + } + reasonMsg := fmt.Sprintf(NoNodeAvailableMsg+": %v.", f.NumAllNodes, strings.Join(sortReasonsHistogram(), ", ")) + return reasonMsg +} + +type genericScheduler struct { + cache schedulercache.Cache + equivalenceCache *EquivalenceCache + schedulingQueue SchedulingQueue + predicates map[string]algorithm.FitPredicate + priorityMetaProducer algorithm.MetadataProducer + predicateMetaProducer algorithm.PredicateMetadataProducer + prioritizers []algorithm.PriorityConfig + extenders []algorithm.SchedulerExtender + lastNodeIndexLock sync.Mutex + lastNodeIndex uint64 + + cachedNodeInfoMap map[string]*schedulercache.NodeInfo + volumeBinder *volumebinder.VolumeBinder + pvcLister corelisters.PersistentVolumeClaimLister +} + +// Schedule tries to schedule the given pod to one of node in the node list. +// If it succeeds, it will return the name of the node. +// If it fails, it will return a Fiterror error with reasons. +func (g *genericScheduler) Schedule(pod *v1.Pod, nodeLister algorithm.NodeLister) (string, error) { + trace := utiltrace.New(fmt.Sprintf("Scheduling %s/%s", pod.Namespace, pod.Name)) + defer trace.LogIfLong(100 * time.Millisecond) + + if err := podPassesBasicChecks(pod, g.pvcLister); err != nil { + return "", err + } + + nodes, err := nodeLister.List() + if err != nil { + return "", err + } + if len(nodes) == 0 { + return "", ErrNoNodesAvailable + } + + // Used for all fit and priority funcs. + err = g.cache.UpdateNodeNameToInfoMap(g.cachedNodeInfoMap) + if err != nil { + return "", err + } + + trace.Step("Computing predicates") + filteredNodes, failedPredicateMap, err := findNodesThatFit(pod, g.cachedNodeInfoMap, nodes, g.predicates, g.extenders, g.predicateMetaProducer, g.equivalenceCache, g.schedulingQueue) + if err != nil { + return "", err + } + + if len(filteredNodes) == 0 { + return "", &FitError{ + Pod: pod, + NumAllNodes: len(nodes), + FailedPredicates: failedPredicateMap, + } + } + + trace.Step("Prioritizing") + + // When only one node after predicate, just use it. + if len(filteredNodes) == 1 { + return filteredNodes[0].Name, nil + } + + metaPrioritiesInterface := g.priorityMetaProducer(pod, g.cachedNodeInfoMap) + priorityList, err := PrioritizeNodes(pod, g.cachedNodeInfoMap, metaPrioritiesInterface, g.prioritizers, filteredNodes, g.extenders) + if err != nil { + return "", err + } + + trace.Step("Selecting host") + return g.selectHost(priorityList) +} + +// Prioritizers returns a slice containing all the scheduler's priority +// functions and their config. It is exposed for testing only. +func (g *genericScheduler) Prioritizers() []algorithm.PriorityConfig { + return g.prioritizers +} + +// Predicates returns a map containing all the scheduler's predicate +// functions. It is exposed for testing only. +func (g *genericScheduler) Predicates() map[string]algorithm.FitPredicate { + return g.predicates +} + +// selectHost takes a prioritized list of nodes and then picks one +// in a round-robin manner from the nodes that had the highest score. +func (g *genericScheduler) selectHost(priorityList schedulerapi.HostPriorityList) (string, error) { + if len(priorityList) == 0 { + return "", fmt.Errorf("empty priorityList") + } + + sort.Sort(sort.Reverse(priorityList)) + maxScore := priorityList[0].Score + firstAfterMaxScore := sort.Search(len(priorityList), func(i int) bool { return priorityList[i].Score < maxScore }) + + g.lastNodeIndexLock.Lock() + ix := int(g.lastNodeIndex % uint64(firstAfterMaxScore)) + g.lastNodeIndex++ + g.lastNodeIndexLock.Unlock() + + return priorityList[ix].Host, nil +} + +// preempt finds nodes with pods that can be preempted to make room for "pod" to +// schedule. It chooses one of the nodes and preempts the pods on the node and +// returns 1) the node, 2) the list of preempted pods if such a node is found, +// 3) A list of pods whose nominated node name should be cleared, and 4) any +// possible error. +func (g *genericScheduler) Preempt(pod *v1.Pod, nodeLister algorithm.NodeLister, scheduleErr error) (*v1.Node, []*v1.Pod, []*v1.Pod, error) { + // Scheduler may return various types of errors. Consider preemption only if + // the error is of type FitError. + fitError, ok := scheduleErr.(*FitError) + if !ok || fitError == nil { + return nil, nil, nil, nil + } + err := g.cache.UpdateNodeNameToInfoMap(g.cachedNodeInfoMap) + if err != nil { + return nil, nil, nil, err + } + if !podEligibleToPreemptOthers(pod, g.cachedNodeInfoMap) { + glog.V(5).Infof("Pod %v is not eligible for more preemption.", pod.Name) + return nil, nil, nil, nil + } + allNodes, err := nodeLister.List() + if err != nil { + return nil, nil, nil, err + } + if len(allNodes) == 0 { + return nil, nil, nil, ErrNoNodesAvailable + } + potentialNodes := nodesWherePreemptionMightHelp(pod, allNodes, fitError.FailedPredicates) + if len(potentialNodes) == 0 { + glog.V(3).Infof("Preemption will not help schedule pod %v on any node.", pod.Name) + // In this case, we should clean-up any existing nominated node name of the pod. + return nil, nil, []*v1.Pod{pod}, nil + } + pdbs, err := g.cache.ListPDBs(labels.Everything()) + if err != nil { + return nil, nil, nil, err + } + nodeToVictims, err := selectNodesForPreemption(pod, g.cachedNodeInfoMap, potentialNodes, g.predicates, g.predicateMetaProducer, g.schedulingQueue, pdbs) + if err != nil { + return nil, nil, nil, err + } + for len(nodeToVictims) > 0 { + node := pickOneNodeForPreemption(nodeToVictims) + if node == nil { + return nil, nil, nil, err + } + passes, pErr := nodePassesExtendersForPreemption(pod, node.Name, nodeToVictims[node].pods, g.cachedNodeInfoMap, g.extenders) + if passes && pErr == nil { + // 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. + nominatedPods := g.getLowerPriorityNominatedPods(pod, node.Name) + return node, nodeToVictims[node].pods, nominatedPods, err + } + if pErr != nil { + glog.Errorf("Error occurred while checking extenders for preemption on node %v: %v", node, pErr) + } + // Remove the node from the map and try to pick a different node. + delete(nodeToVictims, node) + } + return nil, nil, nil, err +} + +// 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 PredicateMeta 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 (g *genericScheduler) getLowerPriorityNominatedPods(pod *v1.Pod, nodeName string) []*v1.Pod { + pods := g.schedulingQueue.WaitingPodsForNode(nodeName) + if len(pods) == 0 { + return nil + } + + var lowerPriorityPods []*v1.Pod + podPriority := util.GetPodPriority(pod) + for _, p := range pods { + if util.GetPodPriority(p) < podPriority { + lowerPriorityPods = append(lowerPriorityPods, p) + } + } + return lowerPriorityPods +} + +// Filters the nodes to find the ones that fit based on the given predicate functions +// Each node is passed through the predicate functions to determine if it is a fit +func findNodesThatFit( + pod *v1.Pod, + nodeNameToInfo map[string]*schedulercache.NodeInfo, + nodes []*v1.Node, + predicateFuncs map[string]algorithm.FitPredicate, + extenders []algorithm.SchedulerExtender, + metadataProducer algorithm.PredicateMetadataProducer, + ecache *EquivalenceCache, + schedulingQueue SchedulingQueue, +) ([]*v1.Node, FailedPredicateMap, error) { + var filtered []*v1.Node + failedPredicateMap := FailedPredicateMap{} + + if len(predicateFuncs) == 0 { + filtered = nodes + } else { + // Create filtered list with enough space to avoid growing it + // and allow assigning. + filtered = make([]*v1.Node, len(nodes)) + errs := errors.MessageCountMap{} + var predicateResultLock sync.Mutex + var filteredLen int32 + + // We can use the same metadata producer for all nodes. + meta := metadataProducer(pod, nodeNameToInfo) + checkNode := func(i int) { + nodeName := nodes[i].Name + fits, failedPredicates, err := podFitsOnNode(pod, meta, nodeNameToInfo[nodeName], predicateFuncs, ecache, schedulingQueue) + if err != nil { + predicateResultLock.Lock() + errs[err.Error()]++ + predicateResultLock.Unlock() + return + } + if fits { + filtered[atomic.AddInt32(&filteredLen, 1)-1] = nodes[i] + } else { + predicateResultLock.Lock() + failedPredicateMap[nodeName] = failedPredicates + predicateResultLock.Unlock() + } + } + workqueue.Parallelize(16, len(nodes), checkNode) + filtered = filtered[:filteredLen] + if len(errs) > 0 { + return []*v1.Node{}, FailedPredicateMap{}, errors.CreateAggregateFromMessageCountMap(errs) + } + } + + if len(filtered) > 0 && len(extenders) != 0 { + for _, extender := range extenders { + filteredList, failedMap, err := extender.Filter(pod, filtered, nodeNameToInfo) + if err != nil { + return []*v1.Node{}, FailedPredicateMap{}, err + } + + for failedNodeName, failedMsg := range failedMap { + if _, found := failedPredicateMap[failedNodeName]; !found { + failedPredicateMap[failedNodeName] = []algorithm.PredicateFailureReason{} + } + failedPredicateMap[failedNodeName] = append(failedPredicateMap[failedNodeName], predicates.NewFailureReason(failedMsg)) + } + filtered = filteredList + if len(filtered) == 0 { + break + } + } + } + return filtered, failedPredicateMap, nil +} + +// addNominatedPods adds pods with equal or greater priority which are nominated +// to run on the node given in nodeInfo to meta and nodeInfo. It returns 1) whether +// any pod was found, 2) augmented meta data, 3) augmented nodeInfo. +func addNominatedPods(podPriority int32, meta algorithm.PredicateMetadata, + nodeInfo *schedulercache.NodeInfo, queue SchedulingQueue) (bool, algorithm.PredicateMetadata, + *schedulercache.NodeInfo) { + if queue == nil || nodeInfo == nil || nodeInfo.Node() == nil { + // This may happen only in tests. + return false, meta, nodeInfo + } + nominatedPods := queue.WaitingPodsForNode(nodeInfo.Node().Name) + if nominatedPods == nil || len(nominatedPods) == 0 { + return false, meta, nodeInfo + } + var metaOut algorithm.PredicateMetadata = nil + if meta != nil { + metaOut = meta.ShallowCopy() + } + nodeInfoOut := nodeInfo.Clone() + for _, p := range nominatedPods { + if util.GetPodPriority(p) >= podPriority { + nodeInfoOut.AddPod(p) + if metaOut != nil { + metaOut.AddPod(p, nodeInfoOut) + } + } + } + return true, metaOut, nodeInfoOut +} + +// podFitsOnNode checks whether a node given by NodeInfo satisfies the given predicate functions. +// This function is called from two different places: Schedule and Preempt. +// When it is called from Schedule, we want to test whether the pod is schedulable +// on the node with all the existing pods on the node plus higher and equal priority +// pods nominated to run on the node. +// When it is called from Preempt, we should remove the victims of preemption and +// add the nominated pods. Removal of the victims is done by SelectVictimsOnNode(). +// It removes victims from meta and NodeInfo before calling this function. +func podFitsOnNode( + pod *v1.Pod, + meta algorithm.PredicateMetadata, + info *schedulercache.NodeInfo, + predicateFuncs map[string]algorithm.FitPredicate, + ecache *EquivalenceCache, + queue SchedulingQueue, +) (bool, []algorithm.PredicateFailureReason, error) { + var ( + equivalenceHash uint64 + failedPredicates []algorithm.PredicateFailureReason + eCacheAvailable bool + invalid bool + fit bool + reasons []algorithm.PredicateFailureReason + err error + ) + predicateResults := make(map[string]HostPredicate) + + if ecache != nil { + // getHashEquivalencePod will return immediately if no equivalence pod found + equivalenceHash, eCacheAvailable = ecache.getHashEquivalencePod(pod) + } + podsAdded := false + // We run predicates twice in some cases. If the node has greater or equal priority + // nominated pods, we run them when those pods are added to meta and nodeInfo. + // If all predicates succeed in this pass, we run them again when these + // nominated pods are not added. This second pass is necessary because some + // predicates such as inter-pod affinity may not pass without the nominated pods. + // If there are no nominated pods for the node or if the first run of the + // predicates fail, we don't run the second pass. + // We consider only equal or higher priority pods in the first pass, because + // those are the current "pod" must yield to them and not take a space opened + // for running them. It is ok if the current "pod" take resources freed for + // lower priority pods. + // Requiring that the new pod is schedulable in both circumstances ensures that + // we are making a conservative decision: predicates like resources and inter-pod + // anti-affinity are more likely to fail when the nominated pods are treated + // as running, while predicates like pod affinity are more likely to fail when + // the nominated pods are treated as not running. We can't just assume the + // nominated pods are running because they are not running right now and in fact, + // they may end up getting scheduled to a different node. + for i := 0; i < 2; i++ { + metaToUse := meta + nodeInfoToUse := info + if i == 0 { + podsAdded, metaToUse, nodeInfoToUse = addNominatedPods(util.GetPodPriority(pod), meta, info, queue) + } else if !podsAdded || len(failedPredicates) != 0 { + break + } + // Bypass eCache if node has any nominated pods. + // TODO(bsalamat): consider using eCache and adding proper eCache invalidations + // when pods are nominated or their nominations change. + eCacheAvailable = eCacheAvailable && !podsAdded + for _, predicateKey := range predicates.PredicatesOrdering() { + //TODO (yastij) : compute average predicate restrictiveness to export it as promethus metric + if predicate, exist := predicateFuncs[predicateKey]; exist { + if eCacheAvailable { + // PredicateWithECache will return its cached predicate results. + fit, reasons, invalid = ecache.PredicateWithECache(pod.GetName(), info.Node().GetName(), predicateKey, equivalenceHash) + } + + // TODO(bsalamat): When one predicate fails and fit is false, why do we continue + // checking other predicates? + if !eCacheAvailable || invalid { + // we need to execute predicate functions since equivalence cache does not work + fit, reasons, err = predicate(pod, metaToUse, nodeInfoToUse) + if err != nil { + return false, []algorithm.PredicateFailureReason{}, err + } + if eCacheAvailable { + // Store data to update eCache after this loop. + if res, exists := predicateResults[predicateKey]; exists { + res.Fit = res.Fit && fit + res.FailReasons = append(res.FailReasons, reasons...) + predicateResults[predicateKey] = res + } else { + predicateResults[predicateKey] = HostPredicate{Fit: fit, FailReasons: reasons} + } + } + } + if !fit { + // eCache is available and valid, and predicates result is unfit, record the fail reasons + failedPredicates = append(failedPredicates, reasons...) + } + } + } + } + + // TODO(bsalamat): This way of updating equiv. cache has a race condition against + // cache invalidations invoked in event handlers. This race has existed despite locks + // in eCache implementation. If cache is invalidated after a predicate is executed + // and before we update the cache, the updates should not be written to the cache. + if eCacheAvailable { + nodeName := info.Node().GetName() + for predKey, result := range predicateResults { + // update equivalence cache with newly computed fit & reasons + // TODO(resouer) should we do this in another thread? any race? + ecache.UpdateCachedPredicateItem(pod.GetName(), nodeName, predKey, result.Fit, result.FailReasons, equivalenceHash) + } + } + return len(failedPredicates) == 0, failedPredicates, nil +} + +// Prioritizes the nodes by running the individual priority functions in parallel. +// Each priority function is expected to set a score of 0-10 +// 0 is the lowest priority score (least preferred node) and 10 is the highest +// Each priority function can also have its own weight +// The node scores returned by the priority function are multiplied by the weights to get weighted scores +// All scores are finally combined (added) to get the total weighted scores of all nodes +func PrioritizeNodes( + pod *v1.Pod, + nodeNameToInfo map[string]*schedulercache.NodeInfo, + meta interface{}, + priorityConfigs []algorithm.PriorityConfig, + nodes []*v1.Node, + extenders []algorithm.SchedulerExtender, +) (schedulerapi.HostPriorityList, error) { + // If no priority configs are provided, then the EqualPriority function is applied + // This is required to generate the priority list in the required format + if len(priorityConfigs) == 0 && len(extenders) == 0 { + result := make(schedulerapi.HostPriorityList, 0, len(nodes)) + for i := range nodes { + hostPriority, err := EqualPriorityMap(pod, meta, nodeNameToInfo[nodes[i].Name]) + if err != nil { + return nil, err + } + result = append(result, hostPriority) + } + return result, nil + } + + var ( + mu = sync.Mutex{} + wg = sync.WaitGroup{} + errs []error + ) + appendError := func(err error) { + mu.Lock() + defer mu.Unlock() + errs = append(errs, err) + } + + results := make([]schedulerapi.HostPriorityList, len(priorityConfigs), len(priorityConfigs)) + + for i, priorityConfig := range priorityConfigs { + if priorityConfig.Function != nil { + // DEPRECATED + wg.Add(1) + go func(index int, config algorithm.PriorityConfig) { + defer wg.Done() + var err error + results[index], err = config.Function(pod, nodeNameToInfo, nodes) + if err != nil { + appendError(err) + } + }(i, priorityConfig) + } else { + results[i] = make(schedulerapi.HostPriorityList, len(nodes)) + } + } + processNode := func(index int) { + nodeInfo := nodeNameToInfo[nodes[index].Name] + var err error + for i := range priorityConfigs { + if priorityConfigs[i].Function != nil { + continue + } + results[i][index], err = priorityConfigs[i].Map(pod, meta, nodeInfo) + if err != nil { + appendError(err) + return + } + } + } + workqueue.Parallelize(16, len(nodes), processNode) + for i, priorityConfig := range priorityConfigs { + if priorityConfig.Reduce == nil { + continue + } + wg.Add(1) + go func(index int, config algorithm.PriorityConfig) { + defer wg.Done() + if err := config.Reduce(pod, meta, nodeNameToInfo, results[index]); err != nil { + appendError(err) + } + if glog.V(10) { + for _, hostPriority := range results[index] { + glog.Infof("%v -> %v: %v, Score: (%d)", pod.Name, hostPriority.Host, config.Name, hostPriority.Score) + } + } + }(i, priorityConfig) + } + // Wait for all computations to be finished. + wg.Wait() + if len(errs) != 0 { + return schedulerapi.HostPriorityList{}, errors.NewAggregate(errs) + } + + // Summarize all scores. + result := make(schedulerapi.HostPriorityList, 0, len(nodes)) + + for i := range nodes { + result = append(result, schedulerapi.HostPriority{Host: nodes[i].Name, Score: 0}) + for j := range priorityConfigs { + result[i].Score += results[j][i].Score * priorityConfigs[j].Weight + } + } + + if len(extenders) != 0 && nodes != nil { + combinedScores := make(map[string]int, len(nodeNameToInfo)) + for _, extender := range extenders { + wg.Add(1) + go func(ext algorithm.SchedulerExtender) { + defer wg.Done() + prioritizedList, weight, err := ext.Prioritize(pod, nodes) + if err != nil { + // Prioritization errors from extender can be ignored, let k8s/other extenders determine the priorities + return + } + mu.Lock() + for i := range *prioritizedList { + host, score := (*prioritizedList)[i].Host, (*prioritizedList)[i].Score + combinedScores[host] += score * weight + } + mu.Unlock() + }(extender) + } + // wait for all go routines to finish + wg.Wait() + for i := range result { + result[i].Score += combinedScores[result[i].Host] + } + } + + if glog.V(10) { + for i := range result { + glog.V(10).Infof("Host %s => Score %d", result[i].Host, result[i].Score) + } + } + return result, nil +} + +// EqualPriority is a prioritizer function that gives an equal weight of one to all nodes +func EqualPriorityMap(_ *v1.Pod, _ interface{}, nodeInfo *schedulercache.NodeInfo) (schedulerapi.HostPriority, error) { + node := nodeInfo.Node() + if node == nil { + return schedulerapi.HostPriority{}, fmt.Errorf("node not found") + } + return schedulerapi.HostPriority{ + Host: node.Name, + Score: 1, + }, nil +} + +// pickOneNodeForPreemption chooses one node among the given nodes. It assumes +// pods in each map entry are ordered by decreasing priority. +// It picks a node based on the following criteria: +// 1. A node with minimum number of PDB violations. +// 2. A node with minimum highest priority victim is picked. +// 3. Ties are broken by sum of priorities of all victims. +// 4. If there are still ties, node with the minimum number of victims is picked. +// 5. If there are still ties, the first such node is picked (sort of randomly). +//TODO(bsalamat): Try to reuse the "min*Nodes" slices in order to save GC time. +func pickOneNodeForPreemption(nodesToVictims map[*v1.Node]*Victims) *v1.Node { + if len(nodesToVictims) == 0 { + return nil + } + minNumPDBViolatingPods := math.MaxInt32 + var minPDBViolatingNodes []*v1.Node + for node, victims := range nodesToVictims { + if len(victims.pods) == 0 { + // We found a node that doesn't need any preemption. Return it! + // This should happen rarely when one or more pods are terminated between + // the time that scheduler tries to schedule the pod and the time that + // preemption logic tries to find nodes for preemption. + return node + } + numPDBViolatingPods := victims.numPDBViolations + if numPDBViolatingPods < minNumPDBViolatingPods { + minNumPDBViolatingPods = numPDBViolatingPods + minPDBViolatingNodes = nil + } + if numPDBViolatingPods == minNumPDBViolatingPods { + minPDBViolatingNodes = append(minPDBViolatingNodes, node) + } + } + if len(minPDBViolatingNodes) == 1 { + return minPDBViolatingNodes[0] + } + + // There are more than one node with minimum number PDB violating pods. Find + // the one with minimum highest priority victim. + minHighestPriority := int32(math.MaxInt32) + var minPriorityNodes []*v1.Node + for _, node := range minPDBViolatingNodes { + victims := nodesToVictims[node] + // highestPodPriority is the highest priority among the victims on this node. + highestPodPriority := util.GetPodPriority(victims.pods[0]) + if highestPodPriority < minHighestPriority { + minHighestPriority = highestPodPriority + minPriorityNodes = nil + } + if highestPodPriority == minHighestPriority { + minPriorityNodes = append(minPriorityNodes, node) + } + } + if len(minPriorityNodes) == 1 { + return minPriorityNodes[0] + } + + // There are a few nodes with minimum highest priority victim. Find the + // smallest sum of priorities. + minSumPriorities := int64(math.MaxInt64) + var minSumPriorityNodes []*v1.Node + for _, node := range minPriorityNodes { + var sumPriorities int64 + for _, pod := range nodesToVictims[node].pods { + // We add MaxInt32+1 to all priorities to make all of them >= 0. This is + // needed so that a node with a few pods with negative priority is not + // picked over a node with a smaller number of pods with the same negative + // priority (and similar scenarios). + sumPriorities += int64(util.GetPodPriority(pod)) + int64(math.MaxInt32+1) + } + if sumPriorities < minSumPriorities { + minSumPriorities = sumPriorities + minSumPriorityNodes = nil + } + if sumPriorities == minSumPriorities { + minSumPriorityNodes = append(minSumPriorityNodes, node) + } + } + if len(minSumPriorityNodes) == 1 { + return minSumPriorityNodes[0] + } + + // There are a few nodes with minimum highest priority victim and sum of priorities. + // Find one with the minimum number of pods. + minNumPods := math.MaxInt32 + var minNumPodNodes []*v1.Node + for _, node := range minSumPriorityNodes { + numPods := len(nodesToVictims[node].pods) + if numPods < minNumPods { + minNumPods = numPods + minNumPodNodes = nil + } + if numPods == minNumPods { + minNumPodNodes = append(minNumPodNodes, node) + } + } + // At this point, even if there are more than one node with the same score, + // return the first one. + if len(minNumPodNodes) > 0 { + return minNumPodNodes[0] + } + glog.Errorf("Error in logic of node scoring for preemption. We should never reach here!") + return nil +} + +// selectNodesForPreemption finds all the nodes with possible victims for +// preemption in parallel. +func selectNodesForPreemption(pod *v1.Pod, + nodeNameToInfo map[string]*schedulercache.NodeInfo, + potentialNodes []*v1.Node, + predicates map[string]algorithm.FitPredicate, + metadataProducer algorithm.PredicateMetadataProducer, + queue SchedulingQueue, + pdbs []*policy.PodDisruptionBudget, +) (map[*v1.Node]*Victims, error) { + + nodeNameToVictims := map[*v1.Node]*Victims{} + var resultLock sync.Mutex + + // We can use the same metadata producer for all nodes. + meta := metadataProducer(pod, nodeNameToInfo) + checkNode := func(i int) { + nodeName := potentialNodes[i].Name + var metaCopy algorithm.PredicateMetadata + if meta != nil { + metaCopy = meta.ShallowCopy() + } + pods, numPDBViolations, fits := selectVictimsOnNode(pod, metaCopy, nodeNameToInfo[nodeName], predicates, queue, pdbs) + if fits { + resultLock.Lock() + victims := Victims{ + pods: pods, + numPDBViolations: numPDBViolations, + } + nodeNameToVictims[potentialNodes[i]] = &victims + resultLock.Unlock() + } + } + workqueue.Parallelize(16, len(potentialNodes), checkNode) + return nodeNameToVictims, nil +} + +func nodePassesExtendersForPreemption( + pod *v1.Pod, + nodeName string, + victims []*v1.Pod, + nodeNameToInfo map[string]*schedulercache.NodeInfo, + extenders []algorithm.SchedulerExtender) (bool, error) { + // If there are any extenders, run them and filter the list of candidate nodes. + if len(extenders) == 0 { + return true, nil + } + // Remove the victims from the corresponding nodeInfo and send nodes to the + // extenders for filtering. + originalNodeInfo := nodeNameToInfo[nodeName] + nodeInfoCopy := nodeNameToInfo[nodeName].Clone() + for _, victim := range victims { + nodeInfoCopy.RemovePod(victim) + } + nodeNameToInfo[nodeName] = nodeInfoCopy + defer func() { nodeNameToInfo[nodeName] = originalNodeInfo }() + filteredNodes := []*v1.Node{nodeInfoCopy.Node()} + for _, extender := range extenders { + var err error + var failedNodesMap map[string]string + filteredNodes, failedNodesMap, err = extender.Filter(pod, filteredNodes, nodeNameToInfo) + if err != nil { + return false, err + } + if _, found := failedNodesMap[nodeName]; found || len(filteredNodes) == 0 { + return false, nil + } + } + return true, nil +} + +// filterPodsWithPDBViolation groups the given "pods" into two groups of "violatingPods" +// and "nonViolatingPods" based on whether their PDBs will be violated if they are +// preempted. +// This function is stable and does not change the order of received pods. So, if it +// receives a sorted list, grouping will preserve the order of the input list. +func filterPodsWithPDBViolation(pods []interface{}, pdbs []*policy.PodDisruptionBudget) (violatingPods, nonViolatingPods []*v1.Pod) { + for _, obj := range pods { + pod := obj.(*v1.Pod) + pdbForPodIsViolated := false + // A pod with no labels will not match any PDB. So, no need to check. + if len(pod.Labels) != 0 { + for _, pdb := range pdbs { + if pdb.Namespace != pod.Namespace { + continue + } + selector, err := metav1.LabelSelectorAsSelector(pdb.Spec.Selector) + if err != nil { + continue + } + // A PDB with a nil or empty selector matches nothing. + if selector.Empty() || !selector.Matches(labels.Set(pod.Labels)) { + continue + } + // We have found a matching PDB. + if pdb.Status.PodDisruptionsAllowed <= 0 { + pdbForPodIsViolated = true + break + } + } + } + if pdbForPodIsViolated { + violatingPods = append(violatingPods, pod) + } else { + nonViolatingPods = append(nonViolatingPods, pod) + } + } + return violatingPods, nonViolatingPods +} + +// selectVictimsOnNode finds minimum set of pods on the given node that should +// be preempted in order to make enough room for "pod" to be scheduled. The +// minimum set selected is subject to the constraint that a higher-priority pod +// is never preempted when a lower-priority pod could be (higher/lower relative +// to one another, not relative to the preemptor "pod"). +// The algorithm first checks if the pod can be scheduled on the node when all the +// lower priority pods are gone. If so, it sorts all the lower priority pods by +// their priority and then puts them into two groups of those whose PodDisruptionBudget +// will be violated if preempted and other non-violating pods. Both groups are +// sorted by priority. It first tries to reprieve as many PDB violating pods as +// possible and then does them same for non-PDB-violating pods while checking +// that the "pod" can still fit on the node. +// NOTE: This function assumes that it is never called if "pod" cannot be scheduled +// due to pod affinity, node affinity, or node anti-affinity reasons. None of +// these predicates can be satisfied by removing more pods from the node. +func selectVictimsOnNode( + pod *v1.Pod, + meta algorithm.PredicateMetadata, + nodeInfo *schedulercache.NodeInfo, + fitPredicates map[string]algorithm.FitPredicate, + queue SchedulingQueue, + pdbs []*policy.PodDisruptionBudget, +) ([]*v1.Pod, int, bool) { + potentialVictims := util.SortableList{CompFunc: util.HigherPriorityPod} + nodeInfoCopy := nodeInfo.Clone() + + removePod := func(rp *v1.Pod) { + nodeInfoCopy.RemovePod(rp) + if meta != nil { + meta.RemovePod(rp) + } + } + addPod := func(ap *v1.Pod) { + nodeInfoCopy.AddPod(ap) + if meta != nil { + meta.AddPod(ap, nodeInfoCopy) + } + } + // As the first step, remove all the lower priority pods from the node and + // check if the given pod can be scheduled. + podPriority := util.GetPodPriority(pod) + for _, p := range nodeInfoCopy.Pods() { + if util.GetPodPriority(p) < podPriority { + potentialVictims.Items = append(potentialVictims.Items, p) + removePod(p) + } + } + potentialVictims.Sort() + // If the new pod does not fit after removing all the lower priority pods, + // we are almost done and this node is not suitable for preemption. The only condition + // that we should check is if the "pod" is failing to schedule due to pod affinity + // failure. + // TODO(bsalamat): Consider checking affinity to lower priority pods if feasible with reasonable performance. + if fits, _, err := podFitsOnNode(pod, meta, nodeInfoCopy, fitPredicates, nil, queue); !fits { + if err != nil { + glog.Warningf("Encountered error while selecting victims on node %v: %v", nodeInfo.Node().Name, err) + } + return nil, 0, false + } + var victims []*v1.Pod + numViolatingVictim := 0 + // 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 priority victims. + violatingVictims, nonViolatingVictims := filterPodsWithPDBViolation(potentialVictims.Items, pdbs) + reprievePod := func(p *v1.Pod) bool { + addPod(p) + fits, _, _ := podFitsOnNode(pod, meta, nodeInfoCopy, fitPredicates, nil, queue) + if !fits { + removePod(p) + victims = append(victims, p) + glog.V(5).Infof("Pod %v is a potential preemption victim on node %v.", p.Name, nodeInfo.Node().Name) + } + return fits + } + for _, p := range violatingVictims { + if !reprievePod(p) { + numViolatingVictim++ + } + } + // Now we try to reprieve non-violating victims. + for _, p := range nonViolatingVictims { + reprievePod(p) + } + return victims, numViolatingVictim, true +} + +// nodesWherePreemptionMightHelp returns a list of nodes with failed predicates +// that may be satisfied by removing pods from the node. +func nodesWherePreemptionMightHelp(pod *v1.Pod, nodes []*v1.Node, failedPredicatesMap FailedPredicateMap) []*v1.Node { + potentialNodes := []*v1.Node{} + for _, node := range nodes { + unresolvableReasonExist := false + failedPredicates, found := failedPredicatesMap[node.Name] + // If we assume that scheduler looks at all nodes and populates the failedPredicateMap + // (which is the case today), the !found case should never happen, but we'd prefer + // to rely less on such assumptions in the code when checking does not impose + // significant overhead. + for _, failedPredicate := range failedPredicates { + switch failedPredicate { + case + predicates.ErrNodeSelectorNotMatch, + predicates.ErrPodNotMatchHostName, + predicates.ErrTaintsTolerationsNotMatch, + predicates.ErrNodeLabelPresenceViolated, + predicates.ErrNodeNotReady, + predicates.ErrNodeNetworkUnavailable, + predicates.ErrNodeUnschedulable, + predicates.ErrNodeUnknownCondition, + predicates.ErrVolumeZoneConflict, + predicates.ErrVolumeNodeConflict, + predicates.ErrVolumeBindConflict: + unresolvableReasonExist = true + break + // TODO(bsalamat): Please add affinity failure cases once we have specific affinity failure errors. + } + } + if !found || !unresolvableReasonExist { + glog.V(3).Infof("Node %v is a potential node for preemption.", node.Name) + potentialNodes = append(potentialNodes, node) + } + } + return potentialNodes +} + +// podEligibleToPreemptOthers determines whether this pod should be considered +// for preempting other pods or not. If this pod has already preempted other +// pods and those are in their graceful termination period, it shouldn't be +// considered for preemption. +// We look at the node that is nominated for this pod and as long as there are +// terminating pods on the node, we don't consider this for preempting more pods. +func podEligibleToPreemptOthers(pod *v1.Pod, nodeNameToInfo map[string]*schedulercache.NodeInfo) bool { + if nodeName, found := pod.Annotations[NominatedNodeAnnotationKey]; found { + if nodeInfo, found := nodeNameToInfo[nodeName]; found { + for _, p := range nodeInfo.Pods() { + if p.DeletionTimestamp != nil && util.GetPodPriority(p) < util.GetPodPriority(pod) { + // There is a terminating pod on the nominated node. + return false + } + } + } + } + return true +} + +// podPassesBasicChecks makes sanity checks on the pod if it can be scheduled. +func podPassesBasicChecks(pod *v1.Pod, pvcLister corelisters.PersistentVolumeClaimLister) error { + // Check PVCs used by the pod + namespace := pod.Namespace + manifest := &(pod.Spec) + for i := range manifest.Volumes { + volume := &manifest.Volumes[i] + if volume.PersistentVolumeClaim == nil { + // Volume is not a PVC, ignore + continue + } + pvcName := volume.PersistentVolumeClaim.ClaimName + pvc, err := pvcLister.PersistentVolumeClaims(namespace).Get(pvcName) + if err != nil { + // The error has already enough context ("persistentvolumeclaim "myclaim" not found") + return err + } + + if pvc.DeletionTimestamp != nil { + return fmt.Errorf("persistentvolumeclaim %q is being deleted", pvc.Name) + } + } + + return nil +} + +func NewGenericScheduler( + cache schedulercache.Cache, + eCache *EquivalenceCache, + podQueue SchedulingQueue, + predicates map[string]algorithm.FitPredicate, + predicateMetaProducer algorithm.PredicateMetadataProducer, + prioritizers []algorithm.PriorityConfig, + priorityMetaProducer algorithm.MetadataProducer, + extenders []algorithm.SchedulerExtender, + volumeBinder *volumebinder.VolumeBinder, + pvcLister corelisters.PersistentVolumeClaimLister) algorithm.ScheduleAlgorithm { + return &genericScheduler{ + cache: cache, + equivalenceCache: eCache, + schedulingQueue: podQueue, + predicates: predicates, + predicateMetaProducer: predicateMetaProducer, + prioritizers: prioritizers, + priorityMetaProducer: priorityMetaProducer, + extenders: extenders, + cachedNodeInfoMap: make(map[string]*schedulercache.NodeInfo), + volumeBinder: volumeBinder, + pvcLister: pvcLister, + } +} diff --git a/plugin/pkg/scheduler/core/generic_scheduler_test.go b/pkg/scheduler/core/generic_scheduler_test.go similarity index 89% rename from plugin/pkg/scheduler/core/generic_scheduler_test.go rename to pkg/scheduler/core/generic_scheduler_test.go index ce4ff1300dc..cdfc6b20fe5 100644 --- a/plugin/pkg/scheduler/core/generic_scheduler_test.go +++ b/pkg/scheduler/core/generic_scheduler_test.go @@ -32,14 +32,18 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates" - algorithmpredicates "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates" - algorithmpriorities "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/priorities" - priorityutil "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/priorities/util" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" - schedulertesting "k8s.io/kubernetes/plugin/pkg/scheduler/testing" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + "k8s.io/kubernetes/pkg/scheduler/algorithm/predicates" + algorithmpredicates "k8s.io/kubernetes/pkg/scheduler/algorithm/predicates" + algorithmpriorities "k8s.io/kubernetes/pkg/scheduler/algorithm/priorities" + priorityutil "k8s.io/kubernetes/pkg/scheduler/algorithm/priorities/util" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" + schedulertesting "k8s.io/kubernetes/pkg/scheduler/testing" +) + +var ( + order = []string{"false", "true", "matches", "nopods", predicates.MatchInterPodAffinityPred} ) func falsePredicate(pod *v1.Pod, meta algorithm.PredicateMetadata, nodeInfo *schedulercache.NodeInfo) (bool, []algorithm.PredicateFailureReason, error) { @@ -181,11 +185,13 @@ func TestSelectHost(t *testing.T) { } func TestGenericScheduler(t *testing.T) { + predicates.SetPredicatesOrdering(order) tests := []struct { name string predicates map[string]algorithm.FitPredicate prioritizers []algorithm.PriorityConfig nodes []string + pvcs []*v1.PersistentVolumeClaim pod *v1.Pod pods []*v1.Pod expectedHosts sets.String @@ -300,6 +306,77 @@ func TestGenericScheduler(t *testing.T) { }, }, }, + { + // Pod with existing PVC + predicates: map[string]algorithm.FitPredicate{"true": truePredicate}, + prioritizers: []algorithm.PriorityConfig{{Map: EqualPriorityMap, Weight: 1}}, + nodes: []string{"machine1", "machine2"}, + pvcs: []*v1.PersistentVolumeClaim{{ObjectMeta: metav1.ObjectMeta{Name: "existingPVC"}}}, + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "ignore"}, + Spec: v1.PodSpec{ + Volumes: []v1.Volume{ + { + VolumeSource: v1.VolumeSource{ + PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ + ClaimName: "existingPVC", + }, + }, + }, + }, + }, + }, + expectedHosts: sets.NewString("machine1", "machine2"), + name: "existing PVC", + wErr: nil, + }, + { + // Pod with non existing PVC + predicates: map[string]algorithm.FitPredicate{"true": truePredicate}, + prioritizers: []algorithm.PriorityConfig{{Map: EqualPriorityMap, Weight: 1}}, + nodes: []string{"machine1", "machine2"}, + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "ignore"}, + Spec: v1.PodSpec{ + Volumes: []v1.Volume{ + { + VolumeSource: v1.VolumeSource{ + PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ + ClaimName: "unknownPVC", + }, + }, + }, + }, + }, + }, + name: "unknown PVC", + expectsErr: true, + wErr: fmt.Errorf("persistentvolumeclaim \"unknownPVC\" not found"), + }, + { + // Pod with deleting PVC + predicates: map[string]algorithm.FitPredicate{"true": truePredicate}, + prioritizers: []algorithm.PriorityConfig{{Map: EqualPriorityMap, Weight: 1}}, + nodes: []string{"machine1", "machine2"}, + pvcs: []*v1.PersistentVolumeClaim{{ObjectMeta: metav1.ObjectMeta{Name: "existingPVC", DeletionTimestamp: &metav1.Time{}}}}, + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "ignore"}, + Spec: v1.PodSpec{ + Volumes: []v1.Volume{ + { + VolumeSource: v1.VolumeSource{ + PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ + ClaimName: "existingPVC", + }, + }, + }, + }, + }, + }, + name: "deleted PVC", + expectsErr: true, + wErr: fmt.Errorf("persistentvolumeclaim \"existingPVC\" is being deleted"), + }, } for _, test := range tests { cache := schedulercache.New(time.Duration(0), wait.NeverStop) @@ -309,9 +386,14 @@ func TestGenericScheduler(t *testing.T) { for _, name := range test.nodes { cache.AddNode(&v1.Node{ObjectMeta: metav1.ObjectMeta{Name: name}}) } + pvcs := []*v1.PersistentVolumeClaim{} + for _, pvc := range test.pvcs { + pvcs = append(pvcs, pvc) + } + pvcLister := schedulertesting.FakePersistentVolumeClaimLister(pvcs) scheduler := NewGenericScheduler( - cache, nil, test.predicates, algorithm.EmptyPredicateMetadataProducer, test.prioritizers, algorithm.EmptyMetadataProducer, []algorithm.SchedulerExtender{}) + cache, nil, NewSchedulingQueue(), test.predicates, algorithm.EmptyPredicateMetadataProducer, test.prioritizers, algorithm.EmptyMetadataProducer, []algorithm.SchedulerExtender{}, nil, pvcLister) machine, err := scheduler.Schedule(test.pod, schedulertesting.FakeNodeLister(makeNodeList(test.nodes))) if !reflect.DeepEqual(err, test.wErr) { @@ -324,6 +406,7 @@ func TestGenericScheduler(t *testing.T) { } func TestFindFitAllError(t *testing.T) { + predicates.SetPredicatesOrdering(order) nodes := []string{"3", "2", "1"} predicates := map[string]algorithm.FitPredicate{"true": truePredicate, "false": falsePredicate} nodeNameToInfo := map[string]*schedulercache.NodeInfo{ @@ -331,7 +414,7 @@ func TestFindFitAllError(t *testing.T) { "2": schedulercache.NewNodeInfo(), "1": schedulercache.NewNodeInfo(), } - _, predicateMap, err := findNodesThatFit(&v1.Pod{}, nodeNameToInfo, makeNodeList(nodes), predicates, nil, algorithm.EmptyPredicateMetadataProducer, nil) + _, predicateMap, err := findNodesThatFit(&v1.Pod{}, nodeNameToInfo, makeNodeList(nodes), predicates, nil, algorithm.EmptyPredicateMetadataProducer, nil, nil) if err != nil { t.Errorf("unexpected error: %v", err) @@ -353,8 +436,9 @@ func TestFindFitAllError(t *testing.T) { } func TestFindFitSomeError(t *testing.T) { + predicates.SetPredicatesOrdering(order) nodes := []string{"3", "2", "1"} - predicates := map[string]algorithm.FitPredicate{"true": truePredicate, "match": matchesPredicate} + predicates := map[string]algorithm.FitPredicate{"true": truePredicate, "matches": matchesPredicate} pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "1"}} nodeNameToInfo := map[string]*schedulercache.NodeInfo{ "3": schedulercache.NewNodeInfo(), @@ -365,7 +449,7 @@ func TestFindFitSomeError(t *testing.T) { nodeNameToInfo[name].SetNode(&v1.Node{ObjectMeta: metav1.ObjectMeta{Name: name}}) } - _, predicateMap, err := findNodesThatFit(pod, nodeNameToInfo, makeNodeList(nodes), predicates, nil, algorithm.EmptyPredicateMetadataProducer, nil) + _, predicateMap, err := findNodesThatFit(pod, nodeNameToInfo, makeNodeList(nodes), predicates, nil, algorithm.EmptyPredicateMetadataProducer, nil, nil) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -517,23 +601,31 @@ func TestZeroRequest(t *testing.T) { const expectedPriority int = 25 for _, test := range tests { // This should match the configuration in defaultPriorities() in - // plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go if you want + // pkg/scheduler/algorithmprovider/defaults/defaults.go if you want // to test what's actually in production. priorityConfigs := []algorithm.PriorityConfig{ {Map: algorithmpriorities.LeastRequestedPriorityMap, Weight: 1}, {Map: algorithmpriorities.BalancedResourceAllocationMap, Weight: 1}, - { - Function: algorithmpriorities.NewSelectorSpreadPriority( - schedulertesting.FakeServiceLister([]*v1.Service{}), - schedulertesting.FakeControllerLister([]*v1.ReplicationController{}), - schedulertesting.FakeReplicaSetLister([]*extensions.ReplicaSet{}), - schedulertesting.FakeStatefulSetLister([]*apps.StatefulSet{})), - Weight: 1, - }, } + selectorSpreadPriorityMap, selectorSpreadPriorityReduce := algorithmpriorities.NewSelectorSpreadPriority( + schedulertesting.FakeServiceLister([]*v1.Service{}), + schedulertesting.FakeControllerLister([]*v1.ReplicationController{}), + schedulertesting.FakeReplicaSetLister([]*extensions.ReplicaSet{}), + schedulertesting.FakeStatefulSetLister([]*apps.StatefulSet{})) + pc := algorithm.PriorityConfig{Map: selectorSpreadPriorityMap, Reduce: selectorSpreadPriorityReduce, Weight: 1} + priorityConfigs = append(priorityConfigs, pc) + nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods, test.nodes) + + mataDataProducer := algorithmpriorities.NewPriorityMetadataFactory( + schedulertesting.FakeServiceLister([]*v1.Service{}), + schedulertesting.FakeControllerLister([]*v1.ReplicationController{}), + schedulertesting.FakeReplicaSetLister([]*extensions.ReplicaSet{}), + schedulertesting.FakeStatefulSetLister([]*apps.StatefulSet{})) + mataData := mataDataProducer(test.pod, nodeNameToInfo) + list, err := PrioritizeNodes( - test.pod, nodeNameToInfo, algorithm.EmptyMetadataProducer, priorityConfigs, + test.pod, nodeNameToInfo, mataData, priorityConfigs, schedulertesting.FakeNodeLister(test.nodes), []algorithm.SchedulerExtender{}) if err != nil { t.Errorf("unexpected error: %v", err) @@ -552,11 +644,11 @@ func TestZeroRequest(t *testing.T) { } } -func printNodeToPods(nodeToPods map[*v1.Node][]*v1.Pod) string { +func printNodeToVictims(nodeToVictims map[*v1.Node]*Victims) string { var output string - for node, pods := range nodeToPods { + for node, victims := range nodeToVictims { output += node.Name + ": [" - for _, pod := range pods { + for _, pod := range victims.pods { output += pod.Name + ", " } output += "]" @@ -564,15 +656,15 @@ func printNodeToPods(nodeToPods map[*v1.Node][]*v1.Pod) string { return output } -func checkPreemptionVictims(testName string, expected map[string]map[string]bool, nodeToPods map[*v1.Node][]*v1.Pod) error { +func checkPreemptionVictims(testName string, expected map[string]map[string]bool, nodeToPods map[*v1.Node]*Victims) error { if len(expected) == len(nodeToPods) { - for k, pods := range nodeToPods { + for k, victims := range nodeToPods { if expPods, ok := expected[k.Name]; ok { - if len(pods) != len(expPods) { - return fmt.Errorf("test [%v]: unexpected number of pods. expected: %v, got: %v", testName, expected, printNodeToPods(nodeToPods)) + if len(victims.pods) != len(expPods) { + return fmt.Errorf("test [%v]: unexpected number of pods. expected: %v, got: %v", testName, expected, printNodeToVictims(nodeToPods)) } prevPriority := int32(math.MaxInt32) - for _, p := range pods { + for _, p := range victims.pods { // Check that pods are sorted by their priority. if *p.Spec.Priority > prevPriority { return fmt.Errorf("test [%v]: pod %v of node %v was not sorted by priority", testName, p.Name, k) @@ -583,11 +675,11 @@ func checkPreemptionVictims(testName string, expected map[string]map[string]bool } } } else { - return fmt.Errorf("test [%v]: unexpected machines. expected: %v, got: %v", testName, expected, printNodeToPods(nodeToPods)) + return fmt.Errorf("test [%v]: unexpected machines. expected: %v, got: %v", testName, expected, printNodeToVictims(nodeToPods)) } } } else { - return fmt.Errorf("test [%v]: unexpected number of machines. expected: %v, got: %v", testName, expected, printNodeToPods(nodeToPods)) + return fmt.Errorf("test [%v]: unexpected number of machines. expected: %v, got: %v", testName, expected, printNodeToVictims(nodeToPods)) } return nil } @@ -656,6 +748,7 @@ var negPriority, lowPriority, midPriority, highPriority, veryHighPriority = int3 // TestSelectNodesForPreemption tests selectNodesForPreemption. This test assumes // that podsFitsOnNode works correctly and is tested separately. func TestSelectNodesForPreemption(t *testing.T) { + predicates.SetPredicatesOrdering(order) tests := []struct { name string predicates map[string]algorithm.FitPredicate @@ -779,10 +872,10 @@ func TestSelectNodesForPreemption(t *testing.T) { nodes = append(nodes, node) } if test.addAffinityPredicate { - test.predicates[predicates.MatchInterPodAffinity] = algorithmpredicates.NewPodAffinityPredicate(FakeNodeInfo(*nodes[0]), schedulertesting.FakePodLister(test.pods)) + test.predicates[predicates.MatchInterPodAffinityPred] = algorithmpredicates.NewPodAffinityPredicate(FakeNodeInfo(*nodes[0]), schedulertesting.FakePodLister(test.pods)) } nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods, nodes) - nodeToPods, err := selectNodesForPreemption(test.pod, nodeNameToInfo, nodes, test.predicates, PredicateMetadata) + nodeToPods, err := selectNodesForPreemption(test.pod, nodeNameToInfo, nodes, test.predicates, PredicateMetadata, nil, nil) if err != nil { t.Error(err) } @@ -794,6 +887,7 @@ func TestSelectNodesForPreemption(t *testing.T) { // TestPickOneNodeForPreemption tests pickOneNodeForPreemption. func TestPickOneNodeForPreemption(t *testing.T) { + predicates.SetPredicatesOrdering(order) tests := []struct { name string predicates map[string]algorithm.FitPredicate @@ -939,7 +1033,7 @@ func TestPickOneNodeForPreemption(t *testing.T) { nodes = append(nodes, makeNode(n, priorityutil.DefaultMilliCpuRequest*5, priorityutil.DefaultMemoryRequest*5)) } nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods, nodes) - candidateNodes, _ := selectNodesForPreemption(test.pod, nodeNameToInfo, nodes, test.predicates, PredicateMetadata) + candidateNodes, _ := selectNodesForPreemption(test.pod, nodeNameToInfo, nodes, test.predicates, PredicateMetadata, nil, nil) node := pickOneNodeForPreemption(candidateNodes) found := false for _, nodeName := range test.expected { @@ -1182,9 +1276,9 @@ func TestPreempt(t *testing.T) { extenders = append(extenders, extender) } scheduler := NewGenericScheduler( - cache, nil, map[string]algorithm.FitPredicate{"matches": algorithmpredicates.PodFitsResources}, algorithm.EmptyPredicateMetadataProducer, []algorithm.PriorityConfig{{Function: numericPriority, Weight: 1}}, algorithm.EmptyMetadataProducer, extenders) + cache, nil, NewSchedulingQueue(), map[string]algorithm.FitPredicate{"matches": algorithmpredicates.PodFitsResources}, algorithm.EmptyPredicateMetadataProducer, []algorithm.PriorityConfig{{Function: numericPriority, Weight: 1}}, algorithm.EmptyMetadataProducer, extenders, nil, schedulertesting.FakePersistentVolumeClaimLister{}) // Call Preempt and check the expected results. - node, victims, err := scheduler.Preempt(test.pod, schedulertesting.FakeNodeLister(makeNodeList(nodeNames)), error(&FitError{Pod: test.pod, FailedPredicates: failedPredMap})) + node, victims, _, err := scheduler.Preempt(test.pod, schedulertesting.FakeNodeLister(makeNodeList(nodeNames)), error(&FitError{Pod: test.pod, FailedPredicates: failedPredMap})) if err != nil { t.Errorf("test [%v]: unexpected error in preemption: %v", test.name, err) } @@ -1212,7 +1306,7 @@ func TestPreempt(t *testing.T) { test.pod.Annotations[NominatedNodeAnnotationKey] = node.Name } // Call preempt again and make sure it doesn't preempt any more pods. - node, victims, err = scheduler.Preempt(test.pod, schedulertesting.FakeNodeLister(makeNodeList(nodeNames)), error(&FitError{Pod: test.pod, FailedPredicates: failedPredMap})) + node, victims, _, err = scheduler.Preempt(test.pod, schedulertesting.FakeNodeLister(makeNodeList(nodeNames)), error(&FitError{Pod: test.pod, FailedPredicates: failedPredMap})) if err != nil { t.Errorf("test [%v]: unexpected error in preemption: %v", test.name, err) } diff --git a/pkg/scheduler/core/scheduling_queue.go b/pkg/scheduler/core/scheduling_queue.go new file mode 100644 index 00000000000..79dfee0a1d7 --- /dev/null +++ b/pkg/scheduler/core/scheduling_queue.go @@ -0,0 +1,732 @@ +/* +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. +*/ + +// This file contains structures that implement scheduling queue types. +// Scheduling queues hold pods waiting to be scheduled. This file has two types +// of scheduling queue: 1) a FIFO, which is mostly the same as cache.FIFO, 2) a +// priority queue which has two sub queues. One sub-queue holds pods that are +// being considered for scheduling. This is called activeQ. Another queue holds +// pods that are already tried and are determined to be unschedulable. The latter +// is called unschedulableQ. +// FIFO is here for flag-gating purposes and allows us to use the traditional +// scheduling queue when util.PodPriorityEnabled() returns false. + +package core + +import ( + "container/heap" + "fmt" + "sync" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/cache" + podutil "k8s.io/kubernetes/pkg/api/v1/pod" + "k8s.io/kubernetes/pkg/scheduler/algorithm/predicates" + priorityutil "k8s.io/kubernetes/pkg/scheduler/algorithm/priorities/util" + "k8s.io/kubernetes/pkg/scheduler/util" + + "github.com/golang/glog" + "reflect" +) + +// 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 { + Add(pod *v1.Pod) error + AddIfNotPresent(pod *v1.Pod) error + AddUnschedulableIfNotPresent(pod *v1.Pod) error + Pop() (*v1.Pod, error) + Update(pod *v1.Pod) error + Delete(pod *v1.Pod) error + MoveAllToActiveQueue() + AssignedPodAdded(pod *v1.Pod) + AssignedPodUpdated(pod *v1.Pod) + WaitingPodsForNode(nodeName string) []*v1.Pod +} + +// NewSchedulingQueue initializes a new scheduling queue. If pod priority is +// enabled a priority queue is returned. If it is disabled, a FIFO is returned. +func NewSchedulingQueue() SchedulingQueue { + if util.PodPriorityEnabled() { + return NewPriorityQueue() + } + return NewFIFO() +} + +// FIFO is basically a simple wrapper around cache.FIFO to make it compatible +// with the SchedulingQueue interface. +type FIFO struct { + *cache.FIFO +} + +var _ = SchedulingQueue(&FIFO{}) // Making sure that FIFO implements SchedulingQueue. + +func (f *FIFO) Add(pod *v1.Pod) error { + return f.FIFO.Add(pod) +} + +func (f *FIFO) AddIfNotPresent(pod *v1.Pod) error { + return f.FIFO.AddIfNotPresent(pod) +} + +// AddUnschedulableIfNotPresent adds an unschedulable pod back to the queue. In +// FIFO it is added to the end of the queue. +func (f *FIFO) AddUnschedulableIfNotPresent(pod *v1.Pod) error { + return f.FIFO.AddIfNotPresent(pod) +} + +func (f *FIFO) Update(pod *v1.Pod) error { + return f.FIFO.Update(pod) +} + +func (f *FIFO) Delete(pod *v1.Pod) error { + return f.FIFO.Delete(pod) +} + +// Pop removes the head of FIFO and returns it. +// This is just a copy/paste of cache.Pop(queue Queue) from fifo.go that scheduler +// has always been using. There is a comment in that file saying that this method +// shouldn't be used in production code, but scheduler has always been using it. +// This function does minimal error checking. +func (f *FIFO) Pop() (*v1.Pod, error) { + var result interface{} + f.FIFO.Pop(func(obj interface{}) error { + result = obj + return nil + }) + return result.(*v1.Pod), nil +} + +// FIFO does not need to react to events, as all pods are always in the active +// scheduling queue anyway. +func (f *FIFO) AssignedPodAdded(pod *v1.Pod) {} +func (f *FIFO) AssignedPodUpdated(pod *v1.Pod) {} + +// MoveAllToActiveQueue does nothing in FIFO as all pods are always in the active queue. +func (f *FIFO) MoveAllToActiveQueue() {} + +// WaitingPodsForNode returns pods that are nominated to run on the given node, +// but FIFO does not support it. +func (f *FIFO) WaitingPodsForNode(nodeName string) []*v1.Pod { + return nil +} + +func NewFIFO() *FIFO { + return &FIFO{FIFO: cache.NewFIFO(cache.MetaNamespaceKeyFunc)} +} + +// UnschedulablePods is an interface for a queue that is used to keep unschedulable +// pods. These pods are not actively reevaluated for scheduling. They are moved +// to the active scheduling queue on certain events, such as termination of a pod +// in the cluster, addition of nodes, etc. +type UnschedulablePods interface { + Add(pod *v1.Pod) + Delete(pod *v1.Pod) + Update(pod *v1.Pod) + GetPodsWaitingForNode(nodeName string) []*v1.Pod + Get(pod *v1.Pod) *v1.Pod + Clear() +} + +// PriorityQueue implements a scheduling queue. It is an alternative to FIFO. +// The head of PriorityQueue is the highest priority pending pod. This structure +// has two sub queues. One sub-queue holds pods that are being considered for +// scheduling. This is called activeQ and is a Heap. Another queue holds +// pods that are already tried and are determined to be unschedulable. The latter +// is called unschedulableQ. +type PriorityQueue struct { + lock sync.RWMutex + cond sync.Cond + + // activeQ is heap structure that scheduler actively looks at to find pods to + // schedule. Head of heap is the highest priority pod. + activeQ *Heap + // unschedulableQ holds pods that have been tried and determined unschedulable. + unschedulableQ *UnschedulablePodsMap + // receivedMoveRequest is set to true whenever we receive a request to move a + // pod from the unschedulableQ to the activeQ, and is set to false, when we pop + // a pod from the activeQ. It indicates if we received a move request when a + // pod was in flight (we were trying to schedule it). In such a case, we put + // the pod back into the activeQ if it is determined unschedulable. + receivedMoveRequest bool +} + +// Making sure that PriorityQueue implements SchedulingQueue. +var _ = SchedulingQueue(&PriorityQueue{}) + +func NewPriorityQueue() *PriorityQueue { + pq := &PriorityQueue{ + activeQ: newHeap(cache.MetaNamespaceKeyFunc, util.HigherPriorityPod), + unschedulableQ: newUnschedulablePodsMap(), + } + pq.cond.L = &pq.lock + return pq +} + +// 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 either queue. +func (p *PriorityQueue) Add(pod *v1.Pod) error { + p.lock.Lock() + defer p.lock.Unlock() + err := p.activeQ.Add(pod) + if err != nil { + glog.Errorf("Error adding pod %v to the scheduling queue: %v", pod.Name, err) + } else { + if p.unschedulableQ.Get(pod) != nil { + glog.Errorf("Error: pod %v is already in the unschedulable queue.", pod.Name) + p.unschedulableQ.Delete(pod) + } + p.cond.Broadcast() + } + return err +} + +// AddIfNotPresent adds a pod to the active queue if it is not present in any of +// the two queues. If it is present in any, it doesn't do any thing. +func (p *PriorityQueue) AddIfNotPresent(pod *v1.Pod) error { + p.lock.Lock() + defer p.lock.Unlock() + if p.unschedulableQ.Get(pod) != nil { + return nil + } + if _, exists, _ := p.activeQ.Get(pod); exists { + return nil + } + err := p.activeQ.Add(pod) + if err != nil { + glog.Errorf("Error adding pod %v to the scheduling queue: %v", pod.Name, err) + } else { + p.cond.Broadcast() + } + return err +} + +func isPodUnschedulable(pod *v1.Pod) bool { + _, cond := podutil.GetPodCondition(&pod.Status, v1.PodScheduled) + return cond != nil && cond.Status == v1.ConditionFalse && cond.Reason == v1.PodReasonUnschedulable +} + +// AddUnschedulableIfNotPresent does nothing if the pod is present in either +// queue. Otherwise it adds the pod to the unschedulable queue if +// p.receivedMoveRequest is false, and to the activeQ if p.receivedMoveRequest is true. +func (p *PriorityQueue) AddUnschedulableIfNotPresent(pod *v1.Pod) error { + p.lock.Lock() + defer p.lock.Unlock() + if p.unschedulableQ.Get(pod) != nil { + return fmt.Errorf("pod is already present in unschedulableQ") + } + if _, exists, _ := p.activeQ.Get(pod); exists { + return fmt.Errorf("pod is already present in the activeQ") + } + if !p.receivedMoveRequest && isPodUnschedulable(pod) { + p.unschedulableQ.Add(pod) + return nil + } + err := p.activeQ.Add(pod) + if err == nil { + p.cond.Broadcast() + } + return err +} + +// Pop removes the head of the active queue and returns it. It blocks if the +// activeQ is empty and waits until a new item is added to the queue. It also +// clears receivedMoveRequest to mark the beginning of a new scheduling cycle. +func (p *PriorityQueue) Pop() (*v1.Pod, error) { + p.lock.Lock() + defer p.lock.Unlock() + for len(p.activeQ.data.queue) == 0 { + p.cond.Wait() + } + obj, err := p.activeQ.Pop() + if err != nil { + return nil, err + } + p.receivedMoveRequest = false + return obj.(*v1.Pod), err +} + +// isPodUpdated checks if the pod is updated in a way that it may have become +// schedulable. It drops status of the pod and compares it with old version. +func isPodUpdated(oldPod, newPod *v1.Pod) bool { + strip := func(pod *v1.Pod) *v1.Pod { + p := pod.DeepCopy() + p.ResourceVersion = "" + p.Generation = 0 + p.Status = v1.PodStatus{} + return p + } + return !reflect.DeepEqual(strip(oldPod), strip(newPod)) +} + +// Update updates a pod in the active queue if present. Otherwise, it removes +// the item from the unschedulable queue and adds the updated one to the active +// queue. +func (p *PriorityQueue) Update(pod *v1.Pod) error { + p.lock.Lock() + defer p.lock.Unlock() + // If the pod is already in the active queue, just update it there. + if _, exists, _ := p.activeQ.Get(pod); exists { + err := p.activeQ.Update(pod) + return err + } + // If the pod is in the unschedulable queue, updating it may make it schedulable. + if oldPod := p.unschedulableQ.Get(pod); oldPod != nil { + if isPodUpdated(oldPod, pod) { + p.unschedulableQ.Delete(oldPod) + err := p.activeQ.Add(pod) + if err == nil { + p.cond.Broadcast() + } + return err + } else { + p.unschedulableQ.Update(pod) + return nil + } + } + // If pod is not in any of the two queue, we put it in the active queue. + err := p.activeQ.Add(pod) + if err == nil { + p.cond.Broadcast() + } + return err +} + +// Delete deletes the item from either of the two queues. It assumes the pod is +// only in one queue. +func (p *PriorityQueue) Delete(pod *v1.Pod) error { + p.lock.Lock() + defer p.lock.Unlock() + if _, exists, _ := p.activeQ.Get(pod); exists { + return p.activeQ.Delete(pod) + } + p.unschedulableQ.Delete(pod) + return nil +} + +// AssignedPodAdded is called when a bound pod is added. Creation of this pod +// may make pending pods with matching affinity terms schedulable. +func (p *PriorityQueue) AssignedPodAdded(pod *v1.Pod) { + p.movePodsToActiveQueue(p.getUnschedulablePodsWithMatchingAffinityTerm(pod)) +} + +// AssignedPodUpdated is called when a bound pod is updated. Change of labels +// may make pending pods with matching affinity terms schedulable. +func (p *PriorityQueue) AssignedPodUpdated(pod *v1.Pod) { + p.movePodsToActiveQueue(p.getUnschedulablePodsWithMatchingAffinityTerm(pod)) +} + +// MoveAllToActiveQueue moves all pods from unschedulableQ to activeQ. This +// function adds all pods and then signals the condition variable to ensure that +// if Pop() is waiting for an item, it receives it after all the pods are in the +// queue and the head is the highest priority pod. +// TODO(bsalamat): We should add a back-off mechanism here so that a high priority +// pod which is unschedulable does not go to the head of the queue frequently. For +// example in a cluster where a lot of pods being deleted, such a high priority +// pod can deprive other pods from getting scheduled. +func (p *PriorityQueue) MoveAllToActiveQueue() { + p.lock.Lock() + defer p.lock.Unlock() + var unschedulablePods []interface{} + for _, pod := range p.unschedulableQ.pods { + unschedulablePods = append(unschedulablePods, pod) + } + p.activeQ.BulkAdd(unschedulablePods) + p.unschedulableQ.Clear() + p.receivedMoveRequest = true + p.cond.Broadcast() +} + +func (p *PriorityQueue) movePodsToActiveQueue(pods []*v1.Pod) { + p.lock.Lock() + defer p.lock.Unlock() + for _, pod := range pods { + p.activeQ.Add(pod) + p.unschedulableQ.Delete(pod) + } + p.receivedMoveRequest = true + p.cond.Broadcast() +} + +// getUnschedulablePodsWithMatchingAffinityTerm returns unschedulable pods which have +// any affinity term that matches "pod". +func (p *PriorityQueue) getUnschedulablePodsWithMatchingAffinityTerm(pod *v1.Pod) []*v1.Pod { + p.lock.RLock() + defer p.lock.RUnlock() + podsToMove := []*v1.Pod{} + for _, up := range p.unschedulableQ.pods { + affinity := up.Spec.Affinity + if affinity != nil && affinity.PodAffinity != nil { + terms := predicates.GetPodAffinityTerms(affinity.PodAffinity) + for _, term := range terms { + namespaces := priorityutil.GetNamespacesFromPodAffinityTerm(up, &term) + selector, err := metav1.LabelSelectorAsSelector(term.LabelSelector) + if err != nil { + glog.Errorf("Error getting label selectors for pod: %v.", up.Name) + } + if priorityutil.PodMatchesTermsNamespaceAndSelector(pod, namespaces, selector) { + podsToMove = append(podsToMove, up) + } + } + } + } + return podsToMove +} + +// WaitingPodsForNode returns pods that are nominated to run on the given node, +// but they are waiting for other pods to be removed from the node before they +// can be actually scheduled. +func (p *PriorityQueue) WaitingPodsForNode(nodeName string) []*v1.Pod { + p.lock.RLock() + defer p.lock.RUnlock() + pods := p.unschedulableQ.GetPodsWaitingForNode(nodeName) + for _, obj := range p.activeQ.List() { + pod := obj.(*v1.Pod) + if pod.Annotations != nil { + if n, ok := pod.Annotations[NominatedNodeAnnotationKey]; ok && n == nodeName { + pods = append(pods, pod) + } + } + } + return pods +} + +// UnschedulablePodsMap holds pods that cannot be scheduled. This data structure +// is used to implement unschedulableQ. +type UnschedulablePodsMap struct { + // pods is a map key by a pod's full-name and the value is a pointer to the pod. + pods map[string]*v1.Pod + // nominatedPods is a map keyed by a node name and the value is a list of + // pods' full-names which are nominated to run on the node. + nominatedPods map[string][]string + keyFunc func(*v1.Pod) string +} + +var _ = UnschedulablePods(&UnschedulablePodsMap{}) + +func NominatedNodeName(pod *v1.Pod) string { + nominatedNodeName, ok := pod.Annotations[NominatedNodeAnnotationKey] + if !ok { + return "" + } + return nominatedNodeName +} + +// Add adds a pod to the unschedulable pods. +func (u *UnschedulablePodsMap) Add(pod *v1.Pod) { + podKey := u.keyFunc(pod) + if _, exists := u.pods[podKey]; !exists { + u.pods[podKey] = pod + nominatedNodeName := NominatedNodeName(pod) + if len(nominatedNodeName) > 0 { + u.nominatedPods[nominatedNodeName] = append(u.nominatedPods[nominatedNodeName], podKey) + } + } +} + +func (u *UnschedulablePodsMap) deleteFromNominated(pod *v1.Pod) { + nominatedNodeName := NominatedNodeName(pod) + if len(nominatedNodeName) > 0 { + podKey := u.keyFunc(pod) + nps := u.nominatedPods[nominatedNodeName] + for i, np := range nps { + if np == podKey { + u.nominatedPods[nominatedNodeName] = append(nps[:i], nps[i+1:]...) + if len(u.nominatedPods[nominatedNodeName]) == 0 { + delete(u.nominatedPods, nominatedNodeName) + } + break + } + } + } +} + +// Delete deletes a pod from the unschedulable pods. +func (u *UnschedulablePodsMap) Delete(pod *v1.Pod) { + podKey := u.keyFunc(pod) + if p, exists := u.pods[podKey]; exists { + u.deleteFromNominated(p) + delete(u.pods, podKey) + } +} + +// Update updates a pod in the unschedulable pods. +func (u *UnschedulablePodsMap) Update(pod *v1.Pod) { + podKey := u.keyFunc(pod) + oldPod, exists := u.pods[podKey] + if !exists { + u.Add(pod) + return + } + u.pods[podKey] = pod + oldNominateNodeName := NominatedNodeName(oldPod) + nominatedNodeName := NominatedNodeName(pod) + if oldNominateNodeName != nominatedNodeName { + u.deleteFromNominated(oldPod) + if len(nominatedNodeName) > 0 { + u.nominatedPods[nominatedNodeName] = append(u.nominatedPods[nominatedNodeName], podKey) + } + } +} + +// Get returns the pod if a pod with the same key as the key of the given "pod" +// is found in the map. It returns nil otherwise. +func (u *UnschedulablePodsMap) Get(pod *v1.Pod) *v1.Pod { + podKey := u.keyFunc(pod) + if p, exists := u.pods[podKey]; exists { + return p + } + return nil +} + +// GetPodsWaitingForNode returns a list of unschedulable pods whose NominatedNodeNames +// are equal to the given nodeName. +func (u *UnschedulablePodsMap) GetPodsWaitingForNode(nodeName string) []*v1.Pod { + var pods []*v1.Pod + for _, key := range u.nominatedPods[nodeName] { + pods = append(pods, u.pods[key]) + } + return pods +} + +// Clear removes all the entries from the unschedulable maps. +func (u *UnschedulablePodsMap) Clear() { + u.pods = make(map[string]*v1.Pod) + u.nominatedPods = make(map[string][]string) +} + +// newUnschedulablePodsMap initializes a new object of UnschedulablePodsMap. +func newUnschedulablePodsMap() *UnschedulablePodsMap { + return &UnschedulablePodsMap{ + pods: make(map[string]*v1.Pod), + nominatedPods: make(map[string][]string), + keyFunc: util.GetPodFullName, + } +} + +// Below is the implementation of the a heap. The logic is pretty much the same +// as cache.heap, however, this heap does not perform synchronization. It leaves +// synchronization to the SchedulingQueue. + +type LessFunc func(interface{}, interface{}) bool +type KeyFunc func(obj interface{}) (string, error) + +type heapItem struct { + obj interface{} // The object which is stored in the heap. + index int // The index of the object's key in the Heap.queue. +} + +type itemKeyValue struct { + key string + obj interface{} +} + +// heapData is an internal struct that implements the standard heap interface +// and keeps the data stored in the heap. +type heapData struct { + // items is a map from key of the objects to the objects and their index. + // We depend on the property that items in the map are in the queue and vice versa. + items map[string]*heapItem + // queue implements a heap data structure and keeps the order of elements + // according to the heap invariant. The queue keeps the keys of objects stored + // in "items". + queue []string + + // keyFunc is used to make the key used for queued item insertion and retrieval, and + // should be deterministic. + keyFunc KeyFunc + // lessFunc is used to compare two objects in the heap. + lessFunc LessFunc +} + +var ( + _ = heap.Interface(&heapData{}) // heapData is a standard heap +) + +// Less compares two objects and returns true if the first one should go +// in front of the second one in the heap. +func (h *heapData) Less(i, j int) bool { + if i > len(h.queue) || j > len(h.queue) { + return false + } + itemi, ok := h.items[h.queue[i]] + if !ok { + return false + } + itemj, ok := h.items[h.queue[j]] + if !ok { + return false + } + return h.lessFunc(itemi.obj, itemj.obj) +} + +// Len returns the number of items in the Heap. +func (h *heapData) Len() int { return len(h.queue) } + +// Swap implements swapping of two elements in the heap. This is a part of standard +// heap interface and should never be called directly. +func (h *heapData) Swap(i, j int) { + h.queue[i], h.queue[j] = h.queue[j], h.queue[i] + item := h.items[h.queue[i]] + item.index = i + item = h.items[h.queue[j]] + item.index = j +} + +// Push is supposed to be called by heap.Push only. +func (h *heapData) Push(kv interface{}) { + keyValue := kv.(*itemKeyValue) + n := len(h.queue) + h.items[keyValue.key] = &heapItem{keyValue.obj, n} + h.queue = append(h.queue, keyValue.key) +} + +// Pop is supposed to be called by heap.Pop only. +func (h *heapData) Pop() interface{} { + key := h.queue[len(h.queue)-1] + h.queue = h.queue[0 : len(h.queue)-1] + item, ok := h.items[key] + if !ok { + // This is an error + return nil + } + delete(h.items, key) + return item.obj +} + +// Heap is a producer/consumer queue that implements a heap data structure. +// It can be used to implement priority queues and similar data structures. +type Heap struct { + // data stores objects and has a queue that keeps their ordering according + // to the heap invariant. + data *heapData +} + +// Add inserts an item, and puts it in the queue. The item is updated if it +// already exists. +func (h *Heap) Add(obj interface{}) error { + key, err := h.data.keyFunc(obj) + if err != nil { + return cache.KeyError{Obj: obj, Err: err} + } + if _, exists := h.data.items[key]; exists { + h.data.items[key].obj = obj + heap.Fix(h.data, h.data.items[key].index) + } else { + heap.Push(h.data, &itemKeyValue{key, obj}) + } + return nil +} + +// BulkAdd adds all the items in the list to the queue. +func (h *Heap) BulkAdd(list []interface{}) error { + for _, obj := range list { + key, err := h.data.keyFunc(obj) + if err != nil { + return cache.KeyError{Obj: obj, Err: err} + } + if _, exists := h.data.items[key]; exists { + h.data.items[key].obj = obj + heap.Fix(h.data, h.data.items[key].index) + } else { + heap.Push(h.data, &itemKeyValue{key, obj}) + } + } + return nil +} + +// AddIfNotPresent inserts an item, and puts it in the queue. If an item with +// the key is present in the map, no changes is made to the item. +func (h *Heap) AddIfNotPresent(obj interface{}) error { + key, err := h.data.keyFunc(obj) + if err != nil { + return cache.KeyError{Obj: obj, Err: err} + } + if _, exists := h.data.items[key]; !exists { + heap.Push(h.data, &itemKeyValue{key, obj}) + } + return nil +} + +// Update is the same as Add in this implementation. When the item does not +// exist, it is added. +func (h *Heap) Update(obj interface{}) error { + return h.Add(obj) +} + +// Delete removes an item. +func (h *Heap) Delete(obj interface{}) error { + key, err := h.data.keyFunc(obj) + if err != nil { + return cache.KeyError{Obj: obj, Err: err} + } + if item, ok := h.data.items[key]; ok { + heap.Remove(h.data, item.index) + return nil + } + return fmt.Errorf("object not found") +} + +// Pop returns the head of the heap. +func (h *Heap) Pop() (interface{}, error) { + obj := heap.Pop(h.data) + if obj != nil { + return obj, nil + } else { + return nil, fmt.Errorf("object was removed from heap data") + } +} + +// Get returns the requested item, or sets exists=false. +func (h *Heap) Get(obj interface{}) (interface{}, bool, error) { + key, err := h.data.keyFunc(obj) + if err != nil { + return nil, false, cache.KeyError{Obj: obj, Err: err} + } + return h.GetByKey(key) +} + +// GetByKey returns the requested item, or sets exists=false. +func (h *Heap) GetByKey(key string) (interface{}, bool, error) { + item, exists := h.data.items[key] + if !exists { + return nil, false, nil + } + return item.obj, true, nil +} + +// List returns a list of all the items. +func (h *Heap) List() []interface{} { + list := make([]interface{}, 0, len(h.data.items)) + for _, item := range h.data.items { + list = append(list, item.obj) + } + return list +} + +// newHeap returns a Heap which can be used to queue up items to process. +func newHeap(keyFn KeyFunc, lessFn LessFunc) *Heap { + return &Heap{ + data: &heapData{ + items: map[string]*heapItem{}, + queue: []string{}, + keyFunc: keyFn, + lessFunc: lessFn, + }, + } +} diff --git a/pkg/scheduler/core/scheduling_queue_test.go b/pkg/scheduler/core/scheduling_queue_test.go new file mode 100644 index 00000000000..e69ae867cc2 --- /dev/null +++ b/pkg/scheduler/core/scheduling_queue_test.go @@ -0,0 +1,394 @@ +/* +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 core + +import ( + "reflect" + "sync" + "testing" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/pkg/scheduler/util" +) + +var mediumPriority = (lowPriority + highPriority) / 2 +var highPriorityPod, medPriorityPod, unschedulablePod = v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "hpp", + Namespace: "ns1", + }, + Spec: v1.PodSpec{ + Priority: &highPriority, + }, +}, + v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mpp", + Namespace: "ns2", + Annotations: map[string]string{ + NominatedNodeAnnotationKey: "node1", "annot2": "val2", + }, + }, + Spec: v1.PodSpec{ + Priority: &mediumPriority, + }, + }, + v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "up", + Namespace: "ns1", + Annotations: map[string]string{ + NominatedNodeAnnotationKey: "node1", "annot2": "val2", + }, + }, + Spec: v1.PodSpec{ + Priority: &lowPriority, + }, + Status: v1.PodStatus{ + Conditions: []v1.PodCondition{ + { + Type: v1.PodScheduled, + Status: v1.ConditionFalse, + Reason: v1.PodReasonUnschedulable, + }, + }, + }, + } + +func TestPriorityQueue_Add(t *testing.T) { + q := NewPriorityQueue() + q.Add(&medPriorityPod) + q.Add(&unschedulablePod) + q.Add(&highPriorityPod) + if p, err := q.Pop(); err != nil || p != &highPriorityPod { + t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPod.Name, p.Name) + } + if p, err := q.Pop(); err != nil || p != &medPriorityPod { + t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPod.Name, p.Name) + } + if p, err := q.Pop(); err != nil || p != &unschedulablePod { + t.Errorf("Expected: %v after Pop, but got: %v", unschedulablePod.Name, p.Name) + } +} + +func TestPriorityQueue_Pop(t *testing.T) { + q := NewPriorityQueue() + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + if p, err := q.Pop(); err != nil || p != &highPriorityPod { + t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPod.Name, p.Name) + } + }() + q.Add(&highPriorityPod) + wg.Wait() +} + +func TestPriorityQueue_Update(t *testing.T) { + q := NewPriorityQueue() + q.Update(&highPriorityPod) + if _, exists, _ := q.activeQ.Get(&highPriorityPod); !exists { + t.Errorf("Expected %v to be added to activeQ.", highPriorityPod.Name) + } + q.Update(&highPriorityPod) + if q.activeQ.data.Len() != 1 { + t.Error("Expected only one item in activeQ.") + } + // Updating an unschedulable pod which is not in any of the two queues, should + // add the pod to activeQ. + q.Update(&unschedulablePod) + if _, exists, _ := q.activeQ.Get(&unschedulablePod); !exists { + t.Errorf("Expected %v to be added to activeQ.", unschedulablePod.Name) + } + // Updating a pod that is already in unschedulableQ, should move the pod to + // activeQ. + q.Update(&unschedulablePod) + if len(q.unschedulableQ.pods) != 0 { + t.Error("Expected unschedulableQ to be empty.") + } + if _, exists, _ := q.activeQ.Get(&unschedulablePod); !exists { + t.Errorf("Expected: %v to be added to activeQ.", unschedulablePod.Name) + } + if p, err := q.Pop(); err != nil || p != &highPriorityPod { + t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPod.Name, p.Name) + } +} + +func TestPriorityQueue_Delete(t *testing.T) { + q := NewPriorityQueue() + q.Update(&highPriorityPod) + q.Add(&unschedulablePod) + q.Delete(&highPriorityPod) + if _, exists, _ := q.activeQ.Get(&unschedulablePod); !exists { + t.Errorf("Expected %v to be in activeQ.", unschedulablePod.Name) + } + if _, exists, _ := q.activeQ.Get(&highPriorityPod); exists { + t.Errorf("Didn't expect %v to be in activeQ.", highPriorityPod.Name) + } +} + +func TestPriorityQueue_MoveAllToActiveQueue(t *testing.T) { + q := NewPriorityQueue() + q.Add(&medPriorityPod) + q.unschedulableQ.Add(&unschedulablePod) + q.unschedulableQ.Add(&highPriorityPod) + q.MoveAllToActiveQueue() + if q.activeQ.data.Len() != 3 { + t.Error("Expected all items to be in activeQ.") + } +} + +// TestPriorityQueue_AssignedPodAdded tests AssignedPodAdded. It checks that +// when a pod with pod affinity is in unschedulableQ and another pod with a +// matching label is added, the unschedulable pod is moved to activeQ. +func TestPriorityQueue_AssignedPodAdded(t *testing.T) { + affinityPod := unschedulablePod.DeepCopy() + affinityPod.Name = "afp" + affinityPod.Spec = v1.PodSpec{ + Affinity: &v1.Affinity{ + PodAffinity: &v1.PodAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ + { + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "service", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"securityscan", "value2"}, + }, + }, + }, + TopologyKey: "region", + }, + }, + }, + }, + Priority: &mediumPriority, + } + labelPod := v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "lbp", + Namespace: affinityPod.Namespace, + Labels: map[string]string{"service": "securityscan"}, + }, + Spec: v1.PodSpec{NodeName: "machine1"}, + } + + q := NewPriorityQueue() + q.Add(&medPriorityPod) + // Add a couple of pods to the unschedulableQ. + q.unschedulableQ.Add(&unschedulablePod) + q.unschedulableQ.Add(affinityPod) + // Simulate addition of an assigned pod. The pod has matching labels for + // affinityPod. So, affinityPod should go to activeQ. + q.AssignedPodAdded(&labelPod) + if q.unschedulableQ.Get(affinityPod) != nil { + t.Error("affinityPod is still in the unschedulableQ.") + } + if _, exists, _ := q.activeQ.Get(affinityPod); !exists { + t.Error("affinityPod is not moved to activeQ.") + } + // Check that the other pod is still in the unschedulableQ. + if q.unschedulableQ.Get(&unschedulablePod) == nil { + t.Error("unschedulablePod is not in the unschedulableQ.") + } +} + +func TestUnschedulablePodsMap(t *testing.T) { + var pods = []*v1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "p0", + Namespace: "ns1", + Annotations: map[string]string{ + NominatedNodeAnnotationKey: "node1", "annot2": "val2", + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "p1", + Namespace: "ns1", + Annotations: map[string]string{ + "annot": "val", + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "p2", + Namespace: "ns2", + Annotations: map[string]string{ + NominatedNodeAnnotationKey: "node3", "annot2": "val2", "annot3": "val3", + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "p3", + Namespace: "ns4", + Annotations: map[string]string{ + NominatedNodeAnnotationKey: "node1", + }, + }, + }, + } + var updatedPods = make([]*v1.Pod, len(pods)) + updatedPods[0] = pods[0].DeepCopy() + updatedPods[0].Annotations[NominatedNodeAnnotationKey] = "node3" + updatedPods[1] = pods[1].DeepCopy() + updatedPods[1].Annotations[NominatedNodeAnnotationKey] = "node3" + updatedPods[3] = pods[3].DeepCopy() + delete(updatedPods[3].Annotations, NominatedNodeAnnotationKey) + + tests := []struct { + podsToAdd []*v1.Pod + expectedMapAfterAdd map[string]*v1.Pod + expectedNominatedAfterAdd map[string][]string + podsToUpdate []*v1.Pod + expectedMapAfterUpdate map[string]*v1.Pod + expectedNominatedAfterUpdate map[string][]string + podsToDelete []*v1.Pod + expectedMapAfterDelete map[string]*v1.Pod + expectedNominatedAfterDelete map[string][]string + }{ + { + podsToAdd: []*v1.Pod{pods[0], pods[1], pods[2], pods[3]}, + expectedMapAfterAdd: map[string]*v1.Pod{ + util.GetPodFullName(pods[0]): pods[0], + util.GetPodFullName(pods[1]): pods[1], + util.GetPodFullName(pods[2]): pods[2], + util.GetPodFullName(pods[3]): pods[3], + }, + expectedNominatedAfterAdd: map[string][]string{ + "node1": {util.GetPodFullName(pods[0]), util.GetPodFullName(pods[3])}, + "node3": {util.GetPodFullName(pods[2])}, + }, + podsToUpdate: []*v1.Pod{updatedPods[0]}, + expectedMapAfterUpdate: map[string]*v1.Pod{ + util.GetPodFullName(pods[0]): updatedPods[0], + util.GetPodFullName(pods[1]): pods[1], + util.GetPodFullName(pods[2]): pods[2], + util.GetPodFullName(pods[3]): pods[3], + }, + expectedNominatedAfterUpdate: map[string][]string{ + "node1": {util.GetPodFullName(pods[3])}, + "node3": {util.GetPodFullName(pods[2]), util.GetPodFullName(pods[0])}, + }, + podsToDelete: []*v1.Pod{pods[0], pods[1]}, + expectedMapAfterDelete: map[string]*v1.Pod{ + util.GetPodFullName(pods[2]): pods[2], + util.GetPodFullName(pods[3]): pods[3], + }, + expectedNominatedAfterDelete: map[string][]string{ + "node1": {util.GetPodFullName(pods[3])}, + "node3": {util.GetPodFullName(pods[2])}, + }, + }, + { + podsToAdd: []*v1.Pod{pods[0], pods[3]}, + expectedMapAfterAdd: map[string]*v1.Pod{ + util.GetPodFullName(pods[0]): pods[0], + util.GetPodFullName(pods[3]): pods[3], + }, + expectedNominatedAfterAdd: map[string][]string{ + "node1": {util.GetPodFullName(pods[0]), util.GetPodFullName(pods[3])}, + }, + podsToUpdate: []*v1.Pod{updatedPods[3]}, + expectedMapAfterUpdate: map[string]*v1.Pod{ + util.GetPodFullName(pods[0]): pods[0], + util.GetPodFullName(pods[3]): updatedPods[3], + }, + expectedNominatedAfterUpdate: map[string][]string{ + "node1": {util.GetPodFullName(pods[0])}, + }, + podsToDelete: []*v1.Pod{pods[0], pods[3]}, + expectedMapAfterDelete: map[string]*v1.Pod{}, + expectedNominatedAfterDelete: map[string][]string{}, + }, + { + podsToAdd: []*v1.Pod{pods[1], pods[2]}, + expectedMapAfterAdd: map[string]*v1.Pod{ + util.GetPodFullName(pods[1]): pods[1], + util.GetPodFullName(pods[2]): pods[2], + }, + expectedNominatedAfterAdd: map[string][]string{ + "node3": {util.GetPodFullName(pods[2])}, + }, + podsToUpdate: []*v1.Pod{updatedPods[1]}, + expectedMapAfterUpdate: map[string]*v1.Pod{ + util.GetPodFullName(pods[1]): updatedPods[1], + util.GetPodFullName(pods[2]): pods[2], + }, + expectedNominatedAfterUpdate: map[string][]string{ + "node3": {util.GetPodFullName(pods[2]), util.GetPodFullName(updatedPods[1])}, + }, + podsToDelete: []*v1.Pod{pods[2], pods[3]}, + expectedMapAfterDelete: map[string]*v1.Pod{ + util.GetPodFullName(pods[1]): updatedPods[1], + }, + expectedNominatedAfterDelete: map[string][]string{ + "node3": {util.GetPodFullName(updatedPods[1])}, + }, + }, + } + + for i, test := range tests { + upm := newUnschedulablePodsMap() + for _, p := range test.podsToAdd { + upm.Add(p) + } + if !reflect.DeepEqual(upm.pods, test.expectedMapAfterAdd) { + t.Errorf("#%d: Unexpected map after adding pods. Expected: %v, got: %v", + i, test.expectedMapAfterAdd, upm.pods) + } + if !reflect.DeepEqual(upm.nominatedPods, test.expectedNominatedAfterAdd) { + t.Errorf("#%d: Unexpected nominated map after adding pods. Expected: %v, got: %v", + i, test.expectedNominatedAfterAdd, upm.nominatedPods) + } + if len(test.podsToUpdate) > 0 { + for _, p := range test.podsToUpdate { + upm.Update(p) + } + if !reflect.DeepEqual(upm.pods, test.expectedMapAfterUpdate) { + t.Errorf("#%d: Unexpected map after updating pods. Expected: %v, got: %v", + i, test.expectedMapAfterUpdate, upm.pods) + } + if !reflect.DeepEqual(upm.nominatedPods, test.expectedNominatedAfterUpdate) { + t.Errorf("#%d: Unexpected nominated map after updating pods. Expected: %v, got: %v", + i, test.expectedNominatedAfterUpdate, upm.nominatedPods) + } + } + for _, p := range test.podsToDelete { + upm.Delete(p) + } + if !reflect.DeepEqual(upm.pods, test.expectedMapAfterDelete) { + t.Errorf("#%d: Unexpected map after deleting pods. Expected: %v, got: %v", + i, test.expectedMapAfterDelete, upm.pods) + } + if !reflect.DeepEqual(upm.nominatedPods, test.expectedNominatedAfterDelete) { + t.Errorf("#%d: Unexpected nominated map after deleting pods. Expected: %v, got: %v", + i, test.expectedNominatedAfterDelete, upm.nominatedPods) + } + upm.Clear() + if len(upm.pods) != 0 { + t.Errorf("Expected the map to be empty, but has %v elements.", len(upm.pods)) + } + } +} diff --git a/pkg/scheduler/factory/BUILD b/pkg/scheduler/factory/BUILD new file mode 100644 index 00000000000..90645c5a22e --- /dev/null +++ b/pkg/scheduler/factory/BUILD @@ -0,0 +1,99 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_library( + name = "go_default_library", + srcs = [ + "factory.go", + "plugins.go", + ], + importpath = "k8s.io/kubernetes/pkg/scheduler/factory", + deps = [ + "//pkg/api/v1/pod:go_default_library", + "//pkg/apis/core/helper:go_default_library", + "//pkg/features:go_default_library", + "//pkg/kubelet/apis:go_default_library", + "//pkg/scheduler:go_default_library", + "//pkg/scheduler/algorithm:go_default_library", + "//pkg/scheduler/algorithm/predicates:go_default_library", + "//pkg/scheduler/algorithm/priorities:go_default_library", + "//pkg/scheduler/api:go_default_library", + "//pkg/scheduler/api/validation:go_default_library", + "//pkg/scheduler/core:go_default_library", + "//pkg/scheduler/schedulercache:go_default_library", + "//pkg/scheduler/util:go_default_library", + "//pkg/scheduler/volumebinder:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/policy/v1beta1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + "//vendor/k8s.io/client-go/informers/apps/v1beta1:go_default_library", + "//vendor/k8s.io/client-go/informers/core/v1:go_default_library", + "//vendor/k8s.io/client-go/informers/extensions/v1beta1:go_default_library", + "//vendor/k8s.io/client-go/informers/policy/v1beta1:go_default_library", + "//vendor/k8s.io/client-go/informers/storage/v1:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/client-go/listers/apps/v1beta1:go_default_library", + "//vendor/k8s.io/client-go/listers/core/v1:go_default_library", + "//vendor/k8s.io/client-go/listers/extensions/v1beta1:go_default_library", + "//vendor/k8s.io/client-go/listers/policy/v1beta1:go_default_library", + "//vendor/k8s.io/client-go/listers/storage/v1:go_default_library", + "//vendor/k8s.io/client-go/tools/cache:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "factory_test.go", + "plugins_test.go", + ], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/scheduler/factory", + deps = [ + "//pkg/api/legacyscheme:go_default_library", + "//pkg/api/testing:go_default_library", + "//pkg/scheduler/algorithm:go_default_library", + "//pkg/scheduler/api:go_default_library", + "//pkg/scheduler/api/latest:go_default_library", + "//pkg/scheduler/core:go_default_library", + "//pkg/scheduler/schedulercache:go_default_library", + "//pkg/scheduler/testing:go_default_library", + "//pkg/scheduler/util:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/client-go/informers:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + "//vendor/k8s.io/client-go/tools/cache:go_default_library", + "//vendor/k8s.io/client-go/util/testing:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/pkg/scheduler/factory/factory.go b/pkg/scheduler/factory/factory.go new file mode 100644 index 00000000000..04b7a585913 --- /dev/null +++ b/pkg/scheduler/factory/factory.go @@ -0,0 +1,1308 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package factory can set up a scheduler. This code is here instead of +// cmd/scheduler for both testability and reuse. +package factory + +import ( + "encoding/json" + "fmt" + "reflect" + "time" + + "github.com/golang/glog" + + "k8s.io/api/core/v1" + "k8s.io/api/policy/v1beta1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/sets" + utilfeature "k8s.io/apiserver/pkg/util/feature" + appsinformers "k8s.io/client-go/informers/apps/v1beta1" + coreinformers "k8s.io/client-go/informers/core/v1" + extensionsinformers "k8s.io/client-go/informers/extensions/v1beta1" + policyinformers "k8s.io/client-go/informers/policy/v1beta1" + storageinformers "k8s.io/client-go/informers/storage/v1" + clientset "k8s.io/client-go/kubernetes" + appslisters "k8s.io/client-go/listers/apps/v1beta1" + corelisters "k8s.io/client-go/listers/core/v1" + extensionslisters "k8s.io/client-go/listers/extensions/v1beta1" + policylisters "k8s.io/client-go/listers/policy/v1beta1" + storagelisters "k8s.io/client-go/listers/storage/v1" + "k8s.io/client-go/tools/cache" + podutil "k8s.io/kubernetes/pkg/api/v1/pod" + "k8s.io/kubernetes/pkg/apis/core/helper" + "k8s.io/kubernetes/pkg/features" + kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis" + "k8s.io/kubernetes/pkg/scheduler" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + "k8s.io/kubernetes/pkg/scheduler/algorithm/predicates" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/api/validation" + "k8s.io/kubernetes/pkg/scheduler/core" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" + "k8s.io/kubernetes/pkg/scheduler/util" + "k8s.io/kubernetes/pkg/scheduler/volumebinder" +) + +const ( + initialGetBackoff = 100 * time.Millisecond + maximalGetBackoff = time.Minute +) + +var ( + serviceAffinitySet = sets.NewString("ServiceAffinity") + matchInterPodAffinitySet = sets.NewString("MatchInterPodAffinity") + generalPredicatesSets = sets.NewString("GeneralPredicates") + noDiskConflictSet = sets.NewString("NoDiskConflict") + maxPDVolumeCountPredicateKeys = []string{"MaxGCEPDVolumeCount", "MaxAzureDiskVolumeCount", "MaxEBSVolumeCount"} +) + +// configFactory is the default implementation of the scheduler.Configurator interface. +type configFactory struct { + client clientset.Interface + // queue for pods that need scheduling + podQueue core.SchedulingQueue + // a means to list all known scheduled pods. + scheduledPodLister corelisters.PodLister + // a means to list all known scheduled pods and pods assumed to have been scheduled. + podLister algorithm.PodLister + // a means to list all nodes + nodeLister corelisters.NodeLister + // a means to list all PersistentVolumes + pVLister corelisters.PersistentVolumeLister + // a means to list all PersistentVolumeClaims + pVCLister corelisters.PersistentVolumeClaimLister + // a means to list all services + serviceLister corelisters.ServiceLister + // a means to list all controllers + controllerLister corelisters.ReplicationControllerLister + // a means to list all replicasets + replicaSetLister extensionslisters.ReplicaSetLister + // a means to list all statefulsets + statefulSetLister appslisters.StatefulSetLister + // a means to list all PodDisruptionBudgets + pdbLister policylisters.PodDisruptionBudgetLister + // a means to list all StorageClasses + storageClassLister storagelisters.StorageClassLister + + // Close this to stop all reflectors + StopEverything chan struct{} + + scheduledPodsHasSynced cache.InformerSynced + + schedulerCache schedulercache.Cache + + // SchedulerName of a scheduler is used to select which pods will be + // processed by this scheduler, based on pods's "spec.SchedulerName". + schedulerName string + + // RequiredDuringScheduling affinity is not symmetric, but there is an implicit PreferredDuringScheduling affinity rule + // corresponding to every RequiredDuringScheduling affinity rule. + // HardPodAffinitySymmetricWeight represents the weight of implicit PreferredDuringScheduling affinity rule, in the range 0-100. + hardPodAffinitySymmetricWeight int32 + + // Equivalence class cache + equivalencePodCache *core.EquivalenceCache + + // Enable equivalence class cache + enableEquivalenceClassCache bool + + // Handles volume binding decisions + volumeBinder *volumebinder.VolumeBinder +} + +// NewConfigFactory initializes the default implementation of a Configurator To encourage eventual privatization of the struct type, we only +// return the interface. +func NewConfigFactory( + schedulerName string, + client clientset.Interface, + nodeInformer coreinformers.NodeInformer, + podInformer coreinformers.PodInformer, + pvInformer coreinformers.PersistentVolumeInformer, + pvcInformer coreinformers.PersistentVolumeClaimInformer, + replicationControllerInformer coreinformers.ReplicationControllerInformer, + replicaSetInformer extensionsinformers.ReplicaSetInformer, + statefulSetInformer appsinformers.StatefulSetInformer, + serviceInformer coreinformers.ServiceInformer, + pdbInformer policyinformers.PodDisruptionBudgetInformer, + storageClassInformer storageinformers.StorageClassInformer, + hardPodAffinitySymmetricWeight int32, + enableEquivalenceClassCache bool, +) scheduler.Configurator { + stopEverything := make(chan struct{}) + schedulerCache := schedulercache.New(30*time.Second, stopEverything) + + // storageClassInformer is only enabled through VolumeScheduling feature gate + var storageClassLister storagelisters.StorageClassLister + if storageClassInformer != nil { + storageClassLister = storageClassInformer.Lister() + } + + c := &configFactory{ + client: client, + podLister: schedulerCache, + podQueue: core.NewSchedulingQueue(), + pVLister: pvInformer.Lister(), + pVCLister: pvcInformer.Lister(), + serviceLister: serviceInformer.Lister(), + controllerLister: replicationControllerInformer.Lister(), + replicaSetLister: replicaSetInformer.Lister(), + statefulSetLister: statefulSetInformer.Lister(), + pdbLister: pdbInformer.Lister(), + storageClassLister: storageClassLister, + schedulerCache: schedulerCache, + StopEverything: stopEverything, + schedulerName: schedulerName, + hardPodAffinitySymmetricWeight: hardPodAffinitySymmetricWeight, + enableEquivalenceClassCache: enableEquivalenceClassCache, + } + + c.scheduledPodsHasSynced = podInformer.Informer().HasSynced + // scheduled pod cache + podInformer.Informer().AddEventHandler( + cache.FilteringResourceEventHandler{ + FilterFunc: func(obj interface{}) bool { + switch t := obj.(type) { + case *v1.Pod: + return assignedNonTerminatedPod(t) + default: + runtime.HandleError(fmt.Errorf("unable to handle object in %T: %T", c, obj)) + return false + } + }, + Handler: cache.ResourceEventHandlerFuncs{ + AddFunc: c.addPodToCache, + UpdateFunc: c.updatePodInCache, + DeleteFunc: c.deletePodFromCache, + }, + }, + ) + // unscheduled pod queue + podInformer.Informer().AddEventHandler( + cache.FilteringResourceEventHandler{ + FilterFunc: func(obj interface{}) bool { + switch t := obj.(type) { + case *v1.Pod: + return unassignedNonTerminatedPod(t) + default: + runtime.HandleError(fmt.Errorf("unable to handle object in %T: %T", c, obj)) + return false + } + }, + Handler: cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + if err := c.podQueue.Add(obj.(*v1.Pod)); err != nil { + runtime.HandleError(fmt.Errorf("unable to queue %T: %v", obj, err)) + } + }, + UpdateFunc: func(oldObj, newObj interface{}) { + pod := newObj.(*v1.Pod) + if c.skipPodUpdate(pod) { + return + } + if err := c.podQueue.Update(pod); err != nil { + runtime.HandleError(fmt.Errorf("unable to update %T: %v", newObj, err)) + } + }, + DeleteFunc: func(obj interface{}) { + pod := obj.(*v1.Pod) + if err := c.podQueue.Delete(pod); err != nil { + runtime.HandleError(fmt.Errorf("unable to dequeue %T: %v", obj, err)) + } + if c.volumeBinder != nil { + // Volume binder only wants to keep unassigned pods + c.volumeBinder.DeletePodBindings(pod) + } + }, + }, + }, + ) + // ScheduledPodLister is something we provide to plug-in functions that + // they may need to call. + c.scheduledPodLister = assignedPodLister{podInformer.Lister()} + + nodeInformer.Informer().AddEventHandler( + cache.ResourceEventHandlerFuncs{ + AddFunc: c.addNodeToCache, + UpdateFunc: c.updateNodeInCache, + DeleteFunc: c.deleteNodeFromCache, + }, + ) + c.nodeLister = nodeInformer.Lister() + + pdbInformer.Informer().AddEventHandler( + cache.ResourceEventHandlerFuncs{ + AddFunc: c.addPDBToCache, + UpdateFunc: c.updatePDBInCache, + DeleteFunc: c.deletePDBFromCache, + }, + ) + c.pdbLister = pdbInformer.Lister() + + // On add and delete of PVs, it will affect equivalence cache items + // related to persistent volume + pvInformer.Informer().AddEventHandler( + cache.ResourceEventHandlerFuncs{ + // MaxPDVolumeCountPredicate: since it relies on the counts of PV. + AddFunc: c.onPvAdd, + DeleteFunc: c.onPvDelete, + }, + ) + c.pVLister = pvInformer.Lister() + + // This is for MaxPDVolumeCountPredicate: add/delete PVC will affect counts of PV when it is bound. + pvcInformer.Informer().AddEventHandler( + cache.ResourceEventHandlerFuncs{ + AddFunc: c.onPvcAdd, + UpdateFunc: c.onPvcUpdate, + DeleteFunc: c.onPvcDelete, + }, + ) + c.pVCLister = pvcInformer.Lister() + + // This is for ServiceAffinity: affected by the selector of the service is updated. + // Also, if new service is added, equivalence cache will also become invalid since + // existing pods may be "captured" by this service and change this predicate result. + serviceInformer.Informer().AddEventHandler( + cache.ResourceEventHandlerFuncs{ + AddFunc: c.onServiceAdd, + UpdateFunc: c.onServiceUpdate, + DeleteFunc: c.onServiceDelete, + }, + ) + c.serviceLister = serviceInformer.Lister() + + // Existing equivalence cache should not be affected by add/delete RC/Deployment etc, + // it only make sense when pod is scheduled or deleted + + if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) { + // Setup volume binder + c.volumeBinder = volumebinder.NewVolumeBinder(client, pvcInformer, pvInformer, nodeInformer, storageClassInformer) + } + + return c +} + +// skipPodUpdate checks whether the specified pod update should be ignored. +// This function will return true if +// - The pod has already been assumed, AND +// - The pod has only its ResourceVersion, Spec.NodeName and/or Annotations +// updated. +func (c *configFactory) skipPodUpdate(pod *v1.Pod) bool { + // Non-assumed pods should never be skipped. + isAssumed, err := c.schedulerCache.IsAssumedPod(pod) + if err != nil { + runtime.HandleError(fmt.Errorf("failed to check whether pod %s/%s is assumed: %v", pod.Namespace, pod.Name, err)) + return false + } + if !isAssumed { + return false + } + + // Gets the assumed pod from the cache. + assumedPod, err := c.schedulerCache.GetPod(pod) + if err != nil { + runtime.HandleError(fmt.Errorf("failed to get assumed pod %s/%s from cache: %v", pod.Namespace, pod.Name, err)) + return false + } + + // Compares the assumed pod in the cache with the pod update. If they are + // equal (with certain fields excluded), this pod update will be skipped. + f := func(pod *v1.Pod) *v1.Pod { + p := pod.DeepCopy() + // ResourceVersion must be excluded because each object update will + // have a new resource version. + p.ResourceVersion = "" + // Spec.NodeName must be excluded because the pod assumed in the cache + // is expected to have a node assigned while the pod update may nor may + // not have this field set. + p.Spec.NodeName = "" + // Annotations must be excluded for the reasons described in + // https://github.com/kubernetes/kubernetes/issues/52914. + p.Annotations = nil + return p + } + assumedPodCopy, podCopy := f(assumedPod), f(pod) + if !reflect.DeepEqual(assumedPodCopy, podCopy) { + return false + } + glog.V(3).Infof("Skipping pod %s/%s update", pod.Namespace, pod.Name) + return true +} + +func (c *configFactory) onPvAdd(obj interface{}) { + if c.enableEquivalenceClassCache { + pv, ok := obj.(*v1.PersistentVolume) + if !ok { + glog.Errorf("cannot convert to *v1.PersistentVolume: %v", obj) + return + } + c.invalidatePredicatesForPv(pv) + } +} + +func (c *configFactory) onPvDelete(obj interface{}) { + if c.enableEquivalenceClassCache { + var pv *v1.PersistentVolume + switch t := obj.(type) { + case *v1.PersistentVolume: + pv = t + case cache.DeletedFinalStateUnknown: + var ok bool + pv, ok = t.Obj.(*v1.PersistentVolume) + if !ok { + glog.Errorf("cannot convert to *v1.PersistentVolume: %v", t.Obj) + return + } + default: + glog.Errorf("cannot convert to *v1.PersistentVolume: %v", t) + return + } + c.invalidatePredicatesForPv(pv) + } +} + +func (c *configFactory) invalidatePredicatesForPv(pv *v1.PersistentVolume) { + // You could have a PVC that points to a PV, but the PV object doesn't exist. + // So when the PV object gets added, we can recount. + invalidPredicates := sets.NewString() + + // PV types which impact MaxPDVolumeCountPredicate + if pv.Spec.AWSElasticBlockStore != nil { + invalidPredicates.Insert("MaxEBSVolumeCount") + } + if pv.Spec.GCEPersistentDisk != nil { + invalidPredicates.Insert("MaxGCEPDVolumeCount") + } + if pv.Spec.AzureDisk != nil { + invalidPredicates.Insert("MaxAzureDiskVolumeCount") + } + + // If PV contains zone related label, it may impact cached NoVolumeZoneConflict + for k := range pv.ObjectMeta.Labels { + if k == kubeletapis.LabelZoneFailureDomain || k == kubeletapis.LabelZoneRegion { + invalidPredicates.Insert("NoVolumeZoneConflict") + break + } + } + + if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) { + // Add/delete impacts the available PVs to choose from + invalidPredicates.Insert(predicates.CheckVolumeBindingPred) + } + + c.equivalencePodCache.InvalidateCachedPredicateItemOfAllNodes(invalidPredicates) +} + +func (c *configFactory) onPvcAdd(obj interface{}) { + if c.enableEquivalenceClassCache { + pvc, ok := obj.(*v1.PersistentVolumeClaim) + if !ok { + glog.Errorf("cannot convert to *v1.PersistentVolumeClaim: %v", obj) + return + } + c.invalidatePredicatesForPvc(pvc) + } + c.podQueue.MoveAllToActiveQueue() +} + +func (c *configFactory) onPvcUpdate(old, new interface{}) { + if !utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) { + return + } + + if c.enableEquivalenceClassCache { + newPVC, ok := new.(*v1.PersistentVolumeClaim) + if !ok { + glog.Errorf("cannot convert to *v1.PersistentVolumeClaim: %v", new) + return + } + oldPVC, ok := old.(*v1.PersistentVolumeClaim) + if !ok { + glog.Errorf("cannot convert to *v1.PersistentVolumeClaim: %v", old) + return + } + c.invalidatePredicatesForPvcUpdate(oldPVC, newPVC) + } + c.podQueue.MoveAllToActiveQueue() +} + +func (c *configFactory) onPvcDelete(obj interface{}) { + if c.enableEquivalenceClassCache { + var pvc *v1.PersistentVolumeClaim + switch t := obj.(type) { + case *v1.PersistentVolumeClaim: + pvc = t + case cache.DeletedFinalStateUnknown: + var ok bool + pvc, ok = t.Obj.(*v1.PersistentVolumeClaim) + if !ok { + glog.Errorf("cannot convert to *v1.PersistentVolumeClaim: %v", t.Obj) + return + } + default: + glog.Errorf("cannot convert to *v1.PersistentVolumeClaim: %v", t) + return + } + c.invalidatePredicatesForPvc(pvc) + } +} + +func (c *configFactory) invalidatePredicatesForPvc(pvc *v1.PersistentVolumeClaim) { + // We need to do this here because the ecache uses PVC uid as part of equivalence hash of pod + + // The bound volume type may change + invalidPredicates := sets.NewString(maxPDVolumeCountPredicateKeys...) + + // The bound volume's label may change + invalidPredicates.Insert("NoVolumeZoneConflict") + + if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) { + // Add/delete impacts the available PVs to choose from + invalidPredicates.Insert(predicates.CheckVolumeBindingPred) + } + c.equivalencePodCache.InvalidateCachedPredicateItemOfAllNodes(invalidPredicates) +} + +func (c *configFactory) invalidatePredicatesForPvcUpdate(old, new *v1.PersistentVolumeClaim) { + invalidPredicates := sets.NewString() + + if old.Spec.VolumeName != new.Spec.VolumeName { + if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) { + // PVC volume binding has changed + invalidPredicates.Insert(predicates.CheckVolumeBindingPred) + } + // The bound volume type may change + invalidPredicates.Insert(maxPDVolumeCountPredicateKeys...) + // The bound volume's label may change + invalidPredicates.Insert("NoVolumeZoneConflict") + } + + c.equivalencePodCache.InvalidateCachedPredicateItemOfAllNodes(invalidPredicates) +} + +func (c *configFactory) onServiceAdd(obj interface{}) { + if c.enableEquivalenceClassCache { + c.equivalencePodCache.InvalidateCachedPredicateItemOfAllNodes(serviceAffinitySet) + } + c.podQueue.MoveAllToActiveQueue() +} + +func (c *configFactory) onServiceUpdate(oldObj interface{}, newObj interface{}) { + if c.enableEquivalenceClassCache { + // TODO(resouer) We may need to invalidate this for specified group of pods only + oldService := oldObj.(*v1.Service) + newService := newObj.(*v1.Service) + if !reflect.DeepEqual(oldService.Spec.Selector, newService.Spec.Selector) { + c.equivalencePodCache.InvalidateCachedPredicateItemOfAllNodes(serviceAffinitySet) + } + } + c.podQueue.MoveAllToActiveQueue() +} + +func (c *configFactory) onServiceDelete(obj interface{}) { + if c.enableEquivalenceClassCache { + c.equivalencePodCache.InvalidateCachedPredicateItemOfAllNodes(serviceAffinitySet) + } + c.podQueue.MoveAllToActiveQueue() +} + +// GetNodeStore provides the cache to the nodes, mostly internal use, but may also be called by mock-tests. +func (c *configFactory) GetNodeLister() corelisters.NodeLister { + return c.nodeLister +} + +func (c *configFactory) GetHardPodAffinitySymmetricWeight() int32 { + return c.hardPodAffinitySymmetricWeight +} + +func (f *configFactory) GetSchedulerName() string { + return f.schedulerName +} + +// GetClient provides a kubernetes client, mostly internal use, but may also be called by mock-tests. +func (f *configFactory) GetClient() clientset.Interface { + return f.client +} + +// GetScheduledPodListerIndexer provides a pod lister, mostly internal use, but may also be called by mock-tests. +func (c *configFactory) GetScheduledPodLister() corelisters.PodLister { + return c.scheduledPodLister +} + +func (c *configFactory) addPodToCache(obj interface{}) { + pod, ok := obj.(*v1.Pod) + if !ok { + glog.Errorf("cannot convert to *v1.Pod: %v", obj) + return + } + + if err := c.schedulerCache.AddPod(pod); err != nil { + glog.Errorf("scheduler cache AddPod failed: %v", err) + } + + c.podQueue.AssignedPodAdded(pod) + + // NOTE: Updating equivalence cache of addPodToCache has been + // handled optimistically in: pkg/scheduler/scheduler.go#assume() +} + +func (c *configFactory) updatePodInCache(oldObj, newObj interface{}) { + oldPod, ok := oldObj.(*v1.Pod) + if !ok { + glog.Errorf("cannot convert oldObj to *v1.Pod: %v", oldObj) + return + } + newPod, ok := newObj.(*v1.Pod) + if !ok { + glog.Errorf("cannot convert newObj to *v1.Pod: %v", newObj) + return + } + + if err := c.schedulerCache.UpdatePod(oldPod, newPod); err != nil { + glog.Errorf("scheduler cache UpdatePod failed: %v", err) + } + + c.invalidateCachedPredicatesOnUpdatePod(newPod, oldPod) + c.podQueue.AssignedPodUpdated(newPod) +} + +func (c *configFactory) invalidateCachedPredicatesOnUpdatePod(newPod *v1.Pod, oldPod *v1.Pod) { + if c.enableEquivalenceClassCache { + // if the pod does not have bound node, updating equivalence cache is meaningless; + // if pod's bound node has been changed, that case should be handled by pod add & delete. + if len(newPod.Spec.NodeName) != 0 && newPod.Spec.NodeName == oldPod.Spec.NodeName { + if !reflect.DeepEqual(oldPod.GetLabels(), newPod.GetLabels()) { + // MatchInterPodAffinity need to be reconsidered for this node, + // as well as all nodes in its same failure domain. + c.equivalencePodCache.InvalidateCachedPredicateItemOfAllNodes( + matchInterPodAffinitySet) + } + // if requested container resource changed, invalidate GeneralPredicates of this node + if !reflect.DeepEqual(predicates.GetResourceRequest(newPod), + predicates.GetResourceRequest(oldPod)) { + c.equivalencePodCache.InvalidateCachedPredicateItem( + newPod.Spec.NodeName, generalPredicatesSets) + } + } + } +} + +func (c *configFactory) deletePodFromCache(obj interface{}) { + var pod *v1.Pod + switch t := obj.(type) { + case *v1.Pod: + pod = t + case cache.DeletedFinalStateUnknown: + var ok bool + pod, ok = t.Obj.(*v1.Pod) + if !ok { + glog.Errorf("cannot convert to *v1.Pod: %v", t.Obj) + return + } + default: + glog.Errorf("cannot convert to *v1.Pod: %v", t) + return + } + if err := c.schedulerCache.RemovePod(pod); err != nil { + glog.Errorf("scheduler cache RemovePod failed: %v", err) + } + + c.invalidateCachedPredicatesOnDeletePod(pod) + c.podQueue.MoveAllToActiveQueue() +} + +func (c *configFactory) invalidateCachedPredicatesOnDeletePod(pod *v1.Pod) { + if c.enableEquivalenceClassCache { + // part of this case is the same as pod add. + c.equivalencePodCache.InvalidateCachedPredicateItemForPodAdd(pod, pod.Spec.NodeName) + // MatchInterPodAffinity need to be reconsidered for this node, + // as well as all nodes in its same failure domain. + // TODO(resouer) can we just do this for nodes in the same failure domain + c.equivalencePodCache.InvalidateCachedPredicateItemOfAllNodes( + matchInterPodAffinitySet) + + // if this pod have these PV, cached result of disk conflict will become invalid. + for _, volume := range pod.Spec.Volumes { + if volume.GCEPersistentDisk != nil || volume.AWSElasticBlockStore != nil || + volume.RBD != nil || volume.ISCSI != nil { + c.equivalencePodCache.InvalidateCachedPredicateItem( + pod.Spec.NodeName, noDiskConflictSet) + } + } + } +} + +func (c *configFactory) addNodeToCache(obj interface{}) { + node, ok := obj.(*v1.Node) + if !ok { + glog.Errorf("cannot convert to *v1.Node: %v", obj) + return + } + + if err := c.schedulerCache.AddNode(node); err != nil { + glog.Errorf("scheduler cache AddNode failed: %v", err) + } + + c.podQueue.MoveAllToActiveQueue() + // NOTE: add a new node does not affect existing predicates in equivalence cache +} + +func (c *configFactory) updateNodeInCache(oldObj, newObj interface{}) { + oldNode, ok := oldObj.(*v1.Node) + if !ok { + glog.Errorf("cannot convert oldObj to *v1.Node: %v", oldObj) + return + } + newNode, ok := newObj.(*v1.Node) + if !ok { + glog.Errorf("cannot convert newObj to *v1.Node: %v", newObj) + return + } + + if err := c.schedulerCache.UpdateNode(oldNode, newNode); err != nil { + glog.Errorf("scheduler cache UpdateNode failed: %v", err) + } + + c.invalidateCachedPredicatesOnNodeUpdate(newNode, oldNode) + c.podQueue.MoveAllToActiveQueue() +} + +func (c *configFactory) invalidateCachedPredicatesOnNodeUpdate(newNode *v1.Node, oldNode *v1.Node) { + if c.enableEquivalenceClassCache { + // Begin to update equivalence cache based on node update + // TODO(resouer): think about lazily initialize this set + invalidPredicates := sets.NewString() + + if !reflect.DeepEqual(oldNode.Status.Allocatable, newNode.Status.Allocatable) { + invalidPredicates.Insert("GeneralPredicates") // "PodFitsResources" + } + if !reflect.DeepEqual(oldNode.GetLabels(), newNode.GetLabels()) { + invalidPredicates.Insert("GeneralPredicates", "ServiceAffinity") // "PodSelectorMatches" + for k, v := range oldNode.GetLabels() { + // any label can be topology key of pod, we have to invalidate in all cases + if v != newNode.GetLabels()[k] { + invalidPredicates.Insert("MatchInterPodAffinity") + } + // NoVolumeZoneConflict will only be affected by zone related label change + if k == kubeletapis.LabelZoneFailureDomain || k == kubeletapis.LabelZoneRegion { + if v != newNode.GetLabels()[k] { + invalidPredicates.Insert("NoVolumeZoneConflict") + } + } + } + } + + oldTaints, oldErr := helper.GetTaintsFromNodeAnnotations(oldNode.GetAnnotations()) + if oldErr != nil { + glog.Errorf("Failed to get taints from old node annotation for equivalence cache") + } + newTaints, newErr := helper.GetTaintsFromNodeAnnotations(newNode.GetAnnotations()) + if newErr != nil { + glog.Errorf("Failed to get taints from new node annotation for equivalence cache") + } + if !reflect.DeepEqual(oldTaints, newTaints) || + !reflect.DeepEqual(oldNode.Spec.Taints, newNode.Spec.Taints) { + invalidPredicates.Insert("PodToleratesNodeTaints") + } + + if !reflect.DeepEqual(oldNode.Status.Conditions, newNode.Status.Conditions) { + oldConditions := make(map[v1.NodeConditionType]v1.ConditionStatus) + newConditions := make(map[v1.NodeConditionType]v1.ConditionStatus) + for _, cond := range oldNode.Status.Conditions { + oldConditions[cond.Type] = cond.Status + } + for _, cond := range newNode.Status.Conditions { + newConditions[cond.Type] = cond.Status + } + if oldConditions[v1.NodeMemoryPressure] != newConditions[v1.NodeMemoryPressure] { + invalidPredicates.Insert("CheckNodeMemoryPressure") + } + if oldConditions[v1.NodeDiskPressure] != newConditions[v1.NodeDiskPressure] { + invalidPredicates.Insert("CheckNodeDiskPressure") + } + if oldConditions[v1.NodeReady] != newConditions[v1.NodeReady] || + oldConditions[v1.NodeOutOfDisk] != newConditions[v1.NodeOutOfDisk] || + oldConditions[v1.NodeNetworkUnavailable] != newConditions[v1.NodeNetworkUnavailable] || + newNode.Spec.Unschedulable != oldNode.Spec.Unschedulable { + invalidPredicates.Insert("CheckNodeCondition") + } + } + c.equivalencePodCache.InvalidateCachedPredicateItem(newNode.GetName(), invalidPredicates) + } +} + +func (c *configFactory) deleteNodeFromCache(obj interface{}) { + var node *v1.Node + switch t := obj.(type) { + case *v1.Node: + node = t + case cache.DeletedFinalStateUnknown: + var ok bool + node, ok = t.Obj.(*v1.Node) + if !ok { + glog.Errorf("cannot convert to *v1.Node: %v", t.Obj) + return + } + default: + glog.Errorf("cannot convert to *v1.Node: %v", t) + return + } + if err := c.schedulerCache.RemoveNode(node); err != nil { + glog.Errorf("scheduler cache RemoveNode failed: %v", err) + } + if c.enableEquivalenceClassCache { + c.equivalencePodCache.InvalidateAllCachedPredicateItemOfNode(node.GetName()) + } +} + +func (c *configFactory) addPDBToCache(obj interface{}) { + pdb, ok := obj.(*v1beta1.PodDisruptionBudget) + if !ok { + glog.Errorf("cannot convert to *v1beta1.PodDisruptionBudget: %v", obj) + return + } + + if err := c.schedulerCache.AddPDB(pdb); err != nil { + glog.Errorf("scheduler cache AddPDB failed: %v", err) + } +} + +func (c *configFactory) updatePDBInCache(oldObj, newObj interface{}) { + oldPDB, ok := oldObj.(*v1beta1.PodDisruptionBudget) + if !ok { + glog.Errorf("cannot convert oldObj to *v1beta1.PodDisruptionBudget: %v", oldObj) + return + } + newPDB, ok := newObj.(*v1beta1.PodDisruptionBudget) + if !ok { + glog.Errorf("cannot convert newObj to *v1beta1.PodDisruptionBudget: %v", newObj) + return + } + + if err := c.schedulerCache.UpdatePDB(oldPDB, newPDB); err != nil { + glog.Errorf("scheduler cache UpdatePDB failed: %v", err) + } +} + +func (c *configFactory) deletePDBFromCache(obj interface{}) { + var pdb *v1beta1.PodDisruptionBudget + switch t := obj.(type) { + case *v1beta1.PodDisruptionBudget: + pdb = t + case cache.DeletedFinalStateUnknown: + var ok bool + pdb, ok = t.Obj.(*v1beta1.PodDisruptionBudget) + if !ok { + glog.Errorf("cannot convert to *v1beta1.PodDisruptionBudget: %v", t.Obj) + return + } + default: + glog.Errorf("cannot convert to *v1beta1.PodDisruptionBudget: %v", t) + return + } + if err := c.schedulerCache.RemovePDB(pdb); err != nil { + glog.Errorf("scheduler cache RemovePDB failed: %v", err) + } +} + +// Create creates a scheduler with the default algorithm provider. +func (f *configFactory) Create() (*scheduler.Config, error) { + return f.CreateFromProvider(DefaultProvider) +} + +// Creates a scheduler from the name of a registered algorithm provider. +func (f *configFactory) CreateFromProvider(providerName string) (*scheduler.Config, error) { + glog.V(2).Infof("Creating scheduler from algorithm provider '%v'", providerName) + provider, err := GetAlgorithmProvider(providerName) + if err != nil { + return nil, err + } + + return f.CreateFromKeys(provider.FitPredicateKeys, provider.PriorityFunctionKeys, []algorithm.SchedulerExtender{}) +} + +// Creates a scheduler from the configuration file +func (f *configFactory) CreateFromConfig(policy schedulerapi.Policy) (*scheduler.Config, error) { + glog.V(2).Infof("Creating scheduler from configuration: %v", policy) + + // validate the policy configuration + if err := validation.ValidatePolicy(policy); err != nil { + return nil, err + } + + predicateKeys := sets.NewString() + for _, predicate := range policy.Predicates { + glog.V(2).Infof("Registering predicate: %s", predicate.Name) + predicateKeys.Insert(RegisterCustomFitPredicate(predicate)) + } + + priorityKeys := sets.NewString() + for _, priority := range policy.Priorities { + glog.V(2).Infof("Registering priority: %s", priority.Name) + priorityKeys.Insert(RegisterCustomPriorityFunction(priority)) + } + + extenders := make([]algorithm.SchedulerExtender, 0) + if len(policy.ExtenderConfigs) != 0 { + for ii := range policy.ExtenderConfigs { + glog.V(2).Infof("Creating extender with config %+v", policy.ExtenderConfigs[ii]) + if extender, err := core.NewHTTPExtender(&policy.ExtenderConfigs[ii]); err != nil { + return nil, err + } else { + extenders = append(extenders, extender) + } + } + } + // Providing HardPodAffinitySymmetricWeight in the policy config is the new and preferred way of providing the value. + // Give it higher precedence than scheduler CLI configuration when it is provided. + if policy.HardPodAffinitySymmetricWeight != 0 { + f.hardPodAffinitySymmetricWeight = policy.HardPodAffinitySymmetricWeight + } + return f.CreateFromKeys(predicateKeys, priorityKeys, extenders) +} + +// getBinder returns an extender that supports bind or a default binder. +func (f *configFactory) getBinder(extenders []algorithm.SchedulerExtender) scheduler.Binder { + for i := range extenders { + if extenders[i].IsBinder() { + return extenders[i] + } + } + return &binder{f.client} +} + +// Creates a scheduler from a set of registered fit predicate keys and priority keys. +func (f *configFactory) CreateFromKeys(predicateKeys, priorityKeys sets.String, extenders []algorithm.SchedulerExtender) (*scheduler.Config, error) { + glog.V(2).Infof("Creating scheduler with fit predicates '%v' and priority functions '%v'", predicateKeys, priorityKeys) + + if f.GetHardPodAffinitySymmetricWeight() < 1 || f.GetHardPodAffinitySymmetricWeight() > 100 { + return nil, fmt.Errorf("invalid hardPodAffinitySymmetricWeight: %d, must be in the range 1-100", f.GetHardPodAffinitySymmetricWeight()) + } + + predicateFuncs, err := f.GetPredicates(predicateKeys) + if err != nil { + return nil, err + } + + priorityConfigs, err := f.GetPriorityFunctionConfigs(priorityKeys) + if err != nil { + return nil, err + } + + priorityMetaProducer, err := f.GetPriorityMetadataProducer() + if err != nil { + return nil, err + } + + predicateMetaProducer, err := f.GetPredicateMetadataProducer() + if err != nil { + return nil, err + } + + // Init equivalence class cache + if f.enableEquivalenceClassCache && getEquivalencePodFuncFactory != nil { + pluginArgs, err := f.getPluginArgs() + if err != nil { + return nil, err + } + f.equivalencePodCache = core.NewEquivalenceCache( + getEquivalencePodFuncFactory(*pluginArgs), + ) + glog.Info("Created equivalence class cache") + } + + algo := core.NewGenericScheduler(f.schedulerCache, f.equivalencePodCache, f.podQueue, predicateFuncs, predicateMetaProducer, priorityConfigs, priorityMetaProducer, extenders, f.volumeBinder, f.pVCLister) + + podBackoff := util.CreateDefaultPodBackoff() + return &scheduler.Config{ + SchedulerCache: f.schedulerCache, + Ecache: f.equivalencePodCache, + // The scheduler only needs to consider schedulable nodes. + NodeLister: &nodeLister{f.nodeLister}, + Algorithm: algo, + Binder: f.getBinder(extenders), + PodConditionUpdater: &podConditionUpdater{f.client}, + PodPreemptor: &podPreemptor{f.client}, + WaitForCacheSync: func() bool { + return cache.WaitForCacheSync(f.StopEverything, f.scheduledPodsHasSynced) + }, + NextPod: func() *v1.Pod { + return f.getNextPod() + }, + Error: f.MakeDefaultErrorFunc(podBackoff, f.podQueue), + StopEverything: f.StopEverything, + VolumeBinder: f.volumeBinder, + }, nil +} + +type nodeLister struct { + corelisters.NodeLister +} + +func (n *nodeLister) List() ([]*v1.Node, error) { + return n.NodeLister.List(labels.Everything()) +} + +func (f *configFactory) GetPriorityFunctionConfigs(priorityKeys sets.String) ([]algorithm.PriorityConfig, error) { + pluginArgs, err := f.getPluginArgs() + if err != nil { + return nil, err + } + + return getPriorityFunctionConfigs(priorityKeys, *pluginArgs) +} + +func (f *configFactory) GetPriorityMetadataProducer() (algorithm.MetadataProducer, error) { + pluginArgs, err := f.getPluginArgs() + if err != nil { + return nil, err + } + + return getPriorityMetadataProducer(*pluginArgs) +} + +func (f *configFactory) GetPredicateMetadataProducer() (algorithm.PredicateMetadataProducer, error) { + pluginArgs, err := f.getPluginArgs() + if err != nil { + return nil, err + } + return getPredicateMetadataProducer(*pluginArgs) +} + +func (f *configFactory) GetPredicates(predicateKeys sets.String) (map[string]algorithm.FitPredicate, error) { + pluginArgs, err := f.getPluginArgs() + if err != nil { + return nil, err + } + + return getFitPredicateFunctions(predicateKeys, *pluginArgs) +} + +func (f *configFactory) getPluginArgs() (*PluginFactoryArgs, error) { + return &PluginFactoryArgs{ + PodLister: f.podLister, + ServiceLister: f.serviceLister, + ControllerLister: f.controllerLister, + ReplicaSetLister: f.replicaSetLister, + StatefulSetLister: f.statefulSetLister, + NodeLister: &nodeLister{f.nodeLister}, + NodeInfo: &predicates.CachedNodeInfo{NodeLister: f.nodeLister}, + PVInfo: &predicates.CachedPersistentVolumeInfo{PersistentVolumeLister: f.pVLister}, + PVCInfo: &predicates.CachedPersistentVolumeClaimInfo{PersistentVolumeClaimLister: f.pVCLister}, + StorageClassInfo: &predicates.CachedStorageClassInfo{StorageClassLister: f.storageClassLister}, + VolumeBinder: f.volumeBinder, + HardPodAffinitySymmetricWeight: f.hardPodAffinitySymmetricWeight, + }, nil +} + +func (f *configFactory) getNextPod() *v1.Pod { + if pod, err := f.podQueue.Pop(); err == nil { + glog.V(4).Infof("About to try and schedule pod %v", pod.Name) + return pod + } else { + glog.Errorf("Error while retrieving next pod from scheduling queue: %v", err) + return nil + } +} + +// unassignedNonTerminatedPod selects pods that are unassigned and non-terminal. +func unassignedNonTerminatedPod(pod *v1.Pod) bool { + if len(pod.Spec.NodeName) != 0 { + return false + } + if pod.Status.Phase == v1.PodSucceeded || pod.Status.Phase == v1.PodFailed { + return false + } + return true +} + +// assignedNonTerminatedPod selects pods that are assigned and non-terminal (scheduled and running). +func assignedNonTerminatedPod(pod *v1.Pod) bool { + if len(pod.Spec.NodeName) == 0 { + return false + } + if pod.Status.Phase == v1.PodSucceeded || pod.Status.Phase == v1.PodFailed { + return false + } + return true +} + +// assignedPodLister filters the pods returned from a PodLister to +// only include those that have a node name set. +type assignedPodLister struct { + corelisters.PodLister +} + +// List lists all Pods in the indexer for a given namespace. +func (l assignedPodLister) List(selector labels.Selector) ([]*v1.Pod, error) { + list, err := l.PodLister.List(selector) + if err != nil { + return nil, err + } + filtered := make([]*v1.Pod, 0, len(list)) + for _, pod := range list { + if len(pod.Spec.NodeName) > 0 { + filtered = append(filtered, pod) + } + } + return filtered, nil +} + +// List lists all Pods in the indexer for a given namespace. +func (l assignedPodLister) Pods(namespace string) corelisters.PodNamespaceLister { + return assignedPodNamespaceLister{l.PodLister.Pods(namespace)} +} + +// assignedPodNamespaceLister filters the pods returned from a PodNamespaceLister to +// only include those that have a node name set. +type assignedPodNamespaceLister struct { + corelisters.PodNamespaceLister +} + +// List lists all Pods in the indexer for a given namespace. +func (l assignedPodNamespaceLister) List(selector labels.Selector) (ret []*v1.Pod, err error) { + list, err := l.PodNamespaceLister.List(selector) + if err != nil { + return nil, err + } + filtered := make([]*v1.Pod, 0, len(list)) + for _, pod := range list { + if len(pod.Spec.NodeName) > 0 { + filtered = append(filtered, pod) + } + } + return filtered, nil +} + +// Get retrieves the Pod from the indexer for a given namespace and name. +func (l assignedPodNamespaceLister) Get(name string) (*v1.Pod, error) { + pod, err := l.PodNamespaceLister.Get(name) + if err != nil { + return nil, err + } + if len(pod.Spec.NodeName) > 0 { + return pod, nil + } + return nil, errors.NewNotFound(schema.GroupResource{Resource: string(v1.ResourcePods)}, name) +} + +type podInformer struct { + informer cache.SharedIndexInformer +} + +func (i *podInformer) Informer() cache.SharedIndexInformer { + return i.informer +} + +func (i *podInformer) Lister() corelisters.PodLister { + return corelisters.NewPodLister(i.informer.GetIndexer()) +} + +// NewPodInformer creates a shared index informer that returns only non-terminal pods. +func NewPodInformer(client clientset.Interface, resyncPeriod time.Duration, schedulerName string) coreinformers.PodInformer { + selector := fields.ParseSelectorOrDie( + "spec.schedulerName=" + schedulerName + + ",status.phase!=" + string(v1.PodSucceeded) + + ",status.phase!=" + string(v1.PodFailed)) + lw := cache.NewListWatchFromClient(client.CoreV1().RESTClient(), string(v1.ResourcePods), metav1.NamespaceAll, selector) + return &podInformer{ + informer: cache.NewSharedIndexInformer(lw, &v1.Pod{}, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}), + } +} + +func (factory *configFactory) MakeDefaultErrorFunc(backoff *util.PodBackoff, podQueue core.SchedulingQueue) func(pod *v1.Pod, err error) { + return func(pod *v1.Pod, err error) { + if err == core.ErrNoNodesAvailable { + glog.V(4).Infof("Unable to schedule %v %v: no nodes are registered to the cluster; waiting", pod.Namespace, pod.Name) + } else { + if _, ok := err.(*core.FitError); ok { + glog.V(4).Infof("Unable to schedule %v %v: no fit: %v; waiting", pod.Namespace, pod.Name, err) + } else if errors.IsNotFound(err) { + if errStatus, ok := err.(errors.APIStatus); ok && errStatus.Status().Details.Kind == "node" { + nodeName := errStatus.Status().Details.Name + // when node is not found, We do not remove the node right away. Trying again to get + // the node and if the node is still not found, then remove it from the scheduler cache. + _, err := factory.client.CoreV1().Nodes().Get(nodeName, metav1.GetOptions{}) + if err != nil && errors.IsNotFound(err) { + node := v1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName}} + factory.schedulerCache.RemoveNode(&node) + // invalidate cached predicate for the node + if factory.enableEquivalenceClassCache { + factory.equivalencePodCache.InvalidateAllCachedPredicateItemOfNode(nodeName) + } + } + } + } else { + glog.Errorf("Error scheduling %v %v: %v; retrying", pod.Namespace, pod.Name, err) + } + } + + backoff.Gc() + // Retry asynchronously. + // Note that this is extremely rudimentary and we need a more real error handling path. + go func() { + defer runtime.HandleCrash() + podID := types.NamespacedName{ + Namespace: pod.Namespace, + Name: pod.Name, + } + origPod := pod + + // When pod priority is enabled, we would like to place an unschedulable + // pod in the unschedulable queue. This ensures that if the pod is nominated + // to run on a node, scheduler takes the pod into account when running + // predicates for the node. + if !util.PodPriorityEnabled() { + entry := backoff.GetEntry(podID) + if !entry.TryWait(backoff.MaxDuration()) { + glog.Warningf("Request for pod %v already in flight, abandoning", podID) + return + } + } + // Get the pod again; it may have changed/been scheduled already. + getBackoff := initialGetBackoff + for { + pod, err := factory.client.CoreV1().Pods(podID.Namespace).Get(podID.Name, metav1.GetOptions{}) + if err == nil { + if len(pod.Spec.NodeName) == 0 { + podQueue.AddUnschedulableIfNotPresent(pod) + } else { + if factory.volumeBinder != nil { + // Volume binder only wants to keep unassigned pods + factory.volumeBinder.DeletePodBindings(pod) + } + } + break + } + if errors.IsNotFound(err) { + glog.Warningf("A pod %v no longer exists", podID) + + if factory.volumeBinder != nil { + // Volume binder only wants to keep unassigned pods + factory.volumeBinder.DeletePodBindings(origPod) + } + return + } + glog.Errorf("Error getting pod %v for retry: %v; retrying...", podID, err) + if getBackoff = getBackoff * 2; getBackoff > maximalGetBackoff { + getBackoff = maximalGetBackoff + } + time.Sleep(getBackoff) + } + }() + } +} + +// nodeEnumerator allows a cache.Poller to enumerate items in an v1.NodeList +type nodeEnumerator struct { + *v1.NodeList +} + +// Len returns the number of items in the node list. +func (ne *nodeEnumerator) Len() int { + if ne.NodeList == nil { + return 0 + } + return len(ne.Items) +} + +// Get returns the item (and ID) with the particular index. +func (ne *nodeEnumerator) Get(index int) interface{} { + return &ne.Items[index] +} + +type binder struct { + Client clientset.Interface +} + +// Bind just does a POST binding RPC. +func (b *binder) Bind(binding *v1.Binding) error { + glog.V(3).Infof("Attempting to bind %v to %v", binding.Name, binding.Target.Name) + return b.Client.CoreV1().Pods(binding.Namespace).Bind(binding) +} + +type podConditionUpdater struct { + Client clientset.Interface +} + +func (p *podConditionUpdater) Update(pod *v1.Pod, condition *v1.PodCondition) error { + glog.V(2).Infof("Updating pod condition for %s/%s to (%s==%s)", pod.Namespace, pod.Name, condition.Type, condition.Status) + if podutil.UpdatePodCondition(&pod.Status, condition) { + _, err := p.Client.CoreV1().Pods(pod.Namespace).UpdateStatus(pod) + return err + } + return nil +} + +type podPreemptor struct { + Client clientset.Interface +} + +func (p *podPreemptor) GetUpdatedPod(pod *v1.Pod) (*v1.Pod, error) { + return p.Client.CoreV1().Pods(pod.Namespace).Get(pod.Name, metav1.GetOptions{}) +} + +func (p *podPreemptor) DeletePod(pod *v1.Pod) error { + return p.Client.CoreV1().Pods(pod.Namespace).Delete(pod.Name, &metav1.DeleteOptions{}) +} + +func (p *podPreemptor) UpdatePodAnnotations(pod *v1.Pod, annotations map[string]string) error { + podCopy := pod.DeepCopy() + if podCopy.Annotations == nil { + podCopy.Annotations = map[string]string{} + } + for k, v := range annotations { + podCopy.Annotations[k] = v + } + ret := &unstructured.Unstructured{} + ret.SetAnnotations(podCopy.Annotations) + patchData, err := json.Marshal(ret) + if err != nil { + return err + } + _, error := p.Client.CoreV1().Pods(podCopy.Namespace).Patch(podCopy.Name, types.MergePatchType, patchData, "status") + return error +} + +func (p *podPreemptor) RemoveNominatedNodeAnnotation(pod *v1.Pod) error { + podCopy := pod.DeepCopy() + if podCopy.Annotations == nil { + return nil + } + if _, exists := podCopy.Annotations[core.NominatedNodeAnnotationKey]; !exists { + return nil + } + // Note: Deleting the entry from the annotations and passing it to Patch() will + // not remove the annotation. That's why we set it to empty string. + podCopy.Annotations[core.NominatedNodeAnnotationKey] = "" + ret := &unstructured.Unstructured{} + ret.SetAnnotations(podCopy.Annotations) + patchData, err := json.Marshal(ret) + if err != nil { + return err + } + _, error := p.Client.CoreV1().Pods(podCopy.Namespace).Patch(podCopy.Name, types.MergePatchType, patchData, "status") + return error +} diff --git a/plugin/pkg/scheduler/factory/factory_test.go b/pkg/scheduler/factory/factory_test.go similarity index 78% rename from plugin/pkg/scheduler/factory/factory_test.go rename to pkg/scheduler/factory/factory_test.go index 33f2020c7b1..7ca0dc36cd1 100644 --- a/plugin/pkg/scheduler/factory/factory_test.go +++ b/pkg/scheduler/factory/factory_test.go @@ -31,14 +31,15 @@ import ( restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/cache" utiltesting "k8s.io/client-go/util/testing" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" apitesting "k8s.io/kubernetes/pkg/api/testing" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - latestschedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api/latest" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" - schedulertesting "k8s.io/kubernetes/plugin/pkg/scheduler/testing" - "k8s.io/kubernetes/plugin/pkg/scheduler/util" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + latestschedulerapi "k8s.io/kubernetes/pkg/scheduler/api/latest" + "k8s.io/kubernetes/pkg/scheduler/core" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" + schedulertesting "k8s.io/kubernetes/pkg/scheduler/testing" + "k8s.io/kubernetes/pkg/scheduler/util" ) const enableEquivalenceCache = true @@ -51,7 +52,7 @@ func TestCreate(t *testing.T) { } server := httptest.NewServer(&handler) defer server.Close() - client := clientset.NewForConfigOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + client := clientset.NewForConfigOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) informerFactory := informers.NewSharedInformerFactory(client, 0) factory := NewConfigFactory( v1.DefaultSchedulerName, @@ -64,6 +65,8 @@ func TestCreate(t *testing.T) { informerFactory.Extensions().V1beta1().ReplicaSets(), informerFactory.Apps().V1beta1().StatefulSets(), informerFactory.Core().V1().Services(), + informerFactory.Policy().V1beta1().PodDisruptionBudgets(), + informerFactory.Storage().V1().StorageClasses(), v1.DefaultHardPodAffinitySymmetricWeight, enableEquivalenceCache, ) @@ -83,7 +86,7 @@ func TestCreateFromConfig(t *testing.T) { } server := httptest.NewServer(&handler) defer server.Close() - client := clientset.NewForConfigOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + client := clientset.NewForConfigOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) informerFactory := informers.NewSharedInformerFactory(client, 0) factory := NewConfigFactory( v1.DefaultSchedulerName, @@ -96,6 +99,8 @@ func TestCreateFromConfig(t *testing.T) { informerFactory.Extensions().V1beta1().ReplicaSets(), informerFactory.Apps().V1beta1().StatefulSets(), informerFactory.Core().V1().Services(), + informerFactory.Policy().V1beta1().PodDisruptionBudgets(), + informerFactory.Storage().V1().StorageClasses(), v1.DefaultHardPodAffinitySymmetricWeight, enableEquivalenceCache, ) @@ -142,7 +147,7 @@ func TestCreateFromConfigWithHardPodAffinitySymmetricWeight(t *testing.T) { } server := httptest.NewServer(&handler) defer server.Close() - client := clientset.NewForConfigOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + client := clientset.NewForConfigOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) informerFactory := informers.NewSharedInformerFactory(client, 0) factory := NewConfigFactory( v1.DefaultSchedulerName, @@ -155,6 +160,8 @@ func TestCreateFromConfigWithHardPodAffinitySymmetricWeight(t *testing.T) { informerFactory.Extensions().V1beta1().ReplicaSets(), informerFactory.Apps().V1beta1().StatefulSets(), informerFactory.Core().V1().Services(), + informerFactory.Policy().V1beta1().PodDisruptionBudgets(), + informerFactory.Storage().V1().StorageClasses(), v1.DefaultHardPodAffinitySymmetricWeight, enableEquivalenceCache, ) @@ -202,7 +209,7 @@ func TestCreateFromEmptyConfig(t *testing.T) { } server := httptest.NewServer(&handler) defer server.Close() - client := clientset.NewForConfigOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + client := clientset.NewForConfigOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) informerFactory := informers.NewSharedInformerFactory(client, 0) factory := NewConfigFactory( v1.DefaultSchedulerName, @@ -215,6 +222,8 @@ func TestCreateFromEmptyConfig(t *testing.T) { informerFactory.Extensions().V1beta1().ReplicaSets(), informerFactory.Apps().V1beta1().StatefulSets(), informerFactory.Core().V1().Services(), + informerFactory.Policy().V1beta1().PodDisruptionBudgets(), + informerFactory.Storage().V1().StorageClasses(), v1.DefaultHardPodAffinitySymmetricWeight, enableEquivalenceCache, ) @@ -259,7 +268,7 @@ func TestDefaultErrorFunc(t *testing.T) { mux.Handle(util.Test.ResourcePath(string(v1.ResourcePods), "bar", "foo"), &handler) server := httptest.NewServer(mux) defer server.Close() - client := clientset.NewForConfigOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + client := clientset.NewForConfigOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) informerFactory := informers.NewSharedInformerFactory(client, 0) factory := NewConfigFactory( v1.DefaultSchedulerName, @@ -272,10 +281,12 @@ func TestDefaultErrorFunc(t *testing.T) { informerFactory.Extensions().V1beta1().ReplicaSets(), informerFactory.Apps().V1beta1().StatefulSets(), informerFactory.Core().V1().Services(), + informerFactory.Policy().V1beta1().PodDisruptionBudgets(), + informerFactory.Storage().V1().StorageClasses(), v1.DefaultHardPodAffinitySymmetricWeight, enableEquivalenceCache, ) - queue := cache.NewFIFO(cache.MetaNamespaceKeyFunc) + queue := &core.FIFO{FIFO: cache.NewFIFO(cache.MetaNamespaceKeyFunc)} podBackoff := util.CreatePodBackoff(1*time.Millisecond, 1*time.Second) errFunc := factory.MakeDefaultErrorFunc(podBackoff, queue) @@ -344,7 +355,7 @@ func TestBind(t *testing.T) { } server := httptest.NewServer(&handler) defer server.Close() - client := clientset.NewForConfigOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + client := clientset.NewForConfigOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) b := binder{client} if err := b.Bind(item.binding); err != nil { @@ -358,95 +369,6 @@ func TestBind(t *testing.T) { } } -// TestResponsibleForPod tests if a pod with an annotation that should cause it to -// be picked up by the default scheduler, is in fact picked by the default scheduler -// Two schedulers are made in the test: one is default scheduler and other scheduler -// is of name "foo-scheduler". A pod must be picked up by at most one of the two -// schedulers. -func TestResponsibleForPod(t *testing.T) { - handler := utiltesting.FakeHandler{ - StatusCode: 500, - ResponseBody: "", - T: t, - } - server := httptest.NewServer(&handler) - defer server.Close() - client := clientset.NewForConfigOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) - // factory of "default-scheduler" - informerFactory := informers.NewSharedInformerFactory(client, 0) - factoryDefaultScheduler := NewConfigFactory( - v1.DefaultSchedulerName, - client, - informerFactory.Core().V1().Nodes(), - informerFactory.Core().V1().Pods(), - informerFactory.Core().V1().PersistentVolumes(), - informerFactory.Core().V1().PersistentVolumeClaims(), - informerFactory.Core().V1().ReplicationControllers(), - informerFactory.Extensions().V1beta1().ReplicaSets(), - informerFactory.Apps().V1beta1().StatefulSets(), - informerFactory.Core().V1().Services(), - v1.DefaultHardPodAffinitySymmetricWeight, - enableEquivalenceCache, - ) - // factory of "foo-scheduler" - factoryFooScheduler := NewConfigFactory( - "foo-scheduler", - client, - informerFactory.Core().V1().Nodes(), - informerFactory.Core().V1().Pods(), - informerFactory.Core().V1().PersistentVolumes(), - informerFactory.Core().V1().PersistentVolumeClaims(), - informerFactory.Core().V1().ReplicationControllers(), - informerFactory.Extensions().V1beta1().ReplicaSets(), - informerFactory.Apps().V1beta1().StatefulSets(), - informerFactory.Core().V1().Services(), - v1.DefaultHardPodAffinitySymmetricWeight, - enableEquivalenceCache, - ) - // scheduler annotations to be tested - schedulerFitsDefault := "default-scheduler" - schedulerFitsFoo := "foo-scheduler" - schedulerFitsNone := "bar-scheduler" - - tests := []struct { - pod *v1.Pod - pickedByDefault bool - pickedByFoo bool - }{ - { - // pod with "spec.Schedulername=default-scheduler" should be picked - // by the scheduler of name "default-scheduler", NOT by the one of name "foo-scheduler" - pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, Spec: v1.PodSpec{SchedulerName: schedulerFitsDefault}}, - pickedByDefault: true, - pickedByFoo: false, - }, - { - // pod with "spec.SchedulerName=foo-scheduler" should be NOT - // be picked by the scheduler of name "default-scheduler", but by the one of name "foo-scheduler" - pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, Spec: v1.PodSpec{SchedulerName: schedulerFitsFoo}}, - pickedByDefault: false, - pickedByFoo: true, - }, - { - // pod with "spec.SchedulerName=foo-scheduler" should be NOT - // be picked by niether the scheduler of name "default-scheduler" nor the one of name "foo-scheduler" - pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, Spec: v1.PodSpec{SchedulerName: schedulerFitsNone}}, - pickedByDefault: false, - pickedByFoo: false, - }, - } - - for _, test := range tests { - podOfDefault := factoryDefaultScheduler.ResponsibleForPod(test.pod) - podOfFoo := factoryFooScheduler.ResponsibleForPod(test.pod) - results := []bool{podOfDefault, podOfFoo} - expected := []bool{test.pickedByDefault, test.pickedByFoo} - if !reflect.DeepEqual(results, expected) { - t.Errorf("expected: {%v, %v}, got {%v, %v}", test.pickedByDefault, test.pickedByFoo, podOfDefault, podOfFoo) - } - } -} - func TestInvalidHardPodAffinitySymmetricWeight(t *testing.T) { handler := utiltesting.FakeHandler{ StatusCode: 500, @@ -454,9 +376,8 @@ func TestInvalidHardPodAffinitySymmetricWeight(t *testing.T) { T: t, } server := httptest.NewServer(&handler) - // TODO: Uncomment when fix #19254 - // defer server.Close() - client := clientset.NewForConfigOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + defer server.Close() + client := clientset.NewForConfigOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) // factory of "default-scheduler" informerFactory := informers.NewSharedInformerFactory(client, 0) factory := NewConfigFactory( @@ -470,6 +391,8 @@ func TestInvalidHardPodAffinitySymmetricWeight(t *testing.T) { informerFactory.Extensions().V1beta1().ReplicaSets(), informerFactory.Apps().V1beta1().StatefulSets(), informerFactory.Core().V1().Services(), + informerFactory.Policy().V1beta1().PodDisruptionBudgets(), + informerFactory.Storage().V1().StorageClasses(), -1, enableEquivalenceCache, ) @@ -487,10 +410,10 @@ func TestInvalidFactoryArgs(t *testing.T) { } server := httptest.NewServer(&handler) defer server.Close() - client := clientset.NewForConfigOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) + client := clientset.NewForConfigOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) testCases := []struct { - hardPodAffinitySymmetricWeight int + hardPodAffinitySymmetricWeight int32 expectErr string }{ { @@ -516,6 +439,8 @@ func TestInvalidFactoryArgs(t *testing.T) { informerFactory.Extensions().V1beta1().ReplicaSets(), informerFactory.Apps().V1beta1().StatefulSets(), informerFactory.Core().V1().Services(), + informerFactory.Policy().V1beta1().PodDisruptionBudgets(), + informerFactory.Storage().V1().StorageClasses(), test.hardPodAffinitySymmetricWeight, enableEquivalenceCache, ) diff --git a/pkg/scheduler/factory/plugins.go b/pkg/scheduler/factory/plugins.go new file mode 100644 index 00000000000..b8733d2961d --- /dev/null +++ b/pkg/scheduler/factory/plugins.go @@ -0,0 +1,540 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package factory + +import ( + "fmt" + "regexp" + "sort" + "strings" + "sync" + + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + "k8s.io/kubernetes/pkg/scheduler/algorithm/predicates" + "k8s.io/kubernetes/pkg/scheduler/algorithm/priorities" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/scheduler/volumebinder" +) + +// PluginFactoryArgs are passed to all plugin factory functions. +type PluginFactoryArgs struct { + PodLister algorithm.PodLister + ServiceLister algorithm.ServiceLister + ControllerLister algorithm.ControllerLister + ReplicaSetLister algorithm.ReplicaSetLister + StatefulSetLister algorithm.StatefulSetLister + NodeLister algorithm.NodeLister + NodeInfo predicates.NodeInfo + PVInfo predicates.PersistentVolumeInfo + PVCInfo predicates.PersistentVolumeClaimInfo + StorageClassInfo predicates.StorageClassInfo + VolumeBinder *volumebinder.VolumeBinder + HardPodAffinitySymmetricWeight int32 +} + +// MetadataProducerFactory produces MetadataProducer from the given args. +// TODO: Rename this to PriorityMetadataProducerFactory. +type MetadataProducerFactory func(PluginFactoryArgs) algorithm.MetadataProducer + +// PredicateMetadataProducerFactory produces PredicateMetadataProducer from the given args. +type PredicateMetadataProducerFactory func(PluginFactoryArgs) algorithm.PredicateMetadataProducer + +// A FitPredicateFactory produces a FitPredicate from the given args. +type FitPredicateFactory func(PluginFactoryArgs) algorithm.FitPredicate + +// DEPRECATED +// Use Map-Reduce pattern for priority functions. +// A PriorityFunctionFactory produces a PriorityConfig from the given args. +type PriorityFunctionFactory func(PluginFactoryArgs) algorithm.PriorityFunction + +// A PriorityFunctionFactory produces map & reduce priority functions +// from a given args. +// FIXME: Rename to PriorityFunctionFactory. +type PriorityFunctionFactory2 func(PluginFactoryArgs) (algorithm.PriorityMapFunction, algorithm.PriorityReduceFunction) + +// A PriorityConfigFactory produces a PriorityConfig from the given function and weight +type PriorityConfigFactory struct { + Function PriorityFunctionFactory + MapReduceFunction PriorityFunctionFactory2 + Weight int +} + +// EquivalencePodFuncFactory produces a function to get equivalence class for given pod. +type EquivalencePodFuncFactory func(PluginFactoryArgs) algorithm.GetEquivalencePodFunc + +var ( + schedulerFactoryMutex sync.Mutex + + // maps that hold registered algorithm types + fitPredicateMap = make(map[string]FitPredicateFactory) + mandatoryFitPredicates = sets.NewString() + priorityFunctionMap = make(map[string]PriorityConfigFactory) + algorithmProviderMap = make(map[string]AlgorithmProviderConfig) + + // Registered metadata producers + priorityMetadataProducer MetadataProducerFactory + predicateMetadataProducer PredicateMetadataProducerFactory + + // get equivalence pod function + getEquivalencePodFuncFactory EquivalencePodFuncFactory +) + +const ( + DefaultProvider = "DefaultProvider" +) + +type AlgorithmProviderConfig struct { + FitPredicateKeys sets.String + PriorityFunctionKeys sets.String +} + +// RegisterFitPredicate registers a fit predicate with the algorithm +// registry. Returns the name with which the predicate was registered. +func RegisterFitPredicate(name string, predicate algorithm.FitPredicate) string { + return RegisterFitPredicateFactory(name, func(PluginFactoryArgs) algorithm.FitPredicate { return predicate }) +} + +// RemoveFitPredicate removes a fit predicate from factory. +func RemoveFitPredicate(name string) { + schedulerFactoryMutex.Lock() + defer schedulerFactoryMutex.Unlock() + + validateAlgorithmNameOrDie(name) + delete(fitPredicateMap, name) + mandatoryFitPredicates.Delete(name) +} + +// RemovePredicateKeyFromAlgoProvider removes a fit predicate key from algorithmProvider. +func RemovePredicateKeyFromAlgoProvider(providerName, key string) error { + schedulerFactoryMutex.Lock() + defer schedulerFactoryMutex.Unlock() + + validateAlgorithmNameOrDie(providerName) + provider, ok := algorithmProviderMap[providerName] + if !ok { + return fmt.Errorf("plugin %v has not been registered", providerName) + } + provider.FitPredicateKeys.Delete(key) + return nil +} + +// RemovePredicateKeyFromAlgorithmProviderMap removes a fit predicate key from all algorithmProviders which in algorithmProviderMap. +func RemovePredicateKeyFromAlgorithmProviderMap(key string) { + schedulerFactoryMutex.Lock() + defer schedulerFactoryMutex.Unlock() + + for _, provider := range algorithmProviderMap { + provider.FitPredicateKeys.Delete(key) + } + return +} + +// InsertPredicateKeyToAlgoProvider insert a fit predicate key to algorithmProvider. +func InsertPredicateKeyToAlgoProvider(providerName, key string) error { + schedulerFactoryMutex.Lock() + defer schedulerFactoryMutex.Unlock() + + validateAlgorithmNameOrDie(providerName) + provider, ok := algorithmProviderMap[providerName] + if !ok { + return fmt.Errorf("plugin %v has not been registered", providerName) + } + provider.FitPredicateKeys.Insert(key) + return nil +} + +// InsertPredicateKeyToAlgorithmProviderMap insert a fit predicate key to all algorithmProviders which in algorithmProviderMap. +func InsertPredicateKeyToAlgorithmProviderMap(key string) { + schedulerFactoryMutex.Lock() + defer schedulerFactoryMutex.Unlock() + + for _, provider := range algorithmProviderMap { + provider.FitPredicateKeys.Insert(key) + } + return +} + +// RegisterMandatoryFitPredicate registers a fit predicate with the algorithm registry, the predicate is used by +// kubelet, DaemonSet; it is always included in configuration. Returns the name with which the predicate was +// registered. +func RegisterMandatoryFitPredicate(name string, predicate algorithm.FitPredicate) string { + schedulerFactoryMutex.Lock() + defer schedulerFactoryMutex.Unlock() + validateAlgorithmNameOrDie(name) + fitPredicateMap[name] = func(PluginFactoryArgs) algorithm.FitPredicate { return predicate } + mandatoryFitPredicates.Insert(name) + return name +} + +// RegisterFitPredicateFactory registers a fit predicate factory with the +// algorithm registry. Returns the name with which the predicate was registered. +func RegisterFitPredicateFactory(name string, predicateFactory FitPredicateFactory) string { + schedulerFactoryMutex.Lock() + defer schedulerFactoryMutex.Unlock() + validateAlgorithmNameOrDie(name) + fitPredicateMap[name] = predicateFactory + return name +} + +// RegisterCustomFitPredicate registers a custom fit predicate with the algorithm registry. +// Returns the name, with which the predicate was registered. +func RegisterCustomFitPredicate(policy schedulerapi.PredicatePolicy) string { + var predicateFactory FitPredicateFactory + var ok bool + + validatePredicateOrDie(policy) + + // generate the predicate function, if a custom type is requested + if policy.Argument != nil { + if policy.Argument.ServiceAffinity != nil { + predicateFactory = func(args PluginFactoryArgs) algorithm.FitPredicate { + predicate, precomputationFunction := predicates.NewServiceAffinityPredicate( + args.PodLister, + args.ServiceLister, + args.NodeInfo, + policy.Argument.ServiceAffinity.Labels, + ) + + // Once we generate the predicate we should also Register the Precomputation + predicates.RegisterPredicateMetadataProducer(policy.Name, precomputationFunction) + return predicate + } + } else if policy.Argument.LabelsPresence != nil { + predicateFactory = func(args PluginFactoryArgs) algorithm.FitPredicate { + return predicates.NewNodeLabelPredicate( + policy.Argument.LabelsPresence.Labels, + policy.Argument.LabelsPresence.Presence, + ) + } + } + } else if predicateFactory, ok = fitPredicateMap[policy.Name]; ok { + // checking to see if a pre-defined predicate is requested + glog.V(2).Infof("Predicate type %s already registered, reusing.", policy.Name) + return policy.Name + } + + if predicateFactory == nil { + glog.Fatalf("Invalid configuration: Predicate type not found for %s", policy.Name) + } + + return RegisterFitPredicateFactory(policy.Name, predicateFactory) +} + +// IsFitPredicateRegistered is useful for testing providers. +func IsFitPredicateRegistered(name string) bool { + schedulerFactoryMutex.Lock() + defer schedulerFactoryMutex.Unlock() + _, ok := fitPredicateMap[name] + return ok +} + +func RegisterPriorityMetadataProducerFactory(factory MetadataProducerFactory) { + schedulerFactoryMutex.Lock() + defer schedulerFactoryMutex.Unlock() + priorityMetadataProducer = factory +} + +func RegisterPredicateMetadataProducerFactory(factory PredicateMetadataProducerFactory) { + schedulerFactoryMutex.Lock() + defer schedulerFactoryMutex.Unlock() + predicateMetadataProducer = factory +} + +// DEPRECATED +// Use Map-Reduce pattern for priority functions. +// Registers a priority function with the algorithm registry. Returns the name, +// with which the function was registered. +func RegisterPriorityFunction(name string, function algorithm.PriorityFunction, weight int) string { + return RegisterPriorityConfigFactory(name, PriorityConfigFactory{ + Function: func(PluginFactoryArgs) algorithm.PriorityFunction { + return function + }, + Weight: weight, + }) +} + +// RegisterPriorityFunction2 registers a priority function with the algorithm registry. Returns the name, +// with which the function was registered. +// FIXME: Rename to PriorityFunctionFactory. +func RegisterPriorityFunction2( + name string, + mapFunction algorithm.PriorityMapFunction, + reduceFunction algorithm.PriorityReduceFunction, + weight int) string { + return RegisterPriorityConfigFactory(name, PriorityConfigFactory{ + MapReduceFunction: func(PluginFactoryArgs) (algorithm.PriorityMapFunction, algorithm.PriorityReduceFunction) { + return mapFunction, reduceFunction + }, + Weight: weight, + }) +} + +func RegisterPriorityConfigFactory(name string, pcf PriorityConfigFactory) string { + schedulerFactoryMutex.Lock() + defer schedulerFactoryMutex.Unlock() + validateAlgorithmNameOrDie(name) + priorityFunctionMap[name] = pcf + return name +} + +// RegisterCustomPriorityFunction registers a custom priority function with the algorithm registry. +// Returns the name, with which the priority function was registered. +func RegisterCustomPriorityFunction(policy schedulerapi.PriorityPolicy) string { + var pcf *PriorityConfigFactory + + validatePriorityOrDie(policy) + + // generate the priority function, if a custom priority is requested + if policy.Argument != nil { + if policy.Argument.ServiceAntiAffinity != nil { + pcf = &PriorityConfigFactory{ + MapReduceFunction: func(args PluginFactoryArgs) (algorithm.PriorityMapFunction, algorithm.PriorityReduceFunction) { + return priorities.NewServiceAntiAffinityPriority( + args.PodLister, + args.ServiceLister, + policy.Argument.ServiceAntiAffinity.Label, + ) + }, + Weight: policy.Weight, + } + } else if policy.Argument.LabelPreference != nil { + pcf = &PriorityConfigFactory{ + MapReduceFunction: func(args PluginFactoryArgs) (algorithm.PriorityMapFunction, algorithm.PriorityReduceFunction) { + return priorities.NewNodeLabelPriority( + policy.Argument.LabelPreference.Label, + policy.Argument.LabelPreference.Presence, + ) + }, + Weight: policy.Weight, + } + } + } else if existingPcf, ok := priorityFunctionMap[policy.Name]; ok { + glog.V(2).Infof("Priority type %s already registered, reusing.", policy.Name) + // set/update the weight based on the policy + pcf = &PriorityConfigFactory{ + Function: existingPcf.Function, + MapReduceFunction: existingPcf.MapReduceFunction, + Weight: policy.Weight, + } + } + + if pcf == nil { + glog.Fatalf("Invalid configuration: Priority type not found for %s", policy.Name) + } + + return RegisterPriorityConfigFactory(policy.Name, *pcf) +} + +// RegisterGetEquivalencePodFunction registers equivalenceFuncFactory to produce equivalence class for given pod. +func RegisterGetEquivalencePodFunction(equivalenceFuncFactory EquivalencePodFuncFactory) { + getEquivalencePodFuncFactory = equivalenceFuncFactory +} + +// IsPriorityFunctionRegistered is useful for testing providers. +func IsPriorityFunctionRegistered(name string) bool { + schedulerFactoryMutex.Lock() + defer schedulerFactoryMutex.Unlock() + _, ok := priorityFunctionMap[name] + return ok +} + +// RegisterAlgorithmProvider registers a new algorithm provider with the algorithm registry. This should +// be called from the init function in a provider plugin. +func RegisterAlgorithmProvider(name string, predicateKeys, priorityKeys sets.String) string { + schedulerFactoryMutex.Lock() + defer schedulerFactoryMutex.Unlock() + validateAlgorithmNameOrDie(name) + algorithmProviderMap[name] = AlgorithmProviderConfig{ + FitPredicateKeys: predicateKeys, + PriorityFunctionKeys: priorityKeys, + } + return name +} + +// GetAlgorithmProvider should not be used to modify providers. It is publicly visible for testing. +func GetAlgorithmProvider(name string) (*AlgorithmProviderConfig, error) { + schedulerFactoryMutex.Lock() + defer schedulerFactoryMutex.Unlock() + + provider, ok := algorithmProviderMap[name] + if !ok { + return nil, fmt.Errorf("plugin %q has not been registered", name) + } + + return &provider, nil +} + +func getFitPredicateFunctions(names sets.String, args PluginFactoryArgs) (map[string]algorithm.FitPredicate, error) { + schedulerFactoryMutex.Lock() + defer schedulerFactoryMutex.Unlock() + + predicates := map[string]algorithm.FitPredicate{} + for _, name := range names.List() { + factory, ok := fitPredicateMap[name] + if !ok { + return nil, fmt.Errorf("Invalid predicate name %q specified - no corresponding function found", name) + } + predicates[name] = factory(args) + } + + // Always include mandatory fit predicates. + for name := range mandatoryFitPredicates { + if factory, found := fitPredicateMap[name]; found { + predicates[name] = factory(args) + } + } + + return predicates, nil +} + +func getPriorityMetadataProducer(args PluginFactoryArgs) (algorithm.MetadataProducer, error) { + schedulerFactoryMutex.Lock() + defer schedulerFactoryMutex.Unlock() + + if priorityMetadataProducer == nil { + return algorithm.EmptyMetadataProducer, nil + } + return priorityMetadataProducer(args), nil +} + +func getPredicateMetadataProducer(args PluginFactoryArgs) (algorithm.PredicateMetadataProducer, error) { + schedulerFactoryMutex.Lock() + defer schedulerFactoryMutex.Unlock() + + if predicateMetadataProducer == nil { + return algorithm.EmptyPredicateMetadataProducer, nil + } + return predicateMetadataProducer(args), nil +} + +func getPriorityFunctionConfigs(names sets.String, args PluginFactoryArgs) ([]algorithm.PriorityConfig, error) { + schedulerFactoryMutex.Lock() + defer schedulerFactoryMutex.Unlock() + + configs := []algorithm.PriorityConfig{} + for _, name := range names.List() { + factory, ok := priorityFunctionMap[name] + if !ok { + return nil, fmt.Errorf("Invalid priority name %s specified - no corresponding function found", name) + } + if factory.Function != nil { + configs = append(configs, algorithm.PriorityConfig{ + Name: name, + Function: factory.Function(args), + Weight: factory.Weight, + }) + } else { + mapFunction, reduceFunction := factory.MapReduceFunction(args) + configs = append(configs, algorithm.PriorityConfig{ + Name: name, + Map: mapFunction, + Reduce: reduceFunction, + Weight: factory.Weight, + }) + } + } + if err := validateSelectedConfigs(configs); err != nil { + return nil, err + } + return configs, nil +} + +// validateSelectedConfigs validates the config weights to avoid the overflow. +func validateSelectedConfigs(configs []algorithm.PriorityConfig) error { + var totalPriority int + for _, config := range configs { + // Checks totalPriority against MaxTotalPriority to avoid overflow + if config.Weight*schedulerapi.MaxPriority > schedulerapi.MaxTotalPriority-totalPriority { + return fmt.Errorf("Total priority of priority functions has overflown") + } + totalPriority += config.Weight * schedulerapi.MaxPriority + } + return nil +} + +var validName = regexp.MustCompile("^[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])$") + +func validateAlgorithmNameOrDie(name string) { + if !validName.MatchString(name) { + glog.Fatalf("Algorithm name %v does not match the name validation regexp \"%v\".", name, validName) + } +} + +func validatePredicateOrDie(predicate schedulerapi.PredicatePolicy) { + if predicate.Argument != nil { + numArgs := 0 + if predicate.Argument.ServiceAffinity != nil { + numArgs++ + } + if predicate.Argument.LabelsPresence != nil { + numArgs++ + } + if numArgs != 1 { + glog.Fatalf("Exactly 1 predicate argument is required, numArgs: %v, Predicate: %s", numArgs, predicate.Name) + } + } +} + +func validatePriorityOrDie(priority schedulerapi.PriorityPolicy) { + if priority.Argument != nil { + numArgs := 0 + if priority.Argument.ServiceAntiAffinity != nil { + numArgs++ + } + if priority.Argument.LabelPreference != nil { + numArgs++ + } + if numArgs != 1 { + glog.Fatalf("Exactly 1 priority argument is required, numArgs: %v, Priority: %s", numArgs, priority.Name) + } + } +} + +func ListRegisteredFitPredicates() []string { + schedulerFactoryMutex.Lock() + defer schedulerFactoryMutex.Unlock() + + names := []string{} + for name := range fitPredicateMap { + names = append(names, name) + } + return names +} + +func ListRegisteredPriorityFunctions() []string { + schedulerFactoryMutex.Lock() + defer schedulerFactoryMutex.Unlock() + + names := []string{} + for name := range priorityFunctionMap { + names = append(names, name) + } + return names +} + +// ListAlgorithmProviders is called when listing all available algorithm providers in `kube-scheduler --help` +func ListAlgorithmProviders() string { + var availableAlgorithmProviders []string + for name := range algorithmProviderMap { + availableAlgorithmProviders = append(availableAlgorithmProviders, name) + } + sort.Strings(availableAlgorithmProviders) + return strings.Join(availableAlgorithmProviders, " | ") +} diff --git a/pkg/scheduler/factory/plugins_test.go b/pkg/scheduler/factory/plugins_test.go new file mode 100644 index 00000000000..a3508c139d2 --- /dev/null +++ b/pkg/scheduler/factory/plugins_test.go @@ -0,0 +1,82 @@ +/* +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 factory + +import ( + "testing" + + "k8s.io/kubernetes/pkg/scheduler/algorithm" + "k8s.io/kubernetes/pkg/scheduler/api" +) + +func TestAlgorithmNameValidation(t *testing.T) { + algorithmNamesShouldValidate := []string{ + "1SomeAlgo1rithm", + "someAlgor-ithm1", + } + algorithmNamesShouldNotValidate := []string{ + "-SomeAlgorithm", + "SomeAlgorithm-", + "Some,Alg:orithm", + } + for _, name := range algorithmNamesShouldValidate { + if !validName.MatchString(name) { + t.Errorf("%v should be a valid algorithm name but is not valid.", name) + } + } + for _, name := range algorithmNamesShouldNotValidate { + if validName.MatchString(name) { + t.Errorf("%v should be an invalid algorithm name but is valid.", name) + } + } +} + +func TestValidatePriorityConfigOverFlow(t *testing.T) { + tests := []struct { + description string + configs []algorithm.PriorityConfig + expected bool + }{ + { + description: "one of the weights is MaxInt", + configs: []algorithm.PriorityConfig{{Weight: api.MaxInt}, {Weight: 5}}, + expected: true, + }, + { + description: "after multiplication with MaxPriority the weight is larger than MaxWeight", + configs: []algorithm.PriorityConfig{{Weight: api.MaxInt/api.MaxPriority + api.MaxPriority}, {Weight: 5}}, + expected: true, + }, + { + description: "normal weights", + configs: []algorithm.PriorityConfig{{Weight: 10000}, {Weight: 5}}, + expected: false, + }, + } + for _, test := range tests { + err := validateSelectedConfigs(test.configs) + if test.expected { + if err == nil { + t.Errorf("Expected Overflow for %s", test.description) + } + } else { + if err != nil { + t.Errorf("Did not expect an overflow for %s", test.description) + } + } + } +} diff --git a/pkg/scheduler/metrics/BUILD b/pkg/scheduler/metrics/BUILD new file mode 100644 index 00000000000..81d6d19d587 --- /dev/null +++ b/pkg/scheduler/metrics/BUILD @@ -0,0 +1,26 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = ["metrics.go"], + importpath = "k8s.io/kubernetes/pkg/scheduler/metrics", + deps = ["//vendor/github.com/prometheus/client_golang/prometheus:go_default_library"], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/plugin/pkg/scheduler/metrics/metrics.go b/pkg/scheduler/metrics/metrics.go similarity index 100% rename from plugin/pkg/scheduler/metrics/metrics.go rename to pkg/scheduler/metrics/metrics.go diff --git a/pkg/scheduler/scheduler.go b/pkg/scheduler/scheduler.go new file mode 100644 index 00000000000..9fae7d117f7 --- /dev/null +++ b/pkg/scheduler/scheduler.go @@ -0,0 +1,494 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES 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 ( + "fmt" + "time" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/wait" + utilfeature "k8s.io/apiserver/pkg/util/feature" + clientset "k8s.io/client-go/kubernetes" + corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/tools/record" + "k8s.io/kubernetes/pkg/features" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + "k8s.io/kubernetes/pkg/scheduler/algorithm/predicates" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/core" + "k8s.io/kubernetes/pkg/scheduler/metrics" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" + "k8s.io/kubernetes/pkg/scheduler/util" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/scheduler/volumebinder" +) + +// Binder knows how to write a binding. +type Binder interface { + Bind(binding *v1.Binding) error +} + +// PodConditionUpdater updates the condition of a pod based on the passed +// PodCondition +type PodConditionUpdater interface { + Update(pod *v1.Pod, podCondition *v1.PodCondition) error +} + +// PodPreemptor has methods needed to delete a pod and to update +// annotations of the preemptor pod. +type PodPreemptor interface { + GetUpdatedPod(pod *v1.Pod) (*v1.Pod, error) + DeletePod(pod *v1.Pod) error + UpdatePodAnnotations(pod *v1.Pod, annots map[string]string) error + RemoveNominatedNodeAnnotation(pod *v1.Pod) error +} + +// Scheduler watches for new unscheduled pods. It attempts to find +// nodes that they fit on and writes bindings back to the api server. +type Scheduler struct { + config *Config +} + +// StopEverything closes the scheduler config's StopEverything channel, to shut +// down the Scheduler. +func (sched *Scheduler) StopEverything() { + close(sched.config.StopEverything) +} + +// Configurator defines I/O, caching, and other functionality needed to +// construct a new scheduler. An implementation of this can be seen in +// factory.go. +type Configurator interface { + GetPriorityFunctionConfigs(priorityKeys sets.String) ([]algorithm.PriorityConfig, error) + GetPriorityMetadataProducer() (algorithm.MetadataProducer, error) + GetPredicateMetadataProducer() (algorithm.PredicateMetadataProducer, error) + GetPredicates(predicateKeys sets.String) (map[string]algorithm.FitPredicate, error) + GetHardPodAffinitySymmetricWeight() int32 + GetSchedulerName() string + MakeDefaultErrorFunc(backoff *util.PodBackoff, podQueue core.SchedulingQueue) func(pod *v1.Pod, err error) + + // Needs to be exposed for things like integration tests where we want to make fake nodes. + GetNodeLister() corelisters.NodeLister + GetClient() clientset.Interface + GetScheduledPodLister() corelisters.PodLister + + Create() (*Config, error) + CreateFromProvider(providerName string) (*Config, error) + CreateFromConfig(policy schedulerapi.Policy) (*Config, error) + CreateFromKeys(predicateKeys, priorityKeys sets.String, extenders []algorithm.SchedulerExtender) (*Config, error) +} + +// Config is an implementation of the Scheduler's configured input data. +// TODO over time we should make this struct a hidden implementation detail of the scheduler. +type Config struct { + // It is expected that changes made via SchedulerCache will be observed + // by NodeLister and Algorithm. + SchedulerCache schedulercache.Cache + // Ecache is used for optimistically invalid affected cache items after + // successfully binding a pod + Ecache *core.EquivalenceCache + NodeLister algorithm.NodeLister + Algorithm algorithm.ScheduleAlgorithm + Binder Binder + // PodConditionUpdater is used only in case of scheduling errors. If we succeed + // with scheduling, PodScheduled condition will be updated in apiserver in /bind + // handler so that binding and setting PodCondition it is atomic. + PodConditionUpdater PodConditionUpdater + // PodPreemptor is used to evict pods and update pod annotations. + PodPreemptor PodPreemptor + + // NextPod should be a function that blocks until the next pod + // is available. We don't use a channel for this, because scheduling + // a pod may take some amount of time and we don't want pods to get + // stale while they sit in a channel. + NextPod func() *v1.Pod + + // WaitForCacheSync waits for scheduler cache to populate. + // It returns true if it was successful, false if the controller should shutdown. + WaitForCacheSync func() bool + + // Error is called if there is an error. It is passed the pod in + // question, and the error + Error func(*v1.Pod, error) + + // Recorder is the EventRecorder to use + Recorder record.EventRecorder + + // Close this to shut down the scheduler. + StopEverything chan struct{} + + // VolumeBinder handles PVC/PV binding for the pod. + VolumeBinder *volumebinder.VolumeBinder +} + +// NewFromConfigurator returns a new scheduler that is created entirely by the Configurator. Assumes Create() is implemented. +// Supports intermediate Config mutation for now if you provide modifier functions which will run after Config is created. +func NewFromConfigurator(c Configurator, modifiers ...func(c *Config)) (*Scheduler, error) { + cfg, err := c.Create() + if err != nil { + return nil, err + } + // Mutate it if any functions were provided, changes might be required for certain types of tests (i.e. change the recorder). + for _, modifier := range modifiers { + modifier(cfg) + } + // From this point on the config is immutable to the outside. + s := &Scheduler{ + config: cfg, + } + metrics.Register() + return s, nil +} + +// NewFromConfig returns a new scheduler using the provided Config. +func NewFromConfig(config *Config) *Scheduler { + metrics.Register() + return &Scheduler{ + config: config, + } +} + +// Run begins watching and scheduling. It waits for cache to be synced, then starts a goroutine and returns immediately. +func (sched *Scheduler) Run() { + if !sched.config.WaitForCacheSync() { + return + } + + if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) { + go sched.config.VolumeBinder.Run(sched.bindVolumesWorker, sched.config.StopEverything) + } + + go wait.Until(sched.scheduleOne, 0, sched.config.StopEverything) +} + +// Config return scheduler's config pointer. It is exposed for testing purposes. +func (sched *Scheduler) Config() *Config { + return sched.config +} + +// schedule implements the scheduling algorithm and returns the suggested host. +func (sched *Scheduler) schedule(pod *v1.Pod) (string, error) { + host, err := sched.config.Algorithm.Schedule(pod, sched.config.NodeLister) + if err != nil { + glog.V(1).Infof("Failed to schedule pod: %v/%v", pod.Namespace, pod.Name) + pod = pod.DeepCopy() + sched.config.Error(pod, err) + sched.config.Recorder.Eventf(pod, v1.EventTypeWarning, "FailedScheduling", "%v", err) + sched.config.PodConditionUpdater.Update(pod, &v1.PodCondition{ + Type: v1.PodScheduled, + Status: v1.ConditionFalse, + Reason: v1.PodReasonUnschedulable, + Message: err.Error(), + }) + return "", err + } + return host, err +} + +// preempt tries to create room for a pod that has failed to schedule, by preempting lower priority pods if possible. +// If it succeeds, it adds the name of the node where preemption has happened to the pod annotations. +// It returns the node name and an error if any. +func (sched *Scheduler) preempt(preemptor *v1.Pod, scheduleErr error) (string, error) { + if !util.PodPriorityEnabled() { + glog.V(3).Infof("Pod priority feature is not enabled. No preemption is performed.") + return "", nil + } + preemptor, err := sched.config.PodPreemptor.GetUpdatedPod(preemptor) + if err != nil { + glog.Errorf("Error getting the updated preemptor pod object: %v", err) + return "", err + } + node, victims, nominatedPodsToClear, err := sched.config.Algorithm.Preempt(preemptor, sched.config.NodeLister, scheduleErr) + if err != nil { + glog.Errorf("Error preempting victims to make room for %v/%v.", preemptor.Namespace, preemptor.Name) + return "", err + } + var nodeName = "" + if node != nil { + nodeName = node.Name + annotations := map[string]string{core.NominatedNodeAnnotationKey: nodeName} + err = sched.config.PodPreemptor.UpdatePodAnnotations(preemptor, annotations) + if err != nil { + glog.Errorf("Error in preemption process. Cannot update pod %v annotations: %v", preemptor.Name, err) + return "", err + } + for _, victim := range victims { + if err := sched.config.PodPreemptor.DeletePod(victim); err != nil { + glog.Errorf("Error preempting pod %v/%v: %v", victim.Namespace, victim.Name, err) + return "", err + } + sched.config.Recorder.Eventf(victim, v1.EventTypeNormal, "Preempted", "by %v/%v on node %v", preemptor.Namespace, preemptor.Name, nodeName) + } + } + // Clearing nominated pods should happen outside of "if node != nil". Node could + // be nil when a pod with nominated node name is eligible to preempt again, + // but preemption logic does not find any node for it. In that case Preempt() + // function of generic_scheduler.go returns the pod itself for removal of the annotation. + for _, p := range nominatedPodsToClear { + rErr := sched.config.PodPreemptor.RemoveNominatedNodeAnnotation(p) + if rErr != nil { + glog.Errorf("Cannot remove nominated node annotation of pod: %v", rErr) + // We do not return as this error is not critical. + } + } + return nodeName, err +} + +// assumeAndBindVolumes will update the volume cache and then asynchronously bind volumes if required. +// +// If volume binding is required, then the bind volumes routine will update the pod to send it back through +// the scheduler. +// +// Otherwise, return nil error and continue to assume the pod. +// +// This function modifies assumed if volume binding is required. +func (sched *Scheduler) assumeAndBindVolumes(assumed *v1.Pod, host string) error { + if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) { + allBound, bindingRequired, err := sched.config.VolumeBinder.Binder.AssumePodVolumes(assumed, host) + if err != nil { + sched.config.Error(assumed, err) + sched.config.Recorder.Eventf(assumed, v1.EventTypeWarning, "FailedScheduling", "AssumePodVolumes failed: %v", err) + sched.config.PodConditionUpdater.Update(assumed, &v1.PodCondition{ + Type: v1.PodScheduled, + Status: v1.ConditionFalse, + Reason: "SchedulerError", + Message: err.Error(), + }) + return err + } + if !allBound { + err = fmt.Errorf("Volume binding started, waiting for completion") + if bindingRequired { + if sched.config.Ecache != nil { + invalidPredicates := sets.NewString(predicates.CheckVolumeBindingPred) + sched.config.Ecache.InvalidateCachedPredicateItemOfAllNodes(invalidPredicates) + } + + // bindVolumesWorker() will update the Pod object to put it back in the scheduler queue + sched.config.VolumeBinder.BindQueue.Add(assumed) + } else { + // We are just waiting for PV controller to finish binding, put it back in the + // scheduler queue + sched.config.Error(assumed, err) + sched.config.Recorder.Eventf(assumed, v1.EventTypeNormal, "FailedScheduling", "%v", err) + sched.config.PodConditionUpdater.Update(assumed, &v1.PodCondition{ + Type: v1.PodScheduled, + Status: v1.ConditionFalse, + Reason: "VolumeBindingWaiting", + }) + } + return err + } + } + return nil +} + +// bindVolumesWorker() processes pods queued in assumeAndBindVolumes() and tries to +// make the API update for volume binding. +// This function runs forever until the volume BindQueue is closed. +func (sched *Scheduler) bindVolumesWorker() { + workFunc := func() bool { + keyObj, quit := sched.config.VolumeBinder.BindQueue.Get() + if quit { + return true + } + defer sched.config.VolumeBinder.BindQueue.Done(keyObj) + + assumed, ok := keyObj.(*v1.Pod) + if !ok { + glog.V(4).Infof("Object is not a *v1.Pod") + return false + } + + // TODO: add metrics + var reason string + var eventType string + + glog.V(5).Infof("Trying to bind volumes for pod \"%v/%v\"", assumed.Namespace, assumed.Name) + + // The Pod is always sent back to the scheduler afterwards. + err := sched.config.VolumeBinder.Binder.BindPodVolumes(assumed) + if err != nil { + glog.V(1).Infof("Failed to bind volumes for pod \"%v/%v\"", assumed.Namespace, assumed.Name, err) + reason = "VolumeBindingFailed" + eventType = v1.EventTypeWarning + } else { + glog.V(4).Infof("Successfully bound volumes for pod \"%v/%v\"", assumed.Namespace, assumed.Name) + reason = "VolumeBindingWaiting" + eventType = v1.EventTypeNormal + err = fmt.Errorf("Volume binding started, waiting for completion") + } + + // Always fail scheduling regardless of binding success. + // The Pod needs to be sent back through the scheduler to: + // * Retry volume binding if it fails. + // * Retry volume binding if dynamic provisioning fails. + // * Bind the Pod to the Node once all volumes are bound. + sched.config.Error(assumed, err) + sched.config.Recorder.Eventf(assumed, eventType, "FailedScheduling", "%v", err) + sched.config.PodConditionUpdater.Update(assumed, &v1.PodCondition{ + Type: v1.PodScheduled, + Status: v1.ConditionFalse, + Reason: reason, + }) + return false + } + + for { + if quit := workFunc(); quit { + glog.V(4).Infof("bindVolumesWorker shutting down") + break + } + } +} + +// 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(assumed *v1.Pod, 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.config.SchedulerCache.AssumePod(assumed); err != nil { + glog.Errorf("scheduler cache AssumePod failed: %v", err) + + // 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). + sched.config.Error(assumed, err) + sched.config.Recorder.Eventf(assumed, v1.EventTypeWarning, "FailedScheduling", "AssumePod failed: %v", err) + sched.config.PodConditionUpdater.Update(assumed, &v1.PodCondition{ + Type: v1.PodScheduled, + Status: v1.ConditionFalse, + Reason: "SchedulerError", + Message: err.Error(), + }) + return err + } + + // Optimistically assume that the binding will succeed, so we need to invalidate affected + // predicates in equivalence cache. + // If the binding fails, these invalidated item will not break anything. + if sched.config.Ecache != nil { + sched.config.Ecache.InvalidateCachedPredicateItemForPodAdd(assumed, host) + } + return nil +} + +// bind binds a pod to a given node defined in a binding object. We expect this to run asynchronously, so we +// handle binding metrics internally. +func (sched *Scheduler) bind(assumed *v1.Pod, b *v1.Binding) error { + bindingStart := time.Now() + // If binding succeeded then PodScheduled condition will be updated in apiserver so that + // it's atomic with setting host. + err := sched.config.Binder.Bind(b) + if err := sched.config.SchedulerCache.FinishBinding(assumed); err != nil { + glog.Errorf("scheduler cache FinishBinding failed: %v", err) + } + if err != nil { + glog.V(1).Infof("Failed to bind pod: %v/%v", assumed.Namespace, assumed.Name) + if err := sched.config.SchedulerCache.ForgetPod(assumed); err != nil { + glog.Errorf("scheduler cache ForgetPod failed: %v", err) + } + sched.config.Error(assumed, err) + sched.config.Recorder.Eventf(assumed, v1.EventTypeWarning, "FailedScheduling", "Binding rejected: %v", err) + sched.config.PodConditionUpdater.Update(assumed, &v1.PodCondition{ + Type: v1.PodScheduled, + Status: v1.ConditionFalse, + Reason: "BindingRejected", + }) + return err + } + + metrics.BindingLatency.Observe(metrics.SinceInMicroseconds(bindingStart)) + sched.config.Recorder.Eventf(assumed, v1.EventTypeNormal, "Scheduled", "Successfully assigned %v to %v", assumed.Name, b.Target.Name) + return nil +} + +// scheduleOne does the entire scheduling workflow for a single pod. It is serialized on the scheduling algorithm's host fitting. +func (sched *Scheduler) scheduleOne() { + pod := sched.config.NextPod() + if pod.DeletionTimestamp != nil { + sched.config.Recorder.Eventf(pod, v1.EventTypeWarning, "FailedScheduling", "skip schedule deleting pod: %v/%v", pod.Namespace, pod.Name) + glog.V(3).Infof("Skip schedule deleting pod: %v/%v", pod.Namespace, pod.Name) + return + } + + glog.V(3).Infof("Attempting to schedule pod: %v/%v", pod.Namespace, pod.Name) + + // Synchronously attempt to find a fit for the pod. + start := time.Now() + suggestedHost, err := sched.schedule(pod) + metrics.SchedulingAlgorithmLatency.Observe(metrics.SinceInMicroseconds(start)) + if err != nil { + // schedule() may have failed because the pod would not fit on any host, so we try to + // preempt, with the expectation that the next time the pod is tried for scheduling it + // will fit due to the preemption. It is also possible that a different pod will schedule + // into the resources that were preempted, but this is harmless. + if fitError, ok := err.(*core.FitError); ok { + sched.preempt(pod, fitError) + } + return + } + + // 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. + assumedPod := pod.DeepCopy() + + // Assume volumes first before assuming the pod. + // + // If no volumes need binding, then nil is returned, and continue to assume the pod. + // + // Otherwise, error is returned and volume binding is started asynchronously for all of the pod's volumes. + // scheduleOne() returns immediately on error, so that it doesn't continue to assume the pod. + // + // After the asynchronous volume binding updates are made, it will send the pod back through the scheduler for + // subsequent passes until all volumes are fully bound. + // + // This function modifies 'assumedPod' if volume binding is required. + err = sched.assumeAndBindVolumes(assumedPod, suggestedHost) + if err != nil { + return + } + + // assume modifies `assumedPod` by setting NodeName=suggestedHost + err = sched.assume(assumedPod, suggestedHost) + if err != nil { + return + } + // bind the pod to its host asynchronously (we can do this b/c of the assumption step above). + go func() { + err := sched.bind(assumedPod, &v1.Binding{ + ObjectMeta: metav1.ObjectMeta{Namespace: assumedPod.Namespace, Name: assumedPod.Name, UID: assumedPod.UID}, + Target: v1.ObjectReference{ + Kind: "Node", + Name: suggestedHost, + }, + }) + metrics.E2eSchedulingLatency.Observe(metrics.SinceInMicroseconds(start)) + if err != nil { + glog.Errorf("Internal error binding pod: (%v)", err) + } + }() +} diff --git a/pkg/scheduler/scheduler_test.go b/pkg/scheduler/scheduler_test.go new file mode 100644 index 00000000000..73d4abcc280 --- /dev/null +++ b/pkg/scheduler/scheduler_test.go @@ -0,0 +1,815 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES 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 ( + "errors" + "fmt" + "reflect" + "testing" + "time" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/diff" + "k8s.io/apimachinery/pkg/util/wait" + utilfeature "k8s.io/apiserver/pkg/util/feature" + clientcache "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/record" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/controller/volume/persistentvolume" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + "k8s.io/kubernetes/pkg/scheduler/algorithm/predicates" + "k8s.io/kubernetes/pkg/scheduler/core" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" + schedulertesting "k8s.io/kubernetes/pkg/scheduler/testing" + "k8s.io/kubernetes/pkg/scheduler/util" + "k8s.io/kubernetes/pkg/scheduler/volumebinder" +) + +type fakeBinder struct { + b func(binding *v1.Binding) error +} + +func (fb fakeBinder) Bind(binding *v1.Binding) error { return fb.b(binding) } + +type fakePodConditionUpdater struct{} + +func (fc fakePodConditionUpdater) Update(pod *v1.Pod, podCondition *v1.PodCondition) error { + return nil +} + +type fakePodPreemptor struct{} + +func (fp fakePodPreemptor) GetUpdatedPod(pod *v1.Pod) (*v1.Pod, error) { + return pod, nil +} + +func (fp fakePodPreemptor) DeletePod(pod *v1.Pod) error { + return nil +} + +func (fp fakePodPreemptor) UpdatePodAnnotations(pod *v1.Pod, annots map[string]string) error { + return nil +} + +func (fp fakePodPreemptor) RemoveNominatedNodeAnnotation(pod *v1.Pod) error { + return nil +} + +func podWithID(id, desiredHost string) *v1.Pod { + return &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: id, SelfLink: util.Test.SelfLink(string(v1.ResourcePods), id)}, + Spec: v1.PodSpec{ + NodeName: desiredHost, + }, + } +} + +func deletingPod(id string) *v1.Pod { + deletionTimestamp := metav1.Now() + return &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: id, SelfLink: util.Test.SelfLink(string(v1.ResourcePods), id), DeletionTimestamp: &deletionTimestamp}, + Spec: v1.PodSpec{ + NodeName: "", + }, + } +} + +func podWithPort(id, desiredHost string, port int) *v1.Pod { + pod := podWithID(id, desiredHost) + pod.Spec.Containers = []v1.Container{ + {Name: "ctr", Ports: []v1.ContainerPort{{HostPort: int32(port)}}}, + } + return pod +} + +func podWithResources(id, desiredHost string, limits v1.ResourceList, requests v1.ResourceList) *v1.Pod { + pod := podWithID(id, desiredHost) + pod.Spec.Containers = []v1.Container{ + {Name: "ctr", Resources: v1.ResourceRequirements{Limits: limits, Requests: requests}}, + } + return pod +} + +type mockScheduler struct { + machine string + err error +} + +func (es mockScheduler) Schedule(pod *v1.Pod, ml algorithm.NodeLister) (string, error) { + return es.machine, es.err +} + +func (es mockScheduler) Predicates() map[string]algorithm.FitPredicate { + return nil +} +func (es mockScheduler) Prioritizers() []algorithm.PriorityConfig { + return nil +} + +func (es mockScheduler) Preempt(pod *v1.Pod, nodeLister algorithm.NodeLister, scheduleErr error) (*v1.Node, []*v1.Pod, []*v1.Pod, error) { + return nil, nil, nil, nil +} + +func TestScheduler(t *testing.T) { + eventBroadcaster := record.NewBroadcaster() + eventBroadcaster.StartLogging(t.Logf).Stop() + errS := errors.New("scheduler") + errB := errors.New("binder") + testNode := v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "machine1"}} + + table := []struct { + injectBindError error + sendPod *v1.Pod + algo algorithm.ScheduleAlgorithm + expectErrorPod *v1.Pod + expectForgetPod *v1.Pod + expectAssumedPod *v1.Pod + expectError error + expectBind *v1.Binding + eventReason string + }{ + { + sendPod: podWithID("foo", ""), + algo: mockScheduler{testNode.Name, nil}, + expectBind: &v1.Binding{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Target: v1.ObjectReference{Kind: "Node", Name: testNode.Name}}, + expectAssumedPod: podWithID("foo", testNode.Name), + eventReason: "Scheduled", + }, { + sendPod: podWithID("foo", ""), + algo: mockScheduler{testNode.Name, errS}, + expectError: errS, + expectErrorPod: podWithID("foo", ""), + eventReason: "FailedScheduling", + }, { + sendPod: podWithID("foo", ""), + algo: mockScheduler{testNode.Name, nil}, + expectBind: &v1.Binding{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Target: v1.ObjectReference{Kind: "Node", Name: testNode.Name}}, + expectAssumedPod: podWithID("foo", testNode.Name), + injectBindError: errB, + expectError: errB, + expectErrorPod: podWithID("foo", testNode.Name), + expectForgetPod: podWithID("foo", testNode.Name), + eventReason: "FailedScheduling", + }, { + sendPod: deletingPod("foo"), + algo: mockScheduler{"", nil}, + eventReason: "FailedScheduling", + }, + } + + for i, item := range table { + var gotError error + var gotPod *v1.Pod + var gotForgetPod *v1.Pod + var gotAssumedPod *v1.Pod + var gotBinding *v1.Binding + configurator := &FakeConfigurator{ + Config: &Config{ + SchedulerCache: &schedulertesting.FakeCache{ + ForgetFunc: func(pod *v1.Pod) { + gotForgetPod = pod + }, + AssumeFunc: func(pod *v1.Pod) { + gotAssumedPod = pod + }, + }, + NodeLister: schedulertesting.FakeNodeLister( + []*v1.Node{&testNode}, + ), + Algorithm: item.algo, + Binder: fakeBinder{func(b *v1.Binding) error { + gotBinding = b + return item.injectBindError + }}, + PodConditionUpdater: fakePodConditionUpdater{}, + Error: func(p *v1.Pod, err error) { + gotPod = p + gotError = err + }, + NextPod: func() *v1.Pod { + return item.sendPod + }, + Recorder: eventBroadcaster.NewRecorder(legacyscheme.Scheme, v1.EventSource{Component: "scheduler"}), + }, + } + + s, _ := NewFromConfigurator(configurator, nil...) + called := make(chan struct{}) + events := eventBroadcaster.StartEventWatcher(func(e *v1.Event) { + if e, a := item.eventReason, e.Reason; e != a { + t.Errorf("%v: expected %v, got %v", i, e, a) + } + close(called) + }) + s.scheduleOne() + <-called + if e, a := item.expectAssumedPod, gotAssumedPod; !reflect.DeepEqual(e, a) { + t.Errorf("%v: assumed pod: wanted %v, got %v", i, e, a) + } + if e, a := item.expectErrorPod, gotPod; !reflect.DeepEqual(e, a) { + t.Errorf("%v: error pod: wanted %v, got %v", i, e, a) + } + if e, a := item.expectForgetPod, gotForgetPod; !reflect.DeepEqual(e, a) { + t.Errorf("%v: forget pod: wanted %v, got %v", i, e, a) + } + if e, a := item.expectError, gotError; !reflect.DeepEqual(e, a) { + t.Errorf("%v: error: wanted %v, got %v", i, e, a) + } + if e, a := item.expectBind, gotBinding; !reflect.DeepEqual(e, a) { + t.Errorf("%v: error: %s", i, diff.ObjectDiff(e, a)) + } + events.Stop() + } +} + +func TestSchedulerNoPhantomPodAfterExpire(t *testing.T) { + stop := make(chan struct{}) + defer close(stop) + queuedPodStore := clientcache.NewFIFO(clientcache.MetaNamespaceKeyFunc) + scache := schedulercache.New(100*time.Millisecond, stop) + pod := podWithPort("pod.Name", "", 8080) + node := v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "machine1"}} + scache.AddNode(&node) + nodeLister := schedulertesting.FakeNodeLister([]*v1.Node{&node}) + predicateMap := map[string]algorithm.FitPredicate{"PodFitsHostPorts": predicates.PodFitsHostPorts} + scheduler, bindingChan, _ := setupTestSchedulerWithOnePodOnNode(t, queuedPodStore, scache, nodeLister, predicateMap, pod, &node) + + waitPodExpireChan := make(chan struct{}) + timeout := make(chan struct{}) + go func() { + for { + select { + case <-timeout: + return + default: + } + pods, err := scache.List(labels.Everything()) + if err != nil { + t.Fatalf("cache.List failed: %v", err) + } + if len(pods) == 0 { + close(waitPodExpireChan) + return + } + time.Sleep(100 * time.Millisecond) + } + }() + // waiting for the assumed pod to expire + select { + 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) + queuedPodStore.Add(secondPod) + scheduler.scheduleOne() + select { + case b := <-bindingChan: + expectBinding := &v1.Binding{ + ObjectMeta: metav1.ObjectMeta{Name: "bar"}, + Target: v1.ObjectReference{Kind: "Node", Name: node.Name}, + } + if !reflect.DeepEqual(expectBinding, b) { + t.Errorf("binding want=%v, get=%v", expectBinding, b) + } + case <-time.After(wait.ForeverTestTimeout): + t.Fatalf("timeout in binding after %v", wait.ForeverTestTimeout) + } +} + +func TestSchedulerNoPhantomPodAfterDelete(t *testing.T) { + stop := make(chan struct{}) + defer close(stop) + queuedPodStore := clientcache.NewFIFO(clientcache.MetaNamespaceKeyFunc) + scache := schedulercache.New(10*time.Minute, stop) + firstPod := podWithPort("pod.Name", "", 8080) + node := v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "machine1"}} + scache.AddNode(&node) + nodeLister := schedulertesting.FakeNodeLister([]*v1.Node{&node}) + predicateMap := map[string]algorithm.FitPredicate{"PodFitsHostPorts": predicates.PodFitsHostPorts} + scheduler, bindingChan, errChan := setupTestSchedulerWithOnePodOnNode(t, queuedPodStore, scache, nodeLister, predicateMap, firstPod, &node) + + // We use conflicted pod ports to incur fit predicate failure. + secondPod := podWithPort("bar", "", 8080) + queuedPodStore.Add(secondPod) + // queuedPodStore: [bar:8080] + // cache: [(assumed)foo:8080] + + scheduler.scheduleOne() + select { + case err := <-errChan: + expectErr := &core.FitError{ + Pod: secondPod, + NumAllNodes: 1, + FailedPredicates: core.FailedPredicateMap{node.Name: []algorithm.PredicateFailureReason{predicates.ErrPodNotFitsHostPorts}}, + } + if !reflect.DeepEqual(expectErr, err) { + t.Errorf("err want=%v, get=%v", expectErr, err) + } + case <-time.After(wait.ForeverTestTimeout): + t.Fatalf("timeout in fitting after %v", wait.ForeverTestTimeout) + } + + // We mimic the workflow of cache behavior when a pod is removed by user. + // Note: if the schedulercache timeout would be super short, the first pod would expire + // and would be removed itself (without any explicit actions on schedulercache). Even in that case, + // explicitly AddPod will as well correct the behavior. + firstPod.Spec.NodeName = node.Name + if err := scache.AddPod(firstPod); err != nil { + t.Fatalf("err: %v", err) + } + if err := scache.RemovePod(firstPod); err != nil { + t.Fatalf("err: %v", err) + } + + queuedPodStore.Add(secondPod) + scheduler.scheduleOne() + select { + case b := <-bindingChan: + expectBinding := &v1.Binding{ + ObjectMeta: metav1.ObjectMeta{Name: "bar"}, + Target: v1.ObjectReference{Kind: "Node", Name: node.Name}, + } + if !reflect.DeepEqual(expectBinding, b) { + t.Errorf("binding want=%v, get=%v", expectBinding, b) + } + case <-time.After(wait.ForeverTestTimeout): + t.Fatalf("timeout in binding after %v", wait.ForeverTestTimeout) + } +} + +// Scheduler should preserve predicate constraint even if binding was longer +// than cache ttl +func TestSchedulerErrorWithLongBinding(t *testing.T) { + stop := make(chan struct{}) + defer close(stop) + + firstPod := podWithPort("foo", "", 8080) + conflictPod := podWithPort("bar", "", 8080) + pods := map[string]*v1.Pod{firstPod.Name: firstPod, conflictPod.Name: conflictPod} + for _, test := range []struct { + Expected map[string]bool + CacheTTL time.Duration + BindingDuration time.Duration + }{ + { + Expected: map[string]bool{firstPod.Name: true}, + CacheTTL: 100 * time.Millisecond, + BindingDuration: 300 * time.Millisecond, + }, + { + Expected: map[string]bool{firstPod.Name: true}, + CacheTTL: 10 * time.Second, + BindingDuration: 300 * time.Millisecond, + }, + } { + queuedPodStore := clientcache.NewFIFO(clientcache.MetaNamespaceKeyFunc) + scache := schedulercache.New(test.CacheTTL, stop) + + node := v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "machine1"}} + scache.AddNode(&node) + + nodeLister := schedulertesting.FakeNodeLister([]*v1.Node{&node}) + predicateMap := map[string]algorithm.FitPredicate{"PodFitsHostPorts": predicates.PodFitsHostPorts} + + scheduler, bindingChan := setupTestSchedulerLongBindingWithRetry( + queuedPodStore, scache, nodeLister, predicateMap, stop, test.BindingDuration) + scheduler.Run() + queuedPodStore.Add(firstPod) + queuedPodStore.Add(conflictPod) + + resultBindings := map[string]bool{} + waitChan := time.After(5 * time.Second) + for finished := false; !finished; { + select { + case b := <-bindingChan: + resultBindings[b.Name] = true + p := pods[b.Name] + p.Spec.NodeName = b.Target.Name + scache.AddPod(p) + case <-waitChan: + finished = true + } + } + if !reflect.DeepEqual(resultBindings, test.Expected) { + t.Errorf("Result binding are not equal to expected. %v != %v", resultBindings, test.Expected) + } + } +} + +// queuedPodStore: pods queued before processing. +// cache: scheduler cache that might contain assumed pods. +func setupTestSchedulerWithOnePodOnNode(t *testing.T, queuedPodStore *clientcache.FIFO, scache schedulercache.Cache, + nodeLister schedulertesting.FakeNodeLister, predicateMap map[string]algorithm.FitPredicate, pod *v1.Pod, node *v1.Node) (*Scheduler, chan *v1.Binding, chan error) { + + scheduler, bindingChan, errChan := setupTestScheduler(queuedPodStore, scache, nodeLister, predicateMap, nil) + + queuedPodStore.Add(pod) + // queuedPodStore: [foo:8080] + // cache: [] + + scheduler.scheduleOne() + // queuedPodStore: [] + // cache: [(assumed)foo:8080] + + select { + case b := <-bindingChan: + expectBinding := &v1.Binding{ + ObjectMeta: metav1.ObjectMeta{Name: pod.Name}, + Target: v1.ObjectReference{Kind: "Node", Name: node.Name}, + } + if !reflect.DeepEqual(expectBinding, b) { + t.Errorf("binding want=%v, get=%v", expectBinding, b) + } + case <-time.After(wait.ForeverTestTimeout): + t.Fatalf("timeout after %v", wait.ForeverTestTimeout) + } + return scheduler, bindingChan, errChan +} + +func TestSchedulerFailedSchedulingReasons(t *testing.T) { + stop := make(chan struct{}) + defer close(stop) + queuedPodStore := clientcache.NewFIFO(clientcache.MetaNamespaceKeyFunc) + scache := schedulercache.New(10*time.Minute, stop) + + // Design the baseline for the pods, and we will make nodes that dont fit it later. + var cpu = int64(4) + var mem = int64(500) + podWithTooBigResourceRequests := podWithResources("bar", "", v1.ResourceList{ + v1.ResourceCPU: *(resource.NewQuantity(cpu, resource.DecimalSI)), + v1.ResourceMemory: *(resource.NewQuantity(mem, resource.DecimalSI)), + }, v1.ResourceList{ + v1.ResourceCPU: *(resource.NewQuantity(cpu, resource.DecimalSI)), + v1.ResourceMemory: *(resource.NewQuantity(mem, resource.DecimalSI)), + }) + + // create several nodes which cannot schedule the above pod + nodes := []*v1.Node{} + for i := 0; i < 100; i++ { + node := v1.Node{ + ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("machine%v", i)}, + Status: v1.NodeStatus{ + Capacity: v1.ResourceList{ + v1.ResourceCPU: *(resource.NewQuantity(cpu/2, resource.DecimalSI)), + v1.ResourceMemory: *(resource.NewQuantity(mem/5, resource.DecimalSI)), + v1.ResourcePods: *(resource.NewQuantity(10, resource.DecimalSI)), + }, + Allocatable: v1.ResourceList{ + v1.ResourceCPU: *(resource.NewQuantity(cpu/2, resource.DecimalSI)), + v1.ResourceMemory: *(resource.NewQuantity(mem/5, resource.DecimalSI)), + v1.ResourcePods: *(resource.NewQuantity(10, resource.DecimalSI)), + }}, + } + scache.AddNode(&node) + nodes = append(nodes, &node) + } + nodeLister := schedulertesting.FakeNodeLister(nodes) + predicateMap := map[string]algorithm.FitPredicate{ + "PodFitsResources": predicates.PodFitsResources, + } + + // Create expected failure reasons for all the nodes. Hopefully they will get rolled up into a non-spammy summary. + failedPredicatesMap := core.FailedPredicateMap{} + for _, node := range nodes { + failedPredicatesMap[node.Name] = []algorithm.PredicateFailureReason{ + predicates.NewInsufficientResourceError(v1.ResourceCPU, 4000, 0, 2000), + predicates.NewInsufficientResourceError(v1.ResourceMemory, 500, 0, 100), + } + } + scheduler, _, errChan := setupTestScheduler(queuedPodStore, scache, nodeLister, predicateMap, nil) + + queuedPodStore.Add(podWithTooBigResourceRequests) + scheduler.scheduleOne() + select { + case err := <-errChan: + expectErr := &core.FitError{ + Pod: podWithTooBigResourceRequests, + NumAllNodes: len(nodes), + FailedPredicates: failedPredicatesMap, + } + if len(fmt.Sprint(expectErr)) > 150 { + t.Errorf("message is too spammy ! %v ", len(fmt.Sprint(expectErr))) + } + if !reflect.DeepEqual(expectErr, err) { + t.Errorf("\n err \nWANT=%+v,\nGOT=%+v", expectErr, err) + } + case <-time.After(wait.ForeverTestTimeout): + t.Fatalf("timeout after %v", wait.ForeverTestTimeout) + } +} + +// queuedPodStore: pods queued before processing. +// scache: scheduler cache that might contain assumed pods. +func setupTestScheduler(queuedPodStore *clientcache.FIFO, scache schedulercache.Cache, nodeLister schedulertesting.FakeNodeLister, predicateMap map[string]algorithm.FitPredicate, recorder record.EventRecorder) (*Scheduler, chan *v1.Binding, chan error) { + algo := core.NewGenericScheduler( + scache, + nil, + nil, + predicateMap, + algorithm.EmptyPredicateMetadataProducer, + []algorithm.PriorityConfig{}, + algorithm.EmptyMetadataProducer, + []algorithm.SchedulerExtender{}, + nil, + schedulertesting.FakePersistentVolumeClaimLister{}) + bindingChan := make(chan *v1.Binding, 1) + errChan := make(chan error, 1) + configurator := &FakeConfigurator{ + Config: &Config{ + SchedulerCache: scache, + NodeLister: nodeLister, + Algorithm: algo, + Binder: fakeBinder{func(b *v1.Binding) error { + bindingChan <- b + return nil + }}, + NextPod: func() *v1.Pod { + return clientcache.Pop(queuedPodStore).(*v1.Pod) + }, + Error: func(p *v1.Pod, err error) { + errChan <- err + }, + Recorder: &record.FakeRecorder{}, + PodConditionUpdater: fakePodConditionUpdater{}, + PodPreemptor: fakePodPreemptor{}, + }, + } + + if recorder != nil { + configurator.Config.Recorder = recorder + } + + sched, _ := NewFromConfigurator(configurator, nil...) + + return sched, bindingChan, errChan +} + +func setupTestSchedulerLongBindingWithRetry(queuedPodStore *clientcache.FIFO, scache schedulercache.Cache, nodeLister schedulertesting.FakeNodeLister, predicateMap map[string]algorithm.FitPredicate, stop chan struct{}, bindingTime time.Duration) (*Scheduler, chan *v1.Binding) { + algo := core.NewGenericScheduler( + scache, + nil, + nil, + predicateMap, + algorithm.EmptyPredicateMetadataProducer, + []algorithm.PriorityConfig{}, + algorithm.EmptyMetadataProducer, + []algorithm.SchedulerExtender{}, + nil, + schedulertesting.FakePersistentVolumeClaimLister{}) + bindingChan := make(chan *v1.Binding, 2) + configurator := &FakeConfigurator{ + Config: &Config{ + SchedulerCache: scache, + NodeLister: nodeLister, + Algorithm: algo, + Binder: fakeBinder{func(b *v1.Binding) error { + time.Sleep(bindingTime) + bindingChan <- b + return nil + }}, + WaitForCacheSync: func() bool { + return true + }, + NextPod: func() *v1.Pod { + return clientcache.Pop(queuedPodStore).(*v1.Pod) + }, + Error: func(p *v1.Pod, err error) { + queuedPodStore.AddIfNotPresent(p) + }, + Recorder: &record.FakeRecorder{}, + PodConditionUpdater: fakePodConditionUpdater{}, + PodPreemptor: fakePodPreemptor{}, + StopEverything: stop, + }, + } + + sched, _ := NewFromConfigurator(configurator, nil...) + + return sched, bindingChan +} + +func setupTestSchedulerWithVolumeBinding(fakeVolumeBinder *volumebinder.VolumeBinder, stop <-chan struct{}, broadcaster record.EventBroadcaster) (*Scheduler, chan *v1.Binding, chan error) { + testNode := v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "machine1"}} + nodeLister := schedulertesting.FakeNodeLister([]*v1.Node{&testNode}) + queuedPodStore := clientcache.NewFIFO(clientcache.MetaNamespaceKeyFunc) + queuedPodStore.Add(podWithID("foo", "")) + scache := schedulercache.New(10*time.Minute, stop) + scache.AddNode(&testNode) + + predicateMap := map[string]algorithm.FitPredicate{ + predicates.CheckVolumeBindingPred: predicates.NewVolumeBindingPredicate(fakeVolumeBinder), + } + + recorder := broadcaster.NewRecorder(legacyscheme.Scheme, v1.EventSource{Component: "scheduler"}) + s, bindingChan, errChan := setupTestScheduler(queuedPodStore, scache, nodeLister, predicateMap, recorder) + s.config.VolumeBinder = fakeVolumeBinder + return s, bindingChan, errChan +} + +// This is a workaround because golint complains that errors cannot +// end with punctuation. However, the real predicate error message does +// end with a period. +func makePredicateError(failReason string) error { + s := fmt.Sprintf("0/1 nodes are available: %v.", failReason) + return fmt.Errorf(s) +} + +func TestSchedulerWithVolumeBinding(t *testing.T) { + order := []string{predicates.CheckVolumeBindingPred, predicates.GeneralPred} + predicates.SetPredicatesOrdering(order) + findErr := fmt.Errorf("find err") + assumeErr := fmt.Errorf("assume err") + bindErr := fmt.Errorf("bind err") + + eventBroadcaster := record.NewBroadcaster() + eventBroadcaster.StartLogging(t.Logf).Stop() + + // This can be small because we wait for pod to finish scheduling first + chanTimeout := 2 * time.Second + + utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true") + defer utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false") + + table := map[string]struct { + expectError error + expectPodBind *v1.Binding + expectAssumeCalled bool + expectBindCalled bool + eventReason string + volumeBinderConfig *persistentvolume.FakeVolumeBinderConfig + }{ + "all-bound": { + volumeBinderConfig: &persistentvolume.FakeVolumeBinderConfig{ + AllBound: true, + FindUnboundSatsified: true, + FindBoundSatsified: true, + }, + expectAssumeCalled: true, + expectPodBind: &v1.Binding{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Target: v1.ObjectReference{Kind: "Node", Name: "machine1"}}, + eventReason: "Scheduled", + }, + "bound,invalid-pv-affinity": { + volumeBinderConfig: &persistentvolume.FakeVolumeBinderConfig{ + AllBound: true, + FindUnboundSatsified: true, + FindBoundSatsified: false, + }, + eventReason: "FailedScheduling", + expectError: makePredicateError("1 VolumeNodeAffinityConflict"), + }, + "unbound,no-matches": { + volumeBinderConfig: &persistentvolume.FakeVolumeBinderConfig{ + FindUnboundSatsified: false, + FindBoundSatsified: true, + }, + eventReason: "FailedScheduling", + expectError: makePredicateError("1 VolumeBindingNoMatch"), + }, + "bound-and-unbound-unsatisfied": { + volumeBinderConfig: &persistentvolume.FakeVolumeBinderConfig{ + FindUnboundSatsified: false, + FindBoundSatsified: false, + }, + eventReason: "FailedScheduling", + expectError: makePredicateError("1 VolumeBindingNoMatch, 1 VolumeNodeAffinityConflict"), + }, + "unbound,found-matches": { + volumeBinderConfig: &persistentvolume.FakeVolumeBinderConfig{ + FindUnboundSatsified: true, + FindBoundSatsified: true, + AssumeBindingRequired: true, + }, + expectAssumeCalled: true, + expectBindCalled: true, + eventReason: "FailedScheduling", + expectError: fmt.Errorf("Volume binding started, waiting for completion"), + }, + "unbound,found-matches,already-bound": { + volumeBinderConfig: &persistentvolume.FakeVolumeBinderConfig{ + FindUnboundSatsified: true, + FindBoundSatsified: true, + AssumeBindingRequired: false, + }, + expectAssumeCalled: true, + expectBindCalled: false, + eventReason: "FailedScheduling", + expectError: fmt.Errorf("Volume binding started, waiting for completion"), + }, + "predicate-error": { + volumeBinderConfig: &persistentvolume.FakeVolumeBinderConfig{ + FindErr: findErr, + }, + eventReason: "FailedScheduling", + expectError: findErr, + }, + "assume-error": { + volumeBinderConfig: &persistentvolume.FakeVolumeBinderConfig{ + FindUnboundSatsified: true, + FindBoundSatsified: true, + AssumeErr: assumeErr, + }, + expectAssumeCalled: true, + eventReason: "FailedScheduling", + expectError: assumeErr, + }, + "bind-error": { + volumeBinderConfig: &persistentvolume.FakeVolumeBinderConfig{ + FindUnboundSatsified: true, + FindBoundSatsified: true, + AssumeBindingRequired: true, + BindErr: bindErr, + }, + expectAssumeCalled: true, + expectBindCalled: true, + eventReason: "FailedScheduling", + expectError: bindErr, + }, + } + + for name, item := range table { + stop := make(chan struct{}) + fakeVolumeBinder := volumebinder.NewFakeVolumeBinder(item.volumeBinderConfig) + internalBinder, ok := fakeVolumeBinder.Binder.(*persistentvolume.FakeVolumeBinder) + if !ok { + t.Fatalf("Failed to get fake volume binder") + } + s, bindingChan, errChan := setupTestSchedulerWithVolumeBinding(fakeVolumeBinder, stop, eventBroadcaster) + + eventChan := make(chan struct{}) + events := eventBroadcaster.StartEventWatcher(func(e *v1.Event) { + if e, a := item.eventReason, e.Reason; e != a { + t.Errorf("%v: expected %v, got %v", name, e, a) + } + close(eventChan) + }) + + go fakeVolumeBinder.Run(s.bindVolumesWorker, stop) + + s.scheduleOne() + + // Wait for pod to succeed or fail scheduling + select { + case <-eventChan: + case <-time.After(wait.ForeverTestTimeout): + t.Fatalf("%v: scheduling timeout after %v", name, wait.ForeverTestTimeout) + } + + events.Stop() + + // Wait for scheduling to return an error + select { + case err := <-errChan: + if item.expectError == nil || !reflect.DeepEqual(item.expectError.Error(), err.Error()) { + t.Errorf("%v: \n err \nWANT=%+v,\nGOT=%+v", name, item.expectError, err) + } + case <-time.After(chanTimeout): + if item.expectError != nil { + t.Errorf("%v: did not receive error after %v", name, chanTimeout) + } + } + + // Wait for pod to succeed binding + select { + case b := <-bindingChan: + if !reflect.DeepEqual(item.expectPodBind, b) { + t.Errorf("%v: \n err \nWANT=%+v,\nGOT=%+v", name, item.expectPodBind, b) + } + case <-time.After(chanTimeout): + if item.expectPodBind != nil { + t.Errorf("%v: did not receive pod binding after %v", name, chanTimeout) + } + } + + if item.expectAssumeCalled != internalBinder.AssumeCalled { + t.Errorf("%v: expectedAssumeCall %v", name, item.expectAssumeCalled) + } + + if item.expectBindCalled != internalBinder.BindCalled { + t.Errorf("%v: expectedBindCall %v", name, item.expectBindCalled) + } + + close(stop) + } +} diff --git a/pkg/scheduler/schedulercache/BUILD b/pkg/scheduler/schedulercache/BUILD new file mode 100644 index 00000000000..19d35198f16 --- /dev/null +++ b/pkg/scheduler/schedulercache/BUILD @@ -0,0 +1,56 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "cache.go", + "interface.go", + "node_info.go", + "util.go", + ], + importpath = "k8s.io/kubernetes/pkg/scheduler/schedulercache", + visibility = ["//visibility:public"], + deps = [ + "//pkg/apis/core/v1/helper:go_default_library", + "//pkg/scheduler/algorithm/priorities/util:go_default_library", + "//pkg/scheduler/util:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/policy/v1beta1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/client-go/tools/cache:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["cache_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/scheduler/schedulercache", + deps = [ + "//pkg/scheduler/algorithm/priorities/util:go_default_library", + "//pkg/scheduler/util:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/policy/v1beta1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/plugin/pkg/scheduler/schedulercache/cache.go b/pkg/scheduler/schedulercache/cache.go similarity index 88% rename from plugin/pkg/scheduler/schedulercache/cache.go rename to pkg/scheduler/schedulercache/cache.go index 9b2dfd7ea77..f891707d505 100644 --- a/plugin/pkg/scheduler/schedulercache/cache.go +++ b/pkg/scheduler/schedulercache/cache.go @@ -26,6 +26,7 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "github.com/golang/glog" + policy "k8s.io/api/policy/v1beta1" ) var ( @@ -55,6 +56,7 @@ type schedulerCache struct { // a map from pod key to podState. podStates map[string]*podState nodes map[string]*NodeInfo + pdbs map[string]*policy.PodDisruptionBudget } type podState struct { @@ -74,6 +76,7 @@ func newSchedulerCache(ttl, period time.Duration, stop <-chan struct{}) *schedul nodes: make(map[string]*NodeInfo), assumedPods: make(map[string]bool), podStates: make(map[string]*podState), + pdbs: make(map[string]*policy.PodDisruptionBudget), } } @@ -101,7 +104,14 @@ func (cache *schedulerCache) List(selector labels.Selector) ([]*v1.Pod, error) { func (cache *schedulerCache) FilteredList(podFilter PodFilter, selector labels.Selector) ([]*v1.Pod, error) { cache.mu.Lock() defer cache.mu.Unlock() - var pods []*v1.Pod + // podFilter is expected to return true for most or all of the pods. We + // can avoid expensive array growth without wasting too much memory by + // pre-allocating capacity. + maxSize := 0 + for _, info := range cache.nodes { + maxSize += len(info.pods) + } + pods := make([]*v1.Pod, 0, maxSize) for _, info := range cache.nodes { for _, pod := range info.pods { if podFilter(pod) && selector.Matches(labels.Set(pod.Labels)) { @@ -238,6 +248,7 @@ func (cache *schedulerCache) AddPod(pod *v1.Pod) error { } delete(cache.assumedPods, key) cache.podStates[key].deadline = nil + cache.podStates[key].pod = pod case !ok: // Pod was expired. We should add it back. cache.addPod(pod) @@ -382,6 +393,39 @@ func (cache *schedulerCache) RemoveNode(node *v1.Node) error { return nil } +func (cache *schedulerCache) AddPDB(pdb *policy.PodDisruptionBudget) error { + cache.mu.Lock() + defer cache.mu.Unlock() + + // Unconditionally update cache. + cache.pdbs[pdb.Name] = pdb + return nil +} + +func (cache *schedulerCache) UpdatePDB(oldPDB, newPDB *policy.PodDisruptionBudget) error { + return cache.AddPDB(newPDB) +} + +func (cache *schedulerCache) RemovePDB(pdb *policy.PodDisruptionBudget) error { + cache.mu.Lock() + defer cache.mu.Unlock() + + delete(cache.pdbs, pdb.Name) + return nil +} + +func (cache *schedulerCache) ListPDBs(selector labels.Selector) ([]*policy.PodDisruptionBudget, error) { + cache.mu.Lock() + defer cache.mu.Unlock() + var pdbs []*policy.PodDisruptionBudget + for _, pdb := range cache.pdbs { + if selector.Matches(labels.Set(pdb.Labels)) { + pdbs = append(pdbs, pdb) + } + } + return pdbs, nil +} + func (cache *schedulerCache) run() { go wait.Until(cache.cleanupExpiredAssumedPods, cache.period, cache.stop) } @@ -402,7 +446,7 @@ func (cache *schedulerCache) cleanupAssumedPods(now time.Time) { panic("Key found in assumed set but not in podStates. Potentially a logical error.") } if !ps.bindingFinished { - glog.Warningf("Couldn't expire cache for pod %v/%v. Binding is still in progress.", + glog.V(3).Infof("Couldn't expire cache for pod %v/%v. Binding is still in progress.", ps.pod.Namespace, ps.pod.Name) continue } diff --git a/pkg/scheduler/schedulercache/cache_test.go b/pkg/scheduler/schedulercache/cache_test.go new file mode 100644 index 00000000000..b5e1243a474 --- /dev/null +++ b/pkg/scheduler/schedulercache/cache_test.go @@ -0,0 +1,1063 @@ +/* +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 schedulercache + +import ( + "fmt" + "reflect" + "strings" + "testing" + "time" + + "k8s.io/api/core/v1" + "k8s.io/api/policy/v1beta1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/intstr" + priorityutil "k8s.io/kubernetes/pkg/scheduler/algorithm/priorities/util" + schedutil "k8s.io/kubernetes/pkg/scheduler/util" +) + +func deepEqualWithoutGeneration(t *testing.T, testcase int, actual, expected *NodeInfo) { + // Ignore generation field. + if actual != nil { + actual.generation = 0 + } + if !reflect.DeepEqual(actual, expected) { + t.Errorf("#%d: node info get=%s, want=%s", testcase, actual, expected) + } +} + +// TestAssumePodScheduled tests that after a pod is assumed, its information is aggregated +// on node level. +func TestAssumePodScheduled(t *testing.T) { + nodeName := "node" + testPods := []*v1.Pod{ + makeBasePod(t, nodeName, "test", "100m", "500", "", []v1.ContainerPort{{HostIP: "127.0.0.1", HostPort: 80, Protocol: "TCP"}}), + 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-nonzero", "", "", "", []v1.ContainerPort{{HostIP: "127.0.0.1", HostPort: 80, Protocol: "TCP"}}), + makeBasePod(t, nodeName, "test", "100m", "500", "example.com/foo:3", []v1.ContainerPort{{HostIP: "127.0.0.1", HostPort: 80, Protocol: "TCP"}}), + makeBasePod(t, nodeName, "test-2", "200m", "1Ki", "example.com/foo:5", []v1.ContainerPort{{HostIP: "127.0.0.1", HostPort: 8080, Protocol: "TCP"}}), + makeBasePod(t, nodeName, "test", "100m", "500", "random-invalid-extended-key:100", []v1.ContainerPort{{}}), + } + + tests := []struct { + pods []*v1.Pod + + wNodeInfo *NodeInfo + }{{ + pods: []*v1.Pod{testPods[0]}, + wNodeInfo: &NodeInfo{ + requestedResource: &Resource{ + MilliCPU: 100, + Memory: 500, + }, + nonzeroRequest: &Resource{ + MilliCPU: 100, + Memory: 500, + }, + allocatableResource: &Resource{}, + pods: []*v1.Pod{testPods[0]}, + usedPorts: map[string]bool{"TCP/127.0.0.1/80": true}, + }, + }, { + pods: []*v1.Pod{testPods[1], testPods[2]}, + wNodeInfo: &NodeInfo{ + requestedResource: &Resource{ + MilliCPU: 300, + Memory: 1524, + }, + nonzeroRequest: &Resource{ + MilliCPU: 300, + Memory: 1524, + }, + allocatableResource: &Resource{}, + pods: []*v1.Pod{testPods[1], testPods[2]}, + usedPorts: map[string]bool{"TCP/127.0.0.1/80": true, "TCP/127.0.0.1/8080": true}, + }, + }, { // test non-zero request + pods: []*v1.Pod{testPods[3]}, + wNodeInfo: &NodeInfo{ + requestedResource: &Resource{ + MilliCPU: 0, + Memory: 0, + }, + nonzeroRequest: &Resource{ + MilliCPU: priorityutil.DefaultMilliCpuRequest, + Memory: priorityutil.DefaultMemoryRequest, + }, + allocatableResource: &Resource{}, + pods: []*v1.Pod{testPods[3]}, + usedPorts: map[string]bool{"TCP/127.0.0.1/80": true}, + }, + }, { + pods: []*v1.Pod{testPods[4]}, + wNodeInfo: &NodeInfo{ + requestedResource: &Resource{ + MilliCPU: 100, + Memory: 500, + ScalarResources: map[v1.ResourceName]int64{"example.com/foo": 3}, + }, + nonzeroRequest: &Resource{ + MilliCPU: 100, + Memory: 500, + }, + allocatableResource: &Resource{}, + pods: []*v1.Pod{testPods[4]}, + usedPorts: map[string]bool{"TCP/127.0.0.1/80": true}, + }, + }, { + pods: []*v1.Pod{testPods[4], testPods[5]}, + wNodeInfo: &NodeInfo{ + requestedResource: &Resource{ + MilliCPU: 300, + Memory: 1524, + ScalarResources: map[v1.ResourceName]int64{"example.com/foo": 8}, + }, + nonzeroRequest: &Resource{ + MilliCPU: 300, + Memory: 1524, + }, + allocatableResource: &Resource{}, + pods: []*v1.Pod{testPods[4], testPods[5]}, + usedPorts: map[string]bool{"TCP/127.0.0.1/80": true, "TCP/127.0.0.1/8080": true}, + }, + }, { + pods: []*v1.Pod{testPods[6]}, + wNodeInfo: &NodeInfo{ + requestedResource: &Resource{ + MilliCPU: 100, + Memory: 500, + }, + nonzeroRequest: &Resource{ + MilliCPU: 100, + Memory: 500, + }, + allocatableResource: &Resource{}, + pods: []*v1.Pod{testPods[6]}, + usedPorts: map[string]bool{}, + }, + }, + } + + for i, tt := range tests { + cache := newSchedulerCache(time.Second, time.Second, nil) + for _, pod := range tt.pods { + if err := cache.AssumePod(pod); err != nil { + t.Fatalf("AssumePod failed: %v", err) + } + } + n := cache.nodes[nodeName] + deepEqualWithoutGeneration(t, i, n, tt.wNodeInfo) + + for _, pod := range tt.pods { + if err := cache.ForgetPod(pod); err != nil { + t.Fatalf("ForgetPod failed: %v", err) + } + } + if cache.nodes[nodeName] != nil { + t.Errorf("NodeInfo should be cleaned for %s", nodeName) + } + } +} + +type testExpirePodStruct struct { + pod *v1.Pod + assumedTime time.Time +} + +func assumeAndFinishBinding(cache *schedulerCache, pod *v1.Pod, assumedTime time.Time) error { + if err := cache.AssumePod(pod); err != nil { + return err + } + return cache.finishBinding(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"}}), + } + now := time.Now() + ttl := 10 * time.Second + tests := []struct { + pods []*testExpirePodStruct + cleanupTime time.Time + + wNodeInfo *NodeInfo + }{{ // assumed pod would expires + pods: []*testExpirePodStruct{ + {pod: testPods[0], assumedTime: now}, + }, + cleanupTime: now.Add(2 * ttl), + wNodeInfo: nil, + }, { // first one would expire, second one would not. + pods: []*testExpirePodStruct{ + {pod: testPods[0], assumedTime: now}, + {pod: testPods[1], assumedTime: now.Add(3 * ttl / 2)}, + }, + cleanupTime: now.Add(2 * ttl), + wNodeInfo: &NodeInfo{ + requestedResource: &Resource{ + MilliCPU: 200, + Memory: 1024, + }, + nonzeroRequest: &Resource{ + MilliCPU: 200, + Memory: 1024, + }, + allocatableResource: &Resource{}, + pods: []*v1.Pod{testPods[1]}, + usedPorts: map[string]bool{"TCP/127.0.0.1/8080": true}, + }, + }} + + for i, tt := range tests { + cache := newSchedulerCache(ttl, time.Second, nil) + + for _, pod := range tt.pods { + if err := assumeAndFinishBinding(cache, pod.pod, pod.assumedTime); err != nil { + t.Fatalf("assumePod failed: %v", err) + } + } + // pods that have assumedTime + ttl < cleanupTime will get expired and removed + cache.cleanupAssumedPods(tt.cleanupTime) + n := cache.nodes[nodeName] + deepEqualWithoutGeneration(t, i, n, tt.wNodeInfo) + } +} + +// 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"}}), + makeBasePod(t, nodeName, "test-2", "200m", "1Ki", "", []v1.ContainerPort{{HostIP: "127.0.0.1", HostPort: 8080, Protocol: "TCP"}}), + } + tests := []struct { + podsToAssume []*v1.Pod + podsToAdd []*v1.Pod + + wNodeInfo *NodeInfo + }{{ // two pod were assumed at same time. But first one is called Add() and gets confirmed. + podsToAssume: []*v1.Pod{testPods[0], testPods[1]}, + podsToAdd: []*v1.Pod{testPods[0]}, + wNodeInfo: &NodeInfo{ + requestedResource: &Resource{ + MilliCPU: 100, + Memory: 500, + }, + nonzeroRequest: &Resource{ + MilliCPU: 100, + Memory: 500, + }, + allocatableResource: &Resource{}, + pods: []*v1.Pod{testPods[0]}, + usedPorts: map[string]bool{"TCP/127.0.0.1/80": true}, + }, + }} + + for i, tt := range tests { + cache := newSchedulerCache(ttl, time.Second, nil) + for _, podToAssume := range tt.podsToAssume { + if err := assumeAndFinishBinding(cache, podToAssume, now); err != nil { + t.Fatalf("assumePod failed: %v", err) + } + } + for _, podToAdd := range tt.podsToAdd { + if err := cache.AddPod(podToAdd); err != nil { + t.Fatalf("AddPod failed: %v", err) + } + } + cache.cleanupAssumedPods(now.Add(2 * ttl)) + // check after expiration. confirmed pods shouldn't be expired. + n := cache.nodes[nodeName] + deepEqualWithoutGeneration(t, i, n, tt.wNodeInfo) + } +} + +// 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}}) + + tests := []struct { + podsToAssume []*v1.Pod + podsToAdd []*v1.Pod + podsToUpdate [][]*v1.Pod + + wNodeInfo map[string]*NodeInfo + }{{ + podsToAssume: []*v1.Pod{assumedPod.DeepCopy()}, + podsToAdd: []*v1.Pod{addedPod.DeepCopy()}, + podsToUpdate: [][]*v1.Pod{{addedPod.DeepCopy(), updatedPod.DeepCopy()}}, + wNodeInfo: map[string]*NodeInfo{ + "assumed-node": nil, + "actual-node": { + requestedResource: &Resource{ + MilliCPU: 200, + Memory: 500, + }, + nonzeroRequest: &Resource{ + MilliCPU: 200, + Memory: 500, + }, + allocatableResource: &Resource{}, + pods: []*v1.Pod{updatedPod.DeepCopy()}, + usedPorts: map[string]bool{"TCP/0.0.0.0/90": true}, + }, + }, + }} + + for i, tt := range tests { + cache := newSchedulerCache(ttl, time.Second, nil) + for _, podToAssume := range tt.podsToAssume { + if err := assumeAndFinishBinding(cache, podToAssume, now); err != nil { + t.Fatalf("assumePod failed: %v", err) + } + } + for _, podToAdd := range tt.podsToAdd { + if err := cache.AddPod(podToAdd); err != nil { + t.Fatalf("AddPod failed: %v", err) + } + } + for _, podToUpdate := range tt.podsToUpdate { + if err := cache.UpdatePod(podToUpdate[0], podToUpdate[1]); err != nil { + t.Fatalf("UpdatePod failed: %v", err) + } + } + for nodeName, expected := range tt.wNodeInfo { + t.Log(nodeName) + n := cache.nodes[nodeName] + deepEqualWithoutGeneration(t, i, n, expected) + } + } +} + +// 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"}}) + tests := []struct { + pod *v1.Pod + + wNodeInfo *NodeInfo + }{{ + pod: basePod, + wNodeInfo: &NodeInfo{ + requestedResource: &Resource{ + MilliCPU: 100, + Memory: 500, + }, + nonzeroRequest: &Resource{ + MilliCPU: 100, + Memory: 500, + }, + allocatableResource: &Resource{}, + pods: []*v1.Pod{basePod}, + usedPorts: map[string]bool{"TCP/127.0.0.1/80": true}, + }, + }} + + now := time.Now() + for i, tt := range tests { + cache := newSchedulerCache(ttl, time.Second, nil) + if err := assumeAndFinishBinding(cache, tt.pod, now); err != nil { + t.Fatalf("assumePod failed: %v", err) + } + cache.cleanupAssumedPods(now.Add(2 * ttl)) + // It should be expired and removed. + n := cache.nodes[nodeName] + if n != nil { + t.Errorf("#%d: expecting nil node info, but get=%v", i, n) + } + if err := cache.AddPod(tt.pod); err != nil { + t.Fatalf("AddPod failed: %v", err) + } + // check after expiration. confirmed pods shouldn't be expired. + n = cache.nodes[nodeName] + deepEqualWithoutGeneration(t, i, n, tt.wNodeInfo) + } +} + +// 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"}}), + } + tests := []struct { + podsToAssume []*v1.Pod + podsToAdd []*v1.Pod + podsToUpdate []*v1.Pod + + wNodeInfo []*NodeInfo + }{{ // add a pod and then update it twice + podsToAdd: []*v1.Pod{testPods[0]}, + podsToUpdate: []*v1.Pod{testPods[0], testPods[1], testPods[0]}, + wNodeInfo: []*NodeInfo{{ + requestedResource: &Resource{ + MilliCPU: 200, + Memory: 1024, + }, + nonzeroRequest: &Resource{ + MilliCPU: 200, + Memory: 1024, + }, + allocatableResource: &Resource{}, + pods: []*v1.Pod{testPods[1]}, + usedPorts: map[string]bool{"TCP/127.0.0.1/8080": true}, + }, { + requestedResource: &Resource{ + MilliCPU: 100, + Memory: 500, + }, + nonzeroRequest: &Resource{ + MilliCPU: 100, + Memory: 500, + }, + allocatableResource: &Resource{}, + pods: []*v1.Pod{testPods[0]}, + usedPorts: map[string]bool{"TCP/127.0.0.1/80": true}, + }}, + }} + + for _, tt := range tests { + cache := newSchedulerCache(ttl, time.Second, nil) + for _, podToAdd := range tt.podsToAdd { + if err := cache.AddPod(podToAdd); err != nil { + t.Fatalf("AddPod failed: %v", err) + } + } + + for i := range tt.podsToUpdate { + if i == 0 { + continue + } + if err := cache.UpdatePod(tt.podsToUpdate[i-1], tt.podsToUpdate[i]); err != nil { + t.Fatalf("UpdatePod failed: %v", err) + } + // check after expiration. confirmed pods shouldn't be expired. + n := cache.nodes[nodeName] + deepEqualWithoutGeneration(t, i, n, tt.wNodeInfo[i-1]) + } + } +} + +// TestExpireAddUpdatePod test the sequence that a pod is expired, added, then updated +func TestExpireAddUpdatePod(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"}}), + } + tests := []struct { + podsToAssume []*v1.Pod + podsToAdd []*v1.Pod + podsToUpdate []*v1.Pod + + wNodeInfo []*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: []*NodeInfo{{ + requestedResource: &Resource{ + MilliCPU: 200, + Memory: 1024, + }, + nonzeroRequest: &Resource{ + MilliCPU: 200, + Memory: 1024, + }, + allocatableResource: &Resource{}, + pods: []*v1.Pod{testPods[1]}, + usedPorts: map[string]bool{"TCP/127.0.0.1/8080": true}, + }, { + requestedResource: &Resource{ + MilliCPU: 100, + Memory: 500, + }, + nonzeroRequest: &Resource{ + MilliCPU: 100, + Memory: 500, + }, + allocatableResource: &Resource{}, + pods: []*v1.Pod{testPods[0]}, + usedPorts: map[string]bool{"TCP/127.0.0.1/80": true}, + }}, + }} + + now := time.Now() + for _, tt := range tests { + cache := newSchedulerCache(ttl, time.Second, nil) + for _, podToAssume := range tt.podsToAssume { + if err := assumeAndFinishBinding(cache, podToAssume, now); err != nil { + t.Fatalf("assumePod failed: %v", err) + } + } + cache.cleanupAssumedPods(now.Add(2 * ttl)) + + for _, podToAdd := range tt.podsToAdd { + if err := cache.AddPod(podToAdd); err != nil { + t.Fatalf("AddPod failed: %v", err) + } + } + + for i := range tt.podsToUpdate { + if i == 0 { + continue + } + if err := cache.UpdatePod(tt.podsToUpdate[i-1], tt.podsToUpdate[i]); err != nil { + t.Fatalf("UpdatePod failed: %v", err) + } + // check after expiration. confirmed pods shouldn't be expired. + n := cache.nodes[nodeName] + deepEqualWithoutGeneration(t, i, n, tt.wNodeInfo[i-1]) + } + } +} + +// TestRemovePod tests after added pod is removed, its information should also be subtracted. +func TestRemovePod(t *testing.T) { + nodeName := "node" + basePod := makeBasePod(t, nodeName, "test", "100m", "500", "", []v1.ContainerPort{{HostIP: "127.0.0.1", HostPort: 80, Protocol: "TCP"}}) + tests := []struct { + pod *v1.Pod + wNodeInfo *NodeInfo + }{{ + pod: basePod, + wNodeInfo: &NodeInfo{ + requestedResource: &Resource{ + MilliCPU: 100, + Memory: 500, + }, + nonzeroRequest: &Resource{ + MilliCPU: 100, + Memory: 500, + }, + allocatableResource: &Resource{}, + pods: []*v1.Pod{basePod}, + usedPorts: map[string]bool{"TCP/127.0.0.1/80": true}, + }, + }} + + for i, tt := range tests { + cache := newSchedulerCache(time.Second, time.Second, nil) + if err := cache.AddPod(tt.pod); err != nil { + t.Fatalf("AddPod failed: %v", err) + } + n := cache.nodes[nodeName] + deepEqualWithoutGeneration(t, i, n, tt.wNodeInfo) + + if err := cache.RemovePod(tt.pod); err != nil { + t.Fatalf("RemovePod failed: %v", err) + } + + n = cache.nodes[nodeName] + if n != nil { + t.Errorf("#%d: expecting pod deleted and nil node info, get=%s", i, n) + } + } +} + +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"}}) + tests := []struct { + pods []*v1.Pod + }{{ + pods: []*v1.Pod{basePod}, + }} + now := time.Now() + ttl := 10 * time.Second + + for i, tt := range tests { + cache := newSchedulerCache(ttl, time.Second, nil) + for _, pod := range tt.pods { + if err := assumeAndFinishBinding(cache, pod, now); err != nil { + t.Fatalf("assumePod failed: %v", err) + } + isAssumed, err := cache.IsAssumedPod(pod) + if err != nil { + t.Fatalf("IsAssumedPod failed: %v.", err) + } + if !isAssumed { + t.Fatalf("Pod is expected to be assumed.") + } + assumedPod, err := cache.GetPod(pod) + if err != nil { + t.Fatalf("GetPod failed: %v.", err) + } + if assumedPod.Namespace != pod.Namespace { + t.Errorf("assumedPod.Namespace != pod.Namespace (%s != %s)", assumedPod.Namespace, pod.Namespace) + } + if assumedPod.Name != pod.Name { + t.Errorf("assumedPod.Name != pod.Name (%s != %s)", assumedPod.Name, pod.Name) + } + } + for _, pod := range tt.pods { + if err := cache.ForgetPod(pod); err != nil { + t.Fatalf("ForgetPod failed: %v", err) + } + isAssumed, err := cache.IsAssumedPod(pod) + if err != nil { + t.Fatalf("IsAssumedPod failed: %v.", err) + } + if isAssumed { + t.Fatalf("Pod is expected to be unassumed.") + } + } + cache.cleanupAssumedPods(now.Add(2 * ttl)) + if n := cache.nodes[nodeName]; n != nil { + t.Errorf("#%d: expecting pod deleted and nil node info, get=%s", i, n) + } + } +} + +// getResourceRequest returns the resource request of all containers in Pods; +// excuding initContainers. +func getResourceRequest(pod *v1.Pod) v1.ResourceList { + result := &Resource{} + for _, container := range pod.Spec.Containers { + result.Add(container.Resources.Requests) + } + + return result.ResourceList() +} + +// buildNodeInfo creates a NodeInfo by simulating node operations in cache. +func buildNodeInfo(node *v1.Node, pods []*v1.Pod) *NodeInfo { + expected := NewNodeInfo() + + // Simulate SetNode. + expected.node = node + expected.allocatableResource = NewResource(node.Status.Allocatable) + expected.taints = node.Spec.Taints + expected.generation++ + + for _, pod := range pods { + // Simulate AddPod + expected.pods = append(expected.pods, pod) + expected.requestedResource.Add(getResourceRequest(pod)) + expected.nonzeroRequest.Add(getResourceRequest(pod)) + expected.usedPorts = schedutil.GetUsedPorts(pod) + expected.generation++ + } + + return expected +} + +// TestNodeOperators tests node operations of cache, including add, update +// and remove. +func TestNodeOperators(t *testing.T) { + // Test datas + nodeName := "test-node" + cpu_1 := resource.MustParse("1000m") + mem_100m := resource.MustParse("100m") + cpu_half := resource.MustParse("500m") + mem_50m := resource.MustParse("50m") + resourceFooName := "example.com/foo" + resourceFoo := resource.MustParse("1") + + tests := []struct { + node *v1.Node + pods []*v1.Pod + }{ + { + node: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: nodeName, + }, + Status: v1.NodeStatus{ + Allocatable: v1.ResourceList{ + v1.ResourceCPU: cpu_1, + v1.ResourceMemory: mem_100m, + v1.ResourceName(resourceFooName): resourceFoo, + }, + }, + Spec: v1.NodeSpec{ + Taints: []v1.Taint{ + { + Key: "test-key", + Value: "test-value", + Effect: v1.TaintEffectPreferNoSchedule, + }, + }, + }, + }, + pods: []*v1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + }, + Spec: v1.PodSpec{ + NodeName: nodeName, + Containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: cpu_half, + v1.ResourceMemory: mem_50m, + }, + }, + Ports: []v1.ContainerPort{ + { + Name: "http", + HostPort: 80, + ContainerPort: 80, + }, + }, + }, + }, + }, + }, + }, + }, + { + node: &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: nodeName, + }, + Status: v1.NodeStatus{ + Allocatable: v1.ResourceList{ + v1.ResourceCPU: cpu_1, + v1.ResourceMemory: mem_100m, + v1.ResourceName(resourceFooName): resourceFoo, + }, + }, + Spec: v1.NodeSpec{ + Taints: []v1.Taint{ + { + Key: "test-key", + Value: "test-value", + Effect: v1.TaintEffectPreferNoSchedule, + }, + }, + }, + }, + pods: []*v1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + }, + Spec: v1.PodSpec{ + NodeName: nodeName, + Containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: cpu_half, + v1.ResourceMemory: mem_50m, + }, + }, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "pod2", + }, + Spec: v1.PodSpec{ + NodeName: nodeName, + Containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: cpu_half, + v1.ResourceMemory: mem_50m, + }, + }, + }, + }, + }, + }, + }, + }, + } + + for _, test := range tests { + expected := buildNodeInfo(test.node, test.pods) + node := test.node + + cache := newSchedulerCache(time.Second, time.Second, nil) + cache.AddNode(node) + for _, pod := range test.pods { + cache.AddPod(pod) + } + + // Case 1: the node was added into cache successfully. + got, found := cache.nodes[node.Name] + if !found { + t.Errorf("Failed to find node %v in schedulercache.", node.Name) + } + + if !reflect.DeepEqual(got, expected) { + t.Errorf("Failed to add node into schedulercache:\n got: %+v \nexpected: %+v", got, expected) + } + + // Case 2: dump cached nodes successfully. + cachedNodes := map[string]*NodeInfo{} + cache.UpdateNodeNameToInfoMap(cachedNodes) + newNode, found := cachedNodes[node.Name] + if !found || len(cachedNodes) != 1 { + t.Errorf("failed to dump cached nodes:\n got: %v \nexpected: %v", cachedNodes, cache.nodes) + } + if !reflect.DeepEqual(newNode, expected) { + t.Errorf("Failed to clone node:\n got: %+v, \n expected: %+v", newNode, expected) + } + + // Case 3: update node attribute successfully. + node.Status.Allocatable[v1.ResourceMemory] = mem_50m + expected.allocatableResource.Memory = mem_50m.Value() + expected.generation++ + cache.UpdateNode(nil, node) + got, found = cache.nodes[node.Name] + if !found { + t.Errorf("Failed to find node %v in schedulercache after UpdateNode.", node.Name) + } + + if !reflect.DeepEqual(got, expected) { + t.Errorf("Failed to update node in schedulercache:\n got: %+v \nexpected: %+v", got, expected) + } + + // Case 4: the node can not be removed if pods is not empty. + cache.RemoveNode(node) + if _, found := cache.nodes[node.Name]; !found { + t.Errorf("The node %v should not be removed if pods is not empty.", node.Name) + } + } +} + +func BenchmarkList1kNodes30kPods(b *testing.B) { + cache := setupCacheOf1kNodes30kPods(b) + b.ResetTimer() + for n := 0; n < b.N; n++ { + cache.List(labels.Everything()) + } +} + +func BenchmarkExpire100Pods(b *testing.B) { + benchmarkExpire(b, 100) +} + +func BenchmarkExpire1kPods(b *testing.B) { + benchmarkExpire(b, 1000) +} + +func BenchmarkExpire10kPods(b *testing.B) { + benchmarkExpire(b, 10000) +} + +func benchmarkExpire(b *testing.B, podNum int) { + now := time.Now() + for n := 0; n < b.N; n++ { + b.StopTimer() + cache := setupCacheWithAssumedPods(b, podNum, now) + b.StartTimer() + cache.cleanupAssumedPods(now.Add(2 * time.Second)) + } +} + +type testingMode interface { + Fatalf(format string, args ...interface{}) +} + +func makeBasePod(t testingMode, nodeName, objName, cpu, mem, extended string, ports []v1.ContainerPort) *v1.Pod { + req := v1.ResourceList{} + if cpu != "" { + req = v1.ResourceList{ + v1.ResourceCPU: resource.MustParse(cpu), + v1.ResourceMemory: resource.MustParse(mem), + } + if extended != "" { + parts := strings.Split(extended, ":") + if len(parts) != 2 { + t.Fatalf("Invalid extended resource string: \"%s\"", extended) + } + req[v1.ResourceName(parts[0])] = resource.MustParse(parts[1]) + } + } + return &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "node_info_cache_test", + Name: objName, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{{ + Resources: v1.ResourceRequirements{ + Requests: req, + }, + Ports: ports, + }}, + NodeName: nodeName, + }, + } +} + +func setupCacheOf1kNodes30kPods(b *testing.B) Cache { + cache := newSchedulerCache(time.Second, time.Second, nil) + for i := 0; i < 1000; i++ { + nodeName := fmt.Sprintf("node-%d", i) + for j := 0; j < 30; j++ { + objName := fmt.Sprintf("%s-pod-%d", nodeName, j) + pod := makeBasePod(b, nodeName, objName, "0", "0", "", nil) + + if err := cache.AddPod(pod); err != nil { + b.Fatalf("AddPod failed: %v", err) + } + } + } + return cache +} + +func setupCacheWithAssumedPods(b *testing.B, podNum int, assumedTime time.Time) *schedulerCache { + cache := newSchedulerCache(time.Second, time.Second, nil) + for i := 0; i < podNum; i++ { + nodeName := fmt.Sprintf("node-%d", i/10) + objName := fmt.Sprintf("%s-pod-%d", nodeName, i%10) + pod := makeBasePod(b, nodeName, objName, "0", "0", "", nil) + + err := assumeAndFinishBinding(cache, pod, assumedTime) + if err != nil { + b.Fatalf("assumePod failed: %v", err) + } + } + return cache +} + +func makePDB(name, namespace string, labels map[string]string, minAvailable int) *v1beta1.PodDisruptionBudget { + intstrMin := intstr.FromInt(minAvailable) + pdb := &v1beta1.PodDisruptionBudget{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + Labels: labels, + }, + Spec: v1beta1.PodDisruptionBudgetSpec{ + MinAvailable: &intstrMin, + Selector: &metav1.LabelSelector{MatchLabels: labels}, + }, + } + + return pdb +} + +// TestPDBOperations tests that a PDB will be add/updated/deleted correctly. +func TestPDBOperations(t *testing.T) { + ttl := 10 * time.Second + testPDBs := []*v1beta1.PodDisruptionBudget{ + makePDB("pdb0", "ns1", map[string]string{"tkey1": "tval1"}, 3), + makePDB("pdb1", "ns1", map[string]string{"tkey1": "tval1", "tkey2": "tval2"}, 1), + makePDB("pdb2", "ns3", map[string]string{"tkey3": "tval3", "tkey2": "tval2"}, 10), + } + updatedPDBs := []*v1beta1.PodDisruptionBudget{ + makePDB("pdb0", "ns1", map[string]string{"tkey4": "tval4"}, 8), + makePDB("pdb1", "ns1", map[string]string{"tkey1": "tval1"}, 1), + makePDB("pdb2", "ns3", map[string]string{"tkey3": "tval3", "tkey1": "tval1", "tkey2": "tval2"}, 10), + } + tests := []struct { + pdbsToAdd []*v1beta1.PodDisruptionBudget + pdbsToUpdate []*v1beta1.PodDisruptionBudget + pdbsToDelete []*v1beta1.PodDisruptionBudget + expectedPDBs []*v1beta1.PodDisruptionBudget // Expected PDBs after all operations + }{ + { + pdbsToAdd: []*v1beta1.PodDisruptionBudget{testPDBs[0]}, + pdbsToUpdate: []*v1beta1.PodDisruptionBudget{testPDBs[0], testPDBs[1], testPDBs[0]}, + expectedPDBs: []*v1beta1.PodDisruptionBudget{testPDBs[0], testPDBs[1]}, // both will be in the cache as they have different names + }, + { + pdbsToAdd: []*v1beta1.PodDisruptionBudget{testPDBs[0]}, + pdbsToUpdate: []*v1beta1.PodDisruptionBudget{testPDBs[0], updatedPDBs[0]}, + expectedPDBs: []*v1beta1.PodDisruptionBudget{updatedPDBs[0]}, + }, + { + pdbsToAdd: []*v1beta1.PodDisruptionBudget{testPDBs[0], testPDBs[2]}, + pdbsToUpdate: []*v1beta1.PodDisruptionBudget{testPDBs[0], updatedPDBs[0]}, + pdbsToDelete: []*v1beta1.PodDisruptionBudget{testPDBs[0]}, + expectedPDBs: []*v1beta1.PodDisruptionBudget{testPDBs[2]}, + }, + } + + for _, test := range tests { + cache := newSchedulerCache(ttl, time.Second, nil) + for _, pdbToAdd := range test.pdbsToAdd { + if err := cache.AddPDB(pdbToAdd); err != nil { + t.Fatalf("AddPDB failed: %v", err) + } + } + + for i := range test.pdbsToUpdate { + if i == 0 { + continue + } + if err := cache.UpdatePDB(test.pdbsToUpdate[i-1], test.pdbsToUpdate[i]); err != nil { + t.Fatalf("UpdatePDB failed: %v", err) + } + } + + for _, pdb := range test.pdbsToDelete { + if err := cache.RemovePDB(pdb); err != nil { + t.Fatalf("RemovePDB failed: %v", err) + } + } + + cachedPDBs, err := cache.ListPDBs(labels.Everything()) + if err != nil { + t.Fatalf("ListPDBs failed: %v", err) + } + if len(cachedPDBs) != len(test.expectedPDBs) { + t.Errorf("Expected %d PDBs, got %d", len(test.expectedPDBs), len(cachedPDBs)) + } + for _, pdb := range test.expectedPDBs { + found := false + // find it among the cached ones + for _, cpdb := range cachedPDBs { + if pdb.Name == cpdb.Name { + found = true + if !reflect.DeepEqual(pdb, cpdb) { + t.Errorf("%v is not equal to %v", pdb, cpdb) + } + break + } + } + if !found { + t.Errorf("PDB with name '%v' was not found in the cache.", pdb.Name) + } + + } + } +} diff --git a/pkg/scheduler/schedulercache/interface.go b/pkg/scheduler/schedulercache/interface.go new file mode 100644 index 00000000000..d8c09fb7e06 --- /dev/null +++ b/pkg/scheduler/schedulercache/interface.go @@ -0,0 +1,121 @@ +/* +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 schedulercache + +import ( + "k8s.io/api/core/v1" + policy "k8s.io/api/policy/v1beta1" + "k8s.io/apimachinery/pkg/labels" +) + +type PodFilter func(*v1.Pod) bool + +// Cache collects pods' information and provides node-level aggregated information. +// It's intended for generic scheduler to do efficient lookup. +// Cache's operations are pod centric. It does incremental updates based on pod events. +// Pod events are sent via network. We don't have guaranteed delivery of all events: +// We use Reflector to list and watch from remote. +// Reflector might be slow and do a relist, which would lead to missing events. +// +// State Machine of a pod's events in scheduler's cache: +// +// +// +-------------------------------------------+ +----+ +// | Add | | | +// | | | | Update +// + Assume Add v v | +//Initial +--------> Assumed +------------+---> Added <--+ +// ^ + + | + +// | | | | | +// | | | Add | | Remove +// | | | | | +// | | | + | +// +----------------+ +-----------> Expired +----> Deleted +// Forget Expire +// +// +// 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. +// 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 { + // 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(pod *v1.Pod) error + + // FinishBinding signals that cache for assumed pod can be expired + FinishBinding(pod *v1.Pod) error + + // ForgetPod removes an assumed pod from cache. + ForgetPod(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(pod *v1.Pod) error + + // UpdatePod removes oldPod's information and adds newPod's information. + UpdatePod(oldPod, newPod *v1.Pod) error + + // RemovePod removes a pod. The pod's information would be subtracted from assigned node. + RemovePod(pod *v1.Pod) error + + // GetPod returns the pod from the cache with the same namespace and the + // 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(pod *v1.Pod) (bool, error) + + // AddNode adds overall information about node. + AddNode(node *v1.Node) error + + // UpdateNode updates overall information about node. + UpdateNode(oldNode, newNode *v1.Node) error + + // RemoveNode removes overall information about node. + RemoveNode(node *v1.Node) error + + // AddPDB adds a PodDisruptionBudget object to the cache. + AddPDB(pdb *policy.PodDisruptionBudget) error + + // UpdatePDB updates a PodDisruptionBudget object in the cache. + UpdatePDB(oldPDB, newPDB *policy.PodDisruptionBudget) error + + // RemovePDB removes a PodDisruptionBudget object from the cache. + RemovePDB(pdb *policy.PodDisruptionBudget) error + + // List lists all cached PDBs matching the selector. + ListPDBs(selector labels.Selector) ([]*policy.PodDisruptionBudget, error) + + // UpdateNodeNameToInfoMap updates the passed infoMap to the current contents of Cache. + // The node info contains aggregated information of pods scheduled (including assumed to be) + // on this node. + UpdateNodeNameToInfoMap(infoMap map[string]*NodeInfo) error + + // List lists all cached pods (including assumed ones). + List(labels.Selector) ([]*v1.Pod, error) + + // FilteredList returns all cached pods that pass the filter. + FilteredList(filter PodFilter, selector labels.Selector) ([]*v1.Pod, error) +} diff --git a/plugin/pkg/scheduler/schedulercache/node_info.go b/pkg/scheduler/schedulercache/node_info.go similarity index 92% rename from plugin/pkg/scheduler/schedulercache/node_info.go rename to pkg/scheduler/schedulercache/node_info.go index 7cbc4568069..c59a2ebd686 100644 --- a/plugin/pkg/scheduler/schedulercache/node_info.go +++ b/pkg/scheduler/schedulercache/node_info.go @@ -24,9 +24,9 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" clientcache "k8s.io/client-go/tools/cache" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" - priorityutil "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/priorities/util" - "k8s.io/kubernetes/plugin/pkg/scheduler/util" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" + priorityutil "k8s.io/kubernetes/pkg/scheduler/algorithm/priorities/util" + "k8s.io/kubernetes/pkg/scheduler/util" ) var emptyResource = Resource{} @@ -38,7 +38,7 @@ type NodeInfo struct { pods []*v1.Pod podsWithAffinity []*v1.Pod - usedPorts map[int]bool + usedPorts map[string]bool // Total requested resource of all pods on this node. // It includes assumed pods which scheduler sends binding to apiserver but @@ -164,7 +164,7 @@ func NewNodeInfo(pods ...*v1.Pod) *NodeInfo { nonzeroRequest: &Resource{}, allocatableResource: &Resource{}, generation: 0, - usedPorts: make(map[int]bool), + usedPorts: make(map[string]bool), } for _, pod := range pods { ni.AddPod(pod) @@ -188,7 +188,7 @@ func (n *NodeInfo) Pods() []*v1.Pod { return n.pods } -func (n *NodeInfo) UsedPorts() map[int]bool { +func (n *NodeInfo) UsedPorts() map[string]bool { if n == nil { return nil } @@ -255,6 +255,11 @@ func (n *NodeInfo) AllocatableResource() Resource { return *n.allocatableResource } +// SetAllocatableResource sets the allocatableResource information of given node. +func (n *NodeInfo) SetAllocatableResource(allocatableResource *Resource) { + n.allocatableResource = allocatableResource +} + func (n *NodeInfo) Clone() *NodeInfo { clone := &NodeInfo{ node: n.node, @@ -264,7 +269,7 @@ func (n *NodeInfo) Clone() *NodeInfo { taintsErr: n.taintsErr, memoryPressureCondition: n.memoryPressureCondition, diskPressureCondition: n.diskPressureCondition, - usedPorts: make(map[int]bool), + usedPorts: make(map[string]bool), generation: n.generation, } if len(n.pods) > 0 { @@ -403,7 +408,26 @@ func (n *NodeInfo) updateUsedPorts(pod *v1.Pod, used bool) { // "0" is explicitly ignored in PodFitsHostPorts, // which is the only function that uses this value. if podPort.HostPort != 0 { - n.usedPorts[int(podPort.HostPort)] = used + // user does not explicitly set protocol, default is tcp + portProtocol := podPort.Protocol + if podPort.Protocol == "" { + portProtocol = v1.ProtocolTCP + } + + // user does not explicitly set hostIP, default is 0.0.0.0 + portHostIP := podPort.HostIP + if podPort.HostIP == "" { + portHostIP = util.DefaultBindAllHostIP + } + + str := fmt.Sprintf("%s/%s/%d", portProtocol, portHostIP, podPort.HostPort) + + if used { + n.usedPorts[str] = used + } else { + delete(n.usedPorts, str) + } + } } } @@ -486,12 +510,11 @@ func getPodKey(pod *v1.Pod) (string, error) { // matches NodeInfo.node and the pod is not found in the pods list. Otherwise, // returns true. func (n *NodeInfo) Filter(pod *v1.Pod) bool { - pFullName := util.GetPodFullName(pod) if pod.Spec.NodeName != n.node.Name { return true } for _, p := range n.pods { - if util.GetPodFullName(p) == pFullName { + if p.Name == pod.Name && p.Namespace == pod.Namespace { return true } } diff --git a/plugin/pkg/scheduler/schedulercache/util.go b/pkg/scheduler/schedulercache/util.go similarity index 100% rename from plugin/pkg/scheduler/schedulercache/util.go rename to pkg/scheduler/schedulercache/util.go diff --git a/pkg/scheduler/testing/BUILD b/pkg/scheduler/testing/BUILD new file mode 100644 index 00000000000..3b4b65c08d5 --- /dev/null +++ b/pkg/scheduler/testing/BUILD @@ -0,0 +1,40 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = [ + "fake_cache.go", + "fake_lister.go", + "pods_to_cache.go", + ], + importpath = "k8s.io/kubernetes/pkg/scheduler/testing", + deps = [ + "//pkg/scheduler/algorithm:go_default_library", + "//pkg/scheduler/schedulercache:go_default_library", + "//vendor/k8s.io/api/apps/v1beta1:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", + "//vendor/k8s.io/api/policy/v1beta1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/client-go/listers/core/v1:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/plugin/pkg/scheduler/testing/fake_cache.go b/pkg/scheduler/testing/fake_cache.go similarity index 81% rename from plugin/pkg/scheduler/testing/fake_cache.go rename to pkg/scheduler/testing/fake_cache.go index f0de231e579..be77503dd4b 100644 --- a/plugin/pkg/scheduler/testing/fake_cache.go +++ b/pkg/scheduler/testing/fake_cache.go @@ -18,8 +18,9 @@ package testing import ( "k8s.io/api/core/v1" + policy "k8s.io/api/policy/v1beta1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" ) // FakeCache is used for testing @@ -66,6 +67,16 @@ func (f *FakeCache) UpdateNodeNameToInfoMap(infoMap map[string]*schedulercache.N return nil } +func (f *FakeCache) AddPDB(pdb *policy.PodDisruptionBudget) error { return nil } + +func (f *FakeCache) UpdatePDB(oldPDB, newPDB *policy.PodDisruptionBudget) error { return nil } + +func (f *FakeCache) RemovePDB(pdb *policy.PodDisruptionBudget) error { return nil } + +func (f *FakeCache) ListPDBs(selector labels.Selector) ([]*policy.PodDisruptionBudget, error) { + return nil, nil +} + func (f *FakeCache) List(s labels.Selector) ([]*v1.Pod, error) { return nil, nil } func (f *FakeCache) FilteredList(filter schedulercache.PodFilter, selector labels.Selector) ([]*v1.Pod, error) { diff --git a/plugin/pkg/scheduler/testing/fake_lister.go b/pkg/scheduler/testing/fake_lister.go similarity index 78% rename from plugin/pkg/scheduler/testing/fake_lister.go rename to pkg/scheduler/testing/fake_lister.go index 35f763087fe..fdf5431fa26 100644 --- a/plugin/pkg/scheduler/testing/fake_lister.go +++ b/pkg/scheduler/testing/fake_lister.go @@ -24,8 +24,9 @@ import ( extensions "k8s.io/api/extensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - . "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" + corelisters "k8s.io/client-go/listers/core/v1" + . "k8s.io/kubernetes/pkg/scheduler/algorithm" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" ) var _ NodeLister = &FakeNodeLister{} @@ -176,3 +177,38 @@ func (f FakeStatefulSetLister) GetPodStatefulSets(pod *v1.Pod) (sss []*apps.Stat } return } + +// FakePersistentVolumeClaimLister implements PersistentVolumeClaimLister on []*v1.PersistentVolumeClaim for test purposes. +type FakePersistentVolumeClaimLister []*v1.PersistentVolumeClaim + +var _ corelisters.PersistentVolumeClaimLister = FakePersistentVolumeClaimLister{} + +func (f FakePersistentVolumeClaimLister) List(selector labels.Selector) (ret []*v1.PersistentVolumeClaim, err error) { + return nil, fmt.Errorf("not implemented") +} + +func (f FakePersistentVolumeClaimLister) PersistentVolumeClaims(namespace string) corelisters.PersistentVolumeClaimNamespaceLister { + return &fakePersistentVolumeClaimNamespaceLister{ + pvcs: f, + namespace: namespace, + } +} + +// fakePersistentVolumeClaimNamespaceLister is implementation of PersistentVolumeClaimNamespaceLister returned by List() above. +type fakePersistentVolumeClaimNamespaceLister struct { + pvcs []*v1.PersistentVolumeClaim + namespace string +} + +func (f *fakePersistentVolumeClaimNamespaceLister) Get(name string) (*v1.PersistentVolumeClaim, error) { + for _, pvc := range f.pvcs { + if pvc.Name == name && pvc.Namespace == f.namespace { + return pvc, nil + } + } + return nil, fmt.Errorf("persistentvolumeclaim %q not found", name) +} + +func (f fakePersistentVolumeClaimNamespaceLister) List(selector labels.Selector) (ret []*v1.PersistentVolumeClaim, err error) { + return nil, fmt.Errorf("not implemented") +} diff --git a/plugin/pkg/scheduler/testing/pods_to_cache.go b/pkg/scheduler/testing/pods_to_cache.go similarity index 96% rename from plugin/pkg/scheduler/testing/pods_to_cache.go rename to pkg/scheduler/testing/pods_to_cache.go index 94f630b53cf..2c5f6c6bf02 100644 --- a/plugin/pkg/scheduler/testing/pods_to_cache.go +++ b/pkg/scheduler/testing/pods_to_cache.go @@ -19,7 +19,7 @@ package testing import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" + "k8s.io/kubernetes/pkg/scheduler/schedulercache" ) // PodsToCache is used for testing diff --git a/pkg/scheduler/testutil.go b/pkg/scheduler/testutil.go new file mode 100644 index 00000000000..249ced16cd8 --- /dev/null +++ b/pkg/scheduler/testutil.go @@ -0,0 +1,115 @@ +/* +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 scheduler + +import ( + "fmt" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/sets" + clientset "k8s.io/client-go/kubernetes" + corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/kubernetes/pkg/scheduler/algorithm" + schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" + "k8s.io/kubernetes/pkg/scheduler/core" + "k8s.io/kubernetes/pkg/scheduler/util" +) + +// FakeConfigurator is an implementation for test. +type FakeConfigurator struct { + Config *Config +} + +// GetPriorityFunctionConfigs is not implemented yet. +func (fc *FakeConfigurator) GetPriorityFunctionConfigs(priorityKeys sets.String) ([]algorithm.PriorityConfig, error) { + return nil, fmt.Errorf("not implemented") +} + +// GetPriorityMetadataProducer is not implemented yet. +func (fc *FakeConfigurator) GetPriorityMetadataProducer() (algorithm.MetadataProducer, error) { + return nil, fmt.Errorf("not implemented") +} + +// GetPredicateMetadataProducer is not implemented yet. +func (fc *FakeConfigurator) GetPredicateMetadataProducer() (algorithm.PredicateMetadataProducer, error) { + return nil, fmt.Errorf("not implemented") +} + +// GetPredicates is not implemented yet. +func (fc *FakeConfigurator) GetPredicates(predicateKeys sets.String) (map[string]algorithm.FitPredicate, error) { + return nil, fmt.Errorf("not implemented") +} + +// GetHardPodAffinitySymmetricWeight is not implemented yet. +func (fc *FakeConfigurator) GetHardPodAffinitySymmetricWeight() int32 { + panic("not implemented") +} + +// GetSchedulerName is not implemented yet. +func (fc *FakeConfigurator) GetSchedulerName() string { + panic("not implemented") +} + +// MakeDefaultErrorFunc is not implemented yet. +func (fc *FakeConfigurator) MakeDefaultErrorFunc(backoff *util.PodBackoff, podQueue core.SchedulingQueue) func(pod *v1.Pod, err error) { + return nil +} + +// ResponsibleForPod is not implemented yet. +func (fc *FakeConfigurator) ResponsibleForPod(pod *v1.Pod) bool { + panic("not implemented") +} + +// GetNodeLister is not implemented yet. +func (fc *FakeConfigurator) GetNodeLister() corelisters.NodeLister { + return nil +} + +// GetClient is not implemented yet. +func (fc *FakeConfigurator) GetClient() clientset.Interface { + return nil +} + +// GetScheduledPodLister is not implemented yet. +func (fc *FakeConfigurator) GetScheduledPodLister() corelisters.PodLister { + return nil +} + +// Run is not implemented yet. +func (fc *FakeConfigurator) Run() { + panic("not implemented") +} + +// Create returns FakeConfigurator.Config +func (fc *FakeConfigurator) Create() (*Config, error) { + return fc.Config, nil +} + +// CreateFromProvider returns FakeConfigurator.Config +func (fc *FakeConfigurator) CreateFromProvider(providerName string) (*Config, error) { + return fc.Config, nil +} + +// CreateFromConfig returns FakeConfigurator.Config +func (fc *FakeConfigurator) CreateFromConfig(policy schedulerapi.Policy) (*Config, error) { + return fc.Config, nil +} + +// CreateFromKeys returns FakeConfigurator.Config +func (fc *FakeConfigurator) CreateFromKeys(predicateKeys, priorityKeys sets.String, extenders []algorithm.SchedulerExtender) (*Config, error) { + return fc.Config, nil +} diff --git a/pkg/scheduler/util/BUILD b/pkg/scheduler/util/BUILD new file mode 100644 index 00000000000..f333e8cd99a --- /dev/null +++ b/pkg/scheduler/util/BUILD @@ -0,0 +1,61 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_test( + name = "go_default_test", + srcs = [ + "backoff_utils_test.go", + "testutil_test.go", + "utils_test.go", + ], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/scheduler/util", + deps = [ + "//pkg/apis/scheduling:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + ], +) + +go_library( + name = "go_default_library", + srcs = [ + "backoff_utils.go", + "testutil.go", + "utils.go", + ], + importpath = "k8s.io/kubernetes/pkg/scheduler/util", + deps = [ + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/install:go_default_library", + "//pkg/apis/scheduling:go_default_library", + "//pkg/features:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/plugin/pkg/scheduler/util/backoff_utils.go b/pkg/scheduler/util/backoff_utils.go similarity index 100% rename from plugin/pkg/scheduler/util/backoff_utils.go rename to pkg/scheduler/util/backoff_utils.go diff --git a/plugin/pkg/scheduler/util/backoff_utils_test.go b/pkg/scheduler/util/backoff_utils_test.go similarity index 100% rename from plugin/pkg/scheduler/util/backoff_utils_test.go rename to pkg/scheduler/util/backoff_utils_test.go diff --git a/pkg/scheduler/util/testutil.go b/pkg/scheduler/util/testutil.go new file mode 100644 index 00000000000..45ce2df112d --- /dev/null +++ b/pkg/scheduler/util/testutil.go @@ -0,0 +1,169 @@ +/* +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 util + +import ( + "fmt" + "mime" + "os" + "reflect" + "strings" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" + + _ "k8s.io/kubernetes/pkg/apis/core/install" +) + +type TestGroup struct { + externalGroupVersion schema.GroupVersion + internalGroupVersion schema.GroupVersion + internalTypes map[string]reflect.Type + externalTypes map[string]reflect.Type +} + +var ( + Groups = make(map[string]TestGroup) + Test TestGroup + + serializer runtime.SerializerInfo +) + +func init() { + if apiMediaType := os.Getenv("KUBE_TEST_API_TYPE"); len(apiMediaType) > 0 { + var ok bool + mediaType, _, err := mime.ParseMediaType(apiMediaType) + if err != nil { + panic(err) + } + serializer, ok = runtime.SerializerInfoForMediaType(legacyscheme.Codecs.SupportedMediaTypes(), mediaType) + if !ok { + panic(fmt.Sprintf("no serializer for %s", apiMediaType)) + } + } + + kubeTestAPI := os.Getenv("KUBE_TEST_API") + if len(kubeTestAPI) != 0 { + // priority is "first in list preferred", so this has to run in reverse order + testGroupVersions := strings.Split(kubeTestAPI, ",") + for i := len(testGroupVersions) - 1; i >= 0; i-- { + gvString := testGroupVersions[i] + groupVersion, err := schema.ParseGroupVersion(gvString) + if err != nil { + panic(fmt.Sprintf("Error parsing groupversion %v: %v", gvString, err)) + } + + internalGroupVersion := schema.GroupVersion{Group: groupVersion.Group, Version: runtime.APIVersionInternal} + Groups[groupVersion.Group] = TestGroup{ + externalGroupVersion: groupVersion, + internalGroupVersion: internalGroupVersion, + internalTypes: legacyscheme.Scheme.KnownTypes(internalGroupVersion), + externalTypes: legacyscheme.Scheme.KnownTypes(groupVersion), + } + } + } + + if _, ok := Groups[api.GroupName]; !ok { + externalGroupVersion := schema.GroupVersion{Group: api.GroupName, Version: legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.Version} + Groups[api.GroupName] = TestGroup{ + externalGroupVersion: externalGroupVersion, + internalGroupVersion: api.SchemeGroupVersion, + internalTypes: legacyscheme.Scheme.KnownTypes(api.SchemeGroupVersion), + externalTypes: legacyscheme.Scheme.KnownTypes(externalGroupVersion), + } + } + + Test = Groups[api.GroupName] +} + +// Codec returns the codec for the API version to test against, as set by the +// KUBE_TEST_API_TYPE env var. +func (g TestGroup) Codec() runtime.Codec { + if serializer.Serializer == nil { + return legacyscheme.Codecs.LegacyCodec(g.externalGroupVersion) + } + return legacyscheme.Codecs.CodecForVersions(serializer.Serializer, legacyscheme.Codecs.UniversalDeserializer(), schema.GroupVersions{g.externalGroupVersion}, nil) +} + +// SelfLink returns a self link that will appear to be for the version Version(). +// 'resource' should be the resource path, e.g. "pods" for the Pod type. 'name' should be +// empty for lists. +func (g TestGroup) SelfLink(resource, name string) string { + if g.externalGroupVersion.Group == api.GroupName { + if name == "" { + return fmt.Sprintf("/api/%s/%s", g.externalGroupVersion.Version, resource) + } + return fmt.Sprintf("/api/%s/%s/%s", g.externalGroupVersion.Version, resource, name) + } + + // TODO: will need a /apis prefix once we have proper multi-group + // support + if name == "" { + return fmt.Sprintf("/apis/%s/%s/%s", g.externalGroupVersion.Group, g.externalGroupVersion.Version, resource) + } + return fmt.Sprintf("/apis/%s/%s/%s/%s", g.externalGroupVersion.Group, g.externalGroupVersion.Version, resource, name) +} + +// ResourcePathWithPrefix returns the appropriate path for the given prefix (watch, proxy, redirect, etc), resource, namespace and name. +// For ex, this is of the form: +// /api/v1/watch/namespaces/foo/pods/pod0 for v1. +func (g TestGroup) ResourcePathWithPrefix(prefix, resource, namespace, name string) string { + var path string + if g.externalGroupVersion.Group == api.GroupName { + path = "/api/" + g.externalGroupVersion.Version + } else { + // TODO: switch back once we have proper multiple group support + // path = "/apis/" + g.Group + "/" + Version(group...) + path = "/apis/" + g.externalGroupVersion.Group + "/" + g.externalGroupVersion.Version + } + + if prefix != "" { + path = path + "/" + prefix + } + if namespace != "" { + path = path + "/namespaces/" + namespace + } + // Resource names are lower case. + resource = strings.ToLower(resource) + if resource != "" { + path = path + "/" + resource + } + if name != "" { + path = path + "/" + name + } + return path +} + +// ResourcePath returns the appropriate path for the given resource, namespace and name. +// For example, this is of the form: +// /api/v1/namespaces/foo/pods/pod0 for v1. +func (g TestGroup) ResourcePath(resource, namespace, name string) string { + return g.ResourcePathWithPrefix("", resource, namespace, name) +} + +// SubResourcePath returns the appropriate path for the given resource, namespace, +// name and subresource. +func (g TestGroup) SubResourcePath(resource, namespace, name, sub string) string { + path := g.ResourcePathWithPrefix("", resource, namespace, name) + if sub != "" { + path = path + "/" + sub + } + + return path +} diff --git a/plugin/pkg/scheduler/util/testutil_test.go b/pkg/scheduler/util/testutil_test.go similarity index 100% rename from plugin/pkg/scheduler/util/testutil_test.go rename to pkg/scheduler/util/testutil_test.go diff --git a/pkg/scheduler/util/utils.go b/pkg/scheduler/util/utils.go new file mode 100644 index 00000000000..66a004e0013 --- /dev/null +++ b/pkg/scheduler/util/utils.go @@ -0,0 +1,120 @@ +/* +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 util + +import ( + "fmt" + "sort" + + "k8s.io/api/core/v1" + "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/apis/scheduling" + "k8s.io/kubernetes/pkg/features" +) + +const DefaultBindAllHostIP = "0.0.0.0" + +// GetUsedPorts returns the used host ports of Pods: if 'port' was used, a 'port:true' pair +// will be in the result; but it does not resolve port conflict. +func GetUsedPorts(pods ...*v1.Pod) map[string]bool { + ports := make(map[string]bool) + for _, pod := range pods { + for j := range pod.Spec.Containers { + container := &pod.Spec.Containers[j] + for k := range container.Ports { + podPort := &container.Ports[k] + // "0" is explicitly ignored in PodFitsHostPorts, + // which is the only function that uses this value. + if podPort.HostPort != 0 { + // user does not explicitly set protocol, default is tcp + portProtocol := podPort.Protocol + if podPort.Protocol == "" { + portProtocol = v1.ProtocolTCP + } + + // user does not explicitly set hostIP, default is 0.0.0.0 + portHostIP := podPort.HostIP + if podPort.HostIP == "" { + portHostIP = "0.0.0.0" + } + + str := fmt.Sprintf("%s/%s/%d", portProtocol, portHostIP, podPort.HostPort) + ports[str] = true + } + } + } + } + return ports +} + +// PodPriorityEnabled indicates whether pod priority feature is enabled. +func PodPriorityEnabled() bool { + return feature.DefaultFeatureGate.Enabled(features.PodPriority) +} + +// GetPodFullName returns a name that uniquely identifies a pod. +func GetPodFullName(pod *v1.Pod) string { + // Use underscore as the delimiter because it is not allowed in pod name + // (DNS subdomain format). + return pod.Name + "_" + pod.Namespace +} + +// GetPodPriority return priority of the given pod. +func GetPodPriority(pod *v1.Pod) int32 { + if pod.Spec.Priority != nil { + return *pod.Spec.Priority + } + // When priority of a running pod 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 was empty. So, we resolve to the static default priority. + return scheduling.DefaultPriorityWhenNoDefaultClassExists +} + +// SortableList is a list that implements sort.Interface. +type SortableList struct { + Items []interface{} + CompFunc LessFunc +} + +// LessFunc is a function that receives two items and returns true if the first +// item should be placed before the second one when the list is sorted. +type LessFunc func(item1, item2 interface{}) bool + +var _ = sort.Interface(&SortableList{}) + +func (l *SortableList) Len() int { return len(l.Items) } + +func (l *SortableList) Less(i, j int) bool { + return l.CompFunc(l.Items[i], l.Items[j]) +} + +func (l *SortableList) Swap(i, j int) { + l.Items[i], l.Items[j] = l.Items[j], l.Items[i] +} + +// Sort sorts the items in the list using the given CompFunc. Item1 is placed +// before Item2 when CompFunc(Item1, Item2) returns true. +func (l *SortableList) Sort() { + sort.Sort(l) +} + +// HigherPriorityPod return true when priority of the first pod is higher than +// the second one. It takes arguments of the type "interface{}" to be used with +// SortableList, but expects those arguments to be *v1.Pod. +func HigherPriorityPod(pod1, pod2 interface{}) bool { + return GetPodPriority(pod1.(*v1.Pod)) > GetPodPriority(pod2.(*v1.Pod)) +} diff --git a/plugin/pkg/scheduler/util/utils_test.go b/pkg/scheduler/util/utils_test.go similarity index 100% rename from plugin/pkg/scheduler/util/utils_test.go rename to pkg/scheduler/util/utils_test.go diff --git a/pkg/scheduler/volumebinder/BUILD b/pkg/scheduler/volumebinder/BUILD new file mode 100644 index 00000000000..0656eeee274 --- /dev/null +++ b/pkg/scheduler/volumebinder/BUILD @@ -0,0 +1,31 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["volume_binder.go"], + importpath = "k8s.io/kubernetes/pkg/scheduler/volumebinder", + visibility = ["//visibility:public"], + deps = [ + "//pkg/controller/volume/persistentvolume:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/client-go/informers/core/v1:go_default_library", + "//vendor/k8s.io/client-go/informers/storage/v1:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/client-go/util/workqueue:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/scheduler/volumebinder/volume_binder.go b/pkg/scheduler/volumebinder/volume_binder.go new file mode 100644 index 00000000000..957c4e18aac --- /dev/null +++ b/pkg/scheduler/volumebinder/volume_binder.go @@ -0,0 +1,74 @@ +/* +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 volumebinder + +import ( + "time" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/wait" + coreinformers "k8s.io/client-go/informers/core/v1" + storageinformers "k8s.io/client-go/informers/storage/v1" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/util/workqueue" + "k8s.io/kubernetes/pkg/controller/volume/persistentvolume" +) + +// VolumeBinder sets up the volume binding library and manages +// the volume binding operations with a queue. +type VolumeBinder struct { + Binder persistentvolume.SchedulerVolumeBinder + BindQueue *workqueue.Type +} + +// NewVolumeBinder sets up the volume binding library and binding queue +func NewVolumeBinder( + client clientset.Interface, + pvcInformer coreinformers.PersistentVolumeClaimInformer, + pvInformer coreinformers.PersistentVolumeInformer, + nodeInformer coreinformers.NodeInformer, + storageClassInformer storageinformers.StorageClassInformer) *VolumeBinder { + + return &VolumeBinder{ + Binder: persistentvolume.NewVolumeBinder(client, pvcInformer, pvInformer, nodeInformer, storageClassInformer), + BindQueue: workqueue.NewNamed("podsToBind"), + } +} + +// NewFakeVolumeBinder sets up a fake volume binder and binding queue +func NewFakeVolumeBinder(config *persistentvolume.FakeVolumeBinderConfig) *VolumeBinder { + return &VolumeBinder{ + Binder: persistentvolume.NewFakeVolumeBinder(config), + BindQueue: workqueue.NewNamed("podsToBind"), + } +} + +// Run starts a goroutine to handle the binding queue with the given function. +func (b *VolumeBinder) Run(bindWorkFunc func(), stopCh <-chan struct{}) { + go wait.Until(bindWorkFunc, time.Second, stopCh) + + <-stopCh + b.BindQueue.ShutDown() +} + +// DeletePodBindings will delete the cached volume bindings for the given pod. +func (b *VolumeBinder) DeletePodBindings(pod *v1.Pod) { + cache := b.Binder.GetBindingsCache() + if cache != nil && pod != nil { + cache.DeleteBindings(pod) + } +} diff --git a/pkg/security/apparmor/BUILD b/pkg/security/apparmor/BUILD index ad686ff2ab2..70877c9fcee 100644 --- a/pkg/security/apparmor/BUILD +++ b/pkg/security/apparmor/BUILD @@ -11,8 +11,39 @@ go_library( srcs = [ "helpers.go", "validate.go", - "validate_disabled.go", - ], + ] + select({ + "@io_bazel_rules_go//go/platform:android": [ + "validate_disabled.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "validate_disabled.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "validate_disabled.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "validate_disabled.go", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "validate_disabled.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "validate_disabled.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "validate_disabled.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "validate_disabled.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "validate_disabled.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "validate_disabled.go", + ], + "//conditions:default": [], + }), importpath = "k8s.io/kubernetes/pkg/security/apparmor", deps = [ "//pkg/features:go_default_library", @@ -29,8 +60,8 @@ go_test( data = [ "testdata/profiles", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/security/apparmor", - library = ":go_default_library", deps = [ "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/security/podsecuritypolicy/BUILD b/pkg/security/podsecuritypolicy/BUILD index 55a68dec3bf..06e0e0af5fd 100644 --- a/pkg/security/podsecuritypolicy/BUILD +++ b/pkg/security/podsecuritypolicy/BUILD @@ -16,7 +16,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/security/podsecuritypolicy/apparmor:go_default_library", "//pkg/security/podsecuritypolicy/capabilities:go_default_library", @@ -27,7 +27,6 @@ go_library( "//pkg/security/podsecuritypolicy/user:go_default_library", "//pkg/security/podsecuritypolicy/util:go_default_library", "//pkg/securitycontext:go_default_library", - "//pkg/util/maps:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", ], @@ -36,11 +35,11 @@ go_library( go_test( name = "go_default_test", srcs = ["provider_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/v1:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/v1:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/security/apparmor:go_default_library", "//pkg/security/podsecuritypolicy/seccomp:go_default_library", diff --git a/pkg/security/podsecuritypolicy/OWNERS b/pkg/security/podsecuritypolicy/OWNERS new file mode 100644 index 00000000000..d9686059621 --- /dev/null +++ b/pkg/security/podsecuritypolicy/OWNERS @@ -0,0 +1,13 @@ +approvers: + - deads2k + - ericchiang + - liggitt + - pweil- + - tallclair +reviewers: + - deads2k + - ericchiang + - liggitt + - php-coder + - pweil- + - tallclair diff --git a/pkg/security/podsecuritypolicy/apparmor/BUILD b/pkg/security/podsecuritypolicy/apparmor/BUILD index 4ec208951a4..28f2fcb440e 100644 --- a/pkg/security/podsecuritypolicy/apparmor/BUILD +++ b/pkg/security/podsecuritypolicy/apparmor/BUILD @@ -11,7 +11,7 @@ go_library( srcs = ["strategy.go"], importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy/apparmor", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/security/apparmor:go_default_library", "//pkg/util/maps:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", @@ -21,10 +21,10 @@ go_library( go_test( name = "go_default_test", srcs = ["strategy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy/apparmor", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/security/apparmor:go_default_library", "//pkg/util/maps:go_default_library", "//vendor/github.com/davecgh/go-spew/spew:go_default_library", diff --git a/pkg/security/podsecuritypolicy/apparmor/strategy.go b/pkg/security/podsecuritypolicy/apparmor/strategy.go index 47755c80c02..9f6affdd2b3 100644 --- a/pkg/security/podsecuritypolicy/apparmor/strategy.go +++ b/pkg/security/podsecuritypolicy/apparmor/strategy.go @@ -21,7 +21,7 @@ import ( "strings" "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/security/apparmor" "k8s.io/kubernetes/pkg/util/maps" ) diff --git a/pkg/security/podsecuritypolicy/apparmor/strategy_test.go b/pkg/security/podsecuritypolicy/apparmor/strategy_test.go index 927bdf9aeba..ac4edc448e3 100644 --- a/pkg/security/podsecuritypolicy/apparmor/strategy_test.go +++ b/pkg/security/podsecuritypolicy/apparmor/strategy_test.go @@ -23,7 +23,7 @@ import ( "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/security/apparmor" "k8s.io/kubernetes/pkg/util/maps" ) diff --git a/pkg/security/podsecuritypolicy/capabilities/BUILD b/pkg/security/podsecuritypolicy/capabilities/BUILD index 939756ecb25..931ae7e3935 100644 --- a/pkg/security/podsecuritypolicy/capabilities/BUILD +++ b/pkg/security/podsecuritypolicy/capabilities/BUILD @@ -9,13 +9,13 @@ load( go_library( name = "go_default_library", srcs = [ + "capabilities.go", "doc.go", - "mustrunas.go", "types.go", ], importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy/capabilities", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", @@ -24,11 +24,11 @@ go_library( go_test( name = "go_default_test", - srcs = ["mustrunas_test.go"], + srcs = ["capabilities_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy/capabilities", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", ], ) diff --git a/pkg/security/podsecuritypolicy/capabilities/capabilities.go b/pkg/security/podsecuritypolicy/capabilities/capabilities.go new file mode 100644 index 00000000000..bb713c7be1e --- /dev/null +++ b/pkg/security/podsecuritypolicy/capabilities/capabilities.go @@ -0,0 +1,152 @@ +/* +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 capabilities + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation/field" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/extensions" +) + +// defaultCapabilities implements the Strategy interface +type defaultCapabilities struct { + defaultAddCapabilities []api.Capability + requiredDropCapabilities []api.Capability + allowedCaps []api.Capability +} + +var _ Strategy = &defaultCapabilities{} + +// NewDefaultCapabilities creates a new defaultCapabilities strategy that will provide defaults and validation +// based on the configured initial caps and allowed caps. +func NewDefaultCapabilities(defaultAddCapabilities, requiredDropCapabilities, allowedCaps []api.Capability) (Strategy, error) { + return &defaultCapabilities{ + defaultAddCapabilities: defaultAddCapabilities, + requiredDropCapabilities: requiredDropCapabilities, + allowedCaps: allowedCaps, + }, nil +} + +// Generate creates the capabilities based on policy rules. Generate will produce the following: +// 1. a capabilities.Add set containing all the required adds (unless the +// container specifically is dropping the cap) and container requested adds +// 2. a capabilities.Drop set containing all the required drops and container requested drops +// +// Returns the original container capabilities if no changes are required. +func (s *defaultCapabilities) Generate(pod *api.Pod, container *api.Container) (*api.Capabilities, error) { + defaultAdd := makeCapSet(s.defaultAddCapabilities) + requiredDrop := makeCapSet(s.requiredDropCapabilities) + containerAdd := sets.NewString() + containerDrop := sets.NewString() + + var containerCapabilities *api.Capabilities + if container.SecurityContext != nil && container.SecurityContext.Capabilities != nil { + containerCapabilities = container.SecurityContext.Capabilities + containerAdd = makeCapSet(container.SecurityContext.Capabilities.Add) + containerDrop = makeCapSet(container.SecurityContext.Capabilities.Drop) + } + + // remove any default adds that the container is specifically dropping + defaultAdd = defaultAdd.Difference(containerDrop) + + combinedAdd := defaultAdd.Union(containerAdd) + combinedDrop := requiredDrop.Union(containerDrop) + + // no changes? return the original capabilities + if (len(combinedAdd) == len(containerAdd)) && (len(combinedDrop) == len(containerDrop)) { + return containerCapabilities, nil + } + + return &api.Capabilities{ + Add: capabilityFromStringSlice(combinedAdd.List()), + Drop: capabilityFromStringSlice(combinedDrop.List()), + }, nil +} + +// Validate ensures that the specified values fall within the range of the strategy. +func (s *defaultCapabilities) Validate(pod *api.Pod, container *api.Container, capabilities *api.Capabilities) field.ErrorList { + allErrs := field.ErrorList{} + + if capabilities == nil { + // if container.SC.Caps is nil then nothing was defaulted by the strategy or requested by the pod author + // if there are no required caps on the strategy and nothing is requested on the pod + // then we can safely return here without further validation. + if len(s.defaultAddCapabilities) == 0 && len(s.requiredDropCapabilities) == 0 { + return allErrs + } + + // container has no requested caps but we have required caps. We should have something in + // at least the drops on the container. + allErrs = append(allErrs, field.Invalid(field.NewPath("capabilities"), capabilities, + "required capabilities are not set on the securityContext")) + return allErrs + } + + allowedAdd := makeCapSet(s.allowedCaps) + allowAllCaps := allowedAdd.Has(string(extensions.AllowAllCapabilities)) + if allowAllCaps { + // skip validation against allowed/defaultAdd/requiredDrop because all capabilities are allowed by a wildcard + return allErrs + } + + // validate that anything being added is in the default or allowed sets + defaultAdd := makeCapSet(s.defaultAddCapabilities) + + for _, cap := range capabilities.Add { + sCap := string(cap) + if !defaultAdd.Has(sCap) && !allowedAdd.Has(sCap) { + allErrs = append(allErrs, field.Invalid(field.NewPath("capabilities", "add"), sCap, "capability may not be added")) + } + } + + // validate that anything that is required to be dropped is in the drop set + containerDrops := makeCapSet(capabilities.Drop) + + for _, requiredDrop := range s.requiredDropCapabilities { + sDrop := string(requiredDrop) + if !containerDrops.Has(sDrop) { + allErrs = append(allErrs, field.Invalid(field.NewPath("capabilities", "drop"), capabilities.Drop, + fmt.Sprintf("%s is required to be dropped but was not found", sDrop))) + } + } + + return allErrs +} + +// capabilityFromStringSlice creates a capability slice from a string slice. +func capabilityFromStringSlice(slice []string) []api.Capability { + if len(slice) == 0 { + return nil + } + caps := []api.Capability{} + for _, c := range slice { + caps = append(caps, api.Capability(c)) + } + return caps +} + +// makeCapSet makes a string set from capabilities. +func makeCapSet(caps []api.Capability) sets.String { + s := sets.NewString() + for _, c := range caps { + s.Insert(string(c)) + } + return s +} diff --git a/pkg/security/podsecuritypolicy/capabilities/capabilities_test.go b/pkg/security/podsecuritypolicy/capabilities/capabilities_test.go new file mode 100644 index 00000000000..56cf5db5a4e --- /dev/null +++ b/pkg/security/podsecuritypolicy/capabilities/capabilities_test.go @@ -0,0 +1,410 @@ +/* +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 capabilities + +import ( + "reflect" + "testing" + + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/extensions" +) + +func TestGenerateAdds(t *testing.T) { + tests := map[string]struct { + defaultAddCaps []api.Capability + containerCaps *api.Capabilities + expectedCaps *api.Capabilities + }{ + "no required, no container requests": {}, + "no required, no container requests, non-nil": { + containerCaps: &api.Capabilities{}, + expectedCaps: &api.Capabilities{}, + }, + "required, no container requests": { + defaultAddCaps: []api.Capability{"foo"}, + expectedCaps: &api.Capabilities{ + Add: []api.Capability{"foo"}, + }, + }, + "required, container requests add required": { + defaultAddCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"foo"}, + }, + expectedCaps: &api.Capabilities{ + Add: []api.Capability{"foo"}, + }, + }, + "multiple required, container requests add required": { + defaultAddCaps: []api.Capability{"foo", "bar", "baz"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"foo"}, + }, + expectedCaps: &api.Capabilities{ + Add: []api.Capability{"bar", "baz", "foo"}, + }, + }, + "required, container requests add non-required": { + defaultAddCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"bar"}, + }, + expectedCaps: &api.Capabilities{ + Add: []api.Capability{"bar", "foo"}, + }, + }, + "generation does not mutate unnecessarily": { + defaultAddCaps: []api.Capability{"foo", "bar"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"foo", "foo", "bar", "baz"}, + }, + expectedCaps: &api.Capabilities{ + Add: []api.Capability{"foo", "foo", "bar", "baz"}, + }, + }, + "generation dedupes": { + defaultAddCaps: []api.Capability{"foo", "bar"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"foo", "baz"}, + }, + expectedCaps: &api.Capabilities{ + Add: []api.Capability{"bar", "baz", "foo"}, + }, + }, + "generation is case sensitive - will not dedupe": { + defaultAddCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"FOO"}, + }, + expectedCaps: &api.Capabilities{ + Add: []api.Capability{"FOO", "foo"}, + }, + }, + } + + for k, v := range tests { + container := &api.Container{ + SecurityContext: &api.SecurityContext{ + Capabilities: v.containerCaps, + }, + } + + strategy, err := NewDefaultCapabilities(v.defaultAddCaps, nil, nil) + if err != nil { + t.Errorf("%s failed: %v", k, err) + continue + } + generatedCaps, err := strategy.Generate(nil, container) + if err != nil { + t.Errorf("%s failed generating: %v", k, err) + continue + } + if v.expectedCaps == nil && generatedCaps != nil { + t.Errorf("%s expected nil caps to be generated but got %v", k, generatedCaps) + continue + } + if !reflect.DeepEqual(v.expectedCaps, generatedCaps) { + t.Errorf("%s did not generate correctly. Expected: %#v, Actual: %#v", k, v.expectedCaps, generatedCaps) + } + } +} + +func TestGenerateDrops(t *testing.T) { + tests := map[string]struct { + defaultAddCaps []api.Capability + requiredDropCaps []api.Capability + containerCaps *api.Capabilities + expectedCaps *api.Capabilities + }{ + "no required, no container requests": { + expectedCaps: nil, + }, + "no required, no container requests, non-nil": { + containerCaps: &api.Capabilities{}, + expectedCaps: &api.Capabilities{}, + }, + "required drops are defaulted": { + requiredDropCaps: []api.Capability{"foo"}, + expectedCaps: &api.Capabilities{ + Drop: []api.Capability{"foo"}, + }, + }, + "required drops are defaulted when making container requests": { + requiredDropCaps: []api.Capability{"baz"}, + containerCaps: &api.Capabilities{ + Drop: []api.Capability{"foo", "bar"}, + }, + expectedCaps: &api.Capabilities{ + Drop: []api.Capability{"bar", "baz", "foo"}, + }, + }, + "required drops do not mutate unnecessarily": { + requiredDropCaps: []api.Capability{"baz"}, + containerCaps: &api.Capabilities{ + Drop: []api.Capability{"foo", "bar", "baz"}, + }, + expectedCaps: &api.Capabilities{ + Drop: []api.Capability{"foo", "bar", "baz"}, + }, + }, + "can drop a required add": { + defaultAddCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Drop: []api.Capability{"foo"}, + }, + expectedCaps: &api.Capabilities{ + Drop: []api.Capability{"foo"}, + }, + }, + "can drop non-required add": { + defaultAddCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Drop: []api.Capability{"bar"}, + }, + expectedCaps: &api.Capabilities{ + Add: []api.Capability{"foo"}, + Drop: []api.Capability{"bar"}, + }, + }, + "defaulting adds and drops, dropping a required add": { + defaultAddCaps: []api.Capability{"foo", "bar", "baz"}, + requiredDropCaps: []api.Capability{"abc"}, + containerCaps: &api.Capabilities{ + Drop: []api.Capability{"foo"}, + }, + expectedCaps: &api.Capabilities{ + Add: []api.Capability{"bar", "baz"}, + Drop: []api.Capability{"abc", "foo"}, + }, + }, + "generation dedupes": { + requiredDropCaps: []api.Capability{"baz", "foo"}, + containerCaps: &api.Capabilities{ + Drop: []api.Capability{"bar", "foo"}, + }, + expectedCaps: &api.Capabilities{ + Drop: []api.Capability{"bar", "baz", "foo"}, + }, + }, + "generation is case sensitive - will not dedupe": { + requiredDropCaps: []api.Capability{"bar"}, + containerCaps: &api.Capabilities{ + Drop: []api.Capability{"BAR"}, + }, + expectedCaps: &api.Capabilities{ + Drop: []api.Capability{"BAR", "bar"}, + }, + }, + } + for k, v := range tests { + container := &api.Container{ + SecurityContext: &api.SecurityContext{ + Capabilities: v.containerCaps, + }, + } + + strategy, err := NewDefaultCapabilities(v.defaultAddCaps, v.requiredDropCaps, nil) + if err != nil { + t.Errorf("%s failed: %v", k, err) + continue + } + generatedCaps, err := strategy.Generate(nil, container) + if err != nil { + t.Errorf("%s failed generating: %v", k, err) + continue + } + if v.expectedCaps == nil && generatedCaps != nil { + t.Errorf("%s expected nil caps to be generated but got %#v", k, generatedCaps) + continue + } + if !reflect.DeepEqual(v.expectedCaps, generatedCaps) { + t.Errorf("%s did not generate correctly. Expected: %#v, Actual: %#v", k, v.expectedCaps, generatedCaps) + } + } +} + +func TestValidateAdds(t *testing.T) { + tests := map[string]struct { + defaultAddCaps []api.Capability + allowedCaps []api.Capability + containerCaps *api.Capabilities + expectedError string + }{ + // no container requests + "no required, no allowed, no container requests": {}, + "no required, allowed, no container requests": { + allowedCaps: []api.Capability{"foo"}, + }, + "required, no allowed, no container requests": { + defaultAddCaps: []api.Capability{"foo"}, + expectedError: `capabilities: Invalid value: "null": required capabilities are not set on the securityContext`, + }, + + // container requests match required + "required, no allowed, container requests valid": { + defaultAddCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"foo"}, + }, + }, + "required, no allowed, container requests invalid": { + defaultAddCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"bar"}, + }, + expectedError: `capabilities.add: Invalid value: "bar": capability may not be added`, + }, + + // container requests match allowed + "no required, allowed, container requests valid": { + allowedCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"foo"}, + }, + }, + "no required, all allowed, container requests valid": { + allowedCaps: []api.Capability{extensions.AllowAllCapabilities}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"foo"}, + }, + }, + "no required, allowed, container requests invalid": { + allowedCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"bar"}, + }, + expectedError: `capabilities.add: Invalid value: "bar": capability may not be added`, + }, + + // required and allowed + "required, allowed, container requests valid required": { + defaultAddCaps: []api.Capability{"foo"}, + allowedCaps: []api.Capability{"bar"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"foo"}, + }, + }, + "required, allowed, container requests valid allowed": { + defaultAddCaps: []api.Capability{"foo"}, + allowedCaps: []api.Capability{"bar"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"bar"}, + }, + }, + "required, allowed, container requests invalid": { + defaultAddCaps: []api.Capability{"foo"}, + allowedCaps: []api.Capability{"bar"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"baz"}, + }, + expectedError: `capabilities.add: Invalid value: "baz": capability may not be added`, + }, + "validation is case sensitive": { + defaultAddCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"FOO"}, + }, + expectedError: `capabilities.add: Invalid value: "FOO": capability may not be added`, + }, + } + + for k, v := range tests { + strategy, err := NewDefaultCapabilities(v.defaultAddCaps, nil, v.allowedCaps) + if err != nil { + t.Errorf("%s failed: %v", k, err) + continue + } + errs := strategy.Validate(nil, nil, v.containerCaps) + if v.expectedError == "" && len(errs) > 0 { + t.Errorf("%s should have passed but had errors %v", k, errs) + continue + } + if v.expectedError != "" && len(errs) == 0 { + t.Errorf("%s should have failed but received no errors", k) + continue + } + if len(errs) == 1 && errs[0].Error() != v.expectedError { + t.Errorf("%s should have failed with %v but received %v", k, v.expectedError, errs[0]) + continue + } + if len(errs) > 1 { + t.Errorf("%s should have failed with at most one error, but received %v: %v", k, len(errs), errs) + } + } +} + +func TestValidateDrops(t *testing.T) { + tests := map[string]struct { + requiredDropCaps []api.Capability + containerCaps *api.Capabilities + expectedError string + }{ + // no container requests + "no required, no container requests": {}, + "required, no container requests": { + requiredDropCaps: []api.Capability{"foo"}, + expectedError: `capabilities: Invalid value: "null": required capabilities are not set on the securityContext`, + }, + + // container requests match required + "required, container requests valid": { + requiredDropCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Drop: []api.Capability{"foo"}, + }, + }, + "required, container requests invalid": { + requiredDropCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Drop: []api.Capability{"bar"}, + }, + expectedError: `capabilities.drop: Invalid value: []core.Capability{"bar"}: foo is required to be dropped but was not found`, + }, + "validation is case sensitive": { + requiredDropCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Drop: []api.Capability{"FOO"}, + }, + expectedError: `capabilities.drop: Invalid value: []core.Capability{"FOO"}: foo is required to be dropped but was not found`, + }, + } + + for k, v := range tests { + strategy, err := NewDefaultCapabilities(nil, v.requiredDropCaps, nil) + if err != nil { + t.Errorf("%s failed: %v", k, err) + continue + } + errs := strategy.Validate(nil, nil, v.containerCaps) + if v.expectedError == "" && len(errs) > 0 { + t.Errorf("%s should have passed but had errors %v", k, errs) + continue + } + if v.expectedError != "" && len(errs) == 0 { + t.Errorf("%s should have failed but received no errors", k) + continue + } + if len(errs) == 1 && errs[0].Error() != v.expectedError { + t.Errorf("%s should have failed with %v but received %v", k, v.expectedError, errs[0]) + continue + } + if len(errs) > 1 { + t.Errorf("%s should have failed with at most one error, but received %v: %v", k, len(errs), errs) + } + } +} diff --git a/pkg/security/podsecuritypolicy/capabilities/mustrunas.go b/pkg/security/podsecuritypolicy/capabilities/mustrunas.go deleted file mode 100644 index 6ab75c214b9..00000000000 --- a/pkg/security/podsecuritypolicy/capabilities/mustrunas.go +++ /dev/null @@ -1,152 +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 capabilities - -import ( - "fmt" - - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/extensions" -) - -// defaultCapabilities implements the Strategy interface -type defaultCapabilities struct { - defaultAddCapabilities []api.Capability - requiredDropCapabilities []api.Capability - allowedCaps []api.Capability -} - -var _ Strategy = &defaultCapabilities{} - -// NewDefaultCapabilities creates a new defaultCapabilities strategy that will provide defaults and validation -// based on the configured initial caps and allowed caps. -func NewDefaultCapabilities(defaultAddCapabilities, requiredDropCapabilities, allowedCaps []api.Capability) (Strategy, error) { - return &defaultCapabilities{ - defaultAddCapabilities: defaultAddCapabilities, - requiredDropCapabilities: requiredDropCapabilities, - allowedCaps: allowedCaps, - }, nil -} - -// Generate creates the capabilities based on policy rules. Generate will produce the following: -// 1. a capabilities.Add set containing all the required adds (unless the -// container specifically is dropping the cap) and container requested adds -// 2. a capabilities.Drop set containing all the required drops and container requested drops -// -// Returns the original container capabilities if no changes are required. -func (s *defaultCapabilities) Generate(pod *api.Pod, container *api.Container) (*api.Capabilities, error) { - defaultAdd := makeCapSet(s.defaultAddCapabilities) - requiredDrop := makeCapSet(s.requiredDropCapabilities) - containerAdd := sets.NewString() - containerDrop := sets.NewString() - - var containerCapabilities *api.Capabilities - if container.SecurityContext != nil && container.SecurityContext.Capabilities != nil { - containerCapabilities = container.SecurityContext.Capabilities - containerAdd = makeCapSet(container.SecurityContext.Capabilities.Add) - containerDrop = makeCapSet(container.SecurityContext.Capabilities.Drop) - } - - // remove any default adds that the container is specifically dropping - defaultAdd = defaultAdd.Difference(containerDrop) - - combinedAdd := defaultAdd.Union(containerAdd) - combinedDrop := requiredDrop.Union(containerDrop) - - // no changes? return the original capabilities - if (len(combinedAdd) == len(containerAdd)) && (len(combinedDrop) == len(containerDrop)) { - return containerCapabilities, nil - } - - return &api.Capabilities{ - Add: capabilityFromStringSlice(combinedAdd.List()), - Drop: capabilityFromStringSlice(combinedDrop.List()), - }, nil -} - -// Validate ensures that the specified values fall within the range of the strategy. -func (s *defaultCapabilities) Validate(pod *api.Pod, container *api.Container, capabilities *api.Capabilities) field.ErrorList { - allErrs := field.ErrorList{} - - if capabilities == nil { - // if container.SC.Caps is nil then nothing was defaulted by the strategy or requested by the pod author - // if there are no required caps on the strategy and nothing is requested on the pod - // then we can safely return here without further validation. - if len(s.defaultAddCapabilities) == 0 && len(s.requiredDropCapabilities) == 0 { - return allErrs - } - - // container has no requested caps but we have required caps. We should have something in - // at least the drops on the container. - allErrs = append(allErrs, field.Invalid(field.NewPath("capabilities"), capabilities, - "required capabilities are not set on the securityContext")) - return allErrs - } - - allowedAdd := makeCapSet(s.allowedCaps) - allowAllCaps := allowedAdd.Has(string(extensions.AllowAllCapabilities)) - if allowAllCaps { - // skip validation against allowed/defaultAdd/requiredDrop because all capabilities are allowed by a wildcard - return allErrs - } - - // validate that anything being added is in the default or allowed sets - defaultAdd := makeCapSet(s.defaultAddCapabilities) - - for _, cap := range capabilities.Add { - sCap := string(cap) - if !defaultAdd.Has(sCap) && !allowedAdd.Has(sCap) { - allErrs = append(allErrs, field.Invalid(field.NewPath("capabilities", "add"), sCap, "capability may not be added")) - } - } - - // validate that anything that is required to be dropped is in the drop set - containerDrops := makeCapSet(capabilities.Drop) - - for _, requiredDrop := range s.requiredDropCapabilities { - sDrop := string(requiredDrop) - if !containerDrops.Has(sDrop) { - allErrs = append(allErrs, field.Invalid(field.NewPath("capabilities", "drop"), capabilities.Drop, - fmt.Sprintf("%s is required to be dropped but was not found", sDrop))) - } - } - - return allErrs -} - -// capabilityFromStringSlice creates a capability slice from a string slice. -func capabilityFromStringSlice(slice []string) []api.Capability { - if len(slice) == 0 { - return nil - } - caps := []api.Capability{} - for _, c := range slice { - caps = append(caps, api.Capability(c)) - } - return caps -} - -// makeCapSet makes a string set from capabilities. -func makeCapSet(caps []api.Capability) sets.String { - s := sets.NewString() - for _, c := range caps { - s.Insert(string(c)) - } - return s -} diff --git a/pkg/security/podsecuritypolicy/capabilities/mustrunas_test.go b/pkg/security/podsecuritypolicy/capabilities/mustrunas_test.go deleted file mode 100644 index 21044e924c7..00000000000 --- a/pkg/security/podsecuritypolicy/capabilities/mustrunas_test.go +++ /dev/null @@ -1,410 +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 capabilities - -import ( - "reflect" - "testing" - - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/extensions" -) - -func TestGenerateAdds(t *testing.T) { - tests := map[string]struct { - defaultAddCaps []api.Capability - containerCaps *api.Capabilities - expectedCaps *api.Capabilities - }{ - "no required, no container requests": {}, - "no required, no container requests, non-nil": { - containerCaps: &api.Capabilities{}, - expectedCaps: &api.Capabilities{}, - }, - "required, no container requests": { - defaultAddCaps: []api.Capability{"foo"}, - expectedCaps: &api.Capabilities{ - Add: []api.Capability{"foo"}, - }, - }, - "required, container requests add required": { - defaultAddCaps: []api.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"foo"}, - }, - expectedCaps: &api.Capabilities{ - Add: []api.Capability{"foo"}, - }, - }, - "multiple required, container requests add required": { - defaultAddCaps: []api.Capability{"foo", "bar", "baz"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"foo"}, - }, - expectedCaps: &api.Capabilities{ - Add: []api.Capability{"bar", "baz", "foo"}, - }, - }, - "required, container requests add non-required": { - defaultAddCaps: []api.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"bar"}, - }, - expectedCaps: &api.Capabilities{ - Add: []api.Capability{"bar", "foo"}, - }, - }, - "generation does not mutate unnecessarily": { - defaultAddCaps: []api.Capability{"foo", "bar"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"foo", "foo", "bar", "baz"}, - }, - expectedCaps: &api.Capabilities{ - Add: []api.Capability{"foo", "foo", "bar", "baz"}, - }, - }, - "generation dedupes": { - defaultAddCaps: []api.Capability{"foo", "bar"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"foo", "baz"}, - }, - expectedCaps: &api.Capabilities{ - Add: []api.Capability{"bar", "baz", "foo"}, - }, - }, - "generation is case sensitive - will not dedupe": { - defaultAddCaps: []api.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"FOO"}, - }, - expectedCaps: &api.Capabilities{ - Add: []api.Capability{"FOO", "foo"}, - }, - }, - } - - for k, v := range tests { - container := &api.Container{ - SecurityContext: &api.SecurityContext{ - Capabilities: v.containerCaps, - }, - } - - strategy, err := NewDefaultCapabilities(v.defaultAddCaps, nil, nil) - if err != nil { - t.Errorf("%s failed: %v", k, err) - continue - } - generatedCaps, err := strategy.Generate(nil, container) - if err != nil { - t.Errorf("%s failed generating: %v", k, err) - continue - } - if v.expectedCaps == nil && generatedCaps != nil { - t.Errorf("%s expected nil caps to be generated but got %v", k, generatedCaps) - continue - } - if !reflect.DeepEqual(v.expectedCaps, generatedCaps) { - t.Errorf("%s did not generate correctly. Expected: %#v, Actual: %#v", k, v.expectedCaps, generatedCaps) - } - } -} - -func TestGenerateDrops(t *testing.T) { - tests := map[string]struct { - defaultAddCaps []api.Capability - requiredDropCaps []api.Capability - containerCaps *api.Capabilities - expectedCaps *api.Capabilities - }{ - "no required, no container requests": { - expectedCaps: nil, - }, - "no required, no container requests, non-nil": { - containerCaps: &api.Capabilities{}, - expectedCaps: &api.Capabilities{}, - }, - "required drops are defaulted": { - requiredDropCaps: []api.Capability{"foo"}, - expectedCaps: &api.Capabilities{ - Drop: []api.Capability{"foo"}, - }, - }, - "required drops are defaulted when making container requests": { - requiredDropCaps: []api.Capability{"baz"}, - containerCaps: &api.Capabilities{ - Drop: []api.Capability{"foo", "bar"}, - }, - expectedCaps: &api.Capabilities{ - Drop: []api.Capability{"bar", "baz", "foo"}, - }, - }, - "required drops do not mutate unnecessarily": { - requiredDropCaps: []api.Capability{"baz"}, - containerCaps: &api.Capabilities{ - Drop: []api.Capability{"foo", "bar", "baz"}, - }, - expectedCaps: &api.Capabilities{ - Drop: []api.Capability{"foo", "bar", "baz"}, - }, - }, - "can drop a required add": { - defaultAddCaps: []api.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Drop: []api.Capability{"foo"}, - }, - expectedCaps: &api.Capabilities{ - Drop: []api.Capability{"foo"}, - }, - }, - "can drop non-required add": { - defaultAddCaps: []api.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Drop: []api.Capability{"bar"}, - }, - expectedCaps: &api.Capabilities{ - Add: []api.Capability{"foo"}, - Drop: []api.Capability{"bar"}, - }, - }, - "defaulting adds and drops, dropping a required add": { - defaultAddCaps: []api.Capability{"foo", "bar", "baz"}, - requiredDropCaps: []api.Capability{"abc"}, - containerCaps: &api.Capabilities{ - Drop: []api.Capability{"foo"}, - }, - expectedCaps: &api.Capabilities{ - Add: []api.Capability{"bar", "baz"}, - Drop: []api.Capability{"abc", "foo"}, - }, - }, - "generation dedupes": { - requiredDropCaps: []api.Capability{"baz", "foo"}, - containerCaps: &api.Capabilities{ - Drop: []api.Capability{"bar", "foo"}, - }, - expectedCaps: &api.Capabilities{ - Drop: []api.Capability{"bar", "baz", "foo"}, - }, - }, - "generation is case sensitive - will not dedupe": { - requiredDropCaps: []api.Capability{"bar"}, - containerCaps: &api.Capabilities{ - Drop: []api.Capability{"BAR"}, - }, - expectedCaps: &api.Capabilities{ - Drop: []api.Capability{"BAR", "bar"}, - }, - }, - } - for k, v := range tests { - container := &api.Container{ - SecurityContext: &api.SecurityContext{ - Capabilities: v.containerCaps, - }, - } - - strategy, err := NewDefaultCapabilities(v.defaultAddCaps, v.requiredDropCaps, nil) - if err != nil { - t.Errorf("%s failed: %v", k, err) - continue - } - generatedCaps, err := strategy.Generate(nil, container) - if err != nil { - t.Errorf("%s failed generating: %v", k, err) - continue - } - if v.expectedCaps == nil && generatedCaps != nil { - t.Errorf("%s expected nil caps to be generated but got %#v", k, generatedCaps) - continue - } - if !reflect.DeepEqual(v.expectedCaps, generatedCaps) { - t.Errorf("%s did not generate correctly. Expected: %#v, Actual: %#v", k, v.expectedCaps, generatedCaps) - } - } -} - -func TestValidateAdds(t *testing.T) { - tests := map[string]struct { - defaultAddCaps []api.Capability - allowedCaps []api.Capability - containerCaps *api.Capabilities - expectedError string - }{ - // no container requests - "no required, no allowed, no container requests": {}, - "no required, allowed, no container requests": { - allowedCaps: []api.Capability{"foo"}, - }, - "required, no allowed, no container requests": { - defaultAddCaps: []api.Capability{"foo"}, - expectedError: `capabilities: Invalid value: "null": required capabilities are not set on the securityContext`, - }, - - // container requests match required - "required, no allowed, container requests valid": { - defaultAddCaps: []api.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"foo"}, - }, - }, - "required, no allowed, container requests invalid": { - defaultAddCaps: []api.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"bar"}, - }, - expectedError: `capabilities.add: Invalid value: "bar": capability may not be added`, - }, - - // container requests match allowed - "no required, allowed, container requests valid": { - allowedCaps: []api.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"foo"}, - }, - }, - "no required, all allowed, container requests valid": { - allowedCaps: []api.Capability{extensions.AllowAllCapabilities}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"foo"}, - }, - }, - "no required, allowed, container requests invalid": { - allowedCaps: []api.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"bar"}, - }, - expectedError: `capabilities.add: Invalid value: "bar": capability may not be added`, - }, - - // required and allowed - "required, allowed, container requests valid required": { - defaultAddCaps: []api.Capability{"foo"}, - allowedCaps: []api.Capability{"bar"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"foo"}, - }, - }, - "required, allowed, container requests valid allowed": { - defaultAddCaps: []api.Capability{"foo"}, - allowedCaps: []api.Capability{"bar"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"bar"}, - }, - }, - "required, allowed, container requests invalid": { - defaultAddCaps: []api.Capability{"foo"}, - allowedCaps: []api.Capability{"bar"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"baz"}, - }, - expectedError: `capabilities.add: Invalid value: "baz": capability may not be added`, - }, - "validation is case sensitive": { - defaultAddCaps: []api.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Add: []api.Capability{"FOO"}, - }, - expectedError: `capabilities.add: Invalid value: "FOO": capability may not be added`, - }, - } - - for k, v := range tests { - strategy, err := NewDefaultCapabilities(v.defaultAddCaps, nil, v.allowedCaps) - if err != nil { - t.Errorf("%s failed: %v", k, err) - continue - } - errs := strategy.Validate(nil, nil, v.containerCaps) - if v.expectedError == "" && len(errs) > 0 { - t.Errorf("%s should have passed but had errors %v", k, errs) - continue - } - if v.expectedError != "" && len(errs) == 0 { - t.Errorf("%s should have failed but received no errors", k) - continue - } - if len(errs) == 1 && errs[0].Error() != v.expectedError { - t.Errorf("%s should have failed with %v but received %v", k, v.expectedError, errs[0]) - continue - } - if len(errs) > 1 { - t.Errorf("%s should have failed with at most one error, but received %v: %v", k, len(errs), errs) - } - } -} - -func TestValidateDrops(t *testing.T) { - tests := map[string]struct { - requiredDropCaps []api.Capability - containerCaps *api.Capabilities - expectedError string - }{ - // no container requests - "no required, no container requests": {}, - "required, no container requests": { - requiredDropCaps: []api.Capability{"foo"}, - expectedError: `capabilities: Invalid value: "null": required capabilities are not set on the securityContext`, - }, - - // container requests match required - "required, container requests valid": { - requiredDropCaps: []api.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Drop: []api.Capability{"foo"}, - }, - }, - "required, container requests invalid": { - requiredDropCaps: []api.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Drop: []api.Capability{"bar"}, - }, - expectedError: `capabilities.drop: Invalid value: []api.Capability{"bar"}: foo is required to be dropped but was not found`, - }, - "validation is case sensitive": { - requiredDropCaps: []api.Capability{"foo"}, - containerCaps: &api.Capabilities{ - Drop: []api.Capability{"FOO"}, - }, - expectedError: `capabilities.drop: Invalid value: []api.Capability{"FOO"}: foo is required to be dropped but was not found`, - }, - } - - for k, v := range tests { - strategy, err := NewDefaultCapabilities(nil, v.requiredDropCaps, nil) - if err != nil { - t.Errorf("%s failed: %v", k, err) - continue - } - errs := strategy.Validate(nil, nil, v.containerCaps) - if v.expectedError == "" && len(errs) > 0 { - t.Errorf("%s should have passed but had errors %v", k, errs) - continue - } - if v.expectedError != "" && len(errs) == 0 { - t.Errorf("%s should have failed but received no errors", k) - continue - } - if len(errs) == 1 && errs[0].Error() != v.expectedError { - t.Errorf("%s should have failed with %v but received %v", k, v.expectedError, errs[0]) - continue - } - if len(errs) > 1 { - t.Errorf("%s should have failed with at most one error, but received %v: %v", k, len(errs), errs) - } - } -} diff --git a/pkg/security/podsecuritypolicy/capabilities/types.go b/pkg/security/podsecuritypolicy/capabilities/types.go index c936d938679..47226fabab8 100644 --- a/pkg/security/podsecuritypolicy/capabilities/types.go +++ b/pkg/security/podsecuritypolicy/capabilities/types.go @@ -18,7 +18,7 @@ package capabilities import ( "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // Strategy defines the interface for all cap constraint strategies. diff --git a/pkg/security/podsecuritypolicy/factory.go b/pkg/security/podsecuritypolicy/factory.go index 0fbd8a714a6..69cf3945d6a 100644 --- a/pkg/security/podsecuritypolicy/factory.go +++ b/pkg/security/podsecuritypolicy/factory.go @@ -20,7 +20,7 @@ import ( "fmt" "k8s.io/apimachinery/pkg/util/errors" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/security/podsecuritypolicy/apparmor" "k8s.io/kubernetes/pkg/security/podsecuritypolicy/capabilities" diff --git a/pkg/security/podsecuritypolicy/group/BUILD b/pkg/security/podsecuritypolicy/group/BUILD index edb5d0eb981..601e7476dee 100644 --- a/pkg/security/podsecuritypolicy/group/BUILD +++ b/pkg/security/podsecuritypolicy/group/BUILD @@ -16,7 +16,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy/group", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/security/podsecuritypolicy/util:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", @@ -29,10 +29,10 @@ go_test( "mustrunas_test.go", "runasany_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy/group", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", ], ) diff --git a/pkg/security/podsecuritypolicy/group/mustrunas.go b/pkg/security/podsecuritypolicy/group/mustrunas.go index b2182afaa3f..8fc48ac294e 100644 --- a/pkg/security/podsecuritypolicy/group/mustrunas.go +++ b/pkg/security/podsecuritypolicy/group/mustrunas.go @@ -20,7 +20,7 @@ import ( "fmt" "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" ) diff --git a/pkg/security/podsecuritypolicy/group/mustrunas_test.go b/pkg/security/podsecuritypolicy/group/mustrunas_test.go index d3075a8862a..395ffd80eb0 100644 --- a/pkg/security/podsecuritypolicy/group/mustrunas_test.go +++ b/pkg/security/podsecuritypolicy/group/mustrunas_test.go @@ -17,10 +17,9 @@ limitations under the License. package group import ( - "testing" - - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" + "testing" ) func TestMustRunAsOptions(t *testing.T) { diff --git a/pkg/security/podsecuritypolicy/group/runasany.go b/pkg/security/podsecuritypolicy/group/runasany.go index 071f2e08ecd..3890f849919 100644 --- a/pkg/security/podsecuritypolicy/group/runasany.go +++ b/pkg/security/podsecuritypolicy/group/runasany.go @@ -18,7 +18,7 @@ package group import ( "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // runAsAny implements the GroupStrategy interface. diff --git a/pkg/security/podsecuritypolicy/group/types.go b/pkg/security/podsecuritypolicy/group/types.go index fa3a11d97d3..dbf1520421c 100644 --- a/pkg/security/podsecuritypolicy/group/types.go +++ b/pkg/security/podsecuritypolicy/group/types.go @@ -18,7 +18,7 @@ package group import ( "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // GroupStrategy defines the interface for all group constraint strategies. diff --git a/pkg/security/podsecuritypolicy/provider.go b/pkg/security/podsecuritypolicy/provider.go index fadacec1dd9..cc5132654b4 100644 --- a/pkg/security/podsecuritypolicy/provider.go +++ b/pkg/security/podsecuritypolicy/provider.go @@ -21,11 +21,10 @@ import ( "strings" "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" "k8s.io/kubernetes/pkg/securitycontext" - "k8s.io/kubernetes/pkg/util/maps" ) // used to pass in the field being validated for reusable group strategies so they @@ -64,17 +63,16 @@ func NewSimpleProvider(psp *extensions.PodSecurityPolicy, namespace string, stra }, nil } -// Create a PodSecurityContext based on the given constraints. If a setting is already set -// on the PodSecurityContext it will not be changed. Validate should be used after the context -// is created to ensure it complies with the required restrictions. -func (s *simpleProvider) CreatePodSecurityContext(pod *api.Pod) (*api.PodSecurityContext, map[string]string, error) { +// DefaultPodSecurityContext sets the default values of the required but not filled fields. +// It modifies the SecurityContext and annotations of the provided pod. Validation should be +// used after the context is defaulted to ensure it complies with the required restrictions. +func (s *simpleProvider) DefaultPodSecurityContext(pod *api.Pod) error { sc := securitycontext.NewPodSecurityContextMutator(pod.Spec.SecurityContext) - annotations := maps.CopySS(pod.Annotations) if sc.SupplementalGroups() == nil { supGroups, err := s.strategies.SupplementalGroupStrategy.Generate(pod) if err != nil { - return nil, nil, err + return err } sc.SetSupplementalGroups(supGroups) } @@ -82,7 +80,7 @@ func (s *simpleProvider) CreatePodSecurityContext(pod *api.Pod) (*api.PodSecurit if sc.FSGroup() == nil { fsGroup, err := s.strategies.FSGroupStrategy.GenerateSingle(pod) if err != nil { - return nil, nil, err + return err } sc.SetFSGroup(fsGroup) } @@ -90,41 +88,42 @@ func (s *simpleProvider) CreatePodSecurityContext(pod *api.Pod) (*api.PodSecurit if sc.SELinuxOptions() == nil { seLinux, err := s.strategies.SELinuxStrategy.Generate(pod, nil) if err != nil { - return nil, nil, err + return err } sc.SetSELinuxOptions(seLinux) } // This is only generated on the pod level. Containers inherit the pod's profile. If the // container has a specific profile set then it will be caught in the validation step. - seccompProfile, err := s.strategies.SeccompStrategy.Generate(annotations, pod) + seccompProfile, err := s.strategies.SeccompStrategy.Generate(pod.Annotations, pod) if err != nil { - return nil, nil, err + return err } if seccompProfile != "" { - if annotations == nil { - annotations = map[string]string{} + if pod.Annotations == nil { + pod.Annotations = map[string]string{} } - annotations[api.SeccompPodAnnotationKey] = seccompProfile + pod.Annotations[api.SeccompPodAnnotationKey] = seccompProfile } - return sc.PodSecurityContext(), annotations, nil + + pod.Spec.SecurityContext = sc.PodSecurityContext() + + return nil } -// Create a SecurityContext based on the given constraints. If a setting is already set on the -// container's security context then it will not be changed. Validation should be used after -// the context is created to ensure it complies with the required restrictions. -func (s *simpleProvider) CreateContainerSecurityContext(pod *api.Pod, container *api.Container) (*api.SecurityContext, map[string]string, error) { +// DefaultContainerSecurityContext sets the default values of the required but not filled fields. +// It modifies the SecurityContext of the container and annotations of the pod. Validation should +// be used after the context is defaulted to ensure it complies with the required restrictions. +func (s *simpleProvider) DefaultContainerSecurityContext(pod *api.Pod, container *api.Container) error { sc := securitycontext.NewEffectiveContainerSecurityContextMutator( securitycontext.NewPodSecurityContextAccessor(pod.Spec.SecurityContext), securitycontext.NewContainerSecurityContextMutator(container.SecurityContext), ) - annotations := maps.CopySS(pod.Annotations) - if sc.RunAsUser() == nil { uid, err := s.strategies.RunAsUserStrategy.Generate(pod, container) if err != nil { - return nil, nil, err + return err } sc.SetRunAsUser(uid) } @@ -132,14 +131,14 @@ func (s *simpleProvider) CreateContainerSecurityContext(pod *api.Pod, container if sc.SELinuxOptions() == nil { seLinux, err := s.strategies.SELinuxStrategy.Generate(pod, container) if err != nil { - return nil, nil, err + return err } sc.SetSELinuxOptions(seLinux) } - annotations, err := s.strategies.AppArmorStrategy.Generate(annotations, container) + annotations, err := s.strategies.AppArmorStrategy.Generate(pod.Annotations, container) if err != nil { - return nil, nil, err + return err } // if we're using the non-root strategy set the marker that this container should not be @@ -152,7 +151,7 @@ func (s *simpleProvider) CreateContainerSecurityContext(pod *api.Pod, container caps, err := s.strategies.CapabilitiesStrategy.Generate(pod, container) if err != nil { - return nil, nil, err + return err } sc.SetCapabilities(caps) @@ -174,7 +173,10 @@ func (s *simpleProvider) CreateContainerSecurityContext(pod *api.Pod, container sc.SetAllowPrivilegeEscalation(&s.psp.Spec.AllowPrivilegeEscalation) } - return sc.ContainerSecurityContext(), annotations, nil + pod.Annotations = annotations + container.SecurityContext = sc.ContainerSecurityContext() + + return nil } // Ensure a pod's SecurityContext is in compliance with the given constraints. @@ -233,9 +235,24 @@ func (s *simpleProvider) ValidatePodSecurityContext(pod *api.Pod, fldPath *field fmt.Sprintf("is not allowed to be used"))) } } + + if fsType == extensions.FlexVolume && len(s.psp.Spec.AllowedFlexVolumes) > 0 { + found := false + driver := v.FlexVolume.Driver + for _, allowedFlexVolume := range s.psp.Spec.AllowedFlexVolumes { + if driver == allowedFlexVolume.Driver { + found = true + break + } + } + if !found { + allErrs = append(allErrs, + field.Invalid(fldPath.Child("volumes").Index(i).Child("driver"), driver, + "Flexvolume driver is not allowed to be used")) + } + } } } - return allErrs } @@ -307,7 +324,7 @@ func (s *simpleProvider) ValidateContainerSecurityContext(pod *api.Pod, containe func (s *simpleProvider) hasInvalidHostPort(container *api.Container, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} for _, cp := range container.Ports { - if cp.HostPort > 0 && !s.isValidHostPort(int(cp.HostPort)) { + if cp.HostPort > 0 && !s.isValidHostPort(cp.HostPort) { detail := fmt.Sprintf("Host port %d is not allowed to be used. Allowed ports: [%s]", cp.HostPort, hostPortRangesToString(s.psp.Spec.HostPorts)) allErrs = append(allErrs, field.Invalid(fldPath.Child("hostPort"), cp.HostPort, detail)) } @@ -316,7 +333,7 @@ func (s *simpleProvider) hasInvalidHostPort(container *api.Container, fldPath *f } // isValidHostPort returns true if the port falls in any range allowed by the PSP. -func (s *simpleProvider) isValidHostPort(port int) bool { +func (s *simpleProvider) isValidHostPort(port int32) bool { for _, hostPortRange := range s.psp.Spec.HostPorts { if port >= hostPortRange.Min && port <= hostPortRange.Max { return true diff --git a/pkg/security/podsecuritypolicy/provider_test.go b/pkg/security/podsecuritypolicy/provider_test.go index cfba7786c67..5d05b19c41d 100644 --- a/pkg/security/podsecuritypolicy/provider_test.go +++ b/pkg/security/podsecuritypolicy/provider_test.go @@ -28,8 +28,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" - k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" + api "k8s.io/kubernetes/pkg/apis/core" + k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/security/apparmor" "k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp" @@ -38,7 +38,7 @@ import ( const defaultContainerName = "test-c" -func TestCreatePodSecurityContextNonmutating(t *testing.T) { +func TestDefaultPodSecurityContextNonmutating(t *testing.T) { // Create a pod with a security context that needs filling in createPod := func() *api.Pod { return &api.Pod{ @@ -82,7 +82,7 @@ func TestCreatePodSecurityContextNonmutating(t *testing.T) { if err != nil { t.Fatalf("unable to create provider %v", err) } - _, _, err = provider.CreatePodSecurityContext(pod) + err = provider.DefaultPodSecurityContext(pod) if err != nil { t.Fatalf("unable to create psc %v", err) } @@ -91,14 +91,14 @@ func TestCreatePodSecurityContextNonmutating(t *testing.T) { // since all the strategies were permissive if !reflect.DeepEqual(createPod(), pod) { diffs := diff.ObjectDiff(createPod(), pod) - t.Errorf("pod was mutated by CreatePodSecurityContext. diff:\n%s", diffs) + t.Errorf("pod was mutated by DefaultPodSecurityContext. diff:\n%s", diffs) } if !reflect.DeepEqual(createPSP(), psp) { - t.Error("psp was mutated by CreatePodSecurityContext") + t.Error("psp was mutated by DefaultPodSecurityContext") } } -func TestCreateContainerSecurityContextNonmutating(t *testing.T) { +func TestDefaultContainerSecurityContextNonmutating(t *testing.T) { untrue := false tests := []struct { security *api.SecurityContext @@ -154,7 +154,7 @@ func TestCreateContainerSecurityContextNonmutating(t *testing.T) { if err != nil { t.Fatalf("unable to create provider %v", err) } - _, _, err = provider.CreateContainerSecurityContext(pod, &pod.Spec.Containers[0]) + err = provider.DefaultContainerSecurityContext(pod, &pod.Spec.Containers[0]) if err != nil { t.Fatalf("unable to create container security context %v", err) } @@ -163,10 +163,10 @@ func TestCreateContainerSecurityContextNonmutating(t *testing.T) { // since all the strategies were permissive if !reflect.DeepEqual(createPod(), pod) { diffs := diff.ObjectDiff(createPod(), pod) - t.Errorf("pod was mutated by CreateContainerSecurityContext. diff:\n%s", diffs) + t.Errorf("pod was mutated by DefaultContainerSecurityContext. diff:\n%s", diffs) } if !reflect.DeepEqual(createPSP(), psp) { - t.Error("psp was mutated by CreateContainerSecurityContext") + t.Error("psp was mutated by DefaultContainerSecurityContext") } } } @@ -256,6 +256,18 @@ func TestValidatePodSecurityContextFailures(t *testing.T) { failSeccompProfilePod := defaultPod() failSeccompProfilePod.Annotations = map[string]string{api.SeccompPodAnnotationKey: "foo"} + podWithInvalidFlexVolumeDriver := defaultPod() + podWithInvalidFlexVolumeDriver.Spec.Volumes = []api.Volume{ + { + Name: "flex-volume", + VolumeSource: api.VolumeSource{ + FlexVolume: &api.FlexVolumeSource{ + Driver: "example/unknown", + }, + }, + }, + } + errorCases := map[string]struct { pod *api.Pod psp *extensions.PodSecurityPolicy @@ -341,6 +353,16 @@ func TestValidatePodSecurityContextFailures(t *testing.T) { psp: defaultPSP(), expectedError: "Forbidden: seccomp may not be set", }, + "fail pod with disallowed flexVolume when flex volumes are allowed": { + pod: podWithInvalidFlexVolumeDriver, + psp: allowFlexVolumesPSP(false, false), + expectedError: "Flexvolume driver is not allowed to be used", + }, + "fail pod with disallowed flexVolume when all volumes are allowed": { + pod: podWithInvalidFlexVolumeDriver, + psp: allowFlexVolumesPSP(false, true), + expectedError: "Flexvolume driver is not allowed to be used", + }, } for k, v := range errorCases { provider, err := NewSimpleProvider(v.psp, "namespace", NewSimpleStrategyFactory()) @@ -358,6 +380,28 @@ func TestValidatePodSecurityContextFailures(t *testing.T) { } } +func allowFlexVolumesPSP(allowAllFlexVolumes, allowAllVolumes bool) *extensions.PodSecurityPolicy { + psp := defaultPSP() + + allowedVolumes := []extensions.AllowedFlexVolume{ + {Driver: "example/foo"}, + {Driver: "example/bar"}, + } + if allowAllFlexVolumes { + allowedVolumes = []extensions.AllowedFlexVolume{} + } + + allowedVolumeType := extensions.FlexVolume + if allowAllVolumes { + allowedVolumeType = extensions.All + } + + psp.Spec.AllowedFlexVolumes = allowedVolumes + psp.Spec.Volumes = []extensions.FSType{allowedVolumeType} + + return psp +} + func TestValidateContainerSecurityContextFailures(t *testing.T) { // fail user strat failUserPSP := defaultPSP() @@ -387,7 +431,7 @@ func TestValidateContainerSecurityContextFailures(t *testing.T) { v1FailInvalidAppArmorPod := defaultV1Pod() apparmor.SetProfileName(v1FailInvalidAppArmorPod, defaultContainerName, apparmor.ProfileNamePrefix+"foo") failInvalidAppArmorPod := &api.Pod{} - k8s_api_v1.Convert_v1_Pod_To_api_Pod(v1FailInvalidAppArmorPod, failInvalidAppArmorPod, nil) + k8s_api_v1.Convert_v1_Pod_To_core_Pod(v1FailInvalidAppArmorPod, failInvalidAppArmorPod, nil) failAppArmorPSP := defaultPSP() failAppArmorPSP.Annotations = map[string]string{ @@ -597,6 +641,18 @@ func TestValidatePodSecurityContextSuccess(t *testing.T) { api.SeccompPodAnnotationKey: "foo", } + flexVolumePod := defaultPod() + flexVolumePod.Spec.Volumes = []api.Volume{ + { + Name: "flex-volume", + VolumeSource: api.VolumeSource{ + FlexVolume: &api.FlexVolumeSource{ + Driver: "example/bar", + }, + }, + }, + } + successCases := map[string]struct { pod *api.Pod psp *extensions.PodSecurityPolicy @@ -653,6 +709,22 @@ func TestValidatePodSecurityContextSuccess(t *testing.T) { pod: seccompPod, psp: seccompPSP, }, + "flex volume driver in a whitelist (all volumes are allowed)": { + pod: flexVolumePod, + psp: allowFlexVolumesPSP(false, true), + }, + "flex volume driver with empty whitelist (all volumes are allowed)": { + pod: flexVolumePod, + psp: allowFlexVolumesPSP(true, true), + }, + "flex volume driver in a whitelist (only flex volumes are allowed)": { + pod: flexVolumePod, + psp: allowFlexVolumesPSP(false, false), + }, + "flex volume driver with empty whitelist (only flex volumes volumes are allowed)": { + pod: flexVolumePod, + psp: allowFlexVolumesPSP(true, false), + }, } for k, v := range successCases { @@ -699,7 +771,7 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) { v1AppArmorPod := defaultV1Pod() apparmor.SetProfileName(v1AppArmorPod, defaultContainerName, apparmor.ProfileRuntimeDefault) appArmorPod := &api.Pod{} - k8s_api_v1.Convert_v1_Pod_To_api_Pod(v1AppArmorPod, appArmorPod, nil) + k8s_api_v1.Convert_v1_Pod_To_core_Pod(v1AppArmorPod, appArmorPod, nil) privPSP := defaultPSP() privPSP.Spec.Privileged = true @@ -893,12 +965,13 @@ func TestGenerateContainerSecurityContextReadOnlyRootFS(t *testing.T) { t.Errorf("%s unable to create provider %v", k, err) continue } - sc, _, err := provider.CreateContainerSecurityContext(v.pod, &v.pod.Spec.Containers[0]) + err = provider.DefaultContainerSecurityContext(v.pod, &v.pod.Spec.Containers[0]) if err != nil { t.Errorf("%s unable to create container security context %v", k, err) continue } + sc := v.pod.Spec.Containers[0].SecurityContext if v.expected == nil && sc.ReadOnlyRootFilesystem != nil { t.Errorf("%s expected a nil ReadOnlyRootFilesystem but got %t", k, *sc.ReadOnlyRootFilesystem) } diff --git a/pkg/security/podsecuritypolicy/seccomp/BUILD b/pkg/security/podsecuritypolicy/seccomp/BUILD index fa673f075ae..995943e4041 100644 --- a/pkg/security/podsecuritypolicy/seccomp/BUILD +++ b/pkg/security/podsecuritypolicy/seccomp/BUILD @@ -11,7 +11,7 @@ go_library( srcs = ["strategy.go"], importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", ], ) @@ -19,10 +19,10 @@ go_library( go_test( name = "go_default_test", srcs = ["strategy_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", ], ) diff --git a/pkg/security/podsecuritypolicy/seccomp/strategy.go b/pkg/security/podsecuritypolicy/seccomp/strategy.go index aaeb0c1f363..715156e8a01 100644 --- a/pkg/security/podsecuritypolicy/seccomp/strategy.go +++ b/pkg/security/podsecuritypolicy/seccomp/strategy.go @@ -21,7 +21,7 @@ import ( "strings" "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) const ( diff --git a/pkg/security/podsecuritypolicy/seccomp/strategy_test.go b/pkg/security/podsecuritypolicy/seccomp/strategy_test.go index 663900ecf27..c79602c2252 100644 --- a/pkg/security/podsecuritypolicy/seccomp/strategy_test.go +++ b/pkg/security/podsecuritypolicy/seccomp/strategy_test.go @@ -22,7 +22,7 @@ import ( "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) var ( diff --git a/pkg/security/podsecuritypolicy/selinux/BUILD b/pkg/security/podsecuritypolicy/selinux/BUILD index 9319a36cc8f..628436d8163 100644 --- a/pkg/security/podsecuritypolicy/selinux/BUILD +++ b/pkg/security/podsecuritypolicy/selinux/BUILD @@ -16,7 +16,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy/selinux", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", ], @@ -28,10 +28,10 @@ go_test( "mustrunas_test.go", "runasany_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy/selinux", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", ], ) diff --git a/pkg/security/podsecuritypolicy/selinux/mustrunas.go b/pkg/security/podsecuritypolicy/selinux/mustrunas.go index 4f13272bda1..b2605cd3304 100644 --- a/pkg/security/podsecuritypolicy/selinux/mustrunas.go +++ b/pkg/security/podsecuritypolicy/selinux/mustrunas.go @@ -20,7 +20,7 @@ import ( "fmt" "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" ) diff --git a/pkg/security/podsecuritypolicy/selinux/mustrunas_test.go b/pkg/security/podsecuritypolicy/selinux/mustrunas_test.go index bd57b8a9892..986fa8adb77 100644 --- a/pkg/security/podsecuritypolicy/selinux/mustrunas_test.go +++ b/pkg/security/podsecuritypolicy/selinux/mustrunas_test.go @@ -17,12 +17,11 @@ limitations under the License. package selinux import ( + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/extensions" "reflect" "strings" "testing" - - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/extensions" ) func TestMustRunAsOptions(t *testing.T) { diff --git a/pkg/security/podsecuritypolicy/selinux/runasany.go b/pkg/security/podsecuritypolicy/selinux/runasany.go index 62fd9083b12..008ad0a41d5 100644 --- a/pkg/security/podsecuritypolicy/selinux/runasany.go +++ b/pkg/security/podsecuritypolicy/selinux/runasany.go @@ -18,7 +18,7 @@ package selinux import ( "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" ) diff --git a/pkg/security/podsecuritypolicy/selinux/runasany_test.go b/pkg/security/podsecuritypolicy/selinux/runasany_test.go index d31550034b6..8e1a7f1b07d 100644 --- a/pkg/security/podsecuritypolicy/selinux/runasany_test.go +++ b/pkg/security/podsecuritypolicy/selinux/runasany_test.go @@ -17,10 +17,9 @@ limitations under the License. package selinux import ( - "testing" - - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" + "testing" ) func TestRunAsAnyOptions(t *testing.T) { diff --git a/pkg/security/podsecuritypolicy/selinux/types.go b/pkg/security/podsecuritypolicy/selinux/types.go index 8f312e64cbc..cdaae809311 100644 --- a/pkg/security/podsecuritypolicy/selinux/types.go +++ b/pkg/security/podsecuritypolicy/selinux/types.go @@ -18,7 +18,7 @@ package selinux import ( "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // SELinuxStrategy defines the interface for all SELinux constraint strategies. diff --git a/pkg/security/podsecuritypolicy/sysctl/BUILD b/pkg/security/podsecuritypolicy/sysctl/BUILD index 20aed789a41..63001584a90 100644 --- a/pkg/security/podsecuritypolicy/sysctl/BUILD +++ b/pkg/security/podsecuritypolicy/sysctl/BUILD @@ -14,8 +14,8 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy/sysctl", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", ], ) @@ -23,11 +23,11 @@ go_library( go_test( name = "go_default_test", srcs = ["mustmatchpatterns_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy/sysctl", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", ], ) diff --git a/pkg/security/podsecuritypolicy/sysctl/mustmatchpatterns.go b/pkg/security/podsecuritypolicy/sysctl/mustmatchpatterns.go index 969993450af..b59dd9d3fff 100644 --- a/pkg/security/podsecuritypolicy/sysctl/mustmatchpatterns.go +++ b/pkg/security/podsecuritypolicy/sysctl/mustmatchpatterns.go @@ -21,8 +21,8 @@ import ( "strings" "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" ) // mustMatchPatterns implements the SysctlsStrategy interface diff --git a/pkg/security/podsecuritypolicy/sysctl/mustmatchpatterns_test.go b/pkg/security/podsecuritypolicy/sysctl/mustmatchpatterns_test.go index f8aef3df102..7622c826695 100644 --- a/pkg/security/podsecuritypolicy/sysctl/mustmatchpatterns_test.go +++ b/pkg/security/podsecuritypolicy/sysctl/mustmatchpatterns_test.go @@ -17,10 +17,9 @@ limitations under the License. package sysctl import ( + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" "testing" - - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper" ) func TestValidate(t *testing.T) { diff --git a/pkg/security/podsecuritypolicy/sysctl/types.go b/pkg/security/podsecuritypolicy/sysctl/types.go index ee9291bfb76..a6c2034a8d4 100644 --- a/pkg/security/podsecuritypolicy/sysctl/types.go +++ b/pkg/security/podsecuritypolicy/sysctl/types.go @@ -18,7 +18,7 @@ package sysctl import ( "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // SysctlsStrategy defines the interface for all sysctl strategies. diff --git a/pkg/security/podsecuritypolicy/types.go b/pkg/security/podsecuritypolicy/types.go index d067a0325bf..1cb7b025b43 100644 --- a/pkg/security/podsecuritypolicy/types.go +++ b/pkg/security/podsecuritypolicy/types.go @@ -18,7 +18,7 @@ package podsecuritypolicy import ( "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/security/podsecuritypolicy/apparmor" "k8s.io/kubernetes/pkg/security/podsecuritypolicy/capabilities" @@ -32,12 +32,12 @@ import ( // Provider provides the implementation to generate a new security // context based on constraints or validate an existing security context against constraints. type Provider interface { - // Create a PodSecurityContext based on the given constraints. Also returns an updated set - // of Pod annotations for alpha feature support. - CreatePodSecurityContext(pod *api.Pod) (*api.PodSecurityContext, map[string]string, error) - // Create a container SecurityContext based on the given constraints. Also returns an updated set - // of Pod annotations for alpha feature support. - CreateContainerSecurityContext(pod *api.Pod, container *api.Container) (*api.SecurityContext, map[string]string, error) + // DefaultPodSecurityContext sets the default values of the required but not filled fields. + // It modifies the SecurityContext and annotations of the provided pod. + DefaultPodSecurityContext(pod *api.Pod) error + // DefaultContainerSecurityContext sets the default values of the required but not filled fields. + // It modifies the SecurityContext of the container and annotations of the pod. + DefaultContainerSecurityContext(pod *api.Pod, container *api.Container) error // Ensure a pod's SecurityContext is in compliance with the given constraints. ValidatePodSecurityContext(pod *api.Pod, fldPath *field.Path) field.ErrorList // Ensure a container's SecurityContext is in compliance with the given constraints diff --git a/pkg/security/podsecuritypolicy/user/BUILD b/pkg/security/podsecuritypolicy/user/BUILD index 41b0223ab4b..f6c2e123548 100644 --- a/pkg/security/podsecuritypolicy/user/BUILD +++ b/pkg/security/podsecuritypolicy/user/BUILD @@ -17,7 +17,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy/user", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/security/podsecuritypolicy/util:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", @@ -31,10 +31,10 @@ go_test( "nonroot_test.go", "runasany_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy/user", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", ], ) diff --git a/pkg/security/podsecuritypolicy/user/mustrunas.go b/pkg/security/podsecuritypolicy/user/mustrunas.go index df891633571..6b0ed1397d5 100644 --- a/pkg/security/podsecuritypolicy/user/mustrunas.go +++ b/pkg/security/podsecuritypolicy/user/mustrunas.go @@ -20,7 +20,7 @@ import ( "fmt" "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" ) diff --git a/pkg/security/podsecuritypolicy/user/mustrunas_test.go b/pkg/security/podsecuritypolicy/user/mustrunas_test.go index 9121882063e..5a0a7703700 100644 --- a/pkg/security/podsecuritypolicy/user/mustrunas_test.go +++ b/pkg/security/podsecuritypolicy/user/mustrunas_test.go @@ -17,11 +17,10 @@ limitations under the License. package user import ( + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/extensions" "strings" "testing" - - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/extensions" ) func TestNewMustRunAs(t *testing.T) { diff --git a/pkg/security/podsecuritypolicy/user/nonroot.go b/pkg/security/podsecuritypolicy/user/nonroot.go index 2a9624fc0b9..68e644a7e32 100644 --- a/pkg/security/podsecuritypolicy/user/nonroot.go +++ b/pkg/security/podsecuritypolicy/user/nonroot.go @@ -18,7 +18,7 @@ package user import ( "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" ) diff --git a/pkg/security/podsecuritypolicy/user/nonroot_test.go b/pkg/security/podsecuritypolicy/user/nonroot_test.go index 7822ccf126f..1597af00832 100644 --- a/pkg/security/podsecuritypolicy/user/nonroot_test.go +++ b/pkg/security/podsecuritypolicy/user/nonroot_test.go @@ -17,10 +17,9 @@ limitations under the License. package user import ( - "testing" - - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" + "testing" ) func TestNonRootOptions(t *testing.T) { diff --git a/pkg/security/podsecuritypolicy/user/runasany.go b/pkg/security/podsecuritypolicy/user/runasany.go index 729201bf645..e1384f2da71 100644 --- a/pkg/security/podsecuritypolicy/user/runasany.go +++ b/pkg/security/podsecuritypolicy/user/runasany.go @@ -18,7 +18,7 @@ package user import ( "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" ) diff --git a/pkg/security/podsecuritypolicy/user/types.go b/pkg/security/podsecuritypolicy/user/types.go index 8df0c766d5c..fbcc34c79ab 100644 --- a/pkg/security/podsecuritypolicy/user/types.go +++ b/pkg/security/podsecuritypolicy/user/types.go @@ -18,7 +18,7 @@ package user import ( "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // RunAsUserStrategy defines the interface for all uid constraint strategies. diff --git a/pkg/security/podsecuritypolicy/util/BUILD b/pkg/security/podsecuritypolicy/util/BUILD index 03652e680a3..5515c016352 100644 --- a/pkg/security/podsecuritypolicy/util/BUILD +++ b/pkg/security/podsecuritypolicy/util/BUILD @@ -14,7 +14,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", ], @@ -23,10 +23,10 @@ go_library( go_test( name = "go_default_test", srcs = ["util_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", ], ) diff --git a/pkg/security/podsecuritypolicy/util/util.go b/pkg/security/podsecuritypolicy/util/util.go index 3ff6d89bc77..d654f88c77f 100644 --- a/pkg/security/podsecuritypolicy/util/util.go +++ b/pkg/security/podsecuritypolicy/util/util.go @@ -21,7 +21,7 @@ import ( "strings" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" ) @@ -67,6 +67,7 @@ func GetAllFSTypesAsSet() sets.String { string(extensions.Projected), string(extensions.PortworxVolume), string(extensions.ScaleIO), + string(extensions.CSI), ) return fstypes } diff --git a/pkg/security/podsecuritypolicy/util/util_test.go b/pkg/security/podsecuritypolicy/util/util_test.go index 048e793fa8f..f4910a4970c 100644 --- a/pkg/security/podsecuritypolicy/util/util_test.go +++ b/pkg/security/podsecuritypolicy/util/util_test.go @@ -17,11 +17,10 @@ limitations under the License. package util import ( + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/extensions" "reflect" "testing" - - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/extensions" ) // TestVolumeSourceFSTypeDrift ensures that for every known type of volume source (by the fields on diff --git a/pkg/securitycontext/BUILD b/pkg/securitycontext/BUILD index 10611402801..50d0e5eda41 100644 --- a/pkg/securitycontext/BUILD +++ b/pkg/securitycontext/BUILD @@ -16,7 +16,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/securitycontext", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", ], ) @@ -27,10 +27,10 @@ go_test( "accessors_test.go", "util_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/securitycontext", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", ], diff --git a/pkg/securitycontext/accessors.go b/pkg/securitycontext/accessors.go index f7abb22a25c..98ac6e0b92a 100644 --- a/pkg/securitycontext/accessors.go +++ b/pkg/securitycontext/accessors.go @@ -19,7 +19,7 @@ package securitycontext import ( "reflect" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // PodSecurityContextAccessor allows reading the values of a PodSecurityContext object diff --git a/pkg/securitycontext/accessors_test.go b/pkg/securitycontext/accessors_test.go index 7f6e73cd0eb..68b129836a8 100644 --- a/pkg/securitycontext/accessors_test.go +++ b/pkg/securitycontext/accessors_test.go @@ -21,7 +21,7 @@ import ( "testing" "k8s.io/apimachinery/pkg/util/diff" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestPodSecurityContextAccessor(t *testing.T) { diff --git a/pkg/securitycontext/fake.go b/pkg/securitycontext/fake.go index 2c2d0d22d45..975445bab06 100644 --- a/pkg/securitycontext/fake.go +++ b/pkg/securitycontext/fake.go @@ -18,7 +18,7 @@ package securitycontext import ( "k8s.io/api/core/v1" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // ValidSecurityContextWithContainerDefaults creates a valid security context provider based on diff --git a/pkg/securitycontext/util.go b/pkg/securitycontext/util.go index dee1564d062..2719e118269 100644 --- a/pkg/securitycontext/util.go +++ b/pkg/securitycontext/util.go @@ -21,7 +21,6 @@ import ( "strings" "k8s.io/api/core/v1" - "k8s.io/kubernetes/pkg/api" ) // HasPrivilegedRequest returns the value of SecurityContext.Privileged, taking into account @@ -165,83 +164,6 @@ func securityContextFromPodSecurityContext(pod *v1.Pod) *v1.SecurityContext { return synthesized } -// TODO: remove the duplicate code -func InternalDetermineEffectiveSecurityContext(pod *api.Pod, container *api.Container) *api.SecurityContext { - effectiveSc := internalSecurityContextFromPodSecurityContext(pod) - containerSc := container.SecurityContext - - if effectiveSc == nil && containerSc == nil { - return nil - } - if effectiveSc != nil && containerSc == nil { - return effectiveSc - } - if effectiveSc == nil && containerSc != nil { - return containerSc - } - - if containerSc.SELinuxOptions != nil { - effectiveSc.SELinuxOptions = new(api.SELinuxOptions) - *effectiveSc.SELinuxOptions = *containerSc.SELinuxOptions - } - - if containerSc.Capabilities != nil { - effectiveSc.Capabilities = new(api.Capabilities) - *effectiveSc.Capabilities = *containerSc.Capabilities - } - - if containerSc.Privileged != nil { - effectiveSc.Privileged = new(bool) - *effectiveSc.Privileged = *containerSc.Privileged - } - - if containerSc.RunAsUser != nil { - effectiveSc.RunAsUser = new(int64) - *effectiveSc.RunAsUser = *containerSc.RunAsUser - } - - if containerSc.RunAsNonRoot != nil { - effectiveSc.RunAsNonRoot = new(bool) - *effectiveSc.RunAsNonRoot = *containerSc.RunAsNonRoot - } - - if containerSc.ReadOnlyRootFilesystem != nil { - effectiveSc.ReadOnlyRootFilesystem = new(bool) - *effectiveSc.ReadOnlyRootFilesystem = *containerSc.ReadOnlyRootFilesystem - } - - if containerSc.AllowPrivilegeEscalation != nil { - effectiveSc.AllowPrivilegeEscalation = new(bool) - *effectiveSc.AllowPrivilegeEscalation = *containerSc.AllowPrivilegeEscalation - } - - return effectiveSc -} - -func internalSecurityContextFromPodSecurityContext(pod *api.Pod) *api.SecurityContext { - if pod.Spec.SecurityContext == nil { - return nil - } - - synthesized := &api.SecurityContext{} - - if pod.Spec.SecurityContext.SELinuxOptions != nil { - synthesized.SELinuxOptions = &api.SELinuxOptions{} - *synthesized.SELinuxOptions = *pod.Spec.SecurityContext.SELinuxOptions - } - if pod.Spec.SecurityContext.RunAsUser != nil { - synthesized.RunAsUser = new(int64) - *synthesized.RunAsUser = *pod.Spec.SecurityContext.RunAsUser - } - - if pod.Spec.SecurityContext.RunAsNonRoot != nil { - synthesized.RunAsNonRoot = new(bool) - *synthesized.RunAsNonRoot = *pod.Spec.SecurityContext.RunAsNonRoot - } - - return synthesized -} - // AddNoNewPrivileges returns if we should add the no_new_privs option. func AddNoNewPrivileges(sc *v1.SecurityContext) bool { if sc == nil { diff --git a/pkg/serviceaccount/BUILD b/pkg/serviceaccount/BUILD index 7300dc6ce93..afafa2849d9 100644 --- a/pkg/serviceaccount/BUILD +++ b/pkg/serviceaccount/BUILD @@ -14,7 +14,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/serviceaccount", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/github.com/dgrijalva/jwt-go:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/serviceaccount/jwt.go b/pkg/serviceaccount/jwt.go index a7051be3077..fc380e33a87 100644 --- a/pkg/serviceaccount/jwt.go +++ b/pkg/serviceaccount/jwt.go @@ -194,7 +194,7 @@ func (j *jwtTokenAuthenticator) AuthenticateToken(token string) (user.Info, bool return nil, false, errors.New("namespace claim is missing") } secretName, _ := claims[SecretNameClaim].(string) - if len(namespace) == 0 { + if len(secretName) == 0 { return nil, false, errors.New("secretName claim is missing") } serviceAccountName, _ := claims[ServiceAccountNameClaim].(string) diff --git a/pkg/serviceaccount/util.go b/pkg/serviceaccount/util.go index df4dc3a9140..0503c1513e3 100644 --- a/pkg/serviceaccount/util.go +++ b/pkg/serviceaccount/util.go @@ -20,7 +20,7 @@ import ( "k8s.io/api/core/v1" apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount" "k8s.io/apiserver/pkg/authentication/user" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // UserInfo returns a user.Info interface for the given namespace, service account name and UID diff --git a/pkg/ssh/BUILD b/pkg/ssh/BUILD index 28794b702c4..d1b42b6b4f5 100644 --- a/pkg/ssh/BUILD +++ b/pkg/ssh/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["ssh_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/ssh", - library = ":go_default_library", deps = [ "//vendor/github.com/golang/glog:go_default_library", "//vendor/golang.org/x/crypto/ssh:go_default_library", diff --git a/pkg/util/BUILD b/pkg/util/BUILD index a206466463d..362af715259 100644 --- a/pkg/util/BUILD +++ b/pkg/util/BUILD @@ -27,6 +27,7 @@ filegroup( "//pkg/util/interrupt:all-srcs", "//pkg/util/io:all-srcs", "//pkg/util/ipconfig:all-srcs", + "//pkg/util/ipset:all-srcs", "//pkg/util/iptables:all-srcs", "//pkg/util/ipvs:all-srcs", "//pkg/util/keymutex:all-srcs", @@ -38,6 +39,7 @@ filegroup( "//pkg/util/net:all-srcs", "//pkg/util/netsh:all-srcs", "//pkg/util/node:all-srcs", + "//pkg/util/normalizer:all-srcs", "//pkg/util/nsenter:all-srcs", "//pkg/util/oom:all-srcs", "//pkg/util/parsers:all-srcs", @@ -45,6 +47,7 @@ filegroup( "//pkg/util/procfs:all-srcs", "//pkg/util/reflector/prometheus:all-srcs", "//pkg/util/removeall:all-srcs", + "//pkg/util/resizefs:all-srcs", "//pkg/util/resourcecontainer:all-srcs", "//pkg/util/rlimit:all-srcs", "//pkg/util/selinux:all-srcs", diff --git a/pkg/util/async/BUILD b/pkg/util/async/BUILD index a00d4843d7b..5cb7bfa0722 100644 --- a/pkg/util/async/BUILD +++ b/pkg/util/async/BUILD @@ -25,8 +25,8 @@ go_test( "bounded_frequency_runner_test.go", "runner_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/async", - library = ":go_default_library", ) filegroup( diff --git a/pkg/util/bandwidth/BUILD b/pkg/util/bandwidth/BUILD index 166acf29b48..af79e9d87f4 100644 --- a/pkg/util/bandwidth/BUILD +++ b/pkg/util/bandwidth/BUILD @@ -12,19 +12,48 @@ go_library( "doc.go", "fake_shaper.go", "interfaces.go", - "unsupported.go", "utils.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "unsupported.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ "linux.go", ], + "@io_bazel_rules_go//go/platform:nacl": [ + "unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "unsupported.go", + ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/pkg/util/bandwidth", deps = [ "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library", @@ -38,19 +67,19 @@ go_test( srcs = [ "utils_test.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "linux_test.go", ], "//conditions:default": [], }), + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/bandwidth", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//vendor/k8s.io/utils/exec:go_default_library", "//vendor/k8s.io/utils/exec/testing:go_default_library", ], diff --git a/pkg/util/bandwidth/utils_test.go b/pkg/util/bandwidth/utils_test.go index 56687975afd..5ce2287ac4c 100644 --- a/pkg/util/bandwidth/utils_test.go +++ b/pkg/util/bandwidth/utils_test.go @@ -22,7 +22,7 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestExtractPodBandwidthResources(t *testing.T) { diff --git a/pkg/util/config/BUILD b/pkg/util/config/BUILD index 68bf1ccc107..6567106eea0 100644 --- a/pkg/util/config/BUILD +++ b/pkg/util/config/BUILD @@ -19,8 +19,8 @@ go_library( go_test( name = "go_default_test", srcs = ["config_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/config", - library = ":go_default_library", ) filegroup( diff --git a/pkg/util/configz/BUILD b/pkg/util/configz/BUILD index 2fbc52d4c92..cf5a458b9fd 100644 --- a/pkg/util/configz/BUILD +++ b/pkg/util/configz/BUILD @@ -15,8 +15,8 @@ go_library( go_test( name = "go_default_test", srcs = ["configz_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/configz", - library = ":go_default_library", ) filegroup( diff --git a/pkg/util/dbus/BUILD b/pkg/util/dbus/BUILD index d596d30649f..e0079ff6a64 100644 --- a/pkg/util/dbus/BUILD +++ b/pkg/util/dbus/BUILD @@ -20,8 +20,8 @@ go_library( go_test( name = "go_default_test", srcs = ["dbus_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/dbus", - library = ":go_default_library", deps = ["//vendor/github.com/godbus/dbus:go_default_library"], ) diff --git a/pkg/util/dbus/fake_dbus.go b/pkg/util/dbus/fake_dbus.go index 44131272e7a..28cde82fb2f 100644 --- a/pkg/util/dbus/fake_dbus.go +++ b/pkg/util/dbus/fake_dbus.go @@ -18,6 +18,7 @@ package dbus import ( "fmt" + "sync" godbus "github.com/godbus/dbus" ) @@ -30,6 +31,7 @@ type DBusFake struct { // DBusFakeConnection represents a fake D-Bus connection type DBusFakeConnection struct { + lock sync.Mutex busObject *fakeObject objects map[string]*fakeObject signalHandlers []chan<- *godbus.Signal @@ -88,6 +90,8 @@ func (conn *DBusFakeConnection) Object(name, path string) Object { // Signal is part of the Connection interface func (conn *DBusFakeConnection) Signal(ch chan<- *godbus.Signal) { + conn.lock.Lock() + defer conn.lock.Unlock() for i := range conn.signalHandlers { if conn.signalHandlers[i] == ch { conn.signalHandlers = append(conn.signalHandlers[:i], conn.signalHandlers[i+1:]...) @@ -109,6 +113,8 @@ func (conn *DBusFakeConnection) AddObject(name, path string, handler DBusFakeHan // EmitSignal emits a signal on conn func (conn *DBusFakeConnection) EmitSignal(name, path, iface, signal string, args ...interface{}) { + conn.lock.Lock() + defer conn.lock.Unlock() sig := &godbus.Signal{ Sender: name, Path: godbus.ObjectPath(path), diff --git a/pkg/util/ebtables/BUILD b/pkg/util/ebtables/BUILD index 6a9f7f0a71e..68fb9b3ad52 100644 --- a/pkg/util/ebtables/BUILD +++ b/pkg/util/ebtables/BUILD @@ -16,8 +16,8 @@ go_library( go_test( name = "go_default_test", srcs = ["ebtables_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/ebtables", - library = ":go_default_library", deps = [ "//vendor/k8s.io/utils/exec:go_default_library", "//vendor/k8s.io/utils/exec/testing:go_default_library", diff --git a/pkg/util/env/BUILD b/pkg/util/env/BUILD index 3e864094f69..d1d623aaf1c 100644 --- a/pkg/util/env/BUILD +++ b/pkg/util/env/BUILD @@ -15,8 +15,8 @@ go_library( go_test( name = "go_default_test", srcs = ["env_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/env", - library = ":go_default_library", deps = ["//vendor/github.com/stretchr/testify/assert:go_default_library"], ) diff --git a/pkg/util/file/BUILD b/pkg/util/file/BUILD index 3a3142a7843..059895a66b4 100644 --- a/pkg/util/file/BUILD +++ b/pkg/util/file/BUILD @@ -3,6 +3,7 @@ package(default_visibility = ["//visibility:public"]) load( "@io_bazel_rules_go//go:def.bzl", "go_library", + "go_test", ) go_library( @@ -23,3 +24,14 @@ filegroup( srcs = [":package-srcs"], tags = ["automanaged"], ) + +go_test( + name = "go_default_test", + srcs = ["file_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/util/file", + deps = [ + "//vendor/github.com/spf13/afero:go_default_library", + "//vendor/github.com/stretchr/testify/assert:go_default_library", + ], +) diff --git a/pkg/util/file/file_test.go b/pkg/util/file/file_test.go new file mode 100644 index 00000000000..21bf495218d --- /dev/null +++ b/pkg/util/file/file_test.go @@ -0,0 +1,149 @@ +/* +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 file + +import ( + "os" + "path/filepath" + "sort" + "testing" + + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" +) + +func RecoverEnv(wd, tmpDir string) { + os.Chdir(wd) + os.RemoveAll(tmpDir) +} + +func TestFileUtils(t *testing.T) { + fs := &afero.Afero{Fs: afero.NewOsFs()} + // Create tmp dir + tmpDir, err := fs.TempDir(os.TempDir(), "util_file_test_") + if err != nil { + t.Fatal("Failed to test: failed to create temp dir.") + } + + // create tmp file + tmpFile, err := fs.TempFile(tmpDir, "test_file_exists_") + if err != nil { + t.Fatal("Failed to test: failed to create temp file.") + } + + // create tmp sym link + tmpSymlinkName := filepath.Join(tmpDir, "test_file_exists_sym_link") + err = os.Symlink(tmpFile.Name(), tmpSymlinkName) + if err != nil { + t.Fatal("Failed to test: failed to create sym link.") + } + + // create tmp sub dir + tmpSubDir, err := fs.TempDir(tmpDir, "sub_") + if err != nil { + t.Fatal("Failed to test: failed to create temp sub dir.") + } + + // record the current dir + currentDir, err := os.Getwd() + if err != nil { + t.Fatal("Failed to test: failed to get current dir.") + } + + // change the work dir to temp dir + err = os.Chdir(tmpDir) + if err != nil { + t.Fatal("Failed to test: failed to change work dir.") + } + + // recover test enviroment + defer RecoverEnv(currentDir, tmpDir) + + t.Run("TestFileExists", func(t *testing.T) { + tests := []struct { + name string + fileName string + expectedError bool + expectedValue bool + }{ + {"file_not_exists", filepath.Join(tmpDir, "file_not_exist_case"), false, false}, + {"file_exists", tmpFile.Name(), false, true}, + } + + for _, test := range tests { + realValued, realError := FileExists(test.fileName) + if test.expectedError { + assert.Errorf(t, realError, "Failed to test with '%s': %s", test.fileName, test.name) + } else { + assert.EqualValuesf(t, test.expectedValue, realValued, "Failed to test with '%s': %s", test.fileName, test.name) + } + } + }) + + t.Run("TestFileOrSymlinkExists", func(t *testing.T) { + tests := []struct { + name string + fileName string + expectedError bool + expectedValue bool + }{ + {"file_not_exists", filepath.Join(tmpDir, "file_not_exist_case"), false, false}, + {"file_exists", tmpFile.Name(), false, true}, + {"symlink_exists", tmpSymlinkName, false, true}, + } + + for _, test := range tests { + realValued, realError := FileOrSymlinkExists(test.fileName) + if test.expectedError { + assert.Errorf(t, realError, "Failed to test with '%s': %s", test.fileName, test.name) + } else { + assert.EqualValuesf(t, test.expectedValue, realValued, "Failed to test with '%s': %s", test.fileName, test.name) + } + } + }) + + t.Run("TestReadDirNoStat", func(t *testing.T) { + _, tmpFileSimpleName := filepath.Split(tmpFile.Name()) + _, tmpSymlinkSimpleName := filepath.Split(tmpSymlinkName) + _, tmpSubDirSimpleName := filepath.Split(tmpSubDir) + + tests := []struct { + name string + dirName string + expectedError bool + expectedValue []string + }{ + {"dir_not_exists", filepath.Join(tmpDir, "file_not_exist_case"), true, []string{}}, + {"dir_is_empty", "", false, []string{tmpFileSimpleName, tmpSymlinkSimpleName, tmpSubDirSimpleName}}, + {"dir_exists", tmpDir, false, []string{tmpFileSimpleName, tmpSymlinkSimpleName, tmpSubDirSimpleName}}, + } + + for _, test := range tests { + realValued, realError := ReadDirNoStat(test.dirName) + + // execute sort action before compare + sort.Strings(realValued) + sort.Strings(test.expectedValue) + + if test.expectedError { + assert.Errorf(t, realError, "Failed to test with '%s': %s", test.dirName, test.name) + } else { + assert.EqualValuesf(t, test.expectedValue, realValued, "Failed to test with '%s': %s", test.dirName, test.name) + } + } + }) +} diff --git a/pkg/util/flock/BUILD b/pkg/util/flock/BUILD index ef9212a2075..040ed5d0c97 100644 --- a/pkg/util/flock/BUILD +++ b/pkg/util/flock/BUILD @@ -7,23 +7,60 @@ load( go_library( name = "go_default_library", - srcs = [ - "flock_other.go", - ] + select({ - "@io_bazel_rules_go//go/platform:darwin_amd64": [ + srcs = select({ + "@io_bazel_rules_go//go/platform:android": [ + "flock_other.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ "flock_unix.go", ], - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:dragonfly": [ "flock_unix.go", ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "flock_unix.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "flock_unix.go", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "flock_other.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "flock_unix.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "flock_unix.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "flock_other.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "flock_other.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "flock_other.go", + ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/pkg/util/flock", deps = select({ - "@io_bazel_rules_go//go/platform:darwin_amd64": [ + "@io_bazel_rules_go//go/platform:darwin": [ "//vendor/golang.org/x/sys/unix:go_default_library", ], - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:dragonfly": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ "//vendor/golang.org/x/sys/unix:go_default_library", ], "//conditions:default": [], diff --git a/pkg/util/goroutinemap/BUILD b/pkg/util/goroutinemap/BUILD index fe6895f0a6c..1085fe19d99 100644 --- a/pkg/util/goroutinemap/BUILD +++ b/pkg/util/goroutinemap/BUILD @@ -20,8 +20,8 @@ go_library( go_test( name = "go_default_test", srcs = ["goroutinemap_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/goroutinemap", - library = ":go_default_library", deps = ["//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library"], ) diff --git a/pkg/util/hash/BUILD b/pkg/util/hash/BUILD index bafca6ba8bc..f719b40347f 100644 --- a/pkg/util/hash/BUILD +++ b/pkg/util/hash/BUILD @@ -16,8 +16,8 @@ go_library( go_test( name = "go_default_test", srcs = ["hash_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/hash", - library = ":go_default_library", deps = ["//vendor/github.com/davecgh/go-spew/spew:go_default_library"], ) diff --git a/pkg/util/initsystem/initsystem.go b/pkg/util/initsystem/initsystem.go index 435cf57ad3e..e4f8870a364 100644 --- a/pkg/util/initsystem/initsystem.go +++ b/pkg/util/initsystem/initsystem.go @@ -43,13 +43,13 @@ type SystemdInitSystem struct{} func (sysd SystemdInitSystem) ServiceStart(service string) error { args := []string{"start", service} - _, err := exec.Command("systemctl", args...).Output() + err := exec.Command("systemctl", args...).Run() return err } func (sysd SystemdInitSystem) ServiceStop(service string) error { args := []string{"stop", service} - _, err := exec.Command("systemctl", args...).Output() + err := exec.Command("systemctl", args...).Run() return err } @@ -65,7 +65,7 @@ func (sysd SystemdInitSystem) ServiceExists(service string) bool { func (sysd SystemdInitSystem) ServiceIsEnabled(service string) bool { args := []string{"is-enabled", service} - _, err := exec.Command("systemctl", args...).Output() + err := exec.Command("systemctl", args...).Run() if err != nil { return false } @@ -86,7 +86,52 @@ func (sysd SystemdInitSystem) ServiceIsActive(service string) bool { return false } -// getInitSystem returns an InitSystem for the current system, or nil +// WindowsInitSystem is the windows implementation of InitSystem +type WindowsInitSystem struct{} + +func (sysd WindowsInitSystem) ServiceStart(service string) error { + args := []string{"Start-Service", service} + err := exec.Command("powershell", args...).Run() + return err +} + +func (sysd WindowsInitSystem) ServiceStop(service string) error { + args := []string{"Stop-Service", service} + err := exec.Command("powershell", args...).Run() + return err +} + +func (sysd WindowsInitSystem) ServiceExists(service string) bool { + args := []string{"Get-Service", service} + err := exec.Command("powershell", args...).Run() + if err != nil { + return false + } + return true + +} + +func (sysd WindowsInitSystem) ServiceIsEnabled(service string) bool { + args := []string{"Get-Service", service + "| select -property starttype"} + outBytes, _ := exec.Command("powershell", args...).Output() + output := strings.TrimSpace(string(outBytes)) + if strings.Contains(output, "Automatic") { + return true + } + return false +} + +func (sysd WindowsInitSystem) ServiceIsActive(service string) bool { + args := []string{"Get-Service", service + "| select -property status"} + outBytes, _ := exec.Command("powershell", args...).Output() + output := strings.TrimSpace(string(outBytes)) + if strings.Contains(output, "Running") { + return true + } + return false +} + +// GetInitSystem returns an InitSystem for the current system, or nil // if we cannot detect a supported init system for pre-flight checks. // This indicates we will skip init system checks, not an error. func GetInitSystem() (InitSystem, error) { @@ -95,5 +140,9 @@ func GetInitSystem() (InitSystem, error) { if err == nil { return &SystemdInitSystem{}, nil } + _, err = exec.LookPath("wininit.exe") + if err == nil { + return &WindowsInitSystem{}, nil + } return nil, fmt.Errorf("no supported init system detected, skipping checking for services") } diff --git a/pkg/util/ipconfig/BUILD b/pkg/util/ipconfig/BUILD index f452283eab7..af4bf61b044 100644 --- a/pkg/util/ipconfig/BUILD +++ b/pkg/util/ipconfig/BUILD @@ -22,8 +22,8 @@ go_library( go_test( name = "go_default_test", srcs = ["ipconfig_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/ipconfig", - library = ":go_default_library", deps = ["//vendor/k8s.io/utils/exec:go_default_library"], ) diff --git a/pkg/util/ipset/BUILD b/pkg/util/ipset/BUILD new file mode 100644 index 00000000000..9baf26f5809 --- /dev/null +++ b/pkg/util/ipset/BUILD @@ -0,0 +1,41 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "ipset.go", + "types.go", + ], + importpath = "k8s.io/kubernetes/pkg/util/ipset", + visibility = ["//visibility:public"], + deps = ["//vendor/k8s.io/utils/exec:go_default_library"], +) + +go_test( + name = "go_default_test", + srcs = ["ipset_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/util/ipset", + deps = [ + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/utils/exec:go_default_library", + "//vendor/k8s.io/utils/exec/testing:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//pkg/util/ipset/testing:all-srcs", + ], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/util/ipset/ipset.go b/pkg/util/ipset/ipset.go new file mode 100644 index 00000000000..7ae28050097 --- /dev/null +++ b/pkg/util/ipset/ipset.go @@ -0,0 +1,344 @@ +/* +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 ipset + +import ( + "bytes" + "fmt" + "regexp" + "strconv" + "strings" + + utilexec "k8s.io/utils/exec" +) + +// Interface is an injectable interface for running ipset commands. Implementations must be goroutine-safe. +type Interface interface { + // FlushSet deletes all entries from a named set. + FlushSet(set string) error + // DestroySet deletes a named set. + DestroySet(set string) error + // DestroyAllSets deletes all sets. + DestroyAllSets() error + // CreateSet creates a new set, it will ignore error when the set already exists if ignoreExistErr=true. + CreateSet(set *IPSet, ignoreExistErr bool) error + // AddEntry adds a new entry to the named set. + AddEntry(entry string, set string, ignoreExistErr bool) error + // DelEntry deletes one entry from the named set + DelEntry(entry string, set string) error + // Test test if an entry exists in the named set + TestEntry(entry string, set string) (bool, error) + // ListEntries lists all the entries from a named set + ListEntries(set string) ([]string, error) + // ListSets list all set names from kernel + ListSets() ([]string, error) + // GetVersion returns the "X.Y" version string for ipset. + GetVersion() (string, error) +} + +// IPSetCmd represents the ipset util. We use ipset command for ipset execute. +const IPSetCmd = "ipset" + +// EntryMemberPattern is the regular expression pattern of ipset member list. +// The raw output of ipset command `ipset list {set}` is similar to, +//Name: foobar +//Type: hash:ip,port +//Revision: 2 +//Header: family inet hashsize 1024 maxelem 65536 +//Size in memory: 16592 +//References: 0 +//Members: +//192.168.1.2,tcp:8080 +//192.168.1.1,udp:53 +var EntryMemberPattern = "(?m)^(.*\n)*Members:\n" + +// VersionPattern is the regular expression pattern of ipset version string. +// ipset version output is similar to "v6.10". +var VersionPattern = "v[0-9]+\\.[0-9]+" + +// IPSet implements an Interface to an set. +type IPSet struct { + // Name is the set name. + Name string + // SetType specifies the ipset type. + SetType Type + // HashFamily specifies the protocol family of the IP addresses to be stored in the set. + // The default is inet, i.e IPv4. If users want to use IPv6, they should specify inet6. + HashFamily string + // HashSize specifies the hash table size of ipset. + HashSize int + // MaxElem specifies the max element number of ipset. + MaxElem int + // PortRange specifies the port range of bitmap:port type ipset. + PortRange string +} + +// Entry represents a ipset entry. +type Entry struct { + // IP is the entry's IP. The IP address protocol corresponds to the HashFamily of IPSet. + // All entries' IP addresses in the same ip set has same the protocol, IPv4 or IPv6. + IP string + // Port is the entry's Port. + Port int + // Protocol is the entry's Protocol. The protocols of entries in the same ip set are all + // the same. The accepted protocols are TCP and UDP. + Protocol string + // Net is the entry's IP network address. Network address with zero prefix size can NOT + // be stored. + Net string + // IP2 is the entry's second IP. IP2 may not be empty for `hash:ip,port,ip` type ip set. + IP2 string + // SetType specifies the type of ip set where the entry exists. + SetType Type +} + +func (e *Entry) String() string { + switch e.SetType { + case HashIPPort: + // Entry{192.168.1.1, udp, 53} -> 192.168.1.1,udp:53 + // Entry{192.168.1.2, tcp, 8080} -> 192.168.1.2,tcp:8080 + return fmt.Sprintf("%s,%s:%s", e.IP, e.Protocol, strconv.Itoa(e.Port)) + case HashIPPortIP: + // Entry{192.168.1.1, udp, 53, 10.0.0.1} -> 192.168.1.1,udp:53,10.0.0.1 + // Entry{192.168.1.2, tcp, 8080, 192.168.1.2} -> 192.168.1.2,tcp:8080,192.168.1.2 + return fmt.Sprintf("%s,%s:%s,%s", e.IP, e.Protocol, strconv.Itoa(e.Port), e.IP2) + case HashIPPortNet: + // Entry{192.168.1.2, udp, 80, 10.0.1.0/24} -> 192.168.1.2,udp:80,10.0.1.0/24 + // Entry{192.168.2,25, tcp, 8080, 10.1.0.0/16} -> 192.168.2,25,tcp:8080,10.1.0.0/16 + return fmt.Sprintf("%s,%s:%s,%s", e.IP, e.Protocol, strconv.Itoa(e.Port), e.Net) + case BitmapPort: + // Entry{53} -> 53 + // Entry{8080} -> 8080 + return strconv.Itoa(e.Port) + } + return "" +} + +type runner struct { + exec utilexec.Interface +} + +// New returns a new Interface which will exec ipset. +func New(exec utilexec.Interface) Interface { + return &runner{ + exec: exec, + } +} + +// CreateSet creates a new set, it will ignore error when the set already exists if ignoreExistErr=true. +func (runner *runner) CreateSet(set *IPSet, ignoreExistErr bool) error { + // Using default values. + if set.HashSize == 0 { + set.HashSize = 1024 + } + if set.MaxElem == 0 { + set.MaxElem = 65536 + } + if set.HashFamily == "" { + set.HashFamily = ProtocolFamilyIPV4 + } + if len(set.HashFamily) != 0 && set.HashFamily != ProtocolFamilyIPV4 && set.HashFamily != ProtocolFamilyIPV6 { + return fmt.Errorf("Currently supported protocol families are: %s and %s, %s is not supported", ProtocolFamilyIPV4, ProtocolFamilyIPV6, set.HashFamily) + } + // Default ipset type is "hash:ip,port" + if len(set.SetType) == 0 { + set.SetType = HashIPPort + } + // Check if setType is supported + if !IsValidIPSetType(set.SetType) { + return fmt.Errorf("Currently supported ipset types are: %v, %s is not supported", ValidIPSetTypes, set.SetType) + } + + return runner.createSet(set, ignoreExistErr) +} + +// If ignoreExistErr is set to true, then the -exist option of ipset will be specified, ipset ignores the error +// otherwise raised when the same set (setname and create parameters are identical) already exists. +func (runner *runner) createSet(set *IPSet, ignoreExistErr bool) error { + args := []string{"create", set.Name, string(set.SetType)} + if set.SetType == HashIPPortIP || set.SetType == HashIPPort { + args = append(args, + "family", set.HashFamily, + "hashsize", strconv.Itoa(set.HashSize), + "maxelem", strconv.Itoa(set.MaxElem), + ) + } + if set.SetType == BitmapPort { + if len(set.PortRange) == 0 { + set.PortRange = DefaultPortRange + } + if !validatePortRange(set.PortRange) { + return fmt.Errorf("invalid port range for %s type ip set: %s, expect: a-b", BitmapPort, set.PortRange) + } + args = append(args, "range", set.PortRange) + } + if ignoreExistErr { + args = append(args, "-exist") + } + if _, err := runner.exec.Command(IPSetCmd, args...).CombinedOutput(); err != nil { + return fmt.Errorf("error creating ipset %s, error: %v", set.Name, err) + } + return nil +} + +// AddEntry adds a new entry to the named set. +// If the -exist option is specified, ipset ignores the error otherwise raised when +// the same set (setname and create parameters are identical) already exists. +func (runner *runner) AddEntry(entry string, set string, ignoreExistErr bool) error { + args := []string{"add", set, entry} + if ignoreExistErr { + args = append(args, "-exist") + } + if _, err := runner.exec.Command(IPSetCmd, args...).CombinedOutput(); err != nil { + return fmt.Errorf("error adding entry %s, error: %v", entry, err) + } + return nil +} + +// DelEntry is used to delete the specified entry from the set. +func (runner *runner) DelEntry(entry string, set string) error { + if _, err := runner.exec.Command(IPSetCmd, "del", set, entry).CombinedOutput(); err != nil { + return fmt.Errorf("error deleting entry %s: from set: %s, error: %v", entry, set, err) + } + return nil +} + +// TestEntry is used to check whether the specified entry is in the set or not. +func (runner *runner) TestEntry(entry string, set string) (bool, error) { + if out, err := runner.exec.Command(IPSetCmd, "test", set, entry).CombinedOutput(); err == nil { + reg, e := regexp.Compile("NOT") + if e == nil && reg.MatchString(string(out)) { + return false, nil + } else if e == nil { + return true, nil + } else { + return false, fmt.Errorf("error testing entry: %s, error: %v", entry, e) + } + } else { + return false, fmt.Errorf("error testing entry %s: %v (%s)", entry, err, out) + } +} + +// FlushSet deletes all entries from a named set. +func (runner *runner) FlushSet(set string) error { + if _, err := runner.exec.Command(IPSetCmd, "flush", set).CombinedOutput(); err != nil { + return fmt.Errorf("error flushing set: %s, error: %v", set, err) + } + return nil +} + +// DestroySet is used to destroy a named set. +func (runner *runner) DestroySet(set string) error { + if _, err := runner.exec.Command(IPSetCmd, "destroy", set).CombinedOutput(); err != nil { + return fmt.Errorf("error destroying set %s:, error: %v", set, err) + } + return nil +} + +// DestroyAllSets is used to destroy all sets. +func (runner *runner) DestroyAllSets() error { + if _, err := runner.exec.Command(IPSetCmd, "destroy").CombinedOutput(); err != nil { + return fmt.Errorf("error destroying all sets, error: %v", err) + } + return nil +} + +// ListSets list all set names from kernel +func (runner *runner) ListSets() ([]string, error) { + out, err := runner.exec.Command(IPSetCmd, "list", "-n").CombinedOutput() + if err != nil { + return nil, fmt.Errorf("error listing all sets, error: %v", err) + } + return strings.Split(string(out), "\n"), nil +} + +// ListEntries lists all the entries from a named set. +func (runner *runner) ListEntries(set string) ([]string, error) { + if len(set) == 0 { + return nil, fmt.Errorf("set name can't be nil") + } + out, err := runner.exec.Command(IPSetCmd, "list", set).CombinedOutput() + if err != nil { + return nil, fmt.Errorf("error listing set: %s, error: %v", set, err) + } + memberMatcher := regexp.MustCompile(EntryMemberPattern) + list := memberMatcher.ReplaceAllString(string(out[:]), "") + strs := strings.Split(list, "\n") + results := make([]string, 0) + for i := range strs { + if len(strs[i]) > 0 { + results = append(results, strs[i]) + } + } + return results, nil +} + +// GetVersion returns the version string. +func (runner *runner) GetVersion() (string, error) { + return getIPSetVersionString(runner.exec) +} + +// getIPSetVersionString runs "ipset --version" to get the version string +// in the form of "X.Y", i.e "6.19" +func getIPSetVersionString(exec utilexec.Interface) (string, error) { + cmd := exec.Command(IPSetCmd, "--version") + cmd.SetStdin(bytes.NewReader([]byte{})) + bytes, err := cmd.CombinedOutput() + if err != nil { + return "", err + } + versionMatcher := regexp.MustCompile(VersionPattern) + match := versionMatcher.FindStringSubmatch(string(bytes)) + if match == nil { + return "", fmt.Errorf("no ipset version found in string: %s", bytes) + } + return match[0], nil +} + +func validatePortRange(portRange string) bool { + strs := strings.Split(portRange, "-") + if len(strs) != 2 { + return false + } + for i := range strs { + if _, err := strconv.Atoi(strs[i]); err != nil { + return false + } + } + return true +} + +// IsNotFoundError returns true if the error indicates "not found". It parses +// the error string looking for known values, which is imperfect but works in +// practice. +func IsNotFoundError(err error) bool { + es := err.Error() + if strings.Contains(es, "does not exist") { + // set with the same name already exists + // xref: https://github.com/Olipro/ipset/blob/master/lib/errcode.c#L32-L33 + return true + } + if strings.Contains(es, "element is missing") { + // entry is missing from the set + // xref: https://github.com/Olipro/ipset/blob/master/lib/parse.c#L1904 + // https://github.com/Olipro/ipset/blob/master/lib/parse.c#L1925 + return true + } + return false +} + +var _ = Interface(&runner{}) diff --git a/pkg/util/ipset/ipset_test.go b/pkg/util/ipset/ipset_test.go new file mode 100644 index 00000000000..71d4ce26d58 --- /dev/null +++ b/pkg/util/ipset/ipset_test.go @@ -0,0 +1,483 @@ +/* +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 ipset + +import ( + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/utils/exec" + fakeexec "k8s.io/utils/exec/testing" +) + +func TestCheckIPSetVersion(t *testing.T) { + testCases := []struct { + vstring string + Expect string + Err bool + }{ + {"ipset v4.0, protocol version: 4", "v4.0", false}, + {"ipset v5.1, protocol version: 5", "v5.1", false}, + {"ipset v6.0, protocol version: 6", "v6.0", false}, + {"ipset v6.1, protocol version: 6", "v6.1", false}, + {"ipset v6.19, protocol version: 6", "v6.19", false}, + {"total junk", "", true}, + } + + for i := range testCases { + fcmd := fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ + // ipset version response + func() ([]byte, error) { return []byte(testCases[i].vstring), nil }, + }, + } + + fexec := fakeexec.FakeExec{ + CommandScript: []fakeexec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + }, + } + + gotVersion, err := getIPSetVersionString(&fexec) + if (err != nil) != testCases[i].Err { + t.Errorf("Expected error: %v, Got error: %v", testCases[i].Err, err) + } + if err == nil { + if testCases[i].Expect != gotVersion { + t.Errorf("Expected result: %v, Got result: %v", testCases[i].Expect, gotVersion) + } + } + } +} + +func TestFlushSet(t *testing.T) { + fcmd := fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ + // Success + func() ([]byte, error) { return []byte{}, nil }, + // Success + func() ([]byte, error) { return []byte{}, nil }, + }, + } + fexec := fakeexec.FakeExec{ + CommandScript: []fakeexec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + }, + } + runner := New(&fexec) + // Success. + err := runner.FlushSet("FOOBAR") + if err != nil { + t.Errorf("expected success, got %v", err) + } + if fcmd.CombinedOutputCalls != 1 { + t.Errorf("expected 1 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) + } + if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "flush", "FOOBAR") { + t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0]) + } + // Flush again + err = runner.FlushSet("FOOBAR") + if err != nil { + t.Errorf("expected success, got %v", err) + } +} + +func TestDestroySet(t *testing.T) { + fcmd := fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ + // Success + func() ([]byte, error) { return []byte{}, nil }, + // Failure + func() ([]byte, error) { + return []byte("ipset v6.19: The set with the given name does not exist"), &fakeexec.FakeExitError{Status: 1} + }, + }, + } + fexec := fakeexec.FakeExec{ + CommandScript: []fakeexec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + }, + } + runner := New(&fexec) + // Success + err := runner.DestroySet("FOOBAR") + if err != nil { + t.Errorf("expected success, got %v", err) + } + if fcmd.CombinedOutputCalls != 1 { + t.Errorf("expected 1 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) + } + if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "destroy", "FOOBAR") { + t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0]) + } + // Failure + err = runner.DestroySet("FOOBAR") + if err == nil { + t.Errorf("expected failure, got nil") + } +} + +func TestDestroyAllSets(t *testing.T) { + fcmd := fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ + // Success + func() ([]byte, error) { return []byte{}, nil }, + // Success + func() ([]byte, error) { return []byte{}, nil }, + }, + } + fexec := fakeexec.FakeExec{ + CommandScript: []fakeexec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + }, + } + runner := New(&fexec) + // Success + err := runner.DestroyAllSets() + if err != nil { + t.Errorf("expected success, got %v", err) + } + if fcmd.CombinedOutputCalls != 1 { + t.Errorf("expected 1 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) + } + if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "destroy") { + t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0]) + } + // Success + err = runner.DestroyAllSets() + if err != nil { + t.Errorf("Unexpected failure: %v", err) + } +} + +func TestCreateSet(t *testing.T) { + testSet := IPSet{ + Name: "FOOBAR", + SetType: HashIPPort, + HashFamily: ProtocolFamilyIPV4, + } + + fcmd := fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ + // Success + func() ([]byte, error) { return []byte{}, nil }, + // Success + func() ([]byte, error) { return []byte{}, nil }, + // Failure + func() ([]byte, error) { + return []byte("ipset v6.19: Set cannot be created: set with the same name already exists"), &fakeexec.FakeExitError{Status: 1} + }, + }, + } + fexec := fakeexec.FakeExec{ + CommandScript: []fakeexec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + }, + } + runner := New(&fexec) + // Create with ignoreExistErr = false, expect success + err := runner.CreateSet(&testSet, false) + if err != nil { + t.Errorf("expected success, got %v", err) + } + if fcmd.CombinedOutputCalls != 1 { + t.Errorf("expected 1 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) + } + if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "create", "FOOBAR", "hash:ip,port", "family", "inet", "hashsize", "1024", "maxelem", "65536") { + t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0]) + } + // Create with ignoreExistErr = true, expect success + err = runner.CreateSet(&testSet, true) + if err != nil { + t.Errorf("expected success, got %v", err) + } + if fcmd.CombinedOutputCalls != 2 { + t.Errorf("expected 2 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) + } + if !sets.NewString(fcmd.CombinedOutputLog[1]...).HasAll("ipset", "create", "FOOBAR", "hash:ip,port", "family", "inet", "hashsize", "1024", "maxelem", "65536", "-exist") { + t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[1]) + } + // Create with ignoreExistErr = false, expect failure + err = runner.CreateSet(&testSet, false) + if err == nil { + t.Errorf("expected failure, got nil") + } +} + +func TestAddEntry(t *testing.T) { + testEntry := &Entry{ + IP: "192.168.1.1", + Port: 53, + Protocol: ProtocolUDP, + SetType: HashIPPort, + } + + fcmd := fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ + // Success + func() ([]byte, error) { return []byte{}, nil }, + // Success + func() ([]byte, error) { return []byte{}, nil }, + // Failure + func() ([]byte, error) { + return []byte("ipset v6.19: Set cannot be created: set with the same name already exists"), &fakeexec.FakeExitError{Status: 1} + }, + }, + } + fexec := fakeexec.FakeExec{ + CommandScript: []fakeexec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + }, + } + runner := New(&fexec) + // Create with ignoreExistErr = false, expect success + err := runner.AddEntry(testEntry.String(), "FOOBAR", false) + if err != nil { + t.Errorf("expected success, got %v", err) + } + if fcmd.CombinedOutputCalls != 1 { + t.Errorf("expected 1 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) + } + if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "add", "FOOBAR", "192.168.1.1,udp:53") { + t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0]) + } + // Create with ignoreExistErr = true, expect success + err = runner.AddEntry(testEntry.String(), "FOOBAR", true) + if err != nil { + t.Errorf("expected success, got %v", err) + } + if fcmd.CombinedOutputCalls != 2 { + t.Errorf("expected 3 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) + } + if !sets.NewString(fcmd.CombinedOutputLog[1]...).HasAll("ipset", "add", "FOOBAR", "192.168.1.1,udp:53", "-exist") { + t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[1]) + } + // Create with ignoreExistErr = false, expect failure + err = runner.AddEntry(testEntry.String(), "FOOBAR", false) + if err == nil { + t.Errorf("expected failure, got nil") + } +} + +func TestDelEntry(t *testing.T) { + // TODO: Test more set type + testEntry := &Entry{ + IP: "192.168.1.1", + Port: 53, + Protocol: ProtocolUDP, + SetType: HashIPPort, + } + + fcmd := fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ + // Success + func() ([]byte, error) { return []byte{}, nil }, + // Failure + func() ([]byte, error) { + return []byte("ipset v6.19: Element cannot be deleted from the set: it's not added"), &fakeexec.FakeExitError{Status: 1} + }, + }, + } + fexec := fakeexec.FakeExec{ + CommandScript: []fakeexec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + }, + } + runner := New(&fexec) + err := runner.DelEntry(testEntry.String(), "FOOBAR") + if err != nil { + t.Errorf("expected success, got %v", err) + } + if fcmd.CombinedOutputCalls != 1 { + t.Errorf("expected 1 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) + } + if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "del", "FOOBAR", "192.168.1.1,udp:53") { + t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0]) + } + err = runner.DelEntry(testEntry.String(), "FOOBAR") + if err == nil { + t.Errorf("expected failure, got nil") + } +} + +func TestTestEntry(t *testing.T) { + // TODO: IPv6? + testEntry := &Entry{ + IP: "10.120.7.100", + Port: 8080, + Protocol: ProtocolTCP, + SetType: HashIPPort, + } + + fcmd := fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ + // Success + func() ([]byte, error) { return []byte("10.120.7.100,tcp:8080 is in set FOOBAR."), nil }, + // Failure + func() ([]byte, error) { + return []byte("192.168.1.3,tcp:8080 is NOT in set FOOBAR."), &fakeexec.FakeExitError{Status: 1} + }, + }, + } + fexec := fakeexec.FakeExec{ + CommandScript: []fakeexec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + }, + } + runner := New(&fexec) + // Success + ok, err := runner.TestEntry(testEntry.String(), "FOOBAR") + if err != nil { + t.Errorf("expected success, got %v", err) + } + if fcmd.CombinedOutputCalls != 1 { + t.Errorf("expected 2 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) + } + if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "test", "FOOBAR", "10.120.7.100,tcp:8080") { + t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0]) + } + if !ok { + t.Errorf("expect entry exists in test set, got not") + } + // Failure + ok, err = runner.TestEntry(testEntry.String(), "FOOBAR") + if err == nil || ok { + t.Errorf("expect entry doesn't exist in test set") + } +} + +func TestListEntries(t *testing.T) { + + output := `Name: foobar +Type: hash:ip,port +Revision: 2 +Header: family inet hashsize 1024 maxelem 65536 +Size in memory: 16592 +References: 0 +Members: +192.168.1.2,tcp:8080 +192.168.1.1,udp:53` + + emptyOutput := `Name: KUBE-NODE-PORT +Type: bitmap:port +Revision: 1 +Header: range 0-65535 +Size in memory: 524432 +References: 1 +Members: + +` + + testCases := []struct { + output string + expected []string + }{ + { + output: output, + expected: []string{"192.168.1.2,tcp:8080", "192.168.1.1,udp:53"}, + }, + { + output: emptyOutput, + expected: []string{}, + }, + } + + for i := range testCases { + fcmd := fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ + // Success + func() ([]byte, error) { + return []byte(testCases[i].output), nil + }, + }, + } + fexec := fakeexec.FakeExec{ + CommandScript: []fakeexec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { + return fakeexec.InitFakeCmd(&fcmd, cmd, args...) + }, + }, + } + runner := New(&fexec) + // Success + entries, err := runner.ListEntries("foobar") + if err != nil { + t.Errorf("expected success, got: %v", err) + } + if fcmd.CombinedOutputCalls != 1 { + t.Errorf("expected 1 CombinedOutput() calls, got: %d", fcmd.CombinedOutputCalls) + } + if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "list", "foobar") { + t.Errorf("wrong CombinedOutput() log, got: %s", fcmd.CombinedOutputLog[0]) + } + if len(entries) != len(testCases[i].expected) { + t.Errorf("expected %d ipset entries, got: %d", len(testCases[i].expected), len(entries)) + } + if !reflect.DeepEqual(entries, testCases[i].expected) { + t.Errorf("expected entries: %v, got: %v", testCases[i].expected, entries) + } + } +} + +func TestListSets(t *testing.T) { + output := `foo +bar +baz` + + expected := []string{"foo", "bar", "baz"} + + fcmd := fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ + // Success + func() ([]byte, error) { return []byte(output), nil }, + }, + } + fexec := fakeexec.FakeExec{ + CommandScript: []fakeexec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }, + }, + } + runner := New(&fexec) + // Success + list, err := runner.ListSets() + if err != nil { + t.Errorf("expected success, got: %v", err) + } + if fcmd.CombinedOutputCalls != 1 { + t.Errorf("expected 1 CombinedOutput() calls, got: %d", fcmd.CombinedOutputCalls) + } + if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "list", "-n") { + t.Errorf("wrong CombinedOutput() log, got: %s", fcmd.CombinedOutputLog[0]) + } + if len(list) != len(expected) { + t.Errorf("expected %d sets, got: %d", len(expected), len(list)) + } + if !reflect.DeepEqual(list, expected) { + t.Errorf("expected sets: %v, got: %v", expected, list) + } +} diff --git a/pkg/util/ipset/testing/BUILD b/pkg/util/ipset/testing/BUILD new file mode 100644 index 00000000000..43a42038c9b --- /dev/null +++ b/pkg/util/ipset/testing/BUILD @@ -0,0 +1,37 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["fake.go"], + importpath = "k8s.io/kubernetes/pkg/util/ipset/testing", + visibility = ["//visibility:public"], + deps = [ + "//pkg/util/ipset:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) + +go_test( + name = "go_default_test", + srcs = ["fake_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/util/ipset/testing", + deps = [ + "//pkg/util/ipset:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + ], +) diff --git a/pkg/util/ipset/testing/fake.go b/pkg/util/ipset/testing/fake.go new file mode 100644 index 00000000000..2a58bdd399d --- /dev/null +++ b/pkg/util/ipset/testing/fake.go @@ -0,0 +1,143 @@ +/* +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 testing + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/kubernetes/pkg/util/ipset" +) + +// FakeIPSet is a no-op implementation of ipset Interface +type FakeIPSet struct { + // version of ipset util + Version string + // The key of Sets map is the ip set name + Sets map[string]*ipset.IPSet + // The key of Entries map is the ip set name where the entries exists + Entries map[string]sets.String +} + +// NewFake create a new fake ipset interface - it initialize the FakeIPSet. +func NewFake(version string) *FakeIPSet { + return &FakeIPSet{ + Version: version, + Sets: make(map[string]*ipset.IPSet), + Entries: make(map[string]sets.String), + } +} + +// GetVersion is part of interface. +func (f *FakeIPSet) GetVersion() (string, error) { + return f.Version, nil +} + +// FlushSet is part of interface. It deletes all entries from a named set but keeps the set itself. +func (f *FakeIPSet) FlushSet(set string) error { + if f.Entries == nil { + return fmt.Errorf("entries map can't be nil") + } + + // delete all entry elements + for true { + if _, has := f.Entries[set].PopAny(); has { + continue + } + break + } + return nil +} + +// DestroySet is part of interface. It deletes both the entries and the set itself. +func (f *FakeIPSet) DestroySet(set string) error { + delete(f.Sets, set) + delete(f.Entries, set) + return nil +} + +// DestroyAllSets is part of interface. +func (f *FakeIPSet) DestroyAllSets() error { + f.Sets = nil + f.Entries = nil + return nil +} + +// CreateSet is part of interface. +func (f *FakeIPSet) CreateSet(set *ipset.IPSet, ignoreExistErr bool) error { + if f.Sets[set.Name] != nil { + if !ignoreExistErr { + // already exists + return fmt.Errorf("Set cannot be created: set with the same name already exists") + } + return nil + } + f.Sets[set.Name] = set + // initialize entry map + f.Entries[set.Name] = sets.NewString() + return nil +} + +// AddEntry is part of interface. +func (f *FakeIPSet) AddEntry(entry string, set string, ignoreExistErr bool) error { + if f.Entries[set].Has(entry) { + if !ignoreExistErr { + // already exists + return fmt.Errorf("Element cannot be added to the set: it's already added") + } + return nil + } + f.Entries[set].Insert(entry) + return nil +} + +// DelEntry is part of interface. +func (f *FakeIPSet) DelEntry(entry string, set string) error { + if f.Entries == nil { + return fmt.Errorf("entries map can't be nil") + } + f.Entries[set].Delete(entry) + return nil +} + +// TestEntry is part of interface. +func (f *FakeIPSet) TestEntry(entry string, set string) (bool, error) { + if f.Entries == nil { + return false, fmt.Errorf("entries map can't be nil") + } + found := f.Entries[set].Has(entry) + return found, nil +} + +// ListEntries is part of interface. +func (f *FakeIPSet) ListEntries(set string) ([]string, error) { + if f.Entries == nil { + return nil, fmt.Errorf("entries map can't be nil") + } + return f.Entries[set].UnsortedList(), nil +} + +// ListSets is part of interface. +func (f *FakeIPSet) ListSets() ([]string, error) { + res := []string{} + for set := range f.Sets { + res = append(res, set) + } + return res, nil +} + +var _ = ipset.Interface(&FakeIPSet{}) diff --git a/pkg/util/ipset/testing/fake_test.go b/pkg/util/ipset/testing/fake_test.go new file mode 100644 index 00000000000..2128395cf8e --- /dev/null +++ b/pkg/util/ipset/testing/fake_test.go @@ -0,0 +1,152 @@ +/* +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 testing + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/kubernetes/pkg/util/ipset" +) + +const testVersion = "v6.19" + +func TestSetEntry(t *testing.T) { + fake := NewFake(testVersion) + version, err := fake.GetVersion() + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if version != testVersion { + t.Errorf("Unexpected version mismatch, expected: %s, got: %s", testVersion, version) + } + // create a set + set := &ipset.IPSet{ + Name: "foo", + SetType: ipset.HashIPPort, + HashFamily: ipset.ProtocolFamilyIPV4, + } + if err := fake.CreateSet(set, true); err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // add two entries + fake.AddEntry("192.168.1.1,tcp:8080", set.Name, true) + fake.AddEntry("192.168.1.2,tcp:8081", set.Name, true) + entries, err := fake.ListEntries(set.Name) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if len(entries) != 2 { + t.Errorf("Expected 2 entries, got %d", len(entries)) + } + expectedEntries := sets.NewString("192.168.1.1,tcp:8080", "192.168.1.2,tcp:8081") + if !expectedEntries.Equal(sets.NewString(entries...)) { + t.Errorf("Unexpected entries mismatch, expected: %v, got: %v", expectedEntries, entries) + } + + // test entries + found, err := fake.TestEntry("192.168.1.1,tcp:8080", set.Name) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if !found { + t.Errorf("Unexpected entry 192.168.1.1,tcp:8080 not found") + } + + found, err = fake.TestEntry("192.168.1.2,tcp:8081", set.Name) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if !found { + t.Errorf("Unexpected entry 192.168.1.2,tcp:8081 not found") + } + + // delete entry from a given set + if err := fake.DelEntry("192.168.1.1,tcp:8080", set.Name); err != nil { + t.Errorf("Unexpected error: %v", err) + } + entries, err = fake.ListEntries(set.Name) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if len(entries) != 1 { + t.Errorf("Expected 1 entries, got %d", len(entries)) + } + expectedEntries = sets.NewString("192.168.1.2,tcp:8081") + if !expectedEntries.Equal(sets.NewString(entries...)) { + t.Errorf("Unexpected entries mismatch, expected: %v, got: %v", expectedEntries, entries) + } + + // Flush set + if err := fake.FlushSet(set.Name); err != nil { + t.Errorf("Unexpected error: %v", err) + } + entries, err = fake.ListEntries(set.Name) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if len(entries) != 0 { + t.Errorf("Expected 0 entries, got %d, entries: %v", len(entries), entries) + } + + // create another set + set2 := &ipset.IPSet{ + Name: "bar", + SetType: ipset.HashIPPortIP, + HashFamily: ipset.ProtocolFamilyIPV6, + } + if err := fake.CreateSet(set2, true); err != nil { + t.Errorf("Unexpected error: %v", err) + } + + setList, err := fake.ListSets() + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if len(setList) != 2 { + t.Errorf("Expected 2 sets, got %d", len(setList)) + } + expectedSets := sets.NewString("foo", "bar") + if !expectedSets.Equal(sets.NewString(setList...)) { + t.Errorf("Unexpected sets mismatch, expected: %v, got: %v", expectedSets, setList) + } + + // Destroy a given set + if err := fake.DestroySet(set.Name); err != nil { + t.Errorf("Unexpected error: %v", err) + } + if fake.Sets[set.Name] != nil { + t.Errorf("Unexpected set: %v", fake.Sets[set.Name]) + } + if fake.Entries[set.Name] != nil { + t.Errorf("Unexpected entries: %v", fake.Entries[set.Name]) + } + + // Destroy all sets + if err := fake.DestroyAllSets(); err != nil { + t.Errorf("Unexpected error: %v", err) + } + if len(fake.Sets) != 0 { + t.Errorf("Expected 0 sets, got %d, sets: %v", len(fake.Sets), fake.Sets) + } + if len(fake.Entries) != 0 { + t.Errorf("Expected 0 entries, got %d, entries: %v", len(fake.Entries), fake.Entries) + } +} + +// TODO: Test ignoreExistErr=false diff --git a/pkg/util/ipset/types.go b/pkg/util/ipset/types.go new file mode 100644 index 00000000000..d2406c00878 --- /dev/null +++ b/pkg/util/ipset/types.go @@ -0,0 +1,70 @@ +/* +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 ipset + +// Type represents the ipset type +type Type string + +const ( + // HashIPPort represents the `hash:ip,port` type ipset. The hash:ip,port is similar to hash:ip but + // you can store IP address and protocol-port pairs in it. TCP, SCTP, UDP, UDPLITE, ICMP and ICMPv6 are supported + // with port numbers/ICMP(v6) types and other protocol numbers without port information. + HashIPPort Type = "hash:ip,port" + // HashIPPortIP represents the `hash:ip,port,ip` type ipset. The hash:ip,port,ip set type uses a hash to store + // IP address, port number and a second IP address triples. The port number is interpreted together with a + // protocol (default TCP) and zero protocol number cannot be used. + HashIPPortIP Type = "hash:ip,port,ip" + // HashIPPortNet represents the `hash:ip,port,net` type ipset. The hash:ip,port,net set type uses a hash to store IP address, port number and IP network address triples. The port + // number is interpreted together with a protocol (default TCP) and zero protocol number cannot be used. Network address + // with zero prefix size cannot be stored either. + HashIPPortNet Type = "hash:ip,port,net" + // BitmapPort represents the `bitmap:port` type ipset. The bitmap:port set type uses a memory range, where each bit + // represents one TCP/UDP port. A bitmap:port type of set can store up to 65535 ports. + BitmapPort Type = "bitmap:port" +) + +// DefaultPortRange defines the default bitmap:port valid port range. +const DefaultPortRange string = "0-65535" + +const ( + // ProtocolFamilyIPV4 represents IPv4 protocol. + ProtocolFamilyIPV4 = "inet" + // ProtocolFamilyIPV6 represents IPv6 protocol. + ProtocolFamilyIPV6 = "inet6" + // ProtocolTCP represents TCP protocol. + ProtocolTCP = "tcp" + // ProtocolUDP represents UDP protocol. + ProtocolUDP = "udp" +) + +// ValidIPSetTypes defines the supported ip set type. +var ValidIPSetTypes = []Type{ + HashIPPort, + HashIPPortIP, + BitmapPort, + HashIPPortNet, +} + +// IsValidIPSetType checks if the given ipset type is valid. +func IsValidIPSetType(set Type) bool { + for _, valid := range ValidIPSetTypes { + if set == valid { + return true + } + } + return false +} diff --git a/pkg/util/iptables/BUILD b/pkg/util/iptables/BUILD index 0f3dbcef4bc..feb59d5c9fb 100644 --- a/pkg/util/iptables/BUILD +++ b/pkg/util/iptables/BUILD @@ -11,12 +11,41 @@ go_library( srcs = [ "doc.go", "iptables.go", - "iptables_unsupported.go", "save_restore.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "iptables_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "iptables_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "iptables_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "iptables_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ "iptables_linux.go", ], + "@io_bazel_rules_go//go/platform:nacl": [ + "iptables_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "iptables_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "iptables_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "iptables_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "iptables_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "iptables_unsupported.go", + ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/pkg/util/iptables", @@ -28,7 +57,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//vendor/golang.org/x/sys/unix:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", @@ -40,15 +69,15 @@ go_library( go_test( name = "go_default_test", srcs = select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "iptables_test.go", ], "//conditions:default": [], }), + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/iptables", - library = ":go_default_library", deps = select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//pkg/util/dbus:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library", diff --git a/pkg/util/iptables/iptables.go b/pkg/util/iptables/iptables.go index 065a36c8a86..292288d218b 100644 --- a/pkg/util/iptables/iptables.go +++ b/pkg/util/iptables/iptables.go @@ -82,6 +82,7 @@ type Table string const ( TableNAT Table = "nat" TableFilter Table = "filter" + TableMangle Table = "mangle" ) type Chain string @@ -91,6 +92,7 @@ const ( ChainPrerouting Chain = "PREROUTING" ChainOutput Chain = "OUTPUT" ChainInput Chain = "INPUT" + ChainForward Chain = "FORWARD" ) const ( @@ -118,9 +120,11 @@ const NoFlushTables FlushFlag = false // (test whether a rule exists). const MinCheckVersion = "1.4.11" -// Minimum iptables versions supporting the -w and -w2 flags -const MinWaitVersion = "1.4.20" -const MinWait2Version = "1.4.22" +// Minimum iptables versions supporting the -w and -w flags +const WaitMinVersion = "1.4.20" +const WaitSecondsMinVersion = "1.4.22" +const WaitString = "-w" +const WaitSecondsString = "-w5" const LockfilePath16x = "/run/xtables.lock" @@ -537,24 +541,24 @@ func getIPTablesWaitFlag(vstring string) []string { return nil } - minVersion, err := utilversion.ParseGeneric(MinWaitVersion) + minVersion, err := utilversion.ParseGeneric(WaitMinVersion) if err != nil { - glog.Errorf("MinWaitVersion (%s) is not a valid version string: %v", MinWaitVersion, err) + glog.Errorf("WaitMinVersion (%s) is not a valid version string: %v", WaitMinVersion, err) return nil } if version.LessThan(minVersion) { return nil } - minVersion, err = utilversion.ParseGeneric(MinWait2Version) + minVersion, err = utilversion.ParseGeneric(WaitSecondsMinVersion) if err != nil { - glog.Errorf("MinWait2Version (%s) is not a valid version string: %v", MinWait2Version, err) + glog.Errorf("WaitSecondsMinVersion (%s) is not a valid version string: %v", WaitSecondsMinVersion, err) return nil } if version.LessThan(minVersion) { - return []string{"-w"} + return []string{WaitString} } else { - return []string{"-w2"} + return []string{WaitSecondsString} } } @@ -590,7 +594,7 @@ func getIPTablesRestoreWaitFlag(exec utilexec.Interface, protocol Protocol) []st return nil } - return []string{"--wait=2"} + return []string{WaitSecondsString} } // getIPTablesRestoreVersionString runs "iptables-restore --version" to get the version string diff --git a/pkg/util/iptables/iptables_test.go b/pkg/util/iptables/iptables_test.go index b59380d185b..d7a7358056f 100644 --- a/pkg/util/iptables/iptables_test.go +++ b/pkg/util/iptables/iptables_test.go @@ -686,11 +686,11 @@ func TestIPTablesWaitFlag(t *testing.T) { {"0.55.55", ""}, {"1.0.55", ""}, {"1.4.19", ""}, - {"1.4.20", "-w"}, - {"1.4.21", "-w"}, - {"1.4.22", "-w2"}, - {"1.5.0", "-w2"}, - {"2.0.0", "-w2"}, + {"1.4.20", WaitString}, + {"1.4.21", WaitString}, + {"1.4.22", WaitSecondsString}, + {"1.5.0", WaitSecondsString}, + {"2.0.0", WaitSecondsString}, } for _, testCase := range testCases { @@ -730,7 +730,7 @@ func TestWaitFlagUnavailable(t *testing.T) { if fcmd.CombinedOutputCalls != 3 { t.Errorf("expected 3 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) } - if sets.NewString(fcmd.CombinedOutputLog[2]...).HasAny("-w", "-w2") { + if sets.NewString(fcmd.CombinedOutputLog[2]...).HasAny(WaitString, WaitSecondsString) { t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[2]) } } @@ -762,10 +762,10 @@ func TestWaitFlagOld(t *testing.T) { if fcmd.CombinedOutputCalls != 3 { t.Errorf("expected 3 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) } - if !sets.NewString(fcmd.CombinedOutputLog[2]...).HasAll("iptables", "-w") { + if !sets.NewString(fcmd.CombinedOutputLog[2]...).HasAll("iptables", WaitString) { t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[2]) } - if sets.NewString(fcmd.CombinedOutputLog[2]...).HasAny("-w2") { + if sets.NewString(fcmd.CombinedOutputLog[2]...).HasAny(WaitSecondsString) { t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[2]) } } @@ -797,10 +797,10 @@ func TestWaitFlagNew(t *testing.T) { if fcmd.CombinedOutputCalls != 3 { t.Errorf("expected 3 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) } - if !sets.NewString(fcmd.CombinedOutputLog[2]...).HasAll("iptables", "-w2") { + if !sets.NewString(fcmd.CombinedOutputLog[2]...).HasAll("iptables", WaitSecondsString) { t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[2]) } - if sets.NewString(fcmd.CombinedOutputLog[2]...).HasAny("-w") { + if sets.NewString(fcmd.CombinedOutputLog[2]...).HasAny(WaitString) { t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[2]) } } @@ -1165,7 +1165,7 @@ func TestRestoreAllWait(t *testing.T) { } commandSet := sets.NewString(fcmd.CombinedOutputLog[2]...) - if !commandSet.HasAll("iptables-restore", "--wait=2", "--counters", "--noflush") { + if !commandSet.HasAll("iptables-restore", WaitSecondsString, "--counters", "--noflush") { t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[2]) } @@ -1214,8 +1214,8 @@ func TestRestoreAllWaitOldIptablesRestore(t *testing.T) { if !commandSet.HasAll("iptables-restore", "--counters", "--noflush") { t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[2]) } - if commandSet.HasAny("--wait=2") { - t.Errorf("wrong CombinedOutput() log (unexpected --wait=2 option), got %s", fcmd.CombinedOutputLog[2]) + if commandSet.HasAny(WaitSecondsString) { + t.Errorf("wrong CombinedOutput() log (unexpected %s option), got %s", WaitSecondsString, fcmd.CombinedOutputLog[2]) } if fcmd.CombinedOutputCalls != 3 { diff --git a/pkg/util/iptables/testing/fake.go b/pkg/util/iptables/testing/fake.go index 8d9ac7c0708..6f398597f77 100644 --- a/pkg/util/iptables/testing/fake.go +++ b/pkg/util/iptables/testing/fake.go @@ -32,6 +32,7 @@ const ( Jump = "-j " Reject = "REJECT" ToDest = "--to-destination " + Recent = "recent " ) type Rule map[string]string @@ -111,7 +112,7 @@ func (f *FakeIPTables) GetRules(chainName string) (rules []Rule) { for _, l := range strings.Split(string(f.Lines), "\n") { if strings.Contains(l, fmt.Sprintf("-A %v", chainName)) { newRule := Rule(map[string]string{}) - for _, arg := range []string{Destination, Source, DPort, Protocol, Jump, ToDest} { + for _, arg := range []string{Destination, Source, DPort, Protocol, Jump, ToDest, Recent} { tok := getToken(l, arg) if tok != "" { newRule[arg] = tok diff --git a/pkg/util/ipvs/BUILD b/pkg/util/ipvs/BUILD index 21c27d219c7..252abb61296 100644 --- a/pkg/util/ipvs/BUILD +++ b/pkg/util/ipvs/BUILD @@ -11,15 +11,15 @@ go_test( srcs = [ "ipvs_test.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "ipvs_linux_test.go", ], "//conditions:default": [], }), + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/ipvs", - library = ":go_default_library", deps = select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//vendor/github.com/docker/libnetwork/ipvs:go_default_library", ], "//conditions:default": [], @@ -30,20 +30,78 @@ go_library( name = "go_default_library", srcs = [ "ipvs.go", - "ipvs_unsupported.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "ipvs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "ipvs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "ipvs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "ipvs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ "ipvs_linux.go", ], + "@io_bazel_rules_go//go/platform:nacl": [ + "ipvs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "ipvs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "ipvs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "ipvs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "ipvs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "ipvs_unsupported.go", + ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/pkg/util/ipvs", - deps = [ - "//vendor/k8s.io/utils/exec:go_default_library", - ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + deps = select({ + "@io_bazel_rules_go//go/platform:android": [ + "//vendor/k8s.io/utils/exec:go_default_library", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "//vendor/k8s.io/utils/exec:go_default_library", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "//vendor/k8s.io/utils/exec:go_default_library", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "//vendor/k8s.io/utils/exec:go_default_library", + ], + "@io_bazel_rules_go//go/platform:linux": [ "//vendor/github.com/docker/libnetwork/ipvs:go_default_library", "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/utils/exec:go_default_library", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "//vendor/k8s.io/utils/exec:go_default_library", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "//vendor/k8s.io/utils/exec:go_default_library", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "//vendor/k8s.io/utils/exec:go_default_library", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "//vendor/k8s.io/utils/exec:go_default_library", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "//vendor/k8s.io/utils/exec:go_default_library", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "//vendor/k8s.io/utils/exec:go_default_library", ], "//conditions:default": [], }), diff --git a/pkg/util/ipvs/OWNERS b/pkg/util/ipvs/OWNERS new file mode 100644 index 00000000000..fce2911d6a2 --- /dev/null +++ b/pkg/util/ipvs/OWNERS @@ -0,0 +1,7 @@ +reviewers: + - thockin + - m1093782566 +approvers: + - thockin + - m1093782566 + diff --git a/pkg/util/ipvs/testing/BUILD b/pkg/util/ipvs/testing/BUILD index cb64dd03d74..8d1dae2ca5d 100644 --- a/pkg/util/ipvs/testing/BUILD +++ b/pkg/util/ipvs/testing/BUILD @@ -29,7 +29,7 @@ filegroup( go_test( name = "go_default_test", srcs = ["fake_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/ipvs/testing", - library = ":go_default_library", deps = ["//pkg/util/ipvs:go_default_library"], ) diff --git a/pkg/util/ipvs/testing/fake.go b/pkg/util/ipvs/testing/fake.go index b33f091213d..6e015a20ee6 100644 --- a/pkg/util/ipvs/testing/fake.go +++ b/pkg/util/ipvs/testing/fake.go @@ -18,6 +18,8 @@ package testing import ( "fmt" + "net" + "strconv" utilipvs "k8s.io/kubernetes/pkg/util/ipvs" ) @@ -39,6 +41,15 @@ func (s *serviceKey) String() string { return fmt.Sprintf("%s:%d/%s", s.IP, s.Port, s.Protocol) } +type realServerKey struct { + Address net.IP + Port uint16 +} + +func (r *realServerKey) String() string { + return net.JoinHostPort(r.Address.String(), strconv.Itoa(int(r.Port))) +} + //NewFake creates a fake ipvs implementation - a cache store. func NewFake() *FakeIPVS { return &FakeIPVS{ @@ -55,6 +66,13 @@ func toServiceKey(serv *utilipvs.VirtualServer) serviceKey { } } +func toRealServerKey(rs *utilipvs.RealServer) *realServerKey { + return &realServerKey{ + Address: rs.Address, + Port: rs.Port, + } +} + //AddVirtualServer is a fake implementation, it simply adds the VirtualServer into the cache store. func (f *FakeIPVS) AddVirtualServer(serv *utilipvs.VirtualServer) error { if serv == nil { @@ -159,18 +177,19 @@ func (f *FakeIPVS) DeleteRealServer(serv *utilipvs.VirtualServer, dest *utilipvs return fmt.Errorf("Failed to delete destination for service %v, service not found", key.String()) } dests := f.Destinations[key] - var i int - for i = range dests { - if dests[i].Equal(dest) { + exist := false + for i := range dests { + if toRealServerKey(dests[i]).String() == toRealServerKey(dest).String() { + // Delete one element + f.Destinations[key] = append(f.Destinations[key][:i], f.Destinations[key][i+1:]...) + exist = true break } } // Not Found - if i >= len(f.Destinations[key]) { + if !exist { return fmt.Errorf("Failed to delete real server for service %v, real server not found", key.String()) } - // Delete one element - f.Destinations[key] = append(f.Destinations[key][:i], f.Destinations[key][i+1:]...) return nil } diff --git a/pkg/util/ipvs/testing/fake_test.go b/pkg/util/ipvs/testing/fake_test.go index 2682bf25825..a38d40840e0 100644 --- a/pkg/util/ipvs/testing/fake_test.go +++ b/pkg/util/ipvs/testing/fake_test.go @@ -113,46 +113,59 @@ func TestRealServer(t *testing.T) { Port: uint16(80), Protocol: string("TCP"), } + rss := []*utilipvs.RealServer{ + {net.ParseIP("172.16.2.1"), 8080, 1}, + {net.ParseIP("172.16.2.2"), 8080, 2}, + {net.ParseIP("172.16.2.3"), 8080, 3}, + } err := fake.AddVirtualServer(vs) if err != nil { t.Errorf("Fail to add virutal server, error: %v", err) } - // Add a real server to the virtual server - rs1 := &utilipvs.RealServer{ - Address: net.ParseIP("172.16.2.1"), + // Add real server to the virtual server + for i := range rss { + if err = fake.AddRealServer(vs, rss[i]); err != nil { + t.Errorf("Fail to add real server, error: %v", err) + } + } + // Delete a real server of the virtual server + // Make sure any position of the list can be real deleted + rssLen := len(rss) + for i := range rss { + // List all real servers of the virtual server + list, err := fake.GetRealServers(vs) + if err != nil { + t.Errorf("Fail to get real servers of the virtual server, error: %v", err) + } + if len(list) != rssLen { + t.Errorf("Expect %d virutal servers, got: %d", len(rss), len(list)) + } + rsToDel := list[i] + if err = fake.DeleteRealServer(vs, rsToDel); err != nil { + t.Errorf("Fail to delete real server of the virtual server, error: %v", err) + } else { + dests, err := fake.GetRealServers(vs) + if err != nil { + t.Errorf("Fail to get real servers of the virtual server, error: %v", err) + } + for _, dest := range dests { + if toRealServerKey(dest).String() == toRealServerKey(rsToDel).String() { + t.Errorf("Expect real server %q be deleted.", rsToDel.String()) + } + } + if err = fake.AddRealServer(vs, rsToDel); err != nil { + t.Errorf("Fail to add real server, error: %v", err) + } + } + } + // Test delete real server that not exist + rs := &utilipvs.RealServer{ + Address: net.ParseIP("172.16.2.4"), Port: uint16(8080), Weight: 1, } - err = fake.AddRealServer(vs, rs1) - if err != nil { - t.Errorf("Fail to add real server, error: %v", err) - } - // Add another real server to the virtual server - rs2 := &utilipvs.RealServer{ - Address: net.ParseIP("172.16.3.2"), - Port: uint16(8080), - Weight: 2, - } - err = fake.AddRealServer(vs, rs2) - if err != nil { - t.Errorf("Fail to add real server, error: %v", err) - } - // List all real servers of the virtual server - list, err := fake.GetRealServers(vs) - if err != nil { - t.Errorf("Fail to get real servers of the virtual server, error: %v", err) - } - if len(list) != 2 { - t.Errorf("Expect 2 virutal servers, got: %d", len(list)) - } - // Delete a real server of the virtual server - err = fake.DeleteRealServer(vs, rs2) - list, err = fake.GetRealServers(vs) - if err != nil { - t.Errorf("Fail to get real servers of the virtual server, error: %v", err) - } - if len(list) != 1 { - t.Errorf("Expect 1 real server, got: %d", len(list)) + if err = fake.DeleteRealServer(vs, rs); err == nil { + t.Errorf("Delete real server that not exist, Expect error, got nil") } // Delete the virtual server err = fake.DeleteVirtualServer(vs) diff --git a/pkg/util/keymutex/BUILD b/pkg/util/keymutex/BUILD index c954729108d..1f0d644afad 100644 --- a/pkg/util/keymutex/BUILD +++ b/pkg/util/keymutex/BUILD @@ -16,8 +16,8 @@ go_library( go_test( name = "go_default_test", srcs = ["keymutex_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/keymutex", - library = ":go_default_library", ) filegroup( diff --git a/pkg/util/keymutex/keymutex_test.go b/pkg/util/keymutex/keymutex_test.go index 2f9bd3e2683..c155a18af5d 100644 --- a/pkg/util/keymutex/keymutex_test.go +++ b/pkg/util/keymutex/keymutex_test.go @@ -91,21 +91,3 @@ func verifyCallbackDoesntHappens(t *testing.T, callbackCh <-chan interface{}) bo return true } } - -func verifyNoError(t *testing.T, err error, name string) { - if err != nil { - t.Fatalf("Unexpected response on %q. Expected: Actual: <%v>", name, err) - } -} - -func verifyError(t *testing.T, err error, name string) { - if err == nil { - t.Fatalf("Unexpected response on %q. Expected: Actual: ", name) - } -} - -func verifyMsg(t *testing.T, expected, actual string) { - if actual != expected { - t.Fatalf("Unexpected testMsg value. Expected: <%v> Actual: <%v>", expected, actual) - } -} diff --git a/pkg/util/labels/BUILD b/pkg/util/labels/BUILD index 96f42ae579d..1438ee12451 100644 --- a/pkg/util/labels/BUILD +++ b/pkg/util/labels/BUILD @@ -19,8 +19,8 @@ go_library( go_test( name = "go_default_test", srcs = ["labels_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/labels", - library = ":go_default_library", deps = ["//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library"], ) diff --git a/pkg/util/limitwriter/BUILD b/pkg/util/limitwriter/BUILD index e04f353a41b..e29c8910078 100644 --- a/pkg/util/limitwriter/BUILD +++ b/pkg/util/limitwriter/BUILD @@ -18,8 +18,8 @@ go_library( go_test( name = "go_default_test", srcs = ["limitwriter_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/limitwriter", - library = ":go_default_library", ) filegroup( diff --git a/pkg/util/metrics/BUILD b/pkg/util/metrics/BUILD index d0629b2b359..f14811992a6 100644 --- a/pkg/util/metrics/BUILD +++ b/pkg/util/metrics/BUILD @@ -21,8 +21,8 @@ go_library( go_test( name = "go_default_test", srcs = ["util_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/metrics", - library = ":go_default_library", deps = [ "//vendor/k8s.io/client-go/util/flowcontrol:go_default_library", ], diff --git a/pkg/util/metrics/util.go b/pkg/util/metrics/util.go index c1d92475622..3980ae818ad 100644 --- a/pkg/util/metrics/util.go +++ b/pkg/util/metrics/util.go @@ -34,40 +34,66 @@ const ( var ( metricsLock sync.Mutex - rateLimiterMetrics = make(map[string]prometheus.Gauge) + rateLimiterMetrics = make(map[string]rateLimiterMetric) ) +type rateLimiterMetric struct { + metric prometheus.Gauge + stopCh chan struct{} +} + func registerRateLimiterMetric(ownerName string) error { metricsLock.Lock() defer metricsLock.Unlock() if _, ok := rateLimiterMetrics[ownerName]; ok { - glog.Errorf("Metric for %v already registered", ownerName) - return fmt.Errorf("Metric for %v already registered", ownerName) + return fmt.Errorf("Rate Limiter Metric for %v already registered", ownerName) } metric := prometheus.NewGauge(prometheus.GaugeOpts{ Name: "rate_limiter_use", Subsystem: ownerName, Help: fmt.Sprintf("A metric measuring the saturation of the rate limiter for %v", ownerName), }) - rateLimiterMetrics[ownerName] = metric if err := prometheus.Register(metric); err != nil { return fmt.Errorf("error registering rate limiter usage metric: %v", err) } + stopCh := make(chan struct{}) + rateLimiterMetrics[ownerName] = rateLimiterMetric{ + metric: metric, + stopCh: stopCh, + } return nil } // RegisterMetricAndTrackRateLimiterUsage registers a metric ownerName_rate_limiter_use in prometheus to track // how much used rateLimiter is and starts a goroutine that updates this metric every updatePeriod func RegisterMetricAndTrackRateLimiterUsage(ownerName string, rateLimiter flowcontrol.RateLimiter) error { - err := registerRateLimiterMetric(ownerName) - if err != nil { + if err := registerRateLimiterMetric(ownerName); err != nil { return err } - go wait.Forever(func() { + go wait.Until(func() { metricsLock.Lock() defer metricsLock.Unlock() - rateLimiterMetrics[ownerName].Set(rateLimiter.Saturation()) - }, updatePeriod) + rateLimiterMetrics[ownerName].metric.Set(rateLimiter.Saturation()) + }, updatePeriod, rateLimiterMetrics[ownerName].stopCh) return nil } + +// UnregisterMetricAndUntrackRateLimiterUsage unregisters a metric ownerName_rate_limiter_use from prometheus and +// stops the goroutine that updates this metric +func UnregisterMetricAndUntrackRateLimiterUsage(ownerName string) bool { + metricsLock.Lock() + defer metricsLock.Unlock() + + rlm, ok := rateLimiterMetrics[ownerName] + if !ok { + glog.Warningf("Rate Limiter Metric for %v not registered", ownerName) + return false + } + + close(rlm.stopCh) + prometheus.Unregister(rlm.metric) + delete(rateLimiterMetrics, ownerName) + + return true +} diff --git a/pkg/util/metrics/util_test.go b/pkg/util/metrics/util_test.go index 38e14b577b4..ee83aa1b7e5 100644 --- a/pkg/util/metrics/util_test.go +++ b/pkg/util/metrics/util_test.go @@ -34,6 +34,11 @@ func TestRegisterMetricAndTrackRateLimiterUsage(t *testing.T) { rateLimiter: flowcontrol.NewTokenBucketRateLimiter(1, 1), err: "", }, + { + ownerName: "owner_name", + rateLimiter: flowcontrol.NewTokenBucketRateLimiter(1, 1), + err: "already registered", + }, { ownerName: "invalid-owner-name", rateLimiter: flowcontrol.NewTokenBucketRateLimiter(1, 1), @@ -52,3 +57,27 @@ func TestRegisterMetricAndTrackRateLimiterUsage(t *testing.T) { } } } + +func TestUnregisterMetricAndUntrackRateLimiterUsage(t *testing.T) { + RegisterMetricAndTrackRateLimiterUsage("owner_name", flowcontrol.NewTokenBucketRateLimiter(1, 1)) + testCases := []struct { + ownerName string + ok bool + }{ + { + ownerName: "owner_name", + ok: true, + }, + { + ownerName: "owner_name", + ok: false, + }, + } + + for i, tc := range testCases { + ok := UnregisterMetricAndUntrackRateLimiterUsage(tc.ownerName) + if tc.ok != ok { + t.Errorf("Case[%d] Expected %v, got %v", i, tc.ok, ok) + } + } +} diff --git a/pkg/util/mount/BUILD b/pkg/util/mount/BUILD index 023fad7ba35..4cc9f1c27d5 100644 --- a/pkg/util/mount/BUILD +++ b/pkg/util/mount/BUILD @@ -13,15 +13,61 @@ go_library( "exec.go", "fake.go", "mount.go", - "mount_unsupported.go", - "nsenter_mount_unsupported.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "exec_mount_unsupported.go", + "mount_unsupported.go", + "nsenter_mount_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "exec_mount_unsupported.go", + "mount_unsupported.go", + "nsenter_mount_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "exec_mount_unsupported.go", + "mount_unsupported.go", + "nsenter_mount_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "exec_mount_unsupported.go", + "mount_unsupported.go", + "nsenter_mount_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "exec_mount.go", "mount_linux.go", "nsenter_mount.go", ], - "@io_bazel_rules_go//go/platform:windows_amd64": [ + "@io_bazel_rules_go//go/platform:nacl": [ + "exec_mount_unsupported.go", + "mount_unsupported.go", + "nsenter_mount_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "exec_mount_unsupported.go", + "mount_unsupported.go", + "nsenter_mount_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "exec_mount_unsupported.go", + "mount_unsupported.go", + "nsenter_mount_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "exec_mount_unsupported.go", + "mount_unsupported.go", + "nsenter_mount_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "exec_mount_unsupported.go", + "mount_unsupported.go", + "nsenter_mount_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "exec_mount_unsupported.go", "mount_windows.go", + "nsenter_mount_unsupported.go", ], "//conditions:default": [], }), @@ -30,7 +76,7 @@ go_library( "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//pkg/util/io:go_default_library", "//pkg/util/nsenter:go_default_library", "//vendor/golang.org/x/sys/unix:go_default_library", @@ -45,18 +91,21 @@ go_test( srcs = [ "safe_format_and_mount_test.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ + "exec_mount_test.go", "mount_linux_test.go", "nsenter_mount_test.go", ], - "@io_bazel_rules_go//go/platform:windows_amd64": [ + "@io_bazel_rules_go//go/platform:windows": [ "mount_windows_test.go", ], "//conditions:default": [], }), + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/mount", - library = ":go_default_library", - deps = ["//vendor/k8s.io/utils/exec/testing:go_default_library"], + deps = [ + "//vendor/k8s.io/utils/exec/testing:go_default_library", + ], ) filegroup( diff --git a/pkg/util/mount/exec_mount.go b/pkg/util/mount/exec_mount.go new file mode 100644 index 00000000000..1dedc5b7aea --- /dev/null +++ b/pkg/util/mount/exec_mount.go @@ -0,0 +1,140 @@ +// +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 mount + +import ( + "fmt" + + "github.com/golang/glog" +) + +// ExecMounter is a mounter that uses provided Exec interface to mount and +// unmount a filesystem. For all other calls it uses a wrapped mounter. +type execMounter struct { + wrappedMounter Interface + exec Exec +} + +func NewExecMounter(exec Exec, wrapped Interface) Interface { + return &execMounter{ + wrappedMounter: wrapped, + exec: exec, + } +} + +// execMounter implements mount.Interface +var _ Interface = &execMounter{} + +// Mount runs mount(8) using given exec interface. +func (m *execMounter) Mount(source string, target string, fstype string, options []string) error { + bind, bindRemountOpts := isBind(options) + + if bind { + err := m.doExecMount(source, target, fstype, []string{"bind"}) + if err != nil { + return err + } + return m.doExecMount(source, target, fstype, bindRemountOpts) + } + + return m.doExecMount(source, target, fstype, options) +} + +// doExecMount calls exec(mount ) using given exec interface. +func (m *execMounter) doExecMount(source, target, fstype string, options []string) error { + glog.V(5).Infof("Exec Mounting %s %s %s %v", source, target, fstype, options) + mountArgs := makeMountArgs(source, target, fstype, options) + output, err := m.exec.Run("mount", mountArgs...) + glog.V(5).Infof("Exec mounted %v: %v: %s", mountArgs, err, string(output)) + if err != nil { + return fmt.Errorf("mount failed: %v\nMounting command: %s\nMounting arguments: %s %s %s %v\nOutput: %s\n", + err, "mount", source, target, fstype, options, string(output)) + } + + return err +} + +// Unmount runs umount(8) using given exec interface. +func (m *execMounter) Unmount(target string) error { + outputBytes, err := m.exec.Run("umount", target) + if err == nil { + glog.V(5).Infof("Exec unmounted %s: %s", target, string(outputBytes)) + } else { + glog.V(5).Infof("Failed to exec unmount %s: err: %q, umount output: %s", target, err, string(outputBytes)) + } + + return err +} + +// List returns a list of all mounted filesystems. +func (m *execMounter) List() ([]MountPoint, error) { + return m.wrappedMounter.List() +} + +// IsLikelyNotMountPoint determines whether a path is a mountpoint. +func (m *execMounter) IsLikelyNotMountPoint(file string) (bool, error) { + return m.wrappedMounter.IsLikelyNotMountPoint(file) +} + +// DeviceOpened checks if block device in use by calling Open with O_EXCL flag. +// Returns true if open returns errno EBUSY, and false if errno is nil. +// Returns an error if errno is any error other than EBUSY. +// Returns with error if pathname is not a device. +func (m *execMounter) DeviceOpened(pathname string) (bool, error) { + return m.wrappedMounter.DeviceOpened(pathname) +} + +// PathIsDevice uses FileInfo returned from os.Stat to check if path refers +// to a device. +func (m *execMounter) PathIsDevice(pathname string) (bool, error) { + return m.wrappedMounter.PathIsDevice(pathname) +} + +//GetDeviceNameFromMount given a mount point, find the volume id from checking /proc/mounts +func (m *execMounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) { + return m.wrappedMounter.GetDeviceNameFromMount(mountPath, pluginDir) +} + +func (m *execMounter) IsMountPointMatch(mp MountPoint, dir string) bool { + return m.wrappedMounter.IsMountPointMatch(mp, dir) +} + +func (m *execMounter) IsNotMountPoint(dir string) (bool, error) { + return m.wrappedMounter.IsNotMountPoint(dir) +} + +func (m *execMounter) MakeRShared(path string) error { + return m.wrappedMounter.MakeRShared(path) +} + +func (m *execMounter) GetFileType(pathname string) (FileType, error) { + return m.wrappedMounter.GetFileType(pathname) +} + +func (m *execMounter) MakeFile(pathname string) error { + return m.wrappedMounter.MakeFile(pathname) +} + +func (m *execMounter) MakeDir(pathname string) error { + return m.wrappedMounter.MakeDir(pathname) +} + +func (m *execMounter) ExistsPath(pathname string) bool { + return m.wrappedMounter.ExistsPath(pathname) +} diff --git a/pkg/util/mount/exec_mount_test.go b/pkg/util/mount/exec_mount_test.go new file mode 100644 index 00000000000..5882477f71e --- /dev/null +++ b/pkg/util/mount/exec_mount_test.go @@ -0,0 +1,153 @@ +// +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 mount + +import ( + "fmt" + "reflect" + "strings" + "testing" +) + +var ( + sourcePath = "/mnt/srv" + destinationPath = "/mnt/dst" + fsType = "xfs" + mountOptions = []string{"vers=1", "foo=bar"} +) + +func TestMount(t *testing.T) { + exec := NewFakeExec(func(cmd string, args ...string) ([]byte, error) { + if cmd != "mount" { + t.Errorf("expected mount command, got %q", cmd) + } + // mount -t fstype -o options source target + expectedArgs := []string{"-t", fsType, "-o", strings.Join(mountOptions, ","), sourcePath, destinationPath} + if !reflect.DeepEqual(expectedArgs, args) { + t.Errorf("expected arguments %q, got %q", strings.Join(expectedArgs, " "), strings.Join(args, " ")) + } + return nil, nil + }) + + wrappedMounter := &fakeMounter{t} + mounter := NewExecMounter(exec, wrappedMounter) + + mounter.Mount(sourcePath, destinationPath, fsType, mountOptions) +} + +func TestBindMount(t *testing.T) { + cmdCount := 0 + exec := NewFakeExec(func(cmd string, args ...string) ([]byte, error) { + cmdCount++ + if cmd != "mount" { + t.Errorf("expected mount command, got %q", cmd) + } + var expectedArgs []string + switch cmdCount { + case 1: + // mount -t fstype -o "bind" source target + expectedArgs = []string{"-t", fsType, "-o", "bind", sourcePath, destinationPath} + case 2: + // mount -t fstype -o "remount,opts" source target + expectedArgs = []string{"-t", fsType, "-o", "remount," + strings.Join(mountOptions, ","), sourcePath, destinationPath} + } + if !reflect.DeepEqual(expectedArgs, args) { + t.Errorf("expected arguments %q, got %q", strings.Join(expectedArgs, " "), strings.Join(args, " ")) + } + return nil, nil + }) + + wrappedMounter := &fakeMounter{t} + mounter := NewExecMounter(exec, wrappedMounter) + bindOptions := append(mountOptions, "bind") + mounter.Mount(sourcePath, destinationPath, fsType, bindOptions) +} + +func TestUnmount(t *testing.T) { + exec := NewFakeExec(func(cmd string, args ...string) ([]byte, error) { + if cmd != "umount" { + t.Errorf("expected unmount command, got %q", cmd) + } + // unmount $target + expectedArgs := []string{destinationPath} + if !reflect.DeepEqual(expectedArgs, args) { + t.Errorf("expected arguments %q, got %q", strings.Join(expectedArgs, " "), strings.Join(args, " ")) + } + return nil, nil + }) + + wrappedMounter := &fakeMounter{t} + mounter := NewExecMounter(exec, wrappedMounter) + + mounter.Unmount(destinationPath) +} + +/* Fake wrapped mounter */ +type fakeMounter struct { + t *testing.T +} + +func (fm *fakeMounter) Mount(source string, target string, fstype string, options []string) error { + // Mount() of wrapped mounter should never be called. We call exec instead. + fm.t.Errorf("Unexpected wrapped mount call") + return fmt.Errorf("Unexpected wrapped mount call") +} + +func (fm *fakeMounter) Unmount(target string) error { + // umount() of wrapped mounter should never be called. We call exec instead. + fm.t.Errorf("Unexpected wrapped mount call") + return fmt.Errorf("Unexpected wrapped mount call") +} + +func (fm *fakeMounter) List() ([]MountPoint, error) { + return nil, nil +} +func (fm *fakeMounter) IsMountPointMatch(mp MountPoint, dir string) bool { + return false +} +func (fm *fakeMounter) IsNotMountPoint(file string) (bool, error) { + return false, nil +} +func (fm *fakeMounter) IsLikelyNotMountPoint(file string) (bool, error) { + return false, nil +} +func (fm *fakeMounter) DeviceOpened(pathname string) (bool, error) { + return false, nil +} +func (fm *fakeMounter) PathIsDevice(pathname string) (bool, error) { + return false, nil +} +func (fm *fakeMounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) { + return "", nil +} +func (fm *fakeMounter) MakeRShared(path string) error { + return nil +} +func (fm *fakeMounter) MakeFile(pathname string) error { + return nil +} +func (fm *fakeMounter) MakeDir(pathname string) error { + return nil +} +func (fm *fakeMounter) ExistsPath(pathname string) bool { + return false +} +func (fm *fakeMounter) GetFileType(pathname string) (FileType, error) { + return FileTypeFile, nil +} diff --git a/pkg/util/mount/exec_mount_unsupported.go b/pkg/util/mount/exec_mount_unsupported.go new file mode 100644 index 00000000000..136704b23e2 --- /dev/null +++ b/pkg/util/mount/exec_mount_unsupported.go @@ -0,0 +1,87 @@ +// +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 mount + +import ( + "errors" +) + +type execMounter struct{} + +// ExecMounter is a mounter that uses provided Exec interface to mount and +// unmount a filesystem. For all other calls it uses a wrapped mounter. +func NewExecMounter(exec Exec, wrapped Interface) Interface { + return &execMounter{} +} + +func (mounter *execMounter) Mount(source string, target string, fstype string, options []string) error { + return nil +} + +func (mounter *execMounter) Unmount(target string) error { + return nil +} + +func (mounter *execMounter) List() ([]MountPoint, error) { + return []MountPoint{}, nil +} + +func (mounter *execMounter) IsMountPointMatch(mp MountPoint, dir string) bool { + return (mp.Path == dir) +} + +func (mounter *execMounter) IsNotMountPoint(dir string) (bool, error) { + return IsNotMountPoint(mounter, dir) +} + +func (mounter *execMounter) IsLikelyNotMountPoint(file string) (bool, error) { + return true, nil +} + +func (mounter *execMounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) { + return "", nil +} + +func (mounter *execMounter) DeviceOpened(pathname string) (bool, error) { + return false, nil +} + +func (mounter *execMounter) PathIsDevice(pathname string) (bool, error) { + return true, nil +} + +func (mounter *execMounter) MakeRShared(path string) error { + return nil +} + +func (mounter *execMounter) GetFileType(pathname string) (FileType, error) { + return FileType("fake"), errors.New("not implemented") +} + +func (mounter *execMounter) MakeDir(pathname string) error { + return nil +} + +func (mounter *execMounter) MakeFile(pathname string) error { + return nil +} + +func (mounter *execMounter) ExistsPath(pathname string) bool { + return true +} diff --git a/pkg/util/mount/fake.go b/pkg/util/mount/fake.go index d34510ae2a8..f4e2e411de1 100644 --- a/pkg/util/mount/fake.go +++ b/pkg/util/mount/fake.go @@ -17,6 +17,7 @@ limitations under the License. package mount import ( + "os" "path/filepath" "sync" @@ -136,6 +137,11 @@ func (f *FakeMounter) IsLikelyNotMountPoint(file string) (bool, error) { f.mutex.Lock() defer f.mutex.Unlock() + _, err := os.Stat(file) + if err != nil { + return true, err + } + // If file is a symlink, get its absolute path absFile, err := filepath.EvalSymlinks(file) if err != nil { diff --git a/pkg/util/mount/mount.go b/pkg/util/mount/mount.go index 1c09e157f8e..953b571900a 100644 --- a/pkg/util/mount/mount.go +++ b/pkg/util/mount/mount.go @@ -19,12 +19,7 @@ limitations under the License. package mount import ( - "fmt" - "path" "path/filepath" - "strings" - - "github.com/golang/glog" ) type FileType string @@ -138,41 +133,6 @@ func (mounter *SafeFormatAndMount) FormatAndMount(source string, target string, return mounter.formatAndMount(source, target, fstype, options) } -// GetMountRefs finds all other references to the device referenced -// by mountPath; returns a list of paths. -func GetMountRefs(mounter Interface, mountPath string) ([]string, error) { - mps, err := mounter.List() - if err != nil { - return nil, err - } - // Find the device name. - deviceName := "" - // If mountPath is symlink, need get its target path. - slTarget, err := filepath.EvalSymlinks(mountPath) - if err != nil { - slTarget = mountPath - } - for i := range mps { - if mps[i].Path == slTarget { - deviceName = mps[i].Device - break - } - } - - // Find all references to the device. - var refs []string - if deviceName == "" { - glog.Warningf("could not determine device for path: %q", mountPath) - } else { - for i := range mps { - if mps[i].Device == deviceName && mps[i].Path != slTarget { - refs = append(refs, mps[i].Path) - } - } - } - return refs, nil -} - // GetMountRefsByDev finds all references to the device provided // by mountPath; returns a list of paths. func GetMountRefsByDev(mounter Interface, mountPath string) ([]string, error) { @@ -239,34 +199,6 @@ func GetDeviceNameFromMount(mounter Interface, mountPath string) (string, int, e return device, refCount, nil } -// getDeviceNameFromMount find the device name from /proc/mounts in which -// the mount path reference should match the given plugin directory. In case no mount path reference -// matches, returns the volume name taken from its given mountPath -func getDeviceNameFromMount(mounter Interface, mountPath, pluginDir string) (string, error) { - refs, err := GetMountRefs(mounter, mountPath) - if err != nil { - glog.V(4).Infof("GetMountRefs failed for mount path %q: %v", mountPath, err) - return "", err - } - if len(refs) == 0 { - glog.V(4).Infof("Directory %s is not mounted", mountPath) - return "", fmt.Errorf("directory %s is not mounted", mountPath) - } - basemountPath := path.Join(pluginDir, MountsInGlobalPDPath) - for _, ref := range refs { - if strings.HasPrefix(ref, basemountPath) { - volumeID, err := filepath.Rel(basemountPath, ref) - if err != nil { - glog.Errorf("Failed to get volume id from mount %s - %v", mountPath, err) - return "", err - } - return volumeID, nil - } - } - - return path.Base(mountPath), nil -} - // IsNotMountPoint determines if a directory is a mountpoint. // It should return ErrNotExist when the directory does not exist. // This method uses the List() of all mountpoints diff --git a/pkg/util/mount/mount_linux.go b/pkg/util/mount/mount_linux.go index 8976bd25542..71064f321e3 100644 --- a/pkg/util/mount/mount_linux.go +++ b/pkg/util/mount/mount_linux.go @@ -22,6 +22,8 @@ import ( "fmt" "os" "os/exec" + "path" + "path/filepath" "strconv" "strings" "syscall" @@ -141,6 +143,41 @@ func (m *Mounter) doMount(mounterPath string, mountCmd string, source string, ta return err } +// GetMountRefs finds all other references to the device referenced +// by mountPath; returns a list of paths. +func GetMountRefs(mounter Interface, mountPath string) ([]string, error) { + mps, err := mounter.List() + if err != nil { + return nil, err + } + // Find the device name. + deviceName := "" + // If mountPath is symlink, need get its target path. + slTarget, err := filepath.EvalSymlinks(mountPath) + if err != nil { + slTarget = mountPath + } + for i := range mps { + if mps[i].Path == slTarget { + deviceName = mps[i].Device + break + } + } + + // Find all references to the device. + var refs []string + if deviceName == "" { + glog.Warningf("could not determine device for path: %q", mountPath) + } else { + for i := range mps { + if mps[i].Device == deviceName && mps[i].Path != slTarget { + refs = append(refs, mps[i].Path) + } + } + } + return refs, nil +} + // detectSystemd returns true if OS runs with systemd as init. When not sure // (permission errors, ...), it returns false. // There may be different ways how to detect systemd, this one makes sure that @@ -299,6 +336,34 @@ func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (str return getDeviceNameFromMount(mounter, mountPath, pluginDir) } +// getDeviceNameFromMount find the device name from /proc/mounts in which +// the mount path reference should match the given plugin directory. In case no mount path reference +// matches, returns the volume name taken from its given mountPath +func getDeviceNameFromMount(mounter Interface, mountPath, pluginDir string) (string, error) { + refs, err := GetMountRefs(mounter, mountPath) + if err != nil { + glog.V(4).Infof("GetMountRefs failed for mount path %q: %v", mountPath, err) + return "", err + } + if len(refs) == 0 { + glog.V(4).Infof("Directory %s is not mounted", mountPath) + return "", fmt.Errorf("directory %s is not mounted", mountPath) + } + basemountPath := path.Join(pluginDir, MountsInGlobalPDPath) + for _, ref := range refs { + if strings.HasPrefix(ref, basemountPath) { + volumeID, err := filepath.Rel(basemountPath, ref) + if err != nil { + glog.Errorf("Failed to get volume id from mount %s - %v", mountPath, err) + return "", err + } + return volumeID, nil + } + } + + return path.Base(mountPath), nil +} + func listProcMounts(mountFilePath string) ([]MountPoint, error) { content, err := utilio.ConsistentRead(mountFilePath, maxListTries) if err != nil { @@ -433,7 +498,7 @@ func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, if mountErr != nil { // Mount failed. This indicates either that the disk is unformatted or // it contains an unexpected filesystem. - existingFormat, err := mounter.getDiskFormat(source) + existingFormat, err := mounter.GetDiskFormat(source) if err != nil { return err } @@ -471,8 +536,8 @@ func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, return mountErr } -// getDiskFormat uses 'lsblk' to see if the given disk is unformated -func (mounter *SafeFormatAndMount) getDiskFormat(disk string) (string, error) { +// GetDiskFormat uses 'lsblk' to see if the given disk is unformated +func (mounter *SafeFormatAndMount) GetDiskFormat(disk string) (string, error) { args := []string{"-n", "-o", "FSTYPE", disk} glog.V(4).Infof("Attempting to determine if disk %q is formatted using lsblk with args: (%v)", disk, args) dataOut, err := mounter.Exec.Run("lsblk", args...) diff --git a/pkg/util/mount/mount_unsupported.go b/pkg/util/mount/mount_unsupported.go index 865d53d09e5..87d1e374819 100644 --- a/pkg/util/mount/mount_unsupported.go +++ b/pkg/util/mount/mount_unsupported.go @@ -43,6 +43,12 @@ func (mounter *Mounter) Unmount(target string) error { return nil } +// GetMountRefs finds all other references to the device referenced +// by mountPath; returns a list of paths. +func GetMountRefs(mounter Interface, mountPath string) ([]string, error) { + return []string{}, nil +} + func (mounter *Mounter) List() ([]MountPoint, error) { return []MountPoint{}, nil } @@ -63,6 +69,10 @@ func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (str return "", nil } +func getDeviceNameFromMount(mounter Interface, mountPath, pluginDir string) (string, error) { + return "", nil +} + func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) { return false, nil } @@ -76,7 +86,7 @@ func (mounter *Mounter) MakeRShared(path string) error { } func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, fstype string, options []string) error { - return nil + return mounter.Interface.Mount(source, target, fstype, options) } func (mounter *SafeFormatAndMount) diskLooksUnformatted(disk string) (bool, error) { diff --git a/pkg/util/mount/mount_windows.go b/pkg/util/mount/mount_windows.go index 50c95caa528..b39951add1a 100644 --- a/pkg/util/mount/mount_windows.go +++ b/pkg/util/mount/mount_windows.go @@ -22,6 +22,7 @@ import ( "fmt" "os" "os/exec" + "path" "path/filepath" "strconv" "strings" @@ -84,12 +85,8 @@ func (mounter *Mounter) Mount(source string, target string, fstype string, optio `$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, $PWord`, options[0], options[1]) - driverLetter, err := getAvailableDriveLetter() - if err != nil { - return err - } - bindSource = driverLetter + ":" - cmdLine += fmt.Sprintf(";New-SmbGlobalMapping -LocalPath %s -RemotePath %s -Credential $Credential", bindSource, source) + bindSource = source + cmdLine += fmt.Sprintf(";New-SmbGlobalMapping -RemotePath %s -Credential $Credential", source) if output, err := exec.Command("powershell", "/c", cmdLine).CombinedOutput(); err != nil { // we don't return error here, even though New-SmbGlobalMapping failed, we still make it successful, @@ -118,6 +115,16 @@ func (mounter *Mounter) Unmount(target string) error { return nil } +// GetMountRefs finds all other references to the device(drive) referenced +// by mountPath; returns a list of paths. +func GetMountRefs(mounter Interface, mountPath string) ([]string, error) { + refs, err := getAllParentLinks(normalizeWindowsPath(mountPath)) + if err != nil { + return nil, err + } + return refs, nil +} + // List returns a list of all mounted filesystems. todo func (mounter *Mounter) List() ([]MountPoint, error) { return []MountPoint{}, nil @@ -152,6 +159,33 @@ func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (str return getDeviceNameFromMount(mounter, mountPath, pluginDir) } +// getDeviceNameFromMount find the device(drive) name in which +// the mount path reference should match the given plugin directory. In case no mount path reference +// matches, returns the volume name taken from its given mountPath +func getDeviceNameFromMount(mounter Interface, mountPath, pluginDir string) (string, error) { + refs, err := GetMountRefs(mounter, mountPath) + if err != nil { + glog.V(4).Infof("GetMountRefs failed for mount path %q: %v", mountPath, err) + return "", err + } + if len(refs) == 0 { + return "", fmt.Errorf("directory %s is not mounted", mountPath) + } + basemountPath := normalizeWindowsPath(path.Join(pluginDir, MountsInGlobalPDPath)) + for _, ref := range refs { + if strings.Contains(ref, basemountPath) { + volumeID, err := filepath.Rel(normalizeWindowsPath(basemountPath), ref) + if err != nil { + glog.Errorf("Failed to get volume id from mount %s - %v", mountPath, err) + return "", err + } + return volumeID, nil + } + } + + return path.Base(mountPath), nil +} + // DeviceOpened determines if the device is in use elsewhere func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) { return false, nil @@ -260,20 +294,6 @@ func normalizeWindowsPath(path string) string { return normalizedPath } -func getAvailableDriveLetter() (string, error) { - cmd := "$used = Get-PSDrive | Select-Object -Expand Name | Where-Object { $_.Length -eq 1 }" - cmd += ";$drive = 67..90 | ForEach-Object { [string][char]$_ } | Where-Object { $used -notcontains $_ } | Select-Object -First 1;$drive" - output, err := exec.Command("powershell", "/c", cmd).CombinedOutput() - if err != nil { - return "", fmt.Errorf("getAvailableDriveLetter failed: %v, output: %q", err, string(output)) - } - - if len(output) == 0 { - return "", fmt.Errorf("azureMount: there is no available drive letter now") - } - return string(output)[:1], nil -} - // ValidateDiskNumber : disk number should be a number in [0, 99] func ValidateDiskNumber(disk string) error { diskNum, err := strconv.Atoi(disk) @@ -300,3 +320,30 @@ func getDriveLetterByDiskNumber(diskNum string, exec Exec) (string, error) { } return string(output)[:1], nil } + +// getAllParentLinks walks all symbolic links and return all the parent targets recursively +func getAllParentLinks(path string) ([]string, error) { + const maxIter = 255 + links := []string{} + for { + links = append(links, path) + if len(links) > maxIter { + return links, fmt.Errorf("unexpected length of parent links: %v", links) + } + + fi, err := os.Lstat(path) + if err != nil { + return links, fmt.Errorf("Lstat: %v", err) + } + if fi.Mode()&os.ModeSymlink == 0 { + break + } + + path, err = os.Readlink(path) + if err != nil { + return links, fmt.Errorf("Readlink error: %v", err) + } + } + + return links, nil +} diff --git a/pkg/util/mount/mount_windows_test.go b/pkg/util/mount/mount_windows_test.go index 6be0fc43e13..5855ede9adb 100644 --- a/pkg/util/mount/mount_windows_test.go +++ b/pkg/util/mount/mount_windows_test.go @@ -19,15 +19,11 @@ limitations under the License. package mount import ( + "fmt" + "os/exec" "testing" ) -func TestGetAvailableDriveLetter(t *testing.T) { - if _, err := getAvailableDriveLetter(); err != nil { - t.Errorf("getAvailableDriveLetter test failed : %v", err) - } -} - func TestNormalizeWindowsPath(t *testing.T) { path := `/var/lib/kubelet/pods/146f8428-83e7-11e7-8dd4-000d3a31dac4/volumes/kubernetes.io~azure-disk` normalizedPath := normalizeWindowsPath(path) @@ -69,3 +65,70 @@ func TestValidateDiskNumber(t *testing.T) { t.Errorf("TestValidateDiskNumber test failed, disk number : %s", diskNum) } } + +func makeLink(link, target string) error { + if output, err := exec.Command("cmd", "/c", "mklink", "/D", link, target).CombinedOutput(); err != nil { + return fmt.Errorf("mklink failed: %v, link(%q) target(%q) output: %q", err, link, target, string(output)) + } + return nil +} + +func removeLink(link string) error { + if output, err := exec.Command("cmd", "/c", "rmdir", link).CombinedOutput(); err != nil { + return fmt.Errorf("rmdir failed: %v, output: %q", err, string(output)) + } + return nil +} + +func setEquivalent(set1, set2 []string) bool { + map1 := make(map[string]bool) + map2 := make(map[string]bool) + for _, s := range set1 { + map1[s] = true + } + for _, s := range set2 { + map2[s] = true + } + + for s := range map1 { + if !map2[s] { + return false + } + } + for s := range map2 { + if !map1[s] { + return false + } + } + return true +} + +// this func must run in admin mode, otherwise it will fail +func TestGetMountRefs(t *testing.T) { + fm := &FakeMounter{MountPoints: []MountPoint{}} + mountPath := `c:\secondmountpath` + expectedRefs := []string{`c:\`, `c:\firstmountpath`, mountPath} + + // remove symbolic links first + for i := 1; i < len(expectedRefs); i++ { + removeLink(expectedRefs[i]) + } + + // create symbolic links + for i := 1; i < len(expectedRefs); i++ { + if err := makeLink(expectedRefs[i], expectedRefs[i-1]); err != nil { + t.Errorf("makeLink failed: %v", err) + } + } + + if refs, err := GetMountRefs(fm, mountPath); err != nil || !setEquivalent(expectedRefs, refs) { + t.Errorf("getMountRefs(%q) = %v, error: %v; expected %v", mountPath, refs, err, expectedRefs) + } + + // remove symbolic links + for i := 1; i < len(expectedRefs); i++ { + if err := removeLink(expectedRefs[i]); err != nil { + t.Errorf("removeLink failed: %v", err) + } + } +} diff --git a/pkg/util/mount/safe_format_and_mount_test.go b/pkg/util/mount/safe_format_and_mount_test.go index 72b768f3bf4..a7e7cc29a23 100644 --- a/pkg/util/mount/safe_format_and_mount_test.go +++ b/pkg/util/mount/safe_format_and_mount_test.go @@ -18,6 +18,8 @@ package mount import ( "fmt" + "io/ioutil" + "os" "runtime" "testing" @@ -50,6 +52,11 @@ func TestSafeFormatAndMount(t *testing.T) { if runtime.GOOS == "darwin" || runtime.GOOS == "windows" { t.Skipf("not supported on GOOS=%s", runtime.GOOS) } + mntDir, err := ioutil.TempDir(os.TempDir(), "mount") + if err != nil { + t.Fatalf("failed to create tmp dir: %v", err) + } + defer os.RemoveAll(mntDir) tests := []struct { description string fstype string @@ -207,7 +214,7 @@ func TestSafeFormatAndMount(t *testing.T) { } device := "/dev/foo" - dest := "/mnt/bar" + dest := mntDir err := mounter.FormatAndMount(device, dest, test.fstype, test.mountOptions) if test.expectedError == nil { if err != nil { diff --git a/pkg/util/net/sets/BUILD b/pkg/util/net/sets/BUILD index 413f5e39053..035282d5c13 100644 --- a/pkg/util/net/sets/BUILD +++ b/pkg/util/net/sets/BUILD @@ -18,8 +18,8 @@ go_library( go_test( name = "go_default_test", srcs = ["ipnet_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/net/sets", - library = ":go_default_library", ) filegroup( diff --git a/pkg/util/netsh/BUILD b/pkg/util/netsh/BUILD index bd305a7296f..2781e51d3cf 100644 --- a/pkg/util/netsh/BUILD +++ b/pkg/util/netsh/BUILD @@ -3,6 +3,7 @@ package(default_visibility = ["//visibility:public"]) load( "@io_bazel_rules_go//go:def.bzl", "go_library", + "go_test", ) go_library( @@ -33,3 +34,16 @@ filegroup( ], tags = ["automanaged"], ) + +go_test( + name = "go_default_test", + srcs = ["netsh_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/util/netsh", + deps = [ + "//vendor/github.com/pkg/errors:go_default_library", + "//vendor/github.com/stretchr/testify/assert:go_default_library", + "//vendor/k8s.io/utils/exec:go_default_library", + "//vendor/k8s.io/utils/exec/testing:go_default_library", + ], +) diff --git a/pkg/util/netsh/netsh.go b/pkg/util/netsh/netsh.go index c3b963c411b..30b66536b1d 100644 --- a/pkg/util/netsh/netsh.go +++ b/pkg/util/netsh/netsh.go @@ -190,9 +190,8 @@ func checkIPExists(ipToCheck string, args []string, runner *runner) (bool, error glog.V(3).Infof("Searching for IP: %v in IP dump: %v", ipToCheck, ipAddressString) showAddressArray := strings.Split(ipAddressString, "\n") for _, showAddress := range showAddressArray { - if strings.Contains(showAddress, "IP Address:") { - ipFromNetsh := strings.TrimLeft(showAddress, "IP Address:") - ipFromNetsh = strings.TrimSpace(ipFromNetsh) + if strings.Contains(showAddress, "IP") { + ipFromNetsh := getIP(showAddress) if ipFromNetsh == ipToCheck { return true, nil } @@ -201,3 +200,12 @@ func checkIPExists(ipToCheck string, args []string, runner *runner) (bool, error return false, nil } + +// getIP gets ip from showAddress (e.g. "IP Address: 10.96.0.4"). +func getIP(showAddress string) string { + list := strings.SplitN(showAddress, ":", 2) + if len(list) != 2 { + return "" + } + return strings.TrimSpace(list[1]) +} diff --git a/pkg/util/netsh/netsh_test.go b/pkg/util/netsh/netsh_test.go new file mode 100644 index 00000000000..2194c8318ea --- /dev/null +++ b/pkg/util/netsh/netsh_test.go @@ -0,0 +1,467 @@ +/* +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 netsh + +import ( + "net" + "os" + "testing" + + "k8s.io/utils/exec" + fakeexec "k8s.io/utils/exec/testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" +) + +func fakeCommonRunner() *runner { + fakeCmd := fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ + // Success + func() ([]byte, error) { + return []byte{}, nil + }, + // utilexec.ExitError exists, and status is not 0 + func() ([]byte, error) { + return nil, &fakeexec.FakeExitError{Status: 1} + }, + // utilexec.ExitError exists, and status is 0 + func() ([]byte, error) { + return nil, &fakeexec.FakeExitError{Status: 0} + }, + // other error exists + func() ([]byte, error) { + return nil, errors.New("not ExitError") + }, + }, + } + + return &runner{ + exec: &fakeexec.FakeExec{ + CommandScript: []fakeexec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { + return fakeexec.InitFakeCmd(&fakeCmd, cmd, args...) + }, + func(cmd string, args ...string) exec.Cmd { + return fakeexec.InitFakeCmd(&fakeCmd, cmd, args...) + }, + func(cmd string, args ...string) exec.Cmd { + return fakeexec.InitFakeCmd(&fakeCmd, cmd, args...) + }, + func(cmd string, args ...string) exec.Cmd { + return fakeexec.InitFakeCmd(&fakeCmd, cmd, args...) + }, + }, + }, + } +} + +func TestEnsurePortProxyRule(t *testing.T) { + runner := fakeCommonRunner() + + tests := []struct { + name string + arguments []string + expectedResult bool + expectedError bool + }{ + {"Success", []string{"ensure-port-proxy-rule"}, true, false}, + {"utilexec.ExitError exists, and status is not 0", []string{"ensure-port-proxy-rule"}, false, false}, + {"utilexec.ExitError exists, and status is 0", []string{"ensure-port-proxy-rule"}, false, true}, + {"other error exists", []string{"ensure-port-proxy-rule"}, false, true}, + } + + for _, test := range tests { + result, err := runner.EnsurePortProxyRule(test.arguments) + if test.expectedError { + assert.Errorf(t, err, "Failed to test: %s", test.name) + } else { + if err != nil { + assert.NoErrorf(t, err, "Failed to test: %s", test.name) + } else { + assert.EqualValuesf(t, test.expectedResult, result, "Failed to test: %s", test.name) + } + } + } + +} + +func TestDeletePortProxyRule(t *testing.T) { + runner := fakeCommonRunner() + + tests := []struct { + name string + arguments []string + expectedError bool + }{ + {"Success", []string{"delete-port-proxy-rule"}, false}, + {"utilexec.ExitError exists, and status is not 0", []string{"delete-port-proxy-rule"}, true}, + {"utilexec.ExitError exists, and status is 0", []string{"delete-port-proxy-rule"}, false}, + {"other error exists", []string{"delete-port-proxy-rule"}, true}, + } + + for _, test := range tests { + err := runner.DeletePortProxyRule(test.arguments) + if test.expectedError { + assert.Errorf(t, err, "Failed to test: %s", test.name) + } else { + assert.NoErrorf(t, err, "Failed to test: %s", test.name) + } + } +} + +func TestEnsureIPAddress(t *testing.T) { + tests := []struct { + name string + arguments []string + ip net.IP + fakeCmdAction []fakeexec.FakeCommandAction + expectedError bool + expectedResult bool + }{ + { + "IP address exists", + []string{"delete-port-proxy-rule"}, + net.IPv4(10, 10, 10, 20), + []fakeexec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { + return fakeexec.InitFakeCmd(&fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ + // IP address exists + func() ([]byte, error) { + return []byte("IP Address:10.10.10.10\nIP Address:10.10.10.20"), nil + }, + }, + }, cmd, args...) + }, + }, + false, + true, + }, + + { + "IP address not exists, but set successful(find it in the second time)", + []string{"ensure-ip-address"}, + net.IPv4(10, 10, 10, 20), + []fakeexec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { + return fakeexec.InitFakeCmd(&fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ + // IP address not exists + func() ([]byte, error) { + return []byte("IP Address:10.10.10.10"), nil + }, + }, + }, cmd, args...) + }, + func(cmd string, args ...string) exec.Cmd { + return fakeexec.InitFakeCmd(&fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ + // Success to set ip + func() ([]byte, error) { + return []byte(""), nil + }, + }, + }, cmd, args...) + }, + func(cmd string, args ...string) exec.Cmd { + return fakeexec.InitFakeCmd(&fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ + // IP address still not exists + func() ([]byte, error) { + return []byte("IP Address:10.10.10.10"), nil + }, + }, + }, cmd, args...) + }, + func(cmd string, args ...string) exec.Cmd { + return fakeexec.InitFakeCmd(&fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ + // IP address exists + func() ([]byte, error) { + return []byte("IP Address:10.10.10.10\nIP Address:10.10.10.20"), nil + }, + }, + }, cmd, args...) + }, + }, + false, + true, + }, + { + "IP address not exists, utilexec.ExitError exists, but status is not 0)", + []string{"ensure-ip-address"}, + net.IPv4(10, 10, 10, 20), + []fakeexec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { + return fakeexec.InitFakeCmd(&fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ + // IP address not exists + func() ([]byte, error) { + return []byte("IP Address:10.10.10.10"), nil + }, + }, + }, cmd, args...) + }, + func(cmd string, args ...string) exec.Cmd { + return fakeexec.InitFakeCmd(&fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ + // Failed to set ip, utilexec.ExitError exists, and status is not 0 + func() ([]byte, error) { + return nil, &fakeexec.FakeExitError{Status: 1} + }, + }, + }, cmd, args...) + }, + }, + false, + false, + }, + { + "IP address not exists, utilexec.ExitError exists, and status is 0)", + []string{"ensure-ip-address"}, + net.IPv4(10, 10, 10, 20), + []fakeexec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { + return fakeexec.InitFakeCmd(&fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ + // IP address not exists + func() ([]byte, error) { + return []byte("IP Address:10.10.10.10"), nil + }, + }, + }, cmd, args...) + }, + func(cmd string, args ...string) exec.Cmd { + return fakeexec.InitFakeCmd(&fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ + // Failed to set ip, utilexec.ExitError exists, and status is 0 + func() ([]byte, error) { + return nil, &fakeexec.FakeExitError{Status: 0} + }, + }, + }, cmd, args...) + }, + }, + true, + false, + }, + { + "IP address not exists, and error is not utilexec.ExitError)", + []string{"ensure-ip-address"}, + net.IPv4(10, 10, 10, 20), + []fakeexec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { + return fakeexec.InitFakeCmd(&fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ + // IP address not exists + func() ([]byte, error) { + return []byte("IP Address:10.10.10.10"), nil + }, + }, + }, cmd, args...) + }, + func(cmd string, args ...string) exec.Cmd { + return fakeexec.InitFakeCmd(&fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ + // Failed to set ip, other error exists + func() ([]byte, error) { + return nil, errors.New("not ExitError") + }, + }, + }, cmd, args...) + }, + }, + true, + false, + }, + } + + for _, test := range tests { + runner := New(&fakeexec.FakeExec{CommandScript: test.fakeCmdAction}) + result, err := runner.EnsureIPAddress(test.arguments, test.ip) + if test.expectedError { + assert.Errorf(t, err, "Failed to test: %s", test.name) + } else { + if err != nil { + assert.NoErrorf(t, err, "Failed to test: %s", test.name) + } else { + assert.EqualValuesf(t, test.expectedResult, result, "Failed to test: %s", test.name) + } + } + } +} + +func TestDeleteIPAddress(t *testing.T) { + runner := fakeCommonRunner() + + tests := []struct { + name string + arguments []string + expectedError bool + }{ + {"Success", []string{"delete-ip-address"}, false}, + {"utilexec.ExitError exists, and status is not 0", []string{"delete-ip-address"}, true}, + {"utilexec.ExitError exists, and status is 0", []string{"delete-ip-address"}, false}, + {"other error exists", []string{"delete-ip-address"}, true}, + } + + for _, test := range tests { + err := runner.DeleteIPAddress(test.arguments) + if test.expectedError { + assert.Errorf(t, err, "Failed to test: %s", test.name) + } else { + assert.NoErrorf(t, err, "Failed to test: %s", test.name) + } + } +} + +func TestGetInterfaceToAddIP(t *testing.T) { + // backup env 'INTERFACE_TO_ADD_SERVICE_IP' + backupValue := os.Getenv("INTERFACE_TO_ADD_SERVICE_IP") + // recover env + defer os.Setenv("INTERFACE_TO_ADD_SERVICE_IP", backupValue) + + tests := []struct { + name string + envToBeSet string + expectedResult string + }{ + {"env_value_is_empty", "", "vEthernet (HNS Internal NIC)"}, + {"env_value_is_not_empty", "eth0", "eth0"}, + } + + fakeExec := fakeexec.FakeExec{ + CommandScript: []fakeexec.FakeCommandAction{}, + } + netsh := New(&fakeExec) + + for _, test := range tests { + os.Setenv("INTERFACE_TO_ADD_SERVICE_IP", test.envToBeSet) + result := netsh.GetInterfaceToAddIP() + + assert.EqualValuesf(t, test.expectedResult, result, "Failed to test: %s", test.name) + } +} + +func TestRestore(t *testing.T) { + runner := New(&fakeexec.FakeExec{ + CommandScript: []fakeexec.FakeCommandAction{}, + }) + + result := runner.Restore([]string{}) + assert.NoErrorf(t, result, "The return value must be nil") +} + +func TestCheckIPExists(t *testing.T) { + fakeCmd := fakeexec.FakeCmd{ + CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{ + // Error exists + func() ([]byte, error) { + return nil, &fakeexec.FakeExitError{Status: 1} + }, + // IP address string is empty + func() ([]byte, error) { + return []byte(""), nil + }, + // "IP Address:" field not exists + func() ([]byte, error) { + return []byte("10.10.10.10"), nil + }, + // IP not exists + func() ([]byte, error) { + return []byte("IP Address:10.10.10.10"), nil + }, + // IP exists + func() ([]byte, error) { + return []byte("IP Address:10.10.10.10\nIP Address:10.10.10.20"), nil + }, + }, + } + fakeExec := fakeexec.FakeExec{ + CommandScript: []fakeexec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { + return fakeexec.InitFakeCmd(&fakeCmd, cmd, args...) + }, + func(cmd string, args ...string) exec.Cmd { + return fakeexec.InitFakeCmd(&fakeCmd, cmd, args...) + }, + func(cmd string, args ...string) exec.Cmd { + return fakeexec.InitFakeCmd(&fakeCmd, cmd, args...) + }, + func(cmd string, args ...string) exec.Cmd { + return fakeexec.InitFakeCmd(&fakeCmd, cmd, args...) + }, + func(cmd string, args ...string) exec.Cmd { + return fakeexec.InitFakeCmd(&fakeCmd, cmd, args...) + }, + }, + } + fakeRunner := &runner{ + exec: &fakeExec, + } + + tests := []struct { + name string + ipToCheck string + arguments []string + expectedError bool + expectedResult bool + }{ + {"Error exists", "10.10.10.20", []string{"check-IP-exists"}, true, false}, + {"IP address string is empty", "10.10.10.20", []string{"check-IP-exists"}, false, false}, + {"'IP Address:' field not exists", "10.10.10.20", []string{"check-IP-exists"}, false, false}, + {"IP not exists", "10.10.10.20", []string{"check-IP-exists"}, false, false}, + {"IP exists", "10.10.10.20", []string{"check-IP-exists"}, false, true}, + } + + for _, test := range tests { + result, err := checkIPExists(test.ipToCheck, test.arguments, fakeRunner) + if test.expectedError { + assert.Errorf(t, err, "Failed to test: %s", test.name) + } else { + assert.EqualValuesf(t, test.expectedResult, result, "Failed to test: %s", test.name) + } + } +} + +func TestGetIP(t *testing.T) { + testcases := []struct { + showAddress string + expectAddress string + }{ + { + showAddress: "IP 地址: 10.96.0.2", + expectAddress: "10.96.0.2", + }, + { + showAddress: "IP Address: 10.96.0.3", + expectAddress: "10.96.0.3", + }, + { + showAddress: "IP Address:10.96.0.4", + expectAddress: "10.96.0.4", + }, + } + + for _, tc := range testcases { + address := getIP(tc.showAddress) + if address != tc.expectAddress { + t.Errorf("expected address=%q, got %q", tc.expectAddress, address) + } + } +} diff --git a/pkg/util/node/BUILD b/pkg/util/node/BUILD index a6b14d3ae87..5188b9e6f80 100644 --- a/pkg/util/node/BUILD +++ b/pkg/util/node/BUILD @@ -11,7 +11,7 @@ go_library( srcs = ["node.go"], importpath = "k8s.io/kubernetes/pkg/util/node", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/kubelet/apis:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", @@ -26,8 +26,8 @@ go_library( go_test( name = "go_default_test", srcs = ["node_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/node", - library = ":go_default_library", deps = [ "//pkg/kubelet/apis:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/util/node/node.go b/pkg/util/node/node.go index 0138a56b599..6bc3ea8a777 100644 --- a/pkg/util/node/node.go +++ b/pkg/util/node/node.go @@ -31,7 +31,7 @@ import ( "k8s.io/apimachinery/pkg/util/strategicpatch" clientset "k8s.io/client-go/kubernetes" v1core "k8s.io/client-go/kubernetes/typed/core/v1" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis" ) @@ -146,15 +146,43 @@ func SetNodeCondition(c clientset.Interface, node types.NodeName, condition v1.N if err != nil { return nil } - _, err = c.Core().Nodes().PatchStatus(string(node), patch) + _, err = c.CoreV1().Nodes().PatchStatus(string(node), patch) return err } +// PatchNodeCIDR patches the specified node's CIDR to the given value. +func PatchNodeCIDR(c clientset.Interface, node types.NodeName, cidr string) error { + raw, err := json.Marshal(cidr) + if err != nil { + return fmt.Errorf("failed to json.Marshal CIDR: %v", err) + } + + patchBytes := []byte(fmt.Sprintf(`{"spec":{"podCIDR":%s}}`, raw)) + + if _, err := c.CoreV1().Nodes().Patch(string(node), types.StrategicMergePatchType, patchBytes); err != nil { + return fmt.Errorf("failed to patch node CIDR: %v", err) + } + return nil +} + // PatchNodeStatus patches node status. -func PatchNodeStatus(c v1core.CoreV1Interface, nodeName types.NodeName, oldNode *v1.Node, newNode *v1.Node) (*v1.Node, error) { +func PatchNodeStatus(c v1core.CoreV1Interface, nodeName types.NodeName, oldNode *v1.Node, newNode *v1.Node) (*v1.Node, []byte, error) { + patchBytes, err := preparePatchBytesforNodeStatus(nodeName, oldNode, newNode) + if err != nil { + return nil, nil, err + } + + updatedNode, err := c.Nodes().Patch(string(nodeName), types.StrategicMergePatchType, patchBytes, "status") + if err != nil { + return nil, nil, fmt.Errorf("failed to patch status %q for node %q: %v", patchBytes, nodeName, err) + } + return updatedNode, patchBytes, nil +} + +func preparePatchBytesforNodeStatus(nodeName types.NodeName, oldNode *v1.Node, newNode *v1.Node) ([]byte, error) { oldData, err := json.Marshal(oldNode) if err != nil { - return nil, fmt.Errorf("failed to marshal old node %#v for node %q: %v", oldNode, nodeName, err) + return nil, fmt.Errorf("failed to Marshal oldData for node %q: %v", nodeName, err) } // Reset spec to make sure only patch for Status or ObjectMeta is generated. @@ -164,17 +192,12 @@ func PatchNodeStatus(c v1core.CoreV1Interface, nodeName types.NodeName, oldNode newNode.Spec = oldNode.Spec newData, err := json.Marshal(newNode) if err != nil { - return nil, fmt.Errorf("failed to marshal new node %#v for node %q: %v", newNode, nodeName, err) + return nil, fmt.Errorf("failed to Marshal newData for node %q: %v", nodeName, err) } patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, v1.Node{}) if err != nil { - return nil, fmt.Errorf("failed to create patch for node %q: %v", nodeName, err) + return nil, fmt.Errorf("failed to CreateTwoWayMergePatch for node %q: %v", nodeName, err) } - - updatedNode, err := c.Nodes().Patch(string(nodeName), types.StrategicMergePatchType, patchBytes, "status") - if err != nil { - return nil, fmt.Errorf("failed to patch status %q for node %q: %v", patchBytes, nodeName, err) - } - return updatedNode, nil + return patchBytes, nil } diff --git a/pkg/util/normalizer/BUILD b/pkg/util/normalizer/BUILD new file mode 100644 index 00000000000..41637f99a9c --- /dev/null +++ b/pkg/util/normalizer/BUILD @@ -0,0 +1,29 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "markdown.go", + "normalizer.go", + ], + importpath = "k8s.io/kubernetes/pkg/util/normalizer", + visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/MakeNowJust/heredoc:go_default_library", + "//vendor/github.com/russross/blackfriday:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/util/normalizer/markdown.go b/pkg/util/normalizer/markdown.go new file mode 100644 index 00000000000..3f064d07354 --- /dev/null +++ b/pkg/util/normalizer/markdown.go @@ -0,0 +1,151 @@ +/* +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. +*/ + +/* +This file is copied from /pkg/kubectl/cmd/templates/markdown.go +In a future PR we should remove the original copy and use +/pkg/util/normalizer everywhere. +*/ + +package normalizer + +import ( + "bytes" + "fmt" + "strings" + + "github.com/russross/blackfriday" +) + +const linebreak = "\n" + +// ASCIIRenderer implements blackfriday.Renderer +var _ blackfriday.Renderer = &ASCIIRenderer{} + +// ASCIIRenderer is a blackfriday.Renderer intended for rendering markdown +// documents as plain text, well suited for human reading on terminals. +type ASCIIRenderer struct { + Indentation string + + listItemCount uint + listLevel uint +} + +// NormalText gets a text chunk *after* the markdown syntax was already +// processed and does a final cleanup on things we don't expect here, like +// removing linebreaks on things that are not a paragraph break (auto unwrap). +func (r *ASCIIRenderer) NormalText(out *bytes.Buffer, text []byte) { + raw := string(text) + lines := strings.Split(raw, linebreak) + for _, line := range lines { + trimmed := strings.Trim(line, " \n\t") + out.WriteString(trimmed) + out.WriteString(" ") + } +} + +// List renders the start and end of a list. +func (r *ASCIIRenderer) List(out *bytes.Buffer, text func() bool, flags int) { + r.listLevel++ + out.WriteString(linebreak) + text() + r.listLevel-- +} + +// ListItem renders list items and supports both ordered and unordered lists. +func (r *ASCIIRenderer) ListItem(out *bytes.Buffer, text []byte, flags int) { + if flags&blackfriday.LIST_ITEM_BEGINNING_OF_LIST != 0 { + r.listItemCount = 1 + } else { + r.listItemCount++ + } + indent := strings.Repeat(r.Indentation, int(r.listLevel)) + var bullet string + if flags&blackfriday.LIST_TYPE_ORDERED != 0 { + bullet += fmt.Sprintf("%d.", r.listItemCount) + } else { + bullet += "*" + } + out.WriteString(indent + bullet + " ") + r.fw(out, text) + out.WriteString(linebreak) +} + +// Paragraph renders the start and end of a paragraph. +func (r *ASCIIRenderer) Paragraph(out *bytes.Buffer, text func() bool) { + out.WriteString(linebreak) + text() + out.WriteString(linebreak) +} + +// BlockCode renders a chunk of text that represents source code. +func (r *ASCIIRenderer) BlockCode(out *bytes.Buffer, text []byte, lang string) { + out.WriteString(linebreak) + lines := []string{} + for _, line := range strings.Split(string(text), linebreak) { + indented := r.Indentation + line + lines = append(lines, indented) + } + out.WriteString(strings.Join(lines, linebreak)) +} + +func (r *ASCIIRenderer) GetFlags() int { return 0 } +func (r *ASCIIRenderer) HRule(out *bytes.Buffer) { + out.WriteString(linebreak + "----------" + linebreak) +} +func (r *ASCIIRenderer) LineBreak(out *bytes.Buffer) { out.WriteString(linebreak) } +func (r *ASCIIRenderer) TitleBlock(out *bytes.Buffer, text []byte) { r.fw(out, text) } +func (r *ASCIIRenderer) Header(out *bytes.Buffer, text func() bool, level int, id string) { text() } +func (r *ASCIIRenderer) BlockHtml(out *bytes.Buffer, text []byte) { r.fw(out, text) } +func (r *ASCIIRenderer) BlockQuote(out *bytes.Buffer, text []byte) { r.fw(out, text) } +func (r *ASCIIRenderer) TableRow(out *bytes.Buffer, text []byte) { r.fw(out, text) } +func (r *ASCIIRenderer) TableHeaderCell(out *bytes.Buffer, text []byte, align int) { r.fw(out, text) } +func (r *ASCIIRenderer) TableCell(out *bytes.Buffer, text []byte, align int) { r.fw(out, text) } +func (r *ASCIIRenderer) Footnotes(out *bytes.Buffer, text func() bool) { text() } +func (r *ASCIIRenderer) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) { r.fw(out, text) } +func (r *ASCIIRenderer) AutoLink(out *bytes.Buffer, link []byte, kind int) { r.fw(out, link) } +func (r *ASCIIRenderer) CodeSpan(out *bytes.Buffer, text []byte) { r.fw(out, text) } +func (r *ASCIIRenderer) DoubleEmphasis(out *bytes.Buffer, text []byte) { r.fw(out, text) } +func (r *ASCIIRenderer) Emphasis(out *bytes.Buffer, text []byte) { r.fw(out, text) } +func (r *ASCIIRenderer) RawHtmlTag(out *bytes.Buffer, text []byte) { r.fw(out, text) } +func (r *ASCIIRenderer) TripleEmphasis(out *bytes.Buffer, text []byte) { r.fw(out, text) } +func (r *ASCIIRenderer) StrikeThrough(out *bytes.Buffer, text []byte) { r.fw(out, text) } +func (r *ASCIIRenderer) FootnoteRef(out *bytes.Buffer, ref []byte, id int) { r.fw(out, ref) } +func (r *ASCIIRenderer) Entity(out *bytes.Buffer, entity []byte) { r.fw(out, entity) } +func (r *ASCIIRenderer) Smartypants(out *bytes.Buffer, text []byte) { r.fw(out, text) } +func (r *ASCIIRenderer) DocumentHeader(out *bytes.Buffer) {} +func (r *ASCIIRenderer) DocumentFooter(out *bytes.Buffer) {} +func (r *ASCIIRenderer) TocHeaderWithAnchor(text []byte, level int, anchor string) {} +func (r *ASCIIRenderer) TocHeader(text []byte, level int) {} +func (r *ASCIIRenderer) TocFinalize() {} + +func (r *ASCIIRenderer) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) { + r.fw(out, header, body) +} + +func (r *ASCIIRenderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) { + r.fw(out, link) +} + +func (r *ASCIIRenderer) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) { + r.fw(out, link) +} + +func (r *ASCIIRenderer) fw(out *bytes.Buffer, text ...[]byte) { + for _, t := range text { + out.Write(t) + } +} diff --git a/pkg/util/normalizer/normalizer.go b/pkg/util/normalizer/normalizer.go new file mode 100644 index 00000000000..28179f70b17 --- /dev/null +++ b/pkg/util/normalizer/normalizer.go @@ -0,0 +1,80 @@ +/* +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. +*/ + +/* +This file is copied from /pkg/kubectl/cmd/templates/normalizer.go +In a future PR we should remove the original copy and use +/pkg/util/normalizer everywhere. +*/ + +package normalizer + +import ( + "strings" + + "github.com/MakeNowJust/heredoc" + "github.com/russross/blackfriday" +) + +const indentation = ` ` + +// LongDesc normalizes a command's long description to follow the conventions. +func LongDesc(s string) string { + if len(s) == 0 { + return s + } + return normalizer{s}.Heredoc().Markdown().Trim().string +} + +// Examples normalizes a command's examples to follow the conventions. +func Examples(s string) string { + if len(s) == 0 { + return s + } + return normalizer{s}.Trim().Indent().string +} + +type normalizer struct { + string +} + +func (s normalizer) Markdown() normalizer { + bytes := []byte(s.string) + formatted := blackfriday.Markdown(bytes, &ASCIIRenderer{Indentation: indentation}, 0) + s.string = string(formatted) + return s +} + +func (s normalizer) Heredoc() normalizer { + s.string = heredoc.Doc(s.string) + return s +} + +func (s normalizer) Trim() normalizer { + s.string = strings.TrimSpace(s.string) + return s +} + +func (s normalizer) Indent() normalizer { + indentedLines := []string{} + for _, line := range strings.Split(s.string, "\n") { + trimmed := strings.TrimSpace(line) + indented := indentation + trimmed + indentedLines = append(indentedLines, indented) + } + s.string = strings.Join(indentedLines, "\n") + return s +} diff --git a/pkg/util/nsenter/BUILD b/pkg/util/nsenter/BUILD index b3f2d2b031d..988fef01b59 100644 --- a/pkg/util/nsenter/BUILD +++ b/pkg/util/nsenter/BUILD @@ -2,21 +2,78 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", - srcs = [ - "nsenter_unsupported.go", - ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + srcs = select({ + "@io_bazel_rules_go//go/platform:android": [ + "nsenter_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "nsenter_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "nsenter_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "nsenter_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ "nsenter.go", ], + "@io_bazel_rules_go//go/platform:nacl": [ + "nsenter_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "nsenter_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "nsenter_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "nsenter_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "nsenter_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "nsenter_unsupported.go", + ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/pkg/util/nsenter", visibility = ["//visibility:public"], - deps = [ - "//vendor/k8s.io/utils/exec:go_default_library", - ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + deps = select({ + "@io_bazel_rules_go//go/platform:android": [ + "//vendor/k8s.io/utils/exec:go_default_library", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "//vendor/k8s.io/utils/exec:go_default_library", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "//vendor/k8s.io/utils/exec:go_default_library", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "//vendor/k8s.io/utils/exec:go_default_library", + ], + "@io_bazel_rules_go//go/platform:linux": [ "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/utils/exec:go_default_library", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "//vendor/k8s.io/utils/exec:go_default_library", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "//vendor/k8s.io/utils/exec:go_default_library", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "//vendor/k8s.io/utils/exec:go_default_library", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "//vendor/k8s.io/utils/exec:go_default_library", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "//vendor/k8s.io/utils/exec:go_default_library", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "//vendor/k8s.io/utils/exec:go_default_library", ], "//conditions:default": [], }), diff --git a/pkg/util/oom/BUILD b/pkg/util/oom/BUILD index 9e027c7f482..127a3bdb4c9 100644 --- a/pkg/util/oom/BUILD +++ b/pkg/util/oom/BUILD @@ -12,16 +12,45 @@ go_library( "doc.go", "oom.go", "oom_fake.go", - "oom_unsupported.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "oom_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "oom_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "oom_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "oom_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ "oom_linux.go", ], + "@io_bazel_rules_go//go/platform:nacl": [ + "oom_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "oom_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "oom_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "oom_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "oom_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "oom_unsupported.go", + ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/pkg/util/oom", deps = select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//pkg/kubelet/cm/util:go_default_library", "//vendor/github.com/golang/glog:go_default_library", ], @@ -32,15 +61,15 @@ go_library( go_test( name = "go_default_test", srcs = select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "oom_linux_test.go", ], "//conditions:default": [], }), + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/oom", - library = ":go_default_library", deps = select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//vendor/github.com/stretchr/testify/assert:go_default_library", ], "//conditions:default": [], diff --git a/pkg/util/parsers/BUILD b/pkg/util/parsers/BUILD index 7070f4b5f89..a60c591351e 100644 --- a/pkg/util/parsers/BUILD +++ b/pkg/util/parsers/BUILD @@ -16,8 +16,8 @@ go_library( go_test( name = "go_default_test", srcs = ["parsers_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/parsers", - library = ":go_default_library", ) filegroup( diff --git a/pkg/util/pointer/BUILD b/pkg/util/pointer/BUILD index 57cde0c2f07..615acda2dac 100644 --- a/pkg/util/pointer/BUILD +++ b/pkg/util/pointer/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["pointer_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/pointer", - library = ":go_default_library", ) go_library( diff --git a/pkg/util/pointer/pointer.go b/pkg/util/pointer/pointer.go index 1a10939bae2..a970bf7f582 100644 --- a/pkg/util/pointer/pointer.go +++ b/pkg/util/pointer/pointer.go @@ -60,3 +60,9 @@ func Int32PtrDerefOr(ptr *int32, def int32) int32 { } return def } + +// BoolPtr returns a pointer to a bool +func BoolPtr(b bool) *bool { + o := b + return &o +} diff --git a/pkg/util/procfs/BUILD b/pkg/util/procfs/BUILD index ea7c3be29f8..2f486249167 100644 --- a/pkg/util/procfs/BUILD +++ b/pkg/util/procfs/BUILD @@ -12,16 +12,45 @@ go_library( "doc.go", "procfs.go", "procfs_fake.go", - "procfs_unsupported.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "procfs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "procfs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "procfs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "procfs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ "procfs_linux.go", ], + "@io_bazel_rules_go//go/platform:nacl": [ + "procfs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "procfs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "procfs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "procfs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "procfs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "procfs_unsupported.go", + ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/pkg/util/procfs", deps = select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", ], @@ -32,7 +61,7 @@ go_library( go_test( name = "go_default_test", srcs = select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "procfs_linux_test.go", ], "//conditions:default": [], @@ -40,10 +69,10 @@ go_test( data = [ "example_proc_cgroup", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/procfs", - library = ":go_default_library", deps = select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//vendor/github.com/stretchr/testify/assert:go_default_library", ], "//conditions:default": [], diff --git a/pkg/util/reflector/prometheus/prometheus.go b/pkg/util/reflector/prometheus/prometheus.go index ab16edf4911..958a0007cdd 100644 --- a/pkg/util/reflector/prometheus/prometheus.go +++ b/pkg/util/reflector/prometheus/prometheus.go @@ -68,6 +68,12 @@ var ( Name: "items_per_watch", Help: "How many items an API watch returns to the reflectors", }, []string{"name"}) + + lastResourceVersion = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Subsystem: reflectorSubsystem, + Name: "last_resource_version", + Help: "Last resource version seen for the reflectors", + }, []string{"name"}) ) func init() { @@ -78,6 +84,7 @@ func init() { prometheus.MustRegister(shortWatchesTotal) prometheus.MustRegister(watchDuration) prometheus.MustRegister(itemsPerWatch) + prometheus.MustRegister(lastResourceVersion) cache.SetReflectorMetricsProvider(prometheusMetricsProvider{}) } @@ -117,11 +124,5 @@ func (prometheusMetricsProvider) NewItemsInWatchMetric(name string) cache.Summar } func (prometheusMetricsProvider) NewLastResourceVersionMetric(name string) cache.GaugeMetric { - rv := prometheus.NewGauge(prometheus.GaugeOpts{ - Subsystem: name, - Name: "last_resource_version", - Help: "last resource version seen for the reflectors", - }) - prometheus.MustRegister(rv) - return rv + return lastResourceVersion.WithLabelValues(name) } diff --git a/pkg/util/removeall/BUILD b/pkg/util/removeall/BUILD index 54d7332eb57..682b646bada 100644 --- a/pkg/util/removeall/BUILD +++ b/pkg/util/removeall/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["removeall_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/removeall", - library = ":go_default_library", deps = [ "//pkg/util/mount:go_default_library", "//vendor/k8s.io/client-go/util/testing:go_default_library", diff --git a/pkg/util/resizefs/BUILD b/pkg/util/resizefs/BUILD new file mode 100644 index 00000000000..6301c603a99 --- /dev/null +++ b/pkg/util/resizefs/BUILD @@ -0,0 +1,95 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = select({ + "@io_bazel_rules_go//go/platform:android": [ + "resizefs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "resizefs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "resizefs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "resizefs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "resizefs_linux.go", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "resizefs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "resizefs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "resizefs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "resizefs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "resizefs_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "resizefs_unsupported.go", + ], + "//conditions:default": [], + }), + importpath = "k8s.io/kubernetes/pkg/util/resizefs", + visibility = ["//visibility:public"], + deps = select({ + "@io_bazel_rules_go//go/platform:android": [ + "//pkg/util/mount:go_default_library", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "//pkg/util/mount:go_default_library", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "//pkg/util/mount:go_default_library", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "//pkg/util/mount:go_default_library", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "//pkg/util/mount:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/utils/exec:go_default_library", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "//pkg/util/mount:go_default_library", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "//pkg/util/mount:go_default_library", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "//pkg/util/mount:go_default_library", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "//pkg/util/mount:go_default_library", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "//pkg/util/mount:go_default_library", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "//pkg/util/mount:go_default_library", + ], + "//conditions:default": [], + }), +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/util/resizefs/resizefs_linux.go b/pkg/util/resizefs/resizefs_linux.go new file mode 100644 index 00000000000..6a4d82d6c03 --- /dev/null +++ b/pkg/util/resizefs/resizefs_linux.go @@ -0,0 +1,143 @@ +// +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 resizefs + +import ( + "fmt" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/util/mount" + utilexec "k8s.io/utils/exec" +) + +const ( + // 'fsck' found errors and corrected them + fsckErrorsCorrected = 1 + // 'fsck' found errors but exited without correcting them + fsckErrorsUncorrected = 4 +) + +// ResizeFs Provides support for resizing file systems +type ResizeFs struct { + mounter *mount.SafeFormatAndMount +} + +// NewResizeFs returns new instance of resizer +func NewResizeFs(mounter *mount.SafeFormatAndMount) *ResizeFs { + return &ResizeFs{mounter: mounter} +} + +// Resize perform resize of file system +func (resizefs *ResizeFs) Resize(devicePath string) (bool, error) { + format, err := resizefs.mounter.GetDiskFormat(devicePath) + + if err != nil { + formatErr := fmt.Errorf("error checking format for device %s: %v", devicePath, err) + return false, formatErr + } + + // If disk has no format, there is no need to resize the disk because mkfs.* + // by default will use whole disk anyways. + if format == "" { + return false, nil + } + + deviceOpened, err := resizefs.mounter.DeviceOpened(devicePath) + + if err != nil { + deviceOpenErr := fmt.Errorf("error verifying if device %s is open: %v", devicePath, err) + return false, deviceOpenErr + } + + if deviceOpened { + deviceAlreadyOpenErr := fmt.Errorf("the device %s is already in use", devicePath) + return false, deviceAlreadyOpenErr + } + + switch format { + case "ext3", "ext4": + fsckErr := resizefs.extFsck(devicePath, format) + if fsckErr != nil { + return false, fsckErr + } + return resizefs.extResize(devicePath) + case "xfs": + fsckErr := resizefs.fsckDevice(devicePath) + if fsckErr != nil { + return false, fsckErr + } + return resizefs.xfsResize(devicePath) + } + return false, fmt.Errorf("resize of format %s is not supported for device %s", format, devicePath) +} + +func (resizefs *ResizeFs) fsckDevice(devicePath string) error { + glog.V(4).Infof("Checking for issues with fsck on device: %s", devicePath) + args := []string{"-a", devicePath} + out, err := resizefs.mounter.Exec.Run("fsck", args...) + if err != nil { + ee, isExitError := err.(utilexec.ExitError) + switch { + case err == utilexec.ErrExecutableNotFound: + glog.Warningf("'fsck' not found on system; continuing resizing without running 'fsck'.") + case isExitError && ee.ExitStatus() == fsckErrorsCorrected: + glog.V(2).Infof("Device %s has errors which were corrected by fsck: %s", devicePath, string(out)) + case isExitError && ee.ExitStatus() == fsckErrorsUncorrected: + return fmt.Errorf("'fsck' found errors on device %s but could not correct them: %s", devicePath, string(out)) + case isExitError && ee.ExitStatus() > fsckErrorsUncorrected: + glog.Infof("`fsck` error %s", string(out)) + } + } + return nil +} + +func (resizefs *ResizeFs) extFsck(devicePath string, fsType string) error { + glog.V(4).Infof("Checking for issues with fsck.%s on device: %s", fsType, devicePath) + args := []string{"-f", "-y", devicePath} + out, err := resizefs.mounter.Run("fsck."+fsType, args...) + if err != nil { + return fmt.Errorf("running fsck.%s failed on %s with error: %v\n Output: %s", fsType, devicePath, err, string(out)) + } + return nil +} + +func (resizefs *ResizeFs) extResize(devicePath string) (bool, error) { + output, err := resizefs.mounter.Exec.Run("resize2fs", devicePath) + if err == nil { + glog.V(2).Infof("Device %s resized successfully", devicePath) + return true, nil + } + + resizeError := fmt.Errorf("resize of device %s failed: %v. resize2fs output: %s", devicePath, err, string(output)) + return false, resizeError + +} + +func (resizefs *ResizeFs) xfsResize(devicePath string) (bool, error) { + args := []string{"-d", devicePath} + output, err := resizefs.mounter.Exec.Run("xfs_growfs", args...) + + if err == nil { + glog.V(2).Infof("Device %s resized successfully", devicePath) + return true, nil + } + + resizeError := fmt.Errorf("resize of device %s failed: %v. xfs_growfs output: %s", devicePath, err, string(output)) + return false, resizeError +} diff --git a/pkg/util/resizefs/resizefs_unsupported.go b/pkg/util/resizefs/resizefs_unsupported.go new file mode 100644 index 00000000000..9241d7d5b27 --- /dev/null +++ b/pkg/util/resizefs/resizefs_unsupported.go @@ -0,0 +1,40 @@ +// +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 resizefs + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/util/mount" +) + +// ResizeFs Provides support for resizing file systems +type ResizeFs struct { + mounter *mount.SafeFormatAndMount +} + +// NewResizeFs returns new instance of resizer +func NewResizeFs(mounter *mount.SafeFormatAndMount) *ResizeFs { + return &ResizeFs{mounter: mounter} +} + +// Resize perform resize of file system +func (resizefs *ResizeFs) Resize(devicePath string) (bool, error) { + return false, fmt.Errorf("Resize is not supported for this build") +} diff --git a/pkg/util/resourcecontainer/BUILD b/pkg/util/resourcecontainer/BUILD index 05b817b4b12..e13b8aea09e 100644 --- a/pkg/util/resourcecontainer/BUILD +++ b/pkg/util/resourcecontainer/BUILD @@ -7,17 +7,45 @@ load( go_library( name = "go_default_library", - srcs = [ - "resource_container_unsupported.go", - ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + srcs = select({ + "@io_bazel_rules_go//go/platform:android": [ + "resource_container_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "resource_container_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "resource_container_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "resource_container_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ "resource_container_linux.go", ], + "@io_bazel_rules_go//go/platform:nacl": [ + "resource_container_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "resource_container_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "resource_container_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "resource_container_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "resource_container_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "resource_container_unsupported.go", + ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/pkg/util/resourcecontainer", deps = select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs:go_default_library", "//vendor/github.com/opencontainers/runc/libcontainer/configs:go_default_library", ], diff --git a/pkg/util/rlimit/BUILD b/pkg/util/rlimit/BUILD index 52fe550b103..7735de7909e 100644 --- a/pkg/util/rlimit/BUILD +++ b/pkg/util/rlimit/BUILD @@ -7,17 +7,45 @@ load( go_library( name = "go_default_library", - srcs = [ - "rlimit_unsupported.go", - ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + srcs = select({ + "@io_bazel_rules_go//go/platform:android": [ + "rlimit_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "rlimit_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "rlimit_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "rlimit_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ "rlimit_linux.go", ], + "@io_bazel_rules_go//go/platform:nacl": [ + "rlimit_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "rlimit_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "rlimit_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "rlimit_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "rlimit_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "rlimit_unsupported.go", + ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/pkg/util/rlimit", deps = select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//vendor/golang.org/x/sys/unix:go_default_library", ], "//conditions:default": [], diff --git a/pkg/util/selinux/BUILD b/pkg/util/selinux/BUILD index f5eab6022f0..dae3792f4c2 100644 --- a/pkg/util/selinux/BUILD +++ b/pkg/util/selinux/BUILD @@ -10,16 +10,45 @@ go_library( srcs = [ "doc.go", "selinux.go", - "selinux_unsupported.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "selinux_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "selinux_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "selinux_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "selinux_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ "selinux_linux.go", ], + "@io_bazel_rules_go//go/platform:nacl": [ + "selinux_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "selinux_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "selinux_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "selinux_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "selinux_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "selinux_unsupported.go", + ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/pkg/util/selinux", deps = select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//vendor/github.com/opencontainers/selinux/go-selinux:go_default_library", ], "//conditions:default": [], diff --git a/pkg/util/slice/BUILD b/pkg/util/slice/BUILD index 611545484c0..5b13d93e4d9 100644 --- a/pkg/util/slice/BUILD +++ b/pkg/util/slice/BUILD @@ -16,8 +16,8 @@ go_library( go_test( name = "go_default_test", srcs = ["slice_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/slice", - library = ":go_default_library", ) filegroup( diff --git a/pkg/util/slice/slice.go b/pkg/util/slice/slice.go index b408dbae841..b9809cc2972 100644 --- a/pkg/util/slice/slice.go +++ b/pkg/util/slice/slice.go @@ -68,3 +68,24 @@ func ContainsString(slice []string, s string, modifier func(s string) string) bo } return false } + +// RemoveString returns a newly created []string that contains all items from slice that +// are not equal to s and modifier(s) in case modifier func is provided. +func RemoveString(slice []string, s string, modifier func(s string) string) []string { + newSlice := make([]string, 0) + for _, item := range slice { + if item == s { + continue + } + if modifier != nil && modifier(item) == s { + continue + } + newSlice = append(newSlice, item) + } + if len(newSlice) == 0 { + // Sanitize for unit tests so we don't need to distinguish empty array + // and nil. + newSlice = nil + } + return newSlice +} diff --git a/pkg/util/slice/slice_test.go b/pkg/util/slice/slice_test.go index 437c8ecee55..19b46c227e8 100644 --- a/pkg/util/slice/slice_test.go +++ b/pkg/util/slice/slice_test.go @@ -89,3 +89,84 @@ func TestShuffleStrings(t *testing.T) { } } } + +func TestContainsString(t *testing.T) { + src := []string{"aa", "bb", "cc"} + if !ContainsString(src, "bb", nil) { + t.Errorf("ContainsString didn't find the string as expected") + } + + modifier := func(s string) string { + if s == "cc" { + return "ee" + } + return s + } + if !ContainsString(src, "ee", modifier) { + t.Errorf("ContainsString didn't find the string by modifier") + } +} + +func TestRemoveString(t *testing.T) { + modifier := func(s string) string { + if s == "ab" { + return "ee" + } + return s + } + tests := []struct { + testName string + input []string + remove string + modifier func(s string) string + want []string + }{ + { + testName: "Nil input slice", + input: nil, + remove: "", + modifier: nil, + want: nil, + }, + { + testName: "Slice doesn't contain the string", + input: []string{"a", "ab", "cdef"}, + remove: "NotPresentInSlice", + modifier: nil, + want: []string{"a", "ab", "cdef"}, + }, + { + testName: "All strings removed, result is nil", + input: []string{"a"}, + remove: "a", + modifier: nil, + want: nil, + }, + { + testName: "No modifier func, one string removed", + input: []string{"a", "ab", "cdef"}, + remove: "ab", + modifier: nil, + want: []string{"a", "cdef"}, + }, + { + testName: "No modifier func, all(three) strings removed", + input: []string{"ab", "a", "ab", "cdef", "ab"}, + remove: "ab", + modifier: nil, + want: []string{"a", "cdef"}, + }, + { + testName: "Removed both the string and the modifier func result", + input: []string{"a", "cd", "ab", "ee"}, + remove: "ee", + modifier: modifier, + want: []string{"a", "cd"}, + }, + } + for _, tt := range tests { + if got := RemoveString(tt.input, tt.remove, tt.modifier); !reflect.DeepEqual(got, tt.want) { + t.Errorf("%v: RemoveString(%v, %q, %T) = %v WANT %v", tt.testName, tt.input, tt.remove, tt.modifier, got, tt.want) + } + } +} diff --git a/pkg/util/strings/BUILD b/pkg/util/strings/BUILD index f71a3326e39..b50d78b265b 100644 --- a/pkg/util/strings/BUILD +++ b/pkg/util/strings/BUILD @@ -23,8 +23,8 @@ go_test( "line_delimiter_test.go", "strings_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/strings", - library = ":go_default_library", ) filegroup( diff --git a/pkg/util/system/BUILD b/pkg/util/system/BUILD index 1c2f1a067f1..58caa161917 100644 --- a/pkg/util/system/BUILD +++ b/pkg/util/system/BUILD @@ -15,8 +15,8 @@ go_library( go_test( name = "go_default_test", srcs = ["system_utils_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/system", - library = ":go_default_library", deps = [ "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/util/tail/BUILD b/pkg/util/tail/BUILD index 4e81446dd40..253a66e4aa0 100644 --- a/pkg/util/tail/BUILD +++ b/pkg/util/tail/BUILD @@ -22,8 +22,8 @@ filegroup( go_test( name = "go_default_test", srcs = ["tail_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/tail", - library = ":go_default_library", ) go_library( diff --git a/pkg/util/taints/BUILD b/pkg/util/taints/BUILD index 1df1e7ba326..09fdb4b2f71 100644 --- a/pkg/util/taints/BUILD +++ b/pkg/util/taints/BUILD @@ -11,8 +11,8 @@ go_library( srcs = ["taints.go"], importpath = "k8s.io/kubernetes/pkg/util/taints", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", @@ -23,10 +23,10 @@ go_library( go_test( name = "go_default_test", srcs = ["taints_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/taints", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", ], diff --git a/pkg/util/taints/taints.go b/pkg/util/taints/taints.go index d748686d2a3..76e4bb86681 100644 --- a/pkg/util/taints/taints.go +++ b/pkg/util/taints/taints.go @@ -25,8 +25,8 @@ import ( utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" ) const ( @@ -45,15 +45,14 @@ func parseTaint(st string) (v1.Taint, error) { parts2 := strings.Split(parts[1], ":") - effect := v1.TaintEffect(parts2[1]) - errs := validation.IsValidLabelValue(parts2[0]) if len(parts2) != 2 || len(errs) != 0 { return taint, fmt.Errorf("invalid taint spec: %v, %s", st, strings.Join(errs, "; ")) } - if effect != v1.TaintEffectNoSchedule && effect != v1.TaintEffectPreferNoSchedule && effect != v1.TaintEffectNoExecute { - return taint, fmt.Errorf("invalid taint spec: %v, unsupported taint effect", st) + effect := v1.TaintEffect(parts2[1]) + if err := validateTaintEffect(effect); err != nil { + return taint, err } taint.Key = parts[0] @@ -63,6 +62,14 @@ func parseTaint(st string) (v1.Taint, error) { return taint, nil } +func validateTaintEffect(effect v1.TaintEffect) error { + if effect != v1.TaintEffectNoSchedule && effect != v1.TaintEffectPreferNoSchedule && effect != v1.TaintEffectNoExecute { + return fmt.Errorf("invalid taint effect: %v, unsupported taint effect", effect) + } + + return nil +} + // NewTaintsVar wraps []api.Taint in a struct that implements flag.Value to allow taints to be // bound to command line flags. func NewTaintsVar(ptr *[]api.Taint) taintsVar { @@ -138,6 +145,14 @@ func ParseTaints(spec []string) ([]v1.Taint, []v1.Taint, error) { taintKey = parts[0] effect = v1.TaintEffect(parts[1]) } + + // If effect is specified, need to validate it. + if len(effect) > 0 { + err := validateTaintEffect(effect) + if err != nil { + return nil, nil, err + } + } taintsToRemove = append(taintsToRemove, v1.Taint{Key: taintKey, Effect: effect}) } else { return nil, nil, fmt.Errorf("unknown taint spec: %v", taintSpec) diff --git a/pkg/util/taints/taints_test.go b/pkg/util/taints/taints_test.go index e27e6ad37ab..110d78f787c 100644 --- a/pkg/util/taints/taints_test.go +++ b/pkg/util/taints/taints_test.go @@ -22,7 +22,7 @@ import ( "testing" "k8s.io/api/core/v1" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "github.com/spf13/pflag" ) @@ -111,3 +111,577 @@ func TestAddOrUpdateTaint(t *testing.T) { newNode, result, err = AddOrUpdateTaint(node, taint) checkResult("Add Duplicate Taint", newNode, taint, result, false, err) } + +func TestTaintExists(t *testing.T) { + testingTaints := []v1.Taint{ + { + Key: "foo_1", + Value: "bar_1", + Effect: v1.TaintEffectNoExecute, + }, + { + Key: "foo_2", + Value: "bar_2", + Effect: v1.TaintEffectNoSchedule, + }, + } + + cases := []struct { + name string + taintToFind *v1.Taint + expectedResult bool + }{ + { + name: "taint exists", + taintToFind: &v1.Taint{Key: "foo_1", Value: "bar_1", Effect: v1.TaintEffectNoExecute}, + expectedResult: true, + }, + { + name: "different key", + taintToFind: &v1.Taint{Key: "no_such_key", Value: "bar_1", Effect: v1.TaintEffectNoExecute}, + expectedResult: false, + }, + { + name: "different effect", + taintToFind: &v1.Taint{Key: "foo_1", Value: "bar_1", Effect: v1.TaintEffectNoSchedule}, + expectedResult: false, + }, + } + + for _, c := range cases { + result := TaintExists(testingTaints, c.taintToFind) + + if result != c.expectedResult { + t.Errorf("[%s] unexpected results: %v", c.name, result) + continue + } + } +} + +func TestRemoveTaint(t *testing.T) { + cases := []struct { + name string + node *v1.Node + taintToRemove *v1.Taint + expectedTaints []v1.Taint + expectedResult bool + }{ + { + name: "remove taint unsuccessfully", + node: &v1.Node{ + Spec: v1.NodeSpec{ + Taints: []v1.Taint{ + { + Key: "foo", + Effect: v1.TaintEffectNoSchedule, + }, + }, + }, + }, + taintToRemove: &v1.Taint{ + Key: "foo_1", + Effect: v1.TaintEffectNoSchedule, + }, + expectedTaints: []v1.Taint{ + { + Key: "foo", + Effect: v1.TaintEffectNoSchedule, + }, + }, + expectedResult: false, + }, + { + name: "remove taint successfully", + node: &v1.Node{ + Spec: v1.NodeSpec{ + Taints: []v1.Taint{ + { + Key: "foo", + Effect: v1.TaintEffectNoSchedule, + }, + }, + }, + }, + taintToRemove: &v1.Taint{ + Key: "foo", + Effect: v1.TaintEffectNoSchedule, + }, + expectedTaints: []v1.Taint{}, + expectedResult: true, + }, + { + name: "remove taint from node with no taint", + node: &v1.Node{ + Spec: v1.NodeSpec{ + Taints: []v1.Taint{}, + }, + }, + taintToRemove: &v1.Taint{ + Key: "foo", + Effect: v1.TaintEffectNoSchedule, + }, + expectedTaints: []v1.Taint{}, + expectedResult: false, + }, + } + + for _, c := range cases { + newNode, result, err := RemoveTaint(c.node, c.taintToRemove) + if err != nil { + t.Errorf("[%s] should not raise error but got: %v", c.name, err) + } + if result != c.expectedResult { + t.Errorf("[%s] should return %t, but got: %t", c.name, c.expectedResult, result) + } + if !reflect.DeepEqual(newNode.Spec.Taints, c.expectedTaints) { + t.Errorf("[%s] the new node object should have taints %v, but got: %v", c.name, c.expectedTaints, newNode.Spec.Taints) + } + } +} + +func TestDeleteTaint(t *testing.T) { + cases := []struct { + name string + taints []v1.Taint + taintToDelete *v1.Taint + expectedTaints []v1.Taint + expectedResult bool + }{ + { + name: "delete taint with different name", + taints: []v1.Taint{ + { + Key: "foo", + Effect: v1.TaintEffectNoSchedule, + }, + }, + taintToDelete: &v1.Taint{Key: "foo_1", Effect: v1.TaintEffectNoSchedule}, + expectedTaints: []v1.Taint{ + { + Key: "foo", + Effect: v1.TaintEffectNoSchedule, + }, + }, + expectedResult: false, + }, + { + name: "delete taint with different effect", + taints: []v1.Taint{ + { + Key: "foo", + Effect: v1.TaintEffectNoSchedule, + }, + }, + taintToDelete: &v1.Taint{Key: "foo", Effect: v1.TaintEffectNoExecute}, + expectedTaints: []v1.Taint{ + { + Key: "foo", + Effect: v1.TaintEffectNoSchedule, + }, + }, + expectedResult: false, + }, + { + name: "delete taint successfully", + taints: []v1.Taint{ + { + Key: "foo", + Effect: v1.TaintEffectNoSchedule, + }, + }, + taintToDelete: &v1.Taint{Key: "foo", Effect: v1.TaintEffectNoSchedule}, + expectedTaints: []v1.Taint{}, + expectedResult: true, + }, + { + name: "delete taint from empty taint array", + taints: []v1.Taint{}, + taintToDelete: &v1.Taint{Key: "foo", Effect: v1.TaintEffectNoSchedule}, + expectedTaints: []v1.Taint{}, + expectedResult: false, + }, + } + + for _, c := range cases { + taints, result := DeleteTaint(c.taints, c.taintToDelete) + if result != c.expectedResult { + t.Errorf("[%s] should return %t, but got: %t", c.name, c.expectedResult, result) + } + if !reflect.DeepEqual(taints, c.expectedTaints) { + t.Errorf("[%s] the result taints should be %v, but got: %v", c.name, c.expectedTaints, taints) + } + } +} + +func TestDeleteTaintByKey(t *testing.T) { + cases := []struct { + name string + taints []v1.Taint + taintKey string + expectedTaints []v1.Taint + expectedResult bool + }{ + { + name: "delete taint unsuccessfully", + taints: []v1.Taint{ + { + Key: "foo", + Value: "bar", + Effect: v1.TaintEffectNoSchedule, + }, + }, + taintKey: "foo_1", + expectedTaints: []v1.Taint{ + { + Key: "foo", + Value: "bar", + Effect: v1.TaintEffectNoSchedule, + }, + }, + expectedResult: false, + }, + { + name: "delete taint successfully", + taints: []v1.Taint{ + { + Key: "foo", + Value: "bar", + Effect: v1.TaintEffectNoSchedule, + }, + }, + taintKey: "foo", + expectedTaints: []v1.Taint{}, + expectedResult: true, + }, + { + name: "delete taint from empty taint array", + taints: []v1.Taint{}, + taintKey: "foo", + expectedTaints: []v1.Taint{}, + expectedResult: false, + }, + } + + for _, c := range cases { + taints, result := DeleteTaintsByKey(c.taints, c.taintKey) + if result != c.expectedResult { + t.Errorf("[%s] should return %t, but got: %t", c.name, c.expectedResult, result) + } + if !reflect.DeepEqual(c.expectedTaints, taints) { + t.Errorf("[%s] the result taints should be %v, but got: %v", c.name, c.expectedTaints, taints) + } + } +} + +func TestCheckIfTaintsAlreadyExists(t *testing.T) { + oldTaints := []v1.Taint{ + { + Key: "foo_1", + Value: "bar", + Effect: v1.TaintEffectNoSchedule, + }, + { + Key: "foo_2", + Value: "bar", + Effect: v1.TaintEffectNoSchedule, + }, + { + Key: "foo_3", + Value: "bar", + Effect: v1.TaintEffectNoSchedule, + }, + } + + cases := []struct { + name string + taintsToCheck []v1.Taint + expectedResult string + }{ + { + name: "empty array", + taintsToCheck: []v1.Taint{}, + expectedResult: "", + }, + { + name: "no match", + taintsToCheck: []v1.Taint{ + { + Key: "foo_1", + Effect: v1.TaintEffectNoExecute, + }, + }, + expectedResult: "", + }, + { + name: "match one taint", + taintsToCheck: []v1.Taint{ + { + Key: "foo_2", + Effect: v1.TaintEffectNoSchedule, + }, + }, + expectedResult: "foo_2", + }, + { + name: "match two taints", + taintsToCheck: []v1.Taint{ + { + Key: "foo_2", + Effect: v1.TaintEffectNoSchedule, + }, + { + Key: "foo_3", + Effect: v1.TaintEffectNoSchedule, + }, + }, + expectedResult: "foo_2,foo_3", + }, + } + + for _, c := range cases { + result := CheckIfTaintsAlreadyExists(oldTaints, c.taintsToCheck) + if result != c.expectedResult { + t.Errorf("[%s] should return '%s', but got: '%s'", c.name, c.expectedResult, result) + } + } +} + +func TestReorganizeTaints(t *testing.T) { + node := &v1.Node{ + Spec: v1.NodeSpec{ + Taints: []v1.Taint{ + { + Key: "foo", + Value: "bar", + Effect: v1.TaintEffectNoSchedule, + }, + }, + }, + } + + cases := []struct { + name string + overwrite bool + taintsToAdd []v1.Taint + taintsToDelete []v1.Taint + expectedTaints []v1.Taint + expectedOperation string + expectedErr bool + }{ + { + name: "no changes with overwrite is true", + overwrite: true, + taintsToAdd: []v1.Taint{}, + taintsToDelete: []v1.Taint{}, + expectedTaints: node.Spec.Taints, + expectedOperation: MODIFIED, + expectedErr: false, + }, + { + name: "no changes with overwrite is false", + overwrite: false, + taintsToAdd: []v1.Taint{}, + taintsToDelete: []v1.Taint{}, + expectedTaints: node.Spec.Taints, + expectedOperation: UNTAINTED, + expectedErr: false, + }, + { + name: "add new taint", + overwrite: false, + taintsToAdd: []v1.Taint{ + { + Key: "foo_1", + Effect: v1.TaintEffectNoExecute, + }, + }, + taintsToDelete: []v1.Taint{}, + expectedTaints: append([]v1.Taint{{Key: "foo_1", Effect: v1.TaintEffectNoExecute}}, node.Spec.Taints...), + expectedOperation: TAINTED, + expectedErr: false, + }, + { + name: "delete taint with effect", + overwrite: false, + taintsToAdd: []v1.Taint{}, + taintsToDelete: []v1.Taint{ + { + Key: "foo", + Effect: v1.TaintEffectNoSchedule, + }, + }, + expectedTaints: []v1.Taint{}, + expectedOperation: UNTAINTED, + expectedErr: false, + }, + { + name: "delete taint with no effect", + overwrite: false, + taintsToAdd: []v1.Taint{}, + taintsToDelete: []v1.Taint{ + { + Key: "foo", + }, + }, + expectedTaints: []v1.Taint{}, + expectedOperation: UNTAINTED, + expectedErr: false, + }, + { + name: "delete non-exist taint", + overwrite: false, + taintsToAdd: []v1.Taint{}, + taintsToDelete: []v1.Taint{ + { + Key: "foo_1", + Effect: v1.TaintEffectNoSchedule, + }, + }, + expectedTaints: node.Spec.Taints, + expectedOperation: UNTAINTED, + expectedErr: true, + }, + { + name: "add new taint and delete old one", + overwrite: false, + taintsToAdd: []v1.Taint{ + { + Key: "foo_1", + Effect: v1.TaintEffectNoSchedule, + }, + }, + taintsToDelete: []v1.Taint{ + { + Key: "foo", + Effect: v1.TaintEffectNoSchedule, + }, + }, + expectedTaints: []v1.Taint{ + { + Key: "foo_1", + Effect: v1.TaintEffectNoSchedule, + }, + }, + expectedOperation: MODIFIED, + expectedErr: false, + }, + } + + for _, c := range cases { + operation, taints, err := ReorganizeTaints(node, c.overwrite, c.taintsToAdd, c.taintsToDelete) + if c.expectedErr && err == nil { + t.Errorf("[%s] expect to see an error, but did not get one", c.name) + } else if !c.expectedErr && err != nil { + t.Errorf("[%s] expect not to see an error, but got one: %v", c.name, err) + } + + if !reflect.DeepEqual(c.expectedTaints, taints) { + t.Errorf("[%s] expect to see taint list %#v, but got: %#v", c.name, c.expectedTaints, taints) + } + + if c.expectedOperation != operation { + t.Errorf("[%s] expect to see operation %s, but got: %s", c.name, c.expectedOperation, operation) + } + } +} + +func TestParseTaints(t *testing.T) { + cases := []struct { + name string + spec []string + expectedTaints []v1.Taint + expectedTaintsToRemove []v1.Taint + expectedErr bool + }{ + { + name: "invalid spec format", + spec: []string{"foo=abc"}, + expectedErr: true, + }, + { + name: "invalid spec effect for adding taint", + spec: []string{"foo=abc:invalid_effect"}, + expectedErr: true, + }, + { + name: "invalid spec effect for deleting taint", + spec: []string{"foo:invalid_effect-"}, + expectedErr: true, + }, + { + name: "add new taints", + spec: []string{"foo=abc:NoSchedule", "bar=abc:NoSchedule"}, + expectedTaints: []v1.Taint{ + { + Key: "foo", + Value: "abc", + Effect: v1.TaintEffectNoSchedule, + }, + { + Key: "bar", + Value: "abc", + Effect: v1.TaintEffectNoSchedule, + }, + }, + expectedErr: false, + }, + { + name: "delete taints", + spec: []string{"foo:NoSchedule-", "bar:NoSchedule-"}, + expectedTaintsToRemove: []v1.Taint{ + { + Key: "foo", + Effect: v1.TaintEffectNoSchedule, + }, + { + Key: "bar", + Effect: v1.TaintEffectNoSchedule, + }, + }, + expectedErr: false, + }, + { + name: "add taints and delete taints", + spec: []string{"foo=abc:NoSchedule", "bar=abc:NoSchedule", "foo:NoSchedule-", "bar:NoSchedule-"}, + expectedTaints: []v1.Taint{ + { + Key: "foo", + Value: "abc", + Effect: v1.TaintEffectNoSchedule, + }, + { + Key: "bar", + Value: "abc", + Effect: v1.TaintEffectNoSchedule, + }, + }, + expectedTaintsToRemove: []v1.Taint{ + { + Key: "foo", + Effect: v1.TaintEffectNoSchedule, + }, + { + Key: "bar", + Effect: v1.TaintEffectNoSchedule, + }, + }, + expectedErr: false, + }, + } + + for _, c := range cases { + taints, taintsToRemove, err := ParseTaints(c.spec) + if c.expectedErr && err == nil { + t.Errorf("[%s] expected error, but got nothing", c.name) + } + if !c.expectedErr && err != nil { + t.Errorf("[%s] expected no error, but got: %v", c.name, err) + } + if !reflect.DeepEqual(c.expectedTaints, taints) { + t.Errorf("[%s] expected returen taints as %v, but got: %v", c.name, c.expectedTaints, taints) + } + if !reflect.DeepEqual(c.expectedTaintsToRemove, taintsToRemove) { + t.Errorf("[%s] expected return taints to be removed as %v, but got: %v", c.name, c.expectedTaintsToRemove, taintsToRemove) + } + } +} diff --git a/pkg/util/template/BUILD b/pkg/util/template/BUILD index 015aafcd846..30ed5135c01 100644 --- a/pkg/util/template/BUILD +++ b/pkg/util/template/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["template_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/template", - library = ":go_default_library", deps = ["//vendor/github.com/stretchr/testify/assert:go_default_library"], ) diff --git a/pkg/util/term/BUILD b/pkg/util/term/BUILD index 01b8ca19dc5..894c6008ced 100644 --- a/pkg/util/term/BUILD +++ b/pkg/util/term/BUILD @@ -7,19 +7,89 @@ load( go_library( name = "go_default_library", - srcs = [ - "setsize.go", - ] + select({ - "@io_bazel_rules_go//go/platform:windows_amd64": [ + srcs = select({ + "@io_bazel_rules_go//go/platform:android": [ + "setsize.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "setsize.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "setsize.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "setsize.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "setsize.go", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "setsize.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "setsize.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "setsize.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "setsize.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "setsize.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ "setsize_unsupported.go", ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/pkg/util/term", - deps = [ - "//vendor/github.com/docker/docker/pkg/term:go_default_library", - "//vendor/k8s.io/client-go/tools/remotecommand:go_default_library", - ], + deps = select({ + "@io_bazel_rules_go//go/platform:android": [ + "//vendor/github.com/docker/docker/pkg/term:go_default_library", + "//vendor/k8s.io/client-go/tools/remotecommand:go_default_library", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "//vendor/github.com/docker/docker/pkg/term:go_default_library", + "//vendor/k8s.io/client-go/tools/remotecommand:go_default_library", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "//vendor/github.com/docker/docker/pkg/term:go_default_library", + "//vendor/k8s.io/client-go/tools/remotecommand:go_default_library", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "//vendor/github.com/docker/docker/pkg/term:go_default_library", + "//vendor/k8s.io/client-go/tools/remotecommand:go_default_library", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "//vendor/github.com/docker/docker/pkg/term:go_default_library", + "//vendor/k8s.io/client-go/tools/remotecommand:go_default_library", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "//vendor/github.com/docker/docker/pkg/term:go_default_library", + "//vendor/k8s.io/client-go/tools/remotecommand:go_default_library", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "//vendor/github.com/docker/docker/pkg/term:go_default_library", + "//vendor/k8s.io/client-go/tools/remotecommand:go_default_library", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "//vendor/github.com/docker/docker/pkg/term:go_default_library", + "//vendor/k8s.io/client-go/tools/remotecommand:go_default_library", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "//vendor/github.com/docker/docker/pkg/term:go_default_library", + "//vendor/k8s.io/client-go/tools/remotecommand:go_default_library", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "//vendor/github.com/docker/docker/pkg/term:go_default_library", + "//vendor/k8s.io/client-go/tools/remotecommand:go_default_library", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "//vendor/k8s.io/client-go/tools/remotecommand:go_default_library", + ], + "//conditions:default": [], + }), ) filegroup( diff --git a/pkg/util/threading/BUILD b/pkg/util/threading/BUILD index 79af21a79bb..2f031931b21 100644 --- a/pkg/util/threading/BUILD +++ b/pkg/util/threading/BUILD @@ -16,8 +16,8 @@ go_library( go_test( name = "go_default_test", srcs = ["deadlock-detector_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/threading", - library = ":go_default_library", ) filegroup( diff --git a/pkg/util/tolerations/BUILD b/pkg/util/tolerations/BUILD index 768f41e50de..a4f8868176a 100644 --- a/pkg/util/tolerations/BUILD +++ b/pkg/util/tolerations/BUILD @@ -13,7 +13,7 @@ go_library( "tolerations.go", ], importpath = "k8s.io/kubernetes/pkg/util/tolerations", - deps = ["//pkg/api:go_default_library"], + deps = ["//pkg/apis/core:go_default_library"], ) filegroup( @@ -32,7 +32,7 @@ filegroup( go_test( name = "go_default_test", srcs = ["tolerations_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/tolerations", - library = ":go_default_library", - deps = ["//pkg/api:go_default_library"], + deps = ["//pkg/apis/core:go_default_library"], ) diff --git a/pkg/util/tolerations/tolerations.go b/pkg/util/tolerations/tolerations.go index 9936d2d43ea..5b5cec8b7d7 100644 --- a/pkg/util/tolerations/tolerations.go +++ b/pkg/util/tolerations/tolerations.go @@ -17,7 +17,7 @@ limitations under the License. package tolerations import ( - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) type key struct { diff --git a/pkg/util/tolerations/tolerations_test.go b/pkg/util/tolerations/tolerations_test.go index e652baf357c..3f88e30d3bc 100644 --- a/pkg/util/tolerations/tolerations_test.go +++ b/pkg/util/tolerations/tolerations_test.go @@ -17,9 +17,8 @@ limitations under the License. package tolerations import ( + api "k8s.io/kubernetes/pkg/apis/core" "testing" - - "k8s.io/kubernetes/pkg/api" ) func TestVerifyAgainstWhitelist(t *testing.T) { diff --git a/pkg/util/version/BUILD b/pkg/util/version/BUILD index 9b3cf2bc921..7ab62aae5bd 100644 --- a/pkg/util/version/BUILD +++ b/pkg/util/version/BUILD @@ -18,8 +18,8 @@ go_library( go_test( name = "go_default_test", srcs = ["version_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/util/version", - library = ":go_default_library", ) filegroup( diff --git a/pkg/version/base.go b/pkg/version/base.go index 6239d53ff85..730e79f03d1 100644 --- a/pkg/version/base.go +++ b/pkg/version/base.go @@ -51,9 +51,13 @@ var ( // semantic version is a git hash, but the version itself is no // longer the direct output of "git describe", but a slight // translation to be semver compliant. + + // NOTE: The $Format strings are replaced during 'git archive' thanks to the + // companion .gitattributes file containing 'export-subst' in this same + // directory. See also https://git-scm.com/docs/gitattributes gitVersion = "v0.0.0-master+$Format:%h$" - gitCommit = "$Format:%H$" // sha1 from git, output of $(git rev-parse HEAD) - gitTreeState = "not a git tree" // state of git tree, either "clean" or "dirty" + gitCommit = "$Format:%H$" // sha1 from git, output of $(git rev-parse HEAD) + gitTreeState = "" // state of git tree, either "clean" or "dirty" buildDate = "1970-01-01T00:00:00Z" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ') ) diff --git a/pkg/volume/BUILD b/pkg/volume/BUILD index 6796dcb4e23..4656b9cff97 100644 --- a/pkg/volume/BUILD +++ b/pkg/volume/BUILD @@ -18,11 +18,40 @@ go_library( "plugins.go", "util.go", "volume.go", - "volume_unsupported.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "volume_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "volume_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "volume_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "volume_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ "volume_linux.go", ], + "@io_bazel_rules_go//go/platform:nacl": [ + "volume_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "volume_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "volume_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "volume_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "volume_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "volume_unsupported.go", + ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/pkg/volume", @@ -53,10 +82,10 @@ go_test( "plugins_test.go", "util_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/util/slice:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", @@ -73,7 +102,7 @@ go_test( srcs = [ "metrics_statfs_test.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "metrics_du_test.go", ], "//conditions:default": [], @@ -84,7 +113,7 @@ go_test( "//pkg/volume/testing:go_default_library", "//vendor/k8s.io/client-go/util/testing:go_default_library", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//vendor/golang.org/x/sys/unix:go_default_library", ], "//conditions:default": [], @@ -108,6 +137,7 @@ filegroup( "//pkg/volume/cephfs:all-srcs", "//pkg/volume/cinder:all-srcs", "//pkg/volume/configmap:all-srcs", + "//pkg/volume/csi:all-srcs", "//pkg/volume/downwardapi:all-srcs", "//pkg/volume/empty_dir:all-srcs", "//pkg/volume/fc:all-srcs", diff --git a/pkg/volume/OWNERS b/pkg/volume/OWNERS index 03856d7faa5..9ee3a37d7ce 100644 --- a/pkg/volume/OWNERS +++ b/pkg/volume/OWNERS @@ -7,15 +7,11 @@ approvers: - gnufied - childsb reviewers: -- thockin -- smarterclayton -- brendandburns -- derekwaynecarr -- pmorie - saad-ali -- justinsb - jsafrane - rootfs - jingxu97 - msau42 - gnufied +- verult +- davidz627 diff --git a/pkg/volume/aws_ebs/BUILD b/pkg/volume/aws_ebs/BUILD index 5d45520f468..9fd852900bb 100644 --- a/pkg/volume/aws_ebs/BUILD +++ b/pkg/volume/aws_ebs/BUILD @@ -37,8 +37,8 @@ go_test( "attacher_test.go", "aws_ebs_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/aws_ebs", - library = ":go_default_library", deps = [ "//pkg/cloudprovider/providers/aws:go_default_library", "//pkg/util/mount:go_default_library", @@ -46,6 +46,7 @@ go_test( "//pkg/volume/testing:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", diff --git a/pkg/volume/aws_ebs/attacher.go b/pkg/volume/aws_ebs/attacher.go index bf141424690..19ca2ea49b4 100644 --- a/pkg/volume/aws_ebs/attacher.go +++ b/pkg/volume/aws_ebs/attacher.go @@ -169,7 +169,7 @@ func (attacher *awsElasticBlockStoreAttacher) WaitForAttach(spec *volume.Spec, d case <-ticker.C: glog.V(5).Infof("Checking AWS Volume %q is attached.", volumeID) if devicePath != "" { - devicePaths := getDiskByIdPaths(partition, devicePath) + devicePaths := getDiskByIdPaths(aws.KubernetesVolumeID(volumeSource.VolumeID), partition, devicePath) path, err := verifyDevicePath(devicePaths) if err != nil { // Log error, if any, and continue checking periodically. See issue #11321 @@ -253,24 +253,10 @@ func (plugin *awsElasticBlockStorePlugin) NewDetacher() (volume.Detacher, error) }, nil } -func (detacher *awsElasticBlockStoreDetacher) Detach(deviceMountPath string, nodeName types.NodeName) error { - volumeID := aws.KubernetesVolumeID(path.Base(deviceMountPath)) +func (detacher *awsElasticBlockStoreDetacher) Detach(volumeName string, nodeName types.NodeName) error { + volumeID := aws.KubernetesVolumeID(path.Base(volumeName)) - attached, err := detacher.awsVolumes.DiskIsAttached(volumeID, nodeName) - if err != nil { - // Log error and continue with detach - glog.Errorf( - "Error checking if volume (%q) is already attached to current node (%q). Will continue and try detach anyway. err=%v", - volumeID, nodeName, err) - } - - if err == nil && !attached { - // Volume is already detached from node. - glog.Infof("detach operation was successful. volume %q is already detached from node %q.", volumeID, nodeName) - return nil - } - - if _, err = detacher.awsVolumes.DetachDisk(volumeID, nodeName); err != nil { + if _, err := detacher.awsVolumes.DetachDisk(volumeID, nodeName); err != nil { glog.Errorf("Error detaching volumeID %q: %v", volumeID, err) return err } diff --git a/pkg/volume/aws_ebs/attacher_test.go b/pkg/volume/aws_ebs/attacher_test.go index 16df5812578..1076d06910e 100644 --- a/pkg/volume/aws_ebs/attacher_test.go +++ b/pkg/volume/aws_ebs/attacher_test.go @@ -26,6 +26,7 @@ import ( volumetest "k8s.io/kubernetes/pkg/volume/testing" "github.com/golang/glog" + "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/types" ) @@ -61,10 +62,9 @@ func TestGetVolumeName_PersistentVolume(t *testing.T) { type testcase struct { name aws.KubernetesVolumeID // For fake AWS: - attach attachCall - detach detachCall - diskIsAttached diskIsAttachedCall - t *testing.T + attach attachCall + detach detachCall + t *testing.T // Actual test to run test func(test *testcase) (string, error) @@ -80,7 +80,6 @@ func TestAttachDetach(t *testing.T) { spec := createVolSpec(diskName, readOnly) attachError := errors.New("Fake attach error") detachError := errors.New("Fake detach error") - diskCheckError := errors.New("Fake DiskIsAttached error") tests := []testcase{ // Successful Attach call { @@ -106,44 +105,18 @@ func TestAttachDetach(t *testing.T) { // Detach succeeds { - name: "Detach_Positive", - diskIsAttached: diskIsAttachedCall{diskName, nodeName, true, nil}, - detach: detachCall{diskName, nodeName, "/dev/sda", nil}, + name: "Detach_Positive", + detach: detachCall{diskName, nodeName, "/dev/sda", nil}, test: func(testcase *testcase) (string, error) { detacher := newDetacher(testcase) mountPath := "/mnt/" + string(diskName) return "", detacher.Detach(mountPath, nodeName) }, }, - - // Disk is already detached - { - name: "Detach_Positive_AlreadyDetached", - diskIsAttached: diskIsAttachedCall{diskName, nodeName, false, nil}, - test: func(testcase *testcase) (string, error) { - detacher := newDetacher(testcase) - mountPath := "/mnt/" + string(diskName) - return "", detacher.Detach(mountPath, nodeName) - }, - }, - - // Detach succeeds when DiskIsAttached fails - { - name: "Detach_Positive_CheckFails", - diskIsAttached: diskIsAttachedCall{diskName, nodeName, false, diskCheckError}, - detach: detachCall{diskName, nodeName, "/dev/sda", nil}, - test: func(testcase *testcase) (string, error) { - detacher := newDetacher(testcase) - mountPath := "/mnt/" + string(diskName) - return "", detacher.Detach(mountPath, nodeName) - }, - }, - // Detach fails { - name: "Detach_Negative", - diskIsAttached: diskIsAttachedCall{diskName, nodeName, false, diskCheckError}, - detach: detachCall{diskName, nodeName, "", detachError}, + name: "Detach_Negative", + detach: detachCall{diskName, nodeName, "", detachError}, test: func(testcase *testcase) (string, error) { detacher := newDetacher(testcase) mountPath := "/mnt/" + string(diskName) @@ -297,28 +270,8 @@ func (testcase *testcase) DetachDisk(diskName aws.KubernetesVolumeID, nodeName t } func (testcase *testcase) DiskIsAttached(diskName aws.KubernetesVolumeID, nodeName types.NodeName) (bool, error) { - expected := &testcase.diskIsAttached - - if expected.diskName == "" && expected.nodeName == "" { - // testcase.diskIsAttached looks uninitialized, test did not expect to - // call DiskIsAttached - testcase.t.Errorf("Unexpected DiskIsAttached call!") - return false, errors.New("Unexpected DiskIsAttached call!") - } - - if expected.diskName != diskName { - testcase.t.Errorf("Unexpected DiskIsAttached call: expected diskName %s, got %s", expected.diskName, diskName) - return false, errors.New("Unexpected DiskIsAttached call: wrong diskName") - } - - if expected.nodeName != nodeName { - testcase.t.Errorf("Unexpected DiskIsAttached call: expected nodeName %s, got %s", expected.nodeName, nodeName) - return false, errors.New("Unexpected DiskIsAttached call: wrong nodeName") - } - - glog.V(4).Infof("DiskIsAttached call: %s, %s, returning %v, %v", diskName, nodeName, expected.isAttached, expected.ret) - - return expected.isAttached, expected.ret + // DetachDisk no longer relies on DiskIsAttached api call + return false, nil } func (testcase *testcase) DisksAreAttached(nodeDisks map[types.NodeName][]aws.KubernetesVolumeID) (map[types.NodeName]map[aws.KubernetesVolumeID]bool, error) { @@ -340,3 +293,10 @@ func (testcase *testcase) GetVolumeLabels(volumeName aws.KubernetesVolumeID) (ma func (testcase *testcase) GetDiskPath(volumeName aws.KubernetesVolumeID) (string, error) { return "", errors.New("Not implemented") } + +func (testcase *testcase) ResizeDisk( + volumeName aws.KubernetesVolumeID, + oldSize resource.Quantity, + newSize resource.Quantity) (resource.Quantity, error) { + return oldSize, errors.New("Not implemented") +} diff --git a/pkg/volume/aws_ebs/aws_ebs.go b/pkg/volume/aws_ebs/aws_ebs.go index 5c9f2702c00..9a0091928e2 100644 --- a/pkg/volume/aws_ebs/aws_ebs.go +++ b/pkg/volume/aws_ebs/aws_ebs.go @@ -241,6 +241,33 @@ func (plugin *awsElasticBlockStorePlugin) ConstructVolumeSpec(volName, mountPath return volume.NewSpecFromVolume(awsVolume), nil } +func (plugin *awsElasticBlockStorePlugin) RequiresFSResize() bool { + return true +} + +func (plugin *awsElasticBlockStorePlugin) ExpandVolumeDevice( + spec *volume.Spec, + newSize resource.Quantity, + oldSize resource.Quantity) (resource.Quantity, error) { + var awsVolume aws.Volumes + + awsVolume, err := getCloudProvider(plugin.host.GetCloudProvider()) + + if err != nil { + return oldSize, err + } + // we don't expect to receive this call for non PVs + rawVolumeName := spec.PersistentVolume.Spec.AWSElasticBlockStore.VolumeID + volumeID := aws.KubernetesVolumeID(rawVolumeName) + + if volumeID == "" { + return oldSize, fmt.Errorf("EBS.ExpandVolumeDevice Invalid volume id for %s", spec.Name()) + } + return awsVolume.ResizeDisk(volumeID, oldSize, newSize) +} + +var _ volume.ExpandableVolumePlugin = &awsElasticBlockStorePlugin{} + // Abstract interface to PD operations. type ebsManager interface { CreateVolume(provisioner *awsElasticBlockStoreProvisioner) (volumeID aws.KubernetesVolumeID, volumeSizeGB int, labels map[string]string, fstype string, err error) diff --git a/pkg/volume/aws_ebs/aws_ebs_test.go b/pkg/volume/aws_ebs/aws_ebs_test.go index 7c149bf06d6..9611cec387f 100644 --- a/pkg/volume/aws_ebs/aws_ebs_test.go +++ b/pkg/volume/aws_ebs/aws_ebs_test.go @@ -71,23 +71,14 @@ func TestGetAccessModes(t *testing.T) { t.Errorf("Can't find the plugin by name") } - if !contains(plug.GetAccessModes(), v1.ReadWriteOnce) { + if !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteOnce) { t.Errorf("Expected to support AccessModeTypes: %s", v1.ReadWriteOnce) } - if contains(plug.GetAccessModes(), v1.ReadOnlyMany) { + if volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadOnlyMany) { t.Errorf("Expected not to support AccessModeTypes: %s", v1.ReadOnlyMany) } } -func contains(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool { - for _, m := range modes { - if m == mode { - return true - } - } - return false -} - type fakePDManager struct { } @@ -177,7 +168,7 @@ func TestPlugin(t *testing.T) { if _, err := os.Stat(path); err == nil { t.Errorf("TearDown() failed, volume path still exists: %s", path) } else if !os.IsNotExist(err) { - t.Errorf("SetUp() failed: %v", err) + t.Errorf("TearDown() failed: %v", err) } // Test Provisioner diff --git a/pkg/volume/aws_ebs/aws_util.go b/pkg/volume/aws_ebs/aws_util.go index 711a9eaed39..932617d4e9a 100644 --- a/pkg/volume/aws_ebs/aws_util.go +++ b/pkg/volume/aws_ebs/aws_util.go @@ -18,6 +18,8 @@ package aws_ebs import ( "fmt" + "os" + "path/filepath" "strconv" "strings" "time" @@ -178,7 +180,7 @@ func verifyAllPathsRemoved(devicePaths []string) (bool, error) { // Returns list of all paths for given EBS mount // This is more interesting on GCE (where we are able to identify volumes under /dev/disk-by-id) // Here it is mostly about applying the partition path -func getDiskByIdPaths(partition string, devicePath string) []string { +func getDiskByIdPaths(volumeID aws.KubernetesVolumeID, partition string, devicePath string) []string { devicePaths := []string{} if devicePath != "" { devicePaths = append(devicePaths, devicePath) @@ -190,6 +192,23 @@ func getDiskByIdPaths(partition string, devicePath string) []string { } } + // We need to find NVME volumes, which are mounted on a "random" nvme path ("/dev/nvme0n1"), + // and we have to get the volume id from the nvme interface + awsVolumeID, err := volumeID.MapToAWSVolumeID() + if err != nil { + glog.Warningf("error mapping volume %q to AWS volume: %v", volumeID, err) + } else { + // This is the magic name on which AWS presents NVME devices under /dev/disk/by-id/ + // For example, vol-0fab1d5e3f72a5e23 creates a symlink at /dev/disk/by-id/nvme-Amazon_Elastic_Block_Store_vol0fab1d5e3f72a5e23 + nvmeName := "nvme-Amazon_Elastic_Block_Store_" + strings.Replace(string(awsVolumeID), "-", "", -1) + nvmePath, err := findNvmeVolume(nvmeName) + if err != nil { + glog.Warningf("error looking for nvme volume %q: %v", volumeID, err) + } else if nvmePath != "" { + devicePaths = append(devicePaths, nvmePath) + } + } + return devicePaths } @@ -202,3 +221,35 @@ func getCloudProvider(cloudProvider cloudprovider.Interface) (*aws.Cloud, error) return awsCloudProvider, nil } + +// findNvmeVolume looks for the nvme volume with the specified name +// It follows the symlink (if it exists) and returns the absolute path to the device +func findNvmeVolume(findName string) (device string, err error) { + p := filepath.Join("/dev/disk/by-id/", findName) + stat, err := os.Lstat(p) + if err != nil { + if os.IsNotExist(err) { + glog.V(6).Infof("nvme path not found %q", p) + return "", nil + } + return "", fmt.Errorf("error getting stat of %q: %v", p, err) + } + + if stat.Mode()&os.ModeSymlink != os.ModeSymlink { + glog.Warningf("nvme file %q found, but was not a symlink", p) + return "", nil + } + + // Find the target, resolving to an absolute path + // For example, /dev/disk/by-id/nvme-Amazon_Elastic_Block_Store_vol0fab1d5e3f72a5e23 -> ../../nvme2n1 + resolved, err := filepath.EvalSymlinks(p) + if err != nil { + return "", fmt.Errorf("error reading target of symlink %q: %v", p, err) + } + + if !strings.HasPrefix(resolved, "/dev") { + return "", fmt.Errorf("resolved symlink for %q was unexpected: %q", p, resolved) + } + + return resolved, nil +} diff --git a/pkg/volume/azure_dd/BUILD b/pkg/volume/azure_dd/BUILD index 95e925f6ffb..1751afd4d50 100644 --- a/pkg/volume/azure_dd/BUILD +++ b/pkg/volume/azure_dd/BUILD @@ -11,22 +11,48 @@ go_library( srcs = [ "attacher.go", "azure_common.go", - "azure_common_unsupported.go", "azure_dd.go", "azure_mounter.go", "azure_provision.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "azure_common_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "azure_common_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "azure_common_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "azure_common_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ "azure_common_linux.go", ], - "@io_bazel_rules_go//go/platform:windows_amd64": [ + "@io_bazel_rules_go//go/platform:nacl": [ + "azure_common_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "azure_common_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "azure_common_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "azure_common_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "azure_common_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ "azure_common_windows.go", ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/pkg/volume/azure_dd", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/cloudprovider:go_default_library", "//pkg/cloudprovider/providers/azure:go_default_library", "//pkg/util/keymutex:go_default_library", @@ -66,8 +92,8 @@ go_test( "azure_common_test.go", "azure_dd_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/azure_dd", - library = ":go_default_library", deps = [ "//pkg/util/mount:go_default_library", "//pkg/volume:go_default_library", diff --git a/pkg/volume/azure_dd/OWNERS b/pkg/volume/azure_dd/OWNERS index 1d61e4196dd..635cdd060b4 100755 --- a/pkg/volume/azure_dd/OWNERS +++ b/pkg/volume/azure_dd/OWNERS @@ -1,15 +1,18 @@ approvers: +- andyzhangx - brendandburns +- feiskyer +- karataliu +- khenidak - rootfs reviewers: -- justinsb -- rootfs +- andyzhangx - brendandburns -- thockin -- smarterclayton -- derekwaynecarr -- pmorie -- saad-ali -- jsafrane +- feiskyer - jingxu97 +- jsafrane - msau42 +- karataliu +- khenidak +- rootfs +- saad-ali diff --git a/pkg/volume/azure_dd/attacher.go b/pkg/volume/azure_dd/attacher.go index 11377b0b0ee..839c96048ce 100644 --- a/pkg/volume/azure_dd/attacher.go +++ b/pkg/volume/azure_dd/attacher.go @@ -60,14 +60,14 @@ var getLunMutex = keymutex.NewKeyMutex() func (a *azureDiskAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string, error) { volumeSource, err := getVolumeSource(spec) if err != nil { - glog.Warningf("failed to get azure disk spec") + glog.Warningf("failed to get azure disk spec (%v)", err) return "", err } instanceid, err := a.cloud.InstanceID(nodeName) if err != nil { - glog.Warningf("failed to get azure instance id") - return "", fmt.Errorf("failed to get azure instance id for node %q", nodeName) + glog.Warningf("failed to get azure instance id (%v)", err) + return "", fmt.Errorf("failed to get azure instance id for node %q (%v)", nodeName, err) } if ind := strings.LastIndex(instanceid, "/"); ind >= 0 { instanceid = instanceid[(ind + 1):] @@ -96,8 +96,8 @@ func (a *azureDiskAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) ( lun, err = diskController.GetNextDiskLun(nodeName) if err != nil { - glog.Warningf("no LUN available for instance %q", nodeName) - return "", fmt.Errorf("all LUNs are used, cannot attach volume %q to instance %q", volumeSource.DiskName, instanceid) + glog.Warningf("no LUN available for instance %q (%v)", nodeName, err) + return "", fmt.Errorf("all LUNs are used, cannot attach volume %q to instance %q (%v)", volumeSource.DiskName, instanceid, err) } glog.V(4).Infof("Trying to attach volume %q lun %d to node %q.", volumeSource.DataDiskURI, lun, nodeName) isManagedDisk := (*volumeSource.Kind == v1.AzureManagedDisk) @@ -156,7 +156,7 @@ func (a *azureDiskAttacher) WaitForAttach(spec *volume.Spec, devicePath string, var err error lun, err := strconv.Atoi(devicePath) if err != nil { - return "", fmt.Errorf("azureDisk - Wait for attach expect device path as a lun number, instead got: %s", devicePath) + return "", fmt.Errorf("azureDisk - Wait for attach expect device path as a lun number, instead got: %s (%v)", devicePath, err) } volumeSource, err := getVolumeSource(spec) @@ -260,7 +260,7 @@ func (d *azureDiskDetacher) Detach(diskURI string, nodeName types.NodeName) erro instanceid, err := d.cloud.InstanceID(nodeName) if err != nil { - glog.Warningf("no instance id for node %q, skip detaching", nodeName) + glog.Warningf("no instance id for node %q, skip detaching (%v)", nodeName, err) return nil } if ind := strings.LastIndex(instanceid, "/"); ind >= 0 { diff --git a/pkg/volume/azure_dd/azure_common.go b/pkg/volume/azure_dd/azure_common.go index 157be0d4220..eda01590738 100644 --- a/pkg/volume/azure_dd/azure_common.go +++ b/pkg/volume/azure_dd/azure_common.go @@ -27,7 +27,7 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/cloudprovider/providers/azure" "k8s.io/kubernetes/pkg/util/mount" "k8s.io/kubernetes/pkg/util/strings" @@ -37,6 +37,7 @@ import ( const ( defaultFSType = "ext4" defaultStorageAccountType = storage.StandardLRS + defaultAzureDiskKind = v1.AzureSharedBlobDisk ) type dataDisk struct { @@ -57,7 +58,7 @@ var ( string(api.AzureDedicatedBlobDisk), string(api.AzureManagedDisk)) - supportedStorageAccountTypes = sets.NewString("Premium_LRS", "Standard_LRS") + supportedStorageAccountTypes = sets.NewString("Premium_LRS", "Standard_LRS", "Standard_GRS", "Standard_RAGRS") ) func getPath(uid types.UID, volName string, host volume.VolumeHost) string { @@ -116,7 +117,7 @@ func normalizeFsType(fsType string) string { func normalizeKind(kind string) (v1.AzureDataDiskKind, error) { if kind == "" { - return v1.AzureDedicatedBlobDisk, nil + return defaultAzureDiskKind, nil } if !supportedDiskKinds.Has(kind) { diff --git a/pkg/volume/azure_dd/azure_common_linux.go b/pkg/volume/azure_dd/azure_common_linux.go index d5d0caa1e06..3c19b9244b5 100644 --- a/pkg/volume/azure_dd/azure_common_linux.go +++ b/pkg/volume/azure_dd/azure_common_linux.go @@ -19,6 +19,7 @@ limitations under the License. package azure_dd import ( + "fmt" "path" "strconv" libstrings "strings" @@ -45,6 +46,28 @@ func listAzureDiskPath(io ioHandler) []string { return azureDiskList } +// getDiskLinkByDevName get disk link by device name from devLinkPath, e.g. /dev/disk/azure/, /dev/disk/by-id/ +func getDiskLinkByDevName(io ioHandler, devLinkPath, devName string) (string, error) { + dirs, err := io.ReadDir(devLinkPath) + glog.V(12).Infof("azureDisk - begin to find %s from %s", devName, devLinkPath) + if err == nil { + for _, f := range dirs { + diskPath := devLinkPath + f.Name() + glog.V(12).Infof("azureDisk - begin to Readlink: %s", diskPath) + link, linkErr := io.Readlink(diskPath) + if linkErr != nil { + glog.Warningf("azureDisk - read link (%s) error: %v", diskPath, linkErr) + continue + } + if libstrings.HasSuffix(link, devName) { + return diskPath, nil + } + } + return "", fmt.Errorf("device name(%s) is not found under %s", devName, devLinkPath) + } + return "", fmt.Errorf("read %s error: %v", devLinkPath, err) +} + func scsiHostRescan(io ioHandler, exec mount.Exec) { scsi_path := "/sys/class/scsi_host/" if dirs, err := io.ReadDir(scsi_path); err == nil { @@ -77,6 +100,19 @@ func findDiskByLunWithConstraint(lun int, io ioHandler, azureDisks []string) (st if len(arr) < 4 { continue } + if len(azureDisks) == 0 { + glog.V(4).Infof("/dev/disk/azure is not populated, now try to parse %v directly", name) + target, err := strconv.Atoi(arr[0]) + if err != nil { + glog.Errorf("failed to parse target from %v (%v), err %v", arr[0], name, err) + continue + } + // as observed, targets 0-3 are used by OS disks. Skip them + if target <= 3 { + continue + } + } + // extract LUN from the path. // LUN is the last index of the array, i.e. 1 in /sys/bus/scsi/devices/3:0:0:1 l, err := strconv.Atoi(arr[3]) @@ -116,15 +152,25 @@ func findDiskByLunWithConstraint(lun int, io ioHandler, azureDisks []string) (st dir := path.Join(sys_path, name, "block") if dev, err := io.ReadDir(dir); err == nil { found := false + devName := dev[0].Name() for _, diskName := range azureDisks { - glog.V(12).Infof("azure disk - validating disk %q with sys disk %q", dev[0].Name(), diskName) - if string(dev[0].Name()) == diskName { + glog.V(12).Infof("azureDisk - validating disk %q with sys disk %q", devName, diskName) + if devName == diskName { found = true break } } if !found { - return "/dev/" + dev[0].Name(), nil + devLinkPaths := []string{"/dev/disk/azure/scsi1/", "/dev/disk/by-id/"} + for _, devLinkPath := range devLinkPaths { + diskPath, err := getDiskLinkByDevName(io, devLinkPath, devName) + if err == nil { + glog.V(4).Infof("azureDisk - found %s by %s under %s", diskPath, devName, devLinkPath) + return diskPath, nil + } + glog.Warningf("azureDisk - getDiskLinkByDevName by %s under %s failed, error: %v", devName, devLinkPath, err) + } + return "/dev/" + devName, nil } } } @@ -150,9 +196,10 @@ func formatIfNotFormatted(disk string, fstype string, exec mount.Exec) { _, err := exec.Run("mkfs."+fstype, args...) if err == nil { // the disk has been formatted successfully try to mount it again. - glog.Infof("azureDisk - Disk successfully formatted (mkfs): %s - %s %s", fstype, disk, "tt") + glog.Infof("azureDisk - Disk successfully formatted with 'mkfs.%s %v'", fstype, args) + } else { + glog.Warningf("azureDisk - Error formatting volume with 'mkfs.%s %v': %v", fstype, args, err) } - glog.Warningf("azureDisk - format of disk %q failed: type:(%q) target:(%q) options:(%q)error:(%v)", disk, fstype, "tt", "o", err) } else { if err != nil { glog.Warningf("azureDisk - Failed to check if the disk %s formatted with error %s, will attach anyway", disk, err) diff --git a/pkg/volume/azure_dd/azure_dd.go b/pkg/volume/azure_dd/azure_dd.go index bb45bf3b4d9..09e4cdb6353 100644 --- a/pkg/volume/azure_dd/azure_dd.go +++ b/pkg/volume/azure_dd/azure_dd.go @@ -28,8 +28,8 @@ import ( // interface exposed by the cloud provider implementing Disk functionlity type DiskController interface { - CreateBlobDisk(dataDiskName string, storageAccountType storage.SkuName, sizeGB int, forceStandAlone bool) (string, error) - DeleteBlobDisk(diskUri string, wasForced bool) error + CreateBlobDisk(dataDiskName string, storageAccountType storage.SkuName, sizeGB int) (string, error) + DeleteBlobDisk(diskUri string) error CreateManagedDisk(diskName string, storageAccountType storage.SkuName, sizeGB int, tags map[string]string) (string, error) DeleteManagedDisk(diskURI string) error @@ -48,7 +48,7 @@ type DiskController interface { GetNextDiskLun(nodeName types.NodeName) (int32, error) // Create a VHD blob - CreateVolume(name, storageAccount string, storageAccountType storage.SkuName, location string, requestGB int) (string, string, int, error) + CreateVolume(name, storageAccount, storageAccountType, location string, requestGB int) (string, string, int, error) // Delete a VHD blob DeleteVolume(diskURI string) error } diff --git a/pkg/volume/azure_dd/azure_mounter.go b/pkg/volume/azure_dd/azure_mounter.go index 74f3d39f083..dfa9907e880 100644 --- a/pkg/volume/azure_dd/azure_mounter.go +++ b/pkg/volume/azure_dd/azure_mounter.go @@ -43,10 +43,16 @@ var _ volume.Unmounter = &azureDiskUnmounter{} var _ volume.Mounter = &azureDiskMounter{} func (m *azureDiskMounter) GetAttributes() volume.Attributes { - volumeSource, _ := getVolumeSource(m.spec) + readOnly := false + volumeSource, err := getVolumeSource(m.spec) + if err != nil { + glog.Infof("azureDisk - mounter failed to get volume source for spec %s %v", m.spec.Name(), err) + } else if volumeSource.ReadOnly != nil { + readOnly = *volumeSource.ReadOnly + } return volume.Attributes{ - ReadOnly: *volumeSource.ReadOnly, - Managed: !*volumeSource.ReadOnly, + ReadOnly: readOnly, + Managed: !readOnly, SupportsSELinux: true, } } @@ -94,10 +100,14 @@ func (m *azureDiskMounter) SetUpAt(dir string, fsGroup *int64) error { options := []string{"bind"} - if *volumeSource.ReadOnly { + if volumeSource.ReadOnly != nil && *volumeSource.ReadOnly { options = append(options, "ro") } + if m.options.MountOptions != nil { + options = volume.JoinMountOptions(m.options.MountOptions, options) + } + glog.V(4).Infof("azureDisk - Attempting to mount %s on %s", diskName, dir) isManagedDisk := (*volumeSource.Kind == v1.AzureManagedDisk) globalPDPath, err := makeGlobalPDPath(m.plugin.host, volumeSource.DataDiskURI, isManagedDisk) @@ -138,7 +148,7 @@ func (m *azureDiskMounter) SetUpAt(dir string, fsGroup *int64) error { return mountErr } - if !*volumeSource.ReadOnly { + if volumeSource.ReadOnly == nil || !*volumeSource.ReadOnly { volume.SetVolumeOwnership(m, fsGroup) } diff --git a/pkg/volume/azure_dd/azure_provision.go b/pkg/volume/azure_dd/azure_provision.go index d037f636d80..5f11743d52b 100644 --- a/pkg/volume/azure_dd/azure_provision.go +++ b/pkg/volume/azure_dd/azure_provision.go @@ -55,14 +55,13 @@ func (d *azureDiskDeleter) Delete() error { return err } - wasStandAlone := (*volumeSource.Kind != v1.AzureSharedBlobDisk) managed := (*volumeSource.Kind == v1.AzureManagedDisk) if managed { return diskController.DeleteManagedDisk(volumeSource.DataDiskURI) } - return diskController.DeleteBlobDisk(volumeSource.DataDiskURI, wasStandAlone) + return diskController.DeleteBlobDisk(volumeSource.DataDiskURI) } func (p *azureDiskProvisioner) Provision() (*v1.PersistentVolume, error) { @@ -149,26 +148,13 @@ func (p *azureDiskProvisioner) Provision() (*v1.PersistentVolume, error) { return nil, err } } else { - forceStandAlone := (kind == v1.AzureDedicatedBlobDisk) if kind == v1.AzureDedicatedBlobDisk { - if location != "" && account != "" { - // use dedicated kind (by default) for compatibility - _, diskURI, _, err = diskController.CreateVolume(name, account, skuName, location, requestGB) - if err != nil { - return nil, err - } - } else { - if location != "" || account != "" { - return nil, fmt.Errorf("AzureDisk - location(%s) and account(%s) must be both empty or specified for dedicated kind, only one value specified is not allowed", - location, account) - } - diskURI, err = diskController.CreateBlobDisk(name, skuName, requestGB, forceStandAlone) - if err != nil { - return nil, err - } + _, diskURI, _, err = diskController.CreateVolume(name, account, storageAccountType, location, requestGB) + if err != nil { + return nil, err } } else { - diskURI, err = diskController.CreateBlobDisk(name, skuName, requestGB, forceStandAlone) + diskURI, err = diskController.CreateBlobDisk(name, skuName, requestGB) if err != nil { return nil, err } diff --git a/pkg/volume/azure_file/BUILD b/pkg/volume/azure_file/BUILD index fec0c0a9504..ea904ced6ce 100644 --- a/pkg/volume/azure_file/BUILD +++ b/pkg/volume/azure_file/BUILD @@ -35,8 +35,8 @@ go_library( go_test( name = "go_default_test", srcs = ["azure_file_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/azure_file", - library = ":go_default_library", deps = [ "//pkg/cloudprovider/providers/azure:go_default_library", "//pkg/cloudprovider/providers/fake:go_default_library", diff --git a/pkg/volume/azure_file/OWNERS b/pkg/volume/azure_file/OWNERS index 1d61e4196dd..c71485c993a 100644 --- a/pkg/volume/azure_file/OWNERS +++ b/pkg/volume/azure_file/OWNERS @@ -1,15 +1,18 @@ approvers: +- andyzhangx - brendandburns +- feiskyer +- karataliu +- khenidak - rootfs reviewers: -- justinsb -- rootfs +- andyzhangx - brendandburns -- thockin -- smarterclayton -- derekwaynecarr -- pmorie -- saad-ali +- feiskyer - jsafrane - jingxu97 +- karataliu +- khenidak - msau42 +- rootfs +- saad-ali diff --git a/pkg/volume/azure_file/azure_file.go b/pkg/volume/azure_file/azure_file.go index 4b924259982..e81d10de85f 100644 --- a/pkg/volume/azure_file/azure_file.go +++ b/pkg/volume/azure_file/azure_file.go @@ -222,11 +222,12 @@ func (b *azureFileMounter) SetUpAt(dir string, fsGroup *int64) error { } else { os.MkdirAll(dir, 0700) // parameters suggested by https://azure.microsoft.com/en-us/documentation/articles/storage-how-to-use-files-linux/ - options := []string{fmt.Sprintf("vers=3.0,username=%s,password=%s,dir_mode=0700,file_mode=0700", accountName, accountKey)} + options := []string{fmt.Sprintf("username=%s,password=%s", accountName, accountKey)} if b.readOnly { options = append(options, "ro") } mountOptions = volume.JoinMountOptions(b.mountOptions, options) + mountOptions = appendDefaultMountOptions(mountOptions) } err = b.mounter.Mount(source, dir, "cifs", mountOptions) diff --git a/pkg/volume/azure_file/azure_file_test.go b/pkg/volume/azure_file/azure_file_test.go index d6fc125963e..cc28b11f73d 100644 --- a/pkg/volume/azure_file/azure_file_test.go +++ b/pkg/volume/azure_file/azure_file_test.go @@ -17,9 +17,11 @@ limitations under the License. package azure_file import ( + "fmt" "io/ioutil" "os" "path" + "reflect" "strings" "testing" @@ -71,20 +73,11 @@ func TestGetAccessModes(t *testing.T) { if err != nil { t.Errorf("Can't find the plugin by name") } - if !contains(plug.GetAccessModes(), v1.ReadWriteOnce) || !contains(plug.GetAccessModes(), v1.ReadOnlyMany) || !contains(plug.GetAccessModes(), v1.ReadWriteMany) { + if !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteOnce) || !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadOnlyMany) || !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteMany) { t.Errorf("Expected three AccessModeTypes: %s, %s, and %s", v1.ReadWriteOnce, v1.ReadOnlyMany, v1.ReadWriteMany) } } -func contains(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool { - for _, m := range modes { - if m == mode { - return true - } - } - return false -} - func getAzureTestCloud(t *testing.T) *azure.Cloud { config := `{ "aadClientId": "--aad-client-id--", @@ -193,7 +186,7 @@ func testPlugin(t *testing.T, tmpDir string, volumeHost volume.VolumeHost) { if _, err := os.Stat(path); err == nil { t.Errorf("TearDown() failed, volume path still exists: %s", path) } else if !os.IsNotExist(err) { - t.Errorf("SetUp() failed: %v", err) + t.Errorf("TearDown() failed: %v", err) } } @@ -367,3 +360,38 @@ func TestGetSecretNameAndNamespaceForPV(t *testing.T) { } } + +func TestAppendDefaultMountOptions(t *testing.T) { + tests := []struct { + options []string + expected []string + }{ + { + options: []string{"dir_mode=0777"}, + expected: []string{"dir_mode=0777", fmt.Sprintf("%s=%s", fileMode, defaultFileMode), fmt.Sprintf("%s=%s", vers, defaultVers)}, + }, + { + options: []string{"file_mode=0777"}, + expected: []string{"file_mode=0777", fmt.Sprintf("%s=%s", dirMode, defaultDirMode), fmt.Sprintf("%s=%s", vers, defaultVers)}, + }, + { + options: []string{"vers=2.1"}, + expected: []string{"vers=2.1", fmt.Sprintf("%s=%s", fileMode, defaultFileMode), fmt.Sprintf("%s=%s", dirMode, defaultDirMode)}, + }, + { + options: []string{""}, + expected: []string{"", fmt.Sprintf("%s=%s", fileMode, defaultFileMode), fmt.Sprintf("%s=%s", dirMode, defaultDirMode), fmt.Sprintf("%s=%s", vers, defaultVers)}, + }, + { + options: []string{"file_mode=0777", "dir_mode=0777"}, + expected: []string{"file_mode=0777", "dir_mode=0777", fmt.Sprintf("%s=%s", vers, defaultVers)}, + }, + } + + for _, test := range tests { + result := appendDefaultMountOptions(test.options) + if !reflect.DeepEqual(result, test.expected) { + t.Errorf("input: %q, appendDefaultMountOptions result: %q, expected: %q", test.options, result, test.expected) + } + } +} diff --git a/pkg/volume/azure_file/azure_util.go b/pkg/volume/azure_file/azure_util.go index 41caf86deff..0a426e09853 100644 --- a/pkg/volume/azure_file/azure_util.go +++ b/pkg/volume/azure_file/azure_util.go @@ -18,6 +18,7 @@ package azure_file import ( "fmt" + "strings" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -25,6 +26,15 @@ import ( "k8s.io/kubernetes/pkg/volume" ) +const ( + fileMode = "file_mode" + dirMode = "dir_mode" + vers = "vers" + defaultFileMode = "0755" + defaultDirMode = "0755" + defaultVers = "3.0" +) + // Abstract interface to azure file operations. type azureUtil interface { GetAzureCredentials(host volume.VolumeHost, nameSpace, secretName string) (string, string, error) @@ -40,7 +50,7 @@ func (s *azureSvc) GetAzureCredentials(host volume.VolumeHost, nameSpace, secret return "", "", fmt.Errorf("Cannot get kube client") } - keys, err := kubeClient.Core().Secrets(nameSpace).Get(secretName, metav1.GetOptions{}) + keys, err := kubeClient.CoreV1().Secrets(nameSpace).Get(secretName, metav1.GetOptions{}) if err != nil { return "", "", fmt.Errorf("Couldn't get secret %v/%v", nameSpace, secretName) } @@ -75,7 +85,7 @@ func (s *azureSvc) SetAzureCredentials(host volume.VolumeHost, nameSpace, accoun }, Type: "Opaque", } - _, err := kubeClient.Core().Secrets(nameSpace).Create(secret) + _, err := kubeClient.CoreV1().Secrets(nameSpace).Create(secret) if errors.IsAlreadyExists(err) { err = nil } @@ -84,3 +94,36 @@ func (s *azureSvc) SetAzureCredentials(host volume.VolumeHost, nameSpace, accoun } return secretName, err } + +// check whether mountOptions contain file_mode and dir_mode, if not, append default mode +func appendDefaultMountOptions(mountOptions []string) []string { + fileModeFlag := false + dirModeFlag := false + versFlag := false + + for _, mountOption := range mountOptions { + if strings.HasPrefix(mountOption, fileMode) { + fileModeFlag = true + } + if strings.HasPrefix(mountOption, dirMode) { + dirModeFlag = true + } + if strings.HasPrefix(mountOption, vers) { + versFlag = true + } + } + + allMountOptions := mountOptions + if !fileModeFlag { + allMountOptions = append(allMountOptions, fmt.Sprintf("%s=%s", fileMode, defaultFileMode)) + } + + if !dirModeFlag { + allMountOptions = append(allMountOptions, fmt.Sprintf("%s=%s", dirMode, defaultDirMode)) + } + + if !versFlag { + allMountOptions = append(allMountOptions, fmt.Sprintf("%s=%s", vers, defaultVers)) + } + return allMountOptions +} diff --git a/pkg/volume/cephfs/BUILD b/pkg/volume/cephfs/BUILD index a61e1fb8595..ca4cf8c54fc 100644 --- a/pkg/volume/cephfs/BUILD +++ b/pkg/volume/cephfs/BUILD @@ -28,8 +28,8 @@ go_library( go_test( name = "go_default_test", srcs = ["cephfs_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/cephfs", - library = ":go_default_library", deps = [ "//pkg/util/mount:go_default_library", "//pkg/volume:go_default_library", diff --git a/pkg/volume/cephfs/OWNERS b/pkg/volume/cephfs/OWNERS index a8a2447c26d..35dc489f905 100644 --- a/pkg/volume/cephfs/OWNERS +++ b/pkg/volume/cephfs/OWNERS @@ -2,13 +2,7 @@ approvers: - rootfs - saad-ali reviewers: -- justinsb - rootfs -- brendandburns -- thockin -- smarterclayton -- derekwaynecarr -- pmorie - saad-ali - jsafrane - jingxu97 diff --git a/pkg/volume/cephfs/cephfs.go b/pkg/volume/cephfs/cephfs.go index 5c0fdd04a74..bbe681d71b5 100644 --- a/pkg/volume/cephfs/cephfs.go +++ b/pkg/volume/cephfs/cephfs.go @@ -19,6 +19,9 @@ package cephfs import ( "fmt" "os" + "os/exec" + "path" + "runtime" "strings" "github.com/golang/glog" @@ -100,7 +103,7 @@ func (plugin *cephfsPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume. if kubeClient == nil { return nil, fmt.Errorf("Cannot get kube client") } - secrets, err := kubeClient.Core().Secrets(secretNs).Get(secretName, metav1.GetOptions{}) + secrets, err := kubeClient.CoreV1().Secrets(secretNs).Get(secretName, metav1.GetOptions{}) if err != nil { err = fmt.Errorf("Couldn't get secret %v/%v err: %v", secretNs, secretName, err) return nil, err @@ -231,15 +234,32 @@ func (cephfsVolume *cephfsMounter) SetUpAt(dir string, fsGroup *int64) error { } os.MkdirAll(dir, 0750) - err = cephfsVolume.execMount(dir) - if err == nil { - return nil + // check whether it belongs to fuse, if not, default to use kernel mount. + if cephfsVolume.checkFuseMount() { + glog.V(4).Infof("CephFS fuse mount.") + err = cephfsVolume.execFuseMount(dir) + // cleanup no matter if fuse mount fail. + keyringPath := cephfsVolume.GetKeyringPath() + _, StatErr := os.Stat(keyringPath) + if !os.IsNotExist(StatErr) { + os.RemoveAll(keyringPath) + } + if err == nil { + // cephfs fuse mount succeeded. + return nil + } else { + // if cephfs fuse mount failed, fallback to kernel mount. + glog.V(4).Infof("CephFS fuse mount failed: %v, fallback to kernel mount.", err) + } } - - // cleanup upon failure - util.UnmountPath(dir, cephfsVolume.mounter) - // return error - return err + glog.V(4).Infof("CephFS kernel mount.") + err = cephfsVolume.execMount(dir) + if err != nil { + // cleanup upon failure. + util.UnmountPath(dir, cephfsVolume.mounter) + return err + } + return nil } type cephfsUnmounter struct { @@ -264,6 +284,14 @@ func (cephfsVolume *cephfs) GetPath() string { return cephfsVolume.plugin.host.GetPodVolumeDir(cephfsVolume.podUID, utilstrings.EscapeQualifiedNameForDisk(name), cephfsVolume.volName) } +// GetKeyringPath creates cephfuse keyring path +func (cephfsVolume *cephfs) GetKeyringPath() string { + name := cephfsPluginName + volumeDir := cephfsVolume.plugin.host.GetPodVolumeDir(cephfsVolume.podUID, utilstrings.EscapeQualifiedNameForDisk(name), cephfsVolume.volName) + volumeKeyringDir := volumeDir + "~keyring" + return volumeKeyringDir +} + func (cephfsVolume *cephfs) execMount(mountpoint string) error { // cephfs mount option ceph_opt := "" @@ -299,6 +327,92 @@ func (cephfsVolume *cephfs) execMount(mountpoint string) error { return nil } +func (cephfsMounter *cephfsMounter) checkFuseMount() bool { + execute := cephfsMounter.plugin.host.GetExec(cephfsMounter.plugin.GetPluginName()) + switch runtime.GOOS { + case "linux": + retBytes, err := execute.Run("/bin/ls", "/sbin/mount.fuse.ceph") + if err == nil && string(retBytes) == "/sbin/mount.fuse.ceph\n" { + glog.V(4).Infof("/sbin/mount.fuse.ceph exists, it should be fuse mount") + return true + } + return false + } + return false +} + +func (cephfsVolume *cephfs) execFuseMount(mountpoint string) error { + // cephfs keyring file + keyring_file := "" + // override secretfile if secret is provided + if cephfsVolume.secret != "" { + // TODO: cephfs fuse currently doesn't support secret option, + // remove keyring file create once secret option is supported. + glog.V(4).Infof("cephfs mount begin using fuse.") + + keyringPath := cephfsVolume.GetKeyringPath() + os.MkdirAll(keyringPath, 0750) + + payload := make(map[string]util.FileProjection, 1) + var fileProjection util.FileProjection + + keyring := fmt.Sprintf("[client.%s]\n", cephfsVolume.id) + "key = " + cephfsVolume.secret + "\n" + + fileProjection.Data = []byte(keyring) + fileProjection.Mode = int32(0644) + fileName := cephfsVolume.id + ".keyring" + + payload[fileName] = fileProjection + + writerContext := fmt.Sprintf("cephfuse:%v.keyring", cephfsVolume.id) + writer, err := util.NewAtomicWriter(keyringPath, writerContext) + if err != nil { + glog.Errorf("failed to create atomic writer: %v", err) + return err + } + + err = writer.Write(payload) + if err != nil { + glog.Errorf("failed to write payload to dir: %v", err) + return err + } + + keyring_file = path.Join(keyringPath, fileName) + + } else { + keyring_file = cephfsVolume.secret_file + } + + // build src like mon1:6789,mon2:6789,mon3:6789:/ + hosts := cephfsVolume.mon + l := len(hosts) + // pass all monitors and let ceph randomize and fail over + i := 0 + src := "" + for i = 0; i < l-1; i++ { + src += hosts[i] + "," + } + src += hosts[i] + + mountArgs := []string{} + mountArgs = append(mountArgs, "-k") + mountArgs = append(mountArgs, keyring_file) + mountArgs = append(mountArgs, "-m") + mountArgs = append(mountArgs, src) + mountArgs = append(mountArgs, mountpoint) + mountArgs = append(mountArgs, "-r") + mountArgs = append(mountArgs, cephfsVolume.path) + + glog.V(4).Infof("Mounting cmd ceph-fuse with arguments (%s)", mountArgs) + command := exec.Command("ceph-fuse", mountArgs...) + output, err := command.CombinedOutput() + if err != nil || !(strings.Contains(string(output), "starting fuse")) { + return fmt.Errorf("Ceph-fuse failed: %v\narguments: %s\nOutput: %s\n", err, mountArgs, string(output)) + } + + return nil +} + func getVolumeSource(spec *volume.Spec) ([]string, string, string, string, bool, error) { if spec.Volume != nil && spec.Volume.CephFS != nil { mon := spec.Volume.CephFS.Monitors diff --git a/pkg/volume/cephfs/cephfs_test.go b/pkg/volume/cephfs/cephfs_test.go index 5cf26effb6c..b09011df25a 100644 --- a/pkg/volume/cephfs/cephfs_test.go +++ b/pkg/volume/cephfs/cephfs_test.go @@ -111,7 +111,7 @@ func TestPlugin(t *testing.T) { if _, err := os.Stat(volumePath); err == nil { t.Errorf("TearDown() failed, volume path still exists: %s", volumePath) } else if !os.IsNotExist(err) { - t.Errorf("SetUp() failed: %v", err) + t.Errorf("TearDown() failed: %v", err) } } diff --git a/pkg/volume/cinder/BUILD b/pkg/volume/cinder/BUILD index 278368682e7..4606eb515b5 100644 --- a/pkg/volume/cinder/BUILD +++ b/pkg/volume/cinder/BUILD @@ -43,8 +43,8 @@ go_test( "attacher_test.go", "cinder_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/cinder", - library = ":go_default_library", deps = [ "//pkg/cloudprovider:go_default_library", "//pkg/util/mount:go_default_library", @@ -52,6 +52,7 @@ go_test( "//pkg/volume/testing:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/client-go/util/testing:go_default_library", ], diff --git a/pkg/volume/cinder/OWNERS b/pkg/volume/cinder/OWNERS index 3184a355eed..d63338e28f0 100644 --- a/pkg/volume/cinder/OWNERS +++ b/pkg/volume/cinder/OWNERS @@ -5,13 +5,7 @@ approvers: - FengyunPan reviewers: - anguslees -- justinsb - rootfs -- brendandburns -- thockin -- smarterclayton -- derekwaynecarr -- pmorie - saad-ali - jsafrane - jingxu97 diff --git a/pkg/volume/cinder/attacher.go b/pkg/volume/cinder/attacher.go index f6d1e4be699..87b58dae01b 100644 --- a/pkg/volume/cinder/attacher.go +++ b/pkg/volume/cinder/attacher.go @@ -44,14 +44,15 @@ var _ volume.Attacher = &cinderDiskAttacher{} var _ volume.AttachableVolumePlugin = &cinderPlugin{} const ( - checkSleepDuration = 1 * time.Second - operationFinishInitDealy = 1 * time.Second + probeVolumeInitDelay = 1 * time.Second + probeVolumeFactor = 2.0 + operationFinishInitDelay = 1 * time.Second operationFinishFactor = 1.1 operationFinishSteps = 10 - diskAttachInitDealy = 1 * time.Second + diskAttachInitDelay = 1 * time.Second diskAttachFactor = 1.2 diskAttachSteps = 15 - diskDetachInitDealy = 1 * time.Second + diskDetachInitDelay = 1 * time.Second diskDetachFactor = 1.2 diskDetachSteps = 13 ) @@ -74,7 +75,7 @@ func (plugin *cinderPlugin) GetDeviceMountRefs(deviceMountPath string) ([]string func (attacher *cinderDiskAttacher) waitOperationFinished(volumeID string) error { backoff := wait.Backoff{ - Duration: operationFinishInitDealy, + Duration: operationFinishInitDelay, Factor: operationFinishFactor, Steps: operationFinishSteps, } @@ -99,7 +100,7 @@ func (attacher *cinderDiskAttacher) waitOperationFinished(volumeID string) error func (attacher *cinderDiskAttacher) waitDiskAttached(instanceID, volumeID string) error { backoff := wait.Backoff{ - Duration: diskAttachInitDealy, + Duration: diskAttachInitDelay, Factor: diskAttachFactor, Steps: diskAttachSteps, } @@ -234,13 +235,13 @@ func (attacher *cinderDiskAttacher) WaitForAttach(spec *volume.Spec, devicePath return "", fmt.Errorf("WaitForAttach failed for Cinder disk %q: devicePath is empty.", volumeID) } - ticker := time.NewTicker(checkSleepDuration) + ticker := time.NewTicker(probeVolumeInitDelay) defer ticker.Stop() timer := time.NewTimer(timeout) defer timer.Stop() + duration := probeVolumeInitDelay for { - probeAttachedVolume() select { case <-ticker.C: glog.V(5).Infof("Checking Cinder disk %q is attached.", volumeID) @@ -256,6 +257,10 @@ func (attacher *cinderDiskAttacher) WaitForAttach(spec *volume.Spec, devicePath } else { // Log an error, and continue checking periodically glog.Errorf("Error: could not find attached Cinder disk %q (path: %q): %v", volumeID, devicePath, err) + // Using exponential backoff instead of linear + ticker.Stop() + duration = time.Duration(float64(duration) * probeVolumeFactor) + ticker = time.NewTicker(duration) } case <-timer.C: return "", fmt.Errorf("Could not find attached Cinder disk %q. Timeout waiting for mount paths to be created.", volumeID) @@ -329,7 +334,7 @@ func (plugin *cinderPlugin) NewDetacher() (volume.Detacher, error) { func (detacher *cinderDiskDetacher) waitOperationFinished(volumeID string) error { backoff := wait.Backoff{ - Duration: operationFinishInitDealy, + Duration: operationFinishInitDelay, Factor: operationFinishFactor, Steps: operationFinishSteps, } @@ -354,7 +359,7 @@ func (detacher *cinderDiskDetacher) waitOperationFinished(volumeID string) error func (detacher *cinderDiskDetacher) waitDiskDetached(instanceID, volumeID string) error { backoff := wait.Backoff{ - Duration: diskDetachInitDealy, + Duration: diskDetachInitDelay, Factor: diskDetachFactor, Steps: diskDetachSteps, } @@ -374,8 +379,8 @@ func (detacher *cinderDiskDetacher) waitDiskDetached(instanceID, volumeID string return err } -func (detacher *cinderDiskDetacher) Detach(deviceMountPath string, nodeName types.NodeName) error { - volumeID := path.Base(deviceMountPath) +func (detacher *cinderDiskDetacher) Detach(volumeName string, nodeName types.NodeName) error { + volumeID := path.Base(volumeName) instances, res := detacher.cinderProvider.Instances() if !res { return fmt.Errorf("failed to list openstack instances") diff --git a/pkg/volume/cinder/attacher_test.go b/pkg/volume/cinder/attacher_test.go index 19a156f4202..f868db675bf 100644 --- a/pkg/volume/cinder/attacher_test.go +++ b/pkg/volume/cinder/attacher_test.go @@ -22,6 +22,7 @@ import ( "testing" "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" "k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/volume" volumetest "k8s.io/kubernetes/pkg/volume/testing" @@ -583,6 +584,10 @@ func (testcase *testcase) InstanceID() (string, error) { return testcase.instanceID, nil } +func (testcase *testcase) ExpandVolume(volumeID string, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) { + return resource.Quantity{}, nil +} + func (testcase *testcase) DeleteVolume(volumeID string) error { return errors.New("Not implemented") } diff --git a/pkg/volume/cinder/cinder.go b/pkg/volume/cinder/cinder.go index cef425b2499..c5b785cd0ab 100644 --- a/pkg/volume/cinder/cinder.go +++ b/pkg/volume/cinder/cinder.go @@ -55,6 +55,7 @@ type CinderProvider interface { DisksAreAttached(instanceID string, volumeIDs []string) (map[string]bool, error) ShouldTrustDevicePath() bool Instances() (cloudprovider.Instances, bool) + ExpandVolume(volumeID string, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) } type cinderPlugin struct { @@ -227,6 +228,31 @@ func (plugin *cinderPlugin) ConstructVolumeSpec(volumeName, mountPath string) (* return volume.NewSpecFromVolume(cinderVolume), nil } +var _ volume.ExpandableVolumePlugin = &cinderPlugin{} + +func (plugin *cinderPlugin) ExpandVolumeDevice(spec *volume.Spec, newSize resource.Quantity, oldSize resource.Quantity) (resource.Quantity, error) { + cinder, _, err := getVolumeSource(spec) + if err != nil { + return oldSize, err + } + cloud, err := plugin.getCloudProvider() + if err != nil { + return oldSize, err + } + + expandedSize, err := cloud.ExpandVolume(cinder.VolumeID, oldSize, newSize) + if err != nil { + return oldSize, err + } + + glog.V(2).Infof("volume %s expanded to new size %d successfully", cinder.VolumeID, int(newSize.Value())) + return expandedSize, nil +} + +func (plugin *cinderPlugin) RequiresFSResize() bool { + return true +} + // Abstract interface to PD operations. type cdManager interface { // Attaches the disk to the kubelet's host machine. diff --git a/pkg/volume/cinder/cinder_test.go b/pkg/volume/cinder/cinder_test.go index a5fc228814f..7a81e4c7836 100644 --- a/pkg/volume/cinder/cinder_test.go +++ b/pkg/volume/cinder/cinder_test.go @@ -194,7 +194,7 @@ func TestPlugin(t *testing.T) { if _, err := os.Stat(path); err == nil { t.Errorf("TearDown() failed, volume path still exists: %s", path) } else if !os.IsNotExist(err) { - t.Errorf("SetUp() failed: %v", err) + t.Errorf("TearDown() failed: %v", err) } // Test Provisioner diff --git a/pkg/volume/cinder/cinder_util.go b/pkg/volume/cinder/cinder_util.go index 1e0d51d6cb1..d994dff0b1a 100644 --- a/pkg/volume/cinder/cinder_util.go +++ b/pkg/volume/cinder/cinder_util.go @@ -144,7 +144,7 @@ func getZonesFromNodes(kubeClient clientset.Interface) (sets.String, error) { // TODO: caching, currently it is overkill because it calls this function // only when it creates dynamic PV zones := make(sets.String) - nodes, err := kubeClient.Core().Nodes().List(metav1.ListOptions{}) + nodes, err := kubeClient.CoreV1().Nodes().List(metav1.ListOptions{}) if err != nil { glog.V(2).Infof("Error listing nodes") return zones, err @@ -224,6 +224,17 @@ func probeAttachedVolume() error { scsiHostRescan() executor := exec.New() + + // udevadm settle waits for udevd to process the device creation + // events for all hardware devices, thus ensuring that any device + // nodes have been created successfully before proceeding. + argsSettle := []string{"settle"} + cmdSettle := executor.Command("udevadm", argsSettle...) + _, errSettle := cmdSettle.CombinedOutput() + if errSettle != nil { + glog.Errorf("error running udevadm settle %v\n", errSettle) + } + args := []string{"trigger"} cmd := executor.Command("udevadm", args...) _, err := cmd.CombinedOutput() diff --git a/pkg/volume/configmap/BUILD b/pkg/volume/configmap/BUILD index 7cdad062d83..b3a3b3b9574 100644 --- a/pkg/volume/configmap/BUILD +++ b/pkg/volume/configmap/BUILD @@ -30,8 +30,8 @@ go_library( go_test( name = "go_default_test", srcs = ["configmap_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/configmap", - library = ":go_default_library", deps = [ "//pkg/volume:go_default_library", "//pkg/volume/empty_dir:go_default_library", diff --git a/pkg/volume/configmap/OWNERS b/pkg/volume/configmap/OWNERS index 72968b277da..baaff6ff460 100644 --- a/pkg/volume/configmap/OWNERS +++ b/pkg/volume/configmap/OWNERS @@ -7,13 +7,7 @@ reviewers: - ivan4th - rata - sjenning -- thockin -- smarterclayton -- brendandburns -- derekwaynecarr -- pmorie - saad-ali -- justinsb - jsafrane - rootfs - jingxu97 diff --git a/pkg/volume/configmap/configmap_test.go b/pkg/volume/configmap/configmap_test.go index 9b238851752..c7be06f00df 100644 --- a/pkg/volume/configmap/configmap_test.go +++ b/pkg/volume/configmap/configmap_test.go @@ -606,6 +606,6 @@ func doTestCleanAndTeardown(plugin volume.VolumePlugin, podUID types.UID, testVo if _, err := os.Stat(volumePath); err == nil { t.Errorf("TearDown() failed, volume path still exists: %s", volumePath) } else if !os.IsNotExist(err) { - t.Errorf("SetUp() failed: %v", err) + t.Errorf("TearDown() failed: %v", err) } } diff --git a/pkg/volume/csi/BUILD b/pkg/volume/csi/BUILD new file mode 100644 index 00000000000..f1bd2c1e7be --- /dev/null +++ b/pkg/volume/csi/BUILD @@ -0,0 +1,74 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "csi_attacher.go", + "csi_client.go", + "csi_mounter.go", + "csi_plugin.go", + ], + importpath = "k8s.io/kubernetes/pkg/volume/csi", + visibility = ["//visibility:public"], + deps = [ + "//pkg/util/mount:go_default_library", + "//pkg/util/strings:go_default_library", + "//pkg/volume:go_default_library", + "//pkg/volume/util:go_default_library", + "//vendor/github.com/container-storage-interface/spec/lib/go/csi:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/golang.org/x/net/context:go_default_library", + "//vendor/google.golang.org/grpc:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/storage/v1alpha1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "csi_attacher_test.go", + "csi_client_test.go", + "csi_mounter_test.go", + "csi_plugin_test.go", + ], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/pkg/volume/csi", + deps = [ + "//pkg/volume:go_default_library", + "//pkg/volume/csi/fake:go_default_library", + "//pkg/volume/testing:go_default_library", + "//vendor/github.com/container-storage-interface/spec/lib/go/csi:go_default_library", + "//vendor/golang.org/x/net/context:go_default_library", + "//vendor/google.golang.org/grpc:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/storage/v1alpha1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", + "//vendor/k8s.io/client-go/util/testing:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//pkg/volume/csi/fake:all-srcs", + ], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/volume/csi/OWNERS b/pkg/volume/csi/OWNERS new file mode 100644 index 00000000000..7d0605ba3dd --- /dev/null +++ b/pkg/volume/csi/OWNERS @@ -0,0 +1,4 @@ +approvers: +- jsafrane +- saad-ali +- vladimirvivien diff --git a/pkg/volume/csi/csi_attacher.go b/pkg/volume/csi/csi_attacher.go new file mode 100644 index 00000000000..2c0b577b8f0 --- /dev/null +++ b/pkg/volume/csi/csi_attacher.go @@ -0,0 +1,272 @@ +/* +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 csi + +import ( + "crypto/sha256" + "errors" + "fmt" + "strings" + "time" + + "github.com/golang/glog" + + "k8s.io/api/core/v1" + storage "k8s.io/api/storage/v1alpha1" + apierrs "k8s.io/apimachinery/pkg/api/errors" + meta "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" + "k8s.io/kubernetes/pkg/volume" +) + +type csiAttacher struct { + plugin *csiPlugin + k8s kubernetes.Interface + waitSleepTime time.Duration +} + +// volume.Attacher methods +var _ volume.Attacher = &csiAttacher{} + +func (c *csiAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string, error) { + if spec == nil { + glog.Error(log("attacher.Attach missing volume.Spec")) + return "", errors.New("missing spec") + } + + csiSource, err := getCSISourceFromSpec(spec) + if err != nil { + glog.Error(log("attacher.Attach failed to get CSI persistent source: %v", err)) + return "", err + } + + node := string(nodeName) + pvName := spec.PersistentVolume.GetName() + attachID := getAttachmentName(csiSource.VolumeHandle, csiSource.Driver, node) + + attachment := &storage.VolumeAttachment{ + ObjectMeta: meta.ObjectMeta{ + Name: attachID, + }, + Spec: storage.VolumeAttachmentSpec{ + NodeName: node, + Attacher: csiSource.Driver, + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &pvName, + }, + }, + Status: storage.VolumeAttachmentStatus{Attached: false}, + } + + _, err = c.k8s.StorageV1alpha1().VolumeAttachments().Create(attachment) + alreadyExist := false + if err != nil { + if !apierrs.IsAlreadyExists(err) { + glog.Error(log("attacher.Attach failed: %v", err)) + return "", err + } + alreadyExist = true + } + + if alreadyExist { + glog.V(4).Info(log("attachment [%v] for volume [%v] already exists (will not be recreated)", attachID, csiSource.VolumeHandle)) + } else { + glog.V(4).Info(log("attachment [%v] for volume [%v] created successfully", attachID, csiSource.VolumeHandle)) + } + + // probe for attachment update here + // NOTE: any error from waiting for attachment is logged only. This is because + // the primariy intent of the enclosing method is to create VolumeAttachment. + // DONOT return that error here as it is mitigated in attacher.WaitForAttach. + volAttachmentOK := true + if _, err := c.waitForVolumeAttachment(csiSource.VolumeHandle, attachID, csiTimeout); err != nil { + volAttachmentOK = false + glog.Error(log("attacher.Attach attempted to wait for attachment to be ready, but failed with: %v", err)) + } + + glog.V(4).Info(log("attacher.Attach finished OK with VolumeAttachment verified=%t: attachment object [%s]", volAttachmentOK, attachID)) + + return attachID, nil +} + +func (c *csiAttacher) WaitForAttach(spec *volume.Spec, attachID string, pod *v1.Pod, timeout time.Duration) (string, error) { + source, err := getCSISourceFromSpec(spec) + if err != nil { + glog.Error(log("attacher.WaitForAttach failed to extract CSI volume source: %v", err)) + return "", err + } + + return c.waitForVolumeAttachment(source.VolumeHandle, attachID, timeout) +} + +func (c *csiAttacher) waitForVolumeAttachment(volumeHandle, attachID string, timeout time.Duration) (string, error) { + glog.V(4).Info(log("probing for updates from CSI driver for [attachment.ID=%v]", attachID)) + + ticker := time.NewTicker(c.waitSleepTime) + defer ticker.Stop() + + timer := time.NewTimer(timeout) // TODO (vladimirvivien) investigate making this configurable + defer timer.Stop() + + //TODO (vladimirvivien) instead of polling api-server, change to a api-server watch + for { + select { + case <-ticker.C: + glog.V(4).Info(log("probing VolumeAttachment [id=%v]", attachID)) + attach, err := c.k8s.StorageV1alpha1().VolumeAttachments().Get(attachID, meta.GetOptions{}) + if err != nil { + glog.Error(log("attacher.WaitForAttach failed (will continue to try): %v", err)) + continue + } + // if being deleted, fail fast + if attach.GetDeletionTimestamp() != nil { + glog.Error(log("VolumeAttachment [%s] has deletion timestamp, will not continue to wait for attachment", attachID)) + return "", errors.New("volume attachment is being deleted") + } + // attachment OK + if attach.Status.Attached { + return attachID, nil + } + // driver reports attach error + attachErr := attach.Status.AttachError + if attachErr != nil { + glog.Error(log("attachment for %v failed: %v", volumeHandle, attachErr.Message)) + return "", errors.New(attachErr.Message) + } + case <-timer.C: + glog.Error(log("attacher.WaitForAttach timeout after %v [volume=%v; attachment.ID=%v]", timeout, volumeHandle, attachID)) + return "", fmt.Errorf("attachment timeout for volume %v", volumeHandle) + } + } +} + +func (c *csiAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName types.NodeName) (map[*volume.Spec]bool, error) { + glog.V(4).Info(log("probing attachment status for %d volume(s) ", len(specs))) + + attached := make(map[*volume.Spec]bool) + + for _, spec := range specs { + if spec == nil { + glog.Error(log("attacher.VolumesAreAttached missing volume.Spec")) + return nil, errors.New("missing spec") + } + source, err := getCSISourceFromSpec(spec) + if err != nil { + glog.Error(log("attacher.VolumesAreAttached failed: %v", err)) + continue + } + + attachID := getAttachmentName(source.VolumeHandle, source.Driver, string(nodeName)) + glog.V(4).Info(log("probing attachment status for VolumeAttachment %v", attachID)) + attach, err := c.k8s.StorageV1alpha1().VolumeAttachments().Get(attachID, meta.GetOptions{}) + if err != nil { + glog.Error(log("attacher.VolumesAreAttached failed for attach.ID=%v: %v", attachID, err)) + continue + } + glog.V(4).Info(log("attacher.VolumesAreAttached attachment [%v] has status.attached=%t", attachID, attach.Status.Attached)) + attached[spec] = attach.Status.Attached + } + + return attached, nil +} + +func (c *csiAttacher) GetDeviceMountPath(spec *volume.Spec) (string, error) { + glog.V(4).Info(log("attacher.GetDeviceMountPath is not implemented")) + return "", nil +} + +func (c *csiAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error { + glog.V(4).Info(log("attacher.MountDevice is not implemented")) + return nil +} + +var _ volume.Detacher = &csiAttacher{} + +func (c *csiAttacher) Detach(volumeName string, nodeName types.NodeName) error { + // volumeName in format driverNamevolumeHandle generated by plugin.GetVolumeName() + if volumeName == "" { + glog.Error(log("detacher.Detach missing value for parameter volumeName")) + return errors.New("missing exepected parameter volumeName") + } + parts := strings.Split(volumeName, volNameSep) + if len(parts) != 2 { + glog.Error(log("detacher.Detach insufficient info encoded in volumeName")) + return errors.New("volumeName missing expected data") + } + + driverName := parts[0] + volID := parts[1] + attachID := getAttachmentName(volID, driverName, string(nodeName)) + if err := c.k8s.StorageV1alpha1().VolumeAttachments().Delete(attachID, nil); err != nil { + glog.Error(log("detacher.Detach failed to delete VolumeAttachment [%s]: %v", attachID, err)) + return err + } + + glog.V(4).Info(log("detacher deleted ok VolumeAttachment.ID=%s", attachID)) + return c.waitForVolumeDetachment(volID, attachID) +} + +func (c *csiAttacher) waitForVolumeDetachment(volumeHandle, attachID string) error { + glog.V(4).Info(log("probing for updates from CSI driver for [attachment.ID=%v]", attachID)) + + ticker := time.NewTicker(c.waitSleepTime) + defer ticker.Stop() + + timeout := c.waitSleepTime * 10 + timer := time.NewTimer(timeout) // TODO (vladimirvivien) investigate making this configurable + defer timer.Stop() + + //TODO (vladimirvivien) instead of polling api-server, change to a api-server watch + for { + select { + case <-ticker.C: + glog.V(4).Info(log("probing VolumeAttachment [id=%v]", attachID)) + attach, err := c.k8s.StorageV1alpha1().VolumeAttachments().Get(attachID, meta.GetOptions{}) + if err != nil { + if apierrs.IsNotFound(err) { + //object deleted or never existed, done + glog.V(4).Info(log("VolumeAttachment object [%v] for volume [%v] not found, object deleted", attachID, volumeHandle)) + return nil + } + glog.Error(log("detacher.WaitForDetach failed for volume [%s] (will continue to try): %v", volumeHandle, err)) + continue + } + + // driver reports attach error + detachErr := attach.Status.DetachError + if detachErr != nil { + glog.Error(log("detachment for VolumeAttachment [%v] for volume [%s] failed: %v", attachID, volumeHandle, detachErr.Message)) + return errors.New(detachErr.Message) + } + case <-timer.C: + glog.Error(log("detacher.WaitForDetach timeout after %v [volume=%v; attachment.ID=%v]", timeout, volumeHandle, attachID)) + return fmt.Errorf("detachment timed out for volume %v", volumeHandle) + } + } +} + +func (c *csiAttacher) UnmountDevice(deviceMountPath string) error { + glog.V(4).Info(log("detacher.UnmountDevice is not implemented")) + return nil +} + +// getAttachmentName returns csi- +func getAttachmentName(volName, csiDriverName, nodeName string) string { + result := sha256.Sum256([]byte(fmt.Sprintf("%s%s%s", volName, csiDriverName, nodeName))) + return fmt.Sprintf("csi-%x", result) +} diff --git a/pkg/volume/csi/csi_attacher_test.go b/pkg/volume/csi/csi_attacher_test.go new file mode 100644 index 00000000000..2b809eb5d64 --- /dev/null +++ b/pkg/volume/csi/csi_attacher_test.go @@ -0,0 +1,321 @@ +/* +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 csi + +import ( + "fmt" + "os" + "testing" + "time" + + storage "k8s.io/api/storage/v1alpha1" + apierrs "k8s.io/apimachinery/pkg/api/errors" + meta "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/kubernetes/pkg/volume" +) + +func makeTestAttachment(attachID, nodeName, pvName string) *storage.VolumeAttachment { + return &storage.VolumeAttachment{ + ObjectMeta: meta.ObjectMeta{ + Name: attachID, + }, + Spec: storage.VolumeAttachmentSpec{ + NodeName: nodeName, + Attacher: "mock", + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &pvName, + }, + }, + Status: storage.VolumeAttachmentStatus{ + Attached: false, + AttachError: nil, + DetachError: nil, + }, + } +} + +func TestAttacherAttach(t *testing.T) { + plug, tmpDir := newTestPlugin(t) + defer os.RemoveAll(tmpDir) + + attacher, err := plug.NewAttacher() + if err != nil { + t.Fatalf("failed to create new attacher: %v", err) + } + + csiAttacher := attacher.(*csiAttacher) + + testCases := []struct { + name string + nodeName string + driverName string + volumeName string + attachID string + shouldFail bool + }{ + { + name: "test ok 1", + nodeName: "testnode-01", + driverName: "testdriver-01", + volumeName: "testvol-01", + attachID: getAttachmentName("testvol-01", "testdriver-01", "testnode-01"), + }, + { + name: "test ok 2", + nodeName: "node02", + driverName: "driver02", + volumeName: "vol02", + attachID: getAttachmentName("vol02", "driver02", "node02"), + }, + { + name: "mismatch vol", + nodeName: "node02", + driverName: "driver02", + volumeName: "vol01", + attachID: getAttachmentName("vol02", "driver02", "node02"), + shouldFail: true, + }, + { + name: "mismatch driver", + nodeName: "node02", + driverName: "driver000", + volumeName: "vol02", + attachID: getAttachmentName("vol02", "driver02", "node02"), + shouldFail: true, + }, + { + name: "mismatch node", + nodeName: "node000", + driverName: "driver000", + volumeName: "vol02", + attachID: getAttachmentName("vol02", "driver02", "node02"), + shouldFail: true, + }, + } + + // attacher loop + for i, tc := range testCases { + t.Log("test case: ", tc.name) + spec := volume.NewSpecFromPersistentVolume(makeTestPV(fmt.Sprintf("test-pv%d", i), 10, tc.driverName, tc.volumeName), false) + + go func(id, nodename string, fail bool) { + attachID, err := csiAttacher.Attach(spec, types.NodeName(nodename)) + if !fail && err != nil { + t.Error("was not expecting failure, but got err: ", err) + } + if attachID != id && !fail { + t.Errorf("expecting attachID %v, got %v", id, attachID) + } + }(tc.attachID, tc.nodeName, tc.shouldFail) + + // update attachment to avoid long waitForAttachment + ticker := time.NewTicker(10 * time.Millisecond) + defer ticker.Stop() + // wait for attachment to be saved + var attach *storage.VolumeAttachment + for i := 0; i < 100; i++ { + attach, err = csiAttacher.k8s.StorageV1alpha1().VolumeAttachments().Get(tc.attachID, meta.GetOptions{}) + if err != nil { + if apierrs.IsNotFound(err) { + <-ticker.C + continue + } + t.Error(err) + } + if attach != nil { + break + } + } + + if attach == nil { + t.Error("attachment not found") + } + attach.Status.Attached = true + _, err = csiAttacher.k8s.StorageV1alpha1().VolumeAttachments().Update(attach) + if err != nil { + t.Error(err) + } + } +} + +func TestAttacherWaitForVolumeAttachment(t *testing.T) { + plug, tmpDir := newTestPlugin(t) + defer os.RemoveAll(tmpDir) + + attacher, err := plug.NewAttacher() + if err != nil { + t.Fatalf("failed to create new attacher: %v", err) + } + csiAttacher := attacher.(*csiAttacher) + nodeName := "test-node" + + testCases := []struct { + name string + attached bool + attachErr *storage.VolumeError + sleepTime time.Duration + timeout time.Duration + shouldFail bool + }{ + {name: "attach ok", attached: true, sleepTime: 10 * time.Millisecond, timeout: 50 * time.Millisecond}, + {name: "attachment error", attachErr: &storage.VolumeError{Message: "missing volume"}, sleepTime: 10 * time.Millisecond, timeout: 30 * time.Millisecond}, + {name: "time ran out", attached: false, sleepTime: 5 * time.Millisecond}, + } + + for i, tc := range testCases { + t.Logf("running test: %v", tc.name) + pvName := fmt.Sprintf("test-pv-%d", i) + volID := fmt.Sprintf("test-vol-%d", i) + attachID := getAttachmentName(volID, testDriver, nodeName) + attachment := makeTestAttachment(attachID, nodeName, pvName) + attachment.Status.Attached = tc.attached + attachment.Status.AttachError = tc.attachErr + csiAttacher.waitSleepTime = tc.sleepTime + + go func() { + _, err := csiAttacher.k8s.StorageV1alpha1().VolumeAttachments().Create(attachment) + if err != nil { + t.Fatalf("failed to attach: %v", err) + } + }() + + retID, err := csiAttacher.waitForVolumeAttachment(volID, attachID, tc.timeout) + if tc.shouldFail && err == nil { + t.Error("expecting failure, but err is nil") + } + if tc.attachErr != nil { + if tc.attachErr.Message != err.Error() { + t.Errorf("expecting error [%v], got [%v]", tc.attachErr.Message, err.Error()) + } + } + if err == nil && retID != attachID { + t.Errorf("attacher.WaitForAttach not returning attachment ID") + } + } +} + +func TestAttacherVolumesAreAttached(t *testing.T) { + plug, tmpDir := newTestPlugin(t) + defer os.RemoveAll(tmpDir) + + attacher, err := plug.NewAttacher() + if err != nil { + t.Fatalf("failed to create new attacher: %v", err) + } + csiAttacher := attacher.(*csiAttacher) + nodeName := "test-node" + + testCases := []struct { + name string + attachedStats map[string]bool + }{ + {"attach + detach", map[string]bool{"vol-01": true, "vol-02": true, "vol-03": false, "vol-04": false, "vol-05": true}}, + {"all detached", map[string]bool{"vol-11": false, "vol-12": false, "vol-13": false, "vol-14": false, "vol-15": false}}, + {"all attached", map[string]bool{"vol-21": true, "vol-22": true, "vol-23": true, "vol-24": true, "vol-25": true}}, + } + + for _, tc := range testCases { + var specs []*volume.Spec + // create and save volume attchments + for volName, stat := range tc.attachedStats { + pv := makeTestPV("test-pv", 10, testDriver, volName) + spec := volume.NewSpecFromPersistentVolume(pv, pv.Spec.PersistentVolumeSource.CSI.ReadOnly) + specs = append(specs, spec) + attachID := getAttachmentName(volName, testDriver, nodeName) + attachment := makeTestAttachment(attachID, nodeName, pv.GetName()) + attachment.Status.Attached = stat + _, err := csiAttacher.k8s.StorageV1alpha1().VolumeAttachments().Create(attachment) + if err != nil { + t.Fatalf("failed to attach: %v", err) + } + } + + // retrieve attached status + stats, err := csiAttacher.VolumesAreAttached(specs, types.NodeName(nodeName)) + if err != nil { + t.Fatal(err) + } + if len(tc.attachedStats) != len(stats) { + t.Errorf("expecting %d attachment status, got %d", len(tc.attachedStats), len(stats)) + } + + // compare attachment status for each spec + for spec, stat := range stats { + source, err := getCSISourceFromSpec(spec) + if err != nil { + t.Error(err) + } + if stat != tc.attachedStats[source.VolumeHandle] { + t.Errorf("expecting volume attachment %t, got %t", tc.attachedStats[source.VolumeHandle], stat) + } + } + } +} + +func TestAttacherDetach(t *testing.T) { + plug, tmpDir := newTestPlugin(t) + defer os.RemoveAll(tmpDir) + + attacher, err := plug.NewAttacher() + if err != nil { + t.Fatalf("failed to create new attacher: %v", err) + } + csiAttacher := attacher.(*csiAttacher) + nodeName := "test-node" + testCases := []struct { + name string + volID string + attachID string + shouldFail bool + }{ + {name: "normal test", volID: "vol-001", attachID: getAttachmentName("vol-001", testDriver, nodeName)}, + {name: "normal test 2", volID: "vol-002", attachID: getAttachmentName("vol-002", testDriver, nodeName)}, + {name: "object not found", volID: "vol-001", attachID: getAttachmentName("vol-002", testDriver, nodeName), shouldFail: true}, + } + + for _, tc := range testCases { + pv := makeTestPV("test-pv", 10, testDriver, tc.volID) + spec := volume.NewSpecFromPersistentVolume(pv, pv.Spec.PersistentVolumeSource.CSI.ReadOnly) + attachment := makeTestAttachment(tc.attachID, nodeName, "test-pv") + _, err := csiAttacher.k8s.StorageV1alpha1().VolumeAttachments().Create(attachment) + if err != nil { + t.Fatalf("failed to attach: %v", err) + } + volumeName, err := plug.GetVolumeName(spec) + if err != nil { + t.Errorf("test case %s failed: %v", tc.name, err) + } + err = csiAttacher.Detach(volumeName, types.NodeName(nodeName)) + if tc.shouldFail && err == nil { + t.Fatal("expecting failure, but err = nil") + } + if !tc.shouldFail && err != nil { + t.Fatalf("unexpected err: %v", err) + } + attach, err := csiAttacher.k8s.StorageV1alpha1().VolumeAttachments().Get(tc.attachID, meta.GetOptions{}) + if err != nil { + if !apierrs.IsNotFound(err) { + t.Fatalf("unexpected err: %v", err) + } + } else { + if attach == nil { + t.Errorf("expecting attachment not to be nil, but it is") + } + } + } +} diff --git a/pkg/volume/csi/csi_client.go b/pkg/volume/csi/csi_client.go new file mode 100644 index 00000000000..c8b8ad0f28e --- /dev/null +++ b/pkg/volume/csi/csi_client.go @@ -0,0 +1,244 @@ +/* +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 csi + +import ( + "bytes" + "errors" + "fmt" + "net" + "time" + + csipb "github.com/container-storage-interface/spec/lib/go/csi" + "github.com/golang/glog" + grpctx "golang.org/x/net/context" + "google.golang.org/grpc" + api "k8s.io/api/core/v1" +) + +type csiClient interface { + AssertSupportedVersion(ctx grpctx.Context, ver *csipb.Version) error + NodeProbe(ctx grpctx.Context, ver *csipb.Version) error + NodePublishVolume( + ctx grpctx.Context, + volumeid string, + readOnly bool, + targetPath string, + accessMode api.PersistentVolumeAccessMode, + volumeInfo map[string]string, + volumeAttribs map[string]string, + fsType string, + ) error + NodeUnpublishVolume(ctx grpctx.Context, volID string, targetPath string) error +} + +// csiClient encapsulates all csi-plugin methods +type csiDriverClient struct { + network string + addr string + conn *grpc.ClientConn + idClient csipb.IdentityClient + nodeClient csipb.NodeClient + ctrlClient csipb.ControllerClient + versionAsserted bool + versionSupported bool + publishAsserted bool + publishCapable bool +} + +func newCsiDriverClient(network, addr string) *csiDriverClient { + return &csiDriverClient{network: network, addr: addr} +} + +// assertConnection ensures a valid connection has been established +// if not, it creates a new connection and associated clients +func (c *csiDriverClient) assertConnection() error { + if c.conn == nil { + conn, err := grpc.Dial( + c.addr, + grpc.WithInsecure(), + grpc.WithDialer(func(target string, timeout time.Duration) (net.Conn, error) { + return net.Dial(c.network, target) + }), + ) + if err != nil { + return err + } + c.conn = conn + c.idClient = csipb.NewIdentityClient(conn) + c.nodeClient = csipb.NewNodeClient(conn) + c.ctrlClient = csipb.NewControllerClient(conn) + + // set supported version + } + + return nil +} + +// AssertSupportedVersion ensures driver supports specified spec version. +// If version is not supported, the assertion fails with an error. +// This test should be done early during the storage operation flow to avoid +// unnecessary calls later. +func (c *csiDriverClient) AssertSupportedVersion(ctx grpctx.Context, ver *csipb.Version) error { + if c.versionAsserted { + if !c.versionSupported { + return fmt.Errorf("version %s not supported", verToStr(ver)) + } + return nil + } + + if err := c.assertConnection(); err != nil { + c.versionAsserted = false + return err + } + + glog.V(4).Info(log("asserting version supported by driver")) + rsp, err := c.idClient.GetSupportedVersions(ctx, &csipb.GetSupportedVersionsRequest{}) + if err != nil { + c.versionAsserted = false + return err + } + + supported := false + vers := rsp.GetSupportedVersions() + glog.V(4).Info(log("driver reports %d versions supported: %s", len(vers), versToStr(vers))) + + for _, v := range vers { + //TODO (vladimirvivien) use more lenient/heuristic for exact or match of ranges etc + if verToStr(v) == verToStr(ver) { + supported = true + break + } + } + + c.versionAsserted = true + c.versionSupported = supported + + if !supported { + return fmt.Errorf("version %s not supported", verToStr(ver)) + } + + glog.V(4).Info(log("version %s supported", verToStr(ver))) + return nil +} + +func (c *csiDriverClient) NodeProbe(ctx grpctx.Context, ver *csipb.Version) error { + glog.V(4).Info(log("sending NodeProbe rpc call to csi driver: [version %v]", ver)) + req := &csipb.NodeProbeRequest{Version: ver} + _, err := c.nodeClient.NodeProbe(ctx, req) + return err +} + +func (c *csiDriverClient) NodePublishVolume( + ctx grpctx.Context, + volID string, + readOnly bool, + targetPath string, + accessMode api.PersistentVolumeAccessMode, + volumeInfo map[string]string, + volumeAttribs map[string]string, + fsType string, +) error { + glog.V(4).Info(log("calling NodePublishVolume rpc [volid=%s,target_path=%s]", volID, targetPath)) + if volID == "" { + return errors.New("missing volume id") + } + if targetPath == "" { + return errors.New("missing target path") + } + if err := c.assertConnection(); err != nil { + glog.Errorf("%v: failed to assert a connection: %v", csiPluginName, err) + return err + } + + req := &csipb.NodePublishVolumeRequest{ + Version: csiVersion, + VolumeId: volID, + TargetPath: targetPath, + Readonly: readOnly, + PublishVolumeInfo: volumeInfo, + VolumeAttributes: volumeAttribs, + + VolumeCapability: &csipb.VolumeCapability{ + AccessMode: &csipb.VolumeCapability_AccessMode{ + Mode: asCSIAccessMode(accessMode), + }, + AccessType: &csipb.VolumeCapability_Mount{ + Mount: &csipb.VolumeCapability_MountVolume{ + FsType: fsType, + }, + }, + }, + } + + _, err := c.nodeClient.NodePublishVolume(ctx, req) + return err +} + +func (c *csiDriverClient) NodeUnpublishVolume(ctx grpctx.Context, volID string, targetPath string) error { + glog.V(4).Info(log("calling NodeUnpublishVolume rpc: [volid=%s, target_path=%s", volID, targetPath)) + if volID == "" { + return errors.New("missing volume id") + } + if targetPath == "" { + return errors.New("missing target path") + } + if err := c.assertConnection(); err != nil { + glog.Error(log("failed to assert a connection: %v", err)) + return err + } + + req := &csipb.NodeUnpublishVolumeRequest{ + Version: csiVersion, + VolumeId: volID, + TargetPath: targetPath, + } + + _, err := c.nodeClient.NodeUnpublishVolume(ctx, req) + return err +} + +func asCSIAccessMode(am api.PersistentVolumeAccessMode) csipb.VolumeCapability_AccessMode_Mode { + switch am { + case api.ReadWriteOnce: + return csipb.VolumeCapability_AccessMode_SINGLE_NODE_WRITER + case api.ReadOnlyMany: + return csipb.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY + case api.ReadWriteMany: + return csipb.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER + } + return csipb.VolumeCapability_AccessMode_UNKNOWN +} + +func verToStr(ver *csipb.Version) string { + if ver == nil { + return "" + } + return fmt.Sprintf("%d.%d.%d", ver.GetMajor(), ver.GetMinor(), ver.GetPatch()) +} + +func versToStr(vers []*csipb.Version) string { + if vers == nil { + return "" + } + str := bytes.NewBufferString("[") + for _, v := range vers { + str.WriteString(fmt.Sprintf("{%s};", verToStr(v))) + } + str.WriteString("]") + return str.String() +} diff --git a/pkg/volume/csi/csi_client_test.go b/pkg/volume/csi/csi_client_test.go new file mode 100644 index 00000000000..744f2bcc8cc --- /dev/null +++ b/pkg/volume/csi/csi_client_test.go @@ -0,0 +1,149 @@ +/* +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 csi + +import ( + "errors" + "testing" + + csipb "github.com/container-storage-interface/spec/lib/go/csi" + grpctx "golang.org/x/net/context" + "google.golang.org/grpc" + api "k8s.io/api/core/v1" + "k8s.io/kubernetes/pkg/volume/csi/fake" +) + +func setupClient(t *testing.T) *csiDriverClient { + client := newCsiDriverClient("unix", "/tmp/test.sock") + client.conn = new(grpc.ClientConn) //avoids creating conn object + + // setup mock grpc clients + client.idClient = fake.NewIdentityClient() + client.nodeClient = fake.NewNodeClient() + client.ctrlClient = fake.NewControllerClient() + + return client +} + +func TestClientAssertSupportedVersion(t *testing.T) { + testCases := []struct { + testName string + ver *csipb.Version + mustFail bool + err error + }{ + {testName: "supported version", ver: &csipb.Version{Major: 0, Minor: 1, Patch: 0}}, + {testName: "unsupported version", ver: &csipb.Version{Major: 0, Minor: 0, Patch: 0}, mustFail: true}, + {testName: "grpc error", ver: &csipb.Version{Major: 0, Minor: 1, Patch: 0}, mustFail: true, err: errors.New("grpc error")}, + } + + for _, tc := range testCases { + t.Log("case: ", tc.testName) + client := setupClient(t) + client.idClient.(*fake.IdentityClient).SetNextError(tc.err) + err := client.AssertSupportedVersion(grpctx.Background(), tc.ver) + if tc.mustFail && err == nil { + t.Error("must fail, but err = nil") + } + } +} + +func TestClientNodeProbe(t *testing.T) { + testCases := []struct { + testName string + ver *csipb.Version + mustFail bool + err error + }{ + {testName: "supported version", ver: &csipb.Version{Major: 0, Minor: 1, Patch: 0}}, + {testName: "grpc error", ver: &csipb.Version{Major: 0, Minor: 1, Patch: 0}, mustFail: true, err: errors.New("grpc error")}, + } + + for _, tc := range testCases { + t.Log("case: ", tc.testName) + client := setupClient(t) + client.nodeClient.(*fake.NodeClient).SetNextError(tc.err) + err := client.NodeProbe(grpctx.Background(), tc.ver) + if tc.mustFail && err == nil { + t.Error("must fail, but err = nil") + } + } +} + +func TestClientNodePublishVolume(t *testing.T) { + testCases := []struct { + name string + volID string + targetPath string + fsType string + mustFail bool + err error + }{ + {name: "test ok", volID: "vol-test", targetPath: "/test/path"}, + {name: "missing volID", targetPath: "/test/path", mustFail: true}, + {name: "missing target path", volID: "vol-test", mustFail: true}, + {name: "bad fs", volID: "vol-test", targetPath: "/test/path", fsType: "badfs", mustFail: true}, + {name: "grpc error", volID: "vol-test", targetPath: "/test/path", mustFail: true, err: errors.New("grpc error")}, + } + + client := setupClient(t) + + for _, tc := range testCases { + t.Log("case: ", tc.name) + client.nodeClient.(*fake.NodeClient).SetNextError(tc.err) + err := client.NodePublishVolume( + grpctx.Background(), + tc.volID, + false, + tc.targetPath, + api.ReadWriteOnce, + map[string]string{"device": "/dev/null"}, + map[string]string{"attr0": "val0"}, + tc.fsType, + ) + + if tc.mustFail && err == nil { + t.Error("must fail, but err is nil: ", err) + } + } +} + +func TestClientNodeUnpublishVolume(t *testing.T) { + testCases := []struct { + name string + volID string + targetPath string + mustFail bool + err error + }{ + {name: "test ok", volID: "vol-test", targetPath: "/test/path"}, + {name: "missing volID", targetPath: "/test/path", mustFail: true}, + {name: "missing target path", volID: "vol-test", mustFail: true}, + {name: "grpc error", volID: "vol-test", targetPath: "/test/path", mustFail: true, err: errors.New("grpc error")}, + } + + client := setupClient(t) + + for _, tc := range testCases { + t.Log("case: ", tc.name) + client.nodeClient.(*fake.NodeClient).SetNextError(tc.err) + err := client.NodeUnpublishVolume(grpctx.Background(), tc.volID, tc.targetPath) + if tc.mustFail && err == nil { + t.Error("must fail, but err is nil: ", err) + } + } +} diff --git a/pkg/volume/csi/csi_mounter.go b/pkg/volume/csi/csi_mounter.go new file mode 100644 index 00000000000..3009fdf47ea --- /dev/null +++ b/pkg/volume/csi/csi_mounter.go @@ -0,0 +1,401 @@ +/* +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 csi + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path" + + "github.com/golang/glog" + grpctx "golang.org/x/net/context" + api "k8s.io/api/core/v1" + meta "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" + kstrings "k8s.io/kubernetes/pkg/util/strings" + "k8s.io/kubernetes/pkg/volume" + "k8s.io/kubernetes/pkg/volume/util" +) + +//TODO (vladimirvivien) move this in a central loc later +var ( + volDataKey = struct { + specVolID, + volHandle, + driverName, + nodeName, + attachmentID string + }{ + "specVolID", + "volumeHandle", + "driverName", + "nodeName", + "attachmentID", + } +) + +type csiMountMgr struct { + k8s kubernetes.Interface + csiClient csiClient + plugin *csiPlugin + driverName string + volumeID string + specVolumeID string + readOnly bool + spec *volume.Spec + pod *api.Pod + podUID types.UID + options volume.VolumeOptions + volumeInfo map[string]string + volume.MetricsNil +} + +// volume.Volume methods +var _ volume.Volume = &csiMountMgr{} + +func (c *csiMountMgr) GetPath() string { + dir := path.Join(getTargetPath(c.podUID, c.specVolumeID, c.plugin.host), "/mount") + glog.V(4).Info(log("mounter.GetPath generated [%s]", dir)) + return dir +} + +func getTargetPath(uid types.UID, specVolumeID string, host volume.VolumeHost) string { + specVolID := kstrings.EscapeQualifiedNameForDisk(specVolumeID) + return host.GetPodVolumeDir(uid, kstrings.EscapeQualifiedNameForDisk(csiPluginName), specVolID) +} + +// volume.Mounter methods +var _ volume.Mounter = &csiMountMgr{} + +func (c *csiMountMgr) CanMount() error { + //TODO (vladimirvivien) use this method to probe controller using CSI.NodeProbe() call + // to ensure Node service is ready in the CSI plugin + return nil +} + +func (c *csiMountMgr) SetUp(fsGroup *int64) error { + return c.SetUpAt(c.GetPath(), fsGroup) +} + +func (c *csiMountMgr) SetUpAt(dir string, fsGroup *int64) error { + glog.V(4).Infof(log("Mounter.SetUpAt(%s)", dir)) + + mounted, err := isDirMounted(c.plugin, dir) + if err != nil { + glog.Error(log("mounter.SetUpAt failed while checking mount status for dir [%s]", dir)) + return err + } + + if mounted { + glog.V(4).Info(log("mounter.SetUpAt skipping mount, dir already mounted [%s]", dir)) + return nil + } + + csiSource, err := getCSISourceFromSpec(c.spec) + if err != nil { + glog.Error(log("mounter.SetupAt failed to get CSI persistent source: %v", err)) + return err + } + + ctx, cancel := grpctx.WithTimeout(grpctx.Background(), csiTimeout) + defer cancel() + + csi := c.csiClient + nodeName := string(c.plugin.host.GetNodeName()) + attachID := getAttachmentName(csiSource.VolumeHandle, csiSource.Driver, nodeName) + + // ensure version is supported + if err := csi.AssertSupportedVersion(ctx, csiVersion); err != nil { + glog.Error(log("mounter.SetUpAt failed to assert version: %v", err)) + return err + } + + // probe driver + // TODO (vladimirvivien) move probe call where it is done only when it is needed. + if err := csi.NodeProbe(ctx, csiVersion); err != nil { + glog.Error(log("mounter.SetUpAt failed to probe driver: %v", err)) + return err + } + + // search for attachment by VolumeAttachment.Spec.Source.PersistentVolumeName + if c.volumeInfo == nil { + attachment, err := c.k8s.StorageV1alpha1().VolumeAttachments().Get(attachID, meta.GetOptions{}) + if err != nil { + glog.Error(log("mounter.SetupAt failed while getting volume attachment [id=%v]: %v", attachID, err)) + return err + } + + if attachment == nil { + glog.Error(log("unable to find VolumeAttachment [id=%s]", attachID)) + return errors.New("no existing VolumeAttachment found") + } + c.volumeInfo = attachment.Status.AttachmentMetadata + } + + // get volume attributes + // TODO: for alpha vol atttributes are passed via PV.Annotations + // Beta will fix that + attribs, err := getVolAttribsFromSpec(c.spec) + if err != nil { + glog.Error(log("mounter.SetUpAt failed to extract volume attributes from PV annotations: %v", err)) + return err + } + + // create target_dir before call to NodePublish + if err := os.MkdirAll(dir, 0750); err != nil { + glog.Error(log("mouter.SetUpAt failed to create dir %#v: %v", dir, err)) + return err + } + glog.V(4).Info(log("created target path successfully [%s]", dir)) + + // persist volume info data for teardown + volData := map[string]string{ + volDataKey.specVolID: c.spec.Name(), + volDataKey.volHandle: csiSource.VolumeHandle, + volDataKey.driverName: csiSource.Driver, + volDataKey.nodeName: nodeName, + volDataKey.attachmentID: attachID, + } + + if err := saveVolumeData(c.plugin, c.podUID, c.spec.Name(), volData); err != nil { + glog.Error(log("mounter.SetUpAt failed to save volume info data: %v", err)) + if err := removeMountDir(c.plugin, dir); err != nil { + glog.Error(log("mounter.SetUpAt failed to remove mount dir after a saveVolumeData() error [%s]: %v", dir, err)) + return err + } + return err + } + + //TODO (vladimirvivien) implement better AccessModes mapping between k8s and CSI + accessMode := api.ReadWriteOnce + if c.spec.PersistentVolume.Spec.AccessModes != nil { + accessMode = c.spec.PersistentVolume.Spec.AccessModes[0] + } + + err = csi.NodePublishVolume( + ctx, + c.volumeID, + c.readOnly, + dir, + accessMode, + c.volumeInfo, + attribs, + "ext4", //TODO needs to be sourced from PV or somewhere else + ) + + if err != nil { + glog.Errorf(log("mounter.SetupAt failed: %v", err)) + if err := removeMountDir(c.plugin, dir); err != nil { + glog.Error(log("mounter.SetuAt failed to remove mount dir after a NodePublish() error [%s]: %v", dir, err)) + return err + } + return err + } + + glog.V(4).Infof(log("mounter.SetUp successfully requested NodePublish [%s]", dir)) + return nil +} + +func (c *csiMountMgr) GetAttributes() volume.Attributes { + return volume.Attributes{ + ReadOnly: c.readOnly, + Managed: !c.readOnly, + SupportsSELinux: false, + } +} + +// volume.Unmounter methods +var _ volume.Unmounter = &csiMountMgr{} + +func (c *csiMountMgr) TearDown() error { + return c.TearDownAt(c.GetPath()) +} +func (c *csiMountMgr) TearDownAt(dir string) error { + glog.V(4).Infof(log("Unmounter.TearDown(%s)", dir)) + + // is dir even mounted ? + // TODO (vladimirvivien) this check may not work for an emptyDir or local storage + // see https://github.com/kubernetes/kubernetes/pull/56836#discussion_r155834524 + mounted, err := isDirMounted(c.plugin, dir) + if err != nil { + glog.Error(log("unmounter.Teardown failed while checking mount status for dir [%s]: %v", dir, err)) + return err + } + + if !mounted { + glog.V(4).Info(log("unmounter.Teardown skipping unmout, dir not mounted [%s]", dir)) + return nil + } + + // load volume info from file + dataDir := path.Dir(dir) // dropoff /mount at end + data, err := loadVolumeData(dataDir, volDataFileName) + if err != nil { + glog.Error(log("unmounter.Teardown failed to load volume data file using dir [%s]: %v", dir, err)) + return err + } + + volID := data[volDataKey.volHandle] + driverName := data[volDataKey.driverName] + + if c.csiClient == nil { + addr := fmt.Sprintf(csiAddrTemplate, driverName) + client := newCsiDriverClient("unix", addr) + glog.V(4).Infof(log("unmounter csiClient setup [volume=%v,driver=%v]", volID, driverName)) + c.csiClient = client + } + + ctx, cancel := grpctx.WithTimeout(grpctx.Background(), csiTimeout) + defer cancel() + + csi := c.csiClient + + // TODO make all assertion calls private within the client itself + if err := csi.AssertSupportedVersion(ctx, csiVersion); err != nil { + glog.Errorf(log("mounter.TearDownAt failed to assert version: %v", err)) + return err + } + + if err := csi.NodeUnpublishVolume(ctx, volID, dir); err != nil { + glog.Errorf(log("mounter.TearDownAt failed: %v", err)) + return err + } + + // clean mount point dir + if err := removeMountDir(c.plugin, dir); err != nil { + glog.Error(log("mounter.TearDownAt failed to clean mount dir [%s]: %v", dir, err)) + return err + } + glog.V(4).Infof(log("mounte.TearDownAt successfully unmounted dir [%s]", dir)) + + return nil +} + +// getVolAttribsFromSpec exracts CSI VolumeAttributes information from PV.Annotations +// using key csi.kubernetes.io/volume-attributes. The annotation value is expected +// to be a JSON-encoded object of form {"key0":"val0",...,"keyN":"valN"} +func getVolAttribsFromSpec(spec *volume.Spec) (map[string]string, error) { + if spec == nil { + return nil, errors.New("missing volume spec") + } + annotations := spec.PersistentVolume.GetAnnotations() + if annotations == nil { + return nil, nil // no annotations found + } + jsonAttribs := annotations[csiVolAttribsAnnotationKey] + if jsonAttribs == "" { + return nil, nil // csi annotation not found + } + attribs := map[string]string{} + if err := json.Unmarshal([]byte(jsonAttribs), &attribs); err != nil { + glog.Error(log("error parsing csi PV.Annotation [%s]=%s: %v", csiVolAttribsAnnotationKey, jsonAttribs, err)) + return nil, err + } + return attribs, nil +} + +// saveVolumeData persists parameter data as json file using the locagion +// generated by /var/lib/kubelet/pods//volumes/kubernetes.io~csi//volume_data.json +func saveVolumeData(p *csiPlugin, podUID types.UID, specVolID string, data map[string]string) error { + dir := getTargetPath(podUID, specVolID, p.host) + dataFilePath := path.Join(dir, volDataFileName) + + file, err := os.Create(dataFilePath) + if err != nil { + glog.Error(log("failed to save volume data file %s: %v", dataFilePath, err)) + return err + } + defer file.Close() + if err := json.NewEncoder(file).Encode(data); err != nil { + glog.Error(log("failed to save volume data file %s: %v", dataFilePath, err)) + return err + } + glog.V(4).Info(log("volume data file saved successfully [%s]", dataFilePath)) + return nil +} + +// loadVolumeData uses the directory returned by mounter.GetPath with value +// /var/lib/kubelet/pods//volumes/kubernetes.io~csi//mount. +// The function extracts specVolumeID and uses it to load the json data file from dir +// /var/lib/kubelet/pods//volumes/kubernetes.io~csi//volume_data.json +func loadVolumeData(dir string, fileName string) (map[string]string, error) { + // remove /mount at the end + dataFileName := path.Join(dir, fileName) + glog.V(4).Info(log("loading volume data file [%s]", dataFileName)) + + file, err := os.Open(dataFileName) + if err != nil { + glog.Error(log("failed to open volume data file [%s]: %v", dataFileName, err)) + return nil, err + } + defer file.Close() + data := map[string]string{} + if err := json.NewDecoder(file).Decode(&data); err != nil { + glog.Error(log("failed to parse volume data file [%s]: %v", dataFileName, err)) + return nil, err + } + + return data, nil +} + +// isDirMounted returns the !notMounted result from IsLikelyNotMountPoint check +func isDirMounted(plug *csiPlugin, dir string) (bool, error) { + mounter := plug.host.GetMounter(plug.GetPluginName()) + notMnt, err := mounter.IsLikelyNotMountPoint(dir) + if err != nil && !os.IsNotExist(err) { + glog.Error(log("isDirMounted IsLikelyNotMountPoint test failed for dir [%v]", dir)) + return false, err + } + return !notMnt, nil +} + +// removeMountDir cleans the mount dir when dir is not mounted and removed the volume data file in dir +func removeMountDir(plug *csiPlugin, mountPath string) error { + glog.V(4).Info(log("removing mount path [%s]", mountPath)) + if pathExists, pathErr := util.PathExists(mountPath); pathErr != nil { + glog.Error(log("failed while checking mount path stat [%s]", pathErr)) + return pathErr + } else if !pathExists { + glog.Warning(log("skipping mount dir removal, path does not exist [%v]", mountPath)) + return nil + } + + mounter := plug.host.GetMounter(plug.GetPluginName()) + notMnt, err := mounter.IsLikelyNotMountPoint(mountPath) + if err != nil { + glog.Error(log("mount dir removal failed [%s]: %v", mountPath, err)) + return err + } + if notMnt { + glog.V(4).Info(log("dir not mounted, deleting it [%s]", mountPath)) + if err := os.Remove(mountPath); err != nil && !os.IsNotExist(err) { + glog.Error(log("failed to remove dir [%s]: %v", mountPath, err)) + return err + } + // remove volume data file as well + dataFile := path.Join(path.Dir(mountPath), volDataFileName) + glog.V(4).Info(log("also deleting volume info data file [%s]", dataFile)) + if err := os.Remove(dataFile); err != nil && !os.IsNotExist(err) { + glog.Error(log("failed to delete volume data file [%s]: %v", dataFile, err)) + return err + } + } + return nil +} diff --git a/pkg/volume/csi/csi_mounter_test.go b/pkg/volume/csi/csi_mounter_test.go new file mode 100644 index 00000000000..13423a52f58 --- /dev/null +++ b/pkg/volume/csi/csi_mounter_test.go @@ -0,0 +1,298 @@ +/* +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 csi + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path" + "testing" + + api "k8s.io/api/core/v1" + storage "k8s.io/api/storage/v1alpha1" + meta "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + fakeclient "k8s.io/client-go/kubernetes/fake" + "k8s.io/kubernetes/pkg/volume" + "k8s.io/kubernetes/pkg/volume/csi/fake" + volumetest "k8s.io/kubernetes/pkg/volume/testing" +) + +var ( + testDriver = "test-driver" + testVol = "vol-123" + testns = "test-ns" + testPodUID = types.UID("test-pod") +) + +func TestMounterGetPath(t *testing.T) { + plug, tmpDir := newTestPlugin(t) + defer os.RemoveAll(tmpDir) + + // TODO (vladimirvivien) specName with slashes will not work + testCases := []struct { + name string + specVolumeName string + path string + }{ + { + name: "simple specName", + specVolumeName: "spec-0", + path: path.Join(tmpDir, fmt.Sprintf("pods/%s/volumes/kubernetes.io~csi/%s/%s", testPodUID, "spec-0", "/mount")), + }, + { + name: "specName with dots", + specVolumeName: "test.spec.1", + path: path.Join(tmpDir, fmt.Sprintf("pods/%s/volumes/kubernetes.io~csi/%s/%s", testPodUID, "test.spec.1", "/mount")), + }, + } + for _, tc := range testCases { + t.Log("test case:", tc.name) + pv := makeTestPV(tc.specVolumeName, 10, testDriver, testVol) + spec := volume.NewSpecFromPersistentVolume(pv, pv.Spec.PersistentVolumeSource.CSI.ReadOnly) + mounter, err := plug.NewMounter( + spec, + &api.Pod{ObjectMeta: meta.ObjectMeta{UID: testPodUID, Namespace: testns}}, + volume.VolumeOptions{}, + ) + if err != nil { + t.Fatalf("Failed to make a new Mounter: %v", err) + } + csiMounter := mounter.(*csiMountMgr) + + path := csiMounter.GetPath() + t.Log("*** GetPath: ", path) + + if tc.path != path { + t.Errorf("expecting path %s, got %s", tc.path, path) + } + } +} + +func TestMounterSetUp(t *testing.T) { + plug, tmpDir := newTestPlugin(t) + defer os.RemoveAll(tmpDir) + fakeClient := fakeclient.NewSimpleClientset() + host := volumetest.NewFakeVolumeHostWithNodeName( + tmpDir, + fakeClient, + nil, + "fakeNode", + ) + plug.host = host + pv := makeTestPV("test-pv", 10, testDriver, testVol) + pvName := pv.GetName() + + mounter, err := plug.NewMounter( + volume.NewSpecFromPersistentVolume(pv, pv.Spec.PersistentVolumeSource.CSI.ReadOnly), + &api.Pod{ObjectMeta: meta.ObjectMeta{UID: testPodUID, Namespace: testns}}, + volume.VolumeOptions{}, + ) + if err != nil { + t.Fatalf("Failed to make a new Mounter: %v", err) + } + + if mounter == nil { + t.Fatal("failed to create CSI mounter") + } + + csiMounter := mounter.(*csiMountMgr) + csiMounter.csiClient = setupClient(t) + + attachID := getAttachmentName(csiMounter.volumeID, csiMounter.driverName, string(plug.host.GetNodeName())) + + attachment := &storage.VolumeAttachment{ + ObjectMeta: meta.ObjectMeta{ + Name: attachID, + }, + Spec: storage.VolumeAttachmentSpec{ + NodeName: "test-node", + Attacher: csiPluginName, + Source: storage.VolumeAttachmentSource{ + PersistentVolumeName: &pvName, + }, + }, + Status: storage.VolumeAttachmentStatus{ + Attached: false, + AttachError: nil, + DetachError: nil, + }, + } + _, err = csiMounter.k8s.StorageV1alpha1().VolumeAttachments().Create(attachment) + if err != nil { + t.Fatalf("failed to setup VolumeAttachment: %v", err) + } + + // Mounter.SetUp() + if err := csiMounter.SetUp(nil); err != nil { + t.Fatalf("mounter.Setup failed: %v", err) + } + path := csiMounter.GetPath() + 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) + } + } + + // ensure call went all the way + pubs := csiMounter.csiClient.(*csiDriverClient).nodeClient.(*fake.NodeClient).GetNodePublishedVolumes() + if pubs[csiMounter.volumeID] != csiMounter.GetPath() { + t.Error("csi server may not have received NodePublishVolume call") + } +} + +func TestUnmounterTeardown(t *testing.T) { + plug, tmpDir := newTestPlugin(t) + defer os.RemoveAll(tmpDir) + + pv := makeTestPV("test-pv", 10, testDriver, testVol) + + unmounter, err := plug.NewUnmounter(pv.ObjectMeta.Name, testPodUID) + if err != nil { + t.Fatalf("failed to make a new Unmounter: %v", err) + } + + csiUnmounter := unmounter.(*csiMountMgr) + csiUnmounter.csiClient = setupClient(t) + + dir := csiUnmounter.GetPath() + + // save the data file prior to unmount + if err := os.MkdirAll(dir, 0755); err != nil && !os.IsNotExist(err) { + t.Errorf("failed to create dir [%s]: %v", dir, err) + } + if err := saveVolumeData( + plug, + testPodUID, + "test-pv", + map[string]string{volDataKey.specVolID: "test-pv", volDataKey.driverName: "driver", volDataKey.volHandle: "vol-handle"}, + ); err != nil { + t.Fatal("failed to save volume data:", err) + } + + err = csiUnmounter.TearDownAt(dir) + if err != nil { + t.Fatal(err) + } + + // ensure csi client call + pubs := csiUnmounter.csiClient.(*csiDriverClient).nodeClient.(*fake.NodeClient).GetNodePublishedVolumes() + if _, ok := pubs[csiUnmounter.volumeID]; ok { + t.Error("csi server may not have received NodeUnpublishVolume call") + } + +} + +func TestGetVolAttribsFromSpec(t *testing.T) { + testCases := []struct { + name string + annotations map[string]string + attribs map[string]string + shouldFail bool + }{ + { + name: "attribs ok", + annotations: map[string]string{"key0": "val0", csiVolAttribsAnnotationKey: `{"k0":"attr0","k1":"attr1","k2":"attr2"}`, "keyN": "valN"}, + attribs: map[string]string{"k0": "attr0", "k1": "attr1", "k2": "attr2"}, + }, + + { + name: "missing attribs", + annotations: map[string]string{"key0": "val0", "keyN": "valN"}, + }, + { + name: "missing annotations", + }, + { + name: "bad json", + annotations: map[string]string{"key0": "val0", csiVolAttribsAnnotationKey: `{"k0""attr0","k1":"attr1,"k2":"attr2"`, "keyN": "valN"}, + attribs: map[string]string{"k0": "attr0", "k1": "attr1", "k2": "attr2"}, + shouldFail: true, + }, + } + spec := volume.NewSpecFromPersistentVolume(makeTestPV("test-pv", 10, testDriver, testVol), false) + for _, tc := range testCases { + t.Log("test case:", tc.name) + spec.PersistentVolume.Annotations = tc.annotations + attribs, err := getVolAttribsFromSpec(spec) + if !tc.shouldFail && err != nil { + t.Error("test case should not fail, but err != nil", err) + } + eq := true + for k, v := range attribs { + if tc.attribs[k] != v { + eq = false + } + } + if !eq { + t.Errorf("expecting attribs %#v, but got %#v", tc.attribs, attribs) + } + } +} + +func TestSaveVolumeData(t *testing.T) { + plug, tmpDir := newTestPlugin(t) + defer os.RemoveAll(tmpDir) + testCases := []struct { + name string + data map[string]string + shouldFail bool + }{ + {name: "test with data ok", data: map[string]string{"key0": "val0", "_key1": "val1", "key2": "val2"}}, + {name: "test with data ok 2 ", data: map[string]string{"_key0_": "val0", "&key1": "val1", "key2": "val2"}}, + } + + for i, tc := range testCases { + t.Log("test case:", tc.name) + specVolID := fmt.Sprintf("spec-volid-%d", i) + mountDir := path.Join(getTargetPath(testPodUID, specVolID, plug.host), "/mount") + if err := os.MkdirAll(mountDir, 0755); err != nil && !os.IsNotExist(err) { + t.Errorf("failed to create dir [%s]: %v", mountDir, err) + } + + err := saveVolumeData(plug, testPodUID, specVolID, tc.data) + + if !tc.shouldFail && err != nil { + t.Error("unexpected failure: ", err) + } + // did file get created + dataDir := getTargetPath(testPodUID, specVolID, plug.host) + file := path.Join(dataDir, volDataFileName) + if _, err := os.Stat(file); err != nil { + t.Error("failed to create data dir:", err) + } + + // validate content + data, err := ioutil.ReadFile(file) + if !tc.shouldFail && err != nil { + t.Error("failed to read data file:", err) + } + + jsonData := new(bytes.Buffer) + if err := json.NewEncoder(jsonData).Encode(tc.data); err != nil { + t.Error("failed to encode json:", err) + } + if string(data) != jsonData.String() { + t.Errorf("expecting encoded data %v, got %v", string(data), jsonData) + } + } +} diff --git a/pkg/volume/csi/csi_plugin.go b/pkg/volume/csi/csi_plugin.go new file mode 100644 index 00000000000..be40992df04 --- /dev/null +++ b/pkg/volume/csi/csi_plugin.go @@ -0,0 +1,253 @@ +/* +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 csi + +import ( + "errors" + "fmt" + "regexp" + "time" + + csipb "github.com/container-storage-interface/spec/lib/go/csi" + "github.com/golang/glog" + api "k8s.io/api/core/v1" + meta "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/kubernetes/pkg/util/mount" + "k8s.io/kubernetes/pkg/volume" +) + +const ( + csiPluginName = "kubernetes.io/csi" + csiVolAttribsAnnotationKey = "csi.volume.kubernetes.io/volume-attributes" + + // TODO (vladimirvivien) implement a more dynamic way to discover + // the unix domain socket path for each installed csi driver. + // TODO (vladimirvivien) would be nice to name socket with a .sock extension + // for consistency. + csiAddrTemplate = "/var/lib/kubelet/plugins/%v/csi.sock" + csiTimeout = 15 * time.Second + volNameSep = "^" + volDataFileName = "vol_data.json" +) + +var ( + // csiVersion supported csi version + csiVersion = &csipb.Version{Major: 0, Minor: 1, Patch: 0} + driverNameRexp = regexp.MustCompile(`^[A-Za-z]+(\.?-?_?[A-Za-z0-9-])+$`) +) + +type csiPlugin struct { + host volume.VolumeHost +} + +// ProbeVolumePlugins returns implemented plugins +func ProbeVolumePlugins() []volume.VolumePlugin { + p := &csiPlugin{ + host: nil, + } + return []volume.VolumePlugin{p} +} + +// volume.VolumePlugin methods +var _ volume.VolumePlugin = &csiPlugin{} + +func (p *csiPlugin) Init(host volume.VolumeHost) error { + glog.Info(log("plugin initializing...")) + p.host = host + return nil +} + +func (p *csiPlugin) GetPluginName() string { + return csiPluginName +} + +// GetvolumeName returns a concatenated string of CSIVolumeSource.DriverCSIVolumeSource.VolumeHandle +// That string value is used in Detach() to extract driver name and volumeName. +func (p *csiPlugin) GetVolumeName(spec *volume.Spec) (string, error) { + csi, err := getCSISourceFromSpec(spec) + if err != nil { + glog.Error(log("plugin.GetVolumeName failed to extract volume source from spec: %v", err)) + return "", err + } + + //TODO (vladimirvivien) this validation should be done at the API validation check + if !isDriverNameValid(csi.Driver) { + glog.Error(log("plugin.GetVolumeName failed to create volume name: invalid csi driver name %s", csi.Driver)) + return "", errors.New("invalid csi driver name") + } + + // return driverNamevolumeHandle + return fmt.Sprintf("%s%s%s", csi.Driver, volNameSep, csi.VolumeHandle), nil +} + +func (p *csiPlugin) CanSupport(spec *volume.Spec) bool { + // TODO (vladimirvivien) CanSupport should also take into account + // the availability/registration of specified Driver in the volume source + return spec.PersistentVolume != nil && spec.PersistentVolume.Spec.CSI != nil +} + +func (p *csiPlugin) RequiresRemount() bool { + return false +} + +func (p *csiPlugin) NewMounter( + spec *volume.Spec, + pod *api.Pod, + _ volume.VolumeOptions) (volume.Mounter, error) { + pvSource, err := getCSISourceFromSpec(spec) + if err != nil { + return nil, err + } + + // TODO (vladimirvivien) consider moving this check in API validation + // check Driver name to conform to CSI spec + if !isDriverNameValid(pvSource.Driver) { + glog.Error(log("driver name does not conform to CSI spec: %s", pvSource.Driver)) + return nil, errors.New("driver name is invalid") + } + + // before it is used in any paths such as socket etc + addr := fmt.Sprintf(csiAddrTemplate, pvSource.Driver) + glog.V(4).Infof(log("setting up mounter for [volume=%v,driver=%v]", pvSource.VolumeHandle, pvSource.Driver)) + client := newCsiDriverClient("unix", addr) + + k8s := p.host.GetKubeClient() + if k8s == nil { + glog.Error(log("failed to get a kubernetes client")) + return nil, errors.New("failed to get a Kubernetes client") + } + + mounter := &csiMountMgr{ + plugin: p, + k8s: k8s, + spec: spec, + pod: pod, + podUID: pod.UID, + driverName: pvSource.Driver, + volumeID: pvSource.VolumeHandle, + specVolumeID: spec.Name(), + csiClient: client, + } + return mounter, nil +} + +func (p *csiPlugin) NewUnmounter(specName string, podUID types.UID) (volume.Unmounter, error) { + glog.V(4).Infof(log("setting up unmounter for [name=%v, podUID=%v]", specName, podUID)) + unmounter := &csiMountMgr{ + plugin: p, + podUID: podUID, + specVolumeID: specName, + } + return unmounter, nil +} + +func (p *csiPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) { + glog.V(4).Info(log("plugin.ConstructVolumeSpec [pv.Name=%v, path=%v]", volumeName, mountPath)) + + volData, err := loadVolumeData(mountPath, volDataFileName) + if err != nil { + glog.Error(log("plugin.ConstructVolumeSpec failed loading volume data using [%s]: %v", mountPath, err)) + return nil, err + } + + glog.V(4).Info(log("plugin.ConstructVolumeSpec extracted [%#v]", volData)) + + pv := &api.PersistentVolume{ + ObjectMeta: meta.ObjectMeta{ + Name: volData[volDataKey.specVolID], + }, + Spec: api.PersistentVolumeSpec{ + PersistentVolumeSource: api.PersistentVolumeSource{ + CSI: &api.CSIPersistentVolumeSource{ + Driver: volData[volDataKey.driverName], + VolumeHandle: volData[volDataKey.volHandle], + }, + }, + }, + } + + return volume.NewSpecFromPersistentVolume(pv, false), nil +} + +func (p *csiPlugin) SupportsMountOption() bool { + // TODO (vladimirvivien) use CSI VolumeCapability.MountVolume.mount_flags + // to probe for the result for this method:w + return false +} + +func (p *csiPlugin) SupportsBulkVolumeVerification() bool { + return false +} + +// volume.AttachableVolumePlugin methods +var _ volume.AttachableVolumePlugin = &csiPlugin{} + +func (p *csiPlugin) NewAttacher() (volume.Attacher, error) { + k8s := p.host.GetKubeClient() + if k8s == nil { + glog.Error(log("unable to get kubernetes client from host")) + return nil, errors.New("unable to get Kubernetes client") + } + + return &csiAttacher{ + plugin: p, + k8s: k8s, + waitSleepTime: 1 * time.Second, + }, nil +} + +func (p *csiPlugin) NewDetacher() (volume.Detacher, error) { + k8s := p.host.GetKubeClient() + if k8s == nil { + glog.Error(log("unable to get kubernetes client from host")) + return nil, errors.New("unable to get Kubernetes client") + } + + return &csiAttacher{ + plugin: p, + k8s: k8s, + waitSleepTime: 1 * time.Second, + }, nil +} + +func (p *csiPlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) { + m := p.host.GetMounter(p.GetPluginName()) + return mount.GetMountRefs(m, deviceMountPath) +} + +func getCSISourceFromSpec(spec *volume.Spec) (*api.CSIPersistentVolumeSource, error) { + if spec.PersistentVolume != nil && + spec.PersistentVolume.Spec.CSI != nil { + return spec.PersistentVolume.Spec.CSI, nil + } + + return nil, fmt.Errorf("CSIPersistentVolumeSource not defined in spec") +} + +// log prepends log string with `kubernetes.io/csi` +func log(msg string, parts ...interface{}) string { + return fmt.Sprintf(fmt.Sprintf("%s: %s", csiPluginName, msg), parts...) +} + +// isDriverNameValid validates the driverName using CSI spec +func isDriverNameValid(name string) bool { + if len(name) == 0 || len(name) > 63 { + return false + } + return driverNameRexp.MatchString(name) +} diff --git a/pkg/volume/csi/csi_plugin_test.go b/pkg/volume/csi/csi_plugin_test.go new file mode 100644 index 00000000000..a2dd7035956 --- /dev/null +++ b/pkg/volume/csi/csi_plugin_test.go @@ -0,0 +1,310 @@ +/* +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 csi + +import ( + "fmt" + "os" + "path" + "testing" + + api "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + meta "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + fakeclient "k8s.io/client-go/kubernetes/fake" + utiltesting "k8s.io/client-go/util/testing" + "k8s.io/kubernetes/pkg/volume" + volumetest "k8s.io/kubernetes/pkg/volume/testing" +) + +// create a plugin mgr to load plugins and setup a fake client +func newTestPlugin(t *testing.T) (*csiPlugin, string) { + tmpDir, err := utiltesting.MkTmpdir("csi-test") + if err != nil { + t.Fatalf("can't create temp dir: %v", err) + } + + fakeClient := fakeclient.NewSimpleClientset() + host := volumetest.NewFakeVolumeHost( + tmpDir, + fakeClient, + nil, + ) + plugMgr := &volume.VolumePluginMgr{} + plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, host) + + plug, err := plugMgr.FindPluginByName(csiPluginName) + if err != nil { + t.Fatalf("can't find plugin %v", csiPluginName) + } + + csiPlug, ok := plug.(*csiPlugin) + if !ok { + t.Fatalf("cannot assert plugin to be type csiPlugin") + } + + return csiPlug, tmpDir +} + +func makeTestPV(name string, sizeGig int, driverName, volID string) *api.PersistentVolume { + return &api.PersistentVolume{ + ObjectMeta: meta.ObjectMeta{ + Name: name, + Namespace: testns, + }, + Spec: api.PersistentVolumeSpec{ + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse( + fmt.Sprintf("%dGi", sizeGig), + ), + }, + PersistentVolumeSource: api.PersistentVolumeSource{ + CSI: &api.CSIPersistentVolumeSource{ + Driver: driverName, + VolumeHandle: volID, + ReadOnly: false, + }, + }, + }, + } +} + +func TestPluginGetPluginName(t *testing.T) { + plug, tmpDir := newTestPlugin(t) + defer os.RemoveAll(tmpDir) + if plug.GetPluginName() != "kubernetes.io/csi" { + t.Errorf("unexpected plugin name %v", plug.GetPluginName()) + } +} + +func TestPluginGetVolumeName(t *testing.T) { + plug, tmpDir := newTestPlugin(t) + defer os.RemoveAll(tmpDir) + testCases := []struct { + name string + driverName string + volName string + shouldFail bool + }{ + {"alphanum names", "testdr", "testvol", false}, + {"mixchar driver", "test.dr.cc", "testvol", false}, + {"mixchar volume", "testdr", "test-vol-name", false}, + {"mixchars all", "test-driver", "test.vol.name", false}, + } + + for _, tc := range testCases { + t.Logf("testing: %s", tc.name) + pv := makeTestPV("test-pv", 10, tc.driverName, tc.volName) + spec := volume.NewSpecFromPersistentVolume(pv, false) + name, err := plug.GetVolumeName(spec) + if tc.shouldFail && err == nil { + t.Fatal("GetVolumeName should fail, but got err=nil") + } + if name != fmt.Sprintf("%s%s%s", tc.driverName, volNameSep, tc.volName) { + t.Errorf("unexpected volume name %s", name) + } + } +} + +func TestPluginCanSupport(t *testing.T) { + plug, tmpDir := newTestPlugin(t) + defer os.RemoveAll(tmpDir) + + pv := makeTestPV("test-pv", 10, testDriver, testVol) + spec := volume.NewSpecFromPersistentVolume(pv, false) + + if !plug.CanSupport(spec) { + t.Errorf("should support CSI spec") + } +} + +func TestPluginConstructVolumeSpec(t *testing.T) { + plug, tmpDir := newTestPlugin(t) + defer os.RemoveAll(tmpDir) + + testCases := []struct { + name string + specVolID string + data map[string]string + shouldFail bool + }{ + { + name: "valid spec name", + specVolID: "test.vol.id", + data: map[string]string{volDataKey.specVolID: "test.vol.id", volDataKey.volHandle: "test-vol0", volDataKey.driverName: "test-driver0"}, + }, + } + + for _, tc := range testCases { + t.Logf("test case: %s", tc.name) + dir := getTargetPath(testPodUID, tc.specVolID, plug.host) + + // create the data file + if tc.data != nil { + mountDir := path.Join(getTargetPath(testPodUID, tc.specVolID, plug.host), "/mount") + if err := os.MkdirAll(mountDir, 0755); err != nil && !os.IsNotExist(err) { + t.Errorf("failed to create dir [%s]: %v", mountDir, err) + } + if err := saveVolumeData(plug, testPodUID, tc.specVolID, tc.data); err != nil { + t.Fatal(err) + } + } + + // rebuild spec + spec, err := plug.ConstructVolumeSpec("test-pv", dir) + if tc.shouldFail { + if err == nil { + t.Fatal("expecting ConstructVolumeSpec to fail, but got nil error") + } + continue + } + + volHandle := spec.PersistentVolume.Spec.CSI.VolumeHandle + if volHandle != tc.data[volDataKey.volHandle] { + t.Errorf("expected volID %s, got volID %s", tc.data[volDataKey.volHandle], volHandle) + } + + if spec.Name() != tc.specVolID { + t.Errorf("Unexpected spec name %s", spec.Name()) + } + } +} + +func TestPluginNewMounter(t *testing.T) { + plug, tmpDir := newTestPlugin(t) + defer os.RemoveAll(tmpDir) + + pv := makeTestPV("test-pv", 10, testDriver, testVol) + mounter, err := plug.NewMounter( + volume.NewSpecFromPersistentVolume(pv, pv.Spec.PersistentVolumeSource.CSI.ReadOnly), + &api.Pod{ObjectMeta: meta.ObjectMeta{UID: testPodUID, Namespace: testns}}, + volume.VolumeOptions{}, + ) + if err != nil { + t.Fatalf("Failed to make a new Mounter: %v", err) + } + + if mounter == nil { + t.Fatal("failed to create CSI mounter") + } + csiMounter := mounter.(*csiMountMgr) + + // validate mounter fields + if csiMounter.driverName != testDriver { + t.Error("mounter driver name not set") + } + if csiMounter.volumeID != testVol { + t.Error("mounter volume id not set") + } + if csiMounter.pod == nil { + t.Error("mounter pod not set") + } + if csiMounter.podUID == types.UID("") { + t.Error("mounter podUID mot set") + } +} + +func TestPluginNewUnmounter(t *testing.T) { + plug, tmpDir := newTestPlugin(t) + defer os.RemoveAll(tmpDir) + + pv := makeTestPV("test-pv", 10, testDriver, testVol) + + unmounter, err := plug.NewUnmounter(pv.ObjectMeta.Name, testPodUID) + csiUnmounter := unmounter.(*csiMountMgr) + + if err != nil { + t.Fatalf("Failed to make a new Unmounter: %v", err) + } + + if csiUnmounter == nil { + t.Fatal("failed to create CSI Unmounter") + } + + if csiUnmounter.podUID != testPodUID { + t.Error("podUID not set") + } + +} + +func TestValidateDriverName(t *testing.T) { + testCases := []struct { + name string + driverName string + valid bool + }{ + + {"ok no punctuations", "comgooglestoragecsigcepd", true}, + {"ok dot only", "io.kubernetes.storage.csi.flex", true}, + {"ok dash only", "io-kubernetes-storage-csi-flex", true}, + {"ok underscore only", "io_kubernetes_storage_csi_flex", true}, + {"ok dot underscores", "io.kubernetes.storage_csi.flex", true}, + {"ok dot dash underscores", "io.kubernetes-storage.csi_flex", true}, + + {"invalid length 0", "", false}, + {"invalid length > 63", "comgooglestoragecsigcepdcomgooglestoragecsigcepdcomgooglestoragecsigcepdcomgooglestoragecsigcepd", false}, + {"invalid start char", "_comgooglestoragecsigcepd", false}, + {"invalid end char", "comgooglestoragecsigcepd/", false}, + {"invalid separators", "com/google/storage/csi~gcepd", false}, + } + + for _, tc := range testCases { + t.Logf("test case: %v", tc.name) + drValid := isDriverNameValid(tc.driverName) + if tc.valid != drValid { + t.Errorf("expecting driverName %s as valid=%t, but got valid=%t", tc.driverName, tc.valid, drValid) + } + } +} + +func TestPluginNewAttacher(t *testing.T) { + plug, tmpDir := newTestPlugin(t) + defer os.RemoveAll(tmpDir) + + attacher, err := plug.NewAttacher() + if err != nil { + t.Fatalf("failed to create new attacher: %v", err) + } + + csiAttacher := attacher.(*csiAttacher) + if csiAttacher.plugin == nil { + t.Error("plugin not set for attacher") + } + if csiAttacher.k8s == nil { + t.Error("Kubernetes client not set for attacher") + } +} + +func TestPluginNewDetacher(t *testing.T) { + plug, tmpDir := newTestPlugin(t) + defer os.RemoveAll(tmpDir) + + detacher, err := plug.NewDetacher() + if err != nil { + t.Fatalf("failed to create new detacher: %v", err) + } + + csiDetacher := detacher.(*csiAttacher) + if csiDetacher.plugin == nil { + t.Error("plugin not set for detacher") + } + if csiDetacher.k8s == nil { + t.Error("Kubernetes client not set for detacher") + } +} diff --git a/pkg/volume/csi/fake/BUILD b/pkg/volume/csi/fake/BUILD new file mode 100644 index 00000000000..49a9c80ccc0 --- /dev/null +++ b/pkg/volume/csi/fake/BUILD @@ -0,0 +1,27 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["fake_client.go"], + importpath = "k8s.io/kubernetes/pkg/volume/csi/fake", + visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/container-storage-interface/spec/lib/go/csi:go_default_library", + "//vendor/golang.org/x/net/context:go_default_library", + "//vendor/google.golang.org/grpc:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/volume/csi/fake/fake_client.go b/pkg/volume/csi/fake/fake_client.go new file mode 100644 index 00000000000..d96ed620db0 --- /dev/null +++ b/pkg/volume/csi/fake/fake_client.go @@ -0,0 +1,230 @@ +/* +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 fake + +import ( + "context" + "errors" + "strings" + + "google.golang.org/grpc" + + csipb "github.com/container-storage-interface/spec/lib/go/csi" + grpctx "golang.org/x/net/context" +) + +// IdentityClient is a CSI identity client used for testing +type IdentityClient struct { + nextErr error +} + +// NewIdentityClient returns a new IdentityClient +func NewIdentityClient() *IdentityClient { + return &IdentityClient{} +} + +// SetNextError injects expected error +func (f *IdentityClient) SetNextError(err error) { + f.nextErr = err +} + +// GetSupportedVersions returns supported version +func (f *IdentityClient) GetSupportedVersions(ctx grpctx.Context, req *csipb.GetSupportedVersionsRequest, opts ...grpc.CallOption) (*csipb.GetSupportedVersionsResponse, error) { + // short circuit with an error + if f.nextErr != nil { + return nil, f.nextErr + } + + rsp := &csipb.GetSupportedVersionsResponse{ + SupportedVersions: []*csipb.Version{ + {Major: 0, Minor: 0, Patch: 1}, + {Major: 0, Minor: 1, Patch: 0}, + {Major: 1, Minor: 0, Patch: 0}, + {Major: 1, Minor: 0, Patch: 1}, + {Major: 1, Minor: 1, Patch: 1}, + }, + } + return rsp, nil +} + +// GetPluginInfo returns plugin info +func (f *IdentityClient) GetPluginInfo(ctx context.Context, in *csipb.GetPluginInfoRequest, opts ...grpc.CallOption) (*csipb.GetPluginInfoResponse, error) { + return nil, nil +} + +// NodeClient returns CSI node client +type NodeClient struct { + nodePublishedVolumes map[string]string + nextErr error +} + +// NewNodeClient returns fake node client +func NewNodeClient() *NodeClient { + return &NodeClient{nodePublishedVolumes: make(map[string]string)} +} + +// SetNextError injects next expected error +func (f *NodeClient) SetNextError(err error) { + f.nextErr = err +} + +// GetNodePublishedVolumes returns node published volumes +func (f *NodeClient) GetNodePublishedVolumes() map[string]string { + return f.nodePublishedVolumes +} + +// NodePublishVolume implements CSI NodePublishVolume +func (f *NodeClient) NodePublishVolume(ctx grpctx.Context, req *csipb.NodePublishVolumeRequest, opts ...grpc.CallOption) (*csipb.NodePublishVolumeResponse, error) { + + if f.nextErr != nil { + return nil, f.nextErr + } + + if req.GetVolumeId() == "" { + return nil, errors.New("missing volume id") + } + if req.GetTargetPath() == "" { + return nil, errors.New("missing target path") + } + fsTypes := "ext4|xfs|zfs" + fsType := req.GetVolumeCapability().GetMount().GetFsType() + if !strings.Contains(fsTypes, fsType) { + return nil, errors.New("invlid fstype") + } + f.nodePublishedVolumes[req.GetVolumeId()] = req.GetTargetPath() + return &csipb.NodePublishVolumeResponse{}, nil +} + +// NodeProbe implements csi NodeProbe +func (f *NodeClient) NodeProbe(ctx context.Context, req *csipb.NodeProbeRequest, opts ...grpc.CallOption) (*csipb.NodeProbeResponse, error) { + if f.nextErr != nil { + return nil, f.nextErr + } + if req.Version == nil { + return nil, errors.New("missing version") + } + return &csipb.NodeProbeResponse{}, nil +} + +// NodeUnpublishVolume implements csi method +func (f *NodeClient) NodeUnpublishVolume(ctx context.Context, req *csipb.NodeUnpublishVolumeRequest, opts ...grpc.CallOption) (*csipb.NodeUnpublishVolumeResponse, error) { + if f.nextErr != nil { + return nil, f.nextErr + } + + if req.GetVolumeId() == "" { + return nil, errors.New("missing volume id") + } + if req.GetTargetPath() == "" { + return nil, errors.New("missing target path") + } + delete(f.nodePublishedVolumes, req.GetVolumeId()) + return &csipb.NodeUnpublishVolumeResponse{}, nil +} + +// GetNodeID implements method +func (f *NodeClient) GetNodeID(ctx context.Context, in *csipb.GetNodeIDRequest, opts ...grpc.CallOption) (*csipb.GetNodeIDResponse, error) { + return nil, nil +} + +// NodeGetCapabilities implements csi method +func (f *NodeClient) NodeGetCapabilities(ctx context.Context, in *csipb.NodeGetCapabilitiesRequest, opts ...grpc.CallOption) (*csipb.NodeGetCapabilitiesResponse, error) { + return nil, nil +} + +// ControllerClient represents a CSI Controller client +type ControllerClient struct { + nextCapabilities []*csipb.ControllerServiceCapability + nextErr error +} + +// NewControllerClient returns a ControllerClient +func NewControllerClient() *ControllerClient { + return &ControllerClient{} +} + +// SetNextError injects next expected error +func (f *ControllerClient) SetNextError(err error) { + f.nextErr = err +} + +// SetNextCapabilities injects next expected capabilities +func (f *ControllerClient) SetNextCapabilities(caps []*csipb.ControllerServiceCapability) { + f.nextCapabilities = caps +} + +// ControllerGetCapabilities implements csi method +func (f *ControllerClient) ControllerGetCapabilities(ctx context.Context, in *csipb.ControllerGetCapabilitiesRequest, opts ...grpc.CallOption) (*csipb.ControllerGetCapabilitiesResponse, error) { + if f.nextErr != nil { + return nil, f.nextErr + } + + if f.nextCapabilities == nil { + f.nextCapabilities = []*csipb.ControllerServiceCapability{ + { + Type: &csipb.ControllerServiceCapability_Rpc{ + Rpc: &csipb.ControllerServiceCapability_RPC{ + Type: csipb.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME, + }, + }, + }, + } + } + return &csipb.ControllerGetCapabilitiesResponse{ + Capabilities: f.nextCapabilities, + }, nil +} + +// CreateVolume implements csi method +func (f *ControllerClient) CreateVolume(ctx context.Context, in *csipb.CreateVolumeRequest, opts ...grpc.CallOption) (*csipb.CreateVolumeResponse, error) { + return nil, nil +} + +// DeleteVolume implements csi method +func (f *ControllerClient) DeleteVolume(ctx context.Context, in *csipb.DeleteVolumeRequest, opts ...grpc.CallOption) (*csipb.DeleteVolumeResponse, error) { + return nil, nil +} + +// ControllerPublishVolume implements csi method +func (f *ControllerClient) ControllerPublishVolume(ctx context.Context, in *csipb.ControllerPublishVolumeRequest, opts ...grpc.CallOption) (*csipb.ControllerPublishVolumeResponse, error) { + return nil, nil +} + +// ControllerUnpublishVolume implements csi method +func (f *ControllerClient) ControllerUnpublishVolume(ctx context.Context, in *csipb.ControllerUnpublishVolumeRequest, opts ...grpc.CallOption) (*csipb.ControllerUnpublishVolumeResponse, error) { + return nil, nil +} + +// ValidateVolumeCapabilities implements csi method +func (f *ControllerClient) ValidateVolumeCapabilities(ctx context.Context, in *csipb.ValidateVolumeCapabilitiesRequest, opts ...grpc.CallOption) (*csipb.ValidateVolumeCapabilitiesResponse, error) { + return nil, nil +} + +// ListVolumes implements csi method +func (f *ControllerClient) ListVolumes(ctx context.Context, in *csipb.ListVolumesRequest, opts ...grpc.CallOption) (*csipb.ListVolumesResponse, error) { + return nil, nil +} + +// GetCapacity implements csi method +func (f *ControllerClient) GetCapacity(ctx context.Context, in *csipb.GetCapacityRequest, opts ...grpc.CallOption) (*csipb.GetCapacityResponse, error) { + return nil, nil +} + +// ControllerProbe implements csi method +func (f *ControllerClient) ControllerProbe(ctx context.Context, in *csipb.ControllerProbeRequest, opts ...grpc.CallOption) (*csipb.ControllerProbeResponse, error) { + return nil, nil +} diff --git a/pkg/volume/downwardapi/BUILD b/pkg/volume/downwardapi/BUILD index d84c809751a..3b5bd7a6e06 100644 --- a/pkg/volume/downwardapi/BUILD +++ b/pkg/volume/downwardapi/BUILD @@ -26,8 +26,8 @@ go_library( go_test( name = "go_default_test", srcs = ["downwardapi_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/downwardapi", - library = ":go_default_library", deps = [ "//pkg/fieldpath:go_default_library", "//pkg/volume:go_default_library", diff --git a/pkg/volume/downwardapi/OWNERS b/pkg/volume/downwardapi/OWNERS index 72968b277da..baaff6ff460 100644 --- a/pkg/volume/downwardapi/OWNERS +++ b/pkg/volume/downwardapi/OWNERS @@ -7,13 +7,7 @@ reviewers: - ivan4th - rata - sjenning -- thockin -- smarterclayton -- brendandburns -- derekwaynecarr -- pmorie - saad-ali -- justinsb - jsafrane - rootfs - jingxu97 diff --git a/pkg/volume/downwardapi/downwardapi_test.go b/pkg/volume/downwardapi/downwardapi_test.go index 4f68da84566..89c73cb983b 100644 --- a/pkg/volume/downwardapi/downwardapi_test.go +++ b/pkg/volume/downwardapi/downwardapi_test.go @@ -63,6 +63,12 @@ func TestCanSupport(t *testing.T) { if plugin.GetPluginName() != downwardAPIPluginName { t.Errorf("Wrong name: %s", plugin.GetPluginName()) } + if !plugin.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{DownwardAPI: &v1.DownwardAPIVolumeSource{}}}}) { + t.Errorf("Expected true") + } + if plugin.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{}}}) { + t.Errorf("Expected false") + } } func TestDownwardAPI(t *testing.T) { @@ -287,7 +293,7 @@ func (test *downwardAPITest) tearDown() { if _, err := os.Stat(test.volumePath); err == nil { test.t.Errorf("TearDown() failed, volume path still exists: %s", test.volumePath) } else if !os.IsNotExist(err) { - test.t.Errorf("SetUp() failed: %v", err) + test.t.Errorf("TearDown() failed: %v", err) } os.RemoveAll(test.rootDir) } diff --git a/pkg/volume/empty_dir/BUILD b/pkg/volume/empty_dir/BUILD index 4f5a52c8958..b57aa47ffe4 100644 --- a/pkg/volume/empty_dir/BUILD +++ b/pkg/volume/empty_dir/BUILD @@ -11,16 +11,45 @@ go_library( srcs = [ "doc.go", "empty_dir.go", - "empty_dir_unsupported.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:android": [ + "empty_dir_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "empty_dir_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "empty_dir_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "empty_dir_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ "empty_dir_linux.go", ], + "@io_bazel_rules_go//go/platform:nacl": [ + "empty_dir_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "empty_dir_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "empty_dir_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "empty_dir_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "empty_dir_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "empty_dir_unsupported.go", + ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/pkg/volume/empty_dir", deps = [ - "//pkg/api/v1/helper:go_default_library", + "//pkg/apis/core/v1/helper:go_default_library", "//pkg/util/mount:go_default_library", "//pkg/util/strings:go_default_library", "//pkg/volume:go_default_library", @@ -31,7 +60,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//vendor/golang.org/x/sys/unix:go_default_library", ], "//conditions:default": [], @@ -41,15 +70,15 @@ go_library( go_test( name = "go_default_test", srcs = select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "empty_dir_test.go", ], "//conditions:default": [], }), + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/empty_dir", - library = ":go_default_library", deps = select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//pkg/util/mount:go_default_library", "//pkg/volume:go_default_library", "//pkg/volume/testing:go_default_library", diff --git a/pkg/volume/empty_dir/OWNERS b/pkg/volume/empty_dir/OWNERS index 72968b277da..baaff6ff460 100644 --- a/pkg/volume/empty_dir/OWNERS +++ b/pkg/volume/empty_dir/OWNERS @@ -7,13 +7,7 @@ reviewers: - ivan4th - rata - sjenning -- thockin -- smarterclayton -- brendandburns -- derekwaynecarr -- pmorie - saad-ali -- justinsb - jsafrane - rootfs - jingxu97 diff --git a/pkg/volume/empty_dir/empty_dir.go b/pkg/volume/empty_dir/empty_dir.go index 68cd56727b8..ef7807601ee 100644 --- a/pkg/volume/empty_dir/empty_dir.go +++ b/pkg/volume/empty_dir/empty_dir.go @@ -26,7 +26,7 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" "k8s.io/kubernetes/pkg/util/mount" stringsutil "k8s.io/kubernetes/pkg/util/strings" "k8s.io/kubernetes/pkg/volume" @@ -227,7 +227,7 @@ func (ed *emptyDir) SetUpAt(dir string, fsGroup *int64) error { err = ed.setupDir(dir) case v1.StorageMediumMemory: err = ed.setupTmpfs(dir) - case v1.StorageMediumHugepages: + case v1.StorageMediumHugePages: err = ed.setupHugepages(dir) default: err = fmt.Errorf("unknown storage medium %q", ed.medium) @@ -392,7 +392,7 @@ func (ed *emptyDir) TearDownAt(dir string) error { ed.medium = v1.StorageMediumMemory return ed.teardownTmpfsOrHugetlbfs(dir) } else if medium == mediumHugepages { - ed.medium = v1.StorageMediumHugepages + ed.medium = v1.StorageMediumHugePages return ed.teardownTmpfsOrHugetlbfs(dir) } } diff --git a/pkg/volume/empty_dir/empty_dir_test.go b/pkg/volume/empty_dir/empty_dir_test.go index 003aea08492..cd4e9b618f0 100644 --- a/pkg/volume/empty_dir/empty_dir_test.go +++ b/pkg/volume/empty_dir/empty_dir_test.go @@ -83,7 +83,7 @@ func TestPluginEmptyRootContext(t *testing.T) { func TestPluginHugetlbfs(t *testing.T) { doTestPlugin(t, pluginTestConfig{ - medium: v1.StorageMediumHugepages, + medium: v1.StorageMediumHugePages, expectedSetupMounts: 1, expectedTeardownMounts: 0, shouldBeMountedBeforeTeardown: true, @@ -216,7 +216,7 @@ func doTestPlugin(t *testing.T, config pluginTestConfig) { if _, err := os.Stat(volPath); err == nil { t.Errorf("TearDown() failed, volume path still exists: %s", volPath) } else if !os.IsNotExist(err) { - t.Errorf("SetUp() failed: %v", err) + t.Errorf("TearDown() failed: %v", err) } // Check the number of physicalMounter calls during tardown diff --git a/pkg/volume/fc/BUILD b/pkg/volume/fc/BUILD index 4c470b71d94..2085bc4f2b3 100644 --- a/pkg/volume/fc/BUILD +++ b/pkg/volume/fc/BUILD @@ -17,6 +17,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/volume/fc", deps = [ + "//pkg/features:go_default_library", "//pkg/util/mount:go_default_library", "//pkg/util/strings:go_default_library", "//pkg/volume:go_default_library", @@ -24,7 +25,9 @@ go_library( "//pkg/volume/util/volumehelper:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", ], ) @@ -34,12 +37,13 @@ go_test( "fc_test.go", "fc_util_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/fc", - library = ":go_default_library", deps = [ "//pkg/util/mount:go_default_library", "//pkg/volume:go_default_library", "//pkg/volume/testing:go_default_library", + "//pkg/volume/util:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", diff --git a/pkg/volume/fc/OWNERS b/pkg/volume/fc/OWNERS index 0f6062cc171..5c95f6dfc50 100644 --- a/pkg/volume/fc/OWNERS +++ b/pkg/volume/fc/OWNERS @@ -2,13 +2,7 @@ approvers: - rootfs - saad-ali reviewers: -- thockin -- smarterclayton -- brendandburns -- derekwaynecarr -- pmorie - saad-ali -- justinsb - jsafrane - rootfs - jingxu97 diff --git a/pkg/volume/fc/attacher.go b/pkg/volume/fc/attacher.go index 4a8121faee8..ff034c58e69 100644 --- a/pkg/volume/fc/attacher.go +++ b/pkg/volume/fc/attacher.go @@ -26,6 +26,8 @@ import ( "github.com/golang/glog" "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/util/mount" "k8s.io/kubernetes/pkg/volume" volumeutil "k8s.io/kubernetes/pkg/volume/util" @@ -135,7 +137,7 @@ func (plugin *fcPlugin) NewDetacher() (volume.Detacher, error) { }, nil } -func (detacher *fcDetacher) Detach(deviceMountPath string, nodeName types.NodeName) error { +func (detacher *fcDetacher) Detach(volumeName string, nodeName types.NodeName) error { return nil } @@ -176,20 +178,37 @@ func volumeSpecToMounter(spec *volume.Spec, host volume.VolumeHost) (*fcDiskMoun } else { return nil, fmt.Errorf("fc: no fc disk information found. failed to make a new mounter") } - - return &fcDiskMounter{ - fcDisk: &fcDisk{ - plugin: &fcPlugin{ - host: host, - }, - wwns: fc.TargetWWNs, - lun: lun, - wwids: wwids, - io: &osIOHandler{}, + fcDisk := &fcDisk{ + plugin: &fcPlugin{ + host: host, }, - fsType: fc.FSType, - readOnly: readOnly, - mounter: volumehelper.NewSafeFormatAndMountFromHost(fcPluginName, host), + wwns: fc.TargetWWNs, + lun: lun, + wwids: wwids, + io: &osIOHandler{}, + } + // TODO: remove feature gate check after no longer needed + if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + volumeMode, err := volumehelper.GetVolumeMode(spec) + if err != nil { + return nil, err + } + glog.V(5).Infof("fc: volumeSpecToMounter volumeMode %s", volumeMode) + return &fcDiskMounter{ + fcDisk: fcDisk, + fsType: fc.FSType, + volumeMode: volumeMode, + readOnly: readOnly, + mounter: volumehelper.NewSafeFormatAndMountFromHost(fcPluginName, host), + deviceUtil: volumeutil.NewDeviceHandler(volumeutil.NewIOHandler()), + }, nil + } + return &fcDiskMounter{ + fcDisk: fcDisk, + fsType: fc.FSType, + readOnly: readOnly, + mounter: volumehelper.NewSafeFormatAndMountFromHost(fcPluginName, host), + deviceUtil: volumeutil.NewDeviceHandler(volumeutil.NewIOHandler()), }, nil } @@ -198,6 +217,7 @@ func volumeSpecToUnmounter(mounter mount.Interface) *fcDiskUnmounter { fcDisk: &fcDisk{ io: &osIOHandler{}, }, - mounter: mounter, + mounter: mounter, + deviceUtil: volumeutil.NewDeviceHandler(volumeutil.NewIOHandler()), } } diff --git a/pkg/volume/fc/disk_manager.go b/pkg/volume/fc/disk_manager.go index 8562d219b1a..13cf923a923 100644 --- a/pkg/volume/fc/disk_manager.go +++ b/pkg/volume/fc/disk_manager.go @@ -27,10 +27,13 @@ import ( // Abstract interface to disk operations. type diskManager interface { MakeGlobalPDName(disk fcDisk) string + MakeGlobalVDPDName(disk fcDisk) string // Attaches the disk to the kubelet's host machine. AttachDisk(b fcDiskMounter) (string, error) // Detaches the disk from the kubelet's host machine. - DetachDisk(disk fcDiskUnmounter, devName string) error + DetachDisk(disk fcDiskUnmounter, devicePath string) error + // Detaches the block disk from the kubelet's host machine. + DetachBlockFCDisk(disk fcDiskUnmapper, mntPath, devicePath string) error } // utility to mount a disk based filesystem diff --git a/pkg/volume/fc/fc.go b/pkg/volume/fc/fc.go index a363ffb85c5..8772ce91558 100644 --- a/pkg/volume/fc/fc.go +++ b/pkg/volume/fc/fc.go @@ -18,16 +18,21 @@ package fc import ( "fmt" + "os" "strconv" "strings" "github.com/golang/glog" "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" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/util/mount" utilstrings "k8s.io/kubernetes/pkg/util/strings" "k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume/util" + "k8s.io/kubernetes/pkg/volume/util/volumehelper" ) // This is the primary entrypoint for volume plugins. @@ -41,6 +46,7 @@ type fcPlugin struct { var _ volume.VolumePlugin = &fcPlugin{} var _ volume.PersistentVolumePlugin = &fcPlugin{} +var _ volume.BlockVolumePlugin = &fcPlugin{} const ( fcPluginName = "kubernetes.io/fc" @@ -115,31 +121,80 @@ func (plugin *fcPlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, return nil, err } - var lun string - var wwids []string - if fc.Lun != nil && len(fc.TargetWWNs) != 0 { - lun = strconv.Itoa(int(*fc.Lun)) - } else if len(fc.WWIDs) != 0 { - for _, wwid := range fc.WWIDs { - wwids = append(wwids, strings.Replace(wwid, " ", "_", -1)) - } - } else { + wwns, lun, wwids, err := getWwnsLunWwids(fc) + if err != nil { return nil, fmt.Errorf("fc: no fc disk information found. failed to make a new mounter") } - + fcDisk := &fcDisk{ + podUID: podUID, + volName: spec.Name(), + wwns: wwns, + lun: lun, + wwids: wwids, + manager: manager, + io: &osIOHandler{}, + plugin: plugin, + } + // TODO: remove feature gate check after no longer needed + if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + volumeMode, err := volumehelper.GetVolumeMode(spec) + if err != nil { + return nil, err + } + glog.V(5).Infof("fc: newMounterInternal volumeMode %s", volumeMode) + return &fcDiskMounter{ + fcDisk: fcDisk, + fsType: fc.FSType, + volumeMode: volumeMode, + readOnly: readOnly, + mounter: &mount.SafeFormatAndMount{Interface: mounter, Exec: exec}, + deviceUtil: util.NewDeviceHandler(util.NewIOHandler()), + }, nil + } return &fcDiskMounter{ + fcDisk: fcDisk, + fsType: fc.FSType, + readOnly: readOnly, + mounter: &mount.SafeFormatAndMount{Interface: mounter, Exec: exec}, + deviceUtil: util.NewDeviceHandler(util.NewIOHandler()), + }, nil + +} + +func (plugin *fcPlugin) NewBlockVolumeMapper(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.BlockVolumeMapper, error) { + // If this called via GenerateUnmapDeviceFunc(), pod is nil. + // Pass empty string as dummy uid since uid isn't used in the case. + var uid types.UID + if pod != nil { + uid = pod.UID + } + return plugin.newBlockVolumeMapperInternal(spec, uid, &FCUtil{}, plugin.host.GetMounter(plugin.GetPluginName()), plugin.host.GetExec(plugin.GetPluginName())) +} + +func (plugin *fcPlugin) newBlockVolumeMapperInternal(spec *volume.Spec, podUID types.UID, manager diskManager, mounter mount.Interface, exec mount.Exec) (volume.BlockVolumeMapper, error) { + fc, readOnly, err := getVolumeSource(spec) + if err != nil { + return nil, err + } + + wwns, lun, wwids, err := getWwnsLunWwids(fc) + if err != nil { + return nil, fmt.Errorf("fc: no fc disk information found. failed to make a new mapper") + } + + return &fcDiskMapper{ fcDisk: &fcDisk{ podUID: podUID, volName: spec.Name(), - wwns: fc.TargetWWNs, + wwns: wwns, lun: lun, wwids: wwids, manager: manager, io: &osIOHandler{}, plugin: plugin}, - fsType: fc.FSType, - readOnly: readOnly, - mounter: &mount.SafeFormatAndMount{Interface: mounter, Exec: exec}, + readOnly: readOnly, + mounter: &mount.SafeFormatAndMount{Interface: mounter, Exec: exec}, + deviceUtil: util.NewDeviceHandler(util.NewIOHandler()), }, nil } @@ -157,7 +212,25 @@ func (plugin *fcPlugin) newUnmounterInternal(volName string, podUID types.UID, m plugin: plugin, io: &osIOHandler{}, }, - mounter: mounter, + mounter: mounter, + deviceUtil: util.NewDeviceHandler(util.NewIOHandler()), + }, nil +} + +func (plugin *fcPlugin) NewBlockVolumeUnmapper(volName string, podUID types.UID) (volume.BlockVolumeUnmapper, error) { + return plugin.newUnmapperInternal(volName, podUID, &FCUtil{}) +} + +func (plugin *fcPlugin) newUnmapperInternal(volName string, podUID types.UID, manager diskManager) (volume.BlockVolumeUnmapper, error) { + return &fcDiskUnmapper{ + fcDisk: &fcDisk{ + podUID: podUID, + volName: volName, + manager: manager, + plugin: plugin, + io: &osIOHandler{}, + }, + deviceUtil: util.NewDeviceHandler(util.NewIOHandler()), }, nil } @@ -171,6 +244,55 @@ func (plugin *fcPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volu return volume.NewSpecFromVolume(fcVolume), nil } +// ConstructBlockVolumeSpec creates a new volume.Spec with following steps. +// - Searchs a file whose name is {pod uuid} under volume plugin directory. +// - If a file is found, then retreives volumePluginDependentPath from globalMapPathUUID. +// - Once volumePluginDependentPath is obtained, store volume information to VolumeSource +// examples: +// mapPath: pods/{podUid}}/{DefaultKubeletVolumeDevicesDirName}/{escapeQualifiedPluginName}/{volumeName} +// globalMapPathUUID : plugins/kubernetes.io/{PluginName}/{DefaultKubeletVolumeDevicesDirName}/{volumePluginDependentPath}/{pod uuid} +func (plugin *fcPlugin) ConstructBlockVolumeSpec(podUID types.UID, volumeName, mapPath string) (*volume.Spec, error) { + pluginDir := plugin.host.GetVolumeDevicePluginDir(fcPluginName) + blkutil := util.NewBlockVolumePathHandler() + globalMapPathUUID, err := blkutil.FindGlobalMapPathUUIDFromPod(pluginDir, mapPath, podUID) + if err != nil { + return nil, err + } + glog.V(5).Infof("globalMapPathUUID: %v, err: %v", globalMapPathUUID, err) + + // Retreive volumePluginDependentPath from globalMapPathUUID + // globalMapPathUUID examples: + // wwn+lun: plugins/kubernetes.io/fc/volumeDevices/50060e801049cfd1-lun-0/{pod uuid} + // wwid: plugins/kubernetes.io/fc/volumeDevices/3600508b400105e210000900000490000/{pod uuid} + arr := strings.Split(globalMapPathUUID, "/") + if len(arr) < 2 { + return nil, fmt.Errorf("Fail to retreive volume plugin information from globalMapPathUUID: %v", globalMapPathUUID) + } + l := len(arr) - 2 + volumeInfo := arr[l] + + // Create volume from wwn+lun or wwid + var fcPV *v1.PersistentVolume + if strings.Contains(volumeInfo, "-lun-") { + wwnLun := strings.Split(volumeInfo, "-lun-") + lun, err := strconv.Atoi(wwnLun[1]) + if err != nil { + return nil, err + } + lun32 := int32(lun) + fcPV = createPersistentVolumeFromFCVolumeSource(volumeName, + v1.FCVolumeSource{TargetWWNs: []string{wwnLun[0]}, Lun: &lun32}) + glog.V(5).Infof("ConstructBlockVolumeSpec: TargetWWNs: %v, Lun: %v", + fcPV.Spec.PersistentVolumeSource.FC.TargetWWNs, + fcPV.Spec.PersistentVolumeSource.FC.Lun) + } else { + fcPV = createPersistentVolumeFromFCVolumeSource(volumeName, + v1.FCVolumeSource{WWIDs: []string{volumeInfo}}) + glog.V(5).Infof("ConstructBlockVolumeSpec: WWIDs: %v", fcPV.Spec.PersistentVolumeSource.FC.WWIDs) + } + return volume.NewSpecFromPersistentVolume(fcPV, false), nil +} + type fcDisk struct { volName string podUID types.UID @@ -192,11 +314,27 @@ func (fc *fcDisk) GetPath() string { return fc.plugin.host.GetPodVolumeDir(fc.podUID, utilstrings.EscapeQualifiedNameForDisk(name), fc.volName) } +func (fc *fcDisk) fcGlobalMapPath(spec *volume.Spec) (string, error) { + mounter, err := volumeSpecToMounter(spec, fc.plugin.host) + if err != nil { + glog.Warningf("failed to get fc mounter: %v", err) + return "", err + } + return fc.manager.MakeGlobalVDPDName(*mounter.fcDisk), nil +} + +func (fc *fcDisk) fcPodDeviceMapPath() (string, string) { + name := fcPluginName + return fc.plugin.host.GetPodVolumeDeviceDir(fc.podUID, utilstrings.EscapeQualifiedNameForDisk(name)), fc.volName +} + type fcDiskMounter struct { *fcDisk - readOnly bool - fsType string - mounter *mount.SafeFormatAndMount + readOnly bool + fsType string + volumeMode v1.PersistentVolumeMode + mounter *mount.SafeFormatAndMount + deviceUtil util.DeviceUtil } var _ volume.Mounter = &fcDiskMounter{} @@ -231,7 +369,8 @@ func (b *fcDiskMounter) SetUpAt(dir string, fsGroup *int64) error { type fcDiskUnmounter struct { *fcDisk - mounter mount.Interface + mounter mount.Interface + deviceUtil util.DeviceUtil } var _ volume.Unmounter = &fcDiskUnmounter{} @@ -246,7 +385,57 @@ func (c *fcDiskUnmounter) TearDownAt(dir string) error { return util.UnmountPath(dir, c.mounter) } +// Block Volumes Support +type fcDiskMapper struct { + *fcDisk + readOnly bool + mounter mount.Interface + deviceUtil util.DeviceUtil +} + +var _ volume.BlockVolumeMapper = &fcDiskMapper{} + +func (b *fcDiskMapper) SetUpDevice() (string, error) { + return "", nil +} + +type fcDiskUnmapper struct { + *fcDisk + deviceUtil util.DeviceUtil +} + +var _ volume.BlockVolumeUnmapper = &fcDiskUnmapper{} + +func (c *fcDiskUnmapper) TearDownDevice(mapPath, devicePath string) error { + err := c.manager.DetachBlockFCDisk(*c, mapPath, devicePath) + if err != nil { + return fmt.Errorf("fc: failed to detach disk: %s\nError: %v", mapPath, err) + } + glog.V(4).Infof("fc: %q is unmounted, deleting the directory", mapPath) + err = os.RemoveAll(mapPath) + if err != nil { + return fmt.Errorf("fc: failed to delete the directory: %s\nError: %v", mapPath, err) + } + glog.V(4).Infof("fc: successfully detached disk: %s", mapPath) + return nil +} + +// GetGlobalMapPath returns global map path and error +// path: plugins/kubernetes.io/{PluginName}/volumeDevices/{WWID}/{podUid} +func (fc *fcDisk) GetGlobalMapPath(spec *volume.Spec) (string, error) { + return fc.fcGlobalMapPath(spec) +} + +// GetPodDeviceMapPath returns pod device map path and volume name +// path: pods/{podUid}/volumeDevices/kubernetes.io~fc +// volumeName: pv0001 +func (fc *fcDisk) GetPodDeviceMapPath() (string, string) { + return fc.fcPodDeviceMapPath() +} + func getVolumeSource(spec *volume.Spec) (*v1.FCVolumeSource, bool, error) { + // fc volumes used directly in a pod have a ReadOnly flag set by the pod author. + // fc volumes used as a PersistentVolume gets the ReadOnly flag indirectly through the persistent-claim volume used to mount the PV if spec.Volume != nil && spec.Volume.FC != nil { return spec.Volume.FC, spec.Volume.FC.ReadOnly, nil } else if spec.PersistentVolume != nil && @@ -256,3 +445,34 @@ func getVolumeSource(spec *volume.Spec) (*v1.FCVolumeSource, bool, error) { return nil, false, fmt.Errorf("Spec does not reference a FibreChannel volume type") } + +func createPersistentVolumeFromFCVolumeSource(volumeName string, fc v1.FCVolumeSource) *v1.PersistentVolume { + block := v1.PersistentVolumeBlock + return &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: volumeName, + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + FC: &fc, + }, + VolumeMode: &block, + }, + } +} + +func getWwnsLunWwids(fc *v1.FCVolumeSource) ([]string, string, []string, error) { + var lun string + var wwids []string + if fc.Lun != nil && len(fc.TargetWWNs) != 0 { + lun = strconv.Itoa(int(*fc.Lun)) + return fc.TargetWWNs, lun, wwids, nil + } + if len(fc.WWIDs) != 0 { + for _, wwid := range fc.WWIDs { + wwids = append(wwids, strings.Replace(wwid, " ", "_", -1)) + } + return fc.TargetWWNs, lun, wwids, nil + } + return nil, "", nil, fmt.Errorf("fc: no fc disk information found") +} diff --git a/pkg/volume/fc/fc_test.go b/pkg/volume/fc/fc_test.go index b8006942817..42a530bc4a5 100644 --- a/pkg/volume/fc/fc_test.go +++ b/pkg/volume/fc/fc_test.go @@ -67,20 +67,11 @@ func TestGetAccessModes(t *testing.T) { if err != nil { t.Errorf("Can't find the plugin by name") } - if !contains(plug.GetAccessModes(), v1.ReadWriteOnce) || !contains(plug.GetAccessModes(), v1.ReadOnlyMany) { + if !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteOnce) || !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadOnlyMany) { t.Errorf("Expected two AccessModeTypes: %s and %s", v1.ReadWriteOnce, v1.ReadOnlyMany) } } -func contains(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool { - for _, m := range modes { - if m == mode { - return true - } - } - return false -} - type fakeDiskManager struct { tmpDir string attachCalled bool @@ -100,6 +91,11 @@ func (fake *fakeDiskManager) Cleanup() { func (fake *fakeDiskManager) MakeGlobalPDName(disk fcDisk) string { return fake.tmpDir } + +func (fake *fakeDiskManager) MakeGlobalVDPDName(disk fcDisk) string { + return fake.tmpDir +} + func (fake *fakeDiskManager) AttachDisk(b fcDiskMounter) (string, error) { globalPath := b.manager.MakeGlobalPDName(*b.fcDisk) err := os.MkdirAll(globalPath, 0750) @@ -124,6 +120,15 @@ func (fake *fakeDiskManager) DetachDisk(c fcDiskUnmounter, mntPath string) error return nil } +func (fake *fakeDiskManager) DetachBlockFCDisk(c fcDiskUnmapper, mapPath, devicePath string) error { + err := os.RemoveAll(mapPath) + if err != nil { + return err + } + fake.detachCalled = true + return nil +} + func doTestPlugin(t *testing.T, spec *volume.Spec) { tmpDir, err := utiltesting.MkTmpdir("fc_test") if err != nil { @@ -190,7 +195,7 @@ func doTestPlugin(t *testing.T, spec *volume.Spec) { if _, err := os.Stat(path); err == nil { t.Errorf("TearDown() failed, volume path still exists: %s", path) } else if !os.IsNotExist(err) { - t.Errorf("SetUp() failed: %v", err) + t.Errorf("TearDown() failed: %v", err) } } @@ -370,3 +375,40 @@ func TestPersistentClaimReadOnlyFlag(t *testing.T) { t.Errorf("Expected true for mounter.IsReadOnly") } } + +func Test_getWwnsLun(t *testing.T) { + num := int32(0) + fc := &v1.FCVolumeSource{ + TargetWWNs: []string{"500a0981891b8dc5"}, + FSType: "ext4", + Lun: &num, + } + wwn, lun, _, err := getWwnsLunWwids(fc) + // if no wwn and lun, exit + if (len(wwn) == 0 && lun != "0") || err != nil { + t.Errorf("no fc disk found") + } +} + +func Test_getWwids(t *testing.T) { + fc := &v1.FCVolumeSource{ + FSType: "ext4", + WWIDs: []string{"3600508b400105e210000900000490000"}, + } + _, _, wwid, err := getWwnsLunWwids(fc) + // if no wwn and lun, exit + if len(wwid) == 0 || err != nil { + t.Errorf("no fc disk found") + } +} + +func Test_getWwnsLunWwidsError(t *testing.T) { + fc := &v1.FCVolumeSource{ + FSType: "ext4", + } + wwn, lun, wwid, err := getWwnsLunWwids(fc) + // expected no wwn and lun and wwid + if (len(wwn) != 0 && lun != "" && len(wwid) != 0) || err == nil { + t.Errorf("unexpected fc disk found") + } +} diff --git a/pkg/volume/fc/fc_util.go b/pkg/volume/fc/fc_util.go index 050785c4221..050989d353f 100644 --- a/pkg/volume/fc/fc_util.go +++ b/pkg/volume/fc/fc_util.go @@ -25,7 +25,11 @@ import ( "strings" "github.com/golang/glog" + "k8s.io/api/core/v1" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/volume" + volumeutil "k8s.io/kubernetes/pkg/volume/util" ) type ioHandler interface { @@ -37,6 +41,11 @@ type ioHandler interface { type osIOHandler struct{} +const ( + byPath = "/dev/disk/by-path/" + byID = "/dev/disk/by-id/" +) + func (handler *osIOHandler) ReadDir(dirname string) ([]os.FileInfo, error) { return ioutil.ReadDir(dirname) } @@ -50,37 +59,17 @@ func (handler *osIOHandler) WriteFile(filename string, data []byte, perm os.File return ioutil.WriteFile(filename, data, perm) } -// given a disk path like /dev/sdx, find the devicemapper parent -// TODO #23192 Convert this code to use the generic code in ../util -// which is used by the iSCSI implementation -func findMultipathDeviceMapper(disk string, io ioHandler) string { - sys_path := "/sys/block/" - if dirs, err := io.ReadDir(sys_path); err == nil { - for _, f := range dirs { - name := f.Name() - if strings.HasPrefix(name, "dm-") { - if _, err1 := io.Lstat(sys_path + name + "/slaves/" + disk); err1 == nil { - return "/dev/" + name - } - } - } - } - return "" -} - // given a wwn and lun, find the device and associated devicemapper parent -func findDisk(wwn, lun string, io ioHandler) (string, string) { +func findDisk(wwn, lun string, io ioHandler, deviceUtil volumeutil.DeviceUtil) (string, string) { fc_path := "-fc-0x" + wwn + "-lun-" + lun - dev_path := "/dev/disk/by-path/" + dev_path := byPath if dirs, err := io.ReadDir(dev_path); err == nil { for _, f := range dirs { name := f.Name() if strings.Contains(name, fc_path) { if disk, err1 := io.EvalSymlinks(dev_path + name); err1 == nil { - arr := strings.Split(disk, "/") - l := len(arr) - 1 - dev := arr[l] - dm := findMultipathDeviceMapper(dev, io) + dm := deviceUtil.FindMultipathDeviceForDevice(disk) + glog.Infof("fc: find disk: %v, dm: %v", disk, dm) return disk, dm } } @@ -90,7 +79,7 @@ func findDisk(wwn, lun string, io ioHandler) (string, string) { } // given a wwid, find the device and associated devicemapper parent -func findDiskWWIDs(wwid string, io ioHandler) (string, string) { +func findDiskWWIDs(wwid string, io ioHandler, deviceUtil volumeutil.DeviceUtil) (string, string) { // Example wwid format: // 3600508b400105e210000900000490000 // @@ -101,7 +90,7 @@ func findDiskWWIDs(wwid string, io ioHandler) (string, string) { // underscore when wwid is exposed under /dev/by-id. fc_path := "scsi-" + wwid - dev_id := "/dev/disk/by-id/" + dev_id := byID if dirs, err := io.ReadDir(dev_id); err == nil { for _, f := range dirs { name := f.Name() @@ -111,10 +100,8 @@ func findDiskWWIDs(wwid string, io ioHandler) (string, string) { glog.V(2).Infof("fc: failed to find a corresponding disk from symlink[%s], error %v", dev_id+name, err) return "", "" } - arr := strings.Split(disk, "/") - l := len(arr) - 1 - dev := arr[l] - dm := findMultipathDeviceMapper(dev, io) + dm := deviceUtil.FindMultipathDeviceForDevice(disk) + glog.Infof("fc: find disk: %v, dm: %v", disk, dm) return disk, dm } } @@ -143,7 +130,7 @@ func scsiHostRescan(io ioHandler) { } } -// make a directory like /var/lib/kubelet/plugins/kubernetes.io/pod/fc/target-lun-0 +// make a directory like /var/lib/kubelet/plugins/kubernetes.io/fc/target-lun-0 func makePDNameInternal(host volume.VolumeHost, wwns []string, lun string, wwids []string) string { if len(wwns) != 0 { return path.Join(host.GetPluginDir(fcPluginName), wwns[0]+"-lun-"+lun) @@ -152,13 +139,27 @@ func makePDNameInternal(host volume.VolumeHost, wwns []string, lun string, wwids } } +// make a directory like /var/lib/kubelet/plugins/kubernetes.io/fc/volumeDevices/target-lun-0 +func makeVDPDNameInternal(host volume.VolumeHost, wwns []string, lun string, wwids []string) string { + if len(wwns) != 0 { + return path.Join(host.GetVolumeDevicePluginDir(fcPluginName), wwns[0]+"-lun-"+lun) + } else { + return path.Join(host.GetVolumeDevicePluginDir(fcPluginName), wwids[0]) + } +} + type FCUtil struct{} func (util *FCUtil) MakeGlobalPDName(fc fcDisk) string { return makePDNameInternal(fc.plugin.host, fc.wwns, fc.lun, fc.wwids) } -func searchDisk(b fcDiskMounter) (string, string) { +// Global volume device plugin dir +func (util *FCUtil) MakeGlobalVDPDName(fc fcDisk) string { + return makeVDPDNameInternal(fc.plugin.host, fc.wwns, fc.lun, fc.wwids) +} + +func searchDisk(b fcDiskMounter) (string, error) { var diskIds []string var disk string var dm string @@ -180,9 +181,9 @@ func searchDisk(b fcDiskMounter) (string, string) { for true { for _, diskId := range diskIds { if len(wwns) != 0 { - disk, dm = findDisk(diskId, lun, io) + disk, dm = findDisk(diskId, lun, io, b.deviceUtil) } else { - disk, dm = findDiskWWIDs(diskId, io) + disk, dm = findDiskWWIDs(diskId, io, b.deviceUtil) } // if multipath device is found, break if dm != "" { @@ -198,14 +199,6 @@ func searchDisk(b fcDiskMounter) (string, string) { scsiHostRescan(io) rescaned = true } - return disk, dm -} - -func (util *FCUtil) AttachDisk(b fcDiskMounter) (string, error) { - devicePath := "" - var disk, dm string - - disk, dm = searchDisk(b) // if no disk matches input wwn and lun, exit if disk == "" && dm == "" { return "", fmt.Errorf("no fc disk found") @@ -213,10 +206,26 @@ func (util *FCUtil) AttachDisk(b fcDiskMounter) (string, error) { // if multipath devicemapper device is found, use it; otherwise use raw disk if dm != "" { - devicePath = dm - } else { - devicePath = disk + return dm, nil } + return disk, nil +} + +func (util *FCUtil) AttachDisk(b fcDiskMounter) (string, error) { + devicePath, err := searchDisk(b) + if err != nil { + return "", err + } + // TODO: remove feature gate check after no longer needed + if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + // If the volumeMode is 'Block', plugin don't have to format the volume. + // The globalPDPath will be created by operationexecutor. Just return devicePath here. + glog.V(5).Infof("fc: AttachDisk volumeMode: %s, devicePath: %s", b.volumeMode, devicePath) + if b.volumeMode == v1.PersistentVolumeBlock { + return devicePath, nil + } + } + // mount it globalPDPath := util.MakeGlobalPDName(*b.fcDisk) if err := os.MkdirAll(globalPDPath, 0750); err != nil { @@ -240,13 +249,153 @@ func (util *FCUtil) AttachDisk(b fcDiskMounter) (string, error) { return devicePath, err } -func (util *FCUtil) DetachDisk(c fcDiskUnmounter, devName string) error { - // Remove scsi device from the node. - if !strings.HasPrefix(devName, "/dev/") { - return fmt.Errorf("fc detach disk: invalid device name: %s", devName) +// DetachDisk removes scsi device file such as /dev/sdX from the node. +func (util *FCUtil) DetachDisk(c fcDiskUnmounter, devicePath string) error { + var devices []string + // devicePath might be like /dev/mapper/mpathX. Find destination. + dstPath, err := c.io.EvalSymlinks(devicePath) + if err != nil { + return err + } + // Find slave + if strings.HasPrefix(dstPath, "/dev/dm-") { + devices = c.deviceUtil.FindSlaveDevicesOnMultipath(dstPath) + } else { + // Add single devicepath to devices + devices = append(devices, dstPath) + } + glog.V(4).Infof("fc: DetachDisk devicePath: %v, dstPath: %v, devices: %v", devicePath, dstPath, devices) + var lastErr error + for _, device := range devices { + err := util.detachFCDisk(c.io, device) + if err != nil { + glog.Errorf("fc: detachFCDisk failed. device: %v err: %v", device, err) + lastErr = fmt.Errorf("fc: detachFCDisk failed. device: %v err: %v", device, err) + } + } + if lastErr != nil { + glog.Errorf("fc: last error occurred during detach disk:\n%v", lastErr) + return lastErr } - arr := strings.Split(devName, "/") - dev := arr[len(arr)-1] - removeFromScsiSubsystem(dev, c.io) return nil } + +// detachFCDisk removes scsi device file such as /dev/sdX from the node. +func (util *FCUtil) detachFCDisk(io ioHandler, devicePath string) error { + // Remove scsi device from the node. + if !strings.HasPrefix(devicePath, "/dev/") { + return fmt.Errorf("fc detach disk: invalid device name: %s", devicePath) + } + arr := strings.Split(devicePath, "/") + dev := arr[len(arr)-1] + removeFromScsiSubsystem(dev, io) + return nil +} + +// DetachBlockFCDisk detaches a volume from kubelet node, removes scsi device file +// such as /dev/sdX from the node, and then removes loopback for the scsi device. +func (util *FCUtil) DetachBlockFCDisk(c fcDiskUnmapper, mapPath, devicePath string) error { + // Check if devicePath is valid + if len(devicePath) != 0 { + if pathExists, pathErr := checkPathExists(devicePath); !pathExists || pathErr != nil { + return pathErr + } + } else { + // TODO: FC plugin can't obtain the devicePath from kubelet becuase devicePath + // in volume object isn't updated when volume is attached to kubelet node. + glog.Infof("fc: devicePath is empty. Try to retreive FC configuration from global map path: %v", mapPath) + } + + // Check if global map path is valid + // global map path examples: + // wwn+lun: plugins/kubernetes.io/fc/volumeDevices/50060e801049cfd1-lun-0/ + // wwid: plugins/kubernetes.io/fc/volumeDevices/3600508b400105e210000900000490000/ + if pathExists, pathErr := checkPathExists(mapPath); !pathExists || pathErr != nil { + return pathErr + } + + // Retreive volume plugin dependent path like '50060e801049cfd1-lun-0' from global map path + arr := strings.Split(mapPath, "/") + if len(arr) < 1 { + return fmt.Errorf("Fail to retreive volume plugin information from global map path: %v", mapPath) + } + volumeInfo := arr[len(arr)-1] + + // Search symbolick link which matches volumeInfo under /dev/disk/by-path or /dev/disk/by-id + // then find destination device path from the link + searchPath := byID + if strings.Contains(volumeInfo, "-lun-") { + searchPath = byPath + } + fis, err := ioutil.ReadDir(searchPath) + if err != nil { + return err + } + for _, fi := range fis { + if strings.Contains(fi.Name(), volumeInfo) { + devicePath = path.Join(searchPath, fi.Name()) + glog.V(5).Infof("fc: updated devicePath: %s", devicePath) + break + } + } + if len(devicePath) == 0 { + return fmt.Errorf("fc: failed to find corresponding device from searchPath: %v", searchPath) + } + dstPath, err := c.io.EvalSymlinks(devicePath) + if err != nil { + return err + } + glog.V(4).Infof("fc: find destination device path from symlink: %v", dstPath) + + // Get loopback device which takes fd lock for device beofore detaching a volume from node. + var devices []string + blkUtil := volumeutil.NewBlockVolumePathHandler() + dm := c.deviceUtil.FindMultipathDeviceForDevice(dstPath) + if len(dm) != 0 { + dstPath = dm + } + loop, err := volumeutil.BlockVolumePathHandler.GetLoopDevice(blkUtil, dstPath) + if err != nil { + glog.Warningf("fc: failed to get loopback for device: %v, err: %v", dstPath, err) + } else { + glog.V(4).Infof("fc: found loopback: %v", loop) + } + + // Detach volume from kubelet node + if len(dm) != 0 { + // Find all devices which are managed by multipath + devices = c.deviceUtil.FindSlaveDevicesOnMultipath(dm) + } else { + // Add single device path to devices + devices = append(devices, dstPath) + } + var lastErr error + for _, device := range devices { + err = util.detachFCDisk(c.io, device) + if err != nil { + glog.Errorf("fc: detachFCDisk failed. device: %v err: %v", device, err) + lastErr = fmt.Errorf("fc: detachFCDisk failed. device: %v err: %v", device, err) + } + } + if lastErr != nil { + glog.Errorf("fc: last error occurred during detach disk:\n%v", lastErr) + return lastErr + } + + // The volume was successfully detached from node. We can safely remove the loopback. + err = volumeutil.BlockVolumePathHandler.RemoveLoopDevice(blkUtil, loop) + if err != nil { + return fmt.Errorf("fc: failed to remove loopback :%v, err: %v", loop, err) + } + return nil +} + +func checkPathExists(path string) (bool, error) { + if pathExists, pathErr := volumeutil.PathExists(path); pathErr != nil { + return pathExists, fmt.Errorf("Error checking if path exists: %v", pathErr) + } else if !pathExists { + glog.Warningf("Warning: Unmap skipped because path does not exist: %v", path) + return pathExists, nil + } + return true, nil +} diff --git a/pkg/volume/fc/fc_util_test.go b/pkg/volume/fc/fc_util_test.go index d9f609592b4..50c5308b940 100644 --- a/pkg/volume/fc/fc_util_test.go +++ b/pkg/volume/fc/fc_util_test.go @@ -20,6 +20,8 @@ import ( "os" "testing" "time" + + "k8s.io/kubernetes/pkg/volume/util" ) type fakeFileInfo struct { @@ -91,10 +93,11 @@ func TestSearchDisk(t *testing.T) { lun: "0", io: &fakeIOHandler{}, }, + deviceUtil: util.NewDeviceHandler(util.NewIOHandler()), } - disk, dm := searchDisk(fakeMounter) + devicePath, error := searchDisk(fakeMounter) // if no disk matches input wwn and lun, exit - if disk == "" && dm == "" { + if devicePath == "" || error != nil { t.Errorf("no fc disk found") } } @@ -105,10 +108,11 @@ func TestSearchDiskWWID(t *testing.T) { wwids: []string{"3600508b400105e210000900000490000"}, io: &fakeIOHandler{}, }, + deviceUtil: util.NewDeviceHandler(util.NewIOHandler()), } - disk, dm := searchDisk(fakeMounter) + devicePath, error := searchDisk(fakeMounter) // if no disk matches input wwid, exit - if disk == "" && dm == "" { + if devicePath == "" || error != nil { t.Errorf("no fc disk found") } } diff --git a/pkg/volume/flexvolume/BUILD b/pkg/volume/flexvolume/BUILD index b55261ee62b..5d28f4963cc 100644 --- a/pkg/volume/flexvolume/BUILD +++ b/pkg/volume/flexvolume/BUILD @@ -54,8 +54,8 @@ go_test( "probe_test.go", "unmounter_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/flexvolume", - library = ":go_default_library", deps = [ "//pkg/util/filesystem:go_default_library", "//pkg/util/mount:go_default_library", diff --git a/pkg/volume/flexvolume/OWNERS b/pkg/volume/flexvolume/OWNERS index 975407b4199..3342c22c4c4 100644 --- a/pkg/volume/flexvolume/OWNERS +++ b/pkg/volume/flexvolume/OWNERS @@ -6,13 +6,7 @@ reviewers: - ivan4th - rata - sjenning -- thockin -- smarterclayton -- brendandburns -- derekwaynecarr -- pmorie - saad-ali -- justinsb - jsafrane - rootfs - jingxu97 diff --git a/pkg/volume/flexvolume/attacher-defaults.go b/pkg/volume/flexvolume/attacher-defaults.go index bf8dcfe8254..e578443c47b 100644 --- a/pkg/volume/flexvolume/attacher-defaults.go +++ b/pkg/volume/flexvolume/attacher-defaults.go @@ -48,7 +48,16 @@ func (a *attacherDefaults) GetDeviceMountPath(spec *volume.Spec, mountsDir strin // MountDevice is part of the volume.Attacher interface func (a *attacherDefaults) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, mounter mount.Interface) error { glog.Warning(logPrefix(a.plugin.flexVolumePlugin), "using default MountDevice for volume ", spec.Name, ", device ", devicePath, ", deviceMountPath ", deviceMountPath) - volSource, readOnly := getVolumeSource(spec) + + volSourceFSType, err := getFSType(spec) + if err != nil { + return err + } + + readOnly, err := getReadOnly(spec) + if err != nil { + return err + } options := make([]string, 0) @@ -60,5 +69,5 @@ func (a *attacherDefaults) MountDevice(spec *volume.Spec, devicePath string, dev diskMounter := &mount.SafeFormatAndMount{Interface: mounter, Exec: a.plugin.host.GetExec(a.plugin.GetPluginName())} - return diskMounter.FormatAndMount(devicePath, deviceMountPath, volSource.FSType, options) + return diskMounter.FormatAndMount(devicePath, deviceMountPath, volSourceFSType, options) } diff --git a/pkg/volume/flexvolume/attacher.go b/pkg/volume/flexvolume/attacher.go index c2efcffa1c3..2464b3f2392 100644 --- a/pkg/volume/flexvolume/attacher.go +++ b/pkg/volume/flexvolume/attacher.go @@ -113,6 +113,8 @@ func (a *flexVolumeAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName t volumesAttachedCheck[spec] = false glog.V(2).Infof("VolumesAreAttached: check volume (%q) is no longer attached", spec.Name()) } + } else { + return nil, err } } return volumesAttachedCheck, nil diff --git a/pkg/volume/flexvolume/common_test.go b/pkg/volume/flexvolume/common_test.go index 079b32adac6..9300c2a7650 100644 --- a/pkg/volume/flexvolume/common_test.go +++ b/pkg/volume/flexvolume/common_test.go @@ -119,7 +119,7 @@ func fakePersistentVolumeSpec() *volume.Spec { }, Spec: v1.PersistentVolumeSpec{ PersistentVolumeSource: v1.PersistentVolumeSource{ - FlexVolume: &v1.FlexVolumeSource{ + FlexVolume: &v1.FlexPersistentVolumeSource{ Driver: "kubernetes.io/fakeAttacher", ReadOnly: false, }, diff --git a/pkg/volume/flexvolume/detacher-defaults.go b/pkg/volume/flexvolume/detacher-defaults.go index 17c85a36532..181f87bde48 100644 --- a/pkg/volume/flexvolume/detacher-defaults.go +++ b/pkg/volume/flexvolume/detacher-defaults.go @@ -27,8 +27,8 @@ import ( type detacherDefaults flexVolumeDetacher // Detach is part of the volume.Detacher interface. -func (d *detacherDefaults) Detach(deviceName string, hostName types.NodeName) error { - glog.Warning(logPrefix(d.plugin.flexVolumePlugin), "using default Detach for device ", deviceName, ", host ", hostName) +func (d *detacherDefaults) Detach(volumeName string, hostName types.NodeName) error { + glog.Warning(logPrefix(d.plugin.flexVolumePlugin), "using default Detach for volume ", volumeName, ", host ", hostName) return nil } diff --git a/pkg/volume/flexvolume/detacher.go b/pkg/volume/flexvolume/detacher.go index 4acbd03eaef..c55ffbfa473 100644 --- a/pkg/volume/flexvolume/detacher.go +++ b/pkg/volume/flexvolume/detacher.go @@ -19,7 +19,6 @@ package flexvolume import ( "fmt" "os" - "time" "github.com/golang/glog" "k8s.io/apimachinery/pkg/types" @@ -34,27 +33,15 @@ type flexVolumeDetacher struct { var _ volume.Detacher = &flexVolumeDetacher{} // Detach is part of the volume.Detacher interface. -func (d *flexVolumeDetacher) Detach(pvOrVolumeName string, hostName types.NodeName) error { +func (d *flexVolumeDetacher) Detach(volumeName string, hostName types.NodeName) error { call := d.plugin.NewDriverCall(detachCmd) - call.Append(pvOrVolumeName) + call.Append(volumeName) call.Append(string(hostName)) _, err := call.Run() if isCmdNotSupportedErr(err) { - return (*detacherDefaults)(d).Detach(pvOrVolumeName, hostName) - } - return err -} - -// WaitForDetach is part of the volume.Detacher interface. -func (d *flexVolumeDetacher) WaitForDetach(devicePath string, timeout time.Duration) error { - call := d.plugin.NewDriverCallWithTimeout(waitForDetachCmd, timeout) - call.Append(devicePath) - - _, err := call.Run() - if isCmdNotSupportedErr(err) { - return (*detacherDefaults)(d).WaitForDetach(devicePath, timeout) + return (*detacherDefaults)(d).Detach(volumeName, hostName) } return err } diff --git a/pkg/volume/flexvolume/driver-call.go b/pkg/volume/flexvolume/driver-call.go index e3e4527ddc9..5b089df07a6 100644 --- a/pkg/volume/flexvolume/driver-call.go +++ b/pkg/volume/flexvolume/driver-call.go @@ -39,7 +39,6 @@ const ( mountDeviceCmd = "mountdevice" detachCmd = "detach" - waitForDetachCmd = "waitfordetach" unmountDeviceCmd = "unmountdevice" mountCmd = "mount" @@ -162,10 +161,25 @@ func (dc *DriverCall) Run() (*DriverStatus, error) { type OptionsForDriver map[string]string func NewOptionsForDriver(spec *volume.Spec, host volume.VolumeHost, extraOptions map[string]string) (OptionsForDriver, error) { - volSource, readOnly := getVolumeSource(spec) + + volSourceFSType, err := getFSType(spec) + if err != nil { + return nil, err + } + + readOnly, err := getReadOnly(spec) + if err != nil { + return nil, err + } + + volSourceOptions, err := getOptions(spec) + if err != nil { + return nil, err + } + options := map[string]string{} - options[optionFSType] = volSource.FSType + options[optionFSType] = volSourceFSType if readOnly { options[optionReadWrite] = "ro" @@ -179,7 +193,7 @@ func NewOptionsForDriver(spec *volume.Spec, host volume.VolumeHost, extraOptions options[key] = value } - for key, value := range volSource.Options { + for key, value := range volSourceOptions { options[key] = value } diff --git a/pkg/volume/flexvolume/flexvolume_test.go b/pkg/volume/flexvolume/flexvolume_test.go index 292beb54506..c90ad49322d 100644 --- a/pkg/volume/flexvolume/flexvolume_test.go +++ b/pkg/volume/flexvolume/flexvolume_test.go @@ -185,7 +185,7 @@ func TestCanSupport(t *testing.T) { if !plugin.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{FlexVolume: &v1.FlexVolumeSource{Driver: "kubernetes.io/fakeAttacher"}}}}) { t.Errorf("Expected true") } - if !plugin.CanSupport(&volume.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{PersistentVolumeSource: v1.PersistentVolumeSource{FlexVolume: &v1.FlexVolumeSource{Driver: "kubernetes.io/fakeAttacher"}}}}}) { + if !plugin.CanSupport(&volume.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{PersistentVolumeSource: v1.PersistentVolumeSource{FlexVolume: &v1.FlexPersistentVolumeSource{Driver: "kubernetes.io/fakeAttacher"}}}}}) { t.Errorf("Expected true") } if plugin.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{}}}) { @@ -208,16 +208,7 @@ func TestGetAccessModes(t *testing.T) { if err != nil { t.Fatalf("Can't find the plugin by name") } - if !contains(plugin.GetAccessModes(), v1.ReadWriteOnce) || !contains(plugin.GetAccessModes(), v1.ReadOnlyMany) { + if !volumetest.ContainsAccessMode(plugin.GetAccessModes(), v1.ReadWriteOnce) || !volumetest.ContainsAccessMode(plugin.GetAccessModes(), v1.ReadOnlyMany) { t.Errorf("Expected two AccessModeTypes: %s and %s", v1.ReadWriteOnce, v1.ReadOnlyMany) } } - -func contains(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool { - for _, m := range modes { - if m == mode { - return true - } - } - return false -} diff --git a/pkg/volume/flexvolume/plugin.go b/pkg/volume/flexvolume/plugin.go index ba46e2c52d1..e402e20e700 100644 --- a/pkg/volume/flexvolume/plugin.go +++ b/pkg/volume/flexvolume/plugin.go @@ -19,6 +19,7 @@ package flexvolume import ( "fmt" "path" + "runtime" "strings" "sync" @@ -100,7 +101,11 @@ func (plugin *flexVolumePlugin) Init(host volume.VolumeHost) error { func (plugin *flexVolumePlugin) getExecutable() string { parts := strings.Split(plugin.driverName, "/") execName := parts[len(parts)-1] - return path.Join(plugin.execPath, execName) + execPath := path.Join(plugin.execPath, execName) + if runtime.GOOS == "windows" { + execPath = volume.GetWindowsPath(execPath) + } + return execPath } // Name is part of the volume.VolumePlugin interface. @@ -132,8 +137,11 @@ func (plugin *flexVolumePlugin) GetVolumeName(spec *volume.Spec) (string, error) // CanSupport is part of the volume.VolumePlugin interface. func (plugin *flexVolumePlugin) CanSupport(spec *volume.Spec) bool { - source, _ := getVolumeSource(spec) - return (source != nil) && (source.Driver == plugin.driverName) + sourceDriver, err := getDriver(spec) + if err != nil { + return false + } + return sourceDriver == plugin.driverName } // RequiresRemount is part of the volume.VolumePlugin interface. @@ -156,10 +164,19 @@ func (plugin *flexVolumePlugin) NewMounter(spec *volume.Spec, pod *api.Pod, _ vo // newMounterInternal is the internal mounter routine to build the volume. func (plugin *flexVolumePlugin) newMounterInternal(spec *volume.Spec, pod *api.Pod, mounter mount.Interface, runner exec.Interface) (volume.Mounter, error) { - source, readOnly := getVolumeSource(spec) + sourceDriver, err := getDriver(spec) + if err != nil { + return nil, err + } + + readOnly, err := getReadOnly(spec) + if err != nil { + return nil, err + } + return &flexVolumeMounter{ flexVolume: &flexVolume{ - driverName: source.Driver, + driverName: sourceDriver, execPath: plugin.getExecutable(), mounter: mounter, plugin: plugin, diff --git a/pkg/volume/flexvolume/util.go b/pkg/volume/flexvolume/util.go index bc86e1a60da..b706712101c 100644 --- a/pkg/volume/flexvolume/util.go +++ b/pkg/volume/flexvolume/util.go @@ -22,15 +22,18 @@ import ( "os" "github.com/golang/glog" - api "k8s.io/api/core/v1" "k8s.io/kubernetes/pkg/util/mount" "k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume/util" ) func addSecretsToOptions(options map[string]string, spec *volume.Spec, namespace string, driverName string, host volume.VolumeHost) error { - fv, _ := getVolumeSource(spec) - if fv.SecretRef == nil { + secretName, secretNamespace, err := getSecretNameAndNamespace(spec, namespace) + if err != nil { + return err + } + + if len(secretName) == 0 || len(secretNamespace) == 0 { return nil } @@ -39,9 +42,9 @@ func addSecretsToOptions(options map[string]string, spec *volume.Spec, namespace return fmt.Errorf("Cannot get kube client") } - secrets, err := util.GetSecretForPV(namespace, fv.SecretRef.Name, driverName, host.GetKubeClient()) + secrets, err := util.GetSecretForPV(secretNamespace, secretName, driverName, host.GetKubeClient()) if err != nil { - err = fmt.Errorf("Couldn't get secret %v/%v err: %v", namespace, fv.SecretRef.Name, err) + err = fmt.Errorf("Couldn't get secret %v/%v err: %v", secretNamespace, secretName, err) return err } for name, data := range secrets { @@ -52,15 +55,68 @@ func addSecretsToOptions(options map[string]string, spec *volume.Spec, namespace return nil } -func getVolumeSource(spec *volume.Spec) (volumeSource *api.FlexVolumeSource, readOnly bool) { +var notFlexVolume = fmt.Errorf("not a flex volume") + +func getDriver(spec *volume.Spec) (string, error) { if spec.Volume != nil && spec.Volume.FlexVolume != nil { - volumeSource = spec.Volume.FlexVolume - readOnly = volumeSource.ReadOnly - } else if spec.PersistentVolume != nil { - volumeSource = spec.PersistentVolume.Spec.FlexVolume - readOnly = spec.ReadOnly + return spec.Volume.FlexVolume.Driver, nil } - return + if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.FlexVolume != nil { + return spec.PersistentVolume.Spec.FlexVolume.Driver, nil + } + return "", notFlexVolume +} + +func getFSType(spec *volume.Spec) (string, error) { + if spec.Volume != nil && spec.Volume.FlexVolume != nil { + return spec.Volume.FlexVolume.FSType, nil + } + if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.FlexVolume != nil { + return spec.PersistentVolume.Spec.FlexVolume.FSType, nil + } + return "", notFlexVolume +} + +func getSecretNameAndNamespace(spec *volume.Spec, podNamespace string) (string, string, error) { + if spec.Volume != nil && spec.Volume.FlexVolume != nil { + if spec.Volume.FlexVolume.SecretRef == nil { + return "", "", nil + } + return spec.Volume.FlexVolume.SecretRef.Name, podNamespace, nil + } + if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.FlexVolume != nil { + if spec.PersistentVolume.Spec.FlexVolume.SecretRef == nil { + return "", "", nil + } + secretName := spec.PersistentVolume.Spec.FlexVolume.SecretRef.Name + secretNamespace := spec.PersistentVolume.Spec.FlexVolume.SecretRef.Namespace + if len(secretNamespace) == 0 { + secretNamespace = podNamespace + } + return secretName, secretNamespace, nil + } + return "", "", notFlexVolume +} + +func getReadOnly(spec *volume.Spec) (bool, error) { + if spec.Volume != nil && spec.Volume.FlexVolume != nil { + return spec.Volume.FlexVolume.ReadOnly, nil + } + if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.FlexVolume != nil { + // ReadOnly is specified at the PV level + return spec.ReadOnly, nil + } + return false, notFlexVolume +} + +func getOptions(spec *volume.Spec) (map[string]string, error) { + if spec.Volume != nil && spec.Volume.FlexVolume != nil { + return spec.Volume.FlexVolume.Options, nil + } + if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.FlexVolume != nil { + return spec.PersistentVolume.Spec.FlexVolume.Options, nil + } + return nil, notFlexVolume } func prepareForMount(mounter mount.Interface, deviceMountPath string) (bool, error) { diff --git a/pkg/volume/flocker/BUILD b/pkg/volume/flocker/BUILD index 3d21d92dc91..a213590e937 100644 --- a/pkg/volume/flocker/BUILD +++ b/pkg/volume/flocker/BUILD @@ -39,8 +39,8 @@ go_test( "flocker_util_test.go", "flocker_volume_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/flocker", - library = ":go_default_library", deps = [ "//pkg/util/mount:go_default_library", "//pkg/volume:go_default_library", diff --git a/pkg/volume/flocker/OWNERS b/pkg/volume/flocker/OWNERS index 35cbcb214a6..f9f7cef504e 100644 --- a/pkg/volume/flocker/OWNERS +++ b/pkg/volume/flocker/OWNERS @@ -4,13 +4,7 @@ approvers: reviewers: - agonzalezro - simonswine -- thockin -- smarterclayton -- brendandburns -- derekwaynecarr -- pmorie - saad-ali -- justinsb - jsafrane - rootfs - jingxu97 diff --git a/pkg/volume/gce_pd/BUILD b/pkg/volume/gce_pd/BUILD index 45b973c02a1..7a4feab0d1c 100644 --- a/pkg/volume/gce_pd/BUILD +++ b/pkg/volume/gce_pd/BUILD @@ -39,14 +39,15 @@ go_test( "attacher_test.go", "gce_pd_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/gce_pd", - library = ":go_default_library", deps = [ "//pkg/util/mount:go_default_library", "//pkg/volume:go_default_library", "//pkg/volume/testing:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", diff --git a/pkg/volume/gce_pd/OWNERS b/pkg/volume/gce_pd/OWNERS index 1f138a15291..5fa7bd72c20 100644 --- a/pkg/volume/gce_pd/OWNERS +++ b/pkg/volume/gce_pd/OWNERS @@ -2,13 +2,11 @@ approvers: - saad-ali - thockin reviewers: -- thockin -- smarterclayton -- pmorie - saad-ali -- justinsb - jsafrane -- markturansky - jingxu97 - matchstick - gnufied +- msau42 +- verult +- davidz627 diff --git a/pkg/volume/gce_pd/attacher.go b/pkg/volume/gce_pd/attacher.go index 588d9aabe76..6ee1fd6d230 100644 --- a/pkg/volume/gce_pd/attacher.go +++ b/pkg/volume/gce_pd/attacher.go @@ -247,8 +247,8 @@ func (plugin *gcePersistentDiskPlugin) NewDetacher() (volume.Detacher, error) { // Callers are responsible for retrying on failure. // Callers are responsible for thread safety between concurrent attach and detach // operations. -func (detacher *gcePersistentDiskDetacher) Detach(deviceMountPath string, nodeName types.NodeName) error { - pdName := path.Base(deviceMountPath) +func (detacher *gcePersistentDiskDetacher) Detach(volumeName string, nodeName types.NodeName) error { + pdName := path.Base(volumeName) attached, err := detacher.gceDisks.DiskIsAttached(pdName, nodeName) if err != nil { diff --git a/pkg/volume/gce_pd/attacher_test.go b/pkg/volume/gce_pd/attacher_test.go index 3d8fe615a21..0539e11a572 100644 --- a/pkg/volume/gce_pd/attacher_test.go +++ b/pkg/volume/gce_pd/attacher_test.go @@ -22,6 +22,7 @@ import ( "testing" "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/kubernetes/pkg/volume" volumetest "k8s.io/kubernetes/pkg/volume/testing" @@ -373,3 +374,10 @@ func (testcase *testcase) DeleteDisk(diskToDelete string) error { func (testcase *testcase) GetAutoLabelsForPD(name string, zone string) (map[string]string, error) { return map[string]string{}, errors.New("Not implemented") } + +func (testcase *testcase) ResizeDisk( + diskName string, + oldSize resource.Quantity, + newSize resource.Quantity) (resource.Quantity, error) { + return oldSize, errors.New("Not implemented") +} diff --git a/pkg/volume/gce_pd/gce_pd.go b/pkg/volume/gce_pd/gce_pd.go index 9ddeb95787e..2b94cb29731 100644 --- a/pkg/volume/gce_pd/gce_pd.go +++ b/pkg/volume/gce_pd/gce_pd.go @@ -47,6 +47,7 @@ var _ volume.VolumePlugin = &gcePersistentDiskPlugin{} var _ volume.PersistentVolumePlugin = &gcePersistentDiskPlugin{} var _ volume.DeletableVolumePlugin = &gcePersistentDiskPlugin{} var _ volume.ProvisionableVolumePlugin = &gcePersistentDiskPlugin{} +var _ volume.ExpandableVolumePlugin = &gcePersistentDiskPlugin{} const ( gcePersistentDiskPluginName = "kubernetes.io/gce-pd" @@ -190,6 +191,28 @@ func (plugin *gcePersistentDiskPlugin) newProvisionerInternal(options volume.Vol }, nil } +func (plugin *gcePersistentDiskPlugin) RequiresFSResize() bool { + return true +} + +func (plugin *gcePersistentDiskPlugin) ExpandVolumeDevice( + spec *volume.Spec, + newSize resource.Quantity, + oldSize resource.Quantity) (resource.Quantity, error) { + cloud, err := getCloudProvider(plugin.host.GetCloudProvider()) + + if err != nil { + return oldSize, err + } + pdName := spec.PersistentVolume.Spec.GCEPersistentDisk.PDName + updatedQuantity, err := cloud.ResizeDisk(pdName, oldSize, newSize) + + if err != nil { + return oldSize, err + } + return updatedQuantity, nil +} + func (plugin *gcePersistentDiskPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) { mounter := plugin.host.GetMounter(plugin.GetPluginName()) pluginDir := plugin.host.GetPluginDir(plugin.GetPluginName()) @@ -400,7 +423,7 @@ func (c *gcePersistentDiskProvisioner) Provision() (*v1.PersistentVolume, error) PersistentVolumeReclaimPolicy: c.options.PersistentVolumeReclaimPolicy, AccessModes: c.options.PVC.Spec.AccessModes, Capacity: v1.ResourceList{ - v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", sizeGB)), + v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dG", sizeGB)), }, PersistentVolumeSource: v1.PersistentVolumeSource{ GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{ diff --git a/pkg/volume/gce_pd/gce_pd_test.go b/pkg/volume/gce_pd/gce_pd_test.go index cb8397da2b8..79c96b8ac50 100644 --- a/pkg/volume/gce_pd/gce_pd_test.go +++ b/pkg/volume/gce_pd/gce_pd_test.go @@ -69,20 +69,11 @@ func TestGetAccessModes(t *testing.T) { if err != nil { t.Errorf("Can't find the plugin by name") } - if !contains(plug.GetAccessModes(), v1.ReadWriteOnce) || !contains(plug.GetAccessModes(), v1.ReadOnlyMany) { + if !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteOnce) || !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadOnlyMany) { t.Errorf("Expected two AccessModeTypes: %s and %s", v1.ReadWriteOnce, v1.ReadOnlyMany) } } -func contains(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool { - for _, m := range modes { - if m == mode { - return true - } - } - return false -} - type fakePDManager struct { } @@ -170,7 +161,7 @@ func TestPlugin(t *testing.T) { if _, err := os.Stat(path); err == nil { t.Errorf("TearDown() failed, volume path still exists: %s", path) } else if !os.IsNotExist(err) { - t.Errorf("SetUp() failed: %v", err) + t.Errorf("TearDown() failed: %v", err) } // Test Provisioner @@ -192,7 +183,7 @@ func TestPlugin(t *testing.T) { } cap := persistentSpec.Spec.Capacity[v1.ResourceStorage] size := cap.Value() - if size != 100*1024*1024*1024 { + if size != 100*volume.GB { t.Errorf("Provision() returned unexpected volume size: %v", size) } diff --git a/pkg/volume/gce_pd/gce_util.go b/pkg/volume/gce_pd/gce_util.go index 026324e6305..022d759d163 100644 --- a/pkg/volume/gce_pd/gce_util.go +++ b/pkg/volume/gce_pd/gce_util.go @@ -81,9 +81,8 @@ func (gceutil *GCEDiskUtil) CreateVolume(c *gcePersistentDiskProvisioner) (strin name := volume.GenerateVolumeName(c.options.ClusterName, c.options.PVName, 63) // GCE PD name can have up to 63 characters capacity := c.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)] - requestBytes := capacity.Value() - // GCE works with gigabytes, convert to GiB with rounding up - requestGB := volume.RoundUpSize(requestBytes, 1024*1024*1024) + // GCE PDs are allocated in chunks of GBs (not GiBs) + requestGB := volume.RoundUpToGB(capacity) // Apply Parameters (case-insensitive). We leave validation of // the values to the cloud provider. @@ -153,7 +152,7 @@ func (gceutil *GCEDiskUtil) CreateVolume(c *gcePersistentDiskProvisioner) (strin // 000 - neither "zone", "zones", or "replica-zones" specified // Pick a zone randomly selected from all active zones where // Kubernetes cluster has a node. - zones, err = cloud.GetAllZones() + zones, err = cloud.GetAllCurrentZones() if err != nil { glog.V(2).Infof("error getting zone information from GCE: %v", err) return "", 0, nil, "", err diff --git a/pkg/volume/git_repo/BUILD b/pkg/volume/git_repo/BUILD index cd24945cf33..b726378cf1b 100644 --- a/pkg/volume/git_repo/BUILD +++ b/pkg/volume/git_repo/BUILD @@ -26,8 +26,8 @@ go_library( go_test( name = "go_default_test", srcs = ["git_repo_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/git_repo", - library = ":go_default_library", deps = [ "//pkg/volume:go_default_library", "//pkg/volume/empty_dir:go_default_library", diff --git a/pkg/volume/git_repo/OWNERS b/pkg/volume/git_repo/OWNERS index 801d55bc1f7..27d4e921659 100644 --- a/pkg/volume/git_repo/OWNERS +++ b/pkg/volume/git_repo/OWNERS @@ -2,13 +2,7 @@ approvers: - thockin - saad-ali reviewers: -- thockin -- smarterclayton -- brendandburns -- derekwaynecarr -- pmorie - saad-ali -- justinsb - jsafrane - rootfs - jingxu97 diff --git a/pkg/volume/git_repo/git_repo_test.go b/pkg/volume/git_repo/git_repo_test.go index 1bee3ad2185..8c6a41a1c1e 100644 --- a/pkg/volume/git_repo/git_repo_test.go +++ b/pkg/volume/git_repo/git_repo_test.go @@ -306,7 +306,7 @@ func doTestPlugin(scenario struct { fmt.Errorf("TearDown() failed, volume path still exists: %s", path)) } else if !os.IsNotExist(err) { allErrs = append(allErrs, - fmt.Errorf("SetUp() failed: %v", err)) + fmt.Errorf("TearDown() failed: %v", err)) } return allErrs } diff --git a/pkg/volume/glusterfs/BUILD b/pkg/volume/glusterfs/BUILD index 5eeb65b40f6..a57114c412e 100644 --- a/pkg/volume/glusterfs/BUILD +++ b/pkg/volume/glusterfs/BUILD @@ -16,7 +16,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/volume/glusterfs", deps = [ - "//pkg/api/v1/helper:go_default_library", + "//pkg/apis/core/v1/helper:go_default_library", "//pkg/util/mount:go_default_library", "//pkg/util/strings:go_default_library", "//pkg/volume:go_default_library", @@ -42,8 +42,8 @@ go_test( "glusterfs_minmax_test.go", "glusterfs_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/glusterfs", - library = ":go_default_library", deps = [ "//pkg/util/mount:go_default_library", "//pkg/volume:go_default_library", diff --git a/pkg/volume/glusterfs/OWNERS b/pkg/volume/glusterfs/OWNERS index 75150d58938..080b69b30fb 100644 --- a/pkg/volume/glusterfs/OWNERS +++ b/pkg/volume/glusterfs/OWNERS @@ -4,13 +4,7 @@ approvers: - jingxu97 - humblec reviewers: -- thockin -- smarterclayton -- brendandburns -- derekwaynecarr -- pmorie - saad-ali -- justinsb - jsafrane - rootfs - humblec diff --git a/pkg/volume/glusterfs/glusterfs.go b/pkg/volume/glusterfs/glusterfs.go index b3fae1aad82..adab5d535c3 100644 --- a/pkg/volume/glusterfs/glusterfs.go +++ b/pkg/volume/glusterfs/glusterfs.go @@ -37,7 +37,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" clientset "k8s.io/client-go/kubernetes" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" "k8s.io/kubernetes/pkg/util/mount" "k8s.io/kubernetes/pkg/util/strings" "k8s.io/kubernetes/pkg/volume" @@ -84,6 +84,7 @@ const ( heketiAnn = "heketi-dynamic-provisioner" glusterTypeAnn = "gluster.org/type" glusterDescAnn = "Gluster-Internal: Dynamically provisioned PV" + heketiVolIDAnn = "gluster.kubernetes.io/heketi-volume-id" ) func (plugin *glusterfsPlugin) Init(host volume.VolumeHost) error { @@ -149,7 +150,7 @@ func (plugin *glusterfsPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volu if kubeClient == nil { return nil, fmt.Errorf("failed to get kube client to initialize mounter") } - ep, err := kubeClient.Core().Endpoints(podNs).Get(epName, metav1.GetOptions{}) + ep, err := kubeClient.CoreV1().Endpoints(podNs).Get(epName, metav1.GetOptions{}) if err != nil { glog.Errorf("failed to get endpoints %s[%v]", epName, err) return nil, err @@ -197,16 +198,11 @@ func (plugin *glusterfsPlugin) newUnmounterInternal(volName string, podUID types } func (plugin *glusterfsPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) { - glusterfsVolume := &v1.Volume{ - Name: volumeName, - VolumeSource: v1.VolumeSource{ - Glusterfs: &v1.GlusterfsVolumeSource{ - EndpointsName: volumeName, - Path: volumeName, - }, - }, - } - return volume.NewSpecFromVolume(glusterfsVolume), nil + + // To reconstrcut volume spec we need endpoint where fetching endpoint from mount + // string looks to be impossible, so returning error. + + return nil, fmt.Errorf("impossible to reconstruct glusterfs volume spec from volume mountpath") } // Glusterfs volumes represent a bare host file or directory mount of an Glusterfs export. @@ -243,7 +239,7 @@ func (b *glusterfsMounter) CanMount() error { exe := b.plugin.host.GetExec(b.plugin.GetPluginName()) switch runtime.GOOS { case "linux": - if _, err := exe.Run("/bin/ls", gciLinuxGlusterMountBinaryPath); err != nil { + if _, err := exe.Run("test", "-x", gciLinuxGlusterMountBinaryPath); err != nil { return fmt.Errorf("Required binary %s is missing", gciLinuxGlusterMountBinaryPath) } } @@ -493,7 +489,7 @@ func (plugin *glusterfsPlugin) collectGids(className string, gidTable *MinMaxAll if kubeClient == nil { return fmt.Errorf("failed to get kube client when collecting gids") } - pvList, err := kubeClient.Core().PersistentVolumes().List(metav1.ListOptions{LabelSelector: labels.Everything().String()}) + pvList, err := kubeClient.CoreV1().PersistentVolumes().List(metav1.ListOptions{LabelSelector: labels.Everything().String()}) if err != nil { glog.Errorf("failed to get existing persistent volumes") return err @@ -700,7 +696,7 @@ func (p *glusterfsVolumeProvisioner) Provision() (*v1.PersistentVolume, error) { glog.V(2).Infof("Allocated GID [%d] for PVC %s", gid, p.options.PVC.Name) - glusterfs, sizeGB, err := p.CreateVolume(gid) + glusterfs, sizeGiB, volID, err := p.CreateVolume(gid) if err != nil { if releaseErr := gidTable.Release(gid); releaseErr != nil { glog.Errorf("error when releasing GID in storageclass: %s", scName) @@ -709,10 +705,12 @@ func (p *glusterfsVolumeProvisioner) Provision() (*v1.PersistentVolume, error) { glog.Errorf("create volume error: %v.", err) return nil, fmt.Errorf("create volume error: %v", err) } + mode := v1.PersistentVolumeFilesystem pv := new(v1.PersistentVolume) pv.Spec.PersistentVolumeSource.Glusterfs = glusterfs pv.Spec.PersistentVolumeReclaimPolicy = p.options.PersistentVolumeReclaimPolicy pv.Spec.AccessModes = p.options.PVC.Spec.AccessModes + pv.Spec.VolumeMode = &mode if len(pv.Spec.AccessModes) == 0 { pv.Spec.AccessModes = p.plugin.GetAccessModes() } @@ -726,29 +724,29 @@ func (p *glusterfsVolumeProvisioner) Provision() (*v1.PersistentVolume, error) { glusterTypeAnn: "file", "Description": glusterDescAnn, v1.MountOptionAnnotation: "auto_unmount", + heketiVolIDAnn: volID, } pv.Spec.Capacity = v1.ResourceList{ - v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", sizeGB)), + v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", sizeGiB)), } return pv, nil } -func (p *glusterfsVolumeProvisioner) CreateVolume(gid int) (r *v1.GlusterfsVolumeSource, size int, err error) { +func (p *glusterfsVolumeProvisioner) CreateVolume(gid int) (r *v1.GlusterfsVolumeSource, size int, volID string, err error) { var clusterIDs []string capacity := p.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)] - volSizeBytes := capacity.Value() - // Glusterfs creates volumes in units of GBs - sz := int(volume.RoundUpSize(volSizeBytes, 1000*1000*1000)) - glog.V(2).Infof("create volume of size: %d bytes and configuration %+v", volSizeBytes, p.provisionerConfig) + // Glusterfs creates volumes in units of GiB, but heketi documentation incorrectly reports GBs + sz := int(volume.RoundUpToGiB(capacity)) + glog.V(2).Infof("create volume of size: %d GiB and configuration %+v", sz, p.provisionerConfig) if p.url == "" { glog.Errorf("REST server endpoint is empty") - return nil, 0, fmt.Errorf("failed to create glusterfs REST client, REST URL is empty") + return nil, 0, "", fmt.Errorf("failed to create glusterfs REST client, REST URL is empty") } cli := gcli.NewClient(p.url, p.user, p.secretValue) if cli == nil { glog.Errorf("failed to create glusterfs rest client") - return nil, 0, fmt.Errorf("failed to create glusterfs REST client, REST server authentication failed") + return nil, 0, "", fmt.Errorf("failed to create glusterfs REST client, REST server authentication failed") } if p.provisionerConfig.clusterID != "" { clusterIDs = dstrings.Split(p.clusterID, ",") @@ -759,16 +757,17 @@ func (p *glusterfsVolumeProvisioner) CreateVolume(gid int) (r *v1.GlusterfsVolum volume, err := cli.VolumeCreate(volumeReq) if err != nil { glog.Errorf("error creating volume %v ", err) - return nil, 0, fmt.Errorf("error creating volume %v", err) + return nil, 0, "", fmt.Errorf("error creating volume %v", err) } glog.V(1).Infof("volume with size: %d and name: %s created", volume.Size, volume.Name) + volID = volume.Id dynamicHostIps, err := getClusterNodes(cli, volume.Cluster) if err != nil { glog.Errorf("error [%v] when getting cluster nodes for volume %s", err, volume) - return nil, 0, fmt.Errorf("error [%v] when getting cluster nodes for volume %s", err, volume) + return nil, 0, "", fmt.Errorf("error [%v] when getting cluster nodes for volume %s", err, volume) } - // The 'endpointname' is created in form of 'gluster-dynamic-'. + // The 'endpointname' is created in form of 'glusterfs-dynamic-'. // createEndpointService() checks for this 'endpoint' existence in PVC's namespace and // If not found, it create an endpoint and svc using the IPs we dynamically picked at time // of volume creation. @@ -781,14 +780,14 @@ func (p *glusterfsVolumeProvisioner) CreateVolume(gid int) (r *v1.GlusterfsVolum if deleteErr != nil { glog.Errorf("error when deleting the volume :%v , manual deletion required", deleteErr) } - return nil, 0, fmt.Errorf("failed to create endpoint/service %v", err) + return nil, 0, "", fmt.Errorf("failed to create endpoint/service %v", err) } glog.V(3).Infof("dynamic ep %v and svc : %v ", endpoint, service) return &v1.GlusterfsVolumeSource{ EndpointsName: endpoint.Name, Path: volume.Name, ReadOnly: false, - }, sz, nil + }, sz, volID, nil } func (p *glusterfsVolumeProvisioner) createEndpointService(namespace string, epServiceName string, hostips []string, pvcname string) (endpoint *v1.Endpoints, service *v1.Service, err error) { @@ -814,7 +813,7 @@ func (p *glusterfsVolumeProvisioner) createEndpointService(namespace string, epS if kubeClient == nil { return nil, nil, fmt.Errorf("failed to get kube client when creating endpoint service") } - _, err = kubeClient.Core().Endpoints(namespace).Create(endpoint) + _, err = kubeClient.CoreV1().Endpoints(namespace).Create(endpoint) if err != nil && errors.IsAlreadyExists(err) { glog.V(1).Infof("endpoint [%s] already exist in namespace [%s]", endpoint, namespace) err = nil @@ -834,7 +833,7 @@ func (p *glusterfsVolumeProvisioner) createEndpointService(namespace string, epS Spec: v1.ServiceSpec{ Ports: []v1.ServicePort{ {Protocol: "TCP", Port: 1}}}} - _, err = kubeClient.Core().Services(namespace).Create(service) + _, err = kubeClient.CoreV1().Services(namespace).Create(service) if err != nil && errors.IsAlreadyExists(err) { glog.V(1).Infof("service [%s] already exist in namespace [%s]", service, namespace) err = nil @@ -851,7 +850,7 @@ func (d *glusterfsVolumeDeleter) deleteEndpointService(namespace string, epServi if kubeClient == nil { return fmt.Errorf("failed to get kube client when deleting endpoint service") } - err = kubeClient.Core().Services(namespace).Delete(epServiceName, nil) + err = kubeClient.CoreV1().Services(namespace).Delete(epServiceName, nil) if err != nil { glog.Errorf("error deleting service %s/%s: %v", namespace, epServiceName, err) return fmt.Errorf("error deleting service %s/%s: %v", namespace, epServiceName, err) @@ -1082,10 +1081,10 @@ func (plugin *glusterfsPlugin) ExpandVolumeDevice(spec *volume.Spec, newSize res // Find out delta size expansionSize := (newSize.Value() - oldSize.Value()) - expansionSizeGB := int(volume.RoundUpSize(expansionSize, 1000*1000*1000)) + expansionSizeGiB := int(volume.RoundUpSize(expansionSize, volume.GIB)) // Make volume expansion request - volumeExpandReq := &gapi.VolumeExpandRequest{Size: expansionSizeGB} + volumeExpandReq := &gapi.VolumeExpandRequest{Size: expansionSizeGiB} // Expand the volume volumeInfoRes, err := cli.VolumeExpand(volumeID, volumeExpandReq) @@ -1095,6 +1094,6 @@ func (plugin *glusterfsPlugin) ExpandVolumeDevice(spec *volume.Spec, newSize res } glog.V(2).Infof("volume %s expanded to new size %d successfully", volumeName, volumeInfoRes.Size) - newVolumeSize := resource.MustParse(fmt.Sprintf("%dG", volumeInfoRes.Size)) + newVolumeSize := resource.MustParse(fmt.Sprintf("%dGi", volumeInfoRes.Size)) return newVolumeSize, nil } diff --git a/pkg/volume/glusterfs/glusterfs_test.go b/pkg/volume/glusterfs/glusterfs_test.go index ba8a2fb3726..716986acd27 100644 --- a/pkg/volume/glusterfs/glusterfs_test.go +++ b/pkg/volume/glusterfs/glusterfs_test.go @@ -73,20 +73,11 @@ func TestGetAccessModes(t *testing.T) { if err != nil { t.Errorf("Can't find the plugin by name") } - if !contains(plug.GetAccessModes(), v1.ReadWriteOnce) || !contains(plug.GetAccessModes(), v1.ReadOnlyMany) || !contains(plug.GetAccessModes(), v1.ReadWriteMany) { + if !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteOnce) || !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadOnlyMany) || !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteMany) { t.Errorf("Expected three AccessModeTypes: %s, %s, and %s", v1.ReadWriteOnce, v1.ReadOnlyMany, v1.ReadWriteMany) } } -func contains(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool { - for _, m := range modes { - if m == mode { - return true - } - } - return false -} - func doTestPlugin(t *testing.T, spec *volume.Spec) { tmpDir, err := utiltesting.MkTmpdir("glusterfs_test") if err != nil { @@ -138,7 +129,7 @@ func doTestPlugin(t *testing.T, spec *volume.Spec) { if _, err := os.Stat(volumePath); err == nil { t.Errorf("TearDown() failed, volume path still exists: %s", volumePath) } else if !os.IsNotExist(err) { - t.Errorf("SetUp() failed: %v", err) + t.Errorf("TearDown() failed: %v", err) } } diff --git a/pkg/volume/host_path/BUILD b/pkg/volume/host_path/BUILD index b158f6de8b1..4297d1b19e5 100644 --- a/pkg/volume/host_path/BUILD +++ b/pkg/volume/host_path/BUILD @@ -28,8 +28,8 @@ go_library( go_test( name = "go_default_test", srcs = ["host_path_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/host_path", - library = ":go_default_library", deps = [ "//pkg/util/file:go_default_library", "//pkg/util/mount:go_default_library", diff --git a/pkg/volume/host_path/OWNERS b/pkg/volume/host_path/OWNERS index 77e285d35a6..2a4ad74aecd 100644 --- a/pkg/volume/host_path/OWNERS +++ b/pkg/volume/host_path/OWNERS @@ -3,13 +3,7 @@ approvers: - saad-ali - thockin reviewers: -- thockin -- smarterclayton -- brendandburns -- derekwaynecarr -- pmorie - saad-ali -- justinsb - jsafrane - rootfs - jingxu97 diff --git a/pkg/volume/iscsi/BUILD b/pkg/volume/iscsi/BUILD index 8e75e24cae8..e056ff00889 100644 --- a/pkg/volume/iscsi/BUILD +++ b/pkg/volume/iscsi/BUILD @@ -23,6 +23,7 @@ go_library( "//pkg/volume/util:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", ], ) @@ -33,8 +34,8 @@ go_test( "iscsi_test.go", "iscsi_util_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/iscsi", - library = ":go_default_library", deps = [ "//pkg/util/mount:go_default_library", "//pkg/volume:go_default_library", diff --git a/pkg/volume/iscsi/OWNERS b/pkg/volume/iscsi/OWNERS index 99e2d8b366e..a7992dec652 100644 --- a/pkg/volume/iscsi/OWNERS +++ b/pkg/volume/iscsi/OWNERS @@ -3,13 +3,7 @@ approvers: - saad-ali - rootfs reviewers: -- thockin -- smarterclayton -- brendandburns -- derekwaynecarr -- pmorie - saad-ali -- justinsb - jsafrane - rootfs - humblec diff --git a/pkg/volume/iscsi/attacher.go b/pkg/volume/iscsi/attacher.go index 4cea774eaa4..b86b2f2499e 100644 --- a/pkg/volume/iscsi/attacher.go +++ b/pkg/volume/iscsi/attacher.go @@ -24,6 +24,7 @@ import ( "github.com/golang/glog" "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/kubernetes/pkg/util/mount" "k8s.io/kubernetes/pkg/volume" @@ -100,7 +101,7 @@ func (attacher *iscsiAttacher) MountDevice(spec *volume.Spec, devicePath string, return err } } - volumeSource, readOnly, err := getVolumeSource(spec) + readOnly, fsType, err := getISCSIVolumeInfo(spec) if err != nil { return err } @@ -112,7 +113,7 @@ func (attacher *iscsiAttacher) MountDevice(spec *volume.Spec, devicePath string, if notMnt { diskMounter := &mount.SafeFormatAndMount{Interface: mounter, Exec: attacher.host.GetExec(iscsiPluginName)} mountOptions := volume.MountOptionFromSpec(spec, options...) - err = diskMounter.FormatAndMount(devicePath, deviceMountPath, volumeSource.FSType, mountOptions) + err = diskMounter.FormatAndMount(devicePath, deviceMountPath, fsType, mountOptions) if err != nil { os.Remove(deviceMountPath) return err @@ -137,7 +138,7 @@ func (plugin *iscsiPlugin) NewDetacher() (volume.Detacher, error) { }, nil } -func (detacher *iscsiDetacher) Detach(deviceMountPath string, nodeName types.NodeName) error { +func (detacher *iscsiDetacher) Detach(volumeName string, nodeName types.NodeName) error { return nil } @@ -159,29 +160,75 @@ func (detacher *iscsiDetacher) UnmountDevice(deviceMountPath string) error { func (attacher *iscsiAttacher) volumeSpecToMounter(spec *volume.Spec, host volume.VolumeHost, pod *v1.Pod) (*iscsiDiskMounter, error) { var secret map[string]string var bkportal []string - iscsi, readOnly, err := getVolumeSource(spec) + readOnly, fsType, err := getISCSIVolumeInfo(spec) if err != nil { return nil, err } - // Obtain secret for AttachDisk - if iscsi.SecretRef != nil && pod != nil { - if secret, err = volumeutil.GetSecretForPod(pod, iscsi.SecretRef.Name, host.GetKubeClient()); err != nil { - glog.Errorf("Couldn't get secret from %v/%v", pod.Namespace, iscsi.SecretRef) + if pod != nil { + chapDiscovery, err := getISCSIDiscoveryCHAPInfo(spec) + if err != nil { return nil, err } + chapSession, err := getISCSISessionCHAPInfo(spec) + if err != nil { + return nil, err + } + if chapDiscovery || chapSession { + secretName, secretNamespace, err := getISCSISecretNameAndNamespace(spec, pod.Namespace) + if err != nil { + return nil, err + } + if len(secretNamespace) == 0 || len(secretName) == 0 { + return nil, fmt.Errorf("CHAP enabled but secret name or namespace is empty") + } + // if secret is provided, retrieve it + kubeClient := host.GetKubeClient() + if kubeClient == nil { + return nil, fmt.Errorf("Cannot get kube client") + } + secretObj, err := kubeClient.CoreV1().Secrets(secretNamespace).Get(secretName, metav1.GetOptions{}) + if err != nil { + err = fmt.Errorf("Couldn't get secret %v/%v error: %v", secretNamespace, secretName, err) + return nil, err + } + secret = make(map[string]string) + for name, data := range secretObj.Data { + glog.V(6).Infof("retrieving CHAP secret name: %s", name) + secret[name] = string(data) + } + } + } - lun := strconv.Itoa(int(iscsi.Lun)) - portal := portalMounter(iscsi.TargetPortal) + tp, portals, iqn, lunStr, err := getISCSITargetInfo(spec) + if err != nil { + return nil, err + } + + lun := strconv.Itoa(int(lunStr)) + portal := portalMounter(tp) bkportal = append(bkportal, portal) - for _, tp := range iscsi.Portals { - bkportal = append(bkportal, portalMounter(string(tp))) + for _, p := range portals { + bkportal = append(bkportal, portalMounter(string(p))) } - iface := iscsi.ISCSIInterface - exec := attacher.host.GetExec(iscsiPluginName) + + iface, initiatorNamePtr, err := getISCSIInitiatorInfo(spec) + if err != nil { + return nil, err + } + var initiatorName string - if iscsi.InitiatorName != nil { - initiatorName = *iscsi.InitiatorName + if initiatorNamePtr != nil { + initiatorName = *initiatorNamePtr } + chapDiscovery, err := getISCSIDiscoveryCHAPInfo(spec) + if err != nil { + return nil, err + } + chapSession, err := getISCSISessionCHAPInfo(spec) + if err != nil { + return nil, err + } + exec := attacher.host.GetExec(iscsiPluginName) return &iscsiDiskMounter{ iscsiDisk: &iscsiDisk{ @@ -190,15 +237,15 @@ func (attacher *iscsiAttacher) volumeSpecToMounter(spec *volume.Spec, host volum }, VolName: spec.Name(), Portals: bkportal, - Iqn: iscsi.IQN, + Iqn: iqn, lun: lun, Iface: iface, - chap_discovery: iscsi.DiscoveryCHAPAuth, - chap_session: iscsi.SessionCHAPAuth, + chap_discovery: chapDiscovery, + chap_session: chapSession, secret: secret, InitiatorName: initiatorName, manager: &ISCSIUtil{}}, - fsType: iscsi.FSType, + fsType: fsType, readOnly: readOnly, mounter: &mount.SafeFormatAndMount{Interface: host.GetMounter(iscsiPluginName), Exec: exec}, exec: exec, diff --git a/pkg/volume/iscsi/iscsi.go b/pkg/volume/iscsi/iscsi.go index 055fbf8a9c1..4ea6e792ef8 100644 --- a/pkg/volume/iscsi/iscsi.go +++ b/pkg/volume/iscsi/iscsi.go @@ -23,6 +23,7 @@ import ( "github.com/golang/glog" "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/kubernetes/pkg/util/mount" utilstrings "k8s.io/kubernetes/pkg/util/strings" @@ -56,16 +57,12 @@ func (plugin *iscsiPlugin) GetPluginName() string { } func (plugin *iscsiPlugin) GetVolumeName(spec *volume.Spec) (string, error) { - volumeSource, _, err := getVolumeSource(spec) + tp, _, iqn, lun, err := getISCSITargetInfo(spec) if err != nil { return "", err } - return fmt.Sprintf( - "%v:%v:%v", - volumeSource.TargetPortal, - volumeSource.IQN, - volumeSource.Lun), nil + return fmt.Sprintf("%v:%v:%v", tp, iqn, lun), nil } func (plugin *iscsiPlugin) CanSupport(spec *volume.Spec) bool { @@ -98,41 +95,80 @@ func (plugin *iscsiPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { func (plugin *iscsiPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) { // Inject real implementations here, test through the internal function. var secret map[string]string - source, _, err := getVolumeSource(spec) + if pod == nil { + return nil, fmt.Errorf("nil pod") + } + chapDiscover, err := getISCSIDiscoveryCHAPInfo(spec) if err != nil { return nil, err } - - if source.SecretRef != nil { - if secret, err = ioutil.GetSecretForPod(pod, source.SecretRef.Name, plugin.host.GetKubeClient()); err != nil { - glog.Errorf("Couldn't get secret from %v/%v", pod.Namespace, source.SecretRef) + chapSession, err := getISCSISessionCHAPInfo(spec) + if err != nil { + return nil, err + } + if chapDiscover || chapSession { + secretName, secretNamespace, err := getISCSISecretNameAndNamespace(spec, pod.Namespace) + if err != nil { return nil, err } - } + if len(secretName) > 0 && len(secretNamespace) > 0 { + // if secret is provideded, retrieve it + kubeClient := plugin.host.GetKubeClient() + if kubeClient == nil { + return nil, fmt.Errorf("Cannot get kube client") + } + secretObj, err := kubeClient.CoreV1().Secrets(secretNamespace).Get(secretName, metav1.GetOptions{}) + if err != nil { + err = fmt.Errorf("Couldn't get secret %v/%v error: %v", secretNamespace, secretName, err) + return nil, err + } + secret = make(map[string]string) + for name, data := range secretObj.Data { + glog.V(4).Infof("retrieving CHAP secret name: %s", name) + secret[name] = string(data) + } + } + } return plugin.newMounterInternal(spec, pod.UID, &ISCSIUtil{}, plugin.host.GetMounter(plugin.GetPluginName()), plugin.host.GetExec(plugin.GetPluginName()), secret) } func (plugin *iscsiPlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager diskManager, mounter mount.Interface, exec mount.Exec, secret map[string]string) (volume.Mounter, error) { // iscsi volumes used directly in a pod have a ReadOnly flag set by the pod author. // iscsi volumes used as a PersistentVolume gets the ReadOnly flag indirectly through the persistent-claim volume used to mount the PV - iscsi, readOnly, err := getVolumeSource(spec) + readOnly, fsType, err := getISCSIVolumeInfo(spec) + if err != nil { + return nil, err + } + tp, portals, iqn, lunStr, err := getISCSITargetInfo(spec) if err != nil { return nil, err } - lun := strconv.Itoa(int(iscsi.Lun)) - portal := portalMounter(iscsi.TargetPortal) + lun := strconv.Itoa(int(lunStr)) + portal := portalMounter(tp) var bkportal []string bkportal = append(bkportal, portal) - for _, tp := range iscsi.Portals { - bkportal = append(bkportal, portalMounter(string(tp))) + for _, p := range portals { + bkportal = append(bkportal, portalMounter(string(p))) + } + + iface, initiatorNamePtr, err := getISCSIInitiatorInfo(spec) + if err != nil { + return nil, err } - iface := iscsi.ISCSIInterface var initiatorName string - if iscsi.InitiatorName != nil { - initiatorName = *iscsi.InitiatorName + if initiatorNamePtr != nil { + initiatorName = *initiatorNamePtr + } + chapDiscovery, err := getISCSIDiscoveryCHAPInfo(spec) + if err != nil { + return nil, err + } + chapSession, err := getISCSISessionCHAPInfo(spec) + if err != nil { + return nil, err } return &iscsiDiskMounter{ @@ -140,16 +176,16 @@ func (plugin *iscsiPlugin) newMounterInternal(spec *volume.Spec, podUID types.UI podUID: podUID, VolName: spec.Name(), Portals: bkportal, - Iqn: iscsi.IQN, + Iqn: iqn, lun: lun, Iface: iface, - chap_discovery: iscsi.DiscoveryCHAPAuth, - chap_session: iscsi.SessionCHAPAuth, + chap_discovery: chapDiscovery, + chap_session: chapSession, secret: secret, InitiatorName: initiatorName, manager: manager, plugin: plugin}, - fsType: iscsi.FSType, + fsType: fsType, readOnly: readOnly, mounter: &mount.SafeFormatAndMount{Interface: mounter, Exec: exec}, exec: exec, @@ -277,13 +313,87 @@ func portalMounter(portal string) string { return portal } -func getVolumeSource(spec *volume.Spec) (*v1.ISCSIVolumeSource, bool, error) { +// get iSCSI volume info: readOnly and fstype +func getISCSIVolumeInfo(spec *volume.Spec) (bool, string, error) { + // for volume source, readonly is in volume spec + // for PV, readonly is in PV spec if spec.Volume != nil && spec.Volume.ISCSI != nil { - return spec.Volume.ISCSI, spec.Volume.ISCSI.ReadOnly, nil + return spec.Volume.ISCSI.ReadOnly, spec.Volume.ISCSI.FSType, nil } else if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.ISCSI != nil { - return spec.PersistentVolume.Spec.ISCSI, spec.ReadOnly, nil + return spec.ReadOnly, spec.PersistentVolume.Spec.ISCSI.FSType, nil } - return nil, false, fmt.Errorf("Spec does not reference an ISCSI volume type") + return false, "", fmt.Errorf("Spec does not reference an ISCSI volume type") +} + +// get iSCSI target info: target portal, portals, iqn, and lun +func getISCSITargetInfo(spec *volume.Spec) (string, []string, string, int32, error) { + if spec.Volume != nil && spec.Volume.ISCSI != nil { + return spec.Volume.ISCSI.TargetPortal, spec.Volume.ISCSI.Portals, spec.Volume.ISCSI.IQN, spec.Volume.ISCSI.Lun, nil + } else if spec.PersistentVolume != nil && + spec.PersistentVolume.Spec.ISCSI != nil { + return spec.PersistentVolume.Spec.ISCSI.TargetPortal, spec.PersistentVolume.Spec.ISCSI.Portals, spec.PersistentVolume.Spec.ISCSI.IQN, spec.PersistentVolume.Spec.ISCSI.Lun, nil + } + + return "", nil, "", 0, fmt.Errorf("Spec does not reference an ISCSI volume type") +} + +// get iSCSI initiator info: iface and initiator name +func getISCSIInitiatorInfo(spec *volume.Spec) (string, *string, error) { + if spec.Volume != nil && spec.Volume.ISCSI != nil { + return spec.Volume.ISCSI.ISCSIInterface, spec.Volume.ISCSI.InitiatorName, nil + } else if spec.PersistentVolume != nil && + spec.PersistentVolume.Spec.ISCSI != nil { + return spec.PersistentVolume.Spec.ISCSI.ISCSIInterface, spec.PersistentVolume.Spec.ISCSI.InitiatorName, nil + } + + return "", nil, fmt.Errorf("Spec does not reference an ISCSI volume type") +} + +// get iSCSI Discovery CHAP boolean +func getISCSIDiscoveryCHAPInfo(spec *volume.Spec) (bool, error) { + if spec.Volume != nil && spec.Volume.ISCSI != nil { + return spec.Volume.ISCSI.DiscoveryCHAPAuth, nil + } else if spec.PersistentVolume != nil && + spec.PersistentVolume.Spec.ISCSI != nil { + return spec.PersistentVolume.Spec.ISCSI.DiscoveryCHAPAuth, nil + } + + return false, fmt.Errorf("Spec does not reference an ISCSI volume type") +} + +// get iSCSI Session CHAP boolean +func getISCSISessionCHAPInfo(spec *volume.Spec) (bool, error) { + if spec.Volume != nil && spec.Volume.ISCSI != nil { + return spec.Volume.ISCSI.SessionCHAPAuth, nil + } else if spec.PersistentVolume != nil && + spec.PersistentVolume.Spec.ISCSI != nil { + return spec.PersistentVolume.Spec.ISCSI.SessionCHAPAuth, nil + } + + return false, fmt.Errorf("Spec does not reference an ISCSI volume type") +} + +// get iSCSI CHAP Secret info: secret name and namespace +func getISCSISecretNameAndNamespace(spec *volume.Spec, defaultSecretNamespace string) (string, string, error) { + if spec.Volume != nil && spec.Volume.ISCSI != nil { + if spec.Volume.ISCSI.SecretRef != nil { + return spec.Volume.ISCSI.SecretRef.Name, defaultSecretNamespace, nil + } + return "", "", nil + } else if spec.PersistentVolume != nil && + spec.PersistentVolume.Spec.ISCSI != nil { + secretRef := spec.PersistentVolume.Spec.ISCSI.SecretRef + secretNs := defaultSecretNamespace + if secretRef != nil { + if len(secretRef.Namespace) != 0 { + secretNs = secretRef.Namespace + } + return secretRef.Name, secretNs, nil + } + return "", "", nil + } + + return "", "", fmt.Errorf("Spec does not reference an ISCSI volume type") } diff --git a/pkg/volume/iscsi/iscsi_test.go b/pkg/volume/iscsi/iscsi_test.go index dd034657939..831cd564395 100644 --- a/pkg/volume/iscsi/iscsi_test.go +++ b/pkg/volume/iscsi/iscsi_test.go @@ -67,20 +67,11 @@ func TestGetAccessModes(t *testing.T) { if err != nil { t.Errorf("Can't find the plugin by name") } - if !contains(plug.GetAccessModes(), v1.ReadWriteOnce) || !contains(plug.GetAccessModes(), v1.ReadOnlyMany) { + if !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteOnce) || !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadOnlyMany) { t.Errorf("Expected two AccessModeTypes: %s and %s", v1.ReadWriteOnce, v1.ReadOnlyMany) } } -func contains(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool { - for _, m := range modes { - if m == mode { - return true - } - } - return false -} - type fakeDiskManager struct { tmpDir string attachCalled bool @@ -188,7 +179,7 @@ func doTestPlugin(t *testing.T, spec *volume.Spec) { if _, err := os.Stat(path); err == nil { t.Errorf("TearDown() failed, volume path still exists: %s", path) } else if !os.IsNotExist(err) { - t.Errorf("SetUp() failed: %v", err) + t.Errorf("TearDown() failed: %v", err) } } @@ -214,7 +205,7 @@ func TestPluginPersistentVolume(t *testing.T) { }, Spec: v1.PersistentVolumeSpec{ PersistentVolumeSource: v1.PersistentVolumeSource{ - ISCSI: &v1.ISCSIVolumeSource{ + ISCSI: &v1.ISCSIPersistentVolumeSource{ TargetPortal: "127.0.0.1:3260", IQN: "iqn.2014-12.server:storage.target01", FSType: "ext4", @@ -239,7 +230,7 @@ func TestPersistentClaimReadOnlyFlag(t *testing.T) { }, Spec: v1.PersistentVolumeSpec{ PersistentVolumeSource: v1.PersistentVolumeSource{ - ISCSI: &v1.ISCSIVolumeSource{ + ISCSI: &v1.ISCSIPersistentVolumeSource{ TargetPortal: "127.0.0.1:3260", IQN: "iqn.2014-12.server:storage.target01", FSType: "ext4", @@ -292,3 +283,146 @@ func TestPortalMounter(t *testing.T) { t.Errorf("wrong portal: %s", portal) } } + +type testcase struct { + name string + defaultNs string + spec *volume.Spec + // Expected return of the test + expectedName string + expectedNs string + expectedIface string + expectedError error +} + +func TestGetSecretNameAndNamespaceForPV(t *testing.T) { + tests := []testcase{ + { + name: "persistent volume source", + defaultNs: "default", + spec: &volume.Spec{ + PersistentVolume: &v1.PersistentVolume{ + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + ISCSI: &v1.ISCSIPersistentVolumeSource{ + TargetPortal: "127.0.0.1:3260", + IQN: "iqn.2014-12.server:storage.target01", + FSType: "ext4", + Lun: 0, + SecretRef: &v1.SecretReference{ + Name: "name", + Namespace: "ns", + }, + }, + }, + }, + }, + }, + expectedName: "name", + expectedNs: "ns", + expectedError: nil, + }, + { + name: "persistent volume source without namespace", + defaultNs: "default", + spec: &volume.Spec{ + PersistentVolume: &v1.PersistentVolume{ + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + ISCSI: &v1.ISCSIPersistentVolumeSource{ + TargetPortal: "127.0.0.1:3260", + IQN: "iqn.2014-12.server:storage.target01", + FSType: "ext4", + Lun: 0, + SecretRef: &v1.SecretReference{ + Name: "name", + }, + }, + }, + }, + }, + }, + expectedName: "name", + expectedNs: "default", + expectedError: nil, + }, + { + name: "pod volume source", + defaultNs: "default", + spec: &volume.Spec{ + Volume: &v1.Volume{ + VolumeSource: v1.VolumeSource{ + ISCSI: &v1.ISCSIVolumeSource{ + TargetPortal: "127.0.0.1:3260", + IQN: "iqn.2014-12.server:storage.target01", + FSType: "ext4", + Lun: 0, + }, + }, + }, + }, + expectedName: "", + expectedNs: "", + expectedError: nil, + }, + } + for _, testcase := range tests { + resultName, resultNs, err := getISCSISecretNameAndNamespace(testcase.spec, testcase.defaultNs) + if err != testcase.expectedError || resultName != testcase.expectedName || resultNs != testcase.expectedNs { + t.Errorf("%s failed: expected err=%v ns=%q name=%q, got %v/%q/%q", testcase.name, testcase.expectedError, testcase.expectedNs, testcase.expectedName, + err, resultNs, resultName) + } + } + +} + +func TestGetISCSIInitiatorInfo(t *testing.T) { + tests := []testcase{ + { + name: "persistent volume source", + spec: &volume.Spec{ + PersistentVolume: &v1.PersistentVolume{ + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + ISCSI: &v1.ISCSIPersistentVolumeSource{ + TargetPortal: "127.0.0.1:3260", + IQN: "iqn.2014-12.server:storage.target01", + FSType: "ext4", + Lun: 0, + ISCSIInterface: "tcp", + }, + }, + }, + }, + }, + expectedIface: "tcp", + expectedError: nil, + }, + { + name: "pod volume source", + spec: &volume.Spec{ + Volume: &v1.Volume{ + VolumeSource: v1.VolumeSource{ + ISCSI: &v1.ISCSIVolumeSource{ + TargetPortal: "127.0.0.1:3260", + IQN: "iqn.2014-12.server:storage.target01", + FSType: "ext4", + Lun: 0, + ISCSIInterface: "tcp", + }, + }, + }, + }, + expectedIface: "tcp", + expectedError: nil, + }, + } + for _, testcase := range tests { + resultIface, _, err := getISCSIInitiatorInfo(testcase.spec) + if err != testcase.expectedError || resultIface != testcase.expectedIface { + t.Errorf("%s failed: expected err=%v iface=%s, got %v/%s", testcase.name, testcase.expectedError, testcase.expectedIface, + err, resultIface) + } + } + +} diff --git a/pkg/volume/iscsi/iscsi_util.go b/pkg/volume/iscsi/iscsi_util.go index 46f9d8fe313..12e8430d85e 100644 --- a/pkg/volume/iscsi/iscsi_util.go +++ b/pkg/volume/iscsi/iscsi_util.go @@ -43,6 +43,8 @@ var ( "node.session.auth.password", "node.session.auth.username_in", "node.session.auth.password_in"} + ifaceTransportNameRe = regexp.MustCompile(`iface.transport_name = (.*)\n`) + ifaceRe = regexp.MustCompile(`.+/iface-([^/]+)/.+`) ) func updateISCSIDiscoverydb(b iscsiDiskMounter, tp string) error { @@ -277,6 +279,12 @@ func (util *ISCSIUtil) AttachDisk(b iscsiDiskMounter) (string, error) { lastErr = fmt.Errorf("iscsi: failed to attach disk: Error: %s (%v)", string(out), err) continue } + // in case of node failure/restart, explicitly set to manual login so it doesn't hang on boot + out, err = b.exec.Run("iscsiadm", "-m", "node", "-p", tp, "-T", b.Iqn, "-o", "update", "node.startup", "-v", "manual") + if err != nil { + // don't fail if we can't set startup mode, but log warning so there is a clue + glog.Warningf("Warning: Failed to set iSCSI login mode to manual. Error: %v", err) + } if exist := waitForPathToExist(&devicePath, 10, iscsiTransport); !exist { glog.Errorf("Could not attach disk: Timeout after 10s") // update last error @@ -429,9 +437,7 @@ func (util *ISCSIUtil) DetachDisk(c iscsiDiskUnmounter, mntPath string) error { } func extractTransportname(ifaceOutput string) (iscsiTransport string) { - re := regexp.MustCompile(`iface.transport_name = (.*)\n`) - - rexOutput := re.FindStringSubmatch(ifaceOutput) + rexOutput := ifaceTransportNameRe.FindStringSubmatch(ifaceOutput) if rexOutput == nil { return "" } @@ -460,9 +466,7 @@ func extractDeviceAndPrefix(mntPath string) (string, string, error) { } func extractIface(mntPath string) (string, bool) { - re := regexp.MustCompile(`.+/iface-([^/]+)/.+`) - - reOutput := re.FindStringSubmatch(mntPath) + reOutput := ifaceRe.FindStringSubmatch(mntPath) if reOutput != nil { return reOutput[1], true } diff --git a/pkg/volume/local/BUILD b/pkg/volume/local/BUILD index 8c8d3b1d8a7..477e5adb88d 100644 --- a/pkg/volume/local/BUILD +++ b/pkg/volume/local/BUILD @@ -33,8 +33,8 @@ go_library( go_test( name = "go_default_test", srcs = ["local_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/local", - library = ":go_default_library", deps = [ "//pkg/volume:go_default_library", "//pkg/volume/testing:go_default_library", diff --git a/pkg/volume/local/OWNERS b/pkg/volume/local/OWNERS index 558224cf9a4..c995ab66a9e 100644 --- a/pkg/volume/local/OWNERS +++ b/pkg/volume/local/OWNERS @@ -6,13 +6,7 @@ approvers: - jingxu97 - jsafrane reviewers: -- thockin -- smarterclayton -- brendandburns -- derekwaynecarr -- pmorie - saad-ali -- justinsb - jsafrane - rootfs - jingxu97 diff --git a/pkg/volume/local/local_test.go b/pkg/volume/local/local_test.go index 87fb829c493..5ad6c933f54 100644 --- a/pkg/volume/local/local_test.go +++ b/pkg/volume/local/local_test.go @@ -93,15 +93,6 @@ func getTestVolume(readOnly bool, path string) *volume.Spec { return volume.NewSpecFromPersistentVolume(pv, readOnly) } -func contains(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool { - for _, m := range modes { - if m == mode { - return true - } - } - return false -} - func TestCanSupport(t *testing.T) { tmpDir, plug := getPlugin(t) defer os.RemoveAll(tmpDir) @@ -116,14 +107,14 @@ func TestGetAccessModes(t *testing.T) { defer os.RemoveAll(tmpDir) modes := plug.GetAccessModes() - if !contains(modes, v1.ReadWriteOnce) { + if !volumetest.ContainsAccessMode(modes, v1.ReadWriteOnce) { t.Errorf("Expected AccessModeType %q", v1.ReadWriteOnce) } - if contains(modes, v1.ReadWriteMany) { + if volumetest.ContainsAccessMode(modes, v1.ReadWriteMany) { t.Errorf("Found AccessModeType %q, expected not", v1.ReadWriteMany) } - if contains(modes, v1.ReadOnlyMany) { + if volumetest.ContainsAccessMode(modes, v1.ReadOnlyMany) { t.Errorf("Found AccessModeType %q, expected not", v1.ReadOnlyMany) } } @@ -202,7 +193,7 @@ func TestMountUnmount(t *testing.T) { if _, err := os.Stat(path); err == nil { t.Errorf("TearDown() failed, volume path still exists: %s", path) } else if !os.IsNotExist(err) { - t.Errorf("SetUp() failed: %v", err) + t.Errorf("TearDown() failed: %v", err) } } diff --git a/pkg/volume/nfs/BUILD b/pkg/volume/nfs/BUILD index ad091ffb4d7..8a3253f8e0f 100644 --- a/pkg/volume/nfs/BUILD +++ b/pkg/volume/nfs/BUILD @@ -28,8 +28,8 @@ go_library( go_test( name = "go_default_test", srcs = ["nfs_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/nfs", - library = ":go_default_library", deps = [ "//pkg/util/mount:go_default_library", "//pkg/volume:go_default_library", diff --git a/pkg/volume/nfs/OWNERS b/pkg/volume/nfs/OWNERS index bd1a85144f0..025423a9fb6 100644 --- a/pkg/volume/nfs/OWNERS +++ b/pkg/volume/nfs/OWNERS @@ -4,13 +4,7 @@ approvers: - jingxu97 reviewers: - sjenning -- thockin -- smarterclayton -- brendandburns -- derekwaynecarr -- pmorie - saad-ali -- justinsb - jsafrane - rootfs - jingxu97 diff --git a/pkg/volume/nfs/nfs.go b/pkg/volume/nfs/nfs.go index 1435db92815..f61fbd25944 100644 --- a/pkg/volume/nfs/nfs.go +++ b/pkg/volume/nfs/nfs.go @@ -194,15 +194,15 @@ func (nfsMounter *nfsMounter) CanMount() error { exec := nfsMounter.plugin.host.GetExec(nfsMounter.plugin.GetPluginName()) switch runtime.GOOS { case "linux": - if _, err := exec.Run("/bin/ls", "/sbin/mount.nfs"); err != nil { + if _, err := exec.Run("test", "-x", "/sbin/mount.nfs"); err != nil { return fmt.Errorf("Required binary /sbin/mount.nfs is missing") } - if _, err := exec.Run("/bin/ls", "/sbin/mount.nfs4"); err != nil { + if _, err := exec.Run("test", "-x", "/sbin/mount.nfs4"); err != nil { return fmt.Errorf("Required binary /sbin/mount.nfs4 is missing") } return nil case "darwin": - if _, err := exec.Run("/bin/ls", "/sbin/mount_nfs"); err != nil { + if _, err := exec.Run("test", "-x", "/sbin/mount_nfs"); err != nil { return fmt.Errorf("Required binary /sbin/mount_nfs is missing") } } diff --git a/pkg/volume/nfs/nfs_test.go b/pkg/volume/nfs/nfs_test.go index 2cb83235b59..924978454b3 100644 --- a/pkg/volume/nfs/nfs_test.go +++ b/pkg/volume/nfs/nfs_test.go @@ -73,7 +73,7 @@ func TestGetAccessModes(t *testing.T) { if err != nil { t.Errorf("Can't find the plugin by name") } - if !contains(plug.GetAccessModes(), v1.ReadWriteOnce) || !contains(plug.GetAccessModes(), v1.ReadOnlyMany) || !contains(plug.GetAccessModes(), v1.ReadWriteMany) { + if !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteOnce) || !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadOnlyMany) || !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteMany) { t.Errorf("Expected three AccessModeTypes: %s, %s, and %s", v1.ReadWriteOnce, v1.ReadOnlyMany, v1.ReadWriteMany) } } @@ -95,15 +95,6 @@ func TestRecycler(t *testing.T) { } } -func contains(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool { - for _, m := range modes { - if m == mode { - return true - } - } - return false -} - func doTestPlugin(t *testing.T, spec *volume.Spec) { tmpDir, err := utiltesting.MkTmpdir("nfs_test") if err != nil { @@ -120,17 +111,16 @@ func doTestPlugin(t *testing.T, spec *volume.Spec) { fake := &mount.FakeMounter{} pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("poduid")}} mounter, err := plug.(*nfsPlugin).newMounterInternal(spec, pod, fake) - volumePath := mounter.GetPath() if err != nil { t.Errorf("Failed to make a new Mounter: %v", err) } if mounter == nil { t.Errorf("Got a nil Mounter") } - path := mounter.GetPath() + volumePath := mounter.GetPath() expectedPath := fmt.Sprintf("%s/pods/poduid/volumes/kubernetes.io~nfs/vol1", tmpDir) - if path != expectedPath { - t.Errorf("Unexpected path, expected %q, got: %q", expectedPath, path) + if volumePath != expectedPath { + t.Errorf("Unexpected path, expected %q, got: %q", expectedPath, volumePath) } if err := mounter.SetUp(nil); err != nil { t.Errorf("Expected success, got: %v", err) @@ -167,13 +157,13 @@ func doTestPlugin(t *testing.T, spec *volume.Spec) { if _, err := os.Stat(volumePath); err == nil { t.Errorf("TearDown() failed, volume path still exists: %s", volumePath) } else if !os.IsNotExist(err) { - t.Errorf("SetUp() failed: %v", err) + t.Errorf("TearDown() failed: %v", err) } if len(fake.Log) != 1 { t.Errorf("Unmount was not called exactly one time. It was called %d times.", len(fake.Log)) } else { if fake.Log[0].Action != mount.FakeActionUnmount { - t.Errorf("Unexpected mounter action: %#v", fake.Log[0]) + t.Errorf("Unexpected unmounter action: %#v", fake.Log[0]) } } diff --git a/pkg/volume/photon_pd/BUILD b/pkg/volume/photon_pd/BUILD index bc72ab05def..3d6dc74f2c1 100644 --- a/pkg/volume/photon_pd/BUILD +++ b/pkg/volume/photon_pd/BUILD @@ -36,8 +36,8 @@ go_test( "attacher_test.go", "photon_pd_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/photon_pd", - library = ":go_default_library", deps = [ "//pkg/cloudprovider/providers/photon:go_default_library", "//pkg/util/mount:go_default_library", diff --git a/pkg/volume/photon_pd/attacher.go b/pkg/volume/photon_pd/attacher.go index 4af726716e6..c3938269caa 100644 --- a/pkg/volume/photon_pd/attacher.go +++ b/pkg/volume/photon_pd/attacher.go @@ -243,10 +243,10 @@ func (plugin *photonPersistentDiskPlugin) NewDetacher() (volume.Detacher, error) } // Detach the given device from the given host. -func (detacher *photonPersistentDiskDetacher) Detach(deviceMountPath string, nodeName types.NodeName) error { +func (detacher *photonPersistentDiskDetacher) Detach(volumeName string, nodeName types.NodeName) error { hostName := string(nodeName) - pdID := deviceMountPath + pdID := volumeName attached, err := detacher.photonDisks.DiskIsAttached(pdID, nodeName) if err != nil { // Log error and continue with detach diff --git a/pkg/volume/photon_pd/photon_pd_test.go b/pkg/volume/photon_pd/photon_pd_test.go index 719727307e5..290fd3eef08 100644 --- a/pkg/volume/photon_pd/photon_pd_test.go +++ b/pkg/volume/photon_pd/photon_pd_test.go @@ -68,23 +68,14 @@ func TestGetAccessModes(t *testing.T) { t.Errorf("Can't find the plugin by name") } - if !contains(plug.GetAccessModes(), v1.ReadWriteOnce) { + if !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteOnce) { t.Errorf("Expected to support AccessModeTypes: %s", v1.ReadWriteOnce) } - if contains(plug.GetAccessModes(), v1.ReadOnlyMany) { + if volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadOnlyMany) { t.Errorf("Expected not to support AccessModeTypes: %s", v1.ReadOnlyMany) } } -func contains(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool { - for _, m := range modes { - if m == mode { - return true - } - } - return false -} - type fakePDManager struct { } @@ -170,7 +161,7 @@ func TestPlugin(t *testing.T) { if _, err := os.Stat(path); err == nil { t.Errorf("TearDown() failed, volume path still exists: %s", path) } else if !os.IsNotExist(err) { - t.Errorf("SetUp() failed: %v", err) + t.Errorf("TearDown() failed: %v", err) } // Test Provisioner diff --git a/pkg/volume/plugins.go b/pkg/volume/plugins.go index 334578b4d1c..42ceaa40d2f 100644 --- a/pkg/volume/plugins.go +++ b/pkg/volume/plugins.go @@ -208,6 +208,26 @@ type ExpandableVolumePlugin interface { RequiresFSResize() bool } +// BlockVolumePlugin is an extend interface of VolumePlugin and is used for block volumes support. +type BlockVolumePlugin interface { + VolumePlugin + // NewBlockVolumeMapper creates a new volume.BlockVolumeMapper from an API specification. + // Ownership of the spec pointer in *not* transferred. + // - spec: The v1.Volume spec + // - pod: The enclosing pod + NewBlockVolumeMapper(spec *Spec, podRef *v1.Pod, opts VolumeOptions) (BlockVolumeMapper, error) + // NewBlockVolumeUnmapper creates a new volume.BlockVolumeUnmapper from recoverable state. + // - name: The volume name, as per the v1.Volume spec. + // - podUID: The UID of the enclosing pod + NewBlockVolumeUnmapper(name string, podUID types.UID) (BlockVolumeUnmapper, error) + // ConstructBlockVolumeSpec constructs a volume spec based on the given + // podUID, volume name and a pod device map path. + // The spec may have incomplete information due to limited information + // from input. This function is used by volume manager to reconstruct + // volume spec by reading the volume directories from disk. + ConstructBlockVolumeSpec(podUID types.UID, volumeName, mountPath string) (*Spec, error) +} + // VolumeHost is an interface that plugins can use to access the kubelet. type VolumeHost interface { // GetPluginDir returns the absolute path to a directory under which @@ -216,6 +236,11 @@ type VolumeHost interface { // GetPodPluginDir(). GetPluginDir(pluginName string) string + // GetVolumeDevicePluginDir returns the absolute path to a directory + // under which a given plugin may store data. + // ex. plugins/kubernetes.io/{PluginName}/{DefaultKubeletVolumeDevicesDirName}/{volumePluginDependentPath}/ + GetVolumeDevicePluginDir(pluginName string) string + // GetPodVolumeDir returns the absolute path a directory which // represents the named volume under the named plugin for the given // pod. If the specified pod does not exist, the result of this call @@ -228,6 +253,13 @@ type VolumeHost interface { // directory might not actually exist on disk yet. GetPodPluginDir(podUID types.UID, pluginName string) string + // GetPodVolumeDeviceDir returns the absolute path a directory which + // represents the named plugin for the given pod. + // If the specified pod does not exist, the result of this call + // might not exist. + // ex. pods/{podUid}/{DefaultKubeletVolumeDevicesDirName}/{escapeQualifiedPluginName}/ + GetPodVolumeDeviceDir(podUID types.UID, pluginName string) string + // GetKubeClient returns a client interface GetKubeClient() clientset.Interface @@ -271,6 +303,9 @@ type VolumeHost interface { // Returns the labels on the node GetNodeLabels() (map[string]string, error) + + // Returns the name of the node + GetNodeName() types.NodeName } // VolumePluginMgr tracks registered plugins. @@ -675,6 +710,32 @@ func (pm *VolumePluginMgr) FindExpandablePluginByName(name string) (ExpandableVo return nil, nil } +// FindMapperPluginBySpec fetches a block volume plugin by spec. +func (pm *VolumePluginMgr) FindMapperPluginBySpec(spec *Spec) (BlockVolumePlugin, error) { + volumePlugin, err := pm.FindPluginBySpec(spec) + if err != nil { + return nil, err + } + + if blockVolumePlugin, ok := volumePlugin.(BlockVolumePlugin); ok { + return blockVolumePlugin, nil + } + return nil, nil +} + +// FindMapperPluginByName fetches a block volume plugin by name. +func (pm *VolumePluginMgr) FindMapperPluginByName(name string) (BlockVolumePlugin, error) { + volumePlugin, err := pm.FindPluginByName(name) + if err != nil { + return nil, err + } + + if blockVolumePlugin, ok := volumePlugin.(BlockVolumePlugin); ok { + return blockVolumePlugin, nil + } + return nil, nil +} + // NewPersistentVolumeRecyclerPodTemplate creates a template for a recycler // pod. By default, a recycler pod simply runs "rm -rf" on a volume and tests // for emptiness. Most attributes of the template will be correct for most @@ -711,7 +772,7 @@ func NewPersistentVolumeRecyclerPodTemplate() *v1.Pod { Containers: []v1.Container{ { Name: "pv-recycler", - Image: "gcr.io/google_containers/busybox", + Image: "busybox:1.27", Command: []string{"/bin/sh"}, Args: []string{"-c", "test -e /scrub && rm -rf /scrub/..?* /scrub/.[!.]* /scrub/* && test -z \"$(ls -A /scrub)\" || exit 1"}, VolumeMounts: []v1.VolumeMount{ diff --git a/pkg/volume/portworx/BUILD b/pkg/volume/portworx/BUILD index d2f7dd633eb..6cf06417976 100644 --- a/pkg/volume/portworx/BUILD +++ b/pkg/volume/portworx/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["portworx_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/portworx", - library = ":go_default_library", deps = [ "//pkg/util/mount:go_default_library", "//pkg/volume:go_default_library", @@ -30,7 +30,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/volume/portworx", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/util/mount:go_default_library", "//pkg/util/strings:go_default_library", "//pkg/volume:go_default_library", diff --git a/pkg/volume/portworx/portworx_test.go b/pkg/volume/portworx/portworx_test.go index 5401b6384dc..074f25c47da 100644 --- a/pkg/volume/portworx/portworx_test.go +++ b/pkg/volume/portworx/portworx_test.go @@ -72,26 +72,17 @@ func TestGetAccessModes(t *testing.T) { t.Errorf("Can't find the plugin by name") } - if !contains(plug.GetAccessModes(), v1.ReadWriteOnce) { + if !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteOnce) { t.Errorf("Expected to support AccessModeTypes: %s", v1.ReadWriteOnce) } - if !contains(plug.GetAccessModes(), v1.ReadWriteMany) { + if !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteMany) { t.Errorf("Expected to support AccessModeTypes: %s", v1.ReadWriteMany) } - if contains(plug.GetAccessModes(), v1.ReadOnlyMany) { + if volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadOnlyMany) { t.Errorf("Expected not to support AccessModeTypes: %s", v1.ReadOnlyMany) } } -func contains(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool { - for _, m := range modes { - if m == mode { - return true - } - } - return false -} - type fakePortworxManager struct { attachCalled bool mountCalled bool diff --git a/pkg/volume/portworx/portworx_util.go b/pkg/volume/portworx/portworx_util.go index cf7ddf8edb9..60677df2c9a 100644 --- a/pkg/volume/portworx/portworx_util.go +++ b/pkg/volume/portworx/portworx_util.go @@ -25,7 +25,7 @@ import ( volumeapi "github.com/libopenstorage/openstorage/volume" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/volume" ) diff --git a/pkg/volume/projected/BUILD b/pkg/volume/projected/BUILD index 219b8aa59d4..4e9511aa644 100644 --- a/pkg/volume/projected/BUILD +++ b/pkg/volume/projected/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["projected_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/projected", - library = ":go_default_library", deps = [ "//pkg/volume:go_default_library", "//pkg/volume/empty_dir:go_default_library", diff --git a/pkg/volume/projected/projected_test.go b/pkg/volume/projected/projected_test.go index 6b825865239..ec07a797355 100644 --- a/pkg/volume/projected/projected_test.go +++ b/pkg/volume/projected/projected_test.go @@ -544,6 +544,48 @@ func TestCollectDataWithDownwardAPI(t *testing.T) { payload map[string]util.FileProjection success bool }{ + { + name: "annotation", + volumeFile: []v1.DownwardAPIVolumeFile{ + {Path: "annotation", FieldRef: &v1.ObjectFieldSelector{ + FieldPath: "metadata.annotations['a1']"}}}, + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: testPodName, + Namespace: testNamespace, + Annotations: map[string]string{ + "a1": "value1", + "a2": "value2", + }, + UID: testPodUID}, + }, + mode: 0644, + payload: map[string]util.FileProjection{ + "annotation": {Data: []byte("value1"), Mode: 0644}, + }, + success: true, + }, + { + name: "annotation-error", + volumeFile: []v1.DownwardAPIVolumeFile{ + {Path: "annotation", FieldRef: &v1.ObjectFieldSelector{ + FieldPath: "metadata.annotations['']"}}}, + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: testPodName, + Namespace: testNamespace, + Annotations: map[string]string{ + "a1": "value1", + "a2": "value2", + }, + UID: testPodUID}, + }, + mode: 0644, + payload: map[string]util.FileProjection{ + "annotation": {Data: []byte("does-not-matter-because-this-test-case-will-fail-anyway"), Mode: 0644}, + }, + success: false, + }, { name: "labels", volumeFile: []v1.DownwardAPIVolumeFile{ @@ -1042,6 +1084,6 @@ func doTestCleanAndTeardown(plugin volume.VolumePlugin, podUID types.UID, testVo if _, err := os.Stat(volumePath); err == nil { t.Errorf("TearDown() failed, volume path still exists: %s", volumePath) } else if !os.IsNotExist(err) { - t.Errorf("SetUp() failed: %v", err) + t.Errorf("TearDown() failed: %v", err) } } diff --git a/pkg/volume/quobyte/BUILD b/pkg/volume/quobyte/BUILD index 7adf8b7d7aa..7b6ea3a0bae 100644 --- a/pkg/volume/quobyte/BUILD +++ b/pkg/volume/quobyte/BUILD @@ -33,8 +33,8 @@ go_library( go_test( name = "go_default_test", srcs = ["quobyte_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/quobyte", - library = ":go_default_library", deps = [ "//pkg/util/mount:go_default_library", "//pkg/volume:go_default_library", diff --git a/pkg/volume/quobyte/OWNERS b/pkg/volume/quobyte/OWNERS index 7e78589072f..331e3c38d44 100644 --- a/pkg/volume/quobyte/OWNERS +++ b/pkg/volume/quobyte/OWNERS @@ -3,13 +3,7 @@ approvers: - saad-ali reviewers: - johscheuer -- thockin -- smarterclayton -- brendandburns -- derekwaynecarr -- pmorie - saad-ali -- justinsb - jsafrane - rootfs - jingxu97 diff --git a/pkg/volume/quobyte/quobyte_test.go b/pkg/volume/quobyte/quobyte_test.go index 1ae60e4078b..61c061ad0f2 100644 --- a/pkg/volume/quobyte/quobyte_test.go +++ b/pkg/volume/quobyte/quobyte_test.go @@ -69,20 +69,11 @@ func TestGetAccessModes(t *testing.T) { if err != nil { t.Errorf("Can't find the plugin by name") } - if !contains(plug.GetAccessModes(), v1.ReadWriteOnce) || !contains(plug.GetAccessModes(), v1.ReadOnlyMany) || !contains(plug.GetAccessModes(), v1.ReadWriteMany) { + if !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteOnce) || !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadOnlyMany) || !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteMany) { t.Errorf("Expected three AccessModeTypes: %s, %s, and %s", v1.ReadWriteOnce, v1.ReadOnlyMany, v1.ReadWriteMany) } } -func contains(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool { - for _, m := range modes { - if m == mode { - return true - } - } - return false -} - func doTestPlugin(t *testing.T, spec *volume.Spec) { tmpDir, err := utiltesting.MkTmpdir("quobyte_test") if err != nil { diff --git a/pkg/volume/rbd/BUILD b/pkg/volume/rbd/BUILD index 2ead168a5c3..ea058ed7c67 100644 --- a/pkg/volume/rbd/BUILD +++ b/pkg/volume/rbd/BUILD @@ -9,6 +9,7 @@ load( go_library( name = "go_default_library", srcs = [ + "attacher.go", "disk_manager.go", "doc.go", "rbd.go", @@ -16,6 +17,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/volume/rbd", deps = [ + "//pkg/util/file:go_default_library", "//pkg/util/mount:go_default_library", "//pkg/util/node:go_default_library", "//pkg/util/strings:go_default_library", @@ -36,16 +38,17 @@ go_library( go_test( name = "go_default_test", srcs = ["rbd_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/rbd", - library = ":go_default_library", deps = [ "//pkg/util/mount:go_default_library", "//pkg/volume:go_default_library", "//pkg/volume/testing:go_default_library", - "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library", "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", "//vendor/k8s.io/client-go/util/testing:go_default_library", ], diff --git a/pkg/volume/rbd/OWNERS b/pkg/volume/rbd/OWNERS index d04766747bc..a700b703ce8 100644 --- a/pkg/volume/rbd/OWNERS +++ b/pkg/volume/rbd/OWNERS @@ -2,13 +2,7 @@ approvers: - rootfs reviewers: - sjenning -- thockin -- smarterclayton -- brendandburns -- derekwaynecarr -- pmorie - saad-ali -- justinsb - jsafrane - rootfs - jingxu97 diff --git a/pkg/volume/rbd/attacher.go b/pkg/volume/rbd/attacher.go new file mode 100644 index 00000000000..1454c231fb4 --- /dev/null +++ b/pkg/volume/rbd/attacher.go @@ -0,0 +1,225 @@ +/* +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 rbd + +import ( + "fmt" + "os" + "time" + + "github.com/golang/glog" + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/kubernetes/pkg/util/mount" + "k8s.io/kubernetes/pkg/volume" + volutil "k8s.io/kubernetes/pkg/volume/util" + "k8s.io/kubernetes/pkg/volume/util/volumehelper" +) + +// NewAttacher implements AttachableVolumePlugin.NewAttacher. +func (plugin *rbdPlugin) NewAttacher() (volume.Attacher, error) { + return plugin.newAttacherInternal(&RBDUtil{}) +} + +func (plugin *rbdPlugin) newAttacherInternal(manager diskManager) (volume.Attacher, error) { + return &rbdAttacher{ + plugin: plugin, + manager: manager, + mounter: volumehelper.NewSafeFormatAndMountFromHost(plugin.GetPluginName(), plugin.host), + }, nil +} + +// NewDetacher implements AttachableVolumePlugin.NewDetacher. +func (plugin *rbdPlugin) NewDetacher() (volume.Detacher, error) { + return plugin.newDetacherInternal(&RBDUtil{}) +} + +func (plugin *rbdPlugin) newDetacherInternal(manager diskManager) (volume.Detacher, error) { + return &rbdDetacher{ + plugin: plugin, + manager: manager, + mounter: volumehelper.NewSafeFormatAndMountFromHost(plugin.GetPluginName(), plugin.host), + }, nil +} + +// GetDeviceMountRefs implements AttachableVolumePlugin.GetDeviceMountRefs. +func (plugin *rbdPlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) { + mounter := plugin.host.GetMounter(plugin.GetPluginName()) + return mount.GetMountRefs(mounter, deviceMountPath) +} + +// rbdAttacher implements volume.Attacher interface. +type rbdAttacher struct { + plugin *rbdPlugin + mounter *mount.SafeFormatAndMount + manager diskManager +} + +var _ volume.Attacher = &rbdAttacher{} + +// Attach implements Attacher.Attach. +// We do not lock image here, because it requires kube-controller-manager to +// access external `rbd` utility. And there is no need since AttachDetach +// controller will not try to attach RWO volumes which are already attached to +// other nodes. +func (attacher *rbdAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string, error) { + return "", nil +} + +// VolumesAreAttached implements Attacher.VolumesAreAttached. +// There is no way to confirm whether the volume is attached or not from +// outside of the kubelet node. This method needs to return true always, like +// iSCSI, FC plugin. +func (attacher *rbdAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName types.NodeName) (map[*volume.Spec]bool, error) { + volumesAttachedCheck := make(map[*volume.Spec]bool) + for _, spec := range specs { + volumesAttachedCheck[spec] = true + } + return volumesAttachedCheck, nil +} + +// WaitForAttach implements Attacher.WaitForAttach. It's called by kublet to +// attach volume onto the node. +// This method is idempotent, callers are responsible for retrying on failure. +func (attacher *rbdAttacher) WaitForAttach(spec *volume.Spec, devicePath string, pod *v1.Pod, timeout time.Duration) (string, error) { + glog.V(4).Infof("rbd: waiting for attach volume (name: %s) for pod (name: %s, uid: %s)", spec.Name(), pod.Name, pod.UID) + mounter, err := attacher.plugin.createMounterFromVolumeSpecAndPod(spec, pod) + if err != nil { + glog.Warningf("failed to create mounter: %v", spec) + return "", err + } + realDevicePath, err := attacher.manager.AttachDisk(*mounter) + if err != nil { + return "", err + } + glog.V(3).Infof("rbd: successfully wait for attach volume (spec: %s, pool: %s, image: %s) at %s", spec.Name(), mounter.Pool, mounter.Image, realDevicePath) + return realDevicePath, nil +} + +// GetDeviceMountPath implements Attacher.GetDeviceMountPath. +func (attacher *rbdAttacher) GetDeviceMountPath(spec *volume.Spec) (string, error) { + img, err := getVolumeSourceImage(spec) + if err != nil { + return "", err + } + pool, err := getVolumeSourcePool(spec) + if err != nil { + return "", err + } + return makePDNameInternal(attacher.plugin.host, pool, img), nil +} + +// MountDevice implements Attacher.MountDevice. It is called by the kubelet to +// mount device at the given mount path. +// This method is idempotent, callers are responsible for retrying on failure. +func (attacher *rbdAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error { + glog.V(4).Infof("rbd: mouting device %s to %s", devicePath, deviceMountPath) + notMnt, err := attacher.mounter.IsLikelyNotMountPoint(deviceMountPath) + if err != nil { + if os.IsNotExist(err) { + if err := os.MkdirAll(deviceMountPath, 0750); err != nil { + return err + } + notMnt = true + } else { + return err + } + } + if !notMnt { + return nil + } + fstype, err := getVolumeSourceFSType(spec) + if err != nil { + return err + } + ro, err := getVolumeSourceReadOnly(spec) + if err != nil { + return err + } + options := []string{} + if ro { + options = append(options, "ro") + } + mountOptions := volume.MountOptionFromSpec(spec, options...) + err = attacher.mounter.FormatAndMount(devicePath, deviceMountPath, fstype, mountOptions) + if err != nil { + os.Remove(deviceMountPath) + return fmt.Errorf("rbd: failed to mount device %s at %s (fstype: %s), error %v", devicePath, deviceMountPath, fstype, err) + } + glog.V(3).Infof("rbd: successfully mount device %s at %s (fstype: %s)", devicePath, deviceMountPath, fstype) + return nil +} + +// rbdDetacher implements volume.Detacher interface. +type rbdDetacher struct { + plugin *rbdPlugin + manager diskManager + mounter *mount.SafeFormatAndMount +} + +var _ volume.Detacher = &rbdDetacher{} + +// UnmountDevice implements Detacher.UnmountDevice. It unmounts the global +// mount of the RBD image. This is called once all bind mounts have been +// unmounted. +// Internally, it does four things: +// - Unmount device from deviceMountPath. +// - Detach device from the node. +// - Remove lock if found. (No need to check volume readonly or not, because +// device is not on the node anymore, it's safe to remove lock.) +// - Remove the deviceMountPath at last. +// This method is idempotent, callers are responsible for retrying on failure. +func (detacher *rbdDetacher) UnmountDevice(deviceMountPath string) error { + if pathExists, pathErr := volutil.PathExists(deviceMountPath); pathErr != nil { + return fmt.Errorf("Error checking if path exists: %v", pathErr) + } else if !pathExists { + glog.Warningf("Warning: Unmount skipped because path does not exist: %v", deviceMountPath) + return nil + } + devicePath, cnt, err := mount.GetDeviceNameFromMount(detacher.mounter, deviceMountPath) + if err != nil { + return err + } + if cnt > 1 { + return fmt.Errorf("rbd: more than 1 reference counts at %s", deviceMountPath) + } + if cnt == 1 { + // Unmount the device from the device mount point. + glog.V(4).Infof("rbd: unmouting device mountpoint %s", deviceMountPath) + if err = detacher.mounter.Unmount(deviceMountPath); err != nil { + return err + } + glog.V(3).Infof("rbd: successfully umount device mountpath %s", deviceMountPath) + } + glog.V(4).Infof("rbd: detaching device %s", devicePath) + err = detacher.manager.DetachDisk(detacher.plugin, deviceMountPath, devicePath) + if err != nil { + return err + } + glog.V(3).Infof("rbd: successfully detach device %s", devicePath) + err = os.Remove(deviceMountPath) + if err != nil { + return err + } + glog.V(3).Infof("rbd: successfully remove device mount point %s", deviceMountPath) + return nil +} + +// Detach implements Detacher.Detach. +func (detacher *rbdDetacher) Detach(volumeName string, nodeName types.NodeName) error { + return nil +} diff --git a/pkg/volume/rbd/disk_manager.go b/pkg/volume/rbd/disk_manager.go index d219d62ab1b..9610ebc2b7a 100644 --- a/pkg/volume/rbd/disk_manager.go +++ b/pkg/volume/rbd/disk_manager.go @@ -23,33 +23,47 @@ limitations under the License. package rbd import ( + "fmt" "os" "github.com/golang/glog" "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" "k8s.io/kubernetes/pkg/util/mount" "k8s.io/kubernetes/pkg/volume" ) // Abstract interface to disk operations. type diskManager interface { + // MakeGlobalPDName creates global persistent disk path. MakeGlobalPDName(disk rbd) string // Attaches the disk to the kubelet's host machine. - AttachDisk(disk rbdMounter) error + // If it successfully attaches, the path to the device + // is returned. Otherwise, an error will be returned. + AttachDisk(disk rbdMounter) (string, error) // Detaches the disk from the kubelet's host machine. - DetachDisk(disk rbdUnmounter, mntPath string) error - // Creates a rbd image - CreateImage(provisioner *rbdVolumeProvisioner) (r *v1.RBDVolumeSource, volumeSizeGB int, err error) - // Deletes a rbd image + DetachDisk(plugin *rbdPlugin, deviceMountPath string, device string) error + // Creates a rbd image. + CreateImage(provisioner *rbdVolumeProvisioner) (r *v1.RBDPersistentVolumeSource, volumeSizeGB int, err error) + // Deletes a rbd image. DeleteImage(deleter *rbdVolumeDeleter) error + // Expands a rbd image + ExpandImage(expander *rbdVolumeExpander, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) } // utility to mount a disk based filesystem func diskSetUp(manager diskManager, b rbdMounter, volPath string, mounter mount.Interface, fsGroup *int64) error { globalPDPath := manager.MakeGlobalPDName(*b.rbd) - // TODO: handle failed mounts here. - notMnt, err := mounter.IsLikelyNotMountPoint(volPath) + notMnt, err := mounter.IsLikelyNotMountPoint(globalPDPath) + if err != nil && !os.IsNotExist(err) { + glog.Errorf("cannot validate mountpoint: %s", globalPDPath) + return err + } + if notMnt { + return fmt.Errorf("no device is mounted at %s", globalPDPath) + } + notMnt, err = mounter.IsLikelyNotMountPoint(volPath) if err != nil && !os.IsNotExist(err) { glog.Errorf("cannot validate mountpoint: %s", volPath) return err @@ -57,10 +71,6 @@ func diskSetUp(manager diskManager, b rbdMounter, volPath string, mounter mount. if !notMnt { return nil } - if err := manager.AttachDisk(b); err != nil { - glog.Errorf("failed to attach disk") - return err - } if err := os.MkdirAll(volPath, 0750); err != nil { glog.Errorf("failed to mkdir:%s", volPath) @@ -89,43 +99,31 @@ func diskSetUp(manager diskManager, b rbdMounter, volPath string, mounter mount. // utility to tear down a disk based filesystem func diskTearDown(manager diskManager, c rbdUnmounter, volPath string, mounter mount.Interface) error { notMnt, err := mounter.IsLikelyNotMountPoint(volPath) - if err != nil { - glog.Errorf("cannot validate mountpoint %s", volPath) + if err != nil && !os.IsNotExist(err) { + glog.Errorf("cannot validate mountpoint: %s", volPath) return err } if notMnt { + glog.V(3).Infof("volume path %s is not a mountpoint, deleting", volPath) return os.Remove(volPath) } - refs, err := mount.GetMountRefs(mounter, volPath) - if err != nil { - glog.Errorf("failed to get reference count %s", volPath) - return err - } + // Unmount the bind-mount inside this pod. if err := mounter.Unmount(volPath); err != nil { glog.Errorf("failed to umount %s", volPath) return err } - // If len(refs) is 1, then all bind mounts have been removed, and the - // remaining reference is the global mount. It is safe to detach. - if len(refs) == 1 { - mntPath := refs[0] - if err := manager.DetachDisk(c, mntPath); err != nil { - glog.Errorf("failed to detach disk from %s", mntPath) - return err - } - } notMnt, mntErr := mounter.IsLikelyNotMountPoint(volPath) - if mntErr != nil { + if mntErr != nil && !os.IsNotExist(mntErr) { glog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr) - return err + return mntErr } if notMnt { if err := os.Remove(volPath); err != nil { + glog.V(2).Info("Error removing mountpoint ", volPath, ": ", err) return err } } return nil - } diff --git a/pkg/volume/rbd/rbd.go b/pkg/volume/rbd/rbd.go index cc345b22d70..6042e743395 100644 --- a/pkg/volume/rbd/rbd.go +++ b/pkg/volume/rbd/rbd.go @@ -41,9 +41,10 @@ var ( // This is the primary entrypoint for volume plugins. func ProbeVolumePlugins() []volume.VolumePlugin { - return []volume.VolumePlugin{&rbdPlugin{nil}} + return []volume.VolumePlugin{&rbdPlugin{}} } +// rbdPlugin implements Volume.VolumePlugin. type rbdPlugin struct { host volume.VolumeHost } @@ -52,6 +53,8 @@ var _ volume.VolumePlugin = &rbdPlugin{} var _ volume.PersistentVolumePlugin = &rbdPlugin{} var _ volume.DeletableVolumePlugin = &rbdPlugin{} var _ volume.ProvisionableVolumePlugin = &rbdPlugin{} +var _ volume.AttachableVolumePlugin = &rbdPlugin{} +var _ volume.ExpandableVolumePlugin = &rbdPlugin{} const ( rbdPluginName = "kubernetes.io/rbd" @@ -78,15 +81,19 @@ func (plugin *rbdPlugin) GetPluginName() string { } func (plugin *rbdPlugin) GetVolumeName(spec *volume.Spec) (string, error) { - volumeSource, _, err := getVolumeSource(spec) + pool, err := getVolumeSourcePool(spec) + if err != nil { + return "", err + } + img, err := getVolumeSourceImage(spec) if err != nil { return "", err } return fmt.Sprintf( "%v:%v", - volumeSource.CephMonitors, - volumeSource.RBDImage), nil + pool, + img), nil } func (plugin *rbdPlugin) CanSupport(spec *volume.Spec) bool { @@ -116,101 +123,14 @@ func (plugin *rbdPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { } } -func (plugin *rbdPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) { - var secret string - var err error - source, _ := plugin.getRBDVolumeSource(spec) - - if source.SecretRef != nil { - if secret, err = parsePodSecret(pod, source.SecretRef.Name, plugin.host.GetKubeClient()); err != nil { - glog.Errorf("Couldn't get secret from %v/%v", pod.Namespace, source.SecretRef) - return nil, err - } - } - - // Inject real implementations here, test through the internal function. - return plugin.newMounterInternal(spec, pod.UID, &RBDUtil{}, plugin.host.GetMounter(plugin.GetPluginName()), plugin.host.GetExec(plugin.GetPluginName()), secret) +type rbdVolumeExpander struct { + *rbdMounter } -func (plugin *rbdPlugin) getRBDVolumeSource(spec *volume.Spec) (*v1.RBDVolumeSource, bool) { - // rbd volumes used directly in a pod have a ReadOnly flag set by the pod author. - // rbd volumes used as a PersistentVolume gets the ReadOnly flag indirectly through the persistent-claim volume used to mount the PV - if spec.Volume != nil && spec.Volume.RBD != nil { - return spec.Volume.RBD, spec.Volume.RBD.ReadOnly - } else { - return spec.PersistentVolume.Spec.RBD, spec.ReadOnly - } -} - -func (plugin *rbdPlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager diskManager, mounter mount.Interface, exec mount.Exec, secret string) (volume.Mounter, error) { - source, readOnly := plugin.getRBDVolumeSource(spec) - pool := source.RBDPool - id := source.RadosUser - keyring := source.Keyring - - return &rbdMounter{ - rbd: &rbd{ - podUID: podUID, - volName: spec.Name(), - Image: source.RBDImage, - Pool: pool, - ReadOnly: readOnly, - manager: manager, - mounter: &mount.SafeFormatAndMount{Interface: mounter, Exec: exec}, - exec: exec, - plugin: plugin, - MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, spec.Name(), plugin.host)), - }, - Mon: source.CephMonitors, - Id: id, - Keyring: keyring, - Secret: secret, - fsType: source.FSType, - mountOptions: volume.MountOptionFromSpec(spec), - }, nil -} - -func (plugin *rbdPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) { - // Inject real implementations here, test through the internal function. - return plugin.newUnmounterInternal(volName, podUID, &RBDUtil{}, plugin.host.GetMounter(plugin.GetPluginName()), plugin.host.GetExec(plugin.GetPluginName())) -} - -func (plugin *rbdPlugin) newUnmounterInternal(volName string, podUID types.UID, manager diskManager, mounter mount.Interface, exec mount.Exec) (volume.Unmounter, error) { - return &rbdUnmounter{ - rbdMounter: &rbdMounter{ - rbd: &rbd{ - podUID: podUID, - volName: volName, - manager: manager, - mounter: &mount.SafeFormatAndMount{Interface: mounter, Exec: exec}, - exec: exec, - plugin: plugin, - MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, volName, plugin.host)), - }, - Mon: make([]string, 0), - }, - }, nil -} - -func (plugin *rbdPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) { - rbdVolume := &v1.Volume{ - Name: volumeName, - VolumeSource: v1.VolumeSource{ - RBD: &v1.RBDVolumeSource{ - CephMonitors: []string{}, - }, - }, - } - return volume.NewSpecFromVolume(rbdVolume), nil -} - -func (plugin *rbdPlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) { - if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.RBD == nil { - return nil, fmt.Errorf("spec.PersistentVolumeSource.Spec.RBD is nil") - } +func (plugin *rbdPlugin) getAdminAndSecret(spec *volume.Spec) (string, string, error) { class, err := volutil.GetClassForVolume(plugin.host.GetKubeClient(), spec.PersistentVolume) if err != nil { - return nil, err + return "", "", err } adminSecretName := "" adminSecretNamespace := rbdDefaultAdminSecretNamespace @@ -232,23 +152,239 @@ func (plugin *rbdPlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) { } secret, err := parsePVSecret(adminSecretNamespace, adminSecretName, plugin.host.GetKubeClient()) if err != nil { - return nil, fmt.Errorf("failed to get admin secret from [%q/%q]: %v", adminSecretNamespace, adminSecretName, err) + return admin, "", fmt.Errorf("failed to get admin secret from [%q/%q]: %v", adminSecretNamespace, adminSecretName, err) } + + return admin, secret, nil +} + +func (plugin *rbdPlugin) ExpandVolumeDevice(spec *volume.Spec, newSize resource.Quantity, oldSize resource.Quantity) (resource.Quantity, error) { + if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.RBD == nil { + return oldSize, fmt.Errorf("spec.PersistentVolumeSource.Spec.RBD is nil") + } + + // get admin and secret + admin, secret, err := plugin.getAdminAndSecret(spec) + if err != nil { + return oldSize, err + } + + expander := &rbdVolumeExpander{ + rbdMounter: &rbdMounter{ + rbd: &rbd{ + volName: spec.Name(), + Image: spec.PersistentVolume.Spec.RBD.RBDImage, + Pool: spec.PersistentVolume.Spec.RBD.RBDPool, + plugin: plugin, + manager: &RBDUtil{}, + mounter: &mount.SafeFormatAndMount{Interface: plugin.host.GetMounter(plugin.GetPluginName())}, + exec: plugin.host.GetExec(plugin.GetPluginName()), + }, + Mon: spec.PersistentVolume.Spec.RBD.CephMonitors, + adminId: admin, + adminSecret: secret, + }, + } + + expandedSize, err := expander.ResizeImage(oldSize, newSize) + if err != nil { + return oldSize, err + } else { + return expandedSize, nil + } +} + +func (expander *rbdVolumeExpander) ResizeImage(oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) { + return expander.manager.ExpandImage(expander, oldSize, newSize) +} + +func (plugin *rbdPlugin) RequiresFSResize() bool { + return true +} + +func (plugin *rbdPlugin) createMounterFromVolumeSpecAndPod(spec *volume.Spec, pod *v1.Pod) (*rbdMounter, error) { + var err error + mon, err := getVolumeSourceMonitors(spec) + if err != nil { + return nil, err + } + img, err := getVolumeSourceImage(spec) + if err != nil { + return nil, err + } + fstype, err := getVolumeSourceFSType(spec) + if err != nil { + return nil, err + } + pool, err := getVolumeSourcePool(spec) + if err != nil { + return nil, err + } + id, err := getVolumeSourceUser(spec) + if err != nil { + return nil, err + } + keyring, err := getVolumeSourceKeyRing(spec) + if err != nil { + return nil, err + } + ro, err := getVolumeSourceReadOnly(spec) + if err != nil { + return nil, err + } + + secretName, secretNs, err := getSecretNameAndNamespace(spec, pod.Namespace) + if err != nil { + return nil, err + } + secret := "" + if len(secretName) > 0 && len(secretNs) > 0 { + // if secret is provideded, retrieve it + kubeClient := plugin.host.GetKubeClient() + if kubeClient == nil { + return nil, fmt.Errorf("Cannot get kube client") + } + secrets, err := kubeClient.CoreV1().Secrets(secretNs).Get(secretName, metav1.GetOptions{}) + if err != nil { + err = fmt.Errorf("Couldn't get secret %v/%v err: %v", secretNs, secretName, err) + return nil, err + } + for _, data := range secrets.Data { + secret = string(data) + } + } + + return &rbdMounter{ + rbd: newRBD("", spec.Name(), img, pool, ro, plugin, &RBDUtil{}), + Mon: mon, + Id: id, + Keyring: keyring, + Secret: secret, + fsType: fstype, + }, nil +} + +func (plugin *rbdPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) { + secretName, secretNs, err := getSecretNameAndNamespace(spec, pod.Namespace) + if err != nil { + return nil, err + } + secret := "" + if len(secretName) > 0 && len(secretNs) > 0 { + // if secret is provideded, retrieve it + kubeClient := plugin.host.GetKubeClient() + if kubeClient == nil { + return nil, fmt.Errorf("Cannot get kube client") + } + secrets, err := kubeClient.CoreV1().Secrets(secretNs).Get(secretName, metav1.GetOptions{}) + if err != nil { + err = fmt.Errorf("Couldn't get secret %v/%v err: %v", secretNs, secretName, err) + return nil, err + } + for _, data := range secrets.Data { + secret = string(data) + } + } + + // Inject real implementations here, test through the internal function. + return plugin.newMounterInternal(spec, pod.UID, &RBDUtil{}, secret) +} + +func (plugin *rbdPlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager diskManager, secret string) (volume.Mounter, error) { + mon, err := getVolumeSourceMonitors(spec) + if err != nil { + return nil, err + } + img, err := getVolumeSourceImage(spec) + if err != nil { + return nil, err + } + fstype, err := getVolumeSourceFSType(spec) + if err != nil { + return nil, err + } + pool, err := getVolumeSourcePool(spec) + if err != nil { + return nil, err + } + id, err := getVolumeSourceUser(spec) + if err != nil { + return nil, err + } + keyring, err := getVolumeSourceKeyRing(spec) + if err != nil { + return nil, err + } + ro, err := getVolumeSourceReadOnly(spec) + if err != nil { + return nil, err + } + + return &rbdMounter{ + rbd: newRBD(podUID, spec.Name(), img, pool, ro, plugin, manager), + Mon: mon, + Id: id, + Keyring: keyring, + Secret: secret, + fsType: fstype, + mountOptions: volume.MountOptionFromSpec(spec), + }, nil +} + +func (plugin *rbdPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) { + // Inject real implementations here, test through the internal function. + return plugin.newUnmounterInternal(volName, podUID, &RBDUtil{}) +} + +func (plugin *rbdPlugin) newUnmounterInternal(volName string, podUID types.UID, manager diskManager) (volume.Unmounter, error) { + return &rbdUnmounter{ + rbdMounter: &rbdMounter{ + rbd: newRBD(podUID, volName, "", "", false, plugin, manager), + Mon: make([]string, 0), + }, + }, nil +} + +func (plugin *rbdPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) { + mounter := plugin.host.GetMounter(plugin.GetPluginName()) + pluginDir := plugin.host.GetPluginDir(plugin.GetPluginName()) + sourceName, err := mounter.GetDeviceNameFromMount(mountPath, pluginDir) + if err != nil { + return nil, err + } + s := dstrings.Split(sourceName, "-image-") + if len(s) != 2 { + return nil, fmt.Errorf("sourceName %s wrong, should be pool+\"-image-\"+imageName", sourceName) + } + rbdVolume := &v1.Volume{ + Name: volumeName, + VolumeSource: v1.VolumeSource{ + RBD: &v1.RBDVolumeSource{ + RBDPool: s[0], + RBDImage: s[1], + }, + }, + } + return volume.NewSpecFromVolume(rbdVolume), nil +} + +func (plugin *rbdPlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) { + if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.RBD == nil { + return nil, fmt.Errorf("spec.PersistentVolumeSource.Spec.RBD is nil") + } + + admin, secret, err := plugin.getAdminAndSecret(spec) + if err != nil { + return nil, err + } + return plugin.newDeleterInternal(spec, admin, secret, &RBDUtil{}) } func (plugin *rbdPlugin) newDeleterInternal(spec *volume.Spec, admin, secret string, manager diskManager) (volume.Deleter, error) { return &rbdVolumeDeleter{ rbdMounter: &rbdMounter{ - rbd: &rbd{ - volName: spec.Name(), - Image: spec.PersistentVolume.Spec.RBD.RBDImage, - Pool: spec.PersistentVolume.Spec.RBD.RBDPool, - manager: manager, - plugin: plugin, - mounter: &mount.SafeFormatAndMount{Interface: plugin.host.GetMounter(plugin.GetPluginName())}, - exec: plugin.host.GetExec(plugin.GetPluginName()), - }, + rbd: newRBD("", spec.Name(), spec.PersistentVolume.Spec.RBD.RBDImage, spec.PersistentVolume.Spec.RBD.RBDPool, false, plugin, manager), Mon: spec.PersistentVolume.Spec.RBD.CephMonitors, adminId: admin, adminSecret: secret, @@ -262,22 +398,20 @@ func (plugin *rbdPlugin) NewProvisioner(options volume.VolumeOptions) (volume.Pr func (plugin *rbdPlugin) newProvisionerInternal(options volume.VolumeOptions, manager diskManager) (volume.Provisioner, error) { return &rbdVolumeProvisioner{ rbdMounter: &rbdMounter{ - rbd: &rbd{ - manager: manager, - plugin: plugin, - mounter: &mount.SafeFormatAndMount{Interface: plugin.host.GetMounter(plugin.GetPluginName())}, - exec: plugin.host.GetExec(plugin.GetPluginName()), - }, + rbd: newRBD("", "", "", "", false, plugin, manager), }, options: options, }, nil } +// rbdVolumeProvisioner implements volume.Provisioner interface. type rbdVolumeProvisioner struct { *rbdMounter options volume.VolumeOptions } +var _ volume.Provisioner = &rbdVolumeProvisioner{} + func (r *rbdVolumeProvisioner) Provision() (*v1.PersistentVolume, error) { if !volume.AccessModesContainedInAll(r.plugin.GetAccessModes(), r.options.PVC.Spec.AccessModes) { return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", r.options.PVC.Spec.AccessModes, r.plugin.GetAccessModes()) @@ -289,8 +423,9 @@ func (r *rbdVolumeProvisioner) Provision() (*v1.PersistentVolume, error) { var err error adminSecretName := "" adminSecretNamespace := rbdDefaultAdminSecretNamespace - secretName := "" secret := "" + secretName := "" + secretNamespace := "" imageFormat := rbdImageFormat2 fstype := "" @@ -313,6 +448,8 @@ func (r *rbdVolumeProvisioner) Provision() (*v1.PersistentVolume, error) { r.Pool = v case "usersecretname": secretName = v + case "usersecretnamespace": + secretNamespace = v case "imageformat": imageFormat = v case "imagefeatures": @@ -370,8 +507,9 @@ func (r *rbdVolumeProvisioner) Provision() (*v1.PersistentVolume, error) { glog.Infof("successfully created rbd image %q", image) pv := new(v1.PersistentVolume) metav1.SetMetaDataAnnotation(&pv.ObjectMeta, volumehelper.VolumeDynamicallyCreatedByKey, "rbd-dynamic-provisioner") - rbd.SecretRef = new(v1.LocalObjectReference) + rbd.SecretRef = new(v1.SecretReference) rbd.SecretRef.Name = secretName + rbd.SecretRef.Namespace = secretNamespace rbd.RadosUser = r.Id rbd.FSType = fstype pv.Spec.PersistentVolumeSource.RBD = rbd @@ -387,10 +525,13 @@ func (r *rbdVolumeProvisioner) Provision() (*v1.PersistentVolume, error) { return pv, nil } +// rbdVolumeDeleter implements volume.Deleter interface. type rbdVolumeDeleter struct { *rbdMounter } +var _ volume.Deleter = &rbdVolumeDeleter{} + func (r *rbdVolumeDeleter) GetPath() string { return getPath(r.podUID, r.volName, r.plugin.host) } @@ -399,6 +540,8 @@ func (r *rbdVolumeDeleter) Delete() error { return r.manager.DeleteImage(r) } +// rbd implmenets volume.Volume interface. +// It's embedded in Mounter/Unmounter/Deleter. type rbd struct { volName string podUID types.UID @@ -413,11 +556,35 @@ type rbd struct { volume.MetricsProvider `json:"-"` } +var _ volume.Volume = &rbd{} + func (rbd *rbd) GetPath() string { // safe to use PodVolumeDir now: volume teardown occurs before pod is cleaned up return getPath(rbd.podUID, rbd.volName, rbd.plugin.host) } +// newRBD creates a new rbd. +func newRBD(podUID types.UID, volName string, image string, pool string, readOnly bool, plugin *rbdPlugin, manager diskManager) *rbd { + return &rbd{ + podUID: podUID, + volName: volName, + Image: image, + Pool: pool, + ReadOnly: readOnly, + plugin: plugin, + mounter: volumehelper.NewSafeFormatAndMountFromHost(plugin.GetPluginName(), plugin.host), + exec: plugin.host.GetExec(plugin.GetPluginName()), + manager: manager, + MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, volName, plugin.host)), + } +} + +// rbdMounter implements volume.Mounter interface. +// It contains information which need to be persisted in whole life cycle of PV +// on the node. It is persisted at the very beginning in the pod mount point +// directory. +// Note: Capitalized field names of this struct determines the information +// persisted on the disk, DO NOT change them. (TODO: refactoring to use a dedicated struct?) type rbdMounter struct { *rbd // capitalized so they can be exported in persistRBD() @@ -456,14 +623,16 @@ func (b *rbdMounter) SetUp(fsGroup *int64) error { func (b *rbdMounter) SetUpAt(dir string, fsGroup *int64) error { // diskSetUp checks mountpoints and prevent repeated calls - glog.V(4).Infof("rbd: attempting to SetUp and mount %s", dir) + glog.V(4).Infof("rbd: attempting to setup at %s", dir) err := diskSetUp(b.manager, *b, dir, b.mounter, fsGroup) if err != nil { - glog.Errorf("rbd: failed to setup mount %s %v", dir, err) + glog.Errorf("rbd: failed to setup at %s %v", dir, err) } + glog.V(3).Infof("rbd: successfully setup at %s", dir) return err } +// rbdUnmounter implements volume.Unmounter interface. type rbdUnmounter struct { *rbdMounter } @@ -477,25 +646,98 @@ func (c *rbdUnmounter) TearDown() error { } func (c *rbdUnmounter) TearDownAt(dir string) error { + glog.V(4).Infof("rbd: attempting to teardown at %s", dir) if pathExists, pathErr := volutil.PathExists(dir); pathErr != nil { return fmt.Errorf("Error checking if path exists: %v", pathErr) } else if !pathExists { glog.Warningf("Warning: Unmount skipped because path does not exist: %v", dir) return nil } - return diskTearDown(c.manager, *c, dir, c.mounter) + err := diskTearDown(c.manager, *c, dir, c.mounter) + if err != nil { + return err + } + glog.V(3).Infof("rbd: successfully teardown at %s", dir) + return nil } -func getVolumeSource( - spec *volume.Spec) (*v1.RBDVolumeSource, bool, error) { +func getVolumeSourceMonitors(spec *volume.Spec) ([]string, error) { if spec.Volume != nil && spec.Volume.RBD != nil { - return spec.Volume.RBD, spec.Volume.RBD.ReadOnly, nil + return spec.Volume.RBD.CephMonitors, nil } else if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.RBD != nil { - return spec.PersistentVolume.Spec.RBD, spec.ReadOnly, nil + return spec.PersistentVolume.Spec.RBD.CephMonitors, nil } - return nil, false, fmt.Errorf("Spec does not reference a RBD volume type") + return nil, fmt.Errorf("Spec does not reference a RBD volume type") +} + +func getVolumeSourceImage(spec *volume.Spec) (string, error) { + if spec.Volume != nil && spec.Volume.RBD != nil { + return spec.Volume.RBD.RBDImage, nil + } else if spec.PersistentVolume != nil && + spec.PersistentVolume.Spec.RBD != nil { + return spec.PersistentVolume.Spec.RBD.RBDImage, nil + } + + return "", fmt.Errorf("Spec does not reference a RBD volume type") +} + +func getVolumeSourceFSType(spec *volume.Spec) (string, error) { + if spec.Volume != nil && spec.Volume.RBD != nil { + return spec.Volume.RBD.FSType, nil + } else if spec.PersistentVolume != nil && + spec.PersistentVolume.Spec.RBD != nil { + return spec.PersistentVolume.Spec.RBD.FSType, nil + } + + return "", fmt.Errorf("Spec does not reference a RBD volume type") +} + +func getVolumeSourcePool(spec *volume.Spec) (string, error) { + if spec.Volume != nil && spec.Volume.RBD != nil { + return spec.Volume.RBD.RBDPool, nil + } else if spec.PersistentVolume != nil && + spec.PersistentVolume.Spec.RBD != nil { + return spec.PersistentVolume.Spec.RBD.RBDPool, nil + } + + return "", fmt.Errorf("Spec does not reference a RBD volume type") +} + +func getVolumeSourceUser(spec *volume.Spec) (string, error) { + if spec.Volume != nil && spec.Volume.RBD != nil { + return spec.Volume.RBD.RadosUser, nil + } else if spec.PersistentVolume != nil && + spec.PersistentVolume.Spec.RBD != nil { + return spec.PersistentVolume.Spec.RBD.RadosUser, nil + } + + return "", fmt.Errorf("Spec does not reference a RBD volume type") +} + +func getVolumeSourceKeyRing(spec *volume.Spec) (string, error) { + if spec.Volume != nil && spec.Volume.RBD != nil { + return spec.Volume.RBD.Keyring, nil + } else if spec.PersistentVolume != nil && + spec.PersistentVolume.Spec.RBD != nil { + return spec.PersistentVolume.Spec.RBD.Keyring, nil + } + + return "", fmt.Errorf("Spec does not reference a RBD volume type") +} + +func getVolumeSourceReadOnly(spec *volume.Spec) (bool, error) { + if spec.Volume != nil && spec.Volume.RBD != nil { + return spec.Volume.RBD.ReadOnly, nil + } else if spec.PersistentVolume != nil && + spec.PersistentVolume.Spec.RBD != nil { + // rbd volumes used as a PersistentVolume gets the ReadOnly flag indirectly through + // the persistent-claim volume used to mount the PV + return spec.ReadOnly, nil + } + + return false, fmt.Errorf("Spec does not reference a RBD volume type") } func parsePodSecret(pod *v1.Pod, secretName string, kubeClient clientset.Interface) (string, error) { @@ -531,3 +773,26 @@ func parseSecretMap(secretMap map[string]string) (string, error) { // If not found, the last secret in the map wins as done before return secret, nil } + +func getSecretNameAndNamespace(spec *volume.Spec, defaultNamespace string) (string, string, error) { + if spec.Volume != nil && spec.Volume.RBD != nil { + localSecretRef := spec.Volume.RBD.SecretRef + if localSecretRef != nil { + return localSecretRef.Name, defaultNamespace, nil + } + return "", "", nil + + } else if spec.PersistentVolume != nil && + spec.PersistentVolume.Spec.RBD != nil { + secretRef := spec.PersistentVolume.Spec.RBD.SecretRef + secretNs := defaultNamespace + if secretRef != nil { + if len(secretRef.Namespace) != 0 { + secretNs = secretRef.Namespace + } + return secretRef.Name, secretNs, nil + } + return "", "", nil + } + return "", "", fmt.Errorf("Spec does not reference an RBD volume type") +} diff --git a/pkg/volume/rbd/rbd_test.go b/pkg/volume/rbd/rbd_test.go index 02f5d324c31..d16ce140992 100644 --- a/pkg/volume/rbd/rbd_test.go +++ b/pkg/volume/rbd/rbd_test.go @@ -18,16 +18,19 @@ package rbd import ( "fmt" - "io/ioutil" "os" "path/filepath" "reflect" + "strings" + "sync" "testing" + "time" - "github.com/stretchr/testify/assert" "k8s.io/api/core/v1" + "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/client-go/kubernetes/fake" utiltesting "k8s.io/client-go/util/testing" "k8s.io/kubernetes/pkg/util/mount" @@ -58,41 +61,47 @@ func TestCanSupport(t *testing.T) { } type fakeDiskManager struct { - tmpDir string + // Make sure we can run tests in parallel. + mutex sync.RWMutex + // Key format: "/" + rbdImageLocks map[string]bool + rbdMapIndex int + rbdDevices map[string]bool } func NewFakeDiskManager() *fakeDiskManager { return &fakeDiskManager{ - tmpDir: utiltesting.MkTmpdirOrDie("rbd_test"), + rbdImageLocks: make(map[string]bool), + rbdMapIndex: 0, + rbdDevices: make(map[string]bool), } } -func (fake *fakeDiskManager) Cleanup() { - os.RemoveAll(fake.tmpDir) +func (fake *fakeDiskManager) MakeGlobalPDName(rbd rbd) string { + return makePDNameInternal(rbd.plugin.host, rbd.Pool, rbd.Image) } -func (fake *fakeDiskManager) MakeGlobalPDName(disk rbd) string { - return fake.tmpDir +func (fake *fakeDiskManager) AttachDisk(b rbdMounter) (string, error) { + fake.mutex.Lock() + defer fake.mutex.Unlock() + fake.rbdMapIndex += 1 + devicePath := fmt.Sprintf("/dev/rbd%d", fake.rbdMapIndex) + fake.rbdDevices[devicePath] = true + return devicePath, nil } -func (fake *fakeDiskManager) AttachDisk(b rbdMounter) error { - globalPath := b.manager.MakeGlobalPDName(*b.rbd) - err := os.MkdirAll(globalPath, 0750) - if err != nil { - return err + +func (fake *fakeDiskManager) DetachDisk(r *rbdPlugin, deviceMountPath string, device string) error { + fake.mutex.Lock() + defer fake.mutex.Unlock() + ok := fake.rbdDevices[device] + if !ok { + return fmt.Errorf("rbd: failed to detach device %s, it does not exist", device) } + delete(fake.rbdDevices, device) return nil } -func (fake *fakeDiskManager) DetachDisk(c rbdUnmounter, mntPath string) error { - globalPath := c.manager.MakeGlobalPDName(*c.rbd) - err := os.RemoveAll(globalPath) - if err != nil { - return err - } - return nil -} - -func (fake *fakeDiskManager) CreateImage(provisioner *rbdVolumeProvisioner) (r *v1.RBDVolumeSource, volumeSizeGB int, err error) { +func (fake *fakeDiskManager) CreateImage(provisioner *rbdVolumeProvisioner) (r *v1.RBDPersistentVolumeSource, volumeSizeGB int, err error) { return nil, 0, fmt.Errorf("not implemented") } @@ -100,35 +109,115 @@ func (fake *fakeDiskManager) DeleteImage(deleter *rbdVolumeDeleter) error { return fmt.Errorf("not implemented") } -func doTestPlugin(t *testing.T, spec *volume.Spec) { - tmpDir, err := utiltesting.MkTmpdir("rbd_test") - if err != nil { - t.Fatalf("error creating temp dir: %v", err) +func (fake *fakeDiskManager) Fencing(r rbdMounter, nodeName string) error { + fake.mutex.Lock() + defer fake.mutex.Unlock() + key := fmt.Sprintf("%s/%s", r.Pool, r.Image) + isLocked, ok := fake.rbdImageLocks[key] + if ok && isLocked { + // not expected in testing + return fmt.Errorf("%s is already locked", key) } - defer os.RemoveAll(tmpDir) + fake.rbdImageLocks[key] = true + return nil +} +func (fake *fakeDiskManager) Defencing(r rbdMounter, nodeName string) error { + fake.mutex.Lock() + defer fake.mutex.Unlock() + key := fmt.Sprintf("%s/%s", r.Pool, r.Image) + isLocked, ok := fake.rbdImageLocks[key] + if !ok || !isLocked { + // not expected in testing + return fmt.Errorf("%s is not locked", key) + } + delete(fake.rbdImageLocks, key) + return nil +} + +func (fake *fakeDiskManager) IsLocked(r rbdMounter, nodeName string) (bool, error) { + fake.mutex.RLock() + defer fake.mutex.RUnlock() + key := fmt.Sprintf("%s/%s", r.Pool, r.Image) + isLocked, ok := fake.rbdImageLocks[key] + return ok && isLocked, nil +} + +func (fake *fakeDiskManager) ExpandImage(rbdExpander *rbdVolumeExpander, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) { + return resource.Quantity{}, fmt.Errorf("not implemented") +} + +// checkMounterLog checks fakeMounter must have expected logs, and the last action msut equal to expectedAction. +func checkMounterLog(t *testing.T, fakeMounter *mount.FakeMounter, expected int, expectedAction mount.FakeAction) { + if len(fakeMounter.Log) != expected { + t.Fatalf("fakeMounter should have %d logs, actual: %d", expected, len(fakeMounter.Log)) + } + lastIndex := len(fakeMounter.Log) - 1 + lastAction := fakeMounter.Log[lastIndex] + if !reflect.DeepEqual(expectedAction, lastAction) { + t.Fatalf("fakeMounter.Log[%d] should be %#v, not: %#v", lastIndex, expectedAction, lastAction) + } +} + +func doTestPlugin(t *testing.T, c *testcase) { + fakeVolumeHost := volumetest.NewFakeVolumeHost(c.root, nil, nil) plugMgr := volume.VolumePluginMgr{} - plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(tmpDir, nil, nil)) - + plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, fakeVolumeHost) plug, err := plugMgr.FindPluginByName("kubernetes.io/rbd") if err != nil { t.Errorf("Can't find the plugin by name") } + fakeMounter := fakeVolumeHost.GetMounter(plug.GetPluginName()).(*mount.FakeMounter) + fakeNodeName := types.NodeName("localhost") fdm := NewFakeDiskManager() - defer fdm.Cleanup() - exec := mount.NewFakeExec(nil) - mounter, err := plug.(*rbdPlugin).newMounterInternal(spec, types.UID("poduid"), fdm, &mount.FakeMounter{}, exec, "secrets") + + // attacher + attacher, err := plug.(*rbdPlugin).newAttacherInternal(fdm) + if err != nil { + t.Errorf("Failed to make a new Attacher: %v", err) + } + deviceAttachPath, err := attacher.Attach(c.spec, fakeNodeName) + if err != nil { + t.Fatal(err) + } + devicePath, err := attacher.WaitForAttach(c.spec, deviceAttachPath, c.pod, time.Second*10) + if err != nil { + t.Fatal(err) + } + if devicePath != c.expectedDevicePath { + t.Errorf("Unexpected path, expected %q, not: %q", c.expectedDevicePath, devicePath) + } + deviceMountPath, err := attacher.GetDeviceMountPath(c.spec) + if err != nil { + t.Fatal(err) + } + if deviceMountPath != c.expectedDeviceMountPath { + t.Errorf("Unexpected mount path, expected %q, not: %q", c.expectedDeviceMountPath, deviceMountPath) + } + err = attacher.MountDevice(c.spec, devicePath, deviceMountPath) + if err != nil { + t.Fatal(err) + } + if _, err := os.Stat(deviceMountPath); err != nil { + if os.IsNotExist(err) { + t.Errorf("Attacher.MountDevice() failed, device mount path not created: %s", deviceMountPath) + } else { + t.Errorf("Attacher.MountDevice() failed: %v", err) + } + } + checkMounterLog(t, fakeMounter, 1, mount.FakeAction{Action: "mount", Target: c.expectedDeviceMountPath, Source: devicePath, FSType: "ext4"}) + + // mounter + mounter, err := plug.(*rbdPlugin).newMounterInternal(c.spec, c.pod.UID, fdm, "secrets") if err != nil { t.Errorf("Failed to make a new Mounter: %v", err) } if mounter == nil { t.Error("Got a nil Mounter") } - path := mounter.GetPath() - expectedPath := fmt.Sprintf("%s/pods/poduid/volumes/kubernetes.io~rbd/vol1", tmpDir) - if path != expectedPath { - t.Errorf("Unexpected path, expected %q, got: %q", expectedPath, path) + if path != c.expectedPodMountPath { + t.Errorf("Unexpected path, expected %q, got: %q", c.expectedPodMountPath, path) } if err := mounter.SetUp(nil); err != nil { @@ -141,8 +230,10 @@ func doTestPlugin(t *testing.T, spec *volume.Spec) { t.Errorf("SetUp() failed: %v", err) } } + checkMounterLog(t, fakeMounter, 2, mount.FakeAction{Action: "mount", Target: c.expectedPodMountPath, Source: devicePath, FSType: ""}) - unmounter, err := plug.(*rbdPlugin).newUnmounterInternal("vol1", types.UID("poduid"), fdm, &mount.FakeMounter{}, exec) + // unmounter + unmounter, err := plug.(*rbdPlugin).newUnmounterInternal(c.spec.Name(), c.pod.UID, fdm) if err != nil { t.Errorf("Failed to make a new Unmounter: %v", err) } @@ -156,40 +247,104 @@ func doTestPlugin(t *testing.T, spec *volume.Spec) { if _, err := os.Stat(path); err == nil { t.Errorf("TearDown() failed, volume path still exists: %s", path) } else if !os.IsNotExist(err) { - t.Errorf("SetUp() failed: %v", err) + t.Errorf("TearDown() failed: %v", err) + } + checkMounterLog(t, fakeMounter, 3, mount.FakeAction{Action: "unmount", Target: c.expectedPodMountPath, Source: "", FSType: ""}) + + // detacher + detacher, err := plug.(*rbdPlugin).newDetacherInternal(fdm) + if err != nil { + t.Errorf("Failed to make a new Attacher: %v", err) + } + err = detacher.UnmountDevice(deviceMountPath) + if err != nil { + t.Fatalf("Detacher.UnmountDevice failed to unmount %s", deviceMountPath) + } + checkMounterLog(t, fakeMounter, 4, mount.FakeAction{Action: "unmount", Target: c.expectedDeviceMountPath, Source: "", FSType: ""}) + err = detacher.Detach(deviceMountPath, fakeNodeName) + if err != nil { + t.Fatalf("Detacher.Detach failed to detach %s from %s", deviceMountPath, fakeNodeName) } } -func TestPluginVolume(t *testing.T) { - vol := &v1.Volume{ - Name: "vol1", - VolumeSource: v1.VolumeSource{ - RBD: &v1.RBDVolumeSource{ - CephMonitors: []string{"a", "b"}, - RBDImage: "bar", - FSType: "ext4", - }, - }, - } - doTestPlugin(t, volume.NewSpecFromVolume(vol)) +type testcase struct { + spec *volume.Spec + root string + pod *v1.Pod + expectedDevicePath string + expectedDeviceMountPath string + expectedPodMountPath string } -func TestPluginPersistentVolume(t *testing.T) { - vol := &v1.PersistentVolume{ - ObjectMeta: metav1.ObjectMeta{ + +func TestPlugin(t *testing.T) { + tmpDir, err := utiltesting.MkTmpdir("rbd_test") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + tmpDir, err = filepath.EvalSymlinks(tmpDir) + if err != nil { + t.Fatal(err) + } + + podUID := uuid.NewUUID() + var cases []*testcase + cases = append(cases, &testcase{ + spec: volume.NewSpecFromVolume(&v1.Volume{ Name: "vol1", - }, - Spec: v1.PersistentVolumeSpec{ - PersistentVolumeSource: v1.PersistentVolumeSource{ + VolumeSource: v1.VolumeSource{ RBD: &v1.RBDVolumeSource{ CephMonitors: []string{"a", "b"}, - RBDImage: "bar", + RBDPool: "pool1", + RBDImage: "image1", FSType: "ext4", }, }, + }), + root: tmpDir, + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testpod", + Namespace: "testns", + UID: podUID, + }, }, - } + expectedDevicePath: "/dev/rbd1", + expectedDeviceMountPath: fmt.Sprintf("%s/plugins/kubernetes.io/rbd/rbd/pool1-image-image1", tmpDir), + expectedPodMountPath: fmt.Sprintf("%s/pods/%s/volumes/kubernetes.io~rbd/vol1", tmpDir, podUID), + }) + cases = append(cases, &testcase{ + spec: volume.NewSpecFromPersistentVolume(&v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "vol2", + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + RBD: &v1.RBDPersistentVolumeSource{ + CephMonitors: []string{"a", "b"}, + RBDPool: "pool2", + RBDImage: "image2", + FSType: "ext4", + }, + }, + }, + }, false), + root: tmpDir, + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testpod", + Namespace: "testns", + UID: podUID, + }, + }, + expectedDevicePath: "/dev/rbd1", + expectedDeviceMountPath: fmt.Sprintf("%s/plugins/kubernetes.io/rbd/rbd/pool2-image-image2", tmpDir), + expectedPodMountPath: fmt.Sprintf("%s/pods/%s/volumes/kubernetes.io~rbd/vol2", tmpDir, podUID), + }) - doTestPlugin(t, volume.NewSpecFromPersistentVolume(vol, false)) + for i := 0; i < len(cases); i++ { + doTestPlugin(t, cases[i]) + } } func TestPersistentClaimReadOnlyFlag(t *testing.T) { @@ -205,7 +360,7 @@ func TestPersistentClaimReadOnlyFlag(t *testing.T) { }, Spec: v1.PersistentVolumeSpec{ PersistentVolumeSource: v1.PersistentVolumeSource{ - RBD: &v1.RBDVolumeSource{ + RBD: &v1.RBDPersistentVolumeSource{ CephMonitors: []string{"a", "b"}, RBDImage: "bar", FSType: "ext4", @@ -249,83 +404,34 @@ func TestPersistentClaimReadOnlyFlag(t *testing.T) { } } -func TestPersistAndLoadRBD(t *testing.T) { - tmpDir, err := utiltesting.MkTmpdir("rbd_test") +func TestGetSecretNameAndNamespace(t *testing.T) { + secretName := "test-secret-name" + secretNamespace := "test-secret-namespace" + + volSpec := &volume.Spec{ + PersistentVolume: &v1.PersistentVolume{ + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + RBD: &v1.RBDPersistentVolumeSource{ + CephMonitors: []string{"a", "b"}, + RBDImage: "bar", + FSType: "ext4", + }, + }, + }, + }, + } + + secretRef := new(v1.SecretReference) + secretRef.Name = secretName + secretRef.Namespace = secretNamespace + volSpec.PersistentVolume.Spec.PersistentVolumeSource.RBD.SecretRef = secretRef + + foundSecretName, foundSecretNamespace, err := getSecretNameAndNamespace(volSpec, "default") if err != nil { - t.Fatalf("error creating temp dir: %v", err) + t.Errorf("getSecretNameAndNamespace failed to get Secret's name and namespace: %v", err) } - defer os.RemoveAll(tmpDir) - - testcases := []struct { - rbdMounter rbdMounter - expectedJSONStr string - expectedLoadedRBDMounter rbdMounter - }{ - { - rbdMounter{}, - `{"Mon":null,"Id":"","Keyring":"","Secret":""}`, - rbdMounter{}, - }, - { - rbdMounter{ - rbd: &rbd{ - podUID: "poduid", - Pool: "kube", - Image: "some-test-image", - ReadOnly: false, - MetricsProvider: volume.NewMetricsStatFS("/tmp"), - }, - Mon: []string{"127.0.0.1"}, - Id: "kube", - Keyring: "", - Secret: "QVFEcTdKdFp4SmhtTFJBQUNwNDI3UnhGRzBvQ1Y0SUJwLy9pRUE9PQ==", - }, - ` -{ - "Pool": "kube", - "Image": "some-test-image", - "ReadOnly": false, - "Mon": ["127.0.0.1"], - "Id": "kube", - "Keyring": "", - "Secret": "QVFEcTdKdFp4SmhtTFJBQUNwNDI3UnhGRzBvQ1Y0SUJwLy9pRUE9PQ==" -} - `, - rbdMounter{ - rbd: &rbd{ - Pool: "kube", - Image: "some-test-image", - ReadOnly: false, - }, - Mon: []string{"127.0.0.1"}, - Id: "kube", - Keyring: "", - Secret: "QVFEcTdKdFp4SmhtTFJBQUNwNDI3UnhGRzBvQ1Y0SUJwLy9pRUE9PQ==", - }, - }, - } - - util := &RBDUtil{} - for _, c := range testcases { - err = util.persistRBD(c.rbdMounter, tmpDir) - if err != nil { - t.Errorf("failed to persist rbd: %v, err: %v", c.rbdMounter, err) - } - jsonFile := filepath.Join(tmpDir, "rbd.json") - jsonData, err := ioutil.ReadFile(jsonFile) - if err != nil { - t.Errorf("failed to read json file %s: %v", jsonFile, err) - } - if !assert.JSONEq(t, c.expectedJSONStr, string(jsonData)) { - t.Errorf("json file does not match expected one: %s, should be %s", string(jsonData), c.expectedJSONStr) - } - tmpRBDMounter := rbdMounter{} - err = util.loadRBD(&tmpRBDMounter, tmpDir) - if err != nil { - t.Errorf("faild to load rbd: %v", err) - } - if !reflect.DeepEqual(tmpRBDMounter, c.expectedLoadedRBDMounter) { - t.Errorf("loaded rbd does not equal to expected one: %v, should be %v", tmpRBDMounter, c.rbdMounter) - } + if strings.Compare(secretName, foundSecretName) != 0 || strings.Compare(secretNamespace, foundSecretNamespace) != 0 { + t.Errorf("getSecretNameAndNamespace returned incorrect values, expected %s and %s but got %s and %s", secretName, secretNamespace, foundSecretName, foundSecretNamespace) } } diff --git a/pkg/volume/rbd/rbd_util.go b/pkg/volume/rbd/rbd_util.go index 1f06f16a4e8..915e0e2e496 100644 --- a/pkg/volume/rbd/rbd_util.go +++ b/pkg/volume/rbd/rbd_util.go @@ -23,27 +23,29 @@ package rbd import ( "encoding/json" - "errors" "fmt" "io/ioutil" - "math/rand" "os" + "os/exec" "path" - "regexp" + "strconv" "strings" "time" "github.com/golang/glog" "k8s.io/api/core/v1" - "k8s.io/kubernetes/pkg/util/mount" + "k8s.io/apimachinery/pkg/api/resource" + fileutil "k8s.io/kubernetes/pkg/util/file" "k8s.io/kubernetes/pkg/util/node" "k8s.io/kubernetes/pkg/volume" + volutil "k8s.io/kubernetes/pkg/volume/util" ) const ( imageWatcherStr = "watcher=" + imageSizeStr = "size " + sizeDivStr = " MB in" kubeLockMagic = "kubelet_lock_magic_" - rbdCmdErr = "executable file not found in $PATH" ) // search /sys/bus for rbd device that matches given pool and image @@ -107,19 +109,39 @@ func makePDNameInternal(host volume.VolumeHost, pool string, image string) strin return path.Join(host.GetPluginDir(rbdPluginName), "rbd", pool+"-image-"+image) } +// RBDUtil implements diskManager interface. type RBDUtil struct{} +var _ diskManager = &RBDUtil{} + func (util *RBDUtil) MakeGlobalPDName(rbd rbd) string { return makePDNameInternal(rbd.plugin.host, rbd.Pool, rbd.Image) } + func rbdErrors(runErr, resultErr error) error { - if runErr.Error() == rbdCmdErr { - return fmt.Errorf("rbd: rbd cmd not found") + if err, ok := runErr.(*exec.Error); ok { + if err.Err == exec.ErrNotFound { + return fmt.Errorf("rbd: rbd cmd not found") + } } return resultErr } -func (util *RBDUtil) rbdLock(b rbdMounter, lock bool) error { +// 'rbd' utility builds a comma-separated list of monitor addresses from '-m' / +// '--mon_host` parameter (comma, semi-colon, or white-space delimited monitor +// addresses) and send it to kernel rbd/libceph modules, which can accept +// comma-seprated list of monitor addresses (e.g. ip1[:port1][,ip2[:port2]...]) +// in theirs first version in linux (see +// https://github.com/torvalds/linux/blob/602adf400201636e95c3fed9f31fba54a3d7e844/net/ceph/ceph_common.c#L239). +// Also, libceph module choose monitor randomly, so we can simply pass all +// addresses without randomization (see +// https://github.com/torvalds/linux/blob/602adf400201636e95c3fed9f31fba54a3d7e844/net/ceph/mon_client.c#L132). +func (util *RBDUtil) kernelRBDMonitorsOpt(mons []string) string { + return strings.Join(mons, ",") +} + +// rbdUnlock releases a lock on image if found. +func (util *RBDUtil) rbdUnlock(b rbdMounter) error { var err error var output, locker string var cmd []byte @@ -140,142 +162,50 @@ func (util *RBDUtil) rbdLock(b rbdMounter, lock bool) error { // construct lock id using host name and a magic prefix lock_id := kubeLockMagic + node.GetHostname("") - l := len(b.Mon) - // avoid mount storm, pick a host randomly - start := rand.Int() % l - // iterate all hosts until mount succeeds. - for i := start; i < start+l; i++ { - mon := b.Mon[i%l] - // cmd "rbd lock list" serves two purposes: - // for fencing, check if lock already held for this host - // this edge case happens if host crashes in the middle of acquiring lock and mounting rbd - // for defencing, get the locker name, something like "client.1234" - args := []string{"lock", "list", b.Image, "--pool", b.Pool, "--id", b.Id, "-m", mon} - args = append(args, secret_opt...) - cmd, err = b.exec.Run("rbd", args...) - output = string(cmd) - glog.Infof("lock list output %q", output) - if err != nil { - continue - } + mon := util.kernelRBDMonitorsOpt(b.Mon) - if lock { - // check if lock is already held for this host by matching lock_id and rbd lock id - if strings.Contains(output, lock_id) { - // this host already holds the lock, exit - glog.V(1).Infof("rbd: lock already held for %s", lock_id) - return nil - } - // clean up orphaned lock if no watcher on the image - used, statusErr := util.rbdStatus(&b) - if statusErr == nil && !used { - re := regexp.MustCompile("client.* " + kubeLockMagic + ".*") - locks := re.FindAllStringSubmatch(output, -1) - for _, v := range locks { - if len(v) > 0 { - lockInfo := strings.Split(v[0], " ") - if len(lockInfo) > 2 { - args := []string{"lock", "remove", b.Image, lockInfo[1], lockInfo[0], "--pool", b.Pool, "--id", b.Id, "-m", mon} - args = append(args, secret_opt...) - cmd, err = b.exec.Run("rbd", args...) - glog.Infof("remove orphaned locker %s from client %s: err %v, output: %s", lockInfo[1], lockInfo[0], err, string(cmd)) - } - } - } - } - - // hold a lock: rbd lock add - args := []string{"lock", "add", b.Image, lock_id, "--pool", b.Pool, "--id", b.Id, "-m", mon} - args = append(args, secret_opt...) - cmd, err = b.exec.Run("rbd", args...) - } else { - // defencing, find locker name - ind := strings.LastIndex(output, lock_id) - 1 - for i := ind; i >= 0; i-- { - if output[i] == '\n' { - locker = output[(i + 1):ind] - break - } - } - // remove a lock: rbd lock remove - args := []string{"lock", "remove", b.Image, lock_id, locker, "--pool", b.Pool, "--id", b.Id, "-m", mon} - args = append(args, secret_opt...) - cmd, err = b.exec.Run("rbd", args...) - } - - if err == nil { - //lock is acquired + // get the locker name, something like "client.1234" + args := []string{"lock", "list", b.Image, "--pool", b.Pool, "--id", b.Id, "-m", mon} + args = append(args, secret_opt...) + cmd, err = b.exec.Run("rbd", args...) + output = string(cmd) + glog.Infof("lock list output %q", output) + if err != nil { + return err + } + ind := strings.LastIndex(output, lock_id) - 1 + for i := ind; i >= 0; i-- { + if output[i] == '\n' { + locker = output[(i + 1):ind] break } } + + // remove a lock if found: rbd lock remove + if len(locker) > 0 { + args := []string{"lock", "remove", b.Image, lock_id, locker, "--pool", b.Pool, "--id", b.Id, "-m", mon} + args = append(args, secret_opt...) + cmd, err = b.exec.Run("rbd", args...) + if err == nil { + glog.V(4).Infof("rbd: successfully remove lock (locker_id: %s) on image: %s/%s with id %s mon %s", lock_id, b.Pool, b.Image, b.Id, mon) + } + } + return err } -func (util *RBDUtil) persistRBD(rbd rbdMounter, mnt string) error { - file := path.Join(mnt, "rbd.json") - fp, err := os.Create(file) - if err != nil { - return fmt.Errorf("rbd: create err %s/%s", file, err) - } - defer fp.Close() - - encoder := json.NewEncoder(fp) - if err = encoder.Encode(rbd); err != nil { - return fmt.Errorf("rbd: encode err: %v.", err) - } - - return nil -} - -func (util *RBDUtil) loadRBD(mounter *rbdMounter, mnt string) error { - file := path.Join(mnt, "rbd.json") - fp, err := os.Open(file) - if err != nil { - return fmt.Errorf("rbd: open err %s/%s", file, err) - } - defer fp.Close() - - decoder := json.NewDecoder(fp) - if err = decoder.Decode(mounter); err != nil { - return fmt.Errorf("rbd: decode err: %v.", err) - } - - return nil -} - -func (util *RBDUtil) fencing(b rbdMounter) error { - // no need to fence readOnly - if (&b).GetAttributes().ReadOnly { - return nil - } - return util.rbdLock(b, true) -} - -func (util *RBDUtil) defencing(c rbdUnmounter) error { - // no need to fence readOnly - if c.ReadOnly { - return nil - } - - return util.rbdLock(*c.rbdMounter, false) -} - -func (util *RBDUtil) AttachDisk(b rbdMounter) error { +// AttachDisk attaches the disk on the node. +func (util *RBDUtil) AttachDisk(b rbdMounter) (string, error) { var err error var output []byte - // create mount point - globalPDPath := b.manager.MakeGlobalPDName(*b.rbd) - notMnt, err := b.mounter.IsLikelyNotMountPoint(globalPDPath) - // in the first time, the path shouldn't exist and IsLikelyNotMountPoint is expected to get NotExist - if err != nil && !os.IsNotExist(err) { - return fmt.Errorf("rbd: %s failed to check mountpoint", globalPDPath) - } - if !notMnt { - return nil - } - if err = os.MkdirAll(globalPDPath, 0750); err != nil { - return fmt.Errorf("rbd: failed to mkdir %s, error", globalPDPath) + globalPDPath := util.MakeGlobalPDName(*b.rbd) + if pathExists, pathErr := volutil.PathExists(globalPDPath); pathErr != nil { + return "", fmt.Errorf("Error checking if path exists: %v", pathErr) + } else if !pathExists { + if err := os.MkdirAll(globalPDPath, 0750); err != nil { + return "", err + } } devicePath, found := waitForPath(b.Pool, b.Image, 1) @@ -285,122 +215,132 @@ func (util *RBDUtil) AttachDisk(b rbdMounter) error { glog.Warningf("rbd: failed to load rbd kernel module:%v", err) } - // fence off other mappers - if err = util.fencing(b); err != nil { - return rbdErrors(err, fmt.Errorf("rbd: failed to lock image %s (maybe locked by other nodes), error %v", b.Image, err)) + // Currently, we don't acquire advisory lock on image, but for backward + // compatibility, we need to check if the image is being used by nodes running old kubelet. + found, rbdOutput, err := util.rbdStatus(&b) + if err != nil { + return "", fmt.Errorf("error: %v, rbd output: %v", err, rbdOutput) + } + if found { + glog.Infof("rbd image %s/%s is still being used ", b.Pool, b.Image) + return "", fmt.Errorf("rbd image %s/%s is still being used. rbd output: %s", b.Pool, b.Image, rbdOutput) } - // rbd lock remove needs ceph and image config - // but kubelet doesn't get them from apiserver during teardown - // so persit rbd config so upon disk detach, rbd lock can be removed - // since rbd json is persisted in the same local directory that is used as rbd mountpoint later, - // the json file remains invisible during rbd mount and thus won't be removed accidentally. - util.persistRBD(b, globalPDPath) - // rbd map - l := len(b.Mon) - // avoid mount storm, pick a host randomly - start := rand.Int() % l - // iterate all hosts until mount succeeds. - for i := start; i < start+l; i++ { - mon := b.Mon[i%l] - glog.V(1).Infof("rbd: map mon %s", mon) - if b.Secret != "" { - output, err = b.exec.Run("rbd", - "map", b.Image, "--pool", b.Pool, "--id", b.Id, "-m", mon, "--key="+b.Secret) - } else { - output, err = b.exec.Run("rbd", - "map", b.Image, "--pool", b.Pool, "--id", b.Id, "-m", mon, "-k", b.Keyring) - } - if err == nil { - break - } - glog.V(1).Infof("rbd: map error %v %s", err, string(output)) + mon := util.kernelRBDMonitorsOpt(b.Mon) + glog.V(1).Infof("rbd: map mon %s", mon) + if b.Secret != "" { + output, err = b.exec.Run("rbd", + "map", b.Image, "--pool", b.Pool, "--id", b.Id, "-m", mon, "--key="+b.Secret) + } else { + output, err = b.exec.Run("rbd", + "map", b.Image, "--pool", b.Pool, "--id", b.Id, "-m", mon, "-k", b.Keyring) } if err != nil { - return fmt.Errorf("rbd: map failed %v %s", err, string(output)) + glog.V(1).Infof("rbd: map error %v, rbd output: %s", err, string(output)) + return "", fmt.Errorf("rbd: map failed %v, rbd output: %s", err, string(output)) } devicePath, found = waitForPath(b.Pool, b.Image, 10) if !found { - return errors.New("Could not map image: Timeout after 10s") + return "", fmt.Errorf("Could not map image %s/%s, Timeout after 10s", b.Pool, b.Image) } - glog.V(3).Infof("rbd: successfully map image %s/%s to %s", b.Pool, b.Image, devicePath) } - - // mount it - if err = b.mounter.FormatAndMount(devicePath, globalPDPath, b.fsType, nil); err != nil { - err = fmt.Errorf("rbd: failed to mount rbd volume %s [%s] to %s, error %v", devicePath, b.fsType, globalPDPath, err) - } - glog.V(3).Infof("rbd: successfully mount image %s/%s at %s", b.Pool, b.Image, globalPDPath) - return err + return devicePath, nil } -func (util *RBDUtil) DetachDisk(c rbdUnmounter, mntPath string) error { - device, cnt, err := mount.GetDeviceNameFromMount(c.mounter, mntPath) +// DetachDisk detaches the disk from the node. +// It detaches device from the node if device is provided, and removes the lock +// if there is persisted RBD info under deviceMountPath. +func (util *RBDUtil) DetachDisk(plugin *rbdPlugin, deviceMountPath string, device string) error { + if len(device) == 0 { + return fmt.Errorf("DetachDisk failed , device is empty") + } + // rbd unmap + exec := plugin.host.GetExec(plugin.GetPluginName()) + output, err := exec.Run("rbd", "unmap", device) if err != nil { - return fmt.Errorf("rbd detach disk: failed to get device from mnt: %s\nError: %v", mntPath, err) + return rbdErrors(err, fmt.Errorf("rbd: failed to unmap device %s, error %v, rbd output: %v", device, err, output)) } - if err = c.mounter.Unmount(mntPath); err != nil { - return fmt.Errorf("rbd detach disk: failed to umount: %s\nError: %v", mntPath, err) + glog.V(3).Infof("rbd: successfully unmap device %s", device) + + // Currently, we don't persist rbd info on the disk, but for backward + // compatbility, we need to clean it if found. + rbdFile := path.Join(deviceMountPath, "rbd.json") + exists, err := fileutil.FileExists(rbdFile) + if err != nil { + return err } - glog.V(3).Infof("rbd: successfully umount mountpoint %s", mntPath) - // if device is no longer used, see if can unmap - if cnt <= 1 { - // rbd unmap - _, err = c.exec.Run("rbd", "unmap", device) + if exists { + glog.V(3).Infof("rbd: old rbd.json is found under %s, cleaning it", deviceMountPath) + err = util.cleanOldRBDFile(plugin, rbdFile) if err != nil { - return rbdErrors(err, fmt.Errorf("rbd: failed to unmap device %s:Error: %v", device, err)) + glog.Errorf("rbd: failed to clean %s", rbdFile) + return err } - - // load ceph and image/pool info to remove fencing - if err := util.loadRBD(c.rbdMounter, mntPath); err == nil { - // remove rbd lock - util.defencing(c) - } - - glog.V(3).Infof("rbd: successfully unmap device %s", device) + glog.V(3).Infof("rbd: successfully remove %s", rbdFile) } return nil } -func (util *RBDUtil) CreateImage(p *rbdVolumeProvisioner) (r *v1.RBDVolumeSource, size int, err error) { +// cleanOldRBDFile read rbd info from rbd.json file and removes lock if found. +// At last, it removes rbd.json file. +func (util *RBDUtil) cleanOldRBDFile(plugin *rbdPlugin, rbdFile string) error { + mounter := &rbdMounter{ + // util.rbdUnlock needs it to run command. + rbd: newRBD("", "", "", "", false, plugin, util), + } + fp, err := os.Open(rbdFile) + if err != nil { + return fmt.Errorf("rbd: open err %s/%s", rbdFile, err) + } + defer fp.Close() + + decoder := json.NewDecoder(fp) + if err = decoder.Decode(mounter); err != nil { + return fmt.Errorf("rbd: decode err: %v.", err) + } + + if err != nil { + glog.Errorf("failed to load rbd info from %s: %v", rbdFile, err) + return err + } + // remove rbd lock if found + // the disk is not attached to this node anymore, so the lock on image + // for this node can be removed safely + err = util.rbdUnlock(*mounter) + if err == nil { + os.Remove(rbdFile) + } + return err +} + +func (util *RBDUtil) CreateImage(p *rbdVolumeProvisioner) (r *v1.RBDPersistentVolumeSource, size int, err error) { var output []byte capacity := p.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)] volSizeBytes := capacity.Value() // convert to MB that rbd defaults on sz := int(volume.RoundUpSize(volSizeBytes, 1024*1024)) volSz := fmt.Sprintf("%d", sz) - // rbd create - l := len(p.rbdMounter.Mon) - // pick a mon randomly - start := rand.Int() % l - // iterate all monitors until create succeeds. - for i := start; i < start+l; i++ { - mon := p.Mon[i%l] - if p.rbdMounter.imageFormat == rbdImageFormat2 { - glog.V(4).Infof("rbd: create %s size %s format %s (features: %s) using mon %s, pool %s id %s key %s", p.rbdMounter.Image, volSz, p.rbdMounter.imageFormat, p.rbdMounter.imageFeatures, mon, p.rbdMounter.Pool, p.rbdMounter.adminId, p.rbdMounter.adminSecret) - } else { - glog.V(4).Infof("rbd: create %s size %s format %s using mon %s, pool %s id %s key %s", p.rbdMounter.Image, volSz, p.rbdMounter.imageFormat, mon, p.rbdMounter.Pool, p.rbdMounter.adminId, p.rbdMounter.adminSecret) - } - args := []string{"create", p.rbdMounter.Image, "--size", volSz, "--pool", p.rbdMounter.Pool, "--id", p.rbdMounter.adminId, "-m", mon, "--key=" + p.rbdMounter.adminSecret, "--image-format", p.rbdMounter.imageFormat} - if p.rbdMounter.imageFormat == rbdImageFormat2 { - // if no image features is provided, it results in empty string - // which disable all RBD image format 2 features as we expected - features := strings.Join(p.rbdMounter.imageFeatures, ",") - args = append(args, "--image-feature", features) - } - output, err = p.exec.Run("rbd", args...) - if err == nil { - break - } else { - glog.Warningf("failed to create rbd image, output %v", string(output)) - } + mon := util.kernelRBDMonitorsOpt(p.Mon) + if p.rbdMounter.imageFormat == rbdImageFormat2 { + glog.V(4).Infof("rbd: create %s size %s format %s (features: %s) using mon %s, pool %s id %s key %s", p.rbdMounter.Image, volSz, p.rbdMounter.imageFormat, p.rbdMounter.imageFeatures, mon, p.rbdMounter.Pool, p.rbdMounter.adminId, p.rbdMounter.adminSecret) + } else { + glog.V(4).Infof("rbd: create %s size %s format %s using mon %s, pool %s id %s key %s", p.rbdMounter.Image, volSz, p.rbdMounter.imageFormat, mon, p.rbdMounter.Pool, p.rbdMounter.adminId, p.rbdMounter.adminSecret) } + args := []string{"create", p.rbdMounter.Image, "--size", volSz, "--pool", p.rbdMounter.Pool, "--id", p.rbdMounter.adminId, "-m", mon, "--key=" + p.rbdMounter.adminSecret, "--image-format", p.rbdMounter.imageFormat} + if p.rbdMounter.imageFormat == rbdImageFormat2 { + // if no image features is provided, it results in empty string + // which disable all RBD image format 2 features as we expected + features := strings.Join(p.rbdMounter.imageFeatures, ",") + args = append(args, "--image-feature", features) + } + output, err = p.exec.Run("rbd", args...) if err != nil { + glog.Warningf("failed to create rbd image, output %v", string(output)) return nil, 0, fmt.Errorf("failed to create rbd image: %v, command output: %s", err, string(output)) } - return &v1.RBDVolumeSource{ + return &v1.RBDPersistentVolumeSource{ CephMonitors: p.rbdMounter.Mon, RBDImage: p.rbdMounter.Image, RBDPool: p.rbdMounter.Pool, @@ -409,68 +349,180 @@ func (util *RBDUtil) CreateImage(p *rbdVolumeProvisioner) (r *v1.RBDVolumeSource func (util *RBDUtil) DeleteImage(p *rbdVolumeDeleter) error { var output []byte - found, err := util.rbdStatus(p.rbdMounter) + found, rbdOutput, err := util.rbdStatus(p.rbdMounter) if err != nil { - return err + return fmt.Errorf("error %v, rbd output: %v", err, rbdOutput) } if found { glog.Info("rbd is still being used ", p.rbdMounter.Image) - return fmt.Errorf("rbd %s is still being used", p.rbdMounter.Image) + return fmt.Errorf("rbd image %s/%s is still being used, rbd output: %v", p.rbdMounter.Pool, p.rbdMounter.Image, rbdOutput) } // rbd rm - l := len(p.rbdMounter.Mon) - // pick a mon randomly - start := rand.Int() % l - // iterate all monitors until rm succeeds. - for i := start; i < start+l; i++ { - mon := p.rbdMounter.Mon[i%l] - glog.V(4).Infof("rbd: rm %s using mon %s, pool %s id %s key %s", p.rbdMounter.Image, mon, p.rbdMounter.Pool, p.rbdMounter.adminId, p.rbdMounter.adminSecret) - output, err = p.exec.Run("rbd", - "rm", p.rbdMounter.Image, "--pool", p.rbdMounter.Pool, "--id", p.rbdMounter.adminId, "-m", mon, "--key="+p.rbdMounter.adminSecret) - if err == nil { - return nil - } else { - glog.Errorf("failed to delete rbd image: %v, command output: %s", err, string(output)) - } + mon := util.kernelRBDMonitorsOpt(p.rbdMounter.Mon) + glog.V(4).Infof("rbd: rm %s using mon %s, pool %s id %s key %s", p.rbdMounter.Image, mon, p.rbdMounter.Pool, p.rbdMounter.adminId, p.rbdMounter.adminSecret) + output, err = p.exec.Run("rbd", + "rm", p.rbdMounter.Image, "--pool", p.rbdMounter.Pool, "--id", p.rbdMounter.adminId, "-m", mon, "--key="+p.rbdMounter.adminSecret) + if err == nil { + return nil } - return err + + glog.Errorf("failed to delete rbd image: %v, command output: %s", err, string(output)) + return fmt.Errorf("error %v, rbd output: %v", err, string(output)) } -// run rbd status command to check if there is watcher on the image -func (util *RBDUtil) rbdStatus(b *rbdMounter) (bool, error) { +// ExpandImage runs rbd resize command to resize the specified image +func (util *RBDUtil) ExpandImage(rbdExpander *rbdVolumeExpander, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) { + var output []byte + var err error + volSizeBytes := newSize.Value() + // convert to MB that rbd defaults on + sz := int(volume.RoundUpSize(volSizeBytes, 1024*1024)) + newVolSz := fmt.Sprintf("%d", sz) + newSizeQuant := resource.MustParse(fmt.Sprintf("%dMi", sz)) + + // check the current size of rbd image, if equals to or greater that the new request size, do nothing + curSize, infoErr := util.rbdInfo(rbdExpander.rbdMounter) + if infoErr != nil { + return oldSize, fmt.Errorf("rbd info failed, error: %v", infoErr) + } + if curSize >= sz { + return newSizeQuant, nil + } + + // rbd resize + mon := util.kernelRBDMonitorsOpt(rbdExpander.rbdMounter.Mon) + glog.V(4).Infof("rbd: resize %s using mon %s, pool %s id %s key %s", rbdExpander.rbdMounter.Image, mon, rbdExpander.rbdMounter.Pool, rbdExpander.rbdMounter.adminId, rbdExpander.rbdMounter.adminSecret) + output, err = rbdExpander.exec.Run("rbd", + "resize", rbdExpander.rbdMounter.Image, "--size", newVolSz, "--pool", rbdExpander.rbdMounter.Pool, "--id", rbdExpander.rbdMounter.adminId, "-m", mon, "--key="+rbdExpander.rbdMounter.adminSecret) + if err == nil { + return newSizeQuant, nil + } + + glog.Errorf("failed to resize rbd image: %v, command output: %s", err, string(output)) + return oldSize, err +} + +// rbdInfo runs `rbd info` command to get the current image size in MB +func (util *RBDUtil) rbdInfo(b *rbdMounter) (int, error) { var err error var output string var cmd []byte - l := len(b.Mon) - start := rand.Int() % l - // iterate all hosts until mount succeeds. - for i := start; i < start+l; i++ { - mon := b.Mon[i%l] - // cmd "rbd status" list the rbd client watch with the following output: - // Watchers: - // watcher=10.16.153.105:0/710245699 client.14163 cookie=1 - glog.V(4).Infof("rbd: status %s using mon %s, pool %s id %s key %s", b.Image, mon, b.Pool, b.adminId, b.adminSecret) - cmd, err = b.exec.Run("rbd", - "status", b.Image, "--pool", b.Pool, "-m", mon, "--id", b.adminId, "--key="+b.adminSecret) - output = string(cmd) + // If we don't have admin id/secret (e.g. attaching), fallback to user id/secret. + id := b.adminId + secret := b.adminSecret + if id == "" { + id = b.Id + secret = b.Secret + } - if err != nil { - if err.Error() == rbdCmdErr { - glog.Errorf("rbd cmd not found") - } else { - // ignore error code, just checkout output for watcher string - glog.Warningf("failed to execute rbd status on mon %s", mon) - } - } + mon := util.kernelRBDMonitorsOpt(b.Mon) + // cmd "rbd info" get the image info with the following output: + // + // # image exists (exit=0) + // rbd info volume-4a5bcc8b-2b55-46da-ba04-0d3dc5227f08 + // size 1024 MB in 256 objects + // order 22 (4096 kB objects) + // block_name_prefix: rbd_data.1253ac238e1f29 + // format: 2 + // ... + // + // rbd info volume-4a5bcc8b-2b55-46da-ba04-0d3dc5227f08 --format json + // {"name":"volume-4a5bcc8b-2b55-46da-ba04-0d3dc5227f08","size":1073741824,"objects":256,"order":22,"object_size":4194304,"block_name_prefix":"rbd_data.1253ac238e1f29","format":2,"features":["layering","exclusive-lock","object-map","fast-diff","deep-flatten"],"flags":[]} + // + // + // # image does not exist (exit=2) + // rbd: error opening image 1234: (2) No such file or directory + // + glog.V(4).Infof("rbd: info %s using mon %s, pool %s id %s key %s", b.Image, mon, b.Pool, id, secret) + cmd, err = b.exec.Run("rbd", + "info", b.Image, "--pool", b.Pool, "-m", mon, "--id", id, "--key="+secret) + output = string(cmd) - if strings.Contains(output, imageWatcherStr) { - glog.V(4).Infof("rbd: watchers on %s: %s", b.Image, output) - return true, nil - } else { - glog.Warningf("rbd: no watchers on %s", b.Image) - return false, nil + if err, ok := err.(*exec.Error); ok { + if err.Err == exec.ErrNotFound { + glog.Errorf("rbd cmd not found") + // fail fast if command not found + return 0, err } } - return false, nil + + // If command never succeed, returns its last error. + if err != nil { + return 0, err + } + + if len(output) == 0 { + return 0, fmt.Errorf("can not get image size info %s: %s", b.Image, output) + } + + // get the size value string, just between `size ` and ` MB in`, such as `size 1024 MB in 256 objects` + sizeIndex := strings.Index(output, imageSizeStr) + divIndex := strings.Index(output, sizeDivStr) + if sizeIndex == -1 || divIndex == -1 || divIndex <= sizeIndex+5 { + return 0, fmt.Errorf("can not get image size info %s: %s", b.Image, output) + } + rbdSizeStr := output[sizeIndex+5 : divIndex] + rbdSize, err := strconv.Atoi(rbdSizeStr) + if err != nil { + return 0, fmt.Errorf("can not convert size str: %s to int", rbdSizeStr) + } + + return rbdSize, nil +} + +// rbdStatus runs `rbd status` command to check if there is watcher on the image. +func (util *RBDUtil) rbdStatus(b *rbdMounter) (bool, string, error) { + var err error + var output string + var cmd []byte + + // If we don't have admin id/secret (e.g. attaching), fallback to user id/secret. + id := b.adminId + secret := b.adminSecret + if id == "" { + id = b.Id + secret = b.Secret + } + + mon := util.kernelRBDMonitorsOpt(b.Mon) + // cmd "rbd status" list the rbd client watch with the following output: + // + // # there is a watcher (exit=0) + // Watchers: + // watcher=10.16.153.105:0/710245699 client.14163 cookie=1 + // + // # there is no watcher (exit=0) + // Watchers: none + // + // Otherwise, exit is non-zero, for example: + // + // # image does not exist (exit=2) + // rbd: error opening image kubernetes-dynamic-pvc-: (2) No such file or directory + // + glog.V(4).Infof("rbd: status %s using mon %s, pool %s id %s key %s", b.Image, mon, b.Pool, id, secret) + cmd, err = b.exec.Run("rbd", + "status", b.Image, "--pool", b.Pool, "-m", mon, "--id", id, "--key="+secret) + output = string(cmd) + + if err, ok := err.(*exec.Error); ok { + if err.Err == exec.ErrNotFound { + glog.Errorf("rbd cmd not found") + // fail fast if command not found + return false, output, err + } + } + + // If command never succeed, returns its last error. + if err != nil { + return false, output, err + } + + if strings.Contains(output, imageWatcherStr) { + glog.V(4).Infof("rbd: watchers on %s: %s", b.Image, output) + return true, output, nil + } else { + glog.Warningf("rbd: no watchers on %s", b.Image) + return false, output, nil + } } diff --git a/pkg/volume/scaleio/BUILD b/pkg/volume/scaleio/BUILD index 9077321da13..c744150d0ef 100644 --- a/pkg/volume/scaleio/BUILD +++ b/pkg/volume/scaleio/BUILD @@ -13,8 +13,8 @@ go_test( "sio_util_test.go", "sio_volume_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/scaleio", - library = ":go_default_library", deps = [ "//pkg/util/mount:go_default_library", "//pkg/volume:go_default_library", @@ -23,6 +23,7 @@ go_test( "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", "//vendor/k8s.io/client-go/util/testing:go_default_library", diff --git a/pkg/volume/scaleio/sio_client.go b/pkg/volume/scaleio/sio_client.go index 2506fe1dd2b..18b8ffb5b18 100644 --- a/pkg/volume/scaleio/sio_client.go +++ b/pkg/volume/scaleio/sio_client.go @@ -373,49 +373,25 @@ func (c *sioClient) GetVolumeRefs(volId sioVolumeID) (refs int, err error) { func (c *sioClient) Devs() (map[string]string, error) { volumeMap := make(map[string]string) - // grab the sdc tool output - out, err := c.exec.Run(c.getSdcCmd(), "--query_vols") - if err != nil { - glog.Error(log("sdc --query_vols failed: %v", err)) - return nil, err - } - - // --query_vols output is a heading followed by list of attached vols as follows: - // Retrieve ? volume(s) - // VOL-ID a2b8419300000000 MDM-ID 788d9efb0a8f20cb - // ... - // parse output and store it in a map as map[]volID - // that map is used later to retrieve device path (next section) - result := string(out) - mdmMap := make(map[string]string) - lines := strings.Split(result, "\n") - for _, line := range lines { - //line e.g.: "VOL-ID a2b8419300000000 MDM-ID 788d9efb0a8f20cb" - if strings.HasPrefix(line, "VOL-ID") { - //split[1] = volID; split[3] = mdmID - split := strings.Split(line, " ") - key := fmt.Sprintf("%s-%s", split[3], split[1]) - mdmMap[key] = split[1] - } - } - files, err := c.getSioDiskPaths() if err != nil { return nil, err } for _, f := range files { - // remove emec-vol- prefix to be left with concated mdmID-volID - mdmVolumeID := strings.Replace(f.Name(), "emc-vol-", "", 1) + // split emc-vol-- to pull out volumeID + parts := strings.Split(f.Name(), "-") + if len(parts) != 4 { + return nil, errors.New("unexpected ScaleIO device name format") + } + volumeID := parts[3] devPath, err := filepath.EvalSymlinks(fmt.Sprintf("%s/%s", sioDiskIDPath, f.Name())) if err != nil { glog.Error(log("devicepath-to-volID mapping error: %v", err)) return nil, err } - // map volID to devicePath - if volumeID, ok := mdmMap[mdmVolumeID]; ok { - volumeMap[volumeID] = devPath - } + // map volumeID to devicePath + volumeMap[volumeID] = devPath } return volumeMap, nil } diff --git a/pkg/volume/scaleio/sio_mgr_test.go b/pkg/volume/scaleio/sio_mgr_test.go index e2fe5c2002b..49ff5d05a81 100644 --- a/pkg/volume/scaleio/sio_mgr_test.go +++ b/pkg/volume/scaleio/sio_mgr_test.go @@ -37,7 +37,7 @@ var ( confKey.sslEnabled: "false", confKey.system: "scaleio", confKey.volumeName: "sio-0001", - confKey.secretRef: "sio-secret", + confKey.secretName: "sio-secret", confKey.username: "c2lvdXNlcgo=", // siouser confKey.password: "c2lvcGFzc3dvcmQK", // siopassword } diff --git a/pkg/volume/scaleio/sio_plugin.go b/pkg/volume/scaleio/sio_plugin.go index d7758756216..babd840c6b7 100644 --- a/pkg/volume/scaleio/sio_plugin.go +++ b/pkg/volume/scaleio/sio_plugin.go @@ -60,12 +60,11 @@ func (p *sioPlugin) GetPluginName() string { } func (p *sioPlugin) GetVolumeName(spec *volume.Spec) (string, error) { - source, err := getVolumeSourceFromSpec(spec) + attribs, err := getVolumeSourceAttribs(spec) if err != nil { return "", err } - - return source.VolumeName, nil + return attribs.volName, nil } func (p *sioPlugin) CanSupport(spec *volume.Spec) bool { @@ -81,29 +80,33 @@ func (p *sioPlugin) NewMounter( spec *volume.Spec, pod *api.Pod, _ volume.VolumeOptions) (volume.Mounter, error) { - sioSource, err := getVolumeSourceFromSpec(spec) + + // extract source info from either ScaleIOVolumeSource or ScaleIOPersistentVolumeSource type + attribs, err := getVolumeSourceAttribs(spec) if err != nil { - glog.Error(log("failed to extract ScaleIOVolumeSource from spec: %v", err)) - return nil, err + return nil, errors.New(log("mounter failed to extract volume attributes from spec: %v", err)) + } + + secretName, secretNS, err := getSecretAndNamespaceFromSpec(spec, pod) + if err != nil { + return nil, errors.New(log("failed to get secret name or secretNamespace: %v", err)) } return &sioVolume{ - pod: pod, - spec: spec, - source: sioSource, - namespace: pod.Namespace, - volSpecName: spec.Name(), - volName: sioSource.VolumeName, - podUID: pod.UID, - readOnly: sioSource.ReadOnly, - fsType: sioSource.FSType, - plugin: p, + pod: pod, + spec: spec, + secretName: secretName, + secretNamespace: secretNS, + volSpecName: spec.Name(), + volName: attribs.volName, + podUID: pod.UID, + readOnly: attribs.readOnly, + fsType: attribs.fsType, + plugin: p, }, nil } // NewUnmounter creates a representation of the volume to unmount -// The specName param can be used to carry the namespace value (if needed) using format: -// specName = [nsSep] where the specname is pre-pended with the namespace func (p *sioPlugin) NewUnmounter(specName string, podUID types.UID) (volume.Unmounter, error) { glog.V(4).Info(log("Unmounter for %s", specName)) @@ -156,22 +159,25 @@ func (p *sioPlugin) GetAccessModes() []api.PersistentVolumeAccessMode { var _ volume.DeletableVolumePlugin = &sioPlugin{} func (p *sioPlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) { - sioSource, err := getVolumeSourceFromSpec(spec) + attribs, err := getVolumeSourceAttribs(spec) if err != nil { - glog.Error(log("deleter failed to extract source from spec: %v", err)) + glog.Error(log("deleter failed to extract volume attributes from spec: %v", err)) return nil, err } - namespace := spec.PersistentVolume.Spec.ClaimRef.Namespace + secretName, secretNS, err := getSecretAndNamespaceFromSpec(spec, nil) + if err != nil { + return nil, errors.New(log("failed to get secret name or secretNamespace: %v", err)) + } return &sioVolume{ - spec: spec, - source: sioSource, - namespace: namespace, - volSpecName: spec.Name(), - volName: sioSource.VolumeName, - plugin: p, - readOnly: sioSource.ReadOnly, + spec: spec, + secretName: secretName, + secretNamespace: secretNS, + volSpecName: spec.Name(), + volName: attribs.volName, + plugin: p, + readOnly: attribs.readOnly, }, nil } @@ -189,13 +195,26 @@ func (p *sioPlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisi return nil, errors.New("option parameters missing") } - namespace := options.PVC.Namespace + // Supports ref of name of secret a couple of ways: + // options.Parameters["secretRef"] for backward compat, or + // options.Parameters["secretName"] + secretName := configData[confKey.secretName] + if secretName == "" { + secretName = configData["secretName"] + configData[confKey.secretName] = secretName + } + + secretNS := configData[confKey.secretNamespace] + if secretNS == "" { + secretNS = options.PVC.Namespace + } return &sioVolume{ - configData: configData, - plugin: p, - options: options, - namespace: namespace, - volSpecName: options.PVName, + configData: configData, + plugin: p, + options: options, + secretName: secretName, + secretNamespace: secretNS, + volSpecName: options.PVName, }, nil } diff --git a/pkg/volume/scaleio/sio_util.go b/pkg/volume/scaleio/sio_util.go index 3c2f13a3405..de0c5811665 100644 --- a/pkg/volume/scaleio/sio_util.go +++ b/pkg/volume/scaleio/sio_util.go @@ -31,11 +31,17 @@ import ( volutil "k8s.io/kubernetes/pkg/volume/util" ) +type volSourceAttribs struct { + volName, + fsType string + readOnly bool +} + var ( confKey = struct { gateway, sslEnabled, - secretRef, + secretName, system, protectionDomain, storagePool, @@ -47,12 +53,13 @@ var ( readOnly, username, password, - namespace, + secretNamespace, sdcGuid string }{ gateway: "gateway", sslEnabled: "sslEnabled", - secretRef: "secretRef", + secretName: "secretRef", + secretNamespace: "secretNamespace", system: "system", protectionDomain: "protectionDomain", storagePool: "storagePool", @@ -64,7 +71,6 @@ var ( readOnly: "readOnly", username: "username", password: "password", - namespace: "namespace", sdcGuid: "sdcGuid", } sdcGuidLabelName = "scaleio.sdcGuid" @@ -79,23 +85,32 @@ var ( protectionDomainNotProvidedErr = errors.New("ScaleIO protection domain not provided") ) -// mapScaleIOVolumeSource maps attributes from a ScaleIOVolumeSource to config -func mapVolumeSource(config map[string]string, source *api.ScaleIOVolumeSource) { - config[confKey.gateway] = source.Gateway - config[confKey.secretRef] = func() string { - if source.SecretRef != nil { - return string(source.SecretRef.Name) - } - return "" - }() - config[confKey.system] = source.System - config[confKey.volumeName] = source.VolumeName - config[confKey.sslEnabled] = strconv.FormatBool(source.SSLEnabled) - config[confKey.protectionDomain] = source.ProtectionDomain - config[confKey.storagePool] = source.StoragePool - config[confKey.storageMode] = source.StorageMode - config[confKey.fsType] = source.FSType - config[confKey.readOnly] = strconv.FormatBool(source.ReadOnly) +// mapVolumeSpec maps attributes from either ScaleIOVolumeSource or ScaleIOPersistentVolumeSource to config +func mapVolumeSpec(config map[string]string, spec *volume.Spec) { + + if source, err := getScaleIOPersistentVolumeSourceFromSpec(spec); err == nil { + config[confKey.gateway] = source.Gateway + config[confKey.system] = source.System + config[confKey.volumeName] = source.VolumeName + config[confKey.sslEnabled] = strconv.FormatBool(source.SSLEnabled) + config[confKey.protectionDomain] = source.ProtectionDomain + config[confKey.storagePool] = source.StoragePool + config[confKey.storageMode] = source.StorageMode + config[confKey.fsType] = source.FSType + config[confKey.readOnly] = strconv.FormatBool(source.ReadOnly) + } + + if source, err := getScaleIOVolumeSourceFromSpec(spec); err == nil { + config[confKey.gateway] = source.Gateway + config[confKey.system] = source.System + config[confKey.volumeName] = source.VolumeName + config[confKey.sslEnabled] = strconv.FormatBool(source.SSLEnabled) + config[confKey.protectionDomain] = source.ProtectionDomain + config[confKey.storagePool] = source.StoragePool + config[confKey.storageMode] = source.StorageMode + config[confKey.fsType] = source.FSType + config[confKey.readOnly] = strconv.FormatBool(source.ReadOnly) + } //optionals applyConfigDefaults(config) @@ -105,7 +120,7 @@ func validateConfigs(config map[string]string) error { if config[confKey.gateway] == "" { return gatewayNotProvidedErr } - if config[confKey.secretRef] == "" { + if config[confKey.secretName] == "" { return secretRefNotProvidedErr } if config[confKey.system] == "" { @@ -202,7 +217,7 @@ func saveConfig(configName string, data map[string]string) error { // attachSecret loads secret object and attaches to configData func attachSecret(plug *sioPlugin, namespace string, configData map[string]string) error { // load secret - secretRefName := configData[confKey.secretRef] + secretRefName := configData[confKey.secretName] kubeClient := plug.host.GetKubeClient() secretMap, err := volutil.GetSecretForPV(namespace, secretRefName, sioPluginName, kubeClient) if err != nil { @@ -244,8 +259,8 @@ func getSdcGuidLabel(plug *sioPlugin) (string, error) { return label, nil } -// getVolumeSourceFromSpec safely extracts ScaleIOVolumeSource from spec -func getVolumeSourceFromSpec(spec *volume.Spec) (*api.ScaleIOVolumeSource, error) { +// getVolumeSourceFromSpec safely extracts ScaleIOVolumeSource or ScaleIOPersistentVolumeSource from spec +func getVolumeSourceFromSpec(spec *volume.Spec) (interface{}, error) { if spec.Volume != nil && spec.Volume.ScaleIO != nil { return spec.Volume.ScaleIO, nil } @@ -257,6 +272,66 @@ func getVolumeSourceFromSpec(spec *volume.Spec) (*api.ScaleIOVolumeSource, error return nil, fmt.Errorf("ScaleIO not defined in spec") } +func getVolumeSourceAttribs(spec *volume.Spec) (*volSourceAttribs, error) { + attribs := new(volSourceAttribs) + if pvSource, err := getScaleIOPersistentVolumeSourceFromSpec(spec); err == nil { + attribs.volName = pvSource.VolumeName + attribs.fsType = pvSource.FSType + attribs.readOnly = pvSource.ReadOnly + } else if pSource, err := getScaleIOVolumeSourceFromSpec(spec); err == nil { + attribs.volName = pSource.VolumeName + attribs.fsType = pSource.FSType + attribs.readOnly = pSource.ReadOnly + } else { + msg := log("failed to get ScaleIOVolumeSource or ScaleIOPersistentVolumeSource from spec") + glog.Error(msg) + return nil, errors.New(msg) + } + return attribs, nil +} + +func getScaleIOPersistentVolumeSourceFromSpec(spec *volume.Spec) (*api.ScaleIOPersistentVolumeSource, error) { + source, err := getVolumeSourceFromSpec(spec) + if err != nil { + return nil, err + } + if val, ok := source.(*api.ScaleIOPersistentVolumeSource); ok { + return val, nil + } + return nil, fmt.Errorf("spec is not a valid ScaleIOPersistentVolume type") +} + +func getScaleIOVolumeSourceFromSpec(spec *volume.Spec) (*api.ScaleIOVolumeSource, error) { + source, err := getVolumeSourceFromSpec(spec) + if err != nil { + return nil, err + } + if val, ok := source.(*api.ScaleIOVolumeSource); ok { + return val, nil + } + return nil, fmt.Errorf("spec is not a valid ScaleIOVolume type") +} + +func getSecretAndNamespaceFromSpec(spec *volume.Spec, pod *api.Pod) (secretName string, secretNS string, err error) { + if source, err := getScaleIOVolumeSourceFromSpec(spec); err == nil { + secretName = source.SecretRef.Name + if pod != nil { + secretNS = pod.Namespace + } + } else if source, err := getScaleIOPersistentVolumeSourceFromSpec(spec); err == nil { + if source.SecretRef != nil { + secretName = source.SecretRef.Name + secretNS = source.SecretRef.Namespace + if secretNS == "" && pod != nil { + secretNS = pod.Namespace + } + } + } else { + return "", "", errors.New("failed to get ScaleIOVolumeSource or ScaleIOPersistentVolumeSource") + } + return secretName, secretNS, nil +} + func log(msg string, parts ...interface{}) string { return fmt.Sprintf(fmt.Sprintf("scaleio: %s", msg), parts...) } diff --git a/pkg/volume/scaleio/sio_util_test.go b/pkg/volume/scaleio/sio_util_test.go index 360b67e3fac..4be4bd75fe9 100644 --- a/pkg/volume/scaleio/sio_util_test.go +++ b/pkg/volume/scaleio/sio_util_test.go @@ -25,6 +25,7 @@ import ( api "k8s.io/api/core/v1" utiltesting "k8s.io/client-go/util/testing" + "k8s.io/kubernetes/pkg/volume" ) var ( @@ -48,7 +49,7 @@ var ( confKey.gateway: "http://sio/", confKey.volSpecName: testSioVolName, confKey.volumeName: "sio-vol", - confKey.secretRef: "sio-secret", + confKey.secretName: "sio-secret", confKey.protectionDomain: "defaultPD", confKey.storagePool: "deraultSP", confKey.fsType: "xfs", @@ -60,7 +61,7 @@ var ( func TestUtilMapVolumeSource(t *testing.T) { data := make(map[string]string) - mapVolumeSource(data, vol.VolumeSource.ScaleIO) + mapVolumeSpec(data, volume.NewSpecFromVolume(vol)) if data[confKey.gateway] != "http://test.scaleio:1111" { t.Error("Unexpected gateway value") } @@ -79,9 +80,6 @@ func TestUtilMapVolumeSource(t *testing.T) { if data[confKey.fsType] != "ext4" { t.Error("Unexpected fstype value") } - if data[confKey.secretRef] != "test-secret" { - t.Error("Unexpected secret ref value") - } if data[confKey.sslEnabled] != "false" { t.Error("Unexpected sslEnabled value") } @@ -92,8 +90,8 @@ func TestUtilMapVolumeSource(t *testing.T) { func TestUtilValidateConfigs(t *testing.T) { data := map[string]string{ - confKey.secretRef: "sio-secret", - confKey.system: "sio", + confKey.secretName: "sio-secret", + confKey.system: "sio", } if err := validateConfigs(data); err != gatewayNotProvidedErr { t.Error("Expecting error for missing gateway, but did not get it") @@ -105,7 +103,7 @@ func TestUtilApplyConfigDefaults(t *testing.T) { confKey.system: "sio", confKey.gateway: "http://sio/", confKey.volumeName: "sio-vol", - confKey.secretRef: "test-secret", + confKey.secretName: "test-secret", } applyConfigDefaults(data) @@ -130,7 +128,7 @@ func TestUtilApplyConfigDefaults(t *testing.T) { if data[confKey.storageMode] != "ThinProvisioned" { t.Error("Unexpected storage mode value") } - if data[confKey.secretRef] != "test-secret" { + if data[confKey.secretName] != "test-secret" { t.Error("Unexpected secret ref value") } if data[confKey.sslEnabled] != "false" { @@ -157,7 +155,7 @@ func TestUtilSaveConfig(t *testing.T) { config := path.Join(tmpDir, testConfigFile) data := map[string]string{ confKey.gateway: "https://test-gateway/", - confKey.secretRef: "sio-secret", + confKey.secretName: "sio-secret", confKey.sslEnabled: "false", } if err := saveConfig(config, data); err != nil { @@ -178,7 +176,7 @@ func TestUtilSaveConfig(t *testing.T) { } func TestUtilAttachSecret(t *testing.T) { - plugMgr, tmpDir := newPluginMgr(t) + plugMgr, tmpDir := newPluginMgr(t, makeScaleIOSecret(testSecret, testns)) defer os.RemoveAll(tmpDir) plug, err := plugMgr.FindPluginByName(sioPluginName) diff --git a/pkg/volume/scaleio/sio_volume.go b/pkg/volume/scaleio/sio_volume.go index 85e7fb0b43f..0d0d35608bd 100644 --- a/pkg/volume/scaleio/sio_volume.go +++ b/pkg/volume/scaleio/sio_volume.go @@ -37,19 +37,19 @@ import ( ) type sioVolume struct { - sioMgr *sioMgr - plugin *sioPlugin - pod *api.Pod - podUID types.UID - spec *volume.Spec - source *api.ScaleIOVolumeSource - namespace string - volSpecName string - volName string - readOnly bool - fsType string - options volume.VolumeOptions - configData map[string]string + sioMgr *sioMgr + plugin *sioPlugin + pod *api.Pod + podUID types.UID + spec *volume.Spec + secretName string + secretNamespace string + volSpecName string + volName string + readOnly bool + fsType string + options volume.VolumeOptions + configData map[string]string volume.MetricsNil } @@ -59,7 +59,6 @@ type sioVolume struct { var _ volume.Volume = &sioVolume{} // GetPath returns the path where the volume will be mounted. -// The volumeName is prefixed with the pod's namespace a - func (v *sioVolume) GetPath() string { return v.plugin.host.GetPodVolumeDir( v.podUID, @@ -128,11 +127,11 @@ func (v *sioVolume) SetUpAt(dir string, fsGroup *int64) error { switch { default: options = append(options, "rw") - case isROM && !v.source.ReadOnly: + case isROM && !v.readOnly: options = append(options, "rw") case isROM: options = append(options, "ro") - case v.source.ReadOnly: + case v.readOnly: options = append(options, "ro") } @@ -327,10 +326,10 @@ func (v *sioVolume) Provision() (*api.PersistentVolume, error) { ), }, PersistentVolumeSource: api.PersistentVolumeSource{ - ScaleIO: &api.ScaleIOVolumeSource{ + ScaleIO: &api.ScaleIOPersistentVolumeSource{ Gateway: v.configData[confKey.gateway], SSLEnabled: sslEnabled, - SecretRef: &api.LocalObjectReference{Name: v.configData[confKey.secretRef]}, + SecretRef: &api.SecretReference{Name: v.secretName, Namespace: v.secretNamespace}, System: v.configData[confKey.system], ProtectionDomain: v.configData[confKey.protectionDomain], StoragePool: v.configData[confKey.storagePool], @@ -366,13 +365,17 @@ func (v *sioVolume) setSioMgr() error { glog.V(4).Info(log("previous config file not found, creating new one")) // prepare config data configData = make(map[string]string) - mapVolumeSource(configData, v.source) + mapVolumeSpec(configData, v.spec) + + // additional config data + configData[confKey.secretNamespace] = v.secretNamespace + configData[confKey.secretName] = v.secretName + configData[confKey.volSpecName] = v.volSpecName + if err := validateConfigs(configData); err != nil { glog.Error(log("config setup failed: %s", err)) return err } - configData[confKey.namespace] = v.namespace - configData[confKey.volSpecName] = v.volSpecName // persist config if err := saveConfig(configName, configData); err != nil { @@ -381,7 +384,7 @@ func (v *sioVolume) setSioMgr() error { } } // merge in secret - if err := attachSecret(v.plugin, v.namespace, configData); err != nil { + if err := attachSecret(v.plugin, v.secretNamespace, configData); err != nil { glog.Error(log("failed to load secret: %v", err)) return err } @@ -414,12 +417,13 @@ func (v *sioVolume) resetSioMgr() error { glog.Error(log("failed to load config data: %v", err)) return err } - v.namespace = configData[confKey.namespace] + v.secretName = configData[confKey.secretName] + v.secretNamespace = configData[confKey.secretNamespace] v.volName = configData[confKey.volumeName] v.volSpecName = configData[confKey.volSpecName] // attach secret - if err := attachSecret(v.plugin, v.namespace, configData); err != nil { + if err := attachSecret(v.plugin, v.secretNamespace, configData); err != nil { glog.Error(log("failed to load secret: %v", err)) return err } @@ -446,21 +450,22 @@ func (v *sioVolume) resetSioMgr() error { func (v *sioVolume) setSioMgrFromConfig() error { glog.V(4).Info(log("setting scaleio mgr from available config")) if v.sioMgr == nil { - configData := v.configData - applyConfigDefaults(configData) - if err := validateConfigs(configData); err != nil { + applyConfigDefaults(v.configData) + + v.configData[confKey.volSpecName] = v.volSpecName + + if err := validateConfigs(v.configData); err != nil { glog.Error(log("config data setup failed: %s", err)) return err } - configData[confKey.namespace] = v.namespace - configData[confKey.volSpecName] = v.volSpecName // copy config and attach secret data := map[string]string{} - for k, v := range configData { + for k, v := range v.configData { data[k] = v } - if err := attachSecret(v.plugin, v.namespace, data); err != nil { + + if err := attachSecret(v.plugin, v.secretNamespace, data); err != nil { glog.Error(log("failed to load secret: %v", err)) return err } @@ -483,16 +488,20 @@ func (v *sioVolume) setSioMgrFromSpec() error { if v.sioMgr == nil { // get config data form spec volume source configData := map[string]string{} - mapVolumeSource(configData, v.source) + mapVolumeSpec(configData, v.spec) + + // additional config + configData[confKey.secretNamespace] = v.secretNamespace + configData[confKey.secretName] = v.secretName + configData[confKey.volSpecName] = v.volSpecName + if err := validateConfigs(configData); err != nil { glog.Error(log("config setup failed: %s", err)) return err } - configData[confKey.namespace] = v.namespace - configData[confKey.volSpecName] = v.volSpecName // attach secret object to config data - if err := attachSecret(v.plugin, v.namespace, configData); err != nil { + if err := attachSecret(v.plugin, v.secretNamespace, configData); err != nil { glog.Error(log("failed to load secret: %v", err)) return err } diff --git a/pkg/volume/scaleio/sio_volume_test.go b/pkg/volume/scaleio/sio_volume_test.go index ad2f75475b8..a72d74e5156 100644 --- a/pkg/volume/scaleio/sio_volume_test.go +++ b/pkg/volume/scaleio/sio_volume_test.go @@ -27,6 +27,7 @@ import ( api "k8s.io/api/core/v1" meta "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" fakeclient "k8s.io/client-go/kubernetes/fake" utiltesting "k8s.io/client-go/util/testing" @@ -39,29 +40,18 @@ var ( testSioPD = "default" testSioVol = "vol-0001" testns = "default" + testSecret = "sio-secret" testSioVolName = fmt.Sprintf("%s%s%s", testns, "-", testSioVol) podUID = types.UID("sio-pod") ) -func newPluginMgr(t *testing.T) (*volume.VolumePluginMgr, string) { +func newPluginMgr(t *testing.T, apiObject runtime.Object) (*volume.VolumePluginMgr, string) { tmpDir, err := utiltesting.MkTmpdir("scaleio-test") if err != nil { t.Fatalf("can't make a temp dir: %v", err) } - config := &api.Secret{ - ObjectMeta: meta.ObjectMeta{ - Name: "sio-secret", - Namespace: testns, - UID: "1234567890", - }, - Type: api.SecretType("kubernetes.io/scaleio"), - Data: map[string][]byte{ - "username": []byte("username"), - "password": []byte("password"), - }, - } - fakeClient := fakeclient.NewSimpleClientset(config) + fakeClient := fakeclient.NewSimpleClientset(apiObject) host := volumetest.NewFakeVolumeHostWithNodeLabels( tmpDir, fakeClient, @@ -74,8 +64,23 @@ func newPluginMgr(t *testing.T) (*volume.VolumePluginMgr, string) { return plugMgr, tmpDir } +func makeScaleIOSecret(name, namespace string) *api.Secret { + return &api.Secret{ + ObjectMeta: meta.ObjectMeta{ + Name: name, + Namespace: namespace, + UID: "1234567890", + }, + Type: api.SecretType("kubernetes.io/scaleio"), + Data: map[string][]byte{ + "username": []byte("username"), + "password": []byte("password"), + }, + } +} + func TestVolumeCanSupport(t *testing.T) { - plugMgr, tmpDir := newPluginMgr(t) + plugMgr, tmpDir := newPluginMgr(t, makeScaleIOSecret(testSecret, testns)) defer os.RemoveAll(tmpDir) plug, err := plugMgr.FindPluginByName(sioPluginName) if err != nil { @@ -100,7 +105,7 @@ func TestVolumeCanSupport(t *testing.T) { PersistentVolume: &api.PersistentVolume{ Spec: api.PersistentVolumeSpec{ PersistentVolumeSource: api.PersistentVolumeSource{ - ScaleIO: &api.ScaleIOVolumeSource{}, + ScaleIO: &api.ScaleIOPersistentVolumeSource{}, }, }, }, @@ -111,7 +116,7 @@ func TestVolumeCanSupport(t *testing.T) { } func TestVolumeGetAccessModes(t *testing.T) { - plugMgr, tmpDir := newPluginMgr(t) + plugMgr, tmpDir := newPluginMgr(t, makeScaleIOSecret(testSecret, testns)) defer os.RemoveAll(tmpDir) plug, err := plugMgr.FindPersistentPluginByName(sioPluginName) if err != nil { @@ -131,7 +136,7 @@ func containsMode(modes []api.PersistentVolumeAccessMode, mode api.PersistentVol } func TestVolumeMounterUnmounter(t *testing.T) { - plugMgr, tmpDir := newPluginMgr(t) + plugMgr, tmpDir := newPluginMgr(t, makeScaleIOSecret(testSecret, testns)) defer os.RemoveAll(tmpDir) plug, err := plugMgr.FindPluginByName(sioPluginName) @@ -153,7 +158,7 @@ func TestVolumeMounterUnmounter(t *testing.T) { StoragePool: "default", VolumeName: testSioVol, FSType: "ext4", - SecretRef: &api.LocalObjectReference{Name: "sio-secret"}, + SecretRef: &api.LocalObjectReference{Name: testSecret}, ReadOnly: false, }, }, @@ -236,7 +241,7 @@ func TestVolumeMounterUnmounter(t *testing.T) { if _, err := os.Stat(path); err == nil { t.Errorf("TearDown() failed, volume path still exists: %s", path) } else if !os.IsNotExist(err) { - t.Errorf("SetUp() failed: %v", err) + t.Errorf("TearDown() failed: %v", err) } // are we still mapped if sio.volume.MappedSdcInfo != nil { @@ -245,7 +250,7 @@ func TestVolumeMounterUnmounter(t *testing.T) { } func TestVolumeProvisioner(t *testing.T) { - plugMgr, tmpDir := newPluginMgr(t) + plugMgr, tmpDir := newPluginMgr(t, makeScaleIOSecret(testSecret, testns)) defer os.RemoveAll(tmpDir) plug, err := plugMgr.FindPluginByName(sioPluginName) @@ -274,7 +279,7 @@ func TestVolumeProvisioner(t *testing.T) { confKey.system: "sio", confKey.protectionDomain: testSioPD, confKey.storagePool: "default", - confKey.secretRef: "sio-secret", + confKey.secretName: testSecret, } provisioner, err := sioPlug.NewProvisioner(options) @@ -296,6 +301,17 @@ func TestVolumeProvisioner(t *testing.T) { t.Fatalf("call to Provision() failed: %v", err) } + if spec.Namespace != testns { + t.Fatalf("unexpected namespace %v", spec.Namespace) + } + if spec.Spec.ScaleIO.SecretRef == nil { + t.Fatalf("unexpected nil value for spec.SecretRef") + } + if spec.Spec.ScaleIO.SecretRef.Name != testSecret || + spec.Spec.ScaleIO.SecretRef.Namespace != testns { + t.Fatalf("spec.SecretRef is not being set properly") + } + spec.Spec.ClaimRef = &api.ObjectReference{Namespace: testns} // validate provision @@ -379,7 +395,7 @@ func TestVolumeProvisioner(t *testing.T) { } func TestVolumeProvisionerWithIncompleteConfig(t *testing.T) { - plugMgr, tmpDir := newPluginMgr(t) + plugMgr, tmpDir := newPluginMgr(t, makeScaleIOSecret(testSecret, testns)) defer os.RemoveAll(tmpDir) plug, err := plugMgr.FindPluginByName(sioPluginName) @@ -411,7 +427,7 @@ func TestVolumeProvisionerWithIncompleteConfig(t *testing.T) { } func TestVolumeProvisionerWithZeroCapacity(t *testing.T) { - plugMgr, tmpDir := newPluginMgr(t) + plugMgr, tmpDir := newPluginMgr(t, makeScaleIOSecret(testSecret, testns)) defer os.RemoveAll(tmpDir) plug, err := plugMgr.FindPluginByName(sioPluginName) @@ -440,7 +456,7 @@ func TestVolumeProvisionerWithZeroCapacity(t *testing.T) { confKey.system: "sio", confKey.protectionDomain: testSioPD, confKey.storagePool: "default", - confKey.secretRef: "sio-secret", + confKey.secretName: "sio-secret", } provisioner, _ := sioPlug.NewProvisioner(options) @@ -457,3 +473,63 @@ func TestVolumeProvisionerWithZeroCapacity(t *testing.T) { } } + +func TestVolumeProvisionerWithSecretNamespace(t *testing.T) { + plugMgr, tmpDir := newPluginMgr(t, makeScaleIOSecret("sio-sec", "sio-ns")) + defer os.RemoveAll(tmpDir) + + plug, err := plugMgr.FindPluginByName(sioPluginName) + if err != nil { + t.Fatalf("Can't find the plugin %v", sioPluginName) + } + sioPlug, ok := plug.(*sioPlugin) + if !ok { + t.Fatal("Cannot assert plugin to be type sioPlugin") + } + + options := volume.VolumeOptions{ + ClusterName: "testcluster", + PVName: "pvc-sio-dynamic-vol", + PVC: volumetest.CreateTestPVC("100Mi", []api.PersistentVolumeAccessMode{api.ReadWriteOnce}), + PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimDelete, + } + + options.PVC.Spec.AccessModes = []api.PersistentVolumeAccessMode{ + api.ReadWriteOnce, + } + + options.PVC.Namespace = "pvc-ns" + options.Parameters = map[string]string{ + confKey.gateway: "http://test.scaleio:11111", + confKey.system: "sio", + confKey.protectionDomain: testSioPD, + confKey.storagePool: "default", + confKey.secretName: "sio-sec", + confKey.secretNamespace: "sio-ns", + } + + provisioner, _ := sioPlug.NewProvisioner(options) + sio := newFakeSio() + sioVol := provisioner.(*sioVolume) + if err := sioVol.setSioMgrFromConfig(); err != nil { + t.Fatalf("failed to create scaleio mgr from config: %v", err) + } + sioVol.sioMgr.client = sio + + spec, err := sioVol.Provision() + if err != nil { + t.Fatalf("call to Provision() failed: %v", err) + } + + if spec.GetObjectMeta().GetNamespace() != "pvc-ns" { + t.Fatalf("unexpected spec.namespace %s", spec.GetObjectMeta().GetNamespace()) + } + + if spec.Spec.ScaleIO.SecretRef.Name != "sio-sec" { + t.Fatalf("unexpected spec.ScaleIOPersistentVolume.SecretRef.Name %v", spec.Spec.ScaleIO.SecretRef.Name) + } + + if spec.Spec.ScaleIO.SecretRef.Namespace != "sio-ns" { + t.Fatalf("unexpected spec.ScaleIOPersistentVolume.SecretRef.Namespace %v", spec.Spec.ScaleIO.SecretRef.Namespace) + } +} diff --git a/pkg/volume/secret/BUILD b/pkg/volume/secret/BUILD index 9fc423f420a..61998067431 100644 --- a/pkg/volume/secret/BUILD +++ b/pkg/volume/secret/BUILD @@ -30,8 +30,8 @@ go_library( go_test( name = "go_default_test", srcs = ["secret_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/secret", - library = ":go_default_library", deps = [ "//pkg/volume:go_default_library", "//pkg/volume/empty_dir:go_default_library", diff --git a/pkg/volume/secret/OWNERS b/pkg/volume/secret/OWNERS index 72968b277da..baaff6ff460 100644 --- a/pkg/volume/secret/OWNERS +++ b/pkg/volume/secret/OWNERS @@ -7,13 +7,7 @@ reviewers: - ivan4th - rata - sjenning -- thockin -- smarterclayton -- brendandburns -- derekwaynecarr -- pmorie - saad-ali -- justinsb - jsafrane - rootfs - jingxu97 diff --git a/pkg/volume/secret/secret_test.go b/pkg/volume/secret/secret_test.go index 6ba6a10d4b0..a644d5bf8e0 100644 --- a/pkg/volume/secret/secret_test.go +++ b/pkg/volume/secret/secret_test.go @@ -626,6 +626,6 @@ func doTestCleanAndTeardown(plugin volume.VolumePlugin, podUID types.UID, testVo if _, err := os.Stat(volumePath); err == nil { t.Errorf("TearDown() failed, volume path still exists: %s", volumePath) } else if !os.IsNotExist(err) { - t.Errorf("SetUp() failed: %v", err) + t.Errorf("TearDown() failed: %v", err) } } diff --git a/pkg/volume/storageos/BUILD b/pkg/volume/storageos/BUILD index a66bdc936e3..f20e382beb7 100644 --- a/pkg/volume/storageos/BUILD +++ b/pkg/volume/storageos/BUILD @@ -37,8 +37,8 @@ go_test( "storageos_test.go", "storageos_util_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/storageos", - library = ":go_default_library", deps = [ "//pkg/util/mount:go_default_library", "//pkg/volume:go_default_library", diff --git a/pkg/volume/storageos/storageos_test.go b/pkg/volume/storageos/storageos_test.go index 645dd8c6cef..a77f2c72790 100644 --- a/pkg/volume/storageos/storageos_test.go +++ b/pkg/volume/storageos/storageos_test.go @@ -69,7 +69,7 @@ func TestGetAccessModes(t *testing.T) { if err != nil { t.Errorf("Can't find the plugin by name") } - if !contains(plug.GetAccessModes(), v1.ReadWriteOnce) || !contains(plug.GetAccessModes(), v1.ReadOnlyMany) { + if !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteOnce) || !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadOnlyMany) { t.Errorf("Expected two AccessModeTypes: %s and %s", v1.ReadWriteOnce, v1.ReadOnlyMany) } } @@ -237,7 +237,7 @@ func TestPlugin(t *testing.T) { if _, err := os.Stat(volPath); err == nil { t.Errorf("TearDown() failed, volume path still exists: %s", volPath) } else if !os.IsNotExist(err) { - t.Errorf("SetUp() failed: %v", err) + t.Errorf("TearDown() failed: %v", err) } if !fakeManager.unmountCalled { @@ -308,15 +308,6 @@ func TestPlugin(t *testing.T) { } } -func contains(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool { - for _, m := range modes { - if m == mode { - return true - } - } - return false -} - func TestPersistentClaimReadOnlyFlag(t *testing.T) { tmpDir, err := utiltesting.MkTmpdir("storageos_test") if err != nil { diff --git a/pkg/volume/testing/BUILD b/pkg/volume/testing/BUILD index 1f6424668d5..f4356f4e580 100644 --- a/pkg/volume/testing/BUILD +++ b/pkg/volume/testing/BUILD @@ -18,6 +18,7 @@ go_library( "//pkg/util/mount:go_default_library", "//pkg/util/strings:go_default_library", "//pkg/volume:go_default_library", + "//pkg/volume/util:go_default_library", "//pkg/volume/util/volumehelper:go_default_library", "//vendor/github.com/stretchr/testify/mock:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/volume/testing/OWNERS b/pkg/volume/testing/OWNERS index 2343a0b8a89..fcb4625ad6d 100755 --- a/pkg/volume/testing/OWNERS +++ b/pkg/volume/testing/OWNERS @@ -5,13 +5,7 @@ approvers: - jsafrane reviewers: - jeffvance -- thockin -- smarterclayton -- brendandburns -- derekwaynecarr -- pmorie - saad-ali -- justinsb - jsafrane - rootfs - jingxu97 diff --git a/pkg/volume/testing/testing.go b/pkg/volume/testing/testing.go index d2903f5f144..766792dd8a2 100644 --- a/pkg/volume/testing/testing.go +++ b/pkg/volume/testing/testing.go @@ -39,6 +39,7 @@ import ( "k8s.io/kubernetes/pkg/util/mount" utilstrings "k8s.io/kubernetes/pkg/util/strings" . "k8s.io/kubernetes/pkg/volume" + "k8s.io/kubernetes/pkg/volume/util" "k8s.io/kubernetes/pkg/volume/util/volumehelper" ) @@ -52,6 +53,7 @@ type fakeVolumeHost struct { exec mount.Exec writer io.Writer nodeLabels map[string]string + nodeName string } func NewFakeVolumeHost(rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin) *fakeVolumeHost { @@ -68,6 +70,12 @@ func NewFakeVolumeHostWithNodeLabels(rootDir string, kubeClient clientset.Interf return volHost } +func NewFakeVolumeHostWithNodeName(rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin, nodeName string) *fakeVolumeHost { + volHost := newFakeVolumeHost(rootDir, kubeClient, plugins, nil) + volHost.nodeName = nodeName + return volHost +} + func newFakeVolumeHost(rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin, cloud cloudprovider.Interface) *fakeVolumeHost { host := &fakeVolumeHost{rootDir: rootDir, kubeClient: kubeClient, cloud: cloud} host.mounter = &mount.FakeMounter{} @@ -81,10 +89,18 @@ func (f *fakeVolumeHost) GetPluginDir(podUID string) string { return path.Join(f.rootDir, "plugins", podUID) } +func (f *fakeVolumeHost) GetVolumeDevicePluginDir(podUID string) string { + return path.Join(f.rootDir, "plugins", podUID) +} + func (f *fakeVolumeHost) GetPodVolumeDir(podUID types.UID, pluginName, volumeName string) string { return path.Join(f.rootDir, "pods", string(podUID), "volumes", pluginName, volumeName) } +func (f *fakeVolumeHost) GetPodVolumeDeviceDir(podUID types.UID, pluginName string) string { + return path.Join(f.rootDir, "pods", string(podUID), "volumeDevices", pluginName) +} + func (f *fakeVolumeHost) GetPodPluginDir(podUID types.UID, pluginName string) string { return path.Join(f.rootDir, "pods", string(podUID), "plugins", pluginName) } @@ -147,7 +163,7 @@ func (f *fakeVolumeHost) GetNodeAllocatable() (v1.ResourceList, error) { func (f *fakeVolumeHost) GetSecretFunc() func(namespace, name string) (*v1.Secret, error) { return func(namespace, name string) (*v1.Secret, error) { - return f.kubeClient.Core().Secrets(namespace).Get(name, metav1.GetOptions{}) + return f.kubeClient.CoreV1().Secrets(namespace).Get(name, metav1.GetOptions{}) } } @@ -157,7 +173,7 @@ func (f *fakeVolumeHost) GetExec(pluginName string) mount.Exec { func (f *fakeVolumeHost) GetConfigMapFunc() func(namespace, name string) (*v1.ConfigMap, error) { return func(namespace, name string) (*v1.ConfigMap, error) { - return f.kubeClient.Core().ConfigMaps(namespace).Get(name, metav1.GetOptions{}) + return f.kubeClient.CoreV1().ConfigMaps(namespace).Get(name, metav1.GetOptions{}) } } @@ -168,6 +184,10 @@ func (f *fakeVolumeHost) GetNodeLabels() (map[string]string, error) { return f.nodeLabels, nil } +func (f *fakeVolumeHost) GetNodeName() types.NodeName { + return types.NodeName(f.nodeName) +} + func ProbeVolumePlugins(config VolumeConfig) []VolumePlugin { if _, ok := config.OtherAttributes["fake-property"]; ok { return []VolumePlugin{ @@ -194,13 +214,16 @@ type FakeVolumePlugin struct { NewAttacherCallCount int NewDetacherCallCount int - Mounters []*FakeVolume - Unmounters []*FakeVolume - Attachers []*FakeVolume - Detachers []*FakeVolume + Mounters []*FakeVolume + Unmounters []*FakeVolume + Attachers []*FakeVolume + Detachers []*FakeVolume + BlockVolumeMappers []*FakeVolume + BlockVolumeUnmappers []*FakeVolume } var _ VolumePlugin = &FakeVolumePlugin{} +var _ BlockVolumePlugin = &FakeVolumePlugin{} var _ RecyclableVolumePlugin = &FakeVolumePlugin{} var _ DeletableVolumePlugin = &FakeVolumePlugin{} var _ ProvisionableVolumePlugin = &FakeVolumePlugin{} @@ -280,6 +303,44 @@ func (plugin *FakeVolumePlugin) GetUnmounters() (Unmounters []*FakeVolume) { return plugin.Unmounters } +// Block volume support +func (plugin *FakeVolumePlugin) NewBlockVolumeMapper(spec *Spec, pod *v1.Pod, opts VolumeOptions) (BlockVolumeMapper, error) { + plugin.Lock() + defer plugin.Unlock() + volume := plugin.getFakeVolume(&plugin.BlockVolumeMappers) + if pod != nil { + volume.PodUID = pod.UID + } + volume.VolName = spec.Name() + volume.Plugin = plugin + return volume, nil +} + +// Block volume support +func (plugin *FakeVolumePlugin) GetBlockVolumeMapper() (BlockVolumeMappers []*FakeVolume) { + plugin.RLock() + defer plugin.RUnlock() + return plugin.BlockVolumeMappers +} + +// Block volume support +func (plugin *FakeVolumePlugin) NewBlockVolumeUnmapper(volName string, podUID types.UID) (BlockVolumeUnmapper, error) { + plugin.Lock() + defer plugin.Unlock() + volume := plugin.getFakeVolume(&plugin.BlockVolumeUnmappers) + volume.PodUID = podUID + volume.VolName = volName + volume.Plugin = plugin + return volume, nil +} + +// Block volume support +func (plugin *FakeVolumePlugin) GetBlockVolumeUnmapper() (BlockVolumeUnmappers []*FakeVolume) { + plugin.RLock() + defer plugin.RUnlock() + return plugin.BlockVolumeUnmappers +} + func (plugin *FakeVolumePlugin) NewAttacher() (Attacher, error) { plugin.Lock() defer plugin.Unlock() @@ -345,10 +406,66 @@ func (plugin *FakeVolumePlugin) ConstructVolumeSpec(volumeName, mountPath string }, nil } +// Block volume support +func (plugin *FakeVolumePlugin) ConstructBlockVolumeSpec(podUID types.UID, volumeName, mountPath string) (*Spec, error) { + return &Spec{ + Volume: &v1.Volume{ + Name: volumeName, + }, + }, nil +} + func (plugin *FakeVolumePlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) { return []string{}, nil } +type FakeFileVolumePlugin struct { +} + +func (plugin *FakeFileVolumePlugin) Init(host VolumeHost) error { + return nil +} + +func (plugin *FakeFileVolumePlugin) GetPluginName() string { + return "fake-file-plugin" +} + +func (plugin *FakeFileVolumePlugin) GetVolumeName(spec *Spec) (string, error) { + return "", nil +} + +func (plugin *FakeFileVolumePlugin) CanSupport(spec *Spec) bool { + return true +} + +func (plugin *FakeFileVolumePlugin) RequiresRemount() bool { + return false +} + +func (plugin *FakeFileVolumePlugin) SupportsMountOption() bool { + return false +} + +func (plugin *FakeFileVolumePlugin) SupportsBulkVolumeVerification() bool { + return false +} + +func (plugin *FakeFileVolumePlugin) NewMounter(spec *Spec, podRef *v1.Pod, opts VolumeOptions) (Mounter, error) { + return nil, nil +} + +func (plugin *FakeFileVolumePlugin) NewUnmounter(name string, podUID types.UID) (Unmounter, error) { + return nil, nil +} + +func (plugin *FakeFileVolumePlugin) ConstructVolumeSpec(volumeName, mountPath string) (*Spec, error) { + return nil, nil +} + +func NewFakeFileVolumePlugin() []VolumePlugin { + return []VolumePlugin{&FakeFileVolumePlugin{}} +} + type FakeVolume struct { sync.RWMutex PodUID types.UID @@ -364,6 +481,10 @@ type FakeVolume struct { MountDeviceCallCount int UnmountDeviceCallCount int GetDeviceMountPathCallCount int + SetUpDeviceCallCount int + TearDownDeviceCallCount int + GlobalMapPathCallCount int + PodDeviceMapPathCallCount int } func (_ *FakeVolume) GetAttributes() Attributes { @@ -422,6 +543,76 @@ func (fv *FakeVolume) TearDownAt(dir string) error { return os.RemoveAll(dir) } +// Block volume support +func (fv *FakeVolume) SetUpDevice() (string, error) { + fv.Lock() + defer fv.Unlock() + fv.SetUpDeviceCallCount++ + return "", nil +} + +// Block volume support +func (fv *FakeVolume) GetSetUpDeviceCallCount() int { + fv.RLock() + defer fv.RUnlock() + return fv.SetUpDeviceCallCount +} + +// Block volume support +func (fv *FakeVolume) GetGlobalMapPath(spec *Spec) (string, error) { + fv.RLock() + defer fv.RUnlock() + fv.GlobalMapPathCallCount++ + return fv.getGlobalMapPath() +} + +// Block volume support +func (fv *FakeVolume) getGlobalMapPath() (string, error) { + return path.Join(fv.Plugin.Host.GetVolumeDevicePluginDir(utilstrings.EscapeQualifiedNameForDisk(fv.Plugin.PluginName)), "pluginDependentPath"), nil +} + +// Block volume support +func (fv *FakeVolume) GetGlobalMapPathCallCount() int { + fv.RLock() + defer fv.RUnlock() + return fv.GlobalMapPathCallCount +} + +// Block volume support +func (fv *FakeVolume) GetPodDeviceMapPath() (string, string) { + fv.RLock() + defer fv.RUnlock() + fv.PodDeviceMapPathCallCount++ + return fv.getPodDeviceMapPath() +} + +// Block volume support +func (fv *FakeVolume) getPodDeviceMapPath() (string, string) { + return path.Join(fv.Plugin.Host.GetPodVolumeDeviceDir(fv.PodUID, utilstrings.EscapeQualifiedNameForDisk(fv.Plugin.PluginName))), fv.VolName +} + +// Block volume support +func (fv *FakeVolume) GetPodDeviceMapPathCallCount() int { + fv.RLock() + defer fv.RUnlock() + return fv.PodDeviceMapPathCallCount +} + +// Block volume support +func (fv *FakeVolume) TearDownDevice(mapPath string, devicePath string) error { + fv.Lock() + defer fv.Unlock() + fv.TearDownDeviceCallCount++ + return nil +} + +// Block volume support +func (fv *FakeVolume) GetTearDownDeviceCallCount() int { + fv.RLock() + defer fv.RUnlock() + return fv.TearDownDeviceCallCount +} + func (fv *FakeVolume) Attach(spec *Spec, nodeName types.NodeName) (string, error) { fv.Lock() defer fv.Unlock() @@ -439,7 +630,7 @@ func (fv *FakeVolume) WaitForAttach(spec *Spec, devicePath string, pod *v1.Pod, fv.Lock() defer fv.Unlock() fv.WaitForAttachCallCount++ - return "", nil + return "/dev/sdb", nil } func (fv *FakeVolume) GetWaitForAttachCallCount() int { @@ -468,7 +659,7 @@ func (fv *FakeVolume) GetMountDeviceCallCount() int { return fv.MountDeviceCallCount } -func (fv *FakeVolume) Detach(deviceMountPath string, nodeName types.NodeName) error { +func (fv *FakeVolume) Detach(volumeName string, nodeName types.NodeName) error { fv.Lock() defer fv.Unlock() fv.DetachCallCount++ @@ -540,6 +731,62 @@ func (fc *FakeProvisioner) Provision() (*v1.PersistentVolume, error) { return pv, nil } +var _ util.BlockVolumePathHandler = &FakeVolumePathHandler{} + +//NewDeviceHandler Create a new IoHandler implementation +func NewBlockVolumePathHandler() util.BlockVolumePathHandler { + return &FakeVolumePathHandler{} +} + +type FakeVolumePathHandler struct { + sync.RWMutex +} + +func (fv *FakeVolumePathHandler) MapDevice(devicePath string, mapDir string, linkName string) error { + // nil is success, else error + return nil +} + +func (fv *FakeVolumePathHandler) UnmapDevice(mapDir string, linkName string) error { + // nil is success, else error + return nil +} + +func (fv *FakeVolumePathHandler) RemoveMapPath(mapPath string) error { + // nil is success, else error + return nil +} + +func (fv *FakeVolumePathHandler) IsSymlinkExist(mapPath string) (bool, error) { + // nil is success, else error + return true, nil +} + +func (fv *FakeVolumePathHandler) GetDeviceSymlinkRefs(devPath string, mapPath string) ([]string, error) { + // nil is success, else error + return []string{}, nil +} + +func (fv *FakeVolumePathHandler) FindGlobalMapPathUUIDFromPod(pluginDir, mapPath string, podUID types.UID) (string, error) { + // nil is success, else error + return "", nil +} + +func (fv *FakeVolumePathHandler) AttachFileDevice(path string) (string, error) { + // nil is success, else error + return "", nil +} + +func (fv *FakeVolumePathHandler) GetLoopDevice(path string) (string, error) { + // nil is success, else error + return "/dev/loop1", nil +} + +func (fv *FakeVolumePathHandler) RemoveLoopDevice(device string) error { + // nil is success, else error + return nil +} + // FindEmptyDirectoryUsageOnTmpfs finds the expected usage of an empty directory existing on // a tmpfs filesystem on this system. func FindEmptyDirectoryUsageOnTmpfs() (*resource.Quantity, error) { @@ -758,6 +1005,93 @@ func VerifyZeroDetachCallCount(fakeVolumePlugin *FakeVolumePlugin) error { return nil } +// VerifySetUpDeviceCallCount ensures that at least one of the Mappers for this +// plugin has the expectedSetUpDeviceCallCount number of calls. Otherwise it +// returns an error. +func VerifySetUpDeviceCallCount( + expectedSetUpDeviceCallCount int, + fakeVolumePlugin *FakeVolumePlugin) error { + for _, mapper := range fakeVolumePlugin.GetBlockVolumeMapper() { + actualCallCount := mapper.GetSetUpDeviceCallCount() + if actualCallCount >= expectedSetUpDeviceCallCount { + return nil + } + } + + return fmt.Errorf( + "No Mapper have expected SetUpDeviceCallCount. Expected: <%v>.", + expectedSetUpDeviceCallCount) +} + +// VerifyTearDownDeviceCallCount ensures that at least one of the Unmappers for this +// plugin has the expectedTearDownDeviceCallCount number of calls. Otherwise it +// returns an error. +func VerifyTearDownDeviceCallCount( + expectedTearDownDeviceCallCount int, + fakeVolumePlugin *FakeVolumePlugin) error { + for _, unmapper := range fakeVolumePlugin.GetBlockVolumeUnmapper() { + actualCallCount := unmapper.GetTearDownDeviceCallCount() + if actualCallCount >= expectedTearDownDeviceCallCount { + return nil + } + } + + return fmt.Errorf( + "No Unmapper have expected TearDownDeviceCallCount. Expected: <%v>.", + expectedTearDownDeviceCallCount) +} + +// VerifyZeroTearDownDeviceCallCount ensures that all Mappers for this plugin have a +// zero TearDownDeviceCallCount. Otherwise it returns an error. +func VerifyZeroTearDownDeviceCallCount(fakeVolumePlugin *FakeVolumePlugin) error { + for _, unmapper := range fakeVolumePlugin.GetBlockVolumeUnmapper() { + actualCallCount := unmapper.GetTearDownDeviceCallCount() + if actualCallCount != 0 { + return fmt.Errorf( + "At least one unmapper has non-zero TearDownDeviceCallCount: <%v>.", + actualCallCount) + } + } + + return nil +} + +// VerifyGetGlobalMapPathCallCount ensures that at least one of the Mappers for this +// plugin has the expectedGlobalMapPathCallCount number of calls. Otherwise it returns +// an error. +func VerifyGetGlobalMapPathCallCount( + expectedGlobalMapPathCallCount int, + fakeVolumePlugin *FakeVolumePlugin) error { + for _, mapper := range fakeVolumePlugin.GetBlockVolumeMapper() { + actualCallCount := mapper.GetGlobalMapPathCallCount() + if actualCallCount == expectedGlobalMapPathCallCount { + return nil + } + } + + return fmt.Errorf( + "No Mappers have expected GetGlobalMapPathCallCount. Expected: <%v>.", + expectedGlobalMapPathCallCount) +} + +// VerifyGetPodDeviceMapPathCallCount ensures that at least one of the Mappers for this +// plugin has the expectedPodDeviceMapPathCallCount number of calls. Otherwise it returns +// an error. +func VerifyGetPodDeviceMapPathCallCount( + expectedPodDeviceMapPathCallCount int, + fakeVolumePlugin *FakeVolumePlugin) error { + for _, mapper := range fakeVolumePlugin.GetBlockVolumeMapper() { + actualCallCount := mapper.GetPodDeviceMapPathCallCount() + if actualCallCount == expectedPodDeviceMapPathCallCount { + return nil + } + } + + return fmt.Errorf( + "No Mappers have expected GetPodDeviceMapPathCallCount. Expected: <%v>.", + expectedPodDeviceMapPathCallCount) +} + // GetTestVolumePluginMgr creates, initializes, and returns a test volume plugin // manager and fake volume plugin using a fake volume host. func GetTestVolumePluginMgr( @@ -803,3 +1137,12 @@ func MetricsEqualIgnoreTimestamp(a *Metrics, b *Metrics) bool { inodesUsed := a.InodesUsed == b.InodesUsed return available && capacity && used && inodes && inodesFree && inodesUsed } + +func ContainsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool { + for _, m := range modes { + if m == mode { + return true + } + } + return false +} diff --git a/pkg/volume/util.go b/pkg/volume/util.go index b976ce947f5..9945bd88b42 100644 --- a/pkg/volume/util.go +++ b/pkg/volume/util.go @@ -36,7 +36,13 @@ import ( "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" - volutil "k8s.io/kubernetes/pkg/volume/util" +) + +const ( + // GB - GigaByte size + GB = 1000 * 1000 * 1000 + // GIB - GibiByte size + GIB = 1024 * 1024 * 1024 ) type RecycleEventRecorder func(eventtype, message string) @@ -48,8 +54,8 @@ type RecycleEventRecorder func(eventtype, message string) // attempted before returning. // // In case there is a pod with the same namespace+name already running, this -// function assumes it's an older instance of the recycler pod and watches -// this old pod instead of starting a new one. +// function deletes it as it is not able to judge if it is an old recycler +// or user has forged a fake recycler to block Kubernetes from recycling.// // // pod - the pod designed by a volume plugin to recycle the volume. pod.Name // will be overwritten with unique name based on PV.Name. @@ -81,20 +87,43 @@ func internalRecycleVolumeByWatchingPodUntilCompletion(pvName string, pod *v1.Po _, err = recyclerClient.CreatePod(pod) if err != nil { if errors.IsAlreadyExists(err) { - glog.V(5).Infof("old recycler pod %q found for volume", pod.Name) - } else { - return fmt.Errorf("unexpected error creating recycler pod: %+v\n", err) + deleteErr := recyclerClient.DeletePod(pod.Name, pod.Namespace) + if deleteErr != nil { + return fmt.Errorf("failed to delete old recycler pod %s/%s: %s", pod.Namespace, pod.Name, deleteErr) + } + // Recycler will try again and the old pod will be hopefuly deleted + // at that time. + return fmt.Errorf("old recycler pod found, will retry later") } + return fmt.Errorf("unexpected error creating recycler pod: %+v", err) } - defer func(pod *v1.Pod) { - glog.V(2).Infof("deleting recycler pod %s/%s", pod.Namespace, pod.Name) - if err := recyclerClient.DeletePod(pod.Name, pod.Namespace); err != nil { - glog.Errorf("failed to delete recycler pod %s/%s: %v", pod.Namespace, pod.Name, err) - } - }(pod) + err = waitForPod(pod, recyclerClient, podCh) - // Now only the old pod or the new pod run. Watch it until it finishes - // and send all events on the pod to the PV + // In all cases delete the recycler pod and log its result. + glog.V(2).Infof("deleting recycler pod %s/%s", pod.Namespace, pod.Name) + deleteErr := recyclerClient.DeletePod(pod.Name, pod.Namespace) + if deleteErr != nil { + glog.Errorf("failed to delete recycler pod %s/%s: %v", pod.Namespace, pod.Name, err) + } + + // Returning recycler error is preferred, the pod will be deleted again on + // the next retry. + if err != nil { + return fmt.Errorf("failed to recycle volume: %s", err) + } + + // Recycle succeeded but we failed to delete the recycler pod. Report it, + // the controller will re-try recycling the PV again shortly. + if deleteErr != nil { + return fmt.Errorf("failed to delete recycler pod: %s", deleteErr) + } + + return nil +} + +// waitForPod watches the pod it until it finishes and send all events on the +// pod to the PV. +func waitForPod(pod *v1.Pod, recyclerClient recyclerClient, podCh <-chan watch.Event) error { for { event, ok := <-podCh if !ok { @@ -164,15 +193,15 @@ type realRecyclerClient struct { } func (c *realRecyclerClient) CreatePod(pod *v1.Pod) (*v1.Pod, error) { - return c.client.Core().Pods(pod.Namespace).Create(pod) + return c.client.CoreV1().Pods(pod.Namespace).Create(pod) } func (c *realRecyclerClient) GetPod(name, namespace string) (*v1.Pod, error) { - return c.client.Core().Pods(namespace).Get(name, metav1.GetOptions{}) + return c.client.CoreV1().Pods(namespace).Get(name, metav1.GetOptions{}) } func (c *realRecyclerClient) DeletePod(name, namespace string) error { - return c.client.Core().Pods(namespace).Delete(name, nil) + return c.client.CoreV1().Pods(namespace).Delete(name, nil) } func (c *realRecyclerClient) Event(eventtype, message string) { @@ -189,13 +218,13 @@ func (c *realRecyclerClient) WatchPod(name, namespace string, stopChannel chan s Watch: true, } - podWatch, err := c.client.Core().Pods(namespace).Watch(options) + podWatch, err := c.client.CoreV1().Pods(namespace).Watch(options) if err != nil { return nil, err } eventSelector, _ := fields.ParseSelector("involvedObject.name=" + name) - eventWatch, err := c.client.Core().Events(namespace).Watch(metav1.ListOptions{ + eventWatch, err := c.client.CoreV1().Events(namespace).Watch(metav1.ListOptions{ FieldSelector: eventSelector.String(), Watch: true, }) @@ -251,9 +280,8 @@ func CalculateTimeoutForVolume(minimumTimeout, timeoutIncrement int, pv *v1.Pers timeout := (pvSize / giSize) * int64(timeoutIncrement) if timeout < int64(minimumTimeout) { return int64(minimumTimeout) - } else { - return timeout } + return timeout } // RoundUpSize calculates how many allocation units are needed to accommodate @@ -265,6 +293,18 @@ func RoundUpSize(volumeSizeBytes int64, allocationUnitBytes int64) int64 { return (volumeSizeBytes + allocationUnitBytes - 1) / allocationUnitBytes } +// RoundUpToGB rounds up given quantity to chunks of GB +func RoundUpToGB(size resource.Quantity) int64 { + requestBytes := size.Value() + return RoundUpSize(requestBytes, GB) +} + +// RoundUpToGiB rounds up given quantity upto chunks of GiB +func RoundUpToGiB(size resource.Quantity) int64 { + requestBytes := size.Value() + return RoundUpSize(requestBytes, GIB) +} + // GenerateVolumeName returns a PV name with clusterName prefix. The function // should be used to generate a name of GCE PD or Cinder volume. It basically // adds "-dynamic-" before the PV name, making sure the resulting @@ -281,7 +321,7 @@ func GenerateVolumeName(clusterName, pvName string, maxLength int) string { return prefix + "-" + pvName } -// Check if the path from the mounter is empty. +// GetPath checks if the path from the mounter is empty. func GetPath(mounter Mounter) (string, error) { path := mounter.GetPath() if path == "" { @@ -290,7 +330,7 @@ func GetPath(mounter Mounter) (string, error) { return path, nil } -// ChooseZone implements our heuristics for choosing a zone for volume creation based on the volume name +// ChooseZoneForVolume implements our heuristics for choosing a zone for volume creation based on the volume name // Volumes are generally round-robin-ed across all active zones, using the hash of the PVC Name. // However, if the PVCName ends with `-`, we will hash the prefix, and then add the integer to the hash. // This means that a StatefulSet's volumes (`claimname-statefulsetname-id`) will spread across available zones, @@ -400,13 +440,6 @@ func getPVCNameHashAndIndexOffset(pvcName string) (hash uint32, index uint32) { func UnmountViaEmptyDir(dir string, host VolumeHost, volName string, volSpec Spec, podUID types.UID) error { glog.V(3).Infof("Tearing down volume %v for pod %v at %v", volName, podUID, dir) - if pathExists, pathErr := volutil.PathExists(dir); pathErr != nil { - return fmt.Errorf("Error checking if path exists: %v", pathErr) - } else if !pathExists { - glog.Warningf("Warning: Unmount skipped because path does not exist: %v", dir) - return nil - } - // Wrap EmptyDir, let it do the teardown. wrapped, err := host.NewWrapperUnmounter(volName, volSpec, podUID) if err != nil { @@ -479,3 +512,12 @@ func AccessModesContainedInAll(indexedModes []v1.PersistentVolumeAccessMode, req } return true } + +// GetWindowsPath get a windows path +func GetWindowsPath(path string) string { + windowsPath := strings.Replace(path, "/", "\\", -1) + if strings.HasPrefix(windowsPath, "\\") { + windowsPath = "c:" + windowsPath + } + return windowsPath +} diff --git a/pkg/volume/util/BUILD b/pkg/volume/util/BUILD index 53c2f8b3745..22c8fd82cd4 100644 --- a/pkg/volume/util/BUILD +++ b/pkg/volume/util/BUILD @@ -11,44 +11,121 @@ go_library( srcs = [ "atomic_writer.go", "device_util.go", - "device_util_unsupported.go", "doc.go", - "fs_unsupported.go", + "error.go", + "finalizer.go", "io_util.go", "metrics.go", "util.go", ] + select({ - "@io_bazel_rules_go//go/platform:darwin_amd64": [ - "fs.go", + "@io_bazel_rules_go//go/platform:android": [ + "device_util_unsupported.go", + "fs_unsupported.go", + "util_unsupported.go", ], - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:darwin": [ + "device_util_unsupported.go", + "fs.go", + "util_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "device_util_unsupported.go", + "fs_unsupported.go", + "util_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "device_util_unsupported.go", + "fs_unsupported.go", + "util_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:linux": [ "device_util_linux.go", "fs.go", + "util_linux.go", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "device_util_unsupported.go", + "fs_unsupported.go", + "util_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "device_util_unsupported.go", + "fs_unsupported.go", + "util_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "device_util_unsupported.go", + "fs_unsupported.go", + "util_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "device_util_unsupported.go", + "fs_unsupported.go", + "util_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "device_util_unsupported.go", + "fs_unsupported.go", + "util_unsupported.go", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "device_util_unsupported.go", + "fs_unsupported.go", + "util_unsupported.go", ], "//conditions:default": [], }), importpath = "k8s.io/kubernetes/pkg/volume/util", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/v1/helper:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core/v1/helper:go_default_library", "//pkg/kubelet/apis:go_default_library", "//pkg/util/mount:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/storage/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", ] + select({ - "@io_bazel_rules_go//go/platform:darwin_amd64": [ - "//vendor/golang.org/x/sys/unix:go_default_library", + "@io_bazel_rules_go//go/platform:android": [ + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", ], - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:darwin": [ "//vendor/golang.org/x/sys/unix:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "//vendor/golang.org/x/sys/unix:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", ], "//conditions:default": [], }), @@ -59,22 +136,22 @@ go_test( srcs = [ "util_test.go", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "atomic_writer_test.go", "device_util_linux_test.go", ], "//conditions:default": [], }), + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/util", - library = ":go_default_library", deps = [ - "//pkg/api/install:go_default_library", - "//pkg/api/v1/helper:go_default_library", + "//pkg/apis/core/install:go_default_library", + "//pkg/apis/core/v1/helper:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", ] + select({ - "@io_bazel_rules_go//go/platform:linux_amd64": [ + "@io_bazel_rules_go//go/platform:linux": [ "//vendor/k8s.io/client-go/util/testing:go_default_library", ], "//conditions:default": [], diff --git a/pkg/volume/util/OWNERS b/pkg/volume/util/OWNERS index abea2824a81..7a41ec511a7 100755 --- a/pkg/volume/util/OWNERS +++ b/pkg/volume/util/OWNERS @@ -1,11 +1,7 @@ approvers: - saad-ali reviewers: -- thockin -- smarterclayton -- pmorie - saad-ali -- justinsb - rootfs - jingxu97 - screeley44 diff --git a/pkg/volume/util/device_util.go b/pkg/volume/util/device_util.go index 9098d7b8597..9d504bc2e78 100644 --- a/pkg/volume/util/device_util.go +++ b/pkg/volume/util/device_util.go @@ -19,6 +19,7 @@ package util //DeviceUtil is a util for common device methods type DeviceUtil interface { FindMultipathDeviceForDevice(disk string) string + FindSlaveDevicesOnMultipath(disk string) []string } type deviceHandler struct { diff --git a/pkg/volume/util/device_util_linux.go b/pkg/volume/util/device_util_linux.go index 0d9851140f6..297004bf950 100644 --- a/pkg/volume/util/device_util_linux.go +++ b/pkg/volume/util/device_util_linux.go @@ -20,6 +20,7 @@ package util import ( "errors" + "path" "strings" ) @@ -59,3 +60,23 @@ func findDeviceForPath(path string, io IoUtil) (string, error) { } return "", errors.New("Illegal path for device " + devicePath) } + +// FindSlaveDevicesOnMultipath given a dm name like /dev/dm-1, find all devices +// which are managed by the devicemapper dm-1. +func (handler *deviceHandler) FindSlaveDevicesOnMultipath(dm string) []string { + var devices []string + io := handler.get_io + // Split path /dev/dm-1 into "", "dev", "dm-1" + parts := strings.Split(dm, "/") + if len(parts) != 3 || !strings.HasPrefix(parts[1], "dev") { + return devices + } + disk := parts[2] + slavesPath := path.Join("/sys/block/", disk, "/slaves/") + if files, err := io.ReadDir(slavesPath); err == nil { + for _, f := range files { + devices = append(devices, path.Join("/dev/", f.Name())) + } + } + return devices +} diff --git a/pkg/volume/util/device_util_linux_test.go b/pkg/volume/util/device_util_linux_test.go index 94ac9b5a47b..6ee7891a808 100644 --- a/pkg/volume/util/device_util_linux_test.go +++ b/pkg/volume/util/device_util_linux_test.go @@ -21,6 +21,7 @@ package util import ( "errors" "os" + "reflect" "testing" "time" ) @@ -29,11 +30,14 @@ type mockOsIOHandler struct{} func (handler *mockOsIOHandler) ReadDir(dirname string) ([]os.FileInfo, error) { switch dirname { - case "/sys/block/dm-2/slaves/": - f := &fakeFileInfo{ + case "/sys/block/dm-1/slaves": + f1 := &fakeFileInfo{ name: "sda", } - return []os.FileInfo{f}, nil + f2 := &fakeFileInfo{ + name: "sdb", + } + return []os.FileInfo{f1, f2}, nil case "/sys/block/": f1 := &fakeFileInfo{ name: "sda", @@ -62,8 +66,10 @@ func (handler *mockOsIOHandler) EvalSymlinks(path string) (string, error) { "/returns/a/dev": "/dev/sde", "/returns/non/dev": "/sys/block", "/dev/disk/by-path/127.0.0.1:3260-eui.02004567A425678D-lun-0": "/dev/sda", + "/dev/disk/by-path/127.0.0.3:3260-eui.03004567A425678D-lun-0": "/dev/sdb", "/dev/dm-2": "/dev/dm-2", "/dev/dm-3": "/dev/dm-3", + "/dev/sdc": "/dev/sdc", "/dev/sde": "/dev/sde", } return links[path], nil @@ -140,3 +146,15 @@ func TestFindDeviceForPath(t *testing.T) { } } + +func TestFindSlaveDevicesOnMultipath(t *testing.T) { + mockDeviceUtil := NewDeviceHandler(&mockOsIOHandler{}) + devices := mockDeviceUtil.FindSlaveDevicesOnMultipath("/dev/dm-1") + if !reflect.DeepEqual(devices, []string{"/dev/sda", "/dev/sdb"}) { + t.Fatalf("failed to find devices managed by mpio device. /dev/sda, /dev/sdb expected got [%s]", devices) + } + dev := mockDeviceUtil.FindSlaveDevicesOnMultipath("/dev/sdc") + if len(dev) != 0 { + t.Fatalf("mpio device not found '' expected got [%s]", dev) + } +} diff --git a/pkg/volume/util/device_util_unsupported.go b/pkg/volume/util/device_util_unsupported.go index 6afb1f13915..0b41eb3741c 100644 --- a/pkg/volume/util/device_util_unsupported.go +++ b/pkg/volume/util/device_util_unsupported.go @@ -22,3 +22,9 @@ package util func (handler *deviceHandler) FindMultipathDeviceForDevice(device string) string { return "" } + +// FindSlaveDevicesOnMultipath unsupported returns "" +func (handler *deviceHandler) FindSlaveDevicesOnMultipath(disk string) []string { + out := []string{} + return out +} diff --git a/pkg/volume/util/error.go b/pkg/volume/util/error.go new file mode 100644 index 00000000000..2c9655866b0 --- /dev/null +++ b/pkg/volume/util/error.go @@ -0,0 +1,41 @@ +/* +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 util + +import ( + k8stypes "k8s.io/apimachinery/pkg/types" +) + +// This error on attach indicates volume is attached to a different node +// than we expected. +type DanglingAttachError struct { + msg string + CurrentNode k8stypes.NodeName + DevicePath string +} + +func (err *DanglingAttachError) Error() string { + return err.msg +} + +func NewDanglingError(msg string, node k8stypes.NodeName, devicePath string) error { + return &DanglingAttachError{ + msg: msg, + CurrentNode: node, + DevicePath: devicePath, + } +} diff --git a/pkg/volume/util/finalizer.go b/pkg/volume/util/finalizer.go new file mode 100644 index 00000000000..1bc03ad8e78 --- /dev/null +++ b/pkg/volume/util/finalizer.go @@ -0,0 +1,22 @@ +/* +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 util + +const ( + // Name of finalizer on PVCs that have a running pod. + PVCProtectionFinalizer = "kubernetes.io/pvc-protection" +) diff --git a/pkg/volume/util/metrics.go b/pkg/volume/util/metrics.go index 087bbfff416..e3af12df890 100644 --- a/pkg/volume/util/metrics.go +++ b/pkg/volume/util/metrics.go @@ -24,8 +24,9 @@ import ( var storageOperationMetric = prometheus.NewHistogramVec( prometheus.HistogramOpts{ - Name: "storage_operation_duration_seconds", - Help: "Storage operation duration", + Name: "storage_operation_duration_seconds", + Help: "Storage operation duration", + Buckets: []float64{.1, .25, .5, 1, 2.5, 5, 10, 15, 25, 50}, }, []string{"volume_plugin", "operation_name"}, ) @@ -48,12 +49,12 @@ func registerMetrics() { } // OperationCompleteHook returns a hook to call when an operation is completed -func OperationCompleteHook(plugin, operationName string) func(error) { +func OperationCompleteHook(plugin, operationName string) func(*error) { requestTime := time.Now() - opComplete := func(err error) { + opComplete := func(err *error) { timeTaken := time.Since(requestTime).Seconds() // Create metric with operation name and plugin name - if err != nil { + if *err != nil { storageOperationErrorMetric.WithLabelValues(plugin, operationName).Inc() } else { storageOperationMetric.WithLabelValues(plugin, operationName).Observe(timeTaken) diff --git a/pkg/volume/util/nestedpendingoperations/BUILD b/pkg/volume/util/nestedpendingoperations/BUILD index c0623b47e1d..b048f8121c3 100644 --- a/pkg/volume/util/nestedpendingoperations/BUILD +++ b/pkg/volume/util/nestedpendingoperations/BUILD @@ -22,8 +22,8 @@ go_library( go_test( name = "go_default_test", srcs = ["nestedpendingoperations_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/util/nestedpendingoperations", - library = ":go_default_library", deps = [ "//pkg/volume/util/types:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/volume/util/nestedpendingoperations/nestedpendingoperations.go b/pkg/volume/util/nestedpendingoperations/nestedpendingoperations.go index 82462c1f2f6..526ea403cee 100644 --- a/pkg/volume/util/nestedpendingoperations/nestedpendingoperations.go +++ b/pkg/volume/util/nestedpendingoperations/nestedpendingoperations.go @@ -55,7 +55,7 @@ type NestedPendingOperations interface { // concatenation of volumeName and podName is removed from the list of // executing operations allowing a new operation to be started with the // volumeName without error. - Run(volumeName v1.UniqueVolumeName, podName types.UniquePodName, operationFunc func() error, operationCompleteFunc func(error)) error + Run(volumeName v1.UniqueVolumeName, podName types.UniquePodName, generatedOperations types.GeneratedOperations) error // Wait blocks until all operations are completed. This is typically // necessary during tests - the test should wait until all operations finish @@ -94,8 +94,7 @@ type operation struct { func (grm *nestedPendingOperations) Run( volumeName v1.UniqueVolumeName, podName types.UniquePodName, - operationFunc func() error, - operationCompleteFunc func(error)) error { + generatedOperations types.GeneratedOperations) error { grm.lock.Lock() defer grm.lock.Unlock() opExists, previousOpIndex := grm.isOperationExists(volumeName, podName) @@ -128,15 +127,20 @@ func (grm *nestedPendingOperations) Run( }) } - go func() (err error) { + go func() (eventErr, detailedErr error) { // Handle unhandled panics (very unlikely) defer k8sRuntime.HandleCrash() // Handle completion of and error, if any, from operationFunc() - defer grm.operationComplete(volumeName, podName, &err) - defer operationCompleteFunc(err) + defer grm.operationComplete(volumeName, podName, &detailedErr) + if generatedOperations.CompleteFunc != nil { + defer generatedOperations.CompleteFunc(&detailedErr) + } + if generatedOperations.EventRecorderFunc != nil { + defer generatedOperations.EventRecorderFunc(&eventErr) + } // Handle panic, if any, from operationFunc() - defer k8sRuntime.RecoverFromPanic(&err) - return operationFunc() + defer k8sRuntime.RecoverFromPanic(&detailedErr) + return generatedOperations.OperationFunc() }() return nil diff --git a/pkg/volume/util/nestedpendingoperations/nestedpendingoperations_test.go b/pkg/volume/util/nestedpendingoperations/nestedpendingoperations_test.go index 8882303bde5..5865f96c21a 100644 --- a/pkg/volume/util/nestedpendingoperations/nestedpendingoperations_test.go +++ b/pkg/volume/util/nestedpendingoperations/nestedpendingoperations_test.go @@ -47,10 +47,10 @@ func Test_NewGoRoutineMap_Positive_SingleOp(t *testing.T) { // Arrange grm := NewNestedPendingOperations(false /* exponentialBackOffOnError */) volumeName := v1.UniqueVolumeName("volume-name") - operation := func() error { return nil } + operation := func() (error, error) { return nil, nil } // Act - err := grm.Run(volumeName, "" /* operationSubName */, operation, func(error) {}) + err := grm.Run(volumeName, "" /* operationSubName */, types.GeneratedOperations{OperationFunc: operation}) // Assert if err != nil { @@ -63,11 +63,11 @@ func Test_NewGoRoutineMap_Positive_TwoOps(t *testing.T) { grm := NewNestedPendingOperations(false /* exponentialBackOffOnError */) volume1Name := v1.UniqueVolumeName("volume1-name") volume2Name := v1.UniqueVolumeName("volume2-name") - operation := func() error { return nil } + operation := func() (error, error) { return nil, nil } // Act - err1 := grm.Run(volume1Name, "" /* operationSubName */, operation, func(error) {}) - err2 := grm.Run(volume2Name, "" /* operationSubName */, operation, func(error) {}) + err1 := grm.Run(volume1Name, "" /* operationSubName */, types.GeneratedOperations{OperationFunc: operation}) + err2 := grm.Run(volume2Name, "" /* operationSubName */, types.GeneratedOperations{OperationFunc: operation}) // Assert if err1 != nil { @@ -85,11 +85,11 @@ func Test_NewGoRoutineMap_Positive_TwoSubOps(t *testing.T) { volumeName := v1.UniqueVolumeName("volume-name") operation1PodName := types.UniquePodName("operation1-podname") operation2PodName := types.UniquePodName("operation2-podname") - operation := func() error { return nil } + operation := func() (error, error) { return nil, nil } // Act - err1 := grm.Run(volumeName, operation1PodName, operation, func(error) {}) - err2 := grm.Run(volumeName, operation2PodName, operation, func(error) {}) + err1 := grm.Run(volumeName, operation1PodName, types.GeneratedOperations{OperationFunc: operation}) + err2 := grm.Run(volumeName, operation2PodName, types.GeneratedOperations{OperationFunc: operation}) // Assert if err1 != nil { @@ -105,10 +105,10 @@ func Test_NewGoRoutineMap_Positive_SingleOpWithExpBackoff(t *testing.T) { // Arrange grm := NewNestedPendingOperations(true /* exponentialBackOffOnError */) volumeName := v1.UniqueVolumeName("volume-name") - operation := func() error { return nil } + operation := func() (error, error) { return nil, nil } // Act - err := grm.Run(volumeName, "" /* operationSubName */, operation, func(error) {}) + err := grm.Run(volumeName, "" /* operationSubName */, types.GeneratedOperations{OperationFunc: operation}) // Assert if err != nil { @@ -122,7 +122,7 @@ func Test_NewGoRoutineMap_Positive_SecondOpAfterFirstCompletes(t *testing.T) { volumeName := v1.UniqueVolumeName("volume-name") operation1DoneCh := make(chan interface{}, 0 /* bufferSize */) operation1 := generateCallbackFunc(operation1DoneCh) - err1 := grm.Run(volumeName, "" /* operationSubName */, operation1, func(error) {}) + err1 := grm.Run(volumeName, "" /* operationSubName */, types.GeneratedOperations{OperationFunc: operation1}) if err1 != nil { t.Fatalf("NewGoRoutine failed. Expected: Actual: <%v>", err1) } @@ -133,7 +133,7 @@ func Test_NewGoRoutineMap_Positive_SecondOpAfterFirstCompletes(t *testing.T) { err2 := retryWithExponentialBackOff( time.Duration(initialOperationWaitTimeShort), func() (bool, error) { - err := grm.Run(volumeName, "" /* operationSubName */, operation2, func(error) {}) + err := grm.Run(volumeName, "" /* operationSubName */, types.GeneratedOperations{OperationFunc: operation2}) if err != nil { t.Logf("Warning: NewGoRoutine failed with %v. Will retry.", err) return false, nil @@ -154,7 +154,7 @@ func Test_NewGoRoutineMap_Positive_SecondOpAfterFirstCompletesWithExpBackoff(t * volumeName := v1.UniqueVolumeName("volume-name") operation1DoneCh := make(chan interface{}, 0 /* bufferSize */) operation1 := generateCallbackFunc(operation1DoneCh) - err1 := grm.Run(volumeName, "" /* operationSubName */, operation1, func(error) {}) + err1 := grm.Run(volumeName, "" /* operationSubName */, types.GeneratedOperations{OperationFunc: operation1}) if err1 != nil { t.Fatalf("NewGoRoutine failed. Expected: Actual: <%v>", err1) } @@ -165,7 +165,7 @@ func Test_NewGoRoutineMap_Positive_SecondOpAfterFirstCompletesWithExpBackoff(t * err2 := retryWithExponentialBackOff( time.Duration(initialOperationWaitTimeShort), func() (bool, error) { - err := grm.Run(volumeName, "" /* operationSubName */, operation2, func(error) {}) + err := grm.Run(volumeName, "" /* operationSubName */, types.GeneratedOperations{OperationFunc: operation2}) if err != nil { t.Logf("Warning: NewGoRoutine failed with %v. Will retry.", err) return false, nil @@ -185,7 +185,7 @@ func Test_NewGoRoutineMap_Positive_SecondOpAfterFirstPanics(t *testing.T) { grm := NewNestedPendingOperations(false /* exponentialBackOffOnError */) volumeName := v1.UniqueVolumeName("volume-name") operation1 := generatePanicFunc() - err1 := grm.Run(volumeName, "" /* operationSubName */, operation1, func(error) {}) + err1 := grm.Run(volumeName, "" /* operationSubName */, types.GeneratedOperations{OperationFunc: operation1}) if err1 != nil { t.Fatalf("NewGoRoutine failed. Expected: Actual: <%v>", err1) } @@ -195,7 +195,7 @@ func Test_NewGoRoutineMap_Positive_SecondOpAfterFirstPanics(t *testing.T) { err2 := retryWithExponentialBackOff( time.Duration(initialOperationWaitTimeShort), func() (bool, error) { - err := grm.Run(volumeName, "" /* operationSubName */, operation2, func(error) {}) + err := grm.Run(volumeName, "" /* operationSubName */, types.GeneratedOperations{OperationFunc: operation2}) if err != nil { t.Logf("Warning: NewGoRoutine failed with %v. Will retry.", err) return false, nil @@ -215,7 +215,7 @@ func Test_NewGoRoutineMap_Positive_SecondOpAfterFirstPanicsWithExpBackoff(t *tes grm := NewNestedPendingOperations(true /* exponentialBackOffOnError */) volumeName := v1.UniqueVolumeName("volume-name") operation1 := generatePanicFunc() - err1 := grm.Run(volumeName, "" /* operationSubName */, operation1, func(error) {}) + err1 := grm.Run(volumeName, "" /* operationSubName */, types.GeneratedOperations{OperationFunc: operation1}) if err1 != nil { t.Fatalf("NewGoRoutine failed. Expected: Actual: <%v>", err1) } @@ -225,7 +225,7 @@ func Test_NewGoRoutineMap_Positive_SecondOpAfterFirstPanicsWithExpBackoff(t *tes err2 := retryWithExponentialBackOff( time.Duration(initialOperationWaitTimeLong), // Longer duration to accommodate for backoff func() (bool, error) { - err := grm.Run(volumeName, "" /* operationSubName */, operation2, func(error) {}) + err := grm.Run(volumeName, "" /* operationSubName */, types.GeneratedOperations{OperationFunc: operation2}) if err != nil { t.Logf("Warning: NewGoRoutine failed with %v. Will retry.", err) return false, nil @@ -246,14 +246,14 @@ func Test_NewGoRoutineMap_Negative_SecondOpBeforeFirstCompletes(t *testing.T) { volumeName := v1.UniqueVolumeName("volume-name") operation1DoneCh := make(chan interface{}, 0 /* bufferSize */) operation1 := generateWaitFunc(operation1DoneCh) - err1 := grm.Run(volumeName, "" /* operationSubName */, operation1, func(error) {}) + err1 := grm.Run(volumeName, "" /* operationSubName */, types.GeneratedOperations{OperationFunc: operation1}) if err1 != nil { t.Fatalf("NewGoRoutine failed. Expected: Actual: <%v>", err1) } operation2 := generateNoopFunc() // Act - err2 := grm.Run(volumeName, "" /* operationSubName */, operation2, func(error) {}) + err2 := grm.Run(volumeName, "" /* operationSubName */, types.GeneratedOperations{OperationFunc: operation2}) // Assert if err2 == nil { @@ -271,14 +271,14 @@ func Test_NewGoRoutineMap_Negative_SecondSubOpBeforeFirstCompletes2(t *testing.T operationPodName := types.UniquePodName("operation-podname") operation1DoneCh := make(chan interface{}, 0 /* bufferSize */) operation1 := generateWaitFunc(operation1DoneCh) - err1 := grm.Run(volumeName, operationPodName, operation1, func(error) {}) + err1 := grm.Run(volumeName, operationPodName, types.GeneratedOperations{OperationFunc: operation1}) if err1 != nil { t.Fatalf("NewGoRoutine failed. Expected: Actual: <%v>", err1) } operation2 := generateNoopFunc() // Act - err2 := grm.Run(volumeName, operationPodName, operation2, func(error) {}) + err2 := grm.Run(volumeName, operationPodName, types.GeneratedOperations{OperationFunc: operation2}) // Assert if err2 == nil { @@ -296,14 +296,14 @@ func Test_NewGoRoutineMap_Negative_SecondSubOpBeforeFirstCompletes(t *testing.T) operationPodName := types.UniquePodName("operation-podname") operation1DoneCh := make(chan interface{}, 0 /* bufferSize */) operation1 := generateWaitFunc(operation1DoneCh) - err1 := grm.Run(volumeName, operationPodName, operation1, func(error) {}) + err1 := grm.Run(volumeName, operationPodName, types.GeneratedOperations{OperationFunc: operation1}) if err1 != nil { t.Fatalf("NewGoRoutine failed. Expected: Actual: <%v>", err1) } operation2 := generateNoopFunc() // Act - err2 := grm.Run(volumeName, operationPodName, operation2, func(error) {}) + err2 := grm.Run(volumeName, operationPodName, types.GeneratedOperations{OperationFunc: operation2}) // Assert if err2 == nil { @@ -320,14 +320,14 @@ func Test_NewGoRoutineMap_Negative_SecondOpBeforeFirstCompletesWithExpBackoff(t volumeName := v1.UniqueVolumeName("volume-name") operation1DoneCh := make(chan interface{}, 0 /* bufferSize */) operation1 := generateWaitFunc(operation1DoneCh) - err1 := grm.Run(volumeName, "" /* operationSubName */, operation1, func(error) {}) + err1 := grm.Run(volumeName, "" /* operationSubName */, types.GeneratedOperations{OperationFunc: operation1}) if err1 != nil { t.Fatalf("NewGoRoutine failed. Expected: Actual: <%v>", err1) } operation2 := generateNoopFunc() // Act - err2 := grm.Run(volumeName, "" /* operationSubName */, operation2, func(error) {}) + err2 := grm.Run(volumeName, "" /* operationSubName */, types.GeneratedOperations{OperationFunc: operation2}) // Assert if err2 == nil { @@ -344,7 +344,7 @@ func Test_NewGoRoutineMap_Positive_ThirdOpAfterFirstCompletes(t *testing.T) { volumeName := v1.UniqueVolumeName("volume-name") operation1DoneCh := make(chan interface{}, 0 /* bufferSize */) operation1 := generateWaitFunc(operation1DoneCh) - err1 := grm.Run(volumeName, "" /* operationSubName */, operation1, func(error) {}) + err1 := grm.Run(volumeName, "" /* operationSubName */, types.GeneratedOperations{OperationFunc: operation1}) if err1 != nil { t.Fatalf("NewGoRoutine failed. Expected: Actual: <%v>", err1) } @@ -352,7 +352,7 @@ func Test_NewGoRoutineMap_Positive_ThirdOpAfterFirstCompletes(t *testing.T) { operation3 := generateNoopFunc() // Act - err2 := grm.Run(volumeName, "" /* operationSubName */, operation2, func(error) {}) + err2 := grm.Run(volumeName, "" /* operationSubName */, types.GeneratedOperations{OperationFunc: operation2}) // Assert if err2 == nil { @@ -367,7 +367,7 @@ func Test_NewGoRoutineMap_Positive_ThirdOpAfterFirstCompletes(t *testing.T) { err3 := retryWithExponentialBackOff( time.Duration(initialOperationWaitTimeShort), func() (bool, error) { - err := grm.Run(volumeName, "" /* operationSubName */, operation3, func(error) {}) + err := grm.Run(volumeName, "" /* operationSubName */, types.GeneratedOperations{OperationFunc: operation3}) if err != nil { t.Logf("Warning: NewGoRoutine failed with %v. Will retry.", err) return false, nil @@ -388,7 +388,7 @@ func Test_NewGoRoutineMap_Positive_ThirdOpAfterFirstCompletesWithExpBackoff(t *t volumeName := v1.UniqueVolumeName("volume-name") operation1DoneCh := make(chan interface{}, 0 /* bufferSize */) operation1 := generateWaitFunc(operation1DoneCh) - err1 := grm.Run(volumeName, "" /* operationSubName */, operation1, func(error) {}) + err1 := grm.Run(volumeName, "" /* operationSubName */, types.GeneratedOperations{OperationFunc: operation1}) if err1 != nil { t.Fatalf("NewGoRoutine failed. Expected: Actual: <%v>", err1) } @@ -396,7 +396,7 @@ func Test_NewGoRoutineMap_Positive_ThirdOpAfterFirstCompletesWithExpBackoff(t *t operation3 := generateNoopFunc() // Act - err2 := grm.Run(volumeName, "" /* operationSubName */, operation2, func(error) {}) + err2 := grm.Run(volumeName, "" /* operationSubName */, types.GeneratedOperations{OperationFunc: operation2}) // Assert if err2 == nil { @@ -411,7 +411,7 @@ func Test_NewGoRoutineMap_Positive_ThirdOpAfterFirstCompletesWithExpBackoff(t *t err3 := retryWithExponentialBackOff( time.Duration(initialOperationWaitTimeShort), func() (bool, error) { - err := grm.Run(volumeName, "" /* operationSubName */, operation3, func(error) {}) + err := grm.Run(volumeName, "" /* operationSubName */, types.GeneratedOperations{OperationFunc: operation3}) if err != nil { t.Logf("Warning: NewGoRoutine failed with %v. Will retry.", err) return false, nil @@ -471,7 +471,7 @@ func Test_NewGoRoutineMap_Positive_Wait(t *testing.T) { volumeName := v1.UniqueVolumeName("volume-name") operation1DoneCh := make(chan interface{}, 0 /* bufferSize */) operation1 := generateWaitFunc(operation1DoneCh) - err := grm.Run(volumeName, "" /* operationSubName */, operation1, func(error) {}) + err := grm.Run(volumeName, "" /* operationSubName */, types.GeneratedOperations{OperationFunc: operation1}) if err != nil { t.Fatalf("NewGoRoutine failed. Expected: Actual: <%v>", err) } @@ -500,7 +500,7 @@ func Test_NewGoRoutineMap_Positive_WaitWithExpBackoff(t *testing.T) { volumeName := v1.UniqueVolumeName("volume-name") operation1DoneCh := make(chan interface{}, 0 /* bufferSize */) operation1 := generateWaitFunc(operation1DoneCh) - err := grm.Run(volumeName, "" /* operationSubName */, operation1, func(error) {}) + err := grm.Run(volumeName, "" /* operationSubName */, types.GeneratedOperations{OperationFunc: operation1}) if err != nil { t.Fatalf("NewGoRoutine failed. Expected: Actual: <%v>", err) } @@ -522,28 +522,28 @@ func Test_NewGoRoutineMap_Positive_WaitWithExpBackoff(t *testing.T) { } } -func generateCallbackFunc(done chan<- interface{}) func() error { - return func() error { +func generateCallbackFunc(done chan<- interface{}) func() (error, error) { + return func() (error, error) { done <- true - return nil + return nil, nil } } -func generateWaitFunc(done <-chan interface{}) func() error { - return func() error { +func generateWaitFunc(done <-chan interface{}) func() (error, error) { + return func() (error, error) { <-done - return nil + return nil, nil } } -func generatePanicFunc() func() error { - return func() error { +func generatePanicFunc() func() (error, error) { + return func() (error, error) { panic("testing panic") } } -func generateNoopFunc() func() error { - return func() error { return nil } +func generateNoopFunc() func() (error, error) { + return func() (error, error) { return nil, nil } } func retryWithExponentialBackOff(initialDuration time.Duration, fn wait.ConditionFunc) error { diff --git a/pkg/volume/util/operationexecutor/BUILD b/pkg/volume/util/operationexecutor/BUILD index 36eafd136d1..4133ca39449 100644 --- a/pkg/volume/util/operationexecutor/BUILD +++ b/pkg/volume/util/operationexecutor/BUILD @@ -18,6 +18,7 @@ go_library( "//pkg/features:go_default_library", "//pkg/kubelet/events:go_default_library", "//pkg/util/mount:go_default_library", + "//pkg/util/resizefs:go_default_library", "//pkg/volume:go_default_library", "//pkg/volume/util:go_default_library", "//pkg/volume/util/nestedpendingoperations:go_default_library", @@ -28,6 +29,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/tools/record:go_default_library", @@ -37,8 +39,8 @@ go_library( go_test( name = "go_default_test", srcs = ["operation_executor_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/util/operationexecutor", - library = ":go_default_library", deps = [ "//pkg/controller/volume/expand/cache:go_default_library", "//pkg/util/mount:go_default_library", diff --git a/pkg/volume/util/operationexecutor/operation_executor.go b/pkg/volume/util/operationexecutor/operation_executor.go index 8b11358436a..7df9f43d79a 100644 --- a/pkg/volume/util/operationexecutor/operation_executor.go +++ b/pkg/volume/util/operationexecutor/operation_executor.go @@ -29,7 +29,9 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + utilfeature "k8s.io/apiserver/pkg/util/feature" expandcache "k8s.io/kubernetes/pkg/controller/volume/expand/cache" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/util/mount" "k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume/util" @@ -105,6 +107,28 @@ type OperationExecutor interface { // actual state of the world to reflect that. UnmountDevice(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, mounter mount.Interface) error + // MapVolume is used when the volumeMode is 'Block'. + // This method creates a symbolic link to the volume from both the pod + // specified in volumeToMount and global map path. + // Specifically it will: + // * Wait for the device to finish attaching (for attachable volumes only). + // * Update actual state of world to reflect volume is globally mounted/mapped. + // * Map volume to global map path using symbolic link. + // * Map the volume to the pod device map path using symbolic link. + // * Update actual state of world to reflect volume is mounted/mapped to the pod path. + MapVolume(waitForAttachTimeout time.Duration, volumeToMount VolumeToMount, actualStateOfWorld ActualStateOfWorldMounterUpdater) error + + // UnmapVolume unmaps symbolic link to the volume from both the pod device + // map path in volumeToUnmount and global map path. + // And then, updates the actual state of the world to reflect that. + UnmapVolume(volumeToUnmount MountedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater) error + + // UnmapDevice checks number of symbolic links under global map path. + // If number of reference is zero, remove global map path directory and + // free a volume for detach. + // It then updates the actual state of the world to reflect that. + UnmapDevice(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, mounter mount.Interface) error + // VerifyControllerAttachedVolume checks if the specified volume is present // in the specified nodes AttachedVolumes Status field. It uses kubeClient // to fetch the node object. @@ -139,7 +163,7 @@ func NewOperationExecutor( // state of the world cache after successful mount/unmount. type ActualStateOfWorldMounterUpdater interface { // Marks the specified volume as mounted to the specified pod - MarkVolumeAsMounted(podName volumetypes.UniquePodName, podUID types.UID, volumeName v1.UniqueVolumeName, mounter volume.Mounter, outerVolumeSpecName string, volumeGidValue string) error + MarkVolumeAsMounted(podName volumetypes.UniquePodName, podUID types.UID, volumeName v1.UniqueVolumeName, mounter volume.Mounter, blockVolumeMapper volume.BlockVolumeMapper, outerVolumeSpecName string, volumeGidValue string) error // Marks the specified volume as unmounted from the specified pod MarkVolumeAsUnmounted(podName volumetypes.UniquePodName, volumeName v1.UniqueVolumeName) error @@ -495,8 +519,16 @@ type MountedVolume struct { // by kubelet to create container.VolumeMap. Mounter volume.Mounter + // BlockVolumeMapper is the volume mapper used to map this volume. It is required + // by kubelet to create container.VolumeMap. + BlockVolumeMapper volume.BlockVolumeMapper + // VolumeGidValue contains the value of the GID annotation, if present. VolumeGidValue string + + // VolumeSpec is a volume spec containing the specification for the volume + // that should be mounted. + VolumeSpec *volume.Spec } // GenerateMsgDetailed returns detailed msgs for mounted volumes @@ -539,30 +571,28 @@ func (oe *operationExecutor) IsOperationPending(volumeName v1.UniqueVolumeName, func (oe *operationExecutor) AttachVolume( volumeToAttach VolumeToAttach, actualStateOfWorld ActualStateOfWorldAttacherUpdater) error { - attachFunc, plugin, err := + generatedOperations, err := oe.operationGenerator.GenerateAttachVolumeFunc(volumeToAttach, actualStateOfWorld) if err != nil { return err } - opCompleteFunc := util.OperationCompleteHook(plugin, "volume_attach") return oe.pendingOperations.Run( - volumeToAttach.VolumeName, "" /* podName */, attachFunc, opCompleteFunc) + volumeToAttach.VolumeName, "" /* podName */, generatedOperations) } func (oe *operationExecutor) DetachVolume( volumeToDetach AttachedVolume, verifySafeToDetach bool, actualStateOfWorld ActualStateOfWorldAttacherUpdater) error { - detachFunc, plugin, err := + generatedOperations, err := oe.operationGenerator.GenerateDetachVolumeFunc(volumeToDetach, verifySafeToDetach, actualStateOfWorld) if err != nil { return err } - opCompleteFunc := util.OperationCompleteHook(plugin, "volume_detach") return oe.pendingOperations.Run( - volumeToDetach.VolumeName, "" /* podName */, detachFunc, opCompleteFunc) + volumeToDetach.VolumeName, "" /* podName */, generatedOperations) } func (oe *operationExecutor) VerifyVolumesAreAttached( @@ -629,7 +659,7 @@ func (oe *operationExecutor) VerifyVolumesAreAttached( } for pluginName, pluginNodeVolumes := range bulkVerifyPluginsByNode { - bulkVerifyVolumeFunc, err := oe.operationGenerator.GenerateBulkVolumeVerifyFunc( + generatedOperations, err := oe.operationGenerator.GenerateBulkVolumeVerifyFunc( pluginNodeVolumes, pluginName, volumeSpecMapByPlugin[pluginName], @@ -638,10 +668,9 @@ func (oe *operationExecutor) VerifyVolumesAreAttached( glog.Errorf("BulkVerifyVolumes.GenerateBulkVolumeVerifyFunc error bulk verifying volumes for plugin %q with %v", pluginName, err) } - opCompleteFunc := util.OperationCompleteHook(pluginName, "verify_volumes_are_attached") // Ugly hack to ensure - we don't do parallel bulk polling of same volume plugin uniquePluginName := v1.UniqueVolumeName(pluginName) - err = oe.pendingOperations.Run(uniquePluginName, "" /* Pod Name */, bulkVerifyVolumeFunc, opCompleteFunc) + err = oe.pendingOperations.Run(uniquePluginName, "" /* Pod Name */, generatedOperations) if err != nil { glog.Errorf("BulkVerifyVolumes.Run Error bulk volume verification for plugin %q with %v", pluginName, err) } @@ -652,15 +681,14 @@ func (oe *operationExecutor) VerifyVolumesAreAttachedPerNode( attachedVolumes []AttachedVolume, nodeName types.NodeName, actualStateOfWorld ActualStateOfWorldAttacherUpdater) error { - volumesAreAttachedFunc, err := + generatedOperations, err := oe.operationGenerator.GenerateVolumesAreAttachedFunc(attachedVolumes, nodeName, actualStateOfWorld) if err != nil { return err } - opCompleteFunc := util.OperationCompleteHook("", "verify_volumes_are_attached_per_node") // Give an empty UniqueVolumeName so that this operation could be executed concurrently. - return oe.pendingOperations.Run("" /* volumeName */, "" /* podName */, volumesAreAttachedFunc, opCompleteFunc) + return oe.pendingOperations.Run("" /* volumeName */, "" /* podName */, generatedOperations) } func (oe *operationExecutor) MountVolume( @@ -668,7 +696,7 @@ func (oe *operationExecutor) MountVolume( volumeToMount VolumeToMount, actualStateOfWorld ActualStateOfWorldMounterUpdater, isRemount bool) error { - mountFunc, plugin, err := oe.operationGenerator.GenerateMountVolumeFunc( + generatedOperations, err := oe.operationGenerator.GenerateMountVolumeFunc( waitForAttachTimeout, volumeToMount, actualStateOfWorld, isRemount) if err != nil { return err @@ -683,16 +711,15 @@ func (oe *operationExecutor) MountVolume( } // TODO mount_device - opCompleteFunc := util.OperationCompleteHook(plugin, "volume_mount") return oe.pendingOperations.Run( - volumeToMount.VolumeName, podName, mountFunc, opCompleteFunc) + volumeToMount.VolumeName, podName, generatedOperations) } func (oe *operationExecutor) UnmountVolume( volumeToUnmount MountedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater) error { - unmountFunc, plugin, err := + generatedOperations, err := oe.operationGenerator.GenerateUnmountVolumeFunc(volumeToUnmount, actualStateOfWorld) if err != nil { return err @@ -702,50 +729,300 @@ func (oe *operationExecutor) UnmountVolume( // same volume in parallel podName := volumetypes.UniquePodName(volumeToUnmount.PodUID) - opCompleteFunc := util.OperationCompleteHook(plugin, "volume_unmount") return oe.pendingOperations.Run( - volumeToUnmount.VolumeName, podName, unmountFunc, opCompleteFunc) + volumeToUnmount.VolumeName, podName, generatedOperations) } func (oe *operationExecutor) UnmountDevice( deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, mounter mount.Interface) error { - unmountDeviceFunc, plugin, err := + generatedOperations, err := oe.operationGenerator.GenerateUnmountDeviceFunc(deviceToDetach, actualStateOfWorld, mounter) if err != nil { return err } - opCompleteFunc := util.OperationCompleteHook(plugin, "unmount_device") return oe.pendingOperations.Run( - deviceToDetach.VolumeName, "" /* podName */, unmountDeviceFunc, opCompleteFunc) + deviceToDetach.VolumeName, "" /* podName */, generatedOperations) } func (oe *operationExecutor) ExpandVolume(pvcWithResizeRequest *expandcache.PVCWithResizeRequest, resizeMap expandcache.VolumeResizeMap) error { - expandFunc, pluginName, err := oe.operationGenerator.GenerateExpandVolumeFunc(pvcWithResizeRequest, resizeMap) + generatedOperations, err := oe.operationGenerator.GenerateExpandVolumeFunc(pvcWithResizeRequest, resizeMap) if err != nil { return err } uniqueVolumeKey := v1.UniqueVolumeName(pvcWithResizeRequest.UniquePVCKey()) - opCompleteFunc := util.OperationCompleteHook(pluginName, "expand_volume") - return oe.pendingOperations.Run(uniqueVolumeKey, "", expandFunc, opCompleteFunc) + + return oe.pendingOperations.Run(uniqueVolumeKey, "", generatedOperations) +} + +func (oe *operationExecutor) MapVolume( + waitForAttachTimeout time.Duration, + volumeToMount VolumeToMount, + actualStateOfWorld ActualStateOfWorldMounterUpdater) error { + generatedOperations, err := oe.operationGenerator.GenerateMapVolumeFunc( + waitForAttachTimeout, volumeToMount, actualStateOfWorld) + if err != nil { + return err + } + + // Avoid executing map from multiple pods referencing the + // same volume in parallel + podName := nestedpendingoperations.EmptyUniquePodName + // TODO: remove this -- not necessary + if !volumeToMount.PluginIsAttachable { + // Non-attachable volume plugins can execute mount for multiple pods + // referencing the same volume in parallel + podName = volumehelper.GetUniquePodName(volumeToMount.Pod) + } + + return oe.pendingOperations.Run( + volumeToMount.VolumeName, podName, generatedOperations) +} + +func (oe *operationExecutor) UnmapVolume( + volumeToUnmount MountedVolume, + actualStateOfWorld ActualStateOfWorldMounterUpdater) error { + generatedOperations, err := + oe.operationGenerator.GenerateUnmapVolumeFunc(volumeToUnmount, actualStateOfWorld) + if err != nil { + return err + } + + // All volume plugins can execute unmap for multiple pods referencing the + // same volume in parallel + podName := volumetypes.UniquePodName(volumeToUnmount.PodUID) + + return oe.pendingOperations.Run( + volumeToUnmount.VolumeName, podName, generatedOperations) +} + +func (oe *operationExecutor) UnmapDevice( + deviceToDetach AttachedVolume, + actualStateOfWorld ActualStateOfWorldMounterUpdater, + mounter mount.Interface) error { + generatedOperations, err := + oe.operationGenerator.GenerateUnmapDeviceFunc(deviceToDetach, actualStateOfWorld, mounter) + if err != nil { + return err + } + + // Avoid executing unmap device from multiple pods referencing + // the same volume in parallel + podName := nestedpendingoperations.EmptyUniquePodName + + return oe.pendingOperations.Run( + deviceToDetach.VolumeName, podName, generatedOperations) } func (oe *operationExecutor) VerifyControllerAttachedVolume( volumeToMount VolumeToMount, nodeName types.NodeName, actualStateOfWorld ActualStateOfWorldAttacherUpdater) error { - verifyControllerAttachedVolumeFunc, plugin, err := + generatedOperations, err := oe.operationGenerator.GenerateVerifyControllerAttachedVolumeFunc(volumeToMount, nodeName, actualStateOfWorld) if err != nil { return err } - opCompleteFunc := util.OperationCompleteHook(plugin, "verify_controller_attached_volume") return oe.pendingOperations.Run( - volumeToMount.VolumeName, "" /* podName */, verifyControllerAttachedVolumeFunc, opCompleteFunc) + volumeToMount.VolumeName, "" /* podName */, generatedOperations) +} + +// VolumeStateHandler defines a set of operations for handling mount/unmount/detach/reconstruct volume-related operations +type VolumeStateHandler interface { + // Volume is attached, mount/map it + MountVolumeHandler(waitForAttachTimeout time.Duration, volumeToMount VolumeToMount, actualStateOfWorld ActualStateOfWorldMounterUpdater, isRemount bool, remountingLogStr string) error + // Volume is mounted/mapped, unmount/unmap it + UnmountVolumeHandler(mountedVolume MountedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater) error + // Volume is not referenced from pod, unmount/unmap and detach it + UnmountDeviceHandler(attachedVolume AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, mounter mount.Interface) error + // Reconstruct volume from mount path + ReconstructVolumeHandler(plugin volume.VolumePlugin, mapperPlugin volume.BlockVolumePlugin, uid types.UID, podName volumetypes.UniquePodName, volumeSpecName string, mountPath string, pluginName string) (*volume.Spec, error) + // check mount path if volume still exists + CheckVolumeExistence(mountPath, volumeName string, mounter mount.Interface, uniqueVolumeName v1.UniqueVolumeName, podName volumetypes.UniquePodName, podUID types.UID, attachable volume.AttachableVolumePlugin) (bool, error) +} + +// NewVolumeHandler return a new instance of volumeHandler depens on a volumeMode +func NewVolumeHandler(volumeSpec *volume.Spec, oe OperationExecutor) (VolumeStateHandler, error) { + + // TODO: remove feature gate check after no longer needed + var volumeHandler VolumeStateHandler + if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + volumeMode, err := volumehelper.GetVolumeMode(volumeSpec) + if err != nil { + return nil, err + } + if volumeMode == v1.PersistentVolumeFilesystem { + volumeHandler = NewFilesystemVolumeHandler(oe) + } else { + volumeHandler = NewBlockVolumeHandler(oe) + } + } else { + volumeHandler = NewFilesystemVolumeHandler(oe) + } + return volumeHandler, nil +} + +// NewFilesystemVolumeHandler returns a new instance of FilesystemVolumeHandler. +func NewFilesystemVolumeHandler(operationExecutor OperationExecutor) FilesystemVolumeHandler { + return FilesystemVolumeHandler{ + oe: operationExecutor} +} + +// NewBlockVolumeHandler returns a new instance of BlockVolumeHandler. +func NewBlockVolumeHandler(operationExecutor OperationExecutor) BlockVolumeHandler { + return BlockVolumeHandler{ + oe: operationExecutor} +} + +// FilesystemVolumeHandler is VolumeHandler for Filesystem volume +type FilesystemVolumeHandler struct { + oe OperationExecutor +} + +// BlockVolumeHandler is VolumeHandler for Block volume +type BlockVolumeHandler struct { + oe OperationExecutor +} + +// MountVolumeHandler mount/remount a volume when a volume is attached +// This method is handler for filesystem volume +func (f FilesystemVolumeHandler) MountVolumeHandler(waitForAttachTimeout time.Duration, volumeToMount VolumeToMount, actualStateOfWorld ActualStateOfWorldMounterUpdater, isRemount bool, remountingLogStr string) error { + glog.V(12).Infof(volumeToMount.GenerateMsgDetailed("Starting operationExecutor.MountVolume", remountingLogStr)) + err := f.oe.MountVolume( + waitForAttachTimeout, + volumeToMount, + actualStateOfWorld, + isRemount) + return err +} + +// UnmountVolumeHandler unmount a volume if a volume is mounted +// This method is handler for filesystem volume +func (f FilesystemVolumeHandler) UnmountVolumeHandler(mountedVolume MountedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater) error { + glog.V(12).Infof(mountedVolume.GenerateMsgDetailed("Starting operationExecutor.UnmountVolume", "")) + err := f.oe.UnmountVolume( + mountedVolume, + actualStateOfWorld) + return err +} + +// UnmountDeviceHandler unmount and detach a device if a volume isn't referenced +// This method is handler for filesystem volume +func (f FilesystemVolumeHandler) UnmountDeviceHandler(attachedVolume AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, mounter mount.Interface) error { + glog.V(12).Infof(attachedVolume.GenerateMsgDetailed("Starting operationExecutor.UnmountDevice", "")) + err := f.oe.UnmountDevice( + attachedVolume, + actualStateOfWorld, + mounter) + return err +} + +// ReconstructVolumeHandler create volumeSpec from mount path +// This method is handler for filesystem volume +func (f FilesystemVolumeHandler) ReconstructVolumeHandler(plugin volume.VolumePlugin, _ volume.BlockVolumePlugin, _ types.UID, _ volumetypes.UniquePodName, volumeSpecName string, mountPath string, _ string) (*volume.Spec, error) { + glog.V(12).Infof("Starting operationExecutor.ReconstructVolumepodName") + volumeSpec, err := plugin.ConstructVolumeSpec(volumeSpecName, mountPath) + if err != nil { + return nil, err + } + return volumeSpec, nil +} + +// CheckVolumeExistence checks mount path directory if volume still exists, return true if volume is there +// Also return true for non-attachable volume case without mount point check +// This method is handler for filesystem volume +func (f FilesystemVolumeHandler) CheckVolumeExistence(mountPath, volumeName string, mounter mount.Interface, uniqueVolumeName v1.UniqueVolumeName, podName volumetypes.UniquePodName, podUID types.UID, attachable volume.AttachableVolumePlugin) (bool, error) { + if attachable != nil { + var isNotMount bool + var mountCheckErr error + if isNotMount, mountCheckErr = mounter.IsLikelyNotMountPoint(mountPath); mountCheckErr != nil { + return false, fmt.Errorf("Could not check whether the volume %q (spec.Name: %q) pod %q (UID: %q) is mounted with: %v", + uniqueVolumeName, + volumeName, + podName, + podUID, + mountCheckErr) + } + return !isNotMount, nil + } + return true, nil +} + +// MountVolumeHandler creates a map to device if a volume is attached +// This method is handler for block volume +func (b BlockVolumeHandler) MountVolumeHandler(waitForAttachTimeout time.Duration, volumeToMount VolumeToMount, actualStateOfWorld ActualStateOfWorldMounterUpdater, _ bool, _ string) error { + glog.V(12).Infof(volumeToMount.GenerateMsgDetailed("Starting operationExecutor.MapVolume", "")) + err := b.oe.MapVolume( + waitForAttachTimeout, + volumeToMount, + actualStateOfWorld) + return err +} + +// UnmountVolumeHandler unmap a volume if a volume is mapped +// This method is handler for block volume +func (b BlockVolumeHandler) UnmountVolumeHandler(mountedVolume MountedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater) error { + glog.V(12).Infof(mountedVolume.GenerateMsgDetailed("Starting operationExecutor.UnmapVolume", "")) + err := b.oe.UnmapVolume( + mountedVolume, + actualStateOfWorld) + return err +} + +// UnmountDeviceHandler detach a device and remove loopback if a volume isn't referenced +// This method is handler for block volume +func (b BlockVolumeHandler) UnmountDeviceHandler(attachedVolume AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, mounter mount.Interface) error { + glog.V(12).Infof(attachedVolume.GenerateMsgDetailed("Starting operationExecutor.UnmapDevice", "")) + err := b.oe.UnmapDevice( + attachedVolume, + actualStateOfWorld, + mounter) + return err +} + +// ReconstructVolumeHandler create volumeSpec from mount path +// This method is handler for block volume +func (b BlockVolumeHandler) ReconstructVolumeHandler(_ volume.VolumePlugin, mapperPlugin volume.BlockVolumePlugin, uid types.UID, podName volumetypes.UniquePodName, volumeSpecName string, mountPath string, pluginName string) (*volume.Spec, error) { + glog.V(12).Infof("Starting operationExecutor.ReconstructVolume") + if mapperPlugin == nil { + return nil, fmt.Errorf("Could not find block volume plugin %q (spec.Name: %q) pod %q (UID: %q)", + pluginName, + volumeSpecName, + podName, + uid) + } + // mountPath contains volumeName on the path. In the case of block volume, {volumeName} is symbolic link + // corresponding to raw block device. + // ex. mountPath: pods/{podUid}}/{DefaultKubeletVolumeDevicesDirName}/{escapeQualifiedPluginName}/{volumeName} + volumeSpec, err := mapperPlugin.ConstructBlockVolumeSpec(uid, volumeSpecName, mountPath) + if err != nil { + return nil, err + } + return volumeSpec, nil +} + +// CheckVolumeExistence checks mount path directory if volume still exists, then return +// true if volume is there. Either plugin is attachable or non-attachable, the plugin +// should have symbolic link associated to raw block device under pod device map +// if volume exists. +// This method is handler for block volume +func (b BlockVolumeHandler) CheckVolumeExistence(mountPath, volumeName string, mounter mount.Interface, uniqueVolumeName v1.UniqueVolumeName, podName volumetypes.UniquePodName, podUID types.UID, _ volume.AttachableVolumePlugin) (bool, error) { + blkutil := util.NewBlockVolumePathHandler() + var islinkExist bool + var checkErr error + if islinkExist, checkErr = blkutil.IsSymlinkExist(mountPath); checkErr != nil { + return false, fmt.Errorf("Could not check whether the block volume %q (spec.Name: %q) pod %q (UID: %q) is mapped to: %v", + uniqueVolumeName, + volumeName, + podName, + podUID, + checkErr) + } + return islinkExist, nil } // TODO: this is a workaround for the unmount device issue caused by gci mounter. diff --git a/pkg/volume/util/operationexecutor/operation_executor_test.go b/pkg/volume/util/operationexecutor/operation_executor_test.go index 35b7064c0d1..18e68a3ab0d 100644 --- a/pkg/volume/util/operationexecutor/operation_executor_test.go +++ b/pkg/volume/util/operationexecutor/operation_executor_test.go @@ -40,6 +40,10 @@ const ( numVolumesToDetach = 2 numVolumesToVerifyAttached = 2 numVolumesToVerifyControllerAttached = 2 + numVolumesToMap = 2 + numAttachableVolumesToUnmap = 2 + numNonAttachableVolumesToUnmap = 2 + numDevicesToUnmap = 2 ) var _ OperationGenerator = &fakeOperationGenerator{} @@ -76,7 +80,6 @@ func TestOperationExecutor_MountVolume_ConcurrentMountForAttachablePlugins(t *te volumesToMount := make([]VolumeToMount, numVolumesToAttach) pdName := "pd-volume" volumeName := v1.UniqueVolumeName(pdName) - // Act for i := range volumesToMount { podName := "pod-" + strconv.Itoa((i + 1)) @@ -228,6 +231,113 @@ func TestOperationExecutor_VerifyControllerAttachedVolumeConcurrently(t *testing } } +func TestOperationExecutor_MapVolume_ConcurrentMapForNonAttachablePlugins(t *testing.T) { + // Arrange + ch, quit, oe := setup() + volumesToMount := make([]VolumeToMount, numVolumesToMap) + secretName := "secret-volume" + volumeName := v1.UniqueVolumeName(secretName) + + // Act + for i := range volumesToMount { + podName := "pod-" + strconv.Itoa((i + 1)) + pod := getTestPodWithSecret(podName, secretName) + volumesToMount[i] = VolumeToMount{ + Pod: pod, + VolumeName: volumeName, + PluginIsAttachable: false, // this field determines whether the plugin is attachable + ReportedInUse: true, + } + oe.MapVolume(0 /* waitForAttachTimeOut */, volumesToMount[i], nil /* actualStateOfWorldMounterUpdater */) + } + + // Assert + if !isOperationRunConcurrently(ch, quit, numVolumesToMap) { + t.Fatalf("Unable to start map operations in Concurrent for non-attachable volumes") + } +} + +func TestOperationExecutor_MapVolume_ConcurrentMapForAttachablePlugins(t *testing.T) { + // Arrange + ch, quit, oe := setup() + volumesToMount := make([]VolumeToMount, numVolumesToAttach) + pdName := "pd-volume" + volumeName := v1.UniqueVolumeName(pdName) + + // Act + for i := range volumesToMount { + podName := "pod-" + strconv.Itoa((i + 1)) + pod := getTestPodWithGCEPD(podName, pdName) + volumesToMount[i] = VolumeToMount{ + Pod: pod, + VolumeName: volumeName, + PluginIsAttachable: true, // this field determines whether the plugin is attachable + ReportedInUse: true, + } + oe.MapVolume(0 /* waitForAttachTimeout */, volumesToMount[i], nil /* actualStateOfWorldMounterUpdater */) + } + + // Assert + if !isOperationRunSerially(ch, quit) { + t.Fatalf("Map operations should not start concurrently for attachable volumes") + } +} + +func TestOperationExecutor_UnmapVolume_ConcurrentUnmapForAllPlugins(t *testing.T) { + // Arrange + ch, quit, oe := setup() + volumesToUnmount := make([]MountedVolume, numAttachableVolumesToUnmap+numNonAttachableVolumesToUnmap) + pdName := "pd-volume" + secretName := "secret-volume" + + // Act + for i := 0; i < numNonAttachableVolumesToUnmap+numAttachableVolumesToUnmap; i++ { + podName := "pod-" + strconv.Itoa(i+1) + if i < numNonAttachableVolumesToUnmap { + pod := getTestPodWithSecret(podName, secretName) + volumesToUnmount[i] = MountedVolume{ + PodName: volumetypes.UniquePodName(podName), + VolumeName: v1.UniqueVolumeName(secretName), + PodUID: pod.UID, + } + } else { + pod := getTestPodWithGCEPD(podName, pdName) + volumesToUnmount[i] = MountedVolume{ + PodName: volumetypes.UniquePodName(podName), + VolumeName: v1.UniqueVolumeName(pdName), + PodUID: pod.UID, + } + } + oe.UnmapVolume(volumesToUnmount[i], nil /* actualStateOfWorldMounterUpdater */) + } + + // Assert + if !isOperationRunConcurrently(ch, quit, numNonAttachableVolumesToUnmap+numAttachableVolumesToUnmap) { + t.Fatalf("Unable to start unmap operations concurrently for volume plugins") + } +} + +func TestOperationExecutor_UnmapDeviceConcurrently(t *testing.T) { + // Arrange + ch, quit, oe := setup() + attachedVolumes := make([]AttachedVolume, numDevicesToUnmap) + pdName := "pd-volume" + + // Act + for i := range attachedVolumes { + attachedVolumes[i] = AttachedVolume{ + VolumeName: v1.UniqueVolumeName(pdName), + NodeName: "node-name", + } + oe.UnmapDevice(attachedVolumes[i], nil /* actualStateOfWorldMounterUpdater */, nil /* mount.Interface */) + } + + // Assert + if !isOperationRunSerially(ch, quit) { + t.Fatalf("Unmap device operations should not start concurrently") + } +} + type fakeOperationGenerator struct { ch chan interface{} quit chan interface{} @@ -240,65 +350,122 @@ func newFakeOperationGenerator(ch chan interface{}, quit chan interface{}) Opera } } -func (fopg *fakeOperationGenerator) GenerateMountVolumeFunc(waitForAttachTimeout time.Duration, volumeToMount VolumeToMount, actualStateOfWorldMounterUpdater ActualStateOfWorldMounterUpdater, isRemount bool) (func() error, string, error) { - return func() error { +func (fopg *fakeOperationGenerator) GenerateMountVolumeFunc(waitForAttachTimeout time.Duration, volumeToMount VolumeToMount, actualStateOfWorldMounterUpdater ActualStateOfWorldMounterUpdater, isRemount bool) (volumetypes.GeneratedOperations, error) { + opFunc := func() (error, error) { startOperationAndBlock(fopg.ch, fopg.quit) - return nil - }, "", nil -} -func (fopg *fakeOperationGenerator) GenerateUnmountVolumeFunc(volumeToUnmount MountedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater) (func() error, string, error) { - return func() error { - startOperationAndBlock(fopg.ch, fopg.quit) - return nil - }, "", nil -} -func (fopg *fakeOperationGenerator) GenerateAttachVolumeFunc(volumeToAttach VolumeToAttach, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, string, error) { - return func() error { - startOperationAndBlock(fopg.ch, fopg.quit) - return nil - }, "", nil -} -func (fopg *fakeOperationGenerator) GenerateDetachVolumeFunc(volumeToDetach AttachedVolume, verifySafeToDetach bool, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, string, error) { - return func() error { - startOperationAndBlock(fopg.ch, fopg.quit) - return nil - }, "", nil -} -func (fopg *fakeOperationGenerator) GenerateVolumesAreAttachedFunc(attachedVolumes []AttachedVolume, nodeName types.NodeName, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, error) { - return func() error { - startOperationAndBlock(fopg.ch, fopg.quit) - return nil + return nil, nil + } + return volumetypes.GeneratedOperations{ + OperationFunc: opFunc, }, nil } -func (fopg *fakeOperationGenerator) GenerateUnmountDeviceFunc(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, mounter mount.Interface) (func() error, string, error) { - return func() error { +func (fopg *fakeOperationGenerator) GenerateUnmountVolumeFunc(volumeToUnmount MountedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater) (volumetypes.GeneratedOperations, error) { + opFunc := func() (error, error) { startOperationAndBlock(fopg.ch, fopg.quit) - return nil - }, "", nil + return nil, nil + } + return volumetypes.GeneratedOperations{ + OperationFunc: opFunc, + }, nil } -func (fopg *fakeOperationGenerator) GenerateVerifyControllerAttachedVolumeFunc(volumeToMount VolumeToMount, nodeName types.NodeName, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, string, error) { - return func() error { +func (fopg *fakeOperationGenerator) GenerateAttachVolumeFunc(volumeToAttach VolumeToAttach, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (volumetypes.GeneratedOperations, error) { + opFunc := func() (error, error) { startOperationAndBlock(fopg.ch, fopg.quit) - return nil - }, "", nil + return nil, nil + } + return volumetypes.GeneratedOperations{ + OperationFunc: opFunc, + }, nil +} +func (fopg *fakeOperationGenerator) GenerateDetachVolumeFunc(volumeToDetach AttachedVolume, verifySafeToDetach bool, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (volumetypes.GeneratedOperations, error) { + opFunc := func() (error, error) { + startOperationAndBlock(fopg.ch, fopg.quit) + return nil, nil + } + return volumetypes.GeneratedOperations{ + OperationFunc: opFunc, + }, nil +} +func (fopg *fakeOperationGenerator) GenerateVolumesAreAttachedFunc(attachedVolumes []AttachedVolume, nodeName types.NodeName, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (volumetypes.GeneratedOperations, error) { + opFunc := func() (error, error) { + startOperationAndBlock(fopg.ch, fopg.quit) + return nil, nil + } + return volumetypes.GeneratedOperations{ + OperationFunc: opFunc, + }, nil +} +func (fopg *fakeOperationGenerator) GenerateUnmountDeviceFunc(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, mounter mount.Interface) (volumetypes.GeneratedOperations, error) { + opFunc := func() (error, error) { + startOperationAndBlock(fopg.ch, fopg.quit) + return nil, nil + } + return volumetypes.GeneratedOperations{ + OperationFunc: opFunc, + }, nil +} +func (fopg *fakeOperationGenerator) GenerateVerifyControllerAttachedVolumeFunc(volumeToMount VolumeToMount, nodeName types.NodeName, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (volumetypes.GeneratedOperations, error) { + opFunc := func() (error, error) { + startOperationAndBlock(fopg.ch, fopg.quit) + return nil, nil + } + return volumetypes.GeneratedOperations{ + OperationFunc: opFunc, + }, nil } func (fopg *fakeOperationGenerator) GenerateExpandVolumeFunc(pvcWithResizeRequest *expandcache.PVCWithResizeRequest, - resizeMap expandcache.VolumeResizeMap) (func() error, string, error) { - return func() error { + resizeMap expandcache.VolumeResizeMap) (volumetypes.GeneratedOperations, error) { + opFunc := func() (error, error) { startOperationAndBlock(fopg.ch, fopg.quit) - return nil - }, "", nil + return nil, nil + } + return volumetypes.GeneratedOperations{ + OperationFunc: opFunc, + }, nil } func (fopg *fakeOperationGenerator) GenerateBulkVolumeVerifyFunc( pluginNodeVolumes map[types.NodeName][]*volume.Spec, pluginNane string, volumeSpecMap map[*volume.Spec]v1.UniqueVolumeName, - actualStateOfWorldAttacherUpdater ActualStateOfWorldAttacherUpdater) (func() error, error) { - return func() error { + actualStateOfWorldAttacherUpdater ActualStateOfWorldAttacherUpdater) (volumetypes.GeneratedOperations, error) { + opFunc := func() (error, error) { startOperationAndBlock(fopg.ch, fopg.quit) - return nil + return nil, nil + } + return volumetypes.GeneratedOperations{ + OperationFunc: opFunc, + }, nil +} + +func (fopg *fakeOperationGenerator) GenerateMapVolumeFunc(waitForAttachTimeout time.Duration, volumeToMount VolumeToMount, actualStateOfWorldMounterUpdater ActualStateOfWorldMounterUpdater) (volumetypes.GeneratedOperations, error) { + opFunc := func() (error, error) { + startOperationAndBlock(fopg.ch, fopg.quit) + return nil, nil + } + return volumetypes.GeneratedOperations{ + OperationFunc: opFunc, + }, nil +} + +func (fopg *fakeOperationGenerator) GenerateUnmapVolumeFunc(volumeToUnmount MountedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater) (volumetypes.GeneratedOperations, error) { + opFunc := func() (error, error) { + startOperationAndBlock(fopg.ch, fopg.quit) + return nil, nil + } + return volumetypes.GeneratedOperations{ + OperationFunc: opFunc, + }, nil +} + +func (fopg *fakeOperationGenerator) GenerateUnmapDeviceFunc(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, mounter mount.Interface) (volumetypes.GeneratedOperations, error) { + opFunc := func() (error, error) { + startOperationAndBlock(fopg.ch, fopg.quit) + return nil, nil + } + return volumetypes.GeneratedOperations{ + OperationFunc: opFunc, }, nil } diff --git a/pkg/volume/util/operationexecutor/operation_generator.go b/pkg/volume/util/operationexecutor/operation_generator.go index e7e289b93fa..357f1a387e7 100644 --- a/pkg/volume/util/operationexecutor/operation_generator.go +++ b/pkg/volume/util/operationexecutor/operation_generator.go @@ -17,7 +17,9 @@ limitations under the License. package operationexecutor import ( + "encoding/json" "fmt" + "strings" "time" "github.com/golang/glog" @@ -25,6 +27,7 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/strategicpatch" utilfeature "k8s.io/apiserver/pkg/util/feature" clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/record" @@ -32,8 +35,10 @@ import ( "k8s.io/kubernetes/pkg/features" kevents "k8s.io/kubernetes/pkg/kubelet/events" "k8s.io/kubernetes/pkg/util/mount" + "k8s.io/kubernetes/pkg/util/resizefs" "k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume/util" + volumetypes "k8s.io/kubernetes/pkg/volume/util/types" "k8s.io/kubernetes/pkg/volume/util/volumehelper" ) @@ -55,44 +60,58 @@ type operationGenerator struct { // which verifies that the components (binaries, etc.) required to mount // the volume are available on the underlying node before attempting mount. checkNodeCapabilitiesBeforeMount bool + + // blkUtil provides volume path related operations for block volume + blkUtil util.BlockVolumePathHandler } // NewOperationGenerator is returns instance of operationGenerator func NewOperationGenerator(kubeClient clientset.Interface, volumePluginMgr *volume.VolumePluginMgr, recorder record.EventRecorder, - checkNodeCapabilitiesBeforeMount bool) OperationGenerator { + checkNodeCapabilitiesBeforeMount bool, + blkUtil util.BlockVolumePathHandler) OperationGenerator { return &operationGenerator{ kubeClient: kubeClient, volumePluginMgr: volumePluginMgr, recorder: recorder, checkNodeCapabilitiesBeforeMount: checkNodeCapabilitiesBeforeMount, + blkUtil: blkUtil, } } // OperationGenerator interface that extracts out the functions from operation_executor to make it dependency injectable type OperationGenerator interface { // Generates the MountVolume function needed to perform the mount of a volume plugin - GenerateMountVolumeFunc(waitForAttachTimeout time.Duration, volumeToMount VolumeToMount, actualStateOfWorldMounterUpdater ActualStateOfWorldMounterUpdater, isRemount bool) (func() error, string, error) + GenerateMountVolumeFunc(waitForAttachTimeout time.Duration, volumeToMount VolumeToMount, actualStateOfWorldMounterUpdater ActualStateOfWorldMounterUpdater, isRemount bool) (volumetypes.GeneratedOperations, error) // Generates the UnmountVolume function needed to perform the unmount of a volume plugin - GenerateUnmountVolumeFunc(volumeToUnmount MountedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater) (func() error, string, error) + GenerateUnmountVolumeFunc(volumeToUnmount MountedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater) (volumetypes.GeneratedOperations, error) // Generates the AttachVolume function needed to perform attach of a volume plugin - GenerateAttachVolumeFunc(volumeToAttach VolumeToAttach, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, string, error) + GenerateAttachVolumeFunc(volumeToAttach VolumeToAttach, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (volumetypes.GeneratedOperations, error) // Generates the DetachVolume function needed to perform the detach of a volume plugin - GenerateDetachVolumeFunc(volumeToDetach AttachedVolume, verifySafeToDetach bool, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, string, error) + GenerateDetachVolumeFunc(volumeToDetach AttachedVolume, verifySafeToDetach bool, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (volumetypes.GeneratedOperations, error) // Generates the VolumesAreAttached function needed to verify if volume plugins are attached - GenerateVolumesAreAttachedFunc(attachedVolumes []AttachedVolume, nodeName types.NodeName, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, error) + GenerateVolumesAreAttachedFunc(attachedVolumes []AttachedVolume, nodeName types.NodeName, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (volumetypes.GeneratedOperations, error) // Generates the UnMountDevice function needed to perform the unmount of a device - GenerateUnmountDeviceFunc(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, mounter mount.Interface) (func() error, string, error) + GenerateUnmountDeviceFunc(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, mounter mount.Interface) (volumetypes.GeneratedOperations, error) // Generates the function needed to check if the attach_detach controller has attached the volume plugin - GenerateVerifyControllerAttachedVolumeFunc(volumeToMount VolumeToMount, nodeName types.NodeName, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, string, error) + GenerateVerifyControllerAttachedVolumeFunc(volumeToMount VolumeToMount, nodeName types.NodeName, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (volumetypes.GeneratedOperations, error) + + // Generates the MapVolume function needed to perform the map of a volume plugin + GenerateMapVolumeFunc(waitForAttachTimeout time.Duration, volumeToMount VolumeToMount, actualStateOfWorldMounterUpdater ActualStateOfWorldMounterUpdater) (volumetypes.GeneratedOperations, error) + + // Generates the UnmapVolume function needed to perform the unmap of a volume plugin + GenerateUnmapVolumeFunc(volumeToUnmount MountedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater) (volumetypes.GeneratedOperations, error) + + // Generates the UnmapDevice function needed to perform the unmap of a device + GenerateUnmapDeviceFunc(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, mounter mount.Interface) (volumetypes.GeneratedOperations, error) // GetVolumePluginMgr returns volume plugin manager GetVolumePluginMgr() *volume.VolumePluginMgr @@ -100,15 +119,15 @@ type OperationGenerator interface { GenerateBulkVolumeVerifyFunc( map[types.NodeName][]*volume.Spec, string, - map[*volume.Spec]v1.UniqueVolumeName, ActualStateOfWorldAttacherUpdater) (func() error, error) + map[*volume.Spec]v1.UniqueVolumeName, ActualStateOfWorldAttacherUpdater) (volumetypes.GeneratedOperations, error) - GenerateExpandVolumeFunc(*expandcache.PVCWithResizeRequest, expandcache.VolumeResizeMap) (func() error, string, error) + GenerateExpandVolumeFunc(*expandcache.PVCWithResizeRequest, expandcache.VolumeResizeMap) (volumetypes.GeneratedOperations, error) } func (og *operationGenerator) GenerateVolumesAreAttachedFunc( attachedVolumes []AttachedVolume, nodeName types.NodeName, - actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, error) { + actualStateOfWorld ActualStateOfWorldAttacherUpdater) (volumetypes.GeneratedOperations, error) { // volumesPerPlugin maps from a volume plugin to a list of volume specs which belong // to this type of plugin @@ -136,7 +155,7 @@ func (og *operationGenerator) GenerateVolumesAreAttachedFunc( volumeSpecMap[volumeAttached.VolumeSpec] = volumeAttached.VolumeName } - return func() error { + volumesAreAttachedFunc := func() (error, error) { // For each volume plugin, pass the list of volume specs to VolumesAreAttached to check // whether the volumes are still attached. @@ -177,7 +196,13 @@ func (og *operationGenerator) GenerateVolumesAreAttachedFunc( } } } - return nil + return nil, nil + } + + return volumetypes.GeneratedOperations{ + OperationFunc: volumesAreAttachedFunc, + CompleteFunc: util.OperationCompleteHook("", "verify_volumes_are_attached_per_node"), + EventRecorderFunc: nil, // nil because we do not want to generate event on error }, nil } @@ -185,9 +210,9 @@ func (og *operationGenerator) GenerateBulkVolumeVerifyFunc( pluginNodeVolumes map[types.NodeName][]*volume.Spec, pluginName string, volumeSpecMap map[*volume.Spec]v1.UniqueVolumeName, - actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, error) { + actualStateOfWorld ActualStateOfWorldAttacherUpdater) (volumetypes.GeneratedOperations, error) { - return func() error { + bulkVolumeVerifyFunc := func() (error, error) { attachableVolumePlugin, err := og.volumePluginMgr.FindAttachablePluginByName(pluginName) if err != nil || attachableVolumePlugin == nil { @@ -195,7 +220,7 @@ func (og *operationGenerator) GenerateBulkVolumeVerifyFunc( "BulkVerifyVolume.FindAttachablePluginBySpec failed for plugin %q with: %v", pluginName, err) - return nil + return nil, nil } volumeAttacher, newAttacherErr := attachableVolumePlugin.NewAttacher() @@ -205,19 +230,19 @@ func (og *operationGenerator) GenerateBulkVolumeVerifyFunc( "BulkVerifyVolume.NewAttacher failed for getting plugin %q with: %v", attachableVolumePlugin, newAttacherErr) - return nil + return nil, nil } bulkVolumeVerifier, ok := volumeAttacher.(volume.BulkVolumeVerifier) if !ok { glog.Errorf("BulkVerifyVolume failed to type assert attacher %q", bulkVolumeVerifier) - return nil + return nil, nil } attached, bulkAttachErr := bulkVolumeVerifier.BulkVerifyVolumes(pluginNodeVolumes) if bulkAttachErr != nil { glog.Errorf("BulkVerifyVolume.BulkVerifyVolumes Error checking volumes are attached with %v", bulkAttachErr) - return nil + return nil, nil } for nodeName, volumeSpecs := range pluginNodeVolumes { @@ -242,39 +267,69 @@ func (og *operationGenerator) GenerateBulkVolumeVerifyFunc( } } - return nil + return nil, nil + } + + return volumetypes.GeneratedOperations{ + OperationFunc: bulkVolumeVerifyFunc, + CompleteFunc: util.OperationCompleteHook(pluginName, "verify_volumes_are_attached"), + EventRecorderFunc: nil, // nil because we do not want to generate event on error }, nil + } func (og *operationGenerator) GenerateAttachVolumeFunc( volumeToAttach VolumeToAttach, - actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, string, error) { + actualStateOfWorld ActualStateOfWorldAttacherUpdater) (volumetypes.GeneratedOperations, error) { // Get attacher plugin + eventRecorderFunc := func(err *error) { + if *err != nil { + for _, pod := range volumeToAttach.ScheduledPods { + og.recorder.Eventf(pod, v1.EventTypeWarning, kevents.FailedAttachVolume, (*err).Error()) + } + } + } + attachableVolumePlugin, err := og.volumePluginMgr.FindAttachablePluginBySpec(volumeToAttach.VolumeSpec) if err != nil || attachableVolumePlugin == nil { - return nil, "", volumeToAttach.GenerateErrorDetailed("AttachVolume.FindAttachablePluginBySpec failed", err) + eventRecorderFunc(&err) + return volumetypes.GeneratedOperations{}, volumeToAttach.GenerateErrorDetailed("AttachVolume.FindAttachablePluginBySpec failed", err) } volumeAttacher, newAttacherErr := attachableVolumePlugin.NewAttacher() if newAttacherErr != nil { - return nil, attachableVolumePlugin.GetPluginName(), volumeToAttach.GenerateErrorDetailed("AttachVolume.NewAttacher failed", newAttacherErr) + eventRecorderFunc(&err) + return volumetypes.GeneratedOperations{}, volumeToAttach.GenerateErrorDetailed("AttachVolume.NewAttacher failed", newAttacherErr) } - return func() error { + attachVolumeFunc := func() (error, error) { // Execute attach devicePath, attachErr := volumeAttacher.Attach( volumeToAttach.VolumeSpec, volumeToAttach.NodeName) if attachErr != nil { - // On failure, return error. Caller will log and retry. - eventErr, detailedErr := volumeToAttach.GenerateError("AttachVolume.Attach failed", attachErr) - for _, pod := range volumeToAttach.ScheduledPods { - og.recorder.Eventf(pod, v1.EventTypeWarning, kevents.FailedMountVolume, eventErr.Error()) + if derr, ok := attachErr.(*util.DanglingAttachError); ok { + addErr := actualStateOfWorld.MarkVolumeAsAttached( + v1.UniqueVolumeName(""), + volumeToAttach.VolumeSpec, + derr.CurrentNode, + derr.DevicePath) + + if addErr != nil { + glog.Errorf("AttachVolume.MarkVolumeAsAttached failed to fix dangling volume error for volume %q with %s", volumeToAttach.VolumeName, addErr) + } + } - return detailedErr + // On failure, return error. Caller will log and retry. + return volumeToAttach.GenerateError("AttachVolume.Attach failed", attachErr) } + // 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) + } glog.Infof(volumeToAttach.GenerateMsgDetailed("AttachVolume.Attach succeeded", "")) // Update actual state of world @@ -282,11 +337,17 @@ func (og *operationGenerator) GenerateAttachVolumeFunc( v1.UniqueVolumeName(""), volumeToAttach.VolumeSpec, volumeToAttach.NodeName, devicePath) if addVolumeNodeErr != nil { // On failure, return error. Caller will log and retry. - return volumeToAttach.GenerateErrorDetailed("AttachVolume.MarkVolumeAsAttached failed", addVolumeNodeErr) + return volumeToAttach.GenerateError("AttachVolume.MarkVolumeAsAttached failed", addVolumeNodeErr) } - return nil - }, attachableVolumePlugin.GetPluginName(), nil + return nil, nil + } + + return volumetypes.GeneratedOperations{ + OperationFunc: attachVolumeFunc, + EventRecorderFunc: eventRecorderFunc, + CompleteFunc: util.OperationCompleteHook(attachableVolumePlugin.GetPluginName(), "volume_attach"), + }, nil } func (og *operationGenerator) GetVolumePluginMgr() *volume.VolumePluginMgr { @@ -296,7 +357,7 @@ func (og *operationGenerator) GetVolumePluginMgr() *volume.VolumePluginMgr { func (og *operationGenerator) GenerateDetachVolumeFunc( volumeToDetach AttachedVolume, verifySafeToDetach bool, - actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, string, error) { + actualStateOfWorld ActualStateOfWorldAttacherUpdater) (volumetypes.GeneratedOperations, error) { var volumeName string var attachableVolumePlugin volume.AttachableVolumePlugin var pluginName string @@ -307,13 +368,13 @@ func (og *operationGenerator) GenerateDetachVolumeFunc( attachableVolumePlugin, err = og.volumePluginMgr.FindAttachablePluginBySpec(volumeToDetach.VolumeSpec) if err != nil || attachableVolumePlugin == nil { - return nil, "", volumeToDetach.GenerateErrorDetailed("DetachVolume.FindAttachablePluginBySpec failed", err) + return volumetypes.GeneratedOperations{}, volumeToDetach.GenerateErrorDetailed("DetachVolume.FindAttachablePluginBySpec failed", err) } volumeName, err = attachableVolumePlugin.GetVolumeName(volumeToDetach.VolumeSpec) if err != nil { - return nil, attachableVolumePlugin.GetPluginName(), volumeToDetach.GenerateErrorDetailed("DetachVolume.GetVolumeName failed", err) + return volumetypes.GeneratedOperations{}, volumeToDetach.GenerateErrorDetailed("DetachVolume.GetVolumeName failed", err) } } else { // Get attacher plugin and the volumeName by splitting the volume unique name in case @@ -321,11 +382,11 @@ func (og *operationGenerator) GenerateDetachVolumeFunc( // when a pod has been deleted during the controller downtime pluginName, volumeName, err = volumehelper.SplitUniqueName(volumeToDetach.VolumeName) if err != nil { - return nil, pluginName, volumeToDetach.GenerateErrorDetailed("DetachVolume.SplitUniqueName failed", err) + return volumetypes.GeneratedOperations{}, volumeToDetach.GenerateErrorDetailed("DetachVolume.SplitUniqueName failed", err) } attachableVolumePlugin, err = og.volumePluginMgr.FindAttachablePluginByName(pluginName) if err != nil { - return nil, pluginName, volumeToDetach.GenerateErrorDetailed("DetachVolume.FindAttachablePluginBySpec failed", err) + return volumetypes.GeneratedOperations{}, volumeToDetach.GenerateErrorDetailed("DetachVolume.FindAttachablePluginBySpec failed", err) } } @@ -335,10 +396,10 @@ func (og *operationGenerator) GenerateDetachVolumeFunc( volumeDetacher, err := attachableVolumePlugin.NewDetacher() if err != nil { - return nil, pluginName, volumeToDetach.GenerateErrorDetailed("DetachVolume.NewDetacher failed", err) + return volumetypes.GeneratedOperations{}, volumeToDetach.GenerateErrorDetailed("DetachVolume.NewDetacher failed", err) } - return func() error { + getVolumePluginMgrFunc := func() (error, error) { var err error if verifySafeToDetach { err = og.verifyVolumeIsSafeToDetach(volumeToDetach) @@ -350,7 +411,7 @@ func (og *operationGenerator) GenerateDetachVolumeFunc( // On failure, add volume back to ReportAsAttached list actualStateOfWorld.AddVolumeToReportAsAttached( volumeToDetach.VolumeName, volumeToDetach.NodeName) - return volumeToDetach.GenerateErrorDetailed("DetachVolume.Detach failed", err) + return volumeToDetach.GenerateError("DetachVolume.Detach failed", err) } glog.Infof(volumeToDetach.GenerateMsgDetailed("DetachVolume.Detach succeeded", "")) @@ -359,25 +420,33 @@ func (og *operationGenerator) GenerateDetachVolumeFunc( actualStateOfWorld.MarkVolumeAsDetached( volumeToDetach.VolumeName, volumeToDetach.NodeName) - return nil - }, pluginName, nil + return nil, nil + } + + return volumetypes.GeneratedOperations{ + OperationFunc: getVolumePluginMgrFunc, + CompleteFunc: util.OperationCompleteHook(pluginName, "volume_detach"), + EventRecorderFunc: nil, // nil because we do not want to generate event on error + }, nil } func (og *operationGenerator) GenerateMountVolumeFunc( waitForAttachTimeout time.Duration, volumeToMount VolumeToMount, actualStateOfWorld ActualStateOfWorldMounterUpdater, - isRemount bool) (func() error, string, error) { + isRemount bool) (volumetypes.GeneratedOperations, error) { // Get mounter plugin volumePlugin, err := og.volumePluginMgr.FindPluginBySpec(volumeToMount.VolumeSpec) if err != nil || volumePlugin == nil { - return nil, "", volumeToMount.GenerateErrorDetailed("MountVolume.FindPluginBySpec failed", err) + return volumetypes.GeneratedOperations{}, volumeToMount.GenerateErrorDetailed("MountVolume.FindPluginBySpec failed", err) } affinityErr := checkNodeAffinity(og, volumeToMount, volumePlugin) if affinityErr != nil { - return nil, volumePlugin.GetPluginName(), affinityErr + eventErr, detailedErr := volumeToMount.GenerateError("MountVolume.NodeAffinity check failed", affinityErr) + og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FailedMountVolume, eventErr.Error()) + return volumetypes.GeneratedOperations{}, detailedErr } volumeMounter, newMounterErr := volumePlugin.NewMounter( @@ -387,13 +456,15 @@ func (og *operationGenerator) GenerateMountVolumeFunc( if newMounterErr != nil { eventErr, detailedErr := volumeToMount.GenerateError("MountVolume.NewMounter initialization failed", newMounterErr) og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FailedMountVolume, eventErr.Error()) - return nil, volumePlugin.GetPluginName(), detailedErr + return volumetypes.GeneratedOperations{}, detailedErr } mountCheckError := checkMountOptionSupport(og, volumeToMount, volumePlugin) if mountCheckError != nil { - return nil, volumePlugin.GetPluginName(), mountCheckError + eventErr, detailedErr := volumeToMount.GenerateError("MountVolume.MountOptionSupport check failed", mountCheckError) + og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.UnsupportedMountOption, eventErr.Error()) + return volumetypes.GeneratedOperations{}, detailedErr } // Get attacher, if possible @@ -410,7 +481,7 @@ func (og *operationGenerator) GenerateMountVolumeFunc( fsGroup = volumeToMount.Pod.Spec.SecurityContext.FSGroup } - return func() error { + mountVolumeFunc := func() (error, error) { if volumeAttacher != nil { // Wait for attachable volumes to finish attaching glog.Infof(volumeToMount.GenerateMsgDetailed("MountVolume.WaitForAttach entering", fmt.Sprintf("DevicePath %q", volumeToMount.DevicePath))) @@ -419,16 +490,24 @@ func (og *operationGenerator) GenerateMountVolumeFunc( volumeToMount.VolumeSpec, volumeToMount.DevicePath, volumeToMount.Pod, waitForAttachTimeout) if err != nil { // On failure, return error. Caller will log and retry. - return volumeToMount.GenerateErrorDetailed("MountVolume.WaitForAttach failed", err) + return volumeToMount.GenerateError("MountVolume.WaitForAttach failed", err) } - glog.Infof(volumeToMount.GenerateMsgDetailed("MountVolume.WaitForAttach succeeded", "")) + glog.Infof(volumeToMount.GenerateMsgDetailed("MountVolume.WaitForAttach succeeded", fmt.Sprintf("DevicePath %q", devicePath))) + + // resizeFileSystem will resize the file system if user has requested a resize of + // underlying persistent volume and is allowed to do so. + resizeSimpleError, resizeDetailedError := og.resizeFileSystem(volumeToMount, devicePath, volumePlugin.GetPluginName()) + + if resizeSimpleError != nil || resizeDetailedError != nil { + return resizeSimpleError, resizeDetailedError + } deviceMountPath, err := volumeAttacher.GetDeviceMountPath(volumeToMount.VolumeSpec) if err != nil { // On failure, return error. Caller will log and retry. - return volumeToMount.GenerateErrorDetailed("MountVolume.GetDeviceMountPath failed", err) + return volumeToMount.GenerateError("MountVolume.GetDeviceMountPath failed", err) } // Mount device to global mount path @@ -438,9 +517,7 @@ func (og *operationGenerator) GenerateMountVolumeFunc( deviceMountPath) if err != nil { // On failure, return error. Caller will log and retry. - eventErr, detailedErr := volumeToMount.GenerateError("MountVolume.MountDevice failed", err) - og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FailedMountVolume, eventErr.Error()) - return detailedErr + return volumeToMount.GenerateError("MountVolume.MountDevice failed", err) } glog.Infof(volumeToMount.GenerateMsgDetailed("MountVolume.MountDevice succeeded", fmt.Sprintf("device mount path %q", deviceMountPath))) @@ -450,7 +527,7 @@ func (og *operationGenerator) GenerateMountVolumeFunc( volumeToMount.VolumeName) if markDeviceMountedErr != nil { // On failure, return error. Caller will log and retry. - return volumeToMount.GenerateErrorDetailed("MountVolume.MarkDeviceAsMounted failed", markDeviceMountedErr) + return volumeToMount.GenerateError("MountVolume.MarkDeviceAsMounted failed", markDeviceMountedErr) } } @@ -459,9 +536,7 @@ func (og *operationGenerator) GenerateMountVolumeFunc( err = fmt.Errorf( "Verify that your node machine has the required components before attempting to mount this volume type. %s", canMountErr) - eventErr, detailedErr := volumeToMount.GenerateError("MountVolume.CanMount failed", err) - og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FailedMountVolume, eventErr.Error()) - return detailedErr + return volumeToMount.GenerateError("MountVolume.CanMount failed", err) } } @@ -469,9 +544,7 @@ func (og *operationGenerator) GenerateMountVolumeFunc( mountErr := volumeMounter.SetUp(fsGroup) if mountErr != nil { // On failure, return error. Caller will log and retry. - eventErr, detailedErr := volumeToMount.GenerateError("MountVolume.SetUp failed", mountErr) - og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FailedMountVolume, eventErr.Error()) - return detailedErr + return volumeToMount.GenerateError("MountVolume.SetUp failed", mountErr) } simpleMsg, detailedMsg := volumeToMount.GenerateMsg("MountVolume.SetUp succeeded", "") @@ -489,39 +562,116 @@ func (og *operationGenerator) GenerateMountVolumeFunc( volumeToMount.Pod.UID, volumeToMount.VolumeName, volumeMounter, + nil, volumeToMount.OuterVolumeSpecName, volumeToMount.VolumeGidValue) if markVolMountedErr != nil { // On failure, return error. Caller will log and retry. - return volumeToMount.GenerateErrorDetailed("MountVolume.MarkVolumeAsMounted failed", markVolMountedErr) + return volumeToMount.GenerateError("MountVolume.MarkVolumeAsMounted failed", markVolMountedErr) } - return nil - }, volumePlugin.GetPluginName(), nil + return nil, nil + } + + eventRecorderFunc := func(err *error) { + if *err != nil { + og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FailedMountVolume, (*err).Error()) + } + } + + return volumetypes.GeneratedOperations{ + OperationFunc: mountVolumeFunc, + EventRecorderFunc: eventRecorderFunc, + CompleteFunc: util.OperationCompleteHook(volumePlugin.GetPluginName(), "volume_mount"), + }, nil +} + +func (og *operationGenerator) resizeFileSystem(volumeToMount VolumeToMount, devicePath string, pluginName string) (simpleErr, detailedErr error) { + if !utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) { + glog.V(6).Infof("Resizing is not enabled for this volume %s", volumeToMount.VolumeName) + return nil, nil + } + + mounter := og.volumePluginMgr.Host.GetMounter(pluginName) + // Get expander, if possible + expandableVolumePlugin, _ := + og.volumePluginMgr.FindExpandablePluginBySpec(volumeToMount.VolumeSpec) + + if expandableVolumePlugin != nil && + expandableVolumePlugin.RequiresFSResize() && + volumeToMount.VolumeSpec.PersistentVolume != nil { + pv := volumeToMount.VolumeSpec.PersistentVolume + pvc, err := og.kubeClient.CoreV1().PersistentVolumeClaims(pv.Spec.ClaimRef.Namespace).Get(pv.Spec.ClaimRef.Name, metav1.GetOptions{}) + if err != nil { + // Return error rather than leave the file system un-resized, caller will log and retry + return volumeToMount.GenerateError("MountVolume.resizeFileSystem get PVC failed", err) + } + + pvcStatusCap := pvc.Status.Capacity[v1.ResourceStorage] + pvSpecCap := pv.Spec.Capacity[v1.ResourceStorage] + if pvcStatusCap.Cmp(pvSpecCap) < 0 { + // File system resize was requested, proceed + glog.V(4).Infof(volumeToMount.GenerateMsgDetailed("MountVolume.resizeFileSystem entering", fmt.Sprintf("DevicePath %q", volumeToMount.DevicePath))) + + if volumeToMount.VolumeSpec.ReadOnly { + simpleMsg, detailedMsg := volumeToMount.GenerateMsg("MountVolume.resizeFileSystem failed", "requested read-only file system") + glog.Warningf(detailedMsg) + og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FileSystemResizeFailed, simpleMsg) + return nil, nil + } + + diskFormatter := &mount.SafeFormatAndMount{ + Interface: mounter, + Exec: og.volumePluginMgr.Host.GetExec(expandableVolumePlugin.GetPluginName()), + } + + resizer := resizefs.NewResizeFs(diskFormatter) + resizeStatus, resizeErr := resizer.Resize(devicePath) + + if resizeErr != nil { + return volumeToMount.GenerateError("MountVolume.resizeFileSystem failed", resizeErr) + } + + if resizeStatus { + simpleMsg, detailedMsg := volumeToMount.GenerateMsg("MountVolume.resizeFileSystem succeeded", "") + og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeNormal, kevents.FileSystemResizeSuccess, simpleMsg) + glog.Infof(detailedMsg) + } + + // File system resize succeeded, now update the PVC's Capacity to match the PV's + err = updatePVCStatusCapacity(pvc.Name, pvc, pv.Spec.Capacity, og.kubeClient) + if err != nil { + // On retry, resizeFileSystem will be called again but do nothing + return volumeToMount.GenerateError("MountVolume.resizeFileSystem update PVC status failed", err) + } + return nil, nil + } + } + return nil, nil } func (og *operationGenerator) GenerateUnmountVolumeFunc( volumeToUnmount MountedVolume, - actualStateOfWorld ActualStateOfWorldMounterUpdater) (func() error, string, error) { + actualStateOfWorld ActualStateOfWorldMounterUpdater) (volumetypes.GeneratedOperations, error) { // Get mountable plugin volumePlugin, err := og.volumePluginMgr.FindPluginByName(volumeToUnmount.PluginName) if err != nil || volumePlugin == nil { - return nil, "", volumeToUnmount.GenerateErrorDetailed("UnmountVolume.FindPluginByName failed", err) + return volumetypes.GeneratedOperations{}, volumeToUnmount.GenerateErrorDetailed("UnmountVolume.FindPluginByName failed", err) } volumeUnmounter, newUnmounterErr := volumePlugin.NewUnmounter( volumeToUnmount.InnerVolumeSpecName, volumeToUnmount.PodUID) if newUnmounterErr != nil { - return nil, volumePlugin.GetPluginName(), volumeToUnmount.GenerateErrorDetailed("UnmountVolume.NewUnmounter failed", newUnmounterErr) + return volumetypes.GeneratedOperations{}, volumeToUnmount.GenerateErrorDetailed("UnmountVolume.NewUnmounter failed", newUnmounterErr) } - return func() error { + unmountVolumeFunc := func() (error, error) { // Execute unmount unmountErr := volumeUnmounter.TearDown() if unmountErr != nil { // On failure, return error. Caller will log and retry. - return volumeToUnmount.GenerateErrorDetailed("UnmountVolume.TearDown failed", unmountErr) + return volumeToUnmount.GenerateError("UnmountVolume.TearDown failed", unmountErr) } glog.Infof( @@ -542,37 +692,43 @@ func (og *operationGenerator) GenerateUnmountVolumeFunc( glog.Errorf(volumeToUnmount.GenerateErrorDetailed("UnmountVolume.MarkVolumeAsUnmounted failed", markVolMountedErr).Error()) } - return nil - }, volumePlugin.GetPluginName(), nil + return nil, nil + } + + return volumetypes.GeneratedOperations{ + OperationFunc: unmountVolumeFunc, + CompleteFunc: util.OperationCompleteHook(volumePlugin.GetPluginName(), "volume_unmount"), + EventRecorderFunc: nil, // nil because we do not want to generate event on error + }, nil } func (og *operationGenerator) GenerateUnmountDeviceFunc( deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, - mounter mount.Interface) (func() error, string, error) { + mounter mount.Interface) (volumetypes.GeneratedOperations, error) { // Get attacher plugin attachableVolumePlugin, err := og.volumePluginMgr.FindAttachablePluginBySpec(deviceToDetach.VolumeSpec) if err != nil || attachableVolumePlugin == nil { - return nil, "", deviceToDetach.GenerateErrorDetailed("UnmountDevice.FindAttachablePluginBySpec failed", err) + return volumetypes.GeneratedOperations{}, deviceToDetach.GenerateErrorDetailed("UnmountDevice.FindAttachablePluginBySpec failed", err) } volumeDetacher, err := attachableVolumePlugin.NewDetacher() if err != nil { - return nil, attachableVolumePlugin.GetPluginName(), deviceToDetach.GenerateErrorDetailed("UnmountDevice.NewDetacher failed", err) + return volumetypes.GeneratedOperations{}, deviceToDetach.GenerateErrorDetailed("UnmountDevice.NewDetacher failed", err) } volumeAttacher, err := attachableVolumePlugin.NewAttacher() if err != nil { - return nil, attachableVolumePlugin.GetPluginName(), deviceToDetach.GenerateErrorDetailed("UnmountDevice.NewAttacher failed", err) + return volumetypes.GeneratedOperations{}, deviceToDetach.GenerateErrorDetailed("UnmountDevice.NewAttacher failed", err) } - return func() error { + unmountDeviceFunc := func() (error, error) { deviceMountPath, err := volumeAttacher.GetDeviceMountPath(deviceToDetach.VolumeSpec) if err != nil { // On failure, return error. Caller will log and retry. - return deviceToDetach.GenerateErrorDetailed("GetDeviceMountPath failed", err) + return deviceToDetach.GenerateError("GetDeviceMountPath failed", err) } refs, err := attachableVolumePlugin.GetDeviceMountRefs(deviceMountPath) @@ -580,64 +736,411 @@ func (og *operationGenerator) GenerateUnmountDeviceFunc( if err == nil { err = fmt.Errorf("The device mount path %q is still mounted by other references %v", deviceMountPath, refs) } - return deviceToDetach.GenerateErrorDetailed("GetDeviceMountRefs check failed", err) + return deviceToDetach.GenerateError("GetDeviceMountRefs check failed", err) } // Execute unmount unmountDeviceErr := volumeDetacher.UnmountDevice(deviceMountPath) if unmountDeviceErr != nil { // On failure, return error. Caller will log and retry. - return deviceToDetach.GenerateErrorDetailed("UnmountDevice failed", unmountDeviceErr) + return deviceToDetach.GenerateError("UnmountDevice failed", unmountDeviceErr) } // Before logging that UnmountDevice succeeded and moving on, // use mounter.PathIsDevice to check if the path is a device, // if so use mounter.DeviceOpened to check if the device is in use anywhere // else on the system. Retry if it returns true. - isDevicePath, devicePathErr := mounter.PathIsDevice(deviceToDetach.DevicePath) - var deviceOpened bool - var deviceOpenedErr error - if !isDevicePath && devicePathErr == nil { - // not a device path or path doesn't exist - //TODO: refer to #36092 - glog.V(3).Infof("Not checking device path %s", deviceToDetach.DevicePath) - deviceOpened = false - } else { - deviceOpened, deviceOpenedErr = mounter.DeviceOpened(deviceToDetach.DevicePath) - if deviceOpenedErr != nil { - return deviceToDetach.GenerateErrorDetailed("UnmountDevice.DeviceOpened failed", deviceOpenedErr) - } + deviceOpened, deviceOpenedErr := isDeviceOpened(deviceToDetach, mounter) + if deviceOpenedErr != nil { + return nil, deviceOpenedErr } // The device is still in use elsewhere. Caller will log and retry. if deviceOpened { - return deviceToDetach.GenerateErrorDetailed( + return deviceToDetach.GenerateError( "UnmountDevice failed", fmt.Errorf("the device is in use when it was no longer expected to be in use")) } - glog.Infof(deviceToDetach.GenerateMsgDetailed("UnmountDevice succeeded", "")) + glog.Infof(deviceToDetach.GenerateMsg("UnmountDevice succeeded", "")) // Update actual state of world markDeviceUnmountedErr := actualStateOfWorld.MarkDeviceAsUnmounted( deviceToDetach.VolumeName) if markDeviceUnmountedErr != nil { // On failure, return error. Caller will log and retry. - return deviceToDetach.GenerateErrorDetailed("MarkDeviceAsUnmounted failed", markDeviceUnmountedErr) + return deviceToDetach.GenerateError("MarkDeviceAsUnmounted failed", markDeviceUnmountedErr) } - return nil - }, attachableVolumePlugin.GetPluginName(), nil + return nil, nil + } + + return volumetypes.GeneratedOperations{ + OperationFunc: unmountDeviceFunc, + CompleteFunc: util.OperationCompleteHook(attachableVolumePlugin.GetPluginName(), "unmount_device"), + EventRecorderFunc: nil, // nil because we do not want to generate event on error + }, nil +} + +// GenerateMapVolumeFunc marks volume as mounted based on following steps. +// If plugin is attachable, call WaitForAttach() and then mark the device +// as mounted. On next step, SetUpDevice is called without dependent of +// plugin type, but this method mainly is targeted for none attachable plugin. +// After setup is done, create symbolic links on both global map path and pod +// device map path. Once symbolic links are created, take fd lock by +// loopback for the device to avoid silent volume replacement. This lock +// will be realased once no one uses the device. +// If all steps are completed, the volume is marked as mounted. +func (og *operationGenerator) GenerateMapVolumeFunc( + waitForAttachTimeout time.Duration, + volumeToMount VolumeToMount, + actualStateOfWorld ActualStateOfWorldMounterUpdater) (volumetypes.GeneratedOperations, error) { + + // Get block volume mapper plugin + var blockVolumeMapper volume.BlockVolumeMapper + blockVolumePlugin, err := + og.volumePluginMgr.FindMapperPluginBySpec(volumeToMount.VolumeSpec) + if err != nil { + return volumetypes.GeneratedOperations{}, volumeToMount.GenerateErrorDetailed("MapVolume.FindMapperPluginBySpec failed", err) + } + if blockVolumePlugin == nil { + return volumetypes.GeneratedOperations{}, volumeToMount.GenerateErrorDetailed("MapVolume.FindMapperPluginBySpec failed to find BlockVolumeMapper plugin. Volume plugin is nil.", nil) + } + affinityErr := checkNodeAffinity(og, volumeToMount, blockVolumePlugin) + if affinityErr != nil { + eventErr, detailedErr := volumeToMount.GenerateError("MapVolume.NodeAffinity check failed", affinityErr) + og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FailedMountVolume, eventErr.Error()) + return volumetypes.GeneratedOperations{}, detailedErr + } + blockVolumeMapper, newMapperErr := blockVolumePlugin.NewBlockVolumeMapper( + volumeToMount.VolumeSpec, + volumeToMount.Pod, + volume.VolumeOptions{}) + if newMapperErr != nil { + eventErr, detailedErr := volumeToMount.GenerateError("MapVolume.NewBlockVolumeMapper initialization failed", newMapperErr) + og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FailedMapVolume, eventErr.Error()) + return volumetypes.GeneratedOperations{}, detailedErr + } + + // Get attacher, if possible + attachableVolumePlugin, _ := + og.volumePluginMgr.FindAttachablePluginBySpec(volumeToMount.VolumeSpec) + var volumeAttacher volume.Attacher + if attachableVolumePlugin != nil { + volumeAttacher, _ = attachableVolumePlugin.NewAttacher() + } + + mapVolumeFunc := func() (error, error) { + var devicePath string + if volumeAttacher != nil { + // Wait for attachable volumes to finish attaching + glog.Infof(volumeToMount.GenerateMsgDetailed("MapVolume.WaitForAttach entering", fmt.Sprintf("DevicePath %q", volumeToMount.DevicePath))) + + devicePath, err = volumeAttacher.WaitForAttach( + volumeToMount.VolumeSpec, volumeToMount.DevicePath, volumeToMount.Pod, waitForAttachTimeout) + if err != nil { + // On failure, return error. Caller will log and retry. + return volumeToMount.GenerateError("MapVolume.WaitForAttach failed", err) + } + + glog.Infof(volumeToMount.GenerateMsgDetailed("MapVolume.WaitForAttach succeeded", fmt.Sprintf("DevicePath %q", devicePath))) + + // Update actual state of world to reflect volume is globally mounted + markDeviceMappedErr := actualStateOfWorld.MarkDeviceAsMounted( + volumeToMount.VolumeName) + if markDeviceMappedErr != nil { + // On failure, return error. Caller will log and retry. + return volumeToMount.GenerateError("MapVolume.MarkDeviceAsMounted failed", markDeviceMappedErr) + } + } + // A plugin doesn't have attacher also needs to map device to global map path with SetUpDevice() + pluginDevicePath, mapErr := blockVolumeMapper.SetUpDevice() + if mapErr != nil { + // On failure, return error. Caller will log and retry. + return volumeToMount.GenerateError("MapVolume.SetUp failed", mapErr) + } + // Update devicePath for none attachable plugin case + if len(devicePath) == 0 { + if len(pluginDevicePath) != 0 { + devicePath = pluginDevicePath + } else { + return volumeToMount.GenerateError("MapVolume failed", fmt.Errorf("Device path of the volume is empty")) + } + } + // Set up global map path under the given plugin directory using symbolic link + globalMapPath, err := + blockVolumeMapper.GetGlobalMapPath(volumeToMount.VolumeSpec) + if err != nil { + // On failure, return error. Caller will log and retry. + return volumeToMount.GenerateError("MapVolume.GetDeviceMountPath failed", err) + } + mapErr = og.blkUtil.MapDevice(devicePath, globalMapPath, string(volumeToMount.Pod.UID)) + if mapErr != nil { + // On failure, return error. Caller will log and retry. + return volumeToMount.GenerateError("MapVolume.MapDevice failed", mapErr) + } + // Device mapping for global map path succeeded + simpleMsg, detailedMsg := volumeToMount.GenerateMsg("MapVolume.MapDevice succeeded", fmt.Sprintf("globalMapPath %q", globalMapPath)) + verbosity := glog.Level(4) + og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeNormal, kevents.SuccessfulMountVolume, simpleMsg) + glog.V(verbosity).Infof(detailedMsg) + + // Map device to pod device map path under the given pod directory using symbolic link + volumeMapPath, volName := blockVolumeMapper.GetPodDeviceMapPath() + mapErr = og.blkUtil.MapDevice(devicePath, volumeMapPath, volName) + if mapErr != nil { + // On failure, return error. Caller will log and retry. + return volumeToMount.GenerateError("MapVolume.MapDevice failed", mapErr) + } + + // Take filedescriptor lock to keep a block device opened. Otherwise, there is a case + // that the block device is silently removed and attached another device with same name. + // Container runtime can't handler this problem. To avoid unexpected condition fd lock + // for the block device is required. + _, err = og.blkUtil.AttachFileDevice(devicePath) + if err != nil { + return volumeToMount.GenerateError("MapVolume.AttachFileDevice failed", err) + } + + // Device mapping for pod device map path succeeded + simpleMsg, detailedMsg = volumeToMount.GenerateMsg("MapVolume.MapDevice succeeded", fmt.Sprintf("volumeMapPath %q", volumeMapPath)) + verbosity = glog.Level(1) + og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeNormal, kevents.SuccessfulMountVolume, simpleMsg) + glog.V(verbosity).Infof(detailedMsg) + + // Update actual state of world + markVolMountedErr := actualStateOfWorld.MarkVolumeAsMounted( + volumeToMount.PodName, + volumeToMount.Pod.UID, + volumeToMount.VolumeName, + nil, + blockVolumeMapper, + volumeToMount.OuterVolumeSpecName, + volumeToMount.VolumeGidValue) + if markVolMountedErr != nil { + // On failure, return error. Caller will log and retry. + return volumeToMount.GenerateError("MapVolume.MarkVolumeAsMounted failed", markVolMountedErr) + } + + return nil, nil + } + + eventRecorderFunc := func(err *error) { + if *err != nil { + og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FailedMapVolume, (*err).Error()) + } + } + + return volumetypes.GeneratedOperations{ + OperationFunc: mapVolumeFunc, + EventRecorderFunc: eventRecorderFunc, + CompleteFunc: util.OperationCompleteHook(blockVolumePlugin.GetPluginName(), "map_volume"), + }, nil +} + +// GenerateUnmapVolumeFunc marks volume as unmonuted based on following steps. +// Remove symbolic links from pod device map path dir and global map path dir. +// Once those cleanups are done, remove pod device map path dir. +// If all steps are completed, the volume is marked as unmounted. +func (og *operationGenerator) GenerateUnmapVolumeFunc( + volumeToUnmount MountedVolume, + actualStateOfWorld ActualStateOfWorldMounterUpdater) (volumetypes.GeneratedOperations, error) { + + // Get block volume unmapper plugin + var blockVolumeUnmapper volume.BlockVolumeUnmapper + blockVolumePlugin, err := + og.volumePluginMgr.FindMapperPluginByName(volumeToUnmount.PluginName) + if err != nil { + return volumetypes.GeneratedOperations{}, volumeToUnmount.GenerateErrorDetailed("UnmapVolume.FindMapperPluginByName failed", err) + } + if blockVolumePlugin == nil { + return volumetypes.GeneratedOperations{}, volumeToUnmount.GenerateErrorDetailed("UnmapVolume.FindMapperPluginByName failed to find BlockVolumeMapper plugin. Volume plugin is nil.", nil) + } + blockVolumeUnmapper, newUnmapperErr := blockVolumePlugin.NewBlockVolumeUnmapper( + volumeToUnmount.InnerVolumeSpecName, volumeToUnmount.PodUID) + if newUnmapperErr != nil { + return volumetypes.GeneratedOperations{}, volumeToUnmount.GenerateErrorDetailed("UnmapVolume.NewUnmapper failed", newUnmapperErr) + } + + unmapVolumeFunc := func() (error, error) { + // Try to unmap volumeName symlink under pod device map path dir + // pods/{podUid}/volumeDevices/{escapeQualifiedPluginName}/{volumeName} + podDeviceUnmapPath, volName := blockVolumeUnmapper.GetPodDeviceMapPath() + unmapDeviceErr := og.blkUtil.UnmapDevice(podDeviceUnmapPath, volName) + if unmapDeviceErr != nil { + // On failure, return error. Caller will log and retry. + return volumeToUnmount.GenerateError("UnmapVolume.UnmapDevice on pod device map path failed", unmapDeviceErr) + } + // Try to unmap podUID symlink under global map path dir + // plugins/kubernetes.io/{PluginName}/volumeDevices/{volumePluginDependentPath}/{podUID} + globalUnmapPath, err := + blockVolumeUnmapper.GetGlobalMapPath(volumeToUnmount.VolumeSpec) + if err != nil { + // On failure, return error. Caller will log and retry. + return volumeToUnmount.GenerateError("UnmapVolume.GetGlobalUnmapPath failed", err) + } + unmapDeviceErr = og.blkUtil.UnmapDevice(globalUnmapPath, string(volumeToUnmount.PodUID)) + if unmapDeviceErr != nil { + // On failure, return error. Caller will log and retry. + return volumeToUnmount.GenerateError("UnmapVolume.UnmapDevice on global map path failed", unmapDeviceErr) + } + + glog.Infof( + "UnmapVolume succeeded for volume %q (OuterVolumeSpecName: %q) pod %q (UID: %q). InnerVolumeSpecName %q. PluginName %q, VolumeGidValue %q", + volumeToUnmount.VolumeName, + volumeToUnmount.OuterVolumeSpecName, + volumeToUnmount.PodName, + volumeToUnmount.PodUID, + volumeToUnmount.InnerVolumeSpecName, + volumeToUnmount.PluginName, + volumeToUnmount.VolumeGidValue) + + // Update actual state of world + markVolUnmountedErr := actualStateOfWorld.MarkVolumeAsUnmounted( + volumeToUnmount.PodName, volumeToUnmount.VolumeName) + if markVolUnmountedErr != nil { + // On failure, just log and exit + glog.Errorf(volumeToUnmount.GenerateErrorDetailed("UnmapVolume.MarkVolumeAsUnmounted failed", markVolUnmountedErr).Error()) + } + + return nil, nil + } + + return volumetypes.GeneratedOperations{ + OperationFunc: unmapVolumeFunc, + CompleteFunc: util.OperationCompleteHook(blockVolumePlugin.GetPluginName(), "unmap_volume"), + EventRecorderFunc: nil, // nil because we do not want to generate event on error + }, nil +} + +// GenerateUnmapDeviceFunc marks device as unmounted based on following steps. +// Check under globalMapPath dir if there isn't pod's symbolic links in it. +// If symbolick link isn't there, the device isn't referenced from Pods. +// Call plugin TearDownDevice to clean-up device connection, stored data under +// globalMapPath, these operations depend on plugin implementation. +// Once TearDownDevice is completed, remove globalMapPath dir. +// After globalMapPath is removed, fd lock by loopback for the device can +// be released safely because no one can consume the device at this point. +// At last, device open status will be checked just in case. +// If all steps are completed, the device is marked as unmounted. +func (og *operationGenerator) GenerateUnmapDeviceFunc( + deviceToDetach AttachedVolume, + actualStateOfWorld ActualStateOfWorldMounterUpdater, + mounter mount.Interface) (volumetypes.GeneratedOperations, error) { + + // Get block volume mapper plugin + var blockVolumeMapper volume.BlockVolumeMapper + blockVolumePlugin, err := + og.volumePluginMgr.FindMapperPluginBySpec(deviceToDetach.VolumeSpec) + if err != nil { + return volumetypes.GeneratedOperations{}, deviceToDetach.GenerateErrorDetailed("UnmapDevice.FindMapperPluginBySpec failed", err) + } + if blockVolumePlugin == nil { + return volumetypes.GeneratedOperations{}, deviceToDetach.GenerateErrorDetailed("UnmapDevice.FindMapperPluginBySpec failed to find BlockVolumeMapper plugin. Volume plugin is nil.", nil) + } + blockVolumeMapper, newMapperErr := blockVolumePlugin.NewBlockVolumeMapper( + deviceToDetach.VolumeSpec, + nil, /* Pod */ + volume.VolumeOptions{}) + if newMapperErr != nil { + return volumetypes.GeneratedOperations{}, deviceToDetach.GenerateErrorDetailed("UnmapDevice.NewBlockVolumeMapper initialization failed", newMapperErr) + } + + blockVolumeUnmapper, newUnmapperErr := blockVolumePlugin.NewBlockVolumeUnmapper( + string(deviceToDetach.VolumeName), + "" /* podUID */) + if newUnmapperErr != nil { + return volumetypes.GeneratedOperations{}, deviceToDetach.GenerateErrorDetailed("UnmapDevice.NewUnmapper failed", newUnmapperErr) + } + + unmapDeviceFunc := func() (error, error) { + // Search under globalMapPath dir if all symbolic links from pods have been removed already. + // If symbolick links are there, pods may still refer the volume. + globalMapPath, err := + blockVolumeMapper.GetGlobalMapPath(deviceToDetach.VolumeSpec) + if err != nil { + // On failure, return error. Caller will log and retry. + return deviceToDetach.GenerateError("UnmapDevice.GetGlobalMapPath failed", err) + } + refs, err := og.blkUtil.GetDeviceSymlinkRefs(deviceToDetach.DevicePath, globalMapPath) + if err != nil { + return deviceToDetach.GenerateError("UnmapDevice.GetDeviceSymlinkRefs check failed", err) + } + if len(refs) > 0 { + err = fmt.Errorf("The device %q is still referenced from other Pods %v", globalMapPath, refs) + return deviceToDetach.GenerateError("UnmapDevice failed", err) + } + + // Execute tear down device + unmapErr := blockVolumeUnmapper.TearDownDevice(globalMapPath, deviceToDetach.DevicePath) + if unmapErr != nil { + // On failure, return error. Caller will log and retry. + return deviceToDetach.GenerateError("UnmapDevice.TearDownDevice failed", unmapErr) + } + + // Plugin finished TearDownDevice(). Now globalMapPath dir and plugin's stored data + // on the dir are unnecessary, clean up it. + removeMapPathErr := og.blkUtil.RemoveMapPath(globalMapPath) + if removeMapPathErr != nil { + // On failure, return error. Caller will log and retry. + return deviceToDetach.GenerateError("UnmapDevice failed", removeMapPathErr) + } + + // The block volume is not referenced from Pods. Release file descriptor lock. + glog.V(5).Infof("UnmapDevice: deviceToDetach.DevicePath: %v", deviceToDetach.DevicePath) + loopPath, err := og.blkUtil.GetLoopDevice(deviceToDetach.DevicePath) + if err != nil { + glog.Warningf(deviceToDetach.GenerateMsgDetailed("UnmapDevice: Couldn't find loopback device which takes file descriptor lock", fmt.Sprintf("device path: %q", deviceToDetach.DevicePath))) + } else { + err = og.blkUtil.RemoveLoopDevice(loopPath) + if err != nil { + return deviceToDetach.GenerateError("UnmapDevice.AttachFileDevice failed", err) + } + } + + // Before logging that UnmapDevice succeeded and moving on, + // use mounter.PathIsDevice to check if the path is a device, + // if so use mounter.DeviceOpened to check if the device is in use anywhere + // else on the system. Retry if it returns true. + deviceOpened, deviceOpenedErr := isDeviceOpened(deviceToDetach, mounter) + if deviceOpenedErr != nil { + return nil, deviceOpenedErr + } + // The device is still in use elsewhere. Caller will log and retry. + if deviceOpened { + return deviceToDetach.GenerateError( + "UnmapDevice failed", + fmt.Errorf("the device is in use when it was no longer expected to be in use")) + } + + glog.Infof(deviceToDetach.GenerateMsgDetailed("UnmapDevice succeeded", "")) + + // Update actual state of world + markDeviceUnmountedErr := actualStateOfWorld.MarkDeviceAsUnmounted( + deviceToDetach.VolumeName) + if markDeviceUnmountedErr != nil { + // On failure, return error. Caller will log and retry. + return deviceToDetach.GenerateError("MarkDeviceAsUnmounted failed", markDeviceUnmountedErr) + } + + return nil, nil + } + + return volumetypes.GeneratedOperations{ + OperationFunc: unmapDeviceFunc, + CompleteFunc: util.OperationCompleteHook(blockVolumePlugin.GetPluginName(), "unmap_device"), + EventRecorderFunc: nil, // nil because we do not want to generate event on error + }, nil } func (og *operationGenerator) GenerateVerifyControllerAttachedVolumeFunc( volumeToMount VolumeToMount, nodeName types.NodeName, - actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, string, error) { + actualStateOfWorld ActualStateOfWorldAttacherUpdater) (volumetypes.GeneratedOperations, error) { volumePlugin, err := og.volumePluginMgr.FindPluginBySpec(volumeToMount.VolumeSpec) if err != nil || volumePlugin == nil { - return nil, "", volumeToMount.GenerateErrorDetailed("VerifyControllerAttachedVolume.FindPluginBySpec failed", err) + return volumetypes.GeneratedOperations{}, volumeToMount.GenerateErrorDetailed("VerifyControllerAttachedVolume.FindPluginBySpec failed", err) } - return func() error { + verifyControllerAttachedVolumeFunc := func() (error, error) { if !volumeToMount.PluginIsAttachable { // If the volume does not implement the attacher interface, it is // assumed to be attached and the actual state of the world is @@ -647,10 +1150,10 @@ func (og *operationGenerator) GenerateVerifyControllerAttachedVolumeFunc( volumeToMount.VolumeName, volumeToMount.VolumeSpec, nodeName, "" /* devicePath */) if addVolumeNodeErr != nil { // On failure, return error. Caller will log and retry. - return volumeToMount.GenerateErrorDetailed("VerifyControllerAttachedVolume.MarkVolumeAsAttachedByUniqueVolumeName failed", addVolumeNodeErr) + return volumeToMount.GenerateError("VerifyControllerAttachedVolume.MarkVolumeAsAttachedByUniqueVolumeName failed", addVolumeNodeErr) } - return nil + return nil, nil } if !volumeToMount.ReportedInUse { @@ -660,19 +1163,19 @@ func (og *operationGenerator) GenerateVerifyControllerAttachedVolumeFunc( // periodically by kubelet, so it may take as much as 10 seconds // before this clears. // Issue #28141 to enable on demand status updates. - return volumeToMount.GenerateErrorDetailed("Volume has not been added to the list of VolumesInUse in the node's volume status", nil) + return volumeToMount.GenerateError("Volume has not been added to the list of VolumesInUse in the node's volume status", nil) } // Fetch current node object - node, fetchErr := og.kubeClient.Core().Nodes().Get(string(nodeName), metav1.GetOptions{}) + node, fetchErr := og.kubeClient.CoreV1().Nodes().Get(string(nodeName), metav1.GetOptions{}) if fetchErr != nil { // On failure, return error. Caller will log and retry. - return volumeToMount.GenerateErrorDetailed("VerifyControllerAttachedVolume failed fetching node from API server", fetchErr) + return volumeToMount.GenerateError("VerifyControllerAttachedVolume failed fetching node from API server", fetchErr) } if node == nil { // On failure, return error. Caller will log and retry. - return volumeToMount.GenerateErrorDetailed( + return volumeToMount.GenerateError( "VerifyControllerAttachedVolume failed", fmt.Errorf("Node object retrieved from API server is nil")) } @@ -684,21 +1187,28 @@ func (og *operationGenerator) GenerateVerifyControllerAttachedVolumeFunc( glog.Infof(volumeToMount.GenerateMsgDetailed("Controller attach succeeded", fmt.Sprintf("device path: %q", attachedVolume.DevicePath))) if addVolumeNodeErr != nil { // On failure, return error. Caller will log and retry. - return volumeToMount.GenerateErrorDetailed("VerifyControllerAttachedVolume.MarkVolumeAsAttached failed", addVolumeNodeErr) + return volumeToMount.GenerateError("VerifyControllerAttachedVolume.MarkVolumeAsAttached failed", addVolumeNodeErr) } - return nil + return nil, nil } } // Volume not attached, return error. Caller will log and retry. - return volumeToMount.GenerateErrorDetailed("Volume not attached according to node status", nil) - }, volumePlugin.GetPluginName(), nil + return volumeToMount.GenerateError("Volume not attached according to node status", nil) + } + + return volumetypes.GeneratedOperations{ + OperationFunc: verifyControllerAttachedVolumeFunc, + CompleteFunc: util.OperationCompleteHook(volumePlugin.GetPluginName(), "verify_controller_attached_volume"), + EventRecorderFunc: nil, // nil because we do not want to generate event on error + }, nil + } func (og *operationGenerator) verifyVolumeIsSafeToDetach( volumeToDetach AttachedVolume) error { // Fetch current node object - node, fetchErr := og.kubeClient.Core().Nodes().Get(string(volumeToDetach.NodeName), metav1.GetOptions{}) + node, fetchErr := og.kubeClient.CoreV1().Nodes().Get(string(volumeToDetach.NodeName), metav1.GetOptions{}) if fetchErr != nil { if errors.IsNotFound(fetchErr) { glog.Warningf(volumeToDetach.GenerateMsgDetailed("Node not found on API server. DetachVolume will skip safe to detach check", "")) @@ -731,17 +1241,20 @@ func (og *operationGenerator) verifyVolumeIsSafeToDetach( func (og *operationGenerator) GenerateExpandVolumeFunc( pvcWithResizeRequest *expandcache.PVCWithResizeRequest, - resizeMap expandcache.VolumeResizeMap) (func() error, string, error) { + resizeMap expandcache.VolumeResizeMap) (volumetypes.GeneratedOperations, error) { volumeSpec := volume.NewSpecFromPersistentVolume(pvcWithResizeRequest.PersistentVolume, false) volumePlugin, err := og.volumePluginMgr.FindExpandablePluginBySpec(volumeSpec) if err != nil { - return nil, "", fmt.Errorf("Error finding plugin for expanding volume: %q with error %v", pvcWithResizeRequest.QualifiedName(), err) + return volumetypes.GeneratedOperations{}, fmt.Errorf("Error finding plugin for expanding volume: %q with error %v", pvcWithResizeRequest.QualifiedName(), err) + } + if volumePlugin == nil { + return volumetypes.GeneratedOperations{}, fmt.Errorf("Can not find plugin for expanding volume: %q", pvcWithResizeRequest.QualifiedName()) } - expandFunc := func() error { + expandVolumeFunc := func() (error, error) { newSize := pvcWithResizeRequest.ExpectedSize pvSize := pvcWithResizeRequest.PersistentVolume.Spec.Capacity[v1.ResourceStorage] if pvSize.Cmp(newSize) < 0 { @@ -751,10 +1264,10 @@ func (og *operationGenerator) GenerateExpandVolumeFunc( pvcWithResizeRequest.CurrentSize) if expandErr != nil { - glog.Errorf("Error expanding volume %q of plugin %s : %v", pvcWithResizeRequest.QualifiedName(), volumePlugin.GetPluginName(), expandErr) - og.recorder.Eventf(pvcWithResizeRequest.PVC, v1.EventTypeWarning, kevents.VolumeResizeFailed, expandErr.Error()) - return expandErr + detailedErr := fmt.Errorf("Error expanding volume %q of plugin %s : %v", pvcWithResizeRequest.QualifiedName(), volumePlugin.GetPluginName(), expandErr) + return detailedErr, detailedErr } + glog.Infof("ExpandVolume succeeded for volume %s", pvcWithResizeRequest.QualifiedName()) newSize = updatedSize // k8s doesn't have transactions, we can't guarantee that after updating PV - updating PVC will be // successful, that is why all PVCs for which pvc.Spec.Size > pvc.Status.Size must be reprocessed @@ -762,10 +1275,10 @@ func (og *operationGenerator) GenerateExpandVolumeFunc( updateErr := resizeMap.UpdatePVSize(pvcWithResizeRequest, newSize) if updateErr != nil { - glog.V(4).Infof("Error updating PV spec capacity for volume %q with : %v", pvcWithResizeRequest.QualifiedName(), updateErr) - og.recorder.Eventf(pvcWithResizeRequest.PVC, v1.EventTypeWarning, kevents.VolumeResizeFailed, updateErr.Error()) - return updateErr + detailedErr := fmt.Errorf("Error updating PV spec capacity for volume %q with : %v", pvcWithResizeRequest.QualifiedName(), updateErr) + return detailedErr, detailedErr } + glog.Infof("ExpandVolume.UpdatePV succeeded for volume %s", pvcWithResizeRequest.QualifiedName()) } // No Cloudprovider resize needed, lets mark resizing as done @@ -776,24 +1289,32 @@ func (og *operationGenerator) GenerateExpandVolumeFunc( err := resizeMap.MarkAsResized(pvcWithResizeRequest, newSize) if err != nil { - glog.Errorf("Error marking pvc %s as resized : %v", pvcWithResizeRequest.QualifiedName(), err) - og.recorder.Eventf(pvcWithResizeRequest.PVC, v1.EventTypeWarning, kevents.VolumeResizeFailed, err.Error()) - return err + detailedErr := fmt.Errorf("Error marking pvc %s as resized : %v", pvcWithResizeRequest.QualifiedName(), err) + return detailedErr, detailedErr } } - return nil + return nil, nil } - return expandFunc, volumePlugin.GetPluginName(), nil + + eventRecorderFunc := func(err *error) { + if *err != nil { + og.recorder.Eventf(pvcWithResizeRequest.PVC, v1.EventTypeWarning, kevents.VolumeResizeFailed, (*err).Error()) + } + } + + return volumetypes.GeneratedOperations{ + OperationFunc: expandVolumeFunc, + EventRecorderFunc: eventRecorderFunc, + CompleteFunc: util.OperationCompleteHook(volumePlugin.GetPluginName(), "expand_volume"), + }, nil } func checkMountOptionSupport(og *operationGenerator, volumeToMount VolumeToMount, plugin volume.VolumePlugin) error { mountOptions := volume.MountOptionFromSpec(volumeToMount.VolumeSpec) if len(mountOptions) > 0 && !plugin.SupportsMountOption() { - eventErr, detailedErr := volumeToMount.GenerateError("Mount options are not supported for this volume type", nil) - og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.UnsupportedMountOption, eventErr.Error()) - return detailedErr + return fmt.Errorf("Mount options are not supported for this volume type") } return nil } @@ -809,15 +1330,60 @@ func checkNodeAffinity(og *operationGenerator, volumeToMount VolumeToMount, plug if pv != nil { nodeLabels, err := og.volumePluginMgr.Host.GetNodeLabels() if err != nil { - return volumeToMount.GenerateErrorDetailed("Error getting node labels", err) + return err } - err = util.CheckNodeAffinity(pv, nodeLabels) if err != nil { - eventErr, detailedErr := volumeToMount.GenerateError("Storage node affinity check failed", err) - og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FailedMountVolume, eventErr.Error()) - return detailedErr + return err } } return nil } + +// isDeviceOpened checks the device status if the device is in use anywhere else on the system +func isDeviceOpened(deviceToDetach AttachedVolume, mounter mount.Interface) (bool, error) { + isDevicePath, devicePathErr := mounter.PathIsDevice(deviceToDetach.DevicePath) + var deviceOpened bool + var deviceOpenedErr error + if !isDevicePath && devicePathErr == nil || + (devicePathErr != nil && strings.Contains(devicePathErr.Error(), "does not exist")) { + // not a device path or path doesn't exist + //TODO: refer to #36092 + glog.V(3).Infof("The path isn't device path or doesn't exist. Skip checking device path: %s", deviceToDetach.DevicePath) + deviceOpened = false + } else { + deviceOpened, deviceOpenedErr = mounter.DeviceOpened(deviceToDetach.DevicePath) + if deviceOpenedErr != nil { + return false, deviceToDetach.GenerateErrorDetailed("DeviceOpened failed", deviceOpenedErr) + } + } + return deviceOpened, nil +} + +func updatePVCStatusCapacity(pvcName string, pvc *v1.PersistentVolumeClaim, capacity v1.ResourceList, client clientset.Interface) error { + pvcCopy := pvc.DeepCopy() + + oldData, err := json.Marshal(pvcCopy) + if err != nil { + return fmt.Errorf("Failed to marshal oldData for pvc %q with %v", pvcName, err) + } + + pvcCopy.Status.Capacity = capacity + pvcCopy.Status.Conditions = []v1.PersistentVolumeClaimCondition{} + newData, err := json.Marshal(pvcCopy) + + if err != nil { + return fmt.Errorf("Failed to marshal newData for pvc %q with %v", pvcName, err) + } + patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, pvcCopy) + + if err != nil { + return fmt.Errorf("Failed to CreateTwoWayMergePatch for pvc %q with %v ", pvcName, err) + } + _, err = client.CoreV1().PersistentVolumeClaims(pvc.Namespace). + Patch(pvcName, types.StrategicMergePatchType, patchBytes, "status") + if err != nil { + return fmt.Errorf("Failed to patch PVC %q with %v", pvcName, err) + } + return nil +} diff --git a/pkg/volume/util/types/types.go b/pkg/volume/util/types/types.go index 9375ad6750c..9815545ff60 100644 --- a/pkg/volume/util/types/types.go +++ b/pkg/volume/util/types/types.go @@ -24,3 +24,11 @@ type UniquePodName types.UID // UniquePVCName defines the type to key pvc off type UniquePVCName types.UID + +// GeneratedOperations contains the operation that is created as well as +// supporting functions required for the operation executor +type GeneratedOperations struct { + OperationFunc func() (eventErr error, detailedErr error) + EventRecorderFunc func(*error) + CompleteFunc func(*error) +} diff --git a/pkg/volume/util/util.go b/pkg/volume/util/util.go index 976ad96890a..106036dfb57 100644 --- a/pkg/volume/util/util.go +++ b/pkg/volume/util/util.go @@ -21,7 +21,7 @@ import ( "io/ioutil" "os" "path" - + "path/filepath" "strings" "github.com/golang/glog" @@ -30,15 +30,23 @@ import ( 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/sets" clientset "k8s.io/client-go/kubernetes" - "k8s.io/kubernetes/pkg/api" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" + "k8s.io/kubernetes/pkg/api/legacyscheme" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis" "k8s.io/kubernetes/pkg/util/mount" ) -const readyFileName = "ready" +const ( + readyFileName = "ready" + losetupPath = "losetup" + + ErrDeviceNotFound = "device not found" + ErrDeviceNotSupported = "device not supported" + ErrNotAvailable = "not available" +) // IsReady checks for the existence of a regular file // called 'ready' in the given directory and returns @@ -147,7 +155,7 @@ func GetSecretForPod(pod *v1.Pod, secretName string, kubeClient clientset.Interf if kubeClient == nil { return secret, fmt.Errorf("Cannot get kube client") } - secrets, err := kubeClient.Core().Secrets(pod.Namespace).Get(secretName, metav1.GetOptions{}) + secrets, err := kubeClient.CoreV1().Secrets(pod.Namespace).Get(secretName, metav1.GetOptions{}) if err != nil { return secret, err } @@ -163,7 +171,7 @@ func GetSecretForPV(secretNamespace, secretName, volumePluginName string, kubeCl if kubeClient == nil { return secret, fmt.Errorf("Cannot get kube client") } - secrets, err := kubeClient.Core().Secrets(secretNamespace).Get(secretName, metav1.GetOptions{}) + secrets, err := kubeClient.CoreV1().Secrets(secretNamespace).Get(secretName, metav1.GetOptions{}) if err != nil { return secret, err } @@ -233,7 +241,7 @@ func LoadPodFromFile(filePath string) (*v1.Pod, error) { } pod := &v1.Pod{} - codec := api.Codecs.LegacyCodec(api.Registry.GroupOrDie(v1.GroupName).GroupVersion) + codec := legacyscheme.Codecs.UniversalDecoder() if err := runtime.DecodeInto(codec, podDef, pod); err != nil { return nil, fmt.Errorf("failed decoding file: %v", err) } @@ -270,3 +278,201 @@ func stringToSet(str, delimiter string) (sets.String, error) { } return zonesSet, nil } + +// BlockVolumePathHandler defines a set of operations for handling block volume-related operations +type BlockVolumePathHandler interface { + // MapDevice creates a symbolic link to block device under specified map path + MapDevice(devicePath string, mapPath string, linkName string) error + // UnmapDevice removes a symbolic link to block device under specified map path + UnmapDevice(mapPath string, linkName string) error + // RemovePath removes a file or directory on specified map path + RemoveMapPath(mapPath string) error + // IsSymlinkExist retruns true if specified symbolic link exists + IsSymlinkExist(mapPath string) (bool, error) + // GetDeviceSymlinkRefs searches symbolic links under global map path + GetDeviceSymlinkRefs(devPath string, mapPath string) ([]string, error) + // FindGlobalMapPathUUIDFromPod finds {pod uuid} symbolic link under globalMapPath + // corresponding to map path symlink, and then return global map path with pod uuid. + FindGlobalMapPathUUIDFromPod(pluginDir, mapPath string, podUID types.UID) (string, error) + // AttachFileDevice takes a path to a regular file and makes it available as an + // attached block device. + AttachFileDevice(path string) (string, error) + // GetLoopDevice returns the full path to the loop device associated with the given path. + GetLoopDevice(path string) (string, error) + // RemoveLoopDevice removes specified loopback device + RemoveLoopDevice(device string) error +} + +// NewBlockVolumePathHandler returns a new instance of BlockVolumeHandler. +func NewBlockVolumePathHandler() BlockVolumePathHandler { + var volumePathHandler VolumePathHandler + return volumePathHandler +} + +// VolumePathHandler is path related operation handlers for block volume +type VolumePathHandler struct { +} + +// MapDevice creates a symbolic link to block device under specified map path +func (v VolumePathHandler) MapDevice(devicePath string, mapPath string, linkName string) error { + // Example of global map path: + // globalMapPath/linkName: plugins/kubernetes.io/{PluginName}/{DefaultKubeletVolumeDevicesDirName}/{volumePluginDependentPath}/{podUid} + // linkName: {podUid} + // + // Example of pod device map path: + // podDeviceMapPath/linkName: pods/{podUid}/{DefaultKubeletVolumeDevicesDirName}/{escapeQualifiedPluginName}/{volumeName} + // linkName: {volumeName} + if len(devicePath) == 0 { + return fmt.Errorf("Failed to map device to map path. devicePath is empty") + } + if len(mapPath) == 0 { + return fmt.Errorf("Failed to map device to map path. mapPath is empty") + } + if !filepath.IsAbs(mapPath) { + return fmt.Errorf("The map path should be absolute: map path: %s", mapPath) + } + glog.V(5).Infof("MapDevice: devicePath %s", devicePath) + glog.V(5).Infof("MapDevice: mapPath %s", mapPath) + glog.V(5).Infof("MapDevice: linkName %s", linkName) + + // Check and create mapPath + _, err := os.Stat(mapPath) + if err != nil && !os.IsNotExist(err) { + glog.Errorf("cannot validate map path: %s", mapPath) + return err + } + if err = os.MkdirAll(mapPath, 0750); err != nil { + return fmt.Errorf("Failed to mkdir %s, error %v", mapPath, err) + } + // Remove old symbolic link(or file) then create new one. + // This should be done because current symbolic link is + // stale accross node reboot. + linkPath := path.Join(mapPath, string(linkName)) + if err = os.Remove(linkPath); err != nil && !os.IsNotExist(err) { + return err + } + err = os.Symlink(devicePath, linkPath) + return err +} + +// UnmapDevice removes a symbolic link associated to block device under specified map path +func (v VolumePathHandler) UnmapDevice(mapPath string, linkName string) error { + if len(mapPath) == 0 { + return fmt.Errorf("Failed to unmap device from map path. mapPath is empty") + } + glog.V(5).Infof("UnmapDevice: mapPath %s", mapPath) + glog.V(5).Infof("UnmapDevice: linkName %s", linkName) + + // Check symbolic link exists + linkPath := path.Join(mapPath, string(linkName)) + if islinkExist, checkErr := v.IsSymlinkExist(linkPath); checkErr != nil { + return checkErr + } else if !islinkExist { + glog.Warningf("Warning: Unmap skipped because symlink does not exist on the path: %v", linkPath) + return nil + } + err := os.Remove(linkPath) + return err +} + +// RemoveMapPath removes a file or directory on specified map path +func (v VolumePathHandler) RemoveMapPath(mapPath string) error { + if len(mapPath) == 0 { + return fmt.Errorf("Failed to remove map path. mapPath is empty") + } + glog.V(5).Infof("RemoveMapPath: mapPath %s", mapPath) + err := os.RemoveAll(mapPath) + if err != nil && !os.IsNotExist(err) { + return err + } + return nil +} + +// IsSymlinkExist returns true if specified file exists and the type is symbolik link. +// If file doesn't exist, or file exists but not symbolick link, return false with no error. +// On other cases, return false with error from Lstat(). +func (v VolumePathHandler) IsSymlinkExist(mapPath string) (bool, error) { + fi, err := os.Lstat(mapPath) + if err == nil { + // If file exits and it's symbolick link, return true and no error + if fi.Mode()&os.ModeSymlink == os.ModeSymlink { + return true, nil + } + // If file exits but it's not symbolick link, return fale and no error + return false, nil + } + // If file doesn't exist, return false and no error + if os.IsNotExist(err) { + return false, nil + } + // Return error from Lstat() + return false, err +} + +// GetDeviceSymlinkRefs searches symbolic links under global map path +func (v VolumePathHandler) GetDeviceSymlinkRefs(devPath string, mapPath string) ([]string, error) { + var refs []string + files, err := ioutil.ReadDir(mapPath) + if err != nil { + return nil, fmt.Errorf("Directory cannot read %v", err) + } + for _, file := range files { + if file.Mode()&os.ModeSymlink != os.ModeSymlink { + continue + } + filename := file.Name() + filepath, err := os.Readlink(path.Join(mapPath, filename)) + if err != nil { + return nil, fmt.Errorf("Symbolic link cannot be retrieved %v", err) + } + glog.V(5).Infof("GetDeviceSymlinkRefs: filepath: %v, devPath: %v", filepath, devPath) + if filepath == devPath { + refs = append(refs, path.Join(mapPath, filename)) + } + } + glog.V(5).Infof("GetDeviceSymlinkRefs: refs %v", refs) + return refs, nil +} + +// FindGlobalMapPathUUIDFromPod finds {pod uuid} symbolic link under globalMapPath +// corresponding to map path symlink, and then return global map path with pod uuid. +// ex. mapPath symlink: pods/{podUid}}/{DefaultKubeletVolumeDevicesDirName}/{escapeQualifiedPluginName}/{volumeName} -> /dev/sdX +// globalMapPath/{pod uuid}: plugins/kubernetes.io/{PluginName}/{DefaultKubeletVolumeDevicesDirName}/{volumePluginDependentPath}/{pod uuid} -> /dev/sdX +func (v VolumePathHandler) FindGlobalMapPathUUIDFromPod(pluginDir, mapPath string, podUID types.UID) (string, error) { + var globalMapPathUUID string + // Find symbolic link named pod uuid under plugin dir + err := filepath.Walk(pluginDir, func(path string, fi os.FileInfo, err error) error { + if err != nil { + return err + } + if (fi.Mode()&os.ModeSymlink == os.ModeSymlink) && (fi.Name() == string(podUID)) { + glog.V(5).Infof("FindGlobalMapPathFromPod: path %s, mapPath %s", path, mapPath) + if res, err := compareSymlinks(path, mapPath); err == nil && res { + globalMapPathUUID = path + } + } + return nil + }) + if err != nil { + return "", err + } + glog.V(5).Infof("FindGlobalMapPathFromPod: globalMapPathUUID %s", globalMapPathUUID) + // Return path contains global map path + {pod uuid} + return globalMapPathUUID, nil +} + +func compareSymlinks(global, pod string) (bool, error) { + devGlobal, err := os.Readlink(global) + if err != nil { + return false, err + } + devPod, err := os.Readlink(pod) + if err != nil { + return false, err + } + glog.V(5).Infof("CompareSymlinks: devGloBal %s, devPod %s", devGlobal, devPod) + if devGlobal == devPod { + return true, nil + } + return false, nil +} diff --git a/pkg/volume/util/util_linux.go b/pkg/volume/util/util_linux.go new file mode 100644 index 00000000000..755a10f012a --- /dev/null +++ b/pkg/volume/util/util_linux.go @@ -0,0 +1,106 @@ +// +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 util + +import ( + "errors" + "fmt" + "os" + "os/exec" + "strings" + + "github.com/golang/glog" +) + +// AttachFileDevice takes a path to a regular file and makes it available as an +// attached block device. +func (v VolumePathHandler) AttachFileDevice(path string) (string, error) { + blockDevicePath, err := v.GetLoopDevice(path) + if err != nil && err.Error() != ErrDeviceNotFound { + return "", err + } + + // If no existing loop device for the path, create one + if blockDevicePath == "" { + glog.V(4).Infof("Creating device for path: %s", path) + blockDevicePath, err = makeLoopDevice(path) + if err != nil { + return "", err + } + } + return blockDevicePath, nil +} + +// GetLoopDevice returns the full path to the loop device associated with the given path. +func (v VolumePathHandler) GetLoopDevice(path string) (string, error) { + _, err := os.Stat(path) + if os.IsNotExist(err) { + return "", errors.New(ErrNotAvailable) + } + if err != nil { + return "", fmt.Errorf("not attachable: %v", err) + } + + args := []string{"-j", path} + cmd := exec.Command(losetupPath, args...) + out, err := cmd.CombinedOutput() + if err != nil { + glog.V(2).Infof("Failed device discover command for path %s: %v", path, err) + return "", err + } + return parseLosetupOutputForDevice(out) +} + +func makeLoopDevice(path string) (string, error) { + args := []string{"-f", "--show", path} + cmd := exec.Command(losetupPath, args...) + out, err := cmd.CombinedOutput() + if err != nil { + glog.V(2).Infof("Failed device create command for path %s: %v", path, err) + return "", err + } + return parseLosetupOutputForDevice(out) +} + +// RemoveLoopDevice removes specified loopback device +func (v VolumePathHandler) RemoveLoopDevice(device string) error { + args := []string{"-d", device} + cmd := exec.Command(losetupPath, args...) + out, err := cmd.CombinedOutput() + if err != nil { + if !strings.Contains(string(out), "No such device or address") { + return err + } + } + return nil +} + +func parseLosetupOutputForDevice(output []byte) (string, error) { + if len(output) == 0 { + return "", errors.New(ErrDeviceNotFound) + } + + // losetup returns device in the format: + // /dev/loop1: [0073]:148662 (/dev/sda) + device := strings.TrimSpace(strings.SplitN(string(output), ":", 2)[0]) + if len(device) == 0 { + return "", errors.New(ErrDeviceNotFound) + } + return device, nil +} diff --git a/pkg/volume/util/util_test.go b/pkg/volume/util/util_test.go index 480e5762739..b11be33eeb2 100644 --- a/pkg/volume/util/util_test.go +++ b/pkg/volume/util/util_test.go @@ -26,8 +26,8 @@ import ( "k8s.io/apimachinery/pkg/util/sets" // util.go uses api.Codecs.LegacyCodec so import this package to do some // resource initialization. - _ "k8s.io/kubernetes/pkg/api/install" - "k8s.io/kubernetes/pkg/api/v1/helper" + _ "k8s.io/kubernetes/pkg/apis/core/install" + "k8s.io/kubernetes/pkg/apis/core/v1/helper" ) var nodeLabels map[string]string = map[string]string{ diff --git a/pkg/volume/util/util_unsupported.go b/pkg/volume/util/util_unsupported.go new file mode 100644 index 00000000000..930e4f663dd --- /dev/null +++ b/pkg/volume/util/util_unsupported.go @@ -0,0 +1,39 @@ +// +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 util + +import ( + "fmt" +) + +// AttachFileDevice takes a path to a regular file and makes it available as an +// attached block device. +func (v VolumePathHandler) AttachFileDevice(path string) (string, error) { + return "", fmt.Errorf("AttachFileDevice not supported for this build.") +} + +// GetLoopDevice returns the full path to the loop device associated with the given path. +func (v VolumePathHandler) GetLoopDevice(path string) (string, error) { + return "", fmt.Errorf("GetLoopDevice not supported for this build.") +} + +// RemoveLoopDevice removes specified loopback device +func (v VolumePathHandler) RemoveLoopDevice(device string) error { + return fmt.Errorf("RemoveLoopDevice not supported for this build.") +} diff --git a/pkg/volume/util/volumehelper/volumehelper.go b/pkg/volume/util/volumehelper/volumehelper.go index b0734601d0a..74b14be5de8 100644 --- a/pkg/volume/util/volumehelper/volumehelper.go +++ b/pkg/volume/util/volumehelper/volumehelper.go @@ -136,3 +136,24 @@ func NewSafeFormatAndMountFromHost(pluginName string, host volume.VolumeHost) *m exec := host.GetExec(pluginName) return &mount.SafeFormatAndMount{Interface: mounter, Exec: exec} } + +// GetVolumeMode retrieves VolumeMode from pv. +// If the volume doesn't have PersistentVolume, it's an inline volume, +// should return volumeMode as filesystem to keep existing behavior. +func GetVolumeMode(volumeSpec *volume.Spec) (v1.PersistentVolumeMode, error) { + if volumeSpec == nil || volumeSpec.PersistentVolume == nil { + return v1.PersistentVolumeFilesystem, nil + } + if volumeSpec.PersistentVolume.Spec.VolumeMode != nil { + return *volumeSpec.PersistentVolume.Spec.VolumeMode, nil + } + return "", fmt.Errorf("cannot get volumeMode for volume: %v", volumeSpec.Name()) +} + +// GetPersistentVolumeClaimVolumeMode retrieves VolumeMode from pvc. +func GetPersistentVolumeClaimVolumeMode(claim *v1.PersistentVolumeClaim) (v1.PersistentVolumeMode, error) { + if claim.Spec.VolumeMode != nil { + return *claim.Spec.VolumeMode, nil + } + return "", fmt.Errorf("cannot get volumeMode from pvc: %v", claim.Name) +} diff --git a/pkg/volume/util_test.go b/pkg/volume/util_test.go index 6eeb00a7543..273722a0c3d 100644 --- a/pkg/volume/util_test.go +++ b/pkg/volume/util_test.go @@ -29,7 +29,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/watch" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/util/slice" ) @@ -128,7 +128,7 @@ func TestRecyclerPod(t *testing.T) { {v1.EventTypeWarning, "Unable to mount volumes for pod \"recycler-for-podRecyclerFailure_default(3c9809e5-347c-11e6-a79b-3c970e965218)\": timeout expired waiting for volumes to attach/mount"}, {v1.EventTypeWarning, "Error syncing pod, skipping: timeout expired waiting for volumes to attach/mount for pod \"default\"/\"recycler-for-podRecyclerFailure\". list of unattached/unmounted"}, }, - expectedError: "Pod was active on the node longer than specified deadline", + expectedError: "failed to recycle volume: Pod was active on the node longer than specified deadline", }, { // Recycler pod gets deleted @@ -143,32 +143,15 @@ func TestRecyclerPod(t *testing.T) { expectedEvents: []mockEvent{ {v1.EventTypeNormal, "Successfully assigned recycler-for-podRecyclerDeleted to 127.0.0.1"}, }, - expectedError: "recycler pod was deleted", + expectedError: "failed to recycle volume: recycler pod was deleted", }, { // Another recycler pod is already running - name: "RecyclerRunning", - existingPod: newPod("podOldRecycler", v1.PodRunning, ""), - createPod: newPod("podNewRecycler", v1.PodFailed, "mock message"), - eventSequence: []watch.Event{ - // Old pod succeeds - newPodEvent(watch.Modified, "podOldRecycler", v1.PodSucceeded, ""), - }, - // No error = old pod succeeded. If the new pod was used, there - // would be error with "mock message". - expectedError: "", - }, - { - // Another recycler pod is already running and fails - name: "FailedRecyclerRunning", - existingPod: newPod("podOldRecycler", v1.PodRunning, ""), - createPod: newPod("podNewRecycler", v1.PodFailed, "mock message"), - eventSequence: []watch.Event{ - // Old pod failure - newPodEvent(watch.Modified, "podOldRecycler", v1.PodFailed, "Pod was active on the node longer than specified deadline"), - }, - // If the new pod was used, there would be error with "mock message". - expectedError: "Pod was active on the node longer than specified deadline", + name: "RecyclerRunning", + existingPod: newPod("podOldRecycler", v1.PodRunning, ""), + createPod: newPod("podNewRecycler", v1.PodFailed, "mock message"), + eventSequence: []watch.Event{}, + expectedError: "old recycler pod found, will retry later", }, } @@ -886,3 +869,34 @@ func TestValidateZone(t *testing.T) { } } } + +func TestGetWindowsPath(t *testing.T) { + tests := []struct { + path string + expectedPath string + }{ + { + path: `/var/lib/kubelet/pods/146f8428-83e7-11e7-8dd4-000d3a31dac4/volumes/kubernetes.io~disk`, + expectedPath: `c:\var\lib\kubelet\pods\146f8428-83e7-11e7-8dd4-000d3a31dac4\volumes\kubernetes.io~disk`, + }, + { + path: `\var/lib/kubelet/pods/146f8428-83e7-11e7-8dd4-000d3a31dac4\volumes\kubernetes.io~disk`, + expectedPath: `c:\var\lib\kubelet\pods\146f8428-83e7-11e7-8dd4-000d3a31dac4\volumes\kubernetes.io~disk`, + }, + { + path: `/`, + expectedPath: `c:\`, + }, + { + path: ``, + expectedPath: ``, + }, + } + + for _, test := range tests { + result := GetWindowsPath(test.path) + if result != test.expectedPath { + t.Errorf("GetWindowsPath(%v) returned (%v), want (%v)", test.path, result, test.expectedPath) + } + } +} diff --git a/pkg/volume/validation/BUILD b/pkg/volume/validation/BUILD index fcda122d2c2..aa814925ee7 100644 --- a/pkg/volume/validation/BUILD +++ b/pkg/volume/validation/BUILD @@ -9,10 +9,10 @@ load( go_test( name = "go_default_test", srcs = ["pv_validation_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/validation", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", ], @@ -23,7 +23,7 @@ go_library( srcs = ["pv_validation.go"], importpath = "k8s.io/kubernetes/pkg/volume/validation", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", ], ) diff --git a/pkg/volume/validation/pv_validation.go b/pkg/volume/validation/pv_validation.go index 45db2f5e526..23fa51fdac9 100644 --- a/pkg/volume/validation/pv_validation.go +++ b/pkg/volume/validation/pv_validation.go @@ -22,7 +22,7 @@ import ( "strings" "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // ValidatePersistentVolume validates PV object for plugin specific validation diff --git a/pkg/volume/validation/pv_validation_test.go b/pkg/volume/validation/pv_validation_test.go index d0a8f1c3eec..d7601185b99 100644 --- a/pkg/volume/validation/pv_validation_test.go +++ b/pkg/volume/validation/pv_validation_test.go @@ -21,7 +21,7 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestValidatePersistentVolumes(t *testing.T) { diff --git a/pkg/volume/volume.go b/pkg/volume/volume.go index 78cb21a32a7..471963556a8 100644 --- a/pkg/volume/volume.go +++ b/pkg/volume/volume.go @@ -37,6 +37,19 @@ type Volume interface { MetricsProvider } +// BlockVolume interface provides methods to generate global map path +// and pod device map path. +type BlockVolume interface { + // GetGlobalMapPath returns a global map path which contains + // symbolic links associated to a block device. + // ex. plugins/kubernetes.io/{PluginName}/{DefaultKubeletVolumeDevicesDirName}/{volumePluginDependentPath}/{pod uuid} + GetGlobalMapPath(spec *Spec) (string, error) + // GetPodDeviceMapPath returns a pod device map path + // and name of a symbolic link associated to a block device. + // ex. pods/{podUid}}/{DefaultKubeletVolumeDevicesDirName}/{escapeQualifiedPluginName}/{volumeName} + GetPodDeviceMapPath() (string, string) +} + // MetricsProvider exposes metrics (e.g. used,available space) related to a // Volume. type MetricsProvider interface { @@ -132,6 +145,34 @@ type Unmounter interface { TearDownAt(dir string) error } +// BlockVolumeMapper interface provides methods to set up/map the volume. +type BlockVolumeMapper interface { + BlockVolume + // SetUpDevice prepares the volume to a self-determined directory path, + // which may or may not exist yet and returns combination of physical + // device path of a block volume and error. + // If the plugin is non-attachable, it should prepare the device + // in /dev/ (or where appropriate) and return unique device path. + // Unique device path across kubelet node reboot is required to avoid + // unexpected block volume destruction. + // If the plugin is attachable, it should not do anything here, + // just return empty string for device path. + // Instead, attachable plugin have to return unique device path + // at attacher.Attach() and attacher.WaitForAttach(). + // This may be called more than once, so implementations must be idempotent. + SetUpDevice() (string, error) +} + +// BlockVolumeUnmapper interface provides methods to cleanup/unmap the volumes. +type BlockVolumeUnmapper interface { + BlockVolume + // TearDownDevice removes traces of the SetUpDevice procedure under + // a self-determined directory. + // If the plugin is non-attachable, this method detaches the volume + // from a node. + TearDownDevice(mapPath string, devicePath string) error +} + // Provisioner is an interface that creates templates for PersistentVolumes // and can create the volume as a new resource in the infrastructure provider. type Provisioner interface { @@ -195,8 +236,10 @@ type BulkVolumeVerifier interface { // Detacher can detach a volume from a node. type Detacher interface { - // Detach the given device from the node with the given Name. - Detach(deviceName string, nodeName types.NodeName) error + // Detach the given volume from the node with the given Name. + // volumeName is name of the volume as returned from plugin's + // GetVolumeName(). + Detach(volumeName string, nodeName types.NodeName) error // UnmountDevice unmounts the global mount of the disk. This // should only be called once all bind mounts have been diff --git a/pkg/volume/vsphere_volume/BUILD b/pkg/volume/vsphere_volume/BUILD index a2bd11e8fbc..bde1c437bdf 100644 --- a/pkg/volume/vsphere_volume/BUILD +++ b/pkg/volume/vsphere_volume/BUILD @@ -38,8 +38,8 @@ go_test( "attacher_test.go", "vsphere_volume_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/volume/vsphere_volume", - library = ":go_default_library", deps = [ "//pkg/cloudprovider/providers/vsphere/vclib:go_default_library", "//pkg/util/mount:go_default_library", diff --git a/pkg/volume/vsphere_volume/OWNERS b/pkg/volume/vsphere_volume/OWNERS index b960f96b681..5f7fd3d2e9e 100755 --- a/pkg/volume/vsphere_volume/OWNERS +++ b/pkg/volume/vsphere_volume/OWNERS @@ -7,11 +7,6 @@ approvers: reviewers: - abithap - abrarshivani -- thockin -- smarterclayton -- brendandburns -- derekwaynecarr -- pmorie - saad-ali - justinsb - jsafrane diff --git a/pkg/volume/vsphere_volume/attacher.go b/pkg/volume/vsphere_volume/attacher.go index aeffd9610d7..fb9886beba2 100644 --- a/pkg/volume/vsphere_volume/attacher.go +++ b/pkg/volume/vsphere_volume/attacher.go @@ -76,7 +76,7 @@ func (attacher *vsphereVMDKAttacher) Attach(spec *volume.Spec, nodeName types.No // vsphereCloud.AttachDisk checks if disk is already attached to host and // succeeds in that case, so no need to do that separately. - diskUUID, err := attacher.vsphereVolumes.AttachDisk(volumeSource.VolumePath, volumeSource.StoragePolicyID, nodeName) + diskUUID, err := attacher.vsphereVolumes.AttachDisk(volumeSource.VolumePath, volumeSource.StoragePolicyName, nodeName) if err != nil { glog.Errorf("Error attaching volume %q to node %q: %+v", volumeSource.VolumePath, nodeName, err) return "", err @@ -251,9 +251,9 @@ func (plugin *vsphereVolumePlugin) NewDetacher() (volume.Detacher, error) { } // Detach the given device from the given node. -func (detacher *vsphereVMDKDetacher) Detach(deviceMountPath string, nodeName types.NodeName) error { +func (detacher *vsphereVMDKDetacher) Detach(volumeName string, nodeName types.NodeName) error { - volPath := getVolPathfromDeviceMountPath(deviceMountPath) + volPath := getVolPathfromVolumeName(volumeName) attached, err := detacher.vsphereVolumes.DiskIsAttached(volPath, nodeName) if err != nil { // Log error and continue with detach diff --git a/pkg/volume/vsphere_volume/vsphere_volume_test.go b/pkg/volume/vsphere_volume/vsphere_volume_test.go index 282cb23d602..34fec92d618 100644 --- a/pkg/volume/vsphere_volume/vsphere_volume_test.go +++ b/pkg/volume/vsphere_volume/vsphere_volume_test.go @@ -144,7 +144,7 @@ func TestPlugin(t *testing.T) { if _, err := os.Stat(path); err == nil { t.Errorf("TearDown() failed, volume path still exists: %s", path) } else if !os.IsNotExist(err) { - t.Errorf("SetUp() failed: %v", err) + t.Errorf("TearDown() failed: %v", err) } // Test Provisioner diff --git a/pkg/volume/vsphere_volume/vsphere_volume_util.go b/pkg/volume/vsphere_volume/vsphere_volume_util.go index 62ce1f7620b..0f5d335ec54 100644 --- a/pkg/volume/vsphere_volume/vsphere_volume_util.go +++ b/pkg/volume/vsphere_volume/vsphere_volume_util.go @@ -170,7 +170,7 @@ func (util *VsphereDiskUtil) DeleteVolume(vd *vsphereVolumeDeleter) error { return nil } -func getVolPathfromDeviceMountPath(deviceMountPath string) string { +func getVolPathfromVolumeName(deviceMountPath string) string { // Assumption: No file or folder is named starting with '[' in datastore volPath := deviceMountPath[strings.LastIndex(deviceMountPath, "["):] // space between datastore and vmdk name in volumePath is encoded as '\040' when returned by GetMountRefs(). diff --git a/plugin/BUILD b/plugin/BUILD index 106463906f0..2d7dd155c1c 100644 --- a/plugin/BUILD +++ b/plugin/BUILD @@ -11,7 +11,6 @@ filegroup( name = "all-srcs", srcs = [ ":package-srcs", - "//plugin/cmd/kube-scheduler:all-srcs", "//plugin/pkg/admission/admit:all-srcs", "//plugin/pkg/admission/alwayspullimages:all-srcs", "//plugin/pkg/admission/antiaffinity:all-srcs", @@ -19,6 +18,7 @@ filegroup( "//plugin/pkg/admission/deny:all-srcs", "//plugin/pkg/admission/eventratelimit:all-srcs", "//plugin/pkg/admission/exec:all-srcs", + "//plugin/pkg/admission/extendedresourcetoleration:all-srcs", "//plugin/pkg/admission/gc:all-srcs", "//plugin/pkg/admission/imagepolicy:all-srcs", "//plugin/pkg/admission/initialresources:all-srcs", @@ -28,6 +28,7 @@ filegroup( "//plugin/pkg/admission/noderestriction:all-srcs", "//plugin/pkg/admission/persistentvolume/label:all-srcs", "//plugin/pkg/admission/persistentvolume/resize:all-srcs", + "//plugin/pkg/admission/persistentvolumeclaim/pvcprotection:all-srcs", "//plugin/pkg/admission/podnodeselector:all-srcs", "//plugin/pkg/admission/podpreset:all-srcs", "//plugin/pkg/admission/podtolerationrestriction:all-srcs", @@ -37,9 +38,7 @@ filegroup( "//plugin/pkg/admission/securitycontext/scdeny:all-srcs", "//plugin/pkg/admission/serviceaccount:all-srcs", "//plugin/pkg/admission/storageclass/setdefault:all-srcs", - "//plugin/pkg/admission/webhook:all-srcs", "//plugin/pkg/auth:all-srcs", - "//plugin/pkg/scheduler:all-srcs", ], tags = ["automanaged"], ) diff --git a/plugin/cmd/kube-scheduler/BUILD b/plugin/cmd/kube-scheduler/BUILD deleted file mode 100644 index cbe26076ab9..00000000000 --- a/plugin/cmd/kube-scheduler/BUILD +++ /dev/null @@ -1,52 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_binary", - "go_library", -) -load("//pkg/version:def.bzl", "version_x_defs") - -go_binary( - name = "kube-scheduler", - gc_linkopts = [ - "-linkmode", - "external", - "-extldflags", - "-static", - ], - importpath = "k8s.io/kubernetes/plugin/cmd/kube-scheduler", - library = ":go_default_library", - x_defs = version_x_defs(), -) - -go_library( - name = "go_default_library", - srcs = ["scheduler.go"], - importpath = "k8s.io/kubernetes/plugin/cmd/kube-scheduler", - deps = [ - "//pkg/version/verflag:go_default_library", - "//plugin/cmd/kube-scheduler/app:go_default_library", - "//plugin/cmd/kube-scheduler/app/options:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/github.com/spf13/pflag:go_default_library", - "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library", - "//vendor/k8s.io/apiserver/pkg/util/logs:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//plugin/cmd/kube-scheduler/app:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/plugin/cmd/kube-scheduler/app/BUILD b/plugin/cmd/kube-scheduler/app/BUILD deleted file mode 100644 index d0d3a7477b1..00000000000 --- a/plugin/cmd/kube-scheduler/app/BUILD +++ /dev/null @@ -1,73 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = [ - "configurator.go", - "server.go", - ], - importpath = "k8s.io/kubernetes/plugin/cmd/kube-scheduler/app", - deps = [ - "//pkg/api:go_default_library", - "//pkg/controller:go_default_library", - "//pkg/features:go_default_library", - "//pkg/util/configz:go_default_library", - "//pkg/version:go_default_library", - "//plugin/cmd/kube-scheduler/app/options:go_default_library", - "//plugin/pkg/scheduler:go_default_library", - "//plugin/pkg/scheduler/algorithmprovider:go_default_library", - "//plugin/pkg/scheduler/api:go_default_library", - "//plugin/pkg/scheduler/api/latest:go_default_library", - "//plugin/pkg/scheduler/factory:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", - "//vendor/github.com/spf13/cobra:go_default_library", - "//vendor/github.com/spf13/pflag:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", - "//vendor/k8s.io/apiserver/pkg/server/healthz:go_default_library", - "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", - "//vendor/k8s.io/client-go/informers:go_default_library", - "//vendor/k8s.io/client-go/informers/apps/v1beta1:go_default_library", - "//vendor/k8s.io/client-go/informers/core/v1:go_default_library", - "//vendor/k8s.io/client-go/informers/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", - "//vendor/k8s.io/client-go/tools/leaderelection:go_default_library", - "//vendor/k8s.io/client-go/tools/leaderelection/resourcelock:go_default_library", - "//vendor/k8s.io/client-go/tools/record:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//plugin/cmd/kube-scheduler/app/options:all-srcs", - ], - tags = ["automanaged"], -) - -go_test( - name = "go_default_test", - srcs = ["configurator_test.go"], - importpath = "k8s.io/kubernetes/plugin/cmd/kube-scheduler/app", - library = ":go_default_library", -) diff --git a/plugin/cmd/kube-scheduler/app/configurator.go b/plugin/cmd/kube-scheduler/app/configurator.go deleted file mode 100644 index 9bb87d43e1d..00000000000 --- a/plugin/cmd/kube-scheduler/app/configurator.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 app - -import ( - "fmt" - "io/ioutil" - "os" - - appsinformers "k8s.io/client-go/informers/apps/v1beta1" - coreinformers "k8s.io/client-go/informers/core/v1" - extensionsinformers "k8s.io/client-go/informers/extensions/v1beta1" - "k8s.io/kubernetes/plugin/cmd/kube-scheduler/app/options" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - utilfeature "k8s.io/apiserver/pkg/util/feature" - v1core "k8s.io/client-go/kubernetes/typed/core/v1" - - clientset "k8s.io/client-go/kubernetes" - restclient "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/tools/record" - "k8s.io/kubernetes/pkg/api" - - "k8s.io/api/core/v1" - - "k8s.io/kubernetes/pkg/features" - "k8s.io/kubernetes/plugin/pkg/scheduler" - _ "k8s.io/kubernetes/plugin/pkg/scheduler/algorithmprovider" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - latestschedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api/latest" - "k8s.io/kubernetes/plugin/pkg/scheduler/factory" - - "github.com/golang/glog" -) - -func createRecorder(kubecli *clientset.Clientset, s *options.SchedulerServer) record.EventRecorder { - eventBroadcaster := record.NewBroadcaster() - eventBroadcaster.StartLogging(glog.Infof) - eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubecli.CoreV1().RESTClient()).Events("")}) - return eventBroadcaster.NewRecorder(api.Scheme, v1.EventSource{Component: s.SchedulerName}) -} - -func createClients(s *options.SchedulerServer) (*clientset.Clientset, *clientset.Clientset, error) { - kubeconfig, err := clientcmd.BuildConfigFromFlags(s.Master, s.Kubeconfig) - if err != nil { - return nil, nil, fmt.Errorf("unable to build config from flags: %v", err) - } - - kubeconfig.ContentType = s.ContentType - // Override kubeconfig qps/burst settings from flags - kubeconfig.QPS = s.KubeAPIQPS - kubeconfig.Burst = int(s.KubeAPIBurst) - kubeClient, err := clientset.NewForConfig(restclient.AddUserAgent(kubeconfig, "scheduler")) - if err != nil { - glog.Fatalf("Invalid API configuration: %v", err) - } - leaderElectionClient := clientset.NewForConfigOrDie(restclient.AddUserAgent(kubeconfig, "leader-election")) - return kubeClient, leaderElectionClient, nil -} - -// CreateScheduler encapsulates the entire creation of a runnable scheduler. -func CreateScheduler( - s *options.SchedulerServer, - kubecli clientset.Interface, - nodeInformer coreinformers.NodeInformer, - podInformer coreinformers.PodInformer, - pvInformer coreinformers.PersistentVolumeInformer, - pvcInformer coreinformers.PersistentVolumeClaimInformer, - replicationControllerInformer coreinformers.ReplicationControllerInformer, - replicaSetInformer extensionsinformers.ReplicaSetInformer, - statefulSetInformer appsinformers.StatefulSetInformer, - serviceInformer coreinformers.ServiceInformer, - recorder record.EventRecorder, -) (*scheduler.Scheduler, error) { - configurator := factory.NewConfigFactory( - s.SchedulerName, - kubecli, - nodeInformer, - podInformer, - pvInformer, - pvcInformer, - replicationControllerInformer, - replicaSetInformer, - statefulSetInformer, - serviceInformer, - s.HardPodAffinitySymmetricWeight, - utilfeature.DefaultFeatureGate.Enabled(features.EnableEquivalenceClassCache), - ) - - // Rebuild the configurator with a default Create(...) method. - configurator = &schedulerConfigurator{ - configurator, - s.PolicyConfigFile, - s.AlgorithmProvider, - s.PolicyConfigMapName, - s.PolicyConfigMapNamespace, - s.UseLegacyPolicyConfig, - } - - return scheduler.NewFromConfigurator(configurator, func(cfg *scheduler.Config) { - cfg.Recorder = recorder - }) -} - -// schedulerConfigurator is an interface wrapper that provides a way to create -// a scheduler from a user provided config file or ConfigMap object. -type schedulerConfigurator struct { - scheduler.Configurator - policyFile string - algorithmProvider string - policyConfigMap string - policyConfigMapNamespace string - useLegacyPolicyConfig bool -} - -// getSchedulerPolicyConfig finds and decodes scheduler's policy config. If no -// such policy is found, it returns nil, nil. -func (sc schedulerConfigurator) getSchedulerPolicyConfig() (*schedulerapi.Policy, error) { - var configData []byte - var policyConfigMapFound bool - var policy schedulerapi.Policy - - // If not in legacy mode, try to find policy ConfigMap. - if !sc.useLegacyPolicyConfig && len(sc.policyConfigMap) != 0 { - namespace := sc.policyConfigMapNamespace - policyConfigMap, err := sc.GetClient().CoreV1().ConfigMaps(namespace).Get(sc.policyConfigMap, metav1.GetOptions{}) - if err != nil { - return nil, fmt.Errorf("Error getting scheduler policy ConfigMap: %v.", err) - } - if policyConfigMap != nil { - var configString string - configString, policyConfigMapFound = policyConfigMap.Data[options.SchedulerPolicyConfigMapKey] - if !policyConfigMapFound { - return nil, fmt.Errorf("No element with key = '%v' is found in the ConfigMap 'Data'.", options.SchedulerPolicyConfigMapKey) - } - glog.V(5).Infof("Scheduler policy ConfigMap: %v", configString) - configData = []byte(configString) - } - } - - // If we are in legacy mode or ConfigMap name is empty, try to use policy - // config file. - if !policyConfigMapFound { - if _, err := os.Stat(sc.policyFile); err != nil { - // No config file is found. - return nil, nil - } - var err error - configData, err = ioutil.ReadFile(sc.policyFile) - if err != nil { - return nil, fmt.Errorf("unable to read policy config: %v", err) - } - } - - if err := runtime.DecodeInto(latestschedulerapi.Codec, configData, &policy); err != nil { - return nil, fmt.Errorf("invalid configuration: %v", err) - } - return &policy, nil -} - -// Create implements the interface for the Configurator, hence it is exported -// even though the struct is not. -func (sc schedulerConfigurator) Create() (*scheduler.Config, error) { - policy, err := sc.getSchedulerPolicyConfig() - if err != nil { - return nil, err - } - // If no policy is found, create scheduler from algorithm provider. - if policy == nil { - if sc.Configurator != nil { - return sc.Configurator.CreateFromProvider(sc.algorithmProvider) - } - return nil, fmt.Errorf("Configurator was nil") - } - - return sc.CreateFromConfig(*policy) -} diff --git a/plugin/cmd/kube-scheduler/app/configurator_test.go b/plugin/cmd/kube-scheduler/app/configurator_test.go deleted file mode 100644 index c4bc1bd1424..00000000000 --- a/plugin/cmd/kube-scheduler/app/configurator_test.go +++ /dev/null @@ -1,31 +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 app - -import ( - "testing" -) - -func TestSchedulerConfiguratorFailure(t *testing.T) { - sc := &schedulerConfigurator{ - // policyfile and algorithm are intentionally undefined. - } - _, err := sc.Create() - if err == nil { - t.Fatalf("Expected error message when creating with incomplete configurator.") - } -} diff --git a/plugin/cmd/kube-scheduler/app/options/BUILD b/plugin/cmd/kube-scheduler/app/options/BUILD deleted file mode 100644 index bef3f769b18..00000000000 --- a/plugin/cmd/kube-scheduler/app/options/BUILD +++ /dev/null @@ -1,51 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = ["options.go"], - importpath = "k8s.io/kubernetes/plugin/cmd/kube-scheduler/app/options", - deps = [ - "//pkg/api:go_default_library", - "//pkg/apis/componentconfig:go_default_library", - "//pkg/apis/componentconfig/install:go_default_library", - "//pkg/apis/componentconfig/v1alpha1:go_default_library", - "//pkg/client/leaderelectionconfig:go_default_library", - "//pkg/features:go_default_library", - "//pkg/kubelet/apis:go_default_library", - "//plugin/pkg/scheduler/factory:go_default_library", - "//vendor/github.com/spf13/pflag:go_default_library", - "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) - -go_test( - name = "go_default_test", - srcs = ["options_test.go"], - importpath = "k8s.io/kubernetes/plugin/cmd/kube-scheduler/app/options", - library = ":go_default_library", - deps = [ - "//pkg/apis/componentconfig:go_default_library", - "//vendor/github.com/spf13/pflag:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", - ], -) diff --git a/plugin/cmd/kube-scheduler/app/options/options.go b/plugin/cmd/kube-scheduler/app/options/options.go deleted file mode 100644 index 4cacad8f5de..00000000000 --- a/plugin/cmd/kube-scheduler/app/options/options.go +++ /dev/null @@ -1,96 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES 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 provides the scheduler flags -package options - -import ( - "fmt" - - utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/componentconfig" - "k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1" - "k8s.io/kubernetes/pkg/client/leaderelectionconfig" - kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis" - "k8s.io/kubernetes/plugin/pkg/scheduler/factory" - - // add the kubernetes feature gates - _ "k8s.io/kubernetes/pkg/features" - // install the componentconfig api so we get its defaulting and conversion functions - _ "k8s.io/kubernetes/pkg/apis/componentconfig/install" - - "github.com/spf13/pflag" -) - -// SchedulerPolicyConfigMapKey defines the key of the element in the -// scheduler's policy ConfigMap that contains scheduler's policy config. -const SchedulerPolicyConfigMapKey string = "policy.cfg" - -// SchedulerServer has all the context and params needed to run a Scheduler -type SchedulerServer struct { - componentconfig.KubeSchedulerConfiguration - // Master is the address of the Kubernetes API server (overrides any - // value in kubeconfig). - Master string - // Kubeconfig is Path to kubeconfig file with authorization and master - // location information. - Kubeconfig string - // Dynamic configuration for scheduler features. -} - -// NewSchedulerServer creates a new SchedulerServer with default parameters -func NewSchedulerServer() *SchedulerServer { - versioned := &v1alpha1.KubeSchedulerConfiguration{} - api.Scheme.Default(versioned) - cfg := componentconfig.KubeSchedulerConfiguration{} - api.Scheme.Convert(versioned, &cfg, nil) - cfg.LeaderElection.LeaderElect = true - s := SchedulerServer{ - KubeSchedulerConfiguration: cfg, - } - return &s -} - -// AddFlags adds flags for a specific SchedulerServer to the specified FlagSet -func (s *SchedulerServer) AddFlags(fs *pflag.FlagSet) { - fs.Int32Var(&s.Port, "port", s.Port, "The port that the scheduler's http service runs on") - fs.StringVar(&s.Address, "address", s.Address, "The IP address to serve on (set to 0.0.0.0 for all interfaces)") - fs.StringVar(&s.AlgorithmProvider, "algorithm-provider", s.AlgorithmProvider, "The scheduling algorithm provider to use, one of: "+factory.ListAlgorithmProviders()) - fs.StringVar(&s.PolicyConfigFile, "policy-config-file", s.PolicyConfigFile, "File with scheduler policy configuration. This file is used if policy ConfigMap is not provided or --use-legacy-policy-config==true") - usage := fmt.Sprintf("Name of the ConfigMap object that contains scheduler's policy configuration. It must exist in the system namespace before scheduler initialization if --use-legacy-policy-config==false. The config must be provided as the value of an element in 'Data' map with the key='%v'", SchedulerPolicyConfigMapKey) - fs.StringVar(&s.PolicyConfigMapName, "policy-configmap", s.PolicyConfigMapName, usage) - fs.StringVar(&s.PolicyConfigMapNamespace, "policy-configmap-namespace", s.PolicyConfigMapNamespace, "The namespace where policy ConfigMap is located. The system namespace will be used if this is not provided or is empty.") - fs.BoolVar(&s.UseLegacyPolicyConfig, "use-legacy-policy-config", false, "When set to true, scheduler will ignore policy ConfigMap and uses policy config file") - fs.BoolVar(&s.EnableProfiling, "profiling", true, "Enable profiling via web interface host:port/debug/pprof/") - fs.BoolVar(&s.EnableContentionProfiling, "contention-profiling", false, "Enable lock contention profiling, if profiling is enabled") - fs.StringVar(&s.Master, "master", s.Master, "The address of the Kubernetes API server (overrides any value in kubeconfig)") - fs.StringVar(&s.Kubeconfig, "kubeconfig", s.Kubeconfig, "Path to kubeconfig file with authorization and master location information.") - fs.StringVar(&s.ContentType, "kube-api-content-type", s.ContentType, "Content type of requests sent to apiserver.") - fs.Float32Var(&s.KubeAPIQPS, "kube-api-qps", s.KubeAPIQPS, "QPS to use while talking with kubernetes apiserver") - fs.Int32Var(&s.KubeAPIBurst, "kube-api-burst", s.KubeAPIBurst, "Burst to use while talking with kubernetes apiserver") - fs.StringVar(&s.SchedulerName, "scheduler-name", s.SchedulerName, "Name of the scheduler, used to select which pods will be processed by this scheduler, based on pod's \"spec.SchedulerName\".") - fs.StringVar(&s.LockObjectNamespace, "lock-object-namespace", s.LockObjectNamespace, "Define the namespace of the lock object.") - fs.StringVar(&s.LockObjectName, "lock-object-name", s.LockObjectName, "Define the name of the lock object.") - fs.IntVar(&s.HardPodAffinitySymmetricWeight, "hard-pod-affinity-symmetric-weight", api.DefaultHardPodAffinitySymmetricWeight, - "RequiredDuringScheduling affinity is not symmetric, but there is an implicit PreferredDuringScheduling affinity rule corresponding "+ - "to every RequiredDuringScheduling affinity rule. --hard-pod-affinity-symmetric-weight represents the weight of implicit PreferredDuringScheduling affinity rule.") - fs.MarkDeprecated("hard-pod-affinity-symmetric-weight", "This option was moved to the policy configuration file") - fs.StringVar(&s.FailureDomains, "failure-domains", kubeletapis.DefaultFailureDomains, "Indicate the \"all topologies\" set for an empty topologyKey when it's used for PreferredDuringScheduling pod anti-affinity.") - fs.MarkDeprecated("failure-domains", "Doesn't have any effect. Will be removed in future version.") - leaderelectionconfig.BindFlags(&s.LeaderElection, fs) - utilfeature.DefaultFeatureGate.AddFlag(fs) -} diff --git a/plugin/cmd/kube-scheduler/app/options/options_test.go b/plugin/cmd/kube-scheduler/app/options/options_test.go deleted file mode 100644 index bd6172ccb37..00000000000 --- a/plugin/cmd/kube-scheduler/app/options/options_test.go +++ /dev/null @@ -1,103 +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 options - -import ( - "reflect" - "testing" - "time" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/diff" - "k8s.io/kubernetes/pkg/apis/componentconfig" - - "github.com/spf13/pflag" -) - -func TestAddFlags(t *testing.T) { - f := pflag.NewFlagSet("addflagstest", pflag.ContinueOnError) - s := NewSchedulerServer() - s.AddFlags(f) - - args := []string{ - "--address=192.168.4.20", - "--algorithm-provider=FooProvider", - "--contention-profiling=true", - "--failure-domains=kubernetes.io/hostname", - "--hard-pod-affinity-symmetric-weight=0", - "--kube-api-burst=80", - "--kube-api-content-type=application/vnd.kubernetes.protobuf", - "--kube-api-qps=40.0", - "--kubeconfig=/foo/bar/kubeconfig", - "--leader-elect=true", - "--leader-elect-lease-duration=20s", - "--leader-elect-renew-deadline=15s", - "--leader-elect-resource-lock=endpoints", - "--leader-elect-retry-period=3s", - "--lock-object-name=test-lock-object-name", - "--lock-object-namespace=test-lock-object-ns", - "--master=192.168.4.20", - "--policy-config-file=/foo/bar/policyconfig", - "--policy-configmap=test-policy-configmap", - "--policy-configmap-namespace=test-policy-configmap-ns", - "--port=10000", - "--profiling=false", - "--scheduler-name=test-scheduler-name", - "--use-legacy-policy-config=true", - } - - f.Parse(args) - - expected := &SchedulerServer{ - KubeSchedulerConfiguration: componentconfig.KubeSchedulerConfiguration{ - Port: 10000, - Address: "192.168.4.20", - AlgorithmProvider: "FooProvider", - PolicyConfigFile: "/foo/bar/policyconfig", - EnableContentionProfiling: true, - EnableProfiling: false, - - ContentType: "application/vnd.kubernetes.protobuf", - KubeAPIQPS: 40.0, - KubeAPIBurst: 80, - SchedulerName: "test-scheduler-name", - LeaderElection: componentconfig.LeaderElectionConfiguration{ - ResourceLock: "endpoints", - LeaderElect: true, - LeaseDuration: metav1.Duration{Duration: 20 * time.Second}, - RenewDeadline: metav1.Duration{Duration: 15 * time.Second}, - RetryPeriod: metav1.Duration{Duration: 3 * time.Second}, - }, - - LockObjectNamespace: "test-lock-object-ns", - LockObjectName: "test-lock-object-name", - - PolicyConfigMapName: "test-policy-configmap", - PolicyConfigMapNamespace: "test-policy-configmap-ns", - UseLegacyPolicyConfig: true, - - HardPodAffinitySymmetricWeight: 0, - FailureDomains: "kubernetes.io/hostname", - }, - Kubeconfig: "/foo/bar/kubeconfig", - Master: "192.168.4.20", - } - - if !reflect.DeepEqual(expected, s) { - t.Errorf("Got different run options than expected.\nDifference detected on:\n%s", diff.ObjectReflectDiff(expected, s)) - } -} diff --git a/plugin/cmd/kube-scheduler/app/server.go b/plugin/cmd/kube-scheduler/app/server.go deleted file mode 100644 index 8c4ad183445..00000000000 --- a/plugin/cmd/kube-scheduler/app/server.go +++ /dev/null @@ -1,190 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES 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 implements a Server object for running the scheduler. -package app - -import ( - "fmt" - "net" - "net/http" - "net/http/pprof" - "os" - goruntime "runtime" - "strconv" - - "k8s.io/apiserver/pkg/server/healthz" - - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/client-go/informers" - "k8s.io/client-go/tools/leaderelection" - "k8s.io/client-go/tools/leaderelection/resourcelock" - "k8s.io/kubernetes/pkg/controller" - "k8s.io/kubernetes/pkg/util/configz" - "k8s.io/kubernetes/pkg/version" - "k8s.io/kubernetes/plugin/cmd/kube-scheduler/app/options" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithmprovider" - "k8s.io/kubernetes/plugin/pkg/scheduler/factory" - - "github.com/golang/glog" - "github.com/prometheus/client_golang/prometheus" - "github.com/spf13/cobra" - "github.com/spf13/pflag" -) - -// NewSchedulerCommand creates a *cobra.Command object with default parameters -func NewSchedulerCommand() *cobra.Command { - s := options.NewSchedulerServer() - s.AddFlags(pflag.CommandLine) - cmd := &cobra.Command{ - Use: "kube-scheduler", - Long: `The Kubernetes scheduler is a policy-rich, topology-aware, -workload-specific function that significantly impacts availability, performance, -and capacity. The scheduler needs to take into account individual and collective -resource requirements, quality of service requirements, hardware/software/policy -constraints, affinity and anti-affinity specifications, data locality, inter-workload -interference, deadlines, and so on. Workload-specific requirements will be exposed -through the API as necessary.`, - Run: func(cmd *cobra.Command, args []string) { - }, - } - - return cmd -} - -// Run runs the specified SchedulerServer. This should never exit. -func Run(s *options.SchedulerServer) error { - // To help debugging, immediately log version - glog.Infof("Version: %+v", version.Get()) - - kubeClient, leaderElectionClient, err := createClients(s) - if err != nil { - return fmt.Errorf("unable to create kube client: %v", err) - } - - recorder := createRecorder(kubeClient, s) - - informerFactory := informers.NewSharedInformerFactory(kubeClient, 0) - // cache only non-terminal pods - podInformer := factory.NewPodInformer(kubeClient, 0) - - // Apply algorithms based on feature gates. - algorithmprovider.ApplyFeatureGates() - - sched, err := CreateScheduler( - s, - kubeClient, - informerFactory.Core().V1().Nodes(), - podInformer, - informerFactory.Core().V1().PersistentVolumes(), - informerFactory.Core().V1().PersistentVolumeClaims(), - informerFactory.Core().V1().ReplicationControllers(), - informerFactory.Extensions().V1beta1().ReplicaSets(), - informerFactory.Apps().V1beta1().StatefulSets(), - informerFactory.Core().V1().Services(), - recorder, - ) - if err != nil { - return fmt.Errorf("error creating scheduler: %v", err) - } - - if s.Port != -1 { - go startHTTP(s) - } - - stop := make(chan struct{}) - defer close(stop) - go podInformer.Informer().Run(stop) - informerFactory.Start(stop) - // Waiting for all cache to sync before scheduling. - informerFactory.WaitForCacheSync(stop) - controller.WaitForCacheSync("scheduler", stop, podInformer.Informer().HasSynced) - - run := func(stopCh <-chan struct{}) { - sched.Run() - <-stopCh - } - - if !s.LeaderElection.LeaderElect { - run(stop) - return fmt.Errorf("finished without leader elect") - } - - id, err := os.Hostname() - if err != nil { - return fmt.Errorf("unable to get hostname: %v", err) - } - - rl, err := resourcelock.New(s.LeaderElection.ResourceLock, - s.LockObjectNamespace, - s.LockObjectName, - leaderElectionClient.CoreV1(), - resourcelock.ResourceLockConfig{ - Identity: id, - EventRecorder: recorder, - }) - if err != nil { - return fmt.Errorf("error creating lock: %v", err) - } - - leaderElector, err := leaderelection.NewLeaderElector( - leaderelection.LeaderElectionConfig{ - Lock: rl, - LeaseDuration: s.LeaderElection.LeaseDuration.Duration, - RenewDeadline: s.LeaderElection.RenewDeadline.Duration, - RetryPeriod: s.LeaderElection.RetryPeriod.Duration, - Callbacks: leaderelection.LeaderCallbacks{ - OnStartedLeading: run, - OnStoppedLeading: func() { - utilruntime.HandleError(fmt.Errorf("lost master")) - }, - }, - }) - if err != nil { - return err - } - - leaderElector.Run() - - return fmt.Errorf("lost lease") -} - -func startHTTP(s *options.SchedulerServer) { - mux := http.NewServeMux() - healthz.InstallHandler(mux) - if s.EnableProfiling { - mux.HandleFunc("/debug/pprof/", pprof.Index) - mux.HandleFunc("/debug/pprof/profile", pprof.Profile) - mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) - mux.HandleFunc("/debug/pprof/trace", pprof.Trace) - if s.EnableContentionProfiling { - goruntime.SetBlockProfileRate(1) - } - } - if c, err := configz.New("componentconfig"); err == nil { - c.Set(s.KubeSchedulerConfiguration) - } else { - glog.Errorf("unable to register configz: %s", err) - } - configz.InstallHandler(mux) - mux.Handle("/metrics", prometheus.Handler()) - - server := &http.Server{ - Addr: net.JoinHostPort(s.Address, strconv.Itoa(int(s.Port))), - Handler: mux, - } - glog.Fatal(server.ListenAndServe()) -} diff --git a/plugin/cmd/kube-scheduler/scheduler.go b/plugin/cmd/kube-scheduler/scheduler.go deleted file mode 100644 index e2ea5b4b296..00000000000 --- a/plugin/cmd/kube-scheduler/scheduler.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES 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 ( - "k8s.io/apiserver/pkg/util/flag" - "k8s.io/apiserver/pkg/util/logs" - "k8s.io/kubernetes/pkg/version/verflag" - "k8s.io/kubernetes/plugin/cmd/kube-scheduler/app" - "k8s.io/kubernetes/plugin/cmd/kube-scheduler/app/options" - - "github.com/golang/glog" - "github.com/spf13/pflag" -) - -func main() { - s := options.NewSchedulerServer() - s.AddFlags(pflag.CommandLine) - - flag.InitFlags() - logs.InitLogs() - defer logs.FlushLogs() - - verflag.PrintAndExitIfRequested() - - if err := app.Run(s); err != nil { - glog.Fatalf("scheduler app failed to run: %v", err) - } -} diff --git a/plugin/pkg/admission/OWNERS b/plugin/pkg/admission/OWNERS index 5c88b018c95..5afda4f8a73 100644 --- a/plugin/pkg/admission/OWNERS +++ b/plugin/pkg/admission/OWNERS @@ -1,4 +1,6 @@ approvers: - derekwaynecarr +- deads2k reviewers: - derekwaynecarr +- deads2k diff --git a/plugin/pkg/admission/admit/BUILD b/plugin/pkg/admission/admit/BUILD index e406017ce29..957b91d3e4c 100644 --- a/plugin/pkg/admission/admit/BUILD +++ b/plugin/pkg/admission/admit/BUILD @@ -16,10 +16,10 @@ go_library( go_test( name = "go_default_test", srcs = ["admission_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/admit", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", ], ) diff --git a/plugin/pkg/admission/admit/admission.go b/plugin/pkg/admission/admit/admission.go index 6ba8e59dd70..3c428d9136f 100644 --- a/plugin/pkg/admission/admit/admission.go +++ b/plugin/pkg/admission/admit/admission.go @@ -29,19 +29,30 @@ func Register(plugins *admission.Plugins) { }) } -// alwaysAdmit is an implementation of admission.Interface which always says yes to an admit request. +// AlwaysAdmit is an implementation of admission.Interface which always says yes to an admit request. // It is useful in tests and when using kubernetes in an open manner. -type alwaysAdmit struct{} +type AlwaysAdmit struct{} -func (alwaysAdmit) Admit(a admission.Attributes) (err error) { +var _ admission.MutationInterface = AlwaysAdmit{} +var _ admission.ValidationInterface = AlwaysAdmit{} + +// Admit makes an admission decision based on the request attributes +func (AlwaysAdmit) Admit(a admission.Attributes) (err error) { return nil } -func (alwaysAdmit) Handles(operation admission.Operation) bool { +// Validate makes an admission decision based on the request attributes. It is NOT allowed to mutate. +func (AlwaysAdmit) Validate(a admission.Attributes) (err error) { + return nil +} + +// Handles returns true if this admission controller can handle the given operation +// where operation can be one of CREATE, UPDATE, DELETE, or CONNECT +func (AlwaysAdmit) Handles(operation admission.Operation) bool { return true } // NewAlwaysAdmit creates a new always admit admission handler -func NewAlwaysAdmit() admission.Interface { - return new(alwaysAdmit) +func NewAlwaysAdmit() *AlwaysAdmit { + return new(AlwaysAdmit) } diff --git a/plugin/pkg/admission/admit/admission_test.go b/plugin/pkg/admission/admit/admission_test.go index 9281aec9663..d7d3737e706 100644 --- a/plugin/pkg/admission/admit/admission_test.go +++ b/plugin/pkg/admission/admit/admission_test.go @@ -20,7 +20,7 @@ import ( "testing" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestAdmissionNonNilAttribute(t *testing.T) { diff --git a/plugin/pkg/admission/alwayspullimages/BUILD b/plugin/pkg/admission/alwayspullimages/BUILD index 5005a88ab87..2d1d398f810 100644 --- a/plugin/pkg/admission/alwayspullimages/BUILD +++ b/plugin/pkg/admission/alwayspullimages/BUILD @@ -11,8 +11,9 @@ go_library( srcs = ["admission.go"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", ], ) @@ -20,10 +21,10 @@ go_library( go_test( name = "go_default_test", srcs = ["admission_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", diff --git a/plugin/pkg/admission/alwayspullimages/admission.go b/plugin/pkg/admission/alwayspullimages/admission.go index 0d71f127abf..c9a90f648a7 100644 --- a/plugin/pkg/admission/alwayspullimages/admission.go +++ b/plugin/pkg/admission/alwayspullimages/admission.go @@ -28,8 +28,9 @@ import ( "io" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // Register registers a plugin @@ -39,15 +40,19 @@ func Register(plugins *admission.Plugins) { }) } -// alwaysPullImages is an implementation of admission.Interface. +// AlwaysPullImages is an implementation of admission.Interface. // It looks at all new pods and overrides each container's image pull policy to Always. -type alwaysPullImages struct { +type AlwaysPullImages struct { *admission.Handler } -func (a *alwaysPullImages) Admit(attributes admission.Attributes) (err error) { +var _ admission.MutationInterface = &AlwaysPullImages{} +var _ admission.ValidationInterface = &AlwaysPullImages{} + +// Admit makes an admission decision based on the request attributes +func (a *AlwaysPullImages) Admit(attributes admission.Attributes) (err error) { // Ignore all calls to subresources or resources other than pods. - if len(attributes.GetSubresource()) != 0 || attributes.GetResource().GroupResource() != api.Resource("pods") { + if shouldIgnore(attributes) { return nil } pod, ok := attributes.GetObject().(*api.Pod) @@ -66,9 +71,51 @@ func (a *alwaysPullImages) Admit(attributes admission.Attributes) (err error) { return nil } +// Validate makes sure that all containers are set to always pull images +func (*AlwaysPullImages) Validate(attributes admission.Attributes) (err error) { + if shouldIgnore(attributes) { + return nil + } + + pod, ok := attributes.GetObject().(*api.Pod) + if !ok { + return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted") + } + + for i := range pod.Spec.InitContainers { + if pod.Spec.InitContainers[i].ImagePullPolicy != api.PullAlways { + return admission.NewForbidden(attributes, + field.NotSupported(field.NewPath("spec", "initContainers").Index(i).Child("imagePullPolicy"), + pod.Spec.InitContainers[i].ImagePullPolicy, []string{string(api.PullAlways)}, + ), + ) + } + } + for i := range pod.Spec.Containers { + if pod.Spec.Containers[i].ImagePullPolicy != api.PullAlways { + return admission.NewForbidden(attributes, + field.NotSupported(field.NewPath("spec", "containers").Index(i).Child("imagePullPolicy"), + pod.Spec.Containers[i].ImagePullPolicy, []string{string(api.PullAlways)}, + ), + ) + } + } + + return nil +} + +func shouldIgnore(attributes admission.Attributes) bool { + // Ignore all calls to subresources or resources other than pods. + if len(attributes.GetSubresource()) != 0 || attributes.GetResource().GroupResource() != api.Resource("pods") { + return true + } + + return false +} + // NewAlwaysPullImages creates a new always pull images admission control handler -func NewAlwaysPullImages() admission.Interface { - return &alwaysPullImages{ +func NewAlwaysPullImages() *AlwaysPullImages { + return &AlwaysPullImages{ Handler: admission.NewHandler(admission.Create, admission.Update), } } diff --git a/plugin/pkg/admission/alwayspullimages/admission_test.go b/plugin/pkg/admission/alwayspullimages/admission_test.go index e291e6e669c..de6fd5df9b3 100644 --- a/plugin/pkg/admission/alwayspullimages/admission_test.go +++ b/plugin/pkg/admission/alwayspullimages/admission_test.go @@ -22,14 +22,14 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // TestAdmission verifies all create requests for pods result in every container's image pull policy // set to Always func TestAdmission(t *testing.T) { namespace := "test" - handler := &alwaysPullImages{} + handler := &AlwaysPullImages{} pod := api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: namespace}, Spec: api.PodSpec{ @@ -63,6 +63,36 @@ func TestAdmission(t *testing.T) { } } +func TestValidate(t *testing.T) { + namespace := "test" + handler := &AlwaysPullImages{} + pod := api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: namespace}, + Spec: api.PodSpec{ + InitContainers: []api.Container{ + {Name: "init1", Image: "image"}, + {Name: "init2", Image: "image", ImagePullPolicy: api.PullNever}, + {Name: "init3", Image: "image", ImagePullPolicy: api.PullIfNotPresent}, + {Name: "init4", Image: "image", ImagePullPolicy: api.PullAlways}, + }, + Containers: []api.Container{ + {Name: "ctr1", Image: "image"}, + {Name: "ctr2", Image: "image", ImagePullPolicy: api.PullNever}, + {Name: "ctr3", Image: "image", ImagePullPolicy: api.PullIfNotPresent}, + {Name: "ctr4", Image: "image", ImagePullPolicy: api.PullAlways}, + }, + }, + } + expectedError := `pods "123" is forbidden: spec.initContainers[0].imagePullPolicy: Unsupported value: "": supported values: "Always"` + err := handler.Validate(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) + if err == nil { + t.Fatal("missing expected error") + } + if err.Error() != expectedError { + t.Fatal(err) + } +} + // TestOtherResources ensures that this admission controller is a no-op for other resources, // subresources, and non-pods. func TestOtherResources(t *testing.T) { @@ -107,7 +137,7 @@ func TestOtherResources(t *testing.T) { } for _, tc := range tests { - handler := &alwaysPullImages{} + handler := &AlwaysPullImages{} err := handler.Admit(admission.NewAttributesRecord(tc.object, nil, api.Kind(tc.kind).WithVersion("version"), namespace, name, api.Resource(tc.resource).WithVersion("version"), tc.subresource, admission.Create, nil)) diff --git a/plugin/pkg/admission/antiaffinity/BUILD b/plugin/pkg/admission/antiaffinity/BUILD index 930e815a4be..5b42d1afb67 100644 --- a/plugin/pkg/admission/antiaffinity/BUILD +++ b/plugin/pkg/admission/antiaffinity/BUILD @@ -14,7 +14,7 @@ go_library( ], importpath = "k8s.io/kubernetes/plugin/pkg/admission/antiaffinity", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/kubelet/apis:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", @@ -24,10 +24,10 @@ go_library( go_test( name = "go_default_test", srcs = ["admission_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/antiaffinity", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/kubelet/apis:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/plugin/pkg/admission/antiaffinity/admission.go b/plugin/pkg/admission/antiaffinity/admission.go index c42a1b211c1..bec95b8d8d7 100644 --- a/plugin/pkg/admission/antiaffinity/admission.go +++ b/plugin/pkg/admission/antiaffinity/admission.go @@ -22,7 +22,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis" ) @@ -33,21 +33,23 @@ func Register(plugins *admission.Plugins) { }) } -// plugin contains the client used by the admission controller -type plugin struct { +// Plugin contains the client used by the admission controller +type Plugin struct { *admission.Handler } +var _ admission.ValidationInterface = &Plugin{} + // NewInterPodAntiAffinity creates a new instance of the LimitPodHardAntiAffinityTopology admission controller -func NewInterPodAntiAffinity() admission.Interface { - return &plugin{ +func NewInterPodAntiAffinity() *Plugin { + return &Plugin{ Handler: admission.NewHandler(admission.Create, admission.Update), } } -// Admit will deny any pod that defines AntiAffinity topology key other than kubeletapis.LabelHostname i.e. "kubernetes.io/hostname" +// Validate will deny any pod that defines AntiAffinity topology key other than kubeletapis.LabelHostname i.e. "kubernetes.io/hostname" // in requiredDuringSchedulingRequiredDuringExecution and requiredDuringSchedulingIgnoredDuringExecution. -func (p *plugin) Admit(attributes admission.Attributes) (err error) { +func (p *Plugin) Validate(attributes admission.Attributes) (err error) { // Ignore all calls to subresources or resources other than pods. if len(attributes.GetSubresource()) != 0 || attributes.GetResource().GroupResource() != api.Resource("pods") { return nil diff --git a/plugin/pkg/admission/antiaffinity/admission_test.go b/plugin/pkg/admission/antiaffinity/admission_test.go index f38be9a1f68..dc79c91128a 100644 --- a/plugin/pkg/admission/antiaffinity/admission_test.go +++ b/plugin/pkg/admission/antiaffinity/admission_test.go @@ -22,7 +22,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis" ) @@ -199,7 +199,7 @@ func TestInterPodAffinityAdmission(t *testing.T) { } for _, test := range tests { pod.Spec.Affinity = test.affinity - err := handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), "foo", "name", api.Resource("pods").WithVersion("version"), "", "ignored", nil)) + err := handler.Validate(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), "foo", "name", api.Resource("pods").WithVersion("version"), "", "ignored", nil)) if test.errorExpected && err == nil { t.Errorf("Expected error for Anti Affinity %+v but did not get an error", test.affinity) @@ -265,9 +265,9 @@ func TestOtherResources(t *testing.T) { } for _, tc := range tests { - handler := &plugin{} + handler := &Plugin{} - err := handler.Admit(admission.NewAttributesRecord(tc.object, nil, api.Kind(tc.kind).WithVersion("version"), namespace, name, api.Resource(tc.resource).WithVersion("version"), tc.subresource, admission.Create, nil)) + err := handler.Validate(admission.NewAttributesRecord(tc.object, nil, api.Kind(tc.kind).WithVersion("version"), namespace, name, api.Resource(tc.resource).WithVersion("version"), tc.subresource, admission.Create, nil)) if tc.expectError { if err == nil { diff --git a/plugin/pkg/admission/defaulttolerationseconds/BUILD b/plugin/pkg/admission/defaulttolerationseconds/BUILD index 571320d80db..48ca06b3b83 100644 --- a/plugin/pkg/admission/defaulttolerationseconds/BUILD +++ b/plugin/pkg/admission/defaulttolerationseconds/BUILD @@ -9,12 +9,12 @@ load( go_test( name = "go_default_test", srcs = ["admission_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", - "//plugin/pkg/scheduler/algorithm:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", + "//pkg/scheduler/algorithm:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", ], ) @@ -24,9 +24,9 @@ go_library( srcs = ["admission.go"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", - "//plugin/pkg/scheduler/algorithm:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", + "//pkg/scheduler/algorithm:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", ], diff --git a/plugin/pkg/admission/defaulttolerationseconds/admission.go b/plugin/pkg/admission/defaulttolerationseconds/admission.go index 430ed22f5be..408ead36eb6 100644 --- a/plugin/pkg/admission/defaulttolerationseconds/admission.go +++ b/plugin/pkg/admission/defaulttolerationseconds/admission.go @@ -23,9 +23,9 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" + "k8s.io/kubernetes/pkg/scheduler/algorithm" ) var ( @@ -45,24 +45,27 @@ func Register(plugins *admission.Plugins) { }) } -// plugin contains the client used by the admission controller +// Plugin contains the client used by the admission controller // It will add default tolerations for every pod // that tolerate taints `notReady:NoExecute` and `unreachable:NoExecute`, // with tolerationSeconds of 300s. // If the pod already specifies a toleration for taint `notReady:NoExecute` // or `unreachable:NoExecute`, the plugin won't touch it. -type plugin struct { +type Plugin struct { *admission.Handler } +var _ admission.MutationInterface = &Plugin{} + // NewDefaultTolerationSeconds creates a new instance of the DefaultTolerationSeconds admission controller -func NewDefaultTolerationSeconds() admission.Interface { - return &plugin{ +func NewDefaultTolerationSeconds() *Plugin { + return &Plugin{ Handler: admission.NewHandler(admission.Create, admission.Update), } } -func (p *plugin) Admit(attributes admission.Attributes) (err error) { +// Admit makes an admission decision based on the request attributes +func (p *Plugin) Admit(attributes admission.Attributes) (err error) { if attributes.GetResource().GroupResource() != api.Resource("pods") { return nil } diff --git a/plugin/pkg/admission/defaulttolerationseconds/admission_test.go b/plugin/pkg/admission/defaulttolerationseconds/admission_test.go index f480d1f0c14..b94e41fe5b1 100644 --- a/plugin/pkg/admission/defaulttolerationseconds/admission_test.go +++ b/plugin/pkg/admission/defaulttolerationseconds/admission_test.go @@ -20,9 +20,9 @@ import ( "testing" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" + "k8s.io/kubernetes/pkg/scheduler/algorithm" ) func TestForgivenessAdmission(t *testing.T) { @@ -33,13 +33,14 @@ func TestForgivenessAdmission(t *testing.T) { } handler := NewDefaultTolerationSeconds() + // NOTE: for anyone who want to modify this test, the order of tolerations matters! tests := []struct { description string requestedPod api.Pod expectedPod api.Pod }{ { - description: "pod has no tolerations, expect add tolerations for `notReady:NoExecute` and `unreachable:NoExecute`", + description: "pod has no tolerations, expect add tolerations for `not-ready:NoExecute` and `unreachable:NoExecute`", requestedPod: api.Pod{ Spec: api.PodSpec{}, }, @@ -63,7 +64,139 @@ func TestForgivenessAdmission(t *testing.T) { }, }, { - description: "pod has tolerations, but none is for taint `notReady:NoExecute` or `unreachable:NoExecute`, expect add tolerations for `notReady:NoExecute` and `unreachable:NoExecute`", + description: "pod has alpha tolerations, expect add tolerations for `not-ready:NoExecute` and `unreachable:NoExecute`" + + ", the alpha tolerations will not be touched", + requestedPod: api.Pod{ + Spec: api.PodSpec{ + Tolerations: []api.Toleration{ + { + Key: algorithm.DeprecatedTaintNodeNotReady, + Operator: api.TolerationOpExists, + Effect: api.TaintEffectNoExecute, + TolerationSeconds: &defaultTolerationSeconds, + }, + { + Key: algorithm.DeprecatedTaintNodeUnreachable, + Operator: api.TolerationOpExists, + Effect: api.TaintEffectNoExecute, + TolerationSeconds: &defaultTolerationSeconds, + }, + }, + }, + }, + expectedPod: api.Pod{ + Spec: api.PodSpec{ + Tolerations: []api.Toleration{ + { + Key: algorithm.DeprecatedTaintNodeNotReady, + Operator: api.TolerationOpExists, + Effect: api.TaintEffectNoExecute, + TolerationSeconds: &defaultTolerationSeconds, + }, + { + Key: algorithm.DeprecatedTaintNodeUnreachable, + Operator: api.TolerationOpExists, + Effect: api.TaintEffectNoExecute, + TolerationSeconds: &defaultTolerationSeconds, + }, + { + Key: algorithm.TaintNodeNotReady, + Operator: api.TolerationOpExists, + Effect: api.TaintEffectNoExecute, + TolerationSeconds: &defaultTolerationSeconds, + }, + { + Key: algorithm.TaintNodeUnreachable, + Operator: api.TolerationOpExists, + Effect: api.TaintEffectNoExecute, + TolerationSeconds: &defaultTolerationSeconds, + }, + }, + }, + }, + }, + { + description: "pod has alpha not-ready toleration, expect add tolerations for `not-ready:NoExecute` and `unreachable:NoExecute`" + + ", the alpha tolerations will not be touched", + requestedPod: api.Pod{ + Spec: api.PodSpec{ + Tolerations: []api.Toleration{ + { + Key: algorithm.DeprecatedTaintNodeNotReady, + Operator: api.TolerationOpExists, + Effect: api.TaintEffectNoExecute, + TolerationSeconds: &defaultTolerationSeconds, + }, + }, + }, + }, + expectedPod: api.Pod{ + Spec: api.PodSpec{ + Tolerations: []api.Toleration{ + { + Key: algorithm.DeprecatedTaintNodeNotReady, + Operator: api.TolerationOpExists, + Effect: api.TaintEffectNoExecute, + TolerationSeconds: &defaultTolerationSeconds, + }, + { + Key: algorithm.TaintNodeNotReady, + Operator: api.TolerationOpExists, + Effect: api.TaintEffectNoExecute, + TolerationSeconds: &defaultTolerationSeconds, + }, + { + Key: algorithm.TaintNodeUnreachable, + Operator: api.TolerationOpExists, + Effect: api.TaintEffectNoExecute, + TolerationSeconds: &defaultTolerationSeconds, + }, + }, + }, + }, + }, + { + description: "pod has alpha unreachable toleration, expect add tolerations for `not-ready:NoExecute` and `unreachable:NoExecute`" + + ", the alpha tolerations will not be touched", + requestedPod: api.Pod{ + Spec: api.PodSpec{ + Tolerations: []api.Toleration{ + { + Key: algorithm.DeprecatedTaintNodeUnreachable, + Operator: api.TolerationOpExists, + Effect: api.TaintEffectNoExecute, + TolerationSeconds: &defaultTolerationSeconds, + }, + }, + }, + }, + expectedPod: api.Pod{ + Spec: api.PodSpec{ + Tolerations: []api.Toleration{ + { + Key: algorithm.DeprecatedTaintNodeUnreachable, + Operator: api.TolerationOpExists, + Effect: api.TaintEffectNoExecute, + TolerationSeconds: &defaultTolerationSeconds, + }, + { + Key: algorithm.TaintNodeNotReady, + Operator: api.TolerationOpExists, + Effect: api.TaintEffectNoExecute, + TolerationSeconds: &defaultTolerationSeconds, + }, + { + Key: algorithm.TaintNodeUnreachable, + Operator: api.TolerationOpExists, + Effect: api.TaintEffectNoExecute, + TolerationSeconds: &defaultTolerationSeconds, + }, + }, + }, + }, + }, + { + description: "pod has tolerations, but none is for taint `not-ready:NoExecute` or `unreachable:NoExecute`, expect add tolerations for `not-ready:NoExecute` and `unreachable:NoExecute`", requestedPod: api.Pod{ Spec: api.PodSpec{ Tolerations: []api.Toleration{ @@ -104,7 +237,7 @@ func TestForgivenessAdmission(t *testing.T) { }, }, { - description: "pod specified a toleration for taint `notReady:NoExecute`, expect add toleration for `unreachable:NoExecute`", + description: "pod specified a toleration for taint `not-ready:NoExecute`, expect add toleration for `unreachable:NoExecute`", requestedPod: api.Pod{ Spec: api.PodSpec{ Tolerations: []api.Toleration{ @@ -137,7 +270,7 @@ func TestForgivenessAdmission(t *testing.T) { }, }, { - description: "pod specified a toleration for taint `unreachable:NoExecute`, expect add toleration for `notReady:NoExecute`", + description: "pod specified a toleration for taint `unreachable:NoExecute`, expect add toleration for `not-ready:NoExecute`", requestedPod: api.Pod{ Spec: api.PodSpec{ Tolerations: []api.Toleration{ @@ -170,7 +303,7 @@ func TestForgivenessAdmission(t *testing.T) { }, }, { - description: "pod specified tolerations for both `notReady:NoExecute` and `unreachable:NoExecute`, expect no change", + description: "pod specified tolerations for both `not-ready:NoExecute` and `unreachable:NoExecute`, expect no change", requestedPod: api.Pod{ Spec: api.PodSpec{ Tolerations: []api.Toleration{ @@ -209,7 +342,7 @@ func TestForgivenessAdmission(t *testing.T) { }, }, { - description: "pod specified toleration for taint `unreachable`, expect add toleration for `notReady:NoExecute`", + description: "pod specified toleration for taint `unreachable`, expect add toleration for `not-ready:NoExecute`", requestedPod: api.Pod{ Spec: api.PodSpec{ Tolerations: []api.Toleration{ @@ -267,8 +400,8 @@ func TestForgivenessAdmission(t *testing.T) { t.Errorf("[%s]: unexpected error %v for pod %+v", test.description, err, test.requestedPod) } - if !helper.Semantic.DeepEqual(test.expectedPod.Annotations, test.requestedPod.Annotations) { - t.Errorf("[%s]: expected %#v got %#v", test.description, test.expectedPod.Annotations, test.requestedPod.Annotations) + if !helper.Semantic.DeepEqual(test.expectedPod.Spec.Tolerations, test.requestedPod.Spec.Tolerations) { + t.Errorf("[%s]: expected %#v got %#v", test.description, test.expectedPod.Spec.Tolerations, test.requestedPod.Spec.Tolerations) } } } diff --git a/plugin/pkg/admission/deny/BUILD b/plugin/pkg/admission/deny/BUILD index 7809eebe1bf..9bb617c49a4 100644 --- a/plugin/pkg/admission/deny/BUILD +++ b/plugin/pkg/admission/deny/BUILD @@ -16,10 +16,10 @@ go_library( go_test( name = "go_default_test", srcs = ["admission_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/deny", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", ], ) diff --git a/plugin/pkg/admission/deny/admission.go b/plugin/pkg/admission/deny/admission.go index bef3429c251..f6f4951bed7 100644 --- a/plugin/pkg/admission/deny/admission.go +++ b/plugin/pkg/admission/deny/admission.go @@ -30,19 +30,30 @@ func Register(plugins *admission.Plugins) { }) } -// alwaysDeny is an implementation of admission.Interface which always says no to an admission request. +// AlwaysDeny is an implementation of admission.Interface which always says no to an admission request. // It is useful in unit tests to force an operation to be forbidden. -type alwaysDeny struct{} +type AlwaysDeny struct{} -func (alwaysDeny) Admit(a admission.Attributes) (err error) { +var _ admission.MutationInterface = AlwaysDeny{} +var _ admission.ValidationInterface = AlwaysDeny{} + +// Admit makes an admission decision based on the request attributes. +func (AlwaysDeny) Admit(a admission.Attributes) (err error) { return admission.NewForbidden(a, errors.New("Admission control is denying all modifications")) } -func (alwaysDeny) Handles(operation admission.Operation) bool { +// Validate makes an admission decision based on the request attributes. It is NOT allowed to mutate. +func (AlwaysDeny) Validate(a admission.Attributes) (err error) { + return admission.NewForbidden(a, errors.New("Admission control is denying all modifications")) +} + +// Handles returns true if this admission controller can handle the given operation +// where operation can be one of CREATE, UPDATE, DELETE, or CONNECT +func (AlwaysDeny) Handles(operation admission.Operation) bool { return true } // NewAlwaysDeny creates an always deny admission handler -func NewAlwaysDeny() admission.Interface { - return new(alwaysDeny) +func NewAlwaysDeny() *AlwaysDeny { + return new(AlwaysDeny) } diff --git a/plugin/pkg/admission/deny/admission_test.go b/plugin/pkg/admission/deny/admission_test.go index a2a6f32d9b3..fcc15ce0b77 100644 --- a/plugin/pkg/admission/deny/admission_test.go +++ b/plugin/pkg/admission/deny/admission_test.go @@ -20,7 +20,7 @@ import ( "testing" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestAdmission(t *testing.T) { diff --git a/plugin/pkg/admission/eventratelimit/BUILD b/plugin/pkg/admission/eventratelimit/BUILD index 67df0c8cbd7..7a392d0c143 100644 --- a/plugin/pkg/admission/eventratelimit/BUILD +++ b/plugin/pkg/admission/eventratelimit/BUILD @@ -12,10 +12,10 @@ go_test( "admission_test.go", "cache_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//plugin/pkg/admission/eventratelimit/apis/eventratelimit:go_default_library", "//vendor/github.com/hashicorp/golang-lru:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", @@ -39,7 +39,7 @@ go_library( ], importpath = "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//plugin/pkg/admission/eventratelimit/apis/eventratelimit:go_default_library", "//plugin/pkg/admission/eventratelimit/apis/eventratelimit/install:go_default_library", "//plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1:go_default_library", diff --git a/plugin/pkg/admission/eventratelimit/admission.go b/plugin/pkg/admission/eventratelimit/admission.go index 4318d66c688..8cd64ebe587 100644 --- a/plugin/pkg/admission/eventratelimit/admission.go +++ b/plugin/pkg/admission/eventratelimit/admission.go @@ -21,8 +21,9 @@ import ( "k8s.io/apiserver/pkg/admission" "k8s.io/client-go/util/flowcontrol" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" eventratelimitapi "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit" + eventratelimitapiv1alpha1 "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1" "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation" ) @@ -43,10 +44,14 @@ func Register(plugins *admission.Plugins) { } return newEventRateLimit(configuration, realClock{}) }) + + // add our config types + eventratelimitapi.AddToScheme(plugins.ConfigScheme) + eventratelimitapiv1alpha1.AddToScheme(plugins.ConfigScheme) } -// eventRateLimitAdmission implements an admission controller that can enforce event rate limits -type eventRateLimitAdmission struct { +// Plugin implements an admission controller that can enforce event rate limits +type Plugin struct { *admission.Handler // limitEnforcers is the collection of limit enforcers. There is one limit enforcer for each // active limit type. As there are 4 limit types, the length of the array will be at most 4. @@ -54,8 +59,10 @@ type eventRateLimitAdmission struct { limitEnforcers []*limitEnforcer } +var _ admission.ValidationInterface = &Plugin{} + // newEventRateLimit configures an admission controller that can enforce event rate limits -func newEventRateLimit(config *eventratelimitapi.Configuration, clock flowcontrol.Clock) (admission.Interface, error) { +func newEventRateLimit(config *eventratelimitapi.Configuration, clock flowcontrol.Clock) (*Plugin, error) { limitEnforcers := make([]*limitEnforcer, 0, len(config.Limits)) for _, limitConfig := range config.Limits { enforcer, err := newLimitEnforcer(limitConfig, clock) @@ -65,7 +72,7 @@ func newEventRateLimit(config *eventratelimitapi.Configuration, clock flowcontro limitEnforcers = append(limitEnforcers, enforcer) } - eventRateLimitAdmission := &eventRateLimitAdmission{ + eventRateLimitAdmission := &Plugin{ Handler: admission.NewHandler(admission.Create, admission.Update), limitEnforcers: limitEnforcers, } @@ -73,8 +80,8 @@ func newEventRateLimit(config *eventratelimitapi.Configuration, clock flowcontro return eventRateLimitAdmission, nil } -// Admit makes admission decisions while enforcing event rate limits -func (a *eventRateLimitAdmission) Admit(attr admission.Attributes) (err error) { +// Validate makes admission decisions while enforcing event rate limits +func (a *Plugin) Validate(attr admission.Attributes) (err error) { // ignore all operations that do not correspond to an Event kind if attr.GetKind().GroupKind() != api.Kind("Event") { return nil diff --git a/plugin/pkg/admission/eventratelimit/admission_test.go b/plugin/pkg/admission/eventratelimit/admission_test.go index 579ab36fb1c..e0e96176c6f 100644 --- a/plugin/pkg/admission/eventratelimit/admission_test.go +++ b/plugin/pkg/admission/eventratelimit/admission_test.go @@ -25,7 +25,7 @@ import ( "k8s.io/apimachinery/pkg/util/clock" "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/authentication/user" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" eventratelimitapi "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit" ) @@ -482,7 +482,7 @@ func TestEventRateLimiting(t *testing.T) { clock.Step(rq.delay) } attributes := attributesForRequest(rq) - err = eventratelimit.Admit(attributes) + err = eventratelimit.Validate(attributes) if rq.accepted != (err == nil) { expectedAction := "admitted" if !rq.accepted { diff --git a/plugin/pkg/admission/eventratelimit/apis/eventratelimit/BUILD b/plugin/pkg/admission/eventratelimit/apis/eventratelimit/BUILD index b519a400be5..7f723eadead 100644 --- a/plugin/pkg/admission/eventratelimit/apis/eventratelimit/BUILD +++ b/plugin/pkg/admission/eventratelimit/apis/eventratelimit/BUILD @@ -16,7 +16,6 @@ go_library( importpath = "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit", deps = [ "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/plugin/pkg/admission/eventratelimit/apis/eventratelimit/doc.go b/plugin/pkg/admission/eventratelimit/apis/eventratelimit/doc.go index c70a60a56fd..56d99bee293 100644 --- a/plugin/pkg/admission/eventratelimit/apis/eventratelimit/doc.go +++ b/plugin/pkg/admission/eventratelimit/apis/eventratelimit/doc.go @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package package eventratelimit // import "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit" diff --git a/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/doc.go b/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/doc.go index 483c858deec..cf325fa9f2b 100644 --- a/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/doc.go +++ b/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:conversion-gen=k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit // +k8s:defaulter-gen=TypeMeta diff --git a/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/zz_generated.conversion.go b/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/zz_generated.conversion.go index b8da46acb6d..d425411e1cc 100644 --- a/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/zz_generated.conversion.go +++ b/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,10 +21,11 @@ limitations under the License. package v1alpha1 import ( + unsafe "unsafe" + conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" eventratelimit "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit" - unsafe "unsafe" ) func init() { diff --git a/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/zz_generated.deepcopy.go b/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/zz_generated.deepcopy.go index 49fc8779134..38307fe6132 100644 --- a/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/zz_generated.deepcopy.go +++ b/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,32 +21,9 @@ limitations under the License. package v1alpha1 import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Configuration).DeepCopyInto(out.(*Configuration)) - return nil - }, InType: reflect.TypeOf(&Configuration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Limit).DeepCopyInto(out.(*Limit)) - return nil - }, InType: reflect.TypeOf(&Limit{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Configuration) DeepCopyInto(out *Configuration) { *out = *in diff --git a/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/zz_generated.defaults.go b/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/zz_generated.defaults.go index 53f9cb92ef7..35985182f5c 100644 --- a/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/zz_generated.defaults.go +++ b/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation/BUILD b/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation/BUILD index 1a272b78321..964679a062d 100644 --- a/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation/BUILD +++ b/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation/BUILD @@ -32,7 +32,7 @@ filegroup( go_test( name = "go_default_test", srcs = ["validation_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation", - library = ":go_default_library", deps = ["//plugin/pkg/admission/eventratelimit/apis/eventratelimit:go_default_library"], ) diff --git a/plugin/pkg/admission/eventratelimit/apis/eventratelimit/zz_generated.deepcopy.go b/plugin/pkg/admission/eventratelimit/apis/eventratelimit/zz_generated.deepcopy.go index a1bd7475410..91ebbcca1b8 100644 --- a/plugin/pkg/admission/eventratelimit/apis/eventratelimit/zz_generated.deepcopy.go +++ b/plugin/pkg/admission/eventratelimit/apis/eventratelimit/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,32 +21,9 @@ limitations under the License. package eventratelimit import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Configuration).DeepCopyInto(out.(*Configuration)) - return nil - }, InType: reflect.TypeOf(&Configuration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Limit).DeepCopyInto(out.(*Limit)) - return nil - }, InType: reflect.TypeOf(&Limit{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Configuration) DeepCopyInto(out *Configuration) { *out = *in diff --git a/plugin/pkg/admission/eventratelimit/limitenforcer.go b/plugin/pkg/admission/eventratelimit/limitenforcer.go index 4fa5ee90f73..3faff618f01 100644 --- a/plugin/pkg/admission/eventratelimit/limitenforcer.go +++ b/plugin/pkg/admission/eventratelimit/limitenforcer.go @@ -25,7 +25,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apiserver/pkg/admission" "k8s.io/client-go/util/flowcontrol" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" eventratelimitapi "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit" ) diff --git a/plugin/pkg/admission/exec/BUILD b/plugin/pkg/admission/exec/BUILD index 55700fcbd51..b47eea96e42 100644 --- a/plugin/pkg/admission/exec/BUILD +++ b/plugin/pkg/admission/exec/BUILD @@ -11,7 +11,7 @@ go_library( srcs = ["admission.go"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/exec", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/kubeapiserver/admission:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", @@ -24,10 +24,10 @@ go_library( go_test( name = "go_default_test", srcs = ["admission_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/exec", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/plugin/pkg/admission/exec/admission.go b/plugin/pkg/admission/exec/admission.go index f8f94b78f81..0188b2ac760 100644 --- a/plugin/pkg/admission/exec/admission.go +++ b/plugin/pkg/admission/exec/admission.go @@ -24,7 +24,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission" ) @@ -42,44 +42,50 @@ func Register(plugins *admission.Plugins) { }) } -// denyExec is an implementation of admission.Interface which says no to a pod/exec on +// DenyExec is an implementation of admission.Interface which says no to a pod/exec on // a pod using host based configurations. -type denyExec struct { +type DenyExec struct { *admission.Handler client internalclientset.Interface // these flags control which items will be checked to deny exec/attach - hostIPC bool - hostPID bool - privileged bool + hostNetwork bool + hostIPC bool + hostPID bool + privileged bool } -var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&denyExec{}) +var _ admission.ValidationInterface = &DenyExec{} + +var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&DenyExec{}) // NewDenyEscalatingExec creates a new admission controller that denies an exec operation on a pod // using host based configurations. -func NewDenyEscalatingExec() admission.Interface { - return &denyExec{ - Handler: admission.NewHandler(admission.Connect), - hostIPC: true, - hostPID: true, - privileged: true, +func NewDenyEscalatingExec() *DenyExec { + return &DenyExec{ + Handler: admission.NewHandler(admission.Connect), + hostNetwork: true, + hostIPC: true, + hostPID: true, + privileged: true, } } // NewDenyExecOnPrivileged creates a new admission controller that is only checking the privileged -// option. This is for legacy support of the DenyExecOnPrivileged admission controller. Most -// of the time NewDenyEscalatingExec should be preferred. -func NewDenyExecOnPrivileged() admission.Interface { - return &denyExec{ - Handler: admission.NewHandler(admission.Connect), - hostIPC: false, - hostPID: false, - privileged: true, +// option. This is for legacy support of the DenyExecOnPrivileged admission controller. +// Most of the time NewDenyEscalatingExec should be preferred. +func NewDenyExecOnPrivileged() *DenyExec { + return &DenyExec{ + Handler: admission.NewHandler(admission.Connect), + hostNetwork: false, + hostIPC: false, + hostPID: false, + privileged: true, } } -func (d *denyExec) Admit(a admission.Attributes) (err error) { +// Validate makes an admission decision based on the request attributes +func (d *DenyExec) Validate(a admission.Attributes) (err error) { connectRequest, ok := a.GetObject().(*rest.ConnectRequest) if !ok { return errors.NewBadRequest("a connect request was received, but could not convert the request object.") @@ -93,12 +99,19 @@ func (d *denyExec) Admit(a admission.Attributes) (err error) { return admission.NewForbidden(a, err) } - if d.hostPID && pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.HostPID { - return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a container using host pid")) - } + if pod.Spec.SecurityContext != nil { + securityContext := pod.Spec.SecurityContext + if d.hostNetwork && securityContext.HostNetwork { + return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a container using host network")) + } - if d.hostIPC && pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.HostIPC { - return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a container using host ipc")) + if d.hostPID && securityContext.HostPID { + return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a container using host pid")) + } + + if d.hostIPC && securityContext.HostIPC { + return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a container using host ipc")) + } } if d.privileged && isPrivileged(pod) { @@ -129,11 +142,13 @@ func isPrivileged(pod *api.Pod) bool { return false } -func (d *denyExec) SetInternalKubeClientSet(client internalclientset.Interface) { +// SetInternalKubeClientSet implements the WantsInternalKubeClientSet interface. +func (d *DenyExec) SetInternalKubeClientSet(client internalclientset.Interface) { d.client = client } -func (d *denyExec) Validate() error { +// ValidateInitialization implements the InitializationValidator interface. +func (d *DenyExec) ValidateInitialization() error { if d.client == nil { return fmt.Errorf("missing client") } diff --git a/plugin/pkg/admission/exec/admission_test.go b/plugin/pkg/admission/exec/admission_test.go index 0508331e5e2..649089ced9f 100644 --- a/plugin/pkg/admission/exec/admission_test.go +++ b/plugin/pkg/admission/exec/admission_test.go @@ -24,14 +24,14 @@ import ( "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/registry/rest" core "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" ) // newAllowEscalatingExec returns `admission.Interface` that allows execution on // "hostIPC", "hostPID" and "privileged". -func newAllowEscalatingExec() admission.Interface { - return &denyExec{ +func newAllowEscalatingExec() *DenyExec { + return &DenyExec{ Handler: admission.NewHandler(admission.Connect), hostIPC: false, hostPID: false, @@ -77,21 +77,21 @@ func TestAdmission(t *testing.T) { } // Get the direct object though to allow testAdmission to inject the client - handler := NewDenyEscalatingExec().(*denyExec) + handler := NewDenyEscalatingExec() for _, tc := range testCases { testAdmission(t, tc.pod, handler, tc.shouldAccept) } // run with a permissive config and all cases should pass - handler = newAllowEscalatingExec().(*denyExec) + handler = newAllowEscalatingExec() for _, tc := range testCases { testAdmission(t, tc.pod, handler, true) } // run against an init container - handler = NewDenyEscalatingExec().(*denyExec) + handler = NewDenyEscalatingExec() for _, tc := range testCases { tc.pod.Spec.InitContainers = tc.pod.Spec.Containers @@ -100,14 +100,14 @@ func TestAdmission(t *testing.T) { } // run with a permissive config and all cases should pass - handler = newAllowEscalatingExec().(*denyExec) + handler = newAllowEscalatingExec() for _, tc := range testCases { testAdmission(t, tc.pod, handler, true) } } -func testAdmission(t *testing.T, pod *api.Pod, handler *denyExec, shouldAccept bool) { +func testAdmission(t *testing.T, pod *api.Pod, handler *DenyExec, shouldAccept bool) { mockClient := &fake.Clientset{} mockClient.AddReactor("get", "pods", func(action core.Action) (bool, runtime.Object, error) { if action.(core.GetAction).GetName() == pod.Name { @@ -118,12 +118,12 @@ func testAdmission(t *testing.T, pod *api.Pod, handler *denyExec, shouldAccept b }) handler.SetInternalKubeClientSet(mockClient) - admission.Validate(handler) + admission.ValidateInitialization(handler) // pods/exec { req := &rest.ConnectRequest{Name: pod.Name, ResourcePath: "pods/exec"} - err := handler.Admit(admission.NewAttributesRecord(req, nil, api.Kind("Pod").WithVersion("version"), "test", "name", api.Resource("pods").WithVersion("version"), "exec", admission.Connect, nil)) + err := handler.Validate(admission.NewAttributesRecord(req, nil, api.Kind("Pod").WithVersion("version"), "test", "name", api.Resource("pods").WithVersion("version"), "exec", admission.Connect, nil)) if shouldAccept && err != nil { t.Errorf("Unexpected error returned from admission handler: %v", err) } @@ -135,7 +135,7 @@ func testAdmission(t *testing.T, pod *api.Pod, handler *denyExec, shouldAccept b // pods/attach { req := &rest.ConnectRequest{Name: pod.Name, ResourcePath: "pods/attach"} - err := handler.Admit(admission.NewAttributesRecord(req, nil, api.Kind("Pod").WithVersion("version"), "test", "name", api.Resource("pods").WithVersion("version"), "attach", admission.Connect, nil)) + err := handler.Validate(admission.NewAttributesRecord(req, nil, api.Kind("Pod").WithVersion("version"), "test", "name", api.Resource("pods").WithVersion("version"), "attach", admission.Connect, nil)) if shouldAccept && err != nil { t.Errorf("Unexpected error returned from admission handler: %v", err) } @@ -184,7 +184,7 @@ func TestDenyExecOnPrivileged(t *testing.T) { } // Get the direct object though to allow testAdmission to inject the client - handler := NewDenyExecOnPrivileged().(*denyExec) + handler := NewDenyExecOnPrivileged() for _, tc := range testCases { testAdmission(t, tc.pod, handler, tc.shouldAccept) diff --git a/plugin/pkg/admission/extendedresourcetoleration/BUILD b/plugin/pkg/admission/extendedresourcetoleration/BUILD new file mode 100644 index 00000000000..f6c8b5b0ad3 --- /dev/null +++ b/plugin/pkg/admission/extendedresourcetoleration/BUILD @@ -0,0 +1,42 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["admission.go"], + importpath = "k8s.io/kubernetes/plugin/pkg/admission/extendedresourcetoleration", + visibility = ["//visibility:public"], + deps = [ + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["admission_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/plugin/pkg/admission/extendedresourcetoleration", + deps = [ + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/plugin/pkg/admission/extendedresourcetoleration/admission.go b/plugin/pkg/admission/extendedresourcetoleration/admission.go new file mode 100644 index 00000000000..410c18160bc --- /dev/null +++ b/plugin/pkg/admission/extendedresourcetoleration/admission.go @@ -0,0 +1,94 @@ +/* +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 extendedresourcetoleration + +import ( + "fmt" + "io" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apiserver/pkg/admission" + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" +) + +// Register is called by the apiserver to register the plugin factory. +func Register(plugins *admission.Plugins) { + plugins.Register("ExtendedResourceToleration", func(config io.Reader) (admission.Interface, error) { + return newExtendedResourceToleration(), nil + }) +} + +// newExtendedResourceToleration creates a new instance of the ExtendedResourceToleration admission controller. +func newExtendedResourceToleration() *plugin { + return &plugin{ + Handler: admission.NewHandler(admission.Create, admission.Update), + } +} + +// Make sure we are implementing the interface. +var _ admission.MutationInterface = &plugin{} + +type plugin struct { + *admission.Handler +} + +// Admit updates the toleration of a pod based on the resources requested by it. +// If an extended resource of name "example.com/device" is requested, it adds +// a toleration with key "example.com/device", operator "Exists" and effect "NoSchedule". +// The rationale for this is described in: +// https://github.com/kubernetes/kubernetes/issues/55080 +func (p *plugin) Admit(attributes admission.Attributes) error { + // Ignore all calls to subresources or resources other than pods. + if len(attributes.GetSubresource()) != 0 || attributes.GetResource().GroupResource() != core.Resource("pods") { + return nil + } + + pod, ok := attributes.GetObject().(*core.Pod) + if !ok { + return errors.NewBadRequest(fmt.Sprintf("expected *core.Pod but got %T", attributes.GetObject())) + } + + resources := sets.String{} + for _, container := range pod.Spec.Containers { + for resourceName := range container.Resources.Requests { + if helper.IsExtendedResourceName(resourceName) { + resources.Insert(string(resourceName)) + } + } + } + for _, container := range pod.Spec.InitContainers { + for resourceName := range container.Resources.Requests { + if helper.IsExtendedResourceName(resourceName) { + resources.Insert(string(resourceName)) + } + } + } + + // Doing .List() so that we get a stable sorted list. + // This allows us to test adding tolerations for multiple extended resources. + for _, resource := range resources.List() { + helper.AddOrUpdateTolerationInPod(pod, &core.Toleration{ + Key: resource, + Operator: core.TolerationOpExists, + Effect: core.TaintEffectNoSchedule, + }) + } + + return nil +} diff --git a/plugin/pkg/admission/extendedresourcetoleration/admission_test.go b/plugin/pkg/admission/extendedresourcetoleration/admission_test.go new file mode 100644 index 00000000000..646ae007e1d --- /dev/null +++ b/plugin/pkg/admission/extendedresourcetoleration/admission_test.go @@ -0,0 +1,382 @@ +/* +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 extendedresourcetoleration + +import ( + "testing" + + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apiserver/pkg/admission" + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" +) + +func TestAdmit(t *testing.T) { + + plugin := newExtendedResourceToleration() + + containerRequestingCPU := core.Container{ + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceCPU: *resource.NewQuantity(2, resource.DecimalSI), + }, + }, + } + + containerRequestingMemory := core.Container{ + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceMemory: *resource.NewQuantity(2048, resource.DecimalSI), + }, + }, + } + + extendedResource1 := "example.com/device-ek" + extendedResource2 := "example.com/device-do" + + containerRequestingExtendedResource1 := core.Container{ + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(extendedResource1): *resource.NewQuantity(1, resource.DecimalSI), + }, + }, + } + containerRequestingExtendedResource2 := core.Container{ + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceName(extendedResource2): *resource.NewQuantity(2, resource.DecimalSI), + }, + }, + } + + tests := []struct { + description string + requestedPod core.Pod + expectedPod core.Pod + }{ + { + description: "empty pod without any extended resources, expect no change in tolerations", + requestedPod: core.Pod{ + Spec: core.PodSpec{}, + }, + expectedPod: core.Pod{ + Spec: core.PodSpec{}, + }, + }, + { + description: "pod with container without any extended resources, expect no change in tolerations", + requestedPod: core.Pod{ + Spec: core.PodSpec{ + Containers: []core.Container{ + containerRequestingCPU, + }, + }, + }, + expectedPod: core.Pod{ + Spec: core.PodSpec{ + Containers: []core.Container{ + containerRequestingCPU, + }, + }, + }, + }, + { + description: "pod with init container without any extended resources, expect no change in tolerations", + requestedPod: core.Pod{ + Spec: core.PodSpec{ + InitContainers: []core.Container{ + containerRequestingMemory, + }, + }, + }, + expectedPod: core.Pod{ + Spec: core.PodSpec{ + InitContainers: []core.Container{ + containerRequestingMemory, + }, + }, + }, + }, + { + description: "pod with container with extended resource, expect toleration to be added", + requestedPod: core.Pod{ + Spec: core.PodSpec{ + Containers: []core.Container{ + containerRequestingExtendedResource1, + }, + }, + }, + expectedPod: core.Pod{ + Spec: core.PodSpec{ + Containers: []core.Container{ + containerRequestingExtendedResource1, + }, + Tolerations: []core.Toleration{ + { + Key: extendedResource1, + Operator: core.TolerationOpExists, + Effect: core.TaintEffectNoSchedule, + }, + }, + }, + }, + }, + { + description: "pod with init container with extended resource, expect toleration to be added", + requestedPod: core.Pod{ + Spec: core.PodSpec{ + InitContainers: []core.Container{ + containerRequestingExtendedResource2, + }, + }, + }, + expectedPod: core.Pod{ + Spec: core.PodSpec{ + InitContainers: []core.Container{ + containerRequestingExtendedResource2, + }, + Tolerations: []core.Toleration{ + { + Key: extendedResource2, + Operator: core.TolerationOpExists, + Effect: core.TaintEffectNoSchedule, + }, + }, + }, + }, + }, + { + description: "pod with existing tolerations and container with extended resource, expect existing tolerations to be preserved and new toleration to be added", + requestedPod: core.Pod{ + Spec: core.PodSpec{ + Containers: []core.Container{ + containerRequestingCPU, + containerRequestingExtendedResource1, + }, + Tolerations: []core.Toleration{ + { + Key: "foo", + Operator: core.TolerationOpEqual, + Value: "bar", + Effect: core.TaintEffectNoSchedule, + }, + }, + }, + }, + expectedPod: core.Pod{ + Spec: core.PodSpec{ + Containers: []core.Container{ + containerRequestingCPU, + containerRequestingExtendedResource1, + }, + Tolerations: []core.Toleration{ + { + Key: "foo", + Operator: core.TolerationOpEqual, + Value: "bar", + Effect: core.TaintEffectNoSchedule, + }, + { + Key: extendedResource1, + Operator: core.TolerationOpExists, + Effect: core.TaintEffectNoSchedule, + }, + }, + }, + }, + }, + { + description: "pod with multiple extended resources, expect multiple tolerations to be added", + requestedPod: core.Pod{ + Spec: core.PodSpec{ + Containers: []core.Container{ + containerRequestingMemory, + containerRequestingExtendedResource1, + }, + InitContainers: []core.Container{ + containerRequestingCPU, + containerRequestingExtendedResource2, + }, + }, + }, + expectedPod: core.Pod{ + Spec: core.PodSpec{ + Containers: []core.Container{ + containerRequestingMemory, + containerRequestingExtendedResource1, + }, + InitContainers: []core.Container{ + containerRequestingCPU, + containerRequestingExtendedResource2, + }, + Tolerations: []core.Toleration{ + // Note the order, it's sorted by the Key + { + Key: extendedResource2, + Operator: core.TolerationOpExists, + Effect: core.TaintEffectNoSchedule, + }, + { + Key: extendedResource1, + Operator: core.TolerationOpExists, + Effect: core.TaintEffectNoSchedule, + }, + }, + }, + }, + }, + { + description: "pod with container requesting extended resource and existing correct toleration, expect no change in tolerations", + requestedPod: core.Pod{ + Spec: core.PodSpec{ + Containers: []core.Container{ + containerRequestingCPU, + containerRequestingMemory, + containerRequestingExtendedResource1, + }, + Tolerations: []core.Toleration{ + { + Key: extendedResource1, + Operator: core.TolerationOpExists, + Effect: core.TaintEffectNoSchedule, + }, + }, + }, + }, + expectedPod: core.Pod{ + Spec: core.PodSpec{ + Containers: []core.Container{ + containerRequestingCPU, + containerRequestingMemory, + containerRequestingExtendedResource1, + }, + Tolerations: []core.Toleration{ + { + Key: extendedResource1, + Operator: core.TolerationOpExists, + Effect: core.TaintEffectNoSchedule, + }, + }, + }, + }, + }, + { + description: "pod with container requesting extended resource and existing toleration with the same key but different effect and value, expect existing tolerations to be preserved and new toleration to be added", + requestedPod: core.Pod{ + Spec: core.PodSpec{ + Containers: []core.Container{ + containerRequestingCPU, + containerRequestingMemory, + containerRequestingExtendedResource1, + }, + Tolerations: []core.Toleration{ + { + Key: extendedResource1, + Operator: core.TolerationOpEqual, + Value: "foo", + Effect: core.TaintEffectNoExecute, + }, + }, + }, + }, + expectedPod: core.Pod{ + Spec: core.PodSpec{ + Containers: []core.Container{ + containerRequestingCPU, + containerRequestingMemory, + containerRequestingExtendedResource1, + }, + Tolerations: []core.Toleration{ + { + Key: extendedResource1, + Operator: core.TolerationOpEqual, + Value: "foo", + Effect: core.TaintEffectNoExecute, + }, + { + Key: extendedResource1, + Operator: core.TolerationOpExists, + Effect: core.TaintEffectNoSchedule, + }, + }, + }, + }, + }, + { + description: "pod with wildcard toleration and container requesting extended resource, expect existing tolerations to be preserved and new toleration to be added", + requestedPod: core.Pod{ + Spec: core.PodSpec{ + Containers: []core.Container{ + containerRequestingCPU, + containerRequestingMemory, + containerRequestingExtendedResource1, + }, + Tolerations: []core.Toleration{ + { + Operator: core.TolerationOpExists, + }, + }, + }, + }, + expectedPod: core.Pod{ + Spec: core.PodSpec{ + Containers: []core.Container{ + containerRequestingCPU, + containerRequestingMemory, + containerRequestingExtendedResource1, + }, + Tolerations: []core.Toleration{ + { + Operator: core.TolerationOpExists, + }, + { + Key: extendedResource1, + Operator: core.TolerationOpExists, + Effect: core.TaintEffectNoSchedule, + }, + }, + }, + }, + }, + } + for i, test := range tests { + err := plugin.Admit(admission.NewAttributesRecord(&test.requestedPod, nil, core.Kind("Pod").WithVersion("version"), "foo", "name", core.Resource("pods").WithVersion("version"), "", "ignored", nil)) + if err != nil { + t.Errorf("[%d: %s] unexpected error %v for pod %+v", i, test.description, err, test.requestedPod) + } + + if !helper.Semantic.DeepEqual(test.expectedPod.Spec.Tolerations, test.requestedPod.Spec.Tolerations) { + t.Errorf("[%d: %s] expected %#v got %#v", i, test.description, test.expectedPod.Spec.Tolerations, test.requestedPod.Spec.Tolerations) + } + } +} + +func TestHandles(t *testing.T) { + plugin := newExtendedResourceToleration() + tests := map[admission.Operation]bool{ + admission.Create: true, + admission.Update: true, + admission.Delete: false, + admission.Connect: false, + } + for op, expected := range tests { + result := plugin.Handles(op) + if result != expected { + t.Errorf("Unexpected result for operation %s: %v\n", op, result) + } + } +} diff --git a/plugin/pkg/admission/gc/BUILD b/plugin/pkg/admission/gc/BUILD index cb7f2d8ae87..944eeebb50e 100644 --- a/plugin/pkg/admission/gc/BUILD +++ b/plugin/pkg/admission/gc/BUILD @@ -25,10 +25,11 @@ go_library( go_test( name = "go_default_test", srcs = ["gc_admission_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/gc", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/kubeapiserver/admission:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/plugin/pkg/admission/gc/gc_admission.go b/plugin/pkg/admission/gc/gc_admission.go index 5c4287925b6..7acd5cf48fd 100644 --- a/plugin/pkg/admission/gc/gc_admission.go +++ b/plugin/pkg/admission/gc/gc_admission.go @@ -63,6 +63,8 @@ type gcPermissionsEnforcement struct { whiteList []whiteListItem } +var _ admission.ValidationInterface = &gcPermissionsEnforcement{} + // whiteListItem describes an entry in a whitelist ignored by gc permission enforcement. type whiteListItem struct { groupResource schema.GroupResource @@ -79,7 +81,7 @@ func (a *gcPermissionsEnforcement) isWhiteListed(groupResource schema.GroupResou return false } -func (a *gcPermissionsEnforcement) Admit(attributes admission.Attributes) (err error) { +func (a *gcPermissionsEnforcement) Validate(attributes admission.Attributes) (err error) { // // if the request is in the whitelist, we skip mutation checks for this resource. if a.isWhiteListed(attributes.GetResource().GroupResource(), attributes.GetSubresource()) { return nil @@ -102,8 +104,8 @@ func (a *gcPermissionsEnforcement) Admit(attributes admission.Attributes) (err e ResourceRequest: true, Path: "", } - allowed, reason, err := a.authorizer.Authorize(deleteAttributes) - if !allowed { + decision, reason, err := a.authorizer.Authorize(deleteAttributes) + if decision != authorizer.DecisionAllow { return admission.NewForbidden(attributes, fmt.Errorf("cannot set an ownerRef on a resource you can't delete: %v, %v", reason, err)) } @@ -120,8 +122,8 @@ func (a *gcPermissionsEnforcement) Admit(attributes admission.Attributes) (err e // resources. User needs to have delete permission on all the // matched Resources. for _, record := range records { - allowed, reason, err := a.authorizer.Authorize(record) - if !allowed { + decision, reason, err := a.authorizer.Authorize(record) + if decision != authorizer.DecisionAllow { return admission.NewForbidden(attributes, fmt.Errorf("cannot set blockOwnerDeletion if an ownerReference refers to a resource you can't set finalizers on: %v, %v", reason, err)) } } @@ -260,7 +262,7 @@ func (a *gcPermissionsEnforcement) SetRESTMapper(restMapper meta.RESTMapper) { a.restMapper = restMapper } -func (a *gcPermissionsEnforcement) Validate() error { +func (a *gcPermissionsEnforcement) ValidateInitialization() error { if a.authorizer == nil { return fmt.Errorf("missing authorizer") } diff --git a/plugin/pkg/admission/gc/gc_admission_test.go b/plugin/pkg/admission/gc/gc_admission_test.go index 80233326ac9..db805c9b704 100644 --- a/plugin/pkg/admission/gc/gc_admission_test.go +++ b/plugin/pkg/admission/gc/gc_admission_test.go @@ -27,46 +27,47 @@ import ( "k8s.io/apiserver/pkg/admission/initializer" "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" kubeadmission "k8s.io/kubernetes/pkg/kubeapiserver/admission" ) type fakeAuthorizer struct{} -func (fakeAuthorizer) Authorize(a authorizer.Attributes) (bool, string, error) { +func (fakeAuthorizer) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) { username := a.GetUser().GetName() if username == "non-deleter" { if a.GetVerb() == "delete" { - return false, "", nil + return authorizer.DecisionNoOpinion, "", nil } if a.GetVerb() == "update" && a.GetSubresource() == "finalizers" { - return false, "", nil + return authorizer.DecisionNoOpinion, "", nil } - return true, "", nil + return authorizer.DecisionAllow, "", nil } if username == "non-pod-deleter" { if a.GetVerb() == "delete" && a.GetResource() == "pods" { - return false, "", nil + return authorizer.DecisionNoOpinion, "", nil } if a.GetVerb() == "update" && a.GetResource() == "pods" && a.GetSubresource() == "finalizers" { - return false, "", nil + return authorizer.DecisionNoOpinion, "", nil } - return true, "", nil + return authorizer.DecisionAllow, "", nil } if username == "non-rc-deleter" { if a.GetVerb() == "delete" && a.GetResource() == "replicationcontrollers" { - return false, "", nil + return authorizer.DecisionNoOpinion, "", nil } if a.GetVerb() == "update" && a.GetResource() == "replicationcontrollers" && a.GetSubresource() == "finalizers" { - return false, "", nil + return authorizer.DecisionNoOpinion, "", nil } - return true, "", nil + return authorizer.DecisionAllow, "", nil } - return true, "", nil + return authorizer.DecisionAllow, "", nil } // newGCPermissionsEnforcement returns the admission controller configured for testing. @@ -85,11 +86,8 @@ func newGCPermissionsEnforcement() (*gcPermissionsEnforcement, error) { whiteList: whiteList, } - genericPluginInitializer, err := initializer.New(nil, nil, fakeAuthorizer{}, nil, nil, nil) - if err != nil { - return nil, err - } - pluginInitializer := kubeadmission.NewPluginInitializer(nil, nil, nil, api.Registry.RESTMapper(), nil) + genericPluginInitializer := initializer.New(nil, nil, fakeAuthorizer{}, nil) + pluginInitializer := kubeadmission.NewPluginInitializer(nil, nil, nil, legacyscheme.Registry.RESTMapper(), nil) initializersChain := admission.PluginInitializers{} initializersChain = append(initializersChain, genericPluginInitializer) initializersChain = append(initializersChain, pluginInitializer) @@ -268,7 +266,7 @@ func TestGCAdmission(t *testing.T) { user := &user.DefaultInfo{Name: tc.username} attributes := admission.NewAttributesRecord(tc.newObj, tc.oldObj, schema.GroupVersionKind{}, metav1.NamespaceDefault, "foo", tc.resource, tc.subresource, operation, user) - err := gcAdmit.Admit(attributes) + err := gcAdmit.Validate(attributes) if !tc.checkError(err) { t.Errorf("%v: unexpected err: %v", tc.name, err) } @@ -516,7 +514,7 @@ func TestBlockOwnerDeletionAdmission(t *testing.T) { user := &user.DefaultInfo{Name: tc.username} attributes := admission.NewAttributesRecord(tc.newObj, tc.oldObj, schema.GroupVersionKind{}, metav1.NamespaceDefault, "foo", tc.resource, tc.subresource, operation, user) - err := gcAdmit.Admit(attributes) + err := gcAdmit.Validate(attributes) if !tc.checkError(err) { t.Errorf("%v: unexpected err: %v", tc.name, err) } diff --git a/plugin/pkg/admission/imagepolicy/BUILD b/plugin/pkg/admission/imagepolicy/BUILD index 3d1256e8256..64ca678a745 100644 --- a/plugin/pkg/admission/imagepolicy/BUILD +++ b/plugin/pkg/admission/imagepolicy/BUILD @@ -15,7 +15,8 @@ go_library( ], importpath = "k8s.io/kubernetes/plugin/pkg/admission/imagepolicy", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/imagepolicy/install:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/imagepolicy/v1alpha1:go_default_library", @@ -36,10 +37,10 @@ go_test( "certs_test.go", "config_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/imagepolicy", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/imagepolicy/install:go_default_library", "//vendor/k8s.io/api/imagepolicy/v1alpha1:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", diff --git a/plugin/pkg/admission/imagepolicy/admission.go b/plugin/pkg/admission/imagepolicy/admission.go index c123a116d7b..6a0e21e6f58 100644 --- a/plugin/pkg/admission/imagepolicy/admission.go +++ b/plugin/pkg/admission/imagepolicy/admission.go @@ -28,17 +28,16 @@ import ( "github.com/golang/glog" + "k8s.io/api/imagepolicy/v1alpha1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime/schema" - kubeschema "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/cache" "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/util/webhook" "k8s.io/client-go/rest" - - "k8s.io/api/imagepolicy/v1alpha1" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" // install the clientgo image policy API for use with api registry _ "k8s.io/kubernetes/pkg/apis/imagepolicy/install" @@ -59,8 +58,8 @@ func Register(plugins *admission.Plugins) { }) } -// imagePolicyWebhook is an implementation of admission.Interface. -type imagePolicyWebhook struct { +// Plugin is an implementation of admission.Interface. +type Plugin struct { *admission.Handler webhook *webhook.GenericWebhook responseCache *cache.LRUExpireCache @@ -70,7 +69,9 @@ type imagePolicyWebhook struct { defaultAllow bool } -func (a *imagePolicyWebhook) statusTTL(status v1alpha1.ImageReviewStatus) time.Duration { +var _ admission.ValidationInterface = &Plugin{} + +func (a *Plugin) statusTTL(status v1alpha1.ImageReviewStatus) time.Duration { if status.Allowed { return a.allowTTL } @@ -78,7 +79,7 @@ func (a *imagePolicyWebhook) statusTTL(status v1alpha1.ImageReviewStatus) time.D } // Filter out annotations that don't match *.image-policy.k8s.io/* -func (a *imagePolicyWebhook) filterAnnotations(allAnnotations map[string]string) map[string]string { +func (a *Plugin) filterAnnotations(allAnnotations map[string]string) map[string]string { annotations := make(map[string]string) for k, v := range allAnnotations { if strings.Contains(k, ".image-policy.k8s.io/") { @@ -89,7 +90,7 @@ func (a *imagePolicyWebhook) filterAnnotations(allAnnotations map[string]string) } // Function to call on webhook failure; behavior determined by defaultAllow flag -func (a *imagePolicyWebhook) webhookError(pod *api.Pod, attributes admission.Attributes, err error) error { +func (a *Plugin) webhookError(pod *api.Pod, attributes admission.Attributes, err error) error { if err != nil { glog.V(2).Infof("error contacting webhook backend: %s", err) if a.defaultAllow { @@ -108,13 +109,10 @@ func (a *imagePolicyWebhook) webhookError(pod *api.Pod, attributes admission.Att return nil } -func (a *imagePolicyWebhook) Admit(attributes admission.Attributes) (err error) { +// Validate makes an admission decision based on the request attributes +func (a *Plugin) Validate(attributes admission.Attributes) (err error) { // Ignore all calls to subresources or resources other than pods. - allowedResources := map[kubeschema.GroupResource]bool{ - api.Resource("pods"): true, - } - - if len(attributes.GetSubresource()) != 0 || !allowedResources[attributes.GetResource().GroupResource()] { + if attributes.GetSubresource() != "" || attributes.GetResource().GroupResource() != api.Resource("pods") { return nil } @@ -146,7 +144,7 @@ func (a *imagePolicyWebhook) Admit(attributes admission.Attributes) (err error) return nil } -func (a *imagePolicyWebhook) admitPod(pod *api.Pod, attributes admission.Attributes, review *v1alpha1.ImageReview) error { +func (a *Plugin) admitPod(pod *api.Pod, attributes admission.Attributes, review *v1alpha1.ImageReview) error { cacheKey, err := json.Marshal(review.Spec) if err != nil { return err @@ -183,7 +181,7 @@ func (a *imagePolicyWebhook) admitPod(pod *api.Pod, attributes admission.Attribu return nil } -// NewImagePolicyWebhook a new imagePolicyWebhook from the provided config file. +// NewImagePolicyWebhook a new ImagePolicyWebhook plugin from the provided config file. // The config file is specified by --admission-control-config-file and has the // following format for a webhook: // @@ -220,7 +218,11 @@ func (a *imagePolicyWebhook) admitPod(pod *api.Pod, attributes admission.Attribu // // For additional HTTP configuration, refer to the kubeconfig documentation // http://kubernetes.io/v1.1/docs/user-guide/kubeconfig-file.html. -func NewImagePolicyWebhook(configFile io.Reader) (admission.Interface, error) { +func NewImagePolicyWebhook(configFile io.Reader) (*Plugin, error) { + if configFile == nil { + return nil, fmt.Errorf("no config specified") + } + // TODO: move this to a versioned configuration file format var config AdmissionConfig d := yaml.NewYAMLOrJSONDecoder(configFile, 4096) @@ -234,11 +236,11 @@ func NewImagePolicyWebhook(configFile io.Reader) (admission.Interface, error) { return nil, err } - gw, err := webhook.NewGenericWebhook(api.Registry, api.Codecs, whConfig.KubeConfigFile, groupVersions, whConfig.RetryBackoff) + gw, err := webhook.NewGenericWebhook(legacyscheme.Registry, legacyscheme.Codecs, whConfig.KubeConfigFile, groupVersions, whConfig.RetryBackoff) if err != nil { return nil, err } - return &imagePolicyWebhook{ + return &Plugin{ Handler: admission.NewHandler(admission.Create, admission.Update), webhook: gw, responseCache: cache.NewLRUExpireCache(1024), diff --git a/plugin/pkg/admission/imagepolicy/admission_test.go b/plugin/pkg/admission/imagepolicy/admission_test.go index 1ee2d76504c..04cd7822548 100644 --- a/plugin/pkg/admission/imagepolicy/admission_test.go +++ b/plugin/pkg/admission/imagepolicy/admission_test.go @@ -32,7 +32,7 @@ import ( "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/authentication/user" "k8s.io/client-go/tools/clientcmd/api/v1" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "fmt" "io/ioutil" @@ -346,7 +346,7 @@ func (m *mockService) HTTPStatusCode() int { return m.statusCode } // newImagePolicyWebhook creates a temporary kubeconfig file from the provided arguments and attempts to load // a new newImagePolicyWebhook from it. -func newImagePolicyWebhook(callbackURL string, clientCert, clientKey, ca []byte, cacheTime time.Duration, defaultAllow bool) (*imagePolicyWebhook, error) { +func newImagePolicyWebhook(callbackURL string, clientCert, clientKey, ca []byte, cacheTime time.Duration, defaultAllow bool) (*Plugin, error) { tempfile, err := ioutil.TempFile("", "") if err != nil { return nil, err @@ -404,7 +404,10 @@ func newImagePolicyWebhook(callbackURL string, clientCert, clientKey, ca []byte, } defer configFile.Close() wh, err := NewImagePolicyWebhook(configFile) - return wh.(*imagePolicyWebhook), err + if err != nil { + return nil, err + } + return wh, err } func TestTLSConfig(t *testing.T) { @@ -474,7 +477,7 @@ func TestTLSConfig(t *testing.T) { // Allow all and see if we get an error. service.Allow() - err = wh.Admit(attr) + err = wh.Validate(attr) if tt.wantAllowed { if err != nil { t.Errorf("expected successful admission") @@ -496,7 +499,7 @@ func TestTLSConfig(t *testing.T) { } service.Deny() - if err := wh.Admit(attr); err == nil { + if err := wh.Validate(attr); err == nil { t.Errorf("%s: incorrectly admitted with DenyAll policy", tt.test) } }() @@ -510,10 +513,10 @@ type webhookCacheTestCase struct { expectedCached bool } -func testWebhookCacheCases(t *testing.T, serv *mockService, wh *imagePolicyWebhook, attr admission.Attributes, tests []webhookCacheTestCase) { +func testWebhookCacheCases(t *testing.T, serv *mockService, wh *Plugin, attr admission.Attributes, tests []webhookCacheTestCase) { for _, test := range tests { serv.statusCode = test.statusCode - err := wh.Admit(attr) + err := wh.Validate(attr) authorized := err == nil if test.expectedErr && err == nil { @@ -746,7 +749,7 @@ func TestContainerCombinations(t *testing.T) { attr := admission.NewAttributesRecord(tt.pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &user.DefaultInfo{}) - err = wh.Admit(attr) + err = wh.Validate(attr) if tt.wantAllowed { if err != nil { t.Errorf("expected successful admission: %s", tt.test) @@ -824,7 +827,7 @@ func TestDefaultAllow(t *testing.T) { attr := admission.NewAttributesRecord(tt.pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &user.DefaultInfo{}) - err = wh.Admit(attr) + err = wh.Validate(attr) if tt.wantAllowed { if err != nil { t.Errorf("expected successful admission") @@ -916,7 +919,7 @@ func TestAnnotationFiltering(t *testing.T) { attr := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &user.DefaultInfo{}) - err = wh.Admit(attr) + err = wh.Validate(attr) if err != nil { t.Errorf("expected successful admission") } diff --git a/plugin/pkg/admission/imagepolicy/config.go b/plugin/pkg/admission/imagepolicy/config.go index 558b290e862..df34a4906b4 100644 --- a/plugin/pkg/admission/imagepolicy/config.go +++ b/plugin/pkg/admission/imagepolicy/config.go @@ -86,7 +86,7 @@ func normalizeConfigDuration(name string, scale, value, min, max, defaultValue t value *= scale // check value is within range - if value <= min || value > max { + if value < min || value > max { return value, fmt.Errorf("valid value is between %v and %v, got %v", min, max, value) } return value, nil diff --git a/plugin/pkg/admission/imagepolicy/config_test.go b/plugin/pkg/admission/imagepolicy/config_test.go index 9f09d181448..8567011ce16 100644 --- a/plugin/pkg/admission/imagepolicy/config_test.go +++ b/plugin/pkg/admission/imagepolicy/config_test.go @@ -89,6 +89,34 @@ func TestConfigNormalization(t *testing.T) { }, wantErr: false, }, + { + test: "config within normal ranges for min values", + config: imagePolicyWebhookConfig{ + AllowTTL: minAllowTTL / time.Second, + DenyTTL: minDenyTTL / time.Second, + RetryBackoff: minRetryBackoff, + }, + normalizedConfig: imagePolicyWebhookConfig{ + AllowTTL: minAllowTTL, + DenyTTL: minDenyTTL, + RetryBackoff: minRetryBackoff * time.Millisecond, + }, + wantErr: false, + }, + { + test: "config within normal ranges for max values", + config: imagePolicyWebhookConfig{ + AllowTTL: maxAllowTTL / time.Second, + DenyTTL: maxDenyTTL / time.Second, + RetryBackoff: maxRetryBackoff / time.Millisecond, + }, + normalizedConfig: imagePolicyWebhookConfig{ + AllowTTL: maxAllowTTL, + DenyTTL: maxDenyTTL, + RetryBackoff: maxRetryBackoff, + }, + wantErr: false, + }, } for _, tt := range tests { err := normalizeWebhookConfig(&tt.config) diff --git a/plugin/pkg/admission/initialresources/BUILD b/plugin/pkg/admission/initialresources/BUILD index e40439051ec..b159034690b 100644 --- a/plugin/pkg/admission/initialresources/BUILD +++ b/plugin/pkg/admission/initialresources/BUILD @@ -17,7 +17,7 @@ go_library( ], importpath = "k8s.io/kubernetes/plugin/pkg/admission/initialresources", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/cloud.google.com/go/compute/metadata:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/hawkular/hawkular-client-go/metrics:go_default_library", @@ -43,10 +43,10 @@ go_test( "hawkular_test.go", "influxdb_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/initialresources", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/github.com/stretchr/testify/require:go_default_library", "//vendor/golang.org/x/oauth2:go_default_library", "//vendor/golang.org/x/oauth2/google:go_default_library", diff --git a/plugin/pkg/admission/initialresources/admission.go b/plugin/pkg/admission/initialresources/admission.go index 99af24fad13..360885091aa 100644 --- a/plugin/pkg/admission/initialresources/admission.go +++ b/plugin/pkg/admission/initialresources/admission.go @@ -28,7 +28,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) var ( @@ -57,15 +57,17 @@ func Register(plugins *admission.Plugins) { }) } -type initialResources struct { +type InitialResources struct { *admission.Handler source dataSource percentile int64 nsOnly bool } -func newInitialResources(source dataSource, percentile int64, nsOnly bool) admission.Interface { - return &initialResources{ +var _ admission.MutationInterface = &InitialResources{} + +func newInitialResources(source dataSource, percentile int64, nsOnly bool) *InitialResources { + return &InitialResources{ Handler: admission.NewHandler(admission.Create), source: source, percentile: percentile, @@ -73,7 +75,8 @@ func newInitialResources(source dataSource, percentile int64, nsOnly bool) admis } } -func (ir initialResources) Admit(a admission.Attributes) (err error) { +// Admit makes an admission decision based on the request attributes +func (ir InitialResources) Admit(a admission.Attributes) (err error) { // Ignore all calls to subresources or resources other than pods. if a.GetSubresource() != "" || a.GetResource().GroupResource() != api.Resource("pods") { return nil @@ -89,7 +92,7 @@ func (ir initialResources) Admit(a admission.Attributes) (err error) { // The method veryfies whether resources should be set for the given pod and // if there is estimation available the method fills Request field. -func (ir initialResources) estimateAndFillResourcesIfNotSet(pod *api.Pod) { +func (ir InitialResources) estimateAndFillResourcesIfNotSet(pod *api.Pod) { var annotations []string for i := range pod.Spec.InitContainers { annotations = append(annotations, ir.estimateContainer(pod, &pod.Spec.InitContainers[i], "init container")...) @@ -106,7 +109,7 @@ func (ir initialResources) estimateAndFillResourcesIfNotSet(pod *api.Pod) { } } -func (ir initialResources) estimateContainer(pod *api.Pod, c *api.Container, message string) []string { +func (ir InitialResources) estimateContainer(pod *api.Pod, c *api.Container, message string) []string { var annotations []string req := c.Resources.Requests cpu := ir.getEstimationIfNeeded(api.ResourceCPU, c, pod.ObjectMeta.Namespace) @@ -137,7 +140,7 @@ func (ir initialResources) estimateContainer(pod *api.Pod, c *api.Container, mes // getEstimationIfNeeded estimates compute resource for container if its corresponding // Request(min amount) and Limit(max amount) both are not specified. -func (ir initialResources) getEstimationIfNeeded(kind api.ResourceName, c *api.Container, ns string) *resource.Quantity { +func (ir InitialResources) getEstimationIfNeeded(kind api.ResourceName, c *api.Container, ns string) *resource.Quantity { requests := c.Resources.Requests limits := c.Resources.Limits var quantity *resource.Quantity @@ -152,7 +155,7 @@ func (ir initialResources) getEstimationIfNeeded(kind api.ResourceName, c *api.C } return quantity } -func (ir initialResources) getEstimation(kind api.ResourceName, c *api.Container, ns string) (*resource.Quantity, error) { +func (ir InitialResources) getEstimation(kind api.ResourceName, c *api.Container, ns string) (*resource.Quantity, error) { end := time.Now() start := end.Add(-week) var usage, samples int64 diff --git a/plugin/pkg/admission/initialresources/admission_test.go b/plugin/pkg/admission/initialresources/admission_test.go index 0b710210181..a265fd93a09 100644 --- a/plugin/pkg/admission/initialresources/admission_test.go +++ b/plugin/pkg/admission/initialresources/admission_test.go @@ -24,7 +24,7 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) type fakeSource struct { @@ -110,7 +110,7 @@ func expectNoAnnotation(t *testing.T, pod *api.Pod) { } } -func admit(t *testing.T, ir admission.Interface, pods []*api.Pod) { +func admit(t *testing.T, ir admission.MutationInterface, pods []*api.Pod) { for i := range pods { p := pods[i] @@ -123,7 +123,7 @@ func admit(t *testing.T, ir admission.Interface, pods []*api.Pod) { } } -func testAdminScenarios(t *testing.T, ir admission.Interface, p *api.Pod) { +func testAdminScenarios(t *testing.T, ir admission.MutationInterface, p *api.Pod) { podKind := api.Kind("Pod").WithVersion("version") podRes := api.Resource("pods").WithVersion("version") @@ -151,7 +151,7 @@ func testAdminScenarios(t *testing.T, ir admission.Interface, p *api.Pod) { } } -func performTest(t *testing.T, ir admission.Interface) { +func performTest(t *testing.T, ir admission.MutationInterface) { pods := getPods() admit(t, ir, pods) testAdminScenarios(t, ir, pods[0]) diff --git a/plugin/pkg/admission/initialresources/data_source.go b/plugin/pkg/admission/initialresources/data_source.go index 0d366c18c4c..a25f5ee8007 100644 --- a/plugin/pkg/admission/initialresources/data_source.go +++ b/plugin/pkg/admission/initialresources/data_source.go @@ -19,9 +19,8 @@ package initialresources import ( "flag" "fmt" + api "k8s.io/kubernetes/pkg/apis/core" "time" - - "k8s.io/kubernetes/pkg/api" ) var ( diff --git a/plugin/pkg/admission/initialresources/gcm.go b/plugin/pkg/admission/initialresources/gcm.go index ce981e3e8cc..fbe64b5dbae 100644 --- a/plugin/pkg/admission/initialresources/gcm.go +++ b/plugin/pkg/admission/initialresources/gcm.go @@ -17,12 +17,11 @@ limitations under the License. package initialresources import ( + api "k8s.io/kubernetes/pkg/apis/core" "math" "sort" "time" - "k8s.io/kubernetes/pkg/api" - gce "cloud.google.com/go/compute/metadata" "golang.org/x/oauth2" "golang.org/x/oauth2/google" diff --git a/plugin/pkg/admission/initialresources/gcm_test.go b/plugin/pkg/admission/initialresources/gcm_test.go index 426306cf2f2..b03960bb354 100644 --- a/plugin/pkg/admission/initialresources/gcm_test.go +++ b/plugin/pkg/admission/initialresources/gcm_test.go @@ -23,7 +23,7 @@ import ( "golang.org/x/oauth2" "golang.org/x/oauth2/google" gcm "google.golang.org/api/cloudmonitoring/v2beta2" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestGCMReturnsErrorIfClientCannotConnect(t *testing.T) { diff --git a/plugin/pkg/admission/initialresources/hawkular.go b/plugin/pkg/admission/initialresources/hawkular.go index fea6da8824a..1f5aaea424e 100644 --- a/plugin/pkg/admission/initialresources/hawkular.go +++ b/plugin/pkg/admission/initialresources/hawkular.go @@ -30,7 +30,7 @@ import ( "github.com/golang/glog" "github.com/hawkular/hawkular-client-go/metrics" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" diff --git a/plugin/pkg/admission/initialresources/hawkular_test.go b/plugin/pkg/admission/initialresources/hawkular_test.go index 6bf86371ad5..306728f0baf 100644 --- a/plugin/pkg/admission/initialresources/hawkular_test.go +++ b/plugin/pkg/admission/initialresources/hawkular_test.go @@ -18,6 +18,7 @@ package initialresources import ( "fmt" + api "k8s.io/kubernetes/pkg/apis/core" "net/http" "net/http/httptest" "net/url" @@ -25,8 +26,6 @@ import ( "testing" "time" - "k8s.io/kubernetes/pkg/api" - assert "github.com/stretchr/testify/require" ) diff --git a/plugin/pkg/admission/initialresources/influxdb.go b/plugin/pkg/admission/initialresources/influxdb.go index ad526ba37f5..2986367a734 100644 --- a/plugin/pkg/admission/initialresources/influxdb.go +++ b/plugin/pkg/admission/initialresources/influxdb.go @@ -22,7 +22,7 @@ import ( "time" influxdb "github.com/influxdata/influxdb/client" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) const ( diff --git a/plugin/pkg/admission/initialresources/influxdb_test.go b/plugin/pkg/admission/initialresources/influxdb_test.go index bada3acf8c1..34106ee7275 100644 --- a/plugin/pkg/admission/initialresources/influxdb_test.go +++ b/plugin/pkg/admission/initialresources/influxdb_test.go @@ -20,7 +20,7 @@ import ( "testing" "time" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) func TestInfluxDBGetUsagePercentileCPU(t *testing.T) { diff --git a/plugin/pkg/admission/limitranger/BUILD b/plugin/pkg/admission/limitranger/BUILD index 774d2da699e..238ae838747 100644 --- a/plugin/pkg/admission/limitranger/BUILD +++ b/plugin/pkg/admission/limitranger/BUILD @@ -14,7 +14,7 @@ go_library( ], importpath = "k8s.io/kubernetes/plugin/pkg/admission/limitranger", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", "//pkg/client/listers/core/internalversion:go_default_library", @@ -33,10 +33,10 @@ go_library( go_test( name = "go_default_test", srcs = ["admission_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/limitranger", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", diff --git a/plugin/pkg/admission/limitranger/admission.go b/plugin/pkg/admission/limitranger/admission.go index 46486c37db7..bee1a9f5677 100644 --- a/plugin/pkg/admission/limitranger/admission.go +++ b/plugin/pkg/admission/limitranger/admission.go @@ -32,7 +32,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" corelisters "k8s.io/kubernetes/pkg/client/listers/core/internalversion" @@ -50,8 +50,8 @@ func Register(plugins *admission.Plugins) { }) } -// limitRanger enforces usage limits on a per resource basis in the namespace -type limitRanger struct { +// LimitRanger enforces usage limits on a per resource basis in the namespace +type LimitRanger struct { *admission.Handler client internalclientset.Interface actions LimitRangerActions @@ -64,18 +64,22 @@ type limitRanger struct { liveTTL time.Duration } +var _ admission.MutationInterface = &LimitRanger{} +var _ admission.ValidationInterface = &LimitRanger{} +var _ kubeapiserveradmission.WantsInternalKubeInformerFactory = &LimitRanger{} + type liveLookupEntry struct { expiry time.Time items []*api.LimitRange } -func (l *limitRanger) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) { +func (l *LimitRanger) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) { limitRangeInformer := f.Core().InternalVersion().LimitRanges() l.SetReadyFunc(limitRangeInformer.Informer().HasSynced) l.lister = limitRangeInformer.Lister() } -func (l *limitRanger) Validate() error { +func (l *LimitRanger) ValidateInitialization() error { if l.lister == nil { return fmt.Errorf("missing limitRange lister") } @@ -86,7 +90,16 @@ func (l *limitRanger) Validate() error { } // Admit admits resources into cluster that do not violate any defined LimitRange in the namespace -func (l *limitRanger) Admit(a admission.Attributes) (err error) { +func (l *LimitRanger) Admit(a admission.Attributes) (err error) { + return l.runLimitFunc(a, l.actions.MutateLimit) +} + +// Validate admits resources into cluster that do not violate any defined LimitRange in the namespace +func (l *LimitRanger) Validate(a admission.Attributes) (err error) { + return l.runLimitFunc(a, l.actions.ValidateLimit) +} + +func (l *LimitRanger) runLimitFunc(a admission.Attributes, limitFn func(limitRange *api.LimitRange, kind string, obj runtime.Object) error) (err error) { if !l.actions.SupportsAttributes(a) { return nil } @@ -100,9 +113,43 @@ func (l *limitRanger) Admit(a admission.Attributes) (err error) { } } + // ignore all objects marked for deletion + oldObj := a.GetOldObject() + if oldObj != nil { + oldAccessor, err := meta.Accessor(oldObj) + if err != nil { + return admission.NewForbidden(a, err) + } + if oldAccessor.GetDeletionTimestamp() != nil { + return nil + } + } + + items, err := l.GetLimitRanges(a) + if err != nil { + return err + } + + // ensure it meets each prescribed min/max + for i := range items { + limitRange := items[i] + + if !l.actions.SupportsLimit(limitRange) { + continue + } + + err = limitFn(limitRange, a.GetResource().Resource, a.GetObject()) + if err != nil { + return admission.NewForbidden(a, err) + } + } + return nil +} + +func (l *LimitRanger) GetLimitRanges(a admission.Attributes) ([]*api.LimitRange, error) { items, err := l.lister.LimitRanges(a.GetNamespace()).List(labels.Everything()) if err != nil { - return admission.NewForbidden(a, fmt.Errorf("unable to %s %v at this time because there was an error enforcing limit ranges", a.GetOperation(), a.GetResource())) + return nil, admission.NewForbidden(a, fmt.Errorf("unable to %s %v at this time because there was an error enforcing limit ranges", a.GetOperation(), a.GetResource())) } // if there are no items held in our indexer, check our live-lookup LRU, if that misses, do the live lookup to prime it. @@ -116,7 +163,7 @@ func (l *limitRanger) Admit(a admission.Attributes) (err error) { // throttling - see #22422 for details. liveList, err := l.client.Core().LimitRanges(a.GetNamespace()).List(metav1.ListOptions{}) if err != nil { - return admission.NewForbidden(a, err) + return nil, admission.NewForbidden(a, err) } newEntry := liveLookupEntry{expiry: time.Now().Add(l.liveTTL)} for i := range liveList.Items { @@ -133,24 +180,11 @@ func (l *limitRanger) Admit(a admission.Attributes) (err error) { } - // ensure it meets each prescribed min/max - for i := range items { - limitRange := items[i] - - if !l.actions.SupportsLimit(limitRange) { - continue - } - - err = l.actions.Limit(limitRange, a.GetResource().Resource, a.GetObject()) - if err != nil { - return admission.NewForbidden(a, err) - } - } - return nil + return items, nil } // NewLimitRanger returns an object that enforces limits based on the supplied limit function -func NewLimitRanger(actions LimitRangerActions) (admission.Interface, error) { +func NewLimitRanger(actions LimitRangerActions) (*LimitRanger, error) { liveLookupCache, err := lru.New(10000) if err != nil { return nil, err @@ -160,7 +194,7 @@ func NewLimitRanger(actions LimitRangerActions) (admission.Interface, error) { actions = &DefaultLimitRangerActions{} } - return &limitRanger{ + return &LimitRanger{ Handler: admission.NewHandler(admission.Create, admission.Update), actions: actions, liveLookupCache: liveLookupCache, @@ -168,10 +202,10 @@ func NewLimitRanger(actions LimitRangerActions) (admission.Interface, error) { }, nil } -var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&limitRanger{}) -var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&limitRanger{}) +var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&LimitRanger{}) +var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&LimitRanger{}) -func (a *limitRanger) SetInternalKubeClientSet(client internalclientset.Interface) { +func (a *LimitRanger) SetInternalKubeClientSet(client internalclientset.Interface) { a.client = client } @@ -399,12 +433,23 @@ var _ LimitRangerActions = &DefaultLimitRangerActions{} // Limit enforces resource requirements of incoming resources against enumerated constraints // on the LimitRange. It may modify the incoming object to apply default resource requirements // if not specified, and enumerated on the LimitRange -func (d *DefaultLimitRangerActions) Limit(limitRange *api.LimitRange, resourceName string, obj runtime.Object) error { +func (d *DefaultLimitRangerActions) MutateLimit(limitRange *api.LimitRange, resourceName string, obj runtime.Object) error { switch resourceName { case "pods": - return PodLimitFunc(limitRange, obj.(*api.Pod)) + return PodMutateLimitFunc(limitRange, obj.(*api.Pod)) + } + return nil +} + +// Limit enforces resource requirements of incoming resources against enumerated constraints +// on the LimitRange. It may modify the incoming object to apply default resource requirements +// if not specified, and enumerated on the LimitRange +func (d *DefaultLimitRangerActions) ValidateLimit(limitRange *api.LimitRange, resourceName string, obj runtime.Object) error { + switch resourceName { + case "pods": + return PodValidateLimitFunc(limitRange, obj.(*api.Pod)) case "persistentvolumeclaims": - return PersistentVolumeClaimLimitFunc(limitRange, obj.(*api.PersistentVolumeClaim)) + return PersistentVolumeClaimValidateLimitFunc(limitRange, obj.(*api.PersistentVolumeClaim)) } return nil } @@ -424,11 +469,11 @@ func (d *DefaultLimitRangerActions) SupportsLimit(limitRange *api.LimitRange) bo return true } -// PersistentVolumeClaimLimitFunc enforces storage limits for PVCs. +// PersistentVolumeClaimValidateLimitFunc enforces storage limits for PVCs. // Users request storage via pvc.Spec.Resources.Requests. Min/Max is enforced by an admin with LimitRange. // Claims will not be modified with default values because storage is a required part of pvc.Spec. // All storage enforced values *only* apply to pvc.Spec.Resources.Requests. -func PersistentVolumeClaimLimitFunc(limitRange *api.LimitRange, pvc *api.PersistentVolumeClaim) error { +func PersistentVolumeClaimValidateLimitFunc(limitRange *api.LimitRange, pvc *api.PersistentVolumeClaim) error { var errs []error for i := range limitRange.Spec.Limits { limit := limitRange.Spec.Limits[i] @@ -452,14 +497,19 @@ func PersistentVolumeClaimLimitFunc(limitRange *api.LimitRange, pvc *api.Persist return utilerrors.NewAggregate(errs) } -// PodLimitFunc enforces resource requirements enumerated by the pod against +// PodMutateLimitFunc sets resource requirements enumerated by the pod against // the specified LimitRange. The pod may be modified to apply default resource // requirements if not specified, and enumerated on the LimitRange -func PodLimitFunc(limitRange *api.LimitRange, pod *api.Pod) error { - var errs []error - +func PodMutateLimitFunc(limitRange *api.LimitRange, pod *api.Pod) error { defaultResources := defaultContainerResourceRequirements(limitRange) mergePodResourceRequirements(pod, &defaultResources) + return nil +} + +// PodValidateLimitFunc enforces resource requirements enumerated by the pod against +// the specified LimitRange. +func PodValidateLimitFunc(limitRange *api.LimitRange, pod *api.Pod) error { + var errs []error for i := range limitRange.Spec.Limits { limit := limitRange.Spec.Limits[i] diff --git a/plugin/pkg/admission/limitranger/admission_test.go b/plugin/pkg/admission/limitranger/admission_test.go index f6fd2c9259a..33213a582bc 100644 --- a/plugin/pkg/admission/limitranger/admission_test.go +++ b/plugin/pkg/admission/limitranger/admission_test.go @@ -29,7 +29,7 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apiserver/pkg/admission" core "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" @@ -430,7 +430,11 @@ func TestPodLimitFunc(t *testing.T) { } for i := range successCases { test := successCases[i] - err := PodLimitFunc(&test.limitRange, &test.pod) + err := PodMutateLimitFunc(&test.limitRange, &test.pod) + if err != nil { + t.Errorf("Unexpected error for pod: %s, %v", test.pod.Name, err) + } + err = PodValidateLimitFunc(&test.limitRange, &test.pod) if err != nil { t.Errorf("Unexpected error for pod: %s, %v", test.pod.Name, err) } @@ -610,7 +614,11 @@ func TestPodLimitFunc(t *testing.T) { } for i := range errorCases { test := errorCases[i] - err := PodLimitFunc(&test.limitRange, &test.pod) + err := PodMutateLimitFunc(&test.limitRange, &test.pod) + if err != nil { + t.Errorf("Unexpected error for pod: %s, %v", test.pod.Name, err) + } + err = PodValidateLimitFunc(&test.limitRange, &test.pod) if err == nil { t.Errorf("Expected error for pod: %s", test.pod.Name) } @@ -628,7 +636,7 @@ func getLocalStorageResourceList(ephemeralStorage string) api.ResourceList { func TestPodLimitFuncApplyDefault(t *testing.T) { limitRange := validLimitRange() testPod := validPodInit(validPod("foo", 1, getResourceRequirements(api.ResourceList{}, api.ResourceList{})), getResourceRequirements(api.ResourceList{}, api.ResourceList{})) - err := PodLimitFunc(&limitRange, &testPod) + err := PodMutateLimitFunc(&limitRange, &testPod) if err != nil { t.Errorf("Unexpected error for valid pod: %s, %v", testPod.Name, err) } @@ -641,16 +649,16 @@ func TestPodLimitFuncApplyDefault(t *testing.T) { requestCpu := container.Resources.Requests.Cpu().String() if limitMemory != "10Mi" { - t.Errorf("Unexpected memory value %s", limitMemory) + t.Errorf("Unexpected limit memory value %s", limitMemory) } if limitCpu != "75m" { - t.Errorf("Unexpected cpu value %s", limitCpu) + t.Errorf("Unexpected limit cpu value %s", limitCpu) } if requestMemory != "5Mi" { - t.Errorf("Unexpected memory value %s", requestMemory) + t.Errorf("Unexpected request memory value %s", requestMemory) } if requestCpu != "50m" { - t.Errorf("Unexpected cpu value %s", requestCpu) + t.Errorf("Unexpected request cpu value %s", requestCpu) } } @@ -662,16 +670,16 @@ func TestPodLimitFuncApplyDefault(t *testing.T) { requestCpu := container.Resources.Requests.Cpu().String() if limitMemory != "10Mi" { - t.Errorf("Unexpected memory value %s", limitMemory) + t.Errorf("Unexpected limit memory value %s", limitMemory) } if limitCpu != "75m" { - t.Errorf("Unexpected cpu value %s", limitCpu) + t.Errorf("Unexpected limit cpu value %s", limitCpu) } if requestMemory != "5Mi" { - t.Errorf("Unexpected memory value %s", requestMemory) + t.Errorf("Unexpected request memory value %s", requestMemory) } if requestCpu != "50m" { - t.Errorf("Unexpected cpu value %s", requestCpu) + t.Errorf("Unexpected request cpu value %s", requestCpu) } } } @@ -687,11 +695,15 @@ func TestLimitRangerIgnoresSubresource(t *testing.T) { testPod := validPod("testPod", 1, api.ResourceRequirements{}) err = handler.Admit(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Update, nil)) + if err != nil { + t.Fatal(err) + } + err = handler.Validate(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Update, nil)) if err == nil { t.Errorf("Expected an error since the pod did not specify resource limits in its update call") } - err = handler.Admit(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "status", admission.Update, nil)) + err = handler.Validate(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "status", admission.Update, nil)) if err != nil { t.Errorf("Should have ignored calls to any subresource of pod %v", err) } @@ -709,14 +721,27 @@ func TestLimitRangerAdmitPod(t *testing.T) { testPod := validPod("testPod", 1, api.ResourceRequirements{}) err = handler.Admit(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Update, nil)) + if err != nil { + t.Fatal(err) + } + err = handler.Validate(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Update, nil)) if err == nil { t.Errorf("Expected an error since the pod did not specify resource limits in its update call") } - err = handler.Admit(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "status", admission.Update, nil)) + err = handler.Validate(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "status", admission.Update, nil)) if err != nil { t.Errorf("Should have ignored calls to any subresource of pod %v", err) } + + // a pod that is undergoing termination should never be blocked + terminatingPod := validPod("terminatingPod", 1, api.ResourceRequirements{}) + now := metav1.Now() + terminatingPod.DeletionTimestamp = &now + err = handler.Validate(admission.NewAttributesRecord(&terminatingPod, &terminatingPod, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "terminatingPod", api.Resource("pods").WithVersion("version"), "", admission.Update, nil)) + if err != nil { + t.Errorf("LimitRange should ignore a pod marked for termination") + } } // newMockClientForTest creates a mock client that returns a client configured for the specified list of limit ranges @@ -738,7 +763,7 @@ func newMockClientForTest(limitRanges []api.LimitRange) *fake.Clientset { } // newHandlerForTest returns a handler configured for testing. -func newHandlerForTest(c clientset.Interface) (admission.Interface, informers.SharedInformerFactory, error) { +func newHandlerForTest(c clientset.Interface) (*LimitRanger, informers.SharedInformerFactory, error) { f := informers.NewSharedInformerFactory(c, 5*time.Minute) handler, err := NewLimitRanger(&DefaultLimitRangerActions{}) if err != nil { @@ -746,7 +771,7 @@ func newHandlerForTest(c clientset.Interface) (admission.Interface, informers.Sh } pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil) pluginInitializer.Initialize(handler) - err = admission.Validate(handler) + err = admission.ValidateInitialization(handler) return handler, f, err } @@ -786,7 +811,7 @@ func TestPersistentVolumeClaimLimitFunc(t *testing.T) { } for i := range successCases { test := successCases[i] - err := PersistentVolumeClaimLimitFunc(&test.limitRange, &test.pvc) + err := PersistentVolumeClaimValidateLimitFunc(&test.limitRange, &test.pvc) if err != nil { t.Errorf("Unexpected error for pvc: %s, %v", test.pvc.Name, err) } @@ -804,7 +829,7 @@ func TestPersistentVolumeClaimLimitFunc(t *testing.T) { } for i := range errorCases { test := errorCases[i] - err := PersistentVolumeClaimLimitFunc(&test.limitRange, &test.pvc) + err := PersistentVolumeClaimValidateLimitFunc(&test.limitRange, &test.pvc) if err == nil { t.Errorf("Expected error for pvc: %s", test.pvc.Name) } diff --git a/plugin/pkg/admission/limitranger/interfaces.go b/plugin/pkg/admission/limitranger/interfaces.go index f42b3269b94..4c520c68480 100644 --- a/plugin/pkg/admission/limitranger/interfaces.go +++ b/plugin/pkg/admission/limitranger/interfaces.go @@ -19,12 +19,14 @@ package limitranger import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) type LimitRangerActions interface { - // Limit is a pluggable function to enforce limits on the object. - Limit(limitRange *api.LimitRange, kind string, obj runtime.Object) error + // MutateLimit is a pluggable function to set limits on the object. + MutateLimit(limitRange *api.LimitRange, kind string, obj runtime.Object) error + // ValidateLimits is a pluggable function to enforce limits on the object. + ValidateLimit(limitRange *api.LimitRange, kind string, obj runtime.Object) error // SupportsAttributes is a pluggable function to allow overridding what resources the limitranger // supports. SupportsAttributes(attr admission.Attributes) bool diff --git a/plugin/pkg/admission/namespace/autoprovision/BUILD b/plugin/pkg/admission/namespace/autoprovision/BUILD index c735795a881..7891b9d2535 100644 --- a/plugin/pkg/admission/namespace/autoprovision/BUILD +++ b/plugin/pkg/admission/namespace/autoprovision/BUILD @@ -11,7 +11,7 @@ go_library( srcs = ["admission.go"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", "//pkg/client/listers/core/internalversion:go_default_library", @@ -25,10 +25,10 @@ go_library( go_test( name = "go_default_test", srcs = ["admission_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", diff --git a/plugin/pkg/admission/namespace/autoprovision/admission.go b/plugin/pkg/admission/namespace/autoprovision/admission.go index f7f2b343aa4..51e113af00e 100644 --- a/plugin/pkg/admission/namespace/autoprovision/admission.go +++ b/plugin/pkg/admission/namespace/autoprovision/admission.go @@ -23,7 +23,7 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" corelisters "k8s.io/kubernetes/pkg/client/listers/core/internalversion" @@ -37,19 +37,21 @@ func Register(plugins *admission.Plugins) { }) } -// provision is an implementation of admission.Interface. +// Provision is an implementation of admission.Interface. // It looks at all incoming requests in a namespace context, and if the namespace does not exist, it creates one. // It is useful in deployments that do not want to restrict creation of a namespace prior to its usage. -type provision struct { +type Provision struct { *admission.Handler client internalclientset.Interface namespaceLister corelisters.NamespaceLister } -var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&provision{}) -var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&provision{}) +var _ admission.MutationInterface = &Provision{} +var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&Provision{}) +var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&Provision{}) -func (p *provision) Admit(a admission.Attributes) error { +// Admit makes an admission decision based on the request attributes +func (p *Provision) Admit(a admission.Attributes) error { // if we're here, then we've already passed authentication, so we're allowed to do what we're trying to do // if we're here, then the API server has found a route, which means that if we have a non-empty namespace // its a namespaced resource. @@ -87,23 +89,26 @@ func (p *provision) Admit(a admission.Attributes) error { } // NewProvision creates a new namespace provision admission control handler -func NewProvision() admission.Interface { - return &provision{ +func NewProvision() *Provision { + return &Provision{ Handler: admission.NewHandler(admission.Create), } } -func (p *provision) SetInternalKubeClientSet(client internalclientset.Interface) { +// SetInternalKubeClientSet implements the WantsInternalKubeClientSet interface. +func (p *Provision) SetInternalKubeClientSet(client internalclientset.Interface) { p.client = client } -func (p *provision) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) { +// SetInternalKubeInformerFactory implements the WantsInternalKubeInformerFactory interface. +func (p *Provision) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) { namespaceInformer := f.Core().InternalVersion().Namespaces() p.namespaceLister = namespaceInformer.Lister() p.SetReadyFunc(namespaceInformer.Informer().HasSynced) } -func (p *provision) Validate() error { +// ValidateInitialization implements the InitializationValidator interface. +func (p *Provision) ValidateInitialization() error { if p.namespaceLister == nil { return fmt.Errorf("missing namespaceLister") } diff --git a/plugin/pkg/admission/namespace/autoprovision/admission_test.go b/plugin/pkg/admission/namespace/autoprovision/admission_test.go index f7429d02531..df316c43789 100644 --- a/plugin/pkg/admission/namespace/autoprovision/admission_test.go +++ b/plugin/pkg/admission/namespace/autoprovision/admission_test.go @@ -27,7 +27,7 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apiserver/pkg/admission" core "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" @@ -35,12 +35,12 @@ import ( ) // newHandlerForTest returns the admission controller configured for testing. -func newHandlerForTest(c clientset.Interface) (admission.Interface, informers.SharedInformerFactory, error) { +func newHandlerForTest(c clientset.Interface) (admission.MutationInterface, informers.SharedInformerFactory, error) { f := informers.NewSharedInformerFactory(c, 5*time.Minute) handler := NewProvision() pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil) pluginInitializer.Initialize(handler) - err := admission.Validate(handler) + err := admission.ValidateInitialization(handler) return handler, f, err } diff --git a/plugin/pkg/admission/namespace/exists/BUILD b/plugin/pkg/admission/namespace/exists/BUILD index c5ee77024b4..615ec29263e 100644 --- a/plugin/pkg/admission/namespace/exists/BUILD +++ b/plugin/pkg/admission/namespace/exists/BUILD @@ -11,7 +11,7 @@ go_library( srcs = ["admission.go"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/namespace/exists", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", "//pkg/client/listers/core/internalversion:go_default_library", @@ -25,10 +25,10 @@ go_library( go_test( name = "go_default_test", srcs = ["admission_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/namespace/exists", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", diff --git a/plugin/pkg/admission/namespace/exists/admission.go b/plugin/pkg/admission/namespace/exists/admission.go index 8bf1e6eccc5..51307482245 100644 --- a/plugin/pkg/admission/namespace/exists/admission.go +++ b/plugin/pkg/admission/namespace/exists/admission.go @@ -23,7 +23,7 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" corelisters "k8s.io/kubernetes/pkg/client/listers/core/internalversion" @@ -37,19 +37,21 @@ func Register(plugins *admission.Plugins) { }) } -// exists is an implementation of admission.Interface. +// Exists is an implementation of admission.Interface. // It rejects all incoming requests in a namespace context if the namespace does not exist. // It is useful in deployments that want to enforce pre-declaration of a Namespace resource. -type exists struct { +type Exists struct { *admission.Handler client internalclientset.Interface namespaceLister corelisters.NamespaceLister } -var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&exists{}) -var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&exists{}) +var _ admission.ValidationInterface = &Exists{} +var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&Exists{}) +var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&Exists{}) -func (e *exists) Admit(a admission.Attributes) error { +// Validate makes an admission decision based on the request attributes +func (e *Exists) Validate(a admission.Attributes) error { // if we're here, then we've already passed authentication, so we're allowed to do what we're trying to do // if we're here, then the API server has found a route, which means that if we have a non-empty namespace // its a namespaced resource. @@ -82,23 +84,26 @@ func (e *exists) Admit(a admission.Attributes) error { } // NewExists creates a new namespace exists admission control handler -func NewExists() admission.Interface { - return &exists{ +func NewExists() *Exists { + return &Exists{ Handler: admission.NewHandler(admission.Create, admission.Update, admission.Delete), } } -func (e *exists) SetInternalKubeClientSet(client internalclientset.Interface) { +// SetInternalKubeClientSet implements the WantsInternalKubeClientSet interface. +func (e *Exists) SetInternalKubeClientSet(client internalclientset.Interface) { e.client = client } -func (e *exists) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) { +// SetInternalKubeInformerFactory implements the WantsInternalKubeInformerFactory interface. +func (e *Exists) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) { namespaceInformer := f.Core().InternalVersion().Namespaces() e.namespaceLister = namespaceInformer.Lister() e.SetReadyFunc(namespaceInformer.Informer().HasSynced) } -func (e *exists) Validate() error { +// ValidateInitialization implements the InitializationValidator interface. +func (e *Exists) ValidateInitialization() error { if e.namespaceLister == nil { return fmt.Errorf("missing namespaceLister") } diff --git a/plugin/pkg/admission/namespace/exists/admission_test.go b/plugin/pkg/admission/namespace/exists/admission_test.go index 79f23bb3db2..6cfc2d96e8b 100644 --- a/plugin/pkg/admission/namespace/exists/admission_test.go +++ b/plugin/pkg/admission/namespace/exists/admission_test.go @@ -26,7 +26,7 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apiserver/pkg/admission" core "k8s.io/client-go/testing" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" @@ -34,12 +34,12 @@ import ( ) // newHandlerForTest returns the admission controller configured for testing. -func newHandlerForTest(c clientset.Interface) (admission.Interface, informers.SharedInformerFactory, error) { +func newHandlerForTest(c clientset.Interface) (admission.ValidationInterface, informers.SharedInformerFactory, error) { f := informers.NewSharedInformerFactory(c, 5*time.Minute) handler := NewExists() pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil) pluginInitializer.Initialize(handler) - err := admission.Validate(handler) + err := admission.ValidateInitialization(handler) return handler, f, err } @@ -87,7 +87,7 @@ func TestAdmissionNamespaceExists(t *testing.T) { informerFactory.Start(wait.NeverStop) pod := newPod(namespace) - err = handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) + err = handler.Validate(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) if err != nil { t.Errorf("unexpected error returned from admission handler") } @@ -107,7 +107,7 @@ func TestAdmissionNamespaceDoesNotExist(t *testing.T) { informerFactory.Start(wait.NeverStop) pod := newPod(namespace) - err = handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) + err = handler.Validate(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) if err == nil { actions := "" for _, action := range mockClient.Actions() { diff --git a/plugin/pkg/admission/noderestriction/BUILD b/plugin/pkg/admission/noderestriction/BUILD index d35a2507ff9..cab4287a3dd 100644 --- a/plugin/pkg/admission/noderestriction/BUILD +++ b/plugin/pkg/admission/noderestriction/BUILD @@ -11,27 +11,30 @@ go_library( srcs = ["admission.go"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/noderestriction", deps = [ - "//pkg/api:go_default_library", "//pkg/api/pod:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/policy:go_default_library", "//pkg/auth/nodeidentifier:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library", + "//pkg/features:go_default_library", "//pkg/kubeapiserver/admission:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", ], ) go_test( name = "go_default_test", srcs = ["admission_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/noderestriction", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/policy:go_default_library", "//pkg/auth/nodeidentifier:go_default_library", "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", diff --git a/plugin/pkg/admission/noderestriction/admission.go b/plugin/pkg/admission/noderestriction/admission.go index f1f77a95569..d958dad3762 100644 --- a/plugin/pkg/admission/noderestriction/admission.go +++ b/plugin/pkg/admission/noderestriction/admission.go @@ -23,13 +23,16 @@ import ( apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/diff" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" + utilfeature "k8s.io/apiserver/pkg/util/feature" podutil "k8s.io/kubernetes/pkg/api/pod" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/policy" "k8s.io/kubernetes/pkg/auth/nodeidentifier" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" coreinternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" + "k8s.io/kubernetes/pkg/features" kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission" ) @@ -69,7 +72,7 @@ func (p *nodePlugin) SetInternalKubeClientSet(f internalclientset.Interface) { p.podsGetter = f.Core() } -func (p *nodePlugin) Validate() error { +func (p *nodePlugin) ValidateInitialization() error { if p.nodeIdentifier == nil { return fmt.Errorf("%s requires a node identifier", PluginName) } @@ -82,6 +85,7 @@ func (p *nodePlugin) Validate() error { var ( podResource = api.Resource("pods") nodeResource = api.Resource("nodes") + pvcResource = api.Resource("persistentvolumeclaims") ) func (c *nodePlugin) Admit(a admission.Attributes) error { @@ -113,6 +117,14 @@ func (c *nodePlugin) Admit(a admission.Attributes) error { case nodeResource: return c.admitNode(nodeName, a) + case pvcResource: + switch a.GetSubresource() { + case "status": + return c.admitPVCStatus(nodeName, a) + default: + return admission.NewForbidden(a, fmt.Errorf("may only update PVC status")) + } + default: return nil } @@ -189,7 +201,7 @@ func (c *nodePlugin) admitPodStatus(nodeName string, a admission.Attributes) err // require an existing pod pod, ok := a.GetOldObject().(*api.Pod) if !ok { - return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject())) + return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetOldObject())) } // only allow a node to update status of a pod bound to itself if pod.Spec.NodeName != nodeName { @@ -241,6 +253,50 @@ func (c *nodePlugin) admitPodEviction(nodeName string, a admission.Attributes) e } } +func (c *nodePlugin) admitPVCStatus(nodeName string, a admission.Attributes) error { + switch a.GetOperation() { + case admission.Update: + if !utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) { + return admission.NewForbidden(a, fmt.Errorf("node %q may not update persistentvolumeclaim metadata", nodeName)) + } + + oldPVC, ok := a.GetOldObject().(*api.PersistentVolumeClaim) + if !ok { + return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetOldObject())) + } + + newPVC, ok := a.GetObject().(*api.PersistentVolumeClaim) + if !ok { + return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject())) + } + + // make copies for comparison + oldPVC = oldPVC.DeepCopy() + newPVC = newPVC.DeepCopy() + + // zero out resourceVersion to avoid comparing differences, + // since the new object could leave it empty to indicate an unconditional update + oldPVC.ObjectMeta.ResourceVersion = "" + newPVC.ObjectMeta.ResourceVersion = "" + + oldPVC.Status.Capacity = nil + newPVC.Status.Capacity = nil + + oldPVC.Status.Conditions = nil + newPVC.Status.Conditions = nil + + // ensure no metadata changed. nodes should not be able to relabel, add finalizers/owners, etc + if !apiequality.Semantic.DeepEqual(oldPVC, newPVC) { + return admission.NewForbidden(a, fmt.Errorf("node %q may not update fields other than status.capacity and status.conditions: %v", nodeName, diff.ObjectReflectDiff(oldPVC, newPVC))) + } + + return nil + + default: + return admission.NewForbidden(a, fmt.Errorf("unexpected operation %q", a.GetOperation())) + } +} + func (c *nodePlugin) admitNode(nodeName string, a admission.Attributes) error { requestedName := a.GetName() if a.GetOperation() == admission.Create { diff --git a/plugin/pkg/admission/noderestriction/admission_test.go b/plugin/pkg/admission/noderestriction/admission_test.go index 7c109b2d3f0..e7737d43eac 100644 --- a/plugin/pkg/admission/noderestriction/admission_test.go +++ b/plugin/pkg/admission/noderestriction/admission_test.go @@ -23,7 +23,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/authentication/user" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/policy" policyapi "k8s.io/kubernetes/pkg/apis/policy" "k8s.io/kubernetes/pkg/auth/nodeidentifier" diff --git a/plugin/pkg/admission/persistentvolume/label/BUILD b/plugin/pkg/admission/persistentvolume/label/BUILD index 36299294a03..3afaf06391c 100644 --- a/plugin/pkg/admission/persistentvolume/label/BUILD +++ b/plugin/pkg/admission/persistentvolume/label/BUILD @@ -14,7 +14,7 @@ go_library( ], importpath = "k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/cloudprovider:go_default_library", "//pkg/cloudprovider/providers/aws:go_default_library", "//pkg/cloudprovider/providers/gce:go_default_library", @@ -29,11 +29,12 @@ go_library( go_test( name = "go_default_test", srcs = ["admission_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/cloudprovider/providers/aws:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", diff --git a/plugin/pkg/admission/persistentvolume/label/admission.go b/plugin/pkg/admission/persistentvolume/label/admission.go index 92dd76f9921..86e1921fcd9 100644 --- a/plugin/pkg/admission/persistentvolume/label/admission.go +++ b/plugin/pkg/admission/persistentvolume/label/admission.go @@ -24,7 +24,7 @@ import ( "github.com/golang/glog" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/cloudprovider/providers/aws" "k8s.io/kubernetes/pkg/cloudprovider/providers/gce" @@ -52,6 +52,7 @@ type persistentVolumeLabel struct { gceCloudProvider *gce.GCECloud } +var _ admission.MutationInterface = &persistentVolumeLabel{} var _ kubeapiserveradmission.WantsCloudConfig = &persistentVolumeLabel{} // NewPersistentVolumeLabel returns an admission.Interface implementation which adds labels to PersistentVolume CREATE requests, diff --git a/plugin/pkg/admission/persistentvolume/label/admission_test.go b/plugin/pkg/admission/persistentvolume/label/admission_test.go index 2094edd00ff..e349c756a4d 100644 --- a/plugin/pkg/admission/persistentvolume/label/admission_test.go +++ b/plugin/pkg/admission/persistentvolume/label/admission_test.go @@ -21,10 +21,11 @@ import ( "fmt" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/cloudprovider/providers/aws" ) @@ -67,6 +68,13 @@ func (c *mockVolumes) DisksAreAttached(nodeDisks map[types.NodeName][]aws.Kubern return nil, fmt.Errorf("not implemented") } +func (c *mockVolumes) ResizeDisk( + diskName aws.KubernetesVolumeID, + oldSize resource.Quantity, + newSize resource.Quantity) (resource.Quantity, error) { + return oldSize, nil +} + func mockVolumeFailure(err error) *mockVolumes { return &mockVolumes{volumeLabelsError: err} } diff --git a/plugin/pkg/admission/persistentvolume/resize/BUILD b/plugin/pkg/admission/persistentvolume/resize/BUILD index 078121a3fb9..95f1d3d5a64 100644 --- a/plugin/pkg/admission/persistentvolume/resize/BUILD +++ b/plugin/pkg/admission/persistentvolume/resize/BUILD @@ -9,10 +9,10 @@ load( go_test( name = "go_default_test", srcs = ["admission_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/resize", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/storage:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", "//pkg/controller:go_default_library", @@ -29,8 +29,8 @@ go_library( srcs = ["admission.go"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/resize", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", "//pkg/client/listers/core/internalversion:go_default_library", "//pkg/client/listers/storage/internalversion:go_default_library", diff --git a/plugin/pkg/admission/persistentvolume/resize/admission.go b/plugin/pkg/admission/persistentvolume/resize/admission.go index eb7b11cdac0..9909f5f3fb7 100644 --- a/plugin/pkg/admission/persistentvolume/resize/admission.go +++ b/plugin/pkg/admission/persistentvolume/resize/admission.go @@ -21,8 +21,8 @@ import ( "io" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" - apihelper "k8s.io/kubernetes/pkg/api/helper" + api "k8s.io/kubernetes/pkg/apis/core" + apihelper "k8s.io/kubernetes/pkg/apis/core/helper" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" pvlister "k8s.io/kubernetes/pkg/client/listers/core/internalversion" storagelisters "k8s.io/kubernetes/pkg/client/listers/storage/internalversion" @@ -43,6 +43,7 @@ func Register(plugins *admission.Plugins) { } var _ admission.Interface = &persistentVolumeClaimResize{} +var _ admission.ValidationInterface = &persistentVolumeClaimResize{} var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&persistentVolumeClaimResize{}) type persistentVolumeClaimResize struct { @@ -68,8 +69,8 @@ func (pvcr *persistentVolumeClaimResize) SetInternalKubeInformerFactory(f inform }) } -// Validate ensures lister is set. -func (pvcr *persistentVolumeClaimResize) Validate() error { +// ValidateInitialization ensures lister is set. +func (pvcr *persistentVolumeClaimResize) ValidateInitialization() error { if pvcr.pvLister == nil { return fmt.Errorf("missing persistent volume lister") } @@ -79,7 +80,7 @@ func (pvcr *persistentVolumeClaimResize) Validate() error { return nil } -func (pvcr *persistentVolumeClaimResize) Admit(a admission.Attributes) error { +func (pvcr *persistentVolumeClaimResize) Validate(a admission.Attributes) error { if a.GetResource().GroupResource() != api.Resource("persistentvolumeclaims") { return nil } @@ -148,9 +149,17 @@ func (pvcr *persistentVolumeClaimResize) allowResize(pvc, oldPvc *api.Persistent // checkVolumePlugin checks whether the volume plugin supports resize func (pvcr *persistentVolumeClaimResize) checkVolumePlugin(pv *api.PersistentVolume) bool { - if pv.Spec.Glusterfs != nil { + if pv.Spec.Glusterfs != nil || pv.Spec.Cinder != nil || pv.Spec.RBD != nil { return true } - return false + if pv.Spec.GCEPersistentDisk != nil { + return true + } + + if pv.Spec.AWSElasticBlockStore != nil { + return true + } + + return false } diff --git a/plugin/pkg/admission/persistentvolume/resize/admission_test.go b/plugin/pkg/admission/persistentvolume/resize/admission_test.go index 237b053822d..ab5e7817ba1 100644 --- a/plugin/pkg/admission/persistentvolume/resize/admission_test.go +++ b/plugin/pkg/admission/persistentvolume/resize/admission_test.go @@ -26,7 +26,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/storage" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" "k8s.io/kubernetes/pkg/controller" @@ -272,7 +272,7 @@ func TestPVCResizeAdmission(t *testing.T) { ctrl := newPlugin() informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) ctrl.SetInternalKubeInformerFactory(informerFactory) - err := ctrl.Validate() + err := ctrl.ValidateInitialization() if err != nil { t.Fatalf("neither pv lister nor storageclass lister can be nil") } @@ -323,7 +323,7 @@ func TestPVCResizeAdmission(t *testing.T) { operation := admission.Update attributes := admission.NewAttributesRecord(tc.newObj, tc.oldObj, schema.GroupVersionKind{}, metav1.NamespaceDefault, "foo", tc.resource, tc.subresource, operation, nil) - err := ctrl.Admit(attributes) + err := ctrl.Validate(attributes) fmt.Println(tc.name) fmt.Println(err) if !tc.checkError(err) { diff --git a/plugin/pkg/admission/persistentvolumeclaim/pvcprotection/BUILD b/plugin/pkg/admission/persistentvolumeclaim/pvcprotection/BUILD new file mode 100644 index 00000000000..c7cece04286 --- /dev/null +++ b/plugin/pkg/admission/persistentvolumeclaim/pvcprotection/BUILD @@ -0,0 +1,51 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["admission.go"], + importpath = "k8s.io/kubernetes/plugin/pkg/admission/persistentvolumeclaim/pvcprotection", + visibility = ["//visibility:public"], + deps = [ + "//pkg/apis/core:go_default_library", + "//pkg/client/informers/informers_generated/internalversion:go_default_library", + "//pkg/client/listers/core/internalversion:go_default_library", + "//pkg/features:go_default_library", + "//pkg/kubeapiserver/admission:go_default_library", + "//pkg/volume/util:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["admission_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/plugin/pkg/admission/persistentvolumeclaim/pvcprotection", + deps = [ + "//pkg/apis/core:go_default_library", + "//pkg/client/informers/informers_generated/internalversion:go_default_library", + "//pkg/controller:go_default_library", + "//pkg/volume/util:go_default_library", + "//vendor/github.com/davecgh/go-spew/spew:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/plugin/pkg/admission/persistentvolumeclaim/pvcprotection/admission.go b/plugin/pkg/admission/persistentvolumeclaim/pvcprotection/admission.go new file mode 100644 index 00000000000..218bca4b829 --- /dev/null +++ b/plugin/pkg/admission/persistentvolumeclaim/pvcprotection/admission.go @@ -0,0 +1,111 @@ +/* +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 pvcprotection + +import ( + "fmt" + "io" + + "github.com/golang/glog" + + admission "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/util/feature" + api "k8s.io/kubernetes/pkg/apis/core" + informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" + corelisters "k8s.io/kubernetes/pkg/client/listers/core/internalversion" + "k8s.io/kubernetes/pkg/features" + kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission" + volumeutil "k8s.io/kubernetes/pkg/volume/util" +) + +const ( + // PluginName is the name of this admission controller plugin + PluginName = "PVCProtection" +) + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { + plugin := newPlugin() + return plugin, nil + }) +} + +// pvcProtectionPlugin holds state for and implements the admission plugin. +type pvcProtectionPlugin struct { + *admission.Handler + lister corelisters.PersistentVolumeClaimLister +} + +var _ admission.Interface = &pvcProtectionPlugin{} +var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&pvcProtectionPlugin{}) + +// newPlugin creates a new admission plugin. +func newPlugin() *pvcProtectionPlugin { + return &pvcProtectionPlugin{ + Handler: admission.NewHandler(admission.Create), + } +} + +func (c *pvcProtectionPlugin) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) { + informer := f.Core().InternalVersion().PersistentVolumeClaims() + c.lister = informer.Lister() + c.SetReadyFunc(informer.Informer().HasSynced) +} + +// ValidateInitialization ensures lister is set. +func (c *pvcProtectionPlugin) ValidateInitialization() error { + if c.lister == nil { + return fmt.Errorf("missing lister") + } + return nil +} + +// Admit sets finalizer on all PVCs. The finalizer is removed by +// PVCProtectionController when it's not referenced by any pod. +// +// This prevents users from deleting a PVC that's used by a running pod. +func (c *pvcProtectionPlugin) Admit(a admission.Attributes) error { + if !feature.DefaultFeatureGate.Enabled(features.PVCProtection) { + return nil + } + + if a.GetResource().GroupResource() != api.Resource("persistentvolumeclaims") { + return nil + } + + if len(a.GetSubresource()) != 0 { + return nil + } + + pvc, ok := a.GetObject().(*api.PersistentVolumeClaim) + // if we can't convert then we don't handle this object so just return + if !ok { + return nil + } + + for _, f := range pvc.Finalizers { + if f == volumeutil.PVCProtectionFinalizer { + // Finalizer is already present, nothing to do + return nil + } + } + + glog.V(4).Infof("adding PVC protection finalizer to %s/%s", pvc.Namespace, pvc.Name) + pvc.Finalizers = append(pvc.Finalizers, volumeutil.PVCProtectionFinalizer) + return nil +} diff --git a/plugin/pkg/admission/persistentvolumeclaim/pvcprotection/admission_test.go b/plugin/pkg/admission/persistentvolumeclaim/pvcprotection/admission_test.go new file mode 100644 index 00000000000..0815c40615b --- /dev/null +++ b/plugin/pkg/admission/persistentvolumeclaim/pvcprotection/admission_test.go @@ -0,0 +1,106 @@ +/* +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 pvcprotection + +import ( + "fmt" + "reflect" + "testing" + + "github.com/davecgh/go-spew/spew" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/util/feature" + api "k8s.io/kubernetes/pkg/apis/core" + informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" + "k8s.io/kubernetes/pkg/controller" + volumeutil "k8s.io/kubernetes/pkg/volume/util" +) + +func TestAdmit(t *testing.T) { + claim := &api.PersistentVolumeClaim{ + TypeMeta: metav1.TypeMeta{ + Kind: "PersistentVolumeClaim", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "claim", + Namespace: "ns", + }, + } + claimWithFinalizer := claim.DeepCopy() + claimWithFinalizer.Finalizers = []string{volumeutil.PVCProtectionFinalizer} + + tests := []struct { + name string + object runtime.Object + expectedObject runtime.Object + featureEnabled bool + }{ + { + "create -> add finalizer", + claim, + claimWithFinalizer, + true, + }, + { + "finalizer already exists -> no new finalizer", + claimWithFinalizer, + claimWithFinalizer, + true, + }, + { + "disabled feature -> no finalizer", + claim, + claim, + false, + }, + } + + ctrl := newPlugin() + informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) + ctrl.SetInternalKubeInformerFactory(informerFactory) + + for _, test := range tests { + feature.DefaultFeatureGate.Set(fmt.Sprintf("PVCProtection=%v", test.featureEnabled)) + obj := test.object.DeepCopyObject() + attrs := admission.NewAttributesRecord( + obj, // new object + obj.DeepCopyObject(), // old object, copy to be sure it's not modified + api.Kind("PersistentVolumeClaim").WithVersion("version"), + claim.Namespace, + claim.Name, + api.Resource("persistentvolumeclaims").WithVersion("version"), + "", // subresource + admission.Create, + nil, // userInfo + ) + + err := ctrl.Admit(attrs) + if err != nil { + t.Errorf("Test %q: got unexpected error: %v", test.name, err) + } + if !reflect.DeepEqual(test.expectedObject, obj) { + t.Errorf("Test %q: Expected object:\n%s\ngot:\n%s", test.name, spew.Sdump(test.expectedObject), spew.Sdump(obj)) + } + } + + // Disable the feature for rest of the tests. + // TODO: remove after alpha + feature.DefaultFeatureGate.Set("PVCProtection=false") +} diff --git a/plugin/pkg/admission/podnodeselector/BUILD b/plugin/pkg/admission/podnodeselector/BUILD index efd60171267..289c0814501 100644 --- a/plugin/pkg/admission/podnodeselector/BUILD +++ b/plugin/pkg/admission/podnodeselector/BUILD @@ -11,7 +11,7 @@ go_library( srcs = ["admission.go"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/podnodeselector", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", "//pkg/client/listers/core/internalversion:go_default_library", @@ -29,10 +29,10 @@ go_library( go_test( name = "go_default_test", srcs = ["admission_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/podnodeselector", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", diff --git a/plugin/pkg/admission/podnodeselector/admission.go b/plugin/pkg/admission/podnodeselector/admission.go index 469286f1e9b..84bb1d6ef14 100644 --- a/plugin/pkg/admission/podnodeselector/admission.go +++ b/plugin/pkg/admission/podnodeselector/admission.go @@ -28,7 +28,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" corelisters "k8s.io/kubernetes/pkg/client/listers/core/internalversion" @@ -59,6 +59,8 @@ type podNodeSelector struct { clusterNodeSelectors map[string]string } +var _ admission.MutationInterface = &podNodeSelector{} +var _ admission.ValidationInterface = &podNodeSelector{} var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&podNodeSelector{}) var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&podNodeSelector{}) @@ -93,26 +95,9 @@ func readConfig(config io.Reader) *pluginConfig { // Admit enforces that pod and its namespace node label selectors matches at least a node in the cluster. func (p *podNodeSelector) Admit(a admission.Attributes) error { - resource := a.GetResource().GroupResource() - if resource != api.Resource("pods") { + if shouldIgnore(a) { return nil } - if a.GetSubresource() != "" { - // only run the checks below on pods proper and not subresources - return nil - } - - obj := a.GetObject() - pod, ok := obj.(*api.Pod) - if !ok { - glog.Errorf("expected pod but got %s", a.GetKind().Kind) - return nil - } - - if !p.WaitForReady() { - return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request")) - } - updateInitialized, err := util.IsUpdatingInitializedObject(a) if err != nil { return err @@ -121,51 +106,96 @@ func (p *podNodeSelector) Admit(a admission.Attributes) error { // node selector of an initialized pod is immutable return nil } - - name := pod.Name - nsName := a.GetNamespace() - var namespace *api.Namespace - - namespace, err = p.namespaceLister.Get(nsName) - if errors.IsNotFound(err) { - namespace, err = p.defaultGetNamespace(nsName) - if err != nil { - if errors.IsNotFound(err) { - return err - } - return errors.NewInternalError(err) - } - } else if err != nil { - return errors.NewInternalError(err) + if !p.WaitForReady() { + return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request")) } - namespaceNodeSelector, err := p.getNodeSelectorMap(namespace) + resource := a.GetResource().GroupResource() + pod := a.GetObject().(*api.Pod) + namespaceNodeSelector, err := p.getNamespaceNodeSelectorMap(a.GetNamespace()) if err != nil { return err } if labels.Conflicts(namespaceNodeSelector, labels.Set(pod.Spec.NodeSelector)) { - return errors.NewForbidden(resource, name, fmt.Errorf("pod node label selector conflicts with its namespace node label selector")) - } - - whitelist, err := labels.ConvertSelectorToLabelsMap(p.clusterNodeSelectors[namespace.Name]) - if err != nil { - return err + return errors.NewForbidden(resource, pod.Name, fmt.Errorf("pod node label selector conflicts with its namespace node label selector")) } // Merge pod node selector = namespace node selector + current pod node selector + // second selector wins podNodeSelectorLabels := labels.Merge(namespaceNodeSelector, pod.Spec.NodeSelector) + pod.Spec.NodeSelector = map[string]string(podNodeSelectorLabels) + return p.Validate(a) +} - // whitelist verification - if !labels.AreLabelsInWhiteList(podNodeSelectorLabels, whitelist) { - return errors.NewForbidden(resource, name, fmt.Errorf("pod node label selector labels conflict with its namespace whitelist")) +// Validate ensures that the pod node selector is allowed +func (p *podNodeSelector) Validate(a admission.Attributes) error { + if shouldIgnore(a) { + return nil + } + if !p.WaitForReady() { + return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request")) + } + + resource := a.GetResource().GroupResource() + pod := a.GetObject().(*api.Pod) + + namespaceNodeSelector, err := p.getNamespaceNodeSelectorMap(a.GetNamespace()) + if err != nil { + return err + } + if labels.Conflicts(namespaceNodeSelector, labels.Set(pod.Spec.NodeSelector)) { + return errors.NewForbidden(resource, pod.Name, fmt.Errorf("pod node label selector conflicts with its namespace node label selector")) + } + + // whitelist verification + whitelist, err := labels.ConvertSelectorToLabelsMap(p.clusterNodeSelectors[a.GetNamespace()]) + if err != nil { + return err + } + if !labels.AreLabelsInWhiteList(pod.Spec.NodeSelector, whitelist) { + return errors.NewForbidden(resource, pod.Name, fmt.Errorf("pod node label selector labels conflict with its namespace whitelist")) } - // Updated pod node selector = namespace node selector + current pod node selector - pod.Spec.NodeSelector = map[string]string(podNodeSelectorLabels) return nil } +func (p *podNodeSelector) getNamespaceNodeSelectorMap(namespaceName string) (labels.Set, error) { + namespace, err := p.namespaceLister.Get(namespaceName) + if errors.IsNotFound(err) { + namespace, err = p.defaultGetNamespace(namespaceName) + if err != nil { + if errors.IsNotFound(err) { + return nil, err + } + return nil, errors.NewInternalError(err) + } + } else if err != nil { + return nil, errors.NewInternalError(err) + } + + return p.getNodeSelectorMap(namespace) +} + +func shouldIgnore(a admission.Attributes) bool { + resource := a.GetResource().GroupResource() + if resource != api.Resource("pods") { + return true + } + if a.GetSubresource() != "" { + // only run the checks below on pods proper and not subresources + return true + } + + _, ok := a.GetObject().(*api.Pod) + if !ok { + glog.Errorf("expected pod but got %s", a.GetKind().Kind) + return true + } + + return false +} + func NewPodNodeSelector(clusterNodeSelectors map[string]string) *podNodeSelector { return &podNodeSelector{ Handler: admission.NewHandler(admission.Create, admission.Update), @@ -183,7 +213,7 @@ func (p *podNodeSelector) SetInternalKubeInformerFactory(f informers.SharedInfor p.SetReadyFunc(namespaceInformer.Informer().HasSynced) } -func (p *podNodeSelector) Validate() error { +func (p *podNodeSelector) ValidateInitialization() error { if p.namespaceLister == nil { return fmt.Errorf("missing namespaceLister") } diff --git a/plugin/pkg/admission/podnodeselector/admission_test.go b/plugin/pkg/admission/podnodeselector/admission_test.go index 09c6779b96e..2f77359317c 100644 --- a/plugin/pkg/admission/podnodeselector/admission_test.go +++ b/plugin/pkg/admission/podnodeselector/admission_test.go @@ -23,7 +23,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" @@ -175,6 +175,12 @@ func TestPodAdmission(t *testing.T) { if test.admit && !labels.Equals(test.mergedNodeSelector, labels.Set(pod.Spec.NodeSelector)) { t.Errorf("Test: %s, expected: %s but got: %s", test.testName, test.mergedNodeSelector, pod.Spec.NodeSelector) } + err = handler.Validate(admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "testNamespace", namespace.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) + if test.admit && err != nil { + t.Errorf("Test: %s, expected no error but got: %s", test.testName, err) + } else if !test.admit && err == nil { + t.Errorf("Test: %s, expected an error", test.testName) + } // handles update of uninitialized pod like it's newly created. err = handler.Admit(admission.NewAttributesRecord(pod, &oldPod, api.Kind("Pod").WithVersion("version"), "testNamespace", namespace.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Update, nil)) @@ -186,6 +192,12 @@ func TestPodAdmission(t *testing.T) { if test.admit && !labels.Equals(test.mergedNodeSelector, labels.Set(pod.Spec.NodeSelector)) { t.Errorf("Test: %s, expected: %s but got: %s", test.testName, test.mergedNodeSelector, pod.Spec.NodeSelector) } + err = handler.Validate(admission.NewAttributesRecord(pod, &oldPod, api.Kind("Pod").WithVersion("version"), "testNamespace", namespace.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Update, nil)) + if test.admit && err != nil { + t.Errorf("Test: %s, expected no error but got: %s", test.testName, err) + } else if !test.admit && err == nil { + t.Errorf("Test: %s, expected an error", test.testName) + } } } @@ -243,6 +255,6 @@ func newHandlerForTest(c clientset.Interface) (*podNodeSelector, informers.Share handler := NewPodNodeSelector(nil) pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil) pluginInitializer.Initialize(handler) - err := admission.Validate(handler) + err := admission.ValidateInitialization(handler) return handler, f, err } diff --git a/plugin/pkg/admission/podpreset/BUILD b/plugin/pkg/admission/podpreset/BUILD index 396807dcd16..862a5cbe6e6 100644 --- a/plugin/pkg/admission/podpreset/BUILD +++ b/plugin/pkg/admission/podpreset/BUILD @@ -9,10 +9,10 @@ load( go_test( name = "go_default_test", srcs = ["admission_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/podpreset", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/settings:go_default_library", "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", @@ -30,8 +30,9 @@ go_library( srcs = ["admission.go"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/podpreset", deps = [ - "//pkg/api:go_default_library", + "//pkg/api/legacyscheme:go_default_library", "//pkg/api/ref:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/settings:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", diff --git a/plugin/pkg/admission/podpreset/admission.go b/plugin/pkg/admission/podpreset/admission.go index 86bf707c8a4..30752a2666c 100644 --- a/plugin/pkg/admission/podpreset/admission.go +++ b/plugin/pkg/admission/podpreset/admission.go @@ -29,8 +29,9 @@ import ( "k8s.io/apimachinery/pkg/labels" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/ref" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/settings" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" @@ -58,6 +59,7 @@ type podPresetPlugin struct { lister settingslisters.PodPresetLister } +var _ admission.MutationInterface = &podPresetPlugin{} var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&podPresetPlugin{}) var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&podPresetPlugin{}) @@ -68,7 +70,7 @@ func NewPlugin() *podPresetPlugin { } } -func (plugin *podPresetPlugin) Validate() error { +func (plugin *podPresetPlugin) ValidateInitialization() error { if plugin.client == nil { return fmt.Errorf("%s requires a client", pluginName) } @@ -346,7 +348,7 @@ func mergeVolumes(volumes []api.Volume, podPresets []*settings.PodPreset) ([]api } func (c *podPresetPlugin) addEvent(pod *api.Pod, pip *settings.PodPreset, message string) { - ref, err := ref.GetReference(api.Scheme, pod) + ref, err := ref.GetReference(legacyscheme.Scheme, pod) if err != nil { glog.Errorf("pip %s: get reference for pod %s failed: %v", pip.GetName(), pod.GetName(), err) return diff --git a/plugin/pkg/admission/podpreset/admission_test.go b/plugin/pkg/admission/podpreset/admission_test.go index bede2f6ca26..af301ac51d8 100644 --- a/plugin/pkg/admission/podpreset/admission_test.go +++ b/plugin/pkg/admission/podpreset/admission_test.go @@ -25,7 +25,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" kadmission "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/authentication/user" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/settings" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" @@ -393,7 +393,7 @@ func TestMergeVolumes(t *testing.T) { // NewTestAdmission provides an admission plugin with test implementations of internal structs. It uses // an authorizer that always returns true. -func NewTestAdmission(lister settingslisters.PodPresetLister, objects ...runtime.Object) kadmission.Interface { +func NewTestAdmission(lister settingslisters.PodPresetLister, objects ...runtime.Object) kadmission.MutationInterface { // Build a test client that the admission plugin can use to look up the service account missing from its cache client := fake.NewSimpleClientset(objects...) diff --git a/plugin/pkg/admission/podtolerationrestriction/BUILD b/plugin/pkg/admission/podtolerationrestriction/BUILD index 6d16eb4dfeb..9efce635d34 100644 --- a/plugin/pkg/admission/podtolerationrestriction/BUILD +++ b/plugin/pkg/admission/podtolerationrestriction/BUILD @@ -9,17 +9,17 @@ load( go_test( name = "go_default_test", srcs = ["admission_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", "//pkg/kubeapiserver/admission:go_default_library", + "//pkg/scheduler/algorithm:go_default_library", "//pkg/util/tolerations:go_default_library", "//plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction:go_default_library", - "//plugin/pkg/scheduler/algorithm:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", @@ -35,20 +35,20 @@ go_library( ], importpath = "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper/qos:go_default_library", - "//pkg/api/v1:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper/qos:go_default_library", + "//pkg/apis/core/v1:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", "//pkg/client/listers/core/internalversion:go_default_library", "//pkg/kubeapiserver/admission:go_default_library", "//pkg/kubeapiserver/admission/util:go_default_library", + "//pkg/scheduler/algorithm:go_default_library", "//pkg/util/tolerations:go_default_library", "//plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction:go_default_library", "//plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/install:go_default_library", "//plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1:go_default_library", "//plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/validation:go_default_library", - "//plugin/pkg/scheduler/algorithm:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", diff --git a/plugin/pkg/admission/podtolerationrestriction/admission.go b/plugin/pkg/admission/podtolerationrestriction/admission.go index 655a2e365a0..3318e221b2c 100644 --- a/plugin/pkg/admission/podtolerationrestriction/admission.go +++ b/plugin/pkg/admission/podtolerationrestriction/admission.go @@ -27,17 +27,18 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" - qoshelper "k8s.io/kubernetes/pkg/api/helper/qos" - k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" + api "k8s.io/kubernetes/pkg/apis/core" + qoshelper "k8s.io/kubernetes/pkg/apis/core/helper/qos" + k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" corelisters "k8s.io/kubernetes/pkg/client/listers/core/internalversion" kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission" "k8s.io/kubernetes/pkg/kubeapiserver/admission/util" + "k8s.io/kubernetes/pkg/scheduler/algorithm" "k8s.io/kubernetes/pkg/util/tolerations" pluginapi "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" + pluginapiv1alpha1 "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1" ) // Register registers a plugin @@ -49,6 +50,9 @@ func Register(plugins *admission.Plugins) { } return NewPodTolerationsPlugin(pluginConfig), nil }) + // add our config types + pluginapi.AddToScheme(plugins.ConfigScheme) + pluginapiv1alpha1.AddToScheme(plugins.ConfigScheme) } // The annotation keys for default and whitelist of tolerations @@ -57,6 +61,8 @@ const ( NSWLTolerations string = "scheduler.alpha.kubernetes.io/tolerationsWhitelist" ) +var _ admission.MutationInterface = &podTolerationsPlugin{} +var _ admission.ValidationInterface = &podTolerationsPlugin{} var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&podTolerationsPlugin{}) type podTolerationsPlugin struct { @@ -77,19 +83,7 @@ type podTolerationsPlugin struct { // scheduler.alpha.kubernetes.io/defaultTolerations and scheduler.alpha.kubernetes.io/tolerationsWhitelist // annotations keys. func (p *podTolerationsPlugin) Admit(a admission.Attributes) error { - resource := a.GetResource().GroupResource() - if resource != api.Resource("pods") { - return nil - } - if a.GetSubresource() != "" { - // only run the checks below on pods proper and not subresources - return nil - } - - obj := a.GetObject() - pod, ok := obj.(*api.Pod) - if !ok { - glog.Errorf("expected pod but got %s", a.GetKind().Kind) + if shouldIgnore(a) { return nil } @@ -97,35 +91,21 @@ func (p *podTolerationsPlugin) Admit(a admission.Attributes) error { return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request")) } - nsName := a.GetNamespace() - namespace, err := p.namespaceLister.Get(nsName) - if errors.IsNotFound(err) { - // in case of latency in our caches, make a call direct to storage to verify that it truly exists or not - namespace, err = p.client.Core().Namespaces().Get(nsName, metav1.GetOptions{}) - if err != nil { - if errors.IsNotFound(err) { - return err - } - return errors.NewInternalError(err) - } - } else if err != nil { - return errors.NewInternalError(err) - } - + pod := a.GetObject().(*api.Pod) var finalTolerations []api.Toleration updateUninitialized, err := util.IsUpdatingUninitializedObject(a) if err != nil { return err } if a.GetOperation() == admission.Create || updateUninitialized { - ts, err := p.getNamespaceDefaultTolerations(namespace) + ts, err := p.getNamespaceDefaultTolerations(a.GetNamespace()) if err != nil { return err } // If the namespace has not specified its default tolerations, // fall back to cluster's default tolerations. - if len(ts) == 0 { + if ts == nil { ts = p.pluginConfig.Default } @@ -148,27 +128,6 @@ func (p *podTolerationsPlugin) Admit(a admission.Attributes) error { finalTolerations = pod.Spec.Tolerations } - // whitelist verification. - if len(finalTolerations) > 0 { - whitelist, err := p.getNamespaceTolerationsWhitelist(namespace) - if err != nil { - return err - } - - // If the namespace has not specified its tolerations whitelist, - // fall back to cluster's whitelist of tolerations. - if len(whitelist) == 0 { - whitelist = p.pluginConfig.Whitelist - } - - if len(whitelist) > 0 { - // check if the merged pod tolerations satisfy its namespace whitelist - if !tolerations.VerifyAgainstWhitelist(finalTolerations, whitelist) { - return fmt.Errorf("pod tolerations (possibly merged with namespace default tolerations) conflict with its namespace whitelist") - } - } - } - if qoshelper.GetPodQOS(pod) != api.PodQOSBestEffort { finalTolerations = tolerations.MergeTolerations(finalTolerations, []api.Toleration{ { @@ -178,10 +137,62 @@ func (p *podTolerationsPlugin) Admit(a admission.Attributes) error { }, }) } - pod.Spec.Tolerations = finalTolerations - return nil + return p.Validate(a) +} +func (p *podTolerationsPlugin) Validate(a admission.Attributes) error { + if shouldIgnore(a) { + return nil + } + + if !p.WaitForReady() { + return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request")) + } + + // whitelist verification. + pod := a.GetObject().(*api.Pod) + if len(pod.Spec.Tolerations) > 0 { + whitelist, err := p.getNamespaceTolerationsWhitelist(a.GetNamespace()) + if err != nil { + return err + } + + // If the namespace has not specified its tolerations whitelist, + // fall back to cluster's whitelist of tolerations. + if whitelist == nil { + whitelist = p.pluginConfig.Whitelist + } + + if len(whitelist) > 0 { + // check if the merged pod tolerations satisfy its namespace whitelist + if !tolerations.VerifyAgainstWhitelist(pod.Spec.Tolerations, whitelist) { + return fmt.Errorf("pod tolerations (possibly merged with namespace default tolerations) conflict with its namespace whitelist") + } + } + } + + return nil +} + +func shouldIgnore(a admission.Attributes) bool { + resource := a.GetResource().GroupResource() + if resource != api.Resource("pods") { + return true + } + if a.GetSubresource() != "" { + // only run the checks below on pods proper and not subresources + return true + } + + obj := a.GetObject() + _, ok := obj.(*api.Pod) + if !ok { + glog.Errorf("expected pod but got %s", a.GetKind().Kind) + return true + } + + return false } func NewPodTolerationsPlugin(pluginConfig *pluginapi.Configuration) *podTolerationsPlugin { @@ -202,7 +213,7 @@ func (p *podTolerationsPlugin) SetInternalKubeInformerFactory(f informers.Shared } -func (p *podTolerationsPlugin) Validate() error { +func (p *podTolerationsPlugin) ValidateInitialization() error { if p.namespaceLister == nil { return fmt.Errorf("missing namespaceLister") } @@ -212,26 +223,72 @@ func (p *podTolerationsPlugin) Validate() error { return nil } -func (p *podTolerationsPlugin) getNamespaceDefaultTolerations(ns *api.Namespace) ([]api.Toleration, error) { +// in exceptional cases, this can result in two live calls, but once the cache catches up, that will stop. +func (p *podTolerationsPlugin) getNamespace(nsName string) (*api.Namespace, error) { + namespace, err := p.namespaceLister.Get(nsName) + if errors.IsNotFound(err) { + // in case of latency in our caches, make a call direct to storage to verify that it truly exists or not + namespace, err = p.client.Core().Namespaces().Get(nsName, metav1.GetOptions{}) + if err != nil { + if errors.IsNotFound(err) { + return nil, err + } + return nil, errors.NewInternalError(err) + } + } else if err != nil { + return nil, errors.NewInternalError(err) + } + + return namespace, nil +} + +func (p *podTolerationsPlugin) getNamespaceDefaultTolerations(nsName string) ([]api.Toleration, error) { + ns, err := p.getNamespace(nsName) + if err != nil { + return nil, err + } return extractNSTolerations(ns, NSDefaultTolerations) } -func (p *podTolerationsPlugin) getNamespaceTolerationsWhitelist(ns *api.Namespace) ([]api.Toleration, error) { +func (p *podTolerationsPlugin) getNamespaceTolerationsWhitelist(nsName string) ([]api.Toleration, error) { + ns, err := p.getNamespace(nsName) + if err != nil { + return nil, err + } return extractNSTolerations(ns, NSWLTolerations) } +// extractNSTolerations extracts default or whitelist of tolerations from +// following namespace annotations keys: "scheduler.alpha.kubernetes.io/defaultTolerations" +// and "scheduler.alpha.kubernetes.io/tolerationsWhitelist". If these keys are +// unset (nil), extractNSTolerations returns nil. If the value to these +// keys are set to empty, an empty toleration is returned, otherwise +// configured tolerations are returned. func extractNSTolerations(ns *api.Namespace, key string) ([]api.Toleration, error) { + // if a namespace does not have any annotations + if len(ns.Annotations) == 0 { + return nil, nil + } + + // if NSWLTolerations or NSDefaultTolerations does not exist + if _, ok := ns.Annotations[key]; !ok { + return nil, nil + } + + // if value is set to empty + if len(ns.Annotations[key]) == 0 { + return []api.Toleration{}, nil + } + var v1Tolerations []v1.Toleration - if len(ns.Annotations) > 0 && ns.Annotations[key] != "" { - err := json.Unmarshal([]byte(ns.Annotations[key]), &v1Tolerations) - if err != nil { - return nil, err - } + err := json.Unmarshal([]byte(ns.Annotations[key]), &v1Tolerations) + if err != nil { + return nil, err } ts := make([]api.Toleration, len(v1Tolerations)) for i := range v1Tolerations { - if err := k8s_api_v1.Convert_v1_Toleration_To_api_Toleration(&v1Tolerations[i], &ts[i], nil); err != nil { + if err := k8s_api_v1.Convert_v1_Toleration_To_core_Toleration(&v1Tolerations[i], &ts[i], nil); err != nil { return nil, err } } diff --git a/plugin/pkg/admission/podtolerationrestriction/admission_test.go b/plugin/pkg/admission/podtolerationrestriction/admission_test.go index d95dd5d374f..1a60e5f2134 100644 --- a/plugin/pkg/admission/podtolerationrestriction/admission_test.go +++ b/plugin/pkg/admission/podtolerationrestriction/admission_test.go @@ -25,14 +25,14 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apiserver/pkg/admission" utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" kubeadmission "k8s.io/kubernetes/pkg/kubeapiserver/admission" + "k8s.io/kubernetes/pkg/scheduler/algorithm" "k8s.io/kubernetes/pkg/util/tolerations" pluginapi "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" ) // TestPodAdmission verifies various scenarios involving pod/namespace tolerations @@ -115,11 +115,11 @@ func TestPodAdmission(t *testing.T) { { pod: bestEffortPod, defaultClusterTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}}, - namespaceTolerations: []api.Toleration{}, + namespaceTolerations: nil, podTolerations: []api.Toleration{}, mergedTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}}, admit: true, - testName: "default cluster tolerations with empty pod tolerations", + testName: "default cluster tolerations with empty pod tolerations and nil namespace tolerations", }, { pod: bestEffortPod, @@ -161,8 +161,9 @@ func TestPodAdmission(t *testing.T) { defaultClusterTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue2", Effect: "NoSchedule", TolerationSeconds: nil}}, namespaceTolerations: []api.Toleration{}, podTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue1", Effect: "NoSchedule", TolerationSeconds: nil}}, - admit: false, - testName: "conflicting pod and default cluster tolerations", + mergedTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue1", Effect: "NoSchedule", TolerationSeconds: nil}}, + admit: true, + testName: "conflicting pod and default cluster tolerations but overridden by empty namespace tolerations", }, { pod: bestEffortPod, @@ -174,6 +175,24 @@ func TestPodAdmission(t *testing.T) { admit: true, testName: "merged pod tolerations satisfy whitelist", }, + { + pod: bestEffortPod, + defaultClusterTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}}, + namespaceTolerations: []api.Toleration{}, + podTolerations: []api.Toleration{}, + mergedTolerations: []api.Toleration{}, + admit: true, + testName: "Override default cluster toleration by empty namespace level toleration", + }, + { + pod: bestEffortPod, + whitelist: []api.Toleration{}, + clusterWhitelist: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue1", Effect: "NoSchedule", TolerationSeconds: nil}}, + podTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}}, + mergedTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}}, + admit: true, + testName: "pod toleration conflicts with default cluster white list which is overridden by empty namespace whitelist", + }, { pod: bestEffortPod, defaultClusterTolerations: []api.Toleration{}, @@ -211,7 +230,7 @@ func TestPodAdmission(t *testing.T) { }, } for _, test := range tests { - if len(test.namespaceTolerations) > 0 { + if test.namespaceTolerations != nil { tolerationStr, err := json.Marshal(test.namespaceTolerations) if err != nil { t.Errorf("error in marshalling namespace tolerations %v", test.namespaceTolerations) @@ -219,7 +238,7 @@ func TestPodAdmission(t *testing.T) { namespace.Annotations = map[string]string{NSDefaultTolerations: string(tolerationStr)} } - if len(test.whitelist) > 0 { + if test.whitelist != nil { tolerationStr, err := json.Marshal(test.whitelist) if err != nil { t.Errorf("error in marshalling namespace whitelist %v", test.whitelist) @@ -344,6 +363,6 @@ func newHandlerForTest(c clientset.Interface) (*podTolerationsPlugin, informers. handler := NewPodTolerationsPlugin(pluginConfig) pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil) pluginInitializer.Initialize(handler) - err = admission.Validate(handler) + err = admission.ValidateInitialization(handler) return handler, f, err } diff --git a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/BUILD b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/BUILD index 526c88cbad9..ef58e5e1fb9 100644 --- a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/BUILD +++ b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/BUILD @@ -15,9 +15,8 @@ go_library( ], importpath = "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/doc.go b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/doc.go index 093d630a723..1ea05752d4e 100644 --- a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/doc.go +++ b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/doc.go @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package package podtolerationrestriction // import "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction" diff --git a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/types.go b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/types.go index 28ae2eae677..d38c6e0613e 100644 --- a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/types.go +++ b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/types.go @@ -18,7 +18,7 @@ package podtolerationrestriction import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/BUILD b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/BUILD index 5b81f25db55..9a48ebb8c00 100644 --- a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/BUILD +++ b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/BUILD @@ -18,7 +18,7 @@ go_library( ], importpath = "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/doc.go b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/doc.go index 3640b251c3f..c21764baee8 100644 --- a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/doc.go +++ b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:conversion-gen=k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction // +k8s:defaulter-gen=TypeMeta diff --git a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/zz_generated.conversion.go b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/zz_generated.conversion.go index 8a44886b82c..36414a7425c 100644 --- a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/zz_generated.conversion.go +++ b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,12 +21,13 @@ limitations under the License. package v1alpha1 import ( + unsafe "unsafe" + v1 "k8s.io/api/core/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/apis/core" podtolerationrestriction "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction" - unsafe "unsafe" ) func init() { @@ -43,8 +44,8 @@ func RegisterConversions(scheme *runtime.Scheme) error { } func autoConvert_v1alpha1_Configuration_To_podtolerationrestriction_Configuration(in *Configuration, out *podtolerationrestriction.Configuration, s conversion.Scope) error { - out.Default = *(*[]api.Toleration)(unsafe.Pointer(&in.Default)) - out.Whitelist = *(*[]api.Toleration)(unsafe.Pointer(&in.Whitelist)) + out.Default = *(*[]core.Toleration)(unsafe.Pointer(&in.Default)) + out.Whitelist = *(*[]core.Toleration)(unsafe.Pointer(&in.Whitelist)) return nil } diff --git a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/zz_generated.deepcopy.go b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/zz_generated.deepcopy.go index 3f9e04a1d05..71857b1199a 100644 --- a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/zz_generated.deepcopy.go +++ b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,28 +22,9 @@ package v1alpha1 import ( v1 "k8s.io/api/core/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Configuration).DeepCopyInto(out.(*Configuration)) - return nil - }, InType: reflect.TypeOf(&Configuration{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Configuration) DeepCopyInto(out *Configuration) { *out = *in diff --git a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/zz_generated.defaults.go b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/zz_generated.defaults.go index 7e6df29d4ae..5e24d22cacd 100644 --- a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/zz_generated.defaults.go +++ b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/validation/BUILD b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/validation/BUILD index 7ef5f8b7668..ee21ec69553 100644 --- a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/validation/BUILD +++ b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/validation/BUILD @@ -11,7 +11,7 @@ go_library( srcs = ["validation.go"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/validation", deps = [ - "//pkg/api/validation:go_default_library", + "//pkg/apis/core/validation:go_default_library", "//plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", ], @@ -33,10 +33,10 @@ filegroup( go_test( name = "go_default_test", srcs = ["validation_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/validation", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction:go_default_library", ], ) diff --git a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/validation/validation.go b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/validation/validation.go index 6312ed12e31..99a36ed3aaf 100644 --- a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/validation/validation.go +++ b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/validation/validation.go @@ -20,7 +20,7 @@ import ( "fmt" "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/apis/core/validation" internalapi "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction" ) diff --git a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/validation/validation_test.go b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/validation/validation_test.go index da41e21dcf6..d444921a51a 100644 --- a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/validation/validation_test.go +++ b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/validation/validation_test.go @@ -17,10 +17,9 @@ limitations under the License. package validation import ( - "testing" - - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" internalapi "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction" + "testing" ) func TestValidateConfiguration(t *testing.T) { diff --git a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/zz_generated.deepcopy.go b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/zz_generated.deepcopy.go index 8760a045b0f..9f45f4afb3f 100644 --- a/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/zz_generated.deepcopy.go +++ b/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,43 +21,24 @@ limitations under the License. package podtolerationrestriction import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - api "k8s.io/kubernetes/pkg/api" - reflect "reflect" + core "k8s.io/kubernetes/pkg/apis/core" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Configuration).DeepCopyInto(out.(*Configuration)) - return nil - }, InType: reflect.TypeOf(&Configuration{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Configuration) DeepCopyInto(out *Configuration) { *out = *in out.TypeMeta = in.TypeMeta if in.Default != nil { in, out := &in.Default, &out.Default - *out = make([]api.Toleration, len(*in)) + *out = make([]core.Toleration, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } if in.Whitelist != nil { in, out := &in.Whitelist, &out.Whitelist - *out = make([]api.Toleration, len(*in)) + *out = make([]core.Toleration, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } diff --git a/plugin/pkg/admission/priority/BUILD b/plugin/pkg/admission/priority/BUILD index 56360c9a751..753e5bf2096 100644 --- a/plugin/pkg/admission/priority/BUILD +++ b/plugin/pkg/admission/priority/BUILD @@ -9,10 +9,10 @@ load( go_test( name = "go_default_test", srcs = ["admission_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/priority", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/scheduling:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", "//pkg/controller:go_default_library", @@ -29,7 +29,7 @@ go_library( srcs = ["admission.go"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/priority", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/scheduling:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", diff --git a/plugin/pkg/admission/priority/admission.go b/plugin/pkg/admission/priority/admission.go index 517058e2404..a06392c4d5c 100644 --- a/plugin/pkg/admission/priority/admission.go +++ b/plugin/pkg/admission/priority/admission.go @@ -24,7 +24,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apiserver/pkg/admission" utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/scheduling" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" @@ -55,8 +55,8 @@ func Register(plugins *admission.Plugins) { }) } -// priorityPlugin is an implementation of admission.Interface. -type priorityPlugin struct { +// PriorityPlugin is an implementation of admission.Interface. +type PriorityPlugin struct { *admission.Handler client internalclientset.Interface lister schedulinglisters.PriorityClassLister @@ -64,17 +64,20 @@ type priorityPlugin struct { globalDefaultPriority *int32 } -var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&priorityPlugin{}) -var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&priorityPlugin{}) +var _ admission.MutationInterface = &PriorityPlugin{} +var _ admission.ValidationInterface = &PriorityPlugin{} +var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&PriorityPlugin{}) +var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&PriorityPlugin{}) // NewPlugin creates a new priority admission plugin. -func NewPlugin() admission.Interface { - return &priorityPlugin{ +func NewPlugin() *PriorityPlugin { + return &PriorityPlugin{ Handler: admission.NewHandler(admission.Create, admission.Update, admission.Delete), } } -func (p *priorityPlugin) Validate() error { +// ValidateInitialization implements the InitializationValidator interface. +func (p *PriorityPlugin) ValidateInitialization() error { if p.client == nil { return fmt.Errorf("%s requires a client", pluginName) } @@ -84,11 +87,13 @@ func (p *priorityPlugin) Validate() error { return nil } -func (p *priorityPlugin) SetInternalKubeClientSet(client internalclientset.Interface) { +// SetInternalKubeClientSet implements the WantsInternalKubeClientSet interface. +func (p *PriorityPlugin) SetInternalKubeClientSet(client internalclientset.Interface) { p.client = client } -func (p *priorityPlugin) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) { +// SetInternalKubeInformerFactory implements the WantsInternalKubeInformerFactory interface. +func (p *PriorityPlugin) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) { priorityInformer := f.Scheduling().InternalVersion().PriorityClasses() p.lister = priorityInformer.Lister() p.SetReadyFunc(priorityInformer.Informer().HasSynced) @@ -96,28 +101,42 @@ func (p *priorityPlugin) SetInternalKubeInformerFactory(f informers.SharedInform var ( podResource = api.Resource("pods") - priorityClassResource = api.Resource("priorityclasses") + priorityClassResource = scheduling.Resource("priorityclasses") ) -// Admit checks Pods and PriorityClasses and admits or rejects them. It also resolves the priority of pods based on their PriorityClass. -func (p *priorityPlugin) Admit(a admission.Attributes) error { +// 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. +func (p *PriorityPlugin) Admit(a admission.Attributes) error { operation := a.GetOperation() - // Ignore all calls to subresources or resources other than pods. - // Ignore all operations other than Create and Update. + // Ignore all calls to subresources if len(a.GetSubresource()) != 0 { return nil } switch a.GetResource().GroupResource() { case podResource: - if operation == admission.Create || operation == admission.Update { + if operation == admission.Create { return p.admitPod(a) } return nil + default: + return nil + } +} + +// Validate checks PriorityClasses and admits or rejects them. +func (p *PriorityPlugin) Validate(a admission.Attributes) error { + operation := a.GetOperation() + // Ignore all calls to subresources + if len(a.GetSubresource()) != 0 { + return nil + } + + switch a.GetResource().GroupResource() { case priorityClassResource: if operation == admission.Create || operation == admission.Update { - return p.admitPriorityClass(a) + return p.validatePriorityClass(a) } if operation == admission.Delete { p.invalidateCachedDefaultPriority() @@ -131,8 +150,7 @@ func (p *priorityPlugin) Admit(a admission.Attributes) error { } // admitPod makes sure a new pod does not set spec.Priority field. It also makes sure that the PriorityClassName exists if it is provided and resolves the pod priority from the PriorityClassName. -// Note that pod validation mechanism prevents update of a pod priority. -func (p *priorityPlugin) admitPod(a admission.Attributes) error { +func (p *PriorityPlugin) admitPod(a admission.Attributes) error { operation := a.GetOperation() pod, ok := a.GetObject().(*api.Pod) if !ok { @@ -159,12 +177,15 @@ func (p *priorityPlugin) admitPod(a admission.Attributes) error { if !ok { // Now that we didn't find any system priority, try resolving by user defined priority classes. pc, err := p.lister.Get(pod.Spec.PriorityClassName) + if err != nil { - return fmt.Errorf("failed to get default priority class %s: %v", pod.Spec.PriorityClassName, err) - } - if pc == nil { - return admission.NewForbidden(a, fmt.Errorf("no PriorityClass with name %v was found", pod.Spec.PriorityClassName)) + 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 } } @@ -173,8 +194,8 @@ func (p *priorityPlugin) admitPod(a admission.Attributes) error { return nil } -// admitPriorityClass 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 *priorityPlugin) admitPriorityClass(a admission.Attributes) error { +// 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 *PriorityPlugin) validatePriorityClass(a admission.Attributes) error { operation := a.GetOperation() pc, ok := a.GetObject().(*scheduling.PriorityClass) if !ok { @@ -204,7 +225,7 @@ func (p *priorityPlugin) admitPriorityClass(a admission.Attributes) error { return nil } -func (p *priorityPlugin) getDefaultPriorityClass() (*scheduling.PriorityClass, error) { +func (p *PriorityPlugin) getDefaultPriorityClass() (*scheduling.PriorityClass, error) { list, err := p.lister.List(labels.Everything()) if err != nil { return nil, err @@ -217,7 +238,7 @@ func (p *priorityPlugin) getDefaultPriorityClass() (*scheduling.PriorityClass, e return nil, nil } -func (p *priorityPlugin) getDefaultPriority() (int32, error) { +func (p *PriorityPlugin) getDefaultPriority() (int32, error) { // If global default priority is cached, return it. if p.globalDefaultPriority != nil { return *p.globalDefaultPriority, nil @@ -236,6 +257,6 @@ func (p *priorityPlugin) getDefaultPriority() (int32, error) { } // invalidateCachedDefaultPriority sets global default priority to nil to indicate that it should be looked up again. -func (p *priorityPlugin) invalidateCachedDefaultPriority() { +func (p *PriorityPlugin) invalidateCachedDefaultPriority() { p.globalDefaultPriority = nil } diff --git a/plugin/pkg/admission/priority/admission_test.go b/plugin/pkg/admission/priority/admission_test.go index b1f48ca54e6..cf159efffdd 100644 --- a/plugin/pkg/admission/priority/admission_test.go +++ b/plugin/pkg/admission/priority/admission_test.go @@ -25,14 +25,14 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apiserver/pkg/admission" utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/scheduling" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/features" ) -func addPriorityClasses(ctrl *priorityPlugin, priorityClasses []*scheduling.PriorityClass) { +func addPriorityClasses(ctrl *PriorityPlugin, priorityClasses []*scheduling.PriorityClass) { informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) ctrl.SetInternalKubeInformerFactory(informerFactory) // First add the existing classes to the cache. @@ -132,22 +132,22 @@ func TestPriorityClassAdmission(t *testing.T) { for _, test := range tests { glog.V(4).Infof("starting test %q", test.name) - ctrl := NewPlugin().(*priorityPlugin) + ctrl := NewPlugin() // Add existing priority classes. addPriorityClasses(ctrl, test.existingClasses) // Now add the new class. attrs := admission.NewAttributesRecord( test.newClass, nil, - api.Kind("PriorityClass").WithVersion("version"), + scheduling.Kind("PriorityClass").WithVersion("version"), "", "", - api.Resource("priorityclasses").WithVersion("version"), + scheduling.Resource("priorityclasses").WithVersion("version"), "", admission.Create, nil, ) - err := ctrl.Admit(attrs) + err := ctrl.Validate(attrs) glog.Infof("Got %v", err) if err != nil && !test.expectError { t.Errorf("Test %q: unexpected error received: %v", test.name, err) @@ -160,8 +160,8 @@ func TestPriorityClassAdmission(t *testing.T) { // TestDefaultPriority tests that default priority is resolved correctly. func TestDefaultPriority(t *testing.T) { - pcResource := api.Resource("priorityclasses").WithVersion("version") - pcKind := api.Kind("PriorityClass").WithVersion("version") + pcResource := scheduling.Resource("priorityclasses").WithVersion("version") + pcKind := scheduling.Kind("PriorityClass").WithVersion("version") updatedDefaultClass1 := *defaultClass1 updatedDefaultClass1.GlobalDefault = false @@ -209,7 +209,7 @@ func TestDefaultPriority(t *testing.T) { for _, test := range tests { glog.V(4).Infof("starting test %q", test.name) - ctrl := NewPlugin().(*priorityPlugin) + ctrl := NewPlugin() addPriorityClasses(ctrl, test.classesBefore) defaultPriority, err := ctrl.getDefaultPriority() if err != nil { @@ -219,7 +219,7 @@ func TestDefaultPriority(t *testing.T) { t.Errorf("Test %q: expected default priority %d, but got %d", test.name, test.expectedDefaultBefore, defaultPriority) } if test.attributes != nil { - err := ctrl.Admit(test.attributes) + err := ctrl.Validate(test.attributes) if err != nil { t.Errorf("Test %q: unexpected error received: %v", test.name, err) } @@ -383,7 +383,7 @@ func TestPodAdmission(t *testing.T) { for _, test := range tests { glog.V(4).Infof("starting test %q", test.name) - ctrl := NewPlugin().(*priorityPlugin) + ctrl := NewPlugin() // Add existing priority classes. addPriorityClasses(ctrl, test.existingClasses) diff --git a/plugin/pkg/admission/resourcequota/BUILD b/plugin/pkg/admission/resourcequota/BUILD index c2c0e0f4724..b51287670b4 100644 --- a/plugin/pkg/admission/resourcequota/BUILD +++ b/plugin/pkg/admission/resourcequota/BUILD @@ -17,12 +17,13 @@ go_library( ], importpath = "k8s.io/kubernetes/plugin/pkg/admission/resourcequota", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", "//pkg/client/listers/core/internalversion:go_default_library", "//pkg/kubeapiserver/admission:go_default_library", "//pkg/quota:go_default_library", + "//pkg/quota/generic:go_default_library", "//pkg/util/reflector/prometheus:go_default_library", "//pkg/util/workqueue/prometheus:go_default_library", "//plugin/pkg/admission/resourcequota/apis/resourcequota:go_default_library", @@ -51,21 +52,18 @@ go_library( go_test( name = "go_default_test", srcs = ["admission_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/resourcequota", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", "//pkg/controller:go_default_library", - "//pkg/quota:go_default_library", - "//pkg/quota/generic:go_default_library", "//pkg/quota/install:go_default_library", "//plugin/pkg/admission/resourcequota/apis/resourcequota:go_default_library", "//vendor/github.com/hashicorp/golang-lru:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", diff --git a/plugin/pkg/admission/resourcequota/admission.go b/plugin/pkg/admission/resourcequota/admission.go index cfb97af50b8..24f8b6354b9 100644 --- a/plugin/pkg/admission/resourcequota/admission.go +++ b/plugin/pkg/admission/resourcequota/admission.go @@ -22,12 +22,13 @@ import ( "time" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission" "k8s.io/kubernetes/pkg/quota" resourcequotaapi "k8s.io/kubernetes/plugin/pkg/admission/resourcequota/apis/resourcequota" + resourcequotaapiv1alpha1 "k8s.io/kubernetes/plugin/pkg/admission/resourcequota/apis/resourcequota/v1alpha1" "k8s.io/kubernetes/plugin/pkg/admission/resourcequota/apis/resourcequota/validation" ) @@ -48,21 +49,26 @@ func Register(plugins *admission.Plugins) { } return NewResourceQuota(configuration, 5, make(chan struct{})) }) + + // add our config types + resourcequotaapi.AddToScheme(plugins.ConfigScheme) + resourcequotaapiv1alpha1.AddToScheme(plugins.ConfigScheme) } -// quotaAdmission implements an admission controller that can enforce quota constraints -type quotaAdmission struct { +// QuotaAdmission implements an admission controller that can enforce quota constraints +type QuotaAdmission struct { *admission.Handler - config *resourcequotaapi.Configuration - stopCh <-chan struct{} - registry quota.Registry - numEvaluators int - quotaAccessor *quotaAccessor - evaluator Evaluator + config *resourcequotaapi.Configuration + stopCh <-chan struct{} + quotaConfiguration quota.Configuration + numEvaluators int + quotaAccessor *quotaAccessor + evaluator Evaluator } -var _ = kubeapiserveradmission.WantsInternalKubeClientSet("aAdmission{}) -var _ = kubeapiserveradmission.WantsQuotaRegistry("aAdmission{}) +var _ admission.ValidationInterface = &QuotaAdmission{} +var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&QuotaAdmission{}) +var _ = kubeapiserveradmission.WantsQuotaConfiguration(&QuotaAdmission{}) type liveLookupEntry struct { expiry time.Time @@ -72,13 +78,13 @@ type liveLookupEntry struct { // NewResourceQuota configures an admission controller that can enforce quota constraints // using the provided registry. The registry must have the capability to handle group/kinds that // are persisted by the server this admission controller is intercepting -func NewResourceQuota(config *resourcequotaapi.Configuration, numEvaluators int, stopCh <-chan struct{}) (admission.Interface, error) { +func NewResourceQuota(config *resourcequotaapi.Configuration, numEvaluators int, stopCh <-chan struct{}) (*QuotaAdmission, error) { quotaAccessor, err := newQuotaAccessor() if err != nil { return nil, err } - return "aAdmission{ + return &QuotaAdmission{ Handler: admission.NewHandler(admission.Create, admission.Update), stopCh: stopCh, numEvaluators: numEvaluators, @@ -87,21 +93,21 @@ func NewResourceQuota(config *resourcequotaapi.Configuration, numEvaluators int, }, nil } -func (a *quotaAdmission) SetInternalKubeClientSet(client internalclientset.Interface) { +func (a *QuotaAdmission) SetInternalKubeClientSet(client internalclientset.Interface) { a.quotaAccessor.client = client } -func (a *quotaAdmission) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) { +func (a *QuotaAdmission) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) { a.quotaAccessor.lister = f.Core().InternalVersion().ResourceQuotas().Lister() } -func (a *quotaAdmission) SetQuotaRegistry(r quota.Registry) { - a.registry = r - a.evaluator = NewQuotaEvaluator(a.quotaAccessor, a.registry, nil, a.config, a.numEvaluators, a.stopCh) +func (a *QuotaAdmission) SetQuotaConfiguration(c quota.Configuration) { + a.quotaConfiguration = c + a.evaluator = NewQuotaEvaluator(a.quotaAccessor, a.quotaConfiguration, nil, a.config, a.numEvaluators, a.stopCh) } -// Validate ensures an authorizer is set. -func (a *quotaAdmission) Validate() error { +// ValidateInitialization ensures an authorizer is set. +func (a *QuotaAdmission) ValidateInitialization() error { if a.quotaAccessor == nil { return fmt.Errorf("missing quotaAccessor") } @@ -111,8 +117,8 @@ func (a *quotaAdmission) Validate() error { if a.quotaAccessor.lister == nil { return fmt.Errorf("missing quotaAccessor.lister") } - if a.registry == nil { - return fmt.Errorf("missing registry") + if a.quotaConfiguration == nil { + return fmt.Errorf("missing quotaConfiguration") } if a.evaluator == nil { return fmt.Errorf("missing evaluator") @@ -120,11 +126,15 @@ func (a *quotaAdmission) Validate() error { return nil } -// Admit makes admission decisions while enforcing quota -func (a *quotaAdmission) Admit(attr admission.Attributes) (err error) { +// Validate makes admission decisions while enforcing quota +func (a *QuotaAdmission) Validate(attr admission.Attributes) (err error) { // ignore all operations that correspond to sub-resource actions if attr.GetSubresource() != "" { return nil } + // ignore all operations that are not namespaced + if attr.GetNamespace() == "" { + return nil + } return a.evaluator.Evaluate(attr) } diff --git a/plugin/pkg/admission/resourcequota/admission_test.go b/plugin/pkg/admission/resourcequota/admission_test.go index 0d197c12381..09855f2ead9 100644 --- a/plugin/pkg/admission/resourcequota/admission_test.go +++ b/plugin/pkg/admission/resourcequota/admission_test.go @@ -26,18 +26,15 @@ 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/util/sets" "k8s.io/apiserver/pkg/admission" utilfeature "k8s.io/apiserver/pkg/util/feature" testcore "k8s.io/client-go/testing" "k8s.io/client-go/tools/cache" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" "k8s.io/kubernetes/pkg/controller" - "k8s.io/kubernetes/pkg/quota" - "k8s.io/kubernetes/pkg/quota/generic" "k8s.io/kubernetes/pkg/quota/install" resourcequotaapi "k8s.io/kubernetes/plugin/pkg/admission/resourcequota/apis/resourcequota" ) @@ -135,14 +132,15 @@ func TestAdmissionIgnoresDelete(t *testing.T) { quotaAccessor.client = kubeClient quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister() config := &resourcequotaapi.Configuration{} - evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh) + quotaConfiguration := install.NewQuotaConfigurationForAdmission() + evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh) - handler := "aAdmission{ + handler := &QuotaAdmission{ Handler: admission.NewHandler(admission.Create, admission.Update), evaluator: evaluator, } namespace := "default" - err := handler.Admit(admission.NewAttributesRecord(nil, nil, api.Kind("Pod").WithVersion("version"), namespace, "name", api.Resource("pods").WithVersion("version"), "", admission.Delete, nil)) + err := handler.Validate(admission.NewAttributesRecord(nil, nil, api.Kind("Pod").WithVersion("version"), namespace, "name", api.Resource("pods").WithVersion("version"), "", admission.Delete, nil)) if err != nil { t.Errorf("ResourceQuota should admit all deletes: %v", err) } @@ -170,19 +168,20 @@ func TestAdmissionIgnoresSubresources(t *testing.T) { quotaAccessor.client = kubeClient quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister() config := &resourcequotaapi.Configuration{} - evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh) + quotaConfiguration := install.NewQuotaConfigurationForAdmission() + evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh) - handler := "aAdmission{ + handler := &QuotaAdmission{ Handler: admission.NewHandler(admission.Create, admission.Update), evaluator: evaluator, } informerFactory.Core().InternalVersion().ResourceQuotas().Informer().GetIndexer().Add(resourceQuota) newPod := validPod("123", 1, getResourceRequirements(getResourceList("100m", "2Gi"), getResourceList("", ""))) - err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) + err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) if err == nil { t.Errorf("Expected an error because the pod exceeded allowed quota") } - err = handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "subresource", admission.Create, nil)) + err = handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "subresource", admission.Create, nil)) if err != nil { t.Errorf("Did not expect an error because the action went to a subresource: %v", err) } @@ -214,15 +213,16 @@ func TestAdmitBelowQuotaLimit(t *testing.T) { quotaAccessor.client = kubeClient quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister() config := &resourcequotaapi.Configuration{} - evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh) + quotaConfiguration := install.NewQuotaConfigurationForAdmission() + evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh) - handler := "aAdmission{ + handler := &QuotaAdmission{ Handler: admission.NewHandler(admission.Create, admission.Update), evaluator: evaluator, } informerFactory.Core().InternalVersion().ResourceQuotas().Informer().GetIndexer().Add(resourceQuota) newPod := validPod("allowed-pod", 1, getResourceRequirements(getResourceList("100m", "2Gi"), getResourceList("", ""))) - err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) + err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -297,9 +297,10 @@ func TestAdmitHandlesOldObjects(t *testing.T) { quotaAccessor.client = kubeClient quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister() config := &resourcequotaapi.Configuration{} - evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh) + quotaConfiguration := install.NewQuotaConfigurationForAdmission() + evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh) - handler := "aAdmission{ + handler := &QuotaAdmission{ Handler: admission.NewHandler(admission.Create, admission.Update), evaluator: evaluator, } @@ -317,7 +318,7 @@ func TestAdmitHandlesOldObjects(t *testing.T) { Ports: []api.ServicePort{{Port: 1234}}, }, } - err := handler.Admit(admission.NewAttributesRecord(newService, existingService, api.Kind("Service").WithVersion("version"), newService.Namespace, newService.Name, api.Resource("services").WithVersion("version"), "", admission.Update, nil)) + err := handler.Validate(admission.NewAttributesRecord(newService, existingService, api.Kind("Service").WithVersion("version"), newService.Namespace, newService.Name, api.Resource("services").WithVersion("version"), "", admission.Update, nil)) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -403,9 +404,10 @@ func TestAdmitHandlesNegativePVCUpdates(t *testing.T) { quotaAccessor.client = kubeClient quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister() config := &resourcequotaapi.Configuration{} - evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh) + quotaConfiguration := install.NewQuotaConfigurationForAdmission() + evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh) - handler := "aAdmission{ + handler := &QuotaAdmission{ Handler: admission.NewHandler(admission.Create, admission.Update), evaluator: evaluator, } @@ -425,7 +427,7 @@ func TestAdmitHandlesNegativePVCUpdates(t *testing.T) { }, } - err = handler.Admit(admission.NewAttributesRecord(newPVC, oldPVC, api.Kind("PersistentVolumeClaim").WithVersion("version"), newPVC.Namespace, newPVC.Name, api.Resource("persistentvolumeclaims").WithVersion("version"), "", admission.Update, nil)) + err = handler.Validate(admission.NewAttributesRecord(newPVC, oldPVC, api.Kind("PersistentVolumeClaim").WithVersion("version"), newPVC.Namespace, newPVC.Name, api.Resource("persistentvolumeclaims").WithVersion("version"), "", admission.Update, nil)) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -469,9 +471,10 @@ func TestAdmitHandlesPVCUpdates(t *testing.T) { quotaAccessor.client = kubeClient quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister() config := &resourcequotaapi.Configuration{} - evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh) + quotaConfiguration := install.NewQuotaConfigurationForAdmission() + evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh) - handler := "aAdmission{ + handler := &QuotaAdmission{ Handler: admission.NewHandler(admission.Create, admission.Update), evaluator: evaluator, } @@ -491,7 +494,7 @@ func TestAdmitHandlesPVCUpdates(t *testing.T) { }, } - err = handler.Admit(admission.NewAttributesRecord(newPVC, oldPVC, api.Kind("PersistentVolumeClaim").WithVersion("version"), newPVC.Namespace, newPVC.Name, api.Resource("persistentvolumeclaims").WithVersion("version"), "", admission.Update, nil)) + err = handler.Validate(admission.NewAttributesRecord(newPVC, oldPVC, api.Kind("PersistentVolumeClaim").WithVersion("version"), newPVC.Namespace, newPVC.Name, api.Resource("persistentvolumeclaims").WithVersion("version"), "", admission.Update, nil)) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -567,9 +570,10 @@ func TestAdmitHandlesCreatingUpdates(t *testing.T) { quotaAccessor.client = kubeClient quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister() config := &resourcequotaapi.Configuration{} - evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh) + quotaConfiguration := install.NewQuotaConfigurationForAdmission() + evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh) - handler := "aAdmission{ + handler := &QuotaAdmission{ Handler: admission.NewHandler(admission.Create, admission.Update), evaluator: evaluator, } @@ -587,7 +591,7 @@ func TestAdmitHandlesCreatingUpdates(t *testing.T) { Ports: []api.ServicePort{{Port: 1234}}, }, } - err := handler.Admit(admission.NewAttributesRecord(newService, oldService, api.Kind("Service").WithVersion("version"), newService.Namespace, newService.Name, api.Resource("services").WithVersion("version"), "", admission.Update, nil)) + err := handler.Validate(admission.NewAttributesRecord(newService, oldService, api.Kind("Service").WithVersion("version"), newService.Namespace, newService.Name, api.Resource("services").WithVersion("version"), "", admission.Update, nil)) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -661,15 +665,16 @@ func TestAdmitExceedQuotaLimit(t *testing.T) { quotaAccessor.client = kubeClient quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister() config := &resourcequotaapi.Configuration{} - evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh) + quotaConfiguration := install.NewQuotaConfigurationForAdmission() + evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh) - handler := "aAdmission{ + handler := &QuotaAdmission{ Handler: admission.NewHandler(admission.Create, admission.Update), evaluator: evaluator, } informerFactory.Core().InternalVersion().ResourceQuotas().Informer().GetIndexer().Add(resourceQuota) newPod := validPod("not-allowed-pod", 1, getResourceRequirements(getResourceList("3", "2Gi"), getResourceList("", ""))) - err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) + err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) if err == nil { t.Errorf("Expected an error exceeding quota") } @@ -705,22 +710,17 @@ func TestAdmitEnforceQuotaConstraints(t *testing.T) { quotaAccessor.client = kubeClient quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister() config := &resourcequotaapi.Configuration{} - evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh) + quotaConfiguration := install.NewQuotaConfigurationForAdmission() + evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh) - handler := "aAdmission{ + handler := &QuotaAdmission{ Handler: admission.NewHandler(admission.Create, admission.Update), evaluator: evaluator, } informerFactory.Core().InternalVersion().ResourceQuotas().Informer().GetIndexer().Add(resourceQuota) // verify all values are specified as required on the quota newPod := validPod("not-allowed-pod", 1, getResourceRequirements(getResourceList("100m", "2Gi"), getResourceList("200m", ""))) - err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) - if err == nil { - t.Errorf("Expected an error because the pod does not specify a memory limit") - } - // verify the requests and limits are actually valid (in this case, we fail because the limits < requests) - newPod = validPod("not-allowed-pod", 1, getResourceRequirements(getResourceList("200m", "2Gi"), getResourceList("100m", "1Gi"))) - err = handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) + err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) if err == nil { t.Errorf("Expected an error because the pod does not specify a memory limit") } @@ -759,9 +759,10 @@ func TestAdmitPodInNamespaceWithoutQuota(t *testing.T) { quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister() quotaAccessor.liveLookupCache = liveLookupCache config := &resourcequotaapi.Configuration{} - evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh) + quotaConfiguration := install.NewQuotaConfigurationForAdmission() + evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh) - handler := "aAdmission{ + handler := &QuotaAdmission{ Handler: admission.NewHandler(admission.Create, admission.Update), evaluator: evaluator, } @@ -770,7 +771,7 @@ func TestAdmitPodInNamespaceWithoutQuota(t *testing.T) { newPod := validPod("not-allowed-pod", 1, getResourceRequirements(getResourceList("100m", "2Gi"), getResourceList("200m", ""))) // Add to the lru cache so we do not do a live client lookup liveLookupCache.Add(newPod.Namespace, liveLookupEntry{expiry: time.Now().Add(time.Duration(30 * time.Second)), items: []*api.ResourceQuota{}}) - err = handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) + err = handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) if err != nil { t.Errorf("Did not expect an error because the pod is in a different namespace than the quota") } @@ -825,9 +826,10 @@ func TestAdmitBelowTerminatingQuotaLimit(t *testing.T) { quotaAccessor.client = kubeClient quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister() config := &resourcequotaapi.Configuration{} - evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh) + quotaConfiguration := install.NewQuotaConfigurationForAdmission() + evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh) - handler := "aAdmission{ + handler := &QuotaAdmission{ Handler: admission.NewHandler(admission.Create, admission.Update), evaluator: evaluator, } @@ -838,7 +840,7 @@ func TestAdmitBelowTerminatingQuotaLimit(t *testing.T) { newPod := validPod("allowed-pod", 1, getResourceRequirements(getResourceList("100m", "2Gi"), getResourceList("", ""))) activeDeadlineSeconds := int64(30) newPod.Spec.ActiveDeadlineSeconds = &activeDeadlineSeconds - err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) + err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -930,9 +932,10 @@ func TestAdmitBelowBestEffortQuotaLimit(t *testing.T) { quotaAccessor.client = kubeClient quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister() config := &resourcequotaapi.Configuration{} - evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh) + quotaConfiguration := install.NewQuotaConfigurationForAdmission() + evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh) - handler := "aAdmission{ + handler := &QuotaAdmission{ Handler: admission.NewHandler(admission.Create, admission.Update), evaluator: evaluator, } @@ -941,7 +944,7 @@ func TestAdmitBelowBestEffortQuotaLimit(t *testing.T) { // create a pod that is best effort because it does not make a request for anything newPod := validPod("allowed-pod", 1, getResourceRequirements(getResourceList("", ""), getResourceList("", ""))) - err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) + err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -1022,15 +1025,16 @@ func TestAdmitBestEffortQuotaLimitIgnoresBurstable(t *testing.T) { quotaAccessor.client = kubeClient quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister() config := &resourcequotaapi.Configuration{} - evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh) + quotaConfiguration := install.NewQuotaConfigurationForAdmission() + evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh) - handler := "aAdmission{ + handler := &QuotaAdmission{ Handler: admission.NewHandler(admission.Create, admission.Update), evaluator: evaluator, } informerFactory.Core().InternalVersion().ResourceQuotas().Informer().GetIndexer().Add(resourceQuota) newPod := validPod("allowed-pod", 1, getResourceRequirements(getResourceList("100m", "1Gi"), getResourceList("", ""))) - err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) + err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -1098,17 +1102,6 @@ func TestAdmissionSetsMissingNamespace(t *testing.T) { }, } - // create a dummy evaluator so we can trigger quota - podEvaluator := &generic.ObjectCountEvaluator{ - AllowCreateOnUpdate: false, - InternalGroupKind: api.Kind("Pod"), - ResourceName: api.ResourcePods, - } - registry := &generic.GenericRegistry{ - InternalEvaluators: map[schema.GroupKind]quota.Evaluator{ - podEvaluator.GroupKind(): podEvaluator, - }, - } stopCh := make(chan struct{}) defer close(stopCh) @@ -1118,10 +1111,10 @@ func TestAdmissionSetsMissingNamespace(t *testing.T) { quotaAccessor.client = kubeClient quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister() config := &resourcequotaapi.Configuration{} - evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh) - evaluator.(*quotaEvaluator).registry = registry + quotaConfiguration := install.NewQuotaConfigurationForAdmission() + evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh) - handler := "aAdmission{ + handler := &QuotaAdmission{ Handler: admission.NewHandler(admission.Create, admission.Update), evaluator: evaluator, } @@ -1131,7 +1124,7 @@ func TestAdmissionSetsMissingNamespace(t *testing.T) { // unset the namespace newPod.ObjectMeta.Namespace = "" - err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) + err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) if err != nil { t.Errorf("Got unexpected error: %v", err) } @@ -1164,23 +1157,24 @@ func TestAdmitRejectsNegativeUsage(t *testing.T) { quotaAccessor.client = kubeClient quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister() config := &resourcequotaapi.Configuration{} - evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh) + quotaConfiguration := install.NewQuotaConfigurationForAdmission() + evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh) - handler := "aAdmission{ + handler := &QuotaAdmission{ Handler: admission.NewHandler(admission.Create, admission.Update), evaluator: evaluator, } informerFactory.Core().InternalVersion().ResourceQuotas().Informer().GetIndexer().Add(resourceQuota) // verify quota rejects negative pvc storage requests newPvc := validPersistentVolumeClaim("not-allowed-pvc", getResourceRequirements(api.ResourceList{api.ResourceStorage: resource.MustParse("-1Gi")}, api.ResourceList{})) - err := handler.Admit(admission.NewAttributesRecord(newPvc, nil, api.Kind("PersistentVolumeClaim").WithVersion("version"), newPvc.Namespace, newPvc.Name, api.Resource("persistentvolumeclaims").WithVersion("version"), "", admission.Create, nil)) + err := handler.Validate(admission.NewAttributesRecord(newPvc, nil, api.Kind("PersistentVolumeClaim").WithVersion("version"), newPvc.Namespace, newPvc.Name, api.Resource("persistentvolumeclaims").WithVersion("version"), "", admission.Create, nil)) if err == nil { t.Errorf("Expected an error because the pvc has negative storage usage") } // verify quota accepts non-negative pvc storage requests newPvc = validPersistentVolumeClaim("not-allowed-pvc", getResourceRequirements(api.ResourceList{api.ResourceStorage: resource.MustParse("1Gi")}, api.ResourceList{})) - err = handler.Admit(admission.NewAttributesRecord(newPvc, nil, api.Kind("PersistentVolumeClaim").WithVersion("version"), newPvc.Namespace, newPvc.Name, api.Resource("persistentvolumeclaims").WithVersion("version"), "", admission.Create, nil)) + err = handler.Validate(admission.NewAttributesRecord(newPvc, nil, api.Kind("PersistentVolumeClaim").WithVersion("version"), newPvc.Namespace, newPvc.Name, api.Resource("persistentvolumeclaims").WithVersion("version"), "", admission.Create, nil)) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -1210,9 +1204,10 @@ func TestAdmitWhenUnrelatedResourceExceedsQuota(t *testing.T) { quotaAccessor.client = kubeClient quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister() config := &resourcequotaapi.Configuration{} - evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh) + quotaConfiguration := install.NewQuotaConfigurationForAdmission() + evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh) - handler := "aAdmission{ + handler := &QuotaAdmission{ Handler: admission.NewHandler(admission.Create, admission.Update), evaluator: evaluator, } @@ -1220,7 +1215,7 @@ func TestAdmitWhenUnrelatedResourceExceedsQuota(t *testing.T) { // create a pod that should pass existing quota newPod := validPod("allowed-pod", 1, getResourceRequirements(getResourceList("", ""), getResourceList("", ""))) - err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) + err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -1246,14 +1241,15 @@ func TestAdmitLimitedResourceNoQuota(t *testing.T) { }, }, } - evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh) + quotaConfiguration := install.NewQuotaConfigurationForAdmission() + evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh) - handler := "aAdmission{ + handler := &QuotaAdmission{ Handler: admission.NewHandler(admission.Create, admission.Update), evaluator: evaluator, } newPod := validPod("not-allowed-pod", 1, getResourceRequirements(getResourceList("3", "2Gi"), getResourceList("", ""))) - err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) + err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) if err == nil { t.Errorf("Expected an error for consuming a limited resource without quota.") } @@ -1279,14 +1275,15 @@ func TestAdmitLimitedResourceNoQuotaIgnoresNonMatchingResources(t *testing.T) { }, }, } - evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh) + quotaConfiguration := install.NewQuotaConfigurationForAdmission() + evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh) - handler := "aAdmission{ + handler := &QuotaAdmission{ Handler: admission.NewHandler(admission.Create, admission.Update), evaluator: evaluator, } newPod := validPod("allowed-pod", 1, getResourceRequirements(getResourceList("3", "2Gi"), getResourceList("", ""))) - err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) + err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -1325,15 +1322,16 @@ func TestAdmitLimitedResourceWithQuota(t *testing.T) { }, }, } - evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh) + quotaConfiguration := install.NewQuotaConfigurationForAdmission() + evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh) - handler := "aAdmission{ + handler := &QuotaAdmission{ Handler: admission.NewHandler(admission.Create, admission.Update), evaluator: evaluator, } indexer.Add(resourceQuota) newPod := validPod("allowed-pod", 1, getResourceRequirements(getResourceList("3", "2Gi"), getResourceList("", ""))) - err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) + err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -1383,16 +1381,17 @@ func TestAdmitLimitedResourceWithMultipleQuota(t *testing.T) { }, }, } - evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh) + quotaConfiguration := install.NewQuotaConfigurationForAdmission() + evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh) - handler := "aAdmission{ + handler := &QuotaAdmission{ Handler: admission.NewHandler(admission.Create, admission.Update), evaluator: evaluator, } indexer.Add(resourceQuota1) indexer.Add(resourceQuota2) newPod := validPod("allowed-pod", 1, getResourceRequirements(getResourceList("3", "2Gi"), getResourceList("", ""))) - err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) + err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -1431,15 +1430,16 @@ func TestAdmitLimitedResourceWithQuotaThatDoesNotCover(t *testing.T) { }, }, } - evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh) + quotaConfiguration := install.NewQuotaConfigurationForAdmission() + evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh) - handler := "aAdmission{ + handler := &QuotaAdmission{ Handler: admission.NewHandler(admission.Create, admission.Update), evaluator: evaluator, } indexer.Add(resourceQuota) newPod := validPod("not-allowed-pod", 1, getResourceRequirements(getResourceList("3", "2Gi"), getResourceList("", ""))) - err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) + err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) if err == nil { t.Fatalf("Expected an error since the quota did not cover cpu") } diff --git a/plugin/pkg/admission/resourcequota/apis/resourcequota/BUILD b/plugin/pkg/admission/resourcequota/apis/resourcequota/BUILD index 6da8520269f..7c43ea4a124 100644 --- a/plugin/pkg/admission/resourcequota/apis/resourcequota/BUILD +++ b/plugin/pkg/admission/resourcequota/apis/resourcequota/BUILD @@ -16,7 +16,6 @@ go_library( importpath = "k8s.io/kubernetes/plugin/pkg/admission/resourcequota/apis/resourcequota", deps = [ "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/plugin/pkg/admission/resourcequota/apis/resourcequota/doc.go b/plugin/pkg/admission/resourcequota/apis/resourcequota/doc.go index fd56cc64a23..5a514f605d4 100644 --- a/plugin/pkg/admission/resourcequota/apis/resourcequota/doc.go +++ b/plugin/pkg/admission/resourcequota/apis/resourcequota/doc.go @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package package resourcequota // import "k8s.io/kubernetes/plugin/pkg/admission/resourcequota/apis/resourcequota" diff --git a/plugin/pkg/admission/resourcequota/apis/resourcequota/v1alpha1/doc.go b/plugin/pkg/admission/resourcequota/apis/resourcequota/v1alpha1/doc.go index 582d9a35f50..53508c2f2da 100644 --- a/plugin/pkg/admission/resourcequota/apis/resourcequota/v1alpha1/doc.go +++ b/plugin/pkg/admission/resourcequota/apis/resourcequota/v1alpha1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:conversion-gen=k8s.io/kubernetes/plugin/pkg/admission/resourcequota/apis/resourcequota // +k8s:defaulter-gen=TypeMeta diff --git a/plugin/pkg/admission/resourcequota/apis/resourcequota/v1alpha1/zz_generated.conversion.go b/plugin/pkg/admission/resourcequota/apis/resourcequota/v1alpha1/zz_generated.conversion.go index 43b75e6b832..d26f8028166 100644 --- a/plugin/pkg/admission/resourcequota/apis/resourcequota/v1alpha1/zz_generated.conversion.go +++ b/plugin/pkg/admission/resourcequota/apis/resourcequota/v1alpha1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,10 +21,11 @@ limitations under the License. package v1alpha1 import ( + unsafe "unsafe" + conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" resourcequota "k8s.io/kubernetes/plugin/pkg/admission/resourcequota/apis/resourcequota" - unsafe "unsafe" ) func init() { diff --git a/plugin/pkg/admission/resourcequota/apis/resourcequota/v1alpha1/zz_generated.deepcopy.go b/plugin/pkg/admission/resourcequota/apis/resourcequota/v1alpha1/zz_generated.deepcopy.go index 7146088ff44..9d36704613c 100644 --- a/plugin/pkg/admission/resourcequota/apis/resourcequota/v1alpha1/zz_generated.deepcopy.go +++ b/plugin/pkg/admission/resourcequota/apis/resourcequota/v1alpha1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,32 +21,9 @@ limitations under the License. package v1alpha1 import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Configuration).DeepCopyInto(out.(*Configuration)) - return nil - }, InType: reflect.TypeOf(&Configuration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LimitedResource).DeepCopyInto(out.(*LimitedResource)) - return nil - }, InType: reflect.TypeOf(&LimitedResource{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Configuration) DeepCopyInto(out *Configuration) { *out = *in diff --git a/plugin/pkg/admission/resourcequota/apis/resourcequota/v1alpha1/zz_generated.defaults.go b/plugin/pkg/admission/resourcequota/apis/resourcequota/v1alpha1/zz_generated.defaults.go index 53f9cb92ef7..35985182f5c 100644 --- a/plugin/pkg/admission/resourcequota/apis/resourcequota/v1alpha1/zz_generated.defaults.go +++ b/plugin/pkg/admission/resourcequota/apis/resourcequota/v1alpha1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/plugin/pkg/admission/resourcequota/apis/resourcequota/validation/BUILD b/plugin/pkg/admission/resourcequota/apis/resourcequota/validation/BUILD index 7067a2ed9c5..a812219624e 100644 --- a/plugin/pkg/admission/resourcequota/apis/resourcequota/validation/BUILD +++ b/plugin/pkg/admission/resourcequota/apis/resourcequota/validation/BUILD @@ -32,7 +32,7 @@ filegroup( go_test( name = "go_default_test", srcs = ["validation_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/resourcequota/apis/resourcequota/validation", - library = ":go_default_library", deps = ["//plugin/pkg/admission/resourcequota/apis/resourcequota:go_default_library"], ) diff --git a/plugin/pkg/admission/resourcequota/apis/resourcequota/zz_generated.deepcopy.go b/plugin/pkg/admission/resourcequota/apis/resourcequota/zz_generated.deepcopy.go index 697a99623ee..4f8250a504a 100644 --- a/plugin/pkg/admission/resourcequota/apis/resourcequota/zz_generated.deepcopy.go +++ b/plugin/pkg/admission/resourcequota/apis/resourcequota/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,32 +21,9 @@ limitations under the License. package resourcequota import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Configuration).DeepCopyInto(out.(*Configuration)) - return nil - }, InType: reflect.TypeOf(&Configuration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LimitedResource).DeepCopyInto(out.(*LimitedResource)) - return nil - }, InType: reflect.TypeOf(&LimitedResource{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Configuration) DeepCopyInto(out *Configuration) { *out = *in diff --git a/plugin/pkg/admission/resourcequota/controller.go b/plugin/pkg/admission/resourcequota/controller.go index 3650f2d39ea..832943d6471 100644 --- a/plugin/pkg/admission/resourcequota/controller.go +++ b/plugin/pkg/admission/resourcequota/controller.go @@ -32,8 +32,9 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apiserver/pkg/admission" "k8s.io/client-go/util/workqueue" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/quota" + "k8s.io/kubernetes/pkg/quota/generic" _ "k8s.io/kubernetes/pkg/util/reflector/prometheus" // for reflector metric registration _ "k8s.io/kubernetes/pkg/util/workqueue/prometheus" // for workqueue metric registration resourcequotaapi "k8s.io/kubernetes/plugin/pkg/admission/resourcequota/apis/resourcequota" @@ -51,6 +52,9 @@ type quotaEvaluator struct { // lockAcquisitionFunc acquires any required locks and returns a cleanup method to defer lockAcquisitionFunc func([]api.ResourceQuota) func() + // how quota was configured + quotaConfiguration quota.Configuration + // registry that knows how to measure usage for objects registry quota.Registry @@ -106,16 +110,18 @@ func newAdmissionWaiter(a admission.Attributes) *admissionWaiter { // NewQuotaEvaluator configures an admission controller that can enforce quota constraints // using the provided registry. The registry must have the capability to handle group/kinds that // are persisted by the server this admission controller is intercepting -func NewQuotaEvaluator(quotaAccessor QuotaAccessor, registry quota.Registry, lockAcquisitionFunc func([]api.ResourceQuota) func(), config *resourcequotaapi.Configuration, workers int, stopCh <-chan struct{}) Evaluator { +func NewQuotaEvaluator(quotaAccessor QuotaAccessor, quotaConfiguration quota.Configuration, lockAcquisitionFunc func([]api.ResourceQuota) func(), config *resourcequotaapi.Configuration, workers int, stopCh <-chan struct{}) Evaluator { // if we get a nil config, just create an empty default. if config == nil { config = &resourcequotaapi.Configuration{} } + return "aEvaluator{ quotaAccessor: quotaAccessor, lockAcquisitionFunc: lockAcquisitionFunc, - registry: registry, + quotaConfiguration: quotaConfiguration, + registry: generic.NewRegistry(quotaConfiguration.Evaluators()), queue: workqueue.NewNamed("admission_quota_controller"), work: map[string][]*admissionWaiter{}, @@ -365,9 +371,8 @@ func limitedByDefault(usage api.ResourceList, limitedResources []resourcequotaap // that capture what the usage would be if the request succeeded. It return an error if there is insufficient quota to satisfy the request func (e *quotaEvaluator) checkRequest(quotas []api.ResourceQuota, a admission.Attributes) ([]api.ResourceQuota, error) { namespace := a.GetNamespace() - evaluators := e.registry.Evaluators() - evaluator, found := evaluators[a.GetKind().GroupKind()] - if !found { + evaluator := e.registry.Get(a.GetResource().GroupResource()) + if evaluator == nil { return quotas, nil } @@ -516,18 +521,27 @@ func (e *quotaEvaluator) Evaluate(a admission.Attributes) error { go e.run() }) - // if we do not know how to evaluate use for this kind, just ignore - evaluators := e.registry.Evaluators() - evaluator, found := evaluators[a.GetKind().GroupKind()] - if !found { + // is this resource ignored? + gvr := a.GetResource() + gr := gvr.GroupResource() + if _, ok := e.quotaConfiguration.IgnoredResources()[gr]; ok { return nil } + + // if we do not know how to evaluate use for this resource, create an evaluator + evaluator := e.registry.Get(gr) + if evaluator == nil { + // create an object count evaluator if no evaluator previously registered + // note, we do not need aggregate usage here, so we pass a nil infomer func + evaluator = generic.NewObjectCountEvaluator(false, gr, nil, "") + e.registry.Add(evaluator) + glog.Infof("quota admission added evaluator for: %s", gr) + } // for this kind, check if the operation could mutate any quota resources // if no resources tracked by quota are impacted, then just return if !evaluator.Handles(a) { return nil } - waiter := newAdmissionWaiter(a) e.addWork(waiter) diff --git a/plugin/pkg/admission/resourcequota/resource_access.go b/plugin/pkg/admission/resourcequota/resource_access.go index edbaff90404..9384770f514 100644 --- a/plugin/pkg/admission/resourcequota/resource_access.go +++ b/plugin/pkg/admission/resourcequota/resource_access.go @@ -25,7 +25,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apiserver/pkg/storage/etcd" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" corelisters "k8s.io/kubernetes/pkg/client/listers/core/internalversion" ) diff --git a/plugin/pkg/admission/security/podsecuritypolicy/BUILD b/plugin/pkg/admission/security/podsecuritypolicy/BUILD index 782b75eb790..5c256b008b2 100644 --- a/plugin/pkg/admission/security/podsecuritypolicy/BUILD +++ b/plugin/pkg/admission/security/podsecuritypolicy/BUILD @@ -11,7 +11,7 @@ go_library( srcs = ["admission.go"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/security/podsecuritypolicy", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", "//pkg/client/listers/extensions/internalversion:go_default_library", @@ -34,14 +34,14 @@ go_library( go_test( name = "go_default_test", srcs = ["admission_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/security/podsecuritypolicy", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", - "//pkg/client/listers/extensions/internalversion:go_default_library", "//pkg/controller:go_default_library", "//pkg/security/apparmor:go_default_library", "//pkg/security/podsecuritypolicy:go_default_library", @@ -52,10 +52,11 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + "//vendor/k8s.io/apiserver/pkg/authentication/serviceaccount:go_default_library", "//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library", "//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", + "//vendor/k8s.io/apiserver/pkg/authorization/authorizerfactory:go_default_library", ], ) diff --git a/plugin/pkg/admission/security/podsecuritypolicy/OWNERS b/plugin/pkg/admission/security/podsecuritypolicy/OWNERS new file mode 100644 index 00000000000..4a0052da6c3 --- /dev/null +++ b/plugin/pkg/admission/security/podsecuritypolicy/OWNERS @@ -0,0 +1,6 @@ +approvers: +- tallclair +- liggitt +reviewers: +- pweil- +- php-coder diff --git a/plugin/pkg/admission/security/podsecuritypolicy/admission.go b/plugin/pkg/admission/security/podsecuritypolicy/admission.go index 19de55b93e1..380cf2b63a0 100644 --- a/plugin/pkg/admission/security/podsecuritypolicy/admission.go +++ b/plugin/pkg/admission/security/podsecuritypolicy/admission.go @@ -31,7 +31,7 @@ import ( genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer" "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" extensionslisters "k8s.io/kubernetes/pkg/client/listers/extensions/internalversion" @@ -49,31 +49,27 @@ const ( // Register registers a plugin func Register(plugins *admission.Plugins) { plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { - plugin := NewPlugin(psp.NewSimpleStrategyFactory(), getMatchingPolicies, true) + plugin := newPlugin(psp.NewSimpleStrategyFactory(), true) return plugin, nil }) } -// PSPMatchFn allows plugging in how PSPs are matched against user information. -type PSPMatchFn func(lister extensionslisters.PodSecurityPolicyLister, user user.Info, sa user.Info, authz authorizer.Authorizer, namespace string) ([]*extensions.PodSecurityPolicy, error) - -// podSecurityPolicyPlugin holds state for and implements the admission plugin. -type podSecurityPolicyPlugin struct { +// PodSecurityPolicyPlugin holds state for and implements the admission plugin. +type PodSecurityPolicyPlugin struct { *admission.Handler strategyFactory psp.StrategyFactory - pspMatcher PSPMatchFn failOnNoPolicies bool authz authorizer.Authorizer lister extensionslisters.PodSecurityPolicyLister } // SetAuthorizer sets the authorizer. -func (plugin *podSecurityPolicyPlugin) SetAuthorizer(authz authorizer.Authorizer) { +func (plugin *PodSecurityPolicyPlugin) SetAuthorizer(authz authorizer.Authorizer) { plugin.authz = authz } -// Validate ensures an authorizer is set. -func (plugin *podSecurityPolicyPlugin) Validate() error { +// ValidateInitialization ensures an authorizer is set. +func (plugin *PodSecurityPolicyPlugin) ValidateInitialization() error { if plugin.authz == nil { return fmt.Errorf("%s requires an authorizer", PluginName) } @@ -83,21 +79,21 @@ func (plugin *podSecurityPolicyPlugin) Validate() error { return nil } -var _ admission.Interface = &podSecurityPolicyPlugin{} -var _ genericadmissioninit.WantsAuthorizer = &podSecurityPolicyPlugin{} -var _ kubeapiserveradmission.WantsInternalKubeInformerFactory = &podSecurityPolicyPlugin{} +var _ admission.MutationInterface = &PodSecurityPolicyPlugin{} +var _ admission.ValidationInterface = &PodSecurityPolicyPlugin{} +var _ genericadmissioninit.WantsAuthorizer = &PodSecurityPolicyPlugin{} +var _ kubeapiserveradmission.WantsInternalKubeInformerFactory = &PodSecurityPolicyPlugin{} -// NewPlugin creates a new PSP admission plugin. -func NewPlugin(strategyFactory psp.StrategyFactory, pspMatcher PSPMatchFn, failOnNoPolicies bool) *podSecurityPolicyPlugin { - return &podSecurityPolicyPlugin{ +// newPlugin creates a new PSP admission plugin. +func newPlugin(strategyFactory psp.StrategyFactory, failOnNoPolicies bool) *PodSecurityPolicyPlugin { + return &PodSecurityPolicyPlugin{ Handler: admission.NewHandler(admission.Create, admission.Update), strategyFactory: strategyFactory, - pspMatcher: pspMatcher, failOnNoPolicies: failOnNoPolicies, } } -func (a *podSecurityPolicyPlugin) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) { +func (a *PodSecurityPolicyPlugin) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) { podSecurityPolicyInformer := f.Extensions().InternalVersion().PodSecurityPolicies() a.lister = podSecurityPolicyInformer.Lister() a.SetReadyFunc(podSecurityPolicyInformer.Informer().HasSynced) @@ -111,107 +107,34 @@ func (a *podSecurityPolicyPlugin) SetInternalKubeInformerFactory(f informers.Sha // 3. Try to generate and validate a PSP with providers. If we find one then admit the pod // with the validated PSP. If we don't find any reject the pod and give all errors from the // failed attempts. -func (c *podSecurityPolicyPlugin) Admit(a admission.Attributes) error { - if a.GetResource().GroupResource() != api.Resource("pods") { +func (c *PodSecurityPolicyPlugin) Admit(a admission.Attributes) error { + if ignore, err := shouldIgnore(a); err != nil { + return err + } else if ignore { return nil } - if len(a.GetSubresource()) != 0 { + // only mutate if this is a CREATE request. On updates we only validate. + // TODO(liggitt): allow spec mutation during initializing updates? + if a.GetOperation() != admission.Create { return nil } - pod, ok := a.GetObject().(*api.Pod) - // if we can't convert then fail closed since we've already checked that this is supposed to be a pod object. - // this shouldn't normally happen during admission but could happen if an integrator passes a versioned - // pod object rather than an internal object. - if !ok { - return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject())) - } + pod := a.GetObject().(*api.Pod) - // if this is an update, see if we are only updating the ownerRef/finalizers. Garbage collection does this - // and we should allow it in general, since you had the power to update and the power to delete. - // The worst that happens is that you delete something, but you aren't controlling the privileged object itself - if a.GetOperation() == admission.Update && rbacregistry.IsOnlyMutatingGCFields(a.GetObject(), a.GetOldObject(), apiequality.Semantic) { - return nil - } - - // get all constraints that are usable by the user - glog.V(4).Infof("getting pod security policies for pod %s (generate: %s)", pod.Name, pod.GenerateName) - var saInfo user.Info - if len(pod.Spec.ServiceAccountName) > 0 { - saInfo = serviceaccount.UserInfo(a.GetNamespace(), pod.Spec.ServiceAccountName, "") - } - - matchedPolicies, err := c.pspMatcher(c.lister, a.GetUserInfo(), saInfo, c.authz, a.GetNamespace()) + // compute the context. Mutation is allowed. ValidatedPSPAnnotation is not taken into account. + allowedPod, pspName, validationErrs, err := c.computeSecurityContext(a, pod, true, "") if err != nil { return admission.NewForbidden(a, err) } - - // if we have no policies and want to succeed then return. Otherwise we'll end up with no - // providers and fail with "unable to validate against any pod security policy" below. - if len(matchedPolicies) == 0 && !c.failOnNoPolicies { - return nil - } - - // sort by name to make order deterministic - // TODO(liggitt): add priority field to allow admins to bucket differently - sort.SliceStable(matchedPolicies, func(i, j int) bool { - return strings.Compare(matchedPolicies[i].Name, matchedPolicies[j].Name) < 0 - }) - - providers, errs := c.createProvidersFromPolicies(matchedPolicies, pod.Namespace) - logProviders(a, pod, providers, errs) - - if len(providers) == 0 { - return admission.NewForbidden(a, fmt.Errorf("no providers available to validate pod request")) - } - - // TODO(liggitt): allow spec mutation during initializing updates? - specMutationAllowed := a.GetOperation() == admission.Create - - // all containers in a single pod must validate under a single provider or we will reject the request - validationErrs := field.ErrorList{} - var ( - allowedPod *api.Pod - allowingProvider psp.Provider - ) - -loop: - for _, provider := range providers { - podCopy := pod.DeepCopy() - - if errs := assignSecurityContext(provider, podCopy, field.NewPath(fmt.Sprintf("provider %s: ", provider.GetPSPName()))); len(errs) > 0 { - validationErrs = append(validationErrs, errs...) - continue - } - - // the entire pod validated - - switch { - case apiequality.Semantic.DeepEqual(pod, podCopy): - // if it validated without mutating anything, use this result - allowedPod = podCopy - allowingProvider = provider - break loop - case specMutationAllowed && allowedPod == nil: - // if mutation is allowed and this is the first PSP to allow the pod, remember it, - // but continue to see if another PSP allows without mutating - allowedPod = podCopy - allowingProvider = provider - glog.V(6).Infof("pod %s (generate: %s) in namespace %s validated against provider %s with mutation", pod.Name, pod.GenerateName, a.GetNamespace(), provider.GetPSPName()) - case !specMutationAllowed: - glog.V(6).Infof("pod %s (generate: %s) in namespace %s validated against provider %s, but required mutation, skipping", pod.Name, pod.GenerateName, a.GetNamespace(), provider.GetPSPName()) - } - } - if allowedPod != nil { *pod = *allowedPod // annotate and accept the pod - glog.V(4).Infof("pod %s (generate: %s) in namespace %s validated against provider %s", pod.Name, pod.GenerateName, a.GetNamespace(), allowingProvider.GetPSPName()) + glog.V(4).Infof("pod %s (generate: %s) in namespace %s validated against provider %s", pod.Name, pod.GenerateName, a.GetNamespace(), pspName) if pod.ObjectMeta.Annotations == nil { pod.ObjectMeta.Annotations = map[string]string{} } - pod.ObjectMeta.Annotations[psputil.ValidatedPSPAnnotation] = allowingProvider.GetPSPName() + pod.ObjectMeta.Annotations[psputil.ValidatedPSPAnnotation] = pspName return nil } @@ -220,41 +143,181 @@ loop: return admission.NewForbidden(a, fmt.Errorf("unable to validate against any pod security policy: %v", validationErrs)) } +func (c *PodSecurityPolicyPlugin) Validate(a admission.Attributes) error { + if ignore, err := shouldIgnore(a); err != nil { + return err + } else if ignore { + return nil + } + + pod := a.GetObject().(*api.Pod) + + // compute the context. Mutation is not allowed. ValidatedPSPAnnotation is used as a hint to gain same speed-up. + allowedPod, _, validationErrs, err := c.computeSecurityContext(a, pod, false, pod.ObjectMeta.Annotations[psputil.ValidatedPSPAnnotation]) + if err != nil { + return admission.NewForbidden(a, err) + } + if apiequality.Semantic.DeepEqual(pod, allowedPod) { + return nil + } + + // we didn't validate against any provider, reject the pod and give the errors for each attempt + glog.V(4).Infof("unable to validate pod %s (generate: %s) in namespace %s against any pod security policy: %v", pod.Name, pod.GenerateName, a.GetNamespace(), validationErrs) + return admission.NewForbidden(a, fmt.Errorf("unable to validate against any pod security policy: %v", validationErrs)) +} + +func shouldIgnore(a admission.Attributes) (bool, error) { + if a.GetResource().GroupResource() != api.Resource("pods") { + return true, nil + } + if len(a.GetSubresource()) != 0 { + return true, nil + } + + // if we can't convert then fail closed since we've already checked that this is supposed to be a pod object. + // this shouldn't normally happen during admission but could happen if an integrator passes a versioned + // pod object rather than an internal object. + if _, ok := a.GetObject().(*api.Pod); !ok { + return false, admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject())) + } + + // if this is an update, see if we are only updating the ownerRef/finalizers. Garbage collection does this + // and we should allow it in general, since you had the power to update and the power to delete. + // The worst that happens is that you delete something, but you aren't controlling the privileged object itself + if a.GetOperation() == admission.Update && rbacregistry.IsOnlyMutatingGCFields(a.GetObject(), a.GetOldObject(), apiequality.Semantic) { + return true, nil + } + + return false, nil +} + +// computeSecurityContext derives a valid security context while trying to avoid any changes to the given pod. I.e. +// if there is a matching policy with the same security context as given, it will be reused. If there is no +// matching policy the returned pod will be nil and the pspName empty. validatedPSPHint is the validated psp name +// saved in kubernetes.io/psp annotation. This psp is usually the one we are looking for. +func (c *PodSecurityPolicyPlugin) computeSecurityContext(a admission.Attributes, pod *api.Pod, specMutationAllowed bool, validatedPSPHint string) (*api.Pod, string, field.ErrorList, error) { + // get all constraints that are usable by the user + glog.V(4).Infof("getting pod security policies for pod %s (generate: %s)", pod.Name, pod.GenerateName) + var saInfo user.Info + if len(pod.Spec.ServiceAccountName) > 0 { + saInfo = serviceaccount.UserInfo(a.GetNamespace(), pod.Spec.ServiceAccountName, "") + } + + policies, err := c.lister.List(labels.Everything()) + if err != nil { + return nil, "", nil, err + } + + // if we have no policies and want to succeed then return. Otherwise we'll end up with no + // providers and fail with "unable to validate against any pod security policy" below. + if len(policies) == 0 && !c.failOnNoPolicies { + return pod, "", nil, nil + } + + // sort policies by name to make order deterministic + // If mutation is not allowed and validatedPSPHint is provided, check the validated policy first. + // TODO(liggitt): add priority field to allow admins to bucket differently + sort.SliceStable(policies, func(i, j int) bool { + if !specMutationAllowed { + if policies[i].Name == validatedPSPHint { + return true + } + if policies[j].Name == validatedPSPHint { + return false + } + } + return strings.Compare(policies[i].Name, policies[j].Name) < 0 + }) + + providers, errs := c.createProvidersFromPolicies(policies, pod.Namespace) + for _, err := range errs { + glog.V(4).Infof("provider creation error: %v", err) + } + + if len(providers) == 0 { + return nil, "", nil, fmt.Errorf("no providers available to validate pod request") + } + + var ( + allowedMutatedPod *api.Pod + allowingMutatingPSP string + // Map of PSP name to associated validation errors. + validationErrs = map[string]field.ErrorList{} + ) + + for _, provider := range providers { + podCopy := pod.DeepCopy() + + if errs := assignSecurityContext(provider, podCopy, field.NewPath(fmt.Sprintf("provider %s: ", provider.GetPSPName()))); len(errs) > 0 { + validationErrs[provider.GetPSPName()] = errs + continue + } + + // the entire pod validated + mutated := !apiequality.Semantic.DeepEqual(pod, podCopy) + if mutated && !specMutationAllowed { + continue + } + + if !isAuthorizedForPolicy(a.GetUserInfo(), saInfo, a.GetNamespace(), provider.GetPSPName(), c.authz) { + continue + } + + switch { + case !mutated: + // if it validated without mutating anything, use this result + return podCopy, provider.GetPSPName(), nil, nil + + case specMutationAllowed && allowedMutatedPod == nil: + // if mutation is allowed and this is the first PSP to allow the pod, remember it, + // but continue to see if another PSP allows without mutating + allowedMutatedPod = podCopy + allowingMutatingPSP = provider.GetPSPName() + } + } + + if allowedMutatedPod != nil { + return allowedMutatedPod, allowingMutatingPSP, nil, nil + } + + // Pod is rejected. Filter the validation errors to only include errors from authorized PSPs. + aggregate := field.ErrorList{} + for psp, errs := range validationErrs { + if isAuthorizedForPolicy(a.GetUserInfo(), saInfo, a.GetNamespace(), psp, c.authz) { + aggregate = append(aggregate, errs...) + } + } + return nil, "", aggregate, nil +} + // assignSecurityContext creates a security context for each container in the pod // and validates that the sc falls within the psp constraints. All containers must validate against // the same psp or is not considered valid. func assignSecurityContext(provider psp.Provider, pod *api.Pod, fldPath *field.Path) field.ErrorList { errs := field.ErrorList{} - psc, pscAnnotations, err := provider.CreatePodSecurityContext(pod) + err := provider.DefaultPodSecurityContext(pod) if err != nil { errs = append(errs, field.Invalid(field.NewPath("spec", "securityContext"), pod.Spec.SecurityContext, err.Error())) } - pod.Spec.SecurityContext = psc - pod.Annotations = pscAnnotations errs = append(errs, provider.ValidatePodSecurityContext(pod, field.NewPath("spec", "securityContext"))...) for i := range pod.Spec.InitContainers { - sc, scAnnotations, err := provider.CreateContainerSecurityContext(pod, &pod.Spec.InitContainers[i]) + err := provider.DefaultContainerSecurityContext(pod, &pod.Spec.InitContainers[i]) if err != nil { errs = append(errs, field.Invalid(field.NewPath("spec", "initContainers").Index(i).Child("securityContext"), "", err.Error())) continue } - pod.Spec.InitContainers[i].SecurityContext = sc - pod.Annotations = scAnnotations errs = append(errs, provider.ValidateContainerSecurityContext(pod, &pod.Spec.InitContainers[i], field.NewPath("spec", "initContainers").Index(i).Child("securityContext"))...) } for i := range pod.Spec.Containers { - sc, scAnnotations, err := provider.CreateContainerSecurityContext(pod, &pod.Spec.Containers[i]) + err := provider.DefaultContainerSecurityContext(pod, &pod.Spec.Containers[i]) if err != nil { errs = append(errs, field.Invalid(field.NewPath("spec", "containers").Index(i).Child("securityContext"), "", err.Error())) continue } - - pod.Spec.Containers[i].SecurityContext = sc - pod.Annotations = scAnnotations errs = append(errs, provider.ValidateContainerSecurityContext(pod, &pod.Spec.Containers[i], field.NewPath("spec", "containers").Index(i).Child("securityContext"))...) } @@ -265,7 +328,7 @@ func assignSecurityContext(provider psp.Provider, pod *api.Pod, fldPath *field.P } // createProvidersFromPolicies creates providers from the constraints supplied. -func (c *podSecurityPolicyPlugin) createProvidersFromPolicies(psps []*extensions.PodSecurityPolicy, namespace string) ([]psp.Provider, []error) { +func (c *PodSecurityPolicyPlugin) createProvidersFromPolicies(psps []*extensions.PodSecurityPolicy, namespace string) ([]psp.Provider, []error) { var ( // collected providers providers []psp.Provider @@ -284,72 +347,36 @@ func (c *podSecurityPolicyPlugin) createProvidersFromPolicies(psps []*extensions return providers, errs } -// getMatchingPolicies returns policies from the lister. For now this returns everything -// in the future it can filter based on UserInfo and permissions. -// -// TODO: this will likely need optimization since the initial implementation will -// always query for authorization. Needs scale testing and possibly checking against -// a cache. -func getMatchingPolicies(lister extensionslisters.PodSecurityPolicyLister, user user.Info, sa user.Info, authz authorizer.Authorizer, namespace string) ([]*extensions.PodSecurityPolicy, error) { - matchedPolicies := make([]*extensions.PodSecurityPolicy, 0) - - list, err := lister.List(labels.Everything()) - if err != nil { - return nil, err - } - - for _, constraint := range list { - if authorizedForPolicy(user, namespace, constraint, authz) || authorizedForPolicy(sa, namespace, constraint, authz) { - matchedPolicies = append(matchedPolicies, constraint) - } - } - - return matchedPolicies, nil +func isAuthorizedForPolicy(user, sa user.Info, namespace, policyName string, authz authorizer.Authorizer) bool { + // Check the service account first, as that is the more common use case. + return authorizedForPolicy(sa, namespace, policyName, authz) || + authorizedForPolicy(user, namespace, policyName, authz) } // authorizedForPolicy returns true if info is authorized to perform the "use" verb on the policy resource. -func authorizedForPolicy(info user.Info, namespace string, policy *extensions.PodSecurityPolicy, authz authorizer.Authorizer) bool { +func authorizedForPolicy(info user.Info, namespace string, policyName string, authz authorizer.Authorizer) bool { if info == nil { return false } - attr := buildAttributes(info, namespace, policy) - allowed, reason, err := authz.Authorize(attr) + attr := buildAttributes(info, namespace, policyName) + decision, reason, err := authz.Authorize(attr) if err != nil { glog.V(5).Infof("cannot authorize for policy: %v,%v", reason, err) } - return allowed + return (decision == authorizer.DecisionAllow) } // buildAttributes builds an attributes record for a SAR based on the user info and policy. -func buildAttributes(info user.Info, namespace string, policy *extensions.PodSecurityPolicy) authorizer.Attributes { +func buildAttributes(info user.Info, namespace string, policyName string) authorizer.Attributes { // check against the namespace that the pod is being created in to allow per-namespace PSP grants. attr := authorizer.AttributesRecord{ User: info, Verb: "use", Namespace: namespace, - Name: policy.Name, + Name: policyName, APIGroup: extensions.GroupName, Resource: "podsecuritypolicies", ResourceRequest: true, } return attr } - -// logProviders logs what providers were found for the pod as well as any errors that were encountered -// while creating providers. -func logProviders(a admission.Attributes, pod *api.Pod, providers []psp.Provider, providerCreationErrs []error) { - for _, err := range providerCreationErrs { - glog.V(4).Infof("provider creation error: %v", err) - } - - if len(providers) == 0 { - glog.V(4).Infof("unable to validate pod %s (generate: %s) in namespace %s against any provider.", pod.Name, pod.GenerateName, a.GetNamespace()) - return - } - - names := make([]string, len(providers)) - for i, p := range providers { - names[i] = p.GetPSPName() - } - glog.V(4).Infof("validating pod %s (generate: %s) in namespace %s against providers: %s", pod.Name, pod.GenerateName, a.GetNamespace(), strings.Join(names, ",")) -} diff --git a/plugin/pkg/admission/security/podsecuritypolicy/admission_test.go b/plugin/pkg/admission/security/podsecuritypolicy/admission_test.go index c1dcf9e3fe5..48febd89769 100644 --- a/plugin/pkg/admission/security/podsecuritypolicy/admission_test.go +++ b/plugin/pkg/admission/security/podsecuritypolicy/admission_test.go @@ -28,15 +28,16 @@ import ( apiequality "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/diff" - "k8s.io/apimachinery/pkg/util/sets" kadmission "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/authentication/serviceaccount" "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" - kapi "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper" + "k8s.io/apiserver/pkg/authorization/authorizerfactory" + "k8s.io/kubernetes/pkg/api/legacyscheme" + kapi "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" "k8s.io/kubernetes/pkg/apis/extensions" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" - extensionslisters "k8s.io/kubernetes/pkg/client/listers/extensions/internalversion" "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/security/apparmor" kpsp "k8s.io/kubernetes/pkg/security/podsecuritypolicy" @@ -46,32 +47,42 @@ import ( const defaultContainerName = "test-c" -// NewTestAdmission provides an admission plugin with test implementations of internal structs. It uses -// an authorizer that always returns true. -func NewTestAdmission(lister extensionslisters.PodSecurityPolicyLister) kadmission.Interface { - return &podSecurityPolicyPlugin{ +// NewTestAdmission provides an admission plugin with test implementations of internal structs. +func NewTestAdmission(psps []*extensions.PodSecurityPolicy, authz authorizer.Authorizer) *PodSecurityPolicyPlugin { + informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) + store := informerFactory.Extensions().InternalVersion().PodSecurityPolicies().Informer().GetStore() + for _, psp := range psps { + store.Add(psp) + } + lister := informerFactory.Extensions().InternalVersion().PodSecurityPolicies().Lister() + if authz == nil { + authz = &TestAuthorizer{} + } + return &PodSecurityPolicyPlugin{ Handler: kadmission.NewHandler(kadmission.Create, kadmission.Update), strategyFactory: kpsp.NewSimpleStrategyFactory(), - pspMatcher: getMatchingPolicies, - authz: &TestAuthorizer{}, + authz: authz, lister: lister, } } -// TestAlwaysAllowedAuthorizer is a testing struct for testing that fulfills the authorizer interface. +// TestAuthorizer is a testing struct for testing that fulfills the authorizer interface. type TestAuthorizer struct { // usernameToNamespaceToAllowedPSPs contains the map of allowed PSPs. // if nil, all PSPs are allowed. usernameToNamespaceToAllowedPSPs map[string]map[string]map[string]bool } -func (t *TestAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) { +func (t *TestAuthorizer) Authorize(a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) { if t.usernameToNamespaceToAllowedPSPs == nil { - return true, "", nil + return authorizer.DecisionAllow, "", nil } allowedInNamespace := t.usernameToNamespaceToAllowedPSPs[a.GetUser().GetName()][a.GetNamespace()][a.GetName()] allowedClusterWide := t.usernameToNamespaceToAllowedPSPs[a.GetUser().GetName()][""][a.GetName()] - return (allowedInNamespace || allowedClusterWide), "", nil + if allowedInNamespace || allowedClusterWide { + return authorizer.DecisionAllow, "", nil + } + return authorizer.DecisionNoOpinion, "", nil } var _ authorizer.Authorizer = &TestAuthorizer{} @@ -85,35 +96,40 @@ func useInitContainers(pod *kapi.Pod) *kapi.Pod { func TestAdmitSeccomp(t *testing.T) { containerName := "container" tests := map[string]struct { - pspAnnotations map[string]string - podAnnotations map[string]string - shouldAdmit bool + pspAnnotations map[string]string + podAnnotations map[string]string + shouldPassAdmit bool + shouldPassValidate bool }{ "no seccomp, no pod annotations": { - pspAnnotations: nil, - podAnnotations: nil, - shouldAdmit: true, + pspAnnotations: nil, + podAnnotations: nil, + shouldPassAdmit: true, + shouldPassValidate: true, }, "no seccomp, pod annotations": { pspAnnotations: nil, podAnnotations: map[string]string{ kapi.SeccompPodAnnotationKey: "foo", }, - shouldAdmit: false, + shouldPassAdmit: false, + shouldPassValidate: false, }, "no seccomp, container annotations": { pspAnnotations: nil, podAnnotations: map[string]string{ kapi.SeccompContainerAnnotationKeyPrefix + containerName: "foo", }, - shouldAdmit: false, + shouldPassAdmit: false, + shouldPassValidate: false, }, "seccomp, allow any no pod annotation": { pspAnnotations: map[string]string{ seccomp.AllowedProfilesAnnotationKey: seccomp.AllowAny, }, - podAnnotations: nil, - shouldAdmit: true, + podAnnotations: nil, + shouldPassAdmit: true, + shouldPassValidate: true, }, "seccomp, allow any pod annotation": { pspAnnotations: map[string]string{ @@ -122,7 +138,8 @@ func TestAdmitSeccomp(t *testing.T) { podAnnotations: map[string]string{ kapi.SeccompPodAnnotationKey: "foo", }, - shouldAdmit: true, + shouldPassAdmit: true, + shouldPassValidate: true, }, "seccomp, allow any container annotation": { pspAnnotations: map[string]string{ @@ -131,7 +148,8 @@ func TestAdmitSeccomp(t *testing.T) { podAnnotations: map[string]string{ kapi.SeccompContainerAnnotationKeyPrefix + containerName: "foo", }, - shouldAdmit: true, + shouldPassAdmit: true, + shouldPassValidate: true, }, "seccomp, allow specific pod annotation failure": { pspAnnotations: map[string]string{ @@ -140,7 +158,8 @@ func TestAdmitSeccomp(t *testing.T) { podAnnotations: map[string]string{ kapi.SeccompPodAnnotationKey: "bar", }, - shouldAdmit: false, + shouldPassAdmit: false, + shouldPassValidate: false, }, "seccomp, allow specific container annotation failure": { pspAnnotations: map[string]string{ @@ -151,7 +170,8 @@ func TestAdmitSeccomp(t *testing.T) { podAnnotations: map[string]string{ kapi.SeccompContainerAnnotationKeyPrefix + containerName: "bar", }, - shouldAdmit: false, + shouldPassAdmit: false, + shouldPassValidate: false, }, "seccomp, allow specific pod annotation pass": { pspAnnotations: map[string]string{ @@ -160,7 +180,8 @@ func TestAdmitSeccomp(t *testing.T) { podAnnotations: map[string]string{ kapi.SeccompPodAnnotationKey: "foo", }, - shouldAdmit: true, + shouldPassAdmit: true, + shouldPassValidate: true, }, "seccomp, allow specific container annotation pass": { pspAnnotations: map[string]string{ @@ -171,7 +192,8 @@ func TestAdmitSeccomp(t *testing.T) { podAnnotations: map[string]string{ kapi.SeccompContainerAnnotationKeyPrefix + containerName: "bar", }, - shouldAdmit: true, + shouldPassAdmit: true, + shouldPassValidate: true, }, } for k, v := range tests { @@ -187,7 +209,7 @@ func TestAdmitSeccomp(t *testing.T) { }, }, } - testPSPAdmit(k, []*extensions.PodSecurityPolicy{psp}, pod, v.shouldAdmit, psp.Name, t) + testPSPAdmit(k, []*extensions.PodSecurityPolicy{psp}, pod, v.shouldPassAdmit, v.shouldPassValidate, psp.Name, t) } } @@ -210,58 +232,65 @@ func TestAdmitPrivileged(t *testing.T) { falseValue := false tests := map[string]struct { - pod *kapi.Pod - psps []*extensions.PodSecurityPolicy - shouldPass bool - expectedPriv *bool - expectedPSP string + pod *kapi.Pod + psps []*extensions.PodSecurityPolicy + shouldPassAdmit bool + shouldPassValidate bool + expectedPriv *bool + expectedPSP string }{ "pod with priv=nil allowed under non priv PSP": { - pod: goodPod(), - psps: []*extensions.PodSecurityPolicy{nonPrivilegedPSP}, - shouldPass: true, - expectedPriv: nil, - expectedPSP: nonPrivilegedPSP.Name, + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{nonPrivilegedPSP}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPriv: nil, + expectedPSP: nonPrivilegedPSP.Name, }, "pod with priv=nil allowed under priv PSP": { - pod: goodPod(), - psps: []*extensions.PodSecurityPolicy{privilegedPSP}, - shouldPass: true, - expectedPriv: nil, - expectedPSP: privilegedPSP.Name, + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{privilegedPSP}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPriv: nil, + expectedPSP: privilegedPSP.Name, }, "pod with priv=false allowed under non priv PSP": { - pod: createPodWithPriv(false), - psps: []*extensions.PodSecurityPolicy{nonPrivilegedPSP}, - shouldPass: true, - expectedPriv: &falseValue, - expectedPSP: nonPrivilegedPSP.Name, + pod: createPodWithPriv(false), + psps: []*extensions.PodSecurityPolicy{nonPrivilegedPSP}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPriv: &falseValue, + expectedPSP: nonPrivilegedPSP.Name, }, "pod with priv=false allowed under priv PSP": { - pod: createPodWithPriv(false), - psps: []*extensions.PodSecurityPolicy{privilegedPSP}, - shouldPass: true, - expectedPriv: &falseValue, - expectedPSP: privilegedPSP.Name, + pod: createPodWithPriv(false), + psps: []*extensions.PodSecurityPolicy{privilegedPSP}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPriv: &falseValue, + expectedPSP: privilegedPSP.Name, }, "pod with priv=true denied by non priv PSP": { - pod: createPodWithPriv(true), - psps: []*extensions.PodSecurityPolicy{nonPrivilegedPSP}, - shouldPass: false, + pod: createPodWithPriv(true), + psps: []*extensions.PodSecurityPolicy{nonPrivilegedPSP}, + shouldPassAdmit: false, + shouldPassValidate: false, }, "pod with priv=true allowed by priv PSP": { - pod: createPodWithPriv(true), - psps: []*extensions.PodSecurityPolicy{nonPrivilegedPSP, privilegedPSP}, - shouldPass: true, - expectedPriv: &trueValue, - expectedPSP: privilegedPSP.Name, + pod: createPodWithPriv(true), + psps: []*extensions.PodSecurityPolicy{nonPrivilegedPSP, privilegedPSP}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPriv: &trueValue, + expectedPSP: privilegedPSP.Name, }, } for k, v := range tests { - testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) + testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) - if v.shouldPass { + if v.shouldPassAdmit { priv := v.pod.Spec.Containers[0].SecurityContext.Privileged if (priv == nil) != (v.expectedPriv == nil) { t.Errorf("%s expected privileged to be %v, got %v", k, v.expectedPriv, priv) @@ -274,12 +303,12 @@ func TestAdmitPrivileged(t *testing.T) { func defaultPod(t *testing.T, pod *kapi.Pod) *kapi.Pod { v1Pod := &v1.Pod{} - if err := kapi.Scheme.Convert(pod, v1Pod, nil); err != nil { + if err := legacyscheme.Scheme.Convert(pod, v1Pod, nil); err != nil { t.Fatal(err) } - kapi.Scheme.Default(v1Pod) + legacyscheme.Scheme.Default(v1Pod) apiPod := &kapi.Pod{} - if err := kapi.Scheme.Convert(v1Pod, apiPod, nil); err != nil { + if err := legacyscheme.Scheme.Convert(v1Pod, apiPod, nil); err != nil { t.Fatal(err) } return apiPod @@ -307,18 +336,29 @@ func TestAdmitPreferNonmutating(t *testing.T) { changedPod := unprivilegedRunAsAnyPod.DeepCopy() changedPod.Spec.Containers[0].Image = "myimage2" + podWithSC := unprivilegedRunAsAnyPod.DeepCopy() + podWithSC.Annotations = map[string]string{psputil.ValidatedPSPAnnotation: privilegedPSP.Name} + changedPodWithSC := changedPod.DeepCopy() + changedPodWithSC.Annotations = map[string]string{psputil.ValidatedPSPAnnotation: privilegedPSP.Name} + gcChangedPod := unprivilegedRunAsAnyPod.DeepCopy() gcChangedPod.OwnerReferences = []metav1.OwnerReference{{Kind: "Foo", Name: "bar"}} gcChangedPod.Finalizers = []string{"foo"} + podWithAnnotation := unprivilegedRunAsAnyPod.DeepCopy() + podWithAnnotation.ObjectMeta.Annotations = map[string]string{ + // "mutating2" is lexicographically behind "mutating1", so "mutating1" should be + // chosen because it's the canonical PSP order. + psputil.ValidatedPSPAnnotation: mutating2.Name, + } + tests := map[string]struct { operation kadmission.Operation pod *kapi.Pod - oldPod *kapi.Pod + podBeforeUpdate *kapi.Pod psps []*extensions.PodSecurityPolicy - shouldPass bool + shouldPassValidate bool expectMutation bool - expectedPodUser *int64 expectedContainerUser *int64 expectedPSP string }{ @@ -326,9 +366,8 @@ func TestAdmitPreferNonmutating(t *testing.T) { operation: kadmission.Create, pod: unprivilegedRunAsAnyPod.DeepCopy(), psps: []*extensions.PodSecurityPolicy{privilegedPSP}, - shouldPass: true, + shouldPassValidate: true, expectMutation: false, - expectedPodUser: nil, expectedContainerUser: nil, expectedPSP: privilegedPSP.Name, }, @@ -336,9 +375,8 @@ func TestAdmitPreferNonmutating(t *testing.T) { operation: kadmission.Create, pod: unprivilegedRunAsAnyPod.DeepCopy(), psps: []*extensions.PodSecurityPolicy{mutating2, mutating1, privilegedPSP}, - shouldPass: true, + shouldPassValidate: true, expectMutation: false, - expectedPodUser: nil, expectedContainerUser: nil, expectedPSP: privilegedPSP.Name, }, @@ -346,96 +384,104 @@ func TestAdmitPreferNonmutating(t *testing.T) { operation: kadmission.Create, pod: unprivilegedRunAsAnyPod.DeepCopy(), psps: []*extensions.PodSecurityPolicy{mutating2, mutating1}, - shouldPass: true, + shouldPassValidate: true, + expectMutation: true, + expectedContainerUser: &mutating1.Spec.RunAsUser.Ranges[0].Min, + expectedPSP: mutating1.Name, + }, + "pod should use deterministic mutating PSP on create even if ValidatedPSPAnnotation is set": { + operation: kadmission.Create, + pod: podWithAnnotation, + psps: []*extensions.PodSecurityPolicy{mutating2, mutating1}, + shouldPassValidate: true, expectMutation: true, - expectedPodUser: nil, expectedContainerUser: &mutating1.Spec.RunAsUser.Ranges[0].Min, expectedPSP: mutating1.Name, }, "pod should prefer non-mutating PSP on update": { operation: kadmission.Update, - pod: unprivilegedRunAsAnyPod.DeepCopy(), - oldPod: changedPod.DeepCopy(), + pod: changedPodWithSC.DeepCopy(), + podBeforeUpdate: podWithSC.DeepCopy(), psps: []*extensions.PodSecurityPolicy{mutating2, mutating1, privilegedPSP}, - shouldPass: true, + shouldPassValidate: true, expectMutation: false, - expectedPodUser: nil, expectedContainerUser: nil, expectedPSP: privilegedPSP.Name, }, - "pod should not allow mutation on update": { + "pod should not mutate on update, but fail validation": { operation: kadmission.Update, - pod: unprivilegedRunAsAnyPod.DeepCopy(), - oldPod: changedPod.DeepCopy(), + pod: changedPod.DeepCopy(), + podBeforeUpdate: unprivilegedRunAsAnyPod.DeepCopy(), psps: []*extensions.PodSecurityPolicy{mutating2, mutating1}, - shouldPass: false, + shouldPassValidate: false, expectMutation: false, - expectedPodUser: nil, expectedContainerUser: nil, expectedPSP: "", }, "pod should be allowed if completely unchanged on update": { operation: kadmission.Update, pod: unprivilegedRunAsAnyPod.DeepCopy(), - oldPod: unprivilegedRunAsAnyPod.DeepCopy(), + podBeforeUpdate: unprivilegedRunAsAnyPod.DeepCopy(), psps: []*extensions.PodSecurityPolicy{mutating2, mutating1}, - shouldPass: true, + shouldPassValidate: true, expectMutation: false, - expectedPodUser: nil, expectedContainerUser: nil, expectedPSP: "", }, "pod should be allowed if unchanged on update except finalizers,ownerrefs": { operation: kadmission.Update, - pod: unprivilegedRunAsAnyPod.DeepCopy(), - oldPod: gcChangedPod.DeepCopy(), + pod: gcChangedPod.DeepCopy(), + podBeforeUpdate: unprivilegedRunAsAnyPod.DeepCopy(), psps: []*extensions.PodSecurityPolicy{mutating2, mutating1}, - shouldPass: true, + shouldPassValidate: true, expectMutation: false, - expectedPodUser: nil, expectedContainerUser: nil, expectedPSP: "", }, } for k, v := range tests { - testPSPAdmitAdvanced(k, v.operation, v.psps, v.pod, v.oldPod, v.shouldPass, v.expectMutation, v.expectedPSP, t) + testPSPAdmitAdvanced(k, v.operation, v.psps, nil, &user.DefaultInfo{}, v.pod, v.podBeforeUpdate, true, v.shouldPassValidate, v.expectMutation, v.expectedPSP, t) - if v.shouldPass { - actualPodUser := (*int64)(nil) - if v.pod.Spec.SecurityContext != nil { - actualPodUser = v.pod.Spec.SecurityContext.RunAsUser - } - if (actualPodUser == nil) != (v.expectedPodUser == nil) { - t.Errorf("%s expected pod user %v, got %v", k, v.expectedPodUser, actualPodUser) - } else if actualPodUser != nil && *actualPodUser != *v.expectedPodUser { - t.Errorf("%s expected pod user %v, got %v", k, *v.expectedPodUser, *actualPodUser) - } + actualPodUser := (*int64)(nil) + if v.pod.Spec.SecurityContext != nil { + actualPodUser = v.pod.Spec.SecurityContext.RunAsUser + } + if actualPodUser != nil { + t.Errorf("%s expected pod user nil, got %v", k, *actualPodUser) + } - actualContainerUser := (*int64)(nil) - if v.pod.Spec.Containers[0].SecurityContext != nil { - actualContainerUser = v.pod.Spec.Containers[0].SecurityContext.RunAsUser - } - if (actualContainerUser == nil) != (v.expectedContainerUser == nil) { - t.Errorf("%s expected container user %v, got %v", k, v.expectedContainerUser, actualContainerUser) - } else if actualContainerUser != nil && *actualContainerUser != *v.expectedContainerUser { - t.Errorf("%s expected container user %v, got %v", k, *v.expectedContainerUser, *actualContainerUser) - } + actualContainerUser := (*int64)(nil) + if v.pod.Spec.Containers[0].SecurityContext != nil { + actualContainerUser = v.pod.Spec.Containers[0].SecurityContext.RunAsUser + } + if (actualContainerUser == nil) != (v.expectedContainerUser == nil) { + t.Errorf("%s expected container user %v, got %v", k, v.expectedContainerUser, actualContainerUser) + } else if actualContainerUser != nil && *actualContainerUser != *v.expectedContainerUser { + t.Errorf("%s expected container user %v, got %v", k, *v.expectedContainerUser, *actualContainerUser) } } } func TestFailClosedOnInvalidPod(t *testing.T) { - plugin := NewTestAdmission(nil) + plugin := NewTestAdmission(nil, nil) pod := &v1.Pod{} attrs := kadmission.NewAttributesRecord(pod, nil, kapi.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, kapi.Resource("pods").WithVersion("version"), "", kadmission.Create, &user.DefaultInfo{}) - err := plugin.Admit(attrs) + err := plugin.Admit(attrs) if err == nil { - t.Fatalf("expected versioned pod object to fail admission") + t.Fatalf("expected versioned pod object to fail mutating admission") } if !strings.Contains(err.Error(), "unexpected type") { - t.Errorf("expected type error but got: %v", err) + t.Errorf("expected type error on Admit but got: %v", err) + } + + err = plugin.Validate(attrs) + if err == nil { + t.Fatalf("expected versioned pod object to fail validating admission") + } + if !strings.Contains(err.Error(), "unexpected type") { + t.Errorf("expected type error on Validate but got: %v", err) } } @@ -467,53 +513,60 @@ func TestAdmitCaps(t *testing.T) { tc := map[string]struct { pod *kapi.Pod psps []*extensions.PodSecurityPolicy - shouldPass bool + shouldPassAdmit bool + shouldPassValidate bool expectedCapabilities *kapi.Capabilities expectedPSP string }{ // UC 1: if a PSP does not define allowed or required caps then a pod requesting a cap // should be rejected. "should reject cap add when not allowed or required": { - pod: createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}), - psps: []*extensions.PodSecurityPolicy{restricted}, - shouldPass: false, + pod: createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}), + psps: []*extensions.PodSecurityPolicy{restricted}, + shouldPassAdmit: false, + shouldPassValidate: false, }, // UC 2: if a PSP allows a cap in the allowed field it should accept the pod request // to add the cap. "should accept cap add when in allowed": { - pod: createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}), - psps: []*extensions.PodSecurityPolicy{restricted, allowsFooInAllowed}, - shouldPass: true, - expectedPSP: allowsFooInAllowed.Name, + pod: createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}), + psps: []*extensions.PodSecurityPolicy{restricted, allowsFooInAllowed}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPSP: allowsFooInAllowed.Name, }, // UC 3: if a PSP requires a cap then it should accept the pod request // to add the cap. "should accept cap add when in required": { - pod: createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}), - psps: []*extensions.PodSecurityPolicy{restricted, allowsFooInRequired}, - shouldPass: true, - expectedPSP: allowsFooInRequired.Name, + pod: createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}), + psps: []*extensions.PodSecurityPolicy{restricted, allowsFooInRequired}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPSP: allowsFooInRequired.Name, }, // UC 4: if a PSP requires a cap to be dropped then it should fail both // in the verification of adds and verification of drops "should reject cap add when requested cap is required to be dropped": { - pod: createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}), - psps: []*extensions.PodSecurityPolicy{restricted, requiresFooToBeDropped}, - shouldPass: false, + pod: createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}), + psps: []*extensions.PodSecurityPolicy{restricted, requiresFooToBeDropped}, + shouldPassAdmit: false, + shouldPassValidate: false, }, // UC 5: if a PSP requires a cap to be dropped it should accept // a manual request to drop the cap. "should accept cap drop when cap is required to be dropped": { - pod: createPodWithCaps(&kapi.Capabilities{Drop: []kapi.Capability{"foo"}}), - psps: []*extensions.PodSecurityPolicy{requiresFooToBeDropped}, - shouldPass: true, - expectedPSP: requiresFooToBeDropped.Name, + pod: createPodWithCaps(&kapi.Capabilities{Drop: []kapi.Capability{"foo"}}), + psps: []*extensions.PodSecurityPolicy{requiresFooToBeDropped}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPSP: requiresFooToBeDropped.Name, }, // UC 6: required add is defaulted "required add is defaulted": { - pod: goodPod(), - psps: []*extensions.PodSecurityPolicy{allowsFooInRequired}, - shouldPass: true, + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{allowsFooInRequired}, + shouldPassAdmit: true, + shouldPassValidate: true, expectedCapabilities: &kapi.Capabilities{ Add: []kapi.Capability{"foo"}, }, @@ -521,9 +574,10 @@ func TestAdmitCaps(t *testing.T) { }, // UC 7: required drop is defaulted "required drop is defaulted": { - pod: goodPod(), - psps: []*extensions.PodSecurityPolicy{requiresFooToBeDropped}, - shouldPass: true, + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{requiresFooToBeDropped}, + shouldPassAdmit: true, + shouldPassValidate: true, expectedCapabilities: &kapi.Capabilities{ Drop: []kapi.Capability{"foo"}, }, @@ -531,15 +585,16 @@ func TestAdmitCaps(t *testing.T) { }, // UC 8: using '*' in allowed caps "should accept cap add when all caps are allowed": { - pod: createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}), - psps: []*extensions.PodSecurityPolicy{restricted, allowAllInAllowed}, - shouldPass: true, - expectedPSP: allowAllInAllowed.Name, + pod: createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}), + psps: []*extensions.PodSecurityPolicy{restricted, allowAllInAllowed}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPSP: allowAllInAllowed.Name, }, } for k, v := range tc { - testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) + testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) if v.expectedCapabilities != nil { if !reflect.DeepEqual(v.expectedCapabilities, v.pod.Spec.Containers[0].SecurityContext.Capabilities) { @@ -550,7 +605,7 @@ func TestAdmitCaps(t *testing.T) { for k, v := range tc { useInitContainers(v.pod) - testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) + testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) if v.expectedCapabilities != nil { if !reflect.DeepEqual(v.expectedCapabilities, v.pod.Spec.InitContainers[0].SecurityContext.Capabilities) { @@ -589,19 +644,19 @@ func TestAdmitVolumes(t *testing.T) { psp := restrictivePSP() // expect a denial for this PSP - testPSPAdmit(fmt.Sprintf("%s denial", string(fsType)), []*extensions.PodSecurityPolicy{psp}, pod, false, "", t) + testPSPAdmit(fmt.Sprintf("%s denial", string(fsType)), []*extensions.PodSecurityPolicy{psp}, pod, false, false, "", t) // also expect a denial for this PSP if it's an init container useInitContainers(pod) - testPSPAdmit(fmt.Sprintf("%s denial", string(fsType)), []*extensions.PodSecurityPolicy{psp}, pod, false, "", t) + testPSPAdmit(fmt.Sprintf("%s denial", string(fsType)), []*extensions.PodSecurityPolicy{psp}, pod, false, false, "", t) // now add the fstype directly to the psp and it should validate psp.Spec.Volumes = []extensions.FSType{fsType} - testPSPAdmit(fmt.Sprintf("%s direct accept", string(fsType)), []*extensions.PodSecurityPolicy{psp}, pod, true, psp.Name, t) + testPSPAdmit(fmt.Sprintf("%s direct accept", string(fsType)), []*extensions.PodSecurityPolicy{psp}, pod, true, true, psp.Name, t) // now change the psp to allow any volumes and the pod should still validate psp.Spec.Volumes = []extensions.FSType{extensions.All} - testPSPAdmit(fmt.Sprintf("%s wildcard accept", string(fsType)), []*extensions.PodSecurityPolicy{psp}, pod, true, psp.Name, t) + testPSPAdmit(fmt.Sprintf("%s wildcard accept", string(fsType)), []*extensions.PodSecurityPolicy{psp}, pod, true, true, psp.Name, t) } } @@ -623,42 +678,47 @@ func TestAdmitHostNetwork(t *testing.T) { tests := map[string]struct { pod *kapi.Pod psps []*extensions.PodSecurityPolicy - shouldPass bool + shouldPassAdmit bool + shouldPassValidate bool expectedHostNetwork bool expectedPSP string }{ "pod without hostnetwork request allowed under noHostNetwork PSP": { pod: goodPod(), psps: []*extensions.PodSecurityPolicy{noHostNetwork}, - shouldPass: true, + shouldPassAdmit: true, + shouldPassValidate: true, expectedHostNetwork: false, expectedPSP: noHostNetwork.Name, }, "pod without hostnetwork request allowed under hostNetwork PSP": { pod: goodPod(), psps: []*extensions.PodSecurityPolicy{hostNetwork}, - shouldPass: true, + shouldPassAdmit: true, + shouldPassValidate: true, expectedHostNetwork: false, expectedPSP: hostNetwork.Name, }, "pod with hostnetwork request denied by noHostNetwork PSP": { - pod: createPodWithHostNetwork(true), - psps: []*extensions.PodSecurityPolicy{noHostNetwork}, - shouldPass: false, + pod: createPodWithHostNetwork(true), + psps: []*extensions.PodSecurityPolicy{noHostNetwork}, + shouldPassAdmit: false, + shouldPassValidate: false, }, "pod with hostnetwork request allowed by hostNetwork PSP": { pod: createPodWithHostNetwork(true), psps: []*extensions.PodSecurityPolicy{noHostNetwork, hostNetwork}, - shouldPass: true, + shouldPassAdmit: true, + shouldPassValidate: true, expectedHostNetwork: true, expectedPSP: hostNetwork.Name, }, } for k, v := range tests { - testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) + testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) - if v.shouldPass { + if v.shouldPassAdmit { if v.pod.Spec.SecurityContext.HostNetwork != v.expectedHostNetwork { t.Errorf("%s expected hostNetwork to be %t", k, v.expectedHostNetwork) } @@ -668,9 +728,9 @@ func TestAdmitHostNetwork(t *testing.T) { // test again with init containers for k, v := range tests { useInitContainers(v.pod) - testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) + testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) - if v.shouldPass { + if v.shouldPassAdmit { if v.pod.Spec.SecurityContext.HostNetwork != v.expectedHostNetwork { t.Errorf("%s expected hostNetwork to be %t", k, v.expectedHostNetwork) } @@ -697,45 +757,51 @@ func TestAdmitHostPorts(t *testing.T) { } tests := map[string]struct { - pod *kapi.Pod - psps []*extensions.PodSecurityPolicy - shouldPass bool - expectedPSP string + pod *kapi.Pod + psps []*extensions.PodSecurityPolicy + shouldPassAdmit bool + shouldPassValidate bool + expectedPSP string }{ "host port out of range": { - pod: createPodWithHostPorts(11), - psps: []*extensions.PodSecurityPolicy{hostPorts}, - shouldPass: false, + pod: createPodWithHostPorts(11), + psps: []*extensions.PodSecurityPolicy{hostPorts}, + shouldPassAdmit: false, + shouldPassValidate: false, }, "host port in range": { - pod: createPodWithHostPorts(5), - psps: []*extensions.PodSecurityPolicy{hostPorts}, - shouldPass: true, - expectedPSP: hostPorts.Name, + pod: createPodWithHostPorts(5), + psps: []*extensions.PodSecurityPolicy{hostPorts}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPSP: hostPorts.Name, }, "no host ports with range": { - pod: goodPod(), - psps: []*extensions.PodSecurityPolicy{hostPorts}, - shouldPass: true, - expectedPSP: hostPorts.Name, + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{hostPorts}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPSP: hostPorts.Name, }, "no host ports without range": { - pod: goodPod(), - psps: []*extensions.PodSecurityPolicy{noHostPorts}, - shouldPass: true, - expectedPSP: noHostPorts.Name, + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{noHostPorts}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPSP: noHostPorts.Name, }, "host ports without range": { - pod: createPodWithHostPorts(5), - psps: []*extensions.PodSecurityPolicy{noHostPorts}, - shouldPass: false, + pod: createPodWithHostPorts(5), + psps: []*extensions.PodSecurityPolicy{noHostPorts}, + shouldPassAdmit: false, + shouldPassValidate: false, }, } for i := 0; i < 2; i++ { for k, v := range tests { v.pod.Spec.Containers, v.pod.Spec.InitContainers = v.pod.Spec.InitContainers, v.pod.Spec.Containers - testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) + testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) } } } @@ -756,44 +822,48 @@ func TestAdmitHostPID(t *testing.T) { hostPID.Spec.HostPID = true tests := map[string]struct { - pod *kapi.Pod - psps []*extensions.PodSecurityPolicy - shouldPass bool - expectedHostPID bool - expectedPSP string + pod *kapi.Pod + psps []*extensions.PodSecurityPolicy + shouldPassAdmit bool + shouldPassValidate bool + expectedHostPID bool + expectedPSP string }{ "pod without hostpid request allowed under noHostPID PSP": { - pod: goodPod(), - psps: []*extensions.PodSecurityPolicy{noHostPID}, - shouldPass: true, - expectedHostPID: false, - expectedPSP: noHostPID.Name, + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{noHostPID}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedHostPID: false, + expectedPSP: noHostPID.Name, }, "pod without hostpid request allowed under hostPID PSP": { - pod: goodPod(), - psps: []*extensions.PodSecurityPolicy{hostPID}, - shouldPass: true, - expectedHostPID: false, - expectedPSP: hostPID.Name, + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{hostPID}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedHostPID: false, + expectedPSP: hostPID.Name, }, "pod with hostpid request denied by noHostPID PSP": { - pod: createPodWithHostPID(true), - psps: []*extensions.PodSecurityPolicy{noHostPID}, - shouldPass: false, + pod: createPodWithHostPID(true), + psps: []*extensions.PodSecurityPolicy{noHostPID}, + shouldPassAdmit: false, }, "pod with hostpid request allowed by hostPID PSP": { - pod: createPodWithHostPID(true), - psps: []*extensions.PodSecurityPolicy{noHostPID, hostPID}, - shouldPass: true, - expectedHostPID: true, - expectedPSP: hostPID.Name, + pod: createPodWithHostPID(true), + psps: []*extensions.PodSecurityPolicy{noHostPID, hostPID}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedHostPID: true, + expectedPSP: hostPID.Name, }, } for k, v := range tests { - testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) + testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) - if v.shouldPass { + if v.shouldPassAdmit { if v.pod.Spec.SecurityContext.HostPID != v.expectedHostPID { t.Errorf("%s expected hostPID to be %t", k, v.expectedHostPID) } @@ -817,44 +887,49 @@ func TestAdmitHostIPC(t *testing.T) { hostIPC.Spec.HostIPC = true tests := map[string]struct { - pod *kapi.Pod - psps []*extensions.PodSecurityPolicy - shouldPass bool - expectedHostIPC bool - expectedPSP string + pod *kapi.Pod + psps []*extensions.PodSecurityPolicy + shouldPassAdmit bool + shouldPassValidate bool + expectedHostIPC bool + expectedPSP string }{ "pod without hostIPC request allowed under noHostIPC PSP": { - pod: goodPod(), - psps: []*extensions.PodSecurityPolicy{noHostIPC}, - shouldPass: true, - expectedHostIPC: false, - expectedPSP: noHostIPC.Name, + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{noHostIPC}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedHostIPC: false, + expectedPSP: noHostIPC.Name, }, "pod without hostIPC request allowed under hostIPC PSP": { - pod: goodPod(), - psps: []*extensions.PodSecurityPolicy{hostIPC}, - shouldPass: true, - expectedHostIPC: false, - expectedPSP: hostIPC.Name, + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{hostIPC}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedHostIPC: false, + expectedPSP: hostIPC.Name, }, "pod with hostIPC request denied by noHostIPC PSP": { - pod: createPodWithHostIPC(true), - psps: []*extensions.PodSecurityPolicy{noHostIPC}, - shouldPass: false, + pod: createPodWithHostIPC(true), + psps: []*extensions.PodSecurityPolicy{noHostIPC}, + shouldPassAdmit: false, + shouldPassValidate: false, }, "pod with hostIPC request allowed by hostIPC PSP": { - pod: createPodWithHostIPC(true), - psps: []*extensions.PodSecurityPolicy{noHostIPC, hostIPC}, - shouldPass: true, - expectedHostIPC: true, - expectedPSP: hostIPC.Name, + pod: createPodWithHostIPC(true), + psps: []*extensions.PodSecurityPolicy{noHostIPC, hostIPC}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedHostIPC: true, + expectedPSP: hostIPC.Name, }, } for k, v := range tests { - testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) + testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) - if v.shouldPass { + if v.shouldPassAdmit { if v.pod.Spec.SecurityContext.HostIPC != v.expectedHostIPC { t.Errorf("%s expected hostIPC to be %t", k, v.expectedHostIPC) } @@ -887,7 +962,8 @@ func TestAdmitSELinux(t *testing.T) { tests := map[string]struct { pod *kapi.Pod psps []*extensions.PodSecurityPolicy - shouldPass bool + shouldPassAdmit bool + shouldPassValidate bool expectedPodSC *kapi.PodSecurityContext expectedContainerSC *kapi.SecurityContext expectedPSP string @@ -895,7 +971,8 @@ func TestAdmitSELinux(t *testing.T) { "runAsAny with no request": { pod: createPodWithSecurityContexts(nil, nil), psps: []*extensions.PodSecurityPolicy{runAsAny}, - shouldPass: true, + shouldPassAdmit: true, + shouldPassValidate: true, expectedPodSC: nil, expectedContainerSC: nil, expectedPSP: runAsAny.Name, @@ -903,7 +980,8 @@ func TestAdmitSELinux(t *testing.T) { "runAsAny with empty pod request": { pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{}, nil), psps: []*extensions.PodSecurityPolicy{runAsAny}, - shouldPass: true, + shouldPassAdmit: true, + shouldPassValidate: true, expectedPodSC: &kapi.PodSecurityContext{}, expectedContainerSC: nil, expectedPSP: runAsAny.Name, @@ -911,7 +989,8 @@ func TestAdmitSELinux(t *testing.T) { "runAsAny with empty container request": { pod: createPodWithSecurityContexts(nil, &kapi.SecurityContext{}), psps: []*extensions.PodSecurityPolicy{runAsAny}, - shouldPass: true, + shouldPassAdmit: true, + shouldPassValidate: true, expectedPodSC: nil, expectedContainerSC: &kapi.SecurityContext{}, expectedPSP: runAsAny.Name, @@ -920,7 +999,8 @@ func TestAdmitSELinux(t *testing.T) { "runAsAny with pod request": { pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}, nil), psps: []*extensions.PodSecurityPolicy{runAsAny}, - shouldPass: true, + shouldPassAdmit: true, + shouldPassValidate: true, expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}, expectedContainerSC: nil, expectedPSP: runAsAny.Name, @@ -928,7 +1008,8 @@ func TestAdmitSELinux(t *testing.T) { "runAsAny with container request": { pod: createPodWithSecurityContexts(nil, &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}), psps: []*extensions.PodSecurityPolicy{runAsAny}, - shouldPass: true, + shouldPassAdmit: true, + shouldPassValidate: true, expectedPodSC: nil, expectedContainerSC: &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}, expectedPSP: runAsAny.Name, @@ -936,26 +1017,30 @@ func TestAdmitSELinux(t *testing.T) { "runAsAny with pod and container request": { pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "bar"}}, &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}), psps: []*extensions.PodSecurityPolicy{runAsAny}, - shouldPass: true, + shouldPassAdmit: true, + shouldPassValidate: true, expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "bar"}}, expectedContainerSC: &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}, expectedPSP: runAsAny.Name, }, "mustRunAs with bad pod request": { - pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}, nil), - psps: []*extensions.PodSecurityPolicy{mustRunAs}, - shouldPass: false, + pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}, nil), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPassAdmit: false, + shouldPassValidate: false, }, "mustRunAs with bad container request": { - pod: createPodWithSecurityContexts(nil, &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}), - psps: []*extensions.PodSecurityPolicy{mustRunAs}, - shouldPass: false, + pod: createPodWithSecurityContexts(nil, &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPassAdmit: false, + shouldPassValidate: false, }, "mustRunAs with no request": { pod: createPodWithSecurityContexts(nil, nil), psps: []*extensions.PodSecurityPolicy{mustRunAs}, - shouldPass: true, + shouldPassAdmit: true, + shouldPassValidate: true, expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: mustRunAs.Spec.SELinux.SELinuxOptions}, expectedContainerSC: nil, expectedPSP: mustRunAs.Name, @@ -966,7 +1051,8 @@ func TestAdmitSELinux(t *testing.T) { nil, ), psps: []*extensions.PodSecurityPolicy{mustRunAs}, - shouldPass: true, + shouldPassAdmit: true, + shouldPassValidate: true, expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: mustRunAs.Spec.SELinux.SELinuxOptions}, expectedContainerSC: nil, expectedPSP: mustRunAs.Name, @@ -977,7 +1063,8 @@ func TestAdmitSELinux(t *testing.T) { nil, ), psps: []*extensions.PodSecurityPolicy{mustRunAs}, - shouldPass: true, + shouldPassAdmit: true, + shouldPassValidate: true, expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: mustRunAs.Spec.SELinux.SELinuxOptions}, expectedContainerSC: nil, expectedPSP: mustRunAs.Name, @@ -985,9 +1072,9 @@ func TestAdmitSELinux(t *testing.T) { } for k, v := range tests { - testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) + testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) - if v.shouldPass { + if v.shouldPassAdmit { if !reflect.DeepEqual(v.expectedPodSC, v.pod.Spec.SecurityContext) { t.Errorf("%s unexpected diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedPodSC, v.pod.Spec.SecurityContext)) } @@ -1021,57 +1108,65 @@ func TestAdmitAppArmor(t *testing.T) { } tests := map[string]struct { - pod *kapi.Pod - psp *extensions.PodSecurityPolicy - shouldPass bool - expectedProfile string + pod *kapi.Pod + psp *extensions.PodSecurityPolicy + shouldPassAdmit bool + shouldPassValidate bool + expectedProfile string }{ "unconstrained with no profile": { - pod: goodPod(), - psp: unconstrainedPSP, - shouldPass: true, - expectedProfile: "", + pod: goodPod(), + psp: unconstrainedPSP, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedProfile: "", }, "unconstrained with profile": { - pod: createPodWithAppArmor(apparmor.ProfileRuntimeDefault), - psp: unconstrainedPSP, - shouldPass: true, - expectedProfile: apparmor.ProfileRuntimeDefault, + pod: createPodWithAppArmor(apparmor.ProfileRuntimeDefault), + psp: unconstrainedPSP, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedProfile: apparmor.ProfileRuntimeDefault, }, "unconstrained with default profile": { - pod: goodPod(), - psp: defaultedPSP, - shouldPass: true, - expectedProfile: apparmor.ProfileRuntimeDefault, + pod: goodPod(), + psp: defaultedPSP, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedProfile: apparmor.ProfileRuntimeDefault, }, "AppArmor enforced with no profile": { - pod: goodPod(), - psp: appArmorPSP, - shouldPass: false, + pod: goodPod(), + psp: appArmorPSP, + shouldPassAdmit: false, + shouldPassValidate: false, }, "AppArmor enforced with default profile": { - pod: goodPod(), - psp: appArmorDefaultPSP, - shouldPass: true, - expectedProfile: apparmor.ProfileRuntimeDefault, + pod: goodPod(), + psp: appArmorDefaultPSP, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedProfile: apparmor.ProfileRuntimeDefault, }, "AppArmor enforced with good profile": { - pod: createPodWithAppArmor(apparmor.ProfileNamePrefix + "foo"), - psp: appArmorDefaultPSP, - shouldPass: true, - expectedProfile: apparmor.ProfileNamePrefix + "foo", + pod: createPodWithAppArmor(apparmor.ProfileNamePrefix + "foo"), + psp: appArmorDefaultPSP, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedProfile: apparmor.ProfileNamePrefix + "foo", }, "AppArmor enforced with local profile": { - pod: createPodWithAppArmor(apparmor.ProfileNamePrefix + "bar"), - psp: appArmorPSP, - shouldPass: false, + pod: createPodWithAppArmor(apparmor.ProfileNamePrefix + "bar"), + psp: appArmorPSP, + shouldPassAdmit: false, + shouldPassValidate: false, }, } for k, v := range tests { - testPSPAdmit(k, []*extensions.PodSecurityPolicy{v.psp}, v.pod, v.shouldPass, v.psp.Name, t) + testPSPAdmit(k, []*extensions.PodSecurityPolicy{v.psp}, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.psp.Name, t) - if v.shouldPass { + if v.shouldPassAdmit { assert.Equal(t, v.expectedProfile, apparmor.GetProfileNameFromPodAnnotations(v.pod.Annotations, defaultContainerName), k) } } @@ -1105,7 +1200,8 @@ func TestAdmitRunAsUser(t *testing.T) { tests := map[string]struct { pod *kapi.Pod psps []*extensions.PodSecurityPolicy - shouldPass bool + shouldPassAdmit bool + shouldPassValidate bool expectedPodSC *kapi.PodSecurityContext expectedContainerSC *kapi.SecurityContext expectedPSP string @@ -1113,7 +1209,8 @@ func TestAdmitRunAsUser(t *testing.T) { "runAsAny no pod request": { pod: createPodWithSecurityContexts(nil, nil), psps: []*extensions.PodSecurityPolicy{runAsAny}, - shouldPass: true, + shouldPassAdmit: true, + shouldPassValidate: true, expectedPodSC: nil, expectedContainerSC: nil, expectedPSP: runAsAny.Name, @@ -1121,7 +1218,8 @@ func TestAdmitRunAsUser(t *testing.T) { "runAsAny pod request": { pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), nil), psps: []*extensions.PodSecurityPolicy{runAsAny}, - shouldPass: true, + shouldPassAdmit: true, + shouldPassValidate: true, expectedPodSC: podSC(userIDPtr(1)), expectedContainerSC: nil, expectedPSP: runAsAny.Name, @@ -1129,27 +1227,31 @@ func TestAdmitRunAsUser(t *testing.T) { "runAsAny container request": { pod: createPodWithSecurityContexts(nil, containerSC(userIDPtr(1))), psps: []*extensions.PodSecurityPolicy{runAsAny}, - shouldPass: true, + shouldPassAdmit: true, + shouldPassValidate: true, expectedPodSC: nil, expectedContainerSC: containerSC(userIDPtr(1)), expectedPSP: runAsAny.Name, }, "mustRunAs pod request out of range": { - pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), nil), - psps: []*extensions.PodSecurityPolicy{mustRunAs}, - shouldPass: false, + pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), nil), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPassAdmit: false, + shouldPassValidate: false, }, "mustRunAs container request out of range": { - pod: createPodWithSecurityContexts(podSC(userIDPtr(999)), containerSC(userIDPtr(1))), - psps: []*extensions.PodSecurityPolicy{mustRunAs}, - shouldPass: false, + pod: createPodWithSecurityContexts(podSC(userIDPtr(999)), containerSC(userIDPtr(1))), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPassAdmit: false, + shouldPassValidate: false, }, "mustRunAs pod request in range": { pod: createPodWithSecurityContexts(podSC(userIDPtr(999)), nil), psps: []*extensions.PodSecurityPolicy{mustRunAs}, - shouldPass: true, + shouldPassAdmit: true, + shouldPassValidate: true, expectedPodSC: podSC(&mustRunAs.Spec.RunAsUser.Ranges[0].Min), expectedContainerSC: nil, expectedPSP: mustRunAs.Name, @@ -1157,7 +1259,8 @@ func TestAdmitRunAsUser(t *testing.T) { "mustRunAs container request in range": { pod: createPodWithSecurityContexts(nil, containerSC(userIDPtr(999))), psps: []*extensions.PodSecurityPolicy{mustRunAs}, - shouldPass: true, + shouldPassAdmit: true, + shouldPassValidate: true, expectedPodSC: nil, expectedContainerSC: containerSC(&mustRunAs.Spec.RunAsUser.Ranges[0].Min), expectedPSP: mustRunAs.Name, @@ -1165,7 +1268,8 @@ func TestAdmitRunAsUser(t *testing.T) { "mustRunAs pod and container request in range": { pod: createPodWithSecurityContexts(podSC(userIDPtr(999)), containerSC(userIDPtr(1000))), psps: []*extensions.PodSecurityPolicy{mustRunAs}, - shouldPass: true, + shouldPassAdmit: true, + shouldPassValidate: true, expectedPodSC: podSC(userIDPtr(999)), expectedContainerSC: containerSC(userIDPtr(1000)), expectedPSP: mustRunAs.Name, @@ -1173,7 +1277,8 @@ func TestAdmitRunAsUser(t *testing.T) { "mustRunAs no request": { pod: createPodWithSecurityContexts(nil, nil), psps: []*extensions.PodSecurityPolicy{mustRunAs}, - shouldPass: true, + shouldPassAdmit: true, + shouldPassValidate: true, expectedPodSC: nil, expectedContainerSC: containerSC(&mustRunAs.Spec.RunAsUser.Ranges[0].Min), expectedPSP: mustRunAs.Name, @@ -1182,32 +1287,37 @@ func TestAdmitRunAsUser(t *testing.T) { "runAsNonRoot no request": { pod: createPodWithSecurityContexts(nil, nil), psps: []*extensions.PodSecurityPolicy{runAsNonRoot}, - shouldPass: true, + shouldPassAdmit: true, + shouldPassValidate: true, expectedPodSC: nil, expectedContainerSC: &kapi.SecurityContext{RunAsNonRoot: &trueValue}, expectedPSP: runAsNonRoot.Name, }, "runAsNonRoot pod request root": { - pod: createPodWithSecurityContexts(podSC(userIDPtr(0)), nil), - psps: []*extensions.PodSecurityPolicy{runAsNonRoot}, - shouldPass: false, + pod: createPodWithSecurityContexts(podSC(userIDPtr(0)), nil), + psps: []*extensions.PodSecurityPolicy{runAsNonRoot}, + shouldPassAdmit: false, + shouldPassValidate: false, }, "runAsNonRoot pod request non-root": { - pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), nil), - psps: []*extensions.PodSecurityPolicy{runAsNonRoot}, - shouldPass: true, - expectedPodSC: podSC(userIDPtr(1)), - expectedPSP: runAsNonRoot.Name, + pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), nil), + psps: []*extensions.PodSecurityPolicy{runAsNonRoot}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPodSC: podSC(userIDPtr(1)), + expectedPSP: runAsNonRoot.Name, }, "runAsNonRoot container request root": { - pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), containerSC(userIDPtr(0))), - psps: []*extensions.PodSecurityPolicy{runAsNonRoot}, - shouldPass: false, + pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), containerSC(userIDPtr(0))), + psps: []*extensions.PodSecurityPolicy{runAsNonRoot}, + shouldPassAdmit: false, + shouldPassValidate: false, }, "runAsNonRoot container request non-root": { pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), containerSC(userIDPtr(2))), psps: []*extensions.PodSecurityPolicy{runAsNonRoot}, - shouldPass: true, + shouldPassAdmit: true, + shouldPassValidate: true, expectedPodSC: podSC(userIDPtr(1)), expectedContainerSC: containerSC(userIDPtr(2)), expectedPSP: runAsNonRoot.Name, @@ -1215,9 +1325,9 @@ func TestAdmitRunAsUser(t *testing.T) { } for k, v := range tests { - testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) + testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) - if v.shouldPass { + if v.shouldPassAdmit { if !reflect.DeepEqual(v.expectedPodSC, v.pod.Spec.SecurityContext) { t.Errorf("%s unexpected pod sc diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedPodSC, v.pod.Spec.SecurityContext)) } @@ -1243,65 +1353,73 @@ func TestAdmitSupplementalGroups(t *testing.T) { mustRunAs.Spec.SupplementalGroups.Ranges = []extensions.GroupIDRange{{Min: int64(999), Max: int64(1000)}} tests := map[string]struct { - pod *kapi.Pod - psps []*extensions.PodSecurityPolicy - shouldPass bool - expectedPodSC *kapi.PodSecurityContext - expectedPSP string + pod *kapi.Pod + psps []*extensions.PodSecurityPolicy + shouldPassAdmit bool + shouldPassValidate bool + expectedPodSC *kapi.PodSecurityContext + expectedPSP string }{ "runAsAny no pod request": { - pod: createPodWithSecurityContexts(nil, nil), - psps: []*extensions.PodSecurityPolicy{runAsAny}, - shouldPass: true, - expectedPodSC: nil, - expectedPSP: runAsAny.Name, + pod: createPodWithSecurityContexts(nil, nil), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPodSC: nil, + expectedPSP: runAsAny.Name, }, "runAsAny empty pod request": { - pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{}, nil), - psps: []*extensions.PodSecurityPolicy{runAsAny}, - shouldPass: true, - expectedPodSC: &kapi.PodSecurityContext{}, - expectedPSP: runAsAny.Name, + pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{}, nil), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPodSC: &kapi.PodSecurityContext{}, + expectedPSP: runAsAny.Name, }, "runAsAny empty pod request empty supplemental groups": { - pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SupplementalGroups: []int64{}}, nil), - psps: []*extensions.PodSecurityPolicy{runAsAny}, - shouldPass: true, - expectedPodSC: &kapi.PodSecurityContext{SupplementalGroups: []int64{}}, - expectedPSP: runAsAny.Name, + pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SupplementalGroups: []int64{}}, nil), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPodSC: &kapi.PodSecurityContext{SupplementalGroups: []int64{}}, + expectedPSP: runAsAny.Name, }, "runAsAny pod request": { - pod: createPodWithSecurityContexts(podSC(1), nil), - psps: []*extensions.PodSecurityPolicy{runAsAny}, - shouldPass: true, - expectedPodSC: &kapi.PodSecurityContext{SupplementalGroups: []int64{1}}, - expectedPSP: runAsAny.Name, + pod: createPodWithSecurityContexts(podSC(1), nil), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPodSC: &kapi.PodSecurityContext{SupplementalGroups: []int64{1}}, + expectedPSP: runAsAny.Name, }, "mustRunAs no pod request": { - pod: createPodWithSecurityContexts(nil, nil), - psps: []*extensions.PodSecurityPolicy{mustRunAs}, - shouldPass: true, - expectedPodSC: podSC(mustRunAs.Spec.SupplementalGroups.Ranges[0].Min), - expectedPSP: mustRunAs.Name, + pod: createPodWithSecurityContexts(nil, nil), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPodSC: podSC(mustRunAs.Spec.SupplementalGroups.Ranges[0].Min), + expectedPSP: mustRunAs.Name, }, "mustRunAs bad pod request": { - pod: createPodWithSecurityContexts(podSC(1), nil), - psps: []*extensions.PodSecurityPolicy{mustRunAs}, - shouldPass: false, + pod: createPodWithSecurityContexts(podSC(1), nil), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPassAdmit: false, + shouldPassValidate: false, }, "mustRunAs good pod request": { - pod: createPodWithSecurityContexts(podSC(999), nil), - psps: []*extensions.PodSecurityPolicy{mustRunAs}, - shouldPass: true, - expectedPodSC: podSC(999), - expectedPSP: mustRunAs.Name, + pod: createPodWithSecurityContexts(podSC(999), nil), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPodSC: podSC(999), + expectedPSP: mustRunAs.Name, }, } for k, v := range tests { - testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) + testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) - if v.shouldPass { + if v.shouldPassAdmit { if !reflect.DeepEqual(v.expectedPodSC, v.pod.Spec.SecurityContext) { t.Errorf("%s unexpected pod sc diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedPodSC, v.pod.Spec.SecurityContext)) } @@ -1327,51 +1445,57 @@ func TestAdmitFSGroup(t *testing.T) { mustRunAs.Name = "mustRunAs" tests := map[string]struct { - pod *kapi.Pod - psps []*extensions.PodSecurityPolicy - shouldPass bool - expectedFSGroup *int64 - expectedPSP string + pod *kapi.Pod + psps []*extensions.PodSecurityPolicy + shouldPassAdmit bool + shouldPassValidate bool + expectedFSGroup *int64 + expectedPSP string }{ "runAsAny no pod request": { - pod: goodPod(), - psps: []*extensions.PodSecurityPolicy{runAsAny}, - shouldPass: true, - expectedFSGroup: nil, - expectedPSP: runAsAny.Name, + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedFSGroup: nil, + expectedPSP: runAsAny.Name, }, "runAsAny pod request": { - pod: createPodWithFSGroup(1), - psps: []*extensions.PodSecurityPolicy{runAsAny}, - shouldPass: true, - expectedFSGroup: groupIDPtr(1), - expectedPSP: runAsAny.Name, + pod: createPodWithFSGroup(1), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedFSGroup: groupIDPtr(1), + expectedPSP: runAsAny.Name, }, "mustRunAs no pod request": { - pod: goodPod(), - psps: []*extensions.PodSecurityPolicy{mustRunAs}, - shouldPass: true, - expectedFSGroup: &mustRunAs.Spec.SupplementalGroups.Ranges[0].Min, - expectedPSP: mustRunAs.Name, + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedFSGroup: &mustRunAs.Spec.SupplementalGroups.Ranges[0].Min, + expectedPSP: mustRunAs.Name, }, "mustRunAs bad pod request": { - pod: createPodWithFSGroup(1), - psps: []*extensions.PodSecurityPolicy{mustRunAs}, - shouldPass: false, + pod: createPodWithFSGroup(1), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPassAdmit: false, + shouldPassValidate: false, }, "mustRunAs good pod request": { - pod: createPodWithFSGroup(999), - psps: []*extensions.PodSecurityPolicy{mustRunAs}, - shouldPass: true, - expectedFSGroup: groupIDPtr(999), - expectedPSP: mustRunAs.Name, + pod: createPodWithFSGroup(999), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedFSGroup: groupIDPtr(999), + expectedPSP: mustRunAs.Name, }, } for k, v := range tests { - testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) + testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) - if v.shouldPass { + if v.shouldPassAdmit { if v.pod.Spec.SecurityContext.FSGroup == nil && v.expectedFSGroup == nil { // ok, don't need to worry about identifying specific diffs continue @@ -1407,51 +1531,57 @@ func TestAdmitReadOnlyRootFilesystem(t *testing.T) { rorfs.Spec.ReadOnlyRootFilesystem = true tests := map[string]struct { - pod *kapi.Pod - psps []*extensions.PodSecurityPolicy - shouldPass bool - expectedRORFS bool - expectedPSP string + pod *kapi.Pod + psps []*extensions.PodSecurityPolicy + shouldPassAdmit bool + shouldPassValidate bool + expectedRORFS bool + expectedPSP string }{ "no-rorfs allows pod request with rorfs": { - pod: createPodWithRORFS(true), - psps: []*extensions.PodSecurityPolicy{noRORFS}, - shouldPass: true, - expectedRORFS: true, - expectedPSP: noRORFS.Name, + pod: createPodWithRORFS(true), + psps: []*extensions.PodSecurityPolicy{noRORFS}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedRORFS: true, + expectedPSP: noRORFS.Name, }, "no-rorfs allows pod request without rorfs": { - pod: createPodWithRORFS(false), - psps: []*extensions.PodSecurityPolicy{noRORFS}, - shouldPass: true, - expectedRORFS: false, - expectedPSP: noRORFS.Name, + pod: createPodWithRORFS(false), + psps: []*extensions.PodSecurityPolicy{noRORFS}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedRORFS: false, + expectedPSP: noRORFS.Name, }, "rorfs rejects pod request without rorfs": { - pod: createPodWithRORFS(false), - psps: []*extensions.PodSecurityPolicy{rorfs}, - shouldPass: false, + pod: createPodWithRORFS(false), + psps: []*extensions.PodSecurityPolicy{rorfs}, + shouldPassAdmit: false, + shouldPassValidate: false, }, "rorfs defaults nil pod request": { - pod: goodPod(), - psps: []*extensions.PodSecurityPolicy{rorfs}, - shouldPass: true, - expectedRORFS: true, - expectedPSP: rorfs.Name, + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{rorfs}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedRORFS: true, + expectedPSP: rorfs.Name, }, "rorfs accepts pod request with rorfs": { - pod: createPodWithRORFS(true), - psps: []*extensions.PodSecurityPolicy{rorfs}, - shouldPass: true, - expectedRORFS: true, - expectedPSP: rorfs.Name, + pod: createPodWithRORFS(true), + psps: []*extensions.PodSecurityPolicy{rorfs}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedRORFS: true, + expectedPSP: rorfs.Name, }, } for k, v := range tests { - testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) + testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) - if v.shouldPass { + if v.shouldPassAdmit { if v.pod.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem == nil || *v.pod.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem != v.expectedRORFS { t.Errorf("%s expected ReadOnlyRootFilesystem to be %t but found %#v", k, v.expectedRORFS, v.pod.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem) @@ -1504,116 +1634,136 @@ func TestAdmitSysctls(t *testing.T) { catchallSysctls.Annotations[extensions.SysctlsPodSecurityPolicyAnnotationKey] = "*" tests := map[string]struct { - pod *kapi.Pod - psps []*extensions.PodSecurityPolicy - shouldPass bool - expectedPSP string + pod *kapi.Pod + psps []*extensions.PodSecurityPolicy + shouldPassAdmit bool + shouldPassValidate bool + expectedPSP string }{ "pod without unsafe sysctls request allowed under noSysctls PSP": { - pod: goodPod(), - psps: []*extensions.PodSecurityPolicy{noSysctls}, - shouldPass: true, - expectedPSP: noSysctls.Name, + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{noSysctls}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPSP: noSysctls.Name, }, "pod without any sysctls request allowed under emptySysctls PSP": { - pod: goodPod(), - psps: []*extensions.PodSecurityPolicy{emptySysctls}, - shouldPass: true, - expectedPSP: emptySysctls.Name, + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{emptySysctls}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPSP: emptySysctls.Name, }, "pod with safe sysctls request allowed under noSysctls PSP": { - pod: podWithSysctls([]string{"a", "b"}, []string{}), - psps: []*extensions.PodSecurityPolicy{noSysctls}, - shouldPass: true, - expectedPSP: noSysctls.Name, + pod: podWithSysctls([]string{"a", "b"}, []string{}), + psps: []*extensions.PodSecurityPolicy{noSysctls}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPSP: noSysctls.Name, }, "pod with unsafe sysctls request allowed under noSysctls PSP": { - pod: podWithSysctls([]string{}, []string{"a", "b"}), - psps: []*extensions.PodSecurityPolicy{noSysctls}, - shouldPass: true, - expectedPSP: noSysctls.Name, + pod: podWithSysctls([]string{}, []string{"a", "b"}), + psps: []*extensions.PodSecurityPolicy{noSysctls}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPSP: noSysctls.Name, }, "pod with safe sysctls request disallowed under emptySysctls PSP": { - pod: podWithSysctls([]string{"a", "b"}, []string{}), - psps: []*extensions.PodSecurityPolicy{emptySysctls}, - shouldPass: false, + pod: podWithSysctls([]string{"a", "b"}, []string{}), + psps: []*extensions.PodSecurityPolicy{emptySysctls}, + shouldPassAdmit: false, + shouldPassValidate: false, }, "pod with unsafe sysctls a, b request disallowed under aSysctls SCC": { - pod: podWithSysctls([]string{}, []string{"a", "b"}), - psps: []*extensions.PodSecurityPolicy{aSysctl}, - shouldPass: false, + pod: podWithSysctls([]string{}, []string{"a", "b"}), + psps: []*extensions.PodSecurityPolicy{aSysctl}, + shouldPassAdmit: false, + shouldPassValidate: false, }, "pod with unsafe sysctls b request disallowed under aSysctls SCC": { - pod: podWithSysctls([]string{}, []string{"b"}), - psps: []*extensions.PodSecurityPolicy{aSysctl}, - shouldPass: false, + pod: podWithSysctls([]string{}, []string{"b"}), + psps: []*extensions.PodSecurityPolicy{aSysctl}, + shouldPassAdmit: false, + shouldPassValidate: false, }, "pod with unsafe sysctls a request allowed under aSysctls SCC": { - pod: podWithSysctls([]string{}, []string{"a"}), - psps: []*extensions.PodSecurityPolicy{aSysctl}, - shouldPass: true, - expectedPSP: aSysctl.Name, + pod: podWithSysctls([]string{}, []string{"a"}), + psps: []*extensions.PodSecurityPolicy{aSysctl}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPSP: aSysctl.Name, }, "pod with safe sysctls a, b request disallowed under aSysctls SCC": { - pod: podWithSysctls([]string{"a", "b"}, []string{}), - psps: []*extensions.PodSecurityPolicy{aSysctl}, - shouldPass: false, + pod: podWithSysctls([]string{"a", "b"}, []string{}), + psps: []*extensions.PodSecurityPolicy{aSysctl}, + shouldPassAdmit: false, + shouldPassValidate: false, }, "pod with safe sysctls b request disallowed under aSysctls SCC": { - pod: podWithSysctls([]string{"b"}, []string{}), - psps: []*extensions.PodSecurityPolicy{aSysctl}, - shouldPass: false, + pod: podWithSysctls([]string{"b"}, []string{}), + psps: []*extensions.PodSecurityPolicy{aSysctl}, + shouldPassAdmit: false, + shouldPassValidate: false, }, "pod with safe sysctls a request allowed under aSysctls SCC": { - pod: podWithSysctls([]string{"a"}, []string{}), - psps: []*extensions.PodSecurityPolicy{aSysctl}, - shouldPass: true, - expectedPSP: aSysctl.Name, + pod: podWithSysctls([]string{"a"}, []string{}), + psps: []*extensions.PodSecurityPolicy{aSysctl}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPSP: aSysctl.Name, }, "pod with unsafe sysctls request disallowed under emptySysctls PSP": { - pod: podWithSysctls([]string{}, []string{"a", "b"}), - psps: []*extensions.PodSecurityPolicy{emptySysctls}, - shouldPass: false, + pod: podWithSysctls([]string{}, []string{"a", "b"}), + psps: []*extensions.PodSecurityPolicy{emptySysctls}, + shouldPassAdmit: false, + shouldPassValidate: false, }, "pod with matching sysctls request allowed under mixedSysctls PSP": { - pod: podWithSysctls([]string{"a.b", "b.c"}, []string{"c", "d.e.f"}), - psps: []*extensions.PodSecurityPolicy{mixedSysctls}, - shouldPass: true, - expectedPSP: mixedSysctls.Name, + pod: podWithSysctls([]string{"a.b", "b.c"}, []string{"c", "d.e.f"}), + psps: []*extensions.PodSecurityPolicy{mixedSysctls}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPSP: mixedSysctls.Name, }, "pod with not-matching unsafe sysctls request disallowed under mixedSysctls PSP": { - pod: podWithSysctls([]string{"a.b", "b.c", "c", "d.e.f"}, []string{"e"}), - psps: []*extensions.PodSecurityPolicy{mixedSysctls}, - shouldPass: false, + pod: podWithSysctls([]string{"a.b", "b.c", "c", "d.e.f"}, []string{"e"}), + psps: []*extensions.PodSecurityPolicy{mixedSysctls}, + shouldPassAdmit: false, + shouldPassValidate: false, }, "pod with not-matching safe sysctls request disallowed under mixedSysctls PSP": { - pod: podWithSysctls([]string{"a.b", "b.c", "c", "d.e.f", "e"}, []string{}), - psps: []*extensions.PodSecurityPolicy{mixedSysctls}, - shouldPass: false, + pod: podWithSysctls([]string{"a.b", "b.c", "c", "d.e.f", "e"}, []string{}), + psps: []*extensions.PodSecurityPolicy{mixedSysctls}, + shouldPassAdmit: false, + shouldPassValidate: false, }, "pod with sysctls request allowed under catchallSysctls PSP": { - pod: podWithSysctls([]string{"e"}, []string{"f"}), - psps: []*extensions.PodSecurityPolicy{catchallSysctls}, - shouldPass: true, - expectedPSP: catchallSysctls.Name, + pod: podWithSysctls([]string{"e"}, []string{"f"}), + psps: []*extensions.PodSecurityPolicy{catchallSysctls}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPSP: catchallSysctls.Name, }, "pod with sysctls request allowed under catchallSysctls PSP, not under mixedSysctls or emptySysctls PSP": { - pod: podWithSysctls([]string{"e"}, []string{"f"}), - psps: []*extensions.PodSecurityPolicy{mixedSysctls, catchallSysctls, emptySysctls}, - shouldPass: true, - expectedPSP: catchallSysctls.Name, + pod: podWithSysctls([]string{"e"}, []string{"f"}), + psps: []*extensions.PodSecurityPolicy{mixedSysctls, catchallSysctls, emptySysctls}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPSP: catchallSysctls.Name, }, "pod with safe c sysctl request allowed under cSysctl PSP, not under aSysctl or bSysctl PSP": { - pod: podWithSysctls([]string{}, []string{"c"}), - psps: []*extensions.PodSecurityPolicy{aSysctl, bSysctl, cSysctl}, - shouldPass: true, - expectedPSP: cSysctl.Name, + pod: podWithSysctls([]string{}, []string{"c"}), + psps: []*extensions.PodSecurityPolicy{aSysctl, bSysctl, cSysctl}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPSP: cSysctl.Name, }, "pod with unsafe c sysctl request allowed under cSysctl PSP, not under aSysctl or bSysctl PSP": { - pod: podWithSysctls([]string{"c"}, []string{}), - psps: []*extensions.PodSecurityPolicy{aSysctl, bSysctl, cSysctl}, - shouldPass: true, - expectedPSP: cSysctl.Name, + pod: podWithSysctls([]string{"c"}, []string{}), + psps: []*extensions.PodSecurityPolicy{aSysctl, bSysctl, cSysctl}, + shouldPassAdmit: true, + shouldPassValidate: true, + expectedPSP: cSysctl.Name, }, } @@ -1623,9 +1773,9 @@ func TestAdmitSysctls(t *testing.T) { t.Fatalf("invalid sysctl annotation: %v", err) } - testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) + testPSPAdmit(k, v.psps, v.pod, v.shouldPassAdmit, v.shouldPassValidate, v.expectedPSP, t) - if v.shouldPass { + if v.shouldPassAdmit { safeSysctls, unsafeSysctls, _ := helper.SysctlsFromPodAnnotations(v.pod.Annotations) if !reflect.DeepEqual(safeSysctls, origSafeSysctls) { t.Errorf("%s: wrong safe sysctls: expected=%v, got=%v", k, origSafeSysctls, safeSysctls) @@ -1637,32 +1787,24 @@ func TestAdmitSysctls(t *testing.T) { } } -func testPSPAdmit(testCaseName string, psps []*extensions.PodSecurityPolicy, pod *kapi.Pod, shouldPass bool, expectedPSP string, t *testing.T) { - testPSPAdmitAdvanced(testCaseName, kadmission.Create, psps, pod, nil, shouldPass, true, expectedPSP, t) +func testPSPAdmit(testCaseName string, psps []*extensions.PodSecurityPolicy, pod *kapi.Pod, shouldPassAdmit, shouldPassValidate bool, expectedPSP string, t *testing.T) { + testPSPAdmitAdvanced(testCaseName, kadmission.Create, psps, nil, &user.DefaultInfo{}, pod, nil, shouldPassAdmit, shouldPassValidate, true, expectedPSP, t) } -func testPSPAdmitAdvanced(testCaseName string, op kadmission.Operation, psps []*extensions.PodSecurityPolicy, pod, oldPod *kapi.Pod, shouldPass bool, canMutate bool, expectedPSP string, t *testing.T) { - informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) - store := informerFactory.Extensions().InternalVersion().PodSecurityPolicies().Informer().GetStore() - - for _, psp := range psps { - store.Add(psp) - } - +func testPSPAdmitAdvanced(testCaseName string, op kadmission.Operation, psps []*extensions.PodSecurityPolicy, authz authorizer.Authorizer, userInfo user.Info, pod, oldPod *kapi.Pod, shouldPassAdmit, shouldPassValidate bool, canMutate bool, expectedPSP string, t *testing.T) { originalPod := pod.DeepCopy() + plugin := NewTestAdmission(psps, authz) - plugin := NewTestAdmission(informerFactory.Extensions().InternalVersion().PodSecurityPolicies().Lister()) - - attrs := kadmission.NewAttributesRecord(pod, oldPod, kapi.Kind("Pod").WithVersion("version"), "namespace", "", kapi.Resource("pods").WithVersion("version"), "", op, &user.DefaultInfo{}) + attrs := kadmission.NewAttributesRecord(pod, oldPod, kapi.Kind("Pod").WithVersion("version"), pod.Namespace, "", kapi.Resource("pods").WithVersion("version"), "", op, userInfo) err := plugin.Admit(attrs) - if shouldPass && err != nil { - t.Errorf("%s: expected no errors but received %v", testCaseName, err) + if shouldPassAdmit && err != nil { + t.Errorf("%s: expected no errors on Admit but received %v", testCaseName, err) } - if shouldPass && err == nil { + if shouldPassAdmit && err == nil { if pod.Annotations[psputil.ValidatedPSPAnnotation] != expectedPSP { - t.Errorf("%s: expected to validate under %q PSP but found %q", testCaseName, expectedPSP, pod.Annotations[psputil.ValidatedPSPAnnotation]) + t.Errorf("%s: expected to be admitted under %q PSP but found %q", testCaseName, expectedPSP, pod.Annotations[psputil.ValidatedPSPAnnotation]) } if !canMutate { @@ -1673,13 +1815,20 @@ func testPSPAdmitAdvanced(testCaseName string, op kadmission.Operation, psps []* delete(originalPodWithoutPSPAnnotation.Annotations, psputil.ValidatedPSPAnnotation) if !apiequality.Semantic.DeepEqual(originalPodWithoutPSPAnnotation.Spec, podWithoutPSPAnnotation.Spec) { - t.Errorf("%s: expected no mutation, got %s", testCaseName, diff.ObjectGoPrintSideBySide(originalPodWithoutPSPAnnotation.Spec, podWithoutPSPAnnotation.Spec)) + t.Errorf("%s: expected no mutation on Admit, got %s", testCaseName, diff.ObjectGoPrintSideBySide(originalPodWithoutPSPAnnotation.Spec, podWithoutPSPAnnotation.Spec)) } } } - if !shouldPass && err == nil { - t.Errorf("%s: expected errors but received none", testCaseName) + if !shouldPassAdmit && err == nil { + t.Errorf("%s: expected errors on Admit but received none", testCaseName) + } + + err = plugin.Validate(attrs) + if shouldPassValidate && err != nil { + t.Errorf("%s: expected no errors on Validate but received %v", testCaseName, err) + } else if !shouldPassValidate && err == nil { + t.Errorf("%s: expected errors on Validate but received none", testCaseName) } } @@ -1803,7 +1952,7 @@ func TestCreateProvidersFromConstraints(t *testing.T) { } for k, v := range testCases { - admit := &podSecurityPolicyPlugin{ + admit := &PodSecurityPolicyPlugin{ Handler: kadmission.NewHandler(kadmission.Create, kadmission.Update), strategyFactory: kpsp.NewSimpleStrategyFactory(), } @@ -1833,59 +1982,59 @@ func TestCreateProvidersFromConstraints(t *testing.T) { } } -func TestGetMatchingPolicies(t *testing.T) { +func TestPolicyAuthorization(t *testing.T) { policyWithName := func(name string) *extensions.PodSecurityPolicy { - p := restrictivePSP() + p := permissivePSP() p.Name = name return p } tests := map[string]struct { - user user.Info - sa user.Info - ns string - expectedPolicies sets.String - inPolicies []*extensions.PodSecurityPolicy - allowed map[string]map[string]map[string]bool + user user.Info + sa string + ns string + expectedPolicy string + inPolicies []*extensions.PodSecurityPolicy + allowed map[string]map[string]map[string]bool }{ "policy allowed by user": { user: &user.DefaultInfo{Name: "user"}, - sa: &user.DefaultInfo{Name: "sa"}, + sa: "sa", ns: "test", allowed: map[string]map[string]map[string]bool{ "user": { "test": {"policy": true}, }, }, - inPolicies: []*extensions.PodSecurityPolicy{policyWithName("policy")}, - expectedPolicies: sets.NewString("policy"), + inPolicies: []*extensions.PodSecurityPolicy{policyWithName("policy")}, + expectedPolicy: "policy", }, "policy allowed by sa": { user: &user.DefaultInfo{Name: "user"}, - sa: &user.DefaultInfo{Name: "sa"}, + sa: "sa", ns: "test", allowed: map[string]map[string]map[string]bool{ - "sa": { + serviceaccount.MakeUsername("test", "sa"): { "test": {"policy": true}, }, }, - inPolicies: []*extensions.PodSecurityPolicy{policyWithName("policy")}, - expectedPolicies: sets.NewString("policy"), + inPolicies: []*extensions.PodSecurityPolicy{policyWithName("policy")}, + expectedPolicy: "policy", }, "no policies allowed": { - user: &user.DefaultInfo{Name: "user"}, - sa: &user.DefaultInfo{Name: "sa"}, - ns: "test", - allowed: map[string]map[string]map[string]bool{}, - inPolicies: []*extensions.PodSecurityPolicy{policyWithName("policy")}, - expectedPolicies: sets.NewString(), + user: &user.DefaultInfo{Name: "user"}, + sa: "sa", + ns: "test", + allowed: map[string]map[string]map[string]bool{}, + inPolicies: []*extensions.PodSecurityPolicy{policyWithName("policy")}, + expectedPolicy: "", }, "multiple policies allowed": { user: &user.DefaultInfo{Name: "user"}, - sa: &user.DefaultInfo{Name: "sa"}, + sa: "sa", ns: "test", allowed: map[string]map[string]map[string]bool{ - "sa": { + serviceaccount.MakeUsername("test", "sa"): { "test": {"policy1": true}, "": {"policy4": true}, "other": {"policy6": true}, @@ -1897,22 +2046,23 @@ func TestGetMatchingPolicies(t *testing.T) { }, }, inPolicies: []*extensions.PodSecurityPolicy{ - policyWithName("policy1"), // allowed by sa - policyWithName("policy2"), // allowed by user - policyWithName("policy3"), // not allowed - policyWithName("policy4"), // allowed by sa at cluster level - policyWithName("policy5"), // allowed by user at cluster level - policyWithName("policy6"), // not allowed in this namespace - policyWithName("policy7"), // not allowed in this namespace + // Prefix to force checking these policies first. + policyWithName("a_policy1"), // not allowed in this namespace + policyWithName("a_policy2"), // not allowed in this namespace + policyWithName("policy2"), // allowed by sa + policyWithName("policy3"), // allowed by user + policyWithName("policy4"), // not allowed + policyWithName("policy5"), // allowed by sa at cluster level + policyWithName("policy6"), // allowed by user at cluster level }, - expectedPolicies: sets.NewString("policy1", "policy2", "policy4", "policy5"), + expectedPolicy: "policy2", }, "policies are not allowed for nil user info": { user: nil, - sa: &user.DefaultInfo{Name: "sa"}, + sa: "sa", ns: "test", allowed: map[string]map[string]map[string]bool{ - "sa": { + serviceaccount.MakeUsername("test", "sa"): { "test": {"policy1": true}, }, "user": { @@ -1925,14 +2075,14 @@ func TestGetMatchingPolicies(t *testing.T) { policyWithName("policy3"), }, // only the policies for the sa are allowed when user info is nil - expectedPolicies: sets.NewString("policy1"), + expectedPolicy: "policy1", }, "policies are not allowed for nil sa info": { user: &user.DefaultInfo{Name: "user"}, - sa: nil, + sa: "", ns: "test", allowed: map[string]map[string]map[string]bool{ - "sa": { + serviceaccount.MakeUsername("test", "sa"): { "test": {"policy1": true}, }, "user": { @@ -1945,14 +2095,14 @@ func TestGetMatchingPolicies(t *testing.T) { policyWithName("policy3"), }, // only the policies for the user are allowed when sa info is nil - expectedPolicies: sets.NewString("policy2"), + expectedPolicy: "policy2", }, "policies are not allowed for nil sa and user info": { user: nil, - sa: nil, + sa: "", ns: "test", allowed: map[string]map[string]map[string]bool{ - "sa": { + serviceaccount.MakeUsername("test", "sa"): { "test": {"policy1": true}, }, "user": { @@ -1965,30 +2115,202 @@ func TestGetMatchingPolicies(t *testing.T) { policyWithName("policy3"), }, // no policies are allowed if sa and user are both nil - expectedPolicies: sets.NewString(), + expectedPolicy: "", }, } for k, v := range tests { - informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) - pspInformer := informerFactory.Extensions().InternalVersion().PodSecurityPolicies() - store := pspInformer.Informer().GetStore() - for _, psp := range v.inPolicies { - store.Add(psp) - } + var ( + oldPod *kapi.Pod + shouldPass = v.expectedPolicy != "" + authz = &TestAuthorizer{usernameToNamespaceToAllowedPSPs: v.allowed} + canMutate = true + ) + pod := goodPod() + pod.Namespace = v.ns + pod.Spec.ServiceAccountName = v.sa + testPSPAdmitAdvanced(k, kadmission.Create, v.inPolicies, authz, v.user, + pod, oldPod, shouldPass, shouldPass, canMutate, v.expectedPolicy, t) + } +} - authz := &TestAuthorizer{usernameToNamespaceToAllowedPSPs: v.allowed} - allowedPolicies, err := getMatchingPolicies(pspInformer.Lister(), v.user, v.sa, authz, v.ns) - if err != nil { - t.Errorf("%s got unexpected error %#v", k, err) - continue - } - allowedPolicyNames := sets.NewString() - for _, p := range allowedPolicies { - allowedPolicyNames.Insert(p.Name) - } - if !v.expectedPolicies.Equal(allowedPolicyNames) { - t.Errorf("%s received unexpected policies. Expected %#v but got %#v", k, v.expectedPolicies.List(), allowedPolicyNames.List()) - } +func TestPolicyAuthorizationErrors(t *testing.T) { + policyWithName := func(name string) *extensions.PodSecurityPolicy { + p := restrictivePSP() + p.Name = name + return p + } + + const ( + sa = "sa" + ns = "test" + userName = "user" + ) + + tests := map[string]struct { + inPolicies []*extensions.PodSecurityPolicy + allowed map[string]map[string]map[string]bool + expectValidationErrs int + }{ + "policies not allowed": { + allowed: map[string]map[string]map[string]bool{}, + inPolicies: []*extensions.PodSecurityPolicy{ + policyWithName("policy1"), + policyWithName("policy2"), + }, + expectValidationErrs: 0, + }, + "policy allowed by user": { + allowed: map[string]map[string]map[string]bool{ + "user": { + "test": {"policy1": true}, + }, + }, + inPolicies: []*extensions.PodSecurityPolicy{ + policyWithName("policy1"), + policyWithName("policy2"), + }, + expectValidationErrs: 1, + }, + "policy allowed by service account": { + allowed: map[string]map[string]map[string]bool{ + serviceaccount.MakeUsername("test", "sa"): { + "test": {"policy2": true}, + }, + }, + inPolicies: []*extensions.PodSecurityPolicy{ + policyWithName("policy1"), + policyWithName("policy2"), + }, + expectValidationErrs: 1, + }, + "multiple policies allowed": { + allowed: map[string]map[string]map[string]bool{ + "user": { + "test": {"policy1": true}, + }, + serviceaccount.MakeUsername("test", "sa"): { + "test": {"policy2": true}, + }, + }, + inPolicies: []*extensions.PodSecurityPolicy{ + policyWithName("policy1"), + policyWithName("policy2"), + }, + expectValidationErrs: 2, + }, + } + for desc, tc := range tests { + t.Run(desc, func(t *testing.T) { + var ( + authz = &TestAuthorizer{usernameToNamespaceToAllowedPSPs: tc.allowed} + privileged = true + ) + pod := goodPod() + pod.Namespace = ns + pod.Spec.ServiceAccountName = sa + pod.Spec.Containers[0].SecurityContext.Privileged = &privileged + + plugin := NewTestAdmission(tc.inPolicies, authz) + attrs := kadmission.NewAttributesRecord(pod, nil, kapi.Kind("Pod").WithVersion("version"), ns, "", kapi.Resource("pods").WithVersion("version"), "", kadmission.Create, &user.DefaultInfo{Name: userName}) + + allowedPod, _, validationErrs, err := plugin.computeSecurityContext(attrs, pod, true, "") + assert.Nil(t, allowedPod) + assert.NoError(t, err) + assert.Len(t, validationErrs, tc.expectValidationErrs) + }) + } +} + +func TestPreferValidatedPSP(t *testing.T) { + restrictivePSPWithName := func(name string) *extensions.PodSecurityPolicy { + p := restrictivePSP() + p.Name = name + return p + } + + permissivePSPWithName := func(name string) *extensions.PodSecurityPolicy { + p := permissivePSP() + p.Name = name + return p + } + + tests := map[string]struct { + inPolicies []*extensions.PodSecurityPolicy + expectValidationErrs int + validatedPSPHint string + expectedPSP string + }{ + "no policy saved in annotations, PSPs are ordered lexicographically": { + inPolicies: []*extensions.PodSecurityPolicy{ + restrictivePSPWithName("001restrictive"), + restrictivePSPWithName("002restrictive"), + permissivePSPWithName("002permissive"), + permissivePSPWithName("001permissive"), + permissivePSPWithName("003permissive"), + }, + expectValidationErrs: 0, + validatedPSPHint: "", + expectedPSP: "001permissive", + }, + "policy saved in annotations is prefered": { + inPolicies: []*extensions.PodSecurityPolicy{ + restrictivePSPWithName("001restrictive"), + restrictivePSPWithName("002restrictive"), + permissivePSPWithName("001permissive"), + permissivePSPWithName("002permissive"), + permissivePSPWithName("003permissive"), + }, + expectValidationErrs: 0, + validatedPSPHint: "002permissive", + expectedPSP: "002permissive", + }, + "policy saved in annotations is invalid": { + inPolicies: []*extensions.PodSecurityPolicy{ + restrictivePSPWithName("001restrictive"), + restrictivePSPWithName("002restrictive"), + }, + expectValidationErrs: 2, + validatedPSPHint: "foo", + expectedPSP: "", + }, + "policy saved in annotations is disallowed anymore": { + inPolicies: []*extensions.PodSecurityPolicy{ + restrictivePSPWithName("001restrictive"), + restrictivePSPWithName("002restrictive"), + }, + expectValidationErrs: 2, + validatedPSPHint: "001restrictive", + expectedPSP: "", + }, + "policy saved in annotations is disallowed anymore, but find another one": { + inPolicies: []*extensions.PodSecurityPolicy{ + restrictivePSPWithName("001restrictive"), + restrictivePSPWithName("002restrictive"), + permissivePSPWithName("002permissive"), + permissivePSPWithName("001permissive"), + }, + expectValidationErrs: 0, + validatedPSPHint: "001restrictive", + expectedPSP: "001permissive", + }, + } + for desc, tc := range tests { + t.Run(desc, func(t *testing.T) { + authz := authorizerfactory.NewAlwaysAllowAuthorizer() + allowPrivilegeEscalation := true + pod := goodPod() + pod.Namespace = "ns" + pod.Spec.ServiceAccountName = "sa" + pod.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation = &allowPrivilegeEscalation + + plugin := NewTestAdmission(tc.inPolicies, authz) + attrs := kadmission.NewAttributesRecord(pod, nil, kapi.Kind("Pod").WithVersion("version"), "ns", "", kapi.Resource("pods").WithVersion("version"), "", kadmission.Update, &user.DefaultInfo{Name: "test"}) + + _, pspName, validationErrs, err := plugin.computeSecurityContext(attrs, pod, false, tc.validatedPSPHint) + assert.NoError(t, err) + assert.Len(t, validationErrs, tc.expectValidationErrs) + assert.Equal(t, pspName, tc.expectedPSP) + }) } } @@ -2063,6 +2385,8 @@ func permissivePSP() *extensions.PodSecurityPolicy { func goodPod() *kapi.Pod { return &kapi.Pod{ ObjectMeta: metav1.ObjectMeta{ + Name: "pod", + Namespace: "namespace", Annotations: map[string]string{}, }, Spec: kapi.PodSpec{ diff --git a/plugin/pkg/admission/securitycontext/scdeny/BUILD b/plugin/pkg/admission/securitycontext/scdeny/BUILD index 7935b1af4c6..61f5962b09c 100644 --- a/plugin/pkg/admission/securitycontext/scdeny/BUILD +++ b/plugin/pkg/admission/securitycontext/scdeny/BUILD @@ -11,7 +11,7 @@ go_library( srcs = ["admission.go"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/securitycontext/scdeny", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", ], @@ -20,10 +20,10 @@ go_library( go_test( name = "go_default_test", srcs = ["admission_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/securitycontext/scdeny", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", ], ) diff --git a/plugin/pkg/admission/securitycontext/scdeny/admission.go b/plugin/pkg/admission/securitycontext/scdeny/admission.go index 24e2ede3a6a..cace3843dad 100644 --- a/plugin/pkg/admission/securitycontext/scdeny/admission.go +++ b/plugin/pkg/admission/securitycontext/scdeny/admission.go @@ -22,7 +22,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // Register registers a plugin @@ -32,19 +32,22 @@ func Register(plugins *admission.Plugins) { }) } -type plugin struct { +// Plugin implements admission.Interface. +type Plugin struct { *admission.Handler } +var _ admission.ValidationInterface = &Plugin{} + // NewSecurityContextDeny creates a new instance of the SecurityContextDeny admission controller -func NewSecurityContextDeny() admission.Interface { - return &plugin{ +func NewSecurityContextDeny() *Plugin { + return &Plugin{ Handler: admission.NewHandler(admission.Create, admission.Update), } } -// Admit will deny any pod that defines SELinuxOptions or RunAsUser. -func (p *plugin) Admit(a admission.Attributes) (err error) { +// Validate will deny any pod that defines SELinuxOptions or RunAsUser. +func (p *Plugin) Validate(a admission.Attributes) (err error) { if a.GetSubresource() != "" || a.GetResource().GroupResource() != api.Resource("pods") { return nil } diff --git a/plugin/pkg/admission/securitycontext/scdeny/admission_test.go b/plugin/pkg/admission/securitycontext/scdeny/admission_test.go index f3c498a1bfd..9fb041c51a8 100644 --- a/plugin/pkg/admission/securitycontext/scdeny/admission_test.go +++ b/plugin/pkg/admission/securitycontext/scdeny/admission_test.go @@ -20,7 +20,7 @@ import ( "testing" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" ) // ensures the SecurityContext is denied if it defines anything more than Caps or Privileged @@ -82,7 +82,7 @@ func TestAdmission(t *testing.T) { p.Spec.SecurityContext = tc.podSc p.Spec.Containers[0].SecurityContext = tc.sc - err := handler.Admit(admission.NewAttributesRecord(p, nil, api.Kind("Pod").WithVersion("version"), "foo", "name", api.Resource("pods").WithVersion("version"), "", "ignored", nil)) + err := handler.Validate(admission.NewAttributesRecord(p, nil, api.Kind("Pod").WithVersion("version"), "foo", "name", api.Resource("pods").WithVersion("version"), "", "ignored", nil)) if err != nil && !tc.expectError { t.Errorf("%v: unexpected error: %v", tc.name, err) } else if err == nil && tc.expectError { @@ -96,7 +96,7 @@ func TestAdmission(t *testing.T) { p.Spec.InitContainers = p.Spec.Containers p.Spec.Containers = nil - err = handler.Admit(admission.NewAttributesRecord(p, nil, api.Kind("Pod").WithVersion("version"), "foo", "name", api.Resource("pods").WithVersion("version"), "", "ignored", nil)) + err = handler.Validate(admission.NewAttributesRecord(p, nil, api.Kind("Pod").WithVersion("version"), "foo", "name", api.Resource("pods").WithVersion("version"), "", "ignored", nil)) if err != nil && !tc.expectError { t.Errorf("%v: unexpected error: %v", tc.name, err) } else if err == nil && tc.expectError { @@ -140,7 +140,7 @@ func TestPodSecurityContextAdmission(t *testing.T) { } for _, test := range tests { pod.Spec.SecurityContext = &test.securityContext - err := handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), "foo", "name", api.Resource("pods").WithVersion("version"), "", "ignored", nil)) + err := handler.Validate(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), "foo", "name", api.Resource("pods").WithVersion("version"), "", "ignored", nil)) if test.errorExpected && err == nil { t.Errorf("Expected error for security context %+v but did not get an error", test.securityContext) diff --git a/plugin/pkg/admission/serviceaccount/BUILD b/plugin/pkg/admission/serviceaccount/BUILD index 4f916fe2372..392c6a1b9c6 100644 --- a/plugin/pkg/admission/serviceaccount/BUILD +++ b/plugin/pkg/admission/serviceaccount/BUILD @@ -14,8 +14,8 @@ go_library( ], importpath = "k8s.io/kubernetes/plugin/pkg/admission/serviceaccount", deps = [ - "//pkg/api:go_default_library", "//pkg/api/pod:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", "//pkg/client/listers/core/internalversion:go_default_library", @@ -35,10 +35,10 @@ go_library( go_test( name = "go_default_test", srcs = ["admission_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/serviceaccount", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", "//pkg/client/listers/core/internalversion:go_default_library", diff --git a/plugin/pkg/admission/serviceaccount/admission.go b/plugin/pkg/admission/serviceaccount/admission.go index 9e319d26d6c..6e245ecf8b0 100644 --- a/plugin/pkg/admission/serviceaccount/admission.go +++ b/plugin/pkg/admission/serviceaccount/admission.go @@ -30,8 +30,8 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/storage/names" - "k8s.io/kubernetes/pkg/api" podutil "k8s.io/kubernetes/pkg/api/pod" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" corelisters "k8s.io/kubernetes/pkg/client/listers/core/internalversion" @@ -80,6 +80,8 @@ type serviceAccount struct { secretLister corelisters.SecretLister } +var _ admission.MutationInterface = &serviceAccount{} +var _ admission.ValidationInterface = &serviceAccount{} var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&serviceAccount{}) var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&serviceAccount{}) @@ -117,8 +119,8 @@ func (a *serviceAccount) SetInternalKubeInformerFactory(f informers.SharedInform }) } -// Validate ensures an authorizer is set. -func (a *serviceAccount) Validate() error { +// ValidateInitialization ensures an authorizer is set. +func (a *serviceAccount) ValidateInitialization() error { if a.client == nil { return fmt.Errorf("missing client") } @@ -132,18 +134,9 @@ func (a *serviceAccount) Validate() error { } func (s *serviceAccount) Admit(a admission.Attributes) (err error) { - if a.GetResource().GroupResource() != api.Resource("pods") { + if shouldIgnore(a) { return nil } - obj := a.GetObject() - if obj == nil { - return nil - } - pod, ok := obj.(*api.Pod) - if !ok { - return nil - } - updateInitialized, err := util.IsUpdatingInitializedObject(a) if err != nil { return err @@ -153,9 +146,56 @@ func (s *serviceAccount) Admit(a admission.Attributes) (err error) { return nil } + pod := a.GetObject().(*api.Pod) + // Don't modify the spec of mirror pods. // That makes the kubelet very angry and confused, and it immediately deletes the pod (because the spec doesn't match) // That said, don't allow mirror pods to reference ServiceAccounts or SecretVolumeSources either + if _, isMirrorPod := pod.Annotations[api.MirrorPodAnnotationKey]; isMirrorPod { + return s.Validate(a) + } + + // Set the default service account if needed + if len(pod.Spec.ServiceAccountName) == 0 { + pod.Spec.ServiceAccountName = DefaultServiceAccountName + } + + serviceAccount, err := s.getServiceAccount(a.GetNamespace(), pod.Spec.ServiceAccountName) + if err != nil { + return admission.NewForbidden(a, fmt.Errorf("error looking up service account %s/%s: %v", a.GetNamespace(), pod.Spec.ServiceAccountName, err)) + } + if s.MountServiceAccountToken && shouldAutomount(serviceAccount, pod) { + if err := s.mountServiceAccountToken(serviceAccount, pod); err != nil { + if _, ok := err.(errors.APIStatus); ok { + return err + } + return admission.NewForbidden(a, err) + } + } + if len(pod.Spec.ImagePullSecrets) == 0 { + pod.Spec.ImagePullSecrets = make([]api.LocalObjectReference, len(serviceAccount.ImagePullSecrets)) + copy(pod.Spec.ImagePullSecrets, serviceAccount.ImagePullSecrets) + } + + return s.Validate(a) +} + +func (s *serviceAccount) Validate(a admission.Attributes) (err error) { + if shouldIgnore(a) { + return nil + } + updateInitialized, err := util.IsUpdatingInitializedObject(a) + if err != nil { + return err + } + if updateInitialized { + // related pod spec fields are immutable after the pod is initialized + return nil + } + + pod := a.GetObject().(*api.Pod) + + // Mirror pods have restrictions on what they can reference if _, isMirrorPod := pod.Annotations[api.MirrorPodAnnotationKey]; isMirrorPod { if len(pod.Spec.ServiceAccountName) != 0 { return admission.NewForbidden(a, fmt.Errorf("a mirror pod may not reference service accounts")) @@ -171,20 +211,11 @@ func (s *serviceAccount) Admit(a admission.Attributes) (err error) { return nil } - // Set the default service account if needed - if len(pod.Spec.ServiceAccountName) == 0 { - pod.Spec.ServiceAccountName = DefaultServiceAccountName - } - // Ensure the referenced service account exists serviceAccount, err := s.getServiceAccount(a.GetNamespace(), pod.Spec.ServiceAccountName) if err != nil { return admission.NewForbidden(a, fmt.Errorf("error looking up service account %s/%s: %v", a.GetNamespace(), pod.Spec.ServiceAccountName, err)) } - if serviceAccount == nil { - // TODO: convert to a ServerTimeout error (or other error that sends a Retry-After header) - return admission.NewForbidden(a, fmt.Errorf("service account %s/%s was not found, retry after the service account is created", a.GetNamespace(), pod.Spec.ServiceAccountName)) - } if s.enforceMountableSecrets(serviceAccount) { if err := s.limitSecretReferences(serviceAccount, pod); err != nil { @@ -192,23 +223,25 @@ func (s *serviceAccount) Admit(a admission.Attributes) (err error) { } } - if s.MountServiceAccountToken && shouldAutomount(serviceAccount, pod) { - if err := s.mountServiceAccountToken(serviceAccount, pod); err != nil { - if _, ok := err.(errors.APIStatus); ok { - return err - } - return admission.NewForbidden(a, err) - } - } - - if len(pod.Spec.ImagePullSecrets) == 0 { - pod.Spec.ImagePullSecrets = make([]api.LocalObjectReference, len(serviceAccount.ImagePullSecrets)) - copy(pod.Spec.ImagePullSecrets, serviceAccount.ImagePullSecrets) - } - return nil } +func shouldIgnore(a admission.Attributes) bool { + if a.GetResource().GroupResource() != api.Resource("pods") { + return true + } + obj := a.GetObject() + if obj == nil { + return true + } + _, ok := obj.(*api.Pod) + if !ok { + return true + } + + return false +} + func shouldAutomount(sa *api.ServiceAccount, pod *api.Pod) bool { // Pod's preference wins if pod.Spec.AutomountServiceAccountToken != nil { @@ -267,7 +300,7 @@ func (s *serviceAccount) getServiceAccount(namespace string, name string) (*api. } } - return nil, nil + return nil, errors.NewNotFound(api.Resource("serviceaccount"), name) } // getReferencedServiceAccountToken returns the name of the first referenced secret which is a ServiceAccountToken for the service account diff --git a/plugin/pkg/admission/serviceaccount/admission_test.go b/plugin/pkg/admission/serviceaccount/admission_test.go index 80337481010..d5167718647 100644 --- a/plugin/pkg/admission/serviceaccount/admission_test.go +++ b/plugin/pkg/admission/serviceaccount/admission_test.go @@ -26,7 +26,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apiserver/pkg/admission" "k8s.io/client-go/tools/cache" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" corelisters "k8s.io/kubernetes/pkg/client/listers/core/internalversion" @@ -35,13 +35,10 @@ import ( ) func TestIgnoresNonCreate(t *testing.T) { - pod := &api.Pod{} for _, op := range []admission.Operation{admission.Delete, admission.Connect} { - attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "myns", "myname", api.Resource("pods").WithVersion("version"), "", op, nil) - handler := admission.NewChainHandler(NewServiceAccount()) - err := handler.Admit(attrs) - if err != nil { - t.Errorf("Expected %s operation allowed, got err: %v", op, err) + handler := NewServiceAccount() + if handler.Handles(op) { + t.Errorf("Expected not to handle operation %s", op) } } } @@ -50,7 +47,7 @@ func TestIgnoresUpdateOfInitializedPod(t *testing.T) { pod := &api.Pod{} oldPod := &api.Pod{} attrs := admission.NewAttributesRecord(pod, oldPod, api.Kind("Pod").WithVersion("version"), "myns", "myname", api.Resource("pods").WithVersion("version"), "", admission.Update, nil) - handler := admission.NewChainHandler(NewServiceAccount()) + handler := NewServiceAccount() err := handler.Admit(attrs) if err != nil { t.Errorf("Expected update of initialized pod allowed, got err: %v", err) diff --git a/plugin/pkg/admission/storageclass/setdefault/BUILD b/plugin/pkg/admission/storageclass/setdefault/BUILD index 3e971c490ee..a3840f47e94 100644 --- a/plugin/pkg/admission/storageclass/setdefault/BUILD +++ b/plugin/pkg/admission/storageclass/setdefault/BUILD @@ -11,8 +11,8 @@ go_library( srcs = ["admission.go"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/storageclass/setdefault", deps = [ - "//pkg/api:go_default_library", - "//pkg/api/helper:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/helper:go_default_library", "//pkg/apis/storage:go_default_library", "//pkg/apis/storage/util:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", @@ -28,10 +28,10 @@ go_library( go_test( name = "go_default_test", srcs = ["admission_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/admission/storageclass/setdefault", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/storage:go_default_library", "//pkg/apis/storage/util:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library", diff --git a/plugin/pkg/admission/storageclass/setdefault/admission.go b/plugin/pkg/admission/storageclass/setdefault/admission.go index 2e218bb0eda..6a9fe197a08 100644 --- a/plugin/pkg/admission/storageclass/setdefault/admission.go +++ b/plugin/pkg/admission/storageclass/setdefault/admission.go @@ -25,8 +25,8 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" admission "k8s.io/apiserver/pkg/admission" - api "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/helper" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/helper" "k8s.io/kubernetes/pkg/apis/storage" storageutil "k8s.io/kubernetes/pkg/apis/storage/util" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" @@ -54,6 +54,7 @@ type claimDefaulterPlugin struct { } var _ admission.Interface = &claimDefaulterPlugin{} +var _ admission.MutationInterface = &claimDefaulterPlugin{} var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&claimDefaulterPlugin{}) // newPlugin creates a new admission plugin. @@ -69,8 +70,8 @@ func (a *claimDefaulterPlugin) SetInternalKubeInformerFactory(f informers.Shared a.SetReadyFunc(informer.Informer().HasSynced) } -// Validate ensures lister is set. -func (a *claimDefaulterPlugin) Validate() error { +// ValidateInitialization ensures lister is set. +func (a *claimDefaulterPlugin) ValidateInitialization() error { if a.lister == nil { return fmt.Errorf("missing lister") } diff --git a/plugin/pkg/admission/storageclass/setdefault/admission_test.go b/plugin/pkg/admission/storageclass/setdefault/admission_test.go index e0516eaf131..6be3765430b 100644 --- a/plugin/pkg/admission/storageclass/setdefault/admission_test.go +++ b/plugin/pkg/admission/storageclass/setdefault/admission_test.go @@ -23,7 +23,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apiserver/pkg/admission" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/storage" storageutil "k8s.io/kubernetes/pkg/apis/storage/util" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" diff --git a/plugin/pkg/admission/webhook/BUILD b/plugin/pkg/admission/webhook/BUILD deleted file mode 100644 index 0190d6aaec3..00000000000 --- a/plugin/pkg/admission/webhook/BUILD +++ /dev/null @@ -1,70 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = [ - "admission.go", - "admissionreview.go", - "doc.go", - "rules.go", - "serviceresolver.go", - ], - importpath = "k8s.io/kubernetes/plugin/pkg/admission/webhook", - visibility = ["//visibility:public"], - deps = [ - "//pkg/apis/admission/install:go_default_library", - "//pkg/kubeapiserver/admission:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/api/admission/v1alpha1:go_default_library", - "//vendor/k8s.io/api/admissionregistration/v1alpha1:go_default_library", - "//vendor/k8s.io/api/authentication/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", - "//vendor/k8s.io/apiserver/pkg/admission/configuration:go_default_library", - "//vendor/k8s.io/apiserver/pkg/admission/initializer:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = [ - "admission_test.go", - "certs_test.go", - "rules_test.go", - "serviceresolver_test.go", - ], - importpath = "k8s.io/kubernetes/plugin/pkg/admission/webhook", - library = ":go_default_library", - deps = [ - "//pkg/api:go_default_library", - "//pkg/apis/admission/install:go_default_library", - "//vendor/k8s.io/api/admission/v1alpha1:go_default_library", - "//vendor/k8s.io/api/admissionregistration/v1alpha1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", - "//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], - visibility = ["//visibility:public"], -) diff --git a/plugin/pkg/admission/webhook/admission.go b/plugin/pkg/admission/webhook/admission.go deleted file mode 100644 index efcd5341796..00000000000 --- a/plugin/pkg/admission/webhook/admission.go +++ /dev/null @@ -1,291 +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 webhook delegates admission checks to dynamically configured webhooks. -package webhook - -import ( - "context" - "fmt" - "io" - "net" - "net/http" - "sync" - "time" - - "github.com/golang/glog" - - admissionv1alpha1 "k8s.io/api/admission/v1alpha1" - "k8s.io/api/admissionregistration/v1alpha1" - 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/runtime/serializer" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/apiserver/pkg/admission" - "k8s.io/apiserver/pkg/admission/configuration" - genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer" - clientset "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - admissioninit "k8s.io/kubernetes/pkg/kubeapiserver/admission" - - // install the clientgo admission API for use with api registry - _ "k8s.io/kubernetes/pkg/apis/admission/install" -) - -var ( - groupVersions = []schema.GroupVersion{ - admissionv1alpha1.SchemeGroupVersion, - } -) - -type ErrCallingWebhook struct { - WebhookName string - Reason error -} - -func (e *ErrCallingWebhook) Error() string { - if e.Reason != nil { - return fmt.Sprintf("failed calling admission webhook %q: %v", e.WebhookName, e.Reason) - } - return fmt.Sprintf("failed calling admission webhook %q; no further details available", e.WebhookName) -} - -// Register registers a plugin -func Register(plugins *admission.Plugins) { - plugins.Register("GenericAdmissionWebhook", func(configFile io.Reader) (admission.Interface, error) { - plugin, err := NewGenericAdmissionWebhook() - if err != nil { - return nil, err - } - - return plugin, nil - }) -} - -// WebhookSource can list dynamic webhook plugins. -type WebhookSource interface { - Run(stopCh <-chan struct{}) - ExternalAdmissionHooks() (*v1alpha1.ExternalAdmissionHookConfiguration, error) -} - -// NewGenericAdmissionWebhook returns a generic admission webhook plugin. -func NewGenericAdmissionWebhook() (*GenericAdmissionWebhook, error) { - return &GenericAdmissionWebhook{ - Handler: admission.NewHandler( - admission.Connect, - admission.Create, - admission.Delete, - admission.Update, - ), - serviceResolver: defaultServiceResolver{}, - }, nil -} - -// GenericAdmissionWebhook is an implementation of admission.Interface. -type GenericAdmissionWebhook struct { - *admission.Handler - hookSource WebhookSource - serviceResolver admissioninit.ServiceResolver - negotiatedSerializer runtime.NegotiatedSerializer - clientCert []byte - clientKey []byte - proxyTransport *http.Transport -} - -var ( - _ = admissioninit.WantsServiceResolver(&GenericAdmissionWebhook{}) - _ = genericadmissioninit.WantsClientCert(&GenericAdmissionWebhook{}) - _ = genericadmissioninit.WantsExternalKubeClientSet(&GenericAdmissionWebhook{}) -) - -func (a *GenericAdmissionWebhook) SetProxyTransport(pt *http.Transport) { - a.proxyTransport = pt -} - -// SetServiceResolver sets a service resolver for the webhook admission plugin. -// Passing a nil resolver does not have an effect, instead a default one will be used. -func (a *GenericAdmissionWebhook) SetServiceResolver(sr admissioninit.ServiceResolver) { - if sr != nil { - a.serviceResolver = sr - } -} - -// SetScheme sets a serializer(NegotiatedSerializer) which is derived from the scheme -func (a *GenericAdmissionWebhook) SetScheme(scheme *runtime.Scheme) { - if scheme != nil { - a.negotiatedSerializer = serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{ - Serializer: serializer.NewCodecFactory(scheme).LegacyCodec(admissionv1alpha1.SchemeGroupVersion), - }) - } -} - -func (a *GenericAdmissionWebhook) SetClientCert(cert, key []byte) { - a.clientCert = cert - a.clientKey = key -} - -func (a *GenericAdmissionWebhook) SetExternalKubeClientSet(client clientset.Interface) { - a.hookSource = configuration.NewExternalAdmissionHookConfigurationManager(client.Admissionregistration().ExternalAdmissionHookConfigurations()) -} - -func (a *GenericAdmissionWebhook) Validate() error { - if a.clientCert == nil || a.clientKey == nil { - return fmt.Errorf("the GenericAdmissionWebhook admission plugin requires a client certificate and the private key to be provided") - } - if a.hookSource == nil { - return fmt.Errorf("the GenericAdmissionWebhook admission plugin requires a Kubernetes client to be provided") - } - if a.negotiatedSerializer == nil { - return fmt.Errorf("the GenericAdmissionWebhook admission plugin requires a runtime.Scheme to be provided to derive a serializer") - } - go a.hookSource.Run(wait.NeverStop) - return nil -} - -func (a *GenericAdmissionWebhook) loadConfiguration(attr admission.Attributes) (*v1alpha1.ExternalAdmissionHookConfiguration, error) { - hookConfig, err := a.hookSource.ExternalAdmissionHooks() - // if ExternalAdmissionHook configuration is disabled, fail open - if err == configuration.ErrDisabled { - return &v1alpha1.ExternalAdmissionHookConfiguration{}, nil - } - if err != nil { - e := apierrors.NewServerTimeout(attr.GetResource().GroupResource(), string(attr.GetOperation()), 1) - e.ErrStatus.Message = fmt.Sprintf("Unable to refresh the ExternalAdmissionHook configuration: %v", err) - e.ErrStatus.Reason = "LoadingConfiguration" - e.ErrStatus.Details.Causes = append(e.ErrStatus.Details.Causes, metav1.StatusCause{ - Type: "ExternalAdmissionHookConfigurationFailure", - Message: "An error has occurred while refreshing the externalAdmissionHook configuration, no resources can be created/updated/deleted/connected until a refresh succeeds.", - }) - return nil, e - } - return hookConfig, nil -} - -// Admit makes an admission decision based on the request attributes. -func (a *GenericAdmissionWebhook) Admit(attr admission.Attributes) error { - hookConfig, err := a.loadConfiguration(attr) - if err != nil { - return err - } - hooks := hookConfig.ExternalAdmissionHooks - ctx := context.TODO() - - errCh := make(chan error, len(hooks)) - wg := sync.WaitGroup{} - wg.Add(len(hooks)) - for i := range hooks { - go func(hook *v1alpha1.ExternalAdmissionHook) { - defer wg.Done() - if err := a.callHook(ctx, hook, attr); err == nil { - return - } else if callErr, ok := err.(*ErrCallingWebhook); ok { - glog.Warningf("Failed calling webhook %v: %v", hook.Name, callErr) - utilruntime.HandleError(callErr) - // Since we are failing open to begin with, we do not send an error down the channel - } else { - glog.Warningf("rejected by webhook %v %t: %v", hook.Name, err, err) - errCh <- err - } - }(&hooks[i]) - } - wg.Wait() - close(errCh) - - var errs []error - for e := range errCh { - errs = append(errs, e) - } - if len(errs) == 0 { - return nil - } - if len(errs) > 1 { - for i := 1; i < len(errs); i++ { - // TODO: merge status errors; until then, just return the first one. - utilruntime.HandleError(errs[i]) - } - } - return errs[0] -} - -func (a *GenericAdmissionWebhook) callHook(ctx context.Context, h *v1alpha1.ExternalAdmissionHook, attr admission.Attributes) error { - matches := false - for _, r := range h.Rules { - m := RuleMatcher{Rule: r, Attr: attr} - if m.Matches() { - matches = true - break - } - } - if !matches { - return nil - } - - // Make the webhook request - request := createAdmissionReview(attr) - client, err := a.hookClient(h) - if err != nil { - return &ErrCallingWebhook{WebhookName: h.Name, Reason: err} - } - if err := client.Post().Context(ctx).Body(&request).Do().Into(&request); err != nil { - return &ErrCallingWebhook{WebhookName: h.Name, Reason: err} - } - - if request.Status.Allowed { - return nil - } - - if request.Status.Result == nil { - return fmt.Errorf("admission webhook %q denied the request without explanation", h.Name) - } - - return &apierrors.StatusError{ - ErrStatus: *request.Status.Result, - } -} - -func (a *GenericAdmissionWebhook) hookClient(h *v1alpha1.ExternalAdmissionHook) (*rest.RESTClient, error) { - u, err := a.serviceResolver.ResolveEndpoint(h.ClientConfig.Service.Namespace, h.ClientConfig.Service.Name) - if err != nil { - return nil, err - } - - var dial func(network, addr string) (net.Conn, error) - if a.proxyTransport != nil && a.proxyTransport.Dial != nil { - dial = a.proxyTransport.Dial - } - - // TODO: cache these instead of constructing one each time - cfg := &rest.Config{ - Host: u.Host, - APIPath: u.Path, - TLSClientConfig: rest.TLSClientConfig{ - ServerName: h.ClientConfig.Service.Name + "." + h.ClientConfig.Service.Namespace + ".svc", - CAData: h.ClientConfig.CABundle, - CertData: a.clientCert, - KeyData: a.clientKey, - }, - UserAgent: "kube-apiserver-admission", - Timeout: 30 * time.Second, - ContentConfig: rest.ContentConfig{ - NegotiatedSerializer: a.negotiatedSerializer, - }, - Dial: dial, - } - return rest.UnversionedRESTClientFor(cfg) -} diff --git a/plugin/pkg/admission/webhook/admission_test.go b/plugin/pkg/admission/webhook/admission_test.go deleted file mode 100644 index a5ccc2e39e5..00000000000 --- a/plugin/pkg/admission/webhook/admission_test.go +++ /dev/null @@ -1,272 +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 webhook - -import ( - "crypto/tls" - "crypto/x509" - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "net/url" - "strings" - "testing" - - "k8s.io/api/admission/v1alpha1" - registrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apiserver/pkg/admission" - "k8s.io/apiserver/pkg/authentication/user" - "k8s.io/kubernetes/pkg/api" - - _ "k8s.io/kubernetes/pkg/apis/admission/install" -) - -type fakeHookSource struct { - hooks []registrationv1alpha1.ExternalAdmissionHook - err error -} - -func (f *fakeHookSource) ExternalAdmissionHooks() (*registrationv1alpha1.ExternalAdmissionHookConfiguration, error) { - if f.err != nil { - return nil, f.err - } - return ®istrationv1alpha1.ExternalAdmissionHookConfiguration{ExternalAdmissionHooks: f.hooks}, nil -} - -func (f *fakeHookSource) Run(stopCh <-chan struct{}) {} - -type fakeServiceResolver struct { - base url.URL - path string -} - -func (f fakeServiceResolver) ResolveEndpoint(namespace, name string) (*url.URL, error) { - if namespace == "failResolve" { - return nil, fmt.Errorf("couldn't resolve service location") - } - u := f.base - u.Path = f.path - return &u, nil -} - -// TestAdmit tests that GenericAdmissionWebhook#Admit works as expected -func TestAdmit(t *testing.T) { - // Create the test webhook server - sCert, err := tls.X509KeyPair(serverCert, serverKey) - if err != nil { - t.Fatal(err) - } - rootCAs := x509.NewCertPool() - rootCAs.AppendCertsFromPEM(caCert) - testServer := httptest.NewUnstartedServer(http.HandlerFunc(webhookHandler)) - testServer.TLS = &tls.Config{ - Certificates: []tls.Certificate{sCert}, - ClientCAs: rootCAs, - ClientAuth: tls.RequireAndVerifyClientCert, - } - testServer.StartTLS() - defer testServer.Close() - serverURL, err := url.ParseRequestURI(testServer.URL) - if err != nil { - t.Fatalf("this should never happen? %v", err) - } - wh, err := NewGenericAdmissionWebhook() - if err != nil { - t.Fatal(err) - } - wh.clientCert = clientCert - wh.clientKey = clientKey - - // Set up a test object for the call - kind := api.Kind("Pod").WithVersion("v1") - name := "my-pod" - namespace := "webhook-test" - object := api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "pod.name": name, - }, - Name: name, - Namespace: namespace, - }, - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "Pod", - }, - } - oldObject := api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, - } - operation := admission.Update - resource := api.Resource("pods").WithVersion("v1") - subResource := "" - userInfo := user.DefaultInfo{ - Name: "webhook-test", - UID: "webhook-test", - } - - type test struct { - hookSource fakeHookSource - path string - expectAllow bool - errorContains string - } - ccfg := registrationv1alpha1.AdmissionHookClientConfig{ - Service: registrationv1alpha1.ServiceReference{ - Name: "webhook-test", - Namespace: "default", - }, - CABundle: caCert, - } - matchEverythingRules := []registrationv1alpha1.RuleWithOperations{{ - Operations: []registrationv1alpha1.OperationType{registrationv1alpha1.OperationAll}, - Rule: registrationv1alpha1.Rule{ - APIGroups: []string{"*"}, - APIVersions: []string{"*"}, - Resources: []string{"*/*"}, - }, - }} - - table := map[string]test{ - "no match": { - hookSource: fakeHookSource{ - hooks: []registrationv1alpha1.ExternalAdmissionHook{{ - Name: "nomatch", - ClientConfig: ccfg, - Rules: []registrationv1alpha1.RuleWithOperations{{ - Operations: []registrationv1alpha1.OperationType{registrationv1alpha1.Create}, - }}, - }}, - }, - path: "disallow", - expectAllow: true, - }, - "match & allow": { - hookSource: fakeHookSource{ - hooks: []registrationv1alpha1.ExternalAdmissionHook{{ - Name: "allow", - ClientConfig: ccfg, - Rules: matchEverythingRules, - }}, - }, - path: "allow", - expectAllow: true, - }, - "match & disallow": { - hookSource: fakeHookSource{ - hooks: []registrationv1alpha1.ExternalAdmissionHook{{ - Name: "disallow", - ClientConfig: ccfg, - Rules: matchEverythingRules, - }}, - }, - path: "disallow", - errorContains: "without explanation", - }, - "match & disallow ii": { - hookSource: fakeHookSource{ - hooks: []registrationv1alpha1.ExternalAdmissionHook{{ - Name: "disallowReason", - ClientConfig: ccfg, - Rules: matchEverythingRules, - }}, - }, - path: "disallowReason", - errorContains: "you shall not pass", - }, - "match & fail (but allow because fail open)": { - hookSource: fakeHookSource{ - hooks: []registrationv1alpha1.ExternalAdmissionHook{{ - Name: "internalErr A", - ClientConfig: ccfg, - Rules: matchEverythingRules, - }, { - Name: "internalErr B", - ClientConfig: ccfg, - Rules: matchEverythingRules, - }, { - Name: "internalErr C", - ClientConfig: ccfg, - Rules: matchEverythingRules, - }}, - }, - path: "internalErr", - expectAllow: true, - }, - } - - for name, tt := range table { - wh.hookSource = &tt.hookSource - wh.serviceResolver = fakeServiceResolver{base: *serverURL, path: tt.path} - wh.SetScheme(api.Scheme) - - err = wh.Admit(admission.NewAttributesRecord(&object, &oldObject, kind, namespace, name, resource, subResource, operation, &userInfo)) - if tt.expectAllow != (err == nil) { - t.Errorf("%q: expected allowed=%v, but got err=%v", name, tt.expectAllow, err) - } - // ErrWebhookRejected is not an error for our purposes - if tt.errorContains != "" { - if err == nil || !strings.Contains(err.Error(), tt.errorContains) { - t.Errorf("%q: expected an error saying %q, but got %v", name, tt.errorContains, err) - } - } - } -} - -func webhookHandler(w http.ResponseWriter, r *http.Request) { - fmt.Printf("got req: %v\n", r.URL.Path) - switch r.URL.Path { - case "/internalErr": - http.Error(w, "webhook internal server error", http.StatusInternalServerError) - return - case "/invalidReq": - w.WriteHeader(http.StatusSwitchingProtocols) - w.Write([]byte("webhook invalid request")) - return - case "/invalidResp": - w.Header().Set("Content-Type", "application/json") - w.Write([]byte("webhook invalid response")) - case "/disallow": - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(&v1alpha1.AdmissionReview{ - Status: v1alpha1.AdmissionReviewStatus{ - Allowed: false, - }, - }) - case "/disallowReason": - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(&v1alpha1.AdmissionReview{ - Status: v1alpha1.AdmissionReviewStatus{ - Allowed: false, - Result: &metav1.Status{ - Message: "you shall not pass", - }, - }, - }) - case "/allow": - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(&v1alpha1.AdmissionReview{ - Status: v1alpha1.AdmissionReviewStatus{ - Allowed: true, - }, - }) - default: - http.NotFound(w, r) - } -} diff --git a/plugin/pkg/admission/webhook/certs_test.go b/plugin/pkg/admission/webhook/certs_test.go deleted file mode 100644 index 815d8214cae..00000000000 --- a/plugin/pkg/admission/webhook/certs_test.go +++ /dev/null @@ -1,215 +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. -*/ - -// This file was generated using openssl by the gencerts.sh script -// and holds raw certificates for the webhook tests. - -package webhook - -var caKey = []byte(`-----BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEArHdRY9333U04xKotqnwKwJ52ihSGwr5hf4XkoZ46+ZYiaPS1 -sEeKkFPRZEZIK22cd/2glyy+AsEURi8yeFut7exu41Y6M5Cfgf64keY2EgRu9DlB -ZyQBTD67TfA64xRbztHmgJpBZHpd00R9o1I05OYso7ALFiqEtHDPvUj3PU17h7c3 -xEfq1k7kQq8Tr0EbEQ+GWdE/X0IHW4J4pIjK010KHZSJz4W0wuVgsjDDvV4RGPIR -cXT/nH36gmM+rcxNgtHgNHCSQ7i6ioUMjvg3utp08Vtw0icK9V5MV2MAGjNh5zCi -Uvl+9rTsW5v7tR9Y5puWqBPEWaBpwJwjJlEaAQIDAQABAoIBAC7xMxgJnKOBl0gA -Qfm7VXnkJ8OhnqR3CTaajQZoeQjiEm+a27ElZ9Os3Lt8XbxkU0hdok5DgVxijVAl -HImh+o9d4TjDiYfrf170o+wiSulQh5q10tVt+WR1Vqn6Dy0rp2l9vE2Yrt/YZp1Q -cRn5ECiVdeT/z6Sy4ffzFLgimhj3AUyipDpBUdvqNnC6+OP2T5ZXDGC2LJGU6v4z -zLwjFV1pMRgxLJFoCD9Zy200OkbMYYAhYgaiXMyu5ZU3AjWynPdswANshccAtFt8 -wqVICN20HEoOW1PEM+Z1wOUxpQYUOfL3ieJp9WKklHe7vbY3Iuaotg0Gmg74TNbR -8dcJsjkCgYEA4i1TcnUenv7Tdj9ectN6vXNeGrBUvBO4XFHKauBcGnhZQLuS9GRt -cUJ+Y3rRLpf7rKE18Zl0YDgJRycfsNjbI6U74pcXZ/XBySPLGcfVrEqaTZ9o5apV -UOwbKkP1vtEuGLipFVuvZdinVLAs5QFrO2HoqmXqcYQhjVqHtT0NmGcCgYEAwzT2 -UI/r14yRH7oaRU/cPmDG0pCfMjaXlKmU/oaqyYypn750NiyZqSWdZf7rhBxOCbPE -tEtTGAPgGkEbb5GmpEDuJRtuj25kT0kIGqU6KjnYR0OO3iIr6mQN12Cv16JiFN5B -VNpNSXkR8Hi2kQimz1W/KzUXoInbzaEji9WF2VcCgYEA0FWt6t0k8pGJmP8v8ZcJ -FR8CjJTlyERl6mvQhvfY/uziUbU13PXwtYXpQ5rquf927IGmXb/bKZIUQb0w/MYT -vNbDvaks/y6pbKwStdGT6VrinSN8DSkD40FImHr3DuhBjLXz0V+dxbN2FpUdFWhk -LNO369VqyVtLSJgeLvxo3HsCgYA12kaZsxq9PGpM9mqI9J8uFkTDkmJY1/a5bI9O -KJi1QbkJ+ODWkTdTEq15lfojWCuvQYjitGUYGvmYRJ3tCaGPbtpEIm095JaHyP4T -W8HQJGUmQ90GKycyYqfu4x2fv4yPdUFQx2jK/DuWu7aiDGD4kg9LPDpob5/T+sBz -s1RZwQKBgDhwksMQXoEbQLdpllqOL45hmXu06/eOnG4tCY3OXlTf8UQXn31aPXdr -75W8VyyyB7vlNj414jI8N5xriGuRt36ihC3vmz+RotcHhCjH4sXnylP7hwg1ZuGh -QE9Df6To9LjpRfZZZ9KdAnyx7P/OZPgXPcK7vd9U3+JQ4E8YW4qG ------END RSA PRIVATE KEY-----`) - -var caCert = []byte(`-----BEGIN CERTIFICATE----- -MIIDPTCCAiWgAwIBAgIJAKa7yqtdLQh0MA0GCSqGSIb3DQEBCwUAMDQxMjAwBgNV -BAMMKWdlbmVyaWNfd2ViaG9va19hZG1pc3Npb25fcGx1Z2luX3Rlc3RzX2NhMCAX -DTE3MDkxMzAwMTAwNFoYDzIyOTEwNjI5MDAxMDA0WjA0MTIwMAYDVQQDDClnZW5l -cmljX3dlYmhvb2tfYWRtaXNzaW9uX3BsdWdpbl90ZXN0c19jYTCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAKx3UWPd991NOMSqLap8CsCedooUhsK+YX+F -5KGeOvmWImj0tbBHipBT0WRGSCttnHf9oJcsvgLBFEYvMnhbre3sbuNWOjOQn4H+ -uJHmNhIEbvQ5QWckAUw+u03wOuMUW87R5oCaQWR6XdNEfaNSNOTmLKOwCxYqhLRw -z71I9z1Ne4e3N8RH6tZO5EKvE69BGxEPhlnRP19CB1uCeKSIytNdCh2Uic+FtMLl -YLIww71eERjyEXF0/5x9+oJjPq3MTYLR4DRwkkO4uoqFDI74N7radPFbcNInCvVe -TFdjABozYecwolL5fva07Fub+7UfWOablqgTxFmgacCcIyZRGgECAwEAAaNQME4w -HQYDVR0OBBYEFPa03YxFI220gcsALquzjQkfHztBMB8GA1UdIwQYMBaAFPa03YxF -I220gcsALquzjQkfHztBMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB -AJkgjVhVNAoVdNA55qPPYxouOpOUNyy6GV8ZKCSepz4O5uSRl1FHxTIoodnYzBvO -RkYBJCMupB4Ee+wuMlZGPd3Tfnl6SaP1V4vGYD1iS4otMM6J2tTJBxoLqQwNaDkc -S2E2zfbQ0OxaRNbMBn9AZvZE4lN2TMVuWuTGl1amMLF7Fsxs2ndt+OCpf8d2sWpW -Enh475ch0PJfqZU0papeSV0XRdn60lTGwIbRldUowTBLYcbKo4tTMsCE3oOK7yul -0QhQ9eJktvMbZNMYwGWz+p4pjs/eqBnlBP0KuXguKFHFXILxPlyRMhsvHKURUnC8 -ugGtqN9AEw6SF2Hf1rzmNno= ------END CERTIFICATE-----`) - -var badCAKey = []byte(`-----BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEA5T0xQ7mapkcE+h3owavy8NDxc2eKRe9q/5/yFp27F8QpnY13 -SQS0GXvDiFW1eXdXTg39loy5fG3SZLd/f0eniF2FWKsRvIJbRGyyVBzySbNvbEit -ekVeKQJTTH3Xlnb2JdRrr4vxWQC7crVPksS+AsB+MzdB39XIYxvK6hqwF7On8gro -tzJ+k9e9+/rk0u8FsOBvnE8Z9MX5qX+00UnHSVZuzQt//rTG4Dqsm1yswwTlUfe9 -XpjSh7D6fpjp5YNH9p3vLZMjoY/7ZqO0svHlcWjyv/zoN35p0gMwOGrByATYi2lo -wYBckI8oC+CzyRO8ThKUDW19fa9jKYcqsIDhwQIDAQABAoIBADFvadFWFFCpXhxm -GMyzPRfLp1YgzQPZ5rQrlPRlnXQ5nFParw+zEPex5e/fs9v27X/qqnYt8M4xjL6l -h7w2Ap34tQnzEkcZwX7XBfn3qBRWur+aSLbmgLDNTJNhS/2pt9lenr5jqm9sJgBN -s1ROUz+arVx0HSOdIbKlyrODf9gMQL6hg/ZYNym2KaaYjnNfyrGtb3GQgYDn5FNQ -TSeseXOm82Ev34pSbtQ/oFtU5W4DW72VFA68ciaLM2XslVl/zcncyDk+0LpvSq+W -PYSQbwU/xZukMXXxbBoQUaorNK1fDOdYXBzSzs+Yzo+71D3azFaqPv6CMlHCYeA8 -EUkDRAECgYEA8rmOnzjoHStjkOr7Yb5zqX4UyC0VvB6S9EJxy2a133K0eXJ82KPR -L7fkkvZk04PwDUpClTqcTw3gblhIaBYsFqTnCn6+8hS1Lqa6OqUYo5/UD1O4mXVh -ebLvss5CNqA2+ii80XkRZSW4w/27CG+u/iBAiS5JeYi0zm89G5PYbCECgYEA8cbR -VoUB6rFNC0VOd/D+LPMWaCU7FguAJQ+8Opoh/JgmxFqWwWYeteuX0tbGrKb8LXSA -XLEL1vrZjQ2W9tKP4hP9rgiZbvNujPABS1ey5mMFDPMy6q2OYgLY85jpeYU9PpRt -dOZiw7a0TFENRU19/WMeSfdDvEzafX6tqA/gwaECgYEA0337V6EuHrx/tPYKs9BO -15CUaxddqNy7DzoWDTUho+E+f9PSFLIow3toHuWyVNrRf8ME4SKAsCFXPM6PyKIJ -KHHnHq3xkt2YQV3lRtQz895/2BsK7ivpEzFmylYOO6q+PJria2MiVQ/ZPm0HWwJ1 -Z9iSYvWB7/O+F2G1zSG1ogECgYEAtoV0VY+VsdplokORCGULTV26JactoufNtqzZ -WZgwXiNy6LrGonv4ZTfU5tszIvXw3FPd75vMp1+6Soze0biF3JNg6DgftK3bYFRz -dbBgIyLPlkYmwxmAqqchp0xhvVaDtLGSrDScjMlp9U8e6JmmqlpgbFBZd1bBfwna -CUzrTOECgYEAx98UtYAFWVwoCZwtqSfzTKHerFN/qMFY34cXhdns2ZWn6kEb6alk -dXqNDkXV/7EBGWAcvKn/zbrwlhVDwPHUETgjCUpxqST0ly6E/JnwiNbrSOWG4Yg0 -RoKj46nUcolMbgRub+yeQqcQ48Y2/1OvnRUQ9aQ5MqVSyTyylgMKlB8= ------END RSA PRIVATE KEY-----`) - -var badCACert = []byte(`-----BEGIN CERTIFICATE----- -MIIDPTCCAiWgAwIBAgIJAOQ6iHm4OCSMMA0GCSqGSIb3DQEBCwUAMDQxMjAwBgNV -BAMMKWdlbmVyaWNfd2ViaG9va19hZG1pc3Npb25fcGx1Z2luX3Rlc3RzX2NhMCAX -DTE3MDkxMzAwMTAwNFoYDzIyOTEwNjI5MDAxMDA0WjA0MTIwMAYDVQQDDClnZW5l -cmljX3dlYmhvb2tfYWRtaXNzaW9uX3BsdWdpbl90ZXN0c19jYTCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAOU9MUO5mqZHBPod6MGr8vDQ8XNnikXvav+f -8haduxfEKZ2Nd0kEtBl7w4hVtXl3V04N/ZaMuXxt0mS3f39Hp4hdhVirEbyCW0Rs -slQc8kmzb2xIrXpFXikCU0x915Z29iXUa6+L8VkAu3K1T5LEvgLAfjM3Qd/VyGMb -yuoasBezp/IK6LcyfpPXvfv65NLvBbDgb5xPGfTF+al/tNFJx0lWbs0Lf/60xuA6 -rJtcrMME5VH3vV6Y0oew+n6Y6eWDR/ad7y2TI6GP+2ajtLLx5XFo8r/86Dd+adID -MDhqwcgE2ItpaMGAXJCPKAvgs8kTvE4SlA1tfX2vYymHKrCA4cECAwEAAaNQME4w -HQYDVR0OBBYEFEe2nQ5ONNJFv/+6pWnzvfqRNTR9MB8GA1UdIwQYMBaAFEe2nQ5O -NNJFv/+6pWnzvfqRNTR9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB -AEORwyo3MJZVuYbqft728t9MWw0wFORzxPnFVLXMIrX223flNEmqvkA8UBSulxmq -yDYOOkOWByd2eemCZtc/MlmiSeYNFSi/PS+METkAef2cZfSQNdIUwwl5BZ4vW973 -J+fDNm0LQh3ZPDngylQ3XprQkF7l+UefRVfbDDTALVDzmJCV/FCm05ijg+HssgwX -+rYI9ZdJbp2z9xftN0RRIwrBQ3S9vA1mq6OoZFco+9Oq5Li5DHv0BpQgOS64dY1z -dtOy4RvQNF8NsITkOlp7f39716448dvq8TJlCdE27ptL44hU82cZib3cwQx8cynu -45owbCivEhgee3RUY7gVuc8= ------END CERTIFICATE-----`) - -var serverKey = []byte(`-----BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAxjTbuWppAQB140aAxVVo0jdcGw5id4IAa8ji3T/X1+mDIkU8 -dA8kfpwgaeurh3Nai0R6LT9ZZ5aJD8a2udhVivY0Q11snw/4G40avL2yPr1VJSKk -lv5/b1knSh53Cvj+7ma6yjn5yBxw8V7v++37yVe6LHP3B0QBvR07n9k507gKHkw/ -R924I6JUdBtlqoIPwRJlWRVyJ5RawfDZ+KX2d9rY0WAxW60Xge8gWPMv8XKViyw+ -p5+5ct6aHiu6/vtRP5N70lL8HXQjr+x0qaFd6m3/SajjX8AKvdIQzDocTzmjdlRL -Rj8kvLYf8fPazSybqWv0JeXkVxCZPv25SN26BwIDAQABAoIBAGYsqXgTmr2hdyQK -HCedt8NmNlzcNXZV1dG6ZPiZCLOM9MSd3GQXykBaS3tOucXBeVOBoVnh5jy4JT+0 -uE1lb/OKp7ZyWqREnynUu4vAXjppb5MNILuVxiuoUdCrk8JcSU6sNm45JMI7px1G -S4AbVkicqKRxw05DiIHsp+fnGyBAPRK5+NxHzvERGH9Fxu6jKngSEfz345hZet07 -C93tREsRYAPymjMO3Z/vLzO8MjwvLrL+ko6RR1/+VQjWG/tSgnSEhOpGibgWl4ni -4MvgipRzxkPfRE0qhAjbiQc/epRKDWWTkVfCDruKPSu5SIqD5NmT+MJTws25ZO4c -C3yh08ECgYEA//nPrMwOFft47maJR+y5TS+ym7pemMAztnB+9sLQB1iAF4RP29sK -s6WsQmxAiqb0X8lP6N0uRXBD/Cc30hXFfkfx4iWZl5/GYSswEVNXVx7GlFjh1DdB -dbAK/04+/pJGHWaWJFyoWy8+HRDezJ33dF/y6BVX85eA1b8jdvrBB/UCgYEAxjmm -fo2B1UDVfPb48SbiVV2w8VVo8p6/AOUxUYg10ZkJj5zSB76FC2yfYVPZGCnt2dcZ -N3EPQ4ETVbYgL9N1fgukzWSez38SFuYDajJUxJ7tt4G9W881pZPfi3B1by7xcyaW -4M9gDvsvM2QYPGiz6mH1DwxG2rtY0ktolWkbyIsCgYEAo8xmYSueY+CsbNl+RWEs -3kCEaXRj7hknvjnUdPEKj3jJVsMbGxPakESWq1Z8In1daSH4GYnXfyWsy2EJLk0y -OHGvTchDtavPFQS+2IddH2mZJvqNX/AP2lBRaTfXxa0yYsPvlcsZDGh5tb3C5Gq9 -G2H+nRZzVnP/REfwWMVy2jUCgYEApgsKlT2RwQGTEx+J/e71bk6R9kX2KC2jj2ts -+X/gnRbVdHAHWydTKPOvOgbTdjNBItXUMKXLBF+tw4FQyt8VrySvwsEDaoplq7q2 -p5FLgnwiYjISXUJgDLembJYiOKUY6b0sa1oqe8IakrDIwGlwM+gkL5u4CmceiuFR -1L374OsCgYAA5KmaCPoDefbvjpQ0EEZ+EFUyT9zVPHvqJa0aYo9KeWGfD/c9gYcQ -WYWmap9Ed7RfMSxSE3oS2JCFIW19Y0HlBiyu+mi3oJ1ErJRmzgNQHmMkzDr1plQ6 -Zs423AVUJak0hiqLhF2o/I+pbbtGXB1TBaR6d6cGNP3wTCHtCjNc6w== ------END RSA PRIVATE KEY-----`) - -var serverCert = []byte(`-----BEGIN CERTIFICATE----- -MIIDJjCCAg6gAwIBAgIJAPJEHs7Aav1jMA0GCSqGSIb3DQEBCwUAMDQxMjAwBgNV -BAMMKWdlbmVyaWNfd2ViaG9va19hZG1pc3Npb25fcGx1Z2luX3Rlc3RzX2NhMCAX -DTE3MDkxMzAwMTAwNFoYDzIyOTEwNjI5MDAxMDA0WjAjMSEwHwYDVQQDExh3ZWJo -b29rLXRlc3QuZGVmYXVsdC5zdmMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK -AoIBAQDGNNu5amkBAHXjRoDFVWjSN1wbDmJ3ggBryOLdP9fX6YMiRTx0DyR+nCBp -66uHc1qLRHotP1lnlokPxra52FWK9jRDXWyfD/gbjRq8vbI+vVUlIqSW/n9vWSdK -HncK+P7uZrrKOfnIHHDxXu/77fvJV7osc/cHRAG9HTuf2TnTuAoeTD9H3bgjolR0 -G2Wqgg/BEmVZFXInlFrB8Nn4pfZ32tjRYDFbrReB7yBY8y/xcpWLLD6nn7ly3poe -K7r++1E/k3vSUvwddCOv7HSpoV3qbf9JqONfwAq90hDMOhxPOaN2VEtGPyS8th/x -89rNLJupa/Ql5eRXEJk+/blI3boHAgMBAAGjSjBIMAkGA1UdEwQCMAAwCwYDVR0P -BAQDAgXgMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHREECDAG -hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQAp99rbIZlUhvNmxhiQCMcltLqtIpGs -ZorxbInV4QCdV+Kybot+L7kUDxu/OqERNLqr1qRsjqJRHUhiZZVv8gjZgwz4bO1/ -ycjUyAoS92OMKEWlvXe1nMnHH3Jw+lXnwiaOumoExhB1gpMjGqsU3mBQrHXGenNS -JyJtRsBFnkgk1hGycJPgNc/juaHuGBexLDdPqOy3Zmv1AtzFhvPzOTEFpaIGa5dz -vewjDBFBCpFFTqpISD93JX6AbkucPVKnfF97DkBLyj3mr+X9Cy0pLGjwN8Gcp1Ez -5MvTqbOmqLypcRJu285K0Yxmv7a38GND7xeoijfnPFjVjYGlacCnI7ps ------END CERTIFICATE-----`) - -var clientKey = []byte(`-----BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEA1jCNOxaX7C6bH8YffvoneePNQVAnMejecJqQz+KJHdrwTlOD -dqzpBVf21tzUo2AU7xoKzpHnEZ5PvBlgzveBhBy3E48ILWRs4ZCnCXYRG62LD6fI -ub0uEt2IwHiT0A6q1fGrAYlrzLkYRz3LQyOQhL0Ca7nB2IeCzY/Vfz0TR2+4bMtY -54Go5G+lZRP1N88E2cplS8YEZLPlQqqVS6J15bkpnAMmQWL5x67cUbnXdST4qvns -3cSqjjC0LKhOd1aInxdCgwL7dT4lwF64Jz1/25OSUVFN5J5Y8xmPqQWHNtauVTjw -/StV1DYfe3LnVhVriQ+aNu/cssfe1Se7x6w7HQIDAQABAoIBACRaOy4jKIfCZTug -UaooZNjQK/8AzpYu8snjwd42kZUKmqyAihhzQl1Qz6kp88ECxqrKHblvk+sullPT -btXRth6pDP150iZ6G+yws1jsu/yZmLeAf5XeoNo81T/tdxDh3GbRTHfHTg+B/rfg -qgXsHFQbDDUiYt8QKMgguFiPEh2WbedyAe1N9LhtEazMhrnLXCUxTXIFddmcKvnk -fTcnV6mG4FftXFwh+Qn4HukphENHD+xazxIp/WFGDbbStQ8sbjkUSydU0w5x/eQ/ -V6z7ibgDmlHoSyKCxc0B0wuNE/BBQzQGvy2T9UhA4V2+vwuQpcXxM77tXMNnmxaX -fEXHQBUCgYEA+sYZKOTL7JCJNnGexVVxn6OWZUuSf94LAyVBCveHnItlFgowzwU1 -NYUFD894SR7orvbnSHvEOvim10C7YoyDcsH5+zt4K7dMnVJ6I6I4mNv0r/1WylHe -JyNkYP7jn6znMqVgvo4YHCMrk2OoPIE8jeH1aN+I/YOTi47BIb+MbfcCgYEA2qdG -AbgwVoWeQT7Z+3iJxTgaZGjO66Id2164HMK0NGeYjNeWikt58Y5Vitblk6h8JqQy -yOipyyIbQFnQbmTo6vlvNTKw2NOrPHkYktridIfFrinYqEmKfhbhQWnZAOTjOnpJ -9RFC5LbErmrf5G6HIWV8bfX/kBvi0QbF6VYuKosCgYB2Ztf0PeqmnCuc4BKFu2z1 -YcidtQvLgawTZSCLrAmEeTBWMqOO6zePOGoGZ/+0DnrwOTVEPOOOsF4d3btbsVpS -8ZE09IQtp9LtqMZwUqSET7385hF3XyYTtpsrTM1uU7WpbPn7np11k4l8gp4pSx+r -Ide8F2bXw6sDRniblZQZSwKBgQCjN4RZmj1zCLEWcS1UuyjUcEm7NEVpvY1eCLmU -tn7AM6i7Ud8NAsRXXXFbf4jGDVoHmkBSmuLMQHxpL+IX1fnMFUA/TMSYRoEnVhnS -3dN3OzaECLazAJqB/uBM7Q9QzIsWRtzYM/dkNU5iCGNy6FK0ykX061HHKBnLAKxR -vsQdewKBgGCZiy/QYdGR3jdfD7Qytfuptgf6zFLB0BqHJTG/YmnonCDTI8ZxmDOF -DspoiVbrjYdHED0IfcnXYNK8+Bv/2vVxAGqqCdA/aJbFXx86zKJvu8ZHcFrWl6Fy -vdk6OsJjqpA0RM/lD2kVvHeaBO44qGmMFfZHv52ONu+wE4mVX6Y8 ------END RSA PRIVATE KEY-----`) - -var clientCert = []byte(`-----BEGIN CERTIFICATE----- -MIIDOzCCAiOgAwIBAgIJAPJEHs7Aav1kMA0GCSqGSIb3DQEBCwUAMDQxMjAwBgNV -BAMMKWdlbmVyaWNfd2ViaG9va19hZG1pc3Npb25fcGx1Z2luX3Rlc3RzX2NhMCAX -DTE3MDkxMzAwMTAwNFoYDzIyOTEwNjI5MDAxMDA0WjA4MTYwNAYDVQQDFC1nZW5l -cmljX3dlYmhvb2tfYWRtaXNzaW9uX3BsdWdpbl90ZXN0c19jbGllbnQwggEiMA0G -CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDWMI07FpfsLpsfxh9++id5481BUCcx -6N5wmpDP4okd2vBOU4N2rOkFV/bW3NSjYBTvGgrOkecRnk+8GWDO94GEHLcTjwgt -ZGzhkKcJdhEbrYsPp8i5vS4S3YjAeJPQDqrV8asBiWvMuRhHPctDI5CEvQJrucHY -h4LNj9V/PRNHb7hsy1jngajkb6VlE/U3zwTZymVLxgRks+VCqpVLonXluSmcAyZB -YvnHrtxRudd1JPiq+ezdxKqOMLQsqE53VoifF0KDAvt1PiXAXrgnPX/bk5JRUU3k -nljzGY+pBYc21q5VOPD9K1XUNh97cudWFWuJD5o279yyx97VJ7vHrDsdAgMBAAGj -SjBIMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMB0GA1UdJQQWMBQGCCsGAQUFBwMC -BggrBgEFBQcDATAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQBh -cqu6S6MkUUtNhlWR33ZfV6Ewoz06IGtUIUDDuRmVuizCDOdCcGOTDvdoXtbZMwUf -ZRVVaSyIjqdz5k+C06mHM7uEEgo+Xo7YolK1AiW9VflG8K47l/+XlLoHFv0eB9k/ -KdlFkzh3pszM4uEiynkLlSpQxyyBOhyaVdQlXdamqmSfIO0HlLza1OAP2rMILeMb -GJ0y+DCxxAspwVvmCUzc5nL6t35+oEDihRk9bjGDLhfh+b0AYSO4RUjzhNlayUBG -/Kbxh6GiimYmWwpIUTcTAbO7gB7Ya5eXmEB80McS0Z6MY/AEBHnu2Cy8BVEDY30f -uOhGOj+iPQxidcxc+wuy ------END CERTIFICATE-----`) diff --git a/plugin/pkg/admission/webhook/doc.go b/plugin/pkg/admission/webhook/doc.go deleted file mode 100644 index e4efbbeb210..00000000000 --- a/plugin/pkg/admission/webhook/doc.go +++ /dev/null @@ -1,18 +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 webhook checks a webhook for configured operation admission -package webhook // import "k8s.io/kubernetes/plugin/pkg/admission/webhook" diff --git a/plugin/pkg/admission/webhook/gencerts.sh b/plugin/pkg/admission/webhook/gencerts.sh deleted file mode 100755 index 3ee1a094fcd..00000000000 --- a/plugin/pkg/admission/webhook/gencerts.sh +++ /dev/null @@ -1,107 +0,0 @@ -#!/bin/bash - -# Copyright 2017 The Kubernetes Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -e - -# gencerts.sh generates the certificates for the generic webhook admission plugin tests. -# -# It is not expected to be run often (there is no go generate rule), and mainly -# exists for documentation purposes. - -CN_BASE="generic_webhook_admission_plugin_tests" - -cat > server.conf << EOF -[req] -req_extensions = v3_req -distinguished_name = req_distinguished_name -[req_distinguished_name] -[ v3_req ] -basicConstraints = CA:FALSE -keyUsage = nonRepudiation, digitalSignature, keyEncipherment -extendedKeyUsage = clientAuth, serverAuth -subjectAltName = @alt_names -[alt_names] -IP.1 = 127.0.0.1 -EOF - -cat > client.conf << EOF -[req] -req_extensions = v3_req -distinguished_name = req_distinguished_name -[req_distinguished_name] -[ v3_req ] -basicConstraints = CA:FALSE -keyUsage = nonRepudiation, digitalSignature, keyEncipherment -extendedKeyUsage = clientAuth, serverAuth -subjectAltName = @alt_names -[alt_names] -IP.1 = 127.0.0.1 -EOF - -# Create a certificate authority -openssl genrsa -out caKey.pem 2048 -openssl req -x509 -new -nodes -key caKey.pem -days 100000 -out caCert.pem -subj "/CN=${CN_BASE}_ca" - -# Create a second certificate authority -openssl genrsa -out badCAKey.pem 2048 -openssl req -x509 -new -nodes -key badCAKey.pem -days 100000 -out badCACert.pem -subj "/CN=${CN_BASE}_ca" - -# Create a server certiticate -openssl genrsa -out serverKey.pem 2048 -openssl req -new -key serverKey.pem -out server.csr -subj "/CN=webhook-test.default.svc" -config server.conf -openssl x509 -req -in server.csr -CA caCert.pem -CAkey caKey.pem -CAcreateserial -out serverCert.pem -days 100000 -extensions v3_req -extfile server.conf - -# Create a client certiticate -openssl genrsa -out clientKey.pem 2048 -openssl req -new -key clientKey.pem -out client.csr -subj "/CN=${CN_BASE}_client" -config client.conf -openssl x509 -req -in client.csr -CA caCert.pem -CAkey caKey.pem -CAcreateserial -out clientCert.pem -days 100000 -extensions v3_req -extfile client.conf - -outfile=certs_test.go - -cat > $outfile << EOF -/* -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. -*/ - -EOF - -echo "// This file was generated using openssl by the gencerts.sh script" >> $outfile -echo "// and holds raw certificates for the webhook tests." >> $outfile -echo "" >> $outfile -echo "package webhook" >> $outfile -for file in caKey caCert badCAKey badCACert serverKey serverCert clientKey clientCert; do - data=$(cat ${file}.pem) - echo "" >> $outfile - echo "var $file = []byte(\`$data\`)" >> $outfile -done - -# Clean up after we're done. -rm *.pem -rm *.csr -rm *.srl -rm *.conf diff --git a/plugin/pkg/admission/webhook/serviceresolver.go b/plugin/pkg/admission/webhook/serviceresolver.go deleted file mode 100644 index 018402934fe..00000000000 --- a/plugin/pkg/admission/webhook/serviceresolver.go +++ /dev/null @@ -1,42 +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 webhook checks a webhook for configured operation admission -package webhook - -import ( - "errors" - "fmt" - "net/url" - - admissioninit "k8s.io/kubernetes/pkg/kubeapiserver/admission" -) - -type defaultServiceResolver struct{} - -var _ admissioninit.ServiceResolver = defaultServiceResolver{} - -// ResolveEndpoint constructs a service URL from a given namespace and name -// note that the name and namespace are required and by default all created addresses use HTTPS scheme. -// for example: -// name=ross namespace=andromeda resolves to https://ross.andromeda.svc -func (sr defaultServiceResolver) ResolveEndpoint(namespace, name string) (*url.URL, error) { - if len(name) == 0 || len(namespace) == 0 { - return &url.URL{}, errors.New("cannot resolve an empty service name or namespace") - } - - return url.Parse(fmt.Sprintf("https://%s.%s.svc", name, namespace)) -} diff --git a/plugin/pkg/auth/authenticator/token/bootstrap/BUILD b/plugin/pkg/auth/authenticator/token/bootstrap/BUILD index c852e70587c..d2f3a239d45 100644 --- a/plugin/pkg/auth/authenticator/token/bootstrap/BUILD +++ b/plugin/pkg/auth/authenticator/token/bootstrap/BUILD @@ -9,10 +9,10 @@ load( go_test( name = "go_default_test", srcs = ["bootstrap_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/bootstrap", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/bootstrap/api:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -27,7 +27,7 @@ go_library( srcs = ["bootstrap.go"], importpath = "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/bootstrap", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/bootstrap/api:go_default_library", "//pkg/client/listers/core/internalversion:go_default_library", "//vendor/github.com/golang/glog:go_default_library", diff --git a/plugin/pkg/auth/authenticator/token/bootstrap/bootstrap.go b/plugin/pkg/auth/authenticator/token/bootstrap/bootstrap.go index fecea0f6855..90a0137489d 100644 --- a/plugin/pkg/auth/authenticator/token/bootstrap/bootstrap.go +++ b/plugin/pkg/auth/authenticator/token/bootstrap/bootstrap.go @@ -31,7 +31,7 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apiserver/pkg/authentication/user" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api" "k8s.io/kubernetes/pkg/client/listers/core/internalversion" ) diff --git a/plugin/pkg/auth/authenticator/token/bootstrap/bootstrap_test.go b/plugin/pkg/auth/authenticator/token/bootstrap/bootstrap_test.go index 7e76faaf63a..f319cc8667b 100644 --- a/plugin/pkg/auth/authenticator/token/bootstrap/bootstrap_test.go +++ b/plugin/pkg/auth/authenticator/token/bootstrap/bootstrap_test.go @@ -25,7 +25,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/authentication/user" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api" ) diff --git a/plugin/pkg/auth/authorizer/node/BUILD b/plugin/pkg/auth/authorizer/node/BUILD index fb4aa7008ac..029fbcf5f43 100644 --- a/plugin/pkg/auth/authorizer/node/BUILD +++ b/plugin/pkg/auth/authorizer/node/BUILD @@ -9,10 +9,10 @@ load( go_test( name = "go_default_test", srcs = ["node_authorizer_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/auth/authorizer/node", - library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/auth/nodeidentifier:go_default_library", "//plugin/pkg/auth/authorizer/rbac/bootstrappolicy:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -30,12 +30,13 @@ go_library( ], importpath = "k8s.io/kubernetes/plugin/pkg/auth/authorizer/node", deps = [ - "//pkg/api:go_default_library", "//pkg/api/persistentvolume:go_default_library", "//pkg/api/pod:go_default_library", + "//pkg/apis/core:go_default_library", "//pkg/apis/rbac:go_default_library", "//pkg/auth/nodeidentifier:go_default_library", "//pkg/client/informers/informers_generated/internalversion/core/internalversion:go_default_library", + "//pkg/features:go_default_library", "//plugin/pkg/auth/authorizer/rbac:go_default_library", "//third_party/forked/gonum/graph:go_default_library", "//third_party/forked/gonum/graph/simple:go_default_library", @@ -43,6 +44,7 @@ go_library( "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", ], ) diff --git a/plugin/pkg/auth/authorizer/node/graph.go b/plugin/pkg/auth/authorizer/node/graph.go index cdcd0201f8b..729cad2ea77 100644 --- a/plugin/pkg/auth/authorizer/node/graph.go +++ b/plugin/pkg/auth/authorizer/node/graph.go @@ -19,9 +19,9 @@ package node import ( "sync" - "k8s.io/kubernetes/pkg/api" pvutil "k8s.io/kubernetes/pkg/api/persistentvolume" podutil "k8s.io/kubernetes/pkg/api/pod" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/third_party/forked/gonum/graph" "k8s.io/kubernetes/third_party/forked/gonum/graph/simple" ) diff --git a/plugin/pkg/auth/authorizer/node/graph_populator.go b/plugin/pkg/auth/authorizer/node/graph_populator.go index ecea060d6ec..64ea648227c 100644 --- a/plugin/pkg/auth/authorizer/node/graph_populator.go +++ b/plugin/pkg/auth/authorizer/node/graph_populator.go @@ -20,7 +20,7 @@ import ( "github.com/golang/glog" "k8s.io/client-go/tools/cache" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" coreinformers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/core/internalversion" ) diff --git a/plugin/pkg/auth/authorizer/node/node_authorizer.go b/plugin/pkg/auth/authorizer/node/node_authorizer.go index 01300801cb4..bf728ccc5e6 100644 --- a/plugin/pkg/auth/authorizer/node/node_authorizer.go +++ b/plugin/pkg/auth/authorizer/node/node_authorizer.go @@ -23,9 +23,11 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/authorization/authorizer" - "k8s.io/kubernetes/pkg/api" + utilfeature "k8s.io/apiserver/pkg/util/feature" + api "k8s.io/kubernetes/pkg/apis/core" rbacapi "k8s.io/kubernetes/pkg/apis/rbac" "k8s.io/kubernetes/pkg/auth/nodeidentifier" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac" "k8s.io/kubernetes/third_party/forked/gonum/graph" "k8s.io/kubernetes/third_party/forked/gonum/graph/traverse" @@ -64,16 +66,16 @@ var ( pvResource = api.Resource("persistentvolumes") ) -func (r *NodeAuthorizer) Authorize(attrs authorizer.Attributes) (bool, string, error) { +func (r *NodeAuthorizer) Authorize(attrs authorizer.Attributes) (authorizer.Decision, string, error) { nodeName, isNode := r.identifier.NodeIdentity(attrs.GetUser()) if !isNode { // reject requests from non-nodes - return false, "", nil + return authorizer.DecisionNoOpinion, "", nil } if len(nodeName) == 0 { // reject requests from unidentifiable nodes glog.V(2).Infof("NODE DENY: unknown node for user %q", attrs.GetUser().GetName()) - return false, fmt.Sprintf("unknown node for user %q", attrs.GetUser().GetName()), nil + return authorizer.DecisionNoOpinion, fmt.Sprintf("unknown node for user %q", attrs.GetUser().GetName()), nil } // subdivide access to specific resources @@ -85,6 +87,11 @@ func (r *NodeAuthorizer) Authorize(attrs authorizer.Attributes) (bool, string, e case configMapResource: return r.authorizeGet(nodeName, configMapVertexType, attrs) case pvcResource: + if utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) { + if attrs.GetSubresource() == "status" { + return r.authorizeStatusUpdate(nodeName, pvcVertexType, attrs) + } + } return r.authorizeGet(nodeName, pvcVertexType, attrs) case pvResource: return r.authorizeGet(nodeName, pvVertexType, attrs) @@ -92,31 +99,59 @@ func (r *NodeAuthorizer) Authorize(attrs authorizer.Attributes) (bool, string, e } // Access to other resources is not subdivided, so just evaluate against the statically defined node rules - return rbac.RulesAllow(attrs, r.nodeRules...), "", nil + if rbac.RulesAllow(attrs, r.nodeRules...) { + return authorizer.DecisionAllow, "", nil + } + return authorizer.DecisionNoOpinion, "", nil +} + +// authorizeStatusUpdate authorizes get/update/patch requests to status subresources of the specified type if they are related to the specified node +func (r *NodeAuthorizer) authorizeStatusUpdate(nodeName string, startingType vertexType, attrs authorizer.Attributes) (authorizer.Decision, string, error) { + switch attrs.GetVerb() { + case "update", "patch": + // ok + default: + glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs) + return authorizer.DecisionNoOpinion, "can only get/update/patch this type", nil + } + + if attrs.GetSubresource() != "status" { + glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs) + return authorizer.DecisionNoOpinion, "can only update status subresource", nil + } + + return r.authorize(nodeName, startingType, attrs) } // authorizeGet authorizes "get" requests to objects of the specified type if they are related to the specified node -func (r *NodeAuthorizer) authorizeGet(nodeName string, startingType vertexType, attrs authorizer.Attributes) (bool, string, error) { - if attrs.GetVerb() != "get" || len(attrs.GetName()) == 0 { +func (r *NodeAuthorizer) authorizeGet(nodeName string, startingType vertexType, attrs authorizer.Attributes) (authorizer.Decision, string, error) { + if attrs.GetVerb() != "get" { glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs) - return false, "can only get individual resources of this type", nil + return authorizer.DecisionNoOpinion, "can only get individual resources of this type", nil } - if len(attrs.GetSubresource()) > 0 { glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs) - return false, "cannot get subresource", nil + return authorizer.DecisionNoOpinion, "cannot get subresource", nil + } + return r.authorize(nodeName, startingType, attrs) +} + +func (r *NodeAuthorizer) authorize(nodeName string, startingType vertexType, attrs authorizer.Attributes) (authorizer.Decision, string, error) { + if len(attrs.GetName()) == 0 { + glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs) + return authorizer.DecisionNoOpinion, "No Object name found", nil } ok, err := r.hasPathFrom(nodeName, startingType, attrs.GetNamespace(), attrs.GetName()) if err != nil { glog.V(2).Infof("NODE DENY: %v", err) - return false, "no path found to object", nil + return authorizer.DecisionNoOpinion, "no path found to object", nil } if !ok { glog.V(2).Infof("NODE DENY: %q %#v", nodeName, attrs) - return false, "no path found to object", nil + return authorizer.DecisionNoOpinion, "no path found to object", nil } - return ok, "", nil + return authorizer.DecisionAllow, "", nil } // hasPathFrom returns true if there is a directed path from the specified type/namespace/name to the specified Node diff --git a/plugin/pkg/auth/authorizer/node/node_authorizer_test.go b/plugin/pkg/auth/authorizer/node/node_authorizer_test.go index 39f10b62ee5..4f5f731bda0 100644 --- a/plugin/pkg/auth/authorizer/node/node_authorizer_test.go +++ b/plugin/pkg/auth/authorizer/node/node_authorizer_test.go @@ -27,7 +27,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" - "k8s.io/kubernetes/pkg/api" + api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/auth/nodeidentifier" "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy" ) @@ -57,71 +57,71 @@ func TestAuthorizer(t *testing.T) { tests := []struct { name string attrs authorizer.AttributesRecord - expect bool + expect authorizer.Decision }{ { name: "allowed configmap", attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "configmaps", Name: "configmap0-pod0-node0", Namespace: "ns0"}, - expect: true, + expect: authorizer.DecisionAllow, }, { name: "allowed secret via pod", attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-pod0-node0", Namespace: "ns0"}, - expect: true, + expect: authorizer.DecisionAllow, }, { name: "allowed shared secret via pod", attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-shared", Namespace: "ns0"}, - expect: true, + expect: authorizer.DecisionAllow, }, { name: "allowed shared secret via pvc", attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret-pv0-pod0-node0-ns0", Namespace: "ns0"}, - expect: true, + expect: authorizer.DecisionAllow, }, { name: "allowed pvc", attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumeclaims", Name: "pvc0-pod0-node0", Namespace: "ns0"}, - expect: true, + expect: authorizer.DecisionAllow, }, { name: "allowed pv", attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumes", Name: "pv0-pod0-node0-ns0", Namespace: ""}, - expect: true, + expect: authorizer.DecisionAllow, }, { name: "disallowed configmap", attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "configmaps", Name: "configmap0-pod0-node1", Namespace: "ns0"}, - expect: false, + expect: authorizer.DecisionNoOpinion, }, { name: "disallowed secret via pod", attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-pod0-node1", Namespace: "ns0"}, - expect: false, + expect: authorizer.DecisionNoOpinion, }, { name: "disallowed shared secret via pvc", attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret-pv0-pod0-node1-ns0", Namespace: "ns0"}, - expect: false, + expect: authorizer.DecisionNoOpinion, }, { name: "disallowed pvc", attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumeclaims", Name: "pvc0-pod0-node1", Namespace: "ns0"}, - expect: false, + expect: authorizer.DecisionNoOpinion, }, { name: "disallowed pv", attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumes", Name: "pv0-pod0-node1-ns0", Namespace: ""}, - expect: false, + expect: authorizer.DecisionNoOpinion, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - ok, _, _ := authz.Authorize(tc.attrs) - if ok != tc.expect { - t.Errorf("expected %v, got %v", tc.expect, ok) + decision, _, _ := authz.Authorize(tc.attrs) + if decision != tc.expect { + t.Errorf("expected %v, got %v", tc.expect, decision) } }) } @@ -186,13 +186,13 @@ func TestAuthorizerSharedResources(t *testing.T) { } for i, tc := range testcases { - ok, _, err := authz.Authorize(authorizer.AttributesRecord{User: tc.User, ResourceRequest: true, Verb: "get", Resource: "secrets", Namespace: "ns1", Name: tc.Secret}) + decision, _, err := authz.Authorize(authorizer.AttributesRecord{User: tc.User, ResourceRequest: true, Verb: "get", Resource: "secrets", Namespace: "ns1", Name: tc.Secret}) if err != nil { t.Errorf("%d: unexpected error: %v", i, err) continue } - if ok != tc.ExpectAllowed { - t.Errorf("%d: expected %v, got %v", i, tc.ExpectAllowed, ok) + if (decision == authorizer.DecisionAllow) != tc.ExpectAllowed { + t.Errorf("%d: expected %v, got %v", i, tc.ExpectAllowed, decision) } } } @@ -301,47 +301,47 @@ func BenchmarkAuthorization(b *testing.B) { tests := []struct { name string attrs authorizer.AttributesRecord - expect bool + expect authorizer.Decision }{ { name: "allowed configmap", attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "configmaps", Name: "configmap0-pod0-node0", Namespace: "ns0"}, - expect: true, + expect: authorizer.DecisionAllow, }, { name: "allowed secret via pod", attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-pod0-node0", Namespace: "ns0"}, - expect: true, + expect: authorizer.DecisionAllow, }, { name: "allowed shared secret via pod", attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-shared", Namespace: "ns0"}, - expect: true, + expect: authorizer.DecisionAllow, }, { name: "disallowed configmap", attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "configmaps", Name: "configmap0-pod0-node1", Namespace: "ns0"}, - expect: false, + expect: authorizer.DecisionNoOpinion, }, { name: "disallowed secret via pod", attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-pod0-node1", Namespace: "ns0"}, - expect: false, + expect: authorizer.DecisionNoOpinion, }, { name: "disallowed shared secret via pvc", attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret-pv0-pod0-node1-ns0", Namespace: "ns0"}, - expect: false, + expect: authorizer.DecisionNoOpinion, }, { name: "disallowed pvc", attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumeclaims", Name: "pvc0-pod0-node1", Namespace: "ns0"}, - expect: false, + expect: authorizer.DecisionNoOpinion, }, { name: "disallowed pv", attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumes", Name: "pv0-pod0-node1-ns0", Namespace: ""}, - expect: false, + expect: authorizer.DecisionNoOpinion, }, } @@ -349,9 +349,9 @@ func BenchmarkAuthorization(b *testing.B) { for _, tc := range tests { b.Run(tc.name, func(b *testing.B) { for i := 0; i < b.N; i++ { - ok, _, _ := authz.Authorize(tc.attrs) - if ok != tc.expect { - b.Errorf("expected %v, got %v", tc.expect, ok) + decision, _, _ := authz.Authorize(tc.attrs) + if decision != tc.expect { + b.Errorf("expected %v, got %v", tc.expect, decision) } } }) @@ -410,7 +410,7 @@ func generate(opts sampleDataOpts) ([]*api.Pod, []*api.PersistentVolume) { for i := 0; i < opts.uniquePVCsPerPod; i++ { pv := &api.PersistentVolume{} pv.Name = fmt.Sprintf("pv%d-%s-%s", i, pod.Name, pod.Namespace) - pv.Spec.FlexVolume = &api.FlexVolumeSource{SecretRef: &api.LocalObjectReference{Name: fmt.Sprintf("secret-%s", pv.Name)}} + pv.Spec.FlexVolume = &api.FlexPersistentVolumeSource{SecretRef: &api.SecretReference{Name: fmt.Sprintf("secret-%s", pv.Name)}} pv.Spec.ClaimRef = &api.ObjectReference{Name: fmt.Sprintf("pvc%d-%s", i, pod.Name), Namespace: pod.Namespace} pvs = append(pvs, pv) @@ -421,7 +421,7 @@ func generate(opts sampleDataOpts) ([]*api.Pod, []*api.PersistentVolume) { for i := 0; i < opts.sharedPVCsPerPod; i++ { pv := &api.PersistentVolume{} pv.Name = fmt.Sprintf("pv%d-shared-%s", i, pod.Namespace) - pv.Spec.FlexVolume = &api.FlexVolumeSource{SecretRef: &api.LocalObjectReference{Name: fmt.Sprintf("secret-%s", pv.Name)}} + pv.Spec.FlexVolume = &api.FlexPersistentVolumeSource{SecretRef: &api.SecretReference{Name: fmt.Sprintf("secret-%s", pv.Name)}} pv.Spec.ClaimRef = &api.ObjectReference{Name: fmt.Sprintf("pvc%d-shared", i), Namespace: pod.Namespace} pvs = append(pvs, pv) diff --git a/plugin/pkg/auth/authorizer/rbac/BUILD b/plugin/pkg/auth/authorizer/rbac/BUILD index 73dff482900..bd96795f0e7 100644 --- a/plugin/pkg/auth/authorizer/rbac/BUILD +++ b/plugin/pkg/auth/authorizer/rbac/BUILD @@ -31,8 +31,8 @@ go_test( "rbac_test.go", "subject_locator_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac", - library = ":go_default_library", deps = [ "//pkg/apis/rbac:go_default_library", "//pkg/registry/rbac/validation:go_default_library", diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/BUILD b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/BUILD index bc346781cdf..87651f3ad36 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/BUILD +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/BUILD @@ -35,8 +35,9 @@ go_test( importpath = "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy_test", deps = [ ":go_default_library", - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", + "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/install:go_default_library", "//pkg/apis/rbac:go_default_library", "//pkg/apis/rbac/install:go_default_library", "//pkg/registry/rbac/validation:go_default_library", @@ -54,8 +55,8 @@ go_test( name = "go_default_test", srcs = ["controller_policy_test.go"], data = glob(["testdata/**"]), + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go index f81abc720c2..c94c0295334 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go @@ -58,14 +58,31 @@ func buildControllerRoles() ([]rbac.ClusterRole, []rbac.ClusterRoleBinding) { // controllerRoleBindings is a slice of roles used for controllers controllerRoleBindings := []rbac.ClusterRoleBinding{} + addControllerRole(&controllerRoles, &controllerRoleBindings, func() rbac.ClusterRole { + role := rbac.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "attachdetach-controller"}, + Rules: []rbac.PolicyRule{ + rbac.NewRule("list", "watch").Groups(legacyGroup).Resources("persistentvolumes", "persistentvolumeclaims").RuleOrDie(), + rbac.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("nodes").RuleOrDie(), + rbac.NewRule("patch", "update").Groups(legacyGroup).Resources("nodes/status").RuleOrDie(), + rbac.NewRule("list", "watch").Groups(legacyGroup).Resources("pods").RuleOrDie(), + eventsRule(), + }, + } + + if utilfeature.DefaultFeatureGate.Enabled(features.CSIPersistentVolume) { + role.Rules = append(role.Rules, rbac.NewRule("get", "create", "delete", "list", "watch").Groups(storageGroup).Resources("volumeattachments").RuleOrDie()) + } + + return role + }()) + addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{ - ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "attachdetach-controller"}, + ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "clusterrole-aggregation-controller"}, Rules: []rbac.PolicyRule{ - rbac.NewRule("list", "watch").Groups(legacyGroup).Resources("persistentvolumes", "persistentvolumeclaims").RuleOrDie(), - rbac.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("nodes").RuleOrDie(), - rbac.NewRule("patch", "update").Groups(legacyGroup).Resources("nodes/status").RuleOrDie(), - rbac.NewRule("list", "watch").Groups(legacyGroup).Resources("pods").RuleOrDie(), - eventsRule(), + // this controller must have full permissions to allow it to mutate any role in any way + rbac.NewRule("*").Groups("*").Resources("*").RuleOrDie(), + rbac.NewRule("*").URLs("*").RuleOrDie(), }, }) addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{ @@ -98,7 +115,7 @@ func buildControllerRoles() ([]rbac.ClusterRole, []rbac.ClusterRoleBinding) { rbac.NewRule("get", "list", "watch", "update").Groups(extensionsGroup, appsGroup).Resources("deployments").RuleOrDie(), rbac.NewRule("update").Groups(extensionsGroup, appsGroup).Resources("deployments/status").RuleOrDie(), rbac.NewRule("update").Groups(extensionsGroup, appsGroup).Resources("deployments/finalizers").RuleOrDie(), - rbac.NewRule("get", "list", "watch", "create", "update", "patch", "delete").Groups(extensionsGroup).Resources("replicasets").RuleOrDie(), + rbac.NewRule("get", "list", "watch", "create", "update", "patch", "delete").Groups(appsGroup, extensionsGroup).Resources("replicasets").RuleOrDie(), // TODO: remove "update" once // https://github.com/kubernetes/kubernetes/issues/36897 is resolved. rbac.NewRule("get", "list", "watch", "update").Groups(legacyGroup).Resources("pods").RuleOrDie(), @@ -109,7 +126,7 @@ func buildControllerRoles() ([]rbac.ClusterRole, []rbac.ClusterRoleBinding) { ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "disruption-controller"}, Rules: []rbac.PolicyRule{ rbac.NewRule("get", "list", "watch").Groups(extensionsGroup, appsGroup).Resources("deployments").RuleOrDie(), - rbac.NewRule("get", "list", "watch").Groups(extensionsGroup).Resources("replicasets").RuleOrDie(), + rbac.NewRule("get", "list", "watch").Groups(appsGroup, extensionsGroup).Resources("replicasets").RuleOrDie(), rbac.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("replicationcontrollers").RuleOrDie(), rbac.NewRule("get", "list", "watch").Groups(policyGroup).Resources("poddisruptionbudgets").RuleOrDie(), rbac.NewRule("get", "list", "watch").Groups(appsGroup).Resources("statefulsets").RuleOrDie(), @@ -156,16 +173,13 @@ func buildControllerRoles() ([]rbac.ClusterRole, []rbac.ClusterRoleBinding) { Rules: []rbac.PolicyRule{ rbac.NewRule("get", "list", "watch").Groups(autoscalingGroup).Resources("horizontalpodautoscalers").RuleOrDie(), rbac.NewRule("update").Groups(autoscalingGroup).Resources("horizontalpodautoscalers/status").RuleOrDie(), - rbac.NewRule("get", "update").Groups(legacyGroup).Resources("replicationcontrollers/scale").RuleOrDie(), - // TODO this should be removable when the HPA contoller is fixed - rbac.NewRule("get", "update").Groups(extensionsGroup).Resources("replicationcontrollers/scale").RuleOrDie(), - rbac.NewRule("get", "update").Groups(extensionsGroup, appsGroup).Resources("deployments/scale", "replicasets/scale").RuleOrDie(), + rbac.NewRule("get", "update").Groups("*").Resources("*/scale").RuleOrDie(), rbac.NewRule("list").Groups(legacyGroup).Resources("pods").RuleOrDie(), // TODO: restrict this to the appropriate namespace rbac.NewRule("get").Groups(legacyGroup).Resources("services/proxy").Names("https:heapster:", "http:heapster:").RuleOrDie(), // allow listing resource metrics and custom metrics rbac.NewRule("list").Groups(resMetricsGroup).Resources("pods").RuleOrDie(), - rbac.NewRule("list").Groups(customMetricsGroup).Resources("*").RuleOrDie(), + rbac.NewRule("get", "list").Groups(customMetricsGroup).Resources("*").RuleOrDie(), eventsRule(), }, }) @@ -230,9 +244,9 @@ func buildControllerRoles() ([]rbac.ClusterRole, []rbac.ClusterRoleBinding) { addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{ ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "replicaset-controller"}, Rules: []rbac.PolicyRule{ - rbac.NewRule("get", "list", "watch", "update").Groups(extensionsGroup).Resources("replicasets").RuleOrDie(), - rbac.NewRule("update").Groups(extensionsGroup).Resources("replicasets/status").RuleOrDie(), - rbac.NewRule("update").Groups(extensionsGroup).Resources("replicasets/finalizers").RuleOrDie(), + rbac.NewRule("get", "list", "watch", "update").Groups(appsGroup, extensionsGroup).Resources("replicasets").RuleOrDie(), + rbac.NewRule("update").Groups(appsGroup, extensionsGroup).Resources("replicasets/status").RuleOrDie(), + rbac.NewRule("update").Groups(appsGroup, extensionsGroup).Resources("replicasets/finalizers").RuleOrDie(), rbac.NewRule("list", "watch", "patch", "create", "delete").Groups(legacyGroup).Resources("pods").RuleOrDie(), eventsRule(), }, @@ -310,6 +324,16 @@ func buildControllerRoles() ([]rbac.ClusterRole, []rbac.ClusterRoleBinding) { eventsRule(), }, }) + if utilfeature.DefaultFeatureGate.Enabled(features.PVCProtection) { + addControllerRole(&controllerRoles, &controllerRoleBindings, rbac.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "pvc-protection-controller"}, + Rules: []rbac.PolicyRule{ + rbac.NewRule("get", "list", "watch", "update").Groups(legacyGroup).Resources("persistentvolumeclaims").RuleOrDie(), + rbac.NewRule("list", "watch", "get").Groups(legacyGroup).Resources("pods").RuleOrDie(), + eventsRule(), + }, + }) + } return controllerRoles, controllerRoleBindings } 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 e1c98019bcf..f1064f27084 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy_test.go +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy_test.go @@ -32,6 +32,7 @@ var rolesWithAllowStar = sets.NewString( saRolePrefix+"generic-garbage-collector", saRolePrefix+"resourcequota-controller", saRolePrefix+"horizontal-pod-autoscaler", + saRolePrefix+"clusterrole-aggregation-controller", ) // TestNoStarsForControllers confirms that no controller role has star verbs, groups, diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go index bea311c7ce9..cbbb3a33ae4 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go @@ -48,6 +48,7 @@ const ( storageGroup = "storage.k8s.io" resMetricsGroup = "metrics.k8s.io" customMetricsGroup = "custom.metrics.k8s.io" + networkingGroup = "networking.k8s.io" ) func addDefaultMetadata(obj runtime.Object) { @@ -91,7 +92,7 @@ func addClusterRoleBindingLabel(rolebindings []rbac.ClusterRoleBinding) { } func NodeRules() []rbac.PolicyRule { - return []rbac.PolicyRule{ + nodePolicyRules := []rbac.PolicyRule{ // Needed to check API access. These creates are non-mutating rbac.NewRule("create").Groups(authenticationGroup).Resources("tokenreviews").RuleOrDie(), rbac.NewRule("create").Groups(authorizationGroup).Resources("subjectaccessreviews", "localsubjectaccessreviews").RuleOrDie(), @@ -123,11 +124,12 @@ func NodeRules() []rbac.PolicyRule { // Needed for imagepullsecrets, rbd/ceph and secret volumes, and secrets in envs // Needed for configmap volume and envs - // Use the NodeRestriction admission plugin to limit a node to get secrets/configmaps referenced by pods bound to itself. + // Use the Node authorization mode to limit a node to get secrets/configmaps referenced by pods bound to itself. rbac.NewRule("get").Groups(legacyGroup).Resources("secrets", "configmaps").RuleOrDie(), // Needed for persistent volumes - // Use the NodeRestriction admission plugin to limit a node to get pv/pvc objects referenced by pods bound to itself. + // Use the Node authorization mode to limit a node to get pv/pvc objects referenced by pods bound to itself. rbac.NewRule("get").Groups(legacyGroup).Resources("persistentvolumeclaims", "persistentvolumes").RuleOrDie(), + // TODO: add to the Node authorizer and restrict to endpoints referenced by pods or PVs bound to the node // Needed for glusterfs volumes rbac.NewRule("get").Groups(legacyGroup).Resources("endpoints").RuleOrDie(), @@ -135,6 +137,20 @@ func NodeRules() []rbac.PolicyRule { // for it to be signed. This allows the kubelet to rotate it's own certificate. rbac.NewRule("create", "get", "list", "watch").Groups(certificatesGroup).Resources("certificatesigningrequests").RuleOrDie(), } + + if utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) { + // Use the Node authorization mode to limit a node to update status of pvc objects referenced by pods bound to itself. + // Use the NodeRestriction admission plugin to limit a node to just update the status stanza. + pvcStatusPolicyRule := rbac.NewRule("get", "update", "patch").Groups(legacyGroup).Resources("persistentvolumeclaims/status").RuleOrDie() + nodePolicyRules = append(nodePolicyRules, pvcStatusPolicyRule) + } + + // CSI + if utilfeature.DefaultFeatureGate.Enabled(features.CSIPersistentVolume) { + volAttachRule := rbac.NewRule("get").Groups(storageGroup).Resources("volumeattachments").RuleOrDie() + nodePolicyRules = append(nodePolicyRules, volAttachRule) + } + return nodePolicyRules } // ClusterRoles returns the cluster roles to bootstrap an API server with @@ -153,7 +169,7 @@ func ClusterRoles() []rbac.ClusterRole { ObjectMeta: metav1.ObjectMeta{Name: "system:discovery"}, Rules: []rbac.PolicyRule{ rbac.NewRule("get").URLs( - "/healthz", "/version", + "/healthz", "/version", "/version/", // remove once swagger 1.2 support is removed "/swaggerapi", "/swaggerapi/*", // do not expand this pattern for openapi discovery docs @@ -169,13 +185,37 @@ func ClusterRoles() []rbac.ClusterRole { ObjectMeta: metav1.ObjectMeta{Name: "system:basic-user"}, Rules: []rbac.PolicyRule{ // TODO add future selfsubjectrulesreview, project request APIs, project listing APIs - rbac.NewRule("create").Groups(authorizationGroup).Resources("selfsubjectaccessreviews").RuleOrDie(), + rbac.NewRule("create").Groups(authorizationGroup).Resources("selfsubjectaccessreviews", "selfsubjectrulesreviews").RuleOrDie(), }, }, { // a role for a namespace level admin. It is `edit` plus the power to grant permissions to other users. ObjectMeta: metav1.ObjectMeta{Name: "admin"}, + AggregationRule: &rbac.AggregationRule{ + ClusterRoleSelectors: []metav1.LabelSelector{{MatchLabels: map[string]string{"rbac.authorization.k8s.io/aggregate-to-admin": "true"}}}, + }, + }, + { + // a role for a namespace level editor. It grants access to all user level actions in a namespace. + // It does not grant powers for "privileged" resources which are domain of the system: `/status` + // subresources or `quota`/`limits` which are used to control namespaces + ObjectMeta: metav1.ObjectMeta{Name: "edit"}, + AggregationRule: &rbac.AggregationRule{ + ClusterRoleSelectors: []metav1.LabelSelector{{MatchLabels: map[string]string{"rbac.authorization.k8s.io/aggregate-to-edit": "true"}}}, + }, + }, + { + // a role for namespace level viewing. It grants Read-only access to non-escalating resources in + // a namespace. + ObjectMeta: metav1.ObjectMeta{Name: "view"}, + AggregationRule: &rbac.AggregationRule{ + ClusterRoleSelectors: []metav1.LabelSelector{{MatchLabels: map[string]string{"rbac.authorization.k8s.io/aggregate-to-view": "true"}}}, + }, + }, + { + // a role for a namespace level admin. It is `edit` plus the power to grant permissions to other users. + ObjectMeta: metav1.ObjectMeta{Name: "system:aggregate-to-admin", Labels: map[string]string{"rbac.authorization.k8s.io/aggregate-to-admin": "true"}}, Rules: []rbac.PolicyRule{ rbac.NewRule(ReadWrite...).Groups(legacyGroup).Resources("pods", "pods/attach", "pods/proxy", "pods/exec", "pods/portforward").RuleOrDie(), rbac.NewRule(ReadWrite...).Groups(legacyGroup).Resources("replicationcontrollers", "replicationcontrollers/scale", "serviceaccounts", @@ -188,7 +228,9 @@ func ClusterRoles() []rbac.ClusterRole { rbac.NewRule("impersonate").Groups(legacyGroup).Resources("serviceaccounts").RuleOrDie(), rbac.NewRule(ReadWrite...).Groups(appsGroup).Resources("statefulsets", - "deployments", "deployments/scale", "deployments/rollback").RuleOrDie(), + "daemonsets", + "deployments", "deployments/scale", "deployments/rollback", + "replicasets", "replicasets/scale").RuleOrDie(), rbac.NewRule(ReadWrite...).Groups(autoscalingGroup).Resources("horizontalpodautoscalers").RuleOrDie(), @@ -196,10 +238,13 @@ func ClusterRoles() []rbac.ClusterRole { rbac.NewRule(ReadWrite...).Groups(extensionsGroup).Resources("daemonsets", "deployments", "deployments/scale", "deployments/rollback", "ingresses", - "replicasets", "replicasets/scale", "replicationcontrollers/scale").RuleOrDie(), + "replicasets", "replicasets/scale", "replicationcontrollers/scale", + "networkpolicies").RuleOrDie(), rbac.NewRule(ReadWrite...).Groups(policyGroup).Resources("poddisruptionbudgets").RuleOrDie(), + rbac.NewRule(ReadWrite...).Groups(networkingGroup).Resources("networkpolicies").RuleOrDie(), + // additional admin powers rbac.NewRule("create").Groups(authorizationGroup).Resources("localsubjectaccessreviews").RuleOrDie(), rbac.NewRule(ReadWrite...).Groups(rbacGroup).Resources("roles", "rolebindings").RuleOrDie(), @@ -209,7 +254,7 @@ func ClusterRoles() []rbac.ClusterRole { // a role for a namespace level editor. It grants access to all user level actions in a namespace. // It does not grant powers for "privileged" resources which are domain of the system: `/status` // subresources or `quota`/`limits` which are used to control namespaces - ObjectMeta: metav1.ObjectMeta{Name: "edit"}, + ObjectMeta: metav1.ObjectMeta{Name: "system:aggregate-to-edit", Labels: map[string]string{"rbac.authorization.k8s.io/aggregate-to-edit": "true"}}, Rules: []rbac.PolicyRule{ rbac.NewRule(ReadWrite...).Groups(legacyGroup).Resources("pods", "pods/attach", "pods/proxy", "pods/exec", "pods/portforward").RuleOrDie(), rbac.NewRule(ReadWrite...).Groups(legacyGroup).Resources("replicationcontrollers", "replicationcontrollers/scale", "serviceaccounts", @@ -222,7 +267,9 @@ func ClusterRoles() []rbac.ClusterRole { rbac.NewRule("impersonate").Groups(legacyGroup).Resources("serviceaccounts").RuleOrDie(), rbac.NewRule(ReadWrite...).Groups(appsGroup).Resources("statefulsets", - "deployments", "deployments/scale", "deployments/rollback").RuleOrDie(), + "daemonsets", + "deployments", "deployments/scale", "deployments/rollback", + "replicasets", "replicasets/scale").RuleOrDie(), rbac.NewRule(ReadWrite...).Groups(autoscalingGroup).Resources("horizontalpodautoscalers").RuleOrDie(), @@ -230,15 +277,18 @@ func ClusterRoles() []rbac.ClusterRole { rbac.NewRule(ReadWrite...).Groups(extensionsGroup).Resources("daemonsets", "deployments", "deployments/scale", "deployments/rollback", "ingresses", - "replicasets", "replicasets/scale", "replicationcontrollers/scale").RuleOrDie(), + "replicasets", "replicasets/scale", "replicationcontrollers/scale", + "networkpolicies").RuleOrDie(), rbac.NewRule(ReadWrite...).Groups(policyGroup).Resources("poddisruptionbudgets").RuleOrDie(), + + rbac.NewRule(ReadWrite...).Groups(networkingGroup).Resources("networkpolicies").RuleOrDie(), }, }, { // a role for namespace level viewing. It grants Read-only access to non-escalating resources in // a namespace. - ObjectMeta: metav1.ObjectMeta{Name: "view"}, + ObjectMeta: metav1.ObjectMeta{Name: "system:aggregate-to-view", Labels: map[string]string{"rbac.authorization.k8s.io/aggregate-to-view": "true"}}, Rules: []rbac.PolicyRule{ rbac.NewRule(Read...).Groups(legacyGroup).Resources("pods", "replicationcontrollers", "replicationcontrollers/scale", "serviceaccounts", "services", "endpoints", "persistentvolumeclaims", "configmaps").RuleOrDie(), @@ -248,16 +298,22 @@ func ClusterRoles() []rbac.ClusterRole { // indicator of which namespaces you have access to. rbac.NewRule(Read...).Groups(legacyGroup).Resources("namespaces").RuleOrDie(), - rbac.NewRule(Read...).Groups(appsGroup).Resources("statefulsets", "deployments", "deployments/scale").RuleOrDie(), + rbac.NewRule(Read...).Groups(appsGroup).Resources("statefulsets", + "daemonsets", + "deployments", "deployments/scale", + "replicasets", "replicasets/scale").RuleOrDie(), rbac.NewRule(Read...).Groups(autoscalingGroup).Resources("horizontalpodautoscalers").RuleOrDie(), rbac.NewRule(Read...).Groups(batchGroup).Resources("jobs", "cronjobs").RuleOrDie(), rbac.NewRule(Read...).Groups(extensionsGroup).Resources("daemonsets", "deployments", "deployments/scale", - "ingresses", "replicasets", "replicasets/scale", "replicationcontrollers/scale").RuleOrDie(), + "ingresses", "replicasets", "replicasets/scale", "replicationcontrollers/scale", + "networkpolicies").RuleOrDie(), rbac.NewRule(Read...).Groups(policyGroup).Resources("poddisruptionbudgets").RuleOrDie(), + + rbac.NewRule(Read...).Groups(networkingGroup).Resources("networkpolicies").RuleOrDie(), }, }, { @@ -294,6 +350,17 @@ func ClusterRoles() []rbac.ClusterRole { eventsRule(), }, }, + { + // a role to use for full access to the kubelet API + ObjectMeta: metav1.ObjectMeta{Name: "system:kubelet-api-admin"}, + Rules: []rbac.PolicyRule{ + // Allow read-only access to the Node API objects + rbac.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("nodes").RuleOrDie(), + // Allow all API calls to the nodes + rbac.NewRule("proxy").Groups(legacyGroup).Resources("nodes").RuleOrDie(), + rbac.NewRule("*").Groups(legacyGroup).Resources("nodes/proxy", "nodes/metrics", "nodes/spec", "nodes/stats", "nodes/log").RuleOrDie(), + }, + }, { // a role to use for bootstrapping a node's client certificates ObjectMeta: metav1.ObjectMeta{Name: "system:node-bootstrapper"}, @@ -350,12 +417,13 @@ func ClusterRoles() []rbac.ClusterRole { rbac.NewRule(Read...).Groups(legacyGroup).Resources("nodes").RuleOrDie(), rbac.NewRule("get", "list", "watch", "delete").Groups(legacyGroup).Resources("pods").RuleOrDie(), rbac.NewRule("create").Groups(legacyGroup).Resources("pods/binding", "bindings").RuleOrDie(), - rbac.NewRule("update").Groups(legacyGroup).Resources("pods/status").RuleOrDie(), + rbac.NewRule("patch", "update").Groups(legacyGroup).Resources("pods/status").RuleOrDie(), // things that select pods rbac.NewRule(Read...).Groups(legacyGroup).Resources("services", "replicationcontrollers").RuleOrDie(), - rbac.NewRule(Read...).Groups(extensionsGroup).Resources("replicasets").RuleOrDie(), + rbac.NewRule(Read...).Groups(appsGroup, extensionsGroup).Resources("replicasets").RuleOrDie(), rbac.NewRule(Read...).Groups(appsGroup).Resources("statefulsets").RuleOrDie(), - // things that pods use + // things that pods use or applies to them + rbac.NewRule(Read...).Groups(policyGroup).Resources("poddisruptionbudgets").RuleOrDie(), rbac.NewRule(Read...).Groups(legacyGroup).Resources("persistentvolumeclaims", "persistentvolumes").RuleOrDie(), }, }, @@ -381,6 +449,13 @@ func ClusterRoles() []rbac.ClusterRole { eventsRule(), }, }, + { + ObjectMeta: metav1.ObjectMeta{Name: "system:aws-cloud-provider"}, + Rules: []rbac.PolicyRule{ + rbac.NewRule("get", "patch").Groups(legacyGroup).Resources("nodes").RuleOrDie(), + eventsRule(), + }, + }, { // a role making the csrapprover controller approve a node client CSR ObjectMeta: metav1.ObjectMeta{Name: "system:certificates.k8s.io:certificatesigningrequests:nodeclient"}, @@ -407,6 +482,18 @@ func ClusterRoles() []rbac.ClusterRole { }) } + if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) { + // Find the scheduler role + for i, role := range roles { + if role.Name == "system:kube-scheduler" { + pvRule := rbac.NewRule("update").Groups(legacyGroup).Resources("persistentvolumes").RuleOrDie() + scRule := rbac.NewRule(Read...).Groups(storageGroup).Resources("storageclasses").RuleOrDie() + roles[i].Rules = append(role.Rules, pvRule, scRule) + break + } + } + } + addClusterRoleLabel(roles) return roles } @@ -423,6 +510,7 @@ func ClusterRoleBindings() []rbac.ClusterRoleBinding { rbac.NewClusterBinding("system:kube-controller-manager").Users(user.KubeControllerManager).BindingOrDie(), rbac.NewClusterBinding("system:kube-dns").SAs("kube-system", "kube-dns").BindingOrDie(), rbac.NewClusterBinding("system:kube-scheduler").Users(user.KubeScheduler).BindingOrDie(), + rbac.NewClusterBinding("system:aws-cloud-provider").SAs("kube-system", "aws-cloud-provider").BindingOrDie(), // This default binding of the system:node role to the system:nodes group is deprecated in 1.7 with the availability of the Node authorizer. // This leaves the binding, but with an empty set of subjects, so that tightening reconciliation can remove the subject. @@ -436,3 +524,11 @@ func ClusterRoleBindings() []rbac.ClusterRoleBinding { return rolebindings } + +func ClusterRolesToAggregate() map[string]string { + return map[string]string{ + "admin": "system:aggregate-to-admin", + "edit": "system:aggregate-to-edit", + "view": "system:aggregate-to-view", + } +} diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy_test.go b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy_test.go index 0435a23abc9..830d92576f9 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy_test.go +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy_test.go @@ -31,8 +31,9 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/pkg/api" - _ "k8s.io/kubernetes/pkg/api/install" + "k8s.io/kubernetes/pkg/api/legacyscheme" + api "k8s.io/kubernetes/pkg/apis/core" + _ "k8s.io/kubernetes/pkg/apis/core/install" "k8s.io/kubernetes/pkg/apis/rbac" _ "k8s.io/kubernetes/pkg/apis/rbac/install" rbacregistryvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation" @@ -52,11 +53,11 @@ func getSemanticRoles(roles []rbac.ClusterRole) semanticRoles { for i := range roles { role := roles[i] switch role.Name { - case "admin": + case "system:aggregate-to-admin": ret.admin = &role - case "edit": + case "system:aggregate-to-edit": ret.edit = &role - case "view": + case "system:aggregate-to-view": ret.view = &role } } @@ -280,11 +281,11 @@ func testObjects(t *testing.T, list *api.List, fixtureFilename string) { t.Fatal(err) } - if err := runtime.EncodeList(api.Codecs.LegacyCodec(v1.SchemeGroupVersion, rbacv1.SchemeGroupVersion), list.Items); err != nil { + if err := runtime.EncodeList(legacyscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion, rbacv1.SchemeGroupVersion), list.Items); err != nil { t.Fatal(err) } - jsonData, err := runtime.Encode(api.Codecs.LegacyCodec(v1.SchemeGroupVersion, rbacv1.SchemeGroupVersion), list) + jsonData, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion, rbacv1.SchemeGroupVersion), list) if err != nil { t.Fatal(err) } @@ -318,8 +319,9 @@ func TestClusterRoleLabel(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %v", err) } - if got, want := accessor.GetLabels(), map[string]string{"kubernetes.io/bootstrapping": "rbac-defaults"}; !reflect.DeepEqual(got, want) { - t.Errorf("ClusterRole: %s GetLabels() = %s, want %s", accessor.GetName(), got, want) + + if accessor.GetLabels()["kubernetes.io/bootstrapping"] != "rbac-defaults" { + t.Errorf("ClusterRole: %s GetLabels() = %s, want %s", accessor.GetName(), accessor.GetLabels(), map[string]string{"kubernetes.io/bootstrapping": "rbac-defaults"}) } } diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-role-bindings.yaml b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-role-bindings.yaml index 1dbc33dd449..dc25b090ebe 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-role-bindings.yaml +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-role-bindings.yaml @@ -17,6 +17,23 @@ items: - apiGroup: rbac.authorization.k8s.io kind: Group name: system:masters +- apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: null + labels: + kubernetes.io/bootstrapping: rbac-defaults + name: system:aws-cloud-provider + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:aws-cloud-provider + subjects: + - kind: ServiceAccount + name: aws-cloud-provider + namespace: kube-system - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding 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 4c6bf1e71a0..208a4cddb55 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-roles.yaml +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-roles.yaml @@ -1,6 +1,10 @@ apiVersion: v1 items: -- apiVersion: rbac.authorization.k8s.io/v1 +- aggregationRule: + clusterRoleSelectors: + - matchLabels: + rbac.authorization.k8s.io/aggregate-to-admin: "true" + apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: annotations: @@ -9,6 +13,51 @@ items: labels: kubernetes.io/bootstrapping: rbac-defaults name: admin + rules: null +- apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: null + labels: + kubernetes.io/bootstrapping: rbac-defaults + name: cluster-admin + rules: + - apiGroups: + - '*' + resources: + - '*' + verbs: + - '*' + - nonResourceURLs: + - '*' + verbs: + - '*' +- aggregationRule: + clusterRoleSelectors: + - matchLabels: + rbac.authorization.k8s.io/aggregate-to-edit: "true" + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: null + labels: + kubernetes.io/bootstrapping: rbac-defaults + name: edit + rules: null +- apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: null + labels: + kubernetes.io/bootstrapping: rbac-defaults + rbac.authorization.k8s.io/aggregate-to-admin: "true" + name: system:aggregate-to-admin rules: - apiGroups: - "" @@ -81,9 +130,12 @@ items: - apiGroups: - apps resources: + - daemonsets - deployments - deployments/rollback - deployments/scale + - replicasets + - replicasets/scale - statefulsets verbs: - create @@ -129,6 +181,7 @@ items: - deployments/rollback - deployments/scale - ingresses + - networkpolicies - replicasets - replicasets/scale - replicationcontrollers/scale @@ -154,6 +207,19 @@ items: - patch - update - watch + - apiGroups: + - networking.k8s.io + resources: + - networkpolicies + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch - apiGroups: - authorization.k8s.io resources: @@ -182,27 +248,8 @@ items: creationTimestamp: null labels: kubernetes.io/bootstrapping: rbac-defaults - name: cluster-admin - rules: - - apiGroups: - - '*' - resources: - - '*' - verbs: - - '*' - - nonResourceURLs: - - '*' - verbs: - - '*' -- apiVersion: rbac.authorization.k8s.io/v1 - kind: ClusterRole - metadata: - annotations: - rbac.authorization.kubernetes.io/autoupdate: "true" - creationTimestamp: null - labels: - kubernetes.io/bootstrapping: rbac-defaults - name: edit + rbac.authorization.k8s.io/aggregate-to-edit: "true" + name: system:aggregate-to-edit rules: - apiGroups: - "" @@ -275,9 +322,12 @@ items: - apiGroups: - apps resources: + - daemonsets - deployments - deployments/rollback - deployments/scale + - replicasets + - replicasets/scale - statefulsets verbs: - create @@ -323,6 +373,7 @@ items: - deployments/rollback - deployments/scale - ingresses + - networkpolicies - replicasets - replicasets/scale - replicationcontrollers/scale @@ -348,6 +399,130 @@ items: - patch - update - watch + - apiGroups: + - networking.k8s.io + resources: + - networkpolicies + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: null + labels: + kubernetes.io/bootstrapping: rbac-defaults + rbac.authorization.k8s.io/aggregate-to-view: "true" + name: system:aggregate-to-view + rules: + - apiGroups: + - "" + resources: + - configmaps + - endpoints + - persistentvolumeclaims + - pods + - replicationcontrollers + - replicationcontrollers/scale + - serviceaccounts + - services + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - bindings + - events + - limitranges + - namespaces/status + - pods/log + - pods/status + - replicationcontrollers/status + - resourcequotas + - resourcequotas/status + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - list + - watch + - apiGroups: + - apps + resources: + - daemonsets + - deployments + - deployments/scale + - replicasets + - replicasets/scale + - statefulsets + verbs: + - get + - list + - watch + - apiGroups: + - autoscaling + resources: + - horizontalpodautoscalers + verbs: + - get + - list + - watch + - apiGroups: + - batch + resources: + - cronjobs + - jobs + verbs: + - get + - list + - watch + - apiGroups: + - extensions + resources: + - daemonsets + - deployments + - deployments/scale + - ingresses + - networkpolicies + - replicasets + - replicasets/scale + - replicationcontrollers/scale + verbs: + - get + - list + - watch + - apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - get + - list + - watch + - apiGroups: + - networking.k8s.io + resources: + - networkpolicies + verbs: + - get + - list + - watch - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -370,6 +545,31 @@ items: - subjectaccessreviews verbs: - create +- apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: null + labels: + kubernetes.io/bootstrapping: rbac-defaults + name: system:aws-cloud-provider + rules: + - apiGroups: + - "" + resources: + - nodes + verbs: + - get + - patch + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - update - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -384,6 +584,7 @@ items: - authorization.k8s.io resources: - selfsubjectaccessreviews + - selfsubjectrulesreviews verbs: - create - apiVersion: rbac.authorization.k8s.io/v1 @@ -439,6 +640,7 @@ items: - /swaggerapi - /swaggerapi/* - /version + - /version/ verbs: - get - apiVersion: rbac.authorization.k8s.io/v1 @@ -633,6 +835,7 @@ items: resources: - pods/status verbs: + - patch - update - apiGroups: - "" @@ -644,6 +847,7 @@ items: - list - watch - apiGroups: + - apps - extensions resources: - replicasets @@ -659,6 +863,14 @@ items: - get - list - watch + - apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - get + - list + - watch - apiGroups: - "" resources: @@ -668,6 +880,40 @@ items: - get - list - watch +- apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: null + labels: + kubernetes.io/bootstrapping: rbac-defaults + name: system:kubelet-api-admin + rules: + - apiGroups: + - "" + resources: + - nodes + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - nodes + verbs: + - proxy + - apiGroups: + - "" + resources: + - nodes/log + - nodes/metrics + - nodes/proxy + - nodes/spec + - nodes/stats + verbs: + - '*' - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -919,7 +1165,11 @@ items: - create - patch - update -- apiVersion: rbac.authorization.k8s.io/v1 +- aggregationRule: + clusterRoleSelectors: + - matchLabels: + rbac.authorization.k8s.io/aggregate-to-view: "true" + apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: annotations: @@ -928,94 +1178,6 @@ items: labels: kubernetes.io/bootstrapping: rbac-defaults name: view - rules: - - apiGroups: - - "" - resources: - - configmaps - - endpoints - - persistentvolumeclaims - - pods - - replicationcontrollers - - replicationcontrollers/scale - - serviceaccounts - - services - verbs: - - get - - list - - watch - - apiGroups: - - "" - resources: - - bindings - - events - - limitranges - - namespaces/status - - pods/log - - pods/status - - replicationcontrollers/status - - resourcequotas - - resourcequotas/status - verbs: - - get - - list - - watch - - apiGroups: - - "" - resources: - - namespaces - verbs: - - get - - list - - watch - - apiGroups: - - apps - resources: - - deployments - - deployments/scale - - statefulsets - verbs: - - get - - list - - watch - - apiGroups: - - autoscaling - resources: - - horizontalpodautoscalers - verbs: - - get - - list - - watch - - apiGroups: - - batch - resources: - - cronjobs - - jobs - verbs: - - get - - list - - watch - - apiGroups: - - extensions - resources: - - daemonsets - - deployments - - deployments/scale - - ingresses - - replicasets - - replicasets/scale - - replicationcontrollers/scale - verbs: - - get - - list - - watch - - apiGroups: - - policy - resources: - - poddisruptionbudgets - verbs: - - get - - list - - watch + rules: null kind: List metadata: {} 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 ddb59dbcefd..ff5d47867bb 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 @@ -34,6 +34,23 @@ items: - kind: ServiceAccount name: certificate-controller namespace: kube-system +- apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: null + labels: + kubernetes.io/bootstrapping: rbac-defaults + name: system:controller:clusterrole-aggregation-controller + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:controller:clusterrole-aggregation-controller + subjects: + - kind: ServiceAccount + name: clusterrole-aggregation-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 bc24fa3cf25..0af7efbfa3e 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-roles.yaml +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-roles.yaml @@ -87,6 +87,26 @@ items: - create - patch - update +- apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: null + labels: + kubernetes.io/bootstrapping: rbac-defaults + name: system:controller:clusterrole-aggregation-controller + rules: + - apiGroups: + - '*' + resources: + - '*' + verbs: + - '*' + - nonResourceURLs: + - '*' + verbs: + - '*' - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -255,6 +275,7 @@ items: verbs: - update - apiGroups: + - apps - extensions resources: - replicasets @@ -303,6 +324,7 @@ items: - list - watch - apiGroups: + - apps - extensions resources: - replicasets @@ -445,25 +467,9 @@ items: verbs: - update - apiGroups: - - "" + - '*' resources: - - replicationcontrollers/scale - verbs: - - get - - update - - apiGroups: - - extensions - resources: - - replicationcontrollers/scale - verbs: - - get - - update - - apiGroups: - - apps - - extensions - resources: - - deployments/scale - - replicasets/scale + - '*/scale' verbs: - get - update @@ -493,6 +499,7 @@ items: resources: - '*' verbs: + - get - list - apiGroups: - "" @@ -765,6 +772,7 @@ items: name: system:controller:replicaset-controller rules: - apiGroups: + - apps - extensions resources: - replicasets @@ -774,12 +782,14 @@ items: - update - watch - apiGroups: + - apps - extensions resources: - replicasets/status verbs: - update - apiGroups: + - apps - extensions resources: - replicasets/finalizers diff --git a/plugin/pkg/auth/authorizer/rbac/rbac.go b/plugin/pkg/auth/authorizer/rbac/rbac.go index 68ef7b2567f..1f507eb0ed2 100644 --- a/plugin/pkg/auth/authorizer/rbac/rbac.go +++ b/plugin/pkg/auth/authorizer/rbac/rbac.go @@ -69,17 +69,17 @@ func (v *authorizingVisitor) visit(rule *rbac.PolicyRule, err error) bool { return true } -func (r *RBACAuthorizer) Authorize(requestAttributes authorizer.Attributes) (bool, string, error) { +func (r *RBACAuthorizer) Authorize(requestAttributes authorizer.Attributes) (authorizer.Decision, string, error) { ruleCheckingVisitor := &authorizingVisitor{requestAttributes: requestAttributes} r.authorizationRuleResolver.VisitRulesFor(requestAttributes.GetUser(), requestAttributes.GetNamespace(), ruleCheckingVisitor.visit) if ruleCheckingVisitor.allowed { - return true, "", nil + return authorizer.DecisionAllow, "", nil } // Build a detailed log of the denial. // Make the whole block conditional so we don't do a lot of string-building we won't use. - if glog.V(2) { + if glog.V(5) { var operation string if requestAttributes.IsResourceRequest() { b := &bytes.Buffer{} @@ -120,7 +120,7 @@ func (r *RBACAuthorizer) Authorize(requestAttributes authorizer.Attributes) (boo if len(ruleCheckingVisitor.errors) > 0 { reason = fmt.Sprintf("%v", utilerrors.NewAggregate(ruleCheckingVisitor.errors)) } - return false, reason, nil + return authorizer.DecisionNoOpinion, reason, nil } func (r *RBACAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { @@ -174,14 +174,14 @@ func RulesAllow(requestAttributes authorizer.Attributes, rules ...rbac.PolicyRul func RuleAllows(requestAttributes authorizer.Attributes, rule *rbac.PolicyRule) bool { if requestAttributes.IsResourceRequest() { - resource := requestAttributes.GetResource() + combinedResource := requestAttributes.GetResource() if len(requestAttributes.GetSubresource()) > 0 { - resource = requestAttributes.GetResource() + "/" + requestAttributes.GetSubresource() + combinedResource = requestAttributes.GetResource() + "/" + requestAttributes.GetSubresource() } return rbac.VerbMatches(rule, requestAttributes.GetVerb()) && rbac.APIGroupMatches(rule, requestAttributes.GetAPIGroup()) && - rbac.ResourceMatches(rule, resource) && + rbac.ResourceMatches(rule, combinedResource, requestAttributes.GetSubresource()) && rbac.ResourceNameMatches(rule, requestAttributes.GetName()) } diff --git a/plugin/pkg/auth/authorizer/rbac/rbac_test.go b/plugin/pkg/auth/authorizer/rbac/rbac_test.go index 16a21241ce8..d4425a60476 100644 --- a/plugin/pkg/auth/authorizer/rbac/rbac_test.go +++ b/plugin/pkg/auth/authorizer/rbac/rbac_test.go @@ -224,13 +224,19 @@ func TestAuthorizer(t *testing.T) { { // test subresource resolution clusterRoles: []*rbac.ClusterRole{ - newClusterRole("admin", newRule("*", "*", "pods/status", "*")), + newClusterRole("admin", + newRule("*", "*", "pods/status", "*"), + newRule("*", "*", "*/scale", "*"), + ), }, roleBindings: []*rbac.RoleBinding{ newRoleBinding("ns1", "admin", bindToClusterRole, "User:admin", "Group:admins"), }, shouldPass: []authorizer.Attributes{ &defaultAttributes{"admin", "", "get", "pods", "status", "ns1", ""}, + &defaultAttributes{"admin", "", "get", "pods", "scale", "ns1", ""}, + &defaultAttributes{"admin", "", "get", "deployments", "scale", "ns1", ""}, + &defaultAttributes{"admin", "", "get", "anything", "scale", "ns1", ""}, }, shouldFail: []authorizer.Attributes{ &defaultAttributes{"admin", "", "get", "pods", "", "ns1", ""}, @@ -241,13 +247,13 @@ func TestAuthorizer(t *testing.T) { ruleResolver, _ := rbacregistryvalidation.NewTestRuleResolver(tt.roles, tt.roleBindings, tt.clusterRoles, tt.clusterRoleBindings) a := RBACAuthorizer{ruleResolver} for _, attr := range tt.shouldPass { - if authorized, _, _ := a.Authorize(attr); !authorized { + if decision, _, _ := a.Authorize(attr); decision != authorizer.DecisionAllow { t.Errorf("case %d: incorrectly restricted %s", i, attr) } } for _, attr := range tt.shouldFail { - if authorized, _, _ := a.Authorize(attr); authorized { + if decision, _, _ := a.Authorize(attr); decision == authorizer.DecisionAllow { t.Errorf("case %d: incorrectly passed %s", i, attr) } } diff --git a/plugin/pkg/scheduler/BUILD b/plugin/pkg/scheduler/BUILD deleted file mode 100644 index 0270aeec5cc..00000000000 --- a/plugin/pkg/scheduler/BUILD +++ /dev/null @@ -1,83 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = ["scheduler_test.go"], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler", - library = ":go_default_library", - deps = [ - "//pkg/api:go_default_library", - "//plugin/pkg/scheduler/algorithm:go_default_library", - "//plugin/pkg/scheduler/algorithm/predicates:go_default_library", - "//plugin/pkg/scheduler/core:go_default_library", - "//plugin/pkg/scheduler/schedulercache:go_default_library", - "//plugin/pkg/scheduler/testing:go_default_library", - "//plugin/pkg/scheduler/util:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/client-go/tools/cache:go_default_library", - "//vendor/k8s.io/client-go/tools/record:go_default_library", - ], -) - -go_library( - name = "go_default_library", - srcs = [ - "scheduler.go", - "testutil.go", - ], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler", - deps = [ - "//pkg/features:go_default_library", - "//plugin/pkg/scheduler/algorithm:go_default_library", - "//plugin/pkg/scheduler/api:go_default_library", - "//plugin/pkg/scheduler/core:go_default_library", - "//plugin/pkg/scheduler/metrics:go_default_library", - "//plugin/pkg/scheduler/schedulercache:go_default_library", - "//plugin/pkg/scheduler/util:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/listers/core/v1:go_default_library", - "//vendor/k8s.io/client-go/tools/cache:go_default_library", - "//vendor/k8s.io/client-go/tools/record:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//plugin/pkg/scheduler/algorithm:all-srcs", - "//plugin/pkg/scheduler/algorithmprovider:all-srcs", - "//plugin/pkg/scheduler/api:all-srcs", - "//plugin/pkg/scheduler/core:all-srcs", - "//plugin/pkg/scheduler/factory:all-srcs", - "//plugin/pkg/scheduler/metrics:all-srcs", - "//plugin/pkg/scheduler/schedulercache:all-srcs", - "//plugin/pkg/scheduler/testing:all-srcs", - "//plugin/pkg/scheduler/util:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/plugin/pkg/scheduler/algorithm/BUILD b/plugin/pkg/scheduler/algorithm/BUILD deleted file mode 100644 index 58a4684e198..00000000000 --- a/plugin/pkg/scheduler/algorithm/BUILD +++ /dev/null @@ -1,58 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = [ - "doc.go", - "scheduler_interface.go", - "types.go", - "well_known_labels.go", - ], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm", - deps = [ - "//plugin/pkg/scheduler/api:go_default_library", - "//plugin/pkg/scheduler/schedulercache:go_default_library", - "//vendor/k8s.io/api/apps/v1beta1:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = [ - "scheduler_interface_test.go", - "types_test.go", - ], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm", - library = ":go_default_library", - deps = [ - "//plugin/pkg/scheduler/schedulercache:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//plugin/pkg/scheduler/algorithm/predicates:all-srcs", - "//plugin/pkg/scheduler/algorithm/priorities:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/plugin/pkg/scheduler/algorithm/doc.go b/plugin/pkg/scheduler/algorithm/doc.go deleted file mode 100644 index 299051b0f8a..00000000000 --- a/plugin/pkg/scheduler/algorithm/doc.go +++ /dev/null @@ -1,19 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES 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 contains a generic Scheduler interface and several -// implementations. -package algorithm // import "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" diff --git a/plugin/pkg/scheduler/algorithm/predicates/BUILD b/plugin/pkg/scheduler/algorithm/predicates/BUILD deleted file mode 100644 index 182896f4cb4..00000000000 --- a/plugin/pkg/scheduler/algorithm/predicates/BUILD +++ /dev/null @@ -1,75 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = [ - "error.go", - "metadata.go", - "predicates.go", - "utils.go", - ], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates", - deps = [ - "//pkg/api/v1/helper:go_default_library", - "//pkg/api/v1/helper/qos:go_default_library", - "//pkg/features:go_default_library", - "//pkg/kubelet/apis:go_default_library", - "//pkg/volume/util:go_default_library", - "//plugin/pkg/scheduler/algorithm:go_default_library", - "//plugin/pkg/scheduler/algorithm/priorities/util:go_default_library", - "//plugin/pkg/scheduler/schedulercache:go_default_library", - "//plugin/pkg/scheduler/util:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/rand:go_default_library", - "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", - "//vendor/k8s.io/client-go/listers/core/v1:go_default_library", - "//vendor/k8s.io/client-go/util/workqueue:go_default_library", - "//vendor/k8s.io/metrics/pkg/client/clientset_generated/clientset:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = [ - "metadata_test.go", - "predicates_test.go", - "utils_test.go", - ], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates", - library = ":go_default_library", - deps = [ - "//pkg/api/v1/helper:go_default_library", - "//pkg/kubelet/apis:go_default_library", - "//plugin/pkg/scheduler/algorithm:go_default_library", - "//plugin/pkg/scheduler/schedulercache:go_default_library", - "//plugin/pkg/scheduler/testing:go_default_library", - "//plugin/pkg/scheduler/util:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/plugin/pkg/scheduler/algorithm/predicates/error.go b/plugin/pkg/scheduler/algorithm/predicates/error.go deleted file mode 100644 index b4cbc0bb59d..00000000000 --- a/plugin/pkg/scheduler/algorithm/predicates/error.go +++ /dev/null @@ -1,117 +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 predicates - -import ( - "fmt" - - "k8s.io/api/core/v1" -) - -var ( - // The predicateName tries to be consistent as the predicate name used in DefaultAlgorithmProvider defined in - // defaults.go (which tend to be stable for backward compatibility) - - // NOTE: If you add a new predicate failure error for a predicate that can never - // be made to pass by removing pods, or you change an existing predicate so that - // it can never be made to pass by removing pods, you need to add the predicate - // failure error in nodesWherePreemptionMightHelp() in scheduler/core/generic_scheduler.go - ErrDiskConflict = newPredicateFailureError("NoDiskConflict") - ErrVolumeZoneConflict = newPredicateFailureError("NoVolumeZoneConflict") - ErrNodeSelectorNotMatch = newPredicateFailureError("MatchNodeSelector") - ErrPodAffinityNotMatch = newPredicateFailureError("MatchInterPodAffinity") - ErrPodAffinityRulesNotMatch = newPredicateFailureError("PodAffinityRulesNotMatch") - ErrPodAntiAffinityRulesNotMatch = newPredicateFailureError("PodAntiAffinityRulesNotMatch") - ErrExistingPodsAntiAffinityRulesNotMatch = newPredicateFailureError("ExistingPodsAntiAffinityRulesNotMatch") - ErrTaintsTolerationsNotMatch = newPredicateFailureError("PodToleratesNodeTaints") - ErrPodNotMatchHostName = newPredicateFailureError("HostName") - ErrPodNotFitsHostPorts = newPredicateFailureError("PodFitsHostPorts") - ErrNodeLabelPresenceViolated = newPredicateFailureError("CheckNodeLabelPresence") - ErrServiceAffinityViolated = newPredicateFailureError("CheckServiceAffinity") - ErrMaxVolumeCountExceeded = newPredicateFailureError("MaxVolumeCount") - ErrNodeUnderMemoryPressure = newPredicateFailureError("NodeUnderMemoryPressure") - ErrNodeUnderDiskPressure = newPredicateFailureError("NodeUnderDiskPressure") - ErrNodeOutOfDisk = newPredicateFailureError("NodeOutOfDisk") - ErrNodeNotReady = newPredicateFailureError("NodeNotReady") - ErrNodeNetworkUnavailable = newPredicateFailureError("NodeNetworkUnavailable") - ErrNodeUnschedulable = newPredicateFailureError("NodeUnschedulable") - ErrNodeUnknownCondition = newPredicateFailureError("NodeUnknownCondition") - ErrVolumeNodeConflict = newPredicateFailureError("NoVolumeNodeConflict") - // ErrFakePredicate is used for test only. The fake predicates returning false also returns error - // as ErrFakePredicate. - ErrFakePredicate = newPredicateFailureError("FakePredicateError") -) - -// InsufficientResourceError is an error type that indicates what kind of resource limit is -// hit and caused the unfitting failure. -type InsufficientResourceError struct { - // resourceName is the name of the resource that is insufficient - ResourceName v1.ResourceName - requested int64 - used int64 - capacity int64 -} - -func NewInsufficientResourceError(resourceName v1.ResourceName, requested, used, capacity int64) *InsufficientResourceError { - return &InsufficientResourceError{ - ResourceName: resourceName, - requested: requested, - used: used, - capacity: capacity, - } -} - -func (e *InsufficientResourceError) Error() string { - return fmt.Sprintf("Node didn't have enough resource: %s, requested: %d, used: %d, capacity: %d", - e.ResourceName, e.requested, e.used, e.capacity) -} - -func (e *InsufficientResourceError) GetReason() string { - return fmt.Sprintf("Insufficient %v", e.ResourceName) -} - -func (e *InsufficientResourceError) GetInsufficientAmount() int64 { - return e.requested - (e.capacity - e.used) -} - -type PredicateFailureError struct { - PredicateName string -} - -func newPredicateFailureError(predicateName string) *PredicateFailureError { - return &PredicateFailureError{PredicateName: predicateName} -} - -func (e *PredicateFailureError) Error() string { - return fmt.Sprintf("Predicate %s failed", e.PredicateName) -} - -func (e *PredicateFailureError) GetReason() string { - return e.PredicateName -} - -type FailureReason struct { - reason string -} - -func NewFailureReason(msg string) *FailureReason { - return &FailureReason{reason: msg} -} - -func (e *FailureReason) GetReason() string { - return e.reason -} diff --git a/plugin/pkg/scheduler/algorithm/predicates/metadata.go b/plugin/pkg/scheduler/algorithm/predicates/metadata.go deleted file mode 100644 index bb15272db3d..00000000000 --- a/plugin/pkg/scheduler/algorithm/predicates/metadata.go +++ /dev/null @@ -1,188 +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 predicates - -import ( - "fmt" - "sync" - - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" - schedutil "k8s.io/kubernetes/plugin/pkg/scheduler/util" - - "github.com/golang/glog" -) - -type PredicateMetadataFactory struct { - podLister algorithm.PodLister -} - -// Note that predicateMetadata and matchingPodAntiAffinityTerm need to be declared in the same file -// due to the way declarations are processed in predicate declaration unit tests. -type matchingPodAntiAffinityTerm struct { - term *v1.PodAffinityTerm - node *v1.Node -} - -// NOTE: When new fields are added/removed or logic is changed, please make sure that -// RemovePod, AddPod, and ShallowCopy functions are updated to work with the new changes. -type predicateMetadata struct { - pod *v1.Pod - podBestEffort bool - podRequest *schedulercache.Resource - podPorts map[int]bool - //key is a pod full name with the anti-affinity rules. - matchingAntiAffinityTerms map[string][]matchingPodAntiAffinityTerm - serviceAffinityInUse bool - serviceAffinityMatchingPodList []*v1.Pod - serviceAffinityMatchingPodServices []*v1.Service -} - -// Ensure that predicateMetadata implements algorithm.PredicateMetadata. -var _ algorithm.PredicateMetadata = &predicateMetadata{} - -// PredicateMetadataProducer: Helper types/variables... -type PredicateMetadataProducer func(pm *predicateMetadata) - -var predicateMetaProducerRegisterLock sync.Mutex -var predicateMetadataProducers map[string]PredicateMetadataProducer = make(map[string]PredicateMetadataProducer) - -func RegisterPredicateMetadataProducer(predicateName string, precomp PredicateMetadataProducer) { - predicateMetaProducerRegisterLock.Lock() - defer predicateMetaProducerRegisterLock.Unlock() - predicateMetadataProducers[predicateName] = precomp -} - -func NewPredicateMetadataFactory(podLister algorithm.PodLister) algorithm.PredicateMetadataProducer { - factory := &PredicateMetadataFactory{ - podLister, - } - return factory.GetMetadata -} - -// GetMetadata returns the predicateMetadata used which will be used by various predicates. -func (pfactory *PredicateMetadataFactory) GetMetadata(pod *v1.Pod, nodeNameToInfoMap map[string]*schedulercache.NodeInfo) algorithm.PredicateMetadata { - // If we cannot compute metadata, just return nil - if pod == nil { - return nil - } - matchingTerms, err := getMatchingAntiAffinityTerms(pod, nodeNameToInfoMap) - if err != nil { - return nil - } - predicateMetadata := &predicateMetadata{ - pod: pod, - podBestEffort: isPodBestEffort(pod), - podRequest: GetResourceRequest(pod), - podPorts: schedutil.GetUsedPorts(pod), - matchingAntiAffinityTerms: matchingTerms, - } - for predicateName, precomputeFunc := range predicateMetadataProducers { - glog.V(10).Infof("Precompute: %v", predicateName) - precomputeFunc(predicateMetadata) - } - return predicateMetadata -} - -// RemovePod changes predicateMetadata assuming that the given `deletedPod` is -// deleted from the system. -func (meta *predicateMetadata) RemovePod(deletedPod *v1.Pod) error { - deletedPodFullName := schedutil.GetPodFullName(deletedPod) - if deletedPodFullName == schedutil.GetPodFullName(meta.pod) { - return fmt.Errorf("deletedPod and meta.pod must not be the same.") - } - // Delete any anti-affinity rule from the deletedPod. - delete(meta.matchingAntiAffinityTerms, deletedPodFullName) - // All pods in the serviceAffinityMatchingPodList are in the same namespace. - // So, if the namespace of the first one is not the same as the namespace of the - // deletedPod, we don't need to check the list, as deletedPod isn't in the list. - if meta.serviceAffinityInUse && - len(meta.serviceAffinityMatchingPodList) > 0 && - deletedPod.Namespace == meta.serviceAffinityMatchingPodList[0].Namespace { - for i, pod := range meta.serviceAffinityMatchingPodList { - if schedutil.GetPodFullName(pod) == deletedPodFullName { - meta.serviceAffinityMatchingPodList = append( - meta.serviceAffinityMatchingPodList[:i], - meta.serviceAffinityMatchingPodList[i+1:]...) - break - } - } - } - return nil -} - -// AddPod changes predicateMetadata assuming that `newPod` is added to the -// system. -func (meta *predicateMetadata) AddPod(addedPod *v1.Pod, nodeInfo *schedulercache.NodeInfo) error { - addedPodFullName := schedutil.GetPodFullName(addedPod) - if addedPodFullName == schedutil.GetPodFullName(meta.pod) { - return fmt.Errorf("addedPod and meta.pod must not be the same.") - } - if nodeInfo.Node() == nil { - return fmt.Errorf("Invalid node in nodeInfo.") - } - // Add matching anti-affinity terms of the addedPod to the map. - podMatchingTerms, err := getMatchingAntiAffinityTermsOfExistingPod(meta.pod, addedPod, nodeInfo.Node()) - if err != nil { - return err - } - if len(podMatchingTerms) > 0 { - existingTerms, found := meta.matchingAntiAffinityTerms[addedPodFullName] - if found { - meta.matchingAntiAffinityTerms[addedPodFullName] = append(existingTerms, - podMatchingTerms...) - } else { - meta.matchingAntiAffinityTerms[addedPodFullName] = podMatchingTerms - } - } - // If addedPod is in the same namespace as the meta.pod, update the list - // of matching pods if applicable. - if meta.serviceAffinityInUse && addedPod.Namespace == meta.pod.Namespace { - selector := CreateSelectorFromLabels(meta.pod.Labels) - if selector.Matches(labels.Set(addedPod.Labels)) { - meta.serviceAffinityMatchingPodList = append(meta.serviceAffinityMatchingPodList, - addedPod) - } - } - return nil -} - -// ShallowCopy copies a metadata struct into a new struct and creates a copy of -// its maps and slices, but it does not copy the contents of pointer values. -func (meta *predicateMetadata) ShallowCopy() algorithm.PredicateMetadata { - newPredMeta := &predicateMetadata{ - pod: meta.pod, - podBestEffort: meta.podBestEffort, - podRequest: meta.podRequest, - serviceAffinityInUse: meta.serviceAffinityInUse, - } - newPredMeta.podPorts = map[int]bool{} - for k, v := range meta.podPorts { - newPredMeta.podPorts[k] = v - } - newPredMeta.matchingAntiAffinityTerms = map[string][]matchingPodAntiAffinityTerm{} - for k, v := range meta.matchingAntiAffinityTerms { - newPredMeta.matchingAntiAffinityTerms[k] = append([]matchingPodAntiAffinityTerm(nil), v...) - } - newPredMeta.serviceAffinityMatchingPodServices = append([]*v1.Service(nil), - meta.serviceAffinityMatchingPodServices...) - newPredMeta.serviceAffinityMatchingPodList = append([]*v1.Pod(nil), - meta.serviceAffinityMatchingPodList...) - return (algorithm.PredicateMetadata)(newPredMeta) -} diff --git a/plugin/pkg/scheduler/algorithm/predicates/metadata_test.go b/plugin/pkg/scheduler/algorithm/predicates/metadata_test.go deleted file mode 100644 index 640da1fcace..00000000000 --- a/plugin/pkg/scheduler/algorithm/predicates/metadata_test.go +++ /dev/null @@ -1,400 +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 predicates - -import ( - "fmt" - "reflect" - "sort" - "testing" - - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" - schedulertesting "k8s.io/kubernetes/plugin/pkg/scheduler/testing" -) - -// sortableAntiAffinityTerms lets us to sort anti-affinity terms. -type sortableAntiAffinityTerms []matchingPodAntiAffinityTerm - -// Less establishes some ordering between two matchingPodAntiAffinityTerms for -// sorting. -func (s sortableAntiAffinityTerms) Less(i, j int) bool { - t1, t2 := s[i], s[j] - if t1.node.Name != t2.node.Name { - return t1.node.Name < t2.node.Name - } - if len(t1.term.Namespaces) != len(t2.term.Namespaces) { - return len(t1.term.Namespaces) < len(t2.term.Namespaces) - } - if t1.term.TopologyKey != t2.term.TopologyKey { - return t1.term.TopologyKey < t2.term.TopologyKey - } - if len(t1.term.LabelSelector.MatchLabels) != len(t2.term.LabelSelector.MatchLabels) { - return len(t1.term.LabelSelector.MatchLabels) < len(t2.term.LabelSelector.MatchLabels) - } - return false -} -func (s sortableAntiAffinityTerms) Len() int { return len(s) } -func (s sortableAntiAffinityTerms) Swap(i, j int) { - s[i], s[j] = s[j], s[i] -} - -var _ = sort.Interface(sortableAntiAffinityTerms{}) - -func sortAntiAffinityTerms(terms map[string][]matchingPodAntiAffinityTerm) { - for k, v := range terms { - sortableTerms := sortableAntiAffinityTerms(v) - sort.Sort(sortableTerms) - terms[k] = sortableTerms - } -} - -// sortablePods lets us to sort pods. -type sortablePods []*v1.Pod - -func (s sortablePods) Less(i, j int) bool { - return s[i].Namespace < s[j].Namespace || - (s[i].Namespace == s[j].Namespace && s[i].Name < s[j].Name) -} -func (s sortablePods) Len() int { return len(s) } -func (s sortablePods) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -var _ = sort.Interface(&sortablePods{}) - -// sortableServices allows us to sort services. -type sortableServices []*v1.Service - -func (s sortableServices) Less(i, j int) bool { - return s[i].Namespace < s[j].Namespace || - (s[i].Namespace == s[j].Namespace && s[i].Name < s[j].Name) -} -func (s sortableServices) Len() int { return len(s) } -func (s sortableServices) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -var _ = sort.Interface(&sortableServices{}) - -// predicateMetadataEquivalent returns true if the two metadata are equivalent. -// Note: this function does not compare podRequest. -func predicateMetadataEquivalent(meta1, meta2 *predicateMetadata) error { - if !reflect.DeepEqual(meta1.pod, meta2.pod) { - return fmt.Errorf("pods are not the same.") - } - if meta1.podBestEffort != meta2.podBestEffort { - return fmt.Errorf("podBestEfforts are not equal.") - } - if meta1.serviceAffinityInUse != meta1.serviceAffinityInUse { - return fmt.Errorf("serviceAffinityInUses are not equal.") - } - if len(meta1.podPorts) != len(meta2.podPorts) { - return fmt.Errorf("podPorts are not equal.") - } - for !reflect.DeepEqual(meta1.podPorts, meta2.podPorts) { - return fmt.Errorf("podPorts are not equal.") - } - sortAntiAffinityTerms(meta1.matchingAntiAffinityTerms) - sortAntiAffinityTerms(meta2.matchingAntiAffinityTerms) - if !reflect.DeepEqual(meta1.matchingAntiAffinityTerms, meta2.matchingAntiAffinityTerms) { - return fmt.Errorf("matchingAntiAffinityTerms are not euqal.") - } - if meta1.serviceAffinityInUse { - sortablePods1 := sortablePods(meta1.serviceAffinityMatchingPodList) - sort.Sort(sortablePods1) - sortablePods2 := sortablePods(meta2.serviceAffinityMatchingPodList) - sort.Sort(sortablePods2) - if !reflect.DeepEqual(sortablePods1, sortablePods2) { - return fmt.Errorf("serviceAffinityMatchingPodLists are not euqal.") - } - - sortableServices1 := sortableServices(meta1.serviceAffinityMatchingPodServices) - sort.Sort(sortableServices1) - sortableServices2 := sortableServices(meta2.serviceAffinityMatchingPodServices) - sort.Sort(sortableServices2) - if !reflect.DeepEqual(sortableServices1, sortableServices2) { - return fmt.Errorf("serviceAffinityMatchingPodServices are not euqal.") - } - } - return nil -} - -func TestPredicateMetadata_AddRemovePod(t *testing.T) { - var label1 = map[string]string{ - "region": "r1", - "zone": "z11", - } - var label2 = map[string]string{ - "region": "r1", - "zone": "z12", - } - var label3 = map[string]string{ - "region": "r2", - "zone": "z21", - } - selector1 := map[string]string{"foo": "bar"} - antiAffinityFooBar := &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"bar"}, - }, - }, - }, - TopologyKey: "region", - }, - }, - } - antiAffinityComplex := &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"bar", "buzz"}, - }, - }, - }, - TopologyKey: "region", - }, - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpNotIn, - Values: []string{"bar", "security", "test"}, - }, - }, - }, - TopologyKey: "zone", - }, - }, - } - - tests := []struct { - description string - pendingPod *v1.Pod - addedPod *v1.Pod - existingPods []*v1.Pod - nodes []*v1.Node - services []*v1.Service - }{ - { - description: "no anti-affinity or service affinity exist", - pendingPod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "pending", Labels: selector1}, - }, - existingPods: []*v1.Pod{ - {ObjectMeta: metav1.ObjectMeta{Name: "p1", Labels: selector1}, - Spec: v1.PodSpec{NodeName: "nodeA"}, - }, - {ObjectMeta: metav1.ObjectMeta{Name: "p2"}, - Spec: v1.PodSpec{NodeName: "nodeC"}, - }, - }, - addedPod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "addedPod", Labels: selector1}, - Spec: v1.PodSpec{NodeName: "nodeB"}, - }, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: label1}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: label2}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: label3}}, - }, - }, - { - description: "metadata anti-affinity terms are updated correctly after adding and removing a pod", - pendingPod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "pending", Labels: selector1}, - }, - existingPods: []*v1.Pod{ - {ObjectMeta: metav1.ObjectMeta{Name: "p1", Labels: selector1}, - Spec: v1.PodSpec{NodeName: "nodeA"}, - }, - {ObjectMeta: metav1.ObjectMeta{Name: "p2"}, - Spec: v1.PodSpec{ - NodeName: "nodeC", - Affinity: &v1.Affinity{ - PodAntiAffinity: antiAffinityFooBar, - }, - }, - }, - }, - addedPod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "addedPod", Labels: selector1}, - Spec: v1.PodSpec{ - NodeName: "nodeB", - Affinity: &v1.Affinity{ - PodAntiAffinity: antiAffinityFooBar, - }, - }, - }, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: label1}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: label2}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: label3}}, - }, - }, - { - description: "metadata service-affinity data are updated correctly after adding and removing a pod", - pendingPod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "pending", Labels: selector1}, - }, - existingPods: []*v1.Pod{ - {ObjectMeta: metav1.ObjectMeta{Name: "p1", Labels: selector1}, - Spec: v1.PodSpec{NodeName: "nodeA"}, - }, - {ObjectMeta: metav1.ObjectMeta{Name: "p2"}, - Spec: v1.PodSpec{NodeName: "nodeC"}, - }, - }, - addedPod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "addedPod", Labels: selector1}, - Spec: v1.PodSpec{NodeName: "nodeB"}, - }, - services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: selector1}}}, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: label1}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: label2}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: label3}}, - }, - }, - { - description: "metadata anti-affinity terms and service affinity data are updated correctly after adding and removing a pod", - pendingPod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "pending", Labels: selector1}, - }, - existingPods: []*v1.Pod{ - {ObjectMeta: metav1.ObjectMeta{Name: "p1", Labels: selector1}, - Spec: v1.PodSpec{NodeName: "nodeA"}, - }, - {ObjectMeta: metav1.ObjectMeta{Name: "p2"}, - Spec: v1.PodSpec{ - NodeName: "nodeC", - Affinity: &v1.Affinity{ - PodAntiAffinity: antiAffinityFooBar, - }, - }, - }, - }, - addedPod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "addedPod", Labels: selector1}, - Spec: v1.PodSpec{ - NodeName: "nodeA", - Affinity: &v1.Affinity{ - PodAntiAffinity: antiAffinityComplex, - }, - }, - }, - services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: selector1}}}, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: label1}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: label2}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: label3}}, - }, - }, - } - - for _, test := range tests { - allPodLister := schedulertesting.FakePodLister(append(test.existingPods, test.addedPod)) - // getMeta creates predicate meta data given the list of pods. - getMeta := func(lister schedulertesting.FakePodLister) (*predicateMetadata, map[string]*schedulercache.NodeInfo) { - nodeInfoMap := schedulercache.CreateNodeNameToInfoMap(lister, test.nodes) - // nodeList is a list of non-pointer nodes to feed to FakeNodeListInfo. - nodeList := []v1.Node{} - for _, n := range test.nodes { - nodeList = append(nodeList, *n) - } - _, precompute := NewServiceAffinityPredicate(lister, schedulertesting.FakeServiceLister(test.services), FakeNodeListInfo(nodeList), nil) - RegisterPredicateMetadataProducer("ServiceAffinityMetaProducer", precompute) - pmf := PredicateMetadataFactory{lister} - meta := pmf.GetMetadata(test.pendingPod, nodeInfoMap) - return meta.(*predicateMetadata), nodeInfoMap - } - - // allPodsMeta is meta data produced when all pods, including test.addedPod - // are given to the metadata producer. - allPodsMeta, _ := getMeta(allPodLister) - // existingPodsMeta1 is meta data produced for test.existingPods (without test.addedPod). - existingPodsMeta1, nodeInfoMap := getMeta(schedulertesting.FakePodLister(test.existingPods)) - // Add test.addedPod to existingPodsMeta1 and make sure meta is equal to allPodsMeta - nodeInfo := nodeInfoMap[test.addedPod.Spec.NodeName] - if err := existingPodsMeta1.AddPod(test.addedPod, nodeInfo); err != nil { - t.Errorf("test [%v]: error adding pod to meta: %v", test.description, err) - } - if err := predicateMetadataEquivalent(allPodsMeta, existingPodsMeta1); err != nil { - t.Errorf("test [%v]: meta data are not equivalent: %v", test.description, err) - } - // Remove the added pod and from existingPodsMeta1 an make sure it is equal - // to meta generated for existing pods. - existingPodsMeta2, _ := getMeta(schedulertesting.FakePodLister(test.existingPods)) - if err := existingPodsMeta1.RemovePod(test.addedPod); err != nil { - t.Errorf("test [%v]: error removing pod from meta: %v", test.description, err) - } - if err := predicateMetadataEquivalent(existingPodsMeta1, existingPodsMeta2); err != nil { - t.Errorf("test [%v]: meta data are not equivalent: %v", test.description, err) - } - } -} - -// TestPredicateMetadata_ShallowCopy tests the ShallowCopy function. It is based -// on the idea that shallow-copy should produce an object that is deep-equal to the original -// object. -func TestPredicateMetadata_ShallowCopy(t *testing.T) { - source := predicateMetadata{ - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "testns", - }, - }, - podBestEffort: true, - podRequest: &schedulercache.Resource{ - MilliCPU: 1000, - Memory: 300, - AllowedPodNumber: 4, - }, - podPorts: map[int]bool{1234: true, 456: false}, - matchingAntiAffinityTerms: map[string][]matchingPodAntiAffinityTerm{ - "term1": { - { - term: &v1.PodAffinityTerm{TopologyKey: "node"}, - node: &v1.Node{ - ObjectMeta: metav1.ObjectMeta{Name: "machine1"}, - }, - }, - }, - }, - serviceAffinityInUse: true, - serviceAffinityMatchingPodList: []*v1.Pod{ - {ObjectMeta: metav1.ObjectMeta{Name: "pod1"}}, - {ObjectMeta: metav1.ObjectMeta{Name: "pod2"}}, - }, - serviceAffinityMatchingPodServices: []*v1.Service{ - {ObjectMeta: metav1.ObjectMeta{Name: "service1"}}, - }, - } - - if !reflect.DeepEqual(source.ShallowCopy().(*predicateMetadata), &source) { - t.Errorf("Copy is not equal to source!") - } -} diff --git a/plugin/pkg/scheduler/algorithm/predicates/utils.go b/plugin/pkg/scheduler/algorithm/predicates/utils.go deleted file mode 100644 index 84d096d23aa..00000000000 --- a/plugin/pkg/scheduler/algorithm/predicates/utils.go +++ /dev/null @@ -1,91 +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 predicates - -import ( - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" -) - -// FindLabelsInSet gets as many key/value pairs as possible out of a label set. -func FindLabelsInSet(labelsToKeep []string, selector labels.Set) map[string]string { - aL := make(map[string]string) - for _, l := range labelsToKeep { - if selector.Has(l) { - aL[l] = selector.Get(l) - } - } - return aL -} - -// AddUnsetLabelsToMap backfills missing values with values we find in a map. -func AddUnsetLabelsToMap(aL map[string]string, labelsToAdd []string, labelSet labels.Set) { - for _, l := range labelsToAdd { - // if the label is already there, dont overwrite it. - if _, exists := aL[l]; exists { - continue - } - // otherwise, backfill this label. - if labelSet.Has(l) { - aL[l] = labelSet.Get(l) - } - } -} - -// FilterPodsByNamespace filters pods outside a namespace from the given list. -func FilterPodsByNamespace(pods []*v1.Pod, ns string) []*v1.Pod { - filtered := []*v1.Pod{} - for _, nsPod := range pods { - if nsPod.Namespace == ns { - filtered = append(filtered, nsPod) - } - } - return filtered -} - -// CreateSelectorFromLabels is used to define a selector that corresponds to the keys in a map. -func CreateSelectorFromLabels(aL map[string]string) labels.Selector { - if aL == nil || len(aL) == 0 { - return labels.Everything() - } - return labels.Set(aL).AsSelector() -} - -// GetEquivalencePod returns a EquivalencePod which contains a group of pod attributes which can be reused. -func GetEquivalencePod(pod *v1.Pod) interface{} { - // For now we only consider pods: - // 1. OwnerReferences is Controller - // 2. with same OwnerReferences - // to be equivalent - if len(pod.OwnerReferences) != 0 { - for _, ref := range pod.OwnerReferences { - if *ref.Controller { - // a pod can only belongs to one controller - return &EquivalencePod{ - ControllerRef: ref, - } - } - } - } - return nil -} - -// EquivalencePod is a group of pod attributes which can be reused as equivalence to schedule other pods. -type EquivalencePod struct { - ControllerRef metav1.OwnerReference -} diff --git a/plugin/pkg/scheduler/algorithm/predicates/utils_test.go b/plugin/pkg/scheduler/algorithm/predicates/utils_test.go deleted file mode 100644 index 305a27d1304..00000000000 --- a/plugin/pkg/scheduler/algorithm/predicates/utils_test.go +++ /dev/null @@ -1,70 +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 predicates - -import ( - "fmt" - - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" -) - -// ExampleUtils is a https://blog.golang.org/examples styled unit test. -func ExampleFindLabelsInSet() { - labelSubset := labels.Set{} - labelSubset["label1"] = "value1" - labelSubset["label2"] = "value2" - // Lets make believe that these pods are on the cluster. - // Utility functions will inspect their labels, filter them, and so on. - nsPods := []*v1.Pod{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pod1", - Namespace: "ns1", - Labels: map[string]string{ - "label1": "wontSeeThis", - "label2": "wontSeeThis", - "label3": "will_see_this", - }, - }, - }, // first pod which will be used via the utilities - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pod2", - Namespace: "ns1", - }, - }, - - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pod3ThatWeWontSee", - }, - }, - } - fmt.Println(FindLabelsInSet([]string{"label1", "label2", "label3"}, nsPods[0].ObjectMeta.Labels)["label3"]) - AddUnsetLabelsToMap(labelSubset, []string{"label1", "label2", "label3"}, nsPods[0].ObjectMeta.Labels) - fmt.Println(labelSubset) - - for _, pod := range FilterPodsByNamespace(nsPods, "ns1") { - fmt.Print(pod.Name, ",") - } - // Output: - // will_see_this - // label1=value1,label2=value2,label3=will_see_this - // pod1,pod2, -} diff --git a/plugin/pkg/scheduler/algorithm/priorities/BUILD b/plugin/pkg/scheduler/algorithm/priorities/BUILD deleted file mode 100644 index 9e78e1dca3a..00000000000 --- a/plugin/pkg/scheduler/algorithm/priorities/BUILD +++ /dev/null @@ -1,89 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = [ - "balanced_resource_allocation.go", - "image_locality.go", - "interpod_affinity.go", - "least_requested.go", - "metadata.go", - "most_requested.go", - "node_affinity.go", - "node_label.go", - "node_prefer_avoid_pods.go", - "selector_spreading.go", - "taint_toleration.go", - "test_util.go", - ], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/priorities", - deps = [ - "//pkg/api/v1/helper:go_default_library", - "//pkg/kubelet/apis:go_default_library", - "//pkg/util/node:go_default_library", - "//plugin/pkg/scheduler/algorithm:go_default_library", - "//plugin/pkg/scheduler/algorithm/predicates:go_default_library", - "//plugin/pkg/scheduler/algorithm/priorities/util:go_default_library", - "//plugin/pkg/scheduler/api:go_default_library", - "//plugin/pkg/scheduler/schedulercache:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/client-go/util/workqueue:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = [ - "balanced_resource_allocation_test.go", - "image_locality_test.go", - "interpod_affinity_test.go", - "least_requested_test.go", - "metadata_test.go", - "most_requested_test.go", - "node_affinity_test.go", - "node_label_test.go", - "node_prefer_avoid_pods_test.go", - "selector_spreading_test.go", - "taint_toleration_test.go", - ], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/priorities", - library = ":go_default_library", - deps = [ - "//pkg/kubelet/apis:go_default_library", - "//plugin/pkg/scheduler/algorithm/priorities/util:go_default_library", - "//plugin/pkg/scheduler/api:go_default_library", - "//plugin/pkg/scheduler/schedulercache:go_default_library", - "//plugin/pkg/scheduler/testing:go_default_library", - "//vendor/k8s.io/api/apps/v1beta1:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//plugin/pkg/scheduler/algorithm/priorities/util:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/plugin/pkg/scheduler/algorithm/priorities/balanced_resource_allocation.go b/plugin/pkg/scheduler/algorithm/priorities/balanced_resource_allocation.go deleted file mode 100644 index 5739bedf85a..00000000000 --- a/plugin/pkg/scheduler/algorithm/priorities/balanced_resource_allocation.go +++ /dev/null @@ -1,116 +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 priorities - -import ( - "fmt" - "math" - - "k8s.io/api/core/v1" - priorityutil "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/priorities/util" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" - - "github.com/golang/glog" -) - -// This is a reasonable size range of all container images. 90%ile of images on dockerhub drops into this range. -const ( - mb int64 = 1024 * 1024 - minImgSize int64 = 23 * mb - maxImgSize int64 = 1000 * mb -) - -// Also used in most/least_requested nad metadata. -// TODO: despaghettify it -func getNonZeroRequests(pod *v1.Pod) *schedulercache.Resource { - result := &schedulercache.Resource{} - for i := range pod.Spec.Containers { - container := &pod.Spec.Containers[i] - cpu, memory := priorityutil.GetNonzeroRequests(&container.Resources.Requests) - result.MilliCPU += cpu - result.Memory += memory - } - return result -} - -func calculateBalancedResourceAllocation(pod *v1.Pod, podRequests *schedulercache.Resource, nodeInfo *schedulercache.NodeInfo) (schedulerapi.HostPriority, error) { - node := nodeInfo.Node() - if node == nil { - return schedulerapi.HostPriority{}, fmt.Errorf("node not found") - } - - allocatableResources := nodeInfo.AllocatableResource() - totalResources := *podRequests - totalResources.MilliCPU += nodeInfo.NonZeroRequest().MilliCPU - totalResources.Memory += nodeInfo.NonZeroRequest().Memory - - cpuFraction := fractionOfCapacity(totalResources.MilliCPU, allocatableResources.MilliCPU) - memoryFraction := fractionOfCapacity(totalResources.Memory, allocatableResources.Memory) - score := int(0) - if cpuFraction >= 1 || memoryFraction >= 1 { - // if requested >= capacity, the corresponding host should never be preferred. - score = 0 - } else { - // Upper and lower boundary of difference between cpuFraction and memoryFraction are -1 and 1 - // respectively. Multilying the absolute value of the difference by 10 scales the value to - // 0-10 with 0 representing well balanced allocation and 10 poorly balanced. Subtracting it from - // 10 leads to the score which also scales from 0 to 10 while 10 representing well balanced. - diff := math.Abs(cpuFraction - memoryFraction) - score = int((1 - diff) * float64(schedulerapi.MaxPriority)) - } - if glog.V(10) { - // We explicitly don't do glog.V(10).Infof() to avoid computing all the parameters if this is - // not logged. There is visible performance gain from it. - glog.V(10).Infof( - "%v -> %v: Balanced Resource Allocation, capacity %d millicores %d memory bytes, total request %d millicores %d memory bytes, score %d", - pod.Name, node.Name, - allocatableResources.MilliCPU, allocatableResources.Memory, - totalResources.MilliCPU, totalResources.Memory, - score, - ) - } - - return schedulerapi.HostPriority{ - Host: node.Name, - Score: score, - }, nil -} - -func fractionOfCapacity(requested, capacity int64) float64 { - if capacity == 0 { - return 1 - } - return float64(requested) / float64(capacity) -} - -// BalancedResourceAllocationMap favors nodes with balanced resource usage rate. -// BalancedResourceAllocationMap should **NOT** be used alone, and **MUST** be used together with LeastRequestedPriority. -// It calculates the difference between the cpu and memory fracion of capacity, and prioritizes the host based on how -// close the two metrics are to each other. -// Detail: score = 10 - abs(cpuFraction-memoryFraction)*10. The algorithm is partly inspired by: -// "Wei Huang et al. An Energy Efficient Virtual Machine Placement Algorithm with Balanced Resource Utilization" -func BalancedResourceAllocationMap(pod *v1.Pod, meta interface{}, nodeInfo *schedulercache.NodeInfo) (schedulerapi.HostPriority, error) { - var nonZeroRequest *schedulercache.Resource - if priorityMeta, ok := meta.(*priorityMetadata); ok { - nonZeroRequest = priorityMeta.nonZeroRequest - } else { - // We couldn't parse metadatat - fallback to computing it. - nonZeroRequest = getNonZeroRequests(pod) - } - return calculateBalancedResourceAllocation(pod, nonZeroRequest, nodeInfo) -} diff --git a/plugin/pkg/scheduler/algorithm/priorities/image_locality.go b/plugin/pkg/scheduler/algorithm/priorities/image_locality.go deleted file mode 100644 index 67dba1b1af2..00000000000 --- a/plugin/pkg/scheduler/algorithm/priorities/image_locality.go +++ /dev/null @@ -1,79 +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 priorities - -import ( - "fmt" - - "k8s.io/api/core/v1" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" -) - -// ImageLocalityPriorityMap is a priority function that favors nodes that already have requested pod container's images. -// It will detect whether the requested images are present on a node, and then calculate a score ranging from 0 to 10 -// based on the total size of those images. -// - If none of the images are present, this node will be given the lowest priority. -// - If some of the images are present on a node, the larger their sizes' sum, the higher the node's priority. -func ImageLocalityPriorityMap(pod *v1.Pod, meta interface{}, nodeInfo *schedulercache.NodeInfo) (schedulerapi.HostPriority, error) { - node := nodeInfo.Node() - if node == nil { - return schedulerapi.HostPriority{}, fmt.Errorf("node not found") - } - - var sumSize int64 - for i := range pod.Spec.Containers { - sumSize += checkContainerImageOnNode(node, &pod.Spec.Containers[i]) - } - return schedulerapi.HostPriority{ - Host: node.Name, - Score: calculateScoreFromSize(sumSize), - }, nil -} - -// calculateScoreFromSize calculates the priority of a node. sumSize is sum size of requested images on this node. -// 1. Split image size range into 10 buckets. -// 2. Decide the priority of a given sumSize based on which bucket it belongs to. -func calculateScoreFromSize(sumSize int64) int { - var score int - switch { - case sumSize == 0 || sumSize < minImgSize: - // score == 0 means none of the images required by this pod are present on this - // node or the total size of the images present is too small to be taken into further consideration. - score = 0 - // If existing images' total size is larger than max, just make it highest priority. - case sumSize >= maxImgSize: - score = schedulerapi.MaxPriority - default: - score = int((int64(schedulerapi.MaxPriority) * (sumSize - minImgSize) / (maxImgSize - minImgSize)) + 1) - } - // Return which bucket the given size belongs to - return score -} - -// checkContainerImageOnNode checks if a container image is present on a node and returns its size. -func checkContainerImageOnNode(node *v1.Node, container *v1.Container) int64 { - for _, image := range node.Status.Images { - for _, name := range image.Names { - if container.Image == name { - // Should return immediately. - return image.SizeBytes - } - } - } - return 0 -} diff --git a/plugin/pkg/scheduler/algorithm/priorities/least_requested.go b/plugin/pkg/scheduler/algorithm/priorities/least_requested.go deleted file mode 100644 index 74306451638..00000000000 --- a/plugin/pkg/scheduler/algorithm/priorities/least_requested.go +++ /dev/null @@ -1,91 +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 priorities - -import ( - "fmt" - - "k8s.io/api/core/v1" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" - - "github.com/golang/glog" -) - -// LeastRequestedPriority is a priority function that favors nodes with fewer requested resources. -// It calculates the percentage of memory and CPU requested by pods scheduled on the node, and prioritizes -// based on the minimum of the average of the fraction of requested to capacity. -// Details: cpu((capacity - sum(requested)) * 10 / capacity) + memory((capacity - sum(requested)) * 10 / capacity) / 2 -func LeastRequestedPriorityMap(pod *v1.Pod, meta interface{}, nodeInfo *schedulercache.NodeInfo) (schedulerapi.HostPriority, error) { - var nonZeroRequest *schedulercache.Resource - if priorityMeta, ok := meta.(*priorityMetadata); ok { - nonZeroRequest = priorityMeta.nonZeroRequest - } else { - // We couldn't parse metadata - fallback to computing it. - nonZeroRequest = getNonZeroRequests(pod) - } - return calculateUnusedPriority(pod, nonZeroRequest, nodeInfo) -} - -// The unused capacity is calculated on a scale of 0-10 -// 0 being the lowest priority and 10 being the highest. -// The more unused resources the higher the score is. -func calculateUnusedScore(requested int64, capacity int64, node string) int64 { - if capacity == 0 { - return 0 - } - if requested > capacity { - glog.V(10).Infof("Combined requested resources %d from existing pods exceeds capacity %d on node %s", - requested, capacity, node) - return 0 - } - return ((capacity - requested) * int64(schedulerapi.MaxPriority)) / capacity -} - -// Calculates host priority based on the amount of unused resources. -// 'node' has information about the resources on the node. -// 'pods' is a list of pods currently scheduled on the node. -func calculateUnusedPriority(pod *v1.Pod, podRequests *schedulercache.Resource, nodeInfo *schedulercache.NodeInfo) (schedulerapi.HostPriority, error) { - node := nodeInfo.Node() - if node == nil { - return schedulerapi.HostPriority{}, fmt.Errorf("node not found") - } - - allocatableResources := nodeInfo.AllocatableResource() - totalResources := *podRequests - totalResources.MilliCPU += nodeInfo.NonZeroRequest().MilliCPU - totalResources.Memory += nodeInfo.NonZeroRequest().Memory - - cpuScore := calculateUnusedScore(totalResources.MilliCPU, allocatableResources.MilliCPU, node.Name) - memoryScore := calculateUnusedScore(totalResources.Memory, allocatableResources.Memory, node.Name) - if glog.V(10) { - // We explicitly don't do glog.V(10).Infof() to avoid computing all the parameters if this is - // not logged. There is visible performance gain from it. - glog.V(10).Infof( - "%v -> %v: Least Requested Priority, capacity %d millicores %d memory bytes, total request %d millicores %d memory bytes, score %d CPU %d memory", - pod.Name, node.Name, - allocatableResources.MilliCPU, allocatableResources.Memory, - totalResources.MilliCPU, totalResources.Memory, - cpuScore, memoryScore, - ) - } - - return schedulerapi.HostPriority{ - Host: node.Name, - Score: int((cpuScore + memoryScore) / 2), - }, nil -} diff --git a/plugin/pkg/scheduler/algorithm/priorities/metadata.go b/plugin/pkg/scheduler/algorithm/priorities/metadata.go deleted file mode 100644 index 6f3818eb530..00000000000 --- a/plugin/pkg/scheduler/algorithm/priorities/metadata.go +++ /dev/null @@ -1,43 +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 priorities - -import ( - "k8s.io/api/core/v1" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" -) - -// priorityMetadata is a type that is passed as metadata for priority functions -type priorityMetadata struct { - nonZeroRequest *schedulercache.Resource - podTolerations []v1.Toleration - affinity *v1.Affinity -} - -// PriorityMetadata is a MetadataProducer. Node info can be nil. -func PriorityMetadata(pod *v1.Pod, nodeNameToInfo map[string]*schedulercache.NodeInfo) interface{} { - // If we cannot compute metadata, just return nil - if pod == nil { - return nil - } - tolerationsPreferNoSchedule := getAllTolerationPreferNoSchedule(pod.Spec.Tolerations) - return &priorityMetadata{ - nonZeroRequest: getNonZeroRequests(pod), - podTolerations: tolerationsPreferNoSchedule, - affinity: pod.Spec.Affinity, - } -} diff --git a/plugin/pkg/scheduler/algorithm/priorities/metadata_test.go b/plugin/pkg/scheduler/algorithm/priorities/metadata_test.go deleted file mode 100644 index b8fd653ba24..00000000000 --- a/plugin/pkg/scheduler/algorithm/priorities/metadata_test.go +++ /dev/null @@ -1,132 +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 priorities - -import ( - "reflect" - "testing" - - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - priorityutil "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/priorities/util" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" -) - -func TestPriorityMetadata(t *testing.T) { - nonZeroReqs := &schedulercache.Resource{} - nonZeroReqs.MilliCPU = priorityutil.DefaultMilliCpuRequest - nonZeroReqs.Memory = priorityutil.DefaultMemoryRequest - - specifiedReqs := &schedulercache.Resource{} - specifiedReqs.MilliCPU = 200 - specifiedReqs.Memory = 2000 - - tolerations := []v1.Toleration{{ - Key: "foo", - Operator: v1.TolerationOpEqual, - Value: "bar", - Effect: v1.TaintEffectPreferNoSchedule, - }} - podAffinity := &v1.Affinity{ - PodAffinity: &v1.PodAffinity{ - PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{ - { - Weight: 5, - PodAffinityTerm: v1.PodAffinityTerm{ - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "security", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"S1"}, - }, - }, - }, - TopologyKey: "region", - }, - }, - }, - }, - } - podWithTolerationsAndAffinity := &v1.Pod{ - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: "container", - Image: "image", - ImagePullPolicy: "Always", - }, - }, - Affinity: podAffinity, - Tolerations: tolerations, - }, - } - podWithTolerationsAndRequests := &v1.Pod{ - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: "container", - Image: "image", - ImagePullPolicy: "Always", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("200m"), - v1.ResourceMemory: resource.MustParse("2000"), - }, - }, - }, - }, - Tolerations: tolerations, - }, - } - tests := []struct { - pod *v1.Pod - test string - expected interface{} - }{ - { - pod: nil, - expected: nil, - test: "pod is nil , priorityMetadata is nil", - }, - { - pod: podWithTolerationsAndAffinity, - expected: &priorityMetadata{ - nonZeroRequest: nonZeroReqs, - podTolerations: tolerations, - affinity: podAffinity, - }, - test: "Produce a priorityMetadata with default requests", - }, - { - pod: podWithTolerationsAndRequests, - expected: &priorityMetadata{ - nonZeroRequest: specifiedReqs, - podTolerations: tolerations, - affinity: nil, - }, - test: "Produce a priorityMetadata with specified requests", - }, - } - for _, test := range tests { - ptData := PriorityMetadata(test.pod, nil) - if !reflect.DeepEqual(test.expected, ptData) { - t.Errorf("%s: expected %#v, got %#v", test.test, test.expected, ptData) - } - } -} diff --git a/plugin/pkg/scheduler/algorithm/priorities/most_requested.go b/plugin/pkg/scheduler/algorithm/priorities/most_requested.go deleted file mode 100644 index 4245d4938ba..00000000000 --- a/plugin/pkg/scheduler/algorithm/priorities/most_requested.go +++ /dev/null @@ -1,94 +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 priorities - -import ( - "fmt" - - "k8s.io/api/core/v1" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" - - "github.com/golang/glog" -) - -// MostRequestedPriority is a priority function that favors nodes with most requested resources. -// It calculates the percentage of memory and CPU requested by pods scheduled on the node, and prioritizes -// based on the maximum of the average of the fraction of requested to capacity. -// Details: (cpu(10 * sum(requested) / capacity) + memory(10 * sum(requested) / capacity)) / 2 -func MostRequestedPriorityMap(pod *v1.Pod, meta interface{}, nodeInfo *schedulercache.NodeInfo) (schedulerapi.HostPriority, error) { - var nonZeroRequest *schedulercache.Resource - if priorityMeta, ok := meta.(*priorityMetadata); ok { - nonZeroRequest = priorityMeta.nonZeroRequest - } else { - // We couldn't parse metadatat - fallback to computing it. - nonZeroRequest = getNonZeroRequests(pod) - } - return calculateUsedPriority(pod, nonZeroRequest, nodeInfo) -} - -// The used capacity is calculated on a scale of 0-10 -// 0 being the lowest priority and 10 being the highest. -// The more resources are used the higher the score is. This function -// is almost a reversed version of least_requested_priority.calculatUnusedScore -// (10 - calculateUnusedScore). The main difference is in rounding. It was added to -// keep the final formula clean and not to modify the widely used (by users -// in their default scheduling policies) calculateUSedScore. -func calculateUsedScore(requested int64, capacity int64, node string) int64 { - if capacity == 0 { - return 0 - } - if requested > capacity { - glog.V(10).Infof("Combined requested resources %d from existing pods exceeds capacity %d on node %s", - requested, capacity, node) - return 0 - } - return (requested * schedulerapi.MaxPriority) / capacity -} - -// Calculate the resource used on a node. 'node' has information about the resources on the node. -// 'pods' is a list of pods currently scheduled on the node. -func calculateUsedPriority(pod *v1.Pod, podRequests *schedulercache.Resource, nodeInfo *schedulercache.NodeInfo) (schedulerapi.HostPriority, error) { - node := nodeInfo.Node() - if node == nil { - return schedulerapi.HostPriority{}, fmt.Errorf("node not found") - } - - allocatableResources := nodeInfo.AllocatableResource() - totalResources := *podRequests - totalResources.MilliCPU += nodeInfo.NonZeroRequest().MilliCPU - totalResources.Memory += nodeInfo.NonZeroRequest().Memory - - cpuScore := calculateUsedScore(totalResources.MilliCPU, allocatableResources.MilliCPU, node.Name) - memoryScore := calculateUsedScore(totalResources.Memory, allocatableResources.Memory, node.Name) - if glog.V(10) { - // We explicitly don't do glog.V(10).Infof() to avoid computing all the parameters if this is - // not logged. There is visible performance gain from it. - glog.V(10).Infof( - "%v -> %v: Most Requested Priority, capacity %d millicores %d memory bytes, total request %d millicores %d memory bytes, score %d CPU %d memory", - pod.Name, node.Name, - allocatableResources.MilliCPU, allocatableResources.Memory, - totalResources.MilliCPU, totalResources.Memory, - cpuScore, memoryScore, - ) - } - - return schedulerapi.HostPriority{ - Host: node.Name, - Score: int((cpuScore + memoryScore) / 2), - }, nil -} diff --git a/plugin/pkg/scheduler/algorithm/priorities/node_affinity.go b/plugin/pkg/scheduler/algorithm/priorities/node_affinity.go deleted file mode 100644 index 1f62091042e..00000000000 --- a/plugin/pkg/scheduler/algorithm/priorities/node_affinity.go +++ /dev/null @@ -1,103 +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 priorities - -import ( - "fmt" - - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/labels" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" - - "github.com/golang/glog" -) - -// CalculateNodeAffinityPriority prioritizes nodes according to node affinity scheduling preferences -// indicated in PreferredDuringSchedulingIgnoredDuringExecution. Each time a node match a preferredSchedulingTerm, -// it will a get an add of preferredSchedulingTerm.Weight. Thus, the more preferredSchedulingTerms -// the node satisfies and the more the preferredSchedulingTerm that is satisfied weights, the higher -// score the node gets. -func CalculateNodeAffinityPriorityMap(pod *v1.Pod, meta interface{}, nodeInfo *schedulercache.NodeInfo) (schedulerapi.HostPriority, error) { - node := nodeInfo.Node() - if node == nil { - return schedulerapi.HostPriority{}, fmt.Errorf("node not found") - } - - var affinity *v1.Affinity - if priorityMeta, ok := meta.(*priorityMetadata); ok { - affinity = priorityMeta.affinity - } else { - // We couldn't parse metadata - fallback to the podspec. - affinity = pod.Spec.Affinity - } - - var count int32 - // A nil element of PreferredDuringSchedulingIgnoredDuringExecution matches no objects. - // An element of PreferredDuringSchedulingIgnoredDuringExecution that refers to an - // empty PreferredSchedulingTerm matches all objects. - if affinity != nil && affinity.NodeAffinity != nil && affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil { - // Match PreferredDuringSchedulingIgnoredDuringExecution term by term. - for i := range affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution { - preferredSchedulingTerm := &affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[i] - if preferredSchedulingTerm.Weight == 0 { - continue - } - - // TODO: Avoid computing it for all nodes if this becomes a performance problem. - nodeSelector, err := v1helper.NodeSelectorRequirementsAsSelector(preferredSchedulingTerm.Preference.MatchExpressions) - if err != nil { - return schedulerapi.HostPriority{}, err - } - if nodeSelector.Matches(labels.Set(node.Labels)) { - count += preferredSchedulingTerm.Weight - } - } - } - - return schedulerapi.HostPriority{ - Host: node.Name, - Score: int(count), - }, nil -} - -func CalculateNodeAffinityPriorityReduce(pod *v1.Pod, meta interface{}, nodeNameToInfo map[string]*schedulercache.NodeInfo, result schedulerapi.HostPriorityList) error { - var maxCount int - for i := range result { - if result[i].Score > maxCount { - maxCount = result[i].Score - } - } - maxCountFloat := float64(maxCount) - - var fScore float64 - for i := range result { - if maxCount > 0 { - fScore = float64(schedulerapi.MaxPriority) * (float64(result[i].Score) / maxCountFloat) - } else { - fScore = 0 - } - if glog.V(10) { - // We explicitly don't do glog.V(10).Infof() to avoid computing all the parameters if this is - // not logged. There is visible performance gain from it. - glog.Infof("%v -> %v: NodeAffinityPriority, Score: (%d)", pod.Name, result[i].Host, int(fScore)) - } - result[i].Score = int(fScore) - } - return nil -} diff --git a/plugin/pkg/scheduler/algorithm/priorities/selector_spreading.go b/plugin/pkg/scheduler/algorithm/priorities/selector_spreading.go deleted file mode 100644 index 8e5eb308ba6..00000000000 --- a/plugin/pkg/scheduler/algorithm/priorities/selector_spreading.go +++ /dev/null @@ -1,269 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package priorities - -import ( - "sync" - - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/util/workqueue" - utilnode "k8s.io/kubernetes/pkg/util/node" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" - - "github.com/golang/glog" -) - -// When zone information is present, give 2/3 of the weighting to zone spreading, 1/3 to node spreading -// TODO: Any way to justify this weighting? -const zoneWeighting float64 = 2.0 / 3.0 - -type SelectorSpread struct { - serviceLister algorithm.ServiceLister - controllerLister algorithm.ControllerLister - replicaSetLister algorithm.ReplicaSetLister - statefulSetLister algorithm.StatefulSetLister -} - -func NewSelectorSpreadPriority( - serviceLister algorithm.ServiceLister, - controllerLister algorithm.ControllerLister, - replicaSetLister algorithm.ReplicaSetLister, - statefulSetLister algorithm.StatefulSetLister) algorithm.PriorityFunction { - selectorSpread := &SelectorSpread{ - serviceLister: serviceLister, - controllerLister: controllerLister, - replicaSetLister: replicaSetLister, - statefulSetLister: statefulSetLister, - } - return selectorSpread.CalculateSpreadPriority -} - -// Returns selectors of services, RCs and RSs matching the given pod. -func getSelectors(pod *v1.Pod, sl algorithm.ServiceLister, cl algorithm.ControllerLister, rsl algorithm.ReplicaSetLister, ssl algorithm.StatefulSetLister) []labels.Selector { - var selectors []labels.Selector - if services, err := sl.GetPodServices(pod); err == nil { - for _, service := range services { - selectors = append(selectors, labels.SelectorFromSet(service.Spec.Selector)) - } - } - if rcs, err := cl.GetPodControllers(pod); err == nil { - for _, rc := range rcs { - selectors = append(selectors, labels.SelectorFromSet(rc.Spec.Selector)) - } - } - if rss, err := rsl.GetPodReplicaSets(pod); err == nil { - for _, rs := range rss { - if selector, err := metav1.LabelSelectorAsSelector(rs.Spec.Selector); err == nil { - selectors = append(selectors, selector) - } - } - } - if sss, err := ssl.GetPodStatefulSets(pod); err == nil { - for _, ss := range sss { - if selector, err := metav1.LabelSelectorAsSelector(ss.Spec.Selector); err == nil { - selectors = append(selectors, selector) - } - } - } - return selectors -} - -func (s *SelectorSpread) getSelectors(pod *v1.Pod) []labels.Selector { - return getSelectors(pod, s.serviceLister, s.controllerLister, s.replicaSetLister, s.statefulSetLister) -} - -// CalculateSpreadPriority spreads pods across hosts and zones, considering pods belonging to the same service or replication controller. -// When a pod is scheduled, it looks for services, RCs or RSs that match the pod, then finds existing pods that match those selectors. -// It favors nodes that have fewer existing matching pods. -// i.e. it pushes the scheduler towards a node where there's the smallest number of -// pods which match the same service, RC or RS selectors as the pod being scheduled. -// Where zone information is included on the nodes, it favors nodes in zones with fewer existing matching pods. -func (s *SelectorSpread) CalculateSpreadPriority(pod *v1.Pod, nodeNameToInfo map[string]*schedulercache.NodeInfo, nodes []*v1.Node) (schedulerapi.HostPriorityList, error) { - selectors := s.getSelectors(pod) - - // Count similar pods by node - countsByNodeName := make(map[string]float64, len(nodes)) - countsByZone := make(map[string]float64, 10) - maxCountByNodeName := float64(0) - countsByNodeNameLock := sync.Mutex{} - - if len(selectors) > 0 { - processNodeFunc := func(i int) { - nodeName := nodes[i].Name - count := float64(0) - for _, nodePod := range nodeNameToInfo[nodeName].Pods() { - if pod.Namespace != nodePod.Namespace { - continue - } - // When we are replacing a failed pod, we often see the previous - // deleted version while scheduling the replacement. - // Ignore the previous deleted version for spreading purposes - // (it can still be considered for resource restrictions etc.) - if nodePod.DeletionTimestamp != nil { - glog.V(4).Infof("skipping pending-deleted pod: %s/%s", nodePod.Namespace, nodePod.Name) - continue - } - matches := false - for _, selector := range selectors { - if selector.Matches(labels.Set(nodePod.ObjectMeta.Labels)) { - matches = true - break - } - } - if matches { - count++ - } - } - zoneId := utilnode.GetZoneKey(nodes[i]) - - countsByNodeNameLock.Lock() - defer countsByNodeNameLock.Unlock() - countsByNodeName[nodeName] = count - if count > maxCountByNodeName { - maxCountByNodeName = count - } - if zoneId != "" { - countsByZone[zoneId] += count - } - } - workqueue.Parallelize(16, len(nodes), processNodeFunc) - } - - // Aggregate by-zone information - // Compute the maximum number of pods hosted in any zone - haveZones := len(countsByZone) != 0 - maxCountByZone := float64(0) - for _, count := range countsByZone { - if count > maxCountByZone { - maxCountByZone = count - } - } - - result := make(schedulerapi.HostPriorityList, 0, len(nodes)) - //score int - scale of 0-maxPriority - // 0 being the lowest priority and maxPriority being the highest - for _, node := range nodes { - // initializing to the default/max node score of maxPriority - fScore := float64(schedulerapi.MaxPriority) - if maxCountByNodeName > 0 { - fScore = float64(schedulerapi.MaxPriority) * ((maxCountByNodeName - countsByNodeName[node.Name]) / maxCountByNodeName) - } - - // If there is zone information present, incorporate it - if haveZones { - zoneId := utilnode.GetZoneKey(node) - if zoneId != "" { - zoneScore := float64(schedulerapi.MaxPriority) * ((maxCountByZone - countsByZone[zoneId]) / maxCountByZone) - fScore = (fScore * (1.0 - zoneWeighting)) + (zoneWeighting * zoneScore) - } - } - - result = append(result, schedulerapi.HostPriority{Host: node.Name, Score: int(fScore)}) - if glog.V(10) { - // We explicitly don't do glog.V(10).Infof() to avoid computing all the parameters if this is - // not logged. There is visible performance gain from it. - glog.V(10).Infof( - "%v -> %v: SelectorSpreadPriority, Score: (%d)", pod.Name, node.Name, int(fScore), - ) - } - } - return result, nil -} - -type ServiceAntiAffinity struct { - podLister algorithm.PodLister - serviceLister algorithm.ServiceLister - label string -} - -func NewServiceAntiAffinityPriority(podLister algorithm.PodLister, serviceLister algorithm.ServiceLister, label string) algorithm.PriorityFunction { - antiAffinity := &ServiceAntiAffinity{ - podLister: podLister, - serviceLister: serviceLister, - label: label, - } - return antiAffinity.CalculateAntiAffinityPriority -} - -// Classifies nodes into ones with labels and without labels. -func (s *ServiceAntiAffinity) getNodeClassificationByLabels(nodes []*v1.Node) (map[string]string, []string) { - labeledNodes := map[string]string{} - nonLabeledNodes := []string{} - for _, node := range nodes { - if labels.Set(node.Labels).Has(s.label) { - label := labels.Set(node.Labels).Get(s.label) - labeledNodes[node.Name] = label - } else { - nonLabeledNodes = append(nonLabeledNodes, node.Name) - } - } - return labeledNodes, nonLabeledNodes -} - -// CalculateAntiAffinityPriority spreads pods by minimizing the number of pods belonging to the same service -// on machines with the same value for a particular label. -// The label to be considered is provided to the struct (ServiceAntiAffinity). -func (s *ServiceAntiAffinity) CalculateAntiAffinityPriority(pod *v1.Pod, nodeNameToInfo map[string]*schedulercache.NodeInfo, nodes []*v1.Node) (schedulerapi.HostPriorityList, error) { - var nsServicePods []*v1.Pod - if services, err := s.serviceLister.GetPodServices(pod); err == nil && len(services) > 0 { - // just use the first service and get the other pods within the service - // TODO: a separate predicate can be created that tries to handle all services for the pod - selector := labels.SelectorFromSet(services[0].Spec.Selector) - pods, err := s.podLister.List(selector) - if err != nil { - return nil, err - } - // consider only the pods that belong to the same namespace - for _, nsPod := range pods { - if nsPod.Namespace == pod.Namespace { - nsServicePods = append(nsServicePods, nsPod) - } - } - } - - // separate out the nodes that have the label from the ones that don't - labeledNodes, nonLabeledNodes := s.getNodeClassificationByLabels(nodes) - podCounts := map[string]int{} - for _, pod := range nsServicePods { - label, exists := labeledNodes[pod.Spec.NodeName] - if !exists { - continue - } - podCounts[label]++ - } - numServicePods := len(nsServicePods) - result := []schedulerapi.HostPriority{} - //score int - scale of 0-maxPriority - // 0 being the lowest priority and maxPriority being the highest - for node := range labeledNodes { - // initializing to the default/max node score of maxPriority - fScore := float64(schedulerapi.MaxPriority) - if numServicePods > 0 { - fScore = float64(schedulerapi.MaxPriority) * (float64(numServicePods-podCounts[labeledNodes[node]]) / float64(numServicePods)) - } - result = append(result, schedulerapi.HostPriority{Host: node, Score: int(fScore)}) - } - // add the open nodes with a score of 0 - for _, node := range nonLabeledNodes { - result = append(result, schedulerapi.HostPriority{Host: node, Score: 0}) - } - return result, nil -} diff --git a/plugin/pkg/scheduler/algorithm/priorities/taint_toleration.go b/plugin/pkg/scheduler/algorithm/priorities/taint_toleration.go deleted file mode 100644 index a017531f2f2..00000000000 --- a/plugin/pkg/scheduler/algorithm/priorities/taint_toleration.go +++ /dev/null @@ -1,100 +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 priorities - -import ( - "fmt" - - "k8s.io/api/core/v1" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" - - "github.com/golang/glog" -) - -// CountIntolerableTaintsPreferNoSchedule gives the count of intolerable taints of a pod with effect PreferNoSchedule -func countIntolerableTaintsPreferNoSchedule(taints []v1.Taint, tolerations []v1.Toleration) (intolerableTaints int) { - for _, taint := range taints { - // check only on taints that have effect PreferNoSchedule - if taint.Effect != v1.TaintEffectPreferNoSchedule { - continue - } - - if !v1helper.TolerationsTolerateTaint(tolerations, &taint) { - intolerableTaints++ - } - } - return -} - -// getAllTolerationEffectPreferNoSchedule gets the list of all Tolerations with Effect PreferNoSchedule or with no effect. -func getAllTolerationPreferNoSchedule(tolerations []v1.Toleration) (tolerationList []v1.Toleration) { - for _, toleration := range tolerations { - // Empty effect means all effects which includes PreferNoSchedule, so we need to collect it as well. - if len(toleration.Effect) == 0 || toleration.Effect == v1.TaintEffectPreferNoSchedule { - tolerationList = append(tolerationList, toleration) - } - } - return -} - -// ComputeTaintTolerationPriorityMap prepares the priority list for all the nodes based on the number of intolerable taints on the node -func ComputeTaintTolerationPriorityMap(pod *v1.Pod, meta interface{}, nodeInfo *schedulercache.NodeInfo) (schedulerapi.HostPriority, error) { - node := nodeInfo.Node() - if node == nil { - return schedulerapi.HostPriority{}, fmt.Errorf("node not found") - } - // To hold all the tolerations with Effect PreferNoSchedule - var tolerationsPreferNoSchedule []v1.Toleration - if priorityMeta, ok := meta.(*priorityMetadata); ok { - tolerationsPreferNoSchedule = priorityMeta.podTolerations - - } else { - tolerationsPreferNoSchedule = getAllTolerationPreferNoSchedule(pod.Spec.Tolerations) - } - - return schedulerapi.HostPriority{ - Host: node.Name, - Score: countIntolerableTaintsPreferNoSchedule(node.Spec.Taints, tolerationsPreferNoSchedule), - }, nil -} - -// ComputeTaintTolerationPriorityReduce calculates the source of each node based on the number of intolerable taints on the node -func ComputeTaintTolerationPriorityReduce(pod *v1.Pod, meta interface{}, nodeNameToInfo map[string]*schedulercache.NodeInfo, result schedulerapi.HostPriorityList) error { - var maxCount int - for i := range result { - if result[i].Score > maxCount { - maxCount = result[i].Score - } - } - maxCountFloat := float64(maxCount) - - for i := range result { - fScore := float64(schedulerapi.MaxPriority) - if maxCountFloat > 0 { - fScore = (1.0 - float64(result[i].Score)/maxCountFloat) * float64(schedulerapi.MaxPriority) - } - if glog.V(10) { - // We explicitly don't do glog.V(10).Infof() to avoid computing all the parameters if this is - // not logged. There is visible performance gain from it. - glog.Infof("%v -> %v: Taint Toleration Priority, Score: (%d)", pod.Name, result[i].Host, int(fScore)) - } - result[i].Score = int(fScore) - } - return nil -} diff --git a/plugin/pkg/scheduler/algorithm/priorities/util/BUILD b/plugin/pkg/scheduler/algorithm/priorities/util/BUILD deleted file mode 100644 index 25374d69937..00000000000 --- a/plugin/pkg/scheduler/algorithm/priorities/util/BUILD +++ /dev/null @@ -1,47 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = ["topologies_test.go"], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/priorities/util", - library = ":go_default_library", - deps = [ - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - ], -) - -go_library( - name = "go_default_library", - srcs = [ - "non_zero.go", - "topologies.go", - "util.go", - ], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/priorities/util", - deps = [ - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/plugin/pkg/scheduler/algorithm/priorities/util/topologies_test.go b/plugin/pkg/scheduler/algorithm/priorities/util/topologies_test.go deleted file mode 100644 index ff101eb7d6b..00000000000 --- a/plugin/pkg/scheduler/algorithm/priorities/util/topologies_test.go +++ /dev/null @@ -1,124 +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 util - -import ( - "testing" - - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func TestNodesHaveSameTopologyKey(t *testing.T) { - tests := []struct { - name string - nodeA, nodeB *v1.Node - topologyKey string - expected bool - }{ - { - name: "nodeA{'a':'a'} vs. empty label in nodeB", - nodeA: &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "a": "a", - }, - }, - }, - nodeB: &v1.Node{}, - expected: false, - topologyKey: "a", - }, - { - name: "nodeA{'a':'a'} vs. nodeB{'a':'a'}", - nodeA: &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "a": "a", - }, - }, - }, - nodeB: &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "a": "a", - }, - }, - }, - expected: true, - topologyKey: "a", - }, - { - name: "nodeA{'a':''} vs. empty label in nodeB", - nodeA: &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "a": "", - }, - }, - }, - nodeB: &v1.Node{}, - expected: false, - topologyKey: "a", - }, - { - name: "nodeA{'a':''} vs. nodeB{'a':''}", - nodeA: &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "a": "", - }, - }, - }, - nodeB: &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "a": "", - }, - }, - }, - expected: true, - topologyKey: "a", - }, - { - name: "nodeA{'a':'a'} vs. nodeB{'a':'a'} by key{'b'}", - nodeA: &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "a": "a", - }, - }, - }, - nodeB: &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "a": "a", - }, - }, - }, - expected: false, - topologyKey: "b", - }, - } - - for _, test := range tests { - got := NodesHaveSameTopologyKey(test.nodeA, test.nodeB, test.topologyKey) - if test.expected != got { - t.Errorf("%v: expected %t, got %t", test.name, test.expected, got) - } - } -} diff --git a/plugin/pkg/scheduler/algorithm/types.go b/plugin/pkg/scheduler/algorithm/types.go deleted file mode 100644 index 8932b1f6bd9..00000000000 --- a/plugin/pkg/scheduler/algorithm/types.go +++ /dev/null @@ -1,164 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package algorithm - -import ( - apps "k8s.io/api/apps/v1beta1" - "k8s.io/api/core/v1" - extensions "k8s.io/api/extensions/v1beta1" - "k8s.io/apimachinery/pkg/labels" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" -) - -// FitPredicate is a function that indicates if a pod fits into an existing node. -// The failure information is given by the error. -type FitPredicate func(pod *v1.Pod, meta PredicateMetadata, nodeInfo *schedulercache.NodeInfo) (bool, []PredicateFailureReason, error) - -// PriorityMapFunction is a function that computes per-node results for a given node. -// TODO: Figure out the exact API of this method. -// TODO: Change interface{} to a specific type. -type PriorityMapFunction func(pod *v1.Pod, meta interface{}, nodeInfo *schedulercache.NodeInfo) (schedulerapi.HostPriority, error) - -// PriorityReduceFunction is a function that aggregated per-node results and computes -// final scores for all nodes. -// TODO: Figure out the exact API of this method. -// TODO: Change interface{} to a specific type. -type PriorityReduceFunction func(pod *v1.Pod, meta interface{}, nodeNameToInfo map[string]*schedulercache.NodeInfo, result schedulerapi.HostPriorityList) error - -// PredicateMetadataProducer is a function that computes predicate metadata for a given pod. -type PredicateMetadataProducer func(pod *v1.Pod, nodeNameToInfo map[string]*schedulercache.NodeInfo) PredicateMetadata - -// MetadataProducer is a function that computes metadata for a given pod. This -// is now used for only for priority functions. For predicates please use PredicateMetadataProducer. -// TODO: Rename this once we have a specific type for priority metadata producer. -type MetadataProducer func(pod *v1.Pod, nodeNameToInfo map[string]*schedulercache.NodeInfo) interface{} - -// DEPRECATED -// Use Map-Reduce pattern for priority functions. -type PriorityFunction func(pod *v1.Pod, nodeNameToInfo map[string]*schedulercache.NodeInfo, nodes []*v1.Node) (schedulerapi.HostPriorityList, error) - -type PriorityConfig struct { - Map PriorityMapFunction - Reduce PriorityReduceFunction - // TODO: Remove it after migrating all functions to - // Map-Reduce pattern. - Function PriorityFunction - Weight int -} - -// EmptyPredicateMetadataProducer returns a no-op MetadataProducer type. -func EmptyPredicateMetadataProducer(pod *v1.Pod, nodeNameToInfo map[string]*schedulercache.NodeInfo) PredicateMetadata { - return nil -} - -// EmptyMetadataProducer returns a no-op MetadataProducer type. -func EmptyMetadataProducer(pod *v1.Pod, nodeNameToInfo map[string]*schedulercache.NodeInfo) interface{} { - return nil -} - -type PredicateFailureReason interface { - GetReason() string -} - -type GetEquivalencePodFunc func(pod *v1.Pod) interface{} - -// NodeLister interface represents anything that can list nodes for a scheduler. -type NodeLister interface { - // We explicitly return []*v1.Node, instead of v1.NodeList, to avoid - // performing expensive copies that are unneeded. - List() ([]*v1.Node, error) -} - -// PodLister interface represents anything that can list pods for a scheduler. -type PodLister interface { - // We explicitly return []*v1.Pod, instead of v1.PodList, to avoid - // performing expensive copies that are unneeded. - List(labels.Selector) ([]*v1.Pod, error) - // This is similar to "List()", but the returned slice does not - // contain pods that don't pass `podFilter`. - FilteredList(podFilter schedulercache.PodFilter, selector labels.Selector) ([]*v1.Pod, error) -} - -// ServiceLister interface represents anything that can produce a list of services; the list is consumed by a scheduler. -type ServiceLister interface { - // Lists all the services - List(labels.Selector) ([]*v1.Service, error) - // Gets the services for the given pod - GetPodServices(*v1.Pod) ([]*v1.Service, error) -} - -// ControllerLister interface represents anything that can produce a list of ReplicationController; the list is consumed by a scheduler. -type ControllerLister interface { - // Lists all the replication controllers - List(labels.Selector) ([]*v1.ReplicationController, error) - // Gets the services for the given pod - GetPodControllers(*v1.Pod) ([]*v1.ReplicationController, error) -} - -// ReplicaSetLister interface represents anything that can produce a list of ReplicaSet; the list is consumed by a scheduler. -type ReplicaSetLister interface { - // Gets the replicasets for the given pod - GetPodReplicaSets(*v1.Pod) ([]*extensions.ReplicaSet, error) -} - -var _ ControllerLister = &EmptyControllerLister{} - -// EmptyControllerLister implements ControllerLister on []v1.ReplicationController returning empty data -type EmptyControllerLister struct{} - -// List returns nil -func (f EmptyControllerLister) List(labels.Selector) ([]*v1.ReplicationController, error) { - return nil, nil -} - -// GetPodControllers returns nil -func (f EmptyControllerLister) GetPodControllers(pod *v1.Pod) (controllers []*v1.ReplicationController, err error) { - return nil, nil -} - -var _ ReplicaSetLister = &EmptyReplicaSetLister{} - -// EmptyReplicaSetLister implements ReplicaSetLister on []extensions.ReplicaSet returning empty data -type EmptyReplicaSetLister struct{} - -// GetPodReplicaSets returns nil -func (f EmptyReplicaSetLister) GetPodReplicaSets(pod *v1.Pod) (rss []*extensions.ReplicaSet, err error) { - return nil, nil -} - -// StatefulSetLister interface represents anything that can produce a list of StatefulSet; the list is consumed by a scheduler. -type StatefulSetLister interface { - // Gets the StatefulSet for the given pod. - GetPodStatefulSets(*v1.Pod) ([]*apps.StatefulSet, error) -} - -var _ StatefulSetLister = &EmptyStatefulSetLister{} - -// EmptyStatefulSetLister implements StatefulSetLister on []apps.StatefulSet returning empty data. -type EmptyStatefulSetLister struct{} - -// GetPodStatefulSets of EmptyStatefulSetLister returns nil. -func (f EmptyStatefulSetLister) GetPodStatefulSets(pod *v1.Pod) (sss []*apps.StatefulSet, err error) { - return nil, nil -} - -type PredicateMetadata interface { - ShallowCopy() PredicateMetadata - AddPod(addedPod *v1.Pod, nodeInfo *schedulercache.NodeInfo) error - RemovePod(deletedPod *v1.Pod) error -} diff --git a/plugin/pkg/scheduler/algorithm/well_known_labels.go b/plugin/pkg/scheduler/algorithm/well_known_labels.go deleted file mode 100644 index 39e7cfc1d3c..00000000000 --- a/plugin/pkg/scheduler/algorithm/well_known_labels.go +++ /dev/null @@ -1,60 +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 algorithm - -const ( - // When feature-gate for TaintBasedEvictions=true flag is enabled, - // TaintNodeNotReady would be automatically added by node controller - // when node is not ready, and removed when node becomes ready. - TaintNodeNotReady = "node.kubernetes.io/not-ready" - - // DeprecatedTaintNodeNotReady is the deprecated version of TaintNodeNotReady. - // It is deprecated since 1.9 - DeprecatedTaintNodeNotReady = "node.alpha.kubernetes.io/notReady" - - // When feature-gate for TaintBasedEvictions=true flag is enabled, - // TaintNodeUnreachable would be automatically added by node controller - // when node becomes unreachable (corresponding to NodeReady status ConditionUnknown) - // and removed when node becomes reachable (NodeReady status ConditionTrue). - TaintNodeUnreachable = "node.alpha.kubernetes.io/unreachable" - - // When feature-gate for TaintBasedEvictions=true flag is enabled, - // TaintNodeOutOfDisk would be automatically added by node controller - // when node becomes out of disk, and removed when node has enough disk. - TaintNodeOutOfDisk = "node.kubernetes.io/out-of-disk" - - // When feature-gate for TaintBasedEvictions=true flag is enabled, - // TaintNodeMemoryPressure would be automatically added by node controller - // when node has memory pressure, and removed when node has enough memory. - TaintNodeMemoryPressure = "node.kubernetes.io/memory-pressure" - - // When feature-gate for TaintBasedEvictions=true flag is enabled, - // TaintNodeDiskPressure would be automatically added by node controller - // when node has disk pressure, and removed when node has enough disk. - TaintNodeDiskPressure = "node.kubernetes.io/disk-pressure" - - // When feature-gate for TaintBasedEvictions=true flag is enabled, - // TaintNodeNetworkUnavailable would be automatically added by node controller - // when node's network is unavailable, and removed when network becomes ready. - TaintNodeNetworkUnavailable = "node.kubernetes.io/network-unavailable" - - // When kubelet is started with the "external" cloud provider, then - // it sets this taint on a node to mark it as unusable, until a controller - // from the cloud-controller-manager intitializes this node, and then removes - // the taint - TaintExternalCloudProvider = "node.cloudprovider.kubernetes.io/uninitialized" -) diff --git a/plugin/pkg/scheduler/algorithmprovider/BUILD b/plugin/pkg/scheduler/algorithmprovider/BUILD deleted file mode 100644 index 904796b74aa..00000000000 --- a/plugin/pkg/scheduler/algorithmprovider/BUILD +++ /dev/null @@ -1,41 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = ["plugins.go"], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/algorithmprovider", - deps = ["//plugin/pkg/scheduler/algorithmprovider/defaults:go_default_library"], -) - -go_test( - name = "go_default_test", - srcs = ["plugins_test.go"], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/algorithmprovider", - library = ":go_default_library", - deps = [ - "//plugin/pkg/scheduler/factory:go_default_library", - "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//plugin/pkg/scheduler/algorithmprovider/defaults:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/plugin/pkg/scheduler/algorithmprovider/defaults/BUILD b/plugin/pkg/scheduler/algorithmprovider/defaults/BUILD deleted file mode 100644 index 872c5f72ec5..00000000000 --- a/plugin/pkg/scheduler/algorithmprovider/defaults/BUILD +++ /dev/null @@ -1,62 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = ["defaults.go"], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/algorithmprovider/defaults", - deps = [ - "//pkg/cloudprovider/providers/aws:go_default_library", - "//pkg/features:go_default_library", - "//plugin/pkg/scheduler/algorithm:go_default_library", - "//plugin/pkg/scheduler/algorithm/predicates:go_default_library", - "//plugin/pkg/scheduler/algorithm/priorities:go_default_library", - "//plugin/pkg/scheduler/core:go_default_library", - "//plugin/pkg/scheduler/factory:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = [ - "compatibility_test.go", - "defaults_test.go", - ], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/algorithmprovider/defaults", - library = ":go_default_library", - deps = [ - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", - "//plugin/pkg/scheduler/api:go_default_library", - "//plugin/pkg/scheduler/api/latest:go_default_library", - "//plugin/pkg/scheduler/factory:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - "//vendor/k8s.io/client-go/informers:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - "//vendor/k8s.io/client-go/util/testing:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go b/plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go deleted file mode 100644 index 0de465248b3..00000000000 --- a/plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go +++ /dev/null @@ -1,287 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package defaults - -import ( - "os" - "strconv" - - "k8s.io/apimachinery/pkg/util/sets" - utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/kubernetes/pkg/cloudprovider/providers/aws" - "k8s.io/kubernetes/pkg/features" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/priorities" - "k8s.io/kubernetes/plugin/pkg/scheduler/core" - "k8s.io/kubernetes/plugin/pkg/scheduler/factory" - - "github.com/golang/glog" -) - -const ( - // DefaultMaxGCEPDVolumes defines the maximum number of PD Volumes for GCE - // GCE instances can have up to 16 PD volumes attached. - DefaultMaxGCEPDVolumes = 16 - // DefaultMaxAzureDiskVolumes defines the maximum number of PD Volumes for Azure - // Larger Azure VMs can actually have much more disks attached. - // TODO We should determine the max based on VM size - DefaultMaxAzureDiskVolumes = 16 - // ClusterAutoscalerProvider defines the default autoscaler provider - ClusterAutoscalerProvider = "ClusterAutoscalerProvider" - // StatefulSetKind defines the name of 'StatefulSet' kind - StatefulSetKind = "StatefulSet" - // KubeMaxPDVols defines the maximum number of PD Volumes per kubelet - KubeMaxPDVols = "KUBE_MAX_PD_VOLS" -) - -func init() { - // Register functions that extract metadata used by predicates and priorities computations. - factory.RegisterPredicateMetadataProducerFactory( - func(args factory.PluginFactoryArgs) algorithm.PredicateMetadataProducer { - return predicates.NewPredicateMetadataFactory(args.PodLister) - }) - factory.RegisterPriorityMetadataProducerFactory( - func(args factory.PluginFactoryArgs) algorithm.MetadataProducer { - return priorities.PriorityMetadata - }) - - registerAlgorithmProvider(defaultPredicates(), defaultPriorities()) - - // IMPORTANT NOTES for predicate developers: - // We are using cached predicate result for pods belonging to the same equivalence class. - // So when implementing a new predicate, you are expected to check whether the result - // of your predicate function can be affected by related API object change (ADD/DELETE/UPDATE). - // If yes, you are expected to invalidate the cached predicate result for related API object change. - // For example: - // https://github.com/kubernetes/kubernetes/blob/36a218e/plugin/pkg/scheduler/factory/factory.go#L422 - - // Registers predicates and priorities that are not enabled by default, but user can pick when creating his - // own set of priorities/predicates. - - // PodFitsPorts has been replaced by PodFitsHostPorts for better user understanding. - // For backwards compatibility with 1.0, PodFitsPorts is registered as well. - factory.RegisterFitPredicate("PodFitsPorts", predicates.PodFitsHostPorts) - // Fit is defined based on the absence of port conflicts. - // This predicate is actually a default predicate, because it is invoked from - // predicates.GeneralPredicates() - factory.RegisterFitPredicate("PodFitsHostPorts", predicates.PodFitsHostPorts) - // Fit is determined by resource availability. - // This predicate is actually a default predicate, because it is invoked from - // predicates.GeneralPredicates() - factory.RegisterFitPredicate("PodFitsResources", predicates.PodFitsResources) - // Fit is determined by the presence of the Host parameter and a string match - // This predicate is actually a default predicate, because it is invoked from - // predicates.GeneralPredicates() - factory.RegisterFitPredicate("HostName", predicates.PodFitsHost) - // Fit is determined by node selector query. - factory.RegisterFitPredicate("MatchNodeSelector", predicates.PodMatchNodeSelector) - - // Use equivalence class to speed up predicates & priorities - factory.RegisterGetEquivalencePodFunction(predicates.GetEquivalencePod) - - // ServiceSpreadingPriority is a priority config factory that spreads pods by minimizing - // the number of pods (belonging to the same service) on the same node. - // Register the factory so that it's available, but do not include it as part of the default priorities - // Largely replaced by "SelectorSpreadPriority", but registered for backward compatibility with 1.0 - factory.RegisterPriorityConfigFactory( - "ServiceSpreadingPriority", - factory.PriorityConfigFactory{ - Function: func(args factory.PluginFactoryArgs) algorithm.PriorityFunction { - return priorities.NewSelectorSpreadPriority(args.ServiceLister, algorithm.EmptyControllerLister{}, algorithm.EmptyReplicaSetLister{}, algorithm.EmptyStatefulSetLister{}) - }, - Weight: 1, - }, - ) - - // EqualPriority is a prioritizer function that gives an equal weight of one to all nodes - // Register the priority function so that its available - // but do not include it as part of the default priorities - factory.RegisterPriorityFunction2("EqualPriority", core.EqualPriorityMap, nil, 1) - // ImageLocalityPriority prioritizes nodes based on locality of images requested by a pod. Nodes with larger size - // of already-installed packages required by the pod will be preferred over nodes with no already-installed - // packages required by the pod or a small total size of already-installed packages required by the pod. - factory.RegisterPriorityFunction2("ImageLocalityPriority", priorities.ImageLocalityPriorityMap, nil, 1) - // Optional, cluster-autoscaler friendly priority function - give used nodes higher priority. - factory.RegisterPriorityFunction2("MostRequestedPriority", priorities.MostRequestedPriorityMap, nil, 1) -} - -func defaultPredicates() sets.String { - return sets.NewString( - // Fit is determined by volume zone requirements. - factory.RegisterFitPredicateFactory( - "NoVolumeZoneConflict", - func(args factory.PluginFactoryArgs) algorithm.FitPredicate { - return predicates.NewVolumeZonePredicate(args.PVInfo, args.PVCInfo) - }, - ), - // Fit is determined by whether or not there would be too many AWS EBS volumes attached to the node - factory.RegisterFitPredicateFactory( - "MaxEBSVolumeCount", - func(args factory.PluginFactoryArgs) algorithm.FitPredicate { - // TODO: allow for generically parameterized scheduler predicates, because this is a bit ugly - maxVols := getMaxVols(aws.DefaultMaxEBSVolumes) - return predicates.NewMaxPDVolumeCountPredicate(predicates.EBSVolumeFilter, maxVols, args.PVInfo, args.PVCInfo) - }, - ), - // Fit is determined by whether or not there would be too many GCE PD volumes attached to the node - factory.RegisterFitPredicateFactory( - "MaxGCEPDVolumeCount", - func(args factory.PluginFactoryArgs) algorithm.FitPredicate { - // TODO: allow for generically parameterized scheduler predicates, because this is a bit ugly - maxVols := getMaxVols(DefaultMaxGCEPDVolumes) - return predicates.NewMaxPDVolumeCountPredicate(predicates.GCEPDVolumeFilter, maxVols, args.PVInfo, args.PVCInfo) - }, - ), - // Fit is determined by whether or not there would be too many Azure Disk volumes attached to the node - factory.RegisterFitPredicateFactory( - "MaxAzureDiskVolumeCount", - func(args factory.PluginFactoryArgs) algorithm.FitPredicate { - // TODO: allow for generically parameterized scheduler predicates, because this is a bit ugly - maxVols := getMaxVols(DefaultMaxAzureDiskVolumes) - return predicates.NewMaxPDVolumeCountPredicate(predicates.AzureDiskVolumeFilter, maxVols, args.PVInfo, args.PVCInfo) - }, - ), - // Fit is determined by inter-pod affinity. - factory.RegisterFitPredicateFactory( - predicates.MatchInterPodAffinity, - func(args factory.PluginFactoryArgs) algorithm.FitPredicate { - return predicates.NewPodAffinityPredicate(args.NodeInfo, args.PodLister) - }, - ), - - // Fit is determined by non-conflicting disk volumes. - factory.RegisterFitPredicate("NoDiskConflict", predicates.NoDiskConflict), - - // GeneralPredicates are the predicates that are enforced by all Kubernetes components - // (e.g. kubelet and all schedulers) - factory.RegisterFitPredicate("GeneralPredicates", predicates.GeneralPredicates), - - // Fit is determined by node memory pressure condition. - factory.RegisterFitPredicate("CheckNodeMemoryPressure", predicates.CheckNodeMemoryPressurePredicate), - - // Fit is determined by node disk pressure condition. - factory.RegisterFitPredicate("CheckNodeDiskPressure", predicates.CheckNodeDiskPressurePredicate), - - // Fit is determied by node condtions: not ready, network unavailable and out of disk. - factory.RegisterMandatoryFitPredicate("CheckNodeCondition", predicates.CheckNodeConditionPredicate), - - // Fit is determined based on whether a pod can tolerate all of the node's taints - factory.RegisterFitPredicate("PodToleratesNodeTaints", predicates.PodToleratesNodeTaints), - - // Fit is determined by volume zone requirements. - factory.RegisterFitPredicateFactory( - "NoVolumeNodeConflict", - func(args factory.PluginFactoryArgs) algorithm.FitPredicate { - return predicates.NewVolumeNodePredicate(args.PVInfo, args.PVCInfo, nil) - }, - ), - ) -} - -// ApplyFeatureGates applies algorithm by feature gates. -func ApplyFeatureGates() { - predSet := defaultPredicates() - - if utilfeature.DefaultFeatureGate.Enabled(features.TaintNodesByCondition) { - // Remove "CheckNodeCondition" predicate - factory.RemoveFitPredicate("CheckNodeCondition") - predSet.Delete("CheckNodeCondition") - - // Fit is determined based on whether a pod can tolerate all of the node's taints - predSet.Insert(factory.RegisterMandatoryFitPredicate("PodToleratesNodeTaints", predicates.PodToleratesNodeTaints)) - - glog.Warningf("TaintNodesByCondition is enabled, PodToleratesNodeTaints predicate is mandatory") - } - - registerAlgorithmProvider(predSet, defaultPriorities()) -} - -func registerAlgorithmProvider(predSet, priSet sets.String) { - // Registers algorithm providers. By default we use 'DefaultProvider', but user can specify one to be used - // by specifying flag. - factory.RegisterAlgorithmProvider(factory.DefaultProvider, predSet, priSet) - // Cluster autoscaler friendly scheduling algorithm. - factory.RegisterAlgorithmProvider(ClusterAutoscalerProvider, predSet, - copyAndReplace(priSet, "LeastRequestedPriority", "MostRequestedPriority")) -} - -func defaultPriorities() sets.String { - return sets.NewString( - // spreads pods by minimizing the number of pods (belonging to the same service or replication controller) on the same node. - factory.RegisterPriorityConfigFactory( - "SelectorSpreadPriority", - factory.PriorityConfigFactory{ - Function: func(args factory.PluginFactoryArgs) algorithm.PriorityFunction { - return priorities.NewSelectorSpreadPriority(args.ServiceLister, args.ControllerLister, args.ReplicaSetLister, args.StatefulSetLister) - }, - Weight: 1, - }, - ), - // pods should be placed in the same topological domain (e.g. same node, same rack, same zone, same power domain, etc.) - // as some other pods, or, conversely, should not be placed in the same topological domain as some other pods. - factory.RegisterPriorityConfigFactory( - "InterPodAffinityPriority", - factory.PriorityConfigFactory{ - Function: func(args factory.PluginFactoryArgs) algorithm.PriorityFunction { - return priorities.NewInterPodAffinityPriority(args.NodeInfo, args.NodeLister, args.PodLister, args.HardPodAffinitySymmetricWeight) - }, - Weight: 1, - }, - ), - - // Prioritize nodes by least requested utilization. - factory.RegisterPriorityFunction2("LeastRequestedPriority", priorities.LeastRequestedPriorityMap, nil, 1), - - // Prioritizes nodes to help achieve balanced resource usage - factory.RegisterPriorityFunction2("BalancedResourceAllocation", priorities.BalancedResourceAllocationMap, nil, 1), - - // Set this weight large enough to override all other priority functions. - // TODO: Figure out a better way to do this, maybe at same time as fixing #24720. - factory.RegisterPriorityFunction2("NodePreferAvoidPodsPriority", priorities.CalculateNodePreferAvoidPodsPriorityMap, nil, 10000), - - // Prioritizes nodes that have labels matching NodeAffinity - factory.RegisterPriorityFunction2("NodeAffinityPriority", priorities.CalculateNodeAffinityPriorityMap, priorities.CalculateNodeAffinityPriorityReduce, 1), - - // Prioritizes nodes that marked with taint which pod can tolerate. - factory.RegisterPriorityFunction2("TaintTolerationPriority", priorities.ComputeTaintTolerationPriorityMap, priorities.ComputeTaintTolerationPriorityReduce, 1), - ) -} - -// getMaxVols checks the max PD volumes environment variable, otherwise returning a default value -func getMaxVols(defaultVal int) int { - if rawMaxVols := os.Getenv(KubeMaxPDVols); rawMaxVols != "" { - if parsedMaxVols, err := strconv.Atoi(rawMaxVols); err != nil { - glog.Errorf("Unable to parse maximum PD volumes value, using default of %v: %v", defaultVal, err) - } else if parsedMaxVols <= 0 { - glog.Errorf("Maximum PD volumes must be a positive value, using default of %v", defaultVal) - } else { - return parsedMaxVols - } - } - - return defaultVal -} - -func copyAndReplace(set sets.String, replaceWhat, replaceWith string) sets.String { - result := sets.NewString(set.List()...) - if result.Has(replaceWhat) { - result.Delete(replaceWhat) - result.Insert(replaceWith) - } - return result -} diff --git a/plugin/pkg/scheduler/algorithmprovider/defaults/defaults_test.go b/plugin/pkg/scheduler/algorithmprovider/defaults/defaults_test.go deleted file mode 100644 index b1e889979bc..00000000000 --- a/plugin/pkg/scheduler/algorithmprovider/defaults/defaults_test.go +++ /dev/null @@ -1,127 +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 defaults - -import ( - "os" - "testing" - - "k8s.io/apimachinery/pkg/util/sets" -) - -func TestGetMaxVols(t *testing.T) { - previousValue := os.Getenv(KubeMaxPDVols) - defaultValue := 39 - - tests := []struct { - rawMaxVols string - expected int - test string - }{ - { - rawMaxVols: "invalid", - expected: defaultValue, - test: "Unable to parse maximum PD volumes value, using default value", - }, - { - rawMaxVols: "-2", - expected: defaultValue, - test: "Maximum PD volumes must be a positive value, using default value", - }, - { - rawMaxVols: "40", - expected: 40, - test: "Parse maximum PD volumes value from env", - }, - } - - for _, test := range tests { - os.Setenv(KubeMaxPDVols, test.rawMaxVols) - result := getMaxVols(defaultValue) - if result != test.expected { - t.Errorf("%s: expected %v got %v", test.test, test.expected, result) - } - } - - os.Unsetenv(KubeMaxPDVols) - if previousValue != "" { - os.Setenv(KubeMaxPDVols, previousValue) - } -} - -func TestCopyAndReplace(t *testing.T) { - testCases := []struct { - set sets.String - replaceWhat string - replaceWith string - expected sets.String - }{ - { - set: sets.String{"A": sets.Empty{}, "B": sets.Empty{}}, - replaceWhat: "A", - replaceWith: "C", - expected: sets.String{"B": sets.Empty{}, "C": sets.Empty{}}, - }, - { - set: sets.String{"A": sets.Empty{}, "B": sets.Empty{}}, - replaceWhat: "D", - replaceWith: "C", - expected: sets.String{"A": sets.Empty{}, "B": sets.Empty{}}, - }, - } - for _, testCase := range testCases { - result := copyAndReplace(testCase.set, testCase.replaceWhat, testCase.replaceWith) - if !result.Equal(testCase.expected) { - t.Errorf("expected %v got %v", testCase.expected, result) - } - } -} - -func TestDefaultPriorities(t *testing.T) { - result := sets.NewString( - "SelectorSpreadPriority", - "InterPodAffinityPriority", - "LeastRequestedPriority", - "BalancedResourceAllocation", - "NodePreferAvoidPodsPriority", - "NodeAffinityPriority", - "TaintTolerationPriority") - if expected := defaultPriorities(); !result.Equal(expected) { - t.Errorf("expected %v got %v", expected, result) - } -} - -func TestDefaultPredicates(t *testing.T) { - result := sets.NewString( - "NoVolumeZoneConflict", - "MaxEBSVolumeCount", - "MaxGCEPDVolumeCount", - "MaxAzureDiskVolumeCount", - "MatchInterPodAffinity", - "NoDiskConflict", - "GeneralPredicates", - "CheckNodeMemoryPressure", - "CheckNodeDiskPressure", - "NoVolumeNodeConflict", - "CheckNodeCondition", - "PodToleratesNodeTaints", - ) - - if expected := defaultPredicates(); !result.Equal(expected) { - t.Errorf("expected %v got %v", expected, result) - } -} diff --git a/plugin/pkg/scheduler/algorithmprovider/plugins.go b/plugin/pkg/scheduler/algorithmprovider/plugins.go deleted file mode 100644 index f357a12d5a9..00000000000 --- a/plugin/pkg/scheduler/algorithmprovider/plugins.go +++ /dev/null @@ -1,26 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package algorithmprovider - -import ( - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithmprovider/defaults" -) - -// ApplyFeatureGates applies algorithm by feature gates. -func ApplyFeatureGates() { - defaults.ApplyFeatureGates() -} diff --git a/plugin/pkg/scheduler/algorithmprovider/plugins_test.go b/plugin/pkg/scheduler/algorithmprovider/plugins_test.go deleted file mode 100644 index 4044bfa52cd..00000000000 --- a/plugin/pkg/scheduler/algorithmprovider/plugins_test.go +++ /dev/null @@ -1,109 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package algorithmprovider - -import ( - "testing" - - utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/kubernetes/plugin/pkg/scheduler/factory" -) - -var ( - algorithmProviderNames = []string{ - factory.DefaultProvider, - } -) - -func TestDefaultConfigExists(t *testing.T) { - p, err := factory.GetAlgorithmProvider(factory.DefaultProvider) - if err != nil { - t.Errorf("error retrieving default provider: %v", err) - } - if p == nil { - t.Error("algorithm provider config should not be nil") - } - if len(p.FitPredicateKeys) == 0 { - t.Error("default algorithm provider shouldn't have 0 fit predicates") - } -} - -func TestAlgorithmProviders(t *testing.T) { - for _, pn := range algorithmProviderNames { - p, err := factory.GetAlgorithmProvider(pn) - if err != nil { - t.Errorf("error retrieving '%s' provider: %v", pn, err) - break - } - if len(p.PriorityFunctionKeys) == 0 { - t.Errorf("%s algorithm provider shouldn't have 0 priority functions", pn) - } - for _, pf := range p.PriorityFunctionKeys.List() { - if !factory.IsPriorityFunctionRegistered(pf) { - t.Errorf("priority function %s is not registered but is used in the %s algorithm provider", pf, pn) - } - } - for _, fp := range p.FitPredicateKeys.List() { - if !factory.IsFitPredicateRegistered(fp) { - t.Errorf("fit predicate %s is not registered but is used in the %s algorithm provider", fp, pn) - } - } - } -} - -func TestApplyFeatureGates(t *testing.T) { - for _, pn := range algorithmProviderNames { - p, err := factory.GetAlgorithmProvider(pn) - if err != nil { - t.Errorf("Error retrieving '%s' provider: %v", pn, err) - break - } - - if !p.FitPredicateKeys.Has("CheckNodeCondition") { - t.Errorf("Failed to find predicate: 'CheckNodeCondition'") - break - } - - if !p.FitPredicateKeys.Has("PodToleratesNodeTaints") { - t.Errorf("Failed to find predicate: 'PodToleratesNodeTaints'") - break - } - } - - // Apply features for algorithm providers. - utilfeature.DefaultFeatureGate.Set("TaintNodesByCondition=True") - - ApplyFeatureGates() - - for _, pn := range algorithmProviderNames { - p, err := factory.GetAlgorithmProvider(pn) - if err != nil { - t.Errorf("Error retrieving '%s' provider: %v", pn, err) - break - } - - if !p.FitPredicateKeys.Has("PodToleratesNodeTaints") { - t.Errorf("Failed to find predicate: 'PodToleratesNodeTaints'") - break - } - - if p.FitPredicateKeys.Has("CheckNodeCondition") { - t.Errorf("Unexpected predicate: 'CheckNodeCondition'") - break - } - } -} diff --git a/plugin/pkg/scheduler/api/BUILD b/plugin/pkg/scheduler/api/BUILD deleted file mode 100644 index c1284a9e630..00000000000 --- a/plugin/pkg/scheduler/api/BUILD +++ /dev/null @@ -1,44 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "doc.go", - "register.go", - "types.go", - "zz_generated.deepcopy.go", - ], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/api", - deps = [ - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//plugin/pkg/scheduler/api/latest:all-srcs", - "//plugin/pkg/scheduler/api/v1:all-srcs", - "//plugin/pkg/scheduler/api/validation:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/plugin/pkg/scheduler/api/doc.go b/plugin/pkg/scheduler/api/doc.go deleted file mode 100644 index 184de2edf31..00000000000 --- a/plugin/pkg/scheduler/api/doc.go +++ /dev/null @@ -1,20 +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,register - -// Package api contains scheduler plugin API objects. -package api // import "k8s.io/kubernetes/plugin/pkg/scheduler/api" diff --git a/plugin/pkg/scheduler/api/latest/BUILD b/plugin/pkg/scheduler/api/latest/BUILD deleted file mode 100644 index ee0f0ffa1c3..00000000000 --- a/plugin/pkg/scheduler/api/latest/BUILD +++ /dev/null @@ -1,33 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["latest.go"], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/api/latest", - deps = [ - "//plugin/pkg/scheduler/api:go_default_library", - "//plugin/pkg/scheduler/api/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/serializer/json:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/serializer/versioning:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/plugin/pkg/scheduler/api/types.go b/plugin/pkg/scheduler/api/types.go deleted file mode 100644 index 0a8baf878e9..00000000000 --- a/plugin/pkg/scheduler/api/types.go +++ /dev/null @@ -1,227 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package api - -import ( - "time" - - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - restclient "k8s.io/client-go/rest" -) - -const ( - MaxUint = ^uint(0) - MaxInt = int(MaxUint >> 1) - MaxTotalPriority = MaxInt - MaxPriority = 10 - MaxWeight = MaxInt / MaxPriority -) - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -type Policy struct { - metav1.TypeMeta - // Holds the information to configure the fit predicate functions - Predicates []PredicatePolicy - // Holds the information to configure the priority functions - Priorities []PriorityPolicy - // Holds the information to communicate with the extender(s) - ExtenderConfigs []ExtenderConfig - // RequiredDuringScheduling affinity is not symmetric, but there is an implicit PreferredDuringScheduling affinity rule - // corresponding to every RequiredDuringScheduling affinity rule. - // HardPodAffinitySymmetricWeight represents the weight of implicit PreferredDuringScheduling affinity rule, in the range 1-100. - HardPodAffinitySymmetricWeight int -} - -type PredicatePolicy struct { - // Identifier of the predicate policy - // For a custom predicate, the name can be user-defined - // For the Kubernetes provided predicates, the name is the identifier of the pre-defined predicate - Name string - // Holds the parameters to configure the given predicate - Argument *PredicateArgument -} - -type PriorityPolicy struct { - // Identifier of the priority policy - // For a custom priority, the name can be user-defined - // For the Kubernetes provided priority functions, the name is the identifier of the pre-defined priority function - Name string - // The numeric multiplier for the node scores that the priority function generates - // The weight should be a positive integer - Weight int - // Holds the parameters to configure the given priority function - Argument *PriorityArgument -} - -// Represents the arguments that the different types of predicates take -// Only one of its members may be specified -type PredicateArgument struct { - // The predicate that provides affinity for pods belonging to a service - // It uses a label to identify nodes that belong to the same "group" - ServiceAffinity *ServiceAffinity - // The predicate that checks whether a particular node has a certain label - // defined or not, regardless of value - LabelsPresence *LabelsPresence -} - -// Represents the arguments that the different types of priorities take. -// Only one of its members may be specified -type PriorityArgument struct { - // The priority function that ensures a good spread (anti-affinity) for pods belonging to a service - // It uses a label to identify nodes that belong to the same "group" - ServiceAntiAffinity *ServiceAntiAffinity - // The priority function that checks whether a particular node has a certain label - // defined or not, regardless of value - LabelPreference *LabelPreference -} - -// Holds the parameters that are used to configure the corresponding predicate -type ServiceAffinity struct { - // The list of labels that identify node "groups" - // All of the labels should match for the node to be considered a fit for hosting the pod - Labels []string -} - -// Holds the parameters that are used to configure the corresponding predicate -type LabelsPresence struct { - // The list of labels that identify node "groups" - // All of the labels should be either present (or absent) for the node to be considered a fit for hosting the pod - Labels []string - // The boolean flag that indicates whether the labels should be present or absent from the node - Presence bool -} - -// Holds the parameters that are used to configure the corresponding priority function -type ServiceAntiAffinity struct { - // Used to identify node "groups" - Label string -} - -// Holds the parameters that are used to configure the corresponding priority function -type LabelPreference struct { - // Used to identify node "groups" - Label string - // This is a boolean flag - // If true, higher priority is given to nodes that have the label - // If false, higher priority is given to nodes that do not have the label - Presence bool -} - -// Holds the parameters used to communicate with the extender. If a verb is unspecified/empty, -// it is assumed that the extender chose not to provide that extension. -type ExtenderConfig struct { - // URLPrefix at which the extender is available - URLPrefix string - // Verb for the filter call, empty if not supported. This verb is appended to the URLPrefix when issuing the filter call to extender. - FilterVerb string - // Verb for the prioritize call, empty if not supported. This verb is appended to the URLPrefix when issuing the prioritize call to extender. - PrioritizeVerb string - // The numeric multiplier for the node scores that the prioritize call generates. - // The weight should be a positive integer - Weight int - // Verb for the bind call, empty if not supported. This verb is appended to the URLPrefix when issuing the bind call to extender. - // If this method is implemented by the extender, it is the extender's responsibility to bind the pod to apiserver. Only one extender - // can implement this function. - BindVerb string - // EnableHttps specifies whether https should be used to communicate with the extender - EnableHttps bool - // TLSConfig specifies the transport layer security config - TLSConfig *restclient.TLSClientConfig - // HTTPTimeout specifies the timeout duration for a call to the extender. Filter timeout fails the scheduling of the pod. Prioritize - // timeout is ignored, k8s/other extenders priorities are used to select the node. - HTTPTimeout time.Duration - // NodeCacheCapable specifies that the extender is capable of caching node information, - // so the scheduler should only send minimal information about the eligible nodes - // assuming that the extender already cached full details of all nodes in the cluster - NodeCacheCapable bool -} - -// ExtenderArgs represents the arguments needed by the extender to filter/prioritize -// nodes for a pod. -type ExtenderArgs struct { - // Pod being scheduled - Pod v1.Pod - // List of candidate nodes where the pod can be scheduled; to be populated - // only if ExtenderConfig.NodeCacheCapable == false - Nodes *v1.NodeList - // List of candidate node names where the pod can be scheduled; to be - // populated only if ExtenderConfig.NodeCacheCapable == true - NodeNames *[]string -} - -// FailedNodesMap represents the filtered out nodes, with node names and failure messages -type FailedNodesMap map[string]string - -// ExtenderFilterResult represents the results of a filter call to an extender -type ExtenderFilterResult struct { - // Filtered set of nodes where the pod can be scheduled; to be populated - // only if ExtenderConfig.NodeCacheCapable == false - Nodes *v1.NodeList - // Filtered set of nodes where the pod can be scheduled; to be populated - // only if ExtenderConfig.NodeCacheCapable == true - NodeNames *[]string - // Filtered out nodes where the pod can't be scheduled and the failure messages - FailedNodes FailedNodesMap - // Error message indicating failure - Error string -} - -// ExtenderBindingArgs represents the arguments to an extender for binding a pod to a node. -type ExtenderBindingArgs struct { - // PodName is the name of the pod being bound - PodName string - // PodNamespace is the namespace of the pod being bound - PodNamespace string - // PodUID is the UID of the pod being bound - PodUID types.UID - // Node selected by the scheduler - Node string -} - -// ExtenderBindingResult represents the result of binding of a pod to a node from an extender. -type ExtenderBindingResult struct { - // Error message indicating failure - Error string -} - -// HostPriority represents the priority of scheduling to a particular host, higher priority is better. -type HostPriority struct { - // Name of the host - Host string - // Score associated with the host - Score int -} - -type HostPriorityList []HostPriority - -func (h HostPriorityList) Len() int { - return len(h) -} - -func (h HostPriorityList) Less(i, j int) bool { - if h[i].Score == h[j].Score { - return h[i].Host < h[j].Host - } - return h[i].Score < h[j].Score -} - -func (h HostPriorityList) Swap(i, j int) { - h[i], h[j] = h[j], h[i] -} diff --git a/plugin/pkg/scheduler/api/v1/BUILD b/plugin/pkg/scheduler/api/v1/BUILD deleted file mode 100644 index 66b8c2fc706..00000000000 --- a/plugin/pkg/scheduler/api/v1/BUILD +++ /dev/null @@ -1,40 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "doc.go", - "register.go", - "types.go", - "zz_generated.deepcopy.go", - ], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/api/v1", - deps = [ - "//plugin/pkg/scheduler/api:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/plugin/pkg/scheduler/api/v1/doc.go b/plugin/pkg/scheduler/api/v1/doc.go deleted file mode 100644 index 49d3063b42f..00000000000 --- a/plugin/pkg/scheduler/api/v1/doc.go +++ /dev/null @@ -1,20 +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,register - -// Package v1 contains scheduler plugin API objects. -package v1 // import "k8s.io/kubernetes/plugin/pkg/scheduler/api/v1" diff --git a/plugin/pkg/scheduler/api/v1/register.go b/plugin/pkg/scheduler/api/v1/register.go deleted file mode 100644 index 406c2e03f57..00000000000 --- a/plugin/pkg/scheduler/api/v1/register.go +++ /dev/null @@ -1,56 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES 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" - "k8s.io/kubernetes/plugin/pkg/scheduler/api" -) - -// SchemeGroupVersion is group version used to register these objects -// TODO this should be in the "scheduler" group -var SchemeGroupVersion = schema.GroupVersion{Group: "", Version: "v1"} - -func init() { - if err := addKnownTypes(api.Scheme); err != nil { - // Programmer error. - panic(err) - } -} - -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, - &Policy{}, - ) - return nil -} diff --git a/plugin/pkg/scheduler/api/v1/types.go b/plugin/pkg/scheduler/api/v1/types.go deleted file mode 100644 index 4f1b2369d22..00000000000 --- a/plugin/pkg/scheduler/api/v1/types.go +++ /dev/null @@ -1,219 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES 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 ( - "time" - - apiv1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - restclient "k8s.io/client-go/rest" -) - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -type Policy struct { - metav1.TypeMeta `json:",inline"` - // Holds the information to configure the fit predicate functions - Predicates []PredicatePolicy `json:"predicates"` - // Holds the information to configure the priority functions - Priorities []PriorityPolicy `json:"priorities"` - // Holds the information to communicate with the extender(s) - ExtenderConfigs []ExtenderConfig `json:"extenders"` - // RequiredDuringScheduling affinity is not symmetric, but there is an implicit PreferredDuringScheduling affinity rule - // corresponding to every RequiredDuringScheduling affinity rule. - // HardPodAffinitySymmetricWeight represents the weight of implicit PreferredDuringScheduling affinity rule, in the range 1-100. - HardPodAffinitySymmetricWeight int `json:"hardPodAffinitySymmetricWeight"` -} - -type PredicatePolicy struct { - // Identifier of the predicate policy - // For a custom predicate, the name can be user-defined - // For the Kubernetes provided predicates, the name is the identifier of the pre-defined predicate - Name string `json:"name"` - // Holds the parameters to configure the given predicate - Argument *PredicateArgument `json:"argument"` -} - -type PriorityPolicy struct { - // Identifier of the priority policy - // For a custom priority, the name can be user-defined - // For the Kubernetes provided priority functions, the name is the identifier of the pre-defined priority function - Name string `json:"name"` - // The numeric multiplier for the node scores that the priority function generates - // The weight should be non-zero and can be a positive or a negative integer - Weight int `json:"weight"` - // Holds the parameters to configure the given priority function - Argument *PriorityArgument `json:"argument"` -} - -// Represents the arguments that the different types of predicates take -// Only one of its members may be specified -type PredicateArgument struct { - // The predicate that provides affinity for pods belonging to a service - // It uses a label to identify nodes that belong to the same "group" - ServiceAffinity *ServiceAffinity `json:"serviceAffinity"` - // The predicate that checks whether a particular node has a certain label - // defined or not, regardless of value - LabelsPresence *LabelsPresence `json:"labelsPresence"` -} - -// Represents the arguments that the different types of priorities take. -// Only one of its members may be specified -type PriorityArgument struct { - // The priority function that ensures a good spread (anti-affinity) for pods belonging to a service - // It uses a label to identify nodes that belong to the same "group" - ServiceAntiAffinity *ServiceAntiAffinity `json:"serviceAntiAffinity"` - // The priority function that checks whether a particular node has a certain label - // defined or not, regardless of value - LabelPreference *LabelPreference `json:"labelPreference"` -} - -// Holds the parameters that are used to configure the corresponding predicate -type ServiceAffinity struct { - // The list of labels that identify node "groups" - // All of the labels should match for the node to be considered a fit for hosting the pod - Labels []string `json:"labels"` -} - -// Holds the parameters that are used to configure the corresponding predicate -type LabelsPresence struct { - // The list of labels that identify node "groups" - // All of the labels should be either present (or absent) for the node to be considered a fit for hosting the pod - Labels []string `json:"labels"` - // The boolean flag that indicates whether the labels should be present or absent from the node - Presence bool `json:"presence"` -} - -// Holds the parameters that are used to configure the corresponding priority function -type ServiceAntiAffinity struct { - // Used to identify node "groups" - Label string `json:"label"` -} - -// Holds the parameters that are used to configure the corresponding priority function -type LabelPreference struct { - // Used to identify node "groups" - Label string `json:"label"` - // This is a boolean flag - // If true, higher priority is given to nodes that have the label - // If false, higher priority is given to nodes that do not have the label - Presence bool `json:"presence"` -} - -// Holds the parameters used to communicate with the extender. If a verb is unspecified/empty, -// it is assumed that the extender chose not to provide that extension. -type ExtenderConfig struct { - // URLPrefix at which the extender is available - URLPrefix string `json:"urlPrefix"` - // Verb for the filter call, empty if not supported. This verb is appended to the URLPrefix when issuing the filter call to extender. - FilterVerb string `json:"filterVerb,omitempty"` - // Verb for the prioritize call, empty if not supported. This verb is appended to the URLPrefix when issuing the prioritize call to extender. - PrioritizeVerb string `json:"prioritizeVerb,omitempty"` - // The numeric multiplier for the node scores that the prioritize call generates. - // The weight should be a positive integer - Weight int `json:"weight,omitempty"` - // Verb for the bind call, empty if not supported. This verb is appended to the URLPrefix when issuing the bind call to extender. - // If this method is implemented by the extender, it is the extender's responsibility to bind the pod to apiserver. Only one extender - // can implement this function. - BindVerb string - // EnableHttps specifies whether https should be used to communicate with the extender - EnableHttps bool `json:"enableHttps,omitempty"` - // TLSConfig specifies the transport layer security config - TLSConfig *restclient.TLSClientConfig `json:"tlsConfig,omitempty"` - // HTTPTimeout specifies the timeout duration for a call to the extender. Filter timeout fails the scheduling of the pod. Prioritize - // timeout is ignored, k8s/other extenders priorities are used to select the node. - HTTPTimeout time.Duration `json:"httpTimeout,omitempty"` - // NodeCacheCapable specifies that the extender is capable of caching node information, - // so the scheduler should only send minimal information about the eligible nodes - // assuming that the extender already cached full details of all nodes in the cluster - NodeCacheCapable bool `json:"nodeCacheCapable,omitempty"` -} - -// ExtenderArgs represents the arguments needed by the extender to filter/prioritize -// nodes for a pod. -type ExtenderArgs struct { - // Pod being scheduled - Pod apiv1.Pod `json:"pod"` - // List of candidate nodes where the pod can be scheduled; to be populated - // only if ExtenderConfig.NodeCacheCapable == false - Nodes *apiv1.NodeList `json:"nodes,omitempty"` - // List of candidate node names where the pod can be scheduled; to be - // populated only if ExtenderConfig.NodeCacheCapable == true - NodeNames *[]string `json:"nodenames,omitempty"` -} - -// FailedNodesMap represents the filtered out nodes, with node names and failure messages -type FailedNodesMap map[string]string - -// ExtenderFilterResult represents the results of a filter call to an extender -type ExtenderFilterResult struct { - // Filtered set of nodes where the pod can be scheduled; to be populated - // only if ExtenderConfig.NodeCacheCapable == false - Nodes *apiv1.NodeList `json:"nodes,omitempty"` - // Filtered set of nodes where the pod can be scheduled; to be populated - // only if ExtenderConfig.NodeCacheCapable == true - NodeNames *[]string `json:"nodenames,omitempty"` - // Filtered out nodes where the pod can't be scheduled and the failure messages - FailedNodes FailedNodesMap `json:"failedNodes,omitempty"` - // Error message indicating failure - Error string `json:"error,omitempty"` -} - -// ExtenderBindingArgs represents the arguments to an extender for binding a pod to a node. -type ExtenderBindingArgs struct { - // PodName is the name of the pod being bound - PodName string - // PodNamespace is the namespace of the pod being bound - PodNamespace string - // PodUID is the UID of the pod being bound - PodUID types.UID - // Node selected by the scheduler - Node string -} - -// ExtenderBindingResult represents the result of binding of a pod to a node from an extender. -type ExtenderBindingResult struct { - // Error message indicating failure - Error string -} - -// HostPriority represents the priority of scheduling to a particular host, higher priority is better. -type HostPriority struct { - // Name of the host - Host string `json:"host"` - // Score associated with the host - Score int `json:"score"` -} - -type HostPriorityList []HostPriority - -func (h HostPriorityList) Len() int { - return len(h) -} - -func (h HostPriorityList) Less(i, j int) bool { - if h[i].Score == h[j].Score { - return h[i].Host < h[j].Host - } - return h[i].Score < h[j].Score -} - -func (h HostPriorityList) Swap(i, j int) { - h[i], h[j] = h[j], h[i] -} diff --git a/plugin/pkg/scheduler/api/v1/zz_generated.deepcopy.go b/plugin/pkg/scheduler/api/v1/zz_generated.deepcopy.go deleted file mode 100644 index 577a3c7f3e4..00000000000 --- a/plugin/pkg/scheduler/api/v1/zz_generated.deepcopy.go +++ /dev/null @@ -1,498 +0,0 @@ -// +build !ignore_autogenerated - -/* -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. -*/ - -// This file was autogenerated by deepcopy-gen. Do not edit it manually! - -package v1 - -import ( - core_v1 "k8s.io/api/core/v1" - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" - rest "k8s.io/client-go/rest" - reflect "reflect" -) - -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExtenderArgs).DeepCopyInto(out.(*ExtenderArgs)) - return nil - }, InType: reflect.TypeOf(&ExtenderArgs{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExtenderBindingArgs).DeepCopyInto(out.(*ExtenderBindingArgs)) - return nil - }, InType: reflect.TypeOf(&ExtenderBindingArgs{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExtenderBindingResult).DeepCopyInto(out.(*ExtenderBindingResult)) - return nil - }, InType: reflect.TypeOf(&ExtenderBindingResult{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExtenderConfig).DeepCopyInto(out.(*ExtenderConfig)) - return nil - }, InType: reflect.TypeOf(&ExtenderConfig{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExtenderFilterResult).DeepCopyInto(out.(*ExtenderFilterResult)) - return nil - }, InType: reflect.TypeOf(&ExtenderFilterResult{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HostPriority).DeepCopyInto(out.(*HostPriority)) - return nil - }, InType: reflect.TypeOf(&HostPriority{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LabelPreference).DeepCopyInto(out.(*LabelPreference)) - return nil - }, InType: reflect.TypeOf(&LabelPreference{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LabelsPresence).DeepCopyInto(out.(*LabelsPresence)) - return nil - }, InType: reflect.TypeOf(&LabelsPresence{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Policy).DeepCopyInto(out.(*Policy)) - return nil - }, InType: reflect.TypeOf(&Policy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PredicateArgument).DeepCopyInto(out.(*PredicateArgument)) - return nil - }, InType: reflect.TypeOf(&PredicateArgument{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PredicatePolicy).DeepCopyInto(out.(*PredicatePolicy)) - return nil - }, InType: reflect.TypeOf(&PredicatePolicy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PriorityArgument).DeepCopyInto(out.(*PriorityArgument)) - return nil - }, InType: reflect.TypeOf(&PriorityArgument{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PriorityPolicy).DeepCopyInto(out.(*PriorityPolicy)) - return nil - }, InType: reflect.TypeOf(&PriorityPolicy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ServiceAffinity).DeepCopyInto(out.(*ServiceAffinity)) - return nil - }, InType: reflect.TypeOf(&ServiceAffinity{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ServiceAntiAffinity).DeepCopyInto(out.(*ServiceAntiAffinity)) - return nil - }, InType: reflect.TypeOf(&ServiceAntiAffinity{})}, - ) -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExtenderArgs) DeepCopyInto(out *ExtenderArgs) { - *out = *in - in.Pod.DeepCopyInto(&out.Pod) - if in.Nodes != nil { - in, out := &in.Nodes, &out.Nodes - if *in == nil { - *out = nil - } else { - *out = new(core_v1.NodeList) - (*in).DeepCopyInto(*out) - } - } - if in.NodeNames != nil { - in, out := &in.NodeNames, &out.NodeNames - if *in == nil { - *out = nil - } else { - *out = new([]string) - if **in != nil { - in, out := *in, *out - *out = make([]string, len(*in)) - copy(*out, *in) - } - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtenderArgs. -func (in *ExtenderArgs) DeepCopy() *ExtenderArgs { - if in == nil { - return nil - } - out := new(ExtenderArgs) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExtenderBindingArgs) DeepCopyInto(out *ExtenderBindingArgs) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtenderBindingArgs. -func (in *ExtenderBindingArgs) DeepCopy() *ExtenderBindingArgs { - if in == nil { - return nil - } - out := new(ExtenderBindingArgs) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExtenderBindingResult) DeepCopyInto(out *ExtenderBindingResult) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtenderBindingResult. -func (in *ExtenderBindingResult) DeepCopy() *ExtenderBindingResult { - if in == nil { - return nil - } - out := new(ExtenderBindingResult) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExtenderConfig) DeepCopyInto(out *ExtenderConfig) { - *out = *in - if in.TLSConfig != nil { - in, out := &in.TLSConfig, &out.TLSConfig - if *in == nil { - *out = nil - } else { - *out = new(rest.TLSClientConfig) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtenderConfig. -func (in *ExtenderConfig) DeepCopy() *ExtenderConfig { - if in == nil { - return nil - } - out := new(ExtenderConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExtenderFilterResult) DeepCopyInto(out *ExtenderFilterResult) { - *out = *in - if in.Nodes != nil { - in, out := &in.Nodes, &out.Nodes - if *in == nil { - *out = nil - } else { - *out = new(core_v1.NodeList) - (*in).DeepCopyInto(*out) - } - } - if in.NodeNames != nil { - in, out := &in.NodeNames, &out.NodeNames - if *in == nil { - *out = nil - } else { - *out = new([]string) - if **in != nil { - in, out := *in, *out - *out = make([]string, len(*in)) - copy(*out, *in) - } - } - } - if in.FailedNodes != nil { - in, out := &in.FailedNodes, &out.FailedNodes - *out = make(FailedNodesMap, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtenderFilterResult. -func (in *ExtenderFilterResult) DeepCopy() *ExtenderFilterResult { - if in == nil { - return nil - } - out := new(ExtenderFilterResult) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HostPriority) DeepCopyInto(out *HostPriority) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostPriority. -func (in *HostPriority) DeepCopy() *HostPriority { - if in == nil { - return nil - } - out := new(HostPriority) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LabelPreference) DeepCopyInto(out *LabelPreference) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LabelPreference. -func (in *LabelPreference) DeepCopy() *LabelPreference { - if in == nil { - return nil - } - out := new(LabelPreference) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LabelsPresence) DeepCopyInto(out *LabelsPresence) { - *out = *in - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LabelsPresence. -func (in *LabelsPresence) DeepCopy() *LabelsPresence { - if in == nil { - return nil - } - out := new(LabelsPresence) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Policy) DeepCopyInto(out *Policy) { - *out = *in - out.TypeMeta = in.TypeMeta - if in.Predicates != nil { - in, out := &in.Predicates, &out.Predicates - *out = make([]PredicatePolicy, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Priorities != nil { - in, out := &in.Priorities, &out.Priorities - *out = make([]PriorityPolicy, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.ExtenderConfigs != nil { - in, out := &in.ExtenderConfigs, &out.ExtenderConfigs - *out = make([]ExtenderConfig, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Policy. -func (in *Policy) DeepCopy() *Policy { - if in == nil { - return nil - } - out := new(Policy) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Policy) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PredicateArgument) DeepCopyInto(out *PredicateArgument) { - *out = *in - if in.ServiceAffinity != nil { - in, out := &in.ServiceAffinity, &out.ServiceAffinity - if *in == nil { - *out = nil - } else { - *out = new(ServiceAffinity) - (*in).DeepCopyInto(*out) - } - } - if in.LabelsPresence != nil { - in, out := &in.LabelsPresence, &out.LabelsPresence - if *in == nil { - *out = nil - } else { - *out = new(LabelsPresence) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PredicateArgument. -func (in *PredicateArgument) DeepCopy() *PredicateArgument { - if in == nil { - return nil - } - out := new(PredicateArgument) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PredicatePolicy) DeepCopyInto(out *PredicatePolicy) { - *out = *in - if in.Argument != nil { - in, out := &in.Argument, &out.Argument - if *in == nil { - *out = nil - } else { - *out = new(PredicateArgument) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PredicatePolicy. -func (in *PredicatePolicy) DeepCopy() *PredicatePolicy { - if in == nil { - return nil - } - out := new(PredicatePolicy) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PriorityArgument) DeepCopyInto(out *PriorityArgument) { - *out = *in - if in.ServiceAntiAffinity != nil { - in, out := &in.ServiceAntiAffinity, &out.ServiceAntiAffinity - if *in == nil { - *out = nil - } else { - *out = new(ServiceAntiAffinity) - **out = **in - } - } - if in.LabelPreference != nil { - in, out := &in.LabelPreference, &out.LabelPreference - if *in == nil { - *out = nil - } else { - *out = new(LabelPreference) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PriorityArgument. -func (in *PriorityArgument) DeepCopy() *PriorityArgument { - if in == nil { - return nil - } - out := new(PriorityArgument) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PriorityPolicy) DeepCopyInto(out *PriorityPolicy) { - *out = *in - if in.Argument != nil { - in, out := &in.Argument, &out.Argument - if *in == nil { - *out = nil - } else { - *out = new(PriorityArgument) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PriorityPolicy. -func (in *PriorityPolicy) DeepCopy() *PriorityPolicy { - if in == nil { - return nil - } - out := new(PriorityPolicy) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServiceAffinity) DeepCopyInto(out *ServiceAffinity) { - *out = *in - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAffinity. -func (in *ServiceAffinity) DeepCopy() *ServiceAffinity { - if in == nil { - return nil - } - out := new(ServiceAffinity) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServiceAntiAffinity) DeepCopyInto(out *ServiceAntiAffinity) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAntiAffinity. -func (in *ServiceAntiAffinity) DeepCopy() *ServiceAntiAffinity { - if in == nil { - return nil - } - out := new(ServiceAntiAffinity) - in.DeepCopyInto(out) - return out -} diff --git a/plugin/pkg/scheduler/api/validation/BUILD b/plugin/pkg/scheduler/api/validation/BUILD deleted file mode 100644 index 2120ba3dc09..00000000000 --- a/plugin/pkg/scheduler/api/validation/BUILD +++ /dev/null @@ -1,38 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = ["validation.go"], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/api/validation", - deps = [ - "//plugin/pkg/scheduler/api:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["validation_test.go"], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/api/validation", - library = ":go_default_library", - deps = ["//plugin/pkg/scheduler/api:go_default_library"], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/plugin/pkg/scheduler/api/validation/validation.go b/plugin/pkg/scheduler/api/validation/validation.go deleted file mode 100644 index 620a0343ed7..00000000000 --- a/plugin/pkg/scheduler/api/validation/validation.go +++ /dev/null @@ -1,50 +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 validation - -import ( - "fmt" - - utilerrors "k8s.io/apimachinery/pkg/util/errors" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" -) - -// ValidatePolicy checks for errors in the Config -// It does not return early so that it can find as many errors as possible -func ValidatePolicy(policy schedulerapi.Policy) error { - var validationErrors []error - - for _, priority := range policy.Priorities { - if priority.Weight <= 0 || priority.Weight >= schedulerapi.MaxWeight { - validationErrors = append(validationErrors, fmt.Errorf("Priority %s should have a positive weight applied to it or it has overflown", priority.Name)) - } - } - - binders := 0 - for _, extender := range policy.ExtenderConfigs { - if extender.Weight <= 0 { - validationErrors = append(validationErrors, fmt.Errorf("Priority for extender %s should have a positive weight applied to it", extender.URLPrefix)) - } - if extender.BindVerb != "" { - binders++ - } - } - if binders > 1 { - validationErrors = append(validationErrors, fmt.Errorf("Only one extender can implement bind, found %v", binders)) - } - return utilerrors.NewAggregate(validationErrors) -} diff --git a/plugin/pkg/scheduler/api/validation/validation_test.go b/plugin/pkg/scheduler/api/validation/validation_test.go deleted file mode 100644 index b7a6cd2ca9a..00000000000 --- a/plugin/pkg/scheduler/api/validation/validation_test.go +++ /dev/null @@ -1,76 +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 validation - -import ( - "errors" - "fmt" - "testing" - - "k8s.io/kubernetes/plugin/pkg/scheduler/api" -) - -func TestValidatePolicy(t *testing.T) { - tests := []struct { - policy api.Policy - expected error - }{ - { - policy: api.Policy{Priorities: []api.PriorityPolicy{{Name: "NoWeightPriority"}}}, - expected: errors.New("Priority NoWeightPriority should have a positive weight applied to it or it has overflown"), - }, - { - policy: api.Policy{Priorities: []api.PriorityPolicy{{Name: "NoWeightPriority", Weight: 0}}}, - expected: errors.New("Priority NoWeightPriority should have a positive weight applied to it or it has overflown"), - }, - { - policy: api.Policy{Priorities: []api.PriorityPolicy{{Name: "WeightPriority", Weight: 2}}}, - expected: nil, - }, - { - policy: api.Policy{Priorities: []api.PriorityPolicy{{Name: "WeightPriority", Weight: -2}}}, - expected: errors.New("Priority WeightPriority should have a positive weight applied to it or it has overflown"), - }, - { - policy: api.Policy{Priorities: []api.PriorityPolicy{{Name: "WeightPriority", Weight: api.MaxWeight}}}, - expected: errors.New("Priority WeightPriority should have a positive weight applied to it or it has overflown"), - }, - { - policy: api.Policy{ExtenderConfigs: []api.ExtenderConfig{{URLPrefix: "http://127.0.0.1:8081/extender", FilterVerb: "filter", Weight: 2}}}, - expected: nil, - }, - { - policy: api.Policy{ExtenderConfigs: []api.ExtenderConfig{{URLPrefix: "http://127.0.0.1:8081/extender", FilterVerb: "filter", Weight: -2}}}, - expected: errors.New("Priority for extender http://127.0.0.1:8081/extender should have a positive weight applied to it"), - }, - { - policy: api.Policy{ - ExtenderConfigs: []api.ExtenderConfig{ - {URLPrefix: "http://127.0.0.1:8081/extender", BindVerb: "bind", Weight: 2}, - {URLPrefix: "http://127.0.0.1:8082/extender", BindVerb: "bind", Weight: 2}, - }}, - expected: errors.New("Only one extender can implement bind, found 2"), - }, - } - - for _, test := range tests { - actual := ValidatePolicy(test.policy) - if fmt.Sprint(test.expected) != fmt.Sprint(actual) { - t.Errorf("expected: %s, actual: %s", test.expected, actual) - } - } -} diff --git a/plugin/pkg/scheduler/api/zz_generated.deepcopy.go b/plugin/pkg/scheduler/api/zz_generated.deepcopy.go deleted file mode 100644 index b5288e26e5b..00000000000 --- a/plugin/pkg/scheduler/api/zz_generated.deepcopy.go +++ /dev/null @@ -1,498 +0,0 @@ -// +build !ignore_autogenerated - -/* -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. -*/ - -// This file was autogenerated by deepcopy-gen. Do not edit it manually! - -package api - -import ( - v1 "k8s.io/api/core/v1" - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" - rest "k8s.io/client-go/rest" - reflect "reflect" -) - -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExtenderArgs).DeepCopyInto(out.(*ExtenderArgs)) - return nil - }, InType: reflect.TypeOf(&ExtenderArgs{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExtenderBindingArgs).DeepCopyInto(out.(*ExtenderBindingArgs)) - return nil - }, InType: reflect.TypeOf(&ExtenderBindingArgs{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExtenderBindingResult).DeepCopyInto(out.(*ExtenderBindingResult)) - return nil - }, InType: reflect.TypeOf(&ExtenderBindingResult{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExtenderConfig).DeepCopyInto(out.(*ExtenderConfig)) - return nil - }, InType: reflect.TypeOf(&ExtenderConfig{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExtenderFilterResult).DeepCopyInto(out.(*ExtenderFilterResult)) - return nil - }, InType: reflect.TypeOf(&ExtenderFilterResult{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HostPriority).DeepCopyInto(out.(*HostPriority)) - return nil - }, InType: reflect.TypeOf(&HostPriority{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LabelPreference).DeepCopyInto(out.(*LabelPreference)) - return nil - }, InType: reflect.TypeOf(&LabelPreference{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LabelsPresence).DeepCopyInto(out.(*LabelsPresence)) - return nil - }, InType: reflect.TypeOf(&LabelsPresence{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Policy).DeepCopyInto(out.(*Policy)) - return nil - }, InType: reflect.TypeOf(&Policy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PredicateArgument).DeepCopyInto(out.(*PredicateArgument)) - return nil - }, InType: reflect.TypeOf(&PredicateArgument{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PredicatePolicy).DeepCopyInto(out.(*PredicatePolicy)) - return nil - }, InType: reflect.TypeOf(&PredicatePolicy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PriorityArgument).DeepCopyInto(out.(*PriorityArgument)) - return nil - }, InType: reflect.TypeOf(&PriorityArgument{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PriorityPolicy).DeepCopyInto(out.(*PriorityPolicy)) - return nil - }, InType: reflect.TypeOf(&PriorityPolicy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ServiceAffinity).DeepCopyInto(out.(*ServiceAffinity)) - return nil - }, InType: reflect.TypeOf(&ServiceAffinity{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ServiceAntiAffinity).DeepCopyInto(out.(*ServiceAntiAffinity)) - return nil - }, InType: reflect.TypeOf(&ServiceAntiAffinity{})}, - ) -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExtenderArgs) DeepCopyInto(out *ExtenderArgs) { - *out = *in - in.Pod.DeepCopyInto(&out.Pod) - if in.Nodes != nil { - in, out := &in.Nodes, &out.Nodes - if *in == nil { - *out = nil - } else { - *out = new(v1.NodeList) - (*in).DeepCopyInto(*out) - } - } - if in.NodeNames != nil { - in, out := &in.NodeNames, &out.NodeNames - if *in == nil { - *out = nil - } else { - *out = new([]string) - if **in != nil { - in, out := *in, *out - *out = make([]string, len(*in)) - copy(*out, *in) - } - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtenderArgs. -func (in *ExtenderArgs) DeepCopy() *ExtenderArgs { - if in == nil { - return nil - } - out := new(ExtenderArgs) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExtenderBindingArgs) DeepCopyInto(out *ExtenderBindingArgs) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtenderBindingArgs. -func (in *ExtenderBindingArgs) DeepCopy() *ExtenderBindingArgs { - if in == nil { - return nil - } - out := new(ExtenderBindingArgs) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExtenderBindingResult) DeepCopyInto(out *ExtenderBindingResult) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtenderBindingResult. -func (in *ExtenderBindingResult) DeepCopy() *ExtenderBindingResult { - if in == nil { - return nil - } - out := new(ExtenderBindingResult) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExtenderConfig) DeepCopyInto(out *ExtenderConfig) { - *out = *in - if in.TLSConfig != nil { - in, out := &in.TLSConfig, &out.TLSConfig - if *in == nil { - *out = nil - } else { - *out = new(rest.TLSClientConfig) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtenderConfig. -func (in *ExtenderConfig) DeepCopy() *ExtenderConfig { - if in == nil { - return nil - } - out := new(ExtenderConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExtenderFilterResult) DeepCopyInto(out *ExtenderFilterResult) { - *out = *in - if in.Nodes != nil { - in, out := &in.Nodes, &out.Nodes - if *in == nil { - *out = nil - } else { - *out = new(v1.NodeList) - (*in).DeepCopyInto(*out) - } - } - if in.NodeNames != nil { - in, out := &in.NodeNames, &out.NodeNames - if *in == nil { - *out = nil - } else { - *out = new([]string) - if **in != nil { - in, out := *in, *out - *out = make([]string, len(*in)) - copy(*out, *in) - } - } - } - if in.FailedNodes != nil { - in, out := &in.FailedNodes, &out.FailedNodes - *out = make(FailedNodesMap, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtenderFilterResult. -func (in *ExtenderFilterResult) DeepCopy() *ExtenderFilterResult { - if in == nil { - return nil - } - out := new(ExtenderFilterResult) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HostPriority) DeepCopyInto(out *HostPriority) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostPriority. -func (in *HostPriority) DeepCopy() *HostPriority { - if in == nil { - return nil - } - out := new(HostPriority) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LabelPreference) DeepCopyInto(out *LabelPreference) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LabelPreference. -func (in *LabelPreference) DeepCopy() *LabelPreference { - if in == nil { - return nil - } - out := new(LabelPreference) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LabelsPresence) DeepCopyInto(out *LabelsPresence) { - *out = *in - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LabelsPresence. -func (in *LabelsPresence) DeepCopy() *LabelsPresence { - if in == nil { - return nil - } - out := new(LabelsPresence) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Policy) DeepCopyInto(out *Policy) { - *out = *in - out.TypeMeta = in.TypeMeta - if in.Predicates != nil { - in, out := &in.Predicates, &out.Predicates - *out = make([]PredicatePolicy, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Priorities != nil { - in, out := &in.Priorities, &out.Priorities - *out = make([]PriorityPolicy, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.ExtenderConfigs != nil { - in, out := &in.ExtenderConfigs, &out.ExtenderConfigs - *out = make([]ExtenderConfig, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Policy. -func (in *Policy) DeepCopy() *Policy { - if in == nil { - return nil - } - out := new(Policy) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Policy) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PredicateArgument) DeepCopyInto(out *PredicateArgument) { - *out = *in - if in.ServiceAffinity != nil { - in, out := &in.ServiceAffinity, &out.ServiceAffinity - if *in == nil { - *out = nil - } else { - *out = new(ServiceAffinity) - (*in).DeepCopyInto(*out) - } - } - if in.LabelsPresence != nil { - in, out := &in.LabelsPresence, &out.LabelsPresence - if *in == nil { - *out = nil - } else { - *out = new(LabelsPresence) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PredicateArgument. -func (in *PredicateArgument) DeepCopy() *PredicateArgument { - if in == nil { - return nil - } - out := new(PredicateArgument) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PredicatePolicy) DeepCopyInto(out *PredicatePolicy) { - *out = *in - if in.Argument != nil { - in, out := &in.Argument, &out.Argument - if *in == nil { - *out = nil - } else { - *out = new(PredicateArgument) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PredicatePolicy. -func (in *PredicatePolicy) DeepCopy() *PredicatePolicy { - if in == nil { - return nil - } - out := new(PredicatePolicy) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PriorityArgument) DeepCopyInto(out *PriorityArgument) { - *out = *in - if in.ServiceAntiAffinity != nil { - in, out := &in.ServiceAntiAffinity, &out.ServiceAntiAffinity - if *in == nil { - *out = nil - } else { - *out = new(ServiceAntiAffinity) - **out = **in - } - } - if in.LabelPreference != nil { - in, out := &in.LabelPreference, &out.LabelPreference - if *in == nil { - *out = nil - } else { - *out = new(LabelPreference) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PriorityArgument. -func (in *PriorityArgument) DeepCopy() *PriorityArgument { - if in == nil { - return nil - } - out := new(PriorityArgument) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PriorityPolicy) DeepCopyInto(out *PriorityPolicy) { - *out = *in - if in.Argument != nil { - in, out := &in.Argument, &out.Argument - if *in == nil { - *out = nil - } else { - *out = new(PriorityArgument) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PriorityPolicy. -func (in *PriorityPolicy) DeepCopy() *PriorityPolicy { - if in == nil { - return nil - } - out := new(PriorityPolicy) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServiceAffinity) DeepCopyInto(out *ServiceAffinity) { - *out = *in - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAffinity. -func (in *ServiceAffinity) DeepCopy() *ServiceAffinity { - if in == nil { - return nil - } - out := new(ServiceAffinity) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServiceAntiAffinity) DeepCopyInto(out *ServiceAntiAffinity) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAntiAffinity. -func (in *ServiceAntiAffinity) DeepCopy() *ServiceAntiAffinity { - if in == nil { - return nil - } - out := new(ServiceAntiAffinity) - in.DeepCopyInto(out) - return out -} diff --git a/plugin/pkg/scheduler/core/BUILD b/plugin/pkg/scheduler/core/BUILD deleted file mode 100644 index a6c32d9f866..00000000000 --- a/plugin/pkg/scheduler/core/BUILD +++ /dev/null @@ -1,74 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = [ - "equivalence_cache_test.go", - "extender_test.go", - "generic_scheduler_test.go", - ], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/core", - library = ":go_default_library", - deps = [ - "//plugin/pkg/scheduler/algorithm:go_default_library", - "//plugin/pkg/scheduler/algorithm/predicates:go_default_library", - "//plugin/pkg/scheduler/algorithm/priorities:go_default_library", - "//plugin/pkg/scheduler/algorithm/priorities/util:go_default_library", - "//plugin/pkg/scheduler/api:go_default_library", - "//plugin/pkg/scheduler/schedulercache:go_default_library", - "//plugin/pkg/scheduler/testing:go_default_library", - "//vendor/k8s.io/api/apps/v1beta1:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - ], -) - -go_library( - name = "go_default_library", - srcs = [ - "equivalence_cache.go", - "extender.go", - "generic_scheduler.go", - ], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/core", - deps = [ - "//pkg/util/hash:go_default_library", - "//plugin/pkg/scheduler/algorithm:go_default_library", - "//plugin/pkg/scheduler/algorithm/predicates:go_default_library", - "//plugin/pkg/scheduler/api:go_default_library", - "//plugin/pkg/scheduler/schedulercache:go_default_library", - "//plugin/pkg/scheduler/util:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/github.com/golang/groupcache/lru:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - "//vendor/k8s.io/apiserver/pkg/util/trace:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - "//vendor/k8s.io/client-go/util/workqueue:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/plugin/pkg/scheduler/core/equivalence_cache_test.go b/plugin/pkg/scheduler/core/equivalence_cache_test.go deleted file mode 100644 index 20f2ed6d238..00000000000 --- a/plugin/pkg/scheduler/core/equivalence_cache_test.go +++ /dev/null @@ -1,448 +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 core - -import ( - "reflect" - "testing" - - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates" -) - -type predicateItemType struct { - fit bool - reasons []algorithm.PredicateFailureReason -} - -func TestUpdateCachedPredicateItem(t *testing.T) { - tests := []struct { - name string - pod string - predicateKey string - nodeName string - fit bool - reasons []algorithm.PredicateFailureReason - equivalenceHash uint64 - expectPredicateMap bool - expectCacheItem HostPredicate - }{ - { - name: "test 1", - pod: "testPod", - predicateKey: "GeneralPredicates", - nodeName: "node1", - fit: true, - equivalenceHash: 123, - expectPredicateMap: false, - expectCacheItem: HostPredicate{ - Fit: true, - }, - }, - { - name: "test 2", - pod: "testPod", - predicateKey: "GeneralPredicates", - nodeName: "node2", - fit: false, - equivalenceHash: 123, - expectPredicateMap: true, - expectCacheItem: HostPredicate{ - Fit: false, - }, - }, - } - for _, test := range tests { - // this case does not need to calculate equivalence hash, just pass an empty function - fakeGetEquivalencePodFunc := func(pod *v1.Pod) interface{} { return nil } - ecache := NewEquivalenceCache(fakeGetEquivalencePodFunc) - if test.expectPredicateMap { - ecache.algorithmCache[test.nodeName] = newAlgorithmCache() - predicateItem := HostPredicate{ - Fit: true, - } - ecache.algorithmCache[test.nodeName].predicatesCache.Add(test.predicateKey, - PredicateMap{ - test.equivalenceHash: predicateItem, - }) - } - ecache.UpdateCachedPredicateItem( - test.pod, - test.nodeName, - test.predicateKey, - test.fit, - test.reasons, - test.equivalenceHash, - ) - - value, ok := ecache.algorithmCache[test.nodeName].predicatesCache.Get(test.predicateKey) - if !ok { - t.Errorf("Failed: %s, can't find expected cache item: %v", - test.name, test.expectCacheItem) - } else { - cachedMapItem := value.(PredicateMap) - if !reflect.DeepEqual(cachedMapItem[test.equivalenceHash], test.expectCacheItem) { - t.Errorf("Failed: %s, expected cached item: %v, but got: %v", - test.name, test.expectCacheItem, cachedMapItem[test.equivalenceHash]) - } - } - } -} - -func TestPredicateWithECache(t *testing.T) { - tests := []struct { - name string - podName string - nodeName string - predicateKey string - equivalenceHashForUpdatePredicate uint64 - equivalenceHashForCalPredicate uint64 - cachedItem predicateItemType - expectedInvalidPredicateKey bool - expectedInvalidEquivalenceHash bool - expectedPredicateItem predicateItemType - }{ - { - name: "test 1", - podName: "testPod", - nodeName: "node1", - equivalenceHashForUpdatePredicate: 123, - equivalenceHashForCalPredicate: 123, - predicateKey: "GeneralPredicates", - cachedItem: predicateItemType{ - fit: false, - reasons: []algorithm.PredicateFailureReason{predicates.ErrPodNotFitsHostPorts}, - }, - expectedInvalidPredicateKey: true, - expectedPredicateItem: predicateItemType{ - fit: false, - reasons: []algorithm.PredicateFailureReason{}, - }, - }, - { - name: "test 2", - podName: "testPod", - nodeName: "node2", - equivalenceHashForUpdatePredicate: 123, - equivalenceHashForCalPredicate: 123, - predicateKey: "GeneralPredicates", - cachedItem: predicateItemType{ - fit: true, - }, - expectedInvalidPredicateKey: false, - expectedPredicateItem: predicateItemType{ - fit: true, - reasons: []algorithm.PredicateFailureReason{}, - }, - }, - { - name: "test 3", - podName: "testPod", - nodeName: "node3", - equivalenceHashForUpdatePredicate: 123, - equivalenceHashForCalPredicate: 123, - predicateKey: "GeneralPredicates", - cachedItem: predicateItemType{ - fit: false, - reasons: []algorithm.PredicateFailureReason{predicates.ErrPodNotFitsHostPorts}, - }, - expectedInvalidPredicateKey: false, - expectedPredicateItem: predicateItemType{ - fit: false, - reasons: []algorithm.PredicateFailureReason{predicates.ErrPodNotFitsHostPorts}, - }, - }, - { - name: "test 4", - podName: "testPod", - nodeName: "node4", - equivalenceHashForUpdatePredicate: 123, - equivalenceHashForCalPredicate: 456, - predicateKey: "GeneralPredicates", - cachedItem: predicateItemType{ - fit: false, - reasons: []algorithm.PredicateFailureReason{predicates.ErrPodNotFitsHostPorts}, - }, - expectedInvalidPredicateKey: false, - expectedInvalidEquivalenceHash: true, - expectedPredicateItem: predicateItemType{ - fit: false, - reasons: []algorithm.PredicateFailureReason{}, - }, - }, - } - - for _, test := range tests { - // this case does not need to calculate equivalence hash, just pass an empty function - fakeGetEquivalencePodFunc := func(pod *v1.Pod) interface{} { return nil } - ecache := NewEquivalenceCache(fakeGetEquivalencePodFunc) - // set cached item to equivalence cache - ecache.UpdateCachedPredicateItem( - test.podName, - test.nodeName, - test.predicateKey, - test.cachedItem.fit, - test.cachedItem.reasons, - test.equivalenceHashForUpdatePredicate, - ) - // if we want to do invalid, invalid the cached item - if test.expectedInvalidPredicateKey { - predicateKeys := sets.NewString() - predicateKeys.Insert(test.predicateKey) - ecache.InvalidateCachedPredicateItem(test.nodeName, predicateKeys) - } - // calculate predicate with equivalence cache - fit, reasons, invalid := ecache.PredicateWithECache(test.podName, - test.nodeName, - test.predicateKey, - test.equivalenceHashForCalPredicate, - ) - // returned invalid should match expectedInvalidPredicateKey or expectedInvalidEquivalenceHash - if test.equivalenceHashForUpdatePredicate != test.equivalenceHashForCalPredicate { - if invalid != test.expectedInvalidEquivalenceHash { - t.Errorf("Failed: %s, expected invalid: %v, but got: %v", - test.name, test.expectedInvalidEquivalenceHash, invalid) - } - } else { - if invalid != test.expectedInvalidPredicateKey { - t.Errorf("Failed: %s, expected invalid: %v, but got: %v", - test.name, test.expectedInvalidPredicateKey, invalid) - } - } - // returned predicate result should match expected predicate item - if fit != test.expectedPredicateItem.fit { - t.Errorf("Failed: %s, expected fit: %v, but got: %v", test.name, test.cachedItem.fit, fit) - } - if !reflect.DeepEqual(reasons, test.expectedPredicateItem.reasons) { - t.Errorf("Failed: %s, expected reasons: %v, but got: %v", - test.name, test.cachedItem.reasons, reasons) - } - } -} - -func TestGetHashEquivalencePod(t *testing.T) { - // use default equivalence class calculator - ecache := NewEquivalenceCache(predicates.GetEquivalencePod) - - isController := true - pod1 := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pod1", - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "v1", - Kind: "ReplicationController", - Name: "rc", - UID: "123", - Controller: &isController, - }, - }, - }, - } - - pod2 := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pod2", - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "v1", - Kind: "ReplicationController", - Name: "rc", - UID: "123", - Controller: &isController, - }, - }, - }, - } - - pod3 := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pod3", - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "v1", - Kind: "ReplicationController", - Name: "rc", - UID: "567", - Controller: &isController, - }, - }, - }, - } - - hash1, _ := ecache.getHashEquivalencePod(pod1) - hash2, _ := ecache.getHashEquivalencePod(pod2) - hash3, _ := ecache.getHashEquivalencePod(pod3) - - if hash1 != hash2 { - t.Errorf("Failed: pod %v and %v is expected to be equivalent", pod1.Name, pod2.Name) - } - - if hash2 == hash3 { - t.Errorf("Failed: pod %v and %v is not expected to be equivalent", pod2.Name, pod3.Name) - } - - // pod4 is a pod without controller ref - pod4 := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pod4", - }, - } - _, found := ecache.getHashEquivalencePod(pod4) - if found { - t.Errorf("Failed: equivalence hash of pod %v is not expected to be found, but got: %v", - pod4.Name, found) - } -} - -func TestInvalidateCachedPredicateItemOfAllNodes(t *testing.T) { - testPredicate := "GeneralPredicates" - // tests is used to initialize all nodes - tests := []struct { - podName string - nodeName string - predicateKey string - equivalenceHashForUpdatePredicate uint64 - cachedItem predicateItemType - }{ - { - podName: "testPod", - nodeName: "node1", - equivalenceHashForUpdatePredicate: 123, - cachedItem: predicateItemType{ - fit: false, - reasons: []algorithm.PredicateFailureReason{ - predicates.ErrPodNotFitsHostPorts, - }, - }, - }, - { - podName: "testPod", - nodeName: "node2", - equivalenceHashForUpdatePredicate: 456, - cachedItem: predicateItemType{ - fit: false, - reasons: []algorithm.PredicateFailureReason{ - predicates.ErrPodNotFitsHostPorts, - }, - }, - }, - { - podName: "testPod", - nodeName: "node3", - equivalenceHashForUpdatePredicate: 123, - cachedItem: predicateItemType{ - fit: true, - }, - }, - } - // this case does not need to calculate equivalence hash, just pass an empty function - fakeGetEquivalencePodFunc := func(pod *v1.Pod) interface{} { return nil } - ecache := NewEquivalenceCache(fakeGetEquivalencePodFunc) - - for _, test := range tests { - // set cached item to equivalence cache - ecache.UpdateCachedPredicateItem( - test.podName, - test.nodeName, - testPredicate, - test.cachedItem.fit, - test.cachedItem.reasons, - test.equivalenceHashForUpdatePredicate, - ) - } - - // invalidate cached predicate for all nodes - ecache.InvalidateCachedPredicateItemOfAllNodes(sets.NewString(testPredicate)) - - // there should be no cached predicate any more - for _, test := range tests { - if algorithmCache, exist := ecache.algorithmCache[test.nodeName]; exist { - if _, exist := algorithmCache.predicatesCache.Get(testPredicate); exist { - t.Errorf("Failed: cached item for predicate key: %v on node: %v should be invalidated", - testPredicate, test.nodeName) - break - } - } - } -} - -func TestInvalidateAllCachedPredicateItemOfNode(t *testing.T) { - testPredicate := "GeneralPredicates" - // tests is used to initialize all nodes - tests := []struct { - podName string - nodeName string - predicateKey string - equivalenceHashForUpdatePredicate uint64 - cachedItem predicateItemType - }{ - { - podName: "testPod", - nodeName: "node1", - equivalenceHashForUpdatePredicate: 123, - cachedItem: predicateItemType{ - fit: false, - reasons: []algorithm.PredicateFailureReason{predicates.ErrPodNotFitsHostPorts}, - }, - }, - { - podName: "testPod", - nodeName: "node2", - equivalenceHashForUpdatePredicate: 456, - cachedItem: predicateItemType{ - fit: false, - reasons: []algorithm.PredicateFailureReason{predicates.ErrPodNotFitsHostPorts}, - }, - }, - { - podName: "testPod", - nodeName: "node3", - equivalenceHashForUpdatePredicate: 123, - cachedItem: predicateItemType{ - fit: true, - }, - }, - } - // this case does not need to calculate equivalence hash, just pass an empty function - fakeGetEquivalencePodFunc := func(pod *v1.Pod) interface{} { return nil } - ecache := NewEquivalenceCache(fakeGetEquivalencePodFunc) - - for _, test := range tests { - // set cached item to equivalence cache - ecache.UpdateCachedPredicateItem( - test.podName, - test.nodeName, - testPredicate, - test.cachedItem.fit, - test.cachedItem.reasons, - test.equivalenceHashForUpdatePredicate, - ) - } - - for _, test := range tests { - // invalidate cached predicate for all nodes - ecache.InvalidateAllCachedPredicateItemOfNode(test.nodeName) - if _, exist := ecache.algorithmCache[test.nodeName]; exist { - t.Errorf("Failed: cached item for node: %v should be invalidated", test.nodeName) - break - } - } -} diff --git a/plugin/pkg/scheduler/core/generic_scheduler.go b/plugin/pkg/scheduler/core/generic_scheduler.go deleted file mode 100644 index fde8f929bc7..00000000000 --- a/plugin/pkg/scheduler/core/generic_scheduler.go +++ /dev/null @@ -1,796 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package core - -import ( - "fmt" - "math" - "sort" - "strings" - "sync" - "sync/atomic" - "time" - - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/util/errors" - utiltrace "k8s.io/apiserver/pkg/util/trace" - "k8s.io/client-go/util/workqueue" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" - "k8s.io/kubernetes/plugin/pkg/scheduler/util" - - "github.com/golang/glog" -) - -type FailedPredicateMap map[string][]algorithm.PredicateFailureReason - -type FitError struct { - Pod *v1.Pod - NumAllNodes int - FailedPredicates FailedPredicateMap -} - -var ErrNoNodesAvailable = fmt.Errorf("no nodes available to schedule pods") - -const ( - NoNodeAvailableMsg = "0/%v nodes are available" - // NominatedNodeAnnotationKey is used to annotate a pod that has preempted other pods. - // The scheduler uses the annotation to find that the pod shouldn't preempt more pods - // when it gets to the head of scheduling queue again. - // See podEligibleToPreemptOthers() for more information. - NominatedNodeAnnotationKey = "NominatedNodeName" -) - -// Error returns detailed information of why the pod failed to fit on each node -func (f *FitError) Error() string { - reasons := make(map[string]int) - for _, predicates := range f.FailedPredicates { - for _, pred := range predicates { - reasons[pred.GetReason()] += 1 - } - } - - sortReasonsHistogram := func() []string { - reasonStrings := []string{} - for k, v := range reasons { - reasonStrings = append(reasonStrings, fmt.Sprintf("%v %v", v, k)) - } - sort.Strings(reasonStrings) - return reasonStrings - } - reasonMsg := fmt.Sprintf(NoNodeAvailableMsg+": %v.", f.NumAllNodes, strings.Join(sortReasonsHistogram(), ", ")) - return reasonMsg -} - -type genericScheduler struct { - cache schedulercache.Cache - equivalenceCache *EquivalenceCache - predicates map[string]algorithm.FitPredicate - priorityMetaProducer algorithm.MetadataProducer - predicateMetaProducer algorithm.PredicateMetadataProducer - prioritizers []algorithm.PriorityConfig - extenders []algorithm.SchedulerExtender - pods algorithm.PodLister - lastNodeIndexLock sync.Mutex - lastNodeIndex uint64 - - cachedNodeInfoMap map[string]*schedulercache.NodeInfo -} - -// Schedule tries to schedule the given pod to one of node in the node list. -// If it succeeds, it will return the name of the node. -// If it fails, it will return a Fiterror error with reasons. -func (g *genericScheduler) Schedule(pod *v1.Pod, nodeLister algorithm.NodeLister) (string, error) { - trace := utiltrace.New(fmt.Sprintf("Scheduling %s/%s", pod.Namespace, pod.Name)) - defer trace.LogIfLong(100 * time.Millisecond) - - nodes, err := nodeLister.List() - if err != nil { - return "", err - } - if len(nodes) == 0 { - return "", ErrNoNodesAvailable - } - - // Used for all fit and priority funcs. - err = g.cache.UpdateNodeNameToInfoMap(g.cachedNodeInfoMap) - if err != nil { - return "", err - } - - trace.Step("Computing predicates") - filteredNodes, failedPredicateMap, err := findNodesThatFit(pod, g.cachedNodeInfoMap, nodes, g.predicates, g.extenders, g.predicateMetaProducer, g.equivalenceCache) - if err != nil { - return "", err - } - - if len(filteredNodes) == 0 { - return "", &FitError{ - Pod: pod, - NumAllNodes: len(nodes), - FailedPredicates: failedPredicateMap, - } - } - - trace.Step("Prioritizing") - - // When only one node after predicate, just use it. - if len(filteredNodes) == 1 { - return filteredNodes[0].Name, nil - } - - metaPrioritiesInterface := g.priorityMetaProducer(pod, g.cachedNodeInfoMap) - priorityList, err := PrioritizeNodes(pod, g.cachedNodeInfoMap, metaPrioritiesInterface, g.prioritizers, filteredNodes, g.extenders) - if err != nil { - return "", err - } - - trace.Step("Selecting host") - return g.selectHost(priorityList) -} - -// Prioritizers returns a slice containing all the scheduler's priority -// functions and their config. It is exposed for testing only. -func (g *genericScheduler) Prioritizers() []algorithm.PriorityConfig { - return g.prioritizers -} - -// Predicates returns a map containing all the scheduler's predicate -// functions. It is exposed for testing only. -func (g *genericScheduler) Predicates() map[string]algorithm.FitPredicate { - return g.predicates -} - -// selectHost takes a prioritized list of nodes and then picks one -// in a round-robin manner from the nodes that had the highest score. -func (g *genericScheduler) selectHost(priorityList schedulerapi.HostPriorityList) (string, error) { - if len(priorityList) == 0 { - return "", fmt.Errorf("empty priorityList") - } - - sort.Sort(sort.Reverse(priorityList)) - maxScore := priorityList[0].Score - firstAfterMaxScore := sort.Search(len(priorityList), func(i int) bool { return priorityList[i].Score < maxScore }) - - g.lastNodeIndexLock.Lock() - ix := int(g.lastNodeIndex % uint64(firstAfterMaxScore)) - g.lastNodeIndex++ - g.lastNodeIndexLock.Unlock() - - return priorityList[ix].Host, nil -} - -// preempt finds nodes with pods that can be preempted to make room for "pod" to -// schedule. It chooses one of the nodes and preempts the pods on the node and -// returns the node and the list of preempted pods if such a node is found. -// TODO(bsalamat): Add priority-based scheduling. More info: today one or more -// pending pods (different from the pod that triggered the preemption(s)) may -// schedule into some portion of the resources freed up by the preemption(s) -// before the pod that triggered the preemption(s) has a chance to schedule -// there, thereby preventing the pod that triggered the preemption(s) from -// scheduling. Solution is given at: -// https://github.com/kubernetes/community/blob/master/contributors/design-proposals/scheduling/pod-preemption.md#preemption-mechanics -func (g *genericScheduler) Preempt(pod *v1.Pod, nodeLister algorithm.NodeLister, scheduleErr error) (*v1.Node, []*v1.Pod, error) { - // Scheduler may return various types of errors. Consider preemption only if - // the error is of type FitError. - fitError, ok := scheduleErr.(*FitError) - if !ok || fitError == nil { - return nil, nil, nil - } - err := g.cache.UpdateNodeNameToInfoMap(g.cachedNodeInfoMap) - if err != nil { - return nil, nil, err - } - if !podEligibleToPreemptOthers(pod, g.cachedNodeInfoMap) { - glog.V(5).Infof("Pod %v is not eligible for more preemption.", pod.Name) - return nil, nil, nil - } - allNodes, err := nodeLister.List() - if err != nil { - return nil, nil, err - } - if len(allNodes) == 0 { - return nil, nil, ErrNoNodesAvailable - } - potentialNodes := nodesWherePreemptionMightHelp(pod, allNodes, fitError.FailedPredicates) - if len(potentialNodes) == 0 { - glog.V(3).Infof("Preemption will not help schedule pod %v on any node.", pod.Name) - return nil, nil, nil - } - nodeToPods, err := selectNodesForPreemption(pod, g.cachedNodeInfoMap, potentialNodes, g.predicates, g.predicateMetaProducer) - if err != nil { - return nil, nil, err - } - for len(nodeToPods) > 0 { - node := pickOneNodeForPreemption(nodeToPods) - if node == nil { - return nil, nil, err - } - passes, pErr := nodePassesExtendersForPreemption(pod, node.Name, nodeToPods[node], g.cachedNodeInfoMap, g.extenders) - if passes && pErr == nil { - return node, nodeToPods[node], err - } - if pErr != nil { - glog.Errorf("Error occurred while checking extenders for preemption on node %v: %v", node, pErr) - } - // Remove the node from the map and try to pick a different node. - delete(nodeToPods, node) - } - return nil, nil, err -} - -// Filters the nodes to find the ones that fit based on the given predicate functions -// Each node is passed through the predicate functions to determine if it is a fit -func findNodesThatFit( - pod *v1.Pod, - nodeNameToInfo map[string]*schedulercache.NodeInfo, - nodes []*v1.Node, - predicateFuncs map[string]algorithm.FitPredicate, - extenders []algorithm.SchedulerExtender, - metadataProducer algorithm.PredicateMetadataProducer, - ecache *EquivalenceCache, -) ([]*v1.Node, FailedPredicateMap, error) { - var filtered []*v1.Node - failedPredicateMap := FailedPredicateMap{} - - if len(predicateFuncs) == 0 { - filtered = nodes - } else { - // Create filtered list with enough space to avoid growing it - // and allow assigning. - filtered = make([]*v1.Node, len(nodes)) - errs := errors.MessageCountMap{} - var predicateResultLock sync.Mutex - var filteredLen int32 - - // We can use the same metadata producer for all nodes. - meta := metadataProducer(pod, nodeNameToInfo) - checkNode := func(i int) { - nodeName := nodes[i].Name - fits, failedPredicates, err := podFitsOnNode(pod, meta, nodeNameToInfo[nodeName], predicateFuncs, ecache) - if err != nil { - predicateResultLock.Lock() - errs[err.Error()]++ - predicateResultLock.Unlock() - return - } - if fits { - filtered[atomic.AddInt32(&filteredLen, 1)-1] = nodes[i] - } else { - predicateResultLock.Lock() - failedPredicateMap[nodeName] = failedPredicates - predicateResultLock.Unlock() - } - } - workqueue.Parallelize(16, len(nodes), checkNode) - filtered = filtered[:filteredLen] - if len(errs) > 0 { - return []*v1.Node{}, FailedPredicateMap{}, errors.CreateAggregateFromMessageCountMap(errs) - } - } - - if len(filtered) > 0 && len(extenders) != 0 { - for _, extender := range extenders { - filteredList, failedMap, err := extender.Filter(pod, filtered, nodeNameToInfo) - if err != nil { - return []*v1.Node{}, FailedPredicateMap{}, err - } - - for failedNodeName, failedMsg := range failedMap { - if _, found := failedPredicateMap[failedNodeName]; !found { - failedPredicateMap[failedNodeName] = []algorithm.PredicateFailureReason{} - } - failedPredicateMap[failedNodeName] = append(failedPredicateMap[failedNodeName], predicates.NewFailureReason(failedMsg)) - } - filtered = filteredList - if len(filtered) == 0 { - break - } - } - } - return filtered, failedPredicateMap, nil -} - -// Checks whether node with a given name and NodeInfo satisfies all predicateFuncs. -func podFitsOnNode(pod *v1.Pod, meta algorithm.PredicateMetadata, info *schedulercache.NodeInfo, predicateFuncs map[string]algorithm.FitPredicate, - ecache *EquivalenceCache) (bool, []algorithm.PredicateFailureReason, error) { - var ( - equivalenceHash uint64 - failedPredicates []algorithm.PredicateFailureReason - eCacheAvailable bool - invalid bool - fit bool - reasons []algorithm.PredicateFailureReason - err error - ) - if ecache != nil { - // getHashEquivalencePod will return immediately if no equivalence pod found - equivalenceHash, eCacheAvailable = ecache.getHashEquivalencePod(pod) - } - for predicateKey, predicate := range predicateFuncs { - // If equivalenceCache is available - if eCacheAvailable { - // PredicateWithECache will returns it's cached predicate results - fit, reasons, invalid = ecache.PredicateWithECache(pod.GetName(), info.Node().GetName(), predicateKey, equivalenceHash) - } - - if !eCacheAvailable || invalid { - // we need to execute predicate functions since equivalence cache does not work - fit, reasons, err = predicate(pod, meta, info) - if err != nil { - return false, []algorithm.PredicateFailureReason{}, err - } - - if eCacheAvailable { - // update equivalence cache with newly computed fit & reasons - // TODO(resouer) should we do this in another thread? any race? - ecache.UpdateCachedPredicateItem(pod.GetName(), info.Node().GetName(), predicateKey, fit, reasons, equivalenceHash) - } - } - - if !fit { - // eCache is available and valid, and predicates result is unfit, record the fail reasons - failedPredicates = append(failedPredicates, reasons...) - } - } - return len(failedPredicates) == 0, failedPredicates, nil -} - -// Prioritizes the nodes by running the individual priority functions in parallel. -// Each priority function is expected to set a score of 0-10 -// 0 is the lowest priority score (least preferred node) and 10 is the highest -// Each priority function can also have its own weight -// The node scores returned by the priority function are multiplied by the weights to get weighted scores -// All scores are finally combined (added) to get the total weighted scores of all nodes -func PrioritizeNodes( - pod *v1.Pod, - nodeNameToInfo map[string]*schedulercache.NodeInfo, - meta interface{}, - priorityConfigs []algorithm.PriorityConfig, - nodes []*v1.Node, - extenders []algorithm.SchedulerExtender, -) (schedulerapi.HostPriorityList, error) { - // If no priority configs are provided, then the EqualPriority function is applied - // This is required to generate the priority list in the required format - if len(priorityConfigs) == 0 && len(extenders) == 0 { - result := make(schedulerapi.HostPriorityList, 0, len(nodes)) - for i := range nodes { - hostPriority, err := EqualPriorityMap(pod, meta, nodeNameToInfo[nodes[i].Name]) - if err != nil { - return nil, err - } - result = append(result, hostPriority) - } - return result, nil - } - - var ( - mu = sync.Mutex{} - wg = sync.WaitGroup{} - errs []error - ) - appendError := func(err error) { - mu.Lock() - defer mu.Unlock() - errs = append(errs, err) - } - - results := make([]schedulerapi.HostPriorityList, len(priorityConfigs), len(priorityConfigs)) - - for i, priorityConfig := range priorityConfigs { - if priorityConfig.Function != nil { - // DEPRECATED - wg.Add(1) - go func(index int, config algorithm.PriorityConfig) { - defer wg.Done() - var err error - results[index], err = config.Function(pod, nodeNameToInfo, nodes) - if err != nil { - appendError(err) - } - }(i, priorityConfig) - } else { - results[i] = make(schedulerapi.HostPriorityList, len(nodes)) - } - } - processNode := func(index int) { - nodeInfo := nodeNameToInfo[nodes[index].Name] - var err error - for i := range priorityConfigs { - if priorityConfigs[i].Function != nil { - continue - } - results[i][index], err = priorityConfigs[i].Map(pod, meta, nodeInfo) - if err != nil { - appendError(err) - return - } - } - } - workqueue.Parallelize(16, len(nodes), processNode) - for i, priorityConfig := range priorityConfigs { - if priorityConfig.Reduce == nil { - continue - } - wg.Add(1) - go func(index int, config algorithm.PriorityConfig) { - defer wg.Done() - if err := config.Reduce(pod, meta, nodeNameToInfo, results[index]); err != nil { - appendError(err) - } - }(i, priorityConfig) - } - // Wait for all computations to be finished. - wg.Wait() - if len(errs) != 0 { - return schedulerapi.HostPriorityList{}, errors.NewAggregate(errs) - } - - // Summarize all scores. - result := make(schedulerapi.HostPriorityList, 0, len(nodes)) - - for i := range nodes { - result = append(result, schedulerapi.HostPriority{Host: nodes[i].Name, Score: 0}) - for j := range priorityConfigs { - result[i].Score += results[j][i].Score * priorityConfigs[j].Weight - } - } - - if len(extenders) != 0 && nodes != nil { - combinedScores := make(map[string]int, len(nodeNameToInfo)) - for _, extender := range extenders { - wg.Add(1) - go func(ext algorithm.SchedulerExtender) { - defer wg.Done() - prioritizedList, weight, err := ext.Prioritize(pod, nodes) - if err != nil { - // Prioritization errors from extender can be ignored, let k8s/other extenders determine the priorities - return - } - mu.Lock() - for i := range *prioritizedList { - host, score := (*prioritizedList)[i].Host, (*prioritizedList)[i].Score - combinedScores[host] += score * weight - } - mu.Unlock() - }(extender) - } - // wait for all go routines to finish - wg.Wait() - for i := range result { - result[i].Score += combinedScores[result[i].Host] - } - } - - if glog.V(10) { - for i := range result { - glog.V(10).Infof("Host %s => Score %d", result[i].Host, result[i].Score) - } - } - return result, nil -} - -// EqualPriority is a prioritizer function that gives an equal weight of one to all nodes -func EqualPriorityMap(_ *v1.Pod, _ interface{}, nodeInfo *schedulercache.NodeInfo) (schedulerapi.HostPriority, error) { - node := nodeInfo.Node() - if node == nil { - return schedulerapi.HostPriority{}, fmt.Errorf("node not found") - } - return schedulerapi.HostPriority{ - Host: node.Name, - Score: 1, - }, nil -} - -// pickOneNodeForPreemption chooses one node among the given nodes. It assumes -// pods in each map entry are ordered by decreasing priority. -// It picks a node based on the following criteria: -// 1. A node with minimum highest priority victim is picked. -// 2. Ties are broken by sum of priorities of all victims. -// 3. If there are still ties, node with the minimum number of victims is picked. -// 4. If there are still ties, the first such node is picked (sort of randomly). -//TODO(bsalamat): Try to reuse the "nodeScore" slices in order to save GC time. -func pickOneNodeForPreemption(nodesToPods map[*v1.Node][]*v1.Pod) *v1.Node { - type nodeScore struct { - node *v1.Node - highestPriority int32 - sumPriorities int64 - numPods int - } - if len(nodesToPods) == 0 { - return nil - } - minHighestPriority := int32(math.MaxInt32) - minPriorityScores := []*nodeScore{} - for node, pods := range nodesToPods { - if len(pods) == 0 { - // We found a node that doesn't need any preemption. Return it! - // This should happen rarely when one or more pods are terminated between - // the time that scheduler tries to schedule the pod and the time that - // preemption logic tries to find nodes for preemption. - return node - } - // highestPodPriority is the highest priority among the victims on this node. - highestPodPriority := util.GetPodPriority(pods[0]) - if highestPodPriority < minHighestPriority { - minHighestPriority = highestPodPriority - minPriorityScores = nil - } - if highestPodPriority == minHighestPriority { - minPriorityScores = append(minPriorityScores, &nodeScore{node: node, highestPriority: highestPodPriority, numPods: len(pods)}) - } - } - if len(minPriorityScores) == 1 { - return minPriorityScores[0].node - } - // There are a few nodes with minimum highest priority victim. Find the - // smallest sum of priorities. - minSumPriorities := int64(math.MaxInt64) - minSumPriorityScores := []*nodeScore{} - for _, nodeScore := range minPriorityScores { - var sumPriorities int64 - for _, pod := range nodesToPods[nodeScore.node] { - // We add MaxInt32+1 to all priorities to make all of them >= 0. This is - // needed so that a node with a few pods with negative priority is not - // picked over a node with a smaller number of pods with the same negative - // priority (and similar scenarios). - sumPriorities += int64(util.GetPodPriority(pod)) + int64(math.MaxInt32+1) - } - if sumPriorities < minSumPriorities { - minSumPriorities = sumPriorities - minSumPriorityScores = nil - } - nodeScore.sumPriorities = sumPriorities - if sumPriorities == minSumPriorities { - minSumPriorityScores = append(minSumPriorityScores, nodeScore) - } - } - if len(minSumPriorityScores) == 1 { - return minSumPriorityScores[0].node - } - // There are a few nodes with minimum highest priority victim and sum of priorities. - // Find one with the minimum number of pods. - minNumPods := math.MaxInt32 - minNumPodScores := []*nodeScore{} - for _, nodeScore := range minSumPriorityScores { - if nodeScore.numPods < minNumPods { - minNumPods = nodeScore.numPods - minNumPodScores = nil - } - if nodeScore.numPods == minNumPods { - minNumPodScores = append(minNumPodScores, nodeScore) - } - } - // At this point, even if there are more than one node with the same score, - // return the first one. - if len(minNumPodScores) > 0 { - return minNumPodScores[0].node - } - glog.Errorf("Error in logic of node scoring for preemption. We should never reach here!") - return nil -} - -// selectNodesForPreemption finds all the nodes with possible victims for -// preemption in parallel. -func selectNodesForPreemption(pod *v1.Pod, - nodeNameToInfo map[string]*schedulercache.NodeInfo, - potentialNodes []*v1.Node, - predicates map[string]algorithm.FitPredicate, - metadataProducer algorithm.PredicateMetadataProducer, -) (map[*v1.Node][]*v1.Pod, error) { - - nodeNameToPods := map[*v1.Node][]*v1.Pod{} - var resultLock sync.Mutex - - // We can use the same metadata producer for all nodes. - meta := metadataProducer(pod, nodeNameToInfo) - checkNode := func(i int) { - nodeName := potentialNodes[i].Name - var metaCopy algorithm.PredicateMetadata - if meta != nil { - metaCopy = meta.ShallowCopy() - } - pods, fits := selectVictimsOnNode(pod, metaCopy, nodeNameToInfo[nodeName], predicates) - if fits { - resultLock.Lock() - nodeNameToPods[potentialNodes[i]] = pods - resultLock.Unlock() - } - } - workqueue.Parallelize(16, len(potentialNodes), checkNode) - return nodeNameToPods, nil -} - -func nodePassesExtendersForPreemption( - pod *v1.Pod, - nodeName string, - victims []*v1.Pod, - nodeNameToInfo map[string]*schedulercache.NodeInfo, - extenders []algorithm.SchedulerExtender) (bool, error) { - // If there are any extenders, run them and filter the list of candidate nodes. - if len(extenders) == 0 { - return true, nil - } - // Remove the victims from the corresponding nodeInfo and send nodes to the - // extenders for filtering. - originalNodeInfo := nodeNameToInfo[nodeName] - nodeInfoCopy := nodeNameToInfo[nodeName].Clone() - for _, victim := range victims { - nodeInfoCopy.RemovePod(victim) - } - nodeNameToInfo[nodeName] = nodeInfoCopy - defer func() { nodeNameToInfo[nodeName] = originalNodeInfo }() - filteredNodes := []*v1.Node{nodeInfoCopy.Node()} - for _, extender := range extenders { - var err error - var failedNodesMap map[string]string - filteredNodes, failedNodesMap, err = extender.Filter(pod, filteredNodes, nodeNameToInfo) - if err != nil { - return false, err - } - if _, found := failedNodesMap[nodeName]; found || len(filteredNodes) == 0 { - return false, nil - } - } - return true, nil -} - -// selectVictimsOnNode finds minimum set of pods on the given node that should -// be preempted in order to make enough room for "pod" to be scheduled. The -// minimum set selected is subject to the constraint that a higher-priority pod -// is never preempted when a lower-priority pod could be (higher/lower relative -// to one another, not relative to the preemptor "pod"). -// The algorithm first checks if the pod can be scheduled on the node when all the -// lower priority pods are gone. If so, it sorts all the lower priority pods by -// their priority and starts from the highest priority one, tries to keep as -// many of them as possible while checking that the "pod" can still fit on the node. -// NOTE: This function assumes that it is never called if "pod" cannot be scheduled -// due to pod affinity, node affinity, or node anti-affinity reasons. None of -// these predicates can be satisfied by removing more pods from the node. -// TODO(bsalamat): Add support for PodDisruptionBudget. -func selectVictimsOnNode( - pod *v1.Pod, - meta algorithm.PredicateMetadata, - nodeInfo *schedulercache.NodeInfo, - fitPredicates map[string]algorithm.FitPredicate) ([]*v1.Pod, bool) { - potentialVictims := util.SortableList{CompFunc: util.HigherPriorityPod} - nodeInfoCopy := nodeInfo.Clone() - - removePod := func(rp *v1.Pod) { - nodeInfoCopy.RemovePod(rp) - if meta != nil { - meta.RemovePod(rp) - } - } - addPod := func(ap *v1.Pod) { - nodeInfoCopy.AddPod(ap) - if meta != nil { - meta.AddPod(ap, nodeInfoCopy) - } - } - // As the first step, remove all the lower priority pods from the node and - // check if the given pod can be scheduled. - podPriority := util.GetPodPriority(pod) - for _, p := range nodeInfoCopy.Pods() { - if util.GetPodPriority(p) < podPriority { - potentialVictims.Items = append(potentialVictims.Items, p) - removePod(p) - } - } - potentialVictims.Sort() - // If the new pod does not fit after removing all the lower priority pods, - // we are almost done and this node is not suitable for preemption. The only condition - // that we should check is if the "pod" is failing to schedule due to pod affinity - // failure. - // TODO(bsalamat): Consider checking affinity to lower priority pods if feasible with reasonable performance. - if fits, _, err := podFitsOnNode(pod, meta, nodeInfoCopy, fitPredicates, nil); !fits { - if err != nil { - glog.Warningf("Encountered error while selecting victims on node %v: %v", nodeInfo.Node().Name, err) - } - return nil, false - } - victims := []*v1.Pod{} - // Try to reprieve as many pods as possible starting from the highest priority one. - for _, p := range potentialVictims.Items { - lpp := p.(*v1.Pod) - addPod(lpp) - if fits, _, _ := podFitsOnNode(pod, meta, nodeInfoCopy, fitPredicates, nil); !fits { - removePod(lpp) - victims = append(victims, lpp) - glog.V(5).Infof("Pod %v is a potential preemption victim on node %v.", lpp.Name, nodeInfo.Node().Name) - } - } - return victims, true -} - -// nodesWherePreemptionMightHelp returns a list of nodes with failed predicates -// that may be satisfied by removing pods from the node. -func nodesWherePreemptionMightHelp(pod *v1.Pod, nodes []*v1.Node, failedPredicatesMap FailedPredicateMap) []*v1.Node { - potentialNodes := []*v1.Node{} - for _, node := range nodes { - unresolvableReasonExist := false - failedPredicates, found := failedPredicatesMap[node.Name] - // If we assume that scheduler looks at all nodes and populates the failedPredicateMap - // (which is the case today), the !found case should never happen, but we'd prefer - // to rely less on such assumptions in the code when checking does not impose - // significant overhead. - for _, failedPredicate := range failedPredicates { - switch failedPredicate { - case - predicates.ErrNodeSelectorNotMatch, - predicates.ErrPodNotMatchHostName, - predicates.ErrTaintsTolerationsNotMatch, - predicates.ErrNodeLabelPresenceViolated, - predicates.ErrNodeNotReady, - predicates.ErrNodeNetworkUnavailable, - predicates.ErrNodeUnschedulable, - predicates.ErrNodeUnknownCondition: - unresolvableReasonExist = true - break - // TODO(bsalamat): Please add affinity failure cases once we have specific affinity failure errors. - } - } - if !found || !unresolvableReasonExist { - glog.V(3).Infof("Node %v is a potential node for preemption.", node.Name) - potentialNodes = append(potentialNodes, node) - } - } - return potentialNodes -} - -// podEligibleToPreemptOthers determines whether this pod should be considered -// for preempting other pods or not. If this pod has already preempted other -// pods and those are in their graceful termination period, it shouldn't be -// considered for preemption. -// We look at the node that is nominated for this pod and as long as there are -// terminating pods on the node, we don't consider this for preempting more pods. -// TODO(bsalamat): Revisit this algorithm once scheduling by priority is added. -func podEligibleToPreemptOthers(pod *v1.Pod, nodeNameToInfo map[string]*schedulercache.NodeInfo) bool { - if nodeName, found := pod.Annotations[NominatedNodeAnnotationKey]; found { - if nodeInfo, found := nodeNameToInfo[nodeName]; found { - for _, p := range nodeInfo.Pods() { - if p.DeletionTimestamp != nil && util.GetPodPriority(p) < util.GetPodPriority(pod) { - // There is a terminating pod on the nominated node. - return false - } - } - } - } - return true -} - -func NewGenericScheduler( - cache schedulercache.Cache, - eCache *EquivalenceCache, - predicates map[string]algorithm.FitPredicate, - predicateMetaProducer algorithm.PredicateMetadataProducer, - prioritizers []algorithm.PriorityConfig, - priorityMetaProducer algorithm.MetadataProducer, - extenders []algorithm.SchedulerExtender) algorithm.ScheduleAlgorithm { - return &genericScheduler{ - cache: cache, - equivalenceCache: eCache, - predicates: predicates, - predicateMetaProducer: predicateMetaProducer, - prioritizers: prioritizers, - priorityMetaProducer: priorityMetaProducer, - extenders: extenders, - cachedNodeInfoMap: make(map[string]*schedulercache.NodeInfo), - } -} diff --git a/plugin/pkg/scheduler/factory/BUILD b/plugin/pkg/scheduler/factory/BUILD deleted file mode 100644 index aef3872f00a..00000000000 --- a/plugin/pkg/scheduler/factory/BUILD +++ /dev/null @@ -1,90 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = [ - "factory.go", - "plugins.go", - ], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/factory", - deps = [ - "//pkg/api/helper:go_default_library", - "//pkg/api/v1/pod:go_default_library", - "//pkg/kubelet/apis:go_default_library", - "//plugin/pkg/scheduler:go_default_library", - "//plugin/pkg/scheduler/algorithm:go_default_library", - "//plugin/pkg/scheduler/algorithm/predicates:go_default_library", - "//plugin/pkg/scheduler/algorithm/priorities:go_default_library", - "//plugin/pkg/scheduler/api:go_default_library", - "//plugin/pkg/scheduler/api/validation:go_default_library", - "//plugin/pkg/scheduler/core:go_default_library", - "//plugin/pkg/scheduler/schedulercache:go_default_library", - "//plugin/pkg/scheduler/util:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", - "//vendor/k8s.io/client-go/informers/apps/v1beta1:go_default_library", - "//vendor/k8s.io/client-go/informers/core/v1:go_default_library", - "//vendor/k8s.io/client-go/informers/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/listers/apps/v1beta1:go_default_library", - "//vendor/k8s.io/client-go/listers/core/v1:go_default_library", - "//vendor/k8s.io/client-go/listers/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/client-go/tools/cache:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = [ - "factory_test.go", - "plugins_test.go", - ], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/factory", - library = ":go_default_library", - deps = [ - "//pkg/api:go_default_library", - "//pkg/api/testing:go_default_library", - "//plugin/pkg/scheduler/algorithm:go_default_library", - "//plugin/pkg/scheduler/api:go_default_library", - "//plugin/pkg/scheduler/api/latest:go_default_library", - "//plugin/pkg/scheduler/schedulercache:go_default_library", - "//plugin/pkg/scheduler/testing:go_default_library", - "//plugin/pkg/scheduler/util:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/client-go/informers:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - "//vendor/k8s.io/client-go/tools/cache:go_default_library", - "//vendor/k8s.io/client-go/util/testing:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/plugin/pkg/scheduler/factory/factory.go b/plugin/pkg/scheduler/factory/factory.go deleted file mode 100644 index 0d46abb7980..00000000000 --- a/plugin/pkg/scheduler/factory/factory.go +++ /dev/null @@ -1,1072 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package factory can set up a scheduler. This code is here instead of -// plugin/cmd/scheduler for both testability and reuse. -package factory - -import ( - "encoding/json" - "fmt" - "reflect" - "time" - - "github.com/golang/glog" - - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/apimachinery/pkg/util/sets" - appsinformers "k8s.io/client-go/informers/apps/v1beta1" - coreinformers "k8s.io/client-go/informers/core/v1" - extensionsinformers "k8s.io/client-go/informers/extensions/v1beta1" - clientset "k8s.io/client-go/kubernetes" - appslisters "k8s.io/client-go/listers/apps/v1beta1" - corelisters "k8s.io/client-go/listers/core/v1" - extensionslisters "k8s.io/client-go/listers/extensions/v1beta1" - "k8s.io/client-go/tools/cache" - "k8s.io/kubernetes/pkg/api/helper" - podutil "k8s.io/kubernetes/pkg/api/v1/pod" - kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis" - "k8s.io/kubernetes/plugin/pkg/scheduler" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/api/validation" - "k8s.io/kubernetes/plugin/pkg/scheduler/core" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" - "k8s.io/kubernetes/plugin/pkg/scheduler/util" -) - -const ( - initialGetBackoff = 100 * time.Millisecond - maximalGetBackoff = time.Minute -) - -var ( - serviceAffinitySet = sets.NewString("ServiceAffinity") - maxPDVolumeCountPredicateSet = sets.NewString("MaxPDVolumeCountPredicate") - matchInterPodAffinitySet = sets.NewString("MatchInterPodAffinity") - generalPredicatesSets = sets.NewString("GeneralPredicates") - noDiskConflictSet = sets.NewString("NoDiskConflict") -) - -// configFactory is the default implementation of the scheduler.Configurator interface. -type configFactory struct { - client clientset.Interface - // queue for pods that need scheduling - podQueue *cache.FIFO - // a means to list all known scheduled pods. - scheduledPodLister corelisters.PodLister - // a means to list all known scheduled pods and pods assumed to have been scheduled. - podLister algorithm.PodLister - // a means to list all nodes - nodeLister corelisters.NodeLister - // a means to list all PersistentVolumes - pVLister corelisters.PersistentVolumeLister - // a means to list all PersistentVolumeClaims - pVCLister corelisters.PersistentVolumeClaimLister - // a means to list all services - serviceLister corelisters.ServiceLister - // a means to list all controllers - controllerLister corelisters.ReplicationControllerLister - // a means to list all replicasets - replicaSetLister extensionslisters.ReplicaSetLister - // a means to list all statefulsets - statefulSetLister appslisters.StatefulSetLister - - // Close this to stop all reflectors - StopEverything chan struct{} - - scheduledPodsHasSynced cache.InformerSynced - - schedulerCache schedulercache.Cache - - // SchedulerName of a scheduler is used to select which pods will be - // processed by this scheduler, based on pods's "spec.SchedulerName". - schedulerName string - - // RequiredDuringScheduling affinity is not symmetric, but there is an implicit PreferredDuringScheduling affinity rule - // corresponding to every RequiredDuringScheduling affinity rule. - // HardPodAffinitySymmetricWeight represents the weight of implicit PreferredDuringScheduling affinity rule, in the range 0-100. - hardPodAffinitySymmetricWeight int - - // Equivalence class cache - equivalencePodCache *core.EquivalenceCache - - // Enable equivalence class cache - enableEquivalenceClassCache bool -} - -// NewConfigFactory initializes the default implementation of a Configurator To encourage eventual privatization of the struct type, we only -// return the interface. -func NewConfigFactory( - schedulerName string, - client clientset.Interface, - nodeInformer coreinformers.NodeInformer, - podInformer coreinformers.PodInformer, - pvInformer coreinformers.PersistentVolumeInformer, - pvcInformer coreinformers.PersistentVolumeClaimInformer, - replicationControllerInformer coreinformers.ReplicationControllerInformer, - replicaSetInformer extensionsinformers.ReplicaSetInformer, - statefulSetInformer appsinformers.StatefulSetInformer, - serviceInformer coreinformers.ServiceInformer, - hardPodAffinitySymmetricWeight int, - enableEquivalenceClassCache bool, -) scheduler.Configurator { - stopEverything := make(chan struct{}) - schedulerCache := schedulercache.New(30*time.Second, stopEverything) - - c := &configFactory{ - client: client, - podLister: schedulerCache, - podQueue: cache.NewFIFO(cache.MetaNamespaceKeyFunc), - pVLister: pvInformer.Lister(), - pVCLister: pvcInformer.Lister(), - serviceLister: serviceInformer.Lister(), - controllerLister: replicationControllerInformer.Lister(), - replicaSetLister: replicaSetInformer.Lister(), - statefulSetLister: statefulSetInformer.Lister(), - schedulerCache: schedulerCache, - StopEverything: stopEverything, - schedulerName: schedulerName, - hardPodAffinitySymmetricWeight: hardPodAffinitySymmetricWeight, - enableEquivalenceClassCache: enableEquivalenceClassCache, - } - - c.scheduledPodsHasSynced = podInformer.Informer().HasSynced - // scheduled pod cache - podInformer.Informer().AddEventHandler( - cache.FilteringResourceEventHandler{ - FilterFunc: func(obj interface{}) bool { - switch t := obj.(type) { - case *v1.Pod: - return assignedNonTerminatedPod(t) - default: - runtime.HandleError(fmt.Errorf("unable to handle object in %T: %T", c, obj)) - return false - } - }, - Handler: cache.ResourceEventHandlerFuncs{ - AddFunc: c.addPodToCache, - UpdateFunc: c.updatePodInCache, - DeleteFunc: c.deletePodFromCache, - }, - }, - ) - // unscheduled pod queue - podInformer.Informer().AddEventHandler( - cache.FilteringResourceEventHandler{ - FilterFunc: func(obj interface{}) bool { - switch t := obj.(type) { - case *v1.Pod: - return unassignedNonTerminatedPod(t) - default: - runtime.HandleError(fmt.Errorf("unable to handle object in %T: %T", c, obj)) - return false - } - }, - Handler: cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - if err := c.podQueue.Add(obj); err != nil { - runtime.HandleError(fmt.Errorf("unable to queue %T: %v", obj, err)) - } - }, - UpdateFunc: func(oldObj, newObj interface{}) { - if c.skipPodUpdate(newObj.(*v1.Pod)) { - return - } - if err := c.podQueue.Update(newObj); err != nil { - runtime.HandleError(fmt.Errorf("unable to update %T: %v", newObj, err)) - } - }, - DeleteFunc: func(obj interface{}) { - if err := c.podQueue.Delete(obj); err != nil { - runtime.HandleError(fmt.Errorf("unable to dequeue %T: %v", obj, err)) - } - }, - }, - }, - ) - // ScheduledPodLister is something we provide to plug-in functions that - // they may need to call. - c.scheduledPodLister = assignedPodLister{podInformer.Lister()} - - nodeInformer.Informer().AddEventHandler( - cache.ResourceEventHandlerFuncs{ - AddFunc: c.addNodeToCache, - UpdateFunc: c.updateNodeInCache, - DeleteFunc: c.deleteNodeFromCache, - }, - ) - c.nodeLister = nodeInformer.Lister() - - // On add and delete of PVs, it will affect equivalence cache items - // related to persistent volume - pvInformer.Informer().AddEventHandler( - cache.ResourceEventHandlerFuncs{ - // MaxPDVolumeCountPredicate: since it relies on the counts of PV. - AddFunc: c.onPvAdd, - DeleteFunc: c.onPvDelete, - }, - ) - c.pVLister = pvInformer.Lister() - - // This is for MaxPDVolumeCountPredicate: add/delete PVC will affect counts of PV when it is bound. - pvcInformer.Informer().AddEventHandler( - cache.ResourceEventHandlerFuncs{ - AddFunc: c.onPvcAdd, - DeleteFunc: c.onPvcDelete, - }, - ) - c.pVCLister = pvcInformer.Lister() - - // This is for ServiceAffinity: affected by the selector of the service is updated. - // Also, if new service is added, equivalence cache will also become invalid since - // existing pods may be "captured" by this service and change this predicate result. - serviceInformer.Informer().AddEventHandler( - cache.ResourceEventHandlerFuncs{ - AddFunc: c.onServiceAdd, - UpdateFunc: c.onServiceUpdate, - DeleteFunc: c.onServiceDelete, - }, - ) - c.serviceLister = serviceInformer.Lister() - - // Existing equivalence cache should not be affected by add/delete RC/Deployment etc, - // it only make sense when pod is scheduled or deleted - - return c -} - -// skipPodUpdate checks whether the specified pod update should be ignored. -// This function will return true if -// - The pod has already been assumed, AND -// - The pod has only its ResourceVersion, Spec.NodeName and/or Annotations -// updated. -func (c *configFactory) skipPodUpdate(pod *v1.Pod) bool { - // Non-assumed pods should never be skipped. - isAssumed, err := c.schedulerCache.IsAssumedPod(pod) - if err != nil { - runtime.HandleError(fmt.Errorf("failed to check whether pod %s/%s is assumed: %v", pod.Namespace, pod.Name, err)) - return false - } - if !isAssumed { - return false - } - - // Gets the assumed pod from the cache. - assumedPod, err := c.schedulerCache.GetPod(pod) - if err != nil { - runtime.HandleError(fmt.Errorf("failed to get assumed pod %s/%s from cache: %v", pod.Namespace, pod.Name, err)) - return false - } - - // Compares the assumed pod in the cache with the pod update. If they are - // equal (with certain fields excluded), this pod update will be skipped. - f := func(pod *v1.Pod) *v1.Pod { - p := pod.DeepCopy() - // ResourceVersion must be excluded because each object update will - // have a new resource version. - p.ResourceVersion = "" - // Spec.NodeName must be excluded because the pod assumed in the cache - // is expected to have a node assigned while the pod update may nor may - // not have this field set. - p.Spec.NodeName = "" - // Annotations must be excluded for the reasons described in - // https://github.com/kubernetes/kubernetes/issues/52914. - p.Annotations = nil - return p - } - assumedPodCopy, podCopy := f(assumedPod), f(pod) - if !reflect.DeepEqual(assumedPodCopy, podCopy) { - return false - } - glog.V(3).Infof("Skipping pod %s/%s update", pod.Namespace, pod.Name) - return true -} - -func (c *configFactory) onPvAdd(obj interface{}) { - if c.enableEquivalenceClassCache { - pv, ok := obj.(*v1.PersistentVolume) - if !ok { - glog.Errorf("cannot convert to *v1.PersistentVolume: %v", obj) - return - } - c.invalidatePredicatesForPv(pv) - } -} - -func (c *configFactory) onPvDelete(obj interface{}) { - if c.enableEquivalenceClassCache { - var pv *v1.PersistentVolume - switch t := obj.(type) { - case *v1.PersistentVolume: - pv = t - case cache.DeletedFinalStateUnknown: - var ok bool - pv, ok = t.Obj.(*v1.PersistentVolume) - if !ok { - glog.Errorf("cannot convert to *v1.PersistentVolume: %v", t.Obj) - return - } - default: - glog.Errorf("cannot convert to *v1.PersistentVolume: %v", t) - return - } - c.invalidatePredicatesForPv(pv) - } -} - -func (c *configFactory) invalidatePredicatesForPv(pv *v1.PersistentVolume) { - invalidPredicates := sets.NewString("MaxPDVolumeCountPredicate") - if pv.Spec.AWSElasticBlockStore != nil { - invalidPredicates.Insert("MaxEBSVolumeCount") - } - if pv.Spec.GCEPersistentDisk != nil { - invalidPredicates.Insert("MaxGCEPDVolumeCount") - } - if pv.Spec.AzureDisk != nil { - invalidPredicates.Insert("MaxAzureDiskVolumeCount") - } - c.equivalencePodCache.InvalidateCachedPredicateItemOfAllNodes(invalidPredicates) -} - -func (c *configFactory) onPvcAdd(obj interface{}) { - if c.enableEquivalenceClassCache { - pvc, ok := obj.(*v1.PersistentVolumeClaim) - if !ok { - glog.Errorf("cannot convert to *v1.PersistentVolumeClaim: %v", obj) - return - } - c.invalidatePredicatesForPvc(pvc) - } -} - -func (c *configFactory) onPvcDelete(obj interface{}) { - if c.enableEquivalenceClassCache { - var pvc *v1.PersistentVolumeClaim - switch t := obj.(type) { - case *v1.PersistentVolumeClaim: - pvc = t - case cache.DeletedFinalStateUnknown: - var ok bool - pvc, ok = t.Obj.(*v1.PersistentVolumeClaim) - if !ok { - glog.Errorf("cannot convert to *v1.PersistentVolumeClaim: %v", t.Obj) - return - } - default: - glog.Errorf("cannot convert to *v1.PersistentVolumeClaim: %v", t) - return - } - c.invalidatePredicatesForPvc(pvc) - } -} - -func (c *configFactory) invalidatePredicatesForPvc(pvc *v1.PersistentVolumeClaim) { - if pvc.Spec.VolumeName != "" { - c.equivalencePodCache.InvalidateCachedPredicateItemOfAllNodes(maxPDVolumeCountPredicateSet) - } -} - -func (c *configFactory) onServiceAdd(obj interface{}) { - if c.enableEquivalenceClassCache { - c.equivalencePodCache.InvalidateCachedPredicateItemOfAllNodes(serviceAffinitySet) - } -} - -func (c *configFactory) onServiceUpdate(oldObj interface{}, newObj interface{}) { - if c.enableEquivalenceClassCache { - // TODO(resouer) We may need to invalidate this for specified group of pods only - oldService := oldObj.(*v1.Service) - newService := newObj.(*v1.Service) - if !reflect.DeepEqual(oldService.Spec.Selector, newService.Spec.Selector) { - c.equivalencePodCache.InvalidateCachedPredicateItemOfAllNodes(serviceAffinitySet) - } - } -} - -func (c *configFactory) onServiceDelete(obj interface{}) { - if c.enableEquivalenceClassCache { - c.equivalencePodCache.InvalidateCachedPredicateItemOfAllNodes(serviceAffinitySet) - } -} - -// GetNodeStore provides the cache to the nodes, mostly internal use, but may also be called by mock-tests. -func (c *configFactory) GetNodeLister() corelisters.NodeLister { - return c.nodeLister -} - -func (c *configFactory) GetHardPodAffinitySymmetricWeight() int { - return c.hardPodAffinitySymmetricWeight -} - -func (f *configFactory) GetSchedulerName() string { - return f.schedulerName -} - -// GetClient provides a kubernetes client, mostly internal use, but may also be called by mock-tests. -func (f *configFactory) GetClient() clientset.Interface { - return f.client -} - -// GetScheduledPodListerIndexer provides a pod lister, mostly internal use, but may also be called by mock-tests. -func (c *configFactory) GetScheduledPodLister() corelisters.PodLister { - return c.scheduledPodLister -} - -func (c *configFactory) addPodToCache(obj interface{}) { - pod, ok := obj.(*v1.Pod) - if !ok { - glog.Errorf("cannot convert to *v1.Pod: %v", obj) - return - } - - if err := c.schedulerCache.AddPod(pod); err != nil { - glog.Errorf("scheduler cache AddPod failed: %v", err) - } - // NOTE: Updating equivalence cache of addPodToCache has been - // handled optimistically in InvalidateCachedPredicateItemForPodAdd. -} - -func (c *configFactory) updatePodInCache(oldObj, newObj interface{}) { - oldPod, ok := oldObj.(*v1.Pod) - if !ok { - glog.Errorf("cannot convert oldObj to *v1.Pod: %v", oldObj) - return - } - newPod, ok := newObj.(*v1.Pod) - if !ok { - glog.Errorf("cannot convert newObj to *v1.Pod: %v", newObj) - return - } - - if err := c.schedulerCache.UpdatePod(oldPod, newPod); err != nil { - glog.Errorf("scheduler cache UpdatePod failed: %v", err) - } - - c.invalidateCachedPredicatesOnUpdatePod(newPod, oldPod) -} - -func (c *configFactory) invalidateCachedPredicatesOnUpdatePod(newPod *v1.Pod, oldPod *v1.Pod) { - if c.enableEquivalenceClassCache { - // if the pod does not have binded node, updating equivalence cache is meaningless; - // if pod's binded node has been changed, that case should be handled by pod add & delete. - if len(newPod.Spec.NodeName) != 0 && newPod.Spec.NodeName == oldPod.Spec.NodeName { - if !reflect.DeepEqual(oldPod.GetLabels(), newPod.GetLabels()) { - // MatchInterPodAffinity need to be reconsidered for this node, - // as well as all nodes in its same failure domain. - c.equivalencePodCache.InvalidateCachedPredicateItemOfAllNodes( - matchInterPodAffinitySet) - } - // if requested container resource changed, invalidate GeneralPredicates of this node - if !reflect.DeepEqual(predicates.GetResourceRequest(newPod), - predicates.GetResourceRequest(oldPod)) { - c.equivalencePodCache.InvalidateCachedPredicateItem( - newPod.Spec.NodeName, generalPredicatesSets) - } - } - } -} - -func (c *configFactory) deletePodFromCache(obj interface{}) { - var pod *v1.Pod - switch t := obj.(type) { - case *v1.Pod: - pod = t - case cache.DeletedFinalStateUnknown: - var ok bool - pod, ok = t.Obj.(*v1.Pod) - if !ok { - glog.Errorf("cannot convert to *v1.Pod: %v", t.Obj) - return - } - default: - glog.Errorf("cannot convert to *v1.Pod: %v", t) - return - } - if err := c.schedulerCache.RemovePod(pod); err != nil { - glog.Errorf("scheduler cache RemovePod failed: %v", err) - } - - c.invalidateCachedPredicatesOnDeletePod(pod) -} - -func (c *configFactory) invalidateCachedPredicatesOnDeletePod(pod *v1.Pod) { - if c.enableEquivalenceClassCache { - // part of this case is the same as pod add. - c.equivalencePodCache.InvalidateCachedPredicateItemForPodAdd(pod, pod.Spec.NodeName) - // MatchInterPodAffinity need to be reconsidered for this node, - // as well as all nodes in its same failure domain. - // TODO(resouer) can we just do this for nodes in the same failure domain - c.equivalencePodCache.InvalidateCachedPredicateItemOfAllNodes( - matchInterPodAffinitySet) - - // if this pod have these PV, cached result of disk conflict will become invalid. - for _, volume := range pod.Spec.Volumes { - if volume.GCEPersistentDisk != nil || volume.AWSElasticBlockStore != nil || - volume.RBD != nil || volume.ISCSI != nil { - c.equivalencePodCache.InvalidateCachedPredicateItem( - pod.Spec.NodeName, noDiskConflictSet) - } - } - } -} - -func (c *configFactory) addNodeToCache(obj interface{}) { - node, ok := obj.(*v1.Node) - if !ok { - glog.Errorf("cannot convert to *v1.Node: %v", obj) - return - } - - if err := c.schedulerCache.AddNode(node); err != nil { - glog.Errorf("scheduler cache AddNode failed: %v", err) - } - - // NOTE: add a new node does not affect existing predicates in equivalence cache -} - -func (c *configFactory) updateNodeInCache(oldObj, newObj interface{}) { - oldNode, ok := oldObj.(*v1.Node) - if !ok { - glog.Errorf("cannot convert oldObj to *v1.Node: %v", oldObj) - return - } - newNode, ok := newObj.(*v1.Node) - if !ok { - glog.Errorf("cannot convert newObj to *v1.Node: %v", newObj) - return - } - - if err := c.schedulerCache.UpdateNode(oldNode, newNode); err != nil { - glog.Errorf("scheduler cache UpdateNode failed: %v", err) - } - - c.invalidateCachedPredicatesOnNodeUpdate(newNode, oldNode) -} - -func (c *configFactory) invalidateCachedPredicatesOnNodeUpdate(newNode *v1.Node, oldNode *v1.Node) { - if c.enableEquivalenceClassCache { - // Begin to update equivalence cache based on node update - // TODO(resouer): think about lazily initialize this set - invalidPredicates := sets.NewString() - - if !reflect.DeepEqual(oldNode.Status.Allocatable, newNode.Status.Allocatable) { - invalidPredicates.Insert("GeneralPredicates") // "PodFitsResources" - } - if !reflect.DeepEqual(oldNode.GetLabels(), newNode.GetLabels()) { - invalidPredicates.Insert("GeneralPredicates", "ServiceAffinity") // "PodSelectorMatches" - for k, v := range oldNode.GetLabels() { - // any label can be topology key of pod, we have to invalidate in all cases - if v != newNode.GetLabels()[k] { - invalidPredicates.Insert("MatchInterPodAffinity") - } - // NoVolumeZoneConflict will only be affected by zone related label change - if k == kubeletapis.LabelZoneFailureDomain || k == kubeletapis.LabelZoneRegion { - if v != newNode.GetLabels()[k] { - invalidPredicates.Insert("NoVolumeZoneConflict") - } - } - } - } - - oldTaints, oldErr := helper.GetTaintsFromNodeAnnotations(oldNode.GetAnnotations()) - if oldErr != nil { - glog.Errorf("Failed to get taints from old node annotation for equivalence cache") - } - newTaints, newErr := helper.GetTaintsFromNodeAnnotations(newNode.GetAnnotations()) - if newErr != nil { - glog.Errorf("Failed to get taints from new node annotation for equivalence cache") - } - if !reflect.DeepEqual(oldTaints, newTaints) || - !reflect.DeepEqual(oldNode.Spec.Taints, newNode.Spec.Taints) { - invalidPredicates.Insert("PodToleratesNodeTaints") - } - - if !reflect.DeepEqual(oldNode.Status.Conditions, newNode.Status.Conditions) { - oldConditions := make(map[v1.NodeConditionType]v1.ConditionStatus) - newConditions := make(map[v1.NodeConditionType]v1.ConditionStatus) - for _, cond := range oldNode.Status.Conditions { - oldConditions[cond.Type] = cond.Status - } - for _, cond := range newNode.Status.Conditions { - newConditions[cond.Type] = cond.Status - } - if oldConditions[v1.NodeMemoryPressure] != newConditions[v1.NodeMemoryPressure] { - invalidPredicates.Insert("CheckNodeMemoryPressure") - } - if oldConditions[v1.NodeDiskPressure] != newConditions[v1.NodeDiskPressure] { - invalidPredicates.Insert("CheckNodeDiskPressure") - } - if oldConditions[v1.NodeReady] != newConditions[v1.NodeReady] || - oldConditions[v1.NodeOutOfDisk] != newConditions[v1.NodeOutOfDisk] || - oldConditions[v1.NodeNetworkUnavailable] != newConditions[v1.NodeNetworkUnavailable] || - newNode.Spec.Unschedulable != oldNode.Spec.Unschedulable { - invalidPredicates.Insert("CheckNodeCondition") - } - } - c.equivalencePodCache.InvalidateCachedPredicateItem(newNode.GetName(), invalidPredicates) - } -} - -func (c *configFactory) deleteNodeFromCache(obj interface{}) { - var node *v1.Node - switch t := obj.(type) { - case *v1.Node: - node = t - case cache.DeletedFinalStateUnknown: - var ok bool - node, ok = t.Obj.(*v1.Node) - if !ok { - glog.Errorf("cannot convert to *v1.Node: %v", t.Obj) - return - } - default: - glog.Errorf("cannot convert to *v1.Node: %v", t) - return - } - if err := c.schedulerCache.RemoveNode(node); err != nil { - glog.Errorf("scheduler cache RemoveNode failed: %v", err) - } - if c.enableEquivalenceClassCache { - c.equivalencePodCache.InvalidateAllCachedPredicateItemOfNode(node.GetName()) - } -} - -// Create creates a scheduler with the default algorithm provider. -func (f *configFactory) Create() (*scheduler.Config, error) { - return f.CreateFromProvider(DefaultProvider) -} - -// Creates a scheduler from the name of a registered algorithm provider. -func (f *configFactory) CreateFromProvider(providerName string) (*scheduler.Config, error) { - glog.V(2).Infof("Creating scheduler from algorithm provider '%v'", providerName) - provider, err := GetAlgorithmProvider(providerName) - if err != nil { - return nil, err - } - - return f.CreateFromKeys(provider.FitPredicateKeys, provider.PriorityFunctionKeys, []algorithm.SchedulerExtender{}) -} - -// Creates a scheduler from the configuration file -func (f *configFactory) CreateFromConfig(policy schedulerapi.Policy) (*scheduler.Config, error) { - glog.V(2).Infof("Creating scheduler from configuration: %v", policy) - - // validate the policy configuration - if err := validation.ValidatePolicy(policy); err != nil { - return nil, err - } - - predicateKeys := sets.NewString() - for _, predicate := range policy.Predicates { - glog.V(2).Infof("Registering predicate: %s", predicate.Name) - predicateKeys.Insert(RegisterCustomFitPredicate(predicate)) - } - - priorityKeys := sets.NewString() - for _, priority := range policy.Priorities { - glog.V(2).Infof("Registering priority: %s", priority.Name) - priorityKeys.Insert(RegisterCustomPriorityFunction(priority)) - } - - extenders := make([]algorithm.SchedulerExtender, 0) - if len(policy.ExtenderConfigs) != 0 { - for ii := range policy.ExtenderConfigs { - glog.V(2).Infof("Creating extender with config %+v", policy.ExtenderConfigs[ii]) - if extender, err := core.NewHTTPExtender(&policy.ExtenderConfigs[ii]); err != nil { - return nil, err - } else { - extenders = append(extenders, extender) - } - } - } - // Providing HardPodAffinitySymmetricWeight in the policy config is the new and preferred way of providing the value. - // Give it higher precedence than scheduler CLI configuration when it is provided. - if policy.HardPodAffinitySymmetricWeight != 0 { - f.hardPodAffinitySymmetricWeight = policy.HardPodAffinitySymmetricWeight - } - return f.CreateFromKeys(predicateKeys, priorityKeys, extenders) -} - -// getBinder returns an extender that supports bind or a default binder. -func (f *configFactory) getBinder(extenders []algorithm.SchedulerExtender) scheduler.Binder { - for i := range extenders { - if extenders[i].IsBinder() { - return extenders[i] - } - } - return &binder{f.client} -} - -// Creates a scheduler from a set of registered fit predicate keys and priority keys. -func (f *configFactory) CreateFromKeys(predicateKeys, priorityKeys sets.String, extenders []algorithm.SchedulerExtender) (*scheduler.Config, error) { - glog.V(2).Infof("Creating scheduler with fit predicates '%v' and priority functions '%v'", predicateKeys, priorityKeys) - - if f.GetHardPodAffinitySymmetricWeight() < 1 || f.GetHardPodAffinitySymmetricWeight() > 100 { - return nil, fmt.Errorf("invalid hardPodAffinitySymmetricWeight: %d, must be in the range 1-100", f.GetHardPodAffinitySymmetricWeight()) - } - - predicateFuncs, err := f.GetPredicates(predicateKeys) - if err != nil { - return nil, err - } - - priorityConfigs, err := f.GetPriorityFunctionConfigs(priorityKeys) - if err != nil { - return nil, err - } - - priorityMetaProducer, err := f.GetPriorityMetadataProducer() - if err != nil { - return nil, err - } - - predicateMetaProducer, err := f.GetPredicateMetadataProducer() - if err != nil { - return nil, err - } - - // Init equivalence class cache - if f.enableEquivalenceClassCache && getEquivalencePodFunc != nil { - f.equivalencePodCache = core.NewEquivalenceCache(getEquivalencePodFunc) - glog.Info("Created equivalence class cache") - } - algo := core.NewGenericScheduler(f.schedulerCache, f.equivalencePodCache, predicateFuncs, predicateMetaProducer, priorityConfigs, priorityMetaProducer, extenders) - - podBackoff := util.CreateDefaultPodBackoff() - return &scheduler.Config{ - SchedulerCache: f.schedulerCache, - Ecache: f.equivalencePodCache, - // The scheduler only needs to consider schedulable nodes. - NodeLister: &nodeLister{f.nodeLister}, - Algorithm: algo, - Binder: f.getBinder(extenders), - PodConditionUpdater: &podConditionUpdater{f.client}, - PodPreemptor: &podPreemptor{f.client}, - WaitForCacheSync: func() bool { - return cache.WaitForCacheSync(f.StopEverything, f.scheduledPodsHasSynced) - }, - NextPod: func() *v1.Pod { - return f.getNextPod() - }, - Error: f.MakeDefaultErrorFunc(podBackoff, f.podQueue), - StopEverything: f.StopEverything, - }, nil -} - -type nodeLister struct { - corelisters.NodeLister -} - -func (n *nodeLister) List() ([]*v1.Node, error) { - return n.NodeLister.List(labels.Everything()) -} - -func (f *configFactory) GetPriorityFunctionConfigs(priorityKeys sets.String) ([]algorithm.PriorityConfig, error) { - pluginArgs, err := f.getPluginArgs() - if err != nil { - return nil, err - } - - return getPriorityFunctionConfigs(priorityKeys, *pluginArgs) -} - -func (f *configFactory) GetPriorityMetadataProducer() (algorithm.MetadataProducer, error) { - pluginArgs, err := f.getPluginArgs() - if err != nil { - return nil, err - } - - return getPriorityMetadataProducer(*pluginArgs) -} - -func (f *configFactory) GetPredicateMetadataProducer() (algorithm.PredicateMetadataProducer, error) { - pluginArgs, err := f.getPluginArgs() - if err != nil { - return nil, err - } - return getPredicateMetadataProducer(*pluginArgs) -} - -func (f *configFactory) GetPredicates(predicateKeys sets.String) (map[string]algorithm.FitPredicate, error) { - pluginArgs, err := f.getPluginArgs() - if err != nil { - return nil, err - } - - return getFitPredicateFunctions(predicateKeys, *pluginArgs) -} - -func (f *configFactory) getPluginArgs() (*PluginFactoryArgs, error) { - return &PluginFactoryArgs{ - PodLister: f.podLister, - ServiceLister: f.serviceLister, - ControllerLister: f.controllerLister, - ReplicaSetLister: f.replicaSetLister, - StatefulSetLister: f.statefulSetLister, - NodeLister: &nodeLister{f.nodeLister}, - NodeInfo: &predicates.CachedNodeInfo{NodeLister: f.nodeLister}, - PVInfo: &predicates.CachedPersistentVolumeInfo{PersistentVolumeLister: f.pVLister}, - PVCInfo: &predicates.CachedPersistentVolumeClaimInfo{PersistentVolumeClaimLister: f.pVCLister}, - HardPodAffinitySymmetricWeight: f.hardPodAffinitySymmetricWeight, - }, nil -} - -func (f *configFactory) getNextPod() *v1.Pod { - for { - pod := cache.Pop(f.podQueue).(*v1.Pod) - if f.ResponsibleForPod(pod) { - glog.V(4).Infof("About to try and schedule pod %v", pod.Name) - return pod - } - } -} - -func (f *configFactory) ResponsibleForPod(pod *v1.Pod) bool { - return f.schedulerName == pod.Spec.SchedulerName -} - -// unassignedNonTerminatedPod selects pods that are unassigned and non-terminal. -func unassignedNonTerminatedPod(pod *v1.Pod) bool { - if len(pod.Spec.NodeName) != 0 { - return false - } - if pod.Status.Phase == v1.PodSucceeded || pod.Status.Phase == v1.PodFailed { - return false - } - return true -} - -// assignedNonTerminatedPod selects pods that are assigned and non-terminal (scheduled and running). -func assignedNonTerminatedPod(pod *v1.Pod) bool { - if len(pod.Spec.NodeName) == 0 { - return false - } - if pod.Status.Phase == v1.PodSucceeded || pod.Status.Phase == v1.PodFailed { - return false - } - return true -} - -// assignedPodLister filters the pods returned from a PodLister to -// only include those that have a node name set. -type assignedPodLister struct { - corelisters.PodLister -} - -// List lists all Pods in the indexer for a given namespace. -func (l assignedPodLister) List(selector labels.Selector) ([]*v1.Pod, error) { - list, err := l.PodLister.List(selector) - if err != nil { - return nil, err - } - filtered := make([]*v1.Pod, 0, len(list)) - for _, pod := range list { - if len(pod.Spec.NodeName) > 0 { - filtered = append(filtered, pod) - } - } - return filtered, nil -} - -// List lists all Pods in the indexer for a given namespace. -func (l assignedPodLister) Pods(namespace string) corelisters.PodNamespaceLister { - return assignedPodNamespaceLister{l.PodLister.Pods(namespace)} -} - -// assignedPodNamespaceLister filters the pods returned from a PodNamespaceLister to -// only include those that have a node name set. -type assignedPodNamespaceLister struct { - corelisters.PodNamespaceLister -} - -// List lists all Pods in the indexer for a given namespace. -func (l assignedPodNamespaceLister) List(selector labels.Selector) (ret []*v1.Pod, err error) { - list, err := l.PodNamespaceLister.List(selector) - if err != nil { - return nil, err - } - filtered := make([]*v1.Pod, 0, len(list)) - for _, pod := range list { - if len(pod.Spec.NodeName) > 0 { - filtered = append(filtered, pod) - } - } - return filtered, nil -} - -// Get retrieves the Pod from the indexer for a given namespace and name. -func (l assignedPodNamespaceLister) Get(name string) (*v1.Pod, error) { - pod, err := l.PodNamespaceLister.Get(name) - if err != nil { - return nil, err - } - if len(pod.Spec.NodeName) > 0 { - return pod, nil - } - return nil, errors.NewNotFound(schema.GroupResource{Resource: string(v1.ResourcePods)}, name) -} - -type podInformer struct { - informer cache.SharedIndexInformer -} - -func (i *podInformer) Informer() cache.SharedIndexInformer { - return i.informer -} - -func (i *podInformer) Lister() corelisters.PodLister { - return corelisters.NewPodLister(i.informer.GetIndexer()) -} - -// NewPodInformer creates a shared index informer that returns only non-terminal pods. -func NewPodInformer(client clientset.Interface, resyncPeriod time.Duration) coreinformers.PodInformer { - selector := fields.ParseSelectorOrDie("status.phase!=" + string(v1.PodSucceeded) + ",status.phase!=" + string(v1.PodFailed)) - lw := cache.NewListWatchFromClient(client.CoreV1().RESTClient(), string(v1.ResourcePods), metav1.NamespaceAll, selector) - return &podInformer{ - informer: cache.NewSharedIndexInformer(lw, &v1.Pod{}, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}), - } -} - -func (factory *configFactory) MakeDefaultErrorFunc(backoff *util.PodBackoff, podQueue *cache.FIFO) func(pod *v1.Pod, err error) { - return func(pod *v1.Pod, err error) { - if err == core.ErrNoNodesAvailable { - glog.V(4).Infof("Unable to schedule %v %v: no nodes are registered to the cluster; waiting", pod.Namespace, pod.Name) - } else { - if _, ok := err.(*core.FitError); ok { - glog.V(4).Infof("Unable to schedule %v %v: no fit: %v; waiting", pod.Namespace, pod.Name, err) - } else { - glog.Errorf("Error scheduling %v %v: %v; retrying", pod.Namespace, pod.Name, err) - } - } - backoff.Gc() - // Retry asynchronously. - // Note that this is extremely rudimentary and we need a more real error handling path. - go func() { - defer runtime.HandleCrash() - podID := types.NamespacedName{ - Namespace: pod.Namespace, - Name: pod.Name, - } - - entry := backoff.GetEntry(podID) - if !entry.TryWait(backoff.MaxDuration()) { - glog.Warningf("Request for pod %v already in flight, abandoning", podID) - return - } - // Get the pod again; it may have changed/been scheduled already. - getBackoff := initialGetBackoff - for { - pod, err := factory.client.CoreV1().Pods(podID.Namespace).Get(podID.Name, metav1.GetOptions{}) - if err == nil { - if len(pod.Spec.NodeName) == 0 { - podQueue.AddIfNotPresent(pod) - } - break - } - if errors.IsNotFound(err) { - glog.Warningf("A pod %v no longer exists", podID) - return - } - glog.Errorf("Error getting pod %v for retry: %v; retrying...", podID, err) - if getBackoff = getBackoff * 2; getBackoff > maximalGetBackoff { - getBackoff = maximalGetBackoff - } - time.Sleep(getBackoff) - } - }() - } -} - -// nodeEnumerator allows a cache.Poller to enumerate items in an v1.NodeList -type nodeEnumerator struct { - *v1.NodeList -} - -// Len returns the number of items in the node list. -func (ne *nodeEnumerator) Len() int { - if ne.NodeList == nil { - return 0 - } - return len(ne.Items) -} - -// Get returns the item (and ID) with the particular index. -func (ne *nodeEnumerator) Get(index int) interface{} { - return &ne.Items[index] -} - -type binder struct { - Client clientset.Interface -} - -// Bind just does a POST binding RPC. -func (b *binder) Bind(binding *v1.Binding) error { - glog.V(3).Infof("Attempting to bind %v to %v", binding.Name, binding.Target.Name) - return b.Client.CoreV1().Pods(binding.Namespace).Bind(binding) -} - -type podConditionUpdater struct { - Client clientset.Interface -} - -func (p *podConditionUpdater) Update(pod *v1.Pod, condition *v1.PodCondition) error { - glog.V(2).Infof("Updating pod condition for %s/%s to (%s==%s)", pod.Namespace, pod.Name, condition.Type, condition.Status) - if podutil.UpdatePodCondition(&pod.Status, condition) { - _, err := p.Client.CoreV1().Pods(pod.Namespace).UpdateStatus(pod) - return err - } - return nil -} - -type podPreemptor struct { - Client clientset.Interface -} - -func (p *podPreemptor) GetUpdatedPod(pod *v1.Pod) (*v1.Pod, error) { - return p.Client.CoreV1().Pods(pod.Namespace).Get(pod.Name, metav1.GetOptions{}) -} - -func (p *podPreemptor) DeletePod(pod *v1.Pod) error { - return p.Client.CoreV1().Pods(pod.Namespace).Delete(pod.Name, &metav1.DeleteOptions{}) -} - -func (p *podPreemptor) UpdatePodAnnotations(pod *v1.Pod, annotations map[string]string) error { - podCopy := pod.DeepCopy() - if podCopy.Annotations == nil { - podCopy.Annotations = map[string]string{} - } - for k, v := range annotations { - podCopy.Annotations[k] = v - } - ret := &unstructured.Unstructured{} - ret.SetAnnotations(podCopy.Annotations) - patchData, err := json.Marshal(ret) - if err != nil { - return err - } - _, error := p.Client.CoreV1().Pods(podCopy.Namespace).Patch(podCopy.Name, types.MergePatchType, patchData) - return error -} diff --git a/plugin/pkg/scheduler/factory/plugins.go b/plugin/pkg/scheduler/factory/plugins.go deleted file mode 100644 index 74840d013b5..00000000000 --- a/plugin/pkg/scheduler/factory/plugins.go +++ /dev/null @@ -1,481 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package factory - -import ( - "fmt" - "regexp" - "sort" - "strings" - "sync" - - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/priorities" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - - "github.com/golang/glog" -) - -// PluginFactoryArgs are passed to all plugin factory functions. -type PluginFactoryArgs struct { - PodLister algorithm.PodLister - ServiceLister algorithm.ServiceLister - ControllerLister algorithm.ControllerLister - ReplicaSetLister algorithm.ReplicaSetLister - StatefulSetLister algorithm.StatefulSetLister - NodeLister algorithm.NodeLister - NodeInfo predicates.NodeInfo - PVInfo predicates.PersistentVolumeInfo - PVCInfo predicates.PersistentVolumeClaimInfo - HardPodAffinitySymmetricWeight int -} - -// MetadataProducerFactory produces MetadataProducer from the given args. -// TODO: Rename this to PriorityMetadataProducerFactory. -type MetadataProducerFactory func(PluginFactoryArgs) algorithm.MetadataProducer - -// PredicateMetadataProducerFactory produces PredicateMetadataProducer from the given args. -type PredicateMetadataProducerFactory func(PluginFactoryArgs) algorithm.PredicateMetadataProducer - -// A FitPredicateFactory produces a FitPredicate from the given args. -type FitPredicateFactory func(PluginFactoryArgs) algorithm.FitPredicate - -// DEPRECATED -// Use Map-Reduce pattern for priority functions. -// A PriorityFunctionFactory produces a PriorityConfig from the given args. -type PriorityFunctionFactory func(PluginFactoryArgs) algorithm.PriorityFunction - -// A PriorityFunctionFactory produces map & reduce priority functions -// from a given args. -// FIXME: Rename to PriorityFunctionFactory. -type PriorityFunctionFactory2 func(PluginFactoryArgs) (algorithm.PriorityMapFunction, algorithm.PriorityReduceFunction) - -// A PriorityConfigFactory produces a PriorityConfig from the given function and weight -type PriorityConfigFactory struct { - Function PriorityFunctionFactory - MapReduceFunction PriorityFunctionFactory2 - Weight int -} - -var ( - schedulerFactoryMutex sync.Mutex - - // maps that hold registered algorithm types - fitPredicateMap = make(map[string]FitPredicateFactory) - mandatoryFitPredicates = sets.NewString() - priorityFunctionMap = make(map[string]PriorityConfigFactory) - algorithmProviderMap = make(map[string]AlgorithmProviderConfig) - - // Registered metadata producers - priorityMetadataProducer MetadataProducerFactory - predicateMetadataProducer PredicateMetadataProducerFactory - - // get equivalence pod function - getEquivalencePodFunc algorithm.GetEquivalencePodFunc -) - -const ( - DefaultProvider = "DefaultProvider" -) - -type AlgorithmProviderConfig struct { - FitPredicateKeys sets.String - PriorityFunctionKeys sets.String -} - -// RegisterFitPredicate registers a fit predicate with the algorithm -// registry. Returns the name with which the predicate was registered. -func RegisterFitPredicate(name string, predicate algorithm.FitPredicate) string { - return RegisterFitPredicateFactory(name, func(PluginFactoryArgs) algorithm.FitPredicate { return predicate }) -} - -// RemoveFitPredicate removes a fit predicate from factory. -func RemoveFitPredicate(name string) { - schedulerFactoryMutex.Lock() - defer schedulerFactoryMutex.Unlock() - - validateAlgorithmNameOrDie(name) - delete(fitPredicateMap, name) - mandatoryFitPredicates.Delete(name) -} - -// RegisterMandatoryFitPredicate registers a fit predicate with the algorithm registry, the predicate is used by -// kubelet, DaemonSet; it is always included in configuration. Returns the name with which the predicate was -// registered. -func RegisterMandatoryFitPredicate(name string, predicate algorithm.FitPredicate) string { - schedulerFactoryMutex.Lock() - defer schedulerFactoryMutex.Unlock() - validateAlgorithmNameOrDie(name) - fitPredicateMap[name] = func(PluginFactoryArgs) algorithm.FitPredicate { return predicate } - mandatoryFitPredicates.Insert(name) - return name -} - -// RegisterFitPredicateFactory registers a fit predicate factory with the -// algorithm registry. Returns the name with which the predicate was registered. -func RegisterFitPredicateFactory(name string, predicateFactory FitPredicateFactory) string { - schedulerFactoryMutex.Lock() - defer schedulerFactoryMutex.Unlock() - validateAlgorithmNameOrDie(name) - fitPredicateMap[name] = predicateFactory - return name -} - -// RegisterCustomFitPredicate registers a custom fit predicate with the algorithm registry. -// Returns the name, with which the predicate was registered. -func RegisterCustomFitPredicate(policy schedulerapi.PredicatePolicy) string { - var predicateFactory FitPredicateFactory - var ok bool - - validatePredicateOrDie(policy) - - // generate the predicate function, if a custom type is requested - if policy.Argument != nil { - if policy.Argument.ServiceAffinity != nil { - predicateFactory = func(args PluginFactoryArgs) algorithm.FitPredicate { - predicate, precomputationFunction := predicates.NewServiceAffinityPredicate( - args.PodLister, - args.ServiceLister, - args.NodeInfo, - policy.Argument.ServiceAffinity.Labels, - ) - - // Once we generate the predicate we should also Register the Precomputation - predicates.RegisterPredicateMetadataProducer(policy.Name, precomputationFunction) - return predicate - } - } else if policy.Argument.LabelsPresence != nil { - predicateFactory = func(args PluginFactoryArgs) algorithm.FitPredicate { - return predicates.NewNodeLabelPredicate( - policy.Argument.LabelsPresence.Labels, - policy.Argument.LabelsPresence.Presence, - ) - } - } - } else if predicateFactory, ok = fitPredicateMap[policy.Name]; ok { - // checking to see if a pre-defined predicate is requested - glog.V(2).Infof("Predicate type %s already registered, reusing.", policy.Name) - return policy.Name - } - - if predicateFactory == nil { - glog.Fatalf("Invalid configuration: Predicate type not found for %s", policy.Name) - } - - return RegisterFitPredicateFactory(policy.Name, predicateFactory) -} - -// IsFitPredicateRegistered is useful for testing providers. -func IsFitPredicateRegistered(name string) bool { - schedulerFactoryMutex.Lock() - defer schedulerFactoryMutex.Unlock() - _, ok := fitPredicateMap[name] - return ok -} - -func RegisterPriorityMetadataProducerFactory(factory MetadataProducerFactory) { - schedulerFactoryMutex.Lock() - defer schedulerFactoryMutex.Unlock() - priorityMetadataProducer = factory -} - -func RegisterPredicateMetadataProducerFactory(factory PredicateMetadataProducerFactory) { - schedulerFactoryMutex.Lock() - defer schedulerFactoryMutex.Unlock() - predicateMetadataProducer = factory -} - -// DEPRECATED -// Use Map-Reduce pattern for priority functions. -// Registers a priority function with the algorithm registry. Returns the name, -// with which the function was registered. -func RegisterPriorityFunction(name string, function algorithm.PriorityFunction, weight int) string { - return RegisterPriorityConfigFactory(name, PriorityConfigFactory{ - Function: func(PluginFactoryArgs) algorithm.PriorityFunction { - return function - }, - Weight: weight, - }) -} - -// RegisterPriorityFunction2 registers a priority function with the algorithm registry. Returns the name, -// with which the function was registered. -// FIXME: Rename to PriorityFunctionFactory. -func RegisterPriorityFunction2( - name string, - mapFunction algorithm.PriorityMapFunction, - reduceFunction algorithm.PriorityReduceFunction, - weight int) string { - return RegisterPriorityConfigFactory(name, PriorityConfigFactory{ - MapReduceFunction: func(PluginFactoryArgs) (algorithm.PriorityMapFunction, algorithm.PriorityReduceFunction) { - return mapFunction, reduceFunction - }, - Weight: weight, - }) -} - -func RegisterPriorityConfigFactory(name string, pcf PriorityConfigFactory) string { - schedulerFactoryMutex.Lock() - defer schedulerFactoryMutex.Unlock() - validateAlgorithmNameOrDie(name) - priorityFunctionMap[name] = pcf - return name -} - -// RegisterCustomPriorityFunction registers a custom priority function with the algorithm registry. -// Returns the name, with which the priority function was registered. -func RegisterCustomPriorityFunction(policy schedulerapi.PriorityPolicy) string { - var pcf *PriorityConfigFactory - - validatePriorityOrDie(policy) - - // generate the priority function, if a custom priority is requested - if policy.Argument != nil { - if policy.Argument.ServiceAntiAffinity != nil { - pcf = &PriorityConfigFactory{ - Function: func(args PluginFactoryArgs) algorithm.PriorityFunction { - return priorities.NewServiceAntiAffinityPriority( - args.PodLister, - args.ServiceLister, - policy.Argument.ServiceAntiAffinity.Label, - ) - }, - Weight: policy.Weight, - } - } else if policy.Argument.LabelPreference != nil { - pcf = &PriorityConfigFactory{ - MapReduceFunction: func(args PluginFactoryArgs) (algorithm.PriorityMapFunction, algorithm.PriorityReduceFunction) { - return priorities.NewNodeLabelPriority( - policy.Argument.LabelPreference.Label, - policy.Argument.LabelPreference.Presence, - ) - }, - Weight: policy.Weight, - } - } - } else if existingPcf, ok := priorityFunctionMap[policy.Name]; ok { - glog.V(2).Infof("Priority type %s already registered, reusing.", policy.Name) - // set/update the weight based on the policy - pcf = &PriorityConfigFactory{ - Function: existingPcf.Function, - MapReduceFunction: existingPcf.MapReduceFunction, - Weight: policy.Weight, - } - } - - if pcf == nil { - glog.Fatalf("Invalid configuration: Priority type not found for %s", policy.Name) - } - - return RegisterPriorityConfigFactory(policy.Name, *pcf) -} - -func RegisterGetEquivalencePodFunction(equivalenceFunc algorithm.GetEquivalencePodFunc) { - getEquivalencePodFunc = equivalenceFunc -} - -// IsPriorityFunctionRegistered is useful for testing providers. -func IsPriorityFunctionRegistered(name string) bool { - schedulerFactoryMutex.Lock() - defer schedulerFactoryMutex.Unlock() - _, ok := priorityFunctionMap[name] - return ok -} - -// RegisterAlgorithmProvider registers a new algorithm provider with the algorithm registry. This should -// be called from the init function in a provider plugin. -func RegisterAlgorithmProvider(name string, predicateKeys, priorityKeys sets.String) string { - schedulerFactoryMutex.Lock() - defer schedulerFactoryMutex.Unlock() - validateAlgorithmNameOrDie(name) - algorithmProviderMap[name] = AlgorithmProviderConfig{ - FitPredicateKeys: predicateKeys, - PriorityFunctionKeys: priorityKeys, - } - return name -} - -// GetAlgorithmProvider should not be used to modify providers. It is publicly visible for testing. -func GetAlgorithmProvider(name string) (*AlgorithmProviderConfig, error) { - schedulerFactoryMutex.Lock() - defer schedulerFactoryMutex.Unlock() - - provider, ok := algorithmProviderMap[name] - if !ok { - return nil, fmt.Errorf("plugin %q has not been registered", name) - } - - return &provider, nil -} - -func getFitPredicateFunctions(names sets.String, args PluginFactoryArgs) (map[string]algorithm.FitPredicate, error) { - schedulerFactoryMutex.Lock() - defer schedulerFactoryMutex.Unlock() - - predicates := map[string]algorithm.FitPredicate{} - for _, name := range names.List() { - factory, ok := fitPredicateMap[name] - if !ok { - return nil, fmt.Errorf("Invalid predicate name %q specified - no corresponding function found", name) - } - predicates[name] = factory(args) - } - - // Always include mandatory fit predicates. - for name := range mandatoryFitPredicates { - if factory, found := fitPredicateMap[name]; found { - predicates[name] = factory(args) - } - } - - return predicates, nil -} - -func getPriorityMetadataProducer(args PluginFactoryArgs) (algorithm.MetadataProducer, error) { - schedulerFactoryMutex.Lock() - defer schedulerFactoryMutex.Unlock() - - if priorityMetadataProducer == nil { - return algorithm.EmptyMetadataProducer, nil - } - return priorityMetadataProducer(args), nil -} - -func getPredicateMetadataProducer(args PluginFactoryArgs) (algorithm.PredicateMetadataProducer, error) { - schedulerFactoryMutex.Lock() - defer schedulerFactoryMutex.Unlock() - - if predicateMetadataProducer == nil { - return algorithm.EmptyPredicateMetadataProducer, nil - } - return predicateMetadataProducer(args), nil -} - -func getPriorityFunctionConfigs(names sets.String, args PluginFactoryArgs) ([]algorithm.PriorityConfig, error) { - schedulerFactoryMutex.Lock() - defer schedulerFactoryMutex.Unlock() - - configs := []algorithm.PriorityConfig{} - for _, name := range names.List() { - factory, ok := priorityFunctionMap[name] - if !ok { - return nil, fmt.Errorf("Invalid priority name %s specified - no corresponding function found", name) - } - if factory.Function != nil { - configs = append(configs, algorithm.PriorityConfig{ - Function: factory.Function(args), - Weight: factory.Weight, - }) - } else { - mapFunction, reduceFunction := factory.MapReduceFunction(args) - configs = append(configs, algorithm.PriorityConfig{ - Map: mapFunction, - Reduce: reduceFunction, - Weight: factory.Weight, - }) - } - } - if err := validateSelectedConfigs(configs); err != nil { - return nil, err - } - return configs, nil -} - -// validateSelectedConfigs validates the config weights to avoid the overflow. -func validateSelectedConfigs(configs []algorithm.PriorityConfig) error { - var totalPriority int - for _, config := range configs { - // Checks totalPriority against MaxTotalPriority to avoid overflow - if config.Weight*schedulerapi.MaxPriority > schedulerapi.MaxTotalPriority-totalPriority { - return fmt.Errorf("Total priority of priority functions has overflown") - } - totalPriority += config.Weight * schedulerapi.MaxPriority - } - return nil -} - -var validName = regexp.MustCompile("^[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])$") - -func validateAlgorithmNameOrDie(name string) { - if !validName.MatchString(name) { - glog.Fatalf("Algorithm name %v does not match the name validation regexp \"%v\".", name, validName) - } -} - -func validatePredicateOrDie(predicate schedulerapi.PredicatePolicy) { - if predicate.Argument != nil { - numArgs := 0 - if predicate.Argument.ServiceAffinity != nil { - numArgs++ - } - if predicate.Argument.LabelsPresence != nil { - numArgs++ - } - if numArgs != 1 { - glog.Fatalf("Exactly 1 predicate argument is required, numArgs: %v, Predicate: %s", numArgs, predicate.Name) - } - } -} - -func validatePriorityOrDie(priority schedulerapi.PriorityPolicy) { - if priority.Argument != nil { - numArgs := 0 - if priority.Argument.ServiceAntiAffinity != nil { - numArgs++ - } - if priority.Argument.LabelPreference != nil { - numArgs++ - } - if numArgs != 1 { - glog.Fatalf("Exactly 1 priority argument is required, numArgs: %v, Priority: %s", numArgs, priority.Name) - } - } -} - -func ListRegisteredFitPredicates() []string { - schedulerFactoryMutex.Lock() - defer schedulerFactoryMutex.Unlock() - - names := []string{} - for name := range fitPredicateMap { - names = append(names, name) - } - return names -} - -func ListRegisteredPriorityFunctions() []string { - schedulerFactoryMutex.Lock() - defer schedulerFactoryMutex.Unlock() - - names := []string{} - for name := range priorityFunctionMap { - names = append(names, name) - } - return names -} - -// ListAlgorithmProviders is called when listing all available algorithm providers in `kube-scheduler --help` -func ListAlgorithmProviders() string { - var availableAlgorithmProviders []string - for name := range algorithmProviderMap { - availableAlgorithmProviders = append(availableAlgorithmProviders, name) - } - sort.Strings(availableAlgorithmProviders) - return strings.Join(availableAlgorithmProviders, " | ") -} diff --git a/plugin/pkg/scheduler/factory/plugins_test.go b/plugin/pkg/scheduler/factory/plugins_test.go deleted file mode 100644 index 0f78fd1789d..00000000000 --- a/plugin/pkg/scheduler/factory/plugins_test.go +++ /dev/null @@ -1,82 +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 factory - -import ( - "testing" - - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" - "k8s.io/kubernetes/plugin/pkg/scheduler/api" -) - -func TestAlgorithmNameValidation(t *testing.T) { - algorithmNamesShouldValidate := []string{ - "1SomeAlgo1rithm", - "someAlgor-ithm1", - } - algorithmNamesShouldNotValidate := []string{ - "-SomeAlgorithm", - "SomeAlgorithm-", - "Some,Alg:orithm", - } - for _, name := range algorithmNamesShouldValidate { - if !validName.MatchString(name) { - t.Errorf("%v should be a valid algorithm name but is not valid.", name) - } - } - for _, name := range algorithmNamesShouldNotValidate { - if validName.MatchString(name) { - t.Errorf("%v should be an invalid algorithm name but is valid.", name) - } - } -} - -func TestValidatePriorityConfigOverFlow(t *testing.T) { - tests := []struct { - description string - configs []algorithm.PriorityConfig - expected bool - }{ - { - description: "one of the weights is MaxInt", - configs: []algorithm.PriorityConfig{{Weight: api.MaxInt}, {Weight: 5}}, - expected: true, - }, - { - description: "after multiplication with MaxPriority the weight is larger than MaxWeight", - configs: []algorithm.PriorityConfig{{Weight: api.MaxInt/api.MaxPriority + api.MaxPriority}, {Weight: 5}}, - expected: true, - }, - { - description: "normal weights", - configs: []algorithm.PriorityConfig{{Weight: 10000}, {Weight: 5}}, - expected: false, - }, - } - for _, test := range tests { - err := validateSelectedConfigs(test.configs) - if test.expected { - if err == nil { - t.Errorf("Expected Overflow for %s", test.description) - } - } else { - if err != nil { - t.Errorf("Did not expect an overflow for %s", test.description) - } - } - } -} diff --git a/plugin/pkg/scheduler/metrics/BUILD b/plugin/pkg/scheduler/metrics/BUILD deleted file mode 100644 index 7d059ed2112..00000000000 --- a/plugin/pkg/scheduler/metrics/BUILD +++ /dev/null @@ -1,26 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["metrics.go"], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/metrics", - deps = ["//vendor/github.com/prometheus/client_golang/prometheus:go_default_library"], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/plugin/pkg/scheduler/scheduler.go b/plugin/pkg/scheduler/scheduler.go deleted file mode 100644 index 729ac23a6cc..00000000000 --- a/plugin/pkg/scheduler/scheduler.go +++ /dev/null @@ -1,341 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES 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 ( - "time" - - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/wait" - utilfeature "k8s.io/apiserver/pkg/util/feature" - clientset "k8s.io/client-go/kubernetes" - corelisters "k8s.io/client-go/listers/core/v1" - "k8s.io/client-go/tools/cache" - "k8s.io/client-go/tools/record" - "k8s.io/kubernetes/pkg/features" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/core" - "k8s.io/kubernetes/plugin/pkg/scheduler/metrics" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" - "k8s.io/kubernetes/plugin/pkg/scheduler/util" - - "github.com/golang/glog" -) - -// Binder knows how to write a binding. -type Binder interface { - Bind(binding *v1.Binding) error -} - -// PodConditionUpdater updates the condition of a pod based on the passed -// PodCondition -type PodConditionUpdater interface { - Update(pod *v1.Pod, podCondition *v1.PodCondition) error -} - -// PodPreemptor has methods needed to delete a pod and to update -// annotations of the preemptor pod. -type PodPreemptor interface { - GetUpdatedPod(pod *v1.Pod) (*v1.Pod, error) - DeletePod(pod *v1.Pod) error - UpdatePodAnnotations(pod *v1.Pod, annots map[string]string) error -} - -// Scheduler watches for new unscheduled pods. It attempts to find -// nodes that they fit on and writes bindings back to the api server. -type Scheduler struct { - config *Config -} - -// StopEverything closes the scheduler config's StopEverything channel, to shut -// down the Scheduler. -func (sched *Scheduler) StopEverything() { - close(sched.config.StopEverything) -} - -// Configurator defines I/O, caching, and other functionality needed to -// construct a new scheduler. An implementation of this can be seen in -// factory.go. -type Configurator interface { - GetPriorityFunctionConfigs(priorityKeys sets.String) ([]algorithm.PriorityConfig, error) - GetPriorityMetadataProducer() (algorithm.MetadataProducer, error) - GetPredicateMetadataProducer() (algorithm.PredicateMetadataProducer, error) - GetPredicates(predicateKeys sets.String) (map[string]algorithm.FitPredicate, error) - GetHardPodAffinitySymmetricWeight() int - GetSchedulerName() string - MakeDefaultErrorFunc(backoff *util.PodBackoff, podQueue *cache.FIFO) func(pod *v1.Pod, err error) - - // Probably doesn't need to be public. But exposed for now in case. - ResponsibleForPod(pod *v1.Pod) bool - - // Needs to be exposed for things like integration tests where we want to make fake nodes. - GetNodeLister() corelisters.NodeLister - GetClient() clientset.Interface - GetScheduledPodLister() corelisters.PodLister - - Create() (*Config, error) - CreateFromProvider(providerName string) (*Config, error) - CreateFromConfig(policy schedulerapi.Policy) (*Config, error) - CreateFromKeys(predicateKeys, priorityKeys sets.String, extenders []algorithm.SchedulerExtender) (*Config, error) -} - -// Config is an implementation of the Scheduler's configured input data. -// TODO over time we should make this struct a hidden implementation detail of the scheduler. -type Config struct { - // It is expected that changes made via SchedulerCache will be observed - // by NodeLister and Algorithm. - SchedulerCache schedulercache.Cache - // Ecache is used for optimistically invalid affected cache items after - // successfully binding a pod - Ecache *core.EquivalenceCache - NodeLister algorithm.NodeLister - Algorithm algorithm.ScheduleAlgorithm - Binder Binder - // PodConditionUpdater is used only in case of scheduling errors. If we succeed - // with scheduling, PodScheduled condition will be updated in apiserver in /bind - // handler so that binding and setting PodCondition it is atomic. - PodConditionUpdater PodConditionUpdater - // PodPreemptor is used to evict pods and update pod annotations. - PodPreemptor PodPreemptor - - // NextPod should be a function that blocks until the next pod - // is available. We don't use a channel for this, because scheduling - // a pod may take some amount of time and we don't want pods to get - // stale while they sit in a channel. - NextPod func() *v1.Pod - - // WaitForCacheSync waits for scheduler cache to populate. - // It returns true if it was successful, false if the controller should shutdown. - WaitForCacheSync func() bool - - // Error is called if there is an error. It is passed the pod in - // question, and the error - Error func(*v1.Pod, error) - - // Recorder is the EventRecorder to use - Recorder record.EventRecorder - - // Close this to shut down the scheduler. - StopEverything chan struct{} -} - -// NewFromConfigurator returns a new scheduler that is created entirely by the Configurator. Assumes Create() is implemented. -// Supports intermediate Config mutation for now if you provide modifier functions which will run after Config is created. -func NewFromConfigurator(c Configurator, modifiers ...func(c *Config)) (*Scheduler, error) { - cfg, err := c.Create() - if err != nil { - return nil, err - } - // Mutate it if any functions were provided, changes might be required for certain types of tests (i.e. change the recorder). - for _, modifier := range modifiers { - modifier(cfg) - } - // From this point on the config is immutable to the outside. - s := &Scheduler{ - config: cfg, - } - metrics.Register() - return s, nil -} - -// Run begins watching and scheduling. It waits for cache to be synced, then starts a goroutine and returns immediately. -func (sched *Scheduler) Run() { - if !sched.config.WaitForCacheSync() { - return - } - - go wait.Until(sched.scheduleOne, 0, sched.config.StopEverything) -} - -// Config return scheduler's config pointer. It is exposed for testing purposes. -func (sched *Scheduler) Config() *Config { - return sched.config -} - -// schedule implements the scheduling algorithm and returns the suggested host. -func (sched *Scheduler) schedule(pod *v1.Pod) (string, error) { - host, err := sched.config.Algorithm.Schedule(pod, sched.config.NodeLister) - if err != nil { - glog.V(1).Infof("Failed to schedule pod: %v/%v", pod.Namespace, pod.Name) - pod = pod.DeepCopy() - sched.config.Error(pod, err) - sched.config.Recorder.Eventf(pod, v1.EventTypeWarning, "FailedScheduling", "%v", err) - sched.config.PodConditionUpdater.Update(pod, &v1.PodCondition{ - Type: v1.PodScheduled, - Status: v1.ConditionFalse, - Reason: v1.PodReasonUnschedulable, - Message: err.Error(), - }) - return "", err - } - return host, err -} - -func (sched *Scheduler) preempt(preemptor *v1.Pod, scheduleErr error) (string, error) { - if !utilfeature.DefaultFeatureGate.Enabled(features.PodPriority) { - glog.V(3).Infof("Pod priority feature is not enabled. No preemption is performed.") - return "", nil - } - preemptor, err := sched.config.PodPreemptor.GetUpdatedPod(preemptor) - if err != nil { - glog.Errorf("Error getting the updated preemptor pod object: %v", err) - return "", err - } - node, victims, err := sched.config.Algorithm.Preempt(preemptor, sched.config.NodeLister, scheduleErr) - if err != nil { - glog.Errorf("Error preempting victims to make room for %v/%v.", preemptor.Namespace, preemptor.Name) - return "", err - } - if node == nil { - return "", err - } - glog.Infof("Preempting %d pod(s) on node %v to make room for %v/%v.", len(victims), node.Name, preemptor.Namespace, preemptor.Name) - annotations := map[string]string{core.NominatedNodeAnnotationKey: node.Name} - err = sched.config.PodPreemptor.UpdatePodAnnotations(preemptor, annotations) - if err != nil { - glog.Errorf("Error in preemption process. Cannot update pod %v annotations: %v", preemptor.Name, err) - return "", err - } - for _, victim := range victims { - if err := sched.config.PodPreemptor.DeletePod(victim); err != nil { - glog.Errorf("Error preempting pod %v/%v: %v", victim.Namespace, victim.Name, err) - return "", err - } - sched.config.Recorder.Eventf(victim, v1.EventTypeNormal, "Preempted", "by %v/%v on node %v", preemptor.Namespace, preemptor.Name, node.Name) - } - return node.Name, err -} - -// 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(assumed *v1.Pod, 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.config.SchedulerCache.AssumePod(assumed); err != nil { - glog.Errorf("scheduler cache AssumePod failed: %v", err) - - // 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). - sched.config.Error(assumed, err) - sched.config.Recorder.Eventf(assumed, v1.EventTypeWarning, "FailedScheduling", "AssumePod failed: %v", err) - sched.config.PodConditionUpdater.Update(assumed, &v1.PodCondition{ - Type: v1.PodScheduled, - Status: v1.ConditionFalse, - Reason: "SchedulerError", - Message: err.Error(), - }) - return err - } - - // Optimistically assume that the binding will succeed, so we need to invalidate affected - // predicates in equivalence cache. - // If the binding fails, these invalidated item will not break anything. - if sched.config.Ecache != nil { - sched.config.Ecache.InvalidateCachedPredicateItemForPodAdd(assumed, host) - } - return nil -} - -// bind binds a pod to a given node defined in a binding object. We expect this to run asynchronously, so we -// handle binding metrics internally. -func (sched *Scheduler) bind(assumed *v1.Pod, b *v1.Binding) error { - bindingStart := time.Now() - // If binding succeeded then PodScheduled condition will be updated in apiserver so that - // it's atomic with setting host. - err := sched.config.Binder.Bind(b) - if err := sched.config.SchedulerCache.FinishBinding(assumed); err != nil { - glog.Errorf("scheduler cache FinishBinding failed: %v", err) - } - if err != nil { - glog.V(1).Infof("Failed to bind pod: %v/%v", assumed.Namespace, assumed.Name) - if err := sched.config.SchedulerCache.ForgetPod(assumed); err != nil { - glog.Errorf("scheduler cache ForgetPod failed: %v", err) - } - sched.config.Error(assumed, err) - sched.config.Recorder.Eventf(assumed, v1.EventTypeWarning, "FailedScheduling", "Binding rejected: %v", err) - sched.config.PodConditionUpdater.Update(assumed, &v1.PodCondition{ - Type: v1.PodScheduled, - Status: v1.ConditionFalse, - Reason: "BindingRejected", - }) - return err - } - - metrics.BindingLatency.Observe(metrics.SinceInMicroseconds(bindingStart)) - sched.config.Recorder.Eventf(assumed, v1.EventTypeNormal, "Scheduled", "Successfully assigned %v to %v", assumed.Name, b.Target.Name) - return nil -} - -// scheduleOne does the entire scheduling workflow for a single pod. It is serialized on the scheduling algorithm's host fitting. -func (sched *Scheduler) scheduleOne() { - pod := sched.config.NextPod() - if pod.DeletionTimestamp != nil { - sched.config.Recorder.Eventf(pod, v1.EventTypeWarning, "FailedScheduling", "skip schedule deleting pod: %v/%v", pod.Namespace, pod.Name) - glog.V(3).Infof("Skip schedule deleting pod: %v/%v", pod.Namespace, pod.Name) - return - } - - glog.V(3).Infof("Attempting to schedule pod: %v/%v", pod.Namespace, pod.Name) - - // Synchronously attempt to find a fit for the pod. - start := time.Now() - suggestedHost, err := sched.schedule(pod) - metrics.SchedulingAlgorithmLatency.Observe(metrics.SinceInMicroseconds(start)) - if err != nil { - // schedule() may have failed because the pod would not fit on any host, so we try to - // preempt, with the expectation that the next time the pod is tried for scheduling it - // will fit due to the preemption. It is also possible that a different pod will schedule - // into the resources that were preempted, but this is harmless. - if fitError, ok := err.(*core.FitError); ok { - sched.preempt(pod, fitError) - } - return - } - - // 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. - assumedPod := *pod - // assume modifies `assumedPod` by setting NodeName=suggestedHost - err = sched.assume(&assumedPod, suggestedHost) - if err != nil { - return - } - - // bind the pod to its host asynchronously (we can do this b/c of the assumption step above). - go func() { - err := sched.bind(&assumedPod, &v1.Binding{ - ObjectMeta: metav1.ObjectMeta{Namespace: assumedPod.Namespace, Name: assumedPod.Name, UID: assumedPod.UID}, - Target: v1.ObjectReference{ - Kind: "Node", - Name: suggestedHost, - }, - }) - metrics.E2eSchedulingLatency.Observe(metrics.SinceInMicroseconds(start)) - if err != nil { - glog.Errorf("Internal error binding pod: (%v)", err) - } - }() -} diff --git a/plugin/pkg/scheduler/scheduler_test.go b/plugin/pkg/scheduler/scheduler_test.go deleted file mode 100644 index 3772fbe8567..00000000000 --- a/plugin/pkg/scheduler/scheduler_test.go +++ /dev/null @@ -1,578 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES 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 ( - "errors" - "fmt" - "reflect" - "testing" - "time" - - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/util/diff" - "k8s.io/apimachinery/pkg/util/wait" - clientcache "k8s.io/client-go/tools/cache" - "k8s.io/client-go/tools/record" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates" - "k8s.io/kubernetes/plugin/pkg/scheduler/core" - "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" - schedulertesting "k8s.io/kubernetes/plugin/pkg/scheduler/testing" - "k8s.io/kubernetes/plugin/pkg/scheduler/util" -) - -type fakeBinder struct { - b func(binding *v1.Binding) error -} - -func (fb fakeBinder) Bind(binding *v1.Binding) error { return fb.b(binding) } - -type fakePodConditionUpdater struct{} - -func (fc fakePodConditionUpdater) Update(pod *v1.Pod, podCondition *v1.PodCondition) error { - return nil -} - -func podWithID(id, desiredHost string) *v1.Pod { - return &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: id, SelfLink: util.Test.SelfLink(string(v1.ResourcePods), id)}, - Spec: v1.PodSpec{ - NodeName: desiredHost, - }, - } -} - -func deletingPod(id string) *v1.Pod { - deletionTimestamp := metav1.Now() - return &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: id, SelfLink: util.Test.SelfLink(string(v1.ResourcePods), id), DeletionTimestamp: &deletionTimestamp}, - Spec: v1.PodSpec{ - NodeName: "", - }, - } -} - -func podWithPort(id, desiredHost string, port int) *v1.Pod { - pod := podWithID(id, desiredHost) - pod.Spec.Containers = []v1.Container{ - {Name: "ctr", Ports: []v1.ContainerPort{{HostPort: int32(port)}}}, - } - return pod -} - -func podWithResources(id, desiredHost string, limits v1.ResourceList, requests v1.ResourceList) *v1.Pod { - pod := podWithID(id, desiredHost) - pod.Spec.Containers = []v1.Container{ - {Name: "ctr", Resources: v1.ResourceRequirements{Limits: limits, Requests: requests}}, - } - return pod -} - -type mockScheduler struct { - machine string - err error -} - -func (es mockScheduler) Schedule(pod *v1.Pod, ml algorithm.NodeLister) (string, error) { - return es.machine, es.err -} - -func (es mockScheduler) Predicates() map[string]algorithm.FitPredicate { - return nil -} -func (es mockScheduler) Prioritizers() []algorithm.PriorityConfig { - return nil -} - -func (es mockScheduler) Preempt(pod *v1.Pod, nodeLister algorithm.NodeLister, scheduleErr error) (*v1.Node, []*v1.Pod, error) { - return nil, nil, nil -} - -func TestScheduler(t *testing.T) { - eventBroadcaster := record.NewBroadcaster() - eventBroadcaster.StartLogging(t.Logf).Stop() - errS := errors.New("scheduler") - errB := errors.New("binder") - testNode := v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "machine1"}} - - table := []struct { - injectBindError error - sendPod *v1.Pod - algo algorithm.ScheduleAlgorithm - expectErrorPod *v1.Pod - expectForgetPod *v1.Pod - expectAssumedPod *v1.Pod - expectError error - expectBind *v1.Binding - eventReason string - }{ - { - sendPod: podWithID("foo", ""), - algo: mockScheduler{testNode.Name, nil}, - expectBind: &v1.Binding{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Target: v1.ObjectReference{Kind: "Node", Name: testNode.Name}}, - expectAssumedPod: podWithID("foo", testNode.Name), - eventReason: "Scheduled", - }, { - sendPod: podWithID("foo", ""), - algo: mockScheduler{testNode.Name, errS}, - expectError: errS, - expectErrorPod: podWithID("foo", ""), - eventReason: "FailedScheduling", - }, { - sendPod: podWithID("foo", ""), - algo: mockScheduler{testNode.Name, nil}, - expectBind: &v1.Binding{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Target: v1.ObjectReference{Kind: "Node", Name: testNode.Name}}, - expectAssumedPod: podWithID("foo", testNode.Name), - injectBindError: errB, - expectError: errB, - expectErrorPod: podWithID("foo", testNode.Name), - expectForgetPod: podWithID("foo", testNode.Name), - eventReason: "FailedScheduling", - }, { - sendPod: deletingPod("foo"), - algo: mockScheduler{"", nil}, - eventReason: "FailedScheduling", - }, - } - - for i, item := range table { - var gotError error - var gotPod *v1.Pod - var gotForgetPod *v1.Pod - var gotAssumedPod *v1.Pod - var gotBinding *v1.Binding - configurator := &FakeConfigurator{ - Config: &Config{ - SchedulerCache: &schedulertesting.FakeCache{ - ForgetFunc: func(pod *v1.Pod) { - gotForgetPod = pod - }, - AssumeFunc: func(pod *v1.Pod) { - gotAssumedPod = pod - }, - }, - NodeLister: schedulertesting.FakeNodeLister( - []*v1.Node{&testNode}, - ), - Algorithm: item.algo, - Binder: fakeBinder{func(b *v1.Binding) error { - gotBinding = b - return item.injectBindError - }}, - PodConditionUpdater: fakePodConditionUpdater{}, - Error: func(p *v1.Pod, err error) { - gotPod = p - gotError = err - }, - NextPod: func() *v1.Pod { - return item.sendPod - }, - Recorder: eventBroadcaster.NewRecorder(api.Scheme, v1.EventSource{Component: "scheduler"}), - }, - } - - s, _ := NewFromConfigurator(configurator, nil...) - called := make(chan struct{}) - events := eventBroadcaster.StartEventWatcher(func(e *v1.Event) { - if e, a := item.eventReason, e.Reason; e != a { - t.Errorf("%v: expected %v, got %v", i, e, a) - } - close(called) - }) - s.scheduleOne() - <-called - if e, a := item.expectAssumedPod, gotAssumedPod; !reflect.DeepEqual(e, a) { - t.Errorf("%v: assumed pod: wanted %v, got %v", i, e, a) - } - if e, a := item.expectErrorPod, gotPod; !reflect.DeepEqual(e, a) { - t.Errorf("%v: error pod: wanted %v, got %v", i, e, a) - } - if e, a := item.expectForgetPod, gotForgetPod; !reflect.DeepEqual(e, a) { - t.Errorf("%v: forget pod: wanted %v, got %v", i, e, a) - } - if e, a := item.expectError, gotError; !reflect.DeepEqual(e, a) { - t.Errorf("%v: error: wanted %v, got %v", i, e, a) - } - if e, a := item.expectBind, gotBinding; !reflect.DeepEqual(e, a) { - t.Errorf("%v: error: %s", i, diff.ObjectDiff(e, a)) - } - events.Stop() - } -} - -func TestSchedulerNoPhantomPodAfterExpire(t *testing.T) { - stop := make(chan struct{}) - defer close(stop) - queuedPodStore := clientcache.NewFIFO(clientcache.MetaNamespaceKeyFunc) - scache := schedulercache.New(100*time.Millisecond, stop) - pod := podWithPort("pod.Name", "", 8080) - node := v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "machine1"}} - scache.AddNode(&node) - nodeLister := schedulertesting.FakeNodeLister([]*v1.Node{&node}) - predicateMap := map[string]algorithm.FitPredicate{"PodFitsHostPorts": predicates.PodFitsHostPorts} - scheduler, bindingChan, _ := setupTestSchedulerWithOnePodOnNode(t, queuedPodStore, scache, nodeLister, predicateMap, pod, &node) - - waitPodExpireChan := make(chan struct{}) - timeout := make(chan struct{}) - go func() { - for { - select { - case <-timeout: - return - default: - } - pods, err := scache.List(labels.Everything()) - if err != nil { - t.Fatalf("cache.List failed: %v", err) - } - if len(pods) == 0 { - close(waitPodExpireChan) - return - } - time.Sleep(100 * time.Millisecond) - } - }() - // waiting for the assumed pod to expire - select { - case <-waitPodExpireChan: - case <-time.After(wait.ForeverTestTimeout): - close(timeout) - t.Fatalf("timeout after %v", wait.ForeverTestTimeout) - } - - // We use conflicted pod ports to incur fit predicate failure if first pod not removed. - secondPod := podWithPort("bar", "", 8080) - queuedPodStore.Add(secondPod) - scheduler.scheduleOne() - select { - case b := <-bindingChan: - expectBinding := &v1.Binding{ - ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Target: v1.ObjectReference{Kind: "Node", Name: node.Name}, - } - if !reflect.DeepEqual(expectBinding, b) { - t.Errorf("binding want=%v, get=%v", expectBinding, b) - } - case <-time.After(wait.ForeverTestTimeout): - t.Fatalf("timeout after %v", wait.ForeverTestTimeout) - } -} - -func TestSchedulerNoPhantomPodAfterDelete(t *testing.T) { - stop := make(chan struct{}) - defer close(stop) - queuedPodStore := clientcache.NewFIFO(clientcache.MetaNamespaceKeyFunc) - scache := schedulercache.New(10*time.Minute, stop) - firstPod := podWithPort("pod.Name", "", 8080) - node := v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "machine1"}} - scache.AddNode(&node) - nodeLister := schedulertesting.FakeNodeLister([]*v1.Node{&node}) - predicateMap := map[string]algorithm.FitPredicate{"PodFitsHostPorts": predicates.PodFitsHostPorts} - scheduler, bindingChan, errChan := setupTestSchedulerWithOnePodOnNode(t, queuedPodStore, scache, nodeLister, predicateMap, firstPod, &node) - - // We use conflicted pod ports to incur fit predicate failure. - secondPod := podWithPort("bar", "", 8080) - queuedPodStore.Add(secondPod) - // queuedPodStore: [bar:8080] - // cache: [(assumed)foo:8080] - - scheduler.scheduleOne() - select { - case err := <-errChan: - expectErr := &core.FitError{ - Pod: secondPod, - NumAllNodes: 1, - FailedPredicates: core.FailedPredicateMap{node.Name: []algorithm.PredicateFailureReason{predicates.ErrPodNotFitsHostPorts}}, - } - if !reflect.DeepEqual(expectErr, err) { - t.Errorf("err want=%v, get=%v", expectErr, err) - } - case <-time.After(wait.ForeverTestTimeout): - t.Fatalf("timeout after %v", wait.ForeverTestTimeout) - } - - // We mimic the workflow of cache behavior when a pod is removed by user. - // Note: if the schedulercache timeout would be super short, the first pod would expire - // and would be removed itself (without any explicit actions on schedulercache). Even in that case, - // explicitly AddPod will as well correct the behavior. - firstPod.Spec.NodeName = node.Name - if err := scache.AddPod(firstPod); err != nil { - t.Fatalf("err: %v", err) - } - if err := scache.RemovePod(firstPod); err != nil { - t.Fatalf("err: %v", err) - } - - queuedPodStore.Add(secondPod) - scheduler.scheduleOne() - select { - case b := <-bindingChan: - expectBinding := &v1.Binding{ - ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Target: v1.ObjectReference{Kind: "Node", Name: node.Name}, - } - if !reflect.DeepEqual(expectBinding, b) { - t.Errorf("binding want=%v, get=%v", expectBinding, b) - } - case <-time.After(wait.ForeverTestTimeout): - t.Fatalf("timeout after %v", wait.ForeverTestTimeout) - } -} - -// Scheduler should preserve predicate constraint even if binding was longer -// than cache ttl -func TestSchedulerErrorWithLongBinding(t *testing.T) { - stop := make(chan struct{}) - defer close(stop) - - firstPod := podWithPort("foo", "", 8080) - conflictPod := podWithPort("bar", "", 8080) - pods := map[string]*v1.Pod{firstPod.Name: firstPod, conflictPod.Name: conflictPod} - for _, test := range []struct { - Expected map[string]bool - CacheTTL time.Duration - BindingDuration time.Duration - }{ - { - Expected: map[string]bool{firstPod.Name: true}, - CacheTTL: 100 * time.Millisecond, - BindingDuration: 300 * time.Millisecond, - }, - { - Expected: map[string]bool{firstPod.Name: true}, - CacheTTL: 10 * time.Second, - BindingDuration: 300 * time.Millisecond, - }, - } { - queuedPodStore := clientcache.NewFIFO(clientcache.MetaNamespaceKeyFunc) - scache := schedulercache.New(test.CacheTTL, stop) - - node := v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "machine1"}} - scache.AddNode(&node) - - nodeLister := schedulertesting.FakeNodeLister([]*v1.Node{&node}) - predicateMap := map[string]algorithm.FitPredicate{"PodFitsHostPorts": predicates.PodFitsHostPorts} - - scheduler, bindingChan := setupTestSchedulerLongBindingWithRetry( - queuedPodStore, scache, nodeLister, predicateMap, stop, test.BindingDuration) - scheduler.Run() - queuedPodStore.Add(firstPod) - queuedPodStore.Add(conflictPod) - - resultBindings := map[string]bool{} - waitChan := time.After(5 * time.Second) - for finished := false; !finished; { - select { - case b := <-bindingChan: - resultBindings[b.Name] = true - p := pods[b.Name] - p.Spec.NodeName = b.Target.Name - scache.AddPod(p) - case <-waitChan: - finished = true - } - } - if !reflect.DeepEqual(resultBindings, test.Expected) { - t.Errorf("Result binding are not equal to expected. %v != %v", resultBindings, test.Expected) - } - } -} - -// queuedPodStore: pods queued before processing. -// cache: scheduler cache that might contain assumed pods. -func setupTestSchedulerWithOnePodOnNode(t *testing.T, queuedPodStore *clientcache.FIFO, scache schedulercache.Cache, - nodeLister schedulertesting.FakeNodeLister, predicateMap map[string]algorithm.FitPredicate, pod *v1.Pod, node *v1.Node) (*Scheduler, chan *v1.Binding, chan error) { - - scheduler, bindingChan, errChan := setupTestScheduler(queuedPodStore, scache, nodeLister, predicateMap) - - queuedPodStore.Add(pod) - // queuedPodStore: [foo:8080] - // cache: [] - - scheduler.scheduleOne() - // queuedPodStore: [] - // cache: [(assumed)foo:8080] - - select { - case b := <-bindingChan: - expectBinding := &v1.Binding{ - ObjectMeta: metav1.ObjectMeta{Name: pod.Name}, - Target: v1.ObjectReference{Kind: "Node", Name: node.Name}, - } - if !reflect.DeepEqual(expectBinding, b) { - t.Errorf("binding want=%v, get=%v", expectBinding, b) - } - case <-time.After(wait.ForeverTestTimeout): - t.Fatalf("timeout after %v", wait.ForeverTestTimeout) - } - return scheduler, bindingChan, errChan -} - -func TestSchedulerFailedSchedulingReasons(t *testing.T) { - stop := make(chan struct{}) - defer close(stop) - queuedPodStore := clientcache.NewFIFO(clientcache.MetaNamespaceKeyFunc) - scache := schedulercache.New(10*time.Minute, stop) - - // Design the baseline for the pods, and we will make nodes that dont fit it later. - var cpu = int64(4) - var mem = int64(500) - podWithTooBigResourceRequests := podWithResources("bar", "", v1.ResourceList{ - v1.ResourceCPU: *(resource.NewQuantity(cpu, resource.DecimalSI)), - v1.ResourceMemory: *(resource.NewQuantity(mem, resource.DecimalSI)), - }, v1.ResourceList{ - v1.ResourceCPU: *(resource.NewQuantity(cpu, resource.DecimalSI)), - v1.ResourceMemory: *(resource.NewQuantity(mem, resource.DecimalSI)), - }) - - // create several nodes which cannot schedule the above pod - nodes := []*v1.Node{} - for i := 0; i < 100; i++ { - node := v1.Node{ - ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("machine%v", i)}, - Status: v1.NodeStatus{ - Capacity: v1.ResourceList{ - v1.ResourceCPU: *(resource.NewQuantity(cpu/2, resource.DecimalSI)), - v1.ResourceMemory: *(resource.NewQuantity(mem/5, resource.DecimalSI)), - v1.ResourcePods: *(resource.NewQuantity(10, resource.DecimalSI)), - }, - Allocatable: v1.ResourceList{ - v1.ResourceCPU: *(resource.NewQuantity(cpu/2, resource.DecimalSI)), - v1.ResourceMemory: *(resource.NewQuantity(mem/5, resource.DecimalSI)), - v1.ResourcePods: *(resource.NewQuantity(10, resource.DecimalSI)), - }}, - } - scache.AddNode(&node) - nodes = append(nodes, &node) - } - nodeLister := schedulertesting.FakeNodeLister(nodes) - predicateMap := map[string]algorithm.FitPredicate{ - "PodFitsResources": predicates.PodFitsResources, - } - - // Create expected failure reasons for all the nodes. Hopefully they will get rolled up into a non-spammy summary. - failedPredicatesMap := core.FailedPredicateMap{} - for _, node := range nodes { - failedPredicatesMap[node.Name] = []algorithm.PredicateFailureReason{ - predicates.NewInsufficientResourceError(v1.ResourceCPU, 4000, 0, 2000), - predicates.NewInsufficientResourceError(v1.ResourceMemory, 500, 0, 100), - } - } - scheduler, _, errChan := setupTestScheduler(queuedPodStore, scache, nodeLister, predicateMap) - - queuedPodStore.Add(podWithTooBigResourceRequests) - scheduler.scheduleOne() - select { - case err := <-errChan: - expectErr := &core.FitError{ - Pod: podWithTooBigResourceRequests, - NumAllNodes: len(nodes), - FailedPredicates: failedPredicatesMap, - } - if len(fmt.Sprint(expectErr)) > 150 { - t.Errorf("message is too spammy ! %v ", len(fmt.Sprint(expectErr))) - } - if !reflect.DeepEqual(expectErr, err) { - t.Errorf("\n err \nWANT=%+v,\nGOT=%+v", expectErr, err) - } - case <-time.After(wait.ForeverTestTimeout): - t.Fatalf("timeout after %v", wait.ForeverTestTimeout) - } -} - -// queuedPodStore: pods queued before processing. -// scache: scheduler cache that might contain assumed pods. -func setupTestScheduler(queuedPodStore *clientcache.FIFO, scache schedulercache.Cache, nodeLister schedulertesting.FakeNodeLister, predicateMap map[string]algorithm.FitPredicate) (*Scheduler, chan *v1.Binding, chan error) { - algo := core.NewGenericScheduler( - scache, - nil, - predicateMap, - algorithm.EmptyPredicateMetadataProducer, - []algorithm.PriorityConfig{}, - algorithm.EmptyMetadataProducer, - []algorithm.SchedulerExtender{}) - bindingChan := make(chan *v1.Binding, 1) - errChan := make(chan error, 1) - configurator := &FakeConfigurator{ - Config: &Config{ - SchedulerCache: scache, - NodeLister: nodeLister, - Algorithm: algo, - Binder: fakeBinder{func(b *v1.Binding) error { - bindingChan <- b - return nil - }}, - NextPod: func() *v1.Pod { - return clientcache.Pop(queuedPodStore).(*v1.Pod) - }, - Error: func(p *v1.Pod, err error) { - errChan <- err - }, - Recorder: &record.FakeRecorder{}, - PodConditionUpdater: fakePodConditionUpdater{}, - }, - } - - sched, _ := NewFromConfigurator(configurator, nil...) - - return sched, bindingChan, errChan -} - -func setupTestSchedulerLongBindingWithRetry(queuedPodStore *clientcache.FIFO, scache schedulercache.Cache, nodeLister schedulertesting.FakeNodeLister, predicateMap map[string]algorithm.FitPredicate, stop chan struct{}, bindingTime time.Duration) (*Scheduler, chan *v1.Binding) { - algo := core.NewGenericScheduler( - scache, - nil, - predicateMap, - algorithm.EmptyPredicateMetadataProducer, - []algorithm.PriorityConfig{}, - algorithm.EmptyMetadataProducer, - []algorithm.SchedulerExtender{}) - bindingChan := make(chan *v1.Binding, 2) - configurator := &FakeConfigurator{ - Config: &Config{ - SchedulerCache: scache, - NodeLister: nodeLister, - Algorithm: algo, - Binder: fakeBinder{func(b *v1.Binding) error { - time.Sleep(bindingTime) - bindingChan <- b - return nil - }}, - WaitForCacheSync: func() bool { - return true - }, - NextPod: func() *v1.Pod { - return clientcache.Pop(queuedPodStore).(*v1.Pod) - }, - Error: func(p *v1.Pod, err error) { - queuedPodStore.AddIfNotPresent(p) - }, - Recorder: &record.FakeRecorder{}, - PodConditionUpdater: fakePodConditionUpdater{}, - StopEverything: stop, - }, - } - - sched, _ := NewFromConfigurator(configurator, nil...) - - return sched, bindingChan -} diff --git a/plugin/pkg/scheduler/schedulercache/BUILD b/plugin/pkg/scheduler/schedulercache/BUILD deleted file mode 100644 index 824e11cd29b..00000000000 --- a/plugin/pkg/scheduler/schedulercache/BUILD +++ /dev/null @@ -1,58 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_library( - name = "go_default_library", - srcs = [ - "cache.go", - "interface.go", - "node_info.go", - "util.go", - ], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache", - deps = [ - "//pkg/api/v1/helper:go_default_library", - "//plugin/pkg/scheduler/algorithm/priorities/util:go_default_library", - "//plugin/pkg/scheduler/util:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/client-go/tools/cache:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["cache_test.go"], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache", - library = ":go_default_library", - deps = [ - "//pkg/api/v1/helper:go_default_library", - "//plugin/pkg/scheduler/algorithm/priorities/util:go_default_library", - "//plugin/pkg/scheduler/util:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/plugin/pkg/scheduler/schedulercache/cache_test.go b/plugin/pkg/scheduler/schedulercache/cache_test.go deleted file mode 100644 index f27c7b5ef7b..00000000000 --- a/plugin/pkg/scheduler/schedulercache/cache_test.go +++ /dev/null @@ -1,902 +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 schedulercache - -import ( - "fmt" - "reflect" - "strings" - "testing" - "time" - - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - v1helper "k8s.io/kubernetes/pkg/api/v1/helper" - priorityutil "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/priorities/util" - schedutil "k8s.io/kubernetes/plugin/pkg/scheduler/util" -) - -func deepEqualWithoutGeneration(t *testing.T, testcase int, actual, expected *NodeInfo) { - // Ignore generation field. - if actual != nil { - actual.generation = 0 - } - if !reflect.DeepEqual(actual, expected) { - t.Errorf("#%d: node info get=%s, want=%s", testcase, actual, expected) - } -} - -// TestAssumePodScheduled tests that after a pod is assumed, its information is aggregated -// on node level. -func TestAssumePodScheduled(t *testing.T) { - nodeName := "node" - testPods := []*v1.Pod{ - makeBasePod(t, nodeName, "test", "100m", "500", "", []v1.ContainerPort{{HostPort: 80}}), - makeBasePod(t, nodeName, "test-1", "100m", "500", "", []v1.ContainerPort{{HostPort: 80}}), - makeBasePod(t, nodeName, "test-2", "200m", "1Ki", "", []v1.ContainerPort{{HostPort: 8080}}), - makeBasePod(t, nodeName, "test-nonzero", "", "", "", []v1.ContainerPort{{HostPort: 80}}), - makeBasePod(t, nodeName, "test", "100m", "500", "oir-foo:3", []v1.ContainerPort{{HostPort: 80}}), - makeBasePod(t, nodeName, "test-2", "200m", "1Ki", "oir-foo:5", []v1.ContainerPort{{HostPort: 8080}}), - makeBasePod(t, nodeName, "test", "100m", "500", "random-invalid-oir-key:100", []v1.ContainerPort{{}}), - } - - tests := []struct { - pods []*v1.Pod - - wNodeInfo *NodeInfo - }{{ - pods: []*v1.Pod{testPods[0]}, - wNodeInfo: &NodeInfo{ - requestedResource: &Resource{ - MilliCPU: 100, - Memory: 500, - }, - nonzeroRequest: &Resource{ - MilliCPU: 100, - Memory: 500, - }, - allocatableResource: &Resource{}, - pods: []*v1.Pod{testPods[0]}, - usedPorts: map[int]bool{80: true}, - }, - }, { - pods: []*v1.Pod{testPods[1], testPods[2]}, - wNodeInfo: &NodeInfo{ - requestedResource: &Resource{ - MilliCPU: 300, - Memory: 1524, - }, - nonzeroRequest: &Resource{ - MilliCPU: 300, - Memory: 1524, - }, - allocatableResource: &Resource{}, - pods: []*v1.Pod{testPods[1], testPods[2]}, - usedPorts: map[int]bool{80: true, 8080: true}, - }, - }, { // test non-zero request - pods: []*v1.Pod{testPods[3]}, - wNodeInfo: &NodeInfo{ - requestedResource: &Resource{ - MilliCPU: 0, - Memory: 0, - }, - nonzeroRequest: &Resource{ - MilliCPU: priorityutil.DefaultMilliCpuRequest, - Memory: priorityutil.DefaultMemoryRequest, - }, - allocatableResource: &Resource{}, - pods: []*v1.Pod{testPods[3]}, - usedPorts: map[int]bool{80: true}, - }, - }, { - pods: []*v1.Pod{testPods[4]}, - wNodeInfo: &NodeInfo{ - requestedResource: &Resource{ - MilliCPU: 100, - Memory: 500, - ScalarResources: map[v1.ResourceName]int64{"pod.alpha.kubernetes.io/opaque-int-resource-oir-foo": 3}, - }, - nonzeroRequest: &Resource{ - MilliCPU: 100, - Memory: 500, - }, - allocatableResource: &Resource{}, - pods: []*v1.Pod{testPods[4]}, - usedPorts: map[int]bool{80: true}, - }, - }, { - pods: []*v1.Pod{testPods[4], testPods[5]}, - wNodeInfo: &NodeInfo{ - requestedResource: &Resource{ - MilliCPU: 300, - Memory: 1524, - ScalarResources: map[v1.ResourceName]int64{"pod.alpha.kubernetes.io/opaque-int-resource-oir-foo": 8}, - }, - nonzeroRequest: &Resource{ - MilliCPU: 300, - Memory: 1524, - }, - allocatableResource: &Resource{}, - pods: []*v1.Pod{testPods[4], testPods[5]}, - usedPorts: map[int]bool{80: true, 8080: true}, - }, - }, { - pods: []*v1.Pod{testPods[6]}, - wNodeInfo: &NodeInfo{ - requestedResource: &Resource{ - MilliCPU: 100, - Memory: 500, - }, - nonzeroRequest: &Resource{ - MilliCPU: 100, - Memory: 500, - }, - allocatableResource: &Resource{}, - pods: []*v1.Pod{testPods[6]}, - usedPorts: map[int]bool{}, - }, - }, - } - - for i, tt := range tests { - cache := newSchedulerCache(time.Second, time.Second, nil) - for _, pod := range tt.pods { - if err := cache.AssumePod(pod); err != nil { - t.Fatalf("AssumePod failed: %v", err) - } - } - n := cache.nodes[nodeName] - deepEqualWithoutGeneration(t, i, n, tt.wNodeInfo) - - for _, pod := range tt.pods { - if err := cache.ForgetPod(pod); err != nil { - t.Fatalf("ForgetPod failed: %v", err) - } - } - if cache.nodes[nodeName] != nil { - t.Errorf("NodeInfo should be cleaned for %s", nodeName) - } - } -} - -type testExpirePodStruct struct { - pod *v1.Pod - assumedTime time.Time -} - -func assumeAndFinishBinding(cache *schedulerCache, pod *v1.Pod, assumedTime time.Time) error { - if err := cache.AssumePod(pod); err != nil { - return err - } - return cache.finishBinding(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{{HostPort: 80}}), - makeBasePod(t, nodeName, "test-2", "200m", "1Ki", "", []v1.ContainerPort{{HostPort: 8080}}), - } - now := time.Now() - ttl := 10 * time.Second - tests := []struct { - pods []*testExpirePodStruct - cleanupTime time.Time - - wNodeInfo *NodeInfo - }{{ // assumed pod would expires - pods: []*testExpirePodStruct{ - {pod: testPods[0], assumedTime: now}, - }, - cleanupTime: now.Add(2 * ttl), - wNodeInfo: nil, - }, { // first one would expire, second one would not. - pods: []*testExpirePodStruct{ - {pod: testPods[0], assumedTime: now}, - {pod: testPods[1], assumedTime: now.Add(3 * ttl / 2)}, - }, - cleanupTime: now.Add(2 * ttl), - wNodeInfo: &NodeInfo{ - requestedResource: &Resource{ - MilliCPU: 200, - Memory: 1024, - }, - nonzeroRequest: &Resource{ - MilliCPU: 200, - Memory: 1024, - }, - allocatableResource: &Resource{}, - pods: []*v1.Pod{testPods[1]}, - usedPorts: map[int]bool{80: false, 8080: true}, - }, - }} - - for i, tt := range tests { - cache := newSchedulerCache(ttl, time.Second, nil) - - for _, pod := range tt.pods { - if err := assumeAndFinishBinding(cache, pod.pod, pod.assumedTime); err != nil { - t.Fatalf("assumePod failed: %v", err) - } - } - // pods that have assumedTime + ttl < cleanupTime will get expired and removed - cache.cleanupAssumedPods(tt.cleanupTime) - n := cache.nodes[nodeName] - deepEqualWithoutGeneration(t, i, n, tt.wNodeInfo) - } -} - -// 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{{HostPort: 80}}), - makeBasePod(t, nodeName, "test-2", "200m", "1Ki", "", []v1.ContainerPort{{HostPort: 8080}}), - } - tests := []struct { - podsToAssume []*v1.Pod - podsToAdd []*v1.Pod - - wNodeInfo *NodeInfo - }{{ // two pod were assumed at same time. But first one is called Add() and gets confirmed. - podsToAssume: []*v1.Pod{testPods[0], testPods[1]}, - podsToAdd: []*v1.Pod{testPods[0]}, - wNodeInfo: &NodeInfo{ - requestedResource: &Resource{ - MilliCPU: 100, - Memory: 500, - }, - nonzeroRequest: &Resource{ - MilliCPU: 100, - Memory: 500, - }, - allocatableResource: &Resource{}, - pods: []*v1.Pod{testPods[0]}, - usedPorts: map[int]bool{80: true, 8080: false}, - }, - }} - - for i, tt := range tests { - cache := newSchedulerCache(ttl, time.Second, nil) - for _, podToAssume := range tt.podsToAssume { - if err := assumeAndFinishBinding(cache, podToAssume, now); err != nil { - t.Fatalf("assumePod failed: %v", err) - } - } - for _, podToAdd := range tt.podsToAdd { - if err := cache.AddPod(podToAdd); err != nil { - t.Fatalf("AddPod failed: %v", err) - } - } - cache.cleanupAssumedPods(now.Add(2 * ttl)) - // check after expiration. confirmed pods shouldn't be expired. - n := cache.nodes[nodeName] - deepEqualWithoutGeneration(t, i, n, tt.wNodeInfo) - } -} - -// 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{{HostPort: 80}}) - tests := []struct { - pod *v1.Pod - - wNodeInfo *NodeInfo - }{{ - pod: basePod, - wNodeInfo: &NodeInfo{ - requestedResource: &Resource{ - MilliCPU: 100, - Memory: 500, - }, - nonzeroRequest: &Resource{ - MilliCPU: 100, - Memory: 500, - }, - allocatableResource: &Resource{}, - pods: []*v1.Pod{basePod}, - usedPorts: map[int]bool{80: true}, - }, - }} - - now := time.Now() - for i, tt := range tests { - cache := newSchedulerCache(ttl, time.Second, nil) - if err := assumeAndFinishBinding(cache, tt.pod, now); err != nil { - t.Fatalf("assumePod failed: %v", err) - } - cache.cleanupAssumedPods(now.Add(2 * ttl)) - // It should be expired and removed. - n := cache.nodes[nodeName] - if n != nil { - t.Errorf("#%d: expecting nil node info, but get=%v", i, n) - } - if err := cache.AddPod(tt.pod); err != nil { - t.Fatalf("AddPod failed: %v", err) - } - // check after expiration. confirmed pods shouldn't be expired. - n = cache.nodes[nodeName] - deepEqualWithoutGeneration(t, i, n, tt.wNodeInfo) - } -} - -// 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{{HostPort: 80}}), - makeBasePod(t, nodeName, "test", "200m", "1Ki", "", []v1.ContainerPort{{HostPort: 8080}}), - } - tests := []struct { - podsToAssume []*v1.Pod - podsToAdd []*v1.Pod - podsToUpdate []*v1.Pod - - wNodeInfo []*NodeInfo - }{{ // add a pod and then update it twice - podsToAdd: []*v1.Pod{testPods[0]}, - podsToUpdate: []*v1.Pod{testPods[0], testPods[1], testPods[0]}, - wNodeInfo: []*NodeInfo{{ - requestedResource: &Resource{ - MilliCPU: 200, - Memory: 1024, - }, - nonzeroRequest: &Resource{ - MilliCPU: 200, - Memory: 1024, - }, - allocatableResource: &Resource{}, - pods: []*v1.Pod{testPods[1]}, - usedPorts: map[int]bool{8080: true}, - }, { - requestedResource: &Resource{ - MilliCPU: 100, - Memory: 500, - }, - nonzeroRequest: &Resource{ - MilliCPU: 100, - Memory: 500, - }, - allocatableResource: &Resource{}, - pods: []*v1.Pod{testPods[0]}, - usedPorts: map[int]bool{80: true}, - }}, - }} - - for _, tt := range tests { - cache := newSchedulerCache(ttl, time.Second, nil) - for _, podToAdd := range tt.podsToAdd { - if err := cache.AddPod(podToAdd); err != nil { - t.Fatalf("AddPod failed: %v", err) - } - } - - for i := range tt.podsToUpdate { - if i == 0 { - continue - } - if err := cache.UpdatePod(tt.podsToUpdate[i-1], tt.podsToUpdate[i]); err != nil { - t.Fatalf("UpdatePod failed: %v", err) - } - // check after expiration. confirmed pods shouldn't be expired. - n := cache.nodes[nodeName] - deepEqualWithoutGeneration(t, i, n, tt.wNodeInfo[i-1]) - } - } -} - -// TestExpireAddUpdatePod test the sequence that a pod is expired, added, then updated -func TestExpireAddUpdatePod(t *testing.T) { - nodeName := "node" - ttl := 10 * time.Second - testPods := []*v1.Pod{ - makeBasePod(t, nodeName, "test", "100m", "500", "", []v1.ContainerPort{{HostPort: 80}}), - makeBasePod(t, nodeName, "test", "200m", "1Ki", "", []v1.ContainerPort{{HostPort: 8080}}), - } - tests := []struct { - podsToAssume []*v1.Pod - podsToAdd []*v1.Pod - podsToUpdate []*v1.Pod - - wNodeInfo []*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: []*NodeInfo{{ - requestedResource: &Resource{ - MilliCPU: 200, - Memory: 1024, - }, - nonzeroRequest: &Resource{ - MilliCPU: 200, - Memory: 1024, - }, - allocatableResource: &Resource{}, - pods: []*v1.Pod{testPods[1]}, - usedPorts: map[int]bool{8080: true}, - }, { - requestedResource: &Resource{ - MilliCPU: 100, - Memory: 500, - }, - nonzeroRequest: &Resource{ - MilliCPU: 100, - Memory: 500, - }, - allocatableResource: &Resource{}, - pods: []*v1.Pod{testPods[0]}, - usedPorts: map[int]bool{80: true}, - }}, - }} - - now := time.Now() - for _, tt := range tests { - cache := newSchedulerCache(ttl, time.Second, nil) - for _, podToAssume := range tt.podsToAssume { - if err := assumeAndFinishBinding(cache, podToAssume, now); err != nil { - t.Fatalf("assumePod failed: %v", err) - } - } - cache.cleanupAssumedPods(now.Add(2 * ttl)) - - for _, podToAdd := range tt.podsToAdd { - if err := cache.AddPod(podToAdd); err != nil { - t.Fatalf("AddPod failed: %v", err) - } - } - - for i := range tt.podsToUpdate { - if i == 0 { - continue - } - if err := cache.UpdatePod(tt.podsToUpdate[i-1], tt.podsToUpdate[i]); err != nil { - t.Fatalf("UpdatePod failed: %v", err) - } - // check after expiration. confirmed pods shouldn't be expired. - n := cache.nodes[nodeName] - deepEqualWithoutGeneration(t, i, n, tt.wNodeInfo[i-1]) - } - } -} - -// TestRemovePod tests after added pod is removed, its information should also be subtracted. -func TestRemovePod(t *testing.T) { - nodeName := "node" - basePod := makeBasePod(t, nodeName, "test", "100m", "500", "", []v1.ContainerPort{{HostPort: 80}}) - tests := []struct { - pod *v1.Pod - wNodeInfo *NodeInfo - }{{ - pod: basePod, - wNodeInfo: &NodeInfo{ - requestedResource: &Resource{ - MilliCPU: 100, - Memory: 500, - }, - nonzeroRequest: &Resource{ - MilliCPU: 100, - Memory: 500, - }, - allocatableResource: &Resource{}, - pods: []*v1.Pod{basePod}, - usedPorts: map[int]bool{80: true}, - }, - }} - - for i, tt := range tests { - cache := newSchedulerCache(time.Second, time.Second, nil) - if err := cache.AddPod(tt.pod); err != nil { - t.Fatalf("AddPod failed: %v", err) - } - n := cache.nodes[nodeName] - deepEqualWithoutGeneration(t, i, n, tt.wNodeInfo) - - if err := cache.RemovePod(tt.pod); err != nil { - t.Fatalf("RemovePod failed: %v", err) - } - - n = cache.nodes[nodeName] - if n != nil { - t.Errorf("#%d: expecting pod deleted and nil node info, get=%s", i, n) - } - } -} - -func TestForgetPod(t *testing.T) { - nodeName := "node" - basePod := makeBasePod(t, nodeName, "test", "100m", "500", "", []v1.ContainerPort{{HostPort: 80}}) - tests := []struct { - pods []*v1.Pod - }{{ - pods: []*v1.Pod{basePod}, - }} - now := time.Now() - ttl := 10 * time.Second - - for i, tt := range tests { - cache := newSchedulerCache(ttl, time.Second, nil) - for _, pod := range tt.pods { - if err := assumeAndFinishBinding(cache, pod, now); err != nil { - t.Fatalf("assumePod failed: %v", err) - } - isAssumed, err := cache.IsAssumedPod(pod) - if err != nil { - t.Fatalf("IsAssumedPod failed: %v.", err) - } - if !isAssumed { - t.Fatalf("Pod is expected to be assumed.") - } - assumedPod, err := cache.GetPod(pod) - if err != nil { - t.Fatalf("GetPod failed: %v.", err) - } - if assumedPod.Namespace != pod.Namespace { - t.Errorf("assumedPod.Namespace != pod.Namespace (%s != %s)", assumedPod.Namespace, pod.Namespace) - } - if assumedPod.Name != pod.Name { - t.Errorf("assumedPod.Name != pod.Name (%s != %s)", assumedPod.Name, pod.Name) - } - } - for _, pod := range tt.pods { - if err := cache.ForgetPod(pod); err != nil { - t.Fatalf("ForgetPod failed: %v", err) - } - isAssumed, err := cache.IsAssumedPod(pod) - if err != nil { - t.Fatalf("IsAssumedPod failed: %v.", err) - } - if isAssumed { - t.Fatalf("Pod is expected to be unassumed.") - } - } - cache.cleanupAssumedPods(now.Add(2 * ttl)) - if n := cache.nodes[nodeName]; n != nil { - t.Errorf("#%d: expecting pod deleted and nil node info, get=%s", i, n) - } - } -} - -// getResourceRequest returns the resource request of all containers in Pods; -// excuding initContainers. -func getResourceRequest(pod *v1.Pod) v1.ResourceList { - result := &Resource{} - for _, container := range pod.Spec.Containers { - result.Add(container.Resources.Requests) - } - - return result.ResourceList() -} - -// buildNodeInfo creates a NodeInfo by simulating node operations in cache. -func buildNodeInfo(node *v1.Node, pods []*v1.Pod) *NodeInfo { - expected := NewNodeInfo() - - // Simulate SetNode. - expected.node = node - expected.allocatableResource = NewResource(node.Status.Allocatable) - expected.taints = node.Spec.Taints - expected.generation++ - - for _, pod := range pods { - // Simulate AddPod - expected.pods = append(expected.pods, pod) - expected.requestedResource.Add(getResourceRequest(pod)) - expected.nonzeroRequest.Add(getResourceRequest(pod)) - expected.usedPorts = schedutil.GetUsedPorts(pod) - expected.generation++ - } - - return expected -} - -// TestNodeOperators tests node operations of cache, including add, update -// and remove. -func TestNodeOperators(t *testing.T) { - // Test datas - nodeName := "test-node" - cpu_1 := resource.MustParse("1000m") - mem_100m := resource.MustParse("100m") - cpu_half := resource.MustParse("500m") - mem_50m := resource.MustParse("50m") - resourceFooName := "pod.alpha.kubernetes.io/opaque-int-resource-foo" - resourceFoo := resource.MustParse("1") - - tests := []struct { - node *v1.Node - pods []*v1.Pod - }{ - { - node: &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: nodeName, - }, - Status: v1.NodeStatus{ - Allocatable: v1.ResourceList{ - v1.ResourceCPU: cpu_1, - v1.ResourceMemory: mem_100m, - v1.ResourceName(resourceFooName): resourceFoo, - }, - }, - Spec: v1.NodeSpec{ - Taints: []v1.Taint{ - { - Key: "test-key", - Value: "test-value", - Effect: v1.TaintEffectPreferNoSchedule, - }, - }, - }, - }, - pods: []*v1.Pod{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pod1", - }, - Spec: v1.PodSpec{ - NodeName: nodeName, - Containers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: cpu_half, - v1.ResourceMemory: mem_50m, - }, - }, - Ports: []v1.ContainerPort{ - { - Name: "http", - HostPort: 80, - ContainerPort: 80, - }, - }, - }, - }, - }, - }, - }, - }, - { - node: &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: nodeName, - }, - Status: v1.NodeStatus{ - Allocatable: v1.ResourceList{ - v1.ResourceCPU: cpu_1, - v1.ResourceMemory: mem_100m, - v1.ResourceName(resourceFooName): resourceFoo, - }, - }, - Spec: v1.NodeSpec{ - Taints: []v1.Taint{ - { - Key: "test-key", - Value: "test-value", - Effect: v1.TaintEffectPreferNoSchedule, - }, - }, - }, - }, - pods: []*v1.Pod{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pod1", - }, - Spec: v1.PodSpec{ - NodeName: nodeName, - Containers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: cpu_half, - v1.ResourceMemory: mem_50m, - }, - }, - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pod2", - }, - Spec: v1.PodSpec{ - NodeName: nodeName, - Containers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: cpu_half, - v1.ResourceMemory: mem_50m, - }, - }, - }, - }, - }, - }, - }, - }, - } - - for _, test := range tests { - expected := buildNodeInfo(test.node, test.pods) - node := test.node - - cache := newSchedulerCache(time.Second, time.Second, nil) - cache.AddNode(node) - for _, pod := range test.pods { - cache.AddPod(pod) - } - - // Case 1: the node was added into cache successfully. - got, found := cache.nodes[node.Name] - if !found { - t.Errorf("Failed to find node %v in schedulercache.", node.Name) - } - - if !reflect.DeepEqual(got, expected) { - t.Errorf("Failed to add node into schedulercache:\n got: %+v \nexpected: %+v", got, expected) - } - - // Case 2: dump cached nodes successfully. - cachedNodes := map[string]*NodeInfo{} - cache.UpdateNodeNameToInfoMap(cachedNodes) - newNode, found := cachedNodes[node.Name] - if !found || len(cachedNodes) != 1 { - t.Errorf("failed to dump cached nodes:\n got: %v \nexpected: %v", cachedNodes, cache.nodes) - } - if !reflect.DeepEqual(newNode, expected) { - t.Errorf("Failed to clone node:\n got: %+v, \n expected: %+v", newNode, expected) - } - - // Case 3: update node attribute successfully. - node.Status.Allocatable[v1.ResourceMemory] = mem_50m - expected.allocatableResource.Memory = mem_50m.Value() - expected.generation++ - cache.UpdateNode(nil, node) - got, found = cache.nodes[node.Name] - if !found { - t.Errorf("Failed to find node %v in schedulercache after UpdateNode.", node.Name) - } - - if !reflect.DeepEqual(got, expected) { - t.Errorf("Failed to update node in schedulercache:\n got: %+v \nexpected: %+v", got, expected) - } - - // Case 4: the node can not be removed if pods is not empty. - cache.RemoveNode(node) - if _, found := cache.nodes[node.Name]; !found { - t.Errorf("The node %v should not be removed if pods is not empty.", node.Name) - } - } -} - -func BenchmarkList1kNodes30kPods(b *testing.B) { - cache := setupCacheOf1kNodes30kPods(b) - b.ResetTimer() - for n := 0; n < b.N; n++ { - cache.List(labels.Everything()) - } -} - -func BenchmarkExpire100Pods(b *testing.B) { - benchmarkExpire(b, 100) -} - -func BenchmarkExpire1kPods(b *testing.B) { - benchmarkExpire(b, 1000) -} - -func BenchmarkExpire10kPods(b *testing.B) { - benchmarkExpire(b, 10000) -} - -func benchmarkExpire(b *testing.B, podNum int) { - now := time.Now() - for n := 0; n < b.N; n++ { - b.StopTimer() - cache := setupCacheWithAssumedPods(b, podNum, now) - b.StartTimer() - cache.cleanupAssumedPods(now.Add(2 * time.Second)) - } -} - -type testingMode interface { - Fatalf(format string, args ...interface{}) -} - -func makeBasePod(t testingMode, nodeName, objName, cpu, mem, oir string, ports []v1.ContainerPort) *v1.Pod { - req := v1.ResourceList{} - if cpu != "" { - req = v1.ResourceList{ - v1.ResourceCPU: resource.MustParse(cpu), - v1.ResourceMemory: resource.MustParse(mem), - } - if oir != "" { - if len(strings.Split(oir, ":")) != 2 { - t.Fatalf("Invalid OIR string") - } - var name v1.ResourceName - if strings.Split(oir, ":")[0] != "random-invalid-oir-key" { - name = v1helper.OpaqueIntResourceName(strings.Split(oir, ":")[0]) - } else { - name = v1.ResourceName(strings.Split(oir, ":")[0]) - } - quantity := resource.MustParse(strings.Split(oir, ":")[1]) - req[name] = quantity - } - } - return &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "node_info_cache_test", - Name: objName, - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{{ - Resources: v1.ResourceRequirements{ - Requests: req, - }, - Ports: ports, - }}, - NodeName: nodeName, - }, - } -} - -func setupCacheOf1kNodes30kPods(b *testing.B) Cache { - cache := newSchedulerCache(time.Second, time.Second, nil) - for i := 0; i < 1000; i++ { - nodeName := fmt.Sprintf("node-%d", i) - for j := 0; j < 30; j++ { - objName := fmt.Sprintf("%s-pod-%d", nodeName, j) - pod := makeBasePod(b, nodeName, objName, "0", "0", "", nil) - - if err := cache.AddPod(pod); err != nil { - b.Fatalf("AddPod failed: %v", err) - } - } - } - return cache -} - -func setupCacheWithAssumedPods(b *testing.B, podNum int, assumedTime time.Time) *schedulerCache { - cache := newSchedulerCache(time.Second, time.Second, nil) - for i := 0; i < podNum; i++ { - nodeName := fmt.Sprintf("node-%d", i/10) - objName := fmt.Sprintf("%s-pod-%d", nodeName, i%10) - pod := makeBasePod(b, nodeName, objName, "0", "0", "", nil) - - err := assumeAndFinishBinding(cache, pod, assumedTime) - if err != nil { - b.Fatalf("assumePod failed: %v", err) - } - } - return cache -} diff --git a/plugin/pkg/scheduler/schedulercache/interface.go b/plugin/pkg/scheduler/schedulercache/interface.go deleted file mode 100644 index a1ef77ea4fa..00000000000 --- a/plugin/pkg/scheduler/schedulercache/interface.go +++ /dev/null @@ -1,108 +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 schedulercache - -import ( - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/labels" -) - -type PodFilter func(*v1.Pod) bool - -// Cache collects pods' information and provides node-level aggregated information. -// It's intended for generic scheduler to do efficient lookup. -// Cache's operations are pod centric. It does incremental updates based on pod events. -// Pod events are sent via network. We don't have guaranteed delivery of all events: -// We use Reflector to list and watch from remote. -// Reflector might be slow and do a relist, which would lead to missing events. -// -// State Machine of a pod's events in scheduler's cache: -// -// -// +-------------------------------------------+ +----+ -// | Add | | | -// | | | | Update -// + Assume Add v v | -//Initial +--------> Assumed +------------+---> Added <--+ -// ^ + + | + -// | | | | | -// | | | Add | | Remove -// | | | | | -// | | | + | -// +----------------+ +-----------> Expired +----> Deleted -// Forget Expire -// -// -// 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. -// 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 { - // 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(pod *v1.Pod) error - - // FinishBinding signals that cache for assumed pod can be expired - FinishBinding(pod *v1.Pod) error - - // ForgetPod removes an assumed pod from cache. - ForgetPod(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(pod *v1.Pod) error - - // UpdatePod removes oldPod's information and adds newPod's information. - UpdatePod(oldPod, newPod *v1.Pod) error - - // RemovePod removes a pod. The pod's information would be subtracted from assigned node. - RemovePod(pod *v1.Pod) error - - // GetPod returns the pod from the cache with the same namespace and the - // 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(pod *v1.Pod) (bool, error) - - // AddNode adds overall information about node. - AddNode(node *v1.Node) error - - // UpdateNode updates overall information about node. - UpdateNode(oldNode, newNode *v1.Node) error - - // RemoveNode removes overall information about node. - RemoveNode(node *v1.Node) error - - // UpdateNodeNameToInfoMap updates the passed infoMap to the current contents of Cache. - // The node info contains aggregated information of pods scheduled (including assumed to be) - // on this node. - UpdateNodeNameToInfoMap(infoMap map[string]*NodeInfo) error - - // List lists all cached pods (including assumed ones). - List(labels.Selector) ([]*v1.Pod, error) - - // FilteredList returns all cached pods that pass the filter. - FilteredList(filter PodFilter, selector labels.Selector) ([]*v1.Pod, error) -} diff --git a/plugin/pkg/scheduler/testing/BUILD b/plugin/pkg/scheduler/testing/BUILD deleted file mode 100644 index 804d32d330b..00000000000 --- a/plugin/pkg/scheduler/testing/BUILD +++ /dev/null @@ -1,38 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "fake_cache.go", - "fake_lister.go", - "pods_to_cache.go", - ], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/testing", - deps = [ - "//plugin/pkg/scheduler/algorithm:go_default_library", - "//plugin/pkg/scheduler/schedulercache:go_default_library", - "//vendor/k8s.io/api/apps/v1beta1:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/plugin/pkg/scheduler/testutil.go b/plugin/pkg/scheduler/testutil.go deleted file mode 100644 index d4bd356aff6..00000000000 --- a/plugin/pkg/scheduler/testutil.go +++ /dev/null @@ -1,115 +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 scheduler - -import ( - "fmt" - - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/util/sets" - clientset "k8s.io/client-go/kubernetes" - corelisters "k8s.io/client-go/listers/core/v1" - "k8s.io/client-go/tools/cache" - "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" - schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" - "k8s.io/kubernetes/plugin/pkg/scheduler/util" -) - -// FakeConfigurator is an implementation for test. -type FakeConfigurator struct { - Config *Config -} - -// GetPriorityFunctionConfigs is not implemented yet. -func (fc *FakeConfigurator) GetPriorityFunctionConfigs(priorityKeys sets.String) ([]algorithm.PriorityConfig, error) { - return nil, fmt.Errorf("not implemented") -} - -// GetPriorityMetadataProducer is not implemented yet. -func (fc *FakeConfigurator) GetPriorityMetadataProducer() (algorithm.MetadataProducer, error) { - return nil, fmt.Errorf("not implemented") -} - -// GetPredicateMetadataProducer is not implemented yet. -func (fc *FakeConfigurator) GetPredicateMetadataProducer() (algorithm.PredicateMetadataProducer, error) { - return nil, fmt.Errorf("not implemented") -} - -// GetPredicates is not implemented yet. -func (fc *FakeConfigurator) GetPredicates(predicateKeys sets.String) (map[string]algorithm.FitPredicate, error) { - return nil, fmt.Errorf("not implemented") -} - -// GetHardPodAffinitySymmetricWeight is not implemented yet. -func (fc *FakeConfigurator) GetHardPodAffinitySymmetricWeight() int { - panic("not implemented") -} - -// GetSchedulerName is not implemented yet. -func (fc *FakeConfigurator) GetSchedulerName() string { - panic("not implemented") -} - -// MakeDefaultErrorFunc is not implemented yet. -func (fc *FakeConfigurator) MakeDefaultErrorFunc(backoff *util.PodBackoff, podQueue *cache.FIFO) func(pod *v1.Pod, err error) { - return nil -} - -// ResponsibleForPod is not implemented yet. -func (fc *FakeConfigurator) ResponsibleForPod(pod *v1.Pod) bool { - panic("not implemented") -} - -// GetNodeLister is not implemented yet. -func (fc *FakeConfigurator) GetNodeLister() corelisters.NodeLister { - return nil -} - -// GetClient is not implemented yet. -func (fc *FakeConfigurator) GetClient() clientset.Interface { - return nil -} - -// GetScheduledPodLister is not implemented yet. -func (fc *FakeConfigurator) GetScheduledPodLister() corelisters.PodLister { - return nil -} - -// Run is not implemented yet. -func (fc *FakeConfigurator) Run() { - panic("not implemented") -} - -// Create returns FakeConfigurator.Config -func (fc *FakeConfigurator) Create() (*Config, error) { - return fc.Config, nil -} - -// CreateFromProvider returns FakeConfigurator.Config -func (fc *FakeConfigurator) CreateFromProvider(providerName string) (*Config, error) { - return fc.Config, nil -} - -// CreateFromConfig returns FakeConfigurator.Config -func (fc *FakeConfigurator) CreateFromConfig(policy schedulerapi.Policy) (*Config, error) { - return fc.Config, nil -} - -// CreateFromKeys returns FakeConfigurator.Config -func (fc *FakeConfigurator) CreateFromKeys(predicateKeys, priorityKeys sets.String, extenders []algorithm.SchedulerExtender) (*Config, error) { - return fc.Config, nil -} diff --git a/plugin/pkg/scheduler/util/BUILD b/plugin/pkg/scheduler/util/BUILD deleted file mode 100644 index c829273d39d..00000000000 --- a/plugin/pkg/scheduler/util/BUILD +++ /dev/null @@ -1,58 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = [ - "backoff_utils_test.go", - "testutil_test.go", - "utils_test.go", - ], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/util", - library = ":go_default_library", - deps = [ - "//pkg/apis/scheduling:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - ], -) - -go_library( - name = "go_default_library", - srcs = [ - "backoff_utils.go", - "testutil.go", - "utils.go", - ], - importpath = "k8s.io/kubernetes/plugin/pkg/scheduler/util", - deps = [ - "//pkg/api:go_default_library", - "//pkg/api/install:go_default_library", - "//pkg/apis/scheduling:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/plugin/pkg/scheduler/util/testutil.go b/plugin/pkg/scheduler/util/testutil.go deleted file mode 100644 index 8b5ef89e830..00000000000 --- a/plugin/pkg/scheduler/util/testutil.go +++ /dev/null @@ -1,168 +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 util - -import ( - "fmt" - "mime" - "os" - "reflect" - "strings" - - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/kubernetes/pkg/api" - - _ "k8s.io/kubernetes/pkg/api/install" -) - -type TestGroup struct { - externalGroupVersion schema.GroupVersion - internalGroupVersion schema.GroupVersion - internalTypes map[string]reflect.Type - externalTypes map[string]reflect.Type -} - -var ( - Groups = make(map[string]TestGroup) - Test TestGroup - - serializer runtime.SerializerInfo -) - -func init() { - if apiMediaType := os.Getenv("KUBE_TEST_API_TYPE"); len(apiMediaType) > 0 { - var ok bool - mediaType, _, err := mime.ParseMediaType(apiMediaType) - if err != nil { - panic(err) - } - serializer, ok = runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), mediaType) - if !ok { - panic(fmt.Sprintf("no serializer for %s", apiMediaType)) - } - } - - kubeTestAPI := os.Getenv("KUBE_TEST_API") - if len(kubeTestAPI) != 0 { - // priority is "first in list preferred", so this has to run in reverse order - testGroupVersions := strings.Split(kubeTestAPI, ",") - for i := len(testGroupVersions) - 1; i >= 0; i-- { - gvString := testGroupVersions[i] - groupVersion, err := schema.ParseGroupVersion(gvString) - if err != nil { - panic(fmt.Sprintf("Error parsing groupversion %v: %v", gvString, err)) - } - - internalGroupVersion := schema.GroupVersion{Group: groupVersion.Group, Version: runtime.APIVersionInternal} - Groups[groupVersion.Group] = TestGroup{ - externalGroupVersion: groupVersion, - internalGroupVersion: internalGroupVersion, - internalTypes: api.Scheme.KnownTypes(internalGroupVersion), - externalTypes: api.Scheme.KnownTypes(groupVersion), - } - } - } - - if _, ok := Groups[api.GroupName]; !ok { - externalGroupVersion := schema.GroupVersion{Group: api.GroupName, Version: api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version} - Groups[api.GroupName] = TestGroup{ - externalGroupVersion: externalGroupVersion, - internalGroupVersion: api.SchemeGroupVersion, - internalTypes: api.Scheme.KnownTypes(api.SchemeGroupVersion), - externalTypes: api.Scheme.KnownTypes(externalGroupVersion), - } - } - - Test = Groups[api.GroupName] -} - -// Codec returns the codec for the API version to test against, as set by the -// KUBE_TEST_API_TYPE env var. -func (g TestGroup) Codec() runtime.Codec { - if serializer.Serializer == nil { - return api.Codecs.LegacyCodec(g.externalGroupVersion) - } - return api.Codecs.CodecForVersions(serializer.Serializer, api.Codecs.UniversalDeserializer(), schema.GroupVersions{g.externalGroupVersion}, nil) -} - -// SelfLink returns a self link that will appear to be for the version Version(). -// 'resource' should be the resource path, e.g. "pods" for the Pod type. 'name' should be -// empty for lists. -func (g TestGroup) SelfLink(resource, name string) string { - if g.externalGroupVersion.Group == api.GroupName { - if name == "" { - return fmt.Sprintf("/api/%s/%s", g.externalGroupVersion.Version, resource) - } - return fmt.Sprintf("/api/%s/%s/%s", g.externalGroupVersion.Version, resource, name) - } - - // TODO: will need a /apis prefix once we have proper multi-group - // support - if name == "" { - return fmt.Sprintf("/apis/%s/%s/%s", g.externalGroupVersion.Group, g.externalGroupVersion.Version, resource) - } - return fmt.Sprintf("/apis/%s/%s/%s/%s", g.externalGroupVersion.Group, g.externalGroupVersion.Version, resource, name) -} - -// ResourcePathWithPrefix returns the appropriate path for the given prefix (watch, proxy, redirect, etc), resource, namespace and name. -// For ex, this is of the form: -// /api/v1/watch/namespaces/foo/pods/pod0 for v1. -func (g TestGroup) ResourcePathWithPrefix(prefix, resource, namespace, name string) string { - var path string - if g.externalGroupVersion.Group == api.GroupName { - path = "/api/" + g.externalGroupVersion.Version - } else { - // TODO: switch back once we have proper multiple group support - // path = "/apis/" + g.Group + "/" + Version(group...) - path = "/apis/" + g.externalGroupVersion.Group + "/" + g.externalGroupVersion.Version - } - - if prefix != "" { - path = path + "/" + prefix - } - if namespace != "" { - path = path + "/namespaces/" + namespace - } - // Resource names are lower case. - resource = strings.ToLower(resource) - if resource != "" { - path = path + "/" + resource - } - if name != "" { - path = path + "/" + name - } - return path -} - -// ResourcePath returns the appropriate path for the given resource, namespace and name. -// For example, this is of the form: -// /api/v1/namespaces/foo/pods/pod0 for v1. -func (g TestGroup) ResourcePath(resource, namespace, name string) string { - return g.ResourcePathWithPrefix("", resource, namespace, name) -} - -// SubResourcePath returns the appropriate path for the given resource, namespace, -// name and subresource. -func (g TestGroup) SubResourcePath(resource, namespace, name, sub string) string { - path := g.ResourcePathWithPrefix("", resource, namespace, name) - if sub != "" { - path = path + "/" + sub - } - - return path -} diff --git a/plugin/pkg/scheduler/util/utils.go b/plugin/pkg/scheduler/util/utils.go deleted file mode 100644 index 2cbe26f2e3a..00000000000 --- a/plugin/pkg/scheduler/util/utils.go +++ /dev/null @@ -1,97 +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 util - -import ( - "sort" - - "k8s.io/api/core/v1" - "k8s.io/kubernetes/pkg/apis/scheduling" -) - -// GetUsedPorts returns the used host ports of Pods: if 'port' was used, a 'port:true' pair -// will be in the result; but it does not resolve port conflict. -func GetUsedPorts(pods ...*v1.Pod) map[int]bool { - ports := make(map[int]bool) - for _, pod := range pods { - for j := range pod.Spec.Containers { - container := &pod.Spec.Containers[j] - for k := range container.Ports { - podPort := &container.Ports[k] - // "0" is explicitly ignored in PodFitsHostPorts, - // which is the only function that uses this value. - if podPort.HostPort != 0 { - ports[int(podPort.HostPort)] = true - } - } - } - } - return ports -} - -// GetPodFullName returns a name that uniquely identifies a pod. -func GetPodFullName(pod *v1.Pod) string { - // Use underscore as the delimiter because it is not allowed in pod name - // (DNS subdomain format). - return pod.Name + "_" + pod.Namespace -} - -// GetPodPriority return priority of the given pod. -func GetPodPriority(pod *v1.Pod) int32 { - if pod.Spec.Priority != nil { - return *pod.Spec.Priority - } - // When priority of a running pod 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 was empty. So, we resolve to the static default priority. - return scheduling.DefaultPriorityWhenNoDefaultClassExists -} - -// SortableList is a list that implements sort.Interface. -type SortableList struct { - Items []interface{} - CompFunc LessFunc -} - -// LessFunc is a function that receives two items and returns true if the first -// item should be placed before the second one when the list is sorted. -type LessFunc func(item1, item2 interface{}) bool - -var _ = sort.Interface(&SortableList{}) - -func (l *SortableList) Len() int { return len(l.Items) } - -func (l *SortableList) Less(i, j int) bool { - return l.CompFunc(l.Items[i], l.Items[j]) -} - -func (l *SortableList) Swap(i, j int) { - l.Items[i], l.Items[j] = l.Items[j], l.Items[i] -} - -// Sort sorts the items in the list using the given CompFunc. Item1 is placed -// before Item2 when CompFunc(Item1, Item2) returns true. -func (l *SortableList) Sort() { - sort.Sort(l) -} - -// HigherPriorityPod return true when priority of the first pod is higher than -// the second one. It takes arguments of the type "interface{}" to be used with -// SortableList, but expects those arguments to be *v1.Pod. -func HigherPriorityPod(pod1, pod2 interface{}) bool { - return GetPodPriority(pod1.(*v1.Pod)) > GetPodPriority(pod2.(*v1.Pod)) -} diff --git a/staging/BUILD b/staging/BUILD index 7b541e908a5..b7f2b8fbd08 100644 --- a/staging/BUILD +++ b/staging/BUILD @@ -17,8 +17,9 @@ filegroup( name = "all-srcs", srcs = [ ":package-srcs", - "//staging/src/k8s.io/api/admission/v1alpha1:all-srcs", + "//staging/src/k8s.io/api/admission/v1beta1:all-srcs", "//staging/src/k8s.io/api/admissionregistration/v1alpha1:all-srcs", + "//staging/src/k8s.io/api/admissionregistration/v1beta1:all-srcs", "//staging/src/k8s.io/api/apps/v1:all-srcs", "//staging/src/k8s.io/api/apps/v1beta1:all-srcs", "//staging/src/k8s.io/api/apps/v1beta2:all-srcs", @@ -33,6 +34,7 @@ filegroup( "//staging/src/k8s.io/api/batch/v2alpha1:all-srcs", "//staging/src/k8s.io/api/certificates/v1beta1:all-srcs", "//staging/src/k8s.io/api/core/v1:all-srcs", + "//staging/src/k8s.io/api/events/v1beta1:all-srcs", "//staging/src/k8s.io/api/extensions/v1beta1:all-srcs", "//staging/src/k8s.io/api/imagepolicy/v1alpha1:all-srcs", "//staging/src/k8s.io/api/networking/v1:all-srcs", @@ -43,6 +45,7 @@ filegroup( "//staging/src/k8s.io/api/scheduling/v1alpha1:all-srcs", "//staging/src/k8s.io/api/settings/v1alpha1:all-srcs", "//staging/src/k8s.io/api/storage/v1:all-srcs", + "//staging/src/k8s.io/api/storage/v1alpha1:all-srcs", "//staging/src/k8s.io/api/storage/v1beta1:all-srcs", "//staging/src/k8s.io/apiextensions-apiserver:all-srcs", "//staging/src/k8s.io/apimachinery/pkg/api/equality:all-srcs", @@ -85,6 +88,7 @@ filegroup( "//staging/src/k8s.io/apimachinery/pkg/util/uuid:all-srcs", "//staging/src/k8s.io/apimachinery/pkg/util/validation:all-srcs", "//staging/src/k8s.io/apimachinery/pkg/util/wait:all-srcs", + "//staging/src/k8s.io/apimachinery/pkg/util/waitgroup:all-srcs", "//staging/src/k8s.io/apimachinery/pkg/util/yaml:all-srcs", "//staging/src/k8s.io/apimachinery/pkg/version:all-srcs", "//staging/src/k8s.io/apimachinery/pkg/watch:all-srcs", @@ -95,6 +99,7 @@ filegroup( "//staging/src/k8s.io/apiserver/pkg/apis/apiserver:all-srcs", "//staging/src/k8s.io/apiserver/pkg/apis/audit:all-srcs", "//staging/src/k8s.io/apiserver/pkg/apis/example:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/apis/example2:all-srcs", "//staging/src/k8s.io/apiserver/pkg/audit:all-srcs", "//staging/src/k8s.io/apiserver/pkg/authentication/authenticator:all-srcs", "//staging/src/k8s.io/apiserver/pkg/authentication/authenticatorfactory:all-srcs", @@ -138,6 +143,7 @@ filegroup( "//staging/src/k8s.io/client-go/informers:all-srcs", "//staging/src/k8s.io/client-go/kubernetes:all-srcs", "//staging/src/k8s.io/client-go/listers/admissionregistration/v1alpha1:all-srcs", + "//staging/src/k8s.io/client-go/listers/admissionregistration/v1beta1:all-srcs", "//staging/src/k8s.io/client-go/listers/apps/v1:all-srcs", "//staging/src/k8s.io/client-go/listers/apps/v1beta1:all-srcs", "//staging/src/k8s.io/client-go/listers/apps/v1beta2:all-srcs", @@ -152,6 +158,7 @@ filegroup( "//staging/src/k8s.io/client-go/listers/batch/v2alpha1:all-srcs", "//staging/src/k8s.io/client-go/listers/certificates/v1beta1:all-srcs", "//staging/src/k8s.io/client-go/listers/core/v1:all-srcs", + "//staging/src/k8s.io/client-go/listers/events/v1beta1:all-srcs", "//staging/src/k8s.io/client-go/listers/extensions/v1beta1:all-srcs", "//staging/src/k8s.io/client-go/listers/imagepolicy/v1alpha1:all-srcs", "//staging/src/k8s.io/client-go/listers/networking/v1:all-srcs", @@ -162,11 +169,13 @@ filegroup( "//staging/src/k8s.io/client-go/listers/scheduling/v1alpha1:all-srcs", "//staging/src/k8s.io/client-go/listers/settings/v1alpha1:all-srcs", "//staging/src/k8s.io/client-go/listers/storage/v1:all-srcs", + "//staging/src/k8s.io/client-go/listers/storage/v1alpha1:all-srcs", "//staging/src/k8s.io/client-go/listers/storage/v1beta1:all-srcs", "//staging/src/k8s.io/client-go/pkg/version:all-srcs", "//staging/src/k8s.io/client-go/plugin/pkg/auth/authenticator/token/oidc/testing:all-srcs", "//staging/src/k8s.io/client-go/plugin/pkg/client/auth:all-srcs", "//staging/src/k8s.io/client-go/rest:all-srcs", + "//staging/src/k8s.io/client-go/scale:all-srcs", "//staging/src/k8s.io/client-go/testing:all-srcs", "//staging/src/k8s.io/client-go/third_party/forked/golang/template:all-srcs", "//staging/src/k8s.io/client-go/tools/auth:all-srcs", @@ -201,6 +210,7 @@ filegroup( "//staging/src/k8s.io/code-generator/cmd/lister-gen:all-srcs", "//staging/src/k8s.io/code-generator/cmd/openapi-gen:all-srcs", "//staging/src/k8s.io/code-generator/cmd/set-gen:all-srcs", + "//staging/src/k8s.io/code-generator/pkg/util:all-srcs", "//staging/src/k8s.io/code-generator/third_party/forked/golang/reflect:all-srcs", "//staging/src/k8s.io/kube-aggregator:all-srcs", "//staging/src/k8s.io/metrics/pkg/apis/custom_metrics:all-srcs", @@ -208,6 +218,7 @@ filegroup( "//staging/src/k8s.io/metrics/pkg/client/clientset_generated/clientset:all-srcs", "//staging/src/k8s.io/metrics/pkg/client/custom_metrics:all-srcs", "//staging/src/k8s.io/sample-apiserver:all-srcs", + "//staging/src/k8s.io/sample-controller:all-srcs", ], tags = ["automanaged"], ) diff --git a/staging/README.md b/staging/README.md index 177f37e23c3..98d47d18b57 100644 --- a/staging/README.md +++ b/staging/README.md @@ -15,6 +15,7 @@ Repositories currently staged here: - [`k8s.io/code-generator`](https://github.com/kubernetes/code-generator) - [`k8s.io/metrics`](https://github.com/kubernetes/metrics) - [`k8s.io/sample-apiserver`](https://github.com/kubernetes/sample-apiserver) +- [`k8s.io/sample-controller`](https://github.com/kubernetes/sample-controller) The code in the staging/ directory is authoritative, i.e. the only copy of the code. You can directly modify such code. diff --git a/staging/src/k8s.io/api/Godeps/Godeps.json b/staging/src/k8s.io/api/Godeps/Godeps.json index 5310765a3cc..ffa35e2d980 100644 --- a/staging/src/k8s.io/api/Godeps/Godeps.json +++ b/staging/src/k8s.io/api/Godeps/Godeps.json @@ -1,6 +1,6 @@ { "ImportPath": "k8s.io/api", - "GoVersion": "go1.8", + "GoVersion": "go1.9", "GodepVersion": "v79", "Packages": [ "./..." @@ -182,6 +182,10 @@ "ImportPath": "k8s.io/apimachinery/pkg/util/intstr", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apimachinery/pkg/util/json", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apimachinery/pkg/util/net", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -216,7 +220,7 @@ }, { "ImportPath": "k8s.io/kube-openapi/pkg/common", - "Rev": "868f2f29720b192240e18284659231b440f9cda5" + "Rev": "39a7bf85c140f972372c2a0d1ee40adbf0c8bfe1" }, { "ImportPath": "k8s.io/apimachinery/pkg/api/resource", diff --git a/vendor/github.com/square/go-jose/LICENSE b/staging/src/k8s.io/api/LICENSE similarity index 100% rename from vendor/github.com/square/go-jose/LICENSE rename to staging/src/k8s.io/api/LICENSE diff --git a/staging/src/k8s.io/api/README.md b/staging/src/k8s.io/api/README.md new file mode 100644 index 00000000000..967543a4545 --- /dev/null +++ b/staging/src/k8s.io/api/README.md @@ -0,0 +1 @@ +This repo is still in the experimental stage. Shortly it will contain the schema of the API that are served by the Kubernetes apiserver. diff --git a/staging/src/k8s.io/api/admission/v1alpha1/BUILD b/staging/src/k8s.io/api/admission/v1alpha1/BUILD deleted file mode 100644 index 96125aa142f..00000000000 --- a/staging/src/k8s.io/api/admission/v1alpha1/BUILD +++ /dev/null @@ -1,43 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "go_default_library", - srcs = [ - "doc.go", - "generated.pb.go", - "register.go", - "types.go", - "types_swagger_doc_generated.go", - "zz_generated.deepcopy.go", - ], - importpath = "k8s.io/api/admission/v1alpha1", - visibility = ["//visibility:public"], - deps = [ - "//vendor/github.com/gogo/protobuf/proto:go_default_library", - "//vendor/k8s.io/api/authentication/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - ], -) - -filegroup( - name = "go_default_library_protos", - srcs = ["generated.proto"], - visibility = ["//visibility:public"], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], - visibility = ["//visibility:public"], -) diff --git a/staging/src/k8s.io/api/admission/v1alpha1/doc.go b/staging/src/k8s.io/api/admission/v1alpha1/doc.go deleted file mode 100644 index 22c61d99d44..00000000000 --- a/staging/src/k8s.io/api/admission/v1alpha1/doc.go +++ /dev/null @@ -1,21 +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,register -// +k8s:openapi-gen=false - -// +groupName=admission.k8s.io -package v1alpha1 // import "k8s.io/api/admission/v1alpha1" diff --git a/staging/src/k8s.io/api/admission/v1alpha1/generated.pb.go b/staging/src/k8s.io/api/admission/v1alpha1/generated.pb.go deleted file mode 100644 index 03fa1f6be8d..00000000000 --- a/staging/src/k8s.io/api/admission/v1alpha1/generated.pb.go +++ /dev/null @@ -1,1031 +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. -*/ - -// Code generated by protoc-gen-gogo. -// source: k8s.io/kubernetes/vendor/k8s.io/api/admission/v1alpha1/generated.proto -// DO NOT EDIT! - -/* - Package v1alpha1 is a generated protocol buffer package. - - It is generated from these files: - k8s.io/kubernetes/vendor/k8s.io/api/admission/v1alpha1/generated.proto - - It has these top-level messages: - AdmissionReview - AdmissionReviewSpec - AdmissionReviewStatus -*/ -package v1alpha1 - -import proto "github.com/gogo/protobuf/proto" -import fmt "fmt" -import math "math" - -import k8s_io_apimachinery_pkg_apis_meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - -import strings "strings" -import reflect "reflect" - -import io "io" - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package - -func (m *AdmissionReview) Reset() { *m = AdmissionReview{} } -func (*AdmissionReview) ProtoMessage() {} -func (*AdmissionReview) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{0} } - -func (m *AdmissionReviewSpec) Reset() { *m = AdmissionReviewSpec{} } -func (*AdmissionReviewSpec) ProtoMessage() {} -func (*AdmissionReviewSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{1} } - -func (m *AdmissionReviewStatus) Reset() { *m = AdmissionReviewStatus{} } -func (*AdmissionReviewStatus) ProtoMessage() {} -func (*AdmissionReviewStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{2} } - -func init() { - proto.RegisterType((*AdmissionReview)(nil), "k8s.io.api.admission.v1alpha1.AdmissionReview") - proto.RegisterType((*AdmissionReviewSpec)(nil), "k8s.io.api.admission.v1alpha1.AdmissionReviewSpec") - proto.RegisterType((*AdmissionReviewStatus)(nil), "k8s.io.api.admission.v1alpha1.AdmissionReviewStatus") -} -func (m *AdmissionReview) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *AdmissionReview) MarshalTo(dAtA []byte) (int, error) { - var i int - _ = i - var l int - _ = l - dAtA[i] = 0xa - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) - n1, err := m.Spec.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n1 - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) - n2, err := m.Status.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n2 - return i, nil -} - -func (m *AdmissionReviewSpec) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *AdmissionReviewSpec) MarshalTo(dAtA []byte) (int, error) { - var i int - _ = i - var l int - _ = l - dAtA[i] = 0xa - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Kind.Size())) - n3, err := m.Kind.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n3 - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Object.Size())) - n4, err := m.Object.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n4 - dAtA[i] = 0x1a - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.OldObject.Size())) - n5, err := m.OldObject.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n5 - dAtA[i] = 0x22 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Operation))) - i += copy(dAtA[i:], m.Operation) - dAtA[i] = 0x2a - i++ - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) - i += copy(dAtA[i:], m.Name) - dAtA[i] = 0x32 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Namespace))) - i += copy(dAtA[i:], m.Namespace) - dAtA[i] = 0x3a - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Resource.Size())) - n6, err := m.Resource.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n6 - dAtA[i] = 0x42 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(len(m.SubResource))) - i += copy(dAtA[i:], m.SubResource) - dAtA[i] = 0x4a - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.UserInfo.Size())) - n7, err := m.UserInfo.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n7 - return i, nil -} - -func (m *AdmissionReviewStatus) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *AdmissionReviewStatus) MarshalTo(dAtA []byte) (int, error) { - var i int - _ = i - var l int - _ = l - dAtA[i] = 0x8 - i++ - if m.Allowed { - dAtA[i] = 1 - } else { - dAtA[i] = 0 - } - i++ - if m.Result != nil { - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Result.Size())) - n8, err := m.Result.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n8 - } - return i, nil -} - -func encodeFixed64Generated(dAtA []byte, offset int, v uint64) int { - dAtA[offset] = uint8(v) - dAtA[offset+1] = uint8(v >> 8) - dAtA[offset+2] = uint8(v >> 16) - dAtA[offset+3] = uint8(v >> 24) - dAtA[offset+4] = uint8(v >> 32) - dAtA[offset+5] = uint8(v >> 40) - dAtA[offset+6] = uint8(v >> 48) - dAtA[offset+7] = uint8(v >> 56) - return offset + 8 -} -func encodeFixed32Generated(dAtA []byte, offset int, v uint32) int { - dAtA[offset] = uint8(v) - dAtA[offset+1] = uint8(v >> 8) - dAtA[offset+2] = uint8(v >> 16) - dAtA[offset+3] = uint8(v >> 24) - return offset + 4 -} -func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - dAtA[offset] = uint8(v) - return offset + 1 -} -func (m *AdmissionReview) Size() (n int) { - var l int - _ = 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 *AdmissionReviewSpec) Size() (n int) { - var l int - _ = l - l = m.Kind.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = m.Object.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = m.OldObject.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Operation) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Name) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Namespace) - n += 1 + l + sovGenerated(uint64(l)) - l = m.Resource.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.SubResource) - n += 1 + l + sovGenerated(uint64(l)) - l = m.UserInfo.Size() - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func (m *AdmissionReviewStatus) Size() (n int) { - var l int - _ = l - n += 2 - if m.Result != nil { - l = m.Result.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - return n -} - -func sovGenerated(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } - } - return n -} -func sozGenerated(x uint64) (n int) { - return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (this *AdmissionReview) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&AdmissionReview{`, - `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "AdmissionReviewSpec", "AdmissionReviewSpec", 1), `&`, ``, 1) + `,`, - `Status:` + strings.Replace(strings.Replace(this.Status.String(), "AdmissionReviewStatus", "AdmissionReviewStatus", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s -} -func (this *AdmissionReviewSpec) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&AdmissionReviewSpec{`, - `Kind:` + strings.Replace(strings.Replace(this.Kind.String(), "GroupVersionKind", "k8s_io_apimachinery_pkg_apis_meta_v1.GroupVersionKind", 1), `&`, ``, 1) + `,`, - `Object:` + strings.Replace(strings.Replace(this.Object.String(), "RawExtension", "k8s_io_apimachinery_pkg_runtime.RawExtension", 1), `&`, ``, 1) + `,`, - `OldObject:` + strings.Replace(strings.Replace(this.OldObject.String(), "RawExtension", "k8s_io_apimachinery_pkg_runtime.RawExtension", 1), `&`, ``, 1) + `,`, - `Operation:` + fmt.Sprintf("%v", this.Operation) + `,`, - `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `Namespace:` + fmt.Sprintf("%v", this.Namespace) + `,`, - `Resource:` + strings.Replace(strings.Replace(this.Resource.String(), "GroupVersionResource", "k8s_io_apimachinery_pkg_apis_meta_v1.GroupVersionResource", 1), `&`, ``, 1) + `,`, - `SubResource:` + fmt.Sprintf("%v", this.SubResource) + `,`, - `UserInfo:` + strings.Replace(strings.Replace(this.UserInfo.String(), "UserInfo", "k8s_io_api_authentication_v1.UserInfo", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s -} -func (this *AdmissionReviewStatus) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&AdmissionReviewStatus{`, - `Allowed:` + fmt.Sprintf("%v", this.Allowed) + `,`, - `Result:` + strings.Replace(fmt.Sprintf("%v", this.Result), "Status", "k8s_io_apimachinery_pkg_apis_meta_v1.Status", 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 *AdmissionReview) 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: AdmissionReview: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: AdmissionReview: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - 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 > l { - return io.ErrUnexpectedEOF - } - if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - 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 > 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 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *AdmissionReviewSpec) 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: AdmissionReviewSpec: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: AdmissionReviewSpec: 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 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 > l { - return io.ErrUnexpectedEOF - } - if err := m.Kind.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - 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 > l { - return io.ErrUnexpectedEOF - } - 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 OldObject", 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 > l { - return io.ErrUnexpectedEOF - } - if err := m.OldObject.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Operation", 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 > l { - return io.ErrUnexpectedEOF - } - m.Operation = Operation(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 5: - 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 > l { - return io.ErrUnexpectedEOF - } - m.Name = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 6: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Namespace", 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 > l { - return io.ErrUnexpectedEOF - } - m.Namespace = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 7: - 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 > l { - return io.ErrUnexpectedEOF - } - if err := m.Resource.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 8: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SubResource", 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 > l { - return io.ErrUnexpectedEOF - } - m.SubResource = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 9: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field UserInfo", 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 > l { - return io.ErrUnexpectedEOF - } - if err := m.UserInfo.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 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *AdmissionReviewStatus) 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: AdmissionReviewStatus: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: AdmissionReviewStatus: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Allowed", 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.Allowed = bool(v != 0) - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Result", 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 > l { - return io.ErrUnexpectedEOF - } - if m.Result == nil { - m.Result = &k8s_io_apimachinery_pkg_apis_meta_v1.Status{} - } - if err := m.Result.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 { - 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 - 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 - } - } - return iNdEx, nil - case 1: - iNdEx += 8 - return iNdEx, nil - 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 - } - } - iNdEx += length - if length < 0 { - return 0, ErrInvalidLengthGenerated - } - return iNdEx, nil - case 3: - for { - var innerWire uint64 - var start int = iNdEx - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowGenerated - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - innerWire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - innerWireType := int(innerWire & 0x7) - if innerWireType == 4 { - break - } - next, err := skipGenerated(dAtA[start:]) - if err != nil { - return 0, err - } - iNdEx = start + next - } - return iNdEx, nil - case 4: - return iNdEx, nil - case 5: - iNdEx += 4 - return iNdEx, nil - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - } - panic("unreachable") -} - -var ( - ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") -) - -func init() { - proto.RegisterFile("k8s.io/kubernetes/vendor/k8s.io/api/admission/v1alpha1/generated.proto", fileDescriptorGenerated) -} - -var fileDescriptorGenerated = []byte{ - // 645 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x94, 0xcf, 0x4f, 0x13, 0x41, - 0x14, 0xc7, 0xbb, 0x5a, 0x4a, 0x3b, 0x18, 0xd1, 0x21, 0x26, 0x1b, 0x12, 0x17, 0xc2, 0xc1, 0x60, - 0x02, 0xb3, 0x01, 0x91, 0x18, 0xe3, 0x85, 0x26, 0x6a, 0x8c, 0x09, 0x98, 0x01, 0x8c, 0x31, 0xc6, - 0x64, 0xba, 0x7d, 0xb4, 0x63, 0xbb, 0x33, 0x9b, 0x9d, 0xd9, 0xa2, 0x37, 0xff, 0x04, 0x0f, 0xfe, - 0x1d, 0xfe, 0x17, 0x26, 0x1c, 0x39, 0x72, 0x22, 0x52, 0xff, 0x0b, 0x4f, 0x66, 0x67, 0x67, 0x77, - 0x4b, 0xa1, 0x2a, 0x9e, 0xda, 0xf7, 0xe3, 0xfb, 0x99, 0xf7, 0xde, 0xbc, 0x59, 0xf4, 0xac, 0xf7, - 0x48, 0x11, 0x2e, 0xfd, 0x5e, 0xd2, 0x82, 0x58, 0x80, 0x06, 0xe5, 0x0f, 0x40, 0xb4, 0x65, 0xec, - 0xdb, 0x00, 0x8b, 0xb8, 0xcf, 0xda, 0x21, 0x57, 0x8a, 0x4b, 0xe1, 0x0f, 0xd6, 0x58, 0x3f, 0xea, - 0xb2, 0x35, 0xbf, 0x03, 0x02, 0x62, 0xa6, 0xa1, 0x4d, 0xa2, 0x58, 0x6a, 0x89, 0xef, 0x66, 0xe9, - 0x84, 0x45, 0x9c, 0x14, 0xe9, 0x24, 0x4f, 0x9f, 0x5f, 0xed, 0x70, 0xdd, 0x4d, 0x5a, 0x24, 0x90, - 0xa1, 0xdf, 0x91, 0x1d, 0xe9, 0x1b, 0x55, 0x2b, 0x39, 0x30, 0x96, 0x31, 0xcc, 0xbf, 0x8c, 0x36, - 0xbf, 0x32, 0x7a, 0x78, 0xa2, 0xbb, 0x20, 0x34, 0x0f, 0x98, 0xce, 0x2a, 0x18, 0x3f, 0x7b, 0x7e, - 0xa3, 0xcc, 0x0e, 0x59, 0xd0, 0xe5, 0x02, 0xe2, 0x4f, 0x7e, 0xd4, 0xeb, 0xa4, 0x0e, 0xe5, 0x87, - 0xa0, 0xd9, 0x65, 0x2a, 0x7f, 0x92, 0x2a, 0x4e, 0x84, 0xe6, 0x21, 0x5c, 0x10, 0x6c, 0xfe, 0x4d, - 0xa0, 0x82, 0x2e, 0x84, 0xec, 0x82, 0xee, 0xc1, 0x24, 0x5d, 0xa2, 0x79, 0xdf, 0xe7, 0x42, 0x2b, - 0x1d, 0x8f, 0x8b, 0x96, 0xbe, 0x3b, 0x68, 0x76, 0x2b, 0x9f, 0x23, 0x85, 0x01, 0x87, 0x43, 0xbc, - 0x87, 0xaa, 0x2a, 0x82, 0xc0, 0x75, 0x16, 0x9d, 0xe5, 0x99, 0xf5, 0x75, 0xf2, 0xc7, 0x91, 0x93, - 0x31, 0xf5, 0x6e, 0x04, 0x41, 0xf3, 0xc6, 0xd1, 0xe9, 0x42, 0x65, 0x78, 0xba, 0x50, 0x4d, 0x2d, - 0x6a, 0x68, 0xf8, 0x1d, 0xaa, 0x29, 0xcd, 0x74, 0xa2, 0xdc, 0x6b, 0x86, 0xbb, 0x71, 0x45, 0xae, - 0xd1, 0x36, 0x6f, 0x5a, 0x72, 0x2d, 0xb3, 0xa9, 0x65, 0x2e, 0x7d, 0x9b, 0x42, 0x73, 0x97, 0x54, - 0x82, 0xdf, 0xa0, 0x6a, 0x8f, 0x8b, 0xb6, 0xed, 0x65, 0x73, 0xe4, 0xcc, 0x62, 0x46, 0x24, 0xea, - 0x75, 0x52, 0x87, 0x22, 0xe9, 0x15, 0x92, 0xc1, 0x1a, 0x79, 0x1e, 0xcb, 0x24, 0x7a, 0x0d, 0x71, - 0xca, 0x7a, 0xc9, 0x45, 0xbb, 0xec, 0x27, 0xb5, 0xa8, 0x21, 0xe2, 0x7d, 0x54, 0x93, 0xad, 0x0f, - 0x10, 0x68, 0xdb, 0xcf, 0xea, 0x44, 0xb6, 0xbd, 0x37, 0x42, 0xd9, 0xe1, 0xd3, 0x8f, 0x1a, 0x44, - 0x8a, 0x2d, 0x1b, 0xd9, 0x31, 0x10, 0x6a, 0x61, 0xf8, 0x3d, 0x6a, 0xc8, 0x7e, 0x3b, 0x73, 0xba, - 0xd7, 0xff, 0x87, 0x7c, 0xdb, 0x92, 0x1b, 0x3b, 0x39, 0x87, 0x96, 0x48, 0xfc, 0x04, 0x35, 0x64, - 0x94, 0xae, 0x00, 0x97, 0xc2, 0xad, 0x2e, 0x3a, 0xcb, 0x8d, 0xa6, 0x57, 0x08, 0xf2, 0xc0, 0xaf, - 0x51, 0x83, 0x96, 0x02, 0xbc, 0x88, 0xaa, 0x82, 0x85, 0xe0, 0x4e, 0x19, 0x61, 0x31, 0x96, 0x6d, - 0x16, 0x02, 0x35, 0x11, 0xec, 0xa3, 0x46, 0xfa, 0xab, 0x22, 0x16, 0x80, 0x5b, 0x33, 0x69, 0x45, - 0x41, 0xdb, 0x79, 0x80, 0x96, 0x39, 0xb8, 0x8b, 0xea, 0x31, 0x28, 0x99, 0xc4, 0x01, 0xb8, 0xd3, - 0xa6, 0xdf, 0xc7, 0x57, 0xbf, 0x25, 0x6a, 0x09, 0xcd, 0x5b, 0xf6, 0xac, 0x7a, 0xee, 0xa1, 0x05, - 0x1d, 0x3f, 0x44, 0x33, 0x2a, 0x69, 0xe5, 0x01, 0xb7, 0x6e, 0x8a, 0x9b, 0xb3, 0x82, 0x99, 0xdd, - 0x32, 0x44, 0x47, 0xf3, 0xf0, 0x1e, 0xaa, 0x27, 0x0a, 0xe2, 0x17, 0xe2, 0x40, 0xba, 0x0d, 0x53, - 0xe0, 0xbd, 0x73, 0xab, 0x7b, 0xee, 0xbb, 0x91, 0x16, 0xb6, 0x6f, 0xb3, 0xcb, 0x62, 0x72, 0x0f, - 0x2d, 0x48, 0x4b, 0x5f, 0x1d, 0x74, 0xe7, 0xd2, 0x15, 0xc7, 0xf7, 0xd1, 0x34, 0xeb, 0xf7, 0xe5, - 0x21, 0x64, 0x5b, 0x5b, 0x6f, 0xce, 0x5a, 0xcc, 0xf4, 0x56, 0xe6, 0xa6, 0x79, 0x1c, 0xbf, 0x1a, - 0x7b, 0x53, 0x2b, 0xff, 0x36, 0x39, 0xfb, 0x96, 0x50, 0xba, 0x7e, 0x14, 0x54, 0xd2, 0xd7, 0xf9, - 0x3b, 0x6a, 0x92, 0xa3, 0x33, 0xaf, 0x72, 0x7c, 0xe6, 0x55, 0x4e, 0xce, 0xbc, 0xca, 0xe7, 0xa1, - 0xe7, 0x1c, 0x0d, 0x3d, 0xe7, 0x78, 0xe8, 0x39, 0x27, 0x43, 0xcf, 0xf9, 0x31, 0xf4, 0x9c, 0x2f, - 0x3f, 0xbd, 0xca, 0xdb, 0x7a, 0xfe, 0x4a, 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, 0xa0, 0x57, 0xa7, - 0x50, 0xd8, 0x05, 0x00, 0x00, -} diff --git a/staging/src/k8s.io/api/admission/v1alpha1/generated.proto b/staging/src/k8s.io/api/admission/v1alpha1/generated.proto deleted file mode 100644 index 8859a46fa9c..00000000000 --- a/staging/src/k8s.io/api/admission/v1alpha1/generated.proto +++ /dev/null @@ -1,95 +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. -*/ - - -// This file was autogenerated by go-to-protobuf. Do not edit it manually! - -syntax = 'proto2'; - -package k8s.io.api.admission.v1alpha1; - -import "k8s.io/api/authentication/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"; -import "k8s.io/apimachinery/pkg/util/intstr/generated.proto"; - -// Package-wide variables from generator "generated". -option go_package = "v1alpha1"; - -// AdmissionReview describes an admission request. -message AdmissionReview { - // Spec describes the attributes for the admission request. - // Since this admission controller is non-mutating the webhook should avoid setting this in its response to avoid the - // cost of deserializing it. - // +optional - optional AdmissionReviewSpec spec = 1; - - // Status is filled in by the webhook and indicates whether the admission request should be permitted. - // +optional - optional AdmissionReviewStatus status = 2; -} - -// AdmissionReviewSpec describes the admission.Attributes for the admission request. -message AdmissionReviewSpec { - // Kind is the type of object being manipulated. For example: Pod - optional k8s.io.apimachinery.pkg.apis.meta.v1.GroupVersionKind kind = 1; - - // Object is the object from the incoming request prior to default values being applied - optional k8s.io.apimachinery.pkg.runtime.RawExtension object = 2; - - // OldObject is the existing object. Only populated for UPDATE requests. - // +optional - optional k8s.io.apimachinery.pkg.runtime.RawExtension oldObject = 3; - - // Operation is the operation being performed - optional string operation = 4; - - // Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and - // rely on the server to generate the name. If that is the case, this method will return the empty string. - // +optional - optional string name = 5; - - // Namespace is the namespace associated with the request (if any). - // +optional - optional string namespace = 6; - - // Resource is the name of the resource being requested. This is not the kind. For example: pods - optional k8s.io.apimachinery.pkg.apis.meta.v1.GroupVersionResource resource = 7; - - // SubResource is the name of the subresource being requested. This is a different resource, scoped to the parent - // resource, but it may have a different kind. For instance, /pods has the resource "pods" and the kind "Pod", while - // /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod" (because status operates on - // pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource - // "binding", and kind "Binding". - // +optional - optional string subResource = 8; - - // UserInfo is information about the requesting user - optional k8s.io.api.authentication.v1.UserInfo userInfo = 9; -} - -// AdmissionReviewStatus describes the status of the admission request. -message AdmissionReviewStatus { - // Allowed indicates whether or not the admission request was permitted. - optional bool allowed = 1; - - // Result contains extra details into why an admission request was denied. - // This field IS NOT consulted in any way if "Allowed" is "true". - // +optional - optional k8s.io.apimachinery.pkg.apis.meta.v1.Status status = 2; -} - diff --git a/staging/src/k8s.io/api/admission/v1alpha1/register.go b/staging/src/k8s.io/api/admission/v1alpha1/register.go deleted file mode 100644 index ea6d5609af3..00000000000 --- a/staging/src/k8s.io/api/admission/v1alpha1/register.go +++ /dev/null @@ -1,51 +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 for this API. -const GroupName = "admission.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 api.Scheme. -func addKnownTypes(scheme *runtime.Scheme) error { - scheme.AddKnownTypes(SchemeGroupVersion, - &AdmissionReview{}, - ) - metav1.AddToGroupVersion(scheme, SchemeGroupVersion) - return nil -} diff --git a/staging/src/k8s.io/api/admission/v1alpha1/types.go b/staging/src/k8s.io/api/admission/v1alpha1/types.go deleted file mode 100644 index 5defadca6bd..00000000000 --- a/staging/src/k8s.io/api/admission/v1alpha1/types.go +++ /dev/null @@ -1,90 +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 ( - authenticationv1 "k8s.io/api/authentication/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" -) - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// AdmissionReview describes an admission request. -type AdmissionReview struct { - metav1.TypeMeta `json:",inline"` - // Spec describes the attributes for the admission request. - // Since this admission controller is non-mutating the webhook should avoid setting this in its response to avoid the - // cost of deserializing it. - // +optional - Spec AdmissionReviewSpec `json:"spec,omitempty" protobuf:"bytes,1,opt,name=spec"` - // Status is filled in by the webhook and indicates whether the admission request should be permitted. - // +optional - Status AdmissionReviewStatus `json:"status,omitempty" protobuf:"bytes,2,opt,name=status"` -} - -// AdmissionReviewSpec describes the admission.Attributes for the admission request. -type AdmissionReviewSpec struct { - // Kind is the type of object being manipulated. For example: Pod - Kind metav1.GroupVersionKind `json:"kind,omitempty" protobuf:"bytes,1,opt,name=kind"` - // Object is the object from the incoming request prior to default values being applied - Object runtime.RawExtension `json:"object,omitempty" protobuf:"bytes,2,opt,name=object"` - // OldObject is the existing object. Only populated for UPDATE requests. - // +optional - OldObject runtime.RawExtension `json:"oldObject,omitempty" protobuf:"bytes,3,opt,name=oldObject"` - // Operation is the operation being performed - Operation Operation `json:"operation,omitempty" protobuf:"bytes,4,opt,name=operation"` - // Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and - // rely on the server to generate the name. If that is the case, this method will return the empty string. - // +optional - Name string `json:"name,omitempty" protobuf:"bytes,5,opt,name=name"` - // Namespace is the namespace associated with the request (if any). - // +optional - Namespace string `json:"namespace,omitempty" protobuf:"bytes,6,opt,name=namespace"` - // Resource is the name of the resource being requested. This is not the kind. For example: pods - Resource metav1.GroupVersionResource `json:"resource,omitempty" protobuf:"bytes,7,opt,name=resource"` - // SubResource is the name of the subresource being requested. This is a different resource, scoped to the parent - // resource, but it may have a different kind. For instance, /pods has the resource "pods" and the kind "Pod", while - // /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod" (because status operates on - // pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource - // "binding", and kind "Binding". - // +optional - SubResource string `json:"subResource,omitempty" protobuf:"bytes,8,opt,name=subResource"` - // UserInfo is information about the requesting user - UserInfo authenticationv1.UserInfo `json:"userInfo,omitempty" protobuf:"bytes,9,opt,name=userInfo"` -} - -// AdmissionReviewStatus describes the status of the admission request. -type AdmissionReviewStatus struct { - // Allowed indicates whether or not the admission request was permitted. - Allowed bool `json:"allowed" protobuf:"varint,1,opt,name=allowed"` - // Result contains extra details into why an admission request was denied. - // This field IS NOT consulted in any way if "Allowed" is "true". - // +optional - Result *metav1.Status `json:"status,omitempty" protobuf:"bytes,2,opt,name=status"` -} - -// Operation is the type of resource operation being checked for admission control -type Operation string - -// Operation constants -const ( - Create Operation = "CREATE" - Update Operation = "UPDATE" - Delete Operation = "DELETE" - Connect Operation = "CONNECT" -) diff --git a/staging/src/k8s.io/api/admission/v1alpha1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/admission/v1alpha1/types_swagger_doc_generated.go deleted file mode 100644 index 07da434c98d..00000000000 --- a/staging/src/k8s.io/api/admission/v1alpha1/types_swagger_doc_generated.go +++ /dev/null @@ -1,67 +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 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-generated-swagger-docs.sh - -// AUTO-GENERATED FUNCTIONS START HERE -var map_AdmissionReview = map[string]string{ - "": "AdmissionReview describes an admission request.", - "spec": "Spec describes the attributes for the admission request. Since this admission controller is non-mutating the webhook should avoid setting this in its response to avoid the cost of deserializing it.", - "status": "Status is filled in by the webhook and indicates whether the admission request should be permitted.", -} - -func (AdmissionReview) SwaggerDoc() map[string]string { - return map_AdmissionReview -} - -var map_AdmissionReviewSpec = map[string]string{ - "": "AdmissionReviewSpec describes the admission.Attributes for the admission request.", - "kind": "Kind is the type of object being manipulated. For example: Pod", - "object": "Object is the object from the incoming request prior to default values being applied", - "oldObject": "OldObject is the existing object. Only populated for UPDATE requests.", - "operation": "Operation is the operation being performed", - "name": "Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and rely on the server to generate the name. If that is the case, this method will return the empty string.", - "namespace": "Namespace is the namespace associated with the request (if any).", - "resource": "Resource is the name of the resource being requested. This is not the kind. For example: pods", - "subResource": "SubResource is the name of the subresource being requested. This is a different resource, scoped to the parent resource, but it may have a different kind. For instance, /pods has the resource \"pods\" and the kind \"Pod\", while /pods/foo/status has the resource \"pods\", the sub resource \"status\", and the kind \"Pod\" (because status operates on pods). The binding resource for a pod though may be /pods/foo/binding, which has resource \"pods\", subresource \"binding\", and kind \"Binding\".", - "userInfo": "UserInfo is information about the requesting user", -} - -func (AdmissionReviewSpec) SwaggerDoc() map[string]string { - return map_AdmissionReviewSpec -} - -var map_AdmissionReviewStatus = map[string]string{ - "": "AdmissionReviewStatus describes the status of the admission request.", - "allowed": "Allowed indicates whether or not the admission request was permitted.", - "status": "Result contains extra details into why an admission request was denied. This field IS NOT consulted in any way if \"Allowed\" is \"true\".", -} - -func (AdmissionReviewStatus) SwaggerDoc() map[string]string { - return map_AdmissionReviewStatus -} - -// AUTO-GENERATED FUNCTIONS END HERE diff --git a/staging/src/k8s.io/api/admission/v1alpha1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/admission/v1alpha1/zz_generated.deepcopy.go deleted file mode 100644 index 667792aeb68..00000000000 --- a/staging/src/k8s.io/api/admission/v1alpha1/zz_generated.deepcopy.go +++ /dev/null @@ -1,127 +0,0 @@ -// +build !ignore_autogenerated - -/* -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. -*/ - -// This file was autogenerated by deepcopy-gen. Do not edit it manually! - -package v1alpha1 - -import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" -) - -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AdmissionReview).DeepCopyInto(out.(*AdmissionReview)) - return nil - }, InType: reflect.TypeOf(&AdmissionReview{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AdmissionReviewSpec).DeepCopyInto(out.(*AdmissionReviewSpec)) - return nil - }, InType: reflect.TypeOf(&AdmissionReviewSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AdmissionReviewStatus).DeepCopyInto(out.(*AdmissionReviewStatus)) - return nil - }, InType: reflect.TypeOf(&AdmissionReviewStatus{})}, - ) -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AdmissionReview) DeepCopyInto(out *AdmissionReview) { - *out = *in - out.TypeMeta = in.TypeMeta - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionReview. -func (in *AdmissionReview) DeepCopy() *AdmissionReview { - if in == nil { - return nil - } - out := new(AdmissionReview) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *AdmissionReview) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AdmissionReviewSpec) DeepCopyInto(out *AdmissionReviewSpec) { - *out = *in - out.Kind = in.Kind - in.Object.DeepCopyInto(&out.Object) - in.OldObject.DeepCopyInto(&out.OldObject) - out.Resource = in.Resource - in.UserInfo.DeepCopyInto(&out.UserInfo) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionReviewSpec. -func (in *AdmissionReviewSpec) DeepCopy() *AdmissionReviewSpec { - if in == nil { - return nil - } - out := new(AdmissionReviewSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AdmissionReviewStatus) DeepCopyInto(out *AdmissionReviewStatus) { - *out = *in - if in.Result != nil { - in, out := &in.Result, &out.Result - if *in == nil { - *out = nil - } else { - *out = new(v1.Status) - (*in).DeepCopyInto(*out) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionReviewStatus. -func (in *AdmissionReviewStatus) DeepCopy() *AdmissionReviewStatus { - if in == nil { - return nil - } - out := new(AdmissionReviewStatus) - in.DeepCopyInto(out) - return out -} diff --git a/staging/src/k8s.io/api/admission/v1beta1/BUILD b/staging/src/k8s.io/api/admission/v1beta1/BUILD new file mode 100644 index 00000000000..fd69b917036 --- /dev/null +++ b/staging/src/k8s.io/api/admission/v1beta1/BUILD @@ -0,0 +1,43 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "generated.pb.go", + "register.go", + "types.go", + "types_swagger_doc_generated.go", + "zz_generated.deepcopy.go", + ], + importpath = "k8s.io/api/admission/v1beta1", + visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/gogo/protobuf/proto:go_default_library", + "//vendor/k8s.io/api/authentication/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + ], +) + +filegroup( + name = "go_default_library_protos", + srcs = ["generated.proto"], + visibility = ["//visibility:public"], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/api/admission/v1beta1/doc.go b/staging/src/k8s.io/api/admission/v1beta1/doc.go new file mode 100644 index 00000000000..a26d4d45a45 --- /dev/null +++ b/staging/src/k8s.io/api/admission/v1beta1/doc.go @@ -0,0 +1,21 @@ +/* +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:openapi-gen=false + +// +groupName=admission.k8s.io +package v1beta1 // import "k8s.io/api/admission/v1beta1" diff --git a/staging/src/k8s.io/api/admission/v1beta1/generated.pb.go b/staging/src/k8s.io/api/admission/v1beta1/generated.pb.go new file mode 100644 index 00000000000..f56a0f063c0 --- /dev/null +++ b/staging/src/k8s.io/api/admission/v1beta1/generated.pb.go @@ -0,0 +1,1208 @@ +/* +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. +*/ + +// Code generated by protoc-gen-gogo. +// source: k8s.io/kubernetes/vendor/k8s.io/api/admission/v1beta1/generated.proto +// DO NOT EDIT! + +/* + Package v1beta1 is a generated protocol buffer package. + + It is generated from these files: + k8s.io/kubernetes/vendor/k8s.io/api/admission/v1beta1/generated.proto + + It has these top-level messages: + AdmissionRequest + AdmissionResponse + AdmissionReview +*/ +package v1beta1 + +import proto "github.com/gogo/protobuf/proto" +import fmt "fmt" +import math "math" + +import k8s_io_apimachinery_pkg_apis_meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +import k8s_io_apimachinery_pkg_types "k8s.io/apimachinery/pkg/types" + +import strings "strings" +import reflect "reflect" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package + +func (m *AdmissionRequest) Reset() { *m = AdmissionRequest{} } +func (*AdmissionRequest) ProtoMessage() {} +func (*AdmissionRequest) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{0} } + +func (m *AdmissionResponse) Reset() { *m = AdmissionResponse{} } +func (*AdmissionResponse) ProtoMessage() {} +func (*AdmissionResponse) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{1} } + +func (m *AdmissionReview) Reset() { *m = AdmissionReview{} } +func (*AdmissionReview) ProtoMessage() {} +func (*AdmissionReview) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{2} } + +func init() { + proto.RegisterType((*AdmissionRequest)(nil), "k8s.io.api.admission.v1beta1.AdmissionRequest") + proto.RegisterType((*AdmissionResponse)(nil), "k8s.io.api.admission.v1beta1.AdmissionResponse") + proto.RegisterType((*AdmissionReview)(nil), "k8s.io.api.admission.v1beta1.AdmissionReview") +} +func (m *AdmissionRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AdmissionRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.UID))) + i += copy(dAtA[i:], m.UID) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Kind.Size())) + n1, err := m.Kind.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Resource.Size())) + n2, err := m.Resource.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n2 + dAtA[i] = 0x22 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.SubResource))) + i += copy(dAtA[i:], m.SubResource) + dAtA[i] = 0x2a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i += copy(dAtA[i:], m.Name) + dAtA[i] = 0x32 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Namespace))) + i += copy(dAtA[i:], m.Namespace) + dAtA[i] = 0x3a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Operation))) + i += copy(dAtA[i:], m.Operation) + dAtA[i] = 0x42 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.UserInfo.Size())) + n3, err := m.UserInfo.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n3 + dAtA[i] = 0x4a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Object.Size())) + n4, err := m.Object.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n4 + dAtA[i] = 0x52 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.OldObject.Size())) + n5, err := m.OldObject.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n5 + return i, nil +} + +func (m *AdmissionResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AdmissionResponse) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.UID))) + i += copy(dAtA[i:], m.UID) + dAtA[i] = 0x10 + i++ + if m.Allowed { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + if m.Result != nil { + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Result.Size())) + n6, err := m.Result.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n6 + } + if m.Patch != nil { + dAtA[i] = 0x22 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Patch))) + i += copy(dAtA[i:], m.Patch) + } + if m.PatchType != nil { + dAtA[i] = 0x2a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.PatchType))) + i += copy(dAtA[i:], *m.PatchType) + } + return i, nil +} + +func (m *AdmissionReview) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AdmissionReview) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Request != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Request.Size())) + n7, err := m.Request.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n7 + } + if m.Response != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Response.Size())) + n8, err := m.Response.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n8 + } + return i, nil +} + +func encodeFixed64Generated(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Generated(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *AdmissionRequest) Size() (n int) { + var l int + _ = l + l = len(m.UID) + n += 1 + l + sovGenerated(uint64(l)) + l = m.Kind.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Resource.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.SubResource) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Namespace) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Operation) + n += 1 + l + sovGenerated(uint64(l)) + l = m.UserInfo.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Object.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.OldObject.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *AdmissionResponse) Size() (n int) { + var l int + _ = l + l = len(m.UID) + n += 1 + l + sovGenerated(uint64(l)) + n += 2 + if m.Result != nil { + l = m.Result.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.Patch != nil { + l = len(m.Patch) + n += 1 + l + sovGenerated(uint64(l)) + } + if m.PatchType != nil { + l = len(*m.PatchType) + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (m *AdmissionReview) Size() (n int) { + var l int + _ = l + if m.Request != nil { + l = m.Request.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.Response != nil { + l = m.Response.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func sovGenerated(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozGenerated(x uint64) (n int) { + return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (this *AdmissionRequest) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&AdmissionRequest{`, + `UID:` + fmt.Sprintf("%v", this.UID) + `,`, + `Kind:` + strings.Replace(strings.Replace(this.Kind.String(), "GroupVersionKind", "k8s_io_apimachinery_pkg_apis_meta_v1.GroupVersionKind", 1), `&`, ``, 1) + `,`, + `Resource:` + strings.Replace(strings.Replace(this.Resource.String(), "GroupVersionResource", "k8s_io_apimachinery_pkg_apis_meta_v1.GroupVersionResource", 1), `&`, ``, 1) + `,`, + `SubResource:` + fmt.Sprintf("%v", this.SubResource) + `,`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `Namespace:` + fmt.Sprintf("%v", this.Namespace) + `,`, + `Operation:` + fmt.Sprintf("%v", this.Operation) + `,`, + `UserInfo:` + strings.Replace(strings.Replace(this.UserInfo.String(), "UserInfo", "k8s_io_api_authentication_v1.UserInfo", 1), `&`, ``, 1) + `,`, + `Object:` + strings.Replace(strings.Replace(this.Object.String(), "RawExtension", "k8s_io_apimachinery_pkg_runtime.RawExtension", 1), `&`, ``, 1) + `,`, + `OldObject:` + strings.Replace(strings.Replace(this.OldObject.String(), "RawExtension", "k8s_io_apimachinery_pkg_runtime.RawExtension", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *AdmissionResponse) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&AdmissionResponse{`, + `UID:` + fmt.Sprintf("%v", this.UID) + `,`, + `Allowed:` + fmt.Sprintf("%v", this.Allowed) + `,`, + `Result:` + strings.Replace(fmt.Sprintf("%v", this.Result), "Status", "k8s_io_apimachinery_pkg_apis_meta_v1.Status", 1) + `,`, + `Patch:` + valueToStringGenerated(this.Patch) + `,`, + `PatchType:` + valueToStringGenerated(this.PatchType) + `,`, + `}`, + }, "") + return s +} +func (this *AdmissionReview) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&AdmissionReview{`, + `Request:` + strings.Replace(fmt.Sprintf("%v", this.Request), "AdmissionRequest", "AdmissionRequest", 1) + `,`, + `Response:` + strings.Replace(fmt.Sprintf("%v", this.Response), "AdmissionResponse", "AdmissionResponse", 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 *AdmissionRequest) 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: AdmissionRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AdmissionRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UID", 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 > l { + return io.ErrUnexpectedEOF + } + m.UID = k8s_io_apimachinery_pkg_types.UID(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Kind", 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.Kind.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.Resource.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SubResource", 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 > l { + return io.ErrUnexpectedEOF + } + m.SubResource = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + 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 > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Namespace", 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 > l { + return io.ErrUnexpectedEOF + } + m.Namespace = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Operation", 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 > l { + return io.ErrUnexpectedEOF + } + m.Operation = Operation(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UserInfo", 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.UserInfo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 9: + 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.Object.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field OldObject", 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.OldObject.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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AdmissionResponse) 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: AdmissionResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AdmissionResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UID", 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 > l { + return io.ErrUnexpectedEOF + } + m.UID = k8s_io_apimachinery_pkg_types.UID(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Allowed", 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.Allowed = bool(v != 0) + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Result", 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 > l { + return io.ErrUnexpectedEOF + } + if m.Result == nil { + m.Result = &k8s_io_apimachinery_pkg_apis_meta_v1.Status{} + } + if err := m.Result.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Patch", 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 > l { + return io.ErrUnexpectedEOF + } + m.Patch = append(m.Patch[:0], dAtA[iNdEx:postIndex]...) + if m.Patch == nil { + m.Patch = []byte{} + } + iNdEx = postIndex + case 5: + 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 > l { + return io.ErrUnexpectedEOF + } + s := PatchType(dAtA[iNdEx:postIndex]) + m.PatchType = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AdmissionReview) 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: AdmissionReview: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AdmissionReview: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Request", 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 > l { + return io.ErrUnexpectedEOF + } + if m.Request == nil { + m.Request = &AdmissionRequest{} + } + if err := m.Request.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Response", 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 > l { + return io.ErrUnexpectedEOF + } + if m.Response == nil { + m.Response = &AdmissionResponse{} + } + if err := m.Response.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 { + 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 + 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 + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + 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 + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthGenerated + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipGenerated(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") +) + +func init() { + proto.RegisterFile("k8s.io/kubernetes/vendor/k8s.io/api/admission/v1beta1/generated.proto", fileDescriptorGenerated) +} + +var fileDescriptorGenerated = []byte{ + // 739 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0xcd, 0x4e, 0xdb, 0x4a, + 0x14, 0x8e, 0x21, 0x7f, 0x9e, 0xa0, 0x0b, 0xcc, 0xdd, 0x58, 0xd1, 0x95, 0xc3, 0x65, 0x71, 0xc5, + 0x95, 0x60, 0x5c, 0x68, 0x8b, 0x50, 0xd5, 0x0d, 0x16, 0xa8, 0x42, 0x95, 0x00, 0x0d, 0xa4, 0x6a, + 0xbb, 0xa8, 0x34, 0x71, 0x86, 0x64, 0x9a, 0xd8, 0xe3, 0x7a, 0xc6, 0xa1, 0xec, 0xfa, 0x08, 0x7d, + 0x93, 0x3e, 0x44, 0x37, 0x2c, 0x59, 0xb2, 0x8a, 0x4a, 0xfa, 0x00, 0xdd, 0xb3, 0xaa, 0x3c, 0x1e, + 0xc7, 0x29, 0x34, 0x2d, 0xad, 0xba, 0xca, 0x9c, 0x73, 0xbe, 0xef, 0x3b, 0xf1, 0x77, 0xce, 0x0c, + 0xd8, 0xed, 0x6d, 0x09, 0xc4, 0xb8, 0xd3, 0x8b, 0x5b, 0x34, 0x0a, 0xa8, 0xa4, 0xc2, 0x19, 0xd0, + 0xa0, 0xcd, 0x23, 0x47, 0x17, 0x48, 0xc8, 0x1c, 0xd2, 0xf6, 0x99, 0x10, 0x8c, 0x07, 0xce, 0x60, + 0xbd, 0x45, 0x25, 0x59, 0x77, 0x3a, 0x34, 0xa0, 0x11, 0x91, 0xb4, 0x8d, 0xc2, 0x88, 0x4b, 0x0e, + 0xff, 0x49, 0xd1, 0x88, 0x84, 0x0c, 0x8d, 0xd1, 0x48, 0xa3, 0xeb, 0x6b, 0x1d, 0x26, 0xbb, 0x71, + 0x0b, 0x79, 0xdc, 0x77, 0x3a, 0xbc, 0xc3, 0x1d, 0x45, 0x6a, 0xc5, 0x27, 0x2a, 0x52, 0x81, 0x3a, + 0xa5, 0x62, 0xf5, 0xd5, 0xc9, 0xd6, 0xb1, 0xec, 0xd2, 0x40, 0x32, 0x8f, 0xc8, 0xb4, 0xff, 0xcd, + 0xd6, 0xf5, 0x07, 0x39, 0xda, 0x27, 0x5e, 0x97, 0x05, 0x34, 0x3a, 0x73, 0xc2, 0x5e, 0x27, 0x49, + 0x08, 0xc7, 0xa7, 0x92, 0x7c, 0x8f, 0xe5, 0x4c, 0x63, 0x45, 0x71, 0x20, 0x99, 0x4f, 0x6f, 0x11, + 0x36, 0x7f, 0x46, 0x10, 0x5e, 0x97, 0xfa, 0xe4, 0x16, 0xef, 0xfe, 0x34, 0x5e, 0x2c, 0x59, 0xdf, + 0x61, 0x81, 0x14, 0x32, 0xba, 0x49, 0x5a, 0xfe, 0x52, 0x02, 0x0b, 0xdb, 0x99, 0x8d, 0x98, 0xbe, + 0x89, 0xa9, 0x90, 0xd0, 0x05, 0xb3, 0x31, 0x6b, 0x5b, 0xc6, 0x92, 0xb1, 0x62, 0xba, 0xf7, 0xce, + 0x87, 0x8d, 0xc2, 0x68, 0xd8, 0x98, 0x6d, 0xee, 0xed, 0x5c, 0x0f, 0x1b, 0xff, 0x4e, 0xeb, 0x22, + 0xcf, 0x42, 0x2a, 0x50, 0x73, 0x6f, 0x07, 0x27, 0x64, 0xf8, 0x1c, 0x14, 0x7b, 0x2c, 0x68, 0x5b, + 0x33, 0x4b, 0xc6, 0x4a, 0x6d, 0x63, 0x13, 0xe5, 0x63, 0x1b, 0xd3, 0x50, 0xd8, 0xeb, 0x24, 0x09, + 0x81, 0x12, 0xef, 0xd0, 0x60, 0x1d, 0x3d, 0x89, 0x78, 0x1c, 0x3e, 0xa3, 0x51, 0xf2, 0x67, 0x9e, + 0xb2, 0xa0, 0xed, 0xce, 0xe9, 0xe6, 0xc5, 0x24, 0xc2, 0x4a, 0x11, 0x76, 0x41, 0x35, 0xa2, 0x82, + 0xc7, 0x91, 0x47, 0xad, 0x59, 0xa5, 0xfe, 0xe8, 0xd7, 0xd5, 0xb1, 0x56, 0x70, 0x17, 0x74, 0x87, + 0x6a, 0x96, 0xc1, 0x63, 0x75, 0xf8, 0x10, 0xd4, 0x44, 0xdc, 0xca, 0x0a, 0x56, 0x51, 0xf9, 0xf1, + 0xb7, 0x26, 0xd4, 0x8e, 0xf2, 0x12, 0x9e, 0xc4, 0xc1, 0x25, 0x50, 0x0c, 0x88, 0x4f, 0xad, 0x92, + 0xc2, 0x8f, 0x3f, 0x61, 0x9f, 0xf8, 0x14, 0xab, 0x0a, 0x74, 0x80, 0x99, 0xfc, 0x8a, 0x90, 0x78, + 0xd4, 0x2a, 0x2b, 0xd8, 0xa2, 0x86, 0x99, 0xfb, 0x59, 0x01, 0xe7, 0x18, 0xf8, 0x18, 0x98, 0x3c, + 0x4c, 0x06, 0xc7, 0x78, 0x60, 0x55, 0x14, 0xc1, 0xce, 0x08, 0x07, 0x59, 0xe1, 0x7a, 0x32, 0xc0, + 0x39, 0x01, 0x1e, 0x83, 0x6a, 0x2c, 0x68, 0xb4, 0x17, 0x9c, 0x70, 0xab, 0xaa, 0x1c, 0xfb, 0x0f, + 0x4d, 0x5e, 0xa3, 0x6f, 0x36, 0x3f, 0x71, 0xaa, 0xa9, 0xd1, 0xb9, 0x3b, 0x59, 0x06, 0x8f, 0x95, + 0x60, 0x13, 0x94, 0x79, 0xeb, 0x35, 0xf5, 0xa4, 0x65, 0x2a, 0xcd, 0xb5, 0xa9, 0x53, 0xd0, 0x8b, + 0x8b, 0x30, 0x39, 0xdd, 0x7d, 0x2b, 0x69, 0x90, 0x0c, 0xc0, 0xfd, 0x4b, 0x4b, 0x97, 0x0f, 0x94, + 0x08, 0xd6, 0x62, 0xf0, 0x15, 0x30, 0x79, 0xbf, 0x9d, 0x26, 0x2d, 0xf0, 0x3b, 0xca, 0x63, 0x2b, + 0x0f, 0x32, 0x1d, 0x9c, 0x4b, 0x2e, 0x7f, 0x98, 0x01, 0x8b, 0x13, 0x1b, 0x2f, 0x42, 0x1e, 0x08, + 0xfa, 0x47, 0x56, 0xfe, 0x7f, 0x50, 0x21, 0xfd, 0x3e, 0x3f, 0xa5, 0xe9, 0xd6, 0x57, 0xdd, 0x79, + 0xad, 0x53, 0xd9, 0x4e, 0xd3, 0x38, 0xab, 0xc3, 0x43, 0x50, 0x16, 0x92, 0xc8, 0x58, 0xe8, 0x0d, + 0x5e, 0xbd, 0xdb, 0x06, 0x1f, 0x29, 0x8e, 0x0b, 0x12, 0xdb, 0x30, 0x15, 0x71, 0x5f, 0x62, 0xad, + 0x03, 0x1b, 0xa0, 0x14, 0x12, 0xe9, 0x75, 0xd5, 0x96, 0xce, 0xb9, 0xe6, 0x68, 0xd8, 0x28, 0x1d, + 0x26, 0x09, 0x9c, 0xe6, 0xe1, 0x16, 0x30, 0xd5, 0xe1, 0xf8, 0x2c, 0xcc, 0x56, 0xb3, 0x9e, 0x98, + 0x74, 0x98, 0x25, 0xaf, 0x27, 0x03, 0x9c, 0x83, 0x97, 0x3f, 0x1a, 0x60, 0x7e, 0xc2, 0xb1, 0x01, + 0xa3, 0xa7, 0xb0, 0x09, 0x2a, 0x51, 0xfa, 0x5a, 0x28, 0xcf, 0x6a, 0x1b, 0x08, 0xfd, 0xe8, 0x61, + 0x46, 0x37, 0xdf, 0x18, 0xb7, 0x96, 0xf8, 0xa2, 0x03, 0x9c, 0x69, 0xc1, 0x17, 0xea, 0x6e, 0xab, + 0x91, 0xe8, 0x97, 0xc3, 0xb9, 0xb3, 0x6e, 0x4a, 0x73, 0xe7, 0xf4, 0x65, 0x56, 0x11, 0x1e, 0xcb, + 0xb9, 0x6b, 0xe7, 0x57, 0x76, 0xe1, 0xe2, 0xca, 0x2e, 0x5c, 0x5e, 0xd9, 0x85, 0x77, 0x23, 0xdb, + 0x38, 0x1f, 0xd9, 0xc6, 0xc5, 0xc8, 0x36, 0x2e, 0x47, 0xb6, 0xf1, 0x69, 0x64, 0x1b, 0xef, 0x3f, + 0xdb, 0x85, 0x97, 0x15, 0x2d, 0xfc, 0x35, 0x00, 0x00, 0xff, 0xff, 0x76, 0x21, 0xd5, 0x35, 0xaf, + 0x06, 0x00, 0x00, +} diff --git a/staging/src/k8s.io/api/admission/v1beta1/generated.proto b/staging/src/k8s.io/api/admission/v1beta1/generated.proto new file mode 100644 index 00000000000..a4e4ca266e7 --- /dev/null +++ b/staging/src/k8s.io/api/admission/v1beta1/generated.proto @@ -0,0 +1,112 @@ +/* +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. +*/ + + +// This file was autogenerated by go-to-protobuf. Do not edit it manually! + +syntax = 'proto2'; + +package k8s.io.api.admission.v1beta1; + +import "k8s.io/api/authentication/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"; +import "k8s.io/apimachinery/pkg/util/intstr/generated.proto"; + +// Package-wide variables from generator "generated". +option go_package = "v1beta1"; + +// AdmissionRequest describes the admission.Attributes for the admission request. +message AdmissionRequest { + // UID is an identifier for the individual request/response. It allows us to distinguish instances of requests which are + // 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 string uid = 1; + + // Kind is the type of object being manipulated. For example: Pod + optional k8s.io.apimachinery.pkg.apis.meta.v1.GroupVersionKind kind = 2; + + // Resource is the name of the resource being requested. This is not the kind. For example: pods + optional k8s.io.apimachinery.pkg.apis.meta.v1.GroupVersionResource resource = 3; + + // SubResource is the name of the subresource being requested. This is a different resource, scoped to the parent + // resource, but it may have a different kind. For instance, /pods has the resource "pods" and the kind "Pod", while + // /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod" (because status operates on + // pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource + // "binding", and kind "Binding". + // +optional + optional string subResource = 4; + + // Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and + // rely on the server to generate the name. If that is the case, this method will return the empty string. + // +optional + optional string name = 5; + + // Namespace is the namespace associated with the request (if any). + // +optional + optional string namespace = 6; + + // Operation is the operation being performed + optional string operation = 7; + + // UserInfo is information about the requesting user + optional k8s.io.api.authentication.v1.UserInfo userInfo = 8; + + // Object is the object from the incoming request prior to default values being applied + // +optional + optional k8s.io.apimachinery.pkg.runtime.RawExtension object = 9; + + // OldObject is the existing object. Only populated for UPDATE requests. + // +optional + optional k8s.io.apimachinery.pkg.runtime.RawExtension oldObject = 10; +} + +// AdmissionResponse describes an admission response. +message AdmissionResponse { + // UID is an identifier for the individual request/response. + // This should be copied over from the corresponding AdmissionRequest. + optional string uid = 1; + + // Allowed indicates whether or not the admission request was permitted. + optional bool allowed = 2; + + // Result contains extra details into why an admission request was denied. + // This field IS NOT consulted in any way if "Allowed" is "true". + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.Status status = 3; + + // The patch body. Currently we only support "JSONPatch" which implements RFC 6902. + // +optional + optional bytes patch = 4; + + // The type of Patch. Currently we only allow "JSONPatch". + // +optional + optional string patchType = 5; +} + +// AdmissionReview describes an admission review request/response. +message AdmissionReview { + // Request describes the attributes for the admission request. + // +optional + optional AdmissionRequest request = 1; + + // Response describes the attributes for the admission response. + // +optional + optional AdmissionResponse response = 2; +} + diff --git a/staging/src/k8s.io/api/admission/v1beta1/register.go b/staging/src/k8s.io/api/admission/v1beta1/register.go new file mode 100644 index 00000000000..78d21a0c8a7 --- /dev/null +++ b/staging/src/k8s.io/api/admission/v1beta1/register.go @@ -0,0 +1,51 @@ +/* +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 v1beta1 + +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 for this API. +const GroupName = "admission.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"} + +// 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, + &AdmissionReview{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/staging/src/k8s.io/api/admission/v1beta1/types.go b/staging/src/k8s.io/api/admission/v1beta1/types.go new file mode 100644 index 00000000000..9ad939c396c --- /dev/null +++ b/staging/src/k8s.io/api/admission/v1beta1/types.go @@ -0,0 +1,116 @@ +/* +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 v1beta1 + +import ( + authenticationv1 "k8s.io/api/authentication/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// AdmissionReview describes an admission review request/response. +type AdmissionReview struct { + metav1.TypeMeta `json:",inline"` + // Request describes the attributes for the admission request. + // +optional + Request *AdmissionRequest `json:"request,omitempty" protobuf:"bytes,1,opt,name=request"` + // Response describes the attributes for the admission response. + // +optional + Response *AdmissionResponse `json:"response,omitempty" protobuf:"bytes,2,opt,name=response"` +} + +// AdmissionRequest describes the admission.Attributes for the admission request. +type AdmissionRequest struct { + // UID is an identifier for the individual request/response. It allows us to distinguish instances of requests which are + // 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. + UID types.UID `json:"uid" protobuf:"bytes,1,opt,name=uid"` + // Kind is the type of object being manipulated. For example: Pod + Kind metav1.GroupVersionKind `json:"kind" protobuf:"bytes,2,opt,name=kind"` + // Resource is the name of the resource being requested. This is not the kind. For example: pods + Resource metav1.GroupVersionResource `json:"resource" protobuf:"bytes,3,opt,name=resource"` + // SubResource is the name of the subresource being requested. This is a different resource, scoped to the parent + // resource, but it may have a different kind. For instance, /pods has the resource "pods" and the kind "Pod", while + // /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod" (because status operates on + // pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource + // "binding", and kind "Binding". + // +optional + SubResource string `json:"subResource,omitempty" protobuf:"bytes,4,opt,name=subResource"` + // Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and + // rely on the server to generate the name. If that is the case, this method will return the empty string. + // +optional + Name string `json:"name,omitempty" protobuf:"bytes,5,opt,name=name"` + // Namespace is the namespace associated with the request (if any). + // +optional + Namespace string `json:"namespace,omitempty" protobuf:"bytes,6,opt,name=namespace"` + // Operation is the operation being performed + Operation Operation `json:"operation" protobuf:"bytes,7,opt,name=operation"` + // UserInfo is information about the requesting user + UserInfo authenticationv1.UserInfo `json:"userInfo" protobuf:"bytes,8,opt,name=userInfo"` + // Object is the object from the incoming request prior to default values being applied + // +optional + Object runtime.RawExtension `json:"object,omitempty" protobuf:"bytes,9,opt,name=object"` + // OldObject is the existing object. Only populated for UPDATE requests. + // +optional + OldObject runtime.RawExtension `json:"oldObject,omitempty" protobuf:"bytes,10,opt,name=oldObject"` +} + +// AdmissionResponse describes an admission response. +type AdmissionResponse struct { + // UID is an identifier for the individual request/response. + // This should be copied over from the corresponding AdmissionRequest. + UID types.UID `json:"uid" protobuf:"bytes,1,opt,name=uid"` + + // Allowed indicates whether or not the admission request was permitted. + Allowed bool `json:"allowed" protobuf:"varint,2,opt,name=allowed"` + + // Result contains extra details into why an admission request was denied. + // This field IS NOT consulted in any way if "Allowed" is "true". + // +optional + Result *metav1.Status `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` + + // The patch body. Currently we only support "JSONPatch" which implements RFC 6902. + // +optional + Patch []byte `json:"patch,omitempty" protobuf:"bytes,4,opt,name=patch"` + + // The type of Patch. Currently we only allow "JSONPatch". + // +optional + PatchType *PatchType `json:"patchType,omitempty" protobuf:"bytes,5,opt,name=patchType"` +} + +// PatchType is the type of patch being used to represent the mutated object +type PatchType string + +// PatchType constants. +const ( + PatchTypeJSONPatch PatchType = "JSONPatch" +) + +// Operation is the type of resource operation being checked for admission control +type Operation string + +// Operation constants +const ( + Create Operation = "CREATE" + Update Operation = "UPDATE" + Delete Operation = "DELETE" + Connect Operation = "CONNECT" +) diff --git a/staging/src/k8s.io/api/admission/v1beta1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/admission/v1beta1/types_swagger_doc_generated.go new file mode 100644 index 00000000000..1f53135179a --- /dev/null +++ b/staging/src/k8s.io/api/admission/v1beta1/types_swagger_doc_generated.go @@ -0,0 +1,71 @@ +/* +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 v1beta1 + +// 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-generated-swagger-docs.sh + +// AUTO-GENERATED FUNCTIONS START HERE +var map_AdmissionRequest = map[string]string{ + "": "AdmissionRequest describes the admission.Attributes for the admission request.", + "uid": "UID is an identifier for the individual request/response. It allows us to distinguish instances of requests which are 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.", + "kind": "Kind is the type of object being manipulated. For example: Pod", + "resource": "Resource is the name of the resource being requested. This is not the kind. For example: pods", + "subResource": "SubResource is the name of the subresource being requested. This is a different resource, scoped to the parent resource, but it may have a different kind. For instance, /pods has the resource \"pods\" and the kind \"Pod\", while /pods/foo/status has the resource \"pods\", the sub resource \"status\", and the kind \"Pod\" (because status operates on pods). The binding resource for a pod though may be /pods/foo/binding, which has resource \"pods\", subresource \"binding\", and kind \"Binding\".", + "name": "Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and rely on the server to generate the name. If that is the case, this method will return the empty string.", + "namespace": "Namespace is the namespace associated with the request (if any).", + "operation": "Operation is the operation being performed", + "userInfo": "UserInfo is information about the requesting user", + "object": "Object is the object from the incoming request prior to default values being applied", + "oldObject": "OldObject is the existing object. Only populated for UPDATE requests.", +} + +func (AdmissionRequest) SwaggerDoc() map[string]string { + return map_AdmissionRequest +} + +var map_AdmissionResponse = map[string]string{ + "": "AdmissionResponse describes an admission response.", + "uid": "UID is an identifier for the individual request/response. This should be copied over from the corresponding AdmissionRequest.", + "allowed": "Allowed indicates whether or not the admission request was permitted.", + "status": "Result contains extra details into why an admission request was denied. This field IS NOT consulted in any way if \"Allowed\" is \"true\".", + "patch": "The patch body. Currently we only support \"JSONPatch\" which implements RFC 6902.", + "patchType": "The type of Patch. Currently we only allow \"JSONPatch\".", +} + +func (AdmissionResponse) SwaggerDoc() map[string]string { + return map_AdmissionResponse +} + +var map_AdmissionReview = map[string]string{ + "": "AdmissionReview describes an admission review request/response.", + "request": "Request describes the attributes for the admission request.", + "response": "Response describes the attributes for the admission response.", +} + +func (AdmissionReview) SwaggerDoc() map[string]string { + return map_AdmissionReview +} + +// AUTO-GENERATED FUNCTIONS END HERE diff --git a/staging/src/k8s.io/api/admission/v1beta1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/admission/v1beta1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..275a326d2fa --- /dev/null +++ b/staging/src/k8s.io/api/admission/v1beta1/zz_generated.deepcopy.go @@ -0,0 +1,130 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +package v1beta1 + +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 *AdmissionRequest) DeepCopyInto(out *AdmissionRequest) { + *out = *in + out.Kind = in.Kind + out.Resource = in.Resource + in.UserInfo.DeepCopyInto(&out.UserInfo) + in.Object.DeepCopyInto(&out.Object) + in.OldObject.DeepCopyInto(&out.OldObject) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionRequest. +func (in *AdmissionRequest) DeepCopy() *AdmissionRequest { + if in == nil { + return nil + } + out := new(AdmissionRequest) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdmissionResponse) DeepCopyInto(out *AdmissionResponse) { + *out = *in + if in.Result != nil { + in, out := &in.Result, &out.Result + if *in == nil { + *out = nil + } else { + *out = new(v1.Status) + (*in).DeepCopyInto(*out) + } + } + if in.Patch != nil { + in, out := &in.Patch, &out.Patch + *out = make([]byte, len(*in)) + copy(*out, *in) + } + if in.PatchType != nil { + in, out := &in.PatchType, &out.PatchType + if *in == nil { + *out = nil + } else { + *out = new(PatchType) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionResponse. +func (in *AdmissionResponse) DeepCopy() *AdmissionResponse { + if in == nil { + return nil + } + out := new(AdmissionResponse) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdmissionReview) DeepCopyInto(out *AdmissionReview) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Request != nil { + in, out := &in.Request, &out.Request + if *in == nil { + *out = nil + } else { + *out = new(AdmissionRequest) + (*in).DeepCopyInto(*out) + } + } + if in.Response != nil { + in, out := &in.Response, &out.Response + if *in == nil { + *out = nil + } else { + *out = new(AdmissionResponse) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionReview. +func (in *AdmissionReview) DeepCopy() *AdmissionReview { + if in == nil { + return nil + } + out := new(AdmissionReview) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AdmissionReview) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} diff --git a/staging/src/k8s.io/api/admissionregistration/v1alpha1/BUILD b/staging/src/k8s.io/api/admissionregistration/v1alpha1/BUILD index b1601cf3d0b..417eab39ff0 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1alpha1/BUILD +++ b/staging/src/k8s.io/api/admissionregistration/v1alpha1/BUILD @@ -19,7 +19,6 @@ go_library( deps = [ "//vendor/github.com/gogo/protobuf/proto:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/staging/src/k8s.io/api/admissionregistration/v1alpha1/doc.go b/staging/src/k8s.io/api/admissionregistration/v1alpha1/doc.go index 49e0e0989df..8a5d1fbbb6e 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1alpha1/doc.go +++ b/staging/src/k8s.io/api/admissionregistration/v1alpha1/doc.go @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:openapi-gen=true // Package v1alpha1 is the v1alpha1 version of the API. // AdmissionConfiguration and AdmissionPluginConfiguration are legacy static admission plugin configuration -// InitializerConfiguration and ExternalAdmissionHookConfiguration is for the +// InitializerConfiguration and validatingWebhookConfiguration is for the // new dynamic admission controller configuration. // +groupName=admissionregistration.k8s.io package v1alpha1 // import "k8s.io/api/admissionregistration/v1alpha1" diff --git a/staging/src/k8s.io/api/admissionregistration/v1alpha1/generated.pb.go b/staging/src/k8s.io/api/admissionregistration/v1alpha1/generated.pb.go index 6a1977f8331..f5d4941d367 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1alpha1/generated.pb.go +++ b/staging/src/k8s.io/api/admissionregistration/v1alpha1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -25,16 +25,10 @@ limitations under the License. k8s.io/kubernetes/vendor/k8s.io/api/admissionregistration/v1alpha1/generated.proto It has these top-level messages: - AdmissionHookClientConfig - ExternalAdmissionHook - ExternalAdmissionHookConfiguration - ExternalAdmissionHookConfigurationList Initializer InitializerConfiguration InitializerConfigurationList Rule - RuleWithOperations - ServiceReference */ package v1alpha1 @@ -58,226 +52,32 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package -func (m *AdmissionHookClientConfig) Reset() { *m = AdmissionHookClientConfig{} } -func (*AdmissionHookClientConfig) ProtoMessage() {} -func (*AdmissionHookClientConfig) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{0} -} - -func (m *ExternalAdmissionHook) Reset() { *m = ExternalAdmissionHook{} } -func (*ExternalAdmissionHook) ProtoMessage() {} -func (*ExternalAdmissionHook) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{1} } - -func (m *ExternalAdmissionHookConfiguration) Reset() { *m = ExternalAdmissionHookConfiguration{} } -func (*ExternalAdmissionHookConfiguration) ProtoMessage() {} -func (*ExternalAdmissionHookConfiguration) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{2} -} - -func (m *ExternalAdmissionHookConfigurationList) Reset() { - *m = ExternalAdmissionHookConfigurationList{} -} -func (*ExternalAdmissionHookConfigurationList) ProtoMessage() {} -func (*ExternalAdmissionHookConfigurationList) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{3} -} - func (m *Initializer) Reset() { *m = Initializer{} } func (*Initializer) ProtoMessage() {} -func (*Initializer) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{4} } +func (*Initializer) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{0} } func (m *InitializerConfiguration) Reset() { *m = InitializerConfiguration{} } func (*InitializerConfiguration) ProtoMessage() {} func (*InitializerConfiguration) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{5} + return fileDescriptorGenerated, []int{1} } func (m *InitializerConfigurationList) Reset() { *m = InitializerConfigurationList{} } func (*InitializerConfigurationList) ProtoMessage() {} func (*InitializerConfigurationList) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{6} + return fileDescriptorGenerated, []int{2} } func (m *Rule) Reset() { *m = Rule{} } func (*Rule) ProtoMessage() {} -func (*Rule) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{7} } - -func (m *RuleWithOperations) Reset() { *m = RuleWithOperations{} } -func (*RuleWithOperations) ProtoMessage() {} -func (*RuleWithOperations) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{8} } - -func (m *ServiceReference) Reset() { *m = ServiceReference{} } -func (*ServiceReference) ProtoMessage() {} -func (*ServiceReference) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{9} } +func (*Rule) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{3} } func init() { - proto.RegisterType((*AdmissionHookClientConfig)(nil), "k8s.io.api.admissionregistration.v1alpha1.AdmissionHookClientConfig") - proto.RegisterType((*ExternalAdmissionHook)(nil), "k8s.io.api.admissionregistration.v1alpha1.ExternalAdmissionHook") - proto.RegisterType((*ExternalAdmissionHookConfiguration)(nil), "k8s.io.api.admissionregistration.v1alpha1.ExternalAdmissionHookConfiguration") - proto.RegisterType((*ExternalAdmissionHookConfigurationList)(nil), "k8s.io.api.admissionregistration.v1alpha1.ExternalAdmissionHookConfigurationList") proto.RegisterType((*Initializer)(nil), "k8s.io.api.admissionregistration.v1alpha1.Initializer") proto.RegisterType((*InitializerConfiguration)(nil), "k8s.io.api.admissionregistration.v1alpha1.InitializerConfiguration") proto.RegisterType((*InitializerConfigurationList)(nil), "k8s.io.api.admissionregistration.v1alpha1.InitializerConfigurationList") proto.RegisterType((*Rule)(nil), "k8s.io.api.admissionregistration.v1alpha1.Rule") - proto.RegisterType((*RuleWithOperations)(nil), "k8s.io.api.admissionregistration.v1alpha1.RuleWithOperations") - proto.RegisterType((*ServiceReference)(nil), "k8s.io.api.admissionregistration.v1alpha1.ServiceReference") } -func (m *AdmissionHookClientConfig) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *AdmissionHookClientConfig) MarshalTo(dAtA []byte) (int, error) { - var i int - _ = i - var l int - _ = l - dAtA[i] = 0xa - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Service.Size())) - n1, err := m.Service.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n1 - if m.CABundle != nil { - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(len(m.CABundle))) - i += copy(dAtA[i:], m.CABundle) - } - return i, nil -} - -func (m *ExternalAdmissionHook) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ExternalAdmissionHook) MarshalTo(dAtA []byte) (int, error) { - var i int - _ = i - var l int - _ = l - dAtA[i] = 0xa - i++ - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) - i += copy(dAtA[i:], m.Name) - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.ClientConfig.Size())) - n2, err := m.ClientConfig.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n2 - if len(m.Rules) > 0 { - for _, msg := range m.Rules { - dAtA[i] = 0x1a - i++ - i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) - n, err := msg.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n - } - } - if m.FailurePolicy != nil { - dAtA[i] = 0x22 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(len(*m.FailurePolicy))) - i += copy(dAtA[i:], *m.FailurePolicy) - } - return i, nil -} - -func (m *ExternalAdmissionHookConfiguration) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ExternalAdmissionHookConfiguration) MarshalTo(dAtA []byte) (int, error) { - var i int - _ = i - var l int - _ = l - dAtA[i] = 0xa - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n3, err := m.ObjectMeta.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n3 - if len(m.ExternalAdmissionHooks) > 0 { - for _, msg := range m.ExternalAdmissionHooks { - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) - n, err := msg.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n - } - } - return i, nil -} - -func (m *ExternalAdmissionHookConfigurationList) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ExternalAdmissionHookConfigurationList) MarshalTo(dAtA []byte) (int, error) { - var i int - _ = i - var l int - _ = l - dAtA[i] = 0xa - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n4, err := m.ListMeta.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n4 - if len(m.Items) > 0 { - for _, msg := range m.Items { - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) - n, err := msg.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n - } - } - return i, nil -} - func (m *Initializer) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -330,11 +130,11 @@ func (m *InitializerConfiguration) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n5, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n1, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n5 + i += n1 if len(m.Initializers) > 0 { for _, msg := range m.Initializers { dAtA[i] = 0x12 @@ -368,11 +168,11 @@ func (m *InitializerConfigurationList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n6, err := m.ListMeta.MarshalTo(dAtA[i:]) + n2, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n6 + i += n2 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -451,73 +251,6 @@ func (m *Rule) MarshalTo(dAtA []byte) (int, error) { return i, nil } -func (m *RuleWithOperations) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *RuleWithOperations) MarshalTo(dAtA []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.Operations) > 0 { - for _, s := range m.Operations { - dAtA[i] = 0xa - i++ - l = len(s) - for l >= 1<<7 { - dAtA[i] = uint8(uint64(l)&0x7f | 0x80) - l >>= 7 - i++ - } - dAtA[i] = uint8(l) - i++ - i += copy(dAtA[i:], s) - } - } - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Rule.Size())) - n7, err := m.Rule.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n7 - return i, nil -} - -func (m *ServiceReference) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ServiceReference) MarshalTo(dAtA []byte) (int, error) { - var i int - _ = i - var l int - _ = l - dAtA[i] = 0xa - i++ - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Namespace))) - i += copy(dAtA[i:], m.Namespace) - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) - i += copy(dAtA[i:], m.Name) - return i, nil -} - func encodeFixed64Generated(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) dAtA[offset+1] = uint8(v >> 8) @@ -545,66 +278,6 @@ func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return offset + 1 } -func (m *AdmissionHookClientConfig) Size() (n int) { - var l int - _ = l - l = m.Service.Size() - n += 1 + l + sovGenerated(uint64(l)) - if m.CABundle != nil { - l = len(m.CABundle) - n += 1 + l + sovGenerated(uint64(l)) - } - return n -} - -func (m *ExternalAdmissionHook) Size() (n int) { - 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)) - } - return n -} - -func (m *ExternalAdmissionHookConfiguration) Size() (n int) { - var l int - _ = l - l = m.ObjectMeta.Size() - n += 1 + l + sovGenerated(uint64(l)) - if len(m.ExternalAdmissionHooks) > 0 { - for _, e := range m.ExternalAdmissionHooks { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - } - return n -} - -func (m *ExternalAdmissionHookConfigurationList) Size() (n int) { - 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 *Initializer) Size() (n int) { var l int _ = l @@ -671,30 +344,6 @@ func (m *Rule) Size() (n int) { return n } -func (m *RuleWithOperations) Size() (n int) { - var l int - _ = l - if len(m.Operations) > 0 { - for _, s := range m.Operations { - l = len(s) - n += 1 + l + sovGenerated(uint64(l)) - } - } - l = m.Rule.Size() - n += 1 + l + sovGenerated(uint64(l)) - return n -} - -func (m *ServiceReference) Size() (n int) { - var l int - _ = l - l = len(m.Namespace) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Name) - n += 1 + l + sovGenerated(uint64(l)) - return n -} - func sovGenerated(x uint64) (n int) { for { n++ @@ -708,52 +357,6 @@ func sovGenerated(x uint64) (n int) { func sozGenerated(x uint64) (n int) { return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (this *AdmissionHookClientConfig) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&AdmissionHookClientConfig{`, - `Service:` + strings.Replace(strings.Replace(this.Service.String(), "ServiceReference", "ServiceReference", 1), `&`, ``, 1) + `,`, - `CABundle:` + valueToStringGenerated(this.CABundle) + `,`, - `}`, - }, "") - return s -} -func (this *ExternalAdmissionHook) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ExternalAdmissionHook{`, - `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `ClientConfig:` + strings.Replace(strings.Replace(this.ClientConfig.String(), "AdmissionHookClientConfig", "AdmissionHookClientConfig", 1), `&`, ``, 1) + `,`, - `Rules:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Rules), "RuleWithOperations", "RuleWithOperations", 1), `&`, ``, 1) + `,`, - `FailurePolicy:` + valueToStringGenerated(this.FailurePolicy) + `,`, - `}`, - }, "") - return s -} -func (this *ExternalAdmissionHookConfiguration) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ExternalAdmissionHookConfiguration{`, - `ObjectMeta:` + strings.Replace(strings.Replace(this.ObjectMeta.String(), "ObjectMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ObjectMeta", 1), `&`, ``, 1) + `,`, - `ExternalAdmissionHooks:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ExternalAdmissionHooks), "ExternalAdmissionHook", "ExternalAdmissionHook", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s -} -func (this *ExternalAdmissionHookConfigurationList) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ExternalAdmissionHookConfigurationList{`, - `ListMeta:` + strings.Replace(strings.Replace(this.ListMeta.String(), "ListMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ListMeta", 1), `&`, ``, 1) + `,`, - `Items:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Items), "ExternalAdmissionHookConfiguration", "ExternalAdmissionHookConfiguration", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s -} func (this *Initializer) String() string { if this == nil { return "nil" @@ -799,28 +402,6 @@ func (this *Rule) String() string { }, "") 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) + `,`, - `}`, - }, "") - return s -} func valueToStringGenerated(v interface{}) string { rv := reflect.ValueOf(v) if rv.IsNil() { @@ -829,509 +410,6 @@ func valueToStringGenerated(v interface{}) string { pv := reflect.Indirect(rv).Interface() return fmt.Sprintf("*%v", pv) } -func (m *AdmissionHookClientConfig) 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: AdmissionHookClientConfig: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: AdmissionHookClientConfig: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Service", 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 > l { - return io.ErrUnexpectedEOF - } - if err := m.Service.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CABundle", 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 > l { - return io.ErrUnexpectedEOF - } - m.CABundle = append(m.CABundle[:0], dAtA[iNdEx:postIndex]...) - if m.CABundle == nil { - m.CABundle = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ExternalAdmissionHook) 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: ExternalAdmissionHook: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ExternalAdmissionHook: 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 > 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 ClientConfig", 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 > l { - return io.ErrUnexpectedEOF - } - if err := m.ClientConfig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Rules", 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 > l { - return io.ErrUnexpectedEOF - } - m.Rules = append(m.Rules, RuleWithOperations{}) - if err := m.Rules[len(m.Rules)-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 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 > l { - return io.ErrUnexpectedEOF - } - s := FailurePolicyType(dAtA[iNdEx:postIndex]) - m.FailurePolicy = &s - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ExternalAdmissionHookConfiguration) 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: ExternalAdmissionHookConfiguration: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ExternalAdmissionHookConfiguration: 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 > 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 ExternalAdmissionHooks", 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 > l { - return io.ErrUnexpectedEOF - } - m.ExternalAdmissionHooks = append(m.ExternalAdmissionHooks, ExternalAdmissionHook{}) - if err := m.ExternalAdmissionHooks[len(m.ExternalAdmissionHooks)-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 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ExternalAdmissionHookConfigurationList) 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: ExternalAdmissionHookConfigurationList: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ExternalAdmissionHookConfigurationList: 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 > 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 > l { - return io.ErrUnexpectedEOF - } - m.Items = append(m.Items, ExternalAdmissionHookConfiguration{}) - 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 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *Initializer) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -1801,223 +879,6 @@ func (m *Rule) Unmarshal(dAtA []byte) error { } return nil } -func (m *RuleWithOperations) 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: RuleWithOperations: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: RuleWithOperations: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Operations", 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 > l { - return io.ErrUnexpectedEOF - } - m.Operations = append(m.Operations, OperationType(dAtA[iNdEx:postIndex])) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Rule", 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 > l { - return io.ErrUnexpectedEOF - } - if err := m.Rule.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 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ServiceReference) 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: ServiceReference: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ServiceReference: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Namespace", 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 > l { - return io.ErrUnexpectedEOF - } - m.Namespace = 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 > 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 { - 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 @@ -2128,60 +989,40 @@ func init() { } var fileDescriptorGenerated = []byte{ - // 871 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x55, 0xcd, 0x8b, 0x23, 0x45, - 0x14, 0x4f, 0x65, 0x32, 0x6c, 0x52, 0x49, 0xd8, 0xdd, 0x42, 0x97, 0x38, 0x48, 0x77, 0xe8, 0xc3, - 0x12, 0x11, 0xbb, 0x9d, 0x51, 0x16, 0x41, 0x44, 0xa7, 0xc7, 0xaf, 0x81, 0xfd, 0x18, 0xcb, 0x45, - 0x41, 0x3c, 0x58, 0xe9, 0xbc, 0x24, 0x65, 0xfa, 0x8b, 0xaa, 0xea, 0xe0, 0x78, 0x10, 0x2f, 0xde, - 0x05, 0x2f, 0x5e, 0xbd, 0x79, 0xf1, 0xff, 0x98, 0xe3, 0x1e, 0xf7, 0x14, 0x9c, 0x16, 0xbc, 0x08, - 0xfe, 0x01, 0x73, 0x92, 0xfe, 0x4a, 0x3a, 0x9b, 0x84, 0x4d, 0x5c, 0x98, 0x5b, 0xea, 0xf7, 0xea, - 0xf7, 0xde, 0xef, 0xfd, 0xf2, 0x5e, 0x35, 0xa6, 0x93, 0x77, 0xa4, 0xc9, 0x03, 0x6b, 0x12, 0xf5, - 0x41, 0xf8, 0xa0, 0x40, 0x5a, 0x53, 0xf0, 0x07, 0x81, 0xb0, 0xf2, 0x00, 0x0b, 0xb9, 0xc5, 0x06, - 0x1e, 0x97, 0x92, 0x07, 0xbe, 0x80, 0x11, 0x97, 0x4a, 0x30, 0xc5, 0x03, 0xdf, 0x9a, 0x1e, 0x32, - 0x37, 0x1c, 0xb3, 0x43, 0x6b, 0x04, 0x3e, 0x08, 0xa6, 0x60, 0x60, 0x86, 0x22, 0x50, 0x01, 0x79, - 0x2d, 0xa3, 0x9a, 0x2c, 0xe4, 0xe6, 0x5a, 0xaa, 0x59, 0x50, 0x0f, 0xde, 0x18, 0x71, 0x35, 0x8e, - 0xfa, 0xa6, 0x13, 0x78, 0xd6, 0x28, 0x18, 0x05, 0x56, 0x9a, 0xa1, 0x1f, 0x0d, 0xd3, 0x53, 0x7a, - 0x48, 0x7f, 0x65, 0x99, 0x0f, 0xde, 0x5e, 0x88, 0xf2, 0x98, 0x33, 0xe6, 0x3e, 0x88, 0x73, 0x2b, - 0x9c, 0x8c, 0x12, 0x40, 0x5a, 0x1e, 0x28, 0x66, 0x4d, 0x57, 0xf4, 0x1c, 0x58, 0x9b, 0x58, 0x22, - 0xf2, 0x15, 0xf7, 0x60, 0x85, 0x70, 0xef, 0x79, 0x04, 0xe9, 0x8c, 0xc1, 0x63, 0x2b, 0xbc, 0xb7, - 0x36, 0xf1, 0x22, 0xc5, 0x5d, 0x8b, 0xfb, 0x4a, 0x2a, 0xf1, 0x2c, 0xc9, 0xf8, 0x03, 0xe1, 0x57, - 0x8e, 0x0b, 0x97, 0x3e, 0x0d, 0x82, 0xc9, 0x89, 0xcb, 0xc1, 0x57, 0x27, 0x81, 0x3f, 0xe4, 0x23, - 0x32, 0xc4, 0x37, 0x24, 0x88, 0x29, 0x77, 0xa0, 0x83, 0xba, 0xa8, 0xd7, 0x3c, 0x7a, 0xd7, 0xdc, - 0xda, 0x5d, 0xf3, 0xf3, 0x8c, 0x49, 0x61, 0x08, 0x02, 0x7c, 0x07, 0xec, 0x9b, 0x17, 0x33, 0xbd, - 0x12, 0xcf, 0xf4, 0x1b, 0x45, 0xa4, 0x48, 0x4e, 0x7a, 0xb8, 0xee, 0x30, 0x3b, 0xf2, 0x07, 0x2e, - 0x74, 0xaa, 0x5d, 0xd4, 0x6b, 0xd9, 0xad, 0x78, 0xa6, 0xd7, 0x4f, 0x8e, 0x33, 0x8c, 0xce, 0xa3, - 0xc6, 0x3f, 0x55, 0xfc, 0xf2, 0x47, 0xdf, 0x29, 0x10, 0x3e, 0x73, 0x97, 0x74, 0x93, 0x2e, 0xae, - 0xf9, 0xcc, 0xcb, 0x84, 0x36, 0xec, 0x56, 0x5e, 0xab, 0xf6, 0x90, 0x79, 0x40, 0xd3, 0x08, 0xf9, - 0x01, 0xb7, 0x9c, 0x52, 0x77, 0x69, 0xa5, 0xe6, 0xd1, 0x87, 0x3b, 0xb4, 0xb4, 0xd1, 0x29, 0xfb, - 0xa5, 0xbc, 0x5e, 0xab, 0x8c, 0xd2, 0xa5, 0x7a, 0xa4, 0x8f, 0xf7, 0x45, 0xe4, 0x82, 0xec, 0xec, - 0x75, 0xf7, 0x7a, 0xcd, 0xa3, 0xf7, 0x76, 0x28, 0x4c, 0x23, 0x17, 0xbe, 0xe4, 0x6a, 0xfc, 0x28, - 0x84, 0x2c, 0x24, 0xed, 0x76, 0x5e, 0x71, 0x3f, 0x89, 0x49, 0x9a, 0xa5, 0x26, 0xf7, 0x71, 0x7b, - 0xc8, 0xb8, 0x1b, 0x09, 0x38, 0x0b, 0x5c, 0xee, 0x9c, 0x77, 0x6a, 0xa9, 0x1d, 0x77, 0xe3, 0x99, - 0xde, 0xfe, 0xb8, 0x1c, 0xb8, 0x9a, 0xe9, 0xb7, 0x97, 0x80, 0xc7, 0xe7, 0x21, 0xd0, 0x65, 0xb2, - 0xf1, 0x5b, 0x15, 0x1b, 0x6b, 0xdd, 0xce, 0x3a, 0x8a, 0x32, 0x2d, 0xe4, 0x1b, 0x5c, 0x4f, 0xa6, - 0x7f, 0xc0, 0x14, 0xcb, 0xe7, 0xe4, 0xcd, 0x52, 0x6f, 0xf3, 0x61, 0x34, 0xc3, 0xc9, 0x28, 0x01, - 0xa4, 0x99, 0xdc, 0x36, 0xa7, 0x87, 0xe6, 0xa3, 0xfe, 0xb7, 0xe0, 0xa8, 0x07, 0xa0, 0x98, 0x4d, - 0xf2, 0x76, 0xf0, 0x02, 0xa3, 0xf3, 0xac, 0xe4, 0x57, 0x84, 0xef, 0xc0, 0x3a, 0x21, 0xb2, 0x53, - 0x4d, 0xcd, 0xfc, 0x60, 0x07, 0x33, 0xd7, 0x76, 0x64, 0x6b, 0xb9, 0x80, 0x3b, 0x6b, 0xc3, 0x92, - 0x6e, 0xa8, 0x6f, 0x5c, 0x21, 0x7c, 0xf7, 0xf9, 0x1e, 0xdd, 0xe7, 0x52, 0x91, 0xaf, 0x57, 0x7c, - 0x32, 0xb7, 0xf3, 0x29, 0x61, 0xa7, 0x2e, 0xdd, 0xca, 0x45, 0xd6, 0x0b, 0xa4, 0xe4, 0x91, 0xc0, - 0xfb, 0x5c, 0x81, 0x57, 0x38, 0xf2, 0xe0, 0x45, 0x1d, 0x59, 0xd2, 0xbf, 0x18, 0xb7, 0xd3, 0xa4, - 0x06, 0xcd, 0x4a, 0x19, 0x3f, 0x21, 0xdc, 0x3c, 0xf5, 0xb9, 0xe2, 0xcc, 0xe5, 0xdf, 0x83, 0xd8, - 0x62, 0x09, 0x1f, 0x17, 0x4b, 0x90, 0xa9, 0xb4, 0x76, 0x5c, 0x82, 0xf5, 0x63, 0x6f, 0xfc, 0x8b, - 0x70, 0xa7, 0xa4, 0xe3, 0xba, 0xc7, 0x33, 0xc4, 0x2d, 0xbe, 0xa8, 0x5e, 0xf4, 0x76, 0x6f, 0x87, - 0xde, 0x4a, 0xe2, 0x17, 0x6f, 0x49, 0x09, 0x94, 0x74, 0xa9, 0x82, 0xf1, 0x37, 0xc2, 0xaf, 0x6e, - 0x6a, 0xf8, 0x1a, 0x66, 0x6d, 0xbc, 0x3c, 0x6b, 0x27, 0xff, 0xaf, 0xd3, 0x6d, 0x26, 0xec, 0x17, - 0x84, 0x6b, 0xc9, 0x5f, 0x4d, 0x5e, 0xc7, 0x0d, 0x16, 0xf2, 0x4f, 0x44, 0x10, 0x85, 0xb2, 0x83, - 0xba, 0x7b, 0xbd, 0x86, 0xdd, 0x8e, 0x67, 0x7a, 0xe3, 0xf8, 0xec, 0x34, 0x03, 0xe9, 0x22, 0x4e, - 0x0e, 0x71, 0x93, 0x85, 0xfc, 0x0b, 0x10, 0x89, 0x8e, 0x4c, 0x65, 0xc3, 0xbe, 0x19, 0xcf, 0xf4, - 0xe6, 0xf1, 0xd9, 0x69, 0x01, 0xd3, 0xf2, 0x9d, 0x24, 0xbf, 0x00, 0x19, 0x44, 0xc2, 0xc9, 0x5f, - 0xe8, 0x3c, 0x3f, 0x2d, 0x40, 0xba, 0x88, 0x1b, 0xbf, 0x23, 0x4c, 0x56, 0xdf, 0x64, 0xf2, 0x3e, - 0xc6, 0xc1, 0xfc, 0x94, 0x8b, 0xd4, 0xd3, 0xa9, 0x99, 0xa3, 0x57, 0x33, 0xbd, 0x3d, 0x3f, 0xa5, - 0x6f, 0x6e, 0x89, 0x42, 0x3e, 0xc3, 0xb5, 0x64, 0xa0, 0xf3, 0x4f, 0xd3, 0xce, 0xcb, 0x31, 0x5f, - 0xb8, 0xe4, 0x44, 0xd3, 0x54, 0x06, 0xe0, 0x5b, 0xcf, 0x7e, 0x89, 0x89, 0x85, 0x1b, 0xc9, 0x32, - 0xca, 0x90, 0x39, 0xc5, 0xae, 0xde, 0xce, 0xa9, 0x8d, 0x87, 0x45, 0x80, 0x2e, 0xee, 0xcc, 0xf7, - 0xba, 0xba, 0x69, 0xaf, 0x6d, 0xf3, 0xe2, 0x52, 0xab, 0x3c, 0xb9, 0xd4, 0x2a, 0x4f, 0x2f, 0xb5, - 0xca, 0x8f, 0xb1, 0x86, 0x2e, 0x62, 0x0d, 0x3d, 0x89, 0x35, 0xf4, 0x34, 0xd6, 0xd0, 0x9f, 0xb1, - 0x86, 0x7e, 0xfe, 0x4b, 0xab, 0x7c, 0x55, 0x2f, 0xf4, 0xfe, 0x17, 0x00, 0x00, 0xff, 0xff, 0xe7, - 0xb5, 0x5f, 0xd5, 0xfb, 0x09, 0x00, 0x00, + // 545 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x51, 0x4d, 0x8b, 0x13, 0x3f, + 0x18, 0x6f, 0xfe, 0xdb, 0x42, 0x9b, 0x76, 0xf9, 0xcb, 0xe0, 0xa1, 0x14, 0x99, 0x96, 0x9e, 0x2a, + 0x62, 0x62, 0x57, 0x59, 0xbc, 0xee, 0xec, 0x41, 0x0a, 0xbe, 0x2c, 0x41, 0x3c, 0x88, 0x07, 0xd3, + 0xf6, 0xd9, 0x69, 0x6c, 0x27, 0x19, 0x92, 0x4c, 0x41, 0x4f, 0x5e, 0xbc, 0x0b, 0x7e, 0xa9, 0x1e, + 0xf7, 0xb8, 0xa7, 0x62, 0x47, 0xf0, 0xe8, 0x67, 0x90, 0x99, 0xe9, 0xec, 0xcc, 0x5a, 0x8b, 0xab, + 0xb7, 0x3c, 0xbf, 0x27, 0xbf, 0xb7, 0x04, 0xb3, 0xf9, 0x63, 0x43, 0x84, 0xa2, 0xf3, 0x68, 0x0c, + 0x5a, 0x82, 0x05, 0x43, 0x97, 0x20, 0xa7, 0x4a, 0xd3, 0xed, 0x82, 0x87, 0x82, 0xf2, 0x69, 0x20, + 0x8c, 0x11, 0x4a, 0x6a, 0xf0, 0x85, 0xb1, 0x9a, 0x5b, 0xa1, 0x24, 0x5d, 0x0e, 0xf9, 0x22, 0x9c, + 0xf1, 0x21, 0xf5, 0x41, 0x82, 0xe6, 0x16, 0xa6, 0x24, 0xd4, 0xca, 0x2a, 0xe7, 0x6e, 0x46, 0x25, + 0x3c, 0x14, 0xe4, 0xb7, 0x54, 0x92, 0x53, 0x3b, 0xf7, 0x7d, 0x61, 0x67, 0xd1, 0x98, 0x4c, 0x54, + 0x40, 0x7d, 0xe5, 0x2b, 0x9a, 0x2a, 0x8c, 0xa3, 0xf3, 0x74, 0x4a, 0x87, 0xf4, 0x94, 0x29, 0x77, + 0x1e, 0x15, 0xa1, 0x02, 0x3e, 0x99, 0x09, 0x09, 0xfa, 0x3d, 0x0d, 0xe7, 0x7e, 0x02, 0x18, 0x1a, + 0x80, 0xe5, 0x74, 0xb9, 0x93, 0xa7, 0x43, 0xf7, 0xb1, 0x74, 0x24, 0xad, 0x08, 0x60, 0x87, 0x70, + 0xfc, 0x27, 0x82, 0x99, 0xcc, 0x20, 0xe0, 0x3b, 0xbc, 0x87, 0xfb, 0x78, 0x91, 0x15, 0x0b, 0x2a, + 0xa4, 0x35, 0x56, 0xff, 0x4a, 0xea, 0x7f, 0x42, 0xb8, 0x39, 0x92, 0xc2, 0x0a, 0xbe, 0x10, 0x1f, + 0x40, 0x3b, 0x3d, 0x5c, 0x95, 0x3c, 0x80, 0x36, 0xea, 0xa1, 0x41, 0xc3, 0x6b, 0xad, 0xd6, 0xdd, + 0x4a, 0xbc, 0xee, 0x56, 0x9f, 0xf3, 0x00, 0x58, 0xba, 0x71, 0x5e, 0xe2, 0x9a, 0x8e, 0x16, 0x60, + 0xda, 0xff, 0xf5, 0x0e, 0x06, 0xcd, 0x23, 0x4a, 0x6e, 0xfc, 0xde, 0x84, 0x45, 0x0b, 0xf0, 0x0e, + 0xb7, 0x9a, 0xb5, 0x64, 0x32, 0x2c, 0x13, 0xeb, 0xff, 0x40, 0xb8, 0x5d, 0xca, 0x71, 0xaa, 0xe4, + 0xb9, 0xf0, 0xa3, 0x4c, 0xc0, 0x79, 0x8b, 0xeb, 0xc9, 0xeb, 0x4e, 0xb9, 0xe5, 0x69, 0xb0, 0xe6, + 0xd1, 0x83, 0x92, 0xeb, 0x55, 0x59, 0x12, 0xce, 0xfd, 0x04, 0x30, 0x24, 0xb9, 0x4d, 0x96, 0x43, + 0xf2, 0x62, 0xfc, 0x0e, 0x26, 0xf6, 0x19, 0x58, 0xee, 0x39, 0x5b, 0x5b, 0x5c, 0x60, 0xec, 0x4a, + 0xd5, 0x09, 0x71, 0x4b, 0x14, 0xee, 0x79, 0xb7, 0xe3, 0xbf, 0xe8, 0x56, 0x0a, 0xef, 0xdd, 0xde, + 0x7a, 0xb5, 0x4a, 0xa0, 0x61, 0xd7, 0x1c, 0xfa, 0xdf, 0x11, 0xbe, 0xb3, 0xaf, 0xf0, 0x53, 0x61, + 0xac, 0xf3, 0x66, 0xa7, 0x34, 0xb9, 0x59, 0xe9, 0x84, 0x9d, 0x56, 0xbe, 0xb5, 0x8d, 0x51, 0xcf, + 0x91, 0x52, 0xe1, 0x19, 0xae, 0x09, 0x0b, 0x41, 0xde, 0xf4, 0xf4, 0xdf, 0x9a, 0x5e, 0x4b, 0x5d, + 0xfc, 0xec, 0x28, 0x51, 0x66, 0x99, 0x41, 0xff, 0x0b, 0xc2, 0xd5, 0xe4, 0xab, 0x9d, 0x7b, 0xb8, + 0xc1, 0x43, 0xf1, 0x44, 0xab, 0x28, 0x34, 0x6d, 0xd4, 0x3b, 0x18, 0x34, 0xbc, 0xc3, 0x78, 0xdd, + 0x6d, 0x9c, 0x9c, 0x8d, 0x32, 0x90, 0x15, 0x7b, 0x67, 0x88, 0x9b, 0x3c, 0x14, 0xaf, 0x40, 0x27, + 0x39, 0xb2, 0x94, 0x0d, 0xef, 0xff, 0x78, 0xdd, 0x6d, 0x9e, 0x9c, 0x8d, 0x72, 0x98, 0x95, 0xef, + 0x24, 0xfa, 0x1a, 0x8c, 0x8a, 0xf4, 0x04, 0x4c, 0xfb, 0xa0, 0xd0, 0x67, 0x39, 0xc8, 0x8a, 0xbd, + 0x47, 0x56, 0x1b, 0xb7, 0x72, 0xb1, 0x71, 0x2b, 0x97, 0x1b, 0xb7, 0xf2, 0x31, 0x76, 0xd1, 0x2a, + 0x76, 0xd1, 0x45, 0xec, 0xa2, 0xcb, 0xd8, 0x45, 0x5f, 0x63, 0x17, 0x7d, 0xfe, 0xe6, 0x56, 0x5e, + 0xd7, 0xf3, 0xd2, 0x3f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x1d, 0xfb, 0x23, 0x89, 0xaa, 0x04, 0x00, + 0x00, } diff --git a/staging/src/k8s.io/api/admissionregistration/v1alpha1/generated.proto b/staging/src/k8s.io/api/admissionregistration/v1alpha1/generated.proto index 5b0d4f0065b..11de02ff405 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1alpha1/generated.proto +++ b/staging/src/k8s.io/api/admissionregistration/v1alpha1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -29,69 +29,6 @@ import "k8s.io/apimachinery/pkg/util/intstr/generated.proto"; // Package-wide variables from generator "generated". option go_package = "v1alpha1"; -// AdmissionHookClientConfig contains the information to make a TLS -// connection with the webhook -message AdmissionHookClientConfig { - // Service is a reference to the service for this webhook. If there is only - // one port open for the service, that port will be used. If there are multiple - // ports open, port 443 will be used if it is open, otherwise it is an error. - // Required - optional ServiceReference service = 1; - - // CABundle is a PEM encoded CA bundle which will be used to validate webhook's server certificate. - // Required - optional bytes caBundle = 2; -} - -// ExternalAdmissionHook describes an external admission webhook and the -// resources and operations it applies to. -message ExternalAdmissionHook { - // The name of the external 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. - // Required - optional AdmissionHookClientConfig clientConfig = 2; - - // Rules describes what operations on what resources/subresources the webhook cares about. - // The webhook cares about an operation if it matches _any_ Rule. - repeated RuleWithOperations rules = 3; - - // 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; -} - -// ExternalAdmissionHookConfiguration describes the configuration of initializers. -message ExternalAdmissionHookConfiguration { - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata. - // +optional - optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - - // ExternalAdmissionHooks is a list of external admission webhooks and the - // affected resources and operations. - // +optional - // +patchMergeKey=name - // +patchStrategy=merge - repeated ExternalAdmissionHook externalAdmissionHooks = 2; -} - -// ExternalAdmissionHookConfigurationList is a list of ExternalAdmissionHookConfiguration. -message ExternalAdmissionHookConfigurationList { - // Standard list metadata. - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds - // +optional - optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; - - // List of ExternalAdmissionHookConfiguration. - repeated ExternalAdmissionHookConfiguration items = 2; -} - // Initializer describes the name and the failure policy of an initializer, and // what resources it applies to. message Initializer { @@ -169,28 +106,3 @@ message Rule { repeated string resources = 3; } -// 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, or * - // for all operations. - // If '*' is present, the length of the slice must be one. - // Required. - repeated string operations = 1; - - // Rule is embedded, it describes other criteria of the rule, like - // APIGroups, APIVersions, Resources, etc. - optional Rule rule = 2; -} - -// ServiceReference holds a reference to Service.legacy.k8s.io -message ServiceReference { - // Namespace is the namespace of the service - // Required - optional string namespace = 1; - - // Name is the name of the service - // Required - optional string name = 2; -} - diff --git a/staging/src/k8s.io/api/admissionregistration/v1alpha1/register.go b/staging/src/k8s.io/api/admissionregistration/v1alpha1/register.go index e178cf743ef..e9a4164c375 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1alpha1/register.go +++ b/staging/src/k8s.io/api/admissionregistration/v1alpha1/register.go @@ -45,8 +45,6 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &InitializerConfiguration{}, &InitializerConfigurationList{}, - &ExternalAdmissionHookConfiguration{}, - &ExternalAdmissionHookConfigurationList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/staging/src/k8s.io/api/admissionregistration/v1alpha1/types.go b/staging/src/k8s.io/api/admissionregistration/v1alpha1/types.go index d4827e59d33..a245f1e858f 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1alpha1/types.go +++ b/staging/src/k8s.io/api/admissionregistration/v1alpha1/types.go @@ -104,116 +104,3 @@ type Rule struct { // Required. Resources []string `json:"resources,omitempty" protobuf:"bytes,3,rep,name=resources"` } - -type FailurePolicyType string - -const ( - // Ignore means the initilizer is removed from the initializers list of an - // object if the initializer is timed out. - Ignore FailurePolicyType = "Ignore" - // For 1.7, only "Ignore" is allowed. "Fail" will be allowed when the - // extensible admission feature is beta. - Fail FailurePolicyType = "Fail" -) - -// +genclient -// +genclient:nonNamespaced -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ExternalAdmissionHookConfiguration describes the configuration of initializers. -type ExternalAdmissionHookConfiguration struct { - metav1.TypeMeta `json:",inline"` - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata. - // +optional - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // ExternalAdmissionHooks is a list of external admission webhooks and the - // affected resources and operations. - // +optional - // +patchMergeKey=name - // +patchStrategy=merge - ExternalAdmissionHooks []ExternalAdmissionHook `json:"externalAdmissionHooks,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,2,rep,name=externalAdmissionHooks"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ExternalAdmissionHookConfigurationList is a list of ExternalAdmissionHookConfiguration. -type ExternalAdmissionHookConfigurationList struct { - metav1.TypeMeta `json:",inline"` - // Standard list metadata. - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds - // +optional - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // List of ExternalAdmissionHookConfiguration. - Items []ExternalAdmissionHookConfiguration `json:"items" protobuf:"bytes,2,rep,name=items"` -} - -// ExternalAdmissionHook describes an external admission webhook and the -// resources and operations it applies to. -type ExternalAdmissionHook struct { - // The name of the external 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. - // Required - ClientConfig AdmissionHookClientConfig `json:"clientConfig" protobuf:"bytes,2,opt,name=clientConfig"` - - // Rules describes what operations on what resources/subresources the webhook cares about. - // The webhook cares about an operation if it matches _any_ Rule. - Rules []RuleWithOperations `json:"rules,omitempty" protobuf:"bytes,3,rep,name=rules"` - - // 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"` -} - -// 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, or * - // for all operations. - // If '*' is present, the length of the slice must be one. - // Required. - Operations []OperationType `json:"operations,omitempty" protobuf:"bytes,1,rep,name=operations,casttype=OperationType"` - // Rule is embedded, it describes other criteria of the rule, like - // APIGroups, APIVersions, Resources, etc. - Rule `json:",inline" protobuf:"bytes,2,opt,name=rule"` -} - -type OperationType string - -// The constants should be kept in sync with those defined in k8s.io/kubernetes/pkg/admission/interface.go. -const ( - OperationAll OperationType = "*" - Create OperationType = "CREATE" - Update OperationType = "UPDATE" - Delete OperationType = "DELETE" - Connect OperationType = "CONNECT" -) - -// AdmissionHookClientConfig contains the information to make a TLS -// connection with the webhook -type AdmissionHookClientConfig struct { - // Service is a reference to the service for this webhook. If there is only - // one port open for the service, that port will be used. If there are multiple - // ports open, port 443 will be used if it is open, otherwise it is an error. - // Required - Service ServiceReference `json:"service" protobuf:"bytes,1,opt,name=service"` - // CABundle is a PEM encoded CA bundle which will be used to validate webhook's server certificate. - // Required - CABundle []byte `json:"caBundle" protobuf:"bytes,2,opt,name=caBundle"` -} - -// ServiceReference holds a reference to Service.legacy.k8s.io -type ServiceReference struct { - // 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 - // Required - Name string `json:"name" protobuf:"bytes,2,opt,name=name"` -} 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 0b30ecc802e..e2494e5d776 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 @@ -27,48 +27,6 @@ package v1alpha1 // Those methods can be generated by using hack/update-generated-swagger-docs.sh // AUTO-GENERATED FUNCTIONS START HERE -var map_AdmissionHookClientConfig = map[string]string{ - "": "AdmissionHookClientConfig contains the information to make a TLS connection with the webhook", - "service": "Service is a reference to the service for this webhook. If there is only one port open for the service, that port will be used. If there are multiple ports open, port 443 will be used if it is open, otherwise it is an error. Required", - "caBundle": "CABundle is a PEM encoded CA bundle which will be used to validate webhook's server certificate. Required", -} - -func (AdmissionHookClientConfig) SwaggerDoc() map[string]string { - return map_AdmissionHookClientConfig -} - -var map_ExternalAdmissionHook = map[string]string{ - "": "ExternalAdmissionHook describes an external admission webhook and the resources and operations it applies to.", - "name": "The name of the external 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.", - "failurePolicy": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Ignore.", -} - -func (ExternalAdmissionHook) SwaggerDoc() map[string]string { - return map_ExternalAdmissionHook -} - -var map_ExternalAdmissionHookConfiguration = map[string]string{ - "": "ExternalAdmissionHookConfiguration describes the configuration of initializers.", - "metadata": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata.", - "externalAdmissionHooks": "ExternalAdmissionHooks is a list of external admission webhooks and the affected resources and operations.", -} - -func (ExternalAdmissionHookConfiguration) SwaggerDoc() map[string]string { - return map_ExternalAdmissionHookConfiguration -} - -var map_ExternalAdmissionHookConfigurationList = map[string]string{ - "": "ExternalAdmissionHookConfigurationList is a list of ExternalAdmissionHookConfiguration.", - "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds", - "items": "List of ExternalAdmissionHookConfiguration.", -} - -func (ExternalAdmissionHookConfigurationList) SwaggerDoc() map[string]string { - return map_ExternalAdmissionHookConfigurationList -} - var map_Initializer = map[string]string{ "": "Initializer describes the name and the failure policy of an initializer, and what resources it applies to.", "name": "Name is the identifier of the initializer. It will be added to the object that needs to be initialized. Name should be fully qualified, e.g., alwayspullimages.kubernetes.io, where \"alwayspullimages\" is the name of the webhook, and kubernetes.io is the name of the organization. Required", @@ -110,23 +68,4 @@ func (Rule) SwaggerDoc() map[string]string { return map_Rule } -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, or * for all operations. If '*' is present, the length of the slice must be one. Required.", -} - -func (RuleWithOperations) SwaggerDoc() map[string]string { - return map_RuleWithOperations -} - -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", -} - -func (ServiceReference) SwaggerDoc() map[string]string { - return map_ServiceReference -} - // AUTO-GENERATED FUNCTIONS END HERE diff --git a/staging/src/k8s.io/api/admissionregistration/v1alpha1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/admissionregistration/v1alpha1/zz_generated.deepcopy.go index 118fed75031..667b262ab6d 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1alpha1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/admissionregistration/v1alpha1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,187 +21,9 @@ limitations under the License. package v1alpha1 import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AdmissionHookClientConfig).DeepCopyInto(out.(*AdmissionHookClientConfig)) - return nil - }, InType: reflect.TypeOf(&AdmissionHookClientConfig{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExternalAdmissionHook).DeepCopyInto(out.(*ExternalAdmissionHook)) - return nil - }, InType: reflect.TypeOf(&ExternalAdmissionHook{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExternalAdmissionHookConfiguration).DeepCopyInto(out.(*ExternalAdmissionHookConfiguration)) - return nil - }, InType: reflect.TypeOf(&ExternalAdmissionHookConfiguration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExternalAdmissionHookConfigurationList).DeepCopyInto(out.(*ExternalAdmissionHookConfigurationList)) - return nil - }, InType: reflect.TypeOf(&ExternalAdmissionHookConfigurationList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Initializer).DeepCopyInto(out.(*Initializer)) - return nil - }, InType: reflect.TypeOf(&Initializer{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*InitializerConfiguration).DeepCopyInto(out.(*InitializerConfiguration)) - return nil - }, InType: reflect.TypeOf(&InitializerConfiguration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*InitializerConfigurationList).DeepCopyInto(out.(*InitializerConfigurationList)) - return nil - }, InType: reflect.TypeOf(&InitializerConfigurationList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Rule).DeepCopyInto(out.(*Rule)) - return nil - }, InType: reflect.TypeOf(&Rule{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RuleWithOperations).DeepCopyInto(out.(*RuleWithOperations)) - return nil - }, InType: reflect.TypeOf(&RuleWithOperations{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ServiceReference).DeepCopyInto(out.(*ServiceReference)) - return nil - }, InType: reflect.TypeOf(&ServiceReference{})}, - ) -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AdmissionHookClientConfig) DeepCopyInto(out *AdmissionHookClientConfig) { - *out = *in - out.Service = in.Service - if in.CABundle != nil { - in, out := &in.CABundle, &out.CABundle - *out = make([]byte, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionHookClientConfig. -func (in *AdmissionHookClientConfig) DeepCopy() *AdmissionHookClientConfig { - if in == nil { - return nil - } - out := new(AdmissionHookClientConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExternalAdmissionHook) DeepCopyInto(out *ExternalAdmissionHook) { - *out = *in - in.ClientConfig.DeepCopyInto(&out.ClientConfig) - if in.Rules != nil { - in, out := &in.Rules, &out.Rules - *out = make([]RuleWithOperations, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.FailurePolicy != nil { - in, out := &in.FailurePolicy, &out.FailurePolicy - if *in == nil { - *out = nil - } else { - *out = new(FailurePolicyType) - **out = **in - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalAdmissionHook. -func (in *ExternalAdmissionHook) DeepCopy() *ExternalAdmissionHook { - if in == nil { - return nil - } - out := new(ExternalAdmissionHook) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExternalAdmissionHookConfiguration) DeepCopyInto(out *ExternalAdmissionHookConfiguration) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.ExternalAdmissionHooks != nil { - in, out := &in.ExternalAdmissionHooks, &out.ExternalAdmissionHooks - *out = make([]ExternalAdmissionHook, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalAdmissionHookConfiguration. -func (in *ExternalAdmissionHookConfiguration) DeepCopy() *ExternalAdmissionHookConfiguration { - if in == nil { - return nil - } - out := new(ExternalAdmissionHookConfiguration) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ExternalAdmissionHookConfiguration) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExternalAdmissionHookConfigurationList) DeepCopyInto(out *ExternalAdmissionHookConfigurationList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ExternalAdmissionHookConfiguration, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalAdmissionHookConfigurationList. -func (in *ExternalAdmissionHookConfigurationList) DeepCopy() *ExternalAdmissionHookConfigurationList { - if in == nil { - return nil - } - out := new(ExternalAdmissionHookConfigurationList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ExternalAdmissionHookConfigurationList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Initializer) DeepCopyInto(out *Initializer) { *out = *in @@ -323,41 +145,3 @@ func (in *Rule) DeepCopy() *Rule { in.DeepCopyInto(out) return out } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RuleWithOperations) DeepCopyInto(out *RuleWithOperations) { - *out = *in - if in.Operations != nil { - in, out := &in.Operations, &out.Operations - *out = make([]OperationType, len(*in)) - copy(*out, *in) - } - in.Rule.DeepCopyInto(&out.Rule) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuleWithOperations. -func (in *RuleWithOperations) DeepCopy() *RuleWithOperations { - if in == nil { - return nil - } - out := new(RuleWithOperations) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServiceReference) DeepCopyInto(out *ServiceReference) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceReference. -func (in *ServiceReference) DeepCopy() *ServiceReference { - if in == nil { - return nil - } - out := new(ServiceReference) - in.DeepCopyInto(out) - return out -} diff --git a/staging/src/k8s.io/api/admissionregistration/v1beta1/BUILD b/staging/src/k8s.io/api/admissionregistration/v1beta1/BUILD new file mode 100644 index 00000000000..dbcfadd2bfb --- /dev/null +++ b/staging/src/k8s.io/api/admissionregistration/v1beta1/BUILD @@ -0,0 +1,44 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "generated.pb.go", + "register.go", + "types.go", + "types_swagger_doc_generated.go", + "zz_generated.deepcopy.go", + ], + importpath = "k8s.io/api/admissionregistration/v1beta1", + deps = [ + "//vendor/github.com/gogo/protobuf/proto:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) + +filegroup( + name = "go_default_library_protos", + srcs = ["generated.proto"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/api/admissionregistration/v1beta1/doc.go b/staging/src/k8s.io/api/admissionregistration/v1beta1/doc.go new file mode 100644 index 00000000000..afbb3d6d3ad --- /dev/null +++ b/staging/src/k8s.io/api/admissionregistration/v1beta1/doc.go @@ -0,0 +1,25 @@ +/* +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:openapi-gen=true + +// Package v1beta1 is the v1beta1 version of the API. +// AdmissionConfiguration and AdmissionPluginConfiguration are legacy static admission plugin configuration +// InitializerConfiguration and validatingWebhookConfiguration is for the +// new dynamic admission controller configuration. +// +groupName=admissionregistration.k8s.io +package v1beta1 // import "k8s.io/api/admissionregistration/v1beta1" diff --git a/staging/src/k8s.io/api/admissionregistration/v1beta1/generated.pb.go b/staging/src/k8s.io/api/admissionregistration/v1beta1/generated.pb.go new file mode 100644 index 00000000000..51e5762a871 --- /dev/null +++ b/staging/src/k8s.io/api/admissionregistration/v1beta1/generated.pb.go @@ -0,0 +1,2150 @@ +/* +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. +*/ + +// Code generated by protoc-gen-gogo. +// source: k8s.io/kubernetes/vendor/k8s.io/api/admissionregistration/v1beta1/generated.proto +// DO NOT EDIT! + +/* + Package v1beta1 is a generated protocol buffer package. + + It is generated from these files: + k8s.io/kubernetes/vendor/k8s.io/api/admissionregistration/v1beta1/generated.proto + + It has these top-level messages: + MutatingWebhookConfiguration + MutatingWebhookConfigurationList + Rule + RuleWithOperations + ServiceReference + ValidatingWebhookConfiguration + ValidatingWebhookConfigurationList + Webhook + WebhookClientConfig +*/ +package v1beta1 + +import proto "github.com/gogo/protobuf/proto" +import fmt "fmt" +import math "math" + +import k8s_io_apimachinery_pkg_apis_meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +import strings "strings" +import reflect "reflect" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package + +func (m *MutatingWebhookConfiguration) Reset() { *m = MutatingWebhookConfiguration{} } +func (*MutatingWebhookConfiguration) ProtoMessage() {} +func (*MutatingWebhookConfiguration) Descriptor() ([]byte, []int) { + return fileDescriptorGenerated, []int{0} +} + +func (m *MutatingWebhookConfigurationList) Reset() { *m = MutatingWebhookConfigurationList{} } +func (*MutatingWebhookConfigurationList) ProtoMessage() {} +func (*MutatingWebhookConfigurationList) Descriptor() ([]byte, []int) { + return fileDescriptorGenerated, []int{1} +} + +func (m *Rule) Reset() { *m = Rule{} } +func (*Rule) ProtoMessage() {} +func (*Rule) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{2} } + +func (m *RuleWithOperations) Reset() { *m = RuleWithOperations{} } +func (*RuleWithOperations) ProtoMessage() {} +func (*RuleWithOperations) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{3} } + +func (m *ServiceReference) Reset() { *m = ServiceReference{} } +func (*ServiceReference) ProtoMessage() {} +func (*ServiceReference) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{4} } + +func (m *ValidatingWebhookConfiguration) Reset() { *m = ValidatingWebhookConfiguration{} } +func (*ValidatingWebhookConfiguration) ProtoMessage() {} +func (*ValidatingWebhookConfiguration) Descriptor() ([]byte, []int) { + return fileDescriptorGenerated, []int{5} +} + +func (m *ValidatingWebhookConfigurationList) Reset() { *m = ValidatingWebhookConfigurationList{} } +func (*ValidatingWebhookConfigurationList) ProtoMessage() {} +func (*ValidatingWebhookConfigurationList) Descriptor() ([]byte, []int) { + return fileDescriptorGenerated, []int{6} +} + +func (m *Webhook) Reset() { *m = Webhook{} } +func (*Webhook) ProtoMessage() {} +func (*Webhook) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{7} } + +func (m *WebhookClientConfig) Reset() { *m = WebhookClientConfig{} } +func (*WebhookClientConfig) ProtoMessage() {} +func (*WebhookClientConfig) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{8} } + +func init() { + proto.RegisterType((*MutatingWebhookConfiguration)(nil), "k8s.io.api.admissionregistration.v1beta1.MutatingWebhookConfiguration") + proto.RegisterType((*MutatingWebhookConfigurationList)(nil), "k8s.io.api.admissionregistration.v1beta1.MutatingWebhookConfigurationList") + proto.RegisterType((*Rule)(nil), "k8s.io.api.admissionregistration.v1beta1.Rule") + proto.RegisterType((*RuleWithOperations)(nil), "k8s.io.api.admissionregistration.v1beta1.RuleWithOperations") + proto.RegisterType((*ServiceReference)(nil), "k8s.io.api.admissionregistration.v1beta1.ServiceReference") + proto.RegisterType((*ValidatingWebhookConfiguration)(nil), "k8s.io.api.admissionregistration.v1beta1.ValidatingWebhookConfiguration") + proto.RegisterType((*ValidatingWebhookConfigurationList)(nil), "k8s.io.api.admissionregistration.v1beta1.ValidatingWebhookConfigurationList") + proto.RegisterType((*Webhook)(nil), "k8s.io.api.admissionregistration.v1beta1.Webhook") + proto.RegisterType((*WebhookClientConfig)(nil), "k8s.io.api.admissionregistration.v1beta1.WebhookClientConfig") +} +func (m *MutatingWebhookConfiguration) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MutatingWebhookConfiguration) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) + n1, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + if len(m.Webhooks) > 0 { + for _, msg := range m.Webhooks { + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *MutatingWebhookConfigurationList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MutatingWebhookConfigurationList) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) + n2, err := m.ListMeta.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n2 + if len(m.Items) > 0 { + for _, msg := range m.Items { + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *Rule) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Rule) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.APIGroups) > 0 { + for _, s := range m.APIGroups { + dAtA[i] = 0xa + i++ + l = len(s) + for l >= 1<<7 { + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + dAtA[i] = uint8(l) + i++ + i += copy(dAtA[i:], s) + } + } + if len(m.APIVersions) > 0 { + for _, s := range m.APIVersions { + dAtA[i] = 0x12 + i++ + l = len(s) + for l >= 1<<7 { + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + dAtA[i] = uint8(l) + i++ + i += copy(dAtA[i:], s) + } + } + if len(m.Resources) > 0 { + for _, s := range m.Resources { + dAtA[i] = 0x1a + i++ + l = len(s) + for l >= 1<<7 { + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + dAtA[i] = uint8(l) + i++ + i += copy(dAtA[i:], s) + } + } + return i, nil +} + +func (m *RuleWithOperations) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RuleWithOperations) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Operations) > 0 { + for _, s := range m.Operations { + dAtA[i] = 0xa + i++ + l = len(s) + for l >= 1<<7 { + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + dAtA[i] = uint8(l) + i++ + i += copy(dAtA[i:], s) + } + } + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Rule.Size())) + n3, err := m.Rule.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n3 + return i, nil +} + +func (m *ServiceReference) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ServiceReference) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Namespace))) + i += copy(dAtA[i:], m.Namespace) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i += copy(dAtA[i:], m.Name) + if m.Path != nil { + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.Path))) + i += copy(dAtA[i:], *m.Path) + } + return i, nil +} + +func (m *ValidatingWebhookConfiguration) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ValidatingWebhookConfiguration) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) + n4, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n4 + if len(m.Webhooks) > 0 { + for _, msg := range m.Webhooks { + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *ValidatingWebhookConfigurationList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ValidatingWebhookConfigurationList) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) + n5, err := m.ListMeta.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n5 + if len(m.Items) > 0 { + for _, msg := range m.Items { + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *Webhook) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Webhook) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i += copy(dAtA[i:], m.Name) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ClientConfig.Size())) + n6, err := m.ClientConfig.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n6 + if len(m.Rules) > 0 { + for _, msg := range m.Rules { + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + if m.FailurePolicy != nil { + dAtA[i] = 0x22 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.FailurePolicy))) + i += copy(dAtA[i:], *m.FailurePolicy) + } + if m.NamespaceSelector != nil { + dAtA[i] = 0x2a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.NamespaceSelector.Size())) + n7, err := m.NamespaceSelector.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n7 + } + return i, nil +} + +func (m *WebhookClientConfig) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *WebhookClientConfig) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Service != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Service.Size())) + n8, err := m.Service.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n8 + } + if m.CABundle != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.CABundle))) + i += copy(dAtA[i:], m.CABundle) + } + if m.URL != nil { + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.URL))) + i += copy(dAtA[i:], *m.URL) + } + return i, nil +} + +func encodeFixed64Generated(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Generated(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *MutatingWebhookConfiguration) Size() (n int) { + 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 *MutatingWebhookConfigurationList) Size() (n int) { + 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 *Rule) Size() (n int) { + 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.Resources) > 0 { + for _, s := range m.Resources { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *RuleWithOperations) Size() (n int) { + var l int + _ = l + if len(m.Operations) > 0 { + for _, s := range m.Operations { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + l = m.Rule.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *ServiceReference) Size() (n int) { + 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)) + } + return n +} + +func (m *ValidatingWebhookConfiguration) Size() (n int) { + 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) { + 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 *Webhook) Size() (n int) { + 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)) + } + return n +} + +func (m *WebhookClientConfig) Size() (n int) { + 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) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozGenerated(x uint64) (n int) { + return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (this *MutatingWebhookConfiguration) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&MutatingWebhookConfiguration{`, + `ObjectMeta:` + strings.Replace(strings.Replace(this.ObjectMeta.String(), "ObjectMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Webhooks:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Webhooks), "Webhook", "Webhook", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *MutatingWebhookConfigurationList) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&MutatingWebhookConfigurationList{`, + `ListMeta:` + strings.Replace(strings.Replace(this.ListMeta.String(), "ListMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Items), "MutatingWebhookConfiguration", "MutatingWebhookConfiguration", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + 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) + `,`, + `}`, + }, "") + 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) + `,`, + `}`, + }, "") + return s +} +func (this *ValidatingWebhookConfiguration) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ValidatingWebhookConfiguration{`, + `ObjectMeta:` + strings.Replace(strings.Replace(this.ObjectMeta.String(), "ObjectMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Webhooks:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Webhooks), "Webhook", "Webhook", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *ValidatingWebhookConfigurationList) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ValidatingWebhookConfigurationList{`, + `ListMeta:` + strings.Replace(strings.Replace(this.ListMeta.String(), "ListMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Items), "ValidatingWebhookConfiguration", "ValidatingWebhookConfiguration", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *Webhook) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&Webhook{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `ClientConfig:` + strings.Replace(strings.Replace(this.ClientConfig.String(), "WebhookClientConfig", "WebhookClientConfig", 1), `&`, ``, 1) + `,`, + `Rules:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Rules), "RuleWithOperations", "RuleWithOperations", 1), `&`, ``, 1) + `,`, + `FailurePolicy:` + valueToStringGenerated(this.FailurePolicy) + `,`, + `NamespaceSelector:` + strings.Replace(fmt.Sprintf("%v", this.NamespaceSelector), "LabelSelector", "k8s_io_apimachinery_pkg_apis_meta_v1.LabelSelector", 1) + `,`, + `}`, + }, "") + return s +} +func (this *WebhookClientConfig) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&WebhookClientConfig{`, + `Service:` + strings.Replace(fmt.Sprintf("%v", this.Service), "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 *MutatingWebhookConfiguration) 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: MutatingWebhookConfiguration: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MutatingWebhookConfiguration: 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 > 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 Webhooks", 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 > l { + return io.ErrUnexpectedEOF + } + m.Webhooks = append(m.Webhooks, Webhook{}) + if err := m.Webhooks[len(m.Webhooks)-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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MutatingWebhookConfigurationList) 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: MutatingWebhookConfigurationList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MutatingWebhookConfigurationList: 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 > 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 > l { + return io.ErrUnexpectedEOF + } + m.Items = append(m.Items, MutatingWebhookConfiguration{}) + 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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Rule) 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: Rule: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Rule: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field APIGroups", 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 > l { + return io.ErrUnexpectedEOF + } + m.APIGroups = append(m.APIGroups, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field APIVersions", 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 > l { + return io.ErrUnexpectedEOF + } + m.APIVersions = append(m.APIVersions, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Resources", 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 > l { + return io.ErrUnexpectedEOF + } + m.Resources = append(m.Resources, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *RuleWithOperations) 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: RuleWithOperations: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RuleWithOperations: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Operations", 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 > l { + return io.ErrUnexpectedEOF + } + m.Operations = append(m.Operations, OperationType(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Rule", 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.Rule.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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ServiceReference) 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: ServiceReference: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ServiceReference: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Namespace", 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 > l { + return io.ErrUnexpectedEOF + } + m.Namespace = 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 > 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 Path", 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 > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.Path = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ValidatingWebhookConfiguration) 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: ValidatingWebhookConfiguration: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ValidatingWebhookConfiguration: 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 > 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 Webhooks", 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 > l { + return io.ErrUnexpectedEOF + } + m.Webhooks = append(m.Webhooks, Webhook{}) + if err := m.Webhooks[len(m.Webhooks)-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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ValidatingWebhookConfigurationList) 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: ValidatingWebhookConfigurationList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ValidatingWebhookConfigurationList: 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 > 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 > l { + return io.ErrUnexpectedEOF + } + m.Items = append(m.Items, ValidatingWebhookConfiguration{}) + 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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Webhook) 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: Webhook: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Webhook: 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 > 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 ClientConfig", 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.ClientConfig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Rules", 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 > l { + return io.ErrUnexpectedEOF + } + m.Rules = append(m.Rules, RuleWithOperations{}) + if err := m.Rules[len(m.Rules)-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 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 > l { + return io.ErrUnexpectedEOF + } + s := FailurePolicyType(dAtA[iNdEx:postIndex]) + m.FailurePolicy = &s + iNdEx = postIndex + case 5: + 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 > l { + return io.ErrUnexpectedEOF + } + if m.NamespaceSelector == nil { + m.NamespaceSelector = &k8s_io_apimachinery_pkg_apis_meta_v1.LabelSelector{} + } + if err := m.NamespaceSelector.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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *WebhookClientConfig) 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: WebhookClientConfig: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: WebhookClientConfig: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Service", 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 > l { + return io.ErrUnexpectedEOF + } + if m.Service == nil { + m.Service = &ServiceReference{} + } + if err := m.Service.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CABundle", 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 > l { + return io.ErrUnexpectedEOF + } + m.CABundle = append(m.CABundle[:0], dAtA[iNdEx:postIndex]...) + if m.CABundle == nil { + m.CABundle = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field URL", 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 > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.URL = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if 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 + 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 + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + 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 + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthGenerated + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipGenerated(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") +) + +func init() { + proto.RegisterFile("k8s.io/kubernetes/vendor/k8s.io/api/admissionregistration/v1beta1/generated.proto", fileDescriptorGenerated) +} + +var fileDescriptorGenerated = []byte{ + // 916 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x54, 0xcf, 0x6f, 0x1b, 0x45, + 0x14, 0xf6, 0xd6, 0x8e, 0x6c, 0x8f, 0x6d, 0xd1, 0x0c, 0x20, 0x99, 0xa8, 0xda, 0xb5, 0x7c, 0x40, + 0x96, 0x50, 0x76, 0x71, 0x8a, 0x10, 0x42, 0x20, 0x94, 0x8d, 0x54, 0x88, 0x94, 0xb4, 0x66, 0x02, + 0xad, 0x84, 0x38, 0x30, 0x5e, 0xbf, 0xd8, 0x83, 0xf7, 0x97, 0x66, 0x66, 0xdd, 0xe6, 0x86, 0xc4, + 0x3f, 0x80, 0xc4, 0x1f, 0xc1, 0x5f, 0xc1, 0x3d, 0x37, 0x7a, 0x41, 0xf4, 0xb4, 0x22, 0xcb, 0x99, + 0x03, 0xd7, 0x9e, 0xd0, 0xce, 0xae, 0xbd, 0x76, 0x1c, 0xa7, 0xee, 0x85, 0x03, 0x37, 0xcf, 0xf7, + 0xde, 0xf7, 0xbd, 0xf7, 0x3d, 0xbf, 0xb7, 0xe8, 0xcb, 0xe9, 0x47, 0xc2, 0x64, 0x81, 0x35, 0x8d, + 0x86, 0xc0, 0x7d, 0x90, 0x20, 0xac, 0x19, 0xf8, 0xa3, 0x80, 0x5b, 0x79, 0x80, 0x86, 0xcc, 0xa2, + 0x23, 0x8f, 0x09, 0xc1, 0x02, 0x9f, 0xc3, 0x98, 0x09, 0xc9, 0xa9, 0x64, 0x81, 0x6f, 0xcd, 0xfa, + 0x43, 0x90, 0xb4, 0x6f, 0x8d, 0xc1, 0x07, 0x4e, 0x25, 0x8c, 0xcc, 0x90, 0x07, 0x32, 0xc0, 0xbd, + 0x8c, 0x69, 0xd2, 0x90, 0x99, 0x37, 0x32, 0xcd, 0x9c, 0xb9, 0xb7, 0x3f, 0x66, 0x72, 0x12, 0x0d, + 0x4d, 0x27, 0xf0, 0xac, 0x71, 0x30, 0x0e, 0x2c, 0x25, 0x30, 0x8c, 0xce, 0xd5, 0x4b, 0x3d, 0xd4, + 0xaf, 0x4c, 0x78, 0xaf, 0xbb, 0xd4, 0x92, 0x13, 0x70, 0xb0, 0x66, 0x6b, 0xc5, 0xf7, 0x4e, 0x8b, + 0x1c, 0x78, 0x26, 0xc1, 0x4f, 0x6b, 0x8b, 0x7d, 0x1a, 0x32, 0x01, 0x7c, 0x06, 0xdc, 0x0a, 0xa7, + 0xe3, 0x34, 0x26, 0x56, 0x13, 0x36, 0x79, 0xd9, 0xfb, 0xa0, 0x90, 0xf3, 0xa8, 0x33, 0x61, 0x3e, + 0xf0, 0x8b, 0x42, 0xc3, 0x03, 0x49, 0x6f, 0x6a, 0xc2, 0xda, 0xc4, 0xe2, 0x91, 0x2f, 0x99, 0x07, + 0x6b, 0x84, 0x0f, 0x5f, 0x45, 0x10, 0xce, 0x04, 0x3c, 0xba, 0xc6, 0xbb, 0xbf, 0x89, 0x17, 0x49, + 0xe6, 0x5a, 0xcc, 0x97, 0x42, 0xf2, 0xeb, 0xa4, 0xee, 0xef, 0x1a, 0xba, 0x77, 0x1a, 0x49, 0x2a, + 0x99, 0x3f, 0x7e, 0x02, 0xc3, 0x49, 0x10, 0x4c, 0x8f, 0x02, 0xff, 0x9c, 0x8d, 0xa3, 0xec, 0xef, + 0xc1, 0xdf, 0xa1, 0x5a, 0xea, 0x6c, 0x44, 0x25, 0x6d, 0x6b, 0x1d, 0xad, 0xd7, 0x38, 0x78, 0xdf, + 0x2c, 0xfe, 0xd3, 0x45, 0x21, 0x33, 0x9c, 0x8e, 0x53, 0x40, 0x98, 0x69, 0xb6, 0x39, 0xeb, 0x9b, + 0x8f, 0x86, 0xdf, 0x83, 0x23, 0x4f, 0x41, 0x52, 0x1b, 0x5f, 0xc6, 0x46, 0x29, 0x89, 0x0d, 0x54, + 0x60, 0x64, 0xa1, 0x8a, 0xcf, 0x50, 0x2d, 0xaf, 0x2c, 0xda, 0x77, 0x3a, 0xe5, 0x5e, 0xe3, 0xa0, + 0x6f, 0x6e, 0xbb, 0x35, 0x66, 0xce, 0xb4, 0x2b, 0x69, 0x09, 0x52, 0x7b, 0x9a, 0x0b, 0x75, 0xff, + 0xd6, 0x50, 0xe7, 0x36, 0x5f, 0x27, 0x4c, 0x48, 0xfc, 0xed, 0x9a, 0x37, 0x73, 0x3b, 0x6f, 0x29, + 0x5b, 0x39, 0xbb, 0x9b, 0x3b, 0xab, 0xcd, 0x91, 0x25, 0x5f, 0x53, 0xb4, 0xc3, 0x24, 0x78, 0x73, + 0x53, 0x0f, 0xb6, 0x37, 0x75, 0x5b, 0xe3, 0x76, 0x2b, 0x2f, 0xb9, 0x73, 0x9c, 0x8a, 0x93, 0xac, + 0x46, 0xf7, 0x67, 0x0d, 0x55, 0x48, 0xe4, 0x02, 0x7e, 0x0f, 0xd5, 0x69, 0xc8, 0x3e, 0xe7, 0x41, + 0x14, 0x8a, 0xb6, 0xd6, 0x29, 0xf7, 0xea, 0x76, 0x2b, 0x89, 0x8d, 0xfa, 0xe1, 0xe0, 0x38, 0x03, + 0x49, 0x11, 0xc7, 0x7d, 0xd4, 0xa0, 0x21, 0x7b, 0x0c, 0x5c, 0x2d, 0xbe, 0x6a, 0xb4, 0x6e, 0xbf, + 0x91, 0xc4, 0x46, 0xe3, 0x70, 0x70, 0x3c, 0x87, 0xc9, 0x72, 0x4e, 0xaa, 0xcf, 0x41, 0x04, 0x11, + 0x77, 0x40, 0xb4, 0xcb, 0x85, 0x3e, 0x99, 0x83, 0xa4, 0x88, 0x77, 0x7f, 0xd1, 0x10, 0x4e, 0xbb, + 0x7a, 0xc2, 0xe4, 0xe4, 0x51, 0x08, 0x99, 0x03, 0x81, 0x3f, 0x43, 0x28, 0x58, 0xbc, 0xf2, 0x26, + 0x0d, 0xb5, 0x1f, 0x0b, 0xf4, 0x65, 0x6c, 0xb4, 0x16, 0xaf, 0xaf, 0x2e, 0x42, 0x20, 0x4b, 0x14, + 0x3c, 0x40, 0x15, 0x1e, 0xb9, 0xd0, 0xbe, 0xb3, 0xf6, 0xa7, 0xbd, 0x62, 0xb2, 0x69, 0x33, 0x76, + 0x33, 0x9f, 0xa0, 0x1a, 0x18, 0x51, 0x4a, 0xdd, 0x1f, 0x35, 0x74, 0xf7, 0x0c, 0xf8, 0x8c, 0x39, + 0x40, 0xe0, 0x1c, 0x38, 0xf8, 0x0e, 0x60, 0x0b, 0xd5, 0x7d, 0xea, 0x81, 0x08, 0xa9, 0x03, 0x6a, + 0x41, 0xea, 0xf6, 0x6e, 0xce, 0xad, 0x3f, 0x9c, 0x07, 0x48, 0x91, 0x83, 0x3b, 0xa8, 0x92, 0x3e, + 0x54, 0x5f, 0xf5, 0xa2, 0x4e, 0x9a, 0x4b, 0x54, 0x04, 0xdf, 0x43, 0x95, 0x90, 0xca, 0x49, 0xbb, + 0xac, 0x32, 0x6a, 0x69, 0x74, 0x40, 0xe5, 0x84, 0x28, 0xb4, 0xfb, 0x87, 0x86, 0xf4, 0xc7, 0xd4, + 0x65, 0xa3, 0xff, 0xdd, 0x3d, 0xfe, 0xa3, 0xa1, 0xee, 0xed, 0xce, 0xfe, 0x83, 0x8b, 0xf4, 0x56, + 0x2f, 0xf2, 0x8b, 0xed, 0x6d, 0xdd, 0xde, 0xfa, 0x86, 0x9b, 0xfc, 0xad, 0x8c, 0xaa, 0x79, 0xfa, + 0x62, 0x33, 0xb4, 0x8d, 0x9b, 0xf1, 0x14, 0x35, 0x1d, 0x97, 0x81, 0x2f, 0x33, 0xe9, 0x7c, 0xb7, + 0x3f, 0x7d, 0xed, 0xd1, 0x1f, 0x2d, 0x89, 0xd8, 0x6f, 0xe5, 0x85, 0x9a, 0xcb, 0x28, 0x59, 0x29, + 0x84, 0x29, 0xda, 0x49, 0x4f, 0x20, 0xbb, 0xe6, 0xc6, 0xc1, 0x27, 0xaf, 0x77, 0x4d, 0xab, 0xa7, + 0x5d, 0x4c, 0x22, 0x8d, 0x09, 0x92, 0x29, 0xe3, 0x13, 0xd4, 0x3a, 0xa7, 0xcc, 0x8d, 0x38, 0x0c, + 0x02, 0x97, 0x39, 0x17, 0xed, 0x8a, 0x1a, 0xc3, 0xbb, 0x49, 0x6c, 0xb4, 0x1e, 0x2c, 0x07, 0x5e, + 0xc6, 0xc6, 0xee, 0x0a, 0xa0, 0x4e, 0x7f, 0x95, 0x8c, 0x9f, 0xa1, 0xdd, 0xc5, 0xc9, 0x9d, 0x81, + 0x0b, 0x8e, 0x0c, 0x78, 0x7b, 0x47, 0x8d, 0xeb, 0xfe, 0x96, 0xdb, 0x42, 0x87, 0xe0, 0xce, 0xa9, + 0xf6, 0xdb, 0x49, 0x6c, 0xec, 0x3e, 0xbc, 0xae, 0x48, 0xd6, 0x8b, 0x74, 0x7f, 0xd5, 0xd0, 0x9b, + 0x37, 0x8c, 0x19, 0x53, 0x54, 0x15, 0xd9, 0xc7, 0x23, 0xdf, 0xda, 0x8f, 0xb7, 0x1f, 0xe2, 0xf5, + 0xaf, 0x8e, 0xdd, 0x48, 0x62, 0xa3, 0x3a, 0x47, 0xe7, 0xba, 0xb8, 0x87, 0x6a, 0x0e, 0xb5, 0x23, + 0x7f, 0x94, 0x7f, 0xf6, 0x9a, 0x76, 0x33, 0xdd, 0xf2, 0xa3, 0xc3, 0x0c, 0x23, 0x8b, 0x28, 0x7e, + 0x07, 0x95, 0x23, 0xee, 0xe6, 0x5f, 0x98, 0x6a, 0x12, 0x1b, 0xe5, 0xaf, 0xc9, 0x09, 0x49, 0x31, + 0x7b, 0xff, 0xf2, 0x4a, 0x2f, 0x3d, 0xbf, 0xd2, 0x4b, 0x2f, 0xae, 0xf4, 0xd2, 0x0f, 0x89, 0xae, + 0x5d, 0x26, 0xba, 0xf6, 0x3c, 0xd1, 0xb5, 0x17, 0x89, 0xae, 0xfd, 0x99, 0xe8, 0xda, 0x4f, 0x7f, + 0xe9, 0xa5, 0x6f, 0xaa, 0x79, 0x6b, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0x78, 0x57, 0x76, 0x28, + 0x10, 0x0a, 0x00, 0x00, +} diff --git a/staging/src/k8s.io/api/admissionregistration/v1beta1/generated.proto b/staging/src/k8s.io/api/admissionregistration/v1beta1/generated.proto new file mode 100644 index 00000000000..cb1270ea47d --- /dev/null +++ b/staging/src/k8s.io/api/admissionregistration/v1beta1/generated.proto @@ -0,0 +1,261 @@ +/* +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. +*/ + + +// This file was autogenerated by go-to-protobuf. Do not edit it manually! + +syntax = 'proto2'; + +package k8s.io.api.admissionregistration.v1beta1; + +import "k8s.io/api/core/v1/generated.proto"; +import "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/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"; +import "k8s.io/apimachinery/pkg/util/intstr/generated.proto"; + +// Package-wide variables from generator "generated". +option go_package = "v1beta1"; + +// 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/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. + // +optional + // +patchMergeKey=name + // +patchStrategy=merge + repeated Webhook Webhooks = 2; +} + +// MutatingWebhookConfigurationList is a list of MutatingWebhookConfiguration. +message MutatingWebhookConfigurationList { + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; + + // List of MutatingWebhookConfiguration. + repeated MutatingWebhookConfiguration items = 2; +} + +// 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. + // If '*' is present, the length of the slice must be one. + // Required. + repeated string apiGroups = 1; + + // APIVersions is the API versions the resources belong to. '*' is all versions. + // If '*' is present, the length of the slice must be one. + // Required. + repeated string apiVersions = 2; + + // Resources is a list of resources this rule applies to. + // + // For 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. + // + // If wildcard is present, the validation rule will ensure resources do not + // overlap with each other. + // + // Depending on the enclosing object, subresources might not be allowed. + // Required. + repeated string resources = 3; +} + +// 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, or * + // for all operations. + // If '*' is present, the length of the slice must be one. + // Required. + repeated string operations = 1; + + // Rule is embedded, it describes other criteria of the rule, like + // APIGroups, APIVersions, Resources, etc. + optional Rule rule = 2; +} + +// ServiceReference holds a reference to Service.legacy.k8s.io +message ServiceReference { + // `namespace` is the namespace of the service. + // Required + optional string namespace = 1; + + // `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 + // this service. + // +optional + optional string path = 3; +} + +// 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/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. + // +optional + // +patchMergeKey=name + // +patchStrategy=merge + repeated Webhook Webhooks = 2; +} + +// ValidatingWebhookConfigurationList is a list of ValidatingWebhookConfiguration. +message ValidatingWebhookConfigurationList { + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; + + // List of ValidatingWebhookConfiguration. + repeated ValidatingWebhookConfiguration items = 2; +} + +// Webhook describes an admission webhook and the resources and operations it applies to. +message Webhook { + // 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. + // Required + optional WebhookClientConfig clientConfig = 2; + + // Rules describes what operations on what resources/subresources the webhook cares about. + // The webhook cares about an operation if it matches _any_ Rule. + repeated RuleWithOperations rules = 3; + + // 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; + + // 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 other cluster scoped resource, + // it is not subjected to the webhook. + // + // For 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": { + // "matchExpressions": [ + // { + // "key": "runlevel", + // "operator": "NotIn", + // "values": [ + // "0", + // "1" + // ] + // } + // ] + // } + // + // If 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": { + // "matchExpressions": [ + // { + // "key": "environment", + // "operator": "In", + // "values": [ + // "prod", + // "staging" + // ] + // } + // ] + // } + // + // See + // https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + // for more examples of label selectors. + // + // Default to the empty LabelSelector, which matches everything. + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector namespaceSelector = 5; +} + +// 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 + // (`[scheme://]host:port/path`). Exactly one of `url` or `service` + // must be specified. + // + // The `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. + // + // Please 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. + // + // The scheme must be "https"; the URL must begin with "https://". + // + // A 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. + // + // Attempting to use a user or basic auth e.g. "user:password@" is not + // allowed. Fragments ("#...") and query parameters ("?...") are not + // allowed, either. + // + // +optional + optional string url = 3; + + // `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`. + // + // If there is only one port open for the service, that port will be + // used. If there are multiple ports open, port 443 will be used if it + // is open, otherwise it is an error. + // + // +optional + optional ServiceReference service = 1; + + // `caBundle` is a PEM encoded CA bundle which will be used to validate + // the webhook's server certificate. + // Required. + optional bytes caBundle = 2; +} + diff --git a/staging/src/k8s.io/api/admissionregistration/v1beta1/register.go b/staging/src/k8s.io/api/admissionregistration/v1beta1/register.go new file mode 100644 index 00000000000..d126da9fb71 --- /dev/null +++ b/staging/src/k8s.io/api/admissionregistration/v1beta1/register.go @@ -0,0 +1,53 @@ +/* +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 v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +const GroupName = "admissionregistration.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"} + +// 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 scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &ValidatingWebhookConfiguration{}, + &ValidatingWebhookConfigurationList{}, + &MutatingWebhookConfiguration{}, + &MutatingWebhookConfigurationList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/staging/src/k8s.io/api/admissionregistration/v1beta1/types.go b/staging/src/k8s.io/api/admissionregistration/v1beta1/types.go new file mode 100644 index 00000000000..30d2750ce33 --- /dev/null +++ b/staging/src/k8s.io/api/admissionregistration/v1beta1/types.go @@ -0,0 +1,281 @@ +/* +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 v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// 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. + // If '*' is present, the length of the slice must be one. + // Required. + APIGroups []string `json:"apiGroups,omitempty" protobuf:"bytes,1,rep,name=apiGroups"` + + // 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" protobuf:"bytes,2,rep,name=apiVersions"` + + // Resources is a list of resources this rule applies to. + // + // For 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. + // + // If wildcard is present, the validation rule will ensure resources do not + // overlap with each other. + // + // Depending on the enclosing object, subresources might not be allowed. + // Required. + Resources []string `json:"resources,omitempty" protobuf:"bytes,3,rep,name=resources"` +} + +type FailurePolicyType string + +const ( + // Ignore means the initializer is removed from the initializers list of an + // object if the initializer is timed out. + Ignore FailurePolicyType = "Ignore" + // For 1.7, only "Ignore" is allowed. "Fail" will be allowed when the + // extensible admission feature is beta. + Fail FailurePolicyType = "Fail" +) + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// 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/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. + // +optional + // +patchMergeKey=name + // +patchStrategy=merge + Webhooks []Webhook `json:"webhooks,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,2,rep,name=Webhooks"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ValidatingWebhookConfigurationList is a list of ValidatingWebhookConfiguration. +type ValidatingWebhookConfigurationList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + // List of ValidatingWebhookConfiguration. + Items []ValidatingWebhookConfiguration `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// 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/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. + // +optional + // +patchMergeKey=name + // +patchStrategy=merge + Webhooks []Webhook `json:"webhooks,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,2,rep,name=Webhooks"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// MutatingWebhookConfigurationList is a list of MutatingWebhookConfiguration. +type MutatingWebhookConfigurationList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + // List of MutatingWebhookConfiguration. + Items []MutatingWebhookConfiguration `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +// Webhook describes an admission webhook and the resources and operations it applies to. +type Webhook struct { + // 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. + // Required + ClientConfig WebhookClientConfig `json:"clientConfig" protobuf:"bytes,2,opt,name=clientConfig"` + + // Rules describes what operations on what resources/subresources the webhook cares about. + // The webhook cares about an operation if it matches _any_ Rule. + Rules []RuleWithOperations `json:"rules,omitempty" protobuf:"bytes,3,rep,name=rules"` + + // 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"` + + // 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 other cluster scoped resource, + // it is not subjected to the webhook. + // + // For 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": { + // "matchExpressions": [ + // { + // "key": "runlevel", + // "operator": "NotIn", + // "values": [ + // "0", + // "1" + // ] + // } + // ] + // } + // + // If 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": { + // "matchExpressions": [ + // { + // "key": "environment", + // "operator": "In", + // "values": [ + // "prod", + // "staging" + // ] + // } + // ] + // } + // + // See + // https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + // for more examples of label selectors. + // + // Default to the empty LabelSelector, which matches everything. + // +optional + NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty" protobuf:"bytes,5,opt,name=namespaceSelector"` +} + +// 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, or * + // for all operations. + // If '*' is present, the length of the slice must be one. + // Required. + Operations []OperationType `json:"operations,omitempty" protobuf:"bytes,1,rep,name=operations,casttype=OperationType"` + // Rule is embedded, it describes other criteria of the rule, like + // APIGroups, APIVersions, Resources, etc. + Rule `json:",inline" protobuf:"bytes,2,opt,name=rule"` +} + +type OperationType string + +// The constants should be kept in sync with those defined in k8s.io/kubernetes/pkg/admission/interface.go. +const ( + OperationAll OperationType = "*" + Create OperationType = "CREATE" + Update OperationType = "UPDATE" + Delete OperationType = "DELETE" + Connect OperationType = "CONNECT" +) + +// 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 + // (`[scheme://]host:port/path`). Exactly one of `url` or `service` + // must be specified. + // + // The `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. + // + // Please 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. + // + // The scheme must be "https"; the URL must begin with "https://". + // + // A 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. + // + // Attempting to use a user or basic auth e.g. "user:password@" is not + // allowed. Fragments ("#...") and query parameters ("?...") are not + // allowed, either. + // + // +optional + URL *string `json:"url,omitempty" protobuf:"bytes,3,opt,name=url"` + + // `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`. + // + // If there is only one port open for the service, that port will be + // used. If there are multiple ports open, port 443 will be used if it + // is open, otherwise it is an error. + // + // +optional + Service *ServiceReference `json:"service" protobuf:"bytes,1,opt,name=service"` + + // `caBundle` is a PEM encoded CA bundle which will be used to validate + // the webhook's server certificate. + // Required. + CABundle []byte `json:"caBundle" protobuf:"bytes,2,opt,name=caBundle"` +} + +// ServiceReference holds a reference to Service.legacy.k8s.io +type ServiceReference struct { + // `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. + // 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 + // this service. + // +optional + Path *string `json:"path,omitempty" protobuf:"bytes,3,opt,name=path"` +} 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 new file mode 100644 index 00000000000..ea8c1e37f25 --- /dev/null +++ b/staging/src/k8s.io/api/admissionregistration/v1beta1/types_swagger_doc_generated.go @@ -0,0 +1,125 @@ +/* +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 v1beta1 + +// 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-generated-swagger-docs.sh + +// AUTO-GENERATED FUNCTIONS START HERE +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/api-conventions.md#metadata.", + "webhooks": "Webhooks is a list of webhooks and the affected resources and operations.", +} + +func (MutatingWebhookConfiguration) SwaggerDoc() map[string]string { + return map_MutatingWebhookConfiguration +} + +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/api-conventions.md#types-kinds", + "items": "List of MutatingWebhookConfiguration.", +} + +func (MutatingWebhookConfigurationList) SwaggerDoc() map[string]string { + return map_MutatingWebhookConfigurationList +} + +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.", +} + +func (Rule) SwaggerDoc() map[string]string { + return map_Rule +} + +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, or * for all operations. If '*' is present, the length of the slice must be one. Required.", +} + +func (RuleWithOperations) SwaggerDoc() map[string]string { + return map_RuleWithOperations +} + +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.", +} + +func (ServiceReference) SwaggerDoc() map[string]string { + return map_ServiceReference +} + +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/api-conventions.md#metadata.", + "webhooks": "Webhooks is a list of webhooks and the affected resources and operations.", +} + +func (ValidatingWebhookConfiguration) SwaggerDoc() map[string]string { + return map_ValidatingWebhookConfiguration +} + +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/api-conventions.md#types-kinds", + "items": "List of ValidatingWebhookConfiguration.", +} + +func (ValidatingWebhookConfigurationList) SwaggerDoc() map[string]string { + return map_ValidatingWebhookConfigurationList +} + +var map_Webhook = map[string]string{ + "": "Webhook 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.", + "failurePolicy": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Ignore.", + "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 other cluster scoped resource, it is not subjected to 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.", +} + +func (Webhook) SwaggerDoc() map[string]string { + return map_Webhook +} + +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`.\n\nIf there is only one port open for the service, that port will be used. If there are multiple ports open, port 443 will be used if it is open, otherwise it is an error.", + "caBundle": "`caBundle` is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. Required.", +} + +func (WebhookClientConfig) SwaggerDoc() map[string]string { + return map_WebhookClientConfig +} + +// AUTO-GENERATED FUNCTIONS END HERE diff --git a/staging/src/k8s.io/api/admissionregistration/v1beta1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/admissionregistration/v1beta1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..fb0cf9c477f --- /dev/null +++ b/staging/src/k8s.io/api/admissionregistration/v1beta1/zz_generated.deepcopy.go @@ -0,0 +1,321 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +package v1beta1 + +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 *MutatingWebhookConfiguration) DeepCopyInto(out *MutatingWebhookConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.Webhooks != nil { + in, out := &in.Webhooks, &out.Webhooks + *out = make([]Webhook, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MutatingWebhookConfiguration. +func (in *MutatingWebhookConfiguration) DeepCopy() *MutatingWebhookConfiguration { + if in == nil { + return nil + } + out := new(MutatingWebhookConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MutatingWebhookConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MutatingWebhookConfigurationList) DeepCopyInto(out *MutatingWebhookConfigurationList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]MutatingWebhookConfiguration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MutatingWebhookConfigurationList. +func (in *MutatingWebhookConfigurationList) DeepCopy() *MutatingWebhookConfigurationList { + if in == nil { + return nil + } + out := new(MutatingWebhookConfigurationList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MutatingWebhookConfigurationList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Rule) DeepCopyInto(out *Rule) { + *out = *in + if in.APIGroups != nil { + in, out := &in.APIGroups, &out.APIGroups + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.APIVersions != nil { + in, out := &in.APIVersions, &out.APIVersions + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Rule. +func (in *Rule) DeepCopy() *Rule { + if in == nil { + return nil + } + out := new(Rule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RuleWithOperations) DeepCopyInto(out *RuleWithOperations) { + *out = *in + if in.Operations != nil { + in, out := &in.Operations, &out.Operations + *out = make([]OperationType, len(*in)) + copy(*out, *in) + } + in.Rule.DeepCopyInto(&out.Rule) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuleWithOperations. +func (in *RuleWithOperations) DeepCopy() *RuleWithOperations { + if in == nil { + return nil + } + out := new(RuleWithOperations) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceReference) DeepCopyInto(out *ServiceReference) { + *out = *in + if in.Path != nil { + in, out := &in.Path, &out.Path + if *in == nil { + *out = nil + } else { + *out = new(string) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceReference. +func (in *ServiceReference) DeepCopy() *ServiceReference { + if in == nil { + return nil + } + out := new(ServiceReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ValidatingWebhookConfiguration) DeepCopyInto(out *ValidatingWebhookConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.Webhooks != nil { + in, out := &in.Webhooks, &out.Webhooks + *out = make([]Webhook, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidatingWebhookConfiguration. +func (in *ValidatingWebhookConfiguration) DeepCopy() *ValidatingWebhookConfiguration { + if in == nil { + return nil + } + out := new(ValidatingWebhookConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ValidatingWebhookConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ValidatingWebhookConfigurationList) DeepCopyInto(out *ValidatingWebhookConfigurationList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ValidatingWebhookConfiguration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidatingWebhookConfigurationList. +func (in *ValidatingWebhookConfigurationList) DeepCopy() *ValidatingWebhookConfigurationList { + if in == nil { + return nil + } + out := new(ValidatingWebhookConfigurationList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ValidatingWebhookConfigurationList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Webhook) DeepCopyInto(out *Webhook) { + *out = *in + in.ClientConfig.DeepCopyInto(&out.ClientConfig) + if in.Rules != nil { + in, out := &in.Rules, &out.Rules + *out = make([]RuleWithOperations, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.FailurePolicy != nil { + in, out := &in.FailurePolicy, &out.FailurePolicy + if *in == nil { + *out = nil + } else { + *out = new(FailurePolicyType) + **out = **in + } + } + if in.NamespaceSelector != nil { + in, out := &in.NamespaceSelector, &out.NamespaceSelector + if *in == nil { + *out = nil + } else { + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Webhook. +func (in *Webhook) DeepCopy() *Webhook { + if in == nil { + return nil + } + out := new(Webhook) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WebhookClientConfig) DeepCopyInto(out *WebhookClientConfig) { + *out = *in + if in.URL != nil { + in, out := &in.URL, &out.URL + if *in == nil { + *out = nil + } else { + *out = new(string) + **out = **in + } + } + if in.Service != nil { + in, out := &in.Service, &out.Service + if *in == nil { + *out = nil + } else { + *out = new(ServiceReference) + (*in).DeepCopyInto(*out) + } + } + if in.CABundle != nil { + in, out := &in.CABundle, &out.CABundle + *out = make([]byte, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookClientConfig. +func (in *WebhookClientConfig) DeepCopy() *WebhookClientConfig { + if in == nil { + return nil + } + out := new(WebhookClientConfig) + in.DeepCopyInto(out) + return out +} diff --git a/staging/src/k8s.io/api/apps/v1/BUILD b/staging/src/k8s.io/api/apps/v1/BUILD index c51aabf5fb8..7902387f59b 100644 --- a/staging/src/k8s.io/api/apps/v1/BUILD +++ b/staging/src/k8s.io/api/apps/v1/BUILD @@ -16,7 +16,6 @@ go_library( "//vendor/github.com/gogo/protobuf/proto:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", diff --git a/staging/src/k8s.io/api/apps/v1/doc.go b/staging/src/k8s.io/api/apps/v1/doc.go index 62eb80cee34..1d66c22232b 100644 --- a/staging/src/k8s.io/api/apps/v1/doc.go +++ b/staging/src/k8s.io/api/apps/v1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:openapi-gen=true package v1 // import "k8s.io/api/apps/v1" diff --git a/staging/src/k8s.io/api/apps/v1/generated.pb.go b/staging/src/k8s.io/api/apps/v1/generated.pb.go index f32e878f8e4..02123859bf7 100644 --- a/staging/src/k8s.io/api/apps/v1/generated.pb.go +++ b/staging/src/k8s.io/api/apps/v1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -25,12 +25,34 @@ limitations under the License. k8s.io/kubernetes/vendor/k8s.io/api/apps/v1/generated.proto It has these top-level messages: + ControllerRevision + ControllerRevisionList DaemonSet + DaemonSetCondition DaemonSetList DaemonSetSpec DaemonSetStatus DaemonSetUpdateStrategy + Deployment + DeploymentCondition + DeploymentList + DeploymentSpec + DeploymentStatus + DeploymentStrategy + ReplicaSet + ReplicaSetCondition + ReplicaSetList + ReplicaSetSpec + ReplicaSetStatus RollingUpdateDaemonSet + RollingUpdateDeployment + RollingUpdateStatefulSetStrategy + StatefulSet + StatefulSetCondition + StatefulSetList + StatefulSetSpec + StatefulSetStatus + StatefulSetUpdateStrategy */ package v1 @@ -38,6 +60,8 @@ import proto "github.com/gogo/protobuf/proto" import fmt "fmt" import math "math" +import k8s_io_api_core_v1 "k8s.io/api/core/v1" + import k8s_io_apimachinery_pkg_apis_meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" import k8s_io_apimachinery_pkg_util_intstr "k8s.io/apimachinery/pkg/util/intstr" @@ -58,38 +82,229 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package +func (m *ControllerRevision) Reset() { *m = ControllerRevision{} } +func (*ControllerRevision) ProtoMessage() {} +func (*ControllerRevision) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{0} } + +func (m *ControllerRevisionList) Reset() { *m = ControllerRevisionList{} } +func (*ControllerRevisionList) ProtoMessage() {} +func (*ControllerRevisionList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{1} } + func (m *DaemonSet) Reset() { *m = DaemonSet{} } func (*DaemonSet) ProtoMessage() {} -func (*DaemonSet) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{0} } +func (*DaemonSet) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{2} } + +func (m *DaemonSetCondition) Reset() { *m = DaemonSetCondition{} } +func (*DaemonSetCondition) ProtoMessage() {} +func (*DaemonSetCondition) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{3} } func (m *DaemonSetList) Reset() { *m = DaemonSetList{} } func (*DaemonSetList) ProtoMessage() {} -func (*DaemonSetList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{1} } +func (*DaemonSetList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{4} } func (m *DaemonSetSpec) Reset() { *m = DaemonSetSpec{} } func (*DaemonSetSpec) ProtoMessage() {} -func (*DaemonSetSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{2} } +func (*DaemonSetSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{5} } func (m *DaemonSetStatus) Reset() { *m = DaemonSetStatus{} } func (*DaemonSetStatus) ProtoMessage() {} -func (*DaemonSetStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{3} } +func (*DaemonSetStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{6} } func (m *DaemonSetUpdateStrategy) Reset() { *m = DaemonSetUpdateStrategy{} } func (*DaemonSetUpdateStrategy) ProtoMessage() {} -func (*DaemonSetUpdateStrategy) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{4} } +func (*DaemonSetUpdateStrategy) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{7} } + +func (m *Deployment) Reset() { *m = Deployment{} } +func (*Deployment) ProtoMessage() {} +func (*Deployment) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{8} } + +func (m *DeploymentCondition) Reset() { *m = DeploymentCondition{} } +func (*DeploymentCondition) ProtoMessage() {} +func (*DeploymentCondition) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{9} } + +func (m *DeploymentList) Reset() { *m = DeploymentList{} } +func (*DeploymentList) ProtoMessage() {} +func (*DeploymentList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{10} } + +func (m *DeploymentSpec) Reset() { *m = DeploymentSpec{} } +func (*DeploymentSpec) ProtoMessage() {} +func (*DeploymentSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{11} } + +func (m *DeploymentStatus) Reset() { *m = DeploymentStatus{} } +func (*DeploymentStatus) ProtoMessage() {} +func (*DeploymentStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{12} } + +func (m *DeploymentStrategy) Reset() { *m = DeploymentStrategy{} } +func (*DeploymentStrategy) ProtoMessage() {} +func (*DeploymentStrategy) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{13} } + +func (m *ReplicaSet) Reset() { *m = ReplicaSet{} } +func (*ReplicaSet) ProtoMessage() {} +func (*ReplicaSet) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{14} } + +func (m *ReplicaSetCondition) Reset() { *m = ReplicaSetCondition{} } +func (*ReplicaSetCondition) ProtoMessage() {} +func (*ReplicaSetCondition) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{15} } + +func (m *ReplicaSetList) Reset() { *m = ReplicaSetList{} } +func (*ReplicaSetList) ProtoMessage() {} +func (*ReplicaSetList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{16} } + +func (m *ReplicaSetSpec) Reset() { *m = ReplicaSetSpec{} } +func (*ReplicaSetSpec) ProtoMessage() {} +func (*ReplicaSetSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{17} } + +func (m *ReplicaSetStatus) Reset() { *m = ReplicaSetStatus{} } +func (*ReplicaSetStatus) ProtoMessage() {} +func (*ReplicaSetStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{18} } func (m *RollingUpdateDaemonSet) Reset() { *m = RollingUpdateDaemonSet{} } func (*RollingUpdateDaemonSet) ProtoMessage() {} -func (*RollingUpdateDaemonSet) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{5} } +func (*RollingUpdateDaemonSet) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{19} } + +func (m *RollingUpdateDeployment) Reset() { *m = RollingUpdateDeployment{} } +func (*RollingUpdateDeployment) ProtoMessage() {} +func (*RollingUpdateDeployment) Descriptor() ([]byte, []int) { + return fileDescriptorGenerated, []int{20} +} + +func (m *RollingUpdateStatefulSetStrategy) Reset() { *m = RollingUpdateStatefulSetStrategy{} } +func (*RollingUpdateStatefulSetStrategy) ProtoMessage() {} +func (*RollingUpdateStatefulSetStrategy) Descriptor() ([]byte, []int) { + return fileDescriptorGenerated, []int{21} +} + +func (m *StatefulSet) Reset() { *m = StatefulSet{} } +func (*StatefulSet) ProtoMessage() {} +func (*StatefulSet) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{22} } + +func (m *StatefulSetCondition) Reset() { *m = StatefulSetCondition{} } +func (*StatefulSetCondition) ProtoMessage() {} +func (*StatefulSetCondition) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{23} } + +func (m *StatefulSetList) Reset() { *m = StatefulSetList{} } +func (*StatefulSetList) ProtoMessage() {} +func (*StatefulSetList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{24} } + +func (m *StatefulSetSpec) Reset() { *m = StatefulSetSpec{} } +func (*StatefulSetSpec) ProtoMessage() {} +func (*StatefulSetSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{25} } + +func (m *StatefulSetStatus) Reset() { *m = StatefulSetStatus{} } +func (*StatefulSetStatus) ProtoMessage() {} +func (*StatefulSetStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{26} } + +func (m *StatefulSetUpdateStrategy) Reset() { *m = StatefulSetUpdateStrategy{} } +func (*StatefulSetUpdateStrategy) ProtoMessage() {} +func (*StatefulSetUpdateStrategy) Descriptor() ([]byte, []int) { + return fileDescriptorGenerated, []int{27} +} func init() { + proto.RegisterType((*ControllerRevision)(nil), "k8s.io.api.apps.v1.ControllerRevision") + proto.RegisterType((*ControllerRevisionList)(nil), "k8s.io.api.apps.v1.ControllerRevisionList") proto.RegisterType((*DaemonSet)(nil), "k8s.io.api.apps.v1.DaemonSet") + proto.RegisterType((*DaemonSetCondition)(nil), "k8s.io.api.apps.v1.DaemonSetCondition") proto.RegisterType((*DaemonSetList)(nil), "k8s.io.api.apps.v1.DaemonSetList") proto.RegisterType((*DaemonSetSpec)(nil), "k8s.io.api.apps.v1.DaemonSetSpec") proto.RegisterType((*DaemonSetStatus)(nil), "k8s.io.api.apps.v1.DaemonSetStatus") proto.RegisterType((*DaemonSetUpdateStrategy)(nil), "k8s.io.api.apps.v1.DaemonSetUpdateStrategy") + proto.RegisterType((*Deployment)(nil), "k8s.io.api.apps.v1.Deployment") + proto.RegisterType((*DeploymentCondition)(nil), "k8s.io.api.apps.v1.DeploymentCondition") + proto.RegisterType((*DeploymentList)(nil), "k8s.io.api.apps.v1.DeploymentList") + proto.RegisterType((*DeploymentSpec)(nil), "k8s.io.api.apps.v1.DeploymentSpec") + proto.RegisterType((*DeploymentStatus)(nil), "k8s.io.api.apps.v1.DeploymentStatus") + proto.RegisterType((*DeploymentStrategy)(nil), "k8s.io.api.apps.v1.DeploymentStrategy") + proto.RegisterType((*ReplicaSet)(nil), "k8s.io.api.apps.v1.ReplicaSet") + proto.RegisterType((*ReplicaSetCondition)(nil), "k8s.io.api.apps.v1.ReplicaSetCondition") + proto.RegisterType((*ReplicaSetList)(nil), "k8s.io.api.apps.v1.ReplicaSetList") + proto.RegisterType((*ReplicaSetSpec)(nil), "k8s.io.api.apps.v1.ReplicaSetSpec") + proto.RegisterType((*ReplicaSetStatus)(nil), "k8s.io.api.apps.v1.ReplicaSetStatus") proto.RegisterType((*RollingUpdateDaemonSet)(nil), "k8s.io.api.apps.v1.RollingUpdateDaemonSet") + proto.RegisterType((*RollingUpdateDeployment)(nil), "k8s.io.api.apps.v1.RollingUpdateDeployment") + proto.RegisterType((*RollingUpdateStatefulSetStrategy)(nil), "k8s.io.api.apps.v1.RollingUpdateStatefulSetStrategy") + proto.RegisterType((*StatefulSet)(nil), "k8s.io.api.apps.v1.StatefulSet") + proto.RegisterType((*StatefulSetCondition)(nil), "k8s.io.api.apps.v1.StatefulSetCondition") + proto.RegisterType((*StatefulSetList)(nil), "k8s.io.api.apps.v1.StatefulSetList") + proto.RegisterType((*StatefulSetSpec)(nil), "k8s.io.api.apps.v1.StatefulSetSpec") + proto.RegisterType((*StatefulSetStatus)(nil), "k8s.io.api.apps.v1.StatefulSetStatus") + proto.RegisterType((*StatefulSetUpdateStrategy)(nil), "k8s.io.api.apps.v1.StatefulSetUpdateStrategy") } +func (m *ControllerRevision) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ControllerRevision) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) + n1, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Data.Size())) + n2, err := m.Data.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n2 + dAtA[i] = 0x18 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Revision)) + return i, nil +} + +func (m *ControllerRevisionList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ControllerRevisionList) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) + n3, err := m.ListMeta.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n3 + if len(m.Items) > 0 { + for _, msg := range m.Items { + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + func (m *DaemonSet) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -108,27 +323,69 @@ func (m *DaemonSet) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n1, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n4, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n1 + i += n4 dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) - n2, err := m.Spec.MarshalTo(dAtA[i:]) + n5, err := m.Spec.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n2 + i += n5 dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) - n3, err := m.Status.MarshalTo(dAtA[i:]) + n6, err := m.Status.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n3 + i += n6 + return i, nil +} + +func (m *DaemonSetCondition) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DaemonSetCondition) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) + i += copy(dAtA[i:], m.Type) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Status))) + i += copy(dAtA[i:], m.Status) + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.LastTransitionTime.Size())) + n7, err := m.LastTransitionTime.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n7 + dAtA[i] = 0x22 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) + i += copy(dAtA[i:], m.Reason) + dAtA[i] = 0x2a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) + i += copy(dAtA[i:], m.Message) return i, nil } @@ -150,11 +407,11 @@ func (m *DaemonSetList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n4, err := m.ListMeta.MarshalTo(dAtA[i:]) + n8, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n4 + i += n8 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -189,28 +446,28 @@ func (m *DaemonSetSpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Selector.Size())) - n5, err := m.Selector.MarshalTo(dAtA[i:]) + n9, err := m.Selector.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n5 + i += n9 } dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Template.Size())) - n6, err := m.Template.MarshalTo(dAtA[i:]) + n10, err := m.Template.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n6 + i += n10 dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.UpdateStrategy.Size())) - n7, err := m.UpdateStrategy.MarshalTo(dAtA[i:]) + n11, err := m.UpdateStrategy.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n7 + i += n11 dAtA[i] = 0x20 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.MinReadySeconds)) @@ -266,6 +523,18 @@ func (m *DaemonSetStatus) MarshalTo(dAtA []byte) (int, error) { i++ i = encodeVarintGenerated(dAtA, i, uint64(*m.CollisionCount)) } + if len(m.Conditions) > 0 { + for _, msg := range m.Conditions { + dAtA[i] = 0x52 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } return i, nil } @@ -292,11 +561,507 @@ func (m *DaemonSetUpdateStrategy) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.RollingUpdate.Size())) - n8, err := m.RollingUpdate.MarshalTo(dAtA[i:]) + n12, err := m.RollingUpdate.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n8 + i += n12 + } + return i, nil +} + +func (m *Deployment) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Deployment) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) + n13, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n13 + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) + n14, err := m.Spec.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n14 + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) + n15, err := m.Status.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n15 + return i, nil +} + +func (m *DeploymentCondition) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeploymentCondition) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) + i += copy(dAtA[i:], m.Type) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Status))) + i += copy(dAtA[i:], m.Status) + dAtA[i] = 0x22 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) + i += copy(dAtA[i:], m.Reason) + dAtA[i] = 0x2a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) + i += copy(dAtA[i:], m.Message) + dAtA[i] = 0x32 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.LastUpdateTime.Size())) + n16, err := m.LastUpdateTime.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n16 + dAtA[i] = 0x3a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.LastTransitionTime.Size())) + n17, err := m.LastTransitionTime.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n17 + return i, nil +} + +func (m *DeploymentList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeploymentList) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) + n18, err := m.ListMeta.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n18 + if len(m.Items) > 0 { + for _, msg := range m.Items { + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *DeploymentSpec) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeploymentSpec) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Replicas != nil { + dAtA[i] = 0x8 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(*m.Replicas)) + } + if m.Selector != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Selector.Size())) + n19, err := m.Selector.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n19 + } + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Template.Size())) + n20, err := m.Template.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n20 + dAtA[i] = 0x22 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Strategy.Size())) + n21, err := m.Strategy.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n21 + dAtA[i] = 0x28 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.MinReadySeconds)) + if m.RevisionHistoryLimit != nil { + dAtA[i] = 0x30 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(*m.RevisionHistoryLimit)) + } + dAtA[i] = 0x38 + i++ + if m.Paused { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + if m.ProgressDeadlineSeconds != nil { + dAtA[i] = 0x48 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(*m.ProgressDeadlineSeconds)) + } + return i, nil +} + +func (m *DeploymentStatus) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeploymentStatus) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0x8 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ObservedGeneration)) + dAtA[i] = 0x10 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Replicas)) + dAtA[i] = 0x18 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.UpdatedReplicas)) + dAtA[i] = 0x20 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.AvailableReplicas)) + dAtA[i] = 0x28 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.UnavailableReplicas)) + if len(m.Conditions) > 0 { + for _, msg := range m.Conditions { + dAtA[i] = 0x32 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + dAtA[i] = 0x38 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ReadyReplicas)) + if m.CollisionCount != nil { + dAtA[i] = 0x40 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(*m.CollisionCount)) + } + return i, nil +} + +func (m *DeploymentStrategy) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeploymentStrategy) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) + i += copy(dAtA[i:], m.Type) + if m.RollingUpdate != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.RollingUpdate.Size())) + n22, err := m.RollingUpdate.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n22 + } + return i, nil +} + +func (m *ReplicaSet) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ReplicaSet) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) + n23, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n23 + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) + n24, err := m.Spec.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n24 + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) + n25, err := m.Status.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n25 + return i, nil +} + +func (m *ReplicaSetCondition) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ReplicaSetCondition) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) + i += copy(dAtA[i:], m.Type) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Status))) + i += copy(dAtA[i:], m.Status) + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.LastTransitionTime.Size())) + n26, err := m.LastTransitionTime.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n26 + dAtA[i] = 0x22 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) + i += copy(dAtA[i:], m.Reason) + dAtA[i] = 0x2a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) + i += copy(dAtA[i:], m.Message) + return i, nil +} + +func (m *ReplicaSetList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ReplicaSetList) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) + n27, err := m.ListMeta.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n27 + if len(m.Items) > 0 { + for _, msg := range m.Items { + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *ReplicaSetSpec) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ReplicaSetSpec) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Replicas != nil { + dAtA[i] = 0x8 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(*m.Replicas)) + } + if m.Selector != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Selector.Size())) + n28, err := m.Selector.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n28 + } + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Template.Size())) + n29, err := m.Template.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n29 + dAtA[i] = 0x20 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.MinReadySeconds)) + return i, nil +} + +func (m *ReplicaSetStatus) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ReplicaSetStatus) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0x8 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Replicas)) + dAtA[i] = 0x10 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.FullyLabeledReplicas)) + dAtA[i] = 0x18 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ObservedGeneration)) + dAtA[i] = 0x20 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ReadyReplicas)) + dAtA[i] = 0x28 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.AvailableReplicas)) + if len(m.Conditions) > 0 { + for _, msg := range m.Conditions { + dAtA[i] = 0x32 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } } return i, nil } @@ -320,11 +1085,358 @@ func (m *RollingUpdateDaemonSet) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.MaxUnavailable.Size())) - n9, err := m.MaxUnavailable.MarshalTo(dAtA[i:]) + n30, err := m.MaxUnavailable.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n9 + i += n30 + } + return i, nil +} + +func (m *RollingUpdateDeployment) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RollingUpdateDeployment) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.MaxUnavailable != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.MaxUnavailable.Size())) + n31, err := m.MaxUnavailable.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n31 + } + if m.MaxSurge != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.MaxSurge.Size())) + n32, err := m.MaxSurge.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n32 + } + return i, nil +} + +func (m *RollingUpdateStatefulSetStrategy) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RollingUpdateStatefulSetStrategy) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Partition != nil { + dAtA[i] = 0x8 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(*m.Partition)) + } + return i, nil +} + +func (m *StatefulSet) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *StatefulSet) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) + n33, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n33 + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) + n34, err := m.Spec.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n34 + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) + n35, err := m.Status.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n35 + return i, nil +} + +func (m *StatefulSetCondition) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *StatefulSetCondition) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) + i += copy(dAtA[i:], m.Type) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Status))) + i += copy(dAtA[i:], m.Status) + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.LastTransitionTime.Size())) + n36, err := m.LastTransitionTime.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n36 + dAtA[i] = 0x22 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) + i += copy(dAtA[i:], m.Reason) + dAtA[i] = 0x2a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) + i += copy(dAtA[i:], m.Message) + return i, nil +} + +func (m *StatefulSetList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *StatefulSetList) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) + n37, err := m.ListMeta.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n37 + if len(m.Items) > 0 { + for _, msg := range m.Items { + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *StatefulSetSpec) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *StatefulSetSpec) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Replicas != nil { + dAtA[i] = 0x8 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(*m.Replicas)) + } + if m.Selector != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Selector.Size())) + n38, err := m.Selector.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n38 + } + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Template.Size())) + n39, err := m.Template.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n39 + if len(m.VolumeClaimTemplates) > 0 { + for _, msg := range m.VolumeClaimTemplates { + dAtA[i] = 0x22 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + dAtA[i] = 0x2a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ServiceName))) + i += copy(dAtA[i:], m.ServiceName) + dAtA[i] = 0x32 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.PodManagementPolicy))) + i += copy(dAtA[i:], m.PodManagementPolicy) + dAtA[i] = 0x3a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.UpdateStrategy.Size())) + n40, err := m.UpdateStrategy.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n40 + if m.RevisionHistoryLimit != nil { + dAtA[i] = 0x40 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(*m.RevisionHistoryLimit)) + } + return i, nil +} + +func (m *StatefulSetStatus) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *StatefulSetStatus) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0x8 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ObservedGeneration)) + dAtA[i] = 0x10 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Replicas)) + dAtA[i] = 0x18 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ReadyReplicas)) + dAtA[i] = 0x20 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.CurrentReplicas)) + dAtA[i] = 0x28 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.UpdatedReplicas)) + dAtA[i] = 0x32 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.CurrentRevision))) + i += copy(dAtA[i:], m.CurrentRevision) + dAtA[i] = 0x3a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.UpdateRevision))) + i += copy(dAtA[i:], m.UpdateRevision) + if m.CollisionCount != nil { + dAtA[i] = 0x48 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(*m.CollisionCount)) + } + if len(m.Conditions) > 0 { + for _, msg := range m.Conditions { + dAtA[i] = 0x52 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *StatefulSetUpdateStrategy) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *StatefulSetUpdateStrategy) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) + i += copy(dAtA[i:], m.Type) + if m.RollingUpdate != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.RollingUpdate.Size())) + n41, err := m.RollingUpdate.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n41 } return i, nil } @@ -356,6 +1468,31 @@ func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return offset + 1 } +func (m *ControllerRevision) Size() (n int) { + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Data.Size() + n += 1 + l + sovGenerated(uint64(l)) + n += 1 + sovGenerated(uint64(m.Revision)) + return n +} + +func (m *ControllerRevisionList) Size() (n int) { + 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 *DaemonSet) Size() (n int) { var l int _ = l @@ -368,6 +1505,22 @@ func (m *DaemonSet) Size() (n int) { return n } +func (m *DaemonSetCondition) Size() (n int) { + 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 *DaemonSetList) Size() (n int) { var l int _ = l @@ -414,6 +1567,12 @@ func (m *DaemonSetStatus) Size() (n int) { if m.CollisionCount != nil { n += 1 + sovGenerated(uint64(*m.CollisionCount)) } + if len(m.Conditions) > 0 { + for _, e := range m.Conditions { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } return n } @@ -429,6 +1588,183 @@ func (m *DaemonSetUpdateStrategy) Size() (n int) { return n } +func (m *Deployment) Size() (n int) { + 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 *DeploymentCondition) Size() (n int) { + 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 = len(m.Reason) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Message) + n += 1 + l + sovGenerated(uint64(l)) + l = m.LastUpdateTime.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.LastTransitionTime.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *DeploymentList) Size() (n int) { + 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 *DeploymentSpec) Size() (n int) { + var l int + _ = l + if m.Replicas != nil { + n += 1 + sovGenerated(uint64(*m.Replicas)) + } + if m.Selector != nil { + l = m.Selector.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + l = m.Template.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Strategy.Size() + n += 1 + l + sovGenerated(uint64(l)) + n += 1 + sovGenerated(uint64(m.MinReadySeconds)) + if m.RevisionHistoryLimit != nil { + n += 1 + sovGenerated(uint64(*m.RevisionHistoryLimit)) + } + n += 2 + if m.ProgressDeadlineSeconds != nil { + n += 1 + sovGenerated(uint64(*m.ProgressDeadlineSeconds)) + } + return n +} + +func (m *DeploymentStatus) Size() (n int) { + var l int + _ = l + n += 1 + sovGenerated(uint64(m.ObservedGeneration)) + n += 1 + sovGenerated(uint64(m.Replicas)) + n += 1 + sovGenerated(uint64(m.UpdatedReplicas)) + n += 1 + sovGenerated(uint64(m.AvailableReplicas)) + n += 1 + sovGenerated(uint64(m.UnavailableReplicas)) + if len(m.Conditions) > 0 { + for _, e := range m.Conditions { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + n += 1 + sovGenerated(uint64(m.ReadyReplicas)) + if m.CollisionCount != nil { + n += 1 + sovGenerated(uint64(*m.CollisionCount)) + } + return n +} + +func (m *DeploymentStrategy) Size() (n int) { + var l int + _ = l + l = len(m.Type) + n += 1 + l + sovGenerated(uint64(l)) + if m.RollingUpdate != nil { + l = m.RollingUpdate.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (m *ReplicaSet) Size() (n int) { + 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 *ReplicaSetCondition) Size() (n int) { + 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 *ReplicaSetList) Size() (n int) { + 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 *ReplicaSetSpec) Size() (n int) { + var l int + _ = l + if m.Replicas != nil { + n += 1 + sovGenerated(uint64(*m.Replicas)) + } + if m.Selector != nil { + l = m.Selector.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + l = m.Template.Size() + n += 1 + l + sovGenerated(uint64(l)) + n += 1 + sovGenerated(uint64(m.MinReadySeconds)) + return n +} + +func (m *ReplicaSetStatus) Size() (n int) { + var l int + _ = l + n += 1 + sovGenerated(uint64(m.Replicas)) + n += 1 + sovGenerated(uint64(m.FullyLabeledReplicas)) + n += 1 + sovGenerated(uint64(m.ObservedGeneration)) + n += 1 + sovGenerated(uint64(m.ReadyReplicas)) + n += 1 + sovGenerated(uint64(m.AvailableReplicas)) + if len(m.Conditions) > 0 { + for _, e := range m.Conditions { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + func (m *RollingUpdateDaemonSet) Size() (n int) { var l int _ = l @@ -439,6 +1775,137 @@ func (m *RollingUpdateDaemonSet) Size() (n int) { return n } +func (m *RollingUpdateDeployment) Size() (n int) { + var l int + _ = l + if m.MaxUnavailable != nil { + l = m.MaxUnavailable.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.MaxSurge != nil { + l = m.MaxSurge.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (m *RollingUpdateStatefulSetStrategy) Size() (n int) { + var l int + _ = l + if m.Partition != nil { + n += 1 + sovGenerated(uint64(*m.Partition)) + } + return n +} + +func (m *StatefulSet) Size() (n int) { + 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 *StatefulSetCondition) Size() (n int) { + 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 *StatefulSetList) Size() (n int) { + 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 *StatefulSetSpec) Size() (n int) { + var l int + _ = l + if m.Replicas != nil { + n += 1 + sovGenerated(uint64(*m.Replicas)) + } + if m.Selector != nil { + l = m.Selector.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + l = m.Template.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.VolumeClaimTemplates) > 0 { + for _, e := range m.VolumeClaimTemplates { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + l = len(m.ServiceName) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.PodManagementPolicy) + n += 1 + l + sovGenerated(uint64(l)) + l = m.UpdateStrategy.Size() + n += 1 + l + sovGenerated(uint64(l)) + if m.RevisionHistoryLimit != nil { + n += 1 + sovGenerated(uint64(*m.RevisionHistoryLimit)) + } + return n +} + +func (m *StatefulSetStatus) Size() (n int) { + var l int + _ = l + n += 1 + sovGenerated(uint64(m.ObservedGeneration)) + n += 1 + sovGenerated(uint64(m.Replicas)) + n += 1 + sovGenerated(uint64(m.ReadyReplicas)) + n += 1 + sovGenerated(uint64(m.CurrentReplicas)) + n += 1 + sovGenerated(uint64(m.UpdatedReplicas)) + l = len(m.CurrentRevision) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.UpdateRevision) + n += 1 + l + sovGenerated(uint64(l)) + if m.CollisionCount != nil { + n += 1 + sovGenerated(uint64(*m.CollisionCount)) + } + if len(m.Conditions) > 0 { + for _, e := range m.Conditions { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *StatefulSetUpdateStrategy) Size() (n int) { + var l int + _ = l + l = len(m.Type) + n += 1 + l + sovGenerated(uint64(l)) + if m.RollingUpdate != nil { + l = m.RollingUpdate.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + func sovGenerated(x uint64) (n int) { for { n++ @@ -452,6 +1919,29 @@ func sovGenerated(x uint64) (n int) { func sozGenerated(x uint64) (n int) { return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } +func (this *ControllerRevision) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ControllerRevision{`, + `ObjectMeta:` + strings.Replace(strings.Replace(this.ObjectMeta.String(), "ObjectMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Data:` + strings.Replace(strings.Replace(this.Data.String(), "RawExtension", "k8s_io_apimachinery_pkg_runtime.RawExtension", 1), `&`, ``, 1) + `,`, + `Revision:` + fmt.Sprintf("%v", this.Revision) + `,`, + `}`, + }, "") + return s +} +func (this *ControllerRevisionList) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ControllerRevisionList{`, + `ListMeta:` + strings.Replace(strings.Replace(this.ListMeta.String(), "ListMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Items), "ControllerRevision", "ControllerRevision", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} func (this *DaemonSet) String() string { if this == nil { return "nil" @@ -464,6 +1954,20 @@ func (this *DaemonSet) String() string { }, "") return s } +func (this *DaemonSetCondition) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&DaemonSetCondition{`, + `Type:` + fmt.Sprintf("%v", this.Type) + `,`, + `Status:` + fmt.Sprintf("%v", this.Status) + `,`, + `LastTransitionTime:` + strings.Replace(strings.Replace(this.LastTransitionTime.String(), "Time", "k8s_io_apimachinery_pkg_apis_meta_v1.Time", 1), `&`, ``, 1) + `,`, + `Reason:` + fmt.Sprintf("%v", this.Reason) + `,`, + `Message:` + fmt.Sprintf("%v", this.Message) + `,`, + `}`, + }, "") + return s +} func (this *DaemonSetList) String() string { if this == nil { return "nil" @@ -503,6 +2007,7 @@ func (this *DaemonSetStatus) String() string { `NumberAvailable:` + fmt.Sprintf("%v", this.NumberAvailable) + `,`, `NumberUnavailable:` + fmt.Sprintf("%v", this.NumberUnavailable) + `,`, `CollisionCount:` + valueToStringGenerated(this.CollisionCount) + `,`, + `Conditions:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Conditions), "DaemonSetCondition", "DaemonSetCondition", 1), `&`, ``, 1) + `,`, `}`, }, "") return s @@ -518,6 +2023,154 @@ func (this *DaemonSetUpdateStrategy) String() string { }, "") return s } +func (this *Deployment) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&Deployment{`, + `ObjectMeta:` + strings.Replace(strings.Replace(this.ObjectMeta.String(), "ObjectMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "DeploymentSpec", "DeploymentSpec", 1), `&`, ``, 1) + `,`, + `Status:` + strings.Replace(strings.Replace(this.Status.String(), "DeploymentStatus", "DeploymentStatus", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *DeploymentCondition) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&DeploymentCondition{`, + `Type:` + fmt.Sprintf("%v", this.Type) + `,`, + `Status:` + fmt.Sprintf("%v", this.Status) + `,`, + `Reason:` + fmt.Sprintf("%v", this.Reason) + `,`, + `Message:` + fmt.Sprintf("%v", this.Message) + `,`, + `LastUpdateTime:` + strings.Replace(strings.Replace(this.LastUpdateTime.String(), "Time", "k8s_io_apimachinery_pkg_apis_meta_v1.Time", 1), `&`, ``, 1) + `,`, + `LastTransitionTime:` + strings.Replace(strings.Replace(this.LastTransitionTime.String(), "Time", "k8s_io_apimachinery_pkg_apis_meta_v1.Time", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *DeploymentList) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&DeploymentList{`, + `ListMeta:` + strings.Replace(strings.Replace(this.ListMeta.String(), "ListMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Items), "Deployment", "Deployment", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *DeploymentSpec) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&DeploymentSpec{`, + `Replicas:` + valueToStringGenerated(this.Replicas) + `,`, + `Selector:` + strings.Replace(fmt.Sprintf("%v", this.Selector), "LabelSelector", "k8s_io_apimachinery_pkg_apis_meta_v1.LabelSelector", 1) + `,`, + `Template:` + strings.Replace(strings.Replace(this.Template.String(), "PodTemplateSpec", "k8s_io_api_core_v1.PodTemplateSpec", 1), `&`, ``, 1) + `,`, + `Strategy:` + strings.Replace(strings.Replace(this.Strategy.String(), "DeploymentStrategy", "DeploymentStrategy", 1), `&`, ``, 1) + `,`, + `MinReadySeconds:` + fmt.Sprintf("%v", this.MinReadySeconds) + `,`, + `RevisionHistoryLimit:` + valueToStringGenerated(this.RevisionHistoryLimit) + `,`, + `Paused:` + fmt.Sprintf("%v", this.Paused) + `,`, + `ProgressDeadlineSeconds:` + valueToStringGenerated(this.ProgressDeadlineSeconds) + `,`, + `}`, + }, "") + return s +} +func (this *DeploymentStatus) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&DeploymentStatus{`, + `ObservedGeneration:` + fmt.Sprintf("%v", this.ObservedGeneration) + `,`, + `Replicas:` + fmt.Sprintf("%v", this.Replicas) + `,`, + `UpdatedReplicas:` + fmt.Sprintf("%v", this.UpdatedReplicas) + `,`, + `AvailableReplicas:` + fmt.Sprintf("%v", this.AvailableReplicas) + `,`, + `UnavailableReplicas:` + fmt.Sprintf("%v", this.UnavailableReplicas) + `,`, + `Conditions:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Conditions), "DeploymentCondition", "DeploymentCondition", 1), `&`, ``, 1) + `,`, + `ReadyReplicas:` + fmt.Sprintf("%v", this.ReadyReplicas) + `,`, + `CollisionCount:` + valueToStringGenerated(this.CollisionCount) + `,`, + `}`, + }, "") + return s +} +func (this *DeploymentStrategy) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&DeploymentStrategy{`, + `Type:` + fmt.Sprintf("%v", this.Type) + `,`, + `RollingUpdate:` + strings.Replace(fmt.Sprintf("%v", this.RollingUpdate), "RollingUpdateDeployment", "RollingUpdateDeployment", 1) + `,`, + `}`, + }, "") + return s +} +func (this *ReplicaSet) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ReplicaSet{`, + `ObjectMeta:` + strings.Replace(strings.Replace(this.ObjectMeta.String(), "ObjectMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "ReplicaSetSpec", "ReplicaSetSpec", 1), `&`, ``, 1) + `,`, + `Status:` + strings.Replace(strings.Replace(this.Status.String(), "ReplicaSetStatus", "ReplicaSetStatus", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *ReplicaSetCondition) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ReplicaSetCondition{`, + `Type:` + fmt.Sprintf("%v", this.Type) + `,`, + `Status:` + fmt.Sprintf("%v", this.Status) + `,`, + `LastTransitionTime:` + strings.Replace(strings.Replace(this.LastTransitionTime.String(), "Time", "k8s_io_apimachinery_pkg_apis_meta_v1.Time", 1), `&`, ``, 1) + `,`, + `Reason:` + fmt.Sprintf("%v", this.Reason) + `,`, + `Message:` + fmt.Sprintf("%v", this.Message) + `,`, + `}`, + }, "") + return s +} +func (this *ReplicaSetList) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ReplicaSetList{`, + `ListMeta:` + strings.Replace(strings.Replace(this.ListMeta.String(), "ListMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Items), "ReplicaSet", "ReplicaSet", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *ReplicaSetSpec) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ReplicaSetSpec{`, + `Replicas:` + valueToStringGenerated(this.Replicas) + `,`, + `Selector:` + strings.Replace(fmt.Sprintf("%v", this.Selector), "LabelSelector", "k8s_io_apimachinery_pkg_apis_meta_v1.LabelSelector", 1) + `,`, + `Template:` + strings.Replace(strings.Replace(this.Template.String(), "PodTemplateSpec", "k8s_io_api_core_v1.PodTemplateSpec", 1), `&`, ``, 1) + `,`, + `MinReadySeconds:` + fmt.Sprintf("%v", this.MinReadySeconds) + `,`, + `}`, + }, "") + return s +} +func (this *ReplicaSetStatus) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ReplicaSetStatus{`, + `Replicas:` + fmt.Sprintf("%v", this.Replicas) + `,`, + `FullyLabeledReplicas:` + fmt.Sprintf("%v", this.FullyLabeledReplicas) + `,`, + `ObservedGeneration:` + fmt.Sprintf("%v", this.ObservedGeneration) + `,`, + `ReadyReplicas:` + fmt.Sprintf("%v", this.ReadyReplicas) + `,`, + `AvailableReplicas:` + fmt.Sprintf("%v", this.AvailableReplicas) + `,`, + `Conditions:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Conditions), "ReplicaSetCondition", "ReplicaSetCondition", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} func (this *RollingUpdateDaemonSet) String() string { if this == nil { return "nil" @@ -528,6 +2181,110 @@ func (this *RollingUpdateDaemonSet) String() string { }, "") return s } +func (this *RollingUpdateDeployment) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&RollingUpdateDeployment{`, + `MaxUnavailable:` + strings.Replace(fmt.Sprintf("%v", this.MaxUnavailable), "IntOrString", "k8s_io_apimachinery_pkg_util_intstr.IntOrString", 1) + `,`, + `MaxSurge:` + strings.Replace(fmt.Sprintf("%v", this.MaxSurge), "IntOrString", "k8s_io_apimachinery_pkg_util_intstr.IntOrString", 1) + `,`, + `}`, + }, "") + return s +} +func (this *RollingUpdateStatefulSetStrategy) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&RollingUpdateStatefulSetStrategy{`, + `Partition:` + valueToStringGenerated(this.Partition) + `,`, + `}`, + }, "") + return s +} +func (this *StatefulSet) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&StatefulSet{`, + `ObjectMeta:` + strings.Replace(strings.Replace(this.ObjectMeta.String(), "ObjectMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "StatefulSetSpec", "StatefulSetSpec", 1), `&`, ``, 1) + `,`, + `Status:` + strings.Replace(strings.Replace(this.Status.String(), "StatefulSetStatus", "StatefulSetStatus", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *StatefulSetCondition) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&StatefulSetCondition{`, + `Type:` + fmt.Sprintf("%v", this.Type) + `,`, + `Status:` + fmt.Sprintf("%v", this.Status) + `,`, + `LastTransitionTime:` + strings.Replace(strings.Replace(this.LastTransitionTime.String(), "Time", "k8s_io_apimachinery_pkg_apis_meta_v1.Time", 1), `&`, ``, 1) + `,`, + `Reason:` + fmt.Sprintf("%v", this.Reason) + `,`, + `Message:` + fmt.Sprintf("%v", this.Message) + `,`, + `}`, + }, "") + return s +} +func (this *StatefulSetList) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&StatefulSetList{`, + `ListMeta:` + strings.Replace(strings.Replace(this.ListMeta.String(), "ListMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Items), "StatefulSet", "StatefulSet", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *StatefulSetSpec) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&StatefulSetSpec{`, + `Replicas:` + valueToStringGenerated(this.Replicas) + `,`, + `Selector:` + strings.Replace(fmt.Sprintf("%v", this.Selector), "LabelSelector", "k8s_io_apimachinery_pkg_apis_meta_v1.LabelSelector", 1) + `,`, + `Template:` + strings.Replace(strings.Replace(this.Template.String(), "PodTemplateSpec", "k8s_io_api_core_v1.PodTemplateSpec", 1), `&`, ``, 1) + `,`, + `VolumeClaimTemplates:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.VolumeClaimTemplates), "PersistentVolumeClaim", "k8s_io_api_core_v1.PersistentVolumeClaim", 1), `&`, ``, 1) + `,`, + `ServiceName:` + fmt.Sprintf("%v", this.ServiceName) + `,`, + `PodManagementPolicy:` + fmt.Sprintf("%v", this.PodManagementPolicy) + `,`, + `UpdateStrategy:` + strings.Replace(strings.Replace(this.UpdateStrategy.String(), "StatefulSetUpdateStrategy", "StatefulSetUpdateStrategy", 1), `&`, ``, 1) + `,`, + `RevisionHistoryLimit:` + valueToStringGenerated(this.RevisionHistoryLimit) + `,`, + `}`, + }, "") + return s +} +func (this *StatefulSetStatus) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&StatefulSetStatus{`, + `ObservedGeneration:` + fmt.Sprintf("%v", this.ObservedGeneration) + `,`, + `Replicas:` + fmt.Sprintf("%v", this.Replicas) + `,`, + `ReadyReplicas:` + fmt.Sprintf("%v", this.ReadyReplicas) + `,`, + `CurrentReplicas:` + fmt.Sprintf("%v", this.CurrentReplicas) + `,`, + `UpdatedReplicas:` + fmt.Sprintf("%v", this.UpdatedReplicas) + `,`, + `CurrentRevision:` + fmt.Sprintf("%v", this.CurrentRevision) + `,`, + `UpdateRevision:` + fmt.Sprintf("%v", this.UpdateRevision) + `,`, + `CollisionCount:` + valueToStringGenerated(this.CollisionCount) + `,`, + `Conditions:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Conditions), "StatefulSetCondition", "StatefulSetCondition", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *StatefulSetUpdateStrategy) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&StatefulSetUpdateStrategy{`, + `Type:` + fmt.Sprintf("%v", this.Type) + `,`, + `RollingUpdate:` + strings.Replace(fmt.Sprintf("%v", this.RollingUpdate), "RollingUpdateStatefulSetStrategy", "RollingUpdateStatefulSetStrategy", 1) + `,`, + `}`, + }, "") + return s +} func valueToStringGenerated(v interface{}) string { rv := reflect.ValueOf(v) if rv.IsNil() { @@ -536,6 +2293,246 @@ func valueToStringGenerated(v interface{}) string { pv := reflect.Indirect(rv).Interface() return fmt.Sprintf("*%v", pv) } +func (m *ControllerRevision) 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: ControllerRevision: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ControllerRevision: 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 > 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 Data", 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.Data.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Revision", wireType) + } + m.Revision = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Revision |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ControllerRevisionList) 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: ControllerRevisionList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ControllerRevisionList: 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 > 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 > l { + return io.ErrUnexpectedEOF + } + m.Items = append(m.Items, ControllerRevision{}) + 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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *DaemonSet) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -676,6 +2673,202 @@ func (m *DaemonSet) Unmarshal(dAtA []byte) error { } return nil } +func (m *DaemonSetCondition) 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: DaemonSetCondition: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DaemonSetCondition: 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 > l { + return io.ErrUnexpectedEOF + } + m.Type = DaemonSetConditionType(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 > 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 > 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 > 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 > 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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *DaemonSetList) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -1170,6 +3363,37 @@ func (m *DaemonSetStatus) Unmarshal(dAtA []byte) error { } } m.CollisionCount = &v + case 10: + 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 > l { + return io.ErrUnexpectedEOF + } + m.Conditions = append(m.Conditions, DaemonSetCondition{}) + 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:]) @@ -1303,6 +3527,1827 @@ func (m *DaemonSetUpdateStrategy) Unmarshal(dAtA []byte) error { } return nil } +func (m *Deployment) 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: Deployment: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Deployment: 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 > 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 > 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 > 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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeploymentCondition) 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: DeploymentCondition: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeploymentCondition: 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 > l { + return io.ErrUnexpectedEOF + } + m.Type = DeploymentConditionType(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 > l { + return io.ErrUnexpectedEOF + } + m.Status = k8s_io_api_core_v1.ConditionStatus(dAtA[iNdEx:postIndex]) + 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 > 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 > l { + return io.ErrUnexpectedEOF + } + m.Message = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LastUpdateTime", 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.LastUpdateTime.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.LastTransitionTime.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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeploymentList) 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: DeploymentList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeploymentList: 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 > 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 > l { + return io.ErrUnexpectedEOF + } + m.Items = append(m.Items, Deployment{}) + 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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeploymentSpec) 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: DeploymentSpec: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeploymentSpec: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Replicas", 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.Replicas = &v + 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 > l { + return io.ErrUnexpectedEOF + } + if m.Selector == nil { + m.Selector = &k8s_io_apimachinery_pkg_apis_meta_v1.LabelSelector{} + } + if err := m.Selector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Template", 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.Template.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Strategy", 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.Strategy.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MinReadySeconds", wireType) + } + m.MinReadySeconds = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MinReadySeconds |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RevisionHistoryLimit", 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.RevisionHistoryLimit = &v + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Paused", 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.Paused = bool(v != 0) + case 9: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ProgressDeadlineSeconds", 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.ProgressDeadlineSeconds = &v + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeploymentStatus) 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: DeploymentStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeploymentStatus: 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) + } + m.ObservedGeneration = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ObservedGeneration |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Replicas", wireType) + } + m.Replicas = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Replicas |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field UpdatedReplicas", wireType) + } + m.UpdatedReplicas = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.UpdatedReplicas |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AvailableReplicas", wireType) + } + m.AvailableReplicas = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.AvailableReplicas |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field UnavailableReplicas", wireType) + } + m.UnavailableReplicas = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.UnavailableReplicas |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + 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 > l { + return io.ErrUnexpectedEOF + } + m.Conditions = append(m.Conditions, DeploymentCondition{}) + if err := m.Conditions[len(m.Conditions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ReadyReplicas", wireType) + } + m.ReadyReplicas = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ReadyReplicas |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field CollisionCount", 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.CollisionCount = &v + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeploymentStrategy) 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: DeploymentStrategy: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeploymentStrategy: 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 > l { + return io.ErrUnexpectedEOF + } + m.Type = DeploymentStrategyType(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RollingUpdate", 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 > l { + return io.ErrUnexpectedEOF + } + if m.RollingUpdate == nil { + m.RollingUpdate = &RollingUpdateDeployment{} + } + if err := m.RollingUpdate.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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ReplicaSet) 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: ReplicaSet: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ReplicaSet: 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 > 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 > 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 > 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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ReplicaSetCondition) 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: ReplicaSetCondition: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ReplicaSetCondition: 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 > l { + return io.ErrUnexpectedEOF + } + m.Type = ReplicaSetConditionType(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 > 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 > 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 > 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 > 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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ReplicaSetList) 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: ReplicaSetList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ReplicaSetList: 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 > 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 > l { + return io.ErrUnexpectedEOF + } + m.Items = append(m.Items, ReplicaSet{}) + 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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ReplicaSetSpec) 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: ReplicaSetSpec: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ReplicaSetSpec: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Replicas", 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.Replicas = &v + 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 > l { + return io.ErrUnexpectedEOF + } + if m.Selector == nil { + m.Selector = &k8s_io_apimachinery_pkg_apis_meta_v1.LabelSelector{} + } + if err := m.Selector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Template", 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.Template.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MinReadySeconds", wireType) + } + m.MinReadySeconds = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MinReadySeconds |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ReplicaSetStatus) 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: ReplicaSetStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ReplicaSetStatus: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Replicas", wireType) + } + m.Replicas = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Replicas |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FullyLabeledReplicas", wireType) + } + m.FullyLabeledReplicas = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.FullyLabeledReplicas |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ObservedGeneration", wireType) + } + m.ObservedGeneration = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ObservedGeneration |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ReadyReplicas", wireType) + } + m.ReadyReplicas = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ReadyReplicas |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AvailableReplicas", wireType) + } + m.AvailableReplicas = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.AvailableReplicas |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + 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 > l { + return io.ErrUnexpectedEOF + } + m.Conditions = append(m.Conditions, ReplicaSetCondition{}) + 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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *RollingUpdateDaemonSet) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -1386,6 +5431,1277 @@ func (m *RollingUpdateDaemonSet) Unmarshal(dAtA []byte) error { } return nil } +func (m *RollingUpdateDeployment) 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: RollingUpdateDeployment: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RollingUpdateDeployment: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxUnavailable", 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 > l { + return io.ErrUnexpectedEOF + } + if m.MaxUnavailable == nil { + m.MaxUnavailable = &k8s_io_apimachinery_pkg_util_intstr.IntOrString{} + } + if err := m.MaxUnavailable.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxSurge", 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 > l { + return io.ErrUnexpectedEOF + } + if m.MaxSurge == nil { + m.MaxSurge = &k8s_io_apimachinery_pkg_util_intstr.IntOrString{} + } + if err := m.MaxSurge.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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *RollingUpdateStatefulSetStrategy) 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: RollingUpdateStatefulSetStrategy: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RollingUpdateStatefulSetStrategy: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Partition", 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.Partition = &v + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StatefulSet) 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: StatefulSet: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatefulSet: 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 > 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 > 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 > 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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StatefulSetCondition) 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: StatefulSetCondition: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatefulSetCondition: 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 > l { + return io.ErrUnexpectedEOF + } + m.Type = StatefulSetConditionType(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 > 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 > 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 > 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 > 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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StatefulSetList) 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: StatefulSetList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatefulSetList: 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 > 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 > l { + return io.ErrUnexpectedEOF + } + m.Items = append(m.Items, StatefulSet{}) + 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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StatefulSetSpec) 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: StatefulSetSpec: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatefulSetSpec: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Replicas", 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.Replicas = &v + 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 > l { + return io.ErrUnexpectedEOF + } + if m.Selector == nil { + m.Selector = &k8s_io_apimachinery_pkg_apis_meta_v1.LabelSelector{} + } + if err := m.Selector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Template", 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.Template.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VolumeClaimTemplates", 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 > l { + return io.ErrUnexpectedEOF + } + m.VolumeClaimTemplates = append(m.VolumeClaimTemplates, k8s_io_api_core_v1.PersistentVolumeClaim{}) + if err := m.VolumeClaimTemplates[len(m.VolumeClaimTemplates)-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 ServiceName", 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 > l { + return io.ErrUnexpectedEOF + } + m.ServiceName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PodManagementPolicy", 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 > l { + return io.ErrUnexpectedEOF + } + m.PodManagementPolicy = PodManagementPolicyType(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UpdateStrategy", 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.UpdateStrategy.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RevisionHistoryLimit", 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.RevisionHistoryLimit = &v + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StatefulSetStatus) 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: StatefulSetStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatefulSetStatus: 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) + } + m.ObservedGeneration = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ObservedGeneration |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Replicas", wireType) + } + m.Replicas = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Replicas |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ReadyReplicas", wireType) + } + m.ReadyReplicas = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ReadyReplicas |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + 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 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field UpdatedReplicas", wireType) + } + m.UpdatedReplicas = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.UpdatedReplicas |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CurrentRevision", 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 > l { + return io.ErrUnexpectedEOF + } + m.CurrentRevision = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UpdateRevision", 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 > l { + return io.ErrUnexpectedEOF + } + m.UpdateRevision = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 9: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field CollisionCount", 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.CollisionCount = &v + case 10: + 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 > l { + return io.ErrUnexpectedEOF + } + m.Conditions = append(m.Conditions, StatefulSetCondition{}) + 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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StatefulSetUpdateStrategy) 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: StatefulSetUpdateStrategy: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatefulSetUpdateStrategy: 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 > l { + return io.ErrUnexpectedEOF + } + m.Type = StatefulSetUpdateStrategyType(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RollingUpdate", 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 > l { + return io.ErrUnexpectedEOF + } + if m.RollingUpdate == nil { + m.RollingUpdate = &RollingUpdateStatefulSetStrategy{} + } + if err := m.RollingUpdate.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 { + 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 @@ -1496,63 +6812,134 @@ func init() { } var fileDescriptorGenerated = []byte{ - // 928 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x95, 0xcf, 0x73, 0xdb, 0x44, - 0x14, 0xc7, 0xad, 0x38, 0x0e, 0xce, 0xa6, 0x71, 0xc8, 0xd2, 0x49, 0x45, 0x18, 0xe4, 0x60, 0x2e, - 0x86, 0x0e, 0x12, 0x6e, 0x81, 0x61, 0xe0, 0xc0, 0x54, 0xe9, 0x4c, 0x29, 0x24, 0x0e, 0xac, 0x1b, - 0x0e, 0x0c, 0xcc, 0xb0, 0x96, 0x1e, 0xce, 0x62, 0xfd, 0x1a, 0xed, 0xca, 0x83, 0x6f, 0x9c, 0x38, - 0xf3, 0xa7, 0xc0, 0x5f, 0xd0, 0x6b, 0x8e, 0x3d, 0xf6, 0xe4, 0x21, 0xe6, 0xbf, 0xe0, 0x02, 0xb3, - 0xab, 0x8d, 0x6d, 0xd9, 0x72, 0x9a, 0x9b, 0xf7, 0xbd, 0xef, 0xf7, 0xb3, 0x6f, 0xdf, 0x3e, 0xaf, - 0xd0, 0xe7, 0xc3, 0x4f, 0xb9, 0xcd, 0x62, 0x67, 0x98, 0xf5, 0x21, 0x8d, 0x40, 0x00, 0x77, 0x46, - 0x10, 0xf9, 0x71, 0xea, 0xe8, 0x04, 0x4d, 0x98, 0x43, 0x93, 0x84, 0x3b, 0xa3, 0x8e, 0x33, 0x80, - 0x08, 0x52, 0x2a, 0xc0, 0xb7, 0x93, 0x34, 0x16, 0x31, 0xc6, 0xb9, 0xc6, 0xa6, 0x09, 0xb3, 0xa5, - 0xc6, 0x1e, 0x75, 0x0e, 0x3f, 0x18, 0x30, 0x71, 0x91, 0xf5, 0x6d, 0x2f, 0x0e, 0x9d, 0x41, 0x3c, - 0x88, 0x1d, 0x25, 0xed, 0x67, 0x3f, 0xab, 0x95, 0x5a, 0xa8, 0x5f, 0x39, 0xe2, 0xb0, 0xb5, 0xb0, - 0x8d, 0x17, 0xa7, 0x50, 0xb2, 0xcd, 0xe1, 0x7b, 0x0b, 0x9a, 0x24, 0x0e, 0x98, 0x37, 0x76, 0x46, - 0x9d, 0x3e, 0x08, 0xba, 0x2a, 0xfd, 0x68, 0x2e, 0x0d, 0xa9, 0x77, 0xc1, 0x22, 0x48, 0xc7, 0x4e, - 0x32, 0x1c, 0xc8, 0x00, 0x77, 0x42, 0x10, 0xb4, 0x6c, 0x03, 0x67, 0x9d, 0x2b, 0xcd, 0x22, 0xc1, - 0x42, 0x58, 0x31, 0x7c, 0xf2, 0x2a, 0x03, 0xf7, 0x2e, 0x20, 0xa4, 0x2b, 0xbe, 0x87, 0xeb, 0x7c, - 0x99, 0x60, 0x81, 0xc3, 0x22, 0xc1, 0x45, 0xba, 0x6c, 0x6a, 0xfd, 0x67, 0xa0, 0xed, 0xc7, 0x14, - 0xc2, 0x38, 0xea, 0x81, 0xc0, 0x3f, 0xa1, 0xba, 0x3c, 0x86, 0x4f, 0x05, 0x35, 0x8d, 0x23, 0xa3, - 0xbd, 0xf3, 0xe0, 0x43, 0x7b, 0x7e, 0x0d, 0x33, 0xaa, 0x9d, 0x0c, 0x07, 0x32, 0xc0, 0x6d, 0xa9, - 0xb6, 0x47, 0x1d, 0xfb, 0xac, 0xff, 0x0b, 0x78, 0xe2, 0x14, 0x04, 0x75, 0xf1, 0xe5, 0xa4, 0x59, - 0x99, 0x4e, 0x9a, 0x68, 0x1e, 0x23, 0x33, 0x2a, 0x3e, 0x46, 0x9b, 0x3c, 0x01, 0xcf, 0xdc, 0x50, - 0xf4, 0x77, 0xec, 0xd5, 0x4b, 0xb6, 0x67, 0xe5, 0xf4, 0x12, 0xf0, 0xdc, 0x3b, 0x1a, 0xb7, 0x29, - 0x57, 0x44, 0x99, 0xf1, 0xd7, 0x68, 0x8b, 0x0b, 0x2a, 0x32, 0x6e, 0x56, 0x15, 0xe6, 0xdd, 0x9b, - 0x31, 0x4a, 0xea, 0x36, 0x34, 0x68, 0x2b, 0x5f, 0x13, 0x8d, 0x68, 0xfd, 0x65, 0xa0, 0xdd, 0x99, - 0xf6, 0x84, 0x71, 0x81, 0x7f, 0x58, 0xe9, 0x82, 0x7d, 0xbb, 0x2e, 0x48, 0xb7, 0xea, 0xc1, 0xeb, - 0x7a, 0xaf, 0xfa, 0x75, 0x64, 0xa1, 0x03, 0x2e, 0xaa, 0x31, 0x01, 0x21, 0x37, 0x37, 0x8e, 0xaa, - 0xed, 0x9d, 0x07, 0x6f, 0xdf, 0x58, 0xbb, 0xbb, 0xab, 0x49, 0xb5, 0xa7, 0xd2, 0x43, 0x72, 0x6b, - 0xeb, 0x79, 0x75, 0xa1, 0x66, 0xd9, 0x18, 0xfc, 0x23, 0xaa, 0x73, 0x08, 0xc0, 0x13, 0x71, 0xaa, - 0x6b, 0x7e, 0x78, 0xcb, 0x9a, 0x69, 0x1f, 0x82, 0x9e, 0xb6, 0xba, 0x77, 0x64, 0xd1, 0xd7, 0x2b, - 0x32, 0x43, 0xe2, 0x6f, 0x51, 0x5d, 0x40, 0x98, 0x04, 0x54, 0x80, 0xbe, 0xba, 0x42, 0xcf, 0xe5, - 0x9f, 0x4b, 0xc2, 0xbe, 0x89, 0xfd, 0x67, 0x5a, 0xa6, 0x2e, 0x6f, 0xd6, 0x87, 0xeb, 0x28, 0x99, - 0x61, 0xf0, 0x10, 0x35, 0xb2, 0xc4, 0x97, 0x4a, 0x21, 0x07, 0x72, 0x30, 0xd6, 0x97, 0x79, 0xff, - 0xc6, 0x86, 0x9c, 0x17, 0x2c, 0xee, 0x81, 0xde, 0xa0, 0x51, 0x8c, 0x93, 0x25, 0x34, 0x7e, 0x84, - 0xf6, 0x42, 0x16, 0x11, 0xa0, 0xfe, 0xb8, 0x07, 0x5e, 0x1c, 0xf9, 0xdc, 0xdc, 0x3c, 0x32, 0xda, - 0x35, 0xf7, 0x9e, 0x06, 0xec, 0x9d, 0x16, 0xd3, 0x64, 0x59, 0x8f, 0x4f, 0xd0, 0xdd, 0x14, 0x46, - 0x8c, 0xb3, 0x38, 0xfa, 0x92, 0x71, 0x11, 0xa7, 0xe3, 0x13, 0x16, 0x32, 0x61, 0x6e, 0x29, 0x8e, - 0x39, 0x9d, 0x34, 0xef, 0x92, 0x92, 0x3c, 0x29, 0x75, 0xb5, 0xfe, 0xac, 0xa1, 0xbd, 0xa5, 0x09, - 0xc5, 0xdf, 0xa1, 0x03, 0x2f, 0x4b, 0x53, 0x88, 0x44, 0x37, 0x0b, 0xfb, 0x90, 0xf6, 0xbc, 0x0b, - 0xf0, 0xb3, 0x00, 0x7c, 0x75, 0xa3, 0x35, 0xd7, 0xd2, 0xb5, 0x1e, 0x1c, 0x97, 0xaa, 0xc8, 0x1a, - 0x37, 0xfe, 0x0a, 0xe1, 0x48, 0x85, 0x4e, 0x19, 0xe7, 0x33, 0xe6, 0x86, 0x62, 0x1e, 0x6a, 0x26, - 0xee, 0xae, 0x28, 0x48, 0x89, 0x4b, 0xd6, 0xe8, 0x03, 0x67, 0x29, 0xf8, 0xcb, 0x35, 0x56, 0x8b, - 0x35, 0x3e, 0x2e, 0x55, 0x91, 0x35, 0x6e, 0xfc, 0x31, 0xda, 0xc9, 0x77, 0x53, 0x3d, 0xd7, 0x97, - 0xf3, 0x86, 0x86, 0xed, 0x74, 0xe7, 0x29, 0xb2, 0xa8, 0x93, 0x47, 0x8b, 0xfb, 0x1c, 0xd2, 0x11, - 0xf8, 0x4f, 0xf2, 0x97, 0x8d, 0xc5, 0x91, 0x59, 0x3b, 0x32, 0xda, 0xd5, 0xf9, 0xd1, 0xce, 0x56, - 0x14, 0xa4, 0xc4, 0x25, 0x8f, 0x96, 0x4f, 0xcd, 0xca, 0xd1, 0xb6, 0x8a, 0x47, 0x3b, 0x2f, 0x55, - 0x91, 0x35, 0x6e, 0x39, 0x7b, 0x79, 0xc9, 0x8f, 0x46, 0x94, 0x05, 0xb4, 0x1f, 0x80, 0xf9, 0x5a, - 0x71, 0xf6, 0xba, 0xc5, 0x34, 0x59, 0xd6, 0xe3, 0x27, 0x68, 0x3f, 0x0f, 0x9d, 0x47, 0x74, 0x06, - 0xa9, 0x2b, 0xc8, 0x9b, 0x1a, 0xb2, 0xdf, 0x5d, 0x16, 0x90, 0x55, 0x0f, 0xfe, 0x0c, 0x35, 0xbc, - 0x38, 0x08, 0xd4, 0x3c, 0x1e, 0xc7, 0x59, 0x24, 0xcc, 0x6d, 0x45, 0xc1, 0xf2, 0x3f, 0x74, 0x5c, - 0xc8, 0x90, 0x25, 0x65, 0xeb, 0xb9, 0x81, 0xee, 0xad, 0xf9, 0x1f, 0xe2, 0x2f, 0xd0, 0xa6, 0x18, - 0x27, 0xa0, 0x06, 0x75, 0xdb, 0xbd, 0x7f, 0xfd, 0x66, 0x3f, 0x1b, 0x27, 0xf0, 0xef, 0xa4, 0xf9, - 0xd6, 0x1a, 0x9b, 0x4c, 0x13, 0x65, 0xc4, 0x1e, 0xda, 0x4d, 0xe5, 0x76, 0xd1, 0x20, 0x97, 0xe8, - 0x57, 0xe6, 0xfd, 0xb2, 0xc7, 0x80, 0x2c, 0x0a, 0xe7, 0x4f, 0xe5, 0xfe, 0x74, 0xd2, 0xdc, 0x2d, - 0xe4, 0x48, 0x91, 0xd9, 0xfa, 0xdd, 0x40, 0x07, 0xe5, 0x66, 0x1c, 0xa0, 0x46, 0x48, 0x7f, 0x5d, - 0x6c, 0xef, 0xab, 0xbe, 0x7f, 0xf2, 0xab, 0x6a, 0xe7, 0x5f, 0x55, 0xfb, 0x69, 0x24, 0xce, 0xd2, - 0x9e, 0x48, 0x59, 0x34, 0xc8, 0x5b, 0x79, 0x5a, 0x60, 0x91, 0x25, 0xb6, 0xdb, 0xbe, 0xbc, 0xb2, - 0x2a, 0x2f, 0xae, 0xac, 0xca, 0xcb, 0x2b, 0xab, 0xf2, 0xdb, 0xd4, 0x32, 0x2e, 0xa7, 0x96, 0xf1, - 0x62, 0x6a, 0x19, 0x2f, 0xa7, 0x96, 0xf1, 0xf7, 0xd4, 0x32, 0xfe, 0xf8, 0xc7, 0xaa, 0x7c, 0xbf, - 0x31, 0xea, 0xfc, 0x1f, 0x00, 0x00, 0xff, 0xff, 0x89, 0x78, 0x09, 0x33, 0x43, 0x09, 0x00, 0x00, + // 2051 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x5a, 0xcd, 0x6f, 0x24, 0x47, + 0x1d, 0x75, 0xcf, 0x87, 0x3d, 0x2e, 0xaf, 0xed, 0xdd, 0xb2, 0xb1, 0x27, 0xbb, 0x64, 0x66, 0x19, + 0x60, 0xe3, 0xcd, 0x66, 0x7b, 0xf0, 0x66, 0x13, 0xa1, 0x2c, 0x02, 0x79, 0xc6, 0x21, 0x84, 0x78, + 0x6c, 0x53, 0x5e, 0xef, 0x61, 0x09, 0x12, 0x35, 0x33, 0xb5, 0xe3, 0x8e, 0xfb, 0x4b, 0xdd, 0xd5, + 0xc3, 0x8e, 0xb8, 0x20, 0x24, 0x38, 0x71, 0xe0, 0x3f, 0x41, 0x08, 0xc1, 0x0d, 0x45, 0x88, 0xcb, + 0x5e, 0x90, 0x22, 0x2e, 0xe4, 0x64, 0xb1, 0x93, 0x13, 0x42, 0x39, 0x72, 0xc9, 0x05, 0x54, 0xd5, + 0xd5, 0xdf, 0xd5, 0x9e, 0xb1, 0x37, 0xeb, 0xa0, 0x68, 0x6f, 0x9e, 0xaa, 0xf7, 0x7b, 0xfd, 0xab, + 0xaa, 0x5f, 0xd5, 0x7b, 0x5d, 0x6d, 0x70, 0xef, 0xf8, 0xdb, 0xae, 0xaa, 0x59, 0xcd, 0x63, 0xaf, + 0x4b, 0x1c, 0x93, 0x50, 0xe2, 0x36, 0x87, 0xc4, 0xec, 0x5b, 0x4e, 0x53, 0x74, 0x60, 0x5b, 0x6b, + 0x62, 0xdb, 0x76, 0x9b, 0xc3, 0xcd, 0xe6, 0x80, 0x98, 0xc4, 0xc1, 0x94, 0xf4, 0x55, 0xdb, 0xb1, + 0xa8, 0x05, 0xa1, 0x8f, 0x51, 0xb1, 0xad, 0xa9, 0x0c, 0xa3, 0x0e, 0x37, 0xaf, 0xde, 0x1e, 0x68, + 0xf4, 0xc8, 0xeb, 0xaa, 0x3d, 0xcb, 0x68, 0x0e, 0xac, 0x81, 0xd5, 0xe4, 0xd0, 0xae, 0xf7, 0x88, + 0xff, 0xe2, 0x3f, 0xf8, 0x5f, 0x3e, 0xc5, 0xd5, 0x46, 0xec, 0x31, 0x3d, 0xcb, 0x21, 0x92, 0xc7, + 0x5c, 0xbd, 0x19, 0xc3, 0xd8, 0x96, 0xae, 0xf5, 0x46, 0xcd, 0xe1, 0x66, 0x97, 0x50, 0x9c, 0x85, + 0xde, 0x8d, 0xa0, 0x06, 0xee, 0x1d, 0x69, 0x26, 0x71, 0x46, 0x4d, 0xfb, 0x78, 0xc0, 0x1a, 0xdc, + 0xa6, 0x41, 0x28, 0x96, 0x3d, 0xa0, 0x99, 0x17, 0xe5, 0x78, 0x26, 0xd5, 0x0c, 0x92, 0x09, 0x78, + 0x73, 0x52, 0x80, 0xdb, 0x3b, 0x22, 0x06, 0xce, 0xc4, 0xbd, 0x9e, 0x17, 0xe7, 0x51, 0x4d, 0x6f, + 0x6a, 0x26, 0x75, 0xa9, 0x93, 0x0e, 0x6a, 0xfc, 0x47, 0x01, 0xb0, 0x6d, 0x99, 0xd4, 0xb1, 0x74, + 0x9d, 0x38, 0x88, 0x0c, 0x35, 0x57, 0xb3, 0x4c, 0xf8, 0x53, 0x50, 0x61, 0xe3, 0xe9, 0x63, 0x8a, + 0xab, 0xca, 0x75, 0x65, 0x63, 0xe1, 0xce, 0xb7, 0xd4, 0x68, 0x3d, 0x42, 0x7a, 0xd5, 0x3e, 0x1e, + 0xb0, 0x06, 0x57, 0x65, 0x68, 0x75, 0xb8, 0xa9, 0xee, 0x75, 0x3f, 0x20, 0x3d, 0xda, 0x21, 0x14, + 0xb7, 0xe0, 0x93, 0x93, 0xfa, 0xcc, 0xf8, 0xa4, 0x0e, 0xa2, 0x36, 0x14, 0xb2, 0xc2, 0x3d, 0x50, + 0xe2, 0xec, 0x05, 0xce, 0x7e, 0x3b, 0x97, 0x5d, 0x0c, 0x5a, 0x45, 0xf8, 0x67, 0x6f, 0x3f, 0xa6, + 0xc4, 0x64, 0xe9, 0xb5, 0x2e, 0x09, 0xea, 0xd2, 0x36, 0xa6, 0x18, 0x71, 0x22, 0xf8, 0x1a, 0xa8, + 0x38, 0x22, 0xfd, 0x6a, 0xf1, 0xba, 0xb2, 0x51, 0x6c, 0x5d, 0x16, 0xa8, 0x4a, 0x30, 0x2c, 0x14, + 0x22, 0x1a, 0x7f, 0x55, 0xc0, 0x5a, 0x76, 0xdc, 0x3b, 0x9a, 0x4b, 0xe1, 0xfb, 0x99, 0xb1, 0xab, + 0xd3, 0x8d, 0x9d, 0x45, 0xf3, 0x91, 0x87, 0x0f, 0x0e, 0x5a, 0x62, 0xe3, 0x7e, 0x0f, 0x94, 0x35, + 0x4a, 0x0c, 0xb7, 0x5a, 0xb8, 0x5e, 0xdc, 0x58, 0xb8, 0x73, 0x43, 0xcd, 0x96, 0xb9, 0x9a, 0x4d, + 0xac, 0xb5, 0x28, 0x28, 0xcb, 0xef, 0xb2, 0x60, 0xe4, 0x73, 0x34, 0xfe, 0xab, 0x80, 0xf9, 0x6d, + 0x4c, 0x0c, 0xcb, 0x3c, 0x20, 0xf4, 0x02, 0x16, 0xad, 0x0d, 0x4a, 0xae, 0x4d, 0x7a, 0x62, 0xd1, + 0xbe, 0x26, 0xcb, 0x3d, 0x4c, 0xe7, 0xc0, 0x26, 0xbd, 0x68, 0xa1, 0xd8, 0x2f, 0xc4, 0x83, 0xe1, + 0x7b, 0x60, 0xd6, 0xa5, 0x98, 0x7a, 0x2e, 0x5f, 0xa6, 0x85, 0x3b, 0x5f, 0x3f, 0x9d, 0x86, 0x43, + 0x5b, 0x4b, 0x82, 0x68, 0xd6, 0xff, 0x8d, 0x04, 0x45, 0xe3, 0x5f, 0x05, 0x00, 0x43, 0x6c, 0xdb, + 0x32, 0xfb, 0x1a, 0x65, 0xf5, 0xfb, 0x16, 0x28, 0xd1, 0x91, 0x4d, 0xf8, 0x34, 0xcc, 0xb7, 0x6e, + 0x04, 0x59, 0xdc, 0x1f, 0xd9, 0xe4, 0xb3, 0x93, 0xfa, 0x5a, 0x36, 0x82, 0xf5, 0x20, 0x1e, 0x03, + 0x77, 0xc2, 0xfc, 0x0a, 0x3c, 0xfa, 0x6e, 0xf2, 0xd1, 0x9f, 0x9d, 0xd4, 0x25, 0xe7, 0x8a, 0x1a, + 0x32, 0x25, 0x13, 0x84, 0x43, 0x00, 0x75, 0xec, 0xd2, 0xfb, 0x0e, 0x36, 0x5d, 0xff, 0x49, 0x9a, + 0x41, 0xc4, 0xc8, 0x5f, 0x9d, 0x6e, 0x79, 0x58, 0x44, 0xeb, 0xaa, 0xc8, 0x02, 0xee, 0x64, 0xd8, + 0x90, 0xe4, 0x09, 0xf0, 0x06, 0x98, 0x75, 0x08, 0x76, 0x2d, 0xb3, 0x5a, 0xe2, 0xa3, 0x08, 0x27, + 0x10, 0xf1, 0x56, 0x24, 0x7a, 0xe1, 0x4d, 0x30, 0x67, 0x10, 0xd7, 0xc5, 0x03, 0x52, 0x2d, 0x73, + 0xe0, 0xb2, 0x00, 0xce, 0x75, 0xfc, 0x66, 0x14, 0xf4, 0x37, 0x7e, 0xaf, 0x80, 0xc5, 0x70, 0xe6, + 0x2e, 0x60, 0xab, 0xb4, 0x92, 0x5b, 0xe5, 0xe5, 0x53, 0xeb, 0x24, 0x67, 0x87, 0x7c, 0x58, 0x8c, + 0xe5, 0xcc, 0x8a, 0x10, 0xfe, 0x04, 0x54, 0x5c, 0xa2, 0x93, 0x1e, 0xb5, 0x1c, 0x91, 0xf3, 0xeb, + 0x53, 0xe6, 0x8c, 0xbb, 0x44, 0x3f, 0x10, 0xa1, 0xad, 0x4b, 0x2c, 0xe9, 0xe0, 0x17, 0x0a, 0x29, + 0xe1, 0x8f, 0x40, 0x85, 0x12, 0xc3, 0xd6, 0x31, 0x25, 0x62, 0x9b, 0x24, 0xea, 0x9b, 0x95, 0x0b, + 0x23, 0xdb, 0xb7, 0xfa, 0xf7, 0x05, 0x8c, 0x6f, 0x94, 0x70, 0x1e, 0x82, 0x56, 0x14, 0xd2, 0xc0, + 0x63, 0xb0, 0xe4, 0xd9, 0x7d, 0x86, 0xa4, 0xec, 0xe8, 0x1e, 0x8c, 0x44, 0xf9, 0xdc, 0x3a, 0x75, + 0x42, 0x0e, 0x13, 0x21, 0xad, 0x35, 0xf1, 0x80, 0xa5, 0x64, 0x3b, 0x4a, 0x51, 0xc3, 0x2d, 0xb0, + 0x6c, 0x68, 0x26, 0x22, 0xb8, 0x3f, 0x3a, 0x20, 0x3d, 0xcb, 0xec, 0xbb, 0xbc, 0x80, 0xca, 0xad, + 0x75, 0x41, 0xb0, 0xdc, 0x49, 0x76, 0xa3, 0x34, 0x1e, 0xee, 0x80, 0xd5, 0xe0, 0x9c, 0xfd, 0x81, + 0xe6, 0x52, 0xcb, 0x19, 0xed, 0x68, 0x86, 0x46, 0xab, 0xb3, 0x9c, 0xa7, 0x3a, 0x3e, 0xa9, 0xaf, + 0x22, 0x49, 0x3f, 0x92, 0x46, 0x35, 0x7e, 0x33, 0x0b, 0x96, 0x53, 0xa7, 0x01, 0x7c, 0x00, 0xd6, + 0x7a, 0x9e, 0xe3, 0x10, 0x93, 0xee, 0x7a, 0x46, 0x97, 0x38, 0x07, 0xbd, 0x23, 0xd2, 0xf7, 0x74, + 0xd2, 0xe7, 0x2b, 0x5a, 0x6e, 0xd5, 0x44, 0xae, 0x6b, 0x6d, 0x29, 0x0a, 0xe5, 0x44, 0xc3, 0x1f, + 0x02, 0x68, 0xf2, 0xa6, 0x8e, 0xe6, 0xba, 0x21, 0x67, 0x81, 0x73, 0x86, 0x1b, 0x70, 0x37, 0x83, + 0x40, 0x92, 0x28, 0x96, 0x63, 0x9f, 0xb8, 0x9a, 0x43, 0xfa, 0xe9, 0x1c, 0x8b, 0xc9, 0x1c, 0xb7, + 0xa5, 0x28, 0x94, 0x13, 0x0d, 0xdf, 0x00, 0x0b, 0xfe, 0xd3, 0xf8, 0x9c, 0x8b, 0xc5, 0x59, 0x11, + 0x64, 0x0b, 0xbb, 0x51, 0x17, 0x8a, 0xe3, 0xd8, 0xd0, 0xac, 0xae, 0x4b, 0x9c, 0x21, 0xe9, 0xbf, + 0xe3, 0x7b, 0x00, 0x26, 0x94, 0x65, 0x2e, 0x94, 0xe1, 0xd0, 0xf6, 0x32, 0x08, 0x24, 0x89, 0x62, + 0x43, 0xf3, 0xab, 0x26, 0x33, 0xb4, 0xd9, 0xe4, 0xd0, 0x0e, 0xa5, 0x28, 0x94, 0x13, 0xcd, 0x6a, + 0xcf, 0x4f, 0x79, 0x6b, 0x88, 0x35, 0x1d, 0x77, 0x75, 0x52, 0x9d, 0x4b, 0xd6, 0xde, 0x6e, 0xb2, + 0x1b, 0xa5, 0xf1, 0xf0, 0x1d, 0x70, 0xc5, 0x6f, 0x3a, 0x34, 0x71, 0x48, 0x52, 0xe1, 0x24, 0x2f, + 0x09, 0x92, 0x2b, 0xbb, 0x69, 0x00, 0xca, 0xc6, 0xc0, 0xb7, 0xc0, 0x52, 0xcf, 0xd2, 0x75, 0x5e, + 0x8f, 0x6d, 0xcb, 0x33, 0x69, 0x75, 0x9e, 0xb3, 0x40, 0xb6, 0x87, 0xda, 0x89, 0x1e, 0x94, 0x42, + 0xc2, 0x87, 0x00, 0xf4, 0x02, 0x39, 0x70, 0xab, 0x20, 0x5f, 0xe8, 0xb3, 0x3a, 0x14, 0x09, 0x70, + 0xd8, 0xe4, 0xa2, 0x18, 0x5b, 0xe3, 0x43, 0x05, 0xac, 0xe7, 0xec, 0x71, 0xf8, 0xbd, 0x84, 0xea, + 0xdd, 0x4a, 0xa9, 0xde, 0xb5, 0x9c, 0xb0, 0x98, 0xf4, 0xf5, 0xc0, 0x22, 0xf3, 0x1d, 0x9a, 0x39, + 0xf0, 0x21, 0xe2, 0x04, 0x7b, 0x55, 0x96, 0x3b, 0x8a, 0x03, 0xa3, 0x63, 0xf8, 0xca, 0xf8, 0xa4, + 0xbe, 0x98, 0xe8, 0x43, 0x49, 0xce, 0xc6, 0x2f, 0x0b, 0x00, 0x6c, 0x13, 0x5b, 0xb7, 0x46, 0x06, + 0x31, 0x2f, 0xc2, 0xb5, 0x6c, 0x27, 0x5c, 0x4b, 0x43, 0xba, 0x10, 0x61, 0x3e, 0xb9, 0xb6, 0x65, + 0x27, 0x65, 0x5b, 0xbe, 0x31, 0x81, 0xe7, 0x74, 0xdf, 0xf2, 0x8f, 0x22, 0x58, 0x89, 0xc0, 0x91, + 0x71, 0xb9, 0x97, 0x58, 0xc2, 0x57, 0x52, 0x4b, 0xb8, 0x2e, 0x09, 0x79, 0x6e, 0xce, 0xe5, 0xf3, + 0x77, 0x10, 0xf0, 0x03, 0xb0, 0xc4, 0xac, 0x8a, 0x5f, 0x08, 0xdc, 0x08, 0xcd, 0x9e, 0xd9, 0x08, + 0x85, 0x42, 0xb6, 0x93, 0x60, 0x42, 0x29, 0xe6, 0x1c, 0xe3, 0x35, 0xf7, 0xbc, 0x8d, 0x57, 0xe3, + 0x0f, 0x0a, 0x58, 0x8a, 0x96, 0xe9, 0x02, 0x6c, 0x52, 0x3b, 0x69, 0x93, 0x6a, 0xa7, 0xd7, 0x65, + 0x8e, 0x4f, 0xfa, 0x7b, 0x29, 0x9e, 0x35, 0x37, 0x4a, 0x1b, 0xec, 0x85, 0xca, 0xd6, 0xb5, 0x1e, + 0x76, 0x85, 0xac, 0x5e, 0xf2, 0x5f, 0xa6, 0xfc, 0x36, 0x14, 0xf6, 0x26, 0x2c, 0x55, 0xe1, 0xf9, + 0x5a, 0xaa, 0xe2, 0xe7, 0x63, 0xa9, 0xee, 0x83, 0x8a, 0x1b, 0x98, 0xa9, 0x12, 0xa7, 0xbc, 0x31, + 0x69, 0x3b, 0x0b, 0x1f, 0x15, 0xb2, 0x86, 0x0e, 0x2a, 0x64, 0x92, 0x79, 0xa7, 0xf2, 0x17, 0xe9, + 0x9d, 0xd8, 0x16, 0xb6, 0xb1, 0xe7, 0x92, 0x3e, 0xaf, 0xfb, 0x4a, 0xb4, 0x85, 0xf7, 0x79, 0x2b, + 0x12, 0xbd, 0xf0, 0x10, 0xac, 0xdb, 0x8e, 0x35, 0x70, 0x88, 0xeb, 0x6e, 0x13, 0xdc, 0xd7, 0x35, + 0x93, 0x04, 0x03, 0xf0, 0x55, 0xef, 0xda, 0xf8, 0xa4, 0xbe, 0xbe, 0x2f, 0x87, 0xa0, 0xbc, 0xd8, + 0xc6, 0x9f, 0x4b, 0xe0, 0x72, 0xfa, 0x44, 0xcc, 0x31, 0x22, 0xca, 0xb9, 0x8c, 0xc8, 0x6b, 0xb1, + 0x12, 0xf5, 0x5d, 0x5a, 0xec, 0x9d, 0x3f, 0x53, 0xa6, 0x5b, 0x60, 0x59, 0x18, 0x8f, 0xa0, 0x53, + 0x58, 0xb1, 0x70, 0x79, 0x0e, 0x93, 0xdd, 0x28, 0x8d, 0x67, 0xf6, 0x22, 0x72, 0x0d, 0x01, 0x49, + 0x29, 0x69, 0x2f, 0xb6, 0xd2, 0x00, 0x94, 0x8d, 0x81, 0x1d, 0xb0, 0xe2, 0x99, 0x59, 0x2a, 0xbf, + 0x5c, 0xae, 0x09, 0xaa, 0x95, 0xc3, 0x2c, 0x04, 0xc9, 0xe2, 0xe0, 0x8f, 0x13, 0x8e, 0x63, 0x96, + 0x1f, 0x04, 0xaf, 0x9c, 0x5e, 0xd1, 0x53, 0x5b, 0x0e, 0x78, 0x0f, 0x2c, 0x3a, 0xdc, 0x50, 0x06, + 0x59, 0xfa, 0xa6, 0xec, 0x2b, 0x22, 0x6c, 0x11, 0xc5, 0x3b, 0x51, 0x12, 0x2b, 0xf1, 0x51, 0x95, + 0x69, 0x7d, 0x54, 0xe3, 0x4f, 0x0a, 0x80, 0xd9, 0x2d, 0x38, 0xf1, 0xe5, 0x3e, 0x13, 0x11, 0x93, + 0xc8, 0xbe, 0xdc, 0xe1, 0xdc, 0x9a, 0xec, 0x70, 0xa2, 0x13, 0x74, 0x3a, 0x8b, 0x23, 0x66, 0xe0, + 0x62, 0x2e, 0x66, 0xa6, 0xb0, 0x38, 0x51, 0x3e, 0xcf, 0x66, 0x71, 0x62, 0x3c, 0xa7, 0x5b, 0x9c, + 0x7f, 0x17, 0xc0, 0x4a, 0x04, 0x9e, 0xda, 0xe2, 0x48, 0x42, 0x5e, 0x5c, 0xce, 0x4c, 0xbe, 0x9c, + 0x61, 0xb6, 0x23, 0x9a, 0xba, 0xff, 0x13, 0xdb, 0x11, 0x25, 0x94, 0x63, 0x3b, 0x7e, 0x57, 0x88, + 0x67, 0xfd, 0xa5, 0xb7, 0x1d, 0xcf, 0x7e, 0xb9, 0xd2, 0xf8, 0x4b, 0x11, 0x5c, 0x4e, 0x6f, 0xc1, + 0x84, 0x0e, 0x2a, 0x13, 0x75, 0x70, 0x1f, 0xac, 0x3e, 0xf2, 0x74, 0x7d, 0xc4, 0xa7, 0x21, 0x26, + 0x86, 0xbe, 0x82, 0x7e, 0x55, 0x44, 0xae, 0x7e, 0x5f, 0x82, 0x41, 0xd2, 0xc8, 0x1c, 0x4d, 0x2f, + 0x9e, 0x4b, 0xd3, 0x33, 0x6a, 0x53, 0x3a, 0x83, 0xda, 0x48, 0xf5, 0xb9, 0x7c, 0x0e, 0x7d, 0x9e, + 0x5a, 0x50, 0x25, 0xc7, 0xd5, 0xc4, 0x77, 0xf8, 0x5f, 0x2b, 0x60, 0x4d, 0xfe, 0xfa, 0x0c, 0x75, + 0xb0, 0x64, 0xe0, 0xc7, 0xf1, 0xcb, 0x8b, 0x49, 0x82, 0xe1, 0x51, 0x4d, 0x57, 0xfd, 0xaf, 0x3b, + 0xea, 0xbb, 0x26, 0xdd, 0x73, 0x0e, 0xa8, 0xa3, 0x99, 0x03, 0x5f, 0x60, 0x3b, 0x09, 0x2e, 0x94, + 0xe2, 0x6e, 0x7c, 0xa2, 0x80, 0xf5, 0x1c, 0x95, 0xbb, 0xd8, 0x4c, 0xe0, 0x43, 0x50, 0x31, 0xf0, + 0xe3, 0x03, 0xcf, 0x19, 0x04, 0x92, 0x7c, 0xf6, 0xe7, 0xf0, 0x8d, 0xdc, 0x11, 0x2c, 0x28, 0xe4, + 0x6b, 0xec, 0x81, 0xeb, 0x89, 0x41, 0xb2, 0x4d, 0x43, 0x1e, 0x79, 0x3a, 0xdf, 0x3f, 0xc2, 0x53, + 0xdc, 0x02, 0xf3, 0x36, 0x76, 0xa8, 0x16, 0x9a, 0xd1, 0x72, 0x6b, 0x71, 0x7c, 0x52, 0x9f, 0xdf, + 0x0f, 0x1a, 0x51, 0xd4, 0xdf, 0xf8, 0x55, 0x01, 0x2c, 0xc4, 0x48, 0x2e, 0x40, 0xdf, 0xdf, 0x4e, + 0xe8, 0xbb, 0xf4, 0x8b, 0x49, 0x7c, 0x54, 0x79, 0x02, 0xdf, 0x49, 0x09, 0xfc, 0x37, 0x27, 0x11, + 0x9d, 0xae, 0xf0, 0x9f, 0x16, 0xc0, 0x6a, 0x0c, 0x1d, 0x49, 0xfc, 0x77, 0x12, 0x12, 0xbf, 0x91, + 0x92, 0xf8, 0xaa, 0x2c, 0xe6, 0x85, 0xc6, 0x4f, 0xd6, 0xf8, 0x3f, 0x2a, 0x60, 0x39, 0x36, 0x77, + 0x17, 0x20, 0xf2, 0xdb, 0x49, 0x91, 0xaf, 0x4f, 0xa8, 0x97, 0x1c, 0x95, 0x7f, 0x52, 0x4e, 0xe4, + 0xfd, 0xa5, 0x97, 0xf9, 0x9f, 0x83, 0xd5, 0xa1, 0xa5, 0x7b, 0x06, 0x69, 0xeb, 0x58, 0x33, 0x02, + 0x00, 0x53, 0x32, 0x36, 0x89, 0x37, 0xa5, 0xf4, 0xc4, 0x71, 0x35, 0x97, 0x12, 0x93, 0x3e, 0x88, + 0x22, 0x23, 0x2d, 0x7e, 0x20, 0xa1, 0x43, 0xd2, 0x87, 0xc0, 0x37, 0xc0, 0x02, 0xd3, 0x54, 0xad, + 0x47, 0x76, 0xb1, 0x11, 0xd4, 0x54, 0xf8, 0x7d, 0xe0, 0x20, 0xea, 0x42, 0x71, 0x1c, 0x3c, 0x02, + 0x2b, 0xb6, 0xd5, 0xef, 0x60, 0x13, 0x0f, 0x08, 0x3b, 0xff, 0xf7, 0xf9, 0xff, 0x42, 0xf0, 0x7b, + 0x87, 0xf9, 0xd6, 0x9b, 0xc1, 0x0b, 0xe9, 0x7e, 0x16, 0xc2, 0x3c, 0xbb, 0xa4, 0x99, 0xef, 0x67, + 0x19, 0x25, 0x34, 0x32, 0x9f, 0xb3, 0xe6, 0x32, 0xff, 0x03, 0x20, 0x2b, 0xae, 0x73, 0x7e, 0xd0, + 0xca, 0xbb, 0x51, 0xa9, 0x9c, 0xeb, 0x6b, 0xd4, 0xa7, 0x25, 0x70, 0x25, 0x73, 0x40, 0x7e, 0x81, + 0x77, 0x1a, 0x19, 0xb7, 0x54, 0x3c, 0x83, 0x5b, 0xda, 0x02, 0xcb, 0xe2, 0x43, 0x58, 0xca, 0x6c, + 0x85, 0x76, 0xb4, 0x9d, 0xec, 0x46, 0x69, 0xbc, 0xec, 0x4e, 0xa5, 0x7c, 0xc6, 0x3b, 0x95, 0x78, + 0x16, 0xe2, 0xff, 0x37, 0xfc, 0xaa, 0xcb, 0x66, 0x21, 0xfe, 0x8d, 0x23, 0x8d, 0x87, 0xdf, 0x0d, + 0x4a, 0x2a, 0x64, 0x98, 0xe3, 0x0c, 0xa9, 0x1a, 0x09, 0x09, 0x52, 0xe8, 0x67, 0xfa, 0xd8, 0xf3, + 0xbe, 0xe4, 0x63, 0xcf, 0xc6, 0x84, 0x52, 0x9e, 0xde, 0x2a, 0xfe, 0x4d, 0x01, 0x2f, 0xe5, 0xee, + 0x01, 0xb8, 0x95, 0xd0, 0xd9, 0xdb, 0x29, 0x9d, 0x7d, 0x39, 0x37, 0x30, 0x26, 0xb6, 0x86, 0xfc, + 0x42, 0xe4, 0xee, 0xc4, 0x0b, 0x11, 0x89, 0x8b, 0x9a, 0x7c, 0x33, 0xd2, 0xda, 0x78, 0xf2, 0xb4, + 0x36, 0xf3, 0xd1, 0xd3, 0xda, 0xcc, 0xc7, 0x4f, 0x6b, 0x33, 0xbf, 0x18, 0xd7, 0x94, 0x27, 0xe3, + 0x9a, 0xf2, 0xd1, 0xb8, 0xa6, 0x7c, 0x3c, 0xae, 0x29, 0xff, 0x1c, 0xd7, 0x94, 0xdf, 0x7e, 0x52, + 0x9b, 0x79, 0x58, 0x18, 0x6e, 0xfe, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x70, 0x5f, 0xbf, 0x58, 0x3d, + 0x26, 0x00, 0x00, } diff --git a/staging/src/k8s.io/api/apps/v1/generated.proto b/staging/src/k8s.io/api/apps/v1/generated.proto index 9c872a8bc05..46473baa821 100644 --- a/staging/src/k8s.io/api/apps/v1/generated.proto +++ b/staging/src/k8s.io/api/apps/v1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -31,6 +31,38 @@ import "k8s.io/apimachinery/pkg/util/intstr/generated.proto"; // Package-wide variables from generator "generated". option go_package = "v1"; +// ControllerRevision implements an immutable snapshot of state data. Clients +// are responsible for serializing and deserializing the objects that contain +// their internal state. +// Once a ControllerRevision has been successfully created, it can not be updated. +// The API Server will fail validation of all requests that attempt to mutate +// the Data field. ControllerRevisions may, however, be deleted. Note that, due to its use by both +// the DaemonSet and StatefulSet controllers for update and rollback, this object is beta. However, +// it may be subject to name and representation changes in future releases, and clients should not +// depend on its stability. It is primarily for internal use by controllers. +message ControllerRevision { + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; + + // Data is the serialized representation of the state. + optional k8s.io.apimachinery.pkg.runtime.RawExtension data = 2; + + // Revision indicates the revision of the state represented by Data. + optional int64 revision = 3; +} + +// ControllerRevisionList is a resource containing a list of ControllerRevision objects. +message ControllerRevisionList { + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; + + // Items is the list of ControllerRevisions + repeated ControllerRevision items = 2; +} + // DaemonSet represents the configuration of a daemon set. message DaemonSet { // Standard object's metadata. @@ -52,6 +84,27 @@ message DaemonSet { optional DaemonSetStatus status = 3; } +// DaemonSetCondition describes the state of a DaemonSet at a certain point. +message DaemonSetCondition { + // Type of DaemonSet condition. + optional string type = 1; + + // Status of the condition, one of True, False, Unknown. + optional string status = 2; + + // Last time the condition transitioned from one status to another. + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.Time lastTransitionTime = 3; + + // The reason for the condition's last transition. + // +optional + optional string reason = 4; + + // A human readable message indicating details about the transition. + // +optional + optional string message = 5; +} + // DaemonSetList is a collection of daemon sets. message DaemonSetList { // Standard list metadata. @@ -67,9 +120,8 @@ message DaemonSetList { message DaemonSetSpec { // A label query over pods that are managed by the daemon set. // Must match in order to be controlled. - // If empty, defaulted to labels on Pod template. + // It must match the pod template's labels. // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors - // +optional optional k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector selector = 1; // An object that describes the pod that will be created. @@ -143,6 +195,12 @@ message DaemonSetStatus { // create the name for the newest ControllerRevision. // +optional optional int32 collisionCount = 9; + + // Represents the latest available observations of a DaemonSet's current state. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + repeated DaemonSetCondition conditions = 10; } // DaemonSetUpdateStrategy is a struct used to control the update strategy for a DaemonSet. @@ -160,6 +218,262 @@ message DaemonSetUpdateStrategy { optional RollingUpdateDaemonSet rollingUpdate = 2; } +// Deployment enables declarative updates for Pods and ReplicaSets. +message Deployment { + // Standard object metadata. + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; + + // Specification of the desired behavior of the Deployment. + // +optional + optional DeploymentSpec spec = 2; + + // Most recently observed status of the Deployment. + // +optional + optional DeploymentStatus status = 3; +} + +// DeploymentCondition describes the state of a deployment at a certain point. +message DeploymentCondition { + // Type of deployment condition. + optional string type = 1; + + // Status of the condition, one of True, False, Unknown. + optional string status = 2; + + // The last time this condition was updated. + optional k8s.io.apimachinery.pkg.apis.meta.v1.Time lastUpdateTime = 6; + + // Last time the condition transitioned from one status to another. + optional k8s.io.apimachinery.pkg.apis.meta.v1.Time lastTransitionTime = 7; + + // The reason for the condition's last transition. + optional string reason = 4; + + // A human readable message indicating details about the transition. + optional string message = 5; +} + +// DeploymentList is a list of Deployments. +message DeploymentList { + // Standard list metadata. + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; + + // Items is the list of Deployments. + repeated Deployment items = 2; +} + +// DeploymentSpec is the specification of the desired behavior of the Deployment. +message DeploymentSpec { + // Number of desired pods. This is a pointer to distinguish between explicit + // zero and not specified. Defaults to 1. + // +optional + optional int32 replicas = 1; + + // Label selector for pods. Existing ReplicaSets whose pods are + // selected by this will be the ones affected by this deployment. + // It must match the pod template's labels. + optional k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector selector = 2; + + // Template describes the pods that will be created. + optional k8s.io.api.core.v1.PodTemplateSpec template = 3; + + // The deployment strategy to use to replace existing pods with new ones. + // +optional + optional DeploymentStrategy strategy = 4; + + // 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 + optional int32 minReadySeconds = 5; + + // The number of old ReplicaSets to retain to allow rollback. + // This is a pointer to distinguish between explicit zero and not specified. + // Defaults to 10. + // +optional + optional int32 revisionHistoryLimit = 6; + + // Indicates that the deployment is paused. + // +optional + optional bool paused = 7; + + // The maximum time in seconds for a deployment to make progress before it + // is considered to be failed. The deployment controller will continue to + // process failed deployments and a condition with a ProgressDeadlineExceeded + // reason will be surfaced in the deployment status. Note that progress will + // not be estimated during the time a deployment is paused. Defaults to 600s. + optional int32 progressDeadlineSeconds = 9; +} + +// DeploymentStatus is the most recently observed status of the Deployment. +message DeploymentStatus { + // The generation observed by the deployment controller. + // +optional + optional int64 observedGeneration = 1; + + // Total number of non-terminated pods targeted by this deployment (their labels match the selector). + // +optional + optional int32 replicas = 2; + + // Total number of non-terminated pods targeted by this deployment that have the desired template spec. + // +optional + optional int32 updatedReplicas = 3; + + // Total number of ready pods targeted by this deployment. + // +optional + optional int32 readyReplicas = 7; + + // Total number of available pods (ready for at least minReadySeconds) targeted by this deployment. + // +optional + optional int32 availableReplicas = 4; + + // Total number of unavailable pods targeted by this deployment. This is the total number of + // pods that are still required for the deployment to have 100% available capacity. They may + // either be pods that are running but not yet available or pods that still have not been created. + // +optional + optional int32 unavailableReplicas = 5; + + // Represents the latest available observations of a deployment's current state. + // +patchMergeKey=type + // +patchStrategy=merge + repeated DeploymentCondition conditions = 6; + + // Count of hash collisions for the Deployment. The Deployment controller uses this + // field as a collision avoidance mechanism when it needs to create the name for the + // newest ReplicaSet. + // +optional + optional int32 collisionCount = 8; +} + +// DeploymentStrategy describes how to replace existing pods with new ones. +message DeploymentStrategy { + // Type of deployment. Can be "Recreate" or "RollingUpdate". Default is RollingUpdate. + // +optional + optional string type = 1; + + // Rolling update config params. Present only if DeploymentStrategyType = + // RollingUpdate. + // --- + // TODO: Update this to follow our convention for oneOf, whatever we decide it + // to be. + // +optional + optional RollingUpdateDeployment rollingUpdate = 2; +} + +// ReplicaSet ensures that a specified number of pod replicas are running at any given time. +message ReplicaSet { + // If the Labels of a ReplicaSet are empty, they are defaulted to + // be the same as the Pod(s) that the ReplicaSet manages. + // Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; + + // Spec defines the specification of the desired behavior of the ReplicaSet. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status + // +optional + optional ReplicaSetSpec spec = 2; + + // Status is the most recently observed status of the ReplicaSet. + // This data may be out of date by some window of time. + // Populated by the system. + // Read-only. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status + // +optional + optional ReplicaSetStatus status = 3; +} + +// ReplicaSetCondition describes the state of a replica set at a certain point. +message ReplicaSetCondition { + // Type of replica set condition. + optional string type = 1; + + // Status of the condition, one of True, False, Unknown. + optional string status = 2; + + // The last time the condition transitioned from one status to another. + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.Time lastTransitionTime = 3; + + // The reason for the condition's last transition. + // +optional + optional string reason = 4; + + // A human readable message indicating details about the transition. + // +optional + optional string message = 5; +} + +// ReplicaSetList is a collection of ReplicaSets. +message ReplicaSetList { + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; + + // List of ReplicaSets. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller + repeated ReplicaSet items = 2; +} + +// ReplicaSetSpec is the specification of a ReplicaSet. +message ReplicaSetSpec { + // Replicas is the number of desired replicas. + // This is a pointer to distinguish between explicit zero and unspecified. + // Defaults to 1. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller + // +optional + 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 + optional int32 minReadySeconds = 4; + + // Selector is a label query over pods that should match the replica count. + // Label keys and values that must match in order to be controlled by this replica set. + // It must match the pod template's labels. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors + optional k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector selector = 2; + + // Template is the object that describes the pod that will be created if + // insufficient replicas are detected. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template + // +optional + optional k8s.io.api.core.v1.PodTemplateSpec template = 3; +} + +// ReplicaSetStatus represents the current status of a ReplicaSet. +message ReplicaSetStatus { + // Replicas is the most recently oberved number of replicas. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller + optional int32 replicas = 1; + + // The number of pods that have labels matching the labels of the pod template of the replicaset. + // +optional + optional int32 fullyLabeledReplicas = 2; + + // The number of ready replicas for this replica set. + // +optional + optional int32 readyReplicas = 4; + + // The number of available replicas (ready for at least minReadySeconds) for this replica set. + // +optional + optional int32 availableReplicas = 5; + + // ObservedGeneration reflects the generation of the most recently observed ReplicaSet. + // +optional + optional int64 observedGeneration = 3; + + // Represents the latest available observations of a replica set's current state. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + repeated ReplicaSetCondition conditions = 6; +} + // Spec to control the desired behavior of daemon set rolling update. message RollingUpdateDaemonSet { // The maximum number of DaemonSet pods that can be unavailable during the @@ -180,3 +494,208 @@ message RollingUpdateDaemonSet { optional k8s.io.apimachinery.pkg.util.intstr.IntOrString maxUnavailable = 1; } +// Spec to control the desired behavior of rolling update. +message RollingUpdateDeployment { + // The maximum number of pods that can be unavailable during the update. + // Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + // Absolute number is calculated from percentage by rounding down. + // This can not be 0 if MaxSurge is 0. + // Defaults to 25%. + // Example: when this is set to 30%, the old RC can be scaled down to 70% of desired pods + // immediately when the rolling update starts. Once new pods are ready, old RC + // can be scaled down further, followed by scaling up the new RC, ensuring + // that the total number of pods available at all times during the update is at + // least 70% of desired pods. + // +optional + optional k8s.io.apimachinery.pkg.util.intstr.IntOrString maxUnavailable = 1; + + // The maximum number of pods that can be scheduled above the desired number of + // pods. + // Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + // This can not be 0 if MaxUnavailable is 0. + // Absolute number is calculated from percentage by rounding up. + // Defaults to 25%. + // Example: when this is set to 30%, the new RC can be scaled up immediately when + // the rolling update starts, such that the total number of old and new pods do not exceed + // 130% of desired pods. Once old pods have been killed, + // new RC can be scaled up further, ensuring that total number of pods running + // at any time during the update is at most 130% of desired pods. + // +optional + optional k8s.io.apimachinery.pkg.util.intstr.IntOrString maxSurge = 2; +} + +// RollingUpdateStatefulSetStrategy is used to communicate parameter for RollingUpdateStatefulSetStrategyType. +message RollingUpdateStatefulSetStrategy { + // Partition indicates the ordinal at which the StatefulSet should be + // partitioned. + // Default value is 0. + // +optional + optional int32 partition = 1; +} + +// StatefulSet represents a set of pods with consistent identities. +// Identities are defined as: +// - Network: A single stable DNS and hostname. +// - Storage: As many VolumeClaims as requested. +// The StatefulSet guarantees that a given network identity will always +// map to the same storage identity. +message StatefulSet { + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; + + // Spec defines the desired identities of pods in this set. + // +optional + optional StatefulSetSpec spec = 2; + + // Status is the current status of Pods in this StatefulSet. This data + // may be out of date by some window of time. + // +optional + optional StatefulSetStatus status = 3; +} + +// StatefulSetCondition describes the state of a statefulset at a certain point. +message StatefulSetCondition { + // Type of statefulset condition. + optional string type = 1; + + // Status of the condition, one of True, False, Unknown. + optional string status = 2; + + // Last time the condition transitioned from one status to another. + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.Time lastTransitionTime = 3; + + // The reason for the condition's last transition. + // +optional + optional string reason = 4; + + // A human readable message indicating details about the transition. + // +optional + optional string message = 5; +} + +// StatefulSetList is a collection of StatefulSets. +message StatefulSetList { + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; + + repeated StatefulSet items = 2; +} + +// A StatefulSetSpec is the specification of a StatefulSet. +message StatefulSetSpec { + // replicas is the desired number of replicas of the given Template. + // These are replicas in the sense that they are instantiations of the + // same Template, but individual replicas also have a consistent identity. + // If unspecified, defaults to 1. + // TODO: Consider a rename of this field. + // +optional + optional int32 replicas = 1; + + // selector is a label query over pods that should match the replica count. + // It must match the pod template's labels. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors + optional k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector selector = 2; + + // template is the object that describes the pod that will be created if + // insufficient replicas are detected. Each pod stamped out by the StatefulSet + // will fulfill this Template, but have a unique identity from the rest + // of the StatefulSet. + optional k8s.io.api.core.v1.PodTemplateSpec template = 3; + + // volumeClaimTemplates is a list of claims that pods are allowed to reference. + // The StatefulSet controller is responsible for mapping network identities to + // claims in a way that maintains the identity of a pod. Every claim in + // this list must have at least one matching (by name) volumeMount in one + // container in the template. A claim in this list takes precedence over + // any volumes in the template, with the same name. + // TODO: Define the behavior if a claim already exists with the same name. + // +optional + repeated k8s.io.api.core.v1.PersistentVolumeClaim volumeClaimTemplates = 4; + + // serviceName is the name of the service that governs this StatefulSet. + // This service must exist before the StatefulSet, and is responsible for + // the network identity of the set. Pods get DNS/hostnames that follow the + // pattern: pod-specific-string.serviceName.default.svc.cluster.local + // where "pod-specific-string" is managed by the StatefulSet controller. + optional string serviceName = 5; + + // podManagementPolicy controls how pods are created during initial scale up, + // when replacing pods on nodes, or when scaling down. The default policy is + // `OrderedReady`, where pods are created in increasing order (pod-0, then + // pod-1, etc) and the controller will wait until each pod is ready before + // continuing. When scaling down, the pods are removed in the opposite order. + // The alternative policy is `Parallel` which will create pods in parallel + // to match the desired scale without waiting, and on scale down will delete + // all pods at once. + // +optional + optional string podManagementPolicy = 6; + + // updateStrategy indicates the StatefulSetUpdateStrategy that will be + // employed to update Pods in the StatefulSet when a revision is made to + // Template. + optional StatefulSetUpdateStrategy updateStrategy = 7; + + // revisionHistoryLimit is the maximum number of revisions that will + // be maintained in the StatefulSet's revision history. The revision history + // consists of all revisions not represented by a currently applied + // StatefulSetSpec version. The default value is 10. + optional int32 revisionHistoryLimit = 8; +} + +// StatefulSetStatus represents the current state of a StatefulSet. +message StatefulSetStatus { + // observedGeneration is the most recent generation observed for this StatefulSet. It corresponds to the + // StatefulSet's generation, which is updated on mutation by the API Server. + // +optional + optional int64 observedGeneration = 1; + + // replicas is the number of Pods created by the StatefulSet controller. + optional int32 replicas = 2; + + // readyReplicas is the number of Pods created by the StatefulSet controller that have a Ready Condition. + optional int32 readyReplicas = 3; + + // currentReplicas is the number of Pods created by the StatefulSet controller from the StatefulSet version + // indicated by currentRevision. + optional int32 currentReplicas = 4; + + // updatedReplicas is the number of Pods created by the StatefulSet controller from the StatefulSet version + // indicated by updateRevision. + optional int32 updatedReplicas = 5; + + // currentRevision, if not empty, indicates the version of the StatefulSet used to generate Pods in the + // sequence [0,currentReplicas). + optional string currentRevision = 6; + + // updateRevision, if not empty, indicates the version of the StatefulSet used to generate Pods in the sequence + // [replicas-updatedReplicas,replicas) + optional string updateRevision = 7; + + // collisionCount is the count of hash collisions for the StatefulSet. The StatefulSet controller + // uses this field as a collision avoidance mechanism when it needs to create the name for the + // newest ControllerRevision. + // +optional + optional int32 collisionCount = 9; + + // Represents the latest available observations of a statefulset's current state. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + repeated StatefulSetCondition conditions = 10; +} + +// StatefulSetUpdateStrategy indicates the strategy that the StatefulSet +// controller will use to perform updates. It includes any additional parameters +// necessary to perform the update for the indicated strategy. +message StatefulSetUpdateStrategy { + // Type indicates the type of the StatefulSetUpdateStrategy. + // Default is RollingUpdate. + // +optional + optional string type = 1; + + // RollingUpdate is used to communicate parameters when Type is RollingUpdateStatefulSetStrategyType. + // +optional + optional RollingUpdateStatefulSetStrategy rollingUpdate = 2; +} + diff --git a/staging/src/k8s.io/api/apps/v1/register.go b/staging/src/k8s.io/api/apps/v1/register.go index 586b53ef0e9..02710104674 100644 --- a/staging/src/k8s.io/api/apps/v1/register.go +++ b/staging/src/k8s.io/api/apps/v1/register.go @@ -41,11 +41,19 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, + &Deployment{}, + &DeploymentList{}, + &StatefulSet{}, + &StatefulSetList{}, &DaemonSet{}, &DaemonSetList{}, + &ReplicaSet{}, + &ReplicaSetList{}, + &ControllerRevision{}, + &ControllerRevisionList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/staging/src/k8s.io/api/apps/v1/types.go b/staging/src/k8s.io/api/apps/v1/types.go index dc589e658de..b5df22c6fd4 100644 --- a/staging/src/k8s.io/api/apps/v1/types.go +++ b/staging/src/k8s.io/api/apps/v1/types.go @@ -19,14 +19,440 @@ package v1 import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" ) const ( ControllerRevisionHashLabelKey = "controller-revision-hash" + StatefulSetRevisionLabel = ControllerRevisionHashLabelKey + DeprecatedRollbackTo = "deprecated.deployment.rollback.to" DeprecatedTemplateGeneration = "deprecated.daemonset.template.generation" + StatefulSetPodNameLabel = "statefulset.kubernetes.io/pod-name" ) +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// StatefulSet represents a set of pods with consistent identities. +// Identities are defined as: +// - Network: A single stable DNS and hostname. +// - Storage: As many VolumeClaims as requested. +// The StatefulSet guarantees that a given network identity will always +// map to the same storage identity. +type StatefulSet struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Spec defines the desired identities of pods in this set. + // +optional + Spec StatefulSetSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` + + // Status is the current status of Pods in this StatefulSet. This data + // may be out of date by some window of time. + // +optional + Status StatefulSetStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` +} + +// PodManagementPolicyType defines the policy for creating pods under a stateful set. +type PodManagementPolicyType string + +const ( + // OrderedReadyPodManagement will create pods in strictly increasing order on + // scale up and strictly decreasing order on scale down, progressing only when + // the previous pod is ready or terminated. At most one pod will be changed + // at any time. + OrderedReadyPodManagement PodManagementPolicyType = "OrderedReady" + // ParallelPodManagement will create and delete pods as soon as the stateful set + // replica count is changed, and will not wait for pods to be ready or complete + // termination. + ParallelPodManagement = "Parallel" +) + +// StatefulSetUpdateStrategy indicates the strategy that the StatefulSet +// controller will use to perform updates. It includes any additional parameters +// necessary to perform the update for the indicated strategy. +type StatefulSetUpdateStrategy struct { + // Type indicates the type of the StatefulSetUpdateStrategy. + // Default is RollingUpdate. + // +optional + Type StatefulSetUpdateStrategyType `json:"type,omitempty" protobuf:"bytes,1,opt,name=type,casttype=StatefulSetStrategyType"` + // RollingUpdate is used to communicate parameters when Type is RollingUpdateStatefulSetStrategyType. + // +optional + RollingUpdate *RollingUpdateStatefulSetStrategy `json:"rollingUpdate,omitempty" protobuf:"bytes,2,opt,name=rollingUpdate"` +} + +// StatefulSetUpdateStrategyType is a string enumeration type that enumerates +// all possible update strategies for the StatefulSet controller. +type StatefulSetUpdateStrategyType string + +const ( + // RollingUpdateStatefulSetStrategyType indicates that update will be + // applied to all Pods in the StatefulSet with respect to the StatefulSet + // ordering constraints. When a scale operation is performed with this + // strategy, new Pods will be created from the specification version indicated + // by the StatefulSet's updateRevision. + RollingUpdateStatefulSetStrategyType = "RollingUpdate" + // OnDeleteStatefulSetStrategyType triggers the legacy behavior. Version + // tracking and ordered rolling restarts are disabled. Pods are recreated + // from the StatefulSetSpec when they are manually deleted. When a scale + // operation is performed with this strategy,specification version indicated + // by the StatefulSet's currentRevision. + OnDeleteStatefulSetStrategyType = "OnDelete" +) + +// RollingUpdateStatefulSetStrategy is used to communicate parameter for RollingUpdateStatefulSetStrategyType. +type RollingUpdateStatefulSetStrategy struct { + // Partition indicates the ordinal at which the StatefulSet should be + // partitioned. + // Default value is 0. + // +optional + Partition *int32 `json:"partition,omitempty" protobuf:"varint,1,opt,name=partition"` +} + +// A StatefulSetSpec is the specification of a StatefulSet. +type StatefulSetSpec struct { + // replicas is the desired number of replicas of the given Template. + // These are replicas in the sense that they are instantiations of the + // same Template, but individual replicas also have a consistent identity. + // If unspecified, defaults to 1. + // TODO: Consider a rename of this field. + // +optional + Replicas *int32 `json:"replicas,omitempty" protobuf:"varint,1,opt,name=replicas"` + + // selector is a label query over pods that should match the replica count. + // It must match the pod template's labels. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors + Selector *metav1.LabelSelector `json:"selector" protobuf:"bytes,2,opt,name=selector"` + + // template is the object that describes the pod that will be created if + // insufficient replicas are detected. Each pod stamped out by the StatefulSet + // will fulfill this Template, but have a unique identity from the rest + // of the StatefulSet. + Template v1.PodTemplateSpec `json:"template" protobuf:"bytes,3,opt,name=template"` + + // volumeClaimTemplates is a list of claims that pods are allowed to reference. + // The StatefulSet controller is responsible for mapping network identities to + // claims in a way that maintains the identity of a pod. Every claim in + // this list must have at least one matching (by name) volumeMount in one + // container in the template. A claim in this list takes precedence over + // any volumes in the template, with the same name. + // TODO: Define the behavior if a claim already exists with the same name. + // +optional + VolumeClaimTemplates []v1.PersistentVolumeClaim `json:"volumeClaimTemplates,omitempty" protobuf:"bytes,4,rep,name=volumeClaimTemplates"` + + // serviceName is the name of the service that governs this StatefulSet. + // This service must exist before the StatefulSet, and is responsible for + // the network identity of the set. Pods get DNS/hostnames that follow the + // pattern: pod-specific-string.serviceName.default.svc.cluster.local + // where "pod-specific-string" is managed by the StatefulSet controller. + ServiceName string `json:"serviceName" protobuf:"bytes,5,opt,name=serviceName"` + + // podManagementPolicy controls how pods are created during initial scale up, + // when replacing pods on nodes, or when scaling down. The default policy is + // `OrderedReady`, where pods are created in increasing order (pod-0, then + // pod-1, etc) and the controller will wait until each pod is ready before + // continuing. When scaling down, the pods are removed in the opposite order. + // The alternative policy is `Parallel` which will create pods in parallel + // to match the desired scale without waiting, and on scale down will delete + // all pods at once. + // +optional + PodManagementPolicy PodManagementPolicyType `json:"podManagementPolicy,omitempty" protobuf:"bytes,6,opt,name=podManagementPolicy,casttype=PodManagementPolicyType"` + + // updateStrategy indicates the StatefulSetUpdateStrategy that will be + // employed to update Pods in the StatefulSet when a revision is made to + // Template. + UpdateStrategy StatefulSetUpdateStrategy `json:"updateStrategy,omitempty" protobuf:"bytes,7,opt,name=updateStrategy"` + + // revisionHistoryLimit is the maximum number of revisions that will + // be maintained in the StatefulSet's revision history. The revision history + // consists of all revisions not represented by a currently applied + // StatefulSetSpec version. The default value is 10. + RevisionHistoryLimit *int32 `json:"revisionHistoryLimit,omitempty" protobuf:"varint,8,opt,name=revisionHistoryLimit"` +} + +// StatefulSetStatus represents the current state of a StatefulSet. +type StatefulSetStatus struct { + // observedGeneration is the most recent generation observed for this StatefulSet. It corresponds to the + // StatefulSet's generation, which is updated on mutation by the API Server. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,1,opt,name=observedGeneration"` + + // replicas is the number of Pods created by the StatefulSet controller. + Replicas int32 `json:"replicas" protobuf:"varint,2,opt,name=replicas"` + + // readyReplicas is the number of Pods created by the StatefulSet controller that have a Ready Condition. + ReadyReplicas int32 `json:"readyReplicas,omitempty" protobuf:"varint,3,opt,name=readyReplicas"` + + // currentReplicas is the number of Pods created by the StatefulSet controller from the StatefulSet version + // indicated by currentRevision. + CurrentReplicas int32 `json:"currentReplicas,omitempty" protobuf:"varint,4,opt,name=currentReplicas"` + + // updatedReplicas is the number of Pods created by the StatefulSet controller from the StatefulSet version + // indicated by updateRevision. + UpdatedReplicas int32 `json:"updatedReplicas,omitempty" protobuf:"varint,5,opt,name=updatedReplicas"` + + // currentRevision, if not empty, indicates the version of the StatefulSet used to generate Pods in the + // sequence [0,currentReplicas). + CurrentRevision string `json:"currentRevision,omitempty" protobuf:"bytes,6,opt,name=currentRevision"` + + // updateRevision, if not empty, indicates the version of the StatefulSet used to generate Pods in the sequence + // [replicas-updatedReplicas,replicas) + UpdateRevision string `json:"updateRevision,omitempty" protobuf:"bytes,7,opt,name=updateRevision"` + + // collisionCount is the count of hash collisions for the StatefulSet. The StatefulSet controller + // uses this field as a collision avoidance mechanism when it needs to create the name for the + // newest ControllerRevision. + // +optional + CollisionCount *int32 `json:"collisionCount,omitempty" protobuf:"varint,9,opt,name=collisionCount"` + + // Represents the latest available observations of a statefulset's current state. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + Conditions []StatefulSetCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,10,rep,name=conditions"` +} + +type StatefulSetConditionType string + +// StatefulSetCondition describes the state of a statefulset at a certain point. +type StatefulSetCondition struct { + // Type of statefulset condition. + Type StatefulSetConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=StatefulSetConditionType"` + // Status of the condition, one of True, False, Unknown. + Status v1.ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=k8s.io/api/core/v1.ConditionStatus"` + // Last time the condition transitioned from one status to another. + // +optional + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,3,opt,name=lastTransitionTime"` + // The reason for the condition's last transition. + // +optional + Reason string `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"` + // A human readable message indicating details about the transition. + // +optional + Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// StatefulSetList is a collection of StatefulSets. +type StatefulSetList struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + Items []StatefulSet `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Deployment enables declarative updates for Pods and ReplicaSets. +type Deployment struct { + metav1.TypeMeta `json:",inline"` + // Standard object metadata. + // +optional + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Specification of the desired behavior of the Deployment. + // +optional + Spec DeploymentSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` + + // Most recently observed status of the Deployment. + // +optional + Status DeploymentStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` +} + +// DeploymentSpec is the specification of the desired behavior of the Deployment. +type DeploymentSpec struct { + // Number of desired pods. This is a pointer to distinguish between explicit + // zero and not specified. Defaults to 1. + // +optional + Replicas *int32 `json:"replicas,omitempty" protobuf:"varint,1,opt,name=replicas"` + + // Label selector for pods. Existing ReplicaSets whose pods are + // selected by this will be the ones affected by this deployment. + // It must match the pod template's labels. + Selector *metav1.LabelSelector `json:"selector" protobuf:"bytes,2,opt,name=selector"` + + // Template describes the pods that will be created. + Template v1.PodTemplateSpec `json:"template" protobuf:"bytes,3,opt,name=template"` + + // The deployment strategy to use to replace existing pods with new ones. + // +optional + Strategy DeploymentStrategy `json:"strategy,omitempty" protobuf:"bytes,4,opt,name=strategy"` + + // 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 + MinReadySeconds int32 `json:"minReadySeconds,omitempty" protobuf:"varint,5,opt,name=minReadySeconds"` + + // The number of old ReplicaSets to retain to allow rollback. + // This is a pointer to distinguish between explicit zero and not specified. + // Defaults to 10. + // +optional + RevisionHistoryLimit *int32 `json:"revisionHistoryLimit,omitempty" protobuf:"varint,6,opt,name=revisionHistoryLimit"` + + // Indicates that the deployment is paused. + // +optional + Paused bool `json:"paused,omitempty" protobuf:"varint,7,opt,name=paused"` + + // The maximum time in seconds for a deployment to make progress before it + // is considered to be failed. The deployment controller will continue to + // process failed deployments and a condition with a ProgressDeadlineExceeded + // reason will be surfaced in the deployment status. Note that progress will + // not be estimated during the time a deployment is paused. Defaults to 600s. + ProgressDeadlineSeconds *int32 `json:"progressDeadlineSeconds,omitempty" protobuf:"varint,9,opt,name=progressDeadlineSeconds"` +} + +const ( + // DefaultDeploymentUniqueLabelKey is the default key of the selector that is added + // to existing RCs (and label key that is added to its pods) to prevent the existing RCs + // to select new pods (and old pods being select by new RC). + DefaultDeploymentUniqueLabelKey string = "pod-template-hash" +) + +// DeploymentStrategy describes how to replace existing pods with new ones. +type DeploymentStrategy struct { + // Type of deployment. Can be "Recreate" or "RollingUpdate". Default is RollingUpdate. + // +optional + Type DeploymentStrategyType `json:"type,omitempty" protobuf:"bytes,1,opt,name=type,casttype=DeploymentStrategyType"` + + // Rolling update config params. Present only if DeploymentStrategyType = + // RollingUpdate. + //--- + // TODO: Update this to follow our convention for oneOf, whatever we decide it + // to be. + // +optional + RollingUpdate *RollingUpdateDeployment `json:"rollingUpdate,omitempty" protobuf:"bytes,2,opt,name=rollingUpdate"` +} + +type DeploymentStrategyType string + +const ( + // Kill all existing pods before creating new ones. + RecreateDeploymentStrategyType DeploymentStrategyType = "Recreate" + + // Replace the old RCs by new one using rolling update i.e gradually scale down the old RCs and scale up the new one. + RollingUpdateDeploymentStrategyType DeploymentStrategyType = "RollingUpdate" +) + +// Spec to control the desired behavior of rolling update. +type RollingUpdateDeployment struct { + // The maximum number of pods that can be unavailable during the update. + // Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + // Absolute number is calculated from percentage by rounding down. + // This can not be 0 if MaxSurge is 0. + // Defaults to 25%. + // Example: when this is set to 30%, the old RC can be scaled down to 70% of desired pods + // immediately when the rolling update starts. Once new pods are ready, old RC + // can be scaled down further, followed by scaling up the new RC, ensuring + // that the total number of pods available at all times during the update is at + // least 70% of desired pods. + // +optional + MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty" protobuf:"bytes,1,opt,name=maxUnavailable"` + + // The maximum number of pods that can be scheduled above the desired number of + // pods. + // Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + // This can not be 0 if MaxUnavailable is 0. + // Absolute number is calculated from percentage by rounding up. + // Defaults to 25%. + // Example: when this is set to 30%, the new RC can be scaled up immediately when + // the rolling update starts, such that the total number of old and new pods do not exceed + // 130% of desired pods. Once old pods have been killed, + // new RC can be scaled up further, ensuring that total number of pods running + // at any time during the update is at most 130% of desired pods. + // +optional + MaxSurge *intstr.IntOrString `json:"maxSurge,omitempty" protobuf:"bytes,2,opt,name=maxSurge"` +} + +// DeploymentStatus is the most recently observed status of the Deployment. +type DeploymentStatus struct { + // The generation observed by the deployment controller. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,1,opt,name=observedGeneration"` + + // Total number of non-terminated pods targeted by this deployment (their labels match the selector). + // +optional + Replicas int32 `json:"replicas,omitempty" protobuf:"varint,2,opt,name=replicas"` + + // Total number of non-terminated pods targeted by this deployment that have the desired template spec. + // +optional + UpdatedReplicas int32 `json:"updatedReplicas,omitempty" protobuf:"varint,3,opt,name=updatedReplicas"` + + // Total number of ready pods targeted by this deployment. + // +optional + ReadyReplicas int32 `json:"readyReplicas,omitempty" protobuf:"varint,7,opt,name=readyReplicas"` + + // Total number of available pods (ready for at least minReadySeconds) targeted by this deployment. + // +optional + AvailableReplicas int32 `json:"availableReplicas,omitempty" protobuf:"varint,4,opt,name=availableReplicas"` + + // Total number of unavailable pods targeted by this deployment. This is the total number of + // pods that are still required for the deployment to have 100% available capacity. They may + // either be pods that are running but not yet available or pods that still have not been created. + // +optional + UnavailableReplicas int32 `json:"unavailableReplicas,omitempty" protobuf:"varint,5,opt,name=unavailableReplicas"` + + // Represents the latest available observations of a deployment's current state. + // +patchMergeKey=type + // +patchStrategy=merge + Conditions []DeploymentCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,6,rep,name=conditions"` + + // Count of hash collisions for the Deployment. The Deployment controller uses this + // field as a collision avoidance mechanism when it needs to create the name for the + // newest ReplicaSet. + // +optional + CollisionCount *int32 `json:"collisionCount,omitempty" protobuf:"varint,8,opt,name=collisionCount"` +} + +type DeploymentConditionType string + +// These are valid conditions of a deployment. +const ( + // Available means the deployment is available, ie. at least the minimum available + // replicas required are up and running for at least minReadySeconds. + DeploymentAvailable DeploymentConditionType = "Available" + // Progressing means the deployment is progressing. Progress for a deployment is + // considered when a new replica set is created or adopted, and when new pods scale + // up or old pods scale down. Progress is not estimated for paused deployments or + // when progressDeadlineSeconds is not specified. + DeploymentProgressing DeploymentConditionType = "Progressing" + // ReplicaFailure is added in a deployment when one of its pods fails to be created + // or deleted. + DeploymentReplicaFailure DeploymentConditionType = "ReplicaFailure" +) + +// DeploymentCondition describes the state of a deployment at a certain point. +type DeploymentCondition struct { + // Type of deployment condition. + Type DeploymentConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=DeploymentConditionType"` + // Status of the condition, one of True, False, Unknown. + Status v1.ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=k8s.io/api/core/v1.ConditionStatus"` + // The last time this condition was updated. + LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty" protobuf:"bytes,6,opt,name=lastUpdateTime"` + // Last time the condition transitioned from one status to another. + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,7,opt,name=lastTransitionTime"` + // The reason for the condition's last transition. + Reason string `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"` + // A human readable message indicating details about the transition. + Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// DeploymentList is a list of Deployments. +type DeploymentList 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 Deployments. + Items []Deployment `json:"items" protobuf:"bytes,2,rep,name=items"` +} + // DaemonSetUpdateStrategy is a struct used to control the update strategy for a DaemonSet. type DaemonSetUpdateStrategy struct { // Type of daemon set update. Can be "RollingUpdate" or "OnDelete". Default is RollingUpdate. @@ -76,10 +502,9 @@ type RollingUpdateDaemonSet struct { type DaemonSetSpec struct { // A label query over pods that are managed by the daemon set. // Must match in order to be controlled. - // If empty, defaulted to labels on Pod template. + // It must match the pod template's labels. // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors - // +optional - Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,1,opt,name=selector"` + Selector *metav1.LabelSelector `json:"selector" protobuf:"bytes,1,opt,name=selector"` // An object that describes the pod that will be created. // The DaemonSet will create exactly one copy of this pod on every node @@ -152,6 +577,33 @@ type DaemonSetStatus struct { // create the name for the newest ControllerRevision. // +optional CollisionCount *int32 `json:"collisionCount,omitempty" protobuf:"varint,9,opt,name=collisionCount"` + + // Represents the latest available observations of a DaemonSet's current state. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + Conditions []DaemonSetCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,10,rep,name=conditions"` +} + +type DaemonSetConditionType string + +// TODO: Add valid condition types of a DaemonSet. + +// DaemonSetCondition describes the state of a DaemonSet at a certain point. +type DaemonSetCondition struct { + // Type of DaemonSet condition. + Type DaemonSetConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=DaemonSetConditionType"` + // Status of the condition, one of True, False, Unknown. + Status v1.ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=k8s.io/api/core/v1.ConditionStatus"` + // Last time the condition transitioned from one status to another. + // +optional + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,3,opt,name=lastTransitionTime"` + // The reason for the condition's last transition. + // +optional + Reason string `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"` + // A human readable message indicating details about the transition. + // +optional + Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"` } // +genclient @@ -199,3 +651,169 @@ type DaemonSetList struct { // A list of daemon sets. Items []DaemonSet `json:"items" protobuf:"bytes,2,rep,name=items"` } + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ReplicaSet ensures that a specified number of pod replicas are running at any given time. +type ReplicaSet struct { + metav1.TypeMeta `json:",inline"` + + // If the Labels of a ReplicaSet are empty, they are defaulted to + // be the same as the Pod(s) that the ReplicaSet manages. + // Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Spec defines the specification of the desired behavior of the ReplicaSet. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status + // +optional + Spec ReplicaSetSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` + + // Status is the most recently observed status of the ReplicaSet. + // This data may be out of date by some window of time. + // Populated by the system. + // Read-only. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status + // +optional + Status ReplicaSetStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ReplicaSetList is a collection of ReplicaSets. +type ReplicaSetList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // List of ReplicaSets. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller + Items []ReplicaSet `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +// ReplicaSetSpec is the specification of a ReplicaSet. +type ReplicaSetSpec struct { + // Replicas is the number of desired replicas. + // This is a pointer to distinguish between explicit zero and unspecified. + // Defaults to 1. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller + // +optional + 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 + MinReadySeconds int32 `json:"minReadySeconds,omitempty" protobuf:"varint,4,opt,name=minReadySeconds"` + + // Selector is a label query over pods that should match the replica count. + // Label keys and values that must match in order to be controlled by this replica set. + // It must match the pod template's labels. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors + Selector *metav1.LabelSelector `json:"selector" protobuf:"bytes,2,opt,name=selector"` + + // Template is the object that describes the pod that will be created if + // insufficient replicas are detected. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template + // +optional + Template v1.PodTemplateSpec `json:"template,omitempty" protobuf:"bytes,3,opt,name=template"` +} + +// ReplicaSetStatus represents the current status of a ReplicaSet. +type ReplicaSetStatus struct { + // Replicas is the most recently oberved number of replicas. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller + Replicas int32 `json:"replicas" protobuf:"varint,1,opt,name=replicas"` + + // The number of pods that have labels matching the labels of the pod template of the replicaset. + // +optional + FullyLabeledReplicas int32 `json:"fullyLabeledReplicas,omitempty" protobuf:"varint,2,opt,name=fullyLabeledReplicas"` + + // The number of ready replicas for this replica set. + // +optional + ReadyReplicas int32 `json:"readyReplicas,omitempty" protobuf:"varint,4,opt,name=readyReplicas"` + + // The number of available replicas (ready for at least minReadySeconds) for this replica set. + // +optional + AvailableReplicas int32 `json:"availableReplicas,omitempty" protobuf:"varint,5,opt,name=availableReplicas"` + + // ObservedGeneration reflects the generation of the most recently observed ReplicaSet. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,3,opt,name=observedGeneration"` + + // Represents the latest available observations of a replica set's current state. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + Conditions []ReplicaSetCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,6,rep,name=conditions"` +} + +type ReplicaSetConditionType string + +// These are valid conditions of a replica set. +const ( + // ReplicaSetReplicaFailure is added in a replica set when one of its pods fails to be created + // due to insufficient quota, limit ranges, pod security policy, node selectors, etc. or deleted + // due to kubelet being down or finalizers are failing. + ReplicaSetReplicaFailure ReplicaSetConditionType = "ReplicaFailure" +) + +// ReplicaSetCondition describes the state of a replica set at a certain point. +type ReplicaSetCondition struct { + // Type of replica set condition. + Type ReplicaSetConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=ReplicaSetConditionType"` + // Status of the condition, one of True, False, Unknown. + Status v1.ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=k8s.io/api/core/v1.ConditionStatus"` + // The last time the condition transitioned from one status to another. + // +optional + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,3,opt,name=lastTransitionTime"` + // The reason for the condition's last transition. + // +optional + Reason string `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"` + // A human readable message indicating details about the transition. + // +optional + Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"` +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ControllerRevision implements an immutable snapshot of state data. Clients +// are responsible for serializing and deserializing the objects that contain +// their internal state. +// Once a ControllerRevision has been successfully created, it can not be updated. +// The API Server will fail validation of all requests that attempt to mutate +// the Data field. ControllerRevisions may, however, be deleted. Note that, due to its use by both +// the DaemonSet and StatefulSet controllers for update and rollback, this object is beta. However, +// it may be subject to name and representation changes in future releases, and clients should not +// depend on its stability. It is primarily for internal use by controllers. +type ControllerRevision struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Data is the serialized representation of the state. + Data runtime.RawExtension `json:"data,omitempty" protobuf:"bytes,2,opt,name=data"` + + // Revision indicates the revision of the state represented by Data. + Revision int64 `json:"revision" protobuf:"varint,3,opt,name=revision"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ControllerRevisionList is a resource containing a list of ControllerRevision objects. +type ControllerRevisionList struct { + metav1.TypeMeta `json:",inline"` + + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Items is the list of ControllerRevisions + Items []ControllerRevision `json:"items" protobuf:"bytes,2,rep,name=items"` +} diff --git a/staging/src/k8s.io/api/apps/v1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/apps/v1/types_swagger_doc_generated.go index 4cc96dc4413..4dec6f2f89e 100644 --- a/staging/src/k8s.io/api/apps/v1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/apps/v1/types_swagger_doc_generated.go @@ -27,6 +27,27 @@ package v1 // Those methods can be generated by using hack/update-generated-swagger-docs.sh // AUTO-GENERATED FUNCTIONS START HERE +var map_ControllerRevision = map[string]string{ + "": "ControllerRevision implements an immutable snapshot of state data. Clients are responsible for serializing and deserializing the objects that contain their internal state. Once a ControllerRevision has been successfully created, it can not be updated. The API Server will fail validation of all requests that attempt to mutate the Data field. ControllerRevisions may, however, be deleted. Note that, due to its use by both the DaemonSet and StatefulSet controllers for update and rollback, this object is beta. However, it may be subject to name and representation changes in future releases, and clients should not depend on its stability. It is primarily for internal use by controllers.", + "metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", + "data": "Data is the serialized representation of the state.", + "revision": "Revision indicates the revision of the state represented by Data.", +} + +func (ControllerRevision) SwaggerDoc() map[string]string { + return map_ControllerRevision +} + +var map_ControllerRevisionList = map[string]string{ + "": "ControllerRevisionList is a resource containing a list of ControllerRevision objects.", + "metadata": "More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", + "items": "Items is the list of ControllerRevisions", +} + +func (ControllerRevisionList) SwaggerDoc() map[string]string { + return map_ControllerRevisionList +} + var map_DaemonSet = map[string]string{ "": "DaemonSet represents the configuration of a daemon set.", "metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", @@ -38,6 +59,19 @@ func (DaemonSet) SwaggerDoc() map[string]string { return map_DaemonSet } +var map_DaemonSetCondition = map[string]string{ + "": "DaemonSetCondition describes the state of a DaemonSet at a certain point.", + "type": "Type of DaemonSet condition.", + "status": "Status of the condition, one of True, False, Unknown.", + "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.", +} + +func (DaemonSetCondition) SwaggerDoc() map[string]string { + return map_DaemonSetCondition +} + var map_DaemonSetList = map[string]string{ "": "DaemonSetList is a collection of daemon sets.", "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", @@ -50,7 +84,7 @@ func (DaemonSetList) SwaggerDoc() map[string]string { var map_DaemonSetSpec = map[string]string{ "": "DaemonSetSpec is the specification of a daemon set.", - "selector": "A label query over pods that are managed by the daemon set. Must match in order to be controlled. If empty, defaulted to labels on Pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", + "selector": "A label query over pods that are managed by the daemon set. Must match in order to be controlled. It must match the pod template's labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", "template": "An object that describes the pod that will be created. The DaemonSet will create exactly one copy of this pod on every node that matches the template's node selector (or on every node if no node selector is specified). More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template", "updateStrategy": "An update strategy to replace existing DaemonSet pods with new pods.", "minReadySeconds": "The minimum number of seconds for which a newly created DaemonSet 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).", @@ -72,6 +106,7 @@ var map_DaemonSetStatus = map[string]string{ "numberAvailable": "The number of nodes that should be running the daemon pod and have one or more of the daemon pod running and available (ready for at least spec.minReadySeconds)", "numberUnavailable": "The number of nodes that should be running the daemon pod and have none of the daemon pod running and available (ready for at least spec.minReadySeconds)", "collisionCount": "Count of hash collisions for the DaemonSet. The DaemonSet controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ControllerRevision.", + "conditions": "Represents the latest available observations of a DaemonSet's current state.", } func (DaemonSetStatus) SwaggerDoc() map[string]string { @@ -88,6 +123,143 @@ func (DaemonSetUpdateStrategy) SwaggerDoc() map[string]string { return map_DaemonSetUpdateStrategy } +var map_Deployment = map[string]string{ + "": "Deployment enables declarative updates for Pods and ReplicaSets.", + "metadata": "Standard object metadata.", + "spec": "Specification of the desired behavior of the Deployment.", + "status": "Most recently observed status of the Deployment.", +} + +func (Deployment) SwaggerDoc() map[string]string { + return map_Deployment +} + +var map_DeploymentCondition = map[string]string{ + "": "DeploymentCondition describes the state of a deployment at a certain point.", + "type": "Type of deployment condition.", + "status": "Status of the condition, one of True, False, Unknown.", + "lastUpdateTime": "The last time this condition was updated.", + "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.", +} + +func (DeploymentCondition) SwaggerDoc() map[string]string { + return map_DeploymentCondition +} + +var map_DeploymentList = map[string]string{ + "": "DeploymentList is a list of Deployments.", + "metadata": "Standard list metadata.", + "items": "Items is the list of Deployments.", +} + +func (DeploymentList) SwaggerDoc() map[string]string { + return map_DeploymentList +} + +var map_DeploymentSpec = map[string]string{ + "": "DeploymentSpec is the specification of the desired behavior of the Deployment.", + "replicas": "Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1.", + "selector": "Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment. It must match the pod template's labels.", + "template": "Template describes the pods that will be created.", + "strategy": "The deployment strategy to use to replace existing pods with new ones.", + "minReadySeconds": "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)", + "revisionHistoryLimit": "The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.", + "paused": "Indicates that the deployment is paused.", + "progressDeadlineSeconds": "The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that progress will not be estimated during the time a deployment is paused. Defaults to 600s.", +} + +func (DeploymentSpec) SwaggerDoc() map[string]string { + return map_DeploymentSpec +} + +var map_DeploymentStatus = map[string]string{ + "": "DeploymentStatus is the most recently observed status of the Deployment.", + "observedGeneration": "The generation observed by the deployment controller.", + "replicas": "Total number of non-terminated pods targeted by this deployment (their labels match the selector).", + "updatedReplicas": "Total number of non-terminated pods targeted by this deployment that have the desired template spec.", + "readyReplicas": "Total number of ready pods targeted by this deployment.", + "availableReplicas": "Total number of available pods (ready for at least minReadySeconds) targeted by this deployment.", + "unavailableReplicas": "Total number of unavailable pods targeted by this deployment. This is the total number of pods that are still required for the deployment to have 100% available capacity. They may either be pods that are running but not yet available or pods that still have not been created.", + "conditions": "Represents the latest available observations of a deployment's current state.", + "collisionCount": "Count of hash collisions for the Deployment. The Deployment controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ReplicaSet.", +} + +func (DeploymentStatus) SwaggerDoc() map[string]string { + return map_DeploymentStatus +} + +var map_DeploymentStrategy = map[string]string{ + "": "DeploymentStrategy describes how to replace existing pods with new ones.", + "type": "Type of deployment. Can be \"Recreate\" or \"RollingUpdate\". Default is RollingUpdate.", + "rollingUpdate": "Rolling update config params. Present only if DeploymentStrategyType = RollingUpdate.", +} + +func (DeploymentStrategy) SwaggerDoc() map[string]string { + return map_DeploymentStrategy +} + +var map_ReplicaSet = map[string]string{ + "": "ReplicaSet ensures that a specified number of pod replicas are running at any given time.", + "metadata": "If the Labels of a ReplicaSet are empty, they are defaulted to be the same as the Pod(s) that the ReplicaSet manages. Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", + "spec": "Spec defines the specification of the desired behavior of the ReplicaSet. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status", + "status": "Status is the most recently observed status of the ReplicaSet. This data may be out of date by some window of time. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status", +} + +func (ReplicaSet) SwaggerDoc() map[string]string { + return map_ReplicaSet +} + +var map_ReplicaSetCondition = map[string]string{ + "": "ReplicaSetCondition describes the state of a replica set at a certain point.", + "type": "Type of replica set condition.", + "status": "Status of the condition, one of True, False, Unknown.", + "lastTransitionTime": "The 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.", +} + +func (ReplicaSetCondition) SwaggerDoc() map[string]string { + return map_ReplicaSetCondition +} + +var map_ReplicaSetList = map[string]string{ + "": "ReplicaSetList is a collection of ReplicaSets.", + "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds", + "items": "List of ReplicaSets. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller", +} + +func (ReplicaSetList) SwaggerDoc() map[string]string { + return map_ReplicaSetList +} + +var map_ReplicaSetSpec = map[string]string{ + "": "ReplicaSetSpec is the specification of a ReplicaSet.", + "replicas": "Replicas is the number of desired replicas. This is a pointer to distinguish between explicit zero and unspecified. Defaults to 1. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller", + "minReadySeconds": "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)", + "selector": "Selector is a label query over pods that should match the replica count. Label keys and values that must match in order to be controlled by this replica set. It must match the pod template's labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", + "template": "Template is the object that describes the pod that will be created if insufficient replicas are detected. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template", +} + +func (ReplicaSetSpec) SwaggerDoc() map[string]string { + return map_ReplicaSetSpec +} + +var map_ReplicaSetStatus = map[string]string{ + "": "ReplicaSetStatus represents the current status of a ReplicaSet.", + "replicas": "Replicas is the most recently oberved number of replicas. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller", + "fullyLabeledReplicas": "The number of pods that have labels matching the labels of the pod template of the replicaset.", + "readyReplicas": "The number of ready replicas for this replica set.", + "availableReplicas": "The number of available replicas (ready for at least minReadySeconds) for this replica set.", + "observedGeneration": "ObservedGeneration reflects the generation of the most recently observed ReplicaSet.", + "conditions": "Represents the latest available observations of a replica set's current state.", +} + +func (ReplicaSetStatus) SwaggerDoc() map[string]string { + return map_ReplicaSetStatus +} + var map_RollingUpdateDaemonSet = map[string]string{ "": "Spec to control the desired behavior of daemon set rolling update.", "maxUnavailable": "The maximum number of DaemonSet pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of total number of DaemonSet pods at the start of the update (ex: 10%). Absolute number is calculated from percentage by rounding up. This cannot be 0. Default value is 1. Example: when this is set to 30%, at most 30% of the total number of nodes that should be running the daemon pod (i.e. status.desiredNumberScheduled) can have their pods stopped for an update at any given time. The update starts by stopping at most 30% of those DaemonSet pods and then brings up new DaemonSet pods in their place. Once the new pods are available, it then proceeds onto other DaemonSet pods, thus ensuring that at least 70% of original number of DaemonSet pods are available at all times during the update.", @@ -97,4 +269,97 @@ func (RollingUpdateDaemonSet) SwaggerDoc() map[string]string { return map_RollingUpdateDaemonSet } +var map_RollingUpdateDeployment = map[string]string{ + "": "Spec to control the desired behavior of rolling update.", + "maxUnavailable": "The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding down. This can not be 0 if MaxSurge is 0. Defaults to 25%. Example: when this is set to 30%, the old RC can be scaled down to 70% of desired pods immediately when the rolling update starts. Once new pods are ready, old RC can be scaled down further, followed by scaling up the new RC, ensuring that the total number of pods available at all times during the update is at least 70% of desired pods.", + "maxSurge": "The maximum number of pods that can be scheduled above the desired number of pods. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up. Defaults to 25%. Example: when this is set to 30%, the new RC can be scaled up immediately when the rolling update starts, such that the total number of old and new pods do not exceed 130% of desired pods. Once old pods have been killed, new RC can be scaled up further, ensuring that total number of pods running at any time during the update is at most 130% of desired pods.", +} + +func (RollingUpdateDeployment) SwaggerDoc() map[string]string { + return map_RollingUpdateDeployment +} + +var map_RollingUpdateStatefulSetStrategy = map[string]string{ + "": "RollingUpdateStatefulSetStrategy is used to communicate parameter for RollingUpdateStatefulSetStrategyType.", + "partition": "Partition indicates the ordinal at which the StatefulSet should be partitioned. Default value is 0.", +} + +func (RollingUpdateStatefulSetStrategy) SwaggerDoc() map[string]string { + return map_RollingUpdateStatefulSetStrategy +} + +var map_StatefulSet = map[string]string{ + "": "StatefulSet represents a set of pods with consistent identities. Identities are defined as:\n - Network: A single stable DNS and hostname.\n - Storage: As many VolumeClaims as requested.\nThe StatefulSet guarantees that a given network identity will always map to the same storage identity.", + "spec": "Spec defines the desired identities of pods in this set.", + "status": "Status is the current status of Pods in this StatefulSet. This data may be out of date by some window of time.", +} + +func (StatefulSet) SwaggerDoc() map[string]string { + return map_StatefulSet +} + +var map_StatefulSetCondition = map[string]string{ + "": "StatefulSetCondition describes the state of a statefulset at a certain point.", + "type": "Type of statefulset condition.", + "status": "Status of the condition, one of True, False, Unknown.", + "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.", +} + +func (StatefulSetCondition) SwaggerDoc() map[string]string { + return map_StatefulSetCondition +} + +var map_StatefulSetList = map[string]string{ + "": "StatefulSetList is a collection of StatefulSets.", +} + +func (StatefulSetList) SwaggerDoc() map[string]string { + return map_StatefulSetList +} + +var map_StatefulSetSpec = map[string]string{ + "": "A StatefulSetSpec is the specification of a StatefulSet.", + "replicas": "replicas is the desired number of replicas of the given Template. These are replicas in the sense that they are instantiations of the same Template, but individual replicas also have a consistent identity. If unspecified, defaults to 1.", + "selector": "selector is a label query over pods that should match the replica count. It must match the pod template's labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", + "template": "template is the object that describes the pod that will be created if insufficient replicas are detected. Each pod stamped out by the StatefulSet will fulfill this Template, but have a unique identity from the rest of the StatefulSet.", + "volumeClaimTemplates": "volumeClaimTemplates is a list of claims that pods are allowed to reference. The StatefulSet controller is responsible for mapping network identities to claims in a way that maintains the identity of a pod. Every claim in this list must have at least one matching (by name) volumeMount in one container in the template. A claim in this list takes precedence over any volumes in the template, with the same name.", + "serviceName": "serviceName is the name of the service that governs this StatefulSet. This service must exist before the StatefulSet, and is responsible for the network identity of the set. Pods get DNS/hostnames that follow the pattern: pod-specific-string.serviceName.default.svc.cluster.local where \"pod-specific-string\" is managed by the StatefulSet controller.", + "podManagementPolicy": "podManagementPolicy controls how pods are created during initial scale up, when replacing pods on nodes, or when scaling down. The default policy is `OrderedReady`, where pods are created in increasing order (pod-0, then pod-1, etc) and the controller will wait until each pod is ready before continuing. When scaling down, the pods are removed in the opposite order. The alternative policy is `Parallel` which will create pods in parallel to match the desired scale without waiting, and on scale down will delete all pods at once.", + "updateStrategy": "updateStrategy indicates the StatefulSetUpdateStrategy that will be employed to update Pods in the StatefulSet when a revision is made to Template.", + "revisionHistoryLimit": "revisionHistoryLimit is the maximum number of revisions that will be maintained in the StatefulSet's revision history. The revision history consists of all revisions not represented by a currently applied StatefulSetSpec version. The default value is 10.", +} + +func (StatefulSetSpec) SwaggerDoc() map[string]string { + return map_StatefulSetSpec +} + +var map_StatefulSetStatus = map[string]string{ + "": "StatefulSetStatus represents the current state of a StatefulSet.", + "observedGeneration": "observedGeneration is the most recent generation observed for this StatefulSet. It corresponds to the StatefulSet's generation, which is updated on mutation by the API Server.", + "replicas": "replicas is the number of Pods created by the StatefulSet controller.", + "readyReplicas": "readyReplicas is the number of Pods created by the StatefulSet controller that have a Ready Condition.", + "currentReplicas": "currentReplicas is the number of Pods created by the StatefulSet controller from the StatefulSet version indicated by currentRevision.", + "updatedReplicas": "updatedReplicas is the number of Pods created by the StatefulSet controller from the StatefulSet version indicated by updateRevision.", + "currentRevision": "currentRevision, if not empty, indicates the version of the StatefulSet used to generate Pods in the sequence [0,currentReplicas).", + "updateRevision": "updateRevision, if not empty, indicates the version of the StatefulSet used to generate Pods in the sequence [replicas-updatedReplicas,replicas)", + "collisionCount": "collisionCount is the count of hash collisions for the StatefulSet. The StatefulSet controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ControllerRevision.", + "conditions": "Represents the latest available observations of a statefulset's current state.", +} + +func (StatefulSetStatus) SwaggerDoc() map[string]string { + return map_StatefulSetStatus +} + +var map_StatefulSetUpdateStrategy = map[string]string{ + "": "StatefulSetUpdateStrategy indicates the strategy that the StatefulSet controller will use to perform updates. It includes any additional parameters necessary to perform the update for the indicated strategy.", + "type": "Type indicates the type of the StatefulSetUpdateStrategy. Default is RollingUpdate.", + "rollingUpdate": "RollingUpdate is used to communicate parameters when Type is RollingUpdateStatefulSetStrategyType.", +} + +func (StatefulSetUpdateStrategy) SwaggerDoc() map[string]string { + return map_StatefulSetUpdateStrategy +} + // AUTO-GENERATED FUNCTIONS END HERE diff --git a/staging/src/k8s.io/api/apps/v1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/apps/v1/zz_generated.deepcopy.go index 72d654a53d4..9419e8e72eb 100644 --- a/staging/src/k8s.io/api/apps/v1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/apps/v1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,48 +21,72 @@ limitations under the License. package v1 import ( + core_v1 "k8s.io/api/core/v1" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" intstr "k8s.io/apimachinery/pkg/util/intstr" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ControllerRevision) DeepCopyInto(out *ControllerRevision) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Data.DeepCopyInto(&out.Data) + return } -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DaemonSet).DeepCopyInto(out.(*DaemonSet)) - return nil - }, InType: reflect.TypeOf(&DaemonSet{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DaemonSetList).DeepCopyInto(out.(*DaemonSetList)) - return nil - }, InType: reflect.TypeOf(&DaemonSetList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DaemonSetSpec).DeepCopyInto(out.(*DaemonSetSpec)) - return nil - }, InType: reflect.TypeOf(&DaemonSetSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DaemonSetStatus).DeepCopyInto(out.(*DaemonSetStatus)) - return nil - }, InType: reflect.TypeOf(&DaemonSetStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DaemonSetUpdateStrategy).DeepCopyInto(out.(*DaemonSetUpdateStrategy)) - return nil - }, InType: reflect.TypeOf(&DaemonSetUpdateStrategy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RollingUpdateDaemonSet).DeepCopyInto(out.(*RollingUpdateDaemonSet)) - return nil - }, InType: reflect.TypeOf(&RollingUpdateDaemonSet{})}, - ) +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControllerRevision. +func (in *ControllerRevision) DeepCopy() *ControllerRevision { + if in == nil { + return nil + } + out := new(ControllerRevision) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ControllerRevision) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ControllerRevisionList) DeepCopyInto(out *ControllerRevisionList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ControllerRevision, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControllerRevisionList. +func (in *ControllerRevisionList) DeepCopy() *ControllerRevisionList { + if in == nil { + return nil + } + out := new(ControllerRevisionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ControllerRevisionList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -94,6 +118,23 @@ func (in *DaemonSet) DeepCopyObject() runtime.Object { } } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DaemonSetCondition) DeepCopyInto(out *DaemonSetCondition) { + *out = *in + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DaemonSetCondition. +func (in *DaemonSetCondition) DeepCopy() *DaemonSetCondition { + if in == nil { + return nil + } + out := new(DaemonSetCondition) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DaemonSetList) DeepCopyInto(out *DaemonSetList) { *out = *in @@ -176,6 +217,13 @@ func (in *DaemonSetStatus) DeepCopyInto(out *DaemonSetStatus) { **out = **in } } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]DaemonSetCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -214,6 +262,336 @@ func (in *DaemonSetUpdateStrategy) DeepCopy() *DaemonSetUpdateStrategy { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Deployment) DeepCopyInto(out *Deployment) { + *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 Deployment. +func (in *Deployment) DeepCopy() *Deployment { + if in == nil { + return nil + } + out := new(Deployment) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Deployment) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeploymentCondition) DeepCopyInto(out *DeploymentCondition) { + *out = *in + in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime) + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentCondition. +func (in *DeploymentCondition) DeepCopy() *DeploymentCondition { + if in == nil { + return nil + } + out := new(DeploymentCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeploymentList) DeepCopyInto(out *DeploymentList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Deployment, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentList. +func (in *DeploymentList) DeepCopy() *DeploymentList { + if in == nil { + return nil + } + out := new(DeploymentList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DeploymentList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeploymentSpec) DeepCopyInto(out *DeploymentSpec) { + *out = *in + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + if *in == nil { + *out = nil + } else { + *out = new(meta_v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + } + in.Template.DeepCopyInto(&out.Template) + in.Strategy.DeepCopyInto(&out.Strategy) + if in.RevisionHistoryLimit != nil { + in, out := &in.RevisionHistoryLimit, &out.RevisionHistoryLimit + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + if in.ProgressDeadlineSeconds != nil { + in, out := &in.ProgressDeadlineSeconds, &out.ProgressDeadlineSeconds + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentSpec. +func (in *DeploymentSpec) DeepCopy() *DeploymentSpec { + if in == nil { + return nil + } + out := new(DeploymentSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeploymentStatus) DeepCopyInto(out *DeploymentStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]DeploymentCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.CollisionCount != nil { + in, out := &in.CollisionCount, &out.CollisionCount + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentStatus. +func (in *DeploymentStatus) DeepCopy() *DeploymentStatus { + if in == nil { + return nil + } + out := new(DeploymentStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeploymentStrategy) DeepCopyInto(out *DeploymentStrategy) { + *out = *in + if in.RollingUpdate != nil { + in, out := &in.RollingUpdate, &out.RollingUpdate + if *in == nil { + *out = nil + } else { + *out = new(RollingUpdateDeployment) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentStrategy. +func (in *DeploymentStrategy) DeepCopy() *DeploymentStrategy { + if in == nil { + return nil + } + out := new(DeploymentStrategy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplicaSet) DeepCopyInto(out *ReplicaSet) { + *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 ReplicaSet. +func (in *ReplicaSet) DeepCopy() *ReplicaSet { + if in == nil { + return nil + } + out := new(ReplicaSet) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ReplicaSet) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplicaSetCondition) DeepCopyInto(out *ReplicaSetCondition) { + *out = *in + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicaSetCondition. +func (in *ReplicaSetCondition) DeepCopy() *ReplicaSetCondition { + if in == nil { + return nil + } + out := new(ReplicaSetCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplicaSetList) DeepCopyInto(out *ReplicaSetList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ReplicaSet, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicaSetList. +func (in *ReplicaSetList) DeepCopy() *ReplicaSetList { + if in == nil { + return nil + } + out := new(ReplicaSetList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ReplicaSetList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplicaSetSpec) DeepCopyInto(out *ReplicaSetSpec) { + *out = *in + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + if *in == nil { + *out = nil + } else { + *out = new(meta_v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + } + in.Template.DeepCopyInto(&out.Template) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicaSetSpec. +func (in *ReplicaSetSpec) DeepCopy() *ReplicaSetSpec { + if in == nil { + return nil + } + out := new(ReplicaSetSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplicaSetStatus) DeepCopyInto(out *ReplicaSetStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]ReplicaSetCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicaSetStatus. +func (in *ReplicaSetStatus) DeepCopy() *ReplicaSetStatus { + if in == nil { + return nil + } + out := new(ReplicaSetStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RollingUpdateDaemonSet) DeepCopyInto(out *RollingUpdateDaemonSet) { *out = *in @@ -238,3 +616,251 @@ func (in *RollingUpdateDaemonSet) DeepCopy() *RollingUpdateDaemonSet { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RollingUpdateDeployment) DeepCopyInto(out *RollingUpdateDeployment) { + *out = *in + if in.MaxUnavailable != nil { + in, out := &in.MaxUnavailable, &out.MaxUnavailable + if *in == nil { + *out = nil + } else { + *out = new(intstr.IntOrString) + **out = **in + } + } + if in.MaxSurge != nil { + in, out := &in.MaxSurge, &out.MaxSurge + if *in == nil { + *out = nil + } else { + *out = new(intstr.IntOrString) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RollingUpdateDeployment. +func (in *RollingUpdateDeployment) DeepCopy() *RollingUpdateDeployment { + if in == nil { + return nil + } + out := new(RollingUpdateDeployment) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RollingUpdateStatefulSetStrategy) DeepCopyInto(out *RollingUpdateStatefulSetStrategy) { + *out = *in + if in.Partition != nil { + in, out := &in.Partition, &out.Partition + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RollingUpdateStatefulSetStrategy. +func (in *RollingUpdateStatefulSetStrategy) DeepCopy() *RollingUpdateStatefulSetStrategy { + if in == nil { + return nil + } + out := new(RollingUpdateStatefulSetStrategy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StatefulSet) DeepCopyInto(out *StatefulSet) { + *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 StatefulSet. +func (in *StatefulSet) DeepCopy() *StatefulSet { + if in == nil { + return nil + } + out := new(StatefulSet) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *StatefulSet) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StatefulSetCondition) DeepCopyInto(out *StatefulSetCondition) { + *out = *in + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatefulSetCondition. +func (in *StatefulSetCondition) DeepCopy() *StatefulSetCondition { + if in == nil { + return nil + } + out := new(StatefulSetCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StatefulSetList) DeepCopyInto(out *StatefulSetList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]StatefulSet, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatefulSetList. +func (in *StatefulSetList) DeepCopy() *StatefulSetList { + if in == nil { + return nil + } + out := new(StatefulSetList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *StatefulSetList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StatefulSetSpec) DeepCopyInto(out *StatefulSetSpec) { + *out = *in + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + if *in == nil { + *out = nil + } else { + *out = new(meta_v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + } + in.Template.DeepCopyInto(&out.Template) + if in.VolumeClaimTemplates != nil { + in, out := &in.VolumeClaimTemplates, &out.VolumeClaimTemplates + *out = make([]core_v1.PersistentVolumeClaim, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + in.UpdateStrategy.DeepCopyInto(&out.UpdateStrategy) + if in.RevisionHistoryLimit != nil { + in, out := &in.RevisionHistoryLimit, &out.RevisionHistoryLimit + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatefulSetSpec. +func (in *StatefulSetSpec) DeepCopy() *StatefulSetSpec { + if in == nil { + return nil + } + out := new(StatefulSetSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StatefulSetStatus) DeepCopyInto(out *StatefulSetStatus) { + *out = *in + if in.CollisionCount != nil { + in, out := &in.CollisionCount, &out.CollisionCount + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]StatefulSetCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatefulSetStatus. +func (in *StatefulSetStatus) DeepCopy() *StatefulSetStatus { + if in == nil { + return nil + } + out := new(StatefulSetStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StatefulSetUpdateStrategy) DeepCopyInto(out *StatefulSetUpdateStrategy) { + *out = *in + if in.RollingUpdate != nil { + in, out := &in.RollingUpdate, &out.RollingUpdate + if *in == nil { + *out = nil + } else { + *out = new(RollingUpdateStatefulSetStrategy) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatefulSetUpdateStrategy. +func (in *StatefulSetUpdateStrategy) DeepCopy() *StatefulSetUpdateStrategy { + if in == nil { + return nil + } + out := new(StatefulSetUpdateStrategy) + in.DeepCopyInto(out) + return out +} diff --git a/staging/src/k8s.io/api/apps/v1beta1/BUILD b/staging/src/k8s.io/api/apps/v1beta1/BUILD index 212c7d3cea2..f40a41e3e20 100644 --- a/staging/src/k8s.io/api/apps/v1beta1/BUILD +++ b/staging/src/k8s.io/api/apps/v1beta1/BUILD @@ -21,7 +21,6 @@ go_library( "//vendor/github.com/gogo/protobuf/sortkeys:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", diff --git a/staging/src/k8s.io/api/apps/v1beta1/doc.go b/staging/src/k8s.io/api/apps/v1beta1/doc.go index 158f0000c7b..6047ed501d6 100644 --- a/staging/src/k8s.io/api/apps/v1beta1/doc.go +++ b/staging/src/k8s.io/api/apps/v1beta1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:openapi-gen=true package v1beta1 // import "k8s.io/api/apps/v1beta1" diff --git a/staging/src/k8s.io/api/apps/v1beta1/generated.pb.go b/staging/src/k8s.io/api/apps/v1beta1/generated.pb.go index 6d5e1f7a1eb..04183fc2db8 100644 --- a/staging/src/k8s.io/api/apps/v1beta1/generated.pb.go +++ b/staging/src/k8s.io/api/apps/v1beta1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -41,6 +41,7 @@ limitations under the License. ScaleSpec ScaleStatus StatefulSet + StatefulSetCondition StatefulSetList StatefulSetSpec StatefulSetStatus @@ -144,22 +145,26 @@ func (m *StatefulSet) Reset() { *m = StatefulSet{} } func (*StatefulSet) ProtoMessage() {} func (*StatefulSet) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{15} } +func (m *StatefulSetCondition) Reset() { *m = StatefulSetCondition{} } +func (*StatefulSetCondition) ProtoMessage() {} +func (*StatefulSetCondition) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{16} } + func (m *StatefulSetList) Reset() { *m = StatefulSetList{} } func (*StatefulSetList) ProtoMessage() {} -func (*StatefulSetList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{16} } +func (*StatefulSetList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{17} } func (m *StatefulSetSpec) Reset() { *m = StatefulSetSpec{} } func (*StatefulSetSpec) ProtoMessage() {} -func (*StatefulSetSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{17} } +func (*StatefulSetSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{18} } func (m *StatefulSetStatus) Reset() { *m = StatefulSetStatus{} } func (*StatefulSetStatus) ProtoMessage() {} -func (*StatefulSetStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{18} } +func (*StatefulSetStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{19} } func (m *StatefulSetUpdateStrategy) Reset() { *m = StatefulSetUpdateStrategy{} } func (*StatefulSetUpdateStrategy) ProtoMessage() {} func (*StatefulSetUpdateStrategy) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{19} + return fileDescriptorGenerated, []int{20} } func init() { @@ -179,6 +184,7 @@ func init() { proto.RegisterType((*ScaleSpec)(nil), "k8s.io.api.apps.v1beta1.ScaleSpec") proto.RegisterType((*ScaleStatus)(nil), "k8s.io.api.apps.v1beta1.ScaleStatus") proto.RegisterType((*StatefulSet)(nil), "k8s.io.api.apps.v1beta1.StatefulSet") + proto.RegisterType((*StatefulSetCondition)(nil), "k8s.io.api.apps.v1beta1.StatefulSetCondition") proto.RegisterType((*StatefulSetList)(nil), "k8s.io.api.apps.v1beta1.StatefulSetList") proto.RegisterType((*StatefulSetSpec)(nil), "k8s.io.api.apps.v1beta1.StatefulSetSpec") proto.RegisterType((*StatefulSetStatus)(nil), "k8s.io.api.apps.v1beta1.StatefulSetStatus") @@ -840,6 +846,48 @@ func (m *StatefulSet) MarshalTo(dAtA []byte) (int, error) { return i, nil } +func (m *StatefulSetCondition) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *StatefulSetCondition) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) + i += copy(dAtA[i:], m.Type) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Status))) + i += copy(dAtA[i:], m.Status) + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.LastTransitionTime.Size())) + n24, err := m.LastTransitionTime.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n24 + dAtA[i] = 0x22 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) + i += copy(dAtA[i:], m.Reason) + dAtA[i] = 0x2a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) + i += copy(dAtA[i:], m.Message) + return i, nil +} + func (m *StatefulSetList) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -858,11 +906,11 @@ func (m *StatefulSetList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n24, err := m.ListMeta.MarshalTo(dAtA[i:]) + n25, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n24 + i += n25 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -902,20 +950,20 @@ func (m *StatefulSetSpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Selector.Size())) - n25, err := m.Selector.MarshalTo(dAtA[i:]) + n26, err := m.Selector.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n25 + i += n26 } dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Template.Size())) - n26, err := m.Template.MarshalTo(dAtA[i:]) + n27, err := m.Template.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n26 + i += n27 if len(m.VolumeClaimTemplates) > 0 { for _, msg := range m.VolumeClaimTemplates { dAtA[i] = 0x22 @@ -939,11 +987,11 @@ func (m *StatefulSetSpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x3a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.UpdateStrategy.Size())) - n27, err := m.UpdateStrategy.MarshalTo(dAtA[i:]) + n28, err := m.UpdateStrategy.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n27 + i += n28 if m.RevisionHistoryLimit != nil { dAtA[i] = 0x40 i++ @@ -997,6 +1045,18 @@ func (m *StatefulSetStatus) MarshalTo(dAtA []byte) (int, error) { i++ i = encodeVarintGenerated(dAtA, i, uint64(*m.CollisionCount)) } + if len(m.Conditions) > 0 { + for _, msg := range m.Conditions { + dAtA[i] = 0x52 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } return i, nil } @@ -1023,11 +1083,11 @@ func (m *StatefulSetUpdateStrategy) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.RollingUpdate.Size())) - n28, err := m.RollingUpdate.MarshalTo(dAtA[i:]) + n29, err := m.RollingUpdate.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n28 + i += n29 } return i, nil } @@ -1286,6 +1346,22 @@ func (m *StatefulSet) Size() (n int) { return n } +func (m *StatefulSetCondition) Size() (n int) { + 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 *StatefulSetList) Size() (n int) { var l int _ = l @@ -1347,6 +1423,12 @@ func (m *StatefulSetStatus) Size() (n int) { if m.CollisionCount != nil { n += 1 + sovGenerated(uint64(*m.CollisionCount)) } + if len(m.Conditions) > 0 { + for _, e := range m.Conditions { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } return n } @@ -1591,6 +1673,20 @@ func (this *StatefulSet) String() string { }, "") return s } +func (this *StatefulSetCondition) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&StatefulSetCondition{`, + `Type:` + fmt.Sprintf("%v", this.Type) + `,`, + `Status:` + fmt.Sprintf("%v", this.Status) + `,`, + `LastTransitionTime:` + strings.Replace(strings.Replace(this.LastTransitionTime.String(), "Time", "k8s_io_apimachinery_pkg_apis_meta_v1.Time", 1), `&`, ``, 1) + `,`, + `Reason:` + fmt.Sprintf("%v", this.Reason) + `,`, + `Message:` + fmt.Sprintf("%v", this.Message) + `,`, + `}`, + }, "") + return s +} func (this *StatefulSetList) String() string { if this == nil { return "nil" @@ -1632,6 +1728,7 @@ func (this *StatefulSetStatus) String() string { `CurrentRevision:` + fmt.Sprintf("%v", this.CurrentRevision) + `,`, `UpdateRevision:` + fmt.Sprintf("%v", this.UpdateRevision) + `,`, `CollisionCount:` + valueToStringGenerated(this.CollisionCount) + `,`, + `Conditions:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Conditions), "StatefulSetCondition", "StatefulSetCondition", 1), `&`, ``, 1) + `,`, `}`, }, "") return s @@ -4017,6 +4114,202 @@ func (m *StatefulSet) Unmarshal(dAtA []byte) error { } return nil } +func (m *StatefulSetCondition) 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: StatefulSetCondition: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatefulSetCondition: 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 > l { + return io.ErrUnexpectedEOF + } + m.Type = StatefulSetConditionType(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 > 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 > 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 > 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 > 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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *StatefulSetList) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -4603,6 +4896,37 @@ func (m *StatefulSetStatus) Unmarshal(dAtA []byte) error { } } m.CollisionCount = &v + case 10: + 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 > l { + return io.ErrUnexpectedEOF + } + m.Conditions = append(m.Conditions, StatefulSetCondition{}) + 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:]) @@ -4846,119 +5170,122 @@ func init() { } var fileDescriptorGenerated = []byte{ - // 1820 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x58, 0x4b, 0x6f, 0x1b, 0xc9, - 0x11, 0xd6, 0x50, 0xa4, 0x44, 0xb6, 0x56, 0x94, 0xd5, 0x52, 0x24, 0xae, 0x36, 0xa1, 0x04, 0x66, - 0xb1, 0x2b, 0xef, 0xae, 0x86, 0x6b, 0xd9, 0x31, 0xfc, 0x00, 0x8c, 0x88, 0x94, 0x63, 0xcb, 0x90, - 0x22, 0xb9, 0x29, 0x39, 0x88, 0x93, 0x00, 0x6e, 0x0e, 0xdb, 0xd4, 0x58, 0xf3, 0xc2, 0x4c, 0x0f, - 0x63, 0x22, 0x97, 0xdc, 0x13, 0xc0, 0x39, 0xe7, 0x57, 0xe4, 0x18, 0x24, 0xb7, 0x9c, 0x74, 0x09, - 0x60, 0xe4, 0x12, 0x9f, 0x84, 0x98, 0xbe, 0xe6, 0x9a, 0x8b, 0x81, 0x00, 0x41, 0xf7, 0xf4, 0xbc, - 0x67, 0x24, 0x2a, 0x40, 0x74, 0xc8, 0x8d, 0xd3, 0x55, 0xf5, 0x55, 0x75, 0x77, 0x75, 0xd5, 0x57, - 0x04, 0x3f, 0x3c, 0xb9, 0xe3, 0xc8, 0xaa, 0xd9, 0x3c, 0x71, 0xbb, 0xc4, 0x36, 0x08, 0x25, 0x4e, - 0x73, 0x40, 0x8c, 0x9e, 0x69, 0x37, 0x85, 0x00, 0x5b, 0x6a, 0x13, 0x5b, 0x96, 0xd3, 0x1c, 0xdc, - 0xe8, 0x12, 0x8a, 0x6f, 0x34, 0xfb, 0xc4, 0x20, 0x36, 0xa6, 0xa4, 0x27, 0x5b, 0xb6, 0x49, 0x4d, - 0xb8, 0xec, 0x29, 0xca, 0xd8, 0x52, 0x65, 0xa6, 0x28, 0x0b, 0xc5, 0x95, 0x8d, 0xbe, 0x4a, 0x8f, - 0xdd, 0xae, 0xac, 0x98, 0x7a, 0xb3, 0x6f, 0xf6, 0xcd, 0x26, 0xd7, 0xef, 0xba, 0x2f, 0xf9, 0x17, - 0xff, 0xe0, 0xbf, 0x3c, 0x9c, 0x95, 0x46, 0xc4, 0xa1, 0x62, 0xda, 0xa4, 0x39, 0x48, 0xf9, 0x5a, - 0xb9, 0x1e, 0xd1, 0xb1, 0x4c, 0x4d, 0x55, 0x86, 0x79, 0x61, 0xad, 0xdc, 0x0a, 0x55, 0x75, 0xac, - 0x1c, 0xab, 0x06, 0xb1, 0x87, 0x4d, 0xeb, 0xa4, 0xcf, 0x16, 0x9c, 0xa6, 0x4e, 0x28, 0xce, 0x72, - 0xd0, 0xcc, 0xb3, 0xb2, 0x5d, 0x83, 0xaa, 0x3a, 0x49, 0x19, 0xdc, 0xbe, 0xc8, 0xc0, 0x51, 0x8e, - 0x89, 0x8e, 0x53, 0x76, 0x37, 0xf3, 0xec, 0x5c, 0xaa, 0x6a, 0x4d, 0xd5, 0xa0, 0x0e, 0xb5, 0x93, - 0x46, 0x8d, 0x7f, 0x49, 0x00, 0xb6, 0x4d, 0x83, 0xda, 0xa6, 0xa6, 0x11, 0x1b, 0x91, 0x81, 0xea, - 0xa8, 0xa6, 0x01, 0x5f, 0x80, 0x32, 0xdb, 0x4f, 0x0f, 0x53, 0x5c, 0x93, 0xd6, 0xa4, 0xf5, 0x99, - 0xcd, 0x6f, 0xe5, 0xf0, 0x52, 0x02, 0x78, 0xd9, 0x3a, 0xe9, 0xb3, 0x05, 0x47, 0x66, 0xda, 0xf2, - 0xe0, 0x86, 0xbc, 0xdf, 0x7d, 0x45, 0x14, 0xba, 0x47, 0x28, 0x6e, 0xc1, 0xd3, 0xb3, 0xd5, 0x89, - 0xd1, 0xd9, 0x2a, 0x08, 0xd7, 0x50, 0x80, 0x0a, 0xf7, 0x41, 0x91, 0xa3, 0x17, 0x38, 0xfa, 0x46, - 0x2e, 0xba, 0xd8, 0xb4, 0x8c, 0xf0, 0x2f, 0x1f, 0xbe, 0xa6, 0xc4, 0x60, 0xe1, 0xb5, 0x3e, 0x11, - 0xd0, 0xc5, 0x6d, 0x4c, 0x31, 0xe2, 0x40, 0xf0, 0x1b, 0x50, 0xb6, 0x45, 0xf8, 0xb5, 0xc9, 0x35, - 0x69, 0x7d, 0xb2, 0x75, 0x4d, 0x68, 0x95, 0xfd, 0x6d, 0xa1, 0x40, 0xa3, 0x71, 0x2a, 0x81, 0xa5, - 0xf4, 0xbe, 0x77, 0x55, 0x87, 0xc2, 0x9f, 0xa7, 0xf6, 0x2e, 0x8f, 0xb7, 0x77, 0x66, 0xcd, 0x77, - 0x1e, 0x38, 0xf6, 0x57, 0x22, 0xfb, 0x3e, 0x00, 0x25, 0x95, 0x12, 0xdd, 0xa9, 0x15, 0xd6, 0x26, - 0xd7, 0x67, 0x36, 0xbf, 0x96, 0x73, 0x72, 0x5d, 0x4e, 0x47, 0xd7, 0x9a, 0x15, 0xb8, 0xa5, 0x1d, - 0x86, 0x80, 0x3c, 0xa0, 0xc6, 0x6f, 0x0b, 0x00, 0x6c, 0x13, 0x4b, 0x33, 0x87, 0x3a, 0x31, 0xe8, - 0x15, 0x5c, 0xdd, 0x0e, 0x28, 0x3a, 0x16, 0x51, 0xc4, 0xd5, 0x7d, 0x99, 0xbb, 0x83, 0x30, 0xa8, - 0x8e, 0x45, 0x94, 0xf0, 0xd2, 0xd8, 0x17, 0xe2, 0x10, 0xf0, 0x29, 0x98, 0x72, 0x28, 0xa6, 0xae, - 0xc3, 0xaf, 0x6c, 0x66, 0xf3, 0xfa, 0x38, 0x60, 0xdc, 0xa0, 0x55, 0x15, 0x70, 0x53, 0xde, 0x37, - 0x12, 0x40, 0x8d, 0xbf, 0x4f, 0x82, 0x85, 0x50, 0xb9, 0x6d, 0x1a, 0x3d, 0x95, 0xb2, 0x94, 0xbe, - 0x0f, 0x8a, 0x74, 0x68, 0x11, 0x7e, 0x26, 0x95, 0xd6, 0x97, 0x7e, 0x30, 0x87, 0x43, 0x8b, 0x7c, - 0x3c, 0x5b, 0x5d, 0xce, 0x30, 0x61, 0x22, 0xc4, 0x8d, 0xe0, 0x6e, 0x10, 0x67, 0x81, 0x9b, 0xdf, - 0x8a, 0x3b, 0xff, 0x78, 0xb6, 0x9a, 0x51, 0x6b, 0xe4, 0x00, 0x29, 0x1e, 0x22, 0xfc, 0x02, 0x4c, - 0xd9, 0x04, 0x3b, 0xa6, 0x51, 0x2b, 0x72, 0xb4, 0x60, 0x2b, 0x88, 0xaf, 0x22, 0x21, 0x85, 0xd7, - 0xc1, 0xb4, 0x4e, 0x1c, 0x07, 0xf7, 0x49, 0xad, 0xc4, 0x15, 0xe7, 0x84, 0xe2, 0xf4, 0x9e, 0xb7, - 0x8c, 0x7c, 0x39, 0x7c, 0x05, 0xaa, 0x1a, 0x76, 0xe8, 0x91, 0xd5, 0xc3, 0x94, 0x1c, 0xaa, 0x3a, - 0xa9, 0x4d, 0xf1, 0x03, 0xfd, 0x6a, 0xbc, 0xbb, 0x67, 0x16, 0xad, 0x25, 0x81, 0x5e, 0xdd, 0x8d, - 0x21, 0xa1, 0x04, 0x32, 0x1c, 0x00, 0xc8, 0x56, 0x0e, 0x6d, 0x6c, 0x38, 0xde, 0x41, 0x31, 0x7f, - 0xd3, 0x97, 0xf6, 0xb7, 0x22, 0xfc, 0xc1, 0xdd, 0x14, 0x1a, 0xca, 0xf0, 0xd0, 0xf8, 0xa3, 0x04, - 0xaa, 0xe1, 0x35, 0x5d, 0xc1, 0x5b, 0x7d, 0x1c, 0x7f, 0xab, 0xdf, 0x1f, 0x23, 0x39, 0x73, 0xde, - 0xe8, 0x3f, 0x0b, 0x00, 0x86, 0x4a, 0xc8, 0xd4, 0xb4, 0x2e, 0x56, 0x4e, 0xe0, 0x1a, 0x28, 0x1a, - 0x58, 0xf7, 0x73, 0x32, 0x78, 0x20, 0x3f, 0xc6, 0x3a, 0x41, 0x5c, 0x02, 0xdf, 0x48, 0x00, 0xba, - 0xfc, 0xe8, 0x7b, 0x5b, 0x86, 0x61, 0x52, 0xcc, 0x4e, 0xc3, 0x0f, 0xa8, 0x3d, 0x46, 0x40, 0xbe, - 0x2f, 0xf9, 0x28, 0x85, 0xf2, 0xd0, 0xa0, 0xf6, 0x30, 0xbc, 0x85, 0xb4, 0x02, 0xca, 0x70, 0x0d, - 0x7f, 0x06, 0x80, 0x2d, 0x30, 0x0f, 0x4d, 0xf1, 0x6c, 0xf3, 0x6b, 0x80, 0xef, 0xbe, 0x6d, 0x1a, - 0x2f, 0xd5, 0x7e, 0x58, 0x58, 0x50, 0x00, 0x81, 0x22, 0x70, 0x2b, 0x0f, 0xc1, 0x72, 0x4e, 0x9c, - 0xf0, 0x1a, 0x98, 0x3c, 0x21, 0x43, 0xef, 0xa8, 0x10, 0xfb, 0x09, 0x17, 0x41, 0x69, 0x80, 0x35, - 0x97, 0x78, 0x6f, 0x12, 0x79, 0x1f, 0xf7, 0x0a, 0x77, 0xa4, 0xc6, 0x1f, 0x4a, 0xd1, 0x4c, 0x61, - 0xf5, 0x06, 0xae, 0xb3, 0xf6, 0x60, 0x69, 0xaa, 0x82, 0x1d, 0x8e, 0x51, 0x6a, 0x7d, 0xe2, 0xb5, - 0x06, 0x6f, 0x0d, 0x05, 0x52, 0xf8, 0x0b, 0x50, 0x76, 0x88, 0x46, 0x14, 0x6a, 0xda, 0xa2, 0xc4, - 0xdd, 0x1c, 0x33, 0xa7, 0x70, 0x97, 0x68, 0x1d, 0x61, 0xea, 0xc1, 0xfb, 0x5f, 0x28, 0x80, 0x84, - 0x4f, 0x41, 0x99, 0x12, 0xdd, 0xd2, 0x30, 0x25, 0xe2, 0xf4, 0x62, 0x79, 0xc5, 0x6a, 0x07, 0x03, - 0x3b, 0x30, 0x7b, 0x87, 0x42, 0x8d, 0x57, 0xcf, 0x20, 0x4f, 0xfd, 0x55, 0x14, 0xc0, 0xc0, 0x9f, - 0x82, 0xb2, 0x43, 0x59, 0x57, 0xef, 0x0f, 0x79, 0x45, 0x39, 0xaf, 0xad, 0x44, 0xeb, 0xa8, 0x67, - 0x12, 0x42, 0xfb, 0x2b, 0x28, 0x80, 0x83, 0x5b, 0x60, 0x4e, 0x57, 0x0d, 0x44, 0x70, 0x6f, 0xd8, - 0x21, 0x8a, 0x69, 0xf4, 0x1c, 0x5e, 0x8a, 0x4a, 0xad, 0x65, 0x61, 0x34, 0xb7, 0x17, 0x17, 0xa3, - 0xa4, 0x3e, 0xdc, 0x05, 0x8b, 0x7e, 0xdb, 0x7d, 0xac, 0x3a, 0xd4, 0xb4, 0x87, 0xbb, 0xaa, 0xae, - 0x52, 0x5e, 0xa0, 0x4a, 0xad, 0xda, 0xe8, 0x6c, 0x75, 0x11, 0x65, 0xc8, 0x51, 0xa6, 0x15, 0xab, - 0x9d, 0x16, 0x76, 0x1d, 0xd2, 0xe3, 0x05, 0xa7, 0x1c, 0xd6, 0xce, 0x03, 0xbe, 0x8a, 0x84, 0x14, - 0xfe, 0x24, 0x96, 0xa6, 0xe5, 0xcb, 0xa5, 0x69, 0x35, 0x3f, 0x45, 0xe1, 0x11, 0x58, 0xb6, 0x6c, - 0xb3, 0x6f, 0x13, 0xc7, 0xd9, 0x26, 0xb8, 0xa7, 0xa9, 0x06, 0xf1, 0x4f, 0xa6, 0xc2, 0x77, 0xf4, - 0xd9, 0xe8, 0x6c, 0x75, 0xf9, 0x20, 0x5b, 0x05, 0xe5, 0xd9, 0x36, 0xfe, 0x52, 0x04, 0xd7, 0x92, - 0x3d, 0x0e, 0x3e, 0x01, 0xd0, 0xec, 0x3a, 0xc4, 0x1e, 0x90, 0xde, 0x23, 0x8f, 0xb8, 0x31, 0x76, - 0x23, 0x71, 0x76, 0x13, 0xbc, 0xdb, 0xfd, 0x94, 0x06, 0xca, 0xb0, 0xf2, 0xf8, 0x91, 0x78, 0x00, - 0x05, 0x1e, 0x68, 0x84, 0x1f, 0xa5, 0x1e, 0xc1, 0x16, 0x98, 0x13, 0x6f, 0xdf, 0x17, 0xf2, 0x64, - 0x8d, 0xdc, 0xfb, 0x51, 0x5c, 0x8c, 0x92, 0xfa, 0xf0, 0x11, 0x98, 0xc7, 0x03, 0xac, 0x6a, 0xb8, - 0xab, 0x91, 0x00, 0xa4, 0xc8, 0x41, 0x3e, 0x15, 0x20, 0xf3, 0x5b, 0x49, 0x05, 0x94, 0xb6, 0x81, - 0x7b, 0x60, 0xc1, 0x35, 0xd2, 0x50, 0x5e, 0x1e, 0x7e, 0x26, 0xa0, 0x16, 0x8e, 0xd2, 0x2a, 0x28, - 0xcb, 0x0e, 0xbe, 0x00, 0x40, 0xf1, 0x1b, 0xb3, 0x53, 0x9b, 0xe2, 0x95, 0xf4, 0x9b, 0x31, 0xde, - 0x4b, 0xd0, 0xcd, 0xc3, 0x2a, 0x16, 0x2c, 0x39, 0x28, 0x82, 0x09, 0xef, 0x83, 0x59, 0x9b, 0xbd, - 0x80, 0x20, 0xd4, 0x69, 0x1e, 0xea, 0x77, 0x84, 0xd9, 0x2c, 0x8a, 0x0a, 0x51, 0x5c, 0x17, 0xde, - 0x03, 0x55, 0xc5, 0xd4, 0x34, 0x9e, 0xf9, 0x6d, 0xd3, 0x35, 0x28, 0x4f, 0xde, 0x52, 0x0b, 0xb2, - 0xce, 0xdc, 0x8e, 0x49, 0x50, 0x42, 0xb3, 0xf1, 0x67, 0x29, 0xda, 0x66, 0xfc, 0xe7, 0x0c, 0xef, - 0xc5, 0xa8, 0xcf, 0x17, 0x09, 0xea, 0xb3, 0x94, 0xb6, 0x88, 0x30, 0x1f, 0x15, 0xcc, 0xb2, 0xe4, - 0x57, 0x8d, 0xbe, 0x77, 0xe1, 0xa2, 0x24, 0x7e, 0x7b, 0xee, 0x53, 0x0a, 0xb4, 0x23, 0x8d, 0x71, - 0x9e, 0xef, 0x3c, 0x2a, 0x44, 0x71, 0xe4, 0xc6, 0x03, 0x50, 0x8d, 0xbf, 0xc3, 0x18, 0xa7, 0x97, - 0x2e, 0xe4, 0xf4, 0x1f, 0x24, 0xb0, 0x9c, 0xe3, 0x1d, 0x6a, 0xa0, 0xaa, 0xe3, 0xd7, 0x91, 0x1c, - 0xb9, 0x90, 0x1b, 0xb3, 0xa9, 0x49, 0xf6, 0xa6, 0x26, 0x79, 0xc7, 0xa0, 0xfb, 0x76, 0x87, 0xda, - 0xaa, 0xd1, 0xf7, 0xee, 0x61, 0x2f, 0x86, 0x85, 0x12, 0xd8, 0xf0, 0x39, 0x28, 0xeb, 0xf8, 0x75, - 0xc7, 0xb5, 0xfb, 0x59, 0xe7, 0x35, 0x9e, 0x1f, 0xde, 0x3f, 0xf6, 0x04, 0x0a, 0x0a, 0xf0, 0x1a, - 0xfb, 0x60, 0x2d, 0xb6, 0x49, 0x56, 0x2a, 0xc8, 0x4b, 0x57, 0xeb, 0x90, 0xf0, 0xc2, 0xbf, 0x06, - 0x15, 0x0b, 0xdb, 0x54, 0x0d, 0xca, 0x45, 0xa9, 0x35, 0x3b, 0x3a, 0x5b, 0xad, 0x1c, 0xf8, 0x8b, - 0x28, 0x94, 0x37, 0xfe, 0x2d, 0x81, 0x52, 0x47, 0xc1, 0x1a, 0xb9, 0x82, 0xd1, 0x61, 0x3b, 0x36, - 0x3a, 0x34, 0x72, 0x93, 0x88, 0xc7, 0x93, 0x3b, 0x35, 0xec, 0x26, 0xa6, 0x86, 0xcf, 0x2f, 0xc0, - 0x39, 0x7f, 0x60, 0xb8, 0x0b, 0x2a, 0x81, 0xbb, 0x58, 0x95, 0x94, 0x2e, 0xaa, 0x92, 0x8d, 0xdf, - 0x17, 0xc0, 0x4c, 0xc4, 0xc5, 0xe5, 0xac, 0xd9, 0x71, 0x47, 0x88, 0x06, 0x2b, 0x43, 0x9b, 0xe3, - 0x6c, 0x44, 0xf6, 0x49, 0x85, 0xc7, 0xdf, 0xc2, 0xee, 0x9d, 0xe6, 0x1a, 0x0f, 0x40, 0x95, 0x62, - 0xbb, 0x4f, 0xa8, 0x2f, 0xe3, 0x07, 0x56, 0x09, 0x99, 0xfe, 0x61, 0x4c, 0x8a, 0x12, 0xda, 0x2b, - 0xf7, 0xc1, 0x6c, 0xcc, 0xd9, 0xa5, 0x48, 0xd8, 0x1b, 0x76, 0x38, 0x61, 0x72, 0x5e, 0x41, 0x76, - 0x3d, 0x89, 0x65, 0xd7, 0x7a, 0xfe, 0x61, 0x46, 0x9e, 0x4c, 0x5e, 0x8e, 0xa1, 0x44, 0x8e, 0x7d, - 0x35, 0x16, 0xda, 0xf9, 0x99, 0xf6, 0x27, 0x09, 0xcc, 0x45, 0xb4, 0xaf, 0x60, 0x82, 0xd9, 0x89, - 0x4f, 0x30, 0x9f, 0x8f, 0xb3, 0x89, 0x9c, 0x11, 0xe6, 0xaf, 0xa5, 0x58, 0xf0, 0xff, 0xf7, 0xa4, - 0xfa, 0x57, 0x60, 0x71, 0x60, 0x6a, 0xae, 0x4e, 0xda, 0x1a, 0x56, 0x75, 0x5f, 0x81, 0x31, 0x98, - 0xc9, 0xe4, 0x1f, 0x15, 0x01, 0x3c, 0xb1, 0x1d, 0xd5, 0xa1, 0xc4, 0xa0, 0xcf, 0x42, 0xcb, 0xd6, - 0x77, 0x85, 0x93, 0xc5, 0x67, 0x19, 0x70, 0x28, 0xd3, 0x09, 0xfc, 0x01, 0x98, 0x61, 0x04, 0x4e, - 0x55, 0x08, 0x9b, 0x05, 0xc5, 0xf4, 0xbf, 0x20, 0x80, 0x66, 0x3a, 0xa1, 0x08, 0x45, 0xf5, 0xe0, - 0x31, 0x58, 0xb0, 0xcc, 0xde, 0x1e, 0x36, 0x70, 0x9f, 0xb0, 0xb6, 0x77, 0xc0, 0xff, 0xd0, 0xe4, - 0x4c, 0xbb, 0xd2, 0xba, 0xed, 0x33, 0xa5, 0x83, 0xb4, 0xca, 0x47, 0x46, 0x59, 0xd3, 0xcb, 0x9c, - 0x07, 0x64, 0x41, 0x42, 0x1b, 0x54, 0x5d, 0xd1, 0x7e, 0xc4, 0xe0, 0xe1, 0xcd, 0xff, 0x9b, 0xe3, - 0x64, 0xd8, 0x51, 0xcc, 0x32, 0xac, 0x46, 0xf1, 0x75, 0x94, 0xf0, 0x90, 0x3b, 0x48, 0x94, 0xff, - 0x9b, 0x41, 0xa2, 0xf1, 0x9b, 0x22, 0x98, 0x4f, 0x3d, 0x5d, 0xf8, 0xa3, 0x73, 0x18, 0xf7, 0xd2, - 0xff, 0x8c, 0x6d, 0xa7, 0x08, 0xe3, 0xe4, 0x25, 0x08, 0xe3, 0x16, 0x98, 0x53, 0x5c, 0xdb, 0x66, - 0xb3, 0x7e, 0x9c, 0x65, 0x07, 0x54, 0xbd, 0x1d, 0x17, 0xa3, 0xa4, 0x7e, 0x16, 0xdb, 0x2f, 0x5d, - 0x92, 0xed, 0x47, 0xa3, 0x10, 0x8c, 0xcd, 0x4b, 0xbb, 0x74, 0x14, 0x82, 0xb8, 0x25, 0xf5, 0x59, - 0xb7, 0xf2, 0x50, 0x03, 0x84, 0xe9, 0x78, 0xb7, 0x3a, 0x8a, 0x49, 0x51, 0x42, 0x3b, 0x83, 0x39, - 0x57, 0xc6, 0x66, 0xce, 0x7f, 0x93, 0xc0, 0xa7, 0xb9, 0x19, 0x0a, 0xb7, 0x62, 0x04, 0x7a, 0x23, - 0x41, 0xa0, 0xbf, 0x97, 0x6b, 0x18, 0xe1, 0xd1, 0x76, 0x36, 0x8f, 0xbe, 0x3b, 0x1e, 0x8f, 0xce, - 0x20, 0x79, 0x17, 0x13, 0xea, 0xd6, 0xc6, 0xe9, 0xfb, 0xfa, 0xc4, 0xdb, 0xf7, 0xf5, 0x89, 0x77, - 0xef, 0xeb, 0x13, 0xbf, 0x1e, 0xd5, 0xa5, 0xd3, 0x51, 0x5d, 0x7a, 0x3b, 0xaa, 0x4b, 0xef, 0x46, - 0x75, 0xe9, 0x1f, 0xa3, 0xba, 0xf4, 0xbb, 0x0f, 0xf5, 0x89, 0xe7, 0xd3, 0xc2, 0xe3, 0x7f, 0x02, - 0x00, 0x00, 0xff, 0xff, 0x3a, 0x2e, 0x29, 0xcd, 0xb9, 0x19, 0x00, 0x00, + // 1871 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x58, 0x4f, 0x6f, 0x1b, 0xc7, + 0x15, 0xd7, 0x52, 0xa4, 0x44, 0x3d, 0x45, 0x94, 0x3d, 0x52, 0x2d, 0x46, 0x69, 0x29, 0x61, 0x1b, + 0x24, 0x72, 0x12, 0x2d, 0x63, 0x25, 0x0d, 0x12, 0xbb, 0x08, 0x2a, 0xca, 0x6e, 0xe2, 0x40, 0xaa, + 0x94, 0xa1, 0x94, 0xa2, 0x69, 0x0b, 0x64, 0xb8, 0x1c, 0xd3, 0x1b, 0xed, 0x3f, 0xec, 0x0e, 0x59, + 0x13, 0xbd, 0xf4, 0x03, 0x14, 0x48, 0xcf, 0xfd, 0x14, 0x3d, 0x16, 0xed, 0xad, 0x27, 0x5f, 0x0a, + 0x04, 0xbd, 0x34, 0x27, 0xa1, 0xa6, 0xaf, 0x6d, 0x6f, 0xbd, 0x18, 0x28, 0x50, 0xcc, 0xec, 0xec, + 0xff, 0x5d, 0x89, 0x2a, 0x20, 0x1d, 0x72, 0xe3, 0xce, 0x7b, 0xef, 0xf7, 0xde, 0xcc, 0xbc, 0xf7, + 0xe6, 0xfd, 0x08, 0x3f, 0x3a, 0x7d, 0xdf, 0xd7, 0x0c, 0xa7, 0x7d, 0x3a, 0xec, 0x51, 0xcf, 0xa6, + 0x8c, 0xfa, 0xed, 0x11, 0xb5, 0xfb, 0x8e, 0xd7, 0x96, 0x02, 0xe2, 0x1a, 0x6d, 0xe2, 0xba, 0x7e, + 0x7b, 0x74, 0xa7, 0x47, 0x19, 0xb9, 0xd3, 0x1e, 0x50, 0x9b, 0x7a, 0x84, 0xd1, 0xbe, 0xe6, 0x7a, + 0x0e, 0x73, 0xd0, 0x5a, 0xa0, 0xa8, 0x11, 0xd7, 0xd0, 0xb8, 0xa2, 0x26, 0x15, 0xd7, 0xb7, 0x07, + 0x06, 0x7b, 0x3c, 0xec, 0x69, 0xba, 0x63, 0xb5, 0x07, 0xce, 0xc0, 0x69, 0x0b, 0xfd, 0xde, 0xf0, + 0x91, 0xf8, 0x12, 0x1f, 0xe2, 0x57, 0x80, 0xb3, 0xae, 0x26, 0x1c, 0xea, 0x8e, 0x47, 0xdb, 0xa3, + 0x9c, 0xaf, 0xf5, 0xdb, 0x09, 0x1d, 0xd7, 0x31, 0x0d, 0x7d, 0x5c, 0x16, 0xd6, 0xfa, 0xbb, 0xb1, + 0xaa, 0x45, 0xf4, 0xc7, 0x86, 0x4d, 0xbd, 0x71, 0xdb, 0x3d, 0x1d, 0xf0, 0x05, 0xbf, 0x6d, 0x51, + 0x46, 0x8a, 0x1c, 0xb4, 0xcb, 0xac, 0xbc, 0xa1, 0xcd, 0x0c, 0x8b, 0xe6, 0x0c, 0xde, 0xbb, 0xc8, + 0xc0, 0xd7, 0x1f, 0x53, 0x8b, 0xe4, 0xec, 0xde, 0x29, 0xb3, 0x1b, 0x32, 0xc3, 0x6c, 0x1b, 0x36, + 0xf3, 0x99, 0x97, 0x35, 0x52, 0xff, 0xa3, 0x00, 0xda, 0x73, 0x6c, 0xe6, 0x39, 0xa6, 0x49, 0x3d, + 0x4c, 0x47, 0x86, 0x6f, 0x38, 0x36, 0xfa, 0x02, 0xea, 0x7c, 0x3f, 0x7d, 0xc2, 0x48, 0x53, 0xd9, + 0x54, 0xb6, 0x16, 0x77, 0xde, 0xd6, 0xe2, 0x4b, 0x89, 0xe0, 0x35, 0xf7, 0x74, 0xc0, 0x17, 0x7c, + 0x8d, 0x6b, 0x6b, 0xa3, 0x3b, 0xda, 0x61, 0xef, 0x4b, 0xaa, 0xb3, 0x03, 0xca, 0x48, 0x07, 0x3d, + 0x3d, 0xdb, 0x98, 0x99, 0x9c, 0x6d, 0x40, 0xbc, 0x86, 0x23, 0x54, 0x74, 0x08, 0x55, 0x81, 0x5e, + 0x11, 0xe8, 0xdb, 0xa5, 0xe8, 0x72, 0xd3, 0x1a, 0x26, 0xbf, 0x7a, 0xf0, 0x84, 0x51, 0x9b, 0x87, + 0xd7, 0x79, 0x49, 0x42, 0x57, 0xef, 0x13, 0x46, 0xb0, 0x00, 0x42, 0x6f, 0x41, 0xdd, 0x93, 0xe1, + 0x37, 0x67, 0x37, 0x95, 0xad, 0xd9, 0xce, 0x0d, 0xa9, 0x55, 0x0f, 0xb7, 0x85, 0x23, 0x0d, 0xf5, + 0xa9, 0x02, 0xb7, 0xf2, 0xfb, 0xde, 0x37, 0x7c, 0x86, 0x7e, 0x91, 0xdb, 0xbb, 0x36, 0xdd, 0xde, + 0xb9, 0xb5, 0xd8, 0x79, 0xe4, 0x38, 0x5c, 0x49, 0xec, 0xfb, 0x08, 0x6a, 0x06, 0xa3, 0x96, 0xdf, + 0xac, 0x6c, 0xce, 0x6e, 0x2d, 0xee, 0xbc, 0xa9, 0x95, 0xe4, 0xba, 0x96, 0x8f, 0xae, 0xb3, 0x24, + 0x71, 0x6b, 0x0f, 0x39, 0x02, 0x0e, 0x80, 0xd4, 0xdf, 0x56, 0x00, 0xee, 0x53, 0xd7, 0x74, 0xc6, + 0x16, 0xb5, 0xd9, 0x35, 0x5c, 0xdd, 0x43, 0xa8, 0xfa, 0x2e, 0xd5, 0xe5, 0xd5, 0xbd, 0x5e, 0xba, + 0x83, 0x38, 0xa8, 0xae, 0x4b, 0xf5, 0xf8, 0xd2, 0xf8, 0x17, 0x16, 0x10, 0xe8, 0x53, 0x98, 0xf3, + 0x19, 0x61, 0x43, 0x5f, 0x5c, 0xd9, 0xe2, 0xce, 0xed, 0x69, 0xc0, 0x84, 0x41, 0xa7, 0x21, 0xe1, + 0xe6, 0x82, 0x6f, 0x2c, 0x81, 0xd4, 0xbf, 0xcf, 0xc2, 0x4a, 0xac, 0xbc, 0xe7, 0xd8, 0x7d, 0x83, + 0xf1, 0x94, 0xbe, 0x07, 0x55, 0x36, 0x76, 0xa9, 0x38, 0x93, 0x85, 0xce, 0xeb, 0x61, 0x30, 0xc7, + 0x63, 0x97, 0xbe, 0x38, 0xdb, 0x58, 0x2b, 0x30, 0xe1, 0x22, 0x2c, 0x8c, 0xd0, 0x7e, 0x14, 0x67, + 0x45, 0x98, 0xbf, 0x9b, 0x76, 0xfe, 0xe2, 0x6c, 0xa3, 0xa0, 0xd7, 0x68, 0x11, 0x52, 0x3a, 0x44, + 0xf4, 0x1a, 0xcc, 0x79, 0x94, 0xf8, 0x8e, 0xdd, 0xac, 0x0a, 0xb4, 0x68, 0x2b, 0x58, 0xac, 0x62, + 0x29, 0x45, 0xb7, 0x61, 0xde, 0xa2, 0xbe, 0x4f, 0x06, 0xb4, 0x59, 0x13, 0x8a, 0xcb, 0x52, 0x71, + 0xfe, 0x20, 0x58, 0xc6, 0xa1, 0x1c, 0x7d, 0x09, 0x0d, 0x93, 0xf8, 0xec, 0xc4, 0xed, 0x13, 0x46, + 0x8f, 0x0d, 0x8b, 0x36, 0xe7, 0xc4, 0x81, 0xbe, 0x31, 0xdd, 0xdd, 0x73, 0x8b, 0xce, 0x2d, 0x89, + 0xde, 0xd8, 0x4f, 0x21, 0xe1, 0x0c, 0x32, 0x1a, 0x01, 0xe2, 0x2b, 0xc7, 0x1e, 0xb1, 0xfd, 0xe0, + 0xa0, 0xb8, 0xbf, 0xf9, 0x4b, 0xfb, 0x5b, 0x97, 0xfe, 0xd0, 0x7e, 0x0e, 0x0d, 0x17, 0x78, 0x50, + 0xff, 0xa8, 0x40, 0x23, 0xbe, 0xa6, 0x6b, 0xa8, 0xd5, 0x8f, 0xd3, 0xb5, 0xfa, 0xfd, 0x29, 0x92, + 0xb3, 0xa4, 0x46, 0xff, 0x59, 0x01, 0x14, 0x2b, 0x61, 0xc7, 0x34, 0x7b, 0x44, 0x3f, 0x45, 0x9b, + 0x50, 0xb5, 0x89, 0x15, 0xe6, 0x64, 0x54, 0x20, 0x3f, 0x21, 0x16, 0xc5, 0x42, 0x82, 0xbe, 0x52, + 0x00, 0x0d, 0xc5, 0xd1, 0xf7, 0x77, 0x6d, 0xdb, 0x61, 0x84, 0x9f, 0x46, 0x18, 0xd0, 0xde, 0x14, + 0x01, 0x85, 0xbe, 0xb4, 0x93, 0x1c, 0xca, 0x03, 0x9b, 0x79, 0xe3, 0xf8, 0x16, 0xf2, 0x0a, 0xb8, + 0xc0, 0x35, 0xfa, 0x39, 0x80, 0x27, 0x31, 0x8f, 0x1d, 0x59, 0xb6, 0xe5, 0x3d, 0x20, 0x74, 0xbf, + 0xe7, 0xd8, 0x8f, 0x8c, 0x41, 0xdc, 0x58, 0x70, 0x04, 0x81, 0x13, 0x70, 0xeb, 0x0f, 0x60, 0xad, + 0x24, 0x4e, 0x74, 0x03, 0x66, 0x4f, 0xe9, 0x38, 0x38, 0x2a, 0xcc, 0x7f, 0xa2, 0x55, 0xa8, 0x8d, + 0x88, 0x39, 0xa4, 0x41, 0x4d, 0xe2, 0xe0, 0xe3, 0x6e, 0xe5, 0x7d, 0x45, 0xfd, 0x43, 0x2d, 0x99, + 0x29, 0xbc, 0xdf, 0xa0, 0x2d, 0xfe, 0x3c, 0xb8, 0xa6, 0xa1, 0x13, 0x5f, 0x60, 0xd4, 0x3a, 0x2f, + 0x05, 0x4f, 0x43, 0xb0, 0x86, 0x23, 0x29, 0xfa, 0x25, 0xd4, 0x7d, 0x6a, 0x52, 0x9d, 0x39, 0x9e, + 0x6c, 0x71, 0xef, 0x4c, 0x99, 0x53, 0xa4, 0x47, 0xcd, 0xae, 0x34, 0x0d, 0xe0, 0xc3, 0x2f, 0x1c, + 0x41, 0xa2, 0x4f, 0xa1, 0xce, 0xa8, 0xe5, 0x9a, 0x84, 0x51, 0x79, 0x7a, 0xa9, 0xbc, 0xe2, 0xbd, + 0x83, 0x83, 0x1d, 0x39, 0xfd, 0x63, 0xa9, 0x26, 0xba, 0x67, 0x94, 0xa7, 0xe1, 0x2a, 0x8e, 0x60, + 0xd0, 0xcf, 0xa0, 0xee, 0x33, 0xfe, 0xaa, 0x0f, 0xc6, 0xa2, 0xa3, 0x9c, 0xf7, 0xac, 0x24, 0xfb, + 0x68, 0x60, 0x12, 0x43, 0x87, 0x2b, 0x38, 0x82, 0x43, 0xbb, 0xb0, 0x6c, 0x19, 0x36, 0xa6, 0xa4, + 0x3f, 0xee, 0x52, 0xdd, 0xb1, 0xfb, 0xbe, 0x68, 0x45, 0xb5, 0xce, 0x9a, 0x34, 0x5a, 0x3e, 0x48, + 0x8b, 0x71, 0x56, 0x1f, 0xed, 0xc3, 0x6a, 0xf8, 0xec, 0x7e, 0x6c, 0xf8, 0xcc, 0xf1, 0xc6, 0xfb, + 0x86, 0x65, 0x30, 0xd1, 0xa0, 0x6a, 0x9d, 0xe6, 0xe4, 0x6c, 0x63, 0x15, 0x17, 0xc8, 0x71, 0xa1, + 0x15, 0xef, 0x9d, 0x2e, 0x19, 0xfa, 0xb4, 0x2f, 0x1a, 0x4e, 0x3d, 0xee, 0x9d, 0x47, 0x62, 0x15, + 0x4b, 0x29, 0xfa, 0x69, 0x2a, 0x4d, 0xeb, 0x97, 0x4b, 0xd3, 0x46, 0x79, 0x8a, 0xa2, 0x13, 0x58, + 0x73, 0x3d, 0x67, 0xe0, 0x51, 0xdf, 0xbf, 0x4f, 0x49, 0xdf, 0x34, 0x6c, 0x1a, 0x9e, 0xcc, 0x82, + 0xd8, 0xd1, 0x2b, 0x93, 0xb3, 0x8d, 0xb5, 0xa3, 0x62, 0x15, 0x5c, 0x66, 0xab, 0xfe, 0xa5, 0x0a, + 0x37, 0xb2, 0x6f, 0x1c, 0xfa, 0x04, 0x90, 0xd3, 0xf3, 0xa9, 0x37, 0xa2, 0xfd, 0x8f, 0x82, 0xc1, + 0x8d, 0x4f, 0x37, 0x8a, 0x98, 0x6e, 0xa2, 0xba, 0x3d, 0xcc, 0x69, 0xe0, 0x02, 0xab, 0x60, 0x3e, + 0x92, 0x05, 0x50, 0x11, 0x81, 0x26, 0xe6, 0xa3, 0x5c, 0x11, 0xec, 0xc2, 0xb2, 0xac, 0xfd, 0x50, + 0x28, 0x92, 0x35, 0x71, 0xef, 0x27, 0x69, 0x31, 0xce, 0xea, 0xa3, 0x8f, 0xe0, 0x26, 0x19, 0x11, + 0xc3, 0x24, 0x3d, 0x93, 0x46, 0x20, 0x55, 0x01, 0xf2, 0xb2, 0x04, 0xb9, 0xb9, 0x9b, 0x55, 0xc0, + 0x79, 0x1b, 0x74, 0x00, 0x2b, 0x43, 0x3b, 0x0f, 0x15, 0xe4, 0xe1, 0x2b, 0x12, 0x6a, 0xe5, 0x24, + 0xaf, 0x82, 0x8b, 0xec, 0xd0, 0x17, 0x00, 0x7a, 0xf8, 0x30, 0xfb, 0xcd, 0x39, 0xd1, 0x49, 0xdf, + 0x9a, 0xa2, 0x5e, 0xa2, 0xd7, 0x3c, 0xee, 0x62, 0xd1, 0x92, 0x8f, 0x13, 0x98, 0xe8, 0x1e, 0x2c, + 0x79, 0xbc, 0x02, 0xa2, 0x50, 0xe7, 0x45, 0xa8, 0xdf, 0x91, 0x66, 0x4b, 0x38, 0x29, 0xc4, 0x69, + 0x5d, 0x74, 0x17, 0x1a, 0xba, 0x63, 0x9a, 0x22, 0xf3, 0xf7, 0x9c, 0xa1, 0xcd, 0x44, 0xf2, 0xd6, + 0x3a, 0x88, 0xbf, 0xcc, 0x7b, 0x29, 0x09, 0xce, 0x68, 0xaa, 0x7f, 0x56, 0x92, 0xcf, 0x4c, 0x58, + 0xce, 0xe8, 0x6e, 0x6a, 0xf4, 0x79, 0x2d, 0x33, 0xfa, 0xdc, 0xca, 0x5b, 0x24, 0x26, 0x1f, 0x03, + 0x96, 0x78, 0xf2, 0x1b, 0xf6, 0x20, 0xb8, 0x70, 0xd9, 0x12, 0xdf, 0x3e, 0xb7, 0x94, 0x22, 0xed, + 0xc4, 0xc3, 0x78, 0x53, 0xec, 0x3c, 0x29, 0xc4, 0x69, 0x64, 0xf5, 0x43, 0x68, 0xa4, 0xeb, 0x30, + 0x35, 0xd3, 0x2b, 0x17, 0xce, 0xf4, 0xcf, 0x15, 0x58, 0x2b, 0xf1, 0x8e, 0x4c, 0x68, 0x58, 0xe4, + 0x49, 0x22, 0x47, 0x2e, 0x9c, 0x8d, 0x39, 0x6b, 0xd2, 0x02, 0xd6, 0xa4, 0x3d, 0xb4, 0xd9, 0xa1, + 0xd7, 0x65, 0x9e, 0x61, 0x0f, 0x82, 0x7b, 0x38, 0x48, 0x61, 0xe1, 0x0c, 0x36, 0xfa, 0x1c, 0xea, + 0x16, 0x79, 0xd2, 0x1d, 0x7a, 0x83, 0xa2, 0xf3, 0x9a, 0xce, 0x8f, 0x78, 0x3f, 0x0e, 0x24, 0x0a, + 0x8e, 0xf0, 0xd4, 0x43, 0xd8, 0x4c, 0x6d, 0x92, 0xb7, 0x0a, 0xfa, 0x68, 0x68, 0x76, 0x69, 0x7c, + 0xe1, 0x6f, 0xc2, 0x82, 0x4b, 0x3c, 0x66, 0x44, 0xed, 0xa2, 0xd6, 0x59, 0x9a, 0x9c, 0x6d, 0x2c, + 0x1c, 0x85, 0x8b, 0x38, 0x96, 0xab, 0xff, 0x55, 0xa0, 0xd6, 0xd5, 0x89, 0x49, 0xaf, 0x81, 0x3a, + 0xdc, 0x4f, 0x51, 0x07, 0xb5, 0x34, 0x89, 0x44, 0x3c, 0xa5, 0xac, 0x61, 0x3f, 0xc3, 0x1a, 0x5e, + 0xbd, 0x00, 0xe7, 0x7c, 0xc2, 0xf0, 0x01, 0x2c, 0x44, 0xee, 0x52, 0x5d, 0x52, 0xb9, 0xa8, 0x4b, + 0xaa, 0xbf, 0xaf, 0xc0, 0x62, 0xc2, 0xc5, 0xe5, 0xac, 0xf9, 0x71, 0x27, 0x06, 0x0d, 0xde, 0x86, + 0x76, 0xa6, 0xd9, 0x88, 0x16, 0x0e, 0x15, 0xc1, 0xfc, 0x16, 0xbf, 0xde, 0xf9, 0x59, 0xe3, 0x43, + 0x68, 0x30, 0xe2, 0x0d, 0x28, 0x0b, 0x65, 0xe2, 0xc0, 0x16, 0xe2, 0x49, 0xff, 0x38, 0x25, 0xc5, + 0x19, 0xed, 0xf5, 0x7b, 0xb0, 0x94, 0x72, 0x76, 0xa9, 0x21, 0xec, 0x2b, 0x7e, 0x38, 0x71, 0x72, + 0x5e, 0x43, 0x76, 0x7d, 0x92, 0xca, 0xae, 0xad, 0xf2, 0xc3, 0x4c, 0x94, 0x4c, 0x59, 0x8e, 0xe1, + 0x4c, 0x8e, 0xbd, 0x31, 0x15, 0xda, 0xf9, 0x99, 0xf6, 0xaf, 0x0a, 0xac, 0x26, 0xb4, 0x63, 0x6e, + 0xfa, 0xc3, 0x54, 0x83, 0xde, 0xca, 0x34, 0xe8, 0x66, 0x91, 0xcd, 0x95, 0x91, 0xd3, 0x62, 0x76, + 0x37, 0x7b, 0xd5, 0xec, 0xee, 0x0a, 0x48, 0xb1, 0xfa, 0x27, 0x05, 0x96, 0x13, 0x67, 0x77, 0x0d, + 0x8c, 0xf1, 0x61, 0x9a, 0x31, 0xbe, 0x3a, 0x4d, 0xd2, 0x94, 0x50, 0xc6, 0xbf, 0xd6, 0x52, 0xc1, + 0x7f, 0xeb, 0x49, 0xcc, 0xaf, 0x61, 0x75, 0xe4, 0x98, 0x43, 0x8b, 0xee, 0x99, 0xc4, 0xb0, 0x42, + 0x05, 0x3e, 0x31, 0xce, 0x66, 0xff, 0x18, 0x8a, 0xe0, 0xa9, 0xe7, 0x1b, 0x3e, 0xa3, 0x36, 0xfb, + 0x2c, 0xb6, 0xec, 0x7c, 0x57, 0x3a, 0x59, 0xfd, 0xac, 0x00, 0x0e, 0x17, 0x3a, 0x41, 0x3f, 0x80, + 0x45, 0x3e, 0x30, 0x1b, 0x3a, 0xe5, 0xdc, 0x5b, 0x26, 0xd6, 0x8a, 0x04, 0x5a, 0xec, 0xc6, 0x22, + 0x9c, 0xd4, 0x43, 0x8f, 0x61, 0xc5, 0x75, 0xfa, 0x07, 0xc4, 0x26, 0x03, 0xca, 0xc7, 0x8c, 0x23, + 0xf1, 0x07, 0xb2, 0x60, 0x36, 0x0b, 0x9d, 0xf7, 0xc2, 0xc9, 0xf4, 0x28, 0xaf, 0xf2, 0x82, 0x53, + 0x84, 0xfc, 0xb2, 0x28, 0xea, 0x22, 0x48, 0xe4, 0x41, 0x63, 0x28, 0x9f, 0x7b, 0x49, 0xf4, 0x82, + 0xff, 0x5b, 0x76, 0xa6, 0xc9, 0xb0, 0x93, 0x94, 0x65, 0xdc, 0xfd, 0xd3, 0xeb, 0x38, 0xe3, 0xa1, + 0x94, 0xb8, 0xd5, 0xff, 0x1f, 0xe2, 0xa6, 0xfe, 0xbb, 0x0a, 0x37, 0x73, 0xad, 0x12, 0xfd, 0xf8, + 0x1c, 0x86, 0x73, 0xeb, 0xca, 0xd8, 0x4d, 0x6e, 0x40, 0x9f, 0xbd, 0xc4, 0x80, 0xbe, 0x0b, 0xcb, + 0xfa, 0xd0, 0xf3, 0xa8, 0xcd, 0x32, 0xac, 0x26, 0xa2, 0x46, 0x7b, 0x69, 0x31, 0xce, 0xea, 0x17, + 0xb1, 0xab, 0xda, 0x25, 0xd9, 0x55, 0x32, 0x0a, 0x39, 0x21, 0x07, 0x69, 0x97, 0x8f, 0x42, 0x0e, + 0xca, 0x59, 0x7d, 0x3e, 0x1d, 0x04, 0xa8, 0x11, 0xc2, 0x7c, 0x7a, 0x3a, 0x38, 0x49, 0x49, 0x71, + 0x46, 0xbb, 0x80, 0xa9, 0x2c, 0x4c, 0xcb, 0x54, 0x10, 0x49, 0x91, 0x30, 0x10, 0x35, 0xbe, 0x3d, + 0x4d, 0x2e, 0x4f, 0xcd, 0xc2, 0xd4, 0xbf, 0x29, 0xf0, 0x72, 0x69, 0x11, 0xa0, 0xdd, 0xd4, 0x93, + 0xbb, 0x9d, 0x79, 0x72, 0xbf, 0x57, 0x6a, 0x98, 0x78, 0x77, 0xbd, 0x62, 0x6a, 0xf4, 0xc1, 0x74, + 0xd4, 0xa8, 0x60, 0x6e, 0xbf, 0x98, 0x23, 0x75, 0xb6, 0x9f, 0x3e, 0x6b, 0xcd, 0x7c, 0xfd, 0xac, + 0x35, 0xf3, 0xcd, 0xb3, 0xd6, 0xcc, 0x6f, 0x26, 0x2d, 0xe5, 0xe9, 0xa4, 0xa5, 0x7c, 0x3d, 0x69, + 0x29, 0xdf, 0x4c, 0x5a, 0xca, 0x3f, 0x26, 0x2d, 0xe5, 0x77, 0xcf, 0x5b, 0x33, 0x9f, 0xcf, 0x4b, + 0x8f, 0xff, 0x0b, 0x00, 0x00, 0xff, 0xff, 0x9d, 0x5d, 0x9e, 0x04, 0x8c, 0x1b, 0x00, 0x00, } diff --git a/staging/src/k8s.io/api/apps/v1beta1/generated.proto b/staging/src/k8s.io/api/apps/v1beta1/generated.proto index dcb034dfbd6..da160922d7b 100644 --- a/staging/src/k8s.io/api/apps/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/apps/v1beta1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -338,6 +338,27 @@ message StatefulSet { optional StatefulSetStatus status = 3; } +// StatefulSetCondition describes the state of a statefulset at a certain point. +message StatefulSetCondition { + // Type of statefulset condition. + optional string type = 1; + + // Status of the condition, one of True, False, Unknown. + optional string status = 2; + + // Last time the condition transitioned from one status to another. + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.Time lastTransitionTime = 3; + + // The reason for the condition's last transition. + // +optional + optional string reason = 4; + + // A human readable message indicating details about the transition. + // +optional + optional string message = 5; +} + // StatefulSetList is a collection of StatefulSets. message StatefulSetList { // +optional @@ -442,6 +463,12 @@ message StatefulSetStatus { // newest ControllerRevision. // +optional optional int32 collisionCount = 9; + + // Represents the latest available observations of a statefulset's current state. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + repeated StatefulSetCondition conditions = 10; } // StatefulSetUpdateStrategy indicates the strategy that the StatefulSet diff --git a/staging/src/k8s.io/api/apps/v1beta1/register.go b/staging/src/k8s.io/api/apps/v1beta1/register.go index 84789be618d..5b16819f2f6 100644 --- a/staging/src/k8s.io/api/apps/v1beta1/register.go +++ b/staging/src/k8s.io/api/apps/v1beta1/register.go @@ -41,7 +41,7 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Deployment{}, diff --git a/staging/src/k8s.io/api/apps/v1beta1/types.go b/staging/src/k8s.io/api/apps/v1beta1/types.go index 63d1c725d8d..dd9e97e1015 100644 --- a/staging/src/k8s.io/api/apps/v1beta1/types.go +++ b/staging/src/k8s.io/api/apps/v1beta1/types.go @@ -26,6 +26,7 @@ import ( const ( ControllerRevisionHashLabelKey = "controller-revision-hash" StatefulSetRevisionLabel = ControllerRevisionHashLabelKey + StatefulSetPodNameLabel = "statefulset.kubernetes.io/pod-name" ) // ScaleSpec describes the attributes of a scale subresource @@ -247,6 +248,31 @@ type StatefulSetStatus struct { // newest ControllerRevision. // +optional CollisionCount *int32 `json:"collisionCount,omitempty" protobuf:"varint,9,opt,name=collisionCount"` + + // Represents the latest available observations of a statefulset's current state. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + Conditions []StatefulSetCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,10,rep,name=conditions"` +} + +type StatefulSetConditionType string + +// StatefulSetCondition describes the state of a statefulset at a certain point. +type StatefulSetCondition struct { + // Type of statefulset condition. + Type StatefulSetConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=StatefulSetConditionType"` + // Status of the condition, one of True, False, Unknown. + Status v1.ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=k8s.io/api/core/v1.ConditionStatus"` + // Last time the condition transitioned from one status to another. + // +optional + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,3,opt,name=lastTransitionTime"` + // The reason for the condition's last transition. + // +optional + Reason string `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"` + // A human readable message indicating details about the transition. + // +optional + Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/staging/src/k8s.io/api/apps/v1beta1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/apps/v1beta1/types_swagger_doc_generated.go index 72d5e69ce0d..d12baf39f5a 100644 --- a/staging/src/k8s.io/api/apps/v1beta1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/apps/v1beta1/types_swagger_doc_generated.go @@ -206,6 +206,19 @@ func (StatefulSet) SwaggerDoc() map[string]string { return map_StatefulSet } +var map_StatefulSetCondition = map[string]string{ + "": "StatefulSetCondition describes the state of a statefulset at a certain point.", + "type": "Type of statefulset condition.", + "status": "Status of the condition, one of True, False, Unknown.", + "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.", +} + +func (StatefulSetCondition) SwaggerDoc() map[string]string { + return map_StatefulSetCondition +} + var map_StatefulSetList = map[string]string{ "": "StatefulSetList is a collection of StatefulSets.", } @@ -240,6 +253,7 @@ var map_StatefulSetStatus = map[string]string{ "currentRevision": "currentRevision, if not empty, indicates the version of the StatefulSet used to generate Pods in the sequence [0,currentReplicas).", "updateRevision": "updateRevision, if not empty, indicates the version of the StatefulSet used to generate Pods in the sequence [replicas-updatedReplicas,replicas)", "collisionCount": "collisionCount is the count of hash collisions for the StatefulSet. The StatefulSet controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ControllerRevision.", + "conditions": "Represents the latest available observations of a statefulset's current state.", } func (StatefulSetStatus) SwaggerDoc() map[string]string { diff --git a/staging/src/k8s.io/api/apps/v1beta1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/apps/v1beta1/zz_generated.deepcopy.go index 2f046b02057..d83e9d6fe5a 100644 --- a/staging/src/k8s.io/api/apps/v1beta1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/apps/v1beta1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,105 +23,10 @@ package v1beta1 import ( core_v1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" intstr "k8s.io/apimachinery/pkg/util/intstr" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ControllerRevision).DeepCopyInto(out.(*ControllerRevision)) - return nil - }, InType: reflect.TypeOf(&ControllerRevision{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ControllerRevisionList).DeepCopyInto(out.(*ControllerRevisionList)) - return nil - }, InType: reflect.TypeOf(&ControllerRevisionList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Deployment).DeepCopyInto(out.(*Deployment)) - return nil - }, InType: reflect.TypeOf(&Deployment{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeploymentCondition).DeepCopyInto(out.(*DeploymentCondition)) - return nil - }, InType: reflect.TypeOf(&DeploymentCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeploymentList).DeepCopyInto(out.(*DeploymentList)) - return nil - }, InType: reflect.TypeOf(&DeploymentList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeploymentRollback).DeepCopyInto(out.(*DeploymentRollback)) - return nil - }, InType: reflect.TypeOf(&DeploymentRollback{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeploymentSpec).DeepCopyInto(out.(*DeploymentSpec)) - return nil - }, InType: reflect.TypeOf(&DeploymentSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeploymentStatus).DeepCopyInto(out.(*DeploymentStatus)) - return nil - }, InType: reflect.TypeOf(&DeploymentStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeploymentStrategy).DeepCopyInto(out.(*DeploymentStrategy)) - return nil - }, InType: reflect.TypeOf(&DeploymentStrategy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RollbackConfig).DeepCopyInto(out.(*RollbackConfig)) - return nil - }, InType: reflect.TypeOf(&RollbackConfig{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RollingUpdateDeployment).DeepCopyInto(out.(*RollingUpdateDeployment)) - return nil - }, InType: reflect.TypeOf(&RollingUpdateDeployment{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RollingUpdateStatefulSetStrategy).DeepCopyInto(out.(*RollingUpdateStatefulSetStrategy)) - return nil - }, InType: reflect.TypeOf(&RollingUpdateStatefulSetStrategy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Scale).DeepCopyInto(out.(*Scale)) - return nil - }, InType: reflect.TypeOf(&Scale{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ScaleSpec).DeepCopyInto(out.(*ScaleSpec)) - return nil - }, InType: reflect.TypeOf(&ScaleSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ScaleStatus).DeepCopyInto(out.(*ScaleStatus)) - return nil - }, InType: reflect.TypeOf(&ScaleStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StatefulSet).DeepCopyInto(out.(*StatefulSet)) - return nil - }, InType: reflect.TypeOf(&StatefulSet{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StatefulSetList).DeepCopyInto(out.(*StatefulSetList)) - return nil - }, InType: reflect.TypeOf(&StatefulSetList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StatefulSetSpec).DeepCopyInto(out.(*StatefulSetSpec)) - return nil - }, InType: reflect.TypeOf(&StatefulSetSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StatefulSetStatus).DeepCopyInto(out.(*StatefulSetStatus)) - return nil - }, InType: reflect.TypeOf(&StatefulSetStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StatefulSetUpdateStrategy).DeepCopyInto(out.(*StatefulSetUpdateStrategy)) - return nil - }, InType: reflect.TypeOf(&StatefulSetUpdateStrategy{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ControllerRevision) DeepCopyInto(out *ControllerRevision) { *out = *in @@ -591,6 +496,23 @@ func (in *StatefulSet) DeepCopyObject() runtime.Object { } } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StatefulSetCondition) DeepCopyInto(out *StatefulSetCondition) { + *out = *in + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatefulSetCondition. +func (in *StatefulSetCondition) DeepCopy() *StatefulSetCondition { + if in == nil { + return nil + } + out := new(StatefulSetCondition) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *StatefulSetList) DeepCopyInto(out *StatefulSetList) { *out = *in @@ -698,6 +620,13 @@ func (in *StatefulSetStatus) DeepCopyInto(out *StatefulSetStatus) { **out = **in } } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]StatefulSetCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } diff --git a/staging/src/k8s.io/api/apps/v1beta2/BUILD b/staging/src/k8s.io/api/apps/v1beta2/BUILD index bd5dbadf856..c13a6ff578e 100644 --- a/staging/src/k8s.io/api/apps/v1beta2/BUILD +++ b/staging/src/k8s.io/api/apps/v1beta2/BUILD @@ -21,7 +21,6 @@ go_library( "//vendor/github.com/gogo/protobuf/sortkeys:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", diff --git a/staging/src/k8s.io/api/apps/v1beta2/doc.go b/staging/src/k8s.io/api/apps/v1beta2/doc.go index dafca1201c5..e93e164e10f 100644 --- a/staging/src/k8s.io/api/apps/v1beta2/doc.go +++ b/staging/src/k8s.io/api/apps/v1beta2/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:openapi-gen=true package v1beta2 // import "k8s.io/api/apps/v1beta2" diff --git a/staging/src/k8s.io/api/apps/v1beta2/generated.pb.go b/staging/src/k8s.io/api/apps/v1beta2/generated.pb.go index 07d95d8aca5..cfd422edbbe 100644 --- a/staging/src/k8s.io/api/apps/v1beta2/generated.pb.go +++ b/staging/src/k8s.io/api/apps/v1beta2/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -28,6 +28,7 @@ limitations under the License. ControllerRevision ControllerRevisionList DaemonSet + DaemonSetCondition DaemonSetList DaemonSetSpec DaemonSetStatus @@ -50,6 +51,7 @@ limitations under the License. ScaleSpec ScaleStatus StatefulSet + StatefulSetCondition StatefulSetList StatefulSetSpec StatefulSetStatus @@ -97,120 +99,129 @@ func (m *DaemonSet) Reset() { *m = DaemonSet{} } func (*DaemonSet) ProtoMessage() {} func (*DaemonSet) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{2} } +func (m *DaemonSetCondition) Reset() { *m = DaemonSetCondition{} } +func (*DaemonSetCondition) ProtoMessage() {} +func (*DaemonSetCondition) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{3} } + func (m *DaemonSetList) Reset() { *m = DaemonSetList{} } func (*DaemonSetList) ProtoMessage() {} -func (*DaemonSetList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{3} } +func (*DaemonSetList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{4} } func (m *DaemonSetSpec) Reset() { *m = DaemonSetSpec{} } func (*DaemonSetSpec) ProtoMessage() {} -func (*DaemonSetSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{4} } +func (*DaemonSetSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{5} } func (m *DaemonSetStatus) Reset() { *m = DaemonSetStatus{} } func (*DaemonSetStatus) ProtoMessage() {} -func (*DaemonSetStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{5} } +func (*DaemonSetStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{6} } func (m *DaemonSetUpdateStrategy) Reset() { *m = DaemonSetUpdateStrategy{} } func (*DaemonSetUpdateStrategy) ProtoMessage() {} -func (*DaemonSetUpdateStrategy) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{6} } +func (*DaemonSetUpdateStrategy) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{7} } func (m *Deployment) Reset() { *m = Deployment{} } func (*Deployment) ProtoMessage() {} -func (*Deployment) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{7} } +func (*Deployment) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{8} } func (m *DeploymentCondition) Reset() { *m = DeploymentCondition{} } func (*DeploymentCondition) ProtoMessage() {} -func (*DeploymentCondition) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{8} } +func (*DeploymentCondition) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{9} } func (m *DeploymentList) Reset() { *m = DeploymentList{} } func (*DeploymentList) ProtoMessage() {} -func (*DeploymentList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{9} } +func (*DeploymentList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{10} } func (m *DeploymentSpec) Reset() { *m = DeploymentSpec{} } func (*DeploymentSpec) ProtoMessage() {} -func (*DeploymentSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{10} } +func (*DeploymentSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{11} } func (m *DeploymentStatus) Reset() { *m = DeploymentStatus{} } func (*DeploymentStatus) ProtoMessage() {} -func (*DeploymentStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{11} } +func (*DeploymentStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{12} } func (m *DeploymentStrategy) Reset() { *m = DeploymentStrategy{} } func (*DeploymentStrategy) ProtoMessage() {} -func (*DeploymentStrategy) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{12} } +func (*DeploymentStrategy) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{13} } func (m *ReplicaSet) Reset() { *m = ReplicaSet{} } func (*ReplicaSet) ProtoMessage() {} -func (*ReplicaSet) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{13} } +func (*ReplicaSet) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{14} } func (m *ReplicaSetCondition) Reset() { *m = ReplicaSetCondition{} } func (*ReplicaSetCondition) ProtoMessage() {} -func (*ReplicaSetCondition) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{14} } +func (*ReplicaSetCondition) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{15} } func (m *ReplicaSetList) Reset() { *m = ReplicaSetList{} } func (*ReplicaSetList) ProtoMessage() {} -func (*ReplicaSetList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{15} } +func (*ReplicaSetList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{16} } func (m *ReplicaSetSpec) Reset() { *m = ReplicaSetSpec{} } func (*ReplicaSetSpec) ProtoMessage() {} -func (*ReplicaSetSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{16} } +func (*ReplicaSetSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{17} } func (m *ReplicaSetStatus) Reset() { *m = ReplicaSetStatus{} } func (*ReplicaSetStatus) ProtoMessage() {} -func (*ReplicaSetStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{17} } +func (*ReplicaSetStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{18} } func (m *RollingUpdateDaemonSet) Reset() { *m = RollingUpdateDaemonSet{} } func (*RollingUpdateDaemonSet) ProtoMessage() {} -func (*RollingUpdateDaemonSet) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{18} } +func (*RollingUpdateDaemonSet) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{19} } func (m *RollingUpdateDeployment) Reset() { *m = RollingUpdateDeployment{} } func (*RollingUpdateDeployment) ProtoMessage() {} func (*RollingUpdateDeployment) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{19} + return fileDescriptorGenerated, []int{20} } func (m *RollingUpdateStatefulSetStrategy) Reset() { *m = RollingUpdateStatefulSetStrategy{} } func (*RollingUpdateStatefulSetStrategy) ProtoMessage() {} func (*RollingUpdateStatefulSetStrategy) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{20} + return fileDescriptorGenerated, []int{21} } func (m *Scale) Reset() { *m = Scale{} } func (*Scale) ProtoMessage() {} -func (*Scale) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{21} } +func (*Scale) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{22} } func (m *ScaleSpec) Reset() { *m = ScaleSpec{} } func (*ScaleSpec) ProtoMessage() {} -func (*ScaleSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{22} } +func (*ScaleSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{23} } func (m *ScaleStatus) Reset() { *m = ScaleStatus{} } func (*ScaleStatus) ProtoMessage() {} -func (*ScaleStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{23} } +func (*ScaleStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{24} } func (m *StatefulSet) Reset() { *m = StatefulSet{} } func (*StatefulSet) ProtoMessage() {} -func (*StatefulSet) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{24} } +func (*StatefulSet) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{25} } + +func (m *StatefulSetCondition) Reset() { *m = StatefulSetCondition{} } +func (*StatefulSetCondition) ProtoMessage() {} +func (*StatefulSetCondition) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{26} } func (m *StatefulSetList) Reset() { *m = StatefulSetList{} } func (*StatefulSetList) ProtoMessage() {} -func (*StatefulSetList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{25} } +func (*StatefulSetList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{27} } func (m *StatefulSetSpec) Reset() { *m = StatefulSetSpec{} } func (*StatefulSetSpec) ProtoMessage() {} -func (*StatefulSetSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{26} } +func (*StatefulSetSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{28} } func (m *StatefulSetStatus) Reset() { *m = StatefulSetStatus{} } func (*StatefulSetStatus) ProtoMessage() {} -func (*StatefulSetStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{27} } +func (*StatefulSetStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{29} } func (m *StatefulSetUpdateStrategy) Reset() { *m = StatefulSetUpdateStrategy{} } func (*StatefulSetUpdateStrategy) ProtoMessage() {} func (*StatefulSetUpdateStrategy) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{28} + return fileDescriptorGenerated, []int{30} } func init() { proto.RegisterType((*ControllerRevision)(nil), "k8s.io.api.apps.v1beta2.ControllerRevision") proto.RegisterType((*ControllerRevisionList)(nil), "k8s.io.api.apps.v1beta2.ControllerRevisionList") proto.RegisterType((*DaemonSet)(nil), "k8s.io.api.apps.v1beta2.DaemonSet") + proto.RegisterType((*DaemonSetCondition)(nil), "k8s.io.api.apps.v1beta2.DaemonSetCondition") proto.RegisterType((*DaemonSetList)(nil), "k8s.io.api.apps.v1beta2.DaemonSetList") proto.RegisterType((*DaemonSetSpec)(nil), "k8s.io.api.apps.v1beta2.DaemonSetSpec") proto.RegisterType((*DaemonSetStatus)(nil), "k8s.io.api.apps.v1beta2.DaemonSetStatus") @@ -233,6 +244,7 @@ func init() { proto.RegisterType((*ScaleSpec)(nil), "k8s.io.api.apps.v1beta2.ScaleSpec") proto.RegisterType((*ScaleStatus)(nil), "k8s.io.api.apps.v1beta2.ScaleStatus") proto.RegisterType((*StatefulSet)(nil), "k8s.io.api.apps.v1beta2.StatefulSet") + proto.RegisterType((*StatefulSetCondition)(nil), "k8s.io.api.apps.v1beta2.StatefulSetCondition") proto.RegisterType((*StatefulSetList)(nil), "k8s.io.api.apps.v1beta2.StatefulSetList") proto.RegisterType((*StatefulSetSpec)(nil), "k8s.io.api.apps.v1beta2.StatefulSetSpec") proto.RegisterType((*StatefulSetStatus)(nil), "k8s.io.api.apps.v1beta2.StatefulSetStatus") @@ -355,6 +367,48 @@ func (m *DaemonSet) MarshalTo(dAtA []byte) (int, error) { return i, nil } +func (m *DaemonSetCondition) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DaemonSetCondition) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) + i += copy(dAtA[i:], m.Type) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Status))) + i += copy(dAtA[i:], m.Status) + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.LastTransitionTime.Size())) + n7, err := m.LastTransitionTime.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n7 + dAtA[i] = 0x22 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) + i += copy(dAtA[i:], m.Reason) + dAtA[i] = 0x2a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) + i += copy(dAtA[i:], m.Message) + return i, nil +} + func (m *DaemonSetList) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -373,11 +427,11 @@ func (m *DaemonSetList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n7, err := m.ListMeta.MarshalTo(dAtA[i:]) + n8, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n7 + i += n8 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -412,28 +466,28 @@ func (m *DaemonSetSpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Selector.Size())) - n8, err := m.Selector.MarshalTo(dAtA[i:]) + n9, err := m.Selector.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n8 + i += n9 } dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Template.Size())) - n9, err := m.Template.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n9 - dAtA[i] = 0x1a - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.UpdateStrategy.Size())) - n10, err := m.UpdateStrategy.MarshalTo(dAtA[i:]) + n10, err := m.Template.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n10 + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.UpdateStrategy.Size())) + n11, err := m.UpdateStrategy.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n11 dAtA[i] = 0x20 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.MinReadySeconds)) @@ -489,6 +543,18 @@ func (m *DaemonSetStatus) MarshalTo(dAtA []byte) (int, error) { i++ i = encodeVarintGenerated(dAtA, i, uint64(*m.CollisionCount)) } + if len(m.Conditions) > 0 { + for _, msg := range m.Conditions { + dAtA[i] = 0x52 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } return i, nil } @@ -515,11 +581,11 @@ func (m *DaemonSetUpdateStrategy) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.RollingUpdate.Size())) - n11, err := m.RollingUpdate.MarshalTo(dAtA[i:]) + n12, err := m.RollingUpdate.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n11 + i += n12 } return i, nil } @@ -542,27 +608,27 @@ func (m *Deployment) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n12, err := m.ObjectMeta.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n12 - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) - n13, err := m.Spec.MarshalTo(dAtA[i:]) + n13, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n13 - dAtA[i] = 0x1a + dAtA[i] = 0x12 i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) - n14, err := m.Status.MarshalTo(dAtA[i:]) + i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) + n14, err := m.Spec.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n14 + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) + n15, err := m.Status.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n15 return i, nil } @@ -600,19 +666,19 @@ func (m *DeploymentCondition) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x32 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.LastUpdateTime.Size())) - n15, err := m.LastUpdateTime.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n15 - dAtA[i] = 0x3a - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.LastTransitionTime.Size())) - n16, err := m.LastTransitionTime.MarshalTo(dAtA[i:]) + n16, err := m.LastUpdateTime.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n16 + dAtA[i] = 0x3a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.LastTransitionTime.Size())) + n17, err := m.LastTransitionTime.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n17 return i, nil } @@ -634,11 +700,11 @@ func (m *DeploymentList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n17, err := m.ListMeta.MarshalTo(dAtA[i:]) + n18, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n17 + i += n18 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -678,28 +744,28 @@ func (m *DeploymentSpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Selector.Size())) - n18, err := m.Selector.MarshalTo(dAtA[i:]) + n19, err := m.Selector.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n18 + i += n19 } dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Template.Size())) - n19, err := m.Template.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n19 - dAtA[i] = 0x22 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Strategy.Size())) - n20, err := m.Strategy.MarshalTo(dAtA[i:]) + n20, err := m.Template.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n20 + dAtA[i] = 0x22 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Strategy.Size())) + n21, err := m.Strategy.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n21 dAtA[i] = 0x28 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.MinReadySeconds)) @@ -800,11 +866,11 @@ func (m *DeploymentStrategy) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.RollingUpdate.Size())) - n21, err := m.RollingUpdate.MarshalTo(dAtA[i:]) + n22, err := m.RollingUpdate.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n21 + i += n22 } return i, nil } @@ -827,27 +893,27 @@ func (m *ReplicaSet) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n22, err := m.ObjectMeta.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n22 - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) - n23, err := m.Spec.MarshalTo(dAtA[i:]) + n23, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n23 - dAtA[i] = 0x1a + dAtA[i] = 0x12 i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) - n24, err := m.Status.MarshalTo(dAtA[i:]) + i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) + n24, err := m.Spec.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n24 + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) + n25, err := m.Status.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n25 return i, nil } @@ -877,11 +943,11 @@ func (m *ReplicaSetCondition) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.LastTransitionTime.Size())) - n25, err := m.LastTransitionTime.MarshalTo(dAtA[i:]) + n26, err := m.LastTransitionTime.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n25 + i += n26 dAtA[i] = 0x22 i++ i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) @@ -911,11 +977,11 @@ func (m *ReplicaSetList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n26, err := m.ListMeta.MarshalTo(dAtA[i:]) + n27, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n26 + i += n27 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -955,20 +1021,20 @@ func (m *ReplicaSetSpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Selector.Size())) - n27, err := m.Selector.MarshalTo(dAtA[i:]) + n28, err := m.Selector.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n27 + i += n28 } dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Template.Size())) - n28, err := m.Template.MarshalTo(dAtA[i:]) + n29, err := m.Template.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n28 + i += n29 dAtA[i] = 0x20 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.MinReadySeconds)) @@ -1039,11 +1105,11 @@ func (m *RollingUpdateDaemonSet) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.MaxUnavailable.Size())) - n29, err := m.MaxUnavailable.MarshalTo(dAtA[i:]) + n30, err := m.MaxUnavailable.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n29 + i += n30 } return i, nil } @@ -1067,21 +1133,21 @@ func (m *RollingUpdateDeployment) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.MaxUnavailable.Size())) - n30, err := m.MaxUnavailable.MarshalTo(dAtA[i:]) + n31, err := m.MaxUnavailable.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n30 + i += n31 } if m.MaxSurge != nil { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.MaxSurge.Size())) - n31, err := m.MaxSurge.MarshalTo(dAtA[i:]) + n32, err := m.MaxSurge.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n31 + i += n32 } return i, nil } @@ -1127,27 +1193,27 @@ func (m *Scale) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n32, err := m.ObjectMeta.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n32 - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) - n33, err := m.Spec.MarshalTo(dAtA[i:]) + n33, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n33 - dAtA[i] = 0x1a + dAtA[i] = 0x12 i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) - n34, err := m.Status.MarshalTo(dAtA[i:]) + i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) + n34, err := m.Spec.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n34 + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) + n35, err := m.Status.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n35 return i, nil } @@ -1237,27 +1303,69 @@ func (m *StatefulSet) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n35, err := m.ObjectMeta.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n35 - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) - n36, err := m.Spec.MarshalTo(dAtA[i:]) + n36, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n36 - dAtA[i] = 0x1a + dAtA[i] = 0x12 i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) - n37, err := m.Status.MarshalTo(dAtA[i:]) + i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) + n37, err := m.Spec.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n37 + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) + n38, err := m.Status.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n38 + return i, nil +} + +func (m *StatefulSetCondition) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *StatefulSetCondition) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) + i += copy(dAtA[i:], m.Type) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Status))) + i += copy(dAtA[i:], m.Status) + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.LastTransitionTime.Size())) + n39, err := m.LastTransitionTime.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n39 + dAtA[i] = 0x22 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) + i += copy(dAtA[i:], m.Reason) + dAtA[i] = 0x2a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) + i += copy(dAtA[i:], m.Message) return i, nil } @@ -1279,11 +1387,11 @@ func (m *StatefulSetList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n38, err := m.ListMeta.MarshalTo(dAtA[i:]) + n40, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n38 + i += n40 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -1323,20 +1431,20 @@ func (m *StatefulSetSpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Selector.Size())) - n39, err := m.Selector.MarshalTo(dAtA[i:]) + n41, err := m.Selector.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n39 + i += n41 } dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Template.Size())) - n40, err := m.Template.MarshalTo(dAtA[i:]) + n42, err := m.Template.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n40 + i += n42 if len(m.VolumeClaimTemplates) > 0 { for _, msg := range m.VolumeClaimTemplates { dAtA[i] = 0x22 @@ -1360,11 +1468,11 @@ func (m *StatefulSetSpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x3a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.UpdateStrategy.Size())) - n41, err := m.UpdateStrategy.MarshalTo(dAtA[i:]) + n43, err := m.UpdateStrategy.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n41 + i += n43 if m.RevisionHistoryLimit != nil { dAtA[i] = 0x40 i++ @@ -1416,6 +1524,18 @@ func (m *StatefulSetStatus) MarshalTo(dAtA []byte) (int, error) { i++ i = encodeVarintGenerated(dAtA, i, uint64(*m.CollisionCount)) } + if len(m.Conditions) > 0 { + for _, msg := range m.Conditions { + dAtA[i] = 0x52 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } return i, nil } @@ -1442,11 +1562,11 @@ func (m *StatefulSetUpdateStrategy) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.RollingUpdate.Size())) - n42, err := m.RollingUpdate.MarshalTo(dAtA[i:]) + n44, err := m.RollingUpdate.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n42 + i += n44 } return i, nil } @@ -1515,6 +1635,22 @@ func (m *DaemonSet) Size() (n int) { return n } +func (m *DaemonSetCondition) Size() (n int) { + 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 *DaemonSetList) Size() (n int) { var l int _ = l @@ -1561,6 +1697,12 @@ func (m *DaemonSetStatus) Size() (n int) { if m.CollisionCount != nil { n += 1 + sovGenerated(uint64(*m.CollisionCount)) } + if len(m.Conditions) > 0 { + for _, e := range m.Conditions { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } return n } @@ -1834,6 +1976,22 @@ func (m *StatefulSet) Size() (n int) { return n } +func (m *StatefulSetCondition) Size() (n int) { + 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 *StatefulSetList) Size() (n int) { var l int _ = l @@ -1893,6 +2051,12 @@ func (m *StatefulSetStatus) Size() (n int) { if m.CollisionCount != nil { n += 1 + sovGenerated(uint64(*m.CollisionCount)) } + if len(m.Conditions) > 0 { + for _, e := range m.Conditions { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } return n } @@ -1956,6 +2120,20 @@ func (this *DaemonSet) String() string { }, "") return s } +func (this *DaemonSetCondition) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&DaemonSetCondition{`, + `Type:` + fmt.Sprintf("%v", this.Type) + `,`, + `Status:` + fmt.Sprintf("%v", this.Status) + `,`, + `LastTransitionTime:` + strings.Replace(strings.Replace(this.LastTransitionTime.String(), "Time", "k8s_io_apimachinery_pkg_apis_meta_v1.Time", 1), `&`, ``, 1) + `,`, + `Reason:` + fmt.Sprintf("%v", this.Reason) + `,`, + `Message:` + fmt.Sprintf("%v", this.Message) + `,`, + `}`, + }, "") + return s +} func (this *DaemonSetList) String() string { if this == nil { return "nil" @@ -1995,6 +2173,7 @@ func (this *DaemonSetStatus) String() string { `NumberAvailable:` + fmt.Sprintf("%v", this.NumberAvailable) + `,`, `NumberUnavailable:` + fmt.Sprintf("%v", this.NumberUnavailable) + `,`, `CollisionCount:` + valueToStringGenerated(this.CollisionCount) + `,`, + `Conditions:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Conditions), "DaemonSetCondition", "DaemonSetCondition", 1), `&`, ``, 1) + `,`, `}`, }, "") return s @@ -2245,6 +2424,20 @@ func (this *StatefulSet) String() string { }, "") return s } +func (this *StatefulSetCondition) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&StatefulSetCondition{`, + `Type:` + fmt.Sprintf("%v", this.Type) + `,`, + `Status:` + fmt.Sprintf("%v", this.Status) + `,`, + `LastTransitionTime:` + strings.Replace(strings.Replace(this.LastTransitionTime.String(), "Time", "k8s_io_apimachinery_pkg_apis_meta_v1.Time", 1), `&`, ``, 1) + `,`, + `Reason:` + fmt.Sprintf("%v", this.Reason) + `,`, + `Message:` + fmt.Sprintf("%v", this.Message) + `,`, + `}`, + }, "") + return s +} func (this *StatefulSetList) String() string { if this == nil { return "nil" @@ -2286,6 +2479,7 @@ func (this *StatefulSetStatus) String() string { `CurrentRevision:` + fmt.Sprintf("%v", this.CurrentRevision) + `,`, `UpdateRevision:` + fmt.Sprintf("%v", this.UpdateRevision) + `,`, `CollisionCount:` + valueToStringGenerated(this.CollisionCount) + `,`, + `Conditions:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Conditions), "StatefulSetCondition", "StatefulSetCondition", 1), `&`, ``, 1) + `,`, `}`, }, "") return s @@ -2689,6 +2883,202 @@ func (m *DaemonSet) Unmarshal(dAtA []byte) error { } return nil } +func (m *DaemonSetCondition) 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: DaemonSetCondition: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DaemonSetCondition: 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 > l { + return io.ErrUnexpectedEOF + } + m.Type = DaemonSetConditionType(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 > 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 > 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 > 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 > 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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *DaemonSetList) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -3183,6 +3573,37 @@ func (m *DaemonSetStatus) Unmarshal(dAtA []byte) error { } } m.CollisionCount = &v + case 10: + 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 > l { + return io.ErrUnexpectedEOF + } + m.Conditions = append(m.Conditions, DaemonSetCondition{}) + 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:]) @@ -5969,6 +6390,202 @@ func (m *StatefulSet) Unmarshal(dAtA []byte) error { } return nil } +func (m *StatefulSetCondition) 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: StatefulSetCondition: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatefulSetCondition: 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 > l { + return io.ErrUnexpectedEOF + } + m.Type = StatefulSetConditionType(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 > 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 > 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 > 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 > 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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *StatefulSetList) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -6554,6 +7171,37 @@ func (m *StatefulSetStatus) Unmarshal(dAtA []byte) error { } } m.CollisionCount = &v + case 10: + 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 > l { + return io.ErrUnexpectedEOF + } + m.Conditions = append(m.Conditions, StatefulSetCondition{}) + 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:]) @@ -6797,138 +7445,142 @@ func init() { } var fileDescriptorGenerated = []byte{ - // 2119 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x5a, 0xcb, 0x6f, 0x1c, 0xb7, - 0x19, 0xd7, 0xec, 0x43, 0xda, 0xa5, 0x2c, 0xc9, 0xa6, 0x54, 0x49, 0x91, 0xdb, 0x95, 0xb0, 0x09, - 0x1c, 0x39, 0xb6, 0x67, 0x6d, 0xe5, 0x81, 0xc4, 0x06, 0xda, 0x6a, 0xa5, 0xd4, 0x76, 0xa0, 0x57, - 0x28, 0xc9, 0x40, 0x83, 0x16, 0x30, 0xb5, 0x4b, 0xaf, 0x26, 0x9a, 0x17, 0x66, 0x38, 0x5b, 0x2f, - 0x7a, 0xe9, 0xa9, 0x40, 0x81, 0x02, 0xe9, 0xb9, 0xff, 0x44, 0x7b, 0x2a, 0x8a, 0xf6, 0x56, 0x04, - 0x85, 0x2f, 0x05, 0x82, 0x5e, 0x92, 0x93, 0x50, 0x6f, 0x8e, 0x3d, 0xf7, 0x12, 0xa0, 0x40, 0x41, - 0x0e, 0xe7, 0xc1, 0x79, 0x58, 0x23, 0x25, 0xde, 0x14, 0xb9, 0x69, 0xf9, 0xfd, 0xbe, 0x1f, 0x3f, - 0x0e, 0x3f, 0x7e, 0xdf, 0x6f, 0x38, 0x02, 0x3f, 0x3e, 0x79, 0xd7, 0x55, 0x35, 0xab, 0x75, 0xe2, - 0x1d, 0x11, 0xc7, 0x24, 0x94, 0xb8, 0xad, 0x3e, 0x31, 0xbb, 0x96, 0xd3, 0x12, 0x06, 0x6c, 0x6b, - 0x2d, 0x6c, 0xdb, 0x6e, 0xab, 0x7f, 0xe7, 0x88, 0x50, 0xbc, 0xd6, 0xea, 0x11, 0x93, 0x38, 0x98, - 0x92, 0xae, 0x6a, 0x3b, 0x16, 0xb5, 0xe0, 0x82, 0x0f, 0x54, 0xb1, 0xad, 0xa9, 0x0c, 0xa8, 0x0a, - 0xe0, 0xd2, 0xad, 0x9e, 0x46, 0x8f, 0xbd, 0x23, 0xb5, 0x63, 0x19, 0xad, 0x9e, 0xd5, 0xb3, 0x5a, - 0x1c, 0x7f, 0xe4, 0x3d, 0xe1, 0xbf, 0xf8, 0x0f, 0xfe, 0x97, 0xcf, 0xb3, 0xd4, 0x8c, 0x4d, 0xd8, - 0xb1, 0x1c, 0xd2, 0xea, 0xdf, 0x49, 0xce, 0xb5, 0x74, 0x3d, 0x86, 0xb1, 0x2d, 0x5d, 0xeb, 0x0c, - 0x44, 0x58, 0x69, 0xe8, 0x5b, 0x11, 0xd4, 0xc0, 0x9d, 0x63, 0xcd, 0x24, 0xce, 0xa0, 0x65, 0x9f, - 0xf4, 0xd8, 0x80, 0xdb, 0x32, 0x08, 0xc5, 0x59, 0x13, 0xb4, 0xf2, 0xbc, 0x1c, 0xcf, 0xa4, 0x9a, - 0x41, 0x52, 0x0e, 0xef, 0x9c, 0xe5, 0xe0, 0x76, 0x8e, 0x89, 0x81, 0x53, 0x7e, 0x6f, 0xe6, 0xf9, - 0x79, 0x54, 0xd3, 0x5b, 0x9a, 0x49, 0x5d, 0xea, 0x24, 0x9d, 0x9a, 0xff, 0x51, 0x00, 0xdc, 0xb0, - 0x4c, 0xea, 0x58, 0xba, 0x4e, 0x1c, 0x44, 0xfa, 0x9a, 0xab, 0x59, 0x26, 0x7c, 0x0c, 0x6a, 0x6c, - 0x3d, 0x5d, 0x4c, 0xf1, 0xa2, 0xb2, 0xa2, 0xac, 0x4e, 0xae, 0xdd, 0x56, 0xa3, 0x4d, 0x09, 0xe9, - 0x55, 0xfb, 0xa4, 0xc7, 0x06, 0x5c, 0x95, 0xa1, 0xd5, 0xfe, 0x1d, 0x75, 0xf7, 0xe8, 0x63, 0xd2, - 0xa1, 0xdb, 0x84, 0xe2, 0x36, 0x7c, 0x76, 0xba, 0x3c, 0x36, 0x3c, 0x5d, 0x06, 0xd1, 0x18, 0x0a, - 0x59, 0xe1, 0x2e, 0xa8, 0x70, 0xf6, 0x12, 0x67, 0xbf, 0x95, 0xcb, 0x2e, 0x16, 0xad, 0x22, 0xfc, - 0x8b, 0xf7, 0x9f, 0x52, 0x62, 0xb2, 0xf0, 0xda, 0x97, 0x04, 0x75, 0x65, 0x13, 0x53, 0x8c, 0x38, - 0x11, 0xbc, 0x09, 0x6a, 0x8e, 0x08, 0x7f, 0xb1, 0xbc, 0xa2, 0xac, 0x96, 0xdb, 0x97, 0x05, 0xaa, - 0x16, 0x2c, 0x0b, 0x85, 0x88, 0xe6, 0x33, 0x05, 0xcc, 0xa7, 0xd7, 0xbd, 0xa5, 0xb9, 0x14, 0xfe, - 0x2c, 0xb5, 0x76, 0xb5, 0xd8, 0xda, 0x99, 0x37, 0x5f, 0x79, 0x38, 0x71, 0x30, 0x12, 0x5b, 0xf7, - 0x1e, 0xa8, 0x6a, 0x94, 0x18, 0xee, 0x62, 0x69, 0xa5, 0xbc, 0x3a, 0xb9, 0x76, 0x43, 0xcd, 0xc9, - 0x75, 0x35, 0x1d, 0x5d, 0x7b, 0x4a, 0xf0, 0x56, 0x1f, 0x32, 0x06, 0xe4, 0x13, 0x35, 0x7f, 0x53, - 0x02, 0xf5, 0x4d, 0x4c, 0x0c, 0xcb, 0xdc, 0x27, 0x74, 0x04, 0x3b, 0xf7, 0x00, 0x54, 0x5c, 0x9b, - 0x74, 0xc4, 0xce, 0x5d, 0xcb, 0x5d, 0x40, 0x18, 0xd3, 0xbe, 0x4d, 0x3a, 0xd1, 0x96, 0xb1, 0x5f, - 0x88, 0x33, 0xc0, 0x3d, 0x30, 0xee, 0x52, 0x4c, 0x3d, 0x97, 0x6f, 0xd8, 0xe4, 0xda, 0x6a, 0x01, - 0x2e, 0x8e, 0x6f, 0x4f, 0x0b, 0xb6, 0x71, 0xff, 0x37, 0x12, 0x3c, 0xcd, 0x3f, 0x29, 0x60, 0x2a, - 0xc4, 0x8e, 0x60, 0x37, 0xef, 0xcb, 0xbb, 0xd9, 0x3c, 0x7b, 0x01, 0x39, 0x9b, 0xf8, 0x69, 0x39, - 0x16, 0x38, 0x7b, 0x44, 0xf0, 0xe7, 0xa0, 0xe6, 0x12, 0x9d, 0x74, 0xa8, 0xe5, 0x88, 0xc0, 0xdf, - 0x2c, 0x18, 0x38, 0x3e, 0x22, 0xfa, 0xbe, 0x70, 0x6d, 0x5f, 0x62, 0x91, 0x07, 0xbf, 0x50, 0x48, - 0x09, 0x3f, 0x04, 0x35, 0x4a, 0x0c, 0x5b, 0xc7, 0x94, 0x88, 0x9d, 0x7c, 0x35, 0x1e, 0x3c, 0x2b, - 0x97, 0x8c, 0x6c, 0xcf, 0xea, 0x1e, 0x08, 0x18, 0xdf, 0xc6, 0xf0, 0x61, 0x04, 0xa3, 0x28, 0xa4, - 0x81, 0x36, 0x98, 0xf6, 0xec, 0x2e, 0x43, 0x52, 0x56, 0x62, 0x7a, 0x03, 0xb1, 0xad, 0xb7, 0xcf, - 0x7e, 0x2a, 0x87, 0x92, 0x5f, 0x7b, 0x5e, 0xcc, 0x32, 0x2d, 0x8f, 0xa3, 0x04, 0x3f, 0x5c, 0x07, - 0x33, 0x86, 0x66, 0x22, 0x82, 0xbb, 0x83, 0x7d, 0xd2, 0xb1, 0xcc, 0xae, 0xbb, 0x58, 0x59, 0x51, - 0x56, 0xab, 0xed, 0x05, 0x41, 0x30, 0xb3, 0x2d, 0x9b, 0x51, 0x12, 0x0f, 0xb7, 0xc0, 0x5c, 0x50, - 0x14, 0x1e, 0x68, 0x2e, 0xb5, 0x9c, 0xc1, 0x96, 0x66, 0x68, 0x74, 0x71, 0x9c, 0xf3, 0x2c, 0x0e, - 0x4f, 0x97, 0xe7, 0x50, 0x86, 0x1d, 0x65, 0x7a, 0x35, 0xff, 0x58, 0x05, 0x33, 0x89, 0x5c, 0x85, - 0x8f, 0xc0, 0x7c, 0xc7, 0x73, 0x1c, 0x62, 0xd2, 0x1d, 0xcf, 0x38, 0x22, 0xce, 0x7e, 0xe7, 0x98, - 0x74, 0x3d, 0x9d, 0x74, 0xf9, 0xb6, 0x56, 0xdb, 0x0d, 0x11, 0xeb, 0xfc, 0x46, 0x26, 0x0a, 0xe5, - 0x78, 0xc3, 0x0f, 0x00, 0x34, 0xf9, 0xd0, 0xb6, 0xe6, 0xba, 0x21, 0x67, 0x89, 0x73, 0x2e, 0x09, - 0x4e, 0xb8, 0x93, 0x42, 0xa0, 0x0c, 0x2f, 0x16, 0x63, 0x97, 0xb8, 0x9a, 0x43, 0xba, 0xc9, 0x18, - 0xcb, 0x72, 0x8c, 0x9b, 0x99, 0x28, 0x94, 0xe3, 0x0d, 0xdf, 0x06, 0x93, 0xfe, 0x6c, 0xfc, 0x99, - 0x8b, 0xcd, 0x99, 0x15, 0x64, 0x93, 0x3b, 0x91, 0x09, 0xc5, 0x71, 0x6c, 0x69, 0xd6, 0x91, 0x4b, - 0x9c, 0x3e, 0xe9, 0xde, 0xf7, 0x1b, 0x16, 0xab, 0xea, 0x55, 0x5e, 0xd5, 0xc3, 0xa5, 0xed, 0xa6, - 0x10, 0x28, 0xc3, 0x8b, 0x2d, 0xcd, 0xcf, 0x9a, 0xd4, 0xd2, 0xc6, 0xe5, 0xa5, 0x1d, 0x66, 0xa2, - 0x50, 0x8e, 0x37, 0xcb, 0x3d, 0x3f, 0xe4, 0xf5, 0x3e, 0xd6, 0x74, 0x7c, 0xa4, 0x93, 0xc5, 0x09, - 0x39, 0xf7, 0x76, 0x64, 0x33, 0x4a, 0xe2, 0xe1, 0x7d, 0x70, 0xc5, 0x1f, 0x3a, 0x34, 0x71, 0x48, - 0x52, 0xe3, 0x24, 0xaf, 0x08, 0x92, 0x2b, 0x3b, 0x49, 0x00, 0x4a, 0xfb, 0xc0, 0xbb, 0x60, 0xba, - 0x63, 0xe9, 0x3a, 0xcf, 0xc7, 0x0d, 0xcb, 0x33, 0xe9, 0x62, 0x9d, 0xb3, 0x40, 0x76, 0x86, 0x36, - 0x24, 0x0b, 0x4a, 0x20, 0x9b, 0x9f, 0x2a, 0x60, 0x21, 0xe7, 0x1c, 0xc2, 0x1f, 0x81, 0x0a, 0x1d, - 0xd8, 0x84, 0x27, 0x6a, 0xbd, 0x7d, 0x23, 0x28, 0xe1, 0x07, 0x03, 0x9b, 0x7c, 0x75, 0xba, 0x7c, - 0x35, 0xc7, 0x8d, 0x99, 0x11, 0x77, 0x84, 0xc7, 0x60, 0x8a, 0xf5, 0x30, 0xcd, 0xec, 0xf9, 0x10, - 0x51, 0x6a, 0x5a, 0xb9, 0x15, 0x01, 0xc5, 0xd1, 0x51, 0xd1, 0xbc, 0x32, 0x3c, 0x5d, 0x9e, 0x92, - 0x6c, 0x48, 0x26, 0x6e, 0xfe, 0xb6, 0x04, 0xc0, 0x26, 0xb1, 0x75, 0x6b, 0x60, 0x10, 0x73, 0x14, - 0x6d, 0xf0, 0xa1, 0xd4, 0x06, 0x5f, 0xcf, 0xaf, 0x71, 0x61, 0x50, 0xb9, 0x7d, 0xf0, 0xc3, 0x44, - 0x1f, 0xbc, 0x5e, 0x84, 0xec, 0xc5, 0x8d, 0xf0, 0xf3, 0x32, 0x98, 0x8d, 0xc0, 0x1b, 0x96, 0xd9, - 0xd5, 0xf8, 0x69, 0xb8, 0x27, 0xed, 0xe8, 0xeb, 0x89, 0x1d, 0x5d, 0xc8, 0x70, 0x89, 0xed, 0xe6, - 0x56, 0x18, 0x67, 0x89, 0xbb, 0xbf, 0x25, 0x4f, 0xfe, 0xd5, 0xe9, 0x72, 0x86, 0xe2, 0x56, 0x43, - 0x26, 0x39, 0x44, 0x78, 0x0d, 0x8c, 0x3b, 0x04, 0xbb, 0x96, 0xc9, 0xcb, 0x42, 0x3d, 0x5a, 0x0a, - 0xe2, 0xa3, 0x48, 0x58, 0xe1, 0x75, 0x30, 0x61, 0x10, 0xd7, 0xc5, 0x3d, 0xc2, 0x2b, 0x40, 0xbd, - 0x3d, 0x23, 0x80, 0x13, 0xdb, 0xfe, 0x30, 0x0a, 0xec, 0xf0, 0x63, 0x30, 0xad, 0x63, 0x57, 0xa4, - 0xe3, 0x81, 0x66, 0x10, 0x7e, 0xc6, 0x27, 0xd7, 0xde, 0x28, 0xb6, 0xf7, 0xcc, 0x23, 0xea, 0x3d, - 0x5b, 0x12, 0x13, 0x4a, 0x30, 0xc3, 0x3e, 0x80, 0x6c, 0xe4, 0xc0, 0xc1, 0xa6, 0xeb, 0x3f, 0x28, - 0x36, 0xdf, 0xc4, 0xb9, 0xe7, 0x0b, 0xeb, 0xd9, 0x56, 0x8a, 0x0d, 0x65, 0xcc, 0xd0, 0xfc, 0xb3, - 0x02, 0xa6, 0xa3, 0x6d, 0x1a, 0x81, 0xc6, 0x79, 0x20, 0x6b, 0x9c, 0x57, 0x0b, 0x24, 0x67, 0x8e, - 0xc8, 0xf9, 0xbc, 0x12, 0x0f, 0x9d, 0xab, 0x9c, 0x55, 0xa6, 0xda, 0x6d, 0x5d, 0xeb, 0x60, 0x57, - 0xb4, 0xc3, 0x4b, 0xbe, 0x62, 0xf7, 0xc7, 0x50, 0x68, 0x95, 0xf4, 0x50, 0xe9, 0xe5, 0xea, 0xa1, - 0xf2, 0x37, 0xa3, 0x87, 0x7e, 0x0a, 0x6a, 0x6e, 0xa0, 0x84, 0x2a, 0x9c, 0xf2, 0x46, 0xa1, 0x83, - 0x2d, 0x44, 0x50, 0x48, 0x1d, 0xca, 0x9f, 0x90, 0x2e, 0x4b, 0xf8, 0x54, 0xbf, 0x4d, 0xe1, 0xc3, - 0x0e, 0xb3, 0x8d, 0x3d, 0x97, 0x74, 0xf9, 0x09, 0xa8, 0x45, 0x87, 0x79, 0x8f, 0x8f, 0x22, 0x61, - 0x85, 0x87, 0x60, 0xc1, 0x76, 0xac, 0x9e, 0x43, 0x5c, 0x77, 0x93, 0xe0, 0xae, 0xae, 0x99, 0x24, - 0x58, 0x80, 0xdf, 0xb2, 0xae, 0x0e, 0x4f, 0x97, 0x17, 0xf6, 0xb2, 0x21, 0x28, 0xcf, 0xb7, 0xf9, - 0xb7, 0x0a, 0xb8, 0x9c, 0xac, 0x8d, 0x39, 0x2a, 0x42, 0xb9, 0x90, 0x8a, 0xb8, 0x19, 0xcb, 0x53, - 0x5f, 0x62, 0xc5, 0xde, 0x2e, 0x53, 0xb9, 0xba, 0x0e, 0x66, 0x84, 0x6a, 0x08, 0x8c, 0x42, 0x47, - 0x85, 0xdb, 0x73, 0x28, 0x9b, 0x51, 0x12, 0xcf, 0xb4, 0x41, 0xd4, 0xf2, 0x03, 0x92, 0x8a, 0xac, - 0x0d, 0xd6, 0x93, 0x00, 0x94, 0xf6, 0x81, 0xdb, 0x60, 0xd6, 0x33, 0xd3, 0x54, 0x7e, 0xba, 0x5c, - 0x15, 0x54, 0xb3, 0x87, 0x69, 0x08, 0xca, 0xf2, 0x83, 0x8f, 0x01, 0xe8, 0x04, 0x05, 0xdd, 0x5d, - 0x1c, 0xe7, 0x25, 0xe1, 0x66, 0x81, 0xb4, 0x0e, 0xbb, 0x40, 0xd4, 0x56, 0xc3, 0x21, 0x17, 0xc5, - 0x38, 0xe1, 0x3d, 0x30, 0xe5, 0x70, 0x49, 0x18, 0x84, 0xea, 0xcb, 0xaa, 0xef, 0x09, 0xb7, 0x29, - 0x14, 0x37, 0x22, 0x19, 0x9b, 0xa1, 0x84, 0x6a, 0x85, 0x95, 0xd0, 0x5f, 0x15, 0x00, 0xd3, 0xe7, - 0x10, 0xde, 0x95, 0x5a, 0xe6, 0xb5, 0x44, 0xcb, 0x9c, 0x4f, 0x7b, 0xc4, 0x3a, 0xa6, 0x96, 0xad, - 0x7f, 0x6e, 0x17, 0xd4, 0x3f, 0x51, 0x41, 0x2d, 0x26, 0x80, 0xc4, 0x63, 0x18, 0xcd, 0x3d, 0x40, - 0x51, 0x01, 0x14, 0x05, 0xf5, 0x0d, 0x08, 0xa0, 0x18, 0xd9, 0x8b, 0x05, 0xd0, 0xbf, 0x4b, 0x60, - 0x36, 0x02, 0x17, 0x16, 0x40, 0x19, 0x2e, 0x2f, 0x4d, 0x00, 0x65, 0x2b, 0x88, 0xf2, 0xcb, 0x56, - 0x10, 0x2f, 0x41, 0x78, 0x71, 0x51, 0x12, 0x3d, 0xba, 0xff, 0x27, 0x51, 0x12, 0x45, 0x95, 0x23, - 0x4a, 0xfe, 0x50, 0x8a, 0x87, 0xfe, 0x9d, 0x17, 0x25, 0x5f, 0xff, 0xca, 0xa4, 0xf9, 0xf7, 0x32, - 0xb8, 0x9c, 0x3c, 0x87, 0x52, 0x83, 0x54, 0xce, 0x6c, 0x90, 0x7b, 0x60, 0xee, 0x89, 0xa7, 0xeb, - 0x03, 0xfe, 0x18, 0x62, 0x5d, 0xd2, 0x6f, 0xad, 0xdf, 0x17, 0x9e, 0x73, 0x3f, 0xc9, 0xc0, 0xa0, - 0x4c, 0xcf, 0x9c, 0x66, 0x5f, 0xbe, 0x50, 0xb3, 0x4f, 0x75, 0xa0, 0xca, 0x39, 0x3a, 0x50, 0x66, - 0xe3, 0xae, 0x5e, 0xa0, 0x71, 0x9f, 0xaf, 0xd3, 0x66, 0x14, 0xae, 0xb3, 0x3a, 0x6d, 0xf3, 0xd7, - 0x0a, 0x98, 0xcf, 0x7e, 0xe1, 0x86, 0x3a, 0x98, 0x36, 0xf0, 0xd3, 0xf8, 0xbd, 0xc4, 0x59, 0x4d, - 0xc4, 0xa3, 0x9a, 0xae, 0xfa, 0x5f, 0x19, 0xd4, 0x87, 0x26, 0xdd, 0x75, 0xf6, 0xa9, 0xa3, 0x99, - 0x3d, 0xbf, 0xf3, 0x6e, 0x4b, 0x5c, 0x28, 0xc1, 0xdd, 0xfc, 0x52, 0x01, 0x0b, 0x39, 0x9d, 0x6f, - 0xb4, 0x91, 0xc0, 0x8f, 0x40, 0xcd, 0xc0, 0x4f, 0xf7, 0x3d, 0xa7, 0x97, 0xd5, 0xab, 0x8b, 0xcd, - 0xc3, 0x4f, 0xf3, 0xb6, 0x60, 0x41, 0x21, 0x5f, 0x73, 0x17, 0xac, 0x48, 0x8b, 0x64, 0x27, 0x87, - 0x3c, 0xf1, 0x74, 0x7e, 0x88, 0x84, 0xd8, 0xb8, 0x01, 0xea, 0x36, 0x76, 0xa8, 0x16, 0x4a, 0xd5, - 0x6a, 0x7b, 0x6a, 0x78, 0xba, 0x5c, 0xdf, 0x0b, 0x06, 0x51, 0x64, 0x6f, 0xfe, 0x57, 0x01, 0xd5, - 0xfd, 0x0e, 0xd6, 0xc9, 0x08, 0xba, 0xfd, 0xa6, 0xd4, 0xed, 0xf3, 0x2f, 0xba, 0x79, 0x3c, 0xb9, - 0x8d, 0x7e, 0x2b, 0xd1, 0xe8, 0x5f, 0x3b, 0x83, 0xe7, 0xc5, 0x3d, 0xfe, 0x3d, 0x50, 0x0f, 0xa7, - 0x3b, 0x5f, 0x01, 0x6a, 0xfe, 0xbe, 0x04, 0x26, 0x63, 0x53, 0x9c, 0xb3, 0x7c, 0x3d, 0x96, 0xca, - 0x3e, 0x3b, 0x98, 0x6b, 0x45, 0x16, 0xa2, 0x06, 0x25, 0xfe, 0x7d, 0x93, 0x3a, 0xf1, 0x17, 0xbc, - 0x74, 0xe5, 0xff, 0x21, 0x98, 0xa6, 0xd8, 0xe9, 0x11, 0x1a, 0xd8, 0xf8, 0x03, 0xab, 0x47, 0xb7, - 0x13, 0x07, 0x92, 0x15, 0x25, 0xd0, 0x4b, 0xf7, 0xc0, 0x94, 0x34, 0x19, 0xbc, 0x0c, 0xca, 0x27, - 0x64, 0xe0, 0xcb, 0x1e, 0xc4, 0xfe, 0x84, 0x73, 0xa0, 0xda, 0xc7, 0xba, 0xe7, 0xe7, 0x79, 0x1d, - 0xf9, 0x3f, 0xee, 0x96, 0xde, 0x55, 0x9a, 0x9f, 0xb0, 0x87, 0x13, 0x25, 0xe7, 0x08, 0xb2, 0xeb, - 0x03, 0x29, 0xbb, 0xf2, 0xbf, 0x03, 0xc5, 0x8f, 0x4c, 0x5e, 0x8e, 0xa1, 0x44, 0x8e, 0xbd, 0x51, - 0x88, 0xed, 0xc5, 0x99, 0xf6, 0x17, 0x05, 0xcc, 0xc4, 0xd0, 0x23, 0x10, 0x38, 0x0f, 0x65, 0x81, - 0xf3, 0x5a, 0x91, 0x45, 0xe4, 0x28, 0x9c, 0x7f, 0x54, 0xa5, 0xe0, 0xbf, 0xf3, 0x12, 0xe7, 0x97, - 0x60, 0xae, 0x6f, 0xe9, 0x9e, 0x41, 0x36, 0x74, 0xac, 0x19, 0x01, 0x80, 0x75, 0xf1, 0x72, 0xf2, - 0xdd, 0x22, 0xa4, 0x27, 0x8e, 0xab, 0xb9, 0x94, 0x98, 0xf4, 0x51, 0xe4, 0x19, 0xe9, 0x90, 0x47, - 0x19, 0x74, 0x28, 0x73, 0x12, 0xf8, 0x36, 0x98, 0x64, 0x7a, 0x42, 0xeb, 0x90, 0x1d, 0x6c, 0x04, - 0xc2, 0x39, 0xfc, 0xe2, 0xb1, 0x1f, 0x99, 0x50, 0x1c, 0x07, 0x8f, 0xc1, 0xac, 0x6d, 0x75, 0xb7, - 0xb1, 0x89, 0x7b, 0x84, 0xb5, 0xbd, 0x3d, 0xfe, 0xaf, 0x08, 0xfc, 0x32, 0xa6, 0xde, 0x7e, 0x27, - 0x78, 0x4b, 0xdf, 0x4b, 0x43, 0xd8, 0x4b, 0x4b, 0xc6, 0x30, 0x7f, 0x69, 0xc9, 0xa2, 0x84, 0x4e, - 0xea, 0x2b, 0x9d, 0x7f, 0x67, 0xb9, 0x56, 0x24, 0xc3, 0x2e, 0xf8, 0x9d, 0x2e, 0xef, 0xae, 0xa9, - 0x76, 0xa1, 0x8f, 0x6c, 0x9f, 0x54, 0xc0, 0x95, 0xd4, 0xd1, 0xfd, 0x16, 0x6f, 0x7b, 0x52, 0x72, - 0xb1, 0x7c, 0x0e, 0xb9, 0xb8, 0x0e, 0x66, 0xc4, 0xf7, 0xbd, 0x84, 0xda, 0x0c, 0xf5, 0xf8, 0x86, - 0x6c, 0x46, 0x49, 0x7c, 0xd6, 0x6d, 0x53, 0xf5, 0x9c, 0xb7, 0x4d, 0xf1, 0x28, 0xc4, 0xff, 0x50, - 0xf8, 0xa9, 0x97, 0x8e, 0x42, 0xfc, 0x2b, 0x45, 0x12, 0xcf, 0x3a, 0x96, 0xcf, 0x1a, 0x32, 0x4c, - 0xc8, 0x1d, 0xeb, 0x50, 0xb2, 0xa2, 0x04, 0xfa, 0x6b, 0x7d, 0xc3, 0xfa, 0xa7, 0x02, 0x5e, 0xc9, - 0xcd, 0x52, 0xb8, 0x2e, 0xbd, 0xf2, 0xdf, 0x4a, 0xbc, 0xf2, 0xff, 0x20, 0xd7, 0x31, 0xf6, 0xe2, - 0xef, 0x64, 0xdf, 0xe3, 0xbc, 0x57, 0xec, 0x1e, 0x27, 0x43, 0xe8, 0x9d, 0x7d, 0xa1, 0xd3, 0xbe, - 0xf5, 0xec, 0x79, 0x63, 0xec, 0xb3, 0xe7, 0x8d, 0xb1, 0x2f, 0x9e, 0x37, 0xc6, 0x7e, 0x35, 0x6c, - 0x28, 0xcf, 0x86, 0x0d, 0xe5, 0xb3, 0x61, 0x43, 0xf9, 0x62, 0xd8, 0x50, 0xfe, 0x35, 0x6c, 0x28, - 0xbf, 0xfb, 0xb2, 0x31, 0xf6, 0xd1, 0x84, 0x98, 0xf1, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xe8, - 0x1d, 0x40, 0xdd, 0x77, 0x25, 0x00, 0x00, + // 2186 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x5a, 0xcb, 0x6f, 0x1c, 0xb7, + 0x19, 0xf7, 0xec, 0x43, 0x5a, 0x51, 0x96, 0x64, 0x53, 0xaa, 0xb4, 0x91, 0xdb, 0x95, 0xb1, 0x09, + 0x1c, 0x39, 0xb6, 0x66, 0x6d, 0xe5, 0x81, 0xc4, 0x2e, 0xda, 0x6a, 0xa5, 0xd4, 0x76, 0xa0, 0x57, + 0x28, 0xcb, 0x40, 0x83, 0x16, 0x35, 0xb5, 0x4b, 0xaf, 0x26, 0x9a, 0x17, 0x66, 0x38, 0x5b, 0x2f, + 0x7a, 0xe9, 0xa9, 0x40, 0x81, 0x02, 0x6d, 0xaf, 0xfd, 0x27, 0x7a, 0x2b, 0x8a, 0xf6, 0x56, 0x04, + 0x85, 0x2f, 0x05, 0x82, 0x5e, 0x92, 0x93, 0x50, 0x6f, 0x4e, 0x45, 0xd1, 0x4b, 0x81, 0x5e, 0x02, + 0x14, 0x28, 0xc8, 0xe1, 0x3c, 0x38, 0x0f, 0xef, 0x48, 0xb1, 0x95, 0x22, 0xc8, 0x6d, 0x87, 0xfc, + 0x7d, 0x3f, 0x7e, 0x24, 0xbf, 0x8f, 0xdf, 0x6f, 0x38, 0x0b, 0xbe, 0x77, 0xf4, 0xb6, 0xab, 0x6a, + 0x56, 0xeb, 0xc8, 0x3b, 0x20, 0x8e, 0x49, 0x28, 0x71, 0x5b, 0x7d, 0x62, 0x76, 0x2d, 0xa7, 0x25, + 0x3a, 0xb0, 0xad, 0xb5, 0xb0, 0x6d, 0xbb, 0xad, 0xfe, 0xcd, 0x03, 0x42, 0xf1, 0x6a, 0xab, 0x47, + 0x4c, 0xe2, 0x60, 0x4a, 0xba, 0xaa, 0xed, 0x58, 0xd4, 0x82, 0x0b, 0x3e, 0x50, 0xc5, 0xb6, 0xa6, + 0x32, 0xa0, 0x2a, 0x80, 0x8b, 0x2b, 0x3d, 0x8d, 0x1e, 0x7a, 0x07, 0x6a, 0xc7, 0x32, 0x5a, 0x3d, + 0xab, 0x67, 0xb5, 0x38, 0xfe, 0xc0, 0x7b, 0xc4, 0x9f, 0xf8, 0x03, 0xff, 0xe5, 0xf3, 0x2c, 0x36, + 0x63, 0x03, 0x76, 0x2c, 0x87, 0xb4, 0xfa, 0x37, 0x93, 0x63, 0x2d, 0x5e, 0x8d, 0x61, 0x6c, 0x4b, + 0xd7, 0x3a, 0x03, 0xe1, 0x56, 0x1a, 0xfa, 0x46, 0x04, 0x35, 0x70, 0xe7, 0x50, 0x33, 0x89, 0x33, + 0x68, 0xd9, 0x47, 0x3d, 0xd6, 0xe0, 0xb6, 0x0c, 0x42, 0x71, 0xd6, 0x00, 0xad, 0x3c, 0x2b, 0xc7, + 0x33, 0xa9, 0x66, 0x90, 0x94, 0xc1, 0x5b, 0xa3, 0x0c, 0xdc, 0xce, 0x21, 0x31, 0x70, 0xca, 0xee, + 0xf5, 0x3c, 0x3b, 0x8f, 0x6a, 0x7a, 0x4b, 0x33, 0xa9, 0x4b, 0x9d, 0xa4, 0x51, 0xf3, 0x3f, 0x0a, + 0x80, 0xeb, 0x96, 0x49, 0x1d, 0x4b, 0xd7, 0x89, 0x83, 0x48, 0x5f, 0x73, 0x35, 0xcb, 0x84, 0x0f, + 0x41, 0x8d, 0xcd, 0xa7, 0x8b, 0x29, 0xae, 0x2b, 0x97, 0x95, 0xe5, 0xc9, 0xd5, 0x1b, 0x6a, 0xb4, + 0x29, 0x21, 0xbd, 0x6a, 0x1f, 0xf5, 0x58, 0x83, 0xab, 0x32, 0xb4, 0xda, 0xbf, 0xa9, 0xee, 0x1c, + 0x7c, 0x48, 0x3a, 0x74, 0x8b, 0x50, 0xdc, 0x86, 0x4f, 0x8e, 0x97, 0xce, 0x0d, 0x8f, 0x97, 0x40, + 0xd4, 0x86, 0x42, 0x56, 0xb8, 0x03, 0x2a, 0x9c, 0xbd, 0xc4, 0xd9, 0x57, 0x72, 0xd9, 0xc5, 0xa4, + 0x55, 0x84, 0x7f, 0xf2, 0xee, 0x63, 0x4a, 0x4c, 0xe6, 0x5e, 0xfb, 0xbc, 0xa0, 0xae, 0x6c, 0x60, + 0x8a, 0x11, 0x27, 0x82, 0xd7, 0x41, 0xcd, 0x11, 0xee, 0xd7, 0xcb, 0x97, 0x95, 0xe5, 0x72, 0xfb, + 0x82, 0x40, 0xd5, 0x82, 0x69, 0xa1, 0x10, 0xd1, 0x7c, 0xa2, 0x80, 0xf9, 0xf4, 0xbc, 0x37, 0x35, + 0x97, 0xc2, 0x1f, 0xa6, 0xe6, 0xae, 0x16, 0x9b, 0x3b, 0xb3, 0xe6, 0x33, 0x0f, 0x07, 0x0e, 0x5a, + 0x62, 0xf3, 0xde, 0x05, 0x55, 0x8d, 0x12, 0xc3, 0xad, 0x97, 0x2e, 0x97, 0x97, 0x27, 0x57, 0xaf, + 0xa9, 0x39, 0xb1, 0xae, 0xa6, 0xbd, 0x6b, 0x4f, 0x09, 0xde, 0xea, 0x3d, 0xc6, 0x80, 0x7c, 0xa2, + 0xe6, 0x2f, 0x4a, 0x60, 0x62, 0x03, 0x13, 0xc3, 0x32, 0xf7, 0x08, 0x3d, 0x83, 0x9d, 0xbb, 0x0b, + 0x2a, 0xae, 0x4d, 0x3a, 0x62, 0xe7, 0xae, 0xe4, 0x4e, 0x20, 0xf4, 0x69, 0xcf, 0x26, 0x9d, 0x68, + 0xcb, 0xd8, 0x13, 0xe2, 0x0c, 0x70, 0x17, 0x8c, 0xb9, 0x14, 0x53, 0xcf, 0xe5, 0x1b, 0x36, 0xb9, + 0xba, 0x5c, 0x80, 0x8b, 0xe3, 0xdb, 0xd3, 0x82, 0x6d, 0xcc, 0x7f, 0x46, 0x82, 0xa7, 0xf9, 0x8f, + 0x12, 0x80, 0x21, 0x76, 0xdd, 0x32, 0xbb, 0x1a, 0x65, 0xe1, 0x7c, 0x0b, 0x54, 0xe8, 0xc0, 0x26, + 0x7c, 0x41, 0x26, 0xda, 0x57, 0x02, 0x57, 0xee, 0x0f, 0x6c, 0xf2, 0xf9, 0xf1, 0xd2, 0x7c, 0xda, + 0x82, 0xf5, 0x20, 0x6e, 0x03, 0x37, 0x43, 0x27, 0x4b, 0xdc, 0xfa, 0x0d, 0x79, 0xe8, 0xcf, 0x8f, + 0x97, 0x32, 0x8e, 0x19, 0x35, 0x64, 0x92, 0x1d, 0x84, 0x7d, 0x00, 0x75, 0xec, 0xd2, 0xfb, 0x0e, + 0x36, 0x5d, 0x7f, 0x24, 0xcd, 0x20, 0x62, 0xfa, 0xaf, 0x15, 0xdb, 0x28, 0x66, 0xd1, 0x5e, 0x14, + 0x5e, 0xc0, 0xcd, 0x14, 0x1b, 0xca, 0x18, 0x01, 0x5e, 0x01, 0x63, 0x0e, 0xc1, 0xae, 0x65, 0xd6, + 0x2b, 0x7c, 0x16, 0xe1, 0x02, 0x22, 0xde, 0x8a, 0x44, 0x2f, 0xbc, 0x0a, 0xc6, 0x0d, 0xe2, 0xba, + 0xb8, 0x47, 0xea, 0x55, 0x0e, 0x9c, 0x11, 0xc0, 0xf1, 0x2d, 0xbf, 0x19, 0x05, 0xfd, 0xcd, 0xdf, + 0x2b, 0x60, 0x2a, 0x5c, 0xb9, 0x33, 0xc8, 0x9c, 0x3b, 0x72, 0xe6, 0x34, 0x47, 0x07, 0x4b, 0x4e, + 0xc2, 0x7c, 0x54, 0x8e, 0x39, 0xce, 0xc2, 0x11, 0xfe, 0x08, 0xd4, 0x5c, 0xa2, 0x93, 0x0e, 0xb5, + 0x1c, 0xe1, 0xf8, 0xeb, 0x05, 0x1d, 0xc7, 0x07, 0x44, 0xdf, 0x13, 0xa6, 0xed, 0xf3, 0xcc, 0xf3, + 0xe0, 0x09, 0x85, 0x94, 0xf0, 0x7d, 0x50, 0xa3, 0xc4, 0xb0, 0x75, 0x4c, 0x89, 0xc8, 0x9a, 0x97, + 0xe3, 0xce, 0xb3, 0x98, 0x61, 0x64, 0xbb, 0x56, 0xf7, 0xbe, 0x80, 0xf1, 0x94, 0x09, 0x17, 0x23, + 0x68, 0x45, 0x21, 0x0d, 0xb4, 0xc1, 0xb4, 0x67, 0x77, 0x19, 0x92, 0xb2, 0xe3, 0xbc, 0x37, 0x10, + 0x31, 0x74, 0x63, 0xf4, 0xaa, 0xec, 0x4b, 0x76, 0xed, 0x79, 0x31, 0xca, 0xb4, 0xdc, 0x8e, 0x12, + 0xfc, 0x70, 0x0d, 0xcc, 0x18, 0x9a, 0x89, 0x08, 0xee, 0x0e, 0xf6, 0x48, 0xc7, 0x32, 0xbb, 0x2e, + 0x0f, 0xa5, 0x6a, 0x7b, 0x41, 0x10, 0xcc, 0x6c, 0xc9, 0xdd, 0x28, 0x89, 0x87, 0x9b, 0x60, 0x2e, + 0x38, 0x80, 0xef, 0x6a, 0x2e, 0xb5, 0x9c, 0xc1, 0xa6, 0x66, 0x68, 0xb4, 0x3e, 0xc6, 0x79, 0xea, + 0xc3, 0xe3, 0xa5, 0x39, 0x94, 0xd1, 0x8f, 0x32, 0xad, 0x9a, 0xbf, 0x19, 0x03, 0x33, 0x89, 0x73, + 0x01, 0x3e, 0x00, 0xf3, 0x1d, 0xcf, 0x71, 0x88, 0x49, 0xb7, 0x3d, 0xe3, 0x80, 0x38, 0x7b, 0x9d, + 0x43, 0xd2, 0xf5, 0x74, 0xd2, 0xe5, 0xdb, 0x5a, 0x6d, 0x37, 0x84, 0xaf, 0xf3, 0xeb, 0x99, 0x28, + 0x94, 0x63, 0x0d, 0xdf, 0x03, 0xd0, 0xe4, 0x4d, 0x5b, 0x9a, 0xeb, 0x86, 0x9c, 0x25, 0xce, 0x19, + 0xa6, 0xe2, 0x76, 0x0a, 0x81, 0x32, 0xac, 0x98, 0x8f, 0x5d, 0xe2, 0x6a, 0x0e, 0xe9, 0x26, 0x7d, + 0x2c, 0xcb, 0x3e, 0x6e, 0x64, 0xa2, 0x50, 0x8e, 0x35, 0x7c, 0x13, 0x4c, 0xfa, 0xa3, 0xf1, 0x35, + 0x17, 0x9b, 0x33, 0x2b, 0xc8, 0x26, 0xb7, 0xa3, 0x2e, 0x14, 0xc7, 0xb1, 0xa9, 0x59, 0x07, 0x2e, + 0x71, 0xfa, 0xa4, 0x7b, 0xc7, 0x17, 0x07, 0xac, 0x82, 0x56, 0x79, 0x05, 0x0d, 0xa7, 0xb6, 0x93, + 0x42, 0xa0, 0x0c, 0x2b, 0x36, 0x35, 0x3f, 0x6a, 0x52, 0x53, 0x1b, 0x93, 0xa7, 0xb6, 0x9f, 0x89, + 0x42, 0x39, 0xd6, 0x2c, 0xf6, 0x7c, 0x97, 0xd7, 0xfa, 0x58, 0xd3, 0xf1, 0x81, 0x4e, 0xea, 0xe3, + 0x72, 0xec, 0x6d, 0xcb, 0xdd, 0x28, 0x89, 0x87, 0x77, 0xc0, 0x45, 0xbf, 0x69, 0xdf, 0xc4, 0x21, + 0x49, 0x8d, 0x93, 0xbc, 0x24, 0x48, 0x2e, 0x6e, 0x27, 0x01, 0x28, 0x6d, 0x03, 0x6f, 0x81, 0xe9, + 0x8e, 0xa5, 0xeb, 0x3c, 0x1e, 0xd7, 0x2d, 0xcf, 0xa4, 0xf5, 0x09, 0xce, 0x02, 0x59, 0x0e, 0xad, + 0x4b, 0x3d, 0x28, 0x81, 0x84, 0x3f, 0x06, 0xa0, 0x13, 0x14, 0x06, 0xb7, 0x0e, 0x46, 0x28, 0x80, + 0x74, 0x59, 0x8a, 0x2a, 0x73, 0xd8, 0xe4, 0xa2, 0x18, 0x65, 0xf3, 0x23, 0x05, 0x2c, 0xe4, 0x24, + 0x3a, 0xfc, 0xae, 0x54, 0x04, 0xaf, 0x25, 0x8a, 0xe0, 0xa5, 0x1c, 0xb3, 0x58, 0x25, 0x3c, 0x04, + 0x53, 0x4c, 0x90, 0x68, 0x66, 0xcf, 0x87, 0x88, 0xb3, 0xac, 0x95, 0x3b, 0x01, 0x14, 0x47, 0x47, + 0xa7, 0xf2, 0xc5, 0xe1, 0xf1, 0xd2, 0x94, 0xd4, 0x87, 0x64, 0xe2, 0xe6, 0x2f, 0x4b, 0x00, 0x6c, + 0x10, 0x5b, 0xb7, 0x06, 0x06, 0x31, 0xcf, 0x42, 0xd3, 0xdc, 0x93, 0x34, 0xcd, 0xab, 0xf9, 0x5b, + 0x12, 0x3a, 0x95, 0x2b, 0x6a, 0xde, 0x4f, 0x88, 0x9a, 0xab, 0x45, 0xc8, 0x9e, 0xad, 0x6a, 0x3e, + 0x29, 0x83, 0xd9, 0x08, 0x1c, 0xc9, 0x9a, 0xdb, 0xd2, 0x8e, 0xbe, 0x9a, 0xd8, 0xd1, 0x85, 0x0c, + 0x93, 0x17, 0xa6, 0x6b, 0x9e, 0xbf, 0xbe, 0x80, 0x1f, 0x82, 0x69, 0x26, 0x64, 0xfc, 0x90, 0xe0, + 0x32, 0x69, 0xec, 0xc4, 0x32, 0x29, 0x2c, 0x6e, 0x9b, 0x12, 0x13, 0x4a, 0x30, 0xe7, 0xc8, 0xb2, + 0xf1, 0x17, 0x2d, 0xcb, 0x9a, 0x7f, 0x50, 0xc0, 0x74, 0xb4, 0x4d, 0x67, 0x20, 0xa2, 0xee, 0xca, + 0x22, 0xea, 0xe5, 0x02, 0xc1, 0x99, 0xa3, 0xa2, 0x3e, 0xa9, 0xc4, 0x5d, 0xe7, 0x32, 0x6a, 0x99, + 0xbd, 0x82, 0xd9, 0xba, 0xd6, 0xc1, 0xae, 0xa8, 0xb7, 0xe7, 0xfd, 0xd7, 0x2f, 0xbf, 0x0d, 0x85, + 0xbd, 0x92, 0xe0, 0x2a, 0xbd, 0x58, 0xc1, 0x55, 0x7e, 0x3e, 0x82, 0xeb, 0x07, 0xa0, 0xe6, 0x06, + 0x52, 0xab, 0xc2, 0x29, 0xaf, 0x15, 0x4a, 0x6c, 0xa1, 0xb2, 0x42, 0xea, 0x50, 0x5f, 0x85, 0x74, + 0x59, 0xca, 0xaa, 0xfa, 0x65, 0x2a, 0x2b, 0x96, 0xcc, 0x36, 0xf6, 0x5c, 0xd2, 0xe5, 0x19, 0x50, + 0x8b, 0x92, 0x79, 0x97, 0xb7, 0x22, 0xd1, 0x0b, 0xf7, 0xc1, 0x82, 0xed, 0x58, 0x3d, 0x87, 0xb8, + 0xee, 0x06, 0xc1, 0x5d, 0x5d, 0x33, 0x49, 0x30, 0x01, 0xbf, 0x26, 0x5e, 0x1a, 0x1e, 0x2f, 0x2d, + 0xec, 0x66, 0x43, 0x50, 0x9e, 0x6d, 0xf3, 0xcf, 0x15, 0x70, 0x21, 0x79, 0x36, 0xe6, 0xc8, 0x14, + 0xe5, 0x54, 0x32, 0xe5, 0x7a, 0x2c, 0x4e, 0x7d, 0x0d, 0x17, 0xbb, 0x2a, 0x48, 0xc5, 0xea, 0x1a, + 0x98, 0x11, 0xb2, 0x24, 0xe8, 0x14, 0x42, 0x2d, 0xdc, 0x9e, 0x7d, 0xb9, 0x1b, 0x25, 0xf1, 0x4c, + 0x7c, 0x44, 0x9a, 0x22, 0x20, 0xa9, 0xc8, 0xe2, 0x63, 0x2d, 0x09, 0x40, 0x69, 0x1b, 0xb8, 0x05, + 0x66, 0x3d, 0x33, 0x4d, 0xe5, 0x87, 0xcb, 0x25, 0x41, 0x35, 0xbb, 0x9f, 0x86, 0xa0, 0x2c, 0x3b, + 0xf8, 0x50, 0xd2, 0x23, 0x63, 0xfc, 0x48, 0xb8, 0x5e, 0x20, 0xac, 0x0b, 0x0b, 0x12, 0x78, 0x1b, + 0x4c, 0x39, 0x5c, 0x73, 0x06, 0xae, 0xfa, 0xba, 0xed, 0x1b, 0xc2, 0x6c, 0x0a, 0xc5, 0x3b, 0x91, + 0x8c, 0xcd, 0x90, 0x5a, 0xb5, 0xa2, 0x52, 0xab, 0xf9, 0x27, 0x05, 0xc0, 0x74, 0x1e, 0x8e, 0xbc, + 0x09, 0x48, 0x59, 0xc4, 0x2a, 0xa6, 0x96, 0xad, 0x7f, 0x6e, 0x14, 0xd4, 0x3f, 0xd1, 0x81, 0x5a, + 0x4c, 0x00, 0x89, 0x65, 0x38, 0x9b, 0x4b, 0x9d, 0xa2, 0x02, 0x28, 0x72, 0xea, 0x39, 0x08, 0xa0, + 0x18, 0xd9, 0xb3, 0x05, 0xd0, 0x3f, 0x4b, 0x60, 0x36, 0x02, 0x17, 0x16, 0x40, 0x19, 0x26, 0x5f, + 0x5f, 0xec, 0x8c, 0xbe, 0xd8, 0x61, 0xa2, 0x24, 0x5a, 0xba, 0xff, 0x27, 0x51, 0x12, 0x79, 0x95, + 0x23, 0x4a, 0x7e, 0x57, 0x8a, 0xbb, 0xfe, 0x95, 0x17, 0x25, 0x5f, 0xfc, 0x4e, 0xa6, 0xf9, 0x97, + 0x32, 0xb8, 0x90, 0xcc, 0x43, 0xa9, 0x40, 0x2a, 0x23, 0x0b, 0xe4, 0x2e, 0x98, 0x7b, 0xe4, 0xe9, + 0xfa, 0x80, 0x2f, 0x43, 0xac, 0x4a, 0xfa, 0xa5, 0xf5, 0x9b, 0xc2, 0x72, 0xee, 0xfb, 0x19, 0x18, + 0x94, 0x69, 0x99, 0x53, 0xec, 0xcb, 0xa7, 0x2a, 0xf6, 0xa9, 0x0a, 0x54, 0x39, 0x41, 0x05, 0xca, + 0x2c, 0xdc, 0xd5, 0x53, 0x14, 0xee, 0x93, 0x55, 0xda, 0x8c, 0x83, 0x6b, 0xe4, 0xab, 0xff, 0xcf, + 0x15, 0x30, 0x9f, 0xfd, 0xc2, 0x0d, 0x75, 0x30, 0x6d, 0xe0, 0xc7, 0xf1, 0x8b, 0x8f, 0x51, 0x45, + 0xc4, 0xa3, 0x9a, 0xae, 0xfa, 0x9f, 0x8c, 0xd4, 0x7b, 0x26, 0xdd, 0x71, 0xf6, 0xa8, 0xa3, 0x99, + 0x3d, 0xbf, 0xf2, 0x6e, 0x49, 0x5c, 0x28, 0xc1, 0xdd, 0xfc, 0x4c, 0x01, 0x0b, 0x39, 0x95, 0xef, + 0x6c, 0x3d, 0x81, 0x1f, 0x80, 0x9a, 0x81, 0x1f, 0xef, 0x79, 0x4e, 0x2f, 0xab, 0x56, 0x17, 0x1b, + 0x87, 0x67, 0xf3, 0x96, 0x60, 0x41, 0x21, 0x5f, 0x73, 0x07, 0x5c, 0x96, 0x26, 0xc9, 0x32, 0x87, + 0x3c, 0xf2, 0x74, 0x9e, 0x44, 0x42, 0x6c, 0x5c, 0x03, 0x13, 0x36, 0x76, 0xa8, 0x16, 0x4a, 0xd5, + 0x6a, 0x7b, 0x6a, 0x78, 0xbc, 0x34, 0xb1, 0x1b, 0x34, 0xa2, 0xa8, 0xbf, 0xf9, 0x5f, 0x05, 0x54, + 0xf7, 0x3a, 0x58, 0x27, 0x67, 0x50, 0xed, 0x37, 0xa4, 0x6a, 0x9f, 0x7f, 0x93, 0xce, 0xfd, 0xc9, + 0x2d, 0xf4, 0x9b, 0x89, 0x42, 0xff, 0xca, 0x08, 0x9e, 0x67, 0xd7, 0xf8, 0x77, 0xc0, 0x44, 0x38, + 0xdc, 0xc9, 0x0e, 0xa0, 0xe6, 0x6f, 0x4b, 0x60, 0x32, 0x36, 0xc4, 0x09, 0x8f, 0xaf, 0x87, 0xd2, + 0xb1, 0xcf, 0x12, 0x73, 0xb5, 0xc8, 0x44, 0xd4, 0xe0, 0x88, 0x7f, 0xd7, 0xa4, 0x4e, 0xfc, 0x05, + 0x2f, 0x7d, 0xf2, 0x7f, 0x07, 0x4c, 0x53, 0xec, 0xf4, 0x08, 0x0d, 0xfa, 0xf8, 0x82, 0x4d, 0x44, + 0xb7, 0x13, 0xf7, 0xa5, 0x5e, 0x94, 0x40, 0x2f, 0xde, 0x06, 0x53, 0xd2, 0x60, 0xf0, 0x02, 0x28, + 0x1f, 0x91, 0x81, 0x2f, 0x7b, 0x10, 0xfb, 0x09, 0xe7, 0x40, 0xb5, 0x8f, 0x75, 0xcf, 0x8f, 0xf3, + 0x09, 0xe4, 0x3f, 0xdc, 0x2a, 0xbd, 0xad, 0x34, 0x7f, 0xc5, 0x16, 0x27, 0x0a, 0xce, 0x33, 0x88, + 0xae, 0xf7, 0xa4, 0xe8, 0xca, 0xff, 0xa8, 0x17, 0x4f, 0x99, 0xbc, 0x18, 0x43, 0x89, 0x18, 0x7b, + 0xad, 0x10, 0xdb, 0xb3, 0x23, 0xed, 0x5f, 0x25, 0x30, 0x17, 0x43, 0x47, 0x72, 0xf2, 0xdb, 0x92, + 0x9c, 0x5c, 0x4e, 0xc8, 0xc9, 0x7a, 0x96, 0xcd, 0xd7, 0x7a, 0x72, 0xb4, 0x9e, 0xfc, 0xa3, 0x02, + 0x66, 0x62, 0x6b, 0x77, 0x06, 0x82, 0xf2, 0x9e, 0x2c, 0x28, 0x5f, 0x29, 0x12, 0x34, 0x39, 0x8a, + 0xf2, 0xaf, 0x55, 0xc9, 0xf9, 0xaf, 0xbc, 0xa4, 0xfc, 0x29, 0x98, 0xeb, 0x5b, 0xba, 0x67, 0x90, + 0x75, 0x1d, 0x6b, 0x46, 0x00, 0x60, 0xaa, 0xa9, 0x9c, 0x7c, 0x97, 0x0b, 0xe9, 0x89, 0xe3, 0x6a, + 0x2e, 0x25, 0x26, 0x7d, 0x10, 0x59, 0x46, 0xba, 0xef, 0x41, 0x06, 0x1d, 0xca, 0x1c, 0x04, 0xbe, + 0x09, 0x26, 0x99, 0x7e, 0xd3, 0x3a, 0x64, 0x1b, 0x1b, 0x41, 0x60, 0x85, 0x9f, 0xb0, 0xf6, 0xa2, + 0x2e, 0x14, 0xc7, 0xc1, 0x43, 0x30, 0x6b, 0x5b, 0xdd, 0x2d, 0x6c, 0xe2, 0x1e, 0x61, 0x32, 0x63, + 0x97, 0xff, 0x8f, 0x87, 0x5f, 0x7e, 0x4d, 0xb4, 0xdf, 0x0a, 0x6e, 0x45, 0x76, 0xd3, 0x10, 0xf6, + 0x92, 0x98, 0xd1, 0xcc, 0x93, 0x3a, 0x8b, 0x12, 0x3a, 0xa9, 0xcf, 0xae, 0xfe, 0x1d, 0xf1, 0x6a, + 0x91, 0x08, 0x3b, 0xe5, 0x87, 0xd7, 0xbc, 0xbb, 0xbd, 0xda, 0xa9, 0xbe, 0x9a, 0xfe, 0xbb, 0x02, + 0x2e, 0xa6, 0x8e, 0xca, 0x2f, 0xf1, 0x76, 0x2d, 0x25, 0xcf, 0xcb, 0x27, 0x90, 0xe7, 0x6b, 0x60, + 0x46, 0x7c, 0xb0, 0x4d, 0xa8, 0xfb, 0xf0, 0xfd, 0x67, 0x5d, 0xee, 0x46, 0x49, 0x7c, 0xd6, 0xed, + 0x5e, 0xf5, 0x84, 0xb7, 0x7b, 0x71, 0x2f, 0xc4, 0x1f, 0x90, 0xfc, 0xd0, 0x4b, 0x7b, 0x21, 0xfe, + 0x87, 0x94, 0xc4, 0x33, 0x85, 0xe0, 0xb3, 0x86, 0x0c, 0xe3, 0xb2, 0x42, 0xd8, 0x97, 0x7a, 0x51, + 0x02, 0xfd, 0x85, 0x3e, 0x4a, 0xe2, 0x8c, 0x8f, 0x92, 0x2b, 0x45, 0xe2, 0xb9, 0xf8, 0xbb, 0xc9, + 0xdf, 0x14, 0xf0, 0x52, 0x6e, 0x22, 0xc0, 0x35, 0xa9, 0xec, 0xae, 0x24, 0xca, 0xee, 0xb7, 0x72, + 0x0d, 0x63, 0xb5, 0xd7, 0xc9, 0xbe, 0x9a, 0x7b, 0xa7, 0xd8, 0xd5, 0x5c, 0x86, 0x76, 0x1f, 0x7d, + 0x47, 0xd7, 0x5e, 0x79, 0xf2, 0xb4, 0x71, 0xee, 0xe3, 0xa7, 0x8d, 0x73, 0x9f, 0x3e, 0x6d, 0x9c, + 0xfb, 0xd9, 0xb0, 0xa1, 0x3c, 0x19, 0x36, 0x94, 0x8f, 0x87, 0x0d, 0xe5, 0xd3, 0x61, 0x43, 0xf9, + 0xfb, 0xb0, 0xa1, 0xfc, 0xfa, 0xb3, 0xc6, 0xb9, 0x0f, 0xc6, 0xc5, 0x88, 0xff, 0x0b, 0x00, 0x00, + 0xff, 0xff, 0xe4, 0x8f, 0x6a, 0x57, 0x17, 0x29, 0x00, 0x00, } diff --git a/staging/src/k8s.io/api/apps/v1beta2/generated.proto b/staging/src/k8s.io/api/apps/v1beta2/generated.proto index 69411bb9d98..4a8b28c150c 100644 --- a/staging/src/k8s.io/api/apps/v1beta2/generated.proto +++ b/staging/src/k8s.io/api/apps/v1beta2/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -31,6 +31,8 @@ import "k8s.io/apimachinery/pkg/util/intstr/generated.proto"; // Package-wide variables from generator "generated". option go_package = "v1beta2"; +// DEPRECATED - This group version of ControllerRevision is deprecated by apps/v1/ControllerRevision. See the +// release notes for more information. // ControllerRevision implements an immutable snapshot of state data. Clients // are responsible for serializing and deserializing the objects that contain // their internal state. @@ -63,6 +65,8 @@ message ControllerRevisionList { repeated ControllerRevision items = 2; } +// DEPRECATED - This group version of DaemonSet is deprecated by apps/v1/DaemonSet. See the release notes for +// more information. // DaemonSet represents the configuration of a daemon set. message DaemonSet { // Standard object's metadata. @@ -84,6 +88,27 @@ message DaemonSet { optional DaemonSetStatus status = 3; } +// DaemonSetCondition describes the state of a DaemonSet at a certain point. +message DaemonSetCondition { + // Type of DaemonSet condition. + optional string type = 1; + + // Status of the condition, one of True, False, Unknown. + optional string status = 2; + + // Last time the condition transitioned from one status to another. + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.Time lastTransitionTime = 3; + + // The reason for the condition's last transition. + // +optional + optional string reason = 4; + + // A human readable message indicating details about the transition. + // +optional + optional string message = 5; +} + // DaemonSetList is a collection of daemon sets. message DaemonSetList { // Standard list metadata. @@ -99,9 +124,8 @@ message DaemonSetList { message DaemonSetSpec { // A label query over pods that are managed by the daemon set. // Must match in order to be controlled. - // If empty, defaulted to labels on Pod template. + // It must match the pod template's labels. // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors - // +optional optional k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector selector = 1; // An object that describes the pod that will be created. @@ -175,6 +199,12 @@ message DaemonSetStatus { // create the name for the newest ControllerRevision. // +optional optional int32 collisionCount = 9; + + // Represents the latest available observations of a DaemonSet's current state. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + repeated DaemonSetCondition conditions = 10; } // DaemonSetUpdateStrategy is a struct used to control the update strategy for a DaemonSet. @@ -192,6 +222,8 @@ message DaemonSetUpdateStrategy { optional RollingUpdateDaemonSet rollingUpdate = 2; } +// DEPRECATED - This group version of Deployment is deprecated by apps/v1/Deployment. See the release notes for +// more information. // Deployment enables declarative updates for Pods and ReplicaSets. message Deployment { // Standard object metadata. @@ -247,7 +279,7 @@ message DeploymentSpec { // Label selector for pods. Existing ReplicaSets whose pods are // selected by this will be the ones affected by this deployment. - // +optional + // It must match the pod template's labels. optional k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector selector = 2; // Template describes the pods that will be created. @@ -336,6 +368,8 @@ message DeploymentStrategy { optional RollingUpdateDeployment rollingUpdate = 2; } +// DEPRECATED - This group version of ReplicaSet is deprecated by apps/v1/ReplicaSet. See the release notes for +// more information. // ReplicaSet ensures that a specified number of pod replicas are running at any given time. message ReplicaSet { // If the Labels of a ReplicaSet are empty, they are defaulted to @@ -407,10 +441,9 @@ message ReplicaSetSpec { optional int32 minReadySeconds = 4; // Selector is a label query over pods that should match the replica count. - // If the selector is empty, it is defaulted to the labels present on the pod template. // Label keys and values that must match in order to be controlled by this replica set. + // It must match the pod template's labels. // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors - // +optional optional k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector selector = 2; // Template is the object that describes the pod that will be created if @@ -549,6 +582,8 @@ message ScaleStatus { optional string targetSelector = 3; } +// DEPRECATED - This group version of StatefulSet is deprecated by apps/v1/StatefulSet. See the release notes for +// more information. // StatefulSet represents a set of pods with consistent identities. // Identities are defined as: // - Network: A single stable DNS and hostname. @@ -569,6 +604,27 @@ message StatefulSet { optional StatefulSetStatus status = 3; } +// StatefulSetCondition describes the state of a statefulset at a certain point. +message StatefulSetCondition { + // Type of statefulset condition. + optional string type = 1; + + // Status of the condition, one of True, False, Unknown. + optional string status = 2; + + // Last time the condition transitioned from one status to another. + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.Time lastTransitionTime = 3; + + // The reason for the condition's last transition. + // +optional + optional string reason = 4; + + // A human readable message indicating details about the transition. + // +optional + optional string message = 5; +} + // StatefulSetList is a collection of StatefulSets. message StatefulSetList { // +optional @@ -588,9 +644,8 @@ message StatefulSetSpec { optional int32 replicas = 1; // selector is a label query over pods that should match the replica count. - // If empty, defaulted to labels on the pod template. + // It must match the pod template's labels. // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors - // +optional optional k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector selector = 2; // template is the object that describes the pod that will be created if @@ -673,6 +728,12 @@ message StatefulSetStatus { // newest ControllerRevision. // +optional optional int32 collisionCount = 9; + + // Represents the latest available observations of a statefulset's current state. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + repeated StatefulSetCondition conditions = 10; } // StatefulSetUpdateStrategy indicates the strategy that the StatefulSet diff --git a/staging/src/k8s.io/api/apps/v1beta2/register.go b/staging/src/k8s.io/api/apps/v1beta2/register.go index e207c7dc5a9..2784ee37712 100644 --- a/staging/src/k8s.io/api/apps/v1beta2/register.go +++ b/staging/src/k8s.io/api/apps/v1beta2/register.go @@ -41,7 +41,7 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Deployment{}, diff --git a/staging/src/k8s.io/api/apps/v1beta2/types.go b/staging/src/k8s.io/api/apps/v1beta2/types.go index fa5676e1ac6..c8be2aacaeb 100644 --- a/staging/src/k8s.io/api/apps/v1beta2/types.go +++ b/staging/src/k8s.io/api/apps/v1beta2/types.go @@ -28,6 +28,7 @@ const ( StatefulSetRevisionLabel = ControllerRevisionHashLabelKey DeprecatedRollbackTo = "deprecated.deployment.rollback.to" DeprecatedTemplateGeneration = "deprecated.daemonset.template.generation" + StatefulSetPodNameLabel = "statefulset.kubernetes.io/pod-name" ) // ScaleSpec describes the attributes of a scale subresource @@ -81,6 +82,8 @@ type Scale struct { // +genclient:method=UpdateScale,verb=update,subresource=scale,input=Scale,result=Scale // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// DEPRECATED - This group version of StatefulSet is deprecated by apps/v1/StatefulSet. See the release notes for +// more information. // StatefulSet represents a set of pods with consistent identities. // Identities are defined as: // - Network: A single stable DNS and hostname. @@ -169,10 +172,9 @@ type StatefulSetSpec struct { Replicas *int32 `json:"replicas,omitempty" protobuf:"varint,1,opt,name=replicas"` // selector is a label query over pods that should match the replica count. - // If empty, defaulted to labels on the pod template. + // It must match the pod template's labels. // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors - // +optional - Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,2,opt,name=selector"` + Selector *metav1.LabelSelector `json:"selector" protobuf:"bytes,2,opt,name=selector"` // template is the object that describes the pod that will be created if // insufficient replicas are detected. Each pod stamped out by the StatefulSet @@ -254,6 +256,31 @@ type StatefulSetStatus struct { // newest ControllerRevision. // +optional CollisionCount *int32 `json:"collisionCount,omitempty" protobuf:"varint,9,opt,name=collisionCount"` + + // Represents the latest available observations of a statefulset's current state. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + Conditions []StatefulSetCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,10,rep,name=conditions"` +} + +type StatefulSetConditionType string + +// StatefulSetCondition describes the state of a statefulset at a certain point. +type StatefulSetCondition struct { + // Type of statefulset condition. + Type StatefulSetConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=StatefulSetConditionType"` + // Status of the condition, one of True, False, Unknown. + Status v1.ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=k8s.io/api/core/v1.ConditionStatus"` + // Last time the condition transitioned from one status to another. + // +optional + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,3,opt,name=lastTransitionTime"` + // The reason for the condition's last transition. + // +optional + Reason string `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"` + // A human readable message indicating details about the transition. + // +optional + Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -269,6 +296,8 @@ type StatefulSetList struct { // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// DEPRECATED - This group version of Deployment is deprecated by apps/v1/Deployment. See the release notes for +// more information. // Deployment enables declarative updates for Pods and ReplicaSets. type Deployment struct { metav1.TypeMeta `json:",inline"` @@ -294,8 +323,8 @@ type DeploymentSpec struct { // Label selector for pods. Existing ReplicaSets whose pods are // selected by this will be the ones affected by this deployment. - // +optional - Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,2,opt,name=selector"` + // It must match the pod template's labels. + Selector *metav1.LabelSelector `json:"selector" protobuf:"bytes,2,opt,name=selector"` // Template describes the pods that will be created. Template v1.PodTemplateSpec `json:"template" protobuf:"bytes,3,opt,name=template"` @@ -525,10 +554,9 @@ type RollingUpdateDaemonSet struct { type DaemonSetSpec struct { // A label query over pods that are managed by the daemon set. // Must match in order to be controlled. - // If empty, defaulted to labels on Pod template. + // It must match the pod template's labels. // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors - // +optional - Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,1,opt,name=selector"` + Selector *metav1.LabelSelector `json:"selector" protobuf:"bytes,1,opt,name=selector"` // An object that describes the pod that will be created. // The DaemonSet will create exactly one copy of this pod on every node @@ -601,11 +629,40 @@ type DaemonSetStatus struct { // create the name for the newest ControllerRevision. // +optional CollisionCount *int32 `json:"collisionCount,omitempty" protobuf:"varint,9,opt,name=collisionCount"` + + // Represents the latest available observations of a DaemonSet's current state. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + Conditions []DaemonSetCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,10,rep,name=conditions"` +} + +type DaemonSetConditionType string + +// TODO: Add valid condition types of a DaemonSet. + +// DaemonSetCondition describes the state of a DaemonSet at a certain point. +type DaemonSetCondition struct { + // Type of DaemonSet condition. + Type DaemonSetConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=DaemonSetConditionType"` + // Status of the condition, one of True, False, Unknown. + Status v1.ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=k8s.io/api/core/v1.ConditionStatus"` + // Last time the condition transitioned from one status to another. + // +optional + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,3,opt,name=lastTransitionTime"` + // The reason for the condition's last transition. + // +optional + Reason string `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"` + // A human readable message indicating details about the transition. + // +optional + Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"` } // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// DEPRECATED - This group version of DaemonSet is deprecated by apps/v1/DaemonSet. See the release notes for +// more information. // DaemonSet represents the configuration of a daemon set. type DaemonSet struct { metav1.TypeMeta `json:",inline"` @@ -652,6 +709,8 @@ type DaemonSetList struct { // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// DEPRECATED - This group version of ReplicaSet is deprecated by apps/v1/ReplicaSet. See the release notes for +// more information. // ReplicaSet ensures that a specified number of pod replicas are running at any given time. type ReplicaSet struct { metav1.TypeMeta `json:",inline"` @@ -707,11 +766,10 @@ type ReplicaSetSpec struct { MinReadySeconds int32 `json:"minReadySeconds,omitempty" protobuf:"varint,4,opt,name=minReadySeconds"` // Selector is a label query over pods that should match the replica count. - // If the selector is empty, it is defaulted to the labels present on the pod template. // Label keys and values that must match in order to be controlled by this replica set. + // It must match the pod template's labels. // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors - // +optional - Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,2,opt,name=selector"` + Selector *metav1.LabelSelector `json:"selector" protobuf:"bytes,2,opt,name=selector"` // Template is the object that describes the pod that will be created if // insufficient replicas are detected. @@ -779,6 +837,8 @@ type ReplicaSetCondition struct { // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// DEPRECATED - This group version of ControllerRevision is deprecated by apps/v1/ControllerRevision. See the +// release notes for more information. // ControllerRevision implements an immutable snapshot of state data. Clients // are responsible for serializing and deserializing the objects that contain // their internal state. diff --git a/staging/src/k8s.io/api/apps/v1beta2/types_swagger_doc_generated.go b/staging/src/k8s.io/api/apps/v1beta2/types_swagger_doc_generated.go index 35ae28ca2d2..e2f133b5181 100644 --- a/staging/src/k8s.io/api/apps/v1beta2/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/apps/v1beta2/types_swagger_doc_generated.go @@ -28,7 +28,7 @@ package v1beta2 // AUTO-GENERATED FUNCTIONS START HERE var map_ControllerRevision = map[string]string{ - "": "ControllerRevision implements an immutable snapshot of state data. Clients are responsible for serializing and deserializing the objects that contain their internal state. Once a ControllerRevision has been successfully created, it can not be updated. The API Server will fail validation of all requests that attempt to mutate the Data field. ControllerRevisions may, however, be deleted. Note that, due to its use by both the DaemonSet and StatefulSet controllers for update and rollback, this object is beta. However, it may be subject to name and representation changes in future releases, and clients should not depend on its stability. It is primarily for internal use by controllers.", + "": "DEPRECATED - This group version of ControllerRevision is deprecated by apps/v1/ControllerRevision. See the release notes for more information. ControllerRevision implements an immutable snapshot of state data. Clients are responsible for serializing and deserializing the objects that contain their internal state. Once a ControllerRevision has been successfully created, it can not be updated. The API Server will fail validation of all requests that attempt to mutate the Data field. ControllerRevisions may, however, be deleted. Note that, due to its use by both the DaemonSet and StatefulSet controllers for update and rollback, this object is beta. However, it may be subject to name and representation changes in future releases, and clients should not depend on its stability. It is primarily for internal use by controllers.", "metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", "data": "Data is the serialized representation of the state.", "revision": "Revision indicates the revision of the state represented by Data.", @@ -49,7 +49,7 @@ func (ControllerRevisionList) SwaggerDoc() map[string]string { } var map_DaemonSet = map[string]string{ - "": "DaemonSet represents the configuration of a daemon set.", + "": "DEPRECATED - This group version of DaemonSet is deprecated by apps/v1/DaemonSet. See the release notes for more information. DaemonSet represents the configuration of a daemon set.", "metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", "spec": "The desired behavior of this daemon set. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status", "status": "The current status of this daemon set. This data may be out of date by some window of time. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status", @@ -59,6 +59,19 @@ func (DaemonSet) SwaggerDoc() map[string]string { return map_DaemonSet } +var map_DaemonSetCondition = map[string]string{ + "": "DaemonSetCondition describes the state of a DaemonSet at a certain point.", + "type": "Type of DaemonSet condition.", + "status": "Status of the condition, one of True, False, Unknown.", + "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.", +} + +func (DaemonSetCondition) SwaggerDoc() map[string]string { + return map_DaemonSetCondition +} + var map_DaemonSetList = map[string]string{ "": "DaemonSetList is a collection of daemon sets.", "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", @@ -71,7 +84,7 @@ func (DaemonSetList) SwaggerDoc() map[string]string { var map_DaemonSetSpec = map[string]string{ "": "DaemonSetSpec is the specification of a daemon set.", - "selector": "A label query over pods that are managed by the daemon set. Must match in order to be controlled. If empty, defaulted to labels on Pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", + "selector": "A label query over pods that are managed by the daemon set. Must match in order to be controlled. It must match the pod template's labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", "template": "An object that describes the pod that will be created. The DaemonSet will create exactly one copy of this pod on every node that matches the template's node selector (or on every node if no node selector is specified). More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template", "updateStrategy": "An update strategy to replace existing DaemonSet pods with new pods.", "minReadySeconds": "The minimum number of seconds for which a newly created DaemonSet 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).", @@ -93,6 +106,7 @@ var map_DaemonSetStatus = map[string]string{ "numberAvailable": "The number of nodes that should be running the daemon pod and have one or more of the daemon pod running and available (ready for at least spec.minReadySeconds)", "numberUnavailable": "The number of nodes that should be running the daemon pod and have none of the daemon pod running and available (ready for at least spec.minReadySeconds)", "collisionCount": "Count of hash collisions for the DaemonSet. The DaemonSet controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ControllerRevision.", + "conditions": "Represents the latest available observations of a DaemonSet's current state.", } func (DaemonSetStatus) SwaggerDoc() map[string]string { @@ -110,7 +124,7 @@ func (DaemonSetUpdateStrategy) SwaggerDoc() map[string]string { } var map_Deployment = map[string]string{ - "": "Deployment enables declarative updates for Pods and ReplicaSets.", + "": "DEPRECATED - This group version of Deployment is deprecated by apps/v1/Deployment. See the release notes for more information. Deployment enables declarative updates for Pods and ReplicaSets.", "metadata": "Standard object metadata.", "spec": "Specification of the desired behavior of the Deployment.", "status": "Most recently observed status of the Deployment.", @@ -147,7 +161,7 @@ func (DeploymentList) SwaggerDoc() map[string]string { var map_DeploymentSpec = map[string]string{ "": "DeploymentSpec is the specification of the desired behavior of the Deployment.", "replicas": "Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1.", - "selector": "Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment.", + "selector": "Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment. It must match the pod template's labels.", "template": "Template describes the pods that will be created.", "strategy": "The deployment strategy to use to replace existing pods with new ones.", "minReadySeconds": "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)", @@ -187,7 +201,7 @@ func (DeploymentStrategy) SwaggerDoc() map[string]string { } var map_ReplicaSet = map[string]string{ - "": "ReplicaSet ensures that a specified number of pod replicas are running at any given time.", + "": "DEPRECATED - This group version of ReplicaSet is deprecated by apps/v1/ReplicaSet. See the release notes for more information. ReplicaSet ensures that a specified number of pod replicas are running at any given time.", "metadata": "If the Labels of a ReplicaSet are empty, they are defaulted to be the same as the Pod(s) that the ReplicaSet manages. Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", "spec": "Spec defines the specification of the desired behavior of the ReplicaSet. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status", "status": "Status is the most recently observed status of the ReplicaSet. This data may be out of date by some window of time. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status", @@ -224,7 +238,7 @@ var map_ReplicaSetSpec = map[string]string{ "": "ReplicaSetSpec is the specification of a ReplicaSet.", "replicas": "Replicas is the number of desired replicas. This is a pointer to distinguish between explicit zero and unspecified. Defaults to 1. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller", "minReadySeconds": "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)", - "selector": "Selector is a label query over pods that should match the replica count. If the selector is empty, it is defaulted to the labels present on the pod template. Label keys and values that must match in order to be controlled by this replica set. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", + "selector": "Selector is a label query over pods that should match the replica count. Label keys and values that must match in order to be controlled by this replica set. It must match the pod template's labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", "template": "Template is the object that describes the pod that will be created if insufficient replicas are detected. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template", } @@ -306,7 +320,7 @@ func (ScaleStatus) SwaggerDoc() map[string]string { } var map_StatefulSet = map[string]string{ - "": "StatefulSet represents a set of pods with consistent identities. Identities are defined as:\n - Network: A single stable DNS and hostname.\n - Storage: As many VolumeClaims as requested.\nThe StatefulSet guarantees that a given network identity will always map to the same storage identity.", + "": "DEPRECATED - This group version of StatefulSet is deprecated by apps/v1/StatefulSet. See the release notes for more information. StatefulSet represents a set of pods with consistent identities. Identities are defined as:\n - Network: A single stable DNS and hostname.\n - Storage: As many VolumeClaims as requested.\nThe StatefulSet guarantees that a given network identity will always map to the same storage identity.", "spec": "Spec defines the desired identities of pods in this set.", "status": "Status is the current status of Pods in this StatefulSet. This data may be out of date by some window of time.", } @@ -315,6 +329,19 @@ func (StatefulSet) SwaggerDoc() map[string]string { return map_StatefulSet } +var map_StatefulSetCondition = map[string]string{ + "": "StatefulSetCondition describes the state of a statefulset at a certain point.", + "type": "Type of statefulset condition.", + "status": "Status of the condition, one of True, False, Unknown.", + "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.", +} + +func (StatefulSetCondition) SwaggerDoc() map[string]string { + return map_StatefulSetCondition +} + var map_StatefulSetList = map[string]string{ "": "StatefulSetList is a collection of StatefulSets.", } @@ -326,7 +353,7 @@ func (StatefulSetList) SwaggerDoc() map[string]string { var map_StatefulSetSpec = map[string]string{ "": "A StatefulSetSpec is the specification of a StatefulSet.", "replicas": "replicas is the desired number of replicas of the given Template. These are replicas in the sense that they are instantiations of the same Template, but individual replicas also have a consistent identity. If unspecified, defaults to 1.", - "selector": "selector is a label query over pods that should match the replica count. If empty, defaulted to labels on the pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", + "selector": "selector is a label query over pods that should match the replica count. It must match the pod template's labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", "template": "template is the object that describes the pod that will be created if insufficient replicas are detected. Each pod stamped out by the StatefulSet will fulfill this Template, but have a unique identity from the rest of the StatefulSet.", "volumeClaimTemplates": "volumeClaimTemplates is a list of claims that pods are allowed to reference. The StatefulSet controller is responsible for mapping network identities to claims in a way that maintains the identity of a pod. Every claim in this list must have at least one matching (by name) volumeMount in one container in the template. A claim in this list takes precedence over any volumes in the template, with the same name.", "serviceName": "serviceName is the name of the service that governs this StatefulSet. This service must exist before the StatefulSet, and is responsible for the network identity of the set. Pods get DNS/hostnames that follow the pattern: pod-specific-string.serviceName.default.svc.cluster.local where \"pod-specific-string\" is managed by the StatefulSet controller.", @@ -349,6 +376,7 @@ var map_StatefulSetStatus = map[string]string{ "currentRevision": "currentRevision, if not empty, indicates the version of the StatefulSet used to generate Pods in the sequence [0,currentReplicas).", "updateRevision": "updateRevision, if not empty, indicates the version of the StatefulSet used to generate Pods in the sequence [replicas-updatedReplicas,replicas)", "collisionCount": "collisionCount is the count of hash collisions for the StatefulSet. The StatefulSet controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ControllerRevision.", + "conditions": "Represents the latest available observations of a statefulset's current state.", } func (StatefulSetStatus) SwaggerDoc() map[string]string { diff --git a/staging/src/k8s.io/api/apps/v1beta2/zz_generated.deepcopy.go b/staging/src/k8s.io/api/apps/v1beta2/zz_generated.deepcopy.go index 124f84a9d4e..f2d847ed597 100644 --- a/staging/src/k8s.io/api/apps/v1beta2/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/apps/v1beta2/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,141 +23,10 @@ package v1beta2 import ( core_v1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" intstr "k8s.io/apimachinery/pkg/util/intstr" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ControllerRevision).DeepCopyInto(out.(*ControllerRevision)) - return nil - }, InType: reflect.TypeOf(&ControllerRevision{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ControllerRevisionList).DeepCopyInto(out.(*ControllerRevisionList)) - return nil - }, InType: reflect.TypeOf(&ControllerRevisionList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DaemonSet).DeepCopyInto(out.(*DaemonSet)) - return nil - }, InType: reflect.TypeOf(&DaemonSet{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DaemonSetList).DeepCopyInto(out.(*DaemonSetList)) - return nil - }, InType: reflect.TypeOf(&DaemonSetList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DaemonSetSpec).DeepCopyInto(out.(*DaemonSetSpec)) - return nil - }, InType: reflect.TypeOf(&DaemonSetSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DaemonSetStatus).DeepCopyInto(out.(*DaemonSetStatus)) - return nil - }, InType: reflect.TypeOf(&DaemonSetStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DaemonSetUpdateStrategy).DeepCopyInto(out.(*DaemonSetUpdateStrategy)) - return nil - }, InType: reflect.TypeOf(&DaemonSetUpdateStrategy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Deployment).DeepCopyInto(out.(*Deployment)) - return nil - }, InType: reflect.TypeOf(&Deployment{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeploymentCondition).DeepCopyInto(out.(*DeploymentCondition)) - return nil - }, InType: reflect.TypeOf(&DeploymentCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeploymentList).DeepCopyInto(out.(*DeploymentList)) - return nil - }, InType: reflect.TypeOf(&DeploymentList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeploymentSpec).DeepCopyInto(out.(*DeploymentSpec)) - return nil - }, InType: reflect.TypeOf(&DeploymentSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeploymentStatus).DeepCopyInto(out.(*DeploymentStatus)) - return nil - }, InType: reflect.TypeOf(&DeploymentStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeploymentStrategy).DeepCopyInto(out.(*DeploymentStrategy)) - return nil - }, InType: reflect.TypeOf(&DeploymentStrategy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicaSet).DeepCopyInto(out.(*ReplicaSet)) - return nil - }, InType: reflect.TypeOf(&ReplicaSet{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicaSetCondition).DeepCopyInto(out.(*ReplicaSetCondition)) - return nil - }, InType: reflect.TypeOf(&ReplicaSetCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicaSetList).DeepCopyInto(out.(*ReplicaSetList)) - return nil - }, InType: reflect.TypeOf(&ReplicaSetList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicaSetSpec).DeepCopyInto(out.(*ReplicaSetSpec)) - return nil - }, InType: reflect.TypeOf(&ReplicaSetSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicaSetStatus).DeepCopyInto(out.(*ReplicaSetStatus)) - return nil - }, InType: reflect.TypeOf(&ReplicaSetStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RollingUpdateDaemonSet).DeepCopyInto(out.(*RollingUpdateDaemonSet)) - return nil - }, InType: reflect.TypeOf(&RollingUpdateDaemonSet{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RollingUpdateDeployment).DeepCopyInto(out.(*RollingUpdateDeployment)) - return nil - }, InType: reflect.TypeOf(&RollingUpdateDeployment{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RollingUpdateStatefulSetStrategy).DeepCopyInto(out.(*RollingUpdateStatefulSetStrategy)) - return nil - }, InType: reflect.TypeOf(&RollingUpdateStatefulSetStrategy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Scale).DeepCopyInto(out.(*Scale)) - return nil - }, InType: reflect.TypeOf(&Scale{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ScaleSpec).DeepCopyInto(out.(*ScaleSpec)) - return nil - }, InType: reflect.TypeOf(&ScaleSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ScaleStatus).DeepCopyInto(out.(*ScaleStatus)) - return nil - }, InType: reflect.TypeOf(&ScaleStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StatefulSet).DeepCopyInto(out.(*StatefulSet)) - return nil - }, InType: reflect.TypeOf(&StatefulSet{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StatefulSetList).DeepCopyInto(out.(*StatefulSetList)) - return nil - }, InType: reflect.TypeOf(&StatefulSetList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StatefulSetSpec).DeepCopyInto(out.(*StatefulSetSpec)) - return nil - }, InType: reflect.TypeOf(&StatefulSetSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StatefulSetStatus).DeepCopyInto(out.(*StatefulSetStatus)) - return nil - }, InType: reflect.TypeOf(&StatefulSetStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StatefulSetUpdateStrategy).DeepCopyInto(out.(*StatefulSetUpdateStrategy)) - return nil - }, InType: reflect.TypeOf(&StatefulSetUpdateStrategy{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ControllerRevision) DeepCopyInto(out *ControllerRevision) { *out = *in @@ -249,6 +118,23 @@ func (in *DaemonSet) DeepCopyObject() runtime.Object { } } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DaemonSetCondition) DeepCopyInto(out *DaemonSetCondition) { + *out = *in + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DaemonSetCondition. +func (in *DaemonSetCondition) DeepCopy() *DaemonSetCondition { + if in == nil { + return nil + } + out := new(DaemonSetCondition) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DaemonSetList) DeepCopyInto(out *DaemonSetList) { *out = *in @@ -331,6 +217,13 @@ func (in *DaemonSetStatus) DeepCopyInto(out *DaemonSetStatus) { **out = **in } } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]DaemonSetCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -880,6 +773,23 @@ func (in *StatefulSet) DeepCopyObject() runtime.Object { } } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StatefulSetCondition) DeepCopyInto(out *StatefulSetCondition) { + *out = *in + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatefulSetCondition. +func (in *StatefulSetCondition) DeepCopy() *StatefulSetCondition { + if in == nil { + return nil + } + out := new(StatefulSetCondition) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *StatefulSetList) DeepCopyInto(out *StatefulSetList) { *out = *in @@ -978,6 +888,13 @@ func (in *StatefulSetStatus) DeepCopyInto(out *StatefulSetStatus) { **out = **in } } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]StatefulSetCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } diff --git a/staging/src/k8s.io/api/authentication/v1/BUILD b/staging/src/k8s.io/api/authentication/v1/BUILD index 3a378d34e69..f2e2b1a989c 100644 --- a/staging/src/k8s.io/api/authentication/v1/BUILD +++ b/staging/src/k8s.io/api/authentication/v1/BUILD @@ -20,7 +20,6 @@ go_library( "//vendor/github.com/gogo/protobuf/proto:go_default_library", "//vendor/github.com/gogo/protobuf/sortkeys:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/staging/src/k8s.io/api/authentication/v1/doc.go b/staging/src/k8s.io/api/authentication/v1/doc.go index 15b117a4c95..2d2ed2ee821 100644 --- a/staging/src/k8s.io/api/authentication/v1/doc.go +++ b/staging/src/k8s.io/api/authentication/v1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +groupName=authentication.k8s.io // +k8s:openapi-gen=true package v1 // import "k8s.io/api/authentication/v1" diff --git a/staging/src/k8s.io/api/authentication/v1/generated.pb.go b/staging/src/k8s.io/api/authentication/v1/generated.pb.go index e7367893981..2e66666eb01 100644 --- a/staging/src/k8s.io/api/authentication/v1/generated.pb.go +++ b/staging/src/k8s.io/api/authentication/v1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/api/authentication/v1/generated.proto b/staging/src/k8s.io/api/authentication/v1/generated.proto index fb7888b6323..ea7b3b2885a 100644 --- a/staging/src/k8s.io/api/authentication/v1/generated.proto +++ b/staging/src/k8s.io/api/authentication/v1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/api/authentication/v1/register.go b/staging/src/k8s.io/api/authentication/v1/register.go index 936237c2be8..2ca79a620a5 100644 --- a/staging/src/k8s.io/api/authentication/v1/register.go +++ b/staging/src/k8s.io/api/authentication/v1/register.go @@ -41,7 +41,7 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &TokenReview{}, diff --git a/staging/src/k8s.io/api/authentication/v1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/authentication/v1/zz_generated.deepcopy.go index c1717c1cd86..f9b32192c3d 100644 --- a/staging/src/k8s.io/api/authentication/v1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/authentication/v1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,40 +21,9 @@ limitations under the License. package v1 import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*TokenReview).DeepCopyInto(out.(*TokenReview)) - return nil - }, InType: reflect.TypeOf(&TokenReview{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*TokenReviewSpec).DeepCopyInto(out.(*TokenReviewSpec)) - return nil - }, InType: reflect.TypeOf(&TokenReviewSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*TokenReviewStatus).DeepCopyInto(out.(*TokenReviewStatus)) - return nil - }, InType: reflect.TypeOf(&TokenReviewStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*UserInfo).DeepCopyInto(out.(*UserInfo)) - return nil - }, InType: reflect.TypeOf(&UserInfo{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TokenReview) DeepCopyInto(out *TokenReview) { *out = *in diff --git a/staging/src/k8s.io/api/authentication/v1beta1/BUILD b/staging/src/k8s.io/api/authentication/v1beta1/BUILD index b02c3602aae..998d793fef7 100644 --- a/staging/src/k8s.io/api/authentication/v1beta1/BUILD +++ b/staging/src/k8s.io/api/authentication/v1beta1/BUILD @@ -20,7 +20,6 @@ go_library( "//vendor/github.com/gogo/protobuf/proto:go_default_library", "//vendor/github.com/gogo/protobuf/sortkeys:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/staging/src/k8s.io/api/authentication/v1beta1/doc.go b/staging/src/k8s.io/api/authentication/v1beta1/doc.go index 065cfbd9957..e0de315d40d 100644 --- a/staging/src/k8s.io/api/authentication/v1beta1/doc.go +++ b/staging/src/k8s.io/api/authentication/v1beta1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +groupName=authentication.k8s.io // +k8s:openapi-gen=true package v1beta1 // import "k8s.io/api/authentication/v1beta1" diff --git a/staging/src/k8s.io/api/authentication/v1beta1/generated.pb.go b/staging/src/k8s.io/api/authentication/v1beta1/generated.pb.go index da085dd20fd..86e362b87b7 100644 --- a/staging/src/k8s.io/api/authentication/v1beta1/generated.pb.go +++ b/staging/src/k8s.io/api/authentication/v1beta1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/api/authentication/v1beta1/generated.proto b/staging/src/k8s.io/api/authentication/v1beta1/generated.proto index 300e53488a1..3d0abd15dac 100644 --- a/staging/src/k8s.io/api/authentication/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/authentication/v1beta1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/api/authentication/v1beta1/register.go b/staging/src/k8s.io/api/authentication/v1beta1/register.go index 7d89e5ef70c..ed23e50f7e9 100644 --- a/staging/src/k8s.io/api/authentication/v1beta1/register.go +++ b/staging/src/k8s.io/api/authentication/v1beta1/register.go @@ -41,7 +41,7 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &TokenReview{}, diff --git a/staging/src/k8s.io/api/authentication/v1beta1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/authentication/v1beta1/zz_generated.deepcopy.go index 1b94ececa1f..65aabe7c422 100644 --- a/staging/src/k8s.io/api/authentication/v1beta1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/authentication/v1beta1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,40 +21,9 @@ limitations under the License. package v1beta1 import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*TokenReview).DeepCopyInto(out.(*TokenReview)) - return nil - }, InType: reflect.TypeOf(&TokenReview{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*TokenReviewSpec).DeepCopyInto(out.(*TokenReviewSpec)) - return nil - }, InType: reflect.TypeOf(&TokenReviewSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*TokenReviewStatus).DeepCopyInto(out.(*TokenReviewStatus)) - return nil - }, InType: reflect.TypeOf(&TokenReviewStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*UserInfo).DeepCopyInto(out.(*UserInfo)) - return nil - }, InType: reflect.TypeOf(&UserInfo{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TokenReview) DeepCopyInto(out *TokenReview) { *out = *in diff --git a/staging/src/k8s.io/api/authorization/v1/BUILD b/staging/src/k8s.io/api/authorization/v1/BUILD index 6bb16adcb01..af9e74a6356 100644 --- a/staging/src/k8s.io/api/authorization/v1/BUILD +++ b/staging/src/k8s.io/api/authorization/v1/BUILD @@ -20,7 +20,6 @@ go_library( "//vendor/github.com/gogo/protobuf/proto:go_default_library", "//vendor/github.com/gogo/protobuf/sortkeys:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/staging/src/k8s.io/api/authorization/v1/doc.go b/staging/src/k8s.io/api/authorization/v1/doc.go index b02e09caeae..c06b798df8c 100644 --- a/staging/src/k8s.io/api/authorization/v1/doc.go +++ b/staging/src/k8s.io/api/authorization/v1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:openapi-gen=true // +groupName=authorization.k8s.io diff --git a/staging/src/k8s.io/api/authorization/v1/generated.pb.go b/staging/src/k8s.io/api/authorization/v1/generated.pb.go index 0795a645492..bdb606c0bf0 100644 --- a/staging/src/k8s.io/api/authorization/v1/generated.pb.go +++ b/staging/src/k8s.io/api/authorization/v1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -730,6 +730,14 @@ func (m *SubjectAccessReviewStatus) MarshalTo(dAtA []byte) (int, error) { i++ i = encodeVarintGenerated(dAtA, i, uint64(len(m.EvaluationError))) i += copy(dAtA[i:], m.EvaluationError) + dAtA[i] = 0x20 + i++ + if m.Denied { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ return i, nil } @@ -1015,6 +1023,7 @@ func (m *SubjectAccessReviewStatus) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) l = len(m.EvaluationError) n += 1 + l + sovGenerated(uint64(l)) + n += 2 return n } @@ -1205,6 +1214,7 @@ func (this *SubjectAccessReviewStatus) String() string { `Allowed:` + fmt.Sprintf("%v", this.Allowed) + `,`, `Reason:` + fmt.Sprintf("%v", this.Reason) + `,`, `EvaluationError:` + fmt.Sprintf("%v", this.EvaluationError) + `,`, + `Denied:` + fmt.Sprintf("%v", this.Denied) + `,`, `}`, }, "") return s @@ -3130,6 +3140,26 @@ func (m *SubjectAccessReviewStatus) Unmarshal(dAtA []byte) error { } m.EvaluationError = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Denied", 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.Denied = bool(v != 0) default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -3422,77 +3452,77 @@ func init() { } var fileDescriptorGenerated = []byte{ - // 1137 bytes of a gzipped FileDescriptorProto + // 1152 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x56, 0x4f, 0x6f, 0x1b, 0x45, - 0x14, 0xf7, 0xae, 0xed, 0xc4, 0x1e, 0x37, 0x24, 0x9d, 0x28, 0xcd, 0x36, 0x15, 0x76, 0xb4, 0x48, - 0x90, 0x8a, 0xb2, 0x4b, 0x4c, 0xdb, 0x44, 0x95, 0x2a, 0x14, 0xab, 0x11, 0x8a, 0xd4, 0x96, 0x6a, + 0x14, 0xf7, 0xae, 0xed, 0xd4, 0x1e, 0x37, 0x24, 0x9d, 0x28, 0xcd, 0x36, 0x15, 0x76, 0xb4, 0x48, + 0x90, 0x8a, 0xb2, 0x4b, 0x4c, 0xdb, 0x44, 0x95, 0x2a, 0x14, 0x2b, 0x11, 0x8a, 0xd4, 0x96, 0x6a, 0xa2, 0x44, 0xa2, 0x08, 0xc4, 0x78, 0x3d, 0xb1, 0x97, 0xd8, 0xbb, 0xcb, 0xcc, 0xac, 0x43, 0x38, - 0x55, 0xe2, 0x0b, 0x70, 0xe4, 0xc0, 0x81, 0x6f, 0x80, 0x90, 0x90, 0xb8, 0x71, 0xe0, 0x80, 0x72, - 0xec, 0xb1, 0x07, 0x64, 0x91, 0xe5, 0xcc, 0x77, 0x40, 0x33, 0x3b, 0xf6, 0xae, 0x93, 0xb5, 0x9b, - 0x70, 0xa0, 0x97, 0xde, 0x76, 0xdf, 0xef, 0xf7, 0xfe, 0xcc, 0x7b, 0x6f, 0xde, 0x3c, 0xf0, 0xe0, - 0x70, 0x93, 0x59, 0xae, 0x6f, 0x1f, 0x86, 0x4d, 0x42, 0x3d, 0xc2, 0x09, 0xb3, 0xfb, 0xc4, 0x6b, - 0xf9, 0xd4, 0x56, 0x00, 0x0e, 0x5c, 0x1b, 0x87, 0xbc, 0xe3, 0x53, 0xf7, 0x1b, 0xcc, 0x5d, 0xdf, - 0xb3, 0xfb, 0xeb, 0x76, 0x9b, 0x78, 0x84, 0x62, 0x4e, 0x5a, 0x56, 0x40, 0x7d, 0xee, 0xc3, 0x1b, - 0x31, 0xd9, 0xc2, 0x81, 0x6b, 0x8d, 0x91, 0xad, 0xfe, 0xfa, 0xca, 0x7b, 0x6d, 0x97, 0x77, 0xc2, - 0xa6, 0xe5, 0xf8, 0x3d, 0xbb, 0xed, 0xb7, 0x7d, 0x5b, 0xea, 0x34, 0xc3, 0x03, 0xf9, 0x27, 0x7f, - 0xe4, 0x57, 0x6c, 0x6b, 0xe5, 0x76, 0xe2, 0xb8, 0x87, 0x9d, 0x8e, 0xeb, 0x11, 0x7a, 0x6c, 0x07, - 0x87, 0x6d, 0x21, 0x60, 0x76, 0x8f, 0x70, 0x9c, 0x11, 0xc1, 0x8a, 0x3d, 0x49, 0x8b, 0x86, 0x1e, - 0x77, 0x7b, 0xe4, 0x9c, 0xc2, 0xdd, 0x97, 0x29, 0x30, 0xa7, 0x43, 0x7a, 0xf8, 0x9c, 0xde, 0x07, - 0x93, 0xf4, 0x42, 0xee, 0x76, 0x6d, 0xd7, 0xe3, 0x8c, 0xd3, 0xb3, 0x4a, 0xe6, 0x06, 0x00, 0xdb, - 0x5f, 0x73, 0x8a, 0xf7, 0x71, 0x37, 0x24, 0xb0, 0x06, 0x8a, 0x2e, 0x27, 0x3d, 0x66, 0x68, 0xab, - 0xf9, 0xb5, 0x72, 0xa3, 0x1c, 0x0d, 0x6a, 0xc5, 0x1d, 0x21, 0x40, 0xb1, 0xfc, 0x5e, 0xe9, 0xfb, - 0x1f, 0x6b, 0xb9, 0x67, 0x7f, 0xae, 0xe6, 0xcc, 0x5f, 0x74, 0x60, 0x3c, 0xf4, 0x1d, 0xdc, 0xdd, - 0x0d, 0x9b, 0x5f, 0x12, 0x87, 0x6f, 0x39, 0x0e, 0x61, 0x0c, 0x91, 0xbe, 0x4b, 0x8e, 0xe0, 0x17, - 0xa0, 0x24, 0xd2, 0xd1, 0xc2, 0x1c, 0x1b, 0xda, 0xaa, 0xb6, 0x56, 0xa9, 0xbf, 0x6f, 0x25, 0x85, - 0x18, 0x45, 0x67, 0x05, 0x87, 0x6d, 0x21, 0x60, 0x96, 0x60, 0x5b, 0xfd, 0x75, 0xeb, 0x63, 0x69, - 0xeb, 0x11, 0xe1, 0xb8, 0x01, 0x4f, 0x06, 0xb5, 0x5c, 0x34, 0xa8, 0x81, 0x44, 0x86, 0x46, 0x56, - 0xe1, 0x3e, 0x28, 0xb0, 0x80, 0x38, 0x86, 0x2e, 0xad, 0xdf, 0xb6, 0xa6, 0x94, 0xd9, 0xca, 0x88, - 0x70, 0x37, 0x20, 0x4e, 0xe3, 0x8a, 0xf2, 0x50, 0x10, 0x7f, 0x48, 0xda, 0x83, 0x9f, 0x83, 0x19, - 0xc6, 0x31, 0x0f, 0x99, 0x91, 0x97, 0x96, 0xef, 0x5e, 0xda, 0xb2, 0xd4, 0x6e, 0xbc, 0xa1, 0x6c, - 0xcf, 0xc4, 0xff, 0x48, 0x59, 0x35, 0x3f, 0x05, 0x4b, 0x8f, 0x7d, 0x0f, 0x11, 0xe6, 0x87, 0xd4, - 0x21, 0x5b, 0x9c, 0x53, 0xb7, 0x19, 0x72, 0xc2, 0xe0, 0x2a, 0x28, 0x04, 0x98, 0x77, 0x64, 0xba, - 0xca, 0x49, 0x68, 0x4f, 0x30, 0xef, 0x20, 0x89, 0x08, 0x46, 0x9f, 0xd0, 0xa6, 0x3c, 0x72, 0x8a, - 0xb1, 0x4f, 0x68, 0x13, 0x49, 0xc4, 0xfc, 0x0a, 0xcc, 0xa7, 0x8c, 0xa3, 0xb0, 0x2b, 0x2b, 0x2a, - 0xa0, 0xb1, 0x8a, 0x0a, 0x0d, 0x86, 0x62, 0x39, 0xbc, 0x0f, 0xe6, 0xbd, 0x44, 0x67, 0x0f, 0x3d, - 0x64, 0x86, 0x2e, 0xa9, 0x8b, 0xd1, 0xa0, 0x96, 0x36, 0x27, 0x20, 0x74, 0x96, 0x6b, 0xfe, 0xa6, - 0x03, 0x98, 0x71, 0x1a, 0x1b, 0x94, 0x3d, 0xdc, 0x23, 0x2c, 0xc0, 0x0e, 0x51, 0x47, 0xba, 0xaa, - 0x02, 0x2e, 0x3f, 0x1e, 0x02, 0x28, 0xe1, 0xbc, 0xfc, 0x70, 0xf0, 0x2d, 0x50, 0x6c, 0x53, 0x3f, - 0x0c, 0x64, 0x61, 0xca, 0x8d, 0x39, 0x45, 0x29, 0x7e, 0x24, 0x84, 0x28, 0xc6, 0xe0, 0x4d, 0x30, - 0xdb, 0x27, 0x94, 0xb9, 0xbe, 0x67, 0x14, 0x24, 0x6d, 0x5e, 0xd1, 0x66, 0xf7, 0x63, 0x31, 0x1a, - 0xe2, 0xf0, 0x16, 0x28, 0x51, 0x15, 0xb8, 0x51, 0x94, 0xdc, 0x05, 0xc5, 0x2d, 0x8d, 0x32, 0x38, - 0x62, 0xc0, 0x3b, 0xa0, 0xc2, 0xc2, 0xe6, 0x48, 0x61, 0x46, 0x2a, 0x2c, 0x2a, 0x85, 0xca, 0x6e, - 0x02, 0xa1, 0x34, 0x4f, 0x1c, 0x4b, 0x9c, 0xd1, 0x98, 0x1d, 0x3f, 0x96, 0x48, 0x01, 0x92, 0x88, - 0xf9, 0xbb, 0x06, 0xae, 0x5c, 0xae, 0x62, 0xef, 0x82, 0x32, 0x0e, 0x5c, 0x79, 0xec, 0x61, 0xad, - 0xe6, 0x44, 0x5e, 0xb7, 0x9e, 0xec, 0xc4, 0x42, 0x94, 0xe0, 0x82, 0x3c, 0x0c, 0x46, 0xb4, 0xf4, - 0x88, 0x3c, 0x74, 0xc9, 0x50, 0x82, 0xc3, 0x0d, 0x30, 0x37, 0xfc, 0x91, 0x45, 0x32, 0x0a, 0x52, - 0xe1, 0x6a, 0x34, 0xa8, 0xcd, 0xa1, 0x34, 0x80, 0xc6, 0x79, 0xe6, 0xaf, 0x3a, 0x58, 0xde, 0x25, - 0xdd, 0x83, 0x57, 0x33, 0x0b, 0x9e, 0x8e, 0xcd, 0x82, 0xcd, 0xe9, 0x37, 0x36, 0x3b, 0xca, 0x57, - 0x36, 0x0f, 0x7e, 0xd0, 0xc1, 0x8d, 0x29, 0x31, 0xc1, 0x23, 0x00, 0xe9, 0xb9, 0xeb, 0xa5, 0xf2, - 0x68, 0x4f, 0x8d, 0xe5, 0xfc, 0xad, 0x6c, 0x5c, 0x8b, 0x06, 0xb5, 0x8c, 0xdb, 0x8a, 0x32, 0x5c, - 0xc0, 0x6f, 0x35, 0xb0, 0xe4, 0x65, 0x4d, 0x2a, 0x95, 0xe6, 0xfa, 0x54, 0xe7, 0x99, 0x33, 0xae, - 0x71, 0x3d, 0x1a, 0xd4, 0xb2, 0xc7, 0x1f, 0xca, 0xf6, 0x25, 0x5e, 0x99, 0x6b, 0xa9, 0xf4, 0x88, - 0x0b, 0xf2, 0xff, 0xf5, 0xd5, 0x27, 0x63, 0x7d, 0xb5, 0x71, 0xd1, 0xbe, 0x4a, 0x05, 0x39, 0xb1, - 0xad, 0x3e, 0x3b, 0xd3, 0x56, 0x77, 0x2e, 0xd2, 0x56, 0x69, 0xc3, 0xd3, 0xbb, 0xea, 0x11, 0x58, - 0x99, 0x1c, 0xd0, 0xa5, 0x87, 0xb3, 0xf9, 0x93, 0x0e, 0x16, 0x5f, 0x3f, 0xf3, 0x97, 0xb9, 0xd6, - 0x7f, 0x14, 0xc0, 0xf2, 0xeb, 0x2b, 0x3d, 0x69, 0xd1, 0x09, 0x19, 0xa1, 0xea, 0x19, 0x1f, 0x15, - 0x67, 0x8f, 0x11, 0x8a, 0x24, 0x02, 0x4d, 0x30, 0xd3, 0x8e, 0x5f, 0xb7, 0xf8, 0xfd, 0x01, 0x22, - 0xc1, 0xea, 0x69, 0x53, 0x08, 0x6c, 0x81, 0x22, 0x11, 0x7b, 0xab, 0x51, 0x5c, 0xcd, 0xaf, 0x55, - 0xea, 0x1f, 0xfe, 0x97, 0xce, 0xb0, 0xe4, 0xe6, 0xbb, 0xed, 0x71, 0x7a, 0x9c, 0xac, 0x13, 0x52, - 0x86, 0x62, 0xe3, 0xf0, 0x4d, 0x90, 0x0f, 0xdd, 0x96, 0x7a, 0xed, 0x2b, 0x8a, 0x92, 0xdf, 0xdb, - 0x79, 0x80, 0x84, 0x7c, 0x05, 0xab, 0xe5, 0x59, 0x9a, 0x80, 0x0b, 0x20, 0x7f, 0x48, 0x8e, 0xe3, - 0x0b, 0x85, 0xc4, 0x27, 0xbc, 0x0f, 0x8a, 0x7d, 0xb1, 0x57, 0xab, 0xfc, 0xbe, 0x33, 0x35, 0xc8, - 0x64, 0x0d, 0x47, 0xb1, 0xd6, 0x3d, 0x7d, 0x53, 0x33, 0x7f, 0xd6, 0xc0, 0xf5, 0x89, 0xed, 0x27, - 0xd6, 0x1d, 0xdc, 0xed, 0xfa, 0x47, 0xa4, 0x25, 0xdd, 0x96, 0x92, 0x75, 0x67, 0x2b, 0x16, 0xa3, - 0x21, 0x0e, 0xdf, 0x06, 0x33, 0x94, 0x60, 0xe6, 0x7b, 0x6a, 0xc5, 0x1a, 0x75, 0x2e, 0x92, 0x52, - 0xa4, 0x50, 0xb8, 0x05, 0xe6, 0x89, 0x70, 0x2f, 0xe3, 0xda, 0xa6, 0xd4, 0x1f, 0x56, 0x6a, 0x59, - 0x29, 0xcc, 0x6f, 0x8f, 0xc3, 0xe8, 0x2c, 0xdf, 0xfc, 0x47, 0x07, 0xc6, 0xa4, 0x91, 0x05, 0x0f, - 0x92, 0x1d, 0x43, 0x82, 0x72, 0xcd, 0xa9, 0xd4, 0x6f, 0x5e, 0xa8, 0xf1, 0x85, 0x46, 0x63, 0x49, - 0x05, 0x32, 0x97, 0x96, 0xa6, 0x56, 0x12, 0xf9, 0x0b, 0x29, 0x58, 0xf0, 0xc6, 0x77, 0xe1, 0x78, - 0x59, 0xaa, 0xd4, 0x6f, 0x5d, 0xb4, 0xcd, 0xa5, 0x37, 0x43, 0x79, 0x5b, 0x38, 0x03, 0x30, 0x74, - 0xce, 0x3e, 0xac, 0x03, 0xe0, 0x7a, 0x8e, 0xdf, 0x0b, 0xba, 0x84, 0x13, 0x99, 0xb6, 0x52, 0x32, - 0xdf, 0x76, 0x46, 0x08, 0x4a, 0xb1, 0xb2, 0xf2, 0x5d, 0xb8, 0x5c, 0xbe, 0x1b, 0x6b, 0x27, 0xa7, - 0xd5, 0xdc, 0xf3, 0xd3, 0x6a, 0xee, 0xc5, 0x69, 0x35, 0xf7, 0x2c, 0xaa, 0x6a, 0x27, 0x51, 0x55, - 0x7b, 0x1e, 0x55, 0xb5, 0x17, 0x51, 0x55, 0xfb, 0x2b, 0xaa, 0x6a, 0xdf, 0xfd, 0x5d, 0xcd, 0x3d, - 0xd5, 0xfb, 0xeb, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0xd9, 0x3f, 0xd9, 0x21, 0x54, 0x0f, 0x00, - 0x00, + 0x55, 0xe2, 0x0b, 0x70, 0xe4, 0xc0, 0x81, 0x6f, 0xc0, 0x05, 0x89, 0x1b, 0x07, 0x0e, 0x28, 0xc7, + 0x1e, 0x8b, 0x84, 0x2c, 0xb2, 0x9c, 0xf9, 0x0e, 0x68, 0x66, 0xc7, 0xde, 0x75, 0xb2, 0x76, 0x13, + 0x0e, 0xf4, 0xd2, 0xdb, 0xee, 0xfb, 0xfd, 0xde, 0x9f, 0x79, 0x7f, 0x66, 0x1e, 0xd8, 0x3a, 0xdc, + 0x60, 0x96, 0xeb, 0xdb, 0x87, 0x61, 0x93, 0x50, 0x8f, 0x70, 0xc2, 0xec, 0x3e, 0xf1, 0x5a, 0x3e, + 0xb5, 0x15, 0x80, 0x03, 0xd7, 0xc6, 0x21, 0xef, 0xf8, 0xd4, 0xfd, 0x06, 0x73, 0xd7, 0xf7, 0xec, + 0xfe, 0x9a, 0xdd, 0x26, 0x1e, 0xa1, 0x98, 0x93, 0x96, 0x15, 0x50, 0x9f, 0xfb, 0xf0, 0x66, 0x4c, + 0xb6, 0x70, 0xe0, 0x5a, 0x63, 0x64, 0xab, 0xbf, 0xb6, 0xfc, 0x5e, 0xdb, 0xe5, 0x9d, 0xb0, 0x69, + 0x39, 0x7e, 0xcf, 0x6e, 0xfb, 0x6d, 0xdf, 0x96, 0x3a, 0xcd, 0xf0, 0x40, 0xfe, 0xc9, 0x1f, 0xf9, + 0x15, 0xdb, 0x5a, 0xbe, 0x93, 0x38, 0xee, 0x61, 0xa7, 0xe3, 0x7a, 0x84, 0x1e, 0xdb, 0xc1, 0x61, + 0x5b, 0x08, 0x98, 0xdd, 0x23, 0x1c, 0x67, 0x44, 0xb0, 0x6c, 0x4f, 0xd2, 0xa2, 0xa1, 0xc7, 0xdd, + 0x1e, 0x39, 0xa7, 0x70, 0xef, 0x65, 0x0a, 0xcc, 0xe9, 0x90, 0x1e, 0x3e, 0xa7, 0xf7, 0xc1, 0x24, + 0xbd, 0x90, 0xbb, 0x5d, 0xdb, 0xf5, 0x38, 0xe3, 0xf4, 0xac, 0x92, 0xb9, 0x0e, 0xc0, 0xf6, 0xd7, + 0x9c, 0xe2, 0x7d, 0xdc, 0x0d, 0x09, 0xac, 0x81, 0xa2, 0xcb, 0x49, 0x8f, 0x19, 0xda, 0x4a, 0x7e, + 0xb5, 0xdc, 0x28, 0x47, 0x83, 0x5a, 0x71, 0x47, 0x08, 0x50, 0x2c, 0xbf, 0x5f, 0xfa, 0xfe, 0xc7, + 0x5a, 0xee, 0xd9, 0x9f, 0x2b, 0x39, 0xf3, 0x67, 0x1d, 0x18, 0x0f, 0x7d, 0x07, 0x77, 0x77, 0xc3, + 0xe6, 0x97, 0xc4, 0xe1, 0x9b, 0x8e, 0x43, 0x18, 0x43, 0xa4, 0xef, 0x92, 0x23, 0xf8, 0x05, 0x28, + 0x89, 0x74, 0xb4, 0x30, 0xc7, 0x86, 0xb6, 0xa2, 0xad, 0x56, 0xea, 0xef, 0x5b, 0x49, 0x21, 0x46, + 0xd1, 0x59, 0xc1, 0x61, 0x5b, 0x08, 0x98, 0x25, 0xd8, 0x56, 0x7f, 0xcd, 0xfa, 0x58, 0xda, 0x7a, + 0x44, 0x38, 0x6e, 0xc0, 0x93, 0x41, 0x2d, 0x17, 0x0d, 0x6a, 0x20, 0x91, 0xa1, 0x91, 0x55, 0xb8, + 0x0f, 0x0a, 0x2c, 0x20, 0x8e, 0xa1, 0x4b, 0xeb, 0x77, 0xac, 0x29, 0x65, 0xb6, 0x32, 0x22, 0xdc, + 0x0d, 0x88, 0xd3, 0xb8, 0xaa, 0x3c, 0x14, 0xc4, 0x1f, 0x92, 0xf6, 0xe0, 0xe7, 0x60, 0x86, 0x71, + 0xcc, 0x43, 0x66, 0xe4, 0xa5, 0xe5, 0x7b, 0x97, 0xb6, 0x2c, 0xb5, 0x1b, 0x6f, 0x28, 0xdb, 0x33, + 0xf1, 0x3f, 0x52, 0x56, 0xcd, 0x4f, 0xc1, 0xe2, 0x63, 0xdf, 0x43, 0x84, 0xf9, 0x21, 0x75, 0xc8, + 0x26, 0xe7, 0xd4, 0x6d, 0x86, 0x9c, 0x30, 0xb8, 0x02, 0x0a, 0x01, 0xe6, 0x1d, 0x99, 0xae, 0x72, + 0x12, 0xda, 0x13, 0xcc, 0x3b, 0x48, 0x22, 0x82, 0xd1, 0x27, 0xb4, 0x29, 0x8f, 0x9c, 0x62, 0xec, + 0x13, 0xda, 0x44, 0x12, 0x31, 0xbf, 0x02, 0x73, 0x29, 0xe3, 0x28, 0xec, 0xca, 0x8a, 0x0a, 0x68, + 0xac, 0xa2, 0x42, 0x83, 0xa1, 0x58, 0x0e, 0x1f, 0x80, 0x39, 0x2f, 0xd1, 0xd9, 0x43, 0x0f, 0x99, + 0xa1, 0x4b, 0xea, 0x42, 0x34, 0xa8, 0xa5, 0xcd, 0x09, 0x08, 0x9d, 0xe5, 0x9a, 0xbf, 0xea, 0x00, + 0x66, 0x9c, 0xc6, 0x06, 0x65, 0x0f, 0xf7, 0x08, 0x0b, 0xb0, 0x43, 0xd4, 0x91, 0xae, 0xa9, 0x80, + 0xcb, 0x8f, 0x87, 0x00, 0x4a, 0x38, 0x2f, 0x3f, 0x1c, 0x7c, 0x0b, 0x14, 0xdb, 0xd4, 0x0f, 0x03, + 0x59, 0x98, 0x72, 0x63, 0x56, 0x51, 0x8a, 0x1f, 0x09, 0x21, 0x8a, 0x31, 0x78, 0x0b, 0x5c, 0xe9, + 0x13, 0xca, 0x5c, 0xdf, 0x33, 0x0a, 0x92, 0x36, 0xa7, 0x68, 0x57, 0xf6, 0x63, 0x31, 0x1a, 0xe2, + 0xf0, 0x36, 0x28, 0x51, 0x15, 0xb8, 0x51, 0x94, 0xdc, 0x79, 0xc5, 0x2d, 0x8d, 0x32, 0x38, 0x62, + 0xc0, 0xbb, 0xa0, 0xc2, 0xc2, 0xe6, 0x48, 0x61, 0x46, 0x2a, 0x2c, 0x28, 0x85, 0xca, 0x6e, 0x02, + 0xa1, 0x34, 0x4f, 0x1c, 0x4b, 0x9c, 0xd1, 0xb8, 0x32, 0x7e, 0x2c, 0x91, 0x02, 0x24, 0x11, 0xf3, + 0x37, 0x0d, 0x5c, 0xbd, 0x5c, 0xc5, 0xde, 0x05, 0x65, 0x1c, 0xb8, 0xf2, 0xd8, 0xc3, 0x5a, 0xcd, + 0x8a, 0xbc, 0x6e, 0x3e, 0xd9, 0x89, 0x85, 0x28, 0xc1, 0x05, 0x79, 0x18, 0x8c, 0x68, 0xe9, 0x11, + 0x79, 0xe8, 0x92, 0xa1, 0x04, 0x87, 0xeb, 0x60, 0x76, 0xf8, 0x23, 0x8b, 0x64, 0x14, 0xa4, 0xc2, + 0xb5, 0x68, 0x50, 0x9b, 0x45, 0x69, 0x00, 0x8d, 0xf3, 0xcc, 0x5f, 0x74, 0xb0, 0xb4, 0x4b, 0xba, + 0x07, 0xaf, 0xe6, 0x2e, 0x78, 0x3a, 0x76, 0x17, 0x6c, 0x4c, 0x9f, 0xd8, 0xec, 0x28, 0x5f, 0xd9, + 0x7d, 0xf0, 0x83, 0x0e, 0x6e, 0x4e, 0x89, 0x09, 0x1e, 0x01, 0x48, 0xcf, 0x8d, 0x97, 0xca, 0xa3, + 0x3d, 0x35, 0x96, 0xf3, 0x53, 0xd9, 0xb8, 0x1e, 0x0d, 0x6a, 0x19, 0xd3, 0x8a, 0x32, 0x5c, 0xc0, + 0x6f, 0x35, 0xb0, 0xe8, 0x65, 0xdd, 0x54, 0x2a, 0xcd, 0xf5, 0xa9, 0xce, 0x33, 0xef, 0xb8, 0xc6, + 0x8d, 0x68, 0x50, 0xcb, 0xbe, 0xfe, 0x50, 0xb6, 0x2f, 0xf1, 0xca, 0x5c, 0x4f, 0xa5, 0x47, 0x0c, + 0xc8, 0xff, 0xd7, 0x57, 0x9f, 0x8c, 0xf5, 0xd5, 0xfa, 0x45, 0xfb, 0x2a, 0x15, 0xe4, 0xc4, 0xb6, + 0xfa, 0xec, 0x4c, 0x5b, 0xdd, 0xbd, 0x48, 0x5b, 0xa5, 0x0d, 0x4f, 0xef, 0xaa, 0x47, 0x60, 0x79, + 0x72, 0x40, 0x97, 0xbe, 0x9c, 0xcd, 0x9f, 0x74, 0xb0, 0xf0, 0xfa, 0x99, 0xbf, 0xcc, 0x58, 0xff, + 0x5e, 0x00, 0x4b, 0xaf, 0x47, 0x7a, 0xd2, 0xa2, 0x13, 0x32, 0x42, 0xd5, 0x33, 0x3e, 0x2a, 0xce, + 0x1e, 0x23, 0x14, 0x49, 0x04, 0x9a, 0x60, 0xa6, 0x1d, 0xbf, 0x6e, 0xf1, 0xfb, 0x03, 0x44, 0x82, + 0xd5, 0xd3, 0xa6, 0x10, 0xd8, 0x02, 0x45, 0x22, 0xf6, 0x56, 0xa3, 0xb8, 0x92, 0x5f, 0xad, 0xd4, + 0x3f, 0xfc, 0x2f, 0x9d, 0x61, 0xc9, 0xcd, 0x77, 0xdb, 0xe3, 0xf4, 0x38, 0x59, 0x27, 0xa4, 0x0c, + 0xc5, 0xc6, 0xe1, 0x9b, 0x20, 0x1f, 0xba, 0x2d, 0xf5, 0xda, 0x57, 0x14, 0x25, 0xbf, 0xb7, 0xb3, + 0x85, 0x84, 0x7c, 0x19, 0xab, 0xe5, 0x59, 0x9a, 0x80, 0xf3, 0x20, 0x7f, 0x48, 0x8e, 0xe3, 0x81, + 0x42, 0xe2, 0x13, 0x3e, 0x00, 0xc5, 0xbe, 0xd8, 0xab, 0x55, 0x7e, 0xdf, 0x99, 0x1a, 0x64, 0xb2, + 0x86, 0xa3, 0x58, 0xeb, 0xbe, 0xbe, 0xa1, 0x99, 0x7f, 0x68, 0xe0, 0xc6, 0xc4, 0xf6, 0x13, 0xeb, + 0x0e, 0xee, 0x76, 0xfd, 0x23, 0xd2, 0x92, 0x6e, 0x4b, 0xc9, 0xba, 0xb3, 0x19, 0x8b, 0xd1, 0x10, + 0x87, 0x6f, 0x83, 0x19, 0x4a, 0x30, 0xf3, 0x3d, 0xb5, 0x62, 0x8d, 0x3a, 0x17, 0x49, 0x29, 0x52, + 0x28, 0xdc, 0x04, 0x73, 0x44, 0xb8, 0x97, 0x71, 0x6d, 0x53, 0xea, 0x0f, 0x2b, 0xb5, 0xa4, 0x14, + 0xe6, 0xb6, 0xc7, 0x61, 0x74, 0x96, 0x2f, 0x5c, 0xb5, 0x88, 0xe7, 0x92, 0x96, 0xdc, 0xc1, 0x4a, + 0x89, 0xab, 0x2d, 0x29, 0x45, 0x0a, 0x35, 0xff, 0xd1, 0x81, 0x31, 0xe9, 0x6a, 0x83, 0x07, 0xc9, + 0x2e, 0x22, 0x41, 0xb9, 0x0e, 0x55, 0xea, 0xb7, 0x2e, 0x34, 0x20, 0x42, 0xa3, 0xb1, 0xa8, 0xdc, + 0xce, 0xa6, 0xa5, 0xa9, 0xd5, 0x45, 0xfe, 0x42, 0x0a, 0xe6, 0xbd, 0xf1, 0x9d, 0x39, 0x5e, 0xaa, + 0x2a, 0xf5, 0xdb, 0x17, 0x1d, 0x07, 0xe9, 0xcd, 0x50, 0xde, 0xe6, 0xcf, 0x00, 0x0c, 0x9d, 0xb3, + 0x0f, 0xeb, 0x00, 0xb8, 0x9e, 0xe3, 0xf7, 0x82, 0x2e, 0xe1, 0x44, 0xa6, 0xb7, 0x94, 0xdc, 0x83, + 0x3b, 0x23, 0x04, 0xa5, 0x58, 0x59, 0x75, 0x29, 0x5c, 0xae, 0x2e, 0x8d, 0xd5, 0x93, 0xd3, 0x6a, + 0xee, 0xf9, 0x69, 0x35, 0xf7, 0xe2, 0xb4, 0x9a, 0x7b, 0x16, 0x55, 0xb5, 0x93, 0xa8, 0xaa, 0x3d, + 0x8f, 0xaa, 0xda, 0x8b, 0xa8, 0xaa, 0xfd, 0x15, 0x55, 0xb5, 0xef, 0xfe, 0xae, 0xe6, 0x9e, 0xea, + 0xfd, 0xb5, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0xd6, 0x0e, 0xab, 0x82, 0x7c, 0x0f, 0x00, 0x00, } diff --git a/staging/src/k8s.io/api/authorization/v1/generated.proto b/staging/src/k8s.io/api/authorization/v1/generated.proto index 7f31d599a81..2cd4af0c077 100644 --- a/staging/src/k8s.io/api/authorization/v1/generated.proto +++ b/staging/src/k8s.io/api/authorization/v1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -121,7 +121,8 @@ message ResourceRule { // +optional repeated string apiGroups = 2; - // Resources is a list of resources this rule applies to. ResourceAll represents all resources. "*" means all. + // 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 repeated string resources = 3; @@ -225,9 +226,16 @@ message SubjectAccessReviewSpec { // 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 + // 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. // +optional optional string reason = 2; diff --git a/staging/src/k8s.io/api/authorization/v1/register.go b/staging/src/k8s.io/api/authorization/v1/register.go index d716eaa98da..59311981e12 100644 --- a/staging/src/k8s.io/api/authorization/v1/register.go +++ b/staging/src/k8s.io/api/authorization/v1/register.go @@ -41,7 +41,7 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &SelfSubjectRulesReview{}, diff --git a/staging/src/k8s.io/api/authorization/v1/types.go b/staging/src/k8s.io/api/authorization/v1/types.go index 99ec3bcbf7f..86b05c54e37 100644 --- a/staging/src/k8s.io/api/authorization/v1/types.go +++ b/staging/src/k8s.io/api/authorization/v1/types.go @@ -169,8 +169,14 @@ type SelfSubjectAccessReviewSpec struct { // 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 + // 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. // +optional Reason string `json:"reason,omitempty" protobuf:"bytes,2,opt,name=reason"` @@ -241,7 +247,8 @@ type ResourceRule struct { // the enumerated resources in any API group will be allowed. "*" means all. // +optional APIGroups []string `json:"apiGroups,omitempty" protobuf:"bytes,2,rep,name=apiGroups"` - // Resources is a list of resources this rule applies to. ResourceAll represents all resources. "*" means all. + // 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 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. 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 8a0fb8a857b..85503660c58 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 @@ -76,7 +76,7 @@ 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. ResourceAll represents all resources. \"*\" 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.", } @@ -148,7 +148,8 @@ 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.", + "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.", } diff --git a/staging/src/k8s.io/api/authorization/v1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/authorization/v1/zz_generated.deepcopy.go index 916974ffcf8..06a78643fce 100644 --- a/staging/src/k8s.io/api/authorization/v1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/authorization/v1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,76 +21,9 @@ limitations under the License. package v1 import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LocalSubjectAccessReview).DeepCopyInto(out.(*LocalSubjectAccessReview)) - return nil - }, InType: reflect.TypeOf(&LocalSubjectAccessReview{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NonResourceAttributes).DeepCopyInto(out.(*NonResourceAttributes)) - return nil - }, InType: reflect.TypeOf(&NonResourceAttributes{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NonResourceRule).DeepCopyInto(out.(*NonResourceRule)) - return nil - }, InType: reflect.TypeOf(&NonResourceRule{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ResourceAttributes).DeepCopyInto(out.(*ResourceAttributes)) - return nil - }, InType: reflect.TypeOf(&ResourceAttributes{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ResourceRule).DeepCopyInto(out.(*ResourceRule)) - return nil - }, InType: reflect.TypeOf(&ResourceRule{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SelfSubjectAccessReview).DeepCopyInto(out.(*SelfSubjectAccessReview)) - return nil - }, InType: reflect.TypeOf(&SelfSubjectAccessReview{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SelfSubjectAccessReviewSpec).DeepCopyInto(out.(*SelfSubjectAccessReviewSpec)) - return nil - }, InType: reflect.TypeOf(&SelfSubjectAccessReviewSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SelfSubjectRulesReview).DeepCopyInto(out.(*SelfSubjectRulesReview)) - return nil - }, InType: reflect.TypeOf(&SelfSubjectRulesReview{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SelfSubjectRulesReviewSpec).DeepCopyInto(out.(*SelfSubjectRulesReviewSpec)) - return nil - }, InType: reflect.TypeOf(&SelfSubjectRulesReviewSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SubjectAccessReview).DeepCopyInto(out.(*SubjectAccessReview)) - return nil - }, InType: reflect.TypeOf(&SubjectAccessReview{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SubjectAccessReviewSpec).DeepCopyInto(out.(*SubjectAccessReviewSpec)) - return nil - }, InType: reflect.TypeOf(&SubjectAccessReviewSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SubjectAccessReviewStatus).DeepCopyInto(out.(*SubjectAccessReviewStatus)) - return nil - }, InType: reflect.TypeOf(&SubjectAccessReviewStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SubjectRulesReviewStatus).DeepCopyInto(out.(*SubjectRulesReviewStatus)) - return nil - }, InType: reflect.TypeOf(&SubjectRulesReviewStatus{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LocalSubjectAccessReview) DeepCopyInto(out *LocalSubjectAccessReview) { *out = *in diff --git a/staging/src/k8s.io/api/authorization/v1beta1/BUILD b/staging/src/k8s.io/api/authorization/v1beta1/BUILD index 9391dcc585f..06c953f2a90 100644 --- a/staging/src/k8s.io/api/authorization/v1beta1/BUILD +++ b/staging/src/k8s.io/api/authorization/v1beta1/BUILD @@ -20,7 +20,6 @@ go_library( "//vendor/github.com/gogo/protobuf/proto:go_default_library", "//vendor/github.com/gogo/protobuf/sortkeys:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/staging/src/k8s.io/api/authorization/v1beta1/doc.go b/staging/src/k8s.io/api/authorization/v1beta1/doc.go index 6809b811e7b..ea4f802e289 100644 --- a/staging/src/k8s.io/api/authorization/v1beta1/doc.go +++ b/staging/src/k8s.io/api/authorization/v1beta1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:openapi-gen=true // +groupName=authorization.k8s.io diff --git a/staging/src/k8s.io/api/authorization/v1beta1/generated.pb.go b/staging/src/k8s.io/api/authorization/v1beta1/generated.pb.go index e1f49d46467..a9a8116b3af 100644 --- a/staging/src/k8s.io/api/authorization/v1beta1/generated.pb.go +++ b/staging/src/k8s.io/api/authorization/v1beta1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -730,6 +730,14 @@ func (m *SubjectAccessReviewStatus) MarshalTo(dAtA []byte) (int, error) { i++ i = encodeVarintGenerated(dAtA, i, uint64(len(m.EvaluationError))) i += copy(dAtA[i:], m.EvaluationError) + dAtA[i] = 0x20 + i++ + if m.Denied { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ return i, nil } @@ -1015,6 +1023,7 @@ func (m *SubjectAccessReviewStatus) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) l = len(m.EvaluationError) n += 1 + l + sovGenerated(uint64(l)) + n += 2 return n } @@ -1205,6 +1214,7 @@ func (this *SubjectAccessReviewStatus) String() string { `Allowed:` + fmt.Sprintf("%v", this.Allowed) + `,`, `Reason:` + fmt.Sprintf("%v", this.Reason) + `,`, `EvaluationError:` + fmt.Sprintf("%v", this.EvaluationError) + `,`, + `Denied:` + fmt.Sprintf("%v", this.Denied) + `,`, `}`, }, "") return s @@ -3130,6 +3140,26 @@ func (m *SubjectAccessReviewStatus) Unmarshal(dAtA []byte) error { } m.EvaluationError = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Denied", 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.Denied = bool(v != 0) default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -3422,77 +3452,78 @@ func init() { } var fileDescriptorGenerated = []byte{ - // 1139 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x56, 0xcf, 0x6f, 0x1b, 0x45, - 0x14, 0xf6, 0xfa, 0x47, 0x62, 0x8f, 0x1b, 0x92, 0x4e, 0x94, 0x66, 0x1b, 0x84, 0x6d, 0x19, 0x09, - 0x05, 0xd1, 0xee, 0x92, 0x50, 0x48, 0x09, 0xf4, 0x10, 0xab, 0x11, 0x8a, 0xd4, 0x96, 0x6a, 0xa2, - 0xe4, 0x40, 0x25, 0x60, 0x76, 0x33, 0xb1, 0x17, 0xdb, 0xbb, 0xcb, 0xcc, 0xac, 0x43, 0x10, 0x87, - 0x1e, 0x39, 0x72, 0xe4, 0xc8, 0x89, 0x3b, 0x47, 0x2e, 0x48, 0x70, 0xca, 0xb1, 0xc7, 0x1c, 0x90, - 0x45, 0x96, 0x3f, 0x82, 0x2b, 0x9a, 0xd9, 0xb1, 0x77, 0x1d, 0xaf, 0xe3, 0x24, 0x87, 0xf6, 0xd2, - 0xdb, 0xce, 0xfb, 0xde, 0xf7, 0xde, 0x9b, 0x37, 0xef, 0xbd, 0x7d, 0x60, 0xa7, 0x7d, 0x9f, 0x19, - 0x8e, 0x67, 0xb6, 0x03, 0x8b, 0x50, 0x97, 0x70, 0xc2, 0xcc, 0x1e, 0x71, 0x0f, 0x3c, 0x6a, 0x2a, - 0x00, 0xfb, 0x8e, 0x89, 0x03, 0xde, 0xf2, 0xa8, 0xf3, 0x3d, 0xe6, 0x8e, 0xe7, 0x9a, 0xbd, 0x35, - 0x8b, 0x70, 0xbc, 0x66, 0x36, 0x89, 0x4b, 0x28, 0xe6, 0xe4, 0xc0, 0xf0, 0xa9, 0xc7, 0x3d, 0x58, - 0x8b, 0x18, 0x06, 0xf6, 0x1d, 0x63, 0x84, 0x61, 0x28, 0xc6, 0xca, 0xdd, 0xa6, 0xc3, 0x5b, 0x81, - 0x65, 0xd8, 0x5e, 0xd7, 0x6c, 0x7a, 0x4d, 0xcf, 0x94, 0x44, 0x2b, 0x38, 0x94, 0x27, 0x79, 0x90, - 0x5f, 0x91, 0xc1, 0x95, 0x7b, 0x71, 0x08, 0x5d, 0x6c, 0xb7, 0x1c, 0x97, 0xd0, 0x63, 0xd3, 0x6f, - 0x37, 0x85, 0x80, 0x99, 0x5d, 0xc2, 0xb1, 0xd9, 0x1b, 0x0b, 0x63, 0xc5, 0x9c, 0xc4, 0xa2, 0x81, - 0xcb, 0x9d, 0x2e, 0x19, 0x23, 0x7c, 0x34, 0x8d, 0xc0, 0xec, 0x16, 0xe9, 0xe2, 0x31, 0xde, 0x07, - 0x93, 0x78, 0x01, 0x77, 0x3a, 0xa6, 0xe3, 0x72, 0xc6, 0xe9, 0x79, 0x52, 0x7d, 0x03, 0x80, 0xed, - 0xef, 0x38, 0xc5, 0xfb, 0xb8, 0x13, 0x10, 0x58, 0x05, 0x05, 0x87, 0x93, 0x2e, 0xd3, 0xb5, 0x5a, - 0x6e, 0xb5, 0xd4, 0x28, 0x85, 0xfd, 0x6a, 0x61, 0x47, 0x08, 0x50, 0x24, 0xdf, 0x2c, 0xfe, 0xfc, - 0x4b, 0x35, 0xf3, 0xfc, 0xef, 0x5a, 0xa6, 0xfe, 0x47, 0x16, 0xe8, 0x8f, 0x3c, 0x1b, 0x77, 0x76, - 0x03, 0xeb, 0x1b, 0x62, 0xf3, 0x2d, 0xdb, 0x26, 0x8c, 0x21, 0xd2, 0x73, 0xc8, 0x11, 0xfc, 0x1a, - 0x14, 0x45, 0x3a, 0x0e, 0x30, 0xc7, 0xba, 0x56, 0xd3, 0x56, 0xcb, 0xeb, 0xef, 0x1b, 0xf1, 0x6b, - 0x0c, 0xa3, 0x33, 0xfc, 0x76, 0x53, 0x08, 0x98, 0x21, 0xb4, 0x8d, 0xde, 0x9a, 0xf1, 0xb9, 0xb4, - 0xf5, 0x98, 0x70, 0xdc, 0x80, 0x27, 0xfd, 0x6a, 0x26, 0xec, 0x57, 0x41, 0x2c, 0x43, 0x43, 0xab, - 0xf0, 0x19, 0xc8, 0x33, 0x9f, 0xd8, 0x7a, 0x56, 0x5a, 0xff, 0xd8, 0x98, 0xf6, 0xd6, 0x46, 0x4a, - 0x98, 0xbb, 0x3e, 0xb1, 0x1b, 0x37, 0x94, 0x9b, 0xbc, 0x38, 0x21, 0x69, 0x14, 0xda, 0x60, 0x86, - 0x71, 0xcc, 0x03, 0xa6, 0xe7, 0xa4, 0xf9, 0x4f, 0xae, 0x67, 0x5e, 0x9a, 0x68, 0xbc, 0xa1, 0x1c, - 0xcc, 0x44, 0x67, 0xa4, 0x4c, 0xd7, 0x9f, 0x81, 0xa5, 0x27, 0x9e, 0x8b, 0x08, 0xf3, 0x02, 0x6a, - 0x93, 0x2d, 0xce, 0xa9, 0x63, 0x05, 0x9c, 0x30, 0x58, 0x03, 0x79, 0x1f, 0xf3, 0x96, 0x4c, 0x5c, - 0x29, 0x8e, 0xef, 0x29, 0xe6, 0x2d, 0x24, 0x11, 0xa1, 0xd1, 0x23, 0xd4, 0x92, 0x97, 0x4f, 0x68, - 0xec, 0x13, 0x6a, 0x21, 0x89, 0xd4, 0xbf, 0x05, 0xf3, 0x09, 0xe3, 0x28, 0xe8, 0xc8, 0xb7, 0x15, - 0xd0, 0xc8, 0xdb, 0x0a, 0x06, 0x43, 0x91, 0x1c, 0x3e, 0x00, 0xf3, 0x6e, 0xcc, 0xd9, 0x43, 0x8f, - 0x98, 0x9e, 0x95, 0xaa, 0x8b, 0x61, 0xbf, 0x9a, 0x34, 0x27, 0x20, 0x74, 0x5e, 0x57, 0x14, 0x04, - 0x4c, 0xb9, 0x8d, 0x09, 0x4a, 0x2e, 0xee, 0x12, 0xe6, 0x63, 0x9b, 0xa8, 0x2b, 0xdd, 0x54, 0x01, - 0x97, 0x9e, 0x0c, 0x00, 0x14, 0xeb, 0x4c, 0xbf, 0x1c, 0x7c, 0x1b, 0x14, 0x9a, 0xd4, 0x0b, 0x7c, - 0xf9, 0x3a, 0xa5, 0xc6, 0x9c, 0x52, 0x29, 0x7c, 0x26, 0x84, 0x28, 0xc2, 0xe0, 0xbb, 0x60, 0xb6, - 0x47, 0x28, 0x73, 0x3c, 0x57, 0xcf, 0x4b, 0xb5, 0x79, 0xa5, 0x36, 0xbb, 0x1f, 0x89, 0xd1, 0x00, - 0x87, 0x77, 0x40, 0x91, 0xaa, 0xc0, 0xf5, 0x82, 0xd4, 0x5d, 0x50, 0xba, 0xc5, 0x61, 0x06, 0x87, - 0x1a, 0xf0, 0x43, 0x50, 0x66, 0x81, 0x35, 0x24, 0xcc, 0x48, 0xc2, 0xa2, 0x22, 0x94, 0x77, 0x63, - 0x08, 0x25, 0xf5, 0xc4, 0xb5, 0xc4, 0x1d, 0xf5, 0xd9, 0xd1, 0x6b, 0x89, 0x14, 0x20, 0x89, 0xd4, - 0xff, 0xd2, 0xc0, 0x8d, 0xab, 0xbd, 0xd8, 0x7b, 0xa0, 0x84, 0x7d, 0x47, 0x5e, 0x7b, 0xf0, 0x56, - 0x73, 0x22, 0xaf, 0x5b, 0x4f, 0x77, 0x22, 0x21, 0x8a, 0x71, 0xa1, 0x3c, 0x08, 0x46, 0xd4, 0xf5, - 0x50, 0x79, 0xe0, 0x92, 0xa1, 0x18, 0x87, 0x1b, 0x60, 0x6e, 0x70, 0x90, 0x8f, 0xa4, 0xe7, 0x25, - 0xe1, 0x66, 0xd8, 0xaf, 0xce, 0xa1, 0x24, 0x80, 0x46, 0xf5, 0xea, 0x7f, 0x66, 0xc1, 0xf2, 0x2e, - 0xe9, 0x1c, 0xbe, 0x9a, 0xa9, 0xf0, 0xd5, 0xc8, 0x54, 0x78, 0x70, 0x89, 0xb6, 0x4d, 0x0f, 0xf5, - 0xd5, 0x4e, 0x86, 0x5f, 0xb3, 0xe0, 0xcd, 0x0b, 0x02, 0x83, 0x3f, 0x00, 0x48, 0xc7, 0x1a, 0x4d, - 0x65, 0xf4, 0xde, 0xf4, 0x80, 0xc6, 0x9b, 0xb4, 0x71, 0x2b, 0xec, 0x57, 0x53, 0x9a, 0x17, 0xa5, - 0xf8, 0x81, 0x3f, 0x6a, 0x60, 0xc9, 0x4d, 0x1b, 0x5c, 0x2a, 0xeb, 0x1b, 0xd3, 0x23, 0x48, 0x9d, - 0x7b, 0x8d, 0xdb, 0x61, 0xbf, 0x9a, 0x3e, 0x12, 0x51, 0xba, 0x43, 0x31, 0x72, 0x6e, 0x25, 0x12, - 0x25, 0x9a, 0xe6, 0xe5, 0xd5, 0xda, 0x97, 0x23, 0xb5, 0xf6, 0xe9, 0x95, 0x6a, 0x2d, 0x11, 0xe9, - 0xc4, 0x52, 0xb3, 0xce, 0x95, 0xda, 0xe6, 0xa5, 0x4b, 0x2d, 0x69, 0xfd, 0xe2, 0x4a, 0x7b, 0x0c, - 0x56, 0x26, 0x47, 0x75, 0xe5, 0xd1, 0x5d, 0xff, 0x3d, 0x0b, 0x16, 0x5f, 0xaf, 0x03, 0xd7, 0x6b, - 0xfa, 0xd3, 0x3c, 0x58, 0x7e, 0xdd, 0xf0, 0x17, 0x37, 0xbc, 0xf8, 0x89, 0x06, 0x8c, 0x50, 0xf5, - 0xe3, 0x1f, 0xbe, 0xd5, 0x1e, 0x23, 0x14, 0x49, 0x04, 0xd6, 0x06, 0xbb, 0x41, 0xf4, 0xc3, 0x02, - 0x22, 0xd3, 0xea, 0x5f, 0xa8, 0x16, 0x03, 0x07, 0x14, 0x88, 0xd8, 0x78, 0xf5, 0x42, 0x2d, 0xb7, - 0x5a, 0x5e, 0x7f, 0x78, 0xed, 0x5a, 0x31, 0xe4, 0xe2, 0xbc, 0xed, 0x72, 0x7a, 0x1c, 0xef, 0x20, - 0x52, 0x86, 0x22, 0x0f, 0xf0, 0x2d, 0x90, 0x0b, 0x9c, 0x03, 0xb5, 0x22, 0x94, 0x95, 0x4a, 0x6e, - 0x6f, 0xe7, 0x21, 0x12, 0xf2, 0x95, 0x43, 0xb5, 0x7b, 0x4b, 0x13, 0x70, 0x01, 0xe4, 0xda, 0xe4, - 0x38, 0xea, 0x33, 0x24, 0x3e, 0x61, 0x03, 0x14, 0x7a, 0x62, 0x2d, 0x57, 0x79, 0xbe, 0x33, 0x3d, - 0xd2, 0x78, 0x95, 0x47, 0x11, 0x75, 0x33, 0x7b, 0x5f, 0xab, 0xff, 0xa6, 0x81, 0xdb, 0x13, 0x0b, - 0x52, 0x2c, 0x4a, 0xb8, 0xd3, 0xf1, 0x8e, 0xc8, 0x81, 0xf4, 0x5d, 0x8c, 0x17, 0xa5, 0xad, 0x48, - 0x8c, 0x06, 0x38, 0x7c, 0x07, 0xcc, 0x50, 0x82, 0x99, 0xe7, 0xaa, 0xe5, 0x6c, 0x58, 0xcb, 0x48, - 0x4a, 0x91, 0x42, 0xe1, 0x16, 0x98, 0x27, 0xc2, 0xbd, 0x0c, 0x6e, 0x9b, 0x52, 0x6f, 0xf0, 0x62, - 0xcb, 0x8a, 0x30, 0xbf, 0x3d, 0x0a, 0xa3, 0xf3, 0xfa, 0xf5, 0xff, 0xb2, 0x40, 0x9f, 0x34, 0xce, - 0x60, 0x3b, 0xde, 0x4e, 0x24, 0x28, 0x17, 0xa4, 0xf2, 0xba, 0x71, 0xf9, 0x56, 0x10, 0xb4, 0xc6, - 0x92, 0x8a, 0x66, 0x2e, 0x29, 0x4d, 0x6c, 0x34, 0xf2, 0x08, 0x8f, 0xc0, 0x82, 0x3b, 0xba, 0x4a, - 0x47, 0xbb, 0x56, 0x79, 0x7d, 0xed, 0x4a, 0x85, 0x2f, 0x5d, 0xea, 0xca, 0xe5, 0xc2, 0x39, 0x80, - 0xa1, 0x31, 0x27, 0x70, 0x1d, 0x00, 0xc7, 0xb5, 0xbd, 0xae, 0xdf, 0x21, 0x9c, 0xc8, 0x04, 0x16, - 0xe3, 0x29, 0xb8, 0x33, 0x44, 0x50, 0x42, 0x2b, 0x2d, 0xf3, 0xf9, 0xab, 0x65, 0xbe, 0x71, 0xf7, - 0xe4, 0xac, 0x92, 0x79, 0x71, 0x56, 0xc9, 0x9c, 0x9e, 0x55, 0x32, 0xcf, 0xc3, 0x8a, 0x76, 0x12, - 0x56, 0xb4, 0x17, 0x61, 0x45, 0x3b, 0x0d, 0x2b, 0xda, 0x3f, 0x61, 0x45, 0xfb, 0xe9, 0xdf, 0x4a, - 0xe6, 0x8b, 0x59, 0x75, 0xc3, 0xff, 0x03, 0x00, 0x00, 0xff, 0xff, 0x29, 0xa9, 0x9d, 0x7c, 0xb1, - 0x0f, 0x00, 0x00, + // 1154 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x56, 0x4d, 0x6f, 0x1b, 0xc5, + 0x1b, 0xf7, 0xfa, 0x25, 0xb1, 0xc7, 0xcd, 0x3f, 0xe9, 0x44, 0x69, 0xb6, 0xf9, 0x0b, 0xdb, 0x32, + 0x12, 0x0a, 0xa2, 0xdd, 0x25, 0xa1, 0x90, 0x12, 0xe8, 0x21, 0x56, 0x22, 0x14, 0xa9, 0x2d, 0xd5, + 0x44, 0xc9, 0x81, 0x4a, 0xc0, 0x78, 0x3d, 0xb1, 0x17, 0xdb, 0xbb, 0xcb, 0xcc, 0xac, 0x43, 0x10, + 0x87, 0x1e, 0x39, 0x72, 0xe4, 0xc8, 0x89, 0xef, 0xc0, 0x05, 0x09, 0x4e, 0x39, 0xf6, 0x18, 0x24, + 0x64, 0x91, 0xe5, 0x43, 0x70, 0x45, 0x33, 0x3b, 0xf6, 0xae, 0xe3, 0x75, 0x1c, 0xe7, 0x40, 0x2f, + 0xbd, 0xed, 0x3c, 0xbf, 0xe7, 0x6d, 0x9e, 0x97, 0xd9, 0x1f, 0xd8, 0x6f, 0x3f, 0x64, 0x86, 0xed, + 0x9a, 0x6d, 0xbf, 0x4e, 0xa8, 0x43, 0x38, 0x61, 0x66, 0x8f, 0x38, 0x0d, 0x97, 0x9a, 0x0a, 0xc0, + 0x9e, 0x6d, 0x62, 0x9f, 0xb7, 0x5c, 0x6a, 0x7f, 0x8b, 0xb9, 0xed, 0x3a, 0x66, 0x6f, 0xa3, 0x4e, + 0x38, 0xde, 0x30, 0x9b, 0xc4, 0x21, 0x14, 0x73, 0xd2, 0x30, 0x3c, 0xea, 0x72, 0x17, 0x56, 0x42, + 0x0b, 0x03, 0x7b, 0xb6, 0x31, 0x62, 0x61, 0x28, 0x8b, 0xb5, 0xfb, 0x4d, 0x9b, 0xb7, 0xfc, 0xba, + 0x61, 0xb9, 0x5d, 0xb3, 0xe9, 0x36, 0x5d, 0x53, 0x1a, 0xd6, 0xfd, 0x63, 0x79, 0x92, 0x07, 0xf9, + 0x15, 0x3a, 0x5c, 0x7b, 0x10, 0xa5, 0xd0, 0xc5, 0x56, 0xcb, 0x76, 0x08, 0x3d, 0x35, 0xbd, 0x76, + 0x53, 0x08, 0x98, 0xd9, 0x25, 0x1c, 0x9b, 0xbd, 0xb1, 0x34, 0xd6, 0xcc, 0x49, 0x56, 0xd4, 0x77, + 0xb8, 0xdd, 0x25, 0x63, 0x06, 0x1f, 0x4c, 0x33, 0x60, 0x56, 0x8b, 0x74, 0xf1, 0x98, 0xdd, 0x7b, + 0x93, 0xec, 0x7c, 0x6e, 0x77, 0x4c, 0xdb, 0xe1, 0x8c, 0xd3, 0xcb, 0x46, 0xd5, 0x2d, 0x00, 0xf6, + 0xbe, 0xe1, 0x14, 0x1f, 0xe1, 0x8e, 0x4f, 0x60, 0x19, 0xe4, 0x6c, 0x4e, 0xba, 0x4c, 0xd7, 0x2a, + 0x99, 0xf5, 0x42, 0xad, 0x10, 0xf4, 0xcb, 0xb9, 0x7d, 0x21, 0x40, 0xa1, 0x7c, 0x3b, 0xff, 0xe3, + 0x4f, 0xe5, 0xd4, 0x8b, 0x3f, 0x2b, 0xa9, 0xea, 0xaf, 0x69, 0xa0, 0x3f, 0x76, 0x2d, 0xdc, 0x39, + 0xf0, 0xeb, 0x5f, 0x11, 0x8b, 0xef, 0x58, 0x16, 0x61, 0x0c, 0x91, 0x9e, 0x4d, 0x4e, 0xe0, 0x97, + 0x20, 0x2f, 0xca, 0xd1, 0xc0, 0x1c, 0xeb, 0x5a, 0x45, 0x5b, 0x2f, 0x6e, 0xbe, 0x6b, 0x44, 0xdd, + 0x18, 0x66, 0x67, 0x78, 0xed, 0xa6, 0x10, 0x30, 0x43, 0x68, 0x1b, 0xbd, 0x0d, 0xe3, 0x53, 0xe9, + 0xeb, 0x09, 0xe1, 0xb8, 0x06, 0xcf, 0xfa, 0xe5, 0x54, 0xd0, 0x2f, 0x83, 0x48, 0x86, 0x86, 0x5e, + 0xe1, 0x73, 0x90, 0x65, 0x1e, 0xb1, 0xf4, 0xb4, 0xf4, 0xfe, 0xa1, 0x31, 0xad, 0xd7, 0x46, 0x42, + 0x9a, 0x07, 0x1e, 0xb1, 0x6a, 0xb7, 0x54, 0x98, 0xac, 0x38, 0x21, 0xe9, 0x14, 0x5a, 0x60, 0x8e, + 0x71, 0xcc, 0x7d, 0xa6, 0x67, 0xa4, 0xfb, 0x8f, 0x6e, 0xe6, 0x5e, 0xba, 0xa8, 0xfd, 0x4f, 0x05, + 0x98, 0x0b, 0xcf, 0x48, 0xb9, 0xae, 0x3e, 0x07, 0x2b, 0x4f, 0x5d, 0x07, 0x11, 0xe6, 0xfa, 0xd4, + 0x22, 0x3b, 0x9c, 0x53, 0xbb, 0xee, 0x73, 0xc2, 0x60, 0x05, 0x64, 0x3d, 0xcc, 0x5b, 0xb2, 0x70, + 0x85, 0x28, 0xbf, 0x67, 0x98, 0xb7, 0x90, 0x44, 0x84, 0x46, 0x8f, 0xd0, 0xba, 0xbc, 0x7c, 0x4c, + 0xe3, 0x88, 0xd0, 0x3a, 0x92, 0x48, 0xf5, 0x6b, 0xb0, 0x18, 0x73, 0x8e, 0xfc, 0x8e, 0xec, 0xad, + 0x80, 0x46, 0x7a, 0x2b, 0x2c, 0x18, 0x0a, 0xe5, 0xf0, 0x11, 0x58, 0x74, 0x22, 0x9b, 0x43, 0xf4, + 0x98, 0xe9, 0x69, 0xa9, 0xba, 0x1c, 0xf4, 0xcb, 0x71, 0x77, 0x02, 0x42, 0x97, 0x75, 0xc5, 0x40, + 0xc0, 0x84, 0xdb, 0x98, 0xa0, 0xe0, 0xe0, 0x2e, 0x61, 0x1e, 0xb6, 0x88, 0xba, 0xd2, 0x6d, 0x95, + 0x70, 0xe1, 0xe9, 0x00, 0x40, 0x91, 0xce, 0xf4, 0xcb, 0xc1, 0x37, 0x41, 0xae, 0x49, 0x5d, 0xdf, + 0x93, 0xdd, 0x29, 0xd4, 0x16, 0x94, 0x4a, 0xee, 0x13, 0x21, 0x44, 0x21, 0x06, 0xdf, 0x06, 0xf3, + 0x3d, 0x42, 0x99, 0xed, 0x3a, 0x7a, 0x56, 0xaa, 0x2d, 0x2a, 0xb5, 0xf9, 0xa3, 0x50, 0x8c, 0x06, + 0x38, 0xbc, 0x07, 0xf2, 0x54, 0x25, 0xae, 0xe7, 0xa4, 0xee, 0x92, 0xd2, 0xcd, 0x0f, 0x2b, 0x38, + 0xd4, 0x80, 0xef, 0x83, 0x22, 0xf3, 0xeb, 0x43, 0x83, 0x39, 0x69, 0xb0, 0xac, 0x0c, 0x8a, 0x07, + 0x11, 0x84, 0xe2, 0x7a, 0xe2, 0x5a, 0xe2, 0x8e, 0xfa, 0xfc, 0xe8, 0xb5, 0x44, 0x09, 0x90, 0x44, + 0xaa, 0xbf, 0x6b, 0xe0, 0xd6, 0x6c, 0x1d, 0x7b, 0x07, 0x14, 0xb0, 0x67, 0xcb, 0x6b, 0x0f, 0x7a, + 0xb5, 0x20, 0xea, 0xba, 0xf3, 0x6c, 0x3f, 0x14, 0xa2, 0x08, 0x17, 0xca, 0x83, 0x64, 0xc4, 0x5c, + 0x0f, 0x95, 0x07, 0x21, 0x19, 0x8a, 0x70, 0xb8, 0x05, 0x16, 0x06, 0x07, 0xd9, 0x24, 0x3d, 0x2b, + 0x0d, 0x6e, 0x07, 0xfd, 0xf2, 0x02, 0x8a, 0x03, 0x68, 0x54, 0xaf, 0xfa, 0x5b, 0x1a, 0xac, 0x1e, + 0x90, 0xce, 0xf1, 0xab, 0x79, 0x15, 0xbe, 0x18, 0x79, 0x15, 0x1e, 0x5d, 0x63, 0x6d, 0x93, 0x53, + 0x7d, 0xb5, 0x2f, 0xc3, 0xcf, 0x69, 0xf0, 0xff, 0x2b, 0x12, 0x83, 0xdf, 0x01, 0x48, 0xc7, 0x16, + 0x4d, 0x55, 0xf4, 0xc1, 0xf4, 0x84, 0xc6, 0x97, 0xb4, 0x76, 0x27, 0xe8, 0x97, 0x13, 0x96, 0x17, + 0x25, 0xc4, 0x81, 0xdf, 0x6b, 0x60, 0xc5, 0x49, 0x7a, 0xb8, 0x54, 0xd5, 0xb7, 0xa6, 0x67, 0x90, + 0xf8, 0xee, 0xd5, 0xee, 0x06, 0xfd, 0x72, 0xf2, 0x93, 0x88, 0x92, 0x03, 0x8a, 0x27, 0xe7, 0x4e, + 0xac, 0x50, 0x62, 0x69, 0xfe, 0xbb, 0x59, 0xfb, 0x7c, 0x64, 0xd6, 0x3e, 0x9e, 0x69, 0xd6, 0x62, + 0x99, 0x4e, 0x1c, 0xb5, 0xfa, 0xa5, 0x51, 0xdb, 0xbe, 0xf6, 0xa8, 0xc5, 0xbd, 0x5f, 0x3d, 0x69, + 0x4f, 0xc0, 0xda, 0xe4, 0xac, 0x66, 0x7e, 0xba, 0xab, 0xbf, 0xa4, 0xc1, 0xf2, 0x6b, 0x3a, 0x70, + 0xb3, 0xa5, 0x3f, 0xcf, 0x82, 0xd5, 0xd7, 0x0b, 0x7f, 0xf5, 0xc2, 0x8b, 0x9f, 0xa8, 0xcf, 0x08, + 0x55, 0x3f, 0xfe, 0x61, 0xaf, 0x0e, 0x19, 0xa1, 0x48, 0x22, 0xb0, 0x32, 0xe0, 0x06, 0xe1, 0x0f, + 0x0b, 0x88, 0x4a, 0xab, 0x7f, 0xa1, 0x22, 0x06, 0x36, 0xc8, 0x11, 0xc1, 0x78, 0xf5, 0x5c, 0x25, + 0xb3, 0x5e, 0xdc, 0xdc, 0xbd, 0xf1, 0xac, 0x18, 0x92, 0x38, 0xef, 0x39, 0x9c, 0x9e, 0x46, 0x1c, + 0x44, 0xca, 0x50, 0x18, 0x01, 0xbe, 0x01, 0x32, 0xbe, 0xdd, 0x50, 0x14, 0xa1, 0xa8, 0x54, 0x32, + 0x87, 0xfb, 0xbb, 0x48, 0xc8, 0xd7, 0x8e, 0x15, 0xf7, 0x96, 0x2e, 0xe0, 0x12, 0xc8, 0xb4, 0xc9, + 0x69, 0xb8, 0x67, 0x48, 0x7c, 0xc2, 0x1a, 0xc8, 0xf5, 0x04, 0x2d, 0x57, 0x75, 0xbe, 0x37, 0x3d, + 0xd3, 0x88, 0xca, 0xa3, 0xd0, 0x74, 0x3b, 0xfd, 0x50, 0xab, 0xfe, 0xa1, 0x81, 0xbb, 0x13, 0x07, + 0x52, 0x10, 0x25, 0xdc, 0xe9, 0xb8, 0x27, 0xa4, 0x21, 0x63, 0xe7, 0x23, 0xa2, 0xb4, 0x13, 0x8a, + 0xd1, 0x00, 0x87, 0x6f, 0x81, 0x39, 0x4a, 0x30, 0x73, 0x1d, 0x45, 0xce, 0x86, 0xb3, 0x8c, 0xa4, + 0x14, 0x29, 0x14, 0xee, 0x80, 0x45, 0x22, 0xc2, 0xcb, 0xe4, 0xf6, 0x28, 0x75, 0x07, 0x1d, 0x5b, + 0x55, 0x06, 0x8b, 0x7b, 0xa3, 0x30, 0xba, 0xac, 0x2f, 0x42, 0x35, 0x88, 0x63, 0x93, 0x86, 0x64, + 0x6f, 0xf9, 0x28, 0xd4, 0xae, 0x94, 0x22, 0x85, 0x56, 0xff, 0x49, 0x03, 0x7d, 0xd2, 0xb3, 0x07, + 0xdb, 0x11, 0x8b, 0x91, 0xa0, 0x24, 0x52, 0xc5, 0x4d, 0xe3, 0xfa, 0x2b, 0x23, 0xcc, 0x6a, 0x2b, + 0x2a, 0xf6, 0x42, 0x5c, 0x1a, 0x63, 0x3e, 0xf2, 0x08, 0x4f, 0xc0, 0x92, 0x33, 0x4a, 0xb9, 0x43, + 0x4e, 0x56, 0xdc, 0xdc, 0x98, 0x69, 0x41, 0x64, 0x48, 0x5d, 0x85, 0x5c, 0xba, 0x04, 0x30, 0x34, + 0x16, 0x04, 0x6e, 0x02, 0x60, 0x3b, 0x96, 0xdb, 0xf5, 0x3a, 0x84, 0x13, 0x59, 0xe8, 0x7c, 0xf4, + 0x5a, 0xee, 0x0f, 0x11, 0x14, 0xd3, 0x4a, 0xea, 0x50, 0x76, 0xb6, 0x0e, 0xd5, 0xee, 0x9f, 0x5d, + 0x94, 0x52, 0x2f, 0x2f, 0x4a, 0xa9, 0xf3, 0x8b, 0x52, 0xea, 0x45, 0x50, 0xd2, 0xce, 0x82, 0x92, + 0xf6, 0x32, 0x28, 0x69, 0xe7, 0x41, 0x49, 0xfb, 0x2b, 0x28, 0x69, 0x3f, 0xfc, 0x5d, 0x4a, 0x7d, + 0x36, 0xaf, 0x6e, 0xf8, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xcc, 0xb3, 0x5e, 0x05, 0xd9, 0x0f, + 0x00, 0x00, } diff --git a/staging/src/k8s.io/api/authorization/v1beta1/generated.proto b/staging/src/k8s.io/api/authorization/v1beta1/generated.proto index 9e9942f367a..b64c0642cbd 100644 --- a/staging/src/k8s.io/api/authorization/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/authorization/v1beta1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -121,7 +121,8 @@ message ResourceRule { // +optional repeated string apiGroups = 2; - // Resources is a list of resources this rule applies to. ResourceAll represents all resources. "*" means all. + // 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 repeated string resources = 3; @@ -225,9 +226,16 @@ message SubjectAccessReviewSpec { // 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 + // 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. // +optional optional string reason = 2; diff --git a/staging/src/k8s.io/api/authorization/v1beta1/register.go b/staging/src/k8s.io/api/authorization/v1beta1/register.go index d8116d5a47a..84255dd6c90 100644 --- a/staging/src/k8s.io/api/authorization/v1beta1/register.go +++ b/staging/src/k8s.io/api/authorization/v1beta1/register.go @@ -41,7 +41,7 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &SelfSubjectRulesReview{}, diff --git a/staging/src/k8s.io/api/authorization/v1beta1/types.go b/staging/src/k8s.io/api/authorization/v1beta1/types.go index a0659d519c0..618ff8c0f1e 100644 --- a/staging/src/k8s.io/api/authorization/v1beta1/types.go +++ b/staging/src/k8s.io/api/authorization/v1beta1/types.go @@ -169,8 +169,14 @@ type SelfSubjectAccessReviewSpec struct { // 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 + // 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. // +optional Reason string `json:"reason,omitempty" protobuf:"bytes,2,opt,name=reason"` @@ -241,7 +247,8 @@ type ResourceRule struct { // the enumerated resources in any API group will be allowed. "*" means all. // +optional APIGroups []string `json:"apiGroups,omitempty" protobuf:"bytes,2,rep,name=apiGroups"` - // Resources is a list of resources this rule applies to. ResourceAll represents all resources. "*" means all. + // 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 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. 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 1d8bb9849ba..2371b21c676 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 @@ -76,7 +76,7 @@ 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. ResourceAll represents all resources. \"*\" 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.", } @@ -148,7 +148,8 @@ 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.", + "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.", } diff --git a/staging/src/k8s.io/api/authorization/v1beta1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/authorization/v1beta1/zz_generated.deepcopy.go index aeb77ddbc86..fed07fbb216 100644 --- a/staging/src/k8s.io/api/authorization/v1beta1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/authorization/v1beta1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,76 +21,9 @@ limitations under the License. package v1beta1 import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LocalSubjectAccessReview).DeepCopyInto(out.(*LocalSubjectAccessReview)) - return nil - }, InType: reflect.TypeOf(&LocalSubjectAccessReview{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NonResourceAttributes).DeepCopyInto(out.(*NonResourceAttributes)) - return nil - }, InType: reflect.TypeOf(&NonResourceAttributes{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NonResourceRule).DeepCopyInto(out.(*NonResourceRule)) - return nil - }, InType: reflect.TypeOf(&NonResourceRule{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ResourceAttributes).DeepCopyInto(out.(*ResourceAttributes)) - return nil - }, InType: reflect.TypeOf(&ResourceAttributes{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ResourceRule).DeepCopyInto(out.(*ResourceRule)) - return nil - }, InType: reflect.TypeOf(&ResourceRule{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SelfSubjectAccessReview).DeepCopyInto(out.(*SelfSubjectAccessReview)) - return nil - }, InType: reflect.TypeOf(&SelfSubjectAccessReview{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SelfSubjectAccessReviewSpec).DeepCopyInto(out.(*SelfSubjectAccessReviewSpec)) - return nil - }, InType: reflect.TypeOf(&SelfSubjectAccessReviewSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SelfSubjectRulesReview).DeepCopyInto(out.(*SelfSubjectRulesReview)) - return nil - }, InType: reflect.TypeOf(&SelfSubjectRulesReview{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SelfSubjectRulesReviewSpec).DeepCopyInto(out.(*SelfSubjectRulesReviewSpec)) - return nil - }, InType: reflect.TypeOf(&SelfSubjectRulesReviewSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SubjectAccessReview).DeepCopyInto(out.(*SubjectAccessReview)) - return nil - }, InType: reflect.TypeOf(&SubjectAccessReview{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SubjectAccessReviewSpec).DeepCopyInto(out.(*SubjectAccessReviewSpec)) - return nil - }, InType: reflect.TypeOf(&SubjectAccessReviewSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SubjectAccessReviewStatus).DeepCopyInto(out.(*SubjectAccessReviewStatus)) - return nil - }, InType: reflect.TypeOf(&SubjectAccessReviewStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SubjectRulesReviewStatus).DeepCopyInto(out.(*SubjectRulesReviewStatus)) - return nil - }, InType: reflect.TypeOf(&SubjectRulesReviewStatus{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LocalSubjectAccessReview) DeepCopyInto(out *LocalSubjectAccessReview) { *out = *in diff --git a/staging/src/k8s.io/api/autoscaling/v1/BUILD b/staging/src/k8s.io/api/autoscaling/v1/BUILD index 22c95c806c5..ccf587be09e 100644 --- a/staging/src/k8s.io/api/autoscaling/v1/BUILD +++ b/staging/src/k8s.io/api/autoscaling/v1/BUILD @@ -21,7 +21,6 @@ go_library( "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/staging/src/k8s.io/api/autoscaling/v1/doc.go b/staging/src/k8s.io/api/autoscaling/v1/doc.go index 1689adb5cbd..9c3be845f00 100644 --- a/staging/src/k8s.io/api/autoscaling/v1/doc.go +++ b/staging/src/k8s.io/api/autoscaling/v1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:openapi-gen=true package v1 // import "k8s.io/api/autoscaling/v1" diff --git a/staging/src/k8s.io/api/autoscaling/v1/generated.pb.go b/staging/src/k8s.io/api/autoscaling/v1/generated.pb.go index db580cdabbc..4c6a171279b 100644 --- a/staging/src/k8s.io/api/autoscaling/v1/generated.pb.go +++ b/staging/src/k8s.io/api/autoscaling/v1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/api/autoscaling/v1/generated.proto b/staging/src/k8s.io/api/autoscaling/v1/generated.proto index e41e62746bd..33eecf4de06 100644 --- a/staging/src/k8s.io/api/autoscaling/v1/generated.proto +++ b/staging/src/k8s.io/api/autoscaling/v1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -138,7 +138,8 @@ message HorizontalPodAutoscalerStatus { // 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 match one of the fields below. + // type is the type of metric source. It should be one of "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 @@ -163,7 +164,8 @@ message MetricSpec { // MetricStatus describes the last-read state of a single metric. message MetricStatus { - // type is the type of metric source. It will match one of the fields below. + // type is the type of metric source. It will be one of "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 diff --git a/staging/src/k8s.io/api/autoscaling/v1/register.go b/staging/src/k8s.io/api/autoscaling/v1/register.go index 521c0e4a3dc..8dfe361edf5 100644 --- a/staging/src/k8s.io/api/autoscaling/v1/register.go +++ b/staging/src/k8s.io/api/autoscaling/v1/register.go @@ -41,7 +41,7 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &HorizontalPodAutoscaler{}, diff --git a/staging/src/k8s.io/api/autoscaling/v1/types.go b/staging/src/k8s.io/api/autoscaling/v1/types.go index e726c140399..eeadaf88475 100644 --- a/staging/src/k8s.io/api/autoscaling/v1/types.go +++ b/staging/src/k8s.io/api/autoscaling/v1/types.go @@ -166,7 +166,8 @@ var ( // 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 match one of the fields below. + // type is the type of metric source. It should be one of "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 @@ -235,7 +236,8 @@ type ResourceMetricSource struct { // MetricStatus describes the last-read state of a single metric. type MetricStatus struct { - // type is the type of metric source. It will match one of the fields below. + // type is the type of metric source. It will be one of "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 diff --git a/staging/src/k8s.io/api/autoscaling/v1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/autoscaling/v1/types_swagger_doc_generated.go index 7f84c2d9348..5506b76f3df 100644 --- a/staging/src/k8s.io/api/autoscaling/v1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/autoscaling/v1/types_swagger_doc_generated.go @@ -99,7 +99,7 @@ func (HorizontalPodAutoscalerStatus) SwaggerDoc() map[string]string { 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 match one of the fields below.", + "type": "type is the type of metric source. It should be one of \"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.", @@ -111,7 +111,7 @@ func (MetricSpec) SwaggerDoc() map[string]string { 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 match one of the fields below.", + "type": "type is the type of metric source. It will be one of \"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.", diff --git a/staging/src/k8s.io/api/autoscaling/v1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/autoscaling/v1/zz_generated.deepcopy.go index 20848f20c84..3622cf450a9 100644 --- a/staging/src/k8s.io/api/autoscaling/v1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/autoscaling/v1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,92 +23,9 @@ package v1 import ( resource "k8s.io/apimachinery/pkg/api/resource" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CrossVersionObjectReference).DeepCopyInto(out.(*CrossVersionObjectReference)) - return nil - }, InType: reflect.TypeOf(&CrossVersionObjectReference{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HorizontalPodAutoscaler).DeepCopyInto(out.(*HorizontalPodAutoscaler)) - return nil - }, InType: reflect.TypeOf(&HorizontalPodAutoscaler{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HorizontalPodAutoscalerCondition).DeepCopyInto(out.(*HorizontalPodAutoscalerCondition)) - return nil - }, InType: reflect.TypeOf(&HorizontalPodAutoscalerCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HorizontalPodAutoscalerList).DeepCopyInto(out.(*HorizontalPodAutoscalerList)) - return nil - }, InType: reflect.TypeOf(&HorizontalPodAutoscalerList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HorizontalPodAutoscalerSpec).DeepCopyInto(out.(*HorizontalPodAutoscalerSpec)) - return nil - }, InType: reflect.TypeOf(&HorizontalPodAutoscalerSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HorizontalPodAutoscalerStatus).DeepCopyInto(out.(*HorizontalPodAutoscalerStatus)) - return nil - }, InType: reflect.TypeOf(&HorizontalPodAutoscalerStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*MetricSpec).DeepCopyInto(out.(*MetricSpec)) - return nil - }, InType: reflect.TypeOf(&MetricSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*MetricStatus).DeepCopyInto(out.(*MetricStatus)) - return nil - }, InType: reflect.TypeOf(&MetricStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ObjectMetricSource).DeepCopyInto(out.(*ObjectMetricSource)) - return nil - }, InType: reflect.TypeOf(&ObjectMetricSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ObjectMetricStatus).DeepCopyInto(out.(*ObjectMetricStatus)) - return nil - }, InType: reflect.TypeOf(&ObjectMetricStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodsMetricSource).DeepCopyInto(out.(*PodsMetricSource)) - return nil - }, InType: reflect.TypeOf(&PodsMetricSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodsMetricStatus).DeepCopyInto(out.(*PodsMetricStatus)) - return nil - }, InType: reflect.TypeOf(&PodsMetricStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ResourceMetricSource).DeepCopyInto(out.(*ResourceMetricSource)) - return nil - }, InType: reflect.TypeOf(&ResourceMetricSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ResourceMetricStatus).DeepCopyInto(out.(*ResourceMetricStatus)) - return nil - }, InType: reflect.TypeOf(&ResourceMetricStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Scale).DeepCopyInto(out.(*Scale)) - return nil - }, InType: reflect.TypeOf(&Scale{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ScaleSpec).DeepCopyInto(out.(*ScaleSpec)) - return nil - }, InType: reflect.TypeOf(&ScaleSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ScaleStatus).DeepCopyInto(out.(*ScaleStatus)) - return nil - }, InType: reflect.TypeOf(&ScaleStatus{})}, - ) -} - // 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 diff --git a/staging/src/k8s.io/api/autoscaling/v2beta1/BUILD b/staging/src/k8s.io/api/autoscaling/v2beta1/BUILD index ea811df6ae8..32fc333eb5d 100644 --- a/staging/src/k8s.io/api/autoscaling/v2beta1/BUILD +++ b/staging/src/k8s.io/api/autoscaling/v2beta1/BUILD @@ -21,7 +21,6 @@ go_library( "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/staging/src/k8s.io/api/autoscaling/v2beta1/doc.go b/staging/src/k8s.io/api/autoscaling/v2beta1/doc.go index 689f369bfe2..da9789e5cb8 100644 --- a/staging/src/k8s.io/api/autoscaling/v2beta1/doc.go +++ b/staging/src/k8s.io/api/autoscaling/v2beta1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:openapi-gen=true package v2beta1 // import "k8s.io/api/autoscaling/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 index 11eb55f3f0c..908c049ed3d 100644 --- a/staging/src/k8s.io/api/autoscaling/v2beta1/generated.pb.go +++ b/staging/src/k8s.io/api/autoscaling/v2beta1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/api/autoscaling/v2beta1/generated.proto b/staging/src/k8s.io/api/autoscaling/v2beta1/generated.proto index de3d2665fdc..0c682b35ea2 100644 --- a/staging/src/k8s.io/api/autoscaling/v2beta1/generated.proto +++ b/staging/src/k8s.io/api/autoscaling/v2beta1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -153,7 +153,8 @@ message HorizontalPodAutoscalerStatus { // 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 match one of the fields below. + // type is the type of metric source. It should be one of "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 @@ -178,7 +179,8 @@ message MetricSpec { // MetricStatus describes the last-read state of a single metric. message MetricStatus { - // type is the type of metric source. It will match one of the fields below. + // type is the type of metric source. It will be one of "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 diff --git a/staging/src/k8s.io/api/autoscaling/v2beta1/register.go b/staging/src/k8s.io/api/autoscaling/v2beta1/register.go index 9025b421c82..12d697f0130 100644 --- a/staging/src/k8s.io/api/autoscaling/v2beta1/register.go +++ b/staging/src/k8s.io/api/autoscaling/v2beta1/register.go @@ -41,7 +41,7 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &HorizontalPodAutoscaler{}, diff --git a/staging/src/k8s.io/api/autoscaling/v2beta1/types.go b/staging/src/k8s.io/api/autoscaling/v2beta1/types.go index 9c72ae25ca7..22e53573b2f 100644 --- a/staging/src/k8s.io/api/autoscaling/v2beta1/types.go +++ b/staging/src/k8s.io/api/autoscaling/v2beta1/types.go @@ -78,7 +78,8 @@ var ( // 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 match one of the fields below. + // type is the type of metric source. It should be one of "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 @@ -210,7 +211,8 @@ type HorizontalPodAutoscalerCondition struct { // MetricStatus describes the last-read state of a single metric. type MetricStatus struct { - // type is the type of metric source. It will match one of the fields below. + // type is the type of metric source. It will be one of "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 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 index c7002b3d1e6..8bcf0f4b305 100644 --- 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 @@ -100,7 +100,7 @@ func (HorizontalPodAutoscalerStatus) SwaggerDoc() map[string]string { 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 match one of the fields below.", + "type": "type is the type of metric source. It should be one of \"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.", @@ -112,7 +112,7 @@ func (MetricSpec) SwaggerDoc() map[string]string { 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 match one of the fields below.", + "type": "type is the type of metric source. It will be one of \"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.", 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 index b538e5b40c9..70bec2b5b6c 100644 --- a/staging/src/k8s.io/api/autoscaling/v2beta1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/autoscaling/v2beta1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,80 +23,9 @@ package v2beta1 import ( resource "k8s.io/apimachinery/pkg/api/resource" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CrossVersionObjectReference).DeepCopyInto(out.(*CrossVersionObjectReference)) - return nil - }, InType: reflect.TypeOf(&CrossVersionObjectReference{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HorizontalPodAutoscaler).DeepCopyInto(out.(*HorizontalPodAutoscaler)) - return nil - }, InType: reflect.TypeOf(&HorizontalPodAutoscaler{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HorizontalPodAutoscalerCondition).DeepCopyInto(out.(*HorizontalPodAutoscalerCondition)) - return nil - }, InType: reflect.TypeOf(&HorizontalPodAutoscalerCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HorizontalPodAutoscalerList).DeepCopyInto(out.(*HorizontalPodAutoscalerList)) - return nil - }, InType: reflect.TypeOf(&HorizontalPodAutoscalerList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HorizontalPodAutoscalerSpec).DeepCopyInto(out.(*HorizontalPodAutoscalerSpec)) - return nil - }, InType: reflect.TypeOf(&HorizontalPodAutoscalerSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HorizontalPodAutoscalerStatus).DeepCopyInto(out.(*HorizontalPodAutoscalerStatus)) - return nil - }, InType: reflect.TypeOf(&HorizontalPodAutoscalerStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*MetricSpec).DeepCopyInto(out.(*MetricSpec)) - return nil - }, InType: reflect.TypeOf(&MetricSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*MetricStatus).DeepCopyInto(out.(*MetricStatus)) - return nil - }, InType: reflect.TypeOf(&MetricStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ObjectMetricSource).DeepCopyInto(out.(*ObjectMetricSource)) - return nil - }, InType: reflect.TypeOf(&ObjectMetricSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ObjectMetricStatus).DeepCopyInto(out.(*ObjectMetricStatus)) - return nil - }, InType: reflect.TypeOf(&ObjectMetricStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodsMetricSource).DeepCopyInto(out.(*PodsMetricSource)) - return nil - }, InType: reflect.TypeOf(&PodsMetricSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodsMetricStatus).DeepCopyInto(out.(*PodsMetricStatus)) - return nil - }, InType: reflect.TypeOf(&PodsMetricStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ResourceMetricSource).DeepCopyInto(out.(*ResourceMetricSource)) - return nil - }, InType: reflect.TypeOf(&ResourceMetricSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ResourceMetricStatus).DeepCopyInto(out.(*ResourceMetricStatus)) - return nil - }, InType: reflect.TypeOf(&ResourceMetricStatus{})}, - ) -} - // 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 diff --git a/staging/src/k8s.io/api/batch/v1/BUILD b/staging/src/k8s.io/api/batch/v1/BUILD index 246a99cfa6c..a7ca7a23267 100644 --- a/staging/src/k8s.io/api/batch/v1/BUILD +++ b/staging/src/k8s.io/api/batch/v1/BUILD @@ -20,7 +20,6 @@ go_library( "//vendor/github.com/gogo/protobuf/proto:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/staging/src/k8s.io/api/batch/v1/doc.go b/staging/src/k8s.io/api/batch/v1/doc.go index 52a1d9039b1..04491807f2b 100644 --- a/staging/src/k8s.io/api/batch/v1/doc.go +++ b/staging/src/k8s.io/api/batch/v1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:openapi-gen=true package v1 // import "k8s.io/api/batch/v1" diff --git a/staging/src/k8s.io/api/batch/v1/generated.pb.go b/staging/src/k8s.io/api/batch/v1/generated.pb.go index 5909ab76633..8599b67339f 100644 --- a/staging/src/k8s.io/api/batch/v1/generated.pb.go +++ b/staging/src/k8s.io/api/batch/v1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/api/batch/v1/generated.proto b/staging/src/k8s.io/api/batch/v1/generated.proto index 08635ad2b2c..b0306f3bc97 100644 --- a/staging/src/k8s.io/api/batch/v1/generated.proto +++ b/staging/src/k8s.io/api/batch/v1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -128,7 +128,7 @@ message JobSpec { // and other jobs to not function correctly. However, You may see // `manualSelector=true` in jobs that were created with the old `extensions/v1beta1` // API. - // More info: https://git.k8s.io/community/contributors/design-proposals/selector-generation.md + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/#specifying-your-own-pod-selector // +optional optional bool manualSelector = 5; diff --git a/staging/src/k8s.io/api/batch/v1/register.go b/staging/src/k8s.io/api/batch/v1/register.go index 2cdfc98ca42..32fa51f0e4a 100644 --- a/staging/src/k8s.io/api/batch/v1/register.go +++ b/staging/src/k8s.io/api/batch/v1/register.go @@ -41,7 +41,7 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Job{}, diff --git a/staging/src/k8s.io/api/batch/v1/types.go b/staging/src/k8s.io/api/batch/v1/types.go index 4f3b83e8a8d..84abb1a9015 100644 --- a/staging/src/k8s.io/api/batch/v1/types.go +++ b/staging/src/k8s.io/api/batch/v1/types.go @@ -107,7 +107,7 @@ type JobSpec struct { // and other jobs to not function correctly. However, You may see // `manualSelector=true` in jobs that were created with the old `extensions/v1beta1` // API. - // More info: https://git.k8s.io/community/contributors/design-proposals/selector-generation.md + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/#specifying-your-own-pod-selector // +optional ManualSelector *bool `json:"manualSelector,omitempty" protobuf:"varint,5,opt,name=manualSelector"` diff --git a/staging/src/k8s.io/api/batch/v1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/batch/v1/types_swagger_doc_generated.go index 53b2d634fd7..0ddf4b11644 100644 --- a/staging/src/k8s.io/api/batch/v1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/batch/v1/types_swagger_doc_generated.go @@ -69,7 +69,7 @@ var map_JobSpec = map[string]string{ "activeDeadlineSeconds": "Specifies the duration in seconds relative to the startTime that the job may be active before the system tries to terminate it; value must be positive integer", "backoffLimit": "Specifies the number of retries before marking this job failed. Defaults to 6", "selector": "A label query over pods that should match the pod count. Normally, the system sets this field for you. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", - "manualSelector": "manualSelector controls generation of pod labels and pod selectors. Leave `manualSelector` unset unless you are certain what you are doing. When false or unset, the system pick labels unique to this job and appends those labels to the pod template. When true, the user is responsible for picking unique labels and specifying the selector. Failure to pick a unique label may cause this and other jobs to not function correctly. However, You may see `manualSelector=true` in jobs that were created with the old `extensions/v1beta1` API. More info: https://git.k8s.io/community/contributors/design-proposals/selector-generation.md", + "manualSelector": "manualSelector controls generation of pod labels and pod selectors. Leave `manualSelector` unset unless you are certain what you are doing. When false or unset, the system pick labels unique to this job and appends those labels to the pod template. When true, the user is responsible for picking unique labels and specifying the selector. Failure to pick a unique label may cause this and other jobs to not function correctly. However, You may see `manualSelector=true` in jobs that were created with the old `extensions/v1beta1` API. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/#specifying-your-own-pod-selector", "template": "Describes the pod that will be created when executing a job. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/", } diff --git a/staging/src/k8s.io/api/batch/v1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/batch/v1/zz_generated.deepcopy.go index 8738bd5d2c3..53392f45afd 100644 --- a/staging/src/k8s.io/api/batch/v1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/batch/v1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,44 +22,9 @@ package v1 import ( meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Job).DeepCopyInto(out.(*Job)) - return nil - }, InType: reflect.TypeOf(&Job{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*JobCondition).DeepCopyInto(out.(*JobCondition)) - return nil - }, InType: reflect.TypeOf(&JobCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*JobList).DeepCopyInto(out.(*JobList)) - return nil - }, InType: reflect.TypeOf(&JobList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*JobSpec).DeepCopyInto(out.(*JobSpec)) - return nil - }, InType: reflect.TypeOf(&JobSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*JobStatus).DeepCopyInto(out.(*JobStatus)) - return nil - }, InType: reflect.TypeOf(&JobStatus{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Job) DeepCopyInto(out *Job) { *out = *in diff --git a/staging/src/k8s.io/api/batch/v1beta1/BUILD b/staging/src/k8s.io/api/batch/v1beta1/BUILD index 99836388858..3f0197b9c6c 100644 --- a/staging/src/k8s.io/api/batch/v1beta1/BUILD +++ b/staging/src/k8s.io/api/batch/v1beta1/BUILD @@ -21,7 +21,6 @@ go_library( "//vendor/k8s.io/api/batch/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/staging/src/k8s.io/api/batch/v1beta1/doc.go b/staging/src/k8s.io/api/batch/v1beta1/doc.go index 653ebe04033..43020ed05cb 100644 --- a/staging/src/k8s.io/api/batch/v1beta1/doc.go +++ b/staging/src/k8s.io/api/batch/v1beta1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:openapi-gen=true package v1beta1 // import "k8s.io/api/batch/v1beta1" diff --git a/staging/src/k8s.io/api/batch/v1beta1/generated.pb.go b/staging/src/k8s.io/api/batch/v1beta1/generated.pb.go index 29a624b094a..6544184dbd6 100644 --- a/staging/src/k8s.io/api/batch/v1beta1/generated.pb.go +++ b/staging/src/k8s.io/api/batch/v1beta1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/api/batch/v1beta1/generated.proto b/staging/src/k8s.io/api/batch/v1beta1/generated.proto index f7b871632b2..9278a3d9bfc 100644 --- a/staging/src/k8s.io/api/batch/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/batch/v1beta1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -71,7 +71,10 @@ message CronJobSpec { optional int64 startingDeadlineSeconds = 2; // Specifies how to treat concurrent executions of a Job. - // Defaults to Allow. + // Valid values are: + // - "Allow" (default): allows CronJobs to run concurrently; + // - "Forbid": forbids concurrent runs, skipping next run if previous run hasn't finished yet; + // - "Replace": cancels currently running job and replaces it with a new one // +optional optional string concurrencyPolicy = 3; diff --git a/staging/src/k8s.io/api/batch/v1beta1/register.go b/staging/src/k8s.io/api/batch/v1beta1/register.go index 569817d3e4f..226de49f4d2 100644 --- a/staging/src/k8s.io/api/batch/v1beta1/register.go +++ b/staging/src/k8s.io/api/batch/v1beta1/register.go @@ -41,7 +41,7 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &JobTemplate{}, diff --git a/staging/src/k8s.io/api/batch/v1beta1/types.go b/staging/src/k8s.io/api/batch/v1beta1/types.go index ad13bba2f92..cb5c9bad294 100644 --- a/staging/src/k8s.io/api/batch/v1beta1/types.go +++ b/staging/src/k8s.io/api/batch/v1beta1/types.go @@ -100,7 +100,10 @@ type CronJobSpec struct { StartingDeadlineSeconds *int64 `json:"startingDeadlineSeconds,omitempty" protobuf:"varint,2,opt,name=startingDeadlineSeconds"` // Specifies how to treat concurrent executions of a Job. - // Defaults to Allow. + // Valid values are: + // - "Allow" (default): allows CronJobs to run concurrently; + // - "Forbid": forbids concurrent runs, skipping next run if previous run hasn't finished yet; + // - "Replace": cancels currently running job and replaces it with a new one // +optional ConcurrencyPolicy ConcurrencyPolicy `json:"concurrencyPolicy,omitempty" protobuf:"bytes,3,opt,name=concurrencyPolicy,casttype=ConcurrencyPolicy"` diff --git a/staging/src/k8s.io/api/batch/v1beta1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/batch/v1beta1/types_swagger_doc_generated.go index f4c74e4327e..3b53ac08a7d 100644 --- a/staging/src/k8s.io/api/batch/v1beta1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/batch/v1beta1/types_swagger_doc_generated.go @@ -52,7 +52,7 @@ var map_CronJobSpec = map[string]string{ "": "CronJobSpec describes how the job execution will look like and when it will actually run.", "schedule": "The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron.", "startingDeadlineSeconds": "Optional deadline in seconds for starting the job if it misses scheduled time for any reason. Missed jobs executions will be counted as failed ones.", - "concurrencyPolicy": "Specifies how to treat concurrent executions of a Job. Defaults to Allow.", + "concurrencyPolicy": "Specifies how to treat concurrent executions of a Job. Valid values are: - \"Allow\" (default): allows CronJobs to run concurrently; - \"Forbid\": forbids concurrent runs, skipping next run if previous run hasn't finished yet; - \"Replace\": cancels currently running job and replaces it with a new one", "suspend": "This flag tells the controller to suspend subsequent executions, it does not apply to already started executions. Defaults to false.", "jobTemplate": "Specifies the job that will be created when executing a CronJob.", "successfulJobsHistoryLimit": "The number of successful finished jobs to retain. This is a pointer to distinguish between explicit zero and not specified. Defaults to 3.", diff --git a/staging/src/k8s.io/api/batch/v1beta1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/batch/v1beta1/zz_generated.deepcopy.go index c730ca982e8..5282837731d 100644 --- a/staging/src/k8s.io/api/batch/v1beta1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/batch/v1beta1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,48 +23,9 @@ package v1beta1 import ( v1 "k8s.io/api/core/v1" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CronJob).DeepCopyInto(out.(*CronJob)) - return nil - }, InType: reflect.TypeOf(&CronJob{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CronJobList).DeepCopyInto(out.(*CronJobList)) - return nil - }, InType: reflect.TypeOf(&CronJobList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CronJobSpec).DeepCopyInto(out.(*CronJobSpec)) - return nil - }, InType: reflect.TypeOf(&CronJobSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CronJobStatus).DeepCopyInto(out.(*CronJobStatus)) - return nil - }, InType: reflect.TypeOf(&CronJobStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*JobTemplate).DeepCopyInto(out.(*JobTemplate)) - return nil - }, InType: reflect.TypeOf(&JobTemplate{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*JobTemplateSpec).DeepCopyInto(out.(*JobTemplateSpec)) - return nil - }, InType: reflect.TypeOf(&JobTemplateSpec{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CronJob) DeepCopyInto(out *CronJob) { *out = *in diff --git a/staging/src/k8s.io/api/batch/v2alpha1/BUILD b/staging/src/k8s.io/api/batch/v2alpha1/BUILD index 6c468eaa49b..0fc0ab57ac5 100644 --- a/staging/src/k8s.io/api/batch/v2alpha1/BUILD +++ b/staging/src/k8s.io/api/batch/v2alpha1/BUILD @@ -21,7 +21,6 @@ go_library( "//vendor/k8s.io/api/batch/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/staging/src/k8s.io/api/batch/v2alpha1/doc.go b/staging/src/k8s.io/api/batch/v2alpha1/doc.go index 288275d6eae..f4ed01ad84b 100644 --- a/staging/src/k8s.io/api/batch/v2alpha1/doc.go +++ b/staging/src/k8s.io/api/batch/v2alpha1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:openapi-gen=true package v2alpha1 // import "k8s.io/api/batch/v2alpha1" diff --git a/staging/src/k8s.io/api/batch/v2alpha1/generated.pb.go b/staging/src/k8s.io/api/batch/v2alpha1/generated.pb.go index af90de0f569..2560953eb8b 100644 --- a/staging/src/k8s.io/api/batch/v2alpha1/generated.pb.go +++ b/staging/src/k8s.io/api/batch/v2alpha1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/api/batch/v2alpha1/generated.proto b/staging/src/k8s.io/api/batch/v2alpha1/generated.proto index 0bdb247e311..e4de3644c38 100644 --- a/staging/src/k8s.io/api/batch/v2alpha1/generated.proto +++ b/staging/src/k8s.io/api/batch/v2alpha1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -71,7 +71,10 @@ message CronJobSpec { optional int64 startingDeadlineSeconds = 2; // Specifies how to treat concurrent executions of a Job. - // Defaults to Allow. + // Valid values are: + // - "Allow" (default): allows CronJobs to run concurrently; + // - "Forbid": forbids concurrent runs, skipping next run if previous run hasn't finished yet; + // - "Replace": cancels currently running job and replaces it with a new one // +optional optional string concurrencyPolicy = 3; diff --git a/staging/src/k8s.io/api/batch/v2alpha1/register.go b/staging/src/k8s.io/api/batch/v2alpha1/register.go index 4b02c0f481c..ac7fa5087a9 100644 --- a/staging/src/k8s.io/api/batch/v2alpha1/register.go +++ b/staging/src/k8s.io/api/batch/v2alpha1/register.go @@ -41,7 +41,7 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &JobTemplate{}, diff --git a/staging/src/k8s.io/api/batch/v2alpha1/types.go b/staging/src/k8s.io/api/batch/v2alpha1/types.go index 451df40e637..cccff94ff2a 100644 --- a/staging/src/k8s.io/api/batch/v2alpha1/types.go +++ b/staging/src/k8s.io/api/batch/v2alpha1/types.go @@ -100,7 +100,10 @@ type CronJobSpec struct { StartingDeadlineSeconds *int64 `json:"startingDeadlineSeconds,omitempty" protobuf:"varint,2,opt,name=startingDeadlineSeconds"` // Specifies how to treat concurrent executions of a Job. - // Defaults to Allow. + // Valid values are: + // - "Allow" (default): allows CronJobs to run concurrently; + // - "Forbid": forbids concurrent runs, skipping next run if previous run hasn't finished yet; + // - "Replace": cancels currently running job and replaces it with a new one // +optional ConcurrencyPolicy ConcurrencyPolicy `json:"concurrencyPolicy,omitempty" protobuf:"bytes,3,opt,name=concurrencyPolicy,casttype=ConcurrencyPolicy"` diff --git a/staging/src/k8s.io/api/batch/v2alpha1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/batch/v2alpha1/types_swagger_doc_generated.go index 2b3ed4c5602..d166b807fae 100644 --- a/staging/src/k8s.io/api/batch/v2alpha1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/batch/v2alpha1/types_swagger_doc_generated.go @@ -52,7 +52,7 @@ var map_CronJobSpec = map[string]string{ "": "CronJobSpec describes how the job execution will look like and when it will actually run.", "schedule": "The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron.", "startingDeadlineSeconds": "Optional deadline in seconds for starting the job if it misses scheduled time for any reason. Missed jobs executions will be counted as failed ones.", - "concurrencyPolicy": "Specifies how to treat concurrent executions of a Job. Defaults to Allow.", + "concurrencyPolicy": "Specifies how to treat concurrent executions of a Job. Valid values are: - \"Allow\" (default): allows CronJobs to run concurrently; - \"Forbid\": forbids concurrent runs, skipping next run if previous run hasn't finished yet; - \"Replace\": cancels currently running job and replaces it with a new one", "suspend": "This flag tells the controller to suspend subsequent executions, it does not apply to already started executions. Defaults to false.", "jobTemplate": "Specifies the job that will be created when executing a CronJob.", "successfulJobsHistoryLimit": "The number of successful finished jobs to retain. This is a pointer to distinguish between explicit zero and not specified.", diff --git a/staging/src/k8s.io/api/batch/v2alpha1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/batch/v2alpha1/zz_generated.deepcopy.go index b572f9ca3ae..387e5610ef6 100644 --- a/staging/src/k8s.io/api/batch/v2alpha1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/batch/v2alpha1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,48 +23,9 @@ package v2alpha1 import ( v1 "k8s.io/api/core/v1" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CronJob).DeepCopyInto(out.(*CronJob)) - return nil - }, InType: reflect.TypeOf(&CronJob{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CronJobList).DeepCopyInto(out.(*CronJobList)) - return nil - }, InType: reflect.TypeOf(&CronJobList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CronJobSpec).DeepCopyInto(out.(*CronJobSpec)) - return nil - }, InType: reflect.TypeOf(&CronJobSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CronJobStatus).DeepCopyInto(out.(*CronJobStatus)) - return nil - }, InType: reflect.TypeOf(&CronJobStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*JobTemplate).DeepCopyInto(out.(*JobTemplate)) - return nil - }, InType: reflect.TypeOf(&JobTemplate{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*JobTemplateSpec).DeepCopyInto(out.(*JobTemplateSpec)) - return nil - }, InType: reflect.TypeOf(&JobTemplateSpec{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CronJob) DeepCopyInto(out *CronJob) { *out = *in diff --git a/staging/src/k8s.io/api/certificates/v1beta1/BUILD b/staging/src/k8s.io/api/certificates/v1beta1/BUILD index 95378e3ff37..4c94dd06481 100644 --- a/staging/src/k8s.io/api/certificates/v1beta1/BUILD +++ b/staging/src/k8s.io/api/certificates/v1beta1/BUILD @@ -20,7 +20,6 @@ go_library( "//vendor/github.com/gogo/protobuf/proto:go_default_library", "//vendor/github.com/gogo/protobuf/sortkeys:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/staging/src/k8s.io/api/certificates/v1beta1/doc.go b/staging/src/k8s.io/api/certificates/v1beta1/doc.go index d98ee7c3d7d..fb23aadb0e9 100644 --- a/staging/src/k8s.io/api/certificates/v1beta1/doc.go +++ b/staging/src/k8s.io/api/certificates/v1beta1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:openapi-gen=true // +groupName=certificates.k8s.io 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 4e09a4bd3cc..7f704bf82ae 100644 --- a/staging/src/k8s.io/api/certificates/v1beta1/generated.pb.go +++ b/staging/src/k8s.io/api/certificates/v1beta1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/api/certificates/v1beta1/generated.proto b/staging/src/k8s.io/api/certificates/v1beta1/generated.proto index e90f4f9cc35..e3cd9000fa8 100644 --- a/staging/src/k8s.io/api/certificates/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/certificates/v1beta1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/api/certificates/v1beta1/register.go b/staging/src/k8s.io/api/certificates/v1beta1/register.go index b5ba3d7eca8..b4f3af9b9ca 100644 --- a/staging/src/k8s.io/api/certificates/v1beta1/register.go +++ b/staging/src/k8s.io/api/certificates/v1beta1/register.go @@ -46,7 +46,7 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &CertificateSigningRequest{}, 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 6ecc1311816..53634ad17b6 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 @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,44 +21,9 @@ limitations under the License. package v1beta1 import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CertificateSigningRequest).DeepCopyInto(out.(*CertificateSigningRequest)) - return nil - }, InType: reflect.TypeOf(&CertificateSigningRequest{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CertificateSigningRequestCondition).DeepCopyInto(out.(*CertificateSigningRequestCondition)) - return nil - }, InType: reflect.TypeOf(&CertificateSigningRequestCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CertificateSigningRequestList).DeepCopyInto(out.(*CertificateSigningRequestList)) - return nil - }, InType: reflect.TypeOf(&CertificateSigningRequestList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CertificateSigningRequestSpec).DeepCopyInto(out.(*CertificateSigningRequestSpec)) - return nil - }, InType: reflect.TypeOf(&CertificateSigningRequestSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CertificateSigningRequestStatus).DeepCopyInto(out.(*CertificateSigningRequestStatus)) - return nil - }, InType: reflect.TypeOf(&CertificateSigningRequestStatus{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CertificateSigningRequest) DeepCopyInto(out *CertificateSigningRequest) { *out = *in diff --git a/staging/src/k8s.io/api/code-of-conduct.md b/staging/src/k8s.io/api/code-of-conduct.md new file mode 100644 index 00000000000..0d15c00cf32 --- /dev/null +++ b/staging/src/k8s.io/api/code-of-conduct.md @@ -0,0 +1,3 @@ +# Kubernetes Community Code of Conduct + +Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) diff --git a/staging/src/k8s.io/api/core/OWNERS b/staging/src/k8s.io/api/core/OWNERS deleted file mode 100644 index 7009d88bf10..00000000000 --- a/staging/src/k8s.io/api/core/OWNERS +++ /dev/null @@ -1,42 +0,0 @@ -approvers: -- erictune -- lavalamp -- smarterclayton -- thockin -reviewers: -- thockin -- lavalamp -- smarterclayton -- wojtek-t -- deads2k -- yujuhong -- brendandburns -- derekwaynecarr -- caesarxuchao -- vishh -- mikedanese -- liggitt -- nikhiljindal -- gmarek -- erictune -- davidopp -- pmorie -- sttts -- dchen1107 -- saad-ali -- zmerlynn -- luxas -- janetkuo -- justinsb -- pwittrock -- roberthbailey -- ncdc -- tallclair -- yifan-gu -- eparis -- mwielgus -- timothysc -- soltysh -- piosz -- jsafrane -- jbeda diff --git a/staging/src/k8s.io/api/core/v1/BUILD b/staging/src/k8s.io/api/core/v1/BUILD index da0d5bf6a21..80409236152 100644 --- a/staging/src/k8s.io/api/core/v1/BUILD +++ b/staging/src/k8s.io/api/core/v1/BUILD @@ -12,8 +12,8 @@ go_test( "taint_test.go", "toleration_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/api/core/v1", - library = ":go_default_library", ) go_library( @@ -38,7 +38,6 @@ go_library( "//vendor/github.com/gogo/protobuf/sortkeys:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", diff --git a/staging/src/k8s.io/api/core/v1/annotation_key_constants.go b/staging/src/k8s.io/api/core/v1/annotation_key_constants.go index e623913fdd1..de4e3cee447 100644 --- a/staging/src/k8s.io/api/core/v1/annotation_key_constants.go +++ b/staging/src/k8s.io/api/core/v1/annotation_key_constants.go @@ -45,12 +45,6 @@ const ( // to one container of a pod. SeccompContainerAnnotationKeyPrefix string = "container.seccomp.security.alpha.kubernetes.io/" - // CreatedByAnnotation represents the key used to store the spec(json) - // used to create the resource. - // This field is deprecated in favor of ControllerRef (see #44407). - // TODO(#50720): Remove this field in v1.9. - CreatedByAnnotation = "kubernetes.io/created-by" - // PreferAvoidPodsAnnotationKey represents the key of preferAvoidPods data (json serialized) // in the Annotations of a Node. PreferAvoidPodsAnnotationKey string = "scheduler.alpha.kubernetes.io/preferAvoidPods" diff --git a/staging/src/k8s.io/api/core/v1/doc.go b/staging/src/k8s.io/api/core/v1/doc.go index a31af9ea49a..96994c62452 100644 --- a/staging/src/k8s.io/api/core/v1/doc.go +++ b/staging/src/k8s.io/api/core/v1/doc.go @@ -15,7 +15,7 @@ limitations under the License. */ // +k8s:openapi-gen=true -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // Package v1 is the v1 version of the core API. package v1 // import "k8s.io/api/core/v1" 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 14075df3ccc..61aa1833f85 100644 --- a/staging/src/k8s.io/api/core/v1/generated.pb.go +++ b/staging/src/k8s.io/api/core/v1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -33,6 +33,7 @@ limitations under the License. AzureFilePersistentVolumeSource AzureFileVolumeSource Binding + CSIPersistentVolumeSource Capabilities CephFSPersistentVolumeSource CephFSVolumeSource @@ -71,9 +72,11 @@ limitations under the License. EnvVarSource Event EventList + EventSeries EventSource ExecAction FCVolumeSource + FlexPersistentVolumeSource FlexVolumeSource FlockerVolumeSource GCEPersistentDiskVolumeSource @@ -84,6 +87,7 @@ limitations under the License. Handler HostAlias HostPathVolumeSource + ISCSIPersistentVolumeSource ISCSIVolumeSource KeyToPath Lifecycle @@ -138,6 +142,8 @@ limitations under the License. PodAntiAffinity PodAttachOptions PodCondition + PodDNSConfig + PodDNSConfigOption PodExecOptions PodList PodLogOptions @@ -158,6 +164,7 @@ limitations under the License. Probe ProjectedVolumeSource QuobyteVolumeSource + RBDPersistentVolumeSource RBDVolumeSource RangeAllocation ReplicationController @@ -172,6 +179,7 @@ limitations under the License. ResourceQuotaStatus ResourceRequirements SELinuxOptions + ScaleIOPersistentVolumeSource ScaleIOVolumeSource Secret SecretEnvSource @@ -198,6 +206,7 @@ limitations under the License. Taint Toleration Volume + VolumeDevice VolumeMount VolumeProjection VolumeSource @@ -270,726 +279,772 @@ func (m *Binding) Reset() { *m = Binding{} } func (*Binding) ProtoMessage() {} func (*Binding) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{7} } +func (m *CSIPersistentVolumeSource) Reset() { *m = CSIPersistentVolumeSource{} } +func (*CSIPersistentVolumeSource) ProtoMessage() {} +func (*CSIPersistentVolumeSource) Descriptor() ([]byte, []int) { + return fileDescriptorGenerated, []int{8} +} + func (m *Capabilities) Reset() { *m = Capabilities{} } func (*Capabilities) ProtoMessage() {} -func (*Capabilities) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{8} } +func (*Capabilities) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{9} } func (m *CephFSPersistentVolumeSource) Reset() { *m = CephFSPersistentVolumeSource{} } func (*CephFSPersistentVolumeSource) ProtoMessage() {} func (*CephFSPersistentVolumeSource) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{9} + return fileDescriptorGenerated, []int{10} } func (m *CephFSVolumeSource) Reset() { *m = CephFSVolumeSource{} } func (*CephFSVolumeSource) ProtoMessage() {} -func (*CephFSVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{10} } +func (*CephFSVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{11} } func (m *CinderVolumeSource) Reset() { *m = CinderVolumeSource{} } func (*CinderVolumeSource) ProtoMessage() {} -func (*CinderVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{11} } +func (*CinderVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{12} } func (m *ClientIPConfig) Reset() { *m = ClientIPConfig{} } func (*ClientIPConfig) ProtoMessage() {} -func (*ClientIPConfig) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{12} } +func (*ClientIPConfig) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{13} } func (m *ComponentCondition) Reset() { *m = ComponentCondition{} } func (*ComponentCondition) ProtoMessage() {} -func (*ComponentCondition) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{13} } +func (*ComponentCondition) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{14} } func (m *ComponentStatus) Reset() { *m = ComponentStatus{} } func (*ComponentStatus) ProtoMessage() {} -func (*ComponentStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{14} } +func (*ComponentStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{15} } func (m *ComponentStatusList) Reset() { *m = ComponentStatusList{} } func (*ComponentStatusList) ProtoMessage() {} -func (*ComponentStatusList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{15} } +func (*ComponentStatusList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{16} } func (m *ConfigMap) Reset() { *m = ConfigMap{} } func (*ConfigMap) ProtoMessage() {} -func (*ConfigMap) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{16} } +func (*ConfigMap) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{17} } func (m *ConfigMapEnvSource) Reset() { *m = ConfigMapEnvSource{} } func (*ConfigMapEnvSource) ProtoMessage() {} -func (*ConfigMapEnvSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{17} } +func (*ConfigMapEnvSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{18} } func (m *ConfigMapKeySelector) Reset() { *m = ConfigMapKeySelector{} } func (*ConfigMapKeySelector) ProtoMessage() {} -func (*ConfigMapKeySelector) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{18} } +func (*ConfigMapKeySelector) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{19} } func (m *ConfigMapList) Reset() { *m = ConfigMapList{} } func (*ConfigMapList) ProtoMessage() {} -func (*ConfigMapList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{19} } +func (*ConfigMapList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{20} } func (m *ConfigMapProjection) Reset() { *m = ConfigMapProjection{} } func (*ConfigMapProjection) ProtoMessage() {} -func (*ConfigMapProjection) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{20} } +func (*ConfigMapProjection) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{21} } func (m *ConfigMapVolumeSource) Reset() { *m = ConfigMapVolumeSource{} } func (*ConfigMapVolumeSource) ProtoMessage() {} -func (*ConfigMapVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{21} } +func (*ConfigMapVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{22} } func (m *Container) Reset() { *m = Container{} } func (*Container) ProtoMessage() {} -func (*Container) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{22} } +func (*Container) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{23} } func (m *ContainerImage) Reset() { *m = ContainerImage{} } func (*ContainerImage) ProtoMessage() {} -func (*ContainerImage) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{23} } +func (*ContainerImage) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{24} } func (m *ContainerPort) Reset() { *m = ContainerPort{} } func (*ContainerPort) ProtoMessage() {} -func (*ContainerPort) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{24} } +func (*ContainerPort) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{25} } func (m *ContainerState) Reset() { *m = ContainerState{} } func (*ContainerState) ProtoMessage() {} -func (*ContainerState) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{25} } +func (*ContainerState) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{26} } func (m *ContainerStateRunning) Reset() { *m = ContainerStateRunning{} } func (*ContainerStateRunning) ProtoMessage() {} -func (*ContainerStateRunning) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{26} } +func (*ContainerStateRunning) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{27} } func (m *ContainerStateTerminated) Reset() { *m = ContainerStateTerminated{} } func (*ContainerStateTerminated) ProtoMessage() {} func (*ContainerStateTerminated) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{27} + return fileDescriptorGenerated, []int{28} } func (m *ContainerStateWaiting) Reset() { *m = ContainerStateWaiting{} } func (*ContainerStateWaiting) ProtoMessage() {} -func (*ContainerStateWaiting) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{28} } +func (*ContainerStateWaiting) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{29} } func (m *ContainerStatus) Reset() { *m = ContainerStatus{} } func (*ContainerStatus) ProtoMessage() {} -func (*ContainerStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{29} } +func (*ContainerStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{30} } func (m *DaemonEndpoint) Reset() { *m = DaemonEndpoint{} } func (*DaemonEndpoint) ProtoMessage() {} -func (*DaemonEndpoint) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{30} } +func (*DaemonEndpoint) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{31} } func (m *DeleteOptions) Reset() { *m = DeleteOptions{} } func (*DeleteOptions) ProtoMessage() {} -func (*DeleteOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{31} } +func (*DeleteOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{32} } func (m *DownwardAPIProjection) Reset() { *m = DownwardAPIProjection{} } func (*DownwardAPIProjection) ProtoMessage() {} -func (*DownwardAPIProjection) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{32} } +func (*DownwardAPIProjection) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{33} } func (m *DownwardAPIVolumeFile) Reset() { *m = DownwardAPIVolumeFile{} } func (*DownwardAPIVolumeFile) ProtoMessage() {} -func (*DownwardAPIVolumeFile) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{33} } +func (*DownwardAPIVolumeFile) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{34} } func (m *DownwardAPIVolumeSource) Reset() { *m = DownwardAPIVolumeSource{} } func (*DownwardAPIVolumeSource) ProtoMessage() {} func (*DownwardAPIVolumeSource) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{34} + return fileDescriptorGenerated, []int{35} } func (m *EmptyDirVolumeSource) Reset() { *m = EmptyDirVolumeSource{} } func (*EmptyDirVolumeSource) ProtoMessage() {} -func (*EmptyDirVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{35} } +func (*EmptyDirVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{36} } func (m *EndpointAddress) Reset() { *m = EndpointAddress{} } func (*EndpointAddress) ProtoMessage() {} -func (*EndpointAddress) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{36} } +func (*EndpointAddress) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{37} } func (m *EndpointPort) Reset() { *m = EndpointPort{} } func (*EndpointPort) ProtoMessage() {} -func (*EndpointPort) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{37} } +func (*EndpointPort) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{38} } func (m *EndpointSubset) Reset() { *m = EndpointSubset{} } func (*EndpointSubset) ProtoMessage() {} -func (*EndpointSubset) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{38} } +func (*EndpointSubset) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{39} } func (m *Endpoints) Reset() { *m = Endpoints{} } func (*Endpoints) ProtoMessage() {} -func (*Endpoints) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{39} } +func (*Endpoints) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{40} } func (m *EndpointsList) Reset() { *m = EndpointsList{} } func (*EndpointsList) ProtoMessage() {} -func (*EndpointsList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{40} } +func (*EndpointsList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{41} } func (m *EnvFromSource) Reset() { *m = EnvFromSource{} } func (*EnvFromSource) ProtoMessage() {} -func (*EnvFromSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{41} } +func (*EnvFromSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{42} } func (m *EnvVar) Reset() { *m = EnvVar{} } func (*EnvVar) ProtoMessage() {} -func (*EnvVar) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{42} } +func (*EnvVar) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{43} } func (m *EnvVarSource) Reset() { *m = EnvVarSource{} } func (*EnvVarSource) ProtoMessage() {} -func (*EnvVarSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{43} } +func (*EnvVarSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{44} } func (m *Event) Reset() { *m = Event{} } func (*Event) ProtoMessage() {} -func (*Event) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{44} } +func (*Event) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{45} } func (m *EventList) Reset() { *m = EventList{} } func (*EventList) ProtoMessage() {} -func (*EventList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{45} } +func (*EventList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{46} } + +func (m *EventSeries) Reset() { *m = EventSeries{} } +func (*EventSeries) ProtoMessage() {} +func (*EventSeries) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{47} } func (m *EventSource) Reset() { *m = EventSource{} } func (*EventSource) ProtoMessage() {} -func (*EventSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{46} } +func (*EventSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{48} } func (m *ExecAction) Reset() { *m = ExecAction{} } func (*ExecAction) ProtoMessage() {} -func (*ExecAction) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{47} } +func (*ExecAction) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{49} } func (m *FCVolumeSource) Reset() { *m = FCVolumeSource{} } func (*FCVolumeSource) ProtoMessage() {} -func (*FCVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{48} } +func (*FCVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{50} } + +func (m *FlexPersistentVolumeSource) Reset() { *m = FlexPersistentVolumeSource{} } +func (*FlexPersistentVolumeSource) ProtoMessage() {} +func (*FlexPersistentVolumeSource) Descriptor() ([]byte, []int) { + return fileDescriptorGenerated, []int{51} +} func (m *FlexVolumeSource) Reset() { *m = FlexVolumeSource{} } func (*FlexVolumeSource) ProtoMessage() {} -func (*FlexVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{49} } +func (*FlexVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{52} } func (m *FlockerVolumeSource) Reset() { *m = FlockerVolumeSource{} } func (*FlockerVolumeSource) ProtoMessage() {} -func (*FlockerVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{50} } +func (*FlockerVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{53} } func (m *GCEPersistentDiskVolumeSource) Reset() { *m = GCEPersistentDiskVolumeSource{} } func (*GCEPersistentDiskVolumeSource) ProtoMessage() {} func (*GCEPersistentDiskVolumeSource) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{51} + return fileDescriptorGenerated, []int{54} } func (m *GitRepoVolumeSource) Reset() { *m = GitRepoVolumeSource{} } func (*GitRepoVolumeSource) ProtoMessage() {} -func (*GitRepoVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{52} } +func (*GitRepoVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{55} } func (m *GlusterfsVolumeSource) Reset() { *m = GlusterfsVolumeSource{} } func (*GlusterfsVolumeSource) ProtoMessage() {} -func (*GlusterfsVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{53} } +func (*GlusterfsVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{56} } func (m *HTTPGetAction) Reset() { *m = HTTPGetAction{} } func (*HTTPGetAction) ProtoMessage() {} -func (*HTTPGetAction) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{54} } +func (*HTTPGetAction) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{57} } func (m *HTTPHeader) Reset() { *m = HTTPHeader{} } func (*HTTPHeader) ProtoMessage() {} -func (*HTTPHeader) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{55} } +func (*HTTPHeader) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{58} } func (m *Handler) Reset() { *m = Handler{} } func (*Handler) ProtoMessage() {} -func (*Handler) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{56} } +func (*Handler) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{59} } func (m *HostAlias) Reset() { *m = HostAlias{} } func (*HostAlias) ProtoMessage() {} -func (*HostAlias) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{57} } +func (*HostAlias) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{60} } func (m *HostPathVolumeSource) Reset() { *m = HostPathVolumeSource{} } func (*HostPathVolumeSource) ProtoMessage() {} -func (*HostPathVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{58} } +func (*HostPathVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{61} } + +func (m *ISCSIPersistentVolumeSource) Reset() { *m = ISCSIPersistentVolumeSource{} } +func (*ISCSIPersistentVolumeSource) ProtoMessage() {} +func (*ISCSIPersistentVolumeSource) Descriptor() ([]byte, []int) { + return fileDescriptorGenerated, []int{62} +} func (m *ISCSIVolumeSource) Reset() { *m = ISCSIVolumeSource{} } func (*ISCSIVolumeSource) ProtoMessage() {} -func (*ISCSIVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{59} } +func (*ISCSIVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{63} } func (m *KeyToPath) Reset() { *m = KeyToPath{} } func (*KeyToPath) ProtoMessage() {} -func (*KeyToPath) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{60} } +func (*KeyToPath) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{64} } func (m *Lifecycle) Reset() { *m = Lifecycle{} } func (*Lifecycle) ProtoMessage() {} -func (*Lifecycle) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{61} } +func (*Lifecycle) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{65} } func (m *LimitRange) Reset() { *m = LimitRange{} } func (*LimitRange) ProtoMessage() {} -func (*LimitRange) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{62} } +func (*LimitRange) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{66} } func (m *LimitRangeItem) Reset() { *m = LimitRangeItem{} } func (*LimitRangeItem) ProtoMessage() {} -func (*LimitRangeItem) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{63} } +func (*LimitRangeItem) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{67} } func (m *LimitRangeList) Reset() { *m = LimitRangeList{} } func (*LimitRangeList) ProtoMessage() {} -func (*LimitRangeList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{64} } +func (*LimitRangeList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{68} } func (m *LimitRangeSpec) Reset() { *m = LimitRangeSpec{} } func (*LimitRangeSpec) ProtoMessage() {} -func (*LimitRangeSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{65} } +func (*LimitRangeSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{69} } func (m *List) Reset() { *m = List{} } func (*List) ProtoMessage() {} -func (*List) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{66} } +func (*List) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{70} } func (m *ListOptions) Reset() { *m = ListOptions{} } func (*ListOptions) ProtoMessage() {} -func (*ListOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{67} } +func (*ListOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{71} } func (m *LoadBalancerIngress) Reset() { *m = LoadBalancerIngress{} } func (*LoadBalancerIngress) ProtoMessage() {} -func (*LoadBalancerIngress) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{68} } +func (*LoadBalancerIngress) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{72} } func (m *LoadBalancerStatus) Reset() { *m = LoadBalancerStatus{} } func (*LoadBalancerStatus) ProtoMessage() {} -func (*LoadBalancerStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{69} } +func (*LoadBalancerStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{73} } func (m *LocalObjectReference) Reset() { *m = LocalObjectReference{} } func (*LocalObjectReference) ProtoMessage() {} -func (*LocalObjectReference) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{70} } +func (*LocalObjectReference) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{74} } func (m *LocalVolumeSource) Reset() { *m = LocalVolumeSource{} } func (*LocalVolumeSource) ProtoMessage() {} -func (*LocalVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{71} } +func (*LocalVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{75} } func (m *NFSVolumeSource) Reset() { *m = NFSVolumeSource{} } func (*NFSVolumeSource) ProtoMessage() {} -func (*NFSVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{72} } +func (*NFSVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{76} } func (m *Namespace) Reset() { *m = Namespace{} } func (*Namespace) ProtoMessage() {} -func (*Namespace) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{73} } +func (*Namespace) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{77} } func (m *NamespaceList) Reset() { *m = NamespaceList{} } func (*NamespaceList) ProtoMessage() {} -func (*NamespaceList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{74} } +func (*NamespaceList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{78} } func (m *NamespaceSpec) Reset() { *m = NamespaceSpec{} } func (*NamespaceSpec) ProtoMessage() {} -func (*NamespaceSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{75} } +func (*NamespaceSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{79} } func (m *NamespaceStatus) Reset() { *m = NamespaceStatus{} } func (*NamespaceStatus) ProtoMessage() {} -func (*NamespaceStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{76} } +func (*NamespaceStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{80} } func (m *Node) Reset() { *m = Node{} } func (*Node) ProtoMessage() {} -func (*Node) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{77} } +func (*Node) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{81} } func (m *NodeAddress) Reset() { *m = NodeAddress{} } func (*NodeAddress) ProtoMessage() {} -func (*NodeAddress) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{78} } +func (*NodeAddress) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{82} } func (m *NodeAffinity) Reset() { *m = NodeAffinity{} } func (*NodeAffinity) ProtoMessage() {} -func (*NodeAffinity) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{79} } +func (*NodeAffinity) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{83} } func (m *NodeCondition) Reset() { *m = NodeCondition{} } func (*NodeCondition) ProtoMessage() {} -func (*NodeCondition) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{80} } +func (*NodeCondition) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{84} } func (m *NodeConfigSource) Reset() { *m = NodeConfigSource{} } func (*NodeConfigSource) ProtoMessage() {} -func (*NodeConfigSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{81} } +func (*NodeConfigSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{85} } func (m *NodeDaemonEndpoints) Reset() { *m = NodeDaemonEndpoints{} } func (*NodeDaemonEndpoints) ProtoMessage() {} -func (*NodeDaemonEndpoints) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{82} } +func (*NodeDaemonEndpoints) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{86} } func (m *NodeList) Reset() { *m = NodeList{} } func (*NodeList) ProtoMessage() {} -func (*NodeList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{83} } +func (*NodeList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{87} } func (m *NodeProxyOptions) Reset() { *m = NodeProxyOptions{} } func (*NodeProxyOptions) ProtoMessage() {} -func (*NodeProxyOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{84} } +func (*NodeProxyOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{88} } func (m *NodeResources) Reset() { *m = NodeResources{} } func (*NodeResources) ProtoMessage() {} -func (*NodeResources) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{85} } +func (*NodeResources) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{89} } func (m *NodeSelector) Reset() { *m = NodeSelector{} } func (*NodeSelector) ProtoMessage() {} -func (*NodeSelector) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{86} } +func (*NodeSelector) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{90} } func (m *NodeSelectorRequirement) Reset() { *m = NodeSelectorRequirement{} } func (*NodeSelectorRequirement) ProtoMessage() {} func (*NodeSelectorRequirement) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{87} + return fileDescriptorGenerated, []int{91} } func (m *NodeSelectorTerm) Reset() { *m = NodeSelectorTerm{} } func (*NodeSelectorTerm) ProtoMessage() {} -func (*NodeSelectorTerm) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{88} } +func (*NodeSelectorTerm) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{92} } func (m *NodeSpec) Reset() { *m = NodeSpec{} } func (*NodeSpec) ProtoMessage() {} -func (*NodeSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{89} } +func (*NodeSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{93} } func (m *NodeStatus) Reset() { *m = NodeStatus{} } func (*NodeStatus) ProtoMessage() {} -func (*NodeStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{90} } +func (*NodeStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{94} } func (m *NodeSystemInfo) Reset() { *m = NodeSystemInfo{} } func (*NodeSystemInfo) ProtoMessage() {} -func (*NodeSystemInfo) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{91} } +func (*NodeSystemInfo) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{95} } func (m *ObjectFieldSelector) Reset() { *m = ObjectFieldSelector{} } func (*ObjectFieldSelector) ProtoMessage() {} -func (*ObjectFieldSelector) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{92} } +func (*ObjectFieldSelector) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{96} } func (m *ObjectMeta) Reset() { *m = ObjectMeta{} } func (*ObjectMeta) ProtoMessage() {} -func (*ObjectMeta) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{93} } +func (*ObjectMeta) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{97} } func (m *ObjectReference) Reset() { *m = ObjectReference{} } func (*ObjectReference) ProtoMessage() {} -func (*ObjectReference) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{94} } +func (*ObjectReference) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{98} } func (m *PersistentVolume) Reset() { *m = PersistentVolume{} } func (*PersistentVolume) ProtoMessage() {} -func (*PersistentVolume) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{95} } +func (*PersistentVolume) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{99} } func (m *PersistentVolumeClaim) Reset() { *m = PersistentVolumeClaim{} } func (*PersistentVolumeClaim) ProtoMessage() {} -func (*PersistentVolumeClaim) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{96} } +func (*PersistentVolumeClaim) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{100} } func (m *PersistentVolumeClaimCondition) Reset() { *m = PersistentVolumeClaimCondition{} } func (*PersistentVolumeClaimCondition) ProtoMessage() {} func (*PersistentVolumeClaimCondition) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{97} + return fileDescriptorGenerated, []int{101} } func (m *PersistentVolumeClaimList) Reset() { *m = PersistentVolumeClaimList{} } func (*PersistentVolumeClaimList) ProtoMessage() {} func (*PersistentVolumeClaimList) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{98} + return fileDescriptorGenerated, []int{102} } func (m *PersistentVolumeClaimSpec) Reset() { *m = PersistentVolumeClaimSpec{} } func (*PersistentVolumeClaimSpec) ProtoMessage() {} func (*PersistentVolumeClaimSpec) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{99} + return fileDescriptorGenerated, []int{103} } func (m *PersistentVolumeClaimStatus) Reset() { *m = PersistentVolumeClaimStatus{} } func (*PersistentVolumeClaimStatus) ProtoMessage() {} func (*PersistentVolumeClaimStatus) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{100} + return fileDescriptorGenerated, []int{104} } func (m *PersistentVolumeClaimVolumeSource) Reset() { *m = PersistentVolumeClaimVolumeSource{} } func (*PersistentVolumeClaimVolumeSource) ProtoMessage() {} func (*PersistentVolumeClaimVolumeSource) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{101} + return fileDescriptorGenerated, []int{105} } func (m *PersistentVolumeList) Reset() { *m = PersistentVolumeList{} } func (*PersistentVolumeList) ProtoMessage() {} -func (*PersistentVolumeList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{102} } +func (*PersistentVolumeList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{106} } func (m *PersistentVolumeSource) Reset() { *m = PersistentVolumeSource{} } func (*PersistentVolumeSource) ProtoMessage() {} func (*PersistentVolumeSource) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{103} + return fileDescriptorGenerated, []int{107} } func (m *PersistentVolumeSpec) Reset() { *m = PersistentVolumeSpec{} } func (*PersistentVolumeSpec) ProtoMessage() {} -func (*PersistentVolumeSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{104} } +func (*PersistentVolumeSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{108} } func (m *PersistentVolumeStatus) Reset() { *m = PersistentVolumeStatus{} } func (*PersistentVolumeStatus) ProtoMessage() {} func (*PersistentVolumeStatus) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{105} + return fileDescriptorGenerated, []int{109} } func (m *PhotonPersistentDiskVolumeSource) Reset() { *m = PhotonPersistentDiskVolumeSource{} } func (*PhotonPersistentDiskVolumeSource) ProtoMessage() {} func (*PhotonPersistentDiskVolumeSource) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{106} + return fileDescriptorGenerated, []int{110} } func (m *Pod) Reset() { *m = Pod{} } func (*Pod) ProtoMessage() {} -func (*Pod) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{107} } +func (*Pod) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{111} } func (m *PodAffinity) Reset() { *m = PodAffinity{} } func (*PodAffinity) ProtoMessage() {} -func (*PodAffinity) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{108} } +func (*PodAffinity) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{112} } func (m *PodAffinityTerm) Reset() { *m = PodAffinityTerm{} } func (*PodAffinityTerm) ProtoMessage() {} -func (*PodAffinityTerm) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{109} } +func (*PodAffinityTerm) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{113} } func (m *PodAntiAffinity) Reset() { *m = PodAntiAffinity{} } func (*PodAntiAffinity) ProtoMessage() {} -func (*PodAntiAffinity) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{110} } +func (*PodAntiAffinity) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{114} } func (m *PodAttachOptions) Reset() { *m = PodAttachOptions{} } func (*PodAttachOptions) ProtoMessage() {} -func (*PodAttachOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{111} } +func (*PodAttachOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{115} } func (m *PodCondition) Reset() { *m = PodCondition{} } func (*PodCondition) ProtoMessage() {} -func (*PodCondition) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{112} } +func (*PodCondition) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{116} } + +func (m *PodDNSConfig) Reset() { *m = PodDNSConfig{} } +func (*PodDNSConfig) ProtoMessage() {} +func (*PodDNSConfig) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{117} } + +func (m *PodDNSConfigOption) Reset() { *m = PodDNSConfigOption{} } +func (*PodDNSConfigOption) ProtoMessage() {} +func (*PodDNSConfigOption) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{118} } func (m *PodExecOptions) Reset() { *m = PodExecOptions{} } func (*PodExecOptions) ProtoMessage() {} -func (*PodExecOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{113} } +func (*PodExecOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{119} } func (m *PodList) Reset() { *m = PodList{} } func (*PodList) ProtoMessage() {} -func (*PodList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{114} } +func (*PodList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{120} } func (m *PodLogOptions) Reset() { *m = PodLogOptions{} } func (*PodLogOptions) ProtoMessage() {} -func (*PodLogOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{115} } +func (*PodLogOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{121} } func (m *PodPortForwardOptions) Reset() { *m = PodPortForwardOptions{} } func (*PodPortForwardOptions) ProtoMessage() {} -func (*PodPortForwardOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{116} } +func (*PodPortForwardOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{122} } func (m *PodProxyOptions) Reset() { *m = PodProxyOptions{} } func (*PodProxyOptions) ProtoMessage() {} -func (*PodProxyOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{117} } +func (*PodProxyOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{123} } func (m *PodSecurityContext) Reset() { *m = PodSecurityContext{} } func (*PodSecurityContext) ProtoMessage() {} -func (*PodSecurityContext) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{118} } +func (*PodSecurityContext) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{124} } func (m *PodSignature) Reset() { *m = PodSignature{} } func (*PodSignature) ProtoMessage() {} -func (*PodSignature) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{119} } +func (*PodSignature) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{125} } func (m *PodSpec) Reset() { *m = PodSpec{} } func (*PodSpec) ProtoMessage() {} -func (*PodSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{120} } +func (*PodSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{126} } func (m *PodStatus) Reset() { *m = PodStatus{} } func (*PodStatus) ProtoMessage() {} -func (*PodStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{121} } +func (*PodStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{127} } func (m *PodStatusResult) Reset() { *m = PodStatusResult{} } func (*PodStatusResult) ProtoMessage() {} -func (*PodStatusResult) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{122} } +func (*PodStatusResult) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{128} } func (m *PodTemplate) Reset() { *m = PodTemplate{} } func (*PodTemplate) ProtoMessage() {} -func (*PodTemplate) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{123} } +func (*PodTemplate) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{129} } func (m *PodTemplateList) Reset() { *m = PodTemplateList{} } func (*PodTemplateList) ProtoMessage() {} -func (*PodTemplateList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{124} } +func (*PodTemplateList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{130} } func (m *PodTemplateSpec) Reset() { *m = PodTemplateSpec{} } func (*PodTemplateSpec) ProtoMessage() {} -func (*PodTemplateSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{125} } +func (*PodTemplateSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{131} } func (m *PortworxVolumeSource) Reset() { *m = PortworxVolumeSource{} } func (*PortworxVolumeSource) ProtoMessage() {} -func (*PortworxVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{126} } +func (*PortworxVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{132} } func (m *Preconditions) Reset() { *m = Preconditions{} } func (*Preconditions) ProtoMessage() {} -func (*Preconditions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{127} } +func (*Preconditions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{133} } func (m *PreferAvoidPodsEntry) Reset() { *m = PreferAvoidPodsEntry{} } func (*PreferAvoidPodsEntry) ProtoMessage() {} -func (*PreferAvoidPodsEntry) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{128} } +func (*PreferAvoidPodsEntry) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{134} } func (m *PreferredSchedulingTerm) Reset() { *m = PreferredSchedulingTerm{} } func (*PreferredSchedulingTerm) ProtoMessage() {} func (*PreferredSchedulingTerm) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{129} + return fileDescriptorGenerated, []int{135} } func (m *Probe) Reset() { *m = Probe{} } func (*Probe) ProtoMessage() {} -func (*Probe) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{130} } +func (*Probe) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{136} } func (m *ProjectedVolumeSource) Reset() { *m = ProjectedVolumeSource{} } func (*ProjectedVolumeSource) ProtoMessage() {} -func (*ProjectedVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{131} } +func (*ProjectedVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{137} } func (m *QuobyteVolumeSource) Reset() { *m = QuobyteVolumeSource{} } func (*QuobyteVolumeSource) ProtoMessage() {} -func (*QuobyteVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{132} } +func (*QuobyteVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{138} } + +func (m *RBDPersistentVolumeSource) Reset() { *m = RBDPersistentVolumeSource{} } +func (*RBDPersistentVolumeSource) ProtoMessage() {} +func (*RBDPersistentVolumeSource) Descriptor() ([]byte, []int) { + return fileDescriptorGenerated, []int{139} +} func (m *RBDVolumeSource) Reset() { *m = RBDVolumeSource{} } func (*RBDVolumeSource) ProtoMessage() {} -func (*RBDVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{133} } +func (*RBDVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{140} } func (m *RangeAllocation) Reset() { *m = RangeAllocation{} } func (*RangeAllocation) ProtoMessage() {} -func (*RangeAllocation) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{134} } +func (*RangeAllocation) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{141} } func (m *ReplicationController) Reset() { *m = ReplicationController{} } func (*ReplicationController) ProtoMessage() {} -func (*ReplicationController) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{135} } +func (*ReplicationController) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{142} } func (m *ReplicationControllerCondition) Reset() { *m = ReplicationControllerCondition{} } func (*ReplicationControllerCondition) ProtoMessage() {} func (*ReplicationControllerCondition) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{136} + return fileDescriptorGenerated, []int{143} } func (m *ReplicationControllerList) Reset() { *m = ReplicationControllerList{} } func (*ReplicationControllerList) ProtoMessage() {} func (*ReplicationControllerList) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{137} + return fileDescriptorGenerated, []int{144} } func (m *ReplicationControllerSpec) Reset() { *m = ReplicationControllerSpec{} } func (*ReplicationControllerSpec) ProtoMessage() {} func (*ReplicationControllerSpec) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{138} + return fileDescriptorGenerated, []int{145} } func (m *ReplicationControllerStatus) Reset() { *m = ReplicationControllerStatus{} } func (*ReplicationControllerStatus) ProtoMessage() {} func (*ReplicationControllerStatus) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{139} + return fileDescriptorGenerated, []int{146} } func (m *ResourceFieldSelector) Reset() { *m = ResourceFieldSelector{} } func (*ResourceFieldSelector) ProtoMessage() {} -func (*ResourceFieldSelector) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{140} } +func (*ResourceFieldSelector) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{147} } func (m *ResourceQuota) Reset() { *m = ResourceQuota{} } func (*ResourceQuota) ProtoMessage() {} -func (*ResourceQuota) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{141} } +func (*ResourceQuota) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{148} } func (m *ResourceQuotaList) Reset() { *m = ResourceQuotaList{} } func (*ResourceQuotaList) ProtoMessage() {} -func (*ResourceQuotaList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{142} } +func (*ResourceQuotaList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{149} } func (m *ResourceQuotaSpec) Reset() { *m = ResourceQuotaSpec{} } func (*ResourceQuotaSpec) ProtoMessage() {} -func (*ResourceQuotaSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{143} } +func (*ResourceQuotaSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{150} } func (m *ResourceQuotaStatus) Reset() { *m = ResourceQuotaStatus{} } func (*ResourceQuotaStatus) ProtoMessage() {} -func (*ResourceQuotaStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{144} } +func (*ResourceQuotaStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{151} } func (m *ResourceRequirements) Reset() { *m = ResourceRequirements{} } func (*ResourceRequirements) ProtoMessage() {} -func (*ResourceRequirements) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{145} } +func (*ResourceRequirements) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{152} } func (m *SELinuxOptions) Reset() { *m = SELinuxOptions{} } func (*SELinuxOptions) ProtoMessage() {} -func (*SELinuxOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{146} } +func (*SELinuxOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{153} } + +func (m *ScaleIOPersistentVolumeSource) Reset() { *m = ScaleIOPersistentVolumeSource{} } +func (*ScaleIOPersistentVolumeSource) ProtoMessage() {} +func (*ScaleIOPersistentVolumeSource) Descriptor() ([]byte, []int) { + return fileDescriptorGenerated, []int{154} +} func (m *ScaleIOVolumeSource) Reset() { *m = ScaleIOVolumeSource{} } func (*ScaleIOVolumeSource) ProtoMessage() {} -func (*ScaleIOVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{147} } +func (*ScaleIOVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{155} } func (m *Secret) Reset() { *m = Secret{} } func (*Secret) ProtoMessage() {} -func (*Secret) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{148} } +func (*Secret) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{156} } func (m *SecretEnvSource) Reset() { *m = SecretEnvSource{} } func (*SecretEnvSource) ProtoMessage() {} -func (*SecretEnvSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{149} } +func (*SecretEnvSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{157} } func (m *SecretKeySelector) Reset() { *m = SecretKeySelector{} } func (*SecretKeySelector) ProtoMessage() {} -func (*SecretKeySelector) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{150} } +func (*SecretKeySelector) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{158} } func (m *SecretList) Reset() { *m = SecretList{} } func (*SecretList) ProtoMessage() {} -func (*SecretList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{151} } +func (*SecretList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{159} } func (m *SecretProjection) Reset() { *m = SecretProjection{} } func (*SecretProjection) ProtoMessage() {} -func (*SecretProjection) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{152} } +func (*SecretProjection) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{160} } func (m *SecretReference) Reset() { *m = SecretReference{} } func (*SecretReference) ProtoMessage() {} -func (*SecretReference) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{153} } +func (*SecretReference) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{161} } func (m *SecretVolumeSource) Reset() { *m = SecretVolumeSource{} } func (*SecretVolumeSource) ProtoMessage() {} -func (*SecretVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{154} } +func (*SecretVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{162} } func (m *SecurityContext) Reset() { *m = SecurityContext{} } func (*SecurityContext) ProtoMessage() {} -func (*SecurityContext) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{155} } +func (*SecurityContext) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{163} } func (m *SerializedReference) Reset() { *m = SerializedReference{} } func (*SerializedReference) ProtoMessage() {} -func (*SerializedReference) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{156} } +func (*SerializedReference) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{164} } func (m *Service) Reset() { *m = Service{} } func (*Service) ProtoMessage() {} -func (*Service) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{157} } +func (*Service) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{165} } func (m *ServiceAccount) Reset() { *m = ServiceAccount{} } func (*ServiceAccount) ProtoMessage() {} -func (*ServiceAccount) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{158} } +func (*ServiceAccount) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{166} } func (m *ServiceAccountList) Reset() { *m = ServiceAccountList{} } func (*ServiceAccountList) ProtoMessage() {} -func (*ServiceAccountList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{159} } +func (*ServiceAccountList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{167} } func (m *ServiceList) Reset() { *m = ServiceList{} } func (*ServiceList) ProtoMessage() {} -func (*ServiceList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{160} } +func (*ServiceList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{168} } func (m *ServicePort) Reset() { *m = ServicePort{} } func (*ServicePort) ProtoMessage() {} -func (*ServicePort) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{161} } +func (*ServicePort) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{169} } func (m *ServiceProxyOptions) Reset() { *m = ServiceProxyOptions{} } func (*ServiceProxyOptions) ProtoMessage() {} -func (*ServiceProxyOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{162} } +func (*ServiceProxyOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{170} } func (m *ServiceSpec) Reset() { *m = ServiceSpec{} } func (*ServiceSpec) ProtoMessage() {} -func (*ServiceSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{163} } +func (*ServiceSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{171} } func (m *ServiceStatus) Reset() { *m = ServiceStatus{} } func (*ServiceStatus) ProtoMessage() {} -func (*ServiceStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{164} } +func (*ServiceStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{172} } func (m *SessionAffinityConfig) Reset() { *m = SessionAffinityConfig{} } func (*SessionAffinityConfig) ProtoMessage() {} -func (*SessionAffinityConfig) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{165} } +func (*SessionAffinityConfig) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{173} } func (m *StorageOSPersistentVolumeSource) Reset() { *m = StorageOSPersistentVolumeSource{} } func (*StorageOSPersistentVolumeSource) ProtoMessage() {} func (*StorageOSPersistentVolumeSource) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{166} + return fileDescriptorGenerated, []int{174} } func (m *StorageOSVolumeSource) Reset() { *m = StorageOSVolumeSource{} } func (*StorageOSVolumeSource) ProtoMessage() {} -func (*StorageOSVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{167} } +func (*StorageOSVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{175} } func (m *Sysctl) Reset() { *m = Sysctl{} } func (*Sysctl) ProtoMessage() {} -func (*Sysctl) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{168} } +func (*Sysctl) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{176} } func (m *TCPSocketAction) Reset() { *m = TCPSocketAction{} } func (*TCPSocketAction) ProtoMessage() {} -func (*TCPSocketAction) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{169} } +func (*TCPSocketAction) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{177} } func (m *Taint) Reset() { *m = Taint{} } func (*Taint) ProtoMessage() {} -func (*Taint) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{170} } +func (*Taint) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{178} } func (m *Toleration) Reset() { *m = Toleration{} } func (*Toleration) ProtoMessage() {} -func (*Toleration) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{171} } +func (*Toleration) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{179} } func (m *Volume) Reset() { *m = Volume{} } func (*Volume) ProtoMessage() {} -func (*Volume) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{172} } +func (*Volume) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{180} } + +func (m *VolumeDevice) Reset() { *m = VolumeDevice{} } +func (*VolumeDevice) ProtoMessage() {} +func (*VolumeDevice) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{181} } func (m *VolumeMount) Reset() { *m = VolumeMount{} } func (*VolumeMount) ProtoMessage() {} -func (*VolumeMount) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{173} } +func (*VolumeMount) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{182} } func (m *VolumeProjection) Reset() { *m = VolumeProjection{} } func (*VolumeProjection) ProtoMessage() {} -func (*VolumeProjection) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{174} } +func (*VolumeProjection) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{183} } func (m *VolumeSource) Reset() { *m = VolumeSource{} } func (*VolumeSource) ProtoMessage() {} -func (*VolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{175} } +func (*VolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{184} } func (m *VsphereVirtualDiskVolumeSource) Reset() { *m = VsphereVirtualDiskVolumeSource{} } func (*VsphereVirtualDiskVolumeSource) ProtoMessage() {} func (*VsphereVirtualDiskVolumeSource) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{176} + return fileDescriptorGenerated, []int{185} } func (m *WeightedPodAffinityTerm) Reset() { *m = WeightedPodAffinityTerm{} } func (*WeightedPodAffinityTerm) ProtoMessage() {} func (*WeightedPodAffinityTerm) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{177} + return fileDescriptorGenerated, []int{186} } func init() { @@ -1001,6 +1056,7 @@ func init() { proto.RegisterType((*AzureFilePersistentVolumeSource)(nil), "k8s.io.api.core.v1.AzureFilePersistentVolumeSource") proto.RegisterType((*AzureFileVolumeSource)(nil), "k8s.io.api.core.v1.AzureFileVolumeSource") proto.RegisterType((*Binding)(nil), "k8s.io.api.core.v1.Binding") + proto.RegisterType((*CSIPersistentVolumeSource)(nil), "k8s.io.api.core.v1.CSIPersistentVolumeSource") proto.RegisterType((*Capabilities)(nil), "k8s.io.api.core.v1.Capabilities") proto.RegisterType((*CephFSPersistentVolumeSource)(nil), "k8s.io.api.core.v1.CephFSPersistentVolumeSource") proto.RegisterType((*CephFSVolumeSource)(nil), "k8s.io.api.core.v1.CephFSVolumeSource") @@ -1039,9 +1095,11 @@ func init() { proto.RegisterType((*EnvVarSource)(nil), "k8s.io.api.core.v1.EnvVarSource") proto.RegisterType((*Event)(nil), "k8s.io.api.core.v1.Event") proto.RegisterType((*EventList)(nil), "k8s.io.api.core.v1.EventList") + proto.RegisterType((*EventSeries)(nil), "k8s.io.api.core.v1.EventSeries") proto.RegisterType((*EventSource)(nil), "k8s.io.api.core.v1.EventSource") proto.RegisterType((*ExecAction)(nil), "k8s.io.api.core.v1.ExecAction") proto.RegisterType((*FCVolumeSource)(nil), "k8s.io.api.core.v1.FCVolumeSource") + proto.RegisterType((*FlexPersistentVolumeSource)(nil), "k8s.io.api.core.v1.FlexPersistentVolumeSource") proto.RegisterType((*FlexVolumeSource)(nil), "k8s.io.api.core.v1.FlexVolumeSource") proto.RegisterType((*FlockerVolumeSource)(nil), "k8s.io.api.core.v1.FlockerVolumeSource") proto.RegisterType((*GCEPersistentDiskVolumeSource)(nil), "k8s.io.api.core.v1.GCEPersistentDiskVolumeSource") @@ -1052,6 +1110,7 @@ func init() { proto.RegisterType((*Handler)(nil), "k8s.io.api.core.v1.Handler") proto.RegisterType((*HostAlias)(nil), "k8s.io.api.core.v1.HostAlias") proto.RegisterType((*HostPathVolumeSource)(nil), "k8s.io.api.core.v1.HostPathVolumeSource") + proto.RegisterType((*ISCSIPersistentVolumeSource)(nil), "k8s.io.api.core.v1.ISCSIPersistentVolumeSource") proto.RegisterType((*ISCSIVolumeSource)(nil), "k8s.io.api.core.v1.ISCSIVolumeSource") proto.RegisterType((*KeyToPath)(nil), "k8s.io.api.core.v1.KeyToPath") proto.RegisterType((*Lifecycle)(nil), "k8s.io.api.core.v1.Lifecycle") @@ -1106,6 +1165,8 @@ func init() { proto.RegisterType((*PodAntiAffinity)(nil), "k8s.io.api.core.v1.PodAntiAffinity") proto.RegisterType((*PodAttachOptions)(nil), "k8s.io.api.core.v1.PodAttachOptions") proto.RegisterType((*PodCondition)(nil), "k8s.io.api.core.v1.PodCondition") + proto.RegisterType((*PodDNSConfig)(nil), "k8s.io.api.core.v1.PodDNSConfig") + proto.RegisterType((*PodDNSConfigOption)(nil), "k8s.io.api.core.v1.PodDNSConfigOption") proto.RegisterType((*PodExecOptions)(nil), "k8s.io.api.core.v1.PodExecOptions") proto.RegisterType((*PodList)(nil), "k8s.io.api.core.v1.PodList") proto.RegisterType((*PodLogOptions)(nil), "k8s.io.api.core.v1.PodLogOptions") @@ -1126,6 +1187,7 @@ func init() { proto.RegisterType((*Probe)(nil), "k8s.io.api.core.v1.Probe") proto.RegisterType((*ProjectedVolumeSource)(nil), "k8s.io.api.core.v1.ProjectedVolumeSource") proto.RegisterType((*QuobyteVolumeSource)(nil), "k8s.io.api.core.v1.QuobyteVolumeSource") + proto.RegisterType((*RBDPersistentVolumeSource)(nil), "k8s.io.api.core.v1.RBDPersistentVolumeSource") proto.RegisterType((*RBDVolumeSource)(nil), "k8s.io.api.core.v1.RBDVolumeSource") proto.RegisterType((*RangeAllocation)(nil), "k8s.io.api.core.v1.RangeAllocation") proto.RegisterType((*ReplicationController)(nil), "k8s.io.api.core.v1.ReplicationController") @@ -1140,6 +1202,7 @@ func init() { proto.RegisterType((*ResourceQuotaStatus)(nil), "k8s.io.api.core.v1.ResourceQuotaStatus") proto.RegisterType((*ResourceRequirements)(nil), "k8s.io.api.core.v1.ResourceRequirements") proto.RegisterType((*SELinuxOptions)(nil), "k8s.io.api.core.v1.SELinuxOptions") + proto.RegisterType((*ScaleIOPersistentVolumeSource)(nil), "k8s.io.api.core.v1.ScaleIOPersistentVolumeSource") proto.RegisterType((*ScaleIOVolumeSource)(nil), "k8s.io.api.core.v1.ScaleIOVolumeSource") proto.RegisterType((*Secret)(nil), "k8s.io.api.core.v1.Secret") proto.RegisterType((*SecretEnvSource)(nil), "k8s.io.api.core.v1.SecretEnvSource") @@ -1166,6 +1229,7 @@ func init() { proto.RegisterType((*Taint)(nil), "k8s.io.api.core.v1.Taint") proto.RegisterType((*Toleration)(nil), "k8s.io.api.core.v1.Toleration") proto.RegisterType((*Volume)(nil), "k8s.io.api.core.v1.Volume") + proto.RegisterType((*VolumeDevice)(nil), "k8s.io.api.core.v1.VolumeDevice") proto.RegisterType((*VolumeMount)(nil), "k8s.io.api.core.v1.VolumeMount") proto.RegisterType((*VolumeProjection)(nil), "k8s.io.api.core.v1.VolumeProjection") proto.RegisterType((*VolumeSource)(nil), "k8s.io.api.core.v1.VolumeSource") @@ -1475,6 +1539,40 @@ func (m *Binding) MarshalTo(dAtA []byte) (int, error) { return i, nil } +func (m *CSIPersistentVolumeSource) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CSIPersistentVolumeSource) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Driver))) + i += copy(dAtA[i:], m.Driver) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.VolumeHandle))) + i += copy(dAtA[i:], m.VolumeHandle) + dAtA[i] = 0x18 + i++ + if m.ReadOnly { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + return i, nil +} + func (m *Capabilities) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -2278,6 +2376,20 @@ func (m *Container) MarshalTo(dAtA []byte) (int, error) { i++ i = encodeVarintGenerated(dAtA, i, uint64(len(m.TerminationMessagePolicy))) i += copy(dAtA[i:], m.TerminationMessagePolicy) + if len(m.VolumeDevices) > 0 { + for _, msg := range m.VolumeDevices { + dAtA[i] = 0xaa + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } return i, nil } @@ -3187,6 +3299,46 @@ func (m *Event) MarshalTo(dAtA []byte) (int, error) { i++ i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) i += copy(dAtA[i:], m.Type) + dAtA[i] = 0x52 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.EventTime.Size())) + n48, err := m.EventTime.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n48 + if m.Series != nil { + dAtA[i] = 0x5a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Series.Size())) + n49, err := m.Series.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n49 + } + dAtA[i] = 0x62 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Action))) + i += copy(dAtA[i:], m.Action) + if m.Related != nil { + dAtA[i] = 0x6a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Related.Size())) + n50, err := m.Related.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n50 + } + dAtA[i] = 0x72 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ReportingController))) + i += copy(dAtA[i:], m.ReportingController) + dAtA[i] = 0x7a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ReportingInstance))) + i += copy(dAtA[i:], m.ReportingInstance) return i, nil } @@ -3208,11 +3360,11 @@ func (m *EventList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n48, err := m.ListMeta.MarshalTo(dAtA[i:]) + n51, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n48 + i += n51 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -3228,6 +3380,39 @@ func (m *EventList) MarshalTo(dAtA []byte) (int, error) { return i, nil } +func (m *EventSeries) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventSeries) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0x8 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Count)) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.LastObservedTime.Size())) + n52, err := m.LastObservedTime.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n52 + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.State))) + i += copy(dAtA[i:], m.State) + return i, nil +} + func (m *EventSource) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -3352,6 +3537,72 @@ func (m *FCVolumeSource) MarshalTo(dAtA []byte) (int, error) { return i, nil } +func (m *FlexPersistentVolumeSource) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *FlexPersistentVolumeSource) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Driver))) + i += copy(dAtA[i:], m.Driver) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.FSType))) + i += copy(dAtA[i:], m.FSType) + if m.SecretRef != nil { + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.SecretRef.Size())) + n53, err := m.SecretRef.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n53 + } + dAtA[i] = 0x20 + i++ + if m.ReadOnly { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + if len(m.Options) > 0 { + keysForOptions := make([]string, 0, len(m.Options)) + for k := range m.Options { + keysForOptions = append(keysForOptions, string(k)) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForOptions) + for _, k := range keysForOptions { + dAtA[i] = 0x2a + i++ + v := m.Options[string(k)] + mapSize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + len(v) + sovGenerated(uint64(len(v))) + i = encodeVarintGenerated(dAtA, i, uint64(mapSize)) + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(k))) + i += copy(dAtA[i:], k) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(v))) + i += copy(dAtA[i:], v) + } + } + return i, nil +} + func (m *FlexVolumeSource) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -3379,11 +3630,11 @@ func (m *FlexVolumeSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.SecretRef.Size())) - n49, err := m.SecretRef.MarshalTo(dAtA[i:]) + n54, err := m.SecretRef.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n49 + i += n54 } dAtA[i] = 0x20 i++ @@ -3567,11 +3818,11 @@ func (m *HTTPGetAction) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Port.Size())) - n50, err := m.Port.MarshalTo(dAtA[i:]) + n55, err := m.Port.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n50 + i += n55 dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(len(m.Host))) @@ -3640,31 +3891,31 @@ func (m *Handler) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Exec.Size())) - n51, err := m.Exec.MarshalTo(dAtA[i:]) + n56, err := m.Exec.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n51 + i += n56 } if m.HTTPGet != nil { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.HTTPGet.Size())) - n52, err := m.HTTPGet.MarshalTo(dAtA[i:]) + n57, err := m.HTTPGet.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n52 + i += n57 } if m.TCPSocket != nil { dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.TCPSocket.Size())) - n53, err := m.TCPSocket.MarshalTo(dAtA[i:]) + n58, err := m.TCPSocket.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n53 + i += n58 } return i, nil } @@ -3734,6 +3985,98 @@ func (m *HostPathVolumeSource) MarshalTo(dAtA []byte) (int, error) { return i, nil } +func (m *ISCSIPersistentVolumeSource) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ISCSIPersistentVolumeSource) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.TargetPortal))) + i += copy(dAtA[i:], m.TargetPortal) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.IQN))) + i += copy(dAtA[i:], m.IQN) + dAtA[i] = 0x18 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Lun)) + dAtA[i] = 0x22 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ISCSIInterface))) + i += copy(dAtA[i:], m.ISCSIInterface) + dAtA[i] = 0x2a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.FSType))) + i += copy(dAtA[i:], m.FSType) + dAtA[i] = 0x30 + i++ + if m.ReadOnly { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + if len(m.Portals) > 0 { + for _, s := range m.Portals { + dAtA[i] = 0x3a + i++ + l = len(s) + for l >= 1<<7 { + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + dAtA[i] = uint8(l) + i++ + i += copy(dAtA[i:], s) + } + } + dAtA[i] = 0x40 + i++ + if m.DiscoveryCHAPAuth { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + if m.SecretRef != nil { + dAtA[i] = 0x52 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.SecretRef.Size())) + n59, err := m.SecretRef.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n59 + } + dAtA[i] = 0x58 + i++ + if m.SessionCHAPAuth { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + if m.InitiatorName != nil { + dAtA[i] = 0x62 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.InitiatorName))) + i += copy(dAtA[i:], *m.InitiatorName) + } + return i, nil +} + func (m *ISCSIVolumeSource) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -3803,11 +4146,11 @@ func (m *ISCSIVolumeSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x52 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.SecretRef.Size())) - n54, err := m.SecretRef.MarshalTo(dAtA[i:]) + n60, err := m.SecretRef.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n54 + i += n60 } dAtA[i] = 0x58 i++ @@ -3876,21 +4219,21 @@ func (m *Lifecycle) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.PostStart.Size())) - n55, err := m.PostStart.MarshalTo(dAtA[i:]) + n61, err := m.PostStart.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n55 + i += n61 } if m.PreStop != nil { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.PreStop.Size())) - n56, err := m.PreStop.MarshalTo(dAtA[i:]) + n62, err := m.PreStop.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n56 + i += n62 } return i, nil } @@ -3913,19 +4256,19 @@ func (m *LimitRange) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n57, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n63, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n57 + i += n63 dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) - n58, err := m.Spec.MarshalTo(dAtA[i:]) + n64, err := m.Spec.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n58 + i += n64 return i, nil } @@ -3972,11 +4315,11 @@ func (m *LimitRangeItem) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64((&v).Size())) - n59, err := (&v).MarshalTo(dAtA[i:]) + n65, err := (&v).MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n59 + i += n65 } } if len(m.Min) > 0 { @@ -4003,11 +4346,11 @@ func (m *LimitRangeItem) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64((&v).Size())) - n60, err := (&v).MarshalTo(dAtA[i:]) + n66, err := (&v).MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n60 + i += n66 } } if len(m.Default) > 0 { @@ -4034,11 +4377,11 @@ func (m *LimitRangeItem) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64((&v).Size())) - n61, err := (&v).MarshalTo(dAtA[i:]) + n67, err := (&v).MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n61 + i += n67 } } if len(m.DefaultRequest) > 0 { @@ -4065,11 +4408,11 @@ func (m *LimitRangeItem) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64((&v).Size())) - n62, err := (&v).MarshalTo(dAtA[i:]) + n68, err := (&v).MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n62 + i += n68 } } if len(m.MaxLimitRequestRatio) > 0 { @@ -4096,11 +4439,11 @@ func (m *LimitRangeItem) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64((&v).Size())) - n63, err := (&v).MarshalTo(dAtA[i:]) + n69, err := (&v).MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n63 + i += n69 } } return i, nil @@ -4124,11 +4467,11 @@ func (m *LimitRangeList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n64, err := m.ListMeta.MarshalTo(dAtA[i:]) + n70, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n64 + i += n70 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -4192,11 +4535,11 @@ func (m *List) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n65, err := m.ListMeta.MarshalTo(dAtA[i:]) + n71, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n65 + i += n71 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -4415,27 +4758,27 @@ func (m *Namespace) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n66, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n72, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n66 + i += n72 dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) - n67, err := m.Spec.MarshalTo(dAtA[i:]) + n73, err := m.Spec.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n67 + i += n73 dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) - n68, err := m.Status.MarshalTo(dAtA[i:]) + n74, err := m.Status.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n68 + i += n74 return i, nil } @@ -4457,11 +4800,11 @@ func (m *NamespaceList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n69, err := m.ListMeta.MarshalTo(dAtA[i:]) + n75, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n69 + i += n75 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -4550,27 +4893,27 @@ func (m *Node) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n70, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n76, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n70 + i += n76 dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) - n71, err := m.Spec.MarshalTo(dAtA[i:]) + n77, err := m.Spec.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n71 + i += n77 dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) - n72, err := m.Status.MarshalTo(dAtA[i:]) + n78, err := m.Status.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n72 + i += n78 return i, nil } @@ -4619,11 +4962,11 @@ func (m *NodeAffinity) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.RequiredDuringSchedulingIgnoredDuringExecution.Size())) - n73, err := m.RequiredDuringSchedulingIgnoredDuringExecution.MarshalTo(dAtA[i:]) + n79, err := m.RequiredDuringSchedulingIgnoredDuringExecution.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n73 + i += n79 } if len(m.PreferredDuringSchedulingIgnoredDuringExecution) > 0 { for _, msg := range m.PreferredDuringSchedulingIgnoredDuringExecution { @@ -4666,19 +5009,19 @@ func (m *NodeCondition) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.LastHeartbeatTime.Size())) - n74, err := m.LastHeartbeatTime.MarshalTo(dAtA[i:]) + n80, err := m.LastHeartbeatTime.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n74 + i += n80 dAtA[i] = 0x22 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.LastTransitionTime.Size())) - n75, err := m.LastTransitionTime.MarshalTo(dAtA[i:]) + n81, err := m.LastTransitionTime.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n75 + i += n81 dAtA[i] = 0x2a i++ i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) @@ -4709,11 +5052,11 @@ func (m *NodeConfigSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ConfigMapRef.Size())) - n76, err := m.ConfigMapRef.MarshalTo(dAtA[i:]) + n82, err := m.ConfigMapRef.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n76 + i += n82 } return i, nil } @@ -4736,11 +5079,11 @@ func (m *NodeDaemonEndpoints) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.KubeletEndpoint.Size())) - n77, err := m.KubeletEndpoint.MarshalTo(dAtA[i:]) + n83, err := m.KubeletEndpoint.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n77 + i += n83 return i, nil } @@ -4762,11 +5105,11 @@ func (m *NodeList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n78, err := m.ListMeta.MarshalTo(dAtA[i:]) + n84, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n78 + i += n84 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -4843,11 +5186,11 @@ func (m *NodeResources) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64((&v).Size())) - n79, err := (&v).MarshalTo(dAtA[i:]) + n85, err := (&v).MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n79 + i += n85 } } return i, nil @@ -5005,11 +5348,11 @@ func (m *NodeSpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x32 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ConfigSource.Size())) - n80, err := m.ConfigSource.MarshalTo(dAtA[i:]) + n86, err := m.ConfigSource.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n80 + i += n86 } return i, nil } @@ -5053,11 +5396,11 @@ func (m *NodeStatus) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64((&v).Size())) - n81, err := (&v).MarshalTo(dAtA[i:]) + n87, err := (&v).MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n81 + i += n87 } } if len(m.Allocatable) > 0 { @@ -5084,11 +5427,11 @@ func (m *NodeStatus) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64((&v).Size())) - n82, err := (&v).MarshalTo(dAtA[i:]) + n88, err := (&v).MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n82 + i += n88 } } dAtA[i] = 0x1a @@ -5122,19 +5465,19 @@ func (m *NodeStatus) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x32 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.DaemonEndpoints.Size())) - n83, err := m.DaemonEndpoints.MarshalTo(dAtA[i:]) + n89, err := m.DaemonEndpoints.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n83 + i += n89 dAtA[i] = 0x3a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.NodeInfo.Size())) - n84, err := m.NodeInfo.MarshalTo(dAtA[i:]) + n90, err := m.NodeInfo.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n84 + i += n90 if len(m.Images) > 0 { for _, msg := range m.Images { dAtA[i] = 0x42 @@ -5306,20 +5649,20 @@ func (m *ObjectMeta) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x42 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.CreationTimestamp.Size())) - n85, err := m.CreationTimestamp.MarshalTo(dAtA[i:]) + n91, err := m.CreationTimestamp.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n85 + i += n91 if m.DeletionTimestamp != nil { dAtA[i] = 0x4a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.DeletionTimestamp.Size())) - n86, err := m.DeletionTimestamp.MarshalTo(dAtA[i:]) + n92, err := m.DeletionTimestamp.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n86 + i += n92 } if m.DeletionGracePeriodSeconds != nil { dAtA[i] = 0x50 @@ -5407,11 +5750,11 @@ func (m *ObjectMeta) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Initializers.Size())) - n87, err := m.Initializers.MarshalTo(dAtA[i:]) + n93, err := m.Initializers.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n87 + i += n93 } return i, nil } @@ -5480,27 +5823,27 @@ func (m *PersistentVolume) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n88, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n94, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n88 + i += n94 dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) - n89, err := m.Spec.MarshalTo(dAtA[i:]) + n95, err := m.Spec.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n89 + i += n95 dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) - n90, err := m.Status.MarshalTo(dAtA[i:]) + n96, err := m.Status.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n90 + i += n96 return i, nil } @@ -5522,27 +5865,27 @@ func (m *PersistentVolumeClaim) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n91, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n97, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n91 + i += n97 dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) - n92, err := m.Spec.MarshalTo(dAtA[i:]) + n98, err := m.Spec.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n92 + i += n98 dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) - n93, err := m.Status.MarshalTo(dAtA[i:]) + n99, err := m.Status.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n93 + i += n99 return i, nil } @@ -5572,19 +5915,19 @@ func (m *PersistentVolumeClaimCondition) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.LastProbeTime.Size())) - n94, err := m.LastProbeTime.MarshalTo(dAtA[i:]) + n100, err := m.LastProbeTime.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n94 + i += n100 dAtA[i] = 0x22 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.LastTransitionTime.Size())) - n95, err := m.LastTransitionTime.MarshalTo(dAtA[i:]) + n101, err := m.LastTransitionTime.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n95 + i += n101 dAtA[i] = 0x2a i++ i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) @@ -5614,11 +5957,11 @@ func (m *PersistentVolumeClaimList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n96, err := m.ListMeta.MarshalTo(dAtA[i:]) + n102, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n96 + i += n102 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -5667,11 +6010,11 @@ func (m *PersistentVolumeClaimSpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Resources.Size())) - n97, err := m.Resources.MarshalTo(dAtA[i:]) + n103, err := m.Resources.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n97 + i += n103 dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(len(m.VolumeName))) @@ -5680,11 +6023,11 @@ func (m *PersistentVolumeClaimSpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x22 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Selector.Size())) - n98, err := m.Selector.MarshalTo(dAtA[i:]) + n104, err := m.Selector.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n98 + i += n104 } if m.StorageClassName != nil { dAtA[i] = 0x2a @@ -5692,6 +6035,12 @@ func (m *PersistentVolumeClaimSpec) MarshalTo(dAtA []byte) (int, error) { i = encodeVarintGenerated(dAtA, i, uint64(len(*m.StorageClassName))) i += copy(dAtA[i:], *m.StorageClassName) } + if m.VolumeMode != nil { + dAtA[i] = 0x32 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.VolumeMode))) + i += copy(dAtA[i:], *m.VolumeMode) + } return i, nil } @@ -5753,11 +6102,11 @@ func (m *PersistentVolumeClaimStatus) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64((&v).Size())) - n99, err := (&v).MarshalTo(dAtA[i:]) + n105, err := (&v).MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n99 + i += n105 } } if len(m.Conditions) > 0 { @@ -5823,11 +6172,11 @@ func (m *PersistentVolumeList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n100, err := m.ListMeta.MarshalTo(dAtA[i:]) + n106, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n100 + i += n106 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -5862,163 +6211,163 @@ func (m *PersistentVolumeSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.GCEPersistentDisk.Size())) - n101, err := m.GCEPersistentDisk.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n101 - } - if m.AWSElasticBlockStore != nil { - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.AWSElasticBlockStore.Size())) - n102, err := m.AWSElasticBlockStore.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n102 - } - if m.HostPath != nil { - dAtA[i] = 0x1a - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.HostPath.Size())) - n103, err := m.HostPath.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n103 - } - if m.Glusterfs != nil { - dAtA[i] = 0x22 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Glusterfs.Size())) - n104, err := m.Glusterfs.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n104 - } - if m.NFS != nil { - dAtA[i] = 0x2a - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.NFS.Size())) - n105, err := m.NFS.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n105 - } - if m.RBD != nil { - dAtA[i] = 0x32 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.RBD.Size())) - n106, err := m.RBD.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n106 - } - if m.ISCSI != nil { - dAtA[i] = 0x3a - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.ISCSI.Size())) - n107, err := m.ISCSI.MarshalTo(dAtA[i:]) + n107, err := m.GCEPersistentDisk.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n107 } - if m.Cinder != nil { - dAtA[i] = 0x42 + if m.AWSElasticBlockStore != nil { + dAtA[i] = 0x12 i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Cinder.Size())) - n108, err := m.Cinder.MarshalTo(dAtA[i:]) + i = encodeVarintGenerated(dAtA, i, uint64(m.AWSElasticBlockStore.Size())) + n108, err := m.AWSElasticBlockStore.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n108 } - if m.CephFS != nil { - dAtA[i] = 0x4a + if m.HostPath != nil { + dAtA[i] = 0x1a i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.CephFS.Size())) - n109, err := m.CephFS.MarshalTo(dAtA[i:]) + i = encodeVarintGenerated(dAtA, i, uint64(m.HostPath.Size())) + n109, err := m.HostPath.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n109 } - if m.FC != nil { - dAtA[i] = 0x52 + if m.Glusterfs != nil { + dAtA[i] = 0x22 i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.FC.Size())) - n110, err := m.FC.MarshalTo(dAtA[i:]) + i = encodeVarintGenerated(dAtA, i, uint64(m.Glusterfs.Size())) + n110, err := m.Glusterfs.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n110 } - if m.Flocker != nil { - dAtA[i] = 0x5a + if m.NFS != nil { + dAtA[i] = 0x2a i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Flocker.Size())) - n111, err := m.Flocker.MarshalTo(dAtA[i:]) + i = encodeVarintGenerated(dAtA, i, uint64(m.NFS.Size())) + n111, err := m.NFS.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n111 } - if m.FlexVolume != nil { - dAtA[i] = 0x62 + if m.RBD != nil { + dAtA[i] = 0x32 i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.FlexVolume.Size())) - n112, err := m.FlexVolume.MarshalTo(dAtA[i:]) + i = encodeVarintGenerated(dAtA, i, uint64(m.RBD.Size())) + n112, err := m.RBD.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n112 } - if m.AzureFile != nil { - dAtA[i] = 0x6a + if m.ISCSI != nil { + dAtA[i] = 0x3a i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.AzureFile.Size())) - n113, err := m.AzureFile.MarshalTo(dAtA[i:]) + i = encodeVarintGenerated(dAtA, i, uint64(m.ISCSI.Size())) + n113, err := m.ISCSI.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n113 } - if m.VsphereVolume != nil { - dAtA[i] = 0x72 + if m.Cinder != nil { + dAtA[i] = 0x42 i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.VsphereVolume.Size())) - n114, err := m.VsphereVolume.MarshalTo(dAtA[i:]) + i = encodeVarintGenerated(dAtA, i, uint64(m.Cinder.Size())) + n114, err := m.Cinder.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n114 } - if m.Quobyte != nil { - dAtA[i] = 0x7a + if m.CephFS != nil { + dAtA[i] = 0x4a i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Quobyte.Size())) - n115, err := m.Quobyte.MarshalTo(dAtA[i:]) + i = encodeVarintGenerated(dAtA, i, uint64(m.CephFS.Size())) + n115, err := m.CephFS.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n115 } + if m.FC != nil { + dAtA[i] = 0x52 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.FC.Size())) + n116, err := m.FC.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n116 + } + if m.Flocker != nil { + dAtA[i] = 0x5a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Flocker.Size())) + n117, err := m.Flocker.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n117 + } + if m.FlexVolume != nil { + dAtA[i] = 0x62 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.FlexVolume.Size())) + n118, err := m.FlexVolume.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n118 + } + if m.AzureFile != nil { + dAtA[i] = 0x6a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.AzureFile.Size())) + n119, err := m.AzureFile.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n119 + } + if m.VsphereVolume != nil { + dAtA[i] = 0x72 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.VsphereVolume.Size())) + n120, err := m.VsphereVolume.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n120 + } + if m.Quobyte != nil { + dAtA[i] = 0x7a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Quobyte.Size())) + n121, err := m.Quobyte.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n121 + } if m.AzureDisk != nil { dAtA[i] = 0x82 i++ dAtA[i] = 0x1 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.AzureDisk.Size())) - n116, err := m.AzureDisk.MarshalTo(dAtA[i:]) + n122, err := m.AzureDisk.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n116 + i += n122 } if m.PhotonPersistentDisk != nil { dAtA[i] = 0x8a @@ -6026,11 +6375,11 @@ func (m *PersistentVolumeSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.PhotonPersistentDisk.Size())) - n117, err := m.PhotonPersistentDisk.MarshalTo(dAtA[i:]) + n123, err := m.PhotonPersistentDisk.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n117 + i += n123 } if m.PortworxVolume != nil { dAtA[i] = 0x92 @@ -6038,11 +6387,11 @@ func (m *PersistentVolumeSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.PortworxVolume.Size())) - n118, err := m.PortworxVolume.MarshalTo(dAtA[i:]) + n124, err := m.PortworxVolume.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n118 + i += n124 } if m.ScaleIO != nil { dAtA[i] = 0x9a @@ -6050,11 +6399,11 @@ func (m *PersistentVolumeSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ScaleIO.Size())) - n119, err := m.ScaleIO.MarshalTo(dAtA[i:]) + n125, err := m.ScaleIO.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n119 + i += n125 } if m.Local != nil { dAtA[i] = 0xa2 @@ -6062,11 +6411,11 @@ func (m *PersistentVolumeSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Local.Size())) - n120, err := m.Local.MarshalTo(dAtA[i:]) + n126, err := m.Local.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n120 + i += n126 } if m.StorageOS != nil { dAtA[i] = 0xaa @@ -6074,11 +6423,23 @@ func (m *PersistentVolumeSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.StorageOS.Size())) - n121, err := m.StorageOS.MarshalTo(dAtA[i:]) + n127, err := m.StorageOS.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n121 + i += n127 + } + if m.CSI != nil { + dAtA[i] = 0xb2 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.CSI.Size())) + n128, err := m.CSI.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n128 } return i, nil } @@ -6122,21 +6483,21 @@ func (m *PersistentVolumeSpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64((&v).Size())) - n122, err := (&v).MarshalTo(dAtA[i:]) + n129, err := (&v).MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n122 + i += n129 } } dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.PersistentVolumeSource.Size())) - n123, err := m.PersistentVolumeSource.MarshalTo(dAtA[i:]) + n130, err := m.PersistentVolumeSource.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n123 + i += n130 if len(m.AccessModes) > 0 { for _, s := range m.AccessModes { dAtA[i] = 0x1a @@ -6156,11 +6517,11 @@ func (m *PersistentVolumeSpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x22 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ClaimRef.Size())) - n124, err := m.ClaimRef.MarshalTo(dAtA[i:]) + n131, err := m.ClaimRef.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n124 + i += n131 } dAtA[i] = 0x2a i++ @@ -6185,6 +6546,12 @@ func (m *PersistentVolumeSpec) MarshalTo(dAtA []byte) (int, error) { i += copy(dAtA[i:], s) } } + if m.VolumeMode != nil { + dAtA[i] = 0x42 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.VolumeMode))) + i += copy(dAtA[i:], *m.VolumeMode) + } return i, nil } @@ -6262,27 +6629,27 @@ func (m *Pod) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n125, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n132, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n125 + i += n132 dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) - n126, err := m.Spec.MarshalTo(dAtA[i:]) + n133, err := m.Spec.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n126 + i += n133 dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) - n127, err := m.Status.MarshalTo(dAtA[i:]) + n134, err := m.Status.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n127 + i += n134 return i, nil } @@ -6347,11 +6714,11 @@ func (m *PodAffinityTerm) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.LabelSelector.Size())) - n128, err := m.LabelSelector.MarshalTo(dAtA[i:]) + n135, err := m.LabelSelector.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n128 + i += n135 } if len(m.Namespaces) > 0 { for _, s := range m.Namespaces { @@ -6497,19 +6864,19 @@ func (m *PodCondition) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.LastProbeTime.Size())) - n129, err := m.LastProbeTime.MarshalTo(dAtA[i:]) + n136, err := m.LastProbeTime.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n129 + i += n136 dAtA[i] = 0x22 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.LastTransitionTime.Size())) - n130, err := m.LastTransitionTime.MarshalTo(dAtA[i:]) + n137, err := m.LastTransitionTime.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n130 + i += n137 dAtA[i] = 0x2a i++ i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) @@ -6521,6 +6888,94 @@ func (m *PodCondition) MarshalTo(dAtA []byte) (int, error) { return i, nil } +func (m *PodDNSConfig) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PodDNSConfig) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Nameservers) > 0 { + for _, s := range m.Nameservers { + dAtA[i] = 0xa + i++ + l = len(s) + for l >= 1<<7 { + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + dAtA[i] = uint8(l) + i++ + i += copy(dAtA[i:], s) + } + } + if len(m.Searches) > 0 { + for _, s := range m.Searches { + dAtA[i] = 0x12 + i++ + l = len(s) + for l >= 1<<7 { + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + dAtA[i] = uint8(l) + i++ + i += copy(dAtA[i:], s) + } + } + if len(m.Options) > 0 { + for _, msg := range m.Options { + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *PodDNSConfigOption) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PodDNSConfigOption) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i += copy(dAtA[i:], m.Name) + if m.Value != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.Value))) + i += copy(dAtA[i:], *m.Value) + } + return i, nil +} + func (m *PodExecOptions) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -6608,11 +7063,11 @@ func (m *PodList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n131, err := m.ListMeta.MarshalTo(dAtA[i:]) + n138, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n131 + i += n138 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -6672,11 +7127,11 @@ func (m *PodLogOptions) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x2a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.SinceTime.Size())) - n132, err := m.SinceTime.MarshalTo(dAtA[i:]) + n139, err := m.SinceTime.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n132 + i += n139 } dAtA[i] = 0x30 i++ @@ -6765,11 +7220,11 @@ func (m *PodSecurityContext) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.SELinuxOptions.Size())) - n133, err := m.SELinuxOptions.MarshalTo(dAtA[i:]) + n140, err := m.SELinuxOptions.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n133 + i += n140 } if m.RunAsUser != nil { dAtA[i] = 0x10 @@ -6820,11 +7275,11 @@ func (m *PodSignature) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.PodController.Size())) - n134, err := m.PodController.MarshalTo(dAtA[i:]) + n141, err := m.PodController.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n134 + i += n141 } return i, nil } @@ -6948,11 +7403,11 @@ func (m *PodSpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x72 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.SecurityContext.Size())) - n135, err := m.SecurityContext.MarshalTo(dAtA[i:]) + n142, err := m.SecurityContext.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n135 + i += n142 } if len(m.ImagePullSecrets) > 0 { for _, msg := range m.ImagePullSecrets { @@ -6984,11 +7439,11 @@ func (m *PodSpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Affinity.Size())) - n136, err := m.Affinity.MarshalTo(dAtA[i:]) + n143, err := m.Affinity.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n136 + i += n143 } dAtA[i] = 0x9a i++ @@ -7063,6 +7518,18 @@ func (m *PodSpec) MarshalTo(dAtA []byte) (int, error) { i++ i = encodeVarintGenerated(dAtA, i, uint64(*m.Priority)) } + if m.DNSConfig != nil { + dAtA[i] = 0xd2 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.DNSConfig.Size())) + n144, err := m.DNSConfig.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n144 + } return i, nil } @@ -7117,11 +7584,11 @@ func (m *PodStatus) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x3a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.StartTime.Size())) - n137, err := m.StartTime.MarshalTo(dAtA[i:]) + n145, err := m.StartTime.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n137 + i += n145 } if len(m.ContainerStatuses) > 0 { for _, msg := range m.ContainerStatuses { @@ -7172,19 +7639,19 @@ func (m *PodStatusResult) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n138, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n146, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n138 + i += n146 dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) - n139, err := m.Status.MarshalTo(dAtA[i:]) + n147, err := m.Status.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n139 + i += n147 return i, nil } @@ -7206,19 +7673,19 @@ func (m *PodTemplate) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n140, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n148, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n140 + i += n148 dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Template.Size())) - n141, err := m.Template.MarshalTo(dAtA[i:]) + n149, err := m.Template.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n141 + i += n149 return i, nil } @@ -7240,11 +7707,11 @@ func (m *PodTemplateList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n142, err := m.ListMeta.MarshalTo(dAtA[i:]) + n150, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n142 + i += n150 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -7278,19 +7745,19 @@ func (m *PodTemplateSpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n143, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n151, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n143 + i += n151 dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) - n144, err := m.Spec.MarshalTo(dAtA[i:]) + n152, err := m.Spec.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n144 + i += n152 return i, nil } @@ -7370,19 +7837,19 @@ func (m *PreferAvoidPodsEntry) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.PodSignature.Size())) - n145, err := m.PodSignature.MarshalTo(dAtA[i:]) + n153, err := m.PodSignature.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n145 + i += n153 dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.EvictionTime.Size())) - n146, err := m.EvictionTime.MarshalTo(dAtA[i:]) + n154, err := m.EvictionTime.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n146 + i += n154 dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) @@ -7415,11 +7882,11 @@ func (m *PreferredSchedulingTerm) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Preference.Size())) - n147, err := m.Preference.MarshalTo(dAtA[i:]) + n155, err := m.Preference.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n147 + i += n155 return i, nil } @@ -7441,11 +7908,11 @@ func (m *Probe) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Handler.Size())) - n148, err := m.Handler.MarshalTo(dAtA[i:]) + n156, err := m.Handler.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n148 + i += n156 dAtA[i] = 0x10 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.InitialDelaySeconds)) @@ -7541,6 +8008,77 @@ func (m *QuobyteVolumeSource) MarshalTo(dAtA []byte) (int, error) { return i, nil } +func (m *RBDPersistentVolumeSource) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RBDPersistentVolumeSource) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.CephMonitors) > 0 { + for _, s := range m.CephMonitors { + dAtA[i] = 0xa + i++ + l = len(s) + for l >= 1<<7 { + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + dAtA[i] = uint8(l) + i++ + i += copy(dAtA[i:], s) + } + } + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.RBDImage))) + i += copy(dAtA[i:], m.RBDImage) + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.FSType))) + i += copy(dAtA[i:], m.FSType) + dAtA[i] = 0x22 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.RBDPool))) + i += copy(dAtA[i:], m.RBDPool) + dAtA[i] = 0x2a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.RadosUser))) + i += copy(dAtA[i:], m.RadosUser) + dAtA[i] = 0x32 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Keyring))) + i += copy(dAtA[i:], m.Keyring) + if m.SecretRef != nil { + dAtA[i] = 0x3a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.SecretRef.Size())) + n157, err := m.SecretRef.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n157 + } + dAtA[i] = 0x40 + i++ + if m.ReadOnly { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + return i, nil +} + func (m *RBDVolumeSource) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -7595,11 +8133,11 @@ func (m *RBDVolumeSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x3a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.SecretRef.Size())) - n149, err := m.SecretRef.MarshalTo(dAtA[i:]) + n158, err := m.SecretRef.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n149 + i += n158 } dAtA[i] = 0x40 i++ @@ -7630,11 +8168,11 @@ func (m *RangeAllocation) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n150, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n159, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n150 + i += n159 dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(len(m.Range))) @@ -7666,27 +8204,27 @@ func (m *ReplicationController) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n151, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n160, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n151 + i += n160 dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) - n152, err := m.Spec.MarshalTo(dAtA[i:]) + n161, err := m.Spec.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n152 + i += n161 dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) - n153, err := m.Status.MarshalTo(dAtA[i:]) + n162, err := m.Status.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n153 + i += n162 return i, nil } @@ -7716,11 +8254,11 @@ func (m *ReplicationControllerCondition) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.LastTransitionTime.Size())) - n154, err := m.LastTransitionTime.MarshalTo(dAtA[i:]) + n163, err := m.LastTransitionTime.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n154 + i += n163 dAtA[i] = 0x22 i++ i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) @@ -7750,11 +8288,11 @@ func (m *ReplicationControllerList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n155, err := m.ListMeta.MarshalTo(dAtA[i:]) + n164, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n155 + i += n164 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -7816,11 +8354,11 @@ func (m *ReplicationControllerSpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Template.Size())) - n156, err := m.Template.MarshalTo(dAtA[i:]) + n165, err := m.Template.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n156 + i += n165 } dAtA[i] = 0x20 i++ @@ -7899,11 +8437,11 @@ func (m *ResourceFieldSelector) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Divisor.Size())) - n157, err := m.Divisor.MarshalTo(dAtA[i:]) + n166, err := m.Divisor.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n157 + i += n166 return i, nil } @@ -7925,27 +8463,27 @@ func (m *ResourceQuota) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n158, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n167, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n158 + i += n167 dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) - n159, err := m.Spec.MarshalTo(dAtA[i:]) + n168, err := m.Spec.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n159 + i += n168 dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) - n160, err := m.Status.MarshalTo(dAtA[i:]) + n169, err := m.Status.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n160 + i += n169 return i, nil } @@ -7967,11 +8505,11 @@ func (m *ResourceQuotaList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n161, err := m.ListMeta.MarshalTo(dAtA[i:]) + n170, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n161 + i += n170 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -8026,11 +8564,11 @@ func (m *ResourceQuotaSpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64((&v).Size())) - n162, err := (&v).MarshalTo(dAtA[i:]) + n171, err := (&v).MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n162 + i += n171 } } if len(m.Scopes) > 0 { @@ -8090,11 +8628,11 @@ func (m *ResourceQuotaStatus) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64((&v).Size())) - n163, err := (&v).MarshalTo(dAtA[i:]) + n172, err := (&v).MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n163 + i += n172 } } if len(m.Used) > 0 { @@ -8121,11 +8659,11 @@ func (m *ResourceQuotaStatus) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64((&v).Size())) - n164, err := (&v).MarshalTo(dAtA[i:]) + n173, err := (&v).MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n164 + i += n173 } } return i, nil @@ -8170,11 +8708,11 @@ func (m *ResourceRequirements) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64((&v).Size())) - n165, err := (&v).MarshalTo(dAtA[i:]) + n174, err := (&v).MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n165 + i += n174 } } if len(m.Requests) > 0 { @@ -8201,11 +8739,11 @@ func (m *ResourceRequirements) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64((&v).Size())) - n166, err := (&v).MarshalTo(dAtA[i:]) + n175, err := (&v).MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n166 + i += n175 } } return i, nil @@ -8245,6 +8783,78 @@ func (m *SELinuxOptions) MarshalTo(dAtA []byte) (int, error) { return i, nil } +func (m *ScaleIOPersistentVolumeSource) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ScaleIOPersistentVolumeSource) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Gateway))) + i += copy(dAtA[i:], m.Gateway) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.System))) + i += copy(dAtA[i:], m.System) + if m.SecretRef != nil { + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.SecretRef.Size())) + n176, err := m.SecretRef.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n176 + } + dAtA[i] = 0x20 + i++ + if m.SSLEnabled { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + dAtA[i] = 0x2a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ProtectionDomain))) + i += copy(dAtA[i:], m.ProtectionDomain) + dAtA[i] = 0x32 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.StoragePool))) + i += copy(dAtA[i:], m.StoragePool) + dAtA[i] = 0x3a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.StorageMode))) + i += copy(dAtA[i:], m.StorageMode) + dAtA[i] = 0x42 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.VolumeName))) + i += copy(dAtA[i:], m.VolumeName) + dAtA[i] = 0x4a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.FSType))) + i += copy(dAtA[i:], m.FSType) + dAtA[i] = 0x50 + i++ + if m.ReadOnly { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + return i, nil +} + func (m *ScaleIOVolumeSource) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -8272,11 +8882,11 @@ func (m *ScaleIOVolumeSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.SecretRef.Size())) - n167, err := m.SecretRef.MarshalTo(dAtA[i:]) + n177, err := m.SecretRef.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n167 + i += n177 } dAtA[i] = 0x20 i++ @@ -8335,11 +8945,11 @@ func (m *Secret) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n168, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n178, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n168 + i += n178 if len(m.Data) > 0 { keysForData := make([]string, 0, len(m.Data)) for k := range m.Data { @@ -8415,11 +9025,11 @@ func (m *SecretEnvSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.LocalObjectReference.Size())) - n169, err := m.LocalObjectReference.MarshalTo(dAtA[i:]) + n179, err := m.LocalObjectReference.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n169 + i += n179 if m.Optional != nil { dAtA[i] = 0x10 i++ @@ -8451,11 +9061,11 @@ func (m *SecretKeySelector) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.LocalObjectReference.Size())) - n170, err := m.LocalObjectReference.MarshalTo(dAtA[i:]) + n180, err := m.LocalObjectReference.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n170 + i += n180 dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(len(m.Key))) @@ -8491,11 +9101,11 @@ func (m *SecretList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n171, err := m.ListMeta.MarshalTo(dAtA[i:]) + n181, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n171 + i += n181 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -8529,11 +9139,11 @@ func (m *SecretProjection) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.LocalObjectReference.Size())) - n172, err := m.LocalObjectReference.MarshalTo(dAtA[i:]) + n182, err := m.LocalObjectReference.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n172 + i += n182 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -8653,11 +9263,11 @@ func (m *SecurityContext) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Capabilities.Size())) - n173, err := m.Capabilities.MarshalTo(dAtA[i:]) + n183, err := m.Capabilities.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n173 + i += n183 } if m.Privileged != nil { dAtA[i] = 0x10 @@ -8673,11 +9283,11 @@ func (m *SecurityContext) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.SELinuxOptions.Size())) - n174, err := m.SELinuxOptions.MarshalTo(dAtA[i:]) + n184, err := m.SELinuxOptions.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n174 + i += n184 } if m.RunAsUser != nil { dAtA[i] = 0x20 @@ -8735,11 +9345,11 @@ func (m *SerializedReference) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Reference.Size())) - n175, err := m.Reference.MarshalTo(dAtA[i:]) + n185, err := m.Reference.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n175 + i += n185 return i, nil } @@ -8761,27 +9371,27 @@ func (m *Service) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n176, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n186, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n176 + i += n186 dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) - n177, err := m.Spec.MarshalTo(dAtA[i:]) + n187, err := m.Spec.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n177 + i += n187 dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) - n178, err := m.Status.MarshalTo(dAtA[i:]) + n188, err := m.Status.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n178 + i += n188 return i, nil } @@ -8803,11 +9413,11 @@ func (m *ServiceAccount) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n179, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n189, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n179 + i += n189 if len(m.Secrets) > 0 { for _, msg := range m.Secrets { dAtA[i] = 0x12 @@ -8863,11 +9473,11 @@ func (m *ServiceAccountList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n180, err := m.ListMeta.MarshalTo(dAtA[i:]) + n190, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n180 + i += n190 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -8901,11 +9511,11 @@ func (m *ServiceList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n181, err := m.ListMeta.MarshalTo(dAtA[i:]) + n191, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n181 + i += n191 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -8950,11 +9560,11 @@ func (m *ServicePort) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x22 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.TargetPort.Size())) - n182, err := m.TargetPort.MarshalTo(dAtA[i:]) + n192, err := m.TargetPort.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n182 + i += n192 dAtA[i] = 0x28 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.NodePort)) @@ -9101,11 +9711,11 @@ func (m *ServiceSpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x72 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.SessionAffinityConfig.Size())) - n183, err := m.SessionAffinityConfig.MarshalTo(dAtA[i:]) + n193, err := m.SessionAffinityConfig.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n183 + i += n193 } return i, nil } @@ -9128,11 +9738,11 @@ func (m *ServiceStatus) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.LoadBalancer.Size())) - n184, err := m.LoadBalancer.MarshalTo(dAtA[i:]) + n194, err := m.LoadBalancer.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n184 + i += n194 return i, nil } @@ -9155,11 +9765,11 @@ func (m *SessionAffinityConfig) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ClientIP.Size())) - n185, err := m.ClientIP.MarshalTo(dAtA[i:]) + n195, err := m.ClientIP.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n185 + i += n195 } return i, nil } @@ -9203,11 +9813,11 @@ func (m *StorageOSPersistentVolumeSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x2a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.SecretRef.Size())) - n186, err := m.SecretRef.MarshalTo(dAtA[i:]) + n196, err := m.SecretRef.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n186 + i += n196 } return i, nil } @@ -9251,11 +9861,11 @@ func (m *StorageOSVolumeSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x2a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.SecretRef.Size())) - n187, err := m.SecretRef.MarshalTo(dAtA[i:]) + n197, err := m.SecretRef.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n187 + i += n197 } return i, nil } @@ -9304,11 +9914,11 @@ func (m *TCPSocketAction) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Port.Size())) - n188, err := m.Port.MarshalTo(dAtA[i:]) + n198, err := m.Port.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n188 + i += n198 dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(len(m.Host))) @@ -9347,11 +9957,11 @@ func (m *Taint) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x22 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.TimeAdded.Size())) - n189, err := m.TimeAdded.MarshalTo(dAtA[i:]) + n199, err := m.TimeAdded.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n189 + i += n199 } return i, nil } @@ -9417,11 +10027,37 @@ func (m *Volume) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.VolumeSource.Size())) - n190, err := m.VolumeSource.MarshalTo(dAtA[i:]) + n200, err := m.VolumeSource.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n190 + i += n200 + return i, nil +} + +func (m *VolumeDevice) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *VolumeDevice) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i += copy(dAtA[i:], m.Name) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.DevicePath))) + i += copy(dAtA[i:], m.DevicePath) return i, nil } @@ -9488,31 +10124,31 @@ func (m *VolumeProjection) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Secret.Size())) - n191, err := m.Secret.MarshalTo(dAtA[i:]) + n201, err := m.Secret.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n191 + i += n201 } if m.DownwardAPI != nil { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.DownwardAPI.Size())) - n192, err := m.DownwardAPI.MarshalTo(dAtA[i:]) + n202, err := m.DownwardAPI.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n192 + i += n202 } if m.ConfigMap != nil { dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ConfigMap.Size())) - n193, err := m.ConfigMap.MarshalTo(dAtA[i:]) + n203, err := m.ConfigMap.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n193 + i += n203 } return i, nil } @@ -9536,163 +10172,163 @@ func (m *VolumeSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.HostPath.Size())) - n194, err := m.HostPath.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n194 - } - if m.EmptyDir != nil { - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.EmptyDir.Size())) - n195, err := m.EmptyDir.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n195 - } - if m.GCEPersistentDisk != nil { - dAtA[i] = 0x1a - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.GCEPersistentDisk.Size())) - n196, err := m.GCEPersistentDisk.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n196 - } - if m.AWSElasticBlockStore != nil { - dAtA[i] = 0x22 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.AWSElasticBlockStore.Size())) - n197, err := m.AWSElasticBlockStore.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n197 - } - if m.GitRepo != nil { - dAtA[i] = 0x2a - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.GitRepo.Size())) - n198, err := m.GitRepo.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n198 - } - if m.Secret != nil { - dAtA[i] = 0x32 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Secret.Size())) - n199, err := m.Secret.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n199 - } - if m.NFS != nil { - dAtA[i] = 0x3a - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.NFS.Size())) - n200, err := m.NFS.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n200 - } - if m.ISCSI != nil { - dAtA[i] = 0x42 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.ISCSI.Size())) - n201, err := m.ISCSI.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n201 - } - if m.Glusterfs != nil { - dAtA[i] = 0x4a - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Glusterfs.Size())) - n202, err := m.Glusterfs.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n202 - } - if m.PersistentVolumeClaim != nil { - dAtA[i] = 0x52 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.PersistentVolumeClaim.Size())) - n203, err := m.PersistentVolumeClaim.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n203 - } - if m.RBD != nil { - dAtA[i] = 0x5a - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.RBD.Size())) - n204, err := m.RBD.MarshalTo(dAtA[i:]) + n204, err := m.HostPath.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n204 } - if m.FlexVolume != nil { - dAtA[i] = 0x62 + if m.EmptyDir != nil { + dAtA[i] = 0x12 i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.FlexVolume.Size())) - n205, err := m.FlexVolume.MarshalTo(dAtA[i:]) + i = encodeVarintGenerated(dAtA, i, uint64(m.EmptyDir.Size())) + n205, err := m.EmptyDir.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n205 } - if m.Cinder != nil { - dAtA[i] = 0x6a + if m.GCEPersistentDisk != nil { + dAtA[i] = 0x1a i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Cinder.Size())) - n206, err := m.Cinder.MarshalTo(dAtA[i:]) + i = encodeVarintGenerated(dAtA, i, uint64(m.GCEPersistentDisk.Size())) + n206, err := m.GCEPersistentDisk.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n206 } - if m.CephFS != nil { - dAtA[i] = 0x72 + if m.AWSElasticBlockStore != nil { + dAtA[i] = 0x22 i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.CephFS.Size())) - n207, err := m.CephFS.MarshalTo(dAtA[i:]) + i = encodeVarintGenerated(dAtA, i, uint64(m.AWSElasticBlockStore.Size())) + n207, err := m.AWSElasticBlockStore.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n207 } - if m.Flocker != nil { - dAtA[i] = 0x7a + if m.GitRepo != nil { + dAtA[i] = 0x2a i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Flocker.Size())) - n208, err := m.Flocker.MarshalTo(dAtA[i:]) + i = encodeVarintGenerated(dAtA, i, uint64(m.GitRepo.Size())) + n208, err := m.GitRepo.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n208 } + if m.Secret != nil { + dAtA[i] = 0x32 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Secret.Size())) + n209, err := m.Secret.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n209 + } + if m.NFS != nil { + dAtA[i] = 0x3a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.NFS.Size())) + n210, err := m.NFS.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n210 + } + if m.ISCSI != nil { + dAtA[i] = 0x42 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ISCSI.Size())) + n211, err := m.ISCSI.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n211 + } + if m.Glusterfs != nil { + dAtA[i] = 0x4a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Glusterfs.Size())) + n212, err := m.Glusterfs.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n212 + } + if m.PersistentVolumeClaim != nil { + dAtA[i] = 0x52 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.PersistentVolumeClaim.Size())) + n213, err := m.PersistentVolumeClaim.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n213 + } + if m.RBD != nil { + dAtA[i] = 0x5a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.RBD.Size())) + n214, err := m.RBD.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n214 + } + if m.FlexVolume != nil { + dAtA[i] = 0x62 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.FlexVolume.Size())) + n215, err := m.FlexVolume.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n215 + } + if m.Cinder != nil { + dAtA[i] = 0x6a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Cinder.Size())) + n216, err := m.Cinder.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n216 + } + if m.CephFS != nil { + dAtA[i] = 0x72 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.CephFS.Size())) + n217, err := m.CephFS.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n217 + } + if m.Flocker != nil { + dAtA[i] = 0x7a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Flocker.Size())) + n218, err := m.Flocker.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n218 + } if m.DownwardAPI != nil { dAtA[i] = 0x82 i++ dAtA[i] = 0x1 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.DownwardAPI.Size())) - n209, err := m.DownwardAPI.MarshalTo(dAtA[i:]) + n219, err := m.DownwardAPI.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n209 + i += n219 } if m.FC != nil { dAtA[i] = 0x8a @@ -9700,11 +10336,11 @@ func (m *VolumeSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.FC.Size())) - n210, err := m.FC.MarshalTo(dAtA[i:]) + n220, err := m.FC.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n210 + i += n220 } if m.AzureFile != nil { dAtA[i] = 0x92 @@ -9712,11 +10348,11 @@ func (m *VolumeSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.AzureFile.Size())) - n211, err := m.AzureFile.MarshalTo(dAtA[i:]) + n221, err := m.AzureFile.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n211 + i += n221 } if m.ConfigMap != nil { dAtA[i] = 0x9a @@ -9724,11 +10360,11 @@ func (m *VolumeSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ConfigMap.Size())) - n212, err := m.ConfigMap.MarshalTo(dAtA[i:]) + n222, err := m.ConfigMap.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n212 + i += n222 } if m.VsphereVolume != nil { dAtA[i] = 0xa2 @@ -9736,11 +10372,11 @@ func (m *VolumeSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.VsphereVolume.Size())) - n213, err := m.VsphereVolume.MarshalTo(dAtA[i:]) + n223, err := m.VsphereVolume.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n213 + i += n223 } if m.Quobyte != nil { dAtA[i] = 0xaa @@ -9748,11 +10384,11 @@ func (m *VolumeSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Quobyte.Size())) - n214, err := m.Quobyte.MarshalTo(dAtA[i:]) + n224, err := m.Quobyte.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n214 + i += n224 } if m.AzureDisk != nil { dAtA[i] = 0xb2 @@ -9760,11 +10396,11 @@ func (m *VolumeSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.AzureDisk.Size())) - n215, err := m.AzureDisk.MarshalTo(dAtA[i:]) + n225, err := m.AzureDisk.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n215 + i += n225 } if m.PhotonPersistentDisk != nil { dAtA[i] = 0xba @@ -9772,11 +10408,11 @@ func (m *VolumeSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.PhotonPersistentDisk.Size())) - n216, err := m.PhotonPersistentDisk.MarshalTo(dAtA[i:]) + n226, err := m.PhotonPersistentDisk.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n216 + i += n226 } if m.PortworxVolume != nil { dAtA[i] = 0xc2 @@ -9784,11 +10420,11 @@ func (m *VolumeSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.PortworxVolume.Size())) - n217, err := m.PortworxVolume.MarshalTo(dAtA[i:]) + n227, err := m.PortworxVolume.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n217 + i += n227 } if m.ScaleIO != nil { dAtA[i] = 0xca @@ -9796,11 +10432,11 @@ func (m *VolumeSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ScaleIO.Size())) - n218, err := m.ScaleIO.MarshalTo(dAtA[i:]) + n228, err := m.ScaleIO.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n218 + i += n228 } if m.Projected != nil { dAtA[i] = 0xd2 @@ -9808,11 +10444,11 @@ func (m *VolumeSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Projected.Size())) - n219, err := m.Projected.MarshalTo(dAtA[i:]) + n229, err := m.Projected.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n219 + i += n229 } if m.StorageOS != nil { dAtA[i] = 0xda @@ -9820,11 +10456,11 @@ func (m *VolumeSource) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.StorageOS.Size())) - n220, err := m.StorageOS.MarshalTo(dAtA[i:]) + n230, err := m.StorageOS.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n220 + i += n230 } return i, nil } @@ -9884,11 +10520,11 @@ func (m *WeightedPodAffinityTerm) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.PodAffinityTerm.Size())) - n221, err := m.PodAffinityTerm.MarshalTo(dAtA[i:]) + n231, err := m.PodAffinityTerm.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n221 + i += n231 return i, nil } @@ -10032,6 +10668,17 @@ func (m *Binding) Size() (n int) { return n } +func (m *CSIPersistentVolumeSource) Size() (n int) { + var l int + _ = l + l = len(m.Driver) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.VolumeHandle) + n += 1 + l + sovGenerated(uint64(l)) + n += 2 + return n +} + func (m *Capabilities) Size() (n int) { var l int _ = l @@ -10321,6 +10968,12 @@ func (m *Container) Size() (n int) { } l = len(m.TerminationMessagePolicy) n += 2 + l + sovGenerated(uint64(l)) + if len(m.VolumeDevices) > 0 { + for _, e := range m.VolumeDevices { + l = e.Size() + n += 2 + l + sovGenerated(uint64(l)) + } + } return n } @@ -10663,6 +11316,22 @@ func (m *Event) Size() (n int) { n += 1 + sovGenerated(uint64(m.Count)) l = len(m.Type) n += 1 + l + sovGenerated(uint64(l)) + l = m.EventTime.Size() + n += 1 + l + sovGenerated(uint64(l)) + if m.Series != nil { + l = m.Series.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + l = len(m.Action) + n += 1 + l + sovGenerated(uint64(l)) + if m.Related != nil { + l = m.Related.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + l = len(m.ReportingController) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.ReportingInstance) + n += 1 + l + sovGenerated(uint64(l)) return n } @@ -10680,6 +11349,17 @@ func (m *EventList) Size() (n int) { return n } +func (m *EventSeries) Size() (n int) { + var l int + _ = l + n += 1 + sovGenerated(uint64(m.Count)) + l = m.LastObservedTime.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.State) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + func (m *EventSource) Size() (n int) { var l int _ = l @@ -10726,6 +11406,29 @@ func (m *FCVolumeSource) Size() (n int) { return n } +func (m *FlexPersistentVolumeSource) Size() (n int) { + var l int + _ = l + l = len(m.Driver) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.FSType) + n += 1 + l + sovGenerated(uint64(l)) + if m.SecretRef != nil { + l = m.SecretRef.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + n += 2 + if len(m.Options) > 0 { + for k, v := range m.Options { + _ = k + _ = v + mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + len(v) + sovGenerated(uint64(len(v))) + n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) + } + } + return n +} + func (m *FlexVolumeSource) Size() (n int) { var l int _ = l @@ -10868,6 +11571,38 @@ func (m *HostPathVolumeSource) Size() (n int) { return n } +func (m *ISCSIPersistentVolumeSource) Size() (n int) { + var l int + _ = l + l = len(m.TargetPortal) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.IQN) + n += 1 + l + sovGenerated(uint64(l)) + n += 1 + sovGenerated(uint64(m.Lun)) + l = len(m.ISCSIInterface) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.FSType) + n += 1 + l + sovGenerated(uint64(l)) + n += 2 + if len(m.Portals) > 0 { + for _, s := range m.Portals { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + n += 2 + if m.SecretRef != nil { + l = m.SecretRef.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + n += 2 + if m.InitiatorName != nil { + l = len(*m.InitiatorName) + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + func (m *ISCSIVolumeSource) Size() (n int) { var l int _ = l @@ -11571,6 +12306,10 @@ func (m *PersistentVolumeClaimSpec) Size() (n int) { l = len(*m.StorageClassName) n += 1 + l + sovGenerated(uint64(l)) } + if m.VolumeMode != nil { + l = len(*m.VolumeMode) + n += 1 + l + sovGenerated(uint64(l)) + } return n } @@ -11713,6 +12452,10 @@ func (m *PersistentVolumeSource) Size() (n int) { l = m.StorageOS.Size() n += 2 + l + sovGenerated(uint64(l)) } + if m.CSI != nil { + l = m.CSI.Size() + n += 2 + l + sovGenerated(uint64(l)) + } return n } @@ -11750,6 +12493,10 @@ func (m *PersistentVolumeSpec) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) } } + if m.VolumeMode != nil { + l = len(*m.VolumeMode) + n += 1 + l + sovGenerated(uint64(l)) + } return n } @@ -11871,6 +12618,42 @@ func (m *PodCondition) Size() (n int) { return n } +func (m *PodDNSConfig) Size() (n int) { + var l int + _ = l + if len(m.Nameservers) > 0 { + for _, s := range m.Nameservers { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + if len(m.Searches) > 0 { + for _, s := range m.Searches { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + if len(m.Options) > 0 { + for _, e := range m.Options { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *PodDNSConfigOption) Size() (n int) { + var l int + _ = l + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + if m.Value != nil { + l = len(*m.Value) + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + func (m *PodExecOptions) Size() (n int) { var l int _ = l @@ -12068,6 +12851,10 @@ func (m *PodSpec) Size() (n int) { if m.Priority != nil { n += 2 + sovGenerated(uint64(*m.Priority)) } + if m.DNSConfig != nil { + l = m.DNSConfig.Size() + n += 2 + l + sovGenerated(uint64(l)) + } return n } @@ -12242,6 +13029,33 @@ func (m *QuobyteVolumeSource) Size() (n int) { return n } +func (m *RBDPersistentVolumeSource) Size() (n int) { + var l int + _ = l + if len(m.CephMonitors) > 0 { + for _, s := range m.CephMonitors { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + l = len(m.RBDImage) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.FSType) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.RBDPool) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.RadosUser) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Keyring) + n += 1 + l + sovGenerated(uint64(l)) + if m.SecretRef != nil { + l = m.SecretRef.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + n += 2 + return n +} + func (m *RBDVolumeSource) Size() (n int) { var l int _ = l @@ -12485,6 +13299,32 @@ func (m *SELinuxOptions) Size() (n int) { return n } +func (m *ScaleIOPersistentVolumeSource) Size() (n int) { + var l int + _ = l + l = len(m.Gateway) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.System) + n += 1 + l + sovGenerated(uint64(l)) + if m.SecretRef != nil { + l = m.SecretRef.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + n += 2 + l = len(m.ProtectionDomain) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.StoragePool) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.StorageMode) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.VolumeName) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.FSType) + n += 1 + l + sovGenerated(uint64(l)) + n += 2 + return n +} + func (m *ScaleIOVolumeSource) Size() (n int) { var l int _ = l @@ -12913,6 +13753,16 @@ func (m *Volume) Size() (n int) { return n } +func (m *VolumeDevice) Size() (n int) { + var l int + _ = l + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.DevicePath) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + func (m *VolumeMount) Size() (n int) { var l int _ = l @@ -13195,6 +14045,18 @@ func (this *Binding) String() string { }, "") return s } +func (this *CSIPersistentVolumeSource) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&CSIPersistentVolumeSource{`, + `Driver:` + fmt.Sprintf("%v", this.Driver) + `,`, + `VolumeHandle:` + fmt.Sprintf("%v", this.VolumeHandle) + `,`, + `ReadOnly:` + fmt.Sprintf("%v", this.ReadOnly) + `,`, + `}`, + }, "") + return s +} func (this *Capabilities) String() string { if this == nil { return "nil" @@ -13398,6 +14260,7 @@ func (this *Container) String() string { `TTY:` + fmt.Sprintf("%v", this.TTY) + `,`, `EnvFrom:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.EnvFrom), "EnvFromSource", "EnvFromSource", 1), `&`, ``, 1) + `,`, `TerminationMessagePolicy:` + fmt.Sprintf("%v", this.TerminationMessagePolicy) + `,`, + `VolumeDevices:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.VolumeDevices), "VolumeDevice", "VolumeDevice", 1), `&`, ``, 1) + `,`, `}`, }, "") return s @@ -13671,6 +14534,12 @@ func (this *Event) String() string { `LastTimestamp:` + strings.Replace(strings.Replace(this.LastTimestamp.String(), "Time", "k8s_io_apimachinery_pkg_apis_meta_v1.Time", 1), `&`, ``, 1) + `,`, `Count:` + fmt.Sprintf("%v", this.Count) + `,`, `Type:` + fmt.Sprintf("%v", this.Type) + `,`, + `EventTime:` + strings.Replace(strings.Replace(this.EventTime.String(), "MicroTime", "k8s_io_apimachinery_pkg_apis_meta_v1.MicroTime", 1), `&`, ``, 1) + `,`, + `Series:` + strings.Replace(fmt.Sprintf("%v", this.Series), "EventSeries", "EventSeries", 1) + `,`, + `Action:` + fmt.Sprintf("%v", this.Action) + `,`, + `Related:` + strings.Replace(fmt.Sprintf("%v", this.Related), "ObjectReference", "ObjectReference", 1) + `,`, + `ReportingController:` + fmt.Sprintf("%v", this.ReportingController) + `,`, + `ReportingInstance:` + fmt.Sprintf("%v", this.ReportingInstance) + `,`, `}`, }, "") return s @@ -13686,6 +14555,18 @@ func (this *EventList) String() string { }, "") return s } +func (this *EventSeries) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&EventSeries{`, + `Count:` + fmt.Sprintf("%v", this.Count) + `,`, + `LastObservedTime:` + strings.Replace(strings.Replace(this.LastObservedTime.String(), "MicroTime", "k8s_io_apimachinery_pkg_apis_meta_v1.MicroTime", 1), `&`, ``, 1) + `,`, + `State:` + fmt.Sprintf("%v", this.State) + `,`, + `}`, + }, "") + return s +} func (this *EventSource) String() string { if this == nil { return "nil" @@ -13721,6 +14602,30 @@ func (this *FCVolumeSource) String() string { }, "") return s } +func (this *FlexPersistentVolumeSource) String() string { + if this == nil { + return "nil" + } + keysForOptions := make([]string, 0, len(this.Options)) + for k := range this.Options { + keysForOptions = append(keysForOptions, k) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForOptions) + mapStringForOptions := "map[string]string{" + for _, k := range keysForOptions { + mapStringForOptions += fmt.Sprintf("%v: %v,", k, this.Options[k]) + } + mapStringForOptions += "}" + s := strings.Join([]string{`&FlexPersistentVolumeSource{`, + `Driver:` + fmt.Sprintf("%v", this.Driver) + `,`, + `FSType:` + fmt.Sprintf("%v", this.FSType) + `,`, + `SecretRef:` + strings.Replace(fmt.Sprintf("%v", this.SecretRef), "SecretReference", "SecretReference", 1) + `,`, + `ReadOnly:` + fmt.Sprintf("%v", this.ReadOnly) + `,`, + `Options:` + mapStringForOptions + `,`, + `}`, + }, "") + return s +} func (this *FlexVolumeSource) String() string { if this == nil { return "nil" @@ -13852,6 +14757,26 @@ func (this *HostPathVolumeSource) String() string { }, "") return s } +func (this *ISCSIPersistentVolumeSource) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ISCSIPersistentVolumeSource{`, + `TargetPortal:` + fmt.Sprintf("%v", this.TargetPortal) + `,`, + `IQN:` + fmt.Sprintf("%v", this.IQN) + `,`, + `Lun:` + fmt.Sprintf("%v", this.Lun) + `,`, + `ISCSIInterface:` + fmt.Sprintf("%v", this.ISCSIInterface) + `,`, + `FSType:` + fmt.Sprintf("%v", this.FSType) + `,`, + `ReadOnly:` + fmt.Sprintf("%v", this.ReadOnly) + `,`, + `Portals:` + fmt.Sprintf("%v", this.Portals) + `,`, + `DiscoveryCHAPAuth:` + fmt.Sprintf("%v", this.DiscoveryCHAPAuth) + `,`, + `SecretRef:` + strings.Replace(fmt.Sprintf("%v", this.SecretRef), "SecretReference", "SecretReference", 1) + `,`, + `SessionCHAPAuth:` + fmt.Sprintf("%v", this.SessionCHAPAuth) + `,`, + `InitiatorName:` + valueToStringGenerated(this.InitiatorName) + `,`, + `}`, + }, "") + return s +} func (this *ISCSIVolumeSource) String() string { if this == nil { return "nil" @@ -14461,6 +15386,7 @@ func (this *PersistentVolumeClaimSpec) String() string { `VolumeName:` + fmt.Sprintf("%v", this.VolumeName) + `,`, `Selector:` + strings.Replace(fmt.Sprintf("%v", this.Selector), "LabelSelector", "k8s_io_apimachinery_pkg_apis_meta_v1.LabelSelector", 1) + `,`, `StorageClassName:` + valueToStringGenerated(this.StorageClassName) + `,`, + `VolumeMode:` + valueToStringGenerated(this.VolumeMode) + `,`, `}`, }, "") return s @@ -14520,22 +15446,23 @@ func (this *PersistentVolumeSource) String() string { `HostPath:` + strings.Replace(fmt.Sprintf("%v", this.HostPath), "HostPathVolumeSource", "HostPathVolumeSource", 1) + `,`, `Glusterfs:` + strings.Replace(fmt.Sprintf("%v", this.Glusterfs), "GlusterfsVolumeSource", "GlusterfsVolumeSource", 1) + `,`, `NFS:` + strings.Replace(fmt.Sprintf("%v", this.NFS), "NFSVolumeSource", "NFSVolumeSource", 1) + `,`, - `RBD:` + strings.Replace(fmt.Sprintf("%v", this.RBD), "RBDVolumeSource", "RBDVolumeSource", 1) + `,`, - `ISCSI:` + strings.Replace(fmt.Sprintf("%v", this.ISCSI), "ISCSIVolumeSource", "ISCSIVolumeSource", 1) + `,`, + `RBD:` + strings.Replace(fmt.Sprintf("%v", this.RBD), "RBDPersistentVolumeSource", "RBDPersistentVolumeSource", 1) + `,`, + `ISCSI:` + strings.Replace(fmt.Sprintf("%v", this.ISCSI), "ISCSIPersistentVolumeSource", "ISCSIPersistentVolumeSource", 1) + `,`, `Cinder:` + strings.Replace(fmt.Sprintf("%v", this.Cinder), "CinderVolumeSource", "CinderVolumeSource", 1) + `,`, `CephFS:` + strings.Replace(fmt.Sprintf("%v", this.CephFS), "CephFSPersistentVolumeSource", "CephFSPersistentVolumeSource", 1) + `,`, `FC:` + strings.Replace(fmt.Sprintf("%v", this.FC), "FCVolumeSource", "FCVolumeSource", 1) + `,`, `Flocker:` + strings.Replace(fmt.Sprintf("%v", this.Flocker), "FlockerVolumeSource", "FlockerVolumeSource", 1) + `,`, - `FlexVolume:` + strings.Replace(fmt.Sprintf("%v", this.FlexVolume), "FlexVolumeSource", "FlexVolumeSource", 1) + `,`, + `FlexVolume:` + strings.Replace(fmt.Sprintf("%v", this.FlexVolume), "FlexPersistentVolumeSource", "FlexPersistentVolumeSource", 1) + `,`, `AzureFile:` + strings.Replace(fmt.Sprintf("%v", this.AzureFile), "AzureFilePersistentVolumeSource", "AzureFilePersistentVolumeSource", 1) + `,`, `VsphereVolume:` + strings.Replace(fmt.Sprintf("%v", this.VsphereVolume), "VsphereVirtualDiskVolumeSource", "VsphereVirtualDiskVolumeSource", 1) + `,`, `Quobyte:` + strings.Replace(fmt.Sprintf("%v", this.Quobyte), "QuobyteVolumeSource", "QuobyteVolumeSource", 1) + `,`, `AzureDisk:` + strings.Replace(fmt.Sprintf("%v", this.AzureDisk), "AzureDiskVolumeSource", "AzureDiskVolumeSource", 1) + `,`, `PhotonPersistentDisk:` + strings.Replace(fmt.Sprintf("%v", this.PhotonPersistentDisk), "PhotonPersistentDiskVolumeSource", "PhotonPersistentDiskVolumeSource", 1) + `,`, `PortworxVolume:` + strings.Replace(fmt.Sprintf("%v", this.PortworxVolume), "PortworxVolumeSource", "PortworxVolumeSource", 1) + `,`, - `ScaleIO:` + strings.Replace(fmt.Sprintf("%v", this.ScaleIO), "ScaleIOVolumeSource", "ScaleIOVolumeSource", 1) + `,`, + `ScaleIO:` + strings.Replace(fmt.Sprintf("%v", this.ScaleIO), "ScaleIOPersistentVolumeSource", "ScaleIOPersistentVolumeSource", 1) + `,`, `Local:` + strings.Replace(fmt.Sprintf("%v", this.Local), "LocalVolumeSource", "LocalVolumeSource", 1) + `,`, `StorageOS:` + strings.Replace(fmt.Sprintf("%v", this.StorageOS), "StorageOSPersistentVolumeSource", "StorageOSPersistentVolumeSource", 1) + `,`, + `CSI:` + strings.Replace(fmt.Sprintf("%v", this.CSI), "CSIPersistentVolumeSource", "CSIPersistentVolumeSource", 1) + `,`, `}`, }, "") return s @@ -14562,6 +15489,7 @@ func (this *PersistentVolumeSpec) String() string { `PersistentVolumeReclaimPolicy:` + fmt.Sprintf("%v", this.PersistentVolumeReclaimPolicy) + `,`, `StorageClassName:` + fmt.Sprintf("%v", this.StorageClassName) + `,`, `MountOptions:` + fmt.Sprintf("%v", this.MountOptions) + `,`, + `VolumeMode:` + valueToStringGenerated(this.VolumeMode) + `,`, `}`, }, "") return s @@ -14664,6 +15592,29 @@ func (this *PodCondition) String() string { }, "") return s } +func (this *PodDNSConfig) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&PodDNSConfig{`, + `Nameservers:` + fmt.Sprintf("%v", this.Nameservers) + `,`, + `Searches:` + fmt.Sprintf("%v", this.Searches) + `,`, + `Options:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Options), "PodDNSConfigOption", "PodDNSConfigOption", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *PodDNSConfigOption) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&PodDNSConfigOption{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `Value:` + valueToStringGenerated(this.Value) + `,`, + `}`, + }, "") + return s +} func (this *PodExecOptions) String() string { if this == nil { return "nil" @@ -14791,6 +15742,7 @@ func (this *PodSpec) String() string { `HostAliases:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.HostAliases), "HostAlias", "HostAlias", 1), `&`, ``, 1) + `,`, `PriorityClassName:` + fmt.Sprintf("%v", this.PriorityClassName) + `,`, `Priority:` + valueToStringGenerated(this.Priority) + `,`, + `DNSConfig:` + strings.Replace(fmt.Sprintf("%v", this.DNSConfig), "PodDNSConfig", "PodDNSConfig", 1) + `,`, `}`, }, "") return s @@ -14944,6 +15896,23 @@ func (this *QuobyteVolumeSource) String() string { }, "") return s } +func (this *RBDPersistentVolumeSource) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&RBDPersistentVolumeSource{`, + `CephMonitors:` + fmt.Sprintf("%v", this.CephMonitors) + `,`, + `RBDImage:` + fmt.Sprintf("%v", this.RBDImage) + `,`, + `FSType:` + fmt.Sprintf("%v", this.FSType) + `,`, + `RBDPool:` + fmt.Sprintf("%v", this.RBDPool) + `,`, + `RadosUser:` + fmt.Sprintf("%v", this.RadosUser) + `,`, + `Keyring:` + fmt.Sprintf("%v", this.Keyring) + `,`, + `SecretRef:` + strings.Replace(fmt.Sprintf("%v", this.SecretRef), "SecretReference", "SecretReference", 1) + `,`, + `ReadOnly:` + fmt.Sprintf("%v", this.ReadOnly) + `,`, + `}`, + }, "") + return s +} func (this *RBDVolumeSource) String() string { if this == nil { return "nil" @@ -15179,6 +16148,25 @@ func (this *SELinuxOptions) String() string { }, "") return s } +func (this *ScaleIOPersistentVolumeSource) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ScaleIOPersistentVolumeSource{`, + `Gateway:` + fmt.Sprintf("%v", this.Gateway) + `,`, + `System:` + fmt.Sprintf("%v", this.System) + `,`, + `SecretRef:` + strings.Replace(fmt.Sprintf("%v", this.SecretRef), "SecretReference", "SecretReference", 1) + `,`, + `SSLEnabled:` + fmt.Sprintf("%v", this.SSLEnabled) + `,`, + `ProtectionDomain:` + fmt.Sprintf("%v", this.ProtectionDomain) + `,`, + `StoragePool:` + fmt.Sprintf("%v", this.StoragePool) + `,`, + `StorageMode:` + fmt.Sprintf("%v", this.StorageMode) + `,`, + `VolumeName:` + fmt.Sprintf("%v", this.VolumeName) + `,`, + `FSType:` + fmt.Sprintf("%v", this.FSType) + `,`, + `ReadOnly:` + fmt.Sprintf("%v", this.ReadOnly) + `,`, + `}`, + }, "") + return s +} func (this *ScaleIOVolumeSource) String() string { if this == nil { return "nil" @@ -15538,6 +16526,17 @@ func (this *Volume) String() string { }, "") return s } +func (this *VolumeDevice) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&VolumeDevice{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `DevicePath:` + fmt.Sprintf("%v", this.DevicePath) + `,`, + `}`, + }, "") + return s +} func (this *VolumeMount) String() string { if this == nil { return "nil" @@ -16732,6 +17731,134 @@ func (m *Binding) Unmarshal(dAtA []byte) error { } return nil } +func (m *CSIPersistentVolumeSource) 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: CSIPersistentVolumeSource: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CSIPersistentVolumeSource: 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 > 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 VolumeHandle", 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 > l { + return io.ErrUnexpectedEOF + } + m.VolumeHandle = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ReadOnly", 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.ReadOnly = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *Capabilities) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -19293,6 +20420,37 @@ func (m *Container) Unmarshal(dAtA []byte) error { } m.TerminationMessagePolicy = TerminationMessagePolicy(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 21: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VolumeDevices", 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 > l { + return io.ErrUnexpectedEOF + } + m.VolumeDevices = append(m.VolumeDevices, VolumeDevice{}) + if err := m.VolumeDevices[len(m.VolumeDevices)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -22522,6 +23680,189 @@ func (m *Event) Unmarshal(dAtA []byte) error { } m.Type = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field EventTime", 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.EventTime.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Series", 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 > l { + return io.ErrUnexpectedEOF + } + if m.Series == nil { + m.Series = &EventSeries{} + } + if err := m.Series.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Action", 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 > l { + return io.ErrUnexpectedEOF + } + m.Action = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Related", 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 > l { + return io.ErrUnexpectedEOF + } + if m.Related == nil { + m.Related = &ObjectReference{} + } + if err := m.Related.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ReportingController", 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 > l { + return io.ErrUnexpectedEOF + } + m.ReportingController = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 15: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ReportingInstance", 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 > l { + return io.ErrUnexpectedEOF + } + m.ReportingInstance = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -22654,6 +23995,134 @@ func (m *EventList) Unmarshal(dAtA []byte) error { } return nil } +func (m *EventSeries) 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: EventSeries: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventSeries: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + 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 |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LastObservedTime", 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.LastObservedTime.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field State", 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 > l { + return io.ErrUnexpectedEOF + } + m.State = EventSeriesState(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *EventSource) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -23018,6 +24487,283 @@ func (m *FCVolumeSource) Unmarshal(dAtA []byte) error { } return nil } +func (m *FlexPersistentVolumeSource) 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: FlexPersistentVolumeSource: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: FlexPersistentVolumeSource: 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 > 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 FSType", 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 > l { + return io.ErrUnexpectedEOF + } + m.FSType = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SecretRef", 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 > l { + return io.ErrUnexpectedEOF + } + if m.SecretRef == nil { + m.SecretRef = &SecretReference{} + } + if err := m.SecretRef.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ReadOnly", 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.ReadOnly = bool(v != 0) + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Options", 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 > l { + return io.ErrUnexpectedEOF + } + var keykey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + keykey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + 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 > l { + return io.ErrUnexpectedEOF + } + mapkey := string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + if m.Options == nil { + m.Options = make(map[string]string) + } + if iNdEx < postIndex { + var valuekey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + valuekey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + var stringLenmapvalue uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapvalue |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapvalue := int(stringLenmapvalue) + if intStringLenmapvalue < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapvalue := iNdEx + intStringLenmapvalue + if postStringIndexmapvalue > l { + return io.ErrUnexpectedEOF + } + mapvalue := string(dAtA[iNdEx:postStringIndexmapvalue]) + iNdEx = postStringIndexmapvalue + m.Options[mapkey] = mapvalue + } else { + var mapvalue string + m.Options[mapkey] = mapvalue + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *FlexVolumeSource) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -24487,6 +26233,343 @@ func (m *HostPathVolumeSource) Unmarshal(dAtA []byte) error { } return nil } +func (m *ISCSIPersistentVolumeSource) 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: ISCSIPersistentVolumeSource: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ISCSIPersistentVolumeSource: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TargetPortal", 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 > l { + return io.ErrUnexpectedEOF + } + m.TargetPortal = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field IQN", 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 > l { + return io.ErrUnexpectedEOF + } + m.IQN = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Lun", wireType) + } + m.Lun = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Lun |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ISCSIInterface", 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 > l { + return io.ErrUnexpectedEOF + } + m.ISCSIInterface = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FSType", 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 > l { + return io.ErrUnexpectedEOF + } + m.FSType = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ReadOnly", 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.ReadOnly = bool(v != 0) + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Portals", 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 > l { + return io.ErrUnexpectedEOF + } + m.Portals = append(m.Portals, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DiscoveryCHAPAuth", 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.DiscoveryCHAPAuth = bool(v != 0) + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SecretRef", 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 > l { + return io.ErrUnexpectedEOF + } + if m.SecretRef == nil { + m.SecretRef = &SecretReference{} + } + if err := m.SecretRef.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SessionCHAPAuth", 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.SessionCHAPAuth = bool(v != 0) + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field InitiatorName", 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 > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.InitiatorName = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *ISCSIVolumeSource) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -31590,6 +33673,36 @@ func (m *PersistentVolumeClaimSpec) Unmarshal(dAtA []byte) error { s := string(dAtA[iNdEx:postIndex]) m.StorageClassName = &s iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VolumeMode", 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 > l { + return io.ErrUnexpectedEOF + } + s := PersistentVolumeMode(dAtA[iNdEx:postIndex]) + m.VolumeMode = &s + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -32302,7 +34415,7 @@ func (m *PersistentVolumeSource) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } if m.RBD == nil { - m.RBD = &RBDVolumeSource{} + m.RBD = &RBDPersistentVolumeSource{} } if err := m.RBD.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err @@ -32335,7 +34448,7 @@ func (m *PersistentVolumeSource) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } if m.ISCSI == nil { - m.ISCSI = &ISCSIVolumeSource{} + m.ISCSI = &ISCSIPersistentVolumeSource{} } if err := m.ISCSI.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err @@ -32500,7 +34613,7 @@ func (m *PersistentVolumeSource) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } if m.FlexVolume == nil { - m.FlexVolume = &FlexVolumeSource{} + m.FlexVolume = &FlexPersistentVolumeSource{} } if err := m.FlexVolume.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err @@ -32731,7 +34844,7 @@ func (m *PersistentVolumeSource) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } if m.ScaleIO == nil { - m.ScaleIO = &ScaleIOVolumeSource{} + m.ScaleIO = &ScaleIOPersistentVolumeSource{} } if err := m.ScaleIO.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err @@ -32803,6 +34916,39 @@ func (m *PersistentVolumeSource) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 22: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CSI", 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 > l { + return io.ErrUnexpectedEOF + } + if m.CSI == nil { + m.CSI = &CSIPersistentVolumeSource{} + } + if err := m.CSI.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -33153,6 +35299,36 @@ func (m *PersistentVolumeSpec) Unmarshal(dAtA []byte) error { } m.MountOptions = append(m.MountOptions, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VolumeMode", 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 > l { + return io.ErrUnexpectedEOF + } + s := PersistentVolumeMode(dAtA[iNdEx:postIndex]) + m.VolumeMode = &s + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -34309,6 +36485,254 @@ func (m *PodCondition) Unmarshal(dAtA []byte) error { } return nil } +func (m *PodDNSConfig) 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: PodDNSConfig: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PodDNSConfig: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Nameservers", 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 > l { + return io.ErrUnexpectedEOF + } + m.Nameservers = append(m.Nameservers, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Searches", 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 > l { + return io.ErrUnexpectedEOF + } + m.Searches = append(m.Searches, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Options", 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 > l { + return io.ErrUnexpectedEOF + } + m.Options = append(m.Options, PodDNSConfigOption{}) + if err := m.Options[len(m.Options)-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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PodDNSConfigOption) 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: PodDNSConfigOption: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PodDNSConfigOption: 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 > 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 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 > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.Value = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *PodExecOptions) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -36119,6 +38543,39 @@ func (m *PodSpec) Unmarshal(dAtA []byte) error { } } m.Priority = &v + case 26: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DNSConfig", 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 > l { + return io.ErrUnexpectedEOF + } + if m.DNSConfig == nil { + m.DNSConfig = &PodDNSConfig{} + } + if err := m.DNSConfig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -37868,6 +40325,283 @@ func (m *QuobyteVolumeSource) Unmarshal(dAtA []byte) error { } return nil } +func (m *RBDPersistentVolumeSource) 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: RBDPersistentVolumeSource: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RBDPersistentVolumeSource: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CephMonitors", 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 > l { + return io.ErrUnexpectedEOF + } + m.CephMonitors = append(m.CephMonitors, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RBDImage", 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 > l { + return io.ErrUnexpectedEOF + } + m.RBDImage = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FSType", 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 > l { + return io.ErrUnexpectedEOF + } + m.FSType = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RBDPool", 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 > l { + return io.ErrUnexpectedEOF + } + m.RBDPool = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RadosUser", 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 > l { + return io.ErrUnexpectedEOF + } + m.RadosUser = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Keyring", 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 > l { + return io.ErrUnexpectedEOF + } + m.Keyring = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SecretRef", 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 > l { + return io.ErrUnexpectedEOF + } + if m.SecretRef == nil { + m.SecretRef = &SecretReference{} + } + if err := m.SecretRef.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ReadOnly", 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.ReadOnly = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *RBDVolumeSource) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -40485,6 +43219,332 @@ func (m *SELinuxOptions) Unmarshal(dAtA []byte) error { } return nil } +func (m *ScaleIOPersistentVolumeSource) 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: ScaleIOPersistentVolumeSource: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ScaleIOPersistentVolumeSource: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Gateway", 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 > l { + return io.ErrUnexpectedEOF + } + m.Gateway = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field System", 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 > l { + return io.ErrUnexpectedEOF + } + m.System = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SecretRef", 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 > l { + return io.ErrUnexpectedEOF + } + if m.SecretRef == nil { + m.SecretRef = &SecretReference{} + } + if err := m.SecretRef.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SSLEnabled", 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.SSLEnabled = bool(v != 0) + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProtectionDomain", 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 > l { + return io.ErrUnexpectedEOF + } + m.ProtectionDomain = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StoragePool", 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 > l { + return io.ErrUnexpectedEOF + } + m.StoragePool = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StorageMode", 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 > l { + return io.ErrUnexpectedEOF + } + m.StorageMode = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VolumeName", 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 > l { + return io.ErrUnexpectedEOF + } + m.VolumeName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FSType", 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 > l { + return io.ErrUnexpectedEOF + } + m.FSType = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 10: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ReadOnly", 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.ReadOnly = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *ScaleIOVolumeSource) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -44692,6 +47752,114 @@ func (m *Volume) Unmarshal(dAtA []byte) error { } return nil } +func (m *VolumeDevice) 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: VolumeDevice: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: VolumeDevice: 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 > 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 DevicePath", 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 > l { + return io.ErrUnexpectedEOF + } + m.DevicePath = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *VolumeMount) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -46344,745 +49512,779 @@ func init() { } var fileDescriptorGenerated = []byte{ - // 11832 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0xbd, 0x6b, 0x70, 0x24, 0xd7, - 0x75, 0x18, 0xac, 0x9e, 0xc1, 0x6b, 0x0e, 0xde, 0x77, 0xb1, 0xe4, 0x10, 0x24, 0x17, 0xcb, 0xa6, - 0x44, 0x2e, 0x5f, 0x80, 0xb8, 0x24, 0x45, 0x5a, 0xa4, 0x28, 0x01, 0x18, 0x60, 0x17, 0xdc, 0xc5, - 0xee, 0xf0, 0x0e, 0x76, 0x57, 0xa4, 0x68, 0x7e, 0x6a, 0x4c, 0x5f, 0x00, 0x4d, 0x34, 0xba, 0x87, - 0xdd, 0x3d, 0xd8, 0x05, 0xcb, 0xaa, 0xfa, 0xa2, 0xc8, 0xca, 0x43, 0xfe, 0xe1, 0x4a, 0x54, 0x89, - 0x63, 0xa9, 0x9c, 0xaa, 0x3c, 0xca, 0x56, 0x9c, 0xa4, 0xe2, 0xc8, 0xb1, 0x1d, 0xc9, 0xa9, 0x38, - 0xce, 0xa3, 0xe4, 0x3f, 0x8a, 0x9d, 0x3f, 0x52, 0x95, 0x2b, 0x88, 0xb5, 0x4a, 0x25, 0xe5, 0x1f, - 0x49, 0x25, 0xf1, 0x2f, 0x23, 0x4e, 0x94, 0xba, 0xcf, 0xbe, 0xb7, 0xa7, 0x7b, 0x66, 0xb0, 0xdc, - 0x05, 0x29, 0x95, 0xff, 0xcd, 0x9c, 0x73, 0xee, 0xb9, 0xb7, 0xef, 0xe3, 0xdc, 0x73, 0xcf, 0x3d, - 0xe7, 0x5c, 0x78, 0x79, 0xf7, 0xa5, 0x78, 0xde, 0x0b, 0x17, 0x76, 0xdb, 0x9b, 0x24, 0x0a, 0x48, - 0x42, 0xe2, 0x85, 0x7d, 0x12, 0xb8, 0x61, 0xb4, 0x20, 0x10, 0x4e, 0xcb, 0x5b, 0x68, 0x86, 0x11, - 0x59, 0xd8, 0x7f, 0x76, 0x61, 0x9b, 0x04, 0x24, 0x72, 0x12, 0xe2, 0xce, 0xb7, 0xa2, 0x30, 0x09, - 0x11, 0xe2, 0x34, 0xf3, 0x4e, 0xcb, 0x9b, 0xa7, 0x34, 0xf3, 0xfb, 0xcf, 0xce, 0x3e, 0xb3, 0xed, - 0x25, 0x3b, 0xed, 0xcd, 0xf9, 0x66, 0xb8, 0xb7, 0xb0, 0x1d, 0x6e, 0x87, 0x0b, 0x8c, 0x74, 0xb3, - 0xbd, 0xc5, 0xfe, 0xb1, 0x3f, 0xec, 0x17, 0x67, 0x31, 0xbb, 0x9e, 0x56, 0x43, 0x6e, 0x25, 0x24, - 0x88, 0xbd, 0x30, 0x88, 0x9f, 0x71, 0x5a, 0x5e, 0x4c, 0xa2, 0x7d, 0x12, 0x2d, 0xb4, 0x76, 0xb7, - 0x29, 0x2e, 0x36, 0x09, 0x16, 0xf6, 0x9f, 0xdd, 0x24, 0x89, 0xd3, 0xd1, 0xa2, 0xd9, 0xe7, 0x53, - 0x76, 0x7b, 0x4e, 0x73, 0xc7, 0x0b, 0x48, 0x74, 0x20, 0x79, 0x2c, 0x44, 0x24, 0x0e, 0xdb, 0x51, - 0x93, 0x1c, 0xab, 0x54, 0xbc, 0xb0, 0x47, 0x12, 0x27, 0xe7, 0xeb, 0x67, 0x17, 0x8a, 0x4a, 0x45, - 0xed, 0x20, 0xf1, 0xf6, 0x3a, 0xab, 0xf9, 0x44, 0xaf, 0x02, 0x71, 0x73, 0x87, 0xec, 0x39, 0x1d, - 0xe5, 0x9e, 0x2b, 0x2a, 0xd7, 0x4e, 0x3c, 0x7f, 0xc1, 0x0b, 0x92, 0x38, 0x89, 0xb2, 0x85, 0xec, - 0xef, 0x59, 0x70, 0x76, 0xf1, 0x46, 0x63, 0xc5, 0x77, 0xe2, 0xc4, 0x6b, 0x2e, 0xf9, 0x61, 0x73, - 0xb7, 0x91, 0x84, 0x11, 0xb9, 0x1e, 0xfa, 0xed, 0x3d, 0xd2, 0x60, 0x1d, 0x81, 0x9e, 0x86, 0x91, - 0x7d, 0xf6, 0x7f, 0xad, 0x56, 0xb5, 0xce, 0x5a, 0xe7, 0x2a, 0x4b, 0x53, 0xdf, 0x39, 0x9c, 0xfb, - 0xc8, 0xed, 0xc3, 0xb9, 0x91, 0xeb, 0x02, 0x8e, 0x15, 0x05, 0x7a, 0x0c, 0x86, 0xb6, 0xe2, 0x8d, - 0x83, 0x16, 0xa9, 0x96, 0x18, 0xed, 0x84, 0xa0, 0x1d, 0x5a, 0x6d, 0x50, 0x28, 0x16, 0x58, 0xb4, - 0x00, 0x95, 0x96, 0x13, 0x25, 0x5e, 0xe2, 0x85, 0x41, 0xb5, 0x7c, 0xd6, 0x3a, 0x37, 0xb8, 0x34, - 0x2d, 0x48, 0x2b, 0x75, 0x89, 0xc0, 0x29, 0x0d, 0x6d, 0x46, 0x44, 0x1c, 0xf7, 0x6a, 0xe0, 0x1f, - 0x54, 0x07, 0xce, 0x5a, 0xe7, 0x46, 0xd2, 0x66, 0x60, 0x01, 0xc7, 0x8a, 0xc2, 0xfe, 0xc5, 0x12, - 0x8c, 0x2c, 0x6e, 0x6d, 0x79, 0x81, 0x97, 0x1c, 0xa0, 0xeb, 0x30, 0x16, 0x84, 0x2e, 0x91, 0xff, - 0xd9, 0x57, 0x8c, 0x9e, 0x3f, 0x3b, 0xdf, 0x39, 0x33, 0xe7, 0xaf, 0x68, 0x74, 0x4b, 0x53, 0xb7, - 0x0f, 0xe7, 0xc6, 0x74, 0x08, 0x36, 0xf8, 0x20, 0x0c, 0xa3, 0xad, 0xd0, 0x55, 0x6c, 0x4b, 0x8c, - 0xed, 0x5c, 0x1e, 0xdb, 0x7a, 0x4a, 0xb6, 0x34, 0x79, 0xfb, 0x70, 0x6e, 0x54, 0x03, 0x60, 0x9d, - 0x09, 0xda, 0x84, 0x49, 0xfa, 0x37, 0x48, 0x3c, 0xc5, 0xb7, 0xcc, 0xf8, 0x3e, 0x5a, 0xc4, 0x57, - 0x23, 0x5d, 0x3a, 0x75, 0xfb, 0x70, 0x6e, 0x32, 0x03, 0xc4, 0x59, 0x86, 0xf6, 0x7b, 0x30, 0xb1, - 0x98, 0x24, 0x4e, 0x73, 0x87, 0xb8, 0x7c, 0x04, 0xd1, 0xf3, 0x30, 0x10, 0x38, 0x7b, 0x44, 0x8c, - 0xef, 0x59, 0xd1, 0xb1, 0x03, 0x57, 0x9c, 0x3d, 0x72, 0x74, 0x38, 0x37, 0x75, 0x2d, 0xf0, 0xde, - 0x6d, 0x8b, 0x59, 0x41, 0x61, 0x98, 0x51, 0xa3, 0xf3, 0x00, 0x2e, 0xd9, 0xf7, 0x9a, 0xa4, 0xee, - 0x24, 0x3b, 0x62, 0xbc, 0x91, 0x28, 0x0b, 0x35, 0x85, 0xc1, 0x1a, 0x95, 0x7d, 0x0b, 0x2a, 0x8b, - 0xfb, 0xa1, 0xe7, 0xd6, 0x43, 0x37, 0x46, 0xbb, 0x30, 0xd9, 0x8a, 0xc8, 0x16, 0x89, 0x14, 0xa8, - 0x6a, 0x9d, 0x2d, 0x9f, 0x1b, 0x3d, 0x7f, 0x2e, 0xf7, 0x63, 0x4d, 0xd2, 0x95, 0x20, 0x89, 0x0e, - 0x96, 0xee, 0x17, 0xf5, 0x4d, 0x66, 0xb0, 0x38, 0xcb, 0xd9, 0xfe, 0x37, 0x25, 0x38, 0xbd, 0xf8, - 0x5e, 0x3b, 0x22, 0x35, 0x2f, 0xde, 0xcd, 0xce, 0x70, 0xd7, 0x8b, 0x77, 0xaf, 0xa4, 0x3d, 0xa0, - 0xa6, 0x56, 0x4d, 0xc0, 0xb1, 0xa2, 0x40, 0xcf, 0xc0, 0x30, 0xfd, 0x7d, 0x0d, 0xaf, 0x89, 0x4f, - 0x3e, 0x25, 0x88, 0x47, 0x6b, 0x4e, 0xe2, 0xd4, 0x38, 0x0a, 0x4b, 0x1a, 0xb4, 0x0e, 0xa3, 0x4d, - 0xb6, 0x20, 0xb7, 0xd7, 0x43, 0x97, 0xb0, 0xc1, 0xac, 0x2c, 0x3d, 0x45, 0xc9, 0x97, 0x53, 0xf0, - 0xd1, 0xe1, 0x5c, 0x95, 0xb7, 0x4d, 0xb0, 0xd0, 0x70, 0x58, 0x2f, 0x8f, 0x6c, 0xb5, 0xbe, 0x06, - 0x18, 0x27, 0xc8, 0x59, 0x5b, 0xe7, 0xb4, 0xa5, 0x32, 0xc8, 0x96, 0xca, 0x58, 0xfe, 0x32, 0x41, - 0xcf, 0xc2, 0xc0, 0xae, 0x17, 0xb8, 0xd5, 0x21, 0xc6, 0xeb, 0x61, 0x3a, 0xe6, 0x97, 0xbc, 0xc0, - 0x3d, 0x3a, 0x9c, 0x9b, 0x36, 0x9a, 0x43, 0x81, 0x98, 0x91, 0xda, 0x7f, 0x62, 0xc1, 0x1c, 0xc3, - 0xad, 0x7a, 0x3e, 0xa9, 0x93, 0x28, 0xf6, 0xe2, 0x84, 0x04, 0x89, 0xd1, 0xa1, 0xe7, 0x01, 0x62, - 0xd2, 0x8c, 0x48, 0xa2, 0x75, 0xa9, 0x9a, 0x18, 0x0d, 0x85, 0xc1, 0x1a, 0x15, 0x15, 0x08, 0xf1, - 0x8e, 0x13, 0xb1, 0xf9, 0x25, 0x3a, 0x56, 0x09, 0x84, 0x86, 0x44, 0xe0, 0x94, 0xc6, 0x10, 0x08, - 0xe5, 0x5e, 0x02, 0x01, 0x7d, 0x0a, 0x26, 0xd3, 0xca, 0xe2, 0x96, 0xd3, 0x94, 0x1d, 0xc8, 0x96, - 0x4c, 0xc3, 0x44, 0xe1, 0x2c, 0xad, 0xfd, 0x0f, 0x2c, 0x31, 0x79, 0xe8, 0x57, 0x7f, 0xc8, 0xbf, - 0xd5, 0xfe, 0x2d, 0x0b, 0x86, 0x97, 0xbc, 0xc0, 0xf5, 0x82, 0x6d, 0xf4, 0x79, 0x18, 0xa1, 0x7b, - 0x93, 0xeb, 0x24, 0x8e, 0x90, 0x7b, 0x1f, 0xd7, 0xd6, 0x96, 0xda, 0x2a, 0xe6, 0x5b, 0xbb, 0xdb, - 0x14, 0x10, 0xcf, 0x53, 0x6a, 0xba, 0xda, 0xae, 0x6e, 0xbe, 0x43, 0x9a, 0xc9, 0x3a, 0x49, 0x9c, - 0xf4, 0x73, 0x52, 0x18, 0x56, 0x5c, 0xd1, 0x25, 0x18, 0x4a, 0x9c, 0x68, 0x9b, 0x24, 0x42, 0x00, - 0xe6, 0x0a, 0x2a, 0x5e, 0x12, 0xd3, 0x15, 0x49, 0x82, 0x26, 0x49, 0xb7, 0x85, 0x0d, 0x56, 0x14, - 0x0b, 0x16, 0x76, 0x13, 0xc6, 0x96, 0x9d, 0x96, 0xb3, 0xe9, 0xf9, 0x5e, 0xe2, 0x91, 0x18, 0x3d, - 0x0e, 0x65, 0xc7, 0x75, 0x99, 0x54, 0xa8, 0x2c, 0x9d, 0xbe, 0x7d, 0x38, 0x57, 0x5e, 0x74, 0xe9, - 0xf4, 0x04, 0x45, 0x75, 0x80, 0x29, 0x05, 0x7a, 0x12, 0x06, 0xdc, 0x28, 0x6c, 0x55, 0x4b, 0x8c, - 0xf2, 0x3e, 0x3a, 0x93, 0x6b, 0x51, 0xd8, 0xca, 0x90, 0x32, 0x1a, 0xfb, 0x77, 0x4a, 0xf0, 0xd0, - 0x32, 0x69, 0xed, 0xac, 0x36, 0x0a, 0xe6, 0xef, 0x39, 0x18, 0xd9, 0x0b, 0x03, 0x2f, 0x09, 0xa3, - 0x58, 0x54, 0xcd, 0x16, 0xd0, 0xba, 0x80, 0x61, 0x85, 0x45, 0x67, 0x61, 0xa0, 0x95, 0x0a, 0xbf, - 0x31, 0x29, 0x38, 0x99, 0xd8, 0x63, 0x18, 0x4a, 0xd1, 0x8e, 0x49, 0x24, 0x16, 0xbe, 0xa2, 0xb8, - 0x16, 0x93, 0x08, 0x33, 0x4c, 0x3a, 0x83, 0xe8, 0xdc, 0x12, 0xb3, 0x32, 0x33, 0x83, 0x28, 0x06, - 0x6b, 0x54, 0xa8, 0x0e, 0x15, 0xfe, 0x0f, 0x93, 0x2d, 0xb6, 0xc6, 0x0b, 0xfa, 0xbd, 0x21, 0x89, - 0x44, 0xbf, 0x8f, 0xb3, 0x29, 0x26, 0x81, 0x38, 0x65, 0x62, 0x4c, 0xb1, 0xa1, 0x9e, 0x53, 0xec, - 0xdb, 0x25, 0x40, 0xbc, 0x0b, 0x7f, 0xcc, 0x3a, 0xee, 0x5a, 0x67, 0xc7, 0xe5, 0x6e, 0x36, 0x97, - 0xc3, 0xa6, 0xe3, 0x67, 0x67, 0xed, 0xdd, 0xea, 0xbd, 0x5f, 0xb0, 0x00, 0x2d, 0x7b, 0x81, 0x4b, - 0xa2, 0x13, 0xd0, 0xb4, 0x8e, 0x27, 0x3b, 0x2e, 0xc3, 0xc4, 0xb2, 0xef, 0x91, 0x20, 0x59, 0xab, - 0x2f, 0x87, 0xc1, 0x96, 0xb7, 0x8d, 0x3e, 0x09, 0x13, 0x54, 0xf1, 0x0c, 0xdb, 0x49, 0x83, 0x34, - 0xc3, 0x80, 0xed, 0xd1, 0x54, 0x5d, 0x43, 0xb7, 0x0f, 0xe7, 0x26, 0x36, 0x0c, 0x0c, 0xce, 0x50, - 0xda, 0x7f, 0x48, 0x3f, 0x34, 0xdc, 0x6b, 0x85, 0x01, 0x09, 0x92, 0xe5, 0x30, 0x70, 0xb9, 0x2e, - 0xf7, 0x49, 0x18, 0x48, 0x68, 0xc3, 0xf9, 0x47, 0x3e, 0x26, 0x87, 0x96, 0x36, 0xf7, 0xe8, 0x70, - 0xee, 0xbe, 0xce, 0x12, 0xec, 0x83, 0x58, 0x19, 0xf4, 0x53, 0x30, 0x14, 0x27, 0x4e, 0xd2, 0x8e, - 0xc5, 0x67, 0x3f, 0x22, 0x3f, 0xbb, 0xc1, 0xa0, 0x47, 0x87, 0x73, 0x93, 0xaa, 0x18, 0x07, 0x61, - 0x51, 0x00, 0x3d, 0x01, 0xc3, 0x7b, 0x24, 0x8e, 0x9d, 0x6d, 0xb9, 0x0d, 0x4f, 0x8a, 0xb2, 0xc3, - 0xeb, 0x1c, 0x8c, 0x25, 0x1e, 0x3d, 0x0a, 0x83, 0x24, 0x8a, 0xc2, 0x48, 0xcc, 0xaa, 0x71, 0x41, - 0x38, 0xb8, 0x42, 0x81, 0x98, 0xe3, 0xec, 0x7f, 0x6f, 0xc1, 0xa4, 0x6a, 0x2b, 0xaf, 0xeb, 0x04, - 0xe4, 0xed, 0x9b, 0x00, 0x4d, 0xf9, 0x81, 0x31, 0x93, 0x77, 0xa3, 0xe7, 0x1f, 0xcb, 0x9b, 0xc2, - 0x9d, 0xdd, 0x98, 0x72, 0x56, 0xa0, 0x18, 0x6b, 0xdc, 0xec, 0x7f, 0x61, 0xc1, 0xa9, 0xcc, 0x17, - 0x5d, 0xf6, 0xe2, 0x04, 0xbd, 0xd5, 0xf1, 0x55, 0xf3, 0xfd, 0x7d, 0x15, 0x2d, 0xcd, 0xbe, 0x49, - 0xcd, 0x39, 0x09, 0xd1, 0xbe, 0xe8, 0x22, 0x0c, 0x7a, 0x09, 0xd9, 0x93, 0x1f, 0xf3, 0x68, 0xd7, - 0x8f, 0xe1, 0xad, 0x4a, 0x47, 0x64, 0x8d, 0x96, 0xc4, 0x9c, 0x81, 0xfd, 0x3f, 0x2d, 0xa8, 0xf0, - 0x69, 0xbb, 0xee, 0xb4, 0x4e, 0x60, 0x2c, 0xd6, 0x60, 0x80, 0x71, 0xe7, 0x0d, 0x7f, 0x3c, 0xbf, - 0xe1, 0xa2, 0x39, 0xf3, 0x54, 0x99, 0xe2, 0x4a, 0xab, 0x12, 0x66, 0x14, 0x84, 0x19, 0x8b, 0xd9, - 0x17, 0xa1, 0xa2, 0x08, 0xd0, 0x14, 0x94, 0x77, 0x09, 0x3f, 0xa8, 0x54, 0x30, 0xfd, 0x89, 0x66, - 0x60, 0x70, 0xdf, 0xf1, 0xdb, 0x62, 0xb1, 0x63, 0xfe, 0xe7, 0x93, 0xa5, 0x97, 0x2c, 0xfb, 0x5b, - 0x6c, 0x8d, 0x89, 0x4a, 0x56, 0x82, 0x7d, 0x21, 0x4c, 0xde, 0x83, 0x19, 0x3f, 0x47, 0x86, 0x89, - 0x8e, 0xe8, 0x5f, 0xe6, 0x3d, 0x24, 0xda, 0x3a, 0x93, 0x87, 0xc5, 0xb9, 0x75, 0xd0, 0x6d, 0x20, - 0x6c, 0xd1, 0x19, 0xe5, 0xf8, 0xac, 0xbd, 0x42, 0x01, 0xbd, 0x2a, 0x60, 0x58, 0x61, 0xa9, 0x80, - 0x98, 0x51, 0x8d, 0xbf, 0x44, 0x0e, 0x1a, 0xc4, 0x27, 0xcd, 0x24, 0x8c, 0x3e, 0xd0, 0xe6, 0x3f, - 0xcc, 0x7b, 0x9f, 0xcb, 0x97, 0x51, 0xc1, 0xa0, 0x7c, 0x89, 0x1c, 0xf0, 0xa1, 0xd0, 0xbf, 0xae, - 0xdc, 0xf5, 0xeb, 0x7e, 0xcd, 0x82, 0x71, 0xf5, 0x75, 0x27, 0xb0, 0x90, 0x96, 0xcc, 0x85, 0xf4, - 0x70, 0xd7, 0xf9, 0x58, 0xb0, 0x84, 0x7e, 0xc4, 0x44, 0x80, 0xa0, 0xa9, 0x47, 0x21, 0xed, 0x1a, - 0x2a, 0xb3, 0x3f, 0xc8, 0x01, 0xe9, 0xe7, 0xbb, 0x2e, 0x91, 0x83, 0x8d, 0x90, 0xaa, 0x0f, 0xf9, - 0xdf, 0x65, 0x8c, 0xda, 0x40, 0xd7, 0x51, 0xfb, 0xf5, 0x12, 0x9c, 0x56, 0x3d, 0x60, 0x6c, 0xd0, - 0x3f, 0xee, 0x7d, 0xf0, 0x2c, 0x8c, 0xba, 0x64, 0xcb, 0x69, 0xfb, 0x89, 0x3a, 0x8b, 0x0e, 0x72, - 0x7b, 0x44, 0x2d, 0x05, 0x63, 0x9d, 0xe6, 0x18, 0xdd, 0xf6, 0x6f, 0x81, 0xc9, 0xde, 0xc4, 0xa1, - 0x33, 0x98, 0x6a, 0x6f, 0x9a, 0x45, 0x61, 0x4c, 0xb7, 0x28, 0x08, 0xeb, 0xc1, 0xa3, 0x30, 0xe8, - 0xed, 0xd1, 0xbd, 0xb8, 0x64, 0x6e, 0xb1, 0x6b, 0x14, 0x88, 0x39, 0x0e, 0x7d, 0x0c, 0x86, 0x9b, - 0xe1, 0xde, 0x9e, 0x13, 0xb8, 0xd5, 0x32, 0xd3, 0x27, 0x47, 0xe9, 0x76, 0xbd, 0xcc, 0x41, 0x58, - 0xe2, 0xd0, 0x43, 0x30, 0xe0, 0x44, 0xdb, 0x71, 0x75, 0x80, 0xd1, 0x8c, 0xd0, 0x9a, 0x16, 0xa3, - 0xed, 0x18, 0x33, 0x28, 0xd5, 0x13, 0x6f, 0x86, 0xd1, 0xae, 0x17, 0x6c, 0xd7, 0xbc, 0x88, 0x29, - 0x7d, 0x9a, 0x9e, 0x78, 0x43, 0x61, 0xb0, 0x46, 0x85, 0x56, 0x61, 0xb0, 0x15, 0x46, 0x49, 0x5c, - 0x1d, 0x62, 0xdd, 0xfd, 0x48, 0xc1, 0x52, 0xe2, 0x5f, 0x5b, 0x0f, 0xa3, 0x24, 0xfd, 0x00, 0xfa, - 0x2f, 0xc6, 0xbc, 0x38, 0xfa, 0x29, 0x28, 0x93, 0x60, 0xbf, 0x3a, 0xcc, 0xb8, 0xcc, 0xe6, 0x71, - 0x59, 0x09, 0xf6, 0xaf, 0x3b, 0x51, 0x2a, 0x67, 0x56, 0x82, 0x7d, 0x4c, 0xcb, 0xa0, 0x37, 0xa0, - 0x22, 0xad, 0x91, 0x71, 0x75, 0xa4, 0x78, 0x8a, 0x61, 0x41, 0x84, 0xc9, 0xbb, 0x6d, 0x2f, 0x22, - 0x7b, 0x24, 0x48, 0xe2, 0xf4, 0x3c, 0x29, 0xb1, 0x31, 0x4e, 0xb9, 0xa1, 0x37, 0x60, 0x8c, 0xeb, - 0x91, 0xeb, 0x61, 0x3b, 0x48, 0xe2, 0x6a, 0x85, 0x35, 0x2f, 0xd7, 0x74, 0x75, 0x3d, 0xa5, 0x5b, - 0x9a, 0x11, 0x4c, 0xc7, 0x34, 0x60, 0x8c, 0x0d, 0x56, 0x08, 0xc3, 0xb8, 0xef, 0xed, 0x93, 0x80, - 0xc4, 0x71, 0x3d, 0x0a, 0x37, 0x49, 0x15, 0x58, 0xcb, 0x1f, 0xc8, 0xb7, 0xe8, 0x84, 0x9b, 0x64, - 0x69, 0xfa, 0xf6, 0xe1, 0xdc, 0xf8, 0x65, 0xbd, 0x0c, 0x36, 0x59, 0xa0, 0x6b, 0x30, 0x41, 0x15, - 0x54, 0x2f, 0x65, 0x3a, 0xda, 0x8b, 0x29, 0xd3, 0x4e, 0xb1, 0x51, 0x08, 0x67, 0x98, 0xa0, 0xd7, - 0xa0, 0xe2, 0x7b, 0x5b, 0xa4, 0x79, 0xd0, 0xf4, 0x49, 0x75, 0x8c, 0x71, 0xcc, 0x5d, 0x56, 0x97, - 0x25, 0x11, 0x3f, 0x00, 0xa8, 0xbf, 0x38, 0x2d, 0x8e, 0xae, 0xc3, 0x7d, 0x09, 0x89, 0xf6, 0xbc, - 0xc0, 0xa1, 0xcb, 0x41, 0xe8, 0x93, 0xcc, 0x2e, 0x36, 0xce, 0xe6, 0xdb, 0x19, 0xd1, 0x75, 0xf7, - 0x6d, 0xe4, 0x52, 0xe1, 0x82, 0xd2, 0xe8, 0x2a, 0x4c, 0xb2, 0x95, 0x50, 0x6f, 0xfb, 0x7e, 0x3d, - 0xf4, 0xbd, 0xe6, 0x41, 0x75, 0x82, 0x31, 0xfc, 0x98, 0x34, 0x7c, 0xad, 0x99, 0x68, 0x7a, 0xe2, - 0x4d, 0xff, 0xe1, 0x6c, 0x69, 0xb4, 0xc9, 0x0c, 0x21, 0xed, 0xc8, 0x4b, 0x0e, 0xe8, 0xfc, 0x25, - 0xb7, 0x92, 0xea, 0x64, 0xd7, 0xf3, 0xa3, 0x4e, 0xaa, 0xac, 0x25, 0x3a, 0x10, 0x67, 0x19, 0xd2, - 0xa5, 0x1d, 0x27, 0xae, 0x17, 0x54, 0xa7, 0x98, 0xc4, 0x50, 0x2b, 0xa3, 0x41, 0x81, 0x98, 0xe3, - 0x98, 0x11, 0x84, 0xfe, 0xb8, 0x4a, 0x25, 0xe8, 0x34, 0x23, 0x4c, 0x8d, 0x20, 0x12, 0x81, 0x53, - 0x1a, 0xba, 0x2d, 0x27, 0xc9, 0x41, 0x15, 0x31, 0x52, 0xb5, 0x5c, 0x36, 0x36, 0xde, 0xc0, 0x14, - 0x8e, 0x2e, 0xc3, 0x30, 0x09, 0xf6, 0x57, 0xa3, 0x70, 0xaf, 0x7a, 0xaa, 0x78, 0xcd, 0xae, 0x70, - 0x12, 0x2e, 0xd0, 0xd3, 0x03, 0x80, 0x00, 0x63, 0xc9, 0x02, 0xdd, 0x82, 0x6a, 0xce, 0x88, 0xf0, - 0x01, 0x98, 0x61, 0x03, 0xf0, 0x8a, 0x28, 0x5b, 0xdd, 0x28, 0xa0, 0x3b, 0xea, 0x82, 0xc3, 0x85, - 0xdc, 0xed, 0x4d, 0x98, 0x50, 0x82, 0x85, 0x8d, 0x2d, 0x9a, 0x83, 0x41, 0x2a, 0x31, 0xe5, 0x91, - 0xba, 0x42, 0xbb, 0x92, 0x99, 0xa6, 0x30, 0x87, 0xb3, 0xae, 0xf4, 0xde, 0x23, 0x4b, 0x07, 0x09, - 0xe1, 0xc7, 0xa2, 0xb2, 0xd6, 0x95, 0x12, 0x81, 0x53, 0x1a, 0xfb, 0xff, 0x72, 0xc5, 0x24, 0x95, - 0x5e, 0x7d, 0xc8, 0xeb, 0xa7, 0x61, 0x64, 0x27, 0x8c, 0x13, 0x4a, 0xcd, 0xea, 0x18, 0x4c, 0x55, - 0x91, 0x8b, 0x02, 0x8e, 0x15, 0x05, 0x7a, 0x19, 0xc6, 0x9b, 0x7a, 0x05, 0x62, 0xb3, 0x39, 0x2d, - 0x8a, 0x98, 0xb5, 0x63, 0x93, 0x16, 0xbd, 0x04, 0x23, 0xec, 0x82, 0xa2, 0x19, 0xfa, 0xe2, 0x00, - 0x26, 0x77, 0xcc, 0x91, 0xba, 0x80, 0x1f, 0x69, 0xbf, 0xb1, 0xa2, 0xa6, 0x87, 0x62, 0xda, 0x84, - 0xb5, 0xba, 0x10, 0xf3, 0xea, 0x50, 0x7c, 0x91, 0x41, 0xb1, 0xc0, 0xda, 0x7f, 0xad, 0xa4, 0xf5, - 0x32, 0x3d, 0x52, 0x10, 0x54, 0x87, 0xe1, 0x9b, 0x8e, 0x97, 0x78, 0xc1, 0xb6, 0xd8, 0xcf, 0x9f, - 0xe8, 0x2a, 0xf3, 0x59, 0xa1, 0x1b, 0xbc, 0x00, 0xdf, 0x95, 0xc4, 0x1f, 0x2c, 0xd9, 0x50, 0x8e, - 0x51, 0x3b, 0x08, 0x28, 0xc7, 0x52, 0xbf, 0x1c, 0x31, 0x2f, 0xc0, 0x39, 0x8a, 0x3f, 0x58, 0xb2, - 0x41, 0x6f, 0x01, 0xc8, 0x79, 0x43, 0x5c, 0x71, 0x31, 0xf0, 0x74, 0x6f, 0xa6, 0x1b, 0xaa, 0xcc, - 0xd2, 0x04, 0xdd, 0xf3, 0xd2, 0xff, 0x58, 0xe3, 0x67, 0x27, 0x4c, 0xef, 0xe9, 0x6c, 0x0c, 0xfa, - 0x1c, 0x5d, 0xaa, 0x4e, 0x94, 0x10, 0x77, 0x31, 0x11, 0x9d, 0xf3, 0x64, 0x7f, 0x6a, 0xeb, 0x86, - 0xb7, 0x47, 0xf4, 0x65, 0x2d, 0x98, 0xe0, 0x94, 0x9f, 0xfd, 0x9b, 0x65, 0xa8, 0x16, 0x35, 0x97, - 0x4e, 0x3a, 0x72, 0xcb, 0x4b, 0x96, 0xa9, 0xba, 0x62, 0x99, 0x93, 0x6e, 0x45, 0xc0, 0xb1, 0xa2, - 0xa0, 0xa3, 0x1f, 0x7b, 0xdb, 0xf2, 0xd4, 0x31, 0x98, 0x8e, 0x7e, 0x83, 0x41, 0xb1, 0xc0, 0x52, - 0xba, 0x88, 0x38, 0xb1, 0xb8, 0x79, 0xd2, 0x66, 0x09, 0x66, 0x50, 0x2c, 0xb0, 0xba, 0xc1, 0x60, - 0xa0, 0x87, 0xc1, 0xc0, 0xe8, 0xa2, 0xc1, 0xbb, 0xdb, 0x45, 0xe8, 0x6d, 0x80, 0x2d, 0x2f, 0xf0, - 0xe2, 0x1d, 0xc6, 0x7d, 0xe8, 0xd8, 0xdc, 0x95, 0xb2, 0xb3, 0xaa, 0xb8, 0x60, 0x8d, 0x23, 0x7a, - 0x01, 0x46, 0xd5, 0x02, 0x5c, 0xab, 0x55, 0x87, 0xcd, 0x6b, 0x8d, 0x54, 0x1a, 0xd5, 0xb0, 0x4e, - 0x67, 0xbf, 0x93, 0x9d, 0x2f, 0x62, 0x05, 0x68, 0xfd, 0x6b, 0xf5, 0xdb, 0xbf, 0xa5, 0xee, 0xfd, - 0x6b, 0xff, 0x6e, 0x19, 0x26, 0x8d, 0xca, 0xda, 0x71, 0x1f, 0x32, 0xeb, 0x02, 0xdd, 0x88, 0x9c, - 0x84, 0x88, 0xf5, 0x67, 0xf7, 0x5e, 0x2a, 0xfa, 0x66, 0x45, 0x57, 0x00, 0x2f, 0x8f, 0xde, 0x86, - 0x8a, 0xef, 0xc4, 0xcc, 0xf8, 0x40, 0xc4, 0xba, 0xeb, 0x87, 0x59, 0xaa, 0xe8, 0x3b, 0x71, 0xa2, - 0xed, 0x05, 0x9c, 0x77, 0xca, 0x92, 0xee, 0x98, 0x54, 0x39, 0x91, 0x57, 0x9b, 0xaa, 0x11, 0x54, - 0x83, 0x39, 0xc0, 0x1c, 0x87, 0x5e, 0x82, 0xb1, 0x88, 0xb0, 0x59, 0xb1, 0x4c, 0x75, 0x2d, 0x36, - 0xcd, 0x06, 0x53, 0xa5, 0x0c, 0x6b, 0x38, 0x6c, 0x50, 0xa6, 0xba, 0xf6, 0x50, 0x17, 0x5d, 0xfb, - 0x09, 0x18, 0x66, 0x3f, 0xd4, 0x0c, 0x50, 0xa3, 0xb1, 0xc6, 0xc1, 0x58, 0xe2, 0xb3, 0x13, 0x66, - 0xa4, 0xcf, 0x09, 0xf3, 0x24, 0x4c, 0xd4, 0x1c, 0xb2, 0x17, 0x06, 0x2b, 0x81, 0xdb, 0x0a, 0xbd, - 0x20, 0x41, 0x55, 0x18, 0x60, 0xbb, 0x03, 0x5f, 0xdb, 0x03, 0x94, 0x03, 0x1e, 0xa0, 0x9a, 0xb3, - 0xfd, 0x07, 0x25, 0x18, 0xaf, 0x11, 0x9f, 0x24, 0x84, 0x9f, 0x35, 0x62, 0xb4, 0x0a, 0x68, 0x3b, - 0x72, 0x9a, 0xa4, 0x4e, 0x22, 0x2f, 0x74, 0x75, 0x63, 0x64, 0x99, 0x19, 0xfc, 0xd1, 0x85, 0x0e, - 0x2c, 0xce, 0x29, 0x81, 0xde, 0x84, 0xf1, 0x56, 0x44, 0x0c, 0x1b, 0x9a, 0x55, 0xa4, 0x2e, 0xd4, - 0x75, 0x42, 0xae, 0xa9, 0x1a, 0x20, 0x6c, 0xb2, 0x42, 0x9f, 0x81, 0xa9, 0x30, 0x6a, 0xed, 0x38, - 0x41, 0x8d, 0xb4, 0x48, 0xe0, 0x52, 0x55, 0x5c, 0xd8, 0x08, 0x66, 0x6e, 0x1f, 0xce, 0x4d, 0x5d, - 0xcd, 0xe0, 0x70, 0x07, 0x35, 0x7a, 0x13, 0xa6, 0x5b, 0x51, 0xd8, 0x72, 0xb6, 0xd9, 0x44, 0x11, - 0x1a, 0x07, 0x97, 0x3e, 0x4f, 0xdf, 0x3e, 0x9c, 0x9b, 0xae, 0x67, 0x91, 0x47, 0x87, 0x73, 0xa7, - 0x58, 0x47, 0x51, 0x48, 0x8a, 0xc4, 0x9d, 0x6c, 0xec, 0x6d, 0x38, 0x5d, 0x0b, 0x6f, 0x06, 0x37, - 0x9d, 0xc8, 0x5d, 0xac, 0xaf, 0x69, 0x87, 0xfb, 0x2b, 0xf2, 0x70, 0xc9, 0xaf, 0x5f, 0x73, 0xf7, - 0x29, 0xad, 0x24, 0x57, 0xff, 0x57, 0x3d, 0x9f, 0x14, 0x18, 0x11, 0xfe, 0x66, 0xc9, 0xa8, 0x29, - 0xa5, 0x57, 0x76, 0x7f, 0xab, 0xd0, 0xee, 0xff, 0x3a, 0x8c, 0x6c, 0x79, 0xc4, 0x77, 0x31, 0xd9, - 0x12, 0x23, 0xf3, 0x78, 0xf1, 0x8d, 0xd2, 0x2a, 0xa5, 0x94, 0x46, 0x23, 0x7e, 0x34, 0x5d, 0x15, - 0x85, 0xb1, 0x62, 0x83, 0x76, 0x61, 0x4a, 0x9e, 0x7d, 0x24, 0x56, 0x2c, 0xe2, 0x27, 0xba, 0x1d, - 0xa8, 0x4c, 0xe6, 0x6c, 0x00, 0x71, 0x86, 0x0d, 0xee, 0x60, 0x4c, 0xcf, 0xa2, 0x7b, 0x74, 0xbb, - 0x1a, 0x60, 0x53, 0x9a, 0x9d, 0x45, 0xd9, 0xb1, 0x9a, 0x41, 0xed, 0xaf, 0x5b, 0x70, 0x7f, 0x47, - 0xcf, 0x08, 0xf3, 0xc2, 0x5d, 0x1e, 0x85, 0xec, 0x71, 0xbf, 0xd4, 0xfb, 0xb8, 0x6f, 0xff, 0x43, - 0x0b, 0x66, 0x56, 0xf6, 0x5a, 0xc9, 0x41, 0xcd, 0x33, 0xef, 0x26, 0x5e, 0x84, 0xa1, 0x3d, 0xe2, - 0x7a, 0xed, 0x3d, 0x31, 0x72, 0x73, 0x52, 0xa4, 0xaf, 0x33, 0xe8, 0xd1, 0xe1, 0xdc, 0x78, 0x23, - 0x09, 0x23, 0x67, 0x9b, 0x70, 0x00, 0x16, 0xe4, 0x6c, 0x63, 0xf4, 0xde, 0x23, 0x97, 0xbd, 0x3d, - 0x4f, 0xde, 0x10, 0x76, 0x35, 0x79, 0xcd, 0xcb, 0x0e, 0x9d, 0x7f, 0xbd, 0xed, 0x04, 0x89, 0x97, - 0x1c, 0x88, 0x6b, 0x17, 0xc9, 0x04, 0xa7, 0xfc, 0xec, 0xef, 0x59, 0x30, 0x29, 0x65, 0xc9, 0xa2, - 0xeb, 0x46, 0x24, 0x8e, 0xd1, 0x2c, 0x94, 0xbc, 0x96, 0x68, 0x25, 0x88, 0x56, 0x96, 0xd6, 0xea, - 0xb8, 0xe4, 0xb5, 0x50, 0x1d, 0x2a, 0xfc, 0xa2, 0x31, 0x9d, 0x5c, 0x7d, 0x5d, 0x57, 0xb2, 0x16, - 0x6c, 0xc8, 0x92, 0x38, 0x65, 0x22, 0xb5, 0x62, 0xb6, 0x0f, 0x95, 0xcd, 0x3b, 0x9b, 0x8b, 0x02, - 0x8e, 0x15, 0x05, 0x3a, 0x07, 0x23, 0x41, 0xe8, 0xf2, 0x7b, 0x5f, 0xbe, 0xa6, 0xd9, 0x94, 0xbd, - 0x22, 0x60, 0x58, 0x61, 0xed, 0x9f, 0xb3, 0x60, 0x4c, 0x7e, 0x59, 0x9f, 0x0a, 0x3a, 0x5d, 0x5a, - 0xa9, 0x72, 0x9e, 0x2e, 0x2d, 0xaa, 0x60, 0x33, 0x8c, 0xa1, 0x57, 0x97, 0x8f, 0xa3, 0x57, 0xdb, - 0x5f, 0x2b, 0xc1, 0x84, 0x6c, 0x4e, 0xa3, 0xbd, 0x19, 0x93, 0x04, 0x6d, 0x40, 0xc5, 0xe1, 0x5d, - 0x4e, 0xe4, 0x8c, 0x7d, 0x34, 0xff, 0xc4, 0x65, 0x8c, 0x4f, 0xaa, 0xea, 0x2c, 0xca, 0xd2, 0x38, - 0x65, 0x84, 0x7c, 0x98, 0x0e, 0xc2, 0x84, 0x6d, 0x7b, 0x0a, 0xdf, 0xed, 0x5e, 0x20, 0xcb, 0xfd, - 0x01, 0xc1, 0x7d, 0xfa, 0x4a, 0x96, 0x0b, 0xee, 0x64, 0x8c, 0x56, 0xa4, 0x95, 0xa7, 0xcc, 0x6a, - 0x38, 0xdb, 0xad, 0x86, 0x62, 0x23, 0x8f, 0xfd, 0xdb, 0x16, 0x54, 0x24, 0xd9, 0x49, 0x5c, 0x01, - 0xad, 0xc3, 0x70, 0xcc, 0x06, 0x41, 0x76, 0x8d, 0xdd, 0xad, 0xe1, 0x7c, 0xbc, 0xd2, 0xdd, 0x9c, - 0xff, 0x8f, 0xb1, 0xe4, 0xc1, 0xcc, 0xd4, 0xaa, 0xf9, 0x1f, 0x12, 0x33, 0xb5, 0x6a, 0x4f, 0xc1, - 0x0e, 0xf3, 0x5f, 0x59, 0x9b, 0xb5, 0xb3, 0x3c, 0x55, 0x3a, 0x5b, 0x11, 0xd9, 0xf2, 0x6e, 0x65, - 0x95, 0xce, 0x3a, 0x83, 0x62, 0x81, 0x45, 0x6f, 0xc1, 0x58, 0x53, 0x5a, 0x77, 0x53, 0x31, 0xf0, - 0x58, 0x57, 0x5b, 0xb9, 0xba, 0x56, 0xe1, 0x3e, 0x61, 0xcb, 0x5a, 0x79, 0x6c, 0x70, 0x33, 0x2f, - 0xe6, 0xcb, 0xbd, 0x2e, 0xe6, 0x53, 0xbe, 0x85, 0x57, 0xcb, 0xf6, 0x2f, 0x59, 0x30, 0xc4, 0x6d, - 0x84, 0xfd, 0x19, 0x55, 0xb5, 0x6b, 0xa2, 0xb4, 0xef, 0xae, 0x53, 0xa0, 0xb8, 0x35, 0x42, 0xeb, - 0x50, 0x61, 0x3f, 0x98, 0xad, 0xa4, 0x5c, 0xec, 0x0c, 0xc7, 0x6b, 0xd5, 0x1b, 0x78, 0x5d, 0x16, - 0xc3, 0x29, 0x07, 0xfb, 0xab, 0x65, 0x2a, 0xaa, 0x52, 0x52, 0x63, 0x07, 0xb7, 0xee, 0xdd, 0x0e, - 0x5e, 0xba, 0x57, 0x3b, 0xf8, 0x36, 0x4c, 0x36, 0xb5, 0x3b, 0xa9, 0x74, 0x24, 0xcf, 0x75, 0x9d, - 0x24, 0xda, 0xf5, 0x15, 0xb7, 0x93, 0x2d, 0x9b, 0x4c, 0x70, 0x96, 0x2b, 0xfa, 0x1c, 0x8c, 0xf1, - 0x71, 0x16, 0xb5, 0x0c, 0xb0, 0x5a, 0x3e, 0x56, 0x3c, 0x5f, 0xf4, 0x2a, 0xd8, 0x4c, 0x6c, 0x68, - 0xc5, 0xb1, 0xc1, 0xcc, 0xfe, 0xf2, 0x20, 0x0c, 0xae, 0xec, 0x93, 0x20, 0x39, 0x01, 0x81, 0xd4, - 0x84, 0x09, 0x2f, 0xd8, 0x0f, 0xfd, 0x7d, 0xe2, 0x72, 0xfc, 0x71, 0x36, 0xd7, 0xfb, 0x04, 0xeb, - 0x89, 0x35, 0x83, 0x05, 0xce, 0xb0, 0xbc, 0x17, 0xa7, 0xf6, 0x0b, 0x30, 0xc4, 0xc7, 0x5e, 0x1c, - 0xd9, 0x73, 0x2d, 0xe0, 0xac, 0x13, 0xc5, 0x2a, 0x48, 0x2d, 0x0a, 0xdc, 0xe4, 0x2e, 0x8a, 0xa3, - 0x77, 0x60, 0x62, 0xcb, 0x8b, 0xe2, 0x84, 0x1e, 0xb7, 0xe3, 0xc4, 0xd9, 0x6b, 0xdd, 0xc1, 0x29, - 0x5d, 0xf5, 0xc3, 0xaa, 0xc1, 0x09, 0x67, 0x38, 0xa3, 0x6d, 0x18, 0xa7, 0x07, 0xc7, 0xb4, 0xaa, - 0xe1, 0x63, 0x57, 0xa5, 0xcc, 0x70, 0x97, 0x75, 0x46, 0xd8, 0xe4, 0x4b, 0x85, 0x49, 0x93, 0x1d, - 0x34, 0x47, 0x98, 0x46, 0xa1, 0x84, 0x09, 0x3f, 0x61, 0x72, 0x1c, 0x95, 0x49, 0xcc, 0x97, 0xa3, - 0x62, 0xca, 0xa4, 0xd4, 0x63, 0xc3, 0xfe, 0x06, 0xdd, 0x1d, 0x69, 0x1f, 0x9e, 0xc0, 0xd6, 0xf2, - 0xaa, 0xb9, 0xb5, 0x3c, 0x50, 0x38, 0x9e, 0x05, 0xdb, 0xca, 0xe7, 0x61, 0x54, 0x1b, 0x6e, 0xb4, - 0x00, 0x95, 0xa6, 0x74, 0x3c, 0x10, 0x52, 0x57, 0xa9, 0x2f, 0xca, 0x23, 0x01, 0xa7, 0x34, 0xb4, - 0x37, 0xa8, 0xb2, 0x97, 0x75, 0x6b, 0xa2, 0xaa, 0x20, 0x66, 0x18, 0xfb, 0x39, 0x80, 0x95, 0x5b, - 0xa4, 0xb9, 0xc8, 0x0f, 0x5e, 0xda, 0xfd, 0x96, 0x55, 0x7c, 0xbf, 0x65, 0xff, 0x07, 0x0b, 0x26, - 0x56, 0x97, 0x0d, 0x85, 0x7c, 0x1e, 0x80, 0x6b, 0xa1, 0x37, 0x6e, 0x5c, 0x91, 0x96, 0x61, 0x6e, - 0xdc, 0x53, 0x50, 0xac, 0x51, 0xa0, 0x07, 0xa0, 0xec, 0xb7, 0x03, 0xa1, 0x1c, 0x0e, 0xdf, 0x3e, - 0x9c, 0x2b, 0x5f, 0x6e, 0x07, 0x98, 0xc2, 0x34, 0x4f, 0xa2, 0x72, 0xdf, 0x9e, 0x44, 0x3d, 0x5d, - 0xb0, 0xd1, 0x1c, 0x0c, 0xde, 0xbc, 0xe9, 0xb9, 0x71, 0x75, 0x30, 0xb5, 0x5a, 0xdf, 0xb8, 0xb1, - 0x56, 0x8b, 0x31, 0x87, 0xdb, 0x7f, 0xa1, 0x0c, 0x53, 0xab, 0x3e, 0xb9, 0x65, 0x7c, 0xd6, 0x63, - 0x30, 0xe4, 0x46, 0xde, 0x3e, 0x89, 0xb2, 0xbb, 0x78, 0x8d, 0x41, 0xb1, 0xc0, 0xf6, 0xed, 0xfd, - 0x74, 0xad, 0x73, 0x3f, 0xbe, 0xdb, 0xfe, 0x5e, 0xbd, 0xbb, 0xe2, 0x2d, 0x18, 0xe6, 0xd7, 0xa4, - 0xbc, 0x33, 0x46, 0xcf, 0x3f, 0x9b, 0xd7, 0x84, 0x6c, 0x5f, 0xcc, 0x0b, 0xc3, 0x07, 0xf7, 0x19, - 0x51, 0x42, 0x4c, 0x40, 0xb1, 0x64, 0x39, 0xfb, 0x49, 0x18, 0xd3, 0x29, 0x8f, 0xe5, 0x3c, 0xf2, - 0x17, 0x2d, 0x38, 0xb5, 0xea, 0x87, 0xcd, 0xdd, 0x8c, 0x2b, 0xda, 0x0b, 0x30, 0x4a, 0xd7, 0x53, - 0x6c, 0xb8, 0xb5, 0x1a, 0x8e, 0xce, 0x02, 0x85, 0x75, 0x3a, 0xad, 0xd8, 0xb5, 0x6b, 0x6b, 0xb5, - 0x3c, 0xff, 0x68, 0x81, 0xc2, 0x3a, 0x9d, 0xfd, 0x5d, 0x0b, 0x1e, 0xbe, 0xb0, 0xbc, 0x92, 0x7a, - 0x63, 0x76, 0xb8, 0x68, 0x53, 0xe5, 0xce, 0xd5, 0x9a, 0x92, 0x2a, 0x77, 0x35, 0xd6, 0x0a, 0x81, - 0xfd, 0xb0, 0x84, 0x1f, 0xfc, 0x8a, 0x05, 0xa7, 0x2e, 0x78, 0x09, 0x26, 0xad, 0x30, 0xeb, 0x2c, - 0x1c, 0x91, 0x56, 0x18, 0x7b, 0x49, 0x18, 0x1d, 0x64, 0x9d, 0x85, 0xb1, 0xc2, 0x60, 0x8d, 0x8a, - 0xd7, 0xbc, 0xef, 0xc5, 0xb4, 0xa5, 0x25, 0xf3, 0x84, 0x89, 0x05, 0x1c, 0x2b, 0x0a, 0xfa, 0x61, - 0xae, 0x17, 0x31, 0x0d, 0xe1, 0x40, 0x2c, 0x67, 0xf5, 0x61, 0x35, 0x89, 0xc0, 0x29, 0x8d, 0xfd, - 0x75, 0x0b, 0x4e, 0x5f, 0xf0, 0xdb, 0x71, 0x42, 0xa2, 0xad, 0xd8, 0x68, 0xec, 0x73, 0x50, 0x21, - 0x52, 0x0b, 0x17, 0x6d, 0x55, 0xfb, 0x86, 0x52, 0xcf, 0xb9, 0xa7, 0xb2, 0xa2, 0xeb, 0xc3, 0xaf, - 0xf3, 0x78, 0xfe, 0x88, 0xdf, 0x2c, 0xc1, 0xf8, 0xc5, 0x8d, 0x8d, 0xfa, 0x05, 0x92, 0x08, 0x91, - 0xd9, 0xdb, 0x82, 0x84, 0xb5, 0x83, 0x70, 0x37, 0x5d, 0xa7, 0x9d, 0x78, 0xfe, 0x3c, 0x0f, 0x8d, - 0x99, 0x5f, 0x0b, 0x92, 0xab, 0x51, 0x23, 0x89, 0xbc, 0x60, 0x3b, 0xf7, 0xe8, 0x2c, 0x05, 0x7b, - 0xb9, 0x48, 0xb0, 0xa3, 0xe7, 0x60, 0x88, 0xc5, 0xe6, 0x48, 0xad, 0xe3, 0x41, 0xa5, 0x2a, 0x30, - 0xe8, 0xd1, 0xe1, 0x5c, 0xe5, 0x1a, 0x5e, 0xe3, 0x7f, 0xb0, 0x20, 0x45, 0xd7, 0x60, 0x74, 0x27, - 0x49, 0x5a, 0x17, 0x89, 0xe3, 0x92, 0x48, 0x4a, 0x87, 0x33, 0x79, 0xd2, 0x81, 0x76, 0x02, 0x27, - 0x4b, 0x17, 0x54, 0x0a, 0x8b, 0xb1, 0xce, 0xc7, 0x6e, 0x00, 0xa4, 0xb8, 0xbb, 0x74, 0x6c, 0xb0, - 0x7f, 0x68, 0xc1, 0xf0, 0x45, 0x27, 0x70, 0x7d, 0x12, 0xa1, 0x57, 0x60, 0x80, 0xdc, 0x22, 0x4d, - 0xb1, 0x83, 0xe7, 0x36, 0x38, 0xdd, 0xe5, 0xb8, 0x11, 0x8c, 0xfe, 0xc7, 0xac, 0x14, 0xba, 0x08, - 0xc3, 0xb4, 0xb5, 0x17, 0x94, 0xcf, 0xf8, 0x23, 0x45, 0x5f, 0xac, 0x86, 0x9d, 0x6f, 0x8c, 0x02, - 0x84, 0x65, 0x71, 0x66, 0xd0, 0x69, 0xb6, 0x1a, 0x54, 0x80, 0x25, 0xdd, 0x8e, 0x5b, 0x1b, 0xcb, - 0x75, 0x4e, 0x24, 0xb8, 0x71, 0x83, 0x8e, 0x04, 0xe2, 0x94, 0x89, 0xbd, 0x01, 0x15, 0x3a, 0xa8, - 0x8b, 0xbe, 0xe7, 0x74, 0xb7, 0x25, 0x3d, 0x05, 0x15, 0x69, 0xd7, 0x89, 0x85, 0xdb, 0x39, 0xe3, - 0x2a, 0xcd, 0x3e, 0x31, 0x4e, 0xf1, 0xf6, 0x16, 0xcc, 0xb0, 0x4b, 0x52, 0x27, 0xd9, 0x31, 0xd6, - 0x58, 0xef, 0xc9, 0xfc, 0xb4, 0xd0, 0xaf, 0xf8, 0xc8, 0x54, 0x35, 0x3f, 0xd9, 0x31, 0xc9, 0x51, - 0xd3, 0xb5, 0xfe, 0xf3, 0x00, 0x4c, 0xaf, 0x35, 0x96, 0x1b, 0xa6, 0x61, 0xf1, 0x25, 0x18, 0xe3, - 0x9a, 0x00, 0x9d, 0xd0, 0x8e, 0x2f, 0x6a, 0x53, 0x17, 0x07, 0x1b, 0x1a, 0x0e, 0x1b, 0x94, 0xe8, - 0x61, 0x28, 0x7b, 0xef, 0x06, 0x59, 0x57, 0xb8, 0xb5, 0xd7, 0xaf, 0x60, 0x0a, 0xa7, 0x68, 0xaa, - 0x54, 0x70, 0x01, 0xaa, 0xd0, 0x4a, 0xb1, 0x78, 0x15, 0x26, 0xbc, 0xb8, 0x19, 0x7b, 0x6b, 0x01, - 0x95, 0x2e, 0x69, 0xcc, 0x45, 0xaa, 0xf1, 0xd3, 0xa6, 0x2a, 0x2c, 0xce, 0x50, 0x6b, 0xd2, 0x7c, - 0xb0, 0x6f, 0xc5, 0xa4, 0xa7, 0xf7, 0x35, 0xd5, 0xb9, 0x5a, 0xec, 0xeb, 0x62, 0xe6, 0x96, 0x23, - 0x74, 0x2e, 0xfe, 0xc1, 0x31, 0x96, 0x38, 0x74, 0x01, 0xa6, 0x9b, 0x3b, 0x4e, 0x6b, 0xb1, 0x9d, - 0xec, 0xd4, 0xbc, 0xb8, 0x19, 0xee, 0x93, 0xe8, 0x80, 0x69, 0xc2, 0x23, 0xa9, 0x91, 0x49, 0x21, - 0x96, 0x2f, 0x2e, 0xd6, 0x29, 0x25, 0xee, 0x2c, 0x63, 0xaa, 0x20, 0x70, 0xd7, 0x54, 0x90, 0x45, - 0x98, 0x94, 0x75, 0x35, 0x48, 0xcc, 0xb6, 0x87, 0x51, 0xd6, 0x3a, 0x15, 0x12, 0x25, 0xc0, 0xaa, - 0x6d, 0x59, 0x7a, 0xf4, 0x22, 0x8c, 0x7b, 0x81, 0x97, 0x78, 0x4e, 0x12, 0x46, 0x6c, 0x73, 0x1d, - 0xe3, 0x1b, 0x06, 0x95, 0xf0, 0x6b, 0x3a, 0x02, 0x9b, 0x74, 0xf6, 0x3b, 0x50, 0x51, 0xbe, 0x66, - 0xd2, 0x5d, 0xd2, 0x2a, 0x70, 0x97, 0xec, 0xbd, 0x23, 0x48, 0x8b, 0x79, 0x39, 0xd7, 0x62, 0xfe, - 0xb7, 0x2c, 0x48, 0x5d, 0x6e, 0xd0, 0x45, 0xa8, 0xb4, 0x42, 0x76, 0x6b, 0x16, 0xc9, 0xab, 0xe8, - 0x07, 0x73, 0x85, 0x07, 0x17, 0x54, 0xbc, 0xff, 0xea, 0xb2, 0x04, 0x4e, 0x0b, 0xa3, 0x25, 0x18, - 0x6e, 0x45, 0xa4, 0x91, 0xb0, 0xa0, 0x91, 0x9e, 0x7c, 0xf8, 0x1c, 0xe1, 0xf4, 0x58, 0x16, 0xb4, - 0x7f, 0xdd, 0x02, 0xe0, 0x46, 0x69, 0x27, 0xd8, 0x26, 0x27, 0x70, 0xd0, 0xae, 0xc1, 0x40, 0xdc, - 0x22, 0xcd, 0x6e, 0xf7, 0x99, 0x69, 0x7b, 0x1a, 0x2d, 0xd2, 0x4c, 0x3b, 0x9c, 0xfe, 0xc3, 0xac, - 0xb4, 0xfd, 0xb3, 0x00, 0x13, 0x29, 0x19, 0x3d, 0x00, 0xa1, 0x67, 0x0c, 0x97, 0xfc, 0x07, 0x32, - 0x2e, 0xf9, 0x15, 0x46, 0xad, 0x79, 0xe1, 0xbf, 0x03, 0xe5, 0x3d, 0xe7, 0x96, 0x38, 0x65, 0x3d, - 0xd5, 0xbd, 0x19, 0x94, 0xff, 0xfc, 0xba, 0x73, 0x8b, 0xeb, 0xb1, 0x4f, 0xc9, 0x09, 0xb2, 0xee, - 0xdc, 0x3a, 0xe2, 0xb7, 0x96, 0x4c, 0x48, 0xd1, 0xc3, 0xdc, 0x17, 0xff, 0x53, 0xfa, 0x9f, 0x4d, - 0x3b, 0x5a, 0x09, 0xab, 0xcb, 0x0b, 0x84, 0x89, 0xb6, 0xaf, 0xba, 0xbc, 0x20, 0x5b, 0x97, 0x17, - 0xf4, 0x51, 0x97, 0x17, 0xa0, 0xf7, 0x60, 0x58, 0x5c, 0x87, 0x30, 0x5f, 0xc2, 0xd1, 0xf3, 0x0b, - 0x7d, 0xd4, 0x27, 0x6e, 0x53, 0x78, 0x9d, 0x0b, 0x52, 0x4f, 0x17, 0xd0, 0x9e, 0xf5, 0xca, 0x0a, - 0xd1, 0xdf, 0xb0, 0x60, 0x42, 0xfc, 0xc6, 0xe4, 0xdd, 0x36, 0x89, 0x13, 0xa1, 0x0f, 0x7c, 0xa2, - 0xff, 0x36, 0x88, 0x82, 0xbc, 0x29, 0x9f, 0x90, 0x62, 0xd6, 0x44, 0xf6, 0x6c, 0x51, 0xa6, 0x15, - 0xe8, 0x1f, 0x5b, 0x30, 0xb3, 0xe7, 0xdc, 0xe2, 0x35, 0x72, 0x18, 0x76, 0x12, 0x2f, 0x14, 0xbe, - 0x91, 0xaf, 0xf4, 0x37, 0xfc, 0x1d, 0xc5, 0x79, 0x23, 0xa5, 0x1b, 0xd5, 0x4c, 0x1e, 0x49, 0xcf, - 0xa6, 0xe6, 0xb6, 0x6b, 0x76, 0x0b, 0x46, 0xe4, 0x7c, 0xcb, 0x39, 0x0d, 0xd5, 0x74, 0x65, 0xe7, - 0xd8, 0xb7, 0x51, 0xda, 0xe9, 0x89, 0xd5, 0x23, 0xe6, 0xda, 0x3d, 0xad, 0xe7, 0x1d, 0x18, 0xd3, - 0xe7, 0xd8, 0x3d, 0xad, 0xeb, 0x5d, 0x38, 0x95, 0x33, 0x97, 0xee, 0x69, 0x95, 0x37, 0xe1, 0x81, - 0xc2, 0xf9, 0x71, 0x2f, 0x2b, 0xb6, 0xbf, 0x69, 0xe9, 0x72, 0xf0, 0x04, 0xcc, 0x53, 0xcb, 0xa6, - 0x79, 0xea, 0x4c, 0xf7, 0x95, 0x53, 0x60, 0xa3, 0x7a, 0x4b, 0x6f, 0x34, 0x95, 0xea, 0xe8, 0x35, - 0x18, 0xf2, 0x29, 0x44, 0xde, 0xc3, 0xd9, 0xbd, 0x57, 0x64, 0xaa, 0x4b, 0x31, 0x78, 0x8c, 0x05, - 0x07, 0xfb, 0xb7, 0x2c, 0x18, 0x38, 0x81, 0x9e, 0xc0, 0x66, 0x4f, 0x3c, 0x53, 0xc8, 0x5a, 0xe4, - 0x3d, 0x98, 0xc7, 0xce, 0xcd, 0x15, 0x99, 0xdb, 0xa1, 0xa0, 0x63, 0xfe, 0x4f, 0x09, 0x46, 0x69, - 0x55, 0xd2, 0x61, 0xe4, 0x65, 0x18, 0xf7, 0x9d, 0x4d, 0xe2, 0x4b, 0x93, 0x79, 0xf6, 0x10, 0x7b, - 0x59, 0x47, 0x62, 0x93, 0x96, 0x16, 0xde, 0xd2, 0x6f, 0x0f, 0x84, 0xfe, 0xa2, 0x0a, 0x1b, 0x57, - 0x0b, 0xd8, 0xa4, 0xa5, 0xe7, 0xa9, 0x9b, 0x4e, 0xd2, 0xdc, 0x11, 0x07, 0x5c, 0xd5, 0xdc, 0x1b, - 0x14, 0x88, 0x39, 0x8e, 0x2a, 0x70, 0x72, 0x76, 0x5e, 0x27, 0x11, 0x53, 0xe0, 0xb8, 0x7a, 0xac, - 0x14, 0x38, 0x6c, 0xa2, 0x71, 0x96, 0x3e, 0x27, 0x36, 0x6f, 0x90, 0xb9, 0xc3, 0xf4, 0x11, 0x9b, - 0x87, 0xea, 0x30, 0xe3, 0x05, 0x4d, 0xbf, 0xed, 0x92, 0x6b, 0x01, 0xd7, 0xee, 0x7c, 0xef, 0x3d, - 0xe2, 0x0a, 0x05, 0x5a, 0x79, 0x2e, 0xad, 0xe5, 0xd0, 0xe0, 0xdc, 0x92, 0xf6, 0xff, 0x07, 0xa7, - 0x2e, 0x87, 0x8e, 0xbb, 0xe4, 0xf8, 0x4e, 0xd0, 0x24, 0xd1, 0x5a, 0xb0, 0xdd, 0xf3, 0x42, 0x5e, - 0xbf, 0x3e, 0x2f, 0xf5, 0xba, 0x3e, 0xb7, 0x77, 0x00, 0xe9, 0x15, 0x08, 0x37, 0x30, 0x0c, 0xc3, - 0x1e, 0xaf, 0x4a, 0x4c, 0xff, 0xc7, 0xf3, 0xb5, 0xeb, 0x8e, 0x96, 0x69, 0x0e, 0x4e, 0x1c, 0x80, - 0x25, 0x23, 0xfb, 0x25, 0xc8, 0x8d, 0xcd, 0xe8, 0x7d, 0x94, 0xb6, 0x5f, 0x80, 0x69, 0x56, 0xf2, - 0x78, 0xc7, 0x3c, 0xfb, 0xaf, 0x58, 0x30, 0x79, 0x25, 0x13, 0x4d, 0xfb, 0x18, 0x0c, 0xf1, 0x0c, - 0x27, 0x59, 0xa3, 0x57, 0x83, 0x41, 0xb1, 0xc0, 0xde, 0x75, 0x9b, 0xcb, 0x8f, 0x2c, 0xa8, 0xa8, - 0xd0, 0xf7, 0x13, 0x50, 0x6a, 0x97, 0x0d, 0xa5, 0x36, 0xd7, 0x16, 0xa0, 0x9a, 0x53, 0xa4, 0xd3, - 0xa2, 0x4b, 0x2a, 0x2e, 0xb4, 0x8b, 0x19, 0x20, 0x65, 0xc3, 0xa3, 0x08, 0x27, 0xcc, 0xe0, 0x51, - 0x19, 0x29, 0xca, 0x6e, 0xc4, 0x15, 0xed, 0x87, 0xe4, 0x46, 0x5c, 0xb5, 0xa7, 0x40, 0xfa, 0xd5, - 0xb5, 0x26, 0xb3, 0x5d, 0xe1, 0xd3, 0xcc, 0x6b, 0x94, 0xad, 0x4d, 0x15, 0x8e, 0x3d, 0x27, 0xbc, - 0x40, 0x05, 0xf4, 0x88, 0x09, 0x32, 0xf1, 0x8f, 0xa7, 0x29, 0x48, 0x8b, 0xd8, 0x17, 0x61, 0x32, - 0xd3, 0x61, 0xe8, 0x05, 0x18, 0x6c, 0xed, 0x38, 0x31, 0xc9, 0x78, 0x01, 0x0d, 0xd6, 0x29, 0xf0, - 0xe8, 0x70, 0x6e, 0x42, 0x15, 0x60, 0x10, 0xcc, 0xa9, 0xed, 0xff, 0x61, 0xc1, 0xc0, 0x95, 0xd0, - 0x3d, 0x89, 0xc9, 0xf4, 0xaa, 0x31, 0x99, 0x1e, 0x2a, 0x4a, 0xf2, 0x52, 0x38, 0x8f, 0x56, 0x33, - 0xf3, 0xe8, 0x4c, 0x21, 0x87, 0xee, 0x53, 0x68, 0x0f, 0x46, 0x59, 0xea, 0x18, 0xe1, 0x95, 0xf4, - 0x9c, 0x71, 0xbe, 0x9a, 0xcb, 0x9c, 0xaf, 0x26, 0x35, 0x52, 0xed, 0x94, 0xf5, 0x04, 0x0c, 0x0b, - 0xcf, 0x98, 0xac, 0x7f, 0xac, 0xa0, 0xc5, 0x12, 0x6f, 0xff, 0x52, 0x19, 0x8c, 0x54, 0x35, 0xe8, - 0xb7, 0x2d, 0x98, 0x8f, 0x78, 0x44, 0x90, 0x5b, 0x6b, 0x47, 0x5e, 0xb0, 0xdd, 0x68, 0xee, 0x10, - 0xb7, 0xed, 0x7b, 0xc1, 0xf6, 0xda, 0x76, 0x10, 0x2a, 0xf0, 0xca, 0x2d, 0xd2, 0x6c, 0x33, 0x3b, - 0x78, 0x8f, 0xbc, 0x38, 0xea, 0xe6, 0xf9, 0xfc, 0xed, 0xc3, 0xb9, 0x79, 0x7c, 0x2c, 0xde, 0xf8, - 0x98, 0x6d, 0x41, 0xdf, 0xb5, 0x60, 0x81, 0x67, 0x70, 0xe9, 0xbf, 0xfd, 0x5d, 0x4e, 0xa3, 0x75, - 0xc9, 0x2a, 0x65, 0xb2, 0x41, 0xa2, 0xbd, 0xa5, 0x17, 0x45, 0x87, 0x2e, 0xd4, 0x8f, 0x57, 0x17, - 0x3e, 0x6e, 0xe3, 0xec, 0x7f, 0x55, 0x86, 0x71, 0xda, 0x8b, 0x69, 0x14, 0xfc, 0x0b, 0xc6, 0x94, - 0x78, 0x24, 0x33, 0x25, 0xa6, 0x0d, 0xe2, 0xbb, 0x13, 0x00, 0x1f, 0xc3, 0xb4, 0xef, 0xc4, 0xc9, - 0x45, 0xe2, 0x44, 0xc9, 0x26, 0x71, 0xd8, 0x55, 0xaf, 0x98, 0xe6, 0xc7, 0xb9, 0x3d, 0x56, 0xe6, - 0xaf, 0xcb, 0x59, 0x66, 0xb8, 0x93, 0x3f, 0xda, 0x07, 0xc4, 0xae, 0x95, 0x23, 0x27, 0x88, 0xf9, - 0xb7, 0x78, 0xc2, 0x46, 0x7e, 0xbc, 0x5a, 0x67, 0x45, 0xad, 0xe8, 0x72, 0x07, 0x37, 0x9c, 0x53, - 0x83, 0xe6, 0x2e, 0x30, 0xd8, 0xaf, 0xbb, 0xc0, 0x50, 0x0f, 0x27, 0xf4, 0x3d, 0x98, 0x12, 0xa3, - 0xb2, 0xe5, 0x6d, 0x8b, 0x4d, 0xfa, 0x8d, 0x8c, 0x3b, 0x91, 0xd5, 0xbf, 0xe3, 0x43, 0x0f, 0x5f, - 0x22, 0xfb, 0x67, 0xe0, 0x14, 0xad, 0xce, 0x74, 0x99, 0x8e, 0x11, 0x81, 0xc9, 0xdd, 0xf6, 0x26, - 0xf1, 0x49, 0x22, 0x61, 0xa2, 0xd2, 0x5c, 0xb5, 0xdf, 0x2c, 0x9d, 0xea, 0x96, 0x97, 0x4c, 0x16, - 0x38, 0xcb, 0xd3, 0xfe, 0x65, 0x0b, 0x98, 0x63, 0xe2, 0x09, 0x6c, 0x7f, 0x9f, 0x32, 0xb7, 0xbf, - 0x6a, 0x91, 0x04, 0x2a, 0xd8, 0xf9, 0x9e, 0xe7, 0xc3, 0x52, 0x8f, 0xc2, 0x5b, 0x07, 0x52, 0xf7, - 0xef, 0xad, 0x71, 0xfd, 0x6f, 0x8b, 0x2f, 0x48, 0x15, 0x20, 0x89, 0xbe, 0x00, 0x23, 0x4d, 0xa7, - 0xe5, 0x34, 0x79, 0x8e, 0xb0, 0x42, 0xeb, 0x8f, 0x51, 0x68, 0x7e, 0x59, 0x94, 0xe0, 0xd6, 0x8c, - 0x8f, 0xcb, 0xaf, 0x94, 0xe0, 0x9e, 0x16, 0x0c, 0x55, 0xe5, 0xec, 0x2e, 0x8c, 0x1b, 0xcc, 0xee, - 0xe9, 0xd1, 0xf7, 0x0b, 0x7c, 0xbb, 0x50, 0x27, 0x96, 0x3d, 0x98, 0x0e, 0xb4, 0xff, 0x54, 0x38, - 0x4a, 0x75, 0xfa, 0xa3, 0xbd, 0x36, 0x04, 0x26, 0x49, 0x35, 0xc7, 0xcb, 0x0c, 0x1b, 0xdc, 0xc9, - 0xd9, 0xfe, 0x3b, 0x16, 0xdc, 0xaf, 0x13, 0x6a, 0xb1, 0xab, 0xbd, 0xec, 0xc9, 0x35, 0x18, 0x09, - 0x5b, 0x24, 0x72, 0xd2, 0x33, 0xd9, 0x39, 0xd9, 0xe9, 0x57, 0x05, 0xfc, 0xe8, 0x70, 0x6e, 0x46, - 0xe7, 0x2e, 0xe1, 0x58, 0x95, 0x44, 0x36, 0x0c, 0xb1, 0xce, 0x88, 0x45, 0x5c, 0x31, 0xcb, 0xa3, - 0xc5, 0xae, 0xbb, 0x62, 0x2c, 0x30, 0xf6, 0xcf, 0x5a, 0x7c, 0x62, 0xe9, 0x4d, 0x47, 0xef, 0xc2, - 0xd4, 0x1e, 0x3d, 0xbe, 0xad, 0xdc, 0x6a, 0x45, 0xdc, 0x8c, 0x2e, 0xfb, 0xe9, 0xa9, 0x5e, 0xfd, - 0xa4, 0x7d, 0xe4, 0x52, 0x55, 0xb4, 0x79, 0x6a, 0x3d, 0xc3, 0x0c, 0x77, 0xb0, 0xb7, 0xff, 0xb4, - 0xc4, 0x57, 0x22, 0xd3, 0xea, 0x9e, 0x80, 0xe1, 0x56, 0xe8, 0x2e, 0xaf, 0xd5, 0xb0, 0xe8, 0x21, - 0x25, 0xae, 0xea, 0x1c, 0x8c, 0x25, 0x1e, 0x9d, 0x07, 0x20, 0xb7, 0x12, 0x12, 0x05, 0x8e, 0xaf, - 0x2e, 0xe3, 0x95, 0xf2, 0xb4, 0xa2, 0x30, 0x58, 0xa3, 0xa2, 0x65, 0x5a, 0x51, 0xb8, 0xef, 0xb9, - 0x2c, 0xb0, 0xa3, 0x6c, 0x96, 0xa9, 0x2b, 0x0c, 0xd6, 0xa8, 0xe8, 0x51, 0xb9, 0x1d, 0xc4, 0x7c, - 0x03, 0x74, 0x36, 0x45, 0x2a, 0x9e, 0x91, 0xf4, 0xa8, 0x7c, 0x4d, 0x47, 0x62, 0x93, 0x16, 0x2d, - 0xc2, 0x50, 0xe2, 0xb0, 0x2b, 0xe6, 0xc1, 0x62, 0x97, 0x9d, 0x0d, 0x4a, 0xa1, 0x27, 0x8d, 0xa2, - 0x05, 0xb0, 0x28, 0x88, 0xde, 0x94, 0x22, 0x98, 0x8b, 0x64, 0xe1, 0x7a, 0x55, 0x38, 0x6d, 0x75, - 0xf1, 0xad, 0xcb, 0x60, 0xe1, 0xd2, 0x65, 0xf0, 0xb2, 0xbf, 0x54, 0x01, 0x48, 0xb5, 0x3d, 0xf4, - 0x5e, 0x87, 0x88, 0x78, 0xba, 0xbb, 0x7e, 0x78, 0xf7, 0xe4, 0x03, 0xfa, 0xb2, 0x05, 0xa3, 0x8e, - 0xef, 0x87, 0x4d, 0x27, 0x61, 0xbd, 0x5c, 0xea, 0x2e, 0xa2, 0x44, 0xfd, 0x8b, 0x69, 0x09, 0xde, - 0x84, 0xe7, 0xe4, 0xed, 0xb1, 0x86, 0xe9, 0xd9, 0x0a, 0xbd, 0x62, 0xf4, 0x71, 0x79, 0x08, 0xe0, - 0xd3, 0x63, 0x36, 0x7b, 0x08, 0xa8, 0x30, 0x69, 0xac, 0xe9, 0xff, 0xe8, 0x9a, 0x91, 0xb3, 0x66, - 0xa0, 0x38, 0x3c, 0xd7, 0x50, 0x7a, 0x7a, 0xa5, 0xab, 0x41, 0x75, 0xdd, 0x05, 0x7d, 0xb0, 0x38, - 0x86, 0x5d, 0xd3, 0xae, 0x7b, 0xb8, 0x9f, 0xbf, 0x03, 0x93, 0xae, 0xb9, 0xdd, 0x8a, 0xd9, 0xf4, - 0x78, 0x11, 0xdf, 0xcc, 0xee, 0x9c, 0x6e, 0xb0, 0x19, 0x04, 0xce, 0x32, 0x46, 0x75, 0x1e, 0x0c, - 0xb0, 0x16, 0x6c, 0x85, 0xc2, 0x85, 0xcf, 0x2e, 0x1c, 0xcb, 0x83, 0x38, 0x21, 0x7b, 0x94, 0x32, - 0xdd, 0x47, 0xaf, 0x88, 0xb2, 0x58, 0x71, 0x41, 0xaf, 0xc1, 0x10, 0x8b, 0xd0, 0x8a, 0xab, 0x23, - 0xc5, 0x76, 0x40, 0x33, 0xb8, 0x38, 0x5d, 0x54, 0xec, 0x6f, 0x8c, 0x05, 0x07, 0x74, 0x51, 0xa6, - 0x08, 0x88, 0xd7, 0x82, 0x6b, 0x31, 0x61, 0x29, 0x02, 0x2a, 0x4b, 0x1f, 0x4d, 0xa3, 0xff, 0x39, - 0x3c, 0x37, 0x3d, 0xa4, 0x51, 0x92, 0xea, 0x2b, 0xe2, 0xbf, 0xcc, 0x3a, 0x59, 0x85, 0xe2, 0xe6, - 0x99, 0x99, 0x29, 0xd3, 0xee, 0xbc, 0x6e, 0xb2, 0xc0, 0x59, 0x9e, 0x27, 0xba, 0x7d, 0xce, 0x06, - 0x30, 0x95, 0x5d, 0x58, 0xf7, 0x74, 0xbb, 0xfe, 0xe1, 0x00, 0x4c, 0x98, 0x13, 0x01, 0x2d, 0x40, - 0x45, 0x30, 0x51, 0xe9, 0xc2, 0xd4, 0xdc, 0x5e, 0x97, 0x08, 0x9c, 0xd2, 0xb0, 0x74, 0x69, 0xac, - 0xb8, 0xe6, 0x9b, 0x95, 0xa6, 0x4b, 0x53, 0x18, 0xac, 0x51, 0x51, 0x25, 0x7a, 0x33, 0x0c, 0x13, - 0xb5, 0x15, 0xa8, 0xd9, 0xb2, 0xc4, 0xa0, 0x58, 0x60, 0xe9, 0x16, 0xb0, 0x4b, 0xa2, 0x80, 0xf8, - 0xa6, 0x25, 0x53, 0x6d, 0x01, 0x97, 0x74, 0x24, 0x36, 0x69, 0xe9, 0x96, 0x16, 0xc6, 0x6c, 0xfa, - 0x09, 0x55, 0x3d, 0xf5, 0x75, 0x6b, 0xf0, 0x08, 0x45, 0x89, 0x47, 0x6f, 0xc0, 0xfd, 0x2a, 0xa0, - 0x10, 0x73, 0xcb, 0xb0, 0xac, 0x71, 0xc8, 0x38, 0x59, 0xdf, 0xbf, 0x9c, 0x4f, 0x86, 0x8b, 0xca, - 0xa3, 0x57, 0x61, 0x42, 0xa8, 0xc0, 0x92, 0xe3, 0xb0, 0xe9, 0xac, 0x70, 0xc9, 0xc0, 0xe2, 0x0c, - 0x35, 0xaa, 0xc1, 0x14, 0x85, 0x30, 0x2d, 0x54, 0x72, 0xe0, 0x81, 0x91, 0x6a, 0xaf, 0xbf, 0x94, - 0xc1, 0xe3, 0x8e, 0x12, 0x68, 0x11, 0x26, 0xb9, 0x8e, 0x42, 0xcf, 0x94, 0x6c, 0x1c, 0x84, 0x67, - 0xad, 0x5a, 0x08, 0x57, 0x4d, 0x34, 0xce, 0xd2, 0xa3, 0x97, 0x60, 0xcc, 0x89, 0x9a, 0x3b, 0x5e, - 0x42, 0x9a, 0x49, 0x3b, 0xe2, 0x09, 0x38, 0x34, 0x6f, 0x8f, 0x45, 0x0d, 0x87, 0x0d, 0x4a, 0xfb, - 0x3d, 0x38, 0x95, 0xe3, 0x94, 0x4f, 0x27, 0x8e, 0xd3, 0xf2, 0xe4, 0x37, 0x65, 0xbc, 0xd6, 0x16, - 0xeb, 0x6b, 0xf2, 0x6b, 0x34, 0x2a, 0x3a, 0x3b, 0x99, 0x49, 0x5c, 0x4b, 0x0d, 0xab, 0x66, 0xe7, - 0xaa, 0x44, 0xe0, 0x94, 0xc6, 0xfe, 0x3d, 0x00, 0xcd, 0xa0, 0xd3, 0x87, 0xcf, 0xd2, 0x4b, 0x30, - 0x26, 0xf3, 0x19, 0x6b, 0x79, 0x34, 0xd5, 0x67, 0x5e, 0xd0, 0x70, 0xd8, 0xa0, 0xa4, 0x6d, 0x0b, - 0x54, 0x16, 0xd0, 0x8c, 0x8f, 0x5c, 0x9a, 0x03, 0x34, 0xa5, 0x41, 0x4f, 0xc3, 0x48, 0x4c, 0xfc, - 0xad, 0xcb, 0x5e, 0xb0, 0x2b, 0x26, 0xb6, 0x92, 0xc2, 0x0d, 0x01, 0xc7, 0x8a, 0x02, 0x2d, 0x41, - 0xb9, 0xed, 0xb9, 0x62, 0x2a, 0xcb, 0x0d, 0xbf, 0x7c, 0x6d, 0xad, 0x76, 0x74, 0x38, 0xf7, 0x48, - 0x51, 0x9a, 0x66, 0x7a, 0xb4, 0x8f, 0xe7, 0xe9, 0xf2, 0xa3, 0x85, 0xf3, 0xee, 0x06, 0x86, 0x8e, - 0x79, 0x37, 0x70, 0x1e, 0x40, 0x7c, 0xb5, 0x9c, 0xcb, 0xe5, 0x74, 0xd4, 0x2e, 0x28, 0x0c, 0xd6, - 0xa8, 0x50, 0x0c, 0xd3, 0xcd, 0x88, 0x38, 0xf2, 0x0c, 0xcd, 0xdd, 0xcb, 0x47, 0xee, 0xdc, 0x40, - 0xb0, 0x9c, 0x65, 0x86, 0x3b, 0xf9, 0xa3, 0x10, 0xa6, 0x5d, 0x11, 0xbf, 0x9a, 0x56, 0x5a, 0x39, - 0xbe, 0x4f, 0x3b, 0x73, 0xc8, 0xc9, 0x32, 0xc2, 0x9d, 0xbc, 0xd1, 0xdb, 0x30, 0x2b, 0x81, 0x9d, - 0x21, 0xc3, 0x6c, 0xb9, 0x94, 0x97, 0xce, 0xdc, 0x3e, 0x9c, 0x9b, 0xad, 0x15, 0x52, 0xe1, 0x2e, - 0x1c, 0x10, 0x86, 0x21, 0x76, 0x97, 0x14, 0x57, 0x47, 0xd9, 0x3e, 0xf7, 0x64, 0xb1, 0x31, 0x80, - 0xce, 0xf5, 0x79, 0x76, 0x0f, 0x25, 0xdc, 0x7c, 0xd3, 0x6b, 0x39, 0x06, 0xc4, 0x82, 0x13, 0xda, - 0x82, 0x51, 0x27, 0x08, 0xc2, 0xc4, 0xe1, 0x2a, 0xd4, 0x58, 0xb1, 0xee, 0xa7, 0x31, 0x5e, 0x4c, - 0x4b, 0x70, 0xee, 0xca, 0x73, 0x50, 0xc3, 0x60, 0x9d, 0x31, 0xba, 0x09, 0x93, 0xe1, 0x4d, 0x2a, - 0x1c, 0xa5, 0x95, 0x22, 0xae, 0x8e, 0xb3, 0xba, 0x9e, 0xef, 0xd3, 0x4e, 0x6b, 0x14, 0xd6, 0xa4, - 0x96, 0xc9, 0x14, 0x67, 0x6b, 0x41, 0xf3, 0x86, 0xb5, 0x7a, 0x22, 0xf5, 0x67, 0x4f, 0xad, 0xd5, - 0xba, 0x71, 0x9a, 0x85, 0xa0, 0x73, 0xb7, 0x55, 0xb6, 0xfa, 0x27, 0x33, 0x21, 0xe8, 0x29, 0x0a, - 0xeb, 0x74, 0x68, 0x07, 0xc6, 0xd2, 0x2b, 0xab, 0x28, 0x66, 0x19, 0x6a, 0x46, 0xcf, 0x9f, 0xef, - 0xef, 0xe3, 0xd6, 0xb4, 0x92, 0xfc, 0xe4, 0xa0, 0x43, 0xb0, 0xc1, 0x79, 0xf6, 0xa7, 0x60, 0x54, - 0x1b, 0xd8, 0xe3, 0x78, 0x65, 0xcf, 0xbe, 0x0a, 0x53, 0xd9, 0xa1, 0x3b, 0x96, 0x57, 0xf7, 0xff, - 0x2a, 0xc1, 0x64, 0xce, 0xcd, 0x15, 0x4b, 0xf5, 0x9c, 0x11, 0xa8, 0x69, 0x66, 0x67, 0x53, 0x2c, - 0x96, 0xfa, 0x10, 0x8b, 0x52, 0x46, 0x97, 0x0b, 0x65, 0xb4, 0x10, 0x85, 0x03, 0xef, 0x47, 0x14, - 0x9a, 0xbb, 0xcf, 0x60, 0x5f, 0xbb, 0xcf, 0x5d, 0x10, 0x9f, 0xc6, 0x06, 0x36, 0xdc, 0xc7, 0x06, - 0xf6, 0xd5, 0x12, 0x4c, 0x65, 0xf3, 0x09, 0x9f, 0xc0, 0x7d, 0xc7, 0x6b, 0xc6, 0x7d, 0x47, 0x7e, - 0xe2, 0xf4, 0x6c, 0x96, 0xe3, 0xa2, 0xbb, 0x0f, 0x9c, 0xb9, 0xfb, 0x78, 0xb2, 0x2f, 0x6e, 0xdd, - 0xef, 0x41, 0xfe, 0x6e, 0x09, 0x4e, 0x67, 0x8b, 0x2c, 0xfb, 0x8e, 0xb7, 0x77, 0x02, 0x7d, 0x73, - 0xd5, 0xe8, 0x9b, 0x67, 0xfa, 0xf9, 0x1a, 0xd6, 0xb4, 0xc2, 0x0e, 0xba, 0x91, 0xe9, 0xa0, 0x85, - 0xfe, 0x59, 0x76, 0xef, 0xa5, 0xef, 0x95, 0xe1, 0x4c, 0x6e, 0xb9, 0xf4, 0xba, 0x60, 0xd5, 0xb8, - 0x2e, 0x38, 0x9f, 0xb9, 0x2e, 0xb0, 0xbb, 0x97, 0xbe, 0x3b, 0xf7, 0x07, 0x22, 0xf2, 0x8c, 0x65, - 0x4f, 0xbb, 0xc3, 0xbb, 0x03, 0x23, 0xf2, 0x4c, 0x31, 0xc2, 0x26, 0xdf, 0x9f, 0xa4, 0x3b, 0x83, - 0xdf, 0xb3, 0xe0, 0x81, 0xdc, 0xb1, 0x39, 0x01, 0xbb, 0xfa, 0x15, 0xd3, 0xae, 0xfe, 0x44, 0xdf, - 0xb3, 0xb5, 0xc0, 0xd0, 0xfe, 0xf5, 0x72, 0xc1, 0xb7, 0x30, 0xcb, 0xe4, 0x55, 0x18, 0x75, 0x9a, - 0x4d, 0x12, 0xc7, 0xeb, 0xa1, 0xab, 0x92, 0x95, 0x3d, 0xc3, 0xb4, 0x8d, 0x14, 0x7c, 0x74, 0x38, - 0x37, 0x9b, 0x65, 0x91, 0xa2, 0xb1, 0xce, 0xc1, 0x4c, 0x80, 0x58, 0xba, 0xab, 0x09, 0x10, 0xcf, - 0x03, 0xec, 0x2b, 0x7b, 0x45, 0xd6, 0xcc, 0xa9, 0x59, 0x32, 0x34, 0x2a, 0xf4, 0xd3, 0xec, 0x14, - 0xc0, 0x9d, 0x81, 0xf8, 0x54, 0x7c, 0xae, 0xcf, 0xb1, 0xd2, 0x1d, 0x8b, 0x78, 0x88, 0xb3, 0x32, - 0x09, 0x2b, 0x96, 0xe8, 0x33, 0x30, 0x15, 0xf3, 0x0c, 0x1a, 0xcb, 0xbe, 0x13, 0xb3, 0xc0, 0x1a, - 0x31, 0x0b, 0x59, 0xdc, 0x72, 0x23, 0x83, 0xc3, 0x1d, 0xd4, 0xf6, 0x57, 0x07, 0xe0, 0xc1, 0x2e, - 0xc2, 0x07, 0x2d, 0x9a, 0x97, 0xf7, 0x4f, 0x65, 0xed, 0x76, 0xb3, 0xb9, 0x85, 0x0d, 0x43, 0x5e, - 0x66, 0x8c, 0x4b, 0xef, 0x7b, 0x8c, 0xbf, 0x62, 0x69, 0x16, 0x55, 0xee, 0xe2, 0xfb, 0xa9, 0x63, - 0x0a, 0xd5, 0xbb, 0x68, 0x62, 0xdd, 0xca, 0xb1, 0x53, 0x9e, 0xef, 0xbb, 0x39, 0x7d, 0x1b, 0x2e, - 0x4f, 0xf6, 0xaa, 0xe7, 0x8b, 0x16, 0x3c, 0x92, 0xdb, 0x5e, 0xc3, 0xd9, 0x68, 0x01, 0x2a, 0x4d, - 0x0a, 0xd4, 0x82, 0xec, 0xd2, 0x50, 0x57, 0x89, 0xc0, 0x29, 0x8d, 0xe1, 0x53, 0x54, 0xea, 0xe9, - 0x53, 0xf4, 0x2f, 0x2d, 0x98, 0xc9, 0x36, 0xe2, 0x04, 0x24, 0xe0, 0x9a, 0x29, 0x01, 0x3f, 0xda, - 0xcf, 0x58, 0x16, 0x08, 0xbf, 0x3f, 0x9e, 0x80, 0xfb, 0x0a, 0xde, 0x8b, 0xd8, 0x87, 0xe9, 0xed, - 0x26, 0x31, 0xc3, 0x17, 0xc5, 0xc7, 0xe4, 0x46, 0x7a, 0x76, 0x8d, 0x75, 0xe4, 0x07, 0xd9, 0x0e, - 0x12, 0xdc, 0x59, 0x05, 0xfa, 0xa2, 0x05, 0x33, 0xce, 0xcd, 0xb8, 0xe3, 0xfd, 0x26, 0x31, 0x67, - 0x9e, 0xcf, 0xb5, 0xaf, 0xf6, 0x78, 0xef, 0x89, 0x85, 0x18, 0xcd, 0xe4, 0x51, 0xe1, 0xdc, 0xba, - 0x10, 0x16, 0x79, 0x21, 0xa9, 0x9e, 0xdc, 0x25, 0xc0, 0x36, 0x2f, 0xfc, 0x89, 0xcb, 0x42, 0x89, - 0xc1, 0x8a, 0x0f, 0xba, 0x0e, 0x95, 0x6d, 0x19, 0x93, 0x28, 0x64, 0x6d, 0xee, 0xe6, 0x95, 0x1b, - 0xb8, 0xc8, 0x63, 0x3e, 0x14, 0x0a, 0xa7, 0xac, 0xd0, 0xab, 0x50, 0x0e, 0xb6, 0xe2, 0x6e, 0x0f, - 0x66, 0x64, 0x7c, 0xf0, 0x78, 0xa4, 0xf4, 0x95, 0xd5, 0x06, 0xa6, 0x05, 0x69, 0xf9, 0x68, 0xd3, - 0x15, 0x57, 0x02, 0xb9, 0xe5, 0xf1, 0x52, 0xad, 0xb3, 0x3c, 0x5e, 0xaa, 0x61, 0x5a, 0x10, 0xad, - 0xc2, 0x20, 0x0b, 0x71, 0x12, 0xf6, 0xfe, 0xdc, 0x4c, 0x0f, 0x1d, 0xe1, 0x5b, 0x3c, 0x74, 0x9a, - 0x81, 0x31, 0x2f, 0x8e, 0x5e, 0x83, 0xa1, 0x26, 0x7b, 0x3f, 0x42, 0x18, 0x67, 0xf2, 0xb3, 0x97, - 0x74, 0xbc, 0x30, 0xc1, 0x6f, 0x39, 0x39, 0x1c, 0x0b, 0x0e, 0x68, 0x03, 0x86, 0x9a, 0xa4, 0xb5, - 0xb3, 0x15, 0x0b, 0x9b, 0xcb, 0xc7, 0x73, 0x79, 0x75, 0x79, 0x2e, 0x45, 0x70, 0x65, 0x14, 0x58, - 0xf0, 0x42, 0x9f, 0x84, 0xd2, 0x56, 0x53, 0x44, 0x3b, 0xe5, 0xda, 0xf9, 0xcd, 0x70, 0xf6, 0xa5, - 0xa1, 0xdb, 0x87, 0x73, 0xa5, 0xd5, 0x65, 0x5c, 0xda, 0x6a, 0xa2, 0x2b, 0x30, 0xbc, 0xc5, 0x63, - 0x92, 0x45, 0x9e, 0xdf, 0xc7, 0xf3, 0xc3, 0xa5, 0x3b, 0xc2, 0x96, 0x79, 0x94, 0x8e, 0x40, 0x60, - 0xc9, 0x04, 0x6d, 0x00, 0x6c, 0xa9, 0xd8, 0x6a, 0x91, 0xe8, 0xf7, 0xa3, 0xfd, 0x44, 0x60, 0x0b, - 0x03, 0x84, 0x82, 0x62, 0x8d, 0x0f, 0xfa, 0x3c, 0x54, 0x1c, 0xf9, 0x22, 0x10, 0x4b, 0xf2, 0x6b, - 0xea, 0x03, 0xe9, 0x82, 0xeb, 0xfe, 0x58, 0x12, 0x9f, 0xad, 0x8a, 0x08, 0xa7, 0x4c, 0xd1, 0x2e, - 0x8c, 0xef, 0xc7, 0xad, 0x1d, 0x22, 0x17, 0x28, 0xcb, 0xfc, 0x5b, 0xb0, 0x21, 0x5d, 0x17, 0x84, - 0x5e, 0x94, 0xb4, 0x1d, 0xbf, 0x43, 0xa6, 0xb0, 0x90, 0xae, 0xeb, 0x3a, 0x33, 0x6c, 0xf2, 0xa6, - 0x9d, 0xfe, 0x6e, 0x3b, 0xdc, 0x3c, 0x48, 0x88, 0xc8, 0x07, 0x9c, 0xdb, 0xe9, 0xaf, 0x73, 0x92, - 0xce, 0x4e, 0x17, 0x08, 0x2c, 0x99, 0xd0, 0x25, 0xec, 0xc8, 0xd7, 0xb6, 0x84, 0x95, 0xe5, 0x89, - 0xc2, 0xee, 0xe9, 0x68, 0x6f, 0xda, 0x29, 0x4c, 0xf6, 0xa5, 0xac, 0x98, 0xcc, 0x6b, 0xed, 0x84, - 0x49, 0x18, 0x64, 0xe4, 0xed, 0x74, 0xb1, 0xcc, 0xab, 0xe7, 0xd0, 0x77, 0xca, 0xbc, 0x3c, 0x2a, - 0x9c, 0x5b, 0x17, 0x72, 0x61, 0xa2, 0x15, 0x46, 0xc9, 0xcd, 0x30, 0x92, 0xb3, 0x0a, 0x75, 0x39, - 0x7e, 0x1b, 0x94, 0xa2, 0x46, 0xe6, 0xa1, 0x6d, 0x62, 0x70, 0x86, 0x27, 0x1d, 0x92, 0xb8, 0xe9, - 0xf8, 0x64, 0xed, 0x6a, 0xf5, 0x54, 0xf1, 0x90, 0x34, 0x38, 0x49, 0xe7, 0x90, 0x08, 0x04, 0x96, - 0x4c, 0xa8, 0xf4, 0x61, 0xa9, 0xe5, 0x59, 0x02, 0xe3, 0x02, 0xe9, 0xd3, 0xe1, 0xbb, 0xcc, 0xa5, - 0x0f, 0x03, 0x63, 0x5e, 0x9c, 0xce, 0x7c, 0xa1, 0x7b, 0x86, 0x71, 0xf5, 0x74, 0xf1, 0xcc, 0x17, - 0x2a, 0xeb, 0xd5, 0x46, 0xb7, 0x99, 0xaf, 0x88, 0x70, 0xca, 0xd4, 0xfe, 0xd6, 0x50, 0xa7, 0xb6, - 0xc0, 0xce, 0x18, 0x5f, 0xb2, 0x3a, 0x2e, 0xe0, 0x3f, 0xd1, 0xaf, 0xc9, 0xe3, 0x2e, 0xea, 0x89, - 0x5f, 0xb4, 0xe0, 0xbe, 0x56, 0xee, 0x47, 0x89, 0xad, 0xb7, 0x3f, 0xcb, 0x09, 0xef, 0x06, 0x95, - 0x1a, 0x3c, 0x1f, 0x8f, 0x0b, 0x6a, 0xca, 0xea, 0xe2, 0xe5, 0xf7, 0xad, 0x8b, 0xaf, 0xc3, 0x08, - 0x53, 0xef, 0xd2, 0x54, 0x44, 0x7d, 0xb9, 0xb1, 0xb1, 0x4d, 0x7c, 0x59, 0x14, 0xc4, 0x8a, 0x05, - 0xfa, 0x39, 0x0b, 0x1e, 0xce, 0x36, 0x1d, 0x13, 0x86, 0x16, 0x69, 0x2d, 0xf9, 0xf1, 0x66, 0x55, - 0x7c, 0xff, 0xc3, 0xf5, 0x6e, 0xc4, 0x47, 0xbd, 0x08, 0x70, 0xf7, 0xca, 0x50, 0x2d, 0xe7, 0x7c, - 0x35, 0x64, 0xde, 0xcf, 0xf5, 0x3e, 0x63, 0xa1, 0xe7, 0x61, 0x6c, 0x2f, 0x6c, 0x07, 0x32, 0xc6, - 0x44, 0x44, 0x10, 0x33, 0x5b, 0xf0, 0xba, 0x06, 0xc7, 0x06, 0xd5, 0xc9, 0xea, 0xfb, 0xdf, 0xb0, - 0x72, 0x14, 0x55, 0x7e, 0x02, 0x7c, 0xc5, 0x3c, 0x01, 0x3e, 0x96, 0x3d, 0x01, 0x76, 0x58, 0xea, - 0x8c, 0xc3, 0x5f, 0xff, 0xe9, 0x7a, 0xfb, 0xcd, 0xd5, 0x64, 0xfb, 0x70, 0xb6, 0x97, 0x70, 0x66, - 0xae, 0x7c, 0xae, 0xba, 0xe3, 0x4e, 0x5d, 0xf9, 0xdc, 0xb5, 0x1a, 0x66, 0x98, 0x7e, 0xb3, 0x7e, - 0xd8, 0xff, 0xcd, 0x82, 0x72, 0x3d, 0x74, 0x4f, 0xc0, 0xf2, 0xf8, 0x29, 0xc3, 0xf2, 0xf8, 0x60, - 0xc1, 0xdb, 0x9d, 0x85, 0x76, 0xc6, 0x95, 0x8c, 0x9d, 0xf1, 0xe1, 0x22, 0x06, 0xdd, 0xad, 0x8a, - 0x7f, 0xaf, 0x0c, 0xfa, 0x4b, 0xa3, 0xe8, 0x5f, 0xdf, 0x89, 0x4f, 0x78, 0xb9, 0xdb, 0xe3, 0xa3, - 0x82, 0x33, 0xf3, 0x00, 0x94, 0xe1, 0xa6, 0x3f, 0x66, 0xae, 0xe1, 0x37, 0x88, 0xb7, 0xbd, 0x93, - 0x10, 0x37, 0xfb, 0x39, 0x27, 0xe7, 0x1a, 0xfe, 0x5f, 0x2c, 0x98, 0xcc, 0xd4, 0x8e, 0xfc, 0xbc, - 0xd8, 0xb5, 0x3b, 0xb4, 0x38, 0x4d, 0xf7, 0x0c, 0x76, 0x9b, 0x07, 0x50, 0xd7, 0x3a, 0xd2, 0xaa, - 0xc3, 0x74, 0x5f, 0x75, 0xef, 0x13, 0x63, 0x8d, 0x02, 0xbd, 0x00, 0xa3, 0x49, 0xd8, 0x0a, 0xfd, - 0x70, 0xfb, 0xe0, 0x12, 0x91, 0x79, 0x66, 0xd4, 0xe5, 0xdb, 0x46, 0x8a, 0xc2, 0x3a, 0x9d, 0xfd, - 0x2b, 0x65, 0xc8, 0xbe, 0x4e, 0xfb, 0xe7, 0x73, 0xf2, 0xc3, 0x39, 0x27, 0xbf, 0x67, 0xc1, 0x14, - 0xad, 0x9d, 0x79, 0x57, 0x49, 0xa7, 0x6a, 0xf5, 0xae, 0x87, 0xd5, 0xe5, 0x5d, 0x8f, 0xc7, 0xa8, - 0xec, 0x72, 0xc3, 0x76, 0x22, 0xac, 0x42, 0x9a, 0x70, 0xa2, 0x50, 0x2c, 0xb0, 0x82, 0x8e, 0x44, - 0x91, 0x88, 0x48, 0xd3, 0xe9, 0x48, 0x14, 0x61, 0x81, 0x95, 0xcf, 0x7e, 0x0c, 0x14, 0x3c, 0xfb, - 0xc1, 0x52, 0xb4, 0x09, 0x8f, 0x1e, 0xa1, 0x50, 0x68, 0x29, 0xda, 0xa4, 0xab, 0x4f, 0x4a, 0x63, - 0x7f, 0xb3, 0x0c, 0x63, 0xf5, 0xd0, 0x4d, 0x2f, 0x56, 0x9e, 0x37, 0x2e, 0x56, 0xce, 0x66, 0x2e, - 0x56, 0xa6, 0x74, 0xda, 0x3f, 0xbf, 0x46, 0xf9, 0xa0, 0xae, 0x51, 0xfe, 0xcc, 0x82, 0x89, 0x7a, - 0xe8, 0xd2, 0x09, 0xfa, 0x93, 0x34, 0x1b, 0xf5, 0x04, 0x80, 0x43, 0x5d, 0x12, 0x00, 0xfe, 0x7d, - 0x0b, 0x86, 0xeb, 0xa1, 0x7b, 0x02, 0x16, 0xd3, 0x57, 0x4c, 0x8b, 0xe9, 0xfd, 0x05, 0x52, 0xb6, - 0xc0, 0x48, 0xfa, 0x1b, 0x65, 0x18, 0xa7, 0xed, 0x0c, 0xb7, 0xe5, 0x28, 0x19, 0x3d, 0x62, 0xf5, - 0xd1, 0x23, 0x54, 0x99, 0x0b, 0x7d, 0x3f, 0xbc, 0x99, 0x1d, 0xb1, 0x55, 0x06, 0xc5, 0x02, 0x8b, - 0x9e, 0x86, 0x91, 0x56, 0x44, 0xf6, 0xbd, 0xb0, 0x1d, 0x67, 0x63, 0x5a, 0xeb, 0x02, 0x8e, 0x15, - 0x05, 0xd5, 0xdb, 0x63, 0x2f, 0x68, 0x12, 0xe9, 0xe5, 0x33, 0xc0, 0xbc, 0x7c, 0x78, 0x0e, 0x55, - 0x0d, 0x8e, 0x0d, 0x2a, 0x74, 0x03, 0x2a, 0xec, 0x3f, 0x5b, 0x37, 0xc7, 0x7f, 0xd5, 0x43, 0x24, - 0x2e, 0x17, 0x0c, 0x70, 0xca, 0x0b, 0x9d, 0x07, 0x48, 0xa4, 0x3f, 0x52, 0x2c, 0x42, 0xae, 0x95, - 0x46, 0xa9, 0x3c, 0x95, 0x62, 0xac, 0x51, 0xa1, 0xa7, 0xa0, 0x92, 0x38, 0x9e, 0x7f, 0xd9, 0x0b, - 0x48, 0x2c, 0xfc, 0xb9, 0x44, 0x5e, 0x72, 0x01, 0xc4, 0x29, 0x9e, 0xee, 0xe8, 0x2c, 0xa0, 0x9f, - 0xbf, 0x09, 0x34, 0xc2, 0xa8, 0xd9, 0x8e, 0x7e, 0x59, 0x41, 0xb1, 0x46, 0x61, 0xbf, 0x04, 0xa7, - 0xeb, 0xa1, 0x5b, 0x0f, 0xa3, 0x64, 0x35, 0x8c, 0x6e, 0x3a, 0x91, 0x2b, 0xc7, 0x6f, 0x4e, 0xa6, - 0xc8, 0xa6, 0xbb, 0xee, 0x20, 0xb7, 0x06, 0x18, 0xc9, 0xaf, 0x9f, 0x63, 0x7b, 0xfa, 0x31, 0x83, - 0x6f, 0xfe, 0x5d, 0x09, 0x50, 0x9d, 0x79, 0x4c, 0x19, 0x0f, 0x47, 0xbd, 0x0d, 0x13, 0x31, 0xb9, - 0xec, 0x05, 0xed, 0x5b, 0xf2, 0x7c, 0xd5, 0x25, 0xb2, 0xa9, 0xb1, 0xa2, 0x53, 0x72, 0x8b, 0x8a, - 0x09, 0xc3, 0x19, 0x6e, 0xb4, 0x0b, 0xa3, 0x76, 0xb0, 0x18, 0x5f, 0x8b, 0x49, 0x24, 0x1e, 0x4a, - 0x62, 0x5d, 0x88, 0x25, 0x10, 0xa7, 0x78, 0x3a, 0x65, 0xd8, 0x9f, 0x2b, 0x61, 0x80, 0xc3, 0x30, - 0x91, 0x93, 0x8c, 0x3d, 0xb5, 0xa1, 0xc1, 0xb1, 0x41, 0x85, 0x56, 0x01, 0xc5, 0xed, 0x56, 0xcb, - 0x67, 0xd7, 0x90, 0x8e, 0x7f, 0x21, 0x0a, 0xdb, 0x2d, 0x7e, 0x95, 0x24, 0x5e, 0xa9, 0x68, 0x74, - 0x60, 0x71, 0x4e, 0x09, 0x2a, 0x18, 0xb6, 0x62, 0xf6, 0x5b, 0xc4, 0xf4, 0x73, 0xdb, 0x66, 0x83, - 0x81, 0xb0, 0xc4, 0xd9, 0x5f, 0x60, 0x9b, 0x19, 0x7b, 0xdf, 0x26, 0x69, 0x47, 0x04, 0xed, 0xc1, - 0x78, 0x8b, 0x6d, 0x58, 0x49, 0x14, 0xfa, 0x3e, 0x91, 0x7a, 0xe3, 0x9d, 0x79, 0x6f, 0xf1, 0xf7, - 0x2e, 0x74, 0x76, 0xd8, 0xe4, 0x6e, 0x7f, 0x69, 0x92, 0xc9, 0xa5, 0x06, 0x3f, 0xb4, 0x0c, 0x0b, - 0x9f, 0x6c, 0xa1, 0xa1, 0xcd, 0x16, 0xbf, 0x27, 0x97, 0x4a, 0x7a, 0xe1, 0xd7, 0x8d, 0x65, 0x59, - 0xf4, 0x3a, 0xbb, 0x83, 0xe3, 0xc2, 0xa0, 0xd7, 0x4b, 0x96, 0x9c, 0xca, 0xb8, 0x6e, 0x13, 0x05, - 0xb1, 0xc6, 0x04, 0x5d, 0x86, 0x71, 0xf1, 0x1c, 0x8a, 0x30, 0x3c, 0x94, 0x8d, 0xe3, 0xef, 0x38, - 0xd6, 0x91, 0x47, 0x59, 0x00, 0x36, 0x0b, 0xa3, 0x6d, 0x78, 0x58, 0x7b, 0xbc, 0x2b, 0xc7, 0x83, - 0x90, 0xcb, 0x96, 0x47, 0x6e, 0x1f, 0xce, 0x3d, 0xbc, 0xd1, 0x8d, 0x10, 0x77, 0xe7, 0x83, 0xae, - 0xc2, 0x69, 0xa7, 0x99, 0x78, 0xfb, 0xa4, 0x46, 0x1c, 0xd7, 0xf7, 0x02, 0x62, 0x26, 0x79, 0x78, - 0xe0, 0xf6, 0xe1, 0xdc, 0xe9, 0xc5, 0x3c, 0x02, 0x9c, 0x5f, 0x0e, 0xbd, 0x02, 0x15, 0x37, 0x88, - 0x45, 0x1f, 0x0c, 0x19, 0xef, 0xd2, 0x55, 0x6a, 0x57, 0x1a, 0xea, 0xfb, 0xd3, 0x3f, 0x38, 0x2d, - 0x80, 0xb6, 0x61, 0x4c, 0x0f, 0xe4, 0x12, 0x6f, 0x1a, 0x3e, 0xd3, 0xe5, 0x6c, 0x6b, 0x44, 0x3f, - 0x71, 0xab, 0x9b, 0xf2, 0xcf, 0x35, 0x02, 0xa3, 0x0c, 0xc6, 0xe8, 0x35, 0x40, 0x31, 0x89, 0xf6, - 0xbd, 0x26, 0x59, 0x6c, 0xb2, 0x24, 0xc3, 0xcc, 0x56, 0x33, 0x62, 0x04, 0x9b, 0xa0, 0x46, 0x07, - 0x05, 0xce, 0x29, 0x85, 0x2e, 0x52, 0x89, 0xa2, 0x43, 0x85, 0x3b, 0xb5, 0x54, 0xf3, 0xaa, 0x35, - 0xd2, 0x8a, 0x48, 0xd3, 0x49, 0x88, 0x6b, 0x72, 0xc4, 0x99, 0x72, 0x74, 0xbf, 0x51, 0x6f, 0x37, - 0x80, 0xe9, 0x04, 0xdc, 0xf9, 0x7e, 0x03, 0x3d, 0x21, 0xed, 0x84, 0x71, 0x72, 0x85, 0x24, 0x37, - 0xc3, 0x68, 0x57, 0x64, 0x66, 0x4b, 0x13, 0x37, 0xa6, 0x28, 0xac, 0xd3, 0x51, 0x8d, 0x88, 0x5d, - 0x82, 0xad, 0xd5, 0xd8, 0x3d, 0xc5, 0x48, 0xba, 0x4e, 0x2e, 0x72, 0x30, 0x96, 0x78, 0x49, 0xba, - 0x56, 0x5f, 0x66, 0xb7, 0x0f, 0x19, 0xd2, 0xb5, 0xfa, 0x32, 0x96, 0x78, 0x44, 0x3a, 0xdf, 0xfc, - 0x9b, 0x28, 0xbe, 0x37, 0xea, 0x94, 0xcb, 0x7d, 0x3e, 0xfb, 0x17, 0xc0, 0x94, 0x7a, 0x6d, 0x90, - 0xa7, 0xac, 0x8b, 0xab, 0x93, 0x6c, 0x92, 0xf4, 0x9f, 0xef, 0x4e, 0xd9, 0xe2, 0xd6, 0x32, 0x9c, - 0x70, 0x07, 0x6f, 0x23, 0x79, 0xc8, 0x54, 0xcf, 0xb7, 0x37, 0x16, 0xa0, 0x12, 0xb7, 0x37, 0xdd, - 0x70, 0xcf, 0xf1, 0x02, 0x76, 0x59, 0xa0, 0x29, 0x22, 0x0d, 0x89, 0xc0, 0x29, 0x0d, 0x5a, 0x85, - 0x11, 0x47, 0x1c, 0xbe, 0x84, 0x79, 0x3f, 0x37, 0x9b, 0x80, 0x3c, 0xa0, 0x71, 0x3b, 0xa8, 0xfc, - 0x87, 0x55, 0x59, 0xf4, 0x32, 0x8c, 0x8b, 0x80, 0x37, 0xe1, 0xab, 0x7a, 0xca, 0x8c, 0x8d, 0x68, - 0xe8, 0x48, 0x6c, 0xd2, 0xa2, 0x9f, 0x86, 0x09, 0xca, 0x25, 0x15, 0x6c, 0xd5, 0x99, 0x7e, 0x24, - 0xa2, 0x96, 0x53, 0x5d, 0x2f, 0x8c, 0x33, 0xcc, 0x90, 0x0b, 0x0f, 0x39, 0xed, 0x24, 0x64, 0xc6, - 0x4a, 0x73, 0xfe, 0x6f, 0x84, 0xbb, 0x24, 0x60, 0xd6, 0xfd, 0x91, 0xa5, 0xb3, 0xb7, 0x0f, 0xe7, - 0x1e, 0x5a, 0xec, 0x42, 0x87, 0xbb, 0x72, 0x41, 0xd7, 0x60, 0x34, 0x09, 0x7d, 0xe1, 0x64, 0x1e, - 0x57, 0xef, 0x2b, 0x4e, 0x7e, 0xb4, 0xa1, 0xc8, 0x74, 0x73, 0x82, 0x2a, 0x8a, 0x75, 0x3e, 0x68, - 0x83, 0xaf, 0x31, 0x96, 0xaa, 0x93, 0xc4, 0xd5, 0xfb, 0x8b, 0x3b, 0x46, 0x65, 0xf4, 0x34, 0x97, - 0xa0, 0x28, 0x89, 0x75, 0x36, 0xe8, 0x02, 0x4c, 0xb7, 0x22, 0x2f, 0x64, 0x13, 0x5b, 0x19, 0x8a, - 0xab, 0x46, 0x5a, 0xbc, 0xe9, 0x7a, 0x96, 0x00, 0x77, 0x96, 0x41, 0xe7, 0xa8, 0x82, 0xca, 0x81, - 0xd5, 0x07, 0xf8, 0x9b, 0x2c, 0x5c, 0x39, 0xe5, 0x30, 0xac, 0xb0, 0xb3, 0x9f, 0x86, 0xe9, 0x0e, - 0x49, 0x79, 0x2c, 0x87, 0xdf, 0x7f, 0x32, 0x08, 0x15, 0x65, 0x0e, 0x44, 0x0b, 0xa6, 0x95, 0xf7, - 0x81, 0xac, 0x95, 0x77, 0x84, 0xea, 0x6b, 0xba, 0x61, 0x77, 0x23, 0xe7, 0x49, 0xf9, 0xb3, 0x05, - 0xa2, 0xa1, 0xff, 0xe8, 0xbc, 0x63, 0x3c, 0xb7, 0x9f, 0x1e, 0x18, 0x07, 0xba, 0x1e, 0x18, 0xfb, - 0x7c, 0xde, 0x91, 0x1e, 0x0d, 0x5b, 0xa1, 0xbb, 0x56, 0xcf, 0xbe, 0x77, 0x56, 0xa7, 0x40, 0xcc, - 0x71, 0x4c, 0xb9, 0xa7, 0xdb, 0x3a, 0x53, 0xee, 0x87, 0xef, 0x50, 0xb9, 0x97, 0x0c, 0x70, 0xca, - 0x0b, 0xf9, 0x30, 0xdd, 0x34, 0x9f, 0xaa, 0x53, 0x11, 0x79, 0x8f, 0xf6, 0x7c, 0x34, 0xae, 0xad, - 0xbd, 0x61, 0xb3, 0x9c, 0xe5, 0x82, 0x3b, 0x19, 0xa3, 0x97, 0x61, 0xe4, 0xdd, 0x30, 0x66, 0xd3, - 0x4e, 0xec, 0x6d, 0x32, 0x06, 0x6a, 0xe4, 0xf5, 0xab, 0x0d, 0x06, 0x3f, 0x3a, 0x9c, 0x1b, 0xad, - 0x87, 0xae, 0xfc, 0x8b, 0x55, 0x01, 0x74, 0x0b, 0x4e, 0x1b, 0x12, 0x41, 0x35, 0x17, 0xfa, 0x6f, - 0xee, 0xc3, 0xa2, 0xba, 0xd3, 0x6b, 0x79, 0x9c, 0x70, 0x7e, 0x05, 0xf6, 0xb7, 0xb8, 0xd1, 0x53, - 0x98, 0x46, 0x48, 0xdc, 0xf6, 0x4f, 0xe2, 0xa1, 0x8a, 0x15, 0xc3, 0x6a, 0x73, 0xc7, 0x86, 0xf5, - 0xdf, 0xb5, 0x98, 0x61, 0x7d, 0x83, 0xec, 0xb5, 0x7c, 0x27, 0x39, 0x09, 0x37, 0xef, 0xd7, 0x61, - 0x24, 0x11, 0xb5, 0x75, 0x7b, 0x5b, 0x43, 0x6b, 0x14, 0xbb, 0x5c, 0x50, 0x1b, 0xa2, 0x84, 0x62, - 0xc5, 0xc6, 0xfe, 0x67, 0x7c, 0x04, 0x24, 0xe6, 0x04, 0x6c, 0x0b, 0x35, 0xd3, 0xb6, 0x30, 0xd7, - 0xe3, 0x0b, 0x0a, 0x6c, 0x0c, 0xff, 0xd4, 0x6c, 0x37, 0x3b, 0x7b, 0x7c, 0xd8, 0x6f, 0x74, 0xec, - 0x5f, 0xb4, 0x60, 0x26, 0xcf, 0x11, 0x80, 0x2a, 0x31, 0xfc, 0xe4, 0xa3, 0x6e, 0xb8, 0x54, 0x0f, - 0x5e, 0x17, 0x70, 0xac, 0x28, 0xfa, 0xce, 0x6f, 0x7f, 0xbc, 0x84, 0x5f, 0x57, 0xc1, 0x7c, 0xd5, - 0x10, 0xbd, 0xca, 0xe3, 0x36, 0x2c, 0xf5, 0xec, 0xe0, 0xf1, 0x62, 0x36, 0xec, 0x5f, 0x2d, 0xc1, - 0x0c, 0x37, 0x51, 0x2f, 0xee, 0x87, 0x9e, 0x5b, 0x0f, 0x5d, 0x11, 0xc5, 0xf2, 0x26, 0x8c, 0xb5, - 0xb4, 0xe3, 0x6a, 0xb7, 0x94, 0x43, 0xfa, 0xb1, 0x36, 0x3d, 0x36, 0xe8, 0x50, 0x6c, 0xf0, 0x42, - 0x2e, 0x8c, 0x91, 0x7d, 0xaf, 0xa9, 0xec, 0x9c, 0xa5, 0x63, 0x8b, 0x74, 0x55, 0xcb, 0x8a, 0xc6, - 0x07, 0x1b, 0x5c, 0xef, 0xc1, 0x2b, 0x34, 0xf6, 0xd7, 0x2c, 0xb8, 0xbf, 0x20, 0x41, 0x11, 0xad, - 0xee, 0x26, 0xbb, 0x0c, 0x10, 0x4f, 0x64, 0xaa, 0xea, 0xf8, 0x15, 0x01, 0x16, 0x58, 0xf4, 0x59, - 0x00, 0x6e, 0xe2, 0xa7, 0x5a, 0xb4, 0xf8, 0xf4, 0xfe, 0x12, 0x77, 0x68, 0xd9, 0x1d, 0x64, 0x79, - 0xac, 0xf1, 0xb2, 0x7f, 0xb9, 0x0c, 0x83, 0xfc, 0xbd, 0xf4, 0x55, 0x18, 0xde, 0xe1, 0xe9, 0x90, - 0xfb, 0xc9, 0xbc, 0x9c, 0x1e, 0x47, 0x38, 0x00, 0xcb, 0xc2, 0x68, 0x1d, 0x4e, 0x89, 0x48, 0xa9, - 0x1a, 0xf1, 0x9d, 0x03, 0x79, 0xaa, 0xe5, 0x4f, 0x93, 0xc8, 0xb4, 0xf9, 0xa7, 0xd6, 0x3a, 0x49, - 0x70, 0x5e, 0x39, 0xf4, 0x6a, 0x47, 0x12, 0x44, 0x9e, 0x48, 0x5a, 0xe9, 0xc0, 0x3d, 0x12, 0x21, - 0xbe, 0x0c, 0xe3, 0xad, 0x8e, 0xf3, 0xbb, 0xf6, 0x54, 0xb5, 0x79, 0x66, 0x37, 0x69, 0x99, 0x57, - 0x41, 0x9b, 0xf9, 0x50, 0x6c, 0xec, 0x44, 0x24, 0xde, 0x09, 0x7d, 0x57, 0xbc, 0xcb, 0x9a, 0x7a, - 0x15, 0x64, 0xf0, 0xb8, 0xa3, 0x04, 0xe5, 0xb2, 0xe5, 0x78, 0x7e, 0x3b, 0x22, 0x29, 0x97, 0x21, - 0x93, 0xcb, 0x6a, 0x06, 0x8f, 0x3b, 0x4a, 0xd0, 0x79, 0x74, 0x5a, 0x3c, 0xea, 0x29, 0xe3, 0xe7, - 0x95, 0xab, 0xc8, 0xb0, 0xf4, 0xa3, 0xef, 0x92, 0xd3, 0x45, 0x5c, 0xf9, 0xab, 0x67, 0x41, 0xb5, - 0x27, 0xe3, 0x84, 0x07, 0xbd, 0xe4, 0x72, 0x27, 0x4f, 0x4b, 0xfe, 0x91, 0x05, 0xa7, 0x72, 0xdc, - 0xc7, 0xb8, 0xa8, 0xda, 0xf6, 0xe2, 0x44, 0xbd, 0x88, 0xa1, 0x89, 0x2a, 0x0e, 0xc7, 0x8a, 0x82, - 0xae, 0x07, 0x2e, 0x0c, 0xb3, 0x02, 0x50, 0xb8, 0x7c, 0x08, 0xec, 0xf1, 0x04, 0x20, 0x3a, 0x0b, - 0x03, 0xed, 0x98, 0x44, 0xf2, 0x4d, 0x46, 0x29, 0xbf, 0x99, 0x45, 0x90, 0x61, 0xa8, 0x46, 0xb9, - 0xad, 0x8c, 0x71, 0x9a, 0x46, 0xc9, 0xcd, 0x71, 0x1c, 0x67, 0x7f, 0xa5, 0x0c, 0x93, 0x19, 0x07, - 0x50, 0xda, 0x90, 0xbd, 0x30, 0xf0, 0x92, 0x50, 0xe5, 0xe0, 0xe3, 0x29, 0x47, 0x48, 0x6b, 0x67, - 0x5d, 0xc0, 0xb1, 0xa2, 0x40, 0x8f, 0xc9, 0x87, 0x7a, 0xb3, 0x2f, 0x7d, 0x2c, 0xd5, 0x8c, 0xb7, - 0x7a, 0xfb, 0x7d, 0xb2, 0xe7, 0x51, 0x18, 0x68, 0x85, 0xea, 0x15, 0x75, 0x35, 0x9e, 0x78, 0xa9, - 0x56, 0x0f, 0x43, 0x1f, 0x33, 0x24, 0xfa, 0x98, 0xf8, 0xfa, 0xcc, 0x7d, 0x05, 0x76, 0xdc, 0x30, - 0xd6, 0xba, 0xe0, 0x09, 0x18, 0xde, 0x25, 0x07, 0x91, 0x17, 0x6c, 0x67, 0x6f, 0x6b, 0x2e, 0x71, - 0x30, 0x96, 0x78, 0x33, 0xe5, 0xfd, 0xf0, 0x3d, 0x79, 0x75, 0x67, 0xa4, 0xe7, 0xae, 0xf6, 0x1b, - 0x16, 0x4c, 0xb2, 0x7c, 0xb7, 0x22, 0x53, 0x83, 0x17, 0x06, 0x27, 0xa0, 0x27, 0x3c, 0x0a, 0x83, - 0x11, 0xad, 0x34, 0xfb, 0x94, 0x06, 0x6b, 0x09, 0xe6, 0x38, 0xf4, 0x10, 0x0c, 0xb0, 0x26, 0xd0, - 0xc1, 0x1b, 0xe3, 0x19, 0xef, 0x6b, 0x4e, 0xe2, 0x60, 0x06, 0x65, 0x21, 0x73, 0x98, 0xb4, 0x7c, - 0x8f, 0x37, 0x3a, 0x35, 0xb7, 0x7e, 0x38, 0x42, 0xe6, 0x72, 0x9b, 0xf6, 0xfe, 0x42, 0xe6, 0xf2, - 0x59, 0x76, 0xd7, 0xc1, 0xff, 0x7b, 0x09, 0xce, 0xe4, 0x96, 0xeb, 0x3b, 0x64, 0xae, 0x7b, 0xe9, - 0xbb, 0x73, 0xd7, 0x9b, 0x7f, 0x05, 0x5b, 0x3e, 0xc1, 0x2b, 0xd8, 0x81, 0x7e, 0xd5, 0x94, 0xc1, - 0x3e, 0x22, 0xd9, 0x72, 0xbb, 0xec, 0x43, 0x12, 0xc9, 0x96, 0xdb, 0xb6, 0x82, 0x33, 0xc4, 0x8f, - 0x4a, 0x05, 0xdf, 0xc2, 0x4e, 0x13, 0xe7, 0xa8, 0x9c, 0x61, 0xc8, 0x58, 0xa8, 0x5d, 0x63, 0x5c, - 0xc6, 0x70, 0x18, 0x56, 0x58, 0xe4, 0x69, 0x31, 0x61, 0xbc, 0x69, 0x2f, 0x1f, 0x6b, 0xc9, 0xcc, - 0x9b, 0xd6, 0x71, 0x3d, 0xad, 0x44, 0x36, 0x3e, 0x6c, 0x5d, 0x3b, 0x01, 0x96, 0xfb, 0x3f, 0x01, - 0x8e, 0xe5, 0x9f, 0xfe, 0xd0, 0x22, 0x4c, 0xee, 0x79, 0x01, 0x7b, 0x0f, 0xd7, 0xd4, 0x7b, 0x54, - 0x88, 0xf4, 0xba, 0x89, 0xc6, 0x59, 0xfa, 0xd9, 0x97, 0x61, 0xfc, 0xce, 0x4d, 0x56, 0xdf, 0x2b, - 0xc3, 0x83, 0x5d, 0x96, 0x3d, 0x97, 0xf5, 0xc6, 0x18, 0x68, 0xb2, 0xbe, 0x63, 0x1c, 0xea, 0x30, - 0xb3, 0xd5, 0xf6, 0xfd, 0x03, 0xe6, 0xe5, 0x44, 0x5c, 0x49, 0x21, 0x14, 0x13, 0x95, 0xcc, 0x7a, - 0x35, 0x87, 0x06, 0xe7, 0x96, 0x44, 0xaf, 0x01, 0x0a, 0x37, 0x59, 0x82, 0x65, 0x37, 0x4d, 0x96, - 0xc1, 0x3a, 0xbe, 0x9c, 0x2e, 0xc6, 0xab, 0x1d, 0x14, 0x38, 0xa7, 0x14, 0xd5, 0x30, 0xd9, 0x0b, - 0xfe, 0xaa, 0x59, 0x19, 0x0d, 0x13, 0xeb, 0x48, 0x6c, 0xd2, 0xa2, 0x0b, 0x30, 0xed, 0xec, 0x3b, - 0x1e, 0x4f, 0x9e, 0x26, 0x19, 0x70, 0x15, 0x53, 0x19, 0x8a, 0x16, 0xb3, 0x04, 0xb8, 0xb3, 0x4c, - 0x26, 0xb8, 0x6d, 0xa8, 0x38, 0xb8, 0xad, 0xbb, 0x5c, 0xec, 0x65, 0xf7, 0xb3, 0xff, 0xa3, 0x45, - 0xb7, 0xaf, 0x9c, 0x07, 0x58, 0x69, 0x3f, 0x28, 0xfb, 0x95, 0x16, 0x67, 0xa6, 0xfa, 0x61, 0x59, - 0x47, 0x62, 0x93, 0x96, 0x4f, 0x88, 0x38, 0x75, 0xb2, 0x36, 0xf4, 0x44, 0x11, 0x00, 0xaa, 0x28, - 0xd0, 0x1b, 0x30, 0xec, 0x7a, 0xfb, 0x5e, 0x1c, 0x46, 0x62, 0xb1, 0x1c, 0xf7, 0xd1, 0x71, 0x25, - 0x07, 0x6b, 0x9c, 0x0d, 0x96, 0xfc, 0xec, 0xaf, 0x94, 0x60, 0x5c, 0xd6, 0xf8, 0x7a, 0x3b, 0x4c, - 0x9c, 0x13, 0xd8, 0x96, 0x2f, 0x18, 0xdb, 0xf2, 0xc7, 0xba, 0x45, 0xc1, 0xb2, 0x26, 0x15, 0x6e, - 0xc7, 0x57, 0x33, 0xdb, 0xf1, 0xe3, 0xbd, 0x59, 0x75, 0xdf, 0x86, 0xff, 0xb9, 0x05, 0xd3, 0x06, - 0xfd, 0x09, 0xec, 0x06, 0xab, 0xe6, 0x6e, 0xf0, 0x48, 0xcf, 0x6f, 0x28, 0xd8, 0x05, 0xbe, 0x51, - 0xca, 0xb4, 0x9d, 0x49, 0xff, 0x77, 0x61, 0x60, 0xc7, 0x89, 0xdc, 0x6e, 0x29, 0x40, 0x3b, 0x0a, - 0xcd, 0x5f, 0x74, 0x22, 0x97, 0xcb, 0xf0, 0xa7, 0xd5, 0xdb, 0x70, 0x4e, 0xe4, 0xf6, 0x8c, 0x29, - 0x60, 0x55, 0xa1, 0x97, 0x60, 0x28, 0x6e, 0x86, 0x2d, 0xe5, 0x7b, 0x79, 0x96, 0xbf, 0x1b, 0x47, - 0x21, 0x47, 0x87, 0x73, 0xc8, 0xac, 0x8e, 0x82, 0xb1, 0xa0, 0x9f, 0xdd, 0x86, 0x8a, 0xaa, 0xfa, - 0x9e, 0x7a, 0x95, 0xff, 0x41, 0x19, 0x4e, 0xe5, 0xcc, 0x0b, 0x14, 0x1b, 0xbd, 0xf5, 0x6c, 0x9f, - 0xd3, 0xe9, 0x7d, 0xf6, 0x57, 0xcc, 0x4e, 0x2c, 0xae, 0x18, 0xff, 0xbe, 0x2b, 0xbd, 0x16, 0x93, - 0x6c, 0xa5, 0x14, 0xd4, 0xbb, 0x52, 0x5a, 0xd9, 0x89, 0x75, 0x35, 0xad, 0x48, 0xb5, 0xf4, 0x9e, - 0x8e, 0xe9, 0x9f, 0x94, 0x61, 0x26, 0x2f, 0x78, 0x1e, 0xfd, 0x4c, 0xe6, 0x41, 0x91, 0xe7, 0xfb, - 0x0d, 0xbb, 0xe7, 0xaf, 0x8c, 0x88, 0x6c, 0x43, 0xf3, 0xe6, 0x13, 0x23, 0x3d, 0xbb, 0x59, 0xd4, - 0xc9, 0x82, 0x7c, 0x22, 0xfe, 0x10, 0x8c, 0x5c, 0xe2, 0x9f, 0xe8, 0xbb, 0x01, 0xe2, 0x05, 0x99, - 0x38, 0x13, 0xe4, 0x23, 0xc1, 0xbd, 0x83, 0x7c, 0x64, 0xcd, 0xb3, 0x1e, 0x8c, 0x6a, 0x5f, 0x73, - 0x4f, 0x47, 0x7c, 0x97, 0xee, 0x28, 0x5a, 0xbb, 0xef, 0xe9, 0xa8, 0x7f, 0xcd, 0x82, 0x8c, 0x9f, - 0x94, 0xb2, 0x7f, 0x58, 0x85, 0xf6, 0x8f, 0xb3, 0x30, 0x10, 0x85, 0x3e, 0xc9, 0xbe, 0x31, 0x81, - 0x43, 0x9f, 0x60, 0x86, 0x51, 0x0f, 0x41, 0x97, 0x8b, 0x1e, 0x82, 0xa6, 0x47, 0x63, 0x9f, 0xec, - 0x13, 0x69, 0x8d, 0x50, 0x32, 0xf9, 0x32, 0x05, 0x62, 0x8e, 0xb3, 0x7f, 0x6d, 0x00, 0x4e, 0xe5, - 0x84, 0xb4, 0xd1, 0x83, 0xca, 0xb6, 0x93, 0x90, 0x9b, 0xce, 0x41, 0x36, 0xef, 0xed, 0x05, 0x0e, - 0xc6, 0x12, 0xcf, 0x7c, 0x39, 0x79, 0xea, 0xbc, 0x8c, 0x8d, 0x48, 0x64, 0xcc, 0x13, 0xd8, 0x7b, - 0xf5, 0x36, 0xf0, 0x79, 0x80, 0x38, 0xf6, 0x57, 0x02, 0xaa, 0x7c, 0xb9, 0xc2, 0x53, 0x34, 0xcd, - 0xb3, 0xd8, 0xb8, 0x2c, 0x30, 0x58, 0xa3, 0x42, 0x35, 0x98, 0x6a, 0x45, 0x61, 0xc2, 0xed, 0x6e, - 0x35, 0xee, 0xa3, 0x30, 0x68, 0x06, 0x27, 0xd5, 0x33, 0x78, 0xdc, 0x51, 0x02, 0xbd, 0x00, 0xa3, - 0x22, 0x60, 0xa9, 0x1e, 0x86, 0xbe, 0xb0, 0xd2, 0xa8, 0x1b, 0xef, 0x46, 0x8a, 0xc2, 0x3a, 0x9d, - 0x56, 0x8c, 0x19, 0xf3, 0x86, 0x73, 0x8b, 0x71, 0x83, 0x9e, 0x46, 0x97, 0xc9, 0xa1, 0x31, 0xd2, - 0x57, 0x0e, 0x8d, 0xd4, 0x6e, 0x55, 0xe9, 0xfb, 0xfe, 0x02, 0x7a, 0x5a, 0x7a, 0xbe, 0x55, 0x86, - 0x21, 0x3e, 0x14, 0x27, 0xa0, 0x8a, 0xad, 0x0a, 0xdb, 0x4d, 0x97, 0x8c, 0x02, 0xbc, 0x2d, 0xf3, - 0x35, 0x27, 0x71, 0xb8, 0x18, 0x52, 0xab, 0x21, 0xb5, 0xf2, 0xa0, 0x79, 0x63, 0xbd, 0xcc, 0x66, - 0x8c, 0x13, 0xc0, 0x79, 0x68, 0xab, 0xe7, 0x6d, 0x80, 0x98, 0xbd, 0x4f, 0x4b, 0x79, 0x88, 0xdc, - 0x14, 0x4f, 0x76, 0xa9, 0xbd, 0xa1, 0x88, 0x79, 0x1b, 0xd2, 0x29, 0xa8, 0x10, 0x58, 0xe3, 0x38, - 0xfb, 0x22, 0x54, 0x14, 0x71, 0xaf, 0x93, 0xdc, 0x98, 0x2e, 0xbc, 0x3e, 0x05, 0x93, 0x99, 0xba, - 0x8e, 0x75, 0x10, 0xfc, 0x4d, 0x0b, 0x26, 0x79, 0x93, 0x57, 0x82, 0x7d, 0xb1, 0xd8, 0xdf, 0x83, - 0x19, 0x3f, 0x67, 0xd1, 0x89, 0x11, 0xed, 0x7f, 0x91, 0xaa, 0x83, 0x5f, 0x1e, 0x16, 0xe7, 0xd6, - 0x41, 0x0f, 0xff, 0xfc, 0x65, 0x6d, 0xc7, 0x17, 0x1e, 0xc8, 0x63, 0x3c, 0xb7, 0x38, 0x87, 0x61, - 0x85, 0xb5, 0xbf, 0x6f, 0xc1, 0x34, 0x6f, 0xf9, 0x25, 0x72, 0xa0, 0x0e, 0x39, 0x1f, 0x64, 0xdb, - 0x45, 0xea, 0xf4, 0x52, 0x41, 0xea, 0x74, 0xfd, 0xd3, 0xca, 0x5d, 0x3f, 0xed, 0x57, 0x2d, 0x10, - 0x33, 0xf0, 0x04, 0xd4, 0xf9, 0x4f, 0x9b, 0xea, 0xfc, 0x6c, 0xf1, 0xa4, 0x2e, 0xd0, 0xe3, 0xff, - 0xcc, 0x82, 0x29, 0x4e, 0x90, 0x5e, 0x5e, 0x7c, 0xa0, 0xe3, 0xd0, 0xcf, 0x7b, 0x3e, 0xea, 0x01, - 0xd5, 0xfc, 0x8f, 0x32, 0x06, 0x6b, 0xa0, 0xeb, 0x60, 0xb9, 0x72, 0x01, 0x1d, 0xe3, 0x9d, 0xaa, - 0x63, 0x67, 0xfb, 0xb3, 0xff, 0xd8, 0x02, 0xc4, 0xab, 0xc9, 0x3e, 0x69, 0xce, 0xb7, 0x3e, 0xed, - 0x40, 0x9f, 0x8a, 0x1a, 0x85, 0xc1, 0x1a, 0xd5, 0x5d, 0xe9, 0x9e, 0xcc, 0x0d, 0x54, 0xb9, 0xf7, - 0x0d, 0xd4, 0x31, 0x7a, 0xf4, 0xaf, 0x0e, 0x40, 0xd6, 0xdd, 0x11, 0x5d, 0x87, 0xb1, 0xa6, 0xd3, - 0x72, 0x36, 0x3d, 0xdf, 0x4b, 0x3c, 0x12, 0x77, 0xbb, 0xba, 0x5e, 0xd6, 0xe8, 0xc4, 0x75, 0x8f, - 0x06, 0xc1, 0x06, 0x1f, 0x34, 0x0f, 0xd0, 0x8a, 0xbc, 0x7d, 0xcf, 0x27, 0xdb, 0xec, 0x44, 0xc3, - 0x62, 0x1e, 0xf8, 0x7d, 0xac, 0x84, 0x62, 0x8d, 0x22, 0xc7, 0x47, 0xbe, 0x7c, 0xef, 0x7c, 0xe4, - 0x07, 0x8e, 0xe9, 0x23, 0x3f, 0xd8, 0x97, 0x8f, 0x3c, 0x86, 0xfb, 0xe4, 0xde, 0x4d, 0xff, 0xaf, - 0x7a, 0x3e, 0x11, 0x0a, 0x1b, 0x8f, 0x84, 0x98, 0xbd, 0x7d, 0x38, 0x77, 0x1f, 0xce, 0xa5, 0xc0, - 0x05, 0x25, 0xd1, 0x67, 0xa1, 0xea, 0xf8, 0x7e, 0x78, 0x53, 0xf5, 0xda, 0x4a, 0xdc, 0x74, 0xfc, - 0x34, 0xf9, 0xed, 0xc8, 0xd2, 0x43, 0xb7, 0x0f, 0xe7, 0xaa, 0x8b, 0x05, 0x34, 0xb8, 0xb0, 0xb4, - 0xbd, 0x0b, 0xa7, 0x1a, 0x24, 0x92, 0x4f, 0xdf, 0xa9, 0x25, 0xb6, 0x01, 0x95, 0x28, 0x23, 0x54, - 0xfa, 0x0a, 0x97, 0xd7, 0x12, 0x93, 0x49, 0x21, 0x92, 0x32, 0xb2, 0xff, 0xd4, 0x82, 0x61, 0xe1, - 0x42, 0x79, 0x02, 0xba, 0xcc, 0xa2, 0x61, 0x56, 0x9a, 0xcb, 0x17, 0xbc, 0xac, 0x31, 0x85, 0x06, - 0xa5, 0xb5, 0x8c, 0x41, 0xe9, 0x91, 0x6e, 0x4c, 0xba, 0x9b, 0x92, 0x7e, 0xa1, 0x0c, 0x13, 0xa6, - 0xfb, 0xe8, 0x09, 0x74, 0xc1, 0x15, 0x18, 0x8e, 0x85, 0xaf, 0x72, 0xa9, 0xd8, 0xe7, 0x2d, 0x3b, - 0x88, 0xe9, 0xcd, 0xb8, 0xf0, 0x4e, 0x96, 0x4c, 0x72, 0x9d, 0xa0, 0xcb, 0xf7, 0xd0, 0x09, 0xba, - 0x97, 0x07, 0xef, 0xc0, 0xdd, 0xf0, 0xe0, 0xb5, 0xbf, 0xcd, 0x84, 0xbf, 0x0e, 0x3f, 0x01, 0xbd, - 0xe0, 0x82, 0xb9, 0x4d, 0xd8, 0x5d, 0x66, 0x96, 0x68, 0x54, 0x81, 0x7e, 0xf0, 0x8f, 0x2c, 0x18, - 0x15, 0x84, 0x27, 0xd0, 0xec, 0xcf, 0x98, 0xcd, 0x7e, 0xb0, 0x4b, 0xb3, 0x0b, 0xda, 0xfb, 0xb7, - 0x4b, 0xaa, 0xbd, 0xf5, 0x30, 0x4a, 0xfa, 0x4a, 0x86, 0x3e, 0x42, 0x4f, 0x83, 0x61, 0x33, 0xf4, - 0xc5, 0x66, 0xfe, 0x50, 0x1a, 0x0c, 0xc7, 0xe1, 0x47, 0xda, 0x6f, 0xac, 0xa8, 0x59, 0xac, 0x56, - 0x18, 0x25, 0x62, 0x03, 0x4d, 0x63, 0xb5, 0xc2, 0x28, 0xc1, 0x0c, 0x83, 0x5c, 0x80, 0xc4, 0x89, - 0xb6, 0x49, 0x42, 0x61, 0x22, 0x7a, 0xb4, 0x78, 0x15, 0xb6, 0x13, 0xcf, 0x9f, 0xf7, 0x82, 0x24, - 0x4e, 0xa2, 0xf9, 0xb5, 0x20, 0xb9, 0x1a, 0xf1, 0xb3, 0x81, 0x16, 0xdd, 0xa6, 0x78, 0x61, 0x8d, - 0xaf, 0x0c, 0xaf, 0x60, 0x75, 0x0c, 0x9a, 0xf7, 0x3d, 0x57, 0x04, 0x1c, 0x2b, 0x0a, 0xfb, 0x45, - 0x26, 0x93, 0x59, 0x07, 0x1d, 0x2f, 0xf0, 0xec, 0xbb, 0x23, 0xaa, 0x6b, 0x99, 0xb1, 0xb7, 0xa6, - 0x87, 0xb7, 0x75, 0x17, 0x81, 0xb4, 0x62, 0xdd, 0x95, 0x38, 0x8d, 0x81, 0x43, 0x9f, 0xeb, 0xb8, - 0x06, 0x7c, 0xa6, 0x87, 0x2c, 0x3d, 0xc6, 0xc5, 0x1f, 0xcb, 0xcc, 0xc7, 0x32, 0x98, 0xad, 0xd5, - 0xb3, 0xe9, 0xea, 0x97, 0x25, 0x02, 0xa7, 0x34, 0x68, 0x41, 0x9c, 0x2c, 0xb9, 0x99, 0xe5, 0xc1, - 0xcc, 0xc9, 0x52, 0x7e, 0xbe, 0x76, 0xb4, 0x7c, 0x16, 0x46, 0xd5, 0x13, 0x40, 0x75, 0xfe, 0x92, - 0x4a, 0x85, 0xeb, 0x52, 0x2b, 0x29, 0x18, 0xeb, 0x34, 0x68, 0x03, 0x26, 0x63, 0xfe, 0x3e, 0x91, - 0x8c, 0x78, 0x10, 0x76, 0x83, 0x27, 0xe5, 0xf5, 0x61, 0xc3, 0x44, 0x1f, 0x31, 0x10, 0x5f, 0xac, - 0x32, 0x46, 0x22, 0xcb, 0x02, 0xbd, 0x0a, 0x13, 0xbe, 0xfe, 0x4e, 0x6b, 0x5d, 0x98, 0x15, 0x94, - 0x2b, 0x97, 0xf1, 0x8a, 0x6b, 0x1d, 0x67, 0xa8, 0xa9, 0x12, 0xa0, 0x43, 0x44, 0x02, 0x1d, 0x27, - 0xd8, 0x26, 0xb1, 0x78, 0xc0, 0x84, 0x29, 0x01, 0x97, 0x0b, 0x68, 0x70, 0x61, 0x69, 0xf4, 0x12, - 0x8c, 0xc9, 0xcf, 0xd7, 0x22, 0x80, 0x52, 0x87, 0x41, 0x0d, 0x87, 0x0d, 0x4a, 0x74, 0x13, 0x4e, - 0xcb, 0xff, 0x1b, 0x91, 0xb3, 0xb5, 0xe5, 0x35, 0x45, 0x00, 0xd6, 0x28, 0x63, 0xb1, 0x28, 0xbd, - 0xa7, 0x57, 0xf2, 0x88, 0x8e, 0x0e, 0xe7, 0xce, 0x8a, 0x5e, 0xcb, 0xc5, 0xb3, 0x41, 0xcc, 0xe7, - 0x8f, 0xd6, 0xe1, 0xd4, 0x0e, 0x71, 0xfc, 0x64, 0x67, 0x79, 0x87, 0x34, 0x77, 0xe5, 0x22, 0x62, - 0x71, 0x45, 0x9a, 0x9b, 0xdd, 0xc5, 0x4e, 0x12, 0x9c, 0x57, 0x0e, 0xbd, 0x05, 0xd5, 0x56, 0x7b, - 0xd3, 0xf7, 0xe2, 0x9d, 0x2b, 0x61, 0xc2, 0x6e, 0x2c, 0xd5, 0x0b, 0x3a, 0x22, 0x00, 0x49, 0xc5, - 0x54, 0xd5, 0x0b, 0xe8, 0x70, 0x21, 0x07, 0xf4, 0x1e, 0x9c, 0xce, 0x4c, 0x06, 0xfe, 0x28, 0x93, - 0x08, 0x54, 0x7a, 0x22, 0x7f, 0x39, 0xe5, 0x14, 0xe0, 0x61, 0x71, 0xb9, 0x28, 0x9c, 0x5f, 0xc5, - 0xfb, 0xbb, 0xc7, 0x7e, 0x97, 0x16, 0xd6, 0xb4, 0x1b, 0xf4, 0x79, 0x18, 0xd3, 0x67, 0x91, 0xd8, - 0x60, 0x1e, 0xeb, 0xf5, 0x26, 0xb1, 0xd0, 0x8d, 0xd4, 0x8c, 0xd2, 0x71, 0xd8, 0xe0, 0x68, 0x13, - 0xc8, 0xff, 0x3e, 0x74, 0x19, 0x46, 0x9a, 0xbe, 0x47, 0x82, 0x64, 0xad, 0xde, 0x2d, 0x70, 0x76, - 0x59, 0xd0, 0x88, 0x0e, 0x13, 0xf9, 0x9b, 0x38, 0x0c, 0x2b, 0x0e, 0xf6, 0xef, 0x94, 0x60, 0xae, - 0x47, 0x0a, 0xaf, 0x8c, 0x0d, 0xd0, 0xea, 0xcb, 0x06, 0xb8, 0x28, 0xdf, 0x03, 0xba, 0x92, 0x39, - 0x7f, 0x66, 0xde, 0xfa, 0x49, 0x4f, 0xa1, 0x59, 0xfa, 0xbe, 0xdd, 0xdf, 0x74, 0x33, 0xe2, 0x40, - 0x4f, 0x2f, 0xc0, 0xba, 0x6e, 0x0f, 0x1e, 0xec, 0x5f, 0xa3, 0x2f, 0x34, 0x05, 0xdb, 0xdf, 0x2e, - 0xc1, 0x69, 0xd5, 0x85, 0x3f, 0xb9, 0x1d, 0x77, 0xad, 0xb3, 0xe3, 0xee, 0x82, 0x21, 0xdd, 0xbe, - 0x0a, 0x43, 0x8d, 0x83, 0xb8, 0x99, 0xf8, 0x7d, 0x28, 0x40, 0x8f, 0x1a, 0x0b, 0x34, 0xdd, 0xa6, - 0xd9, 0x93, 0x7e, 0x62, 0xbd, 0xda, 0x7f, 0xc9, 0x82, 0xc9, 0x8d, 0xe5, 0x7a, 0x23, 0x6c, 0xee, - 0x92, 0x64, 0x91, 0x9b, 0x89, 0xb0, 0xd0, 0x7f, 0xac, 0x3b, 0xd4, 0x6b, 0xf2, 0x34, 0xa6, 0xb3, - 0x30, 0xb0, 0x13, 0xc6, 0x49, 0xf6, 0xb2, 0xe4, 0x62, 0x18, 0x27, 0x98, 0x61, 0xec, 0x3f, 0xb4, - 0x60, 0x90, 0xbd, 0x62, 0xd7, 0xeb, 0xb5, 0xc3, 0x7e, 0xbe, 0x0b, 0xbd, 0x00, 0x43, 0x64, 0x6b, - 0x8b, 0x34, 0x13, 0x31, 0xaa, 0x32, 0x22, 0x67, 0x68, 0x85, 0x41, 0xe9, 0xa6, 0xcf, 0x2a, 0xe3, - 0x7f, 0xb1, 0x20, 0x46, 0x37, 0xa0, 0x92, 0x78, 0x7b, 0x64, 0xd1, 0x75, 0xc5, 0x3d, 0xc5, 0x1d, - 0x04, 0x40, 0x6d, 0x48, 0x06, 0x38, 0xe5, 0x65, 0xff, 0x7c, 0x09, 0x20, 0x8d, 0xda, 0xeb, 0xf5, - 0x89, 0x4b, 0x1d, 0x0f, 0x3a, 0x3e, 0x96, 0xf3, 0xa0, 0x23, 0x4a, 0x19, 0xe6, 0x3c, 0xe7, 0xa8, - 0xba, 0xa9, 0xdc, 0x57, 0x37, 0x0d, 0x1c, 0xa7, 0x9b, 0x96, 0x61, 0x3a, 0x8d, 0x3a, 0x34, 0x43, - 0xb0, 0x59, 0xaa, 0xde, 0x8d, 0x2c, 0x12, 0x77, 0xd2, 0xdb, 0x5f, 0xb6, 0x40, 0xb8, 0x28, 0xf7, - 0x31, 0x99, 0xdf, 0x94, 0x6f, 0xaf, 0x19, 0x39, 0x05, 0xcf, 0x16, 0xfb, 0x6c, 0x8b, 0x4c, 0x82, - 0x6a, 0xf3, 0x30, 0xf2, 0x07, 0x1a, 0xbc, 0xec, 0xbf, 0x5e, 0x82, 0x51, 0x8e, 0x66, 0xf9, 0xea, - 0xfa, 0x68, 0xcd, 0xb1, 0x12, 0x3d, 0xb3, 0x67, 0xc9, 0x28, 0x63, 0x95, 0x0f, 0x58, 0x7f, 0x96, - 0x4c, 0x22, 0x70, 0x4a, 0x83, 0x9e, 0x80, 0xe1, 0xb8, 0xbd, 0xc9, 0xc8, 0x33, 0x5e, 0xca, 0x0d, - 0x0e, 0xc6, 0x12, 0x8f, 0x3e, 0x0b, 0x53, 0xbc, 0x5c, 0x14, 0xb6, 0x9c, 0x6d, 0x6e, 0xdb, 0x19, - 0x54, 0x31, 0x2a, 0x53, 0xeb, 0x19, 0xdc, 0xd1, 0xe1, 0xdc, 0x4c, 0x16, 0xc6, 0xac, 0x82, 0x1d, - 0x5c, 0xe8, 0x8c, 0x9d, 0xca, 0xba, 0xbf, 0xa3, 0x8b, 0x30, 0xc4, 0x85, 0x91, 0x10, 0x0e, 0x5d, - 0xee, 0x7a, 0x34, 0xa7, 0x79, 0xe0, 0x2f, 0xf4, 0x33, 0x21, 0x26, 0xca, 0xa3, 0xb7, 0x60, 0xd4, - 0x0d, 0x6f, 0x06, 0x37, 0x9d, 0xc8, 0x5d, 0xac, 0xaf, 0x89, 0xf1, 0xcc, 0xd5, 0x69, 0x6a, 0x29, - 0x99, 0xee, 0x88, 0xcf, 0xec, 0x9a, 0x29, 0x0a, 0xeb, 0xec, 0xd0, 0x06, 0x4b, 0xb1, 0xc2, 0xdf, - 0x0d, 0xee, 0xe6, 0xd6, 0xa3, 0x9e, 0x1a, 0xd6, 0x38, 0x8f, 0x8b, 0x3c, 0x2c, 0xe2, 0xd5, 0xe1, - 0x94, 0x91, 0xfd, 0xc5, 0x53, 0x60, 0xcc, 0x23, 0x23, 0xd1, 0xb3, 0x75, 0x97, 0x12, 0x3d, 0x63, - 0x18, 0x21, 0x7b, 0xad, 0xe4, 0xa0, 0xe6, 0x45, 0xdd, 0x32, 0xfc, 0xaf, 0x08, 0x9a, 0x4e, 0x9e, - 0x12, 0x83, 0x15, 0x9f, 0xfc, 0x6c, 0xdc, 0xe5, 0x0f, 0x30, 0x1b, 0xf7, 0xc0, 0x09, 0x66, 0xe3, - 0xbe, 0x02, 0xc3, 0xdb, 0x5e, 0x82, 0x49, 0x2b, 0x14, 0x1b, 0x71, 0xee, 0x4c, 0xb8, 0xc0, 0x49, - 0x3a, 0x73, 0xc6, 0x0a, 0x04, 0x96, 0x4c, 0xd0, 0x6b, 0x6a, 0x0d, 0x0c, 0x15, 0xeb, 0xb1, 0x9d, - 0xd7, 0x02, 0xb9, 0xab, 0x40, 0x64, 0xdf, 0x1e, 0xbe, 0xd3, 0xec, 0xdb, 0x2a, 0x7b, 0xf6, 0xc8, - 0xfb, 0xcb, 0x9e, 0x6d, 0x64, 0x17, 0xaf, 0xdc, 0xbd, 0xec, 0xe2, 0x5f, 0xb6, 0xe0, 0x74, 0x2b, - 0x2f, 0xd1, 0xbe, 0xc8, 0x83, 0xfd, 0x42, 0xdf, 0x2f, 0x09, 0x18, 0x15, 0xb2, 0x03, 0x4d, 0x2e, - 0x19, 0xce, 0xaf, 0x4e, 0xa6, 0x29, 0x1f, 0xbd, 0xd3, 0x34, 0xe5, 0xf7, 0x26, 0x61, 0x76, 0x9a, - 0xb4, 0x7c, 0xfc, 0x7d, 0x27, 0x2d, 0x7f, 0x4d, 0x25, 0x2d, 0xef, 0x92, 0xc8, 0x82, 0xa7, 0x24, - 0xef, 0x99, 0xaa, 0x5c, 0x4b, 0x37, 0x3e, 0x79, 0x37, 0xd2, 0x8d, 0xbf, 0x6d, 0x0a, 0x7b, 0x9e, - 0xfb, 0xfa, 0xa9, 0x1e, 0xc2, 0xde, 0xe0, 0xdb, 0x5d, 0xdc, 0xf3, 0xd4, 0xea, 0xd3, 0x77, 0x94, - 0x5a, 0xfd, 0xba, 0x9e, 0xb4, 0x1c, 0xf5, 0xc8, 0xca, 0x4d, 0x89, 0xfa, 0x4c, 0x55, 0x7e, 0x5d, - 0xdf, 0x82, 0x4e, 0x15, 0xf3, 0x55, 0x3b, 0x4d, 0x27, 0xdf, 0xbc, 0x4d, 0xa8, 0x33, 0x05, 0xfa, - 0xcc, 0xc9, 0xa4, 0x40, 0x3f, 0x7d, 0xd7, 0x53, 0xa0, 0xdf, 0x77, 0x02, 0x29, 0xd0, 0xef, 0xff, - 0x40, 0x53, 0xa0, 0x57, 0xef, 0x6d, 0x0a, 0xf4, 0x07, 0xee, 0x46, 0x0a, 0xf4, 0xeb, 0x50, 0x69, - 0xc9, 0x08, 0xc9, 0xea, 0x6c, 0xf1, 0x90, 0xe4, 0x86, 0x51, 0xf2, 0x21, 0x51, 0x28, 0x9c, 0xb2, - 0xa2, 0x7c, 0xd3, 0x94, 0xe8, 0x0f, 0x76, 0x31, 0x59, 0xe5, 0x19, 0x03, 0xba, 0x24, 0x42, 0xff, - 0xcb, 0x25, 0x38, 0xd3, 0x7d, 0x5e, 0xa7, 0x96, 0x84, 0x7a, 0x6a, 0xf9, 0xce, 0x58, 0x12, 0x98, - 0xd2, 0xa5, 0x51, 0xf5, 0x1d, 0x46, 0x7e, 0x01, 0xa6, 0x95, 0xa3, 0x98, 0xef, 0x35, 0x0f, 0xb4, - 0xd7, 0x92, 0x54, 0xec, 0x41, 0x23, 0x4b, 0x80, 0x3b, 0xcb, 0xa0, 0x45, 0x98, 0x34, 0x80, 0x6b, - 0x35, 0xa1, 0xec, 0x2b, 0xd3, 0x45, 0xc3, 0x44, 0xe3, 0x2c, 0xbd, 0xfd, 0x0d, 0x0b, 0xee, 0x2f, - 0xc8, 0x8b, 0xda, 0x77, 0x94, 0xf4, 0x16, 0x4c, 0xb6, 0xcc, 0xa2, 0x3d, 0x92, 0x29, 0x18, 0xd9, - 0x57, 0x55, 0x5b, 0x33, 0x08, 0x9c, 0x65, 0xba, 0x74, 0xee, 0x3b, 0x3f, 0x38, 0xf3, 0x91, 0xdf, - 0xff, 0xc1, 0x99, 0x8f, 0x7c, 0xff, 0x07, 0x67, 0x3e, 0xf2, 0xff, 0xdf, 0x3e, 0x63, 0x7d, 0xe7, - 0xf6, 0x19, 0xeb, 0xf7, 0x6f, 0x9f, 0xb1, 0xbe, 0x7f, 0xfb, 0x8c, 0xf5, 0x47, 0xb7, 0xcf, 0x58, - 0x3f, 0xff, 0xc3, 0x33, 0x1f, 0x79, 0xb3, 0xb4, 0xff, 0xec, 0xff, 0x0b, 0x00, 0x00, 0xff, 0xff, - 0x4f, 0x1e, 0xcd, 0x7b, 0x5c, 0xd1, 0x00, 0x00, + // 12382 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7d, 0x6d, 0x90, 0x24, 0x47, + 0x56, 0xd8, 0x55, 0xf7, 0x7c, 0xf5, 0x9b, 0xef, 0xdc, 0x5d, 0xa9, 0x77, 0x24, 0x6d, 0xaf, 0x4a, + 0x77, 0xd2, 0xea, 0x6b, 0xe6, 0xb4, 0x92, 0x4e, 0xe2, 0x74, 0x27, 0x98, 0x99, 0x9e, 0xd9, 0x6d, + 0xed, 0xce, 0x6c, 0x2b, 0x7b, 0x76, 0xf7, 0x4e, 0x88, 0xf3, 0xd5, 0x74, 0xe7, 0xcc, 0x94, 0xa6, + 0xa6, 0xaa, 0x55, 0x55, 0x3d, 0xbb, 0xa3, 0x80, 0x08, 0x5b, 0x06, 0xfc, 0x01, 0x3f, 0x2e, 0x6c, + 0xc2, 0xc6, 0x40, 0xe0, 0x08, 0x1b, 0x07, 0x9c, 0xb1, 0x1d, 0xc6, 0x60, 0xc0, 0x80, 0x6d, 0x8c, + 0x1d, 0x0e, 0xf8, 0x83, 0xc1, 0xfe, 0x71, 0x44, 0x10, 0x1e, 0xc3, 0x40, 0xd8, 0xc1, 0x0f, 0x3b, + 0x6c, 0xf3, 0x8b, 0x31, 0x36, 0x8e, 0xfc, 0xac, 0xcc, 0xea, 0xaa, 0xee, 0x9e, 0xd5, 0xec, 0x48, + 0x5c, 0xdc, 0xbf, 0xee, 0x7c, 0x2f, 0x5f, 0x66, 0xe5, 0xc7, 0xcb, 0x97, 0x2f, 0xdf, 0x07, 0xbc, + 0xb1, 0xfb, 0x7a, 0x34, 0xef, 0x06, 0x0b, 0xbb, 0x9d, 0x4d, 0x12, 0xfa, 0x24, 0x26, 0xd1, 0xc2, + 0x3e, 0xf1, 0x5b, 0x41, 0xb8, 0x20, 0x00, 0x4e, 0xdb, 0x5d, 0x68, 0x06, 0x21, 0x59, 0xd8, 0x7f, + 0x69, 0x61, 0x9b, 0xf8, 0x24, 0x74, 0x62, 0xd2, 0x9a, 0x6f, 0x87, 0x41, 0x1c, 0x20, 0xc4, 0x71, + 0xe6, 0x9d, 0xb6, 0x3b, 0x4f, 0x71, 0xe6, 0xf7, 0x5f, 0x9a, 0x7b, 0x71, 0xdb, 0x8d, 0x77, 0x3a, + 0x9b, 0xf3, 0xcd, 0x60, 0x6f, 0x61, 0x3b, 0xd8, 0x0e, 0x16, 0x18, 0xea, 0x66, 0x67, 0x8b, 0xfd, + 0x63, 0x7f, 0xd8, 0x2f, 0x4e, 0x62, 0x6e, 0x2d, 0x69, 0x86, 0xdc, 0x8f, 0x89, 0x1f, 0xb9, 0x81, + 0x1f, 0xbd, 0xe8, 0xb4, 0xdd, 0x88, 0x84, 0xfb, 0x24, 0x5c, 0x68, 0xef, 0x6e, 0x53, 0x58, 0x64, + 0x22, 0x2c, 0xec, 0xbf, 0xb4, 0x49, 0x62, 0xa7, 0xab, 0x47, 0x73, 0xaf, 0x24, 0xe4, 0xf6, 0x9c, + 0xe6, 0x8e, 0xeb, 0x93, 0xf0, 0x40, 0xd2, 0x58, 0x08, 0x49, 0x14, 0x74, 0xc2, 0x26, 0x39, 0x51, + 0xad, 0x68, 0x61, 0x8f, 0xc4, 0x4e, 0xc6, 0xd7, 0xcf, 0x2d, 0xe4, 0xd5, 0x0a, 0x3b, 0x7e, 0xec, + 0xee, 0x75, 0x37, 0xf3, 0xb9, 0x7e, 0x15, 0xa2, 0xe6, 0x0e, 0xd9, 0x73, 0xba, 0xea, 0xbd, 0x9c, + 0x57, 0xaf, 0x13, 0xbb, 0xde, 0x82, 0xeb, 0xc7, 0x51, 0x1c, 0xa6, 0x2b, 0xd9, 0xdf, 0xb0, 0xe0, + 0xf2, 0xe2, 0xdd, 0xc6, 0x8a, 0xe7, 0x44, 0xb1, 0xdb, 0x5c, 0xf2, 0x82, 0xe6, 0x6e, 0x23, 0x0e, + 0x42, 0x72, 0x27, 0xf0, 0x3a, 0x7b, 0xa4, 0xc1, 0x06, 0x02, 0xbd, 0x00, 0x63, 0xfb, 0xec, 0x7f, + 0xad, 0x5a, 0xb6, 0x2e, 0x5b, 0x57, 0x4a, 0x4b, 0x33, 0xbf, 0x7e, 0x58, 0xf9, 0xd4, 0xd1, 0x61, + 0x65, 0xec, 0x8e, 0x28, 0xc7, 0x0a, 0x03, 0x3d, 0x0d, 0x23, 0x5b, 0xd1, 0xc6, 0x41, 0x9b, 0x94, + 0x0b, 0x0c, 0x77, 0x4a, 0xe0, 0x8e, 0xac, 0x36, 0x68, 0x29, 0x16, 0x50, 0xb4, 0x00, 0xa5, 0xb6, + 0x13, 0xc6, 0x6e, 0xec, 0x06, 0x7e, 0xb9, 0x78, 0xd9, 0xba, 0x32, 0xbc, 0x34, 0x2b, 0x50, 0x4b, + 0x75, 0x09, 0xc0, 0x09, 0x0e, 0xed, 0x46, 0x48, 0x9c, 0xd6, 0x2d, 0xdf, 0x3b, 0x28, 0x0f, 0x5d, + 0xb6, 0xae, 0x8c, 0x25, 0xdd, 0xc0, 0xa2, 0x1c, 0x2b, 0x0c, 0xfb, 0x47, 0x0a, 0x30, 0xb6, 0xb8, + 0xb5, 0xe5, 0xfa, 0x6e, 0x7c, 0x80, 0xee, 0xc0, 0x84, 0x1f, 0xb4, 0x88, 0xfc, 0xcf, 0xbe, 0x62, + 0xfc, 0xea, 0xe5, 0xf9, 0xee, 0x95, 0x39, 0xbf, 0xae, 0xe1, 0x2d, 0xcd, 0x1c, 0x1d, 0x56, 0x26, + 0xf4, 0x12, 0x6c, 0xd0, 0x41, 0x18, 0xc6, 0xdb, 0x41, 0x4b, 0x91, 0x2d, 0x30, 0xb2, 0x95, 0x2c, + 0xb2, 0xf5, 0x04, 0x6d, 0x69, 0xfa, 0xe8, 0xb0, 0x32, 0xae, 0x15, 0x60, 0x9d, 0x08, 0xda, 0x84, + 0x69, 0xfa, 0xd7, 0x8f, 0x5d, 0x45, 0xb7, 0xc8, 0xe8, 0x3e, 0x95, 0x47, 0x57, 0x43, 0x5d, 0x3a, + 0x77, 0x74, 0x58, 0x99, 0x4e, 0x15, 0xe2, 0x34, 0x41, 0xfb, 0x03, 0x98, 0x5a, 0x8c, 0x63, 0xa7, + 0xb9, 0x43, 0x5a, 0x7c, 0x06, 0xd1, 0x2b, 0x30, 0xe4, 0x3b, 0x7b, 0x44, 0xcc, 0xef, 0x65, 0x31, + 0xb0, 0x43, 0xeb, 0xce, 0x1e, 0x39, 0x3e, 0xac, 0xcc, 0xdc, 0xf6, 0xdd, 0xf7, 0x3b, 0x62, 0x55, + 0xd0, 0x32, 0xcc, 0xb0, 0xd1, 0x55, 0x80, 0x16, 0xd9, 0x77, 0x9b, 0xa4, 0xee, 0xc4, 0x3b, 0x62, + 0xbe, 0x91, 0xa8, 0x0b, 0x55, 0x05, 0xc1, 0x1a, 0x96, 0x7d, 0x1f, 0x4a, 0x8b, 0xfb, 0x81, 0xdb, + 0xaa, 0x07, 0xad, 0x08, 0xed, 0xc2, 0x74, 0x3b, 0x24, 0x5b, 0x24, 0x54, 0x45, 0x65, 0xeb, 0x72, + 0xf1, 0xca, 0xf8, 0xd5, 0x2b, 0x99, 0x1f, 0x6b, 0xa2, 0xae, 0xf8, 0x71, 0x78, 0xb0, 0xf4, 0xa8, + 0x68, 0x6f, 0x3a, 0x05, 0xc5, 0x69, 0xca, 0xf6, 0xbf, 0x2b, 0xc0, 0x85, 0xc5, 0x0f, 0x3a, 0x21, + 0xa9, 0xba, 0xd1, 0x6e, 0x7a, 0x85, 0xb7, 0xdc, 0x68, 0x77, 0x3d, 0x19, 0x01, 0xb5, 0xb4, 0xaa, + 0xa2, 0x1c, 0x2b, 0x0c, 0xf4, 0x22, 0x8c, 0xd2, 0xdf, 0xb7, 0x71, 0x4d, 0x7c, 0xf2, 0x39, 0x81, + 0x3c, 0x5e, 0x75, 0x62, 0xa7, 0xca, 0x41, 0x58, 0xe2, 0xa0, 0x35, 0x18, 0x6f, 0xb2, 0x0d, 0xb9, + 0xbd, 0x16, 0xb4, 0x08, 0x9b, 0xcc, 0xd2, 0xd2, 0xf3, 0x14, 0x7d, 0x39, 0x29, 0x3e, 0x3e, 0xac, + 0x94, 0x79, 0xdf, 0x04, 0x09, 0x0d, 0x86, 0xf5, 0xfa, 0xc8, 0x56, 0xfb, 0x6b, 0x88, 0x51, 0x82, + 0x8c, 0xbd, 0x75, 0x45, 0xdb, 0x2a, 0xc3, 0x6c, 0xab, 0x4c, 0x64, 0x6f, 0x13, 0xf4, 0x12, 0x0c, + 0xed, 0xba, 0x7e, 0xab, 0x3c, 0xc2, 0x68, 0x3d, 0x41, 0xe7, 0xfc, 0x86, 0xeb, 0xb7, 0x8e, 0x0f, + 0x2b, 0xb3, 0x46, 0x77, 0x68, 0x21, 0x66, 0xa8, 0xf6, 0x1f, 0x5b, 0x50, 0x61, 0xb0, 0x55, 0xd7, + 0x23, 0x75, 0x12, 0x46, 0x6e, 0x14, 0x13, 0x3f, 0x36, 0x06, 0xf4, 0x2a, 0x40, 0x44, 0x9a, 0x21, + 0x89, 0xb5, 0x21, 0x55, 0x0b, 0xa3, 0xa1, 0x20, 0x58, 0xc3, 0xa2, 0x0c, 0x21, 0xda, 0x71, 0x42, + 0xb6, 0xbe, 0xc4, 0xc0, 0x2a, 0x86, 0xd0, 0x90, 0x00, 0x9c, 0xe0, 0x18, 0x0c, 0xa1, 0xd8, 0x8f, + 0x21, 0xa0, 0x2f, 0xc2, 0x74, 0xd2, 0x58, 0xd4, 0x76, 0x9a, 0x72, 0x00, 0xd9, 0x96, 0x69, 0x98, + 0x20, 0x9c, 0xc6, 0xb5, 0xff, 0xa1, 0x25, 0x16, 0x0f, 0xfd, 0xea, 0x4f, 0xf8, 0xb7, 0xda, 0xbf, + 0x68, 0xc1, 0xe8, 0x92, 0xeb, 0xb7, 0x5c, 0x7f, 0x1b, 0x7d, 0x15, 0xc6, 0xe8, 0xd9, 0xd4, 0x72, + 0x62, 0x47, 0xf0, 0xbd, 0xcf, 0x6a, 0x7b, 0x4b, 0x1d, 0x15, 0xf3, 0xed, 0xdd, 0x6d, 0x5a, 0x10, + 0xcd, 0x53, 0x6c, 0xba, 0xdb, 0x6e, 0x6d, 0xbe, 0x47, 0x9a, 0xf1, 0x1a, 0x89, 0x9d, 0xe4, 0x73, + 0x92, 0x32, 0xac, 0xa8, 0xa2, 0x1b, 0x30, 0x12, 0x3b, 0xe1, 0x36, 0x89, 0x05, 0x03, 0xcc, 0x64, + 0x54, 0xbc, 0x26, 0xa6, 0x3b, 0x92, 0xf8, 0x4d, 0x92, 0x1c, 0x0b, 0x1b, 0xac, 0x2a, 0x16, 0x24, + 0xec, 0x9f, 0xb6, 0xe0, 0xe2, 0x72, 0xa3, 0x96, 0xb3, 0xae, 0x9e, 0x86, 0x91, 0x56, 0xe8, 0xee, + 0x93, 0x50, 0x8c, 0xb3, 0xa2, 0x52, 0x65, 0xa5, 0x58, 0x40, 0xd1, 0xeb, 0x30, 0xc1, 0x0f, 0xa4, + 0xeb, 0x8e, 0xdf, 0xf2, 0xe4, 0x10, 0x9f, 0x17, 0xd8, 0x13, 0x77, 0x34, 0x18, 0x36, 0x30, 0x4f, + 0x38, 0xd0, 0x4d, 0x98, 0x58, 0x76, 0xda, 0xce, 0xa6, 0xeb, 0xb9, 0xb1, 0x4b, 0x22, 0xf4, 0x0c, + 0x14, 0x9d, 0x56, 0x8b, 0xf1, 0xb0, 0xd2, 0xd2, 0x85, 0xa3, 0xc3, 0x4a, 0x71, 0xb1, 0x45, 0x37, + 0x13, 0x28, 0xac, 0x03, 0x4c, 0x31, 0xd0, 0x73, 0x30, 0xd4, 0x0a, 0x83, 0x76, 0xb9, 0xc0, 0x30, + 0x1f, 0xa1, 0xfb, 0xae, 0x1a, 0x06, 0xed, 0x14, 0x2a, 0xc3, 0xb1, 0x7f, 0xb5, 0x00, 0x8f, 0x2f, + 0x93, 0xf6, 0xce, 0x6a, 0x23, 0x67, 0x54, 0xae, 0xc0, 0xd8, 0x5e, 0xe0, 0xbb, 0x71, 0x10, 0x46, + 0xa2, 0x69, 0xb6, 0xdd, 0xd7, 0x44, 0x19, 0x56, 0x50, 0x74, 0x19, 0x86, 0xda, 0x09, 0xab, 0x9e, + 0x90, 0x6c, 0x9e, 0x31, 0x69, 0x06, 0xa1, 0x18, 0x9d, 0x88, 0x84, 0x82, 0x4d, 0x29, 0x8c, 0xdb, + 0x11, 0x09, 0x31, 0x83, 0x24, 0xeb, 0x9d, 0xee, 0x04, 0xb1, 0x87, 0x52, 0xeb, 0x9d, 0x42, 0xb0, + 0x86, 0x85, 0xea, 0x50, 0xe2, 0xff, 0x30, 0xd9, 0x62, 0x1c, 0x29, 0x67, 0x95, 0x34, 0x24, 0x92, + 0x58, 0x25, 0x93, 0x6c, 0x43, 0xc8, 0x42, 0x9c, 0x10, 0x31, 0xe6, 0x69, 0xa4, 0xef, 0x3c, 0xfd, + 0x72, 0x01, 0x10, 0x1f, 0xc2, 0x3f, 0x67, 0x03, 0x77, 0xbb, 0x7b, 0xe0, 0x32, 0x8f, 0xc6, 0x9b, + 0x41, 0xd3, 0xf1, 0xd2, 0x7b, 0xec, 0xb4, 0x46, 0xef, 0x87, 0x2d, 0x40, 0xcb, 0xae, 0xdf, 0x22, + 0xe1, 0x19, 0xc8, 0x85, 0x27, 0xdb, 0x80, 0x37, 0x61, 0x6a, 0xd9, 0x73, 0x89, 0x1f, 0xd7, 0xea, + 0xcb, 0x81, 0xbf, 0xe5, 0x6e, 0xa3, 0xcf, 0xc3, 0x14, 0x15, 0x93, 0x83, 0x4e, 0xdc, 0x20, 0xcd, + 0xc0, 0x67, 0x12, 0x05, 0x15, 0x2e, 0xd1, 0xd1, 0x61, 0x65, 0x6a, 0xc3, 0x80, 0xe0, 0x14, 0xa6, + 0xfd, 0xbb, 0xf4, 0x43, 0x83, 0xbd, 0x76, 0xe0, 0x13, 0x3f, 0x5e, 0x0e, 0xfc, 0x16, 0x97, 0x3c, + 0x3f, 0x0f, 0x43, 0x31, 0xed, 0x38, 0xff, 0xc8, 0xa7, 0xe5, 0xd4, 0xd2, 0xee, 0x1e, 0x1f, 0x56, + 0x1e, 0xe9, 0xae, 0xc1, 0x3e, 0x88, 0xd5, 0x41, 0xdf, 0x06, 0x23, 0x51, 0xec, 0xc4, 0x9d, 0x48, + 0x7c, 0xf6, 0x93, 0xf2, 0xb3, 0x1b, 0xac, 0xf4, 0xf8, 0xb0, 0x32, 0xad, 0xaa, 0xf1, 0x22, 0x2c, + 0x2a, 0xa0, 0x67, 0x61, 0x74, 0x8f, 0x44, 0x91, 0xb3, 0x2d, 0x85, 0x86, 0x69, 0x51, 0x77, 0x74, + 0x8d, 0x17, 0x63, 0x09, 0x47, 0x4f, 0xc1, 0x30, 0x09, 0xc3, 0x20, 0x14, 0xab, 0x6a, 0x52, 0x20, + 0x0e, 0xaf, 0xd0, 0x42, 0xcc, 0x61, 0xf6, 0x7f, 0xb0, 0x60, 0x5a, 0xf5, 0x95, 0xb7, 0x75, 0x06, + 0xa7, 0xc3, 0x3b, 0x00, 0x4d, 0xf9, 0x81, 0x11, 0xe3, 0x77, 0xe3, 0x57, 0x9f, 0xce, 0x5a, 0xc2, + 0xdd, 0xc3, 0x98, 0x50, 0x56, 0x45, 0x11, 0xd6, 0xa8, 0xd9, 0xff, 0xd2, 0x82, 0x73, 0xa9, 0x2f, + 0xba, 0xe9, 0x46, 0x31, 0x7a, 0xb7, 0xeb, 0xab, 0xe6, 0x07, 0xfb, 0x2a, 0x5a, 0x9b, 0x7d, 0x93, + 0x5a, 0x73, 0xb2, 0x44, 0xfb, 0xa2, 0xeb, 0x30, 0xec, 0xc6, 0x64, 0x4f, 0x7e, 0xcc, 0x53, 0x3d, + 0x3f, 0x86, 0xf7, 0x2a, 0x99, 0x91, 0x1a, 0xad, 0x89, 0x39, 0x01, 0xfb, 0x7f, 0x59, 0x50, 0xe2, + 0xcb, 0x76, 0xcd, 0x69, 0x9f, 0xc1, 0x5c, 0xd4, 0x60, 0x88, 0x51, 0xe7, 0x1d, 0x7f, 0x26, 0xbb, + 0xe3, 0xa2, 0x3b, 0xf3, 0x54, 0xf4, 0xe3, 0x22, 0xb6, 0x62, 0x66, 0xb4, 0x08, 0x33, 0x12, 0x73, + 0xaf, 0x41, 0x49, 0x21, 0xa0, 0x19, 0x28, 0xee, 0x12, 0x7e, 0xad, 0x2a, 0x61, 0xfa, 0x13, 0x9d, + 0x87, 0xe1, 0x7d, 0xc7, 0xeb, 0x88, 0xcd, 0x8e, 0xf9, 0x9f, 0xcf, 0x17, 0x5e, 0xb7, 0xec, 0x5f, + 0x62, 0x7b, 0x4c, 0x34, 0xb2, 0xe2, 0xef, 0x0b, 0x66, 0xf2, 0x01, 0x9c, 0xf7, 0x32, 0x78, 0x98, + 0x18, 0x88, 0xc1, 0x79, 0xde, 0xe3, 0xa2, 0xaf, 0xe7, 0xb3, 0xa0, 0x38, 0xb3, 0x0d, 0x7a, 0x0c, + 0x04, 0x6d, 0xba, 0xa2, 0x1c, 0x8f, 0xf5, 0x57, 0x88, 0xcb, 0xb7, 0x44, 0x19, 0x56, 0x50, 0xca, + 0x20, 0xce, 0xab, 0xce, 0xdf, 0x20, 0x07, 0x0d, 0xe2, 0x91, 0x66, 0x1c, 0x84, 0x1f, 0x6b, 0xf7, + 0x9f, 0xe0, 0xa3, 0xcf, 0xf9, 0xcb, 0xb8, 0x20, 0x50, 0xbc, 0x41, 0x0e, 0xf8, 0x54, 0xe8, 0x5f, + 0x57, 0xec, 0xf9, 0x75, 0x3f, 0x63, 0xc1, 0xa4, 0xfa, 0xba, 0x33, 0xd8, 0x48, 0x4b, 0xe6, 0x46, + 0x7a, 0xa2, 0xe7, 0x7a, 0xcc, 0xd9, 0x42, 0x7f, 0xc6, 0x58, 0x80, 0xc0, 0xa9, 0x87, 0x01, 0x1d, + 0x1a, 0xca, 0xb3, 0x3f, 0xce, 0x09, 0x19, 0xe4, 0xbb, 0x6e, 0x90, 0x83, 0x8d, 0x80, 0x8a, 0x0f, + 0xd9, 0xdf, 0x65, 0xcc, 0xda, 0x50, 0xcf, 0x59, 0xfb, 0xb9, 0x02, 0x5c, 0x50, 0x23, 0x60, 0x1c, + 0xd0, 0x7f, 0xde, 0xc7, 0xe0, 0x25, 0x18, 0x6f, 0x91, 0x2d, 0xa7, 0xe3, 0xc5, 0xea, 0xe6, 0x3c, + 0xcc, 0xb5, 0x27, 0xd5, 0xa4, 0x18, 0xeb, 0x38, 0x27, 0x18, 0xb6, 0x9f, 0x18, 0x67, 0xbc, 0x37, + 0x76, 0xe8, 0x0a, 0xa6, 0xd2, 0x9b, 0xa6, 0xff, 0x98, 0xd0, 0xf5, 0x1f, 0x42, 0xd7, 0xf1, 0x14, + 0x0c, 0xbb, 0x7b, 0xf4, 0x2c, 0x2e, 0x98, 0x47, 0x6c, 0x8d, 0x16, 0x62, 0x0e, 0x43, 0x9f, 0x81, + 0xd1, 0x66, 0xb0, 0xb7, 0xe7, 0xf8, 0xad, 0x72, 0x91, 0xc9, 0x93, 0xe3, 0xf4, 0xb8, 0x5e, 0xe6, + 0x45, 0x58, 0xc2, 0xd0, 0xe3, 0x30, 0xe4, 0x84, 0xdb, 0x51, 0x79, 0x88, 0xe1, 0x8c, 0xd1, 0x96, + 0x16, 0xc3, 0xed, 0x08, 0xb3, 0x52, 0x2a, 0x27, 0xde, 0x0b, 0xc2, 0x5d, 0xd7, 0xdf, 0xae, 0xba, + 0x21, 0x13, 0xfa, 0x34, 0x39, 0xf1, 0xae, 0x82, 0x60, 0x0d, 0x0b, 0xad, 0xc2, 0x70, 0x3b, 0x08, + 0xe3, 0xa8, 0x3c, 0xc2, 0x86, 0xfb, 0xc9, 0x9c, 0xad, 0xc4, 0xbf, 0xb6, 0x1e, 0x84, 0x71, 0xf2, + 0x01, 0xf4, 0x5f, 0x84, 0x79, 0x75, 0xf4, 0x6d, 0x50, 0x24, 0xfe, 0x7e, 0x79, 0x94, 0x51, 0x99, + 0xcb, 0xa2, 0xb2, 0xe2, 0xef, 0xdf, 0x71, 0xc2, 0x84, 0xcf, 0xac, 0xf8, 0xfb, 0x98, 0xd6, 0x41, + 0x5f, 0x86, 0x92, 0xd4, 0x9d, 0x46, 0xe5, 0xb1, 0xfc, 0x25, 0x86, 0x05, 0x12, 0x26, 0xef, 0x77, + 0xdc, 0x90, 0xec, 0x11, 0x3f, 0x8e, 0x92, 0xdb, 0xaf, 0x84, 0x46, 0x38, 0xa1, 0x86, 0xbe, 0x2c, + 0xaf, 0x73, 0x6b, 0x41, 0xc7, 0x8f, 0xa3, 0x72, 0x89, 0x75, 0x2f, 0x53, 0xd1, 0x76, 0x27, 0xc1, + 0x4b, 0xdf, 0xf7, 0x78, 0x65, 0x6c, 0x90, 0x42, 0x18, 0x26, 0x3d, 0x77, 0x9f, 0xf8, 0x24, 0x8a, + 0xea, 0x61, 0xb0, 0x49, 0xca, 0xc0, 0x7a, 0x7e, 0x31, 0x5b, 0xff, 0x14, 0x6c, 0x92, 0xa5, 0xd9, + 0xa3, 0xc3, 0xca, 0xe4, 0x4d, 0xbd, 0x0e, 0x36, 0x49, 0xa0, 0xdb, 0x30, 0x45, 0x05, 0x54, 0x37, + 0x21, 0x3a, 0xde, 0x8f, 0x28, 0x93, 0x4e, 0xb1, 0x51, 0x09, 0xa7, 0x88, 0xa0, 0xb7, 0xa0, 0xe4, + 0xb9, 0x5b, 0xa4, 0x79, 0xd0, 0xf4, 0x48, 0x79, 0x82, 0x51, 0xcc, 0xdc, 0x56, 0x37, 0x25, 0x12, + 0xbf, 0x00, 0xa8, 0xbf, 0x38, 0xa9, 0x8e, 0xee, 0xc0, 0x23, 0x31, 0x09, 0xf7, 0x5c, 0xdf, 0xa1, + 0xdb, 0x41, 0xc8, 0x93, 0x4c, 0x8b, 0x37, 0xc9, 0xd6, 0xdb, 0x25, 0x31, 0x74, 0x8f, 0x6c, 0x64, + 0x62, 0xe1, 0x9c, 0xda, 0xe8, 0x16, 0x4c, 0xb3, 0x9d, 0x50, 0xef, 0x78, 0x5e, 0x3d, 0xf0, 0xdc, + 0xe6, 0x41, 0x79, 0x8a, 0x11, 0xfc, 0x8c, 0x54, 0xd3, 0xd5, 0x4c, 0x30, 0xbd, 0xf1, 0x26, 0xff, + 0x70, 0xba, 0x36, 0xda, 0x64, 0x6a, 0x9b, 0x4e, 0xe8, 0xc6, 0x07, 0x74, 0xfd, 0x92, 0xfb, 0x71, + 0x79, 0xba, 0xe7, 0xfd, 0x51, 0x47, 0x55, 0xba, 0x1d, 0xbd, 0x10, 0xa7, 0x09, 0xd2, 0xad, 0x1d, + 0xc5, 0x2d, 0xd7, 0x2f, 0xcf, 0x30, 0x8e, 0xa1, 0x76, 0x46, 0x83, 0x16, 0x62, 0x0e, 0x63, 0x2a, + 0x1b, 0xfa, 0xe3, 0x16, 0xe5, 0xa0, 0xb3, 0x0c, 0x31, 0x51, 0xd9, 0x48, 0x00, 0x4e, 0x70, 0xe8, + 0xb1, 0x1c, 0xc7, 0x07, 0x65, 0xc4, 0x50, 0xd5, 0x76, 0xd9, 0xd8, 0xf8, 0x32, 0xa6, 0xe5, 0xe8, + 0x26, 0x8c, 0x12, 0x7f, 0x7f, 0x35, 0x0c, 0xf6, 0xca, 0xe7, 0xf2, 0xf7, 0xec, 0x0a, 0x47, 0xe1, + 0x0c, 0x3d, 0xb9, 0x00, 0x88, 0x62, 0x2c, 0x49, 0xa0, 0xfb, 0x50, 0xce, 0x98, 0x11, 0x3e, 0x01, + 0xe7, 0xd9, 0x04, 0x7c, 0x41, 0xd4, 0x2d, 0x6f, 0xe4, 0xe0, 0x1d, 0xf7, 0x80, 0xe1, 0x5c, 0xea, + 0xe8, 0xbb, 0x60, 0x92, 0x6f, 0x28, 0xae, 0xef, 0x8d, 0xca, 0x17, 0xd8, 0xd7, 0x5c, 0xce, 0xdf, + 0x9c, 0x1c, 0x71, 0xe9, 0x82, 0xe8, 0xd0, 0xa4, 0x5e, 0x1a, 0x61, 0x93, 0x9a, 0xbd, 0x09, 0x53, + 0x8a, 0x6f, 0xb1, 0xa5, 0x83, 0x2a, 0x30, 0x4c, 0x19, 0xb2, 0xbc, 0xb1, 0x97, 0xe8, 0x4c, 0x31, + 0x3d, 0x1d, 0xe6, 0xe5, 0x6c, 0xa6, 0xdc, 0x0f, 0xc8, 0xd2, 0x41, 0x4c, 0xf8, 0xad, 0xab, 0xa8, + 0xcd, 0x94, 0x04, 0xe0, 0x04, 0xc7, 0xfe, 0x7f, 0x5c, 0xee, 0x49, 0x98, 0xe3, 0x00, 0xc7, 0xc1, + 0x0b, 0x30, 0xb6, 0x13, 0x44, 0x31, 0xc5, 0x66, 0x6d, 0x0c, 0x27, 0x92, 0xce, 0x75, 0x51, 0x8e, + 0x15, 0x06, 0x7a, 0x03, 0x26, 0x9b, 0x7a, 0x03, 0xe2, 0x2c, 0x53, 0x43, 0x60, 0xb4, 0x8e, 0x4d, + 0x5c, 0xf4, 0x3a, 0x8c, 0xb1, 0xd7, 0x9a, 0x66, 0xe0, 0x89, 0xfb, 0x9d, 0x3c, 0x90, 0xc7, 0xea, + 0xa2, 0xfc, 0x58, 0xfb, 0x8d, 0x15, 0x36, 0xbd, 0x73, 0xd3, 0x2e, 0xd4, 0xea, 0xe2, 0x14, 0x51, + 0x77, 0xee, 0xeb, 0xac, 0x14, 0x0b, 0xa8, 0xfd, 0x37, 0x0a, 0xda, 0x28, 0xd3, 0x1b, 0x0b, 0x41, + 0x75, 0x18, 0xbd, 0xe7, 0xb8, 0xb1, 0xeb, 0x6f, 0x0b, 0x71, 0xe1, 0xd9, 0x9e, 0x47, 0x0a, 0xab, + 0x74, 0x97, 0x57, 0xe0, 0x87, 0x9e, 0xf8, 0x83, 0x25, 0x19, 0x4a, 0x31, 0xec, 0xf8, 0x3e, 0xa5, + 0x58, 0x18, 0x94, 0x22, 0xe6, 0x15, 0x38, 0x45, 0xf1, 0x07, 0x4b, 0x32, 0xe8, 0x5d, 0x00, 0xb9, + 0x2c, 0x49, 0x4b, 0xbc, 0x92, 0xbc, 0xd0, 0x9f, 0xe8, 0x86, 0xaa, 0xb3, 0x34, 0x45, 0x8f, 0xd4, + 0xe4, 0x3f, 0xd6, 0xe8, 0xd9, 0x31, 0x13, 0xab, 0xba, 0x3b, 0x83, 0xbe, 0x93, 0x72, 0x02, 0x27, + 0x8c, 0x49, 0x6b, 0x31, 0x16, 0x83, 0xf3, 0xdc, 0x60, 0x52, 0xf1, 0x86, 0xbb, 0x47, 0x74, 0xae, + 0x21, 0x88, 0xe0, 0x84, 0x9e, 0xfd, 0x0b, 0x45, 0x28, 0xe7, 0x75, 0x97, 0x2e, 0x3a, 0x72, 0xdf, + 0x8d, 0x97, 0xa9, 0x34, 0x64, 0x99, 0x8b, 0x6e, 0x45, 0x94, 0x63, 0x85, 0x41, 0x67, 0x3f, 0x72, + 0xb7, 0xe5, 0xa5, 0x66, 0x38, 0x99, 0xfd, 0x06, 0x2b, 0xc5, 0x02, 0x4a, 0xf1, 0x42, 0xe2, 0x44, + 0xe2, 0x19, 0x4e, 0x5b, 0x25, 0x98, 0x95, 0x62, 0x01, 0xd5, 0xf5, 0x11, 0x43, 0x7d, 0xf4, 0x11, + 0xc6, 0x10, 0x0d, 0x9f, 0xee, 0x10, 0xa1, 0xaf, 0x00, 0x6c, 0xb9, 0xbe, 0x1b, 0xed, 0x30, 0xea, + 0x23, 0x27, 0xa6, 0xae, 0x64, 0xa9, 0x55, 0x45, 0x05, 0x6b, 0x14, 0xd1, 0xab, 0x30, 0xae, 0x36, + 0x60, 0xad, 0x5a, 0x1e, 0x35, 0xdf, 0x78, 0x12, 0x6e, 0x54, 0xc5, 0x3a, 0x9e, 0xfd, 0x5e, 0x7a, + 0xbd, 0x88, 0x1d, 0xa0, 0x8d, 0xaf, 0x35, 0xe8, 0xf8, 0x16, 0x7a, 0x8f, 0xaf, 0xfd, 0x6b, 0x45, + 0x98, 0x36, 0x1a, 0xeb, 0x44, 0x03, 0xf0, 0xac, 0x6b, 0xf4, 0x9c, 0x73, 0x62, 0x22, 0xf6, 0x9f, + 0xdd, 0x7f, 0xab, 0xe8, 0x67, 0x21, 0xdd, 0x01, 0xbc, 0x3e, 0xfa, 0x0a, 0x94, 0x3c, 0x27, 0x62, + 0xba, 0x0d, 0x22, 0xf6, 0xdd, 0x20, 0xc4, 0x92, 0x7b, 0x84, 0x13, 0xc5, 0xda, 0x51, 0xc3, 0x69, + 0x27, 0x24, 0xe9, 0x81, 0x4c, 0x65, 0x1f, 0xf9, 0xce, 0xab, 0x3a, 0x41, 0x05, 0xa4, 0x03, 0xcc, + 0x61, 0xe8, 0x75, 0x98, 0x08, 0x09, 0x5b, 0x15, 0xcb, 0x54, 0x94, 0x63, 0xcb, 0x6c, 0x38, 0x91, + 0xf9, 0xb0, 0x06, 0xc3, 0x06, 0x66, 0x22, 0xca, 0x8f, 0xf4, 0x10, 0xe5, 0x9f, 0x85, 0x51, 0xf6, + 0x43, 0xad, 0x00, 0x35, 0x1b, 0x35, 0x5e, 0x8c, 0x25, 0x3c, 0xbd, 0x60, 0xc6, 0x06, 0x5c, 0x30, + 0xcf, 0xc1, 0x54, 0xd5, 0x21, 0x7b, 0x81, 0xbf, 0xe2, 0xb7, 0xda, 0x81, 0xeb, 0xc7, 0xa8, 0x0c, + 0x43, 0xec, 0x74, 0xe0, 0x7b, 0x7b, 0x88, 0x52, 0xc0, 0x43, 0x54, 0x30, 0xb7, 0x7f, 0xbb, 0x00, + 0x93, 0x55, 0xe2, 0x91, 0x98, 0xf0, 0xab, 0x4c, 0x84, 0x56, 0x01, 0x6d, 0x87, 0x4e, 0x93, 0xd4, + 0x49, 0xe8, 0x06, 0x2d, 0x5d, 0xd7, 0x59, 0x64, 0xef, 0x09, 0xe8, 0x5a, 0x17, 0x14, 0x67, 0xd4, + 0x40, 0xef, 0xc0, 0x64, 0x3b, 0x24, 0x86, 0x8a, 0xce, 0xca, 0x93, 0x46, 0xea, 0x3a, 0x22, 0x17, + 0x84, 0x8d, 0x22, 0x6c, 0x92, 0x42, 0xdf, 0x01, 0x33, 0x41, 0xd8, 0xde, 0x71, 0xfc, 0x2a, 0x69, + 0x13, 0xbf, 0x45, 0x25, 0x7d, 0xa1, 0x82, 0x38, 0x7f, 0x74, 0x58, 0x99, 0xb9, 0x95, 0x82, 0xe1, + 0x2e, 0x6c, 0xf4, 0x0e, 0xcc, 0xb6, 0xc3, 0xa0, 0xed, 0x6c, 0xb3, 0x85, 0x22, 0x04, 0x1a, 0xce, + 0x7d, 0x5e, 0x38, 0x3a, 0xac, 0xcc, 0xd6, 0xd3, 0xc0, 0xe3, 0xc3, 0xca, 0x39, 0x36, 0x50, 0xb4, + 0x24, 0x01, 0xe2, 0x6e, 0x32, 0xf6, 0x36, 0x5c, 0xa8, 0x06, 0xf7, 0xfc, 0x7b, 0x4e, 0xd8, 0x5a, + 0xac, 0xd7, 0x34, 0xdd, 0xc1, 0xba, 0xbc, 0xbb, 0xf2, 0xb7, 0xe8, 0xcc, 0x73, 0x4a, 0xab, 0xc9, + 0xe5, 0x97, 0x55, 0xd7, 0x23, 0x39, 0x3a, 0x8a, 0xbf, 0x5d, 0x30, 0x5a, 0x4a, 0xf0, 0xd5, 0xb3, + 0x82, 0x95, 0xfb, 0xac, 0xf0, 0x36, 0x8c, 0x6d, 0xb9, 0xc4, 0x6b, 0x61, 0xb2, 0x25, 0x66, 0xe6, + 0x99, 0xfc, 0xe7, 0xb5, 0x55, 0x8a, 0x29, 0x75, 0x52, 0xfc, 0xe6, 0xbb, 0x2a, 0x2a, 0x63, 0x45, + 0x06, 0xed, 0xc2, 0x8c, 0xbc, 0x5a, 0x49, 0xa8, 0xd8, 0xc4, 0xcf, 0xf6, 0xba, 0xaf, 0x99, 0xc4, + 0xd9, 0x04, 0xe2, 0x14, 0x19, 0xdc, 0x45, 0x98, 0x5e, 0x75, 0xf7, 0xe8, 0x71, 0x35, 0xc4, 0x96, + 0x34, 0xbb, 0xea, 0xb2, 0x5b, 0x3b, 0x2b, 0xb5, 0x7f, 0xcc, 0x82, 0x47, 0xbb, 0x46, 0x46, 0x68, + 0x2f, 0x4e, 0x79, 0x16, 0xd2, 0xda, 0x84, 0x42, 0x7f, 0x6d, 0x82, 0xfd, 0x8f, 0x2c, 0x38, 0xbf, + 0xb2, 0xd7, 0x8e, 0x0f, 0xaa, 0xae, 0xf9, 0xf4, 0xf1, 0x1a, 0x8c, 0xec, 0x91, 0x96, 0xdb, 0xd9, + 0x13, 0x33, 0x57, 0x91, 0x2c, 0x7d, 0x8d, 0x95, 0x1e, 0x1f, 0x56, 0x26, 0x1b, 0x71, 0x10, 0x3a, + 0xdb, 0x84, 0x17, 0x60, 0x81, 0xce, 0x0e, 0x46, 0xf7, 0x03, 0x72, 0xd3, 0xdd, 0x73, 0xe5, 0x73, + 0x69, 0x4f, 0x8d, 0xda, 0xbc, 0x1c, 0xd0, 0xf9, 0xb7, 0x3b, 0x8e, 0x1f, 0xbb, 0xf1, 0x81, 0x78, + 0xd5, 0x91, 0x44, 0x70, 0x42, 0xcf, 0xfe, 0x86, 0x05, 0xd3, 0x92, 0x97, 0x2c, 0xb6, 0x5a, 0x21, + 0x89, 0x22, 0x34, 0x07, 0x05, 0xb7, 0x2d, 0x7a, 0x09, 0xa2, 0x97, 0x85, 0x5a, 0x1d, 0x17, 0xdc, + 0x36, 0xaa, 0x43, 0x89, 0xbf, 0xba, 0x26, 0x8b, 0x6b, 0xa0, 0xb7, 0x5b, 0xd6, 0x83, 0x0d, 0x59, + 0x13, 0x27, 0x44, 0xa4, 0x54, 0xcc, 0xce, 0xa1, 0xa2, 0xf9, 0x24, 0x74, 0x5d, 0x94, 0x63, 0x85, + 0x81, 0xae, 0xc0, 0x98, 0x1f, 0xb4, 0xf8, 0x23, 0x38, 0xdf, 0xd3, 0x6c, 0xc9, 0xae, 0x8b, 0x32, + 0xac, 0xa0, 0xf6, 0x0f, 0x5a, 0x30, 0x21, 0xbf, 0x6c, 0x40, 0x01, 0x9d, 0x6e, 0xad, 0x44, 0x38, + 0x4f, 0xb6, 0x16, 0x15, 0xb0, 0x19, 0xc4, 0x90, 0xab, 0x8b, 0x27, 0x91, 0xab, 0xed, 0x1f, 0x2d, + 0xc0, 0x94, 0xec, 0x4e, 0xa3, 0xb3, 0x19, 0x91, 0x18, 0x6d, 0x40, 0xc9, 0xe1, 0x43, 0x4e, 0xe4, + 0x8a, 0x7d, 0x2a, 0xfb, 0x42, 0x67, 0xcc, 0x4f, 0x22, 0xea, 0x2c, 0xca, 0xda, 0x38, 0x21, 0x84, + 0x3c, 0x98, 0xf5, 0x83, 0x98, 0x1d, 0x7b, 0x0a, 0xde, 0xeb, 0xd9, 0x21, 0x4d, 0xfd, 0xa2, 0xa0, + 0x3e, 0xbb, 0x9e, 0xa6, 0x82, 0xbb, 0x09, 0xa3, 0x15, 0xa9, 0x44, 0x2a, 0xe6, 0x5f, 0xe1, 0xf4, + 0x59, 0xc8, 0xd6, 0x21, 0xd9, 0xbf, 0x62, 0x41, 0x49, 0xa2, 0x9d, 0xc5, 0x0b, 0xd3, 0x1a, 0x8c, + 0x46, 0x6c, 0x12, 0xe4, 0xd0, 0xd8, 0xbd, 0x3a, 0xce, 0xe7, 0x2b, 0x39, 0xcd, 0xf9, 0xff, 0x08, + 0x4b, 0x1a, 0x4c, 0x0b, 0xae, 0xba, 0xff, 0x09, 0xd1, 0x82, 0xab, 0xfe, 0xe4, 0x9c, 0x30, 0xff, + 0x8d, 0xf5, 0x59, 0x53, 0x15, 0x50, 0xa1, 0xb3, 0x1d, 0x92, 0x2d, 0xf7, 0x7e, 0x5a, 0xe8, 0xac, + 0xb3, 0x52, 0x2c, 0xa0, 0xe8, 0x5d, 0x98, 0x68, 0x4a, 0xe5, 0x71, 0xc2, 0x06, 0x9e, 0xee, 0xa9, + 0x8a, 0x57, 0xaf, 0x36, 0xdc, 0x40, 0x6e, 0x59, 0xab, 0x8f, 0x0d, 0x6a, 0xe6, 0xbb, 0x7f, 0xb1, + 0xdf, 0xbb, 0x7f, 0x42, 0x37, 0xf7, 0xe5, 0xda, 0xfe, 0x71, 0x0b, 0x46, 0xb8, 0x0a, 0x72, 0x30, + 0x9d, 0xad, 0xf6, 0x0a, 0x95, 0x8c, 0xdd, 0x1d, 0x5a, 0x28, 0x1e, 0xa5, 0xd0, 0x1a, 0x94, 0xd8, + 0x0f, 0xa6, 0x8a, 0x29, 0xe6, 0x5b, 0x06, 0xf2, 0x56, 0xf5, 0x0e, 0xde, 0x91, 0xd5, 0x70, 0x42, + 0xc1, 0xfe, 0xa1, 0x22, 0x65, 0x55, 0x09, 0xaa, 0x71, 0x82, 0x5b, 0x0f, 0xef, 0x04, 0x2f, 0x3c, + 0xac, 0x13, 0x7c, 0x1b, 0xa6, 0x9b, 0xda, 0x93, 0x57, 0x32, 0x93, 0x57, 0x7a, 0x2e, 0x12, 0xed, + 0x75, 0x8c, 0xab, 0xe1, 0x96, 0x4d, 0x22, 0x38, 0x4d, 0x15, 0x7d, 0x27, 0x4c, 0xf0, 0x79, 0x16, + 0xad, 0x0c, 0xb1, 0x56, 0x3e, 0x93, 0xbf, 0x5e, 0xf4, 0x26, 0xd8, 0x4a, 0x6c, 0x68, 0xd5, 0xb1, + 0x41, 0xcc, 0xfe, 0x85, 0x31, 0x18, 0x5e, 0xd9, 0x27, 0x7e, 0x7c, 0x06, 0x0c, 0xa9, 0x09, 0x53, + 0xae, 0xbf, 0x1f, 0x78, 0xfb, 0xa4, 0xc5, 0xe1, 0x27, 0x39, 0x5c, 0x1f, 0x11, 0xa4, 0xa7, 0x6a, + 0x06, 0x09, 0x9c, 0x22, 0xf9, 0x30, 0x6e, 0xed, 0xd7, 0x60, 0x84, 0xcf, 0xbd, 0xb8, 0xb2, 0x67, + 0x2a, 0xd8, 0xd9, 0x20, 0x8a, 0x5d, 0x90, 0x68, 0x14, 0xb8, 0x46, 0x5f, 0x54, 0x47, 0xef, 0xc1, + 0xd4, 0x96, 0x1b, 0x46, 0x31, 0xbd, 0x6e, 0x47, 0xb1, 0xb3, 0xd7, 0x7e, 0x80, 0x5b, 0xba, 0x1a, + 0x87, 0x55, 0x83, 0x12, 0x4e, 0x51, 0x46, 0xdb, 0x30, 0x49, 0x2f, 0x8e, 0x49, 0x53, 0xa3, 0x27, + 0x6e, 0x4a, 0xa9, 0xe1, 0x6e, 0xea, 0x84, 0xb0, 0x49, 0x97, 0x32, 0x93, 0x26, 0xbb, 0x68, 0x8e, + 0x31, 0x89, 0x42, 0x31, 0x13, 0x7e, 0xc3, 0xe4, 0x30, 0xca, 0x93, 0x98, 0xa9, 0x48, 0xc9, 0xe4, + 0x49, 0x9a, 0x41, 0xc8, 0x57, 0xa1, 0x44, 0xe8, 0x10, 0x52, 0xc2, 0xe2, 0xb1, 0x61, 0x61, 0xb0, + 0xbe, 0xae, 0xb9, 0xcd, 0x30, 0x30, 0xf5, 0x23, 0x2b, 0x92, 0x12, 0x4e, 0x88, 0xa2, 0x65, 0x18, + 0x89, 0x48, 0xe8, 0x92, 0x48, 0x3c, 0x3b, 0xf4, 0x98, 0x46, 0x86, 0xc6, 0x4d, 0x48, 0xf9, 0x6f, + 0x2c, 0xaa, 0xd2, 0xe5, 0xe5, 0xb0, 0xdb, 0x10, 0x7b, 0x69, 0xd0, 0x96, 0xd7, 0x22, 0x2b, 0xc5, + 0x02, 0x8a, 0xde, 0x82, 0xd1, 0x90, 0x78, 0x4c, 0x01, 0x37, 0x39, 0xf8, 0x22, 0xe7, 0xfa, 0x3c, + 0x5e, 0x0f, 0x4b, 0x02, 0xe8, 0x06, 0xa0, 0x90, 0x50, 0x19, 0xc2, 0xf5, 0xb7, 0x95, 0x01, 0x85, + 0x78, 0x3f, 0x78, 0x4c, 0xb4, 0x7f, 0x0e, 0x27, 0x18, 0x7e, 0x1c, 0x06, 0x9e, 0x47, 0x42, 0x9c, + 0x51, 0x0d, 0x5d, 0x83, 0x59, 0x55, 0x5a, 0xf3, 0xa3, 0xd8, 0xf1, 0x9b, 0x84, 0x3d, 0x1d, 0x94, + 0x12, 0xa9, 0x08, 0xa7, 0x11, 0x70, 0x77, 0x1d, 0xfb, 0xeb, 0x54, 0x9c, 0xa1, 0xa3, 0x75, 0x06, + 0xb2, 0xc0, 0x9b, 0xa6, 0x2c, 0x70, 0x31, 0x77, 0xe6, 0x72, 0xe4, 0x80, 0x23, 0x0b, 0xc6, 0xb5, + 0x99, 0x4d, 0xd6, 0xac, 0xd5, 0x63, 0xcd, 0x76, 0x60, 0x86, 0xae, 0xf4, 0x5b, 0x9b, 0xcc, 0x9b, + 0xa2, 0xc5, 0x16, 0x66, 0xe1, 0xc1, 0x16, 0x66, 0x59, 0x34, 0x30, 0x73, 0x33, 0x45, 0x10, 0x77, + 0x35, 0x81, 0x5e, 0x93, 0xda, 0xa8, 0xa2, 0x61, 0x18, 0xc5, 0x35, 0x4d, 0xc7, 0x87, 0x95, 0x19, + 0xed, 0x43, 0x74, 0xed, 0x93, 0xfd, 0x55, 0xf9, 0x8d, 0x9c, 0xd9, 0x2c, 0x40, 0xa9, 0xa9, 0x16, + 0x8b, 0x65, 0xda, 0xd2, 0xaa, 0xe5, 0x80, 0x13, 0x1c, 0xba, 0x47, 0xe9, 0x15, 0x24, 0x6d, 0xcb, + 0x47, 0x2f, 0x28, 0x98, 0x41, 0xec, 0x97, 0x01, 0x56, 0xee, 0x93, 0x26, 0x5f, 0xea, 0xfa, 0xa3, + 0xae, 0x95, 0xff, 0xa8, 0x6b, 0xff, 0x47, 0x0b, 0xa6, 0x56, 0x97, 0x8d, 0x6b, 0xe2, 0x3c, 0x00, + 0xbf, 0x1b, 0xdd, 0xbd, 0xbb, 0x2e, 0xdf, 0x2b, 0xb8, 0xca, 0x59, 0x95, 0x62, 0x0d, 0x03, 0x5d, + 0x84, 0xa2, 0xd7, 0xf1, 0xc5, 0x95, 0x65, 0xf4, 0xe8, 0xb0, 0x52, 0xbc, 0xd9, 0xf1, 0x31, 0x2d, + 0xd3, 0xcc, 0xe7, 0x8a, 0x03, 0x9b, 0xcf, 0xf5, 0xf5, 0x92, 0x40, 0x15, 0x18, 0xbe, 0x77, 0xcf, + 0x6d, 0x45, 0xe5, 0xe1, 0xe4, 0x2d, 0xe5, 0xee, 0xdd, 0x5a, 0x35, 0xc2, 0xbc, 0xdc, 0xfe, 0x5a, + 0x11, 0xe6, 0x56, 0x3d, 0x72, 0xff, 0x23, 0xda, 0xe3, 0x0e, 0x6a, 0xfc, 0x77, 0x32, 0x79, 0xf1, + 0xa4, 0x96, 0x8e, 0xfd, 0xc7, 0x63, 0x0b, 0x46, 0xb9, 0x81, 0x00, 0x1f, 0x91, 0xf1, 0xab, 0x6f, + 0x64, 0xb5, 0x9e, 0x3f, 0x20, 0xf3, 0x42, 0x3b, 0xc7, 0xed, 0xa6, 0xd4, 0x49, 0x2b, 0x4a, 0xb1, + 0x24, 0x3e, 0xf7, 0x79, 0x98, 0xd0, 0x31, 0x4f, 0x64, 0x40, 0xf5, 0x97, 0x8a, 0x30, 0x43, 0x7b, + 0xf0, 0x50, 0x27, 0xe2, 0x76, 0xf7, 0x44, 0x9c, 0xb6, 0xdd, 0x69, 0xff, 0xd9, 0x78, 0x37, 0x3d, + 0x1b, 0x2f, 0xe5, 0xcd, 0xc6, 0x59, 0xcf, 0xc1, 0x5f, 0xb6, 0xe0, 0xdc, 0xaa, 0x17, 0x34, 0x77, + 0x53, 0x26, 0xb1, 0xaf, 0xc2, 0x38, 0xe5, 0xe3, 0x91, 0xe1, 0x0c, 0x60, 0xb8, 0x87, 0x08, 0x10, + 0xd6, 0xf1, 0xb4, 0x6a, 0xb7, 0x6f, 0xd7, 0xaa, 0x59, 0x5e, 0x25, 0x02, 0x84, 0x75, 0x3c, 0xfb, + 0x37, 0x2d, 0x78, 0xe2, 0xda, 0xf2, 0x4a, 0xb2, 0x14, 0xbb, 0x1c, 0x5b, 0xe8, 0x2d, 0xb0, 0xa5, + 0x75, 0x25, 0xb9, 0x05, 0x56, 0x59, 0x2f, 0x04, 0xf4, 0x93, 0xe2, 0xb4, 0xf5, 0x53, 0x16, 0x9c, + 0xbb, 0xe6, 0xc6, 0xf4, 0x58, 0x4e, 0xbb, 0x58, 0xd0, 0x73, 0x39, 0x72, 0xe3, 0x20, 0x3c, 0x48, + 0xbb, 0x58, 0x60, 0x05, 0xc1, 0x1a, 0x16, 0x6f, 0x79, 0xdf, 0x8d, 0x68, 0x4f, 0x0b, 0xa6, 0x2a, + 0x0a, 0x8b, 0x72, 0xac, 0x30, 0xe8, 0x87, 0xb5, 0xdc, 0x90, 0x5d, 0x25, 0x0e, 0x04, 0x87, 0x55, + 0x1f, 0x56, 0x95, 0x00, 0x9c, 0xe0, 0xd8, 0x3f, 0x66, 0xc1, 0x85, 0x6b, 0x5e, 0x27, 0x8a, 0x49, + 0xb8, 0x15, 0x19, 0x9d, 0x7d, 0x19, 0x4a, 0x44, 0x5e, 0xd7, 0x45, 0x5f, 0x95, 0x80, 0xa9, 0xee, + 0xf1, 0xdc, 0xbf, 0x43, 0xe1, 0x0d, 0x60, 0x5f, 0x7e, 0x32, 0xbb, 0xe8, 0x9f, 0x2d, 0xc0, 0xe4, + 0xf5, 0x8d, 0x8d, 0xfa, 0x35, 0x12, 0x8b, 0x53, 0xac, 0xbf, 0xaa, 0x19, 0x6b, 0x1a, 0xb3, 0x5e, + 0x97, 0xa2, 0x4e, 0xec, 0x7a, 0xf3, 0xdc, 0xa1, 0x70, 0xbe, 0xe6, 0xc7, 0xb7, 0xc2, 0x46, 0x1c, + 0xba, 0xfe, 0x76, 0xa6, 0x8e, 0x4d, 0x9e, 0xb5, 0xc5, 0xbc, 0xb3, 0x16, 0xbd, 0x0c, 0x23, 0xcc, + 0xa3, 0x51, 0x5e, 0x4f, 0x1e, 0x53, 0x77, 0x0a, 0x56, 0x7a, 0x7c, 0x58, 0x29, 0xdd, 0xc6, 0x35, + 0xfe, 0x07, 0x0b, 0x54, 0x74, 0x1b, 0xc6, 0x77, 0xe2, 0xb8, 0x7d, 0x9d, 0x38, 0x2d, 0x12, 0x4a, + 0xee, 0x70, 0x29, 0x8b, 0x3b, 0xd0, 0x41, 0xe0, 0x68, 0xc9, 0x86, 0x4a, 0xca, 0x22, 0xac, 0xd3, + 0xb1, 0x1b, 0x00, 0x09, 0xec, 0x94, 0xf4, 0x0b, 0xf6, 0x1f, 0x58, 0x30, 0xca, 0x9d, 0x4b, 0x42, + 0xf4, 0x05, 0x18, 0x22, 0xf7, 0x49, 0x53, 0x48, 0x8e, 0x99, 0x1d, 0x4e, 0x04, 0x0f, 0xae, 0x2d, + 0xa7, 0xff, 0x31, 0xab, 0x85, 0xae, 0xc3, 0x28, 0xed, 0xed, 0x35, 0xe5, 0x69, 0xf3, 0x64, 0xde, + 0x17, 0xab, 0x69, 0xe7, 0xb2, 0x8a, 0x28, 0xc2, 0xb2, 0x3a, 0xd3, 0xfc, 0x36, 0xdb, 0x0d, 0xca, + 0xc0, 0xe2, 0x5e, 0xe7, 0xec, 0xc6, 0x72, 0x9d, 0x23, 0x09, 0x6a, 0x5c, 0xf3, 0x2b, 0x0b, 0x71, + 0x42, 0xc4, 0xde, 0x80, 0x12, 0x9d, 0xd4, 0x45, 0xcf, 0x75, 0x7a, 0x2b, 0x9d, 0x9f, 0x87, 0x92, + 0x54, 0x00, 0x47, 0xc2, 0xfd, 0x85, 0x51, 0x95, 0xfa, 0xe1, 0x08, 0x27, 0x70, 0x7b, 0x0b, 0xce, + 0x33, 0x6b, 0x0a, 0x27, 0xde, 0x31, 0xf6, 0x58, 0xff, 0xc5, 0xfc, 0x82, 0xb8, 0x88, 0xf1, 0x99, + 0x29, 0x6b, 0xf6, 0xfa, 0x13, 0x92, 0x62, 0x72, 0x29, 0xb3, 0xff, 0x68, 0x08, 0x1e, 0xab, 0x35, + 0xf2, 0xfd, 0x8e, 0x5e, 0x87, 0x09, 0x2e, 0xa6, 0xd1, 0xa5, 0xed, 0x78, 0xa2, 0x5d, 0xf5, 0xd6, + 0xb8, 0xa1, 0xc1, 0xb0, 0x81, 0x89, 0x9e, 0x80, 0xa2, 0xfb, 0xbe, 0x9f, 0x36, 0xce, 0xad, 0xbd, + 0xbd, 0x8e, 0x69, 0x39, 0x05, 0x53, 0x89, 0x8f, 0xb3, 0x52, 0x05, 0x56, 0x52, 0xdf, 0x9b, 0x30, + 0xe5, 0x46, 0xcd, 0xc8, 0xad, 0xf9, 0x94, 0xcf, 0x24, 0x3e, 0x6b, 0x89, 0x92, 0x80, 0x76, 0x5a, + 0x41, 0x71, 0x0a, 0x5b, 0xe3, 0xeb, 0xc3, 0x03, 0x4b, 0x8d, 0x7d, 0xfd, 0x41, 0xa8, 0x40, 0xdc, + 0x66, 0x5f, 0x17, 0x31, 0x43, 0x41, 0x21, 0x10, 0xf3, 0x0f, 0x8e, 0xb0, 0x84, 0xd1, 0x1b, 0x58, + 0x73, 0xc7, 0x69, 0x2f, 0x76, 0xe2, 0x9d, 0xaa, 0x1b, 0x35, 0x83, 0x7d, 0x12, 0x1e, 0xb0, 0xcb, + 0xf3, 0x58, 0x72, 0x03, 0x53, 0x80, 0xe5, 0xeb, 0x8b, 0x75, 0x8a, 0x89, 0xbb, 0xeb, 0x98, 0x52, + 0x21, 0x9c, 0x86, 0x54, 0xb8, 0x08, 0xd3, 0xb2, 0x99, 0x06, 0x89, 0xd8, 0x19, 0x31, 0xce, 0x3a, + 0xa6, 0xbc, 0x49, 0x45, 0xb1, 0xea, 0x56, 0x1a, 0x1f, 0xbd, 0x06, 0x93, 0xae, 0xef, 0xc6, 0xae, + 0x13, 0x07, 0x21, 0x3b, 0x61, 0xf9, 0x3d, 0x99, 0x3d, 0x8a, 0xd6, 0x74, 0x00, 0x36, 0xf1, 0xec, + 0x3f, 0x1c, 0x82, 0x59, 0x36, 0x6d, 0xdf, 0x5a, 0x61, 0x9f, 0x98, 0x15, 0x76, 0xbb, 0x7b, 0x85, + 0x9d, 0x86, 0xb8, 0xfb, 0x71, 0x2e, 0xb3, 0xf7, 0xa0, 0xa4, 0xec, 0xab, 0xa5, 0x8b, 0x80, 0x95, + 0xe3, 0x22, 0xd0, 0x5f, 0xfa, 0x90, 0xcf, 0xb8, 0xc5, 0xcc, 0x67, 0xdc, 0xbf, 0x63, 0x41, 0x62, + 0x66, 0x8a, 0xae, 0x43, 0xa9, 0x1d, 0x30, 0x53, 0x8e, 0x50, 0xda, 0x47, 0x3d, 0x96, 0x79, 0x50, + 0xf1, 0x43, 0x91, 0x8f, 0x5f, 0x5d, 0xd6, 0xc0, 0x49, 0x65, 0xb4, 0x04, 0xa3, 0xed, 0x90, 0x34, + 0x62, 0xe6, 0x28, 0xd9, 0x97, 0x0e, 0x5f, 0x23, 0x1c, 0x1f, 0xcb, 0x8a, 0xf6, 0xcf, 0x59, 0x00, + 0xfc, 0xa5, 0xd4, 0xf1, 0xb7, 0xc9, 0x19, 0x68, 0x7f, 0xab, 0x30, 0x14, 0xb5, 0x49, 0xb3, 0x97, + 0x91, 0x4d, 0xd2, 0x9f, 0x46, 0x9b, 0x34, 0x93, 0x01, 0xa7, 0xff, 0x30, 0xab, 0x6d, 0x7f, 0x1f, + 0xc0, 0x54, 0x82, 0x56, 0x8b, 0xc9, 0x1e, 0x7a, 0xd1, 0x70, 0x43, 0xbb, 0x98, 0x72, 0x43, 0x2b, + 0x31, 0x6c, 0x4d, 0xd1, 0xf8, 0x1e, 0x14, 0xf7, 0x9c, 0xfb, 0x42, 0x93, 0xf4, 0x7c, 0xef, 0x6e, + 0x50, 0xfa, 0xf3, 0x6b, 0xce, 0x7d, 0x7e, 0x67, 0x7a, 0x5e, 0x2e, 0x90, 0x35, 0xe7, 0xfe, 0x31, + 0x37, 0xa5, 0x61, 0x4c, 0xea, 0xa6, 0x1b, 0xc5, 0x1f, 0xfe, 0x97, 0xe4, 0x3f, 0x5b, 0x76, 0xb4, + 0x11, 0xd6, 0x96, 0xeb, 0x8b, 0x77, 0xc3, 0x81, 0xda, 0x72, 0xfd, 0x74, 0x5b, 0xae, 0x3f, 0x40, + 0x5b, 0xae, 0x8f, 0x3e, 0x80, 0x51, 0xf1, 0x46, 0xcf, 0xec, 0xe7, 0x4d, 0x2d, 0x55, 0x5e, 0x7b, + 0xe2, 0x89, 0x9f, 0xb7, 0xb9, 0x20, 0xef, 0x84, 0xa2, 0xb4, 0x6f, 0xbb, 0xb2, 0x41, 0xf4, 0xb7, + 0x2c, 0x98, 0x12, 0xbf, 0x31, 0x79, 0xbf, 0x43, 0xa2, 0x58, 0xc8, 0x9e, 0x9f, 0x1b, 0xbc, 0x0f, + 0xa2, 0x22, 0xef, 0xca, 0xe7, 0x24, 0x9b, 0x35, 0x81, 0x7d, 0x7b, 0x94, 0xea, 0x05, 0xfa, 0x27, + 0x16, 0x9c, 0xdf, 0x73, 0xee, 0xf3, 0x16, 0x79, 0x19, 0x76, 0x62, 0x37, 0x10, 0xfe, 0x00, 0x5f, + 0x18, 0x6c, 0xfa, 0xbb, 0xaa, 0xf3, 0x4e, 0x4a, 0xd3, 0xe1, 0xf3, 0x59, 0x28, 0x7d, 0xbb, 0x9a, + 0xd9, 0xaf, 0xb9, 0x2d, 0x18, 0x93, 0xeb, 0x2d, 0xe3, 0xe6, 0x5d, 0xd5, 0x05, 0xeb, 0x13, 0x9b, + 0x48, 0x68, 0x37, 0x75, 0xd6, 0x8e, 0x58, 0x6b, 0x0f, 0xb5, 0x9d, 0xf7, 0x60, 0x42, 0x5f, 0x63, + 0x0f, 0xb5, 0xad, 0xf7, 0xe1, 0x5c, 0xc6, 0x5a, 0x7a, 0xa8, 0x4d, 0xde, 0x83, 0x8b, 0xb9, 0xeb, + 0xe3, 0x61, 0x36, 0x6c, 0xff, 0xac, 0xa5, 0xf3, 0xc1, 0x33, 0x50, 0xc1, 0x2f, 0x9b, 0x2a, 0xf8, + 0x4b, 0xbd, 0x77, 0x4e, 0x8e, 0x1e, 0xfe, 0x5d, 0xbd, 0xd3, 0x94, 0xab, 0xa3, 0xb7, 0x60, 0xc4, + 0xa3, 0x25, 0xd2, 0x38, 0xc4, 0xee, 0xbf, 0x23, 0x13, 0x59, 0x8a, 0x95, 0x47, 0x58, 0x50, 0xb0, + 0x7f, 0xd1, 0x82, 0xa1, 0x33, 0x18, 0x09, 0x6c, 0x8e, 0xc4, 0x8b, 0xb9, 0xa4, 0x45, 0x64, 0xa2, + 0x79, 0xec, 0xdc, 0x5b, 0x91, 0xd1, 0x97, 0x72, 0x06, 0xe6, 0xff, 0x16, 0x60, 0x9c, 0x36, 0x25, + 0xad, 0x18, 0xdf, 0x80, 0x49, 0xcf, 0xd9, 0x24, 0x9e, 0x7c, 0xc7, 0x4d, 0x2b, 0x4c, 0x6e, 0xea, + 0x40, 0x6c, 0xe2, 0xd2, 0xca, 0x5b, 0xfa, 0x93, 0xb6, 0x90, 0x5f, 0x54, 0x65, 0xe3, 0xbd, 0x1b, + 0x9b, 0xb8, 0xf4, 0xee, 0x7e, 0xcf, 0x89, 0x9b, 0x3b, 0x42, 0x99, 0xa2, 0xba, 0x7b, 0x97, 0x16, + 0x62, 0x0e, 0xa3, 0x02, 0x9c, 0x5c, 0x9d, 0x77, 0xe8, 0xcd, 0x30, 0xf0, 0x85, 0x78, 0xac, 0x04, + 0x38, 0x6c, 0x82, 0x71, 0x1a, 0x3f, 0xc3, 0x1f, 0x7d, 0x98, 0xd9, 0x68, 0x0e, 0xe0, 0x8f, 0x8e, + 0xea, 0x70, 0xde, 0xf5, 0x9b, 0x5e, 0xa7, 0x45, 0x6e, 0xfb, 0x5c, 0xba, 0xf3, 0xdc, 0x0f, 0x48, + 0x4b, 0x08, 0xd0, 0xca, 0x9c, 0xb6, 0x96, 0x81, 0x83, 0x33, 0x6b, 0xda, 0x7f, 0x01, 0xce, 0xdd, + 0x0c, 0x9c, 0xd6, 0x92, 0xe3, 0x39, 0x7e, 0x93, 0x84, 0x35, 0x7f, 0xbb, 0xaf, 0x95, 0x98, 0x6e, + 0xd3, 0x55, 0xe8, 0x67, 0xd3, 0x65, 0xef, 0x00, 0xd2, 0x1b, 0x10, 0xb6, 0xc9, 0x18, 0x46, 0x5d, + 0xde, 0x94, 0x58, 0xfe, 0xcf, 0x64, 0x4b, 0xd7, 0x5d, 0x3d, 0xd3, 0xac, 0x6e, 0x79, 0x01, 0x96, + 0x84, 0xec, 0xd7, 0x21, 0xd3, 0x1f, 0xb1, 0xbf, 0xda, 0xc6, 0x7e, 0x15, 0x66, 0x59, 0xcd, 0x93, + 0xa9, 0x14, 0xec, 0xbf, 0x66, 0xc1, 0xf4, 0x7a, 0x2a, 0x82, 0xc4, 0xd3, 0xec, 0xad, 0x35, 0x43, + 0xef, 0xde, 0x60, 0xa5, 0x58, 0x40, 0x4f, 0x5d, 0xbf, 0xf7, 0x67, 0x16, 0x94, 0x54, 0x70, 0x9a, + 0x33, 0x10, 0x6a, 0x97, 0x0d, 0xa1, 0x36, 0x53, 0xef, 0xa4, 0xba, 0x93, 0x27, 0xd3, 0xa2, 0x1b, + 0x2a, 0x16, 0x42, 0x0f, 0x95, 0x53, 0x42, 0x86, 0x7b, 0xce, 0x4f, 0x99, 0x01, 0x13, 0x64, 0x74, + 0x04, 0x66, 0xa6, 0xa5, 0x70, 0x3f, 0x21, 0x66, 0x5a, 0xaa, 0x3f, 0x39, 0xdc, 0xaf, 0xae, 0x75, + 0x99, 0x9d, 0x0a, 0xdf, 0xce, 0x5c, 0x19, 0xd8, 0xde, 0x54, 0x21, 0x48, 0x2a, 0xc2, 0x35, 0x41, + 0x94, 0x1e, 0x33, 0x46, 0x26, 0xfe, 0xf1, 0x40, 0x42, 0x49, 0x15, 0xfb, 0x3a, 0x4c, 0xa7, 0x06, + 0x0c, 0xbd, 0x0a, 0xc3, 0xed, 0x1d, 0x27, 0x22, 0x29, 0xd3, 0xd4, 0xe1, 0x3a, 0x2d, 0x3c, 0x3e, + 0xac, 0x4c, 0xa9, 0x0a, 0xac, 0x04, 0x73, 0x6c, 0xfb, 0x7f, 0x5a, 0x30, 0xb4, 0x1e, 0xb4, 0xce, + 0x62, 0x31, 0xbd, 0x69, 0x2c, 0xa6, 0xc7, 0xf3, 0xc2, 0xb0, 0xe5, 0xae, 0xa3, 0xd5, 0xd4, 0x3a, + 0xba, 0x94, 0x4b, 0xa1, 0xf7, 0x12, 0xda, 0x83, 0x71, 0x16, 0xdc, 0x4d, 0x98, 0xca, 0xbe, 0x6c, + 0xdc, 0xaf, 0x2a, 0xa9, 0xfb, 0xd5, 0xb4, 0x86, 0xaa, 0xdd, 0xb2, 0x9e, 0x85, 0x51, 0x61, 0xae, + 0x99, 0x76, 0xda, 0x10, 0xb8, 0x58, 0xc2, 0xed, 0x1f, 0x2f, 0x82, 0x11, 0x4c, 0x0e, 0xfd, 0x8a, + 0x05, 0xf3, 0x21, 0xf7, 0x82, 0x6d, 0x55, 0x3b, 0xa1, 0xeb, 0x6f, 0x37, 0x9a, 0x3b, 0xa4, 0xd5, + 0xf1, 0x5c, 0x7f, 0xbb, 0xb6, 0xed, 0x07, 0xaa, 0x78, 0xe5, 0x3e, 0x69, 0x76, 0xd8, 0x9b, 0x4b, + 0x9f, 0xc8, 0x75, 0xca, 0x1c, 0xea, 0xea, 0xd1, 0x61, 0x65, 0x1e, 0x9f, 0x88, 0x36, 0x3e, 0x61, + 0x5f, 0xd0, 0x6f, 0x5a, 0xb0, 0xc0, 0x63, 0xac, 0x0d, 0xde, 0xff, 0x1e, 0xb7, 0xd1, 0xba, 0x24, + 0x95, 0x10, 0xd9, 0x20, 0xe1, 0xde, 0xd2, 0x6b, 0x62, 0x40, 0x17, 0xea, 0x27, 0x6b, 0x0b, 0x9f, + 0xb4, 0x73, 0xf6, 0xbf, 0x29, 0xc2, 0x24, 0x1d, 0xc5, 0x24, 0xf2, 0xcb, 0xab, 0xc6, 0x92, 0x78, + 0x32, 0xb5, 0x24, 0x66, 0x0d, 0xe4, 0xd3, 0x09, 0xfa, 0x12, 0xc1, 0xac, 0xe7, 0x44, 0xf1, 0x75, + 0xe2, 0x84, 0xf1, 0x26, 0x71, 0xb8, 0x99, 0x50, 0xf1, 0xc4, 0x26, 0x4d, 0x4a, 0xfd, 0x75, 0x33, + 0x4d, 0x0c, 0x77, 0xd3, 0x47, 0xfb, 0x80, 0x98, 0xad, 0x53, 0xe8, 0xf8, 0x11, 0xff, 0x16, 0x57, + 0xbc, 0xc7, 0x9c, 0xac, 0xd5, 0x39, 0xd1, 0x2a, 0xba, 0xd9, 0x45, 0x0d, 0x67, 0xb4, 0xa0, 0xd9, + 0xb0, 0x0d, 0x0f, 0x6a, 0xc3, 0x36, 0xd2, 0xc7, 0x33, 0x6a, 0x0f, 0x66, 0xc4, 0xac, 0x6c, 0xb9, + 0xdb, 0xe2, 0x90, 0xfe, 0x72, 0xca, 0xc6, 0xd5, 0x1a, 0xdc, 0x50, 0xa9, 0x8f, 0x81, 0xab, 0xfd, + 0xdd, 0x70, 0x8e, 0x36, 0x67, 0xfa, 0xf1, 0x44, 0x88, 0xc0, 0xf4, 0x6e, 0x67, 0x93, 0x78, 0x24, + 0x96, 0x65, 0xa2, 0xd1, 0x4c, 0xb1, 0xdf, 0xac, 0x9d, 0xc8, 0x96, 0x37, 0x4c, 0x12, 0x38, 0x4d, + 0xd3, 0xfe, 0x49, 0x0b, 0x98, 0xb5, 0xfc, 0x19, 0x1c, 0x7f, 0x5f, 0x34, 0x8f, 0xbf, 0x72, 0x1e, + 0x07, 0xca, 0x39, 0xf9, 0x5e, 0xe1, 0xd3, 0x52, 0x0f, 0x83, 0xfb, 0x07, 0x52, 0xf6, 0xef, 0x2f, + 0x71, 0xfd, 0x1f, 0x8b, 0x6f, 0x48, 0x15, 0x14, 0x00, 0x7d, 0x0f, 0x8c, 0x35, 0x9d, 0xb6, 0xd3, + 0xe4, 0x51, 0x3c, 0x73, 0xb5, 0x3f, 0x46, 0xa5, 0xf9, 0x65, 0x51, 0x83, 0x6b, 0x33, 0x3e, 0x2b, + 0xbf, 0x52, 0x16, 0xf7, 0xd5, 0x60, 0xa8, 0x26, 0xe7, 0x76, 0x61, 0xd2, 0x20, 0xf6, 0x50, 0xaf, + 0xbe, 0xdf, 0xc3, 0x8f, 0x0b, 0x75, 0x63, 0xd9, 0x83, 0x59, 0x5f, 0xfb, 0x4f, 0x99, 0xa3, 0x14, + 0xa7, 0x3f, 0xdd, 0xef, 0x40, 0x60, 0x9c, 0x54, 0xf3, 0x06, 0x48, 0x91, 0xc1, 0xdd, 0x94, 0xed, + 0xbf, 0x67, 0xc1, 0xa3, 0x3a, 0xa2, 0x16, 0xaf, 0xa1, 0x9f, 0x3e, 0xb9, 0x0a, 0x63, 0x41, 0x9b, + 0x84, 0x4e, 0x72, 0x27, 0xbb, 0x22, 0x07, 0xfd, 0x96, 0x28, 0x3f, 0x3e, 0xac, 0x9c, 0xd7, 0xa9, + 0xcb, 0x72, 0xac, 0x6a, 0x22, 0x1b, 0x46, 0xd8, 0x60, 0x44, 0x22, 0x96, 0x06, 0x33, 0x53, 0x64, + 0x4f, 0xab, 0x11, 0x16, 0x10, 0xfb, 0xfb, 0x2c, 0xbe, 0xb0, 0xf4, 0xae, 0xa3, 0xf7, 0x61, 0x66, + 0x8f, 0x5e, 0xdf, 0x56, 0xee, 0xb7, 0x43, 0xae, 0x46, 0x97, 0xe3, 0xf4, 0x7c, 0xbf, 0x71, 0xd2, + 0x3e, 0x32, 0x31, 0x66, 0x5b, 0x4b, 0x11, 0xc3, 0x5d, 0xe4, 0xed, 0x3f, 0x29, 0xf0, 0x9d, 0xc8, + 0xa4, 0xba, 0x67, 0x61, 0xb4, 0x1d, 0xb4, 0x96, 0x6b, 0x55, 0x2c, 0x46, 0x48, 0xb1, 0xab, 0x3a, + 0x2f, 0xc6, 0x12, 0x8e, 0xae, 0x02, 0x90, 0xfb, 0x31, 0x09, 0x7d, 0xc7, 0x53, 0x86, 0x1f, 0x4a, + 0x78, 0x5a, 0x51, 0x10, 0xac, 0x61, 0xd1, 0x3a, 0xed, 0x30, 0xd8, 0x77, 0x5b, 0xcc, 0xdb, 0xb0, + 0x68, 0xd6, 0xa9, 0x2b, 0x08, 0xd6, 0xb0, 0xe8, 0x55, 0xb9, 0xe3, 0x47, 0xfc, 0x00, 0x74, 0x36, + 0x45, 0xf8, 0xb9, 0xb1, 0xe4, 0xaa, 0x7c, 0x5b, 0x07, 0x62, 0x13, 0x17, 0x2d, 0xc2, 0x48, 0xec, + 0x30, 0x73, 0x86, 0xe1, 0x7c, 0xb3, 0xc4, 0x0d, 0x8a, 0xa1, 0x87, 0x75, 0xa4, 0x15, 0xb0, 0xa8, + 0x88, 0xde, 0x91, 0x2c, 0x98, 0xb3, 0x64, 0x61, 0x0f, 0x9c, 0xbb, 0x6c, 0x75, 0xf6, 0xad, 0xf3, + 0x60, 0x61, 0x67, 0x6c, 0xd0, 0xb2, 0xbf, 0xb7, 0x04, 0x90, 0x48, 0x7b, 0xe8, 0x83, 0x2e, 0x16, + 0xf1, 0x42, 0x6f, 0xf9, 0xf0, 0xf4, 0xf8, 0x03, 0xfa, 0x7e, 0x0b, 0xc6, 0x1d, 0xcf, 0x0b, 0x9a, + 0x4e, 0xcc, 0x46, 0xb9, 0xd0, 0x9b, 0x45, 0x89, 0xf6, 0x17, 0x93, 0x1a, 0xbc, 0x0b, 0x2f, 0x4b, + 0x4b, 0x05, 0x0d, 0xd2, 0xb7, 0x17, 0x7a, 0xc3, 0xe8, 0xb3, 0xf2, 0x12, 0xc0, 0x97, 0xc7, 0x5c, + 0xfa, 0x12, 0x50, 0x62, 0xdc, 0x58, 0x93, 0xff, 0xd1, 0x6d, 0x23, 0x4e, 0xdb, 0x50, 0x7e, 0x48, + 0x0a, 0x43, 0xe8, 0xe9, 0x17, 0xa2, 0x0d, 0xd5, 0x75, 0xbf, 0xa8, 0xe1, 0xfc, 0xb8, 0x2d, 0x9a, + 0x74, 0xdd, 0xc7, 0x27, 0xea, 0x3d, 0x98, 0x6e, 0x99, 0xc7, 0xad, 0x58, 0x4d, 0xcf, 0xe4, 0xd1, + 0x4d, 0x9d, 0xce, 0xc9, 0x01, 0x9b, 0x02, 0xe0, 0x34, 0x61, 0x54, 0xe7, 0x1e, 0x6a, 0x35, 0x7f, + 0x2b, 0x10, 0x76, 0xe5, 0x76, 0xee, 0x5c, 0x1e, 0x44, 0x31, 0xd9, 0xa3, 0x98, 0xc9, 0x39, 0xba, + 0x2e, 0xea, 0x62, 0x45, 0x05, 0xbd, 0x05, 0x23, 0xcc, 0x6d, 0x38, 0x2a, 0x8f, 0xe5, 0xeb, 0x01, + 0xcd, 0x88, 0x17, 0xc9, 0xa6, 0x62, 0x7f, 0x23, 0x2c, 0x28, 0xa0, 0xeb, 0x32, 0x2c, 0x4e, 0x54, + 0xf3, 0x6f, 0x47, 0x84, 0x85, 0xc5, 0x29, 0x2d, 0x7d, 0x3a, 0x89, 0x78, 0xc3, 0xcb, 0x33, 0x03, + 0x38, 0x1b, 0x35, 0xa9, 0xbc, 0x22, 0xfe, 0xcb, 0xb8, 0xd0, 0x65, 0xc8, 0xef, 0x9e, 0x19, 0x3b, + 0x3a, 0x19, 0xce, 0x3b, 0x26, 0x09, 0x9c, 0xa6, 0x79, 0xa6, 0xc7, 0xe7, 0x9c, 0x0f, 0x33, 0xe9, + 0x8d, 0xf5, 0x50, 0x8f, 0xeb, 0x3f, 0x18, 0x82, 0x29, 0x73, 0x21, 0xa0, 0x05, 0x28, 0x09, 0x22, + 0x2a, 0x44, 0xa6, 0x5a, 0xdb, 0x6b, 0x12, 0x80, 0x13, 0x1c, 0x16, 0x22, 0x94, 0x55, 0xd7, 0xec, + 0x00, 0x93, 0x10, 0xa1, 0x0a, 0x82, 0x35, 0x2c, 0x2a, 0x44, 0x6f, 0x06, 0x41, 0xac, 0x8e, 0x02, + 0xb5, 0x5a, 0x96, 0x58, 0x29, 0x16, 0x50, 0x7a, 0x04, 0xec, 0x92, 0xd0, 0x27, 0x9e, 0xa9, 0xc9, + 0x54, 0x47, 0xc0, 0x0d, 0x1d, 0x88, 0x4d, 0x5c, 0x7a, 0xa4, 0x05, 0x11, 0x5b, 0x7e, 0x42, 0x54, + 0x4f, 0xec, 0x2a, 0x1b, 0xdc, 0x6d, 0x5e, 0xc2, 0xd1, 0x97, 0xe1, 0x51, 0xe5, 0xe5, 0x8e, 0xb9, + 0x66, 0x58, 0xb6, 0x38, 0x62, 0xdc, 0xac, 0x1f, 0x5d, 0xce, 0x46, 0xc3, 0x79, 0xf5, 0xd1, 0x9b, + 0x30, 0x25, 0x44, 0x60, 0x49, 0x71, 0xd4, 0x34, 0x56, 0xb8, 0x61, 0x40, 0x71, 0x0a, 0x1b, 0x55, + 0x61, 0x86, 0x96, 0x30, 0x29, 0x54, 0x52, 0xe0, 0xde, 0xfa, 0xea, 0xac, 0xbf, 0x91, 0x82, 0xe3, + 0xae, 0x1a, 0x68, 0x11, 0xa6, 0xb9, 0x8c, 0x42, 0xef, 0x94, 0x6c, 0x1e, 0x84, 0xbb, 0x87, 0xda, + 0x08, 0xb7, 0x4c, 0x30, 0x4e, 0xe3, 0xa3, 0xd7, 0x61, 0xc2, 0x09, 0x9b, 0x3b, 0x6e, 0x4c, 0x9a, + 0x71, 0x27, 0xe4, 0x7e, 0x20, 0x9a, 0xb5, 0xc7, 0xa2, 0x06, 0xc3, 0x06, 0xa6, 0xfd, 0x01, 0x9c, + 0xcb, 0xf0, 0x14, 0xa3, 0x0b, 0xc7, 0x69, 0xbb, 0xf2, 0x9b, 0x52, 0x16, 0x92, 0x8b, 0xf5, 0x9a, + 0xfc, 0x1a, 0x0d, 0x8b, 0xae, 0x4e, 0xa6, 0x12, 0xd7, 0x82, 0xb7, 0xab, 0xd5, 0xb9, 0x2a, 0x01, + 0x38, 0xc1, 0xb1, 0x7f, 0x03, 0x40, 0x53, 0xe8, 0x0c, 0x60, 0x1f, 0xf7, 0x3a, 0x4c, 0xc8, 0x8c, + 0x03, 0x5a, 0xa4, 0x6b, 0xf5, 0x99, 0xd7, 0x34, 0x18, 0x36, 0x30, 0x69, 0xdf, 0x7c, 0x15, 0xa7, + 0x3b, 0x65, 0x8f, 0x99, 0x44, 0xe9, 0x4e, 0x70, 0xd0, 0x0b, 0x30, 0x16, 0x11, 0x6f, 0xeb, 0xa6, + 0xeb, 0xef, 0x8a, 0x85, 0xad, 0xb8, 0x70, 0x43, 0x94, 0x63, 0x85, 0x81, 0x96, 0xa0, 0xd8, 0x71, + 0x5b, 0x62, 0x29, 0xcb, 0x03, 0xbf, 0x78, 0xbb, 0x56, 0x3d, 0x3e, 0xac, 0x3c, 0x99, 0x97, 0x48, + 0x81, 0x5e, 0xed, 0xa3, 0x79, 0xba, 0xfd, 0x68, 0xe5, 0xac, 0xb7, 0x81, 0x91, 0x13, 0xbe, 0x0d, + 0x5c, 0x05, 0x10, 0x5f, 0x2d, 0xd7, 0x72, 0x31, 0x99, 0xb5, 0x6b, 0x0a, 0x82, 0x35, 0x2c, 0x14, + 0xc1, 0x6c, 0x33, 0x24, 0x8e, 0xbc, 0x43, 0x73, 0x9f, 0xa7, 0xb1, 0x07, 0x57, 0x10, 0x2c, 0xa7, + 0x89, 0xe1, 0x6e, 0xfa, 0x28, 0x80, 0xd9, 0x96, 0x08, 0xaa, 0x90, 0x34, 0x5a, 0x3a, 0xb9, 0xa3, + 0x15, 0x33, 0xc8, 0x49, 0x13, 0xc2, 0xdd, 0xb4, 0xd1, 0x57, 0x60, 0x4e, 0x16, 0x76, 0xc7, 0xb1, + 0x60, 0xdb, 0xa5, 0xb8, 0x74, 0xe9, 0xe8, 0xb0, 0x32, 0x57, 0xcd, 0xc5, 0xc2, 0x3d, 0x28, 0x20, + 0x0c, 0x23, 0xec, 0x2d, 0x29, 0x2a, 0x8f, 0xb3, 0x73, 0xee, 0xb9, 0x7c, 0x65, 0x00, 0x5d, 0xeb, + 0xf3, 0xec, 0x1d, 0x4a, 0x98, 0x94, 0x27, 0xcf, 0x72, 0xac, 0x10, 0x0b, 0x4a, 0x68, 0x0b, 0xc6, + 0x1d, 0xdf, 0x0f, 0x62, 0x87, 0x8b, 0x50, 0x13, 0xf9, 0xb2, 0x9f, 0x46, 0x78, 0x31, 0xa9, 0xc1, + 0xa9, 0x2b, 0x2b, 0x55, 0x0d, 0x82, 0x75, 0xc2, 0xe8, 0x1e, 0x4c, 0x07, 0xf7, 0x28, 0x73, 0x94, + 0x5a, 0x8a, 0xa8, 0x3c, 0xc9, 0xda, 0x7a, 0x65, 0x40, 0x3d, 0xad, 0x51, 0x59, 0xe3, 0x5a, 0x26, + 0x51, 0x9c, 0x6e, 0x05, 0xcd, 0x1b, 0xda, 0xea, 0xa9, 0xc4, 0x9d, 0x25, 0xd1, 0x56, 0xeb, 0xca, + 0x69, 0x16, 0x17, 0x85, 0x9b, 0x48, 0xb3, 0xdd, 0x3f, 0x9d, 0x8a, 0x8b, 0x92, 0x80, 0xb0, 0x8e, + 0x87, 0x76, 0x60, 0x22, 0x79, 0xb2, 0x0a, 0x23, 0x16, 0x95, 0x6d, 0xfc, 0xea, 0xd5, 0xc1, 0x3e, + 0xae, 0xa6, 0xd5, 0xe4, 0x37, 0x07, 0xbd, 0x04, 0x1b, 0x94, 0xe7, 0xbe, 0x0d, 0xc6, 0xb5, 0x89, + 0x3d, 0x89, 0x07, 0xc0, 0xdc, 0x9b, 0x30, 0x93, 0x9e, 0xba, 0x13, 0x79, 0x10, 0xfc, 0xef, 0x02, + 0x4c, 0x67, 0xbc, 0x5c, 0xb1, 0x64, 0x0c, 0x29, 0x86, 0x9a, 0xe4, 0x5e, 0x30, 0xd9, 0x62, 0x61, + 0x00, 0xb6, 0x28, 0x79, 0x74, 0x31, 0x97, 0x47, 0x0b, 0x56, 0x38, 0xf4, 0x51, 0x58, 0xa1, 0x79, + 0xfa, 0x0c, 0x0f, 0x74, 0xfa, 0x9c, 0x02, 0xfb, 0x34, 0x0e, 0xb0, 0xd1, 0x01, 0x0e, 0xb0, 0x1f, + 0x2a, 0xc0, 0x4c, 0xda, 0xc2, 0xf7, 0x0c, 0xde, 0x3b, 0xde, 0x32, 0xde, 0x3b, 0xb2, 0x53, 0x9b, + 0xa4, 0xed, 0x8e, 0xf3, 0xde, 0x3e, 0x70, 0xea, 0xed, 0xe3, 0xb9, 0x81, 0xa8, 0xf5, 0x7e, 0x07, + 0xf9, 0xfb, 0x05, 0xb8, 0x90, 0xae, 0xb2, 0xec, 0x39, 0xee, 0xde, 0x19, 0x8c, 0xcd, 0x2d, 0x63, + 0x6c, 0x5e, 0x1c, 0xe4, 0x6b, 0x58, 0xd7, 0x72, 0x07, 0xe8, 0x6e, 0x6a, 0x80, 0x16, 0x06, 0x27, + 0xd9, 0x7b, 0x94, 0xbe, 0x51, 0x84, 0x4b, 0x99, 0xf5, 0x92, 0xe7, 0x82, 0x55, 0xe3, 0xb9, 0xe0, + 0x6a, 0xea, 0xb9, 0xc0, 0xee, 0x5d, 0xfb, 0x74, 0xde, 0x0f, 0x84, 0x3b, 0x34, 0x8b, 0x18, 0xfa, + 0x80, 0x6f, 0x07, 0x86, 0x3b, 0xb4, 0x22, 0x84, 0x4d, 0xba, 0xdf, 0x4c, 0x6f, 0x06, 0xbf, 0x61, + 0xc1, 0xc5, 0xcc, 0xb9, 0x39, 0x03, 0xbd, 0xfa, 0xba, 0xa9, 0x57, 0x7f, 0x76, 0xe0, 0xd5, 0x9a, + 0xa3, 0x68, 0xff, 0xc3, 0x62, 0xce, 0xb7, 0x30, 0xcd, 0xe4, 0x2d, 0x18, 0x77, 0x9a, 0x4d, 0x12, + 0x45, 0x6b, 0x41, 0x4b, 0x45, 0xd0, 0x7c, 0x91, 0x49, 0x1b, 0x49, 0xf1, 0xf1, 0x61, 0x65, 0x2e, + 0x4d, 0x22, 0x01, 0x63, 0x9d, 0x82, 0x19, 0xf4, 0xb7, 0x70, 0xaa, 0x41, 0x7f, 0xaf, 0x02, 0xec, + 0x2b, 0x7d, 0x45, 0x5a, 0xcd, 0xa9, 0x69, 0x32, 0x34, 0x2c, 0xf4, 0x5d, 0xec, 0x16, 0xc0, 0x8d, + 0x81, 0xf8, 0x52, 0x7c, 0x79, 0xc0, 0xb9, 0xd2, 0x0d, 0x8b, 0x78, 0xdc, 0x0d, 0xa5, 0x12, 0x56, + 0x24, 0xd1, 0x77, 0xc0, 0x4c, 0xc4, 0xc3, 0x3a, 0x2d, 0x7b, 0x4e, 0xc4, 0x9c, 0xb8, 0xc4, 0x2a, + 0x64, 0xc1, 0x34, 0x1a, 0x29, 0x18, 0xee, 0xc2, 0x46, 0xab, 0xf2, 0xa3, 0x58, 0x0c, 0x2a, 0xbe, + 0x30, 0x9f, 0x4e, 0x3e, 0x48, 0xa4, 0x82, 0x3a, 0x9f, 0x1e, 0x7e, 0x36, 0xf0, 0x5a, 0x4d, 0xfb, + 0x87, 0x86, 0xe0, 0xb1, 0x1e, 0x4c, 0x0c, 0x2d, 0x9a, 0x46, 0x00, 0xcf, 0xa7, 0xf5, 0x7f, 0x73, + 0x99, 0x95, 0x0d, 0x85, 0x60, 0x6a, 0xad, 0x14, 0x3e, 0xf2, 0x5a, 0xf9, 0x01, 0x4b, 0xd3, 0xcc, + 0x72, 0x53, 0xe1, 0x2f, 0x9e, 0x90, 0x39, 0x9f, 0xa2, 0xaa, 0x76, 0x2b, 0x43, 0xdf, 0x79, 0x75, + 0xe0, 0xee, 0x0c, 0xac, 0x00, 0x3d, 0xdb, 0x27, 0xa3, 0x0f, 0x2d, 0x78, 0x32, 0xb3, 0xbf, 0x86, + 0xd1, 0xd2, 0x02, 0x94, 0x9a, 0xb4, 0x50, 0x73, 0x0c, 0x4d, 0x3c, 0xe6, 0x25, 0x00, 0x27, 0x38, + 0x86, 0x6d, 0x52, 0xa1, 0xaf, 0x6d, 0xd2, 0xbf, 0xb6, 0xa0, 0x6b, 0x01, 0x9f, 0x01, 0x27, 0xad, + 0x99, 0x9c, 0xf4, 0xd3, 0x83, 0xcc, 0x65, 0x0e, 0x13, 0xfd, 0xdd, 0x69, 0x78, 0x24, 0xc7, 0x13, + 0x6c, 0x1f, 0x66, 0xb7, 0x9b, 0xc4, 0x74, 0xb9, 0x15, 0x1f, 0x93, 0xe9, 0x9d, 0xdc, 0xd3, 0x3f, + 0x97, 0x5f, 0x88, 0xbb, 0x50, 0x70, 0x77, 0x13, 0xe8, 0x43, 0x0b, 0xce, 0x3b, 0xf7, 0xa2, 0xae, + 0x4c, 0x8d, 0x62, 0xcd, 0xbc, 0x92, 0xa9, 0xa7, 0xed, 0x93, 0xd9, 0x91, 0xb9, 0xc5, 0x9d, 0xcf, + 0xc2, 0xc2, 0x99, 0x6d, 0x21, 0x2c, 0x82, 0x1e, 0x53, 0x79, 0xbb, 0x87, 0x53, 0x78, 0x96, 0xcb, + 0x1e, 0xe7, 0xa9, 0x12, 0x82, 0x15, 0x1d, 0x74, 0x07, 0x4a, 0xdb, 0xd2, 0x8f, 0x56, 0xf0, 0xec, + 0xcc, 0x43, 0x30, 0xd3, 0xd9, 0x96, 0xfb, 0x8e, 0x28, 0x10, 0x4e, 0x48, 0xa1, 0x37, 0xa1, 0xe8, + 0x6f, 0x45, 0xbd, 0x92, 0x4d, 0xa5, 0x6c, 0xf9, 0x78, 0xc0, 0x85, 0xf5, 0xd5, 0x06, 0xa6, 0x15, + 0xd1, 0x75, 0x28, 0x86, 0x9b, 0x2d, 0xf1, 0xb4, 0x90, 0x29, 0x97, 0xe2, 0xa5, 0x6a, 0xf6, 0x22, + 0xe1, 0x94, 0xf0, 0x52, 0x15, 0x53, 0x12, 0xa8, 0x0e, 0xc3, 0xcc, 0x69, 0x4a, 0xbc, 0x20, 0x64, + 0x0a, 0xa4, 0x3d, 0x9c, 0x0f, 0x79, 0x54, 0x06, 0x86, 0x80, 0x39, 0x21, 0xf4, 0x16, 0x8c, 0x34, + 0x59, 0x3e, 0x26, 0xa1, 0xf8, 0xc9, 0x0e, 0xd7, 0xd5, 0x95, 0xb1, 0x89, 0xbf, 0xa0, 0xf2, 0x72, + 0x2c, 0x28, 0xa0, 0x0d, 0x18, 0x69, 0x92, 0xf6, 0xce, 0x56, 0x24, 0xf4, 0x39, 0x9f, 0xcd, 0xa4, + 0xd5, 0x23, 0xfd, 0x98, 0xa0, 0xca, 0x30, 0xb0, 0xa0, 0x85, 0x3e, 0x0f, 0x85, 0xad, 0xa6, 0xf0, + 0xa4, 0xca, 0x7c, 0x43, 0x30, 0x23, 0x65, 0x2c, 0x8d, 0x1c, 0x1d, 0x56, 0x0a, 0xab, 0xcb, 0xb8, + 0xb0, 0xd5, 0x44, 0xeb, 0x30, 0xba, 0xc5, 0x7d, 0xeb, 0x45, 0x00, 0x9b, 0x67, 0xb2, 0xdd, 0xfe, + 0xbb, 0xdc, 0xef, 0xb9, 0x07, 0x90, 0x00, 0x60, 0x49, 0x84, 0xc5, 0x0b, 0x56, 0x31, 0x02, 0x44, + 0xe0, 0xfc, 0xf9, 0x93, 0xc5, 0x75, 0x10, 0x6a, 0x0e, 0x45, 0x05, 0x6b, 0x14, 0xd1, 0x57, 0xa1, + 0xe4, 0xc8, 0xcc, 0x80, 0x22, 0x08, 0xce, 0xcb, 0x99, 0xdb, 0xb1, 0x77, 0xd2, 0x44, 0xbe, 0x96, + 0x15, 0x12, 0x4e, 0x88, 0xa2, 0x5d, 0x98, 0xdc, 0x8f, 0xda, 0x3b, 0x44, 0x6e, 0x5f, 0x16, 0x13, + 0x27, 0xe7, 0xb8, 0xba, 0x23, 0x10, 0xdd, 0x30, 0xee, 0x38, 0x5e, 0x17, 0xc7, 0x61, 0x8e, 0x63, + 0x77, 0x74, 0x62, 0xd8, 0xa4, 0x4d, 0x87, 0xff, 0xfd, 0x4e, 0xb0, 0x79, 0x10, 0x13, 0x11, 0x69, + 0x3f, 0x73, 0xf8, 0xdf, 0xe6, 0x28, 0xdd, 0xc3, 0x2f, 0x00, 0x58, 0x12, 0xa1, 0x1b, 0xdc, 0x91, + 0x59, 0x37, 0x85, 0x2e, 0xe7, 0xd9, 0xdc, 0xe1, 0xe9, 0xea, 0x6f, 0x32, 0x28, 0x8c, 0x33, 0x26, + 0xa4, 0x18, 0x47, 0x6c, 0xef, 0x04, 0x71, 0xe0, 0xa7, 0xb8, 0xf1, 0x6c, 0x3e, 0x47, 0xac, 0x67, + 0xe0, 0x77, 0x73, 0xc4, 0x2c, 0x2c, 0x9c, 0xd9, 0x16, 0x6a, 0xc1, 0x54, 0x3b, 0x08, 0xe3, 0x7b, + 0x41, 0x28, 0xd7, 0x17, 0xea, 0x71, 0xc9, 0x37, 0x30, 0x45, 0x8b, 0xcc, 0x0e, 0xdc, 0x84, 0xe0, + 0x14, 0x4d, 0xf4, 0x25, 0x18, 0x8d, 0x9a, 0x8e, 0x47, 0x6a, 0xb7, 0xca, 0xe7, 0xf2, 0x8f, 0x9a, + 0x06, 0x47, 0xc9, 0x59, 0x5d, 0x6c, 0x72, 0x04, 0x0a, 0x96, 0xe4, 0xd0, 0x2a, 0x0c, 0xb3, 0xf4, + 0x2d, 0x2c, 0x49, 0x40, 0x4e, 0xb0, 0xb5, 0x2e, 0x5b, 0x69, 0xce, 0x91, 0x58, 0x31, 0xe6, 0xd5, + 0xe9, 0x1e, 0x10, 0xb2, 0x6e, 0x10, 0x95, 0x2f, 0xe4, 0xef, 0x01, 0x21, 0x22, 0xdf, 0x6a, 0xf4, + 0xda, 0x03, 0x0a, 0x09, 0x27, 0x44, 0x29, 0x3f, 0xa6, 0x3c, 0xf4, 0x91, 0x7c, 0x7e, 0x9c, 0xcf, + 0x41, 0x19, 0x3f, 0xa6, 0xfc, 0x93, 0x92, 0xb0, 0x3f, 0x1c, 0xed, 0x96, 0x4f, 0xd8, 0xed, 0xe8, + 0x7b, 0xad, 0x2e, 0xd3, 0x81, 0xcf, 0x0d, 0xaa, 0xac, 0x39, 0x45, 0xc9, 0xf4, 0x43, 0x0b, 0x1e, + 0x69, 0x67, 0x7e, 0x88, 0x38, 0xec, 0x07, 0xd3, 0xf9, 0xf0, 0x4f, 0x57, 0x89, 0x3c, 0xb2, 0xe1, + 0x38, 0xa7, 0xa5, 0xb4, 0xf4, 0x5f, 0xfc, 0xc8, 0xd2, 0xff, 0x1a, 0x8c, 0x31, 0x81, 0x32, 0x89, + 0xec, 0x37, 0x90, 0x01, 0x1e, 0x13, 0x1b, 0x96, 0x45, 0x45, 0xac, 0x48, 0xa0, 0x1f, 0xb4, 0xe0, + 0x89, 0x74, 0xd7, 0x31, 0x61, 0x60, 0x11, 0x25, 0x9a, 0x5f, 0xcc, 0x56, 0xc5, 0xf7, 0x3f, 0x51, + 0xef, 0x85, 0x7c, 0xdc, 0x0f, 0x01, 0xf7, 0x6e, 0x0c, 0x55, 0x33, 0x6e, 0x86, 0x23, 0xe6, 0xcb, + 0xe2, 0x00, 0xb7, 0xc3, 0x57, 0x60, 0x62, 0x2f, 0xe8, 0xf8, 0xd2, 0x3b, 0x46, 0xf8, 0x3e, 0x33, + 0x2d, 0xf6, 0x9a, 0x56, 0x8e, 0x0d, 0xac, 0xd4, 0x9d, 0x72, 0xec, 0x41, 0xef, 0x94, 0x67, 0x7b, + 0x53, 0xf9, 0xba, 0x95, 0x21, 0x62, 0xf3, 0xbb, 0xeb, 0x17, 0xcc, 0xbb, 0xeb, 0xd3, 0xe9, 0xbb, + 0x6b, 0x97, 0xae, 0xd2, 0xb8, 0xb6, 0x0e, 0x1e, 0x45, 0x7f, 0xd0, 0x10, 0x8a, 0xb6, 0x07, 0x97, + 0xfb, 0x1d, 0x1c, 0xcc, 0x98, 0xb1, 0xa5, 0x5e, 0xf9, 0x13, 0x63, 0xc6, 0x56, 0xad, 0x8a, 0x19, + 0x64, 0xd0, 0x18, 0x3b, 0xf6, 0x7f, 0xb7, 0xa0, 0x58, 0x0f, 0x5a, 0x67, 0xa0, 0x7b, 0xfd, 0xa2, + 0xa1, 0x7b, 0x7d, 0x2c, 0x27, 0xbf, 0x78, 0xae, 0xa6, 0x75, 0x25, 0xa5, 0x69, 0x7d, 0x22, 0x8f, + 0x40, 0x6f, 0xbd, 0xea, 0x4f, 0x14, 0x41, 0xcf, 0x86, 0x8e, 0xfe, 0xed, 0x83, 0x58, 0xc5, 0x17, + 0x7b, 0x25, 0x48, 0x17, 0x94, 0x99, 0x0d, 0xa4, 0x74, 0xb8, 0xfd, 0x73, 0x66, 0x1c, 0x7f, 0x97, + 0xb8, 0xdb, 0x3b, 0x31, 0x69, 0xa5, 0x3f, 0xe7, 0xec, 0x8c, 0xe3, 0xff, 0xab, 0x05, 0xd3, 0xa9, + 0xd6, 0x91, 0x97, 0xe5, 0xbd, 0xf7, 0x80, 0x3a, 0xb7, 0xd9, 0xbe, 0xee, 0x7e, 0xf3, 0x00, 0xea, + 0x61, 0x4b, 0xea, 0xa3, 0x98, 0x5c, 0xae, 0x5e, 0xbe, 0x22, 0xac, 0x61, 0xa0, 0x57, 0x61, 0x3c, + 0x0e, 0xda, 0x81, 0x17, 0x6c, 0x1f, 0xdc, 0x20, 0x32, 0xaa, 0x93, 0x7a, 0x7e, 0xdc, 0x48, 0x40, + 0x58, 0xc7, 0xb3, 0x7f, 0xaa, 0x08, 0xe9, 0x0c, 0xfa, 0xdf, 0x5a, 0x93, 0x9f, 0xcc, 0x35, 0xf9, + 0x0d, 0x0b, 0x66, 0x68, 0xeb, 0xcc, 0xbe, 0x4c, 0x1e, 0x87, 0x2a, 0x9b, 0x97, 0xd5, 0x23, 0x9b, + 0xd7, 0xd3, 0x94, 0x77, 0xb5, 0x82, 0x4e, 0x2c, 0xf4, 0x59, 0x1a, 0x73, 0xa2, 0xa5, 0x58, 0x40, + 0x05, 0x1e, 0x09, 0x43, 0xe1, 0x93, 0xa7, 0xe3, 0x91, 0x30, 0xc4, 0x02, 0x2a, 0x93, 0x7d, 0x0d, + 0xe5, 0x24, 0xfb, 0x62, 0x31, 0x2a, 0x85, 0x4d, 0x93, 0x10, 0x4c, 0xb4, 0x18, 0x95, 0xd2, 0xd8, + 0x29, 0xc1, 0xb1, 0x7f, 0xb6, 0x08, 0x13, 0xf5, 0xa0, 0x95, 0x3c, 0x2d, 0xbd, 0x62, 0x3c, 0x2d, + 0x5d, 0x4e, 0x3d, 0x2d, 0xcd, 0xe8, 0xb8, 0xdf, 0x7a, 0x48, 0xfa, 0xb8, 0x1e, 0x92, 0xfe, 0x95, + 0xc5, 0x66, 0xad, 0xba, 0xde, 0x10, 0xc9, 0xa8, 0x5f, 0x82, 0x71, 0xc6, 0x90, 0x98, 0x13, 0xa8, + 0x7c, 0x6f, 0x61, 0x39, 0x27, 0xd6, 0x93, 0x62, 0xac, 0xe3, 0xa0, 0x2b, 0x30, 0x16, 0x11, 0x27, + 0x6c, 0xee, 0x28, 0x1e, 0x27, 0x5e, 0x23, 0x78, 0x19, 0x56, 0x50, 0xf4, 0x76, 0x12, 0x1e, 0xb1, + 0x98, 0x9f, 0x56, 0x59, 0xef, 0x0f, 0xdf, 0x22, 0xf9, 0x31, 0x11, 0xed, 0xbb, 0x80, 0xba, 0xf1, + 0x07, 0x30, 0xf4, 0xaa, 0x98, 0x81, 0xd0, 0x4a, 0x5d, 0x41, 0xd0, 0xfe, 0xd4, 0x82, 0xa9, 0x7a, + 0xd0, 0xa2, 0x5b, 0xf7, 0x9b, 0x69, 0x9f, 0xea, 0xb1, 0x61, 0x47, 0x7a, 0xc4, 0x86, 0xfd, 0x07, + 0x16, 0x8c, 0xd6, 0x83, 0xd6, 0x19, 0x68, 0xc1, 0xbf, 0x60, 0x6a, 0xc1, 0x1f, 0xcd, 0x59, 0x12, + 0x39, 0x8a, 0xef, 0x9f, 0x2f, 0xc2, 0x24, 0xed, 0x67, 0xb0, 0x2d, 0x67, 0xc9, 0x18, 0x11, 0x6b, + 0x80, 0x11, 0xa1, 0x62, 0x6e, 0xe0, 0x79, 0xc1, 0xbd, 0xf4, 0x8c, 0xad, 0xb2, 0x52, 0x2c, 0xa0, + 0xe8, 0x05, 0x18, 0x6b, 0x87, 0x64, 0xdf, 0x0d, 0x3a, 0x51, 0xda, 0xdf, 0xb9, 0x2e, 0xca, 0xb1, + 0xc2, 0xa0, 0x37, 0xa3, 0xc8, 0xf5, 0x9b, 0x44, 0x5a, 0x80, 0x0d, 0x31, 0x0b, 0x30, 0x1e, 0xf4, + 0x5d, 0x2b, 0xc7, 0x06, 0x16, 0xba, 0x0b, 0x25, 0xf6, 0x9f, 0x71, 0x94, 0x93, 0xa7, 0x21, 0x13, + 0x99, 0x56, 0x04, 0x01, 0x9c, 0xd0, 0x42, 0x57, 0x01, 0x62, 0x69, 0xab, 0x16, 0x09, 0x77, 0x7c, + 0x25, 0x6b, 0x2b, 0x2b, 0xb6, 0x08, 0x6b, 0x58, 0xe8, 0x79, 0x28, 0xc5, 0x8e, 0xeb, 0xdd, 0x74, + 0x7d, 0x12, 0x09, 0x5b, 0x3f, 0x91, 0x48, 0x45, 0x14, 0xe2, 0x04, 0x4e, 0x65, 0x1d, 0x16, 0xec, + 0x81, 0x27, 0x31, 0x1c, 0x63, 0xd8, 0x4c, 0xd6, 0xb9, 0xa9, 0x4a, 0xb1, 0x86, 0x61, 0xbf, 0x0e, + 0x17, 0xea, 0x41, 0xab, 0x1e, 0x84, 0xf1, 0x6a, 0x10, 0xde, 0x73, 0xc2, 0x96, 0x9c, 0xbf, 0x8a, + 0xcc, 0xe9, 0x41, 0x79, 0xcf, 0x30, 0xdf, 0x99, 0x46, 0xb6, 0x8e, 0x97, 0x99, 0xb4, 0x73, 0x42, + 0xc7, 0xac, 0x7f, 0x5f, 0x60, 0x8c, 0x22, 0x95, 0x59, 0x13, 0x7d, 0x05, 0xa6, 0x22, 0x72, 0xd3, + 0xf5, 0x3b, 0xf7, 0xe5, 0x0d, 0xb6, 0x87, 0xd7, 0x5b, 0x63, 0x45, 0xc7, 0xe4, 0x7a, 0x30, 0xb3, + 0x0c, 0xa7, 0xa8, 0xd1, 0x21, 0x0c, 0x3b, 0xfe, 0x62, 0x74, 0x3b, 0x22, 0xa1, 0xc8, 0xec, 0xc8, + 0x86, 0x10, 0xcb, 0x42, 0x9c, 0xc0, 0xe9, 0x92, 0x61, 0x7f, 0xd6, 0x03, 0x1f, 0x07, 0x41, 0x2c, + 0x17, 0x19, 0xcb, 0x0d, 0xa6, 0x95, 0x63, 0x03, 0x0b, 0xad, 0x02, 0x8a, 0x3a, 0xed, 0xb6, 0xc7, + 0x9e, 0xa8, 0x1d, 0xef, 0x5a, 0x18, 0x74, 0xda, 0xfc, 0x79, 0x50, 0xa4, 0xd5, 0x6a, 0x74, 0x41, + 0x71, 0x46, 0x0d, 0xca, 0x18, 0xb6, 0x22, 0xf6, 0x5b, 0xc4, 0x7b, 0xe0, 0xba, 0xe9, 0x06, 0x2b, + 0xc2, 0x12, 0x66, 0x7f, 0x0f, 0x3b, 0x30, 0x58, 0x42, 0xbe, 0xb8, 0x13, 0x12, 0xb4, 0x07, 0x93, + 0x6d, 0x76, 0x94, 0x8b, 0xd0, 0xe6, 0x62, 0x00, 0x1f, 0xcc, 0xb2, 0x8f, 0x27, 0xe8, 0xd2, 0xc9, + 0x61, 0x93, 0xba, 0xfd, 0x9f, 0xa6, 0x19, 0x5f, 0x6a, 0xf0, 0xeb, 0xdc, 0xa8, 0xb0, 0xd7, 0x17, + 0xb2, 0xeb, 0x5c, 0x7e, 0x0a, 0xcf, 0xe4, 0x08, 0x11, 0x36, 0xff, 0x58, 0xd6, 0x45, 0x6f, 0xb3, + 0x77, 0x55, 0xce, 0x0c, 0xfa, 0x65, 0xf6, 0xe6, 0x58, 0xc6, 0x13, 0xaa, 0xa8, 0x88, 0x35, 0x22, + 0xe8, 0x26, 0x4c, 0x8a, 0xfc, 0x6d, 0x42, 0xb5, 0x53, 0x34, 0x14, 0x03, 0x93, 0x58, 0x07, 0x1e, + 0xa7, 0x0b, 0xb0, 0x59, 0x19, 0x6d, 0xc3, 0x13, 0x5a, 0x32, 0xd3, 0x0c, 0xeb, 0x52, 0xce, 0x5b, + 0x9e, 0x3c, 0x3a, 0xac, 0x3c, 0xb1, 0xd1, 0x0b, 0x11, 0xf7, 0xa6, 0x83, 0x6e, 0xc1, 0x05, 0xa7, + 0x19, 0xbb, 0xfb, 0xa4, 0x4a, 0x9c, 0x96, 0xe7, 0xfa, 0xc4, 0x0c, 0x00, 0x72, 0xf1, 0xe8, 0xb0, + 0x72, 0x61, 0x31, 0x0b, 0x01, 0x67, 0xd7, 0x43, 0x5f, 0x80, 0x52, 0xcb, 0x8f, 0xc4, 0x18, 0x8c, + 0x18, 0x79, 0x7a, 0x4b, 0xd5, 0xf5, 0x86, 0xfa, 0xfe, 0xe4, 0x0f, 0x4e, 0x2a, 0xa0, 0x6d, 0x98, + 0xd0, 0x9d, 0xfc, 0x44, 0x8e, 0xe7, 0x17, 0x7b, 0xdc, 0xfa, 0x0d, 0xcf, 0x38, 0xae, 0xd7, 0x54, + 0xb6, 0xdb, 0x86, 0xd3, 0x9c, 0x41, 0x18, 0xbd, 0x05, 0x88, 0x0a, 0x33, 0x6e, 0x93, 0x2c, 0x36, + 0x59, 0x84, 0x79, 0xa6, 0x0d, 0x1b, 0x33, 0x1c, 0x91, 0x50, 0xa3, 0x0b, 0x03, 0x67, 0xd4, 0x42, + 0xd7, 0x29, 0x47, 0xd1, 0x4b, 0x85, 0xa9, 0xbd, 0x14, 0x80, 0xcb, 0x55, 0xd2, 0x0e, 0x49, 0xd3, + 0x89, 0x49, 0xcb, 0xa4, 0x88, 0x53, 0xf5, 0xe8, 0x79, 0xa3, 0x92, 0x4d, 0x81, 0x69, 0x20, 0xde, + 0x9d, 0x70, 0x8a, 0xde, 0x1d, 0x77, 0x82, 0x28, 0x5e, 0x27, 0xf1, 0xbd, 0x20, 0xdc, 0x15, 0x51, + 0xfb, 0x92, 0x00, 0xb2, 0x09, 0x08, 0xeb, 0x78, 0x54, 0x56, 0x64, 0x0f, 0x9b, 0xb5, 0x2a, 0x7b, + 0x67, 0x1a, 0x4b, 0xf6, 0xc9, 0x75, 0x5e, 0x8c, 0x25, 0x5c, 0xa2, 0xd6, 0xea, 0xcb, 0xec, 0xcd, + 0x28, 0x85, 0x5a, 0xab, 0x2f, 0x63, 0x09, 0x47, 0xa4, 0x3b, 0x07, 0xf2, 0x54, 0xfe, 0xbb, 0x5f, + 0x37, 0x5f, 0x1e, 0x30, 0x0d, 0xb2, 0x0f, 0x33, 0x2a, 0xfb, 0x32, 0x0f, 0x67, 0x18, 0x95, 0xa7, + 0xd9, 0x22, 0x19, 0x3c, 0x16, 0xa2, 0xd2, 0x76, 0xd6, 0x52, 0x94, 0x70, 0x17, 0x6d, 0x23, 0xb0, + 0xcc, 0x4c, 0xdf, 0x64, 0x61, 0x0b, 0x50, 0x8a, 0x3a, 0x9b, 0xad, 0x60, 0xcf, 0x71, 0x7d, 0xf6, + 0xc4, 0xa3, 0x09, 0x22, 0x0d, 0x09, 0xc0, 0x09, 0x0e, 0x5a, 0x85, 0x31, 0x47, 0x5c, 0x4b, 0xc5, + 0xa3, 0x4c, 0x66, 0xa4, 0x09, 0x79, 0x75, 0xe5, 0x62, 0xb6, 0xfc, 0x87, 0x55, 0x5d, 0xf4, 0x06, + 0x4c, 0x0a, 0x67, 0x48, 0x61, 0xc7, 0x7c, 0xce, 0xf4, 0x9b, 0x69, 0xe8, 0x40, 0x6c, 0xe2, 0xa2, + 0xef, 0x82, 0x29, 0x4a, 0x25, 0x61, 0x6c, 0xe5, 0xf3, 0x83, 0x70, 0x44, 0x2d, 0x09, 0x8c, 0x5e, + 0x19, 0xa7, 0x88, 0xa1, 0x16, 0x3c, 0xee, 0x74, 0xe2, 0x80, 0xa9, 0x83, 0xcd, 0xf5, 0xbf, 0x11, + 0xec, 0x12, 0x9f, 0xbd, 0xc4, 0x8c, 0x2d, 0x5d, 0x3e, 0x3a, 0xac, 0x3c, 0xbe, 0xd8, 0x03, 0x0f, + 0xf7, 0xa4, 0x82, 0x6e, 0xc3, 0x78, 0x1c, 0x78, 0xc2, 0x01, 0x21, 0x2a, 0x3f, 0x92, 0x1f, 0x18, + 0x6b, 0x43, 0xa1, 0xe9, 0x8a, 0x16, 0x55, 0x15, 0xeb, 0x74, 0xd0, 0x06, 0xdf, 0x63, 0x2c, 0x64, + 0x30, 0x89, 0xca, 0x8f, 0xe6, 0x0f, 0x8c, 0x8a, 0x2c, 0x6c, 0x6e, 0x41, 0x51, 0x13, 0xeb, 0x64, + 0xd0, 0x35, 0x98, 0x6d, 0x87, 0x6e, 0xc0, 0x16, 0xb6, 0x52, 0xc5, 0x97, 0xcd, 0xbc, 0x1f, 0xf5, + 0x34, 0x02, 0xee, 0xae, 0x43, 0x2f, 0x62, 0xb2, 0xb0, 0x7c, 0x91, 0x27, 0x91, 0xe3, 0xc2, 0x29, + 0x2f, 0xc3, 0x0a, 0x8a, 0xd6, 0x18, 0x5f, 0xe6, 0x57, 0xa6, 0xf2, 0x5c, 0x7e, 0x84, 0x0e, 0xfd, + 0x6a, 0xc5, 0x05, 0x17, 0xf5, 0x17, 0x27, 0x14, 0xe6, 0xbe, 0x1d, 0x66, 0xbb, 0x18, 0xef, 0x89, + 0x6c, 0xcb, 0xff, 0xe9, 0x30, 0x94, 0x94, 0xde, 0x15, 0x2d, 0x98, 0xea, 0xf4, 0x8b, 0x69, 0x75, + 0xfa, 0x18, 0x15, 0xff, 0x74, 0x0d, 0xfa, 0x86, 0x61, 0x19, 0x55, 0xc8, 0xcf, 0x05, 0xa7, 0x2b, + 0x1d, 0xfa, 0x3a, 0x82, 0x6a, 0xd7, 0xe8, 0xe2, 0xc0, 0x7a, 0xf9, 0xa1, 0x9e, 0x37, 0xf3, 0x01, + 0xd3, 0x5b, 0xd3, 0x9b, 0x66, 0x3b, 0x68, 0xd5, 0xea, 0xe9, 0x7c, 0xaf, 0x75, 0x5a, 0x88, 0x39, + 0x8c, 0xdd, 0x15, 0xa8, 0x94, 0xc0, 0xee, 0x0a, 0xa3, 0x0f, 0x78, 0x57, 0x90, 0x04, 0x70, 0x42, + 0x0b, 0x79, 0x30, 0xdb, 0x34, 0x53, 0xf5, 0x2a, 0xe7, 0xcf, 0xa7, 0xfa, 0x26, 0xcd, 0xed, 0x68, + 0x39, 0xfc, 0x96, 0xd3, 0x54, 0x70, 0x37, 0x61, 0xf4, 0x06, 0x8c, 0xbd, 0x1f, 0x44, 0x6c, 0x15, + 0x8b, 0xa3, 0x52, 0xba, 0xdb, 0x8d, 0xbd, 0x7d, 0xab, 0xc1, 0xca, 0x8f, 0x0f, 0x2b, 0xe3, 0xf5, + 0xa0, 0x25, 0xff, 0x62, 0x55, 0x01, 0xdd, 0x87, 0x0b, 0x06, 0x83, 0x51, 0xdd, 0x85, 0xc1, 0xbb, + 0xfb, 0x84, 0x68, 0xee, 0x42, 0x2d, 0x8b, 0x12, 0xce, 0x6e, 0xc0, 0xfe, 0x25, 0xae, 0x5d, 0x16, + 0x3a, 0x28, 0x12, 0x75, 0xbc, 0xb3, 0x48, 0xd4, 0xb5, 0x62, 0xa8, 0xc7, 0x1e, 0xf8, 0x05, 0xe3, + 0xd7, 0x2c, 0xf6, 0x82, 0xb1, 0x41, 0xf6, 0xda, 0x9e, 0x13, 0x9f, 0x85, 0x47, 0xc1, 0xdb, 0x30, + 0x16, 0x8b, 0xd6, 0x7a, 0xe5, 0x16, 0xd3, 0x3a, 0xc5, 0x5e, 0x71, 0xd4, 0xf9, 0x2a, 0x4b, 0xb1, + 0x22, 0x63, 0xff, 0x73, 0x3e, 0x03, 0x12, 0x72, 0x06, 0xaa, 0x8a, 0xaa, 0xa9, 0xaa, 0xa8, 0xf4, + 0xf9, 0x82, 0x1c, 0x95, 0xc5, 0x3f, 0x33, 0xfb, 0xcd, 0xae, 0x32, 0x9f, 0xf4, 0xa7, 0x33, 0xfb, + 0x47, 0x2c, 0x38, 0x9f, 0x65, 0x0d, 0x42, 0x65, 0x22, 0x7e, 0x91, 0x52, 0x4f, 0x89, 0x6a, 0x04, + 0xef, 0x88, 0x72, 0xac, 0x30, 0x06, 0x4e, 0xdb, 0x71, 0xb2, 0xd8, 0x72, 0xb7, 0xc0, 0xcc, 0xea, + 0x8c, 0xde, 0xe4, 0x2e, 0x42, 0x96, 0x4a, 0xbb, 0x7c, 0x32, 0xf7, 0x20, 0xfb, 0xa7, 0x0b, 0x70, + 0x9e, 0xbf, 0x05, 0x2c, 0xee, 0x07, 0x6e, 0xab, 0x1e, 0xb4, 0x84, 0xc3, 0xd4, 0x3b, 0x30, 0xd1, + 0xd6, 0x6e, 0xbf, 0xbd, 0xa2, 0x5b, 0xe9, 0xb7, 0xe4, 0xe4, 0x16, 0xa2, 0x97, 0x62, 0x83, 0x16, + 0x6a, 0xc1, 0x04, 0xd9, 0x77, 0x9b, 0x4a, 0xa1, 0x5c, 0x38, 0x31, 0x4b, 0x57, 0xad, 0xac, 0x68, + 0x74, 0xb0, 0x41, 0xf5, 0x21, 0x64, 0xe1, 0xb3, 0x7f, 0xd4, 0x82, 0x47, 0x73, 0x62, 0x61, 0xd1, + 0xe6, 0xee, 0xb1, 0x57, 0x17, 0x91, 0xd0, 0x4b, 0x35, 0xc7, 0xdf, 0x62, 0xb0, 0x80, 0xa2, 0x2f, + 0x01, 0xf0, 0xb7, 0x14, 0x2a, 0x94, 0x8b, 0x4f, 0x1f, 0x2c, 0x46, 0x8c, 0x16, 0x48, 0x44, 0xd6, + 0xc7, 0x1a, 0x2d, 0xfb, 0x27, 0x8b, 0x30, 0xcc, 0x74, 0xf7, 0x68, 0x15, 0x46, 0x77, 0x78, 0xe4, + 0xed, 0x41, 0x82, 0x7c, 0x27, 0xb7, 0x1b, 0x5e, 0x80, 0x65, 0x65, 0xb4, 0x06, 0xe7, 0x84, 0x53, + 0x5e, 0x95, 0x78, 0xce, 0x81, 0xbc, 0x24, 0xf3, 0x24, 0x58, 0x2a, 0xed, 0x5b, 0xad, 0x1b, 0x05, + 0x67, 0xd5, 0x43, 0x6f, 0x76, 0xc5, 0xdb, 0xe4, 0x31, 0xcb, 0x95, 0x48, 0xdd, 0x27, 0xe6, 0xe6, + 0x1b, 0x30, 0xd9, 0xee, 0x52, 0x07, 0x0c, 0x27, 0xe2, 0xbe, 0xa9, 0x02, 0x30, 0x71, 0x99, 0x19, + 0x48, 0x87, 0x19, 0xbd, 0x6c, 0xec, 0x84, 0x24, 0xda, 0x09, 0xbc, 0x96, 0xc8, 0x4b, 0x9f, 0x98, + 0x81, 0xa4, 0xe0, 0xb8, 0xab, 0x06, 0xa5, 0xb2, 0xe5, 0xb8, 0x5e, 0x27, 0x24, 0x09, 0x95, 0x11, + 0x93, 0xca, 0x6a, 0x0a, 0x8e, 0xbb, 0x6a, 0xd0, 0x75, 0x74, 0x41, 0x24, 0x35, 0x97, 0xa1, 0x1a, + 0x94, 0x6d, 0xcf, 0xa8, 0x74, 0xd9, 0xe8, 0x11, 0x3e, 0x48, 0xd8, 0x56, 0xa8, 0xb4, 0xe8, 0x5a, + 0xca, 0x5c, 0xe1, 0xac, 0x21, 0xa9, 0x3c, 0x48, 0x6a, 0xed, 0xdf, 0xb3, 0xe0, 0x5c, 0x86, 0x0d, + 0x21, 0x67, 0x55, 0xdb, 0x6e, 0x14, 0xab, 0x44, 0x3f, 0x1a, 0xab, 0xe2, 0xe5, 0x58, 0x61, 0xd0, + 0xfd, 0xc0, 0x99, 0x61, 0x9a, 0x01, 0x0a, 0x1b, 0x1d, 0x01, 0x3d, 0x19, 0x03, 0x44, 0x97, 0x61, + 0xa8, 0x13, 0x91, 0x50, 0xe6, 0xa4, 0x96, 0xfc, 0x9b, 0x29, 0x18, 0x19, 0x84, 0x4a, 0x94, 0xdb, + 0x4a, 0xb7, 0xa7, 0x49, 0x94, 0x5c, 0xbb, 0xc7, 0x61, 0xf6, 0xd7, 0x8a, 0x70, 0x31, 0xd7, 0x46, + 0x98, 0x76, 0x69, 0x2f, 0xf0, 0xdd, 0x38, 0x50, 0xef, 0x42, 0x3c, 0xce, 0x0d, 0x69, 0xef, 0xac, + 0x89, 0x72, 0xac, 0x30, 0xd0, 0xd3, 0x30, 0xcc, 0xee, 0xcf, 0x5d, 0xa9, 0x8c, 0x96, 0xaa, 0x3c, + 0xfc, 0x02, 0x07, 0x0f, 0x9c, 0x26, 0xee, 0x29, 0x18, 0x6a, 0x07, 0x81, 0x97, 0x66, 0x46, 0xb4, + 0xbb, 0x41, 0xe0, 0x61, 0x06, 0x44, 0x9f, 0x11, 0xe3, 0x90, 0x7a, 0x08, 0xc1, 0x4e, 0x2b, 0x88, + 0xb4, 0xc1, 0x78, 0x16, 0x46, 0x77, 0xc9, 0x41, 0xe8, 0xfa, 0xdb, 0xe9, 0x07, 0xb2, 0x1b, 0xbc, + 0x18, 0x4b, 0xb8, 0x99, 0xc9, 0x63, 0xf4, 0xb4, 0xf3, 0xbb, 0x8d, 0xf5, 0x3d, 0xda, 0x7e, 0xa0, + 0x08, 0xd3, 0x78, 0xa9, 0xfa, 0xad, 0x89, 0xb8, 0xdd, 0x3d, 0x11, 0xa7, 0x9d, 0xdf, 0xad, 0xff, + 0x6c, 0xfc, 0xbc, 0x05, 0xd3, 0x2c, 0xda, 0xb5, 0x88, 0xd3, 0xe2, 0x06, 0xfe, 0x19, 0x88, 0x6e, + 0x4f, 0xc1, 0x70, 0x48, 0x1b, 0x4d, 0x27, 0x6d, 0x62, 0x3d, 0xc1, 0x1c, 0x86, 0x1e, 0x87, 0x21, + 0xd6, 0x05, 0x3a, 0x79, 0x13, 0x3c, 0xdf, 0x45, 0xd5, 0x89, 0x1d, 0xcc, 0x4a, 0x99, 0xc3, 0x2c, + 0x26, 0x6d, 0xcf, 0xe5, 0x9d, 0x4e, 0x14, 0xea, 0x9f, 0x0c, 0x87, 0xd9, 0xcc, 0xae, 0x7d, 0x34, + 0x87, 0xd9, 0x6c, 0x92, 0xbd, 0xaf, 0x45, 0xff, 0xa3, 0x00, 0x97, 0x32, 0xeb, 0x0d, 0xec, 0x30, + 0xdb, 0xbb, 0xf6, 0xe9, 0xd8, 0x39, 0x64, 0x9b, 0x1f, 0x14, 0xcf, 0xd0, 0xfc, 0x60, 0x68, 0x50, + 0xc9, 0x71, 0x78, 0x00, 0x3f, 0xd6, 0xcc, 0x21, 0xfb, 0x84, 0xf8, 0xb1, 0x66, 0xf6, 0x2d, 0xe7, + 0x5a, 0xf7, 0x67, 0x85, 0x9c, 0x6f, 0x61, 0x17, 0xbc, 0x2b, 0x94, 0xcf, 0x30, 0x60, 0x24, 0x24, + 0xe1, 0x09, 0xce, 0x63, 0x78, 0x19, 0x56, 0x50, 0xe4, 0x6a, 0x1e, 0xa1, 0x85, 0xfc, 0x94, 0x9e, + 0xb9, 0x4d, 0xcd, 0x9b, 0xef, 0x1f, 0x7a, 0x50, 0x99, 0xb4, 0x77, 0xe8, 0x9a, 0x76, 0x29, 0x2f, + 0x0e, 0x7e, 0x29, 0x9f, 0xc8, 0xbe, 0x90, 0xa3, 0x45, 0x98, 0xde, 0x73, 0x7d, 0xca, 0x36, 0x0f, + 0x4c, 0x51, 0x54, 0x05, 0x48, 0x58, 0x33, 0xc1, 0x38, 0x8d, 0x3f, 0xf7, 0x06, 0x4c, 0x3e, 0xb8, + 0x16, 0xf1, 0x1b, 0x45, 0x78, 0xac, 0xc7, 0xb6, 0xe7, 0xbc, 0xde, 0x98, 0x03, 0x8d, 0xd7, 0x77, + 0xcd, 0x43, 0x1d, 0xce, 0x6f, 0x75, 0x3c, 0xef, 0x80, 0x59, 0xf8, 0x91, 0x96, 0xc4, 0x10, 0xb2, + 0xa2, 0x0a, 0x65, 0xbf, 0x9a, 0x81, 0x83, 0x33, 0x6b, 0xa2, 0xb7, 0x00, 0x05, 0x22, 0x9f, 0x70, + 0x12, 0x2a, 0x87, 0x0d, 0x7c, 0x31, 0xd9, 0x8c, 0xb7, 0xba, 0x30, 0x70, 0x46, 0x2d, 0x2a, 0xf4, + 0xd3, 0x53, 0xe9, 0x40, 0x75, 0x2b, 0x25, 0xf4, 0x63, 0x1d, 0x88, 0x4d, 0x5c, 0x74, 0x0d, 0x66, + 0x9d, 0x7d, 0xc7, 0xe5, 0xa1, 0x13, 0x25, 0x01, 0x2e, 0xf5, 0x2b, 0xdd, 0xdd, 0x62, 0x1a, 0x01, + 0x77, 0xd7, 0x49, 0xb9, 0xa4, 0x8e, 0xe4, 0xbb, 0xa4, 0xf6, 0xe6, 0x8b, 0xfd, 0x54, 0xb1, 0xf6, + 0x7f, 0xb6, 0xe8, 0xf1, 0xa5, 0xa5, 0xf9, 0xd7, 0x33, 0x2a, 0x28, 0x95, 0xa2, 0xe6, 0x1d, 0xaa, + 0xc6, 0x61, 0x59, 0x07, 0x62, 0x13, 0x97, 0x2f, 0x88, 0x28, 0x71, 0x54, 0x30, 0x44, 0x77, 0xe1, + 0xfe, 0xad, 0x30, 0xd0, 0x97, 0x61, 0xb4, 0xe5, 0xee, 0xbb, 0x51, 0x10, 0x8a, 0xcd, 0x72, 0x42, + 0x63, 0xf2, 0x84, 0x0f, 0x56, 0x39, 0x19, 0x2c, 0xe9, 0xd9, 0x3f, 0x50, 0x80, 0x49, 0xd9, 0xe2, + 0xdb, 0x9d, 0x20, 0x76, 0xce, 0xe0, 0x58, 0xbe, 0x66, 0x1c, 0xcb, 0x9f, 0xe9, 0xe5, 0x03, 0xcf, + 0xba, 0x94, 0x7b, 0x1c, 0xdf, 0x4a, 0x1d, 0xc7, 0xcf, 0xf4, 0x27, 0xd5, 0xfb, 0x18, 0xfe, 0x17, + 0x16, 0xcc, 0x1a, 0xf8, 0x67, 0x70, 0x1a, 0xac, 0x9a, 0xa7, 0xc1, 0x93, 0x7d, 0xbf, 0x21, 0xe7, + 0x14, 0xf8, 0x7a, 0x21, 0xd5, 0x77, 0xc6, 0xfd, 0xdf, 0x87, 0xa1, 0x1d, 0x27, 0x6c, 0xf5, 0x0a, + 0x00, 0xdc, 0x55, 0x69, 0xfe, 0xba, 0x13, 0xb6, 0x38, 0x0f, 0x7f, 0x41, 0x65, 0x21, 0x75, 0xc2, + 0x56, 0x5f, 0xbf, 0x1c, 0xd6, 0x14, 0x7a, 0x1d, 0x46, 0xa2, 0x66, 0xd0, 0x56, 0x36, 0x79, 0x97, + 0x79, 0x86, 0x52, 0x5a, 0x72, 0x7c, 0x58, 0x41, 0x66, 0x73, 0xb4, 0x18, 0x0b, 0xfc, 0xb9, 0x6d, + 0x28, 0xa9, 0xa6, 0x1f, 0xaa, 0x47, 0xc5, 0x6f, 0x17, 0xe1, 0x5c, 0xc6, 0xba, 0x40, 0x91, 0x31, + 0x5a, 0x2f, 0x0d, 0xb8, 0x9c, 0x3e, 0xe2, 0x78, 0x45, 0xec, 0xc6, 0xd2, 0x12, 0xf3, 0x3f, 0x70, + 0xa3, 0xb7, 0x23, 0x92, 0x6e, 0x94, 0x16, 0xf5, 0x6f, 0x94, 0x36, 0x76, 0x66, 0x43, 0x4d, 0x1b, + 0x52, 0x3d, 0x7d, 0xa8, 0x73, 0xfa, 0xc7, 0x45, 0x38, 0x9f, 0x15, 0x3a, 0x03, 0x7d, 0x77, 0x2a, + 0x9d, 0xd0, 0x2b, 0x83, 0x06, 0xdd, 0xe0, 0x39, 0x86, 0x44, 0xac, 0xb1, 0x79, 0x33, 0xc1, 0x50, + 0xdf, 0x61, 0x16, 0x6d, 0x32, 0x47, 0xb9, 0x90, 0xa7, 0x81, 0x92, 0x5b, 0xfc, 0x73, 0x03, 0x77, + 0x40, 0xe4, 0x8f, 0x8a, 0x52, 0x8e, 0x72, 0xb2, 0xb8, 0xbf, 0xa3, 0x9c, 0x6c, 0x79, 0xce, 0x85, + 0x71, 0xed, 0x6b, 0x1e, 0xea, 0x8c, 0xef, 0xd2, 0x13, 0x45, 0xeb, 0xf7, 0x43, 0x9d, 0xf5, 0x1f, + 0xb5, 0x20, 0x65, 0x09, 0xa7, 0x54, 0x52, 0x56, 0xae, 0x4a, 0xea, 0x32, 0x0c, 0x85, 0x81, 0x47, + 0xd2, 0x19, 0x66, 0x70, 0xe0, 0x11, 0xcc, 0x20, 0x14, 0x23, 0x4e, 0x14, 0x12, 0x13, 0xfa, 0x65, + 0x4b, 0x5c, 0xa3, 0x9e, 0x82, 0x61, 0x8f, 0xec, 0x13, 0xa9, 0x8d, 0x50, 0x3c, 0xf9, 0x26, 0x2d, + 0xc4, 0x1c, 0x66, 0xff, 0xfc, 0x10, 0x3c, 0xd1, 0xd3, 0xd5, 0x94, 0x5e, 0x59, 0xb6, 0x9d, 0x98, + 0xdc, 0x73, 0x0e, 0xd2, 0xf1, 0xaf, 0xaf, 0xf1, 0x62, 0x2c, 0xe1, 0xcc, 0x6e, 0x97, 0x87, 0xd0, + 0x4c, 0x29, 0xf0, 0x44, 0xe4, 0x4c, 0x01, 0x35, 0x15, 0x47, 0xc5, 0xd3, 0x50, 0x1c, 0x5d, 0x05, + 0x88, 0x22, 0x6f, 0xc5, 0xa7, 0x12, 0x58, 0x4b, 0x18, 0x04, 0x27, 0xa1, 0x56, 0x1b, 0x37, 0x05, + 0x04, 0x6b, 0x58, 0xa8, 0x0a, 0x33, 0xed, 0x30, 0x88, 0xb9, 0x3e, 0xb4, 0xca, 0x4d, 0x51, 0x86, + 0x4d, 0x2f, 0xbf, 0x7a, 0x0a, 0x8e, 0xbb, 0x6a, 0xa0, 0x57, 0x61, 0x5c, 0x78, 0xfe, 0xd5, 0x83, + 0xc0, 0x13, 0xaa, 0x1a, 0x65, 0xd8, 0xd0, 0x48, 0x40, 0x58, 0xc7, 0xd3, 0xaa, 0x31, 0x25, 0xeb, + 0x68, 0x66, 0x35, 0xae, 0x68, 0xd5, 0xf0, 0x52, 0x61, 0x74, 0xc6, 0x06, 0x0a, 0xa3, 0x93, 0x28, + 0xaf, 0x4a, 0x03, 0xbf, 0x2b, 0x41, 0x5f, 0x75, 0xcf, 0xcf, 0x0c, 0xc1, 0x39, 0xb1, 0x70, 0x1e, + 0xf6, 0x72, 0xb9, 0xdd, 0xbd, 0x5c, 0x4e, 0x43, 0xbd, 0xf5, 0xad, 0x35, 0x73, 0xd6, 0x6b, 0xe6, + 0x97, 0x8a, 0x30, 0xc2, 0xa7, 0xe2, 0x0c, 0x64, 0xf8, 0x55, 0xa1, 0xf4, 0xeb, 0x11, 0x40, 0x86, + 0xf7, 0x65, 0xbe, 0xea, 0xc4, 0x0e, 0x3f, 0xbf, 0x14, 0x1b, 0x4d, 0xd4, 0x83, 0x68, 0xde, 0x60, + 0xb4, 0x73, 0x29, 0xad, 0x16, 0x70, 0x1a, 0x1a, 0xdb, 0xfd, 0x0a, 0x40, 0xc4, 0x52, 0xe8, 0x53, + 0x1a, 0x22, 0x14, 0xd1, 0x73, 0x3d, 0x5a, 0x6f, 0x28, 0x64, 0xde, 0x87, 0x64, 0x09, 0x2a, 0x00, + 0xd6, 0x28, 0xce, 0xbd, 0x06, 0x25, 0x85, 0xdc, 0x4f, 0x05, 0x30, 0xa1, 0x9f, 0x7a, 0x5f, 0x84, + 0xe9, 0x54, 0x5b, 0x27, 0xd2, 0x20, 0xfc, 0x82, 0x05, 0xd3, 0xbc, 0xcb, 0x2b, 0xfe, 0xbe, 0xd8, + 0xec, 0x1f, 0xc0, 0x79, 0x2f, 0x63, 0xd3, 0x89, 0x19, 0x1d, 0x7c, 0x93, 0x2a, 0x8d, 0x41, 0x16, + 0x14, 0x67, 0xb6, 0x81, 0xae, 0xc0, 0x18, 0x77, 0x74, 0x71, 0x3c, 0xe1, 0x9c, 0x30, 0xc1, 0x53, + 0x52, 0xf0, 0x32, 0xac, 0xa0, 0xf6, 0xef, 0x58, 0x30, 0xcb, 0x7b, 0x7e, 0x83, 0x1c, 0xa8, 0xdb, + 0xf1, 0xc7, 0xd9, 0x77, 0x91, 0x71, 0xa3, 0x90, 0x93, 0x71, 0x43, 0xff, 0xb4, 0x62, 0xcf, 0x4f, + 0xfb, 0x69, 0x0b, 0xc4, 0x0a, 0x3c, 0x83, 0x7b, 0xe0, 0xb7, 0x9b, 0xf7, 0xc0, 0xb9, 0xfc, 0x45, + 0x9d, 0x73, 0x01, 0xfc, 0x53, 0x0b, 0x66, 0x38, 0x42, 0xf2, 0x10, 0xf9, 0xb1, 0xce, 0xc3, 0x20, + 0x69, 0xe0, 0x54, 0xde, 0xed, 0xec, 0x8f, 0x32, 0x26, 0x6b, 0xa8, 0xe7, 0x64, 0xb5, 0xe4, 0x06, + 0x3a, 0x41, 0x7a, 0xc3, 0x13, 0x07, 0x89, 0xb5, 0xff, 0xc8, 0x02, 0xc4, 0x9b, 0x31, 0xce, 0x65, + 0x7a, 0xda, 0xb1, 0x52, 0x4d, 0x13, 0x94, 0xb0, 0x1a, 0x05, 0xc1, 0x1a, 0xd6, 0xa9, 0x0c, 0x4f, + 0xea, 0x35, 0xb9, 0xd8, 0xff, 0x35, 0xf9, 0x04, 0x23, 0xfa, 0xd7, 0x87, 0x20, 0x6d, 0x09, 0x8d, + 0xee, 0xc0, 0x44, 0xd3, 0x69, 0x3b, 0x9b, 0xae, 0xe7, 0xc6, 0x2e, 0x89, 0x7a, 0x99, 0xa1, 0x2c, + 0x6b, 0x78, 0xe2, 0x9d, 0x50, 0x2b, 0xc1, 0x06, 0x1d, 0x34, 0x0f, 0xd0, 0x0e, 0xdd, 0x7d, 0xd7, + 0x23, 0xdb, 0xec, 0x2a, 0xcc, 0xdc, 0xa1, 0xb8, 0x6d, 0x85, 0x2c, 0xc5, 0x1a, 0x46, 0x86, 0xfb, + 0x4c, 0xf1, 0xe1, 0xb9, 0xcf, 0x0c, 0x9d, 0xd0, 0x7d, 0x66, 0x78, 0x20, 0xf7, 0x19, 0x0c, 0x8f, + 0xc8, 0xb3, 0x9b, 0xfe, 0x5f, 0x75, 0x3d, 0x22, 0x04, 0x36, 0xee, 0x24, 0x35, 0x77, 0x74, 0x58, + 0x79, 0x04, 0x67, 0x62, 0xe0, 0x9c, 0x9a, 0xe8, 0x4b, 0x50, 0x76, 0x3c, 0x2f, 0xb8, 0xa7, 0x46, + 0x6d, 0x25, 0x6a, 0x3a, 0x5e, 0x12, 0x33, 0x7d, 0x6c, 0xe9, 0xf1, 0xa3, 0xc3, 0x4a, 0x79, 0x31, + 0x07, 0x07, 0xe7, 0xd6, 0xb6, 0x77, 0xe1, 0x5c, 0x83, 0x84, 0x32, 0x63, 0xaa, 0xda, 0x62, 0x1b, + 0x50, 0x0a, 0x53, 0x4c, 0x65, 0xa0, 0x58, 0x25, 0x5a, 0x3c, 0x4b, 0xc9, 0x44, 0x12, 0x42, 0xf6, + 0x9f, 0x58, 0x30, 0x2a, 0xac, 0xab, 0xcf, 0x40, 0x96, 0x59, 0x34, 0xf4, 0x91, 0x95, 0x6c, 0xc6, + 0xcb, 0x3a, 0x93, 0xab, 0x89, 0xac, 0xa5, 0x34, 0x91, 0x4f, 0xf6, 0x22, 0xd2, 0x5b, 0x07, 0xf9, + 0xc3, 0x45, 0x98, 0x32, 0x2d, 0xcb, 0xcf, 0x60, 0x08, 0xd6, 0x61, 0x34, 0x12, 0x6e, 0x0c, 0x85, + 0x7c, 0xfb, 0xd5, 0xf4, 0x24, 0x26, 0x56, 0x2e, 0xc2, 0x71, 0x41, 0x12, 0xc9, 0xf4, 0x8f, 0x28, + 0x3e, 0x44, 0xff, 0x88, 0x7e, 0xc6, 0xfd, 0x43, 0xa7, 0x61, 0xdc, 0x6f, 0xff, 0x32, 0x63, 0xfe, + 0x7a, 0xf9, 0x19, 0xc8, 0x05, 0xd7, 0xcc, 0x63, 0xc2, 0xee, 0xb1, 0xb2, 0x44, 0xa7, 0x72, 0xe4, + 0x83, 0x7f, 0x6c, 0xc1, 0xb8, 0x40, 0x3c, 0x83, 0x6e, 0x7f, 0x87, 0xd9, 0xed, 0xc7, 0x7a, 0x74, + 0x3b, 0xa7, 0xbf, 0x7f, 0xb7, 0xa0, 0xfa, 0x5b, 0x0f, 0xc2, 0x78, 0xa0, 0x1c, 0x1a, 0x63, 0xf4, + 0x36, 0x18, 0x34, 0x03, 0x4f, 0x1c, 0xe6, 0x8f, 0x27, 0x7e, 0xb2, 0xbc, 0xfc, 0x58, 0xfb, 0x8d, + 0x15, 0x36, 0x73, 0xe3, 0x0c, 0xc2, 0x58, 0x1c, 0xa0, 0x89, 0x1b, 0x67, 0x10, 0xc6, 0x98, 0x41, + 0x50, 0x0b, 0x20, 0x76, 0xc2, 0x6d, 0x12, 0xd3, 0x32, 0xe1, 0x72, 0x9f, 0xbf, 0x0b, 0x3b, 0xb1, + 0xeb, 0xcd, 0xbb, 0x7e, 0x1c, 0xc5, 0xe1, 0x7c, 0xcd, 0x8f, 0x6f, 0x85, 0xfc, 0x6e, 0xa0, 0x39, + 0xbe, 0x2a, 0x5a, 0x58, 0xa3, 0x2b, 0x3d, 0xaf, 0x58, 0x1b, 0xc3, 0xe6, 0x43, 0xe1, 0xba, 0x28, + 0xc7, 0x0a, 0xc3, 0x7e, 0x8d, 0xf1, 0x64, 0x36, 0x40, 0x27, 0xf3, 0x49, 0xfd, 0xcd, 0x31, 0x35, + 0xb4, 0xec, 0x95, 0xa0, 0xaa, 0x7b, 0xbe, 0xf6, 0x66, 0x81, 0xb4, 0x61, 0xdd, 0x2d, 0x20, 0x71, + 0x8f, 0x45, 0xdf, 0xd9, 0xf5, 0x7e, 0xfc, 0x62, 0x1f, 0x5e, 0x7a, 0x82, 0x17, 0x63, 0x16, 0x88, + 0x95, 0x05, 0xac, 0xac, 0xd5, 0xd3, 0x59, 0x4e, 0x96, 0x25, 0x00, 0x27, 0x38, 0x68, 0x41, 0xdc, + 0x2c, 0xb9, 0x7e, 0xee, 0xb1, 0xd4, 0xcd, 0x52, 0x7e, 0xbe, 0x76, 0xb5, 0x7c, 0x09, 0xc6, 0x55, + 0xe6, 0xb8, 0x3a, 0x4f, 0xc0, 0x25, 0x02, 0x10, 0xac, 0x24, 0xc5, 0x58, 0xc7, 0x41, 0x1b, 0x30, + 0x1d, 0xf1, 0xb4, 0x76, 0xd2, 0x19, 0x4a, 0xe8, 0x0d, 0x9e, 0x93, 0xef, 0xce, 0x0d, 0x13, 0x7c, + 0xcc, 0x8a, 0xf8, 0x66, 0x95, 0xee, 0x53, 0x69, 0x12, 0xe8, 0x4d, 0x98, 0xf2, 0xf4, 0xf4, 0xde, + 0x75, 0xa1, 0x56, 0x50, 0x66, 0x99, 0x46, 0xf2, 0xef, 0x3a, 0x4e, 0x61, 0x53, 0x21, 0x40, 0x2f, + 0x11, 0xd1, 0xcb, 0x1c, 0x7f, 0x9b, 0x44, 0x22, 0xef, 0x15, 0x13, 0x02, 0x6e, 0xe6, 0xe0, 0xe0, + 0xdc, 0xda, 0xe8, 0x75, 0x98, 0x90, 0x9f, 0xaf, 0x39, 0x07, 0x26, 0xc6, 0xbf, 0x1a, 0x0c, 0x1b, + 0x98, 0xe8, 0x1e, 0x5c, 0x90, 0xff, 0x37, 0x42, 0x67, 0x6b, 0xcb, 0x6d, 0x0a, 0xdf, 0xcc, 0x71, + 0x46, 0x62, 0x51, 0x7a, 0x42, 0xac, 0x64, 0x21, 0x1d, 0x1f, 0x56, 0x2e, 0x8b, 0x51, 0xcb, 0x84, + 0xb3, 0x49, 0xcc, 0xa6, 0x8f, 0xd6, 0xe0, 0xdc, 0x0e, 0x71, 0xbc, 0x78, 0x67, 0x79, 0x87, 0x34, + 0x77, 0xe5, 0x26, 0x62, 0x2e, 0x87, 0x9a, 0xc9, 0xec, 0xf5, 0x6e, 0x14, 0x9c, 0x55, 0x0f, 0xbd, + 0x0b, 0xe5, 0x76, 0x67, 0xd3, 0x73, 0xa3, 0x9d, 0xf5, 0x20, 0x66, 0x4f, 0xdd, 0x2a, 0xf1, 0x9a, + 0xf0, 0x4d, 0x54, 0xee, 0x96, 0xf5, 0x1c, 0x3c, 0x9c, 0x4b, 0x01, 0x7d, 0x00, 0x17, 0x52, 0x8b, + 0x41, 0x78, 0x4a, 0x4d, 0xe5, 0xc7, 0x82, 0x6c, 0x64, 0x55, 0xe0, 0x1e, 0xb3, 0x99, 0x20, 0x9c, + 0xdd, 0xc4, 0x47, 0x33, 0x80, 0x78, 0x9f, 0x56, 0xd6, 0xa4, 0x1b, 0xf4, 0x55, 0x98, 0xd0, 0x57, + 0x91, 0x38, 0x60, 0x9e, 0xee, 0x97, 0xca, 0x5e, 0xc8, 0x46, 0x6a, 0x45, 0xe9, 0x30, 0x6c, 0x50, + 0xb4, 0x09, 0x64, 0x7f, 0x1f, 0xba, 0x09, 0x63, 0x4d, 0xcf, 0x25, 0x7e, 0x5c, 0xab, 0xf7, 0xf2, + 0xa9, 0x5f, 0x16, 0x38, 0x62, 0xc0, 0x44, 0xf0, 0x3c, 0x5e, 0x86, 0x15, 0x05, 0xfb, 0x57, 0x0b, + 0x50, 0xe9, 0x13, 0x89, 0x31, 0xa5, 0x03, 0xb4, 0x06, 0xd2, 0x01, 0x2e, 0xca, 0x34, 0x72, 0xeb, + 0xa9, 0xfb, 0x67, 0x2a, 0x45, 0x5c, 0x72, 0x0b, 0x4d, 0xe3, 0x0f, 0x6c, 0x37, 0xa9, 0xab, 0x11, + 0x87, 0xfa, 0x5a, 0xf4, 0x1a, 0xcf, 0x07, 0xc3, 0x83, 0x4b, 0xf4, 0xb9, 0xaa, 0x60, 0xfb, 0x97, + 0x0b, 0x70, 0x41, 0x0d, 0xe1, 0x37, 0xef, 0xc0, 0xdd, 0xee, 0x1e, 0xb8, 0x53, 0x50, 0xa4, 0xdb, + 0xb7, 0x60, 0xa4, 0x71, 0x10, 0x35, 0x63, 0x6f, 0x00, 0x01, 0xe8, 0x29, 0x33, 0xb6, 0x8c, 0x3a, + 0xa6, 0x8d, 0xf8, 0x32, 0x7f, 0xc5, 0x82, 0xe9, 0x8d, 0xe5, 0x7a, 0x23, 0x68, 0xee, 0x92, 0x78, + 0x91, 0xab, 0x89, 0xb0, 0x90, 0x7f, 0xac, 0x07, 0x94, 0x6b, 0xb2, 0x24, 0xa6, 0xcb, 0x30, 0xb4, + 0x13, 0x44, 0x71, 0xfa, 0x95, 0xed, 0x7a, 0x10, 0xc5, 0x98, 0x41, 0xec, 0xdf, 0xb5, 0x60, 0x98, + 0x25, 0x3f, 0xed, 0x97, 0x24, 0x77, 0x90, 0xef, 0x42, 0xaf, 0xc2, 0x08, 0xd9, 0xda, 0x22, 0xcd, + 0x58, 0xcc, 0xaa, 0xf4, 0xae, 0x1b, 0x59, 0x61, 0xa5, 0xf4, 0xd0, 0x67, 0x8d, 0xf1, 0xbf, 0x58, + 0x20, 0xa3, 0xbb, 0x50, 0x8a, 0xdd, 0x3d, 0xb2, 0xd8, 0x6a, 0x89, 0x77, 0x8a, 0x07, 0x70, 0x66, + 0xdc, 0x90, 0x04, 0x70, 0x42, 0xcb, 0xfe, 0x5a, 0x01, 0x20, 0x71, 0xe8, 0xed, 0xf7, 0x89, 0x4b, + 0x5d, 0x79, 0x80, 0x9f, 0xce, 0xc8, 0x03, 0x8c, 0x12, 0x82, 0x19, 0x59, 0x80, 0xd5, 0x30, 0x15, + 0x07, 0x1a, 0xa6, 0xa1, 0x93, 0x0c, 0xd3, 0x32, 0xcc, 0x26, 0x0e, 0xc9, 0x66, 0x74, 0x06, 0x16, + 0x99, 0x7d, 0x23, 0x0d, 0xc4, 0xdd, 0xf8, 0xf6, 0xf7, 0x5b, 0x20, 0xdc, 0x0d, 0x06, 0x58, 0xcc, + 0xef, 0xc8, 0x94, 0x9d, 0x46, 0x40, 0xd7, 0xcb, 0xf9, 0xfe, 0x17, 0x22, 0x8c, 0xab, 0x3a, 0x3c, + 0x8c, 0xe0, 0xad, 0x06, 0x2d, 0xbb, 0x05, 0x02, 0x5a, 0x25, 0x4c, 0xc9, 0xd0, 0xbf, 0x37, 0x57, + 0x01, 0x5a, 0x0c, 0x57, 0x4b, 0x01, 0xa8, 0x58, 0x55, 0x55, 0x41, 0xb0, 0x86, 0x65, 0xff, 0xcd, + 0x02, 0x8c, 0xcb, 0x00, 0xa2, 0xf4, 0x1e, 0xdf, 0xbf, 0x95, 0x13, 0x65, 0x0f, 0x60, 0x39, 0x33, + 0x29, 0x61, 0x15, 0x64, 0x5e, 0xcf, 0x99, 0x29, 0x01, 0x38, 0xc1, 0x41, 0xcf, 0xc2, 0x68, 0xd4, + 0xd9, 0x64, 0xe8, 0x29, 0x23, 0xfa, 0x06, 0x2f, 0xc6, 0x12, 0x8e, 0xbe, 0x04, 0x33, 0xbc, 0x5e, + 0x18, 0xb4, 0x9d, 0x6d, 0xae, 0x41, 0x1a, 0x56, 0x5e, 0x6d, 0x33, 0x6b, 0x29, 0xd8, 0xf1, 0x61, + 0xe5, 0x7c, 0xba, 0x8c, 0xe9, 0x1e, 0xbb, 0xa8, 0xd0, 0x7d, 0x31, 0x93, 0x76, 0x98, 0x41, 0xd7, + 0x61, 0x84, 0xb3, 0x3c, 0xc1, 0x82, 0x7a, 0xbc, 0x28, 0x69, 0x6e, 0x36, 0x2c, 0x9c, 0xba, 0xe0, + 0x9a, 0xa2, 0x3e, 0x7a, 0x17, 0xc6, 0x5b, 0xc1, 0x3d, 0xff, 0x9e, 0x13, 0xb6, 0x16, 0xeb, 0x35, + 0xb1, 0x6a, 0x32, 0x25, 0xa7, 0x6a, 0x82, 0xa6, 0xbb, 0xee, 0x30, 0xed, 0x69, 0x02, 0xc2, 0x3a, + 0x39, 0xb4, 0xc1, 0x62, 0x3c, 0xf1, 0xa4, 0xf6, 0xbd, 0xac, 0xce, 0x54, 0x1e, 0x7c, 0x8d, 0xf2, + 0xa4, 0x08, 0x04, 0x25, 0x52, 0xe2, 0x27, 0x84, 0xec, 0x0f, 0xcf, 0x81, 0xb1, 0x5a, 0x8d, 0xec, + 0x01, 0xd6, 0x29, 0x65, 0x0f, 0xc0, 0x30, 0x46, 0xf6, 0xda, 0xf1, 0x41, 0xd5, 0x0d, 0x7b, 0xa5, + 0x9f, 0x59, 0x11, 0x38, 0xdd, 0x34, 0x25, 0x04, 0x2b, 0x3a, 0xd9, 0x29, 0x1e, 0x8a, 0x1f, 0x63, + 0x8a, 0x87, 0xa1, 0x33, 0x4c, 0xf1, 0xb0, 0x0e, 0xa3, 0xdb, 0x6e, 0x8c, 0x49, 0x3b, 0x10, 0xc7, + 0x7d, 0xe6, 0x4a, 0xb8, 0xc6, 0x51, 0xba, 0x03, 0x8c, 0x0b, 0x00, 0x96, 0x44, 0xd0, 0x5b, 0x6a, + 0x0f, 0x8c, 0xe4, 0x4b, 0xcb, 0xdd, 0x8f, 0x0f, 0x99, 0xbb, 0x40, 0xa4, 0x74, 0x18, 0x7d, 0xd0, + 0x94, 0x0e, 0xab, 0x32, 0x11, 0xc3, 0x58, 0xbe, 0x91, 0x26, 0xcb, 0xb3, 0xd0, 0x27, 0xfd, 0x82, + 0x91, 0xb2, 0xa2, 0x74, 0x7a, 0x29, 0x2b, 0xbe, 0xdf, 0x82, 0x0b, 0xed, 0xac, 0xec, 0x2d, 0x22, + 0x91, 0xc2, 0xab, 0x03, 0xa7, 0xa7, 0x31, 0x1a, 0x64, 0xd7, 0xa6, 0x4c, 0x34, 0x9c, 0xdd, 0x1c, + 0x1d, 0xe8, 0x70, 0xb3, 0x25, 0xb2, 0x2f, 0x3c, 0x95, 0x93, 0xfb, 0xa2, 0x47, 0xc6, 0x8b, 0x8d, + 0x8c, 0x8c, 0x0b, 0x9f, 0xce, 0xcb, 0xb8, 0x30, 0x70, 0x9e, 0x85, 0x24, 0xeb, 0xc5, 0xe4, 0x47, + 0xce, 0x7a, 0xf1, 0x96, 0xca, 0x7a, 0xd1, 0x23, 0x92, 0x0e, 0xcf, 0x69, 0xd1, 0x37, 0xd7, 0x85, + 0x96, 0xaf, 0x62, 0xfa, 0x74, 0xf2, 0x55, 0x18, 0xcc, 0x9e, 0xa7, 0x4c, 0x78, 0xbe, 0x0f, 0xb3, + 0x37, 0xe8, 0xf6, 0x66, 0xf7, 0x3c, 0x37, 0xc7, 0xec, 0x03, 0xe5, 0xe6, 0xb8, 0xa3, 0xe7, 0xba, + 0x40, 0x7d, 0x92, 0x39, 0x50, 0xa4, 0x01, 0x33, 0x5c, 0xdc, 0xd1, 0x8f, 0xa0, 0x73, 0xf9, 0x74, + 0xd5, 0x49, 0xd3, 0x4d, 0x37, 0xeb, 0x10, 0xea, 0xce, 0x9c, 0x71, 0xfe, 0x6c, 0x32, 0x67, 0x5c, + 0x38, 0xf5, 0xcc, 0x19, 0x8f, 0x9c, 0x41, 0xe6, 0x8c, 0x47, 0x3f, 0xd6, 0xcc, 0x19, 0xe5, 0x87, + 0x90, 0x39, 0x63, 0x3d, 0xc9, 0x9c, 0x71, 0x31, 0x7f, 0x4a, 0x32, 0xac, 0xd2, 0x72, 0xf2, 0x65, + 0xdc, 0x81, 0x52, 0x5b, 0xfa, 0x54, 0x8b, 0x50, 0x3f, 0xd9, 0x29, 0xfb, 0xb2, 0x1c, 0xaf, 0xf9, + 0x94, 0x28, 0x10, 0x4e, 0x48, 0x51, 0xba, 0x49, 0xfe, 0x8c, 0xc7, 0x7a, 0x28, 0xc6, 0xb2, 0x54, + 0x0e, 0xf9, 0x59, 0x33, 0xec, 0xbf, 0x5a, 0x80, 0x4b, 0xbd, 0xd7, 0x75, 0xa2, 0xaf, 0xa8, 0x27, + 0xfa, 0xf5, 0x94, 0xbe, 0x82, 0x5f, 0x02, 0x12, 0xac, 0x81, 0x03, 0x4f, 0x5c, 0x83, 0x59, 0x65, + 0x8e, 0xe6, 0xb9, 0xcd, 0x03, 0x2d, 0x95, 0x9f, 0x72, 0x8d, 0x69, 0xa4, 0x11, 0x70, 0x77, 0x1d, + 0xb4, 0x08, 0xd3, 0x46, 0x61, 0xad, 0x2a, 0x84, 0x7d, 0xa5, 0x20, 0x69, 0x98, 0x60, 0x9c, 0xc6, + 0xb7, 0xbf, 0x6e, 0xc1, 0xa3, 0x39, 0x21, 0xab, 0x07, 0x8e, 0xab, 0xb0, 0x05, 0xd3, 0x6d, 0xb3, + 0x6a, 0x9f, 0xf0, 0x2b, 0x46, 0x60, 0x6c, 0xd5, 0xd7, 0x14, 0x00, 0xa7, 0x89, 0x2e, 0x5d, 0xf9, + 0xf5, 0xdf, 0xbf, 0xf4, 0xa9, 0xdf, 0xfa, 0xfd, 0x4b, 0x9f, 0xfa, 0x9d, 0xdf, 0xbf, 0xf4, 0xa9, + 0xbf, 0x78, 0x74, 0xc9, 0xfa, 0xf5, 0xa3, 0x4b, 0xd6, 0x6f, 0x1d, 0x5d, 0xb2, 0x7e, 0xe7, 0xe8, + 0x92, 0xf5, 0x7b, 0x47, 0x97, 0xac, 0xaf, 0xfd, 0xc1, 0xa5, 0x4f, 0xbd, 0x53, 0xd8, 0x7f, 0xe9, + 0xff, 0x07, 0x00, 0x00, 0xff, 0xff, 0x18, 0x48, 0x7e, 0xc6, 0x9b, 0xdf, 0x00, 0x00, } diff --git a/staging/src/k8s.io/api/core/v1/generated.proto b/staging/src/k8s.io/api/core/v1/generated.proto index 7bd3bc2c332..6bef759dece 100644 --- a/staging/src/k8s.io/api/core/v1/generated.proto +++ b/staging/src/k8s.io/api/core/v1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -170,6 +170,23 @@ message Binding { optional ObjectReference target = 2; } +// Represents storage that is managed by an external CSI volume driver +message CSIPersistentVolumeSource { + // Driver is the name of the driver to use for this volume. + // Required. + optional string driver = 1; + + // VolumeHandle is the unique volume name returned by the CSI volume + // plugin’s CreateVolume to refer to the volume on all subsequent calls. + // Required. + optional string volumeHandle = 2; + + // Optional: The value to pass to ControllerPublishVolumeRequest. + // Defaults to false (read/write). + // +optional + optional bool readOnly = 3; +} + // Adds and removes POSIX capabilities from running containers. message Capabilities { // Added capabilities @@ -516,6 +533,13 @@ message Container { // +patchStrategy=merge repeated VolumeMount volumeMounts = 9; + // volumeDevices is the list of block devices to be used by the container. + // This is an alpha feature and may change in the future. + // +patchMergeKey=devicePath + // +patchStrategy=merge + // +optional + repeated VolumeDevice volumeDevices = 21; + // Periodic probe of container liveness. // Container will be restarted if the probe fails. // Cannot be updated. @@ -565,7 +589,7 @@ message Container { // Security options the pod should run with. // More info: https://kubernetes.io/docs/concepts/policy/security-context/ - // More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md + // More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ // +optional optional SecurityContext securityContext = 15; @@ -765,6 +789,10 @@ message DeleteOptions { // 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. // +optional optional string propagationPolicy = 4; } @@ -1001,7 +1029,6 @@ message EnvVarSource { } // Event is a report of an event somewhere in the cluster. -// TODO: Decide whether to store these separately or with the object they apply to. message Event { // Standard object's metadata. // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata @@ -1040,6 +1067,30 @@ message Event { // Type of this event (Normal, Warning), new types could be added in the future // +optional optional string type = 9; + + // Time when this Event was first observed. + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.MicroTime eventTime = 10; + + // Data about the Event series this event represents or nil if it's a singleton Event. + // +optional + optional EventSeries series = 11; + + // What action was taken/failed regarding to the Regarding object. + // +optional + optional string action = 12; + + // Optional secondary object for more complex actions. + // +optional + optional ObjectReference related = 13; + + // Name of the controller that emitted this Event, e.g. `kubernetes.io/kubelet`. + // +optional + optional string reportingComponent = 14; + + // ID of the controller instance, e.g. `kubelet-xyzf`. + // +optional + optional string reportingInstance = 15; } // EventList is a list of events. @@ -1053,6 +1104,19 @@ message EventList { repeated Event items = 2; } +// EventSeries contain information on series of events, i.e. thing that was/is happening +// continously for some time. +message EventSeries { + // Number of occurrences in this series up to the last heartbeat time + optional int32 count = 1; + + // Time of the last occurence observed + optional k8s.io.apimachinery.pkg.apis.meta.v1.MicroTime lastObservedTime = 2; + + // State of this Series: Ongoing or Finished + optional string state = 3; +} + // EventSource contains information for an event. message EventSource { // Component from which the event is generated. @@ -1105,8 +1169,38 @@ message FCVolumeSource { repeated string wwids = 5; } +// FlexPersistentVolumeSource represents a generic persistent volume resource that is +// provisioned/attached using an exec based plugin. +message FlexPersistentVolumeSource { + // Driver is the name of the driver to use for this volume. + optional string driver = 1; + + // Filesystem type to mount. + // Must be a filesystem type supported by the host operating system. + // Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + // +optional + optional string fsType = 2; + + // Optional: SecretRef is reference to the secret object containing + // sensitive information to pass to the plugin scripts. This may be + // empty if no secret object is specified. If the secret object + // contains more than one secret, all secrets are passed to the plugin + // scripts. + // +optional + optional SecretReference secretRef = 3; + + // Optional: Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + optional bool readOnly = 4; + + // Optional: Extra command options if any. + // +optional + map options = 5; +} + // FlexVolume represents a generic volume resource that is -// provisioned/attached using an exec based plugin. This is an alpha feature and may change in future. +// provisioned/attached using an exec based plugin. message FlexVolumeSource { // Driver is the name of the driver to use for this volume. optional string driver = 1; @@ -1299,21 +1393,22 @@ message HostPathVolumeSource { optional string type = 2; } -// Represents an ISCSI disk. +// ISCSIPersistentVolumeSource represents an ISCSI disk. // ISCSI volumes can only be mounted as read/write once. // ISCSI volumes support ownership management and SELinux relabeling. -message ISCSIVolumeSource { - // iSCSI target portal. The portal is either an IP or ip_addr:port if the port +message ISCSIPersistentVolumeSource { + // iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port // is other than default (typically TCP ports 860 and 3260). optional string targetPortal = 1; // Target iSCSI Qualified Name. optional string iqn = 2; - // iSCSI target lun number. + // iSCSI Target Lun number. optional int32 lun = 3; - // Optional: Defaults to 'default' (tcp). iSCSI interface name that uses an iSCSI transport. + // iSCSI Interface Name that uses an iSCSI transport. + // Defaults to 'default' (tcp). // +optional optional string iscsiInterface = 4; @@ -1330,7 +1425,7 @@ message ISCSIVolumeSource { // +optional optional bool readOnly = 6; - // iSCSI target portal List. The portal is either an IP or ip_addr:port if the port + // iSCSI Target Portal List. The Portal is either an IP or ip_addr:port if the port // is other than default (typically TCP ports 860 and 3260). // +optional repeated string portals = 7; @@ -1343,11 +1438,67 @@ message ISCSIVolumeSource { // +optional optional bool chapAuthSession = 11; - // CHAP secret for iSCSI target and initiator authentication + // CHAP Secret for iSCSI target and initiator authentication + // +optional + optional SecretReference secretRef = 10; + + // Custom iSCSI Initiator Name. + // If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface + // : will be created for the connection. + // +optional + optional string initiatorName = 12; +} + +// Represents an ISCSI disk. +// ISCSI volumes can only be mounted as read/write once. +// ISCSI volumes support ownership management and SELinux relabeling. +message ISCSIVolumeSource { + // iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port + // is other than default (typically TCP ports 860 and 3260). + optional string targetPortal = 1; + + // Target iSCSI Qualified Name. + optional string iqn = 2; + + // iSCSI Target Lun number. + optional int32 lun = 3; + + // iSCSI Interface Name that uses an iSCSI transport. + // Defaults to 'default' (tcp). + // +optional + optional string iscsiInterface = 4; + + // Filesystem type of the volume that you want to mount. + // Tip: Ensure that the filesystem type is supported by the host operating system. + // Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + // More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + // TODO: how do we prevent errors in the filesystem from compromising the machine + // +optional + optional string fsType = 5; + + // ReadOnly here will force the ReadOnly setting in VolumeMounts. + // Defaults to false. + // +optional + optional bool readOnly = 6; + + // iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port + // is other than default (typically TCP ports 860 and 3260). + // +optional + repeated string portals = 7; + + // whether support iSCSI Discovery CHAP authentication + // +optional + optional bool chapAuthDiscovery = 8; + + // whether support iSCSI Session CHAP authentication + // +optional + optional bool chapAuthSession = 11; + + // CHAP Secret for iSCSI target and initiator authentication // +optional optional LocalObjectReference secretRef = 10; - // Custom iSCSI initiator name. + // Custom iSCSI Initiator Name. // If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface // : will be created for the connection. // +optional @@ -1442,7 +1593,7 @@ message LimitRangeList { optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; // Items is a list of LimitRange objects. - // More info: https://git.k8s.io/community/contributors/design-proposals/admission_control_limit_range.md + // More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/ repeated LimitRange items = 2; } @@ -1593,7 +1744,7 @@ message NamespaceList { // NamespaceSpec describes the attributes on a Namespace. message NamespaceSpec { // Finalizers is an opaque list of values that must be empty to permanently remove object from storage. - // More info: https://git.k8s.io/community/contributors/design-proposals/namespaces.md#finalizers + // More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/ // +optional repeated string finalizers = 1; } @@ -1601,7 +1752,7 @@ message NamespaceSpec { // NamespaceStatus is information about the current status of a Namespace. message NamespaceStatus { // Phase is the current lifecycle phase of the namespace. - // More info: https://git.k8s.io/community/contributors/design-proposals/namespaces.md#phases + // More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/ // +optional optional string phase = 1; } @@ -2198,6 +2349,12 @@ message PersistentVolumeClaimSpec { // More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 // +optional optional string storageClassName = 5; + + // volumeMode defines what type of volume is required by the claim. + // Value of Filesystem is implied when not included in claim spec. + // This is an alpha feature and may change in the future. + // +optional + optional string volumeMode = 6; } // PersistentVolumeClaimStatus is the current status of a persistent volume claim. @@ -2287,12 +2444,12 @@ message PersistentVolumeSource { // RBD represents a Rados Block Device mount on the host that shares a pod's lifetime. // More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md // +optional - optional RBDVolumeSource rbd = 6; + optional RBDPersistentVolumeSource rbd = 6; // ISCSI represents an ISCSI Disk resource that is attached to a // kubelet's host machine and then exposed to the pod. Provisioned by an admin. // +optional - optional ISCSIVolumeSource iscsi = 7; + optional ISCSIPersistentVolumeSource iscsi = 7; // Cinder represents a cinder volume attached and mounted on kubelets host machine // More info: https://releases.k8s.io/HEAD/examples/mysql-cinder-pd/README.md @@ -2312,10 +2469,9 @@ message PersistentVolumeSource { optional FlockerVolumeSource flocker = 11; // FlexVolume represents a generic volume resource that is - // provisioned/attached using an exec based plugin. This is an - // alpha feature and may change in future. + // provisioned/attached using an exec based plugin. // +optional - optional FlexVolumeSource flexVolume = 12; + optional FlexPersistentVolumeSource flexVolume = 12; // AzureFile represents an Azure File Service mount on the host and bind mount to the pod. // +optional @@ -2342,7 +2498,7 @@ message PersistentVolumeSource { // ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. // +optional - optional ScaleIOVolumeSource scaleIO = 19; + optional ScaleIOPersistentVolumeSource scaleIO = 19; // Local represents directly-attached storage with node affinity // +optional @@ -2352,6 +2508,10 @@ message PersistentVolumeSource { // More info: https://releases.k8s.io/HEAD/examples/volumes/storageos/README.md // +optional optional StorageOSPersistentVolumeSource storageos = 21; + + // CSI represents storage that handled by an external CSI driver + // +optional + optional CSIPersistentVolumeSource csi = 22; } // PersistentVolumeSpec is the specification of a persistent volume. @@ -2393,6 +2553,12 @@ message PersistentVolumeSpec { // More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#mount-options // +optional repeated string mountOptions = 7; + + // volumeMode defines if a volume is intended to be used with a formatted filesystem + // or to remain in raw block state. Value of Filesystem is implied when not included in spec. + // This is an alpha feature and may change in the future. + // +optional + optional string volumeMode = 8; } // PersistentVolumeStatus is the current status of a persistent volume. @@ -2474,7 +2640,7 @@ message PodAffinity { // relative to the given namespace(s)) that this pod should be // co-located (affinity) or not co-located (anti-affinity) with, // where co-located is defined as running on a node whose value of -// the label with key tches that of any node on which +// the label with key matches that of any node on which // a pod of the set of pods is running message PodAffinityTerm { // A label query over a set of resources, in this case pods. @@ -2483,16 +2649,14 @@ message PodAffinityTerm { // namespaces specifies which namespaces the labelSelector applies to (matches against); // null or empty list means "this pod's namespace" + // +optional repeated string namespaces = 2; // This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching // the labelSelector in the specified namespaces, where co-located is defined as running on a node // whose value of the label with key topologyKey matches that of any node on which any of the // selected pods is running. - // For PreferredDuringScheduling pod anti-affinity, empty topologyKey is interpreted as "all topologies" - // ("all topologies" here means all the topologyKeys indicated by scheduler command-line argument --failure-domains); - // for affinity and for RequiredDuringScheduling pod anti-affinity, empty topologyKey is not allowed. - // +optional + // Empty topologyKey is not allowed. optional string topologyKey = 3; } @@ -2583,6 +2747,38 @@ message PodCondition { optional string message = 6; } +// PodDNSConfig defines the DNS parameters of a pod in addition to +// those generated from DNSPolicy. +message PodDNSConfig { + // A list of DNS name server IP addresses. + // This will be appended to the base nameservers generated from DNSPolicy. + // Duplicated nameservers will be removed. + // +optional + repeated string nameservers = 1; + + // A list of DNS search domains for host-name lookup. + // This will be appended to the base search paths generated from DNSPolicy. + // Duplicated search paths will be removed. + // +optional + repeated string searches = 2; + + // A list of DNS resolver options. + // This will be merged with the base options generated from DNSPolicy. + // Duplicated entries will be removed. Resolution options given in Options + // will override those that appear in the base DNSPolicy. + // +optional + repeated PodDNSConfigOption options = 3; +} + +// PodDNSConfigOption defines DNS resolver options of a pod. +message PodDNSConfigOption { + // Required. + optional string name = 1; + + // +optional + optional string value = 2; +} + // PodExecOptions is the query options to a Pod's remote exec call. // --- // TODO: This is largely identical to PodAttachOptions above, make sure they stay in sync and see about merging @@ -2807,10 +3003,13 @@ message PodSpec { // +optional optional int64 activeDeadlineSeconds = 5; - // Set DNS policy for containers within the pod. - // One of 'ClusterFirstWithHostNet', 'ClusterFirst' or 'Default'. + // Set DNS policy for the pod. // Defaults to "ClusterFirst". - // To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'. + // Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. + // DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. + // To have DNS options set along with hostNetwork, you have to specify DNS policy + // explicitly to 'ClusterFirstWithHostNet'. + // Note that 'None' policy is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it. // +optional optional string dnsPolicy = 6; @@ -2919,6 +3118,13 @@ message PodSpec { // The higher the value, the higher the priority. // +optional optional int32 priority = 25; + + // Specifies the DNS parameters of a pod. + // Parameters specified here will be merged to the generated DNS + // configuration based on DNSPolicy. + // This is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it. + // +optional + optional PodDNSConfig dnsConfig = 26; } // PodStatus represents information about the status of a pod. Status may trail the actual @@ -3157,6 +3363,57 @@ message QuobyteVolumeSource { optional string group = 5; } +// Represents a Rados Block Device mount that lasts the lifetime of a pod. +// RBD volumes support ownership management and SELinux relabeling. +message RBDPersistentVolumeSource { + // A collection of Ceph monitors. + // More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it + repeated string monitors = 1; + + // The rados image name. + // More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it + optional string image = 2; + + // Filesystem type of the volume that you want to mount. + // Tip: Ensure that the filesystem type is supported by the host operating system. + // Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + // More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + // TODO: how do we prevent errors in the filesystem from compromising the machine + // +optional + optional string fsType = 3; + + // The rados pool name. + // Default is rbd. + // More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it + // +optional + optional string pool = 4; + + // The rados user name. + // Default is admin. + // More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it + // +optional + optional string user = 5; + + // Keyring is the path to key ring for RBDUser. + // Default is /etc/ceph/keyring. + // More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it + // +optional + optional string keyring = 6; + + // SecretRef is name of the authentication secret for RBDUser. If provided + // overrides keyring. + // Default is nil. + // More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it + // +optional + optional SecretReference secretRef = 7; + + // ReadOnly here will force the ReadOnly setting in VolumeMounts. + // Defaults to false. + // More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it + // +optional + optional bool readOnly = 8; +} + // Represents a Rados Block Device mount that lasts the lifetime of a pod. // RBD volumes support ownership management and SELinux relabeling. message RBDVolumeSource { @@ -3377,14 +3634,14 @@ message ResourceQuotaList { optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; // Items is a list of ResourceQuota objects. - // More info: https://git.k8s.io/community/contributors/design-proposals/admission_control_resource_quota.md + // More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/ repeated ResourceQuota items = 2; } // ResourceQuotaSpec defines the desired hard limits to enforce for Quota. message ResourceQuotaSpec { // Hard is the set of desired hard limits for each named resource. - // More info: https://git.k8s.io/community/contributors/design-proposals/admission_control_resource_quota.md + // More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/ // +optional map hard = 1; @@ -3397,7 +3654,7 @@ message ResourceQuotaSpec { // ResourceQuotaStatus defines the enforced hard limits and observed use. message ResourceQuotaStatus { // Hard is the set of enforced hard limits for each named resource. - // More info: https://git.k8s.io/community/contributors/design-proposals/admission_control_resource_quota.md + // More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/ // +optional map hard = 1; @@ -3440,6 +3697,50 @@ message SELinuxOptions { optional string level = 4; } +// ScaleIOPersistentVolumeSource represents a persistent ScaleIO volume +message ScaleIOPersistentVolumeSource { + // The host address of the ScaleIO API Gateway. + optional string gateway = 1; + + // The name of the storage system as configured in ScaleIO. + optional string system = 2; + + // SecretRef references to the secret for ScaleIO user and other + // sensitive information. If this is not provided, Login operation will fail. + optional SecretReference secretRef = 3; + + // Flag to enable/disable SSL communication with Gateway, default false + // +optional + optional bool sslEnabled = 4; + + // The name of the ScaleIO Protection Domain for the configured storage. + // +optional + optional string protectionDomain = 5; + + // The ScaleIO Storage Pool associated with the protection domain. + // +optional + optional string storagePool = 6; + + // Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. + // +optional + optional string storageMode = 7; + + // The name of a volume already created in the ScaleIO system + // that is associated with this volume source. + optional string volumeName = 8; + + // Filesystem type to mount. + // Must be a filesystem type supported by the host operating system. + // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + // +optional + optional string fsType = 9; + + // Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + optional bool readOnly = 10; +} + // ScaleIOVolumeSource represents a persistent ScaleIO volume message ScaleIOVolumeSource { // The host address of the ScaleIO API Gateway. @@ -3456,15 +3757,15 @@ message ScaleIOVolumeSource { // +optional optional bool sslEnabled = 4; - // The name of the Protection Domain for the configured storage (defaults to "default"). + // The name of the ScaleIO Protection Domain for the configured storage. // +optional optional string protectionDomain = 5; - // The Storage Pool associated with the protection domain (defaults to "default"). + // The ScaleIO Storage Pool associated with the protection domain. // +optional optional string storagePool = 6; - // Indicates whether the storage for a volume should be thick or thin (defaults to "thin"). + // Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. // +optional optional string storageMode = 7; @@ -3883,7 +4184,8 @@ message ServiceSpec { // externalName is the external reference that kubedns or equivalent will // return as a CNAME record for this service. No proxying will be involved. - // Must be a valid DNS name and requires Type to be ExternalName. + // Must be a valid RFC-1123 hostname (https://tools.ietf.org/html/rfc1123) + // and requires Type to be ExternalName. // +optional optional string externalName = 10; @@ -4088,6 +4390,15 @@ message Volume { optional VolumeSource volumeSource = 2; } +// volumeDevice describes a mapping of a raw block device within a container. +message VolumeDevice { + // name must match the name of a persistentVolumeClaim in the pod + optional string name = 1; + + // devicePath is the path inside of the container that the device will be mapped to. + optional string devicePath = 2; +} + // VolumeMount describes a mounting of a Volume within a container. message VolumeMount { // This must match the Name of a Volume. @@ -4196,8 +4507,7 @@ message VolumeSource { optional RBDVolumeSource rbd = 11; // FlexVolume represents a generic volume resource that is - // provisioned/attached using an exec based plugin. This is an - // alpha feature and may change in future. + // provisioned/attached using an exec based plugin. // +optional optional FlexVolumeSource flexVolume = 12; diff --git a/staging/src/k8s.io/api/core/v1/register.go b/staging/src/k8s.io/api/core/v1/register.go index 4916ee3c23c..526e1320ade 100644 --- a/staging/src/k8s.io/api/core/v1/register.go +++ b/staging/src/k8s.io/api/core/v1/register.go @@ -14,8 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -//TODO: this file is going to be moved to k8s.io/api - package v1 import ( @@ -43,7 +41,7 @@ var ( AddToScheme = SchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Pod{}, diff --git a/staging/src/k8s.io/api/core/v1/types.go b/staging/src/k8s.io/api/core/v1/types.go index b9cbf2d4b5f..49ef6109276 100644 --- a/staging/src/k8s.io/api/core/v1/types.go +++ b/staging/src/k8s.io/api/core/v1/types.go @@ -302,8 +302,7 @@ type VolumeSource struct { // +optional RBD *RBDVolumeSource `json:"rbd,omitempty" protobuf:"bytes,11,opt,name=rbd"` // FlexVolume represents a generic volume resource that is - // provisioned/attached using an exec based plugin. This is an - // alpha feature and may change in future. + // provisioned/attached using an exec based plugin. // +optional FlexVolume *FlexVolumeSource `json:"flexVolume,omitempty" protobuf:"bytes,12,opt,name=flexVolume"` // Cinder represents a cinder volume attached and mounted on kubelets host machine @@ -398,11 +397,11 @@ type PersistentVolumeSource struct { // RBD represents a Rados Block Device mount on the host that shares a pod's lifetime. // More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md // +optional - RBD *RBDVolumeSource `json:"rbd,omitempty" protobuf:"bytes,6,opt,name=rbd"` + RBD *RBDPersistentVolumeSource `json:"rbd,omitempty" protobuf:"bytes,6,opt,name=rbd"` // ISCSI represents an ISCSI Disk resource that is attached to a // kubelet's host machine and then exposed to the pod. Provisioned by an admin. // +optional - ISCSI *ISCSIVolumeSource `json:"iscsi,omitempty" protobuf:"bytes,7,opt,name=iscsi"` + ISCSI *ISCSIPersistentVolumeSource `json:"iscsi,omitempty" protobuf:"bytes,7,opt,name=iscsi"` // Cinder represents a cinder volume attached and mounted on kubelets host machine // More info: https://releases.k8s.io/HEAD/examples/mysql-cinder-pd/README.md // +optional @@ -417,10 +416,9 @@ type PersistentVolumeSource struct { // +optional Flocker *FlockerVolumeSource `json:"flocker,omitempty" protobuf:"bytes,11,opt,name=flocker"` // FlexVolume represents a generic volume resource that is - // provisioned/attached using an exec based plugin. This is an - // alpha feature and may change in future. + // provisioned/attached using an exec based plugin. // +optional - FlexVolume *FlexVolumeSource `json:"flexVolume,omitempty" protobuf:"bytes,12,opt,name=flexVolume"` + FlexVolume *FlexPersistentVolumeSource `json:"flexVolume,omitempty" protobuf:"bytes,12,opt,name=flexVolume"` // AzureFile represents an Azure File Service mount on the host and bind mount to the pod. // +optional AzureFile *AzureFilePersistentVolumeSource `json:"azureFile,omitempty" protobuf:"bytes,13,opt,name=azureFile"` @@ -440,7 +438,7 @@ type PersistentVolumeSource struct { PortworxVolume *PortworxVolumeSource `json:"portworxVolume,omitempty" protobuf:"bytes,18,opt,name=portworxVolume"` // ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. // +optional - ScaleIO *ScaleIOVolumeSource `json:"scaleIO,omitempty" protobuf:"bytes,19,opt,name=scaleIO"` + ScaleIO *ScaleIOPersistentVolumeSource `json:"scaleIO,omitempty" protobuf:"bytes,19,opt,name=scaleIO"` // Local represents directly-attached storage with node affinity // +optional Local *LocalVolumeSource `json:"local,omitempty" protobuf:"bytes,20,opt,name=local"` @@ -448,6 +446,9 @@ type PersistentVolumeSource struct { // More info: https://releases.k8s.io/HEAD/examples/volumes/storageos/README.md // +optional StorageOS *StorageOSPersistentVolumeSource `json:"storageos,omitempty" protobuf:"bytes,21,opt,name=storageos"` + // CSI represents storage that handled by an external CSI driver + // +optional + CSI *CSIPersistentVolumeSource `json:"csi,omitempty" protobuf:"bytes,22,opt,name=csi"` } const ( @@ -524,6 +525,11 @@ type PersistentVolumeSpec struct { // More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#mount-options // +optional MountOptions []string `json:"mountOptions,omitempty" protobuf:"bytes,7,opt,name=mountOptions"` + // volumeMode defines if a volume is intended to be used with a formatted filesystem + // or to remain in raw block state. Value of Filesystem is implied when not included in spec. + // This is an alpha feature and may change in the future. + // +optional + VolumeMode *PersistentVolumeMode `json:"volumeMode,omitempty" protobuf:"bytes,8,opt,name=volumeMode,casttype=PersistentVolumeMode"` } // PersistentVolumeReclaimPolicy describes a policy for end-of-life maintenance of persistent volumes. @@ -541,6 +547,16 @@ const ( PersistentVolumeReclaimRetain PersistentVolumeReclaimPolicy = "Retain" ) +// PersistentVolumeMode describes how a volume is intended to be consumed, either Block or Filesystem. +type PersistentVolumeMode string + +const ( + // PersistentVolumeBlock means the volume will not be formatted with a filesystem and will remain a raw block device. + PersistentVolumeBlock PersistentVolumeMode = "Block" + // PersistentVolumeFilesystem means the volume will be or is formatted with a filesystem. + PersistentVolumeFilesystem PersistentVolumeMode = "Filesystem" +) + // PersistentVolumeStatus is the current status of a persistent volume. type PersistentVolumeStatus struct { // Phase indicates if a volume is available, bound to a claim, or released by a claim. @@ -628,6 +644,11 @@ type PersistentVolumeClaimSpec struct { // More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 // +optional StorageClassName *string `json:"storageClassName,omitempty" protobuf:"bytes,5,opt,name=storageClassName"` + // volumeMode defines what type of volume is required by the claim. + // Value of Filesystem is implied when not included in claim spec. + // This is an alpha feature and may change in the future. + // +optional + VolumeMode *PersistentVolumeMode `json:"volumeMode,omitempty" protobuf:"bytes,6,opt,name=volumeMode,casttype=PersistentVolumeMode"` } // PersistentVolumeClaimConditionType is a valid value of PersistentVolumeClaimCondition.Type @@ -838,6 +859,50 @@ type RBDVolumeSource struct { ReadOnly bool `json:"readOnly,omitempty" protobuf:"varint,8,opt,name=readOnly"` } +// Represents a Rados Block Device mount that lasts the lifetime of a pod. +// RBD volumes support ownership management and SELinux relabeling. +type RBDPersistentVolumeSource struct { + // A collection of Ceph monitors. + // More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it + CephMonitors []string `json:"monitors" protobuf:"bytes,1,rep,name=monitors"` + // The rados image name. + // More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it + RBDImage string `json:"image" protobuf:"bytes,2,opt,name=image"` + // Filesystem type of the volume that you want to mount. + // Tip: Ensure that the filesystem type is supported by the host operating system. + // Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + // More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + // TODO: how do we prevent errors in the filesystem from compromising the machine + // +optional + FSType string `json:"fsType,omitempty" protobuf:"bytes,3,opt,name=fsType"` + // The rados pool name. + // Default is rbd. + // More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it + // +optional + RBDPool string `json:"pool,omitempty" protobuf:"bytes,4,opt,name=pool"` + // The rados user name. + // Default is admin. + // More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it + // +optional + RadosUser string `json:"user,omitempty" protobuf:"bytes,5,opt,name=user"` + // Keyring is the path to key ring for RBDUser. + // Default is /etc/ceph/keyring. + // More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it + // +optional + Keyring string `json:"keyring,omitempty" protobuf:"bytes,6,opt,name=keyring"` + // SecretRef is name of the authentication secret for RBDUser. If provided + // overrides keyring. + // Default is nil. + // More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it + // +optional + SecretRef *SecretReference `json:"secretRef,omitempty" protobuf:"bytes,7,opt,name=secretRef"` + // ReadOnly here will force the ReadOnly setting in VolumeMounts. + // Defaults to false. + // More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it + // +optional + ReadOnly bool `json:"readOnly,omitempty" protobuf:"varint,8,opt,name=readOnly"` +} + // Represents a cinder volume resource in Openstack. // A Cinder volume must exist before mounting to a container. // The volume must also be in the same region as the kubelet. @@ -945,7 +1010,7 @@ type StorageMedium string const ( StorageMediumDefault StorageMedium = "" // use whatever the default is for the node StorageMediumMemory StorageMedium = "Memory" // use memory (tmpfs) - StorageMediumHugepages StorageMedium = "HugePages" // use hugepages + StorageMediumHugePages StorageMedium = "HugePages" // use hugepages ) // Protocol defines network protocols supported for things like container ports. @@ -1016,8 +1081,34 @@ type QuobyteVolumeSource struct { Group string `json:"group,omitempty" protobuf:"bytes,5,opt,name=group"` } +// FlexPersistentVolumeSource represents a generic persistent volume resource that is +// provisioned/attached using an exec based plugin. +type FlexPersistentVolumeSource struct { + // Driver is the name of the driver to use for this volume. + Driver string `json:"driver" protobuf:"bytes,1,opt,name=driver"` + // Filesystem type to mount. + // Must be a filesystem type supported by the host operating system. + // Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + // +optional + FSType string `json:"fsType,omitempty" protobuf:"bytes,2,opt,name=fsType"` + // Optional: SecretRef is reference to the secret object containing + // sensitive information to pass to the plugin scripts. This may be + // empty if no secret object is specified. If the secret object + // contains more than one secret, all secrets are passed to the plugin + // scripts. + // +optional + SecretRef *SecretReference `json:"secretRef,omitempty" protobuf:"bytes,3,opt,name=secretRef"` + // Optional: Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + ReadOnly bool `json:"readOnly,omitempty" protobuf:"varint,4,opt,name=readOnly"` + // Optional: Extra command options if any. + // +optional + Options map[string]string `json:"options,omitempty" protobuf:"bytes,5,rep,name=options"` +} + // FlexVolume represents a generic volume resource that is -// provisioned/attached using an exec based plugin. This is an alpha feature and may change in future. +// provisioned/attached using an exec based plugin. type FlexVolumeSource struct { // Driver is the name of the driver to use for this volume. Driver string `json:"driver" protobuf:"bytes,1,opt,name=driver"` @@ -1169,14 +1260,15 @@ type NFSVolumeSource struct { // ISCSI volumes can only be mounted as read/write once. // ISCSI volumes support ownership management and SELinux relabeling. type ISCSIVolumeSource struct { - // iSCSI target portal. The portal is either an IP or ip_addr:port if the port + // iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port // is other than default (typically TCP ports 860 and 3260). TargetPortal string `json:"targetPortal" protobuf:"bytes,1,opt,name=targetPortal"` // Target iSCSI Qualified Name. IQN string `json:"iqn" protobuf:"bytes,2,opt,name=iqn"` - // iSCSI target lun number. + // iSCSI Target Lun number. Lun int32 `json:"lun" protobuf:"varint,3,opt,name=lun"` - // Optional: Defaults to 'default' (tcp). iSCSI interface name that uses an iSCSI transport. + // iSCSI Interface Name that uses an iSCSI transport. + // Defaults to 'default' (tcp). // +optional ISCSIInterface string `json:"iscsiInterface,omitempty" protobuf:"bytes,4,opt,name=iscsiInterface"` // Filesystem type of the volume that you want to mount. @@ -1190,7 +1282,7 @@ type ISCSIVolumeSource struct { // Defaults to false. // +optional ReadOnly bool `json:"readOnly,omitempty" protobuf:"varint,6,opt,name=readOnly"` - // iSCSI target portal List. The portal is either an IP or ip_addr:port if the port + // iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port // is other than default (typically TCP ports 860 and 3260). // +optional Portals []string `json:"portals,omitempty" protobuf:"bytes,7,opt,name=portals"` @@ -1200,10 +1292,56 @@ type ISCSIVolumeSource struct { // whether support iSCSI Session CHAP authentication // +optional SessionCHAPAuth bool `json:"chapAuthSession,omitempty" protobuf:"varint,11,opt,name=chapAuthSession"` - // CHAP secret for iSCSI target and initiator authentication + // CHAP Secret for iSCSI target and initiator authentication // +optional SecretRef *LocalObjectReference `json:"secretRef,omitempty" protobuf:"bytes,10,opt,name=secretRef"` - // Custom iSCSI initiator name. + // Custom iSCSI Initiator Name. + // If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface + // : will be created for the connection. + // +optional + InitiatorName *string `json:"initiatorName,omitempty" protobuf:"bytes,12,opt,name=initiatorName"` +} + +// ISCSIPersistentVolumeSource represents an ISCSI disk. +// ISCSI volumes can only be mounted as read/write once. +// ISCSI volumes support ownership management and SELinux relabeling. +type ISCSIPersistentVolumeSource struct { + // iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port + // is other than default (typically TCP ports 860 and 3260). + TargetPortal string `json:"targetPortal" protobuf:"bytes,1,opt,name=targetPortal"` + // Target iSCSI Qualified Name. + IQN string `json:"iqn" protobuf:"bytes,2,opt,name=iqn"` + // iSCSI Target Lun number. + Lun int32 `json:"lun" protobuf:"varint,3,opt,name=lun"` + // iSCSI Interface Name that uses an iSCSI transport. + // Defaults to 'default' (tcp). + // +optional + ISCSIInterface string `json:"iscsiInterface,omitempty" protobuf:"bytes,4,opt,name=iscsiInterface"` + // Filesystem type of the volume that you want to mount. + // Tip: Ensure that the filesystem type is supported by the host operating system. + // Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + // More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + // TODO: how do we prevent errors in the filesystem from compromising the machine + // +optional + FSType string `json:"fsType,omitempty" protobuf:"bytes,5,opt,name=fsType"` + // ReadOnly here will force the ReadOnly setting in VolumeMounts. + // Defaults to false. + // +optional + ReadOnly bool `json:"readOnly,omitempty" protobuf:"varint,6,opt,name=readOnly"` + // iSCSI Target Portal List. The Portal is either an IP or ip_addr:port if the port + // is other than default (typically TCP ports 860 and 3260). + // +optional + Portals []string `json:"portals,omitempty" protobuf:"bytes,7,opt,name=portals"` + // whether support iSCSI Discovery CHAP authentication + // +optional + DiscoveryCHAPAuth bool `json:"chapAuthDiscovery,omitempty" protobuf:"varint,8,opt,name=chapAuthDiscovery"` + // whether support iSCSI Session CHAP authentication + // +optional + SessionCHAPAuth bool `json:"chapAuthSession,omitempty" protobuf:"varint,11,opt,name=chapAuthSession"` + // CHAP Secret for iSCSI target and initiator authentication + // +optional + SecretRef *SecretReference `json:"secretRef,omitempty" protobuf:"bytes,10,opt,name=secretRef"` + // Custom iSCSI Initiator Name. // If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface // : will be created for the connection. // +optional @@ -1352,13 +1490,48 @@ type ScaleIOVolumeSource struct { // Flag to enable/disable SSL communication with Gateway, default false // +optional SSLEnabled bool `json:"sslEnabled,omitempty" protobuf:"varint,4,opt,name=sslEnabled"` - // The name of the Protection Domain for the configured storage (defaults to "default"). + // The name of the ScaleIO Protection Domain for the configured storage. // +optional ProtectionDomain string `json:"protectionDomain,omitempty" protobuf:"bytes,5,opt,name=protectionDomain"` - // The Storage Pool associated with the protection domain (defaults to "default"). + // The ScaleIO Storage Pool associated with the protection domain. // +optional StoragePool string `json:"storagePool,omitempty" protobuf:"bytes,6,opt,name=storagePool"` - // Indicates whether the storage for a volume should be thick or thin (defaults to "thin"). + // Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. + // +optional + StorageMode string `json:"storageMode,omitempty" protobuf:"bytes,7,opt,name=storageMode"` + // The name of a volume already created in the ScaleIO system + // that is associated with this volume source. + VolumeName string `json:"volumeName,omitempty" protobuf:"bytes,8,opt,name=volumeName"` + // Filesystem type to mount. + // Must be a filesystem type supported by the host operating system. + // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + // +optional + FSType string `json:"fsType,omitempty" protobuf:"bytes,9,opt,name=fsType"` + // Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + ReadOnly bool `json:"readOnly,omitempty" protobuf:"varint,10,opt,name=readOnly"` +} + +// ScaleIOPersistentVolumeSource represents a persistent ScaleIO volume +type ScaleIOPersistentVolumeSource struct { + // The host address of the ScaleIO API Gateway. + Gateway string `json:"gateway" protobuf:"bytes,1,opt,name=gateway"` + // The name of the storage system as configured in ScaleIO. + System string `json:"system" protobuf:"bytes,2,opt,name=system"` + // SecretRef references to the secret for ScaleIO user and other + // sensitive information. If this is not provided, Login operation will fail. + SecretRef *SecretReference `json:"secretRef" protobuf:"bytes,3,opt,name=secretRef"` + // Flag to enable/disable SSL communication with Gateway, default false + // +optional + SSLEnabled bool `json:"sslEnabled,omitempty" protobuf:"varint,4,opt,name=sslEnabled"` + // The name of the ScaleIO Protection Domain for the configured storage. + // +optional + ProtectionDomain string `json:"protectionDomain,omitempty" protobuf:"bytes,5,opt,name=protectionDomain"` + // The ScaleIO Storage Pool associated with the protection domain. + // +optional + StoragePool string `json:"storagePool,omitempty" protobuf:"bytes,6,opt,name=storagePool"` + // Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. // +optional StorageMode string `json:"storageMode,omitempty" protobuf:"bytes,7,opt,name=storageMode"` // The name of a volume already created in the ScaleIO system @@ -1542,6 +1715,23 @@ type LocalVolumeSource struct { Path string `json:"path" protobuf:"bytes,1,opt,name=path"` } +// Represents storage that is managed by an external CSI volume driver +type CSIPersistentVolumeSource struct { + // Driver is the name of the driver to use for this volume. + // Required. + Driver string `json:"driver" protobuf:"bytes,1,opt,name=driver"` + + // VolumeHandle is the unique volume name returned by the CSI volume + // plugin’s CreateVolume to refer to the volume on all subsequent calls. + // Required. + VolumeHandle string `json:"volumeHandle" protobuf:"bytes,2,opt,name=volumeHandle"` + + // Optional: The value to pass to ControllerPublishVolumeRequest. + // Defaults to false (read/write). + // +optional + ReadOnly bool `json:"readOnly,omitempty" protobuf:"varint,3,opt,name=readOnly"` +} + // ContainerPort represents a network port in a single container. type ContainerPort struct { // If specified, this must be an IANA_SVC_NAME and unique within the pod. Each @@ -1610,6 +1800,14 @@ const ( MountPropagationBidirectional MountPropagationMode = "Bidirectional" ) +// volumeDevice describes a mapping of a raw block device within a container. +type VolumeDevice struct { + // name must match the name of a persistentVolumeClaim in the pod + Name string `json:"name" protobuf:"bytes,1,opt,name=name"` + // devicePath is the path inside of the container that the device will be mapped to. + DevicePath string `json:"devicePath" protobuf:"bytes,2,opt,name=devicePath"` +} + // EnvVar represents an environment variable present in a Container. type EnvVar struct { // Name of the environment variable. Must be a C_IDENTIFIER. @@ -1953,6 +2151,12 @@ type Container struct { // +patchMergeKey=mountPath // +patchStrategy=merge VolumeMounts []VolumeMount `json:"volumeMounts,omitempty" patchStrategy:"merge" patchMergeKey:"mountPath" protobuf:"bytes,9,rep,name=volumeMounts"` + // volumeDevices is the list of block devices to be used by the container. + // This is an alpha feature and may change in the future. + // +patchMergeKey=devicePath + // +patchStrategy=merge + // +optional + VolumeDevices []VolumeDevice `json:"volumeDevices,omitempty" patchStrategy:"merge" patchMergeKey:"devicePath" protobuf:"bytes,21,rep,name=volumeDevices"` // Periodic probe of container liveness. // Container will be restarted if the probe fails. // Cannot be updated. @@ -1996,7 +2200,7 @@ type Container struct { ImagePullPolicy PullPolicy `json:"imagePullPolicy,omitempty" protobuf:"bytes,14,opt,name=imagePullPolicy,casttype=PullPolicy"` // Security options the pod should run with. // More info: https://kubernetes.io/docs/concepts/policy/security-context/ - // More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md + // More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ // +optional SecurityContext *SecurityContext `json:"securityContext,omitempty" protobuf:"bytes,15,opt,name=securityContext"` @@ -2251,6 +2455,15 @@ const ( // determined by kubelet) DNS settings. DNSDefault DNSPolicy = "Default" + // DNSNone indicates that the pod should use empty DNS settings. DNS + // parameters such as nameservers and search paths should be defined via + // DNSConfig. + DNSNone DNSPolicy = "None" +) + +const ( + // DefaultTerminationGracePeriodSeconds indicates the default duration in + // seconds a pod needs to terminate gracefully. DefaultTerminationGracePeriodSeconds = 30 ) @@ -2394,7 +2607,7 @@ type WeightedPodAffinityTerm struct { // relative to the given namespace(s)) that this pod should be // co-located (affinity) or not co-located (anti-affinity) with, // where co-located is defined as running on a node whose value of -// the label with key tches that of any node on which +// the label with key matches that of any node on which // a pod of the set of pods is running type PodAffinityTerm struct { // A label query over a set of resources, in this case pods. @@ -2402,16 +2615,14 @@ type PodAffinityTerm struct { LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty" protobuf:"bytes,1,opt,name=labelSelector"` // namespaces specifies which namespaces the labelSelector applies to (matches against); // null or empty list means "this pod's namespace" + // +optional Namespaces []string `json:"namespaces,omitempty" protobuf:"bytes,2,rep,name=namespaces"` // This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching // the labelSelector in the specified namespaces, where co-located is defined as running on a node // whose value of the label with key topologyKey matches that of any node on which any of the // selected pods is running. - // For PreferredDuringScheduling pod anti-affinity, empty topologyKey is interpreted as "all topologies" - // ("all topologies" here means all the topologyKeys indicated by scheduler command-line argument --failure-domains); - // for affinity and for RequiredDuringScheduling pod anti-affinity, empty topologyKey is not allowed. - // +optional - TopologyKey string `json:"topologyKey,omitempty" protobuf:"bytes,3,opt,name=topologyKey"` + // Empty topologyKey is not allowed. + TopologyKey string `json:"topologyKey" protobuf:"bytes,3,opt,name=topologyKey"` } // Node affinity is a group of node affinity scheduling rules. @@ -2583,10 +2794,13 @@ type PodSpec struct { // Value must be a positive integer. // +optional ActiveDeadlineSeconds *int64 `json:"activeDeadlineSeconds,omitempty" protobuf:"varint,5,opt,name=activeDeadlineSeconds"` - // Set DNS policy for containers within the pod. - // One of 'ClusterFirstWithHostNet', 'ClusterFirst' or 'Default'. + // Set DNS policy for the pod. // Defaults to "ClusterFirst". - // To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'. + // Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. + // DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. + // To have DNS options set along with hostNetwork, you have to specify DNS policy + // explicitly to 'ClusterFirstWithHostNet'. + // Note that 'None' policy is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it. // +optional DNSPolicy DNSPolicy `json:"dnsPolicy,omitempty" protobuf:"bytes,6,opt,name=dnsPolicy,casttype=DNSPolicy"` // NodeSelector is a selector which must be true for the pod to fit on a node. @@ -2679,6 +2893,12 @@ type PodSpec struct { // The higher the value, the higher the priority. // +optional Priority *int32 `json:"priority,omitempty" protobuf:"bytes,25,opt,name=priority"` + // Specifies the DNS parameters of a pod. + // Parameters specified here will be merged to the generated DNS + // configuration based on DNSPolicy. + // This is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it. + // +optional + DNSConfig *PodDNSConfig `json:"dnsConfig,omitempty" protobuf:"bytes,26,opt,name=dnsConfig"` } // HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the @@ -2746,6 +2966,35 @@ const ( PodQOSBestEffort PodQOSClass = "BestEffort" ) +// PodDNSConfig defines the DNS parameters of a pod in addition to +// those generated from DNSPolicy. +type PodDNSConfig struct { + // A list of DNS name server IP addresses. + // This will be appended to the base nameservers generated from DNSPolicy. + // Duplicated nameservers will be removed. + // +optional + Nameservers []string `json:"nameservers,omitempty" protobuf:"bytes,1,rep,name=nameservers"` + // A list of DNS search domains for host-name lookup. + // This will be appended to the base search paths generated from DNSPolicy. + // Duplicated search paths will be removed. + // +optional + Searches []string `json:"searches,omitempty" protobuf:"bytes,2,rep,name=searches"` + // A list of DNS resolver options. + // This will be merged with the base options generated from DNSPolicy. + // Duplicated entries will be removed. Resolution options given in Options + // will override those that appear in the base DNSPolicy. + // +optional + Options []PodDNSConfigOption `json:"options,omitempty" protobuf:"bytes,3,rep,name=options"` +} + +// PodDNSConfigOption defines DNS resolver options of a pod. +type PodDNSConfigOption struct { + // Required. + Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"` + // +optional + Value *string `json:"value,omitempty" protobuf:"bytes,2,opt,name=value"` +} + // PodStatus represents information about the status of a pod. Status may trail the actual // state of a system. type PodStatus struct { @@ -3209,7 +3458,8 @@ type ServiceSpec struct { // externalName is the external reference that kubedns or equivalent will // return as a CNAME record for this service. No proxying will be involved. - // Must be a valid DNS name and requires Type to be ExternalName. + // Must be a valid RFC-1123 hostname (https://tools.ietf.org/html/rfc1123) + // and requires Type to be ExternalName. // +optional ExternalName string `json:"externalName,omitempty" protobuf:"bytes,10,opt,name=externalName"` @@ -3762,8 +4012,6 @@ const ( ) const ( - // Namespace prefix for opaque counted resources (alpha). - ResourceOpaqueIntPrefix = "pod.alpha.kubernetes.io/opaque-int-resource-" // Default namespace prefix. ResourceDefaultNamespacePrefix = "kubernetes.io/" // Name prefix for huge page resources (alpha). @@ -3825,7 +4073,7 @@ const ( // NamespaceSpec describes the attributes on a Namespace. type NamespaceSpec struct { // Finalizers is an opaque list of values that must be empty to permanently remove object from storage. - // More info: https://git.k8s.io/community/contributors/design-proposals/namespaces.md#finalizers + // More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/ // +optional Finalizers []FinalizerName `json:"finalizers,omitempty" protobuf:"bytes,1,rep,name=finalizers,casttype=FinalizerName"` } @@ -3833,7 +4081,7 @@ type NamespaceSpec struct { // NamespaceStatus is information about the current status of a Namespace. type NamespaceStatus struct { // Phase is the current lifecycle phase of the namespace. - // More info: https://git.k8s.io/community/contributors/design-proposals/namespaces.md#phases + // More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/ // +optional Phase NamespacePhase `json:"phase,omitempty" protobuf:"bytes,1,opt,name=phase,casttype=NamespacePhase"` } @@ -3955,6 +4203,10 @@ type DeleteOptions struct { // 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. // +optional PropagationPolicy *DeletionPropagation `protobuf:"bytes,4,opt,name=propagationPolicy,casttype=DeletionPropagation"` } @@ -4247,7 +4499,6 @@ const ( // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // Event is a report of an event somewhere in the cluster. -// TODO: Decide whether to store these separately or with the object they apply to. type Event struct { metav1.TypeMeta `json:",inline"` // Standard object's metadata. @@ -4287,8 +4538,51 @@ type Event struct { // Type of this event (Normal, Warning), new types could be added in the future // +optional Type string `json:"type,omitempty" protobuf:"bytes,9,opt,name=type"` + + // Time when this Event was first observed. + // +optional + EventTime metav1.MicroTime `json:"eventTime,omitempty" protobuf:"bytes,10,opt,name=eventTime"` + + // Data about the Event series this event represents or nil if it's a singleton Event. + // +optional + Series *EventSeries `json:"series,omitempty" protobuf:"bytes,11,opt,name=series"` + + // What action was taken/failed regarding to the Regarding object. + // +optional + Action string `json:"action,omitempty" protobuf:"bytes,12,opt,name=action"` + + // Optional secondary object for more complex actions. + // +optional + Related *ObjectReference `json:"related,omitempty" protobuf:"bytes,13,opt,name=related"` + + // Name of the controller that emitted this Event, e.g. `kubernetes.io/kubelet`. + // +optional + ReportingController string `json:"reportingComponent" protobuf:"bytes,14,opt,name=reportingComponent"` + + // ID of the controller instance, e.g. `kubelet-xyzf`. + // +optional + ReportingInstance string `json:"reportingInstance" protobuf:"bytes,15,opt,name=reportingInstance"` } +// EventSeries contain information on series of events, i.e. thing that was/is happening +// continously for some time. +type EventSeries struct { + // Number of occurrences in this series up to the last heartbeat time + Count int32 `json:"count,omitempty" protobuf:"varint,1,name=count"` + // Time of the last occurence observed + LastObservedTime metav1.MicroTime `json:"lastObservedTime,omitempty" protobuf:"bytes,2,name=lastObservedTime"` + // State of this Series: Ongoing or Finished + State EventSeriesState `json:"state,omitempty" protobuf:"bytes,3,name=state"` +} + +type EventSeriesState string + +const ( + EventSeriesStateOngoing EventSeriesState = "Ongoing" + EventSeriesStateFinished EventSeriesState = "Finished" + EventSeriesStateUnknown EventSeriesState = "Unknown" +) + // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // EventList is a list of events. @@ -4376,7 +4670,7 @@ type LimitRangeList struct { metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // Items is a list of LimitRange objects. - // More info: https://git.k8s.io/community/contributors/design-proposals/admission_control_limit_range.md + // More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/ Items []LimitRange `json:"items" protobuf:"bytes,2,rep,name=items"` } @@ -4416,6 +4710,13 @@ const ( ResourceLimitsEphemeralStorage ResourceName = "limits.ephemeral-storage" ) +// The following identify resource prefix for Kubernetes object types +const ( + // HugePages request, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) + // As burst is not supported for HugePages, we would only quota its request, and ignore the limit. + ResourceRequestsHugePagesPrefix = "requests.hugepages-" +) + // A ResourceQuotaScope defines a filter that must match each object tracked by a quota type ResourceQuotaScope string @@ -4433,7 +4734,7 @@ const ( // ResourceQuotaSpec defines the desired hard limits to enforce for Quota. type ResourceQuotaSpec struct { // Hard is the set of desired hard limits for each named resource. - // More info: https://git.k8s.io/community/contributors/design-proposals/admission_control_resource_quota.md + // More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/ // +optional Hard ResourceList `json:"hard,omitempty" protobuf:"bytes,1,rep,name=hard,casttype=ResourceList,castkey=ResourceName"` // A collection of filters that must match each object tracked by a quota. @@ -4445,7 +4746,7 @@ type ResourceQuotaSpec struct { // ResourceQuotaStatus defines the enforced hard limits and observed use. type ResourceQuotaStatus struct { // Hard is the set of enforced hard limits for each named resource. - // More info: https://git.k8s.io/community/contributors/design-proposals/admission_control_resource_quota.md + // More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/ // +optional Hard ResourceList `json:"hard,omitempty" protobuf:"bytes,1,rep,name=hard,casttype=ResourceList,castkey=ResourceName"` // Used is the current observed total usage of the resource in the namespace. @@ -4486,7 +4787,7 @@ type ResourceQuotaList struct { metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // Items is a list of ResourceQuota objects. - // More info: https://git.k8s.io/community/contributors/design-proposals/admission_control_resource_quota.md + // More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/ Items []ResourceQuota `json:"items" protobuf:"bytes,2,rep,name=items"` } @@ -4844,7 +5145,7 @@ const ( // corresponding to every RequiredDuringScheduling affinity rule. // When the --hard-pod-affinity-weight scheduler flag is not specified, // DefaultHardPodAffinityWeight defines the weight of the implicit PreferredDuringScheduling affinity rule. - DefaultHardPodAffinitySymmetricWeight int = 1 + DefaultHardPodAffinitySymmetricWeight int32 = 1 ) // Sysctl defines a kernel parameter to be set 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 d27c94ceda1..80cacc974e5 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 @@ -116,6 +116,17 @@ func (Binding) SwaggerDoc() map[string]string { return map_Binding } +var map_CSIPersistentVolumeSource = map[string]string{ + "": "Represents storage that is managed by an external CSI volume driver", + "driver": "Driver is the name of the driver to use for this volume. Required.", + "volumeHandle": "VolumeHandle is the unique volume name returned by the CSI volume plugin’s CreateVolume to refer to the volume on all subsequent calls. Required.", + "readOnly": "Optional: The value to pass to ControllerPublishVolumeRequest. Defaults to false (read/write).", +} + +func (CSIPersistentVolumeSource) SwaggerDoc() map[string]string { + return map_CSIPersistentVolumeSource +} + var map_Capabilities = map[string]string{ "": "Adds and removes POSIX capabilities from running containers.", "add": "Added capabilities", @@ -278,13 +289,14 @@ var map_Container = map[string]string{ "env": "List of environment variables to set in the container. Cannot be updated.", "resources": "Compute Resources required by this container. Cannot be updated. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources", "volumeMounts": "Pod volumes to mount into the container's filesystem. Cannot be updated.", + "volumeDevices": "volumeDevices is the list of block devices to be used by the container. This is an alpha feature and may change in the future.", "livenessProbe": "Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", "readinessProbe": "Periodic probe of container service readiness. Container will be removed from service endpoints if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", "lifecycle": "Actions that the management system should take in response to container lifecycle events. Cannot be updated.", "terminationMessagePath": "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.", "terminationMessagePolicy": "Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated.", "imagePullPolicy": "Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images", - "securityContext": "Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md", + "securityContext": "Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/", "stdin": "Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false.", "stdinOnce": "Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false", "tty": "Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false.", @@ -392,7 +404,7 @@ var map_DeleteOptions = map[string]string{ "gracePeriodSeconds": "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.", "preconditions": "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.", "orphanDependents": "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.", - "PropagationPolicy": "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.", + "PropagationPolicy": "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.", } func (DeleteOptions) SwaggerDoc() map[string]string { @@ -529,16 +541,22 @@ func (EnvVarSource) SwaggerDoc() map[string]string { } var map_Event = map[string]string{ - "": "Event is a report of an event somewhere in the cluster.", - "metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", - "involvedObject": "The object that this event is about.", - "reason": "This should be a short, machine understandable string that gives the reason for the transition into the object's current status.", - "message": "A human-readable description of the status of this operation.", - "source": "The component reporting this event. Should be a short machine understandable string.", - "firstTimestamp": "The time at which the event was first recorded. (Time of server receipt is in TypeMeta.)", - "lastTimestamp": "The time at which the most recent occurrence of this event was recorded.", - "count": "The number of times this event has occurred.", - "type": "Type of this event (Normal, Warning), new types could be added in the future", + "": "Event is a report of an event somewhere in the cluster.", + "metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", + "involvedObject": "The object that this event is about.", + "reason": "This should be a short, machine understandable string that gives the reason for the transition into the object's current status.", + "message": "A human-readable description of the status of this operation.", + "source": "The component reporting this event. Should be a short machine understandable string.", + "firstTimestamp": "The time at which the event was first recorded. (Time of server receipt is in TypeMeta.)", + "lastTimestamp": "The time at which the most recent occurrence of this event was recorded.", + "count": "The number of times this event has occurred.", + "type": "Type of this event (Normal, Warning), new types could be added in the future", + "eventTime": "Time when this Event was first observed.", + "series": "Data about the Event series this event represents or nil if it's a singleton Event.", + "action": "What action was taken/failed regarding to the Regarding object.", + "related": "Optional secondary object for more complex actions.", + "reportingComponent": "Name of the controller that emitted this Event, e.g. `kubernetes.io/kubelet`.", + "reportingInstance": "ID of the controller instance, e.g. `kubelet-xyzf`.", } func (Event) SwaggerDoc() map[string]string { @@ -555,6 +573,17 @@ func (EventList) SwaggerDoc() map[string]string { return map_EventList } +var map_EventSeries = map[string]string{ + "": "EventSeries contain information on series of events, i.e. thing that was/is happening continously for some time.", + "count": "Number of occurrences in this series up to the last heartbeat time", + "lastObservedTime": "Time of the last occurence observed", + "state": "State of this Series: Ongoing or Finished", +} + +func (EventSeries) SwaggerDoc() map[string]string { + return map_EventSeries +} + var map_EventSource = map[string]string{ "": "EventSource contains information for an event.", "component": "Component from which the event is generated.", @@ -587,8 +616,21 @@ func (FCVolumeSource) SwaggerDoc() map[string]string { return map_FCVolumeSource } +var map_FlexPersistentVolumeSource = map[string]string{ + "": "FlexPersistentVolumeSource represents a generic persistent volume resource that is provisioned/attached using an exec based plugin.", + "driver": "Driver is the name of the driver to use for this volume.", + "fsType": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". The default filesystem depends on FlexVolume script.", + "secretRef": "Optional: SecretRef is reference to the secret object containing sensitive information to pass to the plugin scripts. This may be empty if no secret object is specified. If the secret object contains more than one secret, all secrets are passed to the plugin scripts.", + "readOnly": "Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", + "options": "Optional: Extra command options if any.", +} + +func (FlexPersistentVolumeSource) SwaggerDoc() map[string]string { + return map_FlexPersistentVolumeSource +} + var map_FlexVolumeSource = map[string]string{ - "": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.", + "": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.", "driver": "Driver is the name of the driver to use for this volume.", "fsType": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". The default filesystem depends on FlexVolume script.", "secretRef": "Optional: SecretRef is reference to the secret object containing sensitive information to pass to the plugin scripts. This may be empty if no secret object is specified. If the secret object contains more than one secret, all secrets are passed to the plugin scripts.", @@ -698,19 +740,38 @@ func (HostPathVolumeSource) SwaggerDoc() map[string]string { return map_HostPathVolumeSource } -var map_ISCSIVolumeSource = map[string]string{ - "": "Represents an ISCSI disk. ISCSI volumes can only be mounted as read/write once. ISCSI volumes support ownership management and SELinux relabeling.", - "targetPortal": "iSCSI target portal. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).", +var map_ISCSIPersistentVolumeSource = map[string]string{ + "": "ISCSIPersistentVolumeSource represents an ISCSI disk. ISCSI volumes can only be mounted as read/write once. ISCSI volumes support ownership management and SELinux relabeling.", + "targetPortal": "iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).", "iqn": "Target iSCSI Qualified Name.", - "lun": "iSCSI target lun number.", - "iscsiInterface": "Optional: Defaults to 'default' (tcp). iSCSI interface name that uses an iSCSI transport.", + "lun": "iSCSI Target Lun number.", + "iscsiInterface": "iSCSI Interface Name that uses an iSCSI transport. Defaults to 'default' (tcp).", "fsType": "Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi", "readOnly": "ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false.", - "portals": "iSCSI target portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).", + "portals": "iSCSI Target Portal List. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).", "chapAuthDiscovery": "whether support iSCSI Discovery CHAP authentication", "chapAuthSession": "whether support iSCSI Session CHAP authentication", - "secretRef": "CHAP secret for iSCSI target and initiator authentication", - "initiatorName": "Custom iSCSI initiator name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface : will be created for the connection.", + "secretRef": "CHAP Secret for iSCSI target and initiator authentication", + "initiatorName": "Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface : will be created for the connection.", +} + +func (ISCSIPersistentVolumeSource) SwaggerDoc() map[string]string { + return map_ISCSIPersistentVolumeSource +} + +var map_ISCSIVolumeSource = map[string]string{ + "": "Represents an ISCSI disk. ISCSI volumes can only be mounted as read/write once. ISCSI volumes support ownership management and SELinux relabeling.", + "targetPortal": "iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).", + "iqn": "Target iSCSI Qualified Name.", + "lun": "iSCSI Target Lun number.", + "iscsiInterface": "iSCSI Interface Name that uses an iSCSI transport. Defaults to 'default' (tcp).", + "fsType": "Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi", + "readOnly": "ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false.", + "portals": "iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).", + "chapAuthDiscovery": "whether support iSCSI Discovery CHAP authentication", + "chapAuthSession": "whether support iSCSI Session CHAP authentication", + "secretRef": "CHAP Secret for iSCSI target and initiator authentication", + "initiatorName": "Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface : will be created for the connection.", } func (ISCSIVolumeSource) SwaggerDoc() map[string]string { @@ -765,7 +826,7 @@ func (LimitRangeItem) SwaggerDoc() map[string]string { var map_LimitRangeList = map[string]string{ "": "LimitRangeList is a list of LimitRange items.", "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds", - "items": "Items is a list of LimitRange objects. More info: https://git.k8s.io/community/contributors/design-proposals/admission_control_limit_range.md", + "items": "Items is a list of LimitRange objects. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/", } func (LimitRangeList) SwaggerDoc() map[string]string { @@ -866,7 +927,7 @@ func (NamespaceList) SwaggerDoc() map[string]string { var map_NamespaceSpec = map[string]string{ "": "NamespaceSpec describes the attributes on a Namespace.", - "finalizers": "Finalizers is an opaque list of values that must be empty to permanently remove object from storage. More info: https://git.k8s.io/community/contributors/design-proposals/namespaces.md#finalizers", + "finalizers": "Finalizers is an opaque list of values that must be empty to permanently remove object from storage. More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/", } func (NamespaceSpec) SwaggerDoc() map[string]string { @@ -875,7 +936,7 @@ func (NamespaceSpec) SwaggerDoc() map[string]string { var map_NamespaceStatus = map[string]string{ "": "NamespaceStatus is information about the current status of a Namespace.", - "phase": "Phase is the current lifecycle phase of the namespace. More info: https://git.k8s.io/community/contributors/design-proposals/namespaces.md#phases", + "phase": "Phase is the current lifecycle phase of the namespace. More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/", } func (NamespaceStatus) SwaggerDoc() map[string]string { @@ -1151,6 +1212,7 @@ var map_PersistentVolumeClaimSpec = map[string]string{ "resources": "Resources represents the minimum resources the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources", "volumeName": "VolumeName is the binding reference to the PersistentVolume backing this claim.", "storageClassName": "Name of the StorageClass required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1", + "volumeMode": "volumeMode defines what type of volume is required by the claim. Value of Filesystem is implied when not included in claim spec. This is an alpha feature and may change in the future.", } func (PersistentVolumeClaimSpec) SwaggerDoc() map[string]string { @@ -1202,7 +1264,7 @@ var map_PersistentVolumeSource = map[string]string{ "cephfs": "CephFS represents a Ceph FS mount on the host that shares a pod's lifetime", "fc": "FC represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod.", "flocker": "Flocker represents a Flocker volume attached to a kubelet's host machine and exposed to the pod for its usage. This depends on the Flocker control service being running", - "flexVolume": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.", + "flexVolume": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.", "azureFile": "AzureFile represents an Azure File Service mount on the host and bind mount to the pod.", "vsphereVolume": "VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine", "quobyte": "Quobyte represents a Quobyte mount on the host that shares a pod's lifetime", @@ -1212,6 +1274,7 @@ var map_PersistentVolumeSource = map[string]string{ "scaleIO": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.", "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 More info: https://releases.k8s.io/HEAD/examples/volumes/storageos/README.md", + "csi": "CSI represents storage that handled by an external CSI driver", } func (PersistentVolumeSource) SwaggerDoc() map[string]string { @@ -1226,6 +1289,7 @@ var map_PersistentVolumeSpec = map[string]string{ "persistentVolumeReclaimPolicy": "What happens to a persistent volume when released from its claim. Valid options are Retain (default) and Recycle. Recycling must be supported by the volume plugin underlying this persistent volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#reclaiming", "storageClassName": "Name of StorageClass to which this persistent volume belongs. Empty value means that this volume does not belong to any StorageClass.", "mountOptions": "A list of mount options, e.g. [\"ro\", \"soft\"]. Not validated - mount will simply fail if one is invalid. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#mount-options", + "volumeMode": "volumeMode defines if a volume is intended to be used with a formatted filesystem or to remain in raw block state. Value of Filesystem is implied when not included in spec. This is an alpha feature and may change in the future.", } func (PersistentVolumeSpec) SwaggerDoc() map[string]string { @@ -1275,10 +1339,10 @@ func (PodAffinity) SwaggerDoc() map[string]string { } var map_PodAffinityTerm = map[string]string{ - "": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key tches that of any node on which a pod of the set of pods is running", + "": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running", "labelSelector": "A label query over a set of resources, in this case pods.", "namespaces": "namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means \"this pod's namespace\"", - "topologyKey": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. For PreferredDuringScheduling pod anti-affinity, empty topologyKey is interpreted as \"all topologies\" (\"all topologies\" here means all the topologyKeys indicated by scheduler command-line argument --failure-domains); for affinity and for RequiredDuringScheduling pod anti-affinity, empty topologyKey is not allowed.", + "topologyKey": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.", } func (PodAffinityTerm) SwaggerDoc() map[string]string { @@ -1322,6 +1386,26 @@ func (PodCondition) SwaggerDoc() map[string]string { return map_PodCondition } +var map_PodDNSConfig = map[string]string{ + "": "PodDNSConfig defines the DNS parameters of a pod in addition to those generated from DNSPolicy.", + "nameservers": "A list of DNS name server IP addresses. This will be appended to the base nameservers generated from DNSPolicy. Duplicated nameservers will be removed.", + "searches": "A list of DNS search domains for host-name lookup. This will be appended to the base search paths generated from DNSPolicy. Duplicated search paths will be removed.", + "options": "A list of DNS resolver options. This will be merged with the base options generated from DNSPolicy. Duplicated entries will be removed. Resolution options given in Options will override those that appear in the base DNSPolicy.", +} + +func (PodDNSConfig) SwaggerDoc() map[string]string { + return map_PodDNSConfig +} + +var map_PodDNSConfigOption = map[string]string{ + "": "PodDNSConfigOption defines DNS resolver options of a pod.", + "name": "Required.", +} + +func (PodDNSConfigOption) SwaggerDoc() map[string]string { + return map_PodDNSConfigOption +} + var map_PodExecOptions = map[string]string{ "": "PodExecOptions is the query options to a Pod's remote exec call.", "stdin": "Redirect the standard input stream of the pod for this call. Defaults to false.", @@ -1410,7 +1494,7 @@ var map_PodSpec = map[string]string{ "restartPolicy": "Restart policy for all containers within the pod. One of Always, OnFailure, Never. Default to Always. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy", "terminationGracePeriodSeconds": "Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period will be used instead. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. Defaults to 30 seconds.", "activeDeadlineSeconds": "Optional duration in seconds the pod may be active on the node relative to StartTime before the system will actively try to mark it failed and kill associated containers. Value must be a positive integer.", - "dnsPolicy": "Set DNS policy for containers within the pod. One of 'ClusterFirstWithHostNet', 'ClusterFirst' or 'Default'. Defaults to \"ClusterFirst\". To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'.", + "dnsPolicy": "Set DNS policy for the pod. Defaults to \"ClusterFirst\". Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'. Note that 'None' policy is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it.", "nodeSelector": "NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node's labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/", "serviceAccountName": "ServiceAccountName is the name of the ServiceAccount to use to run this pod. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/", "serviceAccount": "DeprecatedServiceAccount is a depreciated alias for ServiceAccountName. Deprecated: Use serviceAccountName instead.", @@ -1429,6 +1513,7 @@ var map_PodSpec = map[string]string{ "hostAliases": "HostAliases is an optional list of hosts and IPs that will be injected into the pod's hosts file if specified. This is only valid for non-hostNetwork pods.", "priorityClassName": "If specified, indicates the pod's priority. \"SYSTEM\" is a special keyword which indicates the highest priority. Any other name must be defined by creating a PriorityClass object with that name. If not specified, the pod priority will be default or zero if there is no default.", "priority": "The priority value. Various system components use this field to find the priority of the pod. 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.", + "dnsConfig": "Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy. This is an alpha feature introduced in v1.9 and CustomPodDNS feature gate must be enabled to use it.", } func (PodSpec) SwaggerDoc() map[string]string { @@ -1571,6 +1656,22 @@ func (QuobyteVolumeSource) SwaggerDoc() map[string]string { return map_QuobyteVolumeSource } +var map_RBDPersistentVolumeSource = map[string]string{ + "": "Represents a Rados Block Device mount that lasts the lifetime of a pod. RBD volumes support ownership management and SELinux relabeling.", + "monitors": "A collection of Ceph monitors. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it", + "image": "The rados image name. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it", + "fsType": "Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd", + "pool": "The rados pool name. Default is rbd. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it", + "user": "The rados user name. Default is admin. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it", + "keyring": "Keyring is the path to key ring for RBDUser. Default is /etc/ceph/keyring. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it", + "secretRef": "SecretRef is name of the authentication secret for RBDUser. If provided overrides keyring. Default is nil. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it", + "readOnly": "ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it", +} + +func (RBDPersistentVolumeSource) SwaggerDoc() map[string]string { + return map_RBDPersistentVolumeSource +} + var map_RBDVolumeSource = map[string]string{ "": "Represents a Rados Block Device mount that lasts the lifetime of a pod. RBD volumes support ownership management and SELinux relabeling.", "monitors": "A collection of Ceph monitors. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md#how-to-use-it", @@ -1683,7 +1784,7 @@ func (ResourceQuota) SwaggerDoc() map[string]string { var map_ResourceQuotaList = map[string]string{ "": "ResourceQuotaList is a list of ResourceQuota items.", "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds", - "items": "Items is a list of ResourceQuota objects. More info: https://git.k8s.io/community/contributors/design-proposals/admission_control_resource_quota.md", + "items": "Items is a list of ResourceQuota objects. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/", } func (ResourceQuotaList) SwaggerDoc() map[string]string { @@ -1692,7 +1793,7 @@ func (ResourceQuotaList) SwaggerDoc() map[string]string { var map_ResourceQuotaSpec = map[string]string{ "": "ResourceQuotaSpec defines the desired hard limits to enforce for Quota.", - "hard": "Hard is the set of desired hard limits for each named resource. More info: https://git.k8s.io/community/contributors/design-proposals/admission_control_resource_quota.md", + "hard": "Hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/", "scopes": "A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.", } @@ -1702,7 +1803,7 @@ func (ResourceQuotaSpec) SwaggerDoc() map[string]string { var map_ResourceQuotaStatus = map[string]string{ "": "ResourceQuotaStatus defines the enforced hard limits and observed use.", - "hard": "Hard is the set of enforced hard limits for each named resource. More info: https://git.k8s.io/community/contributors/design-proposals/admission_control_resource_quota.md", + "hard": "Hard is the set of enforced hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/", "used": "Used is the current observed total usage of the resource in the namespace.", } @@ -1732,15 +1833,33 @@ func (SELinuxOptions) SwaggerDoc() map[string]string { return map_SELinuxOptions } +var map_ScaleIOPersistentVolumeSource = map[string]string{ + "": "ScaleIOPersistentVolumeSource represents a persistent ScaleIO volume", + "gateway": "The host address of the ScaleIO API Gateway.", + "system": "The name of the storage system as configured in ScaleIO.", + "secretRef": "SecretRef references to the secret for ScaleIO user and other sensitive information. If this is not provided, Login operation will fail.", + "sslEnabled": "Flag to enable/disable SSL communication with Gateway, default false", + "protectionDomain": "The name of the ScaleIO Protection Domain for the configured storage.", + "storagePool": "The ScaleIO Storage Pool associated with the protection domain.", + "storageMode": "Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned.", + "volumeName": "The name of a volume already created in the ScaleIO system that is associated with this volume source.", + "fsType": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.", + "readOnly": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", +} + +func (ScaleIOPersistentVolumeSource) SwaggerDoc() map[string]string { + return map_ScaleIOPersistentVolumeSource +} + var map_ScaleIOVolumeSource = map[string]string{ "": "ScaleIOVolumeSource represents a persistent ScaleIO volume", "gateway": "The host address of the ScaleIO API Gateway.", "system": "The name of the storage system as configured in ScaleIO.", "secretRef": "SecretRef references to the secret for ScaleIO user and other sensitive information. If this is not provided, Login operation will fail.", "sslEnabled": "Flag to enable/disable SSL communication with Gateway, default false", - "protectionDomain": "The name of the Protection Domain for the configured storage (defaults to \"default\").", - "storagePool": "The Storage Pool associated with the protection domain (defaults to \"default\").", - "storageMode": "Indicates whether the storage for a volume should be thick or thin (defaults to \"thin\").", + "protectionDomain": "The name of the ScaleIO Protection Domain for the configured storage.", + "storagePool": "The ScaleIO Storage Pool associated with the protection domain.", + "storageMode": "Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned.", "volumeName": "The name of a volume already created in the ScaleIO system that is associated with this volume source.", "fsType": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.", "readOnly": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", @@ -1922,7 +2041,7 @@ var map_ServiceSpec = map[string]string{ "sessionAffinity": "Supports \"ClientIP\" and \"None\". Used to maintain session affinity. Enable client IP based session affinity. Must be ClientIP or None. Defaults to None. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies", "loadBalancerIP": "Only applies to Service Type: LoadBalancer LoadBalancer will get created with the IP specified in this field. This feature depends on whether the underlying cloud-provider supports specifying the loadBalancerIP when a load balancer is created. This field will be ignored if the cloud-provider does not support the feature.", "loadBalancerSourceRanges": "If specified and supported by the platform, this will restrict traffic through the cloud-provider load-balancer will be restricted to the specified client IPs. This field will be ignored if the cloud-provider does not support the feature.\" More info: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/", - "externalName": "externalName is the external reference that kubedns or equivalent will return as a CNAME record for this service. No proxying will be involved. Must be a valid DNS name and requires Type to be ExternalName.", + "externalName": "externalName is the external reference that kubedns or equivalent will return as a CNAME record for this service. No proxying will be involved. Must be a valid RFC-1123 hostname (https://tools.ietf.org/html/rfc1123) and requires Type to be ExternalName.", "externalTrafficPolicy": "externalTrafficPolicy denotes if this Service desires to route external traffic to node-local or cluster-wide endpoints. \"Local\" preserves the client source IP and avoids a second hop for LoadBalancer and Nodeport type services, but risks potentially imbalanced traffic spreading. \"Cluster\" obscures the client source IP and may cause a second hop to another node, but should have good overall load-spreading.", "healthCheckNodePort": "healthCheckNodePort specifies the healthcheck nodePort for the service. If not specified, HealthCheckNodePort is created by the service api backend with the allocated nodePort. Will use user-specified nodePort value if specified by the client. Only effects when Type is set to LoadBalancer and ExternalTrafficPolicy is set to Local.", "publishNotReadyAddresses": "publishNotReadyAddresses, when set to true, indicates that DNS implementations must publish the notReadyAddresses of subsets for the Endpoints associated with the Service. The default value is false. The primary use case for setting this field is to use a StatefulSet's Headless Service to propagate SRV records for its Pods without respect to their readiness for purpose of peer discovery. This field will replace the service.alpha.kubernetes.io/tolerate-unready-endpoints when that annotation is deprecated and all clients have been converted to use this field.", @@ -2031,6 +2150,16 @@ func (Volume) SwaggerDoc() map[string]string { return map_Volume } +var map_VolumeDevice = map[string]string{ + "": "volumeDevice describes a mapping of a raw block device within a container.", + "name": "name must match the name of a persistentVolumeClaim in the pod", + "devicePath": "devicePath is the path inside of the container that the device will be mapped to.", +} + +func (VolumeDevice) SwaggerDoc() map[string]string { + return map_VolumeDevice +} + var map_VolumeMount = map[string]string{ "": "VolumeMount describes a mounting of a Volume within a container.", "name": "This must match the Name of a Volume.", @@ -2068,7 +2197,7 @@ var map_VolumeSource = map[string]string{ "glusterfs": "Glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. More info: https://releases.k8s.io/HEAD/examples/volumes/glusterfs/README.md", "persistentVolumeClaim": "PersistentVolumeClaimVolumeSource represents a reference to a PersistentVolumeClaim in the same namespace. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims", "rbd": "RBD represents a Rados Block Device mount on the host that shares a pod's lifetime. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md", - "flexVolume": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.", + "flexVolume": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.", "cinder": "Cinder represents a cinder volume attached and mounted on kubelets host machine More info: https://releases.k8s.io/HEAD/examples/mysql-cinder-pd/README.md", "cephfs": "CephFS represents a Ceph FS mount on the host that shares a pod's lifetime", "flocker": "Flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running", 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 18d046eb663..8bf91804510 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 @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,737 +23,10 @@ package v1 import ( resource "k8s.io/apimachinery/pkg/api/resource" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" types "k8s.io/apimachinery/pkg/types" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AWSElasticBlockStoreVolumeSource).DeepCopyInto(out.(*AWSElasticBlockStoreVolumeSource)) - return nil - }, InType: reflect.TypeOf(&AWSElasticBlockStoreVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Affinity).DeepCopyInto(out.(*Affinity)) - return nil - }, InType: reflect.TypeOf(&Affinity{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AttachedVolume).DeepCopyInto(out.(*AttachedVolume)) - return nil - }, InType: reflect.TypeOf(&AttachedVolume{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AvoidPods).DeepCopyInto(out.(*AvoidPods)) - return nil - }, InType: reflect.TypeOf(&AvoidPods{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AzureDiskVolumeSource).DeepCopyInto(out.(*AzureDiskVolumeSource)) - return nil - }, InType: reflect.TypeOf(&AzureDiskVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AzureFilePersistentVolumeSource).DeepCopyInto(out.(*AzureFilePersistentVolumeSource)) - return nil - }, InType: reflect.TypeOf(&AzureFilePersistentVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AzureFileVolumeSource).DeepCopyInto(out.(*AzureFileVolumeSource)) - return nil - }, InType: reflect.TypeOf(&AzureFileVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Binding).DeepCopyInto(out.(*Binding)) - return nil - }, InType: reflect.TypeOf(&Binding{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Capabilities).DeepCopyInto(out.(*Capabilities)) - return nil - }, InType: reflect.TypeOf(&Capabilities{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CephFSPersistentVolumeSource).DeepCopyInto(out.(*CephFSPersistentVolumeSource)) - return nil - }, InType: reflect.TypeOf(&CephFSPersistentVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CephFSVolumeSource).DeepCopyInto(out.(*CephFSVolumeSource)) - return nil - }, InType: reflect.TypeOf(&CephFSVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CinderVolumeSource).DeepCopyInto(out.(*CinderVolumeSource)) - return nil - }, InType: reflect.TypeOf(&CinderVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClientIPConfig).DeepCopyInto(out.(*ClientIPConfig)) - return nil - }, InType: reflect.TypeOf(&ClientIPConfig{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ComponentCondition).DeepCopyInto(out.(*ComponentCondition)) - return nil - }, InType: reflect.TypeOf(&ComponentCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ComponentStatus).DeepCopyInto(out.(*ComponentStatus)) - return nil - }, InType: reflect.TypeOf(&ComponentStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ComponentStatusList).DeepCopyInto(out.(*ComponentStatusList)) - return nil - }, InType: reflect.TypeOf(&ComponentStatusList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ConfigMap).DeepCopyInto(out.(*ConfigMap)) - return nil - }, InType: reflect.TypeOf(&ConfigMap{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ConfigMapEnvSource).DeepCopyInto(out.(*ConfigMapEnvSource)) - return nil - }, InType: reflect.TypeOf(&ConfigMapEnvSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ConfigMapKeySelector).DeepCopyInto(out.(*ConfigMapKeySelector)) - return nil - }, InType: reflect.TypeOf(&ConfigMapKeySelector{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ConfigMapList).DeepCopyInto(out.(*ConfigMapList)) - return nil - }, InType: reflect.TypeOf(&ConfigMapList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ConfigMapProjection).DeepCopyInto(out.(*ConfigMapProjection)) - return nil - }, InType: reflect.TypeOf(&ConfigMapProjection{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ConfigMapVolumeSource).DeepCopyInto(out.(*ConfigMapVolumeSource)) - return nil - }, InType: reflect.TypeOf(&ConfigMapVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Container).DeepCopyInto(out.(*Container)) - return nil - }, InType: reflect.TypeOf(&Container{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ContainerImage).DeepCopyInto(out.(*ContainerImage)) - return nil - }, InType: reflect.TypeOf(&ContainerImage{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ContainerPort).DeepCopyInto(out.(*ContainerPort)) - return nil - }, InType: reflect.TypeOf(&ContainerPort{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ContainerState).DeepCopyInto(out.(*ContainerState)) - return nil - }, InType: reflect.TypeOf(&ContainerState{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ContainerStateRunning).DeepCopyInto(out.(*ContainerStateRunning)) - return nil - }, InType: reflect.TypeOf(&ContainerStateRunning{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ContainerStateTerminated).DeepCopyInto(out.(*ContainerStateTerminated)) - return nil - }, InType: reflect.TypeOf(&ContainerStateTerminated{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ContainerStateWaiting).DeepCopyInto(out.(*ContainerStateWaiting)) - return nil - }, InType: reflect.TypeOf(&ContainerStateWaiting{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ContainerStatus).DeepCopyInto(out.(*ContainerStatus)) - return nil - }, InType: reflect.TypeOf(&ContainerStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DaemonEndpoint).DeepCopyInto(out.(*DaemonEndpoint)) - return nil - }, InType: reflect.TypeOf(&DaemonEndpoint{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeleteOptions).DeepCopyInto(out.(*DeleteOptions)) - return nil - }, InType: reflect.TypeOf(&DeleteOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DownwardAPIProjection).DeepCopyInto(out.(*DownwardAPIProjection)) - return nil - }, InType: reflect.TypeOf(&DownwardAPIProjection{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DownwardAPIVolumeFile).DeepCopyInto(out.(*DownwardAPIVolumeFile)) - return nil - }, InType: reflect.TypeOf(&DownwardAPIVolumeFile{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DownwardAPIVolumeSource).DeepCopyInto(out.(*DownwardAPIVolumeSource)) - return nil - }, InType: reflect.TypeOf(&DownwardAPIVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*EmptyDirVolumeSource).DeepCopyInto(out.(*EmptyDirVolumeSource)) - return nil - }, InType: reflect.TypeOf(&EmptyDirVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*EndpointAddress).DeepCopyInto(out.(*EndpointAddress)) - return nil - }, InType: reflect.TypeOf(&EndpointAddress{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*EndpointPort).DeepCopyInto(out.(*EndpointPort)) - return nil - }, InType: reflect.TypeOf(&EndpointPort{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*EndpointSubset).DeepCopyInto(out.(*EndpointSubset)) - return nil - }, InType: reflect.TypeOf(&EndpointSubset{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Endpoints).DeepCopyInto(out.(*Endpoints)) - return nil - }, InType: reflect.TypeOf(&Endpoints{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*EndpointsList).DeepCopyInto(out.(*EndpointsList)) - return nil - }, InType: reflect.TypeOf(&EndpointsList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*EnvFromSource).DeepCopyInto(out.(*EnvFromSource)) - return nil - }, InType: reflect.TypeOf(&EnvFromSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*EnvVar).DeepCopyInto(out.(*EnvVar)) - return nil - }, InType: reflect.TypeOf(&EnvVar{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*EnvVarSource).DeepCopyInto(out.(*EnvVarSource)) - return nil - }, InType: reflect.TypeOf(&EnvVarSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Event).DeepCopyInto(out.(*Event)) - return nil - }, InType: reflect.TypeOf(&Event{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*EventList).DeepCopyInto(out.(*EventList)) - return nil - }, InType: reflect.TypeOf(&EventList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*EventSource).DeepCopyInto(out.(*EventSource)) - return nil - }, InType: reflect.TypeOf(&EventSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExecAction).DeepCopyInto(out.(*ExecAction)) - return nil - }, InType: reflect.TypeOf(&ExecAction{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*FCVolumeSource).DeepCopyInto(out.(*FCVolumeSource)) - return nil - }, InType: reflect.TypeOf(&FCVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*FlexVolumeSource).DeepCopyInto(out.(*FlexVolumeSource)) - return nil - }, InType: reflect.TypeOf(&FlexVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*FlockerVolumeSource).DeepCopyInto(out.(*FlockerVolumeSource)) - return nil - }, InType: reflect.TypeOf(&FlockerVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*GCEPersistentDiskVolumeSource).DeepCopyInto(out.(*GCEPersistentDiskVolumeSource)) - return nil - }, InType: reflect.TypeOf(&GCEPersistentDiskVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*GitRepoVolumeSource).DeepCopyInto(out.(*GitRepoVolumeSource)) - return nil - }, InType: reflect.TypeOf(&GitRepoVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*GlusterfsVolumeSource).DeepCopyInto(out.(*GlusterfsVolumeSource)) - return nil - }, InType: reflect.TypeOf(&GlusterfsVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HTTPGetAction).DeepCopyInto(out.(*HTTPGetAction)) - return nil - }, InType: reflect.TypeOf(&HTTPGetAction{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HTTPHeader).DeepCopyInto(out.(*HTTPHeader)) - return nil - }, InType: reflect.TypeOf(&HTTPHeader{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Handler).DeepCopyInto(out.(*Handler)) - return nil - }, InType: reflect.TypeOf(&Handler{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HostAlias).DeepCopyInto(out.(*HostAlias)) - return nil - }, InType: reflect.TypeOf(&HostAlias{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HostPathVolumeSource).DeepCopyInto(out.(*HostPathVolumeSource)) - return nil - }, InType: reflect.TypeOf(&HostPathVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ISCSIVolumeSource).DeepCopyInto(out.(*ISCSIVolumeSource)) - return nil - }, InType: reflect.TypeOf(&ISCSIVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*KeyToPath).DeepCopyInto(out.(*KeyToPath)) - return nil - }, InType: reflect.TypeOf(&KeyToPath{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Lifecycle).DeepCopyInto(out.(*Lifecycle)) - return nil - }, InType: reflect.TypeOf(&Lifecycle{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LimitRange).DeepCopyInto(out.(*LimitRange)) - return nil - }, InType: reflect.TypeOf(&LimitRange{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LimitRangeItem).DeepCopyInto(out.(*LimitRangeItem)) - return nil - }, InType: reflect.TypeOf(&LimitRangeItem{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LimitRangeList).DeepCopyInto(out.(*LimitRangeList)) - return nil - }, InType: reflect.TypeOf(&LimitRangeList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LimitRangeSpec).DeepCopyInto(out.(*LimitRangeSpec)) - return nil - }, InType: reflect.TypeOf(&LimitRangeSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*List).DeepCopyInto(out.(*List)) - return nil - }, InType: reflect.TypeOf(&List{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ListOptions).DeepCopyInto(out.(*ListOptions)) - return nil - }, InType: reflect.TypeOf(&ListOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LoadBalancerIngress).DeepCopyInto(out.(*LoadBalancerIngress)) - return nil - }, InType: reflect.TypeOf(&LoadBalancerIngress{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LoadBalancerStatus).DeepCopyInto(out.(*LoadBalancerStatus)) - return nil - }, InType: reflect.TypeOf(&LoadBalancerStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LocalObjectReference).DeepCopyInto(out.(*LocalObjectReference)) - return nil - }, InType: reflect.TypeOf(&LocalObjectReference{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LocalVolumeSource).DeepCopyInto(out.(*LocalVolumeSource)) - return nil - }, InType: reflect.TypeOf(&LocalVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NFSVolumeSource).DeepCopyInto(out.(*NFSVolumeSource)) - return nil - }, InType: reflect.TypeOf(&NFSVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Namespace).DeepCopyInto(out.(*Namespace)) - return nil - }, InType: reflect.TypeOf(&Namespace{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NamespaceList).DeepCopyInto(out.(*NamespaceList)) - return nil - }, InType: reflect.TypeOf(&NamespaceList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NamespaceSpec).DeepCopyInto(out.(*NamespaceSpec)) - return nil - }, InType: reflect.TypeOf(&NamespaceSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NamespaceStatus).DeepCopyInto(out.(*NamespaceStatus)) - return nil - }, InType: reflect.TypeOf(&NamespaceStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Node).DeepCopyInto(out.(*Node)) - return nil - }, InType: reflect.TypeOf(&Node{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeAddress).DeepCopyInto(out.(*NodeAddress)) - return nil - }, InType: reflect.TypeOf(&NodeAddress{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeAffinity).DeepCopyInto(out.(*NodeAffinity)) - return nil - }, InType: reflect.TypeOf(&NodeAffinity{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeCondition).DeepCopyInto(out.(*NodeCondition)) - return nil - }, InType: reflect.TypeOf(&NodeCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeConfigSource).DeepCopyInto(out.(*NodeConfigSource)) - return nil - }, InType: reflect.TypeOf(&NodeConfigSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeDaemonEndpoints).DeepCopyInto(out.(*NodeDaemonEndpoints)) - return nil - }, InType: reflect.TypeOf(&NodeDaemonEndpoints{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeList).DeepCopyInto(out.(*NodeList)) - return nil - }, InType: reflect.TypeOf(&NodeList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeProxyOptions).DeepCopyInto(out.(*NodeProxyOptions)) - return nil - }, InType: reflect.TypeOf(&NodeProxyOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeResources).DeepCopyInto(out.(*NodeResources)) - return nil - }, InType: reflect.TypeOf(&NodeResources{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeSelector).DeepCopyInto(out.(*NodeSelector)) - return nil - }, InType: reflect.TypeOf(&NodeSelector{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeSelectorRequirement).DeepCopyInto(out.(*NodeSelectorRequirement)) - return nil - }, InType: reflect.TypeOf(&NodeSelectorRequirement{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeSelectorTerm).DeepCopyInto(out.(*NodeSelectorTerm)) - return nil - }, InType: reflect.TypeOf(&NodeSelectorTerm{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeSpec).DeepCopyInto(out.(*NodeSpec)) - return nil - }, InType: reflect.TypeOf(&NodeSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeStatus).DeepCopyInto(out.(*NodeStatus)) - return nil - }, InType: reflect.TypeOf(&NodeStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NodeSystemInfo).DeepCopyInto(out.(*NodeSystemInfo)) - return nil - }, InType: reflect.TypeOf(&NodeSystemInfo{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ObjectFieldSelector).DeepCopyInto(out.(*ObjectFieldSelector)) - return nil - }, InType: reflect.TypeOf(&ObjectFieldSelector{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ObjectMeta).DeepCopyInto(out.(*ObjectMeta)) - return nil - }, InType: reflect.TypeOf(&ObjectMeta{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ObjectReference).DeepCopyInto(out.(*ObjectReference)) - return nil - }, InType: reflect.TypeOf(&ObjectReference{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PersistentVolume).DeepCopyInto(out.(*PersistentVolume)) - return nil - }, InType: reflect.TypeOf(&PersistentVolume{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PersistentVolumeClaim).DeepCopyInto(out.(*PersistentVolumeClaim)) - return nil - }, InType: reflect.TypeOf(&PersistentVolumeClaim{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PersistentVolumeClaimCondition).DeepCopyInto(out.(*PersistentVolumeClaimCondition)) - return nil - }, InType: reflect.TypeOf(&PersistentVolumeClaimCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PersistentVolumeClaimList).DeepCopyInto(out.(*PersistentVolumeClaimList)) - return nil - }, InType: reflect.TypeOf(&PersistentVolumeClaimList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PersistentVolumeClaimSpec).DeepCopyInto(out.(*PersistentVolumeClaimSpec)) - return nil - }, InType: reflect.TypeOf(&PersistentVolumeClaimSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PersistentVolumeClaimStatus).DeepCopyInto(out.(*PersistentVolumeClaimStatus)) - return nil - }, InType: reflect.TypeOf(&PersistentVolumeClaimStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PersistentVolumeClaimVolumeSource).DeepCopyInto(out.(*PersistentVolumeClaimVolumeSource)) - return nil - }, InType: reflect.TypeOf(&PersistentVolumeClaimVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PersistentVolumeList).DeepCopyInto(out.(*PersistentVolumeList)) - return nil - }, InType: reflect.TypeOf(&PersistentVolumeList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PersistentVolumeSource).DeepCopyInto(out.(*PersistentVolumeSource)) - return nil - }, InType: reflect.TypeOf(&PersistentVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PersistentVolumeSpec).DeepCopyInto(out.(*PersistentVolumeSpec)) - return nil - }, InType: reflect.TypeOf(&PersistentVolumeSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PersistentVolumeStatus).DeepCopyInto(out.(*PersistentVolumeStatus)) - return nil - }, InType: reflect.TypeOf(&PersistentVolumeStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PhotonPersistentDiskVolumeSource).DeepCopyInto(out.(*PhotonPersistentDiskVolumeSource)) - return nil - }, InType: reflect.TypeOf(&PhotonPersistentDiskVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Pod).DeepCopyInto(out.(*Pod)) - return nil - }, InType: reflect.TypeOf(&Pod{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodAffinity).DeepCopyInto(out.(*PodAffinity)) - return nil - }, InType: reflect.TypeOf(&PodAffinity{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodAffinityTerm).DeepCopyInto(out.(*PodAffinityTerm)) - return nil - }, InType: reflect.TypeOf(&PodAffinityTerm{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodAntiAffinity).DeepCopyInto(out.(*PodAntiAffinity)) - return nil - }, InType: reflect.TypeOf(&PodAntiAffinity{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodAttachOptions).DeepCopyInto(out.(*PodAttachOptions)) - return nil - }, InType: reflect.TypeOf(&PodAttachOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodCondition).DeepCopyInto(out.(*PodCondition)) - return nil - }, InType: reflect.TypeOf(&PodCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodExecOptions).DeepCopyInto(out.(*PodExecOptions)) - return nil - }, InType: reflect.TypeOf(&PodExecOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodList).DeepCopyInto(out.(*PodList)) - return nil - }, InType: reflect.TypeOf(&PodList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodLogOptions).DeepCopyInto(out.(*PodLogOptions)) - return nil - }, InType: reflect.TypeOf(&PodLogOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodPortForwardOptions).DeepCopyInto(out.(*PodPortForwardOptions)) - return nil - }, InType: reflect.TypeOf(&PodPortForwardOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodProxyOptions).DeepCopyInto(out.(*PodProxyOptions)) - return nil - }, InType: reflect.TypeOf(&PodProxyOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodSecurityContext).DeepCopyInto(out.(*PodSecurityContext)) - return nil - }, InType: reflect.TypeOf(&PodSecurityContext{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodSignature).DeepCopyInto(out.(*PodSignature)) - return nil - }, InType: reflect.TypeOf(&PodSignature{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodSpec).DeepCopyInto(out.(*PodSpec)) - return nil - }, InType: reflect.TypeOf(&PodSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodStatus).DeepCopyInto(out.(*PodStatus)) - return nil - }, InType: reflect.TypeOf(&PodStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodStatusResult).DeepCopyInto(out.(*PodStatusResult)) - return nil - }, InType: reflect.TypeOf(&PodStatusResult{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodTemplate).DeepCopyInto(out.(*PodTemplate)) - return nil - }, InType: reflect.TypeOf(&PodTemplate{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodTemplateList).DeepCopyInto(out.(*PodTemplateList)) - return nil - }, InType: reflect.TypeOf(&PodTemplateList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodTemplateSpec).DeepCopyInto(out.(*PodTemplateSpec)) - return nil - }, InType: reflect.TypeOf(&PodTemplateSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PortworxVolumeSource).DeepCopyInto(out.(*PortworxVolumeSource)) - return nil - }, InType: reflect.TypeOf(&PortworxVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Preconditions).DeepCopyInto(out.(*Preconditions)) - return nil - }, InType: reflect.TypeOf(&Preconditions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PreferAvoidPodsEntry).DeepCopyInto(out.(*PreferAvoidPodsEntry)) - return nil - }, InType: reflect.TypeOf(&PreferAvoidPodsEntry{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PreferredSchedulingTerm).DeepCopyInto(out.(*PreferredSchedulingTerm)) - return nil - }, InType: reflect.TypeOf(&PreferredSchedulingTerm{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Probe).DeepCopyInto(out.(*Probe)) - return nil - }, InType: reflect.TypeOf(&Probe{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ProjectedVolumeSource).DeepCopyInto(out.(*ProjectedVolumeSource)) - return nil - }, InType: reflect.TypeOf(&ProjectedVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*QuobyteVolumeSource).DeepCopyInto(out.(*QuobyteVolumeSource)) - return nil - }, InType: reflect.TypeOf(&QuobyteVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RBDVolumeSource).DeepCopyInto(out.(*RBDVolumeSource)) - return nil - }, InType: reflect.TypeOf(&RBDVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RangeAllocation).DeepCopyInto(out.(*RangeAllocation)) - return nil - }, InType: reflect.TypeOf(&RangeAllocation{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicationController).DeepCopyInto(out.(*ReplicationController)) - return nil - }, InType: reflect.TypeOf(&ReplicationController{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicationControllerCondition).DeepCopyInto(out.(*ReplicationControllerCondition)) - return nil - }, InType: reflect.TypeOf(&ReplicationControllerCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicationControllerList).DeepCopyInto(out.(*ReplicationControllerList)) - return nil - }, InType: reflect.TypeOf(&ReplicationControllerList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicationControllerSpec).DeepCopyInto(out.(*ReplicationControllerSpec)) - return nil - }, InType: reflect.TypeOf(&ReplicationControllerSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicationControllerStatus).DeepCopyInto(out.(*ReplicationControllerStatus)) - return nil - }, InType: reflect.TypeOf(&ReplicationControllerStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ResourceFieldSelector).DeepCopyInto(out.(*ResourceFieldSelector)) - return nil - }, InType: reflect.TypeOf(&ResourceFieldSelector{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ResourceQuota).DeepCopyInto(out.(*ResourceQuota)) - return nil - }, InType: reflect.TypeOf(&ResourceQuota{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ResourceQuotaList).DeepCopyInto(out.(*ResourceQuotaList)) - return nil - }, InType: reflect.TypeOf(&ResourceQuotaList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ResourceQuotaSpec).DeepCopyInto(out.(*ResourceQuotaSpec)) - return nil - }, InType: reflect.TypeOf(&ResourceQuotaSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ResourceQuotaStatus).DeepCopyInto(out.(*ResourceQuotaStatus)) - return nil - }, InType: reflect.TypeOf(&ResourceQuotaStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ResourceRequirements).DeepCopyInto(out.(*ResourceRequirements)) - return nil - }, InType: reflect.TypeOf(&ResourceRequirements{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SELinuxOptions).DeepCopyInto(out.(*SELinuxOptions)) - return nil - }, InType: reflect.TypeOf(&SELinuxOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ScaleIOVolumeSource).DeepCopyInto(out.(*ScaleIOVolumeSource)) - return nil - }, InType: reflect.TypeOf(&ScaleIOVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Secret).DeepCopyInto(out.(*Secret)) - return nil - }, InType: reflect.TypeOf(&Secret{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SecretEnvSource).DeepCopyInto(out.(*SecretEnvSource)) - return nil - }, InType: reflect.TypeOf(&SecretEnvSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SecretKeySelector).DeepCopyInto(out.(*SecretKeySelector)) - return nil - }, InType: reflect.TypeOf(&SecretKeySelector{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SecretList).DeepCopyInto(out.(*SecretList)) - return nil - }, InType: reflect.TypeOf(&SecretList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SecretProjection).DeepCopyInto(out.(*SecretProjection)) - return nil - }, InType: reflect.TypeOf(&SecretProjection{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SecretReference).DeepCopyInto(out.(*SecretReference)) - return nil - }, InType: reflect.TypeOf(&SecretReference{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SecretVolumeSource).DeepCopyInto(out.(*SecretVolumeSource)) - return nil - }, InType: reflect.TypeOf(&SecretVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SecurityContext).DeepCopyInto(out.(*SecurityContext)) - return nil - }, InType: reflect.TypeOf(&SecurityContext{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SerializedReference).DeepCopyInto(out.(*SerializedReference)) - return nil - }, InType: reflect.TypeOf(&SerializedReference{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Service).DeepCopyInto(out.(*Service)) - return nil - }, InType: reflect.TypeOf(&Service{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ServiceAccount).DeepCopyInto(out.(*ServiceAccount)) - return nil - }, InType: reflect.TypeOf(&ServiceAccount{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ServiceAccountList).DeepCopyInto(out.(*ServiceAccountList)) - return nil - }, InType: reflect.TypeOf(&ServiceAccountList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ServiceList).DeepCopyInto(out.(*ServiceList)) - return nil - }, InType: reflect.TypeOf(&ServiceList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ServicePort).DeepCopyInto(out.(*ServicePort)) - return nil - }, InType: reflect.TypeOf(&ServicePort{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ServiceProxyOptions).DeepCopyInto(out.(*ServiceProxyOptions)) - return nil - }, InType: reflect.TypeOf(&ServiceProxyOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ServiceSpec).DeepCopyInto(out.(*ServiceSpec)) - return nil - }, InType: reflect.TypeOf(&ServiceSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ServiceStatus).DeepCopyInto(out.(*ServiceStatus)) - return nil - }, InType: reflect.TypeOf(&ServiceStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SessionAffinityConfig).DeepCopyInto(out.(*SessionAffinityConfig)) - return nil - }, InType: reflect.TypeOf(&SessionAffinityConfig{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StorageOSPersistentVolumeSource).DeepCopyInto(out.(*StorageOSPersistentVolumeSource)) - return nil - }, InType: reflect.TypeOf(&StorageOSPersistentVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StorageOSVolumeSource).DeepCopyInto(out.(*StorageOSVolumeSource)) - return nil - }, InType: reflect.TypeOf(&StorageOSVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Sysctl).DeepCopyInto(out.(*Sysctl)) - return nil - }, InType: reflect.TypeOf(&Sysctl{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*TCPSocketAction).DeepCopyInto(out.(*TCPSocketAction)) - return nil - }, InType: reflect.TypeOf(&TCPSocketAction{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Taint).DeepCopyInto(out.(*Taint)) - return nil - }, InType: reflect.TypeOf(&Taint{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Toleration).DeepCopyInto(out.(*Toleration)) - return nil - }, InType: reflect.TypeOf(&Toleration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Volume).DeepCopyInto(out.(*Volume)) - return nil - }, InType: reflect.TypeOf(&Volume{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*VolumeMount).DeepCopyInto(out.(*VolumeMount)) - return nil - }, InType: reflect.TypeOf(&VolumeMount{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*VolumeProjection).DeepCopyInto(out.(*VolumeProjection)) - return nil - }, InType: reflect.TypeOf(&VolumeProjection{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*VolumeSource).DeepCopyInto(out.(*VolumeSource)) - return nil - }, InType: reflect.TypeOf(&VolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*VsphereVirtualDiskVolumeSource).DeepCopyInto(out.(*VsphereVirtualDiskVolumeSource)) - return nil - }, InType: reflect.TypeOf(&VsphereVirtualDiskVolumeSource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*WeightedPodAffinityTerm).DeepCopyInto(out.(*WeightedPodAffinityTerm)) - return nil - }, InType: reflect.TypeOf(&WeightedPodAffinityTerm{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AWSElasticBlockStoreVolumeSource) DeepCopyInto(out *AWSElasticBlockStoreVolumeSource) { *out = *in @@ -973,6 +246,22 @@ func (in *Binding) DeepCopyObject() runtime.Object { } } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CSIPersistentVolumeSource) DeepCopyInto(out *CSIPersistentVolumeSource) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CSIPersistentVolumeSource. +func (in *CSIPersistentVolumeSource) DeepCopy() *CSIPersistentVolumeSource { + if in == nil { + return nil + } + out := new(CSIPersistentVolumeSource) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Capabilities) DeepCopyInto(out *Capabilities) { *out = *in @@ -1417,6 +706,11 @@ func (in *Container) DeepCopyInto(out *Container) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.VolumeDevices != nil { + in, out := &in.VolumeDevices, &out.VolumeDevices + *out = make([]VolumeDevice, len(*in)) + copy(*out, *in) + } if in.LivenessProbe != nil { in, out := &in.LivenessProbe, &out.LivenessProbe if *in == nil { @@ -2089,6 +1383,25 @@ func (in *Event) DeepCopyInto(out *Event) { out.Source = in.Source in.FirstTimestamp.DeepCopyInto(&out.FirstTimestamp) in.LastTimestamp.DeepCopyInto(&out.LastTimestamp) + in.EventTime.DeepCopyInto(&out.EventTime) + if in.Series != nil { + in, out := &in.Series, &out.Series + if *in == nil { + *out = nil + } else { + *out = new(EventSeries) + (*in).DeepCopyInto(*out) + } + } + if in.Related != nil { + in, out := &in.Related, &out.Related + if *in == nil { + *out = nil + } else { + *out = new(ObjectReference) + **out = **in + } + } return } @@ -2145,6 +1458,23 @@ func (in *EventList) DeepCopyObject() runtime.Object { } } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EventSeries) DeepCopyInto(out *EventSeries) { + *out = *in + in.LastObservedTime.DeepCopyInto(&out.LastObservedTime) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventSeries. +func (in *EventSeries) DeepCopy() *EventSeries { + if in == nil { + return nil + } + out := new(EventSeries) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EventSource) DeepCopyInto(out *EventSource) { *out = *in @@ -2217,6 +1547,38 @@ func (in *FCVolumeSource) DeepCopy() *FCVolumeSource { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FlexPersistentVolumeSource) DeepCopyInto(out *FlexPersistentVolumeSource) { + *out = *in + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + if *in == nil { + *out = nil + } else { + *out = new(SecretReference) + **out = **in + } + } + if in.Options != nil { + in, out := &in.Options, &out.Options + *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 FlexPersistentVolumeSource. +func (in *FlexPersistentVolumeSource) DeepCopy() *FlexPersistentVolumeSource { + if in == nil { + return nil + } + out := new(FlexPersistentVolumeSource) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FlexVolumeSource) DeepCopyInto(out *FlexVolumeSource) { *out = *in @@ -2440,6 +1802,45 @@ func (in *HostPathVolumeSource) DeepCopy() *HostPathVolumeSource { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ISCSIPersistentVolumeSource) DeepCopyInto(out *ISCSIPersistentVolumeSource) { + *out = *in + if in.Portals != nil { + in, out := &in.Portals, &out.Portals + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + if *in == nil { + *out = nil + } else { + *out = new(SecretReference) + **out = **in + } + } + if in.InitiatorName != nil { + in, out := &in.InitiatorName, &out.InitiatorName + if *in == nil { + *out = nil + } else { + *out = new(string) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ISCSIPersistentVolumeSource. +func (in *ISCSIPersistentVolumeSource) DeepCopy() *ISCSIPersistentVolumeSource { + if in == nil { + return nil + } + out := new(ISCSIPersistentVolumeSource) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ISCSIVolumeSource) DeepCopyInto(out *ISCSIVolumeSource) { *out = *in @@ -3582,6 +2983,15 @@ func (in *PersistentVolumeClaimSpec) DeepCopyInto(out *PersistentVolumeClaimSpec **out = **in } } + if in.VolumeMode != nil { + in, out := &in.VolumeMode, &out.VolumeMode + if *in == nil { + *out = nil + } else { + *out = new(PersistentVolumeMode) + **out = **in + } + } return } @@ -3733,7 +3143,7 @@ func (in *PersistentVolumeSource) DeepCopyInto(out *PersistentVolumeSource) { if *in == nil { *out = nil } else { - *out = new(RBDVolumeSource) + *out = new(RBDPersistentVolumeSource) (*in).DeepCopyInto(*out) } } @@ -3742,7 +3152,7 @@ func (in *PersistentVolumeSource) DeepCopyInto(out *PersistentVolumeSource) { if *in == nil { *out = nil } else { - *out = new(ISCSIVolumeSource) + *out = new(ISCSIPersistentVolumeSource) (*in).DeepCopyInto(*out) } } @@ -3787,7 +3197,7 @@ func (in *PersistentVolumeSource) DeepCopyInto(out *PersistentVolumeSource) { if *in == nil { *out = nil } else { - *out = new(FlexVolumeSource) + *out = new(FlexPersistentVolumeSource) (*in).DeepCopyInto(*out) } } @@ -3850,7 +3260,7 @@ func (in *PersistentVolumeSource) DeepCopyInto(out *PersistentVolumeSource) { if *in == nil { *out = nil } else { - *out = new(ScaleIOVolumeSource) + *out = new(ScaleIOPersistentVolumeSource) (*in).DeepCopyInto(*out) } } @@ -3872,6 +3282,15 @@ func (in *PersistentVolumeSource) DeepCopyInto(out *PersistentVolumeSource) { (*in).DeepCopyInto(*out) } } + if in.CSI != nil { + in, out := &in.CSI, &out.CSI + if *in == nil { + *out = nil + } else { + *out = new(CSIPersistentVolumeSource) + **out = **in + } + } return } @@ -3915,6 +3334,15 @@ func (in *PersistentVolumeSpec) DeepCopyInto(out *PersistentVolumeSpec) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.VolumeMode != nil { + in, out := &in.VolumeMode, &out.VolumeMode + if *in == nil { + *out = nil + } else { + *out = new(PersistentVolumeMode) + **out = **in + } + } return } @@ -4123,6 +3551,64 @@ func (in *PodCondition) DeepCopy() *PodCondition { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodDNSConfig) DeepCopyInto(out *PodDNSConfig) { + *out = *in + if in.Nameservers != nil { + in, out := &in.Nameservers, &out.Nameservers + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Searches != nil { + in, out := &in.Searches, &out.Searches + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Options != nil { + in, out := &in.Options, &out.Options + *out = make([]PodDNSConfigOption, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodDNSConfig. +func (in *PodDNSConfig) DeepCopy() *PodDNSConfig { + if in == nil { + return nil + } + out := new(PodDNSConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodDNSConfigOption) DeepCopyInto(out *PodDNSConfigOption) { + *out = *in + if in.Value != nil { + in, out := &in.Value, &out.Value + if *in == nil { + *out = nil + } else { + *out = new(string) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodDNSConfigOption. +func (in *PodDNSConfigOption) DeepCopy() *PodDNSConfigOption { + if in == nil { + return nil + } + out := new(PodDNSConfigOption) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PodExecOptions) DeepCopyInto(out *PodExecOptions) { *out = *in @@ -4493,6 +3979,15 @@ func (in *PodSpec) DeepCopyInto(out *PodSpec) { **out = **in } } + if in.DNSConfig != nil { + in, out := &in.DNSConfig, &out.DNSConfig + if *in == nil { + *out = nil + } else { + *out = new(PodDNSConfig) + (*in).DeepCopyInto(*out) + } + } return } @@ -4801,6 +4296,36 @@ func (in *QuobyteVolumeSource) DeepCopy() *QuobyteVolumeSource { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RBDPersistentVolumeSource) DeepCopyInto(out *RBDPersistentVolumeSource) { + *out = *in + if in.CephMonitors != nil { + in, out := &in.CephMonitors, &out.CephMonitors + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + if *in == nil { + *out = nil + } else { + *out = new(SecretReference) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RBDPersistentVolumeSource. +func (in *RBDPersistentVolumeSource) DeepCopy() *RBDPersistentVolumeSource { + if in == nil { + return nil + } + out := new(RBDPersistentVolumeSource) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RBDVolumeSource) DeepCopyInto(out *RBDVolumeSource) { *out = *in @@ -5191,6 +4716,31 @@ func (in *SELinuxOptions) DeepCopy() *SELinuxOptions { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ScaleIOPersistentVolumeSource) DeepCopyInto(out *ScaleIOPersistentVolumeSource) { + *out = *in + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + if *in == nil { + *out = nil + } else { + *out = new(SecretReference) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScaleIOPersistentVolumeSource. +func (in *ScaleIOPersistentVolumeSource) DeepCopy() *ScaleIOPersistentVolumeSource { + if in == nil { + return nil + } + out := new(ScaleIOPersistentVolumeSource) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ScaleIOVolumeSource) DeepCopyInto(out *ScaleIOVolumeSource) { *out = *in @@ -5969,6 +5519,22 @@ func (in *Volume) DeepCopy() *Volume { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VolumeDevice) DeepCopyInto(out *VolumeDevice) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeDevice. +func (in *VolumeDevice) DeepCopy() *VolumeDevice { + if in == nil { + return nil + } + out := new(VolumeDevice) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VolumeMount) DeepCopyInto(out *VolumeMount) { *out = *in diff --git a/staging/src/k8s.io/api/events/OWNERS b/staging/src/k8s.io/api/events/OWNERS new file mode 100644 index 00000000000..d9ecd88564b --- /dev/null +++ b/staging/src/k8s.io/api/events/OWNERS @@ -0,0 +1,8 @@ +reviewers: +- gmarek +- deads2k +- sttts +approvers: +- gmarek +- deads2k +- sttts diff --git a/staging/src/k8s.io/api/events/v1beta1/BUILD b/staging/src/k8s.io/api/events/v1beta1/BUILD new file mode 100644 index 00000000000..851874e78cb --- /dev/null +++ b/staging/src/k8s.io/api/events/v1beta1/BUILD @@ -0,0 +1,42 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +filegroup( + name = "go_default_library_protos", + srcs = ["generated.proto"], + visibility = ["//visibility:public"], +) + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "generated.pb.go", + "register.go", + "types.go", + "types_swagger_doc_generated.go", + "zz_generated.deepcopy.go", + ], + importpath = "k8s.io/api/events/v1beta1", + visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/gogo/protobuf/proto:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/api/events/v1beta1/doc.go b/staging/src/k8s.io/api/events/v1beta1/doc.go new file mode 100644 index 00000000000..8b1a3e312de --- /dev/null +++ b/staging/src/k8s.io/api/events/v1beta1/doc.go @@ -0,0 +1,21 @@ +/* +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:openapi-gen=true + +// +groupName=events.k8s.io +package v1beta1 // import "k8s.io/api/events/v1beta1" diff --git a/staging/src/k8s.io/api/events/v1beta1/generated.pb.go b/staging/src/k8s.io/api/events/v1beta1/generated.pb.go new file mode 100644 index 00000000000..9aac8420f80 --- /dev/null +++ b/staging/src/k8s.io/api/events/v1beta1/generated.pb.go @@ -0,0 +1,1306 @@ +/* +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. +*/ + +// Code generated by protoc-gen-gogo. +// source: k8s.io/kubernetes/vendor/k8s.io/api/events/v1beta1/generated.proto +// DO NOT EDIT! + +/* + Package v1beta1 is a generated protocol buffer package. + + It is generated from these files: + k8s.io/kubernetes/vendor/k8s.io/api/events/v1beta1/generated.proto + + It has these top-level messages: + Event + EventList + EventSeries +*/ +package v1beta1 + +import proto "github.com/gogo/protobuf/proto" +import fmt "fmt" +import math "math" + +import k8s_io_api_core_v1 "k8s.io/api/core/v1" + +import strings "strings" +import reflect "reflect" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package + +func (m *Event) Reset() { *m = Event{} } +func (*Event) ProtoMessage() {} +func (*Event) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{0} } + +func (m *EventList) Reset() { *m = EventList{} } +func (*EventList) ProtoMessage() {} +func (*EventList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{1} } + +func (m *EventSeries) Reset() { *m = EventSeries{} } +func (*EventSeries) ProtoMessage() {} +func (*EventSeries) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{2} } + +func init() { + proto.RegisterType((*Event)(nil), "k8s.io.api.events.v1beta1.Event") + proto.RegisterType((*EventList)(nil), "k8s.io.api.events.v1beta1.EventList") + proto.RegisterType((*EventSeries)(nil), "k8s.io.api.events.v1beta1.EventSeries") +} +func (m *Event) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Event) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) + n1, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.EventTime.Size())) + n2, err := m.EventTime.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n2 + if m.Series != nil { + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Series.Size())) + n3, err := m.Series.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n3 + } + dAtA[i] = 0x22 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ReportingController))) + i += copy(dAtA[i:], m.ReportingController) + dAtA[i] = 0x2a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ReportingInstance))) + i += copy(dAtA[i:], m.ReportingInstance) + dAtA[i] = 0x32 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Action))) + i += copy(dAtA[i:], m.Action) + dAtA[i] = 0x3a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) + i += copy(dAtA[i:], m.Reason) + dAtA[i] = 0x42 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Regarding.Size())) + n4, err := m.Regarding.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n4 + if m.Related != nil { + dAtA[i] = 0x4a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Related.Size())) + n5, err := m.Related.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n5 + } + dAtA[i] = 0x52 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Note))) + i += copy(dAtA[i:], m.Note) + dAtA[i] = 0x5a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) + i += copy(dAtA[i:], m.Type) + dAtA[i] = 0x62 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.DeprecatedSource.Size())) + n6, err := m.DeprecatedSource.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n6 + dAtA[i] = 0x6a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.DeprecatedFirstTimestamp.Size())) + n7, err := m.DeprecatedFirstTimestamp.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n7 + dAtA[i] = 0x72 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.DeprecatedLastTimestamp.Size())) + n8, err := m.DeprecatedLastTimestamp.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n8 + dAtA[i] = 0x78 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.DeprecatedCount)) + return i, nil +} + +func (m *EventList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventList) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) + n9, err := m.ListMeta.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n9 + if len(m.Items) > 0 { + for _, msg := range m.Items { + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *EventSeries) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventSeries) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0x8 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Count)) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.LastObservedTime.Size())) + n10, err := m.LastObservedTime.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n10 + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.State))) + i += copy(dAtA[i:], m.State) + return i, nil +} + +func encodeFixed64Generated(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Generated(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *Event) Size() (n int) { + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.EventTime.Size() + n += 1 + l + sovGenerated(uint64(l)) + if m.Series != nil { + l = m.Series.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + l = len(m.ReportingController) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.ReportingInstance) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Action) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Reason) + n += 1 + l + sovGenerated(uint64(l)) + l = m.Regarding.Size() + n += 1 + l + sovGenerated(uint64(l)) + if m.Related != nil { + l = m.Related.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + l = len(m.Note) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Type) + n += 1 + l + sovGenerated(uint64(l)) + l = m.DeprecatedSource.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.DeprecatedFirstTimestamp.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.DeprecatedLastTimestamp.Size() + n += 1 + l + sovGenerated(uint64(l)) + n += 1 + sovGenerated(uint64(m.DeprecatedCount)) + return n +} + +func (m *EventList) Size() (n int) { + 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 *EventSeries) Size() (n int) { + var l int + _ = l + n += 1 + sovGenerated(uint64(m.Count)) + l = m.LastObservedTime.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.State) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func sovGenerated(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozGenerated(x uint64) (n int) { + return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (this *Event) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&Event{`, + `ObjectMeta:` + strings.Replace(strings.Replace(this.ObjectMeta.String(), "ObjectMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `EventTime:` + strings.Replace(strings.Replace(this.EventTime.String(), "MicroTime", "k8s_io_apimachinery_pkg_apis_meta_v1.MicroTime", 1), `&`, ``, 1) + `,`, + `Series:` + strings.Replace(fmt.Sprintf("%v", this.Series), "EventSeries", "EventSeries", 1) + `,`, + `ReportingController:` + fmt.Sprintf("%v", this.ReportingController) + `,`, + `ReportingInstance:` + fmt.Sprintf("%v", this.ReportingInstance) + `,`, + `Action:` + fmt.Sprintf("%v", this.Action) + `,`, + `Reason:` + fmt.Sprintf("%v", this.Reason) + `,`, + `Regarding:` + strings.Replace(strings.Replace(this.Regarding.String(), "ObjectReference", "k8s_io_api_core_v1.ObjectReference", 1), `&`, ``, 1) + `,`, + `Related:` + strings.Replace(fmt.Sprintf("%v", this.Related), "ObjectReference", "k8s_io_api_core_v1.ObjectReference", 1) + `,`, + `Note:` + fmt.Sprintf("%v", this.Note) + `,`, + `Type:` + fmt.Sprintf("%v", this.Type) + `,`, + `DeprecatedSource:` + strings.Replace(strings.Replace(this.DeprecatedSource.String(), "EventSource", "k8s_io_api_core_v1.EventSource", 1), `&`, ``, 1) + `,`, + `DeprecatedFirstTimestamp:` + strings.Replace(strings.Replace(this.DeprecatedFirstTimestamp.String(), "Time", "k8s_io_apimachinery_pkg_apis_meta_v1.Time", 1), `&`, ``, 1) + `,`, + `DeprecatedLastTimestamp:` + strings.Replace(strings.Replace(this.DeprecatedLastTimestamp.String(), "Time", "k8s_io_apimachinery_pkg_apis_meta_v1.Time", 1), `&`, ``, 1) + `,`, + `DeprecatedCount:` + fmt.Sprintf("%v", this.DeprecatedCount) + `,`, + `}`, + }, "") + return s +} +func (this *EventList) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&EventList{`, + `ListMeta:` + strings.Replace(strings.Replace(this.ListMeta.String(), "ListMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Items), "Event", "Event", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *EventSeries) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&EventSeries{`, + `Count:` + fmt.Sprintf("%v", this.Count) + `,`, + `LastObservedTime:` + strings.Replace(strings.Replace(this.LastObservedTime.String(), "MicroTime", "k8s_io_apimachinery_pkg_apis_meta_v1.MicroTime", 1), `&`, ``, 1) + `,`, + `State:` + fmt.Sprintf("%v", this.State) + `,`, + `}`, + }, "") + 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 *Event) 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: Event: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Event: 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 > 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 EventTime", 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.EventTime.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Series", 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 > l { + return io.ErrUnexpectedEOF + } + if m.Series == nil { + m.Series = &EventSeries{} + } + if err := m.Series.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ReportingController", 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 > l { + return io.ErrUnexpectedEOF + } + m.ReportingController = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ReportingInstance", 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 > l { + return io.ErrUnexpectedEOF + } + m.ReportingInstance = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Action", 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 > l { + return io.ErrUnexpectedEOF + } + m.Action = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + 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 > l { + return io.ErrUnexpectedEOF + } + m.Reason = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Regarding", 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.Regarding.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Related", 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 > l { + return io.ErrUnexpectedEOF + } + if m.Related == nil { + m.Related = &k8s_io_api_core_v1.ObjectReference{} + } + if err := m.Related.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Note", 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 > l { + return io.ErrUnexpectedEOF + } + m.Note = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 11: + 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 > l { + return io.ErrUnexpectedEOF + } + m.Type = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DeprecatedSource", 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.DeprecatedSource.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DeprecatedFirstTimestamp", 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.DeprecatedFirstTimestamp.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DeprecatedLastTimestamp", 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.DeprecatedLastTimestamp.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 15: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DeprecatedCount", wireType) + } + m.DeprecatedCount = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.DeprecatedCount |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *EventList) 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: EventList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventList: 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 > 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 > l { + return io.ErrUnexpectedEOF + } + m.Items = append(m.Items, Event{}) + 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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *EventSeries) 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: EventSeries: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventSeries: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + 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 |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LastObservedTime", 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.LastObservedTime.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field State", 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 > l { + return io.ErrUnexpectedEOF + } + m.State = EventSeriesState(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if 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 + 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 + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + 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 + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthGenerated + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipGenerated(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") +) + +func init() { + proto.RegisterFile("k8s.io/kubernetes/vendor/k8s.io/api/events/v1beta1/generated.proto", fileDescriptorGenerated) +} + +var fileDescriptorGenerated = []byte{ + // 814 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x54, 0xcd, 0x6e, 0xdb, 0x46, + 0x10, 0x16, 0x13, 0x4b, 0xb6, 0x56, 0x49, 0x2c, 0x6f, 0x0e, 0xde, 0xb8, 0x00, 0xa5, 0x3a, 0x40, + 0x60, 0x14, 0x08, 0x59, 0xa7, 0x45, 0xdb, 0x6b, 0x18, 0xbb, 0x45, 0x02, 0xbb, 0x01, 0xd6, 0x3e, + 0x15, 0x3d, 0x64, 0x45, 0x4d, 0xe8, 0xad, 0xa5, 0x5d, 0x62, 0x77, 0x29, 0xc0, 0xb7, 0x5e, 0x0a, + 0xf4, 0xd8, 0x67, 0xe8, 0x13, 0xf4, 0x31, 0x7c, 0xcc, 0x31, 0x27, 0xa1, 0x66, 0xdf, 0xa2, 0xa7, + 0x82, 0xcb, 0x95, 0x28, 0x8b, 0x16, 0xec, 0x22, 0x37, 0x72, 0xe6, 0xfb, 0x99, 0x19, 0x0e, 0x07, + 0x45, 0xe7, 0xdf, 0xe9, 0x80, 0xcb, 0xf0, 0x3c, 0x1b, 0x80, 0x12, 0x60, 0x40, 0x87, 0x13, 0x10, + 0x43, 0xa9, 0x42, 0x97, 0x60, 0x29, 0x0f, 0x61, 0x02, 0xc2, 0xe8, 0x70, 0xb2, 0x3f, 0x00, 0xc3, + 0xf6, 0xc3, 0x04, 0x04, 0x28, 0x66, 0x60, 0x18, 0xa4, 0x4a, 0x1a, 0x89, 0x9f, 0x94, 0xd0, 0x80, + 0xa5, 0x3c, 0x28, 0xa1, 0x81, 0x83, 0xee, 0x3c, 0x4f, 0xb8, 0x39, 0xcb, 0x06, 0x41, 0x2c, 0xc7, + 0x61, 0x22, 0x13, 0x19, 0x5a, 0xc6, 0x20, 0x7b, 0x6f, 0xdf, 0xec, 0x8b, 0x7d, 0x2a, 0x95, 0x76, + 0x76, 0x17, 0x4c, 0x63, 0xa9, 0x20, 0x9c, 0xd4, 0xdc, 0x76, 0xbe, 0xae, 0x30, 0x63, 0x16, 0x9f, + 0x71, 0x01, 0xea, 0x22, 0x4c, 0xcf, 0x93, 0x22, 0xa0, 0xc3, 0x31, 0x18, 0x76, 0x13, 0x2b, 0x5c, + 0xc5, 0x52, 0x99, 0x30, 0x7c, 0x0c, 0x35, 0xc2, 0x37, 0xb7, 0x11, 0x74, 0x7c, 0x06, 0x63, 0x56, + 0xe3, 0x7d, 0xb5, 0x8a, 0x97, 0x19, 0x3e, 0x0a, 0xb9, 0x30, 0xda, 0xa8, 0x65, 0xd2, 0xee, 0x9f, + 0x6d, 0xd4, 0x3c, 0x2c, 0x26, 0x87, 0xdf, 0xa1, 0x8d, 0xa2, 0x85, 0x21, 0x33, 0x8c, 0x78, 0x7d, + 0x6f, 0xaf, 0xf3, 0xe2, 0xcb, 0xa0, 0x1a, 0xef, 0x5c, 0x31, 0x48, 0xcf, 0x93, 0x22, 0xa0, 0x83, + 0x02, 0x1d, 0x4c, 0xf6, 0x83, 0xb7, 0x83, 0x5f, 0x20, 0x36, 0xc7, 0x60, 0x58, 0x84, 0x2f, 0xa7, + 0xbd, 0x46, 0x3e, 0xed, 0xa1, 0x2a, 0x46, 0xe7, 0xaa, 0xf8, 0x1d, 0x6a, 0xdb, 0x8f, 0x74, 0xca, + 0xc7, 0x40, 0xee, 0x59, 0x8b, 0xf0, 0x6e, 0x16, 0xc7, 0x3c, 0x56, 0xb2, 0xa0, 0x45, 0x5b, 0xce, + 0xa1, 0x7d, 0x38, 0x53, 0xa2, 0x95, 0x28, 0x7e, 0x83, 0x5a, 0x1a, 0x14, 0x07, 0x4d, 0xee, 0x5b, + 0xf9, 0x67, 0xc1, 0xca, 0x05, 0x09, 0xac, 0xc0, 0x89, 0x45, 0x47, 0x28, 0x9f, 0xf6, 0x5a, 0xe5, + 0x33, 0x75, 0x0a, 0xf8, 0x18, 0x3d, 0x56, 0x90, 0x4a, 0x65, 0xb8, 0x48, 0x5e, 0x49, 0x61, 0x94, + 0x1c, 0x8d, 0x40, 0x91, 0xb5, 0xbe, 0xb7, 0xd7, 0x8e, 0x3e, 0x73, 0x65, 0x3c, 0xa6, 0x75, 0x08, + 0xbd, 0x89, 0x87, 0x7f, 0x40, 0x5b, 0xf3, 0xf0, 0x6b, 0xa1, 0x0d, 0x13, 0x31, 0x90, 0xa6, 0x15, + 0x7b, 0xe2, 0xc4, 0xb6, 0xe8, 0x32, 0x80, 0xd6, 0x39, 0xf8, 0x19, 0x6a, 0xb1, 0xd8, 0x70, 0x29, + 0x48, 0xcb, 0xb2, 0x1f, 0x39, 0x76, 0xeb, 0xa5, 0x8d, 0x52, 0x97, 0x2d, 0x70, 0x0a, 0x98, 0x96, + 0x82, 0xac, 0x5f, 0xc7, 0x51, 0x1b, 0xa5, 0x2e, 0x8b, 0x4f, 0x51, 0x5b, 0x41, 0xc2, 0xd4, 0x90, + 0x8b, 0x84, 0x6c, 0xd8, 0xb1, 0x3d, 0x5d, 0x1c, 0x5b, 0xf1, 0x37, 0x54, 0x9f, 0x99, 0xc2, 0x7b, + 0x50, 0x20, 0xe2, 0x85, 0x2f, 0x41, 0x67, 0x6c, 0x5a, 0x09, 0xe1, 0x37, 0x68, 0x5d, 0xc1, 0xa8, + 0x58, 0x34, 0xd2, 0xbe, 0xbb, 0x66, 0x27, 0x9f, 0xf6, 0xd6, 0x69, 0xc9, 0xa3, 0x33, 0x01, 0xdc, + 0x47, 0x6b, 0x42, 0x1a, 0x20, 0xc8, 0xf6, 0xf1, 0xc0, 0xf9, 0xae, 0xfd, 0x28, 0x0d, 0x50, 0x9b, + 0x29, 0x10, 0xe6, 0x22, 0x05, 0xd2, 0xb9, 0x8e, 0x38, 0xbd, 0x48, 0x81, 0xda, 0x0c, 0x06, 0xd4, + 0x1d, 0x42, 0xaa, 0x20, 0x2e, 0x14, 0x4f, 0x64, 0xa6, 0x62, 0x20, 0x0f, 0x6c, 0x61, 0xbd, 0x9b, + 0x0a, 0x2b, 0x97, 0xc3, 0xc2, 0x22, 0xe2, 0xe4, 0xba, 0x07, 0x4b, 0x02, 0xb4, 0x26, 0x89, 0x7f, + 0xf7, 0x10, 0xa9, 0x82, 0xdf, 0x73, 0xa5, 0xed, 0x62, 0x6a, 0xc3, 0xc6, 0x29, 0x79, 0x68, 0xfd, + 0xbe, 0xb8, 0xdb, 0xca, 0xdb, 0x6d, 0xef, 0x3b, 0x6b, 0x72, 0xb0, 0x42, 0x93, 0xae, 0x74, 0xc3, + 0xbf, 0x79, 0x68, 0xbb, 0x4a, 0x1e, 0xb1, 0xc5, 0x4a, 0x1e, 0xfd, 0xef, 0x4a, 0x7a, 0xae, 0x92, + 0xed, 0x83, 0x9b, 0x25, 0xe9, 0x2a, 0x2f, 0xfc, 0x12, 0x6d, 0x56, 0xa9, 0x57, 0x32, 0x13, 0x86, + 0x6c, 0xf6, 0xbd, 0xbd, 0x66, 0xb4, 0xed, 0x24, 0x37, 0x0f, 0xae, 0xa7, 0xe9, 0x32, 0x7e, 0xf7, + 0x2f, 0x0f, 0x95, 0xff, 0xfb, 0x11, 0xd7, 0x06, 0xff, 0x5c, 0x3b, 0x54, 0xc1, 0xdd, 0x1a, 0x29, + 0xd8, 0xf6, 0x4c, 0x75, 0x9d, 0xf3, 0xc6, 0x2c, 0xb2, 0x70, 0xa4, 0x0e, 0x51, 0x93, 0x1b, 0x18, + 0x6b, 0x72, 0xaf, 0x7f, 0x7f, 0xaf, 0xf3, 0xa2, 0x7f, 0xdb, 0x05, 0x89, 0x1e, 0x3a, 0xb1, 0xe6, + 0xeb, 0x82, 0x46, 0x4b, 0xf6, 0x6e, 0xee, 0xa1, 0xce, 0xc2, 0x85, 0xc1, 0x4f, 0x51, 0x33, 0xb6, + 0xbd, 0x7b, 0xb6, 0xf7, 0x39, 0xa9, 0xec, 0xb8, 0xcc, 0xe1, 0x0c, 0x75, 0x47, 0x4c, 0x9b, 0xb7, + 0x03, 0x0d, 0x6a, 0x02, 0xc3, 0x4f, 0xb9, 0x93, 0xf3, 0xa5, 0x3d, 0x5a, 0x12, 0xa4, 0x35, 0x0b, + 0xfc, 0x2d, 0x6a, 0x6a, 0xc3, 0x0c, 0xd8, 0xa3, 0xd9, 0x8e, 0x3e, 0x9f, 0xd5, 0x76, 0x52, 0x04, + 0xff, 0x9d, 0xf6, 0xba, 0x0b, 0x8d, 0xd8, 0x18, 0x2d, 0xf1, 0xd1, 0xf3, 0xcb, 0x2b, 0xbf, 0xf1, + 0xe1, 0xca, 0x6f, 0x7c, 0xbc, 0xf2, 0x1b, 0xbf, 0xe6, 0xbe, 0x77, 0x99, 0xfb, 0xde, 0x87, 0xdc, + 0xf7, 0x3e, 0xe6, 0xbe, 0xf7, 0x77, 0xee, 0x7b, 0x7f, 0xfc, 0xe3, 0x37, 0x7e, 0x5a, 0x77, 0xf3, + 0xfa, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x69, 0xa9, 0x7b, 0x6e, 0xf2, 0x07, 0x00, 0x00, +} diff --git a/staging/src/k8s.io/api/events/v1beta1/generated.proto b/staging/src/k8s.io/api/events/v1beta1/generated.proto new file mode 100644 index 00000000000..81be470f053 --- /dev/null +++ b/staging/src/k8s.io/api/events/v1beta1/generated.proto @@ -0,0 +1,122 @@ +/* +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. +*/ + + +// This file was autogenerated by go-to-protobuf. Do not edit it manually! + +syntax = 'proto2'; + +package k8s.io.api.events.v1beta1; + +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"; +import "k8s.io/apimachinery/pkg/util/intstr/generated.proto"; + +// Package-wide variables from generator "generated". +option go_package = "v1beta1"; + +// Event is a report of an event somewhere in the cluster. It generally denotes some state change in the system. +message Event { + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; + + // Required. Time when this Event was first observed. + optional k8s.io.apimachinery.pkg.apis.meta.v1.MicroTime eventTime = 2; + + // Data about the Event series this event represents or nil if it's a singleton Event. + // +optional + optional EventSeries series = 3; + + // Name of the controller that emitted this Event, e.g. `kubernetes.io/kubelet`. + // +optional + optional string reportingController = 4; + + // ID of the controller instance, e.g. `kubelet-xyzf`. + // +optional + optional string reportingInstance = 5; + + // What action was taken/failed regarding to the regarding object. + // +optional + optional string action = 6; + + // Why the action was taken. + optional string reason = 7; + + // The object this Event is about. In most cases it's an Object reporting controller implements. + // E.g. ReplicaSetController implements ReplicaSets and this event is emitted because + // it acts on some changes in a ReplicaSet object. + // +optional + optional k8s.io.api.core.v1.ObjectReference regarding = 8; + + // Optional secondary object for more complex actions. E.g. when regarding object triggers + // a creation or deletion of related object. + // +optional + optional k8s.io.api.core.v1.ObjectReference related = 9; + + // Optional. A human-readable description of the status of this operation. + // Maximal length of the note is 1kB, but libraries should be prepared to + // handle values up to 64kB. + // +optional + optional string note = 10; + + // Type of this event (Normal, Warning), new types could be added in the + // future. + // +optional + optional string type = 11; + + // Deprecated field assuring backward compatibility with core.v1 Event type + // +optional + optional k8s.io.api.core.v1.EventSource deprecatedSource = 12; + + // Deprecated field assuring backward compatibility with core.v1 Event type + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.Time deprecatedFirstTimestamp = 13; + + // Deprecated field assuring backward compatibility with core.v1 Event type + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.Time deprecatedLastTimestamp = 14; + + // Deprecated field assuring backward compatibility with core.v1 Event type + // +optional + optional int32 deprecatedCount = 15; +} + +// EventList is a list of Event objects. +message EventList { + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; + + // Items is a list of schema objects. + repeated Event items = 2; +} + +// EventSeries contain information on series of events, i.e. thing that was/is happening +// continously for some time. +message EventSeries { + // Number of occurrences in this series up to the last heartbeat time + optional int32 count = 1; + + // Time when last Event from the series was seen before last heartbeat. + optional k8s.io.apimachinery.pkg.apis.meta.v1.MicroTime lastObservedTime = 2; + + // Information whether this series is ongoing or finished. + optional string state = 3; +} + diff --git a/staging/src/k8s.io/api/events/v1beta1/register.go b/staging/src/k8s.io/api/events/v1beta1/register.go new file mode 100644 index 00000000000..4506782914c --- /dev/null +++ b/staging/src/k8s.io/api/events/v1beta1/register.go @@ -0,0 +1,53 @@ +/* +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 v1beta1 + +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 = "events.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"} + +// 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 api.Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &Event{}, + &EventList{}, + ) + + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/staging/src/k8s.io/api/events/v1beta1/types.go b/staging/src/k8s.io/api/events/v1beta1/types.go new file mode 100644 index 00000000000..1b68bd743e9 --- /dev/null +++ b/staging/src/k8s.io/api/events/v1beta1/types.go @@ -0,0 +1,122 @@ +/* +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 v1beta1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Event is a report of an event somewhere in the cluster. It generally denotes some state change in the system. +type Event struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Required. Time when this Event was first observed. + EventTime metav1.MicroTime `json:"eventTime" protobuf:"bytes,2,opt,name=eventTime"` + + // Data about the Event series this event represents or nil if it's a singleton Event. + // +optional + Series *EventSeries `json:"series,omitempty" protobuf:"bytes,3,opt,name=series"` + + // Name of the controller that emitted this Event, e.g. `kubernetes.io/kubelet`. + // +optional + ReportingController string `json:"reportingController,omitempty" protobuf:"bytes,4,opt,name=reportingController"` + + // ID of the controller instance, e.g. `kubelet-xyzf`. + // +optional + ReportingInstance string `json:"reportingInstance,omitemtpy" protobuf:"bytes,5,opt,name=reportingInstance"` + + // What action was taken/failed regarding to the regarding object. + // +optional + Action string `json:"action,omitemtpy" protobuf:"bytes,6,name=action"` + + // Why the action was taken. + Reason string `json:"reason,omitempty" protobuf:"bytes,7,name=reason"` + + // The object this Event is about. In most cases it's an Object reporting controller implements. + // E.g. ReplicaSetController implements ReplicaSets and this event is emitted because + // it acts on some changes in a ReplicaSet object. + // +optional + Regarding corev1.ObjectReference `json:"regarding,omitempty" protobuf:"bytes,8,opt,name=regarding"` + + // Optional secondary object for more complex actions. E.g. when regarding object triggers + // a creation or deletion of related object. + // +optional + Related *corev1.ObjectReference `json:"related,omitempty" protobuf:"bytes,9,opt,name=related"` + + // Optional. A human-readable description of the status of this operation. + // Maximal length of the note is 1kB, but libraries should be prepared to + // handle values up to 64kB. + // +optional + Note string `json:"note,omitempty" protobuf:"bytes,10,opt,name=note"` + + // Type of this event (Normal, Warning), new types could be added in the + // future. + // +optional + Type string `json:"type,omitempty" protobuf:"bytes,11,opt,name=type"` + + // Deprecated field assuring backward compatibility with core.v1 Event type + // +optional + DeprecatedSource corev1.EventSource `json:"deprecatedSource,omitempty" protobuf:"bytes,12,opt,name=deprecatedSource"` + // Deprecated field assuring backward compatibility with core.v1 Event type + // +optional + DeprecatedFirstTimestamp metav1.Time `json:"deprecatedFirstTimestamp,omitempty" protobuf:"bytes,13,opt,name=deprecatedFirstTimestamp"` + // Deprecated field assuring backward compatibility with core.v1 Event type + // +optional + DeprecatedLastTimestamp metav1.Time `json:"deprecatedLastTimestamp,omitempty" protobuf:"bytes,14,opt,name=deprecatedLastTimestamp"` + // Deprecated field assuring backward compatibility with core.v1 Event type + // +optional + DeprecatedCount int32 `json:"deprecatedCount,omitempty" protobuf:"varint,15,opt,name=deprecatedCount"` +} + +// EventSeries contain information on series of events, i.e. thing that was/is happening +// continously for some time. +type EventSeries struct { + // Number of occurrences in this series up to the last heartbeat time + Count int32 `json:"count" protobuf:"varint,1,opt,name=count"` + // Time when last Event from the series was seen before last heartbeat. + LastObservedTime metav1.MicroTime `json:"lastObservedTime" protobuf:"bytes,2,opt,name=lastObservedTime"` + // Information whether this series is ongoing or finished. + State EventSeriesState `json:"state" protobuf:"bytes,3,opt,name=state"` +} + +type EventSeriesState string + +const ( + EventSeriesStateOngoing EventSeriesState = "Ongoing" + EventSeriesStateFinished EventSeriesState = "Finished" + EventSeriesStateUnknown EventSeriesState = "Unknown" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// EventList is a list of Event objects. +type EventList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Items is a list of schema objects. + Items []Event `json:"items" protobuf:"bytes,2,rep,name=items"` +} diff --git a/staging/src/k8s.io/api/events/v1beta1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/events/v1beta1/types_swagger_doc_generated.go new file mode 100644 index 00000000000..04a4a91227f --- /dev/null +++ b/staging/src/k8s.io/api/events/v1beta1/types_swagger_doc_generated.go @@ -0,0 +1,73 @@ +/* +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 v1beta1 + +// 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-generated-swagger-docs.sh + +// AUTO-GENERATED FUNCTIONS START HERE +var map_Event = map[string]string{ + "": "Event is a report of an event somewhere in the cluster. It generally denotes some state change in the system.", + "eventTime": "Required. Time when this Event was first observed.", + "series": "Data about the Event series this event represents or nil if it's a singleton Event.", + "reportingController": "Name of the controller that emitted this Event, e.g. `kubernetes.io/kubelet`.", + "reportingInstance": "ID of the controller instance, e.g. `kubelet-xyzf`.", + "action": "What action was taken/failed regarding to the regarding object.", + "reason": "Why the action was taken.", + "regarding": "The object this Event is about. In most cases it's an Object reporting controller implements. E.g. ReplicaSetController implements ReplicaSets and this event is emitted because it acts on some changes in a ReplicaSet object.", + "related": "Optional secondary object for more complex actions. E.g. when regarding object triggers a creation or deletion of related object.", + "note": "Optional. A human-readable description of the status of this operation. Maximal length of the note is 1kB, but libraries should be prepared to handle values up to 64kB.", + "type": "Type of this event (Normal, Warning), new types could be added in the future.", + "deprecatedSource": "Deprecated field assuring backward compatibility with core.v1 Event type", + "deprecatedFirstTimestamp": "Deprecated field assuring backward compatibility with core.v1 Event type", + "deprecatedLastTimestamp": "Deprecated field assuring backward compatibility with core.v1 Event type", + "deprecatedCount": "Deprecated field assuring backward compatibility with core.v1 Event type", +} + +func (Event) SwaggerDoc() map[string]string { + return map_Event +} + +var map_EventList = map[string]string{ + "": "EventList is a list of Event objects.", + "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", + "items": "Items is a list of schema objects.", +} + +func (EventList) SwaggerDoc() map[string]string { + return map_EventList +} + +var map_EventSeries = map[string]string{ + "": "EventSeries contain information on series of events, i.e. thing that was/is happening continously for some time.", + "count": "Number of occurrences in this series up to the last heartbeat time", + "lastObservedTime": "Time when last Event from the series was seen before last heartbeat.", + "state": "Information whether this series is ongoing or finished.", +} + +func (EventSeries) SwaggerDoc() map[string]string { + return map_EventSeries +} + +// AUTO-GENERATED FUNCTIONS END HERE diff --git a/staging/src/k8s.io/api/events/v1beta1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/events/v1beta1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..626feacf081 --- /dev/null +++ b/staging/src/k8s.io/api/events/v1beta1/zz_generated.deepcopy.go @@ -0,0 +1,127 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +package v1beta1 + +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 *Event) DeepCopyInto(out *Event) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.EventTime.DeepCopyInto(&out.EventTime) + if in.Series != nil { + in, out := &in.Series, &out.Series + if *in == nil { + *out = nil + } else { + *out = new(EventSeries) + (*in).DeepCopyInto(*out) + } + } + out.Regarding = in.Regarding + if in.Related != nil { + in, out := &in.Related, &out.Related + if *in == nil { + *out = nil + } else { + *out = new(v1.ObjectReference) + **out = **in + } + } + out.DeprecatedSource = in.DeprecatedSource + in.DeprecatedFirstTimestamp.DeepCopyInto(&out.DeprecatedFirstTimestamp) + in.DeprecatedLastTimestamp.DeepCopyInto(&out.DeprecatedLastTimestamp) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Event. +func (in *Event) DeepCopy() *Event { + if in == nil { + return nil + } + out := new(Event) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Event) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EventList) DeepCopyInto(out *EventList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Event, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventList. +func (in *EventList) DeepCopy() *EventList { + if in == nil { + return nil + } + out := new(EventList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *EventList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EventSeries) DeepCopyInto(out *EventSeries) { + *out = *in + in.LastObservedTime.DeepCopyInto(&out.LastObservedTime) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventSeries. +func (in *EventSeries) DeepCopy() *EventSeries { + if in == nil { + return nil + } + out := new(EventSeries) + in.DeepCopyInto(out) + return out +} diff --git a/staging/src/k8s.io/api/extensions/v1beta1/BUILD b/staging/src/k8s.io/api/extensions/v1beta1/BUILD index 4018732695e..360ad999c94 100644 --- a/staging/src/k8s.io/api/extensions/v1beta1/BUILD +++ b/staging/src/k8s.io/api/extensions/v1beta1/BUILD @@ -23,7 +23,6 @@ go_library( "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", diff --git a/staging/src/k8s.io/api/extensions/v1beta1/doc.go b/staging/src/k8s.io/api/extensions/v1beta1/doc.go index ac174dd2780..8ce18304be6 100644 --- a/staging/src/k8s.io/api/extensions/v1beta1/doc.go +++ b/staging/src/k8s.io/api/extensions/v1beta1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:openapi-gen=true package v1beta1 // import "k8s.io/api/extensions/v1beta1" diff --git a/staging/src/k8s.io/api/extensions/v1beta1/generated.pb.go b/staging/src/k8s.io/api/extensions/v1beta1/generated.pb.go index d508216ae08..fcb80615f05 100644 --- a/staging/src/k8s.io/api/extensions/v1beta1/generated.pb.go +++ b/staging/src/k8s.io/api/extensions/v1beta1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -25,13 +25,14 @@ limitations under the License. k8s.io/kubernetes/vendor/k8s.io/api/extensions/v1beta1/generated.proto It has these top-level messages: - APIVersion + AllowedFlexVolume AllowedHostPath CustomMetricCurrentStatus CustomMetricCurrentStatusList CustomMetricTarget CustomMetricTargetList DaemonSet + DaemonSetCondition DaemonSetList DaemonSetSpec DaemonSetStatus @@ -82,10 +83,6 @@ limitations under the License. ScaleSpec ScaleStatus SupplementalGroupsStrategyOptions - ThirdPartyResource - ThirdPartyResourceData - ThirdPartyResourceDataList - ThirdPartyResourceList */ package v1beta1 @@ -117,9 +114,9 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package -func (m *APIVersion) Reset() { *m = APIVersion{} } -func (*APIVersion) ProtoMessage() {} -func (*APIVersion) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{0} } +func (m *AllowedFlexVolume) Reset() { *m = AllowedFlexVolume{} } +func (*AllowedFlexVolume) ProtoMessage() {} +func (*AllowedFlexVolume) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{0} } func (m *AllowedHostPath) Reset() { *m = AllowedHostPath{} } func (*AllowedHostPath) ProtoMessage() {} @@ -149,246 +146,233 @@ func (m *DaemonSet) Reset() { *m = DaemonSet{} } func (*DaemonSet) ProtoMessage() {} func (*DaemonSet) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{6} } +func (m *DaemonSetCondition) Reset() { *m = DaemonSetCondition{} } +func (*DaemonSetCondition) ProtoMessage() {} +func (*DaemonSetCondition) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{7} } + func (m *DaemonSetList) Reset() { *m = DaemonSetList{} } func (*DaemonSetList) ProtoMessage() {} -func (*DaemonSetList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{7} } +func (*DaemonSetList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{8} } func (m *DaemonSetSpec) Reset() { *m = DaemonSetSpec{} } func (*DaemonSetSpec) ProtoMessage() {} -func (*DaemonSetSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{8} } +func (*DaemonSetSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{9} } func (m *DaemonSetStatus) Reset() { *m = DaemonSetStatus{} } func (*DaemonSetStatus) ProtoMessage() {} -func (*DaemonSetStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{9} } +func (*DaemonSetStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{10} } func (m *DaemonSetUpdateStrategy) Reset() { *m = DaemonSetUpdateStrategy{} } func (*DaemonSetUpdateStrategy) ProtoMessage() {} func (*DaemonSetUpdateStrategy) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{10} + return fileDescriptorGenerated, []int{11} } func (m *Deployment) Reset() { *m = Deployment{} } func (*Deployment) ProtoMessage() {} -func (*Deployment) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{11} } +func (*Deployment) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{12} } func (m *DeploymentCondition) Reset() { *m = DeploymentCondition{} } func (*DeploymentCondition) ProtoMessage() {} -func (*DeploymentCondition) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{12} } +func (*DeploymentCondition) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{13} } func (m *DeploymentList) Reset() { *m = DeploymentList{} } func (*DeploymentList) ProtoMessage() {} -func (*DeploymentList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{13} } +func (*DeploymentList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{14} } func (m *DeploymentRollback) Reset() { *m = DeploymentRollback{} } func (*DeploymentRollback) ProtoMessage() {} -func (*DeploymentRollback) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{14} } +func (*DeploymentRollback) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{15} } func (m *DeploymentSpec) Reset() { *m = DeploymentSpec{} } func (*DeploymentSpec) ProtoMessage() {} -func (*DeploymentSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{15} } +func (*DeploymentSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{16} } func (m *DeploymentStatus) Reset() { *m = DeploymentStatus{} } func (*DeploymentStatus) ProtoMessage() {} -func (*DeploymentStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{16} } +func (*DeploymentStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{17} } func (m *DeploymentStrategy) Reset() { *m = DeploymentStrategy{} } func (*DeploymentStrategy) ProtoMessage() {} -func (*DeploymentStrategy) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{17} } +func (*DeploymentStrategy) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{18} } func (m *FSGroupStrategyOptions) Reset() { *m = FSGroupStrategyOptions{} } func (*FSGroupStrategyOptions) ProtoMessage() {} -func (*FSGroupStrategyOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{18} } +func (*FSGroupStrategyOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{19} } func (m *HTTPIngressPath) Reset() { *m = HTTPIngressPath{} } func (*HTTPIngressPath) ProtoMessage() {} -func (*HTTPIngressPath) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{19} } +func (*HTTPIngressPath) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{20} } func (m *HTTPIngressRuleValue) Reset() { *m = HTTPIngressRuleValue{} } func (*HTTPIngressRuleValue) ProtoMessage() {} -func (*HTTPIngressRuleValue) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{20} } +func (*HTTPIngressRuleValue) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{21} } func (m *HostPortRange) Reset() { *m = HostPortRange{} } func (*HostPortRange) ProtoMessage() {} -func (*HostPortRange) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{21} } +func (*HostPortRange) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{22} } func (m *IDRange) Reset() { *m = IDRange{} } func (*IDRange) ProtoMessage() {} -func (*IDRange) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{22} } +func (*IDRange) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{23} } func (m *IPBlock) Reset() { *m = IPBlock{} } func (*IPBlock) ProtoMessage() {} -func (*IPBlock) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{23} } +func (*IPBlock) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{24} } func (m *Ingress) Reset() { *m = Ingress{} } func (*Ingress) ProtoMessage() {} -func (*Ingress) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{24} } +func (*Ingress) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{25} } func (m *IngressBackend) Reset() { *m = IngressBackend{} } func (*IngressBackend) ProtoMessage() {} -func (*IngressBackend) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{25} } +func (*IngressBackend) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{26} } func (m *IngressList) Reset() { *m = IngressList{} } func (*IngressList) ProtoMessage() {} -func (*IngressList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{26} } +func (*IngressList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{27} } func (m *IngressRule) Reset() { *m = IngressRule{} } func (*IngressRule) ProtoMessage() {} -func (*IngressRule) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{27} } +func (*IngressRule) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{28} } func (m *IngressRuleValue) Reset() { *m = IngressRuleValue{} } func (*IngressRuleValue) ProtoMessage() {} -func (*IngressRuleValue) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{28} } +func (*IngressRuleValue) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{29} } func (m *IngressSpec) Reset() { *m = IngressSpec{} } func (*IngressSpec) ProtoMessage() {} -func (*IngressSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{29} } +func (*IngressSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{30} } func (m *IngressStatus) Reset() { *m = IngressStatus{} } func (*IngressStatus) ProtoMessage() {} -func (*IngressStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{30} } +func (*IngressStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{31} } func (m *IngressTLS) Reset() { *m = IngressTLS{} } func (*IngressTLS) ProtoMessage() {} -func (*IngressTLS) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{31} } +func (*IngressTLS) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{32} } func (m *NetworkPolicy) Reset() { *m = NetworkPolicy{} } func (*NetworkPolicy) ProtoMessage() {} -func (*NetworkPolicy) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{32} } +func (*NetworkPolicy) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{33} } func (m *NetworkPolicyEgressRule) Reset() { *m = NetworkPolicyEgressRule{} } func (*NetworkPolicyEgressRule) ProtoMessage() {} func (*NetworkPolicyEgressRule) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{33} + return fileDescriptorGenerated, []int{34} } func (m *NetworkPolicyIngressRule) Reset() { *m = NetworkPolicyIngressRule{} } func (*NetworkPolicyIngressRule) ProtoMessage() {} func (*NetworkPolicyIngressRule) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{34} + return fileDescriptorGenerated, []int{35} } func (m *NetworkPolicyList) Reset() { *m = NetworkPolicyList{} } func (*NetworkPolicyList) ProtoMessage() {} -func (*NetworkPolicyList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{35} } +func (*NetworkPolicyList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{36} } func (m *NetworkPolicyPeer) Reset() { *m = NetworkPolicyPeer{} } func (*NetworkPolicyPeer) ProtoMessage() {} -func (*NetworkPolicyPeer) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{36} } +func (*NetworkPolicyPeer) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{37} } func (m *NetworkPolicyPort) Reset() { *m = NetworkPolicyPort{} } func (*NetworkPolicyPort) ProtoMessage() {} -func (*NetworkPolicyPort) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{37} } +func (*NetworkPolicyPort) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{38} } func (m *NetworkPolicySpec) Reset() { *m = NetworkPolicySpec{} } func (*NetworkPolicySpec) ProtoMessage() {} -func (*NetworkPolicySpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{38} } +func (*NetworkPolicySpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{39} } func (m *PodSecurityPolicy) Reset() { *m = PodSecurityPolicy{} } func (*PodSecurityPolicy) ProtoMessage() {} -func (*PodSecurityPolicy) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{39} } +func (*PodSecurityPolicy) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{40} } func (m *PodSecurityPolicyList) Reset() { *m = PodSecurityPolicyList{} } func (*PodSecurityPolicyList) ProtoMessage() {} -func (*PodSecurityPolicyList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{40} } +func (*PodSecurityPolicyList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{41} } func (m *PodSecurityPolicySpec) Reset() { *m = PodSecurityPolicySpec{} } func (*PodSecurityPolicySpec) ProtoMessage() {} -func (*PodSecurityPolicySpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{41} } +func (*PodSecurityPolicySpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{42} } func (m *ReplicaSet) Reset() { *m = ReplicaSet{} } func (*ReplicaSet) ProtoMessage() {} -func (*ReplicaSet) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{42} } +func (*ReplicaSet) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{43} } func (m *ReplicaSetCondition) Reset() { *m = ReplicaSetCondition{} } func (*ReplicaSetCondition) ProtoMessage() {} -func (*ReplicaSetCondition) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{43} } +func (*ReplicaSetCondition) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{44} } func (m *ReplicaSetList) Reset() { *m = ReplicaSetList{} } func (*ReplicaSetList) ProtoMessage() {} -func (*ReplicaSetList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{44} } +func (*ReplicaSetList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{45} } func (m *ReplicaSetSpec) Reset() { *m = ReplicaSetSpec{} } func (*ReplicaSetSpec) ProtoMessage() {} -func (*ReplicaSetSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{45} } +func (*ReplicaSetSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{46} } func (m *ReplicaSetStatus) Reset() { *m = ReplicaSetStatus{} } func (*ReplicaSetStatus) ProtoMessage() {} -func (*ReplicaSetStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{46} } +func (*ReplicaSetStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{47} } func (m *ReplicationControllerDummy) Reset() { *m = ReplicationControllerDummy{} } func (*ReplicationControllerDummy) ProtoMessage() {} func (*ReplicationControllerDummy) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{47} + return fileDescriptorGenerated, []int{48} } func (m *RollbackConfig) Reset() { *m = RollbackConfig{} } func (*RollbackConfig) ProtoMessage() {} -func (*RollbackConfig) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{48} } +func (*RollbackConfig) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{49} } func (m *RollingUpdateDaemonSet) Reset() { *m = RollingUpdateDaemonSet{} } func (*RollingUpdateDaemonSet) ProtoMessage() {} -func (*RollingUpdateDaemonSet) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{49} } +func (*RollingUpdateDaemonSet) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{50} } func (m *RollingUpdateDeployment) Reset() { *m = RollingUpdateDeployment{} } func (*RollingUpdateDeployment) ProtoMessage() {} func (*RollingUpdateDeployment) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{50} + return fileDescriptorGenerated, []int{51} } func (m *RunAsUserStrategyOptions) Reset() { *m = RunAsUserStrategyOptions{} } func (*RunAsUserStrategyOptions) ProtoMessage() {} func (*RunAsUserStrategyOptions) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{51} + return fileDescriptorGenerated, []int{52} } func (m *SELinuxStrategyOptions) Reset() { *m = SELinuxStrategyOptions{} } func (*SELinuxStrategyOptions) ProtoMessage() {} -func (*SELinuxStrategyOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{52} } +func (*SELinuxStrategyOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{53} } func (m *Scale) Reset() { *m = Scale{} } func (*Scale) ProtoMessage() {} -func (*Scale) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{53} } +func (*Scale) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{54} } func (m *ScaleSpec) Reset() { *m = ScaleSpec{} } func (*ScaleSpec) ProtoMessage() {} -func (*ScaleSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{54} } +func (*ScaleSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{55} } func (m *ScaleStatus) Reset() { *m = ScaleStatus{} } func (*ScaleStatus) ProtoMessage() {} -func (*ScaleStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{55} } +func (*ScaleStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{56} } func (m *SupplementalGroupsStrategyOptions) Reset() { *m = SupplementalGroupsStrategyOptions{} } func (*SupplementalGroupsStrategyOptions) ProtoMessage() {} func (*SupplementalGroupsStrategyOptions) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{56} + return fileDescriptorGenerated, []int{57} } -func (m *ThirdPartyResource) Reset() { *m = ThirdPartyResource{} } -func (*ThirdPartyResource) ProtoMessage() {} -func (*ThirdPartyResource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{57} } - -func (m *ThirdPartyResourceData) Reset() { *m = ThirdPartyResourceData{} } -func (*ThirdPartyResourceData) ProtoMessage() {} -func (*ThirdPartyResourceData) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{58} } - -func (m *ThirdPartyResourceDataList) Reset() { *m = ThirdPartyResourceDataList{} } -func (*ThirdPartyResourceDataList) ProtoMessage() {} -func (*ThirdPartyResourceDataList) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{59} -} - -func (m *ThirdPartyResourceList) Reset() { *m = ThirdPartyResourceList{} } -func (*ThirdPartyResourceList) ProtoMessage() {} -func (*ThirdPartyResourceList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{60} } - func init() { - proto.RegisterType((*APIVersion)(nil), "k8s.io.api.extensions.v1beta1.APIVersion") + proto.RegisterType((*AllowedFlexVolume)(nil), "k8s.io.api.extensions.v1beta1.AllowedFlexVolume") proto.RegisterType((*AllowedHostPath)(nil), "k8s.io.api.extensions.v1beta1.AllowedHostPath") proto.RegisterType((*CustomMetricCurrentStatus)(nil), "k8s.io.api.extensions.v1beta1.CustomMetricCurrentStatus") proto.RegisterType((*CustomMetricCurrentStatusList)(nil), "k8s.io.api.extensions.v1beta1.CustomMetricCurrentStatusList") proto.RegisterType((*CustomMetricTarget)(nil), "k8s.io.api.extensions.v1beta1.CustomMetricTarget") proto.RegisterType((*CustomMetricTargetList)(nil), "k8s.io.api.extensions.v1beta1.CustomMetricTargetList") proto.RegisterType((*DaemonSet)(nil), "k8s.io.api.extensions.v1beta1.DaemonSet") + proto.RegisterType((*DaemonSetCondition)(nil), "k8s.io.api.extensions.v1beta1.DaemonSetCondition") proto.RegisterType((*DaemonSetList)(nil), "k8s.io.api.extensions.v1beta1.DaemonSetList") proto.RegisterType((*DaemonSetSpec)(nil), "k8s.io.api.extensions.v1beta1.DaemonSetSpec") proto.RegisterType((*DaemonSetStatus)(nil), "k8s.io.api.extensions.v1beta1.DaemonSetStatus") @@ -439,12 +423,8 @@ func init() { proto.RegisterType((*ScaleSpec)(nil), "k8s.io.api.extensions.v1beta1.ScaleSpec") proto.RegisterType((*ScaleStatus)(nil), "k8s.io.api.extensions.v1beta1.ScaleStatus") proto.RegisterType((*SupplementalGroupsStrategyOptions)(nil), "k8s.io.api.extensions.v1beta1.SupplementalGroupsStrategyOptions") - proto.RegisterType((*ThirdPartyResource)(nil), "k8s.io.api.extensions.v1beta1.ThirdPartyResource") - proto.RegisterType((*ThirdPartyResourceData)(nil), "k8s.io.api.extensions.v1beta1.ThirdPartyResourceData") - proto.RegisterType((*ThirdPartyResourceDataList)(nil), "k8s.io.api.extensions.v1beta1.ThirdPartyResourceDataList") - proto.RegisterType((*ThirdPartyResourceList)(nil), "k8s.io.api.extensions.v1beta1.ThirdPartyResourceList") } -func (m *APIVersion) Marshal() (dAtA []byte, err error) { +func (m *AllowedFlexVolume) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalTo(dAtA) @@ -454,15 +434,15 @@ func (m *APIVersion) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *APIVersion) MarshalTo(dAtA []byte) (int, error) { +func (m *AllowedFlexVolume) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l dAtA[i] = 0xa i++ - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) - i += copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Driver))) + i += copy(dAtA[i:], m.Driver) return i, nil } @@ -650,6 +630,48 @@ func (m *DaemonSet) MarshalTo(dAtA []byte) (int, error) { return i, nil } +func (m *DaemonSetCondition) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DaemonSetCondition) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) + i += copy(dAtA[i:], m.Type) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Status))) + i += copy(dAtA[i:], m.Status) + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.LastTransitionTime.Size())) + n6, err := m.LastTransitionTime.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n6 + dAtA[i] = 0x22 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) + i += copy(dAtA[i:], m.Reason) + dAtA[i] = 0x2a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) + i += copy(dAtA[i:], m.Message) + return i, nil +} + func (m *DaemonSetList) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -668,11 +690,11 @@ func (m *DaemonSetList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n6, err := m.ListMeta.MarshalTo(dAtA[i:]) + n7, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n6 + i += n7 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -707,28 +729,28 @@ func (m *DaemonSetSpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Selector.Size())) - n7, err := m.Selector.MarshalTo(dAtA[i:]) + n8, err := m.Selector.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n7 + i += n8 } dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Template.Size())) - n8, err := m.Template.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n8 - dAtA[i] = 0x1a - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.UpdateStrategy.Size())) - n9, err := m.UpdateStrategy.MarshalTo(dAtA[i:]) + n9, err := m.Template.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n9 + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.UpdateStrategy.Size())) + n10, err := m.UpdateStrategy.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n10 dAtA[i] = 0x20 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.MinReadySeconds)) @@ -787,6 +809,18 @@ func (m *DaemonSetStatus) MarshalTo(dAtA []byte) (int, error) { i++ i = encodeVarintGenerated(dAtA, i, uint64(*m.CollisionCount)) } + if len(m.Conditions) > 0 { + for _, msg := range m.Conditions { + dAtA[i] = 0x52 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } return i, nil } @@ -813,11 +847,11 @@ func (m *DaemonSetUpdateStrategy) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.RollingUpdate.Size())) - n10, err := m.RollingUpdate.MarshalTo(dAtA[i:]) + n11, err := m.RollingUpdate.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n10 + i += n11 } return i, nil } @@ -840,27 +874,27 @@ func (m *Deployment) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n11, err := m.ObjectMeta.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n11 - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) - n12, err := m.Spec.MarshalTo(dAtA[i:]) + n12, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n12 - dAtA[i] = 0x1a + dAtA[i] = 0x12 i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) - n13, err := m.Status.MarshalTo(dAtA[i:]) + i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) + n13, err := m.Spec.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n13 + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) + n14, err := m.Status.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n14 return i, nil } @@ -898,19 +932,19 @@ func (m *DeploymentCondition) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x32 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.LastUpdateTime.Size())) - n14, err := m.LastUpdateTime.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n14 - dAtA[i] = 0x3a - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.LastTransitionTime.Size())) - n15, err := m.LastTransitionTime.MarshalTo(dAtA[i:]) + n15, err := m.LastUpdateTime.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n15 + dAtA[i] = 0x3a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.LastTransitionTime.Size())) + n16, err := m.LastTransitionTime.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n16 return i, nil } @@ -932,11 +966,11 @@ func (m *DeploymentList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n16, err := m.ListMeta.MarshalTo(dAtA[i:]) + n17, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n16 + i += n17 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -996,11 +1030,11 @@ func (m *DeploymentRollback) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.RollbackTo.Size())) - n17, err := m.RollbackTo.MarshalTo(dAtA[i:]) + n18, err := m.RollbackTo.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n17 + i += n18 return i, nil } @@ -1028,28 +1062,28 @@ func (m *DeploymentSpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Selector.Size())) - n18, err := m.Selector.MarshalTo(dAtA[i:]) + n19, err := m.Selector.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n18 + i += n19 } dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Template.Size())) - n19, err := m.Template.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n19 - dAtA[i] = 0x22 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Strategy.Size())) - n20, err := m.Strategy.MarshalTo(dAtA[i:]) + n20, err := m.Template.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n20 + dAtA[i] = 0x22 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Strategy.Size())) + n21, err := m.Strategy.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n21 dAtA[i] = 0x28 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.MinReadySeconds)) @@ -1070,11 +1104,11 @@ func (m *DeploymentSpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x42 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.RollbackTo.Size())) - n21, err := m.RollbackTo.MarshalTo(dAtA[i:]) + n22, err := m.RollbackTo.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n21 + i += n22 } if m.ProgressDeadlineSeconds != nil { dAtA[i] = 0x48 @@ -1160,11 +1194,11 @@ func (m *DeploymentStrategy) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.RollingUpdate.Size())) - n22, err := m.RollingUpdate.MarshalTo(dAtA[i:]) + n23, err := m.RollingUpdate.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n22 + i += n23 } return i, nil } @@ -1225,11 +1259,11 @@ func (m *HTTPIngressPath) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Backend.Size())) - n23, err := m.Backend.MarshalTo(dAtA[i:]) + n24, err := m.Backend.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n23 + i += n24 return i, nil } @@ -1366,27 +1400,27 @@ func (m *Ingress) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n24, err := m.ObjectMeta.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n24 - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) - n25, err := m.Spec.MarshalTo(dAtA[i:]) + n25, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n25 - dAtA[i] = 0x1a + dAtA[i] = 0x12 i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) - n26, err := m.Status.MarshalTo(dAtA[i:]) + i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) + n26, err := m.Spec.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n26 + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) + n27, err := m.Status.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n27 return i, nil } @@ -1412,11 +1446,11 @@ func (m *IngressBackend) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ServicePort.Size())) - n27, err := m.ServicePort.MarshalTo(dAtA[i:]) + n28, err := m.ServicePort.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n27 + i += n28 return i, nil } @@ -1438,11 +1472,11 @@ func (m *IngressList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n28, err := m.ListMeta.MarshalTo(dAtA[i:]) + n29, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n28 + i += n29 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -1480,11 +1514,11 @@ func (m *IngressRule) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.IngressRuleValue.Size())) - n29, err := m.IngressRuleValue.MarshalTo(dAtA[i:]) + n30, err := m.IngressRuleValue.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n29 + i += n30 return i, nil } @@ -1507,11 +1541,11 @@ func (m *IngressRuleValue) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.HTTP.Size())) - n30, err := m.HTTP.MarshalTo(dAtA[i:]) + n31, err := m.HTTP.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n30 + i += n31 } return i, nil } @@ -1535,11 +1569,11 @@ func (m *IngressSpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Backend.Size())) - n31, err := m.Backend.MarshalTo(dAtA[i:]) + n32, err := m.Backend.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n31 + i += n32 } if len(m.TLS) > 0 { for _, msg := range m.TLS { @@ -1586,11 +1620,11 @@ func (m *IngressStatus) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.LoadBalancer.Size())) - n32, err := m.LoadBalancer.MarshalTo(dAtA[i:]) + n33, err := m.LoadBalancer.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n32 + i += n33 return i, nil } @@ -1649,19 +1683,19 @@ func (m *NetworkPolicy) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n33, err := m.ObjectMeta.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n33 - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) - n34, err := m.Spec.MarshalTo(dAtA[i:]) + n34, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n34 + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) + n35, err := m.Spec.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n35 return i, nil } @@ -1767,11 +1801,11 @@ func (m *NetworkPolicyList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n35, err := m.ListMeta.MarshalTo(dAtA[i:]) + n36, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n35 + i += n36 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -1806,32 +1840,32 @@ func (m *NetworkPolicyPeer) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.PodSelector.Size())) - n36, err := m.PodSelector.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n36 - } - if m.NamespaceSelector != nil { - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.NamespaceSelector.Size())) - n37, err := m.NamespaceSelector.MarshalTo(dAtA[i:]) + n37, err := m.PodSelector.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n37 } - if m.IPBlock != nil { - dAtA[i] = 0x1a + if m.NamespaceSelector != nil { + dAtA[i] = 0x12 i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.IPBlock.Size())) - n38, err := m.IPBlock.MarshalTo(dAtA[i:]) + i = encodeVarintGenerated(dAtA, i, uint64(m.NamespaceSelector.Size())) + n38, err := m.NamespaceSelector.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n38 } + if m.IPBlock != nil { + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.IPBlock.Size())) + n39, err := m.IPBlock.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n39 + } return i, nil } @@ -1860,11 +1894,11 @@ func (m *NetworkPolicyPort) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Port.Size())) - n39, err := m.Port.MarshalTo(dAtA[i:]) + n40, err := m.Port.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n39 + i += n40 } return i, nil } @@ -1887,11 +1921,11 @@ func (m *NetworkPolicySpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.PodSelector.Size())) - n40, err := m.PodSelector.MarshalTo(dAtA[i:]) + n41, err := m.PodSelector.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n40 + i += n41 if len(m.Ingress) > 0 { for _, msg := range m.Ingress { dAtA[i] = 0x12 @@ -1952,19 +1986,19 @@ func (m *PodSecurityPolicy) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n41, err := m.ObjectMeta.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n41 - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) - n42, err := m.Spec.MarshalTo(dAtA[i:]) + n42, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n42 + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) + n43, err := m.Spec.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n43 return i, nil } @@ -1986,11 +2020,11 @@ func (m *PodSecurityPolicyList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n43, err := m.ListMeta.MarshalTo(dAtA[i:]) + n44, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n43 + i += n44 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -2128,35 +2162,35 @@ func (m *PodSecurityPolicySpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x52 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.SELinux.Size())) - n44, err := m.SELinux.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n44 - dAtA[i] = 0x5a - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.RunAsUser.Size())) - n45, err := m.RunAsUser.MarshalTo(dAtA[i:]) + n45, err := m.SELinux.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n45 - dAtA[i] = 0x62 + dAtA[i] = 0x5a i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.SupplementalGroups.Size())) - n46, err := m.SupplementalGroups.MarshalTo(dAtA[i:]) + i = encodeVarintGenerated(dAtA, i, uint64(m.RunAsUser.Size())) + n46, err := m.RunAsUser.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n46 - dAtA[i] = 0x6a + dAtA[i] = 0x62 i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.FSGroup.Size())) - n47, err := m.FSGroup.MarshalTo(dAtA[i:]) + i = encodeVarintGenerated(dAtA, i, uint64(m.SupplementalGroups.Size())) + n47, err := m.SupplementalGroups.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n47 + dAtA[i] = 0x6a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.FSGroup.Size())) + n48, err := m.FSGroup.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n48 dAtA[i] = 0x70 i++ if m.ReadOnlyRootFilesystem { @@ -2201,6 +2235,20 @@ func (m *PodSecurityPolicySpec) MarshalTo(dAtA []byte) (int, error) { i += n } } + if len(m.AllowedFlexVolumes) > 0 { + for _, msg := range m.AllowedFlexVolumes { + dAtA[i] = 0x92 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } return i, nil } @@ -2222,27 +2270,27 @@ func (m *ReplicaSet) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n48, err := m.ObjectMeta.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n48 - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) - n49, err := m.Spec.MarshalTo(dAtA[i:]) + n49, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n49 - dAtA[i] = 0x1a + dAtA[i] = 0x12 i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) - n50, err := m.Status.MarshalTo(dAtA[i:]) + i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) + n50, err := m.Spec.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n50 + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) + n51, err := m.Status.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n51 return i, nil } @@ -2272,11 +2320,11 @@ func (m *ReplicaSetCondition) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.LastTransitionTime.Size())) - n51, err := m.LastTransitionTime.MarshalTo(dAtA[i:]) + n52, err := m.LastTransitionTime.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n51 + i += n52 dAtA[i] = 0x22 i++ i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) @@ -2306,11 +2354,11 @@ func (m *ReplicaSetList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n52, err := m.ListMeta.MarshalTo(dAtA[i:]) + n53, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n52 + i += n53 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -2350,20 +2398,20 @@ func (m *ReplicaSetSpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Selector.Size())) - n53, err := m.Selector.MarshalTo(dAtA[i:]) + n54, err := m.Selector.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n53 + i += n54 } dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Template.Size())) - n54, err := m.Template.MarshalTo(dAtA[i:]) + n55, err := m.Template.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n54 + i += n55 dAtA[i] = 0x20 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.MinReadySeconds)) @@ -2473,11 +2521,11 @@ func (m *RollingUpdateDaemonSet) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.MaxUnavailable.Size())) - n55, err := m.MaxUnavailable.MarshalTo(dAtA[i:]) + n56, err := m.MaxUnavailable.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n55 + i += n56 } return i, nil } @@ -2501,21 +2549,21 @@ func (m *RollingUpdateDeployment) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.MaxUnavailable.Size())) - n56, err := m.MaxUnavailable.MarshalTo(dAtA[i:]) + n57, err := m.MaxUnavailable.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n56 + i += n57 } if m.MaxSurge != nil { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.MaxSurge.Size())) - n57, err := m.MaxSurge.MarshalTo(dAtA[i:]) + n58, err := m.MaxSurge.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n57 + i += n58 } return i, nil } @@ -2577,11 +2625,11 @@ func (m *SELinuxStrategyOptions) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.SELinuxOptions.Size())) - n58, err := m.SELinuxOptions.MarshalTo(dAtA[i:]) + n59, err := m.SELinuxOptions.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n58 + i += n59 } return i, nil } @@ -2604,27 +2652,27 @@ func (m *Scale) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n59, err := m.ObjectMeta.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n59 - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) - n60, err := m.Spec.MarshalTo(dAtA[i:]) + n60, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n60 - dAtA[i] = 0x1a + dAtA[i] = 0x12 i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) - n61, err := m.Status.MarshalTo(dAtA[i:]) + i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) + n61, err := m.Spec.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n61 + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) + n62, err := m.Status.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n62 return i, nil } @@ -2730,156 +2778,6 @@ func (m *SupplementalGroupsStrategyOptions) MarshalTo(dAtA []byte) (int, error) return i, nil } -func (m *ThirdPartyResource) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ThirdPartyResource) MarshalTo(dAtA []byte) (int, error) { - var i int - _ = i - var l int - _ = l - dAtA[i] = 0xa - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n62, err := m.ObjectMeta.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n62 - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Description))) - i += copy(dAtA[i:], m.Description) - if len(m.Versions) > 0 { - for _, msg := range m.Versions { - dAtA[i] = 0x1a - i++ - i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) - n, err := msg.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n - } - } - return i, nil -} - -func (m *ThirdPartyResourceData) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ThirdPartyResourceData) MarshalTo(dAtA []byte) (int, error) { - var i int - _ = i - var l int - _ = l - dAtA[i] = 0xa - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n63, err := m.ObjectMeta.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n63 - if m.Data != nil { - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Data))) - i += copy(dAtA[i:], m.Data) - } - return i, nil -} - -func (m *ThirdPartyResourceDataList) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ThirdPartyResourceDataList) MarshalTo(dAtA []byte) (int, error) { - var i int - _ = i - var l int - _ = l - dAtA[i] = 0xa - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n64, err := m.ListMeta.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n64 - if len(m.Items) > 0 { - for _, msg := range m.Items { - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) - n, err := msg.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n - } - } - return i, nil -} - -func (m *ThirdPartyResourceList) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ThirdPartyResourceList) MarshalTo(dAtA []byte) (int, error) { - var i int - _ = i - var l int - _ = l - dAtA[i] = 0xa - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n65, err := m.ListMeta.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n65 - if len(m.Items) > 0 { - for _, msg := range m.Items { - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) - n, err := msg.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n - } - } - return i, nil -} - func encodeFixed64Generated(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) dAtA[offset+1] = uint8(v >> 8) @@ -2907,10 +2805,10 @@ func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return offset + 1 } -func (m *APIVersion) Size() (n int) { +func (m *AllowedFlexVolume) Size() (n int) { var l int _ = l - l = len(m.Name) + l = len(m.Driver) n += 1 + l + sovGenerated(uint64(l)) return n } @@ -2979,6 +2877,22 @@ func (m *DaemonSet) Size() (n int) { return n } +func (m *DaemonSetCondition) Size() (n int) { + 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 *DaemonSetList) Size() (n int) { var l int _ = l @@ -3026,6 +2940,12 @@ func (m *DaemonSetStatus) Size() (n int) { if m.CollisionCount != nil { n += 1 + sovGenerated(uint64(*m.CollisionCount)) } + if len(m.Conditions) > 0 { + for _, e := range m.Conditions { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } return n } @@ -3531,6 +3451,12 @@ func (m *PodSecurityPolicySpec) Size() (n int) { n += 2 + l + sovGenerated(uint64(l)) } } + if len(m.AllowedFlexVolumes) > 0 { + for _, e := range m.AllowedFlexVolumes { + l = e.Size() + n += 2 + l + sovGenerated(uint64(l)) + } + } return n } @@ -3722,62 +3648,6 @@ func (m *SupplementalGroupsStrategyOptions) Size() (n int) { return n } -func (m *ThirdPartyResource) Size() (n int) { - var l int - _ = l - l = m.ObjectMeta.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Description) - n += 1 + l + sovGenerated(uint64(l)) - if len(m.Versions) > 0 { - for _, e := range m.Versions { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - } - return n -} - -func (m *ThirdPartyResourceData) Size() (n int) { - var l int - _ = l - l = m.ObjectMeta.Size() - n += 1 + l + sovGenerated(uint64(l)) - if m.Data != nil { - l = len(m.Data) - n += 1 + l + sovGenerated(uint64(l)) - } - return n -} - -func (m *ThirdPartyResourceDataList) Size() (n int) { - 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 *ThirdPartyResourceList) Size() (n int) { - 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 sovGenerated(x uint64) (n int) { for { n++ @@ -3791,12 +3661,12 @@ func sovGenerated(x uint64) (n int) { func sozGenerated(x uint64) (n int) { return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (this *APIVersion) String() string { +func (this *AllowedFlexVolume) String() string { if this == nil { return "nil" } - s := strings.Join([]string{`&APIVersion{`, - `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + s := strings.Join([]string{`&AllowedFlexVolume{`, + `Driver:` + fmt.Sprintf("%v", this.Driver) + `,`, `}`, }, "") return s @@ -3865,6 +3735,20 @@ func (this *DaemonSet) String() string { }, "") return s } +func (this *DaemonSetCondition) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&DaemonSetCondition{`, + `Type:` + fmt.Sprintf("%v", this.Type) + `,`, + `Status:` + fmt.Sprintf("%v", this.Status) + `,`, + `LastTransitionTime:` + strings.Replace(strings.Replace(this.LastTransitionTime.String(), "Time", "k8s_io_apimachinery_pkg_apis_meta_v1.Time", 1), `&`, ``, 1) + `,`, + `Reason:` + fmt.Sprintf("%v", this.Reason) + `,`, + `Message:` + fmt.Sprintf("%v", this.Message) + `,`, + `}`, + }, "") + return s +} func (this *DaemonSetList) String() string { if this == nil { return "nil" @@ -3905,6 +3789,7 @@ func (this *DaemonSetStatus) String() string { `NumberAvailable:` + fmt.Sprintf("%v", this.NumberAvailable) + `,`, `NumberUnavailable:` + fmt.Sprintf("%v", this.NumberUnavailable) + `,`, `CollisionCount:` + valueToStringGenerated(this.CollisionCount) + `,`, + `Conditions:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Conditions), "DaemonSetCondition", "DaemonSetCondition", 1), `&`, ``, 1) + `,`, `}`, }, "") return s @@ -4303,6 +4188,7 @@ func (this *PodSecurityPolicySpec) String() string { `DefaultAllowPrivilegeEscalation:` + valueToStringGenerated(this.DefaultAllowPrivilegeEscalation) + `,`, `AllowPrivilegeEscalation:` + valueToStringGenerated(this.AllowPrivilegeEscalation) + `,`, `AllowedHostPaths:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.AllowedHostPaths), "AllowedHostPath", "AllowedHostPath", 1), `&`, ``, 1) + `,`, + `AllowedFlexVolumes:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.AllowedFlexVolumes), "AllowedFlexVolume", "AllowedFlexVolume", 1), `&`, ``, 1) + `,`, `}`, }, "") return s @@ -4489,51 +4375,6 @@ func (this *SupplementalGroupsStrategyOptions) String() string { }, "") return s } -func (this *ThirdPartyResource) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ThirdPartyResource{`, - `ObjectMeta:` + strings.Replace(strings.Replace(this.ObjectMeta.String(), "ObjectMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ObjectMeta", 1), `&`, ``, 1) + `,`, - `Description:` + fmt.Sprintf("%v", this.Description) + `,`, - `Versions:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Versions), "APIVersion", "APIVersion", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s -} -func (this *ThirdPartyResourceData) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ThirdPartyResourceData{`, - `ObjectMeta:` + strings.Replace(strings.Replace(this.ObjectMeta.String(), "ObjectMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ObjectMeta", 1), `&`, ``, 1) + `,`, - `Data:` + valueToStringGenerated(this.Data) + `,`, - `}`, - }, "") - return s -} -func (this *ThirdPartyResourceDataList) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ThirdPartyResourceDataList{`, - `ListMeta:` + strings.Replace(strings.Replace(this.ListMeta.String(), "ListMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ListMeta", 1), `&`, ``, 1) + `,`, - `Items:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Items), "ThirdPartyResourceData", "ThirdPartyResourceData", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s -} -func (this *ThirdPartyResourceList) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&ThirdPartyResourceList{`, - `ListMeta:` + strings.Replace(strings.Replace(this.ListMeta.String(), "ListMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ListMeta", 1), `&`, ``, 1) + `,`, - `Items:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Items), "ThirdPartyResource", "ThirdPartyResource", 1), `&`, ``, 1) + `,`, - `}`, - }, "") - return s -} func valueToStringGenerated(v interface{}) string { rv := reflect.ValueOf(v) if rv.IsNil() { @@ -4542,7 +4383,7 @@ func valueToStringGenerated(v interface{}) string { pv := reflect.Indirect(rv).Interface() return fmt.Sprintf("*%v", pv) } -func (m *APIVersion) Unmarshal(dAtA []byte) error { +func (m *AllowedFlexVolume) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4565,15 +4406,15 @@ func (m *APIVersion) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: APIVersion: wiretype end group for non-group") + return fmt.Errorf("proto: AllowedFlexVolume: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: APIVersion: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: AllowedFlexVolume: 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 Driver", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -4598,7 +4439,7 @@ func (m *APIVersion) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Name = string(dAtA[iNdEx:postIndex]) + m.Driver = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex @@ -5220,6 +5061,202 @@ func (m *DaemonSet) Unmarshal(dAtA []byte) error { } return nil } +func (m *DaemonSetCondition) 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: DaemonSetCondition: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DaemonSetCondition: 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 > l { + return io.ErrUnexpectedEOF + } + m.Type = DaemonSetConditionType(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 > 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 > 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 > 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 > 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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *DaemonSetList) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -5733,6 +5770,37 @@ func (m *DaemonSetStatus) Unmarshal(dAtA []byte) error { } } m.CollisionCount = &v + case 10: + 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 > l { + return io.ErrUnexpectedEOF + } + m.Conditions = append(m.Conditions, DaemonSetCondition{}) + 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:]) @@ -10207,6 +10275,37 @@ func (m *PodSecurityPolicySpec) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 18: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowedFlexVolumes", 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 > l { + return io.ErrUnexpectedEOF + } + m.AllowedFlexVolumes = append(m.AllowedFlexVolumes, AllowedFlexVolume{}) + if err := m.AllowedFlexVolumes[len(m.AllowedFlexVolumes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -12076,479 +12175,6 @@ func (m *SupplementalGroupsStrategyOptions) Unmarshal(dAtA []byte) error { } return nil } -func (m *ThirdPartyResource) 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: ThirdPartyResource: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ThirdPartyResource: 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 > 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 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 > l { - return io.ErrUnexpectedEOF - } - m.Description = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Versions", 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 > l { - return io.ErrUnexpectedEOF - } - m.Versions = append(m.Versions, APIVersion{}) - if err := m.Versions[len(m.Versions)-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 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ThirdPartyResourceData) 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: ThirdPartyResourceData: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ThirdPartyResourceData: 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 > 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 Data", 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 > l { - return io.ErrUnexpectedEOF - } - m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) - if m.Data == nil { - m.Data = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ThirdPartyResourceDataList) 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: ThirdPartyResourceDataList: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ThirdPartyResourceDataList: 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 > 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 > l { - return io.ErrUnexpectedEOF - } - m.Items = append(m.Items, ThirdPartyResourceData{}) - 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 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ThirdPartyResourceList) 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: ThirdPartyResourceList: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ThirdPartyResourceList: 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 > 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 > l { - return io.ErrUnexpectedEOF - } - m.Items = append(m.Items, ThirdPartyResource{}) - 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 { - 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 @@ -12659,232 +12285,229 @@ func init() { } var fileDescriptorGenerated = []byte{ - // 3632 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x5b, 0xcd, 0x6f, 0x24, 0xc7, - 0x75, 0xdf, 0x9e, 0x0f, 0xce, 0xf0, 0x71, 0xf9, 0x55, 0x5c, 0x91, 0x63, 0x4a, 0xcb, 0x59, 0xb7, - 0x80, 0xcd, 0x4a, 0x91, 0x66, 0xb4, 0x94, 0x56, 0x56, 0x24, 0xc4, 0x0e, 0x87, 0xdc, 0x0f, 0x3a, - 0xfc, 0x18, 0xd5, 0x0c, 0x69, 0x67, 0x91, 0x75, 0xd4, 0x9c, 0x29, 0x0e, 0x7b, 0xd9, 0xd3, 0xdd, - 0xee, 0xae, 0xa6, 0x39, 0x97, 0x20, 0x87, 0xc0, 0x40, 0x80, 0x04, 0x49, 0x0e, 0x0e, 0x94, 0x5b, - 0x7c, 0xc9, 0x29, 0x41, 0x7c, 0x4b, 0x0e, 0x86, 0x81, 0x00, 0x0e, 0xb0, 0x08, 0x9c, 0xc0, 0xa7, - 0xc4, 0x27, 0x22, 0xa2, 0x8e, 0xf9, 0x07, 0x82, 0x3d, 0x04, 0x41, 0x55, 0x57, 0x7f, 0x77, 0x73, - 0x66, 0xa8, 0x25, 0x11, 0xf8, 0x36, 0x5d, 0xef, 0xbd, 0xdf, 0x7b, 0xf5, 0xaa, 0xea, 0xbd, 0x57, - 0x1f, 0x03, 0x8f, 0x8e, 0x3f, 0xb2, 0x6b, 0xaa, 0x51, 0x3f, 0x76, 0x0e, 0x88, 0xa5, 0x13, 0x4a, - 0xec, 0xfa, 0x09, 0xd1, 0xbb, 0x86, 0x55, 0x17, 0x04, 0xc5, 0x54, 0xeb, 0xe4, 0x94, 0x12, 0xdd, - 0x56, 0x0d, 0xdd, 0xae, 0x9f, 0xdc, 0x3f, 0x20, 0x54, 0xb9, 0x5f, 0xef, 0x11, 0x9d, 0x58, 0x0a, - 0x25, 0xdd, 0x9a, 0x69, 0x19, 0xd4, 0x40, 0xb7, 0x5d, 0xf6, 0x9a, 0x62, 0xaa, 0xb5, 0x80, 0xbd, - 0x26, 0xd8, 0x97, 0xdf, 0xed, 0xa9, 0xf4, 0xc8, 0x39, 0xa8, 0x75, 0x8c, 0x7e, 0xbd, 0x67, 0xf4, - 0x8c, 0x3a, 0x97, 0x3a, 0x70, 0x0e, 0xf9, 0x17, 0xff, 0xe0, 0xbf, 0x5c, 0xb4, 0x65, 0x39, 0xa4, - 0xbc, 0x63, 0x58, 0xa4, 0x7e, 0x92, 0xd0, 0xb8, 0xfc, 0x56, 0x88, 0xc7, 0x34, 0x34, 0xb5, 0x33, - 0xc8, 0x32, 0x6e, 0xf9, 0x83, 0x80, 0xb5, 0xaf, 0x74, 0x8e, 0x54, 0x9d, 0x58, 0x83, 0xba, 0x79, - 0xdc, 0xe3, 0xb2, 0x16, 0xb1, 0x0d, 0xc7, 0xea, 0x90, 0xb1, 0xa4, 0xec, 0x7a, 0x9f, 0x50, 0x25, - 0xcd, 0xac, 0x7a, 0x96, 0x94, 0xe5, 0xe8, 0x54, 0xed, 0x27, 0xd5, 0x7c, 0x38, 0x4c, 0xc0, 0xee, - 0x1c, 0x91, 0xbe, 0x92, 0x90, 0x7b, 0x3f, 0x4b, 0xce, 0xa1, 0xaa, 0x56, 0x57, 0x75, 0x6a, 0x53, - 0x2b, 0x2e, 0x24, 0xd7, 0x00, 0xd6, 0x9a, 0x9b, 0xfb, 0xc4, 0x62, 0xc3, 0x83, 0xee, 0x40, 0x41, - 0x57, 0xfa, 0xa4, 0x22, 0xdd, 0x91, 0xee, 0x4d, 0x36, 0x6e, 0xbe, 0x38, 0xab, 0xde, 0x38, 0x3f, - 0xab, 0x16, 0x76, 0x94, 0x3e, 0xc1, 0x9c, 0x22, 0x3f, 0x84, 0xd9, 0x35, 0x4d, 0x33, 0x7e, 0x40, - 0xba, 0x4f, 0x0c, 0x9b, 0x36, 0x15, 0x7a, 0x84, 0x56, 0x01, 0x4c, 0x85, 0x1e, 0x35, 0x2d, 0x72, - 0xa8, 0x9e, 0x0a, 0x51, 0x24, 0x44, 0xa1, 0xe9, 0x53, 0x70, 0x88, 0x4b, 0xfe, 0x6b, 0x09, 0xbe, - 0xb6, 0xee, 0xd8, 0xd4, 0xe8, 0x6f, 0x13, 0x6a, 0xa9, 0x9d, 0x75, 0xc7, 0xb2, 0x88, 0x4e, 0x5b, - 0x54, 0xa1, 0x8e, 0x3d, 0xdc, 0x0c, 0xf4, 0x14, 0x8a, 0x27, 0x8a, 0xe6, 0x90, 0x4a, 0xee, 0x8e, - 0x74, 0x6f, 0x6a, 0xb5, 0x56, 0x0b, 0x66, 0x9b, 0xdf, 0xf7, 0x9a, 0x79, 0xdc, 0xe3, 0xd3, 0xcf, - 0x1b, 0xd0, 0xda, 0xa7, 0x8e, 0xa2, 0x53, 0x95, 0x0e, 0x1a, 0xb7, 0x04, 0xe4, 0x4d, 0xa1, 0x77, - 0x9f, 0x61, 0x61, 0x17, 0x52, 0xfe, 0x43, 0xb8, 0x9d, 0x69, 0xda, 0x96, 0x6a, 0x53, 0xf4, 0x0c, - 0x8a, 0x2a, 0x25, 0x7d, 0xbb, 0x22, 0xdd, 0xc9, 0xdf, 0x9b, 0x5a, 0xfd, 0xa8, 0x76, 0xe1, 0x54, - 0xaf, 0x65, 0x82, 0x35, 0xa6, 0x85, 0x19, 0xc5, 0x4d, 0x06, 0x87, 0x5d, 0x54, 0xf9, 0x2f, 0x25, - 0x40, 0x61, 0x99, 0xb6, 0x62, 0xf5, 0x08, 0x1d, 0xc1, 0x29, 0xbf, 0xf7, 0xd5, 0x9c, 0xb2, 0x20, - 0x20, 0xa7, 0x5c, 0x85, 0x11, 0x9f, 0x98, 0xb0, 0x98, 0x34, 0x89, 0x3b, 0x63, 0x3f, 0xea, 0x8c, - 0xfb, 0x63, 0x38, 0xc3, 0x45, 0xc9, 0xf0, 0xc2, 0x8f, 0x72, 0x30, 0xb9, 0xa1, 0x90, 0xbe, 0xa1, - 0xb7, 0x08, 0x45, 0x9f, 0x41, 0x99, 0xad, 0xaf, 0xae, 0x42, 0x15, 0xee, 0x80, 0xa9, 0xd5, 0xf7, - 0x2e, 0xea, 0x9d, 0x5d, 0x63, 0xdc, 0xb5, 0x93, 0xfb, 0xb5, 0xdd, 0x83, 0xe7, 0xa4, 0x43, 0xb7, - 0x09, 0x55, 0x82, 0x39, 0x19, 0xb4, 0x61, 0x1f, 0x15, 0xed, 0x40, 0xc1, 0x36, 0x49, 0x47, 0xf8, - 0xee, 0x9d, 0x21, 0xdd, 0xf0, 0x2d, 0x6b, 0x99, 0xa4, 0x13, 0x0c, 0x06, 0xfb, 0xc2, 0x1c, 0x07, - 0xed, 0xc3, 0x84, 0xcd, 0x47, 0xb9, 0x92, 0x4f, 0x8c, 0xc6, 0xc5, 0x88, 0xee, 0xdc, 0x98, 0x11, - 0x98, 0x13, 0xee, 0x37, 0x16, 0x68, 0xf2, 0x4f, 0x25, 0x98, 0xf6, 0x79, 0xf9, 0x08, 0xfc, 0x7e, - 0xc2, 0x37, 0xb5, 0xd1, 0x7c, 0xc3, 0xa4, 0xb9, 0x67, 0xe6, 0x84, 0xae, 0xb2, 0xd7, 0x12, 0xf2, - 0xcb, 0xb6, 0x37, 0xbe, 0x39, 0x3e, 0xbe, 0xf7, 0x46, 0xed, 0x46, 0xc6, 0xb0, 0xfe, 0x55, 0x21, - 0x64, 0x3e, 0x73, 0x17, 0x7a, 0x06, 0x65, 0x9b, 0x68, 0xa4, 0x43, 0x0d, 0x4b, 0x98, 0xff, 0xfe, - 0x88, 0xe6, 0x2b, 0x07, 0x44, 0x6b, 0x09, 0xd1, 0xc6, 0x4d, 0x66, 0xbf, 0xf7, 0x85, 0x7d, 0x48, - 0xf4, 0x29, 0x94, 0x29, 0xe9, 0x9b, 0x9a, 0x42, 0xbd, 0x75, 0xf1, 0x66, 0xb8, 0x0b, 0x2c, 0x99, - 0x30, 0xb0, 0xa6, 0xd1, 0x6d, 0x0b, 0x36, 0x3e, 0xa4, 0xbe, 0x4b, 0xbc, 0x56, 0xec, 0xc3, 0xa0, - 0x13, 0x98, 0x71, 0xcc, 0x2e, 0xe3, 0xa4, 0x2c, 0x94, 0xf6, 0x06, 0x62, 0x88, 0x3f, 0x1c, 0xd5, - 0x37, 0x7b, 0x11, 0xe9, 0xc6, 0xa2, 0xd0, 0x35, 0x13, 0x6d, 0xc7, 0x31, 0x2d, 0x68, 0x0d, 0x66, - 0xfb, 0xaa, 0x8e, 0x89, 0xd2, 0x1d, 0xb4, 0x48, 0xc7, 0xd0, 0xbb, 0x76, 0xa5, 0x70, 0x47, 0xba, - 0x57, 0x6c, 0x2c, 0x09, 0x80, 0xd9, 0xed, 0x28, 0x19, 0xc7, 0xf9, 0xd1, 0xb7, 0x01, 0x79, 0xdd, - 0x78, 0xec, 0x66, 0x02, 0xd5, 0xd0, 0x2b, 0xc5, 0x3b, 0xd2, 0xbd, 0x7c, 0x63, 0x59, 0xa0, 0xa0, - 0x76, 0x82, 0x03, 0xa7, 0x48, 0xa1, 0x2d, 0xb8, 0x65, 0x91, 0x13, 0x95, 0xf5, 0xf1, 0x89, 0x6a, - 0x53, 0xc3, 0x1a, 0x6c, 0xa9, 0x7d, 0x95, 0x56, 0x26, 0xb8, 0x4d, 0x95, 0xf3, 0xb3, 0xea, 0x2d, - 0x9c, 0x42, 0xc7, 0xa9, 0x52, 0xf2, 0x4f, 0x8a, 0x30, 0x1b, 0x5b, 0x03, 0x68, 0x1f, 0x16, 0x3b, - 0x6e, 0xc0, 0xdc, 0x71, 0xfa, 0x07, 0xc4, 0x6a, 0x75, 0x8e, 0x48, 0xd7, 0xd1, 0x48, 0x97, 0x4f, - 0x94, 0x62, 0x63, 0x45, 0x58, 0xbc, 0xb8, 0x9e, 0xca, 0x85, 0x33, 0xa4, 0x99, 0x17, 0x74, 0xde, - 0xb4, 0xad, 0xda, 0xb6, 0x8f, 0x99, 0xe3, 0x98, 0xbe, 0x17, 0x76, 0x12, 0x1c, 0x38, 0x45, 0x8a, - 0xd9, 0xd8, 0x25, 0xb6, 0x6a, 0x91, 0x6e, 0xdc, 0xc6, 0x7c, 0xd4, 0xc6, 0x8d, 0x54, 0x2e, 0x9c, - 0x21, 0x8d, 0x1e, 0xc0, 0x94, 0xab, 0x8d, 0x8f, 0x9f, 0x18, 0x68, 0x3f, 0x44, 0xef, 0x04, 0x24, - 0x1c, 0xe6, 0x63, 0x5d, 0x33, 0x0e, 0x6c, 0x62, 0x9d, 0x90, 0x6e, 0xf6, 0x00, 0xef, 0x26, 0x38, - 0x70, 0x8a, 0x14, 0xeb, 0x9a, 0x3b, 0x03, 0x13, 0x5d, 0x9b, 0x88, 0x76, 0x6d, 0x2f, 0x95, 0x0b, - 0x67, 0x48, 0xb3, 0x79, 0xec, 0x9a, 0xbc, 0x76, 0xa2, 0xa8, 0x9a, 0x72, 0xa0, 0x91, 0x4a, 0x29, - 0x3a, 0x8f, 0x77, 0xa2, 0x64, 0x1c, 0xe7, 0x47, 0x8f, 0x61, 0xde, 0x6d, 0xda, 0xd3, 0x15, 0x1f, - 0xa4, 0xcc, 0x41, 0xbe, 0x26, 0x40, 0xe6, 0x77, 0xe2, 0x0c, 0x38, 0x29, 0x83, 0x3e, 0x86, 0x99, - 0x8e, 0xa1, 0x69, 0x7c, 0x3e, 0xae, 0x1b, 0x8e, 0x4e, 0x2b, 0x93, 0x1c, 0x05, 0xb1, 0xf5, 0xb8, - 0x1e, 0xa1, 0xe0, 0x18, 0xa7, 0xfc, 0xaf, 0x12, 0x2c, 0x65, 0xac, 0x69, 0xf4, 0x2d, 0x28, 0xd0, - 0x81, 0xe9, 0x65, 0xeb, 0xdf, 0xf4, 0x12, 0x44, 0x7b, 0x60, 0x92, 0x97, 0x67, 0xd5, 0xd7, 0x33, - 0xc4, 0x18, 0x19, 0x73, 0x41, 0xa4, 0xc3, 0xb4, 0xc5, 0xd4, 0xe9, 0x3d, 0x97, 0x45, 0x04, 0xaf, - 0x07, 0x43, 0x62, 0x0c, 0x0e, 0xcb, 0x04, 0xc1, 0x78, 0xfe, 0xfc, 0xac, 0x3a, 0x1d, 0xa1, 0xe1, - 0x28, 0xbc, 0xfc, 0x79, 0x0e, 0x60, 0x83, 0x98, 0x9a, 0x31, 0xe8, 0x13, 0xfd, 0x3a, 0x12, 0xee, - 0x6e, 0x24, 0xe1, 0xbe, 0x3b, 0x2c, 0x76, 0xfa, 0xa6, 0x65, 0x66, 0xdc, 0xef, 0xc4, 0x32, 0x6e, - 0x7d, 0x74, 0xc8, 0x8b, 0x53, 0xee, 0x7f, 0xe6, 0x61, 0x21, 0x60, 0x5e, 0x37, 0xf4, 0xae, 0xca, - 0xd7, 0xc7, 0x27, 0x91, 0x31, 0xfe, 0x8d, 0xd8, 0x18, 0x2f, 0xa5, 0x88, 0x84, 0xc6, 0x77, 0xcb, - 0xb7, 0x36, 0xc7, 0xc5, 0x3f, 0x88, 0x2a, 0x7f, 0x79, 0x56, 0x4d, 0xd9, 0xf3, 0xd4, 0x7c, 0xa4, - 0xa8, 0x89, 0xe8, 0x2e, 0x4c, 0x58, 0x44, 0xb1, 0x0d, 0x9d, 0x07, 0x8a, 0xc9, 0xa0, 0x2b, 0x98, - 0xb7, 0x62, 0x41, 0x45, 0x6f, 0x41, 0xa9, 0x4f, 0x6c, 0x5b, 0xe9, 0x11, 0x1e, 0x13, 0x26, 0x1b, - 0xb3, 0x82, 0xb1, 0xb4, 0xed, 0x36, 0x63, 0x8f, 0x8e, 0x9e, 0xc3, 0x8c, 0xa6, 0xd8, 0x62, 0x82, - 0xb6, 0xd5, 0x3e, 0xe1, 0xab, 0x7e, 0x6a, 0xf5, 0xed, 0xd1, 0xe6, 0x01, 0x93, 0x08, 0x32, 0xdb, - 0x56, 0x04, 0x09, 0xc7, 0x90, 0xd1, 0x09, 0x20, 0xd6, 0xd2, 0xb6, 0x14, 0xdd, 0x76, 0x1d, 0xc5, - 0xf4, 0x95, 0xc6, 0xd6, 0xe7, 0x47, 0xb8, 0xad, 0x04, 0x1a, 0x4e, 0xd1, 0x20, 0xff, 0x4c, 0x82, - 0x99, 0x60, 0x98, 0xae, 0xa1, 0x9a, 0xda, 0x89, 0x56, 0x53, 0x6f, 0x8d, 0x3c, 0x45, 0x33, 0xca, - 0xa9, 0xff, 0xc9, 0x01, 0x0a, 0x98, 0xd8, 0x02, 0x3f, 0x50, 0x3a, 0xc7, 0x23, 0xec, 0x15, 0x7e, - 0x24, 0x01, 0x12, 0xe1, 0x79, 0x4d, 0xd7, 0x0d, 0xca, 0x23, 0xbe, 0x67, 0xd6, 0xe6, 0xc8, 0x66, - 0x79, 0x1a, 0x6b, 0x7b, 0x09, 0xac, 0x87, 0x3a, 0xb5, 0x06, 0xc1, 0x88, 0x24, 0x19, 0x70, 0x8a, - 0x01, 0x48, 0x01, 0xb0, 0x04, 0x66, 0xdb, 0x10, 0x0b, 0xf9, 0xdd, 0x11, 0x62, 0x1e, 0x13, 0x58, - 0x37, 0xf4, 0x43, 0xb5, 0x17, 0x84, 0x1d, 0xec, 0x03, 0xe1, 0x10, 0xe8, 0xf2, 0x43, 0x58, 0xca, - 0xb0, 0x16, 0xcd, 0x41, 0xfe, 0x98, 0x0c, 0x5c, 0xb7, 0x61, 0xf6, 0x13, 0xdd, 0x0a, 0xef, 0xa9, - 0x26, 0xc5, 0x76, 0xe8, 0xe3, 0xdc, 0x47, 0x92, 0xfc, 0xd3, 0x62, 0x78, 0xee, 0xf0, 0x52, 0xf6, - 0x1e, 0x94, 0x2d, 0x62, 0x6a, 0x6a, 0x47, 0xb1, 0x45, 0x85, 0xc2, 0xab, 0x52, 0x2c, 0xda, 0xb0, - 0x4f, 0x8d, 0x14, 0xbd, 0xb9, 0xab, 0x2d, 0x7a, 0xf3, 0xaf, 0xa6, 0xe8, 0xfd, 0x03, 0x28, 0xdb, - 0x5e, 0xb9, 0x5b, 0xe0, 0x90, 0xf7, 0xc7, 0x88, 0xaf, 0xa2, 0xd2, 0xf5, 0x15, 0xf8, 0x35, 0xae, - 0x0f, 0x9a, 0x56, 0xdd, 0x16, 0xc7, 0xac, 0x6e, 0x5f, 0x69, 0x45, 0xca, 0x62, 0xaa, 0xa9, 0x38, - 0x36, 0xe9, 0xf2, 0x40, 0x54, 0x0e, 0x62, 0x6a, 0x93, 0xb7, 0x62, 0x41, 0x45, 0xcf, 0x22, 0x53, - 0xb6, 0x7c, 0x99, 0x29, 0x3b, 0x93, 0x3d, 0x5d, 0xd1, 0x1e, 0x2c, 0x99, 0x96, 0xd1, 0xb3, 0x88, - 0x6d, 0x6f, 0x10, 0xa5, 0xab, 0xa9, 0x3a, 0xf1, 0xfc, 0xe3, 0x96, 0x2a, 0xaf, 0x9f, 0x9f, 0x55, - 0x97, 0x9a, 0xe9, 0x2c, 0x38, 0x4b, 0x56, 0x7e, 0x51, 0x80, 0xb9, 0x78, 0x06, 0xcc, 0xa8, 0x1e, - 0xa5, 0x4b, 0x55, 0x8f, 0xef, 0x84, 0x16, 0x83, 0x5b, 0x5a, 0xfb, 0xa3, 0x9f, 0xb2, 0x20, 0xd6, - 0x60, 0x56, 0x44, 0x03, 0x8f, 0x28, 0xea, 0x67, 0x7f, 0xf4, 0xf7, 0xa2, 0x64, 0x1c, 0xe7, 0x67, - 0x35, 0x61, 0x50, 0xea, 0x79, 0x20, 0x85, 0x68, 0x4d, 0xb8, 0x16, 0x67, 0xc0, 0x49, 0x19, 0xb4, - 0x0d, 0x0b, 0x8e, 0x9e, 0x84, 0x72, 0x67, 0xe3, 0xeb, 0x02, 0x6a, 0x61, 0x2f, 0xc9, 0x82, 0xd3, - 0xe4, 0xd0, 0x21, 0x40, 0xc7, 0x4b, 0xdb, 0x76, 0x65, 0x82, 0x47, 0xd8, 0xd5, 0x91, 0xd7, 0x8e, - 0x9f, 0xf1, 0x83, 0xb8, 0xe6, 0x37, 0xd9, 0x38, 0x84, 0x8c, 0x3e, 0x81, 0x69, 0x8b, 0x6f, 0x08, - 0x3c, 0x83, 0xdd, 0xa2, 0xfa, 0x35, 0x21, 0x36, 0x8d, 0xc3, 0x44, 0x1c, 0xe5, 0x4d, 0xa9, 0x83, - 0xcb, 0x23, 0xd7, 0xc1, 0xff, 0x2c, 0x85, 0x93, 0x90, 0x5f, 0x02, 0x7f, 0x1c, 0x29, 0x8f, 0xee, - 0xc6, 0xca, 0xa3, 0xc5, 0xa4, 0x44, 0xa8, 0x3a, 0x32, 0xd2, 0xab, 0xdf, 0x0f, 0xc7, 0xaa, 0x7e, - 0x83, 0xe4, 0x39, 0xbc, 0xfc, 0xfd, 0xb1, 0x04, 0x8b, 0x8f, 0x5a, 0x8f, 0x2d, 0xc3, 0x31, 0x3d, - 0x73, 0x76, 0x4d, 0xd7, 0xaf, 0xdf, 0x80, 0x82, 0xe5, 0x68, 0x5e, 0x3f, 0xde, 0xf4, 0xfa, 0x81, - 0x1d, 0x8d, 0xf5, 0x63, 0x21, 0x26, 0xe5, 0x76, 0x82, 0x09, 0xa0, 0x1d, 0x98, 0xb0, 0x14, 0xbd, - 0x47, 0xbc, 0xb4, 0x7a, 0x77, 0x88, 0xf5, 0x9b, 0x1b, 0x98, 0xb1, 0x87, 0x8a, 0x37, 0x2e, 0x8d, - 0x05, 0x8a, 0xfc, 0x67, 0x12, 0xcc, 0x3e, 0x69, 0xb7, 0x9b, 0x9b, 0x3a, 0x5f, 0xd1, 0xfc, 0xf0, - 0xf5, 0x0e, 0x14, 0x4c, 0x85, 0x1e, 0xc5, 0x33, 0x3d, 0xa3, 0x61, 0x4e, 0x41, 0xdf, 0x85, 0x12, - 0x8b, 0x24, 0x44, 0xef, 0x8e, 0x58, 0x6a, 0x0b, 0xf8, 0x86, 0x2b, 0x14, 0x54, 0x88, 0xa2, 0x01, - 0x7b, 0x70, 0xf2, 0x31, 0xdc, 0x0a, 0x99, 0xc3, 0xfc, 0xc1, 0xcf, 0x0c, 0x51, 0x0b, 0x8a, 0x4c, - 0xb3, 0x77, 0x24, 0x38, 0xec, 0xe4, 0x2b, 0xd6, 0xa5, 0xa0, 0xd2, 0x61, 0x5f, 0x36, 0x76, 0xb1, - 0xe4, 0x6d, 0x98, 0xe6, 0x27, 0xce, 0x86, 0x45, 0xb9, 0x5b, 0xd0, 0x6d, 0xc8, 0xf7, 0x55, 0x5d, - 0xe4, 0xd9, 0x29, 0x21, 0x93, 0x67, 0x39, 0x82, 0xb5, 0x73, 0xb2, 0x72, 0x2a, 0x22, 0x4f, 0x40, - 0x56, 0x4e, 0x31, 0x6b, 0x97, 0x1f, 0x43, 0x49, 0xb8, 0x3b, 0x0c, 0x94, 0xbf, 0x18, 0x28, 0x9f, - 0x02, 0xb4, 0x0b, 0xa5, 0xcd, 0x66, 0x43, 0x33, 0xdc, 0xaa, 0xab, 0xa3, 0x76, 0xad, 0xf8, 0x58, - 0xac, 0x6f, 0x6e, 0x60, 0xcc, 0x29, 0x48, 0x86, 0x09, 0x72, 0xda, 0x21, 0x26, 0xe5, 0x33, 0x62, - 0xb2, 0x01, 0x6c, 0x94, 0x1f, 0xf2, 0x16, 0x2c, 0x28, 0xf2, 0x9f, 0xe7, 0xa0, 0x24, 0xdc, 0x71, - 0x0d, 0xbb, 0xb0, 0xad, 0xc8, 0x2e, 0xec, 0xed, 0xd1, 0xa6, 0x46, 0xe6, 0x16, 0xac, 0x1d, 0xdb, - 0x82, 0xbd, 0x33, 0x22, 0xde, 0xc5, 0xfb, 0xaf, 0x9f, 0x48, 0x30, 0x13, 0x9d, 0x94, 0xe8, 0x01, - 0x4c, 0xb1, 0x84, 0xa3, 0x76, 0xc8, 0x4e, 0x50, 0xe7, 0xfa, 0xa7, 0x23, 0xad, 0x80, 0x84, 0xc3, - 0x7c, 0xa8, 0xe7, 0x8b, 0xb1, 0x79, 0x24, 0x3a, 0x9d, 0xed, 0x52, 0x87, 0xaa, 0x5a, 0xcd, 0xbd, - 0x38, 0xa9, 0x6d, 0xea, 0x74, 0xd7, 0x6a, 0x51, 0x4b, 0xd5, 0x7b, 0x09, 0x45, 0x7c, 0x52, 0x86, - 0x91, 0xe5, 0x7f, 0x92, 0x60, 0x4a, 0x98, 0x7c, 0x0d, 0xbb, 0x8a, 0xdf, 0x8d, 0xee, 0x2a, 0xee, - 0x8e, 0xb8, 0xc0, 0xd3, 0xb7, 0x14, 0x7f, 0x1b, 0x98, 0xce, 0x96, 0x34, 0x9b, 0xd5, 0x47, 0x86, - 0x4d, 0xe3, 0xb3, 0x9a, 0x2d, 0x46, 0xcc, 0x29, 0xc8, 0x81, 0x39, 0x35, 0x16, 0x03, 0x84, 0x6b, - 0xeb, 0xa3, 0x59, 0xe2, 0x8b, 0x35, 0x2a, 0x02, 0x7e, 0x2e, 0x4e, 0xc1, 0x09, 0x15, 0x32, 0x81, - 0x04, 0x17, 0xfa, 0x14, 0x0a, 0x47, 0x94, 0x9a, 0x29, 0x07, 0xc9, 0x43, 0x22, 0x4f, 0x60, 0x42, - 0x99, 0xf7, 0xae, 0xdd, 0x6e, 0x62, 0x0e, 0x25, 0xff, 0x6f, 0xe0, 0x8f, 0x96, 0x3b, 0xc7, 0xfd, - 0x78, 0x2a, 0x5d, 0x26, 0x9e, 0x4e, 0xa5, 0xc5, 0x52, 0xf4, 0x04, 0xf2, 0x54, 0x1b, 0x75, 0x5b, - 0x28, 0x10, 0xdb, 0x5b, 0xad, 0x20, 0x20, 0xb5, 0xb7, 0x5a, 0x98, 0x41, 0xa0, 0x5d, 0x28, 0xb2, - 0xec, 0xc3, 0x96, 0x60, 0x7e, 0xf4, 0x25, 0xcd, 0xfa, 0x1f, 0x4c, 0x08, 0xf6, 0x65, 0x63, 0x17, - 0x47, 0xfe, 0x3e, 0x4c, 0x47, 0xd6, 0x29, 0xfa, 0x0c, 0x6e, 0x6a, 0x86, 0xd2, 0x6d, 0x28, 0x9a, - 0xa2, 0x77, 0x88, 0x77, 0x6a, 0x7f, 0x37, 0x6d, 0x87, 0xb1, 0x15, 0xe2, 0x13, 0xab, 0xdc, 0xbf, - 0x7b, 0x0b, 0xd3, 0x70, 0x04, 0x51, 0x56, 0x00, 0x82, 0x3e, 0xa2, 0x2a, 0x14, 0xd9, 0x3c, 0x73, - 0xf3, 0xc9, 0x64, 0x63, 0x92, 0x59, 0xc8, 0xa6, 0x9f, 0x8d, 0xdd, 0x76, 0xb4, 0x0a, 0x60, 0x93, - 0x8e, 0x45, 0x28, 0x0f, 0x06, 0xb9, 0xe8, 0x0d, 0x64, 0xcb, 0xa7, 0xe0, 0x10, 0x97, 0xfc, 0x2f, - 0x12, 0x4c, 0xef, 0x10, 0xfa, 0x03, 0xc3, 0x3a, 0x6e, 0xf2, 0xcb, 0xe2, 0x6b, 0x08, 0xb6, 0x38, - 0x12, 0x6c, 0xdf, 0x1b, 0x32, 0x32, 0x11, 0xeb, 0xb2, 0x42, 0xae, 0xfc, 0x33, 0x09, 0x96, 0x22, - 0x9c, 0x0f, 0x83, 0xa5, 0xbb, 0x07, 0x45, 0xd3, 0xb0, 0xa8, 0x97, 0x88, 0xc7, 0x52, 0xc8, 0xc2, - 0x58, 0x28, 0x15, 0x33, 0x18, 0xec, 0xa2, 0xa1, 0x2d, 0xc8, 0x51, 0x43, 0x4c, 0xd5, 0xf1, 0x30, - 0x09, 0xb1, 0x1a, 0x20, 0x30, 0x73, 0x6d, 0x03, 0xe7, 0xa8, 0xc1, 0x06, 0xa2, 0x12, 0xe1, 0x0a, - 0x07, 0x9f, 0x2b, 0xea, 0x01, 0x86, 0xc2, 0xa1, 0x65, 0xf4, 0x2f, 0xdd, 0x07, 0x7f, 0x20, 0x1e, - 0x59, 0x46, 0x1f, 0x73, 0x2c, 0xf9, 0xe7, 0x12, 0xcc, 0x47, 0x38, 0xaf, 0x21, 0xf0, 0x7f, 0x1a, - 0x0d, 0xfc, 0xef, 0x8c, 0xd3, 0x91, 0x8c, 0xf0, 0xff, 0xf3, 0x5c, 0xac, 0x1b, 0xac, 0xc3, 0xe8, - 0x10, 0xa6, 0x4c, 0xa3, 0xdb, 0x7a, 0x05, 0xf7, 0x74, 0xb3, 0x2c, 0x6f, 0x36, 0x03, 0x2c, 0x1c, - 0x06, 0x46, 0xa7, 0x30, 0xaf, 0x2b, 0x7d, 0x62, 0x9b, 0x4a, 0x87, 0xb4, 0x5e, 0xc1, 0x01, 0xc9, - 0x6b, 0xfc, 0x22, 0x20, 0x8e, 0x88, 0x93, 0x4a, 0xd0, 0x36, 0x94, 0x54, 0x93, 0xd7, 0x71, 0xa2, - 0x76, 0x19, 0x9a, 0x45, 0xdd, 0xaa, 0xcf, 0x8d, 0xe7, 0xe2, 0x03, 0x7b, 0x18, 0xf2, 0xdf, 0xc5, - 0x67, 0x03, 0x9b, 0x7f, 0xe8, 0x31, 0x94, 0xf9, 0xb3, 0x8b, 0x8e, 0xa1, 0x79, 0x37, 0x03, 0x6c, - 0x64, 0x9b, 0xa2, 0xed, 0xe5, 0x59, 0xf5, 0xf5, 0x94, 0x43, 0x5f, 0x8f, 0x8c, 0x7d, 0x61, 0xb4, - 0x03, 0x05, 0xf3, 0xab, 0x54, 0x30, 0x3c, 0xc9, 0xf1, 0xb2, 0x85, 0xe3, 0xc8, 0x7f, 0x9c, 0x8f, - 0x99, 0xcb, 0x53, 0xdd, 0xf3, 0x57, 0x36, 0xea, 0x7e, 0xc5, 0x94, 0x39, 0xf2, 0x07, 0x50, 0x12, - 0x19, 0x5e, 0x4c, 0xe6, 0x6f, 0x8c, 0x33, 0x99, 0xc3, 0x59, 0xcc, 0xdf, 0xb0, 0x78, 0x8d, 0x1e, - 0x30, 0xfa, 0x1e, 0x4c, 0x10, 0x57, 0x85, 0x9b, 0x1b, 0x3f, 0x1c, 0x47, 0x45, 0x10, 0x57, 0x83, - 0x42, 0x55, 0xb4, 0x09, 0x54, 0xf4, 0x2d, 0xe6, 0x2f, 0xc6, 0xcb, 0x36, 0x81, 0x76, 0xa5, 0xc0, - 0xd3, 0xd5, 0x6d, 0xb7, 0xdb, 0x7e, 0xf3, 0xcb, 0xb3, 0x2a, 0x04, 0x9f, 0x38, 0x2c, 0x21, 0xff, - 0x9b, 0x04, 0xf3, 0xdc, 0x43, 0x1d, 0xc7, 0x52, 0xe9, 0xe0, 0xda, 0x12, 0xd3, 0x7e, 0x24, 0x31, - 0x7d, 0x30, 0xc4, 0x2d, 0x09, 0x0b, 0x33, 0x93, 0xd3, 0x2f, 0x24, 0x78, 0x2d, 0xc1, 0x7d, 0x0d, - 0x71, 0x71, 0x2f, 0x1a, 0x17, 0xdf, 0x1b, 0xb7, 0x43, 0x19, 0xb1, 0xf1, 0xf3, 0x9b, 0x29, 0xdd, - 0xe1, 0x2b, 0x65, 0x15, 0xc0, 0xb4, 0xd4, 0x13, 0x55, 0x23, 0x3d, 0x71, 0x3b, 0x5d, 0x0e, 0xbd, - 0x81, 0xf2, 0x29, 0x38, 0xc4, 0x85, 0x6c, 0x58, 0xec, 0x92, 0x43, 0xc5, 0xd1, 0xe8, 0x5a, 0xb7, - 0xbb, 0xae, 0x98, 0xca, 0x81, 0xaa, 0xa9, 0x54, 0x15, 0xc7, 0x05, 0x93, 0x8d, 0x4f, 0xdc, 0x5b, - 0xe3, 0x34, 0x8e, 0x97, 0x67, 0xd5, 0xdb, 0x69, 0xb7, 0x43, 0x1e, 0xcb, 0x00, 0x67, 0x40, 0xa3, - 0x01, 0x54, 0x2c, 0xf2, 0x7d, 0x47, 0xb5, 0x48, 0x77, 0xc3, 0x32, 0xcc, 0x88, 0xda, 0x3c, 0x57, - 0xfb, 0xdb, 0xe7, 0x67, 0xd5, 0x0a, 0xce, 0xe0, 0x19, 0xae, 0x38, 0x13, 0x1e, 0x3d, 0x87, 0x05, - 0xc5, 0x7d, 0x3a, 0x16, 0xd1, 0xea, 0xae, 0x92, 0x8f, 0xce, 0xcf, 0xaa, 0x0b, 0x6b, 0x49, 0xf2, - 0x70, 0x85, 0x69, 0xa0, 0xa8, 0x0e, 0xa5, 0x13, 0x43, 0x73, 0xfa, 0xc4, 0xae, 0x14, 0x39, 0x3e, - 0x4b, 0x04, 0xa5, 0x7d, 0xb7, 0xe9, 0xe5, 0x59, 0x75, 0xe2, 0x51, 0x8b, 0xaf, 0x3e, 0x8f, 0x8b, - 0x6d, 0x28, 0x59, 0x2d, 0x29, 0x56, 0x3c, 0x3f, 0x31, 0x2e, 0x07, 0x51, 0xeb, 0x49, 0x40, 0xc2, - 0x61, 0x3e, 0xf4, 0x0c, 0x26, 0x8f, 0xc4, 0xa9, 0x84, 0x5d, 0x29, 0x8d, 0x94, 0x84, 0x23, 0xa7, - 0x18, 0x8d, 0x79, 0xa1, 0x62, 0xd2, 0x6b, 0xb6, 0x71, 0x80, 0x88, 0xde, 0x82, 0x12, 0xff, 0xd8, - 0xdc, 0xe0, 0xc7, 0x71, 0xe5, 0x20, 0xb6, 0x3d, 0x71, 0x9b, 0xb1, 0x47, 0xf7, 0x58, 0x37, 0x9b, - 0xeb, 0xfc, 0x58, 0x38, 0xc6, 0xba, 0xd9, 0x5c, 0xc7, 0x1e, 0x1d, 0x7d, 0x06, 0x25, 0x9b, 0x6c, - 0xa9, 0xba, 0x73, 0x5a, 0x81, 0x91, 0x2e, 0x95, 0x5b, 0x0f, 0x39, 0x77, 0xec, 0x60, 0x2c, 0xd0, - 0x20, 0xe8, 0xd8, 0x83, 0x45, 0x47, 0x30, 0x69, 0x39, 0xfa, 0x9a, 0xbd, 0x67, 0x13, 0xab, 0x32, - 0xc5, 0x75, 0x0c, 0x0b, 0xe7, 0xd8, 0xe3, 0x8f, 0x6b, 0xf1, 0x3d, 0xe4, 0x73, 0xe0, 0x00, 0x1c, - 0xfd, 0xa9, 0x04, 0xc8, 0x76, 0x4c, 0x53, 0x23, 0x7d, 0xa2, 0x53, 0x45, 0xe3, 0x67, 0x71, 0x76, - 0xe5, 0x26, 0xd7, 0xf9, 0x3b, 0xc3, 0xfa, 0x95, 0x10, 0x8c, 0x2b, 0xf7, 0x0f, 0xbd, 0x93, 0xac, - 0x38, 0x45, 0x2f, 0x73, 0xed, 0xa1, 0xcd, 0x7f, 0x57, 0xa6, 0x47, 0x72, 0x6d, 0xfa, 0x99, 0x63, - 0xe0, 0x5a, 0x41, 0xc7, 0x1e, 0x2c, 0xda, 0x87, 0x45, 0x8b, 0x28, 0xdd, 0x5d, 0x5d, 0x1b, 0x60, - 0xc3, 0xa0, 0x8f, 0x54, 0x8d, 0xd8, 0x03, 0x9b, 0x92, 0x7e, 0x65, 0x86, 0x0f, 0xbb, 0xff, 0x28, - 0x03, 0xa7, 0x72, 0xe1, 0x0c, 0x69, 0xd4, 0x87, 0xaa, 0x17, 0x32, 0xd8, 0x7a, 0xf2, 0x63, 0xd6, - 0x43, 0xbb, 0xa3, 0x68, 0xee, 0x3d, 0xc0, 0x2c, 0x57, 0xf0, 0xe6, 0xf9, 0x59, 0xb5, 0xba, 0x71, - 0x31, 0x2b, 0x1e, 0x86, 0x85, 0xbe, 0x0b, 0x15, 0x25, 0x4b, 0xcf, 0x1c, 0xd7, 0xf3, 0x06, 0x8b, - 0x43, 0x99, 0x0a, 0x32, 0xa5, 0x11, 0x85, 0x39, 0x25, 0xfa, 0x42, 0xd5, 0xae, 0xcc, 0x8f, 0x74, - 0x10, 0x19, 0x7b, 0xd8, 0x1a, 0x1c, 0x46, 0xc4, 0x08, 0x36, 0x4e, 0x68, 0xe0, 0xcf, 0x27, 0xc4, - 0x61, 0xfa, 0xf5, 0xbc, 0x57, 0x1c, 0xef, 0xf9, 0x44, 0x60, 0xda, 0x2b, 0x7b, 0x3e, 0x11, 0x82, - 0xbc, 0xf8, 0xf8, 0xee, 0xbf, 0x73, 0xb0, 0x10, 0x30, 0x8f, 0xfc, 0x7c, 0x22, 0x45, 0xe4, 0xca, - 0x9e, 0x4f, 0xa4, 0xbf, 0x3f, 0xc8, 0x5f, 0xf5, 0xfb, 0x83, 0x2b, 0x78, 0xb6, 0xc1, 0x9f, 0x34, - 0x04, 0xae, 0xfb, 0xff, 0xf7, 0xa4, 0x21, 0xb0, 0x2d, 0xa3, 0xc8, 0xfa, 0x87, 0x5c, 0xb8, 0x03, - 0xbf, 0xf6, 0xf7, 0xea, 0x5f, 0xfd, 0x51, 0xa7, 0xfc, 0x8b, 0x3c, 0xcc, 0xc5, 0x57, 0x63, 0xe4, - 0xfa, 0x55, 0x1a, 0x7a, 0xfd, 0xda, 0x84, 0x5b, 0x87, 0x8e, 0xa6, 0x0d, 0xb8, 0x1b, 0x42, 0x77, - 0xb0, 0xee, 0xf5, 0xc9, 0x1b, 0x42, 0xf2, 0xd6, 0xa3, 0x14, 0x1e, 0x9c, 0x2a, 0x99, 0x71, 0x95, - 0x9c, 0xbf, 0xd4, 0x55, 0x72, 0xe2, 0x66, 0xb3, 0x30, 0xc6, 0xcd, 0x66, 0xea, 0xb5, 0x70, 0xf1, - 0x12, 0xd7, 0xc2, 0x97, 0xb9, 0xc7, 0x4d, 0x09, 0x62, 0xc3, 0xee, 0x71, 0xe5, 0x37, 0x60, 0x59, - 0x88, 0x51, 0x7e, 0xc5, 0xaa, 0x53, 0xcb, 0xd0, 0x34, 0x62, 0x6d, 0x38, 0xfd, 0xfe, 0x40, 0xfe, - 0x26, 0xcc, 0x44, 0x1f, 0x0f, 0xb8, 0x23, 0xed, 0xbe, 0x5f, 0x10, 0x97, 0x58, 0xa1, 0x91, 0x76, - 0xdb, 0xb1, 0xcf, 0x21, 0xff, 0x50, 0x82, 0xc5, 0xf4, 0x47, 0x82, 0x48, 0x83, 0x99, 0xbe, 0x72, - 0x1a, 0x7e, 0x51, 0x29, 0x5d, 0xf2, 0x78, 0x81, 0xdf, 0x1a, 0x6f, 0x47, 0xb0, 0x70, 0x0c, 0x5b, - 0xfe, 0x52, 0x82, 0xa5, 0x8c, 0xfb, 0xda, 0xeb, 0xb5, 0x04, 0x3d, 0x85, 0x72, 0x5f, 0x39, 0x6d, - 0x39, 0x56, 0x8f, 0x5c, 0xfa, 0x40, 0x85, 0x47, 0x8c, 0x6d, 0x81, 0x82, 0x7d, 0x3c, 0xf9, 0xc7, - 0x12, 0x54, 0xb2, 0x4a, 0x5b, 0xf4, 0x20, 0x72, 0xb3, 0xfc, 0xf5, 0xd8, 0xcd, 0xf2, 0x7c, 0x42, - 0xee, 0x8a, 0xee, 0x95, 0xff, 0x5e, 0x82, 0xc5, 0xf4, 0x12, 0x1f, 0xbd, 0x1f, 0xb1, 0xb0, 0x1a, - 0xb3, 0x70, 0x36, 0x26, 0x25, 0xec, 0xfb, 0x1e, 0xcc, 0x88, 0x8d, 0x80, 0x80, 0x11, 0x5e, 0x95, - 0xd3, 0x62, 0xa5, 0x80, 0xf0, 0x0a, 0x5f, 0x3e, 0x5e, 0xd1, 0x36, 0x1c, 0x43, 0x93, 0xff, 0x24, - 0x07, 0xc5, 0x56, 0x47, 0xd1, 0xc8, 0x35, 0x94, 0x59, 0xdf, 0x8e, 0x94, 0x59, 0xc3, 0xfe, 0xfd, - 0xc0, 0xad, 0xca, 0xac, 0xb0, 0x70, 0xac, 0xc2, 0x7a, 0x7b, 0x24, 0xb4, 0x8b, 0x8b, 0xab, 0xdf, - 0x82, 0x49, 0x5f, 0xe9, 0x78, 0x31, 0x5f, 0xfe, 0x9b, 0x1c, 0x4c, 0x85, 0x54, 0x8c, 0x99, 0x31, - 0x0e, 0x23, 0x99, 0x76, 0x94, 0xff, 0x41, 0x85, 0x74, 0xd5, 0xbc, 0xdc, 0xea, 0x3e, 0x12, 0x0c, - 0x9e, 0x85, 0x25, 0x53, 0xee, 0x37, 0x61, 0x86, 0xf2, 0xff, 0x09, 0xf9, 0xc7, 0x90, 0x79, 0x3e, - 0x17, 0xfd, 0xa7, 0xa5, 0xed, 0x08, 0x15, 0xc7, 0xb8, 0x97, 0x3f, 0x81, 0xe9, 0x88, 0xb2, 0xb1, - 0xde, 0xf8, 0xfd, 0xa3, 0x04, 0x5f, 0x1f, 0xba, 0x49, 0x44, 0x8d, 0xc8, 0x22, 0xa9, 0xc5, 0x16, - 0xc9, 0x4a, 0x36, 0xc0, 0x15, 0xbe, 0x15, 0xf9, 0x61, 0x0e, 0x50, 0xfb, 0x48, 0xb5, 0xba, 0x4d, - 0xc5, 0xa2, 0x03, 0x2c, 0xfe, 0xec, 0x75, 0x0d, 0x0b, 0xe6, 0x01, 0x4c, 0x75, 0x89, 0xdd, 0xb1, - 0x54, 0xee, 0x1c, 0x51, 0x9d, 0xfb, 0x07, 0x29, 0x1b, 0x01, 0x09, 0x87, 0xf9, 0xd0, 0x77, 0xa0, - 0x7c, 0xe2, 0xfe, 0x09, 0xd1, 0x3b, 0x9c, 0x1d, 0x56, 0x48, 0x06, 0x7f, 0x5b, 0x0c, 0xe6, 0x8f, - 0x68, 0xb0, 0xb1, 0x0f, 0x26, 0x7f, 0x2e, 0xc1, 0x62, 0xd2, 0x11, 0x1b, 0xcc, 0xd4, 0xab, 0x77, - 0xc6, 0x1b, 0x50, 0xe0, 0xe8, 0xcc, 0x0b, 0x37, 0xdd, 0x43, 0x77, 0xa6, 0x19, 0xf3, 0x56, 0xf9, - 0x3f, 0x24, 0x58, 0x4e, 0x37, 0xed, 0x1a, 0xca, 0xf6, 0xa7, 0xd1, 0xb2, 0x7d, 0xd8, 0x39, 0x45, - 0xba, 0x9d, 0x19, 0x25, 0xfc, 0xbf, 0xa7, 0xfa, 0xfc, 0x1a, 0x3a, 0xb5, 0x1f, 0xed, 0xd4, 0xfd, - 0xb1, 0x3b, 0x95, 0xde, 0xa1, 0xc6, 0xbb, 0x2f, 0xbe, 0x58, 0xb9, 0xf1, 0xcb, 0x2f, 0x56, 0x6e, - 0xfc, 0xea, 0x8b, 0x95, 0x1b, 0x7f, 0x74, 0xbe, 0x22, 0xbd, 0x38, 0x5f, 0x91, 0x7e, 0x79, 0xbe, - 0x22, 0xfd, 0xea, 0x7c, 0x45, 0xfa, 0xaf, 0xf3, 0x15, 0xe9, 0x2f, 0xbe, 0x5c, 0xb9, 0xf1, 0xb4, - 0x24, 0x70, 0xff, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x55, 0xc4, 0x0b, 0x53, 0x44, 0x3d, 0x00, 0x00, + // 3571 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x5b, 0xcd, 0x6f, 0x1c, 0x47, + 0x76, 0x57, 0xcf, 0x0c, 0x39, 0xc3, 0x47, 0xf1, 0xab, 0x28, 0x93, 0x63, 0xca, 0xe2, 0xc8, 0x6d, + 0x40, 0x91, 0x1c, 0x69, 0xc6, 0x92, 0x2d, 0x59, 0xb1, 0x10, 0x3b, 0x1c, 0x52, 0x1f, 0x74, 0xf8, + 0xa5, 0x1a, 0x52, 0x71, 0x8c, 0xc8, 0x71, 0x73, 0xa6, 0x38, 0x6c, 0xb1, 0xa7, 0xbb, 0xdd, 0x5d, + 0x4d, 0x73, 0x80, 0x20, 0xc8, 0x21, 0x08, 0x10, 0x20, 0x41, 0x92, 0x83, 0xf3, 0x71, 0x8b, 0x2f, + 0x39, 0x25, 0x48, 0x6e, 0xc9, 0xc1, 0x30, 0x10, 0xc0, 0x0b, 0x08, 0x0b, 0x2f, 0xe0, 0xdb, 0xfa, + 0x44, 0xac, 0xe9, 0xd3, 0x62, 0xff, 0x81, 0x85, 0x0e, 0x8b, 0x45, 0x55, 0x57, 0x7f, 0x77, 0x73, + 0x66, 0x68, 0x89, 0x58, 0x2c, 0xf6, 0xc6, 0xa9, 0xf7, 0xde, 0xef, 0xbd, 0x7a, 0xf5, 0xea, 0xbd, + 0xd7, 0x55, 0x45, 0xb8, 0xb7, 0x77, 0xdb, 0xae, 0xaa, 0x46, 0x6d, 0xcf, 0xd9, 0x26, 0x96, 0x4e, + 0x28, 0xb1, 0x6b, 0xfb, 0x44, 0x6f, 0x19, 0x56, 0x4d, 0x10, 0x14, 0x53, 0xad, 0x91, 0x03, 0x4a, + 0x74, 0x5b, 0x35, 0x74, 0xbb, 0xb6, 0x7f, 0x7d, 0x9b, 0x50, 0xe5, 0x7a, 0xad, 0x4d, 0x74, 0x62, + 0x29, 0x94, 0xb4, 0xaa, 0xa6, 0x65, 0x50, 0x03, 0x5d, 0x70, 0xd9, 0xab, 0x8a, 0xa9, 0x56, 0x03, + 0xf6, 0xaa, 0x60, 0x9f, 0xbb, 0xd6, 0x56, 0xe9, 0xae, 0xb3, 0x5d, 0x6d, 0x1a, 0x9d, 0x5a, 0xdb, + 0x68, 0x1b, 0x35, 0x2e, 0xb5, 0xed, 0xec, 0xf0, 0x5f, 0xfc, 0x07, 0xff, 0xcb, 0x45, 0x9b, 0x93, + 0x43, 0xca, 0x9b, 0x86, 0x45, 0x6a, 0xfb, 0x09, 0x8d, 0x73, 0x57, 0x42, 0x3c, 0xa6, 0xa1, 0xa9, + 0xcd, 0x6e, 0x96, 0x71, 0x73, 0x6f, 0x05, 0xac, 0x1d, 0xa5, 0xb9, 0xab, 0xea, 0xc4, 0xea, 0xd6, + 0xcc, 0xbd, 0x36, 0x97, 0xb5, 0x88, 0x6d, 0x38, 0x56, 0x93, 0x0c, 0x24, 0x65, 0xd7, 0x3a, 0x84, + 0x2a, 0x69, 0x66, 0xd5, 0xb2, 0xa4, 0x2c, 0x47, 0xa7, 0x6a, 0x27, 0xa9, 0xe6, 0x56, 0x2f, 0x01, + 0xbb, 0xb9, 0x4b, 0x3a, 0x4a, 0x42, 0xee, 0xcd, 0x2c, 0x39, 0x87, 0xaa, 0x5a, 0x4d, 0xd5, 0xa9, + 0x4d, 0xad, 0xb8, 0x90, 0x7c, 0x07, 0xa6, 0x16, 0x34, 0xcd, 0xf8, 0x94, 0xb4, 0xee, 0x69, 0xe4, + 0xe0, 0x91, 0xa1, 0x39, 0x1d, 0x82, 0x2e, 0xc1, 0x70, 0xcb, 0x52, 0xf7, 0x89, 0x55, 0x96, 0x2e, + 0x4a, 0x97, 0x47, 0xea, 0xe3, 0x4f, 0x0f, 0x2b, 0x67, 0x8e, 0x0e, 0x2b, 0xc3, 0x4b, 0x7c, 0x14, + 0x0b, 0xaa, 0x7c, 0x17, 0x26, 0x84, 0xf0, 0x03, 0xc3, 0xa6, 0x1b, 0x0a, 0xdd, 0x45, 0x37, 0x00, + 0x4c, 0x85, 0xee, 0x6e, 0x58, 0x64, 0x47, 0x3d, 0x10, 0xe2, 0x48, 0x88, 0xc3, 0x86, 0x4f, 0xc1, + 0x21, 0x2e, 0xf9, 0xdf, 0x24, 0x78, 0x79, 0xd1, 0xb1, 0xa9, 0xd1, 0x59, 0x25, 0xd4, 0x52, 0x9b, + 0x8b, 0x8e, 0x65, 0x11, 0x9d, 0x36, 0xa8, 0x42, 0x1d, 0x1b, 0x5d, 0x84, 0x82, 0xae, 0x74, 0x88, + 0xc0, 0x3a, 0x2b, 0xb0, 0x0a, 0x6b, 0x4a, 0x87, 0x60, 0x4e, 0x41, 0x1f, 0xc2, 0xd0, 0xbe, 0xa2, + 0x39, 0xa4, 0x9c, 0xbb, 0x28, 0x5d, 0x1e, 0xbd, 0x51, 0xad, 0x06, 0xa1, 0xe7, 0x3b, 0xa2, 0x6a, + 0xee, 0xb5, 0x79, 0x2c, 0x7a, 0xab, 0x5b, 0x7d, 0xe8, 0x28, 0x3a, 0x55, 0x69, 0xb7, 0x7e, 0x4e, + 0x40, 0x9e, 0x15, 0x7a, 0x1f, 0x31, 0x2c, 0xec, 0x42, 0xca, 0x7f, 0x09, 0x17, 0x32, 0x4d, 0x5b, + 0x51, 0x6d, 0x8a, 0x1e, 0xc3, 0x90, 0x4a, 0x49, 0xc7, 0x2e, 0x4b, 0x17, 0xf3, 0x97, 0x47, 0x6f, + 0xdc, 0xae, 0x1e, 0x1b, 0xf7, 0xd5, 0x4c, 0xb0, 0xfa, 0x98, 0x30, 0x63, 0x68, 0x99, 0xc1, 0x61, + 0x17, 0x55, 0xfe, 0x27, 0x09, 0x50, 0x58, 0x66, 0x53, 0xb1, 0xda, 0x84, 0xf6, 0xe1, 0x94, 0x3f, + 0xfd, 0x61, 0x4e, 0x99, 0x16, 0x90, 0xa3, 0xae, 0xc2, 0x88, 0x4f, 0x4c, 0x98, 0x49, 0x9a, 0xc4, + 0x9d, 0xf1, 0x28, 0xea, 0x8c, 0xeb, 0x03, 0x38, 0xc3, 0x45, 0xc9, 0xf0, 0xc2, 0x67, 0x39, 0x18, + 0x59, 0x52, 0x48, 0xc7, 0xd0, 0x1b, 0x84, 0xa2, 0x8f, 0xa1, 0xc4, 0x36, 0x5b, 0x4b, 0xa1, 0x0a, + 0x77, 0xc0, 0xe8, 0x8d, 0x37, 0x8e, 0x9b, 0x9d, 0x5d, 0x65, 0xdc, 0xd5, 0xfd, 0xeb, 0xd5, 0xf5, + 0xed, 0x27, 0xa4, 0x49, 0x57, 0x09, 0x55, 0x82, 0x98, 0x0c, 0xc6, 0xb0, 0x8f, 0x8a, 0xd6, 0xa0, + 0x60, 0x9b, 0xa4, 0x29, 0x7c, 0x77, 0xb5, 0xc7, 0x34, 0x7c, 0xcb, 0x1a, 0x26, 0x69, 0x06, 0x8b, + 0xc1, 0x7e, 0x61, 0x8e, 0x83, 0x1e, 0xc1, 0xb0, 0xcd, 0x57, 0xb9, 0x9c, 0x4f, 0xac, 0xc6, 0xf1, + 0x88, 0x6e, 0x6c, 0xf8, 0x1b, 0xd0, 0xfd, 0x8d, 0x05, 0x9a, 0xfc, 0xf3, 0x1c, 0x20, 0x9f, 0x77, + 0xd1, 0xd0, 0x5b, 0x2a, 0x55, 0x0d, 0x1d, 0xbd, 0x03, 0x05, 0xda, 0x35, 0xbd, 0xe8, 0xb8, 0xe4, + 0x19, 0xb4, 0xd9, 0x35, 0xc9, 0xb3, 0xc3, 0xca, 0x4c, 0x52, 0x82, 0x51, 0x30, 0x97, 0x41, 0x2b, + 0xbe, 0xa9, 0x39, 0x2e, 0xfd, 0x56, 0x54, 0xf5, 0xb3, 0xc3, 0x4a, 0x4a, 0x2e, 0xae, 0xfa, 0x48, + 0x51, 0x03, 0xd1, 0x3e, 0x20, 0x4d, 0xb1, 0xe9, 0xa6, 0xa5, 0xe8, 0xb6, 0xab, 0x49, 0xed, 0x10, + 0xe1, 0x84, 0xd7, 0xfb, 0x5b, 0x34, 0x26, 0x51, 0x9f, 0x13, 0x56, 0xa0, 0x95, 0x04, 0x1a, 0x4e, + 0xd1, 0xc0, 0x32, 0x98, 0x45, 0x14, 0xdb, 0xd0, 0xcb, 0x85, 0x68, 0x06, 0xc3, 0x7c, 0x14, 0x0b, + 0x2a, 0xba, 0x02, 0xc5, 0x0e, 0xb1, 0x6d, 0xa5, 0x4d, 0xca, 0x43, 0x9c, 0x71, 0x42, 0x30, 0x16, + 0x57, 0xdd, 0x61, 0xec, 0xd1, 0xe5, 0x2f, 0x24, 0x18, 0xf3, 0x3d, 0xc7, 0xa3, 0xfd, 0xcf, 0x12, + 0x71, 0x58, 0xed, 0x6f, 0x4a, 0x4c, 0x9a, 0x47, 0xe1, 0xa4, 0xd0, 0x56, 0xf2, 0x46, 0x42, 0x31, + 0xb8, 0xea, 0xed, 0xa5, 0x1c, 0xdf, 0x4b, 0x97, 0xfb, 0x0d, 0x99, 0x8c, 0x2d, 0xf4, 0xcf, 0x85, + 0x90, 0xf9, 0x2c, 0x34, 0xd1, 0x63, 0x28, 0xd9, 0x44, 0x23, 0x4d, 0x6a, 0x58, 0xc2, 0xfc, 0x37, + 0xfb, 0x34, 0x5f, 0xd9, 0x26, 0x5a, 0x43, 0x88, 0xd6, 0xcf, 0x32, 0xfb, 0xbd, 0x5f, 0xd8, 0x87, + 0x44, 0x0f, 0xa1, 0x44, 0x49, 0xc7, 0xd4, 0x14, 0xea, 0xe5, 0xa0, 0xd7, 0xc2, 0x53, 0x60, 0x91, + 0xc3, 0xc0, 0x36, 0x8c, 0xd6, 0xa6, 0x60, 0xe3, 0xdb, 0xc7, 0x77, 0x89, 0x37, 0x8a, 0x7d, 0x18, + 0xb4, 0x0f, 0xe3, 0x8e, 0xd9, 0x62, 0x9c, 0x94, 0xd5, 0xb0, 0x76, 0x57, 0x44, 0xd2, 0xad, 0x7e, + 0x7d, 0xb3, 0x15, 0x91, 0xae, 0xcf, 0x08, 0x5d, 0xe3, 0xd1, 0x71, 0x1c, 0xd3, 0x82, 0x16, 0x60, + 0xa2, 0xa3, 0xea, 0x98, 0x28, 0xad, 0x6e, 0x83, 0x34, 0x0d, 0xbd, 0x65, 0xf3, 0xb0, 0x1a, 0xaa, + 0xcf, 0x0a, 0x80, 0x89, 0xd5, 0x28, 0x19, 0xc7, 0xf9, 0xd1, 0xfb, 0x80, 0xbc, 0x69, 0xdc, 0x77, + 0x4b, 0xb0, 0x6a, 0xe8, 0x3c, 0xe6, 0xf2, 0x41, 0x70, 0x6f, 0x26, 0x38, 0x70, 0x8a, 0x14, 0x5a, + 0x81, 0x73, 0x16, 0xd9, 0x57, 0xd9, 0x1c, 0x1f, 0xa8, 0x36, 0x35, 0xac, 0xee, 0x8a, 0xda, 0x51, + 0x69, 0x79, 0x98, 0xdb, 0x54, 0x3e, 0x3a, 0xac, 0x9c, 0xc3, 0x29, 0x74, 0x9c, 0x2a, 0x25, 0xff, + 0xcb, 0x30, 0x4c, 0xc4, 0xf2, 0x0d, 0x7a, 0x04, 0x33, 0x4d, 0xb7, 0x38, 0xad, 0x39, 0x9d, 0x6d, + 0x62, 0x35, 0x9a, 0xbb, 0xa4, 0xe5, 0x68, 0xa4, 0xc5, 0x03, 0x65, 0xa8, 0x3e, 0x2f, 0x2c, 0x9e, + 0x59, 0x4c, 0xe5, 0xc2, 0x19, 0xd2, 0xcc, 0x0b, 0x3a, 0x1f, 0x5a, 0x55, 0x6d, 0xdb, 0xc7, 0xcc, + 0x71, 0x4c, 0xdf, 0x0b, 0x6b, 0x09, 0x0e, 0x9c, 0x22, 0xc5, 0x6c, 0x6c, 0x11, 0x5b, 0xb5, 0x48, + 0x2b, 0x6e, 0x63, 0x3e, 0x6a, 0xe3, 0x52, 0x2a, 0x17, 0xce, 0x90, 0x46, 0x37, 0x61, 0xd4, 0xd5, + 0xc6, 0xd7, 0x4f, 0x2c, 0xb4, 0x5f, 0x0e, 0xd7, 0x02, 0x12, 0x0e, 0xf3, 0xb1, 0xa9, 0x19, 0xdb, + 0x36, 0xb1, 0xf6, 0x49, 0x2b, 0x7b, 0x81, 0xd7, 0x13, 0x1c, 0x38, 0x45, 0x8a, 0x4d, 0xcd, 0x8d, + 0xc0, 0xc4, 0xd4, 0x86, 0xa3, 0x53, 0xdb, 0x4a, 0xe5, 0xc2, 0x19, 0xd2, 0x2c, 0x8e, 0x5d, 0x93, + 0x17, 0xf6, 0x15, 0x55, 0x53, 0xb6, 0x35, 0x52, 0x2e, 0x46, 0xe3, 0x78, 0x2d, 0x4a, 0xc6, 0x71, + 0x7e, 0x74, 0x1f, 0xa6, 0xdc, 0xa1, 0x2d, 0x5d, 0xf1, 0x41, 0x4a, 0x1c, 0xe4, 0x65, 0x01, 0x32, + 0xb5, 0x16, 0x67, 0xc0, 0x49, 0x19, 0xf4, 0x0e, 0x8c, 0x37, 0x0d, 0x4d, 0xe3, 0xf1, 0xb8, 0x68, + 0x38, 0x3a, 0x2d, 0x8f, 0x70, 0x14, 0xc4, 0xf6, 0xe3, 0x62, 0x84, 0x82, 0x63, 0x9c, 0x88, 0x00, + 0x34, 0xbd, 0x82, 0x63, 0x97, 0xa1, 0xaf, 0x5e, 0x23, 0x59, 0xf4, 0x82, 0x1e, 0xc0, 0x1f, 0xb2, + 0x71, 0x08, 0x58, 0xfe, 0xb1, 0x04, 0xb3, 0x19, 0xa9, 0x03, 0xbd, 0x17, 0x29, 0xb1, 0xbf, 0x1f, + 0x2b, 0xb1, 0xe7, 0x33, 0xc4, 0x42, 0x75, 0x56, 0x87, 0x31, 0x8b, 0xcd, 0x4a, 0x6f, 0xbb, 0x2c, + 0x22, 0x47, 0xde, 0xec, 0x31, 0x0d, 0x1c, 0x96, 0x09, 0x72, 0xfe, 0xd4, 0xd1, 0x61, 0x65, 0x2c, + 0x42, 0xc3, 0x51, 0x78, 0xf9, 0x5f, 0x73, 0x00, 0x4b, 0xc4, 0xd4, 0x8c, 0x6e, 0x87, 0xe8, 0xa7, + 0xd1, 0x43, 0xad, 0x47, 0x7a, 0xa8, 0x6b, 0xbd, 0x96, 0xc7, 0x37, 0x2d, 0xb3, 0x89, 0xfa, 0x93, + 0x58, 0x13, 0x55, 0xeb, 0x1f, 0xf2, 0xf8, 0x2e, 0xea, 0xa7, 0x79, 0x98, 0x0e, 0x98, 0x83, 0x36, + 0xea, 0x4e, 0x64, 0x8d, 0x7f, 0x2f, 0xb6, 0xc6, 0xb3, 0x29, 0x22, 0x2f, 0xac, 0x8f, 0x7a, 0xfe, + 0xfd, 0x0c, 0x7a, 0x02, 0xe3, 0xac, 0x71, 0x72, 0xc3, 0x83, 0xb7, 0x65, 0xc3, 0x03, 0xb7, 0x65, + 0x7e, 0x01, 0x5d, 0x89, 0x20, 0xe1, 0x18, 0x72, 0x46, 0x1b, 0x58, 0x7c, 0xd1, 0x6d, 0xa0, 0xfc, + 0xa5, 0x04, 0xe3, 0xc1, 0x32, 0x9d, 0x42, 0xd3, 0xb6, 0x16, 0x6d, 0xda, 0xae, 0xf4, 0x1d, 0xa2, + 0x19, 0x5d, 0xdb, 0x2f, 0x59, 0x83, 0xef, 0x33, 0xb1, 0x0d, 0xbe, 0xad, 0x34, 0xf7, 0xfa, 0xf8, + 0xfc, 0xfb, 0x4c, 0x02, 0x24, 0xaa, 0xc0, 0x82, 0xae, 0x1b, 0x54, 0x71, 0x73, 0xa5, 0x6b, 0xd6, + 0x72, 0xdf, 0x66, 0x79, 0x1a, 0xab, 0x5b, 0x09, 0xac, 0xbb, 0x3a, 0xb5, 0xba, 0xc1, 0x8a, 0x24, + 0x19, 0x70, 0x8a, 0x01, 0x48, 0x01, 0xb0, 0x04, 0xe6, 0xa6, 0x21, 0x36, 0xf2, 0xb5, 0x3e, 0x72, + 0x1e, 0x13, 0x58, 0x34, 0xf4, 0x1d, 0xb5, 0x1d, 0xa4, 0x1d, 0xec, 0x03, 0xe1, 0x10, 0xe8, 0xdc, + 0x5d, 0x98, 0xcd, 0xb0, 0x16, 0x4d, 0x42, 0x7e, 0x8f, 0x74, 0x5d, 0xb7, 0x61, 0xf6, 0x27, 0x3a, + 0x17, 0xfe, 0x4c, 0x1e, 0x11, 0x5f, 0xb8, 0xef, 0xe4, 0x6e, 0x4b, 0xf2, 0x17, 0x43, 0xe1, 0xd8, + 0xe1, 0x1d, 0xf3, 0x65, 0x28, 0x59, 0xc4, 0xd4, 0xd4, 0xa6, 0x62, 0x8b, 0x46, 0x88, 0x37, 0xbf, + 0x58, 0x8c, 0x61, 0x9f, 0x1a, 0xe9, 0xad, 0x73, 0x2f, 0xb6, 0xb7, 0xce, 0x3f, 0x9f, 0xde, 0xfa, + 0xcf, 0xa1, 0x64, 0x7b, 0x5d, 0x75, 0x81, 0x43, 0x5e, 0x1f, 0x20, 0xbf, 0x8a, 0x86, 0xda, 0x57, + 0xe0, 0xb7, 0xd2, 0x3e, 0x68, 0x5a, 0x13, 0x3d, 0x34, 0x60, 0x13, 0xfd, 0x5c, 0x1b, 0x5f, 0x96, + 0x53, 0x4d, 0xc5, 0xb1, 0x49, 0x8b, 0x27, 0xa2, 0x52, 0x90, 0x53, 0x37, 0xf8, 0x28, 0x16, 0x54, + 0xf4, 0x38, 0x12, 0xb2, 0xa5, 0x93, 0x84, 0xec, 0x78, 0x76, 0xb8, 0xa2, 0x2d, 0x98, 0x35, 0x2d, + 0xa3, 0x6d, 0x11, 0xdb, 0x5e, 0x22, 0x4a, 0x4b, 0x53, 0x75, 0xe2, 0xf9, 0xc7, 0xed, 0x88, 0xce, + 0x1f, 0x1d, 0x56, 0x66, 0x37, 0xd2, 0x59, 0x70, 0x96, 0xac, 0xfc, 0xb4, 0x00, 0x93, 0xf1, 0x0a, + 0x98, 0xd1, 0xa4, 0x4a, 0x27, 0x6a, 0x52, 0xaf, 0x86, 0x36, 0x83, 0xdb, 0xc1, 0xfb, 0xab, 0x9f, + 0xb2, 0x21, 0x16, 0x60, 0x42, 0x64, 0x03, 0x8f, 0x28, 0xda, 0x74, 0x7f, 0xf5, 0xb7, 0xa2, 0x64, + 0x1c, 0xe7, 0x67, 0xad, 0x67, 0xd0, 0x51, 0x7a, 0x20, 0x85, 0x68, 0xeb, 0xb9, 0x10, 0x67, 0xc0, + 0x49, 0x19, 0xb4, 0x0a, 0xd3, 0x8e, 0x9e, 0x84, 0x72, 0xa3, 0xf1, 0xbc, 0x80, 0x9a, 0xde, 0x4a, + 0xb2, 0xe0, 0x34, 0x39, 0xb4, 0x13, 0xe9, 0x46, 0x87, 0x79, 0x86, 0xbd, 0xd1, 0xf7, 0xde, 0xe9, + 0xbb, 0x1d, 0x45, 0x77, 0x60, 0xcc, 0xe2, 0xdf, 0x1d, 0x9e, 0xc1, 0x6e, 0xef, 0xfe, 0x92, 0x10, + 0x1b, 0xc3, 0x61, 0x22, 0x8e, 0xf2, 0xa6, 0xb4, 0xdb, 0xa5, 0x7e, 0xdb, 0x6d, 0xf9, 0xff, 0xa5, + 0x70, 0x11, 0xf2, 0x5b, 0xe0, 0x5e, 0xa7, 0x4c, 0x09, 0x89, 0x50, 0x77, 0x64, 0xa4, 0x77, 0xbf, + 0xb7, 0x06, 0xea, 0x7e, 0x83, 0xe2, 0xd9, 0xbb, 0xfd, 0xfd, 0x5c, 0x82, 0x99, 0x7b, 0x8d, 0xfb, + 0x96, 0xe1, 0x98, 0x9e, 0x39, 0xeb, 0xa6, 0xeb, 0xd7, 0xb7, 0xa1, 0x60, 0x39, 0x9a, 0x37, 0x8f, + 0xd7, 0xbc, 0x79, 0x60, 0x47, 0x63, 0xf3, 0x98, 0x8e, 0x49, 0xb9, 0x93, 0x60, 0x02, 0x68, 0x0d, + 0x86, 0x2d, 0x45, 0x6f, 0x13, 0xaf, 0xac, 0x5e, 0xea, 0x61, 0xfd, 0xf2, 0x12, 0x66, 0xec, 0xa1, + 0xe6, 0x8d, 0x4b, 0x63, 0x81, 0x22, 0xff, 0xbd, 0x04, 0x13, 0x0f, 0x36, 0x37, 0x37, 0x96, 0x75, + 0xbe, 0xa3, 0xf9, 0x79, 0xfa, 0x45, 0x28, 0x98, 0x0a, 0xdd, 0x8d, 0x57, 0x7a, 0x46, 0xc3, 0x9c, + 0x82, 0x3e, 0x80, 0x22, 0xcb, 0x24, 0x44, 0x6f, 0xf5, 0xd9, 0x6a, 0x0b, 0xf8, 0xba, 0x2b, 0x14, + 0x74, 0x88, 0x62, 0x00, 0x7b, 0x70, 0xf2, 0x1e, 0x9c, 0x0b, 0x99, 0xc3, 0xfc, 0xc1, 0x8f, 0x81, + 0x51, 0x03, 0x86, 0x98, 0x66, 0xef, 0x94, 0xb7, 0xd7, 0x61, 0x66, 0x6c, 0x4a, 0x41, 0xa7, 0xc3, + 0x7e, 0xd9, 0xd8, 0xc5, 0x92, 0x57, 0x61, 0x8c, 0x5f, 0x22, 0x18, 0x16, 0xe5, 0x6e, 0x41, 0x17, + 0x20, 0xdf, 0x51, 0x75, 0x51, 0x67, 0x47, 0x85, 0x4c, 0x9e, 0xd5, 0x08, 0x36, 0xce, 0xc9, 0xca, + 0x81, 0xc8, 0x3c, 0x01, 0x59, 0x39, 0xc0, 0x6c, 0x5c, 0xbe, 0x0f, 0x45, 0xe1, 0xee, 0x30, 0x50, + 0xfe, 0x78, 0xa0, 0x7c, 0x0a, 0xd0, 0x3a, 0x14, 0x97, 0x37, 0xea, 0x9a, 0xe1, 0x76, 0x5d, 0x4d, + 0xb5, 0x65, 0xc5, 0xd7, 0x62, 0x71, 0x79, 0x09, 0x63, 0x4e, 0x41, 0x32, 0x0c, 0x93, 0x83, 0x26, + 0x31, 0x29, 0x8f, 0x88, 0x91, 0x3a, 0xb0, 0x55, 0xbe, 0xcb, 0x47, 0xb0, 0xa0, 0xc8, 0xff, 0x90, + 0x83, 0xa2, 0x70, 0xc7, 0x29, 0x7c, 0x85, 0xad, 0x44, 0xbe, 0xc2, 0x5e, 0xef, 0x2f, 0x34, 0x32, + 0x3f, 0xc1, 0x36, 0x63, 0x9f, 0x60, 0x57, 0xfb, 0xc4, 0x3b, 0xfe, 0xfb, 0xeb, 0x7f, 0x24, 0x18, + 0x8f, 0x06, 0x25, 0xba, 0x09, 0xa3, 0xac, 0xe0, 0xa8, 0x4d, 0xb2, 0x16, 0xf4, 0xb9, 0xfe, 0x21, + 0x4c, 0x23, 0x20, 0xe1, 0x30, 0x1f, 0x6a, 0xfb, 0x62, 0x2c, 0x8e, 0xc4, 0xa4, 0xb3, 0x5d, 0xea, + 0x50, 0x55, 0xab, 0xba, 0x17, 0x63, 0xd5, 0x65, 0x9d, 0xae, 0x5b, 0x0d, 0x6a, 0xa9, 0x7a, 0x3b, + 0xa1, 0x88, 0x07, 0x65, 0x18, 0x59, 0xfe, 0x3f, 0x09, 0x46, 0x85, 0xc9, 0xa7, 0xf0, 0x55, 0xf1, + 0xc7, 0xd1, 0xaf, 0x8a, 0x4b, 0x7d, 0x6e, 0xf0, 0xf4, 0x4f, 0x8a, 0xff, 0x08, 0x4c, 0x67, 0x5b, + 0x9a, 0x45, 0xf5, 0xae, 0x61, 0xd3, 0x78, 0x54, 0xb3, 0xcd, 0x88, 0x39, 0x05, 0x39, 0x30, 0xa9, + 0xc6, 0x72, 0x80, 0x70, 0x6d, 0xad, 0x3f, 0x4b, 0x7c, 0xb1, 0x7a, 0x59, 0xc0, 0x4f, 0xc6, 0x29, + 0x38, 0xa1, 0x42, 0x26, 0x90, 0xe0, 0x42, 0x0f, 0xa1, 0xb0, 0x4b, 0xa9, 0x99, 0x72, 0x5e, 0xdd, + 0x23, 0xf3, 0x04, 0x26, 0x94, 0xf8, 0xec, 0x36, 0x37, 0x37, 0x30, 0x87, 0x92, 0x7f, 0x15, 0xf8, + 0xa3, 0xe1, 0xc6, 0xb8, 0x9f, 0x4f, 0xa5, 0x93, 0xe4, 0xd3, 0xd1, 0xb4, 0x5c, 0x8a, 0x1e, 0x40, + 0x9e, 0x6a, 0xfd, 0x7e, 0x16, 0x0a, 0xc4, 0xcd, 0x95, 0x46, 0x90, 0x90, 0x36, 0x57, 0x1a, 0x98, + 0x41, 0xa0, 0x75, 0x18, 0x62, 0xd5, 0x87, 0x6d, 0xc1, 0x7c, 0xff, 0x5b, 0x9a, 0xcd, 0x3f, 0x08, + 0x08, 0xf6, 0xcb, 0xc6, 0x2e, 0x8e, 0xfc, 0x09, 0x8c, 0x45, 0xf6, 0x29, 0xfa, 0x18, 0xce, 0x6a, + 0x86, 0xd2, 0xaa, 0x2b, 0x9a, 0xa2, 0x37, 0x89, 0x77, 0x39, 0x70, 0x29, 0xed, 0x0b, 0x63, 0x25, + 0xc4, 0x27, 0x76, 0xb9, 0x7f, 0x9d, 0x1a, 0xa6, 0xe1, 0x08, 0xa2, 0xac, 0x00, 0x04, 0x73, 0x44, + 0x15, 0x18, 0x62, 0x71, 0xe6, 0xd6, 0x93, 0x91, 0xfa, 0x08, 0xb3, 0x90, 0x85, 0x9f, 0x8d, 0xdd, + 0x71, 0x74, 0x03, 0xc0, 0x26, 0x4d, 0x8b, 0x50, 0x9e, 0x0c, 0x72, 0xd1, 0x4b, 0xe5, 0x86, 0x4f, + 0xc1, 0x21, 0x2e, 0xf9, 0x47, 0x12, 0x8c, 0xad, 0x11, 0xfa, 0xa9, 0x61, 0xed, 0x6d, 0xf0, 0xc7, + 0x00, 0xa7, 0x90, 0x6c, 0x71, 0x24, 0xd9, 0xbe, 0xd1, 0x63, 0x65, 0x22, 0xd6, 0x65, 0xa5, 0x5c, + 0xf9, 0x4b, 0x09, 0x66, 0x23, 0x9c, 0x77, 0x83, 0xad, 0xbb, 0x05, 0x43, 0xa6, 0x61, 0x51, 0xaf, + 0x10, 0x0f, 0xa4, 0x90, 0xa5, 0xb1, 0x50, 0x29, 0x66, 0x30, 0xd8, 0x45, 0x43, 0x2b, 0x90, 0xa3, + 0x86, 0x08, 0xd5, 0xc1, 0x30, 0x09, 0xb1, 0xea, 0x20, 0x30, 0x73, 0x9b, 0x06, 0xce, 0x51, 0x83, + 0x2d, 0x44, 0x39, 0xc2, 0x15, 0x4e, 0x3e, 0x2f, 0x68, 0x06, 0x18, 0x0a, 0x3b, 0x96, 0xd1, 0x39, + 0xf1, 0x1c, 0xfc, 0x85, 0xb8, 0x67, 0x19, 0x1d, 0xcc, 0xb1, 0xe4, 0xaf, 0x24, 0x98, 0x8a, 0x70, + 0x9e, 0x42, 0xe2, 0x7f, 0x18, 0x4d, 0xfc, 0x57, 0x07, 0x99, 0x48, 0x46, 0xfa, 0xff, 0x2a, 0x17, + 0x9b, 0x06, 0x9b, 0x30, 0xda, 0x81, 0x51, 0xd3, 0x68, 0x35, 0x9e, 0xc3, 0x75, 0xe0, 0x04, 0xab, + 0x9b, 0x1b, 0x01, 0x16, 0x0e, 0x03, 0xa3, 0x03, 0x98, 0xd2, 0x95, 0x0e, 0xb1, 0x4d, 0xa5, 0x49, + 0x1a, 0xcf, 0xe1, 0x80, 0xe4, 0x25, 0x7e, 0xdf, 0x10, 0x47, 0xc4, 0x49, 0x25, 0x68, 0x15, 0x8a, + 0xaa, 0xc9, 0xfb, 0x38, 0xd1, 0xbb, 0xf4, 0xac, 0xa2, 0x6e, 0xd7, 0xe7, 0xe6, 0x73, 0xf1, 0x03, + 0x7b, 0x18, 0xf2, 0x7f, 0xc6, 0xa3, 0x81, 0xc5, 0x1f, 0xba, 0x0f, 0x25, 0xfe, 0xac, 0xa6, 0x69, + 0x68, 0xde, 0xcd, 0x00, 0x5b, 0xd9, 0x0d, 0x31, 0xf6, 0xec, 0xb0, 0x72, 0x3e, 0xe5, 0xd0, 0xd7, + 0x23, 0x63, 0x5f, 0x18, 0xad, 0x41, 0xc1, 0xfc, 0x21, 0x1d, 0x0c, 0x2f, 0x72, 0xbc, 0x6d, 0xe1, + 0x38, 0xf2, 0x5f, 0xe7, 0x63, 0xe6, 0xf2, 0x52, 0xf7, 0xe4, 0xb9, 0xad, 0xba, 0xdf, 0x31, 0x65, + 0xae, 0xfc, 0x36, 0x14, 0x45, 0x85, 0x17, 0xc1, 0xfc, 0xf6, 0x20, 0xc1, 0x1c, 0xae, 0x62, 0xfe, + 0x07, 0x8b, 0x37, 0xe8, 0x01, 0xa3, 0x8f, 0x60, 0x98, 0xb8, 0x2a, 0xdc, 0xda, 0x78, 0x6b, 0x10, + 0x15, 0x41, 0x5e, 0x0d, 0x1a, 0x55, 0x31, 0x26, 0x50, 0xd1, 0x7b, 0xcc, 0x5f, 0x8c, 0x97, 0x7d, + 0x04, 0xda, 0xe5, 0x02, 0x2f, 0x57, 0x17, 0xdc, 0x69, 0xfb, 0xc3, 0xcf, 0x0e, 0x2b, 0x10, 0xfc, + 0xc4, 0x61, 0x09, 0xf9, 0x27, 0x12, 0x4c, 0x71, 0x0f, 0x35, 0x1d, 0x4b, 0xa5, 0xdd, 0x53, 0x2b, + 0x4c, 0x8f, 0x22, 0x85, 0xe9, 0xad, 0x1e, 0x6e, 0x49, 0x58, 0x98, 0x59, 0x9c, 0xbe, 0x96, 0xe0, + 0xa5, 0x04, 0xf7, 0x29, 0xe4, 0xc5, 0xad, 0x68, 0x5e, 0x7c, 0x63, 0xd0, 0x09, 0x65, 0xbd, 0x91, + 0x18, 0x4b, 0x99, 0x0e, 0xdf, 0x29, 0x37, 0x00, 0x4c, 0x4b, 0xdd, 0x57, 0x35, 0xd2, 0x16, 0x97, + 0xe0, 0xa5, 0xd0, 0xb3, 0x36, 0x9f, 0x82, 0x43, 0x5c, 0xc8, 0x86, 0x99, 0x16, 0xd9, 0x51, 0x1c, + 0x8d, 0x2e, 0xb4, 0x5a, 0x8b, 0x8a, 0xa9, 0x6c, 0xab, 0x9a, 0x4a, 0x55, 0x71, 0x5c, 0x30, 0x52, + 0xbf, 0xe3, 0x5e, 0x4e, 0xa7, 0x71, 0x3c, 0x3b, 0xac, 0x5c, 0x48, 0xbb, 0x1d, 0xf2, 0x58, 0xba, + 0x38, 0x03, 0x1a, 0x75, 0xa1, 0x6c, 0x91, 0x4f, 0x1c, 0xd5, 0x22, 0xad, 0x25, 0xcb, 0x30, 0x23, + 0x6a, 0xf3, 0x5c, 0xed, 0x1f, 0x1e, 0x1d, 0x56, 0xca, 0x38, 0x83, 0xa7, 0xb7, 0xe2, 0x4c, 0x78, + 0xf4, 0x04, 0xa6, 0x15, 0xf7, 0x35, 0x60, 0x44, 0xab, 0xbb, 0x4b, 0x6e, 0x1f, 0x1d, 0x56, 0xa6, + 0x17, 0x92, 0xe4, 0xde, 0x0a, 0xd3, 0x40, 0x51, 0x0d, 0x8a, 0xfb, 0xfc, 0xad, 0xa2, 0x5d, 0x1e, + 0xe2, 0xf8, 0xac, 0x10, 0x14, 0xdd, 0xe7, 0x8b, 0x0c, 0x73, 0xf8, 0x5e, 0x83, 0xef, 0x3e, 0x8f, + 0x8b, 0x7d, 0x50, 0xb2, 0x5e, 0x52, 0xec, 0x78, 0x7e, 0x62, 0x5c, 0x0a, 0xb2, 0xd6, 0x83, 0x80, + 0x84, 0xc3, 0x7c, 0xe8, 0x31, 0x8c, 0xec, 0x8a, 0x53, 0x09, 0xbb, 0x5c, 0xec, 0xab, 0x08, 0x47, + 0x4e, 0x31, 0xea, 0x53, 0x42, 0xc5, 0x88, 0x37, 0x6c, 0xe3, 0x00, 0x11, 0x5d, 0x81, 0x22, 0xff, + 0xb1, 0xbc, 0xc4, 0x8f, 0xe3, 0x4a, 0x41, 0x6e, 0x7b, 0xe0, 0x0e, 0x63, 0x8f, 0xee, 0xb1, 0x2e, + 0x6f, 0x2c, 0xf2, 0x63, 0xe1, 0x18, 0xeb, 0xf2, 0xc6, 0x22, 0xf6, 0xe8, 0xe8, 0x63, 0x28, 0xda, + 0x64, 0x45, 0xd5, 0x9d, 0x83, 0x32, 0xf4, 0x75, 0xa9, 0xdc, 0xb8, 0xcb, 0xb9, 0x63, 0x07, 0x63, + 0x81, 0x06, 0x41, 0xc7, 0x1e, 0x2c, 0xda, 0x85, 0x11, 0xcb, 0xd1, 0x17, 0xec, 0x2d, 0x9b, 0x58, + 0xe5, 0x51, 0xae, 0xa3, 0x57, 0x3a, 0xc7, 0x1e, 0x7f, 0x5c, 0x8b, 0xef, 0x21, 0x9f, 0x03, 0x07, + 0xe0, 0xe8, 0xef, 0x24, 0x40, 0xb6, 0x63, 0x9a, 0x1a, 0xe9, 0x10, 0x9d, 0x2a, 0x1a, 0x3f, 0x8b, + 0xb3, 0xcb, 0x67, 0xb9, 0xce, 0x3f, 0xea, 0x35, 0xaf, 0x84, 0x60, 0x5c, 0xb9, 0x7f, 0xe8, 0x9d, + 0x64, 0xc5, 0x29, 0x7a, 0x99, 0x6b, 0x77, 0x6c, 0xfe, 0x77, 0x79, 0xac, 0x2f, 0xd7, 0xa6, 0x9f, + 0x39, 0x06, 0xae, 0x15, 0x74, 0xec, 0xc1, 0xa2, 0x47, 0x30, 0x63, 0x11, 0xa5, 0xb5, 0xae, 0x6b, + 0x5d, 0x6c, 0x18, 0xf4, 0x9e, 0xaa, 0x11, 0xbb, 0x6b, 0x53, 0xd2, 0x29, 0x8f, 0xf3, 0x65, 0xf7, + 0xdf, 0x7e, 0xe0, 0x54, 0x2e, 0x9c, 0x21, 0x8d, 0x3a, 0x50, 0xf1, 0x52, 0x06, 0xdb, 0x4f, 0x7e, + 0xce, 0xba, 0x6b, 0x37, 0x15, 0xcd, 0xbd, 0x07, 0x98, 0xe0, 0x0a, 0x5e, 0x3b, 0x3a, 0xac, 0x54, + 0x96, 0x8e, 0x67, 0xc5, 0xbd, 0xb0, 0xd0, 0x07, 0x50, 0x56, 0xb2, 0xf4, 0x4c, 0x72, 0x3d, 0xaf, + 0xb0, 0x3c, 0x94, 0xa9, 0x20, 0x53, 0x1a, 0x51, 0x98, 0x54, 0xa2, 0x8f, 0x8e, 0xed, 0xf2, 0x54, + 0x5f, 0x07, 0x91, 0xb1, 0xb7, 0xca, 0xc1, 0x61, 0x44, 0x8c, 0x60, 0xe3, 0x84, 0x06, 0xf4, 0x17, + 0x80, 0x94, 0xf8, 0x3b, 0x69, 0xbb, 0x8c, 0xfa, 0x2a, 0x3f, 0x89, 0x07, 0xd6, 0x41, 0xd8, 0x25, + 0x48, 0x36, 0x4e, 0xd1, 0xc3, 0x1f, 0x6f, 0x88, 0xa3, 0xfc, 0xd3, 0x79, 0x00, 0x3b, 0xd8, 0xe3, + 0x8d, 0xc0, 0xb4, 0xe7, 0xf6, 0x78, 0x23, 0x04, 0x79, 0xfc, 0xe1, 0xe1, 0x2f, 0x72, 0x30, 0x1d, + 0x30, 0xf7, 0xfd, 0x78, 0x23, 0x45, 0xe4, 0x77, 0x8f, 0x60, 0x7b, 0x3f, 0x82, 0xfd, 0x52, 0x82, + 0xf1, 0xc0, 0x75, 0xbf, 0x79, 0x0f, 0x2a, 0x02, 0xdb, 0x32, 0x5a, 0xbc, 0xff, 0xce, 0x85, 0x27, + 0xf0, 0x5b, 0x7f, 0xab, 0xff, 0xc3, 0x5f, 0xae, 0xca, 0x5f, 0xe7, 0x61, 0x32, 0xbe, 0x1b, 0x23, + 0x97, 0xbf, 0x52, 0xcf, 0xcb, 0xdf, 0x0d, 0x38, 0xb7, 0xe3, 0x68, 0x5a, 0x97, 0xbb, 0x21, 0x74, + 0x03, 0xec, 0x5e, 0xde, 0xbc, 0x22, 0x24, 0xcf, 0xdd, 0x4b, 0xe1, 0xc1, 0xa9, 0x92, 0x19, 0x17, + 0xd9, 0xf9, 0x13, 0x5d, 0x64, 0x27, 0xee, 0x55, 0x0b, 0x03, 0xdc, 0xab, 0xa6, 0x5e, 0x4a, 0x0f, + 0x9d, 0xe0, 0x52, 0xfa, 0x24, 0xb7, 0xc8, 0x29, 0x49, 0xac, 0xe7, 0xa3, 0xc6, 0x57, 0x60, 0x4e, + 0x88, 0x51, 0x7e, 0xc1, 0xab, 0x53, 0xcb, 0xd0, 0x34, 0x62, 0x2d, 0x39, 0x9d, 0x4e, 0x57, 0x7e, + 0x17, 0xc6, 0xa3, 0x4f, 0x17, 0xdc, 0x95, 0x76, 0x5f, 0x4f, 0x88, 0x2b, 0xb4, 0xd0, 0x4a, 0xbb, + 0xe3, 0xd8, 0xe7, 0x90, 0xff, 0x46, 0x82, 0x99, 0xf4, 0x27, 0x8a, 0x48, 0x83, 0xf1, 0x8e, 0x72, + 0x10, 0x7e, 0x36, 0x2a, 0x9d, 0xf0, 0x70, 0x83, 0xdf, 0x59, 0xaf, 0x46, 0xb0, 0x70, 0x0c, 0x5b, + 0xfe, 0x5e, 0x82, 0xd9, 0x8c, 0xdb, 0xe2, 0xd3, 0xb5, 0x04, 0x7d, 0x08, 0xa5, 0x8e, 0x72, 0xd0, + 0x70, 0xac, 0x36, 0x39, 0xf1, 0x71, 0x0e, 0xcf, 0x18, 0xab, 0x02, 0x05, 0xfb, 0x78, 0xf2, 0xe7, + 0x12, 0x94, 0xb3, 0x1a, 0x6b, 0x74, 0x33, 0x72, 0xaf, 0xfd, 0x6a, 0xec, 0x5e, 0x7b, 0x2a, 0x21, + 0xf7, 0x82, 0x6e, 0xb5, 0xff, 0x4b, 0x82, 0x99, 0xf4, 0x0f, 0x0c, 0xf4, 0x66, 0xc4, 0xc2, 0x4a, + 0xcc, 0xc2, 0x89, 0x98, 0x94, 0xb0, 0xef, 0x23, 0x18, 0x17, 0x9f, 0x21, 0x02, 0x46, 0x78, 0x55, + 0x4e, 0xcb, 0x95, 0x02, 0xc2, 0x6b, 0xbb, 0xf9, 0x7a, 0x45, 0xc7, 0x70, 0x0c, 0x4d, 0xfe, 0xdb, + 0x1c, 0x0c, 0x35, 0x9a, 0x8a, 0x46, 0x4e, 0xa1, 0xcd, 0x7a, 0x3f, 0xd2, 0x66, 0xf5, 0xfa, 0x17, + 0x0f, 0x6e, 0x55, 0x66, 0x87, 0x85, 0x63, 0x1d, 0xd6, 0xeb, 0x7d, 0xa1, 0x1d, 0xdf, 0x5c, 0xfd, + 0x01, 0x8c, 0xf8, 0x4a, 0x07, 0xcb, 0xf9, 0xf2, 0xbf, 0xe7, 0x60, 0x34, 0xa4, 0x62, 0xc0, 0x8a, + 0xb1, 0x13, 0xa9, 0xb4, 0xfd, 0xfc, 0x63, 0x5d, 0x48, 0x57, 0xd5, 0xab, 0xad, 0xee, 0x13, 0xc5, + 0xe0, 0x51, 0x5a, 0xb2, 0xe4, 0xbe, 0x0b, 0xe3, 0x94, 0xff, 0xe3, 0x99, 0x7f, 0x08, 0x9a, 0xe7, + 0xb1, 0xe8, 0x3f, 0x6c, 0xdd, 0x8c, 0x50, 0x71, 0x8c, 0x7b, 0xee, 0x0e, 0x8c, 0x45, 0x94, 0x0d, + 0xf4, 0xc2, 0xf0, 0x7f, 0x25, 0x78, 0xb5, 0xe7, 0x27, 0x2a, 0xaa, 0x47, 0x36, 0x49, 0x35, 0xb6, + 0x49, 0xe6, 0xb3, 0x01, 0x5e, 0xdc, 0x4b, 0x95, 0xfa, 0xb5, 0xa7, 0xdf, 0xcd, 0x9f, 0xf9, 0xe6, + 0xbb, 0xf9, 0x33, 0xdf, 0x7e, 0x37, 0x7f, 0xe6, 0xaf, 0x8e, 0xe6, 0xa5, 0xa7, 0x47, 0xf3, 0xd2, + 0x37, 0x47, 0xf3, 0xd2, 0xb7, 0x47, 0xf3, 0xd2, 0xcf, 0x8e, 0xe6, 0xa5, 0x7f, 0xfc, 0x7e, 0xfe, + 0xcc, 0x87, 0x45, 0x01, 0xf7, 0xeb, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb1, 0xb3, 0xc8, 0xe2, 0x54, + 0x3c, 0x00, 0x00, } diff --git a/staging/src/k8s.io/api/extensions/v1beta1/generated.proto b/staging/src/k8s.io/api/extensions/v1beta1/generated.proto index a6fe9dd72b6..8308786d147 100644 --- a/staging/src/k8s.io/api/extensions/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/extensions/v1beta1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -32,11 +32,10 @@ import "k8s.io/apimachinery/pkg/util/intstr/generated.proto"; // Package-wide variables from generator "generated". option go_package = "v1beta1"; -// An APIVersion represents a single concrete version of an object model. -message APIVersion { - // Name of this version (e.g. 'v1'). - // +optional - optional string name = 1; +// AllowedFlexVolume represents a single Flexvolume that is allowed to be used. +message AllowedFlexVolume { + // Driver is the name of the Flexvolume driver. + optional string driver = 1; } // defines the host volume conditions that will be enabled by a policy @@ -100,6 +99,27 @@ message DaemonSet { optional DaemonSetStatus status = 3; } +// DaemonSetCondition describes the state of a DaemonSet at a certain point. +message DaemonSetCondition { + // Type of DaemonSet condition. + optional string type = 1; + + // Status of the condition, one of True, False, Unknown. + optional string status = 2; + + // Last time the condition transitioned from one status to another. + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.Time lastTransitionTime = 3; + + // The reason for the condition's last transition. + // +optional + optional string reason = 4; + + // A human readable message indicating details about the transition. + // +optional + optional string message = 5; +} + // DaemonSetList is a collection of daemon sets. message DaemonSetList { // Standard list metadata. @@ -197,6 +217,12 @@ message DaemonSetStatus { // create the name for the newest ControllerRevision. // +optional optional int32 collisionCount = 9; + + // Represents the latest available observations of a DaemonSet's current state. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + repeated DaemonSetCondition conditions = 10; } message DaemonSetUpdateStrategy { @@ -441,6 +467,7 @@ message IDRange { optional int64 max = 2; } +// DEPRECATED 1.9 - This group version of IPBlock is deprecated by networking/v1/IPBlock. // IPBlock describes a particular CIDR (Ex. "192.168.1.1/24") that is allowed to the pods // matched by a NetworkPolicySpec's podSelector. The except entry describes CIDRs that should // not be included within this rule. @@ -582,6 +609,7 @@ message IngressTLS { optional string secretName = 2; } +// DEPRECATED 1.9 - This group version of NetworkPolicy is deprecated by networking/v1/NetworkPolicy. // NetworkPolicy describes what network traffic is allowed for a set of Pods message NetworkPolicy { // Standard object's metadata. @@ -594,6 +622,7 @@ message NetworkPolicy { optional NetworkPolicySpec spec = 2; } +// DEPRECATED 1.9 - This group version of NetworkPolicyEgressRule is deprecated by networking/v1/NetworkPolicyEgressRule. // NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods // matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. // This type is beta-level in 1.8 @@ -615,6 +644,7 @@ message NetworkPolicyEgressRule { repeated NetworkPolicyPeer to = 2; } +// DEPRECATED 1.9 - This group version of NetworkPolicyIngressRule is deprecated by networking/v1/NetworkPolicyIngressRule. // This NetworkPolicyIngressRule matches traffic if and only if the traffic matches both ports AND from. message NetworkPolicyIngressRule { // List of ports which should be made accessible on the pods selected for this rule. @@ -634,6 +664,7 @@ message NetworkPolicyIngressRule { repeated NetworkPolicyPeer from = 2; } +// DEPRECATED 1.9 - This group version of NetworkPolicyList is deprecated by networking/v1/NetworkPolicyList. // Network Policy List is a list of NetworkPolicy objects. message NetworkPolicyList { // Standard list metadata. @@ -645,6 +676,7 @@ message NetworkPolicyList { repeated NetworkPolicy items = 2; } +// DEPRECATED 1.9 - This group version of NetworkPolicyPeer is deprecated by networking/v1/NetworkPolicyPeer. message NetworkPolicyPeer { // This is a label selector which selects Pods in this namespace. // This field follows standard label selector semantics. @@ -664,6 +696,7 @@ message NetworkPolicyPeer { optional IPBlock ipBlock = 3; } +// DEPRECATED 1.9 - This group version of NetworkPolicyPort is deprecated by networking/v1/NetworkPolicyPort. message NetworkPolicyPort { // Optional. The protocol (TCP or UDP) which traffic must match. // If not specified, this field defaults to TCP. @@ -679,6 +712,7 @@ message NetworkPolicyPort { optional k8s.io.apimachinery.pkg.util.intstr.IntOrString port = 2; } +// DEPRECATED 1.9 - This group version of NetworkPolicySpec is deprecated by networking/v1/NetworkPolicySpec. message NetworkPolicySpec { // Selects the pods to which this NetworkPolicy object applies. The array of ingress rules // is applied to any pods selected by this field. Multiple network policies can select the @@ -752,8 +786,9 @@ message PodSecurityPolicySpec { optional bool privileged = 1; // DefaultAddCapabilities is the default set of capabilities that will be added to the container - // unless the pod spec specifically drops the capability. You may not list a capabiility in both - // DefaultAddCapabilities and RequiredDropCapabilities. + // unless the pod spec specifically drops the capability. You may not list a capability in both + // DefaultAddCapabilities and RequiredDropCapabilities. Capabilities added here are implicitly + // allowed, and need not be included in the AllowedCapabilities list. // +optional repeated string defaultAddCapabilities = 2; @@ -822,6 +857,12 @@ message PodSecurityPolicySpec { // is a white list of allowed host paths. Empty indicates that all host paths may be used. // +optional repeated AllowedHostPath allowedHostPaths = 17; + + // AllowedFlexVolumes is a whitelist of allowed Flexvolumes. Empty or nil indicates that all + // Flexvolumes may be used. This parameter is effective only when the usage of the Flexvolumes + // is allowed in the "Volumes" field. + // +optional + repeated AllowedFlexVolume allowedFlexVolumes = 18; } // DEPRECATED - This group version of ReplicaSet is deprecated by apps/v1beta2/ReplicaSet. See the release notes for @@ -1016,7 +1057,7 @@ message SELinuxStrategyOptions { optional string rule = 1; // seLinuxOptions required to run as; required for MustRunAs - // More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md + // More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ // +optional optional k8s.io.api.core.v1.SELinuxOptions seLinuxOptions = 2; } @@ -1074,51 +1115,3 @@ message SupplementalGroupsStrategyOptions { repeated IDRange ranges = 2; } -// A ThirdPartyResource is a generic representation of a resource, it is used by add-ons and plugins to add new resource -// types to the API. It consists of one or more Versions of the api. -message ThirdPartyResource { - // Standard object metadata - // +optional - optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - - // Description is the description of this object. - // +optional - optional string description = 2; - - // Versions are versions for this third party object - // +optional - repeated APIVersion versions = 3; -} - -// An internal object, used for versioned storage in etcd. Not exposed to the end user. -message ThirdPartyResourceData { - // Standard object metadata. - // +optional - optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - - // Data is the raw JSON data for this data. - // +optional - optional bytes data = 2; -} - -// ThirdPartyResrouceDataList is a list of ThirdPartyResourceData. -message ThirdPartyResourceDataList { - // Standard list metadata - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata - // +optional - optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; - - // Items is the list of ThirdpartyResourceData. - repeated ThirdPartyResourceData items = 2; -} - -// ThirdPartyResourceList is a list of ThirdPartyResources. -message ThirdPartyResourceList { - // Standard list metadata. - // +optional - optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; - - // Items is the list of ThirdPartyResources. - repeated ThirdPartyResource items = 2; -} - diff --git a/staging/src/k8s.io/api/extensions/v1beta1/register.go b/staging/src/k8s.io/api/extensions/v1beta1/register.go index 626701cf013..7625f678137 100644 --- a/staging/src/k8s.io/api/extensions/v1beta1/register.go +++ b/staging/src/k8s.io/api/extensions/v1beta1/register.go @@ -41,7 +41,7 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Deployment{}, @@ -49,12 +49,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &DeploymentRollback{}, &ReplicationControllerDummy{}, &Scale{}, - &ThirdPartyResource{}, - &ThirdPartyResourceList{}, &DaemonSetList{}, &DaemonSet{}, - &ThirdPartyResourceData{}, - &ThirdPartyResourceDataList{}, &Ingress{}, &IngressList{}, &ReplicaSet{}, diff --git a/staging/src/k8s.io/api/extensions/v1beta1/types.go b/staging/src/k8s.io/api/extensions/v1beta1/types.go index 4993d6398e4..c3d9f72d734 100644 --- a/staging/src/k8s.io/api/extensions/v1beta1/types.go +++ b/staging/src/k8s.io/api/extensions/v1beta1/types.go @@ -100,63 +100,6 @@ type CustomMetricCurrentStatusList struct { Items []CustomMetricCurrentStatus `json:"items" protobuf:"bytes,1,rep,name=items"` } -// +genclient -// +genclient:nonNamespaced -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// A ThirdPartyResource is a generic representation of a resource, it is used by add-ons and plugins to add new resource -// types to the API. It consists of one or more Versions of the api. -type ThirdPartyResource struct { - metav1.TypeMeta `json:",inline"` - - // Standard object metadata - // +optional - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // Description is the description of this object. - // +optional - Description string `json:"description,omitempty" protobuf:"bytes,2,opt,name=description"` - - // Versions are versions for this third party object - // +optional - Versions []APIVersion `json:"versions,omitempty" protobuf:"bytes,3,rep,name=versions"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ThirdPartyResourceList is a list of ThirdPartyResources. -type ThirdPartyResourceList 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 ThirdPartyResources. - Items []ThirdPartyResource `json:"items" protobuf:"bytes,2,rep,name=items"` -} - -// An APIVersion represents a single concrete version of an object model. -type APIVersion struct { - // Name of this version (e.g. 'v1'). - // +optional - Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// An internal object, used for versioned storage in etcd. Not exposed to the end user. -type ThirdPartyResourceData struct { - metav1.TypeMeta `json:",inline"` - // Standard object metadata. - // +optional - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // Data is the raw JSON data for this data. - // +optional - Data []byte `json:"data,omitempty" protobuf:"bytes,2,opt,name=data"` -} - // +genclient // +genclient:method=GetScale,verb=get,subresource=scale,result=Scale // +genclient:method=UpdateScale,verb=update,subresource=scale,input=Scale,result=Scale @@ -532,6 +475,33 @@ type DaemonSetStatus struct { // create the name for the newest ControllerRevision. // +optional CollisionCount *int32 `json:"collisionCount,omitempty" protobuf:"varint,9,opt,name=collisionCount"` + + // Represents the latest available observations of a DaemonSet's current state. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + Conditions []DaemonSetCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,10,rep,name=conditions"` +} + +type DaemonSetConditionType string + +// TODO: Add valid condition types of a DaemonSet. + +// DaemonSetCondition describes the state of a DaemonSet at a certain point. +type DaemonSetCondition struct { + // Type of DaemonSet condition. + Type DaemonSetConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=DaemonSetConditionType"` + // Status of the condition, one of True, False, Unknown. + Status v1.ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=k8s.io/api/core/v1.ConditionStatus"` + // Last time the condition transitioned from one status to another. + // +optional + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,3,opt,name=lastTransitionTime"` + // The reason for the condition's last transition. + // +optional + Reason string `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"` + // A human readable message indicating details about the transition. + // +optional + Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"` } // +genclient @@ -588,20 +558,6 @@ type DaemonSetList struct { Items []DaemonSet `json:"items" protobuf:"bytes,2,rep,name=items"` } -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ThirdPartyResrouceDataList is a list of ThirdPartyResourceData. -type ThirdPartyResourceDataList struct { - metav1.TypeMeta `json:",inline"` - // Standard list metadata - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata - // +optional - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // Items is the list of ThirdpartyResourceData. - Items []ThirdPartyResourceData `json:"items" protobuf:"bytes,2,rep,name=items"` -} - // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -926,8 +882,9 @@ type PodSecurityPolicySpec struct { // +optional Privileged bool `json:"privileged,omitempty" protobuf:"varint,1,opt,name=privileged"` // DefaultAddCapabilities is the default set of capabilities that will be added to the container - // unless the pod spec specifically drops the capability. You may not list a capabiility in both - // DefaultAddCapabilities and RequiredDropCapabilities. + // unless the pod spec specifically drops the capability. You may not list a capability in both + // DefaultAddCapabilities and RequiredDropCapabilities. Capabilities added here are implicitly + // allowed, and need not be included in the AllowedCapabilities list. // +optional DefaultAddCapabilities []v1.Capability `json:"defaultAddCapabilities,omitempty" protobuf:"bytes,2,rep,name=defaultAddCapabilities,casttype=k8s.io/api/core/v1.Capability"` // RequiredDropCapabilities are the capabilities that will be dropped from the container. These @@ -981,6 +938,11 @@ type PodSecurityPolicySpec struct { // is a white list of allowed host paths. Empty indicates that all host paths may be used. // +optional AllowedHostPaths []AllowedHostPath `json:"allowedHostPaths,omitempty" protobuf:"bytes,17,rep,name=allowedHostPaths"` + // AllowedFlexVolumes is a whitelist of allowed Flexvolumes. Empty or nil indicates that all + // Flexvolumes may be used. This parameter is effective only when the usage of the Flexvolumes + // is allowed in the "Volumes" field. + // +optional + AllowedFlexVolumes []AllowedFlexVolume `json:"allowedFlexVolumes,omitempty" protobuf:"bytes,18,rep,name=allowedFlexVolumes"` } // defines the host volume conditions that will be enabled by a policy @@ -1024,6 +986,12 @@ var ( All FSType = "*" ) +// AllowedFlexVolume represents a single Flexvolume that is allowed to be used. +type AllowedFlexVolume struct { + // Driver is the name of the Flexvolume driver. + Driver string `json:"driver" protobuf:"bytes,1,opt,name=driver"` +} + // Host Port Range defines a range of host ports that will be enabled by a policy // for pods to use. It requires both the start and end to be defined. type HostPortRange struct { @@ -1038,7 +1006,7 @@ type SELinuxStrategyOptions struct { // type is the strategy that will dictate the allowable labels that may be set. Rule SELinuxStrategy `json:"rule" protobuf:"bytes,1,opt,name=rule,casttype=SELinuxStrategy"` // seLinuxOptions required to run as; required for MustRunAs - // More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md + // More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ // +optional SELinuxOptions *v1.SELinuxOptions `json:"seLinuxOptions,omitempty" protobuf:"bytes,2,opt,name=seLinuxOptions"` } @@ -1144,6 +1112,7 @@ type PodSecurityPolicyList struct { // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// DEPRECATED 1.9 - This group version of NetworkPolicy is deprecated by networking/v1/NetworkPolicy. // NetworkPolicy describes what network traffic is allowed for a set of Pods type NetworkPolicy struct { metav1.TypeMeta `json:",inline"` @@ -1157,6 +1126,7 @@ type NetworkPolicy struct { Spec NetworkPolicySpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` } +// DEPRECATED 1.9 - This group version of PolicyType is deprecated by networking/v1/PolicyType. // Policy Type string describes the NetworkPolicy type // This type is beta-level in 1.8 type PolicyType string @@ -1168,6 +1138,7 @@ const ( PolicyTypeEgress PolicyType = "Egress" ) +// DEPRECATED 1.9 - This group version of NetworkPolicySpec is deprecated by networking/v1/NetworkPolicySpec. type NetworkPolicySpec struct { // Selects the pods to which this NetworkPolicy object applies. The array of ingress rules // is applied to any pods selected by this field. Multiple network policies can select the @@ -1210,6 +1181,7 @@ type NetworkPolicySpec struct { PolicyTypes []PolicyType `json:"policyTypes,omitempty" protobuf:"bytes,4,rep,name=policyTypes,casttype=PolicyType"` } +// DEPRECATED 1.9 - This group version of NetworkPolicyIngressRule is deprecated by networking/v1/NetworkPolicyIngressRule. // This NetworkPolicyIngressRule matches traffic if and only if the traffic matches both ports AND from. type NetworkPolicyIngressRule struct { // List of ports which should be made accessible on the pods selected for this rule. @@ -1229,6 +1201,7 @@ type NetworkPolicyIngressRule struct { From []NetworkPolicyPeer `json:"from,omitempty" protobuf:"bytes,2,rep,name=from"` } +// DEPRECATED 1.9 - This group version of NetworkPolicyEgressRule is deprecated by networking/v1/NetworkPolicyEgressRule. // NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods // matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. // This type is beta-level in 1.8 @@ -1250,6 +1223,7 @@ type NetworkPolicyEgressRule struct { To []NetworkPolicyPeer `json:"to,omitempty" protobuf:"bytes,2,rep,name=to"` } +// DEPRECATED 1.9 - This group version of NetworkPolicyPort is deprecated by networking/v1/NetworkPolicyPort. type NetworkPolicyPort struct { // Optional. The protocol (TCP or UDP) which traffic must match. // If not specified, this field defaults to TCP. @@ -1265,6 +1239,7 @@ type NetworkPolicyPort struct { Port *intstr.IntOrString `json:"port,omitempty" protobuf:"bytes,2,opt,name=port"` } +// DEPRECATED 1.9 - This group version of IPBlock is deprecated by networking/v1/IPBlock. // IPBlock describes a particular CIDR (Ex. "192.168.1.1/24") that is allowed to the pods // matched by a NetworkPolicySpec's podSelector. The except entry describes CIDRs that should // not be included within this rule. @@ -1279,6 +1254,7 @@ type IPBlock struct { Except []string `json:"except,omitempty" protobuf:"bytes,2,rep,name=except"` } +// DEPRECATED 1.9 - This group version of NetworkPolicyPeer is deprecated by networking/v1/NetworkPolicyPeer. type NetworkPolicyPeer struct { // Exactly one of the following must be specified. @@ -1302,6 +1278,7 @@ type NetworkPolicyPeer struct { // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// DEPRECATED 1.9 - This group version of NetworkPolicyList is deprecated by networking/v1/NetworkPolicyList. // Network Policy List is a list of NetworkPolicy objects. type NetworkPolicyList struct { metav1.TypeMeta `json:",inline"` diff --git a/staging/src/k8s.io/api/extensions/v1beta1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/extensions/v1beta1/types_swagger_doc_generated.go index e722f925d95..236d934fa24 100644 --- a/staging/src/k8s.io/api/extensions/v1beta1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/extensions/v1beta1/types_swagger_doc_generated.go @@ -27,13 +27,13 @@ package v1beta1 // Those methods can be generated by using hack/update-generated-swagger-docs.sh // AUTO-GENERATED FUNCTIONS START HERE -var map_APIVersion = map[string]string{ - "": "An APIVersion represents a single concrete version of an object model.", - "name": "Name of this version (e.g. 'v1').", +var map_AllowedFlexVolume = map[string]string{ + "": "AllowedFlexVolume represents a single Flexvolume that is allowed to be used.", + "driver": "Driver is the name of the Flexvolume driver.", } -func (APIVersion) SwaggerDoc() map[string]string { - return map_APIVersion +func (AllowedFlexVolume) SwaggerDoc() map[string]string { + return map_AllowedFlexVolume } var map_AllowedHostPath = map[string]string{ @@ -75,6 +75,19 @@ func (DaemonSet) SwaggerDoc() map[string]string { return map_DaemonSet } +var map_DaemonSetCondition = map[string]string{ + "": "DaemonSetCondition describes the state of a DaemonSet at a certain point.", + "type": "Type of DaemonSet condition.", + "status": "Status of the condition, one of True, False, Unknown.", + "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.", +} + +func (DaemonSetCondition) SwaggerDoc() map[string]string { + return map_DaemonSetCondition +} + var map_DaemonSetList = map[string]string{ "": "DaemonSetList is a collection of daemon sets.", "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", @@ -110,6 +123,7 @@ var map_DaemonSetStatus = map[string]string{ "numberAvailable": "The number of nodes that should be running the daemon pod and have one or more of the daemon pod running and available (ready for at least spec.minReadySeconds)", "numberUnavailable": "The number of nodes that should be running the daemon pod and have none of the daemon pod running and available (ready for at least spec.minReadySeconds)", "collisionCount": "Count of hash collisions for the DaemonSet. The DaemonSet controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ControllerRevision.", + "conditions": "Represents the latest available observations of a DaemonSet's current state.", } func (DaemonSetStatus) SwaggerDoc() map[string]string { @@ -264,7 +278,7 @@ func (IDRange) SwaggerDoc() map[string]string { } var map_IPBlock = map[string]string{ - "": "IPBlock describes a particular CIDR (Ex. \"192.168.1.1/24\") that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The except entry describes CIDRs that should not be included within this rule.", + "": "DEPRECATED 1.9 - This group version of IPBlock is deprecated by networking/v1/IPBlock. IPBlock describes a particular CIDR (Ex. \"192.168.1.1/24\") that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The except entry describes CIDRs that should not be included within this rule.", "cidr": "CIDR is a string representing the IP Block Valid examples are \"192.168.1.1/24\"", "except": "Except is a slice of CIDRs that should not be included within an IP Block Valid examples are \"192.168.1.1/24\" Except values will be rejected if they are outside the CIDR range", } @@ -352,7 +366,7 @@ func (IngressTLS) SwaggerDoc() map[string]string { } var map_NetworkPolicy = map[string]string{ - "": "NetworkPolicy describes what network traffic is allowed for a set of Pods", + "": "DEPRECATED 1.9 - This group version of NetworkPolicy is deprecated by networking/v1/NetworkPolicy. NetworkPolicy describes what network traffic is allowed for a set of Pods", "metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", "spec": "Specification of the desired behavior for this NetworkPolicy.", } @@ -362,7 +376,7 @@ func (NetworkPolicy) SwaggerDoc() map[string]string { } var map_NetworkPolicyEgressRule = map[string]string{ - "": "NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8", + "": "DEPRECATED 1.9 - This group version of NetworkPolicyEgressRule is deprecated by networking/v1/NetworkPolicyEgressRule. NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8", "ports": "List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.", "to": "List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list.", } @@ -372,7 +386,7 @@ func (NetworkPolicyEgressRule) SwaggerDoc() map[string]string { } var map_NetworkPolicyIngressRule = map[string]string{ - "": "This NetworkPolicyIngressRule matches traffic if and only if the traffic matches both ports AND from.", + "": "DEPRECATED 1.9 - This group version of NetworkPolicyIngressRule is deprecated by networking/v1/NetworkPolicyIngressRule. This NetworkPolicyIngressRule matches traffic if and only if the traffic matches both ports AND from.", "ports": "List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.", "from": "List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least on item, this rule allows traffic only if the traffic matches at least one item in the from list.", } @@ -382,7 +396,7 @@ func (NetworkPolicyIngressRule) SwaggerDoc() map[string]string { } var map_NetworkPolicyList = map[string]string{ - "": "Network Policy List is a list of NetworkPolicy objects.", + "": "DEPRECATED 1.9 - This group version of NetworkPolicyList is deprecated by networking/v1/NetworkPolicyList. Network Policy List is a list of NetworkPolicy objects.", "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", "items": "Items is a list of schema objects.", } @@ -392,6 +406,7 @@ func (NetworkPolicyList) SwaggerDoc() map[string]string { } var map_NetworkPolicyPeer = map[string]string{ + "": "DEPRECATED 1.9 - This group version of NetworkPolicyPeer is deprecated by networking/v1/NetworkPolicyPeer.", "podSelector": "This is a label selector which selects Pods in this namespace. This field follows standard label selector semantics. If present but empty, this selector selects all pods in this namespace.", "namespaceSelector": "Selects Namespaces using cluster scoped-labels. This matches all pods in all namespaces selected by this label selector. This field follows standard label selector semantics. If present but empty, this selector selects all namespaces.", "ipBlock": "IPBlock defines policy on a particular IPBlock", @@ -402,6 +417,7 @@ func (NetworkPolicyPeer) SwaggerDoc() map[string]string { } var map_NetworkPolicyPort = map[string]string{ + "": "DEPRECATED 1.9 - This group version of NetworkPolicyPort is deprecated by networking/v1/NetworkPolicyPort.", "protocol": "Optional. The protocol (TCP or UDP) which traffic must match. If not specified, this field defaults to TCP.", "port": "If specified, the port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.", } @@ -411,6 +427,7 @@ func (NetworkPolicyPort) SwaggerDoc() map[string]string { } var map_NetworkPolicySpec = map[string]string{ + "": "DEPRECATED 1.9 - This group version of NetworkPolicySpec is deprecated by networking/v1/NetworkPolicySpec.", "podSelector": "Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace.", "ingress": "List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default).", "egress": "List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8", @@ -444,7 +461,7 @@ func (PodSecurityPolicyList) SwaggerDoc() map[string]string { var map_PodSecurityPolicySpec = map[string]string{ "": "Pod Security Policy Spec defines the policy enforced.", "privileged": "privileged determines if a pod can request to be run as privileged.", - "defaultAddCapabilities": "DefaultAddCapabilities is the default set of capabilities that will be added to the container unless the pod spec specifically drops the capability. You may not list a capabiility in both DefaultAddCapabilities and RequiredDropCapabilities.", + "defaultAddCapabilities": "DefaultAddCapabilities is the default set of capabilities that will be added to the container unless the pod spec specifically drops the capability. You may not list a capability in both DefaultAddCapabilities and RequiredDropCapabilities. Capabilities added here are implicitly allowed, and need not be included in the AllowedCapabilities list.", "requiredDropCapabilities": "RequiredDropCapabilities are the capabilities that will be dropped from the container. These are required to be dropped and cannot be added.", "allowedCapabilities": "AllowedCapabilities is a list of capabilities that can be requested to add to the container. Capabilities in this field may be added at the pod author's discretion. You must not list a capability in both AllowedCapabilities and RequiredDropCapabilities.", "volumes": "volumes is a white list of allowed volume plugins. Empty indicates that all plugins may be used.", @@ -460,6 +477,7 @@ var map_PodSecurityPolicySpec = map[string]string{ "defaultAllowPrivilegeEscalation": "DefaultAllowPrivilegeEscalation controls the default setting for whether a process can gain more privileges than its parent process.", "allowPrivilegeEscalation": "AllowPrivilegeEscalation determines if a pod can request to allow privilege escalation. If unspecified, defaults to true.", "allowedHostPaths": "is a white list of allowed host paths. Empty indicates that all host paths may be used.", + "allowedFlexVolumes": "AllowedFlexVolumes is a whitelist of allowed Flexvolumes. Empty or nil indicates that all Flexvolumes may be used. This parameter is effective only when the usage of the Flexvolumes is allowed in the \"Volumes\" field.", } func (PodSecurityPolicySpec) SwaggerDoc() map[string]string { @@ -575,7 +593,7 @@ func (RunAsUserStrategyOptions) SwaggerDoc() map[string]string { var map_SELinuxStrategyOptions = map[string]string{ "": "SELinux Strategy Options defines the strategy type and any options used to create the strategy.", "rule": "type is the strategy that will dictate the allowable labels that may be set.", - "seLinuxOptions": "seLinuxOptions required to run as; required for MustRunAs More info: https://git.k8s.io/community/contributors/design-proposals/security_context.md", + "seLinuxOptions": "seLinuxOptions required to run as; required for MustRunAs More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/", } func (SELinuxStrategyOptions) SwaggerDoc() map[string]string { @@ -623,45 +641,4 @@ func (SupplementalGroupsStrategyOptions) SwaggerDoc() map[string]string { return map_SupplementalGroupsStrategyOptions } -var map_ThirdPartyResource = map[string]string{ - "": "A ThirdPartyResource is a generic representation of a resource, it is used by add-ons and plugins to add new resource types to the API. It consists of one or more Versions of the api.", - "metadata": "Standard object metadata", - "description": "Description is the description of this object.", - "versions": "Versions are versions for this third party object", -} - -func (ThirdPartyResource) SwaggerDoc() map[string]string { - return map_ThirdPartyResource -} - -var map_ThirdPartyResourceData = map[string]string{ - "": "An internal object, used for versioned storage in etcd. Not exposed to the end user.", - "metadata": "Standard object metadata.", - "data": "Data is the raw JSON data for this data.", -} - -func (ThirdPartyResourceData) SwaggerDoc() map[string]string { - return map_ThirdPartyResourceData -} - -var map_ThirdPartyResourceDataList = map[string]string{ - "": "ThirdPartyResrouceDataList is a list of ThirdPartyResourceData.", - "metadata": "Standard list metadata More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", - "items": "Items is the list of ThirdpartyResourceData.", -} - -func (ThirdPartyResourceDataList) SwaggerDoc() map[string]string { - return map_ThirdPartyResourceDataList -} - -var map_ThirdPartyResourceList = map[string]string{ - "": "ThirdPartyResourceList is a list of ThirdPartyResources.", - "metadata": "Standard list metadata.", - "items": "Items is the list of ThirdPartyResources.", -} - -func (ThirdPartyResourceList) SwaggerDoc() map[string]string { - return map_ThirdPartyResourceList -} - // AUTO-GENERATED FUNCTIONS END HERE diff --git a/staging/src/k8s.io/api/extensions/v1beta1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/extensions/v1beta1/zz_generated.deepcopy.go index c173b3f418d..564d4177d0a 100644 --- a/staging/src/k8s.io/api/extensions/v1beta1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/extensions/v1beta1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,281 +23,22 @@ package v1beta1 import ( core_v1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" intstr "k8s.io/apimachinery/pkg/util/intstr" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*APIVersion).DeepCopyInto(out.(*APIVersion)) - return nil - }, InType: reflect.TypeOf(&APIVersion{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AllowedHostPath).DeepCopyInto(out.(*AllowedHostPath)) - return nil - }, InType: reflect.TypeOf(&AllowedHostPath{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CustomMetricCurrentStatus).DeepCopyInto(out.(*CustomMetricCurrentStatus)) - return nil - }, InType: reflect.TypeOf(&CustomMetricCurrentStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CustomMetricCurrentStatusList).DeepCopyInto(out.(*CustomMetricCurrentStatusList)) - return nil - }, InType: reflect.TypeOf(&CustomMetricCurrentStatusList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CustomMetricTarget).DeepCopyInto(out.(*CustomMetricTarget)) - return nil - }, InType: reflect.TypeOf(&CustomMetricTarget{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CustomMetricTargetList).DeepCopyInto(out.(*CustomMetricTargetList)) - return nil - }, InType: reflect.TypeOf(&CustomMetricTargetList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DaemonSet).DeepCopyInto(out.(*DaemonSet)) - return nil - }, InType: reflect.TypeOf(&DaemonSet{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DaemonSetList).DeepCopyInto(out.(*DaemonSetList)) - return nil - }, InType: reflect.TypeOf(&DaemonSetList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DaemonSetSpec).DeepCopyInto(out.(*DaemonSetSpec)) - return nil - }, InType: reflect.TypeOf(&DaemonSetSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DaemonSetStatus).DeepCopyInto(out.(*DaemonSetStatus)) - return nil - }, InType: reflect.TypeOf(&DaemonSetStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DaemonSetUpdateStrategy).DeepCopyInto(out.(*DaemonSetUpdateStrategy)) - return nil - }, InType: reflect.TypeOf(&DaemonSetUpdateStrategy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Deployment).DeepCopyInto(out.(*Deployment)) - return nil - }, InType: reflect.TypeOf(&Deployment{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeploymentCondition).DeepCopyInto(out.(*DeploymentCondition)) - return nil - }, InType: reflect.TypeOf(&DeploymentCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeploymentList).DeepCopyInto(out.(*DeploymentList)) - return nil - }, InType: reflect.TypeOf(&DeploymentList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeploymentRollback).DeepCopyInto(out.(*DeploymentRollback)) - return nil - }, InType: reflect.TypeOf(&DeploymentRollback{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeploymentSpec).DeepCopyInto(out.(*DeploymentSpec)) - return nil - }, InType: reflect.TypeOf(&DeploymentSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeploymentStatus).DeepCopyInto(out.(*DeploymentStatus)) - return nil - }, InType: reflect.TypeOf(&DeploymentStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeploymentStrategy).DeepCopyInto(out.(*DeploymentStrategy)) - return nil - }, InType: reflect.TypeOf(&DeploymentStrategy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*FSGroupStrategyOptions).DeepCopyInto(out.(*FSGroupStrategyOptions)) - return nil - }, InType: reflect.TypeOf(&FSGroupStrategyOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HTTPIngressPath).DeepCopyInto(out.(*HTTPIngressPath)) - return nil - }, InType: reflect.TypeOf(&HTTPIngressPath{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HTTPIngressRuleValue).DeepCopyInto(out.(*HTTPIngressRuleValue)) - return nil - }, InType: reflect.TypeOf(&HTTPIngressRuleValue{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*HostPortRange).DeepCopyInto(out.(*HostPortRange)) - return nil - }, InType: reflect.TypeOf(&HostPortRange{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*IDRange).DeepCopyInto(out.(*IDRange)) - return nil - }, InType: reflect.TypeOf(&IDRange{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*IPBlock).DeepCopyInto(out.(*IPBlock)) - return nil - }, InType: reflect.TypeOf(&IPBlock{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Ingress).DeepCopyInto(out.(*Ingress)) - return nil - }, InType: reflect.TypeOf(&Ingress{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*IngressBackend).DeepCopyInto(out.(*IngressBackend)) - return nil - }, InType: reflect.TypeOf(&IngressBackend{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*IngressList).DeepCopyInto(out.(*IngressList)) - return nil - }, InType: reflect.TypeOf(&IngressList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*IngressRule).DeepCopyInto(out.(*IngressRule)) - return nil - }, InType: reflect.TypeOf(&IngressRule{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*IngressRuleValue).DeepCopyInto(out.(*IngressRuleValue)) - return nil - }, InType: reflect.TypeOf(&IngressRuleValue{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*IngressSpec).DeepCopyInto(out.(*IngressSpec)) - return nil - }, InType: reflect.TypeOf(&IngressSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*IngressStatus).DeepCopyInto(out.(*IngressStatus)) - return nil - }, InType: reflect.TypeOf(&IngressStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*IngressTLS).DeepCopyInto(out.(*IngressTLS)) - return nil - }, InType: reflect.TypeOf(&IngressTLS{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NetworkPolicy).DeepCopyInto(out.(*NetworkPolicy)) - return nil - }, InType: reflect.TypeOf(&NetworkPolicy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NetworkPolicyEgressRule).DeepCopyInto(out.(*NetworkPolicyEgressRule)) - return nil - }, InType: reflect.TypeOf(&NetworkPolicyEgressRule{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NetworkPolicyIngressRule).DeepCopyInto(out.(*NetworkPolicyIngressRule)) - return nil - }, InType: reflect.TypeOf(&NetworkPolicyIngressRule{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NetworkPolicyList).DeepCopyInto(out.(*NetworkPolicyList)) - return nil - }, InType: reflect.TypeOf(&NetworkPolicyList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NetworkPolicyPeer).DeepCopyInto(out.(*NetworkPolicyPeer)) - return nil - }, InType: reflect.TypeOf(&NetworkPolicyPeer{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NetworkPolicyPort).DeepCopyInto(out.(*NetworkPolicyPort)) - return nil - }, InType: reflect.TypeOf(&NetworkPolicyPort{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NetworkPolicySpec).DeepCopyInto(out.(*NetworkPolicySpec)) - return nil - }, InType: reflect.TypeOf(&NetworkPolicySpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodSecurityPolicy).DeepCopyInto(out.(*PodSecurityPolicy)) - return nil - }, InType: reflect.TypeOf(&PodSecurityPolicy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodSecurityPolicyList).DeepCopyInto(out.(*PodSecurityPolicyList)) - return nil - }, InType: reflect.TypeOf(&PodSecurityPolicyList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodSecurityPolicySpec).DeepCopyInto(out.(*PodSecurityPolicySpec)) - return nil - }, InType: reflect.TypeOf(&PodSecurityPolicySpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicaSet).DeepCopyInto(out.(*ReplicaSet)) - return nil - }, InType: reflect.TypeOf(&ReplicaSet{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicaSetCondition).DeepCopyInto(out.(*ReplicaSetCondition)) - return nil - }, InType: reflect.TypeOf(&ReplicaSetCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicaSetList).DeepCopyInto(out.(*ReplicaSetList)) - return nil - }, InType: reflect.TypeOf(&ReplicaSetList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicaSetSpec).DeepCopyInto(out.(*ReplicaSetSpec)) - return nil - }, InType: reflect.TypeOf(&ReplicaSetSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicaSetStatus).DeepCopyInto(out.(*ReplicaSetStatus)) - return nil - }, InType: reflect.TypeOf(&ReplicaSetStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ReplicationControllerDummy).DeepCopyInto(out.(*ReplicationControllerDummy)) - return nil - }, InType: reflect.TypeOf(&ReplicationControllerDummy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RollbackConfig).DeepCopyInto(out.(*RollbackConfig)) - return nil - }, InType: reflect.TypeOf(&RollbackConfig{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RollingUpdateDaemonSet).DeepCopyInto(out.(*RollingUpdateDaemonSet)) - return nil - }, InType: reflect.TypeOf(&RollingUpdateDaemonSet{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RollingUpdateDeployment).DeepCopyInto(out.(*RollingUpdateDeployment)) - return nil - }, InType: reflect.TypeOf(&RollingUpdateDeployment{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RunAsUserStrategyOptions).DeepCopyInto(out.(*RunAsUserStrategyOptions)) - return nil - }, InType: reflect.TypeOf(&RunAsUserStrategyOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SELinuxStrategyOptions).DeepCopyInto(out.(*SELinuxStrategyOptions)) - return nil - }, InType: reflect.TypeOf(&SELinuxStrategyOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Scale).DeepCopyInto(out.(*Scale)) - return nil - }, InType: reflect.TypeOf(&Scale{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ScaleSpec).DeepCopyInto(out.(*ScaleSpec)) - return nil - }, InType: reflect.TypeOf(&ScaleSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ScaleStatus).DeepCopyInto(out.(*ScaleStatus)) - return nil - }, InType: reflect.TypeOf(&ScaleStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*SupplementalGroupsStrategyOptions).DeepCopyInto(out.(*SupplementalGroupsStrategyOptions)) - return nil - }, InType: reflect.TypeOf(&SupplementalGroupsStrategyOptions{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ThirdPartyResource).DeepCopyInto(out.(*ThirdPartyResource)) - return nil - }, InType: reflect.TypeOf(&ThirdPartyResource{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ThirdPartyResourceData).DeepCopyInto(out.(*ThirdPartyResourceData)) - return nil - }, InType: reflect.TypeOf(&ThirdPartyResourceData{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ThirdPartyResourceDataList).DeepCopyInto(out.(*ThirdPartyResourceDataList)) - return nil - }, InType: reflect.TypeOf(&ThirdPartyResourceDataList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ThirdPartyResourceList).DeepCopyInto(out.(*ThirdPartyResourceList)) - return nil - }, InType: reflect.TypeOf(&ThirdPartyResourceList{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *APIVersion) DeepCopyInto(out *APIVersion) { +func (in *AllowedFlexVolume) DeepCopyInto(out *AllowedFlexVolume) { *out = *in return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIVersion. -func (in *APIVersion) DeepCopy() *APIVersion { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedFlexVolume. +func (in *AllowedFlexVolume) DeepCopy() *AllowedFlexVolume { if in == nil { return nil } - out := new(APIVersion) + out := new(AllowedFlexVolume) in.DeepCopyInto(out) return out } @@ -427,6 +168,23 @@ func (in *DaemonSet) DeepCopyObject() runtime.Object { } } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DaemonSetCondition) DeepCopyInto(out *DaemonSetCondition) { + *out = *in + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DaemonSetCondition. +func (in *DaemonSetCondition) DeepCopy() *DaemonSetCondition { + if in == nil { + return nil + } + out := new(DaemonSetCondition) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DaemonSetList) DeepCopyInto(out *DaemonSetList) { *out = *in @@ -509,6 +267,13 @@ func (in *DaemonSetStatus) DeepCopyInto(out *DaemonSetStatus) { **out = **in } } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]DaemonSetCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -1445,6 +1210,11 @@ func (in *PodSecurityPolicySpec) DeepCopyInto(out *PodSecurityPolicySpec) { *out = make([]AllowedHostPath, len(*in)) copy(*out, *in) } + if in.AllowedFlexVolumes != nil { + in, out := &in.AllowedFlexVolumes, &out.AllowedFlexVolumes + *out = make([]AllowedFlexVolume, len(*in)) + copy(*out, *in) + } return } @@ -1831,135 +1601,3 @@ func (in *SupplementalGroupsStrategyOptions) DeepCopy() *SupplementalGroupsStrat in.DeepCopyInto(out) return out } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ThirdPartyResource) DeepCopyInto(out *ThirdPartyResource) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.Versions != nil { - in, out := &in.Versions, &out.Versions - *out = make([]APIVersion, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ThirdPartyResource. -func (in *ThirdPartyResource) DeepCopy() *ThirdPartyResource { - if in == nil { - return nil - } - out := new(ThirdPartyResource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ThirdPartyResource) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ThirdPartyResourceData) DeepCopyInto(out *ThirdPartyResourceData) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.Data != nil { - in, out := &in.Data, &out.Data - *out = make([]byte, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ThirdPartyResourceData. -func (in *ThirdPartyResourceData) DeepCopy() *ThirdPartyResourceData { - if in == nil { - return nil - } - out := new(ThirdPartyResourceData) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ThirdPartyResourceData) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ThirdPartyResourceDataList) DeepCopyInto(out *ThirdPartyResourceDataList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ThirdPartyResourceData, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ThirdPartyResourceDataList. -func (in *ThirdPartyResourceDataList) DeepCopy() *ThirdPartyResourceDataList { - if in == nil { - return nil - } - out := new(ThirdPartyResourceDataList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ThirdPartyResourceDataList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ThirdPartyResourceList) DeepCopyInto(out *ThirdPartyResourceList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ThirdPartyResource, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ThirdPartyResourceList. -func (in *ThirdPartyResourceList) DeepCopy() *ThirdPartyResourceList { - if in == nil { - return nil - } - out := new(ThirdPartyResourceList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ThirdPartyResourceList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} diff --git a/staging/src/k8s.io/api/imagepolicy/v1alpha1/BUILD b/staging/src/k8s.io/api/imagepolicy/v1alpha1/BUILD index ccdb4c7b8e7..455205f7adf 100644 --- a/staging/src/k8s.io/api/imagepolicy/v1alpha1/BUILD +++ b/staging/src/k8s.io/api/imagepolicy/v1alpha1/BUILD @@ -20,7 +20,6 @@ go_library( "//vendor/github.com/gogo/protobuf/proto:go_default_library", "//vendor/github.com/gogo/protobuf/sortkeys:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/staging/src/k8s.io/api/imagepolicy/v1alpha1/doc.go b/staging/src/k8s.io/api/imagepolicy/v1alpha1/doc.go index 3f6a01e5347..3b4840ad64e 100644 --- a/staging/src/k8s.io/api/imagepolicy/v1alpha1/doc.go +++ b/staging/src/k8s.io/api/imagepolicy/v1alpha1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:openapi-gen=true // +groupName=imagepolicy.k8s.io diff --git a/staging/src/k8s.io/api/imagepolicy/v1alpha1/generated.pb.go b/staging/src/k8s.io/api/imagepolicy/v1alpha1/generated.pb.go index e695bb5e70c..f521979b776 100644 --- a/staging/src/k8s.io/api/imagepolicy/v1alpha1/generated.pb.go +++ b/staging/src/k8s.io/api/imagepolicy/v1alpha1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/api/imagepolicy/v1alpha1/generated.proto b/staging/src/k8s.io/api/imagepolicy/v1alpha1/generated.proto index 9a09cba267d..a19967cbe94 100644 --- a/staging/src/k8s.io/api/imagepolicy/v1alpha1/generated.proto +++ b/staging/src/k8s.io/api/imagepolicy/v1alpha1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/api/imagepolicy/v1alpha1/register.go b/staging/src/k8s.io/api/imagepolicy/v1alpha1/register.go index 3e762bb4f94..477571bbb27 100644 --- a/staging/src/k8s.io/api/imagepolicy/v1alpha1/register.go +++ b/staging/src/k8s.io/api/imagepolicy/v1alpha1/register.go @@ -41,7 +41,7 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &ImageReview{}, diff --git a/staging/src/k8s.io/api/imagepolicy/v1alpha1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/imagepolicy/v1alpha1/zz_generated.deepcopy.go index 95b204e0eb9..f0463d29d25 100644 --- a/staging/src/k8s.io/api/imagepolicy/v1alpha1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/imagepolicy/v1alpha1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,40 +21,9 @@ limitations under the License. package v1alpha1 import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ImageReview).DeepCopyInto(out.(*ImageReview)) - return nil - }, InType: reflect.TypeOf(&ImageReview{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ImageReviewContainerSpec).DeepCopyInto(out.(*ImageReviewContainerSpec)) - return nil - }, InType: reflect.TypeOf(&ImageReviewContainerSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ImageReviewSpec).DeepCopyInto(out.(*ImageReviewSpec)) - return nil - }, InType: reflect.TypeOf(&ImageReviewSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ImageReviewStatus).DeepCopyInto(out.(*ImageReviewStatus)) - return nil - }, InType: reflect.TypeOf(&ImageReviewStatus{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ImageReview) DeepCopyInto(out *ImageReview) { *out = *in diff --git a/staging/src/k8s.io/api/networking/v1/BUILD b/staging/src/k8s.io/api/networking/v1/BUILD index ba6ce8a1f48..beb3afcaf71 100644 --- a/staging/src/k8s.io/api/networking/v1/BUILD +++ b/staging/src/k8s.io/api/networking/v1/BUILD @@ -20,7 +20,6 @@ go_library( "//vendor/github.com/gogo/protobuf/proto:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", diff --git a/staging/src/k8s.io/api/networking/v1/doc.go b/staging/src/k8s.io/api/networking/v1/doc.go index 8bcc30b07f4..ef9ae2ae4cf 100644 --- a/staging/src/k8s.io/api/networking/v1/doc.go +++ b/staging/src/k8s.io/api/networking/v1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:openapi-gen=true // +groupName=networking.k8s.io package v1 // import "k8s.io/api/networking/v1" diff --git a/staging/src/k8s.io/api/networking/v1/generated.pb.go b/staging/src/k8s.io/api/networking/v1/generated.pb.go index df4d9f97959..05aaf1d9a8d 100644 --- a/staging/src/k8s.io/api/networking/v1/generated.pb.go +++ b/staging/src/k8s.io/api/networking/v1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/api/networking/v1/generated.proto b/staging/src/k8s.io/api/networking/v1/generated.proto index ae28d2f2df2..06365ebe3fc 100644 --- a/staging/src/k8s.io/api/networking/v1/generated.proto +++ b/staging/src/k8s.io/api/networking/v1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/api/networking/v1/register.go b/staging/src/k8s.io/api/networking/v1/register.go index 2ba9951d8ad..f47f22e9e88 100644 --- a/staging/src/k8s.io/api/networking/v1/register.go +++ b/staging/src/k8s.io/api/networking/v1/register.go @@ -41,7 +41,7 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &NetworkPolicy{}, diff --git a/staging/src/k8s.io/api/networking/v1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/networking/v1/zz_generated.deepcopy.go index 0e6709667de..3dbe87055cc 100644 --- a/staging/src/k8s.io/api/networking/v1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/networking/v1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,57 +23,10 @@ package v1 import ( core_v1 "k8s.io/api/core/v1" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" intstr "k8s.io/apimachinery/pkg/util/intstr" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*IPBlock).DeepCopyInto(out.(*IPBlock)) - return nil - }, InType: reflect.TypeOf(&IPBlock{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NetworkPolicy).DeepCopyInto(out.(*NetworkPolicy)) - return nil - }, InType: reflect.TypeOf(&NetworkPolicy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NetworkPolicyEgressRule).DeepCopyInto(out.(*NetworkPolicyEgressRule)) - return nil - }, InType: reflect.TypeOf(&NetworkPolicyEgressRule{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NetworkPolicyIngressRule).DeepCopyInto(out.(*NetworkPolicyIngressRule)) - return nil - }, InType: reflect.TypeOf(&NetworkPolicyIngressRule{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NetworkPolicyList).DeepCopyInto(out.(*NetworkPolicyList)) - return nil - }, InType: reflect.TypeOf(&NetworkPolicyList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NetworkPolicyPeer).DeepCopyInto(out.(*NetworkPolicyPeer)) - return nil - }, InType: reflect.TypeOf(&NetworkPolicyPeer{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NetworkPolicyPort).DeepCopyInto(out.(*NetworkPolicyPort)) - return nil - }, InType: reflect.TypeOf(&NetworkPolicyPort{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*NetworkPolicySpec).DeepCopyInto(out.(*NetworkPolicySpec)) - return nil - }, InType: reflect.TypeOf(&NetworkPolicySpec{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IPBlock) DeepCopyInto(out *IPBlock) { *out = *in diff --git a/staging/src/k8s.io/api/policy/v1beta1/BUILD b/staging/src/k8s.io/api/policy/v1beta1/BUILD index 1a9e7ba4ee9..6e4a07deb67 100644 --- a/staging/src/k8s.io/api/policy/v1beta1/BUILD +++ b/staging/src/k8s.io/api/policy/v1beta1/BUILD @@ -20,7 +20,6 @@ go_library( "//vendor/github.com/gogo/protobuf/proto:go_default_library", "//vendor/github.com/gogo/protobuf/sortkeys:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", diff --git a/staging/src/k8s.io/api/policy/v1beta1/doc.go b/staging/src/k8s.io/api/policy/v1beta1/doc.go index 1e9f4974da7..9c456f9237f 100644 --- a/staging/src/k8s.io/api/policy/v1beta1/doc.go +++ b/staging/src/k8s.io/api/policy/v1beta1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // Package policy is for any kind of policy object. Suitable examples, even if // they aren't all here, are PodDisruptionBudget, PodSecurityPolicy, diff --git a/staging/src/k8s.io/api/policy/v1beta1/generated.pb.go b/staging/src/k8s.io/api/policy/v1beta1/generated.pb.go index a66aeff354c..4ed4d29ca6b 100644 --- a/staging/src/k8s.io/api/policy/v1beta1/generated.pb.go +++ b/staging/src/k8s.io/api/policy/v1beta1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/api/policy/v1beta1/generated.proto b/staging/src/k8s.io/api/policy/v1beta1/generated.proto index a276be1c93d..2e01cf3d9b6 100644 --- a/staging/src/k8s.io/api/policy/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/policy/v1beta1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/api/policy/v1beta1/register.go b/staging/src/k8s.io/api/policy/v1beta1/register.go index e0c6247a790..d77f1304070 100644 --- a/staging/src/k8s.io/api/policy/v1beta1/register.go +++ b/staging/src/k8s.io/api/policy/v1beta1/register.go @@ -41,7 +41,7 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &PodDisruptionBudget{}, diff --git a/staging/src/k8s.io/api/policy/v1beta1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/policy/v1beta1/zz_generated.deepcopy.go index 93c201e2dd5..78a597b5b9b 100644 --- a/staging/src/k8s.io/api/policy/v1beta1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/policy/v1beta1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,45 +22,10 @@ package v1beta1 import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" intstr "k8s.io/apimachinery/pkg/util/intstr" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Eviction).DeepCopyInto(out.(*Eviction)) - return nil - }, InType: reflect.TypeOf(&Eviction{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodDisruptionBudget).DeepCopyInto(out.(*PodDisruptionBudget)) - return nil - }, InType: reflect.TypeOf(&PodDisruptionBudget{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodDisruptionBudgetList).DeepCopyInto(out.(*PodDisruptionBudgetList)) - return nil - }, InType: reflect.TypeOf(&PodDisruptionBudgetList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodDisruptionBudgetSpec).DeepCopyInto(out.(*PodDisruptionBudgetSpec)) - return nil - }, InType: reflect.TypeOf(&PodDisruptionBudgetSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodDisruptionBudgetStatus).DeepCopyInto(out.(*PodDisruptionBudgetStatus)) - return nil - }, InType: reflect.TypeOf(&PodDisruptionBudgetStatus{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Eviction) DeepCopyInto(out *Eviction) { *out = *in diff --git a/staging/src/k8s.io/api/rbac/v1/BUILD b/staging/src/k8s.io/api/rbac/v1/BUILD index 5a686d7ca18..539afb7ae85 100644 --- a/staging/src/k8s.io/api/rbac/v1/BUILD +++ b/staging/src/k8s.io/api/rbac/v1/BUILD @@ -19,7 +19,6 @@ go_library( deps = [ "//vendor/github.com/gogo/protobuf/proto:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/staging/src/k8s.io/api/rbac/v1/doc.go b/staging/src/k8s.io/api/rbac/v1/doc.go index 737261a097f..28ceb269b4e 100644 --- a/staging/src/k8s.io/api/rbac/v1/doc.go +++ b/staging/src/k8s.io/api/rbac/v1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:openapi-gen=true // +groupName=rbac.authorization.k8s.io diff --git a/staging/src/k8s.io/api/rbac/v1/generated.pb.go b/staging/src/k8s.io/api/rbac/v1/generated.pb.go index 1285ac1942a..5343731cc68 100644 --- a/staging/src/k8s.io/api/rbac/v1/generated.pb.go +++ b/staging/src/k8s.io/api/rbac/v1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -25,6 +25,7 @@ limitations under the License. k8s.io/kubernetes/vendor/k8s.io/api/rbac/v1/generated.proto It has these top-level messages: + AggregationRule ClusterRole ClusterRoleBinding ClusterRoleBindingList @@ -43,6 +44,8 @@ import proto "github.com/gogo/protobuf/proto" import fmt "fmt" import math "math" +import k8s_io_apimachinery_pkg_apis_meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + import strings "strings" import reflect "reflect" @@ -59,51 +62,56 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package +func (m *AggregationRule) Reset() { *m = AggregationRule{} } +func (*AggregationRule) ProtoMessage() {} +func (*AggregationRule) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{0} } + func (m *ClusterRole) Reset() { *m = ClusterRole{} } func (*ClusterRole) ProtoMessage() {} -func (*ClusterRole) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{0} } +func (*ClusterRole) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{1} } func (m *ClusterRoleBinding) Reset() { *m = ClusterRoleBinding{} } func (*ClusterRoleBinding) ProtoMessage() {} -func (*ClusterRoleBinding) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{1} } +func (*ClusterRoleBinding) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{2} } func (m *ClusterRoleBindingList) Reset() { *m = ClusterRoleBindingList{} } func (*ClusterRoleBindingList) ProtoMessage() {} -func (*ClusterRoleBindingList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{2} } +func (*ClusterRoleBindingList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{3} } func (m *ClusterRoleList) Reset() { *m = ClusterRoleList{} } func (*ClusterRoleList) ProtoMessage() {} -func (*ClusterRoleList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{3} } +func (*ClusterRoleList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{4} } func (m *PolicyRule) Reset() { *m = PolicyRule{} } func (*PolicyRule) ProtoMessage() {} -func (*PolicyRule) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{4} } +func (*PolicyRule) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{5} } func (m *Role) Reset() { *m = Role{} } func (*Role) ProtoMessage() {} -func (*Role) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{5} } +func (*Role) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{6} } func (m *RoleBinding) Reset() { *m = RoleBinding{} } func (*RoleBinding) ProtoMessage() {} -func (*RoleBinding) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{6} } +func (*RoleBinding) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{7} } func (m *RoleBindingList) Reset() { *m = RoleBindingList{} } func (*RoleBindingList) ProtoMessage() {} -func (*RoleBindingList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{7} } +func (*RoleBindingList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{8} } func (m *RoleList) Reset() { *m = RoleList{} } func (*RoleList) ProtoMessage() {} -func (*RoleList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{8} } +func (*RoleList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{9} } func (m *RoleRef) Reset() { *m = RoleRef{} } func (*RoleRef) ProtoMessage() {} -func (*RoleRef) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{9} } +func (*RoleRef) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{10} } func (m *Subject) Reset() { *m = Subject{} } func (*Subject) ProtoMessage() {} -func (*Subject) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{10} } +func (*Subject) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{11} } func init() { + proto.RegisterType((*AggregationRule)(nil), "k8s.io.api.rbac.v1.AggregationRule") proto.RegisterType((*ClusterRole)(nil), "k8s.io.api.rbac.v1.ClusterRole") proto.RegisterType((*ClusterRoleBinding)(nil), "k8s.io.api.rbac.v1.ClusterRoleBinding") proto.RegisterType((*ClusterRoleBindingList)(nil), "k8s.io.api.rbac.v1.ClusterRoleBindingList") @@ -116,6 +124,36 @@ func init() { proto.RegisterType((*RoleRef)(nil), "k8s.io.api.rbac.v1.RoleRef") proto.RegisterType((*Subject)(nil), "k8s.io.api.rbac.v1.Subject") } +func (m *AggregationRule) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AggregationRule) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.ClusterRoleSelectors) > 0 { + for _, msg := range m.ClusterRoleSelectors { + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + func (m *ClusterRole) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -151,6 +189,16 @@ func (m *ClusterRole) MarshalTo(dAtA []byte) (int, error) { i += n } } + if m.AggregationRule != nil { + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.AggregationRule.Size())) + n2, err := m.AggregationRule.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n2 + } return i, nil } @@ -172,11 +220,11 @@ func (m *ClusterRoleBinding) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n2, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n3, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n2 + i += n3 if len(m.Subjects) > 0 { for _, msg := range m.Subjects { dAtA[i] = 0x12 @@ -192,11 +240,11 @@ func (m *ClusterRoleBinding) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.RoleRef.Size())) - n3, err := m.RoleRef.MarshalTo(dAtA[i:]) + n4, err := m.RoleRef.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n3 + i += n4 return i, nil } @@ -218,11 +266,11 @@ func (m *ClusterRoleBindingList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n4, err := m.ListMeta.MarshalTo(dAtA[i:]) + n5, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n4 + i += n5 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -256,11 +304,11 @@ func (m *ClusterRoleList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n5, err := m.ListMeta.MarshalTo(dAtA[i:]) + n6, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n5 + i += n6 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -387,11 +435,11 @@ func (m *Role) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n6, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n7, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n6 + i += n7 if len(m.Rules) > 0 { for _, msg := range m.Rules { dAtA[i] = 0x12 @@ -425,11 +473,11 @@ func (m *RoleBinding) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n7, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n8, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n7 + i += n8 if len(m.Subjects) > 0 { for _, msg := range m.Subjects { dAtA[i] = 0x12 @@ -445,11 +493,11 @@ func (m *RoleBinding) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.RoleRef.Size())) - n8, err := m.RoleRef.MarshalTo(dAtA[i:]) + n9, err := m.RoleRef.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n8 + i += n9 return i, nil } @@ -471,11 +519,11 @@ func (m *RoleBindingList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n9, err := m.ListMeta.MarshalTo(dAtA[i:]) + n10, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n9 + i += n10 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -509,11 +557,11 @@ func (m *RoleList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n10, err := m.ListMeta.MarshalTo(dAtA[i:]) + n11, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n10 + i += n11 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -620,6 +668,18 @@ func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return offset + 1 } +func (m *AggregationRule) Size() (n int) { + var l int + _ = l + if len(m.ClusterRoleSelectors) > 0 { + for _, e := range m.ClusterRoleSelectors { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + func (m *ClusterRole) Size() (n int) { var l int _ = l @@ -631,6 +691,10 @@ func (m *ClusterRole) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) } } + if m.AggregationRule != nil { + l = m.AggregationRule.Size() + n += 1 + l + sovGenerated(uint64(l)) + } return n } @@ -811,6 +875,16 @@ func sovGenerated(x uint64) (n int) { func sozGenerated(x uint64) (n int) { return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } +func (this *AggregationRule) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&AggregationRule{`, + `ClusterRoleSelectors:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ClusterRoleSelectors), "LabelSelector", "k8s_io_apimachinery_pkg_apis_meta_v1.LabelSelector", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} func (this *ClusterRole) String() string { if this == nil { return "nil" @@ -818,6 +892,7 @@ func (this *ClusterRole) String() string { s := strings.Join([]string{`&ClusterRole{`, `ObjectMeta:` + strings.Replace(strings.Replace(this.ObjectMeta.String(), "ObjectMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ObjectMeta", 1), `&`, ``, 1) + `,`, `Rules:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Rules), "PolicyRule", "PolicyRule", 1), `&`, ``, 1) + `,`, + `AggregationRule:` + strings.Replace(fmt.Sprintf("%v", this.AggregationRule), "AggregationRule", "AggregationRule", 1) + `,`, `}`, }, "") return s @@ -948,6 +1023,87 @@ func valueToStringGenerated(v interface{}) string { pv := reflect.Indirect(rv).Interface() return fmt.Sprintf("*%v", pv) } +func (m *AggregationRule) 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: AggregationRule: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AggregationRule: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClusterRoleSelectors", 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 > l { + return io.ErrUnexpectedEOF + } + m.ClusterRoleSelectors = append(m.ClusterRoleSelectors, k8s_io_apimachinery_pkg_apis_meta_v1.LabelSelector{}) + if err := m.ClusterRoleSelectors[len(m.ClusterRoleSelectors)-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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *ClusterRole) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -1038,6 +1194,39 @@ func (m *ClusterRole) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AggregationRule", 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 > l { + return io.ErrUnexpectedEOF + } + if m.AggregationRule == nil { + m.AggregationRule = &AggregationRule{} + } + if err := m.AggregationRule.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -2504,52 +2693,57 @@ func init() { } var fileDescriptorGenerated = []byte{ - // 743 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x94, 0x4f, 0x6b, 0x13, 0x4f, - 0x18, 0xc7, 0x33, 0xf9, 0x43, 0xb3, 0x93, 0x5f, 0xc8, 0xaf, 0x2b, 0xc8, 0x52, 0x61, 0x13, 0x72, - 0x90, 0x80, 0xba, 0x6b, 0xaa, 0xa8, 0x20, 0x3d, 0xb8, 0x15, 0xa5, 0xb4, 0xd6, 0x32, 0xa2, 0x07, - 0xf1, 0xe0, 0x66, 0x33, 0x4d, 0xc7, 0x64, 0xff, 0x30, 0x33, 0x1b, 0x28, 0x5e, 0xc4, 0x9b, 0x37, - 0xdf, 0x85, 0x17, 0xbd, 0xe9, 0x2b, 0xf0, 0xd2, 0x63, 0x8f, 0x3d, 0x05, 0xbb, 0xbe, 0x10, 0x65, - 0x66, 0x77, 0xb3, 0x49, 0x93, 0xd8, 0x9e, 0x02, 0xe2, 0x29, 0x99, 0xe7, 0xf9, 0x7c, 0x9f, 0xf9, - 0xce, 0xb3, 0x33, 0x0f, 0xbc, 0xdf, 0xbf, 0xc7, 0x0c, 0xe2, 0x9b, 0xfd, 0xb0, 0x83, 0xa9, 0x87, - 0x39, 0x66, 0xe6, 0x10, 0x7b, 0x5d, 0x9f, 0x9a, 0x49, 0xc2, 0x0e, 0x88, 0x49, 0x3b, 0xb6, 0x63, - 0x0e, 0xdb, 0x66, 0x0f, 0x7b, 0x98, 0xda, 0x1c, 0x77, 0x8d, 0x80, 0xfa, 0xdc, 0x57, 0xd5, 0x98, - 0x31, 0xec, 0x80, 0x18, 0x82, 0x31, 0x86, 0xed, 0xb5, 0x1b, 0x3d, 0xc2, 0x0f, 0xc2, 0x8e, 0xe1, - 0xf8, 0xae, 0xd9, 0xf3, 0x7b, 0xbe, 0x29, 0xd1, 0x4e, 0xb8, 0x2f, 0x57, 0x72, 0x21, 0xff, 0xc5, - 0x25, 0xd6, 0x6e, 0x67, 0xdb, 0xb8, 0xb6, 0x73, 0x40, 0x3c, 0x4c, 0x0f, 0xcd, 0xa0, 0xdf, 0x13, - 0x01, 0x66, 0xba, 0x98, 0xdb, 0x73, 0x36, 0x5e, 0x33, 0x17, 0xa9, 0x68, 0xe8, 0x71, 0xe2, 0xe2, - 0x19, 0xc1, 0x9d, 0xf3, 0x04, 0xcc, 0x39, 0xc0, 0xae, 0x3d, 0xa3, 0xbb, 0xb5, 0x48, 0x17, 0x72, - 0x32, 0x30, 0x89, 0xc7, 0x19, 0xa7, 0x67, 0x45, 0xcd, 0xaf, 0x00, 0x56, 0x36, 0x07, 0x21, 0xe3, - 0x98, 0x22, 0x7f, 0x80, 0xd5, 0xd7, 0xb0, 0x2c, 0x0e, 0xd2, 0xb5, 0xb9, 0xad, 0x81, 0x06, 0x68, - 0x55, 0xd6, 0x6f, 0x1a, 0x59, 0xe7, 0xc6, 0x75, 0x8d, 0xa0, 0xdf, 0x13, 0x01, 0x66, 0x08, 0xda, - 0x18, 0xb6, 0x8d, 0xa7, 0x9d, 0x37, 0xd8, 0xe1, 0x4f, 0x30, 0xb7, 0x2d, 0xf5, 0x68, 0x54, 0xcf, - 0x45, 0xa3, 0x3a, 0xcc, 0x62, 0x68, 0x5c, 0x55, 0xdd, 0x84, 0x25, 0x1a, 0x0e, 0x30, 0xd3, 0xf2, - 0x8d, 0x42, 0xab, 0xb2, 0xae, 0x1b, 0xb3, 0x1f, 0xc6, 0xd8, 0xf3, 0x07, 0xc4, 0x39, 0x44, 0xe1, - 0x00, 0x5b, 0xd5, 0xa4, 0x58, 0x49, 0xac, 0x18, 0x8a, 0xb5, 0xcd, 0x0f, 0x79, 0xa8, 0x4e, 0xd8, - 0xb6, 0x88, 0xd7, 0x25, 0x5e, 0x6f, 0x09, 0xee, 0xb7, 0x60, 0x99, 0x85, 0x32, 0x91, 0x1e, 0xe0, - 0xca, 0xbc, 0x03, 0x3c, 0x8b, 0x19, 0xeb, 0xff, 0xa4, 0x58, 0x39, 0x09, 0x30, 0x34, 0x96, 0xab, - 0x8f, 0xe0, 0x0a, 0xf5, 0x07, 0x18, 0xe1, 0x7d, 0xad, 0x20, 0xbd, 0xce, 0xad, 0x84, 0x62, 0xc4, - 0xaa, 0x25, 0x95, 0x56, 0x92, 0x00, 0x4a, 0xc5, 0xcd, 0xef, 0x00, 0x5e, 0x9e, 0xed, 0xc5, 0x0e, - 0x61, 0x5c, 0x7d, 0x35, 0xd3, 0x0f, 0xe3, 0x62, 0xfd, 0x10, 0x6a, 0xd9, 0x8d, 0xf1, 0x01, 0xd2, - 0xc8, 0x44, 0x2f, 0xb6, 0x61, 0x89, 0x70, 0xec, 0xa6, 0x8d, 0xb8, 0x3a, 0xcf, 0xfe, 0xac, 0xb1, - 0xec, 0x8b, 0x6e, 0x09, 0x31, 0x8a, 0x6b, 0x34, 0xbf, 0x01, 0x58, 0x9b, 0x80, 0x97, 0x60, 0xff, - 0xe1, 0xb4, 0xfd, 0xfa, 0x79, 0xf6, 0xe7, 0xfb, 0xfe, 0x05, 0x20, 0xcc, 0xae, 0xab, 0x5a, 0x87, - 0xa5, 0x21, 0xa6, 0x1d, 0xa6, 0x81, 0x46, 0xa1, 0xa5, 0x58, 0x8a, 0xe0, 0x5f, 0x88, 0x00, 0x8a, - 0xe3, 0xea, 0x35, 0xa8, 0xd8, 0x01, 0x79, 0x4c, 0xfd, 0x30, 0x88, 0x77, 0x56, 0xac, 0x6a, 0x34, - 0xaa, 0x2b, 0x0f, 0xf6, 0xb6, 0xe2, 0x20, 0xca, 0xf2, 0x02, 0xa6, 0x98, 0xf9, 0x21, 0x75, 0x30, - 0xd3, 0x0a, 0x19, 0x8c, 0xd2, 0x20, 0xca, 0xf2, 0xea, 0x5d, 0x58, 0x4d, 0x17, 0xbb, 0xb6, 0x8b, - 0x99, 0x56, 0x94, 0x82, 0xd5, 0x68, 0x54, 0xaf, 0xa2, 0xc9, 0x04, 0x9a, 0xe6, 0xd4, 0x0d, 0x58, - 0xf3, 0x7c, 0x2f, 0x45, 0x9e, 0xa3, 0x1d, 0xa6, 0x95, 0xa4, 0xf4, 0x52, 0x34, 0xaa, 0xd7, 0x76, - 0xa7, 0x53, 0xe8, 0x2c, 0xdb, 0xfc, 0x02, 0x60, 0xf1, 0x6f, 0x9a, 0x1d, 0xef, 0xf3, 0xb0, 0xf2, - 0xcf, 0x0f, 0x0d, 0xf1, 0xdc, 0x96, 0x3b, 0x2d, 0x2e, 0xf2, 0xdc, 0xce, 0x1f, 0x13, 0x9f, 0x00, - 0x2c, 0x2f, 0x69, 0x3e, 0x6c, 0x4c, 0x1b, 0xd6, 0x16, 0x1a, 0x9e, 0xef, 0xf4, 0x2d, 0x4c, 0xbb, - 0xae, 0x5e, 0x87, 0xe5, 0xf4, 0x4d, 0x4b, 0x9f, 0x4a, 0xb6, 0x6f, 0xfa, 0xec, 0xd1, 0x98, 0x50, - 0x1b, 0xb0, 0xd8, 0x27, 0x5e, 0x57, 0xcb, 0x4b, 0xf2, 0xbf, 0x84, 0x2c, 0x6e, 0x13, 0xaf, 0x8b, - 0x64, 0x46, 0x10, 0x9e, 0xed, 0x62, 0x79, 0x03, 0x26, 0x08, 0xf1, 0x9a, 0x91, 0xcc, 0x34, 0x3f, - 0x03, 0xb8, 0x92, 0xdc, 0x9e, 0x71, 0x3d, 0xb0, 0xb0, 0xde, 0xa4, 0xbf, 0xfc, 0x45, 0xfc, 0xfd, - 0x79, 0x77, 0xd5, 0x84, 0x8a, 0xf8, 0x65, 0x81, 0xed, 0x60, 0xad, 0x28, 0xb1, 0xd5, 0x04, 0x53, - 0x76, 0xd3, 0x04, 0xca, 0x18, 0xab, 0x75, 0x74, 0xaa, 0xe7, 0x8e, 0x4f, 0xf5, 0xdc, 0xc9, 0xa9, - 0x9e, 0x7b, 0x17, 0xe9, 0xe0, 0x28, 0xd2, 0xc1, 0x71, 0xa4, 0x83, 0x93, 0x48, 0x07, 0x3f, 0x22, - 0x1d, 0x7c, 0xfc, 0xa9, 0xe7, 0x5e, 0xe6, 0x87, 0xed, 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x66, - 0x92, 0x08, 0x1d, 0x04, 0x0a, 0x00, 0x00, + // 827 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x55, 0xcf, 0x8b, 0x23, 0x45, + 0x18, 0x4d, 0x65, 0x12, 0x26, 0x5d, 0x31, 0xc4, 0x2d, 0x17, 0x69, 0xa2, 0x74, 0x86, 0x16, 0x24, + 0xa0, 0x76, 0x9b, 0x5d, 0x51, 0x41, 0xf6, 0xb0, 0xbd, 0xa2, 0x0c, 0x3b, 0x8e, 0x4b, 0x2d, 0x7a, + 0x10, 0x0f, 0x56, 0x77, 0x6a, 0x3b, 0x65, 0xfa, 0x17, 0x55, 0xd5, 0x81, 0xc5, 0x8b, 0x08, 0x1e, + 0xbc, 0x79, 0xd4, 0xbf, 0xc0, 0x8b, 0x1e, 0xfd, 0x0b, 0xbc, 0xcc, 0x71, 0x8f, 0x7b, 0x0a, 0x4e, + 0xfb, 0x87, 0x28, 0xfd, 0x2b, 0x9d, 0xa4, 0x3b, 0x4e, 0x4e, 0x01, 0xf1, 0x34, 0x53, 0xdf, 0xf7, + 0xde, 0xfb, 0x5e, 0xbf, 0xa9, 0xaf, 0x06, 0x7e, 0xb0, 0x78, 0x5f, 0x18, 0x2c, 0x34, 0x17, 0xb1, + 0x4d, 0x79, 0x40, 0x25, 0x15, 0xe6, 0x92, 0x06, 0xb3, 0x90, 0x9b, 0x45, 0x83, 0x44, 0xcc, 0xe4, + 0x36, 0x71, 0xcc, 0xe5, 0xd4, 0x74, 0x69, 0x40, 0x39, 0x91, 0x74, 0x66, 0x44, 0x3c, 0x94, 0x21, + 0x42, 0x39, 0xc6, 0x20, 0x11, 0x33, 0x52, 0x8c, 0xb1, 0x9c, 0x8e, 0xde, 0x72, 0x99, 0x9c, 0xc7, + 0xb6, 0xe1, 0x84, 0xbe, 0xe9, 0x86, 0x6e, 0x68, 0x66, 0x50, 0x3b, 0x7e, 0x92, 0x9d, 0xb2, 0x43, + 0xf6, 0x5b, 0x2e, 0x31, 0x9a, 0xd4, 0xc7, 0x10, 0x2f, 0x9a, 0x93, 0xda, 0xb0, 0xd1, 0x3b, 0x15, + 0xd2, 0x27, 0xce, 0x9c, 0x05, 0x94, 0x3f, 0x35, 0xa3, 0x85, 0x9b, 0x16, 0x84, 0xe9, 0x53, 0x49, + 0x1a, 0x2c, 0x8e, 0xcc, 0x7d, 0x2c, 0x1e, 0x07, 0x92, 0xf9, 0xb4, 0x46, 0x78, 0xf7, 0x26, 0x82, + 0x70, 0xe6, 0xd4, 0x27, 0x35, 0xde, 0xdd, 0x7d, 0xbc, 0x58, 0x32, 0xcf, 0x64, 0x81, 0x14, 0x92, + 0xef, 0x92, 0xf4, 0x9f, 0x01, 0x1c, 0xde, 0x77, 0x5d, 0x4e, 0x5d, 0x22, 0x59, 0x18, 0xe0, 0xd8, + 0xa3, 0xe8, 0x7b, 0x00, 0x6f, 0x3b, 0x5e, 0x2c, 0x24, 0xe5, 0x38, 0xf4, 0xe8, 0x63, 0xea, 0x51, + 0x47, 0x86, 0x5c, 0xa8, 0xe0, 0xec, 0x64, 0xd2, 0xbf, 0x73, 0xd7, 0xa8, 0x42, 0x5f, 0x0f, 0x32, + 0xa2, 0x85, 0x9b, 0x16, 0x84, 0x91, 0xe6, 0x60, 0x2c, 0xa7, 0xc6, 0x05, 0xb1, 0xa9, 0x57, 0x72, + 0xad, 0x57, 0xaf, 0x56, 0xe3, 0x56, 0xb2, 0x1a, 0xdf, 0x7e, 0xd0, 0x20, 0x8c, 0x1b, 0xc7, 0xe9, + 0x3f, 0xb5, 0x61, 0x7f, 0x03, 0x8e, 0xbe, 0x82, 0xbd, 0x54, 0x7c, 0x46, 0x24, 0x51, 0xc1, 0x19, + 0x98, 0xf4, 0xef, 0xbc, 0x7d, 0x98, 0x95, 0x4f, 0xed, 0xaf, 0xa9, 0x23, 0x3f, 0xa1, 0x92, 0x58, + 0xa8, 0xf0, 0x01, 0xab, 0x1a, 0x5e, 0xab, 0xa2, 0x07, 0xb0, 0xcb, 0x63, 0x8f, 0x0a, 0xb5, 0x9d, + 0x7d, 0xa9, 0x66, 0xd4, 0xaf, 0x97, 0xf1, 0x28, 0xf4, 0x98, 0xf3, 0x34, 0x0d, 0xca, 0x1a, 0x14, + 0x62, 0xdd, 0xf4, 0x24, 0x70, 0xce, 0x45, 0x36, 0x1c, 0x92, 0xed, 0x44, 0xd5, 0x93, 0xcc, 0xed, + 0x6b, 0x4d, 0x72, 0x3b, 0xe1, 0x5b, 0x2f, 0x25, 0xab, 0xf1, 0xee, 0x5f, 0x04, 0xef, 0x0a, 0xea, + 0x3f, 0xb4, 0x21, 0xda, 0x88, 0xc6, 0x62, 0xc1, 0x8c, 0x05, 0xee, 0x11, 0x12, 0x3a, 0x87, 0x3d, + 0x11, 0x67, 0x8d, 0x32, 0xa4, 0x57, 0x9a, 0xbe, 0xea, 0x71, 0x8e, 0xb1, 0x5e, 0x2c, 0xc4, 0x7a, + 0x45, 0x41, 0xe0, 0x35, 0x1d, 0x7d, 0x04, 0x4f, 0x79, 0xe8, 0x51, 0x4c, 0x9f, 0x14, 0xf9, 0x34, + 0x2a, 0xe1, 0x1c, 0x62, 0x0d, 0x0b, 0xa5, 0xd3, 0xa2, 0x80, 0x4b, 0xb2, 0xfe, 0x07, 0x80, 0x2f, + 0xd7, 0xb3, 0xb8, 0x60, 0x42, 0xa2, 0x2f, 0x6b, 0x79, 0x18, 0x07, 0x5e, 0x5e, 0x26, 0xf2, 0x34, + 0xd6, 0x1f, 0x50, 0x56, 0x36, 0xb2, 0x78, 0x08, 0xbb, 0x4c, 0x52, 0xbf, 0x0c, 0xe2, 0xf5, 0x26, + 0xfb, 0x75, 0x63, 0xd5, 0xad, 0x39, 0x4f, 0xc9, 0x38, 0xd7, 0xd0, 0x7f, 0x07, 0x70, 0xb8, 0x01, + 0x3e, 0x82, 0xfd, 0x0f, 0xb7, 0xed, 0x8f, 0x6f, 0xb2, 0xdf, 0xec, 0xfb, 0x6f, 0x00, 0x61, 0xb5, + 0x12, 0x68, 0x0c, 0xbb, 0x4b, 0xca, 0xed, 0xfc, 0xad, 0x50, 0x2c, 0x25, 0xc5, 0x7f, 0x9e, 0x16, + 0x70, 0x5e, 0x47, 0x6f, 0x40, 0x85, 0x44, 0xec, 0x63, 0x1e, 0xc6, 0x51, 0x3e, 0x59, 0xb1, 0x06, + 0xc9, 0x6a, 0xac, 0xdc, 0x7f, 0x74, 0x9e, 0x17, 0x71, 0xd5, 0x4f, 0xc1, 0x9c, 0x8a, 0x30, 0xe6, + 0x0e, 0x15, 0xea, 0x49, 0x05, 0xc6, 0x65, 0x11, 0x57, 0x7d, 0xf4, 0x1e, 0x1c, 0x94, 0x87, 0x4b, + 0xe2, 0x53, 0xa1, 0x76, 0x32, 0xc2, 0xad, 0x64, 0x35, 0x1e, 0xe0, 0xcd, 0x06, 0xde, 0xc6, 0xa1, + 0x7b, 0x70, 0x18, 0x84, 0x41, 0x09, 0xf9, 0x0c, 0x5f, 0x08, 0xb5, 0x9b, 0x51, 0xb3, 0x5d, 0xbc, + 0xdc, 0x6e, 0xe1, 0x5d, 0xac, 0xfe, 0x1b, 0x80, 0x9d, 0xff, 0xd0, 0xfb, 0xa4, 0x7f, 0xd7, 0x86, + 0xfd, 0xff, 0xfd, 0xa3, 0x91, 0xae, 0xdb, 0x71, 0x5f, 0x8b, 0x43, 0xd6, 0xed, 0xe6, 0x67, 0xe2, + 0x17, 0x00, 0x7b, 0x47, 0x7a, 0x1f, 0xee, 0x6d, 0x1b, 0x56, 0xf7, 0x1a, 0x6e, 0x76, 0xfa, 0x0d, + 0x2c, 0x53, 0x47, 0x6f, 0xc2, 0x5e, 0xb9, 0xd3, 0x99, 0x4f, 0xa5, 0x9a, 0x5b, 0xae, 0x3d, 0x5e, + 0x23, 0xd0, 0x19, 0xec, 0x2c, 0x58, 0x30, 0x53, 0xdb, 0x19, 0xf2, 0x85, 0x02, 0xd9, 0x79, 0xc8, + 0x82, 0x19, 0xce, 0x3a, 0x29, 0x22, 0x20, 0x7e, 0xfe, 0x6f, 0x75, 0x03, 0x91, 0x6e, 0x33, 0xce, + 0x3a, 0xfa, 0xaf, 0x00, 0x9e, 0x16, 0xb7, 0x67, 0xad, 0x07, 0xf6, 0xea, 0x6d, 0xfa, 0x6b, 0x1f, + 0xe2, 0xef, 0xdf, 0xa7, 0x23, 0x13, 0x2a, 0xe9, 0x4f, 0x11, 0x11, 0x87, 0xaa, 0x9d, 0x0c, 0x76, + 0xab, 0x80, 0x29, 0x97, 0x65, 0x03, 0x57, 0x18, 0x6b, 0x72, 0x75, 0xad, 0xb5, 0x9e, 0x5d, 0x6b, + 0xad, 0xe7, 0xd7, 0x5a, 0xeb, 0xdb, 0x44, 0x03, 0x57, 0x89, 0x06, 0x9e, 0x25, 0x1a, 0x78, 0x9e, + 0x68, 0xe0, 0xcf, 0x44, 0x03, 0x3f, 0xfe, 0xa5, 0xb5, 0xbe, 0x68, 0x2f, 0xa7, 0xff, 0x04, 0x00, + 0x00, 0xff, 0xff, 0x32, 0xe3, 0x23, 0xf8, 0x2e, 0x0b, 0x00, 0x00, } diff --git a/staging/src/k8s.io/api/rbac/v1/generated.proto b/staging/src/k8s.io/api/rbac/v1/generated.proto index 29aa3d5eebd..2f8d863df1d 100644 --- a/staging/src/k8s.io/api/rbac/v1/generated.proto +++ b/staging/src/k8s.io/api/rbac/v1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,6 +21,7 @@ syntax = 'proto2'; package k8s.io.api.rbac.v1; +import "k8s.io/api/rbac/v1alpha1/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"; @@ -29,6 +30,14 @@ import "k8s.io/apimachinery/pkg/util/intstr/generated.proto"; // Package-wide variables from generator "generated". option go_package = "v1"; +// AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole +message AggregationRule { + // ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. + // If any of the selectors match, then the ClusterRole's permissions will be added + // +optional + repeated k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector clusterRoleSelectors = 1; +} + // ClusterRole is a cluster level, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding or ClusterRoleBinding. message ClusterRole { // Standard object's metadata. @@ -37,6 +46,12 @@ message ClusterRole { // Rules holds all the PolicyRules for this ClusterRole repeated PolicyRule rules = 2; + + // 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 + // stomped by the controller. + // +optional + optional AggregationRule aggregationRule = 3; } // ClusterRoleBinding references a ClusterRole, but not contain it. It can reference a ClusterRole in the global namespace, diff --git a/staging/src/k8s.io/api/rbac/v1/register.go b/staging/src/k8s.io/api/rbac/v1/register.go index 7336b5455e6..8f1fd460a21 100644 --- a/staging/src/k8s.io/api/rbac/v1/register.go +++ b/staging/src/k8s.io/api/rbac/v1/register.go @@ -40,7 +40,7 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Role{}, diff --git a/staging/src/k8s.io/api/rbac/v1/types.go b/staging/src/k8s.io/api/rbac/v1/types.go index 8dbd1a8b89a..91990548bc4 100644 --- a/staging/src/k8s.io/api/rbac/v1/types.go +++ b/staging/src/k8s.io/api/rbac/v1/types.go @@ -170,6 +170,20 @@ type ClusterRole struct { // Rules holds all the PolicyRules for this ClusterRole 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 + // stomped by the controller. + // +optional + AggregationRule *AggregationRule `json:"aggregationRule,omitempty" protobuf:"bytes,3,opt,name=aggregationRule"` +} + +// AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole +type AggregationRule struct { + // ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. + // If any of the selectors match, then the ClusterRole's permissions will be added + // +optional + ClusterRoleSelectors []metav1.LabelSelector `json:"clusterRoleSelectors,omitempty" protobuf:"bytes,1,rep,name=clusterRoleSelectors"` } // +genclient diff --git a/staging/src/k8s.io/api/rbac/v1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/rbac/v1/types_swagger_doc_generated.go index 7770d4085b1..280ae5a82f1 100644 --- a/staging/src/k8s.io/api/rbac/v1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/rbac/v1/types_swagger_doc_generated.go @@ -27,10 +27,20 @@ package v1 // Those methods can be generated by using hack/update-generated-swagger-docs.sh // AUTO-GENERATED FUNCTIONS START HERE +var map_AggregationRule = map[string]string{ + "": "AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole", + "clusterRoleSelectors": "ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. If any of the selectors match, then the ClusterRole's permissions will be added", +} + +func (AggregationRule) SwaggerDoc() map[string]string { + return map_AggregationRule +} + var map_ClusterRole = map[string]string{ - "": "ClusterRole is a cluster level, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding or ClusterRoleBinding.", - "metadata": "Standard object's metadata.", - "rules": "Rules holds all the PolicyRules for this ClusterRole", + "": "ClusterRole is a cluster level, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding or ClusterRoleBinding.", + "metadata": "Standard object's metadata.", + "rules": "Rules holds all the PolicyRules for this ClusterRole", + "aggregationRule": "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 stomped by the controller.", } func (ClusterRole) SwaggerDoc() map[string]string { diff --git a/staging/src/k8s.io/api/rbac/v1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/rbac/v1/zz_generated.deepcopy.go index 7ffc81869d2..085edaa121c 100644 --- a/staging/src/k8s.io/api/rbac/v1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/rbac/v1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,66 +21,31 @@ limitations under the License. package v1 import ( - conversion "k8s.io/apimachinery/pkg/conversion" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AggregationRule) DeepCopyInto(out *AggregationRule) { + *out = *in + if in.ClusterRoleSelectors != nil { + in, out := &in.ClusterRoleSelectors, &out.ClusterRoleSelectors + *out = make([]meta_v1.LabelSelector, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return } -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterRole).DeepCopyInto(out.(*ClusterRole)) - return nil - }, InType: reflect.TypeOf(&ClusterRole{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterRoleBinding).DeepCopyInto(out.(*ClusterRoleBinding)) - return nil - }, InType: reflect.TypeOf(&ClusterRoleBinding{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterRoleBindingList).DeepCopyInto(out.(*ClusterRoleBindingList)) - return nil - }, InType: reflect.TypeOf(&ClusterRoleBindingList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterRoleList).DeepCopyInto(out.(*ClusterRoleList)) - return nil - }, InType: reflect.TypeOf(&ClusterRoleList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PolicyRule).DeepCopyInto(out.(*PolicyRule)) - return nil - }, InType: reflect.TypeOf(&PolicyRule{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Role).DeepCopyInto(out.(*Role)) - return nil - }, InType: reflect.TypeOf(&Role{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RoleBinding).DeepCopyInto(out.(*RoleBinding)) - return nil - }, InType: reflect.TypeOf(&RoleBinding{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RoleBindingList).DeepCopyInto(out.(*RoleBindingList)) - return nil - }, InType: reflect.TypeOf(&RoleBindingList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RoleList).DeepCopyInto(out.(*RoleList)) - return nil - }, InType: reflect.TypeOf(&RoleList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RoleRef).DeepCopyInto(out.(*RoleRef)) - return nil - }, InType: reflect.TypeOf(&RoleRef{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Subject).DeepCopyInto(out.(*Subject)) - return nil - }, InType: reflect.TypeOf(&Subject{})}, - ) +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AggregationRule. +func (in *AggregationRule) DeepCopy() *AggregationRule { + if in == nil { + return nil + } + out := new(AggregationRule) + in.DeepCopyInto(out) + return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -95,6 +60,15 @@ func (in *ClusterRole) DeepCopyInto(out *ClusterRole) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.AggregationRule != nil { + in, out := &in.AggregationRule, &out.AggregationRule + if *in == nil { + *out = nil + } else { + *out = new(AggregationRule) + (*in).DeepCopyInto(*out) + } + } return } diff --git a/staging/src/k8s.io/api/rbac/v1alpha1/BUILD b/staging/src/k8s.io/api/rbac/v1alpha1/BUILD index cd794228632..68d7cdfbb1d 100644 --- a/staging/src/k8s.io/api/rbac/v1alpha1/BUILD +++ b/staging/src/k8s.io/api/rbac/v1alpha1/BUILD @@ -19,7 +19,6 @@ go_library( deps = [ "//vendor/github.com/gogo/protobuf/proto:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/staging/src/k8s.io/api/rbac/v1alpha1/doc.go b/staging/src/k8s.io/api/rbac/v1alpha1/doc.go index 619b47bf25e..5236a477f00 100644 --- a/staging/src/k8s.io/api/rbac/v1alpha1/doc.go +++ b/staging/src/k8s.io/api/rbac/v1alpha1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:openapi-gen=true // +groupName=rbac.authorization.k8s.io diff --git a/staging/src/k8s.io/api/rbac/v1alpha1/generated.pb.go b/staging/src/k8s.io/api/rbac/v1alpha1/generated.pb.go index 31e68aeedaa..c07cdc75dc7 100644 --- a/staging/src/k8s.io/api/rbac/v1alpha1/generated.pb.go +++ b/staging/src/k8s.io/api/rbac/v1alpha1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -25,6 +25,7 @@ limitations under the License. k8s.io/kubernetes/vendor/k8s.io/api/rbac/v1alpha1/generated.proto It has these top-level messages: + AggregationRule ClusterRole ClusterRoleBinding ClusterRoleBindingList @@ -43,6 +44,8 @@ import proto "github.com/gogo/protobuf/proto" import fmt "fmt" import math "math" +import k8s_io_apimachinery_pkg_apis_meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + import strings "strings" import reflect "reflect" @@ -59,51 +62,56 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package +func (m *AggregationRule) Reset() { *m = AggregationRule{} } +func (*AggregationRule) ProtoMessage() {} +func (*AggregationRule) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{0} } + func (m *ClusterRole) Reset() { *m = ClusterRole{} } func (*ClusterRole) ProtoMessage() {} -func (*ClusterRole) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{0} } +func (*ClusterRole) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{1} } func (m *ClusterRoleBinding) Reset() { *m = ClusterRoleBinding{} } func (*ClusterRoleBinding) ProtoMessage() {} -func (*ClusterRoleBinding) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{1} } +func (*ClusterRoleBinding) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{2} } func (m *ClusterRoleBindingList) Reset() { *m = ClusterRoleBindingList{} } func (*ClusterRoleBindingList) ProtoMessage() {} -func (*ClusterRoleBindingList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{2} } +func (*ClusterRoleBindingList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{3} } func (m *ClusterRoleList) Reset() { *m = ClusterRoleList{} } func (*ClusterRoleList) ProtoMessage() {} -func (*ClusterRoleList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{3} } +func (*ClusterRoleList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{4} } func (m *PolicyRule) Reset() { *m = PolicyRule{} } func (*PolicyRule) ProtoMessage() {} -func (*PolicyRule) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{4} } +func (*PolicyRule) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{5} } func (m *Role) Reset() { *m = Role{} } func (*Role) ProtoMessage() {} -func (*Role) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{5} } +func (*Role) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{6} } func (m *RoleBinding) Reset() { *m = RoleBinding{} } func (*RoleBinding) ProtoMessage() {} -func (*RoleBinding) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{6} } +func (*RoleBinding) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{7} } func (m *RoleBindingList) Reset() { *m = RoleBindingList{} } func (*RoleBindingList) ProtoMessage() {} -func (*RoleBindingList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{7} } +func (*RoleBindingList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{8} } func (m *RoleList) Reset() { *m = RoleList{} } func (*RoleList) ProtoMessage() {} -func (*RoleList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{8} } +func (*RoleList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{9} } func (m *RoleRef) Reset() { *m = RoleRef{} } func (*RoleRef) ProtoMessage() {} -func (*RoleRef) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{9} } +func (*RoleRef) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{10} } func (m *Subject) Reset() { *m = Subject{} } func (*Subject) ProtoMessage() {} -func (*Subject) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{10} } +func (*Subject) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{11} } func init() { + proto.RegisterType((*AggregationRule)(nil), "k8s.io.api.rbac.v1alpha1.AggregationRule") proto.RegisterType((*ClusterRole)(nil), "k8s.io.api.rbac.v1alpha1.ClusterRole") proto.RegisterType((*ClusterRoleBinding)(nil), "k8s.io.api.rbac.v1alpha1.ClusterRoleBinding") proto.RegisterType((*ClusterRoleBindingList)(nil), "k8s.io.api.rbac.v1alpha1.ClusterRoleBindingList") @@ -116,6 +124,36 @@ func init() { proto.RegisterType((*RoleRef)(nil), "k8s.io.api.rbac.v1alpha1.RoleRef") proto.RegisterType((*Subject)(nil), "k8s.io.api.rbac.v1alpha1.Subject") } +func (m *AggregationRule) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AggregationRule) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.ClusterRoleSelectors) > 0 { + for _, msg := range m.ClusterRoleSelectors { + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + func (m *ClusterRole) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -151,6 +189,16 @@ func (m *ClusterRole) MarshalTo(dAtA []byte) (int, error) { i += n } } + if m.AggregationRule != nil { + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.AggregationRule.Size())) + n2, err := m.AggregationRule.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n2 + } return i, nil } @@ -172,11 +220,11 @@ func (m *ClusterRoleBinding) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n2, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n3, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n2 + i += n3 if len(m.Subjects) > 0 { for _, msg := range m.Subjects { dAtA[i] = 0x12 @@ -192,11 +240,11 @@ func (m *ClusterRoleBinding) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.RoleRef.Size())) - n3, err := m.RoleRef.MarshalTo(dAtA[i:]) + n4, err := m.RoleRef.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n3 + i += n4 return i, nil } @@ -218,11 +266,11 @@ func (m *ClusterRoleBindingList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n4, err := m.ListMeta.MarshalTo(dAtA[i:]) + n5, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n4 + i += n5 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -256,11 +304,11 @@ func (m *ClusterRoleList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n5, err := m.ListMeta.MarshalTo(dAtA[i:]) + n6, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n5 + i += n6 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -387,11 +435,11 @@ func (m *Role) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n6, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n7, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n6 + i += n7 if len(m.Rules) > 0 { for _, msg := range m.Rules { dAtA[i] = 0x12 @@ -425,11 +473,11 @@ func (m *RoleBinding) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n7, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n8, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n7 + i += n8 if len(m.Subjects) > 0 { for _, msg := range m.Subjects { dAtA[i] = 0x12 @@ -445,11 +493,11 @@ func (m *RoleBinding) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.RoleRef.Size())) - n8, err := m.RoleRef.MarshalTo(dAtA[i:]) + n9, err := m.RoleRef.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n8 + i += n9 return i, nil } @@ -471,11 +519,11 @@ func (m *RoleBindingList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n9, err := m.ListMeta.MarshalTo(dAtA[i:]) + n10, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n9 + i += n10 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -509,11 +557,11 @@ func (m *RoleList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n10, err := m.ListMeta.MarshalTo(dAtA[i:]) + n11, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n10 + i += n11 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -620,6 +668,18 @@ func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return offset + 1 } +func (m *AggregationRule) Size() (n int) { + var l int + _ = l + if len(m.ClusterRoleSelectors) > 0 { + for _, e := range m.ClusterRoleSelectors { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + func (m *ClusterRole) Size() (n int) { var l int _ = l @@ -631,6 +691,10 @@ func (m *ClusterRole) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) } } + if m.AggregationRule != nil { + l = m.AggregationRule.Size() + n += 1 + l + sovGenerated(uint64(l)) + } return n } @@ -811,6 +875,16 @@ func sovGenerated(x uint64) (n int) { func sozGenerated(x uint64) (n int) { return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } +func (this *AggregationRule) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&AggregationRule{`, + `ClusterRoleSelectors:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ClusterRoleSelectors), "LabelSelector", "k8s_io_apimachinery_pkg_apis_meta_v1.LabelSelector", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} func (this *ClusterRole) String() string { if this == nil { return "nil" @@ -818,6 +892,7 @@ func (this *ClusterRole) String() string { s := strings.Join([]string{`&ClusterRole{`, `ObjectMeta:` + strings.Replace(strings.Replace(this.ObjectMeta.String(), "ObjectMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ObjectMeta", 1), `&`, ``, 1) + `,`, `Rules:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Rules), "PolicyRule", "PolicyRule", 1), `&`, ``, 1) + `,`, + `AggregationRule:` + strings.Replace(fmt.Sprintf("%v", this.AggregationRule), "AggregationRule", "AggregationRule", 1) + `,`, `}`, }, "") return s @@ -948,6 +1023,87 @@ func valueToStringGenerated(v interface{}) string { pv := reflect.Indirect(rv).Interface() return fmt.Sprintf("*%v", pv) } +func (m *AggregationRule) 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: AggregationRule: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AggregationRule: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClusterRoleSelectors", 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 > l { + return io.ErrUnexpectedEOF + } + m.ClusterRoleSelectors = append(m.ClusterRoleSelectors, k8s_io_apimachinery_pkg_apis_meta_v1.LabelSelector{}) + if err := m.ClusterRoleSelectors[len(m.ClusterRoleSelectors)-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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *ClusterRole) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -1038,6 +1194,39 @@ func (m *ClusterRole) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AggregationRule", 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 > l { + return io.ErrUnexpectedEOF + } + if m.AggregationRule == nil { + m.AggregationRule = &AggregationRule{} + } + if err := m.AggregationRule.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -2504,53 +2693,58 @@ func init() { } var fileDescriptorGenerated = []byte{ - // 766 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x94, 0xcd, 0x6e, 0xd3, 0x40, - 0x10, 0xc7, 0xb3, 0xf9, 0xa0, 0xc9, 0x86, 0x28, 0xd4, 0x48, 0xc8, 0xea, 0xc1, 0x09, 0x11, 0x48, - 0x95, 0x28, 0x36, 0x2d, 0x08, 0xb8, 0x70, 0x68, 0x7a, 0x40, 0x81, 0xd2, 0x96, 0x45, 0xf4, 0x80, - 0x38, 0xb0, 0x71, 0xb6, 0xc9, 0x12, 0x7f, 0x69, 0xd7, 0x8e, 0x54, 0x71, 0xe1, 0x09, 0x10, 0x17, - 0x1e, 0x83, 0x0b, 0xdc, 0xe0, 0x05, 0xca, 0xad, 0xc7, 0x9e, 0x22, 0x6a, 0x1e, 0x04, 0xb4, 0x6b, - 0x3b, 0x4e, 0x9a, 0x86, 0xf4, 0x14, 0x09, 0x89, 0x93, 0xbd, 0x33, 0xbf, 0xf9, 0xef, 0xcc, 0xec, - 0xee, 0xc0, 0xcd, 0xfe, 0x43, 0xae, 0x53, 0xd7, 0xe8, 0x07, 0x6d, 0xc2, 0x1c, 0xe2, 0x13, 0x6e, - 0x0c, 0x88, 0xd3, 0x71, 0x99, 0x11, 0x3b, 0xb0, 0x47, 0x0d, 0xd6, 0xc6, 0xa6, 0x31, 0x58, 0xc7, - 0x96, 0xd7, 0xc3, 0xeb, 0x46, 0x97, 0x38, 0x84, 0x61, 0x9f, 0x74, 0x74, 0x8f, 0xb9, 0xbe, 0xab, - 0xa8, 0x11, 0xa9, 0x63, 0x8f, 0xea, 0x82, 0xd4, 0x13, 0x72, 0xe5, 0x76, 0x97, 0xfa, 0xbd, 0xa0, - 0xad, 0x9b, 0xae, 0x6d, 0x74, 0xdd, 0xae, 0x6b, 0xc8, 0x80, 0x76, 0x70, 0x20, 0x57, 0x72, 0x21, - 0xff, 0x22, 0xa1, 0x95, 0x7b, 0xe9, 0x96, 0x36, 0x36, 0x7b, 0xd4, 0x21, 0xec, 0xd0, 0xf0, 0xfa, - 0x5d, 0x61, 0xe0, 0x86, 0x4d, 0x7c, 0x6c, 0x0c, 0xa6, 0xb6, 0x5f, 0x31, 0x66, 0x45, 0xb1, 0xc0, - 0xf1, 0xa9, 0x4d, 0xa6, 0x02, 0xee, 0xcf, 0x0b, 0xe0, 0x66, 0x8f, 0xd8, 0x78, 0x2a, 0xee, 0xee, - 0xac, 0xb8, 0xc0, 0xa7, 0x96, 0x41, 0x1d, 0x9f, 0xfb, 0xec, 0x6c, 0x50, 0xe3, 0x1b, 0x80, 0xe5, - 0x2d, 0x2b, 0xe0, 0x3e, 0x61, 0xc8, 0xb5, 0x88, 0xf2, 0x06, 0x16, 0x45, 0x21, 0x1d, 0xec, 0x63, - 0x15, 0xd4, 0xc1, 0x6a, 0x79, 0xe3, 0x8e, 0x9e, 0xf6, 0x6f, 0xa4, 0xab, 0x7b, 0xfd, 0xae, 0x30, - 0x70, 0x5d, 0xd0, 0xfa, 0x60, 0x5d, 0xdf, 0x6d, 0xbf, 0x25, 0xa6, 0xff, 0x8c, 0xf8, 0xb8, 0xa9, - 0x1c, 0x0d, 0x6b, 0x99, 0x70, 0x58, 0x83, 0xa9, 0x0d, 0x8d, 0x54, 0x95, 0x16, 0x2c, 0xb0, 0xc0, - 0x22, 0x5c, 0xcd, 0xd6, 0x73, 0xab, 0xe5, 0x8d, 0x1b, 0xfa, 0xac, 0xe3, 0xd1, 0xf7, 0x5c, 0x8b, - 0x9a, 0x87, 0x28, 0xb0, 0x48, 0xb3, 0x12, 0x4b, 0x16, 0xc4, 0x8a, 0xa3, 0x48, 0xa1, 0xf1, 0x29, - 0x0b, 0x95, 0xb1, 0xe4, 0x9b, 0xd4, 0xe9, 0x50, 0xa7, 0xbb, 0x80, 0x1a, 0x76, 0x61, 0x91, 0x07, - 0xd2, 0x91, 0x94, 0x71, 0x7d, 0x76, 0x19, 0x2f, 0x22, 0xb2, 0x79, 0x25, 0x96, 0x2c, 0xc6, 0x06, - 0x8e, 0x46, 0x22, 0xca, 0x36, 0x5c, 0x62, 0xae, 0x45, 0x10, 0x39, 0x50, 0x73, 0x32, 0xe3, 0xbf, - 0xe8, 0xa1, 0x08, 0x6c, 0x56, 0x63, 0xbd, 0xa5, 0xd8, 0x80, 0x12, 0x89, 0xc6, 0x0f, 0x00, 0xaf, - 0x4d, 0xf7, 0x65, 0x9b, 0x72, 0x5f, 0x79, 0x3d, 0xd5, 0x1b, 0xfd, 0x62, 0xbd, 0x11, 0xd1, 0xb2, - 0x33, 0xa3, 0x32, 0x12, 0xcb, 0x58, 0x5f, 0x9e, 0xc3, 0x02, 0xf5, 0x89, 0x9d, 0x34, 0x65, 0x6d, - 0x76, 0x11, 0xd3, 0xe9, 0xa5, 0x67, 0xdc, 0x12, 0x12, 0x28, 0x52, 0x6a, 0x7c, 0x07, 0xb0, 0x3a, - 0x06, 0x2f, 0xa0, 0x88, 0x27, 0x93, 0x45, 0xdc, 0xbc, 0x58, 0x11, 0xe7, 0x67, 0xff, 0x1b, 0x40, - 0x98, 0x5e, 0x63, 0xa5, 0x06, 0x0b, 0x03, 0xc2, 0xda, 0x5c, 0x05, 0xf5, 0xdc, 0x6a, 0xa9, 0x59, - 0x12, 0xfc, 0xbe, 0x30, 0xa0, 0xc8, 0xae, 0xdc, 0x82, 0x25, 0xec, 0xd1, 0xc7, 0xcc, 0x0d, 0x3c, - 0xae, 0xe6, 0x24, 0x54, 0x09, 0x87, 0xb5, 0xd2, 0xe6, 0x5e, 0x2b, 0x32, 0xa2, 0xd4, 0x2f, 0x60, - 0x46, 0xb8, 0x1b, 0x30, 0x93, 0x70, 0x35, 0x9f, 0xc2, 0x28, 0x31, 0xa2, 0xd4, 0xaf, 0x3c, 0x80, - 0x95, 0x64, 0xb1, 0x83, 0x6d, 0xc2, 0xd5, 0x82, 0x0c, 0x58, 0x0e, 0x87, 0xb5, 0x0a, 0x1a, 0x77, - 0xa0, 0x49, 0x4e, 0x79, 0x04, 0xab, 0x8e, 0xeb, 0x24, 0xc8, 0x4b, 0xb4, 0xcd, 0xd5, 0x4b, 0x32, - 0xf4, 0x6a, 0x38, 0xac, 0x55, 0x77, 0x26, 0x5d, 0xe8, 0x2c, 0xdb, 0xf8, 0x0a, 0x60, 0xfe, 0xdf, - 0x9b, 0x2c, 0x1f, 0xb2, 0xb0, 0xfc, 0x7f, 0xa4, 0x8c, 0x8d, 0x14, 0xf1, 0x0c, 0x17, 0x3b, 0x4b, - 0x2e, 0xfe, 0x0c, 0xe7, 0x0f, 0x91, 0xcf, 0x00, 0x16, 0x17, 0x34, 0x3d, 0xb6, 0x26, 0xd3, 0xd6, - 0xe6, 0xa4, 0x7d, 0x7e, 0xbe, 0xef, 0x60, 0x72, 0x02, 0xca, 0x1a, 0x2c, 0x26, 0x2f, 0x5e, 0x66, - 0x5b, 0x4a, 0x77, 0x4f, 0x86, 0x02, 0x1a, 0x11, 0x4a, 0x1d, 0xe6, 0xfb, 0xd4, 0xe9, 0xa8, 0x59, - 0x49, 0x5e, 0x8e, 0xc9, 0xfc, 0x53, 0xea, 0x74, 0x90, 0xf4, 0x08, 0xc2, 0xc1, 0x36, 0x91, 0x77, - 0x62, 0x8c, 0x10, 0x6f, 0x1d, 0x49, 0x4f, 0xe3, 0x0b, 0x80, 0x4b, 0xf1, 0x7d, 0x1a, 0xe9, 0x81, - 0x99, 0x7a, 0x1b, 0x10, 0x62, 0x8f, 0xee, 0x13, 0xc6, 0xa9, 0xeb, 0xc4, 0xfb, 0x8e, 0x6e, 0xfa, - 0xe6, 0x5e, 0x2b, 0xf6, 0xa0, 0x31, 0x6a, 0x7e, 0x0e, 0x8a, 0x01, 0x4b, 0xe2, 0xcb, 0x3d, 0x6c, - 0x12, 0x35, 0x2f, 0xb1, 0xe5, 0x18, 0x2b, 0xed, 0x24, 0x0e, 0x94, 0x32, 0x4d, 0xfd, 0xe8, 0x54, - 0xcb, 0x1c, 0x9f, 0x6a, 0x99, 0x93, 0x53, 0x2d, 0xf3, 0x3e, 0xd4, 0xc0, 0x51, 0xa8, 0x81, 0xe3, - 0x50, 0x03, 0x27, 0xa1, 0x06, 0x7e, 0x86, 0x1a, 0xf8, 0xf8, 0x4b, 0xcb, 0xbc, 0x2a, 0x26, 0xcd, - 0xff, 0x13, 0x00, 0x00, 0xff, 0xff, 0x1d, 0x38, 0x05, 0x46, 0x58, 0x0a, 0x00, 0x00, + // 844 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x55, 0xbf, 0x8f, 0xe3, 0x44, + 0x14, 0xce, 0x64, 0x13, 0x36, 0x99, 0x65, 0x15, 0x6e, 0x38, 0x21, 0x6b, 0x85, 0x9c, 0xc5, 0x02, + 0xe9, 0x10, 0x87, 0xcd, 0xee, 0x21, 0xa0, 0xa1, 0x58, 0x5f, 0x81, 0x16, 0x96, 0xbd, 0x65, 0x4e, + 0x5c, 0x81, 0x28, 0x98, 0x38, 0x73, 0xce, 0x10, 0xdb, 0x63, 0xcd, 0x8c, 0x23, 0x9d, 0x68, 0x68, + 0x68, 0x11, 0x0d, 0x05, 0x3d, 0x2d, 0x0d, 0x94, 0xfc, 0x03, 0x4b, 0x77, 0xe5, 0x56, 0x11, 0x6b, + 0xfe, 0x10, 0x90, 0xc7, 0x76, 0xec, 0xfc, 0x22, 0xa9, 0x22, 0x21, 0x51, 0x25, 0xf3, 0xde, 0xf7, + 0xbe, 0xf7, 0xde, 0x37, 0xf3, 0x9e, 0xe1, 0xd9, 0xf8, 0x03, 0x69, 0x33, 0xee, 0x8c, 0x93, 0x01, + 0x15, 0x11, 0x55, 0x54, 0x3a, 0x13, 0x1a, 0x0d, 0xb9, 0x70, 0x0a, 0x07, 0x89, 0x99, 0x23, 0x06, + 0xc4, 0x73, 0x26, 0x27, 0x24, 0x88, 0x47, 0xe4, 0xc4, 0xf1, 0x69, 0x44, 0x05, 0x51, 0x74, 0x68, + 0xc7, 0x82, 0x2b, 0x8e, 0x8c, 0x1c, 0x69, 0x93, 0x98, 0xd9, 0x19, 0xd2, 0x2e, 0x91, 0x47, 0x6f, + 0xfb, 0x4c, 0x8d, 0x92, 0x81, 0xed, 0xf1, 0xd0, 0xf1, 0xb9, 0xcf, 0x1d, 0x1d, 0x30, 0x48, 0x9e, + 0xea, 0x93, 0x3e, 0xe8, 0x7f, 0x39, 0xd1, 0xd1, 0xbb, 0x55, 0xca, 0x90, 0x78, 0x23, 0x16, 0x51, + 0xf1, 0xcc, 0x89, 0xc7, 0x7e, 0x66, 0x90, 0x4e, 0x48, 0x15, 0x71, 0x26, 0x4b, 0xe9, 0x8f, 0x9c, + 0x75, 0x51, 0x22, 0x89, 0x14, 0x0b, 0xe9, 0x52, 0xc0, 0x7b, 0x9b, 0x02, 0xa4, 0x37, 0xa2, 0x21, + 0x59, 0x8a, 0x7b, 0xb0, 0x2e, 0x2e, 0x51, 0x2c, 0x70, 0x58, 0xa4, 0xa4, 0x12, 0x8b, 0x41, 0xd6, + 0x4f, 0x00, 0xf6, 0xce, 0x7c, 0x5f, 0x50, 0x9f, 0x28, 0xc6, 0x23, 0x9c, 0x04, 0x14, 0x7d, 0x07, + 0xe0, 0x5d, 0x2f, 0x48, 0xa4, 0xa2, 0x02, 0xf3, 0x80, 0x3e, 0xa6, 0x01, 0xf5, 0x14, 0x17, 0xd2, + 0x00, 0xc7, 0x7b, 0xf7, 0x0e, 0x4e, 0x1f, 0xd8, 0x95, 0xa0, 0xb3, 0x44, 0x76, 0x3c, 0xf6, 0x33, + 0x83, 0xb4, 0x33, 0x1d, 0xec, 0xc9, 0x89, 0x7d, 0x41, 0x06, 0x34, 0x28, 0x63, 0xdd, 0x57, 0xaf, + 0xa7, 0xfd, 0x46, 0x3a, 0xed, 0xdf, 0x7d, 0xb8, 0x82, 0x18, 0xaf, 0x4c, 0x67, 0xfd, 0xdc, 0x84, + 0x07, 0x35, 0x38, 0xfa, 0x0a, 0x76, 0x32, 0xf2, 0x21, 0x51, 0xc4, 0x00, 0xc7, 0xe0, 0xde, 0xc1, + 0xe9, 0x3b, 0xdb, 0x95, 0xf2, 0x68, 0xf0, 0x35, 0xf5, 0xd4, 0xa7, 0x54, 0x11, 0x17, 0x15, 0x75, + 0xc0, 0xca, 0x86, 0x67, 0xac, 0xe8, 0x1c, 0xb6, 0x45, 0x12, 0x50, 0x69, 0x34, 0x75, 0xa7, 0xaf, + 0xdb, 0xeb, 0x9e, 0x8e, 0x7d, 0xc5, 0x03, 0xe6, 0x3d, 0xcb, 0xe4, 0x72, 0x0f, 0x0b, 0xca, 0x76, + 0x76, 0x92, 0x38, 0x67, 0x40, 0x23, 0xd8, 0x23, 0xf3, 0xba, 0x1a, 0x7b, 0xba, 0xe6, 0x37, 0xd7, + 0x93, 0x2e, 0x5c, 0x84, 0xfb, 0x72, 0x3a, 0xed, 0x2f, 0xde, 0x0e, 0x5e, 0xa4, 0xb5, 0x7e, 0x6c, + 0x42, 0x54, 0x93, 0xc9, 0x65, 0xd1, 0x90, 0x45, 0xfe, 0x0e, 0xd4, 0x7a, 0x04, 0x3b, 0x32, 0xd1, + 0x8e, 0x52, 0xb0, 0xd7, 0xd6, 0xf7, 0xf6, 0x38, 0x47, 0xba, 0x2f, 0x15, 0x94, 0x9d, 0xc2, 0x20, + 0xf1, 0x8c, 0x04, 0x5d, 0xc0, 0x7d, 0xc1, 0x03, 0x8a, 0xe9, 0xd3, 0x42, 0xab, 0x7f, 0xe1, 0xc3, + 0x39, 0xd0, 0xed, 0x15, 0x7c, 0xfb, 0x85, 0x01, 0x97, 0x14, 0xd6, 0x1f, 0x00, 0xbe, 0xb2, 0xac, + 0xcb, 0x05, 0x93, 0x0a, 0x7d, 0xb9, 0xa4, 0x8d, 0xbd, 0xe5, 0xa3, 0x66, 0x32, 0x57, 0x66, 0xd6, + 0x46, 0x69, 0xa9, 0xe9, 0xf2, 0x19, 0x6c, 0x33, 0x45, 0xc3, 0x52, 0x94, 0xfb, 0xeb, 0x9b, 0x58, + 0x2e, 0xaf, 0x7a, 0x4d, 0xe7, 0x19, 0x05, 0xce, 0x99, 0xac, 0xdf, 0x01, 0xec, 0xd5, 0xc0, 0x3b, + 0x68, 0xe2, 0xe3, 0xf9, 0x26, 0xde, 0xd8, 0xae, 0x89, 0xd5, 0xd5, 0xff, 0x0d, 0x20, 0xac, 0x06, + 0x06, 0xf5, 0x61, 0x7b, 0x42, 0xc5, 0x20, 0xdf, 0x27, 0x5d, 0xb7, 0x9b, 0xe1, 0x9f, 0x64, 0x06, + 0x9c, 0xdb, 0xd1, 0x5b, 0xb0, 0x4b, 0x62, 0xf6, 0x91, 0xe0, 0x49, 0x2c, 0x8d, 0x3d, 0x0d, 0x3a, + 0x4c, 0xa7, 0xfd, 0xee, 0xd9, 0xd5, 0x79, 0x6e, 0xc4, 0x95, 0x3f, 0x03, 0x0b, 0x2a, 0x79, 0x22, + 0x3c, 0x2a, 0x8d, 0x56, 0x05, 0xc6, 0xa5, 0x11, 0x57, 0x7e, 0xf4, 0x3e, 0x3c, 0x2c, 0x0f, 0x97, + 0x24, 0xa4, 0xd2, 0x68, 0xeb, 0x80, 0x3b, 0xe9, 0xb4, 0x7f, 0x88, 0xeb, 0x0e, 0x3c, 0x8f, 0x43, + 0x1f, 0xc2, 0x5e, 0xc4, 0xa3, 0x12, 0xf2, 0x39, 0xbe, 0x90, 0xc6, 0x0b, 0x3a, 0x54, 0xcf, 0xe8, + 0xe5, 0xbc, 0x0b, 0x2f, 0x62, 0xad, 0xdf, 0x00, 0x6c, 0xfd, 0xe7, 0x76, 0x98, 0xf5, 0x7d, 0x13, + 0x1e, 0xfc, 0xbf, 0x52, 0x6a, 0x2b, 0x25, 0x1b, 0xc3, 0xdd, 0xee, 0x92, 0xed, 0xc7, 0x70, 0xf3, + 0x12, 0xf9, 0x05, 0xc0, 0xce, 0x8e, 0xb6, 0xc7, 0xc3, 0xf9, 0xb2, 0xcd, 0x0d, 0x65, 0xaf, 0xae, + 0xf7, 0x1b, 0x58, 0xde, 0x00, 0xba, 0x0f, 0x3b, 0xe5, 0xc4, 0xeb, 0x6a, 0xbb, 0x55, 0xf6, 0x72, + 0x29, 0xe0, 0x19, 0x02, 0x1d, 0xc3, 0xd6, 0x98, 0x45, 0x43, 0xa3, 0xa9, 0x91, 0x2f, 0x16, 0xc8, + 0xd6, 0x27, 0x2c, 0x1a, 0x62, 0xed, 0xc9, 0x10, 0x11, 0x09, 0xf3, 0x4f, 0x72, 0x0d, 0x91, 0xcd, + 0x3a, 0xd6, 0x1e, 0xeb, 0x57, 0x00, 0xf7, 0x8b, 0xf7, 0x34, 0xe3, 0x03, 0x6b, 0xf9, 0x4e, 0x21, + 0x24, 0x31, 0x7b, 0x42, 0x85, 0x64, 0x3c, 0x2a, 0xf2, 0xce, 0x5e, 0xfa, 0xd9, 0xd5, 0x79, 0xe1, + 0xc1, 0x35, 0xd4, 0xe6, 0x1a, 0x90, 0x03, 0xbb, 0xd9, 0xaf, 0x8c, 0x89, 0x47, 0x8d, 0x96, 0x86, + 0xdd, 0x29, 0x60, 0xdd, 0xcb, 0xd2, 0x81, 0x2b, 0x8c, 0x6b, 0x5f, 0xdf, 0x9a, 0x8d, 0xe7, 0xb7, + 0x66, 0xe3, 0xe6, 0xd6, 0x6c, 0x7c, 0x9b, 0x9a, 0xe0, 0x3a, 0x35, 0xc1, 0xf3, 0xd4, 0x04, 0x37, + 0xa9, 0x09, 0xfe, 0x4c, 0x4d, 0xf0, 0xc3, 0x5f, 0x66, 0xe3, 0x8b, 0x4e, 0x29, 0xfe, 0x3f, 0x01, + 0x00, 0x00, 0xff, 0xff, 0xeb, 0xcc, 0xe2, 0x61, 0x5e, 0x0b, 0x00, 0x00, } diff --git a/staging/src/k8s.io/api/rbac/v1alpha1/generated.proto b/staging/src/k8s.io/api/rbac/v1alpha1/generated.proto index 89f45b28e71..41a193f55d0 100644 --- a/staging/src/k8s.io/api/rbac/v1alpha1/generated.proto +++ b/staging/src/k8s.io/api/rbac/v1alpha1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -29,6 +29,14 @@ import "k8s.io/apimachinery/pkg/util/intstr/generated.proto"; // Package-wide variables from generator "generated". option go_package = "v1alpha1"; +// AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole +message AggregationRule { + // ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. + // If any of the selectors match, then the ClusterRole's permissions will be added + // +optional + repeated k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector clusterRoleSelectors = 1; +} + // ClusterRole is a cluster level, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding or ClusterRoleBinding. message ClusterRole { // Standard object's metadata. @@ -37,6 +45,12 @@ message ClusterRole { // Rules holds all the PolicyRules for this ClusterRole repeated PolicyRule rules = 2; + + // 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 + // stomped by the controller. + // +optional + optional AggregationRule aggregationRule = 3; } // ClusterRoleBinding references a ClusterRole, but not contain it. It can reference a ClusterRole in the global namespace, diff --git a/staging/src/k8s.io/api/rbac/v1alpha1/register.go b/staging/src/k8s.io/api/rbac/v1alpha1/register.go index 9cfd71f451e..0c6977685be 100644 --- a/staging/src/k8s.io/api/rbac/v1alpha1/register.go +++ b/staging/src/k8s.io/api/rbac/v1alpha1/register.go @@ -40,7 +40,7 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Role{}, diff --git a/staging/src/k8s.io/api/rbac/v1alpha1/types.go b/staging/src/k8s.io/api/rbac/v1alpha1/types.go index 06fa6ce8e8f..843d998ec9e 100644 --- a/staging/src/k8s.io/api/rbac/v1alpha1/types.go +++ b/staging/src/k8s.io/api/rbac/v1alpha1/types.go @@ -172,6 +172,20 @@ type ClusterRole struct { // Rules holds all the PolicyRules for this ClusterRole 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 + // stomped by the controller. + // +optional + AggregationRule *AggregationRule `json:"aggregationRule,omitempty" protobuf:"bytes,3,opt,name=aggregationRule"` +} + +// AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole +type AggregationRule struct { + // ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. + // If any of the selectors match, then the ClusterRole's permissions will be added + // +optional + ClusterRoleSelectors []metav1.LabelSelector `json:"clusterRoleSelectors,omitempty" protobuf:"bytes,1,rep,name=clusterRoleSelectors"` } // +genclient diff --git a/staging/src/k8s.io/api/rbac/v1alpha1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/rbac/v1alpha1/types_swagger_doc_generated.go index d58a722af17..e56cd0f1015 100644 --- a/staging/src/k8s.io/api/rbac/v1alpha1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/rbac/v1alpha1/types_swagger_doc_generated.go @@ -27,10 +27,20 @@ package v1alpha1 // Those methods can be generated by using hack/update-generated-swagger-docs.sh // AUTO-GENERATED FUNCTIONS START HERE +var map_AggregationRule = map[string]string{ + "": "AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole", + "clusterRoleSelectors": "ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. If any of the selectors match, then the ClusterRole's permissions will be added", +} + +func (AggregationRule) SwaggerDoc() map[string]string { + return map_AggregationRule +} + var map_ClusterRole = map[string]string{ - "": "ClusterRole is a cluster level, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding or ClusterRoleBinding.", - "metadata": "Standard object's metadata.", - "rules": "Rules holds all the PolicyRules for this ClusterRole", + "": "ClusterRole is a cluster level, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding or ClusterRoleBinding.", + "metadata": "Standard object's metadata.", + "rules": "Rules holds all the PolicyRules for this ClusterRole", + "aggregationRule": "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 stomped by the controller.", } func (ClusterRole) SwaggerDoc() map[string]string { diff --git a/staging/src/k8s.io/api/rbac/v1alpha1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/rbac/v1alpha1/zz_generated.deepcopy.go index e4cab832534..3037f666cb3 100644 --- a/staging/src/k8s.io/api/rbac/v1alpha1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/rbac/v1alpha1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,66 +21,31 @@ limitations under the License. package v1alpha1 import ( - conversion "k8s.io/apimachinery/pkg/conversion" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AggregationRule) DeepCopyInto(out *AggregationRule) { + *out = *in + if in.ClusterRoleSelectors != nil { + in, out := &in.ClusterRoleSelectors, &out.ClusterRoleSelectors + *out = make([]v1.LabelSelector, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return } -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterRole).DeepCopyInto(out.(*ClusterRole)) - return nil - }, InType: reflect.TypeOf(&ClusterRole{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterRoleBinding).DeepCopyInto(out.(*ClusterRoleBinding)) - return nil - }, InType: reflect.TypeOf(&ClusterRoleBinding{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterRoleBindingList).DeepCopyInto(out.(*ClusterRoleBindingList)) - return nil - }, InType: reflect.TypeOf(&ClusterRoleBindingList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterRoleList).DeepCopyInto(out.(*ClusterRoleList)) - return nil - }, InType: reflect.TypeOf(&ClusterRoleList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PolicyRule).DeepCopyInto(out.(*PolicyRule)) - return nil - }, InType: reflect.TypeOf(&PolicyRule{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Role).DeepCopyInto(out.(*Role)) - return nil - }, InType: reflect.TypeOf(&Role{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RoleBinding).DeepCopyInto(out.(*RoleBinding)) - return nil - }, InType: reflect.TypeOf(&RoleBinding{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RoleBindingList).DeepCopyInto(out.(*RoleBindingList)) - return nil - }, InType: reflect.TypeOf(&RoleBindingList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RoleList).DeepCopyInto(out.(*RoleList)) - return nil - }, InType: reflect.TypeOf(&RoleList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RoleRef).DeepCopyInto(out.(*RoleRef)) - return nil - }, InType: reflect.TypeOf(&RoleRef{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Subject).DeepCopyInto(out.(*Subject)) - return nil - }, InType: reflect.TypeOf(&Subject{})}, - ) +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AggregationRule. +func (in *AggregationRule) DeepCopy() *AggregationRule { + if in == nil { + return nil + } + out := new(AggregationRule) + in.DeepCopyInto(out) + return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -95,6 +60,15 @@ func (in *ClusterRole) DeepCopyInto(out *ClusterRole) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.AggregationRule != nil { + in, out := &in.AggregationRule, &out.AggregationRule + if *in == nil { + *out = nil + } else { + *out = new(AggregationRule) + (*in).DeepCopyInto(*out) + } + } return } diff --git a/staging/src/k8s.io/api/rbac/v1beta1/BUILD b/staging/src/k8s.io/api/rbac/v1beta1/BUILD index 3d0fcbd6a43..c9aa763f410 100644 --- a/staging/src/k8s.io/api/rbac/v1beta1/BUILD +++ b/staging/src/k8s.io/api/rbac/v1beta1/BUILD @@ -19,7 +19,6 @@ go_library( deps = [ "//vendor/github.com/gogo/protobuf/proto:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/staging/src/k8s.io/api/rbac/v1beta1/doc.go b/staging/src/k8s.io/api/rbac/v1beta1/doc.go index bb7d47df999..4b77c9c6b83 100644 --- a/staging/src/k8s.io/api/rbac/v1beta1/doc.go +++ b/staging/src/k8s.io/api/rbac/v1beta1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:openapi-gen=true // +groupName=rbac.authorization.k8s.io diff --git a/staging/src/k8s.io/api/rbac/v1beta1/generated.pb.go b/staging/src/k8s.io/api/rbac/v1beta1/generated.pb.go index 9cb4935c022..c2525e0dff2 100644 --- a/staging/src/k8s.io/api/rbac/v1beta1/generated.pb.go +++ b/staging/src/k8s.io/api/rbac/v1beta1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -25,6 +25,7 @@ limitations under the License. k8s.io/kubernetes/vendor/k8s.io/api/rbac/v1beta1/generated.proto It has these top-level messages: + AggregationRule ClusterRole ClusterRoleBinding ClusterRoleBindingList @@ -43,6 +44,8 @@ import proto "github.com/gogo/protobuf/proto" import fmt "fmt" import math "math" +import k8s_io_apimachinery_pkg_apis_meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + import strings "strings" import reflect "reflect" @@ -59,51 +62,56 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package +func (m *AggregationRule) Reset() { *m = AggregationRule{} } +func (*AggregationRule) ProtoMessage() {} +func (*AggregationRule) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{0} } + func (m *ClusterRole) Reset() { *m = ClusterRole{} } func (*ClusterRole) ProtoMessage() {} -func (*ClusterRole) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{0} } +func (*ClusterRole) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{1} } func (m *ClusterRoleBinding) Reset() { *m = ClusterRoleBinding{} } func (*ClusterRoleBinding) ProtoMessage() {} -func (*ClusterRoleBinding) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{1} } +func (*ClusterRoleBinding) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{2} } func (m *ClusterRoleBindingList) Reset() { *m = ClusterRoleBindingList{} } func (*ClusterRoleBindingList) ProtoMessage() {} -func (*ClusterRoleBindingList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{2} } +func (*ClusterRoleBindingList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{3} } func (m *ClusterRoleList) Reset() { *m = ClusterRoleList{} } func (*ClusterRoleList) ProtoMessage() {} -func (*ClusterRoleList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{3} } +func (*ClusterRoleList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{4} } func (m *PolicyRule) Reset() { *m = PolicyRule{} } func (*PolicyRule) ProtoMessage() {} -func (*PolicyRule) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{4} } +func (*PolicyRule) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{5} } func (m *Role) Reset() { *m = Role{} } func (*Role) ProtoMessage() {} -func (*Role) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{5} } +func (*Role) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{6} } func (m *RoleBinding) Reset() { *m = RoleBinding{} } func (*RoleBinding) ProtoMessage() {} -func (*RoleBinding) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{6} } +func (*RoleBinding) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{7} } func (m *RoleBindingList) Reset() { *m = RoleBindingList{} } func (*RoleBindingList) ProtoMessage() {} -func (*RoleBindingList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{7} } +func (*RoleBindingList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{8} } func (m *RoleList) Reset() { *m = RoleList{} } func (*RoleList) ProtoMessage() {} -func (*RoleList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{8} } +func (*RoleList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{9} } func (m *RoleRef) Reset() { *m = RoleRef{} } func (*RoleRef) ProtoMessage() {} -func (*RoleRef) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{9} } +func (*RoleRef) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{10} } func (m *Subject) Reset() { *m = Subject{} } func (*Subject) ProtoMessage() {} -func (*Subject) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{10} } +func (*Subject) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{11} } func init() { + proto.RegisterType((*AggregationRule)(nil), "k8s.io.api.rbac.v1beta1.AggregationRule") proto.RegisterType((*ClusterRole)(nil), "k8s.io.api.rbac.v1beta1.ClusterRole") proto.RegisterType((*ClusterRoleBinding)(nil), "k8s.io.api.rbac.v1beta1.ClusterRoleBinding") proto.RegisterType((*ClusterRoleBindingList)(nil), "k8s.io.api.rbac.v1beta1.ClusterRoleBindingList") @@ -116,6 +124,36 @@ func init() { proto.RegisterType((*RoleRef)(nil), "k8s.io.api.rbac.v1beta1.RoleRef") proto.RegisterType((*Subject)(nil), "k8s.io.api.rbac.v1beta1.Subject") } +func (m *AggregationRule) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AggregationRule) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.ClusterRoleSelectors) > 0 { + for _, msg := range m.ClusterRoleSelectors { + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + func (m *ClusterRole) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -151,6 +189,16 @@ func (m *ClusterRole) MarshalTo(dAtA []byte) (int, error) { i += n } } + if m.AggregationRule != nil { + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.AggregationRule.Size())) + n2, err := m.AggregationRule.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n2 + } return i, nil } @@ -172,11 +220,11 @@ func (m *ClusterRoleBinding) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n2, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n3, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n2 + i += n3 if len(m.Subjects) > 0 { for _, msg := range m.Subjects { dAtA[i] = 0x12 @@ -192,11 +240,11 @@ func (m *ClusterRoleBinding) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.RoleRef.Size())) - n3, err := m.RoleRef.MarshalTo(dAtA[i:]) + n4, err := m.RoleRef.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n3 + i += n4 return i, nil } @@ -218,11 +266,11 @@ func (m *ClusterRoleBindingList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n4, err := m.ListMeta.MarshalTo(dAtA[i:]) + n5, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n4 + i += n5 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -256,11 +304,11 @@ func (m *ClusterRoleList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n5, err := m.ListMeta.MarshalTo(dAtA[i:]) + n6, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n5 + i += n6 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -387,11 +435,11 @@ func (m *Role) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n6, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n7, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n6 + i += n7 if len(m.Rules) > 0 { for _, msg := range m.Rules { dAtA[i] = 0x12 @@ -425,11 +473,11 @@ func (m *RoleBinding) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n7, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n8, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n7 + i += n8 if len(m.Subjects) > 0 { for _, msg := range m.Subjects { dAtA[i] = 0x12 @@ -445,11 +493,11 @@ func (m *RoleBinding) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.RoleRef.Size())) - n8, err := m.RoleRef.MarshalTo(dAtA[i:]) + n9, err := m.RoleRef.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n8 + i += n9 return i, nil } @@ -471,11 +519,11 @@ func (m *RoleBindingList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n9, err := m.ListMeta.MarshalTo(dAtA[i:]) + n10, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n9 + i += n10 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -509,11 +557,11 @@ func (m *RoleList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n10, err := m.ListMeta.MarshalTo(dAtA[i:]) + n11, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n10 + i += n11 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -620,6 +668,18 @@ func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return offset + 1 } +func (m *AggregationRule) Size() (n int) { + var l int + _ = l + if len(m.ClusterRoleSelectors) > 0 { + for _, e := range m.ClusterRoleSelectors { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + func (m *ClusterRole) Size() (n int) { var l int _ = l @@ -631,6 +691,10 @@ func (m *ClusterRole) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) } } + if m.AggregationRule != nil { + l = m.AggregationRule.Size() + n += 1 + l + sovGenerated(uint64(l)) + } return n } @@ -811,6 +875,16 @@ func sovGenerated(x uint64) (n int) { func sozGenerated(x uint64) (n int) { return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } +func (this *AggregationRule) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&AggregationRule{`, + `ClusterRoleSelectors:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ClusterRoleSelectors), "LabelSelector", "k8s_io_apimachinery_pkg_apis_meta_v1.LabelSelector", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} func (this *ClusterRole) String() string { if this == nil { return "nil" @@ -818,6 +892,7 @@ func (this *ClusterRole) String() string { s := strings.Join([]string{`&ClusterRole{`, `ObjectMeta:` + strings.Replace(strings.Replace(this.ObjectMeta.String(), "ObjectMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ObjectMeta", 1), `&`, ``, 1) + `,`, `Rules:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Rules), "PolicyRule", "PolicyRule", 1), `&`, ``, 1) + `,`, + `AggregationRule:` + strings.Replace(fmt.Sprintf("%v", this.AggregationRule), "AggregationRule", "AggregationRule", 1) + `,`, `}`, }, "") return s @@ -948,6 +1023,87 @@ func valueToStringGenerated(v interface{}) string { pv := reflect.Indirect(rv).Interface() return fmt.Sprintf("*%v", pv) } +func (m *AggregationRule) 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: AggregationRule: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AggregationRule: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClusterRoleSelectors", 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 > l { + return io.ErrUnexpectedEOF + } + m.ClusterRoleSelectors = append(m.ClusterRoleSelectors, k8s_io_apimachinery_pkg_apis_meta_v1.LabelSelector{}) + if err := m.ClusterRoleSelectors[len(m.ClusterRoleSelectors)-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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *ClusterRole) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -1038,6 +1194,39 @@ func (m *ClusterRole) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AggregationRule", 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 > l { + return io.ErrUnexpectedEOF + } + if m.AggregationRule == nil { + m.AggregationRule = &AggregationRule{} + } + if err := m.AggregationRule.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -2504,52 +2693,58 @@ func init() { } var fileDescriptorGenerated = []byte{ - // 751 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x94, 0xcd, 0x6e, 0xd3, 0x4a, - 0x14, 0xc7, 0xe3, 0x7c, 0x28, 0xf1, 0xe4, 0x46, 0xb9, 0xf5, 0x95, 0xee, 0xb5, 0x2a, 0x5d, 0x27, - 0x0a, 0x2c, 0x2a, 0x95, 0xda, 0xb4, 0x20, 0x60, 0x83, 0x04, 0x66, 0x01, 0x55, 0x4b, 0xa8, 0x06, - 0xc1, 0x02, 0xb1, 0x60, 0xe2, 0x4c, 0xd3, 0x21, 0xf1, 0x87, 0x66, 0xc6, 0x91, 0x2a, 0x36, 0x3c, - 0x00, 0x0b, 0x24, 0x5e, 0x83, 0x15, 0x3b, 0x78, 0x82, 0x2c, 0xbb, 0xec, 0x2a, 0xa2, 0xe6, 0x41, - 0x40, 0x33, 0xb6, 0xe3, 0xa4, 0x69, 0xda, 0xac, 0x22, 0x21, 0xb1, 0x4a, 0xe6, 0x9c, 0xdf, 0xf9, - 0x9f, 0x0f, 0xcf, 0x1c, 0xf0, 0xa0, 0x7f, 0x8f, 0x99, 0xc4, 0xb7, 0xfa, 0x61, 0x07, 0x53, 0x0f, - 0x73, 0xcc, 0xac, 0x21, 0xf6, 0xba, 0x3e, 0xb5, 0x12, 0x07, 0x0a, 0x88, 0x45, 0x3b, 0xc8, 0xb1, - 0x86, 0xdb, 0x1d, 0xcc, 0xd1, 0xb6, 0xd5, 0xc3, 0x1e, 0xa6, 0x88, 0xe3, 0xae, 0x19, 0x50, 0x9f, - 0xfb, 0xda, 0x7f, 0x31, 0x68, 0xa2, 0x80, 0x98, 0x02, 0x34, 0x13, 0x70, 0x7d, 0xab, 0x47, 0xf8, - 0x51, 0xd8, 0x31, 0x1d, 0xdf, 0xb5, 0x7a, 0x7e, 0xcf, 0xb7, 0x24, 0xdf, 0x09, 0x0f, 0xe5, 0x49, - 0x1e, 0xe4, 0xbf, 0x58, 0x67, 0xfd, 0x76, 0x96, 0xd0, 0x45, 0xce, 0x11, 0xf1, 0x30, 0x3d, 0xb6, - 0x82, 0x7e, 0x4f, 0x18, 0x98, 0xe5, 0x62, 0x8e, 0xac, 0xe1, 0x5c, 0xf6, 0x75, 0x6b, 0x51, 0x14, - 0x0d, 0x3d, 0x4e, 0x5c, 0x3c, 0x17, 0x70, 0xe7, 0xaa, 0x00, 0xe6, 0x1c, 0x61, 0x17, 0xcd, 0xc5, - 0xdd, 0x5a, 0x14, 0x17, 0x72, 0x32, 0xb0, 0x88, 0xc7, 0x19, 0xa7, 0xe7, 0x83, 0x5a, 0x5f, 0x15, - 0x50, 0x7d, 0x34, 0x08, 0x19, 0xc7, 0x14, 0xfa, 0x03, 0xac, 0xbd, 0x01, 0x15, 0xd1, 0x48, 0x17, - 0x71, 0xa4, 0x2b, 0x4d, 0x65, 0xa3, 0xba, 0x73, 0xd3, 0xcc, 0xc6, 0x37, 0xd1, 0x35, 0x83, 0x7e, - 0x4f, 0x18, 0x98, 0x29, 0x68, 0x73, 0xb8, 0x6d, 0x3e, 0xeb, 0xbc, 0xc5, 0x0e, 0x7f, 0x8a, 0x39, - 0xb2, 0xb5, 0xd1, 0xb8, 0x91, 0x8b, 0xc6, 0x0d, 0x90, 0xd9, 0xe0, 0x44, 0x55, 0x7b, 0x02, 0x4a, - 0x34, 0x1c, 0x60, 0xa6, 0xe7, 0x9b, 0x85, 0x8d, 0xea, 0xce, 0x35, 0x73, 0xc1, 0xd7, 0x31, 0x0f, - 0xfc, 0x01, 0x71, 0x8e, 0x61, 0x38, 0xc0, 0x76, 0x2d, 0x51, 0x2c, 0x89, 0x13, 0x83, 0xb1, 0x40, - 0xeb, 0x53, 0x1e, 0x68, 0x53, 0xb5, 0xdb, 0xc4, 0xeb, 0x12, 0xaf, 0xb7, 0x82, 0x16, 0xda, 0xa0, - 0xc2, 0x42, 0xe9, 0x48, 0xbb, 0x68, 0x2e, 0xec, 0xe2, 0x79, 0x0c, 0xda, 0x7f, 0x27, 0x8a, 0x95, - 0xc4, 0xc0, 0xe0, 0x44, 0x43, 0xdb, 0x03, 0x65, 0xea, 0x0f, 0x30, 0xc4, 0x87, 0x7a, 0x41, 0x16, - 0xbc, 0x58, 0x0e, 0xc6, 0x9c, 0x5d, 0x4f, 0xe4, 0xca, 0x89, 0x01, 0xa6, 0x0a, 0xad, 0x91, 0x02, - 0xfe, 0x9d, 0x9f, 0xca, 0x3e, 0x61, 0x5c, 0x7b, 0x3d, 0x37, 0x19, 0x73, 0xb9, 0xc9, 0x88, 0x68, - 0x39, 0x97, 0x49, 0x17, 0xa9, 0x65, 0x6a, 0x2a, 0x07, 0xa0, 0x44, 0x38, 0x76, 0xd3, 0x91, 0x6c, - 0x2e, 0xec, 0x61, 0xbe, 0xba, 0xec, 0x03, 0xef, 0x0a, 0x05, 0x18, 0x0b, 0xb5, 0xbe, 0x29, 0xa0, - 0x3e, 0x05, 0xaf, 0xa0, 0x87, 0xdd, 0xd9, 0x1e, 0xae, 0x2f, 0xd5, 0xc3, 0xc5, 0xc5, 0xff, 0x54, - 0x00, 0xc8, 0xae, 0xb0, 0xd6, 0x00, 0xa5, 0x21, 0xa6, 0x1d, 0xa6, 0x2b, 0xcd, 0xc2, 0x86, 0x6a, - 0xab, 0x82, 0x7f, 0x29, 0x0c, 0x30, 0xb6, 0x6b, 0x9b, 0x40, 0x45, 0x01, 0x79, 0x4c, 0xfd, 0x30, - 0x88, 0xd3, 0xab, 0x76, 0x2d, 0x1a, 0x37, 0xd4, 0x87, 0x07, 0xbb, 0xb1, 0x11, 0x66, 0x7e, 0x01, - 0x53, 0xcc, 0xfc, 0x90, 0x3a, 0x98, 0xe9, 0x85, 0x0c, 0x86, 0xa9, 0x11, 0x66, 0x7e, 0xed, 0x2e, - 0xa8, 0xa5, 0x87, 0x36, 0x72, 0x31, 0xd3, 0x8b, 0x32, 0x60, 0x2d, 0x1a, 0x37, 0x6a, 0x70, 0xda, - 0x01, 0x67, 0x39, 0xed, 0x3e, 0xa8, 0x7b, 0xbe, 0x97, 0x22, 0x2f, 0xe0, 0x3e, 0xd3, 0x4b, 0x32, - 0xf4, 0x9f, 0x68, 0xdc, 0xa8, 0xb7, 0x67, 0x5d, 0xf0, 0x3c, 0xdb, 0xfa, 0xa2, 0x80, 0xe2, 0x6f, - 0xb7, 0x54, 0x3e, 0xe4, 0x41, 0xf5, 0xcf, 0x36, 0x99, 0x6c, 0x13, 0xf1, 0x04, 0x57, 0xbb, 0x46, - 0x96, 0x7e, 0x82, 0x57, 0xef, 0x8f, 0xcf, 0x0a, 0xa8, 0xac, 0x68, 0x71, 0xd8, 0xb3, 0x55, 0xff, - 0x7f, 0x79, 0xd5, 0x17, 0x97, 0xfb, 0x0e, 0xa4, 0xf3, 0xd7, 0x6e, 0x80, 0x4a, 0xfa, 0xd8, 0x65, - 0xb1, 0x6a, 0x96, 0x3c, 0xdd, 0x07, 0x70, 0x42, 0x68, 0x4d, 0x50, 0xec, 0x13, 0xaf, 0xab, 0xe7, - 0x25, 0xf9, 0x57, 0x42, 0x16, 0xf7, 0x88, 0xd7, 0x85, 0xd2, 0x23, 0x08, 0x0f, 0xb9, 0x58, 0x5e, - 0x88, 0x29, 0x42, 0x3c, 0x73, 0x28, 0x3d, 0x62, 0x56, 0xe5, 0xe4, 0x32, 0x4d, 0xf4, 0x94, 0x85, - 0x7a, 0xd3, 0xf5, 0xe5, 0x97, 0xa9, 0xef, 0xf2, 0xec, 0x9a, 0x05, 0x54, 0xf1, 0xcb, 0x02, 0xe4, - 0x60, 0xbd, 0x28, 0xb1, 0xb5, 0x04, 0x53, 0xdb, 0xa9, 0x03, 0x66, 0x8c, 0xbd, 0x35, 0x3a, 0x33, - 0x72, 0x27, 0x67, 0x46, 0xee, 0xf4, 0xcc, 0xc8, 0xbd, 0x8f, 0x0c, 0x65, 0x14, 0x19, 0xca, 0x49, - 0x64, 0x28, 0xa7, 0x91, 0xa1, 0x7c, 0x8f, 0x0c, 0xe5, 0xe3, 0x0f, 0x23, 0xf7, 0xaa, 0x9c, 0x4c, - 0xfd, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0x74, 0x24, 0x6a, 0xfa, 0x45, 0x0a, 0x00, 0x00, + // 833 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x55, 0xbf, 0x8f, 0xe3, 0x44, + 0x14, 0xce, 0x64, 0x13, 0x6d, 0x3c, 0xcb, 0x2a, 0xdc, 0x70, 0x02, 0x6b, 0x05, 0xce, 0x2a, 0x50, + 0x44, 0x3a, 0xce, 0x66, 0xef, 0x10, 0xd0, 0x20, 0x71, 0xa6, 0x80, 0xd5, 0x2d, 0x61, 0x35, 0x27, + 0x28, 0x10, 0x05, 0x63, 0x67, 0xce, 0x19, 0xe2, 0x5f, 0x9a, 0x19, 0x47, 0x3a, 0xd1, 0xd0, 0xd0, + 0x51, 0x20, 0x51, 0xd1, 0x52, 0x53, 0x51, 0xf2, 0x17, 0xa4, 0xbc, 0xf2, 0xaa, 0x88, 0x35, 0x7f, + 0x08, 0x68, 0xfc, 0x23, 0x4e, 0xe2, 0xf8, 0x2e, 0x55, 0x24, 0xa4, 0xab, 0x76, 0xe7, 0xbd, 0xef, + 0x7d, 0xef, 0x7b, 0x9f, 0x67, 0x5e, 0xe0, 0x27, 0xb3, 0x8f, 0x84, 0xc9, 0x22, 0x6b, 0x96, 0x38, + 0x94, 0x87, 0x54, 0x52, 0x61, 0xcd, 0x69, 0x38, 0x89, 0xb8, 0x55, 0x24, 0x48, 0xcc, 0x2c, 0xee, + 0x10, 0xd7, 0x9a, 0x5f, 0x38, 0x54, 0x92, 0x0b, 0xcb, 0xa3, 0x21, 0xe5, 0x44, 0xd2, 0x89, 0x19, + 0xf3, 0x48, 0x46, 0xe8, 0x8d, 0x1c, 0x68, 0x92, 0x98, 0x99, 0x0a, 0x68, 0x16, 0xc0, 0xb3, 0xbb, + 0x1e, 0x93, 0xd3, 0xc4, 0x31, 0xdd, 0x28, 0xb0, 0xbc, 0xc8, 0x8b, 0xac, 0x0c, 0xef, 0x24, 0x8f, + 0xb3, 0x53, 0x76, 0xc8, 0xfe, 0xcb, 0x79, 0xce, 0x46, 0xf5, 0x86, 0xc4, 0x8f, 0xa7, 0xf5, 0x8e, + 0x67, 0xef, 0x57, 0xc8, 0x80, 0xb8, 0x53, 0x16, 0x52, 0xfe, 0xc4, 0x8a, 0x67, 0x9e, 0x0a, 0x08, + 0x2b, 0xa0, 0x92, 0x58, 0xf3, 0x7a, 0x95, 0xd5, 0x54, 0xc5, 0x93, 0x50, 0xb2, 0x80, 0xd6, 0x0a, + 0x3e, 0x78, 0x51, 0x81, 0x70, 0xa7, 0x34, 0x20, 0xb5, 0xba, 0xfb, 0x4d, 0x75, 0x89, 0x64, 0xbe, + 0xc5, 0x42, 0x29, 0x24, 0xdf, 0x2e, 0x1a, 0xfe, 0x06, 0x60, 0xff, 0x81, 0xe7, 0x71, 0xea, 0x11, + 0xc9, 0xa2, 0x10, 0x27, 0x3e, 0x45, 0x3f, 0x01, 0x78, 0xdb, 0xf5, 0x13, 0x21, 0x29, 0xc7, 0x91, + 0x4f, 0x1f, 0x51, 0x9f, 0xba, 0x32, 0xe2, 0x42, 0x07, 0xe7, 0x47, 0xa3, 0x93, 0x7b, 0xf7, 0xcd, + 0xca, 0xf9, 0x55, 0x23, 0x33, 0x9e, 0x79, 0x2a, 0x20, 0x4c, 0xe5, 0x83, 0x39, 0xbf, 0x30, 0xaf, + 0x88, 0x43, 0xfd, 0xb2, 0xd6, 0x7e, 0x73, 0xb1, 0x1c, 0xb4, 0xd2, 0xe5, 0xe0, 0xf6, 0xa7, 0x3b, + 0x88, 0xf1, 0xce, 0x76, 0xc3, 0xdf, 0xdb, 0xf0, 0x64, 0x0d, 0x8e, 0xbe, 0x83, 0x3d, 0x45, 0x3e, + 0x21, 0x92, 0xe8, 0xe0, 0x1c, 0x8c, 0x4e, 0xee, 0xbd, 0xb7, 0x9f, 0x94, 0x2f, 0x9d, 0xef, 0xa9, + 0x2b, 0xbf, 0xa0, 0x92, 0xd8, 0xa8, 0xd0, 0x01, 0xab, 0x18, 0x5e, 0xb1, 0xa2, 0xcf, 0x61, 0x97, + 0x27, 0x3e, 0x15, 0x7a, 0x3b, 0x9b, 0xf4, 0x6d, 0xb3, 0xe1, 0x8e, 0x99, 0xd7, 0x91, 0xcf, 0xdc, + 0x27, 0xca, 0x2d, 0xfb, 0xb4, 0x60, 0xec, 0xaa, 0x93, 0xc0, 0x39, 0x01, 0xf2, 0x60, 0x9f, 0x6c, + 0xda, 0xaa, 0x1f, 0x65, 0x92, 0x47, 0x8d, 0x9c, 0x5b, 0x9f, 0xc1, 0x7e, 0x2d, 0x5d, 0x0e, 0xb6, + 0xbf, 0x0d, 0xde, 0x66, 0x1d, 0xfe, 0xda, 0x86, 0x68, 0xcd, 0x24, 0x9b, 0x85, 0x13, 0x16, 0x7a, + 0x07, 0xf0, 0x6a, 0x0c, 0x7b, 0x22, 0xc9, 0x12, 0xa5, 0x5d, 0xe7, 0x8d, 0xa3, 0x3d, 0xca, 0x81, + 0xf6, 0xab, 0x05, 0x63, 0xaf, 0x08, 0x08, 0xbc, 0xe2, 0x40, 0x0f, 0xe1, 0x31, 0x8f, 0x7c, 0x8a, + 0xe9, 0xe3, 0xc2, 0xa9, 0x66, 0x3a, 0x9c, 0xe3, 0xec, 0x7e, 0x41, 0x77, 0x5c, 0x04, 0x70, 0xc9, + 0x30, 0x5c, 0x00, 0xf8, 0x7a, 0xdd, 0x95, 0x2b, 0x26, 0x24, 0xfa, 0xb6, 0xe6, 0x8c, 0xb9, 0xe7, + 0x85, 0x66, 0x22, 0xf7, 0x65, 0x35, 0x45, 0x19, 0x59, 0x73, 0xe5, 0x1a, 0x76, 0x99, 0xa4, 0x41, + 0x69, 0xc9, 0x9d, 0xc6, 0x19, 0xea, 0xea, 0xaa, 0x9b, 0x74, 0xa9, 0x18, 0x70, 0x4e, 0x34, 0xfc, + 0x0b, 0xc0, 0xfe, 0x1a, 0xf8, 0x00, 0x33, 0x5c, 0x6e, 0xce, 0xf0, 0xce, 0x5e, 0x33, 0xec, 0x16, + 0xff, 0x2f, 0x80, 0xb0, 0x7a, 0x2b, 0x68, 0x00, 0xbb, 0x73, 0xca, 0x9d, 0x7c, 0x93, 0x68, 0xb6, + 0xa6, 0xf0, 0x5f, 0xab, 0x00, 0xce, 0xe3, 0xe8, 0x0e, 0xd4, 0x48, 0xcc, 0x3e, 0xe3, 0x51, 0x12, + 0xe7, 0xed, 0x35, 0xfb, 0x34, 0x5d, 0x0e, 0xb4, 0x07, 0xd7, 0x97, 0x79, 0x10, 0x57, 0x79, 0x05, + 0xe6, 0x54, 0x44, 0x09, 0x77, 0xa9, 0xd0, 0x8f, 0x2a, 0x30, 0x2e, 0x83, 0xb8, 0xca, 0xa3, 0x0f, + 0xe1, 0x69, 0x79, 0x18, 0x93, 0x80, 0x0a, 0xbd, 0x93, 0x15, 0xdc, 0x4a, 0x97, 0x83, 0x53, 0xbc, + 0x9e, 0xc0, 0x9b, 0x38, 0xf4, 0x31, 0xec, 0x87, 0x51, 0x58, 0x42, 0xbe, 0xc2, 0x57, 0x42, 0xef, + 0x66, 0xa5, 0xd9, 0xfb, 0x1c, 0x6f, 0xa6, 0xf0, 0x36, 0x76, 0xf8, 0x27, 0x80, 0x9d, 0xff, 0xdb, + 0xf6, 0x1a, 0xfe, 0xdc, 0x86, 0x27, 0x2f, 0xb7, 0xc9, 0x6a, 0x9b, 0xa8, 0x27, 0x78, 0xd8, 0x35, + 0xb2, 0xf7, 0x13, 0x7c, 0xf1, 0xfe, 0xf8, 0x03, 0xc0, 0xde, 0x81, 0x16, 0x87, 0xbd, 0xa9, 0xfa, + 0xad, 0xe7, 0xab, 0xde, 0x2d, 0xf7, 0x07, 0x58, 0xfa, 0x8f, 0xde, 0x85, 0xbd, 0xf2, 0xb1, 0x67, + 0x62, 0xb5, 0xaa, 0x79, 0xb9, 0x0f, 0xf0, 0x0a, 0x81, 0xce, 0x61, 0x67, 0xc6, 0xc2, 0x89, 0xde, + 0xce, 0x90, 0xaf, 0x14, 0xc8, 0xce, 0x43, 0x16, 0x4e, 0x70, 0x96, 0x51, 0x88, 0x90, 0x04, 0xf9, + 0x0f, 0xf1, 0x1a, 0x42, 0x3d, 0x73, 0x9c, 0x65, 0x94, 0x57, 0xc7, 0xc5, 0x65, 0x5a, 0xf1, 0x81, + 0x46, 0xbe, 0x75, 0x7d, 0xed, 0x7d, 0xf4, 0x3d, 0xbf, 0x3b, 0xb2, 0xa0, 0xa6, 0xfe, 0x8a, 0x98, + 0xb8, 0x54, 0xef, 0x64, 0xb0, 0x5b, 0x05, 0x4c, 0x1b, 0x97, 0x09, 0x5c, 0x61, 0xec, 0xbb, 0x8b, + 0x1b, 0xa3, 0xf5, 0xf4, 0xc6, 0x68, 0x3d, 0xbb, 0x31, 0x5a, 0x3f, 0xa6, 0x06, 0x58, 0xa4, 0x06, + 0x78, 0x9a, 0x1a, 0xe0, 0x59, 0x6a, 0x80, 0xbf, 0x53, 0x03, 0xfc, 0xf2, 0x8f, 0xd1, 0xfa, 0xe6, + 0xb8, 0x70, 0xfd, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xd1, 0x99, 0xaf, 0xff, 0x74, 0x0b, 0x00, + 0x00, } diff --git a/staging/src/k8s.io/api/rbac/v1beta1/generated.proto b/staging/src/k8s.io/api/rbac/v1beta1/generated.proto index 6469de720a5..aa9960b8ec4 100644 --- a/staging/src/k8s.io/api/rbac/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/rbac/v1beta1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,6 +21,7 @@ syntax = 'proto2'; package k8s.io.api.rbac.v1beta1; +import "k8s.io/api/rbac/v1alpha1/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"; @@ -29,6 +30,14 @@ import "k8s.io/apimachinery/pkg/util/intstr/generated.proto"; // Package-wide variables from generator "generated". option go_package = "v1beta1"; +// AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole +message AggregationRule { + // ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. + // If any of the selectors match, then the ClusterRole's permissions will be added + // +optional + repeated k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector clusterRoleSelectors = 1; +} + // ClusterRole is a cluster level, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding or ClusterRoleBinding. message ClusterRole { // Standard object's metadata. @@ -37,6 +46,12 @@ message ClusterRole { // Rules holds all the PolicyRules for this ClusterRole repeated PolicyRule rules = 2; + + // 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 + // stomped by the controller. + // +optional + optional AggregationRule aggregationRule = 3; } // ClusterRoleBinding references a ClusterRole, but not contain it. It can reference a ClusterRole in the global namespace, @@ -85,7 +100,8 @@ message PolicyRule { // +optional repeated string apiGroups = 2; - // Resources is a list of resources this rule applies to. ResourceAll represents all resources. + // Resources is a list of resources this rule applies to. '*' represents all resources in the specified apiGroups. + // '*/foo' represents the subresource 'foo' for all resources in the specified apiGroups. // +optional repeated string resources = 3; diff --git a/staging/src/k8s.io/api/rbac/v1beta1/register.go b/staging/src/k8s.io/api/rbac/v1beta1/register.go index 8f60f01953f..c8526a656ba 100644 --- a/staging/src/k8s.io/api/rbac/v1beta1/register.go +++ b/staging/src/k8s.io/api/rbac/v1beta1/register.go @@ -40,7 +40,7 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Role{}, diff --git a/staging/src/k8s.io/api/rbac/v1beta1/types.go b/staging/src/k8s.io/api/rbac/v1beta1/types.go index 30f95a77405..091fc1dc95f 100644 --- a/staging/src/k8s.io/api/rbac/v1beta1/types.go +++ b/staging/src/k8s.io/api/rbac/v1beta1/types.go @@ -54,7 +54,8 @@ type PolicyRule struct { // the enumerated resources in any API group will be allowed. // +optional APIGroups []string `json:"apiGroups,omitempty" protobuf:"bytes,2,rep,name=apiGroups"` - // Resources is a list of resources this rule applies to. ResourceAll represents all resources. + // Resources is a list of resources this rule applies to. '*' represents all resources in the specified apiGroups. + // '*/foo' represents the subresource 'foo' for all resources in the specified apiGroups. // +optional 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. @@ -170,6 +171,19 @@ type ClusterRole struct { // Rules holds all the PolicyRules for this ClusterRole 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 + // stomped by the controller. + // +optional + AggregationRule *AggregationRule `json:"aggregationRule,omitempty" protobuf:"bytes,3,opt,name=aggregationRule"` +} + +// AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole +type AggregationRule struct { + // ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. + // If any of the selectors match, then the ClusterRole's permissions will be added + // +optional + ClusterRoleSelectors []metav1.LabelSelector `json:"clusterRoleSelectors,omitempty" protobuf:"bytes,1,rep,name=clusterRoleSelectors"` } // +genclient diff --git a/staging/src/k8s.io/api/rbac/v1beta1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/rbac/v1beta1/types_swagger_doc_generated.go index 1463d8feac0..6180d6d43e7 100644 --- a/staging/src/k8s.io/api/rbac/v1beta1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/rbac/v1beta1/types_swagger_doc_generated.go @@ -27,10 +27,20 @@ package v1beta1 // Those methods can be generated by using hack/update-generated-swagger-docs.sh // AUTO-GENERATED FUNCTIONS START HERE +var map_AggregationRule = map[string]string{ + "": "AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole", + "clusterRoleSelectors": "ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. If any of the selectors match, then the ClusterRole's permissions will be added", +} + +func (AggregationRule) SwaggerDoc() map[string]string { + return map_AggregationRule +} + var map_ClusterRole = map[string]string{ - "": "ClusterRole is a cluster level, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding or ClusterRoleBinding.", - "metadata": "Standard object's metadata.", - "rules": "Rules holds all the PolicyRules for this ClusterRole", + "": "ClusterRole is a cluster level, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding or ClusterRoleBinding.", + "metadata": "Standard object's metadata.", + "rules": "Rules holds all the PolicyRules for this ClusterRole", + "aggregationRule": "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 stomped by the controller.", } func (ClusterRole) SwaggerDoc() map[string]string { @@ -72,7 +82,7 @@ var map_PolicyRule = map[string]string{ "": "PolicyRule holds information that describes a policy rule, but does not contain information about who the rule applies to or which namespace the rule applies to.", "verbs": "Verbs is a list of Verbs that apply to ALL the ResourceKinds and AttributeRestrictions contained in this rule. VerbAll represents all kinds.", "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.", - "resources": "Resources is a list of resources this rule applies to. ResourceAll represents all resources.", + "resources": "Resources is a list of resources this rule applies to. '*' represents all resources in the specified apiGroups. '*/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.", "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 Since non-resource URLs are not namespaced, this field is only applicable for ClusterRoles referenced from a ClusterRoleBinding. Rules can either apply to API resources (such as \"pods\" or \"secrets\") or non-resource URL paths (such as \"/api\"), but not both.", } diff --git a/staging/src/k8s.io/api/rbac/v1beta1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/rbac/v1beta1/zz_generated.deepcopy.go index 922727646f2..7e035cd27a4 100644 --- a/staging/src/k8s.io/api/rbac/v1beta1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/rbac/v1beta1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,66 +21,31 @@ limitations under the License. package v1beta1 import ( - conversion "k8s.io/apimachinery/pkg/conversion" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AggregationRule) DeepCopyInto(out *AggregationRule) { + *out = *in + if in.ClusterRoleSelectors != nil { + in, out := &in.ClusterRoleSelectors, &out.ClusterRoleSelectors + *out = make([]v1.LabelSelector, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return } -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterRole).DeepCopyInto(out.(*ClusterRole)) - return nil - }, InType: reflect.TypeOf(&ClusterRole{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterRoleBinding).DeepCopyInto(out.(*ClusterRoleBinding)) - return nil - }, InType: reflect.TypeOf(&ClusterRoleBinding{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterRoleBindingList).DeepCopyInto(out.(*ClusterRoleBindingList)) - return nil - }, InType: reflect.TypeOf(&ClusterRoleBindingList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ClusterRoleList).DeepCopyInto(out.(*ClusterRoleList)) - return nil - }, InType: reflect.TypeOf(&ClusterRoleList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PolicyRule).DeepCopyInto(out.(*PolicyRule)) - return nil - }, InType: reflect.TypeOf(&PolicyRule{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Role).DeepCopyInto(out.(*Role)) - return nil - }, InType: reflect.TypeOf(&Role{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RoleBinding).DeepCopyInto(out.(*RoleBinding)) - return nil - }, InType: reflect.TypeOf(&RoleBinding{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RoleBindingList).DeepCopyInto(out.(*RoleBindingList)) - return nil - }, InType: reflect.TypeOf(&RoleBindingList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RoleList).DeepCopyInto(out.(*RoleList)) - return nil - }, InType: reflect.TypeOf(&RoleList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RoleRef).DeepCopyInto(out.(*RoleRef)) - return nil - }, InType: reflect.TypeOf(&RoleRef{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Subject).DeepCopyInto(out.(*Subject)) - return nil - }, InType: reflect.TypeOf(&Subject{})}, - ) +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AggregationRule. +func (in *AggregationRule) DeepCopy() *AggregationRule { + if in == nil { + return nil + } + out := new(AggregationRule) + in.DeepCopyInto(out) + return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -95,6 +60,15 @@ func (in *ClusterRole) DeepCopyInto(out *ClusterRole) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.AggregationRule != nil { + in, out := &in.AggregationRule, &out.AggregationRule + if *in == nil { + *out = nil + } else { + *out = new(AggregationRule) + (*in).DeepCopyInto(*out) + } + } return } diff --git a/staging/src/k8s.io/api/scheduling/v1alpha1/BUILD b/staging/src/k8s.io/api/scheduling/v1alpha1/BUILD index e7a95040f67..c8c19766472 100644 --- a/staging/src/k8s.io/api/scheduling/v1alpha1/BUILD +++ b/staging/src/k8s.io/api/scheduling/v1alpha1/BUILD @@ -19,7 +19,6 @@ go_library( deps = [ "//vendor/github.com/gogo/protobuf/proto:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/staging/src/k8s.io/api/scheduling/v1alpha1/doc.go b/staging/src/k8s.io/api/scheduling/v1alpha1/doc.go index bf9015986c9..e10d07ff742 100644 --- a/staging/src/k8s.io/api/scheduling/v1alpha1/doc.go +++ b/staging/src/k8s.io/api/scheduling/v1alpha1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:openapi-gen=true // +groupName=scheduling.k8s.io diff --git a/staging/src/k8s.io/api/scheduling/v1alpha1/generated.pb.go b/staging/src/k8s.io/api/scheduling/v1alpha1/generated.pb.go index 1a68ffe1130..39c0b9e6a4e 100644 --- a/staging/src/k8s.io/api/scheduling/v1alpha1/generated.pb.go +++ b/staging/src/k8s.io/api/scheduling/v1alpha1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/api/scheduling/v1alpha1/generated.proto b/staging/src/k8s.io/api/scheduling/v1alpha1/generated.proto index 625cae7bee0..75b4968cc31 100644 --- a/staging/src/k8s.io/api/scheduling/v1alpha1/generated.proto +++ b/staging/src/k8s.io/api/scheduling/v1alpha1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/api/scheduling/v1alpha1/register.go b/staging/src/k8s.io/api/scheduling/v1alpha1/register.go index 91ce6e0cc3a..24689f0ad84 100644 --- a/staging/src/k8s.io/api/scheduling/v1alpha1/register.go +++ b/staging/src/k8s.io/api/scheduling/v1alpha1/register.go @@ -41,7 +41,7 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &PriorityClass{}, 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 index a3ae5c0ff96..344e6cc622d 100644 --- a/staging/src/k8s.io/api/scheduling/v1alpha1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/scheduling/v1alpha1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,32 +21,9 @@ limitations under the License. package v1alpha1 import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PriorityClass).DeepCopyInto(out.(*PriorityClass)) - return nil - }, InType: reflect.TypeOf(&PriorityClass{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PriorityClassList).DeepCopyInto(out.(*PriorityClassList)) - return nil - }, InType: reflect.TypeOf(&PriorityClassList{})}, - ) -} - // 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 diff --git a/staging/src/k8s.io/api/settings/v1alpha1/BUILD b/staging/src/k8s.io/api/settings/v1alpha1/BUILD index 1474fc9c659..d7457427dfc 100644 --- a/staging/src/k8s.io/api/settings/v1alpha1/BUILD +++ b/staging/src/k8s.io/api/settings/v1alpha1/BUILD @@ -20,7 +20,6 @@ go_library( "//vendor/github.com/gogo/protobuf/proto:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/staging/src/k8s.io/api/settings/v1alpha1/doc.go b/staging/src/k8s.io/api/settings/v1alpha1/doc.go index ae1bfb9122b..05a62c569ed 100644 --- a/staging/src/k8s.io/api/settings/v1alpha1/doc.go +++ b/staging/src/k8s.io/api/settings/v1alpha1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:openapi-gen=true // +groupName=settings.k8s.io diff --git a/staging/src/k8s.io/api/settings/v1alpha1/generated.pb.go b/staging/src/k8s.io/api/settings/v1alpha1/generated.pb.go index 47e24af73b2..bfc6a5a11b4 100644 --- a/staging/src/k8s.io/api/settings/v1alpha1/generated.pb.go +++ b/staging/src/k8s.io/api/settings/v1alpha1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/api/settings/v1alpha1/generated.proto b/staging/src/k8s.io/api/settings/v1alpha1/generated.proto index 430319d7dca..098e8dd9bf8 100644 --- a/staging/src/k8s.io/api/settings/v1alpha1/generated.proto +++ b/staging/src/k8s.io/api/settings/v1alpha1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/api/settings/v1alpha1/register.go b/staging/src/k8s.io/api/settings/v1alpha1/register.go index 2f39af0f9b2..eee278d9592 100644 --- a/staging/src/k8s.io/api/settings/v1alpha1/register.go +++ b/staging/src/k8s.io/api/settings/v1alpha1/register.go @@ -41,7 +41,7 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &PodPreset{}, diff --git a/staging/src/k8s.io/api/settings/v1alpha1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/settings/v1alpha1/zz_generated.deepcopy.go index 07e6ab72d0e..5376686586c 100644 --- a/staging/src/k8s.io/api/settings/v1alpha1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/settings/v1alpha1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,36 +22,9 @@ package v1alpha1 import ( v1 "k8s.io/api/core/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodPreset).DeepCopyInto(out.(*PodPreset)) - return nil - }, InType: reflect.TypeOf(&PodPreset{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodPresetList).DeepCopyInto(out.(*PodPresetList)) - return nil - }, InType: reflect.TypeOf(&PodPresetList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodPresetSpec).DeepCopyInto(out.(*PodPresetSpec)) - return nil - }, InType: reflect.TypeOf(&PodPresetSpec{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PodPreset) DeepCopyInto(out *PodPreset) { *out = *in diff --git a/staging/src/k8s.io/api/storage/v1/BUILD b/staging/src/k8s.io/api/storage/v1/BUILD index 2e2a0f04272..50a09f0eba2 100644 --- a/staging/src/k8s.io/api/storage/v1/BUILD +++ b/staging/src/k8s.io/api/storage/v1/BUILD @@ -17,7 +17,6 @@ go_library( "//vendor/github.com/gogo/protobuf/sortkeys:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/staging/src/k8s.io/api/storage/v1/doc.go b/staging/src/k8s.io/api/storage/v1/doc.go index 0672c58e14e..8f4a4045c43 100644 --- a/staging/src/k8s.io/api/storage/v1/doc.go +++ b/staging/src/k8s.io/api/storage/v1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +groupName=storage.k8s.io // +k8s:openapi-gen=true package v1 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 4befedff1ef..31988b70dc7 100644 --- a/staging/src/k8s.io/api/storage/v1/generated.pb.go +++ b/staging/src/k8s.io/api/storage/v1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -146,6 +146,12 @@ func (m *StorageClass) MarshalTo(dAtA []byte) (int, error) { } i++ } + if m.VolumeBindingMode != nil { + dAtA[i] = 0x3a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.VolumeBindingMode))) + i += copy(dAtA[i:], *m.VolumeBindingMode) + } return i, nil } @@ -242,6 +248,10 @@ func (m *StorageClass) Size() (n int) { if m.AllowVolumeExpansion != nil { n += 2 } + if m.VolumeBindingMode != nil { + l = len(*m.VolumeBindingMode) + n += 1 + l + sovGenerated(uint64(l)) + } return n } @@ -293,6 +303,7 @@ func (this *StorageClass) String() string { `ReclaimPolicy:` + valueToStringGenerated(this.ReclaimPolicy) + `,`, `MountOptions:` + fmt.Sprintf("%v", this.MountOptions) + `,`, `AllowVolumeExpansion:` + valueToStringGenerated(this.AllowVolumeExpansion) + `,`, + `VolumeBindingMode:` + valueToStringGenerated(this.VolumeBindingMode) + `,`, `}`, }, "") return s @@ -600,6 +611,36 @@ func (m *StorageClass) Unmarshal(dAtA []byte) error { } b := bool(v != 0) m.AllowVolumeExpansion = &b + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VolumeBindingMode", 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 > l { + return io.ErrUnexpectedEOF + } + s := VolumeBindingMode(dAtA[iNdEx:postIndex]) + m.VolumeBindingMode = &s + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -842,43 +883,44 @@ func init() { } var fileDescriptorGenerated = []byte{ - // 593 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0x41, 0x6f, 0xd3, 0x3e, - 0x18, 0xc6, 0x9b, 0x76, 0xfd, 0x6b, 0x73, 0x37, 0xfd, 0xab, 0x30, 0xa4, 0xa8, 0x87, 0xb4, 0x1a, - 0x97, 0x0a, 0x09, 0x7b, 0xdd, 0x06, 0x9a, 0x90, 0x40, 0xa2, 0x68, 0x12, 0x48, 0x9b, 0x56, 0x05, - 0x89, 0x03, 0xe2, 0x80, 0x9b, 0xbd, 0x64, 0x26, 0x89, 0x1d, 0xd9, 0x4e, 0xa0, 0x37, 0x3e, 0x02, - 0x9f, 0x87, 0x13, 0xc7, 0x1d, 0x77, 0xdc, 0x29, 0x62, 0xe1, 0x5b, 0x70, 0x42, 0x49, 0xca, 0x92, - 0xad, 0x9d, 0xd8, 0xcd, 0x7e, 0xdf, 0xe7, 0xf7, 0xd8, 0x7e, 0xfd, 0xa0, 0xe7, 0xfe, 0xbe, 0xc2, - 0x4c, 0x10, 0x3f, 0x9e, 0x82, 0xe4, 0xa0, 0x41, 0x91, 0x04, 0xf8, 0x89, 0x90, 0x64, 0xde, 0xa0, - 0x11, 0x23, 0x4a, 0x0b, 0x49, 0x3d, 0x20, 0xc9, 0x88, 0x78, 0xc0, 0x41, 0x52, 0x0d, 0x27, 0x38, - 0x92, 0x42, 0x0b, 0xf3, 0x7e, 0x29, 0xc3, 0x34, 0x62, 0x78, 0x2e, 0xc3, 0xc9, 0xa8, 0xf7, 0xc8, - 0x63, 0xfa, 0x34, 0x9e, 0x62, 0x57, 0x84, 0xc4, 0x13, 0x9e, 0x20, 0x85, 0x7a, 0x1a, 0x7f, 0x2c, - 0x76, 0xc5, 0xa6, 0x58, 0x95, 0x2e, 0xbd, 0x87, 0x4b, 0x0f, 0x9b, 0x82, 0xa6, 0x0b, 0x27, 0xf6, - 0xf6, 0x2a, 0x6d, 0x48, 0xdd, 0x53, 0xc6, 0x41, 0xce, 0x48, 0xe4, 0x7b, 0x79, 0x41, 0x91, 0x10, - 0x34, 0x5d, 0x72, 0xcf, 0x1e, 0xb9, 0x8d, 0x92, 0x31, 0xd7, 0x2c, 0x84, 0x05, 0xe0, 0xc9, 0xbf, - 0x00, 0xe5, 0x9e, 0x42, 0x48, 0x17, 0xb8, 0xdd, 0xdb, 0xb8, 0x58, 0xb3, 0x80, 0x30, 0xae, 0x95, - 0x96, 0x37, 0xa1, 0xad, 0x1f, 0x2b, 0x68, 0xfd, 0x4d, 0xf9, 0xee, 0x97, 0x01, 0x55, 0xca, 0xfc, - 0x80, 0x56, 0xf3, 0x97, 0x9c, 0x50, 0x4d, 0x2d, 0x63, 0x60, 0x0c, 0x3b, 0x3b, 0xdb, 0xb8, 0x9a, - 0xf4, 0x95, 0x31, 0x8e, 0x7c, 0x2f, 0x2f, 0x28, 0x9c, 0xab, 0x71, 0x32, 0xc2, 0xc7, 0xd3, 0x4f, - 0xe0, 0xea, 0x23, 0xd0, 0x74, 0x6c, 0x9e, 0xa5, 0xfd, 0x46, 0x96, 0xf6, 0x51, 0x55, 0x73, 0xae, - 0x5c, 0xcd, 0xc7, 0xa8, 0x13, 0x49, 0x91, 0x30, 0xc5, 0x04, 0x07, 0x69, 0x35, 0x07, 0xc6, 0x70, - 0x6d, 0x7c, 0x6f, 0x8e, 0x74, 0x26, 0x55, 0xcb, 0xa9, 0xeb, 0x4c, 0x0f, 0xa1, 0x88, 0x4a, 0x1a, - 0x82, 0x06, 0xa9, 0xac, 0xd6, 0xa0, 0x35, 0xec, 0xec, 0xec, 0xe2, 0xa5, 0x21, 0xc0, 0xf5, 0x17, - 0xe1, 0xc9, 0x15, 0x75, 0xc0, 0xb5, 0x9c, 0x55, 0xb7, 0xab, 0x1a, 0x4e, 0xcd, 0xda, 0xf4, 0xd1, - 0x86, 0x04, 0x37, 0xa0, 0x2c, 0x9c, 0x88, 0x80, 0xb9, 0x33, 0x6b, 0xa5, 0xb8, 0xe1, 0x41, 0x96, - 0xf6, 0x37, 0x9c, 0x7a, 0xe3, 0x77, 0xda, 0xdf, 0xae, 0xc5, 0xc7, 0x15, 0x32, 0xcf, 0x0e, 0x9e, - 0x80, 0x54, 0x4c, 0x69, 0xe0, 0xfa, 0xad, 0x08, 0xe2, 0x10, 0xae, 0x31, 0xce, 0x75, 0x6f, 0x73, - 0x0f, 0xad, 0x87, 0x22, 0xe6, 0xfa, 0x38, 0xd2, 0x4c, 0x70, 0x65, 0xb5, 0x07, 0xad, 0xe1, 0xda, - 0xb8, 0x9b, 0xa5, 0xfd, 0xf5, 0xa3, 0x5a, 0xdd, 0xb9, 0xa6, 0x32, 0x0f, 0xd1, 0x26, 0x0d, 0x02, - 0xf1, 0xb9, 0x3c, 0xe0, 0xe0, 0x4b, 0x44, 0x79, 0x3e, 0x25, 0xeb, 0xbf, 0x81, 0x31, 0x5c, 0x1d, - 0x5b, 0x59, 0xda, 0xdf, 0x7c, 0xb1, 0xa4, 0xef, 0x2c, 0xa5, 0x7a, 0xcf, 0xd0, 0xff, 0x37, 0x66, - 0x64, 0x76, 0x51, 0xcb, 0x87, 0x59, 0x11, 0x80, 0x35, 0x27, 0x5f, 0x9a, 0x9b, 0xa8, 0x9d, 0xd0, - 0x20, 0x86, 0xf2, 0xbf, 0x9c, 0x72, 0xf3, 0xb4, 0xb9, 0x6f, 0x6c, 0x7d, 0x37, 0x50, 0xb7, 0x3e, - 0xf0, 0x43, 0xa6, 0xb4, 0xf9, 0x7e, 0x21, 0x46, 0xf8, 0x6e, 0x31, 0xca, 0xe9, 0x22, 0x44, 0xdd, - 0xf9, 0x37, 0xad, 0xfe, 0xad, 0xd4, 0x22, 0xf4, 0x0a, 0xb5, 0x99, 0x86, 0x50, 0x59, 0xcd, 0x22, - 0x06, 0x0f, 0xee, 0x10, 0x83, 0xf1, 0xc6, 0xdc, 0xaf, 0xfd, 0x3a, 0x27, 0x9d, 0xd2, 0x60, 0x3c, - 0x3c, 0xbb, 0xb4, 0x1b, 0xe7, 0x97, 0x76, 0xe3, 0xe2, 0xd2, 0x6e, 0x7c, 0xcd, 0x6c, 0xe3, 0x2c, - 0xb3, 0x8d, 0xf3, 0xcc, 0x36, 0x2e, 0x32, 0xdb, 0xf8, 0x99, 0xd9, 0xc6, 0xb7, 0x5f, 0x76, 0xe3, - 0x5d, 0x33, 0x19, 0xfd, 0x09, 0x00, 0x00, 0xff, 0xff, 0xbb, 0x57, 0xe7, 0x15, 0xb0, 0x04, 0x00, - 0x00, + // 623 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x52, 0x4f, 0x6f, 0xd3, 0x3c, + 0x18, 0x6f, 0xda, 0xb7, 0x2f, 0x9b, 0xbb, 0x89, 0x2e, 0x0c, 0x29, 0xea, 0x21, 0xa9, 0xc6, 0xa5, + 0x9a, 0x84, 0xb3, 0x6e, 0x03, 0x4d, 0x48, 0x20, 0x11, 0x34, 0x09, 0xa4, 0x4d, 0xab, 0x82, 0x34, + 0x21, 0xc4, 0x01, 0x37, 0x7d, 0xc8, 0x4c, 0x13, 0x3b, 0xb2, 0x9d, 0x40, 0x6f, 0x7c, 0x04, 0xce, + 0x7c, 0x14, 0x3e, 0xc1, 0x8e, 0x3b, 0xee, 0x14, 0xb1, 0xf0, 0x2d, 0x76, 0x42, 0x49, 0xca, 0x9a, + 0xad, 0x9d, 0xd8, 0x2d, 0xfe, 0xfd, 0xb3, 0x9f, 0x27, 0x3f, 0xf4, 0x62, 0xbc, 0x27, 0x31, 0xe5, + 0xf6, 0x38, 0x1e, 0x82, 0x60, 0xa0, 0x40, 0xda, 0x09, 0xb0, 0x11, 0x17, 0xf6, 0x94, 0x20, 0x11, + 0xb5, 0xa5, 0xe2, 0x82, 0xf8, 0x60, 0x27, 0x7d, 0xdb, 0x07, 0x06, 0x82, 0x28, 0x18, 0xe1, 0x48, + 0x70, 0xc5, 0xf5, 0x87, 0xa5, 0x0c, 0x93, 0x88, 0xe2, 0xa9, 0x0c, 0x27, 0xfd, 0xce, 0x63, 0x9f, + 0xaa, 0x93, 0x78, 0x88, 0x3d, 0x1e, 0xda, 0x3e, 0xf7, 0xb9, 0x5d, 0xa8, 0x87, 0xf1, 0xa7, 0xe2, + 0x54, 0x1c, 0x8a, 0xaf, 0x32, 0xa5, 0xb3, 0xb9, 0xf0, 0xb2, 0x21, 0x28, 0x32, 0x77, 0x63, 0x67, + 0x77, 0xa6, 0x0d, 0x89, 0x77, 0x42, 0x19, 0x88, 0x89, 0x1d, 0x8d, 0xfd, 0x1c, 0x90, 0x76, 0x08, + 0x8a, 0x2c, 0x78, 0x67, 0xc7, 0xbe, 0xcd, 0x25, 0x62, 0xa6, 0x68, 0x08, 0x73, 0x86, 0xa7, 0xff, + 0x32, 0x48, 0xef, 0x04, 0x42, 0x32, 0xe7, 0xdb, 0xb9, 0xcd, 0x17, 0x2b, 0x1a, 0xd8, 0x94, 0x29, + 0xa9, 0xc4, 0x4d, 0xd3, 0xc6, 0x8f, 0x26, 0x5a, 0x79, 0x5b, 0xce, 0xfd, 0x2a, 0x20, 0x52, 0xea, + 0x1f, 0xd1, 0x52, 0x3e, 0xc9, 0x88, 0x28, 0x62, 0x68, 0x5d, 0xad, 0xd7, 0xda, 0xde, 0xc2, 0xb3, + 0x4d, 0x5f, 0x05, 0xe3, 0x68, 0xec, 0xe7, 0x80, 0xc4, 0xb9, 0x1a, 0x27, 0x7d, 0x7c, 0x34, 0xfc, + 0x0c, 0x9e, 0x3a, 0x04, 0x45, 0x1c, 0xfd, 0x34, 0xb5, 0x6a, 0x59, 0x6a, 0xa1, 0x19, 0xe6, 0x5e, + 0xa5, 0xea, 0x4f, 0x50, 0x2b, 0x12, 0x3c, 0xa1, 0x92, 0x72, 0x06, 0xc2, 0xa8, 0x77, 0xb5, 0xde, + 0xb2, 0xf3, 0x60, 0x6a, 0x69, 0x0d, 0x66, 0x94, 0x5b, 0xd5, 0xe9, 0x3e, 0x42, 0x11, 0x11, 0x24, + 0x04, 0x05, 0x42, 0x1a, 0x8d, 0x6e, 0xa3, 0xd7, 0xda, 0xde, 0xc1, 0x0b, 0x4b, 0x80, 0xab, 0x13, + 0xe1, 0xc1, 0x95, 0x6b, 0x9f, 0x29, 0x31, 0x99, 0xbd, 0x6e, 0x46, 0xb8, 0x95, 0x68, 0x7d, 0x8c, + 0x56, 0x05, 0x78, 0x01, 0xa1, 0xe1, 0x80, 0x07, 0xd4, 0x9b, 0x18, 0xff, 0x15, 0x2f, 0xdc, 0xcf, + 0x52, 0x6b, 0xd5, 0xad, 0x12, 0x97, 0xa9, 0xb5, 0x55, 0xa9, 0x8f, 0xc7, 0x45, 0xde, 0x1d, 0x3c, + 0x00, 0x21, 0xa9, 0x54, 0xc0, 0xd4, 0x31, 0x0f, 0xe2, 0x10, 0xae, 0x79, 0xdc, 0xeb, 0xd9, 0xfa, + 0x2e, 0x5a, 0x09, 0x79, 0xcc, 0xd4, 0x51, 0xa4, 0x28, 0x67, 0xd2, 0x68, 0x76, 0x1b, 0xbd, 0x65, + 0xa7, 0x9d, 0xa5, 0xd6, 0xca, 0x61, 0x05, 0x77, 0xaf, 0xa9, 0xf4, 0x03, 0xb4, 0x4e, 0x82, 0x80, + 0x7f, 0x29, 0x2f, 0xd8, 0xff, 0x1a, 0x11, 0x96, 0x6f, 0xc9, 0xf8, 0xbf, 0xab, 0xf5, 0x96, 0x1c, + 0x23, 0x4b, 0xad, 0xf5, 0x97, 0x0b, 0x78, 0x77, 0xa1, 0x4b, 0x7f, 0x87, 0xd6, 0x92, 0x02, 0x72, + 0x28, 0x1b, 0x51, 0xe6, 0x1f, 0xf2, 0x11, 0x18, 0xf7, 0x8a, 0xa1, 0x37, 0xb3, 0xd4, 0x5a, 0x3b, + 0xbe, 0x49, 0x5e, 0x2e, 0x02, 0xdd, 0xf9, 0x90, 0xce, 0x73, 0x74, 0xff, 0xc6, 0xf6, 0xf5, 0x36, + 0x6a, 0x8c, 0x61, 0x52, 0x54, 0x6b, 0xd9, 0xcd, 0x3f, 0xf5, 0x75, 0xd4, 0x4c, 0x48, 0x10, 0x43, + 0xd9, 0x04, 0xb7, 0x3c, 0x3c, 0xab, 0xef, 0x69, 0x1b, 0x3f, 0x35, 0xd4, 0xae, 0xfe, 0xca, 0x03, + 0x2a, 0x95, 0xfe, 0x61, 0xae, 0xa0, 0xf8, 0x6e, 0x05, 0xcd, 0xdd, 0x45, 0x3d, 0xdb, 0xd3, 0x02, + 0x2c, 0xfd, 0x45, 0x2a, 0xe5, 0x7c, 0x8d, 0x9a, 0x54, 0x41, 0x28, 0x8d, 0x7a, 0x51, 0xb0, 0x47, + 0x77, 0x28, 0x98, 0xb3, 0x3a, 0xcd, 0x6b, 0xbe, 0xc9, 0x9d, 0x6e, 0x19, 0xe0, 0xf4, 0x4e, 0x2f, + 0xcc, 0xda, 0xd9, 0x85, 0x59, 0x3b, 0xbf, 0x30, 0x6b, 0xdf, 0x32, 0x53, 0x3b, 0xcd, 0x4c, 0xed, + 0x2c, 0x33, 0xb5, 0xf3, 0xcc, 0xd4, 0x7e, 0x65, 0xa6, 0xf6, 0xfd, 0xb7, 0x59, 0x7b, 0x5f, 0x4f, + 0xfa, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xee, 0x56, 0xcc, 0xfd, 0x0a, 0x05, 0x00, 0x00, } diff --git a/staging/src/k8s.io/api/storage/v1/generated.proto b/staging/src/k8s.io/api/storage/v1/generated.proto index 6a335e5268a..72b3ceb832a 100644 --- a/staging/src/k8s.io/api/storage/v1/generated.proto +++ b/staging/src/k8s.io/api/storage/v1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -63,6 +63,13 @@ message StorageClass { // AllowVolumeExpansion shows whether the storage class allow volume expand // +optional optional bool allowVolumeExpansion = 6; + + // VolumeBindingMode indicates how PersistentVolumeClaims should be + // provisioned and bound. When unset, VolumeBindingImmediate is used. + // This field is alpha-level and is only honored by servers that enable + // the VolumeScheduling feature. + // +optional + optional string volumeBindingMode = 7; } // StorageClassList is a collection of storage classes. diff --git a/staging/src/k8s.io/api/storage/v1/register.go b/staging/src/k8s.io/api/storage/v1/register.go index 59c4e59ba19..c058add8400 100644 --- a/staging/src/k8s.io/api/storage/v1/register.go +++ b/staging/src/k8s.io/api/storage/v1/register.go @@ -41,7 +41,7 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &StorageClass{}, diff --git a/staging/src/k8s.io/api/storage/v1/types.go b/staging/src/k8s.io/api/storage/v1/types.go index 9afdafb62ac..288d40abb8f 100644 --- a/staging/src/k8s.io/api/storage/v1/types.go +++ b/staging/src/k8s.io/api/storage/v1/types.go @@ -59,6 +59,13 @@ type StorageClass struct { // AllowVolumeExpansion shows whether the storage class allow volume expand // +optional AllowVolumeExpansion *bool `json:"allowVolumeExpansion,omitempty" protobuf:"varint,6,opt,name=allowVolumeExpansion"` + + // VolumeBindingMode indicates how PersistentVolumeClaims should be + // provisioned and bound. When unset, VolumeBindingImmediate is used. + // This field is alpha-level and is only honored by servers that enable + // the VolumeScheduling feature. + // +optional + VolumeBindingMode *VolumeBindingMode `json:"volumeBindingMode,omitempty" protobuf:"bytes,7,opt,name=volumeBindingMode"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -74,3 +81,18 @@ type StorageClassList struct { // Items is the list of StorageClasses Items []StorageClass `json:"items" protobuf:"bytes,2,rep,name=items"` } + +// VolumeBindingMode indicates how PersistentVolumeClaims should be bound. +type VolumeBindingMode string + +const ( + // VolumeBindingImmediate indicates that PersistentVolumeClaims should be + // immediately provisioned and bound. This is the default mode. + VolumeBindingImmediate VolumeBindingMode = "Immediate" + + // VolumeBindingWaitForFirstConsumer indicates that PersistentVolumeClaims + // should not be provisioned and bound until the first Pod is created that + // references the PeristentVolumeClaim. The volume provisioning and + // binding will occur during Pod scheduing. + VolumeBindingWaitForFirstConsumer VolumeBindingMode = "WaitForFirstConsumer" +) 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 b4be857dd1a..3eb9bdab769 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 @@ -35,6 +35,7 @@ var map_StorageClass = map[string]string{ "reclaimPolicy": "Dynamically provisioned PersistentVolumes of this storage class are created with this reclaimPolicy. Defaults to Delete.", "mountOptions": "Dynamically provisioned PersistentVolumes of this storage class are created with these mountOptions, e.g. [\"ro\", \"soft\"]. Not validated - mount of the PVs will simply fail if one is invalid.", "allowVolumeExpansion": "AllowVolumeExpansion shows whether the storage class allow volume expand", + "volumeBindingMode": "VolumeBindingMode indicates how PersistentVolumeClaims should be provisioned and bound. When unset, VolumeBindingImmediate is used. This field is alpha-level and is only honored by servers that enable the VolumeScheduling feature.", } func (StorageClass) 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 50c707abb9d..63bdcc43c62 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 @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,32 +22,9 @@ package v1 import ( core_v1 "k8s.io/api/core/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StorageClass).DeepCopyInto(out.(*StorageClass)) - return nil - }, InType: reflect.TypeOf(&StorageClass{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StorageClassList).DeepCopyInto(out.(*StorageClassList)) - return nil - }, InType: reflect.TypeOf(&StorageClassList{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *StorageClass) DeepCopyInto(out *StorageClass) { *out = *in @@ -83,6 +60,15 @@ func (in *StorageClass) DeepCopyInto(out *StorageClass) { **out = **in } } + if in.VolumeBindingMode != nil { + in, out := &in.VolumeBindingMode, &out.VolumeBindingMode + if *in == nil { + *out = nil + } else { + *out = new(VolumeBindingMode) + **out = **in + } + } return } diff --git a/staging/src/k8s.io/api/storage/v1alpha1/BUILD b/staging/src/k8s.io/api/storage/v1alpha1/BUILD new file mode 100644 index 00000000000..98342a4262d --- /dev/null +++ b/staging/src/k8s.io/api/storage/v1alpha1/BUILD @@ -0,0 +1,42 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +filegroup( + name = "go_default_library_protos", + srcs = ["generated.proto"], + visibility = ["//visibility:public"], +) + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "generated.pb.go", + "register.go", + "types.go", + "types_swagger_doc_generated.go", + "zz_generated.deepcopy.go", + ], + importpath = "k8s.io/api/storage/v1alpha1", + visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/gogo/protobuf/proto:go_default_library", + "//vendor/github.com/gogo/protobuf/sortkeys:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/api/storage/v1alpha1/doc.go b/staging/src/k8s.io/api/storage/v1alpha1/doc.go new file mode 100644 index 00000000000..aa94aff7fbb --- /dev/null +++ b/staging/src/k8s.io/api/storage/v1alpha1/doc.go @@ -0,0 +1,20 @@ +/* +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,register +// +groupName=storage.k8s.io +// +k8s:openapi-gen=true +package v1alpha1 // import "k8s.io/api/storage/v1alpha1" diff --git a/staging/src/k8s.io/api/storage/v1alpha1/generated.pb.go b/staging/src/k8s.io/api/storage/v1alpha1/generated.pb.go new file mode 100644 index 00000000000..586a1b67244 --- /dev/null +++ b/staging/src/k8s.io/api/storage/v1alpha1/generated.pb.go @@ -0,0 +1,1523 @@ +/* +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. +*/ + +// Code generated by protoc-gen-gogo. +// source: k8s.io/kubernetes/vendor/k8s.io/api/storage/v1alpha1/generated.proto +// DO NOT EDIT! + +/* + Package v1alpha1 is a generated protocol buffer package. + + It is generated from these files: + k8s.io/kubernetes/vendor/k8s.io/api/storage/v1alpha1/generated.proto + + It has these top-level messages: + VolumeAttachment + VolumeAttachmentList + VolumeAttachmentSource + VolumeAttachmentSpec + VolumeAttachmentStatus + VolumeError +*/ +package v1alpha1 + +import proto "github.com/gogo/protobuf/proto" +import fmt "fmt" +import math "math" + +import github_com_gogo_protobuf_sortkeys "github.com/gogo/protobuf/sortkeys" + +import strings "strings" +import reflect "reflect" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package + +func (m *VolumeAttachment) Reset() { *m = VolumeAttachment{} } +func (*VolumeAttachment) ProtoMessage() {} +func (*VolumeAttachment) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{0} } + +func (m *VolumeAttachmentList) Reset() { *m = VolumeAttachmentList{} } +func (*VolumeAttachmentList) ProtoMessage() {} +func (*VolumeAttachmentList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{1} } + +func (m *VolumeAttachmentSource) Reset() { *m = VolumeAttachmentSource{} } +func (*VolumeAttachmentSource) ProtoMessage() {} +func (*VolumeAttachmentSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{2} } + +func (m *VolumeAttachmentSpec) Reset() { *m = VolumeAttachmentSpec{} } +func (*VolumeAttachmentSpec) ProtoMessage() {} +func (*VolumeAttachmentSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{3} } + +func (m *VolumeAttachmentStatus) Reset() { *m = VolumeAttachmentStatus{} } +func (*VolumeAttachmentStatus) ProtoMessage() {} +func (*VolumeAttachmentStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{4} } + +func (m *VolumeError) Reset() { *m = VolumeError{} } +func (*VolumeError) ProtoMessage() {} +func (*VolumeError) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{5} } + +func init() { + proto.RegisterType((*VolumeAttachment)(nil), "k8s.io.api.storage.v1alpha1.VolumeAttachment") + proto.RegisterType((*VolumeAttachmentList)(nil), "k8s.io.api.storage.v1alpha1.VolumeAttachmentList") + proto.RegisterType((*VolumeAttachmentSource)(nil), "k8s.io.api.storage.v1alpha1.VolumeAttachmentSource") + proto.RegisterType((*VolumeAttachmentSpec)(nil), "k8s.io.api.storage.v1alpha1.VolumeAttachmentSpec") + proto.RegisterType((*VolumeAttachmentStatus)(nil), "k8s.io.api.storage.v1alpha1.VolumeAttachmentStatus") + proto.RegisterType((*VolumeError)(nil), "k8s.io.api.storage.v1alpha1.VolumeError") +} +func (m *VolumeAttachment) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *VolumeAttachment) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) + n1, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) + n2, err := m.Spec.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n2 + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) + n3, err := m.Status.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n3 + return i, nil +} + +func (m *VolumeAttachmentList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *VolumeAttachmentList) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) + n4, err := m.ListMeta.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n4 + if len(m.Items) > 0 { + for _, msg := range m.Items { + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *VolumeAttachmentSource) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *VolumeAttachmentSource) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.PersistentVolumeName != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.PersistentVolumeName))) + i += copy(dAtA[i:], *m.PersistentVolumeName) + } + return i, nil +} + +func (m *VolumeAttachmentSpec) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *VolumeAttachmentSpec) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Attacher))) + i += copy(dAtA[i:], m.Attacher) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Source.Size())) + n5, err := m.Source.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n5 + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.NodeName))) + i += copy(dAtA[i:], m.NodeName) + return i, nil +} + +func (m *VolumeAttachmentStatus) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *VolumeAttachmentStatus) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0x8 + i++ + if m.Attached { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + if len(m.AttachmentMetadata) > 0 { + keysForAttachmentMetadata := make([]string, 0, len(m.AttachmentMetadata)) + for k := range m.AttachmentMetadata { + keysForAttachmentMetadata = append(keysForAttachmentMetadata, string(k)) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForAttachmentMetadata) + for _, k := range keysForAttachmentMetadata { + dAtA[i] = 0x12 + i++ + v := m.AttachmentMetadata[string(k)] + mapSize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + len(v) + sovGenerated(uint64(len(v))) + i = encodeVarintGenerated(dAtA, i, uint64(mapSize)) + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(k))) + i += copy(dAtA[i:], k) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(v))) + i += copy(dAtA[i:], v) + } + } + if m.AttachError != nil { + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.AttachError.Size())) + n6, err := m.AttachError.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n6 + } + if m.DetachError != nil { + dAtA[i] = 0x22 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.DetachError.Size())) + n7, err := m.DetachError.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n7 + } + return i, nil +} + +func (m *VolumeError) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *VolumeError) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Time.Size())) + n8, err := m.Time.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n8 + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) + i += copy(dAtA[i:], m.Message) + return i, nil +} + +func encodeFixed64Generated(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Generated(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *VolumeAttachment) Size() (n int) { + 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 *VolumeAttachmentList) Size() (n int) { + 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 *VolumeAttachmentSource) Size() (n int) { + var l int + _ = l + if m.PersistentVolumeName != nil { + l = len(*m.PersistentVolumeName) + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (m *VolumeAttachmentSpec) Size() (n int) { + var l int + _ = l + l = len(m.Attacher) + n += 1 + l + sovGenerated(uint64(l)) + l = m.Source.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.NodeName) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *VolumeAttachmentStatus) Size() (n int) { + var l int + _ = l + n += 2 + if len(m.AttachmentMetadata) > 0 { + for k, v := range m.AttachmentMetadata { + _ = k + _ = v + mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + len(v) + sovGenerated(uint64(len(v))) + n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) + } + } + if m.AttachError != nil { + l = m.AttachError.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.DetachError != nil { + l = m.DetachError.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (m *VolumeError) Size() (n int) { + var l int + _ = l + l = m.Time.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Message) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func sovGenerated(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozGenerated(x uint64) (n int) { + return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (this *VolumeAttachment) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&VolumeAttachment{`, + `ObjectMeta:` + strings.Replace(strings.Replace(this.ObjectMeta.String(), "ObjectMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "VolumeAttachmentSpec", "VolumeAttachmentSpec", 1), `&`, ``, 1) + `,`, + `Status:` + strings.Replace(strings.Replace(this.Status.String(), "VolumeAttachmentStatus", "VolumeAttachmentStatus", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *VolumeAttachmentList) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&VolumeAttachmentList{`, + `ListMeta:` + strings.Replace(strings.Replace(this.ListMeta.String(), "ListMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Items), "VolumeAttachment", "VolumeAttachment", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *VolumeAttachmentSource) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&VolumeAttachmentSource{`, + `PersistentVolumeName:` + valueToStringGenerated(this.PersistentVolumeName) + `,`, + `}`, + }, "") + return s +} +func (this *VolumeAttachmentSpec) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&VolumeAttachmentSpec{`, + `Attacher:` + fmt.Sprintf("%v", this.Attacher) + `,`, + `Source:` + strings.Replace(strings.Replace(this.Source.String(), "VolumeAttachmentSource", "VolumeAttachmentSource", 1), `&`, ``, 1) + `,`, + `NodeName:` + fmt.Sprintf("%v", this.NodeName) + `,`, + `}`, + }, "") + return s +} +func (this *VolumeAttachmentStatus) String() string { + if this == nil { + return "nil" + } + keysForAttachmentMetadata := make([]string, 0, len(this.AttachmentMetadata)) + for k := range this.AttachmentMetadata { + keysForAttachmentMetadata = append(keysForAttachmentMetadata, k) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForAttachmentMetadata) + mapStringForAttachmentMetadata := "map[string]string{" + for _, k := range keysForAttachmentMetadata { + mapStringForAttachmentMetadata += fmt.Sprintf("%v: %v,", k, this.AttachmentMetadata[k]) + } + mapStringForAttachmentMetadata += "}" + s := strings.Join([]string{`&VolumeAttachmentStatus{`, + `Attached:` + fmt.Sprintf("%v", this.Attached) + `,`, + `AttachmentMetadata:` + mapStringForAttachmentMetadata + `,`, + `AttachError:` + strings.Replace(fmt.Sprintf("%v", this.AttachError), "VolumeError", "VolumeError", 1) + `,`, + `DetachError:` + strings.Replace(fmt.Sprintf("%v", this.DetachError), "VolumeError", "VolumeError", 1) + `,`, + `}`, + }, "") + return s +} +func (this *VolumeError) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&VolumeError{`, + `Time:` + strings.Replace(strings.Replace(this.Time.String(), "Time", "k8s_io_apimachinery_pkg_apis_meta_v1.Time", 1), `&`, ``, 1) + `,`, + `Message:` + fmt.Sprintf("%v", this.Message) + `,`, + `}`, + }, "") + 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 *VolumeAttachment) 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: VolumeAttachment: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: VolumeAttachment: 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 > 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 > 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 > 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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *VolumeAttachmentList) 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: VolumeAttachmentList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: VolumeAttachmentList: 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 > 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 > l { + return io.ErrUnexpectedEOF + } + m.Items = append(m.Items, VolumeAttachment{}) + 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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *VolumeAttachmentSource) 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: VolumeAttachmentSource: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: VolumeAttachmentSource: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PersistentVolumeName", 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 > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.PersistentVolumeName = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *VolumeAttachmentSpec) 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: VolumeAttachmentSpec: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: VolumeAttachmentSpec: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Attacher", 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 > l { + return io.ErrUnexpectedEOF + } + m.Attacher = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Source", 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.Source.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + 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 > l { + return io.ErrUnexpectedEOF + } + m.NodeName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *VolumeAttachmentStatus) 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: VolumeAttachmentStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: VolumeAttachmentStatus: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Attached", 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.Attached = bool(v != 0) + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AttachmentMetadata", 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 > l { + return io.ErrUnexpectedEOF + } + var keykey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + keykey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + 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 > l { + return io.ErrUnexpectedEOF + } + mapkey := string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + if m.AttachmentMetadata == nil { + m.AttachmentMetadata = make(map[string]string) + } + if iNdEx < postIndex { + var valuekey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + valuekey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + var stringLenmapvalue uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapvalue |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapvalue := int(stringLenmapvalue) + if intStringLenmapvalue < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapvalue := iNdEx + intStringLenmapvalue + if postStringIndexmapvalue > l { + return io.ErrUnexpectedEOF + } + mapvalue := string(dAtA[iNdEx:postStringIndexmapvalue]) + iNdEx = postStringIndexmapvalue + m.AttachmentMetadata[mapkey] = mapvalue + } else { + var mapvalue string + m.AttachmentMetadata[mapkey] = mapvalue + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AttachError", 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 > l { + return io.ErrUnexpectedEOF + } + if m.AttachError == nil { + m.AttachError = &VolumeError{} + } + if err := m.AttachError.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DetachError", 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 > l { + return io.ErrUnexpectedEOF + } + if m.DetachError == nil { + m.DetachError = &VolumeError{} + } + if err := m.DetachError.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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *VolumeError) 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: VolumeError: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: VolumeError: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Time", 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.Time.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + 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 > 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 { + 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 + 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 + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + 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 + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthGenerated + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipGenerated(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") +) + +func init() { + proto.RegisterFile("k8s.io/kubernetes/vendor/k8s.io/api/storage/v1alpha1/generated.proto", fileDescriptorGenerated) +} + +var fileDescriptorGenerated = []byte{ + // 745 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x94, 0x3d, 0x6f, 0xdb, 0x46, + 0x18, 0xc7, 0x45, 0x49, 0xb6, 0xe5, 0x53, 0x5f, 0x8c, 0x83, 0xd0, 0x0a, 0x2a, 0x40, 0x19, 0x9a, + 0xdc, 0xa2, 0x3e, 0x56, 0x76, 0x51, 0x18, 0xdd, 0x44, 0xd8, 0x43, 0x51, 0xcb, 0x2d, 0xe8, 0xa2, + 0x43, 0xdb, 0xa1, 0x27, 0xf2, 0x31, 0x45, 0x4b, 0x7c, 0xc1, 0xdd, 0x51, 0x88, 0xb7, 0x4c, 0x99, + 0xb3, 0xe5, 0x1b, 0xe4, 0xb3, 0x68, 0x8b, 0x47, 0x4f, 0x42, 0xcc, 0x7c, 0x8b, 0x2c, 0x09, 0x78, + 0x3c, 0x89, 0xb2, 0x29, 0x25, 0xb6, 0x37, 0x3e, 0xcf, 0x3d, 0xff, 0xdf, 0xf3, 0x76, 0x47, 0x74, + 0x3c, 0x3a, 0xe2, 0xc4, 0x0b, 0x8d, 0x51, 0x3c, 0x00, 0x16, 0x80, 0x00, 0x6e, 0x4c, 0x20, 0x70, + 0x42, 0x66, 0xa8, 0x03, 0x1a, 0x79, 0x06, 0x17, 0x21, 0xa3, 0x2e, 0x18, 0x93, 0x2e, 0x1d, 0x47, + 0x43, 0xda, 0x35, 0x5c, 0x08, 0x80, 0x51, 0x01, 0x0e, 0x89, 0x58, 0x28, 0x42, 0xfc, 0x5d, 0x16, + 0x4c, 0x68, 0xe4, 0x11, 0x15, 0x4c, 0xe6, 0xc1, 0xad, 0x7d, 0xd7, 0x13, 0xc3, 0x78, 0x40, 0xec, + 0xd0, 0x37, 0xdc, 0xd0, 0x0d, 0x0d, 0xa9, 0x19, 0xc4, 0x17, 0xd2, 0x92, 0x86, 0xfc, 0xca, 0x58, + 0xad, 0x7e, 0x9e, 0x18, 0x9e, 0x09, 0x08, 0xb8, 0x17, 0x06, 0x7c, 0x9f, 0x46, 0x1e, 0x07, 0x36, + 0x01, 0x66, 0x44, 0x23, 0x37, 0x3d, 0xe3, 0x77, 0x03, 0x8c, 0x49, 0x77, 0x00, 0xa2, 0x58, 0x5a, + 0xeb, 0xe7, 0x1c, 0xe7, 0x53, 0x7b, 0xe8, 0x05, 0xc0, 0xae, 0x72, 0x86, 0x0f, 0x82, 0x1a, 0x93, + 0xa2, 0xca, 0x58, 0xa7, 0x62, 0x71, 0x20, 0x3c, 0x1f, 0x0a, 0x82, 0x5f, 0x3e, 0x27, 0xe0, 0xf6, + 0x10, 0x7c, 0x5a, 0xd0, 0x1d, 0xae, 0xd3, 0xc5, 0xc2, 0x1b, 0x1b, 0x5e, 0x20, 0xb8, 0x60, 0xf7, + 0x45, 0x9d, 0xd7, 0x65, 0xb4, 0xf3, 0x77, 0x38, 0x8e, 0x7d, 0xe8, 0x09, 0x41, 0xed, 0xa1, 0x0f, + 0x81, 0xc0, 0xff, 0xa3, 0x5a, 0xda, 0x8d, 0x43, 0x05, 0x6d, 0x6a, 0xbb, 0xda, 0x5e, 0xfd, 0xe0, + 0x27, 0x92, 0xaf, 0x65, 0x01, 0x27, 0xd1, 0xc8, 0x4d, 0x1d, 0x9c, 0xa4, 0xd1, 0x64, 0xd2, 0x25, + 0x7f, 0x0c, 0x2e, 0xc1, 0x16, 0x7d, 0x10, 0xd4, 0xc4, 0xd3, 0x59, 0xbb, 0x94, 0xcc, 0xda, 0x28, + 0xf7, 0x59, 0x0b, 0x2a, 0x3e, 0x47, 0x55, 0x1e, 0x81, 0xdd, 0x2c, 0x4b, 0x7a, 0x97, 0x7c, 0x62, + 0xe9, 0xe4, 0x7e, 0x79, 0xe7, 0x11, 0xd8, 0xe6, 0x17, 0x0a, 0x5f, 0x4d, 0x2d, 0x4b, 0xc2, 0xf0, + 0xbf, 0x68, 0x93, 0x0b, 0x2a, 0x62, 0xde, 0xac, 0x48, 0xec, 0xe1, 0xe3, 0xb0, 0x52, 0x6a, 0x7e, + 0xa5, 0xc0, 0x9b, 0x99, 0x6d, 0x29, 0x64, 0x67, 0xaa, 0xa1, 0xc6, 0x7d, 0xc9, 0xa9, 0xc7, 0x05, + 0xfe, 0xaf, 0x30, 0x2c, 0xf2, 0xb0, 0x61, 0xa5, 0x6a, 0x39, 0xaa, 0x1d, 0x95, 0xb2, 0x36, 0xf7, + 0x2c, 0x0d, 0xca, 0x42, 0x1b, 0x9e, 0x00, 0x9f, 0x37, 0xcb, 0xbb, 0x95, 0xbd, 0xfa, 0xc1, 0xfe, + 0xa3, 0x5a, 0x32, 0xbf, 0x54, 0xe4, 0x8d, 0xdf, 0x52, 0x86, 0x95, 0xa1, 0x3a, 0x17, 0xe8, 0x9b, + 0x42, 0xf3, 0x61, 0xcc, 0x6c, 0xc0, 0xa7, 0xa8, 0x11, 0x01, 0xe3, 0x1e, 0x17, 0x10, 0x88, 0x2c, + 0xe6, 0x8c, 0xfa, 0x20, 0xfb, 0xda, 0x36, 0x9b, 0xc9, 0xac, 0xdd, 0xf8, 0x73, 0xc5, 0xb9, 0xb5, + 0x52, 0xd5, 0x79, 0xb3, 0x62, 0x64, 0xe9, 0xba, 0xf0, 0x8f, 0xa8, 0x46, 0xa5, 0x07, 0x98, 0x42, + 0x2f, 0x46, 0xd0, 0x53, 0x7e, 0x6b, 0x11, 0x21, 0xd7, 0x2a, 0xcb, 0x53, 0xb7, 0xe5, 0x91, 0x6b, + 0x95, 0xd2, 0xa5, 0xb5, 0x4a, 0xdb, 0x52, 0xc8, 0xb4, 0x94, 0x20, 0x74, 0xb2, 0x2e, 0x2b, 0x77, + 0x4b, 0x39, 0x53, 0x7e, 0x6b, 0x11, 0xd1, 0xf9, 0x50, 0x59, 0x31, 0x3a, 0x79, 0x3f, 0x96, 0x7a, + 0x72, 0x64, 0x4f, 0xb5, 0x42, 0x4f, 0xce, 0xa2, 0x27, 0x07, 0xbf, 0xd2, 0x10, 0xa6, 0x0b, 0x44, + 0x7f, 0x7e, 0x7f, 0xb2, 0x25, 0xff, 0xfe, 0x84, 0x7b, 0x4b, 0x7a, 0x05, 0xda, 0x49, 0x20, 0xd8, + 0x95, 0xd9, 0x52, 0x55, 0xe0, 0x62, 0x80, 0xb5, 0xa2, 0x04, 0x7c, 0x89, 0xea, 0x99, 0xf7, 0x84, + 0xb1, 0x90, 0xa9, 0x97, 0xb4, 0xf7, 0x80, 0x8a, 0x64, 0xbc, 0xa9, 0x27, 0xb3, 0x76, 0xbd, 0x97, + 0x03, 0xde, 0xcf, 0xda, 0xf5, 0xa5, 0x73, 0x6b, 0x19, 0x9e, 0xe6, 0x72, 0x20, 0xcf, 0x55, 0x7d, + 0x4a, 0xae, 0x63, 0x58, 0x9f, 0x6b, 0x09, 0xde, 0x3a, 0x41, 0xdf, 0xae, 0x19, 0x11, 0xde, 0x41, + 0x95, 0x11, 0x5c, 0x65, 0x37, 0xd1, 0x4a, 0x3f, 0x71, 0x03, 0x6d, 0x4c, 0xe8, 0x38, 0xce, 0x6e, + 0xdc, 0xb6, 0x95, 0x19, 0xbf, 0x96, 0x8f, 0xb4, 0xce, 0x0b, 0x0d, 0x2d, 0xe7, 0xc0, 0xa7, 0xa8, + 0x9a, 0xfe, 0x93, 0xd5, 0xcb, 0xff, 0xe1, 0x61, 0x2f, 0xff, 0x2f, 0xcf, 0x87, 0xfc, 0x0f, 0x96, + 0x5a, 0x96, 0xa4, 0xe0, 0xef, 0xd1, 0x96, 0x0f, 0x9c, 0x53, 0x57, 0x65, 0x36, 0xbf, 0x56, 0x41, + 0x5b, 0xfd, 0xcc, 0x6d, 0xcd, 0xcf, 0x4d, 0x32, 0xbd, 0xd5, 0x4b, 0xd7, 0xb7, 0x7a, 0xe9, 0xe6, + 0x56, 0x2f, 0x3d, 0x4f, 0x74, 0x6d, 0x9a, 0xe8, 0xda, 0x75, 0xa2, 0x6b, 0x37, 0x89, 0xae, 0xbd, + 0x4d, 0x74, 0xed, 0xe5, 0x3b, 0xbd, 0xf4, 0x4f, 0x6d, 0x3e, 0xb8, 0x8f, 0x01, 0x00, 0x00, 0xff, + 0xff, 0x68, 0x82, 0x7b, 0x73, 0x9e, 0x07, 0x00, 0x00, +} diff --git a/staging/src/k8s.io/api/storage/v1alpha1/generated.proto b/staging/src/k8s.io/api/storage/v1alpha1/generated.proto new file mode 100644 index 00000000000..289ef5f3ee6 --- /dev/null +++ b/staging/src/k8s.io/api/storage/v1alpha1/generated.proto @@ -0,0 +1,128 @@ +/* +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. +*/ + + +// This file was autogenerated by go-to-protobuf. Do not edit it manually! + +syntax = 'proto2'; + +package k8s.io.api.storage.v1alpha1; + +import "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/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"; +import "k8s.io/apimachinery/pkg/util/intstr/generated.proto"; + +// Package-wide variables from generator "generated". +option go_package = "v1alpha1"; + +// VolumeAttachment captures the intent to attach or detach the specified volume +// to/from the specified node. +// +// VolumeAttachment objects are non-namespaced. +message VolumeAttachment { + // Standard object metadata. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; + + // Specification of the desired attach/detach volume behavior. + // Populated by the Kubernetes system. + optional VolumeAttachmentSpec spec = 2; + + // Status of the VolumeAttachment request. + // Populated by the entity completing the attach or detach + // operation, i.e. the external-attacher. + // +optional + optional VolumeAttachmentStatus status = 3; +} + +// VolumeAttachmentList is a collection of VolumeAttachment objects. +message VolumeAttachmentList { + // Standard list metadata + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; + + // Items is the list of VolumeAttachments + repeated VolumeAttachment items = 2; +} + +// VolumeAttachmentSource represents a volume that should be attached. +// Right now only PersistenVolumes can be attached via external attacher, +// in future we may allow also inline volumes in pods. +// Exactly one member can be set. +message VolumeAttachmentSource { + // Name of the persistent volume to attach. + // +optional + optional string persistentVolumeName = 1; +} + +// VolumeAttachmentSpec is the specification of a VolumeAttachment request. +message VolumeAttachmentSpec { + // Attacher indicates the name of the volume driver that MUST handle this + // request. This is the name returned by GetPluginName(). + optional string attacher = 1; + + // Source represents the volume that should be attached. + optional VolumeAttachmentSource source = 2; + + // The node that the volume should be attached to. + optional string nodeName = 3; +} + +// VolumeAttachmentStatus is the status of a VolumeAttachment request. +message VolumeAttachmentStatus { + // Indicates the volume is successfully attached. + // This field must only be set by the entity completing the attach + // operation, i.e. the external-attacher. + optional bool attached = 1; + + // Upon successful attach, this field is populated with any + // information returned by the attach operation that must be passed + // into subsequent WaitForAttach or Mount calls. + // This field must only be set by the entity completing the attach + // operation, i.e. the external-attacher. + // +optional + map attachmentMetadata = 2; + + // The last error encountered during attach operation, if any. + // This field must only be set by the entity completing the attach + // operation, i.e. the external-attacher. + // +optional + optional VolumeError attachError = 3; + + // The last error encountered during detach operation, if any. + // This field must only be set by the entity completing the detach + // operation, i.e. the external-attacher. + // +optional + optional VolumeError detachError = 4; +} + +// VolumeError captures an error encountered during a volume operation. +message VolumeError { + // Time the error was encountered. + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.Time time = 1; + + // String detailing the error encountered during Attach or Detach operation. + // This string maybe logged, so it should not contain sensitive + // information. + // +optional + optional string message = 2; +} + diff --git a/staging/src/k8s.io/api/storage/v1alpha1/register.go b/staging/src/k8s.io/api/storage/v1alpha1/register.go new file mode 100644 index 00000000000..7b81ee49c2b --- /dev/null +++ b/staging/src/k8s.io/api/storage/v1alpha1/register.go @@ -0,0 +1,50 @@ +/* +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 = "storage.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 ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +// Adds the list of known types to the given scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &VolumeAttachment{}, + &VolumeAttachmentList{}, + ) + + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/staging/src/k8s.io/api/storage/v1alpha1/types.go b/staging/src/k8s.io/api/storage/v1alpha1/types.go new file mode 100644 index 00000000000..964bb5f7b17 --- /dev/null +++ b/staging/src/k8s.io/api/storage/v1alpha1/types.go @@ -0,0 +1,126 @@ +/* +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" + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// VolumeAttachment captures the intent to attach or detach the specified volume +// to/from the specified node. +// +// VolumeAttachment objects are non-namespaced. +type VolumeAttachment struct { + metav1.TypeMeta `json:",inline"` + + // Standard object metadata. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Specification of the desired attach/detach volume behavior. + // Populated by the Kubernetes system. + Spec VolumeAttachmentSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` + + // Status of the VolumeAttachment request. + // Populated by the entity completing the attach or detach + // operation, i.e. the external-attacher. + // +optional + Status VolumeAttachmentStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// VolumeAttachmentList is a collection of VolumeAttachment objects. +type VolumeAttachmentList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Items is the list of VolumeAttachments + Items []VolumeAttachment `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +// VolumeAttachmentSpec is the specification of a VolumeAttachment request. +type VolumeAttachmentSpec struct { + // Attacher indicates the name of the volume driver that MUST handle this + // request. This is the name returned by GetPluginName(). + Attacher string `json:"attacher" protobuf:"bytes,1,opt,name=attacher"` + + // Source represents the volume that should be attached. + Source VolumeAttachmentSource `json:"source" protobuf:"bytes,2,opt,name=source"` + + // The node that the volume should be attached to. + NodeName string `json:"nodeName" protobuf:"bytes,3,opt,name=nodeName"` +} + +// VolumeAttachmentSource represents a volume that should be attached. +// Right now only PersistenVolumes can be attached via external attacher, +// in future we may allow also inline volumes in pods. +// Exactly one member can be set. +type VolumeAttachmentSource struct { + // Name of the persistent volume to attach. + // +optional + PersistentVolumeName *string `json:"persistentVolumeName,omitempty" protobuf:"bytes,1,opt,name=persistentVolumeName"` + + // Placeholder for *VolumeSource to accommodate inline volumes in pods. +} + +// VolumeAttachmentStatus is the status of a VolumeAttachment request. +type VolumeAttachmentStatus struct { + // Indicates the volume is successfully attached. + // This field must only be set by the entity completing the attach + // operation, i.e. the external-attacher. + Attached bool `json:"attached" protobuf:"varint,1,opt,name=attached"` + + // Upon successful attach, this field is populated with any + // information returned by the attach operation that must be passed + // into subsequent WaitForAttach or Mount calls. + // This field must only be set by the entity completing the attach + // operation, i.e. the external-attacher. + // +optional + AttachmentMetadata map[string]string `json:"attachmentMetadata,omitempty" protobuf:"bytes,2,rep,name=attachmentMetadata"` + + // The last error encountered during attach operation, if any. + // This field must only be set by the entity completing the attach + // operation, i.e. the external-attacher. + // +optional + AttachError *VolumeError `json:"attachError,omitempty" protobuf:"bytes,3,opt,name=attachError,casttype=VolumeError"` + + // The last error encountered during detach operation, if any. + // This field must only be set by the entity completing the detach + // operation, i.e. the external-attacher. + // +optional + DetachError *VolumeError `json:"detachError,omitempty" protobuf:"bytes,4,opt,name=detachError,casttype=VolumeError"` +} + +// VolumeError captures an error encountered during a volume operation. +type VolumeError struct { + // Time the error was encountered. + // +optional + Time metav1.Time `json:"time,omitempty" protobuf:"bytes,1,opt,name=time"` + + // String detailing the error encountered during Attach or Detach operation. + // This string maybe logged, so it should not contain sensitive + // information. + // +optional + Message string `json:"message,omitempty" protobuf:"bytes,2,opt,name=message"` +} diff --git a/staging/src/k8s.io/api/storage/v1alpha1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/storage/v1alpha1/types_swagger_doc_generated.go new file mode 100644 index 00000000000..faca8e939ea --- /dev/null +++ b/staging/src/k8s.io/api/storage/v1alpha1/types_swagger_doc_generated.go @@ -0,0 +1,93 @@ +/* +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 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-generated-swagger-docs.sh + +// AUTO-GENERATED FUNCTIONS START HERE +var map_VolumeAttachment = map[string]string{ + "": "VolumeAttachment captures the intent to attach or detach the specified volume to/from the specified node.\n\nVolumeAttachment objects are non-namespaced.", + "metadata": "Standard object metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", + "spec": "Specification of the desired attach/detach volume behavior. Populated by the Kubernetes system.", + "status": "Status of the VolumeAttachment request. Populated by the entity completing the attach or detach operation, i.e. the external-attacher.", +} + +func (VolumeAttachment) SwaggerDoc() map[string]string { + return map_VolumeAttachment +} + +var map_VolumeAttachmentList = map[string]string{ + "": "VolumeAttachmentList is a collection of VolumeAttachment objects.", + "metadata": "Standard list metadata More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata", + "items": "Items is the list of VolumeAttachments", +} + +func (VolumeAttachmentList) SwaggerDoc() map[string]string { + return map_VolumeAttachmentList +} + +var map_VolumeAttachmentSource = map[string]string{ + "": "VolumeAttachmentSource represents a volume that should be attached. Right now only PersistenVolumes can be attached via external attacher, in future we may allow also inline volumes in pods. Exactly one member can be set.", + "persistentVolumeName": "Name of the persistent volume to attach.", +} + +func (VolumeAttachmentSource) SwaggerDoc() map[string]string { + return map_VolumeAttachmentSource +} + +var map_VolumeAttachmentSpec = map[string]string{ + "": "VolumeAttachmentSpec is the specification of a VolumeAttachment request.", + "attacher": "Attacher indicates the name of the volume driver that MUST handle this request. This is the name returned by GetPluginName().", + "source": "Source represents the volume that should be attached.", + "nodeName": "The node that the volume should be attached to.", +} + +func (VolumeAttachmentSpec) SwaggerDoc() map[string]string { + return map_VolumeAttachmentSpec +} + +var map_VolumeAttachmentStatus = map[string]string{ + "": "VolumeAttachmentStatus is the status of a VolumeAttachment request.", + "attached": "Indicates the volume is successfully attached. This field must only be set by the entity completing the attach operation, i.e. the external-attacher.", + "attachmentMetadata": "Upon successful attach, this field is populated with any information returned by the attach operation that must be passed into subsequent WaitForAttach or Mount calls. This field must only be set by the entity completing the attach operation, i.e. the external-attacher.", + "attachError": "The last error encountered during attach operation, if any. This field must only be set by the entity completing the attach operation, i.e. the external-attacher.", + "detachError": "The last error encountered during detach operation, if any. This field must only be set by the entity completing the detach operation, i.e. the external-attacher.", +} + +func (VolumeAttachmentStatus) SwaggerDoc() map[string]string { + return map_VolumeAttachmentStatus +} + +var map_VolumeError = map[string]string{ + "": "VolumeError captures an error encountered during a volume operation.", + "time": "Time the error was encountered.", + "message": "String detailing the error encountered during Attach or Detach operation. This string maybe logged, so it should not contain sensitive information.", +} + +func (VolumeError) SwaggerDoc() map[string]string { + return map_VolumeError +} + +// AUTO-GENERATED FUNCTIONS END HERE diff --git a/staging/src/k8s.io/api/storage/v1alpha1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/storage/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..e1561dba1c4 --- /dev/null +++ b/staging/src/k8s.io/api/storage/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,188 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +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 *VolumeAttachment) DeepCopyInto(out *VolumeAttachment) { + *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 VolumeAttachment. +func (in *VolumeAttachment) DeepCopy() *VolumeAttachment { + if in == nil { + return nil + } + out := new(VolumeAttachment) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VolumeAttachment) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VolumeAttachmentList) DeepCopyInto(out *VolumeAttachmentList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]VolumeAttachment, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeAttachmentList. +func (in *VolumeAttachmentList) DeepCopy() *VolumeAttachmentList { + if in == nil { + return nil + } + out := new(VolumeAttachmentList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VolumeAttachmentList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VolumeAttachmentSource) DeepCopyInto(out *VolumeAttachmentSource) { + *out = *in + if in.PersistentVolumeName != nil { + in, out := &in.PersistentVolumeName, &out.PersistentVolumeName + if *in == nil { + *out = nil + } else { + *out = new(string) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeAttachmentSource. +func (in *VolumeAttachmentSource) DeepCopy() *VolumeAttachmentSource { + if in == nil { + return nil + } + out := new(VolumeAttachmentSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VolumeAttachmentSpec) DeepCopyInto(out *VolumeAttachmentSpec) { + *out = *in + in.Source.DeepCopyInto(&out.Source) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeAttachmentSpec. +func (in *VolumeAttachmentSpec) DeepCopy() *VolumeAttachmentSpec { + if in == nil { + return nil + } + out := new(VolumeAttachmentSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VolumeAttachmentStatus) DeepCopyInto(out *VolumeAttachmentStatus) { + *out = *in + if in.AttachmentMetadata != nil { + in, out := &in.AttachmentMetadata, &out.AttachmentMetadata + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.AttachError != nil { + in, out := &in.AttachError, &out.AttachError + if *in == nil { + *out = nil + } else { + *out = new(VolumeError) + (*in).DeepCopyInto(*out) + } + } + if in.DetachError != nil { + in, out := &in.DetachError, &out.DetachError + if *in == nil { + *out = nil + } else { + *out = new(VolumeError) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeAttachmentStatus. +func (in *VolumeAttachmentStatus) DeepCopy() *VolumeAttachmentStatus { + if in == nil { + return nil + } + out := new(VolumeAttachmentStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VolumeError) DeepCopyInto(out *VolumeError) { + *out = *in + in.Time.DeepCopyInto(&out.Time) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeError. +func (in *VolumeError) DeepCopy() *VolumeError { + if in == nil { + return nil + } + out := new(VolumeError) + in.DeepCopyInto(out) + return out +} diff --git a/staging/src/k8s.io/api/storage/v1beta1/BUILD b/staging/src/k8s.io/api/storage/v1beta1/BUILD index 4ea13789fe3..e659e9de015 100644 --- a/staging/src/k8s.io/api/storage/v1beta1/BUILD +++ b/staging/src/k8s.io/api/storage/v1beta1/BUILD @@ -17,7 +17,6 @@ go_library( "//vendor/github.com/gogo/protobuf/sortkeys:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/staging/src/k8s.io/api/storage/v1beta1/doc.go b/staging/src/k8s.io/api/storage/v1beta1/doc.go index 6137d7a4c25..8957a4cf245 100644 --- a/staging/src/k8s.io/api/storage/v1beta1/doc.go +++ b/staging/src/k8s.io/api/storage/v1beta1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +groupName=storage.k8s.io // +k8s:openapi-gen=true package v1beta1 // import "k8s.io/api/storage/v1beta1" 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 b31d6f12296..c9ae2c8b718 100644 --- a/staging/src/k8s.io/api/storage/v1beta1/generated.pb.go +++ b/staging/src/k8s.io/api/storage/v1beta1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -146,6 +146,12 @@ func (m *StorageClass) MarshalTo(dAtA []byte) (int, error) { } i++ } + if m.VolumeBindingMode != nil { + dAtA[i] = 0x3a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.VolumeBindingMode))) + i += copy(dAtA[i:], *m.VolumeBindingMode) + } return i, nil } @@ -242,6 +248,10 @@ func (m *StorageClass) Size() (n int) { if m.AllowVolumeExpansion != nil { n += 2 } + if m.VolumeBindingMode != nil { + l = len(*m.VolumeBindingMode) + n += 1 + l + sovGenerated(uint64(l)) + } return n } @@ -293,6 +303,7 @@ func (this *StorageClass) String() string { `ReclaimPolicy:` + valueToStringGenerated(this.ReclaimPolicy) + `,`, `MountOptions:` + fmt.Sprintf("%v", this.MountOptions) + `,`, `AllowVolumeExpansion:` + valueToStringGenerated(this.AllowVolumeExpansion) + `,`, + `VolumeBindingMode:` + valueToStringGenerated(this.VolumeBindingMode) + `,`, `}`, }, "") return s @@ -600,6 +611,36 @@ func (m *StorageClass) Unmarshal(dAtA []byte) error { } b := bool(v != 0) m.AllowVolumeExpansion = &b + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VolumeBindingMode", 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 > l { + return io.ErrUnexpectedEOF + } + s := VolumeBindingMode(dAtA[iNdEx:postIndex]) + m.VolumeBindingMode = &s + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -842,42 +883,44 @@ func init() { } var fileDescriptorGenerated = []byte{ - // 589 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0x4f, 0x4f, 0xd4, 0x40, - 0x18, 0xc6, 0xb7, 0x2c, 0xab, 0x30, 0x0b, 0x71, 0x53, 0x39, 0x34, 0x7b, 0xe8, 0x6e, 0x38, 0xf5, - 0xc2, 0x0c, 0x20, 0x1a, 0x62, 0xe2, 0xc1, 0x12, 0x0e, 0x26, 0x10, 0x36, 0x35, 0xf1, 0x60, 0x3c, - 0x38, 0x5b, 0x5e, 0xcb, 0xd8, 0x76, 0xa6, 0x99, 0x99, 0xae, 0xee, 0xcd, 0x8f, 0xe0, 0x37, 0xf2, - 0x64, 0xc2, 0x91, 0x23, 0xa7, 0x46, 0xea, 0xb7, 0xf0, 0x64, 0xfa, 0x47, 0x5a, 0x58, 0x88, 0xdc, - 0x3a, 0xef, 0xfb, 0xfc, 0x9e, 0xb7, 0xf3, 0xce, 0x83, 0x0e, 0xc2, 0x7d, 0x85, 0x99, 0x20, 0x61, - 0x3a, 0x05, 0xc9, 0x41, 0x83, 0x22, 0x33, 0xe0, 0xa7, 0x42, 0x92, 0xba, 0x41, 0x13, 0x46, 0x94, - 0x16, 0x92, 0x06, 0x40, 0x66, 0x3b, 0x53, 0xd0, 0x74, 0x87, 0x04, 0xc0, 0x41, 0x52, 0x0d, 0xa7, - 0x38, 0x91, 0x42, 0x0b, 0x73, 0x58, 0x69, 0x31, 0x4d, 0x18, 0xae, 0xb5, 0xb8, 0xd6, 0x0e, 0xb7, - 0x02, 0xa6, 0xcf, 0xd2, 0x29, 0xf6, 0x45, 0x4c, 0x02, 0x11, 0x08, 0x52, 0x22, 0xd3, 0xf4, 0x53, - 0x79, 0x2a, 0x0f, 0xe5, 0x57, 0x65, 0x35, 0xdc, 0x6c, 0x8d, 0xf5, 0x85, 0x2c, 0x66, 0xde, 0x1e, - 0x37, 0xdc, 0x6b, 0x34, 0x31, 0xf5, 0xcf, 0x18, 0x07, 0x39, 0x27, 0x49, 0x18, 0x14, 0x05, 0x45, - 0x62, 0xd0, 0xf4, 0x2e, 0x8a, 0xdc, 0x47, 0xc9, 0x94, 0x6b, 0x16, 0xc3, 0x02, 0xf0, 0xe2, 0x7f, - 0x80, 0xf2, 0xcf, 0x20, 0xa6, 0x0b, 0xdc, 0xb3, 0xfb, 0xb8, 0x54, 0xb3, 0x88, 0x30, 0xae, 0x95, - 0x96, 0xb7, 0xa1, 0xcd, 0x9f, 0xcb, 0x68, 0xed, 0x6d, 0xb5, 0xba, 0x83, 0x88, 0x2a, 0x65, 0x7e, - 0x44, 0x2b, 0xc5, 0x4d, 0x4e, 0xa9, 0xa6, 0x96, 0x31, 0x36, 0x9c, 0xfe, 0xee, 0x36, 0x6e, 0xd6, - 0x7c, 0x6d, 0x8c, 0x93, 0x30, 0x28, 0x0a, 0x0a, 0x17, 0x6a, 0x3c, 0xdb, 0xc1, 0x27, 0xd3, 0xcf, - 0xe0, 0xeb, 0x63, 0xd0, 0xd4, 0x35, 0xcf, 0xb3, 0x51, 0x27, 0xcf, 0x46, 0xa8, 0xa9, 0x79, 0xd7, - 0xae, 0xe6, 0x73, 0xd4, 0x4f, 0xa4, 0x98, 0x31, 0xc5, 0x04, 0x07, 0x69, 0x2d, 0x8d, 0x0d, 0x67, - 0xd5, 0x7d, 0x5a, 0x23, 0xfd, 0x49, 0xd3, 0xf2, 0xda, 0x3a, 0x33, 0x42, 0x28, 0xa1, 0x92, 0xc6, - 0xa0, 0x41, 0x2a, 0xab, 0x3b, 0xee, 0x3a, 0xfd, 0xdd, 0x7d, 0x7c, 0x7f, 0x02, 0x70, 0xfb, 0x5a, - 0x78, 0x72, 0x8d, 0x1e, 0x72, 0x2d, 0xe7, 0xcd, 0x2f, 0x36, 0x0d, 0xaf, 0xe5, 0x6f, 0x86, 0x68, - 0x5d, 0x82, 0x1f, 0x51, 0x16, 0x4f, 0x44, 0xc4, 0xfc, 0xb9, 0xb5, 0x5c, 0xfe, 0xe6, 0x61, 0x9e, - 0x8d, 0xd6, 0xbd, 0x76, 0xe3, 0x4f, 0x36, 0xda, 0x5e, 0xcc, 0x0e, 0x9e, 0x80, 0x54, 0x4c, 0x69, - 0xe0, 0xfa, 0x9d, 0x88, 0xd2, 0x18, 0x6e, 0x30, 0xde, 0x4d, 0x6f, 0x73, 0x0f, 0xad, 0xc5, 0x22, - 0xe5, 0xfa, 0x24, 0xd1, 0x4c, 0x70, 0x65, 0xf5, 0xc6, 0x5d, 0x67, 0xd5, 0x1d, 0xe4, 0xd9, 0x68, - 0xed, 0xb8, 0x55, 0xf7, 0x6e, 0xa8, 0xcc, 0x23, 0xb4, 0x41, 0xa3, 0x48, 0x7c, 0xa9, 0x06, 0x1c, - 0x7e, 0x4d, 0x28, 0x2f, 0x56, 0x65, 0x3d, 0x1a, 0x1b, 0xce, 0x8a, 0x6b, 0xe5, 0xd9, 0x68, 0xe3, - 0xf5, 0x1d, 0x7d, 0xef, 0x4e, 0x6a, 0xf8, 0x0a, 0x3d, 0xb9, 0xb5, 0x23, 0x73, 0x80, 0xba, 0x21, - 0xcc, 0xcb, 0x14, 0xac, 0x7a, 0xc5, 0xa7, 0xb9, 0x81, 0x7a, 0x33, 0x1a, 0xa5, 0x50, 0x3d, 0x9a, - 0x57, 0x1d, 0x5e, 0x2e, 0xed, 0x1b, 0x9b, 0x3f, 0x0c, 0x34, 0x68, 0x2f, 0xfc, 0x88, 0x29, 0x6d, - 0x7e, 0x58, 0xc8, 0x12, 0x7e, 0x58, 0x96, 0x0a, 0xba, 0x4c, 0xd2, 0xa0, 0x7e, 0xa6, 0x95, 0x7f, - 0x95, 0x56, 0x8e, 0x8e, 0x51, 0x8f, 0x69, 0x88, 0x95, 0xb5, 0x54, 0x66, 0xc1, 0x79, 0x68, 0x16, - 0xdc, 0xf5, 0xda, 0xb4, 0xf7, 0xa6, 0xc0, 0xbd, 0xca, 0xc5, 0xdd, 0x3a, 0xbf, 0xb2, 0x3b, 0x17, - 0x57, 0x76, 0xe7, 0xf2, 0xca, 0xee, 0x7c, 0xcb, 0x6d, 0xe3, 0x3c, 0xb7, 0x8d, 0x8b, 0xdc, 0x36, - 0x2e, 0x73, 0xdb, 0xf8, 0x95, 0xdb, 0xc6, 0xf7, 0xdf, 0x76, 0xe7, 0xfd, 0xe3, 0xda, 0xf1, 0x6f, - 0x00, 0x00, 0x00, 0xff, 0xff, 0x1b, 0xae, 0x44, 0x72, 0xc1, 0x04, 0x00, 0x00, + // 622 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xcd, 0x6e, 0xd3, 0x40, + 0x14, 0x85, 0xe3, 0x86, 0xd0, 0x76, 0xd2, 0x8a, 0xd4, 0x74, 0x61, 0x65, 0x61, 0x47, 0x5d, 0x45, + 0x48, 0x1d, 0xb7, 0xa5, 0xa0, 0x0a, 0x89, 0x05, 0xae, 0xba, 0x40, 0x6a, 0xd4, 0xc8, 0x48, 0x15, + 0x42, 0x2c, 0x98, 0x38, 0x17, 0x77, 0x88, 0x3d, 0x63, 0xcd, 0x8c, 0x03, 0xd9, 0xf1, 0x08, 0xbc, + 0x01, 0x8f, 0xc2, 0xb6, 0xcb, 0x2e, 0xbb, 0xb2, 0xa8, 0x79, 0x8b, 0xae, 0x90, 0x7f, 0x68, 0xdc, + 0xfc, 0x88, 0xee, 0x3c, 0xf7, 0x9e, 0xef, 0xdc, 0x99, 0xeb, 0x83, 0x8e, 0x47, 0x47, 0x12, 0x53, + 0x6e, 0x8f, 0xe2, 0x01, 0x08, 0x06, 0x0a, 0xa4, 0x3d, 0x06, 0x36, 0xe4, 0xc2, 0x2e, 0x1b, 0x24, + 0xa2, 0xb6, 0x54, 0x5c, 0x10, 0x1f, 0xec, 0xf1, 0xfe, 0x00, 0x14, 0xd9, 0xb7, 0x7d, 0x60, 0x20, + 0x88, 0x82, 0x21, 0x8e, 0x04, 0x57, 0x5c, 0x6f, 0x17, 0x5a, 0x4c, 0x22, 0x8a, 0x4b, 0x2d, 0x2e, + 0xb5, 0xed, 0x5d, 0x9f, 0xaa, 0x8b, 0x78, 0x80, 0x3d, 0x1e, 0xda, 0x3e, 0xf7, 0xb9, 0x9d, 0x23, + 0x83, 0xf8, 0x73, 0x7e, 0xca, 0x0f, 0xf9, 0x57, 0x61, 0xd5, 0xde, 0xa9, 0x8c, 0xf5, 0xb8, 0xc8, + 0x66, 0xce, 0x8e, 0x6b, 0x1f, 0x4e, 0x35, 0x21, 0xf1, 0x2e, 0x28, 0x03, 0x31, 0xb1, 0xa3, 0x91, + 0x9f, 0x15, 0xa4, 0x1d, 0x82, 0x22, 0x8b, 0x28, 0x7b, 0x19, 0x25, 0x62, 0xa6, 0x68, 0x08, 0x73, + 0xc0, 0xcb, 0xff, 0x01, 0xd2, 0xbb, 0x80, 0x90, 0xcc, 0x71, 0xcf, 0x97, 0x71, 0xb1, 0xa2, 0x81, + 0x4d, 0x99, 0x92, 0x4a, 0xcc, 0x42, 0x3b, 0x3f, 0x1b, 0x68, 0xe3, 0x5d, 0xb1, 0xba, 0xe3, 0x80, + 0x48, 0xa9, 0x7f, 0x42, 0x6b, 0xd9, 0x4b, 0x86, 0x44, 0x11, 0x43, 0xeb, 0x68, 0xdd, 0xe6, 0xc1, + 0x1e, 0x9e, 0xae, 0xf9, 0xce, 0x18, 0x47, 0x23, 0x3f, 0x2b, 0x48, 0x9c, 0xa9, 0xf1, 0x78, 0x1f, + 0x9f, 0x0d, 0xbe, 0x80, 0xa7, 0x7a, 0xa0, 0x88, 0xa3, 0x5f, 0x26, 0x56, 0x2d, 0x4d, 0x2c, 0x34, + 0xad, 0xb9, 0x77, 0xae, 0xfa, 0x0b, 0xd4, 0x8c, 0x04, 0x1f, 0x53, 0x49, 0x39, 0x03, 0x61, 0xac, + 0x74, 0xb4, 0xee, 0xba, 0xf3, 0xb4, 0x44, 0x9a, 0xfd, 0x69, 0xcb, 0xad, 0xea, 0xf4, 0x00, 0xa1, + 0x88, 0x08, 0x12, 0x82, 0x02, 0x21, 0x8d, 0x7a, 0xa7, 0xde, 0x6d, 0x1e, 0x1c, 0xe1, 0xe5, 0x09, + 0xc0, 0xd5, 0x67, 0xe1, 0xfe, 0x1d, 0x7a, 0xc2, 0x94, 0x98, 0x4c, 0xaf, 0x38, 0x6d, 0xb8, 0x15, + 0x7f, 0x7d, 0x84, 0x36, 0x05, 0x78, 0x01, 0xa1, 0x61, 0x9f, 0x07, 0xd4, 0x9b, 0x18, 0x8f, 0xf2, + 0x6b, 0x9e, 0xa4, 0x89, 0xb5, 0xe9, 0x56, 0x1b, 0xb7, 0x89, 0xb5, 0x37, 0x9f, 0x1d, 0xdc, 0x07, + 0x21, 0xa9, 0x54, 0xc0, 0xd4, 0x39, 0x0f, 0xe2, 0x10, 0xee, 0x31, 0xee, 0x7d, 0x6f, 0xfd, 0x10, + 0x6d, 0x84, 0x3c, 0x66, 0xea, 0x2c, 0x52, 0x94, 0x33, 0x69, 0x34, 0x3a, 0xf5, 0xee, 0xba, 0xd3, + 0x4a, 0x13, 0x6b, 0xa3, 0x57, 0xa9, 0xbb, 0xf7, 0x54, 0xfa, 0x29, 0xda, 0x26, 0x41, 0xc0, 0xbf, + 0x16, 0x03, 0x4e, 0xbe, 0x45, 0x84, 0x65, 0xab, 0x32, 0x1e, 0x77, 0xb4, 0xee, 0x9a, 0x63, 0xa4, + 0x89, 0xb5, 0xfd, 0x66, 0x41, 0xdf, 0x5d, 0x48, 0xe9, 0xef, 0xd1, 0xd6, 0x38, 0x2f, 0x39, 0x94, + 0x0d, 0x29, 0xf3, 0x7b, 0x7c, 0x08, 0xc6, 0x6a, 0xfe, 0xe8, 0x67, 0x69, 0x62, 0x6d, 0x9d, 0xcf, + 0x36, 0x6f, 0x17, 0x15, 0xdd, 0x79, 0x93, 0xf6, 0x6b, 0xf4, 0x64, 0x66, 0xfb, 0x7a, 0x0b, 0xd5, + 0x47, 0x30, 0xc9, 0xf3, 0xb5, 0xee, 0x66, 0x9f, 0xfa, 0x36, 0x6a, 0x8c, 0x49, 0x10, 0x43, 0x11, + 0x07, 0xb7, 0x38, 0xbc, 0x5a, 0x39, 0xd2, 0x76, 0x7e, 0x69, 0xa8, 0x55, 0xfd, 0x95, 0xa7, 0x54, + 0x2a, 0xfd, 0xe3, 0x5c, 0x4a, 0xf1, 0xc3, 0x52, 0x9a, 0xd1, 0x79, 0x46, 0x5b, 0x65, 0x00, 0xd6, + 0xfe, 0x55, 0x2a, 0x09, 0xed, 0xa1, 0x06, 0x55, 0x10, 0x4a, 0x63, 0x25, 0x4f, 0x59, 0xf7, 0xa1, + 0x29, 0x73, 0x36, 0x4b, 0xd3, 0xc6, 0xdb, 0x0c, 0x77, 0x0b, 0x17, 0x67, 0xf7, 0xf2, 0xc6, 0xac, + 0x5d, 0xdd, 0x98, 0xb5, 0xeb, 0x1b, 0xb3, 0xf6, 0x3d, 0x35, 0xb5, 0xcb, 0xd4, 0xd4, 0xae, 0x52, + 0x53, 0xbb, 0x4e, 0x4d, 0xed, 0x77, 0x6a, 0x6a, 0x3f, 0xfe, 0x98, 0xb5, 0x0f, 0xab, 0xa5, 0xe3, + 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x66, 0xe2, 0x8e, 0x84, 0x1b, 0x05, 0x00, 0x00, } diff --git a/staging/src/k8s.io/api/storage/v1beta1/generated.proto b/staging/src/k8s.io/api/storage/v1beta1/generated.proto index f8070b67cf4..f9e1d29503c 100644 --- a/staging/src/k8s.io/api/storage/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/storage/v1beta1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -63,6 +63,13 @@ message StorageClass { // AllowVolumeExpansion shows whether the storage class allow volume expand // +optional optional bool allowVolumeExpansion = 6; + + // VolumeBindingMode indicates how PersistentVolumeClaims should be + // provisioned and bound. When unset, VolumeBindingImmediate is used. + // This field is alpha-level and is only honored by servers that enable + // the VolumeScheduling feature. + // +optional + optional string volumeBindingMode = 7; } // StorageClassList is a collection of storage classes. diff --git a/staging/src/k8s.io/api/storage/v1beta1/register.go b/staging/src/k8s.io/api/storage/v1beta1/register.go index 759ba81aa9b..7f1f0c8e835 100644 --- a/staging/src/k8s.io/api/storage/v1beta1/register.go +++ b/staging/src/k8s.io/api/storage/v1beta1/register.go @@ -41,7 +41,7 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &StorageClass{}, diff --git a/staging/src/k8s.io/api/storage/v1beta1/types.go b/staging/src/k8s.io/api/storage/v1beta1/types.go index e5036b55b33..7fb9ad98077 100644 --- a/staging/src/k8s.io/api/storage/v1beta1/types.go +++ b/staging/src/k8s.io/api/storage/v1beta1/types.go @@ -59,6 +59,13 @@ type StorageClass struct { // AllowVolumeExpansion shows whether the storage class allow volume expand // +optional AllowVolumeExpansion *bool `json:"allowVolumeExpansion,omitempty" protobuf:"varint,6,opt,name=allowVolumeExpansion"` + + // VolumeBindingMode indicates how PersistentVolumeClaims should be + // provisioned and bound. When unset, VolumeBindingImmediate is used. + // This field is alpha-level and is only honored by servers that enable + // the VolumeScheduling feature. + // +optional + VolumeBindingMode *VolumeBindingMode `json:"volumeBindingMode,omitempty" protobuf:"bytes,7,opt,name=volumeBindingMode"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -74,3 +81,18 @@ type StorageClassList struct { // Items is the list of StorageClasses Items []StorageClass `json:"items" protobuf:"bytes,2,rep,name=items"` } + +// VolumeBindingMode indicates how PersistentVolumeClaims should be bound. +type VolumeBindingMode string + +const ( + // VolumeBindingImmediate indicates that PersistentVolumeClaims should be + // immediately provisioned and bound. This is the default mode. + VolumeBindingImmediate VolumeBindingMode = "Immediate" + + // VolumeBindingWaitForFirstConsumer indicates that PersistentVolumeClaims + // should not be provisioned and bound until the first Pod is created that + // references the PeristentVolumeClaim. The volume provisioning and + // binding will occur during Pod scheduing. + VolumeBindingWaitForFirstConsumer VolumeBindingMode = "WaitForFirstConsumer" +) 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 e2148c231f4..85886f7dfb3 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 @@ -35,6 +35,7 @@ var map_StorageClass = map[string]string{ "reclaimPolicy": "Dynamically provisioned PersistentVolumes of this storage class are created with this reclaimPolicy. Defaults to Delete.", "mountOptions": "Dynamically provisioned PersistentVolumes of this storage class are created with these mountOptions, e.g. [\"ro\", \"soft\"]. Not validated - mount of the PVs will simply fail if one is invalid.", "allowVolumeExpansion": "AllowVolumeExpansion shows whether the storage class allow volume expand", + "volumeBindingMode": "VolumeBindingMode indicates how PersistentVolumeClaims should be provisioned and bound. When unset, VolumeBindingImmediate is used. This field is alpha-level and is only honored by servers that enable the VolumeScheduling feature.", } func (StorageClass) 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 bf1f91a67d7..715b17f43da 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 @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,32 +22,9 @@ package v1beta1 import ( v1 "k8s.io/api/core/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StorageClass).DeepCopyInto(out.(*StorageClass)) - return nil - }, InType: reflect.TypeOf(&StorageClass{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StorageClassList).DeepCopyInto(out.(*StorageClassList)) - return nil - }, InType: reflect.TypeOf(&StorageClassList{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *StorageClass) DeepCopyInto(out *StorageClass) { *out = *in @@ -83,6 +60,15 @@ func (in *StorageClass) DeepCopyInto(out *StorageClass) { **out = **in } } + if in.VolumeBindingMode != nil { + in, out := &in.VolumeBindingMode, &out.VolumeBindingMode + if *in == nil { + *out = nil + } else { + *out = new(VolumeBindingMode) + **out = **in + } + } return } diff --git a/staging/src/k8s.io/apiextensions-apiserver/BUILD b/staging/src/k8s.io/apiextensions-apiserver/BUILD index ad62604f378..12f3029b253 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/BUILD @@ -8,8 +8,8 @@ load( go_binary( name = "apiextensions-apiserver", + embed = [":go_default_library"], importpath = "k8s.io/apiextensions-apiserver", - library = ":go_default_library", ) go_library( diff --git a/staging/src/k8s.io/apiextensions-apiserver/Godeps/Godeps.json b/staging/src/k8s.io/apiextensions-apiserver/Godeps/Godeps.json index c24c1f0d1af..08607c1ddcf 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/Godeps/Godeps.json +++ b/staging/src/k8s.io/apiextensions-apiserver/Godeps/Godeps.json @@ -1,6 +1,6 @@ { "ImportPath": "k8s.io/apiextensions-apiserver", - "GoVersion": "go1.8", + "GoVersion": "go1.9", "GodepVersion": "v79", "Packages": [ "./..." @@ -168,27 +168,31 @@ }, { "ImportPath": "github.com/golang/protobuf/jsonpb", - "Rev": "4bd1920723d7b7c925de087aa32e2187708897f7" + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" }, { "ImportPath": "github.com/golang/protobuf/proto", - "Rev": "4bd1920723d7b7c925de087aa32e2187708897f7" + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" }, { "ImportPath": "github.com/golang/protobuf/ptypes", - "Rev": "4bd1920723d7b7c925de087aa32e2187708897f7" + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" }, { "ImportPath": "github.com/golang/protobuf/ptypes/any", - "Rev": "4bd1920723d7b7c925de087aa32e2187708897f7" + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" }, { "ImportPath": "github.com/golang/protobuf/ptypes/duration", - "Rev": "4bd1920723d7b7c925de087aa32e2187708897f7" + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" + }, + { + "ImportPath": "github.com/golang/protobuf/ptypes/struct", + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" }, { "ImportPath": "github.com/golang/protobuf/ptypes/timestamp", - "Rev": "4bd1920723d7b7c925de087aa32e2187708897f7" + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" }, { "ImportPath": "github.com/google/btree", @@ -256,7 +260,7 @@ }, { "ImportPath": "github.com/json-iterator/go", - "Rev": "36b14963da70d11297d313183d7e6388c8510e1e" + "Rev": "13f86432b882000a51c6e610c620974462691a97" }, { "ImportPath": "github.com/juju/ratelimit", @@ -290,10 +294,6 @@ "ImportPath": "github.com/peterbourgon/diskv", "Rev": "5f041e8faa004a95c88a202771f4cc3e991971e6" }, - { - "ImportPath": "github.com/pkg/errors", - "Rev": "a22138067af1c4942683050411a841ade67fe1eb" - }, { "ImportPath": "github.com/pmezard/go-difflib/difflib", "Rev": "d8ed2627bdf02c080bf22230dbb337003b7aba2d" @@ -392,11 +392,11 @@ }, { "ImportPath": "golang.org/x/sys/unix", - "Rev": "7ddbeae9ae08c6a06a59597f0c9edbc5ff2444ce" + "Rev": "95c6576299259db960f6c5b9b69ea52422860fce" }, { "ImportPath": "golang.org/x/sys/windows", - "Rev": "7ddbeae9ae08c6a06a59597f0c9edbc5ff2444ce" + "Rev": "95c6576299259db960f6c5b9b69ea52422860fce" }, { "ImportPath": "golang.org/x/text/cases", @@ -514,10 +514,18 @@ "ImportPath": "gopkg.in/yaml.v2", "Rev": "53feefa2559fb8dfa8d81baad31be332c97d6c77" }, + { + "ImportPath": "k8s.io/api/admission/v1beta1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/api/admissionregistration/v1alpha1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/api/admissionregistration/v1beta1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/api/apps/v1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -574,6 +582,10 @@ "ImportPath": "k8s.io/api/core/v1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/api/events/v1beta1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/api/extensions/v1beta1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -610,6 +622,10 @@ "ImportPath": "k8s.io/api/storage/v1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/api/storage/v1alpha1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/api/storage/v1beta1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -690,10 +706,6 @@ "ImportPath": "k8s.io/apimachinery/pkg/conversion/queryparams", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, - { - "ImportPath": "k8s.io/apimachinery/pkg/conversion/unstructured", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - }, { "ImportPath": "k8s.io/apimachinery/pkg/fields", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -818,6 +830,10 @@ "ImportPath": "k8s.io/apimachinery/pkg/util/wait", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apimachinery/pkg/util/waitgroup", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apimachinery/pkg/util/yaml", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -854,6 +870,10 @@ "ImportPath": "k8s.io/apiserver/pkg/admission/initializer", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apiserver/pkg/admission/metrics", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apiserver/pkg/admission/plugin/initialization", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -862,6 +882,46 @@ "ImportPath": "k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apiserver/pkg/admission/plugin/webhook/config", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/apiserver/pkg/admission/plugin/webhook/errors", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/apiserver/pkg/admission/plugin/webhook/mutating", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/apiserver/pkg/admission/plugin/webhook/namespace", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/apiserver/pkg/admission/plugin/webhook/request", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/apiserver/pkg/admission/plugin/webhook/rules", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/apiserver/pkg/admission/plugin/webhook/validating", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/apiserver/pkg/admission/plugin/webhook/versioned", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apiserver/pkg/apis/apiserver", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1146,6 +1206,10 @@ "ImportPath": "k8s.io/client-go/informers/admissionregistration/v1alpha1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/client-go/informers/admissionregistration/v1beta1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/client-go/informers/apps", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1206,6 +1270,14 @@ "ImportPath": "k8s.io/client-go/informers/core/v1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/client-go/informers/events", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/client-go/informers/events/v1beta1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/client-go/informers/extensions", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1274,6 +1346,10 @@ "ImportPath": "k8s.io/client-go/informers/storage/v1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/client-go/informers/storage/v1alpha1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/client-go/informers/storage/v1beta1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1290,6 +1366,10 @@ "ImportPath": "k8s.io/client-go/kubernetes/typed/admissionregistration/v1alpha1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/client-go/kubernetes/typed/apps/v1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1346,6 +1426,10 @@ "ImportPath": "k8s.io/client-go/kubernetes/typed/core/v1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/client-go/kubernetes/typed/events/v1beta1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/client-go/kubernetes/typed/extensions/v1beta1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1382,6 +1466,10 @@ "ImportPath": "k8s.io/client-go/kubernetes/typed/storage/v1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/client-go/kubernetes/typed/storage/v1alpha1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/client-go/kubernetes/typed/storage/v1beta1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1390,6 +1478,10 @@ "ImportPath": "k8s.io/client-go/listers/admissionregistration/v1alpha1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/client-go/listers/admissionregistration/v1beta1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/client-go/listers/apps/v1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1430,6 +1522,10 @@ "ImportPath": "k8s.io/client-go/listers/core/v1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/client-go/listers/events/v1beta1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/client-go/listers/extensions/v1beta1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1466,6 +1562,10 @@ "ImportPath": "k8s.io/client-go/listers/storage/v1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/client-go/listers/storage/v1alpha1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/client-go/listers/storage/v1beta1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1548,19 +1648,23 @@ }, { "ImportPath": "k8s.io/kube-openapi/pkg/builder", - "Rev": "868f2f29720b192240e18284659231b440f9cda5" + "Rev": "39a7bf85c140f972372c2a0d1ee40adbf0c8bfe1" }, { "ImportPath": "k8s.io/kube-openapi/pkg/common", - "Rev": "868f2f29720b192240e18284659231b440f9cda5" + "Rev": "39a7bf85c140f972372c2a0d1ee40adbf0c8bfe1" }, { "ImportPath": "k8s.io/kube-openapi/pkg/handler", - "Rev": "868f2f29720b192240e18284659231b440f9cda5" + "Rev": "39a7bf85c140f972372c2a0d1ee40adbf0c8bfe1" }, { "ImportPath": "k8s.io/kube-openapi/pkg/util", - "Rev": "868f2f29720b192240e18284659231b440f9cda5" + "Rev": "39a7bf85c140f972372c2a0d1ee40adbf0c8bfe1" + }, + { + "ImportPath": "k8s.io/kube-openapi/pkg/util/proto", + "Rev": "39a7bf85c140f972372c2a0d1ee40adbf0c8bfe1" }, { "ImportPath": "k8s.io/apimachinery/pkg/api/equality", @@ -1574,10 +1678,6 @@ "ImportPath": "k8s.io/apimachinery/pkg/api/meta", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, - { - "ImportPath": "k8s.io/apimachinery/pkg/api/resource", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - }, { "ImportPath": "k8s.io/apimachinery/pkg/api/testing/fuzzer", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1598,10 +1698,6 @@ "ImportPath": "k8s.io/apimachinery/pkg/apimachinery/registered", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, - { - "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/fuzzer", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - }, { "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1650,10 +1746,6 @@ "ImportPath": "k8s.io/apimachinery/pkg/util/errors", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, - { - "ImportPath": "k8s.io/apimachinery/pkg/util/intstr", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - }, { "ImportPath": "k8s.io/apimachinery/pkg/util/json", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1770,10 +1862,6 @@ "ImportPath": "k8s.io/client-go/tools/cache", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, - { - "ImportPath": "k8s.io/client-go/tools/clientcmd", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - }, { "ImportPath": "k8s.io/client-go/util/flowcontrol", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" diff --git a/staging/src/k8s.io/apiextensions-apiserver/LICENSE b/staging/src/k8s.io/apiextensions-apiserver/LICENSE new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/README.md b/staging/src/k8s.io/apiextensions-apiserver/README.md new file mode 100644 index 00000000000..be75b9ba49d --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/README.md @@ -0,0 +1,20 @@ +# apiextensions-apiserver + +Implements: https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/thirdpartyresources.md + +It provides an API for registering `CustomResourceDefinitions`. + +## Purpose + +This API server provides the implementation for `CustomResourceDefinitions` which is included as +delegate server inside of `kube-apiserver`. + + +## Compatibility + +HEAD of this repo will match HEAD of k8s.io/apiserver, k8s.io/apimachinery, and k8s.io/client-go. + +## Where does it come from? + +`apiextensions-apiserver` is synced from https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiextensions-apiserver. +Code changes are made in that location, merged into `k8s.io/kubernetes` and later synced here. diff --git a/staging/src/k8s.io/apiextensions-apiserver/code-of-conduct.md b/staging/src/k8s.io/apiextensions-apiserver/code-of-conduct.md new file mode 100644 index 00000000000..0d15c00cf32 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/code-of-conduct.md @@ -0,0 +1,3 @@ +# Kubernetes Community Code of Conduct + +Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/BUILD b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/BUILD index 3f09a3cc392..9bf1895c2b9 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/BUILD @@ -1,33 +1,3 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_binary", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["main.go"], - importpath = "k8s.io/apiextensions-apiserver/examples/client-go", - deps = [ - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1:go_default_library", - "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/client:go_default_library", - "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/controller:go_default_library", - "//vendor/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", - ], -) - -go_binary( - name = "client-go", - importpath = "k8s.io/apiextensions-apiserver/examples/client-go", - library = ":go_default_library", -) - filegroup( name = "package-srcs", srcs = glob(["**"]), @@ -39,9 +9,11 @@ filegroup( name = "all-srcs", srcs = [ ":package-srcs", - "//staging/src/k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1:all-srcs", - "//staging/src/k8s.io/apiextensions-apiserver/examples/client-go/client:all-srcs", - "//staging/src/k8s.io/apiextensions-apiserver/examples/client-go/controller:all-srcs", + "//staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr:all-srcs", + "//staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned:all-srcs", + "//staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions:all-srcs", + "//staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/listers/cr/v1:all-srcs", ], tags = ["automanaged"], + visibility = ["//visibility:public"], ) diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/README.md b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/README.md index 3ac247f4a1f..1949e5d47f6 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/README.md +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/README.md @@ -2,18 +2,29 @@ **Note:** CustomResourceDefinition is the successor of the deprecated ThirdPartyResource. -This particular example demonstrates how to perform basic operations such as: +This particular example demonstrates how to generate a client for CustomResources using [`k8s.io/code-generator`](https://github.com/kubernetes/code-generator). The clientset can +be generated using the `./hack/update-codegen.sh` script. -* How to register a new custom resource (custom resource type) using a CustomResourceDefinition -* How to create/get/list instances of your new resource type (update/delete/etc work as well but are not demonstrated) -* How to setup a controller on resource handling create/update/delete events +The `update-codegen` script will automatically generate the following files and +directories: -## Running +* `pkg/apis/cr/v1/zz_generated.deepcopy.go` +* `pkg/client/` -``` -# assumes you have a working kubeconfig, not required if operating in-cluster -go run *.go -kubeconfig=$HOME/.kube/config -``` +The following code-generators are used: + +* `deepcopy-gen` - creates a method `func (t* T) DeepCopy() *T` for each type T +* `client-gen` - creates typed clientsets for CustomResource APIGroups +* `informer-gen` - creates informers for CustomResources which offer an event based +interface to react on changes of CustomResources on the server +* `lister-gen` - creates listers for CustomResources which offer a read-only caching layer for GET and LIST requests. + +Changes should not be made to these files manually, and when creating your own +controller based off of this implementation you should not copy these files and +instead run the `update-codegen` script to generate your own. + +Please see [`k8s.io/sample-controller`](https://github.com/kubernetes/sample-controller) for an example +controller for CustomResources using the generated client. ## Use Cases @@ -44,9 +55,3 @@ type User struct { Password string `json:"password"` } ``` - -## Cleanup - -Successfully running this program will clean the created artifacts. If you terminate the program without completing, you can clean up the created CustomResourceDefinition with: - - kubectl delete crd examples.cr.client-go.k8s.io diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1/BUILD b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1/BUILD deleted file mode 100644 index f81f1a33d4d..00000000000 --- a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1/BUILD +++ /dev/null @@ -1,53 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = ["roundtrip_test.go"], - importpath = "k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1", - library = ":go_default_library", - deps = [ - "//vendor/github.com/google/gofuzz:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/testing/fuzzer:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/testing/roundtrip:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/fuzzer:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", - ], -) - -go_library( - name = "go_default_library", - srcs = [ - "doc.go", - "register.go", - "types.go", - "zz_generated.deepcopy.go", - ], - importpath = "k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1", - deps = [ - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1/doc.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1/doc.go deleted file mode 100644 index be4d74fc2a1..00000000000 --- a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1/doc.go +++ /dev/null @@ -1,18 +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 -package v1 diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1/register.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1/register.go deleted file mode 100644 index 1ee348eeab5..00000000000 --- a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1/register.go +++ /dev/null @@ -1,49 +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 v1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -var ( - SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) - AddToScheme = SchemeBuilder.AddToScheme -) - -// GroupName is the group name used in this package. -const GroupName = "cr.client-go.k8s.io" - -// SchemeGroupVersion is the group version used to register these objects. -var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"} - -// Resource takes an unqualified resource and returns a Group-qualified GroupResource. -func Resource(resource string) schema.GroupResource { - return SchemeGroupVersion.WithResource(resource).GroupResource() -} - -// addKnownTypes adds the set of types defined in this package to the supplied scheme. -func addKnownTypes(scheme *runtime.Scheme) error { - scheme.AddKnownTypes(SchemeGroupVersion, - &Example{}, - &ExampleList{}, - ) - metav1.AddToGroupVersion(scheme, SchemeGroupVersion) - return nil -} diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1/roundtrip_test.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1/roundtrip_test.go deleted file mode 100644 index 7373c78b588..00000000000 --- a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1/roundtrip_test.go +++ /dev/null @@ -1,66 +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 v1 - -import ( - "math/rand" - "testing" - - "github.com/google/gofuzz" - - "k8s.io/apimachinery/pkg/api/testing/fuzzer" - roundtrip "k8s.io/apimachinery/pkg/api/testing/roundtrip" - metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/serializer" - runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" -) - -var _ runtime.Object = &Example{} -var _ metav1.ObjectMetaAccessor = &Example{} - -var _ runtime.Object = &ExampleList{} -var _ metav1.ListMetaAccessor = &ExampleList{} - -func exampleFuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} { - return []interface{}{ - func(obj *ExampleList, c fuzz.Continue) { - c.FuzzNoCustom(obj) - obj.Items = make([]Example, c.Intn(10)) - for i := range obj.Items { - c.Fuzz(&obj.Items[i]) - } - }, - } -} - -// TestRoundTrip tests that the third-party kinds can be marshaled and unmarshaled correctly to/from JSON -// without the loss of information. Moreover, deep copy is tested. -func TestRoundTrip(t *testing.T) { - scheme := runtime.NewScheme() - codecs := serializer.NewCodecFactory(scheme) - - AddToScheme(scheme) - - seed := rand.Int63() - fuzzerFuncs := fuzzer.MergeFuzzerFuncs(metafuzzer.Funcs, exampleFuzzerFuncs) - fuzzer := fuzzer.FuzzerFor(fuzzerFuncs, rand.NewSource(seed), codecs) - - roundtrip.RoundTripSpecificKindWithoutProtobuf(t, SchemeGroupVersion.WithKind("Example"), scheme, codecs, fuzzer, nil) - roundtrip.RoundTripSpecificKindWithoutProtobuf(t, SchemeGroupVersion.WithKind("ExampleList"), scheme, codecs, fuzzer, nil) -} diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1/types.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1/types.go deleted file mode 100644 index 03ff9495638..00000000000 --- a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1/types.go +++ /dev/null @@ -1,55 +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 v1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const ExampleResourcePlural = "examples" - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type Example struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata"` - Spec ExampleSpec `json:"spec"` - Status ExampleStatus `json:"status,omitempty"` -} - -type ExampleSpec struct { - Foo string `json:"foo"` - Bar bool `json:"bar"` -} - -type ExampleStatus struct { - State ExampleState `json:"state,omitempty"` - Message string `json:"message,omitempty"` -} - -type ExampleState string - -const ( - ExampleStateCreated ExampleState = "Created" - ExampleStateProcessed ExampleState = "Processed" -) - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type ExampleList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - Items []Example `json:"items"` -} diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1/zz_generated.deepcopy.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1/zz_generated.deepcopy.go deleted file mode 100644 index e9f2a4c0410..00000000000 --- a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1/zz_generated.deepcopy.go +++ /dev/null @@ -1,146 +0,0 @@ -// +build !ignore_autogenerated - -/* -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. -*/ - -// This file was autogenerated by deepcopy-gen. Do not edit it manually! - -package v1 - -import ( - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" -) - -// GetGeneratedDeepCopyFuncs returns the generated funcs, since we aren't registering them. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func GetGeneratedDeepCopyFuncs() []conversion.GeneratedDeepCopyFunc { - return []conversion.GeneratedDeepCopyFunc{ - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Example).DeepCopyInto(out.(*Example)) - return nil - }, InType: reflect.TypeOf(&Example{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExampleList).DeepCopyInto(out.(*ExampleList)) - return nil - }, InType: reflect.TypeOf(&ExampleList{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExampleSpec).DeepCopyInto(out.(*ExampleSpec)) - return nil - }, InType: reflect.TypeOf(&ExampleSpec{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExampleStatus).DeepCopyInto(out.(*ExampleStatus)) - return nil - }, InType: reflect.TypeOf(&ExampleStatus{})}, - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Example) DeepCopyInto(out *Example) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec - out.Status = in.Status - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Example. -func (in *Example) DeepCopy() *Example { - if in == nil { - return nil - } - out := new(Example) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Example) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExampleList) DeepCopyInto(out *ExampleList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Example, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExampleList. -func (in *ExampleList) DeepCopy() *ExampleList { - if in == nil { - return nil - } - out := new(ExampleList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ExampleList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExampleSpec) DeepCopyInto(out *ExampleSpec) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExampleSpec. -func (in *ExampleSpec) DeepCopy() *ExampleSpec { - if in == nil { - return nil - } - out := new(ExampleSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExampleStatus) DeepCopyInto(out *ExampleStatus) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExampleStatus. -func (in *ExampleStatus) DeepCopy() *ExampleStatus { - if in == nil { - return nil - } - out := new(ExampleStatus) - in.DeepCopyInto(out) - return out -} diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/client/BUILD b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/client/BUILD deleted file mode 100644 index 02431533907..00000000000 --- a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/client/BUILD +++ /dev/null @@ -1,40 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "client.go", - "cr.go", - ], - importpath = "k8s.io/apiextensions-apiserver/examples/client-go/client", - deps = [ - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1:go_default_library", - "//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library", - "//vendor/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/client/client.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/client/client.go deleted file mode 100644 index b5f69dc2217..00000000000 --- a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/client/client.go +++ /dev/null @@ -1,45 +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 client - -import ( - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/client-go/rest" - - crv1 "k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1" -) - -func NewClient(cfg *rest.Config) (*rest.RESTClient, *runtime.Scheme, error) { - scheme := runtime.NewScheme() - if err := crv1.AddToScheme(scheme); err != nil { - return nil, nil, err - } - - config := *cfg - config.GroupVersion = &crv1.SchemeGroupVersion - config.APIPath = "/apis" - config.ContentType = runtime.ContentTypeJSON - config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: serializer.NewCodecFactory(scheme)} - - client, err := rest.RESTClientFor(&config) - if err != nil { - return nil, nil, err - } - - return client, scheme, nil -} diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/client/cr.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/client/cr.go deleted file mode 100644 index 66513b56943..00000000000 --- a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/client/cr.go +++ /dev/null @@ -1,103 +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 client - -import ( - "fmt" - "reflect" - "time" - - apiv1 "k8s.io/api/core/v1" - crv1 "k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1" - apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/errors" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/client-go/rest" - // Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters). - // _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" -) - -const exampleCRDName = crv1.ExampleResourcePlural + "." + crv1.GroupName - -func CreateCustomResourceDefinition(clientset apiextensionsclient.Interface) (*apiextensionsv1beta1.CustomResourceDefinition, error) { - crd := &apiextensionsv1beta1.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: exampleCRDName, - }, - Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{ - Group: crv1.GroupName, - Version: crv1.SchemeGroupVersion.Version, - Scope: apiextensionsv1beta1.NamespaceScoped, - Names: apiextensionsv1beta1.CustomResourceDefinitionNames{ - Plural: crv1.ExampleResourcePlural, - Kind: reflect.TypeOf(crv1.Example{}).Name(), - }, - }, - } - _, err := clientset.ApiextensionsV1beta1().CustomResourceDefinitions().Create(crd) - if err != nil { - return nil, err - } - - // wait for CRD being established - err = wait.Poll(500*time.Millisecond, 60*time.Second, func() (bool, error) { - crd, err = clientset.ApiextensionsV1beta1().CustomResourceDefinitions().Get(exampleCRDName, metav1.GetOptions{}) - if err != nil { - return false, err - } - for _, cond := range crd.Status.Conditions { - switch cond.Type { - case apiextensionsv1beta1.Established: - if cond.Status == apiextensionsv1beta1.ConditionTrue { - return true, err - } - case apiextensionsv1beta1.NamesAccepted: - if cond.Status == apiextensionsv1beta1.ConditionFalse { - fmt.Printf("Name conflict: %v\n", cond.Reason) - } - } - } - return false, err - }) - if err != nil { - deleteErr := clientset.ApiextensionsV1beta1().CustomResourceDefinitions().Delete(exampleCRDName, nil) - if deleteErr != nil { - return nil, errors.NewAggregate([]error{err, deleteErr}) - } - return nil, err - } - return crd, nil -} - -func WaitForExampleInstanceProcessed(exampleClient *rest.RESTClient, name string) error { - return wait.Poll(100*time.Millisecond, 10*time.Second, func() (bool, error) { - var example crv1.Example - err := exampleClient.Get(). - Resource(crv1.ExampleResourcePlural). - Namespace(apiv1.NamespaceDefault). - Name(name). - Do().Into(&example) - - if err == nil && example.Status.State == crv1.ExampleStateProcessed { - return true, nil - } - - return false, err - }) -} diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/controller/BUILD b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/controller/BUILD deleted file mode 100644 index d823dcf7af1..00000000000 --- a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/controller/BUILD +++ /dev/null @@ -1,33 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["controller.go"], - importpath = "k8s.io/apiextensions-apiserver/examples/client-go/controller", - deps = [ - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/client-go/rest:go_default_library", - "//vendor/k8s.io/client-go/tools/cache:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/controller/controller.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/controller/controller.go deleted file mode 100644 index d22cefebdac..00000000000 --- a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/controller/controller.go +++ /dev/null @@ -1,120 +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 controller - -import ( - "context" - "fmt" - - apiv1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/cache" - - crv1 "k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1" -) - -// Watcher is an example of watching on resource create/update/delete events -type ExampleController struct { - ExampleClient *rest.RESTClient - ExampleScheme *runtime.Scheme -} - -// Run starts an Example resource controller -func (c *ExampleController) Run(ctx context.Context) error { - fmt.Print("Watch Example objects\n") - - // Watch Example objects - _, err := c.watchExamples(ctx) - if err != nil { - fmt.Printf("Failed to register watch for Example resource: %v\n", err) - return err - } - - <-ctx.Done() - return ctx.Err() -} - -func (c *ExampleController) watchExamples(ctx context.Context) (cache.Controller, error) { - source := cache.NewListWatchFromClient( - c.ExampleClient, - crv1.ExampleResourcePlural, - apiv1.NamespaceAll, - fields.Everything()) - - _, controller := cache.NewInformer( - source, - - // The object type. - &crv1.Example{}, - - // resyncPeriod - // Every resyncPeriod, all resources in the cache will retrigger events. - // Set to 0 to disable the resync. - 0, - - // Your custom resource event handlers. - cache.ResourceEventHandlerFuncs{ - AddFunc: c.onAdd, - UpdateFunc: c.onUpdate, - DeleteFunc: c.onDelete, - }) - - go controller.Run(ctx.Done()) - return controller, nil -} - -func (c *ExampleController) onAdd(obj interface{}) { - example := obj.(*crv1.Example) - fmt.Printf("[CONTROLLER] OnAdd %s\n", example.ObjectMeta.SelfLink) - - // NEVER modify objects from the store. It's a read-only, local cache. - // You can use DeepCopy() to make a deep copy of original object and modify this copy - // Or create a copy manually for better performance - exampleCopy := example.DeepCopy() - exampleCopy.Status = crv1.ExampleStatus{ - State: crv1.ExampleStateProcessed, - Message: "Successfully processed by controller", - } - - err := c.ExampleClient.Put(). - Name(example.ObjectMeta.Name). - Namespace(example.ObjectMeta.Namespace). - Resource(crv1.ExampleResourcePlural). - Body(exampleCopy). - Do(). - Error() - - if err != nil { - fmt.Printf("ERROR updating status: %v\n", err) - } else { - fmt.Printf("UPDATED status: %#v\n", exampleCopy) - } -} - -func (c *ExampleController) onUpdate(oldObj, newObj interface{}) { - oldExample := oldObj.(*crv1.Example) - newExample := newObj.(*crv1.Example) - fmt.Printf("[CONTROLLER] OnUpdate oldObj: %s\n", oldExample.ObjectMeta.SelfLink) - fmt.Printf("[CONTROLLER] OnUpdate newObj: %s\n", newExample.ObjectMeta.SelfLink) -} - -func (c *ExampleController) onDelete(obj interface{}) { - example := obj.(*crv1.Example) - fmt.Printf("[CONTROLLER] OnDelete %s\n", example.ObjectMeta.SelfLink) -} diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/hack/update-codegen.sh b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/hack/update-codegen.sh new file mode 100755 index 00000000000..3659ad33002 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/hack/update-codegen.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# Copyright 2017 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +SCRIPT_ROOT=$(dirname ${BASH_SOURCE})/.. +CODEGEN_PKG=${CODEGEN_PKG:-$(cd ${SCRIPT_ROOT}; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo ../code-generator)} + +# generate the code with: +# --output-base because this script should also be able to run inside the vendor dir of +# k8s.io/kubernetes. The output-base is needed for the generators to output into the vendor dir +# instead of the $GOPATH directly. For normal projects this can be dropped. +${CODEGEN_PKG}/generate-groups.sh all \ + k8s.io/apiextensions-apiserver/examples/client-go/pkg/client k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis \ + cr:v1 \ + --output-base "$(dirname ${BASH_SOURCE})/../../../../.." + +# To use your own boilerplate text append: +# --go-header-file ${SCRIPT_ROOT}/hack/custom-boilerplate.go.txt diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/hack/verify-codegen.sh b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/hack/verify-codegen.sh new file mode 100755 index 00000000000..9cc02a5a4a2 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/hack/verify-codegen.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# Copyright 2017 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +SCRIPT_ROOT=$(dirname "${BASH_SOURCE}")/.. + +DIFFROOT="${SCRIPT_ROOT}/pkg" +TMP_DIFFROOT="${SCRIPT_ROOT}/_tmp/pkg" +_tmp="${SCRIPT_ROOT}/_tmp" + +cleanup() { + rm -rf "${_tmp}" +} +trap "cleanup" EXIT SIGINT + +cleanup + +mkdir -p "${TMP_DIFFROOT}" +cp -a "${DIFFROOT}"/* "${TMP_DIFFROOT}" + +"${SCRIPT_ROOT}/hack/update-codegen.sh" +echo "diffing ${DIFFROOT} against freshly generated codegen" +ret=0 +diff -Naupr "${DIFFROOT}" "${TMP_DIFFROOT}" || ret=$? +cp -a "${TMP_DIFFROOT}"/* "${DIFFROOT}" +if [[ $ret -eq 0 ]] +then + echo "${DIFFROOT} up to date." +else + echo "${DIFFROOT} is out of date. Please run hack/update-codegen.sh" + exit 1 +fi diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/main.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/main.go deleted file mode 100644 index 6b2efb7eba6..00000000000 --- a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/main.go +++ /dev/null @@ -1,123 +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. -*/ - -// Note: the example only works with the code within the same release/branch. -package main - -import ( - "context" - "flag" - "fmt" - - apiv1 "k8s.io/api/core/v1" - apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/tools/clientcmd" - - // Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters). - // _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" - - crv1 "k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1" - exampleclient "k8s.io/apiextensions-apiserver/examples/client-go/client" - examplecontroller "k8s.io/apiextensions-apiserver/examples/client-go/controller" -) - -func main() { - masterURL := flag.String("master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.") - kubeconfig := flag.String("kubeconfig", "", "Path to a kube config. Only required if out-of-cluster.") - flag.Parse() - - // Create the client config. Use masterURL and kubeconfig if given, otherwise assume in-cluster. - config, err := clientcmd.BuildConfigFromFlags(*masterURL, *kubeconfig) - if err != nil { - panic(err) - } - - apiextensionsclientset, err := apiextensionsclient.NewForConfig(config) - if err != nil { - panic(err) - } - - // initialize custom resource using a CustomResourceDefinition if it does not exist - crd, err := exampleclient.CreateCustomResourceDefinition(apiextensionsclientset) - if err != nil && !apierrors.IsAlreadyExists(err) { - panic(err) - } - - if crd != nil { - defer apiextensionsclientset.ApiextensionsV1beta1().CustomResourceDefinitions().Delete(crd.Name, nil) - } - - // make a new config for our extension's API group, using the first config as a baseline - exampleClient, exampleScheme, err := exampleclient.NewClient(config) - if err != nil { - panic(err) - } - - // start a controller on instances of our custom resource - controller := examplecontroller.ExampleController{ - ExampleClient: exampleClient, - ExampleScheme: exampleScheme, - } - - ctx, cancelFunc := context.WithCancel(context.Background()) - defer cancelFunc() - go controller.Run(ctx) - - // Create an instance of our custom resource - example := &crv1.Example{ - ObjectMeta: metav1.ObjectMeta{ - Name: "example1", - }, - Spec: crv1.ExampleSpec{ - Foo: "hello", - Bar: true, - }, - Status: crv1.ExampleStatus{ - State: crv1.ExampleStateCreated, - Message: "Created, not processed yet", - }, - } - var result crv1.Example - err = exampleClient.Post(). - Resource(crv1.ExampleResourcePlural). - Namespace(apiv1.NamespaceDefault). - Body(example). - Do().Into(&result) - if err == nil { - fmt.Printf("CREATED: %#v\n", result) - } else if apierrors.IsAlreadyExists(err) { - fmt.Printf("ALREADY EXISTS: %#v\n", result) - } else { - panic(err) - } - - // Poll until Example object is handled by controller and gets status updated to "Processed" - err = exampleclient.WaitForExampleInstanceProcessed(exampleClient, "example1") - if err != nil { - panic(err) - } - fmt.Print("PROCESSED\n") - - // Fetch a list of our CRs - exampleList := crv1.ExampleList{} - err = exampleClient.Get().Resource(crv1.ExampleResourcePlural).Do().Into(&exampleList) - if err != nil { - panic(err) - } - fmt.Printf("LIST: %#v\n", exampleList) -} diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/BUILD b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/BUILD new file mode 100644 index 00000000000..b600f92122a --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/BUILD @@ -0,0 +1,25 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["register.go"], + importpath = "k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr", + visibility = ["//visibility:public"], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1:all-srcs", + ], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/register.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/register.go new file mode 100644 index 00000000000..6eb299ecae2 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/register.go @@ -0,0 +1,21 @@ +/* +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 cr + +const ( + GroupName = "cr.example.apiextensions.k8s.io" +) diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1/BUILD b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1/BUILD new file mode 100644 index 00000000000..0379da4b1a3 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1/BUILD @@ -0,0 +1,36 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "register.go", + "types.go", + "zz_generated.deepcopy.go", + ], + importpath = "k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1", + deps = [ + "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1/doc.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1/doc.go new file mode 100644 index 00000000000..73d79a45d57 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1/doc.go @@ -0,0 +1,21 @@ +/* +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 + +// Package v1 is the v1 version of the API. +// +groupName=cr.example.apiextensions.k8s.io +package v1 diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1/register.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1/register.go new file mode 100644 index 00000000000..bd3b2ba6493 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1/register.go @@ -0,0 +1,51 @@ +/* +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 v1 + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + cr "k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr" +) + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: cr.GroupName, Version: "v1"} + +// Kind takes an unqualified kind and returns back 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() +} + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +// Adds the list of known types to Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &Example{}, + &ExampleList{}, + ) + return nil +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1/types.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1/types.go new file mode 100644 index 00000000000..274b30f61ac --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1/types.go @@ -0,0 +1,63 @@ +/* +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 v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +genclient:noStatus +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Example is a specification for an Example resource +type Example struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + + Spec ExampleSpec `json:"spec"` + Status ExampleStatus `json:"status,omitempty"` +} + +// ExampleSpec is the spec for an Example resource +type ExampleSpec struct { + Foo string `json:"foo"` + Bar bool `json:"bar"` +} + +// ExampleStatus is the status for an Example resource +type ExampleStatus struct { + State ExampleState `json:"state,omitempty"` + Message string `json:"message,omitempty"` +} + +type ExampleState string + +const ( + ExampleStateCreated ExampleState = "Created" + ExampleStateProcessed ExampleState = "Processed" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ExampleList is a list of Example resources +type ExampleList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []Example `json:"items"` +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1/zz_generated.deepcopy.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..97a35e3f9df --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1/zz_generated.deepcopy.go @@ -0,0 +1,120 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +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 *Example) DeepCopyInto(out *Example) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Example. +func (in *Example) DeepCopy() *Example { + if in == nil { + return nil + } + out := new(Example) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Example) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExampleList) DeepCopyInto(out *ExampleList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Example, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExampleList. +func (in *ExampleList) DeepCopy() *ExampleList { + if in == nil { + return nil + } + out := new(ExampleList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ExampleList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExampleSpec) DeepCopyInto(out *ExampleSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExampleSpec. +func (in *ExampleSpec) DeepCopy() *ExampleSpec { + if in == nil { + return nil + } + out := new(ExampleSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExampleStatus) DeepCopyInto(out *ExampleStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExampleStatus. +func (in *ExampleStatus) DeepCopy() *ExampleStatus { + if in == nil { + return nil + } + out := new(ExampleStatus) + in.DeepCopyInto(out) + return out +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/BUILD b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/BUILD new file mode 100644 index 00000000000..0698e1f9add --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/BUILD @@ -0,0 +1,37 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "clientset.go", + "doc.go", + ], + importpath = "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned", + visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1:go_default_library", + "//vendor/k8s.io/client-go/discovery:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + "//vendor/k8s.io/client-go/util/flowcontrol:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/fake:all-srcs", + "//staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/scheme:all-srcs", + "//staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1:all-srcs", + ], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/clientset.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/clientset.go new file mode 100644 index 00000000000..45745dd2740 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/clientset.go @@ -0,0 +1,98 @@ +/* +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 versioned + +import ( + glog "github.com/golang/glog" + crv1 "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1" + discovery "k8s.io/client-go/discovery" + rest "k8s.io/client-go/rest" + flowcontrol "k8s.io/client-go/util/flowcontrol" +) + +type Interface interface { + Discovery() discovery.DiscoveryInterface + CrV1() crv1.CrV1Interface + // Deprecated: please explicitly pick a version if possible. + Cr() crv1.CrV1Interface +} + +// Clientset contains the clients for groups. Each group has exactly one +// version included in a Clientset. +type Clientset struct { + *discovery.DiscoveryClient + crV1 *crv1.CrV1Client +} + +// CrV1 retrieves the CrV1Client +func (c *Clientset) CrV1() crv1.CrV1Interface { + return c.crV1 +} + +// Deprecated: Cr retrieves the default version of CrClient. +// Please explicitly pick a version. +func (c *Clientset) Cr() crv1.CrV1Interface { + return c.crV1 +} + +// Discovery retrieves the DiscoveryClient +func (c *Clientset) Discovery() discovery.DiscoveryInterface { + if c == nil { + return nil + } + return c.DiscoveryClient +} + +// NewForConfig creates a new Clientset for the given config. +func NewForConfig(c *rest.Config) (*Clientset, error) { + configShallowCopy := *c + if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { + configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) + } + var cs Clientset + var err error + cs.crV1, err = crv1.NewForConfig(&configShallowCopy) + if err != nil { + return nil, err + } + + cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy) + if err != nil { + glog.Errorf("failed to create the DiscoveryClient: %v", err) + return nil, err + } + return &cs, nil +} + +// NewForConfigOrDie creates a new Clientset for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *Clientset { + var cs Clientset + cs.crV1 = crv1.NewForConfigOrDie(c) + + cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c) + return &cs +} + +// New creates a new Clientset for the given RESTClient. +func New(c rest.Interface) *Clientset { + var cs Clientset + cs.crV1 = crv1.New(c) + + cs.DiscoveryClient = discovery.NewDiscoveryClient(c) + return &cs +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/doc.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/doc.go new file mode 100644 index 00000000000..7d2f4d80d3a --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/doc.go @@ -0,0 +1,18 @@ +/* +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. +*/ + +// This package has the automatically generated clientset. +package versioned diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/fake/BUILD b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/fake/BUILD new file mode 100644 index 00000000000..3620b1569a2 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/fake/BUILD @@ -0,0 +1,40 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "clientset_generated.go", + "doc.go", + "register.go", + ], + importpath = "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/fake", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1:go_default_library", + "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned:go_default_library", + "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1:go_default_library", + "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/fake:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", + "//vendor/k8s.io/client-go/discovery:go_default_library", + "//vendor/k8s.io/client-go/discovery/fake:go_default_library", + "//vendor/k8s.io/client-go/testing:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) 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 new file mode 100644 index 00000000000..2a03c770e61 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/fake/clientset_generated.go @@ -0,0 +1,71 @@ +/* +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 fake + +import ( + clientset "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned" + crv1 "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1" + fakecrv1 "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/fake" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/discovery" + fakediscovery "k8s.io/client-go/discovery/fake" + "k8s.io/client-go/testing" +) + +// NewSimpleClientset returns a clientset that will respond with the provided objects. +// It's backed by a very simple object tracker that processes creates, updates and deletions as-is, +// without applying any validations and/or defaults. It shouldn't be considered a replacement +// for a real clientset and is mostly useful in simple unit tests. +func NewSimpleClientset(objects ...runtime.Object) *Clientset { + o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) + for _, obj := range objects { + if err := o.Add(obj); err != nil { + panic(err) + } + } + + fakePtr := testing.Fake{} + fakePtr.AddReactor("*", "*", testing.ObjectReaction(o)) + fakePtr.AddWatchReactor("*", testing.DefaultWatchReactor(watch.NewFake(), nil)) + + return &Clientset{fakePtr, &fakediscovery.FakeDiscovery{Fake: &fakePtr}} +} + +// Clientset implements clientset.Interface. Meant to be embedded into a +// struct to get a default implementation. This makes faking out just the method +// you want to test easier. +type Clientset struct { + testing.Fake + discovery *fakediscovery.FakeDiscovery +} + +func (c *Clientset) Discovery() discovery.DiscoveryInterface { + return c.discovery +} + +var _ clientset.Interface = &Clientset{} + +// CrV1 retrieves the CrV1Client +func (c *Clientset) CrV1() crv1.CrV1Interface { + return &fakecrv1.FakeCrV1{Fake: &c.Fake} +} + +// Cr retrieves the CrV1Client +func (c *Clientset) Cr() crv1.CrV1Interface { + return &fakecrv1.FakeCrV1{Fake: &c.Fake} +} diff --git a/federation/client/clientset_generated/federation_clientset/fake/doc.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/fake/doc.go similarity index 100% rename from federation/client/clientset_generated/federation_clientset/fake/doc.go rename to staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/fake/doc.go diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/fake/register.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/fake/register.go new file mode 100644 index 00000000000..f926bc58f2f --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/fake/register.go @@ -0,0 +1,53 @@ +/* +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 fake + +import ( + crv1 "k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + serializer "k8s.io/apimachinery/pkg/runtime/serializer" +) + +var scheme = runtime.NewScheme() +var codecs = serializer.NewCodecFactory(scheme) +var parameterCodec = runtime.NewParameterCodec(scheme) + +func init() { + v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) + AddToScheme(scheme) +} + +// AddToScheme adds all types of this clientset into the given scheme. This allows composition +// of clientsets, like in: +// +// import ( +// "k8s.io/client-go/kubernetes" +// clientsetscheme "k8s.io/client-go/kuberentes/scheme" +// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" +// ) +// +// kclientset, _ := kubernetes.NewForConfig(c) +// aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) +// +// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types +// correctly. +func AddToScheme(scheme *runtime.Scheme) { + crv1.AddToScheme(scheme) + +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/scheme/BUILD b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/scheme/BUILD new file mode 100644 index 00000000000..8b8fa05a1dc --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/scheme/BUILD @@ -0,0 +1,32 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "register.go", + ], + importpath = "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/scheme", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/federation/client/clientset_generated/federation_clientset/scheme/doc.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/scheme/doc.go similarity index 100% rename from federation/client/clientset_generated/federation_clientset/scheme/doc.go rename to staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/scheme/doc.go diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/scheme/register.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/scheme/register.go new file mode 100644 index 00000000000..e37edaa5da2 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/scheme/register.go @@ -0,0 +1,53 @@ +/* +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 scheme + +import ( + crv1 "k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + serializer "k8s.io/apimachinery/pkg/runtime/serializer" +) + +var Scheme = runtime.NewScheme() +var Codecs = serializer.NewCodecFactory(Scheme) +var ParameterCodec = runtime.NewParameterCodec(Scheme) + +func init() { + v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) + AddToScheme(Scheme) +} + +// AddToScheme adds all types of this clientset into the given scheme. This allows composition +// of clientsets, like in: +// +// import ( +// "k8s.io/client-go/kubernetes" +// clientsetscheme "k8s.io/client-go/kuberentes/scheme" +// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" +// ) +// +// kclientset, _ := kubernetes.NewForConfig(c) +// aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) +// +// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types +// correctly. +func AddToScheme(scheme *runtime.Scheme) { + crv1.AddToScheme(scheme) + +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/BUILD b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/BUILD new file mode 100644 index 00000000000..25b9f2fc448 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/BUILD @@ -0,0 +1,39 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "cr_client.go", + "doc.go", + "example.go", + "generated_expansion.go", + ], + importpath = "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1:go_default_library", + "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/scheme:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/fake:all-srcs", + ], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/cr_client.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/cr_client.go new file mode 100644 index 00000000000..8c662905c50 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/cr_client.go @@ -0,0 +1,88 @@ +/* +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 v1 + +import ( + v1 "k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1" + "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/scheme" + serializer "k8s.io/apimachinery/pkg/runtime/serializer" + rest "k8s.io/client-go/rest" +) + +type CrV1Interface interface { + RESTClient() rest.Interface + ExamplesGetter +} + +// CrV1Client is used to interact with features provided by the cr.client-go.k8s.io group. +type CrV1Client struct { + restClient rest.Interface +} + +func (c *CrV1Client) Examples(namespace string) ExampleInterface { + return newExamples(c, namespace) +} + +// NewForConfig creates a new CrV1Client for the given config. +func NewForConfig(c *rest.Config) (*CrV1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientFor(&config) + if err != nil { + return nil, err + } + return &CrV1Client{client}, nil +} + +// NewForConfigOrDie creates a new CrV1Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *CrV1Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new CrV1Client for the given RESTClient. +func New(c rest.Interface) *CrV1Client { + return &CrV1Client{c} +} + +func setConfigDefaults(config *rest.Config) error { + gv := v1.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs} + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + + return nil +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *CrV1Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/doc.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/doc.go similarity index 100% rename from federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/doc.go rename to staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/doc.go diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/example.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/example.go new file mode 100644 index 00000000000..66e0e1cfffe --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/example.go @@ -0,0 +1,155 @@ +/* +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 v1 + +import ( + v1 "k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1" + scheme "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/scheme" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// ExamplesGetter has a method to return a ExampleInterface. +// A group's client should implement this interface. +type ExamplesGetter interface { + Examples(namespace string) ExampleInterface +} + +// ExampleInterface has methods to work with Example resources. +type ExampleInterface interface { + Create(*v1.Example) (*v1.Example, error) + Update(*v1.Example) (*v1.Example, error) + Delete(name string, options *meta_v1.DeleteOptions) error + DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error + Get(name string, options meta_v1.GetOptions) (*v1.Example, error) + List(opts meta_v1.ListOptions) (*v1.ExampleList, error) + Watch(opts meta_v1.ListOptions) (watch.Interface, error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.Example, err error) + ExampleExpansion +} + +// examples implements ExampleInterface +type examples struct { + client rest.Interface + ns string +} + +// newExamples returns a Examples +func newExamples(c *CrV1Client, namespace string) *examples { + return &examples{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the example, and returns the corresponding example object, and an error if there is any. +func (c *examples) Get(name string, options meta_v1.GetOptions) (result *v1.Example, err error) { + result = &v1.Example{} + err = c.client.Get(). + Namespace(c.ns). + Resource("examples"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Examples that match those selectors. +func (c *examples) List(opts meta_v1.ListOptions) (result *v1.ExampleList, err error) { + result = &v1.ExampleList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("examples"). + VersionedParams(&opts, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested examples. +func (c *examples) Watch(opts meta_v1.ListOptions) (watch.Interface, error) { + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("examples"). + VersionedParams(&opts, scheme.ParameterCodec). + Watch() +} + +// Create takes the representation of a example and creates it. Returns the server's representation of the example, and an error, if there is any. +func (c *examples) Create(example *v1.Example) (result *v1.Example, err error) { + result = &v1.Example{} + err = c.client.Post(). + Namespace(c.ns). + Resource("examples"). + Body(example). + Do(). + Into(result) + return +} + +// Update takes the representation of a example and updates it. Returns the server's representation of the example, and an error, if there is any. +func (c *examples) Update(example *v1.Example) (result *v1.Example, err error) { + result = &v1.Example{} + err = c.client.Put(). + Namespace(c.ns). + Resource("examples"). + Name(example.Name). + Body(example). + Do(). + Into(result) + return +} + +// Delete takes name of the example and deletes it. Returns an error if one occurs. +func (c *examples) Delete(name string, options *meta_v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("examples"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *examples) DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("examples"). + VersionedParams(&listOptions, scheme.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Patch applies the patch and returns the patched example. +func (c *examples) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.Example, err error) { + result = &v1.Example{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("examples"). + SubResource(subresources...). + Name(name). + Body(data). + Do(). + Into(result) + return +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/fake/BUILD b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/fake/BUILD new file mode 100644 index 00000000000..d9c1a5e6c61 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/fake/BUILD @@ -0,0 +1,37 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "fake_cr_client.go", + "fake_example.go", + ], + importpath = "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/fake", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1:go_default_library", + "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + "//vendor/k8s.io/client-go/testing:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/fake/doc.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/fake/doc.go similarity index 100% rename from federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1/fake/doc.go rename to staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/fake/doc.go diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/fake/fake_cr_client.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/fake/fake_cr_client.go new file mode 100644 index 00000000000..b3f6247c4b8 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/fake/fake_cr_client.go @@ -0,0 +1,38 @@ +/* +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 fake + +import ( + v1 "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1" + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" +) + +type FakeCrV1 struct { + *testing.Fake +} + +func (c *FakeCrV1) Examples(namespace string) v1.ExampleInterface { + return &FakeExamples{c, namespace} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeCrV1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/fake/fake_example.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/fake/fake_example.go new file mode 100644 index 00000000000..7a8dd94b95e --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/fake/fake_example.go @@ -0,0 +1,126 @@ +/* +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 fake + +import ( + cr_v1 "k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeExamples implements ExampleInterface +type FakeExamples struct { + Fake *FakeCrV1 + ns string +} + +var examplesResource = schema.GroupVersionResource{Group: "cr.client-go.k8s.io", Version: "v1", Resource: "examples"} + +var examplesKind = schema.GroupVersionKind{Group: "cr.client-go.k8s.io", Version: "v1", Kind: "Example"} + +// Get takes name of the example, and returns the corresponding example object, and an error if there is any. +func (c *FakeExamples) Get(name string, options v1.GetOptions) (result *cr_v1.Example, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(examplesResource, c.ns, name), &cr_v1.Example{}) + + if obj == nil { + return nil, err + } + return obj.(*cr_v1.Example), err +} + +// List takes label and field selectors, and returns the list of Examples that match those selectors. +func (c *FakeExamples) List(opts v1.ListOptions) (result *cr_v1.ExampleList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(examplesResource, examplesKind, c.ns, opts), &cr_v1.ExampleList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &cr_v1.ExampleList{} + for _, item := range obj.(*cr_v1.ExampleList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested examples. +func (c *FakeExamples) Watch(opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(examplesResource, c.ns, opts)) + +} + +// Create takes the representation of a example and creates it. Returns the server's representation of the example, and an error, if there is any. +func (c *FakeExamples) Create(example *cr_v1.Example) (result *cr_v1.Example, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(examplesResource, c.ns, example), &cr_v1.Example{}) + + if obj == nil { + return nil, err + } + return obj.(*cr_v1.Example), err +} + +// Update takes the representation of a example and updates it. Returns the server's representation of the example, and an error, if there is any. +func (c *FakeExamples) Update(example *cr_v1.Example) (result *cr_v1.Example, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(examplesResource, c.ns, example), &cr_v1.Example{}) + + if obj == nil { + return nil, err + } + return obj.(*cr_v1.Example), err +} + +// Delete takes name of the example and deletes it. Returns an error if one occurs. +func (c *FakeExamples) Delete(name string, options *v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(examplesResource, c.ns, name), &cr_v1.Example{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeExamples) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(examplesResource, c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &cr_v1.ExampleList{}) + return err +} + +// Patch applies the patch and returns the patched example. +func (c *FakeExamples) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *cr_v1.Example, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(examplesResource, c.ns, name, data, subresources...), &cr_v1.Example{}) + + if obj == nil { + return nil, err + } + return obj.(*cr_v1.Example), err +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/generated_expansion.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/generated_expansion.go new file mode 100644 index 00000000000..70b162feb8c --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned/typed/cr/v1/generated_expansion.go @@ -0,0 +1,19 @@ +/* +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 v1 + +type ExampleExpansion interface{} diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/BUILD b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/BUILD new file mode 100644 index 00000000000..cd892cb0716 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/BUILD @@ -0,0 +1,38 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "factory.go", + "generic.go", + ], + importpath = "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1:go_default_library", + "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned:go_default_library", + "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/cr:go_default_library", + "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/internalinterfaces:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/client-go/tools/cache:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/cr:all-srcs", + "//staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/internalinterfaces:all-srcs", + ], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/cr/BUILD b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/cr/BUILD new file mode 100644 index 00000000000..5d32d09b755 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/cr/BUILD @@ -0,0 +1,29 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["interface.go"], + importpath = "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/cr", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/cr/v1:go_default_library", + "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/internalinterfaces:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/cr/v1:all-srcs", + ], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/cr/interface.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/cr/interface.go new file mode 100644 index 00000000000..9366da3bedf --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/cr/interface.go @@ -0,0 +1,44 @@ +/* +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. +*/ + +// This file was automatically generated by informer-gen + +package cr + +import ( + v1 "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/cr/v1" + internalinterfaces "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to each of this group's versions. +type Interface interface { + // V1 provides access to shared informers for resources in V1. + V1() v1.Interface +} + +type group struct { + internalinterfaces.SharedInformerFactory +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory) Interface { + return &group{f} +} + +// V1 returns a new v1.Interface. +func (g *group) V1() v1.Interface { + return v1.New(g.SharedInformerFactory) +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/cr/v1/BUILD b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/cr/v1/BUILD new file mode 100644 index 00000000000..8ac5ac14eb7 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/cr/v1/BUILD @@ -0,0 +1,35 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "example.go", + "interface.go", + ], + importpath = "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/cr/v1", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1:go_default_library", + "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned:go_default_library", + "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/internalinterfaces:go_default_library", + "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/listers/cr/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", + "//vendor/k8s.io/client-go/tools/cache:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) 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 new file mode 100644 index 00000000000..39918ff5ae6 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/cr/v1/example.go @@ -0,0 +1,73 @@ +/* +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. +*/ + +// This file was automatically generated by informer-gen + +package v1 + +import ( + cr_v1 "k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1" + versioned "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned" + internalinterfaces "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/internalinterfaces" + v1 "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/listers/cr/v1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" + time "time" +) + +// ExampleInformer provides access to a shared informer and lister for +// Examples. +type ExampleInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1.ExampleLister +} + +type exampleInformer struct { + factory internalinterfaces.SharedInformerFactory +} + +// NewExampleInformer 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 NewExampleInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) { + return client.CrV1().Examples(namespace).List(options) + }, + WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) { + return client.CrV1().Examples(namespace).Watch(options) + }, + }, + &cr_v1.Example{}, + resyncPeriod, + indexers, + ) +} + +func defaultExampleInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewExampleInformer(client, meta_v1.NamespaceAll, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +} + +func (f *exampleInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&cr_v1.Example{}, defaultExampleInformer) +} + +func (f *exampleInformer) Lister() v1.ExampleLister { + return v1.NewExampleLister(f.Informer().GetIndexer()) +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/cr/v1/interface.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/cr/v1/interface.go new file mode 100644 index 00000000000..0612ce3bdbe --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/cr/v1/interface.go @@ -0,0 +1,43 @@ +/* +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. +*/ + +// This file was automatically generated by informer-gen + +package v1 + +import ( + internalinterfaces "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // Examples returns a ExampleInformer. + Examples() ExampleInformer +} + +type version struct { + internalinterfaces.SharedInformerFactory +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory) Interface { + return &version{f} +} + +// Examples returns a ExampleInformer. +func (v *version) Examples() ExampleInformer { + return &exampleInformer{factory: v.SharedInformerFactory} +} 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 new file mode 100644 index 00000000000..f9ae8be1274 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/factory.go @@ -0,0 +1,118 @@ +/* +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. +*/ + +// This file was automatically generated by informer-gen + +package externalversions + +import ( + versioned "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned" + cr "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/cr" + internalinterfaces "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/internalinterfaces" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + cache "k8s.io/client-go/tools/cache" + reflect "reflect" + sync "sync" + time "time" +) + +type sharedInformerFactory struct { + client versioned.Interface + lock sync.Mutex + defaultResync time.Duration + + informers map[reflect.Type]cache.SharedIndexInformer + // startedInformers is used for tracking which informers have been started. + // This allows Start() to be called multiple times safely. + startedInformers map[reflect.Type]bool +} + +// NewSharedInformerFactory constructs a new instance of sharedInformerFactory +func NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory { + return &sharedInformerFactory{ + client: client, + defaultResync: defaultResync, + informers: make(map[reflect.Type]cache.SharedIndexInformer), + startedInformers: make(map[reflect.Type]bool), + } +} + +// Start initializes all requested informers. +func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { + f.lock.Lock() + defer f.lock.Unlock() + + for informerType, informer := range f.informers { + if !f.startedInformers[informerType] { + go informer.Run(stopCh) + f.startedInformers[informerType] = true + } + } +} + +// WaitForCacheSync waits for all started informers' cache were synced. +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 +} + +// InternalInformerFor returns the SharedIndexInformer for obj using an internal +// client. +func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer { + f.lock.Lock() + defer f.lock.Unlock() + + informerType := reflect.TypeOf(obj) + informer, exists := f.informers[informerType] + if exists { + return informer + } + informer = newFunc(f.client, f.defaultResync) + f.informers[informerType] = informer + + return informer +} + +// SharedInformerFactory provides shared informers for resources in all known +// API group versions. +type SharedInformerFactory interface { + internalinterfaces.SharedInformerFactory + ForResource(resource schema.GroupVersionResource) (GenericInformer, error) + WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool + + Cr() cr.Interface +} + +func (f *sharedInformerFactory) Cr() cr.Interface { + return cr.New(f) +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/generic.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/generic.go new file mode 100644 index 00000000000..12390b7a9ff --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/generic.go @@ -0,0 +1,61 @@ +/* +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. +*/ + +// This file was automatically generated by informer-gen + +package externalversions + +import ( + "fmt" + v1 "k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1" + schema "k8s.io/apimachinery/pkg/runtime/schema" + cache "k8s.io/client-go/tools/cache" +) + +// GenericInformer is type of SharedIndexInformer which will locate and delegate to other +// sharedInformers based on type +type GenericInformer interface { + Informer() cache.SharedIndexInformer + Lister() cache.GenericLister +} + +type genericInformer struct { + informer cache.SharedIndexInformer + resource schema.GroupResource +} + +// Informer returns the SharedIndexInformer. +func (f *genericInformer) Informer() cache.SharedIndexInformer { + return f.informer +} + +// Lister returns the GenericLister. +func (f *genericInformer) Lister() cache.GenericLister { + return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource) +} + +// ForResource gives generic access to a shared informer of the matching type +// TODO extend this to unknown resources with a client pool +func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { + switch resource { + // Group=Cr, Version=V1 + case v1.SchemeGroupVersion.WithResource("examples"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Cr().V1().Examples().Informer()}, nil + + } + + return nil, fmt.Errorf("no informer found for %v", resource) +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/internalinterfaces/BUILD b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/internalinterfaces/BUILD new file mode 100644 index 00000000000..20b8782997a --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/internalinterfaces/BUILD @@ -0,0 +1,27 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["factory_interfaces.go"], + importpath = "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/internalinterfaces", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/client-go/tools/cache:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) 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 new file mode 100644 index 00000000000..d8f00586bf6 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go @@ -0,0 +1,34 @@ +/* +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. +*/ + +// This file was automatically generated by informer-gen + +package internalinterfaces + +import ( + versioned "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/clientset/versioned" + runtime "k8s.io/apimachinery/pkg/runtime" + cache "k8s.io/client-go/tools/cache" + time "time" +) + +type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer + +// SharedInformerFactory a small interface to allow for adding an informer without an import cycle +type SharedInformerFactory interface { + Start(stopCh <-chan struct{}) + InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/listers/cr/v1/BUILD b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/listers/cr/v1/BUILD new file mode 100644 index 00000000000..69e2f3c9260 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/listers/cr/v1/BUILD @@ -0,0 +1,31 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "example.go", + "expansion_generated.go", + ], + importpath = "k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/listers/cr/v1", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/client-go/tools/cache:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/listers/cr/v1/example.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/listers/cr/v1/example.go new file mode 100644 index 00000000000..554c9f2db37 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/listers/cr/v1/example.go @@ -0,0 +1,94 @@ +/* +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. +*/ + +// This file was automatically generated by lister-gen + +package v1 + +import ( + v1 "k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis/cr/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// ExampleLister helps list Examples. +type ExampleLister interface { + // List lists all Examples in the indexer. + List(selector labels.Selector) (ret []*v1.Example, err error) + // Examples returns an object that can list and get Examples. + Examples(namespace string) ExampleNamespaceLister + ExampleListerExpansion +} + +// exampleLister implements the ExampleLister interface. +type exampleLister struct { + indexer cache.Indexer +} + +// NewExampleLister returns a new ExampleLister. +func NewExampleLister(indexer cache.Indexer) ExampleLister { + return &exampleLister{indexer: indexer} +} + +// List lists all Examples in the indexer. +func (s *exampleLister) List(selector labels.Selector) (ret []*v1.Example, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1.Example)) + }) + return ret, err +} + +// Examples returns an object that can list and get Examples. +func (s *exampleLister) Examples(namespace string) ExampleNamespaceLister { + return exampleNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// ExampleNamespaceLister helps list and get Examples. +type ExampleNamespaceLister interface { + // List lists all Examples in the indexer for a given namespace. + List(selector labels.Selector) (ret []*v1.Example, err error) + // Get retrieves the Example from the indexer for a given namespace and name. + Get(name string) (*v1.Example, error) + ExampleNamespaceListerExpansion +} + +// exampleNamespaceLister implements the ExampleNamespaceLister +// interface. +type exampleNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all Examples in the indexer for a given namespace. +func (s exampleNamespaceLister) List(selector labels.Selector) (ret []*v1.Example, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1.Example)) + }) + return ret, err +} + +// Get retrieves the Example from the indexer for a given namespace and name. +func (s exampleNamespaceLister) Get(name string) (*v1.Example, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1.Resource("example"), name) + } + return obj.(*v1.Example), nil +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/listers/cr/v1/expansion_generated.go b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/listers/cr/v1/expansion_generated.go new file mode 100644 index 00000000000..7397d6f6c57 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/examples/client-go/pkg/client/listers/cr/v1/expansion_generated.go @@ -0,0 +1,27 @@ +/* +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. +*/ + +// This file was automatically generated by lister-gen + +package v1 + +// ExampleListerExpansion allows custom methods to be added to +// ExampleLister. +type ExampleListerExpansion interface{} + +// ExampleNamespaceListerExpansion allows custom methods to be added to +// ExampleNamespaceLister. +type ExampleNamespaceListerExpansion interface{} diff --git a/staging/src/k8s.io/apiextensions-apiserver/hack/update-codegen.sh b/staging/src/k8s.io/apiextensions-apiserver/hack/update-codegen.sh index efc52ecd886..8b1e582bb46 100755 --- a/staging/src/k8s.io/apiextensions-apiserver/hack/update-codegen.sh +++ b/staging/src/k8s.io/apiextensions-apiserver/hack/update-codegen.sh @@ -50,7 +50,7 @@ apiextensions/ apiextensions/v1beta1 ) INPUT="--input ${INPUT_APIS[@]}" -CLIENTSET_PATH="--clientset-path k8s.io/apiextensions-apiserver/pkg/client/clientset" +CLIENTSET_PATH="--output-package k8s.io/apiextensions-apiserver/pkg/client/clientset" ${CLIENTGEN} ${INPUT_BASE} ${INPUT} ${CLIENTSET_PATH} --output-base ${SCRIPT_BASE} ${CLIENTGEN} --clientset-name="clientset" ${INPUT_BASE} --input apiextensions/v1beta1 ${CLIENTSET_PATH} --output-base ${SCRIPT_BASE} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/BUILD b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/BUILD index 3dc0a063a99..5f6161f0cbb 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/BUILD @@ -20,7 +20,6 @@ go_library( importpath = "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions", deps = [ "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], @@ -29,8 +28,8 @@ go_library( go_test( name = "go_default_test", srcs = ["helpers_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions", - library = ":go_default_library", deps = ["//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library"], ) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/doc.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/doc.go index d3b8f8db4a3..0517ec6a844 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/doc.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // Package apiextensions is the internal version of the API. // +groupName=apiextensions.k8s.io diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer/fuzzer.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer/fuzzer.go index 18a33291368..a7cfb0ae7cb 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer/fuzzer.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer/fuzzer.go @@ -58,7 +58,7 @@ func Funcs(codecs runtimeserializer.CodecFactory) []interface{} { case reflect.Interface, reflect.Map, reflect.Slice, reflect.Ptr: isValue = false } - if isValue || c.Intn(5) == 0 { + if isValue || c.Intn(10) == 0 { c.Fuzz(vobj.Field(i).Addr().Interface()) } } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/helpers.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/helpers.go index 299ddc22b45..8dc7f72d660 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/helpers.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/helpers.go @@ -16,11 +16,18 @@ limitations under the License. package apiextensions +import ( + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + // SetCRDCondition sets the status condition. It either overwrites the existing one or // creates a new one func SetCRDCondition(crd *CustomResourceDefinition, newCondition CustomResourceDefinitionCondition) { existingCondition := FindCRDCondition(crd, newCondition.Type) if existingCondition == nil { + newCondition.LastTransitionTime = metav1.NewTime(time.Now()) crd.Status.Conditions = append(crd.Status.Conditions, newCondition) return } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install/BUILD b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install/BUILD index 3a0a056a283..1ae69bfa797 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["roundtrip_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/testing/roundtrip:go_default_library", diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/register.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/register.go index df3bc363f3e..273f7f123b6 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/register.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/register.go @@ -41,7 +41,7 @@ var ( AddToScheme = SchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &CustomResourceDefinition{}, diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/BUILD b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/BUILD index 32f2ab8fed5..46ee524ea10 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/BUILD @@ -60,8 +60,8 @@ go_test( "conversion_test.go", "marshal_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/doc.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/doc.go index 67af169a132..50ab2b54c69 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/doc.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:conversion-gen=k8s.io/apiextensions-apiserver/pkg/apis/apiextensions // +k8s:defaulter-gen=TypeMeta diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.pb.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.pb.go index e9b16e6db84..488f5356022 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.pb.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.proto b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.proto index 57a69e60c1b..c4d2b2dde31 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.proto +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -105,7 +105,6 @@ message CustomResourceDefinitionSpec { optional string scope = 4; // Validation describes the validation methods for CustomResources - // This field is alpha-level and should only be sent to servers that enable the CustomResourceValidation feature. // +optional optional CustomResourceValidation validation = 5; } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/register.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/register.go index 8ced548fa96..77f849975f8 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/register.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/register.go @@ -43,7 +43,7 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &CustomResourceDefinition{}, diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/types.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/types.go index 937092d2491..9ac37efe0fb 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/types.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/types.go @@ -29,7 +29,6 @@ type CustomResourceDefinitionSpec struct { // Scope indicates whether this resource is cluster or namespace scoped. Default is namespaced Scope ResourceScope `json:"scope" protobuf:"bytes,4,opt,name=scope,casttype=ResourceScope"` // Validation describes the validation methods for CustomResources - // This field is alpha-level and should only be sent to servers that enable the CustomResourceValidation feature. // +optional Validation *CustomResourceValidation `json:"validation,omitempty" protobuf:"bytes,5,opt,name=validation"` } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/zz_generated.conversion.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/zz_generated.conversion.go index 26fcb6cc04c..f7d46f6e502 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/zz_generated.conversion.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,10 +21,11 @@ limitations under the License. package v1beta1 import ( + unsafe "unsafe" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - unsafe "unsafe" ) func init() { diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/zz_generated.deepcopy.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/zz_generated.deepcopy.go index 6f97eb6db63..363e970dae6 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,76 +21,9 @@ limitations under the License. package v1beta1 import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CustomResourceDefinition).DeepCopyInto(out.(*CustomResourceDefinition)) - return nil - }, InType: reflect.TypeOf(&CustomResourceDefinition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CustomResourceDefinitionCondition).DeepCopyInto(out.(*CustomResourceDefinitionCondition)) - return nil - }, InType: reflect.TypeOf(&CustomResourceDefinitionCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CustomResourceDefinitionList).DeepCopyInto(out.(*CustomResourceDefinitionList)) - return nil - }, InType: reflect.TypeOf(&CustomResourceDefinitionList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CustomResourceDefinitionNames).DeepCopyInto(out.(*CustomResourceDefinitionNames)) - return nil - }, InType: reflect.TypeOf(&CustomResourceDefinitionNames{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CustomResourceDefinitionSpec).DeepCopyInto(out.(*CustomResourceDefinitionSpec)) - return nil - }, InType: reflect.TypeOf(&CustomResourceDefinitionSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CustomResourceDefinitionStatus).DeepCopyInto(out.(*CustomResourceDefinitionStatus)) - return nil - }, InType: reflect.TypeOf(&CustomResourceDefinitionStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CustomResourceValidation).DeepCopyInto(out.(*CustomResourceValidation)) - return nil - }, InType: reflect.TypeOf(&CustomResourceValidation{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExternalDocumentation).DeepCopyInto(out.(*ExternalDocumentation)) - return nil - }, InType: reflect.TypeOf(&ExternalDocumentation{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*JSON).DeepCopyInto(out.(*JSON)) - return nil - }, InType: reflect.TypeOf(&JSON{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*JSONSchemaProps).DeepCopyInto(out.(*JSONSchemaProps)) - return nil - }, InType: reflect.TypeOf(&JSONSchemaProps{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*JSONSchemaPropsOrArray).DeepCopyInto(out.(*JSONSchemaPropsOrArray)) - return nil - }, InType: reflect.TypeOf(&JSONSchemaPropsOrArray{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*JSONSchemaPropsOrBool).DeepCopyInto(out.(*JSONSchemaPropsOrBool)) - return nil - }, InType: reflect.TypeOf(&JSONSchemaPropsOrBool{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*JSONSchemaPropsOrStringArray).DeepCopyInto(out.(*JSONSchemaPropsOrStringArray)) - return nil - }, InType: reflect.TypeOf(&JSONSchemaPropsOrStringArray{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CustomResourceDefinition) DeepCopyInto(out *CustomResourceDefinition) { *out = *in diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/zz_generated.defaults.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/zz_generated.defaults.go index 55798082edc..5c30d5b0a5f 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/zz_generated.defaults.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/BUILD b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/BUILD index 25b1bbd5919..15884eb628e 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/BUILD @@ -24,8 +24,8 @@ go_library( go_test( name = "go_default_test", srcs = ["validation_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/zz_generated.deepcopy.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/zz_generated.deepcopy.go index ae6f2d79f89..f94bbae2c92 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,72 +21,9 @@ limitations under the License. package apiextensions import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CustomResourceDefinition).DeepCopyInto(out.(*CustomResourceDefinition)) - return nil - }, InType: reflect.TypeOf(&CustomResourceDefinition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CustomResourceDefinitionCondition).DeepCopyInto(out.(*CustomResourceDefinitionCondition)) - return nil - }, InType: reflect.TypeOf(&CustomResourceDefinitionCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CustomResourceDefinitionList).DeepCopyInto(out.(*CustomResourceDefinitionList)) - return nil - }, InType: reflect.TypeOf(&CustomResourceDefinitionList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CustomResourceDefinitionNames).DeepCopyInto(out.(*CustomResourceDefinitionNames)) - return nil - }, InType: reflect.TypeOf(&CustomResourceDefinitionNames{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CustomResourceDefinitionSpec).DeepCopyInto(out.(*CustomResourceDefinitionSpec)) - return nil - }, InType: reflect.TypeOf(&CustomResourceDefinitionSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CustomResourceDefinitionStatus).DeepCopyInto(out.(*CustomResourceDefinitionStatus)) - return nil - }, InType: reflect.TypeOf(&CustomResourceDefinitionStatus{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CustomResourceValidation).DeepCopyInto(out.(*CustomResourceValidation)) - return nil - }, InType: reflect.TypeOf(&CustomResourceValidation{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExternalDocumentation).DeepCopyInto(out.(*ExternalDocumentation)) - return nil - }, InType: reflect.TypeOf(&ExternalDocumentation{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*JSONSchemaProps).DeepCopyInto(out.(*JSONSchemaProps)) - return nil - }, InType: reflect.TypeOf(&JSONSchemaProps{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*JSONSchemaPropsOrArray).DeepCopyInto(out.(*JSONSchemaPropsOrArray)) - return nil - }, InType: reflect.TypeOf(&JSONSchemaPropsOrArray{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*JSONSchemaPropsOrBool).DeepCopyInto(out.(*JSONSchemaPropsOrBool)) - return nil - }, InType: reflect.TypeOf(&JSONSchemaPropsOrBool{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*JSONSchemaPropsOrStringArray).DeepCopyInto(out.(*JSONSchemaPropsOrStringArray)) - return nil - }, InType: reflect.TypeOf(&JSONSchemaPropsOrStringArray{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CustomResourceDefinition) DeepCopyInto(out *CustomResourceDefinition) { *out = *in diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/BUILD b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/BUILD index 7b5d2d28337..a3842388097 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/BUILD @@ -85,7 +85,7 @@ filegroup( go_test( name = "go_default_test", srcs = ["customresource_handler_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apiextensions-apiserver/pkg/apiserver", - library = ":go_default_library", deps = ["//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library"], ) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go index 4470684447f..773a0657726 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go @@ -309,7 +309,6 @@ func (r *crdHandler) getServingInfoFor(crd *apiextensions.CustomResourceDefiniti &metav1.GetOptions{}, &metav1.DeleteOptions{}, ) - parameterScheme.AddGeneratedDeepCopyFuncs(metav1.GetGeneratedDeepCopyFuncs()...) parameterCodec := runtime.NewParameterCodec(parameterScheme) kind := schema.GroupVersionKind{Group: crd.Spec.Group, Version: crd.Spec.Version, Kind: crd.Status.AcceptedNames.Kind} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/BUILD b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/BUILD index a85afaef513..0fff89c68a6 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/BUILD @@ -33,8 +33,8 @@ filegroup( go_test( name = "go_default_test", srcs = ["validation_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apiextensions-apiserver/pkg/apiserver/validation", - library = ":go_default_library", deps = [ "//vendor/github.com/go-openapi/spec:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library", diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/clientset.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/clientset.go index bae98c78bf3..441572a4ffb 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/clientset.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/clientset.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/doc.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/doc.go index 7f670fed477..05edbcd1c2a 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/doc.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. 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 473f88f47da..3d67b5e6ddd 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 @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake/doc.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake/doc.go index 3fd8e1e2cdc..8a3101e3981 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake/doc.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake/register.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake/register.go index 857e10e624c..1b0ef276dec 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake/register.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake/register.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme/doc.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme/doc.go index 3ec2200d099..3d3ab5f4edf 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme/doc.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme/register.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme/register.go index 95f4bb41ab0..af2951a76db 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme/register.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme/register.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/apiextensions_client.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/apiextensions_client.go index 6a15dadd7b9..90d280f892b 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/apiextensions_client.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/apiextensions_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/customresourcedefinition.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/customresourcedefinition.go index 73238715b48..e1477df5f47 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/customresourcedefinition.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/customresourcedefinition.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/doc.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/doc.go index 1b50aa19970..35b3db3f354 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/doc.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/fake/doc.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/fake/doc.go index c58fac35e4b..63e2c8a0821 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/fake/doc.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/fake/fake_apiextensions_client.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/fake/fake_apiextensions_client.go index 252845f99ff..c88a38f548f 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/fake/fake_apiextensions_client.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/fake/fake_apiextensions_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/fake/fake_customresourcedefinition.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/fake/fake_customresourcedefinition.go index 7a2edf9ab10..6ee92631ff8 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/fake/fake_customresourcedefinition.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/fake/fake_customresourcedefinition.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/generated_expansion.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/generated_expansion.go index 2f721078b07..35554a068d2 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/generated_expansion.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/generated_expansion.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/clientset.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/clientset.go index 72803e76923..076c61b1c3c 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/clientset.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/clientset.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/doc.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/doc.go index b667dd5157a..4ede718dee1 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/doc.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/fake/clientset_generated.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/fake/clientset_generated.go index fdc0beee577..3bf97b3dac8 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/fake/clientset_generated.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/fake/clientset_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/fake/doc.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/fake/doc.go index 3fd8e1e2cdc..8a3101e3981 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/fake/doc.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/fake/register.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/fake/register.go index 1ebf1f9d071..3c89961f165 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/fake/register.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/fake/register.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/scheme/doc.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/scheme/doc.go index 3ec2200d099..3d3ab5f4edf 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/scheme/doc.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/scheme/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/scheme/register.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/scheme/register.go index 92e65a67dc8..388104e0048 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/scheme/register.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/scheme/register.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -17,6 +17,8 @@ limitations under the License. package scheme import ( + os "os" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install" announced "k8s.io/apimachinery/pkg/apimachinery/announced" registered "k8s.io/apimachinery/pkg/apimachinery/registered" @@ -24,7 +26,6 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" serializer "k8s.io/apimachinery/pkg/runtime/serializer" - os "os" ) var Scheme = runtime.NewScheme() diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/apiextensions_client.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/apiextensions_client.go index 28890867903..fedecdaa536 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/apiextensions_client.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/apiextensions_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/customresourcedefinition.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/customresourcedefinition.go index 7758b635f74..b4f14bc16f0 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/customresourcedefinition.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/customresourcedefinition.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/doc.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/doc.go index 3adf06d8934..8615019757d 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/doc.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/fake/doc.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/fake/doc.go index c58fac35e4b..63e2c8a0821 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/fake/doc.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/fake/fake_apiextensions_client.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/fake/fake_apiextensions_client.go index e90e8c6b45a..3c9d1e02ec0 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/fake/fake_apiextensions_client.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/fake/fake_apiextensions_client.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/fake/fake_customresourcedefinition.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/fake/fake_customresourcedefinition.go index 6cf8f7e7d3e..790b172e6f5 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/fake/fake_customresourcedefinition.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/fake/fake_customresourcedefinition.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/generated_expansion.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/generated_expansion.go index b1721b0d04f..a1df3b36f56 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/generated_expansion.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion/generated_expansion.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/BUILD b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/BUILD index b0625ed6164..57f38ce2590 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/BUILD @@ -17,6 +17,7 @@ go_library( "//vendor/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/internalinterfaces:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/interface.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/interface.go index de7f6e34e65..0a7e72223df 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/interface.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -30,15 +30,17 @@ type Interface interface { } type group struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &group{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // V1beta1 returns a new v1beta1.Interface. func (g *group) V1beta1() v1beta1.Interface { - return v1beta1.New(g.SharedInformerFactory) + return v1beta1.New(g.factory, g.namespace, g.tweakListOptions) } 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 e28ec5db662..a0bffb88f4b 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 @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,6 +19,8 @@ limitations under the License. package v1beta1 import ( + time "time" + apiextensions_v1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" clientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" internalinterfaces "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/internalinterfaces" @@ -27,7 +29,6 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" cache "k8s.io/client-go/tools/cache" - time "time" ) // CustomResourceDefinitionInformer provides access to a shared informer and lister for @@ -38,19 +39,33 @@ type CustomResourceDefinitionInformer interface { } type customResourceDefinitionInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc } // NewCustomResourceDefinitionInformer 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 NewCustomResourceDefinitionInformer(client clientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredCustomResourceDefinitionInformer(client, resyncPeriod, indexers, nil) +} + +// 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( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.ApiextensionsV1beta1().CustomResourceDefinitions().List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.ApiextensionsV1beta1().CustomResourceDefinitions().Watch(options) }, }, @@ -60,12 +75,12 @@ func NewCustomResourceDefinitionInformer(client clientset.Interface, resyncPerio ) } -func defaultCustomResourceDefinitionInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewCustomResourceDefinitionInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *customResourceDefinitionInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredCustomResourceDefinitionInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *customResourceDefinitionInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&apiextensions_v1beta1.CustomResourceDefinition{}, defaultCustomResourceDefinitionInformer) + return f.factory.InformerFor(&apiextensions_v1beta1.CustomResourceDefinition{}, f.defaultInformer) } func (f *customResourceDefinitionInformer) Lister() v1beta1.CustomResourceDefinitionLister { diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1beta1/interface.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1beta1/interface.go index cada14028b4..667ce98e4f2 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1beta1/interface.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1beta1/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -29,15 +29,17 @@ type Interface interface { } type version struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &version{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // CustomResourceDefinitions returns a CustomResourceDefinitionInformer. func (v *version) CustomResourceDefinitions() CustomResourceDefinitionInformer { - return &customResourceDefinitionInformer{factory: v.SharedInformerFactory} + return &customResourceDefinitionInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } 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 0f94a664d92..e39d41c13f3 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 @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,21 +19,25 @@ limitations under the License. package externalversions import ( - clientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" - apiextensions "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions" - internalinterfaces "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/internalinterfaces" - runtime "k8s.io/apimachinery/pkg/runtime" - schema "k8s.io/apimachinery/pkg/runtime/schema" - cache "k8s.io/client-go/tools/cache" reflect "reflect" sync "sync" time "time" + + clientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + apiextensions "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions" + internalinterfaces "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/internalinterfaces" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + cache "k8s.io/client-go/tools/cache" ) type sharedInformerFactory struct { - client clientset.Interface - lock sync.Mutex - defaultResync time.Duration + client clientset.Interface + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc + lock sync.Mutex + defaultResync time.Duration informers map[reflect.Type]cache.SharedIndexInformer // startedInformers is used for tracking which informers have been started. @@ -43,8 +47,17 @@ type sharedInformerFactory struct { // NewSharedInformerFactory constructs a new instance of sharedInformerFactory func NewSharedInformerFactory(client clientset.Interface, defaultResync time.Duration) SharedInformerFactory { + return NewFilteredSharedInformerFactory(client, defaultResync, v1.NamespaceAll, nil) +} + +// NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory. +// Listers obtained via this SharedInformerFactory will be subject to the same filters +// as specified here. +func NewFilteredSharedInformerFactory(client clientset.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory { return &sharedInformerFactory{ client: client, + namespace: namespace, + tweakListOptions: tweakListOptions, defaultResync: defaultResync, informers: make(map[reflect.Type]cache.SharedIndexInformer), startedInformers: make(map[reflect.Type]bool), @@ -114,5 +127,5 @@ type SharedInformerFactory interface { } func (f *sharedInformerFactory) Apiextensions() apiextensions.Interface { - return apiextensions.New(f) + return apiextensions.New(f, f.namespace, f.tweakListOptions) } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/generic.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/generic.go index 06abdaa9de0..4485a199b29 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/generic.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/generic.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -20,6 +20,7 @@ package externalversions import ( "fmt" + v1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" schema "k8s.io/apimachinery/pkg/runtime/schema" cache "k8s.io/client-go/tools/cache" @@ -51,7 +52,7 @@ func (f *genericInformer) Lister() cache.GenericLister { // TODO extend this to unknown resources with a client pool func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { switch resource { - // Group=Apiextensions, Version=V1beta1 + // Group=apiextensions.k8s.io, Version=v1beta1 case v1beta1.SchemeGroupVersion.WithResource("customresourcedefinitions"): return &genericInformer{resource: resource.GroupResource(), informer: f.Apiextensions().V1beta1().CustomResourceDefinitions().Informer()}, nil diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/internalinterfaces/BUILD b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/internalinterfaces/BUILD index 4b56af2b315..56bb1f8d800 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/internalinterfaces/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/internalinterfaces/BUILD @@ -11,6 +11,7 @@ go_library( importpath = "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/internalinterfaces", deps = [ "//vendor/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", ], 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 6f7b0a81069..14f84b3063d 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 @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,10 +19,12 @@ limitations under the License. package internalinterfaces import ( + time "time" + clientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" cache "k8s.io/client-go/tools/cache" - time "time" ) type NewInformerFunc func(clientset.Interface, time.Duration) cache.SharedIndexInformer @@ -32,3 +34,5 @@ type SharedInformerFactory interface { Start(stopCh <-chan struct{}) InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer } + +type TweakListOptionsFunc func(*v1.ListOptions) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/BUILD b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/BUILD index ed5eed5a0e5..ec24afd538f 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/BUILD @@ -17,6 +17,7 @@ go_library( "//vendor/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/internalinterfaces:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/interface.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/interface.go index 8137e94b71e..923953a4809 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/interface.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -30,15 +30,17 @@ type Interface interface { } type group struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &group{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // InternalVersion returns a new internalversion.Interface. func (g *group) InternalVersion() internalversion.Interface { - return internalversion.New(g.SharedInformerFactory) + return internalversion.New(g.factory, g.namespace, g.tweakListOptions) } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion/customresourcedefinition.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion/customresourcedefinition.go index 4c14fd1119f..f814ba73d2e 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion/customresourcedefinition.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion/customresourcedefinition.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,6 +19,8 @@ limitations under the License. package internalversion import ( + time "time" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" internalclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset" internalinterfaces "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/internalinterfaces" @@ -27,7 +29,6 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" cache "k8s.io/client-go/tools/cache" - time "time" ) // CustomResourceDefinitionInformer provides access to a shared informer and lister for @@ -38,19 +39,33 @@ type CustomResourceDefinitionInformer interface { } type customResourceDefinitionInformer struct { - factory internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc } // NewCustomResourceDefinitionInformer 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 NewCustomResourceDefinitionInformer(client internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredCustomResourceDefinitionInformer(client, resyncPeriod, indexers, nil) +} + +// 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 internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Apiextensions().CustomResourceDefinitions().List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } return client.Apiextensions().CustomResourceDefinitions().Watch(options) }, }, @@ -60,12 +75,12 @@ func NewCustomResourceDefinitionInformer(client internalclientset.Interface, res ) } -func defaultCustomResourceDefinitionInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewCustomResourceDefinitionInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +func (f *customResourceDefinitionInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredCustomResourceDefinitionInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } func (f *customResourceDefinitionInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&apiextensions.CustomResourceDefinition{}, defaultCustomResourceDefinitionInformer) + return f.factory.InformerFor(&apiextensions.CustomResourceDefinition{}, f.defaultInformer) } func (f *customResourceDefinitionInformer) Lister() internalversion.CustomResourceDefinitionLister { diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion/interface.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion/interface.go index e9ba1c90a2e..d80ab3abbe8 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion/interface.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -29,15 +29,17 @@ type Interface interface { } type version struct { - internalinterfaces.SharedInformerFactory + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc } // New returns a new Interface. -func New(f internalinterfaces.SharedInformerFactory) Interface { - return &version{f} +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } // CustomResourceDefinitions returns a CustomResourceDefinitionInformer. func (v *version) CustomResourceDefinitions() CustomResourceDefinitionInformer { - return &customResourceDefinitionInformer{factory: v.SharedInformerFactory} + return &customResourceDefinitionInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/factory.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/factory.go index fc5fda9fc48..dffd521ac66 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/factory.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/factory.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,21 +19,25 @@ limitations under the License. package internalversion import ( - internalclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset" - apiextensions "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions" - internalinterfaces "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/internalinterfaces" - runtime "k8s.io/apimachinery/pkg/runtime" - schema "k8s.io/apimachinery/pkg/runtime/schema" - cache "k8s.io/client-go/tools/cache" reflect "reflect" sync "sync" time "time" + + internalclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset" + apiextensions "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions" + internalinterfaces "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/internalinterfaces" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + cache "k8s.io/client-go/tools/cache" ) type sharedInformerFactory struct { - client internalclientset.Interface - lock sync.Mutex - defaultResync time.Duration + client internalclientset.Interface + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc + lock sync.Mutex + defaultResync time.Duration informers map[reflect.Type]cache.SharedIndexInformer // startedInformers is used for tracking which informers have been started. @@ -43,8 +47,17 @@ type sharedInformerFactory struct { // NewSharedInformerFactory constructs a new instance of sharedInformerFactory func NewSharedInformerFactory(client internalclientset.Interface, defaultResync time.Duration) SharedInformerFactory { + return NewFilteredSharedInformerFactory(client, defaultResync, v1.NamespaceAll, nil) +} + +// NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory. +// Listers obtained via this SharedInformerFactory will be subject to the same filters +// as specified here. +func NewFilteredSharedInformerFactory(client internalclientset.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory { return &sharedInformerFactory{ client: client, + namespace: namespace, + tweakListOptions: tweakListOptions, defaultResync: defaultResync, informers: make(map[reflect.Type]cache.SharedIndexInformer), startedInformers: make(map[reflect.Type]bool), @@ -114,5 +127,5 @@ type SharedInformerFactory interface { } func (f *sharedInformerFactory) Apiextensions() apiextensions.Interface { - return apiextensions.New(f) + return apiextensions.New(f, f.namespace, f.tweakListOptions) } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/generic.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/generic.go index a338c57c539..78d66fae20f 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/generic.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/generic.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -20,6 +20,7 @@ package internalversion import ( "fmt" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" schema "k8s.io/apimachinery/pkg/runtime/schema" cache "k8s.io/client-go/tools/cache" @@ -51,7 +52,7 @@ func (f *genericInformer) Lister() cache.GenericLister { // TODO extend this to unknown resources with a client pool func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { switch resource { - // Group=Apiextensions, Version=InternalVersion + // Group=apiextensions.k8s.io, Version=internalVersion case apiextensions.SchemeGroupVersion.WithResource("customresourcedefinitions"): return &genericInformer{resource: resource.GroupResource(), informer: f.Apiextensions().InternalVersion().CustomResourceDefinitions().Informer()}, nil diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/internalinterfaces/BUILD b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/internalinterfaces/BUILD index 9235b4b64d3..398d1f2f8e1 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/internalinterfaces/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/internalinterfaces/BUILD @@ -11,6 +11,7 @@ go_library( importpath = "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/internalinterfaces", deps = [ "//vendor/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", ], diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/internalinterfaces/factory_interfaces.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/internalinterfaces/factory_interfaces.go index 395b69ff897..71a1acfc527 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/internalinterfaces/factory_interfaces.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/internalinterfaces/factory_interfaces.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -19,10 +19,12 @@ limitations under the License. package internalinterfaces import ( + time "time" + internalclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" cache "k8s.io/client-go/tools/cache" - time "time" ) type NewInformerFunc func(internalclientset.Interface, time.Duration) cache.SharedIndexInformer @@ -32,3 +34,5 @@ type SharedInformerFactory interface { Start(stopCh <-chan struct{}) InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer } + +type TweakListOptionsFunc func(*v1.ListOptions) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion/BUILD b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion/BUILD index 023b4d89486..54732b7f9c8 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion/BUILD @@ -15,7 +15,6 @@ go_library( deps = [ "//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", ], diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion/customresourcedefinition.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion/customresourcedefinition.go index 27bb4cad884..0c3159fa6bb 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion/customresourcedefinition.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion/customresourcedefinition.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,7 +21,6 @@ package internalversion import ( apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" ) @@ -55,8 +54,7 @@ func (s *customResourceDefinitionLister) List(selector labels.Selector) (ret []* // Get retrieves the CustomResourceDefinition from the index for a given name. func (s *customResourceDefinitionLister) Get(name string) (*apiextensions.CustomResourceDefinition, error) { - key := &apiextensions.CustomResourceDefinition{ObjectMeta: v1.ObjectMeta{Name: name}} - obj, exists, err := s.indexer.Get(key) + obj, exists, err := s.indexer.GetByKey(name) if err != nil { return nil, err } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion/expansion_generated.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion/expansion_generated.go index 3be1e916499..33e966b806a 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion/expansion_generated.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1beta1/BUILD b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1beta1/BUILD index 5970f58a12d..f57e6d77fcf 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1beta1/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1beta1/BUILD @@ -15,7 +15,6 @@ go_library( deps = [ "//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", ], diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1beta1/customresourcedefinition.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1beta1/customresourcedefinition.go index f9b2a8f1e4b..ba735065702 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1beta1/customresourcedefinition.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1beta1/customresourcedefinition.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,7 +21,6 @@ package v1beta1 import ( v1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" ) @@ -55,8 +54,7 @@ func (s *customResourceDefinitionLister) List(selector labels.Selector) (ret []* // Get retrieves the CustomResourceDefinition from the index for a given name. func (s *customResourceDefinitionLister) Get(name string) (*v1beta1.CustomResourceDefinition, error) { - key := &v1beta1.CustomResourceDefinition{ObjectMeta: v1.ObjectMeta{Name: name}} - obj, exists, err := s.indexer.Get(key) + obj, exists, err := s.indexer.GetByKey(name) if err != nil { return nil, err } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1beta1/expansion_generated.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1beta1/expansion_generated.go index 5723fc39b73..a4aa2c7efa1 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1beta1/expansion_generated.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1beta1/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status/BUILD b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status/BUILD index 9dbd03a3bf7..a2e00d653b0 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["naming_controller_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apiextensions-apiserver/pkg/controller/status", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion:go_default_library", @@ -30,7 +30,6 @@ go_library( "//vendor/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status/naming_controller.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status/naming_controller.go index 271d11adf86..c0a7cc86f9d 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status/naming_controller.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status/naming_controller.go @@ -25,7 +25,6 @@ import ( "github.com/golang/glog" apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" utilerrors "k8s.io/apimachinery/pkg/util/errors" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -192,22 +191,20 @@ func (c *NamingConditionController) calculateNamesAndConditions(in *apiextension // set EstablishedCondition to true if all names are accepted. Never set it back to false. establishedCondition := apiextensions.CustomResourceDefinitionCondition{ - Type: apiextensions.Established, - Status: apiextensions.ConditionFalse, - Reason: "NotAccepted", - Message: "not all names are accepted", - LastTransitionTime: metav1.NewTime(time.Now()), + Type: apiextensions.Established, + Status: apiextensions.ConditionFalse, + Reason: "NotAccepted", + Message: "not all names are accepted", } if old := apiextensions.FindCRDCondition(in, apiextensions.Established); old != nil { establishedCondition = *old } if establishedCondition.Status != apiextensions.ConditionTrue && namesAcceptedCondition.Status == apiextensions.ConditionTrue { establishedCondition = apiextensions.CustomResourceDefinitionCondition{ - Type: apiextensions.Established, - Status: apiextensions.ConditionTrue, - Reason: "InitialNamesAccepted", - Message: "the initial names have been accepted", - LastTransitionTime: metav1.NewTime(time.Now()), + Type: apiextensions.Established, + Status: apiextensions.ConditionTrue, + Reason: "InitialNamesAccepted", + Message: "the initial names have been accepted", } } 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 fa78ce6ab51..80b37bf7bce 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 @@ -29,6 +29,7 @@ const ( // owner: @sttts, @nikhita // alpha: v1.8 + // beta: v1.9 // // CustomResourceValidation is a list of validation methods for CustomResources CustomResourceValidation utilfeature.Feature = "CustomResourceValidation" @@ -42,5 +43,5 @@ func init() { // To add a new feature, define a key for it above and add it here. The features will be // available throughout Kubernetes binaries. var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureSpec{ - CustomResourceValidation: {Default: false, PreRelease: utilfeature.Alpha}, + CustomResourceValidation: {Default: true, PreRelease: utilfeature.Beta}, } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/strategy.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/strategy.go index 18c6c2b7454..f001dc57a0f 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/strategy.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/strategy.go @@ -135,7 +135,7 @@ func (a customResourceValidator) Validate(ctx genericapirequest.Context, obj run return field.ErrorList{field.Invalid(field.NewPath("kind"), typeAccessor.GetKind(), fmt.Sprintf("must be %v", a.kind.Kind))} } if typeAccessor.GetAPIVersion() != a.kind.Group+"/"+a.kind.Version { - return field.ErrorList{field.Invalid(field.NewPath("apiVersion"), typeAccessor.GetKind(), fmt.Sprintf("must be %v", a.kind.Group+"/"+a.kind.Version))} + return field.ErrorList{field.Invalid(field.NewPath("apiVersion"), typeAccessor.GetAPIVersion(), fmt.Sprintf("must be %v", a.kind.Group+"/"+a.kind.Version))} } customResourceObject, ok := obj.(*unstructured.Unstructured) @@ -169,7 +169,7 @@ func (a customResourceValidator) ValidateUpdate(ctx genericapirequest.Context, o return field.ErrorList{field.Invalid(field.NewPath("kind"), typeAccessor.GetKind(), fmt.Sprintf("must be %v", a.kind.Kind))} } if typeAccessor.GetAPIVersion() != a.kind.Group+"/"+a.kind.Version { - return field.ErrorList{field.Invalid(field.NewPath("apiVersion"), typeAccessor.GetKind(), fmt.Sprintf("must be %v", a.kind.Group+"/"+a.kind.Version))} + return field.ErrorList{field.Invalid(field.NewPath("apiVersion"), typeAccessor.GetAPIVersion(), fmt.Sprintf("must be %v", a.kind.Group+"/"+a.kind.Version))} } customResourceObject, ok := obj.(*unstructured.Unstructured) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/etcd.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/etcd.go index 92f42e1907b..85d981f1a3b 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/etcd.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/etcd.go @@ -167,6 +167,6 @@ func (r *StatusREST) New() runtime.Object { } // Update alters the status subset of an object. -func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { - return r.store.Update(ctx, name, objInfo) +func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation) } diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/BUILD b/staging/src/k8s.io/apiextensions-apiserver/test/integration/BUILD index e779469bf2d..1aca59abf8e 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/BUILD @@ -9,7 +9,6 @@ go_test( name = "go_default_test", srcs = [ "basic_test.go", - "client-go_test.go", "finalization_test.go", "registration_test.go", "validation_test.go", @@ -19,10 +18,6 @@ go_test( deps = [ "//vendor/github.com/coreos/etcd/clientv3:go_default_library", "//vendor/github.com/stretchr/testify/require:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1:go_default_library", - "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/client:go_default_library", - "//vendor/k8s.io/apiextensions-apiserver/examples/client-go/controller:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/apiserver:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/test/integration/testserver:go_default_library", @@ -31,9 +26,9 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", "//vendor/k8s.io/client-go/dynamic:go_default_library", ], ) diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/basic_test.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/basic_test.go index 03bdd873b85..72fdf592ada 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/basic_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/basic_test.go @@ -28,6 +28,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "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/dynamic" @@ -546,6 +547,69 @@ func TestPreserveInt(t *testing.T) { } } +func TestPatch(t *testing.T) { + stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer() + if err != nil { + t.Fatal(err) + } + defer close(stopCh) + + noxuDefinition := testserver.NewNoxuCustomResourceDefinition(apiextensionsv1beta1.ClusterScoped) + noxuVersionClient, err := testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, clientPool) + if err != nil { + t.Fatal(err) + } + + ns := "not-the-default" + noxuNamespacedResourceClient := noxuVersionClient.Resource(&metav1.APIResource{ + Name: noxuDefinition.Spec.Names.Plural, + Namespaced: true, + }, ns) + + noxuInstanceToCreate := testserver.NewNoxuInstance(ns, "foo") + createdNoxuInstance, err := noxuNamespacedResourceClient.Create(noxuInstanceToCreate) + if err != nil { + t.Fatal(err) + } + + patch := []byte(`{"num": {"num2":999}}`) + createdNoxuInstance, err = noxuNamespacedResourceClient.Patch("foo", types.MergePatchType, patch) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // a patch with no change + createdNoxuInstance, err = noxuNamespacedResourceClient.Patch("foo", types.MergePatchType, patch) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // an empty patch + createdNoxuInstance, err = noxuNamespacedResourceClient.Patch("foo", types.MergePatchType, []byte(`{}`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + originalJSON, err := runtime.Encode(unstructured.UnstructuredJSONScheme, createdNoxuInstance) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + gottenNoxuInstance, err := runtime.Decode(unstructured.UnstructuredJSONScheme, originalJSON) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Check if int is preserved. + unstructuredObj := gottenNoxuInstance.(*unstructured.Unstructured).Object + num := unstructuredObj["num"].(map[string]interface{}) + num1 := num["num1"].(int64) + num2 := num["num2"].(int64) + if num1 != 9223372036854775807 || num2 != 999 { + t.Errorf("Expected %v, got %v, %v", `9223372036854775807, 999`, num1, num2) + } +} + func TestCrossNamespaceListWatch(t *testing.T) { stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer() if err != nil { diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/client-go_test.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/client-go_test.go deleted file mode 100644 index 34c04002cea..00000000000 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/client-go_test.go +++ /dev/null @@ -1,96 +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 integration - -import ( - "context" - "testing" - - apiv1 "k8s.io/api/core/v1" - "k8s.io/apiextensions-apiserver/test/integration/testserver" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - examplecrv1 "k8s.io/apiextensions-apiserver/examples/client-go/apis/cr/v1" - exampleclient "k8s.io/apiextensions-apiserver/examples/client-go/client" - examplecontroller "k8s.io/apiextensions-apiserver/examples/client-go/controller" -) - -func TestClientGoCustomResourceExample(t *testing.T) { - t.Logf("Creating apiextensions apiserver") - config, err := testserver.DefaultServerConfig() - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - stopCh, apiExtensionClient, _, err := testserver.StartServer(config) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - defer close(stopCh) - - t.Logf("Creating CustomResourceDefinition") - crd, err := exampleclient.CreateCustomResourceDefinition(apiExtensionClient) - if err != nil { - t.Fatalf("unexpected error creating the CustomResourceDefinition: %v", err) - } - defer apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Delete(crd.Name, nil) - - exampleClient, exampleScheme, err := exampleclient.NewClient(config.GenericConfig.LoopbackClientConfig) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - - t.Logf("Starting a controller on instances of custom resource %q", examplecrv1.ExampleResourcePlural) - controller := examplecontroller.ExampleController{ - ExampleClient: exampleClient, - ExampleScheme: exampleScheme, - } - - ctx, cancelFunc := context.WithCancel(context.Background()) - defer cancelFunc() - go controller.Run(ctx) - - // Create an instance of our custom resource - t.Logf("Creating custom resource instance") - example := &examplecrv1.Example{ - ObjectMeta: metav1.ObjectMeta{ - Name: "example1", - }, - Spec: examplecrv1.ExampleSpec{ - Foo: "hello", - Bar: true, - }, - Status: examplecrv1.ExampleStatus{ - State: examplecrv1.ExampleStateCreated, - Message: "Created, not processed yet", - }, - } - var result examplecrv1.Example - err = exampleClient.Post(). - Resource(examplecrv1.ExampleResourcePlural). - Namespace(apiv1.NamespaceDefault). - Body(example). - Do().Into(&result) - if err != nil { - t.Fatalf("Failed to create an instance of the custom resource: %v", err) - } - - t.Logf("Waiting instance to be processed by the controller") - if err := exampleclient.WaitForExampleInstanceProcessed(exampleClient, "example1"); err != nil { - t.Fatalf("Instance was not processed correctly: %v", err) - } - t.Logf("Instance is processed") -} diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/resources.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/resources.go index d2d739de9be..562281f4c97 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/resources.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/resources.go @@ -146,7 +146,11 @@ func NewCurletInstance(namespace, name string) *unstructured.Unstructured { } } -func CreateNewCustomResourceDefinition(crd *apiextensionsv1beta1.CustomResourceDefinition, apiExtensionsClient clientset.Interface, clientPool dynamic.ClientPool) (dynamic.Interface, error) { +// CreateNewCustomResourceDefinitionWatchUnsafe creates the CRD and makes sure +// the apiextension apiserver has installed the CRD. But it's not safe to watch +// the created CR. Please call CreateNewCustomResourceDefinition if you need to +// watch the CR. +func CreateNewCustomResourceDefinitionWatchUnsafe(crd *apiextensionsv1beta1.CustomResourceDefinition, apiExtensionsClient clientset.Interface, clientPool dynamic.ClientPool) (dynamic.Interface, error) { _, err := apiExtensionsClient.Apiextensions().CustomResourceDefinitions().Create(crd) if err != nil { return nil, err @@ -169,7 +173,11 @@ func CreateNewCustomResourceDefinition(crd *apiextensionsv1beta1.CustomResourceD return nil, err } - dynamicClient, err := clientPool.ClientForGroupVersionResource(schema.GroupVersionResource{Group: crd.Spec.Group, Version: crd.Spec.Version, Resource: crd.Spec.Names.Plural}) + return clientPool.ClientForGroupVersionResource(schema.GroupVersionResource{Group: crd.Spec.Group, Version: crd.Spec.Version, Resource: crd.Spec.Names.Plural}) +} + +func CreateNewCustomResourceDefinition(crd *apiextensionsv1beta1.CustomResourceDefinition, apiExtensionsClient clientset.Interface, clientPool dynamic.ClientPool) (dynamic.Interface, error) { + dynamicClient, err := CreateNewCustomResourceDefinitionWatchUnsafe(crd, apiExtensionsClient, clientPool) if err != nil { return nil, err } diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/validation_test.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/validation_test.go index d540fa59b88..2538149e266 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/validation_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/validation_test.go @@ -25,7 +25,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/wait" - utilfeature "k8s.io/apiserver/pkg/util/feature" apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" "k8s.io/apiextensions-apiserver/test/integration/testserver" @@ -176,12 +175,6 @@ func TestCustomResourceValidation(t *testing.T) { } defer close(stopCh) - // enable alpha feature CustomResourceValidation - err = utilfeature.DefaultFeatureGate.Set("CustomResourceValidation=true") - if err != nil { - t.Errorf("failed to enable feature gate for CustomResourceValidation: %v", err) - } - noxuDefinition := newNoxuValidationCRD(apiextensionsv1beta1.NamespaceScoped) noxuVersionClient, err := testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, clientPool) if err != nil { @@ -203,12 +196,6 @@ func TestCustomResourceUpdateValidation(t *testing.T) { } defer close(stopCh) - // enable alpha feature CustomResourceValidation - err = utilfeature.DefaultFeatureGate.Set("CustomResourceValidation=true") - if err != nil { - t.Errorf("failed to enable feature gate for CustomResourceValidation: %v", err) - } - noxuDefinition := newNoxuValidationCRD(apiextensionsv1beta1.NamespaceScoped) noxuVersionClient, err := testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, clientPool) if err != nil { @@ -252,12 +239,6 @@ func TestCustomResourceValidationErrors(t *testing.T) { } defer close(stopCh) - // enable alpha feature CustomResourceValidation - err = utilfeature.DefaultFeatureGate.Set("CustomResourceValidation=true") - if err != nil { - t.Errorf("failed to enable feature gate for CustomResourceValidation: %v", err) - } - noxuDefinition := newNoxuValidationCRD(apiextensionsv1beta1.NamespaceScoped) noxuVersionClient, err := testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, clientPool) if err != nil { @@ -349,12 +330,6 @@ func TestCRValidationOnCRDUpdate(t *testing.T) { } defer close(stopCh) - // enable alpha feature CustomResourceValidation - err = utilfeature.DefaultFeatureGate.Set("CustomResourceValidation=true") - if err != nil { - t.Errorf("failed to enable feature gate for CustomResourceValidation: %v", err) - } - noxuDefinition := newNoxuValidationCRD(apiextensionsv1beta1.NamespaceScoped) // set stricter schema @@ -409,12 +384,6 @@ func TestForbiddenFieldsInSchema(t *testing.T) { } defer close(stopCh) - // enable alpha feature CustomResourceValidation - err = utilfeature.DefaultFeatureGate.Set("CustomResourceValidation=true") - if err != nil { - t.Errorf("failed to enable feature gate for CustomResourceValidation: %v", err) - } - noxuDefinition := newNoxuValidationCRD(apiextensionsv1beta1.NamespaceScoped) noxuDefinition.Spec.Validation.OpenAPIV3Schema.AdditionalProperties.Allows = false diff --git a/staging/src/k8s.io/apimachinery/Godeps/Godeps.json b/staging/src/k8s.io/apimachinery/Godeps/Godeps.json index 603fd650e21..a264f944b4e 100644 --- a/staging/src/k8s.io/apimachinery/Godeps/Godeps.json +++ b/staging/src/k8s.io/apimachinery/Godeps/Godeps.json @@ -1,6 +1,6 @@ { "ImportPath": "k8s.io/apimachinery", - "GoVersion": "go1.8", + "GoVersion": "go1.9", "GodepVersion": "v79", "Packages": [ "./..." @@ -80,12 +80,40 @@ }, { "ImportPath": "github.com/golang/protobuf/proto", - "Rev": "4bd1920723d7b7c925de087aa32e2187708897f7" + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" + }, + { + "ImportPath": "github.com/golang/protobuf/ptypes", + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" + }, + { + "ImportPath": "github.com/golang/protobuf/ptypes/any", + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" + }, + { + "ImportPath": "github.com/golang/protobuf/ptypes/duration", + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" + }, + { + "ImportPath": "github.com/golang/protobuf/ptypes/timestamp", + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" }, { "ImportPath": "github.com/google/gofuzz", "Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" }, + { + "ImportPath": "github.com/googleapis/gnostic/OpenAPIv2", + "Rev": "0c5108395e2debce0d731cf0287ddf7242066aba" + }, + { + "ImportPath": "github.com/googleapis/gnostic/compiler", + "Rev": "0c5108395e2debce0d731cf0287ddf7242066aba" + }, + { + "ImportPath": "github.com/googleapis/gnostic/extensions", + "Rev": "0c5108395e2debce0d731cf0287ddf7242066aba" + }, { "ImportPath": "github.com/hashicorp/golang-lru", "Rev": "a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4" @@ -96,7 +124,7 @@ }, { "ImportPath": "github.com/json-iterator/go", - "Rev": "36b14963da70d11297d313183d7e6388c8510e1e" + "Rev": "13f86432b882000a51c6e610c620974462691a97" }, { "ImportPath": "github.com/mailru/easyjson/buffer", @@ -216,7 +244,11 @@ }, { "ImportPath": "k8s.io/kube-openapi/pkg/common", - "Rev": "868f2f29720b192240e18284659231b440f9cda5" + "Rev": "39a7bf85c140f972372c2a0d1ee40adbf0c8bfe1" + }, + { + "ImportPath": "k8s.io/kube-openapi/pkg/util/proto", + "Rev": "39a7bf85c140f972372c2a0d1ee40adbf0c8bfe1" } ] } diff --git a/staging/src/k8s.io/apimachinery/LICENSE b/staging/src/k8s.io/apimachinery/LICENSE new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/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. diff --git a/staging/src/k8s.io/apimachinery/README.md b/staging/src/k8s.io/apimachinery/README.md new file mode 100644 index 00000000000..258250ce2cb --- /dev/null +++ b/staging/src/k8s.io/apimachinery/README.md @@ -0,0 +1,29 @@ +# apimachinery + +Scheme, typing, encoding, decoding, and conversion packages for Kubernetes and Kubernetes-like API objects. + + +## Purpose + +This library is a shared dependency for servers and clients to work with Kubernetes API infrastructure without direct +type dependencies. Its first consumers are `k8s.io/kubernetes`, `k8s.io/client-go`, and `k8s.io/apiserver`. + + +## Compatibility + +There are *NO compatibility guarantees* for this repository. It is in direct support of Kubernetes, so branches +will track Kubernetes and be compatible with that repo. As we more cleanly separate the layers, we will review the +compatibility guarantee. + + +## Where does it come from? + +`apimachinery` is synced from https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery. +Code changes are made in that location, merged into `k8s.io/kubernetes` and later synced here. + + +## Things you should *NOT* do + + 1. Add API types to this repo. This is for the machinery, not for the types. + 2. Directly modify any files under `pkg` in this repo. Those are driven from `k8s.io/kubernetes/staging/src/k8s.io/apimachinery`. + 3. Expect compatibility. This repo is direct support of Kubernetes and the API isn't yet stable enough for API guarantees. diff --git a/staging/src/k8s.io/apimachinery/code-of-conduct.md b/staging/src/k8s.io/apimachinery/code-of-conduct.md new file mode 100644 index 00000000000..0d15c00cf32 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/code-of-conduct.md @@ -0,0 +1,3 @@ +# Kubernetes Community Code of Conduct + +Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) diff --git a/staging/src/k8s.io/apimachinery/pkg/api/errors/BUILD b/staging/src/k8s.io/apimachinery/pkg/api/errors/BUILD index 80e205320ad..384b432bd99 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/errors/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/api/errors/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["errors_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/api/errors", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/staging/src/k8s.io/apimachinery/pkg/api/errors/errors.go b/staging/src/k8s.io/apimachinery/pkg/api/errors/errors.go index d5503fac5d9..9960600be33 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/errors/errors.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/errors/errors.go @@ -405,84 +405,84 @@ func NewGenericServerResponse(code int, verb string, qualifiedResource schema.Gr // IsNotFound returns true if the specified error was created by NewNotFound. func IsNotFound(err error) bool { - return reasonForError(err) == metav1.StatusReasonNotFound + return ReasonForError(err) == metav1.StatusReasonNotFound } // IsAlreadyExists determines if the err is an error which indicates that a specified resource already exists. func IsAlreadyExists(err error) bool { - return reasonForError(err) == metav1.StatusReasonAlreadyExists + return ReasonForError(err) == metav1.StatusReasonAlreadyExists } // IsConflict determines if the err is an error which indicates the provided update conflicts. func IsConflict(err error) bool { - return reasonForError(err) == metav1.StatusReasonConflict + return ReasonForError(err) == metav1.StatusReasonConflict } // IsInvalid determines if the err is an error which indicates the provided resource is not valid. func IsInvalid(err error) bool { - return reasonForError(err) == metav1.StatusReasonInvalid + return ReasonForError(err) == metav1.StatusReasonInvalid } // IsGone is true if the error indicates the requested resource is no longer available. func IsGone(err error) bool { - return reasonForError(err) == metav1.StatusReasonGone + return ReasonForError(err) == metav1.StatusReasonGone } // IsResourceExpired is true if the error indicates the resource has expired and the current action is // no longer possible. func IsResourceExpired(err error) bool { - return reasonForError(err) == metav1.StatusReasonExpired + return ReasonForError(err) == metav1.StatusReasonExpired } // IsMethodNotSupported determines if the err is an error which indicates the provided action could not // be performed because it is not supported by the server. func IsMethodNotSupported(err error) bool { - return reasonForError(err) == metav1.StatusReasonMethodNotAllowed + return ReasonForError(err) == metav1.StatusReasonMethodNotAllowed } // IsServiceUnavailable is true if the error indicates the underlying service is no longer available. func IsServiceUnavailable(err error) bool { - return reasonForError(err) == metav1.StatusReasonServiceUnavailable + return ReasonForError(err) == metav1.StatusReasonServiceUnavailable } // IsBadRequest determines if err is an error which indicates that the request is invalid. func IsBadRequest(err error) bool { - return reasonForError(err) == metav1.StatusReasonBadRequest + return ReasonForError(err) == metav1.StatusReasonBadRequest } // IsUnauthorized determines if err is an error which indicates that the request is unauthorized and // requires authentication by the user. func IsUnauthorized(err error) bool { - return reasonForError(err) == metav1.StatusReasonUnauthorized + return ReasonForError(err) == metav1.StatusReasonUnauthorized } // IsForbidden determines if err is an error which indicates that the request is forbidden and cannot // be completed as requested. func IsForbidden(err error) bool { - return reasonForError(err) == metav1.StatusReasonForbidden + return ReasonForError(err) == metav1.StatusReasonForbidden } // IsTimeout determines if err is an error which indicates that request times out due to long // processing. func IsTimeout(err error) bool { - return reasonForError(err) == metav1.StatusReasonTimeout + return ReasonForError(err) == metav1.StatusReasonTimeout } // IsServerTimeout determines if err is an error which indicates that the request needs to be retried // by the client. func IsServerTimeout(err error) bool { - return reasonForError(err) == metav1.StatusReasonServerTimeout + return ReasonForError(err) == metav1.StatusReasonServerTimeout } // IsInternalError determines if err is an error which indicates an internal server error. func IsInternalError(err error) bool { - return reasonForError(err) == metav1.StatusReasonInternalError + return ReasonForError(err) == metav1.StatusReasonInternalError } // IsTooManyRequests determines if err is an error which indicates that there are too many requests // that the server cannot handle. func IsTooManyRequests(err error) bool { - if reasonForError(err) == metav1.StatusReasonTooManyRequests { + if ReasonForError(err) == metav1.StatusReasonTooManyRequests { return true } switch t := err.(type) { @@ -536,7 +536,8 @@ func SuggestsClientDelay(err error) (int, bool) { return 0, false } -func reasonForError(err error) metav1.StatusReason { +// ReasonForError returns the HTTP status for a particular error. +func ReasonForError(err error) metav1.StatusReason { switch t := err.(type) { case APIStatus: return t.Status().Reason diff --git a/staging/src/k8s.io/apimachinery/pkg/api/errors/errors_test.go b/staging/src/k8s.io/apimachinery/pkg/api/errors/errors_test.go index afc26fd31c4..303a9d3f48f 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/errors/errors_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/errors/errors_test.go @@ -188,8 +188,8 @@ func TestNewInvalid(t *testing.T) { } } -func Test_reasonForError(t *testing.T) { - if e, a := metav1.StatusReasonUnknown, reasonForError(nil); e != a { +func TestReasonForError(t *testing.T) { + if e, a := metav1.StatusReasonUnknown, ReasonForError(nil); e != a { t.Errorf("unexpected reason type: %#v", a) } } diff --git a/staging/src/k8s.io/apimachinery/pkg/api/meta/BUILD b/staging/src/k8s.io/apimachinery/pkg/api/meta/BUILD index 5e2cb106531..21097f9b9d4 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/meta/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/api/meta/BUILD @@ -14,8 +14,8 @@ go_test( "priority_test.go", "restmapper_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/api/meta", - library = ":go_default_library", deps = [ "//vendor/github.com/google/gofuzz:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -34,6 +34,7 @@ go_library( "firsthit_restmapper.go", "help.go", "interfaces.go", + "lazy.go", "meta.go", "multirestmapper.go", "priority.go", @@ -51,6 +52,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", ], ) diff --git a/staging/src/k8s.io/apimachinery/pkg/api/meta/errors.go b/staging/src/k8s.io/apimachinery/pkg/api/meta/errors.go index 1503bd6d846..cbf5d0263c6 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/meta/errors.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/meta/errors.go @@ -20,6 +20,7 @@ import ( "fmt" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/sets" ) // AmbiguousResourceError is returned if the RESTMapper finds multiple matches for a resource @@ -85,11 +86,26 @@ func (e *NoResourceMatchError) Error() string { // NoKindMatchError is returned if the RESTMapper can't find any match for a kind type NoKindMatchError struct { - PartialKind schema.GroupVersionKind + // GroupKind is the API group and kind that was searched + GroupKind schema.GroupKind + // SearchedVersions is the optional list of versions the search was restricted to + SearchedVersions []string } func (e *NoKindMatchError) Error() string { - return fmt.Sprintf("no matches for %v", e.PartialKind) + searchedVersions := sets.NewString() + for _, v := range e.SearchedVersions { + searchedVersions.Insert(schema.GroupVersion{Group: e.GroupKind.Group, Version: v}.String()) + } + + switch len(searchedVersions) { + case 0: + return fmt.Sprintf("no matches for kind %q in group %q", e.GroupKind.Kind, e.GroupKind.Group) + case 1: + return fmt.Sprintf("no matches for kind %q in version %q", e.GroupKind.Kind, searchedVersions.List()[0]) + default: + return fmt.Sprintf("no matches for kind %q in versions %q", e.GroupKind.Kind, searchedVersions.List()) + } } func IsNoMatchError(err error) bool { diff --git a/staging/src/k8s.io/apimachinery/pkg/api/meta/interfaces.go b/staging/src/k8s.io/apimachinery/pkg/api/meta/interfaces.go index bf1723693a2..5dc9d89e67f 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/meta/interfaces.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/meta/interfaces.go @@ -75,6 +75,9 @@ type MetadataAccessor interface { Annotations(obj runtime.Object) (map[string]string, error) SetAnnotations(obj runtime.Object, annotations map[string]string) error + Continue(obj runtime.Object) (string, error) + SetContinue(obj runtime.Object, c string) error + runtime.ResourceVersioner } diff --git a/staging/src/k8s.io/apimachinery/pkg/api/meta/lazy.go b/staging/src/k8s.io/apimachinery/pkg/api/meta/lazy.go new file mode 100644 index 00000000000..7f92f39a43f --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/api/meta/lazy.go @@ -0,0 +1,121 @@ +/* +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 meta + +import ( + "sync" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// lazyObject defers loading the mapper and typer until necessary. +type lazyObject struct { + loader func() (RESTMapper, runtime.ObjectTyper, error) + + lock sync.Mutex + loaded bool + err error + mapper RESTMapper + typer runtime.ObjectTyper +} + +// NewLazyObjectLoader handles unrecoverable errors when creating a RESTMapper / ObjectTyper by +// returning those initialization errors when the interface methods are invoked. This defers the +// initialization and any server calls until a client actually needs to perform the action. +func NewLazyObjectLoader(fn func() (RESTMapper, runtime.ObjectTyper, error)) (RESTMapper, runtime.ObjectTyper) { + obj := &lazyObject{loader: fn} + return obj, obj +} + +// init lazily loads the mapper and typer, returning an error if initialization has failed. +func (o *lazyObject) init() error { + o.lock.Lock() + defer o.lock.Unlock() + if o.loaded { + return o.err + } + o.mapper, o.typer, o.err = o.loader() + o.loaded = true + return o.err +} + +var _ RESTMapper = &lazyObject{} +var _ runtime.ObjectTyper = &lazyObject{} + +func (o *lazyObject) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) { + if err := o.init(); err != nil { + return schema.GroupVersionKind{}, err + } + return o.mapper.KindFor(resource) +} + +func (o *lazyObject) KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) { + if err := o.init(); err != nil { + return []schema.GroupVersionKind{}, err + } + return o.mapper.KindsFor(resource) +} + +func (o *lazyObject) ResourceFor(input schema.GroupVersionResource) (schema.GroupVersionResource, error) { + if err := o.init(); err != nil { + return schema.GroupVersionResource{}, err + } + return o.mapper.ResourceFor(input) +} + +func (o *lazyObject) ResourcesFor(input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) { + if err := o.init(); err != nil { + return []schema.GroupVersionResource{}, err + } + return o.mapper.ResourcesFor(input) +} + +func (o *lazyObject) RESTMapping(gk schema.GroupKind, versions ...string) (*RESTMapping, error) { + if err := o.init(); err != nil { + return nil, err + } + return o.mapper.RESTMapping(gk, versions...) +} + +func (o *lazyObject) RESTMappings(gk schema.GroupKind, versions ...string) ([]*RESTMapping, error) { + if err := o.init(); err != nil { + return nil, err + } + return o.mapper.RESTMappings(gk, versions...) +} + +func (o *lazyObject) ResourceSingularizer(resource string) (singular string, err error) { + if err := o.init(); err != nil { + return "", err + } + return o.mapper.ResourceSingularizer(resource) +} + +func (o *lazyObject) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) { + if err := o.init(); err != nil { + return nil, false, err + } + return o.typer.ObjectKinds(obj) +} + +func (o *lazyObject) Recognizes(gvk schema.GroupVersionKind) bool { + if err := o.init(); err != nil { + return false + } + return o.typer.Recognizes(gvk) +} diff --git a/staging/src/k8s.io/apimachinery/pkg/api/meta/meta.go b/staging/src/k8s.io/apimachinery/pkg/api/meta/meta.go index 1889b951262..c2d51b43c73 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/meta/meta.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/meta/meta.go @@ -89,7 +89,7 @@ func ListAccessor(obj interface{}) (List, error) { } return nil, errNotList default: - panic(fmt.Errorf("%T does not implement the List interface", obj)) + return nil, errNotList } } @@ -367,6 +367,23 @@ func (resourceAccessor) SetResourceVersion(obj runtime.Object, version string) e return nil } +func (resourceAccessor) Continue(obj runtime.Object) (string, error) { + accessor, err := ListAccessor(obj) + if err != nil { + return "", err + } + return accessor.GetContinue(), nil +} + +func (resourceAccessor) SetContinue(obj runtime.Object, version string) error { + accessor, err := ListAccessor(obj) + if err != nil { + return err + } + accessor.SetContinue(version) + return nil +} + // extractFromOwnerReference extracts v to o. v is the OwnerReferences field of an object. func extractFromOwnerReference(v reflect.Value, o *metav1.OwnerReference) error { if err := runtime.Field(v, "APIVersion", &o.APIVersion); err != nil { diff --git a/staging/src/k8s.io/apimachinery/pkg/api/meta/multirestmapper.go b/staging/src/k8s.io/apimachinery/pkg/api/meta/multirestmapper.go index 679098fe56f..6b01bf197fa 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/meta/multirestmapper.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/meta/multirestmapper.go @@ -179,7 +179,7 @@ func (m MultiRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (* if len(errors) > 0 { return nil, utilerrors.NewAggregate(errors) } - return nil, &NoKindMatchError{PartialKind: gk.WithVersion("")} + return nil, &NoKindMatchError{GroupKind: gk, SearchedVersions: versions} } // RESTMappings returns all possible RESTMappings for the provided group kind, or an error @@ -204,7 +204,7 @@ func (m MultiRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) ( return nil, utilerrors.NewAggregate(errors) } if len(allMappings) == 0 { - return nil, &NoKindMatchError{PartialKind: gk.WithVersion("")} + return nil, &NoKindMatchError{GroupKind: gk, SearchedVersions: versions} } return allMappings, nil } diff --git a/staging/src/k8s.io/apimachinery/pkg/api/meta/multirestmapper_test.go b/staging/src/k8s.io/apimachinery/pkg/api/meta/multirestmapper_test.go index dec07a16f7b..b71ca468d32 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/meta/multirestmapper_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/meta/multirestmapper_test.go @@ -261,42 +261,78 @@ func TestMultiRESTMapperRESTMappings(t *testing.T) { tcs := []struct { name string - mapper MultiRESTMapper - input schema.GroupKind - result []*RESTMapping - err error + mapper MultiRESTMapper + groupKind schema.GroupKind + versions []string + result []*RESTMapping + err error }{ { - name: "empty", - mapper: MultiRESTMapper{}, - input: schema.GroupKind{Kind: "Foo"}, - result: nil, - err: &NoKindMatchError{PartialKind: schema.GroupVersionKind{Kind: "Foo"}}, + name: "empty with no versions", + mapper: MultiRESTMapper{}, + groupKind: schema.GroupKind{Kind: "Foo"}, + result: nil, + err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}}, }, { - name: "ignore not found", - mapper: MultiRESTMapper{fixedRESTMapper{err: &NoKindMatchError{PartialKind: schema.GroupVersionKind{Kind: "IGNORE_THIS"}}}}, - input: schema.GroupKind{Kind: "Foo"}, - result: nil, - err: &NoKindMatchError{PartialKind: schema.GroupVersionKind{Kind: "Foo"}}, + name: "empty with one version", + mapper: MultiRESTMapper{}, + groupKind: schema.GroupKind{Kind: "Foo"}, + versions: []string{"v1beta"}, + result: nil, + err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}, SearchedVersions: []string{"v1beta"}}, }, { - name: "accept first failure", - mapper: MultiRESTMapper{fixedRESTMapper{err: errors.New("fail on this")}, fixedRESTMapper{mappings: []*RESTMapping{mapping1}}}, - input: schema.GroupKind{Kind: "Foo"}, - result: nil, - err: errors.New("fail on this"), + name: "empty with multi(two) vesions", + mapper: MultiRESTMapper{}, + groupKind: schema.GroupKind{Kind: "Foo"}, + versions: []string{"v1beta", "v2"}, + result: nil, + err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}, SearchedVersions: []string{"v1beta", "v2"}}, }, { - name: "return both", - mapper: MultiRESTMapper{fixedRESTMapper{mappings: []*RESTMapping{mapping1}}, fixedRESTMapper{mappings: []*RESTMapping{mapping2}}}, - input: schema.GroupKind{Kind: "Foo"}, - result: []*RESTMapping{mapping1, mapping2}, + name: "ignore not found with kind not exist", + mapper: MultiRESTMapper{fixedRESTMapper{err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "IGNORE_THIS"}}}}, + groupKind: schema.GroupKind{Kind: "Foo"}, + versions: nil, + result: nil, + err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}}, + }, + { + name: "ignore not found with version not exist", + mapper: MultiRESTMapper{fixedRESTMapper{err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}, SearchedVersions: []string{"v1"}}}}, + groupKind: schema.GroupKind{Kind: "Foo"}, + versions: []string{"v1beta"}, + result: nil, + err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}, SearchedVersions: []string{"v1beta"}}, + }, + { + name: "ignore not found with multi versions not exist", + mapper: MultiRESTMapper{fixedRESTMapper{err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}, SearchedVersions: []string{"v1"}}}}, + groupKind: schema.GroupKind{Kind: "Foo"}, + versions: []string{"v1beta", "v2"}, + result: nil, + err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}, SearchedVersions: []string{"v1beta", "v2"}}, + }, + { + name: "accept first failure", + mapper: MultiRESTMapper{fixedRESTMapper{err: errors.New("fail on this")}, fixedRESTMapper{mappings: []*RESTMapping{mapping1}}}, + groupKind: schema.GroupKind{Kind: "Foo"}, + versions: []string{"v1beta"}, + result: nil, + err: errors.New("fail on this"), + }, + { + name: "return both", + mapper: MultiRESTMapper{fixedRESTMapper{mappings: []*RESTMapping{mapping1}}, fixedRESTMapper{mappings: []*RESTMapping{mapping2}}}, + groupKind: schema.GroupKind{Kind: "Foo"}, + versions: []string{"v1beta"}, + result: []*RESTMapping{mapping1, mapping2}, }, } for _, tc := range tcs { - actualResult, actualErr := tc.mapper.RESTMappings(tc.input) + actualResult, actualErr := tc.mapper.RESTMappings(tc.groupKind, tc.versions...) if e, a := tc.result, actualResult; !reflect.DeepEqual(e, a) { t.Errorf("%s: expected %v, got %v", tc.name, e, a) } diff --git a/staging/src/k8s.io/apimachinery/pkg/api/meta/priority.go b/staging/src/k8s.io/apimachinery/pkg/api/meta/priority.go index 2a14aa7ab17..df28e64ffaa 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/meta/priority.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/meta/priority.go @@ -153,7 +153,7 @@ func kindMatches(pattern schema.GroupVersionKind, kind schema.GroupVersionKind) } func (m PriorityRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (mapping *RESTMapping, err error) { - mappings, err := m.Delegate.RESTMappings(gk) + mappings, err := m.Delegate.RESTMappings(gk, versions...) if err != nil { return nil, err } diff --git a/staging/src/k8s.io/apimachinery/pkg/api/meta/priority_test.go b/staging/src/k8s.io/apimachinery/pkg/api/meta/priority_test.go index f273a39f9f6..098d53bd513 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/meta/priority_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/meta/priority_test.go @@ -234,13 +234,13 @@ func TestPriorityRESTMapperRESTMapping(t *testing.T) { name: "empty", mapper: PriorityRESTMapper{Delegate: MultiRESTMapper{}}, input: schema.GroupKind{Kind: "Foo"}, - err: &NoKindMatchError{PartialKind: schema.GroupVersionKind{Kind: "Foo"}}, + err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}}, }, { name: "ignore not found", - mapper: PriorityRESTMapper{Delegate: MultiRESTMapper{fixedRESTMapper{err: &NoKindMatchError{PartialKind: schema.GroupVersionKind{Kind: "IGNORE_THIS"}}}}}, + mapper: PriorityRESTMapper{Delegate: MultiRESTMapper{fixedRESTMapper{err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "IGNORE_THIS"}}}}}, input: schema.GroupKind{Kind: "Foo"}, - err: &NoKindMatchError{PartialKind: schema.GroupVersionKind{Kind: "Foo"}}, + err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}}, }, { name: "accept first failure", diff --git a/staging/src/k8s.io/apimachinery/pkg/api/meta/restmapper.go b/staging/src/k8s.io/apimachinery/pkg/api/meta/restmapper.go index 55155a6e437..ff945acd147 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/meta/restmapper.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/meta/restmapper.go @@ -472,7 +472,7 @@ func (m *DefaultRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) return nil, err } if len(mappings) == 0 { - return nil, &NoKindMatchError{PartialKind: gk.WithVersion("")} + return nil, &NoKindMatchError{GroupKind: gk, SearchedVersions: versions} } // since we rely on RESTMappings method // take the first match and return to the caller @@ -510,7 +510,7 @@ func (m *DefaultRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string } if len(potentialGVK) == 0 { - return nil, &NoKindMatchError{PartialKind: gk.WithVersion("")} + return nil, &NoKindMatchError{GroupKind: gk, SearchedVersions: versions} } for _, gvk := range potentialGVK { diff --git a/staging/src/k8s.io/apimachinery/pkg/api/meta/unstructured.go b/staging/src/k8s.io/apimachinery/pkg/api/meta/unstructured.go index 3ebf2481573..4e13efea3c3 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/meta/unstructured.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/meta/unstructured.go @@ -21,8 +21,24 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" ) +// InterfacesForUnstructuredConversion returns VersionInterfaces suitable for +// dealing with unstructured.Unstructured objects and supports conversion +// from typed objects (provided by parent) to untyped objects. +func InterfacesForUnstructuredConversion(parent VersionInterfacesFunc) VersionInterfacesFunc { + return func(version schema.GroupVersion) (*VersionInterfaces, error) { + if i, err := parent(version); err == nil { + return &VersionInterfaces{ + ObjectConvertor: i.ObjectConvertor, + MetadataAccessor: NewAccessor(), + }, nil + } + return InterfacesForUnstructured(version) + } +} + // InterfacesForUnstructured returns VersionInterfaces suitable for -// dealing with unstructured.Unstructured objects. +// dealing with unstructured.Unstructured objects. It will return errors for +// other conversions. func InterfacesForUnstructured(schema.GroupVersion) (*VersionInterfaces, error) { return &VersionInterfaces{ ObjectConvertor: &unstructured.UnstructuredObjectConverter{}, diff --git a/staging/src/k8s.io/apimachinery/pkg/api/resource/BUILD b/staging/src/k8s.io/apimachinery/pkg/api/resource/BUILD index 5b887546303..fab98203507 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/resource/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/api/resource/BUILD @@ -15,8 +15,8 @@ go_test( "quantity_test.go", "scale_int_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/api/resource", - library = ":go_default_library", deps = [ "//vendor/github.com/google/gofuzz:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", @@ -42,7 +42,6 @@ go_library( "//vendor/github.com/gogo/protobuf/proto:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", "//vendor/gopkg.in/inf.v0:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/kube-openapi/pkg/common:go_default_library", ], ) diff --git a/staging/src/k8s.io/apimachinery/pkg/api/resource/generated.pb.go b/staging/src/k8s.io/apimachinery/pkg/api/resource/generated.pb.go index 8b2e338a7ef..6de71e5087d 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/resource/generated.pb.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/resource/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apimachinery/pkg/api/resource/generated.proto b/staging/src/k8s.io/apimachinery/pkg/api/resource/generated.proto index 091d11bdba1..40185777e7e 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/resource/generated.proto +++ b/staging/src/k8s.io/apimachinery/pkg/api/resource/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apimachinery/pkg/api/resource/zz_generated.deepcopy.go b/staging/src/k8s.io/apimachinery/pkg/api/resource/zz_generated.deepcopy.go index 118dfca07e1..186d9007e66 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/resource/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/resource/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -20,23 +20,6 @@ limitations under the License. package resource -import ( - conversion "k8s.io/apimachinery/pkg/conversion" - reflect "reflect" -) - -// GetGeneratedDeepCopyFuncs returns the generated funcs, since we aren't registering them. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func GetGeneratedDeepCopyFuncs() []conversion.GeneratedDeepCopyFunc { - return []conversion.GeneratedDeepCopyFunc{ - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Quantity).DeepCopyInto(out.(*Quantity)) - return nil - }, InType: reflect.TypeOf(&Quantity{})}, - } -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Quantity) DeepCopyInto(out *Quantity) { *out = in.DeepCopy() diff --git a/staging/src/k8s.io/apimachinery/pkg/api/testing/fuzzer/BUILD b/staging/src/k8s.io/apimachinery/pkg/api/testing/fuzzer/BUILD index 3b58ce2489b..00a56d25050 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/testing/fuzzer/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/api/testing/fuzzer/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["valuefuzz_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/api/testing/fuzzer", - library = ":go_default_library", ) go_library( diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validation/BUILD b/staging/src/k8s.io/apimachinery/pkg/api/validation/BUILD index 8546d1a5932..8f6cd93d4c0 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/validation/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/api/validation/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["objectmeta_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/api/validation", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validation/path/BUILD b/staging/src/k8s.io/apimachinery/pkg/api/validation/path/BUILD index e50632c5092..33546216a38 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/validation/path/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/api/validation/path/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["name_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/api/validation/path", - library = ":go_default_library", ) go_library( diff --git a/staging/src/k8s.io/apimachinery/pkg/apimachinery/BUILD b/staging/src/k8s.io/apimachinery/pkg/apimachinery/BUILD index 2c6753d141e..90d016fab84 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apimachinery/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/apimachinery/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["types_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/apimachinery", - library = ":go_default_library", deps = ["//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library"], ) diff --git a/staging/src/k8s.io/apimachinery/pkg/apimachinery/announced/BUILD b/staging/src/k8s.io/apimachinery/pkg/apimachinery/announced/BUILD index 314ddcad227..27734e8f529 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apimachinery/announced/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/apimachinery/announced/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["announced_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/apimachinery/announced", - library = ":go_default_library", deps = ["//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library"], ) diff --git a/staging/src/k8s.io/apimachinery/pkg/apimachinery/registered/BUILD b/staging/src/k8s.io/apimachinery/pkg/apimachinery/registered/BUILD index 8982b562911..873b34838c0 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apimachinery/registered/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/apimachinery/registered/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["registered_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/apimachinery/registered", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/apimachinery:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", diff --git a/staging/src/k8s.io/apimachinery/pkg/apimachinery/registered/registered.go b/staging/src/k8s.io/apimachinery/pkg/apimachinery/registered/registered.go index 5150db01659..0da94f50f60 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apimachinery/registered/registered.go +++ b/staging/src/k8s.io/apimachinery/pkg/apimachinery/registered/registered.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package to keep track of API Versions that can be registered and are enabled in api.Scheme. +// Package to keep track of API Versions that can be registered and are enabled in a Scheme. package registered import ( diff --git a/staging/src/k8s.io/apimachinery/pkg/apimachinery/types.go b/staging/src/k8s.io/apimachinery/pkg/apimachinery/types.go index 213e34bc00e..baca784fad1 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apimachinery/types.go +++ b/staging/src/k8s.io/apimachinery/pkg/apimachinery/types.go @@ -38,7 +38,7 @@ type GroupMeta struct { // to go through the InterfacesFor method below. SelfLinker runtime.SelfLinker - // RESTMapper provides the default mapping between REST paths and the objects declared in api.Scheme and all known + // RESTMapper provides the default mapping between REST paths and the objects declared in a Scheme and all known // versions. RESTMapper meta.RESTMapper diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/fuzzer/fuzzer.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/fuzzer/fuzzer.go index e5f5bd4c796..27dfb3d9e90 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/fuzzer/fuzzer.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/fuzzer/fuzzer.go @@ -18,7 +18,10 @@ package fuzzer import ( "fmt" + "math/rand" + "sort" "strconv" + "strings" "github.com/google/gofuzz" @@ -96,7 +99,80 @@ func genericFuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} { } } +// taken from gofuzz internals for RandString +type charRange struct { + first, last rune +} + +func (c *charRange) choose(r *rand.Rand) rune { + count := int64(c.last - c.first + 1) + ch := c.first + rune(r.Int63n(count)) + + return ch +} + +// randomLabelPart produces a valid random label value or name-part +// of a label key. +func randomLabelPart(c fuzz.Continue, canBeEmpty bool) string { + validStartEnd := []charRange{{'0', '9'}, {'a', 'z'}, {'A', 'Z'}} + validMiddle := []charRange{{'0', '9'}, {'a', 'z'}, {'A', 'Z'}, + {'.', '.'}, {'-', '-'}, {'_', '_'}} + + partLen := c.Rand.Intn(64) // len is [0, 63] + if !canBeEmpty { + partLen = c.Rand.Intn(63) + 1 // len is [1, 63] + } + + runes := make([]rune, partLen) + if partLen == 0 { + return string(runes) + } + + runes[0] = validStartEnd[c.Rand.Intn(len(validStartEnd))].choose(c.Rand) + for i := range runes[1:] { + runes[i+1] = validMiddle[c.Rand.Intn(len(validMiddle))].choose(c.Rand) + } + runes[len(runes)-1] = validStartEnd[c.Rand.Intn(len(validStartEnd))].choose(c.Rand) + + return string(runes) +} + +func randomDNSLabel(c fuzz.Continue) string { + validStartEnd := []charRange{{'0', '9'}, {'a', 'z'}} + validMiddle := []charRange{{'0', '9'}, {'a', 'z'}, {'-', '-'}} + + partLen := c.Rand.Intn(63) + 1 // len is [1, 63] + runes := make([]rune, partLen) + + runes[0] = validStartEnd[c.Rand.Intn(len(validStartEnd))].choose(c.Rand) + for i := range runes[1:] { + runes[i+1] = validMiddle[c.Rand.Intn(len(validMiddle))].choose(c.Rand) + } + runes[len(runes)-1] = validStartEnd[c.Rand.Intn(len(validStartEnd))].choose(c.Rand) + + return string(runes) +} + +func randomLabelKey(c fuzz.Continue) string { + namePart := randomLabelPart(c, false) + prefixPart := "" + + usePrefix := c.RandBool() + if usePrefix { + // we can fit, with dots, at most 3 labels in the 253 allotted characters + prefixPartsLen := c.Rand.Intn(2) + 1 + prefixParts := make([]string, prefixPartsLen) + for i := range prefixParts { + prefixParts[i] = randomDNSLabel(c) + } + prefixPart = strings.Join(prefixParts, ".") + "/" + } + + return prefixPart + namePart +} + func v1FuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} { + return []interface{}{ func(j *metav1.TypeMeta, c fuzz.Continue) { // We have to customize the randomization of TypeMetas because their @@ -120,6 +196,57 @@ func v1FuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} { j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10) j.SelfLink = c.RandString() }, + func(j *metav1.LabelSelector, c fuzz.Continue) { + c.FuzzNoCustom(j) + // we can't have an entirely empty selector, so force + // use of MatchExpression if necessary + if len(j.MatchLabels) == 0 && len(j.MatchExpressions) == 0 { + j.MatchExpressions = make([]metav1.LabelSelectorRequirement, c.Rand.Intn(2)+1) + } + + if j.MatchLabels != nil { + fuzzedMatchLabels := make(map[string]string, len(j.MatchLabels)) + for i := 0; i < len(j.MatchLabels); i++ { + fuzzedMatchLabels[randomLabelKey(c)] = randomLabelPart(c, true) + } + j.MatchLabels = fuzzedMatchLabels + } + + validOperators := []metav1.LabelSelectorOperator{ + metav1.LabelSelectorOpIn, + metav1.LabelSelectorOpNotIn, + metav1.LabelSelectorOpExists, + metav1.LabelSelectorOpDoesNotExist, + } + + if j.MatchExpressions != nil { + // NB: the label selector parser code sorts match expressions by key, and sorts the values, + // so we need to make sure ours are sorted as well here to preserve round-trip comparision. + // In practice, not sorting doesn't hurt anything... + + for i := range j.MatchExpressions { + req := metav1.LabelSelectorRequirement{} + c.Fuzz(&req) + req.Key = randomLabelKey(c) + req.Operator = validOperators[c.Rand.Intn(len(validOperators))] + if req.Operator == metav1.LabelSelectorOpIn || req.Operator == metav1.LabelSelectorOpNotIn { + if len(req.Values) == 0 { + // we must have some values here, so randomly choose a short length + req.Values = make([]string, c.Rand.Intn(2)+1) + } + for i := range req.Values { + req.Values[i] = randomLabelPart(c, true) + } + sort.Strings(req.Values) + } else { + req.Values = nil + } + j.MatchExpressions[i] = req + } + + sort.Slice(j.MatchExpressions, func(a, b int) bool { return j.MatchExpressions[a].Key < j.MatchExpressions[b].Key }) + } + }, } } diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/BUILD b/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/BUILD index 636707fe2dc..5f11cb1a997 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/BUILD @@ -12,8 +12,8 @@ go_test( "register_test.go", "roundtrip_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/apis/meta/internalversion", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/api/testing/roundtrip:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/fuzzer:go_default_library", diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/register_test.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/register_test.go index 8a6fefa4173..8116f807435 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/register_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/register_test.go @@ -59,11 +59,11 @@ func TestListOptions(t *testing.T) { } // verify kind registration - if gvk, unversioned, err := scheme.ObjectKind(in); err != nil || unversioned || gvk != metav1.SchemeGroupVersion.WithKind("ListOptions") { - t.Errorf("unexpected: %v %v %v", gvk, unversioned, err) + if gvks, unversioned, err := scheme.ObjectKinds(in); err != nil || unversioned || gvks[0] != metav1.SchemeGroupVersion.WithKind("ListOptions") { + t.Errorf("unexpected: %v %v %v", gvks[0], unversioned, err) } - if gvk, unversioned, err := scheme.ObjectKind(out); err != nil || unversioned || gvk != SchemeGroupVersion.WithKind("ListOptions") { - t.Errorf("unexpected: %v %v %v", gvk, unversioned, err) + if gvks, unversioned, err := scheme.ObjectKinds(out); err != nil || unversioned || gvks[0] != SchemeGroupVersion.WithKind("ListOptions") { + t.Errorf("unexpected: %v %v %v", gvks[0], unversioned, err) } actual = &metav1.ListOptions{} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/zz_generated.deepcopy.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/zz_generated.deepcopy.go index f13ff9fc50d..2bc1c3f9686 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,27 +21,9 @@ limitations under the License. package internalversion import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -// GetGeneratedDeepCopyFuncs returns the generated funcs, since we aren't registering them. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func GetGeneratedDeepCopyFuncs() []conversion.GeneratedDeepCopyFunc { - return []conversion.GeneratedDeepCopyFunc{ - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*List).DeepCopyInto(out.(*List)) - return nil - }, InType: reflect.TypeOf(&List{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ListOptions).DeepCopyInto(out.(*ListOptions)) - return nil - }, InType: reflect.TypeOf(&ListOptions{})}, - } -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *List) DeepCopyInto(out *List) { *out = *in diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/BUILD b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/BUILD index 7354f5e2fa1..c851816d782 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/BUILD @@ -18,8 +18,8 @@ go_test( "time_test.go", "types_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/apis/meta/v1", - library = ":go_default_library", deps = [ "//vendor/github.com/ghodss/yaml:go_default_library", "//vendor/github.com/json-iterator/go:go_default_library", @@ -93,3 +93,13 @@ filegroup( srcs = ["generated.proto"], visibility = ["//visibility:public"], ) + +go_test( + name = "go_default_xtest", + srcs = ["conversion_test.go"], + importpath = "k8s.io/apimachinery/pkg/apis/meta/v1_test", + deps = [ + "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + ], +) diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/conversion.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/conversion.go index 7049c9a33ba..c62f853351e 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/conversion.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/conversion.go @@ -38,6 +38,7 @@ func AddConversionFuncs(scheme *runtime.Scheme) error { Convert_intstr_IntOrString_To_intstr_IntOrString, Convert_unversioned_Time_To_unversioned_Time, + Convert_unversioned_MicroTime_To_unversioned_MicroTime, Convert_Pointer_v1_Duration_To_v1_Duration, Convert_v1_Duration_To_Pointer_v1_Duration, @@ -199,6 +200,12 @@ func Convert_v1_Duration_To_Pointer_v1_Duration(in *Duration, out **Duration, s return nil } +func Convert_unversioned_MicroTime_To_unversioned_MicroTime(in *MicroTime, out *MicroTime, s conversion.Scope) error { + // Cannot deep copy these, because time.Time has unexported fields. + *out = *in + return nil +} + // Convert_Slice_string_To_unversioned_Time allows converting a URL query parameter value func Convert_Slice_string_To_unversioned_Time(input *[]string, out *Time, s conversion.Scope) error { str := "" @@ -252,7 +259,6 @@ func Convert_map_to_unversioned_LabelSelector(in *map[string]string, out *LabelS if in == nil { return nil } - out = new(LabelSelector) for labelKey, labelValue := range *in { AddLabelToSelector(out, labelKey, labelValue) } diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/conversion_test.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/conversion_test.go new file mode 100644 index 00000000000..bc591584ef5 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/conversion_test.go @@ -0,0 +1,49 @@ +/* +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 v1_test + +import ( + "testing" + + apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestMapToLabelSelectorRoundTrip(t *testing.T) { + // We should be able to round-trip a map-only selector through LabelSelector. + inputs := []map[string]string{ + nil, + {}, + {"one": "foo"}, + {"one": "foo", "two": "bar"}, + } + for _, in := range inputs { + ls := &v1.LabelSelector{} + if err := v1.Convert_map_to_unversioned_LabelSelector(&in, ls, nil); err != nil { + t.Errorf("Convert_map_to_unversioned_LabelSelector(%#v): %v", in, err) + continue + } + out := map[string]string{} + if err := v1.Convert_unversioned_LabelSelector_to_map(ls, &out, nil); err != nil { + t.Errorf("Convert_unversioned_LabelSelector_to_map(%#v): %v", ls, err) + continue + } + if !apiequality.Semantic.DeepEqual(in, out) { + t.Errorf("map-selector conversion round-trip failed: got %v; want %v", out, in) + } + } +} 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 653b30237b9..1fa478f5aea 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 @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -50,6 +50,7 @@ limitations under the License. MicroTime ObjectMeta OwnerReference + Patch Preconditions RootPaths ServerAddressByClientCIDR @@ -196,51 +197,55 @@ func (m *OwnerReference) Reset() { *m = OwnerReference{} } func (*OwnerReference) ProtoMessage() {} func (*OwnerReference) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{24} } +func (m *Patch) Reset() { *m = Patch{} } +func (*Patch) ProtoMessage() {} +func (*Patch) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{25} } + func (m *Preconditions) Reset() { *m = Preconditions{} } func (*Preconditions) ProtoMessage() {} -func (*Preconditions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{25} } +func (*Preconditions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{26} } func (m *RootPaths) Reset() { *m = RootPaths{} } func (*RootPaths) ProtoMessage() {} -func (*RootPaths) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{26} } +func (*RootPaths) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{27} } func (m *ServerAddressByClientCIDR) Reset() { *m = ServerAddressByClientCIDR{} } func (*ServerAddressByClientCIDR) ProtoMessage() {} func (*ServerAddressByClientCIDR) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{27} + return fileDescriptorGenerated, []int{28} } func (m *Status) Reset() { *m = Status{} } func (*Status) ProtoMessage() {} -func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{28} } +func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{29} } func (m *StatusCause) Reset() { *m = StatusCause{} } func (*StatusCause) ProtoMessage() {} -func (*StatusCause) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{29} } +func (*StatusCause) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{30} } func (m *StatusDetails) Reset() { *m = StatusDetails{} } func (*StatusDetails) ProtoMessage() {} -func (*StatusDetails) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{30} } +func (*StatusDetails) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{31} } func (m *Time) Reset() { *m = Time{} } func (*Time) ProtoMessage() {} -func (*Time) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{31} } +func (*Time) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{32} } func (m *Timestamp) Reset() { *m = Timestamp{} } func (*Timestamp) ProtoMessage() {} -func (*Timestamp) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{32} } +func (*Timestamp) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{33} } func (m *TypeMeta) Reset() { *m = TypeMeta{} } func (*TypeMeta) ProtoMessage() {} -func (*TypeMeta) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{33} } +func (*TypeMeta) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{34} } func (m *Verbs) Reset() { *m = Verbs{} } func (*Verbs) ProtoMessage() {} -func (*Verbs) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{34} } +func (*Verbs) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{35} } func (m *WatchEvent) Reset() { *m = WatchEvent{} } func (*WatchEvent) ProtoMessage() {} -func (*WatchEvent) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{35} } +func (*WatchEvent) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{36} } func init() { proto.RegisterType((*APIGroup)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.APIGroup") @@ -268,6 +273,7 @@ func init() { proto.RegisterType((*MicroTime)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.MicroTime") proto.RegisterType((*ObjectMeta)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta") proto.RegisterType((*OwnerReference)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.OwnerReference") + proto.RegisterType((*Patch)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.Patch") proto.RegisterType((*Preconditions)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.Preconditions") proto.RegisterType((*RootPaths)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.RootPaths") proto.RegisterType((*ServerAddressByClientCIDR)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.ServerAddressByClientCIDR") @@ -1317,6 +1323,24 @@ func (m *OwnerReference) MarshalTo(dAtA []byte) (int, error) { return i, nil } +func (m *Patch) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Patch) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + func (m *Preconditions) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -2065,6 +2089,12 @@ func (m *OwnerReference) Size() (n int) { return n } +func (m *Patch) Size() (n int) { + var l int + _ = l + return n +} + func (m *Preconditions) Size() (n int) { var l int _ = l @@ -2464,6 +2494,15 @@ func (this *OwnerReference) String() string { }, "") return s } +func (this *Patch) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&Patch{`, + `}`, + }, "") + return s +} func (this *Preconditions) String() string { if this == nil { return "nil" @@ -6382,6 +6421,56 @@ func (m *OwnerReference) Unmarshal(dAtA []byte) error { } return nil } +func (m *Patch) 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: Patch: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Patch: 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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *Preconditions) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -7715,7 +7804,7 @@ func init() { } var fileDescriptorGenerated = []byte{ - // 2428 bytes of a gzipped FileDescriptorProto + // 2435 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x59, 0x4d, 0x6c, 0x23, 0x49, 0x15, 0x4e, 0xdb, 0xb1, 0x63, 0x3f, 0xc7, 0xf9, 0xa9, 0xcd, 0x80, 0x37, 0x02, 0x3b, 0xdb, 0x8b, 0x56, 0x59, 0x98, 0xb5, 0x49, 0x16, 0x56, 0xc3, 0x00, 0x03, 0xe9, 0x38, 0x33, 0x8a, 0x76, 0x32, @@ -7723,7 +7812,7 @@ var fileDescriptorGenerated = []byte{ 0x33, 0x09, 0x1c, 0xd8, 0x03, 0x48, 0x1c, 0x10, 0x9a, 0x23, 0x27, 0xb4, 0x23, 0xb8, 0x70, 0xe5, 0xc4, 0x05, 0x4e, 0x48, 0xcc, 0x71, 0x24, 0x2e, 0x7b, 0x40, 0xd6, 0x8e, 0xf7, 0xc0, 0x09, 0x71, 0xcf, 0x09, 0x55, 0x75, 0xf5, 0x9f, 0x1d, 0x4f, 0xda, 0x3b, 0x0b, 0xe2, 0x14, 0xf7, 0xfb, 0xf9, - 0xde, 0xab, 0xaa, 0xf7, 0x5e, 0xbd, 0x7a, 0x81, 0xbd, 0xe3, 0x6b, 0xac, 0x6e, 0x7b, 0x8d, 0xe3, + 0xde, 0xab, 0x57, 0xaf, 0x5e, 0xbd, 0x7a, 0x81, 0xbd, 0xe3, 0x6b, 0xac, 0x6e, 0x7b, 0x8d, 0xe3, 0xfe, 0x01, 0xa1, 0x2e, 0xe1, 0x84, 0x35, 0x4e, 0x88, 0xdb, 0xf6, 0x68, 0x43, 0x31, 0xcc, 0x9e, 0xdd, 0x35, 0xad, 0x23, 0xdb, 0x25, 0xf4, 0xac, 0xd1, 0x3b, 0xee, 0x08, 0x02, 0x6b, 0x74, 0x09, 0x37, 0x1b, 0x27, 0x1b, 0x8d, 0x0e, 0x71, 0x09, 0x35, 0x39, 0x69, 0xd7, 0x7b, 0xd4, 0xe3, 0x1e, @@ -7735,137 +7824,138 @@ var fileDescriptorGenerated = []byte{ 0xff, 0x96, 0x85, 0xc2, 0x56, 0x6b, 0xf7, 0x16, 0xf5, 0xfa, 0x3d, 0xb4, 0x06, 0xb3, 0xae, 0xd9, 0x25, 0x15, 0x6d, 0x4d, 0x5b, 0x2f, 0x1a, 0xf3, 0x4f, 0x07, 0xb5, 0x99, 0xe1, 0xa0, 0x36, 0x7b, 0xc7, 0xec, 0x12, 0x2c, 0x39, 0xc8, 0x81, 0xc2, 0x09, 0xa1, 0xcc, 0xf6, 0x5c, 0x56, 0xc9, 0xac, - 0x65, 0xd7, 0x4b, 0x9b, 0x37, 0xea, 0x69, 0x36, 0xad, 0x2e, 0x0d, 0xdc, 0xf7, 0x55, 0x6f, 0x7a, - 0xb4, 0x69, 0x33, 0xcb, 0x3b, 0x21, 0xf4, 0xcc, 0x58, 0x52, 0x56, 0x0a, 0x8a, 0xc9, 0x70, 0x68, - 0x01, 0xfd, 0x5c, 0x83, 0xa5, 0x1e, 0x25, 0x87, 0x84, 0x52, 0xd2, 0x56, 0xfc, 0x4a, 0x76, 0x4d, - 0xfb, 0x0c, 0xcc, 0x56, 0x94, 0xd9, 0xa5, 0xd6, 0x08, 0x3e, 0x1e, 0xb3, 0x88, 0x7e, 0xa7, 0xc1, - 0x2a, 0x23, 0xf4, 0x84, 0xd0, 0xad, 0x76, 0x9b, 0x12, 0xc6, 0x8c, 0xb3, 0x6d, 0xc7, 0x26, 0x2e, - 0xdf, 0xde, 0x6d, 0x62, 0x56, 0x99, 0x95, 0xfb, 0xf0, 0x9d, 0x74, 0x0e, 0xed, 0x4f, 0xc2, 0x31, - 0x74, 0xe5, 0xd1, 0xea, 0x44, 0x11, 0x86, 0x5f, 0xe0, 0x86, 0x7e, 0x08, 0xf3, 0xc1, 0x41, 0xde, - 0xb6, 0x19, 0x47, 0xf7, 0x21, 0xdf, 0x11, 0x1f, 0xac, 0xa2, 0x49, 0x07, 0xeb, 0xe9, 0x1c, 0x0c, - 0x30, 0x8c, 0x05, 0xe5, 0x4f, 0x5e, 0x7e, 0x32, 0xac, 0xd0, 0xf4, 0x3f, 0x67, 0xa1, 0xb4, 0xd5, - 0xda, 0xc5, 0x84, 0x79, 0x7d, 0x6a, 0x91, 0x14, 0x41, 0xb3, 0x09, 0x20, 0xfe, 0xb2, 0x9e, 0x69, - 0x91, 0x76, 0x25, 0xb3, 0xa6, 0xad, 0x17, 0x0c, 0xa4, 0xe4, 0xe0, 0x4e, 0xc8, 0xc1, 0x31, 0x29, - 0x81, 0x7a, 0x6c, 0xbb, 0x6d, 0x79, 0xda, 0x31, 0xd4, 0x77, 0x6d, 0xb7, 0x8d, 0x25, 0x07, 0xdd, - 0x86, 0xdc, 0x09, 0xa1, 0x07, 0x62, 0xff, 0x45, 0x40, 0x7c, 0x25, 0xdd, 0xf2, 0xee, 0x0b, 0x15, - 0xa3, 0x38, 0x1c, 0xd4, 0x72, 0xf2, 0x27, 0xf6, 0x41, 0x50, 0x1d, 0x80, 0x1d, 0x79, 0x94, 0x4b, - 0x77, 0x2a, 0xb9, 0xb5, 0xec, 0x7a, 0xd1, 0x58, 0x10, 0xfe, 0xed, 0x87, 0x54, 0x1c, 0x93, 0x40, - 0xd7, 0x60, 0x9e, 0xd9, 0x6e, 0xa7, 0xef, 0x98, 0x54, 0x10, 0x2a, 0x79, 0xe9, 0xe7, 0x8a, 0xf2, - 0x73, 0x7e, 0x3f, 0xc6, 0xc3, 0x09, 0x49, 0x61, 0xc9, 0x32, 0x39, 0xe9, 0x78, 0xd4, 0x26, 0xac, - 0x32, 0x17, 0x59, 0xda, 0x0e, 0xa9, 0x38, 0x26, 0x81, 0x5e, 0x87, 0x9c, 0xdc, 0xf9, 0x4a, 0x41, - 0x9a, 0x28, 0x2b, 0x13, 0x39, 0x79, 0x2c, 0xd8, 0xe7, 0xa1, 0x37, 0x61, 0x4e, 0x65, 0x4d, 0xa5, - 0x28, 0xc5, 0x16, 0x95, 0xd8, 0x5c, 0x10, 0xd6, 0x01, 0x5f, 0xff, 0xa3, 0x06, 0x8b, 0xb1, 0xf3, - 0x93, 0xb1, 0x72, 0x0d, 0xe6, 0x3b, 0xb1, 0x4c, 0x51, 0x67, 0x19, 0xae, 0x26, 0x9e, 0x45, 0x38, - 0x21, 0x89, 0x08, 0x14, 0xa9, 0x42, 0x0a, 0x2a, 0xc2, 0x46, 0xea, 0x40, 0x0b, 0x7c, 0x88, 0x2c, - 0xc5, 0x88, 0x0c, 0x47, 0xc8, 0xfa, 0x3f, 0x35, 0x19, 0x74, 0x41, 0x8d, 0x40, 0xeb, 0xb1, 0x3a, - 0xa4, 0xc9, 0x2d, 0x9c, 0x9f, 0x50, 0x43, 0x2e, 0x49, 0xde, 0xcc, 0xff, 0x45, 0xf2, 0x5e, 0x2f, + 0x65, 0xd7, 0x4b, 0x9b, 0x37, 0xea, 0x69, 0x82, 0x56, 0x97, 0x06, 0xee, 0xfb, 0xaa, 0x37, 0x3d, + 0xda, 0xb4, 0x99, 0xe5, 0x9d, 0x10, 0x7a, 0x66, 0x2c, 0x29, 0x2b, 0x05, 0xc5, 0x64, 0x38, 0xb4, + 0x80, 0x7e, 0xae, 0xc1, 0x52, 0x8f, 0x92, 0x43, 0x42, 0x29, 0x69, 0x2b, 0x7e, 0x25, 0xbb, 0xa6, + 0x7d, 0x06, 0x66, 0x2b, 0xca, 0xec, 0x52, 0x6b, 0x04, 0x1f, 0x8f, 0x59, 0x44, 0xbf, 0xd3, 0x60, + 0x95, 0x11, 0x7a, 0x42, 0xe8, 0x56, 0xbb, 0x4d, 0x09, 0x63, 0xc6, 0xd9, 0xb6, 0x63, 0x13, 0x97, + 0x6f, 0xef, 0x36, 0x31, 0xab, 0xcc, 0xca, 0x38, 0x7c, 0x27, 0x9d, 0x43, 0xfb, 0x93, 0x70, 0x0c, + 0x5d, 0x79, 0xb4, 0x3a, 0x51, 0x84, 0xe1, 0x17, 0xb8, 0xa1, 0x1f, 0xc2, 0x7c, 0xb0, 0x91, 0xb7, + 0x6d, 0xc6, 0xd1, 0x7d, 0xc8, 0x77, 0xc4, 0x07, 0xab, 0x68, 0xd2, 0xc1, 0x7a, 0x3a, 0x07, 0x03, + 0x0c, 0x63, 0x41, 0xf9, 0x93, 0x97, 0x9f, 0x0c, 0x2b, 0x34, 0xfd, 0xcf, 0x59, 0x28, 0x6d, 0xb5, + 0x76, 0x31, 0x61, 0x5e, 0x9f, 0x5a, 0x24, 0x45, 0xd2, 0x6c, 0x02, 0x88, 0xbf, 0xac, 0x67, 0x5a, + 0xa4, 0x5d, 0xc9, 0xac, 0x69, 0xeb, 0x05, 0x03, 0x29, 0x39, 0xb8, 0x13, 0x72, 0x70, 0x4c, 0x4a, + 0xa0, 0x1e, 0xdb, 0x6e, 0x5b, 0xee, 0x76, 0x0c, 0xf5, 0x5d, 0xdb, 0x6d, 0x63, 0xc9, 0x41, 0xb7, + 0x21, 0x77, 0x42, 0xe8, 0x81, 0x88, 0xbf, 0x48, 0x88, 0xaf, 0xa4, 0x5b, 0xde, 0x7d, 0xa1, 0x62, + 0x14, 0x87, 0x83, 0x5a, 0x4e, 0xfe, 0xc4, 0x3e, 0x08, 0xaa, 0x03, 0xb0, 0x23, 0x8f, 0x72, 0xe9, + 0x4e, 0x25, 0xb7, 0x96, 0x5d, 0x2f, 0x1a, 0x0b, 0xc2, 0xbf, 0xfd, 0x90, 0x8a, 0x63, 0x12, 0xe8, + 0x1a, 0xcc, 0x33, 0xdb, 0xed, 0xf4, 0x1d, 0x93, 0x0a, 0x42, 0x25, 0x2f, 0xfd, 0x5c, 0x51, 0x7e, + 0xce, 0xef, 0xc7, 0x78, 0x38, 0x21, 0x29, 0x2c, 0x59, 0x26, 0x27, 0x1d, 0x8f, 0xda, 0x84, 0x55, + 0xe6, 0x22, 0x4b, 0xdb, 0x21, 0x15, 0xc7, 0x24, 0xd0, 0xeb, 0x90, 0x93, 0x91, 0xaf, 0x14, 0xa4, + 0x89, 0xb2, 0x32, 0x91, 0x93, 0xdb, 0x82, 0x7d, 0x1e, 0x7a, 0x13, 0xe6, 0xd4, 0xa9, 0xa9, 0x14, + 0xa5, 0xd8, 0xa2, 0x12, 0x9b, 0x0b, 0xd2, 0x3a, 0xe0, 0xeb, 0x7f, 0xd4, 0x60, 0x31, 0xb6, 0x7f, + 0x32, 0x57, 0xae, 0xc1, 0x7c, 0x27, 0x76, 0x52, 0xd4, 0x5e, 0x86, 0xab, 0x89, 0x9f, 0x22, 0x9c, + 0x90, 0x44, 0x04, 0x8a, 0x54, 0x21, 0x05, 0x15, 0x61, 0x23, 0x75, 0xa2, 0x05, 0x3e, 0x44, 0x96, + 0x62, 0x44, 0x86, 0x23, 0x64, 0xfd, 0x9f, 0x9a, 0x4c, 0xba, 0xa0, 0x46, 0xa0, 0xf5, 0x58, 0x1d, + 0xd2, 0x64, 0x08, 0xe7, 0x27, 0xd4, 0x90, 0x4b, 0x0e, 0x6f, 0xe6, 0xff, 0xe2, 0xf0, 0x5e, 0x2f, 0xfc, 0xe6, 0xc3, 0xda, 0xcc, 0x07, 0xff, 0x58, 0x9b, 0xd1, 0x3f, 0xc9, 0x40, 0xb9, 0x49, 0x1c, 0xc2, 0xc9, 0xdd, 0x1e, 0x97, 0x2b, 0xb8, 0x09, 0xa8, 0x43, 0x4d, 0x8b, 0xb4, 0x08, 0xb5, 0xbd, - 0xf6, 0x3e, 0xb1, 0x3c, 0xb7, 0xcd, 0xe4, 0x11, 0x65, 0x8d, 0xcf, 0x0d, 0x07, 0x35, 0x74, 0x6b, - 0x8c, 0x8b, 0x2f, 0xd0, 0x40, 0x0e, 0x94, 0x7b, 0x54, 0xfe, 0xb6, 0xb9, 0x2a, 0xe0, 0x22, 0x71, - 0xde, 0x4e, 0xb7, 0xf6, 0x56, 0x5c, 0xd5, 0x58, 0x1e, 0x0e, 0x6a, 0xe5, 0x04, 0x09, 0x27, 0xc1, - 0xd1, 0x77, 0x61, 0xc9, 0xa3, 0xbd, 0x23, 0xd3, 0x6d, 0x92, 0x1e, 0x71, 0xdb, 0xc4, 0xe5, 0x4c, - 0x26, 0x73, 0xc1, 0x58, 0x11, 0x65, 0xf7, 0xee, 0x08, 0x0f, 0x8f, 0x49, 0xa3, 0x07, 0xb0, 0xdc, - 0xa3, 0x5e, 0xcf, 0xec, 0x98, 0x02, 0xb1, 0xe5, 0x39, 0xb6, 0x75, 0x26, 0x93, 0xbd, 0x68, 0x5c, - 0x1d, 0x0e, 0x6a, 0xcb, 0xad, 0x51, 0xe6, 0xf9, 0xa0, 0xf6, 0x8a, 0xdc, 0x3a, 0x41, 0x89, 0x98, - 0x78, 0x1c, 0x46, 0xdf, 0x85, 0x42, 0xb3, 0x4f, 0x25, 0x05, 0x7d, 0x1b, 0x0a, 0x6d, 0xf5, 0x5b, - 0xed, 0xea, 0x6b, 0xc1, 0x9d, 0x14, 0xc8, 0x9c, 0x0f, 0x6a, 0x65, 0x71, 0xf5, 0xd6, 0x03, 0x02, - 0x0e, 0x55, 0xf4, 0x87, 0x50, 0xde, 0x39, 0xed, 0x79, 0x94, 0x07, 0xe7, 0xf5, 0x06, 0xe4, 0x89, - 0x24, 0x48, 0xb4, 0x42, 0x54, 0x48, 0x7d, 0x31, 0xac, 0xb8, 0x22, 0xb1, 0xc9, 0xa9, 0x69, 0x71, - 0x55, 0x11, 0xc3, 0xc4, 0xde, 0x11, 0x44, 0xec, 0xf3, 0xf4, 0x27, 0x1a, 0xc0, 0x2d, 0x12, 0x62, - 0x6f, 0xc1, 0x62, 0x90, 0x14, 0xc9, 0x5c, 0xfd, 0xbc, 0xd2, 0x5e, 0xc4, 0x49, 0x36, 0x1e, 0x95, - 0x47, 0x2d, 0x58, 0xb1, 0x5d, 0xcb, 0xe9, 0xb7, 0xc9, 0x3d, 0xd7, 0x76, 0x6d, 0x6e, 0x9b, 0x8e, - 0xfd, 0x93, 0xb0, 0x2e, 0x7f, 0x41, 0xe1, 0xac, 0xec, 0x5e, 0x20, 0x83, 0x2f, 0xd4, 0xd4, 0x1f, - 0x42, 0x51, 0x56, 0x08, 0x51, 0x9c, 0xa3, 0x72, 0xa5, 0xbd, 0xa0, 0x5c, 0x05, 0xd5, 0x3d, 0x33, - 0xa9, 0xba, 0xc7, 0x12, 0xc2, 0x81, 0xb2, 0xaf, 0x1b, 0x5c, 0x38, 0xa9, 0x2c, 0x5c, 0x85, 0x42, - 0xb0, 0x70, 0x65, 0x25, 0x6c, 0x34, 0x02, 0x20, 0x1c, 0x4a, 0xc4, 0xac, 0x1d, 0x41, 0xa2, 0xda, - 0xa5, 0x33, 0x16, 0xab, 0xbe, 0x99, 0x17, 0x57, 0xdf, 0x98, 0xa5, 0x9f, 0x41, 0x65, 0x52, 0x77, - 0xf2, 0x12, 0xf5, 0x38, 0xbd, 0x2b, 0xfa, 0xaf, 0x35, 0x58, 0x8a, 0x23, 0xa5, 0x3f, 0xbe, 0xf4, - 0x46, 0x2e, 0xbf, 0xc7, 0x63, 0x3b, 0xf2, 0x5b, 0x0d, 0x56, 0x12, 0x4b, 0x9b, 0xea, 0xc4, 0xa7, - 0x70, 0x2a, 0x1e, 0x1c, 0xd9, 0x29, 0x82, 0xa3, 0x01, 0xa5, 0xdd, 0x30, 0xee, 0xe9, 0xe5, 0x9d, - 0x8f, 0xfe, 0x17, 0x0d, 0xe6, 0x63, 0x1a, 0x0c, 0x3d, 0x84, 0x39, 0x51, 0xdf, 0x6c, 0xb7, 0xa3, - 0xba, 0xb2, 0x94, 0x97, 0x65, 0x0c, 0x24, 0x5a, 0x57, 0xcb, 0x47, 0xc2, 0x01, 0x24, 0x6a, 0x41, - 0x9e, 0x12, 0xd6, 0x77, 0xb8, 0x2a, 0xed, 0x57, 0x53, 0x5e, 0x6b, 0xdc, 0xe4, 0x7d, 0x66, 0x80, - 0xa8, 0x51, 0x58, 0xea, 0x63, 0x85, 0xa3, 0xff, 0x3d, 0x03, 0xe5, 0xdb, 0xe6, 0x01, 0x71, 0xf6, - 0x89, 0x43, 0x2c, 0xee, 0x51, 0xf4, 0x53, 0x28, 0x75, 0x4d, 0x6e, 0x1d, 0x49, 0x6a, 0xd0, 0x5b, - 0x36, 0xd3, 0x19, 0x4a, 0x20, 0xd5, 0xf7, 0x22, 0x98, 0x1d, 0x97, 0xd3, 0x33, 0xe3, 0x15, 0xb5, - 0xb0, 0x52, 0x8c, 0x83, 0xe3, 0xd6, 0xe4, 0x83, 0x40, 0x7e, 0xef, 0x9c, 0xf6, 0xc4, 0x25, 0x3a, - 0xfd, 0x3b, 0x24, 0xe1, 0x02, 0x26, 0xef, 0xf7, 0x6d, 0x4a, 0xba, 0xc4, 0xe5, 0xd1, 0x83, 0x60, - 0x6f, 0x04, 0x1f, 0x8f, 0x59, 0x5c, 0xbd, 0x01, 0x4b, 0xa3, 0xce, 0xa3, 0x25, 0xc8, 0x1e, 0x93, - 0x33, 0x3f, 0x16, 0xb0, 0xf8, 0x89, 0x56, 0x20, 0x77, 0x62, 0x3a, 0x7d, 0x55, 0x7f, 0xb0, 0xff, - 0x71, 0x3d, 0x73, 0x4d, 0xd3, 0x7f, 0xaf, 0x41, 0x65, 0x92, 0x23, 0xe8, 0x8b, 0x31, 0x20, 0xa3, - 0xa4, 0xbc, 0xca, 0xbe, 0x4b, 0xce, 0x7c, 0xd4, 0x1d, 0x28, 0x78, 0x3d, 0xf1, 0x84, 0xf3, 0xa8, - 0x8a, 0xf3, 0x37, 0x83, 0xd8, 0xbd, 0xab, 0xe8, 0xe7, 0x83, 0xda, 0x95, 0x04, 0x7c, 0xc0, 0xc0, - 0xa1, 0x2a, 0xd2, 0x21, 0x2f, 0xfd, 0x11, 0x97, 0xb2, 0x68, 0x9f, 0xe4, 0xe1, 0xdf, 0x97, 0x14, - 0xac, 0x38, 0xfa, 0x9f, 0x34, 0x98, 0x95, 0xed, 0xe1, 0x43, 0x28, 0x88, 0xfd, 0x6b, 0x9b, 0xdc, - 0x94, 0x7e, 0xa5, 0x7e, 0x4c, 0x08, 0xed, 0x3d, 0xc2, 0xcd, 0x28, 0xbf, 0x02, 0x0a, 0x0e, 0x11, - 0x11, 0x86, 0x9c, 0xcd, 0x49, 0x37, 0x38, 0xc8, 0xb7, 0x26, 0x42, 0xab, 0xf7, 0x6f, 0x1d, 0x9b, - 0x8f, 0x76, 0x4e, 0x39, 0x71, 0xc5, 0x61, 0x44, 0xc5, 0x60, 0x57, 0x60, 0x60, 0x1f, 0x4a, 0xff, - 0x83, 0x06, 0xa1, 0x29, 0x91, 0xee, 0x8c, 0x38, 0x87, 0xb7, 0x6d, 0xf7, 0x58, 0x6d, 0x6b, 0xe8, - 0xce, 0xbe, 0xa2, 0xe3, 0x50, 0xe2, 0xa2, 0x2b, 0x36, 0x33, 0xe5, 0x15, 0x7b, 0x15, 0x0a, 0x96, - 0xe7, 0x72, 0xdb, 0xed, 0x8f, 0xd5, 0x97, 0x6d, 0x45, 0xc7, 0xa1, 0x84, 0xfe, 0x2c, 0x0b, 0x25, - 0xe1, 0x6b, 0x70, 0xc7, 0x7f, 0x13, 0xca, 0x4e, 0xfc, 0xf4, 0x94, 0xcf, 0x57, 0x14, 0x44, 0x32, - 0x1f, 0x71, 0x52, 0x56, 0x28, 0x1f, 0xda, 0xc4, 0x69, 0x87, 0xca, 0x99, 0xa4, 0xf2, 0xcd, 0x38, - 0x13, 0x27, 0x65, 0x45, 0x9d, 0x7d, 0x24, 0xe2, 0x5a, 0x35, 0x6a, 0xe1, 0xd6, 0x7e, 0x4f, 0x10, - 0xb1, 0xcf, 0xbb, 0x68, 0x7f, 0x66, 0xa7, 0xdc, 0x9f, 0xeb, 0xb0, 0x20, 0x0e, 0xd2, 0xeb, 0xf3, - 0xa0, 0x9b, 0xcd, 0xc9, 0xbe, 0x0b, 0x0d, 0x07, 0xb5, 0x85, 0xf7, 0x12, 0x1c, 0x3c, 0x22, 0x39, - 0xb1, 0x7d, 0xc9, 0x7f, 0xda, 0xf6, 0x45, 0xac, 0xda, 0xb1, 0xbb, 0x36, 0xaf, 0xcc, 0x49, 0x27, - 0xc2, 0x55, 0xdf, 0x16, 0x44, 0xec, 0xf3, 0x12, 0x47, 0x5a, 0xb8, 0xf4, 0x48, 0xdf, 0x87, 0xe2, - 0x9e, 0x6d, 0x51, 0x4f, 0xac, 0x45, 0x5c, 0x4c, 0x2c, 0xd1, 0xb4, 0x87, 0x05, 0x3c, 0x58, 0x63, - 0xc0, 0x17, 0xae, 0xb8, 0xa6, 0xeb, 0xf9, 0xad, 0x79, 0x2e, 0x72, 0xe5, 0x8e, 0x20, 0x62, 0x9f, - 0x77, 0x7d, 0x45, 0xdc, 0x47, 0xbf, 0x7c, 0x52, 0x9b, 0x79, 0xfc, 0xa4, 0x36, 0xf3, 0xe1, 0x13, - 0x75, 0x37, 0xfd, 0x0b, 0x00, 0xee, 0x1e, 0xfc, 0x98, 0x58, 0x7e, 0xcc, 0x5f, 0xfe, 0x2a, 0x17, - 0x3d, 0x86, 0x1a, 0x06, 0xc9, 0x17, 0x6c, 0x66, 0xa4, 0xc7, 0x88, 0xf1, 0x70, 0x42, 0x12, 0x35, - 0xa0, 0x18, 0xbe, 0xd4, 0x55, 0x7c, 0x2f, 0x2b, 0xb5, 0x62, 0xf8, 0x9c, 0xc7, 0x91, 0x4c, 0x22, - 0x01, 0x67, 0x2f, 0x4d, 0x40, 0x03, 0xb2, 0x7d, 0xbb, 0x2d, 0x43, 0xa2, 0x68, 0x7c, 0x35, 0x28, - 0x80, 0xf7, 0x76, 0x9b, 0xe7, 0x83, 0xda, 0x6b, 0x93, 0x66, 0x5c, 0xfc, 0xac, 0x47, 0x58, 0xfd, - 0xde, 0x6e, 0x13, 0x0b, 0xe5, 0x8b, 0x82, 0x34, 0x3f, 0x65, 0x90, 0x6e, 0x02, 0xa8, 0x55, 0x0b, - 0x6d, 0x3f, 0x36, 0xc2, 0xa9, 0xc5, 0xad, 0x90, 0x83, 0x63, 0x52, 0x88, 0xc1, 0xb2, 0x45, 0x89, - 0xfc, 0x2d, 0x8e, 0x9e, 0x71, 0xb3, 0xeb, 0xbf, 0xdb, 0x4b, 0x9b, 0x5f, 0x4e, 0x57, 0x31, 0x85, - 0x9a, 0xf1, 0xaa, 0x32, 0xb3, 0xbc, 0x3d, 0x0a, 0x86, 0xc7, 0xf1, 0x91, 0x07, 0xcb, 0x6d, 0xf5, - 0xea, 0x89, 0x8c, 0x16, 0xa7, 0x36, 0x7a, 0x45, 0x18, 0x6c, 0x8e, 0x02, 0xe1, 0x71, 0x6c, 0xf4, - 0x43, 0x58, 0x0d, 0x88, 0xe3, 0x4f, 0xcf, 0x0a, 0xc8, 0x9d, 0xaa, 0x8a, 0xc7, 0x70, 0x73, 0xa2, - 0x14, 0x7e, 0x01, 0x02, 0x6a, 0x43, 0xde, 0xf1, 0xbb, 0x8b, 0x92, 0xbc, 0x11, 0xbe, 0x95, 0x6e, - 0x15, 0x51, 0xf4, 0xd7, 0xe3, 0x5d, 0x45, 0xf8, 0xfc, 0x52, 0x0d, 0x85, 0xc2, 0x46, 0xa7, 0x50, - 0x32, 0x5d, 0xd7, 0xe3, 0xa6, 0xff, 0x18, 0x9e, 0x97, 0xa6, 0xb6, 0xa6, 0x36, 0xb5, 0x15, 0x61, - 0x8c, 0x74, 0x31, 0x31, 0x0e, 0x8e, 0x9b, 0x42, 0x8f, 0x60, 0xd1, 0x7b, 0xe4, 0x12, 0x8a, 0xc9, - 0x21, 0xa1, 0xc4, 0xb5, 0x08, 0xab, 0x94, 0xa5, 0xf5, 0xaf, 0xa5, 0xb4, 0x9e, 0x50, 0x8e, 0x42, - 0x3a, 0x49, 0x67, 0x78, 0xd4, 0x0a, 0xaa, 0x03, 0x1c, 0xda, 0xae, 0xea, 0x45, 0x2b, 0x0b, 0xd1, - 0xe8, 0xe9, 0x66, 0x48, 0xc5, 0x31, 0x09, 0xf4, 0x75, 0x28, 0x59, 0x4e, 0x9f, 0x71, 0xe2, 0xcf, - 0xb8, 0x16, 0x65, 0x06, 0x85, 0xeb, 0xdb, 0x8e, 0x58, 0x38, 0x2e, 0x87, 0x8e, 0x60, 0xde, 0x8e, - 0x35, 0xbd, 0x95, 0x25, 0x19, 0x8b, 0x9b, 0x53, 0x77, 0xba, 0xcc, 0x58, 0x12, 0x95, 0x28, 0x4e, - 0xc1, 0x09, 0xe4, 0xd5, 0x6f, 0x40, 0xe9, 0x53, 0xf6, 0x60, 0xa2, 0x87, 0x1b, 0x3d, 0xba, 0xa9, - 0x7a, 0xb8, 0xbf, 0x66, 0x60, 0x21, 0xb9, 0xe1, 0xe1, 0x5b, 0x47, 0x9b, 0x38, 0xb3, 0x0c, 0xaa, - 0x72, 0x76, 0x62, 0x55, 0x56, 0xc5, 0x6f, 0xf6, 0x65, 0x8a, 0xdf, 0x26, 0x80, 0xd9, 0xb3, 0x83, - 0xba, 0xe7, 0xd7, 0xd1, 0xb0, 0x72, 0x45, 0x53, 0x34, 0x1c, 0x93, 0x92, 0x53, 0x49, 0xcf, 0xe5, - 0xd4, 0x73, 0x1c, 0x42, 0xd5, 0x65, 0xea, 0x4f, 0x25, 0x43, 0x2a, 0x8e, 0x49, 0xa0, 0x9b, 0x80, - 0x0e, 0x1c, 0xcf, 0x3a, 0x96, 0x5b, 0x10, 0xe4, 0xb9, 0xac, 0x92, 0x05, 0x7f, 0x28, 0x65, 0x8c, - 0x71, 0xf1, 0x05, 0x1a, 0xfa, 0x5d, 0x48, 0x8e, 0x91, 0xd0, 0x0d, 0x7f, 0x03, 0xb4, 0x70, 0xce, - 0x33, 0xdd, 0xe2, 0xf5, 0xab, 0x50, 0xc4, 0x9e, 0xc7, 0x5b, 0x26, 0x3f, 0x62, 0xa8, 0x06, 0xb9, - 0x9e, 0xf8, 0xa1, 0x66, 0x84, 0x72, 0xec, 0x2b, 0x39, 0xd8, 0xa7, 0xeb, 0xbf, 0xd2, 0xe0, 0xd5, - 0x89, 0x23, 0x3b, 0xb1, 0x91, 0x56, 0xf8, 0xa5, 0x5c, 0x0a, 0x37, 0x32, 0x92, 0xc3, 0x31, 0x29, - 0xd1, 0x80, 0x25, 0xe6, 0x7c, 0xa3, 0x0d, 0x58, 0xc2, 0x1a, 0x4e, 0xca, 0xea, 0xff, 0xce, 0x40, - 0xde, 0x7f, 0x8d, 0xfd, 0x97, 0x7b, 0xee, 0x37, 0x20, 0xcf, 0xa4, 0x1d, 0xe5, 0x5e, 0x58, 0x24, - 0x7d, 0xeb, 0x58, 0x71, 0x45, 0xef, 0xd2, 0x25, 0x8c, 0x99, 0x9d, 0x20, 0x66, 0xc3, 0xde, 0x65, - 0xcf, 0x27, 0xe3, 0x80, 0x8f, 0xde, 0x11, 0x8f, 0x4f, 0x93, 0x85, 0xed, 0x60, 0x35, 0x80, 0xc4, - 0x92, 0x7a, 0x3e, 0xa8, 0xcd, 0x2b, 0x70, 0xf9, 0x8d, 0x95, 0x34, 0x7a, 0x00, 0x73, 0x6d, 0xc2, - 0x4d, 0xdb, 0xf1, 0xbb, 0xc0, 0xd4, 0x03, 0x49, 0x1f, 0xac, 0xe9, 0xab, 0x1a, 0x25, 0xe1, 0x93, - 0xfa, 0xc0, 0x01, 0xa0, 0xc8, 0x37, 0xcb, 0x6b, 0xfb, 0xd3, 0xf9, 0x5c, 0x94, 0x6f, 0xdb, 0x5e, - 0x9b, 0x60, 0xc9, 0xd1, 0x1f, 0x6b, 0x50, 0xf2, 0x91, 0xb6, 0xcd, 0x3e, 0x23, 0x68, 0x23, 0x5c, - 0x85, 0x7f, 0xdc, 0xc1, 0x55, 0x3c, 0xfb, 0xde, 0x59, 0x8f, 0x9c, 0x0f, 0x6a, 0x45, 0x29, 0x26, - 0x3e, 0xc2, 0x05, 0xc4, 0xf6, 0x28, 0x73, 0xc9, 0x1e, 0xbd, 0x0e, 0x39, 0xd9, 0x71, 0xab, 0xcd, - 0x0c, 0xfb, 0x3b, 0xd9, 0x95, 0x63, 0x9f, 0xa7, 0x7f, 0x9c, 0x81, 0x72, 0x62, 0x71, 0x29, 0x9a, - 0xb9, 0x70, 0x42, 0x92, 0x49, 0x31, 0x75, 0x9b, 0xfc, 0x3f, 0x95, 0xef, 0x43, 0xde, 0x12, 0xeb, - 0x0b, 0xfe, 0xa9, 0xb5, 0x31, 0xcd, 0x51, 0xc8, 0x9d, 0x89, 0x22, 0x49, 0x7e, 0x32, 0xac, 0x00, - 0xd1, 0x2d, 0x58, 0xa6, 0x84, 0xd3, 0xb3, 0xad, 0x43, 0x4e, 0x68, 0xbc, 0xed, 0xcf, 0x45, 0xed, - 0x0e, 0x1e, 0x15, 0xc0, 0xe3, 0x3a, 0x41, 0x85, 0xcc, 0xbf, 0x44, 0x85, 0xd4, 0x1d, 0x98, 0xfd, - 0x1f, 0xb6, 0xe6, 0x3f, 0x80, 0x62, 0xd4, 0x3c, 0x7d, 0xc6, 0x26, 0xf5, 0x1f, 0x41, 0x41, 0x44, - 0x63, 0xd0, 0xf4, 0x5f, 0x72, 0x01, 0x25, 0xaf, 0x86, 0x4c, 0x9a, 0xab, 0x41, 0xdf, 0x04, 0xff, - 0x5f, 0x65, 0xa2, 0x9a, 0xfa, 0x0f, 0xf5, 0x58, 0x35, 0x8d, 0xbf, 0xba, 0x63, 0x93, 0xb2, 0x5f, - 0x68, 0x00, 0xf2, 0xd5, 0xb8, 0x73, 0x42, 0x5c, 0x2e, 0x1c, 0x13, 0x27, 0x30, 0xea, 0x98, 0x4c, - 0x23, 0xc9, 0x41, 0xf7, 0x20, 0xef, 0xc9, 0xa6, 0x4a, 0x8d, 0xae, 0xa6, 0x9c, 0x02, 0x84, 0x51, - 0xe7, 0x77, 0x66, 0x58, 0x81, 0x19, 0xeb, 0x4f, 0x9f, 0x57, 0x67, 0x9e, 0x3d, 0xaf, 0xce, 0x7c, - 0xf4, 0xbc, 0x3a, 0xf3, 0xc1, 0xb0, 0xaa, 0x3d, 0x1d, 0x56, 0xb5, 0x67, 0xc3, 0xaa, 0xf6, 0xd1, - 0xb0, 0xaa, 0x7d, 0x3c, 0xac, 0x6a, 0x8f, 0x3f, 0xa9, 0xce, 0x3c, 0xc8, 0x9c, 0x6c, 0xfc, 0x27, - 0x00, 0x00, 0xff, 0xff, 0x66, 0xe7, 0x2a, 0x84, 0x4b, 0x20, 0x00, 0x00, + 0xf6, 0x3e, 0xb1, 0x3c, 0xb7, 0xcd, 0xe4, 0x16, 0x65, 0x8d, 0xcf, 0x0d, 0x07, 0x35, 0x74, 0x6b, + 0x8c, 0x8b, 0x2f, 0xd0, 0x40, 0x0e, 0x94, 0x7b, 0x54, 0xfe, 0xb6, 0xb9, 0x2a, 0xe0, 0xe2, 0xe0, + 0xbc, 0x9d, 0x6e, 0xed, 0xad, 0xb8, 0xaa, 0xb1, 0x3c, 0x1c, 0xd4, 0xca, 0x09, 0x12, 0x4e, 0x82, + 0xa3, 0xef, 0xc2, 0x92, 0x47, 0x7b, 0x47, 0xa6, 0xdb, 0x24, 0x3d, 0xe2, 0xb6, 0x89, 0xcb, 0x99, + 0x3c, 0xcc, 0x05, 0x63, 0x45, 0x94, 0xdd, 0xbb, 0x23, 0x3c, 0x3c, 0x26, 0x8d, 0x1e, 0xc0, 0x72, + 0x8f, 0x7a, 0x3d, 0xb3, 0x63, 0x0a, 0xc4, 0x96, 0xe7, 0xd8, 0xd6, 0x99, 0x3c, 0xec, 0x45, 0xe3, + 0xea, 0x70, 0x50, 0x5b, 0x6e, 0x8d, 0x32, 0xcf, 0x07, 0xb5, 0x57, 0x64, 0xe8, 0x04, 0x25, 0x62, + 0xe2, 0x71, 0x18, 0x7d, 0x17, 0x0a, 0xcd, 0x3e, 0x95, 0x14, 0xf4, 0x6d, 0x28, 0xb4, 0xd5, 0x6f, + 0x15, 0xd5, 0xd7, 0x82, 0x3b, 0x29, 0x90, 0x39, 0x1f, 0xd4, 0xca, 0xe2, 0xea, 0xad, 0x07, 0x04, + 0x1c, 0xaa, 0xe8, 0x0f, 0xa1, 0xbc, 0x73, 0xda, 0xf3, 0x28, 0x0f, 0xf6, 0xeb, 0x0d, 0xc8, 0x13, + 0x49, 0x90, 0x68, 0x85, 0xa8, 0x90, 0xfa, 0x62, 0x58, 0x71, 0xc5, 0xc1, 0x26, 0xa7, 0xa6, 0xc5, + 0x55, 0x45, 0x0c, 0x0f, 0xf6, 0x8e, 0x20, 0x62, 0x9f, 0xa7, 0x3f, 0xd1, 0x00, 0x6e, 0x91, 0x10, + 0x7b, 0x0b, 0x16, 0x83, 0x43, 0x91, 0x3c, 0xab, 0x9f, 0x57, 0xda, 0x8b, 0x38, 0xc9, 0xc6, 0xa3, + 0xf2, 0xa8, 0x05, 0x2b, 0xb6, 0x6b, 0x39, 0xfd, 0x36, 0xb9, 0xe7, 0xda, 0xae, 0xcd, 0x6d, 0xd3, + 0xb1, 0x7f, 0x12, 0xd6, 0xe5, 0x2f, 0x28, 0x9c, 0x95, 0xdd, 0x0b, 0x64, 0xf0, 0x85, 0x9a, 0xfa, + 0x43, 0x28, 0xca, 0x0a, 0x21, 0x8a, 0x73, 0x54, 0xae, 0xb4, 0x17, 0x94, 0xab, 0xa0, 0xba, 0x67, + 0x26, 0x55, 0xf7, 0xd8, 0x81, 0x70, 0xa0, 0xec, 0xeb, 0x06, 0x17, 0x4e, 0x2a, 0x0b, 0x57, 0xa1, + 0x10, 0x2c, 0x5c, 0x59, 0x09, 0x1b, 0x8d, 0x00, 0x08, 0x87, 0x12, 0x31, 0x6b, 0x47, 0x90, 0xa8, + 0x76, 0xe9, 0x8c, 0xc5, 0xaa, 0x6f, 0xe6, 0xc5, 0xd5, 0x37, 0x66, 0xe9, 0x67, 0x50, 0x99, 0xd4, + 0x9d, 0xbc, 0x44, 0x3d, 0x4e, 0xef, 0x8a, 0xfe, 0x6b, 0x0d, 0x96, 0xe2, 0x48, 0xe9, 0xb7, 0x2f, + 0xbd, 0x91, 0xcb, 0xef, 0xf1, 0x58, 0x44, 0x7e, 0xab, 0xc1, 0x4a, 0x62, 0x69, 0x53, 0xed, 0xf8, + 0x14, 0x4e, 0xc5, 0x93, 0x23, 0x3b, 0x45, 0x72, 0x34, 0xa0, 0xb4, 0x1b, 0xe6, 0x3d, 0xbd, 0xbc, + 0xf3, 0xd1, 0xff, 0xa2, 0xc1, 0x7c, 0x4c, 0x83, 0xa1, 0x87, 0x30, 0x27, 0xea, 0x9b, 0xed, 0x76, + 0x54, 0x57, 0x96, 0xf2, 0xb2, 0x8c, 0x81, 0x44, 0xeb, 0x6a, 0xf9, 0x48, 0x38, 0x80, 0x44, 0x2d, + 0xc8, 0x53, 0xc2, 0xfa, 0x0e, 0x57, 0xa5, 0xfd, 0x6a, 0xca, 0x6b, 0x8d, 0x9b, 0xbc, 0xcf, 0x0c, + 0x10, 0x35, 0x0a, 0x4b, 0x7d, 0xac, 0x70, 0xf4, 0xbf, 0x67, 0xa0, 0x7c, 0xdb, 0x3c, 0x20, 0xce, + 0x3e, 0x71, 0x88, 0xc5, 0x3d, 0x8a, 0x7e, 0x0a, 0xa5, 0xae, 0xc9, 0xad, 0x23, 0x49, 0x0d, 0x7a, + 0xcb, 0x66, 0x3a, 0x43, 0x09, 0xa4, 0xfa, 0x5e, 0x04, 0xb3, 0xe3, 0x72, 0x7a, 0x66, 0xbc, 0xa2, + 0x16, 0x56, 0x8a, 0x71, 0x70, 0xdc, 0x9a, 0x7c, 0x10, 0xc8, 0xef, 0x9d, 0xd3, 0x9e, 0xb8, 0x44, + 0xa7, 0x7f, 0x87, 0x24, 0x5c, 0xc0, 0xe4, 0xfd, 0xbe, 0x4d, 0x49, 0x97, 0xb8, 0x3c, 0x7a, 0x10, + 0xec, 0x8d, 0xe0, 0xe3, 0x31, 0x8b, 0xab, 0x37, 0x60, 0x69, 0xd4, 0x79, 0xb4, 0x04, 0xd9, 0x63, + 0x72, 0xe6, 0xe7, 0x02, 0x16, 0x3f, 0xd1, 0x0a, 0xe4, 0x4e, 0x4c, 0xa7, 0xaf, 0xea, 0x0f, 0xf6, + 0x3f, 0xae, 0x67, 0xae, 0x69, 0xfa, 0xef, 0x35, 0xa8, 0x4c, 0x72, 0x04, 0x7d, 0x31, 0x06, 0x64, + 0x94, 0x94, 0x57, 0xd9, 0x77, 0xc9, 0x99, 0x8f, 0xba, 0x03, 0x05, 0xaf, 0x27, 0x9e, 0x70, 0x1e, + 0x55, 0x79, 0xfe, 0x66, 0x90, 0xbb, 0x77, 0x15, 0xfd, 0x7c, 0x50, 0xbb, 0x92, 0x80, 0x0f, 0x18, + 0x38, 0x54, 0x45, 0x3a, 0xe4, 0xa5, 0x3f, 0xe2, 0x52, 0x16, 0xed, 0x93, 0xdc, 0xfc, 0xfb, 0x92, + 0x82, 0x15, 0x47, 0xff, 0x93, 0x06, 0xb3, 0xb2, 0x3d, 0x7c, 0x08, 0x05, 0x11, 0xbf, 0xb6, 0xc9, + 0x4d, 0xe9, 0x57, 0xea, 0xc7, 0x84, 0xd0, 0xde, 0x23, 0xdc, 0x8c, 0xce, 0x57, 0x40, 0xc1, 0x21, + 0x22, 0xc2, 0x90, 0xb3, 0x39, 0xe9, 0x06, 0x1b, 0xf9, 0xd6, 0x44, 0x68, 0xf5, 0xfe, 0xad, 0x63, + 0xf3, 0xd1, 0xce, 0x29, 0x27, 0xae, 0xd8, 0x8c, 0xa8, 0x18, 0xec, 0x0a, 0x0c, 0xec, 0x43, 0xe9, + 0x7f, 0xd0, 0x20, 0x34, 0x25, 0x8e, 0x3b, 0x23, 0xce, 0xe1, 0x6d, 0xdb, 0x3d, 0x56, 0x61, 0x0d, + 0xdd, 0xd9, 0x57, 0x74, 0x1c, 0x4a, 0x5c, 0x74, 0xc5, 0x66, 0xa6, 0xbc, 0x62, 0xaf, 0x42, 0xc1, + 0xf2, 0x5c, 0x6e, 0xbb, 0xfd, 0xb1, 0xfa, 0xb2, 0xad, 0xe8, 0x38, 0x94, 0xd0, 0x9f, 0x65, 0xa1, + 0x24, 0x7c, 0x0d, 0xee, 0xf8, 0x6f, 0x42, 0xd9, 0x89, 0xef, 0x9e, 0xf2, 0xf9, 0x8a, 0x82, 0x48, + 0x9e, 0x47, 0x9c, 0x94, 0x15, 0xca, 0x87, 0x36, 0x71, 0xda, 0xa1, 0x72, 0x26, 0xa9, 0x7c, 0x33, + 0xce, 0xc4, 0x49, 0x59, 0x51, 0x67, 0x1f, 0x89, 0xbc, 0x56, 0x8d, 0x5a, 0x18, 0xda, 0xef, 0x09, + 0x22, 0xf6, 0x79, 0x17, 0xc5, 0x67, 0x76, 0xca, 0xf8, 0x5c, 0x87, 0x05, 0xb1, 0x91, 0x5e, 0x9f, + 0x07, 0xdd, 0x6c, 0x4e, 0xf6, 0x5d, 0x68, 0x38, 0xa8, 0x2d, 0xbc, 0x97, 0xe0, 0xe0, 0x11, 0xc9, + 0x89, 0xed, 0x4b, 0xfe, 0xd3, 0xb6, 0x2f, 0x62, 0xd5, 0x8e, 0xdd, 0xb5, 0x79, 0x65, 0x4e, 0x3a, + 0x11, 0xae, 0xfa, 0xb6, 0x20, 0x62, 0x9f, 0x97, 0xd8, 0xd2, 0xc2, 0xa5, 0x5b, 0xfa, 0x3e, 0x14, + 0xf7, 0x6c, 0x8b, 0x7a, 0x62, 0x2d, 0xe2, 0x62, 0x62, 0x89, 0xa6, 0x3d, 0x2c, 0xe0, 0xc1, 0x1a, + 0x03, 0xbe, 0x70, 0xc5, 0x35, 0x5d, 0xcf, 0x6f, 0xcd, 0x73, 0x91, 0x2b, 0x77, 0x04, 0x11, 0xfb, + 0xbc, 0xeb, 0x2b, 0xe2, 0x3e, 0xfa, 0xe5, 0x93, 0xda, 0xcc, 0xe3, 0x27, 0xb5, 0x99, 0x0f, 0x9f, + 0xa8, 0xbb, 0xe9, 0x5f, 0x00, 0x70, 0xf7, 0xe0, 0xc7, 0xc4, 0xf2, 0x73, 0xfe, 0xf2, 0x57, 0xb9, + 0xe8, 0x31, 0xd4, 0x30, 0x48, 0xbe, 0x60, 0x33, 0x23, 0x3d, 0x46, 0x8c, 0x87, 0x13, 0x92, 0xa8, + 0x01, 0xc5, 0xf0, 0xa5, 0xae, 0xf2, 0x7b, 0x59, 0xa9, 0x15, 0xc3, 0xe7, 0x3c, 0x8e, 0x64, 0x12, + 0x07, 0x70, 0xf6, 0xd2, 0x03, 0x68, 0x40, 0xb6, 0x6f, 0xb7, 0x65, 0x4a, 0x14, 0x8d, 0xaf, 0x06, + 0x05, 0xf0, 0xde, 0x6e, 0xf3, 0x7c, 0x50, 0x7b, 0x6d, 0xd2, 0x8c, 0x8b, 0x9f, 0xf5, 0x08, 0xab, + 0xdf, 0xdb, 0x6d, 0x62, 0xa1, 0x7c, 0x51, 0x92, 0xe6, 0xa7, 0x4c, 0xd2, 0x4d, 0x00, 0xb5, 0x6a, + 0xa1, 0xed, 0xe7, 0x46, 0x38, 0xb5, 0xb8, 0x15, 0x72, 0x70, 0x4c, 0x0a, 0x31, 0x58, 0xb6, 0x28, + 0x91, 0xbf, 0xc5, 0xd6, 0x33, 0x6e, 0x76, 0xfd, 0x77, 0x7b, 0x69, 0xf3, 0xcb, 0xe9, 0x2a, 0xa6, + 0x50, 0x33, 0x5e, 0x55, 0x66, 0x96, 0xb7, 0x47, 0xc1, 0xf0, 0x38, 0x3e, 0xf2, 0x60, 0xb9, 0xad, + 0x5e, 0x3d, 0x91, 0xd1, 0xe2, 0xd4, 0x46, 0xaf, 0x08, 0x83, 0xcd, 0x51, 0x20, 0x3c, 0x8e, 0x8d, + 0x7e, 0x08, 0xab, 0x01, 0x71, 0xfc, 0xe9, 0x59, 0x01, 0x19, 0xa9, 0xaa, 0x78, 0x0c, 0x37, 0x27, + 0x4a, 0xe1, 0x17, 0x20, 0xa0, 0x36, 0xe4, 0x1d, 0xbf, 0xbb, 0x28, 0xc9, 0x1b, 0xe1, 0x5b, 0xe9, + 0x56, 0x11, 0x65, 0x7f, 0x3d, 0xde, 0x55, 0x84, 0xcf, 0x2f, 0xd5, 0x50, 0x28, 0x6c, 0x74, 0x0a, + 0x25, 0xd3, 0x75, 0x3d, 0x6e, 0xfa, 0x8f, 0xe1, 0x79, 0x69, 0x6a, 0x6b, 0x6a, 0x53, 0x5b, 0x11, + 0xc6, 0x48, 0x17, 0x13, 0xe3, 0xe0, 0xb8, 0x29, 0xf4, 0x08, 0x16, 0xbd, 0x47, 0x2e, 0xa1, 0x98, + 0x1c, 0x12, 0x4a, 0x5c, 0x8b, 0xb0, 0x4a, 0x59, 0x5a, 0xff, 0x5a, 0x4a, 0xeb, 0x09, 0xe5, 0x28, + 0xa5, 0x93, 0x74, 0x86, 0x47, 0xad, 0xa0, 0x3a, 0xc0, 0xa1, 0xed, 0xaa, 0x5e, 0xb4, 0xb2, 0x10, + 0x8d, 0x9e, 0x6e, 0x86, 0x54, 0x1c, 0x93, 0x40, 0x5f, 0x87, 0x92, 0xe5, 0xf4, 0x19, 0x27, 0xfe, + 0x8c, 0x6b, 0x51, 0x9e, 0xa0, 0x70, 0x7d, 0xdb, 0x11, 0x0b, 0xc7, 0xe5, 0xd0, 0x11, 0xcc, 0xdb, + 0xb1, 0xa6, 0xb7, 0xb2, 0x24, 0x73, 0x71, 0x73, 0xea, 0x4e, 0x97, 0x19, 0x4b, 0xa2, 0x12, 0xc5, + 0x29, 0x38, 0x81, 0xbc, 0xfa, 0x0d, 0x28, 0x7d, 0xca, 0x1e, 0x4c, 0xf4, 0x70, 0xa3, 0x5b, 0x37, + 0x55, 0x0f, 0xf7, 0xd7, 0x0c, 0x2c, 0x24, 0x03, 0x1e, 0xbe, 0x75, 0xb4, 0x89, 0x33, 0xcb, 0xa0, + 0x2a, 0x67, 0x27, 0x56, 0x65, 0x55, 0xfc, 0x66, 0x5f, 0xa6, 0xf8, 0x6d, 0x02, 0x98, 0x3d, 0x3b, + 0xa8, 0x7b, 0x7e, 0x1d, 0x0d, 0x2b, 0x57, 0x34, 0x45, 0xc3, 0x31, 0x29, 0x39, 0x95, 0xf4, 0x5c, + 0x4e, 0x3d, 0xc7, 0x21, 0x54, 0x5d, 0xa6, 0xfe, 0x54, 0x32, 0xa4, 0xe2, 0x98, 0x04, 0xba, 0x09, + 0xe8, 0xc0, 0xf1, 0xac, 0x63, 0x19, 0x82, 0xe0, 0x9c, 0xcb, 0x2a, 0x59, 0xf0, 0x87, 0x52, 0xc6, + 0x18, 0x17, 0x5f, 0xa0, 0xa1, 0xcf, 0x41, 0xae, 0x25, 0xda, 0x0a, 0xfd, 0x2e, 0x24, 0xe7, 0x49, + 0xe8, 0x86, 0x1f, 0x09, 0x2d, 0x1c, 0xf8, 0x4c, 0x17, 0x05, 0xfd, 0x2a, 0x14, 0xb1, 0xe7, 0xf1, + 0x96, 0xc9, 0x8f, 0x18, 0xaa, 0x41, 0xae, 0x27, 0x7e, 0xa8, 0x61, 0xa1, 0x9c, 0xff, 0x4a, 0x0e, + 0xf6, 0xe9, 0xfa, 0xaf, 0x34, 0x78, 0x75, 0xe2, 0xec, 0x4e, 0x44, 0xd4, 0x0a, 0xbf, 0x94, 0x4b, + 0x61, 0x44, 0x23, 0x39, 0x1c, 0x93, 0x12, 0x9d, 0x58, 0x62, 0xe0, 0x37, 0xda, 0x89, 0x25, 0xac, + 0xe1, 0xa4, 0xac, 0xfe, 0xef, 0x0c, 0xe4, 0xfd, 0x67, 0xd9, 0x7f, 0xb9, 0xf9, 0x7e, 0x03, 0xf2, + 0x4c, 0xda, 0x51, 0xee, 0x85, 0xd5, 0xd2, 0xb7, 0x8e, 0x15, 0x57, 0x34, 0x31, 0x5d, 0xc2, 0x98, + 0xd9, 0x09, 0x92, 0x37, 0x6c, 0x62, 0xf6, 0x7c, 0x32, 0x0e, 0xf8, 0xe8, 0x1d, 0xf1, 0x0a, 0x35, + 0x59, 0xd8, 0x17, 0x56, 0x03, 0x48, 0x2c, 0xa9, 0xe7, 0x83, 0xda, 0xbc, 0x02, 0x97, 0xdf, 0x58, + 0x49, 0xa3, 0x07, 0x30, 0xd7, 0x26, 0xdc, 0xb4, 0x1d, 0xbf, 0x1d, 0x4c, 0x3d, 0x99, 0xf4, 0xc1, + 0x9a, 0xbe, 0xaa, 0x51, 0x12, 0x3e, 0xa9, 0x0f, 0x1c, 0x00, 0x8a, 0x83, 0x67, 0x79, 0x6d, 0x7f, + 0x4c, 0x9f, 0x8b, 0x0e, 0xde, 0xb6, 0xd7, 0x26, 0x58, 0x72, 0xf4, 0xc7, 0x1a, 0x94, 0x7c, 0xa4, + 0x6d, 0xb3, 0xcf, 0x08, 0xda, 0x08, 0x57, 0xe1, 0x6f, 0x77, 0x70, 0x27, 0xcf, 0xbe, 0x77, 0xd6, + 0x23, 0xe7, 0x83, 0x5a, 0x51, 0x8a, 0x89, 0x8f, 0x70, 0x01, 0xb1, 0x18, 0x65, 0x2e, 0x89, 0xd1, + 0xeb, 0x90, 0x93, 0xad, 0xb7, 0x0a, 0x66, 0xd8, 0xe8, 0xc9, 0xf6, 0x1c, 0xfb, 0x3c, 0xfd, 0xe3, + 0x0c, 0x94, 0x13, 0x8b, 0x4b, 0xd1, 0xd5, 0x85, 0xa3, 0x92, 0x4c, 0x8a, 0xf1, 0xdb, 0xe4, 0x7f, + 0xae, 0x7c, 0x1f, 0xf2, 0x96, 0x58, 0x5f, 0xf0, 0xdf, 0xad, 0x8d, 0x69, 0xb6, 0x42, 0x46, 0x26, + 0xca, 0x24, 0xf9, 0xc9, 0xb0, 0x02, 0x44, 0xb7, 0x60, 0x99, 0x12, 0x4e, 0xcf, 0xb6, 0x0e, 0x39, + 0xa1, 0xf1, 0xfe, 0x3f, 0x17, 0xf5, 0x3d, 0x78, 0x54, 0x00, 0x8f, 0xeb, 0x04, 0xa5, 0x32, 0xff, + 0x12, 0xa5, 0x52, 0x77, 0x60, 0xf6, 0x7f, 0xd8, 0xa3, 0xff, 0x00, 0x8a, 0x51, 0x17, 0xf5, 0x19, + 0x9b, 0xd4, 0x7f, 0x04, 0x05, 0x91, 0x8d, 0x41, 0xf7, 0x7f, 0xc9, 0x4d, 0x94, 0xbc, 0x23, 0x32, + 0x69, 0xee, 0x08, 0x7d, 0x13, 0xfc, 0xff, 0x99, 0x89, 0x6a, 0xea, 0xbf, 0xd8, 0x63, 0xd5, 0x34, + 0xfe, 0xfc, 0x8e, 0x8d, 0xcc, 0x7e, 0xa1, 0x01, 0xc8, 0xe7, 0xe3, 0xce, 0x09, 0x71, 0xb9, 0x70, + 0x4c, 0xec, 0xc0, 0xa8, 0x63, 0xf2, 0x18, 0x49, 0x0e, 0xba, 0x07, 0x79, 0x4f, 0x76, 0x57, 0x6a, + 0x86, 0x35, 0xe5, 0x38, 0x20, 0xcc, 0x3a, 0xbf, 0x45, 0xc3, 0x0a, 0xcc, 0x58, 0x7f, 0xfa, 0xbc, + 0x3a, 0xf3, 0xec, 0x79, 0x75, 0xe6, 0xa3, 0xe7, 0xd5, 0x99, 0x0f, 0x86, 0x55, 0xed, 0xe9, 0xb0, + 0xaa, 0x3d, 0x1b, 0x56, 0xb5, 0x8f, 0x86, 0x55, 0xed, 0xe3, 0x61, 0x55, 0x7b, 0xfc, 0x49, 0x75, + 0xe6, 0x41, 0xe6, 0x64, 0xe3, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x6c, 0xc5, 0x28, 0xb2, 0x54, + 0x20, 0x00, 0x00, } 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 ea48226b73f..b37a445c2a8 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 @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -149,6 +149,10 @@ message DeleteOptions { // 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. // +optional optional string propagationPolicy = 4; } @@ -510,15 +514,16 @@ message ObjectMeta { // 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 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. + // 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. // // Populated by the system when a graceful deletion is requested. @@ -616,6 +621,10 @@ message OwnerReference { optional bool blockOwnerDeletion = 7; } +// Patch is provided to give a concrete name and type to the Kubernetes PATCH request body. +message Patch { +} + // Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out. message Preconditions { // Specifies the target UID. diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/group_version_test.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/group_version_test.go index 2217aa293d4..1f7f07e81d0 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/group_version_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/group_version_test.go @@ -47,7 +47,7 @@ func TestGroupVersionUnmarshalJSON(t *testing.T) { t.Errorf("JSON codec failed to unmarshal input '%s': expected %+v, got %+v", c.input, c.expect, result.GV) } // test the json-iterator codec - if err := jsoniter.ConfigFastest.Unmarshal(c.input, &result); err != nil { + if err := jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal(c.input, &result); err != nil { t.Errorf("json-iterator codec failed to unmarshal input '%v': %v", c.input, err) } if !reflect.DeepEqual(result.GV, c.expect) { diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/register.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/register.go index 6e449a436a1..b300d37015a 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/register.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/register.go @@ -70,7 +70,6 @@ func AddToGroupVersion(scheme *runtime.Scheme, groupVersion schema.GroupVersion) ) // register manually. This usually goes through the SchemeBuilder, which we cannot use here. - scheme.AddGeneratedDeepCopyFuncs(GetGeneratedDeepCopyFuncs()...) AddConversionFuncs(scheme) RegisterDefaults(scheme) } @@ -90,6 +89,5 @@ func init() { ) // register manually. This usually goes through the SchemeBuilder, which we cannot use here. - scheme.AddGeneratedDeepCopyFuncs(GetGeneratedDeepCopyFuncs()...) RegisterDefaults(scheme) } diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/time.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/time.go index 435f6a8f599..0a9f2a37756 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/time.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/time.go @@ -80,7 +80,13 @@ func (t *Time) Before(u *Time) bool { // Equal reports whether the time instant t is equal to u. func (t *Time) Equal(u *Time) bool { - return t.Time.Equal(u.Time) + if t == nil && u == nil { + return true + } + if t != nil && u != nil { + return t.Time.Equal(u.Time) + } + return false } // Unix returns the local time corresponding to the given Unix time diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/time_test.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/time_test.go index c0fafab9bfc..9923958ee00 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/time_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/time_test.go @@ -171,3 +171,27 @@ func TestTimeProto(t *testing.T) { } } } + +func TestTimeEqual(t *testing.T) { + t1 := NewTime(time.Now()) + cases := []struct { + name string + x *Time + y *Time + result bool + }{ + {"nil =? nil", nil, nil, true}, + {"!nil =? !nil", &t1, &t1, true}, + {"nil =? !nil", nil, &t1, false}, + {"!nil =? nil", &t1, nil, false}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := c.x.Equal(c.y) + if result != c.result { + t.Errorf("Failed equality test for '%v', '%v': expected %+v, got %+v", c.x, c.y, c.result, result) + } + }) + } +} 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 13ae66c6f47..c8ee4e5d65b 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 @@ -177,15 +177,16 @@ type ObjectMeta struct { // 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 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. + // 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. // // Populated by the system when a graceful deletion is requested. @@ -445,6 +446,10 @@ type DeleteOptions struct { // 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. // +optional PropagationPolicy *DeletionPropagation `json:"propagationPolicy,omitempty" protobuf:"varint,4,opt,name=propagationPolicy"` } 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 49d2de1ef79..5dbba4b02f5 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 @@ -90,7 +90,7 @@ var map_DeleteOptions = map[string]string{ "gracePeriodSeconds": "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.", "preconditions": "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.", "orphanDependents": "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.", - "propagationPolicy": "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.", + "propagationPolicy": "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.", } func (DeleteOptions) SwaggerDoc() map[string]string { @@ -214,7 +214,7 @@ var map_ObjectMeta = map[string]string{ "resourceVersion": "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/api-conventions.md#concurrency-control-and-consistency", "generation": "A sequence number representing a specific generation of the desired state. Populated by the system. Read-only.", "creationTimestamp": "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/api-conventions.md#metadata", - "deletionTimestamp": "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 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/api-conventions.md#metadata", + "deletionTimestamp": "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/api-conventions.md#metadata", "deletionGracePeriodSeconds": "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.", "labels": "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: http://kubernetes.io/docs/user-guide/labels", "annotations": "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: http://kubernetes.io/docs/user-guide/annotations", diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types_test.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types_test.go index 21aa9560e92..116f7505a42 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types_test.go @@ -58,7 +58,7 @@ func TestVerbsUgorjiUnmarshalJSON(t *testing.T) { for i, c := range cases { var result APIResource - if err := jsoniter.ConfigFastest.Unmarshal([]byte(c.input), &result); err != nil { + if err := jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal([]byte(c.input), &result); err != nil { t.Errorf("[%d] Failed to unmarshal input '%v': %v", i, c.input, err) } if !reflect.DeepEqual(result, c.result) { diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/BUILD b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/BUILD index ec27bfd78fa..4f3bf562de9 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/BUILD @@ -8,24 +8,30 @@ load( go_test( name = "go_default_test", - srcs = ["unstructured_test.go"], + srcs = [ + "helpers_test.go", + "unstructured_list_test.go", + ], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured", - library = ":go_default_library", - deps = ["//vendor/github.com/stretchr/testify/assert:go_default_library"], + deps = [ + "//vendor/github.com/stretchr/testify/assert:go_default_library", + "//vendor/github.com/stretchr/testify/require:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + ], ) go_library( name = "go_default_library", srcs = [ + "helpers.go", "unstructured.go", + "unstructured_list.go", "zz_generated.deepcopy.go", ], importpath = "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured", deps = [ - "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion/unstructured:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/helpers.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/helpers.go new file mode 100644 index 00000000000..fdc688f0732 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/helpers.go @@ -0,0 +1,469 @@ +/* +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 unstructured + +import ( + gojson "encoding/json" + "errors" + "fmt" + "io" + "strings" + + 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/json" +) + +// NestedFieldCopy returns a deep copy of the value of a nested field. +// Returns false if the value is missing. +// No error is returned for a nil field. +func NestedFieldCopy(obj map[string]interface{}, fields ...string) (interface{}, bool, error) { + val, found, err := nestedFieldNoCopy(obj, fields...) + if !found || err != nil { + return nil, found, err + } + return runtime.DeepCopyJSONValue(val), true, nil +} + +func nestedFieldNoCopy(obj map[string]interface{}, fields ...string) (interface{}, bool, error) { + var val interface{} = obj + for _, field := range fields { + if m, ok := val.(map[string]interface{}); ok { + val, ok = m[field] + if !ok { + return nil, false, nil + } + } else { + return nil, false, fmt.Errorf("%v is of the type %T, expected map[string]interface{}", val, val) + } + } + return val, true, nil +} + +// NestedString returns the string value of a nested field. +// Returns false if value is not found and an error if not a string. +func NestedString(obj map[string]interface{}, fields ...string) (string, bool, error) { + val, found, err := nestedFieldNoCopy(obj, fields...) + if !found || err != nil { + return "", found, err + } + s, ok := val.(string) + if !ok { + return "", false, fmt.Errorf("%v is of the type %T, expected string", val, val) + } + return s, true, nil +} + +// NestedBool returns the bool value of a nested field. +// Returns false if value is not found and an error if not a bool. +func NestedBool(obj map[string]interface{}, fields ...string) (bool, bool, error) { + val, found, err := nestedFieldNoCopy(obj, fields...) + if !found || err != nil { + return false, found, err + } + b, ok := val.(bool) + if !ok { + return false, false, fmt.Errorf("%v is of the type %T, expected bool", val, val) + } + return b, true, nil +} + +// NestedFloat64 returns the float64 value of a nested field. +// Returns false if value is not found and an error if not a float64. +func NestedFloat64(obj map[string]interface{}, fields ...string) (float64, bool, error) { + val, found, err := nestedFieldNoCopy(obj, fields...) + if !found || err != nil { + return 0, found, err + } + f, ok := val.(float64) + if !ok { + return 0, false, fmt.Errorf("%v is of the type %T, expected float64", val, val) + } + return f, true, nil +} + +// NestedInt64 returns the int64 value of a nested field. +// Returns false if value is not found and an error if not an int64. +func NestedInt64(obj map[string]interface{}, fields ...string) (int64, bool, error) { + val, found, err := nestedFieldNoCopy(obj, fields...) + if !found || err != nil { + return 0, found, err + } + i, ok := val.(int64) + if !ok { + return 0, false, fmt.Errorf("%v is of the type %T, expected int64", val, val) + } + return i, true, nil +} + +// NestedStringSlice returns a copy of []string value of a nested field. +// Returns false if value is not found and an error if not a []interface{} or contains non-string items in the slice. +func NestedStringSlice(obj map[string]interface{}, fields ...string) ([]string, bool, error) { + val, found, err := nestedFieldNoCopy(obj, fields...) + if !found || err != nil { + return nil, found, err + } + m, ok := val.([]interface{}) + if !ok { + return nil, false, fmt.Errorf("%v is of the type %T, expected []interface{}", val, val) + } + strSlice := make([]string, 0, len(m)) + for _, v := range m { + if str, ok := v.(string); ok { + strSlice = append(strSlice, str) + } else { + return nil, false, fmt.Errorf("contains non-string key in the slice: %v is of the type %T, expected string", v, v) + } + } + return strSlice, true, nil +} + +// NestedSlice returns a deep copy of []interface{} value of a nested field. +// Returns false if value is not found and an error if not a []interface{}. +func NestedSlice(obj map[string]interface{}, fields ...string) ([]interface{}, bool, error) { + val, found, err := nestedFieldNoCopy(obj, fields...) + if !found || err != nil { + return nil, found, err + } + _, ok := val.([]interface{}) + if !ok { + return nil, false, fmt.Errorf("%v is of the type %T, expected []interface{}", val, val) + } + return runtime.DeepCopyJSONValue(val).([]interface{}), true, nil +} + +// NestedStringMap returns a copy of map[string]string value of a nested field. +// Returns false if value is not found and an error if not a map[string]interface{} or contains non-string values in the map. +func NestedStringMap(obj map[string]interface{}, fields ...string) (map[string]string, bool, error) { + m, found, err := nestedMapNoCopy(obj, fields...) + if !found || err != nil { + return nil, found, err + } + strMap := make(map[string]string, len(m)) + for k, v := range m { + if str, ok := v.(string); ok { + strMap[k] = str + } else { + return nil, false, fmt.Errorf("contains non-string key in the map: %v is of the type %T, expected string", v, v) + } + } + return strMap, true, nil +} + +// NestedMap returns a deep copy of map[string]interface{} value of a nested field. +// Returns false if value is not found and an error if not a map[string]interface{}. +func NestedMap(obj map[string]interface{}, fields ...string) (map[string]interface{}, bool, error) { + m, found, err := nestedMapNoCopy(obj, fields...) + if !found || err != nil { + return nil, found, err + } + return runtime.DeepCopyJSON(m), true, nil +} + +// nestedMapNoCopy returns a map[string]interface{} value of a nested field. +// Returns false if value is not found and an error if not a map[string]interface{}. +func nestedMapNoCopy(obj map[string]interface{}, fields ...string) (map[string]interface{}, bool, error) { + val, found, err := nestedFieldNoCopy(obj, fields...) + if !found || err != nil { + return nil, found, err + } + m, ok := val.(map[string]interface{}) + if !ok { + return nil, false, fmt.Errorf("%v is of the type %T, expected map[string]interface{}", val, val) + } + return m, true, nil +} + +// SetNestedField sets the value of a nested field to a deep copy of the value provided. +// Returns false if value cannot be set because one of the nesting levels is not a map[string]interface{}. +func SetNestedField(obj map[string]interface{}, value interface{}, fields ...string) bool { + return setNestedFieldNoCopy(obj, runtime.DeepCopyJSONValue(value), fields...) +} + +func setNestedFieldNoCopy(obj map[string]interface{}, value interface{}, fields ...string) bool { + m := obj + for _, field := range fields[:len(fields)-1] { + if val, ok := m[field]; ok { + if valMap, ok := val.(map[string]interface{}); ok { + m = valMap + } else { + return false + } + } else { + newVal := make(map[string]interface{}) + m[field] = newVal + m = newVal + } + } + m[fields[len(fields)-1]] = value + return true +} + +// SetNestedStringSlice sets the string slice value of a nested field. +// Returns false if value cannot be set because one of the nesting levels is not a map[string]interface{}. +func SetNestedStringSlice(obj map[string]interface{}, value []string, fields ...string) bool { + m := make([]interface{}, 0, len(value)) // convert []string into []interface{} + for _, v := range value { + m = append(m, v) + } + return setNestedFieldNoCopy(obj, m, fields...) +} + +// SetNestedSlice sets the slice value of a nested field. +// Returns false if value cannot be set because one of the nesting levels is not a map[string]interface{}. +func SetNestedSlice(obj map[string]interface{}, value []interface{}, fields ...string) bool { + return SetNestedField(obj, value, fields...) +} + +// SetNestedStringMap sets the map[string]string value of a nested field. +// Returns false if value cannot be set because one of the nesting levels is not a map[string]interface{}. +func SetNestedStringMap(obj map[string]interface{}, value map[string]string, fields ...string) bool { + m := make(map[string]interface{}, len(value)) // convert map[string]string into map[string]interface{} + for k, v := range value { + m[k] = v + } + return setNestedFieldNoCopy(obj, m, fields...) +} + +// SetNestedMap sets the map[string]interface{} value of a nested field. +// Returns false if value cannot be set because one of the nesting levels is not a map[string]interface{}. +func SetNestedMap(obj map[string]interface{}, value map[string]interface{}, fields ...string) bool { + return SetNestedField(obj, value, fields...) +} + +// RemoveNestedField removes the nested field from the obj. +func RemoveNestedField(obj map[string]interface{}, fields ...string) { + m := obj + for _, field := range fields[:len(fields)-1] { + if x, ok := m[field].(map[string]interface{}); ok { + m = x + } else { + return + } + } + delete(m, fields[len(fields)-1]) +} + +func getNestedString(obj map[string]interface{}, fields ...string) string { + val, found, err := NestedString(obj, fields...) + if !found || err != nil { + return "" + } + return val +} + +func extractOwnerReference(v map[string]interface{}) metav1.OwnerReference { + // though this field is a *bool, but when decoded from JSON, it's + // unmarshalled as bool. + var controllerPtr *bool + if controller, found, err := NestedBool(v, "controller"); err == nil && found { + controllerPtr = &controller + } + var blockOwnerDeletionPtr *bool + if blockOwnerDeletion, found, err := NestedBool(v, "blockOwnerDeletion"); err == nil && found { + blockOwnerDeletionPtr = &blockOwnerDeletion + } + return metav1.OwnerReference{ + Kind: getNestedString(v, "kind"), + Name: getNestedString(v, "name"), + APIVersion: getNestedString(v, "apiVersion"), + UID: types.UID(getNestedString(v, "uid")), + Controller: controllerPtr, + BlockOwnerDeletion: blockOwnerDeletionPtr, + } +} + +// UnstructuredJSONScheme is capable of converting JSON data into the Unstructured +// type, which can be used for generic access to objects without a predefined scheme. +// TODO: move into serializer/json. +var UnstructuredJSONScheme runtime.Codec = unstructuredJSONScheme{} + +type unstructuredJSONScheme struct{} + +func (s unstructuredJSONScheme) Decode(data []byte, _ *schema.GroupVersionKind, obj runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { + var err error + if obj != nil { + err = s.decodeInto(data, obj) + } else { + obj, err = s.decode(data) + } + + if err != nil { + return nil, nil, err + } + + gvk := obj.GetObjectKind().GroupVersionKind() + if len(gvk.Kind) == 0 { + return nil, &gvk, runtime.NewMissingKindErr(string(data)) + } + + return obj, &gvk, nil +} + +func (unstructuredJSONScheme) Encode(obj runtime.Object, w io.Writer) error { + switch t := obj.(type) { + case *Unstructured: + return json.NewEncoder(w).Encode(t.Object) + case *UnstructuredList: + items := make([]interface{}, 0, len(t.Items)) + for _, i := range t.Items { + items = append(items, i.Object) + } + listObj := make(map[string]interface{}, len(t.Object)+1) + for k, v := range t.Object { // Make a shallow copy + listObj[k] = v + } + listObj["items"] = items + return json.NewEncoder(w).Encode(listObj) + case *runtime.Unknown: + // TODO: Unstructured needs to deal with ContentType. + _, err := w.Write(t.Raw) + return err + default: + return json.NewEncoder(w).Encode(t) + } +} + +func (s unstructuredJSONScheme) decode(data []byte) (runtime.Object, error) { + type detector struct { + Items gojson.RawMessage + } + var det detector + if err := json.Unmarshal(data, &det); err != nil { + return nil, err + } + + if det.Items != nil { + list := &UnstructuredList{} + err := s.decodeToList(data, list) + return list, err + } + + // No Items field, so it wasn't a list. + unstruct := &Unstructured{} + err := s.decodeToUnstructured(data, unstruct) + return unstruct, err +} + +func (s unstructuredJSONScheme) decodeInto(data []byte, obj runtime.Object) error { + switch x := obj.(type) { + case *Unstructured: + return s.decodeToUnstructured(data, x) + case *UnstructuredList: + return s.decodeToList(data, x) + case *runtime.VersionedObjects: + o, err := s.decode(data) + if err == nil { + x.Objects = []runtime.Object{o} + } + return err + default: + return json.Unmarshal(data, x) + } +} + +func (unstructuredJSONScheme) decodeToUnstructured(data []byte, unstruct *Unstructured) error { + m := make(map[string]interface{}) + if err := json.Unmarshal(data, &m); err != nil { + return err + } + + unstruct.Object = m + + return nil +} + +func (s unstructuredJSONScheme) decodeToList(data []byte, list *UnstructuredList) error { + type decodeList struct { + Items []gojson.RawMessage + } + + var dList decodeList + if err := json.Unmarshal(data, &dList); err != nil { + return err + } + + if err := json.Unmarshal(data, &list.Object); err != nil { + return err + } + + // For typed lists, e.g., a PodList, API server doesn't set each item's + // APIVersion and Kind. We need to set it. + listAPIVersion := list.GetAPIVersion() + listKind := list.GetKind() + itemKind := strings.TrimSuffix(listKind, "List") + + delete(list.Object, "items") + list.Items = make([]Unstructured, 0, len(dList.Items)) + for _, i := range dList.Items { + unstruct := &Unstructured{} + if err := s.decodeToUnstructured([]byte(i), unstruct); err != nil { + return err + } + // This is hacky. Set the item's Kind and APIVersion to those inferred + // from the List. + if len(unstruct.GetKind()) == 0 && len(unstruct.GetAPIVersion()) == 0 { + unstruct.SetKind(itemKind) + unstruct.SetAPIVersion(listAPIVersion) + } + list.Items = append(list.Items, *unstruct) + } + return nil +} + +// UnstructuredObjectConverter is an ObjectConverter for use with +// Unstructured objects. Since it has no schema or type information, +// it will only succeed for no-op conversions. This is provided as a +// sane implementation for APIs that require an object converter. +type UnstructuredObjectConverter struct{} + +func (UnstructuredObjectConverter) Convert(in, out, context interface{}) error { + unstructIn, ok := in.(*Unstructured) + if !ok { + return fmt.Errorf("input type %T in not valid for unstructured conversion", in) + } + + unstructOut, ok := out.(*Unstructured) + if !ok { + return fmt.Errorf("output type %T in not valid for unstructured conversion", out) + } + + // maybe deep copy the map? It is documented in the + // ObjectConverter interface that this function is not + // guaranteed to not mutate the input. Or maybe set the input + // object to nil. + unstructOut.Object = unstructIn.Object + return nil +} + +func (UnstructuredObjectConverter) ConvertToVersion(in runtime.Object, target runtime.GroupVersioner) (runtime.Object, error) { + if kind := in.GetObjectKind().GroupVersionKind(); !kind.Empty() { + gvk, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{kind}) + if !ok { + // TODO: should this be a typed error? + return nil, fmt.Errorf("%v is unstructured and is not suitable for converting to %q", kind, target) + } + in.GetObjectKind().SetGroupVersionKind(gvk) + } + return in, nil +} + +func (UnstructuredObjectConverter) ConvertFieldLabel(version, kind, label, value string) (string, string, error) { + return "", "", errors.New("unstructured cannot convert field labels") +} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/helpers_test.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/helpers_test.go new file mode 100644 index 00000000000..9e774d1c19c --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/helpers_test.go @@ -0,0 +1,60 @@ +/* +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 unstructured + +import ( + "io/ioutil" + "sync" + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestCodecOfUnstructuredList tests that there are no data races in Encode(). +// i.e. that it does not mutate the object being encoded. +func TestCodecOfUnstructuredList(t *testing.T) { + var wg sync.WaitGroup + concurrency := 10 + list := UnstructuredList{ + Object: map[string]interface{}{}, + } + wg.Add(concurrency) + for i := 0; i < concurrency; i++ { + go func() { + defer wg.Done() + assert.NoError(t, UnstructuredJSONScheme.Encode(&list, ioutil.Discard)) + }() + } + wg.Wait() +} + +func TestRemoveNestedField(t *testing.T) { + obj := map[string]interface{}{ + "x": map[string]interface{}{ + "y": 1, + "a": "foo", + }, + } + RemoveNestedField(obj, "x", "a") + assert.Len(t, obj["x"], 1) + RemoveNestedField(obj, "x", "y") + assert.Empty(t, obj["x"]) + RemoveNestedField(obj, "x") + assert.Empty(t, obj) + RemoveNestedField(obj, "x") // Remove of a non-existent field + assert.Empty(t, obj) +} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go index 7213b7a1662..2a13330490a 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go @@ -18,20 +18,13 @@ package unstructured import ( "bytes" - gojson "encoding/json" "errors" "fmt" - "io" - "strings" - - "github.com/golang/glog" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/conversion/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/json" utilruntime "k8s.io/apimachinery/pkg/util/runtime" ) @@ -54,39 +47,31 @@ type Unstructured struct { var _ metav1.Object = &Unstructured{} var _ runtime.Unstructured = &Unstructured{} -var _ runtime.Unstructured = &UnstructuredList{} -func (obj *Unstructured) GetObjectKind() schema.ObjectKind { return obj } -func (obj *UnstructuredList) GetObjectKind() schema.ObjectKind { return obj } - -func (obj *Unstructured) IsUnstructuredObject() {} -func (obj *UnstructuredList) IsUnstructuredObject() {} +func (obj *Unstructured) GetObjectKind() schema.ObjectKind { return obj } func (obj *Unstructured) IsList() bool { - if obj.Object != nil { - _, ok := obj.Object["items"] - return ok - } - return false -} -func (obj *UnstructuredList) IsList() bool { return true } - -func (obj *Unstructured) EachListItem(fn func(runtime.Object) error) error { - if obj.Object == nil { - return fmt.Errorf("content is not a list") - } field, ok := obj.Object["items"] if !ok { - return fmt.Errorf("content is not a list") + return false + } + _, ok = field.([]interface{}) + return ok +} + +func (obj *Unstructured) EachListItem(fn func(runtime.Object) error) error { + field, ok := obj.Object["items"] + if !ok { + return errors.New("content is not a list") } items, ok := field.([]interface{}) if !ok { - return nil + return fmt.Errorf("content is not a list: %T", field) } for _, item := range items { child, ok := item.(map[string]interface{}) if !ok { - return fmt.Errorf("items member is not an object") + return fmt.Errorf("items member is not an object: %T", child) } if err := fn(&Unstructured{Object: child}); err != nil { return err @@ -95,15 +80,6 @@ func (obj *Unstructured) EachListItem(fn func(runtime.Object) error) error { return nil } -func (obj *UnstructuredList) EachListItem(fn func(runtime.Object) error) error { - for i := range obj.Items { - if err := fn(&obj.Items[i]); err != nil { - return err - } - } - return nil -} - func (obj *Unstructured) UnstructuredContent() map[string]interface{} { if obj.Object == nil { obj.Object = make(map[string]interface{}) @@ -111,23 +87,8 @@ func (obj *Unstructured) UnstructuredContent() map[string]interface{} { return obj.Object } -// UnstructuredContent returns a map contain an overlay of the Items field onto -// the Object field. Items always overwrites overlay. Changing "items" in the -// returned object will affect items in the underlying Items field, but changing -// the "items" slice itself will have no effect. -// TODO: expose SetUnstructuredContent on runtime.Unstructured that allows -// items to be changed. -func (obj *UnstructuredList) UnstructuredContent() map[string]interface{} { - out := obj.Object - if out == nil { - out = make(map[string]interface{}) - } - items := make([]interface{}, len(obj.Items)) - for i, item := range obj.Items { - items[i] = item.Object - } - out["items"] = items - return out +func (obj *Unstructured) SetUnstructuredContent(content map[string]interface{}) { + obj.Object = content } // MarshalJSON ensures that the unstructured object produces proper @@ -151,227 +112,61 @@ func (in *Unstructured) DeepCopy() *Unstructured { } out := new(Unstructured) *out = *in - out.Object = unstructured.DeepCopyJSON(in.Object) + out.Object = runtime.DeepCopyJSON(in.Object) return out } -func (in *UnstructuredList) DeepCopy() *UnstructuredList { - if in == nil { - return nil - } - out := new(UnstructuredList) - *out = *in - out.Object = unstructured.DeepCopyJSON(in.Object) - out.Items = make([]Unstructured, len(in.Items)) - for i := range in.Items { - in.Items[i].DeepCopyInto(&out.Items[i]) - } - return out -} - -func getNestedField(obj map[string]interface{}, fields ...string) interface{} { - var val interface{} = obj - for _, field := range fields { - if _, ok := val.(map[string]interface{}); !ok { - return nil - } - val = val.(map[string]interface{})[field] - } - return val -} - -func getNestedString(obj map[string]interface{}, fields ...string) string { - if str, ok := getNestedField(obj, fields...).(string); ok { - return str - } - return "" -} - -func getNestedInt64(obj map[string]interface{}, fields ...string) int64 { - if str, ok := getNestedField(obj, fields...).(int64); ok { - return str - } - return 0 -} - -func getNestedInt64Pointer(obj map[string]interface{}, fields ...string) *int64 { - nested := getNestedField(obj, fields...) - switch n := nested.(type) { - case int64: - return &n - case *int64: - return n - default: - return nil - } -} - -func getNestedSlice(obj map[string]interface{}, fields ...string) []string { - if m, ok := getNestedField(obj, fields...).([]interface{}); ok { - strSlice := make([]string, 0, len(m)) - for _, v := range m { - if str, ok := v.(string); ok { - strSlice = append(strSlice, str) - } - } - return strSlice - } - return nil -} - -func getNestedMap(obj map[string]interface{}, fields ...string) map[string]string { - if m, ok := getNestedField(obj, fields...).(map[string]interface{}); ok { - strMap := make(map[string]string, len(m)) - for k, v := range m { - if str, ok := v.(string); ok { - strMap[k] = str - } - } - return strMap - } - return nil -} - -func setNestedField(obj map[string]interface{}, value interface{}, fields ...string) { - m := obj - if len(fields) > 1 { - for _, field := range fields[0 : len(fields)-1] { - if _, ok := m[field].(map[string]interface{}); !ok { - m[field] = make(map[string]interface{}) - } - m = m[field].(map[string]interface{}) - } - } - m[fields[len(fields)-1]] = value -} - -func setNestedSlice(obj map[string]interface{}, value []string, fields ...string) { - m := make([]interface{}, 0, len(value)) - for _, v := range value { - m = append(m, v) - } - setNestedField(obj, m, fields...) -} - -func setNestedMap(obj map[string]interface{}, value map[string]string, fields ...string) { - m := make(map[string]interface{}, len(value)) - for k, v := range value { - m[k] = v - } - setNestedField(obj, m, fields...) -} - func (u *Unstructured) setNestedField(value interface{}, fields ...string) { if u.Object == nil { u.Object = make(map[string]interface{}) } - setNestedField(u.Object, value, fields...) + SetNestedField(u.Object, value, fields...) } func (u *Unstructured) setNestedSlice(value []string, fields ...string) { if u.Object == nil { u.Object = make(map[string]interface{}) } - setNestedSlice(u.Object, value, fields...) + SetNestedStringSlice(u.Object, value, fields...) } func (u *Unstructured) setNestedMap(value map[string]string, fields ...string) { if u.Object == nil { u.Object = make(map[string]interface{}) } - setNestedMap(u.Object, value, fields...) -} - -func extractOwnerReference(src interface{}) metav1.OwnerReference { - v := src.(map[string]interface{}) - // though this field is a *bool, but when decoded from JSON, it's - // unmarshalled as bool. - var controllerPtr *bool - controller, ok := (getNestedField(v, "controller")).(bool) - if !ok { - controllerPtr = nil - } else { - controllerCopy := controller - controllerPtr = &controllerCopy - } - var blockOwnerDeletionPtr *bool - blockOwnerDeletion, ok := (getNestedField(v, "blockOwnerDeletion")).(bool) - if !ok { - blockOwnerDeletionPtr = nil - } else { - blockOwnerDeletionCopy := blockOwnerDeletion - blockOwnerDeletionPtr = &blockOwnerDeletionCopy - } - return metav1.OwnerReference{ - Kind: getNestedString(v, "kind"), - Name: getNestedString(v, "name"), - APIVersion: getNestedString(v, "apiVersion"), - UID: (types.UID)(getNestedString(v, "uid")), - Controller: controllerPtr, - BlockOwnerDeletion: blockOwnerDeletionPtr, - } -} - -func setOwnerReference(src metav1.OwnerReference) map[string]interface{} { - ret := make(map[string]interface{}) - setNestedField(ret, src.Kind, "kind") - setNestedField(ret, src.Name, "name") - setNestedField(ret, src.APIVersion, "apiVersion") - setNestedField(ret, string(src.UID), "uid") - // json.Unmarshal() extracts boolean json fields as bool, not as *bool and hence extractOwnerReference() - // expects bool or a missing field, not *bool. So if pointer is nil, fields are omitted from the ret object. - // If pointer is non-nil, they are set to the referenced value. - if src.Controller != nil { - setNestedField(ret, *src.Controller, "controller") - } - if src.BlockOwnerDeletion != nil { - setNestedField(ret, *src.BlockOwnerDeletion, "blockOwnerDeletion") - } - return ret -} - -func getOwnerReferences(object map[string]interface{}) ([]map[string]interface{}, error) { - field := getNestedField(object, "metadata", "ownerReferences") - if field == nil { - return nil, fmt.Errorf("cannot find field metadata.ownerReferences in %v", object) - } - ownerReferences, ok := field.([]map[string]interface{}) - if ok { - return ownerReferences, nil - } - // TODO: This is hacky... - interfaces, ok := field.([]interface{}) - if !ok { - return nil, fmt.Errorf("expect metadata.ownerReferences to be a slice in %#v", object) - } - ownerReferences = make([]map[string]interface{}, 0, len(interfaces)) - for i := 0; i < len(interfaces); i++ { - r, ok := interfaces[i].(map[string]interface{}) - if !ok { - return nil, fmt.Errorf("expect element metadata.ownerReferences to be a map[string]interface{} in %#v", object) - } - ownerReferences = append(ownerReferences, r) - } - return ownerReferences, nil + SetNestedStringMap(u.Object, value, fields...) } func (u *Unstructured) GetOwnerReferences() []metav1.OwnerReference { - original, err := getOwnerReferences(u.Object) - if err != nil { - glog.V(6).Info(err) + field, found, err := nestedFieldNoCopy(u.Object, "metadata", "ownerReferences") + if !found || err != nil { + return nil + } + original, ok := field.([]interface{}) + if !ok { return nil } ret := make([]metav1.OwnerReference, 0, len(original)) - for i := 0; i < len(original); i++ { - ret = append(ret, extractOwnerReference(original[i])) + for _, obj := range original { + o, ok := obj.(map[string]interface{}) + if !ok { + // expected map[string]interface{}, got something else + return nil + } + ret = append(ret, extractOwnerReference(o)) } return ret } func (u *Unstructured) SetOwnerReferences(references []metav1.OwnerReference) { - var newReferences = make([]map[string]interface{}, 0, len(references)) - for i := 0; i < len(references); i++ { - newReferences = append(newReferences, setOwnerReference(references[i])) + newReferences := make([]interface{}, 0, len(references)) + for _, reference := range references { + out, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&reference) + if err != nil { + utilruntime.HandleError(fmt.Errorf("unable to convert Owner Reference: %v", err)) + continue + } + newReferences = append(newReferences, out) } u.setNestedField(newReferences, "metadata", "ownerReferences") } @@ -433,7 +228,11 @@ func (u *Unstructured) SetResourceVersion(version string) { } func (u *Unstructured) GetGeneration() int64 { - return getNestedInt64(u.Object, "metadata", "generation") + val, found, err := NestedInt64(u.Object, "metadata", "generation") + if !found || err != nil { + return 0 + } + return val } func (u *Unstructured) SetGeneration(generation int64) { @@ -464,6 +263,10 @@ func (u *Unstructured) GetCreationTimestamp() metav1.Time { func (u *Unstructured) SetCreationTimestamp(timestamp metav1.Time) { ts, _ := timestamp.MarshalQueryParameter() + if len(ts) == 0 || timestamp.Time.IsZero() { + RemoveNestedField(u.Object, "metadata", "creationTimestamp") + return + } u.setNestedField(ts, "metadata", "creationTimestamp") } @@ -478,7 +281,7 @@ func (u *Unstructured) GetDeletionTimestamp() *metav1.Time { func (u *Unstructured) SetDeletionTimestamp(timestamp *metav1.Time) { if timestamp == nil { - u.setNestedField(nil, "metadata", "deletionTimestamp") + RemoveNestedField(u.Object, "metadata", "deletionTimestamp") return } ts, _ := timestamp.MarshalQueryParameter() @@ -486,15 +289,24 @@ func (u *Unstructured) SetDeletionTimestamp(timestamp *metav1.Time) { } func (u *Unstructured) GetDeletionGracePeriodSeconds() *int64 { - return getNestedInt64Pointer(u.Object, "metadata", "deletionGracePeriodSeconds") + val, found, err := NestedInt64(u.Object, "metadata", "deletionGracePeriodSeconds") + if !found || err != nil { + return nil + } + return &val } func (u *Unstructured) SetDeletionGracePeriodSeconds(deletionGracePeriodSeconds *int64) { - u.setNestedField(deletionGracePeriodSeconds, "metadata", "deletionGracePeriodSeconds") + if deletionGracePeriodSeconds == nil { + RemoveNestedField(u.Object, "metadata", "deletionGracePeriodSeconds") + return + } + u.setNestedField(*deletionGracePeriodSeconds, "metadata", "deletionGracePeriodSeconds") } func (u *Unstructured) GetLabels() map[string]string { - return getNestedMap(u.Object, "metadata", "labels") + m, _, _ := NestedStringMap(u.Object, "metadata", "labels") + return m } func (u *Unstructured) SetLabels(labels map[string]string) { @@ -502,7 +314,8 @@ func (u *Unstructured) SetLabels(labels map[string]string) { } func (u *Unstructured) GetAnnotations() map[string]string { - return getNestedMap(u.Object, "metadata", "annotations") + m, _, _ := NestedStringMap(u.Object, "metadata", "annotations") + return m } func (u *Unstructured) SetAnnotations(annotations map[string]string) { @@ -523,41 +336,34 @@ func (u *Unstructured) GroupVersionKind() schema.GroupVersionKind { return gvk } -var converter = unstructured.NewConverter(false) - func (u *Unstructured) GetInitializers() *metav1.Initializers { - field := getNestedField(u.Object, "metadata", "initializers") - if field == nil { - return nil - } - obj, ok := field.(map[string]interface{}) - if !ok { + m, found, err := nestedMapNoCopy(u.Object, "metadata", "initializers") + if !found || err != nil { return nil } out := &metav1.Initializers{} - if err := converter.FromUnstructured(obj, out); err != nil { + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(m, out); err != nil { utilruntime.HandleError(fmt.Errorf("unable to retrieve initializers for object: %v", err)) + return nil } return out } func (u *Unstructured) SetInitializers(initializers *metav1.Initializers) { - if u.Object == nil { - u.Object = make(map[string]interface{}) - } if initializers == nil { - setNestedField(u.Object, nil, "metadata", "initializers") + RemoveNestedField(u.Object, "metadata", "initializers") return } - out, err := converter.ToUnstructured(initializers) + out, err := runtime.DefaultUnstructuredConverter.ToUnstructured(initializers) if err != nil { utilruntime.HandleError(fmt.Errorf("unable to retrieve initializers for object: %v", err)) } - setNestedField(u.Object, out, "metadata", "initializers") + u.setNestedField(out, "metadata", "initializers") } func (u *Unstructured) GetFinalizers() []string { - return getNestedSlice(u.Object, "metadata", "finalizers") + val, _, _ := NestedStringSlice(u.Object, "metadata", "finalizers") + return val } func (u *Unstructured) SetFinalizers(finalizers []string) { @@ -571,272 +377,3 @@ func (u *Unstructured) GetClusterName() string { func (u *Unstructured) SetClusterName(clusterName string) { u.setNestedField(clusterName, "metadata", "clusterName") } - -// UnstructuredList allows lists that do not have Golang structs -// registered to be manipulated generically. This can be used to deal -// with the API lists from a plug-in. -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// +k8s:deepcopy-gen=true -type UnstructuredList struct { - Object map[string]interface{} - - // Items is a list of unstructured objects. - Items []Unstructured `json:"items"` -} - -var _ metav1.ListInterface = &UnstructuredList{} - -// MarshalJSON ensures that the unstructured list object produces proper -// JSON when passed to Go's standard JSON library. -func (u *UnstructuredList) MarshalJSON() ([]byte, error) { - var buf bytes.Buffer - err := UnstructuredJSONScheme.Encode(u, &buf) - return buf.Bytes(), err -} - -// UnmarshalJSON ensures that the unstructured list object properly -// decodes JSON when passed to Go's standard JSON library. -func (u *UnstructuredList) UnmarshalJSON(b []byte) error { - _, _, err := UnstructuredJSONScheme.Decode(b, nil, u) - return err -} - -func (u *UnstructuredList) setNestedField(value interface{}, fields ...string) { - if u.Object == nil { - u.Object = make(map[string]interface{}) - } - setNestedField(u.Object, value, fields...) -} - -func (u *UnstructuredList) GetAPIVersion() string { - return getNestedString(u.Object, "apiVersion") -} - -func (u *UnstructuredList) SetAPIVersion(version string) { - u.setNestedField(version, "apiVersion") -} - -func (u *UnstructuredList) GetKind() string { - return getNestedString(u.Object, "kind") -} - -func (u *UnstructuredList) SetKind(kind string) { - u.setNestedField(kind, "kind") -} - -func (u *UnstructuredList) GetResourceVersion() string { - return getNestedString(u.Object, "metadata", "resourceVersion") -} - -func (u *UnstructuredList) SetResourceVersion(version string) { - u.setNestedField(version, "metadata", "resourceVersion") -} - -func (u *UnstructuredList) GetSelfLink() string { - return getNestedString(u.Object, "metadata", "selfLink") -} - -func (u *UnstructuredList) SetSelfLink(selfLink string) { - u.setNestedField(selfLink, "metadata", "selfLink") -} - -func (u *UnstructuredList) GetContinue() string { - return getNestedString(u.Object, "metadata", "continue") -} - -func (u *UnstructuredList) SetContinue(c string) { - u.setNestedField(c, "metadata", "continue") -} - -func (u *UnstructuredList) SetGroupVersionKind(gvk schema.GroupVersionKind) { - u.SetAPIVersion(gvk.GroupVersion().String()) - u.SetKind(gvk.Kind) -} - -func (u *UnstructuredList) GroupVersionKind() schema.GroupVersionKind { - gv, err := schema.ParseGroupVersion(u.GetAPIVersion()) - if err != nil { - return schema.GroupVersionKind{} - } - gvk := gv.WithKind(u.GetKind()) - return gvk -} - -// UnstructuredJSONScheme is capable of converting JSON data into the Unstructured -// type, which can be used for generic access to objects without a predefined scheme. -// TODO: move into serializer/json. -var UnstructuredJSONScheme runtime.Codec = unstructuredJSONScheme{} - -type unstructuredJSONScheme struct{} - -func (s unstructuredJSONScheme) Decode(data []byte, _ *schema.GroupVersionKind, obj runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { - var err error - if obj != nil { - err = s.decodeInto(data, obj) - } else { - obj, err = s.decode(data) - } - - if err != nil { - return nil, nil, err - } - - gvk := obj.GetObjectKind().GroupVersionKind() - if len(gvk.Kind) == 0 { - return nil, &gvk, runtime.NewMissingKindErr(string(data)) - } - - return obj, &gvk, nil -} - -func (unstructuredJSONScheme) Encode(obj runtime.Object, w io.Writer) error { - switch t := obj.(type) { - case *Unstructured: - return json.NewEncoder(w).Encode(t.Object) - case *UnstructuredList: - items := make([]map[string]interface{}, 0, len(t.Items)) - for _, i := range t.Items { - items = append(items, i.Object) - } - listObj := make(map[string]interface{}, len(t.Object)+1) - for k, v := range t.Object { // Make a shallow copy - listObj[k] = v - } - listObj["items"] = items - return json.NewEncoder(w).Encode(listObj) - case *runtime.Unknown: - // TODO: Unstructured needs to deal with ContentType. - _, err := w.Write(t.Raw) - return err - default: - return json.NewEncoder(w).Encode(t) - } -} - -func (s unstructuredJSONScheme) decode(data []byte) (runtime.Object, error) { - type detector struct { - Items gojson.RawMessage - } - var det detector - if err := json.Unmarshal(data, &det); err != nil { - return nil, err - } - - if det.Items != nil { - list := &UnstructuredList{} - err := s.decodeToList(data, list) - return list, err - } - - // No Items field, so it wasn't a list. - unstruct := &Unstructured{} - err := s.decodeToUnstructured(data, unstruct) - return unstruct, err -} - -func (s unstructuredJSONScheme) decodeInto(data []byte, obj runtime.Object) error { - switch x := obj.(type) { - case *Unstructured: - return s.decodeToUnstructured(data, x) - case *UnstructuredList: - return s.decodeToList(data, x) - case *runtime.VersionedObjects: - o, err := s.decode(data) - if err == nil { - x.Objects = []runtime.Object{o} - } - return err - default: - return json.Unmarshal(data, x) - } -} - -func (unstructuredJSONScheme) decodeToUnstructured(data []byte, unstruct *Unstructured) error { - m := make(map[string]interface{}) - if err := json.Unmarshal(data, &m); err != nil { - return err - } - - unstruct.Object = m - - return nil -} - -func (s unstructuredJSONScheme) decodeToList(data []byte, list *UnstructuredList) error { - type decodeList struct { - Items []gojson.RawMessage - } - - var dList decodeList - if err := json.Unmarshal(data, &dList); err != nil { - return err - } - - if err := json.Unmarshal(data, &list.Object); err != nil { - return err - } - - // For typed lists, e.g., a PodList, API server doesn't set each item's - // APIVersion and Kind. We need to set it. - listAPIVersion := list.GetAPIVersion() - listKind := list.GetKind() - itemKind := strings.TrimSuffix(listKind, "List") - - delete(list.Object, "items") - list.Items = nil - for _, i := range dList.Items { - unstruct := &Unstructured{} - if err := s.decodeToUnstructured([]byte(i), unstruct); err != nil { - return err - } - // This is hacky. Set the item's Kind and APIVersion to those inferred - // from the List. - if len(unstruct.GetKind()) == 0 && len(unstruct.GetAPIVersion()) == 0 { - unstruct.SetKind(itemKind) - unstruct.SetAPIVersion(listAPIVersion) - } - list.Items = append(list.Items, *unstruct) - } - return nil -} - -// UnstructuredObjectConverter is an ObjectConverter for use with -// Unstructured objects. Since it has no schema or type information, -// it will only succeed for no-op conversions. This is provided as a -// sane implementation for APIs that require an object converter. -type UnstructuredObjectConverter struct{} - -func (UnstructuredObjectConverter) Convert(in, out, context interface{}) error { - unstructIn, ok := in.(*Unstructured) - if !ok { - return fmt.Errorf("input type %T in not valid for unstructured conversion", in) - } - - unstructOut, ok := out.(*Unstructured) - if !ok { - return fmt.Errorf("output type %T in not valid for unstructured conversion", out) - } - - // maybe deep copy the map? It is documented in the - // ObjectConverter interface that this function is not - // guaranteeed to not mutate the input. Or maybe set the input - // object to nil. - unstructOut.Object = unstructIn.Object - return nil -} - -func (UnstructuredObjectConverter) ConvertToVersion(in runtime.Object, target runtime.GroupVersioner) (runtime.Object, error) { - if kind := in.GetObjectKind().GroupVersionKind(); !kind.Empty() { - gvk, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{kind}) - if !ok { - // TODO: should this be a typed error? - return nil, fmt.Errorf("%v is unstructured and is not suitable for converting to %q", kind, target) - } - in.GetObjectKind().SetGroupVersionKind(gvk) - } - return in, nil -} - -func (UnstructuredObjectConverter) ConvertFieldLabel(version, kind, label, value string) (string, string, error) { - return "", "", errors.New("unstructured cannot convert field labels") -} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_list.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_list.go new file mode 100644 index 00000000000..57d78a09dcc --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_list.go @@ -0,0 +1,189 @@ +/* +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 unstructured + +import ( + "bytes" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var _ runtime.Unstructured = &UnstructuredList{} +var _ metav1.ListInterface = &UnstructuredList{} + +// UnstructuredList allows lists that do not have Golang structs +// registered to be manipulated generically. This can be used to deal +// with the API lists from a plug-in. +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:deepcopy-gen=true +type UnstructuredList struct { + Object map[string]interface{} + + // Items is a list of unstructured objects. + Items []Unstructured `json:"items"` +} + +func (u *UnstructuredList) GetObjectKind() schema.ObjectKind { return u } + +func (u *UnstructuredList) IsList() bool { return true } + +func (u *UnstructuredList) EachListItem(fn func(runtime.Object) error) error { + for i := range u.Items { + if err := fn(&u.Items[i]); err != nil { + return err + } + } + return nil +} + +// UnstructuredContent returns a map contain an overlay of the Items field onto +// the Object field. Items always overwrites overlay. Changing "items" in the +// returned object will affect items in the underlying Items field, but changing +// the "items" slice itself will have no effect. +// TODO: expose SetUnstructuredContent on runtime.Unstructured that allows +// items to be changed. +func (u *UnstructuredList) UnstructuredContent() map[string]interface{} { + out := u.Object + if out == nil { + out = make(map[string]interface{}) + } + items := make([]interface{}, len(u.Items)) + for i, item := range u.Items { + items[i] = item.Object + } + out["items"] = items + return out +} + +// SetUnstructuredContent obeys the conventions of List and keeps Items and the items +// array in sync. If items is not an array of objects in the incoming map, then any +// mismatched item will be removed. +func (obj *UnstructuredList) SetUnstructuredContent(content map[string]interface{}) { + obj.Object = content + if content == nil { + obj.Items = nil + return + } + items, ok := obj.Object["items"].([]interface{}) + if !ok || items == nil { + items = []interface{}{} + } + unstructuredItems := make([]Unstructured, 0, len(items)) + newItems := make([]interface{}, 0, len(items)) + for _, item := range items { + o, ok := item.(map[string]interface{}) + if !ok { + continue + } + unstructuredItems = append(unstructuredItems, Unstructured{Object: o}) + newItems = append(newItems, o) + } + obj.Items = unstructuredItems + obj.Object["items"] = newItems +} + +func (u *UnstructuredList) DeepCopy() *UnstructuredList { + if u == nil { + return nil + } + out := new(UnstructuredList) + *out = *u + out.Object = runtime.DeepCopyJSON(u.Object) + out.Items = make([]Unstructured, len(u.Items)) + for i := range u.Items { + u.Items[i].DeepCopyInto(&out.Items[i]) + } + return out +} + +// MarshalJSON ensures that the unstructured list object produces proper +// JSON when passed to Go's standard JSON library. +func (u *UnstructuredList) MarshalJSON() ([]byte, error) { + var buf bytes.Buffer + err := UnstructuredJSONScheme.Encode(u, &buf) + return buf.Bytes(), err +} + +// UnmarshalJSON ensures that the unstructured list object properly +// decodes JSON when passed to Go's standard JSON library. +func (u *UnstructuredList) UnmarshalJSON(b []byte) error { + _, _, err := UnstructuredJSONScheme.Decode(b, nil, u) + return err +} + +func (u *UnstructuredList) GetAPIVersion() string { + return getNestedString(u.Object, "apiVersion") +} + +func (u *UnstructuredList) SetAPIVersion(version string) { + u.setNestedField(version, "apiVersion") +} + +func (u *UnstructuredList) GetKind() string { + return getNestedString(u.Object, "kind") +} + +func (u *UnstructuredList) SetKind(kind string) { + u.setNestedField(kind, "kind") +} + +func (u *UnstructuredList) GetResourceVersion() string { + return getNestedString(u.Object, "metadata", "resourceVersion") +} + +func (u *UnstructuredList) SetResourceVersion(version string) { + u.setNestedField(version, "metadata", "resourceVersion") +} + +func (u *UnstructuredList) GetSelfLink() string { + return getNestedString(u.Object, "metadata", "selfLink") +} + +func (u *UnstructuredList) SetSelfLink(selfLink string) { + u.setNestedField(selfLink, "metadata", "selfLink") +} + +func (u *UnstructuredList) GetContinue() string { + return getNestedString(u.Object, "metadata", "continue") +} + +func (u *UnstructuredList) SetContinue(c string) { + u.setNestedField(c, "metadata", "continue") +} + +func (u *UnstructuredList) SetGroupVersionKind(gvk schema.GroupVersionKind) { + u.SetAPIVersion(gvk.GroupVersion().String()) + u.SetKind(gvk.Kind) +} + +func (u *UnstructuredList) GroupVersionKind() schema.GroupVersionKind { + gv, err := schema.ParseGroupVersion(u.GetAPIVersion()) + if err != nil { + return schema.GroupVersionKind{} + } + gvk := gv.WithKind(u.GetKind()) + return gvk +} + +func (u *UnstructuredList) setNestedField(value interface{}, fields ...string) { + if u.Object == nil { + u.Object = make(map[string]interface{}) + } + SetNestedField(u.Object, value, fields...) +} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_list_test.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_list_test.go new file mode 100644 index 00000000000..04ada449b95 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_list_test.go @@ -0,0 +1,86 @@ +/* +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 unstructured + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestUnstructuredList(t *testing.T) { + list := &UnstructuredList{ + Object: map[string]interface{}{"kind": "List", "apiVersion": "v1"}, + Items: []Unstructured{ + {Object: map[string]interface{}{"kind": "Pod", "apiVersion": "v1", "metadata": map[string]interface{}{"name": "test"}}}, + }, + } + content := list.UnstructuredContent() + items := content["items"].([]interface{}) + require.Len(t, items, 1) + val, found, err := NestedFieldCopy(items[0].(map[string]interface{}), "metadata", "name") + require.True(t, found) + require.NoError(t, err) + assert.Equal(t, "test", val) +} + +func TestNilDeletionTimestamp(t *testing.T) { + var u Unstructured + del := u.GetDeletionTimestamp() + if del != nil { + t.Errorf("unexpected non-nil deletion timestamp: %v", del) + } + u.SetDeletionTimestamp(u.GetDeletionTimestamp()) + del = u.GetDeletionTimestamp() + if del != nil { + t.Errorf("unexpected non-nil deletion timestamp: %v", del) + } + _, ok := u.Object["metadata"] + assert.False(t, ok) + + now := metav1.Now() + u.SetDeletionTimestamp(&now) + assert.Equal(t, now.Unix(), u.GetDeletionTimestamp().Unix()) + u.SetDeletionTimestamp(nil) + metadata := u.Object["metadata"].(map[string]interface{}) + _, ok = metadata["deletionTimestamp"] + assert.False(t, ok) +} + +func TestEmptyCreationTimestampIsOmitted(t *testing.T) { + var u Unstructured + now := metav1.Now() + + // set an initial creationTimestamp and ensure the field exists + u.SetCreationTimestamp(now) + metadata := u.Object["metadata"].(map[string]interface{}) + creationTimestamp, exists := metadata["creationTimestamp"] + if !exists { + t.Fatalf("unexpected missing creationTimestamp") + } + + // set an empty timestamp and ensure the field no longer exists + u.SetCreationTimestamp(metav1.Time{}) + metadata = u.Object["metadata"].(map[string]interface{}) + creationTimestamp, exists = metadata["creationTimestamp"] + if exists { + t.Errorf("unexpected creation timestamp field: %q", creationTimestamp) + } +} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_test.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_test.go deleted file mode 100644 index 41b8771ff8b..00000000000 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_test.go +++ /dev/null @@ -1,78 +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 unstructured - -import ( - "io/ioutil" - "sync" - "testing" - - "github.com/stretchr/testify/assert" -) - -// TestCodecOfUnstructuredList tests that there are no data races in Encode(). -// i.e. that it does not mutate the object being encoded. -func TestCodecOfUnstructuredList(t *testing.T) { - var wg sync.WaitGroup - concurrency := 10 - list := UnstructuredList{ - Object: map[string]interface{}{}, - } - wg.Add(concurrency) - for i := 0; i < concurrency; i++ { - go func() { - defer wg.Done() - assert.NoError(t, UnstructuredJSONScheme.Encode(&list, ioutil.Discard)) - }() - } - wg.Wait() -} - -func TestUnstructuredList(t *testing.T) { - list := &UnstructuredList{ - Object: map[string]interface{}{"kind": "List", "apiVersion": "v1"}, - Items: []Unstructured{ - {Object: map[string]interface{}{"kind": "Pod", "apiVersion": "v1", "metadata": map[string]interface{}{"name": "test"}}}, - }, - } - content := list.UnstructuredContent() - items := content["items"].([]interface{}) - if len(items) != 1 { - t.Fatalf("unexpected items: %#v", items) - } - if getNestedField(items[0].(map[string]interface{}), "metadata", "name") != "test" { - t.Fatalf("unexpected fields: %#v", items[0]) - } -} - -func TestNilDeletionTimestamp(t *testing.T) { - var u Unstructured - del := u.GetDeletionTimestamp() - if del != nil { - t.Errorf("unexpected non-nil deletion timestamp: %v", del) - } - u.SetDeletionTimestamp(u.GetDeletionTimestamp()) - del = u.GetDeletionTimestamp() - if del != nil { - t.Errorf("unexpected non-nil deletion timestamp: %v", del) - } - metadata := u.Object["metadata"].(map[string]interface{}) - deletionTimestamp := metadata["deletionTimestamp"] - if deletionTimestamp != nil { - t.Errorf("unexpected deletion timestamp field: %q", deletionTimestamp) - } -} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/zz_generated.deepcopy.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/zz_generated.deepcopy.go index 248acf989bf..e3bae45ecc0 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,27 +21,9 @@ limitations under the License. package unstructured import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -// GetGeneratedDeepCopyFuncs returns the generated funcs, since we aren't registering them. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func GetGeneratedDeepCopyFuncs() []conversion.GeneratedDeepCopyFunc { - return []conversion.GeneratedDeepCopyFunc{ - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Unstructured).DeepCopyInto(out.(*Unstructured)) - return nil - }, InType: reflect.TypeOf(&Unstructured{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*UnstructuredList).DeepCopyInto(out.(*UnstructuredList)) - return nil - }, InType: reflect.TypeOf(&UnstructuredList{})}, - } -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Unstructured) DeepCopyInto(out *Unstructured) { clone := in.DeepCopy() diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation/BUILD b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation/BUILD index 216ef6368b2..436ae33a5c3 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["validation_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/apis/meta/v1/validation", - library = ":go_default_library", deps = ["//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library"], ) 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 c73e777b50b..2aa20902547 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 @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,164 +21,10 @@ limitations under the License. package v1 import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" types "k8s.io/apimachinery/pkg/types" - reflect "reflect" ) -// GetGeneratedDeepCopyFuncs returns the generated funcs, since we aren't registering them. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func GetGeneratedDeepCopyFuncs() []conversion.GeneratedDeepCopyFunc { - return []conversion.GeneratedDeepCopyFunc{ - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*APIGroup).DeepCopyInto(out.(*APIGroup)) - return nil - }, InType: reflect.TypeOf(&APIGroup{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*APIGroupList).DeepCopyInto(out.(*APIGroupList)) - return nil - }, InType: reflect.TypeOf(&APIGroupList{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*APIResource).DeepCopyInto(out.(*APIResource)) - return nil - }, InType: reflect.TypeOf(&APIResource{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*APIResourceList).DeepCopyInto(out.(*APIResourceList)) - return nil - }, InType: reflect.TypeOf(&APIResourceList{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*APIVersions).DeepCopyInto(out.(*APIVersions)) - return nil - }, InType: reflect.TypeOf(&APIVersions{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*DeleteOptions).DeepCopyInto(out.(*DeleteOptions)) - return nil - }, InType: reflect.TypeOf(&DeleteOptions{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Duration).DeepCopyInto(out.(*Duration)) - return nil - }, InType: reflect.TypeOf(&Duration{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExportOptions).DeepCopyInto(out.(*ExportOptions)) - return nil - }, InType: reflect.TypeOf(&ExportOptions{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*GetOptions).DeepCopyInto(out.(*GetOptions)) - return nil - }, InType: reflect.TypeOf(&GetOptions{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*GroupKind).DeepCopyInto(out.(*GroupKind)) - return nil - }, InType: reflect.TypeOf(&GroupKind{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*GroupResource).DeepCopyInto(out.(*GroupResource)) - return nil - }, InType: reflect.TypeOf(&GroupResource{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*GroupVersion).DeepCopyInto(out.(*GroupVersion)) - return nil - }, InType: reflect.TypeOf(&GroupVersion{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*GroupVersionForDiscovery).DeepCopyInto(out.(*GroupVersionForDiscovery)) - return nil - }, InType: reflect.TypeOf(&GroupVersionForDiscovery{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*GroupVersionKind).DeepCopyInto(out.(*GroupVersionKind)) - return nil - }, InType: reflect.TypeOf(&GroupVersionKind{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*GroupVersionResource).DeepCopyInto(out.(*GroupVersionResource)) - return nil - }, InType: reflect.TypeOf(&GroupVersionResource{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Initializer).DeepCopyInto(out.(*Initializer)) - return nil - }, InType: reflect.TypeOf(&Initializer{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Initializers).DeepCopyInto(out.(*Initializers)) - return nil - }, InType: reflect.TypeOf(&Initializers{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*InternalEvent).DeepCopyInto(out.(*InternalEvent)) - return nil - }, InType: reflect.TypeOf(&InternalEvent{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LabelSelector).DeepCopyInto(out.(*LabelSelector)) - return nil - }, InType: reflect.TypeOf(&LabelSelector{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*LabelSelectorRequirement).DeepCopyInto(out.(*LabelSelectorRequirement)) - return nil - }, InType: reflect.TypeOf(&LabelSelectorRequirement{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*List).DeepCopyInto(out.(*List)) - return nil - }, InType: reflect.TypeOf(&List{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ListMeta).DeepCopyInto(out.(*ListMeta)) - return nil - }, InType: reflect.TypeOf(&ListMeta{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ListOptions).DeepCopyInto(out.(*ListOptions)) - return nil - }, InType: reflect.TypeOf(&ListOptions{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*MicroTime).DeepCopyInto(out.(*MicroTime)) - return nil - }, InType: reflect.TypeOf(&MicroTime{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ObjectMeta).DeepCopyInto(out.(*ObjectMeta)) - return nil - }, InType: reflect.TypeOf(&ObjectMeta{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*OwnerReference).DeepCopyInto(out.(*OwnerReference)) - return nil - }, InType: reflect.TypeOf(&OwnerReference{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Patch).DeepCopyInto(out.(*Patch)) - return nil - }, InType: reflect.TypeOf(&Patch{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Preconditions).DeepCopyInto(out.(*Preconditions)) - return nil - }, InType: reflect.TypeOf(&Preconditions{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RootPaths).DeepCopyInto(out.(*RootPaths)) - return nil - }, InType: reflect.TypeOf(&RootPaths{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ServerAddressByClientCIDR).DeepCopyInto(out.(*ServerAddressByClientCIDR)) - return nil - }, InType: reflect.TypeOf(&ServerAddressByClientCIDR{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Status).DeepCopyInto(out.(*Status)) - return nil - }, InType: reflect.TypeOf(&Status{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StatusCause).DeepCopyInto(out.(*StatusCause)) - return nil - }, InType: reflect.TypeOf(&StatusCause{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*StatusDetails).DeepCopyInto(out.(*StatusDetails)) - return nil - }, InType: reflect.TypeOf(&StatusDetails{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Time).DeepCopyInto(out.(*Time)) - return nil - }, InType: reflect.TypeOf(&Time{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Timestamp).DeepCopyInto(out.(*Timestamp)) - return nil - }, InType: reflect.TypeOf(&Timestamp{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*WatchEvent).DeepCopyInto(out.(*WatchEvent)) - return nil - }, InType: reflect.TypeOf(&WatchEvent{})}, - } -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *APIGroup) DeepCopyInto(out *APIGroup) { *out = *in diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.defaults.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.defaults.go index 6df448eb9fd..88d7af085be 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.defaults.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1alpha1/generated.pb.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1alpha1/generated.pb.go index 2d43bf94f65..4fcddb3ab3f 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1alpha1/generated.pb.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1alpha1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1alpha1/generated.proto b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1alpha1/generated.proto index f3aedd8014c..7509f6e867b 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1alpha1/generated.proto +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1alpha1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1alpha1/zz_generated.deepcopy.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1alpha1/zz_generated.deepcopy.go index 043456cdb1d..1b3172004d6 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1alpha1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1alpha1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,47 +21,9 @@ limitations under the License. package v1alpha1 import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -// GetGeneratedDeepCopyFuncs returns the generated funcs, since we aren't registering them. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func GetGeneratedDeepCopyFuncs() []conversion.GeneratedDeepCopyFunc { - return []conversion.GeneratedDeepCopyFunc{ - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PartialObjectMetadata).DeepCopyInto(out.(*PartialObjectMetadata)) - return nil - }, InType: reflect.TypeOf(&PartialObjectMetadata{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PartialObjectMetadataList).DeepCopyInto(out.(*PartialObjectMetadataList)) - return nil - }, InType: reflect.TypeOf(&PartialObjectMetadataList{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Table).DeepCopyInto(out.(*Table)) - return nil - }, InType: reflect.TypeOf(&Table{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*TableColumnDefinition).DeepCopyInto(out.(*TableColumnDefinition)) - return nil - }, InType: reflect.TypeOf(&TableColumnDefinition{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*TableOptions).DeepCopyInto(out.(*TableOptions)) - return nil - }, InType: reflect.TypeOf(&TableOptions{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*TableRow).DeepCopyInto(out.(*TableRow)) - return nil - }, InType: reflect.TypeOf(&TableRow{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*TableRowCondition).DeepCopyInto(out.(*TableRowCondition)) - return nil - }, InType: reflect.TypeOf(&TableRowCondition{})}, - } -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PartialObjectMetadata) DeepCopyInto(out *PartialObjectMetadata) { *out = *in diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1alpha1/zz_generated.defaults.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1alpha1/zz_generated.defaults.go index 7e6df29d4ae..5e24d22cacd 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1alpha1/zz_generated.defaults.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1alpha1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/BUILD b/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/BUILD index 1f2b043a300..817a0e6ad4b 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/BUILD @@ -16,7 +16,6 @@ go_library( importpath = "k8s.io/apimachinery/pkg/apis/testapigroup", deps = [ "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/doc.go b/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/doc.go index f8c455eecb0..419d0cb4659 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/doc.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +groupName=testapigroup.apimachinery.k8s.io // // package testapigroup contains an testapigroup API used to demonstrate how to create api groups. Moreover, this is diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/install/BUILD b/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/install/BUILD index d0e74e87091..b2107d46f02 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/install/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/install/BUILD @@ -22,8 +22,8 @@ go_library( go_test( name = "go_default_test", srcs = ["roundtrip_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/apis/testapigroup/install", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/api/testing/roundtrip:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/testapigroup/fuzzer:go_default_library", diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/register.go b/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/register.go index cbf628fc780..ab2afcf7298 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/register.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/register.go @@ -42,7 +42,7 @@ func Resource(resource string) schema.GroupResource { return SchemeGroupVersion.WithResource(resource).GroupResource() } -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Carp{}, diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1/doc.go b/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1/doc.go index 9f935b3d4bf..732d2038130 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1/doc.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:conversion-gen=k8s.io/apimachinery/pkg/apis/testapigroup // +k8s:openapi-gen=false // +k8s:defaulter-gen=TypeMeta diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1/register.go b/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1/register.go index d04bc1b58ab..8658a683fc2 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1/register.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1/register.go @@ -53,7 +53,7 @@ func init() { localSchemeBuilder.Register(addKnownTypes, addConversionFuncs, addDefaultingFuncs) } -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Carp{}, diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.conversion.go b/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.conversion.go index bc060c309bf..67f5f803e67 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.conversion.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,11 +21,12 @@ limitations under the License. package v1 import ( + unsafe "unsafe" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" testapigroup "k8s.io/apimachinery/pkg/apis/testapigroup" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - unsafe "unsafe" ) func init() { diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.deepcopy.go b/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.deepcopy.go index 82e8dbfe76f..d10f8865a2f 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,44 +22,9 @@ package v1 import ( meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Carp).DeepCopyInto(out.(*Carp)) - return nil - }, InType: reflect.TypeOf(&Carp{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CarpCondition).DeepCopyInto(out.(*CarpCondition)) - return nil - }, InType: reflect.TypeOf(&CarpCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CarpList).DeepCopyInto(out.(*CarpList)) - return nil - }, InType: reflect.TypeOf(&CarpList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CarpSpec).DeepCopyInto(out.(*CarpSpec)) - return nil - }, InType: reflect.TypeOf(&CarpSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CarpStatus).DeepCopyInto(out.(*CarpStatus)) - return nil - }, InType: reflect.TypeOf(&CarpStatus{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Carp) DeepCopyInto(out *Carp) { *out = *in diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.defaults.go b/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.defaults.go index 6df448eb9fd..88d7af085be 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.defaults.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/zz_generated.deepcopy.go b/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/zz_generated.deepcopy.go index a2d6b83b6d9..77a7bcae8f3 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,44 +22,9 @@ package testapigroup import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Carp).DeepCopyInto(out.(*Carp)) - return nil - }, InType: reflect.TypeOf(&Carp{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CarpCondition).DeepCopyInto(out.(*CarpCondition)) - return nil - }, InType: reflect.TypeOf(&CarpCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CarpList).DeepCopyInto(out.(*CarpList)) - return nil - }, InType: reflect.TypeOf(&CarpList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CarpSpec).DeepCopyInto(out.(*CarpSpec)) - return nil - }, InType: reflect.TypeOf(&CarpSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*CarpStatus).DeepCopyInto(out.(*CarpStatus)) - return nil - }, InType: reflect.TypeOf(&CarpStatus{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Carp) DeepCopyInto(out *Carp) { *out = *in diff --git a/staging/src/k8s.io/apimachinery/pkg/conversion/BUILD b/staging/src/k8s.io/apimachinery/pkg/conversion/BUILD index 7e100d4fa43..653418164c3 100644 --- a/staging/src/k8s.io/apimachinery/pkg/conversion/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/conversion/BUILD @@ -10,11 +10,10 @@ go_test( name = "go_default_test", srcs = [ "converter_test.go", - "deep_copy_test.go", "helper_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/conversion", - library = ":go_default_library", deps = [ "//vendor/github.com/google/gofuzz:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", @@ -25,7 +24,6 @@ go_test( go_library( name = "go_default_library", srcs = [ - "cloner.go", "converter.go", "deep_equal.go", "doc.go", @@ -47,7 +45,6 @@ filegroup( srcs = [ ":package-srcs", "//staging/src/k8s.io/apimachinery/pkg/conversion/queryparams:all-srcs", - "//staging/src/k8s.io/apimachinery/pkg/conversion/unstructured:all-srcs", ], tags = ["automanaged"], ) diff --git a/staging/src/k8s.io/apimachinery/pkg/conversion/cloner.go b/staging/src/k8s.io/apimachinery/pkg/conversion/cloner.go deleted file mode 100644 index c5dec1f31e4..00000000000 --- a/staging/src/k8s.io/apimachinery/pkg/conversion/cloner.go +++ /dev/null @@ -1,249 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package conversion - -import ( - "fmt" - "reflect" -) - -// Cloner knows how to copy one type to another. -type Cloner struct { - // Map from the type to a function which can do the deep copy. - deepCopyFuncs map[reflect.Type]reflect.Value - generatedDeepCopyFuncs map[reflect.Type]func(in interface{}, out interface{}, c *Cloner) error -} - -// NewCloner creates a new Cloner object. -func NewCloner() *Cloner { - c := &Cloner{ - deepCopyFuncs: map[reflect.Type]reflect.Value{}, - generatedDeepCopyFuncs: map[reflect.Type]func(in interface{}, out interface{}, c *Cloner) error{}, - } - if err := c.RegisterDeepCopyFunc(byteSliceDeepCopy); err != nil { - // If one of the deep-copy functions is malformed, detect it immediately. - panic(err) - } - return c -} - -// Prevent recursing into every byte... -func byteSliceDeepCopy(in *[]byte, out *[]byte, c *Cloner) error { - if *in != nil { - *out = make([]byte, len(*in)) - copy(*out, *in) - } else { - *out = nil - } - return nil -} - -// Verifies whether a deep-copy function has a correct signature. -func verifyDeepCopyFunctionSignature(ft reflect.Type) error { - if ft.Kind() != reflect.Func { - return fmt.Errorf("expected func, got: %v", ft) - } - if ft.NumIn() != 3 { - return fmt.Errorf("expected three 'in' params, got %v", ft) - } - if ft.NumOut() != 1 { - return fmt.Errorf("expected one 'out' param, got %v", ft) - } - if ft.In(0).Kind() != reflect.Ptr { - return fmt.Errorf("expected pointer arg for 'in' param 0, got: %v", ft) - } - if ft.In(1) != ft.In(0) { - return fmt.Errorf("expected 'in' param 0 the same as param 1, got: %v", ft) - } - var forClonerType Cloner - if expected := reflect.TypeOf(&forClonerType); ft.In(2) != expected { - return fmt.Errorf("expected '%v' arg for 'in' param 2, got: '%v'", expected, ft.In(2)) - } - var forErrorType error - // This convolution is necessary, otherwise TypeOf picks up on the fact - // that forErrorType is nil - errorType := reflect.TypeOf(&forErrorType).Elem() - if ft.Out(0) != errorType { - return fmt.Errorf("expected error return, got: %v", ft) - } - return nil -} - -// RegisterGeneratedDeepCopyFunc registers a copying func with the Cloner. -// deepCopyFunc must take three parameters: a type input, a pointer to a -// type output, and a pointer to Cloner. It should return an error. -// -// Example: -// c.RegisterGeneratedDeepCopyFunc( -// func(in Pod, out *Pod, c *Cloner) error { -// // deep copy logic... -// return nil -// }) -func (c *Cloner) RegisterDeepCopyFunc(deepCopyFunc interface{}) error { - fv := reflect.ValueOf(deepCopyFunc) - ft := fv.Type() - if err := verifyDeepCopyFunctionSignature(ft); err != nil { - return err - } - c.deepCopyFuncs[ft.In(0)] = fv - return nil -} - -// GeneratedDeepCopyFunc bundles an untyped generated deep-copy function of a type -// with a reflection type object used as a key to lookup the deep-copy function. -type GeneratedDeepCopyFunc struct { - Fn func(in interface{}, out interface{}, c *Cloner) error - InType reflect.Type -} - -// Similar to RegisterDeepCopyFunc, but registers deep copy function that were -// automatically generated. -func (c *Cloner) RegisterGeneratedDeepCopyFunc(fn GeneratedDeepCopyFunc) error { - c.generatedDeepCopyFuncs[fn.InType] = fn.Fn - return nil -} - -// DeepCopy will perform a deep copy of a given object. -func (c *Cloner) DeepCopy(in interface{}) (interface{}, error) { - // Can be invalid if we run DeepCopy(X) where X is a nil interface type. - // For example, we get an invalid value when someone tries to deep-copy - // a nil labels.Selector. - // This does not occur if X is nil and is a pointer to a concrete type. - if in == nil { - return nil, nil - } - inValue := reflect.ValueOf(in) - outValue, err := c.deepCopy(inValue) - if err != nil { - return nil, err - } - return outValue.Interface(), nil -} - -func (c *Cloner) deepCopy(src reflect.Value) (reflect.Value, error) { - inType := src.Type() - - switch src.Kind() { - case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice: - if src.IsNil() { - return src, nil - } - } - - if fv, ok := c.deepCopyFuncs[inType]; ok { - return c.customDeepCopy(src, fv) - } - if fv, ok := c.generatedDeepCopyFuncs[inType]; ok { - var outValue reflect.Value - outValue = reflect.New(inType.Elem()) - err := fv(src.Interface(), outValue.Interface(), c) - return outValue, err - } - return c.defaultDeepCopy(src) -} - -func (c *Cloner) customDeepCopy(src, fv reflect.Value) (reflect.Value, error) { - outValue := reflect.New(src.Type().Elem()) - args := []reflect.Value{src, outValue, reflect.ValueOf(c)} - result := fv.Call(args)[0].Interface() - // This convolution is necessary because nil interfaces won't convert - // to error. - if result == nil { - return outValue, nil - } - return outValue, result.(error) -} - -func (c *Cloner) defaultDeepCopy(src reflect.Value) (reflect.Value, error) { - switch src.Kind() { - case reflect.Chan, reflect.Func, reflect.UnsafePointer, reflect.Uintptr: - return src, fmt.Errorf("cannot deep copy kind: %s", src.Kind()) - case reflect.Array: - dst := reflect.New(src.Type()) - for i := 0; i < src.Len(); i++ { - copyVal, err := c.deepCopy(src.Index(i)) - if err != nil { - return src, err - } - dst.Elem().Index(i).Set(copyVal) - } - return dst.Elem(), nil - case reflect.Interface: - if src.IsNil() { - return src, nil - } - return c.deepCopy(src.Elem()) - case reflect.Map: - if src.IsNil() { - return src, nil - } - dst := reflect.MakeMap(src.Type()) - for _, k := range src.MapKeys() { - copyVal, err := c.deepCopy(src.MapIndex(k)) - if err != nil { - return src, err - } - dst.SetMapIndex(k, copyVal) - } - return dst, nil - case reflect.Ptr: - if src.IsNil() { - return src, nil - } - dst := reflect.New(src.Type().Elem()) - copyVal, err := c.deepCopy(src.Elem()) - if err != nil { - return src, err - } - dst.Elem().Set(copyVal) - return dst, nil - case reflect.Slice: - if src.IsNil() { - return src, nil - } - dst := reflect.MakeSlice(src.Type(), 0, src.Len()) - for i := 0; i < src.Len(); i++ { - copyVal, err := c.deepCopy(src.Index(i)) - if err != nil { - return src, err - } - dst = reflect.Append(dst, copyVal) - } - return dst, nil - case reflect.Struct: - dst := reflect.New(src.Type()) - for i := 0; i < src.NumField(); i++ { - if !dst.Elem().Field(i).CanSet() { - // Can't set private fields. At this point, the - // best we can do is a shallow copy. For - // example, time.Time is a value type with - // private members that can be shallow copied. - return src, nil - } - copyVal, err := c.deepCopy(src.Field(i)) - if err != nil { - return src, err - } - dst.Elem().Field(i).Set(copyVal) - } - return dst.Elem(), nil - - default: - // Value types like numbers, booleans, and strings. - return src, nil - } -} diff --git a/staging/src/k8s.io/apimachinery/pkg/conversion/deep_copy_test.go b/staging/src/k8s.io/apimachinery/pkg/conversion/deep_copy_test.go deleted file mode 100644 index fdef856f291..00000000000 --- a/staging/src/k8s.io/apimachinery/pkg/conversion/deep_copy_test.go +++ /dev/null @@ -1,161 +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 conversion - -import ( - "math/rand" - "reflect" - "testing" - - "github.com/google/gofuzz" -) - -func TestDeepCopy(t *testing.T) { - semantic := EqualitiesOrDie() - f := fuzz.New().NilChance(.5).NumElements(0, 100) - table := []interface{}{ - map[string]string{}, - int(5), - "hello world", - struct { - A, B, C struct { - D map[string]int - } - X []int - Y []byte - }{}, - } - for _, obj := range table { - obj2, err := NewCloner().DeepCopy(obj) - if err != nil { - t.Errorf("Error: couldn't copy %#v", obj) - continue - } - if e, a := obj, obj2; !semantic.DeepEqual(e, a) { - t.Errorf("expected %#v\ngot %#v", e, a) - } - - obj3 := reflect.New(reflect.TypeOf(obj)).Interface() - f.Fuzz(obj3) - obj4, err := NewCloner().DeepCopy(obj3) - if err != nil { - t.Errorf("Error: couldn't copy %#v", obj) - continue - } - if e, a := obj3, obj4; !semantic.DeepEqual(e, a) { - t.Errorf("expected %#v\ngot %#v", e, a) - } - f.Fuzz(obj3) - } -} - -func copyOrDie(t *testing.T, in interface{}) interface{} { - out, err := NewCloner().DeepCopy(in) - if err != nil { - t.Fatalf("DeepCopy failed: %#q: %v", in, err) - } - return out -} - -func TestDeepCopySliceSeparate(t *testing.T) { - x := []int{5} - y := copyOrDie(t, x).([]int) - x[0] = 3 - if y[0] == 3 { - t.Errorf("deep copy wasn't deep: %#q %#q", x, y) - } -} - -func TestDeepCopyArraySeparate(t *testing.T) { - x := [1]int{5} - y := copyOrDie(t, x).([1]int) - x[0] = 3 - if y[0] == 3 { - t.Errorf("deep copy wasn't deep: %#q %#q", x, y) - } -} - -func TestDeepCopyMapSeparate(t *testing.T) { - x := map[string]int{"foo": 5} - y := copyOrDie(t, x).(map[string]int) - x["foo"] = 3 - if y["foo"] == 3 { - t.Errorf("deep copy wasn't deep: %#q %#q", x, y) - } -} - -func TestDeepCopyPointerSeparate(t *testing.T) { - z := 5 - x := &z - y := copyOrDie(t, x).(*int) - *x = 3 - if *y == 3 { - t.Errorf("deep copy wasn't deep: %#q %#q", x, y) - } -} - -func TestDeepCopyStruct(t *testing.T) { - type Foo struct { - A int - } - type Bar struct { - Foo - F *Foo - } - a := &Bar{Foo{1}, &Foo{2}} - b := copyOrDie(t, a).(*Bar) - a.A = 3 - a.F.A = 4 - - if b.A != 1 || b.F.A != 2 { - t.Errorf("deep copy wasn't deep: %#v, %#v", a, b) - } -} - -var result interface{} - -func BenchmarkDeepCopy(b *testing.B) { - table := []interface{}{ - map[string]string{}, - int(5), - "hello world", - struct { - A, B, C struct { - D map[string]int - } - X []int - Y []byte - }{}, - } - - f := fuzz.New().RandSource(rand.NewSource(1)).NilChance(.5).NumElements(0, 100) - for i := range table { - out := table[i] - obj := reflect.New(reflect.TypeOf(out)).Interface() - f.Fuzz(obj) - table[i] = obj - } - - b.ResetTimer() - var r interface{} - for i := 0; i < b.N; i++ { - for j := range table { - r, _ = NewCloner().DeepCopy(table[j]) - } - } - result = r -} diff --git a/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/BUILD b/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/BUILD deleted file mode 100644 index 91c1bb00bdf..00000000000 --- a/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/BUILD +++ /dev/null @@ -1,39 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = [ - "converter.go", - "doc.go", - ], - importpath = "k8s.io/apimachinery/pkg/conversion/unstructured", - deps = [ - "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/testing:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/doc.go b/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/doc.go deleted file mode 100644 index cd40e74bed3..00000000000 --- a/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/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 unstructured provides conversion from runtime objects -// to map[string]interface{} representation. -package unstructured // import "k8s.io/apimachinery/pkg/conversion/unstructured" diff --git a/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/testing/BUILD b/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/testing/BUILD deleted file mode 100644 index eaeca02a7ed..00000000000 --- a/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/testing/BUILD +++ /dev/null @@ -1,29 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_test") - -go_test( - name = "go_default_test", - srcs = ["converter_test.go"], - importpath = "k8s.io/apimachinery/pkg/conversion/unstructured/testing", - deps = [ - "//vendor/github.com/stretchr/testify/assert:go_default_library", - "//vendor/github.com/stretchr/testify/require:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion/unstructured:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], - visibility = ["//visibility:public"], -) diff --git a/staging/src/k8s.io/apimachinery/pkg/fields/BUILD b/staging/src/k8s.io/apimachinery/pkg/fields/BUILD index 2bae1350393..addb286a230 100644 --- a/staging/src/k8s.io/apimachinery/pkg/fields/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/fields/BUILD @@ -12,8 +12,8 @@ go_test( "fields_test.go", "selector_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/fields", - library = ":go_default_library", ) go_library( diff --git a/staging/src/k8s.io/apimachinery/pkg/labels/BUILD b/staging/src/k8s.io/apimachinery/pkg/labels/BUILD index 3612719d3b6..dc6af2643d9 100644 --- a/staging/src/k8s.io/apimachinery/pkg/labels/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/labels/BUILD @@ -12,8 +12,8 @@ go_test( "labels_test.go", "selector_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/labels", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/selection:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", @@ -31,7 +31,6 @@ go_library( importpath = "k8s.io/apimachinery/pkg/labels", deps = [ "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/selection:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", diff --git a/staging/src/k8s.io/apimachinery/pkg/labels/zz_generated.deepcopy.go b/staging/src/k8s.io/apimachinery/pkg/labels/zz_generated.deepcopy.go index 80ba3fb751d..d22cddbff76 100644 --- a/staging/src/k8s.io/apimachinery/pkg/labels/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apimachinery/pkg/labels/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -20,23 +20,6 @@ limitations under the License. package labels -import ( - conversion "k8s.io/apimachinery/pkg/conversion" - reflect "reflect" -) - -// GetGeneratedDeepCopyFuncs returns the generated funcs, since we aren't registering them. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func GetGeneratedDeepCopyFuncs() []conversion.GeneratedDeepCopyFunc { - return []conversion.GeneratedDeepCopyFunc{ - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Requirement).DeepCopyInto(out.(*Requirement)) - return nil - }, InType: reflect.TypeOf(&Requirement{})}, - } -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Requirement) DeepCopyInto(out *Requirement) { *out = *in diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/BUILD b/staging/src/k8s.io/apimachinery/pkg/runtime/BUILD index 2085e643fd6..ab87922aa45 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["swagger_doc_generator_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/runtime", - library = ":go_default_library", ) go_library( @@ -19,6 +19,7 @@ go_library( "codec.go", "codec_check.go", "conversion.go", + "converter.go", "doc.go", "embedded.go", "error.go", @@ -37,10 +38,13 @@ go_library( importpath = "k8s.io/apimachinery/pkg/runtime", deps = [ "//vendor/github.com/gogo/protobuf/proto:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/conversion/queryparams:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", ], ) @@ -48,6 +52,7 @@ go_test( name = "go_default_xtest", srcs = [ "conversion_test.go", + "converter_test.go", "embedded_test.go", "extension_test.go", "scheme_test.go", @@ -56,13 +61,17 @@ go_test( deps = [ "//vendor/github.com/google/gofuzz:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", + "//vendor/github.com/stretchr/testify/assert:go_default_library", + "//vendor/github.com/stretchr/testify/require:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/testing:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library", ], ) diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/codec.go b/staging/src/k8s.io/apimachinery/pkg/runtime/codec.go index d9748f0664c..5b3080aa584 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/codec.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/codec.go @@ -139,6 +139,7 @@ func NewParameterCodec(scheme *Scheme) ParameterCodec { typer: scheme, convertor: scheme, creator: scheme, + defaulter: scheme, } } @@ -147,6 +148,7 @@ type parameterCodec struct { typer ObjectTyper convertor ObjectConvertor creator ObjectCreater + defaulter ObjectDefaulter } var _ ParameterCodec = ¶meterCodec{} @@ -163,9 +165,17 @@ func (c *parameterCodec) DecodeParameters(parameters url.Values, from schema.Gro } for i := range targetGVKs { if targetGVKs[i].GroupVersion() == from { - return c.convertor.Convert(¶meters, into, nil) + if err := c.convertor.Convert(¶meters, into, nil); err != nil { + return err + } + // in the case where we going into the same object we're receiving, default on the outbound object + if c.defaulter != nil { + c.defaulter.Default(into) + } + return nil } } + input, err := c.creator.New(from.WithKind(targetGVKs[0].Kind)) if err != nil { return err @@ -173,6 +183,10 @@ func (c *parameterCodec) DecodeParameters(parameters url.Values, from schema.Gro if err := c.convertor.Convert(¶meters, input, nil); err != nil { return err } + // if we have defaulter, default the input before converting to output + if c.defaulter != nil { + c.defaulter.Default(input) + } return c.convertor.Convert(input, into, nil) } diff --git a/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/converter.go b/staging/src/k8s.io/apimachinery/pkg/runtime/converter.go similarity index 90% rename from staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/converter.go rename to staging/src/k8s.io/apimachinery/pkg/runtime/converter.go index d0e625d2c17..f6f7c10de62 100644 --- a/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/converter.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/converter.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package unstructured +package runtime import ( "bytes" @@ -27,19 +27,18 @@ import ( "strings" "sync" "sync/atomic" + "time" - apiequality "k8s.io/apimachinery/pkg/api/equality" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/diff" + "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/util/json" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "github.com/golang/glog" ) -// Converter is an interface for converting between interface{} +// UnstructuredConverter is an interface for converting between interface{} // and map[string]interface representation. -type Converter interface { +type UnstructuredConverter interface { ToUnstructured(obj interface{}) (map[string]interface{}, error) FromUnstructured(u map[string]interface{}, obj interface{}) error } @@ -78,7 +77,16 @@ var ( float64Type = reflect.TypeOf(float64(0)) boolType = reflect.TypeOf(bool(false)) fieldCache = newFieldsCache() - DefaultConverter = NewConverter(parseBool(os.Getenv("KUBE_PATCH_CONVERSION_DETECTOR"))) + + // DefaultUnstructuredConverter performs unstructured to Go typed object conversions. + DefaultUnstructuredConverter = &unstructuredConverter{ + mismatchDetection: parseBool(os.Getenv("KUBE_PATCH_CONVERSION_DETECTOR")), + comparison: conversion.EqualitiesOrDie( + func(a, b time.Time) bool { + return a.UTC() == b.UTC() + }, + ), + } ) func parseBool(key string) bool { @@ -92,24 +100,30 @@ func parseBool(key string) bool { return value } -// ConverterImpl knows how to convert between interface{} and +// unstructuredConverter knows how to convert between interface{} and // Unstructured in both ways. -type converterImpl struct { +type unstructuredConverter struct { // If true, we will be additionally running conversion via json // to ensure that the result is true. // This is supposed to be set only in tests. mismatchDetection bool + // comparison is the default test logic used to compare + comparison conversion.Equalities } -func NewConverter(mismatchDetection bool) Converter { - return &converterImpl{ - mismatchDetection: mismatchDetection, +// NewTestUnstructuredConverter creates an UnstructuredConverter that accepts JSON typed maps and translates them +// to Go types via reflection. It performs mismatch detection automatically and is intended for use by external +// test tools. Use DefaultUnstructuredConverter if you do not explicitly need mismatch detection. +func NewTestUnstructuredConverter(comparison conversion.Equalities) UnstructuredConverter { + return &unstructuredConverter{ + mismatchDetection: true, + comparison: comparison, } } // FromUnstructured converts an object from map[string]interface{} representation into a concrete type. // It uses encoding/json/Unmarshaler if object implements it or reflection if not. -func (c *converterImpl) FromUnstructured(u map[string]interface{}, obj interface{}) error { +func (c *unstructuredConverter) FromUnstructured(u map[string]interface{}, obj interface{}) error { t := reflect.TypeOf(obj) value := reflect.ValueOf(obj) if t.Kind() != reflect.Ptr || value.IsNil() { @@ -122,8 +136,8 @@ func (c *converterImpl) FromUnstructured(u map[string]interface{}, obj interface if (err != nil) != (newErr != nil) { glog.Fatalf("FromUnstructured unexpected error for %v: error: %v", u, err) } - if err == nil && !apiequality.Semantic.DeepEqual(obj, newObj) { - glog.Fatalf("FromUnstructured mismatch for %#v, diff: %v", obj, diff.ObjectReflectDiff(obj, newObj)) + if err == nil && !c.comparison.DeepEqual(obj, newObj) { + glog.Fatalf("FromUnstructured mismatch\nobj1: %#v\nobj2: %#v", obj, newObj) } } return err @@ -393,11 +407,12 @@ func interfaceFromUnstructured(sv, dv reflect.Value) error { // ToUnstructured converts an object into map[string]interface{} representation. // It uses encoding/json/Marshaler if object implements it or reflection if not. -func (c *converterImpl) ToUnstructured(obj interface{}) (map[string]interface{}, error) { +func (c *unstructuredConverter) ToUnstructured(obj interface{}) (map[string]interface{}, error) { var u map[string]interface{} var err error - if unstr, ok := obj.(runtime.Unstructured); ok { - u = DeepCopyJSON(unstr.UnstructuredContent()) + if unstr, ok := obj.(Unstructured); ok { + // UnstructuredContent() mutates the object so we need to make a copy first + u = unstr.DeepCopyObject().(Unstructured).UnstructuredContent() } else { t := reflect.TypeOf(obj) value := reflect.ValueOf(obj) @@ -413,8 +428,8 @@ func (c *converterImpl) ToUnstructured(obj interface{}) (map[string]interface{}, if (err != nil) != (newErr != nil) { glog.Fatalf("ToUnstructured unexpected error for %v: error: %v; newErr: %v", obj, err, newErr) } - if err == nil && !apiequality.Semantic.DeepEqual(u, newUnstr) { - glog.Fatalf("ToUnstructured mismatch for %#v, diff: %v", u, diff.ObjectReflectDiff(u, newUnstr)) + if err == nil && !c.comparison.DeepEqual(u, newUnstr) { + glog.Fatalf("ToUnstructured mismatch\nobj1: %#v\nobj2: %#v", u, newUnstr) } } if err != nil { @@ -426,21 +441,23 @@ func (c *converterImpl) ToUnstructured(obj interface{}) (map[string]interface{}, // DeepCopyJSON deep copies the passed value, assuming it is a valid JSON representation i.e. only contains // types produced by json.Unmarshal(). func DeepCopyJSON(x map[string]interface{}) map[string]interface{} { - return deepCopyJSON(x).(map[string]interface{}) + return DeepCopyJSONValue(x).(map[string]interface{}) } -func deepCopyJSON(x interface{}) interface{} { +// DeepCopyJSONValue deep copies the passed value, assuming it is a valid JSON representation i.e. only contains +// types produced by json.Unmarshal(). +func DeepCopyJSONValue(x interface{}) interface{} { switch x := x.(type) { case map[string]interface{}: clone := make(map[string]interface{}, len(x)) for k, v := range x { - clone[k] = deepCopyJSON(v) + clone[k] = DeepCopyJSONValue(v) } return clone case []interface{}: clone := make([]interface{}, len(x)) for i, v := range x { - clone[i] = deepCopyJSON(v) + clone[i] = DeepCopyJSONValue(v) } return clone case string, int64, bool, float64, nil, encodingjson.Number: diff --git a/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/testing/converter_test.go b/staging/src/k8s.io/apimachinery/pkg/runtime/converter_test.go similarity index 86% rename from staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/testing/converter_test.go rename to staging/src/k8s.io/apimachinery/pkg/runtime/converter_test.go index f367c6310b6..7820b8cefd8 100644 --- a/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/testing/converter_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/converter_test.go @@ -18,7 +18,7 @@ limitations under the License. // Unstructured type depends on unstructured converter package but we want to test how the converter handles // the Unstructured type so we need to import both. -package testing +package runtime_test import ( encodingjson "encoding/json" @@ -26,9 +26,11 @@ import ( "reflect" "strconv" "testing" + "time" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - conversionunstructured "k8s.io/apimachinery/pkg/conversion/unstructured" + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/json" @@ -36,6 +38,12 @@ import ( "github.com/stretchr/testify/require" ) +var simpleEquality = conversion.EqualitiesOrDie( + func(a, b time.Time) bool { + return a.UTC() == b.UTC() + }, +) + // Definte a number of test types. type A struct { A int `json:"aa,omitempty"` @@ -127,7 +135,7 @@ func doRoundTrip(t *testing.T, item interface{}) { return } unmarshalledObj := reflect.New(reflect.TypeOf(item).Elem()).Interface() - err = json.Unmarshal(data, &unmarshalledObj) + err = json.Unmarshal(data, unmarshalledObj) if err != nil { t.Errorf("Error when unmarshaling to object: %v", err) return @@ -137,14 +145,15 @@ func doRoundTrip(t *testing.T, item interface{}) { return } - newUnstr, err := conversionunstructured.DefaultConverter.ToUnstructured(item) + // TODO: should be using mismatch detection but fails due to another error + newUnstr, err := runtime.DefaultUnstructuredConverter.ToUnstructured(item) if err != nil { t.Errorf("ToUnstructured failed: %v", err) return } newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface() - err = conversionunstructured.DefaultConverter.FromUnstructured(newUnstr, newObj) + err = runtime.NewTestUnstructuredConverter(simpleEquality).FromUnstructured(newUnstr, newObj) if err != nil { t.Errorf("FromUnstructured failed: %v", err) return @@ -160,6 +169,38 @@ func TestRoundTrip(t *testing.T) { testCases := []struct { obj interface{} }{ + { + obj: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "kind": "List", + }, + // Not testing a list with nil Items because items is a non-optional field and hence + // is always marshaled into an empty array which is not equal to nil when unmarshalled and will fail. + // That is expected. + Items: []unstructured.Unstructured{}, + }, + }, + { + obj: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "kind": "List", + }, + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "Pod", + }, + }, + }, + }, + }, + { + obj: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "Pod", + }, + }, + }, { obj: &unstructured.Unstructured{ Object: map[string]interface{}{ @@ -239,10 +280,9 @@ func TestRoundTrip(t *testing.T) { } for i := range testCases { - doRoundTrip(t, testCases[i].obj) - if t.Failed() { - break - } + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + doRoundTrip(t, testCases[i].obj) + }) } } @@ -252,7 +292,7 @@ func TestRoundTrip(t *testing.T) { // produces the same object. func doUnrecognized(t *testing.T, jsonData string, item interface{}, expectedErr error) { unmarshalledObj := reflect.New(reflect.TypeOf(item).Elem()).Interface() - err := json.Unmarshal([]byte(jsonData), &unmarshalledObj) + err := json.Unmarshal([]byte(jsonData), unmarshalledObj) if (err != nil) != (expectedErr != nil) { t.Errorf("Unexpected error when unmarshaling to object: %v, expected: %v", err, expectedErr) return @@ -265,7 +305,7 @@ func doUnrecognized(t *testing.T, jsonData string, item interface{}, expectedErr return } newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface() - err = conversionunstructured.DefaultConverter.FromUnstructured(unstr, newObj) + err = runtime.NewTestUnstructuredConverter(simpleEquality).FromUnstructured(unstr, newObj) if (err != nil) != (expectedErr != nil) { t.Errorf("Unexpected error in FromUnstructured: %v, expected: %v", err, expectedErr) } @@ -457,11 +497,10 @@ func TestUnrecognized(t *testing.T) { }, } - for i := range testCases { - doUnrecognized(t, testCases[i].data, testCases[i].obj, testCases[i].err) - if t.Failed() { - break - } + for _, tc := range testCases { + t.Run(tc.data, func(t *testing.T) { + doUnrecognized(t, tc.data, tc.obj, tc.err) + }) } } @@ -479,7 +518,7 @@ func TestDeepCopyJSON(t *testing.T) { "f": true, "g": encodingjson.Number("123"), } - deepCopy := conversionunstructured.DeepCopyJSON(src) + deepCopy := runtime.DeepCopyJSON(src) assert.Equal(t, src, deepCopy) } @@ -487,7 +526,7 @@ func TestFloatIntConversion(t *testing.T) { unstr := map[string]interface{}{"fd": float64(3)} var obj F - if err := conversionunstructured.DefaultConverter.FromUnstructured(unstr, &obj); err != nil { + if err := runtime.NewTestUnstructuredConverter(simpleEquality).FromUnstructured(unstr, &obj); err != nil { t.Errorf("Unexpected error in FromUnstructured: %v", err) } @@ -525,7 +564,7 @@ func TestCustomToUnstructured(t *testing.T) { tc := tc t.Run(tc.Data, func(t *testing.T) { t.Parallel() - result, err := conversionunstructured.DefaultConverter.ToUnstructured(&G{ + result, err := runtime.NewTestUnstructuredConverter(simpleEquality).ToUnstructured(&G{ CustomValue1: CustomValue{data: []byte(tc.Data)}, CustomValue2: &CustomValue{data: []byte(tc.Data)}, CustomPointer1: CustomPointer{data: []byte(tc.Data)}, @@ -550,7 +589,7 @@ func TestCustomToUnstructuredTopLevel(t *testing.T) { obj := obj t.Run(strconv.Itoa(i), func(t *testing.T) { t.Parallel() - result, err := conversionunstructured.DefaultConverter.ToUnstructured(obj) + result, err := runtime.NewTestUnstructuredConverter(simpleEquality).ToUnstructured(obj) require.NoError(t, err) assert.Equal(t, expected, result) }) diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/error.go b/staging/src/k8s.io/apimachinery/pkg/runtime/error.go index 21a3557077b..86b24840f0a 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/error.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/error.go @@ -94,8 +94,6 @@ type missingVersionErr struct { data string } -// IsMissingVersion returns true if the error indicates that the provided object -// is missing a 'Version' field. func NewMissingVersionErr(data string) error { return &missingVersionErr{data} } @@ -104,6 +102,8 @@ func (k *missingVersionErr) Error() string { return fmt.Sprintf("Object 'apiVersion' is missing in '%s'", k.data) } +// IsMissingVersion returns true if the error indicates that the provided object +// is missing a 'Version' field. func IsMissingVersion(err error) bool { if err == nil { return false diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/generated.pb.go b/staging/src/k8s.io/apimachinery/pkg/runtime/generated.pb.go index bce8336a8ad..f561fd476e6 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/generated.pb.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/generated.proto b/staging/src/k8s.io/apimachinery/pkg/runtime/generated.proto index b3fd09c3c5e..02e388e9087 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/generated.proto +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/interfaces.go b/staging/src/k8s.io/apimachinery/pkg/runtime/interfaces.go index c90eef5ac38..9d00f1650e9 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/interfaces.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/interfaces.go @@ -233,13 +233,13 @@ type Object interface { // Unstructured objects store values as map[string]interface{}, with only values that can be serialized // to JSON allowed. type Unstructured interface { - // IsUnstructuredObject is a marker interface to allow objects that can be serialized but not introspected - // to bypass conversion. - IsUnstructuredObject() + Object // UnstructuredContent returns a non-nil, mutable map of the contents of this object. Values may be // []interface{}, map[string]interface{}, or any primitive type. Contents are typically serialized to // and from JSON. UnstructuredContent() map[string]interface{} + // SetUnstructuredContent updates the object content to match the provided map. + SetUnstructuredContent(map[string]interface{}) // IsList returns true if this type is a list or matches the list convention - has an array called "items". IsList() bool // EachListItem should pass a single item out of the list as an Object to the provided function. Any diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/schema/BUILD b/staging/src/k8s.io/apimachinery/pkg/runtime/schema/BUILD index 032d866edb4..91ead696c1b 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/schema/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/schema/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["group_version_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/runtime/schema", - library = ":go_default_library", ) go_library( diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/schema/generated.pb.go b/staging/src/k8s.io/apimachinery/pkg/runtime/schema/generated.pb.go index e2cc1216617..5357628adde 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/schema/generated.pb.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/schema/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/schema/generated.proto b/staging/src/k8s.io/apimachinery/pkg/runtime/schema/generated.proto index ebc1a263d29..50c2f2a632e 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/schema/generated.proto +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/schema/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/scheme.go b/staging/src/k8s.io/apimachinery/pkg/runtime/scheme.go index c597fcf99fa..08b7553810b 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/scheme.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/scheme.go @@ -68,10 +68,6 @@ type Scheme struct { // converter stores all registered conversion functions. It also has // default coverting behavior. converter *conversion.Converter - - // cloner stores all registered copy functions. It also has default - // deep copy behavior. - cloner *conversion.Cloner } // Function to convert a field selector to internal representation. @@ -80,11 +76,10 @@ type FieldLabelConversionFunc func(label, value string) (internalLabel, internal // NewScheme creates a new Scheme. This scheme is pluggable by default. func NewScheme() *Scheme { s := &Scheme{ - gvkToType: map[schema.GroupVersionKind]reflect.Type{}, - typeToGVK: map[reflect.Type][]schema.GroupVersionKind{}, - unversionedTypes: map[reflect.Type]schema.GroupVersionKind{}, - unversionedKinds: map[string]reflect.Type{}, - cloner: conversion.NewCloner(), + gvkToType: map[schema.GroupVersionKind]reflect.Type{}, + typeToGVK: map[reflect.Type][]schema.GroupVersionKind{}, + unversionedTypes: map[reflect.Type]schema.GroupVersionKind{}, + unversionedKinds: map[string]reflect.Type{}, fieldLabelConversionFuncs: map[string]map[string]FieldLabelConversionFunc{}, defaulterFuncs: map[reflect.Type]func(interface{}){}, } @@ -222,19 +217,22 @@ func (s *Scheme) AllKnownTypes() map[schema.GroupVersionKind]reflect.Type { return s.gvkToType } -// ObjectKind returns the group,version,kind of the go object and true if this object -// is considered unversioned, or an error if it's not a pointer or is unregistered. -func (s *Scheme) ObjectKind(obj Object) (schema.GroupVersionKind, bool, error) { - gvks, unversionedType, err := s.ObjectKinds(obj) - if err != nil { - return schema.GroupVersionKind{}, false, err - } - return gvks[0], unversionedType, nil -} - // ObjectKinds returns all possible group,version,kind of the go object, true if the // object is considered unversioned, or an error if it's not a pointer or is unregistered. func (s *Scheme) ObjectKinds(obj Object) ([]schema.GroupVersionKind, bool, error) { + // Unstructured objects are always considered to have their declared GVK + if _, ok := obj.(Unstructured); ok { + // we require that the GVK be populated in order to recognize the object + gvk := obj.GetObjectKind().GroupVersionKind() + if len(gvk.Kind) == 0 { + return nil, false, NewMissingKindErr("unstructured object has no kind") + } + if len(gvk.Version) == 0 { + return nil, false, NewMissingVersionErr("unstructured object has no version") + } + return []schema.GroupVersionKind{gvk}, false, nil + } + v, err := conversion.EnforcePtr(obj) if err != nil { return nil, false, err @@ -343,7 +341,7 @@ func (s *Scheme) AddConversionFuncs(conversionFuncs ...interface{}) error { return nil } -// Similar to AddConversionFuncs, but registers conversion functions that were +// AddGeneratedConversionFuncs registers conversion functions that were // automatically generated. func (s *Scheme) AddGeneratedConversionFuncs(conversionFuncs ...interface{}) error { for _, f := range conversionFuncs { @@ -354,29 +352,6 @@ func (s *Scheme) AddGeneratedConversionFuncs(conversionFuncs ...interface{}) err return nil } -// AddDeepCopyFuncs adds a function to the list of deep-copy functions. -// For the expected format of deep-copy function, see the comment for -// Copier.RegisterDeepCopyFunction. -func (s *Scheme) AddDeepCopyFuncs(deepCopyFuncs ...interface{}) error { - for _, f := range deepCopyFuncs { - if err := s.cloner.RegisterDeepCopyFunc(f); err != nil { - return err - } - } - return nil -} - -// Similar to AddDeepCopyFuncs, but registers deep-copy functions that were -// automatically generated. -func (s *Scheme) AddGeneratedDeepCopyFuncs(deepCopyFuncs ...conversion.GeneratedDeepCopyFunc) error { - for _, fn := range deepCopyFuncs { - if err := s.cloner.RegisterGeneratedDeepCopyFunc(fn); err != nil { - return err - } - } - return nil -} - // AddFieldLabelConversionFunc adds a conversion function to convert field selectors // of the given kind from the given version to internal version representation. func (s *Scheme) AddFieldLabelConversionFunc(version, kind string, conversionFunc FieldLabelConversionFunc) error { @@ -424,10 +399,68 @@ func (s *Scheme) Default(src Object) { // testing of conversion functions. Returns an error if the conversion isn't // possible. You can call this with types that haven't been registered (for example, // a to test conversion of types that are nested within registered types). The -// context interface is passed to the convertor. -// TODO: identify whether context should be hidden, or behind a formal context/scope -// interface +// context interface is passed to the convertor. Convert also supports Unstructured +// types and will convert them intelligently. func (s *Scheme) Convert(in, out interface{}, context interface{}) error { + unstructuredIn, okIn := in.(Unstructured) + unstructuredOut, okOut := out.(Unstructured) + switch { + case okIn && okOut: + // converting unstructured input to an unstructured output is a straight copy - unstructured + // is a "smart holder" and the contents are passed by reference between the two objects + unstructuredOut.SetUnstructuredContent(unstructuredIn.UnstructuredContent()) + return nil + + case okOut: + // if the output is an unstructured object, use the standard Go type to unstructured + // conversion. The object must not be internal. + obj, ok := in.(Object) + if !ok { + return fmt.Errorf("unable to convert object type %T to Unstructured, must be a runtime.Object", in) + } + gvks, unversioned, err := s.ObjectKinds(obj) + if err != nil { + return err + } + gvk := gvks[0] + + // if no conversion is necessary, convert immediately + if unversioned || gvk.Version != APIVersionInternal { + content, err := DefaultUnstructuredConverter.ToUnstructured(in) + if err != nil { + return err + } + unstructuredOut.SetUnstructuredContent(content) + return nil + } + + // attempt to convert the object to an external version first. + target, ok := context.(GroupVersioner) + if !ok { + return fmt.Errorf("unable to convert the internal object type %T to Unstructured without providing a preferred version to convert to", in) + } + // Convert is implicitly unsafe, so we don't need to perform a safe conversion + versioned, err := s.UnsafeConvertToVersion(obj, target) + if err != nil { + return err + } + content, err := DefaultUnstructuredConverter.ToUnstructured(versioned) + if err != nil { + return err + } + unstructuredOut.SetUnstructuredContent(content) + return nil + + case okIn: + // converting an unstructured object to any type is modeled by first converting + // the input to a versioned type, then running standard conversions + typed, err := s.unstructuredToTyped(unstructuredIn) + if err != nil { + return err + } + in = typed + } + flags, meta := s.generateConvertMeta(in) meta.Context = context if flags == 0 { @@ -436,8 +469,8 @@ func (s *Scheme) Convert(in, out interface{}, context interface{}) error { return s.converter.Convert(in, out, flags, meta) } -// Converts the given field label and value for an kind field selector from -// versioned representation to an unversioned one. +// ConvertFieldLabel alters the given field label and value for an kind field selector from +// versioned representation to an unversioned one or returns an error. func (s *Scheme) ConvertFieldLabel(version, kind, label, value string) (string, string, error) { if s.fieldLabelConversionFuncs[version] == nil { return DefaultMetaV1FieldSelectorConversion(label, value) @@ -467,15 +500,30 @@ func (s *Scheme) UnsafeConvertToVersion(in Object, target GroupVersioner) (Objec // convertToVersion handles conversion with an optional copy. func (s *Scheme) convertToVersion(copy bool, in Object, target GroupVersioner) (Object, error) { - // determine the incoming kinds with as few allocations as possible. - t := reflect.TypeOf(in) - if t.Kind() != reflect.Ptr { - return nil, fmt.Errorf("only pointer types may be converted: %v", t) - } - t = t.Elem() - if t.Kind() != reflect.Struct { - return nil, fmt.Errorf("only pointers to struct types may be converted: %v", t) + var t reflect.Type + + if u, ok := in.(Unstructured); ok { + typed, err := s.unstructuredToTyped(u) + if err != nil { + return nil, err + } + + in = typed + // unstructuredToTyped returns an Object, which must be a pointer to a struct. + t = reflect.TypeOf(in).Elem() + + } else { + // determine the incoming kinds with as few allocations as possible. + t = reflect.TypeOf(in) + if t.Kind() != reflect.Ptr { + return nil, fmt.Errorf("only pointer types may be converted: %v", t) + } + t = t.Elem() + if t.Kind() != reflect.Struct { + return nil, fmt.Errorf("only pointers to struct types may be converted: %v", t) + } } + kinds, ok := s.typeToGVK[t] if !ok || len(kinds) == 0 { return nil, NewNotRegisteredErrForType(t) @@ -491,7 +539,6 @@ func (s *Scheme) convertToVersion(copy bool, in Object, target GroupVersioner) ( } return copyAndSetTargetKind(copy, in, unversionedKind) } - return nil, NewNotRegisteredErrForTarget(t, target) } @@ -529,6 +576,25 @@ func (s *Scheme) convertToVersion(copy bool, in Object, target GroupVersioner) ( return out, nil } +// unstructuredToTyped attempts to transform an unstructured object to a typed +// object if possible. It will return an error if conversion is not possible, or the versioned +// Go form of the object. Note that this conversion will lose fields. +func (s *Scheme) unstructuredToTyped(in Unstructured) (Object, error) { + // the type must be something we recognize + gvks, _, err := s.ObjectKinds(in) + if err != nil { + return nil, err + } + typed, err := s.New(gvks[0]) + if err != nil { + return nil, err + } + if err := DefaultUnstructuredConverter.FromUnstructured(in.UnstructuredContent(), typed); err != nil { + return nil, fmt.Errorf("unable to convert unstructured object to %v: %v", gvks[0], err) + } + return typed, nil +} + // generateConvertMeta constructs the meta value we pass to Convert. func (s *Scheme) generateConvertMeta(in interface{}) (conversion.FieldMatchingFlags, *conversion.Meta) { return s.converter.DefaultMeta(reflect.TypeOf(in)) diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/scheme_test.go b/staging/src/k8s.io/apimachinery/pkg/runtime/scheme_test.go index 3e623f64217..24743dcaecb 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/scheme_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/scheme_test.go @@ -17,6 +17,7 @@ limitations under the License. package runtime_test import ( + "fmt" "reflect" "strings" "testing" @@ -126,14 +127,84 @@ func TestScheme(t *testing.T) { t.Errorf("Expected %v, got %v", e, a) } + // Test convert internal to unstructured + unstructuredObj := &runtimetesting.Unstructured{} + err = scheme.Convert(simple, unstructuredObj, nil) + if err == nil || !strings.Contains(err.Error(), "to Unstructured without providing a preferred version to convert to") { + t.Fatalf("Unexpected non-error: %v", err) + } + err = scheme.Convert(simple, unstructuredObj, schema.GroupVersion{Group: "test.group", Version: "testExternal"}) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if e, a := simple.TestString, unstructuredObj.Object["testString"].(string); e != a { + t.Errorf("Expected %v, got %v", e, a) + } + if e := unstructuredObj.GetObjectKind().GroupVersionKind(); !reflect.DeepEqual(e, schema.GroupVersionKind{Group: "test.group", Version: "testExternal", Kind: "Simple"}) { + t.Errorf("Unexpected object kind: %#v", e) + } + if gvks, unversioned, err := scheme.ObjectKinds(unstructuredObj); err != nil || !reflect.DeepEqual(gvks[0], schema.GroupVersionKind{Group: "test.group", Version: "testExternal", Kind: "Simple"}) || unversioned { + t.Errorf("Scheme did not recognize unversioned: %v, %#v %t", err, gvks, unversioned) + } + + // Test convert external to unstructured + unstructuredObj = &runtimetesting.Unstructured{} + err = scheme.Convert(external, unstructuredObj, nil) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if e, a := simple.TestString, unstructuredObj.Object["testString"].(string); e != a { + t.Errorf("Expected %v, got %v", e, a) + } + if e := unstructuredObj.GetObjectKind().GroupVersionKind(); !reflect.DeepEqual(e, schema.GroupVersionKind{Group: "test.group", Version: "testExternal", Kind: "Simple"}) { + t.Errorf("Unexpected object kind: %#v", e) + } + + // Test convert unstructured to unstructured + uIn := &runtimetesting.Unstructured{Object: map[string]interface{}{ + "test": []interface{}{"other", "test"}, + }} + uOut := &runtimetesting.Unstructured{} + err = scheme.Convert(uIn, uOut, nil) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if !reflect.DeepEqual(uIn.Object, uOut.Object) { + t.Errorf("Unexpected object contents: %#v", uOut.Object) + } + + // Test convert unstructured to structured + externalOut := &runtimetesting.ExternalSimple{} + err = scheme.Convert(unstructuredObj, externalOut, nil) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if !reflect.DeepEqual(external, externalOut) { + t.Errorf("Unexpected object contents: %#v", externalOut) + } + // Encode and Convert should each have caused an increment. - if e, a := 2, internalToExternalCalls; e != a { + if e, a := 3, internalToExternalCalls; e != a { t.Errorf("Expected %v, got %v", e, a) } // DecodeInto and Decode should each have caused an increment because of a conversion if e, a := 2, externalToInternalCalls; e != a { t.Errorf("Expected %v, got %v", e, a) } + + // Verify that unstructured types must have V and K set + emptyObj := &runtimetesting.Unstructured{Object: make(map[string]interface{})} + if _, _, err := scheme.ObjectKinds(emptyObj); !runtime.IsMissingKind(err) { + t.Errorf("unexpected error: %v", err) + } + emptyObj.SetGroupVersionKind(schema.GroupVersionKind{Kind: "Test"}) + if _, _, err := scheme.ObjectKinds(emptyObj); !runtime.IsMissingVersion(err) { + t.Errorf("unexpected error: %v", err) + } + emptyObj.SetGroupVersionKind(schema.GroupVersionKind{Kind: "Test", Version: "v1"}) + if _, _, err := scheme.ObjectKinds(emptyObj); err != nil { + t.Errorf("unexpected error: %v", err) + } } func TestBadJSONRejection(t *testing.T) { @@ -367,6 +438,7 @@ func GetTestScheme() *runtime.Scheme { internalGV := schema.GroupVersion{Version: "__internal"} externalGV := schema.GroupVersion{Version: "v1"} alternateExternalGV := schema.GroupVersion{Group: "custom", Version: "v1"} + alternateInternalGV := schema.GroupVersion{Group: "custom", Version: "__internal"} differentExternalGV := schema.GroupVersion{Group: "other", Version: "v2"} s := runtime.NewScheme() @@ -380,10 +452,15 @@ func GetTestScheme() *runtime.Scheme { s.AddKnownTypeWithName(internalGV.WithKind("TestType3"), &runtimetesting.TestType1{}) s.AddKnownTypeWithName(externalGV.WithKind("TestType3"), &runtimetesting.ExternalTestType1{}) s.AddKnownTypeWithName(externalGV.WithKind("TestType4"), &runtimetesting.ExternalTestType1{}) + s.AddKnownTypeWithName(alternateInternalGV.WithKind("TestType3"), &runtimetesting.TestType1{}) s.AddKnownTypeWithName(alternateExternalGV.WithKind("TestType3"), &runtimetesting.ExternalTestType1{}) s.AddKnownTypeWithName(alternateExternalGV.WithKind("TestType5"), &runtimetesting.ExternalTestType1{}) s.AddKnownTypeWithName(differentExternalGV.WithKind("TestType1"), &runtimetesting.ExternalTestType1{}) s.AddUnversionedTypes(externalGV, &runtimetesting.UnversionedType{}) + + s.AddConversionFuncs(func(in *runtimetesting.TestType1, out *runtimetesting.ExternalTestType1, s conversion.Scope) { + out.A = in.A + }) return s } @@ -540,6 +617,28 @@ func TestConvertToVersion(t *testing.T) { gv: schema.GroupVersion{Version: "__internal"}, out: &runtimetesting.TestType1{A: "test"}, }, + // converts from unstructured to internal + { + scheme: GetTestScheme(), + in: &runtimetesting.Unstructured{Object: map[string]interface{}{ + "apiVersion": "custom/v1", + "kind": "TestType3", + "A": "test", + }}, + gv: schema.GroupVersion{Version: "__internal"}, + out: &runtimetesting.TestType1{A: "test"}, + }, + // converts from unstructured to external + { + scheme: GetTestScheme(), + in: &runtimetesting.Unstructured{Object: map[string]interface{}{ + "apiVersion": "custom/v1", + "kind": "TestType3", + "A": "test", + }}, + gv: schema.GroupVersion{Group: "custom", Version: "v1"}, + out: &runtimetesting.ExternalTestType1{MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "custom/v1", ObjectKind: "TestType3"}, A: "test"}, + }, // prefers the best match { scheme: GetTestScheme(), @@ -711,51 +810,88 @@ func TestConvertToVersion(t *testing.T) { }, } for i, test := range testCases { - original := test.in.DeepCopyObject() - out, err := test.scheme.ConvertToVersion(test.in, test.gv) - switch { - case test.errFn != nil: - if !test.errFn(err) { - t.Errorf("%d: unexpected error: %v", i, err) + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + original := test.in.DeepCopyObject() + out, err := test.scheme.ConvertToVersion(test.in, test.gv) + switch { + case test.errFn != nil: + if !test.errFn(err) { + t.Fatalf("unexpected error: %v", err) + } + return + case err != nil: + t.Fatalf("unexpected error: %v", err) + } + if out == test.in { + t.Fatalf("ConvertToVersion should always copy out: %#v", out) } - continue - case err != nil: - t.Errorf("%d: unexpected error: %v", i, err) - continue - } - if out == test.in { - t.Errorf("%d: ConvertToVersion should always copy out: %#v", i, out) - continue - } - if test.same { - if !reflect.DeepEqual(original, test.in) { - t.Errorf("%d: unexpected mutation of input: %s", i, diff.ObjectReflectDiff(original, test.in)) - continue + if test.same { + if !reflect.DeepEqual(original, test.in) { + t.Fatalf("unexpected mutation of input: %s", diff.ObjectReflectDiff(original, test.in)) + } + if !reflect.DeepEqual(out, test.out) { + t.Fatalf("unexpected out: %s", diff.ObjectReflectDiff(out, test.out)) + } + unsafe, err := test.scheme.UnsafeConvertToVersion(test.in, test.gv) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !reflect.DeepEqual(unsafe, test.out) { + t.Fatalf("unexpected unsafe: %s", diff.ObjectReflectDiff(unsafe, test.out)) + } + if unsafe != test.in { + t.Fatalf("UnsafeConvertToVersion should return same object: %#v", unsafe) + } + return } if !reflect.DeepEqual(out, test.out) { - t.Errorf("%d: unexpected out: %s", i, diff.ObjectReflectDiff(out, test.out)) - continue + t.Fatalf("unexpected out: %s", diff.ObjectReflectDiff(out, test.out)) } - unsafe, err := test.scheme.UnsafeConvertToVersion(test.in, test.gv) - if err != nil { - t.Errorf("%d: unexpected error: %v", i, err) - continue + }) + } +} + +func TestConvert(t *testing.T) { + testCases := []struct { + scheme *runtime.Scheme + in runtime.Object + into runtime.Object + gv runtime.GroupVersioner + out runtime.Object + errFn func(error) bool + }{ + // converts from internal to unstructured, given a target version + { + scheme: GetTestScheme(), + in: &runtimetesting.TestType1{A: "test"}, + into: &runtimetesting.Unstructured{}, + out: &runtimetesting.Unstructured{Object: map[string]interface{}{ + "myVersionKey": "custom/v1", + "myKindKey": "TestType3", + "A": "test", + }}, + gv: schema.GroupVersion{Group: "custom", Version: "v1"}, + }, + } + for i, test := range testCases { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + err := test.scheme.Convert(test.in, test.into, test.gv) + switch { + case test.errFn != nil: + if !test.errFn(err) { + t.Fatalf("unexpected error: %v", err) + } + return + case err != nil: + t.Fatalf("unexpected error: %v", err) + return } - if !reflect.DeepEqual(unsafe, test.out) { - t.Errorf("%d: unexpected unsafe: %s", i, diff.ObjectReflectDiff(unsafe, test.out)) - continue + + if !reflect.DeepEqual(test.into, test.out) { + t.Fatalf("unexpected out: %s", diff.ObjectReflectDiff(test.into, test.out)) } - if unsafe != test.in { - t.Errorf("%d: UnsafeConvertToVersion should return same object: %#v", i, unsafe) - continue - } - continue - } - if !reflect.DeepEqual(out, test.out) { - t.Errorf("%d: unexpected out: %s", i, diff.ObjectReflectDiff(out, test.out)) - continue - } + }) } } diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/BUILD b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/BUILD index 9403c3376f4..43d36a79277 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["codec_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/runtime/serializer", - library = ":go_default_library", deps = [ "//vendor/github.com/ghodss/yaml:go_default_library", "//vendor/github.com/google/gofuzz:go_default_library", diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/BUILD b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/BUILD index 0d43ce95429..0fdceeda9ce 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["meta_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/runtime/serializer/json", - library = ":go_default_library", ) go_library( diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/json.go b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/json.go index ce3d77c2b3b..2b795b5b84f 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/json.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/json.go @@ -98,11 +98,29 @@ func init() { jsoniter.RegisterTypeDecoderFunc("interface {}", decodeNumberAsInt64IfPossible) } +// gvkWithDefaults returns group kind and version defaulting from provided default +func gvkWithDefaults(actual, defaultGVK schema.GroupVersionKind) schema.GroupVersionKind { + if len(actual.Kind) == 0 { + actual.Kind = defaultGVK.Kind + } + if len(actual.Version) == 0 && len(actual.Group) == 0 { + actual.Group = defaultGVK.Group + actual.Version = defaultGVK.Version + } + if len(actual.Version) == 0 && actual.Group == defaultGVK.Group { + actual.Version = defaultGVK.Version + } + return actual +} + // Decode attempts to convert the provided data into YAML or JSON, extract the stored schema kind, apply the provided default gvk, and then -// load that data into an object matching the desired schema kind or the provided into. If into is *runtime.Unknown, the raw data will be -// extracted and no decoding will be performed. If into is not registered with the typer, then the object will be straight decoded using -// normal JSON/YAML unmarshalling. If into is provided and the original data is not fully qualified with kind/version/group, the type of -// the into will be used to alter the returned gvk. On success or most errors, the method will return the calculated schema kind. +// load that data into an object matching the desired schema kind or the provided into. +// If into is *runtime.Unknown, the raw data will be extracted and no decoding will be performed. +// If into is not registered with the typer, then the object will be straight decoded using normal JSON/YAML unmarshalling. +// If into is provided and the original data is not fully qualified with kind/version/group, the type of the into will be used to alter the returned gvk. +// If into is nil or data's gvk different from into's gvk, it will generate a new Object with ObjectCreater.New(gvk) +// On success or most errors, the method will return the calculated schema kind. +// The gvk calculate priority will be originalData > default gvk > into func (s *Serializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { if versioned, ok := into.(*runtime.VersionedObjects); ok { into = versioned.Last() @@ -129,17 +147,7 @@ func (s *Serializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, i } if gvk != nil { - // apply kind and version defaulting from provided default - if len(actual.Kind) == 0 { - actual.Kind = gvk.Kind - } - if len(actual.Version) == 0 && len(actual.Group) == 0 { - actual.Group = gvk.Group - actual.Version = gvk.Version - } - if len(actual.Version) == 0 && actual.Group == gvk.Group { - actual.Version = gvk.Version - } + *actual = gvkWithDefaults(*actual, *gvk) } if unk, ok := into.(*runtime.Unknown); ok && unk != nil { @@ -150,27 +158,18 @@ func (s *Serializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, i } if into != nil { + _, isUnstructured := into.(runtime.Unstructured) types, _, err := s.typer.ObjectKinds(into) switch { - case runtime.IsNotRegisteredError(err): - if err := jsoniter.ConfigFastest.Unmarshal(data, into); err != nil { + case runtime.IsNotRegisteredError(err), isUnstructured: + if err := jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal(data, into); err != nil { return nil, actual, err } return into, actual, nil case err != nil: return nil, actual, err default: - typed := types[0] - if len(actual.Kind) == 0 { - actual.Kind = typed.Kind - } - if len(actual.Version) == 0 && len(actual.Group) == 0 { - actual.Group = typed.Group - actual.Version = typed.Version - } - if len(actual.Version) == 0 && actual.Group == typed.Group { - actual.Version = typed.Version - } + *actual = gvkWithDefaults(*actual, types[0]) } } @@ -187,7 +186,7 @@ func (s *Serializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, i return nil, actual, err } - if err := jsoniter.ConfigFastest.Unmarshal(data, obj); err != nil { + if err := jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal(data, obj); err != nil { return nil, actual, err } return obj, actual, nil @@ -196,7 +195,7 @@ func (s *Serializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, i // Encode serializes the provided object to the given writer. func (s *Serializer) Encode(obj runtime.Object, w io.Writer) error { if s.yaml { - json, err := jsoniter.ConfigFastest.Marshal(obj) + json, err := jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(obj) if err != nil { return err } @@ -209,7 +208,7 @@ func (s *Serializer) Encode(obj runtime.Object, w io.Writer) error { } if s.pretty { - data, err := jsoniter.ConfigFastest.MarshalIndent(obj, "", " ") + data, err := jsoniter.ConfigCompatibleWithStandardLibrary.MarshalIndent(obj, "", " ") if err != nil { return err } diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/json_test.go b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/json_test.go index 469bf3ed8df..c8ae5550c52 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/json_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/json_test.go @@ -94,6 +94,32 @@ func TestDecode(t *testing.T) { expectedObject: &testDecodable{}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, }, + // group version, kind is defaulted + { + data: []byte(`{"apiVersion":"other1/blah1"}`), + defaultGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + creater: &mockCreater{obj: &testDecodable{}}, + expectedObject: &testDecodable{}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other1", Version: "blah1"}, + }, + // gvk all provided then not defaulted at all + { + data: []byte(`{"kind":"Test","apiVersion":"other/blah"}`), + defaultGVK: &schema.GroupVersionKind{Kind: "Test1", Group: "other1", Version: "blah1"}, + creater: &mockCreater{obj: &testDecodable{}}, + expectedObject: &testDecodable{}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + }, + //gvk defaulting if kind not provided in data and defaultGVK use into's kind + { + data: []byte(`{"apiVersion":"b1/c1"}`), + into: &testDecodable{gvk: schema.GroupVersionKind{Kind: "a3", Group: "b1", Version: "c1"}}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "a3", Group: "b1", Version: "c1"}}, + defaultGVK: nil, + creater: &mockCreater{obj: &testDecodable{}}, + expectedObject: &testDecodable{gvk: schema.GroupVersionKind{Kind: "a3", Group: "b1", Version: "c1"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "a3", Group: "b1", Version: "c1"}, + }, // accept runtime.Unknown as into and bypass creator { diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/streaming/BUILD b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/streaming/BUILD index 4903338fa6b..f1a2f341b7f 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/streaming/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/streaming/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["streaming_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/runtime/serializer/streaming", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/testing/BUILD b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/testing/BUILD index fce9bea295a..fbc00c7ca47 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/testing/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/testing/BUILD @@ -14,7 +14,6 @@ go_library( ], importpath = "k8s.io/apimachinery/pkg/runtime/serializer/testing", deps = [ - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/testing/zz_generated.deepcopy.go b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/testing/zz_generated.deepcopy.go index 2a71e4b5f1c..d43ae7f0449 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/testing/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/testing/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,39 +21,9 @@ limitations under the License. package testing import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -// GetGeneratedDeepCopyFuncs returns the generated funcs, since we aren't registering them. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func GetGeneratedDeepCopyFuncs() []conversion.GeneratedDeepCopyFunc { - return []conversion.GeneratedDeepCopyFunc{ - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExternalInternalSame).DeepCopyInto(out.(*ExternalInternalSame)) - return nil - }, InType: reflect.TypeOf(&ExternalInternalSame{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExternalTestType1).DeepCopyInto(out.(*ExternalTestType1)) - return nil - }, InType: reflect.TypeOf(&ExternalTestType1{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExternalTestType2).DeepCopyInto(out.(*ExternalTestType2)) - return nil - }, InType: reflect.TypeOf(&ExternalTestType2{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*TestType1).DeepCopyInto(out.(*TestType1)) - return nil - }, InType: reflect.TypeOf(&TestType1{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*TestType2).DeepCopyInto(out.(*TestType2)) - return nil - }, InType: reflect.TypeOf(&TestType2{})}, - } -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExternalInternalSame) DeepCopyInto(out *ExternalInternalSame) { *out = *in diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/versioning/BUILD b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/versioning/BUILD index a1b0e6eb277..2e262cb6b19 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/versioning/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/versioning/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["versioning_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/runtime/serializer/versioning", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/testing/BUILD b/staging/src/k8s.io/apimachinery/pkg/runtime/testing/BUILD index f3fbe2e9aac..cee23e55778 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/testing/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/testing/BUILD @@ -14,9 +14,9 @@ go_library( ], importpath = "k8s.io/apimachinery/pkg/runtime/testing", deps = [ - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library", ], ) diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/testing/types.go b/staging/src/k8s.io/apimachinery/pkg/runtime/testing/types.go index c051fb1d277..f7345db0caf 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/testing/types.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/testing/types.go @@ -17,8 +17,11 @@ limitations under the License. package testing import ( + "fmt" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/json" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -213,3 +216,105 @@ func (obj *MyWeirdCustomEmbeddedVersionKindField) GroupVersionKind() schema.Grou func (obj *TestType2) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } func (obj *ExternalTestType2) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } + +// +k8s:deepcopy-gen=false +type Unstructured struct { + // Object is a JSON compatible map with string, float, int, bool, []interface{}, or + // map[string]interface{} + // children. + Object map[string]interface{} +} + +var _ runtime.Unstructured = &Unstructured{} + +func (obj *Unstructured) GetObjectKind() schema.ObjectKind { return obj } + +func (obj *Unstructured) IsList() bool { + if obj.Object != nil { + _, ok := obj.Object["items"] + return ok + } + return false +} + +func (obj *Unstructured) EachListItem(fn func(runtime.Object) error) error { + if obj.Object == nil { + return fmt.Errorf("content is not a list") + } + field, ok := obj.Object["items"] + if !ok { + return fmt.Errorf("content is not a list") + } + items, ok := field.([]interface{}) + if !ok { + return nil + } + for _, item := range items { + child, ok := item.(map[string]interface{}) + if !ok { + return fmt.Errorf("items member is not an object") + } + if err := fn(&Unstructured{Object: child}); err != nil { + return err + } + } + return nil +} + +func (obj *Unstructured) UnstructuredContent() map[string]interface{} { + if obj.Object == nil { + obj.Object = make(map[string]interface{}) + } + return obj.Object +} + +func (obj *Unstructured) SetUnstructuredContent(content map[string]interface{}) { + obj.Object = content +} + +// MarshalJSON ensures that the unstructured object produces proper +// JSON when passed to Go's standard JSON library. +func (u *Unstructured) MarshalJSON() ([]byte, error) { + return json.Marshal(u.Object) +} + +// UnmarshalJSON ensures that the unstructured object properly decodes +// JSON when passed to Go's standard JSON library. +func (u *Unstructured) UnmarshalJSON(b []byte) error { + return json.Unmarshal(b, &u.Object) +} + +func (in *Unstructured) DeepCopyObject() runtime.Object { + return in.DeepCopy() +} + +func (in *Unstructured) DeepCopy() *Unstructured { + if in == nil { + return nil + } + out := new(Unstructured) + *out = *in + out.Object = runtime.DeepCopyJSON(in.Object) + return out +} + +func (u *Unstructured) GroupVersionKind() schema.GroupVersionKind { + apiVersion, ok := u.Object["apiVersion"].(string) + if !ok { + return schema.GroupVersionKind{} + } + gv, err := schema.ParseGroupVersion(apiVersion) + if err != nil { + return schema.GroupVersionKind{} + } + kind, ok := u.Object["kind"].(string) + if ok { + return gv.WithKind(kind) + } + return schema.GroupVersionKind{} +} + +func (u *Unstructured) SetGroupVersionKind(gvk schema.GroupVersionKind) { + u.Object["apiVersion"] = gvk.GroupVersion().String() + u.Object["kind"] = gvk.Kind +} diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/testing/zz_generated.deepcopy.go b/staging/src/k8s.io/apimachinery/pkg/runtime/testing/zz_generated.deepcopy.go index 8cacbc8b42e..1927e34c554 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/testing/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/testing/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,103 +21,9 @@ limitations under the License. package testing import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -// GetGeneratedDeepCopyFuncs returns the generated funcs, since we aren't registering them. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func GetGeneratedDeepCopyFuncs() []conversion.GeneratedDeepCopyFunc { - return []conversion.GeneratedDeepCopyFunc{ - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*EmbeddedTest).DeepCopyInto(out.(*EmbeddedTest)) - return nil - }, InType: reflect.TypeOf(&EmbeddedTest{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*EmbeddedTestExternal).DeepCopyInto(out.(*EmbeddedTestExternal)) - return nil - }, InType: reflect.TypeOf(&EmbeddedTestExternal{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExtensionA).DeepCopyInto(out.(*ExtensionA)) - return nil - }, InType: reflect.TypeOf(&ExtensionA{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExtensionB).DeepCopyInto(out.(*ExtensionB)) - return nil - }, InType: reflect.TypeOf(&ExtensionB{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExternalComplex).DeepCopyInto(out.(*ExternalComplex)) - return nil - }, InType: reflect.TypeOf(&ExternalComplex{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExternalExtensionType).DeepCopyInto(out.(*ExternalExtensionType)) - return nil - }, InType: reflect.TypeOf(&ExternalExtensionType{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExternalInternalSame).DeepCopyInto(out.(*ExternalInternalSame)) - return nil - }, InType: reflect.TypeOf(&ExternalInternalSame{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExternalOptionalExtensionType).DeepCopyInto(out.(*ExternalOptionalExtensionType)) - return nil - }, InType: reflect.TypeOf(&ExternalOptionalExtensionType{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExternalSimple).DeepCopyInto(out.(*ExternalSimple)) - return nil - }, InType: reflect.TypeOf(&ExternalSimple{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExternalTestType1).DeepCopyInto(out.(*ExternalTestType1)) - return nil - }, InType: reflect.TypeOf(&ExternalTestType1{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ExternalTestType2).DeepCopyInto(out.(*ExternalTestType2)) - return nil - }, InType: reflect.TypeOf(&ExternalTestType2{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*InternalComplex).DeepCopyInto(out.(*InternalComplex)) - return nil - }, InType: reflect.TypeOf(&InternalComplex{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*InternalExtensionType).DeepCopyInto(out.(*InternalExtensionType)) - return nil - }, InType: reflect.TypeOf(&InternalExtensionType{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*InternalOptionalExtensionType).DeepCopyInto(out.(*InternalOptionalExtensionType)) - return nil - }, InType: reflect.TypeOf(&InternalOptionalExtensionType{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*InternalSimple).DeepCopyInto(out.(*InternalSimple)) - return nil - }, InType: reflect.TypeOf(&InternalSimple{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ObjectTest).DeepCopyInto(out.(*ObjectTest)) - return nil - }, InType: reflect.TypeOf(&ObjectTest{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ObjectTestExternal).DeepCopyInto(out.(*ObjectTestExternal)) - return nil - }, InType: reflect.TypeOf(&ObjectTestExternal{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*TestType1).DeepCopyInto(out.(*TestType1)) - return nil - }, InType: reflect.TypeOf(&TestType1{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*TestType2).DeepCopyInto(out.(*TestType2)) - return nil - }, InType: reflect.TypeOf(&TestType2{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*UnknownType).DeepCopyInto(out.(*UnknownType)) - return nil - }, InType: reflect.TypeOf(&UnknownType{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*UnversionedType).DeepCopyInto(out.(*UnversionedType)) - return nil - }, InType: reflect.TypeOf(&UnversionedType{})}, - } -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EmbeddedTest) DeepCopyInto(out *EmbeddedTest) { *out = *in diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/zz_generated.deepcopy.go b/staging/src/k8s.io/apimachinery/pkg/runtime/zz_generated.deepcopy.go index d347461ac69..82cf19ce1a8 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -20,31 +20,6 @@ limitations under the License. package runtime -import ( - conversion "k8s.io/apimachinery/pkg/conversion" - reflect "reflect" -) - -// GetGeneratedDeepCopyFuncs returns the generated funcs, since we aren't registering them. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func GetGeneratedDeepCopyFuncs() []conversion.GeneratedDeepCopyFunc { - return []conversion.GeneratedDeepCopyFunc{ - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*RawExtension).DeepCopyInto(out.(*RawExtension)) - return nil - }, InType: reflect.TypeOf(&RawExtension{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Unknown).DeepCopyInto(out.(*Unknown)) - return nil - }, InType: reflect.TypeOf(&Unknown{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*VersionedObjects).DeepCopyInto(out.(*VersionedObjects)) - return nil - }, InType: reflect.TypeOf(&VersionedObjects{})}, - } -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RawExtension) DeepCopyInto(out *RawExtension) { *out = *in diff --git a/staging/src/k8s.io/apimachinery/pkg/test/BUILD b/staging/src/k8s.io/apimachinery/pkg/test/BUILD index 314f8c9d6d0..c4946d52ac5 100644 --- a/staging/src/k8s.io/apimachinery/pkg/test/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/test/BUILD @@ -16,8 +16,8 @@ go_test( "runtime_serializer_protobuf_protobuf_test.go", "runtime_unversioned_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/test", - library = ":go_default_library", deps = [ "//vendor/github.com/google/gofuzz:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", @@ -46,7 +46,6 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/testapigroup:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/testapigroup/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", 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 338206d42c0..08179540e01 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 @@ -84,6 +84,7 @@ func TestDecode(t *testing.T) { json: []byte(`{"apiVersion": "test", "kind": "test_list", "items": []}`), want: &unstructured.UnstructuredList{ Object: map[string]interface{}{"apiVersion": "test", "kind": "test_list"}, + Items: []unstructured.Unstructured{}, }, }, { @@ -147,14 +148,14 @@ func TestUnstructuredGetters(t *testing.T) { "annotations": map[string]interface{}{ "test_annotation": "test_value", }, - "ownerReferences": []map[string]interface{}{ - { + "ownerReferences": []interface{}{ + map[string]interface{}{ "kind": "Pod", "name": "poda", "apiVersion": "v1", "uid": "1", }, - { + map[string]interface{}{ "kind": "Pod", "name": "podb", "apiVersion": "v1", @@ -273,7 +274,7 @@ func TestUnstructuredSetters(t *testing.T) { "selfLink": "test_selfLink", "creationTimestamp": "2009-11-10T23:00:00Z", "deletionTimestamp": "2010-11-10T23:00:00Z", - "deletionGracePeriodSeconds": &ten, + "deletionGracePeriodSeconds": ten, "generation": ten, "labels": map[string]interface{}{ "test_label": "test_value", @@ -281,14 +282,14 @@ func TestUnstructuredSetters(t *testing.T) { "annotations": map[string]interface{}{ "test_annotation": "test_value", }, - "ownerReferences": []map[string]interface{}{ - { + "ownerReferences": []interface{}{ + map[string]interface{}{ "kind": "Pod", "name": "poda", "apiVersion": "v1", "uid": "1", }, - { + map[string]interface{}{ "kind": "Pod", "name": "podb", "apiVersion": "v1", diff --git a/staging/src/k8s.io/apimachinery/pkg/test/zz_generated.deepcopy.go b/staging/src/k8s.io/apimachinery/pkg/test/zz_generated.deepcopy.go index c258ff7dd79..bf67d16817a 100644 --- a/staging/src/k8s.io/apimachinery/pkg/test/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apimachinery/pkg/test/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,27 +21,9 @@ limitations under the License. package test import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -// GetGeneratedDeepCopyFuncs returns the generated funcs, since we aren't registering them. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func GetGeneratedDeepCopyFuncs() []conversion.GeneratedDeepCopyFunc { - return []conversion.GeneratedDeepCopyFunc{ - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*List).DeepCopyInto(out.(*List)) - return nil - }, InType: reflect.TypeOf(&List{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ListV1).DeepCopyInto(out.(*ListV1)) - return nil - }, InType: reflect.TypeOf(&ListV1{})}, - } -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *List) DeepCopyInto(out *List) { *out = *in diff --git a/staging/src/k8s.io/apimachinery/pkg/util/cache/BUILD b/staging/src/k8s.io/apimachinery/pkg/util/cache/BUILD index d589c0d152a..3b868ef57e8 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/cache/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/util/cache/BUILD @@ -12,8 +12,8 @@ go_test( "cache_test.go", "lruexpirecache_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/util/cache", - library = ":go_default_library", deps = [ "//vendor/github.com/golang/groupcache/lru:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library", diff --git a/staging/src/k8s.io/apimachinery/pkg/util/clock/BUILD b/staging/src/k8s.io/apimachinery/pkg/util/clock/BUILD index 62ad5a87b18..e5c117d6646 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/clock/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/util/clock/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["clock_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/util/clock", - library = ":go_default_library", ) go_library( diff --git a/staging/src/k8s.io/apimachinery/pkg/util/diff/BUILD b/staging/src/k8s.io/apimachinery/pkg/util/diff/BUILD index 4ba69bc6354..47d9732b093 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/diff/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/util/diff/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["diff_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/util/diff", - library = ":go_default_library", ) go_library( diff --git a/staging/src/k8s.io/apimachinery/pkg/util/errors/BUILD b/staging/src/k8s.io/apimachinery/pkg/util/errors/BUILD index d13ff240719..61999329a19 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/errors/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/util/errors/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["errors_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/util/errors", - library = ":go_default_library", ) go_library( diff --git a/staging/src/k8s.io/apimachinery/pkg/util/framer/BUILD b/staging/src/k8s.io/apimachinery/pkg/util/framer/BUILD index f0b7cdec52a..8022f0aba20 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/framer/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/util/framer/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["framer_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/util/framer", - library = ":go_default_library", ) go_library( diff --git a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/BUILD b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/BUILD index 94c1d94a249..6450c3a6d75 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["httpstream_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/util/httpstream", - library = ":go_default_library", ) go_library( diff --git a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/BUILD b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/BUILD index 8342083ad9f..278bf12ae8e 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/BUILD @@ -13,8 +13,8 @@ go_test( "roundtripper_test.go", "upgrade_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/util/httpstream/spdy", - library = ":go_default_library", deps = [ "//vendor/github.com/elazarl/goproxy:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/httpstream:go_default_library", 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 index 12bef075dab..d2d3ad8cb01 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go @@ -110,7 +110,7 @@ func (s *SpdyRoundTripper) Dial(req *http.Request) (net.Conn, error) { func (s *SpdyRoundTripper) dial(req *http.Request) (net.Conn, error) { proxier := s.proxier if proxier == nil { - proxier = http.ProxyFromEnvironment + proxier = utilnet.NewProxierWithNoProxyCIDR(http.ProxyFromEnvironment) } proxyURL, err := proxier(req) if err != nil { diff --git a/staging/src/k8s.io/apimachinery/pkg/util/intstr/BUILD b/staging/src/k8s.io/apimachinery/pkg/util/intstr/BUILD index 2e3fe651619..8c66be54fc8 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/intstr/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/util/intstr/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["intstr_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/util/intstr", - library = ":go_default_library", deps = ["//vendor/github.com/ghodss/yaml:go_default_library"], ) diff --git a/staging/src/k8s.io/apimachinery/pkg/util/intstr/generated.pb.go b/staging/src/k8s.io/apimachinery/pkg/util/intstr/generated.pb.go index 433dfa5cd9b..161e9a6f8a5 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/intstr/generated.pb.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/intstr/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apimachinery/pkg/util/intstr/generated.proto b/staging/src/k8s.io/apimachinery/pkg/util/intstr/generated.proto index cccaf6f6891..6819d468d3f 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/intstr/generated.proto +++ b/staging/src/k8s.io/apimachinery/pkg/util/intstr/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apimachinery/pkg/util/json/BUILD b/staging/src/k8s.io/apimachinery/pkg/util/json/BUILD index c9b57bcba3f..5838be3f7b1 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/json/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/util/json/BUILD @@ -15,8 +15,8 @@ go_library( go_test( name = "go_default_test", srcs = ["json_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/util/json", - library = ":go_default_library", ) filegroup( diff --git a/staging/src/k8s.io/apimachinery/pkg/util/jsonmergepatch/BUILD b/staging/src/k8s.io/apimachinery/pkg/util/jsonmergepatch/BUILD index 79b5e54d6a4..233ccad91d1 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/jsonmergepatch/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/util/jsonmergepatch/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["patch_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/util/jsonmergepatch", - library = ":go_default_library", deps = [ "//vendor/github.com/davecgh/go-spew/spew:go_default_library", "//vendor/github.com/evanphx/json-patch:go_default_library", diff --git a/staging/src/k8s.io/apimachinery/pkg/util/mergepatch/BUILD b/staging/src/k8s.io/apimachinery/pkg/util/mergepatch/BUILD index 00715956646..3f50c7618d9 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/mergepatch/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/util/mergepatch/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["util_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/util/mergepatch", - library = ":go_default_library", ) go_library( diff --git a/staging/src/k8s.io/apimachinery/pkg/util/mergepatch/errors.go b/staging/src/k8s.io/apimachinery/pkg/util/mergepatch/errors.go index ac3c1e8cfcf..16501d5afe9 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/mergepatch/errors.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/mergepatch/errors.go @@ -29,6 +29,7 @@ var ( ErrBadPatchFormatForRetainKeys = errors.New("invalid patch format of retainKeys") ErrBadPatchFormatForSetElementOrderList = errors.New("invalid patch format of setElementOrder list") ErrPatchContentNotMatchRetainKeys = errors.New("patch content doesn't match retainKeys list") + ErrUnsupportedStrategicMergePatchFormat = errors.New("strategic merge patch format is not supported") ) func ErrNoMergeKey(m map[string]interface{}, k string) error { diff --git a/staging/src/k8s.io/apimachinery/pkg/util/net/BUILD b/staging/src/k8s.io/apimachinery/pkg/util/net/BUILD index d7390ed5c29..8f6999c92e6 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/net/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/util/net/BUILD @@ -15,8 +15,8 @@ go_test( "port_split_test.go", "util_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/util/net", - library = ":go_default_library", deps = ["//vendor/github.com/spf13/pflag:go_default_library"], ) 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 b544a60a501..bc2a531b9d2 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/net/http.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/net/http.go @@ -276,17 +276,7 @@ func NewProxierWithNoProxyCIDR(delegate func(req *http.Request) (*url.URL, error } return func(req *http.Request) (*url.URL, error) { - host := req.URL.Host - // for some urls, the Host is already the host, not the host:port - if net.ParseIP(host) == nil { - var err error - host, _, err = net.SplitHostPort(req.URL.Host) - if err != nil { - return delegate(req) - } - } - - ip := net.ParseIP(host) + ip := net.ParseIP(req.URL.Hostname()) if ip == nil { return delegate(req) } 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 8f5dd9cdffa..98bd6497174 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 @@ -172,6 +172,30 @@ func TestProxierWithNoProxyCIDR(t *testing.T) { url: "https://192.168.143.1:8443/api", expectedDelegated: false, }, + { + name: "IPv6 cidr", + noProxy: "2001:db8::/48", + url: "https://[2001:db8::1]/api", + expectedDelegated: false, + }, + { + name: "IPv6+port cidr", + noProxy: "2001:db8::/48", + url: "https://[2001:db8::1]:8443/api", + expectedDelegated: false, + }, + { + name: "IPv6, not matching cidr", + noProxy: "2001:db8::/48", + url: "https://[2001:db8:1::1]/api", + expectedDelegated: true, + }, + { + name: "IPv6+port, not matching cidr", + noProxy: "2001:db8::/48", + url: "https://[2001:db8:1::1]:8443/api", + expectedDelegated: true, + }, } for _, test := range testCases { diff --git a/staging/src/k8s.io/apimachinery/pkg/util/proxy/BUILD b/staging/src/k8s.io/apimachinery/pkg/util/proxy/BUILD index 368915c2c81..24083b248d2 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/proxy/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/util/proxy/BUILD @@ -13,8 +13,8 @@ go_test( "transport_test.go", "upgradeaware_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/util/proxy", - library = ":go_default_library", deps = [ "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/github.com/stretchr/testify/require:go_default_library", 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 5bf22969741..6c34ab5241d 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/proxy/transport.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/proxy/transport.go @@ -109,7 +109,7 @@ func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { } if redirect := resp.Header.Get("Location"); redirect != "" { - resp.Header.Set("Location", t.rewriteURL(redirect, req.URL)) + resp.Header.Set("Location", t.rewriteURL(redirect, req.URL, req.Host)) return resp, nil } @@ -131,21 +131,39 @@ func (rt *Transport) WrappedRoundTripper() http.RoundTripper { // rewriteURL rewrites a single URL to go through the proxy, if the URL refers // to the same host as sourceURL, which is the page on which the target URL -// occurred. If any error occurs (e.g. parsing), it returns targetURL. -func (t *Transport) rewriteURL(targetURL string, sourceURL *url.URL) string { +// occurred, or if the URL matches the sourceRequestHost. If any error occurs (e.g. +// parsing), it returns targetURL. +func (t *Transport) rewriteURL(targetURL string, sourceURL *url.URL, sourceRequestHost string) string { url, err := url.Parse(targetURL) if err != nil { return targetURL } - isDifferentHost := url.Host != "" && url.Host != sourceURL.Host + // Example: + // When API server processes a proxy request to a service (e.g. /api/v1/namespace/foo/service/bar/proxy/), + // the sourceURL.Host (i.e. req.URL.Host) is the endpoint IP address of the service. The + // sourceRequestHost (i.e. req.Host) is the Host header that specifies the host on which the + // URL is sought, which can be different from sourceURL.Host. For example, if user sends the + // request through "kubectl proxy" locally (i.e. localhost:8001/api/v1/namespace/foo/service/bar/proxy/), + // sourceRequestHost is "localhost:8001". + // + // If the service's response URL contains non-empty host, and url.Host is equal to either sourceURL.Host + // or sourceRequestHost, we should not consider the returned URL to be a completely different host. + // It's the API server's responsibility to rewrite a same-host-and-absolute-path URL and append the + // necessary URL prefix (i.e. /api/v1/namespace/foo/service/bar/proxy/). + isDifferentHost := url.Host != "" && url.Host != sourceURL.Host && url.Host != sourceRequestHost isRelative := !strings.HasPrefix(url.Path, "/") if isDifferentHost || isRelative { return targetURL } - url.Scheme = t.Scheme - url.Host = t.Host + // Do not rewrite scheme and host if the Transport has empty scheme and host + // when targetURL already contains the sourceRequestHost + if !(url.Host == sourceRequestHost && t.Scheme == "" && t.Host == "") { + url.Scheme = t.Scheme + url.Host = t.Host + } + origPath := url.Path // Do not rewrite URL if the sourceURL already contains the necessary prefix. if strings.HasPrefix(url.Path, t.PathPrepend) { @@ -223,7 +241,7 @@ func (t *Transport) rewriteResponse(req *http.Request, resp *http.Response) (*ht } urlRewriter := func(targetUrl string) string { - return t.rewriteURL(targetUrl, req.URL) + return t.rewriteURL(targetUrl, req.URL, req.Host) } err := rewriteHTML(reader, writer, urlRewriter) if err != nil { diff --git a/staging/src/k8s.io/apimachinery/pkg/util/proxy/transport_test.go b/staging/src/k8s.io/apimachinery/pkg/util/proxy/transport_test.go index f32bcf69edc..e5450078403 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/proxy/transport_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/proxy/transport_test.go @@ -53,6 +53,9 @@ func TestProxyTransport(t *testing.T) { Host: "foo.com", PathPrepend: "/proxy/node/node1:10250", } + emptyHostAndSchemeTransport := &Transport{ + PathPrepend: "/proxy/node/node1:10250", + } type Item struct { input string sourceURL string @@ -62,6 +65,7 @@ func TestProxyTransport(t *testing.T) { forwardedURI string redirect string redirectWant string + reqHost string } table := map[string]Item{ @@ -158,6 +162,14 @@ func TestProxyTransport(t *testing.T) { redirectWant: "http://example.com/redirected/target/", forwardedURI: "/proxy/node/node1:10250/redirect", }, + "redirect abs use reqHost no host no scheme": { + sourceURL: "http://mynode.com/redirect", + transport: emptyHostAndSchemeTransport, + redirect: "http://10.0.0.1:8001/redirected/target/", + redirectWant: "http://10.0.0.1:8001/proxy/node/node1:10250/redirected/target/", + forwardedURI: "/proxy/node/node1:10250/redirect", + reqHost: "10.0.0.1:8001", + }, "source contains the redirect already": { input: `
      kubelet.loggoogle.log
      `, sourceURL: "http://foo.com/logs/log.log", @@ -233,6 +245,9 @@ func TestProxyTransport(t *testing.T) { t.Errorf("%v: Unexpected error: %v", name, err) return } + if item.reqHost != "" { + req.Host = item.reqHost + } resp, err := item.transport.RoundTrip(req) if err != nil { t.Errorf("%v: Unexpected error: %v", name, err) diff --git a/staging/src/k8s.io/apimachinery/pkg/util/rand/BUILD b/staging/src/k8s.io/apimachinery/pkg/util/rand/BUILD index b7769be86e4..12254b4a20d 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/rand/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/util/rand/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["rand_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/util/rand", - library = ":go_default_library", ) go_library( diff --git a/staging/src/k8s.io/apimachinery/pkg/util/runtime/BUILD b/staging/src/k8s.io/apimachinery/pkg/util/runtime/BUILD index 40892fa783c..521efc220e4 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/runtime/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/util/runtime/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["runtime_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/util/runtime", - library = ":go_default_library", ) go_library( 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 748174e1919..442dde7df2c 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/runtime/runtime.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/runtime/runtime.go @@ -128,7 +128,9 @@ func (r *rudimentaryErrorBackoff) OnError(error) { r.lastErrorTimeLock.Lock() defer r.lastErrorTimeLock.Unlock() d := time.Since(r.lastErrorTime) - if d < r.minPeriod { + if d < r.minPeriod && d >= 0 { + // If the time moves backwards for any reason, do nothing + // TODO: remove check "d >= 0" after go 1.8 is no longer supported time.Sleep(r.minPeriod - d) } r.lastErrorTime = time.Now() diff --git a/staging/src/k8s.io/apimachinery/pkg/util/sets/BUILD b/staging/src/k8s.io/apimachinery/pkg/util/sets/BUILD index 5a6175ad4fd..17bb4010ed2 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/sets/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/util/sets/BUILD @@ -51,8 +51,8 @@ $(location //vendor/k8s.io/code-generator/cmd/set-gen) \ go_test( name = "go_default_test", srcs = ["set_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/util/sets", - library = ":go_default_library", ) filegroup( diff --git a/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/BUILD b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/BUILD index 45770861ed6..2f4bcea4d8d 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/BUILD @@ -9,25 +9,38 @@ load( go_test( name = "go_default_test", srcs = ["patch_test.go"], + data = [ + "testdata/swagger-merge-item.json", + "testdata/swagger-precision-item.json", + ], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/util/strategicpatch", - library = ":go_default_library", deps = [ "//vendor/github.com/davecgh/go-spew/spew:go_default_library", "//vendor/github.com/ghodss/yaml:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/mergepatch:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/strategicpatch/testing:go_default_library", ], ) go_library( name = "go_default_library", - srcs = ["patch.go"], + srcs = [ + "errors.go", + "meta.go", + "patch.go", + "types.go", + ], importpath = "k8s.io/apimachinery/pkg/util/strategicpatch", deps = [ + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/mergepatch:go_default_library", "//vendor/k8s.io/apimachinery/third_party/forked/golang/json:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", ], ) @@ -40,6 +53,9 @@ filegroup( filegroup( name = "all-srcs", - srcs = [":package-srcs"], + srcs = [ + ":package-srcs", + "//staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/testing:all-srcs", + ], tags = ["automanaged"], ) diff --git a/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/errors.go b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/errors.go new file mode 100644 index 00000000000..ab66d04523a --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/errors.go @@ -0,0 +1,49 @@ +/* +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 strategicpatch + +import ( + "fmt" +) + +type LookupPatchMetaError struct { + Path string + Err error +} + +func (e LookupPatchMetaError) Error() string { + return fmt.Sprintf("LookupPatchMetaError(%s): %v", e.Path, e.Err) +} + +type FieldNotFoundError struct { + Path string + Field string +} + +func (e FieldNotFoundError) Error() string { + return fmt.Sprintf("unable to find api field %q in %s", e.Field, e.Path) +} + +type InvalidTypeError struct { + Path string + Expected string + Actual string +} + +func (e InvalidTypeError) Error() string { + return fmt.Sprintf("invalid type for %s: got %q, expected %q", e.Path, e.Actual, e.Expected) +} diff --git a/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/meta.go b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/meta.go new file mode 100644 index 00000000000..c31de15e7aa --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/meta.go @@ -0,0 +1,194 @@ +/* +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 strategicpatch + +import ( + "errors" + "fmt" + "reflect" + + "k8s.io/apimachinery/pkg/util/mergepatch" + forkedjson "k8s.io/apimachinery/third_party/forked/golang/json" + openapi "k8s.io/kube-openapi/pkg/util/proto" +) + +type PatchMeta struct { + patchStrategies []string + patchMergeKey string +} + +func (pm PatchMeta) GetPatchStrategies() []string { + if pm.patchStrategies == nil { + return []string{} + } + return pm.patchStrategies +} + +func (pm PatchMeta) SetPatchStrategies(ps []string) { + pm.patchStrategies = ps +} + +func (pm PatchMeta) GetPatchMergeKey() string { + return pm.patchMergeKey +} + +func (pm PatchMeta) SetPatchMergeKey(pmk string) { + pm.patchMergeKey = pmk +} + +type LookupPatchMeta interface { + // LookupPatchMetadataForStruct gets subschema and the patch metadata (e.g. patch strategy and merge key) for map. + LookupPatchMetadataForStruct(key string) (LookupPatchMeta, PatchMeta, error) + // LookupPatchMetadataForSlice get subschema and the patch metadata for slice. + LookupPatchMetadataForSlice(key string) (LookupPatchMeta, PatchMeta, error) + // Get the type name of the field + Name() string +} + +type PatchMetaFromStruct struct { + T reflect.Type +} + +func NewPatchMetaFromStruct(dataStruct interface{}) (PatchMetaFromStruct, error) { + t, err := getTagStructType(dataStruct) + return PatchMetaFromStruct{T: t}, err +} + +var _ LookupPatchMeta = PatchMetaFromStruct{} + +func (s PatchMetaFromStruct) LookupPatchMetadataForStruct(key string) (LookupPatchMeta, PatchMeta, error) { + fieldType, fieldPatchStrategies, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadataForStruct(s.T, key) + if err != nil { + return nil, PatchMeta{}, err + } + + return PatchMetaFromStruct{T: fieldType}, + PatchMeta{ + patchStrategies: fieldPatchStrategies, + patchMergeKey: fieldPatchMergeKey, + }, nil +} + +func (s PatchMetaFromStruct) LookupPatchMetadataForSlice(key string) (LookupPatchMeta, PatchMeta, error) { + subschema, patchMeta, err := s.LookupPatchMetadataForStruct(key) + if err != nil { + return nil, PatchMeta{}, err + } + elemPatchMetaFromStruct := subschema.(PatchMetaFromStruct) + t := elemPatchMetaFromStruct.T + + var elemType reflect.Type + switch t.Kind() { + // If t is an array or a slice, get the element type. + // If element is still an array or a slice, return an error. + // Otherwise, return element type. + case reflect.Array, reflect.Slice: + elemType = t.Elem() + if elemType.Kind() == reflect.Array || elemType.Kind() == reflect.Slice { + return nil, PatchMeta{}, errors.New("unexpected slice of slice") + } + // If t is an pointer, get the underlying element. + // If the underlying element is neither an array nor a slice, the pointer is pointing to a slice, + // e.g. https://github.com/kubernetes/kubernetes/blob/bc22e206c79282487ea0bf5696d5ccec7e839a76/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch_test.go#L2782-L2822 + // If the underlying element is either an array or a slice, return its element type. + case reflect.Ptr: + t = t.Elem() + if t.Kind() == reflect.Array || t.Kind() == reflect.Slice { + t = t.Elem() + } + elemType = t + default: + return nil, PatchMeta{}, fmt.Errorf("expected slice or array type, but got: %s", s.T.Kind().String()) + } + + return PatchMetaFromStruct{T: elemType}, patchMeta, nil +} + +func (s PatchMetaFromStruct) Name() string { + return s.T.Kind().String() +} + +func getTagStructType(dataStruct interface{}) (reflect.Type, error) { + if dataStruct == nil { + return nil, mergepatch.ErrBadArgKind(struct{}{}, nil) + } + + t := reflect.TypeOf(dataStruct) + // Get the underlying type for pointers + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + + if t.Kind() != reflect.Struct { + return nil, mergepatch.ErrBadArgKind(struct{}{}, dataStruct) + } + + return t, nil +} + +func GetTagStructTypeOrDie(dataStruct interface{}) reflect.Type { + t, err := getTagStructType(dataStruct) + if err != nil { + panic(err) + } + return t +} + +type PatchMetaFromOpenAPI struct { + Schema openapi.Schema +} + +func NewPatchMetaFromOpenAPI(s openapi.Schema) PatchMetaFromOpenAPI { + return PatchMetaFromOpenAPI{Schema: s} +} + +var _ LookupPatchMeta = PatchMetaFromOpenAPI{} + +func (s PatchMetaFromOpenAPI) LookupPatchMetadataForStruct(key string) (LookupPatchMeta, PatchMeta, error) { + if s.Schema == nil { + return nil, PatchMeta{}, nil + } + kindItem := NewKindItem(key, s.Schema.GetPath()) + s.Schema.Accept(kindItem) + + err := kindItem.Error() + if err != nil { + return nil, PatchMeta{}, err + } + return PatchMetaFromOpenAPI{Schema: kindItem.subschema}, + kindItem.patchmeta, nil +} + +func (s PatchMetaFromOpenAPI) LookupPatchMetadataForSlice(key string) (LookupPatchMeta, PatchMeta, error) { + if s.Schema == nil { + return nil, PatchMeta{}, nil + } + sliceItem := NewSliceItem(key, s.Schema.GetPath()) + s.Schema.Accept(sliceItem) + + err := sliceItem.Error() + if err != nil { + return nil, PatchMeta{}, err + } + return PatchMetaFromOpenAPI{Schema: sliceItem.subschema}, + sliceItem.patchmeta, nil +} + +func (s PatchMetaFromOpenAPI) Name() string { + schema := s.Schema + return schema.GetName() +} 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 8884c738ed9..09dcd0fd594 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go @@ -22,9 +22,9 @@ import ( "sort" "strings" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/mergepatch" - forkedjson "k8s.io/apimachinery/third_party/forked/golang/json" ) // An alternate implementation of JSON Merge Patch @@ -92,6 +92,16 @@ type MergeOptions struct { // return a patch that yields the modified document when applied to the original document, or an error // if either of the two documents is invalid. func CreateTwoWayMergePatch(original, modified []byte, dataStruct interface{}, fns ...mergepatch.PreconditionFunc) ([]byte, error) { + schema, err := NewPatchMetaFromStruct(dataStruct) + if err != nil { + return nil, err + } + + return CreateTwoWayMergePatchUsingLookupPatchMeta(original, modified, schema, fns...) +} + +func CreateTwoWayMergePatchUsingLookupPatchMeta( + original, modified []byte, schema LookupPatchMeta, fns ...mergepatch.PreconditionFunc) ([]byte, error) { originalMap := map[string]interface{}{} if len(original) > 0 { if err := json.Unmarshal(original, &originalMap); err != nil { @@ -106,7 +116,7 @@ func CreateTwoWayMergePatch(original, modified []byte, dataStruct interface{}, f } } - patchMap, err := CreateTwoWayMergeMapPatch(originalMap, modifiedMap, dataStruct, fns...) + patchMap, err := CreateTwoWayMergeMapPatchUsingLookupPatchMeta(originalMap, modifiedMap, schema, fns...) if err != nil { return nil, err } @@ -118,15 +128,19 @@ func CreateTwoWayMergePatch(original, modified []byte, dataStruct interface{}, f // encoded JSONMap. // The serialized version of the map can then be passed to StrategicMergeMapPatch. func CreateTwoWayMergeMapPatch(original, modified JSONMap, dataStruct interface{}, fns ...mergepatch.PreconditionFunc) (JSONMap, error) { - t, err := getTagStructType(dataStruct) + schema, err := NewPatchMetaFromStruct(dataStruct) if err != nil { return nil, err } + return CreateTwoWayMergeMapPatchUsingLookupPatchMeta(original, modified, schema, fns...) +} + +func CreateTwoWayMergeMapPatchUsingLookupPatchMeta(original, modified JSONMap, schema LookupPatchMeta, fns ...mergepatch.PreconditionFunc) (JSONMap, error) { diffOptions := DiffOptions{ SetElementOrder: true, } - patchMap, err := diffMaps(original, modified, t, diffOptions) + patchMap, err := diffMaps(original, modified, schema, diffOptions) if err != nil { return nil, err } @@ -151,12 +165,9 @@ func CreateTwoWayMergeMapPatch(original, modified JSONMap, dataStruct interface{ // - IFF list of primitives && merge strategy - use parallel deletion list // - IFF list of maps or primitives with replace strategy (default) - set patch value to the value in modified // - Build $retainKeys directive for fields with retainKeys patch strategy -func diffMaps(original, modified map[string]interface{}, t reflect.Type, diffOptions DiffOptions) (map[string]interface{}, error) { +func diffMaps(original, modified map[string]interface{}, schema LookupPatchMeta, diffOptions DiffOptions) (map[string]interface{}, error) { patch := map[string]interface{}{} - // Get the underlying type for pointers - if t.Kind() == reflect.Ptr { - t = t.Elem() - } + // This will be used to build the $retainKeys directive sent in the patch retainKeysList := make([]interface{}, 0, len(modified)) @@ -198,10 +209,10 @@ func diffMaps(original, modified map[string]interface{}, t reflect.Type, diffOpt switch originalValueTyped := originalValue.(type) { case map[string]interface{}: modifiedValueTyped := modifiedValue.(map[string]interface{}) - err = handleMapDiff(key, originalValueTyped, modifiedValueTyped, patch, t, diffOptions) + err = handleMapDiff(key, originalValueTyped, modifiedValueTyped, patch, schema, diffOptions) case []interface{}: modifiedValueTyped := modifiedValue.([]interface{}) - err = handleSliceDiff(key, originalValueTyped, modifiedValueTyped, patch, t, diffOptions) + err = handleSliceDiff(key, originalValueTyped, modifiedValueTyped, patch, schema, diffOptions) default: replacePatchFieldIfNotEqual(key, originalValue, modifiedValue, patch, diffOptions) } @@ -248,8 +259,9 @@ func handleDirectiveMarker(key string, originalValue, modifiedValue interface{}, // patch is the patch map that contains key and the updated value, and it is the parent of originalValue, modifiedValue // diffOptions contains multiple options to control how we do the diff. func handleMapDiff(key string, originalValue, modifiedValue, patch map[string]interface{}, - t reflect.Type, diffOptions DiffOptions) error { - fieldType, fieldPatchStrategies, _, err := forkedjson.LookupPatchMetadata(t, key) + schema LookupPatchMeta, diffOptions DiffOptions) error { + subschema, patchMeta, err := schema.LookupPatchMetadataForStruct(key) + if err != nil { // We couldn't look up metadata for the field // If the values are identical, this doesn't matter, no patch is needed @@ -259,7 +271,7 @@ func handleMapDiff(key string, originalValue, modifiedValue, patch map[string]in // Otherwise, return the error return err } - retainKeys, patchStrategy, err := extractRetainKeysPatchStrategy(fieldPatchStrategies) + retainKeys, patchStrategy, err := extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies()) if err != nil { return err } @@ -271,7 +283,7 @@ func handleMapDiff(key string, originalValue, modifiedValue, patch map[string]in patch[key] = modifiedValue } default: - patchValue, err := diffMaps(originalValue, modifiedValue, fieldType, diffOptions) + patchValue, err := diffMaps(originalValue, modifiedValue, subschema, diffOptions) if err != nil { return err } @@ -290,8 +302,8 @@ func handleMapDiff(key string, originalValue, modifiedValue, patch map[string]in // patch is the patch map that contains key and the updated value, and it is the parent of originalValue, modifiedValue // diffOptions contains multiple options to control how we do the diff. func handleSliceDiff(key string, originalValue, modifiedValue []interface{}, patch map[string]interface{}, - t reflect.Type, diffOptions DiffOptions) error { - fieldType, fieldPatchStrategies, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadata(t, key) + schema LookupPatchMeta, diffOptions DiffOptions) error { + subschema, patchMeta, err := schema.LookupPatchMetadataForSlice(key) if err != nil { // We couldn't look up metadata for the field // If the values are identical, this doesn't matter, no patch is needed @@ -301,7 +313,7 @@ func handleSliceDiff(key string, originalValue, modifiedValue []interface{}, pat // Otherwise, return the error return err } - retainKeys, patchStrategy, err := extractRetainKeysPatchStrategy(fieldPatchStrategies) + retainKeys, patchStrategy, err := extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies()) if err != nil { return err } @@ -309,7 +321,7 @@ func handleSliceDiff(key string, originalValue, modifiedValue []interface{}, pat // Merge the 2 slices using mergePatchKey case mergeDirective: diffOptions.BuildRetainKeysDirective = retainKeys - addList, deletionList, setOrderList, err := diffLists(originalValue, modifiedValue, fieldType.Elem(), fieldPatchMergeKey, diffOptions) + addList, deletionList, setOrderList, err := diffLists(originalValue, modifiedValue, subschema, patchMeta.GetPatchMergeKey(), diffOptions) if err != nil { return err } @@ -515,6 +527,9 @@ func normalizeSliceOrder(toSort, order []interface{}, mergeKey string, kind refl return nil, err } toSort, toDelete, err = extractToDeleteItems(toSort) + if err != nil { + return nil, err + } } sort.SliceStable(toSort, func(i, j int) bool { @@ -533,7 +548,7 @@ func normalizeSliceOrder(toSort, order []interface{}, mergeKey string, kind refl // another list to set the order of the list // Only list of primitives with merge strategy will generate a parallel deletion list. // These two lists should yield modified when applied to original, for lists with merge semantics. -func diffLists(original, modified []interface{}, t reflect.Type, mergeKey string, diffOptions DiffOptions) ([]interface{}, []interface{}, []interface{}, error) { +func diffLists(original, modified []interface{}, schema LookupPatchMeta, mergeKey string, diffOptions DiffOptions) ([]interface{}, []interface{}, []interface{}, error) { if len(original) == 0 { // Both slices are empty - do nothing if len(modified) == 0 || diffOptions.IgnoreChangesAndAdditions { @@ -553,8 +568,14 @@ func diffLists(original, modified []interface{}, t reflect.Type, mergeKey string kind := elementType.Kind() switch kind { case reflect.Map: - patchList, deleteList, err = diffListsOfMaps(original, modified, t, mergeKey, diffOptions) + patchList, deleteList, err = diffListsOfMaps(original, modified, schema, mergeKey, diffOptions) + if err != nil { + return nil, nil, nil, err + } patchList, err = normalizeSliceOrder(patchList, modified, mergeKey, kind) + if err != nil { + return nil, nil, nil, err + } orderSame, err := isOrderSame(original, modified, mergeKey) if err != nil { return nil, nil, nil, err @@ -580,6 +601,9 @@ func diffLists(original, modified []interface{}, t reflect.Type, mergeKey string return nil, nil, nil, mergepatch.ErrNoListOfLists default: patchList, deleteList, err = diffListsOfScalars(original, modified, diffOptions) + if err != nil { + return nil, nil, nil, err + } patchList, err = normalizeSliceOrder(patchList, modified, mergeKey, kind) // generate the setElementOrder list when there are content changes or order changes if diffOptions.SetElementOrder && ((!diffOptions.IgnoreDeletions && len(deleteList) > 0) || @@ -690,15 +714,15 @@ func compareListValuesAtIndex(list1Inbounds, list2Inbounds bool, list1Value, lis // diffListsOfMaps takes a pair of lists and // returns a (recursive) strategic merge patch list contains additions and changes and // a deletion list contains deletions -func diffListsOfMaps(original, modified []interface{}, t reflect.Type, mergeKey string, diffOptions DiffOptions) ([]interface{}, []interface{}, error) { +func diffListsOfMaps(original, modified []interface{}, schema LookupPatchMeta, mergeKey string, diffOptions DiffOptions) ([]interface{}, []interface{}, error) { patch := make([]interface{}, 0, len(modified)) deletionList := make([]interface{}, 0, len(original)) - originalSorted, err := sortMergeListsByNameArray(original, t, mergeKey, false) + originalSorted, err := sortMergeListsByNameArray(original, schema, mergeKey, false) if err != nil { return nil, nil, err } - modifiedSorted, err := sortMergeListsByNameArray(modified, t, mergeKey, false) + modifiedSorted, err := sortMergeListsByNameArray(modified, schema, mergeKey, false) if err != nil { return nil, nil, err } @@ -733,7 +757,7 @@ func diffListsOfMaps(original, modified []interface{}, t reflect.Type, mergeKey switch { case bothInBounds && ItemMatchesOriginalAndModifiedSlice(originalElementMergeKeyValueString, modifiedElementMergeKeyValueString): // Merge key values are equal, so recurse - patchValue, err := diffMaps(originalElement, modifiedElement, t, diffOptions) + patchValue, err := diffMaps(originalElement, modifiedElement, schema, diffOptions) if err != nil { return nil, nil, err } @@ -786,6 +810,15 @@ func getMapAndMergeKeyValueByIndex(index int, mergeKey string, listOfMaps []inte // must be json encoded content. A patch can be created from an original and a modified document // by calling CreateStrategicMergePatch. func StrategicMergePatch(original, patch []byte, dataStruct interface{}) ([]byte, error) { + schema, err := NewPatchMetaFromStruct(dataStruct) + if err != nil { + return nil, err + } + + return StrategicMergePatchUsingLookupPatchMeta(original, patch, schema) +} + +func StrategicMergePatchUsingLookupPatchMeta(original, patch []byte, schema LookupPatchMeta) ([]byte, error) { originalMap, err := handleUnmarshal(original) if err != nil { return nil, err @@ -795,7 +828,7 @@ func StrategicMergePatch(original, patch []byte, dataStruct interface{}) ([]byte return nil, err } - result, err := StrategicMergeMapPatch(originalMap, patchMap, dataStruct) + result, err := StrategicMergeMapPatchUsingLookupPatchMeta(originalMap, patchMap, schema) if err != nil { return nil, err } @@ -816,38 +849,35 @@ func handleUnmarshal(j []byte) (map[string]interface{}, error) { return m, nil } -// StrategicMergePatch applies a strategic merge patch. The original and patch documents +// StrategicMergeMapPatch applies a strategic merge patch. The original and patch documents // must be JSONMap. A patch can be created from an original and modified document by // calling CreateTwoWayMergeMapPatch. // Warning: the original and patch JSONMap objects are mutated by this function and should not be reused. func StrategicMergeMapPatch(original, patch JSONMap, dataStruct interface{}) (JSONMap, error) { - t, err := getTagStructType(dataStruct) + schema, err := NewPatchMetaFromStruct(dataStruct) if err != nil { return nil, err } + + // We need the go struct tags `patchMergeKey` and `patchStrategy` for fields that support a strategic merge patch. + // For native resources, we can easily figure out these tags since we know the fields. + + // Because custom resources are decoded as Unstructured and because we're missing the metadata about how to handle + // each field in a strategic merge patch, we can't find the go struct tags. Hence, we can't easily do a strategic merge + // for custom resources. So we should fail fast and return an error. + if _, ok := dataStruct.(*unstructured.Unstructured); ok { + return nil, mergepatch.ErrUnsupportedStrategicMergePatchFormat + } + + return StrategicMergeMapPatchUsingLookupPatchMeta(original, patch, schema) +} + +func StrategicMergeMapPatchUsingLookupPatchMeta(original, patch JSONMap, schema LookupPatchMeta) (JSONMap, error) { mergeOptions := MergeOptions{ MergeParallelList: true, IgnoreUnmatchedNulls: true, } - return mergeMap(original, patch, t, mergeOptions) -} - -func getTagStructType(dataStruct interface{}) (reflect.Type, error) { - if dataStruct == nil { - return nil, mergepatch.ErrBadArgKind(struct{}{}, nil) - } - - t := reflect.TypeOf(dataStruct) - // Get the underlying type for pointers - if t.Kind() == reflect.Ptr { - t = t.Elem() - } - - if t.Kind() != reflect.Struct { - return nil, mergepatch.ErrBadArgKind(struct{}{}, dataStruct) - } - - return t, nil + return mergeMap(original, patch, schema, mergeOptions) } // handleDirectiveInMergeMap handles the patch directive when merging 2 maps. @@ -1054,8 +1084,8 @@ func applyRetainKeysDirective(original, patch map[string]interface{}, options Me // Then, sort them by the relative order in setElementOrder, patch list and live list. // The precedence is $setElementOrder > order in patch list > order in live list. // This function will delete the item after merging it to prevent process it again in the future. -// Ref: https://git.k8s.io/community/contributors/design-proposals/preserve-order-in-strategic-merge-patch.md -func mergePatchIntoOriginal(original, patch map[string]interface{}, t reflect.Type, mergeOptions MergeOptions) error { +// Ref: https://git.k8s.io/community/contributors/design-proposals/cli/preserve-order-in-strategic-merge-patch.md +func mergePatchIntoOriginal(original, patch map[string]interface{}, schema LookupPatchMeta, mergeOptions MergeOptions) error { for key, patchV := range patch { // Do nothing if there is no ordering directive if !strings.HasPrefix(key, setElementOrderDirectivePrefix) { @@ -1082,9 +1112,9 @@ func mergePatchIntoOriginal(original, patch map[string]interface{}, t reflect.Ty var ( ok bool originalFieldValue, patchFieldValue, merged []interface{} - patchStrategy, mergeKey string - patchStrategies []string - fieldType reflect.Type + patchStrategy string + patchMeta PatchMeta + subschema LookupPatchMeta ) typedSetElementOrderList, ok := setElementOrderInPatch.([]interface{}) if !ok { @@ -1110,16 +1140,16 @@ func mergePatchIntoOriginal(original, patch map[string]interface{}, t reflect.Ty return mergepatch.ErrBadArgType(patchFieldValue, patchList) } } - fieldType, patchStrategies, mergeKey, err = forkedjson.LookupPatchMetadata(t, originalKey) + subschema, patchMeta, err = schema.LookupPatchMetadataForSlice(originalKey) if err != nil { return err } - _, patchStrategy, err = extractRetainKeysPatchStrategy(patchStrategies) + _, patchStrategy, err = extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies()) if err != nil { return err } // Check for consistency between the element order list and the field it applies to - err = validatePatchWithSetOrderList(patchFieldValue, typedSetElementOrderList, mergeKey) + err = validatePatchWithSetOrderList(patchFieldValue, typedSetElementOrderList, patchMeta.GetPatchMergeKey()) if err != nil { return err } @@ -1132,8 +1162,8 @@ func mergePatchIntoOriginal(original, patch map[string]interface{}, t reflect.Ty // list was added merged = patchFieldValue case foundOriginal && foundPatch: - merged, err = mergeSliceHandler(originalList, patchList, fieldType, - patchStrategy, mergeKey, false, mergeOptions) + merged, err = mergeSliceHandler(originalList, patchList, subschema, + patchStrategy, patchMeta.GetPatchMergeKey(), false, mergeOptions) if err != nil { return err } @@ -1143,13 +1173,13 @@ func mergePatchIntoOriginal(original, patch map[string]interface{}, t reflect.Ty // Split all items into patch items and server-only items and then enforce the order. var patchItems, serverOnlyItems []interface{} - if len(mergeKey) == 0 { + if len(patchMeta.GetPatchMergeKey()) == 0 { // Primitives doesn't need merge key to do partitioning. patchItems, serverOnlyItems = partitionPrimitivesByPresentInList(merged, typedSetElementOrderList) } else { // Maps need merge key to do partitioning. - patchItems, serverOnlyItems, err = partitionMapsByPresentInList(merged, typedSetElementOrderList, mergeKey) + patchItems, serverOnlyItems, err = partitionMapsByPresentInList(merged, typedSetElementOrderList, patchMeta.GetPatchMergeKey()) if err != nil { return err } @@ -1163,7 +1193,7 @@ func mergePatchIntoOriginal(original, patch map[string]interface{}, t reflect.Ty // normalize merged list // typedSetElementOrderList contains all the relative order in typedPatchList, // so don't need to use typedPatchList - both, err := normalizeElementOrder(patchItems, serverOnlyItems, typedSetElementOrderList, originalFieldValue, mergeKey, kind) + both, err := normalizeElementOrder(patchItems, serverOnlyItems, typedSetElementOrderList, originalFieldValue, patchMeta.GetPatchMergeKey(), kind) if err != nil { return err } @@ -1225,7 +1255,7 @@ func partitionMapsByPresentInList(original, partitionBy []interface{}, mergeKey // If patch contains any null field (e.g. field_1: null) that is not // present in original, then to propagate it to the end result use // mergeOptions.IgnoreUnmatchedNulls == false. -func mergeMap(original, patch map[string]interface{}, t reflect.Type, mergeOptions MergeOptions) (map[string]interface{}, error) { +func mergeMap(original, patch map[string]interface{}, schema LookupPatchMeta, mergeOptions MergeOptions) (map[string]interface{}, error) { if v, ok := patch[directiveMarker]; ok { return handleDirectiveInMergeMap(v, patch) } @@ -1245,7 +1275,7 @@ func mergeMap(original, patch map[string]interface{}, t reflect.Type, mergeOptio // When not merging the directive, it will make sure $setElementOrder list exist only in original. // When merging the directive, it will process $setElementOrder and its patch list together. // This function will delete the merged elements from patch so they will not be reprocessed - err = mergePatchIntoOriginal(original, patch, t, mergeOptions) + err = mergePatchIntoOriginal(original, patch, schema, mergeOptions) if err != nil { return nil, err } @@ -1283,11 +1313,6 @@ func mergeMap(original, patch map[string]interface{}, t reflect.Type, mergeOptio continue } - // If the data type is a pointer, resolve the element. - if t.Kind() == reflect.Ptr { - t = t.Elem() - } - originalType := reflect.TypeOf(original[k]) patchType := reflect.TypeOf(patchV) if originalType != patchType { @@ -1295,22 +1320,27 @@ func mergeMap(original, patch map[string]interface{}, t reflect.Type, mergeOptio continue } // If they're both maps or lists, recurse into the value. - // First find the fieldPatchStrategy and fieldPatchMergeKey. - fieldType, fieldPatchStrategies, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadata(t, k) - if err != nil { - return nil, err - } - _, patchStrategy, err := extractRetainKeysPatchStrategy(fieldPatchStrategies) - if err != nil { - return nil, err - } - switch originalType.Kind() { case reflect.Map: - - original[k], err = mergeMapHandler(original[k], patchV, fieldType, patchStrategy, mergeOptions) + subschema, patchMeta, err2 := schema.LookupPatchMetadataForStruct(k) + if err2 != nil { + return nil, err2 + } + _, patchStrategy, err2 := extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies()) + if err2 != nil { + return nil, err2 + } + original[k], err = mergeMapHandler(original[k], patchV, subschema, patchStrategy, mergeOptions) case reflect.Slice: - original[k], err = mergeSliceHandler(original[k], patchV, fieldType, patchStrategy, fieldPatchMergeKey, isDeleteList, mergeOptions) + subschema, patchMeta, err2 := schema.LookupPatchMetadataForSlice(k) + if err2 != nil { + return nil, err2 + } + _, patchStrategy, err2 := extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies()) + if err2 != nil { + return nil, err2 + } + original[k], err = mergeSliceHandler(original[k], patchV, subschema, patchStrategy, patchMeta.GetPatchMergeKey(), isDeleteList, mergeOptions) default: original[k] = patchV } @@ -1323,7 +1353,7 @@ func mergeMap(original, patch map[string]interface{}, t reflect.Type, mergeOptio // mergeMapHandler handles how to merge `patchV` whose key is `key` with `original` respecting // fieldPatchStrategy and mergeOptions. -func mergeMapHandler(original, patch interface{}, fieldType reflect.Type, +func mergeMapHandler(original, patch interface{}, schema LookupPatchMeta, fieldPatchStrategy string, mergeOptions MergeOptions) (map[string]interface{}, error) { typedOriginal, typedPatch, err := mapTypeAssertion(original, patch) if err != nil { @@ -1331,7 +1361,7 @@ func mergeMapHandler(original, patch interface{}, fieldType reflect.Type, } if fieldPatchStrategy != replaceDirective { - return mergeMap(typedOriginal, typedPatch, fieldType, mergeOptions) + return mergeMap(typedOriginal, typedPatch, schema, mergeOptions) } else { return typedPatch, nil } @@ -1339,7 +1369,7 @@ func mergeMapHandler(original, patch interface{}, fieldType reflect.Type, // mergeSliceHandler handles how to merge `patchV` whose key is `key` with `original` respecting // fieldPatchStrategy, fieldPatchMergeKey, isDeleteList and mergeOptions. -func mergeSliceHandler(original, patch interface{}, fieldType reflect.Type, +func mergeSliceHandler(original, patch interface{}, schema LookupPatchMeta, fieldPatchStrategy, fieldPatchMergeKey string, isDeleteList bool, mergeOptions MergeOptions) ([]interface{}, error) { typedOriginal, typedPatch, err := sliceTypeAssertion(original, patch) if err != nil { @@ -1347,8 +1377,7 @@ func mergeSliceHandler(original, patch interface{}, fieldType reflect.Type, } if fieldPatchStrategy == mergeDirective { - elemType := fieldType.Elem() - return mergeSlice(typedOriginal, typedPatch, elemType, fieldPatchMergeKey, mergeOptions, isDeleteList) + return mergeSlice(typedOriginal, typedPatch, schema, fieldPatchMergeKey, mergeOptions, isDeleteList) } else { return typedPatch, nil } @@ -1357,7 +1386,7 @@ func mergeSliceHandler(original, patch interface{}, fieldType reflect.Type, // Merge two slices together. Note: This may modify both the original slice and // the patch because getting a deep copy of a slice in golang is highly // non-trivial. -func mergeSlice(original, patch []interface{}, elemType reflect.Type, mergeKey string, mergeOptions MergeOptions, isDeleteList bool) ([]interface{}, error) { +func mergeSlice(original, patch []interface{}, schema LookupPatchMeta, mergeKey string, mergeOptions MergeOptions, isDeleteList bool) ([]interface{}, error) { if len(original) == 0 && len(patch) == 0 { return original, nil } @@ -1382,7 +1411,7 @@ func mergeSlice(original, patch []interface{}, elemType reflect.Type, mergeKey s } else { if mergeKey == "" { - return nil, fmt.Errorf("cannot merge lists without merge key for type %s", elemType.Kind().String()) + return nil, fmt.Errorf("cannot merge lists without merge key for %s", schema.Name()) } original, patch, err = mergeSliceWithSpecialElements(original, patch, mergeKey) @@ -1390,7 +1419,7 @@ func mergeSlice(original, patch []interface{}, elemType reflect.Type, mergeKey s return nil, err } - merged, err = mergeSliceWithoutSpecialElements(original, patch, mergeKey, elemType, mergeOptions) + merged, err = mergeSliceWithoutSpecialElements(original, patch, mergeKey, schema, mergeOptions) if err != nil { return nil, err } @@ -1468,7 +1497,7 @@ func deleteMatchingEntries(original []interface{}, mergeKey string, mergeValue i // mergeSliceWithoutSpecialElements merges slices with non-special elements. // original and patch must be slices of maps, they should be checked before calling this function. -func mergeSliceWithoutSpecialElements(original, patch []interface{}, mergeKey string, elemType reflect.Type, mergeOptions MergeOptions) ([]interface{}, error) { +func mergeSliceWithoutSpecialElements(original, patch []interface{}, mergeKey string, schema LookupPatchMeta, mergeOptions MergeOptions) ([]interface{}, error) { for _, v := range patch { typedV := v.(map[string]interface{}) mergeValue, ok := typedV[mergeKey] @@ -1487,7 +1516,7 @@ func mergeSliceWithoutSpecialElements(original, patch []interface{}, mergeKey st var mergedMaps interface{} var err error // Merge into original. - mergedMaps, err = mergeMap(originalMap, typedV, elemType, mergeOptions) + mergedMaps, err = mergeMap(originalMap, typedV, schema, mergeOptions) if err != nil { return nil, err } @@ -1520,7 +1549,7 @@ func findMapInSliceBasedOnKeyValue(m []interface{}, key string, value interface{ for k, v := range m { typedV, ok := v.(map[string]interface{}) if !ok { - return nil, 0, false, fmt.Errorf("value for key %v is not a map.", k) + return nil, 0, false, fmt.Errorf("value for key %v is not a map", k) } valueToMatch, ok := typedV[key] @@ -1536,14 +1565,14 @@ func findMapInSliceBasedOnKeyValue(m []interface{}, key string, value interface{ // by key. This is needed by tests because in JSON, list order is significant, // but in Strategic Merge Patch, merge lists do not have significant order. // Sorting the lists allows for order-insensitive comparison of patched maps. -func sortMergeListsByName(mapJSON []byte, dataStruct interface{}) ([]byte, error) { +func sortMergeListsByName(mapJSON []byte, schema LookupPatchMeta) ([]byte, error) { var m map[string]interface{} err := json.Unmarshal(mapJSON, &m) if err != nil { - return nil, err + return nil, mergepatch.ErrBadJSONDoc } - newM, err := sortMergeListsByNameMap(m, reflect.TypeOf(dataStruct)) + newM, err := sortMergeListsByNameMap(m, schema) if err != nil { return nil, err } @@ -1552,7 +1581,7 @@ func sortMergeListsByName(mapJSON []byte, dataStruct interface{}) ([]byte, error } // Function sortMergeListsByNameMap recursively sorts the merge lists by its mergeKey in a map. -func sortMergeListsByNameMap(s map[string]interface{}, t reflect.Type) (map[string]interface{}, error) { +func sortMergeListsByNameMap(s map[string]interface{}, schema LookupPatchMeta) (map[string]interface{}, error) { newS := map[string]interface{}{} for k, v := range s { if k == retainKeysDirective { @@ -1573,26 +1602,29 @@ func sortMergeListsByNameMap(s map[string]interface{}, t reflect.Type) (map[stri return nil, mergepatch.ErrBadPatchFormatForSetElementOrderList } } else if k != directiveMarker { - fieldType, fieldPatchStrategies, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadata(t, k) - if err != nil { - return nil, err - } - _, patchStrategy, err := extractRetainKeysPatchStrategy(fieldPatchStrategies) - if err != nil { - return nil, err - } - - // If v is a map or a merge slice, recurse. - if typedV, ok := v.(map[string]interface{}); ok { - var err error - v, err = sortMergeListsByNameMap(typedV, fieldType) + // recurse for map and slice. + switch typedV := v.(type) { + case map[string]interface{}: + subschema, _, err := schema.LookupPatchMetadataForStruct(k) + if err != nil { + return nil, err + } + v, err = sortMergeListsByNameMap(typedV, subschema) + if err != nil { + return nil, err + } + case []interface{}: + subschema, patchMeta, err := schema.LookupPatchMetadataForSlice(k) + if err != nil { + return nil, err + } + _, patchStrategy, err := extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies()) if err != nil { return nil, err } - } else if typedV, ok := v.([]interface{}); ok { if patchStrategy == mergeDirective { var err error - v, err = sortMergeListsByNameArray(typedV, fieldType.Elem(), fieldPatchMergeKey, true) + v, err = sortMergeListsByNameArray(typedV, subschema, patchMeta.GetPatchMergeKey(), true) if err != nil { return nil, err } @@ -1607,7 +1639,7 @@ func sortMergeListsByNameMap(s map[string]interface{}, t reflect.Type) (map[stri } // Function sortMergeListsByNameMap recursively sorts the merge lists by its mergeKey in an array. -func sortMergeListsByNameArray(s []interface{}, elemType reflect.Type, mergeKey string, recurse bool) ([]interface{}, error) { +func sortMergeListsByNameArray(s []interface{}, schema LookupPatchMeta, mergeKey string, recurse bool) ([]interface{}, error) { if len(s) == 0 { return s, nil } @@ -1630,7 +1662,7 @@ func sortMergeListsByNameArray(s []interface{}, elemType reflect.Type, mergeKey for _, elem := range s { if recurse { typedElem := elem.(map[string]interface{}) - newElem, err := sortMergeListsByNameMap(typedElem, elemType) + newElem, err := sortMergeListsByNameMap(typedElem, schema) if err != nil { return nil, err } @@ -1776,18 +1808,13 @@ func sliceElementType(slices ...[]interface{}) (reflect.Type, error) { // objects overlap with different values in any key. All keys are required to be // strings. Since patches of the same Type have congruent keys, this is valid // for multiple patch types. This method supports strategic merge patch semantics. -func MergingMapsHaveConflicts(left, right map[string]interface{}, dataStruct interface{}) (bool, error) { - t, err := getTagStructType(dataStruct) - if err != nil { - return true, err - } - - return mergingMapFieldsHaveConflicts(left, right, t, "", "") +func MergingMapsHaveConflicts(left, right map[string]interface{}, schema LookupPatchMeta) (bool, error) { + return mergingMapFieldsHaveConflicts(left, right, schema, "", "") } func mergingMapFieldsHaveConflicts( left, right interface{}, - fieldType reflect.Type, + schema LookupPatchMeta, fieldPatchStrategy, fieldPatchMergeKey string, ) (bool, error) { switch leftType := left.(type) { @@ -1818,15 +1845,14 @@ func mergingMapFieldsHaveConflicts( return false, nil } // Check the individual keys. - return mapsHaveConflicts(leftType, rightType, fieldType) + return mapsHaveConflicts(leftType, rightType, schema) case []interface{}: rightType, ok := right.([]interface{}) if !ok { return true, nil } - return slicesHaveConflicts(leftType, rightType, fieldType, fieldPatchStrategy, fieldPatchMergeKey) - + return slicesHaveConflicts(leftType, rightType, schema, fieldPatchStrategy, fieldPatchMergeKey) case string, float64, bool, int, int64, nil: return !reflect.DeepEqual(left, right), nil default: @@ -1834,21 +1860,37 @@ func mergingMapFieldsHaveConflicts( } } -func mapsHaveConflicts(typedLeft, typedRight map[string]interface{}, structType reflect.Type) (bool, error) { +func mapsHaveConflicts(typedLeft, typedRight map[string]interface{}, schema LookupPatchMeta) (bool, error) { for key, leftValue := range typedLeft { if key != directiveMarker && key != retainKeysDirective { if rightValue, ok := typedRight[key]; ok { - fieldType, fieldPatchStrategies, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadata(structType, key) - if err != nil { - return true, err - } - _, patchStrategy, err := extractRetainKeysPatchStrategy(fieldPatchStrategies) - if err != nil { - return true, err + var subschema LookupPatchMeta + var patchMeta PatchMeta + var patchStrategy string + var err error + switch leftValue.(type) { + case []interface{}: + subschema, patchMeta, err = schema.LookupPatchMetadataForSlice(key) + if err != nil { + return true, err + } + _, patchStrategy, err = extractRetainKeysPatchStrategy(patchMeta.patchStrategies) + if err != nil { + return true, err + } + case map[string]interface{}: + subschema, patchMeta, err = schema.LookupPatchMetadataForStruct(key) + if err != nil { + return true, err + } + _, patchStrategy, err = extractRetainKeysPatchStrategy(patchMeta.patchStrategies) + if err != nil { + return true, err + } } if hasConflicts, err := mergingMapFieldsHaveConflicts(leftValue, rightValue, - fieldType, patchStrategy, fieldPatchMergeKey); hasConflicts { + subschema, patchStrategy, patchMeta.GetPatchMergeKey()); hasConflicts { return true, err } } @@ -1860,7 +1902,7 @@ func mapsHaveConflicts(typedLeft, typedRight map[string]interface{}, structType func slicesHaveConflicts( typedLeft, typedRight []interface{}, - fieldType reflect.Type, + schema LookupPatchMeta, fieldPatchStrategy, fieldPatchMergeKey string, ) (bool, error) { elementType, err := sliceElementType(typedLeft, typedRight) @@ -1868,7 +1910,6 @@ func slicesHaveConflicts( return true, err } - valueType := fieldType.Elem() if fieldPatchStrategy == mergeDirective { // Merging lists of scalars have no conflicts by definition // So we only need to check further if the elements are maps @@ -1887,7 +1928,7 @@ func slicesHaveConflicts( return true, err } - return mapsOfMapsHaveConflicts(leftMap, rightMap, valueType) + return mapsOfMapsHaveConflicts(leftMap, rightMap, schema) } // Either we don't have type information, or these are non-merging lists @@ -1905,7 +1946,7 @@ func slicesHaveConflicts( // Compare the slices element by element in order // This test will fail if the slices are not sorted for i := range typedLeft { - if hasConflicts, err := mergingMapFieldsHaveConflicts(typedLeft[i], typedRight[i], valueType, "", ""); hasConflicts { + if hasConflicts, err := mergingMapFieldsHaveConflicts(typedLeft[i], typedRight[i], schema, "", ""); hasConflicts { return true, err } } @@ -1932,10 +1973,10 @@ func sliceOfMapsToMapOfMaps(slice []interface{}, mergeKey string) (map[string]in return result, nil } -func mapsOfMapsHaveConflicts(typedLeft, typedRight map[string]interface{}, structType reflect.Type) (bool, error) { +func mapsOfMapsHaveConflicts(typedLeft, typedRight map[string]interface{}, schema LookupPatchMeta) (bool, error) { for key, leftValue := range typedLeft { if rightValue, ok := typedRight[key]; ok { - if hasConflicts, err := mergingMapFieldsHaveConflicts(leftValue, rightValue, structType, "", ""); hasConflicts { + if hasConflicts, err := mergingMapFieldsHaveConflicts(leftValue, rightValue, schema, "", ""); hasConflicts { return true, err } } @@ -1955,7 +1996,7 @@ func mapsOfMapsHaveConflicts(typedLeft, typedRight map[string]interface{}, struc // in a way that is different from how it is changed in current (e.g., deleting it, changing its // value). We also propagate values fields that do not exist in original but are explicitly // defined in modified. -func CreateThreeWayMergePatch(original, modified, current []byte, dataStruct interface{}, overwrite bool, fns ...mergepatch.PreconditionFunc) ([]byte, error) { +func CreateThreeWayMergePatch(original, modified, current []byte, schema LookupPatchMeta, overwrite bool, fns ...mergepatch.PreconditionFunc) ([]byte, error) { originalMap := map[string]interface{}{} if len(original) > 0 { if err := json.Unmarshal(original, &originalMap); err != nil { @@ -1977,11 +2018,6 @@ func CreateThreeWayMergePatch(original, modified, current []byte, dataStruct int } } - t, err := getTagStructType(dataStruct) - if err != nil { - return nil, err - } - // The patch is the difference from current to modified without deletions, plus deletions // from original to modified. To find it, we compute deletions, which are the deletions from // original to modified, and delta, which is the difference from current to modified without @@ -1990,7 +2026,7 @@ func CreateThreeWayMergePatch(original, modified, current []byte, dataStruct int IgnoreDeletions: true, SetElementOrder: true, } - deltaMap, err := diffMaps(currentMap, modifiedMap, t, deltaMapDiffOptions) + deltaMap, err := diffMaps(currentMap, modifiedMap, schema, deltaMapDiffOptions) if err != nil { return nil, err } @@ -1998,13 +2034,13 @@ func CreateThreeWayMergePatch(original, modified, current []byte, dataStruct int SetElementOrder: true, IgnoreChangesAndAdditions: true, } - deletionsMap, err := diffMaps(originalMap, modifiedMap, t, deletionsMapDiffOptions) + deletionsMap, err := diffMaps(originalMap, modifiedMap, schema, deletionsMapDiffOptions) if err != nil { return nil, err } mergeOptions := MergeOptions{} - patchMap, err := mergeMap(deletionsMap, deltaMap, t, mergeOptions) + patchMap, err := mergeMap(deletionsMap, deltaMap, schema, mergeOptions) if err != nil { return nil, err } @@ -2020,12 +2056,12 @@ func CreateThreeWayMergePatch(original, modified, current []byte, dataStruct int // then return a conflict error. if !overwrite { changeMapDiffOptions := DiffOptions{} - changedMap, err := diffMaps(originalMap, currentMap, t, changeMapDiffOptions) + changedMap, err := diffMaps(originalMap, currentMap, schema, changeMapDiffOptions) if err != nil { return nil, err } - hasConflicts, err := MergingMapsHaveConflicts(patchMap, changedMap, dataStruct) + hasConflicts, err := MergingMapsHaveConflicts(patchMap, changedMap, schema) if err != nil { return nil, err } 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 7f6372db6ad..4721803ccd1 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 @@ -17,17 +17,25 @@ limitations under the License. package strategicpatch import ( - "encoding/json" "fmt" + "path/filepath" "reflect" "strings" "testing" "github.com/davecgh/go-spew/spew" "github.com/ghodss/yaml" + "k8s.io/apimachinery/pkg/runtime" + "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" +) + +var ( + fakeMergeItemSchema = sptest.Fake{Path: filepath.Join("testdata", "swagger-merge-item.json")} + fakePrecisionItemSchema = sptest.Fake{Path: filepath.Join("testdata", "swagger-precision-item.json")} ) type SortMergeListTestCases struct { @@ -86,31 +94,34 @@ type StrategicMergePatchRawTestCaseData struct { } type MergeItem struct { - Name string - Value string - Other string - MergingList []MergeItem `patchStrategy:"merge" patchMergeKey:"name"` - NonMergingList []MergeItem - MergingIntList []int `patchStrategy:"merge"` - NonMergingIntList []int - MergeItemPtr *MergeItem `patchStrategy:"merge" patchMergeKey:"name"` - SimpleMap map[string]string - ReplacingItem runtime.RawExtension `patchStrategy:"replace"` - RetainKeysMap RetainKeysMergeItem `patchStrategy:"retainKeys"` - RetainKeysMergingList []MergeItem `patchStrategy:"merge,retainKeys" patchMergeKey:"name"` + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` + Other string `json:"other,omitempty"` + MergingList []MergeItem `json:"mergingList,omitempty" patchStrategy:"merge" patchMergeKey:"name"` + NonMergingList []MergeItem `json:"nonMergingList,omitempty"` + MergingIntList []int `json:"mergingIntList,omitempty" patchStrategy:"merge"` + NonMergingIntList []int `json:"nonMergingIntList,omitempty"` + MergeItemPtr *MergeItem `json:"mergeItemPtr,omitempty" patchStrategy:"merge" patchMergeKey:"name"` + SimpleMap map[string]string `json:"simpleMap,omitempty"` + ReplacingItem runtime.RawExtension `json:"replacingItem,omitempty" patchStrategy:"replace"` + RetainKeysMap RetainKeysMergeItem `json:"retainKeysMap,omitempty" patchStrategy:"retainKeys"` + RetainKeysMergingList []MergeItem `json:"retainKeysMergingList,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"name"` } type RetainKeysMergeItem struct { - Name string - Value string - Other string - SimpleMap map[string]string - MergingIntList []int `patchStrategy:"merge"` - MergingList []MergeItem `patchStrategy:"merge" patchMergeKey:"name"` - NonMergingList []MergeItem + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` + Other string `json:"other,omitempty"` + SimpleMap map[string]string `json:"simpleMap,omitempty"` + MergingIntList []int `json:"mergingIntList,omitempty" patchStrategy:"merge"` + MergingList []MergeItem `json:"mergingList,omitempty" patchStrategy:"merge" patchMergeKey:"name"` + NonMergingList []MergeItem `json:"nonMergingList,omitempty"` } -var mergeItem MergeItem +var ( + mergeItem MergeItem + mergeItemStructSchema = PatchMetaFromStruct{T: GetTagStructTypeOrDie(mergeItem)} +) // These are test cases for SortMergeList, used to assert that it (recursively) // sorts both merging and non merging lists correctly. @@ -151,7 +162,6 @@ testCases: - name: 3 - name: 2 - description: sort lists of maps and nested lists of maps - fieldTypes: original: mergingList: - name: 2 @@ -271,6 +281,14 @@ testCases: `) func TestSortMergeLists(t *testing.T) { + mergeItemOpenapiSchema := PatchMetaFromOpenAPI{ + Schema: sptest.GetSchemaOrDie(fakeMergeItemSchema, "mergeItem"), + } + schemas := []LookupPatchMeta{ + mergeItemStructSchema, + mergeItemOpenapiSchema, + } + tc := SortMergeListTestCases{} err := yaml.Unmarshal(sortMergeListTestCaseData, &tc) if err != nil { @@ -278,12 +296,15 @@ func TestSortMergeLists(t *testing.T) { return } - for _, c := range tc.TestCases { - got := sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Original), c.Description) - expected := testObjectToJSONOrFail(t, c.Sorted) - if !reflect.DeepEqual(got, expected) { - t.Errorf("error in test case: %s\ncannot sort object:\n%s\nexpected:\n%s\ngot:\n%s\n", - c.Description, mergepatch.ToYAMLOrError(c.Original), mergepatch.ToYAMLOrError(c.Sorted), jsonToYAMLOrError(got)) + for _, schema := range schemas { + for _, c := range tc.TestCases { + temp := testObjectToJSONOrFail(t, c.Original) + got := sortJsonOrFail(t, temp, c.Description, schema) + expected := testObjectToJSONOrFail(t, c.Sorted) + if !reflect.DeepEqual(got, expected) { + t.Errorf("using %s error in test case: %s\ncannot sort object:\n%s\nexpected:\n%s\ngot:\n%s\n", + getSchemaType(schema), c.Description, mergepatch.ToYAMLOrError(c.Original), mergepatch.ToYAMLOrError(c.Sorted), jsonToYAMLOrError(got)) + } } } } @@ -633,9 +654,32 @@ mergingIntList: ExpectedError: "doesn't match", }, }, + { + Description: "missing merge key should error out", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + value: a +`), + TwoWay: []byte(` +mergingList: + - value: b +`), + ExpectedError: "does not contain declared merge key", + }, + }, } func TestCustomStrategicMergePatch(t *testing.T) { + mergeItemOpenapiSchema := PatchMetaFromOpenAPI{ + Schema: sptest.GetSchemaOrDie(fakeMergeItemSchema, "mergeItem"), + } + schemas := []LookupPatchMeta{ + mergeItemStructSchema, + mergeItemOpenapiSchema, + } + tc := StrategicMergePatchTestCases{} err := yaml.Unmarshal(customStrategicMergePatchTestCaseData, &tc) if err != nil { @@ -643,14 +687,16 @@ func TestCustomStrategicMergePatch(t *testing.T) { return } - for _, c := range tc.TestCases { - original, expectedTwoWayPatch, _, expectedResult := twoWayTestCaseToJSONOrFail(t, c) - testPatchApplication(t, original, expectedTwoWayPatch, expectedResult, c.Description, "") - } + for _, schema := range schemas { + for _, c := range tc.TestCases { + original, expectedTwoWayPatch, _, expectedResult := twoWayTestCaseToJSONOrFail(t, c, schema) + testPatchApplication(t, original, expectedTwoWayPatch, expectedResult, c.Description, "", schema) + } - for _, c := range customStrategicMergePatchRawTestCases { - original, expectedTwoWayPatch, _, expectedResult := twoWayRawTestCaseToJSONOrFail(t, c) - testPatchApplication(t, original, expectedTwoWayPatch, expectedResult, c.Description, c.ExpectedError) + for _, c := range customStrategicMergePatchRawTestCases { + original, expectedTwoWayPatch, _, expectedResult := twoWayRawTestCaseToJSONOrFail(t, c) + testPatchApplication(t, original, expectedTwoWayPatch, expectedResult, c.Description, c.ExpectedError, schema) + } } } @@ -2350,68 +2396,6 @@ mergingList: value: 1 - name: 2 other: b -`), - }, - }, - { - Description: "replace non merging list nested in merging list with value conflict", - StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ - Original: []byte(` -mergingList: - - name: 1 - nonMergingList: - - name: 1 - - name: 2 - value: 2 - - name: 2 -`), - TwoWay: []byte(` -$setElementOrder/mergingList: - - name: 1 - - name: 2 -mergingList: - - name: 1 - nonMergingList: - - name: 1 - value: 1 -`), - Modified: []byte(` -mergingList: - - name: 1 - nonMergingList: - - name: 1 - value: 1 - - name: 2 -`), - Current: []byte(` -mergingList: - - name: 1 - other: a - nonMergingList: - - name: 1 - value: c - - name: 2 - other: b -`), - ThreeWay: []byte(` -$setElementOrder/mergingList: - - name: 1 - - name: 2 -mergingList: - - name: 1 - nonMergingList: - - name: 1 - value: 1 -`), - Result: []byte(` -mergingList: - - name: 1 - other: a - nonMergingList: - - name: 1 - value: 1 - - name: 2 - other: b `), }, }, @@ -6041,14 +6025,16 @@ mergeItemPtr: } func TestStrategicMergePatch(t *testing.T) { - testStrategicMergePatchWithCustomArguments(t, "bad original", - "", "{}", mergeItem, mergepatch.ErrBadJSONDoc) - testStrategicMergePatchWithCustomArguments(t, "bad patch", - "{}", "", mergeItem, mergepatch.ErrBadJSONDoc) - testStrategicMergePatchWithCustomArguments(t, "bad struct", + testStrategicMergePatchWithCustomArgumentsUsingStruct(t, "bad struct", "{}", "{}", []byte(""), mergepatch.ErrBadArgKind(struct{}{}, []byte{})) - testStrategicMergePatchWithCustomArguments(t, "nil struct", - "{}", "{}", nil, mergepatch.ErrBadArgKind(struct{}{}, nil)) + + mergeItemOpenapiSchema := PatchMetaFromOpenAPI{ + Schema: sptest.GetSchemaOrDie(fakeMergeItemSchema, "mergeItem"), + } + schemas := []LookupPatchMeta{ + mergeItemStructSchema, + mergeItemOpenapiSchema, + } tc := StrategicMergePatchTestCases{} err := yaml.Unmarshal(createStrategicMergePatchTestCaseData, &tc) @@ -6057,53 +6043,76 @@ func TestStrategicMergePatch(t *testing.T) { return } - for _, c := range tc.TestCases { - testTwoWayPatch(t, c) - testThreeWayPatch(t, c) - } + for _, schema := range schemas { + testStrategicMergePatchWithCustomArguments(t, "bad original", + "", "{}", schema, mergepatch.ErrBadJSONDoc) + testStrategicMergePatchWithCustomArguments(t, "bad patch", + "{}", "", schema, mergepatch.ErrBadJSONDoc) + testStrategicMergePatchWithCustomArguments(t, "nil struct", + "{}", "{}", nil, mergepatch.ErrBadArgKind(struct{}{}, nil)) - // run multiple times to exercise different map traversal orders - for i := 0; i < 10; i++ { - for _, c := range strategicMergePatchRawTestCases { - testTwoWayPatchForRawTestCase(t, c) - testThreeWayPatchForRawTestCase(t, c) + for _, c := range tc.TestCases { + testTwoWayPatch(t, c, schema) + testThreeWayPatch(t, c, schema) + } + + // run multiple times to exercise different map traversal orders + for i := 0; i < 10; i++ { + for _, c := range strategicMergePatchRawTestCases { + testTwoWayPatchForRawTestCase(t, c, schema) + testThreeWayPatchForRawTestCase(t, c, schema) + } } } } -func testStrategicMergePatchWithCustomArguments(t *testing.T, description, original, patch string, dataStruct interface{}, err error) { - _, err2 := StrategicMergePatch([]byte(original), []byte(patch), dataStruct) - if err2 != err { - if err2 == nil { - t.Errorf("expected error: %s\ndid not occur in test case: %s", err, description) +func testStrategicMergePatchWithCustomArgumentsUsingStruct(t *testing.T, description, original, patch string, dataStruct interface{}, expected error) { + schema, actual := NewPatchMetaFromStruct(dataStruct) + // If actual is not nil, check error. If errors match, return. + if actual != nil { + checkErrorsEqual(t, description, expected, actual, schema) + return + } + testStrategicMergePatchWithCustomArguments(t, description, original, patch, schema, expected) +} + +func testStrategicMergePatchWithCustomArguments(t *testing.T, description, original, patch string, schema LookupPatchMeta, expected error) { + _, actual := StrategicMergePatch([]byte(original), []byte(patch), schema) + checkErrorsEqual(t, description, expected, actual, schema) +} + +func checkErrorsEqual(t *testing.T, description string, expected, actual error, schema LookupPatchMeta) { + if actual != expected { + if actual == nil { + t.Errorf("using %s expected error: %s\ndid not occur in test case: %s", getSchemaType(schema), expected, description) return } - if err == nil || err2.Error() != err.Error() { - t.Errorf("unexpected error: %s\noccurred in test case: %s", err2, description) + if expected == nil || actual.Error() != expected.Error() { + t.Errorf("using %s unexpected error: %s\noccurred in test case: %s", getSchemaType(schema), actual, description) return } } } -func testTwoWayPatch(t *testing.T, c StrategicMergePatchTestCase) { - original, expectedPatch, modified, expectedResult := twoWayTestCaseToJSONOrFail(t, c) +func testTwoWayPatch(t *testing.T, c StrategicMergePatchTestCase, schema LookupPatchMeta) { + original, expectedPatch, modified, expectedResult := twoWayTestCaseToJSONOrFail(t, c, schema) - actualPatch, err := CreateTwoWayMergePatch(original, modified, mergeItem) + actualPatch, err := CreateTwoWayMergePatchUsingLookupPatchMeta(original, modified, schema) if err != nil { - t.Errorf("error: %s\nin test case: %s\ncannot create two way patch: %s:\n%s\n", - err, c.Description, original, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData)) + t.Errorf("using %s error: %s\nin test case: %s\ncannot create two way patch: %s:\n%s\n", + getSchemaType(schema), err, c.Description, original, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData)) return } testPatchCreation(t, expectedPatch, actualPatch, c.Description) - testPatchApplication(t, original, actualPatch, expectedResult, c.Description, "") + testPatchApplication(t, original, actualPatch, expectedResult, c.Description, "", schema) } -func testTwoWayPatchForRawTestCase(t *testing.T, c StrategicMergePatchRawTestCase) { +func testTwoWayPatchForRawTestCase(t *testing.T, c StrategicMergePatchRawTestCase, schema LookupPatchMeta) { original, expectedPatch, modified, expectedResult := twoWayRawTestCaseToJSONOrFail(t, c) - actualPatch, err := CreateTwoWayMergePatch(original, modified, mergeItem) + actualPatch, err := CreateTwoWayMergePatchUsingLookupPatchMeta(original, modified, schema) if err != nil { t.Errorf("error: %s\nin test case: %s\ncannot create two way patch:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n", err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) @@ -6111,18 +6120,18 @@ func testTwoWayPatchForRawTestCase(t *testing.T, c StrategicMergePatchRawTestCas } testPatchCreation(t, expectedPatch, actualPatch, c.Description) - testPatchApplication(t, original, actualPatch, expectedResult, c.Description, c.ExpectedError) + testPatchApplication(t, original, actualPatch, expectedResult, c.Description, c.ExpectedError, schema) } -func twoWayTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchTestCase) ([]byte, []byte, []byte, []byte) { +func twoWayTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchTestCase, schema LookupPatchMeta) ([]byte, []byte, []byte, []byte) { expectedResult := c.TwoWayResult if expectedResult == nil { expectedResult = c.Modified } - return sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Original), c.Description), - sortJsonOrFail(t, testObjectToJSONOrFail(t, c.TwoWay), c.Description), - sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Modified), c.Description), - sortJsonOrFail(t, testObjectToJSONOrFail(t, expectedResult), c.Description) + return sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Original), c.Description, schema), + sortJsonOrFail(t, testObjectToJSONOrFail(t, c.TwoWay), c.Description, schema), + sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Modified), c.Description, schema), + sortJsonOrFail(t, testObjectToJSONOrFail(t, expectedResult), c.Description, schema) } func twoWayRawTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchRawTestCase) ([]byte, []byte, []byte, []byte) { @@ -6136,94 +6145,94 @@ func twoWayRawTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchRawTestCas yamlToJSONOrError(t, expectedResult) } -func testThreeWayPatch(t *testing.T, c StrategicMergePatchTestCase) { - original, modified, current, expected, result := threeWayTestCaseToJSONOrFail(t, c) - actual, err := CreateThreeWayMergePatch(original, modified, current, mergeItem, false) +func testThreeWayPatch(t *testing.T, c StrategicMergePatchTestCase, schema LookupPatchMeta) { + original, modified, current, expected, result := threeWayTestCaseToJSONOrFail(t, c, schema) + actual, err := CreateThreeWayMergePatch(original, modified, current, schema, false) if err != nil { if !mergepatch.IsConflict(err) { - t.Errorf("error: %s\nin test case: %s\ncannot create three way patch:\n%s\n", - err, c.Description, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData)) + t.Errorf("using %s error: %s\nin test case: %s\ncannot create three way patch:\n%s\n", + getSchemaType(schema), err, c.Description, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData)) return } if !strings.Contains(c.Description, "conflict") { - t.Errorf("unexpected conflict: %s\nin test case: %s\ncannot create three way patch:\n%s\n", - err, c.Description, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData)) + t.Errorf("using %s unexpected conflict: %s\nin test case: %s\ncannot create three way patch:\n%s\n", + getSchemaType(schema), err, c.Description, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData)) return } if len(c.Result) > 0 { - actual, err := CreateThreeWayMergePatch(original, modified, current, mergeItem, true) + actual, err := CreateThreeWayMergePatch(original, modified, current, schema, true) if err != nil { - t.Errorf("error: %s\nin test case: %s\ncannot force three way patch application:\n%s\n", - err, c.Description, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData)) + t.Errorf("using %s error: %s\nin test case: %s\ncannot force three way patch application:\n%s\n", + getSchemaType(schema), err, c.Description, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData)) return } testPatchCreation(t, expected, actual, c.Description) - testPatchApplication(t, current, actual, result, c.Description, "") + testPatchApplication(t, current, actual, result, c.Description, "", schema) } return } if strings.Contains(c.Description, "conflict") || len(c.Result) < 1 { - t.Errorf("error in test case: %s\nexpected conflict did not occur:\n%s\n", - c.Description, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData)) + t.Errorf("using %s error in test case: %s\nexpected conflict did not occur:\n%s\n", + getSchemaType(schema), c.Description, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData)) return } testPatchCreation(t, expected, actual, c.Description) - testPatchApplication(t, current, actual, result, c.Description, "") + testPatchApplication(t, current, actual, result, c.Description, "", schema) } -func testThreeWayPatchForRawTestCase(t *testing.T, c StrategicMergePatchRawTestCase) { +func testThreeWayPatchForRawTestCase(t *testing.T, c StrategicMergePatchRawTestCase, schema LookupPatchMeta) { original, modified, current, expected, result := threeWayRawTestCaseToJSONOrFail(t, c) - actual, err := CreateThreeWayMergePatch(original, modified, current, mergeItem, false) + actual, err := CreateThreeWayMergePatch(original, modified, current, schema, false) if err != nil { if !mergepatch.IsConflict(err) { - t.Errorf("error: %s\nin test case: %s\ncannot create three way patch:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n", - err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) + t.Errorf("using %s error: %s\nin test case: %s\ncannot create three way patch:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n", + getSchemaType(schema), err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) return } if !strings.Contains(c.Description, "conflict") { - t.Errorf("unexpected conflict: %s\nin test case: %s\ncannot create three way patch:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n", - err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) + t.Errorf("using %s unexpected conflict: %s\nin test case: %s\ncannot create three way patch:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n", + getSchemaType(schema), err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) return } if len(c.Result) > 0 { - actual, err := CreateThreeWayMergePatch(original, modified, current, mergeItem, true) + actual, err := CreateThreeWayMergePatch(original, modified, current, schema, true) if err != nil { - t.Errorf("error: %s\nin test case: %s\ncannot force three way patch application:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n", - err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) + t.Errorf("using %s error: %s\nin test case: %s\ncannot force three way patch application:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n", + getSchemaType(schema), err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) return } testPatchCreation(t, expected, actual, c.Description) - testPatchApplication(t, current, actual, result, c.Description, c.ExpectedError) + testPatchApplication(t, current, actual, result, c.Description, c.ExpectedError, schema) } return } if strings.Contains(c.Description, "conflict") || len(c.Result) < 1 { - t.Errorf("error: %s\nin test case: %s\nexpected conflict did not occur:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n", - err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) + t.Errorf("using %s error: %s\nin test case: %s\nexpected conflict did not occur:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n", + getSchemaType(schema), err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) return } testPatchCreation(t, expected, actual, c.Description) - testPatchApplication(t, current, actual, result, c.Description, c.ExpectedError) + testPatchApplication(t, current, actual, result, c.Description, c.ExpectedError, schema) } -func threeWayTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchTestCase) ([]byte, []byte, []byte, []byte, []byte) { - return sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Original), c.Description), - sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Modified), c.Description), - sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Current), c.Description), - sortJsonOrFail(t, testObjectToJSONOrFail(t, c.ThreeWay), c.Description), - sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Result), c.Description) +func threeWayTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchTestCase, schema LookupPatchMeta) ([]byte, []byte, []byte, []byte, []byte) { + return sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Original), c.Description, schema), + sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Modified), c.Description, schema), + sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Current), c.Description, schema), + sortJsonOrFail(t, testObjectToJSONOrFail(t, c.ThreeWay), c.Description, schema), + sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Result), c.Description, schema) } func threeWayRawTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchRawTestCase) ([]byte, []byte, []byte, []byte, []byte) { @@ -6242,22 +6251,22 @@ func testPatchCreation(t *testing.T, expected, actual []byte, description string } } -func testPatchApplication(t *testing.T, original, patch, expected []byte, description, expectedError string) { - result, err := StrategicMergePatch(original, patch, mergeItem) +func testPatchApplication(t *testing.T, original, patch, expected []byte, description, expectedError string, schema LookupPatchMeta) { + result, err := StrategicMergePatchUsingLookupPatchMeta(original, patch, schema) if len(expectedError) != 0 { if err != nil && strings.Contains(err.Error(), expectedError) { return } - t.Errorf("expected error should contain:\n%s\nin test case: %s\nbut got:\n%s\n", expectedError, description, err) + t.Errorf("using %s expected error should contain:\n%s\nin test case: %s\nbut got:\n%s\n", getSchemaType(schema), expectedError, description, err) } if err != nil { - t.Errorf("error: %s\nin test case: %s\ncannot apply patch:\n%s\nto original:\n%s\n", - err, description, jsonToYAMLOrError(patch), jsonToYAMLOrError(original)) + t.Errorf("using %s error: %s\nin test case: %s\ncannot apply patch:\n%s\nto original:\n%s\n", + getSchemaType(schema), err, description, jsonToYAMLOrError(patch), jsonToYAMLOrError(original)) return } if !reflect.DeepEqual(result, expected) { - format := "error in test case: %s\npatch application failed:\noriginal:\n%s\npatch:\n%s\nexpected:\n%s\ngot:\n%s\n" + format := "using error in test case: %s\npatch application failed:\noriginal:\n%s\npatch:\n%s\nexpected:\n%s\ngot:\n%s\n" t.Errorf(format, description, jsonToYAMLOrError(original), jsonToYAMLOrError(patch), jsonToYAMLOrError(expected), jsonToYAMLOrError(result)) @@ -6277,19 +6286,23 @@ func testObjectToJSONOrFail(t *testing.T, o map[string]interface{}) []byte { return j } -func sortJsonOrFail(t *testing.T, j []byte, description string) []byte { +func sortJsonOrFail(t *testing.T, j []byte, description string, schema LookupPatchMeta) []byte { if j == nil { return nil } - r, err := sortMergeListsByName(j, mergeItem) + r, err := sortMergeListsByName(j, schema) if err != nil { - t.Errorf("error: %s\nin test case: %s\ncannot sort object:\n%s\n", err, description, j) + t.Errorf("using %s error: %s\n in test case: %s\ncannot sort object:\n%s\n", getSchemaType(schema), err, description, j) return nil } return r } +func getSchemaType(schema LookupPatchMeta) string { + return reflect.TypeOf(schema).String() +} + func jsonToYAMLOrError(j []byte) string { y, err := jsonToYAML(j) if err != nil { @@ -6336,14 +6349,17 @@ func yamlToJSONOrError(t *testing.T, y []byte) []byte { } type PrecisionItem struct { - Name string - Int32 int32 - Int64 int64 - Float32 float32 - Float64 float64 + Name string `json:"name,omitempty"` + Int32 int32 `json:"int32,omitempty"` + Int64 int64 `json:"int64,omitempty"` + Float32 float32 `json:"float32,omitempty"` + Float64 float64 `json:"float64,omitempty"` } -var precisionItem PrecisionItem +var ( + precisionItem PrecisionItem + precisionItemStructSchema = PatchMetaFromStruct{T: GetTagStructTypeOrDie(precisionItem)} +) func TestNumberConversion(t *testing.T) { testcases := map[string]struct { @@ -6396,25 +6412,35 @@ func TestNumberConversion(t *testing.T) { }, } - for k, tc := range testcases { - patch, err := CreateTwoWayMergePatch([]byte(tc.Old), []byte(tc.New), precisionItem) - if err != nil { - t.Errorf("%s: unexpected error %v", k, err) - continue - } - if tc.ExpectedPatch != string(patch) { - t.Errorf("%s: expected %s, got %s", k, tc.ExpectedPatch, string(patch)) - continue - } + precisionItemOpenapiSchema := PatchMetaFromOpenAPI{ + Schema: sptest.GetSchemaOrDie(fakePrecisionItemSchema, "precisionItem"), + } + precisionItemSchemas := []LookupPatchMeta{ + precisionItemStructSchema, + precisionItemOpenapiSchema, + } - result, err := StrategicMergePatch([]byte(tc.Old), patch, precisionItem) - if err != nil { - t.Errorf("%s: unexpected error %v", k, err) - continue - } - if tc.ExpectedResult != string(result) { - t.Errorf("%s: expected %s, got %s", k, tc.ExpectedResult, string(result)) - continue + for _, schema := range precisionItemSchemas { + for k, tc := range testcases { + patch, err := CreateTwoWayMergePatchUsingLookupPatchMeta([]byte(tc.Old), []byte(tc.New), schema) + if err != nil { + t.Errorf("using %s in testcase %s: unexpected error %v", getSchemaType(schema), k, err) + continue + } + if tc.ExpectedPatch != string(patch) { + t.Errorf("using %s in testcase %s: expected %s, got %s", getSchemaType(schema), k, tc.ExpectedPatch, string(patch)) + continue + } + + result, err := StrategicMergePatchUsingLookupPatchMeta([]byte(tc.Old), patch, schema) + if err != nil { + t.Errorf("using %s in testcase %s: unexpected error %v", getSchemaType(schema), k, err) + continue + } + if tc.ExpectedResult != string(result) { + t.Errorf("using %s in testcase %s: expected %s, got %s", getSchemaType(schema), k, tc.ExpectedResult, string(result)) + continue + } } } } @@ -6437,7 +6463,7 @@ replacingItem: name: my-object value: some-value other: current-other -merginglist: +mergingList: - name: 1 - name: 2 - name: 3 @@ -6451,7 +6477,7 @@ replacingItem: name: my-object value: some-value other: current-other -merginglist: +mergingList: - name: 1 - name: 2 - name: 3 @@ -6461,7 +6487,7 @@ replacingItem: The: RawExtension `), TwoWay: []byte(` -merginglist: +mergingList: - name: 1 - name: 2 - name: 3 @@ -6474,7 +6500,7 @@ replacingItem: name: my-object value: some-value other: current-other -merginglist: +mergingList: - name: 1 - name: 2 - name: 3 @@ -6493,7 +6519,7 @@ replacingItem: name: my-object value: some-value other: current-other -merginglist: +mergingList: - name: 1 - name: 2 - name: 3 @@ -6511,7 +6537,7 @@ replacingItem: name: my-object value: some-value other: current-other -merginglist: +mergingList: - name: 1 replacingItem: Some: Generic @@ -6523,7 +6549,7 @@ replacingItem: name: my-object value: some-value other: current-other -merginglist: +mergingList: - name: 1 - name: 3 replacingItem: @@ -6536,7 +6562,7 @@ replacingItem: name: my-object value: some-value other: current-other -merginglist: +mergingList: - name: 1 - name: 2 replacingItem: @@ -6545,10 +6571,10 @@ replacingItem: The: RawExtension `), TwoWay: []byte(` -$setElementOrder/merginglist: +$setElementOrder/mergingList: - name: 1 - name: 2 -merginglist: +mergingList: - name: 2 replacingItem: Newly: Modified @@ -6559,7 +6585,7 @@ replacingItem: name: my-object value: some-value other: current-other -merginglist: +mergingList: - name: 1 - name: 2 replacingItem: @@ -6568,10 +6594,10 @@ replacingItem: The: RawExtension `), ThreeWay: []byte(` -$setElementOrder/merginglist: +$setElementOrder/mergingList: - name: 1 - name: 2 -merginglist: +mergingList: - name: 2 replacingItem: Newly: Modified @@ -6582,7 +6608,7 @@ replacingItem: name: my-object value: some-value other: current-other -merginglist: +mergingList: - name: 1 - name: 2 - name: 3 @@ -6596,9 +6622,19 @@ replacingItem: } func TestReplaceWithRawExtension(t *testing.T) { - for _, c := range replaceRawExtensionPatchTestCases { - testTwoWayPatchForRawTestCase(t, c) - testThreeWayPatchForRawTestCase(t, c) + mergeItemOpenapiSchema := PatchMetaFromOpenAPI{ + Schema: sptest.GetSchemaOrDie(fakeMergeItemSchema, "mergeItem"), + } + schemas := []LookupPatchMeta{ + mergeItemStructSchema, + mergeItemOpenapiSchema, + } + + for _, schema := range schemas { + for _, c := range replaceRawExtensionPatchTestCases { + testTwoWayPatchForRawTestCase(t, c, schema) + testThreeWayPatchForRawTestCase(t, c, schema) + } } } @@ -6658,60 +6694,70 @@ func TestUnknownField(t *testing.T) { }, } + mergeItemOpenapiSchema := PatchMetaFromOpenAPI{ + Schema: sptest.GetSchemaOrDie(fakeMergeItemSchema, "mergeItem"), + } + schemas := []LookupPatchMeta{ + mergeItemStructSchema, + mergeItemOpenapiSchema, + } + for _, k := range sets.StringKeySet(testcases).List() { tc := testcases[k] - func() { - twoWay, err := CreateTwoWayMergePatch([]byte(tc.Original), []byte(tc.Modified), &MergeItem{}) - if err != nil { - if len(tc.ExpectedTwoWayErr) == 0 { - t.Errorf("%s: error making two-way patch: %v", k, err) + for _, schema := range schemas { + func() { + twoWay, err := CreateTwoWayMergePatchUsingLookupPatchMeta([]byte(tc.Original), []byte(tc.Modified), schema) + if err != nil { + if len(tc.ExpectedTwoWayErr) == 0 { + t.Errorf("using %s in testcase %s: error making two-way patch: %v", getSchemaType(schema), k, err) + } + if !strings.Contains(err.Error(), tc.ExpectedTwoWayErr) { + t.Errorf("using %s in testcase %s: expected error making two-way patch to contain '%s', got %s", getSchemaType(schema), k, tc.ExpectedTwoWayErr, err) + } + return } - if !strings.Contains(err.Error(), tc.ExpectedTwoWayErr) { - t.Errorf("%s: expected error making two-way patch to contain '%s', got %s", k, tc.ExpectedTwoWayErr, err) + + if string(twoWay) != tc.ExpectedTwoWay { + t.Errorf("using %s in testcase %s: expected two-way patch:\n\t%s\ngot\n\t%s", getSchemaType(schema), k, string(tc.ExpectedTwoWay), string(twoWay)) + return } - return - } - if string(twoWay) != tc.ExpectedTwoWay { - t.Errorf("%s: expected two-way patch:\n\t%s\ngot\n\t%s", k, string(tc.ExpectedTwoWay), string(twoWay)) - return - } - - twoWayResult, err := StrategicMergePatch([]byte(tc.Original), twoWay, MergeItem{}) - if err != nil { - t.Errorf("%s: error applying two-way patch: %v", k, err) - return - } - if string(twoWayResult) != tc.ExpectedTwoWayResult { - t.Errorf("%s: expected two-way result:\n\t%s\ngot\n\t%s", k, string(tc.ExpectedTwoWayResult), string(twoWayResult)) - return - } - }() - - func() { - threeWay, err := CreateThreeWayMergePatch([]byte(tc.Original), []byte(tc.Modified), []byte(tc.Current), &MergeItem{}, false) - if err != nil { - if len(tc.ExpectedThreeWayErr) == 0 { - t.Errorf("%s: error making three-way patch: %v", k, err) - } else if !strings.Contains(err.Error(), tc.ExpectedThreeWayErr) { - t.Errorf("%s: expected error making three-way patch to contain '%s', got %s", k, tc.ExpectedThreeWayErr, err) + twoWayResult, err := StrategicMergePatchUsingLookupPatchMeta([]byte(tc.Original), twoWay, schema) + if err != nil { + t.Errorf("using %s in testcase %s: error applying two-way patch: %v", getSchemaType(schema), k, err) + return } - return - } + if string(twoWayResult) != tc.ExpectedTwoWayResult { + t.Errorf("using %s in testcase %s: expected two-way result:\n\t%s\ngot\n\t%s", getSchemaType(schema), k, string(tc.ExpectedTwoWayResult), string(twoWayResult)) + return + } + }() - if string(threeWay) != tc.ExpectedThreeWay { - t.Errorf("%s: expected three-way patch:\n\t%s\ngot\n\t%s", k, string(tc.ExpectedThreeWay), string(threeWay)) - return - } + func() { + threeWay, err := CreateThreeWayMergePatch([]byte(tc.Original), []byte(tc.Modified), []byte(tc.Current), schema, false) + if err != nil { + if len(tc.ExpectedThreeWayErr) == 0 { + t.Errorf("using %s in testcase %s: error making three-way patch: %v", getSchemaType(schema), k, err) + } else if !strings.Contains(err.Error(), tc.ExpectedThreeWayErr) { + t.Errorf("using %s in testcase %s: expected error making three-way patch to contain '%s', got %s", getSchemaType(schema), k, tc.ExpectedThreeWayErr, err) + } + return + } - threeWayResult, err := StrategicMergePatch([]byte(tc.Current), threeWay, MergeItem{}) - if err != nil { - t.Errorf("%s: error applying three-way patch: %v", k, err) - return - } else if string(threeWayResult) != tc.ExpectedThreeWayResult { - t.Errorf("%s: expected three-way result:\n\t%s\ngot\n\t%s", k, string(tc.ExpectedThreeWayResult), string(threeWayResult)) - return - } - }() + if string(threeWay) != tc.ExpectedThreeWay { + t.Errorf("using %s in testcase %s: expected three-way patch:\n\t%s\ngot\n\t%s", getSchemaType(schema), k, string(tc.ExpectedThreeWay), string(threeWay)) + return + } + + threeWayResult, err := StrategicMergePatch([]byte(tc.Current), threeWay, schema) + if err != nil { + t.Errorf("using %s in testcase %s: error applying three-way patch: %v", getSchemaType(schema), k, err) + return + } else if string(threeWayResult) != tc.ExpectedThreeWayResult { + t.Errorf("using %s in testcase %s: expected three-way result:\n\t%s\ngot\n\t%s", getSchemaType(schema), k, string(tc.ExpectedThreeWayResult), string(threeWayResult)) + return + } + }() + } } } diff --git a/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-merge-item.json b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-merge-item.json new file mode 100644 index 00000000000..1d06b699c43 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-merge-item.json @@ -0,0 +1,170 @@ +{ + "swagger": "2.0", + "info": { + "title": "StrategicMergePatchTestingMergeItem", + "version": "v1.9.0" + }, + "paths": {}, + "definitions": { + "mergeItem": { + "description": "MergeItem is type definition for testing strategic merge.", + "required": [], + "properties": { + "name": { + "description": "Name field.", + "type": "string" + }, + "value": { + "description": "Value field.", + "type": "string" + }, + "other": { + "description": "Other field.", + "type": "string" + }, + "mergingList": { + "description": "MergingList field.", + "type": "array", + "items": { + "$ref": "#/definitions/mergeItem" + }, + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + }, + "nonMergingList": { + "description": "NonMergingList field.", + "type": "array", + "items": { + "$ref": "#/definitions/mergeItem" + } + }, + "mergingIntList": { + "description": "MergingIntList field.", + "type": "array", + "items": { + "type": "integer", + "format": "int32" + }, + "x-kubernetes-patch-strategy": "merge" + }, + "nonMergingIntList": { + "description": "NonMergingIntList field.", + "type": "array", + "items": { + "type": "integer", + "format": "int32" + } + }, + "mergeItemPtr": { + "description": "MergeItemPtr field.", + "$ref": "#/definitions/mergeItem", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + }, + "simpleMap": { + "description": "SimpleMap field.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "replacingItem": { + "description": "ReplacingItem field.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.runtime.RawExtension", + "x-kubernetes-patch-strategy": "replace" + }, + "retainKeysMap": { + "description": "RetainKeysMap field.", + "$ref": "#/definitions/retainKeysMergeItem", + "x-kubernetes-patch-strategy": "retainKeys" + }, + "retainKeysMergingList": { + "description": "RetainKeysMergingList field.", + "type": "array", + "items": { + "$ref": "#/definitions/mergeItem" + }, + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge,retainKeys" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "fake-group", + "kind": "mergeItem", + "version": "some-version" + } + ] + }, + "retainKeysMergeItem": { + "description": "RetainKeysMergeItem is type definition for testing strategic merge.", + "required": [], + "properties": { + "name": { + "description": "Name field.", + "type": "string" + }, + "value": { + "description": "Value field.", + "type": "string" + }, + "other": { + "description": "Other field.", + "type": "string" + }, + "simpleMap": { + "description": "SimpleMap field.", + "additionalProperties": "object", + "items": { + "type": "string" + } + }, + "mergingList": { + "description": "MergingList field.", + "type": "array", + "items": { + "$ref": "#/definitions/mergeItem" + }, + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + }, + "nonMergingList": { + "description": "NonMergingList field.", + "type": "array", + "items": { + "$ref": "#/definitions/mergeItem" + } + }, + "mergingIntList": { + "description": "MergingIntList field.", + "type": "array", + "items": { + "type": "integer", + "format": "int32" + }, + "x-kubernetes-patch-strategy": "merge" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "fake-group", + "kind": "retainKeysMergeItem", + "version": "some-version" + } + ] + }, + "io.k8s.apimachinery.pkg.runtime.RawExtension": { + "description": "RawExtension is used to hold extensions in external versions.", + "required": [ + "Raw" + ], + "properties": { + "Raw": { + "description": "Raw is the underlying serialization of this object.", + "type": "string", + "format": "byte" + } + } + } + } +} diff --git a/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-precision-item.json b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-precision-item.json new file mode 100644 index 00000000000..a35ae31f65e --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-precision-item.json @@ -0,0 +1,47 @@ +{ + "swagger": "2.0", + "info": { + "title": "StrategicMergePatchTestingPrecisionItem", + "version": "v1.9.0" + }, + "paths": {}, + "definitions": { + "precisionItem": { + "description": "PrecisionItem is type definition for testing strategic merge.", + "required": [], + "properties": { + "name": { + "description": "Name field.", + "type": "string" + }, + "int32": { + "description": "Int32 field.", + "type": "integer", + "format": "int32" + }, + "int64": { + "description": "Int64 field.", + "type": "integer", + "format": "int64" + }, + "float32": { + "description": "Float32 field.", + "type": "number", + "format": "float32" + }, + "float64": { + "description": "Float64 field.", + "type": "number", + "format": "float64" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "fake-group", + "kind": "precisionItem", + "version": "some-version" + } + ] + } + } +} diff --git a/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/testing/BUILD b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/testing/BUILD new file mode 100644 index 00000000000..8b4268ffef8 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/testing/BUILD @@ -0,0 +1,30 @@ +package(default_visibility = ["//visibility:public"]) + +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["openapi.go"], + importpath = "k8s.io/apimachinery/pkg/util/strategicpatch/testing", + visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library", + "//vendor/github.com/googleapis/gnostic/compiler:go_default_library", + "//vendor/gopkg.in/yaml.v2:go_default_library", + "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/testing/openapi.go b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/testing/openapi.go new file mode 100644 index 00000000000..c101f301d33 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/testing/openapi.go @@ -0,0 +1,84 @@ +/* +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 testing + +import ( + "io/ioutil" + "os" + "sync" + + "gopkg.in/yaml.v2" + + "github.com/googleapis/gnostic/OpenAPIv2" + "github.com/googleapis/gnostic/compiler" + openapi "k8s.io/kube-openapi/pkg/util/proto" +) + +// Fake opens and returns a openapi swagger from a file Path. It will +// parse only once and then return the same copy everytime. +type Fake struct { + Path string + + once sync.Once + document *openapi_v2.Document + err error +} + +// OpenAPISchema returns the openapi document and a potential error. +func (f *Fake) OpenAPISchema() (*openapi_v2.Document, error) { + f.once.Do(func() { + _, err := os.Stat(f.Path) + if err != nil { + f.err = err + return + } + spec, err := ioutil.ReadFile(f.Path) + if err != nil { + f.err = err + return + } + var info yaml.MapSlice + err = yaml.Unmarshal(spec, &info) + if err != nil { + f.err = err + return + } + f.document, f.err = openapi_v2.NewDocument(info, compiler.NewContext("$root", nil)) + }) + return f.document, f.err +} + +func getSchema(f Fake, model string) (openapi.Schema, error) { + s, err := f.OpenAPISchema() + if err != nil { + return nil, err + } + m, err := openapi.NewOpenAPIData(s) + if err != nil { + return nil, err + } + return m.LookupModel(model), nil +} + +// GetSchemaOrDie returns returns the openapi schema. +func GetSchemaOrDie(f Fake, model string) openapi.Schema { + s, err := getSchema(f, model) + if err != nil { + panic(err) + } + return s +} diff --git a/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/types.go b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/types.go new file mode 100644 index 00000000000..f84d65aacb3 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/types.go @@ -0,0 +1,193 @@ +/* +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 strategicpatch + +import ( + "errors" + "strings" + + "k8s.io/apimachinery/pkg/util/mergepatch" + openapi "k8s.io/kube-openapi/pkg/util/proto" +) + +const ( + patchStrategyOpenapiextensionKey = "x-kubernetes-patch-strategy" + patchMergeKeyOpenapiextensionKey = "x-kubernetes-patch-merge-key" +) + +type LookupPatchItem interface { + openapi.SchemaVisitor + + Error() error + Path() *openapi.Path +} + +type kindItem struct { + key string + path *openapi.Path + err error + patchmeta PatchMeta + subschema openapi.Schema + hasVisitKind bool +} + +func NewKindItem(key string, path *openapi.Path) *kindItem { + return &kindItem{ + key: key, + path: path, + } +} + +var _ LookupPatchItem = &kindItem{} + +func (item *kindItem) Error() error { + return item.err +} + +func (item *kindItem) Path() *openapi.Path { + return item.path +} + +func (item *kindItem) VisitPrimitive(schema *openapi.Primitive) { + item.err = errors.New("expected kind, but got primitive") +} + +func (item *kindItem) VisitArray(schema *openapi.Array) { + item.err = errors.New("expected kind, but got slice") +} + +func (item *kindItem) VisitMap(schema *openapi.Map) { + item.err = errors.New("expected kind, but got map") +} + +func (item *kindItem) VisitReference(schema openapi.Reference) { + if !item.hasVisitKind { + schema.SubSchema().Accept(item) + } +} + +func (item *kindItem) VisitKind(schema *openapi.Kind) { + subschema, ok := schema.Fields[item.key] + if !ok { + item.err = FieldNotFoundError{Path: schema.GetPath().String(), Field: item.key} + return + } + + mergeKey, patchStrategies, err := parsePatchMetadata(subschema.GetExtensions()) + if err != nil { + item.err = err + return + } + item.patchmeta = PatchMeta{ + patchStrategies: patchStrategies, + patchMergeKey: mergeKey, + } + item.subschema = subschema +} + +type sliceItem struct { + key string + path *openapi.Path + err error + patchmeta PatchMeta + subschema openapi.Schema + hasVisitKind bool +} + +func NewSliceItem(key string, path *openapi.Path) *sliceItem { + return &sliceItem{ + key: key, + path: path, + } +} + +var _ LookupPatchItem = &sliceItem{} + +func (item *sliceItem) Error() error { + return item.err +} + +func (item *sliceItem) Path() *openapi.Path { + return item.path +} + +func (item *sliceItem) VisitPrimitive(schema *openapi.Primitive) { + item.err = errors.New("expected slice, but got primitive") +} + +func (item *sliceItem) VisitArray(schema *openapi.Array) { + if !item.hasVisitKind { + item.err = errors.New("expected visit kind first, then visit array") + } + subschema := schema.SubType + item.subschema = subschema +} + +func (item *sliceItem) VisitMap(schema *openapi.Map) { + item.err = errors.New("expected slice, but got map") +} + +func (item *sliceItem) VisitReference(schema openapi.Reference) { + if !item.hasVisitKind { + schema.SubSchema().Accept(item) + } else { + item.subschema = schema.SubSchema() + } +} + +func (item *sliceItem) VisitKind(schema *openapi.Kind) { + subschema, ok := schema.Fields[item.key] + if !ok { + item.err = FieldNotFoundError{Path: schema.GetPath().String(), Field: item.key} + return + } + + mergeKey, patchStrategies, err := parsePatchMetadata(subschema.GetExtensions()) + if err != nil { + item.err = err + return + } + item.patchmeta = PatchMeta{ + patchStrategies: patchStrategies, + patchMergeKey: mergeKey, + } + item.hasVisitKind = true + subschema.Accept(item) +} + +func parsePatchMetadata(extensions map[string]interface{}) (string, []string, error) { + ps, foundPS := extensions[patchStrategyOpenapiextensionKey] + var patchStrategies []string + var mergeKey, patchStrategy string + var ok bool + if foundPS { + patchStrategy, ok = ps.(string) + if ok { + patchStrategies = strings.Split(patchStrategy, ",") + } else { + return "", nil, mergepatch.ErrBadArgType(patchStrategy, ps) + } + } + mk, foundMK := extensions[patchMergeKeyOpenapiextensionKey] + if foundMK { + mergeKey, ok = mk.(string) + if !ok { + return "", nil, mergepatch.ErrBadArgType(mergeKey, mk) + } + } + return mergeKey, patchStrategies, nil +} diff --git a/staging/src/k8s.io/apimachinery/pkg/util/validation/BUILD b/staging/src/k8s.io/apimachinery/pkg/util/validation/BUILD index 9680c1fa7b7..40ee235010c 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/validation/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/util/validation/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["validation_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/util/validation", - library = ":go_default_library", deps = ["//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library"], ) diff --git a/staging/src/k8s.io/apimachinery/pkg/util/validation/field/BUILD b/staging/src/k8s.io/apimachinery/pkg/util/validation/field/BUILD index 5508ab94c8e..6a2f815ed88 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/validation/field/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/util/validation/field/BUILD @@ -12,8 +12,8 @@ go_test( "errors_test.go", "path_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/util/validation/field", - library = ":go_default_library", ) go_library( diff --git a/staging/src/k8s.io/apimachinery/pkg/util/wait/BUILD b/staging/src/k8s.io/apimachinery/pkg/util/wait/BUILD index 6eca13c02b5..20046645a33 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/wait/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/util/wait/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["wait_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/util/wait", - library = ":go_default_library", deps = ["//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library"], ) diff --git a/staging/src/k8s.io/apimachinery/pkg/util/waitgroup/BUILD b/staging/src/k8s.io/apimachinery/pkg/util/waitgroup/BUILD new file mode 100644 index 00000000000..a7ecb9d68e4 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/util/waitgroup/BUILD @@ -0,0 +1,32 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "waitgroup.go", + ], + importpath = "k8s.io/apimachinery/pkg/util/waitgroup", + visibility = ["//visibility:public"], +) + +go_test( + name = "go_default_test", + srcs = ["waitgroup_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/apimachinery/pkg/util/waitgroup", +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apimachinery/pkg/util/waitgroup/doc.go b/staging/src/k8s.io/apimachinery/pkg/util/waitgroup/doc.go new file mode 100644 index 00000000000..a6f29cd7c4d --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/util/waitgroup/doc.go @@ -0,0 +1,19 @@ +/* +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 waitgroup implements SafeWaitGroup wrap of sync.WaitGroup. +// Add with positive delta when waiting will fail, to prevent sync.WaitGroup race issue. +package waitgroup // import "k8s.io/apimachinery/pkg/util/waitgroup" diff --git a/staging/src/k8s.io/apimachinery/pkg/util/waitgroup/waitgroup.go b/staging/src/k8s.io/apimachinery/pkg/util/waitgroup/waitgroup.go new file mode 100644 index 00000000000..488f563407d --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/util/waitgroup/waitgroup.go @@ -0,0 +1,57 @@ +/* +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 waitgroup + +import ( + "fmt" + "sync" +) + +// SafeWaitGroup must not be copied after first use. +type SafeWaitGroup struct { + wg sync.WaitGroup + mu sync.RWMutex + // wait indicate whether Wait is called, if true, + // then any Add with positive delta will return error. + wait bool +} + +// Add adds delta, which may be negative, similar to sync.WaitGroup. +// If Add with a positive delta happens after Wait, it will return error, +// which prevent unsafe Add. +func (wg *SafeWaitGroup) Add(delta int) error { + wg.mu.RLock() + defer wg.mu.RUnlock() + if wg.wait && delta > 0 { + return fmt.Errorf("add with postive delta after Wait is forbidden") + } + wg.wg.Add(delta) + return nil +} + +// Done decrements the WaitGroup counter. +func (wg *SafeWaitGroup) Done() { + wg.wg.Done() +} + +// Wait blocks until the WaitGroup counter is zero. +func (wg *SafeWaitGroup) Wait() { + wg.mu.Lock() + wg.wait = true + wg.mu.Unlock() + wg.wg.Wait() +} diff --git a/staging/src/k8s.io/apimachinery/pkg/util/waitgroup/waitgroup_test.go b/staging/src/k8s.io/apimachinery/pkg/util/waitgroup/waitgroup_test.go new file mode 100644 index 00000000000..b5b7557b856 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/util/waitgroup/waitgroup_test.go @@ -0,0 +1,60 @@ +/* +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 waitgroup test cases reference golang sync.WaitGroup https://golang.org/src/sync/waitgroup_test.go. +package waitgroup + +import ( + "testing" +) + +func TestWaitGroup(t *testing.T) { + wg1 := &SafeWaitGroup{} + wg2 := &SafeWaitGroup{} + n := 16 + wg1.Add(n) + wg2.Add(n) + exited := make(chan bool, n) + for i := 0; i != n; i++ { + go func(i int) { + wg1.Done() + wg2.Wait() + exited <- true + }(i) + } + wg1.Wait() + for i := 0; i != n; i++ { + select { + case <-exited: + t.Fatal("SafeWaitGroup released group too soon") + default: + } + wg2.Done() + } + for i := 0; i != n; i++ { + <-exited // Will block if barrier fails to unlock someone. + } +} + +func TestWaitGroupAddFail(t *testing.T) { + wg := &SafeWaitGroup{} + wg.Add(1) + wg.Done() + wg.Wait() + if err := wg.Add(1); err == nil { + t.Errorf("Should return error when add positive after Wait") + } +} diff --git a/staging/src/k8s.io/apimachinery/pkg/util/yaml/BUILD b/staging/src/k8s.io/apimachinery/pkg/util/yaml/BUILD index e660edfe7cb..0208039a88c 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/yaml/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/util/yaml/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["decoder_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/util/yaml", - library = ":go_default_library", ) go_library( diff --git a/staging/src/k8s.io/apimachinery/pkg/util/yaml/decoder.go b/staging/src/k8s.io/apimachinery/pkg/util/yaml/decoder.go index 6ebfaea707d..56de33a7fdf 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/yaml/decoder.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/yaml/decoder.go @@ -122,7 +122,7 @@ func (d *YAMLDecoder) Read(data []byte) (n int, err error) { if left <= len(data) { copy(data, d.remaining) d.remaining = nil - return len(d.remaining), nil + return left, nil } // caller will need to reread diff --git a/staging/src/k8s.io/apimachinery/pkg/util/yaml/decoder_test.go b/staging/src/k8s.io/apimachinery/pkg/util/yaml/decoder_test.go index bd4403648f4..3c1ad7b2219 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/yaml/decoder_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/yaml/decoder_test.go @@ -22,12 +22,38 @@ import ( "encoding/json" "fmt" "io" + "io/ioutil" "math/rand" "reflect" "strings" "testing" ) +func TestYAMLDecoderReadBytesLength(t *testing.T) { + d := `--- +stuff: 1 + test-foo: 1 +` + testCases := []struct { + bufLen int + expectLen int + expectErr error + }{ + {len(d), len(d), nil}, + {len(d) + 10, len(d), nil}, + {len(d) - 10, len(d) - 10, io.ErrShortBuffer}, + } + + for i, testCase := range testCases { + r := NewDocumentDecoder(ioutil.NopCloser(bytes.NewReader([]byte(d)))) + b := make([]byte, testCase.bufLen) + n, err := r.Read(b) + if err != testCase.expectErr || n != testCase.expectLen { + t.Fatalf("%d: unexpected body: %d / %v", i, n, err) + } + } +} + func TestSplitYAMLDocument(t *testing.T) { testCases := []struct { input string diff --git a/staging/src/k8s.io/apimachinery/pkg/watch/BUILD b/staging/src/k8s.io/apimachinery/pkg/watch/BUILD index da068f70d45..36c4ad64e3f 100644 --- a/staging/src/k8s.io/apimachinery/pkg/watch/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/watch/BUILD @@ -20,7 +20,6 @@ go_library( importpath = "k8s.io/apimachinery/pkg/watch", deps = [ "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", @@ -49,8 +48,8 @@ go_test( go_test( name = "go_default_test", srcs = ["until_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/pkg/watch", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", diff --git a/staging/src/k8s.io/apimachinery/pkg/watch/zz_generated.deepcopy.go b/staging/src/k8s.io/apimachinery/pkg/watch/zz_generated.deepcopy.go index 322923d4a03..738d0a29cbb 100644 --- a/staging/src/k8s.io/apimachinery/pkg/watch/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apimachinery/pkg/watch/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -20,23 +20,6 @@ limitations under the License. package watch -import ( - conversion "k8s.io/apimachinery/pkg/conversion" - reflect "reflect" -) - -// GetGeneratedDeepCopyFuncs returns the generated funcs, since we aren't registering them. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func GetGeneratedDeepCopyFuncs() []conversion.GeneratedDeepCopyFunc { - return []conversion.GeneratedDeepCopyFunc{ - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Event).DeepCopyInto(out.(*Event)) - return nil - }, InType: reflect.TypeOf(&Event{})}, - } -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Event) DeepCopyInto(out *Event) { *out = *in diff --git a/staging/src/k8s.io/apimachinery/third_party/forked/golang/json/BUILD b/staging/src/k8s.io/apimachinery/third_party/forked/golang/json/BUILD index 4c20d9771d1..d4b5f696ffa 100644 --- a/staging/src/k8s.io/apimachinery/third_party/forked/golang/json/BUILD +++ b/staging/src/k8s.io/apimachinery/third_party/forked/golang/json/BUILD @@ -15,8 +15,8 @@ go_library( go_test( name = "go_default_test", srcs = ["fields_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/third_party/forked/golang/json", - library = ":go_default_library", ) filegroup( diff --git a/staging/src/k8s.io/apimachinery/third_party/forked/golang/json/fields.go b/staging/src/k8s.io/apimachinery/third_party/forked/golang/json/fields.go index 006972ecafd..8205a4dd138 100644 --- a/staging/src/k8s.io/apimachinery/third_party/forked/golang/json/fields.go +++ b/staging/src/k8s.io/apimachinery/third_party/forked/golang/json/fields.go @@ -26,17 +26,14 @@ const ( // struct field given the struct type and the JSON name of the field. // It returns field type, a slice of patch strategies, merge key and error. // TODO: fix the returned errors to be introspectable. -func LookupPatchMetadata(t reflect.Type, jsonField string) ( +func LookupPatchMetadataForStruct(t reflect.Type, jsonField string) ( elemType reflect.Type, patchStrategies []string, patchMergeKey string, e error) { if t.Kind() == reflect.Ptr { t = t.Elem() } - if t.Kind() == reflect.Map { - elemType = t.Elem() - return - } + if t.Kind() != reflect.Struct { - e = fmt.Errorf("merging an object in json but data type is not map or struct, instead is: %s", + e = fmt.Errorf("merging an object in json but data type is not struct, instead is: %s", t.Kind().String()) return } diff --git a/staging/src/k8s.io/apimachinery/third_party/forked/golang/json/fields_test.go b/staging/src/k8s.io/apimachinery/third_party/forked/golang/json/fields_test.go index 04d8cebd9b2..33b78bc43c3 100644 --- a/staging/src/k8s.io/apimachinery/third_party/forked/golang/json/fields_test.go +++ b/staging/src/k8s.io/apimachinery/third_party/forked/golang/json/fields_test.go @@ -14,7 +14,7 @@ func TestLookupPtrToStruct(t *testing.T) { Inner []Elem `json:"inner" patchStrategy:"merge" patchMergeKey:"key"` } outer := &Outer{} - elemType, patchStrategies, patchMergeKey, err := LookupPatchMetadata(reflect.TypeOf(outer), "inner") + elemType, patchStrategies, patchMergeKey, err := LookupPatchMetadataForStruct(reflect.TypeOf(outer), "inner") if err != nil { t.Fatal(err) } diff --git a/staging/src/k8s.io/apimachinery/third_party/forked/golang/reflect/BUILD b/staging/src/k8s.io/apimachinery/third_party/forked/golang/reflect/BUILD index 9f09628b627..1069d9b93d2 100644 --- a/staging/src/k8s.io/apimachinery/third_party/forked/golang/reflect/BUILD +++ b/staging/src/k8s.io/apimachinery/third_party/forked/golang/reflect/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["deep_equal_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apimachinery/third_party/forked/golang/reflect", - library = ":go_default_library", ) go_library( diff --git a/staging/src/k8s.io/apiserver/Godeps/Godeps.json b/staging/src/k8s.io/apiserver/Godeps/Godeps.json index 77de2ce1ae3..49ef5aaa071 100644 --- a/staging/src/k8s.io/apiserver/Godeps/Godeps.json +++ b/staging/src/k8s.io/apiserver/Godeps/Godeps.json @@ -1,6 +1,6 @@ { "ImportPath": "k8s.io/apiserver", - "GoVersion": "go1.8", + "GoVersion": "go1.9", "GodepVersion": "v79", "Packages": [ "./..." @@ -360,27 +360,31 @@ }, { "ImportPath": "github.com/golang/protobuf/jsonpb", - "Rev": "4bd1920723d7b7c925de087aa32e2187708897f7" + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" }, { "ImportPath": "github.com/golang/protobuf/proto", - "Rev": "4bd1920723d7b7c925de087aa32e2187708897f7" + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" }, { "ImportPath": "github.com/golang/protobuf/ptypes", - "Rev": "4bd1920723d7b7c925de087aa32e2187708897f7" + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" }, { "ImportPath": "github.com/golang/protobuf/ptypes/any", - "Rev": "4bd1920723d7b7c925de087aa32e2187708897f7" + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" }, { "ImportPath": "github.com/golang/protobuf/ptypes/duration", - "Rev": "4bd1920723d7b7c925de087aa32e2187708897f7" + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" + }, + { + "ImportPath": "github.com/golang/protobuf/ptypes/struct", + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" }, { "ImportPath": "github.com/golang/protobuf/ptypes/timestamp", - "Rev": "4bd1920723d7b7c925de087aa32e2187708897f7" + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" }, { "ImportPath": "github.com/google/btree", @@ -404,31 +408,31 @@ }, { "ImportPath": "github.com/gophercloud/gophercloud", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/utils", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gophercloud/gophercloud/pagination", - "Rev": "b4c2377fa77951a0e08163f52dc9b3e206355194" + "Rev": "8e59687aa4b27ab22a0bf3295f1e165ff7bd5f97" }, { "ImportPath": "github.com/gregjones/httpcache", @@ -476,7 +480,7 @@ }, { "ImportPath": "github.com/json-iterator/go", - "Rev": "36b14963da70d11297d313183d7e6388c8510e1e" + "Rev": "13f86432b882000a51c6e610c620974462691a97" }, { "ImportPath": "github.com/juju/ratelimit", @@ -514,10 +518,6 @@ "ImportPath": "github.com/peterbourgon/diskv", "Rev": "5f041e8faa004a95c88a202771f4cc3e991971e6" }, - { - "ImportPath": "github.com/pkg/errors", - "Rev": "a22138067af1c4942683050411a841ade67fe1eb" - }, { "ImportPath": "github.com/pmezard/go-difflib/difflib", "Rev": "d8ed2627bdf02c080bf22230dbb337003b7aba2d" @@ -636,11 +636,11 @@ }, { "ImportPath": "golang.org/x/sys/unix", - "Rev": "7ddbeae9ae08c6a06a59597f0c9edbc5ff2444ce" + "Rev": "95c6576299259db960f6c5b9b69ea52422860fce" }, { "ImportPath": "golang.org/x/sys/windows", - "Rev": "7ddbeae9ae08c6a06a59597f0c9edbc5ff2444ce" + "Rev": "95c6576299259db960f6c5b9b69ea52422860fce" }, { "ImportPath": "golang.org/x/text/cases", @@ -762,10 +762,18 @@ "ImportPath": "gopkg.in/yaml.v2", "Rev": "53feefa2559fb8dfa8d81baad31be332c97d6c77" }, + { + "ImportPath": "k8s.io/api/admission/v1beta1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/api/admissionregistration/v1alpha1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/api/admissionregistration/v1beta1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/api/apps/v1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -822,6 +830,10 @@ "ImportPath": "k8s.io/api/core/v1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/api/events/v1beta1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/api/extensions/v1beta1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -858,6 +870,10 @@ "ImportPath": "k8s.io/api/storage/v1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/api/storage/v1alpha1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/api/storage/v1beta1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -942,10 +958,6 @@ "ImportPath": "k8s.io/apimachinery/pkg/conversion/queryparams", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, - { - "ImportPath": "k8s.io/apimachinery/pkg/conversion/unstructured", - "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - }, { "ImportPath": "k8s.io/apimachinery/pkg/fields", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1070,6 +1082,10 @@ "ImportPath": "k8s.io/apimachinery/pkg/util/wait", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apimachinery/pkg/util/waitgroup", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apimachinery/pkg/util/yaml", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1110,6 +1126,10 @@ "ImportPath": "k8s.io/client-go/informers/admissionregistration/v1alpha1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/client-go/informers/admissionregistration/v1beta1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/client-go/informers/apps", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1170,6 +1190,14 @@ "ImportPath": "k8s.io/client-go/informers/core/v1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/client-go/informers/events", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/client-go/informers/events/v1beta1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/client-go/informers/extensions", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1238,6 +1266,10 @@ "ImportPath": "k8s.io/client-go/informers/storage/v1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/client-go/informers/storage/v1alpha1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/client-go/informers/storage/v1beta1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1258,6 +1290,14 @@ "ImportPath": "k8s.io/client-go/kubernetes/typed/admissionregistration/v1alpha1/fake", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1/fake", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/client-go/kubernetes/typed/apps/v1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1370,6 +1410,14 @@ "ImportPath": "k8s.io/client-go/kubernetes/typed/core/v1/fake", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/client-go/kubernetes/typed/events/v1beta1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/client-go/kubernetes/typed/events/v1beta1/fake", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/client-go/kubernetes/typed/extensions/v1beta1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1442,6 +1490,14 @@ "ImportPath": "k8s.io/client-go/kubernetes/typed/storage/v1/fake", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/client-go/kubernetes/typed/storage/v1alpha1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/client-go/kubernetes/typed/storage/v1alpha1/fake", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/client-go/kubernetes/typed/storage/v1beta1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1454,6 +1510,10 @@ "ImportPath": "k8s.io/client-go/listers/admissionregistration/v1alpha1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/client-go/listers/admissionregistration/v1beta1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/client-go/listers/apps/v1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1494,6 +1554,10 @@ "ImportPath": "k8s.io/client-go/listers/core/v1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/client-go/listers/events/v1beta1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/client-go/listers/extensions/v1beta1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1530,6 +1594,10 @@ "ImportPath": "k8s.io/client-go/listers/storage/v1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/client-go/listers/storage/v1alpha1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/client-go/listers/storage/v1beta1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1608,19 +1676,23 @@ }, { "ImportPath": "k8s.io/kube-openapi/pkg/builder", - "Rev": "868f2f29720b192240e18284659231b440f9cda5" + "Rev": "39a7bf85c140f972372c2a0d1ee40adbf0c8bfe1" }, { "ImportPath": "k8s.io/kube-openapi/pkg/common", - "Rev": "868f2f29720b192240e18284659231b440f9cda5" + "Rev": "39a7bf85c140f972372c2a0d1ee40adbf0c8bfe1" }, { "ImportPath": "k8s.io/kube-openapi/pkg/handler", - "Rev": "868f2f29720b192240e18284659231b440f9cda5" + "Rev": "39a7bf85c140f972372c2a0d1ee40adbf0c8bfe1" }, { "ImportPath": "k8s.io/kube-openapi/pkg/util", - "Rev": "868f2f29720b192240e18284659231b440f9cda5" + "Rev": "39a7bf85c140f972372c2a0d1ee40adbf0c8bfe1" + }, + { + "ImportPath": "k8s.io/kube-openapi/pkg/util/proto", + "Rev": "39a7bf85c140f972372c2a0d1ee40adbf0c8bfe1" }, { "ImportPath": "k8s.io/client-go/discovery", @@ -1674,6 +1746,10 @@ "ImportPath": "k8s.io/client-go/tools/clientcmd", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/client-go/tools/clientcmd/api", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/client-go/tools/clientcmd/api/v1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" diff --git a/staging/src/k8s.io/apiserver/LICENSE b/staging/src/k8s.io/apiserver/LICENSE new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/staging/src/k8s.io/apiserver/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. diff --git a/staging/src/k8s.io/apiserver/README.md b/staging/src/k8s.io/apiserver/README.md new file mode 100644 index 00000000000..96927ae703c --- /dev/null +++ b/staging/src/k8s.io/apiserver/README.md @@ -0,0 +1,30 @@ +# apiserver + +Generic library for building a Kubernetes aggregated API server. + + +## Purpose + +This library contains code to create Kubernetes aggregation server complete with delegated authentication and authorization, +`kubectl` compatible discovery information, optional admission chain, and versioned types. It's first consumers are +`k8s.io/kubernetes`, `k8s.io/kube-aggregator`, and `github.com/kubernetes-incubator/service-catalog`. + + +## Compatibility + +There are *NO compatibility guarantees* for this repository, yet. It is in direct support of Kubernetes, so branches +will track Kubernetes and be compatible with that repo. As we more cleanly separate the layers, we will review the +compatibility guarantee. We have a goal to make this easier to use in 2017. + + +## Where does it come from? + +`apiserver` is synced from https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiserver. +Code changes are made in that location, merged into `k8s.io/kubernetes` and later synced here. + + +## Things you should *NOT* do + + 1. Directly modify any files under `pkg` in this repo. Those are driven from `k8s.io/kuberenetes/staging/src/k8s.io/apiserver`. + 2. Expect compatibility. This repo is changing quickly in direct support of + Kubernetes and the API isn't yet stable enough for API guarantees. diff --git a/staging/src/k8s.io/apiserver/code-of-conduct.md b/staging/src/k8s.io/apiserver/code-of-conduct.md new file mode 100644 index 00000000000..0d15c00cf32 --- /dev/null +++ b/staging/src/k8s.io/apiserver/code-of-conduct.md @@ -0,0 +1,3 @@ +# Kubernetes Community Code of Conduct + +Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/BUILD index 2dba798d7dc..aab87e45791 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/admission/BUILD @@ -12,13 +12,16 @@ go_test( "chain_test.go", "config_test.go", "errors_test.go", + "handler_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/admission", - library = ":go_default_library", deps = [ + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apiserver/pkg/apis/apiserver:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1:go_default_library", ], ) @@ -39,15 +42,12 @@ go_library( "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apiserver/pkg/apis/apiserver:go_default_library", - "//vendor/k8s.io/apiserver/pkg/apis/apiserver/install:go_default_library", "//vendor/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1:go_default_library", "//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library", ], @@ -66,8 +66,19 @@ filegroup( ":package-srcs", "//staging/src/k8s.io/apiserver/pkg/admission/configuration:all-srcs", "//staging/src/k8s.io/apiserver/pkg/admission/initializer:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/metrics:all-srcs", "//staging/src/k8s.io/apiserver/pkg/admission/plugin/initialization:all-srcs", "//staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/errors:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/initializer:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/rules:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/versioned:all-srcs", ], tags = ["automanaged"], ) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/chain.go b/staging/src/k8s.io/apiserver/pkg/admission/chain.go index 45c7f72f9cf..011641ff065 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/chain.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/chain.go @@ -16,11 +16,12 @@ limitations under the License. package admission -// chainAdmissionHandler is an instance of admission.Interface that performs admission control using a chain of admission handlers +// chainAdmissionHandler is an instance of admission.NamedHandler that performs admission control using +// a chain of admission handlers type chainAdmissionHandler []Interface // NewChainHandler creates a new chain handler from an array of handlers. Used for testing. -func NewChainHandler(handlers ...Interface) Interface { +func NewChainHandler(handlers ...Interface) chainAdmissionHandler { return chainAdmissionHandler(handlers) } @@ -30,9 +31,27 @@ func (admissionHandler chainAdmissionHandler) Admit(a Attributes) error { if !handler.Handles(a.GetOperation()) { continue } - err := handler.Admit(a) - if err != nil { - return err + if mutator, ok := handler.(MutationInterface); ok { + err := mutator.Admit(a) + if err != nil { + return err + } + } + } + return nil +} + +// Validate performs an admission control check using a chain of handlers, and returns immediately on first error +func (admissionHandler chainAdmissionHandler) Validate(a Attributes) error { + for _, handler := range admissionHandler { + if !handler.Handles(a.GetOperation()) { + continue + } + if validator, ok := handler.(ValidationInterface); ok { + err := validator.Validate(a) + if err != nil { + return err + } } } return nil diff --git a/staging/src/k8s.io/apiserver/pkg/admission/chain_test.go b/staging/src/k8s.io/apiserver/pkg/admission/chain_test.go index 3a0dc7ce116..e3821e731da 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/chain_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/chain_test.go @@ -20,14 +20,15 @@ import ( "fmt" "testing" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" ) type FakeHandler struct { *Handler - name string - admit bool - admitCalled bool + name string + admit, admitCalled bool + validate, validateCalled bool } func (h *FakeHandler) Admit(a Attributes) (err error) { @@ -38,17 +39,29 @@ func (h *FakeHandler) Admit(a Attributes) (err error) { return fmt.Errorf("Don't admit") } -func makeHandler(name string, admit bool, ops ...Operation) Interface { +func (h *FakeHandler) Validate(a Attributes) (err error) { + h.validateCalled = true + if h.validate { + return nil + } + return fmt.Errorf("Don't validate") +} + +func makeHandler(name string, accept bool, ops ...Operation) *FakeHandler { return &FakeHandler{ - name: name, - admit: admit, - Handler: NewHandler(ops...), + name: name, + admit: accept, + validate: accept, + Handler: NewHandler(ops...), } } -func TestAdmit(t *testing.T) { +func TestAdmitAndValidate(t *testing.T) { + sysns := metav1.NamespaceSystem + otherns := "default" tests := []struct { name string + ns string operation Operation chain chainAdmissionHandler accept bool @@ -56,6 +69,7 @@ func TestAdmit(t *testing.T) { }{ { name: "all accept", + ns: sysns, operation: Create, chain: []Interface{ makeHandler("a", true, Update, Delete, Create), @@ -67,6 +81,7 @@ func TestAdmit(t *testing.T) { }, { name: "ignore handler", + ns: otherns, operation: Create, chain: []Interface{ makeHandler("a", true, Update, Delete, Create), @@ -78,6 +93,7 @@ func TestAdmit(t *testing.T) { }, { name: "ignore all", + ns: sysns, operation: Connect, chain: []Interface{ makeHandler("a", true, Update, Delete, Create), @@ -89,6 +105,7 @@ func TestAdmit(t *testing.T) { }, { name: "reject one", + ns: otherns, operation: Delete, chain: []Interface{ makeHandler("a", true, Update, Delete, Create), @@ -100,18 +117,46 @@ func TestAdmit(t *testing.T) { }, } for _, test := range tests { - err := test.chain.Admit(NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, "", "", schema.GroupVersionResource{}, "", test.operation, nil)) + t.Logf("testcase = %s", test.name) + // call admit and check that validate was not called at all + err := test.chain.Admit(NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, test.ns, "", schema.GroupVersionResource{}, "", test.operation, nil)) accepted := (err == nil) if accepted != test.accept { - t.Errorf("%s: unexpected result of admit call: %v\n", test.name, accepted) + t.Errorf("unexpected result of admit call: %v", accepted) } for _, h := range test.chain { fake := h.(*FakeHandler) _, shouldBeCalled := test.calls[fake.name] if shouldBeCalled != fake.admitCalled { - t.Errorf("%s: handler %s not called as expected: %v", test.name, fake.name, fake.admitCalled) + t.Errorf("admit handler %s not called as expected: %v", fake.name, fake.admitCalled) continue } + if fake.validateCalled { + t.Errorf("validate handler %s called during admit", fake.name) + } + + // reset value for validation test + fake.admitCalled = false + } + + // call validate and check that admit was not called at all + err = test.chain.Validate(NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, test.ns, "", schema.GroupVersionResource{}, "", test.operation, nil)) + accepted = (err == nil) + if accepted != test.accept { + t.Errorf("unexpected result of validate call: %v\n", accepted) + } + for _, h := range test.chain { + fake := h.(*FakeHandler) + + _, shouldBeCalled := test.calls[fake.name] + if shouldBeCalled != fake.validateCalled { + t.Errorf("validate handler %s not called as expected: %v", fake.name, fake.validateCalled) + continue + } + + if fake.admitCalled { + t.Errorf("mutating handler unexpectedly called: %s", fake.name) + } } } } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/config.go b/staging/src/k8s.io/apiserver/pkg/admission/config.go index d233c7cb92b..eb979861207 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/config.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/config.go @@ -17,6 +17,7 @@ limitations under the License. package admission import ( + "bytes" "fmt" "io" "io/ioutil" @@ -27,29 +28,14 @@ import ( "github.com/ghodss/yaml" "github.com/golang/glog" - "bytes" - - "k8s.io/apimachinery/pkg/apimachinery/announced" - "k8s.io/apimachinery/pkg/apimachinery/registered" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apiserver/pkg/apis/apiserver" - "k8s.io/apiserver/pkg/apis/apiserver/install" apiserverv1alpha1 "k8s.io/apiserver/pkg/apis/apiserver/v1alpha1" ) -var ( - groupFactoryRegistry = make(announced.APIGroupFactoryRegistry) - registry = registered.NewOrDie(os.Getenv("KUBE_API_VERSIONS")) - scheme = runtime.NewScheme() - codecs = serializer.NewCodecFactory(scheme) -) - -func init() { - install.Install(groupFactoryRegistry, registry, scheme) -} - func makeAbs(path, base string) (string, error) { if filepath.IsAbs(path) { return path, nil @@ -70,7 +56,7 @@ func makeAbs(path, base string) (string, error) { // set of pluginNames whose config location references the specified configFilePath. // It does this to preserve backward compatibility when admission control files were opaque. // It returns an error if the file did not exist. -func ReadAdmissionConfiguration(pluginNames []string, configFilePath string) (ConfigProvider, error) { +func ReadAdmissionConfiguration(pluginNames []string, configFilePath string, configScheme *runtime.Scheme) (ConfigProvider, error) { if configFilePath == "" { return configProvider{config: &apiserver.AdmissionConfiguration{}}, nil } @@ -79,6 +65,7 @@ func ReadAdmissionConfiguration(pluginNames []string, configFilePath string) (Co if err != nil { return nil, fmt.Errorf("unable to read admission control configuration from %q [%v]", configFilePath, err) } + codecs := serializer.NewCodecFactory(configScheme) decoder := codecs.UniversalDecoder() decodedObj, err := runtime.Decode(decoder, data) // we were able to decode the file successfully @@ -99,7 +86,10 @@ func ReadAdmissionConfiguration(pluginNames []string, configFilePath string) (Co } decodedConfig.Plugins[i].Path = absPath } - return configProvider{config: decodedConfig}, nil + return configProvider{ + config: decodedConfig, + scheme: configScheme, + }, nil } // we got an error where the decode wasn't related to a missing type if !(runtime.IsMissingVersion(err) || runtime.IsMissingKind(err) || runtime.IsNotRegisteredError(err)) { @@ -119,25 +109,29 @@ func ReadAdmissionConfiguration(pluginNames []string, configFilePath string) (Co Path: configFilePath}) } } - scheme.Default(externalConfig) + configScheme.Default(externalConfig) internalConfig := &apiserver.AdmissionConfiguration{} - if err := scheme.Convert(externalConfig, internalConfig, nil); err != nil { + if err := configScheme.Convert(externalConfig, internalConfig, nil); err != nil { return nil, err } - return configProvider{config: internalConfig}, nil + return configProvider{ + config: internalConfig, + scheme: configScheme, + }, nil } type configProvider struct { config *apiserver.AdmissionConfiguration + scheme *runtime.Scheme } // GetAdmissionPluginConfigurationFor returns a reader that holds the admission plugin configuration. -func GetAdmissionPluginConfigurationFor(pluginCfg apiserver.AdmissionPluginConfiguration) (io.Reader, error) { +func GetAdmissionPluginConfigurationFor(pluginCfg apiserver.AdmissionPluginConfiguration, scheme *runtime.Scheme) (io.Reader, error) { // if there is nothing nested in the object, we return the named location obj := pluginCfg.Configuration if obj != nil { // serialize the configuration and build a reader for it - content, err := writeYAML(obj) + content, err := writeYAML(obj, scheme) if err != nil { return nil, err } @@ -156,8 +150,8 @@ func GetAdmissionPluginConfigurationFor(pluginCfg apiserver.AdmissionPluginConfi return nil, nil } -// GetAdmissionPluginConfiguration takes the admission configuration and returns a reader -// for the specified plugin. If no specific configuration is present, we return a nil reader. +// ConfigFor returns a reader for the specified plugin. +// If no specific configuration is present, we return a nil reader. func (p configProvider) ConfigFor(pluginName string) (io.Reader, error) { // there is no config, so there is no potential config if p.config == nil { @@ -168,7 +162,7 @@ func (p configProvider) ConfigFor(pluginName string) (io.Reader, error) { if pluginName != pluginCfg.Name { continue } - pluginConfig, err := GetAdmissionPluginConfigurationFor(pluginCfg) + pluginConfig, err := GetAdmissionPluginConfigurationFor(pluginCfg, p.scheme) if err != nil { return nil, err } @@ -179,8 +173,17 @@ func (p configProvider) ConfigFor(pluginName string) (io.Reader, error) { } // writeYAML writes the specified object to a byte array as yaml. -func writeYAML(obj runtime.Object) ([]byte, error) { - json, err := runtime.Encode(codecs.LegacyCodec(), obj) +func writeYAML(obj runtime.Object, scheme *runtime.Scheme) ([]byte, error) { + gvks, _, err := scheme.ObjectKinds(obj) + if err != nil { + return nil, err + } + gvs := []schema.GroupVersion{} + for _, gvk := range gvks { + gvs = append(gvs, gvk.GroupVersion()) + } + codecs := serializer.NewCodecFactory(scheme) + json, err := runtime.Encode(codecs.LegacyCodec(gvs...), obj) if err != nil { return nil, err } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/config_test.go b/staging/src/k8s.io/apiserver/pkg/admission/config_test.go index 13489bed287..debde2463d2 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/config_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/config_test.go @@ -22,7 +22,10 @@ import ( "reflect" "testing" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/apis/apiserver" + apiserverapi "k8s.io/apiserver/pkg/apis/apiserver" + apiserverapiv1alpha1 "k8s.io/apiserver/pkg/apis/apiserver/v1alpha1" ) func TestReadAdmissionConfiguration(t *testing.T) { @@ -132,11 +135,16 @@ func TestReadAdmissionConfiguration(t *testing.T) { PluginNames: []string{"NamespaceLifecycle", "InitialResources"}, }, } + + scheme := runtime.NewScheme() + apiserverapi.AddToScheme(scheme) + apiserverapiv1alpha1.AddToScheme(scheme) + for testName, testCase := range testCases { if err = ioutil.WriteFile(configFileName, []byte(testCase.ConfigBody), 0644); err != nil { t.Fatalf("unexpected err writing temp file: %v", err) } - config, err := ReadAdmissionConfiguration(testCase.PluginNames, configFileName) + config, err := ReadAdmissionConfiguration(testCase.PluginNames, configFileName, scheme) if err != nil { t.Fatalf("unexpected err: %v", err) } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/configuration/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/configuration/BUILD index d2cb75d9f99..c892344c3e7 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/configuration/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/admission/configuration/BUILD @@ -10,13 +10,15 @@ go_test( name = "go_default_test", srcs = [ "configuration_manager_test.go", - "external_admission_hook_manager_test.go", "initializer_manager_test.go", + "mutating_webhook_manager_test.go", + "validating_webhook_manager_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/admission/configuration", - library = ":go_default_library", deps = [ "//vendor/k8s.io/api/admissionregistration/v1alpha1:go_default_library", + "//vendor/k8s.io/api/admissionregistration/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", @@ -29,13 +31,15 @@ go_library( name = "go_default_library", srcs = [ "configuration_manager.go", - "external_admission_hook_manager.go", "initializer_manager.go", + "mutating_webhook_manager.go", + "validating_webhook_manager.go", ], importpath = "k8s.io/apiserver/pkg/admission/configuration", deps = [ "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/admissionregistration/v1alpha1:go_default_library", + "//vendor/k8s.io/api/admissionregistration/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/admission/configuration/configuration_manager.go b/staging/src/k8s.io/apiserver/pkg/admission/configuration/configuration_manager.go index d31b391c0b7..4c4bf74c92f 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/configuration/configuration_manager.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/configuration/configuration_manager.go @@ -104,6 +104,7 @@ func (a *poller) bootstrapping() { // bootstrapGracePeriod is read-only, so no lock is required timer := time.NewTimer(a.bootstrapGracePeriod) go func() { + defer timer.Stop() <-timer.C a.lock.Lock() defer a.lock.Unlock() diff --git a/staging/src/k8s.io/apiserver/pkg/admission/configuration/configuration_manager_test.go b/staging/src/k8s.io/apiserver/pkg/admission/configuration/configuration_manager_test.go index 26c262e3ef6..7506b7bf076 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/configuration/configuration_manager_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/configuration/configuration_manager_test.go @@ -50,6 +50,7 @@ func TestTolerateBootstrapFailure(t *testing.T) { go func() { // The test might have false negative, but won't be flaky timer := time.NewTimer(2 * time.Second) + defer timer.Stop() <-timer.C fakeGetSucceedLock.Lock() defer fakeGetSucceedLock.Unlock() diff --git a/staging/src/k8s.io/apiserver/pkg/admission/configuration/external_admission_hook_manager.go b/staging/src/k8s.io/apiserver/pkg/admission/configuration/external_admission_hook_manager.go deleted file mode 100644 index 024f5fae0b9..00000000000 --- a/staging/src/k8s.io/apiserver/pkg/admission/configuration/external_admission_hook_manager.go +++ /dev/null @@ -1,83 +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 configuration - -import ( - "fmt" - "reflect" - - "github.com/golang/glog" - - "k8s.io/api/admissionregistration/v1alpha1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" -) - -type ExternalAdmissionHookConfigurationLister interface { - List(opts metav1.ListOptions) (*v1alpha1.ExternalAdmissionHookConfigurationList, error) -} - -type ExternalAdmissionHookConfigurationManager struct { - *poller -} - -func NewExternalAdmissionHookConfigurationManager(c ExternalAdmissionHookConfigurationLister) *ExternalAdmissionHookConfigurationManager { - getFn := func() (runtime.Object, error) { - list, err := c.List(metav1.ListOptions{}) - if err != nil { - if errors.IsNotFound(err) || errors.IsForbidden(err) { - glog.V(5).Infof("ExternalAdmissionHookConfiguration are disabled due to an error: %v", err) - return nil, ErrDisabled - } - return nil, err - } - return mergeExternalAdmissionHookConfigurations(list), nil - } - - return &ExternalAdmissionHookConfigurationManager{ - newPoller(getFn), - } -} - -// ExternalAdmissionHooks returns the merged ExternalAdmissionHookConfiguration. -func (im *ExternalAdmissionHookConfigurationManager) ExternalAdmissionHooks() (*v1alpha1.ExternalAdmissionHookConfiguration, error) { - configuration, err := im.poller.configuration() - if err != nil { - return nil, err - } - externalAdmissionHookConfiguration, ok := configuration.(*v1alpha1.ExternalAdmissionHookConfiguration) - if !ok { - return nil, fmt.Errorf("expected type %v, got type %v", reflect.TypeOf(externalAdmissionHookConfiguration), reflect.TypeOf(configuration)) - } - return externalAdmissionHookConfiguration, nil -} - -func (im *ExternalAdmissionHookConfigurationManager) Run(stopCh <-chan struct{}) { - im.poller.Run(stopCh) -} - -func mergeExternalAdmissionHookConfigurations( - list *v1alpha1.ExternalAdmissionHookConfigurationList, -) *v1alpha1.ExternalAdmissionHookConfiguration { - configurations := list.Items - var ret v1alpha1.ExternalAdmissionHookConfiguration - for _, c := range configurations { - ret.ExternalAdmissionHooks = append(ret.ExternalAdmissionHooks, c.ExternalAdmissionHooks...) - } - return &ret -} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/configuration/external_admission_hook_manager_test.go b/staging/src/k8s.io/apiserver/pkg/admission/configuration/external_admission_hook_manager_test.go deleted file mode 100644 index 1b849b1d26b..00000000000 --- a/staging/src/k8s.io/apiserver/pkg/admission/configuration/external_admission_hook_manager_test.go +++ /dev/null @@ -1,40 +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 configuration - -import ( - "testing" - - "k8s.io/api/admissionregistration/v1alpha1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -type disabledWebhookConfigLister struct{} - -func (l *disabledWebhookConfigLister) List(options metav1.ListOptions) (*v1alpha1.ExternalAdmissionHookConfigurationList, error) { - return nil, errors.NewNotFound(schema.GroupResource{Group: "admissionregistration", Resource: "externalAdmissionHookConfigurations"}, "") -} -func TestWebhookConfigDisabled(t *testing.T) { - manager := NewExternalAdmissionHookConfigurationManager(&disabledWebhookConfigLister{}) - manager.sync() - _, err := manager.ExternalAdmissionHooks() - if err.Error() != ErrDisabled.Error() { - t.Errorf("expected %v, got %v", ErrDisabled, err) - } -} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/configuration/mutating_webhook_manager.go b/staging/src/k8s.io/apiserver/pkg/admission/configuration/mutating_webhook_manager.go new file mode 100644 index 00000000000..bf4d0eabf98 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/configuration/mutating_webhook_manager.go @@ -0,0 +1,101 @@ +/* +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 configuration + +import ( + "fmt" + "reflect" + "sort" + + "github.com/golang/glog" + + "k8s.io/api/admissionregistration/v1beta1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +type MutatingWebhookConfigurationLister interface { + List(opts metav1.ListOptions) (*v1beta1.MutatingWebhookConfigurationList, error) +} + +// MutatingWebhookConfigurationManager collects the mutating webhook objects so that they can be called. +type MutatingWebhookConfigurationManager struct { + *poller +} + +func NewMutatingWebhookConfigurationManager(c MutatingWebhookConfigurationLister) *MutatingWebhookConfigurationManager { + getFn := func() (runtime.Object, error) { + list, err := c.List(metav1.ListOptions{}) + if err != nil { + if errors.IsNotFound(err) || errors.IsForbidden(err) { + glog.V(5).Infof("MutatingWebhookConfiguration are disabled due to an error: %v", err) + return nil, ErrDisabled + } + return nil, err + } + return mergeMutatingWebhookConfigurations(list), nil + } + + return &MutatingWebhookConfigurationManager{ + newPoller(getFn), + } +} + +// Webhooks returns the merged MutatingWebhookConfiguration. +func (im *MutatingWebhookConfigurationManager) Webhooks() (*v1beta1.MutatingWebhookConfiguration, error) { + configuration, err := im.poller.configuration() + if err != nil { + return nil, err + } + mutatingWebhookConfiguration, ok := configuration.(*v1beta1.MutatingWebhookConfiguration) + if !ok { + return nil, fmt.Errorf("expected type %v, got type %v", reflect.TypeOf(mutatingWebhookConfiguration), reflect.TypeOf(configuration)) + } + return mutatingWebhookConfiguration, nil +} + +func (im *MutatingWebhookConfigurationManager) Run(stopCh <-chan struct{}) { + im.poller.Run(stopCh) +} + +func mergeMutatingWebhookConfigurations( + list *v1beta1.MutatingWebhookConfigurationList, +) *v1beta1.MutatingWebhookConfiguration { + configurations := append([]v1beta1.MutatingWebhookConfiguration{}, list.Items...) + var ret v1beta1.MutatingWebhookConfiguration + // The internal order of webhooks for each configuration is provided by the user + // but configurations themselves can be in any order. As we are going to run these + // webhooks in serial, they are sorted here to have a deterministic order. + sort.Sort(byName(configurations)) + for _, c := range configurations { + ret.Webhooks = append(ret.Webhooks, c.Webhooks...) + } + return &ret +} + +// byName sorts MutatingWebhookConfiguration by name. These objects are all in +// cluster namespace (aka no namespace) thus they all have unique names. +type byName []v1beta1.MutatingWebhookConfiguration + +func (x byName) Len() int { return len(x) } + +func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x byName) Less(i, j int) bool { + return x[i].ObjectMeta.Name < x[j].ObjectMeta.Name +} 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 new file mode 100644 index 00000000000..97333880b09 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/configuration/mutating_webhook_manager_test.go @@ -0,0 +1,40 @@ +/* +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 configuration + +import ( + "testing" + + "k8s.io/api/admissionregistration/v1beta1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type disabledMutatingWebhookConfigLister struct{} + +func (l *disabledMutatingWebhookConfigLister) List(options metav1.ListOptions) (*v1beta1.MutatingWebhookConfigurationList, error) { + return nil, errors.NewNotFound(schema.GroupResource{Group: "admissionregistration", Resource: "MutatingWebhookConfigurations"}, "") +} +func TestMutatingWebhookConfigDisabled(t *testing.T) { + manager := NewMutatingWebhookConfigurationManager(&disabledMutatingWebhookConfigLister{}) + manager.sync() + _, err := manager.Webhooks() + if err.Error() != ErrDisabled.Error() { + t.Errorf("expected %v, got %v", ErrDisabled, err) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/configuration/validating_webhook_manager.go b/staging/src/k8s.io/apiserver/pkg/admission/configuration/validating_webhook_manager.go new file mode 100644 index 00000000000..8f9fd34daae --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/configuration/validating_webhook_manager.go @@ -0,0 +1,84 @@ +/* +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 configuration + +import ( + "fmt" + "reflect" + + "github.com/golang/glog" + + "k8s.io/api/admissionregistration/v1beta1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +type ValidatingWebhookConfigurationLister interface { + List(opts metav1.ListOptions) (*v1beta1.ValidatingWebhookConfigurationList, error) +} + +// ValidatingWebhookConfigurationManager collects the validating webhook objects so that they can be called. +type ValidatingWebhookConfigurationManager struct { + *poller +} + +func NewValidatingWebhookConfigurationManager(c ValidatingWebhookConfigurationLister) *ValidatingWebhookConfigurationManager { + getFn := func() (runtime.Object, error) { + list, err := c.List(metav1.ListOptions{}) + if err != nil { + if errors.IsNotFound(err) || errors.IsForbidden(err) { + glog.V(5).Infof("ValidatingWebhookConfiguration are disabled due to an error: %v", err) + return nil, ErrDisabled + } + return nil, err + } + return mergeValidatingWebhookConfigurations(list), nil + } + + return &ValidatingWebhookConfigurationManager{ + newPoller(getFn), + } +} + +// Webhooks returns the merged ValidatingWebhookConfiguration. +func (im *ValidatingWebhookConfigurationManager) Webhooks() (*v1beta1.ValidatingWebhookConfiguration, error) { + configuration, err := im.poller.configuration() + if err != nil { + return nil, err + } + validatingWebhookConfiguration, ok := configuration.(*v1beta1.ValidatingWebhookConfiguration) + if !ok { + return nil, fmt.Errorf("expected type %v, got type %v", reflect.TypeOf(validatingWebhookConfiguration), reflect.TypeOf(configuration)) + } + return validatingWebhookConfiguration, nil +} + +func (im *ValidatingWebhookConfigurationManager) Run(stopCh <-chan struct{}) { + im.poller.Run(stopCh) +} + +func mergeValidatingWebhookConfigurations( + list *v1beta1.ValidatingWebhookConfigurationList, +) *v1beta1.ValidatingWebhookConfiguration { + configurations := list.Items + var ret v1beta1.ValidatingWebhookConfiguration + for _, c := range configurations { + ret.Webhooks = append(ret.Webhooks, c.Webhooks...) + } + return &ret +} 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 new file mode 100644 index 00000000000..60ba5367325 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/configuration/validating_webhook_manager_test.go @@ -0,0 +1,40 @@ +/* +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 configuration + +import ( + "testing" + + "k8s.io/api/admissionregistration/v1beta1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type disabledValidatingWebhookConfigLister struct{} + +func (l *disabledValidatingWebhookConfigLister) List(options metav1.ListOptions) (*v1beta1.ValidatingWebhookConfigurationList, error) { + return nil, errors.NewNotFound(schema.GroupResource{Group: "admissionregistration", Resource: "ValidatingWebhookConfigurations"}, "") +} +func TestWebhookConfigDisabled(t *testing.T) { + manager := NewValidatingWebhookConfigurationManager(&disabledValidatingWebhookConfigLister{}) + manager.sync() + _, err := manager.Webhooks() + if err.Error() != ErrDisabled.Error() { + t.Errorf("expected %v, got %v", ErrDisabled, err) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/handler.go b/staging/src/k8s.io/apiserver/pkg/admission/handler.go index 5de066f0587..d2a9e7d4cf7 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/handler.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/handler.go @@ -66,14 +66,8 @@ func (h *Handler) WaitForReady() bool { if h.readyFunc == nil { return true } - return h.waitForReadyInternal(time.After(timeToWaitForReady)) -} -func (h *Handler) waitForReadyInternal(timeout <-chan time.Time) bool { - // there is no configured ready func, so return immediately - if h.readyFunc == nil { - return true - } + timeout := time.After(timeToWaitForReady) for !h.readyFunc() { select { case <-time.After(100 * time.Millisecond): diff --git a/staging/src/k8s.io/apiserver/pkg/admission/handler_test.go b/staging/src/k8s.io/apiserver/pkg/admission/handler_test.go new file mode 100644 index 00000000000..79741302a91 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/handler_test.go @@ -0,0 +1,57 @@ +/* +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 admission + +import ( + "testing" + "time" +) + +func TestWaitForReady(t *testing.T) { + handler := newFakeHandler() + + // 1. test no readyFunc + if !handler.WaitForReady() { + t.Errorf("Expect ready for no readyFunc provided.") + } + + // 2. readyFunc return ready immediately + readyFunc := func() bool { + return true + } + handler.SetReadyFunc(readyFunc) + if !handler.WaitForReady() { + t.Errorf("Expect ready for readyFunc returns ready immediately.") + } + + // 3. readyFunc always return not ready. WaitForReady timeout + readyFunc = func() bool { + return false + } + startTime := time.Now() + handler.SetReadyFunc(readyFunc) + if handler.WaitForReady() { + t.Errorf("Expect not ready for readyFunc returns not ready immediately.") + } + if time.Since(startTime) < timeToWaitForReady { + t.Errorf("Expect WaitForReady timeout.") + } +} + +func newFakeHandler() *Handler { + return NewHandler(Create, Update) +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/initializer/initializer.go b/staging/src/k8s.io/apiserver/pkg/admission/initializer/initializer.go index bce5433a34b..abe764bb94d 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/initializer/initializer.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/initializer/initializer.go @@ -28,11 +28,7 @@ type pluginInitializer struct { externalClient kubernetes.Interface externalInformers informers.SharedInformerFactory authorizer authorizer.Authorizer - // serverIdentifyingClientCert used to provide identity when calling out to admission plugins - serverIdentifyingClientCert []byte - // serverIdentifyingClientKey private key for the client certificate used when calling out to admission plugins - serverIdentifyingClientKey []byte - scheme *runtime.Scheme + scheme *runtime.Scheme } // New creates an instance of admission plugins initializer. @@ -41,18 +37,14 @@ func New( extClientset kubernetes.Interface, extInformers informers.SharedInformerFactory, authz authorizer.Authorizer, - serverIdentifyingClientCert, - serverIdentifyingClientKey []byte, scheme *runtime.Scheme, -) (pluginInitializer, error) { +) pluginInitializer { return pluginInitializer{ - externalClient: extClientset, - externalInformers: extInformers, - authorizer: authz, - serverIdentifyingClientCert: serverIdentifyingClientCert, - serverIdentifyingClientKey: serverIdentifyingClientKey, - scheme: scheme, - }, nil + externalClient: extClientset, + externalInformers: extInformers, + authorizer: authz, + scheme: scheme, + } } // Initialize checks the initialization interfaces implemented by a plugin @@ -70,10 +62,6 @@ func (i pluginInitializer) Initialize(plugin admission.Interface) { wants.SetAuthorizer(i.authorizer) } - if wants, ok := plugin.(WantsClientCert); ok { - wants.SetClientCert(i.serverIdentifyingClientCert, i.serverIdentifyingClientKey) - } - if wants, ok := plugin.(WantsScheme); ok { wants.SetScheme(i.scheme) } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/initializer/initializer_test.go b/staging/src/k8s.io/apiserver/pkg/admission/initializer/initializer_test.go index 0550bd17cc8..c2d6ea234a0 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/initializer/initializer_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/initializer/initializer_test.go @@ -33,10 +33,7 @@ import ( // the WantsScheme interface is implemented by a plugin. func TestWantsScheme(t *testing.T) { scheme := runtime.NewScheme() - target, err := initializer.New(nil, nil, nil, nil, nil, scheme) - if err != nil { - t.Fatal(err) - } + target := initializer.New(nil, nil, nil, scheme) wantSchemeAdmission := &WantSchemeAdmission{} target.Initialize(wantSchemeAdmission) if wantSchemeAdmission.scheme != scheme { @@ -47,10 +44,7 @@ func TestWantsScheme(t *testing.T) { // TestWantsAuthorizer ensures that the authorizer is injected // when the WantsAuthorizer interface is implemented by a plugin. func TestWantsAuthorizer(t *testing.T) { - target, err := initializer.New(nil, nil, &TestAuthorizer{}, nil, nil, nil) - if err != nil { - t.Fatal(err) - } + target := initializer.New(nil, nil, &TestAuthorizer{}, nil) wantAuthorizerAdmission := &WantAuthorizerAdmission{} target.Initialize(wantAuthorizerAdmission) if wantAuthorizerAdmission.auth == nil { @@ -62,10 +56,7 @@ func TestWantsAuthorizer(t *testing.T) { // when the WantsExternalKubeClientSet interface is implemented by a plugin. func TestWantsExternalKubeClientSet(t *testing.T) { cs := &fake.Clientset{} - target, err := initializer.New(cs, nil, &TestAuthorizer{}, nil, nil, nil) - if err != nil { - t.Fatal(err) - } + target := initializer.New(cs, nil, &TestAuthorizer{}, nil) wantExternalKubeClientSet := &WantExternalKubeClientSet{} target.Initialize(wantExternalKubeClientSet) if wantExternalKubeClientSet.cs != cs { @@ -78,10 +69,7 @@ func TestWantsExternalKubeClientSet(t *testing.T) { func TestWantsExternalKubeInformerFactory(t *testing.T) { cs := &fake.Clientset{} sf := informers.NewSharedInformerFactory(cs, time.Duration(1)*time.Second) - target, err := initializer.New(cs, sf, &TestAuthorizer{}, nil, nil, nil) - if err != nil { - t.Fatal(err) - } + target := initializer.New(cs, sf, &TestAuthorizer{}, nil) wantExternalKubeInformerFactory := &WantExternalKubeInformerFactory{} target.Initialize(wantExternalKubeInformerFactory) if wantExternalKubeInformerFactory.sf != sf { @@ -89,20 +77,6 @@ func TestWantsExternalKubeInformerFactory(t *testing.T) { } } -// TestWantsClientCert ensures that the client certificate and key are injected -// when the WantsClientCert interface is implemented by a plugin. -func TestWantsClientCert(t *testing.T) { - target, err := initializer.New(nil, nil, nil, []byte("cert"), []byte("key"), nil) - if err != nil { - t.Fatal(err) - } - wantClientCert := &clientCertWanter{} - target.Initialize(wantClientCert) - if string(wantClientCert.gotCert) != "cert" || string(wantClientCert.gotKey) != "key" { - t.Errorf("expected client cert to be initialized, clientCert = %v, clientKey = %v", wantClientCert.gotCert, wantClientCert.gotKey) - } -} - // WantExternalKubeInformerFactory is a test stub that fulfills the WantsExternalKubeInformerFactory interface type WantExternalKubeInformerFactory struct { sf informers.SharedInformerFactory @@ -113,7 +87,7 @@ func (self *WantExternalKubeInformerFactory) SetExternalKubeInformerFactory(sf i } func (self *WantExternalKubeInformerFactory) Admit(a admission.Attributes) error { return nil } func (self *WantExternalKubeInformerFactory) Handles(o admission.Operation) bool { return false } -func (self *WantExternalKubeInformerFactory) Validate() error { return nil } +func (self *WantExternalKubeInformerFactory) ValidateInitialization() error { return nil } var _ admission.Interface = &WantExternalKubeInformerFactory{} var _ initializer.WantsExternalKubeInformerFactory = &WantExternalKubeInformerFactory{} @@ -126,7 +100,7 @@ type WantExternalKubeClientSet struct { func (self *WantExternalKubeClientSet) SetExternalKubeClientSet(cs kubernetes.Interface) { self.cs = cs } func (self *WantExternalKubeClientSet) Admit(a admission.Attributes) error { return nil } func (self *WantExternalKubeClientSet) Handles(o admission.Operation) bool { return false } -func (self *WantExternalKubeClientSet) Validate() error { return nil } +func (self *WantExternalKubeClientSet) ValidateInitialization() error { return nil } var _ admission.Interface = &WantExternalKubeClientSet{} var _ initializer.WantsExternalKubeClientSet = &WantExternalKubeClientSet{} @@ -139,7 +113,7 @@ type WantAuthorizerAdmission struct { func (self *WantAuthorizerAdmission) SetAuthorizer(a authorizer.Authorizer) { self.auth = a } func (self *WantAuthorizerAdmission) Admit(a admission.Attributes) error { return nil } func (self *WantAuthorizerAdmission) Handles(o admission.Operation) bool { return false } -func (self *WantAuthorizerAdmission) Validate() error { return nil } +func (self *WantAuthorizerAdmission) ValidateInitialization() error { return nil } var _ admission.Interface = &WantAuthorizerAdmission{} var _ initializer.WantsAuthorizer = &WantAuthorizerAdmission{} @@ -147,8 +121,8 @@ var _ initializer.WantsAuthorizer = &WantAuthorizerAdmission{} // TestAuthorizer is a test stub that fulfills the WantsAuthorizer interface. type TestAuthorizer struct{} -func (t *TestAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) { - return false, "", nil +func (t *TestAuthorizer) Authorize(a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) { + return authorizer.DecisionNoOpinion, "", nil } // wantClientCert is a test stub for testing that fulfulls the WantsClientCert interface. @@ -159,7 +133,7 @@ type clientCertWanter struct { func (s *clientCertWanter) SetClientCert(cert, key []byte) { s.gotCert, s.gotKey = cert, key } func (s *clientCertWanter) Admit(a admission.Attributes) error { return nil } func (s *clientCertWanter) Handles(o admission.Operation) bool { return false } -func (s *clientCertWanter) Validate() error { return nil } +func (s *clientCertWanter) ValidateInitialization() error { return nil } // WantSchemeAdmission is a test stub that fulfills the WantsScheme interface. type WantSchemeAdmission struct { @@ -169,7 +143,7 @@ type WantSchemeAdmission struct { func (self *WantSchemeAdmission) SetScheme(s *runtime.Scheme) { self.scheme = s } func (self *WantSchemeAdmission) Admit(a admission.Attributes) error { return nil } func (self *WantSchemeAdmission) Handles(o admission.Operation) bool { return false } -func (self *WantSchemeAdmission) Validate() error { return nil } +func (self *WantSchemeAdmission) ValidateInitialization() error { return nil } var _ admission.Interface = &WantSchemeAdmission{} var _ initializer.WantsScheme = &WantSchemeAdmission{} 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 067b4a1da23..98a07585406 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/initializer/interfaces.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/initializer/interfaces.go @@ -27,30 +27,23 @@ import ( // WantsExternalKubeClientSet defines a function which sets external ClientSet for admission plugins that need it type WantsExternalKubeClientSet interface { SetExternalKubeClientSet(kubernetes.Interface) - admission.Validator + admission.InitializationValidator } // WantsExternalKubeInformerFactory defines a function which sets InformerFactory for admission plugins that need it type WantsExternalKubeInformerFactory interface { SetExternalKubeInformerFactory(informers.SharedInformerFactory) - admission.Validator + admission.InitializationValidator } // WantsAuthorizer defines a function which sets Authorizer for admission plugins that need it. type WantsAuthorizer interface { SetAuthorizer(authorizer.Authorizer) - admission.Validator -} - -// WantsClientCert defines a fuction that accepts a cert & key for admission -// plugins that need to make calls and prove their identity. -type WantsClientCert interface { - SetClientCert(cert, key []byte) - admission.Validator + admission.InitializationValidator } // WantsScheme defines a function that accepts runtime.Scheme for admission plugins that need it. type WantsScheme interface { SetScheme(*runtime.Scheme) - admission.Validator + admission.InitializationValidator } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/interfaces.go b/staging/src/k8s.io/apiserver/pkg/admission/interfaces.go index a8e671db514..76d3864e275 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/interfaces.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/interfaces.go @@ -53,14 +53,26 @@ type Attributes interface { // Interface is an abstract, pluggable interface for Admission Control decisions. type Interface interface { - // Admit makes an admission decision based on the request attributes - Admit(a Attributes) (err error) - // Handles returns true if this admission controller can handle the given operation // where operation can be one of CREATE, UPDATE, DELETE, or CONNECT Handles(operation Operation) bool } +type MutationInterface interface { + Interface + + // Admit makes an admission decision based on the request attributes + Admit(a Attributes) (err error) +} + +// ValidationInterface is an abstract, pluggable interface for Admission Control decisions. +type ValidationInterface interface { + Interface + + // Validate makes an admission decision based on the request attributes. It is NOT allowed to mutate + Validate(a Attributes) (err error) +} + // Operation is the type of resource operation being checked for admission control type Operation string @@ -78,10 +90,10 @@ type PluginInitializer interface { Initialize(plugin Interface) } -// Validator holds Validate functions, which are responsible for validation of initialized shared resources -// and should be implemented on admission plugins -type Validator interface { - Validate() error +// InitializationValidator holds ValidateInitialization functions, which are responsible for validation of initialized +// shared resources and should be implemented on admission plugins +type InitializationValidator interface { + ValidateInitialization() error } // ConfigProvider provides a way to get configuration for an admission plugin based on its name diff --git a/staging/src/k8s.io/apiserver/pkg/admission/metrics/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/metrics/BUILD new file mode 100644 index 00000000000..22e4b1f3b2d --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/metrics/BUILD @@ -0,0 +1,42 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["metrics.go"], + importpath = "k8s.io/apiserver/pkg/admission/metrics", + visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "metrics_test.go", + "testutil_test.go", + ], + embed = [":go_default_library"], + importpath = "k8s.io/apiserver/pkg/admission/metrics", + deps = [ + "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", + "//vendor/github.com/prometheus/client_model/go:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/metrics/metrics.go b/staging/src/k8s.io/apiserver/pkg/admission/metrics/metrics.go new file mode 100644 index 00000000000..0955a98c9b8 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/metrics/metrics.go @@ -0,0 +1,217 @@ +/* +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 metrics + +import ( + "fmt" + "strconv" + "time" + + "github.com/prometheus/client_golang/prometheus" + + "k8s.io/apiserver/pkg/admission" +) + +const ( + namespace = "apiserver" + subsystem = "admission" +) + +var ( + // Use buckets ranging from 25 ms to ~2.5 seconds. + latencyBuckets = prometheus.ExponentialBuckets(25000, 2.5, 5) + latencySummaryMaxAge = 5 * time.Hour + + // Metrics provides access to all admission metrics. + Metrics = newAdmissionMetrics() +) + +// ObserverFunc is a func that emits metrics. +type ObserverFunc func(elapsed time.Duration, rejected bool, attr admission.Attributes, stepType string, extraLabels ...string) + +const ( + stepValidate = "validate" + stepAdmit = "admit" +) + +// WithControllerMetrics is a decorator for named admission handlers. +func WithControllerMetrics(i admission.Interface, name string) admission.Interface { + return WithMetrics(i, Metrics.ObserveAdmissionController, name) +} + +// WithStepMetrics is a decorator for a whole admission phase, i.e. admit or validation.admission step. +func WithStepMetrics(i admission.Interface) admission.Interface { + return WithMetrics(i, Metrics.ObserveAdmissionStep) +} + +// WithMetrics is a decorator for admission handlers with a generic observer func. +func WithMetrics(i admission.Interface, observer ObserverFunc, extraLabels ...string) admission.Interface { + return &pluginHandlerWithMetrics{ + Interface: i, + observer: observer, + extraLabels: extraLabels, + } +} + +// pluginHandlerWithMetrics decorates a admission handler with metrics. +type pluginHandlerWithMetrics struct { + admission.Interface + observer ObserverFunc + extraLabels []string +} + +// Admit performs a mutating admission control check and emit metrics. +func (p pluginHandlerWithMetrics) Admit(a admission.Attributes) error { + mutatingHandler, ok := p.Interface.(admission.MutationInterface) + if !ok { + return nil + } + + start := time.Now() + err := mutatingHandler.Admit(a) + p.observer(time.Since(start), err != nil, a, stepAdmit, p.extraLabels...) + return err +} + +// Validate performs a non-mutating admission control check and emits metrics. +func (p pluginHandlerWithMetrics) Validate(a admission.Attributes) error { + validatingHandler, ok := p.Interface.(admission.ValidationInterface) + if !ok { + return nil + } + + start := time.Now() + err := validatingHandler.Validate(a) + p.observer(time.Since(start), err != nil, a, stepValidate, p.extraLabels...) + return err +} + +// AdmissionMetrics instruments admission with prometheus metrics. +type AdmissionMetrics struct { + step *metricSet + controller *metricSet + webhook *metricSet +} + +// newAdmissionMetrics create a new AdmissionMetrics, configured with default metric names. +func newAdmissionMetrics() *AdmissionMetrics { + // Admission metrics for a step of the admission flow. The entire admission flow is broken down into a series of steps + // Each step is identified by a distinct type label value. + step := newMetricSet("step", + []string{"type", "operation", "group", "version", "resource", "subresource", "rejected"}, + "Admission sub-step %s, broken out for each operation and API resource and step type (validate or admit).", true) + + // Built-in admission controller metrics. Each admission controller is identified by name. + controller := newMetricSet("controller", + []string{"name", "type", "operation", "group", "version", "resource", "subresource", "rejected"}, + "Admission controller %s, identified by name and broken out for each operation and API resource and type (validate or admit).", false) + + // Admission webhook metrics. Each webhook is identified by name. + webhook := newMetricSet("webhook", + []string{"name", "type", "operation", "group", "version", "resource", "subresource", "rejected"}, + "Admission webhook %s, identified by name and broken out for each operation and API resource and type (validate or admit).", false) + + step.mustRegister() + controller.mustRegister() + webhook.mustRegister() + return &AdmissionMetrics{step: step, controller: controller, webhook: webhook} +} + +func (m *AdmissionMetrics) reset() { + m.step.reset() + m.controller.reset() + m.webhook.reset() +} + +// ObserveAdmissionStep records admission related metrics for a admission step, identified by step type. +func (m *AdmissionMetrics) ObserveAdmissionStep(elapsed time.Duration, rejected bool, attr admission.Attributes, stepType string, extraLabels ...string) { + gvr := attr.GetResource() + m.step.observe(elapsed, append(extraLabels, stepType, string(attr.GetOperation()), gvr.Group, gvr.Version, gvr.Resource, attr.GetSubresource(), strconv.FormatBool(rejected))...) +} + +// ObserveAdmissionController records admission related metrics for a built-in admission controller, identified by it's plugin handler name. +func (m *AdmissionMetrics) ObserveAdmissionController(elapsed time.Duration, rejected bool, attr admission.Attributes, stepType string, extraLabels ...string) { + gvr := attr.GetResource() + m.controller.observe(elapsed, append(extraLabels, stepType, string(attr.GetOperation()), gvr.Group, gvr.Version, gvr.Resource, attr.GetSubresource(), strconv.FormatBool(rejected))...) +} + +// ObserveWebhook records admission related metrics for a admission webhook. +func (m *AdmissionMetrics) ObserveWebhook(elapsed time.Duration, rejected bool, attr admission.Attributes, stepType string, extraLabels ...string) { + gvr := attr.GetResource() + m.webhook.observe(elapsed, append(extraLabels, stepType, string(attr.GetOperation()), gvr.Group, gvr.Version, gvr.Resource, attr.GetSubresource(), strconv.FormatBool(rejected))...) +} + +type metricSet struct { + latencies *prometheus.HistogramVec + latenciesSummary *prometheus.SummaryVec +} + +func newMetricSet(name string, labels []string, helpTemplate string, hasSummary bool) *metricSet { + var summary *prometheus.SummaryVec + if hasSummary { + summary = prometheus.NewSummaryVec( + prometheus.SummaryOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: fmt.Sprintf("%s_admission_latencies_seconds_summary", name), + Help: fmt.Sprintf(helpTemplate, "latency summary"), + MaxAge: latencySummaryMaxAge, + }, + labels, + ) + } + + return &metricSet{ + latencies: prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: fmt.Sprintf("%s_admission_latencies_seconds", name), + Help: fmt.Sprintf(helpTemplate, "latency histogram"), + Buckets: latencyBuckets, + }, + labels, + ), + + latenciesSummary: summary, + } +} + +// MustRegister registers all the prometheus metrics in the metricSet. +func (m *metricSet) mustRegister() { + prometheus.MustRegister(m.latencies) + if m.latenciesSummary != nil { + prometheus.MustRegister(m.latenciesSummary) + } +} + +// Reset resets all the prometheus metrics in the metricSet. +func (m *metricSet) reset() { + m.latencies.Reset() + if m.latenciesSummary != nil { + m.latenciesSummary.Reset() + } +} + +// Observe records an observed admission event to all metrics in the metricSet. +func (m *metricSet) observe(elapsed time.Duration, labels ...string) { + elapsedMicroseconds := float64(elapsed / time.Microsecond) + m.latencies.WithLabelValues(labels...).Observe(elapsedMicroseconds) + if m.latenciesSummary != nil { + m.latenciesSummary.WithLabelValues(labels...).Observe(elapsedMicroseconds) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/metrics/metrics_test.go b/staging/src/k8s.io/apiserver/pkg/admission/metrics/metrics_test.go new file mode 100644 index 00000000000..859e3d30eb9 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/metrics/metrics_test.go @@ -0,0 +1,247 @@ +/* +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 metrics + +import ( + "fmt" + "testing" + "time" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/admission" +) + +var ( + kind = schema.GroupVersionKind{Group: "kgroup", Version: "kversion", Kind: "kind"} + resource = schema.GroupVersionResource{Group: "rgroup", Version: "rversion", Resource: "resource"} + attr = admission.NewAttributesRecord(nil, nil, kind, "ns", "name", resource, "subresource", admission.Create, nil) +) + +func TestObserveAdmissionStep(t *testing.T) { + Metrics.reset() + handler := WithStepMetrics(&mutatingAndValidatingFakeHandler{admission.NewHandler(admission.Create), true, true}) + handler.(admission.MutationInterface).Admit(attr) + handler.(admission.ValidationInterface).Validate(attr) + wantLabels := map[string]string{ + "operation": string(admission.Create), + "group": resource.Group, + "version": resource.Version, + "resource": resource.Resource, + "subresource": "subresource", + "type": "admit", + "rejected": "false", + } + expectHistogramCountTotal(t, "apiserver_admission_step_admission_latencies_seconds", wantLabels, 1) + expectFindMetric(t, "apiserver_admission_step_admission_latencies_seconds_summary", wantLabels) + + wantLabels["type"] = "validate" + expectHistogramCountTotal(t, "apiserver_admission_step_admission_latencies_seconds", wantLabels, 1) + expectFindMetric(t, "apiserver_admission_step_admission_latencies_seconds_summary", wantLabels) +} + +func TestObserveAdmissionController(t *testing.T) { + Metrics.reset() + handler := WithControllerMetrics(&mutatingAndValidatingFakeHandler{admission.NewHandler(admission.Create), true, true}, "a") + handler.(admission.MutationInterface).Admit(attr) + handler.(admission.ValidationInterface).Validate(attr) + wantLabels := map[string]string{ + "name": "a", + "operation": string(admission.Create), + "group": resource.Group, + "version": resource.Version, + "resource": resource.Resource, + "subresource": "subresource", + "type": "admit", + "rejected": "false", + } + expectHistogramCountTotal(t, "apiserver_admission_controller_admission_latencies_seconds", wantLabels, 1) + + wantLabels["type"] = "validate" + expectHistogramCountTotal(t, "apiserver_admission_controller_admission_latencies_seconds", wantLabels, 1) +} + +func TestObserveWebhook(t *testing.T) { + Metrics.reset() + Metrics.ObserveWebhook(2*time.Second, false, attr, stepAdmit, "x") + wantLabels := map[string]string{ + "name": "x", + "operation": string(admission.Create), + "group": resource.Group, + "version": resource.Version, + "resource": resource.Resource, + "subresource": "subresource", + "type": "admit", + "rejected": "false", + } + expectHistogramCountTotal(t, "apiserver_admission_webhook_admission_latencies_seconds", wantLabels, 1) +} + +func TestWithMetrics(t *testing.T) { + Metrics.reset() + + type Test struct { + name string + ns string + operation admission.Operation + handler admission.Interface + admit, validate bool + } + for _, test := range []Test{ + { + "both-interfaces-admit-and-validate", + "some-ns", + admission.Create, + &mutatingAndValidatingFakeHandler{admission.NewHandler(admission.Create, admission.Update), true, true}, + true, true, + }, + { + "both-interfaces-dont-admit", + "some-ns", + admission.Create, + &mutatingAndValidatingFakeHandler{admission.NewHandler(admission.Create, admission.Update), false, true}, + false, true, + }, + { + "both-interfaces-admit-dont-validate", + "some-ns", + admission.Create, + &mutatingAndValidatingFakeHandler{admission.NewHandler(admission.Create, admission.Update), true, false}, + true, false, + }, + { + "validate-interfaces-validate", + "some-ns", + admission.Create, + &validatingFakeHandler{admission.NewHandler(admission.Create, admission.Update), true}, + true, true, + }, + { + "validate-interfaces-dont-validate", + "some-ns", + admission.Create, + &validatingFakeHandler{admission.NewHandler(admission.Create, admission.Update), false}, + true, false, + }, + { + "mutating-interfaces-admit", + "some-ns", + admission.Create, + &mutatingFakeHandler{admission.NewHandler(admission.Create, admission.Update), true}, + true, true, + }, + { + "mutating-interfaces-dont-admit", + "some-ns", + admission.Create, + &mutatingFakeHandler{admission.NewHandler(admission.Create, admission.Update), false}, + false, true, + }, + } { + Metrics.reset() + + h := WithMetrics(test.handler, Metrics.ObserveAdmissionController, test.name) + + // test mutation + err := h.(admission.MutationInterface).Admit(admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, test.ns, "", schema.GroupVersionResource{}, "", test.operation, nil)) + if test.admit && err != nil { + t.Errorf("expected admit to succeed, but failed: %v", err) + continue + } else if !test.admit && err == nil { + t.Errorf("expected admit to fail, but it succeeded") + continue + } + + filter := map[string]string{"type": "admit", "rejected": "false"} + if !test.admit { + filter["rejected"] = "true" + } + if _, mutating := test.handler.(admission.MutationInterface); mutating { + expectHistogramCountTotal(t, "apiserver_admission_controller_admission_latencies_seconds", filter, 1) + } else { + expectHistogramCountTotal(t, "apiserver_admission_controller_admission_latencies_seconds", filter, 0) + } + + if err != nil { + // skip validation step if mutation failed + continue + } + + // test validation + err = h.(admission.ValidationInterface).Validate(admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, test.ns, "", schema.GroupVersionResource{}, "", test.operation, nil)) + if test.validate && err != nil { + t.Errorf("expected admit to succeed, but failed: %v", err) + continue + } else if !test.validate && err == nil { + t.Errorf("expected validation to fail, but it succeeded") + continue + } + + filter = map[string]string{"type": "validate", "rejected": "false"} + if !test.validate { + filter["rejected"] = "true" + } + if _, validating := test.handler.(admission.ValidationInterface); validating { + expectHistogramCountTotal(t, "apiserver_admission_controller_admission_latencies_seconds", filter, 1) + } else { + expectHistogramCountTotal(t, "apiserver_admission_controller_admission_latencies_seconds", filter, 0) + } + } +} + +type mutatingAndValidatingFakeHandler struct { + *admission.Handler + admit bool + validate bool +} + +func (h *mutatingAndValidatingFakeHandler) Admit(a admission.Attributes) (err error) { + if h.admit { + return nil + } + return fmt.Errorf("don't admit") +} + +func (h *mutatingAndValidatingFakeHandler) Validate(a admission.Attributes) (err error) { + if h.validate { + return nil + } + return fmt.Errorf("don't validate") +} + +type validatingFakeHandler struct { + *admission.Handler + validate bool +} + +func (h *validatingFakeHandler) Validate(a admission.Attributes) (err error) { + if h.validate { + return nil + } + return fmt.Errorf("don't validate") +} + +type mutatingFakeHandler struct { + *admission.Handler + admit bool +} + +func (h *mutatingFakeHandler) Admit(a admission.Attributes) (err error) { + if h.admit { + return nil + } + return fmt.Errorf("don't admit") +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/metrics/testutil_test.go b/staging/src/k8s.io/apiserver/pkg/admission/metrics/testutil_test.go new file mode 100644 index 00000000000..af5ee79a8b6 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/metrics/testutil_test.go @@ -0,0 +1,91 @@ +/* +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 metrics + +import ( + "testing" + + "github.com/prometheus/client_golang/prometheus" + ptype "github.com/prometheus/client_model/go" +) + +func labelsMatch(metric *ptype.Metric, labelFilter map[string]string) bool { + for _, lp := range metric.GetLabel() { + if value, ok := labelFilter[lp.GetName()]; ok && lp.GetValue() != value { + return false + } + } + return true +} + +// expectFindMetric find a metric with the given name nad labels or reports a fatal test error. +func expectFindMetric(t *testing.T, name string, expectedLabels map[string]string) *ptype.Metric { + metrics, err := prometheus.DefaultGatherer.Gather() + if err != nil { + t.Fatalf("Failed to gather metrics: %s", err) + } + + for _, mf := range metrics { + if mf.GetName() == name { + for _, metric := range mf.GetMetric() { + if labelsMatch(metric, expectedLabels) { + gotLabelCount := len(metric.GetLabel()) + wantLabelCount := len(expectedLabels) + if wantLabelCount != gotLabelCount { + t.Errorf("Got metric with %d labels, but wanted %d labels. Wanted %#+v for %s", + gotLabelCount, wantLabelCount, expectedLabels, metric.String()) + } + return metric + } + } + } + } + t.Fatalf("No metric found with name %s and labels %#+v", name, expectedLabels) + return nil +} + +// expectHistogramCountTotal ensures that the sum of counts of metrics matching the labelFilter is as +// expected. +func expectHistogramCountTotal(t *testing.T, name string, labelFilter map[string]string, wantCount int) { + metrics, err := prometheus.DefaultGatherer.Gather() + if err != nil { + t.Fatalf("Failed to gather metrics: %s", err) + } + + counterSum := 0 + for _, mf := range metrics { + if mf.GetName() != name { + continue // Ignore other metrics. + } + for _, metric := range mf.GetMetric() { + if !labelsMatch(metric, labelFilter) { + continue + } + counterSum += int(metric.GetHistogram().GetSampleCount()) + } + } + if wantCount != counterSum { + t.Errorf("Wanted count %d, got %d for metric %s with labels %#+v", wantCount, counterSum, name, labelFilter) + for _, mf := range metrics { + if mf.GetName() == name { + for _, metric := range mf.GetMetric() { + t.Logf("\tnear match: %s", metric.String()) + } + } + } + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/initialization/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/initialization/BUILD index 6479a44881c..9410779b8f7 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/initialization/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/initialization/BUILD @@ -46,8 +46,8 @@ filegroup( go_test( name = "go_default_test", srcs = ["initialization_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/admission/plugin/initialization", - library = ":go_default_library", deps = [ "//vendor/k8s.io/api/admissionregistration/v1alpha1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/initialization/initialization.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/initialization/initialization.go index e536e290dfb..1bb59da5d53 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/initialization/initialization.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/initialization/initialization.go @@ -74,7 +74,8 @@ func NewInitializer() admission.Interface { return &initializer{} } -func (i *initializer) Validate() error { +// ValidateInitialization implements the InitializationValidator interface. +func (i *initializer) ValidateInitialization() error { if i.config == nil { return fmt.Errorf("the Initializer admission plugin requires a Kubernetes client to be provided") } @@ -94,10 +95,12 @@ func (i *initializer) Validate() error { return nil } +// SetExternalKubeClientSet implements the WantsExternalKubeClientSet interface. func (i *initializer) SetExternalKubeClientSet(client clientset.Interface) { - i.config = configuration.NewInitializerConfigurationManager(client.Admissionregistration().InitializerConfigurations()) + i.config = configuration.NewInitializerConfigurationManager(client.AdmissionregistrationV1alpha1().InitializerConfigurations()) } +// SetAuthorizer implements the WantsAuthorizer interface. func (i *initializer) SetAuthorizer(a authorizer.Authorizer) { i.authorizer = a } @@ -257,7 +260,7 @@ func (i *initializer) Admit(a admission.Attributes) (err error) { func (i *initializer) canInitialize(a admission.Attributes, message string) error { // caller must have the ability to mutate un-initialized resources - authorized, reason, err := i.authorizer.Authorize(authorizer.AttributesRecord{ + decision, reason, err := i.authorizer.Authorize(authorizer.AttributesRecord{ Name: a.GetName(), ResourceRequest: true, User: a.GetUserInfo(), @@ -270,12 +273,14 @@ func (i *initializer) canInitialize(a admission.Attributes, message string) erro if err != nil { return err } - if !authorized { + if decision != authorizer.DecisionAllow { return errors.NewForbidden(a.GetResource().GroupResource(), a.GetName(), fmt.Errorf("%s: %s", message, reason)) } return nil } +// Handles returns true if this admission controller can handle the given operation +// where operation can be one of CREATE, UPDATE, DELETE, or CONNECT func (i *initializer) Handles(op admission.Operation) bool { return op == admission.Create || op == admission.Update } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/initialization/initialization_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/initialization/initialization_test.go index 0832fe8d1be..a3bb0991b34 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/initialization/initialization_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/initialization/initialization_test.go @@ -109,11 +109,11 @@ type fakeAuthorizer struct { accept bool } -func (f *fakeAuthorizer) Authorize(a authorizer.Attributes) (bool, string, error) { +func (f *fakeAuthorizer) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) { if f.accept { - return true, "", nil + return authorizer.DecisionAllow, "", nil } - return false, "denied", nil + return authorizer.DecisionNoOpinion, "denied", nil } func TestAdmitUpdate(t *testing.T) { diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/BUILD index 1c821c23cc0..101cd77000b 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/BUILD @@ -30,8 +30,8 @@ go_library( go_test( name = "go_default_test", srcs = ["admission_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle", - library = ":go_default_library", deps = [ "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", 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 20e7bd88d17..81c24f6a5a6 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 @@ -57,9 +57,9 @@ func Register(plugins *admission.Plugins) { }) } -// lifecycle is an implementation of admission.Interface. +// Lifecycle is an implementation of admission.Interface. // It enforces life-cycle constraints around a Namespace depending on its Phase -type lifecycle struct { +type Lifecycle struct { *admission.Handler client kubernetes.Interface immortalNamespaces sets.String @@ -69,23 +69,10 @@ type lifecycle struct { forceLiveLookupCache *utilcache.LRUExpireCache } -type forceLiveLookupEntry struct { - expiry time.Time -} +var _ = initializer.WantsExternalKubeInformerFactory(&Lifecycle{}) +var _ = initializer.WantsExternalKubeClientSet(&Lifecycle{}) -var _ = initializer.WantsExternalKubeInformerFactory(&lifecycle{}) -var _ = initializer.WantsExternalKubeClientSet(&lifecycle{}) - -func makeNamespaceKey(namespace string) *v1.Namespace { - return &v1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: namespace, - Namespace: "", - }, - } -} - -func (l *lifecycle) Admit(a admission.Attributes) error { +func (l *Lifecycle) Admit(a admission.Attributes) error { // prevent deletion of immortal namespaces if a.GetOperation() == admission.Delete && a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() && l.immortalNamespaces.Has(a.GetName()) { return errors.NewForbidden(a.GetResource().GroupResource(), a.GetName(), fmt.Errorf("this namespace may not be deleted")) @@ -165,7 +152,7 @@ func (l *lifecycle) Admit(a admission.Attributes) error { // refuse to operate on non-existent namespaces if !exists || forceLiveLookup { // as a last resort, make a call directly to storage - namespace, err = l.client.Core().Namespaces().Get(a.GetNamespace(), metav1.GetOptions{}) + namespace, err = l.client.CoreV1().Namespaces().Get(a.GetNamespace(), metav1.GetOptions{}) switch { case errors.IsNotFound(err): return err @@ -182,37 +169,40 @@ func (l *lifecycle) Admit(a admission.Attributes) error { } // TODO: This should probably not be a 403 - return admission.NewForbidden(a, fmt.Errorf("unable to create new content in namespace %s because it is being terminated.", a.GetNamespace())) + return admission.NewForbidden(a, fmt.Errorf("unable to create new content in namespace %s because it is being terminated", a.GetNamespace())) } return nil } -// NewLifecycle creates a new namespace lifecycle admission control handler -func NewLifecycle(immortalNamespaces sets.String) (admission.Interface, error) { +// NewLifecycle creates a new namespace Lifecycle admission control handler +func NewLifecycle(immortalNamespaces sets.String) (*Lifecycle, error) { return newLifecycleWithClock(immortalNamespaces, clock.RealClock{}) } -func newLifecycleWithClock(immortalNamespaces sets.String, clock utilcache.Clock) (admission.Interface, error) { +func newLifecycleWithClock(immortalNamespaces sets.String, clock utilcache.Clock) (*Lifecycle, error) { forceLiveLookupCache := utilcache.NewLRUExpireCacheWithClock(100, clock) - return &lifecycle{ + return &Lifecycle{ Handler: admission.NewHandler(admission.Create, admission.Update, admission.Delete), immortalNamespaces: immortalNamespaces, forceLiveLookupCache: forceLiveLookupCache, }, nil } -func (l *lifecycle) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) { +// SetExternalKubeInformerFactory implements the WantsExternalKubeInformerFactory interface. +func (l *Lifecycle) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) { namespaceInformer := f.Core().V1().Namespaces() l.namespaceLister = namespaceInformer.Lister() l.SetReadyFunc(namespaceInformer.Informer().HasSynced) } -func (l *lifecycle) SetExternalKubeClientSet(client kubernetes.Interface) { +// SetExternalKubeClientSet implements the WantsExternalKubeClientSet interface. +func (l *Lifecycle) SetExternalKubeClientSet(client kubernetes.Interface) { l.client = client } -func (l *lifecycle) Validate() error { +// ValidateInitialization implements the InitializationValidator interface. +func (l *Lifecycle) ValidateInitialization() error { if l.namespaceLister == nil { return fmt.Errorf("missing namespaceLister") } 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 b0cad52f824..3eca3b67f21 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 @@ -37,23 +37,20 @@ import ( ) // newHandlerForTest returns a configured handler for testing. -func newHandlerForTest(c clientset.Interface) (admission.Interface, informers.SharedInformerFactory, error) { +func newHandlerForTest(c clientset.Interface) (*Lifecycle, informers.SharedInformerFactory, error) { return newHandlerForTestWithClock(c, clock.RealClock{}) } // newHandlerForTestWithClock returns a configured handler for testing. -func newHandlerForTestWithClock(c clientset.Interface, cacheClock clock.Clock) (admission.Interface, informers.SharedInformerFactory, error) { +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) if err != nil { return nil, f, err } - pluginInitializer, err := kubeadmission.New(c, f, nil, nil, nil, nil) - if err != nil { - return handler, f, err - } + pluginInitializer := kubeadmission.New(c, f, nil, nil) pluginInitializer.Initialize(handler) - err = admission.Validate(handler) + err = admission.ValidateInitialization(handler) return handler, f, err } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/BUILD new file mode 100644 index 00000000000..b4cfcdfb6e6 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/BUILD @@ -0,0 +1,60 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "authentication.go", + "client.go", + "kubeconfig.go", + "serviceresolver.go", + ], + importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/config", + visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/hashicorp/golang-lru:go_default_library", + "//vendor/k8s.io/api/admissionregistration/v1beta1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/errors:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", + "//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "authentication_test.go", + "serviceresolver_test.go", + ], + embed = [":go_default_library"], + importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/config", + deps = [ + "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + "//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission:all-srcs", + ], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/BUILD new file mode 100644 index 00000000000..3b6bdc2abee --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/BUILD @@ -0,0 +1,36 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "register.go", + "types.go", + "zz_generated.deepcopy.go", + ], + importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/install:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1:all-srcs", + ], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/doc.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/doc.go new file mode 100644 index 00000000000..63ab310393c --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/doc.go @@ -0,0 +1,19 @@ +/* +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 + +package webhookadmission diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/install/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/install/BUILD new file mode 100644 index 00000000000..867b0a9aa91 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/install/BUILD @@ -0,0 +1,29 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["install.go"], + importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/install", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) 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 new file mode 100644 index 00000000000..bbb3eaf62c4 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/install/install.go @@ -0,0 +1,43 @@ +/* +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 install installs the experimental API group, making it available as +// an option to all of the API encoding/decoding machinery. +package install + +import ( + "k8s.io/apimachinery/pkg/apimachinery/announced" + "k8s.io/apimachinery/pkg/apimachinery/registered" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission" + "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1" +) + +// Install registers the API group and adds types to a scheme +func Install(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *registered.APIRegistrationManager, scheme *runtime.Scheme) { + if err := announced.NewGroupMetaFactory( + &announced.GroupMetaFactoryArgs{ + GroupName: webhookadmission.GroupName, + VersionPreferenceOrder: []string{v1alpha1.SchemeGroupVersion.Version}, + AddInternalObjectsToScheme: webhookadmission.AddToScheme, + }, + announced.VersionToSchemeFunc{ + v1alpha1.SchemeGroupVersion.Version: v1alpha1.AddToScheme, + }, + ).Announce(groupFactoryRegistry).RegisterAndEnable(registry, scheme); err != nil { + panic(err) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/register.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/register.go new file mode 100644 index 00000000000..c958d15baa5 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/register.go @@ -0,0 +1,51 @@ +/* +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 webhookadmission + +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 { + // TODO this will get cleaned up with the scheme types are fixed + scheme.AddKnownTypes(SchemeGroupVersion, + &WebhookAdmission{}, + ) + return nil +} 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 new file mode 100644 index 00000000000..71ce47b1f42 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/types.go @@ -0,0 +1,29 @@ +/* +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 webhookadmission + +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 + + // KubeConfigFile is the path to the kubeconfig file. + KubeConfigFile string +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/BUILD new file mode 100644 index 00000000000..01d39435ecb --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/BUILD @@ -0,0 +1,36 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "register.go", + "types.go", + "zz_generated.conversion.go", + "zz_generated.deepcopy.go", + "zz_generated.defaults.go", + ], + importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) 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 new file mode 100644 index 00000000000..04c376f7795 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/doc.go @@ -0,0 +1,23 @@ +/* +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 + +// Package v1alpha1 is the v1alpha1 version of the API. +// +groupName=apiserver.config.k8s.io +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 new file mode 100644 index 00000000000..56489f7804c --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/register.go @@ -0,0 +1,50 @@ +/* +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 new file mode 100644 index 00000000000..a49a6a813ea --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/types.go @@ -0,0 +1,29 @@ +/* +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 new file mode 100644 index 00000000000..d7e35ffed5e --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/zz_generated.conversion.go @@ -0,0 +1,60 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by conversion-gen. Do not edit it manually! + +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(scheme *runtime.Scheme) error { + return scheme.AddGeneratedConversionFuncs( + Convert_v1alpha1_WebhookAdmission_To_webhookadmission_WebhookAdmission, + Convert_webhookadmission_WebhookAdmission_To_v1alpha1_WebhookAdmission, + ) +} + +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 new file mode 100644 index 00000000000..c9ef335c700 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,51 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +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 + } else { + 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 new file mode 100644 index 00000000000..5e24d22cacd --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/zz_generated.defaults.go @@ -0,0 +1,32 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by defaulter-gen. Do not edit it manually! + +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/apis/webhookadmission/zz_generated.deepcopy.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/zz_generated.deepcopy.go new file mode 100644 index 00000000000..890594a7d57 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/zz_generated.deepcopy.go @@ -0,0 +1,51 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +package webhookadmission + +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 + } else { + return nil + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/authentication.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/authentication.go new file mode 100644 index 00000000000..dd956f140a7 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/authentication.go @@ -0,0 +1,156 @@ +/* +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 config + +import ( + "fmt" + "io/ioutil" + "strings" + "time" + + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" +) + +// AuthenticationInfoResolverWrapper can be used to inject Dial function to the +// rest.Config generated by the resolver. +type AuthenticationInfoResolverWrapper func(AuthenticationInfoResolver) AuthenticationInfoResolver + +// AuthenticationInfoResolver builds rest.Config base on the server name. +type AuthenticationInfoResolver interface { + ClientConfigFor(server string) (*rest.Config, error) +} + +// AuthenticationInfoResolverFunc implements AuthenticationInfoResolver. +type AuthenticationInfoResolverFunc func(server string) (*rest.Config, error) + +//ClientConfigFor implements AuthenticationInfoResolver. +func (a AuthenticationInfoResolverFunc) ClientConfigFor(server string) (*rest.Config, error) { + return a(server) +} + +type defaultAuthenticationInfoResolver struct { + kubeconfig clientcmdapi.Config +} + +// NewDefaultAuthenticationInfoResolver generates an AuthenticationInfoResolver +// that builds rest.Config based on the kubeconfig file. kubeconfigFile is the +// path to the kubeconfig. +func NewDefaultAuthenticationInfoResolver(kubeconfigFile string) (AuthenticationInfoResolver, error) { + if len(kubeconfigFile) == 0 { + return &defaultAuthenticationInfoResolver{}, nil + } + + loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + loadingRules.ExplicitPath = kubeconfigFile + loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{}) + clientConfig, err := loader.RawConfig() + if err != nil { + return nil, err + } + + return &defaultAuthenticationInfoResolver{kubeconfig: clientConfig}, nil +} + +func (c *defaultAuthenticationInfoResolver) ClientConfigFor(server string) (*rest.Config, error) { + // exact match + if authConfig, ok := c.kubeconfig.AuthInfos[server]; ok { + return restConfigFromKubeconfig(authConfig) + } + + // star prefixed match + serverSteps := strings.Split(server, ".") + for i := 1; i < len(serverSteps); i++ { + nickName := "*." + strings.Join(serverSteps[i:], ".") + if authConfig, ok := c.kubeconfig.AuthInfos[nickName]; ok { + return restConfigFromKubeconfig(authConfig) + } + } + + // if we're trying to hit the kube-apiserver and there wasn't an explicit config, use the in-cluster config + if server == "kubernetes.default.svc" { + // if we can find an in-cluster-config use that. If we can't, fall through. + inClusterConfig, err := rest.InClusterConfig() + if err == nil { + return setGlobalDefaults(inClusterConfig), nil + } + } + + // star (default) match + if authConfig, ok := c.kubeconfig.AuthInfos["*"]; ok { + return restConfigFromKubeconfig(authConfig) + } + + // use the current context from the kubeconfig if possible + if len(c.kubeconfig.CurrentContext) > 0 { + if currContext, ok := c.kubeconfig.Contexts[c.kubeconfig.CurrentContext]; ok { + if len(currContext.AuthInfo) > 0 { + if currAuth, ok := c.kubeconfig.AuthInfos[currContext.AuthInfo]; ok { + return restConfigFromKubeconfig(currAuth) + } + } + } + } + + // anonymous + return setGlobalDefaults(&rest.Config{}), nil +} + +func restConfigFromKubeconfig(configAuthInfo *clientcmdapi.AuthInfo) (*rest.Config, error) { + config := &rest.Config{} + + // blindly overwrite existing values based on precedence + if len(configAuthInfo.Token) > 0 { + config.BearerToken = configAuthInfo.Token + } else if len(configAuthInfo.TokenFile) > 0 { + tokenBytes, err := ioutil.ReadFile(configAuthInfo.TokenFile) + if err != nil { + return nil, err + } + config.BearerToken = string(tokenBytes) + } + if len(configAuthInfo.Impersonate) > 0 { + config.Impersonate = rest.ImpersonationConfig{ + UserName: configAuthInfo.Impersonate, + Groups: configAuthInfo.ImpersonateGroups, + Extra: configAuthInfo.ImpersonateUserExtra, + } + } + if len(configAuthInfo.ClientCertificate) > 0 || len(configAuthInfo.ClientCertificateData) > 0 { + config.CertFile = configAuthInfo.ClientCertificate + config.CertData = configAuthInfo.ClientCertificateData + config.KeyFile = configAuthInfo.ClientKey + config.KeyData = configAuthInfo.ClientKeyData + } + if len(configAuthInfo.Username) > 0 || len(configAuthInfo.Password) > 0 { + config.Username = configAuthInfo.Username + config.Password = configAuthInfo.Password + } + if configAuthInfo.AuthProvider != nil { + return nil, fmt.Errorf("auth provider not supported") + } + + return setGlobalDefaults(config), nil +} + +func setGlobalDefaults(config *rest.Config) *rest.Config { + config.UserAgent = "kube-apiserver-admission" + config.Timeout = 30 * time.Second + + return config +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/authentication_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/authentication_test.go new file mode 100644 index 00000000000..cd63bd94bbd --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/authentication_test.go @@ -0,0 +1,130 @@ +/* +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 config + +import ( + "testing" + + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/util/diff" + "k8s.io/client-go/rest" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" +) + +func TestAuthenticationDetection(t *testing.T) { + tests := []struct { + name string + kubeconfig clientcmdapi.Config + serverName string + expected rest.Config + }{ + { + name: "empty", + serverName: "foo.com", + }, + { + name: "fallback to current context", + serverName: "foo.com", + kubeconfig: clientcmdapi.Config{ + AuthInfos: map[string]*clientcmdapi.AuthInfo{ + "bar.com": {Token: "bar"}, + }, + Contexts: map[string]*clientcmdapi.Context{ + "ctx": { + AuthInfo: "bar.com", + }, + }, + CurrentContext: "ctx", + }, + expected: rest.Config{BearerToken: "bar"}, + }, + { + name: "exact match", + serverName: "foo.com", + kubeconfig: clientcmdapi.Config{ + AuthInfos: map[string]*clientcmdapi.AuthInfo{ + "foo.com": {Token: "foo"}, + "*.com": {Token: "foo-star"}, + "bar.com": {Token: "bar"}, + }, + }, + expected: rest.Config{BearerToken: "foo"}, + }, + { + name: "partial star match", + serverName: "foo.com", + kubeconfig: clientcmdapi.Config{ + AuthInfos: map[string]*clientcmdapi.AuthInfo{ + "*.com": {Token: "foo-star"}, + "bar.com": {Token: "bar"}, + }, + }, + expected: rest.Config{BearerToken: "foo-star"}, + }, + { + name: "full star match", + serverName: "foo.com", + kubeconfig: clientcmdapi.Config{ + AuthInfos: map[string]*clientcmdapi.AuthInfo{ + "*": {Token: "star"}, + "bar.com": {Token: "bar"}, + }, + }, + expected: rest.Config{BearerToken: "star"}, + }, + { + name: "skip bad in cluster config", + serverName: "kubernetes.default.svc", + kubeconfig: clientcmdapi.Config{ + AuthInfos: map[string]*clientcmdapi.AuthInfo{ + "*": {Token: "star"}, + "bar.com": {Token: "bar"}, + }, + }, + expected: rest.Config{BearerToken: "star"}, + }, + { + name: "most selective", + serverName: "one.two.three.com", + kubeconfig: clientcmdapi.Config{ + AuthInfos: map[string]*clientcmdapi.AuthInfo{ + "*.two.three.com": {Token: "first"}, + "*.three.com": {Token: "second"}, + "*.com": {Token: "third"}, + }, + }, + expected: rest.Config{BearerToken: "first"}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + resolver := defaultAuthenticationInfoResolver{kubeconfig: tc.kubeconfig} + actual, err := resolver.ClientConfigFor(tc.serverName) + if err != nil { + t.Fatal(err) + } + actual.UserAgent = "" + actual.Timeout = 0 + + if !equality.Semantic.DeepEqual(*actual, tc.expected) { + t.Errorf("%v", diff.ObjectReflectDiff(tc.expected, *actual)) + } + }) + } + +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/client.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/client.go new file mode 100644 index 00000000000..28fac414e17 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/client.go @@ -0,0 +1,175 @@ +/* +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 config + +import ( + "encoding/json" + "errors" + "fmt" + "net" + "net/url" + + lru "github.com/hashicorp/golang-lru" + "k8s.io/api/admissionregistration/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors" + "k8s.io/client-go/rest" +) + +const ( + defaultCacheSize = 200 +) + +var ( + ErrNeedServiceOrURL = errors.New("webhook configuration must have either service or URL") +) + +// ClientManager builds REST clients to talk to webhooks. It caches the clients +// to avoid duplicate creation. +type ClientManager struct { + authInfoResolver AuthenticationInfoResolver + serviceResolver ServiceResolver + negotiatedSerializer runtime.NegotiatedSerializer + cache *lru.Cache +} + +// NewClientManager creates a ClientManager. +func NewClientManager() (ClientManager, error) { + cache, err := lru.New(defaultCacheSize) + if err != nil { + return ClientManager{}, err + } + return ClientManager{ + cache: cache, + }, nil +} + +// SetAuthenticationInfoResolverWrapper sets the +// AuthenticationInfoResolverWrapper. +func (cm *ClientManager) SetAuthenticationInfoResolverWrapper(wrapper AuthenticationInfoResolverWrapper) { + if wrapper != nil { + cm.authInfoResolver = wrapper(cm.authInfoResolver) + } +} + +// SetAuthenticationInfoResolver sets the AuthenticationInfoResolver. +func (cm *ClientManager) SetAuthenticationInfoResolver(resolver AuthenticationInfoResolver) { + cm.authInfoResolver = resolver +} + +// SetServiceResolver sets the ServiceResolver. +func (cm *ClientManager) SetServiceResolver(sr ServiceResolver) { + if sr != nil { + cm.serviceResolver = sr + } +} + +// SetNegotiatedSerializer sets the NegotiatedSerializer. +func (cm *ClientManager) SetNegotiatedSerializer(n runtime.NegotiatedSerializer) { + cm.negotiatedSerializer = n +} + +// Validate checks if ClientManager is properly set up. +func (cm *ClientManager) Validate() error { + var errs []error + if cm.negotiatedSerializer == nil { + errs = append(errs, fmt.Errorf("the ClientManager requires a negotiatedSerializer")) + } + if cm.serviceResolver == nil { + errs = append(errs, fmt.Errorf("the ClientManager requires a serviceResolver")) + } + if cm.authInfoResolver == nil { + errs = append(errs, fmt.Errorf("the ClientManager requires an authInfoResolver")) + } + return utilerrors.NewAggregate(errs) +} + +// HookClient get a RESTClient from the cache, or constructs one based on the +// webhook configuration. +func (cm *ClientManager) HookClient(h *v1beta1.Webhook) (*rest.RESTClient, error) { + cacheKey, err := json.Marshal(h.ClientConfig) + if err != nil { + return nil, err + } + if client, ok := cm.cache.Get(string(cacheKey)); ok { + return client.(*rest.RESTClient), nil + } + + complete := func(cfg *rest.Config) (*rest.RESTClient, error) { + cfg.TLSClientConfig.CAData = h.ClientConfig.CABundle + cfg.ContentConfig.NegotiatedSerializer = cm.negotiatedSerializer + cfg.ContentConfig.ContentType = runtime.ContentTypeJSON + client, err := rest.UnversionedRESTClientFor(cfg) + if err == nil { + cm.cache.Add(string(cacheKey), client) + } + return client, err + } + + if svc := h.ClientConfig.Service; svc != nil { + serverName := svc.Name + "." + svc.Namespace + ".svc" + restConfig, err := cm.authInfoResolver.ClientConfigFor(serverName) + if err != nil { + return nil, err + } + cfg := rest.CopyConfig(restConfig) + host := serverName + ":443" + cfg.Host = "https://" + host + if svc.Path != nil { + cfg.APIPath = *svc.Path + } + cfg.TLSClientConfig.ServerName = serverName + + delegateDialer := cfg.Dial + if delegateDialer == nil { + delegateDialer = net.Dial + } + cfg.Dial = func(network, addr string) (net.Conn, error) { + if addr == host { + u, err := cm.serviceResolver.ResolveEndpoint(svc.Namespace, svc.Name) + if err != nil { + return nil, err + } + addr = u.Host + } + return delegateDialer(network, addr) + } + + return complete(cfg) + } + + if h.ClientConfig.URL == nil { + return nil, &webhookerrors.ErrCallingWebhook{WebhookName: h.Name, Reason: ErrNeedServiceOrURL} + } + + u, err := url.Parse(*h.ClientConfig.URL) + if err != nil { + return nil, &webhookerrors.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Unparsable URL: %v", err)} + } + + restConfig, err := cm.authInfoResolver.ClientConfigFor(u.Host) + if err != nil { + return nil, err + } + + cfg := rest.CopyConfig(restConfig) + cfg.Host = u.Host + cfg.APIPath = u.Path + + return complete(cfg) +} 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 new file mode 100644 index 00000000000..7cf0d319345 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/kubeconfig.go @@ -0,0 +1,68 @@ +/* +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 config + +import ( + "fmt" + "io" + "io/ioutil" + "path" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission" + "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1" +) + +var ( + scheme = runtime.NewScheme() + codecs = serializer.NewCodecFactory(scheme) +) + +func init() { + webhookadmission.AddToScheme(scheme) + 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 := ioutil.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) + } + + if !path.IsAbs(config.KubeConfigFile) { + return "", field.Invalid(field.NewPath("kubeConfigFile"), config.KubeConfigFile, "must be an absolute file path") + } + + kubeconfigFile = config.KubeConfigFile + } + return kubeconfigFile, nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/serviceresolver.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/serviceresolver.go new file mode 100644 index 00000000000..47b96a709fe --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/serviceresolver.go @@ -0,0 +1,45 @@ +/* +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 config + +import ( + "errors" + "fmt" + "net/url" +) + +// ServiceResolver knows how to convert a service reference into an actual location. +type ServiceResolver interface { + ResolveEndpoint(namespace, name string) (*url.URL, error) +} + +type defaultServiceResolver struct{} + +func NewDefaultServiceResolver() ServiceResolver { + return &defaultServiceResolver{} +} + +// ResolveEndpoint constructs a service URL from a given namespace and name +// note that the name and namespace are required and by default all created addresses use HTTPS scheme. +// for example: +// name=ross namespace=andromeda resolves to https://ross.andromeda.svc:443 +func (sr defaultServiceResolver) ResolveEndpoint(namespace, name string) (*url.URL, error) { + if len(name) == 0 || len(namespace) == 0 { + return nil, errors.New("cannot resolve an empty service name or namespace") + } + return &url.URL{Scheme: "https", Host: fmt.Sprintf("%s.%s.svc:443", name, namespace)}, nil +} diff --git a/plugin/pkg/admission/webhook/serviceresolver_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/serviceresolver_test.go similarity index 85% rename from plugin/pkg/admission/webhook/serviceresolver_test.go rename to staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/serviceresolver_test.go index 3fb2f7a53c5..6fd24d06333 100644 --- a/plugin/pkg/admission/webhook/serviceresolver_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/serviceresolver_test.go @@ -14,8 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package webhook checks a webhook for configured operation admission -package webhook +package config import ( "fmt" @@ -30,7 +29,7 @@ func TestDefaultServiceResolver(t *testing.T) { expectError bool }{ // scenario 1: a service name along with a namespace resolves - {serviceName: "ross", serviceNamespace: "andromeda", expectedOutput: "https://ross.andromeda.svc"}, + {serviceName: "ross", serviceNamespace: "andromeda", expectedOutput: "https://ross.andromeda.svc:443"}, // scenario 2: a service name without a namespace does not resolve {serviceName: "ross", expectError: true}, // scenario 3: cannot resolve an empty service name @@ -49,8 +48,10 @@ func TestDefaultServiceResolver(t *testing.T) { if err == nil && scenario.expectError { t.Error("expected an error but got nothing") } - if serviceURL.String() != scenario.expectedOutput { - t.Errorf("expected = %s, got = %s", scenario.expectedOutput, serviceURL.String()) + if !scenario.expectError { + if serviceURL.String() != scenario.expectedOutput { + t.Errorf("expected = %s, got = %s", scenario.expectedOutput, serviceURL.String()) + } } }) } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/errors/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/errors/BUILD new file mode 100644 index 00000000000..60b64519b11 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/errors/BUILD @@ -0,0 +1,38 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "errors.go", + "statuserror.go", + ], + importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/errors", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["statuserror_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/errors", + deps = ["//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library"], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/errors/doc.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/errors/doc.go new file mode 100644 index 00000000000..6e86a1b5f20 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/errors/doc.go @@ -0,0 +1,18 @@ +/* +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 errors contains utilities for admission webhook specific errors +package errors // import "k8s.io/apiserver/pkg/admission/plugin/webhook/errors" diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/errors/errors.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/errors/errors.go new file mode 100644 index 00000000000..2396152286c --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/errors/errors.go @@ -0,0 +1,34 @@ +/* +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 errors + +import "fmt" + +// ErrCallingWebhook is returned for transport-layer errors calling webhooks. It +// represents a failure to talk to the webhook, not the webhook rejecting a +// request. +type ErrCallingWebhook struct { + WebhookName string + Reason error +} + +func (e *ErrCallingWebhook) Error() string { + if e.Reason != nil { + return fmt.Sprintf("failed calling admission webhook %q: %v", e.WebhookName, e.Reason) + } + return fmt.Sprintf("failed calling admission webhook %q; no further details available", e.WebhookName) +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/errors/statuserror.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/errors/statuserror.go new file mode 100644 index 00000000000..f37dec0177b --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/errors/statuserror.go @@ -0,0 +1,47 @@ +/* +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 errors + +import ( + "fmt" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// ToStatusErr returns a StatusError with information about the webhook plugin +func ToStatusErr(webhookName string, result *metav1.Status) *apierrors.StatusError { + deniedBy := fmt.Sprintf("admission webhook %q denied the request", webhookName) + const noExp = "without explanation" + + if result == nil { + result = &metav1.Status{Status: metav1.StatusFailure} + } + + switch { + case len(result.Message) > 0: + result.Message = fmt.Sprintf("%s: %s", deniedBy, result.Message) + case len(result.Reason) > 0: + result.Message = fmt.Sprintf("%s: %s", deniedBy, result.Reason) + default: + result.Message = fmt.Sprintf("%s %s", deniedBy, noExp) + } + + return &apierrors.StatusError{ + ErrStatus: *result, + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/errors/statuserror_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/errors/statuserror_test.go new file mode 100644 index 00000000000..98b780b4517 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/errors/statuserror_test.go @@ -0,0 +1,73 @@ +/* +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 errors + +import ( + "fmt" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestToStatusErr(t *testing.T) { + hookName := "foo" + deniedBy := fmt.Sprintf("admission webhook %q denied the request", hookName) + tests := []struct { + name string + result *metav1.Status + expectedError string + }{ + { + "nil result", + nil, + deniedBy + " without explanation", + }, + { + "only message", + &metav1.Status{ + Message: "you shall not pass", + }, + deniedBy + ": you shall not pass", + }, + { + "only reason", + &metav1.Status{ + Reason: metav1.StatusReasonForbidden, + }, + deniedBy + ": Forbidden", + }, + { + "message and reason", + &metav1.Status{ + Message: "you shall not pass", + Reason: metav1.StatusReasonForbidden, + }, + deniedBy + ": you shall not pass", + }, + { + "no message, no reason", + &metav1.Status{}, + deniedBy + " without explanation", + }, + } + for _, test := range tests { + err := ToStatusErr(hookName, test.result) + if err == nil || err.Error() != test.expectedError { + t.Errorf("%s: expected an error saying %q, but got %v", test.name, test.expectedError, err) + } + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/initializer/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/initializer/BUILD new file mode 100644 index 00000000000..f81cea70d67 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/initializer/BUILD @@ -0,0 +1,37 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["initializer.go"], + importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/initializer", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["initializer_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/initializer", + deps = [ + "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/initializer/initializer.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/initializer/initializer.go new file mode 100644 index 00000000000..398200b92ca --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/initializer/initializer.go @@ -0,0 +1,76 @@ +/* +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 initializer + +import ( + "net/url" + + "k8s.io/apiserver/pkg/admission" + webhookconfig "k8s.io/apiserver/pkg/admission/plugin/webhook/config" +) + +// WantsServiceResolver defines a fuction that accepts a ServiceResolver for +// admission plugins that need to make calls to services. +type WantsServiceResolver interface { + SetServiceResolver(webhookconfig.ServiceResolver) +} + +// ServiceResolver knows how to convert a service reference into an actual +// location. +type ServiceResolver interface { + ResolveEndpoint(namespace, name string) (*url.URL, error) +} + +// WantsAuthenticationInfoResolverWrapper defines a function that wraps the standard AuthenticationInfoResolver +// to allow the apiserver to control what is returned as auth info +type WantsAuthenticationInfoResolverWrapper interface { + SetAuthenticationInfoResolverWrapper(webhookconfig.AuthenticationInfoResolverWrapper) + admission.InitializationValidator +} + +// PluginInitializer is used for initialization of the webhook admission plugin. +type PluginInitializer struct { + serviceResolver webhookconfig.ServiceResolver + authenticationInfoResolverWrapper webhookconfig.AuthenticationInfoResolverWrapper +} + +var _ admission.PluginInitializer = &PluginInitializer{} + +// NewPluginInitializer constructs new instance of PluginInitializer +func NewPluginInitializer( + authenticationInfoResolverWrapper webhookconfig.AuthenticationInfoResolverWrapper, + serviceResolver webhookconfig.ServiceResolver, +) *PluginInitializer { + return &PluginInitializer{ + authenticationInfoResolverWrapper: authenticationInfoResolverWrapper, + serviceResolver: serviceResolver, + } +} + +// Initialize checks the initialization interfaces implemented by each plugin +// and provide the appropriate initialization data +func (i *PluginInitializer) Initialize(plugin admission.Interface) { + if wants, ok := plugin.(WantsServiceResolver); ok { + wants.SetServiceResolver(i.serviceResolver) + } + + if wants, ok := plugin.(WantsAuthenticationInfoResolverWrapper); ok { + if i.authenticationInfoResolverWrapper != nil { + wants.SetAuthenticationInfoResolverWrapper(i.authenticationInfoResolverWrapper) + } + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/initializer/initializer_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/initializer/initializer_test.go new file mode 100644 index 00000000000..553690d9a58 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/initializer/initializer_test.go @@ -0,0 +1,54 @@ +/* +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 initializer + +import ( + "net/url" + "testing" + + "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/admission/plugin/webhook/config" +) + +type doNothingAdmission struct{} + +func (doNothingAdmission) Admit(a admission.Attributes) error { return nil } +func (doNothingAdmission) Handles(o admission.Operation) bool { return false } +func (doNothingAdmission) Validate() error { return nil } + +type fakeServiceResolver struct{} + +func (f *fakeServiceResolver) ResolveEndpoint(namespace, name string) (*url.URL, error) { + return nil, nil +} + +type serviceWanter struct { + doNothingAdmission + got ServiceResolver +} + +func (s *serviceWanter) SetServiceResolver(sr config.ServiceResolver) { s.got = sr } + +func TestWantsServiceResolver(t *testing.T) { + sw := &serviceWanter{} + fsr := &fakeServiceResolver{} + i := NewPluginInitializer(nil, fsr) + i.Initialize(sw) + if got, ok := sw.got.(*fakeServiceResolver); !ok || got != fsr { + t.Errorf("plumbing fail - %v %v#", ok, got) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/BUILD new file mode 100644 index 00000000000..0d46b5d7627 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/BUILD @@ -0,0 +1,71 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "admission.go", + "doc.go", + ], + importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/mutating", + visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/evanphx/json-patch:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/api/admission/v1beta1:go_default_library", + "//vendor/k8s.io/api/admissionregistration/v1beta1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer/json:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/configuration:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/initializer:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/metrics:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/errors:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/request:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/rules:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/versioned:go_default_library", + "//vendor/k8s.io/client-go/informers:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["admission_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/mutating", + deps = [ + "//vendor/k8s.io/api/admission/v1beta1:go_default_library", + "//vendor/k8s.io/api/admissionregistration/v1beta1:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts:go_default_library", + "//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/admission.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/admission.go new file mode 100644 index 00000000000..f944152770d --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/admission.go @@ -0,0 +1,321 @@ +/* +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 mutating delegates admission checks to dynamically configured +// mutating webhooks. +package mutating + +import ( + "context" + "fmt" + "io" + "time" + + jsonpatch "github.com/evanphx/json-patch" + "github.com/golang/glog" + + admissionv1beta1 "k8s.io/api/admission/v1beta1" + "k8s.io/api/admissionregistration/v1beta1" + 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/runtime/serializer/json" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/admission/configuration" + genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer" + admissionmetrics "k8s.io/apiserver/pkg/admission/metrics" + "k8s.io/apiserver/pkg/admission/plugin/webhook/config" + webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors" + "k8s.io/apiserver/pkg/admission/plugin/webhook/namespace" + "k8s.io/apiserver/pkg/admission/plugin/webhook/request" + "k8s.io/apiserver/pkg/admission/plugin/webhook/rules" + "k8s.io/apiserver/pkg/admission/plugin/webhook/versioned" + "k8s.io/client-go/informers" + clientset "k8s.io/client-go/kubernetes" +) + +const ( + // Name of admission plug-in + PluginName = "MutatingAdmissionWebhook" +) + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register(PluginName, func(configFile io.Reader) (admission.Interface, error) { + plugin, err := NewMutatingWebhook(configFile) + if err != nil { + return nil, err + } + + return plugin, nil + }) +} + +// WebhookSource can list dynamic webhook plugins. +type WebhookSource interface { + Run(stopCh <-chan struct{}) + Webhooks() (*v1beta1.MutatingWebhookConfiguration, error) +} + +// NewMutatingWebhook returns a generic admission webhook plugin. +func NewMutatingWebhook(configFile io.Reader) (*MutatingWebhook, error) { + kubeconfigFile, err := config.LoadConfig(configFile) + if err != nil { + return nil, err + } + + cm, err := config.NewClientManager() + if err != nil { + return nil, err + } + authInfoResolver, err := config.NewDefaultAuthenticationInfoResolver(kubeconfigFile) + if err != nil { + return nil, err + } + // Set defaults which may be overridden later. + cm.SetAuthenticationInfoResolver(authInfoResolver) + cm.SetServiceResolver(config.NewDefaultServiceResolver()) + + return &MutatingWebhook{ + Handler: admission.NewHandler( + admission.Connect, + admission.Create, + admission.Delete, + admission.Update, + ), + clientManager: cm, + }, nil +} + +var _ admission.MutationInterface = &MutatingWebhook{} + +// MutatingWebhook is an implementation of admission.Interface. +type MutatingWebhook struct { + *admission.Handler + hookSource WebhookSource + namespaceMatcher namespace.Matcher + clientManager config.ClientManager + convertor versioned.Convertor + jsonSerializer runtime.Serializer +} + +var ( + _ = genericadmissioninit.WantsExternalKubeClientSet(&MutatingWebhook{}) +) + +// TODO find a better way wire this, but keep this pull small for now. +func (a *MutatingWebhook) SetAuthenticationInfoResolverWrapper(wrapper config.AuthenticationInfoResolverWrapper) { + a.clientManager.SetAuthenticationInfoResolverWrapper(wrapper) +} + +// SetServiceResolver sets a service resolver for the webhook admission plugin. +// Passing a nil resolver does not have an effect, instead a default one will be used. +func (a *MutatingWebhook) SetServiceResolver(sr config.ServiceResolver) { + a.clientManager.SetServiceResolver(sr) +} + +// SetScheme sets a serializer(NegotiatedSerializer) which is derived from the scheme +func (a *MutatingWebhook) SetScheme(scheme *runtime.Scheme) { + if scheme != nil { + a.clientManager.SetNegotiatedSerializer(serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{ + Serializer: serializer.NewCodecFactory(scheme).LegacyCodec(admissionv1beta1.SchemeGroupVersion), + })) + a.convertor.Scheme = scheme + a.jsonSerializer = json.NewSerializer(json.DefaultMetaFactory, scheme, scheme, false) + } +} + +// WantsExternalKubeClientSet defines a function which sets external ClientSet for admission plugins that need it +func (a *MutatingWebhook) SetExternalKubeClientSet(client clientset.Interface) { + a.namespaceMatcher.Client = client + a.hookSource = configuration.NewMutatingWebhookConfigurationManager(client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations()) +} + +// SetExternalKubeInformerFactory implements the WantsExternalKubeInformerFactory interface. +func (a *MutatingWebhook) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) { + namespaceInformer := f.Core().V1().Namespaces() + a.namespaceMatcher.NamespaceLister = namespaceInformer.Lister() + a.SetReadyFunc(namespaceInformer.Informer().HasSynced) +} + +// ValidateInitialization implements the InitializationValidator interface. +func (a *MutatingWebhook) ValidateInitialization() error { + if a.hookSource == nil { + return fmt.Errorf("MutatingWebhook admission plugin requires a Kubernetes client to be provided") + } + if a.jsonSerializer == nil { + return fmt.Errorf("MutatingWebhook admission plugin's jsonSerializer is not properly setup") + } + if err := a.namespaceMatcher.Validate(); err != nil { + return fmt.Errorf("MutatingWebhook.namespaceMatcher is not properly setup: %v", err) + } + if err := a.clientManager.Validate(); err != nil { + return fmt.Errorf("MutatingWebhook.clientManager is not properly setup: %v", err) + } + if err := a.convertor.Validate(); err != nil { + return fmt.Errorf("MutatingWebhook.convertor is not properly setup: %v", err) + } + go a.hookSource.Run(wait.NeverStop) + return nil +} + +func (a *MutatingWebhook) loadConfiguration(attr admission.Attributes) (*v1beta1.MutatingWebhookConfiguration, error) { + hookConfig, err := a.hookSource.Webhooks() + // if Webhook configuration is disabled, fail open + if err == configuration.ErrDisabled { + return &v1beta1.MutatingWebhookConfiguration{}, nil + } + if err != nil { + e := apierrors.NewServerTimeout(attr.GetResource().GroupResource(), string(attr.GetOperation()), 1) + e.ErrStatus.Message = fmt.Sprintf("Unable to refresh the Webhook configuration: %v", err) + e.ErrStatus.Reason = "LoadingConfiguration" + e.ErrStatus.Details.Causes = append(e.ErrStatus.Details.Causes, metav1.StatusCause{ + Type: "MutatingWebhookConfigurationFailure", + Message: "An error has occurred while refreshing the MutatingWebhook configuration, no resources can be created/updated/deleted/connected until a refresh succeeds.", + }) + return nil, e + } + return hookConfig, nil +} + +// Admit makes an admission decision based on the request attributes. +func (a *MutatingWebhook) Admit(attr admission.Attributes) error { + hookConfig, err := a.loadConfiguration(attr) + if err != nil { + return err + } + hooks := hookConfig.Webhooks + ctx := context.TODO() + + var relevantHooks []*v1beta1.Webhook + for i := range hooks { + call, err := a.shouldCallHook(&hooks[i], attr) + if err != nil { + return err + } + if call { + relevantHooks = append(relevantHooks, &hooks[i]) + } + } + + if len(relevantHooks) == 0 { + // no matching hooks + return nil + } + + // convert the object to the external version before sending it to the webhook + versionedAttr := versioned.Attributes{ + Attributes: attr, + } + if oldObj := attr.GetOldObject(); oldObj != nil { + out, err := a.convertor.ConvertToGVK(oldObj, attr.GetKind()) + if err != nil { + return apierrors.NewInternalError(err) + } + versionedAttr.OldObject = out + } + if obj := attr.GetObject(); obj != nil { + out, err := a.convertor.ConvertToGVK(obj, attr.GetKind()) + if err != nil { + return apierrors.NewInternalError(err) + } + versionedAttr.Object = out + } + + for _, hook := range relevantHooks { + t := time.Now() + err := a.callAttrMutatingHook(ctx, hook, versionedAttr) + admissionmetrics.Metrics.ObserveWebhook(time.Since(t), err != nil, attr, "admit", hook.Name) + if err == nil { + continue + } + + ignoreClientCallFailures := hook.FailurePolicy != nil && *hook.FailurePolicy == v1beta1.Ignore + if callErr, ok := err.(*webhookerrors.ErrCallingWebhook); ok { + if ignoreClientCallFailures { + glog.Warningf("Failed calling webhook, failing open %v: %v", hook.Name, callErr) + utilruntime.HandleError(callErr) + continue + } + glog.Warningf("Failed calling webhook, failing closed %v: %v", hook.Name, err) + } + return apierrors.NewInternalError(err) + } + + // convert attr.Object to the internal version + return a.convertor.Convert(versionedAttr.Object, attr.GetObject()) +} + +// TODO: factor into a common place along with the validating webhook version. +func (a *MutatingWebhook) shouldCallHook(h *v1beta1.Webhook, attr admission.Attributes) (bool, *apierrors.StatusError) { + var matches bool + for _, r := range h.Rules { + m := rules.Matcher{Rule: r, Attr: attr} + if m.Matches() { + matches = true + break + } + } + if !matches { + return false, nil + } + + return a.namespaceMatcher.MatchNamespaceSelector(h, attr) +} + +// note that callAttrMutatingHook updates attr +func (a *MutatingWebhook) callAttrMutatingHook(ctx context.Context, h *v1beta1.Webhook, attr versioned.Attributes) error { + // Make the webhook request + request := request.CreateAdmissionReview(attr) + client, err := a.clientManager.HookClient(h) + if err != nil { + return &webhookerrors.ErrCallingWebhook{WebhookName: h.Name, Reason: err} + } + response := &admissionv1beta1.AdmissionReview{} + if err := client.Post().Context(ctx).Body(&request).Do().Into(response); err != nil { + return &webhookerrors.ErrCallingWebhook{WebhookName: h.Name, Reason: err} + } + + if !response.Response.Allowed { + return webhookerrors.ToStatusErr(h.Name, response.Response.Result) + } + + patchJS := response.Response.Patch + if len(patchJS) == 0 { + return nil + } + patchObj, err := jsonpatch.DecodePatch(patchJS) + if err != nil { + return apierrors.NewInternalError(err) + } + objJS, err := runtime.Encode(a.jsonSerializer, attr.Object) + if err != nil { + return apierrors.NewInternalError(err) + } + patchedJS, err := patchObj.Apply(objJS) + if err != nil { + return apierrors.NewInternalError(err) + } + // TODO: if we have multiple mutating webhooks, we can remember the json + // instead of encoding and decoding for each one. + if _, _, err := a.jsonSerializer.Decode(patchedJS, nil, attr.Object); err != nil { + return apierrors.NewInternalError(err) + } + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/admission_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/admission_test.go new file mode 100644 index 00000000000..203baa9c3f7 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/admission_test.go @@ -0,0 +1,649 @@ +/* +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 mutating + +import ( + "crypto/tls" + "crypto/x509" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "sync/atomic" + "testing" + + "k8s.io/api/admission/v1beta1" + registrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" + corev1 "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" + "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/admission/plugin/webhook/config" + "k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts" + "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/client-go/rest" +) + +type fakeHookSource struct { + hooks []registrationv1beta1.Webhook + err error +} + +func (f *fakeHookSource) Webhooks() (*registrationv1beta1.MutatingWebhookConfiguration, error) { + if f.err != nil { + return nil, f.err + } + for i, h := range f.hooks { + if h.NamespaceSelector == nil { + f.hooks[i].NamespaceSelector = &metav1.LabelSelector{} + } + } + return ®istrationv1beta1.MutatingWebhookConfiguration{Webhooks: f.hooks}, nil +} + +func (f *fakeHookSource) Run(stopCh <-chan struct{}) {} + +type fakeServiceResolver struct { + base url.URL +} + +func (f fakeServiceResolver) ResolveEndpoint(namespace, name string) (*url.URL, error) { + if namespace == "failResolve" { + return nil, fmt.Errorf("couldn't resolve service location") + } + u := f.base + return &u, nil +} + +type fakeNamespaceLister struct { + namespaces map[string]*corev1.Namespace +} + +func (f fakeNamespaceLister) List(selector labels.Selector) (ret []*corev1.Namespace, err error) { + return nil, nil +} +func (f fakeNamespaceLister) Get(name string) (*corev1.Namespace, error) { + ns, ok := f.namespaces[name] + if ok { + return ns, nil + } + return nil, errors.NewNotFound(corev1.Resource("namespaces"), name) +} + +// ccfgSVC returns a client config using the service reference mechanism. +func ccfgSVC(urlPath string) registrationv1beta1.WebhookClientConfig { + return registrationv1beta1.WebhookClientConfig{ + Service: ®istrationv1beta1.ServiceReference{ + Name: "webhook-test", + Namespace: "default", + Path: &urlPath, + }, + CABundle: testcerts.CACert, + } +} + +type urlConfigGenerator struct { + baseURL *url.URL +} + +// ccfgURL returns a client config using the URL mechanism. +func (c urlConfigGenerator) ccfgURL(urlPath string) registrationv1beta1.WebhookClientConfig { + u2 := *c.baseURL + u2.Path = urlPath + urlString := u2.String() + return registrationv1beta1.WebhookClientConfig{ + URL: &urlString, + CABundle: testcerts.CACert, + } +} + +// TestAdmit tests that MutatingWebhook#Admit works as expected +func TestAdmit(t *testing.T) { + scheme := runtime.NewScheme() + v1beta1.AddToScheme(scheme) + corev1.AddToScheme(scheme) + + testServer := newTestServer(t) + testServer.StartTLS() + defer testServer.Close() + serverURL, err := url.ParseRequestURI(testServer.URL) + if err != nil { + t.Fatalf("this should never happen? %v", err) + } + wh, err := NewMutatingWebhook(nil) + if err != nil { + t.Fatal(err) + } + cm, err := config.NewClientManager() + if err != nil { + t.Fatalf("cannot create client manager: %v", err) + } + cm.SetAuthenticationInfoResolver(newFakeAuthenticationInfoResolver(new(int32))) + cm.SetServiceResolver(fakeServiceResolver{base: *serverURL}) + wh.clientManager = cm + wh.SetScheme(scheme) + if err = wh.clientManager.Validate(); err != nil { + t.Fatal(err) + } + namespace := "webhook-test" + wh.namespaceMatcher.NamespaceLister = fakeNamespaceLister{map[string]*corev1.Namespace{ + namespace: { + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "runlevel": "0", + }, + }, + }, + }, + } + + // Set up a test object for the call + kind := corev1.SchemeGroupVersion.WithKind("Pod") + name := "my-pod" + object := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "pod.name": name, + }, + Name: name, + Namespace: namespace, + }, + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Pod", + }, + } + oldObject := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, + } + operation := admission.Update + resource := corev1.Resource("pods").WithVersion("v1") + subResource := "" + userInfo := user.DefaultInfo{ + Name: "webhook-test", + UID: "webhook-test", + } + + ccfgURL := urlConfigGenerator{serverURL}.ccfgURL + + type test struct { + hookSource fakeHookSource + path string + expectAllow bool + errorContains string + } + + matchEverythingRules := []registrationv1beta1.RuleWithOperations{{ + Operations: []registrationv1beta1.OperationType{registrationv1beta1.OperationAll}, + Rule: registrationv1beta1.Rule{ + APIGroups: []string{"*"}, + APIVersions: []string{"*"}, + Resources: []string{"*/*"}, + }, + }} + + policyFail := registrationv1beta1.Fail + policyIgnore := registrationv1beta1.Ignore + + table := map[string]test{ + "no match": { + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "nomatch", + ClientConfig: ccfgSVC("disallow"), + Rules: []registrationv1beta1.RuleWithOperations{{ + Operations: []registrationv1beta1.OperationType{registrationv1beta1.Create}, + }}, + }}, + }, + expectAllow: true, + }, + "match & allow": { + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "allow", + ClientConfig: ccfgSVC("allow"), + Rules: matchEverythingRules, + }}, + }, + expectAllow: true, + }, + "match & disallow": { + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "disallow", + ClientConfig: ccfgSVC("disallow"), + Rules: matchEverythingRules, + }}, + }, + errorContains: "without explanation", + }, + "match & disallow ii": { + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "disallowReason", + ClientConfig: ccfgSVC("disallowReason"), + Rules: matchEverythingRules, + }}, + }, + errorContains: "you shall not pass", + }, + "match & disallow & but allowed because namespaceSelector exempt the namespace": { + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "disallow", + ClientConfig: ccfgSVC("disallow"), + Rules: newMatchEverythingRules(), + NamespaceSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "runlevel", + Values: []string{"1"}, + Operator: metav1.LabelSelectorOpIn, + }}, + }, + }}, + }, + expectAllow: true, + }, + "match & disallow & but allowed because namespaceSelector exempt the namespace ii": { + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "disallow", + ClientConfig: ccfgSVC("disallow"), + Rules: newMatchEverythingRules(), + NamespaceSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "runlevel", + Values: []string{"0"}, + Operator: metav1.LabelSelectorOpNotIn, + }}, + }, + }}, + }, + expectAllow: true, + }, + "match & fail (but allow because fail open)": { + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "internalErr A", + ClientConfig: ccfgSVC("internalErr"), + Rules: matchEverythingRules, + FailurePolicy: &policyIgnore, + }, { + Name: "internalErr B", + ClientConfig: ccfgSVC("internalErr"), + Rules: matchEverythingRules, + FailurePolicy: &policyIgnore, + }, { + Name: "internalErr C", + ClientConfig: ccfgSVC("internalErr"), + Rules: matchEverythingRules, + FailurePolicy: &policyIgnore, + }}, + }, + expectAllow: true, + }, + "match & fail (but disallow because fail closed on nil)": { + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "internalErr A", + ClientConfig: ccfgSVC("internalErr"), + Rules: matchEverythingRules, + }, { + Name: "internalErr B", + ClientConfig: ccfgSVC("internalErr"), + Rules: matchEverythingRules, + }, { + Name: "internalErr C", + ClientConfig: ccfgSVC("internalErr"), + Rules: matchEverythingRules, + }}, + }, + expectAllow: false, + }, + "match & fail (but fail because fail closed)": { + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "internalErr A", + ClientConfig: ccfgSVC("internalErr"), + Rules: matchEverythingRules, + FailurePolicy: &policyFail, + }, { + Name: "internalErr B", + ClientConfig: ccfgSVC("internalErr"), + Rules: matchEverythingRules, + FailurePolicy: &policyFail, + }, { + Name: "internalErr C", + ClientConfig: ccfgSVC("internalErr"), + Rules: matchEverythingRules, + FailurePolicy: &policyFail, + }}, + }, + expectAllow: false, + }, + "match & allow (url)": { + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "allow", + ClientConfig: ccfgURL("allow"), + Rules: matchEverythingRules, + }}, + }, + expectAllow: true, + }, + "match & disallow (url)": { + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "disallow", + ClientConfig: ccfgURL("disallow"), + Rules: matchEverythingRules, + }}, + }, + errorContains: "without explanation", + }, + // No need to test everything with the url case, since only the + // connection is different. + } + + for name, tt := range table { + if !strings.Contains(name, "no match") { + continue + } + t.Run(name, func(t *testing.T) { + wh.hookSource = &tt.hookSource + err = wh.Admit(admission.NewAttributesRecord(&object, &oldObject, kind, namespace, name, resource, subResource, operation, &userInfo)) + if tt.expectAllow != (err == nil) { + t.Errorf("expected allowed=%v, but got err=%v", tt.expectAllow, err) + } + // ErrWebhookRejected is not an error for our purposes + if tt.errorContains != "" { + if err == nil || !strings.Contains(err.Error(), tt.errorContains) { + t.Errorf(" expected an error saying %q, but got %v", tt.errorContains, err) + } + } + if _, isStatusErr := err.(*apierrors.StatusError); err != nil && !isStatusErr { + t.Errorf("%s: expected a StatusError, got %T", name, err) + } + }) + } +} + +// TestAdmitCachedClient tests that MutatingWebhook#Admit should cache restClient +func TestAdmitCachedClient(t *testing.T) { + scheme := runtime.NewScheme() + v1beta1.AddToScheme(scheme) + corev1.AddToScheme(scheme) + + testServer := newTestServer(t) + testServer.StartTLS() + defer testServer.Close() + serverURL, err := url.ParseRequestURI(testServer.URL) + if err != nil { + t.Fatalf("this should never happen? %v", err) + } + wh, err := NewMutatingWebhook(nil) + if err != nil { + t.Fatal(err) + } + cm, err := config.NewClientManager() + if err != nil { + t.Fatalf("cannot create client manager: %v", err) + } + cm.SetServiceResolver(fakeServiceResolver{base: *serverURL}) + wh.clientManager = cm + wh.SetScheme(scheme) + namespace := "webhook-test" + wh.namespaceMatcher.NamespaceLister = fakeNamespaceLister{map[string]*corev1.Namespace{ + namespace: { + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "runlevel": "0", + }, + }, + }, + }, + } + + // Set up a test object for the call + kind := corev1.SchemeGroupVersion.WithKind("Pod") + name := "my-pod" + object := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "pod.name": name, + }, + Name: name, + Namespace: namespace, + }, + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Pod", + }, + } + oldObject := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, + } + operation := admission.Update + resource := corev1.Resource("pods").WithVersion("v1") + subResource := "" + userInfo := user.DefaultInfo{ + Name: "webhook-test", + UID: "webhook-test", + } + ccfgURL := urlConfigGenerator{serverURL}.ccfgURL + + type test struct { + name string + hookSource fakeHookSource + expectAllow bool + expectCache bool + } + + policyIgnore := registrationv1beta1.Ignore + cases := []test{ + { + name: "cache 1", + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "cache1", + ClientConfig: ccfgSVC("allow"), + Rules: newMatchEverythingRules(), + FailurePolicy: &policyIgnore, + }}, + }, + expectAllow: true, + expectCache: true, + }, + { + name: "cache 2", + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "cache2", + ClientConfig: ccfgSVC("internalErr"), + Rules: newMatchEverythingRules(), + FailurePolicy: &policyIgnore, + }}, + }, + expectAllow: true, + expectCache: true, + }, + { + name: "cache 3", + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "cache3", + ClientConfig: ccfgSVC("allow"), + Rules: newMatchEverythingRules(), + FailurePolicy: &policyIgnore, + }}, + }, + expectAllow: true, + expectCache: false, + }, + { + name: "cache 4", + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "cache4", + ClientConfig: ccfgURL("allow"), + Rules: newMatchEverythingRules(), + FailurePolicy: &policyIgnore, + }}, + }, + expectAllow: true, + expectCache: true, + }, + { + name: "cache 5", + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "cache5", + ClientConfig: ccfgURL("allow"), + Rules: newMatchEverythingRules(), + FailurePolicy: &policyIgnore, + }}, + }, + expectAllow: true, + expectCache: false, + }, + } + + for _, testcase := range cases { + t.Run(testcase.name, func(t *testing.T) { + wh.hookSource = &testcase.hookSource + authInfoResolverCount := new(int32) + r := newFakeAuthenticationInfoResolver(authInfoResolverCount) + wh.clientManager.SetAuthenticationInfoResolver(r) + if err = wh.clientManager.Validate(); err != nil { + t.Fatal(err) + } + + err = wh.Admit(admission.NewAttributesRecord(&object, &oldObject, kind, namespace, testcase.name, resource, subResource, operation, &userInfo)) + if testcase.expectAllow != (err == nil) { + t.Errorf("expected allowed=%v, but got err=%v", testcase.expectAllow, err) + } + + if testcase.expectCache && *authInfoResolverCount != 1 { + t.Errorf("expected cacheclient, but got none") + } + + if !testcase.expectCache && *authInfoResolverCount != 0 { + t.Errorf("expected not cacheclient, but got cache") + } + }) + } + +} + +func newTestServer(t *testing.T) *httptest.Server { + // Create the test webhook server + sCert, err := tls.X509KeyPair(testcerts.ServerCert, testcerts.ServerKey) + if err != nil { + t.Fatal(err) + } + rootCAs := x509.NewCertPool() + rootCAs.AppendCertsFromPEM(testcerts.CACert) + testServer := httptest.NewUnstartedServer(http.HandlerFunc(webhookHandler)) + testServer.TLS = &tls.Config{ + Certificates: []tls.Certificate{sCert}, + ClientCAs: rootCAs, + ClientAuth: tls.RequireAndVerifyClientCert, + } + return testServer +} + +func webhookHandler(w http.ResponseWriter, r *http.Request) { + fmt.Printf("got req: %v\n", r.URL.Path) + switch r.URL.Path { + case "/internalErr": + http.Error(w, "webhook internal server error", http.StatusInternalServerError) + return + case "/invalidReq": + w.WriteHeader(http.StatusSwitchingProtocols) + w.Write([]byte("webhook invalid request")) + return + case "/invalidResp": + w.Header().Set("Content-Type", "application/json") + w.Write([]byte("webhook invalid response")) + case "/disallow": + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(&v1beta1.AdmissionReview{ + Response: &v1beta1.AdmissionResponse{ + Allowed: false, + }, + }) + case "/disallowReason": + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(&v1beta1.AdmissionReview{ + Response: &v1beta1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Message: "you shall not pass", + }, + }, + }) + case "/allow": + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(&v1beta1.AdmissionReview{ + Response: &v1beta1.AdmissionResponse{ + Allowed: true, + }, + }) + default: + http.NotFound(w, r) + } +} + +func newFakeAuthenticationInfoResolver(count *int32) *fakeAuthenticationInfoResolver { + return &fakeAuthenticationInfoResolver{ + restConfig: &rest.Config{ + TLSClientConfig: rest.TLSClientConfig{ + CAData: testcerts.CACert, + CertData: testcerts.ClientCert, + KeyData: testcerts.ClientKey, + }, + }, + cachedCount: count, + } +} + +type fakeAuthenticationInfoResolver struct { + restConfig *rest.Config + cachedCount *int32 +} + +func (c *fakeAuthenticationInfoResolver) ClientConfigFor(server string) (*rest.Config, error) { + atomic.AddInt32(c.cachedCount, 1) + return c.restConfig, nil +} + +func newMatchEverythingRules() []registrationv1beta1.RuleWithOperations { + return []registrationv1beta1.RuleWithOperations{{ + Operations: []registrationv1beta1.OperationType{registrationv1beta1.OperationAll}, + Rule: registrationv1beta1.Rule{ + APIGroups: []string{"*"}, + APIVersions: []string{"*"}, + Resources: []string{"*/*"}, + }, + }} +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/doc.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/doc.go new file mode 100644 index 00000000000..d804aca1cfc --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/doc.go @@ -0,0 +1,19 @@ +/* +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 mutating makes calls to mutating webhooks during the admission +// process. +package mutating // import "k8s.io/apiserver/pkg/admission/plugin/webhook/mutating" diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/BUILD new file mode 100644 index 00000000000..3c25272691b --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/BUILD @@ -0,0 +1,52 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "matcher.go", + ], + importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/namespace", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/api/admissionregistration/v1beta1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/client-go/listers/core/v1:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["matcher_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/namespace", + deps = [ + "//vendor/k8s.io/api/admissionregistration/v1beta1:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/doc.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/doc.go new file mode 100644 index 00000000000..d1a28533836 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/doc.go @@ -0,0 +1,20 @@ +/* +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 namespace defines the utilities that are used by the webhook +// plugin to decide if a webhook should be applied to an object based on its +// namespace. +package namespace // import "k8s.io/apiserver/pkg/admission/plugin/webhook/namespace" diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/matcher.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/matcher.go new file mode 100644 index 00000000000..b9157b9ba76 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/matcher.go @@ -0,0 +1,117 @@ +/* +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 namespace + +import ( + "fmt" + + "k8s.io/api/admissionregistration/v1beta1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apiserver/pkg/admission" + clientset "k8s.io/client-go/kubernetes" + corelisters "k8s.io/client-go/listers/core/v1" +) + +// Matcher decides if a request is exempted by the NamespaceSelector of a +// webhook configuration. +type Matcher struct { + NamespaceLister corelisters.NamespaceLister + Client clientset.Interface +} + +// Validate checks if the Matcher has a NamespaceLister and Client. +func (m *Matcher) Validate() error { + var errs []error + if m.NamespaceLister == nil { + errs = append(errs, fmt.Errorf("the namespace matcher requires a namespaceLister")) + } + if m.Client == nil { + errs = append(errs, fmt.Errorf("the namespace matcher requires a namespaceLister")) + } + return utilerrors.NewAggregate(errs) +} + +// GetNamespaceLabels gets the labels of the namespace related to the attr. +func (m *Matcher) GetNamespaceLabels(attr admission.Attributes) (map[string]string, error) { + // If the request itself is creating or updating a namespace, then get the + // labels from attr.Object, because namespaceLister doesn't have the latest + // namespace yet. + // + // However, if the request is deleting a namespace, then get the label from + // the namespace in the namespaceLister, because a delete request is not + // going to change the object, and attr.Object will be a DeleteOptions + // rather than a namespace object. + if attr.GetResource().Resource == "namespaces" && + len(attr.GetSubresource()) == 0 && + (attr.GetOperation() == admission.Create || attr.GetOperation() == admission.Update) { + accessor, err := meta.Accessor(attr.GetObject()) + if err != nil { + return nil, err + } + return accessor.GetLabels(), nil + } + + namespaceName := attr.GetNamespace() + namespace, err := m.NamespaceLister.Get(namespaceName) + if err != nil && !apierrors.IsNotFound(err) { + return nil, err + } + if apierrors.IsNotFound(err) { + // in case of latency in our caches, make a call direct to storage to verify that it truly exists or not + namespace, err = m.Client.CoreV1().Namespaces().Get(namespaceName, metav1.GetOptions{}) + if err != nil { + return nil, err + } + } + return namespace.Labels, nil +} + +// MatchNamespaceSelector decideds whether the request matches the +// namespaceSelctor of the webhook. Only when they match, the webhook is called. +func (m *Matcher) MatchNamespaceSelector(h *v1beta1.Webhook, attr admission.Attributes) (bool, *apierrors.StatusError) { + namespaceName := attr.GetNamespace() + if len(namespaceName) == 0 && attr.GetResource().Resource != "namespaces" { + // If the request is about a cluster scoped resource, and it is not a + // namespace, it is exempted from all webhooks for now. + // TODO: figure out a way selective exempt cluster scoped resources. + // Also update the comment in types.go + return false, nil + } + namespaceLabels, err := m.GetNamespaceLabels(attr) + // this means the namespace is not found, for backwards compatibility, + // return a 404 + if apierrors.IsNotFound(err) { + status, ok := err.(apierrors.APIStatus) + if !ok { + return false, apierrors.NewInternalError(err) + } + return false, &apierrors.StatusError{status.Status()} + } + if err != nil { + return false, apierrors.NewInternalError(err) + } + // TODO: adding an LRU cache to cache the translation + selector, err := metav1.LabelSelectorAsSelector(h.NamespaceSelector) + if err != nil { + return false, apierrors.NewInternalError(err) + } + return selector.Matches(labels.Set(namespaceLabels)), nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/matcher_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/matcher_test.go new file mode 100644 index 00000000000..e8f77ba903e --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/matcher_test.go @@ -0,0 +1,129 @@ +/* +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 namespace + +import ( + "reflect" + "testing" + + registrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" + corev1 "k8s.io/api/core/v1" + "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/apiserver/pkg/admission" +) + +type fakeNamespaceLister struct { + namespaces map[string]*corev1.Namespace +} + +func (f fakeNamespaceLister) List(selector labels.Selector) (ret []*corev1.Namespace, err error) { + return nil, nil +} +func (f fakeNamespaceLister) Get(name string) (*corev1.Namespace, error) { + ns, ok := f.namespaces[name] + if ok { + return ns, nil + } + return nil, errors.NewNotFound(corev1.Resource("namespaces"), name) +} + +func TestGetNamespaceLabels(t *testing.T) { + namespace1Labels := map[string]string{ + "runlevel": "1", + } + namespace1 := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "1", + Labels: namespace1Labels, + }, + } + namespace2Labels := map[string]string{ + "runlevel": "2", + } + namespace2 := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "2", + Labels: namespace2Labels, + }, + } + namespaceLister := fakeNamespaceLister{map[string]*corev1.Namespace{ + "1": &namespace1, + }, + } + + tests := []struct { + name string + attr admission.Attributes + expectedLabels map[string]string + }{ + { + name: "request is for creating namespace, the labels should be from the object itself", + attr: admission.NewAttributesRecord(&namespace2, nil, schema.GroupVersionKind{}, "", namespace2.Name, schema.GroupVersionResource{Resource: "namespaces"}, "", admission.Create, nil), + expectedLabels: namespace2Labels, + }, + { + name: "request is for updating namespace, the labels should be from the new object", + attr: admission.NewAttributesRecord(&namespace2, nil, schema.GroupVersionKind{}, namespace2.Name, namespace2.Name, schema.GroupVersionResource{Resource: "namespaces"}, "", admission.Update, nil), + expectedLabels: namespace2Labels, + }, + { + name: "request is for deleting namespace, the labels should be from the cache", + attr: admission.NewAttributesRecord(&namespace2, nil, schema.GroupVersionKind{}, namespace1.Name, namespace1.Name, schema.GroupVersionResource{Resource: "namespaces"}, "", admission.Delete, nil), + expectedLabels: namespace1Labels, + }, + { + name: "request is for namespace/finalizer", + attr: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, namespace1.Name, "mock-name", schema.GroupVersionResource{Resource: "namespaces"}, "finalizers", admission.Create, nil), + expectedLabels: namespace1Labels, + }, + { + name: "request is for pod", + attr: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, namespace1.Name, "mock-name", schema.GroupVersionResource{Resource: "pods"}, "", admission.Create, nil), + expectedLabels: namespace1Labels, + }, + } + matcher := Matcher{ + NamespaceLister: namespaceLister, + } + for _, tt := range tests { + actualLabels, err := matcher.GetNamespaceLabels(tt.attr) + if err != nil { + t.Error(err) + } + if !reflect.DeepEqual(actualLabels, tt.expectedLabels) { + t.Errorf("expected labels to be %#v, got %#v", tt.expectedLabels, actualLabels) + } + } +} + +func TestExemptClusterScopedResource(t *testing.T) { + hook := ®istrationv1beta1.Webhook{ + NamespaceSelector: &metav1.LabelSelector{}, + } + attr := admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, "", "mock-name", schema.GroupVersionResource{Version: "v1", Resource: "nodes"}, "", admission.Create, nil) + matcher := Matcher{} + matches, err := matcher.MatchNamespaceSelector(hook, attr) + if err != nil { + t.Fatal(err) + } + if matches { + t.Errorf("cluster scoped resources (but not a namespace) should be exempted from all webhooks") + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request/BUILD new file mode 100644 index 00000000000..036015274f0 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request/BUILD @@ -0,0 +1,33 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "admissionreview.go", + "doc.go", + ], + importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/request", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/api/admission/v1beta1:go_default_library", + "//vendor/k8s.io/api/authentication/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/plugin/pkg/admission/webhook/admissionreview.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request/admissionreview.go similarity index 75% rename from plugin/pkg/admission/webhook/admissionreview.go rename to staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request/admissionreview.go index c9e139c713a..5b8a41db29e 100644 --- a/plugin/pkg/admission/webhook/admissionreview.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request/admissionreview.go @@ -14,19 +14,19 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package webhook delegates admission checks to dynamically configured webhooks. -package webhook +package request import ( - admissionv1alpha1 "k8s.io/api/admission/v1alpha1" + admissionv1beta1 "k8s.io/api/admission/v1beta1" authenticationv1 "k8s.io/api/authentication/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/uuid" "k8s.io/apiserver/pkg/admission" ) -// createAdmissionReview creates an AdmissionReview for the provided admission.Attributes -func createAdmissionReview(attr admission.Attributes) admissionv1alpha1.AdmissionReview { +// CreateAdmissionReview creates an AdmissionReview for the provided admission.Attributes +func CreateAdmissionReview(attr admission.Attributes) admissionv1beta1.AdmissionReview { gvk := attr.GetKind() gvr := attr.GetResource() aUserInfo := attr.GetUserInfo() @@ -42,29 +42,30 @@ func createAdmissionReview(attr admission.Attributes) admissionv1alpha1.Admissio userInfo.Extra[key] = authenticationv1.ExtraValue(val) } - return admissionv1alpha1.AdmissionReview{ - Spec: admissionv1alpha1.AdmissionReviewSpec{ - Name: attr.GetName(), - Namespace: attr.GetNamespace(), + return admissionv1beta1.AdmissionReview{ + Request: &admissionv1beta1.AdmissionRequest{ + UID: uuid.NewUUID(), + Kind: metav1.GroupVersionKind{ + Group: gvk.Group, + Kind: gvk.Kind, + Version: gvk.Version, + }, Resource: metav1.GroupVersionResource{ Group: gvr.Group, Resource: gvr.Resource, Version: gvr.Version, }, SubResource: attr.GetSubresource(), - Operation: admissionv1alpha1.Operation(attr.GetOperation()), + Name: attr.GetName(), + Namespace: attr.GetNamespace(), + Operation: admissionv1beta1.Operation(attr.GetOperation()), + UserInfo: userInfo, Object: runtime.RawExtension{ Object: attr.GetObject(), }, OldObject: runtime.RawExtension{ Object: attr.GetOldObject(), }, - Kind: metav1.GroupVersionKind{ - Group: gvk.Group, - Kind: gvk.Kind, - Version: gvk.Version, - }, - UserInfo: userInfo, }, } } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request/doc.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request/doc.go new file mode 100644 index 00000000000..fbacf337178 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request/doc.go @@ -0,0 +1,18 @@ +/* +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 request creates admissionReview request based on admission attributes. +package request // import "k8s.io/apiserver/pkg/admission/plugin/webhook/request" diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/rules/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/rules/BUILD new file mode 100644 index 00000000000..4e62c6ac0f2 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/rules/BUILD @@ -0,0 +1,38 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["rules.go"], + importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/rules", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/api/admissionregistration/v1beta1:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["rules_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/rules", + deps = [ + "//vendor/k8s.io/api/admissionregistration/v1beta1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/plugin/pkg/admission/webhook/rules.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/rules/rules.go similarity index 79% rename from plugin/pkg/admission/webhook/rules.go rename to staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/rules/rules.go index 6fc734e8de7..eb99357569d 100644 --- a/plugin/pkg/admission/webhook/rules.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/rules/rules.go @@ -14,22 +14,23 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package webhook checks a webhook for configured operation admission -package webhook +package rules import ( "strings" - "k8s.io/api/admissionregistration/v1alpha1" + "k8s.io/api/admissionregistration/v1beta1" "k8s.io/apiserver/pkg/admission" ) -type RuleMatcher struct { - Rule v1alpha1.RuleWithOperations +// Matcher determines if the Attr matches the Rule. +type Matcher struct { + Rule v1beta1.RuleWithOperations Attr admission.Attributes } -func (r *RuleMatcher) Matches() bool { +// Matches returns if the Attr matches the Rule. +func (r *Matcher) Matches() bool { return r.operation() && r.group() && r.version() && @@ -49,23 +50,23 @@ func exactOrWildcard(items []string, requested string) bool { return false } -func (r *RuleMatcher) group() bool { +func (r *Matcher) group() bool { return exactOrWildcard(r.Rule.APIGroups, r.Attr.GetResource().Group) } -func (r *RuleMatcher) version() bool { +func (r *Matcher) version() bool { return exactOrWildcard(r.Rule.APIVersions, r.Attr.GetResource().Version) } -func (r *RuleMatcher) operation() bool { +func (r *Matcher) operation() bool { attrOp := r.Attr.GetOperation() for _, op := range r.Rule.Operations { - if op == v1alpha1.OperationAll { + if op == v1beta1.OperationAll { return true } // The constants are the same such that this is a valid cast (and this // is tested). - if op == v1alpha1.OperationType(attrOp) { + if op == v1beta1.OperationType(attrOp) { return true } } @@ -80,7 +81,7 @@ func splitResource(resSub string) (res, sub string) { return parts[0], "" } -func (r *RuleMatcher) resource() bool { +func (r *Matcher) resource() bool { opRes, opSub := r.Attr.GetResource().Resource, r.Attr.GetSubresource() for _, res := range r.Rule.Resources { res, sub := splitResource(res) diff --git a/plugin/pkg/admission/webhook/rules_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/rules/rules_test.go similarity index 95% rename from plugin/pkg/admission/webhook/rules_test.go rename to staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/rules/rules_test.go index 0e464aa202d..3418a17086a 100644 --- a/plugin/pkg/admission/webhook/rules_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/rules/rules_test.go @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package webhook +package rules import ( "testing" - adreg "k8s.io/api/admissionregistration/v1alpha1" + adreg "k8s.io/api/admissionregistration/v1beta1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/admission" ) @@ -77,13 +77,13 @@ func TestGroup(t *testing.T) { for name, tt := range table { for _, m := range tt.match { - r := RuleMatcher{tt.rule, m} + r := Matcher{tt.rule, m} if !r.group() { t.Errorf("%v: expected match %#v", name, m) } } for _, m := range tt.noMatch { - r := RuleMatcher{tt.rule, m} + r := Matcher{tt.rule, m} if r.group() { t.Errorf("%v: expected no match %#v", name, m) } @@ -121,13 +121,13 @@ func TestVersion(t *testing.T) { } for name, tt := range table { for _, m := range tt.match { - r := RuleMatcher{tt.rule, m} + r := Matcher{tt.rule, m} if !r.version() { t.Errorf("%v: expected match %#v", name, m) } } for _, m := range tt.noMatch { - r := RuleMatcher{tt.rule, m} + r := Matcher{tt.rule, m} if r.version() { t.Errorf("%v: expected no match %#v", name, m) } @@ -204,13 +204,13 @@ func TestOperation(t *testing.T) { } for name, tt := range table { for _, m := range tt.match { - r := RuleMatcher{tt.rule, m} + r := Matcher{tt.rule, m} if !r.operation() { t.Errorf("%v: expected match %#v", name, m) } } for _, m := range tt.noMatch { - r := RuleMatcher{tt.rule, m} + r := Matcher{tt.rule, m} if r.operation() { t.Errorf("%v: expected no match %#v", name, m) } @@ -285,13 +285,13 @@ func TestResource(t *testing.T) { } for name, tt := range table { for _, m := range tt.match { - r := RuleMatcher{tt.rule, m} + r := Matcher{tt.rule, m} if !r.resource() { t.Errorf("%v: expected match %#v", name, m) } } for _, m := range tt.noMatch { - r := RuleMatcher{tt.rule, m} + r := Matcher{tt.rule, m} if r.resource() { t.Errorf("%v: expected no match %#v", name, m) } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts/BUILD new file mode 100644 index 00000000000..392232d3354 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts/BUILD @@ -0,0 +1,25 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "certs.go", + "doc.go", + ], + importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts", + visibility = ["//visibility:public"], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts/certs.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts/certs.go new file mode 100644 index 00000000000..bb7b81f6ae7 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts/certs.go @@ -0,0 +1,216 @@ +/* +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. +*/ + +// This file was generated using openssl by the gencerts.sh script +// and holds raw certificates for the webhook tests. + +package testcerts + +var CAKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEArqnW4K+UsmPzbSB7JYhN0HNsJNItjw/87SJxIjGqUttC+2ts ++Y+BddcQgO0EfzP68QJ6+H7itmzdqWPTfoZJiuy+twXxgFwMH2WCKB6I5CgnVHcD +acmdP7vOgr1GcyqIV16NWEnt6hxRNfVnerduLWWfVr0/wGY3ajw06FHEx7oL1Jfx +1lo4UPVdp39Pt5rOww66FP8etRaEhy2AHEtfHS4L4GxcJL/0n3w8UIfMqYjTKW9l +zuKXUurz9HEJ3N+JMItolf+3ohMp/xciiSBvHzFbfu/Zv2nXqWGcxqOmnM1L8ZLw +Q4c3ZRQ/n9tIacEZyy69VASAQcIjMdwIHcWZLwIDAQABAoIBADybeJGMu6dPIYfr +bm/upTnA43S/bcmnvZc3jVRVMYoAxXRiqXTLhBu03egu1pGhIuGAf9U8ikTM7/m4 +RwovZNONJPxzVoK47gfy/EAZoFyzRjp79bY+nI8iBx28ufZ6esb+a0OIm8LRwqhb +mGWvws6D5c9+aeHEVlRJwf4faY34ASEbw19QhOLfPCGp0wOy4MX3aIMaCfZ+iHYc +GAVaf44rWmTYKHEkLMABky9jGIXXJXROY/ggKWC1zXdPVChO40ECd1b2XGry6Ta/ +j+quFXDgI7b/ju/7jLnDCQjuC5G6E7X3n8KLZtzJrReiwpyeFo86GWc5E99umOyB +tPjwNikCgYEA1mbNAVg3mBOFcEAN0j5QKEy1hv16nVlGRwBye/0nG3mUHCs+UzC2 +fQyDfPcfXZERyDb4QJCJ4Y6VSVoo3Jn1okFPbz7y2eKdjo1N65T9HrK7G+QZXinH +/72LktfAWphdz2JKuSnrlJS8YupSx7pS+lpz+W+rcDpdYVJXoD6L6SMCgYEA0I1E +4h3MH5O46GJvMoGW4PH5FIi5nbWj69Y4/nRJCACNlLPJ+k3lcf0OTYcy9pllv5Ya +EV5n0qHAH7ACudKoB6YqvDsrZxfv8tlmWLBTFp5QQpBdlMWjgGSbJLbkxvt3rUfF +x/eQebvzSqp69R0/XqJ9fxWXvdtZoZYXNJxVPoUCgYAgA7W077FNegzA2C+4Jync ++qdYgt0eRchircRqk0CVr6/YDPT/gxSc05OGw3fhhtn65YpoSaztC1drXpUfa7Xs +BoiP+fxVYKtaL+tktBifztx1q7fGAcMlgu4mfSTx4jKP1wOFZqcQxqzisE6wGDhv +vbX3lx8oYO60q5D+EpjdtQKBgDM/A3YsrEP2ILG5vmlCvrh3vST2k+XVBHqnIUol +eOymdiPcKf1/tqnT7PfQCQ3fk8kIMU+jSw/O/07KCWFwCioXAtlOENQ8ZZHfKe8R +JNmh/UbeAqDUD+E014qmBoF+uWGzCT6h7rZ7IMVwLtacYT33366it67Hf7bdEsay +w5+hAoGABSgjlf9WsC7WzY6sZwZG25aBMGFw6vr3jawLiuNk3Hr+pGV/H7PEzSh+ +vBpvC0Vkp5Dg32asmME40LbYpMu2BV4E1wK17i+DZVUMezNO0mABykWecyPYdmxL +bJtLu4yaP84W433T5E6G7Im+x+KjXI7TRzpQZFQnVadmmpuurUY= +-----END RSA PRIVATE KEY-----`) + +var CACert = []byte(`-----BEGIN CERTIFICATE----- +MIIDPTCCAiWgAwIBAgIJALTyMgMR6YygMA0GCSqGSIb3DQEBCwUAMDQxMjAwBgNV +BAMMKWdlbmVyaWNfd2ViaG9va19hZG1pc3Npb25fcGx1Z2luX3Rlc3RzX2NhMCAX +DTE3MTExNjAwMDUzOVoYDzIyOTEwOTAxMDAwNTM5WjA0MTIwMAYDVQQDDClnZW5l +cmljX3dlYmhvb2tfYWRtaXNzaW9uX3BsdWdpbl90ZXN0c19jYTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAK6p1uCvlLJj820geyWITdBzbCTSLY8P/O0i +cSIxqlLbQvtrbPmPgXXXEIDtBH8z+vECevh+4rZs3alj036GSYrsvrcF8YBcDB9l +gigeiOQoJ1R3A2nJnT+7zoK9RnMqiFdejVhJ7eocUTX1Z3q3bi1ln1a9P8BmN2o8 +NOhRxMe6C9SX8dZaOFD1Xad/T7eazsMOuhT/HrUWhIctgBxLXx0uC+BsXCS/9J98 +PFCHzKmI0ylvZc7il1Lq8/RxCdzfiTCLaJX/t6ITKf8XIokgbx8xW37v2b9p16lh +nMajppzNS/GS8EOHN2UUP5/bSGnBGcsuvVQEgEHCIzHcCB3FmS8CAwEAAaNQME4w +HQYDVR0OBBYEFBsZKbynr9Iix+ud0FxQvMVIPZqOMB8GA1UdIwQYMBaAFBsZKbyn +r9Iix+ud0FxQvMVIPZqOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB +AJZXfcHyAsq5qr1UqhutrNlsW2u7kkAc+Ql5wZEdXIyjKKC+kOllWqKo5IPtmMIi +R5VCm1g3iFCUV6FdXNtfF7tWZqaHV58nkJYlDc2yZxQzaWQeu8U92w+Qr5H1ansL +FOGS6A4+rMj2EDEt+lCmsz+l5UD7nrhnyGMzeumASQ6cXPV1uB2LTc4IzsOYFKs9 +nt9SDH7tF+0bQwZ1YUrfMYJpNp6ETjpPKJVhq8/FGqwT+9egFbgjAhrEpPccFkXo +D7NLhM1JHUiqNQStDq9mDKLHKmp++UScrNc66b6egN1sIPBHyLu8ApzcF2YHuEYC +RGyWZ0sJtjzWjK7IU9RdmLI= +-----END CERTIFICATE-----`) + +var BadCAKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEArgYoZAUDj1/dQ2G+dMib+b1m39ACK6wBt4zDjl8x5iUZZjDy +O1l/Hj6n3NUHphFsYZ5070ds8GwXANvYGFi3F6GFiRW/R5lcChmhxhY5JJtWquDp +amonNEjiAuiFf6u6CKeBvm8CgcH/Cbzc4XP1oJZiSaGOpmqGiHlLeL25y6ZR3X7D +jOPalSi9WHQN0m9DS7EMvyalySAwzJKlcOpSeBPdMTp1Ay1HShJiiNj8sdCRLpq7 +rwHypKS6mVNItsAnx+jc40d162dmFQg8FBm3M6d5QTrJBAfuvoLrlNCtssl11hOi +XDYJshTBVI9HAR/lzQiF+coZOHJwYuYXp7xYOwIDAQABAoIBAQChzSvkwxyqQ9Gw +AsNYReVv8IAj/HzoKgd2p7RzPWNhvoC9GSk/sViVwF/G3XM9HtoMcY37pAdQCs/g +hoeHK4UgvZcw/D1azuZapbZaPPNoa93LB08/F+/XlyQ83ACz0fEodsYVT5WfG8aL +QUSFgpGQfAJqv4GojUcEwPJBEvYat8I028fzYMlLJ9m45pQFzFsGKU36vs6esPkL +MKbVO1qI6CEVDtnLkIo2bE5vpo8w5C22HseFO+E+1VNaHIRK5TBBGGWVJVfUZ3bq +7LWngkaN9gspCauKkTozj2bl611lFRI7wbA32WV1eIYgNT+8jTESo/oHpsnpRSI7 +4UMp5GExAoGBAN7wkIS4zWGF2hijsorcPLBx3YOsBxW26Qhl/ck6a3lVGZHrDijc +u8hDhOWNDxSSqUwQiL7UAVcE7npw6XpZ37Obc//t+Hm/gGUOIGlB/Pl3g4h7pF7T +s2pXIMKvF0dfQGpmqgCytUz7Oho4LLbkywky7jykMc8IlVuTZEdKbdZHAoGBAMfU +nR+79gT8yIBruEX3VI71Vbce0Pn+3+G+PO12uUN6XqMb9YA+f5aS6AwP5EupERwn +YvkMkCNSYYkV+GU5b+N9Pn7xt33dnEqhrGPUrOLoIAl6qJ7jc78GZy67SCuIKrZZ +AN5qFQlRCENv28C+0Ne6rMX+8/JL1Mxo+0J/6QRtAoGAbNhs5q/Hbm7IfbEmkY9X +fhoJuai6yMpF2hjZoG6KXHHFCy4E+sRSVkNI1j5Zd4TnbUDBUtH1WYQJ3vPTui24 +/1rNds27u81YpX4RKvLRzQahzHf5V2bquOeTEhokNm915rz7EV4vEEe0JWr5wc3Q +p0wbbrYHr3oUWeKLWhcnqy8CgYEAh9XiHMFDIe7HSGxw7baLl0Xzxy++dEGp5CTR ++8VZeCIFlLCbuFpDlpI0BIcE891wEQhBAfRlQm1seagimoRpp2Tqh5Y92eQ7qout +yIq4HuIVbPwhBSit9Gsg1qZeD6FXD27+5TGNLTEVAepWofXTtuFhMpH1N34OoAi4 +y2Jxfh0CgYB4IrPUeBAZKiC6Lo6nwxo0rrsiHLJwXJojLwsUt0zG5vh3mI25KAB1 +a3ARRbiRKU/IX5I9ToclJ3h0a1nVr1kzV/E5f+5FgQ9swkTNEbM8gBsc5X9ZayjD +Hfv6+p7TH3bReXDKtpOUgso0dIy2anN6Ppu1wODtrFnUOJ9wkO4OSg== +-----END RSA PRIVATE KEY-----`) + +var BadCACert = []byte(`-----BEGIN CERTIFICATE----- +MIIDPTCCAiWgAwIBAgIJAIaoBDrksTyaMA0GCSqGSIb3DQEBCwUAMDQxMjAwBgNV +BAMMKWdlbmVyaWNfd2ViaG9va19hZG1pc3Npb25fcGx1Z2luX3Rlc3RzX2NhMCAX +DTE3MTExNjAwMDUzOVoYDzIyOTEwOTAxMDAwNTM5WjA0MTIwMAYDVQQDDClnZW5l +cmljX3dlYmhvb2tfYWRtaXNzaW9uX3BsdWdpbl90ZXN0c19jYTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAK4GKGQFA49f3UNhvnTIm/m9Zt/QAiusAbeM +w45fMeYlGWYw8jtZfx4+p9zVB6YRbGGedO9HbPBsFwDb2BhYtxehhYkVv0eZXAoZ +ocYWOSSbVqrg6WpqJzRI4gLohX+rugingb5vAoHB/wm83OFz9aCWYkmhjqZqhoh5 +S3i9ucumUd1+w4zj2pUovVh0DdJvQ0uxDL8mpckgMMySpXDqUngT3TE6dQMtR0oS +YojY/LHQkS6au68B8qSkuplTSLbAJ8fo3ONHdetnZhUIPBQZtzOneUE6yQQH7r6C +65TQrbLJddYTolw2CbIUwVSPRwEf5c0IhfnKGThycGLmF6e8WDsCAwEAAaNQME4w +HQYDVR0OBBYEFFFthspVCOb5fSkQ2BFCykech3RVMB8GA1UdIwQYMBaAFFFthspV +COb5fSkQ2BFCykech3RVMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB +AEgGbcx1qhdi4lFNC0YRHJxjn3JPW6tr4qgDiusqMj9TF9/RohKOvLblq2kSB0x3 +pyDMkVv2rd5U4qtKruEQ1OgY3cB7hy6mt/ZhldF540Lli8j9N63LMRXwIu068j2W +WSiWV416LOZEcuid7mZjAsbG4xvaDg/yW1RBpA3XnwMSmr7Y+T6XkjzgT3WWiwOf +4ANc3ecsl53x/beb9YF+TjqmjmtGSgUW78UTAsGFFKmjJ/cStQUaMCEvS9Gun7hH +eLarZIVV5Ia/FziGHoi7Q44C66pXD437xmkR1ueExoKwXbBt4c5GeH1rJjUVnlyk +pMokZBC57nXx8krZVEu1SRA= +-----END CERTIFICATE-----`) + +var ServerKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA13f50PPWuR/InxLIoJjHdNSG+jVUd25CY7ZL2J023X2BAY+1 +M6jkLR6C2nSFZnn58ubiB74/d1g/Fg1Twd419iR615A013f+qOoyFx3LFHxU1S6e +v22fgJ6ntK/+4QD5MwNgOwD8k1jN2WxHqNWn16IF4Tidbv8M9A35YHAdtYDYaOJC +kzjVztzRw1y6bKRakpMXxHylQyWmAKDJ2GSbRTbGtjr7Ji54WBfG43k94tO5X8K4 +VGbz/uxrKe1IFMHNOlrjR438dbOXusksx9EIqDA9a42J3qjr5NKSqzCIbgBFl6qu +45V3A7cdRI/sJ2G1aqlWIXh2fAQiaFQAEBrPfwIDAQABAoIBAAZbxgWCjJ2d8H+x +QDZtC8XI18redAWqPU9P++ECkrHqmDoBkalanJEwS1BDDATAKL4gTh9IX/sXoZT3 +A7e+5PzEitN9r/GD2wIFF0FTYcDTAnXgEFM52vEivXQ5lV3yd2gn+1kCaHG4typp +ZZv34iIc5+uDjjHOWQWCvA86f8XxX5EfYH+GkjfixTtN2xhWWlfi9vzYeESS4Jbt +tqfH0iEaZ1Bm/qvb8vFgKiuSTOoSpaf+ojAdtPtXDjf1bBtQQG+RSQkP59O/taLM +FCVuRrU8EtdB0+9anwmAP+O2UqjL5izA578lQtdIh13jHtGEgOcnfGNUphK11y9r +Mg5V28ECgYEA9fwI6Xy1Rb9b9irp4bU5Ec99QXa4x2bxld5cDdNOZWJQu9OnaIbg +kw/1SyUkZZCGMmibM/BiWGKWoDf8E+rn/ujGOtd70sR9U0A94XMPqEv7iHxhpZmD +rZuSz4/snYbOWCZQYXFoD/nqOwE7Atnz7yh+Jti0qxBQ9bmkb9o0QW8CgYEA4D3d +okzodg5QQ1y9L0J6jIC6YysoDedveYZMd4Un9bKlZEJev4OwiT4xXmSGBYq/7dzo +OJOvN6qgPfibr27mSB8NkAk6jL/VdJf3thWxNYmjF4E3paLJ24X31aSipN1Ta6K3 +KKQUQRvixVoI1q+8WHAubBDEqvFnNYRHD+AjKvECgYBkekjhpvEcxme4DBtw+OeQ +4OJXJTmhKemwwB12AERboWc88d3GEqIVMEWQJmHRotFOMfCDrMNfOxYv5+5t7FxL +gaXHT1Hi7CQNJ4afWrKgmjjqrXPtguGIvq2fXzjVt8T9uNjIlNxe+kS1SXFjXsgH +ftDY6VgTMB0B4ozKq6UAvQKBgQDER8K5buJHe+3rmMCMHn+Qfpkndr4ftYXQ9Kn4 +MFiy6sV0hdfTgRzEdOjXu9vH/BRVy3iFFVhYvIR42iTEIal2VaAUhM94Je5cmSyd +eE1eFHTqfRPNazmPaqttmSc4cfa0D4CNFVoZR6RupIl6Cect7jvkIaVUD+wMXxWo +osOFsQKBgDLwVhZWoQ13RV/jfQxS3veBUnHJwQJ7gKlL1XZ16mpfEOOVnJF7Es8j +TIIXXYhgSy/XshUbsgXQ+YGliye/rXSCTXHBXvWShOqxEMgeMYMRkcm8ZLp/DH7C +kC2pemkLPUJqgSh1PASGcJbDJIvFGUfP69tUCYpHpk3nHzexuAg3 +-----END RSA PRIVATE KEY-----`) + +var ServerCert = []byte(`-----BEGIN CERTIFICATE----- +MIIDQDCCAiigAwIBAgIJANWw74P5KJk2MA0GCSqGSIb3DQEBCwUAMDQxMjAwBgNV +BAMMKWdlbmVyaWNfd2ViaG9va19hZG1pc3Npb25fcGx1Z2luX3Rlc3RzX2NhMCAX +DTE3MTExNjAwMDUzOVoYDzIyOTEwOTAxMDAwNTM5WjAjMSEwHwYDVQQDExh3ZWJo +b29rLXRlc3QuZGVmYXVsdC5zdmMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQDXd/nQ89a5H8ifEsigmMd01Ib6NVR3bkJjtkvYnTbdfYEBj7UzqOQtHoLa +dIVmefny5uIHvj93WD8WDVPB3jX2JHrXkDTXd/6o6jIXHcsUfFTVLp6/bZ+Anqe0 +r/7hAPkzA2A7APyTWM3ZbEeo1afXogXhOJ1u/wz0DflgcB21gNho4kKTONXO3NHD +XLpspFqSkxfEfKVDJaYAoMnYZJtFNsa2OvsmLnhYF8bjeT3i07lfwrhUZvP+7Gsp +7UgUwc06WuNHjfx1s5e6ySzH0QioMD1rjYneqOvk0pKrMIhuAEWXqq7jlXcDtx1E +j+wnYbVqqVYheHZ8BCJoVAAQGs9/AgMBAAGjZDBiMAkGA1UdEwQCMAAwCwYDVR0P +BAQDAgXgMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATApBgNVHREEIjAg +hwR/AAABghh3ZWJob29rLXRlc3QuZGVmYXVsdC5zdmMwDQYJKoZIhvcNAQELBQAD +ggEBAD/GKSPNyQuAOw/jsYZesb+RMedbkzs18sSwlxAJQMUrrXwlVdHrA8q5WhE6 +ABLqU1b8lQ8AWun07R8k5tqTmNvCARrAPRUqls/ryER+3Y9YEcxEaTc3jKNZFLbc +T6YtcnkdhxsiO136wtiuatpYL91RgCmuSpR8+7jEHhuFU01iaASu7ypFrUzrKHTF +bKwiLRQi1cMzVcLErq5CDEKiKhUkoDucyARFszrGt9vNIl/YCcBOkcNvM3c05Hn3 +M++C29JwS3Hwbubg6WO3wjFjoEhpCwU6qRYUz3MRp4tHO4kxKXx+oQnUiFnR7vW0 +YkNtGc1RUDHwecCTFpJtPb7Yu/E= +-----END CERTIFICATE-----`) + +var ClientKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAo3q//YITCuCWOSLPAdXSGUU+KvydADr63dy5CDTW5wBovnh9 +9Lb0r7iZUkSklk0nMbqVILMGt87MuuW2sdge2paWMlhMlh1R5gWuKSQUahF6pHrD +O9fOeUEHxpK0hI1l/gGBKP5DjoGNALu9m2AEkUG02BXZJ0AVUpbtgXDtf6AbdSlt +ZrlCiETkMuzuYZ8xHDS/AhnR6d8TMQ0hh1dj2UpR0jrMmMg5Im3+D7r35NuiCSoy +LCYSecBLCQ4TZnylLHMLzhgOhReqCdwaKeunliDPascgGvtcabEak7grPzyD2nUV +Zt1yysTXnsV87OF+b1tuwXFFo6W3KGqTtwik5QIDAQABAoIBAQChJ5dts7VL6ruM +FXlViM/1Y2H2hFHM8VduMHEi2tvimm+nHCamf1jUhLh39fz9wY7aoeDyfCkqNy1x +LJQd2zwHJZ1ogcz1ym96vqzCF7QcH6Dz1aTyMDp1I5sjsGlNpgoeDKOjoos8Rw+V +4nz2VwAJpWk9/sOzwqOCaBA3ovgs7zdpPcFhMMle0v79TOUBQ/aa86X0xtRDxwuH +hT8Z2t5hPjSLAjLO9cT0i5bYVVVnvq1ZTGXETXEwi7mMI2HPLELdcTSwesxTTRpt +ACIJOuwHPK5KxxC2HFHTyS0THaCDCR9Hqk8lwKEa+CBmjeCc2MwXuYsVGKsm0FaZ +viS+fGBBAoGBANcglIBOb8WA+BR+F7Bi7jU3nVtalU4DAHKbhSYg8EAxMIuWMq14 +UK+h2Qz2RrT1ezegVAm/NEqLX28vhYm1yz2RHDCUqAughKvhwNJ2mkLLREsJNATw +AMXDS2KhDPIsbJMKY66Gci17+q2FhyXiW10dxpTReVqnOiT1qeUilPkRAoGBAMKK +HG5EGaiF3brr8swsmPaqq33aXqo1k40/pd97xuauPIC/HBLeu+g6w3z8QecOKEYk ++Z5R/o/rsjCIpG4uF19lyAKZ9IgGpHX0rbEfWEyl5WARDOegXHGVfj1DNGhZEtO+ +kSq1i5LteQSfRXvarbhbV7bKgvJYtLK5960XaM6VAoGBALyIPfzQQN5LL57t/p7D +pNWYvtwf37d1o//M0fzfYw4uzceXQySJy9SQN+NHNiJC/NB8PwonupEV4fZUJGjS +nKKBOL5OmZNPAtaLy2vnKzwcXeaQ0zj8iQDILZnrYKggTKr0sPVzuD6qZ7+IxS9r +V/ycKrujdQIAilF3xoQcMYixAoGAfx2NvENFXMez/brFGMKfZLZafk7dAm0lr+sB +8MjJS9xX7mxx5Kajs/gJ2rZePaMTj9oDPX8oTlRdR7dRcikt3oj8Ky78CJIGjojF +ofHwWY0hFyes/gDbxuA+77rlGLXzRmbEJlsgC26eX/XOikJ2tvsAkpE7BS4PTKWV +gAXG1w0CgYEAq4rhFKVi37qKI8kVHO5O3lvRfKOmiMs2j2X+3T2QSTGcGRZD31EO +ImRWsYCAaX97313xYhjTT4jJzNU4fdnJ5hFte+Nt7BK1h/ze4+0XGJBK7wnDqaqg +kL0SB6nxr/Gqnhwx+wEaLkfhiy7Gx0E0IoSGEELsW/MMgvzzAo1/jaM= +-----END RSA PRIVATE KEY-----`) + +var ClientCert = []byte(`-----BEGIN CERTIFICATE----- +MIIDVTCCAj2gAwIBAgIJANWw74P5KJk3MA0GCSqGSIb3DQEBCwUAMDQxMjAwBgNV +BAMMKWdlbmVyaWNfd2ViaG9va19hZG1pc3Npb25fcGx1Z2luX3Rlc3RzX2NhMCAX +DTE3MTExNjAwMDUzOVoYDzIyOTEwOTAxMDAwNTM5WjA4MTYwNAYDVQQDFC1nZW5l +cmljX3dlYmhvb2tfYWRtaXNzaW9uX3BsdWdpbl90ZXN0c19jbGllbnQwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCjer/9ghMK4JY5Is8B1dIZRT4q/J0A +Ovrd3LkINNbnAGi+eH30tvSvuJlSRKSWTScxupUgswa3zsy65bax2B7alpYyWEyW +HVHmBa4pJBRqEXqkesM71855QQfGkrSEjWX+AYEo/kOOgY0Au72bYASRQbTYFdkn +QBVSlu2BcO1/oBt1KW1muUKIROQy7O5hnzEcNL8CGdHp3xMxDSGHV2PZSlHSOsyY +yDkibf4Puvfk26IJKjIsJhJ5wEsJDhNmfKUscwvOGA6FF6oJ3Bop66eWIM9qxyAa ++1xpsRqTuCs/PIPadRVm3XLKxNeexXzs4X5vW27BcUWjpbcoapO3CKTlAgMBAAGj +ZDBiMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMB0GA1UdJQQWMBQGCCsGAQUFBwMC +BggrBgEFBQcDATApBgNVHREEIjAghwR/AAABghh3ZWJob29rLXRlc3QuZGVmYXVs +dC5zdmMwDQYJKoZIhvcNAQELBQADggEBACDF/OlwaoxLu4h4bvyNJnuQdsw3O2Zz +xEADJOkeqM389hYmTlfyPFFhHocFW79ObUxa+73haBXTI6wFP0wSr2jaSQ86j85/ +V99S8WP/D4jmVqXXTe43o3WvvKFUHfJ7BO4OEHED0orRe11IcSkP8emSHHehqXxg +V0P3s1cZao7pPplRSZjcOC5dimEfKnx7ibBh22a8wjq2vPbGxTDf56nkeq4/fbc5 +MaAAeVpyFlN6ueREaz7ixy0r3yLMhC9xr4E6p8VvWsYBkQHWyukiUzbwVUwpK+Rw +Hy80c9+1z7X9/eKr9N/fzwbfrGjb3rbi7o1UHEEwiLaq1a+Df6dP92o= +-----END CERTIFICATE-----`) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts/doc.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts/doc.go new file mode 100644 index 00000000000..a06fe3a6f8f --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts/doc.go @@ -0,0 +1,19 @@ +/* +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 testcerts contains generated key pairs used by the unit tests of +// mutating and validating webhooks. They are for testing only. +package testcerts // import "k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts" diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts/gencerts.sh b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts/gencerts.sh new file mode 100755 index 00000000000..d4145c606ac --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts/gencerts.sh @@ -0,0 +1,109 @@ +#!/bin/bash + +# Copyright 2017 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e + +# gencerts.sh generates the certificates for the generic webhook admission plugin tests. +# +# It is not expected to be run often (there is no go generate rule), and mainly +# exists for documentation purposes. + +CN_BASE="generic_webhook_admission_plugin_tests" + +cat > server.conf << EOF +[req] +req_extensions = v3_req +distinguished_name = req_distinguished_name +[req_distinguished_name] +[ v3_req ] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth, serverAuth +subjectAltName = @alt_names +[alt_names] +IP.1 = 127.0.0.1 +DNS.1 = webhook-test.default.svc +EOF + +cat > client.conf << EOF +[req] +req_extensions = v3_req +distinguished_name = req_distinguished_name +[req_distinguished_name] +[ v3_req ] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth, serverAuth +subjectAltName = @alt_names +[alt_names] +IP.1 = 127.0.0.1 +DNS.1 = webhook-test.default.svc +EOF + +# Create a certificate authority +openssl genrsa -out CAKey.pem 2048 +openssl req -x509 -new -nodes -key CAKey.pem -days 100000 -out CACert.pem -subj "/CN=${CN_BASE}_ca" + +# Create a second certificate authority +openssl genrsa -out BadCAKey.pem 2048 +openssl req -x509 -new -nodes -key BadCAKey.pem -days 100000 -out BadCACert.pem -subj "/CN=${CN_BASE}_ca" + +# Create a server certiticate +openssl genrsa -out ServerKey.pem 2048 +openssl req -new -key ServerKey.pem -out server.csr -subj "/CN=webhook-test.default.svc" -config server.conf +openssl x509 -req -in server.csr -CA CACert.pem -CAkey CAKey.pem -CAcreateserial -out ServerCert.pem -days 100000 -extensions v3_req -extfile server.conf + +# Create a client certiticate +openssl genrsa -out ClientKey.pem 2048 +openssl req -new -key ClientKey.pem -out client.csr -subj "/CN=${CN_BASE}_client" -config client.conf +openssl x509 -req -in client.csr -CA CACert.pem -CAkey CAKey.pem -CAcreateserial -out ClientCert.pem -days 100000 -extensions v3_req -extfile client.conf + +outfile=certs.go + +cat > $outfile << EOF +/* +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. +*/ + +EOF + +echo "// This file was generated using openssl by the gencerts.sh script" >> $outfile +echo "// and holds raw certificates for the webhook tests." >> $outfile +echo "" >> $outfile +echo "package testcerts" >> $outfile +for file in CAKey CACert BadCAKey BadCACert ServerKey ServerCert ClientKey ClientCert; do + data=$(cat ${file}.pem) + echo "" >> $outfile + echo "var $file = []byte(\`$data\`)" >> $outfile +done + +# Clean up after we're done. +rm *.pem +rm *.csr +rm *.srl +rm *.conf diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/BUILD new file mode 100644 index 00000000000..5ab45072db6 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/BUILD @@ -0,0 +1,71 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "admission.go", + "doc.go", + ], + importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/validating", + visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/api/admission/v1beta1:go_default_library", + "//vendor/k8s.io/api/admissionregistration/v1beta1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/configuration:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/initializer:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/metrics:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/errors:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/request:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/rules:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/versioned:go_default_library", + "//vendor/k8s.io/client-go/informers:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["admission_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/validating", + deps = [ + "//vendor/k8s.io/api/admission/v1beta1:go_default_library", + "//vendor/k8s.io/api/admissionregistration/v1beta1:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts:go_default_library", + "//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/admission.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/admission.go new file mode 100644 index 00000000000..b88556631cf --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/admission.go @@ -0,0 +1,326 @@ +/* +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 validating delegates admission checks to dynamically configured +// validating webhooks. +package validating + +import ( + "context" + "fmt" + "io" + "sync" + "time" + + "github.com/golang/glog" + + admissionv1beta1 "k8s.io/api/admission/v1beta1" + "k8s.io/api/admissionregistration/v1beta1" + 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" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/admission/configuration" + genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer" + admissionmetrics "k8s.io/apiserver/pkg/admission/metrics" + "k8s.io/apiserver/pkg/admission/plugin/webhook/config" + webhookadmissionapi "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission" + webhookadmissionapiv1alpha1 "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1" + webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors" + "k8s.io/apiserver/pkg/admission/plugin/webhook/namespace" + "k8s.io/apiserver/pkg/admission/plugin/webhook/request" + "k8s.io/apiserver/pkg/admission/plugin/webhook/rules" + "k8s.io/apiserver/pkg/admission/plugin/webhook/versioned" + "k8s.io/client-go/informers" + clientset "k8s.io/client-go/kubernetes" +) + +const ( + // Name of admission plug-in + PluginName = "ValidatingAdmissionWebhook" +) + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register(PluginName, func(configFile io.Reader) (admission.Interface, error) { + plugin, err := NewValidatingAdmissionWebhook(configFile) + if err != nil { + return nil, err + } + + return plugin, nil + }) + // add our config types + webhookadmissionapi.AddToScheme(plugins.ConfigScheme) + webhookadmissionapiv1alpha1.AddToScheme(plugins.ConfigScheme) +} + +// WebhookSource can list dynamic webhook plugins. +type WebhookSource interface { + Run(stopCh <-chan struct{}) + Webhooks() (*v1beta1.ValidatingWebhookConfiguration, error) +} + +// NewValidatingAdmissionWebhook returns a generic admission webhook plugin. +func NewValidatingAdmissionWebhook(configFile io.Reader) (*ValidatingAdmissionWebhook, error) { + kubeconfigFile, err := config.LoadConfig(configFile) + if err != nil { + return nil, err + } + + cm, err := config.NewClientManager() + if err != nil { + return nil, err + } + authInfoResolver, err := config.NewDefaultAuthenticationInfoResolver(kubeconfigFile) + if err != nil { + return nil, err + } + // Set defaults which may be overridden later. + cm.SetAuthenticationInfoResolver(authInfoResolver) + cm.SetServiceResolver(config.NewDefaultServiceResolver()) + + return &ValidatingAdmissionWebhook{ + Handler: admission.NewHandler( + admission.Connect, + admission.Create, + admission.Delete, + admission.Update, + ), + clientManager: cm, + }, nil +} + +var _ admission.ValidationInterface = &ValidatingAdmissionWebhook{} + +// ValidatingAdmissionWebhook is an implementation of admission.Interface. +type ValidatingAdmissionWebhook struct { + *admission.Handler + hookSource WebhookSource + namespaceMatcher namespace.Matcher + clientManager config.ClientManager + convertor versioned.Convertor +} + +var ( + _ = genericadmissioninit.WantsExternalKubeClientSet(&ValidatingAdmissionWebhook{}) +) + +// TODO find a better way wire this, but keep this pull small for now. +func (a *ValidatingAdmissionWebhook) SetAuthenticationInfoResolverWrapper(wrapper config.AuthenticationInfoResolverWrapper) { + a.clientManager.SetAuthenticationInfoResolverWrapper(wrapper) +} + +// SetServiceResolver sets a service resolver for the webhook admission plugin. +// Passing a nil resolver does not have an effect, instead a default one will be used. +func (a *ValidatingAdmissionWebhook) SetServiceResolver(sr config.ServiceResolver) { + a.clientManager.SetServiceResolver(sr) +} + +// SetScheme sets a serializer(NegotiatedSerializer) which is derived from the scheme +func (a *ValidatingAdmissionWebhook) SetScheme(scheme *runtime.Scheme) { + if scheme != nil { + a.clientManager.SetNegotiatedSerializer(serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{ + Serializer: serializer.NewCodecFactory(scheme).LegacyCodec(admissionv1beta1.SchemeGroupVersion), + })) + a.convertor.Scheme = scheme + } +} + +// WantsExternalKubeClientSet defines a function which sets external ClientSet for admission plugins that need it +func (a *ValidatingAdmissionWebhook) SetExternalKubeClientSet(client clientset.Interface) { + a.namespaceMatcher.Client = client + a.hookSource = configuration.NewValidatingWebhookConfigurationManager(client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations()) +} + +// SetExternalKubeInformerFactory implements the WantsExternalKubeInformerFactory interface. +func (a *ValidatingAdmissionWebhook) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) { + namespaceInformer := f.Core().V1().Namespaces() + a.namespaceMatcher.NamespaceLister = namespaceInformer.Lister() + a.SetReadyFunc(namespaceInformer.Informer().HasSynced) +} + +// ValidateInitialization implements the InitializationValidator interface. +func (a *ValidatingAdmissionWebhook) ValidateInitialization() error { + if a.hookSource == nil { + return fmt.Errorf("ValidatingAdmissionWebhook admission plugin requires a Kubernetes client to be provided") + } + if err := a.namespaceMatcher.Validate(); err != nil { + return fmt.Errorf("ValidatingAdmissionWebhook.namespaceMatcher is not properly setup: %v", err) + } + if err := a.clientManager.Validate(); err != nil { + return fmt.Errorf("ValidatingAdmissionWebhook.clientManager is not properly setup: %v", err) + } + if err := a.convertor.Validate(); err != nil { + return fmt.Errorf("ValidatingAdmissionWebhook.convertor is not properly setup: %v", err) + } + go a.hookSource.Run(wait.NeverStop) + return nil +} + +func (a *ValidatingAdmissionWebhook) loadConfiguration(attr admission.Attributes) (*v1beta1.ValidatingWebhookConfiguration, error) { + hookConfig, err := a.hookSource.Webhooks() + // if Webhook configuration is disabled, fail open + if err == configuration.ErrDisabled { + return &v1beta1.ValidatingWebhookConfiguration{}, nil + } + if err != nil { + e := apierrors.NewServerTimeout(attr.GetResource().GroupResource(), string(attr.GetOperation()), 1) + e.ErrStatus.Message = fmt.Sprintf("Unable to refresh the Webhook configuration: %v", err) + e.ErrStatus.Reason = "LoadingConfiguration" + e.ErrStatus.Details.Causes = append(e.ErrStatus.Details.Causes, metav1.StatusCause{ + Type: "ValidatingWebhookConfigurationFailure", + Message: "An error has occurred while refreshing the ValidatingWebhook configuration, no resources can be created/updated/deleted/connected until a refresh succeeds.", + }) + return nil, e + } + return hookConfig, nil +} + +// Validate makes an admission decision based on the request attributes. +func (a *ValidatingAdmissionWebhook) Validate(attr admission.Attributes) error { + hookConfig, err := a.loadConfiguration(attr) + if err != nil { + return err + } + hooks := hookConfig.Webhooks + ctx := context.TODO() + + var relevantHooks []*v1beta1.Webhook + for i := range hooks { + call, err := a.shouldCallHook(&hooks[i], attr) + if err != nil { + return err + } + if call { + relevantHooks = append(relevantHooks, &hooks[i]) + } + } + + if len(relevantHooks) == 0 { + // no matching hooks + return nil + } + + // convert the object to the external version before sending it to the webhook + versionedAttr := versioned.Attributes{ + Attributes: attr, + } + if oldObj := attr.GetOldObject(); oldObj != nil { + out, err := a.convertor.ConvertToGVK(oldObj, attr.GetKind()) + if err != nil { + return apierrors.NewInternalError(err) + } + versionedAttr.OldObject = out + } + if obj := attr.GetObject(); obj != nil { + out, err := a.convertor.ConvertToGVK(obj, attr.GetKind()) + if err != nil { + return apierrors.NewInternalError(err) + } + versionedAttr.Object = out + } + + wg := sync.WaitGroup{} + errCh := make(chan error, len(relevantHooks)) + wg.Add(len(relevantHooks)) + for i := range relevantHooks { + go func(hook *v1beta1.Webhook) { + defer wg.Done() + + t := time.Now() + err := a.callHook(ctx, hook, versionedAttr) + admissionmetrics.Metrics.ObserveWebhook(time.Since(t), err != nil, attr, "validating", hook.Name) + if err == nil { + return + } + + ignoreClientCallFailures := hook.FailurePolicy != nil && *hook.FailurePolicy == v1beta1.Ignore + if callErr, ok := err.(*webhookerrors.ErrCallingWebhook); ok { + if ignoreClientCallFailures { + glog.Warningf("Failed calling webhook, failing open %v: %v", hook.Name, callErr) + utilruntime.HandleError(callErr) + return + } + + glog.Warningf("Failed calling webhook, failing closed %v: %v", hook.Name, err) + errCh <- apierrors.NewInternalError(err) + return + } + + glog.Warningf("rejected by webhook %q: %#v", hook.Name, err) + errCh <- err + }(relevantHooks[i]) + } + wg.Wait() + close(errCh) + + var errs []error + for e := range errCh { + errs = append(errs, e) + } + if len(errs) == 0 { + return nil + } + if len(errs) > 1 { + for i := 1; i < len(errs); i++ { + // TODO: merge status errors; until then, just return the first one. + utilruntime.HandleError(errs[i]) + } + } + return errs[0] +} + +// TODO: factor into a common place along with the validating webhook version. +func (a *ValidatingAdmissionWebhook) shouldCallHook(h *v1beta1.Webhook, attr admission.Attributes) (bool, *apierrors.StatusError) { + var matches bool + for _, r := range h.Rules { + m := rules.Matcher{Rule: r, Attr: attr} + if m.Matches() { + matches = true + break + } + } + if !matches { + return false, nil + } + + return a.namespaceMatcher.MatchNamespaceSelector(h, attr) +} + +func (a *ValidatingAdmissionWebhook) callHook(ctx context.Context, h *v1beta1.Webhook, attr admission.Attributes) error { + // Make the webhook request + request := request.CreateAdmissionReview(attr) + client, err := a.clientManager.HookClient(h) + if err != nil { + return &webhookerrors.ErrCallingWebhook{WebhookName: h.Name, Reason: err} + } + response := &admissionv1beta1.AdmissionReview{} + if err := client.Post().Context(ctx).Body(&request).Do().Into(response); err != nil { + return &webhookerrors.ErrCallingWebhook{WebhookName: h.Name, Reason: err} + } + + if response.Response == nil { + return &webhookerrors.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")} + } + if response.Response.Allowed { + return nil + } + return webhookerrors.ToStatusErr(h.Name, response.Response.Result) +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/admission_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/admission_test.go new file mode 100644 index 00000000000..46ae76d63e2 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/admission_test.go @@ -0,0 +1,674 @@ +/* +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 validating + +import ( + "crypto/tls" + "crypto/x509" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "sync/atomic" + "testing" + + "k8s.io/api/admission/v1beta1" + registrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" + corev1 "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" + "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/admission/plugin/webhook/config" + "k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts" + "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/client-go/rest" +) + +type fakeHookSource struct { + hooks []registrationv1beta1.Webhook + err error +} + +func (f *fakeHookSource) Webhooks() (*registrationv1beta1.ValidatingWebhookConfiguration, error) { + if f.err != nil { + return nil, f.err + } + for i, h := range f.hooks { + if h.NamespaceSelector == nil { + f.hooks[i].NamespaceSelector = &metav1.LabelSelector{} + } + } + return ®istrationv1beta1.ValidatingWebhookConfiguration{Webhooks: f.hooks}, nil +} + +func (f *fakeHookSource) Run(stopCh <-chan struct{}) {} + +type fakeServiceResolver struct { + base url.URL +} + +func (f fakeServiceResolver) ResolveEndpoint(namespace, name string) (*url.URL, error) { + if namespace == "failResolve" { + return nil, fmt.Errorf("couldn't resolve service location") + } + u := f.base + return &u, nil +} + +type fakeNamespaceLister struct { + namespaces map[string]*corev1.Namespace +} + +func (f fakeNamespaceLister) List(selector labels.Selector) (ret []*corev1.Namespace, err error) { + return nil, nil +} +func (f fakeNamespaceLister) Get(name string) (*corev1.Namespace, error) { + ns, ok := f.namespaces[name] + if ok { + return ns, nil + } + return nil, errors.NewNotFound(corev1.Resource("namespaces"), name) +} + +// ccfgSVC returns a client config using the service reference mechanism. +func ccfgSVC(urlPath string) registrationv1beta1.WebhookClientConfig { + return registrationv1beta1.WebhookClientConfig{ + Service: ®istrationv1beta1.ServiceReference{ + Name: "webhook-test", + Namespace: "default", + Path: &urlPath, + }, + CABundle: testcerts.CACert, + } +} + +type urlConfigGenerator struct { + baseURL *url.URL +} + +// ccfgURL returns a client config using the URL mechanism. +func (c urlConfigGenerator) ccfgURL(urlPath string) registrationv1beta1.WebhookClientConfig { + u2 := *c.baseURL + u2.Path = urlPath + urlString := u2.String() + return registrationv1beta1.WebhookClientConfig{ + URL: &urlString, + CABundle: testcerts.CACert, + } +} + +// TestValidate tests that ValidatingAdmissionWebhook#Validate works as expected +func TestValidate(t *testing.T) { + scheme := runtime.NewScheme() + v1beta1.AddToScheme(scheme) + corev1.AddToScheme(scheme) + + testServer := newTestServer(t) + testServer.StartTLS() + defer testServer.Close() + serverURL, err := url.ParseRequestURI(testServer.URL) + if err != nil { + t.Fatalf("this should never happen? %v", err) + } + wh, err := NewValidatingAdmissionWebhook(nil) + if err != nil { + t.Fatal(err) + } + cm, err := config.NewClientManager() + if err != nil { + t.Fatalf("cannot create client manager: %v", err) + } + cm.SetAuthenticationInfoResolver(newFakeAuthenticationInfoResolver(new(int32))) + cm.SetServiceResolver(fakeServiceResolver{base: *serverURL}) + wh.clientManager = cm + wh.SetScheme(scheme) + if err = wh.clientManager.Validate(); err != nil { + t.Fatal(err) + } + namespace := "webhook-test" + wh.namespaceMatcher.NamespaceLister = fakeNamespaceLister{map[string]*corev1.Namespace{ + namespace: { + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "runlevel": "0", + }, + }, + }, + }, + } + + // Set up a test object for the call + kind := corev1.SchemeGroupVersion.WithKind("Pod") + name := "my-pod" + object := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "pod.name": name, + }, + Name: name, + Namespace: namespace, + }, + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Pod", + }, + } + oldObject := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, + } + operation := admission.Update + resource := corev1.Resource("pods").WithVersion("v1") + subResource := "" + userInfo := user.DefaultInfo{ + Name: "webhook-test", + UID: "webhook-test", + } + + ccfgURL := urlConfigGenerator{serverURL}.ccfgURL + + type test struct { + hookSource fakeHookSource + path string + expectAllow bool + errorContains string + } + + matchEverythingRules := []registrationv1beta1.RuleWithOperations{{ + Operations: []registrationv1beta1.OperationType{registrationv1beta1.OperationAll}, + Rule: registrationv1beta1.Rule{ + APIGroups: []string{"*"}, + APIVersions: []string{"*"}, + Resources: []string{"*/*"}, + }, + }} + + policyFail := registrationv1beta1.Fail + policyIgnore := registrationv1beta1.Ignore + + table := map[string]test{ + "no match": { + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "nomatch", + ClientConfig: ccfgSVC("disallow"), + Rules: []registrationv1beta1.RuleWithOperations{{ + Operations: []registrationv1beta1.OperationType{registrationv1beta1.Create}, + }}, + }}, + }, + expectAllow: true, + }, + "match & allow": { + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "allow", + ClientConfig: ccfgSVC("allow"), + Rules: matchEverythingRules, + }}, + }, + expectAllow: true, + }, + "match & disallow": { + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "disallow", + ClientConfig: ccfgSVC("disallow"), + Rules: matchEverythingRules, + }}, + }, + errorContains: "without explanation", + }, + "match & disallow ii": { + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "disallowReason", + ClientConfig: ccfgSVC("disallowReason"), + Rules: matchEverythingRules, + }}, + }, + errorContains: "you shall not pass", + }, + "match & disallow & but allowed because namespaceSelector exempt the namespace": { + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "disallow", + ClientConfig: ccfgSVC("disallow"), + Rules: newMatchEverythingRules(), + NamespaceSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "runlevel", + Values: []string{"1"}, + Operator: metav1.LabelSelectorOpIn, + }}, + }, + }}, + }, + expectAllow: true, + }, + "match & disallow & but allowed because namespaceSelector exempt the namespace ii": { + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "disallow", + ClientConfig: ccfgSVC("disallow"), + Rules: newMatchEverythingRules(), + NamespaceSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "runlevel", + Values: []string{"0"}, + Operator: metav1.LabelSelectorOpNotIn, + }}, + }, + }}, + }, + expectAllow: true, + }, + "match & fail (but allow because fail open)": { + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "internalErr A", + ClientConfig: ccfgSVC("internalErr"), + Rules: matchEverythingRules, + FailurePolicy: &policyIgnore, + }, { + Name: "internalErr B", + ClientConfig: ccfgSVC("internalErr"), + Rules: matchEverythingRules, + FailurePolicy: &policyIgnore, + }, { + Name: "internalErr C", + ClientConfig: ccfgSVC("internalErr"), + Rules: matchEverythingRules, + FailurePolicy: &policyIgnore, + }}, + }, + expectAllow: true, + }, + "match & fail (but disallow because fail closed on nil)": { + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "internalErr A", + ClientConfig: ccfgSVC("internalErr"), + Rules: matchEverythingRules, + }, { + Name: "internalErr B", + ClientConfig: ccfgSVC("internalErr"), + Rules: matchEverythingRules, + }, { + Name: "internalErr C", + ClientConfig: ccfgSVC("internalErr"), + Rules: matchEverythingRules, + }}, + }, + expectAllow: false, + }, + "match & fail (but fail because fail closed)": { + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "internalErr A", + ClientConfig: ccfgSVC("internalErr"), + Rules: matchEverythingRules, + FailurePolicy: &policyFail, + }, { + Name: "internalErr B", + ClientConfig: ccfgSVC("internalErr"), + Rules: matchEverythingRules, + FailurePolicy: &policyFail, + }, { + Name: "internalErr C", + ClientConfig: ccfgSVC("internalErr"), + Rules: matchEverythingRules, + FailurePolicy: &policyFail, + }}, + }, + expectAllow: false, + }, + "match & allow (url)": { + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "allow", + ClientConfig: ccfgURL("allow"), + Rules: matchEverythingRules, + }}, + }, + expectAllow: true, + }, + "match & disallow (url)": { + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "disallow", + ClientConfig: ccfgURL("disallow"), + Rules: matchEverythingRules, + }}, + }, + errorContains: "without explanation", + }, + "absent response and fail open": { + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "nilResponse", + ClientConfig: ccfgURL("nilResponse"), + FailurePolicy: &policyIgnore, + Rules: matchEverythingRules, + }}, + }, + expectAllow: true, + }, + "absent response and fail closed": { + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "nilResponse", + ClientConfig: ccfgURL("nilResponse"), + FailurePolicy: &policyFail, + Rules: matchEverythingRules, + }}, + }, + errorContains: "Webhook response was absent", + }, + // No need to test everything with the url case, since only the + // connection is different. + } + + for name, tt := range table { + if !strings.Contains(name, "no match") { + continue + } + t.Run(name, func(t *testing.T) { + wh.hookSource = &tt.hookSource + err = wh.Validate(admission.NewAttributesRecord(&object, &oldObject, kind, namespace, name, resource, subResource, operation, &userInfo)) + if tt.expectAllow != (err == nil) { + t.Errorf("expected allowed=%v, but got err=%v", tt.expectAllow, err) + } + // ErrWebhookRejected is not an error for our purposes + if tt.errorContains != "" { + if err == nil || !strings.Contains(err.Error(), tt.errorContains) { + t.Errorf(" expected an error saying %q, but got %v", tt.errorContains, err) + } + } + if _, isStatusErr := err.(*apierrors.StatusError); err != nil && !isStatusErr { + t.Errorf("%s: expected a StatusError, got %T", name, err) + } + }) + } +} + +// TestValidateCachedClient tests that ValidatingAdmissionWebhook#Validate should cache restClient +func TestValidateCachedClient(t *testing.T) { + scheme := runtime.NewScheme() + v1beta1.AddToScheme(scheme) + corev1.AddToScheme(scheme) + + testServer := newTestServer(t) + testServer.StartTLS() + defer testServer.Close() + serverURL, err := url.ParseRequestURI(testServer.URL) + if err != nil { + t.Fatalf("this should never happen? %v", err) + } + wh, err := NewValidatingAdmissionWebhook(nil) + if err != nil { + t.Fatal(err) + } + cm, err := config.NewClientManager() + if err != nil { + t.Fatalf("cannot create client manager: %v", err) + } + cm.SetServiceResolver(fakeServiceResolver{base: *serverURL}) + wh.clientManager = cm + wh.SetScheme(scheme) + namespace := "webhook-test" + wh.namespaceMatcher.NamespaceLister = fakeNamespaceLister{map[string]*corev1.Namespace{ + namespace: { + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "runlevel": "0", + }, + }, + }, + }, + } + + // Set up a test object for the call + kind := corev1.SchemeGroupVersion.WithKind("Pod") + name := "my-pod" + object := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "pod.name": name, + }, + Name: name, + Namespace: namespace, + }, + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Pod", + }, + } + oldObject := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, + } + operation := admission.Update + resource := corev1.Resource("pods").WithVersion("v1") + subResource := "" + userInfo := user.DefaultInfo{ + Name: "webhook-test", + UID: "webhook-test", + } + ccfgURL := urlConfigGenerator{serverURL}.ccfgURL + + type test struct { + name string + hookSource fakeHookSource + expectAllow bool + expectCache bool + } + + policyIgnore := registrationv1beta1.Ignore + cases := []test{ + { + name: "cache 1", + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "cache1", + ClientConfig: ccfgSVC("allow"), + Rules: newMatchEverythingRules(), + FailurePolicy: &policyIgnore, + }}, + }, + expectAllow: true, + expectCache: true, + }, + { + name: "cache 2", + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "cache2", + ClientConfig: ccfgSVC("internalErr"), + Rules: newMatchEverythingRules(), + FailurePolicy: &policyIgnore, + }}, + }, + expectAllow: true, + expectCache: true, + }, + { + name: "cache 3", + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "cache3", + ClientConfig: ccfgSVC("allow"), + Rules: newMatchEverythingRules(), + FailurePolicy: &policyIgnore, + }}, + }, + expectAllow: true, + expectCache: false, + }, + { + name: "cache 4", + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "cache4", + ClientConfig: ccfgURL("allow"), + Rules: newMatchEverythingRules(), + FailurePolicy: &policyIgnore, + }}, + }, + expectAllow: true, + expectCache: true, + }, + { + name: "cache 5", + hookSource: fakeHookSource{ + hooks: []registrationv1beta1.Webhook{{ + Name: "cache5", + ClientConfig: ccfgURL("allow"), + Rules: newMatchEverythingRules(), + FailurePolicy: &policyIgnore, + }}, + }, + expectAllow: true, + expectCache: false, + }, + } + + for _, testcase := range cases { + t.Run(testcase.name, func(t *testing.T) { + wh.hookSource = &testcase.hookSource + authInfoResolverCount := new(int32) + r := newFakeAuthenticationInfoResolver(authInfoResolverCount) + wh.clientManager.SetAuthenticationInfoResolver(r) + if err = wh.clientManager.Validate(); err != nil { + t.Fatal(err) + } + + err = wh.Validate(admission.NewAttributesRecord(&object, &oldObject, kind, namespace, testcase.name, resource, subResource, operation, &userInfo)) + if testcase.expectAllow != (err == nil) { + t.Errorf("expected allowed=%v, but got err=%v", testcase.expectAllow, err) + } + + if testcase.expectCache && *authInfoResolverCount != 1 { + t.Errorf("expected cacheclient, but got none") + } + + if !testcase.expectCache && *authInfoResolverCount != 0 { + t.Errorf("expected not cacheclient, but got cache") + } + }) + } + +} + +func newTestServer(t *testing.T) *httptest.Server { + // Create the test webhook server + sCert, err := tls.X509KeyPair(testcerts.ServerCert, testcerts.ServerKey) + if err != nil { + t.Fatal(err) + } + rootCAs := x509.NewCertPool() + rootCAs.AppendCertsFromPEM(testcerts.CACert) + testServer := httptest.NewUnstartedServer(http.HandlerFunc(webhookHandler)) + testServer.TLS = &tls.Config{ + Certificates: []tls.Certificate{sCert}, + ClientCAs: rootCAs, + ClientAuth: tls.RequireAndVerifyClientCert, + } + return testServer +} + +func webhookHandler(w http.ResponseWriter, r *http.Request) { + fmt.Printf("got req: %v\n", r.URL.Path) + switch r.URL.Path { + case "/internalErr": + http.Error(w, "webhook internal server error", http.StatusInternalServerError) + return + case "/invalidReq": + w.WriteHeader(http.StatusSwitchingProtocols) + w.Write([]byte("webhook invalid request")) + return + case "/invalidResp": + w.Header().Set("Content-Type", "application/json") + w.Write([]byte("webhook invalid response")) + case "/disallow": + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(&v1beta1.AdmissionReview{ + Response: &v1beta1.AdmissionResponse{ + Allowed: false, + }, + }) + case "/disallowReason": + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(&v1beta1.AdmissionReview{ + Response: &v1beta1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Message: "you shall not pass", + }, + }, + }) + case "/allow": + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(&v1beta1.AdmissionReview{ + Response: &v1beta1.AdmissionResponse{ + Allowed: true, + }, + }) + case "/nilResposne": + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(&v1beta1.AdmissionReview{}) + default: + http.NotFound(w, r) + } +} + +func newFakeAuthenticationInfoResolver(count *int32) *fakeAuthenticationInfoResolver { + return &fakeAuthenticationInfoResolver{ + restConfig: &rest.Config{ + TLSClientConfig: rest.TLSClientConfig{ + CAData: testcerts.CACert, + CertData: testcerts.ClientCert, + KeyData: testcerts.ClientKey, + }, + }, + cachedCount: count, + } +} + +type fakeAuthenticationInfoResolver struct { + restConfig *rest.Config + cachedCount *int32 +} + +func (c *fakeAuthenticationInfoResolver) ClientConfigFor(server string) (*rest.Config, error) { + atomic.AddInt32(c.cachedCount, 1) + return c.restConfig, nil +} + +func newMatchEverythingRules() []registrationv1beta1.RuleWithOperations { + return []registrationv1beta1.RuleWithOperations{{ + Operations: []registrationv1beta1.OperationType{registrationv1beta1.OperationAll}, + Rule: registrationv1beta1.Rule{ + APIGroups: []string{"*"}, + APIVersions: []string{"*"}, + Resources: []string{"*/*"}, + }, + }} +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/doc.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/doc.go new file mode 100644 index 00000000000..ede53c668f2 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/doc.go @@ -0,0 +1,19 @@ +/* +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 validating makes calls to validating (i.e., non-mutating) webhooks +// during the admission process. +package validating // import "k8s.io/apiserver/pkg/admission/plugin/webhook/validating" diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/versioned/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/versioned/BUILD new file mode 100644 index 00000000000..f16c617ac15 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/versioned/BUILD @@ -0,0 +1,47 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "attributes.go", + "conversion.go", + "doc.go", + ], + importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/versioned", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["conversion_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/versioned", + deps = [ + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/example:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/example/v1:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/example2/v1:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/versioned/attributes.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/versioned/attributes.go new file mode 100644 index 00000000000..58f8ae6aa37 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/versioned/attributes.go @@ -0,0 +1,42 @@ +/* +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 versioned + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/admission" +) + +// Attributes is a wrapper around the original admission attributes. It allows +// override the internal objects with the versioned ones. +type Attributes struct { + admission.Attributes + OldObject runtime.Object + Object runtime.Object +} + +// GetObject overrides the original GetObjects() and it returns the versioned +// object. +func (v Attributes) GetObject() runtime.Object { + return v.Object +} + +// GetOldObject overrides the original GetOldObjects() and it returns the +// versioned oldObject. +func (v Attributes) GetOldObject() runtime.Object { + return v.OldObject +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/versioned/conversion.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/versioned/conversion.go new file mode 100644 index 00000000000..a1ba712fc74 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/versioned/conversion.go @@ -0,0 +1,67 @@ +/* +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 versioned + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// Convertor converts objects to the desired version. +type Convertor struct { + Scheme *runtime.Scheme +} + +// Convert converts the in object to the out object and returns an error if the +// conversion fails. +func (c Convertor) Convert(in runtime.Object, out runtime.Object) error { + // For custom resources, because ConvertToGVK reuses the passed in object as + // the output. c.Scheme.Convert resets the objects to empty if in == out, so + // we skip the conversion if that's the case. + if in == out { + return nil + } + return c.Scheme.Convert(in, out, nil) +} + +// ConvertToGVK converts object to the desired gvk. +func (c Convertor) ConvertToGVK(obj runtime.Object, gvk schema.GroupVersionKind) (runtime.Object, error) { + // Unlike other resources, custom resources do not have internal version, so + // if obj is a custom resource, it should not need conversion. + if obj.GetObjectKind().GroupVersionKind() == gvk { + return obj, nil + } + out, err := c.Scheme.New(gvk) + if err != nil { + return nil, err + } + err = c.Scheme.Convert(obj, out, nil) + if err != nil { + return nil, err + } + return out, nil +} + +// Validate checks if the conversion has a scheme. +func (c *Convertor) Validate() error { + if c.Scheme == nil { + return fmt.Errorf("the Convertor requires a scheme") + } + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/versioned/conversion_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/versioned/conversion_test.go new file mode 100644 index 00000000000..1429c71e100 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/versioned/conversion_test.go @@ -0,0 +1,219 @@ +/* +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 versioned + +import ( + "reflect" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/apis/example" + examplev1 "k8s.io/apiserver/pkg/apis/example/v1" + example2v1 "k8s.io/apiserver/pkg/apis/example2/v1" +) + +func initiateScheme() *runtime.Scheme { + s := runtime.NewScheme() + example.AddToScheme(s) + examplev1.AddToScheme(s) + example2v1.AddToScheme(s) + return s +} + +func TestConvertToGVK(t *testing.T) { + scheme := initiateScheme() + c := Convertor{Scheme: scheme} + table := map[string]struct { + obj runtime.Object + gvk schema.GroupVersionKind + expectedObj runtime.Object + }{ + "convert example#Pod to example/v1#Pod": { + obj: &example.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Labels: map[string]string{ + "key": "value", + }, + }, + Spec: example.PodSpec{ + RestartPolicy: example.RestartPolicy("never"), + }, + }, + gvk: examplev1.SchemeGroupVersion.WithKind("Pod"), + expectedObj: &examplev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Labels: map[string]string{ + "key": "value", + }, + }, + Spec: examplev1.PodSpec{ + RestartPolicy: examplev1.RestartPolicy("never"), + }, + }, + }, + "convert example#replicaset to example2/v1#replicaset": { + obj: &example.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rs1", + Labels: map[string]string{ + "key": "value", + }, + }, + Spec: example.ReplicaSetSpec{ + Replicas: 1, + }, + }, + gvk: example2v1.SchemeGroupVersion.WithKind("ReplicaSet"), + expectedObj: &example2v1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rs1", + Labels: map[string]string{ + "key": "value", + }, + }, + Spec: example2v1.ReplicaSetSpec{ + Replicas: func() *int32 { var i int32; i = 1; return &i }(), + }, + }, + }, + "no conversion for Unstructured object whose gvk matches the desired gvk": { + obj: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "mygroup.k8s.io/v1", + "kind": "Flunder", + "data": map[string]interface{}{ + "Key": "Value", + }, + }, + }, + gvk: schema.GroupVersionKind{Group: "mygroup.k8s.io", Version: "v1", Kind: "Flunder"}, + expectedObj: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "mygroup.k8s.io/v1", + "kind": "Flunder", + "data": map[string]interface{}{ + "Key": "Value", + }, + }, + }, + }, + } + + for name, test := range table { + t.Run(name, func(t *testing.T) { + actual, err := c.ConvertToGVK(test.obj, test.gvk) + if err != nil { + t.Error(err) + } + if !reflect.DeepEqual(actual, test.expectedObj) { + t.Errorf("\nexpected:\n%#v\ngot:\n %#v\n", test.expectedObj, actual) + } + }) + } +} + +func TestConvert(t *testing.T) { + scheme := initiateScheme() + c := Convertor{Scheme: scheme} + sampleCRD := unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "mygroup.k8s.io/v1", + "kind": "Flunder", + "data": map[string]interface{}{ + "Key": "Value", + }, + }, + } + + table := map[string]struct { + in runtime.Object + out runtime.Object + expectedObj runtime.Object + }{ + "convert example/v1#Pod to example#Pod": { + in: &examplev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Labels: map[string]string{ + "key": "value", + }, + }, + Spec: examplev1.PodSpec{ + RestartPolicy: examplev1.RestartPolicy("never"), + }, + }, + out: &example.Pod{}, + expectedObj: &example.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Labels: map[string]string{ + "key": "value", + }, + }, + Spec: example.PodSpec{ + RestartPolicy: example.RestartPolicy("never"), + }, + }, + }, + "convert example2/v1#replicaset to example#replicaset": { + in: &example2v1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rs1", + Labels: map[string]string{ + "key": "value", + }, + }, + Spec: example2v1.ReplicaSetSpec{ + Replicas: func() *int32 { var i int32; i = 1; return &i }(), + }, + }, + out: &example.ReplicaSet{}, + expectedObj: &example.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rs1", + Labels: map[string]string{ + "key": "value", + }, + }, + Spec: example.ReplicaSetSpec{ + Replicas: 1, + }, + }, + }, + "no conversion if the object is the same": { + in: &sampleCRD, + out: &sampleCRD, + expectedObj: &sampleCRD, + }, + } + for name, test := range table { + t.Run(name, func(t *testing.T) { + err := c.Convert(test.in, test.out) + if err != nil { + t.Error(err) + } + if !reflect.DeepEqual(test.out, test.expectedObj) { + t.Errorf("\nexpected:\n%#v\ngot:\n %#v\n", test.expectedObj, test.out) + } + }) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/versioned/doc.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/versioned/doc.go new file mode 100644 index 00000000000..d557a9fec84 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/versioned/doc.go @@ -0,0 +1,19 @@ +/* +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 versioned provides tools for making sure the objects sent to a +// webhook are in a version the webhook understands. +package versioned // import "k8s.io/apiserver/pkg/admission/plugin/webhook/versioned" diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugins.go b/staging/src/k8s.io/apiserver/pkg/admission/plugins.go index 5ddfc7e1f84..3ede44a173f 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugins.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugins.go @@ -25,6 +25,8 @@ import ( "sort" "sync" + "k8s.io/apimachinery/pkg/runtime" + "github.com/golang/glog" ) @@ -37,6 +39,16 @@ type Factory func(config io.Reader) (Interface, error) type Plugins struct { lock sync.Mutex registry map[string]Factory + + // ConfigScheme is used to parse the admission plugin config file. + // It is exposed to act as a hook for extending server providing their own config. + ConfigScheme *runtime.Scheme +} + +func NewPlugins() *Plugins { + return &Plugins{ + ConfigScheme: runtime.NewScheme(), + } } // All registered admission options. @@ -118,10 +130,12 @@ func splitStream(config io.Reader) (io.Reader, io.Reader, error) { return bytes.NewBuffer(configBytes), bytes.NewBuffer(configBytes), nil } +type Decorator func(handler Interface, name string) Interface + // NewFromPlugins returns an admission.Interface that will enforce admission control decisions of all // the given plugins. -func (ps *Plugins) NewFromPlugins(pluginNames []string, configProvider ConfigProvider, pluginInitializer PluginInitializer) (Interface, error) { - plugins := []Interface{} +func (ps *Plugins) NewFromPlugins(pluginNames []string, configProvider ConfigProvider, pluginInitializer PluginInitializer, decorator Decorator) (Interface, error) { + handlers := []Interface{} for _, pluginName := range pluginNames { pluginConfig, err := configProvider.ConfigFor(pluginName) if err != nil { @@ -133,10 +147,14 @@ func (ps *Plugins) NewFromPlugins(pluginNames []string, configProvider ConfigPro return nil, err } if plugin != nil { - plugins = append(plugins, plugin) + if decorator != nil { + handlers = append(handlers, decorator(plugin, pluginName)) + } else { + handlers = append(handlers, plugin) + } } } - return chainAdmissionHandler(plugins), nil + return chainAdmissionHandler(handlers), nil } // InitPlugin creates an instance of the named interface. @@ -156,18 +174,18 @@ func (ps *Plugins) InitPlugin(name string, config io.Reader, pluginInitializer P pluginInitializer.Initialize(plugin) // ensure that plugins have been properly initialized - if err := Validate(plugin); err != nil { + if err := ValidateInitialization(plugin); err != nil { return nil, err } return plugin, nil } -// Validate will call the Validate function in each plugin if they implement -// the Validator interface. -func Validate(plugin Interface) error { - if validater, ok := plugin.(Validator); ok { - err := validater.Validate() +// ValidateInitialization will call the InitializationValidate function in each plugin if they implement +// the InitializationValidator interface. +func ValidateInitialization(plugin Interface) error { + if validater, ok := plugin.(InitializationValidator); ok { + err := validater.ValidateInitialization() if err != nil { return err } diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/BUILD b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/BUILD index aa4c3606f50..d8956d72be5 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/BUILD @@ -16,7 +16,6 @@ go_library( importpath = "k8s.io/apiserver/pkg/apis/apiserver", deps = [ "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/doc.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/doc.go index 46880d5cb7f..a89863a35fc 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/doc.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // Package apiserver is the internal version of the API. // +groupName=apiserver.k8s.io diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/register.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/register.go index 1410518b9ed..ffe9942a6cc 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/register.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/register.go @@ -41,7 +41,7 @@ var ( AddToScheme = SchemeBuilder.AddToScheme ) -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &AdmissionConfiguration{}, diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/BUILD b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/BUILD index 3075c3bbec8..2640ff64b64 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/BUILD @@ -8,6 +8,7 @@ load( go_library( name = "go_default_library", srcs = [ + "conversion.go", "doc.go", "register.go", "types.go", diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/conversion.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/conversion.go new file mode 100644 index 00000000000..378cc080d3a --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/conversion.go @@ -0,0 +1,88 @@ +/* +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 ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +var _ runtime.NestedObjectDecoder = &AdmissionConfiguration{} + +// DecodeNestedObjects handles encoding RawExtensions on the AdmissionConfiguration, ensuring the +// objects are decoded with the provided decoder. +func (c *AdmissionConfiguration) DecodeNestedObjects(d runtime.Decoder) error { + // decoding failures result in a runtime.Unknown object being created in Object and passed + // to conversion + for k, v := range c.Plugins { + decodeNestedRawExtensionOrUnknown(d, &v.Configuration) + c.Plugins[k] = v + } + return nil +} + +var _ runtime.NestedObjectEncoder = &AdmissionConfiguration{} + +// EncodeNestedObjects handles encoding RawExtensions on the AdmissionConfiguration, ensuring the +// objects are encoded with the provided encoder. +func (c *AdmissionConfiguration) EncodeNestedObjects(e runtime.Encoder) error { + for k, v := range c.Plugins { + if err := encodeNestedRawExtension(e, &v.Configuration); err != nil { + return err + } + c.Plugins[k] = v + } + return nil +} + +// decodeNestedRawExtensionOrUnknown decodes the raw extension into an object once. If called +// On a RawExtension that has already been decoded (has an object), it will not run again. +func decodeNestedRawExtensionOrUnknown(d runtime.Decoder, ext *runtime.RawExtension) { + if ext.Raw == nil || ext.Object != nil { + return + } + obj, gvk, err := d.Decode(ext.Raw, nil, nil) + if err != nil { + unk := &runtime.Unknown{Raw: ext.Raw} + if runtime.IsNotRegisteredError(err) { + if _, gvk, err := d.Decode(ext.Raw, nil, unk); err == nil { + unk.APIVersion = gvk.GroupVersion().String() + unk.Kind = gvk.Kind + ext.Object = unk + return + } + } + // TODO: record mime-type with the object + if gvk != nil { + unk.APIVersion = gvk.GroupVersion().String() + unk.Kind = gvk.Kind + } + obj = unk + } + ext.Object = obj +} + +func encodeNestedRawExtension(e runtime.Encoder, ext *runtime.RawExtension) error { + if ext.Raw != nil || ext.Object == nil { + return nil + } + data, err := runtime.Encode(e, ext.Object) + if err != nil { + return err + } + ext.Raw = data + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/doc.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/doc.go index 10cc03cfed4..7dd031a793c 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/doc.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:conversion-gen=k8s.io/apiserver/pkg/apis/apiserver // +k8s:defaulter-gen=TypeMeta diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/register.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/register.go index 090adec3201..466b19ae5bb 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/register.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/register.go @@ -42,7 +42,7 @@ func init() { localSchemeBuilder.Register(addKnownTypes) } -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &AdmissionConfiguration{}, 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 8b6a1695634..b1af97ec392 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 @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.deepcopy.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.deepcopy.go index 35164ce77d1..c8b46fac5d8 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,32 +21,9 @@ limitations under the License. package v1alpha1 import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AdmissionConfiguration).DeepCopyInto(out.(*AdmissionConfiguration)) - return nil - }, InType: reflect.TypeOf(&AdmissionConfiguration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AdmissionPluginConfiguration).DeepCopyInto(out.(*AdmissionPluginConfiguration)) - return nil - }, InType: reflect.TypeOf(&AdmissionPluginConfiguration{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AdmissionConfiguration) DeepCopyInto(out *AdmissionConfiguration) { *out = *in diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.defaults.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.defaults.go index 7e6df29d4ae..5e24d22cacd 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.defaults.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/zz_generated.deepcopy.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/zz_generated.deepcopy.go index b455115d81a..7e5fb6edb45 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,32 +21,9 @@ limitations under the License. package apiserver import ( - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AdmissionConfiguration).DeepCopyInto(out.(*AdmissionConfiguration)) - return nil - }, InType: reflect.TypeOf(&AdmissionConfiguration{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*AdmissionPluginConfiguration).DeepCopyInto(out.(*AdmissionPluginConfiguration)) - return nil - }, InType: reflect.TypeOf(&AdmissionPluginConfiguration{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AdmissionConfiguration) DeepCopyInto(out *AdmissionConfiguration) { *out = *in diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/BUILD b/staging/src/k8s.io/apiserver/pkg/apis/audit/BUILD index fc5d519776c..2388d79f8ce 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/BUILD @@ -17,7 +17,6 @@ go_library( importpath = "k8s.io/apiserver/pkg/apis/audit", deps = [ "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/install/BUILD b/staging/src/k8s.io/apiserver/pkg/apis/audit/install/BUILD index f6489a53416..c4acc260e50 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/install/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/install/BUILD @@ -37,8 +37,8 @@ filegroup( go_test( name = "go_default_test", srcs = ["roundtrip_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/apis/audit/install", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/api/testing/roundtrip:go_default_library", "//vendor/k8s.io/apiserver/pkg/apis/audit/fuzzer:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/register.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/register.go index e14b82c1b13..9abf739ae0c 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/register.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/register.go @@ -49,6 +49,5 @@ func addKnownTypes(scheme *runtime.Scheme) error { &Policy{}, &PolicyList{}, ) - scheme.AddGeneratedDeepCopyFuncs(GetGeneratedDeepCopyFuncs()...) return nil } 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 00b5c1dc07b..2b318d5754d 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/types.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/types.go @@ -153,6 +153,11 @@ type Policy struct { // The default audit level is None, but can be overridden by a catch-all rule at the end of the list. // PolicyRules are strictly ordered. Rules []PolicyRule + + // OmitStages is a list of stages for which no events are created. Note that this can also + // be specified per rule in which case the union of both are omitted. + // +optional + OmitStages []Stage } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -208,8 +213,10 @@ type PolicyRule struct { // +optional NonResourceURLs []string - // OmitStages specify events generated in which stages will not be emitted to backend. + // OmitStages is a list of stages for which no events are created. Note that this can also + // be specified policy wide in which case the union of both are omitted. // An empty list means no restrictions will apply. + // +optional OmitStages []Stage } diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/BUILD b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/BUILD index 04b9b943db9..93ebb21c528 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/BUILD @@ -53,8 +53,8 @@ filegroup( go_test( name = "go_default_test", srcs = ["conversion_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/apis/audit/v1alpha1", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/doc.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/doc.go index b7020f207bb..27cc4c5ea52 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/doc.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:conversion-gen=k8s.io/apiserver/pkg/apis/audit // +k8s:openapi-gen=true // +k8s:defaulter-gen=TypeMeta diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/generated.pb.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/generated.pb.go index ecfaed571ec..12e674bb187 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/generated.pb.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -414,6 +414,21 @@ func (m *Policy) MarshalTo(dAtA []byte) (int, error) { i += n } } + if len(m.OmitStages) > 0 { + for _, s := range m.OmitStages { + dAtA[i] = 0x1a + i++ + l = len(s) + for l >= 1<<7 { + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + dAtA[i] = uint8(l) + i++ + i += copy(dAtA[i:], s) + } + } return i, nil } @@ -723,6 +738,12 @@ func (m *Policy) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) } } + if len(m.OmitStages) > 0 { + for _, s := range m.OmitStages { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } return n } @@ -874,6 +895,7 @@ func (this *Policy) String() string { s := strings.Join([]string{`&Policy{`, `ObjectMeta:` + strings.Replace(strings.Replace(this.ObjectMeta.String(), "ObjectMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ObjectMeta", 1), `&`, ``, 1) + `,`, `Rules:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Rules), "PolicyRule", "PolicyRule", 1), `&`, ``, 1) + `,`, + `OmitStages:` + fmt.Sprintf("%v", this.OmitStages) + `,`, `}`, }, "") return s @@ -2044,6 +2066,35 @@ func (m *Policy) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field OmitStages", 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 > l { + return io.ErrUnexpectedEOF + } + m.OmitStages = append(m.OmitStages, Stage(dAtA[iNdEx:postIndex])) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -2570,80 +2621,80 @@ func init() { } var fileDescriptorGenerated = []byte{ - // 1185 bytes of a gzipped FileDescriptorProto + // 1190 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x56, 0x4f, 0x6f, 0x1b, 0x45, 0x14, 0xcf, 0xd6, 0x71, 0x62, 0x4f, 0x1a, 0x27, 0x9d, 0x22, 0xba, 0xca, 0xc1, 0x36, 0x46, 0x42, 0x11, 0x84, 0xdd, 0xa4, 0x04, 0x5a, 0x0e, 0x1c, 0x62, 0x15, 0x81, 0xa5, 0x34, 0x84, 0x49, 0x5c, - 0x89, 0x3f, 0x07, 0xd6, 0xf6, 0x8b, 0x3d, 0xc4, 0xde, 0x5d, 0x66, 0x66, 0x8d, 0x72, 0xe3, 0xc0, - 0x15, 0x89, 0x3b, 0x1f, 0xa6, 0xe2, 0x50, 0x29, 0xc7, 0x1e, 0x7b, 0xb2, 0x88, 0xf9, 0x16, 0x39, - 0xa1, 0x99, 0x9d, 0xdd, 0xd9, 0x75, 0x6a, 0xd5, 0xe1, 0xd0, 0xdb, 0xce, 0x7b, 0xbf, 0xf7, 0x9b, - 0xf7, 0xde, 0xbe, 0x3f, 0x83, 0xbe, 0x3d, 0x7f, 0xcc, 0x1d, 0x1a, 0xb8, 0xe7, 0x51, 0x07, 0x98, - 0x0f, 0x02, 0xb8, 0x3b, 0x06, 0xbf, 0x17, 0x30, 0x57, 0x2b, 0xbc, 0x90, 0x72, 0x60, 0x63, 0x60, - 0x6e, 0x78, 0xde, 0x57, 0x27, 0xd7, 0x8b, 0x7a, 0x54, 0xb8, 0xe3, 0x3d, 0x6f, 0x18, 0x0e, 0xbc, - 0x3d, 0xb7, 0x0f, 0x3e, 0x30, 0x4f, 0x40, 0xcf, 0x09, 0x59, 0x20, 0x02, 0xbc, 0x1d, 0x5b, 0x3a, - 0xa9, 0xa5, 0x13, 0x9e, 0xf7, 0xd5, 0xc9, 0x51, 0x96, 0x4e, 0x62, 0xb9, 0xf5, 0x71, 0x9f, 0x8a, - 0x41, 0xd4, 0x71, 0xba, 0xc1, 0xc8, 0xed, 0x07, 0xfd, 0xc0, 0x55, 0x04, 0x9d, 0xe8, 0x4c, 0x9d, - 0xd4, 0x41, 0x7d, 0xc5, 0xc4, 0x5b, 0x3b, 0xc6, 0x25, 0xd7, 0x8b, 0xc4, 0x00, 0x7c, 0x41, 0xbb, - 0x9e, 0xa0, 0x81, 0xef, 0x8e, 0x6f, 0xb8, 0xb1, 0xb5, 0x6f, 0xd0, 0x23, 0xaf, 0x3b, 0xa0, 0x3e, - 0xb0, 0x0b, 0x13, 0xc3, 0x08, 0x84, 0xf7, 0x3a, 0x2b, 0x77, 0x9e, 0x15, 0x8b, 0x7c, 0x41, 0x47, - 0x70, 0xc3, 0xe0, 0xb3, 0x37, 0x19, 0xf0, 0xee, 0x00, 0x46, 0xde, 0x0d, 0xbb, 0x4f, 0xe6, 0xd9, - 0x45, 0x82, 0x0e, 0x5d, 0xea, 0x0b, 0x2e, 0xd8, 0xac, 0x51, 0xe3, 0x05, 0x42, 0xc5, 0x2f, 0xc7, - 0xe0, 0x0b, 0xfc, 0x13, 0x2a, 0xc9, 0x10, 0x7a, 0x9e, 0xf0, 0x6c, 0xab, 0x6e, 0x6d, 0xaf, 0x3d, - 0xdc, 0x75, 0x4c, 0xde, 0x53, 0x46, 0x93, 0x7a, 0x89, 0x76, 0xc6, 0x7b, 0xce, 0x37, 0x9d, 0x9f, - 0xa1, 0x2b, 0x9e, 0x82, 0xf0, 0x9a, 0xf8, 0x72, 0x52, 0x5b, 0x9a, 0x4e, 0x6a, 0xc8, 0xc8, 0x48, - 0xca, 0x8a, 0x77, 0x50, 0x71, 0x08, 0x63, 0x18, 0xda, 0x77, 0xea, 0xd6, 0x76, 0xb9, 0xf9, 0xae, - 0x06, 0x17, 0x0f, 0xa5, 0xf0, 0x3a, 0xf9, 0x20, 0x31, 0x08, 0xff, 0x80, 0xca, 0x32, 0x5a, 0x2e, - 0xbc, 0x51, 0x68, 0x17, 0x94, 0x43, 0x1f, 0x2e, 0xe6, 0xd0, 0x29, 0x1d, 0x41, 0xf3, 0x9e, 0x66, - 0x2f, 0x9f, 0x26, 0x24, 0xc4, 0xf0, 0xe1, 0x23, 0xb4, 0xaa, 0x2a, 0xa7, 0xf5, 0xc4, 0x5e, 0x56, - 0xce, 0xec, 0x6b, 0xf8, 0xea, 0x41, 0x2c, 0xbe, 0x9e, 0xd4, 0xde, 0x9b, 0x97, 0x4f, 0x71, 0x11, - 0x02, 0x77, 0xda, 0xad, 0x27, 0x24, 0x21, 0x91, 0xa1, 0x71, 0xe1, 0xf5, 0xc1, 0x2e, 0xe6, 0x43, - 0x3b, 0x91, 0xc2, 0xeb, 0xe4, 0x83, 0xc4, 0x20, 0xfc, 0x10, 0x21, 0x06, 0xbf, 0x44, 0xc0, 0x45, - 0x9b, 0xb4, 0xec, 0x15, 0x65, 0x92, 0xa6, 0x8e, 0xa4, 0x1a, 0x92, 0x41, 0xe1, 0x3a, 0x5a, 0x1e, - 0x03, 0xeb, 0xd8, 0xab, 0x0a, 0x7d, 0x57, 0xa3, 0x97, 0x9f, 0x01, 0xeb, 0x10, 0xa5, 0xc1, 0x5f, - 0xa3, 0xe5, 0x88, 0x03, 0xb3, 0x4b, 0x2a, 0x57, 0x1f, 0x64, 0x72, 0xe5, 0xe4, 0x6b, 0x5b, 0xe6, - 0xa8, 0xcd, 0x81, 0xb5, 0xfc, 0xb3, 0xc0, 0x30, 0x49, 0x09, 0x51, 0x0c, 0x78, 0x80, 0x36, 0xe9, - 0x28, 0x04, 0xc6, 0x03, 0x5f, 0x96, 0x8a, 0xd4, 0xd8, 0xe5, 0x5b, 0xb1, 0xbe, 0x33, 0x9d, 0xd4, - 0x36, 0x5b, 0x33, 0x1c, 0xe4, 0x06, 0x2b, 0xfe, 0x08, 0x95, 0x79, 0x10, 0xb1, 0x2e, 0xb4, 0x8e, - 0xb9, 0x8d, 0xea, 0x85, 0xed, 0x72, 0x73, 0x5d, 0xfe, 0xb4, 0x93, 0x44, 0x48, 0x8c, 0x1e, 0x9f, - 0xa1, 0x72, 0xa0, 0xea, 0x8a, 0xc0, 0x99, 0xbd, 0xa6, 0xfc, 0xf9, 0xdc, 0x59, 0x74, 0x34, 0xe8, - 0x32, 0x25, 0x70, 0x06, 0x0c, 0xfc, 0x2e, 0xc4, 0xf7, 0xa4, 0x42, 0x62, 0xa8, 0xf1, 0x00, 0x55, - 0x18, 0xf0, 0x30, 0xf0, 0x39, 0x9c, 0x08, 0x4f, 0x44, 0xdc, 0xbe, 0xab, 0x2e, 0xdb, 0x59, 0xac, - 0xfc, 0x62, 0x9b, 0x26, 0x9e, 0x4e, 0x6a, 0x15, 0x92, 0xe3, 0x21, 0x33, 0xbc, 0xd8, 0x43, 0xeb, - 0xfa, 0x17, 0xc7, 0x8e, 0xd8, 0xeb, 0xea, 0xa2, 0xed, 0xb9, 0x17, 0xe9, 0x11, 0xe0, 0xb4, 0xfd, - 0x73, 0x3f, 0xf8, 0xd5, 0x6f, 0xde, 0x9b, 0x4e, 0x6a, 0xeb, 0x24, 0x4b, 0x41, 0xf2, 0x8c, 0xb8, - 0x67, 0x82, 0xd1, 0x77, 0x54, 0x6e, 0x79, 0x47, 0x2e, 0x10, 0x7d, 0xc9, 0x0c, 0x27, 0xfe, 0xc3, - 0x42, 0xb6, 0xbe, 0x97, 0x40, 0x17, 0xe8, 0x18, 0x7a, 0x69, 0xdf, 0xd9, 0x1b, 0xea, 0x42, 0x77, - 0xb1, 0xec, 0x3d, 0xa5, 0x5d, 0x16, 0xa8, 0x0e, 0xae, 0xeb, 0xca, 0xb4, 0xc9, 0x1c, 0x62, 0x32, - 0xf7, 0x4a, 0x1c, 0xa0, 0x8a, 0x6a, 0x35, 0xe3, 0xc4, 0xe6, 0xff, 0x73, 0x22, 0xe9, 0xe4, 0xca, - 0x49, 0x8e, 0x8e, 0xcc, 0xd0, 0x37, 0x9e, 0x5b, 0xa8, 0xac, 0xe6, 0xe8, 0x21, 0xe5, 0x02, 0xff, - 0x78, 0x63, 0x96, 0x3a, 0x8b, 0x5d, 0x2c, 0xad, 0xd5, 0x24, 0xdd, 0xd4, 0xf7, 0x96, 0x12, 0x49, - 0x66, 0x8e, 0x9e, 0xa2, 0x22, 0x15, 0x30, 0xe2, 0xf6, 0x9d, 0x7a, 0x61, 0x26, 0xa6, 0x37, 0xf4, - 0x80, 0xf2, 0xb0, 0xb9, 0x9e, 0x4c, 0xa7, 0x96, 0x64, 0x21, 0x31, 0x59, 0xe3, 0x2f, 0x0b, 0x55, - 0xbe, 0x62, 0x41, 0x14, 0x12, 0x88, 0x5b, 0x8e, 0xe3, 0xf7, 0x51, 0xb1, 0x2f, 0x25, 0x2a, 0x86, - 0xb2, 0xb1, 0x8b, 0x61, 0xb1, 0x4e, 0xb6, 0x30, 0x4b, 0x2c, 0x94, 0x47, 0xba, 0x85, 0x53, 0x1a, - 0x62, 0xf4, 0xf8, 0x91, 0x2c, 0xf8, 0xf8, 0x70, 0xe4, 0x8d, 0x80, 0xdb, 0x05, 0x65, 0xa0, 0xcb, - 0x38, 0xa3, 0x20, 0x79, 0x5c, 0xe3, 0xf7, 0x02, 0xda, 0x98, 0xe9, 0x60, 0xbc, 0x83, 0x4a, 0x09, - 0x48, 0x7b, 0x98, 0x66, 0x2d, 0xe1, 0x22, 0x29, 0x02, 0xbb, 0xa8, 0xec, 0x4b, 0xaa, 0xd0, 0xeb, - 0x82, 0xde, 0x40, 0xe9, 0x8e, 0x38, 0x4a, 0x14, 0xc4, 0x60, 0xe4, 0xc4, 0x95, 0x07, 0xb5, 0x7b, - 0x32, 0x13, 0x57, 0x62, 0x89, 0xd2, 0xe0, 0x26, 0x2a, 0x44, 0xb4, 0xa7, 0x37, 0xc8, 0xae, 0x06, - 0x14, 0xda, 0x8b, 0x6e, 0x0f, 0x69, 0x2c, 0x77, 0x81, 0x17, 0xd2, 0x67, 0xc0, 0x38, 0x0d, 0x7c, - 0xbd, 0x3e, 0xd2, 0x5d, 0x70, 0x70, 0xdc, 0xd2, 0x1a, 0x92, 0x41, 0xe1, 0x03, 0xb4, 0x91, 0x84, - 0x95, 0x18, 0xc6, 0x4b, 0xe4, 0x81, 0x36, 0xdc, 0x20, 0x79, 0x35, 0x99, 0xc5, 0xe3, 0x4f, 0xd1, - 0x1a, 0x8f, 0x3a, 0x69, 0xfa, 0xe2, 0xad, 0x72, 0x5f, 0x9b, 0xaf, 0x9d, 0x18, 0x15, 0xc9, 0xe2, - 0x1a, 0x2f, 0x2c, 0xb4, 0x72, 0x1c, 0x0c, 0x69, 0xf7, 0xe2, 0x2d, 0xbc, 0x17, 0xbe, 0x43, 0x45, - 0x16, 0x0d, 0x21, 0xa9, 0xf3, 0xfd, 0xc5, 0xeb, 0x3c, 0x76, 0x91, 0x44, 0x43, 0x30, 0x45, 0x2b, - 0x4f, 0x9c, 0xc4, 0x8c, 0x8d, 0xbf, 0x2d, 0x84, 0x62, 0xd0, 0x5b, 0xe8, 0xd7, 0x76, 0xbe, 0x5f, - 0x77, 0x6f, 0x1b, 0xc7, 0x9c, 0x86, 0x7d, 0x5e, 0x48, 0x62, 0x90, 0xa1, 0x99, 0xd7, 0x95, 0xb5, - 0xc8, 0xeb, 0xaa, 0x86, 0x8a, 0x72, 0xd5, 0x27, 0x1d, 0x5b, 0x96, 0x48, 0xb9, 0x91, 0x39, 0x89, - 0xe5, 0xd8, 0x41, 0x48, 0x7e, 0xa8, 0x56, 0x4f, 0xda, 0xb4, 0x22, 0x7f, 0x55, 0x3b, 0x95, 0x92, - 0x0c, 0x42, 0x12, 0xca, 0x57, 0x08, 0xb7, 0x97, 0x0d, 0xa1, 0x7c, 0x9c, 0x70, 0x12, 0xcb, 0x31, - 0xcd, 0xce, 0x89, 0xa2, 0xca, 0xc4, 0xe3, 0xc5, 0x33, 0x91, 0x9f, 0x4c, 0xa6, 0x73, 0x5f, 0x3b, - 0x65, 0x1c, 0x84, 0xd2, 0x36, 0xe6, 0xf6, 0x8a, 0xf1, 0x3d, 0xed, 0x73, 0x4e, 0x32, 0x08, 0xfc, - 0x05, 0xda, 0xf0, 0x03, 0x3f, 0xa1, 0x6a, 0x93, 0x43, 0x6e, 0xaf, 0x2a, 0xa3, 0xfb, 0xb2, 0x97, - 0x8e, 0xf2, 0x2a, 0x32, 0x8b, 0xc5, 0x8f, 0x10, 0x0a, 0x46, 0x54, 0xa8, 0x0d, 0xc1, 0xed, 0x92, - 0xb2, 0x7c, 0xa0, 0xaa, 0x3a, 0x95, 0x9a, 0x27, 0x60, 0x06, 0xda, 0x74, 0x2e, 0xaf, 0xaa, 0x4b, - 0x2f, 0xaf, 0xaa, 0x4b, 0xaf, 0xae, 0xaa, 0x4b, 0xbf, 0x4d, 0xab, 0xd6, 0xe5, 0xb4, 0x6a, 0xbd, - 0x9c, 0x56, 0xad, 0x57, 0xd3, 0xaa, 0xf5, 0xcf, 0xb4, 0x6a, 0xfd, 0xf9, 0x6f, 0x75, 0xe9, 0xfb, - 0x52, 0x92, 0x84, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0x9e, 0x6c, 0xe1, 0x29, 0x5b, 0x0d, 0x00, - 0x00, + 0x89, 0x3f, 0x07, 0xd6, 0xf6, 0x8b, 0xbd, 0xc4, 0xde, 0x5d, 0x66, 0x66, 0x8d, 0x72, 0xe3, 0xc0, + 0x15, 0x89, 0x3b, 0x1f, 0xa6, 0xe2, 0x80, 0x94, 0x63, 0x8f, 0x3d, 0x59, 0xc4, 0x7c, 0x8b, 0x1c, + 0x10, 0x9a, 0xd9, 0x99, 0x9d, 0x5d, 0xa7, 0x56, 0x1d, 0x0e, 0xbd, 0xed, 0xbc, 0xf7, 0x7b, 0xbf, + 0xf7, 0xe6, 0xed, 0xfb, 0x33, 0xe8, 0xeb, 0xf3, 0xc7, 0xcc, 0xf1, 0x43, 0xf7, 0x3c, 0xee, 0x00, + 0x0d, 0x80, 0x03, 0x73, 0xc7, 0x10, 0xf4, 0x42, 0xea, 0x2a, 0x85, 0x17, 0xf9, 0x0c, 0xe8, 0x18, + 0xa8, 0x1b, 0x9d, 0xf7, 0xe5, 0xc9, 0xf5, 0xe2, 0x9e, 0xcf, 0xdd, 0xf1, 0x9e, 0x37, 0x8c, 0x06, + 0xde, 0x9e, 0xdb, 0x87, 0x00, 0xa8, 0xc7, 0xa1, 0xe7, 0x44, 0x34, 0xe4, 0x21, 0xde, 0x4e, 0x2c, + 0x9d, 0xd4, 0xd2, 0x89, 0xce, 0xfb, 0xf2, 0xe4, 0x48, 0x4b, 0x47, 0x5b, 0x6e, 0x7d, 0xd8, 0xf7, + 0xf9, 0x20, 0xee, 0x38, 0xdd, 0x70, 0xe4, 0xf6, 0xc3, 0x7e, 0xe8, 0x4a, 0x82, 0x4e, 0x7c, 0x26, + 0x4f, 0xf2, 0x20, 0xbf, 0x12, 0xe2, 0xad, 0x1d, 0x13, 0x92, 0xeb, 0xc5, 0x7c, 0x00, 0x01, 0xf7, + 0xbb, 0x1e, 0xf7, 0xc3, 0xc0, 0x1d, 0xdf, 0x08, 0x63, 0x6b, 0xdf, 0xa0, 0x47, 0x5e, 0x77, 0xe0, + 0x07, 0x40, 0x2f, 0xcc, 0x1d, 0x46, 0xc0, 0xbd, 0x57, 0x59, 0xb9, 0xf3, 0xac, 0x68, 0x1c, 0x70, + 0x7f, 0x04, 0x37, 0x0c, 0x3e, 0x79, 0x9d, 0x01, 0xeb, 0x0e, 0x60, 0xe4, 0xdd, 0xb0, 0xfb, 0x68, + 0x9e, 0x5d, 0xcc, 0xfd, 0xa1, 0xeb, 0x07, 0x9c, 0x71, 0x3a, 0x6b, 0xd4, 0xf8, 0x0b, 0xa1, 0xe2, + 0xe7, 0x63, 0x08, 0x38, 0xfe, 0x01, 0x95, 0xc4, 0x15, 0x7a, 0x1e, 0xf7, 0x6c, 0xab, 0x6e, 0x6d, + 0xaf, 0x3d, 0xdc, 0x75, 0x4c, 0xde, 0x53, 0x46, 0x93, 0x7a, 0x81, 0x76, 0xc6, 0x7b, 0xce, 0x57, + 0x9d, 0x1f, 0xa1, 0xcb, 0x9f, 0x02, 0xf7, 0x9a, 0xf8, 0x72, 0x52, 0x5b, 0x9a, 0x4e, 0x6a, 0xc8, + 0xc8, 0x48, 0xca, 0x8a, 0x77, 0x50, 0x71, 0x08, 0x63, 0x18, 0xda, 0x77, 0xea, 0xd6, 0x76, 0xb9, + 0xf9, 0xb6, 0x02, 0x17, 0x0f, 0x85, 0xf0, 0x5a, 0x7f, 0x90, 0x04, 0x84, 0xbf, 0x43, 0x65, 0x71, + 0x5b, 0xc6, 0xbd, 0x51, 0x64, 0x17, 0x64, 0x40, 0xef, 0x2f, 0x16, 0xd0, 0xa9, 0x3f, 0x82, 0xe6, + 0x3d, 0xc5, 0x5e, 0x3e, 0xd5, 0x24, 0xc4, 0xf0, 0xe1, 0x23, 0xb4, 0x2a, 0x2b, 0xa7, 0xf5, 0xc4, + 0x5e, 0x96, 0xc1, 0xec, 0x2b, 0xf8, 0xea, 0x41, 0x22, 0xbe, 0x9e, 0xd4, 0xde, 0x99, 0x97, 0x4f, + 0x7e, 0x11, 0x01, 0x73, 0xda, 0xad, 0x27, 0x44, 0x93, 0x88, 0xab, 0x31, 0xee, 0xf5, 0xc1, 0x2e, + 0xe6, 0xaf, 0x76, 0x22, 0x84, 0xd7, 0xfa, 0x83, 0x24, 0x20, 0xfc, 0x10, 0x21, 0x0a, 0x3f, 0xc5, + 0xc0, 0x78, 0x9b, 0xb4, 0xec, 0x15, 0x69, 0x92, 0xa6, 0x8e, 0xa4, 0x1a, 0x92, 0x41, 0xe1, 0x3a, + 0x5a, 0x1e, 0x03, 0xed, 0xd8, 0xab, 0x12, 0x7d, 0x57, 0xa1, 0x97, 0x9f, 0x01, 0xed, 0x10, 0xa9, + 0xc1, 0x5f, 0xa2, 0xe5, 0x98, 0x01, 0xb5, 0x4b, 0x32, 0x57, 0xef, 0x65, 0x72, 0xe5, 0xe4, 0x6b, + 0x5b, 0xe4, 0xa8, 0xcd, 0x80, 0xb6, 0x82, 0xb3, 0xd0, 0x30, 0x09, 0x09, 0x91, 0x0c, 0x78, 0x80, + 0x36, 0xfd, 0x51, 0x04, 0x94, 0x85, 0x81, 0x28, 0x15, 0xa1, 0xb1, 0xcb, 0xb7, 0x62, 0x7d, 0x6b, + 0x3a, 0xa9, 0x6d, 0xb6, 0x66, 0x38, 0xc8, 0x0d, 0x56, 0xfc, 0x01, 0x2a, 0xb3, 0x30, 0xa6, 0x5d, + 0x68, 0x1d, 0x33, 0x1b, 0xd5, 0x0b, 0xdb, 0xe5, 0xe6, 0xba, 0xf8, 0x69, 0x27, 0x5a, 0x48, 0x8c, + 0x1e, 0x9f, 0xa1, 0x72, 0x28, 0xeb, 0x8a, 0xc0, 0x99, 0xbd, 0x26, 0xe3, 0xf9, 0xd4, 0x59, 0x74, + 0x34, 0xa8, 0x32, 0x25, 0x70, 0x06, 0x14, 0x82, 0x2e, 0x24, 0x7e, 0x52, 0x21, 0x31, 0xd4, 0x78, + 0x80, 0x2a, 0x14, 0x58, 0x14, 0x06, 0x0c, 0x4e, 0xb8, 0xc7, 0x63, 0x66, 0xdf, 0x95, 0xce, 0x76, + 0x16, 0x2b, 0xbf, 0xc4, 0xa6, 0x89, 0xa7, 0x93, 0x5a, 0x85, 0xe4, 0x78, 0xc8, 0x0c, 0x2f, 0xf6, + 0xd0, 0xba, 0xfa, 0xc5, 0x49, 0x20, 0xf6, 0xba, 0x74, 0xb4, 0x3d, 0xd7, 0x91, 0x1a, 0x01, 0x4e, + 0x3b, 0x38, 0x0f, 0xc2, 0x9f, 0x83, 0xe6, 0xbd, 0xe9, 0xa4, 0xb6, 0x4e, 0xb2, 0x14, 0x24, 0xcf, + 0x88, 0x7b, 0xe6, 0x32, 0xca, 0x47, 0xe5, 0x96, 0x3e, 0x72, 0x17, 0x51, 0x4e, 0x66, 0x38, 0xf1, + 0x6f, 0x16, 0xb2, 0x95, 0x5f, 0x02, 0x5d, 0xf0, 0xc7, 0xd0, 0x4b, 0xfb, 0xce, 0xde, 0x90, 0x0e, + 0xdd, 0xc5, 0xb2, 0xf7, 0xd4, 0xef, 0xd2, 0x50, 0x76, 0x70, 0x5d, 0x55, 0xa6, 0x4d, 0xe6, 0x10, + 0x93, 0xb9, 0x2e, 0x71, 0x88, 0x2a, 0xb2, 0xd5, 0x4c, 0x10, 0x9b, 0xff, 0x2f, 0x08, 0xdd, 0xc9, + 0x95, 0x93, 0x1c, 0x1d, 0x99, 0xa1, 0x6f, 0x3c, 0xb7, 0x50, 0x59, 0xce, 0xd1, 0x43, 0x9f, 0x71, + 0xfc, 0xfd, 0x8d, 0x59, 0xea, 0x2c, 0xe6, 0x58, 0x58, 0xcb, 0x49, 0xba, 0xa9, 0xfc, 0x96, 0xb4, + 0x24, 0x33, 0x47, 0x4f, 0x51, 0xd1, 0xe7, 0x30, 0x62, 0xf6, 0x9d, 0x7a, 0x61, 0xe6, 0x4e, 0xaf, + 0xe9, 0x01, 0x19, 0x61, 0x73, 0x5d, 0x4f, 0xa7, 0x96, 0x60, 0x21, 0x09, 0x59, 0xe3, 0x0f, 0x0b, + 0x55, 0xbe, 0xa0, 0x61, 0x1c, 0x11, 0x48, 0x5a, 0x8e, 0xe1, 0x77, 0x51, 0xb1, 0x2f, 0x24, 0xf2, + 0x0e, 0x65, 0x63, 0x97, 0xc0, 0x12, 0x9d, 0x68, 0x61, 0xaa, 0x2d, 0x64, 0x44, 0xaa, 0x85, 0x53, + 0x1a, 0x62, 0xf4, 0xf8, 0x91, 0x28, 0xf8, 0xe4, 0x70, 0xe4, 0x8d, 0x80, 0xd9, 0x05, 0x69, 0xa0, + 0xca, 0x38, 0xa3, 0x20, 0x79, 0x5c, 0xe3, 0xd7, 0x02, 0xda, 0x98, 0xe9, 0x60, 0xbc, 0x83, 0x4a, + 0x1a, 0xa4, 0x22, 0x4c, 0xb3, 0xa6, 0xb9, 0x48, 0x8a, 0xc0, 0x2e, 0x2a, 0x07, 0x82, 0x2a, 0xf2, + 0xba, 0xa0, 0x36, 0x50, 0xba, 0x23, 0x8e, 0xb4, 0x82, 0x18, 0x8c, 0x98, 0xb8, 0xe2, 0x20, 0x77, + 0x4f, 0x66, 0xe2, 0x0a, 0x2c, 0x91, 0x1a, 0xdc, 0x44, 0x85, 0xd8, 0xef, 0xa9, 0x0d, 0xb2, 0xab, + 0x00, 0x85, 0xf6, 0xa2, 0xdb, 0x43, 0x18, 0x8b, 0x5d, 0xe0, 0x45, 0xfe, 0x33, 0xa0, 0xcc, 0x0f, + 0x03, 0xb5, 0x3e, 0xd2, 0x5d, 0x70, 0x70, 0xdc, 0x52, 0x1a, 0x92, 0x41, 0xe1, 0x03, 0xb4, 0xa1, + 0xaf, 0xa5, 0x0d, 0x93, 0x25, 0xf2, 0x40, 0x19, 0x6e, 0x90, 0xbc, 0x9a, 0xcc, 0xe2, 0xf1, 0xc7, + 0x68, 0x8d, 0xc5, 0x9d, 0x34, 0x7d, 0xc9, 0x56, 0xb9, 0xaf, 0xcc, 0xd7, 0x4e, 0x8c, 0x8a, 0x64, + 0x71, 0x8d, 0x7f, 0x2d, 0xb4, 0x72, 0x1c, 0x0e, 0xfd, 0xee, 0xc5, 0x1b, 0x78, 0x2f, 0x7c, 0x83, + 0x8a, 0x34, 0x1e, 0x82, 0xae, 0xf3, 0xfd, 0xc5, 0xeb, 0x3c, 0x09, 0x91, 0xc4, 0x43, 0x30, 0x45, + 0x2b, 0x4e, 0x8c, 0x24, 0x8c, 0xf8, 0x11, 0x42, 0xe1, 0xc8, 0xe7, 0xb2, 0xa9, 0x75, 0x11, 0x3e, + 0x90, 0x81, 0xa4, 0x52, 0xb3, 0xb5, 0x33, 0xd0, 0xc6, 0x9f, 0x16, 0x42, 0x09, 0xfb, 0x1b, 0x68, + 0xf4, 0x76, 0xbe, 0xd1, 0x77, 0x6f, 0x9b, 0x80, 0x39, 0x9d, 0xfe, 0xbc, 0xa0, 0xef, 0x20, 0x72, + 0x62, 0x9e, 0x65, 0xd6, 0x22, 0xcf, 0xb2, 0x1a, 0x2a, 0x8a, 0x37, 0x82, 0x6e, 0xf5, 0xb2, 0x40, + 0x8a, 0x55, 0xce, 0x48, 0x22, 0xc7, 0x0e, 0x42, 0xe2, 0x43, 0xce, 0x08, 0x9d, 0xda, 0x8a, 0x48, + 0x6d, 0x3b, 0x95, 0x92, 0x0c, 0x42, 0x10, 0x8a, 0xe7, 0x0b, 0xb3, 0x97, 0x0d, 0xa1, 0x78, 0xd5, + 0x30, 0x92, 0xc8, 0xb1, 0x9f, 0x1d, 0x30, 0x45, 0x99, 0x89, 0xc7, 0x8b, 0x67, 0x22, 0x3f, 0xd2, + 0x4c, 0xcb, 0xbf, 0x72, 0x3c, 0x39, 0x08, 0xa5, 0xfd, 0xcf, 0xec, 0x15, 0x13, 0x7b, 0x3a, 0x20, + 0x18, 0xc9, 0x20, 0xf0, 0x67, 0x68, 0x23, 0x08, 0x03, 0x4d, 0xd5, 0x26, 0x87, 0xcc, 0x5e, 0x95, + 0x46, 0xf7, 0x45, 0x13, 0x1e, 0xe5, 0x55, 0x64, 0x16, 0x3b, 0x53, 0x85, 0xa5, 0x85, 0xab, 0xb0, + 0xe9, 0x5c, 0x5e, 0x55, 0x97, 0x5e, 0x5c, 0x55, 0x97, 0x5e, 0x5e, 0x55, 0x97, 0x7e, 0x99, 0x56, + 0xad, 0xcb, 0x69, 0xd5, 0x7a, 0x31, 0xad, 0x5a, 0x2f, 0xa7, 0x55, 0xeb, 0xef, 0x69, 0xd5, 0xfa, + 0xfd, 0x9f, 0xea, 0xd2, 0xb7, 0x25, 0x9d, 0x84, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0x55, 0x14, + 0x18, 0x3e, 0x94, 0x0d, 0x00, 0x00, } diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/generated.proto b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/generated.proto index 9fb04cd06ef..926eb65edc9 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/generated.proto +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -164,6 +164,11 @@ message Policy { // The default audit level is None, but can be overridden by a catch-all rule at the end of the list. // PolicyRules are strictly ordered. repeated PolicyRule rules = 2; + + // OmitStages is a list of stages for which no events are created. Note that this can also + // be specified per rule in which case the union of both are omitted. + // +optional + repeated string omitStages = 3; } // PolicyList is a list of audit Policies. @@ -214,8 +219,10 @@ message PolicyRule { // +optional repeated string nonResourceURLs = 7; - // OmitStages specify events generated in which stages will not be emitted to backend. + // OmitStages is a list of stages for which no events are created. Note that this can also + // be specified policy wide in which case the union of both are omitted. // An empty list means no restrictions will apply. + // +optional repeated string omitStages = 8; } diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/types.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/types.go index ba00bf733e7..21f10c78d48 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/types.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/types.go @@ -160,6 +160,11 @@ type Policy struct { // The default audit level is None, but can be overridden by a catch-all rule at the end of the list. // PolicyRules are strictly ordered. Rules []PolicyRule `json:"rules" protobuf:"bytes,2,rep,name=rules"` + + // OmitStages is a list of stages for which no events are created. Note that this can also + // be specified per rule in which case the union of both are omitted. + // +optional + OmitStages []Stage `json:"omitStages,omitempty" protobuf:"bytes,3,rep,name=omitStages"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -215,8 +220,10 @@ type PolicyRule struct { // +optional NonResourceURLs []string `json:"nonResourceURLs,omitempty" protobuf:"bytes,7,rep,name=nonResourceURLs"` - // OmitStages specify events generated in which stages will not be emitted to backend. + // OmitStages is a list of stages for which no events are created. Note that this can also + // be specified policy wide in which case the union of both are omitted. // An empty list means no restrictions will apply. + // +optional OmitStages []Stage `json:"omitStages,omitempty" protobuf:"bytes,8,rep,name=omitStages"` } diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/zz_generated.conversion.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/zz_generated.conversion.go index 51b922e3536..38aaf458349 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/zz_generated.conversion.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,13 +21,14 @@ limitations under the License. package v1alpha1 import ( + unsafe "unsafe" + authentication_v1 "k8s.io/api/authentication/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" types "k8s.io/apimachinery/pkg/types" audit "k8s.io/apiserver/pkg/apis/audit" - unsafe "unsafe" ) func init() { @@ -207,6 +208,7 @@ func autoConvert_audit_ObjectReference_To_v1alpha1_ObjectReference(in *audit.Obj func autoConvert_v1alpha1_Policy_To_audit_Policy(in *Policy, out *audit.Policy, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta out.Rules = *(*[]audit.PolicyRule)(unsafe.Pointer(&in.Rules)) + out.OmitStages = *(*[]audit.Stage)(unsafe.Pointer(&in.OmitStages)) return nil } @@ -218,6 +220,7 @@ func Convert_v1alpha1_Policy_To_audit_Policy(in *Policy, out *audit.Policy, s co func autoConvert_audit_Policy_To_v1alpha1_Policy(in *audit.Policy, out *Policy, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta out.Rules = *(*[]PolicyRule)(unsafe.Pointer(&in.Rules)) + out.OmitStages = *(*[]Stage)(unsafe.Pointer(&in.OmitStages)) return nil } diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/zz_generated.deepcopy.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/zz_generated.deepcopy.go index 5bc4218ecbe..bb4ea53105f 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,52 +23,9 @@ package v1alpha1 import ( v1 "k8s.io/api/authentication/v1" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Event).DeepCopyInto(out.(*Event)) - return nil - }, InType: reflect.TypeOf(&Event{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*EventList).DeepCopyInto(out.(*EventList)) - return nil - }, InType: reflect.TypeOf(&EventList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*GroupResources).DeepCopyInto(out.(*GroupResources)) - return nil - }, InType: reflect.TypeOf(&GroupResources{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ObjectReference).DeepCopyInto(out.(*ObjectReference)) - return nil - }, InType: reflect.TypeOf(&ObjectReference{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Policy).DeepCopyInto(out.(*Policy)) - return nil - }, InType: reflect.TypeOf(&Policy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PolicyList).DeepCopyInto(out.(*PolicyList)) - return nil - }, InType: reflect.TypeOf(&PolicyList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PolicyRule).DeepCopyInto(out.(*PolicyRule)) - return nil - }, InType: reflect.TypeOf(&PolicyRule{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Event) DeepCopyInto(out *Event) { *out = *in @@ -238,6 +195,11 @@ func (in *Policy) DeepCopyInto(out *Policy) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.OmitStages != nil { + in, out := &in.OmitStages, &out.OmitStages + *out = make([]Stage, len(*in)) + copy(*out, *in) + } return } diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/zz_generated.defaults.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/zz_generated.defaults.go index 7e6df29d4ae..5e24d22cacd 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/zz_generated.defaults.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/BUILD b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/BUILD index 07e14e74c03..93cc89ad620 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/BUILD @@ -54,8 +54,8 @@ filegroup( go_test( name = "go_default_test", srcs = ["conversion_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/apis/audit/v1beta1", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/doc.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/doc.go index 226d9263a83..38814725867 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/doc.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:conversion-gen=k8s.io/apiserver/pkg/apis/audit // +k8s:openapi-gen=true // +k8s:defaulter-gen=TypeMeta diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/generated.pb.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/generated.pb.go index ee84bbcb2c7..b982df3ed8f 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/generated.pb.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -418,6 +418,21 @@ func (m *Policy) MarshalTo(dAtA []byte) (int, error) { i += n } } + if len(m.OmitStages) > 0 { + for _, s := range m.OmitStages { + dAtA[i] = 0x1a + i++ + l = len(s) + for l >= 1<<7 { + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + dAtA[i] = uint8(l) + i++ + i += copy(dAtA[i:], s) + } + } return i, nil } @@ -729,6 +744,12 @@ func (m *Policy) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) } } + if len(m.OmitStages) > 0 { + for _, s := range m.OmitStages { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } return n } @@ -881,6 +902,7 @@ func (this *Policy) String() string { s := strings.Join([]string{`&Policy{`, `ObjectMeta:` + strings.Replace(strings.Replace(this.ObjectMeta.String(), "ObjectMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ObjectMeta", 1), `&`, ``, 1) + `,`, `Rules:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Rules), "PolicyRule", "PolicyRule", 1), `&`, ``, 1) + `,`, + `OmitStages:` + fmt.Sprintf("%v", this.OmitStages) + `,`, `}`, }, "") return s @@ -2080,6 +2102,35 @@ func (m *Policy) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field OmitStages", 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 > l { + return io.ErrUnexpectedEOF + } + m.OmitStages = append(m.OmitStages, Stage(dAtA[iNdEx:postIndex])) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -2606,81 +2657,81 @@ func init() { } var fileDescriptorGenerated = []byte{ - // 1211 bytes of a gzipped FileDescriptorProto + // 1216 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x56, 0x41, 0x6f, 0x1b, 0x45, 0x14, 0xce, 0xd6, 0x71, 0x63, 0x4f, 0x1a, 0x27, 0x9d, 0x22, 0xba, 0xca, 0xc1, 0x36, 0x46, 0x82, 0x08, 0xd2, 0xdd, 0xa6, 0x2d, 0x24, 0x17, 0x0e, 0xb1, 0x8a, 0xc0, 0x52, 0x1a, 0xa2, 0x71, 0x5c, 0x21, 0xe0, 0xc0, 0xda, 0x7e, 0xb1, 0x87, 0xd8, 0xbb, 0xcb, 0xcc, 0xac, 0x51, 0x6e, 0xfc, 0x01, - 0x24, 0xee, 0xfc, 0x0b, 0x7e, 0x40, 0xc5, 0xa1, 0x87, 0x1c, 0x7b, 0xec, 0xc9, 0x22, 0xe6, 0x5f, - 0xe4, 0x84, 0x66, 0x76, 0x76, 0x67, 0x6d, 0xd7, 0xd4, 0xe1, 0xd0, 0xdb, 0xee, 0x7b, 0xdf, 0xf7, - 0xcd, 0x7b, 0x6f, 0xe6, 0xbd, 0x19, 0x74, 0x72, 0x7e, 0xc0, 0x1d, 0x1a, 0xb8, 0xe7, 0x51, 0x1b, - 0x98, 0x0f, 0x02, 0xb8, 0x3b, 0x02, 0xbf, 0x1b, 0x30, 0x57, 0x3b, 0xbc, 0x90, 0x72, 0x60, 0x23, - 0x60, 0x6e, 0x78, 0xde, 0x53, 0x7f, 0xae, 0x17, 0x75, 0xa9, 0x70, 0x47, 0x7b, 0x6d, 0x10, 0xde, - 0x9e, 0xdb, 0x03, 0x1f, 0x98, 0x27, 0xa0, 0xeb, 0x84, 0x2c, 0x10, 0x01, 0xfe, 0x38, 0x26, 0x3a, - 0x29, 0xd1, 0x09, 0xcf, 0x7b, 0xea, 0xcf, 0x51, 0x44, 0x47, 0x13, 0xb7, 0x1f, 0xf4, 0xa8, 0xe8, - 0x47, 0x6d, 0xa7, 0x13, 0x0c, 0xdd, 0x5e, 0xd0, 0x0b, 0x5c, 0xc5, 0x6f, 0x47, 0x67, 0xea, 0x4f, - 0xfd, 0xa8, 0xaf, 0x58, 0x77, 0x7b, 0xd7, 0x04, 0xe4, 0x7a, 0x91, 0xe8, 0x83, 0x2f, 0x68, 0xc7, - 0x13, 0x34, 0xf0, 0xdd, 0xd1, 0x5c, 0x14, 0xdb, 0x4f, 0x0c, 0x7a, 0xe8, 0x75, 0xfa, 0xd4, 0x07, - 0x76, 0x61, 0x32, 0x18, 0x82, 0xf0, 0xde, 0xc4, 0x72, 0x17, 0xb1, 0x58, 0xe4, 0x0b, 0x3a, 0x84, - 0x39, 0xc2, 0xe7, 0x6f, 0x23, 0xf0, 0x4e, 0x1f, 0x86, 0xde, 0x1c, 0xef, 0xf1, 0x22, 0x5e, 0x24, - 0xe8, 0xc0, 0xa5, 0xbe, 0xe0, 0x82, 0xcd, 0x91, 0x0e, 0xde, 0xbe, 0x25, 0xde, 0x20, 0xec, 0xcf, - 0xef, 0x49, 0xed, 0x25, 0x42, 0xf9, 0x2f, 0x47, 0xe0, 0x0b, 0xfc, 0x23, 0x2a, 0xc8, 0xe4, 0xbb, - 0x9e, 0xf0, 0x6c, 0xab, 0x6a, 0xed, 0xac, 0x3f, 0x7a, 0xe8, 0x98, 0x0d, 0x4b, 0x63, 0x31, 0x7b, - 0x26, 0xd1, 0xce, 0x68, 0xcf, 0xf9, 0xa6, 0xfd, 0x13, 0x74, 0xc4, 0x33, 0x10, 0x5e, 0x1d, 0x5f, - 0x8e, 0x2b, 0x2b, 0x93, 0x71, 0x05, 0x19, 0x1b, 0x49, 0x55, 0xf1, 0x2e, 0xca, 0x0f, 0x60, 0x04, - 0x03, 0xfb, 0x56, 0xd5, 0xda, 0x29, 0xd6, 0xdf, 0xd7, 0xe0, 0xfc, 0x91, 0x34, 0x5e, 0x27, 0x1f, - 0x24, 0x06, 0xe1, 0xef, 0x51, 0x51, 0xd6, 0x89, 0x0b, 0x6f, 0x18, 0xda, 0x39, 0x15, 0xd0, 0x27, - 0xcb, 0x05, 0x74, 0x4a, 0x87, 0x50, 0xbf, 0xab, 0xd5, 0x8b, 0xa7, 0x89, 0x08, 0x31, 0x7a, 0xf8, - 0x18, 0xad, 0xa9, 0xc2, 0x34, 0x9e, 0xda, 0xab, 0x2a, 0x98, 0x27, 0x1a, 0xbe, 0x76, 0x18, 0x9b, - 0xaf, 0xc7, 0x95, 0x0f, 0x16, 0xed, 0x84, 0xb8, 0x08, 0x81, 0x3b, 0xad, 0xc6, 0x53, 0x92, 0x88, - 0xc8, 0xd4, 0xb8, 0xf0, 0x7a, 0x60, 0xe7, 0xa7, 0x53, 0x6b, 0x4a, 0xe3, 0x75, 0xf2, 0x41, 0x62, - 0x10, 0x7e, 0x84, 0x10, 0x83, 0x9f, 0x23, 0xe0, 0xa2, 0x45, 0x1a, 0xf6, 0x6d, 0x45, 0x49, 0x4b, - 0x47, 0x52, 0x0f, 0xc9, 0xa0, 0x70, 0x15, 0xad, 0x8e, 0x80, 0xb5, 0xed, 0x35, 0x85, 0xbe, 0xa3, - 0xd1, 0xab, 0xcf, 0x81, 0xb5, 0x89, 0xf2, 0xe0, 0xaf, 0xd1, 0x6a, 0xc4, 0x81, 0xd9, 0x05, 0x55, - 0xab, 0x8f, 0x32, 0xb5, 0x72, 0xa6, 0xbb, 0x42, 0xd6, 0xa8, 0xc5, 0x81, 0x35, 0xfc, 0xb3, 0xc0, - 0x28, 0x49, 0x0b, 0x51, 0x0a, 0xb8, 0x8f, 0xb6, 0xe8, 0x30, 0x04, 0xc6, 0x03, 0x5f, 0x1e, 0x15, - 0xe9, 0xb1, 0x8b, 0x37, 0x52, 0x7d, 0x6f, 0x32, 0xae, 0x6c, 0x35, 0x66, 0x34, 0xc8, 0x9c, 0x2a, - 0xfe, 0x14, 0x15, 0x79, 0x10, 0xb1, 0x0e, 0x34, 0x4e, 0xb8, 0x8d, 0xaa, 0xb9, 0x9d, 0x62, 0x7d, - 0x43, 0x6e, 0x5a, 0x33, 0x31, 0x12, 0xe3, 0xc7, 0x80, 0x8a, 0x81, 0x3a, 0x57, 0x04, 0xce, 0xec, - 0x75, 0x15, 0xcf, 0x81, 0xb3, 0xe4, 0x4c, 0xd1, 0xa7, 0x94, 0xc0, 0x19, 0x30, 0xf0, 0x3b, 0x10, - 0x2f, 0x93, 0x1a, 0x89, 0x51, 0xc6, 0x7d, 0x54, 0x62, 0xc0, 0xc3, 0xc0, 0xe7, 0xd0, 0x14, 0x9e, - 0x88, 0xb8, 0x7d, 0x47, 0xad, 0xb5, 0xbb, 0xdc, 0xe9, 0x8b, 0x39, 0x75, 0x3c, 0x19, 0x57, 0x4a, - 0x64, 0x4a, 0x87, 0xcc, 0xe8, 0x62, 0x0f, 0x6d, 0xe8, 0x1d, 0x8e, 0x03, 0xb1, 0x37, 0xd4, 0x42, - 0x3b, 0x0b, 0x17, 0xd2, 0xb3, 0xc3, 0x69, 0xf9, 0xe7, 0x7e, 0xf0, 0x8b, 0x5f, 0xbf, 0x3b, 0x19, - 0x57, 0x36, 0x48, 0x56, 0x82, 0x4c, 0x2b, 0xe2, 0xae, 0x49, 0x46, 0xaf, 0x51, 0xba, 0xe1, 0x1a, - 0x53, 0x89, 0xe8, 0x45, 0x66, 0x34, 0xf1, 0x6f, 0x16, 0xb2, 0xf5, 0xba, 0x04, 0x3a, 0x40, 0x47, - 0xd0, 0x4d, 0xdb, 0xce, 0xde, 0x54, 0x0b, 0xba, 0xcb, 0x55, 0xef, 0x19, 0xed, 0xb0, 0x40, 0x35, - 0x70, 0x55, 0x1f, 0x4c, 0x9b, 0x2c, 0x10, 0x26, 0x0b, 0x97, 0xc4, 0x01, 0x2a, 0xa9, 0x4e, 0x33, - 0x41, 0x6c, 0xfd, 0xbf, 0x20, 0x92, 0x46, 0x2e, 0x35, 0xa7, 0xe4, 0xc8, 0x8c, 0x7c, 0xed, 0x85, - 0x85, 0x8a, 0x6a, 0x8c, 0x1e, 0x51, 0x2e, 0xf0, 0x0f, 0x73, 0xa3, 0xd4, 0x59, 0x6e, 0x61, 0xc9, - 0x56, 0x83, 0x74, 0x4b, 0xaf, 0x5b, 0x48, 0x2c, 0x99, 0x31, 0xda, 0x44, 0x79, 0x2a, 0x60, 0xc8, - 0xed, 0x5b, 0xd5, 0xdc, 0x8c, 0xf4, 0x7f, 0xb7, 0x80, 0x0a, 0xb0, 0xbe, 0x91, 0xcc, 0xa6, 0x86, - 0x14, 0x21, 0xb1, 0x56, 0xed, 0x0f, 0x0b, 0x95, 0xbe, 0x62, 0x41, 0x14, 0x12, 0x88, 0x1b, 0x8e, - 0xe3, 0x0f, 0x51, 0xbe, 0x27, 0x2d, 0x2a, 0x85, 0xa2, 0xe1, 0xc5, 0xb0, 0xd8, 0x27, 0x1b, 0x98, - 0x25, 0x0c, 0x15, 0x90, 0x6e, 0xe0, 0x54, 0x86, 0x18, 0x3f, 0xde, 0x97, 0xe7, 0x3d, 0xfe, 0x39, - 0xf6, 0x86, 0xc0, 0xed, 0x9c, 0x22, 0xe8, 0x53, 0x9c, 0x71, 0x90, 0x69, 0x5c, 0xed, 0xcf, 0x1c, - 0xda, 0x9c, 0x69, 0x60, 0xbc, 0x8b, 0x0a, 0x09, 0x48, 0x47, 0x98, 0x16, 0x2d, 0xd1, 0x22, 0x29, - 0x02, 0xbb, 0xa8, 0xe8, 0x4b, 0xa9, 0xd0, 0xeb, 0x80, 0xbe, 0x7f, 0xd2, 0x1b, 0xe2, 0x38, 0x71, - 0x10, 0x83, 0x91, 0xf3, 0x56, 0xfe, 0xa8, 0x9b, 0x27, 0x33, 0x6f, 0x25, 0x96, 0x28, 0x0f, 0xae, - 0xa3, 0x5c, 0x44, 0xbb, 0xfa, 0xfe, 0x78, 0xa8, 0x01, 0xb9, 0xd6, 0xb2, 0x77, 0x87, 0x24, 0xcb, - 0x24, 0xbc, 0x90, 0xaa, 0x8a, 0xea, 0xab, 0x23, 0x4d, 0xe2, 0xf0, 0xa4, 0x11, 0x57, 0x3a, 0x45, - 0xc8, 0x7b, 0xc3, 0x0b, 0xe9, 0x73, 0x60, 0x9c, 0x06, 0xfe, 0xec, 0xbd, 0x71, 0x78, 0xd2, 0xd0, - 0x1e, 0x92, 0x41, 0xe1, 0x43, 0xb4, 0x99, 0x14, 0x21, 0x21, 0xc6, 0x57, 0xc8, 0x7d, 0x4d, 0xdc, - 0x24, 0xd3, 0x6e, 0x32, 0x8b, 0xc7, 0x9f, 0xa1, 0x75, 0x1e, 0xb5, 0xd3, 0x62, 0x17, 0x14, 0xfd, - 0x9e, 0xa6, 0xaf, 0x37, 0x8d, 0x8b, 0x64, 0x71, 0xb5, 0x97, 0x16, 0xba, 0x7d, 0x12, 0x0c, 0x68, - 0xe7, 0xe2, 0x1d, 0xbc, 0x2d, 0xbe, 0x45, 0x79, 0x16, 0x0d, 0x20, 0x69, 0x8a, 0xc7, 0x4b, 0x37, - 0x45, 0x1c, 0x21, 0x89, 0x06, 0x60, 0x4e, 0xb8, 0xfc, 0xe3, 0x24, 0x16, 0xac, 0xfd, 0x65, 0x21, - 0x14, 0x83, 0xde, 0x41, 0x6f, 0x9f, 0x4e, 0xf7, 0xb6, 0x7b, 0xc3, 0x34, 0x16, 0x34, 0xf7, 0x8b, - 0x5c, 0x92, 0x82, 0xcc, 0xcc, 0xbc, 0xc3, 0xac, 0x65, 0xde, 0x61, 0x15, 0x94, 0x97, 0x8f, 0x82, - 0xa4, 0xbb, 0x8b, 0x12, 0x29, 0xef, 0x6e, 0x4e, 0x62, 0x3b, 0x76, 0x10, 0x92, 0x1f, 0xea, 0x88, - 0x26, 0x2d, 0x5d, 0x92, 0x1b, 0xd5, 0x4a, 0xad, 0x24, 0x83, 0x90, 0x82, 0xf2, 0xbd, 0xc2, 0xed, - 0x55, 0x23, 0x28, 0x9f, 0x31, 0x9c, 0xc4, 0x76, 0xdc, 0xcf, 0xce, 0x94, 0xbc, 0x2a, 0xc4, 0xfe, - 0xd2, 0x85, 0x98, 0x1e, 0x62, 0xa6, 0xc9, 0xdf, 0x38, 0x90, 0x1c, 0x84, 0xd2, 0x8e, 0xe7, 0xf6, - 0x6d, 0x13, 0x7a, 0x3a, 0x12, 0x38, 0xc9, 0x20, 0xf0, 0x17, 0x68, 0xd3, 0x0f, 0xfc, 0x44, 0xaa, - 0x45, 0x8e, 0xb8, 0xbd, 0xa6, 0x48, 0xf7, 0x64, 0x23, 0x1d, 0x4f, 0xbb, 0xc8, 0x2c, 0x16, 0xef, - 0x23, 0x14, 0x0c, 0xa9, 0x50, 0x77, 0x09, 0xb7, 0x0b, 0x8a, 0x79, 0x5f, 0x1d, 0xe9, 0xd4, 0x6a, - 0xde, 0x8a, 0x19, 0x68, 0xfd, 0xc1, 0xe5, 0x55, 0x79, 0xe5, 0xd5, 0x55, 0x79, 0xe5, 0xf5, 0x55, - 0x79, 0xe5, 0xd7, 0x49, 0xd9, 0xba, 0x9c, 0x94, 0xad, 0x57, 0x93, 0xb2, 0xf5, 0x7a, 0x52, 0xb6, - 0xfe, 0x9e, 0x94, 0xad, 0xdf, 0xff, 0x29, 0xaf, 0x7c, 0xb7, 0xa6, 0x6b, 0xf0, 0x6f, 0x00, 0x00, - 0x00, 0xff, 0xff, 0xfd, 0x2a, 0x16, 0x68, 0xbb, 0x0d, 0x00, 0x00, + 0x24, 0xee, 0xfc, 0x0b, 0x7e, 0x40, 0xc5, 0x81, 0x43, 0x8e, 0x3d, 0xf6, 0x64, 0x11, 0xf3, 0x2f, + 0x22, 0x21, 0xa1, 0x99, 0x9d, 0xdd, 0x59, 0xdb, 0x35, 0x75, 0x38, 0xf4, 0xb6, 0xf3, 0xde, 0xf7, + 0x7d, 0xf3, 0xe6, 0xcd, 0x7b, 0x6f, 0x16, 0x9d, 0x9c, 0x1f, 0x70, 0x87, 0x06, 0xee, 0x79, 0xd4, + 0x06, 0xe6, 0x83, 0x00, 0xee, 0x8e, 0xc0, 0xef, 0x06, 0xcc, 0xd5, 0x0e, 0x2f, 0xa4, 0x1c, 0xd8, + 0x08, 0x98, 0x1b, 0x9e, 0xf7, 0xd4, 0xca, 0xf5, 0xa2, 0x2e, 0x15, 0xee, 0x68, 0xaf, 0x0d, 0xc2, + 0xdb, 0x73, 0x7b, 0xe0, 0x03, 0xf3, 0x04, 0x74, 0x9d, 0x90, 0x05, 0x22, 0xc0, 0x1f, 0xc6, 0x44, + 0x27, 0x25, 0x3a, 0xe1, 0x79, 0x4f, 0xad, 0x1c, 0x45, 0x74, 0x34, 0x71, 0xfb, 0x41, 0x8f, 0x8a, + 0x7e, 0xd4, 0x76, 0x3a, 0xc1, 0xd0, 0xed, 0x05, 0xbd, 0xc0, 0x55, 0xfc, 0x76, 0x74, 0xa6, 0x56, + 0x6a, 0xa1, 0xbe, 0x62, 0xdd, 0xed, 0x5d, 0x13, 0x90, 0xeb, 0x45, 0xa2, 0x0f, 0xbe, 0xa0, 0x1d, + 0x4f, 0xd0, 0xc0, 0x77, 0x47, 0x73, 0x51, 0x6c, 0x3f, 0x31, 0xe8, 0xa1, 0xd7, 0xe9, 0x53, 0x1f, + 0xd8, 0x85, 0x39, 0xc1, 0x10, 0x84, 0xf7, 0x3a, 0x96, 0xbb, 0x88, 0xc5, 0x22, 0x5f, 0xd0, 0x21, + 0xcc, 0x11, 0x3e, 0x7d, 0x13, 0x81, 0x77, 0xfa, 0x30, 0xf4, 0xe6, 0x78, 0x8f, 0x17, 0xf1, 0x22, + 0x41, 0x07, 0x2e, 0xf5, 0x05, 0x17, 0x6c, 0x8e, 0x74, 0xf0, 0xe6, 0x2b, 0xf1, 0x06, 0x61, 0x7f, + 0xfe, 0x4e, 0x6a, 0x7f, 0x22, 0x94, 0xff, 0x7c, 0x04, 0xbe, 0xc0, 0xdf, 0xa3, 0x82, 0x3c, 0x7c, + 0xd7, 0x13, 0x9e, 0x6d, 0x55, 0xad, 0x9d, 0xf5, 0x47, 0x0f, 0x1d, 0x73, 0x61, 0x69, 0x2c, 0xe6, + 0xce, 0x24, 0xda, 0x19, 0xed, 0x39, 0x5f, 0xb5, 0x7f, 0x80, 0x8e, 0x78, 0x06, 0xc2, 0xab, 0xe3, + 0xcb, 0x71, 0x65, 0x65, 0x32, 0xae, 0x20, 0x63, 0x23, 0xa9, 0x2a, 0xde, 0x45, 0xf9, 0x01, 0x8c, + 0x60, 0x60, 0xdf, 0xaa, 0x5a, 0x3b, 0xc5, 0xfa, 0xbb, 0x1a, 0x9c, 0x3f, 0x92, 0xc6, 0xeb, 0xe4, + 0x83, 0xc4, 0x20, 0xfc, 0x2d, 0x2a, 0xca, 0x3c, 0x71, 0xe1, 0x0d, 0x43, 0x3b, 0xa7, 0x02, 0xfa, + 0x68, 0xb9, 0x80, 0x4e, 0xe9, 0x10, 0xea, 0x77, 0xb5, 0x7a, 0xf1, 0x34, 0x11, 0x21, 0x46, 0x0f, + 0x1f, 0xa3, 0x35, 0x95, 0x98, 0xc6, 0x53, 0x7b, 0x55, 0x05, 0xf3, 0x44, 0xc3, 0xd7, 0x0e, 0x63, + 0xf3, 0xf5, 0xb8, 0xf2, 0xde, 0xa2, 0x9b, 0x10, 0x17, 0x21, 0x70, 0xa7, 0xd5, 0x78, 0x4a, 0x12, + 0x11, 0x79, 0x34, 0x2e, 0xbc, 0x1e, 0xd8, 0xf9, 0xe9, 0xa3, 0x35, 0xa5, 0xf1, 0x3a, 0xf9, 0x20, + 0x31, 0x08, 0x3f, 0x42, 0x88, 0xc1, 0x8f, 0x11, 0x70, 0xd1, 0x22, 0x0d, 0xfb, 0xb6, 0xa2, 0xa4, + 0xa9, 0x23, 0xa9, 0x87, 0x64, 0x50, 0xb8, 0x8a, 0x56, 0x47, 0xc0, 0xda, 0xf6, 0x9a, 0x42, 0xdf, + 0xd1, 0xe8, 0xd5, 0xe7, 0xc0, 0xda, 0x44, 0x79, 0xf0, 0x97, 0x68, 0x35, 0xe2, 0xc0, 0xec, 0x82, + 0xca, 0xd5, 0x07, 0x99, 0x5c, 0x39, 0xd3, 0x5d, 0x21, 0x73, 0xd4, 0xe2, 0xc0, 0x1a, 0xfe, 0x59, + 0x60, 0x94, 0xa4, 0x85, 0x28, 0x05, 0xdc, 0x47, 0x5b, 0x74, 0x18, 0x02, 0xe3, 0x81, 0x2f, 0x4b, + 0x45, 0x7a, 0xec, 0xe2, 0x8d, 0x54, 0xdf, 0x99, 0x8c, 0x2b, 0x5b, 0x8d, 0x19, 0x0d, 0x32, 0xa7, + 0x8a, 0x3f, 0x46, 0x45, 0x1e, 0x44, 0xac, 0x03, 0x8d, 0x13, 0x6e, 0xa3, 0x6a, 0x6e, 0xa7, 0x58, + 0xdf, 0x90, 0x97, 0xd6, 0x4c, 0x8c, 0xc4, 0xf8, 0x31, 0xa0, 0x62, 0xa0, 0xea, 0x8a, 0xc0, 0x99, + 0xbd, 0xae, 0xe2, 0x39, 0x70, 0x96, 0x9c, 0x29, 0xba, 0x4a, 0x09, 0x9c, 0x01, 0x03, 0xbf, 0x03, + 0xf1, 0x36, 0xa9, 0x91, 0x18, 0x65, 0xdc, 0x47, 0x25, 0x06, 0x3c, 0x0c, 0x7c, 0x0e, 0x4d, 0xe1, + 0x89, 0x88, 0xdb, 0x77, 0xd4, 0x5e, 0xbb, 0xcb, 0x55, 0x5f, 0xcc, 0xa9, 0xe3, 0xc9, 0xb8, 0x52, + 0x22, 0x53, 0x3a, 0x64, 0x46, 0x17, 0x7b, 0x68, 0x43, 0xdf, 0x70, 0x1c, 0x88, 0xbd, 0xa1, 0x36, + 0xda, 0x59, 0xb8, 0x91, 0x9e, 0x1d, 0x4e, 0xcb, 0x3f, 0xf7, 0x83, 0x9f, 0xfc, 0xfa, 0xdd, 0xc9, + 0xb8, 0xb2, 0x41, 0xb2, 0x12, 0x64, 0x5a, 0x11, 0x77, 0xcd, 0x61, 0xf4, 0x1e, 0xa5, 0x1b, 0xee, + 0x31, 0x75, 0x10, 0xbd, 0xc9, 0x8c, 0x26, 0xfe, 0xc5, 0x42, 0xb6, 0xde, 0x97, 0x40, 0x07, 0xe8, + 0x08, 0xba, 0x69, 0xdb, 0xd9, 0x9b, 0x6a, 0x43, 0x77, 0xb9, 0xec, 0x3d, 0xa3, 0x1d, 0x16, 0xa8, + 0x06, 0xae, 0xea, 0xc2, 0xb4, 0xc9, 0x02, 0x61, 0xb2, 0x70, 0x4b, 0x1c, 0xa0, 0x92, 0xea, 0x34, + 0x13, 0xc4, 0xd6, 0xff, 0x0b, 0x22, 0x69, 0xe4, 0x52, 0x73, 0x4a, 0x8e, 0xcc, 0xc8, 0xd7, 0x5e, + 0x58, 0xa8, 0xa8, 0xc6, 0xe8, 0x11, 0xe5, 0x02, 0x7f, 0x37, 0x37, 0x4a, 0x9d, 0xe5, 0x36, 0x96, + 0x6c, 0x35, 0x48, 0xb7, 0xf4, 0xbe, 0x85, 0xc4, 0x92, 0x19, 0xa3, 0x4d, 0x94, 0xa7, 0x02, 0x86, + 0xdc, 0xbe, 0x55, 0xcd, 0xcd, 0x48, 0xff, 0x77, 0x0b, 0xa8, 0x00, 0xeb, 0x1b, 0xc9, 0x6c, 0x6a, + 0x48, 0x11, 0x12, 0x6b, 0xd5, 0x7e, 0xb3, 0x50, 0xe9, 0x0b, 0x16, 0x44, 0x21, 0x81, 0xb8, 0xe1, + 0x38, 0x7e, 0x1f, 0xe5, 0x7b, 0xd2, 0xa2, 0x8e, 0x50, 0x34, 0xbc, 0x18, 0x16, 0xfb, 0x64, 0x03, + 0xb3, 0x84, 0xa1, 0x02, 0xd2, 0x0d, 0x9c, 0xca, 0x10, 0xe3, 0xc7, 0xfb, 0xb2, 0xde, 0xe3, 0xc5, + 0xb1, 0x37, 0x04, 0x6e, 0xe7, 0x14, 0x41, 0x57, 0x71, 0xc6, 0x41, 0xa6, 0x71, 0xb5, 0xdf, 0x73, + 0x68, 0x73, 0xa6, 0x81, 0xf1, 0x2e, 0x2a, 0x24, 0x20, 0x1d, 0x61, 0x9a, 0xb4, 0x44, 0x8b, 0xa4, + 0x08, 0xec, 0xa2, 0xa2, 0x2f, 0xa5, 0x42, 0xaf, 0x03, 0xfa, 0xfd, 0x49, 0x5f, 0x88, 0xe3, 0xc4, + 0x41, 0x0c, 0x46, 0xce, 0x5b, 0xb9, 0x50, 0x2f, 0x4f, 0x66, 0xde, 0x4a, 0x2c, 0x51, 0x1e, 0x5c, + 0x47, 0xb9, 0x88, 0x76, 0xf5, 0xfb, 0xf1, 0x50, 0x03, 0x72, 0xad, 0x65, 0xdf, 0x0e, 0x49, 0x96, + 0x87, 0xf0, 0x42, 0xaa, 0x32, 0xaa, 0x9f, 0x8e, 0xf4, 0x10, 0x87, 0x27, 0x8d, 0x38, 0xd3, 0x29, + 0x42, 0xbe, 0x1b, 0x5e, 0x48, 0x9f, 0x03, 0xe3, 0x34, 0xf0, 0x67, 0xdf, 0x8d, 0xc3, 0x93, 0x86, + 0xf6, 0x90, 0x0c, 0x0a, 0x1f, 0xa2, 0xcd, 0x24, 0x09, 0x09, 0x31, 0x7e, 0x42, 0xee, 0x6b, 0xe2, + 0x26, 0x99, 0x76, 0x93, 0x59, 0x3c, 0xfe, 0x04, 0xad, 0xf3, 0xa8, 0x9d, 0x26, 0xbb, 0xa0, 0xe8, + 0xf7, 0x34, 0x7d, 0xbd, 0x69, 0x5c, 0x24, 0x8b, 0xab, 0xfd, 0x63, 0xa1, 0xdb, 0x27, 0xc1, 0x80, + 0x76, 0x2e, 0xde, 0xc2, 0xbf, 0xc5, 0xd7, 0x28, 0xcf, 0xa2, 0x01, 0x24, 0x4d, 0xf1, 0x78, 0xe9, + 0xa6, 0x88, 0x23, 0x24, 0xd1, 0x00, 0x4c, 0x85, 0xcb, 0x15, 0x27, 0xb1, 0x20, 0xde, 0x47, 0x28, + 0x18, 0x52, 0xa1, 0x06, 0x40, 0x52, 0xb1, 0xf7, 0x55, 0x1c, 0xa9, 0xd5, 0x3c, 0xf0, 0x19, 0x68, + 0xed, 0x0f, 0x0b, 0xa1, 0x58, 0xfd, 0x2d, 0x0c, 0x85, 0xd3, 0xe9, 0xa1, 0xe0, 0xde, 0xf0, 0xfc, + 0x0b, 0xa6, 0xc2, 0x8b, 0x5c, 0x72, 0x04, 0x99, 0x12, 0xf3, 0x03, 0x67, 0x2d, 0xf3, 0x03, 0x57, + 0x41, 0x79, 0xf9, 0x37, 0x91, 0x8c, 0x85, 0xa2, 0x44, 0xca, 0x47, 0x9f, 0x93, 0xd8, 0x8e, 0x1d, + 0x84, 0xe4, 0x87, 0xaa, 0xed, 0x24, 0xb3, 0x25, 0x99, 0xd9, 0x56, 0x6a, 0x25, 0x19, 0x84, 0x14, + 0x94, 0x3f, 0x3a, 0xdc, 0x5e, 0x35, 0x82, 0xf2, 0xff, 0x87, 0x93, 0xd8, 0x8e, 0xfb, 0xd9, 0x61, + 0x94, 0x57, 0x89, 0xd8, 0x5f, 0x3a, 0x11, 0xd3, 0xd3, 0xcf, 0x4c, 0x87, 0xd7, 0x4e, 0x32, 0x07, + 0xa1, 0x74, 0x54, 0x70, 0xfb, 0xb6, 0x09, 0x3d, 0x9d, 0x25, 0x9c, 0x64, 0x10, 0xf8, 0x33, 0xb4, + 0xe9, 0x07, 0x7e, 0x22, 0xd5, 0x22, 0x47, 0xdc, 0x5e, 0x53, 0xa4, 0x7b, 0xb2, 0x03, 0x8f, 0xa7, + 0x5d, 0x64, 0x16, 0x3b, 0x53, 0x83, 0x85, 0xa5, 0x6b, 0xb0, 0xfe, 0xe0, 0xf2, 0xaa, 0xbc, 0xf2, + 0xf2, 0xaa, 0xbc, 0xf2, 0xea, 0xaa, 0xbc, 0xf2, 0xf3, 0xa4, 0x6c, 0x5d, 0x4e, 0xca, 0xd6, 0xcb, + 0x49, 0xd9, 0x7a, 0x35, 0x29, 0x5b, 0x7f, 0x4d, 0xca, 0xd6, 0xaf, 0x7f, 0x97, 0x57, 0xbe, 0x59, + 0xd3, 0x39, 0xf8, 0x37, 0x00, 0x00, 0xff, 0xff, 0x04, 0xbb, 0x40, 0x37, 0xf4, 0x0d, 0x00, 0x00, } diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/generated.proto b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/generated.proto index 830285c3f00..bbac7f2b708 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/generated.proto +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -174,6 +174,11 @@ message Policy { // The default audit level is None, but can be overridden by a catch-all rule at the end of the list. // PolicyRules are strictly ordered. repeated PolicyRule rules = 2; + + // OmitStages is a list of stages for which no events are created. Note that this can also + // be specified per rule in which case the union of both are omitted. + // +optional + repeated string omitStages = 3; } // PolicyList is a list of audit Policies. @@ -224,8 +229,10 @@ message PolicyRule { // +optional repeated string nonResourceURLs = 7; - // OmitStages specify events generated in which stages will not be emitted to backend. + // OmitStages is a list of stages for which no events are created. Note that this can also + // be specified policy wide in which case the union of both are omitted. // An empty list means no restrictions will apply. + // +optional repeated string omitStages = 8; } diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/types.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/types.go index b76170f4492..259599c050e 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/types.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/types.go @@ -156,6 +156,11 @@ type Policy struct { // The default audit level is None, but can be overridden by a catch-all rule at the end of the list. // PolicyRules are strictly ordered. Rules []PolicyRule `json:"rules" protobuf:"bytes,2,rep,name=rules"` + + // OmitStages is a list of stages for which no events are created. Note that this can also + // be specified per rule in which case the union of both are omitted. + // +optional + OmitStages []Stage `json:"omitStages,omitempty" protobuf:"bytes,3,rep,name=omitStages"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -211,8 +216,10 @@ type PolicyRule struct { // +optional NonResourceURLs []string `json:"nonResourceURLs,omitempty" protobuf:"bytes,7,rep,name=nonResourceURLs"` - // OmitStages specify events generated in which stages will not be emitted to backend. + // OmitStages is a list of stages for which no events are created. Note that this can also + // be specified policy wide in which case the union of both are omitted. // An empty list means no restrictions will apply. + // +optional OmitStages []Stage `json:"omitStages,omitempty" protobuf:"bytes,8,rep,name=omitStages"` } diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/zz_generated.conversion.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/zz_generated.conversion.go index 5a73a9fa579..3bb79c7b7ae 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/zz_generated.conversion.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,13 +21,14 @@ limitations under the License. package v1beta1 import ( + unsafe "unsafe" + authentication_v1 "k8s.io/api/authentication/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" types "k8s.io/apimachinery/pkg/types" audit "k8s.io/apiserver/pkg/apis/audit" - unsafe "unsafe" ) func init() { @@ -202,6 +203,7 @@ func Convert_audit_ObjectReference_To_v1beta1_ObjectReference(in *audit.ObjectRe func autoConvert_v1beta1_Policy_To_audit_Policy(in *Policy, out *audit.Policy, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta out.Rules = *(*[]audit.PolicyRule)(unsafe.Pointer(&in.Rules)) + out.OmitStages = *(*[]audit.Stage)(unsafe.Pointer(&in.OmitStages)) return nil } @@ -213,6 +215,7 @@ func Convert_v1beta1_Policy_To_audit_Policy(in *Policy, out *audit.Policy, s con func autoConvert_audit_Policy_To_v1beta1_Policy(in *audit.Policy, out *Policy, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta out.Rules = *(*[]PolicyRule)(unsafe.Pointer(&in.Rules)) + out.OmitStages = *(*[]Stage)(unsafe.Pointer(&in.OmitStages)) return nil } diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/zz_generated.deepcopy.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/zz_generated.deepcopy.go index 8b1014a7fd0..67f5fdac322 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -23,52 +23,9 @@ package v1beta1 import ( v1 "k8s.io/api/authentication/v1" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Event).DeepCopyInto(out.(*Event)) - return nil - }, InType: reflect.TypeOf(&Event{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*EventList).DeepCopyInto(out.(*EventList)) - return nil - }, InType: reflect.TypeOf(&EventList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*GroupResources).DeepCopyInto(out.(*GroupResources)) - return nil - }, InType: reflect.TypeOf(&GroupResources{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ObjectReference).DeepCopyInto(out.(*ObjectReference)) - return nil - }, InType: reflect.TypeOf(&ObjectReference{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Policy).DeepCopyInto(out.(*Policy)) - return nil - }, InType: reflect.TypeOf(&Policy{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PolicyList).DeepCopyInto(out.(*PolicyList)) - return nil - }, InType: reflect.TypeOf(&PolicyList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PolicyRule).DeepCopyInto(out.(*PolicyRule)) - return nil - }, InType: reflect.TypeOf(&PolicyRule{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Event) DeepCopyInto(out *Event) { *out = *in @@ -238,6 +195,11 @@ func (in *Policy) DeepCopyInto(out *Policy) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.OmitStages != nil { + in, out := &in.OmitStages, &out.OmitStages + *out = make([]Stage, len(*in)) + copy(*out, *in) + } return } diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/zz_generated.defaults.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/zz_generated.defaults.go index e24e70be38b..b61dda74c23 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/zz_generated.defaults.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/validation/BUILD b/staging/src/k8s.io/apiserver/pkg/apis/audit/validation/BUILD index 956db3591a8..60740ace095 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/validation/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/validation/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["validation_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/apis/audit/validation", - library = ":go_default_library", deps = ["//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library"], ) 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 6520a763948..f80aba01ffe 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 @@ -26,6 +26,7 @@ import ( func ValidatePolicy(policy *audit.Policy) field.ErrorList { var allErrs field.ErrorList + allErrs = append(allErrs, validateOmitStages(policy.OmitStages, field.NewPath("omitStages"))...) rulePath := field.NewPath("rules") for i, rule := range policy.Rules { allErrs = append(allErrs, validatePolicyRule(rule, rulePath.Index(i))...) diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/validation/validation_test.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/validation/validation_test.go index 53d60782a40..a25d5dfcaa6 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/validation/validation_test.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/validation/validation_test.go @@ -54,7 +54,9 @@ func TestValidatePolicy(t *testing.T) { for _, rule := range validRules { successCases = append(successCases, audit.Policy{Rules: []audit.PolicyRule{rule}}) } - successCases = append(successCases, audit.Policy{}) // Empty policy is valid. + successCases = append(successCases, audit.Policy{}) // Empty policy is valid. + successCases = append(successCases, audit.Policy{OmitStages: []audit.Stage{ // Policy with omitStages + audit.Stage("RequestReceived")}}) successCases = append(successCases, audit.Policy{Rules: validRules}) // Multiple rules. for i, policy := range successCases { @@ -113,7 +115,7 @@ func TestValidatePolicy(t *testing.T) { Resources: []audit.GroupResources{{ResourceNames: []string{"leader"}}}, Namespaces: []string{"kube-system"}, }, - { // invalid omitStages + { // invalid omitStages in rule Level: audit.LevelMetadata, OmitStages: []audit.Stage{ audit.Stage("foo"), @@ -124,7 +126,21 @@ func TestValidatePolicy(t *testing.T) { for _, rule := range invalidRules { errorCases = append(errorCases, audit.Policy{Rules: []audit.PolicyRule{rule}}) } - errorCases = append(errorCases, audit.Policy{Rules: append(validRules, audit.PolicyRule{})}) // Multiple rules. + + // Multiple rules. + errorCases = append(errorCases, audit.Policy{Rules: append(validRules, audit.PolicyRule{})}) + + // invalid omitStages in policy + policy := audit.Policy{OmitStages: []audit.Stage{ + audit.Stage("foo"), + }, + Rules: []audit.PolicyRule{ + { + Level: audit.LevelMetadata, + }, + }, + } + errorCases = append(errorCases, policy) for i, policy := range errorCases { if errs := ValidatePolicy(&policy); len(errs) == 0 { diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/zz_generated.deepcopy.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/zz_generated.deepcopy.go index 62b1f23d71b..c52aea05a07 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,51 +22,9 @@ package audit import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -// GetGeneratedDeepCopyFuncs returns the generated funcs, since we aren't registering them. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func GetGeneratedDeepCopyFuncs() []conversion.GeneratedDeepCopyFunc { - return []conversion.GeneratedDeepCopyFunc{ - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Event).DeepCopyInto(out.(*Event)) - return nil - }, InType: reflect.TypeOf(&Event{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*EventList).DeepCopyInto(out.(*EventList)) - return nil - }, InType: reflect.TypeOf(&EventList{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*GroupResources).DeepCopyInto(out.(*GroupResources)) - return nil - }, InType: reflect.TypeOf(&GroupResources{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*ObjectReference).DeepCopyInto(out.(*ObjectReference)) - return nil - }, InType: reflect.TypeOf(&ObjectReference{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Policy).DeepCopyInto(out.(*Policy)) - return nil - }, InType: reflect.TypeOf(&Policy{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PolicyList).DeepCopyInto(out.(*PolicyList)) - return nil - }, InType: reflect.TypeOf(&PolicyList{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PolicyRule).DeepCopyInto(out.(*PolicyRule)) - return nil - }, InType: reflect.TypeOf(&PolicyRule{})}, - {Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*UserInfo).DeepCopyInto(out.(*UserInfo)) - return nil - }, InType: reflect.TypeOf(&UserInfo{})}, - } -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Event) DeepCopyInto(out *Event) { *out = *in @@ -234,6 +192,11 @@ func (in *Policy) DeepCopyInto(out *Policy) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.OmitStages != nil { + in, out := &in.OmitStages, &out.OmitStages + *out = make([]Stage, len(*in)) + copy(*out, *in) + } return } diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example/BUILD b/staging/src/k8s.io/apiserver/pkg/apis/example/BUILD index ddc12bc71b8..3bd547f02b5 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/example/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/apis/example/BUILD @@ -16,7 +16,6 @@ go_library( importpath = "k8s.io/apiserver/pkg/apis/example", deps = [ "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example/doc.go b/staging/src/k8s.io/apiserver/pkg/apis/example/doc.go index cb410d443b4..2676eee8197 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/example/doc.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/example/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +groupName=example.k8s.io // // package example contains an example API used to demonstrate how to create api groups. Moreover, this is diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example/install/BUILD b/staging/src/k8s.io/apiserver/pkg/apis/example/install/BUILD index 69f1cbb7b70..51f33af0079 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/example/install/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/apis/example/install/BUILD @@ -22,8 +22,8 @@ go_library( go_test( name = "go_default_test", srcs = ["roundtrip_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/apis/example/install", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/api/testing/roundtrip:go_default_library", "//vendor/k8s.io/apiserver/pkg/apis/example/fuzzer:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example/install/install.go b/staging/src/k8s.io/apiserver/pkg/apis/example/install/install.go index e44a29e5a9c..5352108e789 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/example/install/install.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/example/install/install.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package install installs the certificates API group, making it available as +// Package install installs the example API group, making it available as // an option to all of the API encoding/decoding machinery. package install diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example/register.go b/staging/src/k8s.io/apiserver/pkg/apis/example/register.go index 2b699c4b495..d25456b0c54 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/example/register.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/example/register.go @@ -42,10 +42,11 @@ func Resource(resource string) schema.GroupResource { return SchemeGroupVersion.WithResource(resource).GroupResource() } -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Pod{}, + &ReplicaSet{}, ) return nil } diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example/types.go b/staging/src/k8s.io/apiserver/pkg/apis/example/types.go index 6dd3e217242..243c1c03306 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/example/types.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/example/types.go @@ -136,3 +136,35 @@ type PodList struct { Items []Pod } + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ReplicaSet ensures that a specified number of pod replicas are running at any given time. +type ReplicaSet struct { + metav1.TypeMeta + // +optional + metav1.ObjectMeta + + // Spec defines the desired behavior of this ReplicaSet. + // +optional + Spec ReplicaSetSpec + + // Status is the current status of this ReplicaSet. This data may be + // out of date by some window of time. + // +optional + Status ReplicaSetStatus +} + +// ReplicaSetSpec is the specification of a ReplicaSet. +// As the internal representation of a ReplicaSet, it must have +// a Template set. +type ReplicaSetSpec struct { + // Replicas is the number of desired replicas. + Replicas int32 +} + +// ReplicaSetStatus represents the current status of a ReplicaSet. +type ReplicaSetStatus struct { + // Replicas is the number of actual replicas. + Replicas int32 +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example/v1/doc.go b/staging/src/k8s.io/apiserver/pkg/apis/example/v1/doc.go index 819ae5ef7a8..4b22d37fb27 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/example/v1/doc.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/example/v1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:deepcopy-gen=package,register +// +k8s:deepcopy-gen=package // +k8s:conversion-gen=k8s.io/apiserver/pkg/apis/example // +k8s:openapi-gen=false // +k8s:defaulter-gen=TypeMeta diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example/v1/generated.pb.go b/staging/src/k8s.io/apiserver/pkg/apis/example/v1/generated.pb.go index 2d6596bef94..d0055ba037f 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/example/v1/generated.pb.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/example/v1/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example/v1/generated.proto b/staging/src/k8s.io/apiserver/pkg/apis/example/v1/generated.proto index 72075566c79..de59bc3ddaa 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/example/v1/generated.proto +++ b/staging/src/k8s.io/apiserver/pkg/apis/example/v1/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example/v1/register.go b/staging/src/k8s.io/apiserver/pkg/apis/example/v1/register.go index f7b6c7df61a..cfb74eea47e 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/example/v1/register.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/example/v1/register.go @@ -53,7 +53,7 @@ func init() { localSchemeBuilder.Register(addKnownTypes, addConversionFuncs, addDefaultingFuncs) } -// Adds the list of known types to api.Scheme. +// Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Pod{}, diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example/v1/zz_generated.conversion.go b/staging/src/k8s.io/apiserver/pkg/apis/example/v1/zz_generated.conversion.go index 8fa603fde76..848690ea1a8 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/example/v1/zz_generated.conversion.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/example/v1/zz_generated.conversion.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -21,11 +21,12 @@ limitations under the License. package v1 import ( + unsafe "unsafe" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" example "k8s.io/apiserver/pkg/apis/example" - unsafe "unsafe" ) func init() { diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example/v1/zz_generated.deepcopy.go b/staging/src/k8s.io/apiserver/pkg/apis/example/v1/zz_generated.deepcopy.go index 2187f876ede..8a28b30b557 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/example/v1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/example/v1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,44 +22,9 @@ package v1 import ( meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Pod).DeepCopyInto(out.(*Pod)) - return nil - }, InType: reflect.TypeOf(&Pod{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodCondition).DeepCopyInto(out.(*PodCondition)) - return nil - }, InType: reflect.TypeOf(&PodCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodList).DeepCopyInto(out.(*PodList)) - return nil - }, InType: reflect.TypeOf(&PodList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodSpec).DeepCopyInto(out.(*PodSpec)) - return nil - }, InType: reflect.TypeOf(&PodSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodStatus).DeepCopyInto(out.(*PodStatus)) - return nil - }, InType: reflect.TypeOf(&PodStatus{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Pod) DeepCopyInto(out *Pod) { *out = *in diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example/v1/zz_generated.defaults.go b/staging/src/k8s.io/apiserver/pkg/apis/example/v1/zz_generated.defaults.go index 6df448eb9fd..88d7af085be 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/example/v1/zz_generated.defaults.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/example/v1/zz_generated.defaults.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example/zz_generated.deepcopy.go b/staging/src/k8s.io/apiserver/pkg/apis/example/zz_generated.deepcopy.go index 02ba7f8c236..7a102f3e1ec 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/example/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/example/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2017 The Kubernetes Authors. +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. @@ -22,44 +22,9 @@ package example import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - reflect "reflect" ) -func init() { - SchemeBuilder.Register(RegisterDeepCopies) -} - -// RegisterDeepCopies adds deep-copy functions to the given scheme. Public -// to allow building arbitrary schemes. -// -// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. -func RegisterDeepCopies(scheme *runtime.Scheme) error { - return scheme.AddGeneratedDeepCopyFuncs( - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*Pod).DeepCopyInto(out.(*Pod)) - return nil - }, InType: reflect.TypeOf(&Pod{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodCondition).DeepCopyInto(out.(*PodCondition)) - return nil - }, InType: reflect.TypeOf(&PodCondition{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodList).DeepCopyInto(out.(*PodList)) - return nil - }, InType: reflect.TypeOf(&PodList{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodSpec).DeepCopyInto(out.(*PodSpec)) - return nil - }, InType: reflect.TypeOf(&PodSpec{})}, - conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { - in.(*PodStatus).DeepCopyInto(out.(*PodStatus)) - return nil - }, InType: reflect.TypeOf(&PodStatus{})}, - ) -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Pod) DeepCopyInto(out *Pod) { *out = *in @@ -213,3 +178,64 @@ func (in *PodStatus) DeepCopy() *PodStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplicaSet) DeepCopyInto(out *ReplicaSet) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicaSet. +func (in *ReplicaSet) DeepCopy() *ReplicaSet { + if in == nil { + return nil + } + out := new(ReplicaSet) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ReplicaSet) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplicaSetSpec) DeepCopyInto(out *ReplicaSetSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicaSetSpec. +func (in *ReplicaSetSpec) DeepCopy() *ReplicaSetSpec { + if in == nil { + return nil + } + out := new(ReplicaSetSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplicaSetStatus) DeepCopyInto(out *ReplicaSetStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicaSetStatus. +func (in *ReplicaSetStatus) DeepCopy() *ReplicaSetStatus { + if in == nil { + return nil + } + out := new(ReplicaSetStatus) + in.DeepCopyInto(out) + return out +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/BUILD b/staging/src/k8s.io/apiserver/pkg/apis/example2/BUILD new file mode 100644 index 00000000000..d0948caf75b --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/BUILD @@ -0,0 +1,38 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "register.go", + "zz_generated.deepcopy.go", + ], + importpath = "k8s.io/apiserver/pkg/apis/example2", + deps = [ + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/example:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//staging/src/k8s.io/apiserver/pkg/apis/example2/install:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/apis/example2/v1:all-srcs", + ], + tags = ["automanaged"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/doc.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/doc.go new file mode 100644 index 00000000000..ae0ecc109ea --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/doc.go @@ -0,0 +1,24 @@ +/* +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 +// +groupName=example2.k8s.io +// +// package example2 contains an example API whose internal version is defined in +// another group ("example"). This happens if a type is moved to a different +// group. It's not recommended to move types across groups, though Kubernetes +// have a few cases due to historical reasons. This package is for tests. +package example2 // import "k8s.io/apiserver/pkg/apis/example2" diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/install/BUILD b/staging/src/k8s.io/apiserver/pkg/apis/example2/install/BUILD new file mode 100644 index 00000000000..9909a6a7beb --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/install/BUILD @@ -0,0 +1,45 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_library( + name = "go_default_library", + srcs = ["install.go"], + importpath = "k8s.io/apiserver/pkg/apis/example2/install", + deps = [ + "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/example:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/example2:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/example2/v1:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["roundtrip_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/apiserver/pkg/apis/example2/install", + deps = [ + "//vendor/k8s.io/apimachinery/pkg/api/testing/roundtrip:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/example/fuzzer:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/install/install.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/install/install.go new file mode 100644 index 00000000000..8980ecbefd4 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/install/install.go @@ -0,0 +1,44 @@ +/* +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 install installs the example2 API group, making it available as +// an option to all of the API encoding/decoding machinery. +package install + +import ( + "k8s.io/apimachinery/pkg/apimachinery/announced" + "k8s.io/apimachinery/pkg/apimachinery/registered" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/apis/example" + "k8s.io/apiserver/pkg/apis/example2" + example2v1 "k8s.io/apiserver/pkg/apis/example2/v1" +) + +// Install registers the API group and adds types to a scheme +func Install(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *registered.APIRegistrationManager, scheme *runtime.Scheme) { + if err := announced.NewGroupMetaFactory( + &announced.GroupMetaFactoryArgs{ + GroupName: example2.GroupName, + VersionPreferenceOrder: []string{example2v1.SchemeGroupVersion.Version}, + AddInternalObjectsToScheme: example.AddToScheme, + }, + announced.VersionToSchemeFunc{ + example2v1.SchemeGroupVersion.Version: example2v1.AddToScheme, + }, + ).Announce(groupFactoryRegistry).RegisterAndEnable(registry, scheme); err != nil { + panic(err) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/install/roundtrip_test.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/install/roundtrip_test.go new file mode 100644 index 00000000000..7f8d4e82a6a --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/install/roundtrip_test.go @@ -0,0 +1,28 @@ +/* +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 install + +import ( + "testing" + + "k8s.io/apimachinery/pkg/api/testing/roundtrip" + examplefuzzer "k8s.io/apiserver/pkg/apis/example/fuzzer" +) + +func TestRoundTrip(t *testing.T) { + roundtrip.RoundTripTestForAPIGroup(t, Install, examplefuzzer.Funcs) +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/register.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/register.go new file mode 100644 index 00000000000..c9b2fd79afc --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/register.go @@ -0,0 +1,52 @@ +/* +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 example2 + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/apis/example" +) + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +// GroupName is the group name use in this package +const GroupName = "example2.apiserver.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() +} + +// Adds the list of known types to the given scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &example.ReplicaSet{}, + ) + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/BUILD b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/BUILD new file mode 100644 index 00000000000..92280a1a1c1 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/BUILD @@ -0,0 +1,50 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = [ + "conversion.go", + "defaults.go", + "doc.go", + "generated.pb.go", + "register.go", + "types.go", + "types_swagger_doc_generated.go", + "zz_generated.conversion.go", + "zz_generated.deepcopy.go", + "zz_generated.defaults.go", + ], + importpath = "k8s.io/apiserver/pkg/apis/example2/v1", + deps = [ + "//vendor/github.com/gogo/protobuf/proto:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/example:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) + +filegroup( + name = "go_default_library_protos", + srcs = ["generated.proto"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/conversion.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/conversion.go new file mode 100644 index 00000000000..21abdefd5d6 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/conversion.go @@ -0,0 +1,51 @@ +/* +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 v1 + +import ( + conversion "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime" + example "k8s.io/apiserver/pkg/apis/example" +) + +func addConversionFuncs(scheme *runtime.Scheme) error { + // Add non-generated conversion functions to handle the *int32 -> int32 + // conversion. A pointer is useful in the versioned type so we can default + // it, but a plain int32 is more convenient in the internal type. These + // functions are the same as the autogenerated ones in every other way. + err := scheme.AddConversionFuncs( + Convert_example_ReplicaSetSpec_To_v1_ReplicaSetSpec, + Convert_v1_ReplicaSetSpec_To_example_ReplicaSetSpec, + ) + if err != nil { + return err + } + return nil +} + +func Convert_example_ReplicaSetSpec_To_v1_ReplicaSetSpec(in *example.ReplicaSetSpec, out *ReplicaSetSpec, s conversion.Scope) error { + out.Replicas = new(int32) + *out.Replicas = int32(in.Replicas) + return nil +} + +func Convert_v1_ReplicaSetSpec_To_example_ReplicaSetSpec(in *ReplicaSetSpec, out *example.ReplicaSetSpec, s conversion.Scope) error { + if in.Replicas != nil { + out.Replicas = *in.Replicas + } + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/defaults.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/defaults.go new file mode 100644 index 00000000000..436ccde2964 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/defaults.go @@ -0,0 +1,26 @@ +/* +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 v1 + +import ( + "k8s.io/apimachinery/pkg/runtime" +) + +func addDefaultingFuncs(scheme *runtime.Scheme) error { + // return RegisterDefaults(scheme) + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/doc.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/doc.go new file mode 100644 index 00000000000..5784d44f398 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/doc.go @@ -0,0 +1,24 @@ +/* +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/apis/example2 +// +k8s:conversion-gen=k8s.io/apiserver/pkg/apis/example +// +k8s:openapi-gen=false +// +k8s:defaulter-gen=TypeMeta + +// +groupName=example2.apiserver.k8s.io +package v1 // import "k8s.io/apiserver/pkg/apis/example2/v1" diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/generated.pb.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/generated.pb.go new file mode 100644 index 00000000000..310a41f6c29 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/generated.pb.go @@ -0,0 +1,682 @@ +/* +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. +*/ + +// Code generated by protoc-gen-gogo. +// source: k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/apis/example2/v1/generated.proto +// DO NOT EDIT! + +/* + Package v1 is a generated protocol buffer package. + + It is generated from these files: + k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/apis/example2/v1/generated.proto + + It has these top-level messages: + ReplicaSet + ReplicaSetSpec + ReplicaSetStatus +*/ +package v1 + +import proto "github.com/gogo/protobuf/proto" +import fmt "fmt" +import math "math" + +import strings "strings" +import reflect "reflect" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package + +func (m *ReplicaSet) Reset() { *m = ReplicaSet{} } +func (*ReplicaSet) ProtoMessage() {} +func (*ReplicaSet) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{0} } + +func (m *ReplicaSetSpec) Reset() { *m = ReplicaSetSpec{} } +func (*ReplicaSetSpec) ProtoMessage() {} +func (*ReplicaSetSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{1} } + +func (m *ReplicaSetStatus) Reset() { *m = ReplicaSetStatus{} } +func (*ReplicaSetStatus) ProtoMessage() {} +func (*ReplicaSetStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{2} } + +func init() { + proto.RegisterType((*ReplicaSet)(nil), "k8s.io.apiserver.pkg.apis.example2.v1.ReplicaSet") + proto.RegisterType((*ReplicaSetSpec)(nil), "k8s.io.apiserver.pkg.apis.example2.v1.ReplicaSetSpec") + proto.RegisterType((*ReplicaSetStatus)(nil), "k8s.io.apiserver.pkg.apis.example2.v1.ReplicaSetStatus") +} +func (m *ReplicaSet) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ReplicaSet) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) + n1, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) + n2, err := m.Spec.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n2 + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) + n3, err := m.Status.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n3 + return i, nil +} + +func (m *ReplicaSetSpec) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ReplicaSetSpec) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Replicas != nil { + dAtA[i] = 0x8 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(*m.Replicas)) + } + return i, nil +} + +func (m *ReplicaSetStatus) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ReplicaSetStatus) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0x8 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Replicas)) + return i, nil +} + +func encodeFixed64Generated(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Generated(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *ReplicaSet) Size() (n int) { + 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 *ReplicaSetSpec) Size() (n int) { + var l int + _ = l + if m.Replicas != nil { + n += 1 + sovGenerated(uint64(*m.Replicas)) + } + return n +} + +func (m *ReplicaSetStatus) Size() (n int) { + var l int + _ = l + n += 1 + sovGenerated(uint64(m.Replicas)) + return n +} + +func sovGenerated(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozGenerated(x uint64) (n int) { + return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (this *ReplicaSet) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ReplicaSet{`, + `ObjectMeta:` + strings.Replace(strings.Replace(this.ObjectMeta.String(), "ObjectMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "ReplicaSetSpec", "ReplicaSetSpec", 1), `&`, ``, 1) + `,`, + `Status:` + strings.Replace(strings.Replace(this.Status.String(), "ReplicaSetStatus", "ReplicaSetStatus", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *ReplicaSetSpec) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ReplicaSetSpec{`, + `Replicas:` + valueToStringGenerated(this.Replicas) + `,`, + `}`, + }, "") + return s +} +func (this *ReplicaSetStatus) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ReplicaSetStatus{`, + `Replicas:` + fmt.Sprintf("%v", this.Replicas) + `,`, + `}`, + }, "") + 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 *ReplicaSet) 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: ReplicaSet: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ReplicaSet: 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 > 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 > 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 > 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 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ReplicaSetSpec) 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: ReplicaSetSpec: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ReplicaSetSpec: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Replicas", 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.Replicas = &v + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ReplicaSetStatus) 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: ReplicaSetStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ReplicaSetStatus: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Replicas", wireType) + } + m.Replicas = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Replicas |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if 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 + 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 + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + 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 + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthGenerated + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipGenerated(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") +) + +func init() { + proto.RegisterFile("k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/apis/example2/v1/generated.proto", fileDescriptorGenerated) +} + +var fileDescriptorGenerated = []byte{ + // 421 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x91, 0xcf, 0x8e, 0xd3, 0x30, + 0x10, 0x87, 0x93, 0xb2, 0xac, 0x2a, 0xb3, 0x5a, 0xad, 0x72, 0xaa, 0x7a, 0x70, 0x51, 0x24, 0xa4, + 0x1e, 0xc0, 0x26, 0xcb, 0x5f, 0x71, 0x42, 0xb9, 0x03, 0x52, 0xf6, 0x80, 0xc4, 0x05, 0x1c, 0x77, + 0x48, 0x4d, 0x9a, 0xd8, 0xb2, 0x9d, 0x08, 0x6e, 0x3c, 0x02, 0x8f, 0xc1, 0xa3, 0xf4, 0xb8, 0xc7, + 0x3d, 0x55, 0x34, 0xbc, 0x08, 0xaa, 0x13, 0x12, 0xb1, 0xed, 0x0a, 0xb8, 0xe5, 0x67, 0xcf, 0xf7, + 0xcd, 0x64, 0x8c, 0x5e, 0xe7, 0xcf, 0x0d, 0x11, 0x92, 0xe6, 0x55, 0x0a, 0xba, 0x04, 0x0b, 0x86, + 0xd6, 0x50, 0x2e, 0xa4, 0xa6, 0xdd, 0x05, 0x53, 0xc2, 0x80, 0xae, 0x41, 0x53, 0x95, 0x67, 0x2e, + 0x51, 0xf8, 0xcc, 0x0a, 0xb5, 0x82, 0x73, 0x5a, 0x47, 0x34, 0x83, 0x12, 0x34, 0xb3, 0xb0, 0x20, + 0x4a, 0x4b, 0x2b, 0x83, 0x7b, 0x2d, 0x46, 0x7a, 0x8c, 0xa8, 0x3c, 0x73, 0x89, 0xfc, 0xc6, 0x48, + 0x1d, 0x4d, 0x1f, 0x64, 0xc2, 0x2e, 0xab, 0x94, 0x70, 0x59, 0xd0, 0x4c, 0x66, 0x92, 0x3a, 0x3a, + 0xad, 0x3e, 0xba, 0xe4, 0x82, 0xfb, 0x6a, 0xad, 0xd3, 0x70, 0x18, 0x86, 0x72, 0xa9, 0xe1, 0x40, + 0xe7, 0xe9, 0xe3, 0xa1, 0xa6, 0x60, 0x7c, 0x29, 0x4a, 0xd0, 0x5f, 0x86, 0x99, 0x0b, 0xb0, 0xec, + 0x10, 0x45, 0x6f, 0xa2, 0x74, 0x55, 0x5a, 0x51, 0xc0, 0x1e, 0xf0, 0xf4, 0x6f, 0x80, 0xe1, 0x4b, + 0x28, 0xd8, 0x1e, 0xf7, 0xe8, 0x26, 0xae, 0xb2, 0x62, 0x45, 0x45, 0x69, 0x8d, 0xd5, 0xd7, 0xa1, + 0xf0, 0xfb, 0x08, 0xa1, 0x04, 0xd4, 0x4a, 0x70, 0x76, 0x01, 0x36, 0xf8, 0x80, 0xc6, 0xbb, 0xff, + 0x58, 0x30, 0xcb, 0x26, 0xfe, 0x5d, 0x7f, 0x7e, 0xe7, 0xfc, 0x21, 0x19, 0xf6, 0xdd, 0x6b, 0x87, + 0x95, 0xef, 0xaa, 0x49, 0x1d, 0x91, 0x37, 0xe9, 0x27, 0xe0, 0xf6, 0x15, 0x58, 0x16, 0x07, 0xeb, + 0xcd, 0xcc, 0x6b, 0x36, 0x33, 0x34, 0x9c, 0x25, 0xbd, 0x35, 0x78, 0x8b, 0x8e, 0x8c, 0x02, 0x3e, + 0x19, 0x39, 0xfb, 0x13, 0xf2, 0x4f, 0xaf, 0x49, 0x86, 0x11, 0x2f, 0x14, 0xf0, 0xf8, 0xa4, 0x6b, + 0x71, 0xb4, 0x4b, 0x89, 0x13, 0x06, 0xef, 0xd1, 0xb1, 0xb1, 0xcc, 0x56, 0x66, 0x72, 0xcb, 0xa9, + 0x9f, 0xfd, 0xbf, 0xda, 0xe1, 0xf1, 0x69, 0x27, 0x3f, 0x6e, 0x73, 0xd2, 0x69, 0xc3, 0x17, 0xe8, + 0xf4, 0xcf, 0x31, 0x82, 0x39, 0x1a, 0xeb, 0xf6, 0xc4, 0xb8, 0x6d, 0xdd, 0x8e, 0x4f, 0x9a, 0xcd, + 0x6c, 0xdc, 0x55, 0x99, 0xa4, 0xbf, 0x0d, 0x5f, 0xa2, 0xb3, 0xeb, 0x7d, 0x82, 0xfb, 0x7b, 0xf4, + 0x59, 0xd7, 0xf9, 0x80, 0x21, 0x9e, 0xaf, 0xb7, 0xd8, 0xbb, 0xdc, 0x62, 0xef, 0x6a, 0x8b, 0xbd, + 0xaf, 0x0d, 0xf6, 0xd7, 0x0d, 0xf6, 0x2f, 0x1b, 0xec, 0x5f, 0x35, 0xd8, 0xff, 0xd1, 0x60, 0xff, + 0xdb, 0x4f, 0xec, 0xbd, 0x1b, 0xd5, 0xd1, 0xaf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x69, 0x4a, 0x84, + 0xe4, 0x71, 0x03, 0x00, 0x00, +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/generated.proto b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/generated.proto new file mode 100644 index 00000000000..e0bcac5e542 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/generated.proto @@ -0,0 +1,71 @@ +/* +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. +*/ + + +// This file was autogenerated by go-to-protobuf. Do not edit it manually! + +syntax = 'proto2'; + +package k8s.io.apiserver.pkg.apis.example2.v1; + +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"; +import "k8s.io/apimachinery/pkg/util/intstr/generated.proto"; + +// Package-wide variables from generator "generated". +option go_package = "v1"; + +// ReplicaSet ensures that a specified number of pod replicas are running at any given time. +message ReplicaSet { + // If the Labels of a ReplicaSet are empty, they are defaulted to + // be the same as the Pod(s) that the ReplicaSet manages. + // Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; + + // Spec defines the specification of the desired behavior of the ReplicaSet. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status + // +optional + optional ReplicaSetSpec spec = 2; + + // Status is the most recently observed status of the ReplicaSet. + // This data may be out of date by some window of time. + // Populated by the system. + // Read-only. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status + // +optional + optional ReplicaSetStatus status = 3; +} + +// ReplicaSetSpec is the specification of a ReplicaSet. +message ReplicaSetSpec { + // Replicas is the number of desired replicas. + // This is a pointer to distinguish between explicit zero and unspecified. + // Defaults to 1. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller + // +optional + optional int32 replicas = 1; +} + +// ReplicaSetStatus represents the current status of a ReplicaSet. +message ReplicaSetStatus { + // Replicas is the most recently oberved number of replicas. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller + optional int32 replicas = 1; +} + diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/register.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/register.go new file mode 100644 index 00000000000..1cb0f5eb8d5 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/register.go @@ -0,0 +1,63 @@ +/* +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 v1 + +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 = "example2.apiserver.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"} + +// 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() +} + +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, addConversionFuncs, addDefaultingFuncs) +} + +// Adds the list of known types to the given scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &ReplicaSet{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/types.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/types.go new file mode 100644 index 00000000000..e6e6fb00c19 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/types.go @@ -0,0 +1,64 @@ +/* +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 v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ReplicaSet ensures that a specified number of pod replicas are running at any given time. +type ReplicaSet struct { + metav1.TypeMeta `json:",inline"` + + // If the Labels of a ReplicaSet are empty, they are defaulted to + // be the same as the Pod(s) that the ReplicaSet manages. + // Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Spec defines the specification of the desired behavior of the ReplicaSet. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status + // +optional + Spec ReplicaSetSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` + + // Status is the most recently observed status of the ReplicaSet. + // This data may be out of date by some window of time. + // Populated by the system. + // Read-only. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status + // +optional + Status ReplicaSetStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` +} + +// ReplicaSetSpec is the specification of a ReplicaSet. +type ReplicaSetSpec struct { + // Replicas is the number of desired replicas. + // This is a pointer to distinguish between explicit zero and unspecified. + // Defaults to 1. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller + // +optional + Replicas *int32 `json:"replicas,omitempty" protobuf:"varint,1,opt,name=replicas"` +} + +// ReplicaSetStatus represents the current status of a ReplicaSet. +type ReplicaSetStatus struct { + // Replicas is the most recently oberved number of replicas. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller + Replicas int32 `json:"replicas" protobuf:"varint,1,opt,name=replicas"` +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/types_swagger_doc_generated.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/types_swagger_doc_generated.go new file mode 100644 index 00000000000..c7be42d5a19 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/types_swagger_doc_generated.go @@ -0,0 +1,17 @@ +/* +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 v1 diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/zz_generated.conversion.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/zz_generated.conversion.go new file mode 100644 index 00000000000..7e22d8e0d72 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/zz_generated.conversion.go @@ -0,0 +1,111 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by conversion-gen. Do not edit it manually! + +package v1 + +import ( + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + conversion "k8s.io/apimachinery/pkg/conversion" + runtime "k8s.io/apimachinery/pkg/runtime" + example "k8s.io/apiserver/pkg/apis/example" +) + +func init() { + localSchemeBuilder.Register(RegisterConversions) +} + +// RegisterConversions adds conversion functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterConversions(scheme *runtime.Scheme) error { + return scheme.AddGeneratedConversionFuncs( + Convert_v1_ReplicaSet_To_example_ReplicaSet, + Convert_example_ReplicaSet_To_v1_ReplicaSet, + Convert_v1_ReplicaSetSpec_To_example_ReplicaSetSpec, + Convert_example_ReplicaSetSpec_To_v1_ReplicaSetSpec, + Convert_v1_ReplicaSetStatus_To_example_ReplicaSetStatus, + Convert_example_ReplicaSetStatus_To_v1_ReplicaSetStatus, + ) +} + +func autoConvert_v1_ReplicaSet_To_example_ReplicaSet(in *ReplicaSet, out *example.ReplicaSet, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_ReplicaSetSpec_To_example_ReplicaSetSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1_ReplicaSetStatus_To_example_ReplicaSetStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1_ReplicaSet_To_example_ReplicaSet is an autogenerated conversion function. +func Convert_v1_ReplicaSet_To_example_ReplicaSet(in *ReplicaSet, out *example.ReplicaSet, s conversion.Scope) error { + return autoConvert_v1_ReplicaSet_To_example_ReplicaSet(in, out, s) +} + +func autoConvert_example_ReplicaSet_To_v1_ReplicaSet(in *example.ReplicaSet, out *ReplicaSet, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_example_ReplicaSetSpec_To_v1_ReplicaSetSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_example_ReplicaSetStatus_To_v1_ReplicaSetStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_example_ReplicaSet_To_v1_ReplicaSet is an autogenerated conversion function. +func Convert_example_ReplicaSet_To_v1_ReplicaSet(in *example.ReplicaSet, out *ReplicaSet, s conversion.Scope) error { + return autoConvert_example_ReplicaSet_To_v1_ReplicaSet(in, out, s) +} + +func autoConvert_v1_ReplicaSetSpec_To_example_ReplicaSetSpec(in *ReplicaSetSpec, out *example.ReplicaSetSpec, s conversion.Scope) error { + if err := meta_v1.Convert_Pointer_int32_To_int32(&in.Replicas, &out.Replicas, s); err != nil { + return err + } + return nil +} + +func autoConvert_example_ReplicaSetSpec_To_v1_ReplicaSetSpec(in *example.ReplicaSetSpec, out *ReplicaSetSpec, s conversion.Scope) error { + if err := meta_v1.Convert_int32_To_Pointer_int32(&in.Replicas, &out.Replicas, s); err != nil { + return err + } + return nil +} + +func autoConvert_v1_ReplicaSetStatus_To_example_ReplicaSetStatus(in *ReplicaSetStatus, out *example.ReplicaSetStatus, s conversion.Scope) error { + out.Replicas = in.Replicas + return nil +} + +// Convert_v1_ReplicaSetStatus_To_example_ReplicaSetStatus is an autogenerated conversion function. +func Convert_v1_ReplicaSetStatus_To_example_ReplicaSetStatus(in *ReplicaSetStatus, out *example.ReplicaSetStatus, s conversion.Scope) error { + return autoConvert_v1_ReplicaSetStatus_To_example_ReplicaSetStatus(in, out, s) +} + +func autoConvert_example_ReplicaSetStatus_To_v1_ReplicaSetStatus(in *example.ReplicaSetStatus, out *ReplicaSetStatus, s conversion.Scope) error { + out.Replicas = in.Replicas + return nil +} + +// Convert_example_ReplicaSetStatus_To_v1_ReplicaSetStatus is an autogenerated conversion function. +func Convert_example_ReplicaSetStatus_To_v1_ReplicaSetStatus(in *example.ReplicaSetStatus, out *ReplicaSetStatus, s conversion.Scope) error { + return autoConvert_example_ReplicaSetStatus_To_v1_ReplicaSetStatus(in, out, s) +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/zz_generated.deepcopy.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..f843a758d52 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/zz_generated.deepcopy.go @@ -0,0 +1,95 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +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 *ReplicaSet) DeepCopyInto(out *ReplicaSet) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicaSet. +func (in *ReplicaSet) DeepCopy() *ReplicaSet { + if in == nil { + return nil + } + out := new(ReplicaSet) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ReplicaSet) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplicaSetSpec) DeepCopyInto(out *ReplicaSetSpec) { + *out = *in + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicaSetSpec. +func (in *ReplicaSetSpec) DeepCopy() *ReplicaSetSpec { + if in == nil { + return nil + } + out := new(ReplicaSetSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplicaSetStatus) DeepCopyInto(out *ReplicaSetStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicaSetStatus. +func (in *ReplicaSetStatus) DeepCopy() *ReplicaSetStatus { + if in == nil { + return nil + } + out := new(ReplicaSetStatus) + in.DeepCopyInto(out) + return out +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/zz_generated.defaults.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/zz_generated.defaults.go new file mode 100644 index 00000000000..88d7af085be --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/zz_generated.defaults.go @@ -0,0 +1,32 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by defaulter-gen. Do not edit it manually! + +package v1 + +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/apis/example2/zz_generated.deepcopy.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/zz_generated.deepcopy.go new file mode 100644 index 00000000000..3d312899b0d --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/zz_generated.deepcopy.go @@ -0,0 +1,21 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +package example2 diff --git a/staging/src/k8s.io/apiserver/pkg/audit/BUILD b/staging/src/k8s.io/apiserver/pkg/audit/BUILD index 525424cdded..0df9fd91f06 100644 --- a/staging/src/k8s.io/apiserver/pkg/audit/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/audit/BUILD @@ -39,8 +39,8 @@ go_library( go_test( name = "go_default_test", srcs = ["union_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/audit", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/audit/policy/BUILD b/staging/src/k8s.io/apiserver/pkg/audit/policy/BUILD index 8efedfb44a4..0873ac9955f 100644 --- a/staging/src/k8s.io/apiserver/pkg/audit/policy/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/audit/policy/BUILD @@ -12,8 +12,8 @@ go_test( "checker_test.go", "reader_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/audit/policy", - library = ":go_default_library", deps = [ "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/github.com/stretchr/testify/require:go_default_library", @@ -34,7 +34,7 @@ go_library( importpath = "k8s.io/apiserver/pkg/audit/policy", deps = [ "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library", "//vendor/k8s.io/apiserver/pkg/apis/audit/v1alpha1:go_default_library", "//vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1:go_default_library", 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 b92ffe26b68..3259013ad72 100644 --- a/staging/src/k8s.io/apiserver/pkg/audit/policy/checker.go +++ b/staging/src/k8s.io/apiserver/pkg/audit/policy/checker.go @@ -36,9 +36,26 @@ type Checker interface { // NewChecker creates a new policy checker. func NewChecker(policy *audit.Policy) Checker { + for i, rule := range policy.Rules { + policy.Rules[i].OmitStages = unionStages(policy.OmitStages, rule.OmitStages) + } return &policyChecker{*policy} } +func unionStages(stageLists ...[]audit.Stage) []audit.Stage { + m := make(map[audit.Stage]bool) + for _, sl := range stageLists { + for _, s := range sl { + m[s] = true + } + } + result := make([]audit.Stage, 0, len(m)) + for key := range m { + result = append(result, key) + } + return result +} + // FakeChecker creates a checker that returns a constant level for all requests (for testing). func FakeChecker(level audit.Level, stage []audit.Stage) Checker { return &fakeChecker{level, stage} @@ -54,7 +71,7 @@ func (p *policyChecker) LevelAndStages(attrs authorizer.Attributes) (audit.Level return rule.Level, rule.OmitStages } } - return DefaultAuditLevel, nil + return DefaultAuditLevel, p.OmitStages } // Check whether the rule matches the request attrs. 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 d6cc5f09171..0f323436a92 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 @@ -28,12 +28,12 @@ import ( "k8s.io/apiserver/pkg/authorization/authorizer" ) -func TestChecker(t *testing.T) { - tim := &user.DefaultInfo{ +var ( + tim = &user.DefaultInfo{ Name: "tim@k8s.io", Groups: []string{"humans", "developers"}, } - attrs := map[string]authorizer.Attributes{ + attrs = map[string]authorizer.Attributes{ "namespaced": &authorizer.AttributesRecord{ User: tim, Verb: "get", @@ -75,7 +75,7 @@ func TestChecker(t *testing.T) { }, } - rules := map[string]audit.PolicyRule{ + rules = map[string]audit.PolicyRule{ "default": { Level: audit.LevelMetadata, }, @@ -151,65 +151,165 @@ func TestChecker(t *testing.T) { }, }, } +) - test := func(req string, expLevel audit.Level, expOmitStages []audit.Stage, ruleNames ...string) { - policy := audit.Policy{} - for _, rule := range ruleNames { - require.Contains(t, rules, rule) - policy.Rules = append(policy.Rules, rules[rule]) +func test(t *testing.T, req string, expLevel audit.Level, policyStages, expOmitStages []audit.Stage, ruleNames ...string) { + policy := audit.Policy{OmitStages: policyStages} + for _, rule := range ruleNames { + require.Contains(t, rules, rule) + policy.Rules = append(policy.Rules, rules[rule]) + } + require.Contains(t, attrs, req) + actualLevel, actualOmitStages := NewChecker(&policy).LevelAndStages(attrs[req]) + assert.Equal(t, expLevel, actualLevel, "request:%s rules:%s", req, strings.Join(ruleNames, ",")) + assert.True(t, stageEqual(expOmitStages, actualOmitStages), "request:%s rules:%s, expected stages: %v, actual stages: %v", + req, strings.Join(ruleNames, ","), expOmitStages, actualOmitStages) +} + +func testAuditLevel(t *testing.T, stages []audit.Stage) { + test(t, "namespaced", audit.LevelMetadata, stages, stages, "default") + test(t, "namespaced", audit.LevelNone, stages, stages, "create") + test(t, "namespaced", audit.LevelMetadata, stages, stages, "tims") + test(t, "namespaced", audit.LevelMetadata, stages, stages, "humans") + test(t, "namespaced", audit.LevelNone, stages, stages, "serviceAccounts") + test(t, "namespaced", audit.LevelRequestResponse, stages, stages, "getPods") + test(t, "namespaced", audit.LevelNone, stages, stages, "getClusterRoles") + test(t, "namespaced", audit.LevelNone, stages, stages, "getLogs") + test(t, "namespaced", audit.LevelNone, stages, stages, "getMetrics") + test(t, "namespaced", audit.LevelMetadata, stages, stages, "getMetrics", "serviceAccounts", "default") + test(t, "namespaced", audit.LevelRequestResponse, stages, stages, "getMetrics", "getPods", "default") + test(t, "namespaced", audit.LevelRequestResponse, stages, stages, "getPodLogs", "getPods") + + test(t, "cluster", audit.LevelMetadata, stages, stages, "default") + test(t, "cluster", audit.LevelNone, stages, stages, "create") + test(t, "cluster", audit.LevelMetadata, stages, stages, "tims") + test(t, "cluster", audit.LevelMetadata, stages, stages, "humans") + test(t, "cluster", audit.LevelNone, stages, stages, "serviceAccounts") + test(t, "cluster", audit.LevelNone, stages, stages, "getPods") + test(t, "cluster", audit.LevelRequestResponse, stages, stages, "getClusterRoles") + test(t, "cluster", audit.LevelRequest, stages, stages, "clusterRoleEdit", "getClusterRoles") + test(t, "cluster", audit.LevelNone, stages, stages, "getLogs") + test(t, "cluster", audit.LevelNone, stages, stages, "getMetrics") + test(t, "cluster", audit.LevelMetadata, stages, stages, "getMetrics", "serviceAccounts", "default") + test(t, "cluster", audit.LevelRequestResponse, stages, stages, "getMetrics", "getClusterRoles", "default") + test(t, "cluster", audit.LevelNone, stages, stages, "getPodLogs", "getPods") + + test(t, "nonResource", audit.LevelMetadata, stages, stages, "default") + test(t, "nonResource", audit.LevelNone, stages, stages, "create") + test(t, "nonResource", audit.LevelMetadata, stages, stages, "tims") + test(t, "nonResource", audit.LevelMetadata, stages, stages, "humans") + test(t, "nonResource", audit.LevelNone, stages, stages, "serviceAccounts") + test(t, "nonResource", audit.LevelNone, stages, stages, "getPods") + test(t, "nonResource", audit.LevelNone, stages, stages, "getClusterRoles") + test(t, "nonResource", audit.LevelRequestResponse, stages, stages, "getLogs") + test(t, "nonResource", audit.LevelNone, stages, stages, "getMetrics") + test(t, "nonResource", audit.LevelMetadata, stages, stages, "getMetrics", "serviceAccounts", "default") + test(t, "nonResource", audit.LevelRequestResponse, stages, stages, "getLogs", "getClusterRoles", "default") + test(t, "nonResource", audit.LevelNone, stages, stages, "getPodLogs", "getPods") + + test(t, "subresource", audit.LevelRequest, stages, stages, "getPodLogs", "getPods") + +} + +func TestChecker(t *testing.T) { + testAuditLevel(t, nil) + + // test omitStages pre rule + test(t, "namespaced", audit.LevelRequest, nil, []audit.Stage{audit.StageRequestReceived}, "omit RequestReceived", "getPods", "default") + test(t, "namespaced", audit.LevelRequest, nil, []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted, audit.StageResponseComplete}, "only audit panic", "getPods", "default") + test(t, "cluster", audit.LevelRequest, nil, []audit.Stage{audit.StageRequestReceived}, "omit RequestReceived", "getPods", "default") + test(t, "cluster", audit.LevelRequest, nil, []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted, audit.StageResponseComplete}, "only audit panic", "getPods", "default") + test(t, "nonResource", audit.LevelRequest, nil, []audit.Stage{audit.StageRequestReceived}, "omit RequestReceived", "getPods", "default") + test(t, "nonResource", audit.LevelRequest, nil, []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted, audit.StageResponseComplete}, "only audit panic", "getPods", "default") +} + +func TestCheckerPolicyOmitStages(t *testing.T) { + policyStages := []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted} + testAuditLevel(t, policyStages) + + // test omitStages policy wide + test(t, "namespaced", audit.LevelRequest, policyStages, []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted}, "omit RequestReceived", "getPods", "default") + test(t, "namespaced", audit.LevelRequest, policyStages, []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted, audit.StageResponseComplete}, "only audit panic", "getPods", "default") + test(t, "cluster", audit.LevelRequest, policyStages, []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted}, "omit RequestReceived", "getPods", "default") + test(t, "cluster", audit.LevelRequest, policyStages, []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted, audit.StageResponseComplete}, "only audit panic", "getPods", "default") + test(t, "nonResource", audit.LevelMetadata, policyStages, []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted}, "default", "omit RequestReceived", "getPods") + test(t, "nonResource", audit.LevelRequest, policyStages, []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted, audit.StageResponseComplete}, "only audit panic", "getPods", "default") +} + +// stageEqual returns true if s1 and s2 are super set of each other +func stageEqual(s1, s2 []audit.Stage) bool { + m1 := make(map[audit.Stage]bool) + m2 := make(map[audit.Stage]bool) + for _, s := range s1 { + m1[s] = true + } + for _, s := range s2 { + m2[s] = true + } + if len(m1) != len(m2) { + return false + } + for key, value := range m1 { + if m2[key] != value { + return false } - require.Contains(t, attrs, req) - actualLevel, actualOmitStages := NewChecker(&policy).LevelAndStages(attrs[req]) - assert.Equal(t, expLevel, actualLevel, "request:%s rules:%s", req, strings.Join(ruleNames, ",")) - assert.Equal(t, expOmitStages, actualOmitStages, "request:%s rules:%s", req, strings.Join(ruleNames, ",")) + } + return true +} + +func TestUnionStages(t *testing.T) { + var testCases = []struct { + s1, s2, exp []audit.Stage + }{ + { + []audit.Stage{}, + []audit.Stage{}, + []audit.Stage{}, + }, + { + []audit.Stage{audit.StageRequestReceived}, + []audit.Stage{}, + []audit.Stage{audit.StageRequestReceived}, + }, + { + []audit.Stage{audit.StageRequestReceived}, + []audit.Stage{audit.StageRequestReceived}, + []audit.Stage{audit.StageRequestReceived}, + }, + { + []audit.Stage{audit.StageRequestReceived}, + []audit.Stage{audit.StageResponseStarted}, + []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted}, + }, + { + []audit.Stage{audit.StageRequestReceived, audit.StageRequestReceived}, + []audit.Stage{audit.StageRequestReceived, audit.StageRequestReceived}, + []audit.Stage{audit.StageRequestReceived}, + }, + { + []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted}, + []audit.Stage{audit.StagePanic, audit.StageRequestReceived}, + []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted, audit.StagePanic}, + }, + { + nil, + []audit.Stage{audit.StageRequestReceived}, + []audit.Stage{audit.StageRequestReceived}, + }, } - test("namespaced", audit.LevelMetadata, nil, "default") - test("namespaced", audit.LevelNone, nil, "create") - test("namespaced", audit.LevelMetadata, nil, "tims") - test("namespaced", audit.LevelMetadata, nil, "humans") - test("namespaced", audit.LevelNone, nil, "serviceAccounts") - test("namespaced", audit.LevelRequestResponse, nil, "getPods") - test("namespaced", audit.LevelNone, nil, "getClusterRoles") - test("namespaced", audit.LevelNone, nil, "getLogs") - test("namespaced", audit.LevelNone, nil, "getMetrics") - test("namespaced", audit.LevelMetadata, nil, "getMetrics", "serviceAccounts", "default") - test("namespaced", audit.LevelRequestResponse, nil, "getMetrics", "getPods", "default") - test("namespaced", audit.LevelRequestResponse, nil, "getPodLogs", "getPods") - test("namespaced", audit.LevelRequest, []audit.Stage{audit.StageRequestReceived}, "omit RequestReceived", "getPods", "default") - test("namespaced", audit.LevelRequest, []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted, audit.StageResponseComplete}, "only audit panic", "getPods", "default") - - test("cluster", audit.LevelMetadata, nil, "default") - test("cluster", audit.LevelNone, nil, "create") - test("cluster", audit.LevelMetadata, nil, "tims") - test("cluster", audit.LevelMetadata, nil, "humans") - test("cluster", audit.LevelNone, nil, "serviceAccounts") - test("cluster", audit.LevelNone, nil, "getPods") - test("cluster", audit.LevelRequestResponse, nil, "getClusterRoles") - test("cluster", audit.LevelRequest, nil, "clusterRoleEdit", "getClusterRoles") - test("cluster", audit.LevelNone, nil, "getLogs") - test("cluster", audit.LevelNone, nil, "getMetrics") - test("cluster", audit.LevelMetadata, nil, "getMetrics", "serviceAccounts", "default") - test("cluster", audit.LevelRequestResponse, nil, "getMetrics", "getClusterRoles", "default") - test("cluster", audit.LevelNone, nil, "getPodLogs", "getPods") - test("cluster", audit.LevelRequest, []audit.Stage{audit.StageRequestReceived}, "omit RequestReceived", "getPods", "default") - test("cluster", audit.LevelRequest, []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted, audit.StageResponseComplete}, "only audit panic", "getPods", "default") - - test("nonResource", audit.LevelMetadata, nil, "default") - test("nonResource", audit.LevelNone, nil, "create") - test("nonResource", audit.LevelMetadata, nil, "tims") - test("nonResource", audit.LevelMetadata, nil, "humans") - test("nonResource", audit.LevelNone, nil, "serviceAccounts") - test("nonResource", audit.LevelNone, nil, "getPods") - test("nonResource", audit.LevelNone, nil, "getClusterRoles") - test("nonResource", audit.LevelRequestResponse, nil, "getLogs") - test("nonResource", audit.LevelNone, nil, "getMetrics") - test("nonResource", audit.LevelMetadata, nil, "getMetrics", "serviceAccounts", "default") - test("nonResource", audit.LevelRequestResponse, nil, "getLogs", "getClusterRoles", "default") - test("nonResource", audit.LevelNone, nil, "getPodLogs", "getPods") - test("nonResource", audit.LevelRequest, []audit.Stage{audit.StageRequestReceived}, "omit RequestReceived", "getPods", "default") - test("nonResource", audit.LevelRequest, []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted, audit.StageResponseComplete}, "only audit panic", "getPods", "default") - - test("subresource", audit.LevelRequest, nil, "getPodLogs", "getPods") - test("subresource", audit.LevelRequest, nil, "getPods", "getPodLogs") + for _, tc := range testCases { + result := unionStages(tc.s1, tc.s2) + assert.Len(t, result, len(tc.exp)) + for _, expStage := range tc.exp { + ok := false + for _, resultStage := range result { + if resultStage == expStage { + ok = true + break + } + } + assert.True(t, ok) + } + } } diff --git a/staging/src/k8s.io/apiserver/pkg/audit/policy/reader.go b/staging/src/k8s.io/apiserver/pkg/audit/policy/reader.go index b748e8649cc..1d02e1a3fb9 100644 --- a/staging/src/k8s.io/apiserver/pkg/audit/policy/reader.go +++ b/staging/src/k8s.io/apiserver/pkg/audit/policy/reader.go @@ -20,7 +20,7 @@ import ( "fmt" "io/ioutil" - "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" auditinternal "k8s.io/apiserver/pkg/apis/audit" auditv1alpha1 "k8s.io/apiserver/pkg/apis/audit/v1alpha1" auditv1beta1 "k8s.io/apiserver/pkg/apis/audit/v1beta1" @@ -30,6 +30,20 @@ import ( "github.com/golang/glog" ) +var ( + apiGroupVersions = []schema.GroupVersion{ + auditv1beta1.SchemeGroupVersion, + auditv1alpha1.SchemeGroupVersion, + } + apiGroupVersionSet = map[schema.GroupVersion]bool{} +) + +func init() { + for _, gv := range apiGroupVersions { + apiGroupVersionSet[gv] = true + } +} + func LoadPolicyFromFile(filePath string) (*auditinternal.Policy, error) { if filePath == "" { return nil, fmt.Errorf("file path not specified") @@ -40,11 +54,18 @@ func LoadPolicyFromFile(filePath string) (*auditinternal.Policy, error) { } policy := &auditinternal.Policy{} - decoder := audit.Codecs.UniversalDecoder(auditv1beta1.SchemeGroupVersion, auditv1alpha1.SchemeGroupVersion) - if err := runtime.DecodeInto(decoder, policyDef, policy); err != nil { + decoder := audit.Codecs.UniversalDecoder(apiGroupVersions...) + + _, gvk, err := decoder.Decode(policyDef, nil, policy) + if err != nil { return nil, fmt.Errorf("failed decoding file %q: %v", filePath, err) } + // Ensure the policy file contained an apiVersion and kind. + if !apiGroupVersionSet[schema.GroupVersion{Group: gvk.Group, Version: gvk.Version}] { + return nil, fmt.Errorf("unknown group version field %v in policy file %s", gvk, filePath) + } + if err := validation.ValidatePolicy(policy); err != nil { return nil, err.ToAggregate() } diff --git a/staging/src/k8s.io/apiserver/pkg/audit/policy/reader_test.go b/staging/src/k8s.io/apiserver/pkg/audit/policy/reader_test.go index f9bda28463c..b05297a983a 100644 --- a/staging/src/k8s.io/apiserver/pkg/audit/policy/reader_test.go +++ b/staging/src/k8s.io/apiserver/pkg/audit/policy/reader_test.go @@ -71,6 +71,24 @@ rules: - level: Metadata ` +const policyWithNoVersionOrKind = ` +rules: + - level: None + nonResourceURLs: + - /healthz* + - /version + - level: RequestResponse + users: ["tim"] + userGroups: ["testers", "developers"] + verbs: ["patch", "delete", "create"] + resources: + - group: "" + - group: "rbac.authorization.k8s.io" + resources: ["clusterroles", "clusterrolebindings"] + namespaces: ["default", "kube-system"] + - level: Metadata +` + var expectedPolicy = &audit.Policy{ Rules: []audit.PolicyRule{{ Level: audit.LevelNone, @@ -104,6 +122,15 @@ func TestParserV1alpha1(t *testing.T) { } } +func TestParsePolicyWithNoVersionOrKind(t *testing.T) { + f, err := writePolicy(t, policyWithNoVersionOrKind) + require.NoError(t, err) + defer os.Remove(f) + + _, err = LoadPolicyFromFile(f) + assert.Contains(t, err.Error(), "unknown group version field") +} + func TestParserV1beta1(t *testing.T) { f, err := writePolicy(t, policyDefV1beta1) require.NoError(t, err) diff --git a/staging/src/k8s.io/apiserver/pkg/authentication/group/BUILD b/staging/src/k8s.io/apiserver/pkg/authentication/group/BUILD index ea84fb9dcd9..cdb74e4e42e 100644 --- a/staging/src/k8s.io/apiserver/pkg/authentication/group/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/authentication/group/BUILD @@ -12,8 +12,8 @@ go_test( "group_adder_test.go", "token_group_adder_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/authentication/group", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library", "//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/authentication/request/anonymous/BUILD b/staging/src/k8s.io/apiserver/pkg/authentication/request/anonymous/BUILD index c20d90c88c8..78165e4f4c8 100644 --- a/staging/src/k8s.io/apiserver/pkg/authentication/request/anonymous/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/authentication/request/anonymous/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["anonymous_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/authentication/request/anonymous", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/authentication/request/bearertoken/BUILD b/staging/src/k8s.io/apiserver/pkg/authentication/request/bearertoken/BUILD index 93f6ad9e932..b338a0c7862 100644 --- a/staging/src/k8s.io/apiserver/pkg/authentication/request/bearertoken/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/authentication/request/bearertoken/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["bearertoken_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/authentication/request/bearertoken", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library", "//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/BUILD b/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/BUILD index a37735742f5..351b1b64520 100644 --- a/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["requestheader_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/authentication/request/headerrequest", - library = ":go_default_library", deps = ["//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library"], ) diff --git a/staging/src/k8s.io/apiserver/pkg/authentication/request/union/BUILD b/staging/src/k8s.io/apiserver/pkg/authentication/request/union/BUILD index 4b2dcadf2df..ce7c9bb5007 100644 --- a/staging/src/k8s.io/apiserver/pkg/authentication/request/union/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/authentication/request/union/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["unionauth_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/authentication/request/union", - library = ":go_default_library", deps = ["//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library"], ) diff --git a/staging/src/k8s.io/apiserver/pkg/authentication/request/websocket/BUILD b/staging/src/k8s.io/apiserver/pkg/authentication/request/websocket/BUILD index 1ea8e4c5b4c..bab85829524 100644 --- a/staging/src/k8s.io/apiserver/pkg/authentication/request/websocket/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/authentication/request/websocket/BUILD @@ -20,8 +20,8 @@ go_library( go_test( name = "go_default_test", srcs = ["protocol_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/authentication/request/websocket", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library", "//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/authentication/request/x509/BUILD b/staging/src/k8s.io/apiserver/pkg/authentication/request/x509/BUILD index 2114dc00bd7..4297b8c4e7c 100644 --- a/staging/src/k8s.io/apiserver/pkg/authentication/request/x509/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/authentication/request/x509/BUILD @@ -15,8 +15,8 @@ go_test( "testdata/intermediate.pem", "testdata/root.pem", ], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/authentication/request/x509", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/authentication/request/x509/x509.go b/staging/src/k8s.io/apiserver/pkg/authentication/request/x509/x509.go index d583a321846..708a89e9eac 100644 --- a/staging/src/k8s.io/apiserver/pkg/authentication/request/x509/x509.go +++ b/staging/src/k8s.io/apiserver/pkg/authentication/request/x509/x509.go @@ -47,6 +47,10 @@ var clientCertificateExpirationHistogram = prometheus.NewHistogram( (2 * 24 * time.Hour).Seconds(), (4 * 24 * time.Hour).Seconds(), (7 * 24 * time.Hour).Seconds(), + (30 * 24 * time.Hour).Seconds(), + (3 * 30 * 24 * time.Hour).Seconds(), + (6 * 30 * 24 * time.Hour).Seconds(), + (12 * 30 * 24 * time.Hour).Seconds(), }, }, ) diff --git a/staging/src/k8s.io/apiserver/pkg/authentication/serviceaccount/BUILD b/staging/src/k8s.io/apiserver/pkg/authentication/serviceaccount/BUILD index db06fbde332..21dc23720c6 100644 --- a/staging/src/k8s.io/apiserver/pkg/authentication/serviceaccount/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/authentication/serviceaccount/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["util_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/authentication/serviceaccount", - library = ":go_default_library", ) go_library( diff --git a/staging/src/k8s.io/apiserver/pkg/authentication/token/cache/BUILD b/staging/src/k8s.io/apiserver/pkg/authentication/token/cache/BUILD index 59cd4322f72..55387ec4696 100644 --- a/staging/src/k8s.io/apiserver/pkg/authentication/token/cache/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/authentication/token/cache/BUILD @@ -12,8 +12,8 @@ go_test( "cache_test.go", "cached_token_authenticator_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/authentication/token/cache", - library = ":go_default_library", deps = [ "//vendor/github.com/pborman/uuid:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/authentication/token/tokenfile/BUILD b/staging/src/k8s.io/apiserver/pkg/authentication/token/tokenfile/BUILD index 4d32dd1d31c..6b2233591c5 100644 --- a/staging/src/k8s.io/apiserver/pkg/authentication/token/tokenfile/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/authentication/token/tokenfile/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["tokenfile_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/authentication/token/tokenfile", - library = ":go_default_library", deps = ["//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library"], ) diff --git a/staging/src/k8s.io/apiserver/pkg/authentication/token/tokenfile/tokenfile.go b/staging/src/k8s.io/apiserver/pkg/authentication/token/tokenfile/tokenfile.go index 43ff764de9e..57bb6c596d8 100644 --- a/staging/src/k8s.io/apiserver/pkg/authentication/token/tokenfile/tokenfile.go +++ b/staging/src/k8s.io/apiserver/pkg/authentication/token/tokenfile/tokenfile.go @@ -62,11 +62,17 @@ func NewCSV(path string) (*TokenAuthenticator, error) { if len(record) < 3 { return nil, fmt.Errorf("token file '%s' must have at least 3 columns (token, user name, user uid), found %d", path, len(record)) } + + recordNum++ + if record[0] == "" { + glog.Warningf("empty token has been found in token file '%s', record number '%d'", path, recordNum) + continue + } + obj := &user.DefaultInfo{ Name: record[1], UID: record[2], } - recordNum++ if _, exist := tokens[record[0]]; exist { glog.Warningf("duplicate token has been found in token file '%s', record number '%d'", path, recordNum) } diff --git a/staging/src/k8s.io/apiserver/pkg/authentication/token/tokenfile/tokenfile_test.go b/staging/src/k8s.io/apiserver/pkg/authentication/token/tokenfile/tokenfile_test.go index 6fd13ba2d80..c02d4f1b69d 100644 --- a/staging/src/k8s.io/apiserver/pkg/authentication/token/tokenfile/tokenfile_test.go +++ b/staging/src/k8s.io/apiserver/pkg/authentication/token/tokenfile/tokenfile_test.go @@ -125,6 +125,16 @@ func TestInsufficientColumnsTokenFile(t *testing.T) { } } +func TestEmptyTokenTokenFile(t *testing.T) { + auth, err := newWithContents(t, ",user5,uid5\n") + if err != nil { + t.Fatalf("unexpected error %v", err) + } + if len(auth.tokens) != 0 { + t.Fatalf("empty token should not be recorded") + } +} + func newWithContents(t *testing.T, contents string) (auth *TokenAuthenticator, err error) { f, err := ioutil.TempFile("", "tokenfile_test") if err != nil { diff --git a/staging/src/k8s.io/apiserver/pkg/authentication/token/union/BUILD b/staging/src/k8s.io/apiserver/pkg/authentication/token/union/BUILD index 0f940c33475..3167a6bc1df 100644 --- a/staging/src/k8s.io/apiserver/pkg/authentication/token/union/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/authentication/token/union/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["unionauth_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/authentication/token/union", - library = ":go_default_library", deps = ["//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library"], ) diff --git a/staging/src/k8s.io/apiserver/pkg/authorization/authorizer/interfaces.go b/staging/src/k8s.io/apiserver/pkg/authorization/authorizer/interfaces.go index e94da3e1a44..594109096b0 100644 --- a/staging/src/k8s.io/apiserver/pkg/authorization/authorizer/interfaces.go +++ b/staging/src/k8s.io/apiserver/pkg/authorization/authorizer/interfaces.go @@ -67,12 +67,12 @@ type Attributes interface { // zero or more calls to methods of the Attributes interface. It returns nil when an action is // authorized, otherwise it returns an error. type Authorizer interface { - Authorize(a Attributes) (authorized bool, reason string, err error) + Authorize(a Attributes) (authorized Decision, reason string, err error) } -type AuthorizerFunc func(a Attributes) (bool, string, error) +type AuthorizerFunc func(a Attributes) (Decision, string, error) -func (f AuthorizerFunc) Authorize(a Attributes) (bool, string, error) { +func (f AuthorizerFunc) Authorize(a Attributes) (Decision, string, error) { return f(a) } @@ -144,3 +144,15 @@ func (a AttributesRecord) IsResourceRequest() bool { func (a AttributesRecord) GetPath() string { return a.Path } + +type Decision int + +const ( + // DecisionDeny means that an authorizer decided to deny the action. + DecisionDeny Decision = iota + // DecisionAllow means that an authorizer decided to allow the action. + DecisionAllow + // DecisionNoOpionion means that an authorizer has no opinion on wether + // to allow or deny an action. + DecisionNoOpinion +) diff --git a/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/BUILD b/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/BUILD index 6a4764ced52..660c4daacfc 100644 --- a/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/BUILD @@ -8,9 +8,9 @@ load( go_test( name = "go_default_test", - srcs = ["authz_test.go"], + srcs = ["builtin_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/authorization/authorizerfactory", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library", "//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/authz_test.go b/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/authz_test.go deleted file mode 100644 index 73a42834882..00000000000 --- a/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/authz_test.go +++ /dev/null @@ -1,56 +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 authorizerfactory - -import ( - "testing" - - "k8s.io/apiserver/pkg/authentication/user" - "k8s.io/apiserver/pkg/authorization/authorizer" -) - -// NewAlwaysAllowAuthorizer must return a struct which implements authorizer.Authorizer -// and always return nil. -func TestNewAlwaysAllowAuthorizer(t *testing.T) { - aaa := NewAlwaysAllowAuthorizer() - if authorized, _, _ := aaa.Authorize(nil); !authorized { - t.Errorf("AlwaysAllowAuthorizer.Authorize did not authorize successfully.") - } -} - -// NewAlwaysDenyAuthorizer must return a struct which implements authorizer.Authorizer -// and always return an error as everything is forbidden. -func TestNewAlwaysDenyAuthorizer(t *testing.T) { - ada := NewAlwaysDenyAuthorizer() - if authorized, _, _ := ada.Authorize(nil); authorized { - t.Errorf("AlwaysDenyAuthorizer.Authorize returned nil instead of error.") - } -} - -func TestPrivilegedGroupAuthorizer(t *testing.T) { - auth := NewPrivilegedGroups("allow-01", "allow-01") - - yes := authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"no", "allow-01"}}} - no := authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"no", "deny-01"}}} - - if authorized, _, _ := auth.Authorize(yes); !authorized { - t.Errorf("failed") - } - if authorized, _, _ := auth.Authorize(no); authorized { - t.Errorf("failed") - } -} diff --git a/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/builtin.go b/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/builtin.go index 8381e83f437..fc36bc0bc93 100644 --- a/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/builtin.go +++ b/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/builtin.go @@ -28,8 +28,8 @@ import ( // It is useful in tests and when using kubernetes in an open manner. type alwaysAllowAuthorizer struct{} -func (alwaysAllowAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) { - return true, "", nil +func (alwaysAllowAuthorizer) Authorize(a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) { + return authorizer.DecisionAllow, "", nil } func (alwaysAllowAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { @@ -56,8 +56,8 @@ func NewAlwaysAllowAuthorizer() *alwaysAllowAuthorizer { // It is useful in unit tests to force an operation to be forbidden. type alwaysDenyAuthorizer struct{} -func (alwaysDenyAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) { - return false, "Everything is forbidden.", nil +func (alwaysDenyAuthorizer) Authorize(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) { + return authorizer.DecisionNoOpinion, "Everything is forbidden.", nil } func (alwaysDenyAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { @@ -68,35 +68,22 @@ func NewAlwaysDenyAuthorizer() *alwaysDenyAuthorizer { return new(alwaysDenyAuthorizer) } -// alwaysFailAuthorizer is an implementation of authorizer.Attributes -// which always says no to an authorization request. -// It is useful in unit tests to force an operation to fail with error. -type alwaysFailAuthorizer struct{} - -func (alwaysFailAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) { - return false, "", errors.New("Authorization failure.") -} - -func NewAlwaysFailAuthorizer() authorizer.Authorizer { - return new(alwaysFailAuthorizer) -} - type privilegedGroupAuthorizer struct { groups []string } -func (r *privilegedGroupAuthorizer) Authorize(attr authorizer.Attributes) (bool, string, error) { +func (r *privilegedGroupAuthorizer) Authorize(attr authorizer.Attributes) (authorizer.Decision, string, error) { if attr.GetUser() == nil { - return false, "Error", errors.New("no user on request.") + return authorizer.DecisionNoOpinion, "Error", errors.New("no user on request.") } for _, attr_group := range attr.GetUser().GetGroups() { for _, priv_group := range r.groups { if priv_group == attr_group { - return true, "", nil + return authorizer.DecisionAllow, "", nil } } } - return false, "", nil + return authorizer.DecisionNoOpinion, "", nil } // NewPrivilegedGroups is for use in loopback scenarios diff --git a/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/builtin_test.go b/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/builtin_test.go new file mode 100644 index 00000000000..4e22b551fa2 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory/builtin_test.go @@ -0,0 +1,52 @@ +/* +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 authorizerfactory + +import ( + "testing" + + "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/apiserver/pkg/authorization/authorizer" +) + +func TestNewAlwaysAllowAuthorizer(t *testing.T) { + aaa := NewAlwaysAllowAuthorizer() + if decision, _, _ := aaa.Authorize(nil); decision != authorizer.DecisionAllow { + t.Errorf("AlwaysAllowAuthorizer.Authorize did not authorize successfully.") + } +} + +func TestNewAlwaysDenyAuthorizer(t *testing.T) { + ada := NewAlwaysDenyAuthorizer() + if decision, _, _ := ada.Authorize(nil); decision == authorizer.DecisionAllow { + t.Errorf("AlwaysDenyAuthorizer.Authorize returned nil instead of error.") + } +} + +func TestPrivilegedGroupAuthorizer(t *testing.T) { + auth := NewPrivilegedGroups("allow-01", "allow-01") + + yes := authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"no", "allow-01"}}} + no := authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"no", "deny-01"}}} + + if authorized, _, _ := auth.Authorize(yes); authorized != authorizer.DecisionAllow { + t.Errorf("failed") + } + if authorized, _, _ := auth.Authorize(no); authorized == authorizer.DecisionAllow { + t.Errorf("failed") + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/authorization/union/BUILD b/staging/src/k8s.io/apiserver/pkg/authorization/union/BUILD index 0614f330cc5..84f5aac6476 100644 --- a/staging/src/k8s.io/apiserver/pkg/authorization/union/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/authorization/union/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["union_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/authorization/union", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library", "//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/authorization/union/union.go b/staging/src/k8s.io/apiserver/pkg/authorization/union/union.go index 367da59d1be..3246e4c0759 100644 --- a/staging/src/k8s.io/apiserver/pkg/authorization/union/union.go +++ b/staging/src/k8s.io/apiserver/pkg/authorization/union/union.go @@ -14,6 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package union implements an authorizer that combines multiple subauthorizer. +// The union authorizer iterates over each subauthorizer and returns the first +// decision that is either an Allow decision or a Deny decision. If a +// subauthorizer returns a NoOpinion, then the union authorizer moves onto the +// next authorizer or, if the subauthorizer was the last authorizer, returns +// NoOpinion as the aggregate decision. I.e. union authorizer creates an +// aggregate decision and supports short-circut allows and denies from +// subauthorizers. package union import ( @@ -33,14 +41,14 @@ func New(authorizationHandlers ...authorizer.Authorizer) authorizer.Authorizer { } // Authorizes against a chain of authorizer.Authorizer objects and returns nil if successful and returns error if unsuccessful -func (authzHandler unionAuthzHandler) Authorize(a authorizer.Attributes) (bool, string, error) { +func (authzHandler unionAuthzHandler) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) { var ( errlist []error reasonlist []string ) for _, currAuthzHandler := range authzHandler { - authorized, reason, err := currAuthzHandler.Authorize(a) + decision, reason, err := currAuthzHandler.Authorize(a) if err != nil { errlist = append(errlist, err) @@ -48,13 +56,15 @@ func (authzHandler unionAuthzHandler) Authorize(a authorizer.Attributes) (bool, if len(reason) != 0 { reasonlist = append(reasonlist, reason) } - if !authorized { - continue + switch decision { + case authorizer.DecisionAllow, authorizer.DecisionDeny: + return decision, reason, err + case authorizer.DecisionNoOpinion: + // continue to the next authorizer } - return true, reason, nil } - return false, strings.Join(reasonlist, "\n"), utilerrors.NewAggregate(errlist) + return authorizer.DecisionNoOpinion, strings.Join(reasonlist, "\n"), utilerrors.NewAggregate(errlist) } // unionAuthzRulesHandler authorizer against a chain of authorizer.RuleResolver diff --git a/staging/src/k8s.io/apiserver/pkg/authorization/union/union_test.go b/staging/src/k8s.io/apiserver/pkg/authorization/union/union_test.go index 96d989fb67b..a6413897915 100644 --- a/staging/src/k8s.io/apiserver/pkg/authorization/union/union_test.go +++ b/staging/src/k8s.io/apiserver/pkg/authorization/union/union_test.go @@ -17,6 +17,7 @@ limitations under the License. package union import ( + "errors" "fmt" "reflect" "testing" @@ -26,49 +27,43 @@ import ( ) type mockAuthzHandler struct { - isAuthorized bool - err error + decision authorizer.Decision + err error } -func (mock *mockAuthzHandler) Authorize(a authorizer.Attributes) (bool, string, error) { - if mock.err != nil { - return false, "", mock.err - } - if !mock.isAuthorized { - return false, "", nil - } - return true, "", nil +func (mock *mockAuthzHandler) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) { + return mock.decision, "", mock.err } func TestAuthorizationSecondPasses(t *testing.T) { - handler1 := &mockAuthzHandler{isAuthorized: false} - handler2 := &mockAuthzHandler{isAuthorized: true} + handler1 := &mockAuthzHandler{decision: authorizer.DecisionNoOpinion} + handler2 := &mockAuthzHandler{decision: authorizer.DecisionAllow} authzHandler := New(handler1, handler2) authorized, _, _ := authzHandler.Authorize(nil) - if !authorized { + if authorized != authorizer.DecisionAllow { t.Errorf("Unexpected authorization failure") } } func TestAuthorizationFirstPasses(t *testing.T) { - handler1 := &mockAuthzHandler{isAuthorized: true} - handler2 := &mockAuthzHandler{isAuthorized: false} + handler1 := &mockAuthzHandler{decision: authorizer.DecisionAllow} + handler2 := &mockAuthzHandler{decision: authorizer.DecisionNoOpinion} authzHandler := New(handler1, handler2) authorized, _, _ := authzHandler.Authorize(nil) - if !authorized { + if authorized != authorizer.DecisionAllow { t.Errorf("Unexpected authorization failure") } } func TestAuthorizationNonePasses(t *testing.T) { - handler1 := &mockAuthzHandler{isAuthorized: false} - handler2 := &mockAuthzHandler{isAuthorized: false} + handler1 := &mockAuthzHandler{decision: authorizer.DecisionNoOpinion} + handler2 := &mockAuthzHandler{decision: authorizer.DecisionNoOpinion} authzHandler := New(handler1, handler2) authorized, _, _ := authzHandler.Authorize(nil) - if authorized { + if authorized == authorizer.DecisionAllow { t.Errorf("Expected failed authorization") } } @@ -223,3 +218,49 @@ func getNonResourceRules(infos []authorizer.NonResourceRuleInfo) []authorizer.De } return rules } + +func TestAuthorizationUnequivocalDeny(t *testing.T) { + cs := []struct { + authorizers []authorizer.Authorizer + decision authorizer.Decision + }{ + { + authorizers: []authorizer.Authorizer{}, + decision: authorizer.DecisionNoOpinion, + }, + { + authorizers: []authorizer.Authorizer{ + &mockAuthzHandler{decision: authorizer.DecisionNoOpinion}, + &mockAuthzHandler{decision: authorizer.DecisionAllow}, + &mockAuthzHandler{decision: authorizer.DecisionDeny}, + }, + decision: authorizer.DecisionAllow, + }, + { + authorizers: []authorizer.Authorizer{ + &mockAuthzHandler{decision: authorizer.DecisionNoOpinion}, + &mockAuthzHandler{decision: authorizer.DecisionDeny}, + &mockAuthzHandler{decision: authorizer.DecisionAllow}, + }, + decision: authorizer.DecisionDeny, + }, + { + authorizers: []authorizer.Authorizer{ + &mockAuthzHandler{decision: authorizer.DecisionNoOpinion}, + &mockAuthzHandler{decision: authorizer.DecisionDeny, err: errors.New("webhook failed closed")}, + &mockAuthzHandler{decision: authorizer.DecisionAllow}, + }, + decision: authorizer.DecisionDeny, + }, + } + for i, c := range cs { + t.Run(fmt.Sprintf("case %v", i), func(t *testing.T) { + authzHandler := New(c.authorizers...) + + decision, _, _ := authzHandler.Authorize(nil) + if decision != c.decision { + t.Errorf("Unexpected authorization failure: %v, expected: %v", decision, c.decision) + } + }) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/BUILD b/staging/src/k8s.io/apiserver/pkg/endpoints/BUILD index f73d619255a..f2c1ce34509 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/BUILD @@ -15,8 +15,8 @@ go_test( "proxy_test.go", "watch_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/endpoints", - library = ":go_default_library", deps = [ "//vendor/github.com/emicklei/go-restful:go_default_library", "//vendor/golang.org/x/net/websocket:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/apiserver_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/apiserver_test.go index f6ac5353ff8..a0cc897aa83 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/apiserver_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/apiserver_test.go @@ -82,13 +82,23 @@ func (alwaysAdmit) Handles(operation admission.Operation) bool { return true } -type alwaysDeny struct{} +type alwaysMutatingDeny struct{} -func (alwaysDeny) Admit(a admission.Attributes) (err error) { - return admission.NewForbidden(a, errors.New("Admission control is denying all modifications")) +func (alwaysMutatingDeny) Admit(a admission.Attributes) (err error) { + return admission.NewForbidden(a, errors.New("Mutating admission control is denying all modifications")) } -func (alwaysDeny) Handles(operation admission.Operation) bool { +func (alwaysMutatingDeny) Handles(operation admission.Operation) bool { + return true +} + +type alwaysValidatingDeny struct{} + +func (alwaysValidatingDeny) Validate(a admission.Attributes) (err error) { + return admission.NewForbidden(a, errors.New("Validating admission control is denying all modifications")) +} + +func (alwaysValidatingDeny) Handles(operation admission.Operation) bool { return true } @@ -258,11 +268,6 @@ func handle(storage map[string]rest.Storage) http.Handler { return handleInternal(storage, admissionControl, selfLinker, nil) } -// tests with a deny admission controller -func handleDeny(storage map[string]rest.Storage) http.Handler { - return handleInternal(storage, alwaysDeny{}, selfLinker, nil) -} - // tests using the new namespace scope mechanism func handleNamespaced(storage map[string]rest.Storage) http.Handler { return handleInternal(storage, admissionControl, selfLinker, nil) @@ -436,6 +441,10 @@ func (storage *SimpleRESTStorage) ConvertToTable(ctx request.Context, obj runtim func (storage *SimpleRESTStorage) List(ctx request.Context, options *metainternalversion.ListOptions) (runtime.Object, error) { storage.checkContext(ctx) result := &genericapitesting.SimpleList{ + ListMeta: metav1.ListMeta{ + ResourceVersion: "10", + SelfLink: "/test/link", + }, Items: storage.list, } storage.requestedLabelSelector = labels.Everything() @@ -519,7 +528,7 @@ func (storage *SimpleRESTStorage) NewList() runtime.Object { return &genericapitesting.SimpleList{} } -func (storage *SimpleRESTStorage) Create(ctx request.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { +func (storage *SimpleRESTStorage) Create(ctx request.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) { storage.checkContext(ctx) storage.created = obj.(*genericapitesting.Simple) if err := storage.errors["create"]; err != nil { @@ -529,10 +538,13 @@ func (storage *SimpleRESTStorage) Create(ctx request.Context, obj runtime.Object if storage.injectedFunction != nil { obj, err = storage.injectedFunction(obj) } + if err := createValidation(obj); err != nil { + return nil, err + } return obj, err } -func (storage *SimpleRESTStorage) Update(ctx request.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { +func (storage *SimpleRESTStorage) Update(ctx request.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { storage.checkContext(ctx) obj, err := objInfo.UpdatedObject(ctx, &storage.item) if err != nil { @@ -545,6 +557,9 @@ func (storage *SimpleRESTStorage) Update(ctx request.Context, name string, objIn if storage.injectedFunction != nil { obj, err = storage.injectedFunction(obj) } + if err := updateValidation(&storage.item, obj); err != nil { + return nil, false, err + } return obj, false, err } @@ -714,7 +729,7 @@ type NamedCreaterRESTStorage struct { createdName string } -func (storage *NamedCreaterRESTStorage) Create(ctx request.Context, name string, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { +func (storage *NamedCreaterRESTStorage) Create(ctx request.Context, name string, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) { storage.checkContext(ctx) storage.created = obj.(*genericapitesting.Simple) storage.createdName = name @@ -725,6 +740,9 @@ func (storage *NamedCreaterRESTStorage) Create(ctx request.Context, name string, if storage.injectedFunction != nil { obj, err = storage.injectedFunction(obj) } + if err := createValidation(obj); err != nil { + return nil, err + } return obj, err } @@ -1818,24 +1836,10 @@ func TestGetPretty(t *testing.T) { func TestGetTable(t *testing.T) { now := metav1.Now() - storage := map[string]rest.Storage{} obj := genericapitesting.Simple{ - ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", CreationTimestamp: now, UID: types.UID("abcdef0123")}, + ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", ResourceVersion: "10", SelfLink: "/blah", CreationTimestamp: now, UID: types.UID("abcdef0123")}, Other: "foo", } - simpleStorage := SimpleRESTStorage{ - item: obj, - } - selfLinker := &setTestSelfLinker{ - t: t, - expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id", - name: "id", - namespace: "default", - } - storage["simple"] = &simpleStorage - handler := handleLinker(storage, selfLinker) - server := httptest.NewServer(handler) - defer server.Close() m, err := meta.Accessor(&obj) if err != nil { @@ -1858,15 +1862,34 @@ func TestGetTable(t *testing.T) { pretty bool expected *metav1alpha1.Table statusCode int + item bool }{ { accept: runtime.ContentTypeJSON + ";as=Table;v=v1;g=meta.k8s.io", statusCode: http.StatusNotAcceptable, }, { + item: true, accept: runtime.ContentTypeJSON + ";as=Table;v=v1alpha1;g=meta.k8s.io", expected: &metav1alpha1.Table{ TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1alpha1"}, + ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"}, + ColumnDefinitions: []metav1alpha1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]}, + {Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]}, + }, + Rows: []metav1alpha1.TableRow{ + {Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBody}}, + }, + }, + }, + { + item: true, + accept: runtime.ContentTypeJSON + ";as=Table;v=v1alpha1;g=meta.k8s.io", + params: url.Values{"includeObject": []string{"Metadata"}}, + expected: &metav1alpha1.Table{ + TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1alpha1"}, + ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"}, ColumnDefinitions: []metav1alpha1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]}, {Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]}, @@ -1881,6 +1904,7 @@ func TestGetTable(t *testing.T) { params: url.Values{"includeObject": []string{"Metadata"}}, expected: &metav1alpha1.Table{ TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1alpha1"}, + ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/test/link"}, ColumnDefinitions: []metav1alpha1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]}, {Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]}, @@ -1892,36 +1916,69 @@ func TestGetTable(t *testing.T) { }, } for i, test := range tests { - u, err := url.Parse(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id") - if err != nil { - t.Fatal(err) - } - u.RawQuery = test.params.Encode() - req := &http.Request{Method: "GET", URL: u} - req.Header = http.Header{} - req.Header.Set("Accept", test.accept) - resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatal(err) - } - if test.statusCode != 0 { - if resp.StatusCode != test.statusCode { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + storage := map[string]rest.Storage{} + simpleStorage := SimpleRESTStorage{ + item: obj, + list: []genericapitesting.Simple{obj}, + } + selfLinker := &setTestSelfLinker{ + t: t, + expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple", + namespace: "default", + } + if test.item { + selfLinker.expectedSet += "/id" + selfLinker.name = "id" + } + storage["simple"] = &simpleStorage + handler := handleLinker(storage, selfLinker) + server := httptest.NewServer(handler) + defer server.Close() + + var id string + if test.item { + id = "/id" + } + u, err := url.Parse(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple" + id) + if err != nil { + t.Fatal(err) + } + u.RawQuery = test.params.Encode() + req := &http.Request{Method: "GET", URL: u} + req.Header = http.Header{} + req.Header.Set("Accept", test.accept) + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatal(err) + } + if test.statusCode != 0 { + if resp.StatusCode != test.statusCode { + t.Errorf("%d: unexpected response: %#v", i, resp) + } + obj, _, err := extractBodyObject(resp, unstructured.UnstructuredJSONScheme) + if err != nil { + t.Fatalf("%d: unexpected body read error: %v", i, err) + } + gvk := schema.GroupVersionKind{Version: "v1", Kind: "Status"} + if obj.GetObjectKind().GroupVersionKind() != gvk { + t.Fatalf("%d: unexpected error body: %#v", i, obj) + } + return + } + if resp.StatusCode != http.StatusOK { t.Errorf("%d: unexpected response: %#v", i, resp) } - continue - } - if resp.StatusCode != http.StatusOK { - t.Errorf("%d: unexpected response: %#v", i, resp) - } - var itemOut metav1alpha1.Table - body, err := extractBody(resp, &itemOut) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(test.expected, &itemOut) { - t.Log(body) - t.Errorf("%d: did not match: %s", i, diff.ObjectReflectDiff(test.expected, &itemOut)) - } + var itemOut metav1alpha1.Table + body, err := extractBody(resp, &itemOut) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(test.expected, &itemOut) { + t.Log(body) + t.Errorf("%d: did not match: %s", i, diff.ObjectReflectDiff(test.expected, &itemOut)) + } + }) } } @@ -1969,11 +2026,29 @@ func TestGetPartialObjectMetadata(t *testing.T) { accept: runtime.ContentTypeJSON + ";as=PartialObjectMetadata;v=v1;g=meta.k8s.io", statusCode: http.StatusNotAcceptable, }, + { + accept: runtime.ContentTypeJSON + ";as=PartialObjectMetadata;v=v1;g=meta.k8s.io, application/json", + expectKind: schema.GroupVersionKind{Kind: "Simple", Group: testGroupVersion.Group, Version: testGroupVersion.Version}, + }, + { + accept: runtime.ContentTypeJSON + ";as=PartialObjectMetadata;v=v1alpha1;g=meta.k8s.io, application/json", + expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1alpha1"}, + }, { list: true, accept: runtime.ContentTypeJSON + ";as=PartialObjectMetadata;v=v1alpha1;g=meta.k8s.io", statusCode: http.StatusNotAcceptable, }, + { + list: true, + accept: runtime.ContentTypeJSON + ";as=PartialObjectMetadata;v=v1;g=meta.k8s.io, application/json", + expectKind: schema.GroupVersionKind{Kind: "SimpleList", Group: testGroupVersion.Group, Version: testGroupVersion.Version}, + }, + { + list: true, + accept: runtime.ContentTypeJSON + ";as=PartialObjectMetadataList;v=v1alpha1;g=meta.k8s.io, application/json", + expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadataList", Group: "meta.k8s.io", Version: "v1alpha1"}, + }, { accept: runtime.ContentTypeJSON + ";as=PartialObjectMetadataList;v=v1alpha1;g=meta.k8s.io", statusCode: http.StatusNotAcceptable, @@ -2024,18 +2099,37 @@ func TestGetPartialObjectMetadata(t *testing.T) { if resp.StatusCode != test.statusCode { t.Errorf("%d: unexpected response: %#v", i, resp) } + obj, _, err := extractBodyObject(resp, unstructured.UnstructuredJSONScheme) + if err != nil { + t.Errorf("%d: unexpected body read error: %v", i, err) + continue + } + gvk := schema.GroupVersionKind{Version: "v1", Kind: "Status"} + if obj.GetObjectKind().GroupVersionKind() != gvk { + t.Errorf("%d: unexpected error body: %#v", i, obj) + } continue } if resp.StatusCode != http.StatusOK { t.Errorf("%d: invalid status: %#v\n%s", i, resp, bodyOrDie(resp)) continue } - itemOut, body, err := extractBodyObject(resp, metainternalversion.Codecs.LegacyCodec(metav1alpha1.SchemeGroupVersion)) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(test.expected, itemOut) { - t.Errorf("%d: did not match: %s", i, diff.ObjectReflectDiff(test.expected, itemOut)) + body := "" + if test.expected != nil { + itemOut, d, err := extractBodyObject(resp, metainternalversion.Codecs.LegacyCodec(metav1alpha1.SchemeGroupVersion)) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(test.expected, itemOut) { + t.Errorf("%d: did not match: %s", i, diff.ObjectReflectDiff(test.expected, itemOut)) + } + body = d + } else { + d, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatal(err) + } + body = string(d) } obj := &unstructured.Unstructured{} if err := json.Unmarshal([]byte(body), obj); err != nil { @@ -2813,22 +2907,27 @@ func TestLegacyDeleteIgnoresOptions(t *testing.T) { } func TestDeleteInvokesAdmissionControl(t *testing.T) { - storage := map[string]rest.Storage{} - simpleStorage := SimpleRESTStorage{} - ID := "id" - storage["simple"] = &simpleStorage - handler := handleDeny(storage) - server := httptest.NewServer(handler) - defer server.Close() + // TODO: remove mutating deny when we removed it from the endpoint implementation and ported all plugins + for _, admit := range []admission.Interface{alwaysMutatingDeny{}, alwaysValidatingDeny{}} { + t.Logf("Testing %T", admit) - client := http.Client{} - request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, nil) - response, err := client.Do(request) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - if response.StatusCode != http.StatusForbidden { - t.Errorf("Unexpected response %#v", response) + storage := map[string]rest.Storage{} + simpleStorage := SimpleRESTStorage{} + ID := "id" + storage["simple"] = &simpleStorage + handler := handleInternal(storage, admit, selfLinker, nil) + server := httptest.NewServer(handler) + defer server.Close() + + client := http.Client{} + request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, nil) + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if response.StatusCode != http.StatusForbidden { + t.Errorf("Unexpected response %#v", response) + } } } @@ -2971,38 +3070,42 @@ func TestUpdate(t *testing.T) { } func TestUpdateInvokesAdmissionControl(t *testing.T) { - storage := map[string]rest.Storage{} - simpleStorage := SimpleRESTStorage{} - ID := "id" - storage["simple"] = &simpleStorage - handler := handleDeny(storage) - server := httptest.NewServer(handler) - defer server.Close() + for _, admit := range []admission.Interface{alwaysMutatingDeny{}, alwaysValidatingDeny{}} { + t.Logf("Testing %T", admit) - item := &genericapitesting.Simple{ - ObjectMeta: metav1.ObjectMeta{ - Name: ID, - Namespace: metav1.NamespaceDefault, - }, - Other: "bar", - } - body, err := runtime.Encode(testCodec, item) - if err != nil { - // The following cases will fail, so die now - t.Fatalf("unexpected error: %v", err) - } + storage := map[string]rest.Storage{} + simpleStorage := SimpleRESTStorage{} + ID := "id" + storage["simple"] = &simpleStorage + handler := handleInternal(storage, admit, selfLinker, nil) + server := httptest.NewServer(handler) + defer server.Close() - client := http.Client{} - request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) - response, err := client.Do(request) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - dump, _ := httputil.DumpResponse(response, true) - t.Log(string(dump)) + item := &genericapitesting.Simple{ + ObjectMeta: metav1.ObjectMeta{ + Name: ID, + Namespace: metav1.NamespaceDefault, + }, + Other: "bar", + } + body, err := runtime.Encode(testCodec, item) + if err != nil { + // The following cases will fail, so die now + t.Fatalf("unexpected error: %v", err) + } - if response.StatusCode != http.StatusForbidden { - t.Errorf("Unexpected response %#v", response) + client := http.Client{} + request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + dump, _ := httputil.DumpResponse(response, true) + t.Log(string(dump)) + + if response.StatusCode != http.StatusForbidden { + t.Errorf("Unexpected response %#v", response) + } } } @@ -3602,49 +3705,53 @@ func TestCreateInNamespace(t *testing.T) { } } -func TestCreateInvokesAdmissionControl(t *testing.T) { - storage := SimpleRESTStorage{ - injectedFunction: func(obj runtime.Object) (runtime.Object, error) { - time.Sleep(5 * time.Millisecond) - return obj, nil - }, - } - selfLinker := &setTestSelfLinker{ - t: t, - name: "bar", - namespace: "other", - expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/foo/bar", - } - handler := handleInternal(map[string]rest.Storage{"foo": &storage}, alwaysDeny{}, selfLinker, nil) - server := httptest.NewServer(handler) - defer server.Close() - client := http.Client{} +func TestCreateInvokeAdmissionControl(t *testing.T) { + for _, admit := range []admission.Interface{alwaysMutatingDeny{}, alwaysValidatingDeny{}} { + t.Logf("Testing %T", admit) - simple := &genericapitesting.Simple{ - Other: "bar", - } - data, err := runtime.Encode(testCodec, simple) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/other/foo", bytes.NewBuffer(data)) - if err != nil { - t.Errorf("unexpected error: %v", err) - } + storage := SimpleRESTStorage{ + injectedFunction: func(obj runtime.Object) (runtime.Object, error) { + time.Sleep(5 * time.Millisecond) + return obj, nil + }, + } + selfLinker := &setTestSelfLinker{ + t: t, + name: "bar", + namespace: "other", + expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/foo/bar", + } + handler := handleInternal(map[string]rest.Storage{"foo": &storage}, admit, selfLinker, nil) + server := httptest.NewServer(handler) + defer server.Close() + client := http.Client{} - wg := sync.WaitGroup{} - wg.Add(1) - var response *http.Response - go func() { - response, err = client.Do(request) - wg.Done() - }() - wg.Wait() - if err != nil { - t.Errorf("unexpected error: %v", err) - } - if response.StatusCode != http.StatusForbidden { - t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusForbidden, response) + simple := &genericapitesting.Simple{ + Other: "bar", + } + data, err := runtime.Encode(testCodec, simple) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/other/foo", bytes.NewBuffer(data)) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + wg := sync.WaitGroup{} + wg.Add(1) + var response *http.Response + go func() { + response, err = client.Do(request) + wg.Done() + }() + wg.Wait() + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if response.StatusCode != http.StatusForbidden { + t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusForbidden, response) + } } } @@ -3867,7 +3974,8 @@ func TestUpdateChecksAPIVersion(t *testing.T) { } type SimpleXGSubresourceRESTStorage struct { - item genericapitesting.SimpleXGSubresource + item genericapitesting.SimpleXGSubresource + itemGVK schema.GroupVersionKind } func (storage *SimpleXGSubresourceRESTStorage) New() runtime.Object { @@ -3878,6 +3986,12 @@ func (storage *SimpleXGSubresourceRESTStorage) Get(ctx request.Context, id strin return storage.item.DeepCopyObject(), nil } +var _ = rest.GroupVersionKindProvider(&SimpleXGSubresourceRESTStorage{}) + +func (storage *SimpleXGSubresourceRESTStorage) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind { + return storage.itemGVK +} + func TestXGSubresource(t *testing.T) { container := restful.NewContainer() container.Router(restful.CurlyRouter{}) @@ -3888,6 +4002,7 @@ func TestXGSubresource(t *testing.T) { item: genericapitesting.SimpleXGSubresource{ SubresourceInfo: "foo", }, + itemGVK: testGroup2Version.WithKind("SimpleXGSubresource"), } storage := map[string]rest.Storage{ "simple": &SimpleRESTStorage{}, @@ -3913,10 +4028,6 @@ func TestXGSubresource(t *testing.T) { GroupVersion: testGroupVersion, OptionsExternalVersion: &testGroupVersion, Serializer: codecs, - - SubresourceGroupVersionKind: map[string]schema.GroupVersionKind{ - "simple/subsimple": testGroup2Version.WithKind("SimpleXGSubresource"), - }, } if err := (&group).InstallREST(container); err != nil { diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/discovery/BUILD b/staging/src/k8s.io/apiserver/pkg/endpoints/discovery/BUILD index 008dcff5679..d3d12009205 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/discovery/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/discovery/BUILD @@ -12,8 +12,8 @@ go_test( "addresses_test.go", "root_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/endpoints/discovery", - library = ":go_default_library", deps = [ "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/BUILD b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/BUILD index 664756aba15..0e64044e688 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/BUILD @@ -17,8 +17,8 @@ go_test( "legacy_audit_test.go", "requestinfo_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/endpoints/filters", - library = ":go_default_library", deps = [ "//vendor/github.com/pborman/uuid:go_default_library", "//vendor/k8s.io/api/authentication/v1:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/authorization.go b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/authorization.go index d244257a04d..f60756b6fd1 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/authorization.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/authorization.go @@ -47,7 +47,7 @@ func WithAuthorization(handler http.Handler, requestContextMapper request.Reques return } authorized, reason, err := a.Authorize(attributes) - if authorized { + if authorized == authorizer.DecisionAllow { handler.ServeHTTP(w, req) return } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation.go b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation.go index 8f67caf86fe..9af292e1cef 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation.go @@ -110,8 +110,8 @@ func WithImpersonation(handler http.Handler, requestContextMapper request.Reques return } - allowed, reason, err := a.Authorize(actingAsAttributes) - if err != nil || !allowed { + decision, reason, err := a.Authorize(actingAsAttributes) + if err != nil || decision != authorizer.DecisionAllow { glog.V(4).Infof("Forbidden: %#v, Reason: %s, Error: %v", req.RequestURI, reason, err) responsewriters.Forbidden(ctx, actingAsAttributes, w, req, reason, s) return diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation_test.go index d43776507ed..814de2a26e6 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation_test.go @@ -35,50 +35,50 @@ import ( type impersonateAuthorizer struct{} -func (impersonateAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) { +func (impersonateAuthorizer) Authorize(a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) { user := a.GetUser() switch { case user.GetName() == "system:admin": - return true, "", nil + return authorizer.DecisionAllow, "", nil case user.GetName() == "tester": - return false, "", fmt.Errorf("works on my machine") + return authorizer.DecisionNoOpinion, "", fmt.Errorf("works on my machine") case user.GetName() == "deny-me": - return false, "denied", nil + return authorizer.DecisionNoOpinion, "denied", nil } if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "wheel" && a.GetVerb() == "impersonate" && a.GetResource() == "users" { - return true, "", nil + return authorizer.DecisionAllow, "", nil } if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "sa-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "serviceaccounts" { - return true, "", nil + return authorizer.DecisionAllow, "", nil } if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "regular-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "users" { - return true, "", nil + return authorizer.DecisionAllow, "", nil } if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "group-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "groups" { - return true, "", nil + return authorizer.DecisionAllow, "", nil } if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "extra-setter-scopes" && a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "scopes" { - return true, "", nil + return authorizer.DecisionAllow, "", nil } if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "extra-setter-particular-scopes" && a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "scopes" && a.GetName() == "scope-a" { - return true, "", nil + return authorizer.DecisionAllow, "", nil } if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "extra-setter-project" && a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "project" { - return true, "", nil + return authorizer.DecisionAllow, "", nil } - return false, "deny by default", nil + return authorizer.DecisionNoOpinion, "deny by default", nil } func TestImpersonationFilter(t *testing.T) { diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/groupversion.go b/staging/src/k8s.io/apiserver/pkg/endpoints/groupversion.go index a60e4563125..4b882375817 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/groupversion.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/groupversion.go @@ -75,12 +75,6 @@ type APIGroupVersion struct { MinRequestTimeout time.Duration - // SubresourceGroupVersionKind contains the GroupVersionKind overrides for each subresource that is - // accessible from this API group version. The GroupVersionKind is that of the external version of - // the subresource. The key of this map should be the path of the subresource. The keys here should - // match the keys in the Storage map above for subresources. - SubresourceGroupVersionKind map[string]schema.GroupVersionKind - // EnableAPIResponseCompression indicates whether API Responses should support compression // if the client requests it via Accept-Encoding EnableAPIResponseCompression bool diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/BUILD b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/BUILD index f90e24c359f..16901d6a6b0 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/BUILD @@ -12,8 +12,8 @@ go_test( "namer_test.go", "rest_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/endpoints/handlers", - library = ":go_default_library", deps = [ "//vendor/github.com/evanphx/json-patch:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", @@ -21,6 +21,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", @@ -37,12 +38,16 @@ go_test( go_library( name = "go_default_library", srcs = [ + "create.go", + "delete.go", "doc.go", + "get.go", "namer.go", "patch.go", "proxy.go", "response.go", "rest.go", + "update.go", "watch.go", ], importpath = "k8s.io/apiserver/pkg/endpoints/handlers", @@ -55,7 +60,6 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1alpha1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/conversion/unstructured:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go new file mode 100644 index 00000000000..dc3560623ea --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go @@ -0,0 +1,168 @@ +/* +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 handlers + +import ( + "fmt" + "net/http" + "time" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/audit" + "k8s.io/apiserver/pkg/endpoints/handlers/negotiation" + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/rest" + utiltrace "k8s.io/apiserver/pkg/util/trace" +) + +func createHandler(r rest.NamedCreater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface, includeName bool) http.HandlerFunc { + return func(w http.ResponseWriter, req *http.Request) { + // For performance tracking purposes. + trace := utiltrace.New("Create " + req.URL.Path) + defer trace.LogIfLong(500 * time.Millisecond) + + // TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer) + timeout := parseTimeout(req.URL.Query().Get("timeout")) + + var ( + namespace, name string + err error + ) + if includeName { + namespace, name, err = scope.Namer.Name(req) + } else { + namespace, err = scope.Namer.Namespace(req) + } + if err != nil { + scope.err(err, w, req) + return + } + + ctx := scope.ContextFunc(req) + ctx = request.WithNamespace(ctx, namespace) + + gv := scope.Kind.GroupVersion() + s, err := negotiation.NegotiateInputSerializer(req, scope.Serializer) + if err != nil { + scope.err(err, w, req) + return + } + decoder := scope.Serializer.DecoderToVersion(s.Serializer, schema.GroupVersion{Group: gv.Group, Version: runtime.APIVersionInternal}) + + body, err := readBody(req) + if err != nil { + scope.err(err, w, req) + return + } + + defaultGVK := scope.Kind + original := r.New() + trace.Step("About to convert to expected version") + obj, gvk, err := decoder.Decode(body, &defaultGVK, original) + if err != nil { + err = transformDecodeError(typer, err, original, gvk, body) + scope.err(err, w, req) + return + } + if gvk.GroupVersion() != gv { + err = errors.NewBadRequest(fmt.Sprintf("the API version in the data (%s) does not match the expected API version (%v)", gvk.GroupVersion().String(), gv.String())) + scope.err(err, w, req) + return + } + trace.Step("Conversion done") + + ae := request.AuditEventFrom(ctx) + audit.LogRequestObject(ae, obj, scope.Resource, scope.Subresource, scope.Serializer) + + userInfo, _ := request.UserFrom(ctx) + admissionAttributes := admission.NewAttributesRecord(obj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, userInfo) + if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Create) { + err = mutatingAdmission.Admit(admissionAttributes) + if err != nil { + scope.err(err, w, req) + return + } + } + + // TODO: replace with content type negotiation? + includeUninitialized := req.URL.Query().Get("includeUninitialized") == "1" + + trace.Step("About to store object in database") + result, err := finishRequest(timeout, func() (runtime.Object, error) { + return r.Create( + ctx, + name, + obj, + rest.AdmissionToValidateObjectFunc(admit, admissionAttributes), + includeUninitialized, + ) + }) + if err != nil { + scope.err(err, w, req) + return + } + trace.Step("Object stored in database") + + requestInfo, ok := request.RequestInfoFrom(ctx) + if !ok { + scope.err(fmt.Errorf("missing requestInfo"), w, req) + return + } + if err := setSelfLink(result, requestInfo, scope.Namer); err != nil { + scope.err(err, w, req) + return + } + trace.Step("Self-link added") + + // If the object is partially initialized, always indicate it via StatusAccepted + code := http.StatusCreated + if accessor, err := meta.Accessor(result); err == nil { + if accessor.GetInitializers() != nil { + code = http.StatusAccepted + } + } + status, ok := result.(*metav1.Status) + if ok && err == nil && status.Code == 0 { + status.Code = int32(code) + } + + transformResponseObject(ctx, scope, req, w, code, result) + } +} + +// CreateNamedResource returns a function that will handle a resource creation with name. +func CreateNamedResource(r rest.NamedCreater, scope RequestScope, typer runtime.ObjectTyper, admission admission.Interface) http.HandlerFunc { + return createHandler(r, scope, typer, admission, true) +} + +// CreateResource returns a function that will handle a resource creation. +func CreateResource(r rest.Creater, scope RequestScope, typer runtime.ObjectTyper, admission admission.Interface) http.HandlerFunc { + return createHandler(&namedCreaterAdapter{r}, scope, typer, admission, false) +} + +type namedCreaterAdapter struct { + rest.Creater +} + +func (c *namedCreaterAdapter) Create(ctx request.Context, name string, obj runtime.Object, createValidatingAdmission rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) { + return c.Creater.Create(ctx, obj, createValidatingAdmission, includeUninitialized) +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/delete.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/delete.go new file mode 100644 index 00000000000..0bc5a659b55 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/delete.go @@ -0,0 +1,282 @@ +/* +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 handlers + +import ( + "fmt" + "net/http" + "time" + + "k8s.io/apimachinery/pkg/api/errors" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/audit" + "k8s.io/apiserver/pkg/endpoints/handlers/negotiation" + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/rest" + utiltrace "k8s.io/apiserver/pkg/util/trace" +) + +// DeleteResource returns a function that will handle a resource deletion +// TODO admission here becomes solely validating admission +func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestScope, admit admission.Interface) http.HandlerFunc { + return func(w http.ResponseWriter, req *http.Request) { + // For performance tracking purposes. + trace := utiltrace.New("Delete " + req.URL.Path) + defer trace.LogIfLong(500 * time.Millisecond) + + // TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer) + timeout := parseTimeout(req.URL.Query().Get("timeout")) + + namespace, name, err := scope.Namer.Name(req) + if err != nil { + scope.err(err, w, req) + return + } + ctx := scope.ContextFunc(req) + ctx = request.WithNamespace(ctx, namespace) + + options := &metav1.DeleteOptions{} + if allowsOptions { + body, err := readBody(req) + if err != nil { + scope.err(err, w, req) + return + } + if len(body) > 0 { + s, err := negotiation.NegotiateInputSerializer(req, metainternalversion.Codecs) + if err != nil { + scope.err(err, w, req) + return + } + // For backwards compatibility, we need to allow existing clients to submit per group DeleteOptions + // It is also allowed to pass a body with meta.k8s.io/v1.DeleteOptions + defaultGVK := scope.MetaGroupVersion.WithKind("DeleteOptions") + obj, _, err := metainternalversion.Codecs.DecoderToVersion(s.Serializer, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, options) + if err != nil { + scope.err(err, w, req) + return + } + if obj != options { + scope.err(fmt.Errorf("decoded object cannot be converted to DeleteOptions"), w, req) + return + } + trace.Step("Decoded delete options") + + ae := request.AuditEventFrom(ctx) + audit.LogRequestObject(ae, obj, scope.Resource, scope.Subresource, scope.Serializer) + trace.Step("Recorded the audit event") + } else { + if values := req.URL.Query(); len(values) > 0 { + if err := metainternalversion.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, options); err != nil { + err = errors.NewBadRequest(err.Error()) + scope.err(err, w, req) + return + } + } + } + } + + trace.Step("About to check admission control") + if admit != nil && admit.Handles(admission.Delete) { + userInfo, _ := request.UserFrom(ctx) + attrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, userInfo) + if mutatingAdmission, ok := admit.(admission.MutationInterface); ok { + if err := mutatingAdmission.Admit(attrs); err != nil { + scope.err(err, w, req) + return + } + } + if validatingAdmission, ok := admit.(admission.ValidationInterface); ok { + if err := validatingAdmission.Validate(attrs); err != nil { + scope.err(err, w, req) + return + } + } + } + + trace.Step("About to delete object from database") + wasDeleted := true + result, err := finishRequest(timeout, func() (runtime.Object, error) { + obj, deleted, err := r.Delete(ctx, name, options) + wasDeleted = deleted + return obj, err + }) + if err != nil { + scope.err(err, w, req) + return + } + trace.Step("Object deleted from database") + + status := http.StatusOK + // Return http.StatusAccepted if the resource was not deleted immediately and + // user requested cascading deletion by setting OrphanDependents=false. + // Note: We want to do this always if resource was not deleted immediately, but + // that will break existing clients. + // Other cases where resource is not instantly deleted are: namespace deletion + // and pod graceful deletion. + if !wasDeleted && options.OrphanDependents != nil && *options.OrphanDependents == false { + status = http.StatusAccepted + } + // if the rest.Deleter returns a nil object, fill out a status. Callers may return a valid + // object with the response. + if result == nil { + result = &metav1.Status{ + Status: metav1.StatusSuccess, + Code: int32(status), + Details: &metav1.StatusDetails{ + Name: name, + Kind: scope.Kind.Kind, + }, + } + } else { + // when a non-status response is returned, set the self link + requestInfo, ok := request.RequestInfoFrom(ctx) + if !ok { + scope.err(fmt.Errorf("missing requestInfo"), w, req) + return + } + if _, ok := result.(*metav1.Status); !ok { + if err := setSelfLink(result, requestInfo, scope.Namer); err != nil { + scope.err(err, w, req) + return + } + } + } + + transformResponseObject(ctx, scope, req, w, status, result) + } +} + +// DeleteCollection returns a function that will handle a collection deletion +func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestScope, admit admission.Interface) http.HandlerFunc { + return func(w http.ResponseWriter, req *http.Request) { + // TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer) + timeout := parseTimeout(req.URL.Query().Get("timeout")) + + namespace, err := scope.Namer.Namespace(req) + if err != nil { + scope.err(err, w, req) + return + } + + ctx := scope.ContextFunc(req) + ctx = request.WithNamespace(ctx, namespace) + + if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Delete) { + userInfo, _ := request.UserFrom(ctx) + + err = mutatingAdmission.Admit(admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, "", scope.Resource, scope.Subresource, admission.Delete, userInfo)) + if err != nil { + scope.err(err, w, req) + return + } + } + // TODO: avoid calling Handles twice + if validatingAdmission, ok := admit.(admission.ValidationInterface); ok && validatingAdmission.Handles(admission.Delete) { + userInfo, _ := request.UserFrom(ctx) + + err = validatingAdmission.Validate(admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, "", scope.Resource, scope.Subresource, admission.Delete, userInfo)) + if err != nil { + scope.err(err, w, req) + return + } + } + + listOptions := metainternalversion.ListOptions{} + if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, &listOptions); err != nil { + err = errors.NewBadRequest(err.Error()) + scope.err(err, w, req) + return + } + + // transform fields + // TODO: DecodeParametersInto should do this. + if listOptions.FieldSelector != nil { + fn := func(label, value string) (newLabel, newValue string, err error) { + return scope.Convertor.ConvertFieldLabel(scope.Kind.GroupVersion().String(), scope.Kind.Kind, label, value) + } + if listOptions.FieldSelector, err = listOptions.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 + } + } + + options := &metav1.DeleteOptions{} + if checkBody { + body, err := readBody(req) + if err != nil { + scope.err(err, w, req) + return + } + if len(body) > 0 { + s, err := negotiation.NegotiateInputSerializer(req, scope.Serializer) + if err != nil { + scope.err(err, w, req) + return + } + defaultGVK := scope.Kind.GroupVersion().WithKind("DeleteOptions") + obj, _, err := scope.Serializer.DecoderToVersion(s.Serializer, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, options) + if err != nil { + scope.err(err, w, req) + return + } + if obj != options { + scope.err(fmt.Errorf("decoded object cannot be converted to DeleteOptions"), w, req) + return + } + + ae := request.AuditEventFrom(ctx) + audit.LogRequestObject(ae, obj, scope.Resource, scope.Subresource, scope.Serializer) + } + } + + result, err := finishRequest(timeout, func() (runtime.Object, error) { + return r.DeleteCollection(ctx, options, &listOptions) + }) + if err != nil { + scope.err(err, w, req) + return + } + + // if the rest.Deleter returns a nil object, fill out a status. Callers may return a valid + // object with the response. + if result == nil { + result = &metav1.Status{ + Status: metav1.StatusSuccess, + Code: http.StatusOK, + Details: &metav1.StatusDetails{ + Kind: scope.Kind.Kind, + }, + } + } else { + // when a non-status response is returned, set the self link + if _, ok := result.(*metav1.Status); !ok { + if _, err := setListSelfLink(result, ctx, req, scope.Namer); err != nil { + scope.err(err, w, req) + return + } + } + } + + transformResponseObject(ctx, scope, req, w, http.StatusOK, result) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/get.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/get.go new file mode 100644 index 00000000000..7461ece64c2 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/get.go @@ -0,0 +1,278 @@ +/* +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 handlers + +import ( + "fmt" + "math/rand" + "net/http" + "net/url" + "strings" + "time" + + "github.com/golang/glog" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/endpoints/metrics" + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/rest" + utiltrace "k8s.io/apiserver/pkg/util/trace" +) + +// getterFunc performs a get request with the given context and object name. The request +// may be used to deserialize an options object to pass to the getter. +type getterFunc func(ctx request.Context, name string, req *http.Request, trace *utiltrace.Trace) (runtime.Object, error) + +// getResourceHandler is an HTTP handler function for get requests. It delegates to the +// passed-in getterFunc to perform the actual get. +func getResourceHandler(scope RequestScope, getter getterFunc) http.HandlerFunc { + return func(w http.ResponseWriter, req *http.Request) { + trace := utiltrace.New("Get " + req.URL.Path) + defer trace.LogIfLong(500 * time.Millisecond) + + namespace, name, err := scope.Namer.Name(req) + if err != nil { + scope.err(err, w, req) + return + } + ctx := scope.ContextFunc(req) + ctx = request.WithNamespace(ctx, namespace) + + result, err := getter(ctx, name, req, trace) + if err != nil { + scope.err(err, w, req) + return + } + requestInfo, ok := request.RequestInfoFrom(ctx) + if !ok { + scope.err(fmt.Errorf("missing requestInfo"), w, req) + return + } + if err := setSelfLink(result, requestInfo, scope.Namer); err != nil { + scope.err(err, w, req) + return + } + + trace.Step("About to write a response") + transformResponseObject(ctx, scope, req, w, http.StatusOK, result) + } +} + +// GetResource returns a function that handles retrieving a single resource from a rest.Storage object. +func GetResource(r rest.Getter, e rest.Exporter, scope RequestScope) http.HandlerFunc { + return getResourceHandler(scope, + func(ctx request.Context, name string, req *http.Request, trace *utiltrace.Trace) (runtime.Object, error) { + // check for export + options := metav1.GetOptions{} + if values := req.URL.Query(); len(values) > 0 { + exports := metav1.ExportOptions{} + if err := metainternalversion.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, &exports); err != nil { + err = errors.NewBadRequest(err.Error()) + return nil, err + } + if exports.Export { + if e == nil { + return nil, errors.NewBadRequest(fmt.Sprintf("export of %q is not supported", scope.Resource.Resource)) + } + return e.Export(ctx, name, exports) + } + if err := metainternalversion.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, &options); err != nil { + err = errors.NewBadRequest(err.Error()) + return nil, err + } + } + if trace != nil { + trace.Step("About to Get from storage") + } + return r.Get(ctx, name, &options) + }) +} + +// GetResourceWithOptions returns a function that handles retrieving a single resource from a rest.Storage object. +func GetResourceWithOptions(r rest.GetterWithOptions, scope RequestScope, isSubresource bool) http.HandlerFunc { + return getResourceHandler(scope, + func(ctx request.Context, name string, req *http.Request, trace *utiltrace.Trace) (runtime.Object, error) { + opts, subpath, subpathKey := r.NewGetOptions() + trace.Step("About to process Get options") + if err := getRequestOptions(req, scope, opts, subpath, subpathKey, isSubresource); err != nil { + err = errors.NewBadRequest(err.Error()) + return nil, err + } + if trace != nil { + trace.Step("About to Get from storage") + } + return r.Get(ctx, name, opts) + }) +} + +// getRequestOptions parses out options and can include path information. The path information shouldn't include the subresource. +func getRequestOptions(req *http.Request, scope RequestScope, into runtime.Object, subpath bool, subpathKey string, isSubresource bool) error { + if into == nil { + return nil + } + + query := req.URL.Query() + if subpath { + newQuery := make(url.Values) + for k, v := range query { + newQuery[k] = v + } + + ctx := scope.ContextFunc(req) + requestInfo, _ := request.RequestInfoFrom(ctx) + startingIndex := 2 + if isSubresource { + startingIndex = 3 + } + + p := strings.Join(requestInfo.Parts[startingIndex:], "/") + + // ensure non-empty subpaths correctly reflect a leading slash + if len(p) > 0 && !strings.HasPrefix(p, "/") { + p = "/" + p + } + + // ensure subpaths correctly reflect the presence of a trailing slash on the original request + if strings.HasSuffix(requestInfo.Path, "/") && !strings.HasSuffix(p, "/") { + p += "/" + } + + newQuery[subpathKey] = []string{p} + query = newQuery + } + return scope.ParameterCodec.DecodeParameters(query, scope.Kind.GroupVersion(), into) +} + +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) { + // For performance tracking purposes. + trace := utiltrace.New("List " + req.URL.Path) + + 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 := scope.ContextFunc(req) + ctx = request.WithNamespace(ctx, namespace) + + opts := metainternalversion.ListOptions{} + if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, &opts); err != nil { + err = errors.NewBadRequest(err.Error()) + 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.GroupVersion().String(), scope.Kind.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 + } + } + + 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) + if opts.FieldSelector != nil && !opts.FieldSelector.Empty() { + // It doesn't make sense to ask for both a name + // and a field selector, since just the name is + // sufficient to narrow down the request to a + // single object. + scope.err(errors.NewBadRequest("both a name and a field selector provided; please provide one or the other."), w, req) + return + } + opts.FieldSelector = nameSelector + } + + 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)) + } + glog.V(2).Infof("Starting watch for %s, rv=%s labels=%s fields=%s timeout=%s", req.URL.Path, opts.ResourceVersion, opts.LabelSelector, opts.FieldSelector, timeout) + + watcher, err := rw.Watch(ctx, &opts) + if err != nil { + scope.err(err, w, req) + return + } + requestInfo, _ := request.RequestInfoFrom(ctx) + metrics.RecordLongRunning(req, requestInfo, func() { + serveWatch(watcher, scope, req, w, timeout) + }) + return + } + + // Log only long List requests (ignore Watch). + defer trace.LogIfLong(500 * time.Millisecond) + trace.Step("About to List from storage") + result, err := r.List(ctx, &opts) + if err != nil { + scope.err(err, w, req) + return + } + trace.Step("Listing from storage done") + numberOfItems, err := setListSelfLink(result, ctx, req, scope.Namer) + if err != nil { + scope.err(err, w, req) + return + } + trace.Step("Self-linking done") + // Ensure empty lists return a non-nil items slice + if numberOfItems == 0 && meta.IsListType(result) { + if err := meta.SetList(result, []runtime.Object{}); err != nil { + scope.err(err, w, req) + return + } + } + + transformResponseObject(ctx, scope, req, w, http.StatusOK, result) + trace.Step(fmt.Sprintf("Writing http response done (%d items)", numberOfItems)) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/BUILD b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/BUILD index c0ceb323d8d..936a35639d0 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/BUILD @@ -9,8 +9,8 @@ load( go_test( name = "go_default_test", srcs = ["negotiate_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/endpoints/handlers/negotiation", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/negotiate.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/negotiate.go index 8f8a50fe3e7..7f4225a5b93 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/negotiate.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/negotiate.go @@ -273,6 +273,13 @@ func acceptMediaTypeOptions(params map[string]string, accepts *AcceptedMediaType return options, true } +type candidateMediaType struct { + accepted *AcceptedMediaType + clauses goautoneg.Accept +} + +type candidateMediaTypeSlice []candidateMediaType + // NegotiateMediaTypeOptions returns the most appropriate content type given the accept header and // a list of alternatives along with the accepted media type parameters. func NegotiateMediaTypeOptions(header string, accepted []AcceptedMediaType, endpoint EndpointRestrictions) (MediaTypeOptions, bool) { @@ -282,6 +289,7 @@ func NegotiateMediaTypeOptions(header string, accepted []AcceptedMediaType, endp }, true } + var candidates candidateMediaTypeSlice clauses := goautoneg.ParseAccept(header) for _, clause := range clauses { for i := range accepted { @@ -290,12 +298,17 @@ func NegotiateMediaTypeOptions(header string, accepted []AcceptedMediaType, endp case clause.Type == accepts.Type && clause.SubType == accepts.SubType, clause.Type == accepts.Type && clause.SubType == "*", clause.Type == "*" && clause.SubType == "*": - // TODO: should we prefer the first type with no unrecognized options? Do we need to ignore unrecognized - // parameters. - return acceptMediaTypeOptions(clause.Params, accepts, endpoint) + candidates = append(candidates, candidateMediaType{accepted: accepts, clauses: clause}) } } } + + for _, v := range candidates { + if retVal, ret := acceptMediaTypeOptions(v.clauses.Params, v.accepted, endpoint); ret { + return retVal, true + } + } + return MediaTypeOptions{}, false } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/negotiate_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/negotiate_test.go index 8a747ff73dc..1d11d0a3048 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/negotiate_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/negotiate_test.go @@ -181,7 +181,26 @@ func TestNegotiate(t *testing.T) { serializer: fakeCodec, params: map[string]string{"pretty": "1"}, }, - + { + req: &http.Request{ + Header: http.Header{ + "Accept": []string{"application/json;as=BOGUS;v=v1alpha1;g=meta.k8s.io, application/json"}, + }, + }, + contentType: "application/json", + ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json"}}, + serializer: fakeCodec, + }, + { + req: &http.Request{ + Header: http.Header{ + "Accept": []string{"application/BOGUS, application/json"}, + }, + }, + contentType: "application/json", + ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json"}}, + serializer: fakeCodec, + }, // "application" is not a valid media type, so the server will reject the response during // negotiation (the server, in error, has specified an invalid media type) { diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go index ca585e6ddf4..1ac736d09dd 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go @@ -18,16 +18,353 @@ package handlers import ( "fmt" - - "k8s.io/apimachinery/pkg/conversion/unstructured" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/json" - "k8s.io/apimachinery/pkg/util/strategicpatch" + "net/http" + "strings" + "time" "github.com/evanphx/json-patch" + "github.com/golang/glog" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/json" + "k8s.io/apimachinery/pkg/util/mergepatch" + "k8s.io/apimachinery/pkg/util/strategicpatch" + "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/audit" + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/rest" ) +// PatchResource returns a function that will handle a resource patch +// TODO: Eventually PatchResource should just use GuaranteedUpdate and this routine should be a bit cleaner +func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface, converter runtime.ObjectConvertor) http.HandlerFunc { + return func(w http.ResponseWriter, req *http.Request) { + // TODO: we either want to remove timeout or document it (if we + // document, move timeout out of this function and declare it in + // api_installer) + timeout := parseTimeout(req.URL.Query().Get("timeout")) + + namespace, name, err := scope.Namer.Name(req) + if err != nil { + scope.err(err, w, req) + return + } + + ctx := scope.ContextFunc(req) + ctx = request.WithNamespace(ctx, namespace) + + versionedObj, err := converter.ConvertToVersion(r.New(), scope.Kind.GroupVersion()) + if err != nil { + scope.err(err, w, req) + return + } + + // TODO: handle this in negotiation + contentType := req.Header.Get("Content-Type") + // Remove "; charset=" if included in header. + if idx := strings.Index(contentType, ";"); idx > 0 { + contentType = contentType[:idx] + } + patchType := types.PatchType(contentType) + + patchJS, err := readBody(req) + if err != nil { + scope.err(err, w, req) + return + } + + ae := request.AuditEventFrom(ctx) + audit.LogRequestPatch(ae, patchJS) + + s, ok := runtime.SerializerInfoForMediaType(scope.Serializer.SupportedMediaTypes(), runtime.ContentTypeJSON) + if !ok { + scope.err(fmt.Errorf("no serializer defined for JSON"), w, req) + return + } + gv := scope.Kind.GroupVersion() + codec := runtime.NewCodec( + scope.Serializer.EncoderForVersion(s.Serializer, gv), + scope.Serializer.DecoderToVersion(s.Serializer, schema.GroupVersion{Group: gv.Group, Version: runtime.APIVersionInternal}), + ) + + userInfo, _ := request.UserFrom(ctx) + staticAdmissionAttributes := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo) + updateMutation := func(updatedObject runtime.Object, currentObject runtime.Object) error { + if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && admit.Handles(admission.Update) { + return mutatingAdmission.Admit(admission.NewAttributesRecord(updatedObject, currentObject, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo)) + } + return nil + } + + result, err := patchResource( + ctx, + updateMutation, + rest.AdmissionToValidateObjectFunc(admit, staticAdmissionAttributes), + rest.AdmissionToValidateObjectUpdateFunc(admit, staticAdmissionAttributes), + timeout, versionedObj, + r, + name, + patchType, + patchJS, + scope.Namer, scope.Creater, scope.Defaulter, scope.UnsafeConvertor, scope.Kind, scope.Resource, codec) + if err != nil { + scope.err(err, w, req) + return + } + + requestInfo, ok := request.RequestInfoFrom(ctx) + if !ok { + scope.err(fmt.Errorf("missing requestInfo"), w, req) + return + } + if err := setSelfLink(result, requestInfo, scope.Namer); err != nil { + scope.err(err, w, req) + return + } + + transformResponseObject(ctx, scope, req, w, http.StatusOK, result) + } +} + +type mutateObjectUpdateFunc func(obj, old runtime.Object) error + +// patchResource divides PatchResource for easier unit testing +func patchResource( + ctx request.Context, + updateMutation mutateObjectUpdateFunc, + createValidation rest.ValidateObjectFunc, + updateValidation rest.ValidateObjectUpdateFunc, + timeout time.Duration, + versionedObj runtime.Object, + patcher rest.Patcher, + name string, + patchType types.PatchType, + patchJS []byte, + namer ScopeNamer, + creater runtime.ObjectCreater, + defaulter runtime.ObjectDefaulter, + unsafeConvertor runtime.ObjectConvertor, + kind schema.GroupVersionKind, + resource schema.GroupVersionResource, + codec runtime.Codec, +) (runtime.Object, error) { + + namespace := request.NamespaceValue(ctx) + + var ( + originalObjJS []byte + originalPatchedObjJS []byte + originalObjMap map[string]interface{} + getOriginalPatchMap func() (map[string]interface{}, error) + lastConflictErr error + originalResourceVersion string + ) + + // applyPatch is called every time GuaranteedUpdate asks for the updated object, + // and is given the currently persisted object as input. + applyPatch := func(_ request.Context, _, currentObject runtime.Object) (runtime.Object, error) { + // Make sure we actually have a persisted currentObject + if hasUID, err := hasUID(currentObject); err != nil { + return nil, err + } else if !hasUID { + return nil, errors.NewNotFound(resource.GroupResource(), name) + } + + currentResourceVersion := "" + if currentMetadata, err := meta.Accessor(currentObject); err == nil { + currentResourceVersion = currentMetadata.GetResourceVersion() + } + + switch { + case originalObjJS == nil && originalObjMap == nil: + // first time through, + // 1. apply the patch + // 2. save the original and patched to detect whether there were conflicting changes on retries + + originalResourceVersion = currentResourceVersion + objToUpdate := patcher.New() + + // For performance reasons, in case of strategicpatch, we avoid json + // marshaling and unmarshaling and operate just on map[string]interface{}. + // In case of other patch types, we still have to operate on JSON + // representations. + switch patchType { + case types.JSONPatchType, types.MergePatchType: + originalJS, patchedJS, err := patchObjectJSON(patchType, codec, currentObject, patchJS, objToUpdate, versionedObj) + if err != nil { + return nil, interpretPatchError(err) + } + originalObjJS, originalPatchedObjJS = originalJS, patchedJS + + // Make a getter that can return a fresh strategic patch map if needed for conflict retries + // We have to rebuild it each time we need it, because the map gets mutated when being applied + var originalPatchBytes []byte + getOriginalPatchMap = func() (map[string]interface{}, error) { + if originalPatchBytes == nil { + // Compute once + originalPatchBytes, err = strategicpatch.CreateTwoWayMergePatch(originalObjJS, originalPatchedObjJS, versionedObj) + if err != nil { + return nil, interpretPatchError(err) + } + } + // Return a fresh map every time + originalPatchMap := make(map[string]interface{}) + if err := json.Unmarshal(originalPatchBytes, &originalPatchMap); err != nil { + return nil, errors.NewBadRequest(err.Error()) + } + return originalPatchMap, nil + } + + case types.StrategicMergePatchType: + // Since the patch is applied on versioned objects, we need to convert the + // current object to versioned representation first. + currentVersionedObject, err := unsafeConvertor.ConvertToVersion(currentObject, kind.GroupVersion()) + if err != nil { + return nil, err + } + versionedObjToUpdate, err := creater.New(kind) + if err != nil { + return nil, err + } + // Capture the original object map and patch for possible retries. + originalMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(currentVersionedObject) + if err != nil { + return nil, err + } + if err := strategicPatchObject(codec, defaulter, currentVersionedObject, patchJS, versionedObjToUpdate, versionedObj); err != nil { + return nil, err + } + // Convert the object back to unversioned. + gvk := kind.GroupKind().WithVersion(runtime.APIVersionInternal) + unversionedObjToUpdate, err := unsafeConvertor.ConvertToVersion(versionedObjToUpdate, gvk.GroupVersion()) + if err != nil { + return nil, err + } + objToUpdate = unversionedObjToUpdate + // Store unstructured representation for possible retries. + originalObjMap = originalMap + // Make a getter that can return a fresh strategic patch map if needed for conflict retries + // We have to rebuild it each time we need it, because the map gets mutated when being applied + getOriginalPatchMap = func() (map[string]interface{}, error) { + patchMap := make(map[string]interface{}) + if err := json.Unmarshal(patchJS, &patchMap); err != nil { + return nil, errors.NewBadRequest(err.Error()) + } + return patchMap, nil + } + } + if err := checkName(objToUpdate, name, namespace, namer); err != nil { + return nil, err + } + return objToUpdate, nil + + default: + // on a conflict, + // 1. build a strategic merge patch from originalJS and the patchedJS. Different patch types can + // be specified, but a strategic merge patch should be expressive enough handle them. Build the + // patch with this type to handle those cases. + // 2. build a strategic merge patch from originalJS and the currentJS + // 3. ensure no conflicts between the two patches + // 4. apply the #1 patch to the currentJS object + + // Since the patch is applied on versioned objects, we need to convert the + // current object to versioned representation first. + currentVersionedObject, err := unsafeConvertor.ConvertToVersion(currentObject, kind.GroupVersion()) + if err != nil { + return nil, err + } + currentObjMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(currentVersionedObject) + if err != nil { + return nil, err + } + + var currentPatchMap map[string]interface{} + if originalObjMap != nil { + var err error + currentPatchMap, err = strategicpatch.CreateTwoWayMergeMapPatch(originalObjMap, currentObjMap, versionedObj) + if err != nil { + return nil, interpretPatchError(err) + } + } else { + // Compute current patch. + currentObjJS, err := runtime.Encode(codec, currentObject) + if err != nil { + return nil, err + } + currentPatch, err := strategicpatch.CreateTwoWayMergePatch(originalObjJS, currentObjJS, versionedObj) + if err != nil { + return nil, interpretPatchError(err) + } + currentPatchMap = make(map[string]interface{}) + if err := json.Unmarshal(currentPatch, ¤tPatchMap); err != nil { + return nil, errors.NewBadRequest(err.Error()) + } + } + + // Get a fresh copy of the original strategic patch each time through, since applying it mutates the map + originalPatchMap, err := getOriginalPatchMap() + if err != nil { + return nil, err + } + + hasConflicts, err := mergepatch.HasConflicts(originalPatchMap, currentPatchMap) + if err != nil { + return nil, err + } + + if hasConflicts { + diff1, _ := json.Marshal(currentPatchMap) + diff2, _ := json.Marshal(originalPatchMap) + patchDiffErr := fmt.Errorf("there is a meaningful conflict (firstResourceVersion: %q, currentResourceVersion: %q):\n diff1=%v\n, diff2=%v\n", originalResourceVersion, currentResourceVersion, string(diff1), string(diff2)) + glog.V(4).Infof("patchResource failed for resource %s, because there is a meaningful conflict(firstResourceVersion: %q, currentResourceVersion: %q):\n diff1=%v\n, diff2=%v\n", name, originalResourceVersion, currentResourceVersion, string(diff1), string(diff2)) + + // Return the last conflict error we got if we have one + if lastConflictErr != nil { + return nil, lastConflictErr + } + // Otherwise manufacture one of our own + return nil, errors.NewConflict(resource.GroupResource(), name, patchDiffErr) + } + + versionedObjToUpdate, err := creater.New(kind) + if err != nil { + return nil, err + } + if err := applyPatchToObject(codec, defaulter, currentObjMap, originalPatchMap, versionedObjToUpdate, versionedObj); err != nil { + return nil, err + } + // Convert the object back to unversioned. + gvk := kind.GroupKind().WithVersion(runtime.APIVersionInternal) + objToUpdate, err := unsafeConvertor.ConvertToVersion(versionedObjToUpdate, gvk.GroupVersion()) + if err != nil { + return nil, err + } + + return objToUpdate, nil + } + } + + // applyAdmission is called every time GuaranteedUpdate asks for the updated object, + // and is given the currently persisted object and the patched object as input. + applyAdmission := func(ctx request.Context, patchedObject runtime.Object, currentObject runtime.Object) (runtime.Object, error) { + return patchedObject, updateMutation(patchedObject, currentObject) + } + updatedObjectInfo := rest.DefaultUpdatedObjectInfo(nil, applyPatch, applyAdmission) + + return finishRequest(timeout, func() (runtime.Object, error) { + updateObject, _, updateErr := patcher.Update(ctx, name, updatedObjectInfo, createValidation, updateValidation) + for i := 0; i < MaxRetryWhenPatchConflicts && (errors.IsConflict(updateErr)); i++ { + lastConflictErr = updateErr + updateObject, _, updateErr = patcher.Update(ctx, name, updatedObjectInfo, createValidation, updateValidation) + } + return updateObject, updateErr + }) +} + // patchObjectJSON patches the with and stores // the result in . // Currently it also returns the original and patched objects serialized to @@ -87,14 +424,14 @@ func strategicPatchObject( objToUpdate runtime.Object, versionedObj runtime.Object, ) error { - originalObjMap, err := unstructured.DefaultConverter.ToUnstructured(originalObject) + originalObjMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(originalObject) if err != nil { return err } patchMap := make(map[string]interface{}) if err := json.Unmarshal(patchJS, &patchMap); err != nil { - return err + return errors.NewBadRequest(err.Error()) } if err := applyPatchToObject(codec, defaulter, originalObjMap, patchMap, objToUpdate, versionedObj); err != nil { @@ -116,11 +453,11 @@ func applyPatchToObject( ) error { patchedObjMap, err := strategicpatch.StrategicMergeMapPatch(originalMap, patchMap, versionedObj) if err != nil { - return err + return interpretPatchError(err) } // Rather than serialize the patched map to JSON, then decode it to an object, we go directly from a map to an object - if err := unstructured.DefaultConverter.FromUnstructured(patchedObjMap, objToUpdate); err != nil { + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(patchedObjMap, objToUpdate); err != nil { return err } // Decoding from JSON to a versioned object would apply defaults, so we do the same here @@ -128,3 +465,15 @@ func applyPatchToObject( return nil } + +// interpretPatchError interprets the error type and returns an error with appropriate HTTP code. +func interpretPatchError(err error) error { + switch err { + case mergepatch.ErrBadJSONDoc, mergepatch.ErrBadPatchFormatForPrimitiveList, mergepatch.ErrBadPatchFormatForRetainKeys, mergepatch.ErrBadPatchFormatForSetElementOrderList, mergepatch.ErrUnsupportedStrategicMergePatchFormat: + return errors.NewBadRequest(err.Error()) + case mergepatch.ErrNoListOfLists, mergepatch.ErrPatchContentNotMatchRetainKeys: + return errors.NewGenericServerResponse(http.StatusUnprocessableEntity, "", schema.GroupResource{}, "", err.Error(), 0, false) + default: + return err + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/BUILD b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/BUILD index 31963fd3b6d..a1ee2eda2f4 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/BUILD @@ -12,8 +12,8 @@ go_test( "errors_test.go", "status_test.go", ], + embed = [":go_default_library"], importpath = "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters", - library = ":go_default_library", deps = [ "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/errors_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/errors_test.go index 3496f6c8ec1..0dcf7adf97f 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/errors_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/errors_test.go @@ -68,15 +68,15 @@ func TestForbidden(t *testing.T) { reason string contentType string }{ - {`{"metadata":{},"status":"Failure","message":"forbidden: User \"NAME\" cannot GET path \"/whatever\"","reason":"Forbidden","details":{},"code":403} + {`{"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"forbidden: User \"NAME\" cannot GET path \"/whatever\"","reason":"Forbidden","details":{},"code":403} `, authorizer.AttributesRecord{User: u, Verb: "GET", Path: "/whatever"}, "", "application/json"}, - {`{"metadata":{},"status":"Failure","message":"forbidden: User \"NAME\" cannot GET path \"/\u0026lt;script\u0026gt;\"","reason":"Forbidden","details":{},"code":403} + {`{"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"forbidden: User \"NAME\" cannot GET path \"/\u0026lt;script\u0026gt;\"","reason":"Forbidden","details":{},"code":403} `, authorizer.AttributesRecord{User: u, Verb: "GET", Path: "/